概要
デバイスツリー(Device Tree)は、組み込みLinuxシステムでボードのハードウェア構成をOSに記述・伝えるためのデータ構造とファイル形式です。どのペリフェラルがどのアドレスにあるか、クロック周波数・割り込み番号・GPIO番号・I2Cアドレスなどをテキスト形式(DTS: Device Tree Source)で記述し、コンパイルしたバイナリ(DTB: Device Tree Blob)をブートローダーからカーネルに渡します。
デバイスツリーが登場する以前、組み込みLinuxではボード固有の情報がカーネルのC言語ソースコード(board file)にハードコードされていました。ARM Linuxのboard fileが爆発的に増え「ARM mess問題」と呼ばれた状況を解決するため、2011年にLinus Torvaldsの要請のもとデバイスツリーへの移行が本格化しました。現在ではARMベースの組み込みLinuxで標準的に使われており、Zephyrもデバイスツリーを採用しています。
歴史・背景
デバイスツリーの起源はIBMとAppleが共同開発した「Open Firmware」(IEEE 1275)です。PowerPCマシン向けに1990年代から使われており、ハードウェア構成をOFW(Open Firmware)ツリーとして記述する仕組みが原型です。
Linuxカーネルでは2005年頃からPowerPC向けにデバイスツリー記述(.dts)が採用されていました。2011年にARMアーキテクチャでもデバイスツリーへの移行が決まり、2012〜2013年頃のカーネルではほぼ全ての主要ARMボードがデバイスツリーに対応しました。
2016年以降、Zephyr RTOSもデバイスツリーをLinuxから輸入して採用し、RTOSの世界にも広まりました。デバイスツリー仕様(Device Tree Specification)はlinuxfoundation.orgのワーキンググループが策定・管理しています。
技術仕様
ファイルの種類
| 拡張子 | 説明 |
|---|---|
| .dts | Device Tree Source:ルートボード定義ファイル |
| .dtsi | Device Tree Source Include:共通部分を定義して複数から#includeする |
| .dtb | Device Tree Blob:.dtsをコンパイルしたバイナリ |
| .dtbo | Device Tree Blob Overlay:ベースDTBに追加する差分DTB |
| .overlay | Zephyrで使うDTSオーバーレイファイル |
DTS構文
/dts-v1/; /* DTSバージョン宣言 */
/* インクルード(SoC定義ファイル) */
#include "bcm2711.dtsi"
#include "bcm2711-rpi.dtsi"
#include <dt-bindings/gpio/gpio.h>
/* ルートノード */
/ {
/* ボード名と互換性(文字列リスト) */
model = "Raspberry Pi 4 Model B";
compatible = "raspberrypi,4-model-b", "brcm,bcm2711";
/* メモリ定義(4GB版) */
memory@0 {
device_type = "memory";
reg = <0x0 0x0 0x0 0x80000000>, /* 2GB */
<0x1 0x80000000 0x0 0x80000000>; /* 追加2GB(アドレス拡張) */
};
/* I2Cに接続したBME280センサーの定義 */
&i2c1 {
status = "okay";
clock-frequency = <400000>; /* 400kHz(ファストモード) */
bme280: bme280@76 {
compatible = "bosch,bme280";
reg = <0x76>; /* I2Cアドレス */
};
};
/* GPIOキー定義 */
gpio-keys {
compatible = "gpio-keys";
button0: button0 {
label = "USER_BUTTON";
gpios = <&gpio 17 GPIO_ACTIVE_LOW>; /* GPIO17, Lowアクティブ */
linux,code = <11>; /* KEY_0 */
};
};
/* PWM制御のLED */
pwm-leds {
compatible = "pwm-leds";
led0 {
label = "green:act";
pwms = <&pwm0 0 500000 0>; /* PWM ch0, 500us周期 */
max-brightness = <255>;
};
};
};
プロパティの型
node@address {
/* 文字列 */
compatible = "vendor,my-chip"; /* ドライバマッチングに使う */
label = "my-uart";
/* 整数(32bit) */
reg = <0x40000000 0x1000>; /* アドレス・サイズ(16進数) */
clock-frequency = <115200>; /* 10進数でも可 */
/* バイト列 */
mac-address = [01 02 03 04 05 06];
/* ブール(存在=true) */
interrupt-controller;
/* phandle参照(別ノードへの参照) */
clocks = <&rcc STM32_CLOCK_BUS_APB1 0x00200000>;
/* ステータス */
status = "okay"; /* 有効 */
/* status = "disabled"; 無効 */
};
interrupt指定
uart0: uart@40011000 {
compatible = "arm,pl011";
reg = <0x40011000 0x1000>;
/* インタラプトコントローラへの参照 */
interrupt-parent = <&gic>;
/* GIC SPI割り込み番号37、レベルトリガ(高) */
interrupts = <GIC_SPI 37 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&rcc CLK_UART0>;
status = "okay";
};
動作原理
DTSからDTBへのコンパイル
.dtsファイルはDTCコンパイラ(Device Tree Compiler)で.dtb(バイナリ)に変換されます:
# DTSをDTBにコンパイル
dtc -I dts -O dtb -o myboard.dtb myboard.dts
# DTBをDTSに逆コンパイル(解析用)
dtc -I dtb -O dts -o myboard.dts myboard.dtb
# Linuxカーネルビルド時に自動でDTBが生成される
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- dtbs
# → arch/arm64/boot/dts/broadcom/bcm2711-rpi-4-b.dtb 等が生成
ブートローダーからカーネルへの受け渡し
U-Bootは起動時にDTBのアドレスをカーネルに渡します(ARM64の場合、レジスタx0=DTBアドレス):
# U-Boot シェルでの手動起動例
fatload mmc 0:1 0x80000000 Image # カーネル
fatload mmc 0:1 0x83000000 bcm2711-rpi-4-b.dtb # DTB
booti 0x80000000 - 0x83000000 # カーネル起動("-"はinitramfs省略)
カーネルでのDTB解析
Linuxカーネルは起動時にDTBを解析してデバイスを認識します。compatibleプロパティを使ってドライバとデバイスをマッチングします:
/* ドライバのマッチングテーブル(カーネル内のI2Cドライバ例) */
static const struct of_device_id bme280_of_match[] = {
{ .compatible = "bosch,bme280", },
{ .compatible = "bosch,bmp280", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, bme280_of_match);
/* プローブ関数(デバイスが認識されたときに呼ばれる) */
static int bme280_probe(struct i2c_client *client,
const struct i2c_device_id *id) {
/* DTから設定値を取得 */
int irq = of_irq_get(client->dev.of_node, 0);
/* ... */
}
DTBオーバーレイ(Raspberry Piのoverlayシステム)
Raspberry Piではベースのシステム設定(bcm2711-rpi-4-b.dtb)に対して、追加の.dtboオーバーレイを適用することで設定を拡張できます:
# /boot/config.txt(Raspberry Pi のブートローダー設定)
dtparam=i2c_arm=on # I2C有効化
dtoverlay=i2c-rtc,ds3231 # RTCモジュール追加
dtoverlay=dwc2 # USB OTGモード
用途・ユースケース
組み込みLinuxのSBC・カスタムボード
Raspberry Pi・BeagleBone・ODROID・産業用SBCなど、ARMベースのボードでは必ずDTBが使われます。カスタム設計のボードではボード固有の.dtsを作成してLinuxをポーティングします。
Zephyrでのハードウェア定義
ZephyrはデバイスツリーをLinuxから採用し、MCUのペリフェラル構成を定義します。アプリケーションは.overlayファイルでベースボード定義をカスタマイズします。
Yocto / BuildrootでのBSP管理
YoctoやBuildrootのBSPレイヤーにはボードのDTSファイルが含まれており、製品カスタマイズのDTSオーバーレイを追加できます。
実装・開発のポイント
よくある間違いと確認方法
# 起動中にDTBが読まれているか確認
ls /sys/firmware/devicetree/base/
# 特定ノードのプロパティ確認
cat /sys/firmware/devicetree/base/i2c@7e804000/status
# → okay
# ドライバがデバイスを認識しているか確認
ls /sys/bus/i2c/devices/
# → 1-0076(I2Cバス1のアドレス0x76)
# DTBの内容を全展開
dtc -I fs /sys/firmware/devicetree/base/ 2>/dev/null | less
カスタムDTSの作成
/* my-product.dts */
/dts-v1/;
/* ベースボードのDTSをインクルード */
#include "stm32mp157c-dk2.dts"
/* カスタム追加 */
&i2c4 {
my-sensor@48 {
compatible = "myvendor,my-sensor";
reg = <0x48>;
vdd-supply = <&v3v3>;
};
};
/* 使用しないデバイスを無効化 */
&sdmmc2 {
status = "disabled";
};
Zephyrでのオーバーレイ活用
/* app.overlay(Zephyrアプリ用カスタマイズ) */
&i2c0 {
bme280@76 {
compatible = "bosch,bme280";
reg = <0x76>;
};
};
&uart0 {
current-speed = <9600>; /* デフォルトの115200から変更 */
};
他技術との比較
デバイスツリー vs ACPIテーブル(x86系)
x86系PCでは、デバイスツリーの代わりにACPI(Advanced Configuration and Power Interface)テーブルが使われます。ACPIはより高機能(AMLという独自言語でドライバロジックも記述可能)ですが、組み込みシステムには過剰です。ARM ServerではACPIとDTBの両方が使えます。
デバイスツリー vs Board File
デバイスツリー登場以前のLinuxは、ボード固有の情報をC言語で記述した「board file」(arch/arm/mach-*/board-*.c)に書いていました。board fileはカーネルソースに取り込む必要があったため、変更のたびにカーネルの再ビルドが必要でした。デバイスツリーによってカーネルとボード設定が分離され、同じカーネルイメージを複数のボードで共有できるようになりました。