【入門】LinuxのDevicetree(デバイスツリー)とは?仕組みと基本的な書き方を徹底解説

この記事は約6分で読めます。

組み込みLinux開発において避けては通れないのが「Devicetree(デバイスツリー)」です。ハードウェアの構成をカーネルに伝える重要な役割を担っていますが、独特の構文を持つため、最初はハードルが高く感じるかもしれません。

実はこのDevicetree、Linuxだけの専売特許ではありません。近年IoT向けリアルタイムOS(RTOS)として急速に普及しているZephyr OSでも、標準的なハードウェア記述の仕組みとして採用されています。つまり、一度Devicetreeをマスターすれば、Linux搭載のSoCだけでなく、マイコン開発でもその知識をフル活用できるのです。

この記事では、Devicetreeがなぜ必要なのかという「仕組み」から、実際の.dtsファイルの「書き方」まで、SEOと実用性を意識して分かりやすく解説します。

Devicetreeが導入された背景と仕組み

かつてのLinuxカーネルでは、ハードウェアの構成情報(メモリアドレス、割り込み番号、接続されているデバイスなど)がカーネルのCソースコード内に直接書き込まれていました。しかし、ARMアーキテクチャの普及により多種多様なSoCやボードが登場すると、カーネルソース内にボード固有のコードが溢れかえり、メンテナンスが困難になりました。

この問題を解決するために導入されたのがDevicetreeです。ハードウェアの記述をカーネルソースから分離し、独立したデータ構造として外出ししました。これにより、カーネルのバイナリ(zImageなど)を再コンパイルすることなく、異なるハードウェア構成に対応できるようになりました。

Devicetreeを構成する3つの重要な要素は以下の通りです。

用語フルスペル役割
DTSDevicetree Source人間が読み書きできるテキスト形式のソースファイル(拡張子 .dts / .dtsi)。
DTBDevicetree BlobDTSをコンパイルしたバイナリファイル(拡張子 .dtb)。ブートローダがメモリにロードし、カーネルに渡す。
DTCDevicetree CompilerDTSをDTBに変換、またはDTBをDTSに逆変換するためのコンパイラツール。

Devicetreeの基本的な構文と書き方(JSONとの比較)

Devicetreeは、ハードウェアの構成を「ツリー構造」で表現します。データ構造としてはJSONフォーマットに非常に似ており、普段Webやアプリ開発をしている方であれば「JSONのような階層型の連想配列」とイメージするとスッと理解できるはずです。

(※厳密にはJSONそのものではなく、C言語ライクな独自構文であるため、ダブルクォーテーションなしのキー名や、配列を示す < >、行末のセミコロン ;、/* */ によるコメントアウトなどを使用します。)

各デバイスはノードと呼ばれ(JSONでいうオブジェクト {} に相当)、ノードの中にそのデバイスの特性を示すプロパティ(JSONでいうキーと値のペア)を記述します。

最も基本的なDTSファイルの例を見てみましょう。

/dts-v1/;

/ {
    model = "Example Board";
    compatible = "example,board-v1";
    #address-cells = <1>;
    #size-cells = <1>;

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x40000000>; /* 1GBのRAMを0x80000000に配置 */
    };

    serial@101f1000 {
        compatible = "arm,pl011";
        reg = <0x101f1000 0x1000>;
        interrupts = <1 14>;
        status = "okay";
    };
};

重要なプロパティの解説

  • /(ルートノード): すべてのDevicetreeはルートノードから始まります。JSONのいちばん外側の {} のようなものです。
  • compatible: 最も重要なプロパティです。"ベンダー名, デバイス名"の形式で記述し、OS(LinuxカーネルやZephyr)はこれを見てどのデバイスドライバを紐付けるかを決定します。
  • reg: デバイスのベースアドレスとサイズ(範囲)を指定します。上記例のシリアルポートは、0x101f1000から0x1000バイトのメモリ領域を使用します。
  • status: デバイスの有効/無効を切り替えます。"okay"で有効化され、"disabled"で無効化されます。

dtsとdtsiの違いとインクルード(分割手法)

実際の開発現場では、1つのファイルにすべてのハードウェア情報を記述することはありません。C言語のヘッダファイルのように、ファイルを分割してインクルード(再利用)する手法が一般的です。

  • .dtsi(Includeファイル): SoC(CPU)に内蔵されている共通のペリフェラルやコントローラの定義を書きます。
  • .dts(Boardファイル): そのSoCを搭載した特定の基板(ボード)固有の定義(LED、外付けセンサー、特定のピンの有効化など)を書きます。

基板固有のDTSで、共通のDTSIの設定を上書きする例:

/* myboard.dts */
#include "soc-base.dtsi"

/* soc-base.dtsiで定義されているシリアル通信ノード(uart0)を上書き */
&uart0 {
    status = "okay";
    current-speed = <115200>;
};

&(アンパサンド)を使ってラベル付けされたノード(この場合はuart0)を参照し、後からstatusを"okay"に変更して有効化するテクニックは非常によく使われます。

コンパイルと稼働中の確認方法

作成した.dtsファイルをカーネルが読み込めるバイナリ(.dtb)にするには、dtcコマンドを使用します。(※Zephyrの場合はビルドシステムであるWestやCMakeが自動で処理してくれます)

dtc -I dts -O dtb -o myboard.dtb myboard.dts

また、現在稼働しているLinuxシステムがどのようなDevicetreeを認識しているかは、実機の /proc/device-tree/ ディレクトリ以下を覗くことで確認できます。ファイルシステムのようにノードがディレクトリ化されており、トラブルシューティング時に重宝します。

まとめ

今回は、LinuxやZephyr OSにおけるDevicetree(デバイスツリー)の基本的な仕組みと書き方について解説しました。この記事の重要なポイントは以下の通りです。

  • 導入の目的: ハードウェア固有の情報をカーネルソースから分離し、保守性と移植性を高めるため。
  • 適用範囲の広さ: Linuxだけでなく、Zephyr OSなどのモダンなRTOSでも標準採用されている。
  • 構造のイメージ: 厳密にはC言語ライクな独自構文だが、「JSONのような階層構造データ」として捉えると直感的に理解しやすい。
  • 3つの基本要素: 人間が読み書きする「DTS」、コンパイルされたバイナリ「DTB」、変換ツールである「DTC」。
  • ファイルの分割: SoC共通の定義は .dtsi に、基板固有の設定は .dts に記述して上書き・インクルードする。

Devicetreeは独特の構文を持つため最初は難しく感じるかもしれませんが、基本ルールと「JSONに似たツリー構造である」というイメージを持てば、ハードウェアとソフトウェアを繋ぐ非常に合理的で強力な仕組みであることが分かります。

まずは手元のRaspberry PiやZephyr対応の評価ボードなどのDTSファイルを眺めながら、実際のハードウェア構成がどのようにコードへ落とし込まれているのかを確認し、Devicetreeへの理解を深めていきましょう。

コメント

タイトルとURLをコピーしました