洗練されたラズパイのようなSBC(Single Board Computer)と異なり、中華製SBCではメーカー整備によるDevice Treeの定義が不完全でハードが期待通りに動作しない事も多い。しかし逆に言うとDevice Tree定義に少し手を加えるだけでハードが動いてくれる事もあり、特に中華製SBCを触る上ではDevice Treeの知識は必須ではないかと思う。
Device Treeに関しては、下記リンクの解説が良くまとまっていて個人的には分かりやすかった。
Device Treeをちょっと触ってカスタマイズするために最低限必要な内容を抜粋してまとめてみようと思う。
Device Treeの役割
Device Treeの役割は主に以下の2つ。
- システムのハードウエア構成をデータ構造で記述する
- 個々のハードウエアとドライバの関連付けを行う
Device Treeにバス配線やメモリ配置などの情報を記述することで、細かい配線やメモリ配置が違っていたとしてもハードウエアのドライバは共通利用出来る。特にARM系はSoCのバリエーションが非常に多い一方で、実は内蔵ペリフェラルの中身は同一という事も珍しくない。このような場合は、ハードウエアのバリエーションをDevice Treeが吸収することで一見別物のペリフェラルを同一のドライバで動かすことが出来、ドライバの共通化が出来る。このようなDevice Treeによる仕組みの存在が、特にARM系のLinuxディストリビューション(RaspbianとかArmbianなど)におけるバイナリ配布の簡素化を支えている。
Device Tree設定ファイルの種類
拡張子が3種類あるが、dtsとdtsiは役割が少し違うだけで記法や中身は基本的に一緒。
拡張子 | 説明 |
---|---|
.dts | ボードレベルの定義ファイル |
.dtsi | SoCレベルの定義が含まれ、通常.dtsから参照される(dtsiのiはincludedの頭文字) |
.dtb | テキストの定義ファイルをコンパイルしてバイナリ化されたファイル |
Device Treeの記述ルール
Device TreeはC言語の構造体に似た文法で記述する。下記リンクの仕様書に記法の詳細が書かれている。

Device Treeという名前の通り親ノードに子ノードがぶら下がるツリー構造となっていて、各ノードにプロパティが定義される。
/* */で囲んだ部分と、//以降の部分はコメントとして扱われる。
ルートノードの記述
1行目の”/dts-v1/”はファイル先頭の約束ごと。2行目の先頭にある”/”はルートノードを表している。
/dts-v1/;
/ {
[プロパティの記述]
[子ノードの記述]
};
ルートノードであっても中身の記述は一般ノードと同一(次を参照)。
一般ノードの記述
ハードウエアの構成単位ごとにノードを割り当て、プロパティを記述する。
[label:] node-name[@unit-address] {
[プロパティの記述]
[子ノードの記述]
};
- label : 必須ではないが、他からノードを参照するために定義しておくと便利(&labelで参照できる)
- node-name : ノードを識別するための識別子(必須)
- [プロパティの記述] : “プロパティ名 = 値”で記述する。下の表に主なプロパティを抜粋(他にもドライバが要求するプロパティなど色々定義される)。
プロパティ名 | 説明 |
---|---|
compatible | ノードのハードウエアに対応するドライバを指定する |
status | “okay”に設定されるとハードウエアが有効化される |
reg | 親ノードから要求されているアドレスとサイズのパラメータを記述する |
#address-cells | 子ノードに要求するアドレスパラメータの数を指定 |
#size-cells | 子ノードに要求するサイズパラメータの数を指定 |
- [子ノードの記述] : 入れ子にしてノードを記述することで子ノードを定義出来る
デバイスツリーの例
dtsiファイルの記述例
例えば、とあるチップのdtsiファイルではSPIバスが以下のような感じで定義される。(SPIしか定義されておらず、実際にこういうのはあり得ないが)
/dts-v1/;
/ {
soc {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
ranges;
spi0: spi@1c05000 {
compatible = "allwinner,sun8i-h3-spi";
reg = <0x01c05000 0x1000>;
interrupts = <10>;
clocks = <&ccu CLK_BUS_SPI0>, <&ccu CLK_BUS_SPI0>;
clock-names = "ahb", "mod";
resets = <&ccu RST_BUS_SPI0>;
status = "disabled";
#address-cells = <1>;
#size-cells = <0>;
};
};
};
- 行番号2〜21: ルートノードの定義
- 行番号2: ルートノードを表す、お約束の表記
- 行番号3〜20: socノードの定義
- 行番号5〜6: socノードの子ノードには、アドレスとサイズのパラメータが1つずつ必要
- 行番号9〜19: spi0ノードの定義
- 行番号9: “spi0″とラベルが付けられている(&spi0で他からこのノードを参照可能)
- 行番号10: “allwinner,sun8i-h3-spi”にマッチするドライバを利用する
- 行番号11: 親ノードから要求されているパラメータの値
- 行番号16: このノードは無効化されている
- 行番号17〜18: 子ノードにはアドレスパラメータを要求(サイズパラメータは不要)
dtsファイルの記述例
以下がdtsファイルの記述例。hogehoge.dtsiは上で例に挙げたdtsiファイルの想定だ。ラベル参照ではツリー階層を無視してよく、ルートノードと同じ階層に中身を定義出来る。
/dts-v1/;
#include "hogehoge.dtsi"
/ {
model = "Hoggehoge SBC";
};
&spi0 {
pinctrl-names = "default";
pinctrl-0 = <&spi0_pins_a>;
status = "okay";
spidev {
compatible = "linux,spidev";
reg = <0>;
};
};
- 行番号4〜6: ルートノードの定義(ボード名が設定されているだけ)
- 行番号8〜17: spi0ノードの定義
- 行番号8: dtsiファイルで定義されたspi0のラベルを”&spi0″表記で参照している
- 行番号10: SPIドライバが要求しているpinctrl-0プロパティを定義(ラベル”spi0_pins_a”で定義されるGPIOピンのリストを参照している)
- 行番号11: このノードを有効化(dtsiファイルの行番号16で無効化されていたのをオーバーライド)
- 行番号13〜16: 子ノードの定義
- 行番号15: 親ノード(spi0)が要求しているアドレスパラメータの値
逆変換
コンパイル済みのデバイスツリーbin.dtbを以下のようにdtcコマンドの引数に渡すと、text.dtsに人間の読めるテキスト形式に逆変換される。
dtc -I dtb -O dts -o text.dts bin.dtb
まとめ
非常にざっくりとだが、Device Treeについて解説した。Armbianなどのバイナリディストリビューションだけを使っている間は特に意識する必要性も無いが、デバイスツリーの設定はマイナーなSBCなどではドライバとハードウエアを関連付けるキモ。カーネル設定と並んで重要な作業なので、方法を覚えておいて損は無いと思う。
コメント