Linux Device Treeとは 始める時の知識まとめ

Linuxが動作するボードを作ろうと思うとDevice Treeが必須になるみたいです。 殆ど英語でしか書かれていないので、まず日本語のサイトを読んで、ある程度知識を付けてから英語サイトを見ると習得しやすいように感じる。 それでもなかなか難しいので、物覚えの悪い私は随時ここに書き足してます。 Device Treeは覚える事が沢山あるので、抜けや間違いがあるかもしれませんが、一度読んで頂いて英語のサイトと見比べて戴くと分かりやすいのかなと思います。

Device Tree とは

  • ARM Linux向けに使用されているプロパティハードウェア詳細情報が構造体で詳細に記述されたデータ。
  • LinuxでのDevice Treeの主な目的は、non-discoverable device(=platform device =non-enumerated device)の情報をLinuxに渡すための新しい仕組み。 組込みLinuxのデバイスはPCI等を使わなければ大体 non-discoverable device。
  • Linux2.6まではハードコーディング(.c :C言語のプログラム)で組み込んでいたが、今ではDevice Treeのメリットが大きいので、ハードコーディングは非推奨。
  • Device Treeを使えば、ハード変更によってLinux Kernelを変更せずに使える。
  • デバイスのベースアドレスや、クロック、割り込み番号といった、ハードウェア固有のプロパティをカーネルから分離しデバイスドライバの再利用性を高めることが目的。 
  • ドライバは、デバイスツリーを読みこんでハードウェアのアドレスや割り込み番号等取得し、ハードウェアの制御の仕方を記述。 デバイスツリーだけ読めば、異なるデバイスでも同じドライバで動作できる。
  • *.dts :Device Tree Source  最上位のファイル。 dtbに変換する時指定するファイル。(最上位なのでボードレベルの定義ファイル。)
  • *.dtsi:.dtsへインクルードするファイル。各デバイスやSoC(System on chip)の情報が含まれる。
  • #includeで.dtsiや.hファイルをインクルードする。
  • *.dtb:Device Tree Blob,dtsがコンパイルされたバイナリファイル。 ブート時にKernelに渡す。 FDT:Flattened Device Treeと呼ばれる事もある。
  • DTC:Device Tree Compiler
    • $ dtc -O dtb -o foo.dtb foo.dts // .dts を dtb にコンパイル
    • $ fdtdump foo.dtb // .dtb を .dts に逆コンパイル
    • OR $ dtc -I dtb -O dts -s foo.dtb // .dtb を .dts に逆コンパイル 
      -sはnode, propertyをsortして出力
  • DTO:Device Tree Overlay …Kernelが読み込んだ Device Tree Blobを上書きするDevice Treeの総称。
  • *.dtbo:Device Tree Blob Overlay… DTOの実行形式のファイル拡張子。
  • linux-<version#>/arch/arm/boot/dts/に配置されている
  • ブートローダーがdtbをメモリ上(RAM:DDR)に配置
  • 起動したとき、U-BOOTなどのブートローダがDevice Tree(dtb)をメモリ(DDR)にロードして、KernelがロードされたDevice TreeをKernel空間に読み込む。
  • Kernel空間に読み込まれたDevice Treeは、Kernel空間で動作するKernelモジュール(ドライバ)で読み出す事が出来る。
  • .dtbを入れ替えれば、同じLinux Kernelで異なるボードを動作させる事が出来る。
  • .dtsには.dtsiや.h等の多くのインクルードファイルがあり、.dtsiにも更にインクルードファイルがあり、ファイル構成も階層化されている。 また、.dtsiや.dtsファイルで後からnodeやpropertyを上書き変更・削除・追加等が出来るので、基のチップの.dtsiを読みこんで繋げながら新しい仕様に合うよう微調整する。
  • .dtsが基板のイメージで、インクルードする.dtsiがそれぞれのチップの情報の様な感じ。
  • Device Treeは、ハードウェアに依存しないファームウェアのIEEE規格Open Firmwareの推奨にできるだけ準拠するようになっている。 なので、Device Treeの情報を読み込む為のC言語ライブラリもof_で始まるメソッドやマクロやファイル名になっている。

Device Treeの書き方

  • dtsの構成要素はnodeとpropertyの2つだけ

node

  • nodeはデバイスやバスを表す。
    • node-name@unit-addressでunit-addressはそのデバイスの実際のアドレスを示す。reg propertyがあるデバイスは、reg propertyの最初のaddressをnode-name@でunit addressを示さなければならない。 アドレスはuint32 16進数で表す。 64bit addressの時もunit32を2つ使って表す。
    • node-name@0,0や1,0で表されている場合は、チップセレクト番号とオフセットを表している。
    • node-nameは、、1~31半角英語大小文字と数字と『, . _ + -』が使える。しかし、慣習的に『_』は使わず、『-』を使い、アルファベットは小文字だけ使う。
    • 出来るだけ用意されている推奨のGeneric Namesを使うこと。(Device Tree Specifications参照)
    • コアなどはcpu@0,cpu@1等、node-nameは同じでもアドレスで区別する。
    • nodeは各種属性情報のpropertyとchild nodeを持つことができる。
    • nodeにlabelを付ける事ができる。
    • label name: node name@unitaddress の形式で、labelの後には『:』が続く。
    • child nodeは&label名でparent nodeを参照できる。あるデバイスAがPLLクロックBを使う場合、PLL Bがparent nodeでデバイスAがchild-nodeとなりchild-nodeのデバイスAのclocks propertyでPLL Bを使うと&label名で関連づける。
    • ルートnodeで先頭に&が付いたnode-nameは、その下のpropertyが既に定義されている値を上書き(override)する時に使われる。 また#includeしているデバイスツリーに定義されたnode-nameと同じ場合、そのnodeを参照し、値を上書き(override)する。
    • 例えば、a.dtsiとb.dtsi 共にc.dtsにincludeされる場合、b.dtsi内に、i2c0 : i2c@44e0b000というノードがあり、a.dtsでルートノードとして&i2c0を記述すると、b.dtsiのi2c@44e0b000を参照して、Property Valueを上書きできる。
    • トップレベルで#includeしているdtsiファイルは絶対に変更(編集)せず、基板専用のでdtsファイル内で参照(&)を使ってdtsi内の値を上書きする事。 
    • node-nameには以下を使う(ePAPR 2.2.2に全node-nameのリストが記載されている)
      • / ルートノード
      • cpu
      • memory
      • ethernet
      • serial
      • uart
      • i2c
      • spi
      • flash
      • gpio
        • gpio-controller nodeは、gpio-controller; の空propertyと#gpio-cells propertyが必要。
      • interrupt-controller
      • external-bus
      • pci
      • aliases
      • chosen
        • 実際のデバイスではない。
        • FirmwareとOS間のデータやり取り
        • bootargsでKernelに設定値を渡せる。(uEnv.txtやboot.scrより優先)
        • 通常、記述する事はないが、ブート時に.dtsに書き込まれる。
      • soc
      • expgpio
      • firmware等など
    • Device Tree記述の始まりは、/(ルートnode)で始まる。
    • 各nodeの下にぶら下がるnodeはchild node。
    • child nodeから見た上位のnodeはparent node。
    • 同じparent nodeにぶら下がり同列のnodeはsibling nodeと呼ばれる。
    • node名は、Kernel Device Driver sysfsプログラムでdeviceとして使われる。

Path Names

Linuxのディレクトリのようにルートノード『/』から始まって、子孫(descendant) nodeと『/』で区切ってつなげる。

  • /node-name-1/node-name-2/node-name-N
  • 例 /cpus/cpu@1
  • unit addressなしで明確にユニークデバイスを表現出来る場合はunit addressは不要。 しかし、もし明確でなければ、動作保証はないのでunit addressを付ける方がよい。

Property組み込み

  • propertyはproperty-name = valueで表わされ、アドレスや割り込み番号等のハードウェア情報を記述する
  • property名が『#』で始まるのは、number ofの意味と同じ。そのproperty名が示すものの数を表す。
  • ラベルを追記できる。 label:
    • [label:] proerty-name =value;
    • [label:] proerty-name;
    • &labelとするとnodeのphandleとなり、labelをpoint(&は参照と同じ)する。 ”-@”オプションでコンパイルすればオーバーレイファイルからの参照も可能。

Property Valueの型

property-name=value; で設定する。 各Property ValueはByte単位のアドレスとなり、0~複数byteで構成される。

  • <empty>:値なし。 例: ranges; 『=』もつかない。
  • <u32>:符号なし32bit整数(big-endian)<0x11223344>の16進数(Hex)で表現される。 アドレスの低い方から、『0x11』『0x22』『0x33』『0x44』配置される。 『89』と書いたら『0x59』に変換されてたので10進表記も可能。
  • <u64>:符号なし64bit整数(big-endian)<0x11223344 0x55667788>とu32 2つで表現される。
  • <string>:””で括る。  compatible=”arm,cortex-a9″; 実際、末尾の”9″(0x39)の次にNULL Termination “\0″(0x00)が自動的に入る。
  • <stringlist>:文字列を列挙したもの。”hello”,”ichiri”,”john”とstringを『,』カンマで区切る。 区切りと末尾にはNULL Termination “\0″(0x00)が自動的に入る。
  • <phandle>:pointer handle u32で表現。 デバイスツリー内の他のnodeを参照する為のphandle値。参照可能なnodeはユニークなphandle値を定義する。 ラベルを指定するか、phandleを記述していなかったら、phandle番号が自動的に割り振られる。
  • <prop-encoded-array>: Propertyによって表現形式は異なるので各Propertyの詳細を参照する必要がある。 Array/Cell-list:<>で括る reg=<0xffd04000 0x1000>; < >内の数値はuint32で、複数の数値がある場合は、それぞれをcellと呼ぶ。 addressはこの表記を使う。
  • Binary Data:[]で括る mac-address=[12 34 56 ab cd ef];
  • 複数データ:,で連結 mixed-property ="this is string", [0x01 0x23 0x45 0x67], <0x12345678>;

Property種類

  • property nameには標準と非標準がある。
  • property nameは、1~31半角英語大小文字と数字と『, . _ + – ? #』が使える。しかし、慣習的に『_』は使わず、『-』を使い、アルファベットは小文字だけ使う。 
  • よく使われる標準property-name
    1. compatible
      • 超重要
      • Property value type <stringlist>
      • cpu、ethernet、gpioなど全てのdeviceに記述する。
      • 推奨される書き方はcompatible=”manufacturer, model”, “manufacturer, model”;
      • Device Tree内のcompatible情報はKernelがマッチTableに登録する。 ドライバにもcompatible propertyがあり、ドライバがインサートされKernelに登録されると、Kernelが登録されているDevice Tree compatibleを検索し、マッチするとKernelがそのドライバのprobe()を発動しドライバーの動作が始まる。 
      • compatible = “ti,am335x-bone-black”, “ti,am335x-bone”, “ti,am33xx”; の場合、OSは左から参照してマッチするdriverを探す。
      • driverの中にKernelのdriver tableに登録するdevice名がある。 このdevice名とcompatibleの値がマッチしたら、そのdevice名があるdriverを紐付ける。
      • 2つの値でnamespaceを形成する。
    2. model
      • Property value type <string>
      • “manufacturer, model” で表す。
      • model = “TI AM335x BeagleBone Black”;
    3. status
      • Property value type <string>
      • “okay”, “disabled”,”reserved”,”fail”,”fail-sss”など決まった形式で表す。
      • sssはデバイス特有の値で検知されたエラー内容を表す
      • status propertyがなければ”okay”として扱われる。
      • status = “disabled”; が設定されると、compatibleでマッチしても自動的にprobeが実行さなくなり、結果このデバイスはロードされなくなる。
    4. phandle —pointer handle
      • nodeを示すid番号を設定。
      • 通常はphandleを書かない。
      • phandleでidを書かなければ、DTCコンパイラがidを割り付ける。
      • <&xxx>という書式でlabel名 xxx:を持つnodeを指定できる。 <&xxx>と書かれたところがphandle。
      • nodeを示すid番号を設定。
      • 通常はphandleを書かない。
      • phandleでidを書かなければ、DTCコンパイラがidを割り付ける。
      • <&xxx>という書式でlabel名 xxx:を持つnodeを指定できる。 <&xxx>と書かれたところがphandle。
    5. #address-cell,#size-cells
      • Property value type <u32>
      • child device のreg property値のaddressの個数と、各addressに対するsizeの個数を表す。
      • #address-cells = <1>;
      • #size-cells = <1>;
      • 上記の場合、child nodeでreg=<0x4600 0x100>; の場合、address は1個、sizeも1個指定なので、0x4600がaddressで0x100がsizeとなる。
      • #address-cell,#size-cellsは、デバイスツリー階層の子に引き継がれる。
      • cpuなどは、#address-cells=<1>; #size-cells=<0>;
    6. reg
      • 超重要
      • Property value type <prop-encoded-array>
      • nodeのマッピングの詳細かユニット番号を記述する。
      • メモリマップデバイス
        • CPUから直接アクセスできてメモリ領域がある場合、先頭アドレスと領域を指定する。 DDR、Flashなど。
        • reg =<address length>; or <chip-select-number offset length>;
        • reg = <0x4b000000 0x1000000>;
        • #address-cells = <2>; で #size-cells=<1>; の時は、reg =<address1 length1 address2 length2>;となる。 アクセス領域が別れているときなど。
        • SoCなどで20byteブロックがoffset 0x3000にあり256byteブロックがoffset 0xFE00など複数ある場合
        • reg = <0x3000 0x20 0xFE00 0x100>;
      • メモリマップデバイス出ない時
        • SoCの複数コアのCPUやデバイスドライバ経由でアクセスするシリアル通信(I2C、SDIO、SPI、UART等)等の場合は、ユニット番号となる。
        • cpu@0 {reg=<0>;};
        • cpu@1 {reg=<1>;};
        • cpu@2 {reg=<2>;};
        • cpu@3 {reg=<3>;};
        • i2c@3 {reg=<3>;};
        • この場合、親ノードでaddress-cells = <1>; #size-cells=<0>; を指定しておく。
      • reg propertyがないnodeは@unit addressは省略しなければならない。また、 unit addressなしでもその階層でユニークなnode nameでなければならない。
    7. virtual-reg
      • Property value type <u32>
      • デバイスの仮想と物理アドレスのマッピングをクライアントプログラムに受け渡す。
      • あまり使われていない。 /linux/arch/arm/boot/dts/ 内のデバイスツリーファイルでは使われていなかった。 $ grep -rl virtual-reg *
    8. ranges
      • Property value type <empty> or <prop-encoded-array>
      • <child-bus-address parent-bus-address length> の3つの値で表される。
      • SoC等複雑な構成の場合、parent-nodeからchild-node内で使うアドレスを指定する。
      • ranges = <0x0 0xe0000000 0x00100000>; の場合、parent-nodeからの物理アドレスは、0xe0000000だが、child-node内では0x0として扱える。 child-nodeのアドレス記述を簡素化できる。
    9. dma-ranges
    10. interrupts
      • Property value type <prop-encoded-array>
      • 割込を出力するnodeのproperty。
      • 値は 割込指定子としてinterrupt domain rootで定義されたフォーマットの任意の番号。
      • 割込を送る相手が1個の場合<>内はu32値を2つ。
        • 最初の値が割込番号
        • 次の値はエッジ検出かレベル検出などの検出方法
      • Zynqの様に複数コアの割込コントローラの場合の<>内はu32値を3つ。
        • 最初の値が、PPIかSPIか
        • 真ん中の値は割込番号
        • 最後の値は、エッジ検出かレベル検出などの検出方法とcpuへの接続
      • PICの場合は、CPUも1個なのでinterrupts=<0x0A 8>; 0xAは割込番号、8はレベルを表す。
      • Zynq UltraScale+の場合は『arm,gic-400』でinterrupts=<0x1 0x09 0xf04>; と成っていた。 『https://elixir.bootlin.com/linux/v4.3/source/Documentation/devicetree/bindings/arm/gic.txt』によると
        • 最初の0x1:
          • 0: PPI: Per Processor Interrupts
          • 1: SPI Shared Processor Interrupts
        • 0x09は、割込番号
          • SPI用が『0~987』
          • PPI用は『0~15』
        • 0xf04
          • [3:0]の部分、ここでは『4』
            • 1: low-to-high の立ち上がりエッジ検出
            • 2: high-to-lowの立ち下がりエッジ検出(SPIでは無効)
            • 4: active high levelのhiレベル検出
            • 8: active low levelのlowレベル検出(SPIでは無効)
          • [15:8]の部分、ここでは『f』
            • 各ビットが『1』ならそれぞれがCPUに接続される
            • 『0xf』は『0b1111』なので、4つのCPUに接続されている
    11. interrupt parent
      • 割込を出力するnodeのproperty。 割込コントローラのphandleを設定する。
    12. interrupt-controller
      • Property value type <empty>
      • 割込コントローラnodeと割込を受けるnodeのproperty。 gpioなど。 値は空のproperty。
    13. #interrupt-cells
      • Property value type <u32>
    14. interrupt-map
      • Property value type <prop-encoded-array>
    15. interrupts-extended
      • Property value type <phandle> or <prop-encoded-array>
      • 割込コントローラやCPUが複数ある場合、相手のphandleを指定するため、interrupts propertyの代わりに使う。 値はu32値を3つ使う。 最初の値は、相手のphanlde値(数字か&で始まるラベル名)。 後の2つの値はinterrupt propertyと同じ。
      • interrupts propertyを上書きする。
    16. device_type
      • cpu, memory, memory-controller, pci, soc, cache, display, interrupt-controller, serial, adb 等等
      • of_find_node_by_type()

Addressing

  • 各アドレスが割り当てられるデバイス node内にreg=< >で記述する。
  • addressは<u32>の16進(Hex)で記述。 <0x1234abef>等、x含めアルファベットの16進数は全て小文字で記述すべき。 unit-addressにはleading zeroは使わない。
  • 64bit systemの時は、<u32>を2つ使うので、#address-cells, #size-cellsに<2>としてよい。
  • 次のようなタプルで表される。
    reg = <address1 length1 [address2 length2] [address3 length3] … > —32bitの場合 #address-cells=<1>; #size=cells=<1>
  • reg = <address1up address1low length1 [address2up address2low length2] [address3up address3low length3] … > —64bitの場合。 メモリ長が32bitを超える時は、lengthもセルを2つ使用できる。 #address-cells=<2>; #size=cells=<1>
  • 通常は先頭address1とlength1だけだが、gpioの様に飛び飛びのアドレスやサイズに割り当てられている時は、address2 length2など複数の先頭アドレスと長さを指定できる。
  • #address-cells, #size-cellsは、子のnodeのreg propertyのそれぞれのフィールドにいくつのセルがあるかを示す。
  • CPUからみたバスのアドレスと外部バスから見たアドレスと、分けて記述する事ができる。 例えば、CPUから見たバスの通常のアドレスと、外部バス(子ノード)に接続されたデバイスの外部バス(子ノード)から見たチップセレクト番号など。 しかし、チップセレクト番号はranges propertyを使って各チップセレクト番号(子ノードアドレス)に対するCPU(親ノード)から見たアドレス(親ノードアドレス)の関連付け記述が別途必要。
  • 外部バス(external-bus)下のデバイスでチップセレクトで呼ばれる場合は、システムを表現するベストの方法でアドレスを表記する。
  • 下の例ではアドレスに2つのセルを使用。<0 0 0x1000> 最初の0はチップセレクト番号、2つ目の0はオフセットを表す。
  • rangesでチップセレクト番号にCPUからのアドレスを割り当てる。
  • rangesのlengthとregでのlengthが1:1でないのは、PCIや複数のデバイスでメモリエリアが重なっていて、プログラマブルでアドレスマッピング割り当てる場合など、デバイス100%のレンジを使わない場合等がある為。
  • 子ノードに独自アドレスがあり、その親にranges propertyが無い場合は、その直接の親ノードからしかその子ノードにアクセスできない。
例 https://elinux.org/DeviceDevice Tree Linux_Tree_Usage から引用
    external-bus {
        #address-cells = <2>;
        #size-cells = <1>;
        ranges = <0 0  0x10100000   0x10000     // Chipselect 1, Ethernet
                  1 0  0x10160000   0x10000     // Chipselect 2, i2c controller
                  2 0  0x30000000   0x1000000>; // Chipselect 3, NOR Flash

        ethernet@0,0 {
            compatible = "smc,smc91c111";
            reg = <0 0 0x1000>;
        };

        i2c@1,0 {
            compatible = "acme,a1234-i2c-bus";
            reg = <1 0 0x1000>;
            rtc@58 {
                compatible = "maxim,ds1338";
            };
        };

        flash@2,0 {
            compatible = "samsung,k8f1315ebm", "cfi-flash";
            reg = <2 0 0x4000000>;
        };
    };

割込み Interrupt

  • 割込み信号はノード間で独立して表わされる。
  • interrup-controller, #interrupt-cells, interrupt-parent, interrupsの4つのpropertyで表わされる。
  • interrupt-controller:値を持たない。 割り込みを受取るデバイス(interrupt controller:intc)に記述する。
  • #interrupt-cells:interrupt controllerノードのproperty。interrupts propertyのセルの数を示す。 #address-cellsや#size-cellsと同様。
  • interrupt-parent:デバイスnodeのpropertyで、デバイスに接続されたinterrupt controllerのphandle(<&intc>等)を含む。Device Treeの範囲({ })内のinterrupt controllerを示す。 interrupt-parentのphandle(<&intc>等)がシステムのデフォルト値になる。
  • interrupts:デバイスnodeのproperty。 Interrupt指定子を含みデバイス上のそれぞれの割込み出力。#interrupt-cells=<2>で、interrupts=<4 2>とは、4は割り込みのライン番号。 2は割り込みとなるパターン。 (int controllerによって変わるのでデバイスのマニュアル参照。以下はGICの例)
    • 1 = low-to-high edge triggered
    • 2 = high-to-low edge triggered (invalid for SPIs)
    • 4 = active high level-sensitive
    • 8 = active low level-sensitive (invalid for SPIs)
  • interrupts =<INT_ID_NUMBER LEVEL/SENSE INT_TYPE TYPE_INFO>;
  • MPICの場合は、4セル使う場合がある。 またlevel senseの番号はGICと若干変わる。
    • 0 = low-to-high edge sensitive type enabled
    • 1 = active-low level sensitive type enabled
    • 2 = active-high level sensitive type enabled
    • 3 = high-to-low edge sensitive type enabled
  • MPICのinterrupt type
    • 0 = normal
    • 1 = error interrupt
    • 2 = MPIC inter-processor interrupt
    • 3 = MPIC timer interrupt

  • iomuxc
    • fsl, pins =<PIN_FUNC_ID CONFIG>; で記述。
  • gpio-keys node — Linuxが持っているGPIOのイベントをkeyに変換する機能。
    • linux,code=<69>; でそのGPIOのピンに信号が来るとNUMLOCK(69)を発生する。
    • linux,code=<KEY_VOLUMEUP>; など、インクルードする.hファイルに書かれている定数名でも構わない。

Labels

  • Labelはnodeやproperty valueに使うことが出来る。
  • 半角大小英、数字と『_』(underscore)で1~31文字。
  • Labelはdtsの中だけで使用され、dtbになるとLabelはFull Pathやphandleに変換される。

書き方

  • 最上位nodeは”/”のみなので、/{…};
  • 最初の行は、/dts-v1/;
  • コメント
    • 1行の時 //
    • 複数行の時 /* . . . */
  • node-nameはethernet, serial, gpio など機能を表す名前を付ける事が推奨されている。 ASCII文字で最長31文字。
  • 同じ階層に同じnode-nameがある場合は、同じnodeとみなす。
  • 後の定義が上書きする。
  • /delete-node/ node-name@unit-address, /delete-property/ property名で定義を消せる。

aliases

/aliasesノードでalias名を定義できる。 aliasノードはルートノードに『/aliases』として定義すること。

aliasで使えるのは、半角で小文字英、数字、『-』(ハイフン)のみの、1~31文字。

Devicetree Specification, Release v0.3-40-g7e1cc17から引用
aliases{
    serial0= "/simple-bus@fe000000/serial@llc500";
    ethernet0= "/simple-bus@fe000000/ethernet@31c000;
}

の場合、クライアントプログラムはserial0を/simple-bus@fe000000/serial@llc500として参照する。

例 https://elinux.org/Device_Tree_Usage から引用
/dts-v1/;
//ここでincludeされているnodeはルートnodeとして取り込まれる。
#include "am33xx.dtsi"
#include "am335x-bone-common.dtsi"
#include "am335x-boneblack-common.dtsi"
#include "am335x-boneblack-hdmi.dtsi"

/ {
    //ここはダミールートnodeとして、includeしているルートnodeをoverrideしている。
	model = "TI AM335x BeagleBone Black";
	compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";  //特にここ

	chosen {
		base_dtb = "am335x-boneblack.dts";
		base_dtb_timestamp = __TIMESTAMP__;
	};
};
{
        node1 {
                a-string-property = "A string";
                a-string-list-property = "first string", "second string";
                a-byte-data-property = [01 23 34 56];
                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>; /* each number (cell) is a uint32 */
                child-node1 {
                };
        };
};

参考

https://www.devicetree.org/specifications/  <—公式サイト、英語ですがそんなに難しくないので、慣れたら簡単に参照出来ると思います。

Device Tree Linux

デバイスツリーについて調べてみた@Qiita

[Linux][kernel] Device Tree についてのまとめ@Qiita

Device Tree詳解 大阪NDS<—上2つとこれを読むと次の段階に入りやすい。

Device Tree Usage elinux.org<—非常にいいですが英語のみ(日本語にしました

Device Tree Reference - eLinux.org

とあるエンジニアの備忘log <—–Headerの詳細等も書かれています。 デバッグ時に役立ちます。

Full Technical description of device tree format  ePAPR v1.1<—ePAPRのコンパイラを使う時用

OPP(Operating Performance Point) どうもSoCなどで電圧や周波数等を指定するみたい。

bindings

既存ドライバに対応したデバイスツリーの書き方は、binding documentを見て作成すること。 必須(Required properties)と推奨とオプショナルと例が記載されている。 BBBのi2cの場合は、以下のリンクの下の./i2c/i2c-omap.txtに記載されている。

  • https://www.kernel.org/doc/Documentation/devicetree/bindings/

#define は使用不可

#includeは使えるけど、#defineは使えないみたいです。 device tree specifications や検索してみましたが見つかりませんでした。(間違っているかもしれません)

LinuxとePAPRコンパイラ

dtsからdtbにコンパイルするとき、コンパイラによって初期値や使える名称が異なる様です。 私は、組込みLinuxなので上記もLinux向けに書いている積もりです。

Device Tree Linux - eLinux.org

デバイスツリー確認

Kernelが使用しているデバイスツリー情報取得

/proc/device-treeの下にデバイスツリーの各nodeがディレクトリとなって、Property nameがファイルとなって存在している。 ファイルをhexdump、strings、cat等で見ると中を見れる。

$ cd /proc/device-tree                                                          
$ ls                                                                            
#address-cells    clk_mcasp0_fixed  leds              opp-table                 
#size-cells       compatible        memory@80000000   pmu@4b000000              
aliases           cpus              model             serial-number             
chosen            fixedregulator0   name              soc                       
clk_mcasp0        interrupt-parent  ocp               sound   # hexdump -C '/proc/device-tree/#size-cells'
00000000  00 00 00 01                                       |....|
00000004

$ strings /proc/device-tree/compatible
ti,am335x-bone-black
ti,am335x-bone
ti,am33xx

$ hexdump -C '/proc/device-tree/compatible'
00000000  74 69 2c 61 6d 33 33 35  78 2d 62 6f 6e 65 2d 62  |ti,am335x-bone-b|
00000010  6c 61 63 6b 00 74 69 2c  61 6d 33 33 35 78 2d 62  |lack.ti,am335x-b|  
00000020  6f 6e 65 00 74 69 2c 61  6d 33 33 78 78 00        |one.ti,am33xx.|    
0000002e                                                                        

$ hexdump -C '/proc/device-tree/model'                                          
00000000  54 49 20 41 4d 33 33 35  78 20 42 65 61 67 6c 65  |TI AM335x Beagle|  
00000010  42 6f 6e 65 20 42 6c 61  63 6b 00                 |Bone Black.|       
0000001b

$ hexdump -C /proc/device-tree/cpus/cpu@0/compatible                            
00000000  61 72 6d 2c 63 6f 72 74  65 78 2d 61 38 00        |arm,cortex-a8.|    
0000000e

★ デバイスツリー逆変換 dtbからdts —よく使います

全てを復活出来るわけではありません、ラベルと参照とinclude情報は消えていて一つの結合されたdtsファイルになります。 ラベル、参照、includeがされているかを確認出来ます。 compatilbleなど、値をカスケードしている場合は、『\0』で置き換えられてしまっています。

// dtc(device tree compiler)で逆変換 下の例では、a.dtsに出力。 nanoかviで閲覧。
$ dtc -I dtb -O dts am335x-boneblack.dtb -o a.dts

// dtc not found の場合
$ sudo apt update
$ sudo apt install device-tree-compilery

Kernel CMD

chosenノード内のbootargsに書き込めば使えます。 但し、boot.srcに記述した内容が上書きされます。

	chosen {
		bootargs = "console=ttyS0,115200 loglevel=8";
		initrd-start = <0xc8000000>;
		initrd-end = <0xc8200000>;
	};

その他メモ

  • cpu
  • cpu-map
    • cluster毎に分ける。 例えば3コアをLinuxにして、1コアをRTOS等。

コメント