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