DTS設備樹學習(二)

一、前言
簡單的說,如果要使用Device Tree,首先用戶要了解自己的硬件配置和系統運行參數,並把這些信息組織成Device Tree source file。通過DTC(Device Tree Compiler),可以將這些適合人類閱讀的Device Tree source file變成適合機器處理的Device Tree binary file(有一個更好聽的名字,DTB,device tree blob)。在系統啓動的時候,boot program(例如:firmware、bootloader)可以將保存在flash中的DTB copy到內存(當然也可以通過其他方式,例如可以通過bootloader的交互式命令加載DTB,組織成DTB保存在內存中),並把DTB的起始地址傳遞給client program(例如OS kernel,bootloader或者其他特殊功能的程序)。對於計算機系統(computer system),一般是firmware->bootloader->OS,對於嵌入式系統,一般是bootloader->OS。

二、設備樹的組成和應用
2.1 DTS和DTSI

.dts文件是一種ASCII文本對Device Tree的描述,放置在內核的/arch/arm/boot/dts目錄。一般而言,一個.dts文件對應一個ARM的machine。

.dtsi文件作用:由於一個SOC可能有多個不同的電路板,而每個電路板擁有一個 .dts。這些dts勢必會存在許多共同部分,爲了減少代碼的冗餘,設備樹將這些共同部分提煉保存在.dtsi文件中,供不同的dts共同使用。.dtsi的使用方法,類似於C語言的頭文件,在dts文件中需要進行include *.dtsi文件。當然,dtsi本身也支持include 另一個dtsi文件。

2.2. DTC

DTC爲編譯工具,它可以將.dts文件編譯成.dtb文件。DTC的源碼位於內核的scripts/dtc目錄,內核選中CONFIG_OF,編譯內核的時候,主機可執行程序DTC就會被編譯出來。 即scripts/dtc/Makefile中

hostprogs-y := dtc

always := $(hostprogs-y)

在內核的arch/arm/boot/dts/Makefile中,若選中某種SOC,則與其對應相關的所有dtb文件都將編譯出來。在linux下,make dtbs可單獨編譯dtb。以下截取了S5PV210平臺的一部分。

888行是我自己添加的一個設備樹文件

2.3. DTB

DTC編譯*.dts生成的二進制文件(.dtb),bootloader在引導內核時,會預先讀取.dtb到內存,進而由內核解析。

2.4. Bootloader

Bootloader需要將設備樹在內存中的地址傳給內核。在ARM中通過bootm或bootz命令來進行傳遞。bootm [kernel_addr] [initrd_address] [dtb_address],其中kernel_addr爲內核鏡像的地址,initrd爲initrd的地址,dtb_address爲dtb所在的地址。若initrd_address爲空,則用“-”來代替。

三、設備樹中dts、dtsi文件的基本語法
DTS的基本語法範例,如圖所示。

它包括一系列節點,以及描述節點的屬性。

“/”爲root節點。在一個.dts文件中,有且僅有一個root節點;在root節點下有“node1”,“node2”子節點,稱root爲“node1”和“node2”的parent節點,除了root節點外,每個節點有且僅有一個parent;其中子節點node1下還存在子節點“child-nodel1”和“child-node2”。

注:如果看過內核/arch/arm/boot/dts目錄的讀者看到這可能有一個疑問。在每個.dsti和.dts中都會存在一個“/”根節點,那麼如果在一個設備樹文件中include一個.dtsi文件,那麼豈不是存在多個“/”根節點了麼。其實不然,編譯器DTC在對.dts進行編譯生成dtb時,會對node進行合併操作,最終生成的dtb只有一個root node。Dtc會進行合併操作這一點從屬性上也可以得到驗證。這個後面分析。

在節點的{}裏面是描述該節點的屬性(property),即設備的特性。它的值是多樣化的:

1.它可以是字符串string,如model = “YIC System SMDKV210 based on S5PV210”;

也可能是字符串數組string-list,如compatible = “yic,smdkv210”, “samsung,s5pv210”;

2.它也可以是32 bit unsigned integers,整形用<>表示, reg = <0x30000000 0x20000000>;

3.它也可以是binary data,十六進制用[]表示,local-mac-address = [00 00 de ad be ef];

4.它也可能是空,empty_property;

爲了瞭解Device Tree的結構,我們首先給出一個Device Tree的示例:

/dts-v1/;
#include <dt-bindings/input/input.h>
#include “s5pv210.dtsi”

/ {
model = “YIC System SMDKV210 based on S5PV210”;
compatible = “yic,smdkv210”, “samsung,s5pv210”;

chosen {
    bootargs = "console=ttySAC2,115200n8 root=/dev/nfs nfsroot=192.168.0.101:/home/run/work/rootfs/rootfs_3.16.57 ip=192.1    68.0.20 init=/linuxrci earlyprintk";
};  

memory@30000000 {
    device_type = "memory";
    reg = <0x30000000 0x20000000>;
};  

ethernet@88000000 {
compatible = “davicom,dm9000”;
reg = <0x88000000 0x2 0x88000004 0x2>;
interrupt-parent = <&gph1>;
interrupts = <2 4>;
local-mac-address = [00 00 de ad be ef];
davicom,no-eeprom;
clocks = <&clocks CLK_SROMC>;
clock-names = “sromc”;
};

key {
    empty_property;
}

};

四、Device Tree source file語法介紹
1.dts文件的基本組成單元爲:

[label:] node-name[@unit-address] {
[properties definitions] //就是屬性定義,對當前節點描述,將硬件信息提供給內核處理
[child nodes] //子節點
}
“[]”表示option,因此可以定義一個只有node name的空節點。如下:

key {

};
label方便在dts文件中引用,具體後面會描述。

@unit-address通常用區分名字相同的外設備,比如有兩塊內存。

memory@30000000 {
device_type = “memory”;
reg = <0x30000000 0x20000000>;
};

memory@50000000 {
device_type = “memory”;
reg = <0x50000000 0x20000000>;
};
這樣,在同一級別,就能通過節點名字區別不同設備。(不同級別設備節點是可以相同的,如下節點是可以的)

AAAAA {
device_type = “AAAAA”;
reg = <0x50000000 1>;

    AAAAA {
        device_type = "AAAAA";
        reg = <0x60000000 1>;
    };
};

child node的格式和node是完全一樣的,因此,一個dts文件中就是若干嵌套組成的node,property以及child note、child note property描述。

我們先以上面實例中的一部分爲例來分析:

/dts-v1/;
#include <dt-bindings/input/input.h>
#include “s5pv210.dtsi”

/ {
model = “YIC System SMDKV210 based on S5PV210”;
compatible = “yic,smdkv210”, “samsung,s5pv210”;

chosen {
    bootargs = "console=ttySAC2,115200n8 root=/dev/nfs nfsroot=192.168.0.101:/home/run/work/rootfs/rootfs_3.16.57 ip=192.168.0.20 init=/linuxrci earlyprintk";
};  

memory@30000000 {
    device_type = "memory";
    reg = <0x30000000 0x20000000>;
};  

};
device tree顧名思義是一個樹狀的結構,既然是樹,必然有根。

“/”是根節點的node name。

“{”和“}”之間的內容是該節點的具體的定義,其內容包括各種屬性的定義以及child node的定義。

chosen和memory都是sub node,sub node的結構和root node是完全一樣的,因此,sub node也有自己的屬性和它自己的sub node,最終形成了一個樹狀的device tree。

說到屬性中的<>代表的值,這裏引入Arrays of cells的概念:cell表示由尖括號分隔的32位無符號整數

舉例

/* example 1 */
interrupt = <17 0x0c>;

/* example 2,表示一個64位數據 */
clock-frequency = <0x00000001 0x00000000>;

/* example 3,A null-terminated string (有結束符的字符串) */
compatible = “simple-bus”;

/* example 4,A bytestring(字節序列) */
local-mac-address = [00 00 12 34 56 78];  // 每個byte使用2個16進制數來表示
local-mac-address = [000012345678];       // 每個byte使用2個16進制數來表示

chosen node主要用來描述由系統firmware指定的runtime parameter。如果存在chosen這個node,其parent node必須是名字是“/”的根節點。原來通過tag list傳遞的一些linux kernel的運行時參數可以通過Device Tree傳遞。例如command line可以通過bootargs這個property這個屬性傳遞;initrd的開始地址也可以通過linux,initrd-start這個property這個屬性傳遞。在本例中,chosen節點包含了bootargsbootargs的屬性。

chosen {
bootargs = “console=ttySAC2,115200n8 root=/dev/nfs nfsroot=192.168.0.101:/home/run/work/rootfs/rootfs_3.16.57 ip=192.168.0.20 init=/linuxrci earlyprintk”;
};
通過該command line可以控制內核從net啓動,當然,具體項目要相應修改command line以便適應不同的需求。我們知道,device tree用於HW platform識別,runtime parameter傳遞以及硬件設備描述。chosen節點並沒有描述任何硬件設備節點的信息,它只是傳遞了runtime parameter。

memory device node是所有設備樹文件的必備節點,它定義了系統物理內存的layout。device_type屬性定義了該node的設備類型,例如cpu、serial等。對於memory node,其device_type必須等於memory。reg屬性定義了訪問該device node的地址信息。對於device node,reg描述了memory-mapped IO register的offset和length。對於memory node,定義了該memory的起始地址和長度。

當然memory也可以通過command line來傳遞.

chosen {
bootargs = “console=ttySAC2,115200n8 root=/dev/nfs nfsroot=192.168.0.101:/home/run/work/rootfs/rootfs_3.16.57
ip=192.168.0.20 init=/linuxrci earlyprintk mem=512M@30000000”;
};

在dts文件中,對於properties,有一些常用的、默認的、特殊的屬性,定義如下:

model
        設備製造商的描述,如果有2款板子配置基本一致, 它們的compatible是一樣的那麼就通過model來分辨這2款板子
compatible
        定義一系列的字符串, 用來指定內核中哪個machine_desc可以支持本設備,即這個板子兼容哪些平臺 。一般"供應商,產品"。
reg             
        描述設備資源在其父總線定義的地址空間中的地址。通常這意味着內存映射IO寄存器塊的偏移量和長度,但在某些總線類型上可能有不同的含義。根節點定義的地址空間中的地址是CPU實際地址。
#address-cells
       在它的子節點的reg屬性中, 使用多少個u32整數來描述地址(address)
#size-cells     
      在它的子節點的reg屬性中, 使用多少個u32整數來描述地址長度(size)
phandle
      節點中的phandle屬性, 它的取值必須是唯一的(不要跟其他的phandle值一樣),使用phandle值來引用節點
bootargs
      內核command line參數, 跟u-boot中設置的bootargs作用一樣 
cpus

/cpus節點下面有1個或多個cpu子節點,cpu子節點用reg屬性來表明自己是那個cpu。

下面對上面屬性的常見用法做舉例分析:

model:

model = “YIC System SMDKV210 based on S5PV210”;
compatible:

對於根節點,用來指定內核中哪個machine_desc可以支持本設備,即這個板子兼容哪些平臺 。通常格式爲,",",manufacturer表示供應商,model字符串代表了與該設備兼容的其他設備

compatible = “yic,smdkv210”, “samsung,s5pv210”;
該屬性的值是string list,定義了一系列的modle(每個string是一個model)。這些字符串列表被操作系統用來選擇用哪一個driver來驅動該設備。假設定義該屬性: compatible = “yic,smdkv210”, “samsung,s5pv210”;那麼操作操作系統可能首先使用 "yic,smdkv210"來匹配適合的driver,如果沒有匹配到,那麼使用字符串"samsung,s5pv210"來繼續尋找適合的driver。對於root node,compatible屬性是用來匹配machine type的(在device tree代碼分析文章中會給出更細緻的描述)。

下面給出代碼中對應的匹配值。

#address-cells

#size-cells

reg

注:“cells”是由尖括號分隔的32位無符號整數:cell-property = <0xbeef 123 0xabcd1234>

下面表示32位系統中,1個512M內存的分佈。

/ {

......

#address-cells = <1>; 
#size-cells = <1>; 
memory@30000000 {
    device_type = "memory";
    reg = <0x30000000 0x20000000>;
};

};

下面表示32位系統中,2個512M內存的分佈。

/ {

......

#address-cells = <1>; 
#size-cells = <1>; 
memory@30000000 {
    device_type = "memory";
    reg = <0x30000000 0x20000000
           0x70000000 0x20000000>;
};

};

如果我們的CPU是64位的,2G的內怎麼描述呢?

/ {

......

#address-cells = <2>; 
#size-cells = <2>; 
memory@100000000 {
    device_type = "memory";
    reg = <0x00000001 0x0 0x0 0x80000000>;
};

};
如果不指定#address-cells和#size-cells   的大小,那麼系統會認爲只是兩塊32位內存還是1塊64位內存呢?

總結:可編址的設備使用下列屬性來將地址信息編碼進設備樹:

reg

#address-cells

#size-cells

每個可尋址的設備有一個reg屬性,即以下面形式表示的元組列表:

reg = <address1 length1 [address2 length2] [address3 length3] … >

每個元組,。每個地址值由一個或多個32位整數列表組成,被稱做cells。同樣地,長度值可以是cells列表,也可以爲空。

既然address和length字段是大小可變的變量,父節點的#address-cells和#size-cells屬性用來說明各個子節點有多少個cells。換句話說,正確解釋一個子節點的reg屬性需要父節點的#address-cells和#size-cells值。

每個地址值是1 cell (32位) ,並且每個的長度值也爲1 cell,這在32位系統中是非常典型的。64位計算機可以在設備樹中使用2作爲#address-cells和#size-cells的值來實現64位尋址。

當然,在特殊情況下,是不需要長度的,即爲0。如下,CPU個數是一個整數,沒有寬度的。

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

    cpu@0 {
        device_type = "cpu";
        compatible = "arm,cortex-a8";
        reg = <0>;
    };  
};  

bootargs:

內核command line參數, 跟u-boot中設置的bootargs作用一樣。

cpus:

cpus節點下面有1個或多個cpu子節點,cpu子節點用reg屬性來表明自己是那個cpu。所以cpus中有兩個屬性。

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

    cpu@0 {
        device_type = "cpu";
        compatible = "arm,cortex-a8";
        reg = <0>;
    };  
};  

cpu的格式基本都是固定格式。缺一不可。

多核的通常會設置cpu的頻率,

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

    cpu0: cpu@0 {
        device_type = "cpu";
        compatible = "arm,cortex-a15";
        reg = <0x0>;
        clock-frequency = <1600000000>;
    };  

    cpu1: cpu@1 {
        device_type = "cpu";
        compatible = "arm,cortex-a15";
        reg = <0x1>;
        clock-frequency = <1600000000>;
    };  

    cpu2: cpu@2 {
        device_type = "cpu";
        compatible = "arm,cortex-a15";
        reg = <0x2>;
        clock-frequency = <1600000000>;
    };  

    cpu3: cpu@3 {
        device_type = "cpu";
        compatible = "arm,cortex-a15";
        reg = <0x3>;
        clock-frequency = <1600000000>;
    };
};

device_type:

搜索了arch/arm/boot/dts下面的所有device_type,發現只有這幾種選項。

在dts文件中,引用其他節點:

1)phandle方式引用:

pic@10000000 {
phandle = <1>;
interrupt-controller;
};
another-device-node {
interrupt-parent = <1>; // 使用phandle值爲1來引用上述節點
};
這種方式要自己確認,在設備樹文件中phandle = <1>這個常量只能取值一次。

2)label 方式引用

節點格式

[label:] node-name[@unit-address] {
[properties definitions] //就是屬性定義,對當前節點描述,將硬件信息提供給內核處理
[child nodes] //子節點
}

PIC: pic@10000000 {
interrupt-controller;
};
another-device-node {
interrupt-parent = <&PIC>; // 使用label來引用上述節點,
// 使用lable時實際上也是使用phandle來引用,
// 在編譯dts文件爲dtb文件時, 編譯器dtc會在dtb中插入phandle屬性
};
這裏的label方式其實原理和phandle方式是一樣的,只不過lable對於我們使用來說更好辨認。dtc在編譯的時候會在使用label的節點中增加一個phandle的屬性,增加一個唯一的value,並把使用它的位置替換爲該value。

覆蓋規則:

同一層次的節點,後面的會覆蓋前面的節點。

memory@30000000 {
    device_type = "memory";
    reg = <0x30000000 0x20000000>;
};  
memory@30000000 {
    reg = <0x30000000 0x10000000>;
};  

上面這種情況和dtsi裏一個dts文件裏一個是相同的效果。

編譯

make dtbs CROSS_COMPILE=arm-none-linux-gnueabi-
反編譯

dtc -I dtb -O dts -o tmp.dts s5pv210-x210.dtb
查看tmp.dts文件

memory@30000000 {
    device_type = "memory";
    reg = <0x30000000 0x10000000>;
};   

確認已經替換。

直接引用方式覆蓋(增加)節點屬性:

假設下面節點定義在dtsi文件中

xusbxti: oscillator@1 {
compatible = “fixed-clock”;
reg = <1>;
clock-frequency = <0>;
clock-output-names = “xusbxti”;
#clock-cells = <0>;
};
某個dis文件包含了該dtsi文件,並定義瞭如下內容

&xusbxti {
clock-frequency = <24000000>;
};

反編譯後的clock-frequency值爲0x16e33600也即爲替換後的新值24000000

這裏要特別注意一點:

直接覆蓋方式引用時,新的覆蓋要放在根節點外面即,剛纔的例子要按照這種方式替換clock-frequency屬性。

/ {

};

&xusbxti {
clock-frequency = <24000000>;
};
 
————————————————
版權聲明:本文爲CSDN博主「to_run_away」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/qq_16777851/article/details/87291146

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