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