2020-02-21
關鍵字:dts解析、dts語法
什么是設備樹?
設備樹:device tree。它是Linux開發中用於描述硬件信息的文件。如:數量、類別、地址、總線情況與中斷等。設備樹文件其實就是一種適合人類閱讀的文本文件,它以 .dts 作為后綴,通常保存在 ./arch/arm/boot/dts 目錄下。dts 文件也是可以編譯的,它的編譯產物是 .dtb 文件,這個文件會在 bootloader 中被讀取,並傳遞給 kernel。
在早期的 Linux 版本中,如 Linux 2.6 及以前,設備的信息是直接依附於程序代碼的,它們被保存在 arch/arm/plat-xxx 與 arch/arm/mach-xxx 兩個目錄內。
dts的編譯:
.dts 文件可以經 dtc 編譯器編譯生成 .dtb 文件,.dtb 文件就可以直接下載進開發板中運行使用了。dtc 編譯器的源碼位於內核目錄的 scripts/dtc 目錄內。.dtb 文件一般是在 bootcmd 中被指定的,由此 bootloader 就會去加載設備樹信息了。
設備樹的語法
以下是一個dts的語法模板:
/{ node1{ a-string-property="A string"; a-string-list-property="first string","second string"; a-byte-data-property=[0x01 0x23 0x34 0x56]; child-node1{ first-child-property; second-child-property=<1>; a-string-property="hello world"; }; child-node2{ }; }; node2{ an-empty-property; a-cell-perperty=<1 2 3 4>;/*each number(cell) is a uint32*/ child-node1{ }; }; };
設備樹中的信息都是以“節點”存在的。首先整個設備樹有且只有一個“根節點”,它以 /{}; 表示。所有的設備信息描述都是在根節點下面寫明的。節點可以嵌套。設備樹描述信息以鍵值對形式實現。除了典型的 key-value 對形式外,也可以寫僅有 key 沒有 value 的屬性,它被稱為“標識位”,如上面模板中的 first-child-property; 所示。
關於節點名稱:
除根節點以外,每一個節點都必須要有一個名字,其形式為:<名字>[@<設備地址>]。尖括號內的表示必填項,方括號內的表示選填項,若有方括號中的內容時必須要添加一個 @ 字符用於分隔。例如:
/{ mipi_phy: video-phy@10020710 { //... }; camera { //... }; keypad@100A0000 { //... }; };
節點名稱中,第一部分的“名字”可以隨意填寫,但最好起一個能夠快速辨別的名字。第一部分的名字應不超過31個ASCII字符。第二部分的設備地址就比較直白了,不過其實這里也並不一定非得填設備地址,其實也是可以自由填寫的,真正的設備地址其實是要在節點內部以reg屬性標明的。同級節點的完整名稱必須唯一。
關於屬性property:
設備樹語法中的屬性是以鍵值對形式表現的。它的值可以為空也可以包含任意字節流。屬性的幾個基本數據表示形式如下:
1、文本字符串
其值用雙引號表示,如:string-property="a string"
2、Cells
32位的無符號整數,用尖括號限定,如:cell-property=<0xbeef 123 0xafbd>
3、二進制數據
用方括號限定,如:binary-property=[01 23 33 42]
4、自由集合
不同形式的靈氣可以通過逗號連在一起,如:mixed-property="a string",[01 09 76],<0x12 0x44 0x87>;
5、字符串列表
通過逗號可以創建字符串列表,如:string-list="red fish", "gold fish";
compatible 屬性:
這個屬性是一個常見屬性,基本上每一個節點都會有它的蹤影。這個屬性中的值常用於在代碼中作匹配使用。它的值的命名形式通常以 "<制造商>,<型號>" 為格式。compatible 的命名最好不要使用通配字符,而應該是要確切地寫出它的名稱。例如,我們就應該寫成 "compatible=<rockchip,3128>" 而不應寫成 "compatible=<rockchip,31xx>" 。
#address-cells 與 #size-cells 屬性:
這兩條屬性是用於限定其子節點(僅下一級節點)中reg屬性的。直接看下面的例子:
external-bus{
#address-cells=<2>;
#size-cells=<1>;
ethernet@0,0{
compatible="smc,smc91c11";
reg=<0 0 0x1000>;
interrupts=<5 2>;
};
};
#address-cells為2,表示 reg 中的地址的長度占 2 個數。在上面的例子中就是 reg 中的 0 0 表示地址。
#size-cells為1,表示 reg 中的長度的值占 1 個數。在上面的例子中就是 0x1000。
有了這兩條限定,external-bus 節點內的所有子節點的 reg 都必須要填 3 個值,其中前面兩個值表示地址,第三個值表示長度。
需要注意的是,這兩條屬性僅限定它所在的節點中的子節點。這兩條屬性所在節點的子節點的子節點是不受它的管控的。
reg 屬性:
reg 屬性是用於描述地址值的。它的形式通常為: reg = <address1 length1 [address2 length2] [address3 length3]>;
中斷信息屬性:
描述中斷需要四個屬性共同作用:
1、interrupt-controller
這條屬性是沒有值的。它用於將該節點定義為一個接收中斷信號的設備,即中斷控制器。
2、#interrupt-cells
這條屬性用於聲明該中斷控制器的中斷指示符中cell的個數。類似於 #address-cells 和 #size-cells。
3、interrupt-parent
4、interrupts
用於描述中斷指示符的列表,對應於該設備上的每個中斷輸出信號。
以下是一個中斷語法的示例:
serial 節點描述了一個中斷信息,intc 節點則是一個中斷控制器。interrupt-parent 中的中斷信息將會被 serial 使用到,serial 中的 interrupts 所使用的值就是 &intc 中的。而因為 intc 是中斷控制器,所以當 serial 中斷產生時,將會先到達 intc 再到 CPU。所以 #interrupt-cells 中的值所限定的就是 serial 中的 reg 。
編寫代碼去讀取設備樹中的信息
Linux內核中提供了一些專門用於讀取設備樹信息的函數接口,它們的簽名如下:
//跟據節點路徑查找device_node結構對象 struct device_node *of_find_node_by_path(const char *path); //根據property中的name來在device_node中查找property. struct property *of_find_property(const struct device_node *np, const char *name, int *lenp); //比較compat參數與指定節點中的compatible中的值,類似於strcmp函數 int of_device_is_compatible(const struct device_node *device, const char *compat); //獲得父節點 struct device_node *of_get_parent(const struct device_node *node); //根據屬性名稱讀出指定數量個屬性數組。 int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz); //讀取該節點的第index個中斷號 unsigned int irq_of_parse_and_map(struct device_node *dev, int index);
下面通過一個小程序來演示讀取dts中記載的信息:
假設我們有如下dts描述信息:
test_node@12345{ compatible = "farsigt,test"; reg = <0x123344 0x28 0x76543 0xfe>; testprop,mytest; test_list_string = "red fish","fly fish","blue fish"; interrupt-parent = <&gpx1>; interrupts=<1 2>; };
這段描述信息只要寫進內核設備樹中並正確編譯,就可以在 /proc/device-tree 目錄下找到一個名稱為 test_node@12345 的目錄,其內包含有這個節點所描述的所有信息。
則可以有如下示例驅動程序:
#include <linux/init.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_irq.h> #include <linux/interrupt.h> irqreturn_t key_irq_handler(int irqno, void *devid) { printk("key_irq_handler()\n"); return IRQ_HANDLED; } static int __init dts_drv_init() { //要在代碼中獲取到所有的節點信息,首先要把這個節點獲取到。 struct device_node *np = of_find_node_by_path("/test_node@12345"); printk("name:%s, full name:%s\n", np->name, np->full_name); //獲取到節點中的屬性信息。 struct property *prop = of_find_property(np, "compatible", NULL); printk("compatible prop name:%s, prop value:%s, prop length:%d\n", prop->name, prop->value, prop->length); //一個專門針對 compatible 屬性的函數接口。 int ret = of_device_is_compatible(np, "farsight,test");//np節點中是否有一個名字叫 farsight,test的compatible屬性? printk("Is have 'farsight,test' compatible? %d\n", ret); //獲取比較特殊的屬性值。 u32 regdata[4]; ret = of_property_read_u32_array(np, "reg", regdata, 4);//這個size要根據dts中實際的數量來。 if(!ret) { printk("get ret array success.0x%x,0x%x,0x%x,0x%x\n", regdata[0], regdata[1], regdata[2], regdata[3]); } //獲取字符列表。 const char *pstr[3];//有幾個數值就創建幾個大小的指針數組。 ret = of_property_read_string(np, "test_list_string", pstr/*這個方式是否正確?*/); if(!ret) { printk("string list,%s,%s,%s\n", pstr[0], pstr[1], pstr[2]); } //或還有一種方式。 int i = 0; for(; i < 3; i++) { ret = of_property_read_string_index(np, "test_list_string", i, &pstr[i]); if(!ret) { printk("%s\n", pstr[i]); } } //獲取標志屬性。 if(of_find_property(np, "testprop,mytest", NULL)) { printk("You have this property,does it mean any thing?\n"); } //獲取到中斷號碼。 int irqno = irq_of_parse_and_map(np, 0/*從0開始獲取。*/); printk("irqno:%d\n", irqno); //中斷號獲取到了就去申請中斷以驗證是否有效。 ret = request_irq(irqno, key_irq_handler, IRQ_TRIGGER_FALLING|IRQ_TRIGGER_RISING, "key_irqqq", NULL); return 0; } static int __exit dts_drv_exit() { free_irq(irqno, NULL);//與申請中斷時的參數一致。 } module_init(dts_drv_init); module_exit(dts_drv_exit); MODULE_LICENSE("GPL");