これまでの記事を通して、Devicetree(DTS)の仕組みや、JSONに似たツリー構造の書き方、アドレスやピンマルチプレクス(pinctrl)といった実践的な構文について解説してきました。
しかし、DTSファイルを書いただけではハードウェアは動きません。OS(LinuxやZephyr)のカーネル内に存在する「デバイスドライバ(C言語コード)」が、Devicetreeの情報を読み取って初めて動作します。
今回は、DTSに書いた情報がドライバのCコードとどうやって紐付くのか、そして後付けでハードウェア構成を変更するデバイスツリーオーバーレイ(DTO)の仕組みについて解説します。
1. どうやってドライバとDTSが紐付くのか?(Probeの仕組み)
LinuxでもZephyrでも、Devicetreeに記述されたノードとデバイスドライバを紐付ける一番の鍵は compatible プロパティです。
OSの起動時、カーネルは読み込んだDevicetree(DTB)の各ノードをスキャンし、それに一致する compatible 文字列を持つドライバを探して、初期化関数(Probe関数)を呼び出します。
Linuxドライバでの紐付け例
LinuxカーネルのCソースコードでは、of_device_id 構造体の配列を使って、自分が対応する compatible 文字列をカーネルに登録します。
/* ドライバ側のCコード (Linux) */
#include <linux/of.h>
#include <linux/platform_device.h>
/* このドライバが対応するcompatible文字列のリスト */
static const struct of_device_id my_custom_of_match[] = {
{ .compatible = "vendor,my-custom-sensor", },
{ /* 終端記号 */ }
};
MODULE_DEVICE_TABLE(of, my_custom_of_match);
static struct platform_driver my_custom_driver = {
.probe = my_custom_probe, /* 紐付いた時に呼ばれる関数 */
.driver = {
.name = "my_custom_driver",
.of_match_table = my_custom_of_match, /* ここでDTSとの照合ルールを渡す */
},
};Zephyr OSでの紐付け例
Zephyrでは、マクロを駆使してコンパイル時にドライバとノードを静的に紐付けます。メモリのオーバーヘッドを極限まで減らすためのRTOSならではのアプローチです。
/* ドライバ側のCコード (Zephyr) */
#define DT_DRV_COMPAT vendor_my_custom_sensor
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
/* 初期化関数 */
static int my_custom_init(const struct device *dev) {
/* ... 初期化処理 ... */
return 0;
}
/* DT_DRV_COMPATに一致するノードごとに初期化関数を登録するマクロ */
DEVICE_DT_INST_DEFINE(0, my_custom_init, NULL, NULL, NULL,
POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, NULL);どちらのOSも、「DTS側の compatible」と「Cソースコード側の compatible」の完全一致をトリガーとして動作を開始していることが分かります。
2. C言語コードの中でプロパティの値を読み取る方法
初期化関数(Probe関数)が呼ばれた後、ドライバは自分が制御するデバイスのレジスタアドレス(reg)や割り込み番号(interrupts)を取得する必要があります。
Linuxの場合:of_* APIを使用
Linuxでは of_(Open Firmwareの略。Devicetreeの源流)から始まる関数群を使ってノードのプロパティを読み取ります。
static int my_custom_probe(struct platform_device *pdev) {
struct device_node *np = pdev->dev.of_node;
u32 clock_speed;
/* 例: "clock-frequency" プロパティの数値を読み取る */
if (of_property_read_u32(np, "clock-frequency", &clock_speed)) {
dev_err(&pdev->dev, "クロック速度がDTSに定義されていません\n");
return -EINVAL;
}
dev_info(&pdev->dev, "設定されたクロック: %d Hz\n", clock_speed);
return 0;
}Zephyrの場合:DT_* マクロを使用
Zephyrでは実行時のパース処理を省くため、Cのプリプロセッサマクロを使って、ビルド時にDTSの値をCの定数に変換します。
/* DTS内で label="my_sensor" となっているノードのプロパティを取得する例 */
#define MY_SENSOR_NODE DT_NODELABEL(my_sensor)
/* "clock-frequency"プロパティの値をCの定数として取得 */
#define CLOCK_FREQ DT_PROP(MY_SENSOR_NODE, clock_frequency)
static int my_custom_init(const struct device *dev) {
printk("設定されたクロック: %d Hz\n", CLOCK_FREQ);
return 0;
}3. デバイスツリーオーバーレイ(DTO)とは?
現代の組み込み開発で非常に重要になる「デバイスツリーオーバーレイ(Device Tree Overlay = DTO)」について触れておきます。
拡張ボード(Raspberry PiのHATや、評価ボードのシールドなど)を接続した場合、ベースとなるSoCのハードウェア構成自体は変わりませんが、I2CやSPIの先に新しいセンサーが追加された状態になります。
このとき、大元の DTB 全体を再コンパイルするのではなく、差分情報だけを書いた .dtbo(Overlay)ファイルを実行時やブートローダで動的に後乗せ(パッチ当て)する仕組みがDTOです。
オーバーレイ(.overlay / .dtso)の記述例:
/dts-v1/ /plugin/;
/* 追加する内容(ターゲットとなるノードを指定して上書きする) */
&i2c1 {
status = "okay";
/* I2C1バスに新しいセンサーを後付けする */
new_sensor@76 {
compatible = "bosch,bme280";
reg = <0x76>;
};
};Zephyr OSでもこのオーバーレイの仕組みは強力にサポートされており、プロジェクトディレクトリに app.overlay というファイルを置くだけで、大元のボード定義(.dts)を破壊することなく、アプリケーション固有のピン変更やセンサー追加を簡単に実現できます。
まとめ
今回は、DevicetreeがC言語のデバイスドライバとどのように連携するのか、そして動的に構成を変更するオーバーレイ(DTO)の仕組みについて解説しました。
- ドライバとの紐付け:
compatible文字列の完全一致をトリガーに、OSが自動的にドライバの初期化(Probe)を実行する。 - 値の読み取り: Linuxは
of_関数群(実行時)、ZephyrはDT_マクロ群(ビルド時)を使ってDTSのプロパティ値を取得する。 - オーバーレイ(DTO): ベースのハードウェア定義を変更せず、拡張ボードなどの追加・変更差分だけを後乗せする強力な仕組み。
これで「なぜ必要なのか(仕組み)」「どう書くのか(構文)」「どう使われるのか(ドライバ連携)」という、Devicetreeの一連のフローが見えてきたかと思います。
Devicetreeは組み込みLinuxやZephyr開発における共通言語です。この強力なデータ構造を味方につけて、よりスムーズなハードウェア制御を目指しましょう!


コメント