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. 如果設備樹節點沒有寫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

 

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

 

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

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

相關文章
相關標籤/搜索