🐧 Linux 総合学習プラットフォーム
ブートローダ U-Boot ・ 上級

デバイスツリー(DTB)の役割

ARMなどの組込み環境では、接続されている周辺機器をカーネル自身が自動検出できないため、ハードウェア構成を外部データとして与えます。これがデバイスツリーで、人が書く .dts をコンパイルした .dtb(バイナリ)をU-Bootがメモリへ読み込み、起動時にカーネルへ渡します。CPU・メモリ量・各ペリフェラルのアドレスや割り込み番号などが木構造で記述されます。U-Bootの fdt コマンドを使えば、ロード済みDTBの内容を確認したり、bootargs などのノードを起動直前に書き換えたりできます。

x86のパソコンでは、接続されている周辺機器をOSがある程度自動で見つけられる仕組み(PCIやACPIなど)が整っています。ところがARMをはじめとする多くの組込みSoCでは、どんな周辺機器がどのアドレスにつながっているかを、ハードウェア側から自動で問い合わせる標準的な手段がありません。同じCPUコアを使っていても、基板ごとに搭載するメモリ量も、つながっているセンサやコントローラも、その配線も千差万別です。この「カーネルには分からないハードウェアの構成」を、外部データとしてカーネルに教えてあげる仕組みが、デバイスツリー(Device Tree)です(boot-dtb)。これがあるおかげで、一つのカーネルバイナリを、デバイスツリーだけ差し替えることで複数の異なる基板で動かせるようになります。

.dts と .dtb

デバイスツリーには二つの形があります。人間が読み書きするテキスト形式が .dts(Device Tree Source、デバイスツリーソース)で、これをコンパイルして得られるバイナリ形式が .dtb(Device Tree Blob、デバイスツリーブロブ)です。.dts はC言語のソースコードのようなテキストファイルで、開発者がエディタで編集します。これを dtc(Device Tree Compiler)というコンパイラにかけると、機械が効率よく読める .dtb が生成されます。Linuxカーネルのソースツリーには各ボード用の .dts が含まれており、カーネルをビルドすると対応する .dtb も一緒に作られます。U-Bootが起動時にメモリへ読み込むのは、このコンパイル済みの .dtb のほうです。テキストの .dts で書いて、バイナリの .dtb で渡す——この二段構えを押さえておきましょう。共通部分を切り出した .dtsi(インクルード用の断片)を取り込んで、基板固有の差分だけを .dts に書く、という分割もよく使われ、似た基板どうしで記述を再利用できます。

何が書かれているか

デバイスツリーには、その基板のハードウェア構成が木構造(ツリー)で記述されています。木の根(ルートノード)から枝分かれする形で、CPUの種類と数、搭載されているメモリの開始アドレスと容量、そして各ペリフェラル(シリアル、I2C、SPI、Ethernet、GPIOなど)の情報が並びます。各ペリフェラルについては、それがメモリ空間のどのアドレスに割り当てられているか(レジスタの位置)、どの割り込み番号を使うか、どのクロックで動くか、といった具体的なパラメータが記述されます。カーネルはこの記述を読むことで、「このアドレスにこういうコントローラがあって、割り込みは何番」と理解し、対応するドライバを正しく初期化できます。逆に言えば、デバイスツリーの記述が実際の配線と食い違っていると、その周辺機器は動きません。ハードウェアとソフトウェアをつなぐ「設計図」のような役割を担っているわけです。

U-Bootがカーネルへ橋渡しする

デバイスツリーは、ブートローダとカーネルの連携において要となる存在です(boot-bootloader)。流れを整理すると、まずU-Bootがストレージやネットワークから .dtb をメモリの所定のアドレス(fdt_addr_r など)へ読み込みます。そして前のトピックで見たとおり、カーネルを起動するコマンド(bootz や booti、bootm)に、カーネルのアドレスと並べてこのDTBのアドレスを引数として渡します。これによりカーネルは、起動した直後に渡されたアドレスからデバイスツリーを読み取り、自分が載っているハードウェアの全体像を把握します。U-Boot自身も、デバイスツリーを使って自分が動くハードウェアを認識する作りになっていることが多く、デバイスツリーはブートローダとカーネルの両方で共有される、いわばハードウェア記述の共通言語になっています。

fdt コマンドで操作する

U-Bootには、メモリへ読み込んだデバイスツリーを確認・編集するための fdt コマンドが用意されています(FDTは Flattened Device Tree、デバイスツリーをメモリ上に展開した形式を指します)。まず fdt addr アドレス で、操作対象とするDTBのメモリ上の位置をU-Bootに教えます。そのうえで fdt print と打つと、読み込まれているデバイスツリーの内容が木構造のまま表示され、どんなノードがあり、各プロパティにどんな値が入っているかを確認できます。特定のノードだけ見たいときは、fdt print /chosen のようにパスを指定します。md(memory display)コマンドで該当アドレスを16進ダンプして、確かにDTBが読み込まれているか(先頭のマジックナンバーなど)を生のバイト列で確かめる、という低レベルな確認の仕方もあります。

fdt コマンドの強力なところは、表示だけでなく、起動の直前にデバイスツリーの内容をその場で書き換えられる点です。たとえば fdt set というサブコマンドを使えば、特定のノードのプロパティを変更できます。よく使われるのが /chosen ノードの bootargs プロパティの書き換えで、これによりカーネルへ渡す起動引数を、デバイスツリー経由で動的に差し込めます。.dtb を作り直さなくても、U-Bootのコマンドだけでハードウェア記述の一部を調整できるため、開発中に「このプロパティを変えたら挙動はどうなるか」を素早く試せます。実務では、基板の改版でペリフェラルの構成が変わったときにデバイスツリーを更新したり、同じカーネルを複数の基板バリエーションで使い回すためにデバイスツリーだけを差し替えたり、といった場面でデバイスツリーの理解が効いてきます。電源直後のチップを起こすブートローダ(boot-bootloader)が、最後にカーネルへハードウェアの設計図であるデバイスツリーを手渡す——この一連の橋渡しを理解することが、組込みLinuxの起動を本当の意味でつかむ鍵になります。

この項目に出てくる用語

デバイスツリー(DTB)でばいすつりー
ハードウェア構成を木構造で記述したデータ。U-Bootが読み込みカーネルへ渡す。
ブートローダぶーとろーだ
OS本体を起動する前に走り、カーネルを読み込んで制御を渡すプログラム。

関連コマンド

fdtbootmmd

▶ 学習アプリでこの続きを学ぶ・演習する