linux之 DeviceTree基礎

DeviceTree基礎

DeviceTree(以下簡稱DT)用於描述設備信息以及設備於總線之間的層級關係,DT可用於描述絕大多數板級設備的細節,包括CPU、內存、中斷、總線以及外設等,與DT相關的Object有dts、dtsi、dtc、dtb、dt.img。

  • dts:DT源文件稱爲dts文件,Ascii文本文件,一般一個dts文件對應一個Machine,ARM架構下dts文件存放於arch/arm/boot/dts/目錄下
  • dtsi:多個Machine/SoC公用的dt文件,i代表include
  • dtc:DeviceTree Compile,用於將dts文件編譯成二進制dtb文件
  • dtb:DeviceTree Bolb,由dtc編譯dts文件生成的二進制目標文件
  • dt.img:多個dtb文件打包形成dt.img,以適配多個Machine,dts/dtb的結構是標準化的,dt.img有頭信息和多個dtb組成,因爲沒有統一的標準,不同的廠商頭信息可能是不同的

目前Android廠商大都使用kernel + ramdisk.img + dt.img的方式打包成boot.img。

本章將詳細介紹如下內容:

  • devicetree文件結構
  • devicetree語法基礎
  • devicetree文件結構實例解析
  • device tree compile用法介紹

最新內容請參考:lonzoc’s gitbook

DTS文件結構


DTS文件主要由:root-node、child-node、property、include組成

  • root-node: 由’/’表示,DT的Entry Point,所有設備均以子節點的形式處於根節點下
  • child-node: node的形式爲 node-name {};{}中是該node的實際內容,根節點下一般是Platform設備和總線,外設以子節點形式存在於總線類的節點中。如下的示例中,cpus 這個節點位於根節點下,代表着所有cpu,cpu0~x以子節點形式處於cpus下,代表着SoC上所有的cpu
  • property: 屬性,以key-value的形式表示,位於節點中
  • include file: 用於包含其他源文件到dts中,dtsi一般中多個Machine公用的文件(i代表include),h文件在dts中一般用於宏定義
 /include/ "skeleton.dtsi"
 /include/ <dt-bindings/clock/msm-clocks-a7.h>
/ {
        model = "Qualcomm Technologies, Inc. MSM 8226";
        compatible = "qcom,msm8226";
        interrupt-parent = <&intc>;

        cpus {
                #size-cells = <0>;
                #address-cells = <1>;

                CPU0: cpu@0 {
                        device_type = "cpu";
                        compatible = "arm,cortex-a7";
                        reg = <0x0>;
                };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

注: 
1. 並非所有node都代表實際的設備,例如上例中的cpus。 
2. node通常還可以用來表示Runtime Configuration,例如bootargs,boot address

DT語法基礎

這一節將介紹兩個概念:Property,Label and Reference。

Property

屬性(Property)是DT中描述設備的原語,其形式爲:

          Key = Value;

Key的種類有很多種,但通常可以大致分爲:dt保留的key,例如compatible/#address-cell/reg等;以及廠商自定義的key,例如goodix,irq-gpio等。需要明確的是,並非IEEE或者Linux定義了key的種類,key種類由解析devicetree的代碼決定,之所以說compatible/reg這些屬性是保留的是因爲內核中這些key已經約定俗成了。

按key可以表述的數據類型,可以分爲:

  • 字符串:string-prop = “a string”;
  • 字符串列表:string-list = “hi”,”str”,”list”;
  • 整型數據:cell-prop = <0xaa>;
  • 二進制數據:binary-prop = [aa bb f8];
  • 混合數據:mixed-prop = “str”,<0x123>,[ff dd];
  • 布爾類型:bool-porp;
  • 引用類型:ref-prop = <&other-node>

可以看到,devicetree中使用不同的符號包裹數據來表示不同的數據類型;上述數據類型中的key只是爲了表達意思,並非linux devicetree中使用的key;布爾類型的Property只有key沒有value,例如定義了bool-prop這代表這個屬性爲true,若沒定義則代表該屬性爲false

常見的Key及意義

Key 類型 釋義
compatible string 用於device與driver匹配
reg integer 表示設備的地址空間
address-cells integer 設置子節點reg屬性中地址數據的cell數
size-cells integer 設置子節點reg屬性中地址長度的cell數
interrupt-parent reference 指定該設備的中斷連接到的int controller

上表中知識簡單羅列了最常用的Key,實際Machine的dts中包含衆多的Key,並且絕大多數Key是由SoC廠商或者外設廠商自定義的。有關這些Key的意義以及寫法介紹,內核要求廠商在Documents/devicetree/bindings/目錄下提供文檔介紹,如果大家在閱讀或編寫dts文件時遇到問題,應該首先到bindings目錄下查閱對應的介紹文檔。

有關屬性中Key的命名原則有一個約定俗成的做法:SoC廠商或其他外設廠商應使用 vendor,prop的形式。屬性Compatible也應遵守”vendor,model”的形式。 
例如qcom,qcom,clk-rates;goodix,irq-gpio

Labels and Reference

標籤和引用是爲了方便編寫dts文件,當標籤用於節點時開發人員可在任意地方引用該標籤而不用關注該標籤的全路徑;當標籤應用於屬性時,開發人員可以在其他屬性中引用該標籤,避免重複的工作。例如Label一個字符串,避免在每個需要的地方重複寫同一個字符串。

全路徑在dts中即節點的在樹形結構中的位置,即從根節點索引到該節點所經過的所有父節點組合

Lable的方法很簡單,請看如下例子

Label
  i2c0: i2c@ff121288 {};
  msmgpio: gpio@fd510000 {
      msmgpio-comp: compatible = "msm-gpio";
  };

Reference
/*goodix-ts is a i2c touch device */
  &i2c0 {
    goodix-ts@5d { 
        goodix,rst-gpio = <&msmgpio 16 0x00>;
    }
  }

 Without Reference
 / {
   i2c@ff121288 {
      goodix-ts@5d { 
          goodix,rst-gpio = <&msmgpio 16 0x00>;
      }
   };
 };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

上例中i2c0msmgpiomsmgpio-comp均是Label,我們在描述goodix-ts設備的時候引用了標籤。 假設goodix-ts掛載在i2c0上,不必像Without Reference下的示例一樣完整書寫i2c0的全路徑,而只需引用前面定義的i2c0這個標籤即可。屬性goodix,rst-gpio中引用到了msmgpio,這是比較常見的設定gpio pin的方法。對比使用標籤引用和不使用標籤引用的寫法我們就能感受到標籤與引用的妙處了。

另外,也可以使用alias爲標籤定義別名,如:alias { i2c_0 = &i2c0};,這樣在引用的地方就不用再寫&號了,alias+label+reference是非常常用的做法。

Linux DTS文件結構解析

本節將以Qcom的dts文件爲例,介紹dts文件的結構、dts如何與一個Machine對應。

內核版本:MSM Kernel3.10

Repo:https://android.googlesource.com/kernel/msm

Branch: android-msm-angler-3.10-marshmallow-dr

Qcom的dts文件位於arch/arm/boot/dts/qcom/,arm64平臺的dts軟鏈接到前面的目錄下,因此arm/arm64平臺的dts均存放於前面的目錄下。以Qcom MSM8974平臺爲例,其對應的dts文件有

msm8974-v1-fluid.dts
msm8974-v1-liquid.dts
msm8974-v1-mtp.dts
msm8974-xx-xxx.dts
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

這裏僅羅列幾個dts文件,由於平臺版本的不同,存在不同的dts。那Build內核的時候是如何決定哪些dts需要被編譯呢? 答案在arch/arm/boot/dts/qcom/Makefile中,

ifeq ($(CONFIG_OF),y)
#include $(srctree)/arch/arm/boot/dts/qcom/Makefile.board
dtb-$(CONFIG_ARCH_MSM8974) += msm8974-v1-cdp.dtb \
    msm8974-v1-fluid.dtb \
    msm8974-v1-liquid.dtb \
    msm8974-v1-mtp.dtb \
    msm8974-v1-rumi.dtb \
    msm8974-v1-sim.dtb \
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

以上截取了Makefile的一部分,Build Kernel時將根據實際Platform類型決定哪些dts文件將被包含到dtb target中,本例中平臺爲MSM8974,包含了數個dts文件。最終多個dtb文件打包成dt.img,dt.img與Kernel,Ramdisk一起打包生成boot.img。Bootloader啓動時從SoC寄存器中讀取id信息, 從dt.img中找到對應的dtb並裝載到RAM中然後傳遞地址給Kernel,匹配的屬性即:dtb根節點下的compatible、qcom,msm-id屬性。

一個具體的dts文件通常包含多個dtsi文件,大致分爲以下幾類 
1. 骨架類dtis - skeleton.dtsi,skeleton64.dtsi 
2. pinctrl dtsi - msm8974-pinctrl.dtsi,定義Pin Mux 
3. regulator dtsi - msm8974-regulator.dtsi,定義電源 
4. clock dtsi - msm8974-clock.dtsi,定義時鐘信號 
5. panel/camera/sensor dtsi - msm8974-mdss-panel.dtsi,msm8974-camera-sensor-xxx.dtsi 
6. gpu/ion/leds/xxx

dtsi類型比較多,這裏進列出部分。

Skeleton DTSI

Skeleton的作用是定義設備啓動所需要的最小的組件,它定義了root-node下的最基本且必要的child-node類型,通常對應SoC上的基礎設施如CPU,Memory等。

/*
 * Skeleton device tree in the 64 bits version; the bare minimum
 * needed to boot; just include and add a compatible value.  The
 * bootloader will typically populate the memory node.
 */
/ {
    #address-cells = <2>;
    #size-cells = <2>;
    cpus { };
    soc { };
    chosen { };
    aliases { };
    memory { device_type = "memory"; reg = <0 0 0 0>; };
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

如上Code來源於skeleton64.dtsi,在root-node下一共定義了5個基本的child-node,cpus代表SoC上所有的cpu,具體cpu的個數以及參數定義在cpus的child-node下;soc代表平臺上的所有片上外設以及片外外設,例如uart/clock/spi/i2c/display/…具體這些外設的定義在其他dts/dtsi文件中;chosen用於定義runtime configuration;aliases用於定義node的別名;memory用於定義設備上的物理內存。

address-cells定義了根節點的所有子節點的地址空間屬性(reg屬性)中使用2個cell來表示啓示地址

size-cells定義了根節點的所有子節點的地址空間屬性(reg屬性)中使用2個cell來表示地址空間的長度

Main-DTS File

Main dts file將各種功能的dtsi文件組合,形成一個功能完善的Machine(SoC+Periph),以msm8974-v1-fluid.dts爲例

/dts-v1/;      //定義了dts的版本
#include "msm8974-v1.dtsi"  // 各種設備的定義分散到獨立的dtsi中
#include "msm8974-fluid.dtsi"
/ {
    model = "Qualcomm Technologies, Inc. MSM 8974 FLUID";
    compatible = "qcom,msm8974-fluid", "qcom,msm8974", "qcom,fluid";
    qcom,msm-id = <126 3 0>,
              <185 3 0>,
              <186 3 0>;
};
&pm8941_chg {
    qcom,charging-disabled;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

細心的同學可能看到了有些屬性直接定義在root-node下,這部分屬性用於定義Platform的ID信息,compatible屬性定義匹配的平臺類型,qcom,msm-id是SoC廠商定義的Key,這個屬性定義匹配的SoC,Kernel啓動時會從SoC的寄存器中讀取id與之比對。

DTS中&用於引用已經定義的node,假設要在8974-v1-fluid.dts中定義個i2c adpater,則應該這樣寫

&soc {
    // i2c adapter屬於SoC上的設備,因此定義在soc節點下
    i2c@0xff188888 {
         // node content
    }
    i2c@0xff399999 {
         // node content
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

這裏又出現了一個符號@,@的作用是描述一個設備的地址信息,準確的說是該設備在IO地址空間的起始地址。

IO地址空間是讀寫和控制所有SoC上的外設的地址空間,可以理解爲將SoC上的外設的寄存器地址映射成CPU可以理解的地址空間,例如上述代碼中的0xff188888,CPU可以通過訪問以0xff188888開始的地址來讀寫和控制i2c(當然這個地址是我假設的)

在ARM架構中,Memory和IO被映射到一個同一個地址空間內,CPU訪問Memory和訪問IO使用相同的指令,但這一點在x86平臺中是不同的。

@符號還用與區分相同類型的外設,例如SoC上擁有4個i2c apapter,他們可以都叫i2c,但用@+地址來區分;當然你可以可以用aliase來定別名,這樣就使用加@了。

不同的dtsi定義了不同類型的參數,實際開發過程中可以根據文件名或者include關係找到你需要關注的設備,當然我更喜歡用find/grep命令來幫助我定位設備。

DeviceTree Compiler(DTC)


DTC 是編譯device tree源文件的工具鏈,根據官方的介紹,DTC工具鏈將一種文件格式作爲輸入轉換成另一種文件格式。典型的輸入文件爲可讀的dts文本文件,輸出文件是二進制形式的dtb文件。當然DTC同樣可以以二進制的dtb文件作爲輸入,輸出dts文件。

這意味着,使用DTC可以使dts文件與dtb文件相互轉換。

目前DTC支持輸入格式爲:dts,dtb,fs;支持的輸出格式有:dtb,dts,asm。

CommandLine

DTC的命令格式爲:

       dtc [options] [<input_filename>]
  • 1
  • 1

編譯dts,生成dtb

      dtc -I dts -O dtb -o output.dtb  file-a.dts file-b.dts 
  • 1
  • 1

反編譯dtb,生成dts

      dtc -I dtb -O dts -o output.dts  file-z.dtb
  • 1
  • 1

DeviceTree Bolb and dt.img


有關dtb以及dt.img的知識,我將會簡單介紹一下dtb、dtimg文件的結構,這部分屬於進階的內容,將放到其他章節講解。

END


有關DeviceTree的基礎知識就介紹到這裏,後續會介紹DeviceTree的API以及常用設備的dts開發方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章