Zephyrのdriverを作ってみよう④~device tree~
はじめに
Zephyrのdriverを作る方法を学びます。 まずは、qemu_cortex_m3をターゲットとした場合の、UART device driverを写経します。
|> zephyr/drivers/serial/uart_stellaris.c
全体で700行くらいなので、全てを単純に写経する、というよりポイントを押さえて行きたいと思います。
前回までで、ポーリングベースの送受信ができるUART Driverを実装しました。
tomo-wait-for-it-yuki.hatenablog.com
今回は、ハードウェアを定義するためのdevice treeを作成していきます。 device treeは他のモジュールとのインタフェースにも使われます。UARTの場合はshell subsystemがどのUARTデバイスを入出力に利用するか、の指定はdevice treeの記述を元に行われます。
device tree
Linuxでハードウェア構成を記述するための形式です。主にARMなど組込みで用いられるアーキテクチャで利用されます。
組込みLinuxでplatform deviceを記述する3つの方法(board file/device tree/ACPI DSDT)
Linux Foundation傘下のプロジェクトのためか、Zephyrもハードウェア構成の記述にdevice treeを用います。 文法は同じですが、扱われ方は、Linuxとはかなり違います。
Linuxでは、device treeは単独でblobを形成し、Linux kernelの外に置かれます。そして、Linux kernelはランタイムでこのblobにある情報を参照します。
一方、Zephyrでは、device treeを、ビルド時にheaderファイルを生成するために利用します。 ソフトウェアが扱うハードウェア構成は、ビルド時に固定されます。
UART device tree nodeの使われ方
まず、写経対象であるstellaris-uartのdevice tree nodeがどのように記述されるか、を見ます。
|> build/zephyr/qemu_cortex_m3.dts_compiled
uart0: uart@4000c000 { compatible = "ti,stellaris-uart"; reg = <0x4000c000 0x4c>; interrupts = <0x05 0x03>; status = "ok"; label = "UART_0"; current-speed = <0x1c200>; };
このdevice treeノードから生成されるheaderファイルは次のようになります。
|> build/zephyr/include/generated/generated_dts_board.h
/* uart@4000c000 */ #define CONFIG_UART_CONSOLE_ON_DEV_NAME "UART_0" #define DT_TI_STELLARIS_UART_4000C000_BASE_ADDRESS 0x4000c000 #define DT_TI_STELLARIS_UART_4000C000_CURRENT_SPEED 115200 #define DT_TI_STELLARIS_UART_4000C000_IRQ_0 5 #define DT_TI_STELLARIS_UART_4000C000_IRQ_0_PRIORITY 3 #define DT_TI_STELLARIS_UART_4000C000_LABEL "UART_0" #define DT_TI_STELLARIS_UART_4000C000_SIZE 76 #define DT_TI_STELLARIS_UART_UART_0_BASE_ADDRESS DT_TI_STELLARIS_UART_4000C000_BASE_ADDRESS #define DT_TI_STELLARIS_UART_UART_0_CURRENT_SPEED DT_TI_STELLARIS_UART_4000C000_CURRENT_SPEED #define DT_TI_STELLARIS_UART_UART_0_IRQ DT_TI_STELLARIS_UART_4000C000_IRQ_0 #define DT_TI_STELLARIS_UART_UART_0_IRQ_PRIORITY DT_TI_STELLARIS_UART_4000C000_IRQ_0_PRIORITY #define DT_TI_STELLARIS_UART_UART_0_LABEL DT_TI_STELLARIS_UART_4000C000_LABEL #define DT_TI_STELLARIS_UART_UART_0_SIZE DT_TI_STELLARIS_UART_4000C000_SIZE #define UART_0_BASE_ADDRESS DT_TI_STELLARIS_UART_4000C000_BASE_ADDRESS #define UART_0_CURRENT_SPEED DT_TI_STELLARIS_UART_4000C000_CURRENT_SPEED #define UART_0_IRQ DT_TI_STELLARIS_UART_4000C000_IRQ_0 #define UART_0_IRQ_PRIORITY DT_TI_STELLARIS_UART_4000C000_IRQ_0_PRIORITY #define UART_0_LABEL DT_TI_STELLARIS_UART_4000C000_LABEL #define UART_0_SIZE DT_TI_STELLARIS_UART_4000C000_SIZE
Driverでは、このheaderファイルで定義されたマクロを使って、デバイスパラメータの設定や、デバイス名の参照を行います。
static const struct uart_device_config uart_my_uart_dev_cfg_0 = { .base = (u8_t *)DT_TI_STELLARIS_UART_4000C000_BASE_ADDRESS, .sys_clk_freq = DT_UART_MY_UART_CLK_FREQ, };
UART device tree binding
対応するdevice treeのbindingを見てみます。
|> zephyr/dts/bindings/serial
# at dts/bindings/serial $ ls altera,jtag-uart.yaml nxp,kinetis-uart.yaml arm,cmsdk-uart.yaml nxp,lpc-usart.yaml atmel,sam0-uart.yaml sifive,uart0.yaml atmel,sam-uart.yaml silabs,gecko-leuart.yaml atmel,sam-usart.yaml silabs,gecko-uart.yaml cypress,psoc6-uart.yaml silabs,gecko-usart.yaml intel,qmsi-uart.yaml st,stm32-lpuart.yaml microsemi,coreuart.yaml st,stm32-uart.yaml nordic,nrf-uarte.yaml st,stm32-usart.yaml nordic,nrf-uart.yaml ti,cc32xx-uart.yaml ns16550.yaml ti,msp432p4xx-uart.yaml nxp,imx-uart.yaml ti,stellaris-uart.yaml nxp,kinetis-lpsci.yaml uart-device.yaml nxp,kinetis-lpuart.yaml uart.yaml
Zephyrでは、device treeのbindingはyaml形式で記述します。 pythonで解析しやすいですし、人間にとっても見やすいので、これはLinuxから進化しているところだと思います。
早速、これまで写経してきたstellaris-uart
のbindingを見てみます。
|> ti,stellaris-uart.yaml
--- title: TI Stellaris UART version: 0.1 description: > This binding gives a base representation of the TI Stellaris UART inherits: !include uart.yaml properties: compatible: constraint: "ti,stellaris-uart" reg: type: array description: mmio register space generation: define category: required interrupts: type: array category: required description: required interrupts generation: define ...
ファイル名と、compatibleが対応していそうです。後程、自分のDriverのbindingを作るときに試してみます。
UARTデバイスで共通して必要となるプロパティは、uart.yaml
で定義されており、それを継承しています。
|> uart.yaml
--- title: Uart Base Structure version: 0.1 description: > This binding gives the base structures for all UART devices child: bus: uart properties: compatible: type: string category: required description: compatible strings generation: define clock-frequency: type: int category: optional description: Clock frequency information for UART operation generation: define current-speed: type: int category: required description: Initial baud rate setting for UART generation: define ...
自作Driverのbinding作成
stellarisのbindingをコピーして、compatibleだけ変更します。
# at zephyr/dts/bindings/serial cp ti,stellaris-uart.yaml tn,my-uart.yaml
properties: compatible: - constraint: "ti,stellaris-uart" + constraint: "tn,my-uart"
device tree nodeの修正
compatibleを上書きします。
|> qemu_cortex_m3.dts
&uart0 {
+ compatible = "tn,my-uart";
status = "ok";
current-speed = <115200>;
};
UART driverの修正
device treeから生成されるマクロを使用している部分を修正します。 例えば、次のような箇所です。
static const struct uart_device_config uart_my_uart_dev_cfg_0 = { - .base = (u8_t *)DT_TI_STELLARIS_UART_4000C000_BASE_ADDRESS, + .base = (u8_t *)DT_TN_MY_UART_4000C000_BASE_ADDRESS, .sys_clk_freq = DT_UART_MY_UART_CLK_FREQ, };
ビルド
ビルドしてみます。
$ mkdir build && cd $_ $ cmake -GNinja -DBOARD=qemu_cortex_m3 .. $ ninja [3/112] Preparing syscall dependency handling [107/112] Linking C executable zephyr/zephyr_prebuilt.elf Memory region Used Size Region Size %age Used FLASH: 23548 B 256 KB 8.98% SRAM: 8196 B 64 KB 12.51% IDT_LIST: 8 B 2 KB 0.39% [112/112] Linking C executable zephyr/zephyr.elf
通りましたね…。
生成されたheaderを確認してみます。
|> build/zephyr/include/generated/generated_dts_board.h
/* uart@4000c000 */ #define CONFIG_UART_CONSOLE_ON_DEV_NAME "UART_0" #define DT_TN_MY_UART_4000C000_BASE_ADDRESS 0x4000c000 #define DT_TN_MY_UART_4000C000_CURRENT_SPEED 115200 #define DT_TN_MY_UART_4000C000_IRQ_0 5 #define DT_TN_MY_UART_4000C000_IRQ_0_PRIORITY 3 #define DT_TN_MY_UART_4000C000_LABEL "UART_0" #define DT_TN_MY_UART_4000C000_SIZE 76 ...
ちゃんとマクロが生成されています。
動作確認
$ ninja run [1/1] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: cortex-m3 qemu-system-arm: warning: nic stellaris_enet.0 has no peer ***** Booting Zephyr OS 1.13.99 ***** Hello World! qemu_cortex_m3 uart:~$ clear help history resize shell
問題ありません。
実験
compatibleから対応するbindingを見つけるのを、どうやっているか調べます。 まず、単純にyamlのファイル名とcompatibleをマッチングしている可能性があります。 実験してみます。
$ mv tn,my-uart.yaml ti,my-uart.yaml
これは、普通にビルドが通ります。
ということで、ちゃんとyamlのcompatible
まで見に行っているみたいですね。
ざっと、pythonを見てみると、device treeからすべてのcompatible
を取得した後、対象のyamlディレクトリ内からcompatibleで引っかけているみたいです。
$ find . -name *.yaml | wc -l 232
全ファイル舐めても232しかないので、計算時間はあまり気にならなさそうです。