2023 年 03 月 24 日 Linux 技術ネタ
Linux には、デバイスツリーというハードウェア固有設定をコンフィグファイルとしてソースコードから切り離す便利な機能がありますが、日本語、英語ともに情報が少なく、見よう見まねで対応されている方が多いのではないでしょうか?
リネオ社内で聞いてみたところ、ネットで検索してもなかなか有力な情報が出てこないため、例えば、『サウンドの widgets と routing プロパティの設定方法 (ルール) が理解できていない。』などコメントをもらいました。
OSS の Linux は、ソースコードを誰でも読むことができる反面、情報が溢れて何が正しいのかわかりづらいケース、公式ドキュメントの説明不足でソースコードを読まないと理解できないケースが多々あります。一方で、ソースコードから機能を読み解くことは、ハードルが高く、容易なことではありません。
本ブログエントリでは、デバイスツリーとはどのようなものかと 1 事例としてサウンドの widgets と routing プロパティの設定方法について紹介します。
本ブログは全2部構成となっており、前編では「デバイスツリーについて」を解説していきます。
デバイスツリーとはシステムのハードウェア構成を表現するデータ構造で、主にブートローダ等のイニシャルプログラムが OS にハードウェア情報を伝達するために利用します。デバイスツリーをサポートする OS であればこのデータを元にデバイスを識別し、必要なドライバモジュールをロードすることができます。
デバイスツリーは元々 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 デバイスツリー) と呼んでいます。
デバイスツリーを採用することによって、特に組み込みシステムにおいて次のようなメリットが考えられます。
デバイスツリーは、以下 3 つの要素から構成されます。
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
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 { }; }; };
● ノード
ノードは、ボードに実装された具体的なデバイス、バスまたはその集合を表します。
● プロパティ
プロパティは、デバイスやバスに関する詳細情報で、デバイス名、レジスタのアドレス、サイズ等を指定します。
代表的なノードとして、cpus ノードと memory ノードを紹介します。
システムに実装された CPU を表すノードで、ルートノードに必ず一つ必要なノードです。
以下は、Cortex-A15 コアを二つ実装した場合の例です。
親ノードである cpus とコア数分の子ノード cpu@N で構成されます。
@ の後ろの番号はコア番号です。
... cpus { cpu@0 { compatible = "arm,cortex-a15"; }; cpu@1 { compatible = "arm,cortex-a15"; }; }; ...
ボードに実装されたメモリを表すノードで、ルートノードに必ず一つ以上必要なノードです。
以下は、メモリを 1GB 搭載していて、物理アドレスの先頭 (0x0番地) にマッピングされている場合の例です。
reg プロパティで、先頭アドレスとサイズを指定しています。
... memory { device_type = "memory" reg = <0x00000000 0x40000000>; }; ...
ハードウェアを特定する名称を設定します。
後方互換性のあるハードウェアを複数指定でき、まだ Linux カーネルで未サポートだった場合、互換性のある他のドライバや初期化コードが自動的に選択されるようになります。 例えば、Beagle ボードの場合、OMAP3 ファミリの SoC を搭載しているため互換情報として "ti,omap3" を指定できます
(例:Beagle ボードの場合)
... compatible = "ti,omap3-beagle", "ti,omap3"; ...
制御レジスタや IO メモリ等、デバイスのレジスタ情報を設定します。 通常、アドレスとサイズをセットで定義します。
※前がアドレスで後がサイズ
例の場合 0xd4200000 番地から 2MB (0x200000) 分のレジスタがマッピングされています。
... reg = <0xd4200000 0x00200000>; ...
デバイスが生成する割り込みを識別する値で、デバイスの割り込み番号や割り込み検出モードなどを設定します。
複数の割り込み番号を持つデバイスの場合はスペースで区切って列挙します。 例の場合、イーサネットデバイスの interrupt プロパティとして、割り込み番号= 30、LOW レベル割り込み検出を設定しています。
... ethernet@gpmc { reg = <7 0 0xff>; interrupt-parent = <&gpio5>; interrupts = <30 IRQ_TYPE_LEVEL_LOW>; }; ...
dts フォーマットとして、代表的なノードとプロパティを紹介しましたが、dts フォーマットの更に詳しい情報は デバイスツリー の Wiki ページ に記載があります。
● 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>
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 ノードの値として追加する |
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 年 09 月 02 日 Vigiles サポート
2024 年 03 月 01 日 Vigiles サポート
2023 年 08 月 28 日 Vigiles サポート
2024 年 03 月 26 日 Yocto Project よもやま話
2023 年 07 月 25 日 Yocto Project よもやま話
2023 年 06 月 20 日 Yocto Project よもやま話
2024 年 01 月 10 日 Linux 技術ネタ
2023 年 12 月 12 日 Linux 技術ネタ
2023 年 03 月 31 日 Linux 技術ネタ
2024 年 07 月 26 日 イベントレポート
2024 年 07 月 09 日 イベントレポート
2024 年 06 月 03 日 イベントレポート
2023 年 05 月 30 日 リクルート
2022 年 12 月 27 日 リクルート
2022 年 09 月 27 日 リクルート
2024 年 09 月 25 日 信州リネオ便り
2024 年 08 月 20 日 信州リネオ便り
2024 年 08 月 07 日 信州リネオ便り
2019 年 12 月 10 日 ソリューション統括部
2019 年 12 月 10 日 ソリューション統括部
2019 年 12 月 10 日 ソリューション統括部
2019 年 12 月 13 日 マーケティング統括部
2019 年 04 月 25 日 マーケティング統括部
2018 年 12 月 18 日 マーケティング統括部