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() "); 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 ", 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 ", 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 ", 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 ", 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 ", 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 ", pstr[i]); } } //获取标志属性。 if(of_find_property(np, "testprop,mytest", NULL)) { printk("You have this property,does it mean any thing? "); } //获取到中断号码。 int irqno = irq_of_parse_and_map(np, 0/*从0开始获取。*/); printk("irqno:%d ", 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");