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

1、設備樹dts文件的語法規範node

 

1. DTS文件佈局(layout)linux

/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 節點ide

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格式:ui

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

(1) Property的2種格式this

[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. 相關資料

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

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

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

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

 

2、設備樹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

 

3、設備樹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>;
     };
 };

5. 一個使用場景

經過反編譯dtb獲取的dts文件比較純淨,由於實際項目上可能有多個dtsi被包含進來,搞的人眼花繚亂。經過反編譯獲得的dts文件只須要看這一個文件便可。

 

4、設備樹節點變爲 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/bindingsb. 參考同類型的單板的設備樹文件 arch/arm/boot/dtsc. 網上搜索d. 沒有其它辦法了,就去研究驅動源代碼。

相關文章
相關標籤/搜索