リネオブログ

デバイスツリーとサウンド設定(widgets, routing)について学ぶ:前編

2023 年 03 月 24 日   Linux 技術ネタ

目次

  1. はじめに
  2. デバイスツリーとは
  3. dts ファイルのフォーマット
  4. dtbファイルの使用方法
  5. U-Boot でのサポート状況
  6. 参考文献

1. はじめに

Linux には、デバイスツリーというハードウェア固有設定をコンフィグファイルとしてソースコードから切り離す便利な機能がありますが、日本語、英語ともに情報が少なく、見よう見まねで対応されている方が多いのではないでしょうか?

リネオ社内で聞いてみたところ、ネットで検索してもなかなか有力な情報が出てこないため、例えば、『サウンドの widgets と routing プロパティの設定方法 (ルール) が理解できていない。』などコメントをもらいました。
OSS の Linux は、ソースコードを誰でも読むことができる反面、情報が溢れて何が正しいのかわかりづらいケース、公式ドキュメントの説明不足でソースコードを読まないと理解できないケースが多々あります。一方で、ソースコードから機能を読み解くことは、ハードルが高く、容易なことではありません。

本ブログエントリでは、デバイスツリーとはどのようなものかと 1 事例としてサウンドの widgets と routing プロパティの設定方法について紹介します。
本ブログは全2部構成となっており、前編では「デバイスツリーについて」を解説していきます。

2. デバイスツリーとは

2.1 概要

デバイスツリーとはシステムのハードウェア構成を表現するデータ構造で、主にブートローダ等のイニシャルプログラムが OS にハードウェア情報を伝達するために利用します。デバイスツリーをサポートする OS であればこのデータを元にデバイスを識別し、必要なドライバモジュールをロードすることができます。

デバイスツリーの図

2.2 歴史

デバイスツリーは元々 OpenFirmware と呼ばれるファームウェアにおいて OS にシステム情報を受け渡す手段として作られた仕組みです。OpenFirmware は 1980 年代にサン・マイクロシステムズ社の技術者によって設計された OS をロードするためのファームウェアで、PowerPC や SPARC 等のプラットフォームで採用されていましたが、BIOS や u-boot 等とは異なり特定のハードウェアや OS に依存しないオープンな規格です。そのため、OpenFirmware に準拠した実装は複数存在しており、現在では OpenBIOS プロジェクトがフリーソフトウェアとして公開している実装がポピュラーです。
また デバイスツリーを利用するには OS 側のサポートも必要ですが、Linux カーネルでは 2.6.35の時点で PowerPC、SPARC、MicroBlaze の 3 つのアーキテクチャが既にサポートしていました。Linux カーネルのソースコードに「arch/<アーキテクチャ>/boot/dts」ディレクトリが存在し、配下に .dts ファイルがあれば、対象の <アーキテクチャ> は デバイスツリーに対応しています。

2023 年 1 月現在の rolling-stable リリースである linux-6.1.8 では、ARM、Xtensa、x86、C-SKY、RISC-V、MicroBlaze、OpenRISC、ARC、PowerPC、SuperH、NiosⅡ、MIPS の 12 のアーキテクチャのデバイスツリーへの対応が確認できました。

なお u-boot や Linux カーネルでは OpenFirmware からデバイスツリーのデータフォーマットのみを取り入れ、これを fdt(Flattened デバイスツリー) と呼んでいます。

2.3 メリット

デバイスツリーを採用することによって、特に組み込みシステムにおいて次のようなメリットが考えられます。

  • ファームウェアにおけるハードウェアの表現が統一されプラットフォーム向けの実装がし易くなる。
  • マルチプラットフォームに対応したカーネルイメージを構築しやすくなる。
  • カーネルやドライバからプラットフォームやボード依存のコードを減らすことができる。
  • OS は、PCI や USB のような自動認識するデバイスでなくても、デバイスツリーの定義でシステムに必要なデバイスを特定することができる。
  • ハードウェアリヴィジョンの更新によるデバイスドライバのソースコード肥大化を抑制することができる。

2.4 構成

デバイスツリーは、以下 3 つの要素から構成されます。

  • dts (デバイスツリー Source)
    • dtb の元になるテキスト情報
    • XXX.dts, または XXX.dtsi
    • 一般的に、ボード固有定義は dts ファイルに、SoC 共通定義は dtsi ファイルに記載
  • dtc (デバイスツリー Compiler)
    • dts から dtb を生成するコンパイラ
  • dtb (デバイスツリー Blob)
    • デバイス情報の実体 (バイナリイメージ)

デバイスツリーの3要素

2.5 dtb ファイルの生成方法

dtb ファイルの生成方法について紹介します。

● [生成方法1] カーネルのソースツリー上でビルド

Linux カーネルと一緒にビルドする場合です。コンフィギュレーションファイル (.config) の設定が必要です。

以下を実行すると、コンフィギュレーションファイル (.config) で指定された SoC の dtb が ソースツリーの scripts/dtc 配下の dtc コマンドを使用してビルドされます。
(<アーキテクチャ>,<クロスコンパイラ>はカーネルビルド時と同様に対象 SoC に対応するアーキテクチャとクロスコンパイラを指定します)。

$ make ARCH=<アーキテクチャ> CROSS_COMPILE=<クロスコンパイラ> dtbs 

● [生成方法2] dtc コマンドでビルド

dtb ファイルを単体でビルドする場合です。
dtc コマンドを以下のように使用することで、dts ファイルからdtb ファイルをビルド可能です。

$ dtc -O dtb -I dts -o xxx.dtb xxx.dts
オプション オプション指定例 説明
-O -O dtb 出力ファイル形式を指定する
-I -I dts 入力ファイル形式を指定する
-o -o xxx.dtb 出力ファイル名を指定する

● [確認方法]dtb の逆コンパイル

dtc コマンドでは出力と入力を逆にすることで、dtb ファイルから dts ファイルを生成することができます(逆コンパイル)。

プロパティは後から上書き可能であるため、複数の dts/dtsi ファイルから dtb ファイルがビルドされる場合、最終的にどのようなプロパティになっているのかの理解は容易ではありません。一方、逆コンパイルのアウトプットは1つのファイルになるため、確認が容易です。
出力フォーマットを dts、入力フォーマットを dtb、出力ファイル名に dts ファイル名、対象ファイルに dtb ファイルを指定します。

$ dtc -O dts -I dtb -o xxx.dts xxx.dtb 

3. dts ファイルのフォーマット

3.1 dts ファイルの基本フォーマット

dts ファイルの基本フォーマットについて説明します。

以下の例では、ノードを赤字、プロパティを青字としています。
dts は、複数のノードで構成されたツリーデータ構造になっています。

/ {
    node1 {
        a-string-property = "A string";
        a-string-list-property = "first string", "second string";
        a-byte-data-property = [0x01 0x23 0x34 0x56];
        child-node1 {
            first-child-property;
            second-child-property = <1>;
            a-string-property = "Hello, world";
        };
        child-node2 {
        };
    };
    node2 {
        an-empty-property;
        a-cell-property = <1 2 3 4>;
        child-node1 {
        };
    };
};

● ノード

ノードは、ボードに実装された具体的なデバイス、バスまたはその集合を表します。

  • プロパティまたは子ノードが含まれます。
  • "/" はルートノードを表しており、ツリーはこのノードを起点とします。
  • 各ノードはノード名とそれに続く "{}" で括られた要素で構成されます。
  • 各ノードは最後のセミコロンによって区切られます。

● プロパティ

プロパティは、デバイスやバスに関する詳細情報で、デバイス名、レジスタのアドレス、サイズ等を指定します。

  • プロパティ名と設定値を "=" でつなげて記述します。
  • 最後のセミコロンによって区切られます。
  • 書式には設定する値のタイプによっていくつかの種類があります。
  • 文字列型:string-property = "a string"
  • 文字列の集合:string-list = "red fish", "blue fish";
  • 32bit 符号なし整数値:cell-property = <0xbeef 123 0xabcd1234>
  • バイナリデータ:binary-property = [0x01 0x23 0x45 0x67]

3.2 dts フォーマット:代表的なノード

代表的なノードとして、cpus ノードと memory ノードを紹介します。

3.2.1. cpus ノード

システムに実装された CPU を表すノードで、ルートノードに必ず一つ必要なノードです。

以下は、Cortex-A15 コアを二つ実装した場合の例です。
親ノードである cpus とコア数分の子ノード cpu@N で構成されます。
@ の後ろの番号はコア番号です。

...
cpus {
        cpu@0 {
                compatible = "arm,cortex-a15";
        };
        cpu@1 {
                compatible = "arm,cortex-a15";
        };
};
...
3.2.2. memory ノード

ボードに実装されたメモリを表すノードで、ルートノードに必ず一つ以上必要なノードです。

以下は、メモリを 1GB 搭載していて、物理アドレスの先頭 (0x0番地) にマッピングされている場合の例です。
reg プロパティで、先頭アドレスとサイズを指定しています。

...
memory {
	device_type = "memory"
	reg = <0x00000000 0x40000000>;
};
...

3.3 dts フォーマット:代表的なプロパティ

3.3.1. compatible プロパティ

ハードウェアを特定する名称を設定します。
後方互換性のあるハードウェアを複数指定でき、まだ Linux カーネルで未サポートだった場合、互換性のある他のドライバや初期化コードが自動的に選択されるようになります。 例えば、Beagle ボードの場合、OMAP3 ファミリの SoC を搭載しているため互換情報として "ti,omap3" を指定できます

(例:Beagle ボードの場合)

...
compatible = "ti,omap3-beagle", "ti,omap3";
...
3.3.2. reg プロパティ

制御レジスタや IO メモリ等、デバイスのレジスタ情報を設定します。 通常、アドレスとサイズをセットで定義します。
※前がアドレスで後がサイズ

例の場合 0xd4200000 番地から 2MB (0x200000) 分のレジスタがマッピングされています。

...
reg = <0xd4200000 0x00200000>;
...
3.3.3. interrupts プロパティ

デバイスが生成する割り込みを識別する値で、デバイスの割り込み番号や割り込み検出モードなどを設定します。

複数の割り込み番号を持つデバイスの場合はスペースで区切って列挙します。 例の場合、イーサネットデバイスの interrupt プロパティとして、割り込み番号= 30、LOW レベル割り込み検出を設定しています。

...
ethernet@gpmc {
        reg = <7 0 0xff>;
        interrupt-parent = <&gpio5>;
        interrupts = <30 IRQ_TYPE_LEVEL_LOW>;
};
...

3.4 dts フォーマット参照情報

dts フォーマットとして、代表的なノードとプロパティを紹介しましたが、dts フォーマットの更に詳しい情報は デバイスツリー の Wiki ページ に記載があります。

4. dtbファイルの使用方法

● dtb ファイルが独立している場合

U-boot → Linux(ARM) の場合、bootm コマンドの引数として dtb アドレスを渡します。

=> bootm <kernel load addr> - <dtb load addr>

● dtb ファイルが Linux カーネルと一体化している場合

Linux のカーネルコンフィグレーションでカーネルとの一体化を指定してビルドします。
※ARM の場合は、CONFIG_ARM_APPENDED_dtb

bootm コマンドの引数としてカーネルイメージ+ dtb ファイルをロードしたアドレスを渡します

=> bootm <kernel+dtb load addr>

5. U-Boot でのサポート状況

5.1 u-boot の fdt サポート

u-boot には少なくとも v1.3.0 版の時点で Flattened デバイスツリーライブラリ (libfdt) のコードがマージされており、デバイスツリー の情報を利用することが可能でした。u-boot ソースツリーでは lib/libfdt ディレクトリに実装されており、特定のプラットフォームで利用可能となっています。libfdt が有効なプラットフォームの場合 u-boot に fdt コマンドが追加され、u-boot のコマンドラインやスクリプトから dtb の参照、設定等ができるようになります。
fdt のコマンドとその役割を下表に示します。

コマンド 説明
fdt addr <addr> [<length>] dtb のアドレス(とサイズ)を設定する
fdt move <fdt> <newaddr> <lengthv <fdt> にある dtb 情報を <newaddr> に<length> 分コピーする
fdt resize dtb のサイズを 4KB アラインされたサイズまでパッディングする (fdt ヘッダの情報も更新される)
fdt print <path> [<prop>] 指定された <path> 以下の fdt 情報を再帰的に表示する
fdt list <path> [<prop>] 指定された <path> 以下の fdt のツリー構造を再帰的に表示する(プロパティは表示しない)
fdt set <path> <prop> [<val>] 指定されたパスのプロパティの値を <val> に変更する
fdt mknode <path> <node> 指定されたパスの下に新たなノードを追加する
fdt rm <path> [<prop>] 指定されたパスのノード (またはプロパティ) を削除する
fdt header fdt ヘッダ情報を表示する
fdt bootcpu <id> ブート CPU を CPUID で指定する
fdt memory <addr> <size> memory ノードを新たに追加、または更新する
fdt rsvmem print 現在のメモリ予約状況を表示する
fdt rsvmem add <addr> <size> メモリの予約領域を追加する
fdt rsvmem delete <index> メモリの予約領域を削除する
fdt chosen [<start> <end>] <start> から <end> にある情報を chosen ノードの値として追加する

5.2 u-boot と Linux カーネルのインターフェイスについて

fdt はデータフォーマットだけを規定するものであり、インターフェイスについては特に規定はありません。そのため、dtb をブートローダからカーネルに受け渡す方法はアーキテクチャによって異なります。

ARM アーキテクチャの Linux カーネルでは従来から ATAG インターフェイスと呼ばれる方法を採用しており、Linuxカーネルのエントリポイントに入る際、r0、r1、r2 の汎用レジスタに下記のような 3 つの引数を渡してカーネルを起動しています。

r0 0
r1 マシンタイプ番号
r2 RAM上のタグ付きリストの物理アドレス

マシンタイプ番号はボード固有の識別番号で、Linux カーネルソースツリーの arch/arm/tools/mach-types にリストされています。Linux カーネルはこの番号が一致しなければ起動出来ない仕組みになっています。タグ付きリストとはブートローダが作成する情報で、ATAG_CORE(0x54410002) から始まるリスト情報です。Linux カーネルのコマンドラインのアドレス (ATAG_CMDLINE) 、システムメモリの開始アドレス、サイズ (ATAG_MEM) 等の情報が含まれています。

以上のインターフェイスが fdt を使用する場合には次のように変わります。

r0 0
r1 マシンタイプ番号
r2 RAM 上の dtb の物理アドレス

つまり r2 で渡すアドレスが dtb のものに変わります。Linux カーネルでは r2 のアドレスに格納された値をチェックし、ATAG_CORE(0x54410002) または dtb のマジック番号 (0xd00dfeed) よりどちらの情報かを判別します。

ユーザが u-boot の bootm コマンドでLinuxカーネルを起動する際には、下記のように 3 番目の引数に dtbのアドレスを指定することでLinuxカーネルに dtb を受け渡すことが出来ます。なお2番目の引数は initrd のアドレスで、使用しない場合は下記のようにハイフンを指定します。

ハイフンを指定

参考文献

予告!2024年9月スタート、セキュリティ基礎講座
Vigiles サポート
Yocto Project よもやま話
Yocto よもやま話 第 14 回 「Yocto 4.3 Nanbield リリース」
Yocto よもやま話 第 14 回 「Yocto 4.3 Nanbield リリース」

2024 年 03 月 26 日 Yocto Project よもやま話

Yocto よもやま話 第 13 回 「Yocto Project の最新動向 2023 夏」
Yocto よもやま話 第 13 回 「Yocto Project の最新動向 2023 夏」

2023 年 07 月 25 日 Yocto Project よもやま話

Yocto よもやま話 第 12 回 「Yocto Project 始めます その 2」
Yocto よもやま話 第 12 回 「Yocto Project 始めます その 2」

2023 年 06 月 20 日 Yocto Project よもやま話

Linux 技術ネタ
RISC-Vについて学ぶ-後編
RISC-Vについて学ぶ-後編

2024 年 01 月 10 日 Linux 技術ネタ

RISC-Vについて学ぶ-前編
RISC-Vについて学ぶ-前編

2023 年 12 月 12 日 Linux 技術ネタ

イベントレポート
EdgeTech+ 2023 出展レポート
EdgeTech+ 2023 出展レポート

2023 年 12 月 14 日 イベントレポート

EdgeTech+ West 2023 出展レポート
EdgeTech+ West 2023 出展レポート

2023 年 08 月 09 日 イベントレポート

リクルート
新卒採用、絶賛募集中!
新卒採用、絶賛募集中!

2023 年 05 月 30 日 リクルート

国立大学オンライン研修レポート 2022
国立大学オンライン研修レポート 2022

2022 年 09 月 27 日 リクルート

北小野通信
ソリューション統括部
シリコンバレー探検記 2019 ~番外編~
シリコンバレー探検記 2019 ~番外編~

2019 年 12 月 10 日 ソリューション統括部

シリコンバレー探検記 2019 ~後編~
シリコンバレー探検記 2019 ~後編~

2019 年 12 月 10 日 ソリューション統括部

シリコンバレー探検記 2019 ~前編~
シリコンバレー探検記 2019 ~前編~

2019 年 12 月 10 日 ソリューション統括部

マーケティング統括部
大成功決起大会!!(ET2019)
大成功決起大会!!(ET2019)

2019 年 12 月 13 日 マーケティング統括部

ESEC 2019 決起大会
ESEC 2019 決起大会

2019 年 04 月 25 日 マーケティング統括部

シリコンバレー探検記 その 2
シリコンバレー探検記 その 2

2018 年 12 月 18 日 マーケティング統括部