ikwzmさんの記事を参考にZynqでDevice Tree Overlayを使ってみた.
Device Tree Overlayとは,Linux kernelを起動したままdevice treeを新たに追加することのできる機能である.
- なぜ必要?
- Linux Kernelのビルドとkernel packageの作成
- ConfigFSの準備
- Device Tree Overlayを使ってみる
- 追記: ブート時にカーネルモジュールを読み込む方法
なぜ必要?
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.debとlinux-header-4.14.34-XXX.deb(XXXは別の文字に読み替えてください)というDebian用のパッケージが作成されていると思う.Linux headerの方はカーネルモジュールのコンパイルに使用する.
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
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上でビルドすることにした