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

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

Device Tree とは

  • ARM Linux向けに使用されているプロパティハードウェア情報が構造体で詳細に記述されたファイル。
  • Device Treeを使えば、ハード変更によってLinux Kernelを変更せずに使える。
  • デバイスのベースアドレスや、クロック、割り込み番号といった、ハードウェア固有のプロパティをカーネルから分離しデバイスドライバの再利用性を高めることが目的。 
  • ドライバは、デバイスツリーを読みこんでハードウェアのアドレスや割り込み番号等取得し、ハードウェアの制御の仕方を記述。 デバイスツリーだけ読めば、異なるデバイスでも同じドライバで動作できる。
  • *.dts :Device Tree Source  ボードレベルの定義ファイル。
  • *.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 -s foo.dtb // .dtb を .dts に逆コンパイル 
      -sはnode, propertyをsortして出力
  • DTO:Device Tree Overlay
  • dtbo:Device Tree Blob Overlay
  • /arch/arm/boot/dts/に配置されている
  • ブートローダーがdtbをメモリ上に配置
  • 起動したkernelからDevie Treeを参照
  • .dtbを入れ替えれば、同じLinux Kernelで異なるボードを動作させる事が出来る。
  • .dtsには.dtsiや.h等の多くのインクルードファイルがあり、.dtsiにも更にインクルードファイルがあり、ファイル構成も階層化されている。 また、.dtsiや.dtsファイルで後からnodeやpropertyを上書き変更・削除・追加等が出来るので、基のチップの.dtsiを読みこんで繋げながら新しい仕様に合うよう微調整する。
  • .dtsが基板のイメージで、インクルードする.dtsiがそれぞれのチップの情報の様な感じ。

Device Treeの書き方

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

node

  • nodeはデバイスやバスを表す
    • node名@addressでaddressはそのデバイスの実際のアドレスを示す。reg propertyがあるデバイスは、node名@でアドレスを示さなければならない。 アドレスはuint32 16進数。
    • node名@0,0や1,0で表されている場合は、チップセレクト番号とオフセットを表している。
    • コアなどはcpu@0,cpu@1等、node名は同じでもアドレスで区別する。
    • nodeは各種属性情報のpropertyとchild nodeを持つことができる。
    • nodeに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の外側で2、&で始まるノード名は、その下のpropertyが既に定義されている値をオーバーライドする時に使われる。 
    • node名には以下を使う(ePAPR 2.2.2に全node名のリストが記載されている)
      • / ルートノード
      • cpu
      • memory
      • ethernet
      • serial
      • uart
      • i2c
      • spi
      • flash
      • gpio
      • interrupt-controller
      • external-bus
      • pci
      • aliases
      • chosen
        • 実際のデバイスではない。
        • FirmwareとOS間のデータやり取り
        • 通常、記述する事はないが、ブート時に.dtsに書き込まれる。
      • soc
      • expgpio
      • firmware

Property

  • propertyはproperty-name = valueで表わされ、アドレスや割り込み番号等のハードウェア情報を記述する
  • property名が『#』で始まるのは、number ofの意味と同じ。そのproperty名が示すものの数を表す。
  • ラベルを追記できる。 label:
    • [label:] proerty-name =value;
    • [label:] proerty-name;
    • &labelとするとnodeのphandleとなり、labelをpoint(&は参照と同じ)する。 ”-@”オプションでコンパイルすればオーバーレイファイルからの参照も可能。
  • よく使われる標準property-name
    1. compatible
      • cpu、ethernet、gpioなど全てのdeviceに記述する。
      • osはcompatibleをみて使用するドライバを決める。
      • valueは、”<manufacturer_name>,<model_name>” で示す。
      • 2つの値でnamespaceを形成する。
    2. status
    3. phandle —pointer handle
      • nodeを示すid番号を設定。
      • 通常はphandleを書かない。
      • phandleでidを書かなければ、DTCコンパイラがidを割り付ける。
      • <&xxx>という書式でlabel名 xxx:を持つnodeを指定できる。 <&xxx>と書かれたところがphandle。 interrupts propertyの記述で
    4. reg =<address length>; or <chip-select-number offset length>;
    5. #address-cell,#size-cells
    6. ranges chip selectのアドレスとlengthを指定する
    7. interrupt-controller
    8. #interrupt-cells
    9. interrupt-parent
    10. interrupts

valueの型

  • Text string:””で括る  compatible=”arm,cortex-a9″;
  • Array/Cell-list:<>で括る reg=<0xffd04000 0x1000>; < >内の数値はuint32で、複数の数値がある場合は、それぞれをcellと呼ぶ。 addressはこの表記を使う。
  • Binary Data:[]で括る mac-address=[12 34 56 ab cd ef];
  • 複数データ:,で連結 mixed-property ="a string", [0x01 0x23 0x45 0x67], <0x12345678>;

Addressing

  • 各アドレスが割り当てられるデバイス nodeないにreg=< >で記述する。
  • addressはuint32
  • 64bit systemの時は、unit32を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/Device_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ファイルに書かれている定数名でも構わない。

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

例 https://elinux.org/Device_Tree_Usage から引用
/dts-v1/;
/ {
        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 {
                };
        };
};

参考

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

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

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

Device Tree Usage elinux.org<—非常にいいですが英語のみ

Full Technical description of device tree format  ePAPR v1.1<—ここから下は読んでませんが、今後更に詳細を調べる時の為にリンクだけ残しています。

Devicetree Specification unknown-rev

devicetree.org

コメント

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