ZynqでDevice Tree Overlayを使う
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上でビルドすることにした