Linux设备树语法以及处理过程
Linux设备树语法详解 - Abnor - 博客园 (cnblogs.com)
Linux设备树(2)——设备树格式和使用 - Hello-World3 - 博客园 (cnblogs.com)
https://www.cnblogs.com/hellokitty2/p/10999432.html
1、概念
设备树的出现是为了实现驱动代码和设备信息的分离,在设备树出现以前,所有关于设备的具体信息都要写在驱动里,一旦外围设备变化,驱动代码就要重写。引入了设备树之后,驱动代码只负责处理驱动的逻辑,而关于设备的具体信息存放到设备树文件中,这样,如果只是硬件接口信息的变化而没有驱动逻辑的变化,驱动开发者只需要修改设备树文件信息,不需要改写驱动代码。比如在ARM Linux内,一个.dts(device tree source)文件对应一个ARM的machine,一般放置在内核的"arch/arm/boot/dts/"目录内。
2、设备树框架
设备树用树状结构描述设备信息,它有以下几种特性
- 每个设备树文件都有一个根节点,每个设备都是一个节点。
- 节点间可以嵌套,形成父子关系,这样就可以方便的描述设备间的关系。
- 每个设备的属性都用一组key-value对(键值对)来描述。
- 每个属性的描述用
;
结束
其文件布局如下所示,/表示板子:
/dts-v1/;
[memory reservations] // 格式为: /memreserve/ <address> <length>;
/ {
[property definitions]
[child nodes]
};
(1)默认属性
板子的属性:
#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 line参数,跟u-boot中设置的bootargs作用一样
(4)/cpus节点
/cpus节点下有1个或多个cpu子节点, cpu子节点中用reg属性用来标明自己是哪一个cpu,所以 /cpus 中有以下2个属性:
#address-cells // 在它的子节点的reg属性中, 使用多少个u32整数来描述地址(address)
#size-cells // 在它的子节点的reg属性中, 使用多少个u32整数来描述大小(size),必须设置为0
节点名
每个节点名只要是长度不超过31个字符的ASCII字符串,此外Linux中约定设备名称应写成<name>[@<unit_address>]
的形式,其中name就是设备名,最长可以是31个字符长度。unit_address一般是设备地址,用来唯一标识一个节点,下面就是典型节点名的写法
firmware@0203F000{
compatible = "samsung,secure-firmware";
reg = <0x0203F000 0x1000>
}
引用
当我们找一个节点的时候,我们必须书写完整的节点路径,这样当一个节点嵌套比较深的时候就不是很方便,所以,设备树有两种方式给节点标注引用,就是起别名。
(1)通过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>;
};
键值对
在设备树中,键值对是描述属性的方式。Linux设备树语法中定义了一些有规范意义的属性,包括compatible、address、interrupt等。
compatible
“compatible”表示“兼容”,对于某个 LED,内核中可能有 A、B、C 三个驱
动都支持它,那可以这样写:
led {
compatible = “A”, “B”, “C”;
};
内核启动时,就会为这个 LED 按这样的优先顺序为它找到驱动程序:A、B、C。
根节点下也有 compatible 属性,用来选择哪一个“machine desc”:一个
内核可以支持 machine A,也支持 machine B,内核启动后会根据根节点的
compatible 属性找到对应的 machine desc 结构体,执行其中的初始化函数。
address
(几乎)所有的设备都需要与CPU的IO口相连,所以其IO端口信息就需要在设备节点节点中说明。常用的属性有
- #address-cells,用来描述子节点"reg"属性的地址表中用来描述首地址的cell的数量,
- #size-cells,用来描述子节点"reg"属性的地址表中用来描述地址长度的cell的数量。
/*解析成平台设备的设备名字为"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>;
};
};
interrupts
一个计算机系统中大量设备都是通过中断请求CPU服务的,所以设备节点中就需要在指定中断号。常用的属性有
- interrupt-controller 一个空属性用来声明这个node接收中断信号,即这个node是一个中断控制器。
- #interrupt-cells,是中断控制器节点的属性,用来标识这个控制器需要几个单位做中断描述符,用来描述子节点中"interrupts"属性使用了父节点中的interrupts属性的具体的哪个值。一般,如果父节点的该属性的值是3,则子节点的interrupts一个cell的三个32bits整数值分别为:<中断域 中断 触发方式>,如果父节点的该属性是2,则是<中断 触发方式>。触发方式如下几种:
bits[3:0] 用来表示中断触发类型(trigger type and level flags):
1 = low-to-high edge triggered,上升沿触发
2 = high-to-low edge triggered,下降沿触发
4 = active high level-sensitive,高电平触发
8 = active low level-sensitive,低电平触发
- interrupt-parent,标识此设备节点属于哪一个中断控制器,如果没有设置这个属性,会自动依附父节点的
- interrupts,一个中断标识符列表,表示每一个中断输出信号
ethernet@5000000 {
compatible = "davicom , dm9000" ;
reg = <ox05000000 0×2 0x05000004 0x2>;
interrupt-parent = <&gpx0>;
interrupts = <64>;
ethernet@5000000节点的interrupt-parent是gpx0,gpx0节点的属性如下所示:
gpx0:gpx0 {
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
interrupt-parent = <&gic>;
interrupts = <0 16 0>,<0 17 0>,<0 18 0>,<0 19 0>,
<0 20 0>,<0 21 0>, <0 22 0>,<0 23 0> ;
#interrupt-cells = <2>;
};
在gpx0节点中,指定了"#interrupt-cells = <2>;",所以在ethernet@5000000节点中的属性"interrupts = <6 4>;"表示ethernet@5000000的中断在作为irq parant的gpx0中的中断偏移量,即gpx0中的属性"interrupts"中的"<0 22 0>",中断方式为高电平触发。
gpio
gpio也是最常见的IO口,常用的属性有
- "gpio-controller",用来说明该节点描述的是一个gpio控制器
- "#gpio-cells",用来描述gpio使用节点的属性一个cell的内容,即 `属性 = <&引用GPIO节点别名 GPIO标号 工作模式>
以下设备树表示foo_device节点使用了gpio组的第15个引脚,工作模式为高电平有效
foo_device {
compatible = "acme,foo";
...
led-gpios = <&gpio 15 GPIO_ACTIVE_HIGH>, /* red */
<&gpio 16 GPIO_ACTIVE_HIGH>, /* green */
<&gpio 17 GPIO_ACTIVE_HIGH>; /* blue */
3、内核对设备树的处理过程
从源代码文件 dts 文件开始,设备树的处理过程为:
1、dts在PC机上被编译为dtb文件
2、u-boot把dtb文件传给内核
3、内核解析dtb文件,把每一个节点都转换为device_node结构体
4、对于某些device_node结构体,会被转换为platform_device结构体
platform_device和plateform_driver如何进行配对
这两个结构体通过plateform_match函数进行匹配,函数定义如下:
1、首先比较:是否强制选择某个driver
比较platform_device.driver_override
和 platform_driver.driver.name
可以设置 platform_device
的 driver_override
,强制选择某个 platform_driver
。
2、然后比较:设备树信息
比较:platform_device.dev.of_node
和 platform_driver.driver.of_match_table
。
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属性存在,但是匹配补上就立即返回,不在进行后续的匹配。
3、如果平台驱动端提供了 pdrv->id_table
,则使用平台设备的名字与平台驱动 id_table
列表中的名字进行匹配
4、最后直接匹配平台设备的名字和平台驱动的名字