エムベデッド分野において、ハードウェアの多様性が課題となるが、多くのインターフェースは標準化されている。以前は各ベンダーが他社のコードをコピーすることでドライバ開発を行っていたが、大量の冗長コードを生み出していた。Linuxカーネルではこの問題を解決するために、共通ライブラリとして抽象化し、デバイスが「何のデバイスか」を指定するだけで、カーネルが適切なドライバを自動選択できるようにした。これにより品質の均一性と管理の容易さが向上した。
デバイストリートに関与する主なファイルはDTS(Device Tree Source)、DTSI(Device Tree Include)、DTB(Device Tree Blob)である。基本的なフローは、DTSを編集し(DTSIは共通データを含むヘッダーファイルとして利用される)、DTC(Device Tree Compiler)でDTBに変換し、ブートローダー経由でカーネルに渡すというものだ。
DTSは特定のハードウェアを記述し、DTSIは共有可能なハードウェア情報を含む。DTBはコンパイル後のバイナリファイルである。以下はDTSの例:
/dts-v1/;
/ {
compatible = "custom_board";
model = "Custom Board Design";
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
device_type = "cpu";
compatible = "arm,cortex-a9";
reg = <0x0>;
};
};
memory {
device_type = "memory";
reg = <0x90000000 0x8000000>;
};
serial@0 {
compatible = "st,stm32-usart";
reg = <0x40011000 0x400>;
interrupt-parent = <&gic>;
interrupts = <123 1>;
};
};
この例では、compatibleフィールドがデバイスの識別子として使用され、ドライバとのマッチングに使われる。regはレジスタのベースアドレスとサイズを示し、interruptsは割り込み番号を、interrupt-parentは割り込みコントローラーを指定する。#address-cellsと#size-cellsはサブノードのアドレスとサイズの表現形式を定義する。
ドライバ側では以下のようにマッチングが行われる:
static const struct of_device_id uart_match[] = {
{ .compatible = "st,stm32-usart" },
{ }
};
MODULE_DEVICE_TABLE(of, uart_match);
static int uart_probe(struct platform_device *pdev) {
struct resource *res;
void __iomem *base;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENODEV;
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
// UART初期化処理とドライバ登録
return 0;
}
static struct platform_driver uart_driver = {
.probe = uart_probe,
.driver = {
.name = "stm32-uart",
.of_match_table = uart_match,
},
};
module_platform_driver(uart_driver);
このように、DTSに記載されたcompatibleフィールドとドライバコードのof_match_tableが一致すれば、デバイスの初期化が開始される。
デバイストリートの構文では、ノードとプロパティの2種類がある。以下はノードの例:
uart1: serial@40020000 {
compatible = "mychip,uart-v2";
reg = <0x40020000 0x2000>;
interrupts = <6>;
status = "disabled";
};
ノードの形式は[ラベル]: ノード名[@ユニットアドレス] { ... }となる。ラベルはグローバルユニークでなければならないが、ノード名は重複可能である。例えば、&uart1 { status = "enabled"; };のように、既存ノードのプロパティを変更できる。
プロパティの例として、compatible = "mychip,uart-v2";があり、文字列は""、数値は<>、バイト列は[]で表現される。これらの型はカンマで区切って複数指定できる。
デバイストリートにはオーバーレイ(overlay)という機能があり、既存のDTBに追加情報を合成する。以下はオーバーレイの作成手順:
dtc -I dts -O dtb -o base.dtb base.dts
dtc -I dts -O dtb -o overlay.dtb overlay.dts
fdtoverlay -i base.dtb -o merged.dtb overlay.dtb
よくある質問への回答:
- DTSは単なる設定ファイルであり、実際のドライバはカーネルまたはベンダー固有のコードに存在する。
- ブートローダーとカーネルは同じDTBを共有するが、ブートローダーがDTBをメモリに読み込み、カーネルにアドレスを渡すことで実現される。
- DTSの記述規則は一貫しておらず、各ドライバが必要とする情報に依存する。
- 既存デバイスを追加するには、DTSファイルを編集するだけで済む。
- 新規デバイスを追加するには、ドライバの実装とDTSの更新が必須となる。
参考資料:
- Linuxカーネルにおけるデバイストリートの仕組み
- DTSによるデバイス記述の詳細(CSDNブログ)