Linux設備樹(2)——設備樹格式和使用


一、設備樹dts文件的語法規范

 

1. DTS文件布局(layout)

/dts-v1/;
[memory reservations]    // 格式為: /memreserve/ <address> <length>;
/ {
    [property definitions]
    [child nodes]
};

(1) 特殊的、默認的屬性

a. 根節點的:

#address-cells   // 在它的子節點的reg屬性中, 使用多少個u32整數來描述地址(address)
#size-cells      // 在它的子節點的reg屬性中, 使用多少個u32整數來描述大小(size)
compatible       // 定義一系列的字符串, 用來指定內核中哪個 machine_desc 可以支持本設備,即這個板子兼容哪些平台                  
model            // 這個板子是什么,比如有2款板子配置基本一致, 它們的compatible是一樣的,那么就通過model來分辨這2款板子

(2) /memory 節點

device_type = "memory";
reg             // 用來指定內存的地址、大小

(3) /chosen 節點

bootargs        // 內核 command lin e參數,跟u-boot中設置的bootargs作用一樣

(4) /cpus 節點

/cpus節點下有1個或多個cpu子節點, cpu子節點中用reg屬性用來標明自己是哪一個cpu,所以 /cpus 中有以下2個屬性:

#address-cells   // 在它的子節點的reg屬性中, 使用多少個u32整數來描述地址(address)
#size-cells      // 在它的子節點的reg屬性中, 使用多少個u32整數來描述大小(size),必須設置為0

 

2. Devicetree node格式:

[label:] node-name[@unit-address] {
    [properties definitions]
    [child nodes]
};

(1) Property的2種格式

[label:] property-name = value;    //有值

[label:] property-name;    //有值

(2) Property的3種取值 

arrays of cells:1個或多個32位數據,64位數據使用2個32位數據表示。
string:字符串, 
bytestring:1個或多個字節

示例: 
a. Arrays of cells,一個cell就是一個32位的數據
interrupts = <17 0xc>;

b. 64bit數據使用2個cell來表示
clock-frequency = <0x00000001 0x00000000>;

c. null-terminated string
compatible = "simple-bus";

d. bytestring(字節序列)
local-mac-address = [00 00 12 34 56 78];  // 每個byte使用2個16進制數來表示
local-mac-address = [000012345678];       // 每個byte使用2個16進制數來表示(中間也可以沒有空格)

e. 可以是各種值的組合, 用逗號隔開:
compatible = "ns16550", "ns8250";    //是可以附多個值的,對每個字符串的獲取可參考__of_device_is_compatible()
example = <0xf00f0000 19>, "a strange property format";

 

3. 引用其他節點

(1) 通過phandle來引用 // 節點中的phandle屬性, 它的取值必須是唯一的(不要跟其他的phandle值一樣),例子:

pic@10000000 {
    phandle = <1>;
    interrupt-controller;
};

another-device-node {
    interrupt-parent = <1>;   // 使用phandle值為1來引用上述節點
};

(2) 通過label來引用

PIC: pic@10000000 {
    interrupt-controller;
};

another-device-node {
    /*
     * 使用label來引用上述節點,使用lable時實際上也是使用phandle來引用,
     * 在編譯dts文件為dtb文件時,編譯器dtc會在dtb中插入phandle屬性。
    */
    interrupt-parent = <&PIC>;   
};

 

4. dts文件示例

/dts-v1/;

/memreserve/ 0x33f00000 0x100000 //預留1M內存,不給內核使用

/ {
    model = "SMDK24440";
    /*
     * 這里指定了兩個值,從左到右依次匹配,只要有一個值匹配上了即可,匹配函數可見上
     * 面的__of_device_is_compatible().
     * 所有的字符串,一般是從具體到一般。
     * 也可以是前面是我們自己開發的平台的,后面是EVB的。利用EVB的進行匹配,自己的起
     * 說明作用。
     */
    compatible = "samsung,smdk2440", "samsung,smdk24xx";

    /*
     * 一個cells表示一個32bit的unsigned int。
     * 這里表示在其子節點里面,起始地址使用一個32bit的int表示,
     * 大小使用一個32bit的int表示。
     */
    #address-cells = <1>;
    #size-cells = <1>;
    
    /*解析成平台設備的設備名字為"30000000.memory",設備樹中的路徑名是"/memory@30000000"*/
    memory@30000000 {
        /*內存的device_type是約定好的,必須寫為"memory"*/
        device_type = "memory";
        /*
         * 表示一段內存,起始地址是0x30000000,大小是0x4000000字節。
         * 若是reg=<0x30000000 0x4000000 0 4096> 則表示兩段內存,另一段的
         * 起始地址是0,大小是4096字節。解析成這樣的結果的原因是上面指定了
         * address-cells和size-cells都為1.
         */
        reg =  <0x30000000 0x4000000>;
        
        /*解析成平台設備的設備名字為"38000000.trunk",設備樹中的路徑名是"/memory@30000000/trunk@38000000"*/
        trunk@38000000 {
            device_type = "memory_1";
            reg =  <0x38000000 0x4000000>;
        };
    };

    /*指定命令行啟動參數*/
    chosen {
        bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";
    };

    
    led {
        compatible = "jz2440_led";
        pin = <S3C2410_GPF(5)>;
    };
    
    
    pic@10000000 {
        /*這個phandle必須是唯一的*/
        phandle = <1>;
        interrupt-controller;
    };

    another-device-node {
        interrupt-parent = <1>;   // 使用phandle值為1來引用上面的節點
    };
    
    /*上面的引用比較麻煩,可以使用下面的方法來引用lable*/
    PIC: pic@10000000 {
        interrupt-controller;
    };
    
    another-device-node {
        /*
         * 使用label來引用上述節點,使用lable時實際上也是
         * 使用phandle來引用,在編譯dts文件為dtb文件時, 編譯
         * 器dtc會在dtb中插入phandle屬性
         */
        interrupt-parent = <&PIC>;
    };

};

 

5. dts文件對dtsi文件中節點的引用與改寫

  設備樹中把一些公共的部分寫在 .dtsi 文件中,.dts 文件可以去包含 .dtsi 文件,兩者的語法格式是相同的。若是把上面內容定義在 smdk2440.dtsi 文件中,使用基於smdk2440的平台的dts文件包含它,並且想覆蓋led節點的方法是在dts文件中:

(1) 若是led節點在dtsi中沒有指定label,需要通過全路徑引用

/dts-v1/;
#include "jz2440.dtsi"

/ {
    &led {
        pin = <S3C2410_GPF(6)>;
    };

};

 

(2) 若是在dtsi中指定了label,如在dtsi中的表示為

    Led1: led {
        compatible = "jz2440_led";
        pin = <S3C2410_GPF(5)>;
    };

這樣的話在dts文件中只需要下面操作即可:

/dts-v1/;
#include "jz2440.dtsi"

&Led1 {
    pin = <S3C2410_GPF(6)>;
}

也就是后面寫的屬性會覆蓋前面寫的屬性。

使用lable后,不需要也不能寫在根節點里面了,直接寫。

設備樹中任何節點的路徑名不能相同,否則就被認為是同一個設備樹節點。

可以通過反編譯dtb文件來驗證修改的正確性,將dtb轉換為dts的方法: ./scripts/dtc/dtc -I dtb -O dts -o tmp.dts arch/arm/boot/dts/jz2440.dtb

6. 若是設備樹節點沒有寫status項,默認就是是能的

static bool __of_device_is_available(const struct device_node *device)
{
    const char *status;
    int statlen;

    if (!device)
        return false;

    status = __of_get_property(device, "status", &statlen);
    if (status == NULL)
        return true; //默認為使能

    if (statlen > 0) {
        if (!strcmp(status, "okay") || !strcmp(status, "ok")) //ok和okay都可以
            return true;
    }
    //表中的fail和fail-sss沒做具體處理
    return false;
}

 

7. 相關資料

DT官方文檔: https://www.devicetree.org/specifications/

官方文檔(DTB格式): https://www.devicetree.org/specifications/

內核文檔: Documentation/devicetree/booting-without-of.txt

內核文檔: Documentation/devicetree/usage-model.txt

 

二、設備樹dtb的內存布局

1. DTB文件布局:

             ------------------------------
     base -> |  struct boot_param_header  |
             ------------------------------
             |      (alignment gap) (*)   |
             ------------------------------
             |      memory reserve map    |
             ------------------------------
             |      (alignment gap)       |
             ------------------------------
             |                            |
             |    device-tree structure   |
             |                            |
             ------------------------------
             |      (alignment gap)       |
             ------------------------------
             |                            |
             |     device-tree strings    |
             |                            |
      -----> ------------------------------
      |
      |
      --- (base + totalsize)

“device-tree strings” 區域中存放dts中所有屬性的名字,使用‘\0’隔開各個字符。如“compatible”、“#address-cells”、“#size-cells”、“device_type”、“reg”、“bootargs”等左值字符串。但是右值字符串不是存放在這里的。

“memory reserve map” 中存放預留內存信息,例如:“/memreserve/ 0x33f00000 0x100000”,使用struct fdt_reserve_entry結構存儲。

“device-tree structure” 中存儲所有的設備節點

2. 注意,在dtb文件中數據的存放格式是大字節序的,大小字節序只對數值的存儲有差別,對於字符串的存儲是沒有差別的。

3. 相關結構體描述定義在:linux4.14.39/scripts/dtc/libfdt/fdt.h

/*設備樹的頭部信息描述,用UE打開一個dtb文件,最開始的就是fdt_header*/
struct fdt_header

/*描述reserved的內存*/
struct fdt_reserve_entry

4. 參考文檔:Documentation\devicetree\booting-without-of.txt

 

三、設備樹dtc,dtdiff工具

1. dtc工具安裝

# apt-get install device-tree-compiler
# dtc 
# dtc  --help

2. 由dts生成dtb:

# dtc -I dts -O dtb -o devicetree.dtb jz2440.dts

3. 由dtb生成dts

# dtc -I dtb -O dts -o tmp.dts devicetree.dtb

4. dtc --help

Usage: dtc [options] <input file>

Options: -[qI:O:o:V:d:R:S:p:fb:i:H:sW:E:hv]
  -q, --quiet                
    Quiet: -q suppress warnings, -qq errors, -qqq all
  -I, --in-format <arg>      
    Input formats are:
        dts - device tree source text
        dtb - device tree blob
        fs  - /proc/device-tree style directory
  -o, --out <arg>            
    Output file
  -O, --out-format <arg>     
    Output formats are:
        dts - device tree source text
        dtb - device tree blob
        asm - assembler source
  -V, --out-version <arg>    
    Blob version to produce, defaults to %d (for dtb and asm output)
  -d, --out-dependency <arg> 
    Output dependency file
  -R, --reserve <arg>        
    tMake space for <number> reserve map entries (for dtb and asm output)
  -S, --space <arg>          
    Make the blob at least <bytes> long (extra space)
  -p, --pad <arg>            
    Add padding to the blob of <bytes> long (extra space)
  -b, --boot-cpu <arg>       
    Set the physical boot cpu
  -f, --force                
    Try to produce output even if the input tree has errors
  -i, --include <arg>        
    Add a path to search for include files
  -s, --sort                 
    Sort nodes and properties before outputting (useful for comparing trees)
  -H, --phandle <arg>        
    Valid phandle formats are:
        legacy - "linux,phandle" properties only
        epapr  - "phandle" properties only
        both   - Both "linux,phandle" and "phandle" properties
  -W, --warning <arg>        
    Enable/disable warnings (prefix with "no-")
  -E, --error <arg>          
    Enable/disable errors (prefix with "no-")
  -h, --help                 
    Print this help and exit
  -v, --version              
    Print version and exit

 

5. dtdiff 工具用於對比設備樹的差別:

# dtdiff devicetree.dtb devicetree_1.dtb 
--- /dev/fd/63    2019-06-08 11:43:56.086042406 +0800
+++ /dev/fd/62    2019-06-08 11:43:56.086042406 +0800
@@ -17,6 +17,6 @@
 
     memory@30000000 {
         device_type = "memory";
-        reg = <0x30000000 0x4000000>;
+        reg = <0x30000000 0x4000002>;
     };
 };

dtdiff使用的是兩個dtb文件進行對比的,,它可以將節點名進行對齊對比,比diff -Naur對比兩個目錄下的dtc文件要方便。

5. 一個使用場景

通過反編譯dtb獲取的dts文件比較純凈,因為實際項目上可能有多個dtsi被包含進來,搞的人眼花繚亂。通過反編譯得到的dts文件只需要看這一個文件即可。

 

四、設備樹節點變為 platform_device 的過程和與驅動的匹配過程

1. 在dts文件中構造節點,每一個節點中都含有資源,充當平台設備的設備端。編譯后生成 .dtb 文件傳給內核,內核解析設備樹后為每一個節點生成一個 device_node 結構,然后根據這個結構生成平台設備的設備端。根據設備樹節點的 compatible 屬性來匹配平台設備的驅動端。

.dts ---> .dtb ---> struct device_node ---> struct platform_device

注: 
dts  - device tree source  // 設備樹源文件
dtb  - device tree blob    // 設備樹二進制文件, 由dts編譯得來
blob - binary large object

 

2. 來自dts的platform_device結構體 與 我們寫的platform_driver 的匹配過程

  來自 dts 的 platform_device 結構體 里面有成員 ".dev.of_node",它里面含有各種屬性, 比如 compatible, reg, pin等。我們寫的 platform_driver 里面有成員 ".driver.of_match_table",它表示能支持哪些來自於 dts 的 platform_device。

  如果設備端的 of_node 中的 compatible 跟 驅動端的 of_match_table 中的 compatible 一致,就可以匹配成功,則調用platform_driver 中的 probe 函數。在probe函數中,可以繼續從 of_node 中獲得各種屬性來確定硬件資源。

platform_match(struct device *dev, struct device_driver *drv) // drivers/base/platform.c
    of_driver_match_device(dev, drv) // include/linux/of_device.h 
        of_match_device(drv->of_match_table, dev) // drivers/of/device.c
            of_match_node(matches, dev->of_node); // drivers/of/device.c
                __of_match_node(matches, node); // drivers/of/device.c
                    //對於驅動of_device_id中給出的每一項都與設備樹節點的compatible屬性中的每一個值進行匹配,
                    //返回匹配度最高的計數值best_match
                    __of_device_is_compatible(node, matches->compatible, matches->type, matches->name);
static int platform_match(struct device *dev, struct device_driver *drv)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct platform_driver *pdrv = to_platform_driver(drv);

    /* When driver_override is set, only bind to the matching driver */
    if (pdev->driver_override)
        return !strcmp(pdev->driver_override, drv->name);

    /* Attempt an OF style match first */
    if (of_driver_match_device(dev, drv))
        return 1;

    /* Then try ACPI style match */
    if (acpi_driver_match_device(dev, drv))
        return 1;

    /* Then try to match against the id table */
    if (pdrv->id_table)
        return platform_match_id(pdrv->id_table, pdev) != NULL;

    /* fall-back to driver name match */
    return (strcmp(pdev->name, drv->name) == 0);
}

platform_match分析:

a. 如果 pdev->driver_override 被賦值,就直接使用它進行設備和驅動的名字進行匹配。
b. 嘗試使用設備樹進行匹配
c. 如果平台驅動端提供了 pdrv->id_table,則使用平台設備的名字與平台驅動 id_table 列表中的名字進行匹配。
d. 否則直接匹配平台設備的名字和平台驅動的名字。

3. 有可能compatible相同也不一定選擇的就是這個匹配,因為有可能有匹配度更高的,比如除了compatible匹配上了以外,name和type也都匹配上了,那么匹配度就是最高的。

of_match_table是struct device_driver的成員 const struct of_device_id *of_match_table,定義如下:

struct of_device_id {
    char    name[32];
    char    type[32];
    char    compatible[128];
    const void *data;
};

  通過設備樹的compatible屬性的匹配的規則就是:如果compatible屬性不存在,就匹配type和name,但是權重極低。若是compatible屬性存在,但是匹配補上就立即返回,不在進行后續的匹配。

4. compatible 屬性的書寫規范為:"廠家,產品",例如: "jz2440,led"

5. 設備樹是平台總線設備模型的改進

  引入設備樹之前,平台設備模型的資源定義在平台設備的設備端,引入設備樹后定義在設備樹中,可以說設備樹是對平台設備模型的一種改進,本質上還是平台設備模型。

6. 設備樹dts文件的語法

a. 可以使用一些C語言語法
b. 使用/* */ 或 // 來注釋
c. 每一句源代碼都使用 ";" 隔開

比如:
#define S3C2410_GPA(_nr) ((1<<16)+1) // dts文件中使用C語言的語法定義宏

補充: 是 platform_match 進行匹配的原因

start_kernel     // init/main.c
    rest_init();
        pid = kernel_thread(kernel_init, NULL, CLONE_FS); /*創建kernel_init內核線程*/
                    kernel_init
                        kernel_init_freeable();
                            do_basic_setup(void)
                                /*負責設備節點,固件,平台設備初始化,sysfs文件框架搭建*/
                                driver_init(void)
                                    platform_bus_init(void)
                                        bus_register(&platform_bus_type);
                                            .match = platform_match,

 

 

五. 設備樹的/sys目錄下的文件和驅動獲取

1. 設備樹的sysfs屬性都存在於of_node下,of_node(open firmare node) 這個目錄下就是設備樹中節點的成員了。可以直接打印里面的 reg 成員寄存器中的值

# hexdump reg
# hexdump -C reg 以字節和ASCII碼的方式顯示出來,可以自己加"-h"選項查看出來。

例如:

led {
    compatible = "jz2440_led"; 
    reg = <(5<<16)+5, 1>
};
# hexdump -C reg 的結果就是:
00 05 00 05 00 00 00 01        // 設備樹中是使用大字節序描述的。

 

2. 驅動中對設備樹節點屬性的獲取

參考 linux4.14.39/include/linux/of.h,這里面的函數都是以 struct device_node 結構為參數的。

設備樹節點構造成的 struct device_node 結構存在於:

struct platform_device; //include/linux/platform_device.h
    struct device dev; //include/linux/device.h
        struct device_node *of_node; //include/linux/of.h 對應的設備樹節點

3. 內核幫助文檔

(1) 驅動程序中使用的設備樹節點的內容的編寫方法在:documentation/devicetree/bindings

(2) 整個設備樹如何寫參考EVB板的: arch/arm64/boot/dts/qcom

 

4. 使用設備樹編程

一個寫的好的驅動程序,會盡量確定所用的資源,只把不能確定的資源留給設備樹,讓設備樹來指定。

在設備樹節點中填寫哪些內容可以通過下面方法確定:
a. 看文檔 documentation/devicetree/bindings
b. 參考同類型的單板的設備樹文件 arch/arm/boot/dts
c. 網上搜索
d. 沒有其它辦法了,就去研究驅動源代碼。

 

5.在/sys/firmware/devicetree/向上層導出了設備樹,也就是說設備樹不僅可以配置內核,還可以配置上層應用程序.

eg: 在system代碼中通過/sys下的設備樹節點文件來讀取配置.
 /sys/firmware/devicetree/base/chosen/bootargs

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM