Linux设备树

发布时间 2023-12-21 19:53:32作者: mjy66

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、设备树框架

设备树用树状结构描述设备信息,它有以下几种特性

  1. 每个设备树文件都有一个根节点,每个设备都是一个节点。
  2. 节点间可以嵌套,形成父子关系,这样就可以方便的描述设备间的关系。
  3. 每个设备的属性都用一组key-value对(键值对)来描述。
  4. 每个属性的描述用;结束

其文件布局如下所示,/表示板子:

/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_overrideplatform_driver.driver.name
可以设置 platform_devicedriver_override,强制选择某个 platform_driver

2、然后比较:设备树信息

比较:platform_device.dev.of_nodeplatform_driver.driver.of_match_table

of_match_tablestruct 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、最后直接匹配平台设备的名字和平台驱动的名字