メモ置き場

メモ置き場です.開発したものや調べたことについて書きます.

[tex: ]

ZynqでDevice Tree Overlayを使う

qiita.com

ikwzmさんの記事を参考にZynqでDevice Tree Overlayを使ってみた.
Device Tree Overlayとは,Linux kernelを起動したままdevice treeを新たに追加することのできる機能である.

なぜ必要?

Zynqを使って開発を進めていくと,PLの構成を変更すること多々出てくる.変更に合わせデバイスツリーも変更しなければならないが,そのたびに一々再起動するのは面倒である.
Device Tree Overlayの機能を使って,動的にDevice Treeを変更すればすごく楽になる.

この目的を達成するには,PSでLinuxが立ち上がった後にPLの構成を書き換える機能が必要になってくる.そちらについては後日まとめる.

Linux Kernelのビルドとkernel packageの作成

Device Tree Overlayの機能がONになったkernelを作成する必要がある.
https://github.com/ikwzm/FPGA-SoC-Linux
こちらにkernelビルド用のconfigurationがあるのでお借りする.kernel sourceはこの記事を参考に取ってきておく.

ビルド準備

$ git clone git@github.com:ikwzm/FPGA-SoC-Linux.git
$ cd stable-linux

FPGA-SoC-Linux/scrips/build-linux-kernel.shにビルド用のスクリプトがあるが,中身に沿って実行していってみる.
まずstable_linuxでブランチを作成する.kernelのバージョンは4.14.34を使う.

$ git checkout -b dto-zynq refs/tags/v4.14.34

FPGA-SoC-Linux/filesに3つのパッチファイルがあるので当てる.

$ patch -p1 FPGA-SoC-Linux/files/linux-4.14.34-armv7-fpga.diff
$ patch -p1 FPGA-SoC-Linux/files/linux-4.14.34-armv7-fpga-patch-usb-chipidea.diff
$ pathc -p1 FPGA-SoC-Linux/files/linux-4.14.34-armv7-fpga-patch-builddeb.diff
$ git add --update 
$ git commit -m "Add files"

arch/arm/configs/armv7_fpga_defconfigファイルといくつかのdtsファイルが生成される.
armv7_fpga_defconfigを見てみると,CONFIG_OF_OVERLAYというオプションが入っている.
またzynq-zybo-z7.dtsというデバイスリーファイルが生成されるが,以前作成したものと構成は同じ.Device Tree Overlayを使う場合は,シンボル情報を埋め込んだデバイスツリーをブート時に読み込んでおく必要がある*1.zynq-zybo-z7.dtsの/ノードの最後に

/ {
    __symbols__ {
        cpu0 = "/cpus/cpu@0";
        cpu1 = "/cpus/cpu@1";
        regulator_vccpint = "/fixedregulator";
        amba = "/amba";
        adc = "/amba/adc@f8007100";
        can0 = "/amba/can@e0008000";
        can1 = "/amba/can@e0009000";
        gpio0 = "/amba/gpio@e000a000";
        i2c0 = "/amba/i2c@e0004000";
        i2c1 = "/amba/i2c@e0005000";
        intc = "/amba/interrupt-controller@f8f01000";
        L2 = "/amba/cache-controller@f8f02000";
        mc = "/amba/memory-controller@f8006000";
        uart0 = "/amba/serial@e0000000";
        uart1 = "/amba/serial@e0001000";
        spi0 = "/amba/spi@e0006000";
        spi1 = "/amba/spi@e0007000";
        gem0 = "/amba/ethernet@e000b000";
        ethernet_phy = "/amba/ethernet@e000b000/ethernet-phy@0";
        gem1 = "/amba/ethernet@e000c000";
        sdhci0 = "/amba/sdhci@e0100000";
        sdhci1 = "/amba/sdhci@e0101000";
        slcr = "/amba/slcr@f8000000";
        clkc = "/amba/slcr@f8000000/clkc@100";
        rstc = "/amba/slcr@f8000000/rstc@200";
        pinctrl0 = "/amba/slcr@f8000000/pinctrl@700";
        dmac_s = "/amba/dmac@f8003000";
        devcfg = "/amba/devcfg@f8007000";
        global_timer = "/amba/timer@f8f00200";
        ttc0 = "/amba/timer@f8001000";
        ttc1 = "/amba/timer@f8002000";
        scutimer = "/amba/timer@f8f00600";
        usb0 = "/amba/usb@e0002000";
        usb1 = "/amba/usb@e0003000";
        watchdog0 = "/amba/watchdog@f8005000";
        usb_phy0 = "/phy0";
    };
};

を追加しておく.コンパイルの際は-@オプションをつける.

dtc -I dts -O dtb -@ -o devicetree.dtb zynq-zybo-z7.dts

作成したdevicetree.dtbをSDカードの第一パーティションにコピーしておく.

kernelのビルド

ビルドする.

$ export ARCH=arm
$ export CROSS_COMPILE=arm-linux-gnueabihf-
$ export DTC_FLAGS=--symbols
$ make armv7_fpga_defconfig

Debian用のkernelパッケージ,カーネルモジュールのコンパイルに必要なヘッダファイルのパッケージ,及びuImageを作成する.Debian kernel package作成方法は下記サイトを参考にした.
BuildADebianKernelPackage - Debian Wiki

$ make -j`nproc` bindeb-pkg
$ make make ARCH=arm UIMAGE_LOADADDR=0x8000 uImage

Debian Package作成の際に必要なコンパイルは終わっているのでuImageのビルドはすぐ終わると思う.
これで../linux-image-4.14.34-XXXXX.deblinux-header-4.14.34-XXX.deb(XXXは別の文字に読み替えてください)というDebian用のパッケージが作成されていると思う.Linux headerの方はカーネルモジュールのコンパイルに使用する.

作成したuImageをSDカードの第一パーティションに置いてLinuxを起動する.

Debian packageのインストール

Linuxが起動したらheaderのインストールを行う.作成したdebファイル(linux-headers-4.14-34-XXX.deb)をZynqへコピーする.Zynqではsshで通信ができるのでscpコマンドで転送した.

$ scp linux-headers-4.14.34-XXX.deb aho@192.168.Y.Y.:~

ここからはZynq上で作業する.パッケージのインストールを行う.

aho@zynq:~$ uname -r
4.14.34-XXX
aho@zynq:~$ sudo dpkg- i linux-headers-4.14.34-XXX.deb
aho@zynq:~$ ls /usr/src
/usr/src/linux-headers-4-14.34-XXX

カーネルモジュールのコンパイルにはlib/modules/$(uname -r)/buildにこのヘッダーへシンボリックリンクを貼っておく必要がある.

aho@zynq:~$ sudo ln -s /usr/src/linux-headers-$(uname -r)/ /lib/modules/$(uname -r)/build

これでZynq上でカーネルモジュールをコンパイルする準備が整う*2

ConfigFSの準備

Device Tree Overlayは現状カーネル内部からしか使えないらしい.そこでConfigFSというものを使う.ConfigFSとは,kernel objectをユーザー空間から操作する仮想ファイルシステムのことらしい*3/configにマウントして使うそう.
ikwzmさんが作成してくださったConfigFSを利用する.
GitHub - ikwzm/dtbocfg: Device Tree Blob Overlay Configuration File System
Zynq上で作業する.

aho@zynq:~$ git clone git@github.com:ikwzm/dtbocfg.git
aho@zynq:~$ cd dtbconfg
aho@zynq:~$ make

dtbconf.koが生成されるので,インストールする.

aho@zynq:~$ sudo su
root@zynq:~$ insmod dtbconfg.ko
root@zynq:~$ dmesg | grep dtb
[ 7791.638177] dtbocfg: loading out-of-tree module taints kernel.
[ 7791.638866] dtbocfg_module_init
[ 7791.638939] dtbocfg_module_init: OK

/configにマウントする.

root@zynq:~$ mkdir /config
root@zynq:~$ mount -t configfs none /config
root@zynq:~$ ls /config/device-tree/overlays/

Device Tree Overlayを使ってみる

使ってみる,といっても今回は適当なデバイスツリーを用意して,新たに登録ができるかをやってみる.
本来やりたい「Linuxe kernelを起動したままPLの構成を更新する」という操作はもう少し後の記事でまとめようと思う.

Device Tree Overlayの文法

Device Tree Overlayで使うdtsの文法はこのページにかかれている.

{
	/* ignored properties by the overlay */

	fragment@0 {	/* first child node */

		target=<phandle>;	/* phandle target of the overlay */
	or
		target-path="/path";	/* target path of the overlay */

		__overlay__ {
			property-a;	/* add property-a to the target */
			node-a {	/* add to an existing, or create a node-a */
				...
			};
		};
	}
	fragment@1 {	/* second child node */
		...
	};
	/* more fragments follow */
}

fragmentというノードの下に追加したいデバイスを書いていく.target = とかtarget = "/path..."ではオーバーレイする親ノードを参照して,参照されたノードに追加されるようにする.
追加したいデバイス__overlay__という記述の間に書いておく.書き方は普通のデバイスツリーの文法に従えばいいみたい.例が載っているので見てみると

---- foo.dts -----------------------------------------------------------------
	/* FOO platform */
	/ {
		compatible = "corp,foo";

		/* shared resources */
		res: res {
		};

		/* On chip peripherals */
		ocp: ocp {
			/* peripherals that are always instantiated */
			peripheral1 { ... };
		}
	};

これが,オーバーレイする前に読まれているデバイスツリー(foo.dtsと名前がついている).ocpという名前のノードに複数のperipheralの記述が続いているようなデバイスツリー.ここにbar.dtsというデバイスツリーをオーバーレイする.

---- bar.dts -----------------------------------------------------------------
/plugin/;	/* allow undefined label references and record them */
/ {
	....	/* various properties for loader use; i.e. part id etc. */
	fragment@0 {
		target = <&ocp>;
		__overlay__ {
			/* bar peripheral */
			bar {
				compatible = "corp,bar";
				... /* various properties and child nodes */
			}
		};
	};
};

target = <&ocp> でocpに新たにデバイスツリーを追加することを示している.その下に__overlay__があり,barというノードを追加している.ただし1点注意が必要である.bar.dtsではocpというラベルは定義されていないため,このままbar.dtsをdtcでコンパイルするとエラーになる.それを回避するために一番初めに/plugin/ と追加する.もちろん,ospというラベルは追加するデバイスツリーで定義されていることが前提条件.オーバーレイすると,以下のようなデバイスツリーが読み込まれているのと同等になる.

---- foo+bar.dts -------------------------------------------------------------
	/* FOO platform + bar peripheral */
	/ {
		compatible = "corp,foo";

		/* shared resources */
		res: res {
		};

		/* On chip peripherals */
		ocp: ocp {
			/* peripherals that are always instantiated */
			peripheral1 { ... };

			/* bar peripheral */
			bar {
				compatible = "corp,bar";
				... /* various properties and child nodes */
			}
		}
	};

ZynqでDevice Tree Overlayをやってみる

具体的な変更についてはこの記事を参考にしてください.ここでは方法だけ紹介します.

追加するdevice treeをnew.dtsとして保存しておく.

$ dtc -I dts -O dtb -o new.dtbo new.dts
# .dtboとしてコンパイルすること
$ mkdir /config/device-tree/overlays/newdev
$ cp cp new.dtbo /config/device-tree/overlays/newdev/dtbo
$ echo 1 >/config/device-tree/overlays/newdev/status

これでDevice Tree Overlayが完了する.

追記: ブート時にカーネルモジュールを読み込む方法

こちらの記事を参照.
カーネルモジュールをブート時に自動で読み込む - メモ置き場

*1:デバイスツリーにシンボル情報を埋め込む - Qiita

*2:ZynqのCPUよりも,強力な開発マシンのCPUを使ったほうが効率が良いが,クロスコンパイルがうまく行かなかったのでZynq上でビルドすることにした

*3:0から作るLinuxプログラム Ext2ファイルシステムその1 ファイルシステムの概要