设备树
1. 设备树由来
在Linux内核源码引入内核源码的ARM体系结构引入设备树之前,相关的BSP代码中充斥了大量的平台设备代码(platform device)。而这些代码大多是重复的、杂乱的,这就给内核移植带来了巨大的困难。以至于Linux之父Linus怒斥ARM社区。
为了解决此问题,设备树就被引用到Linux中来。(需要注意的是设备树并不是Linux的首创,设备树最初是在Power PC中开始使用的)
2. 设备树的目的
设备树是一个描述硬件的数据结构。它也不能把所有硬件的配置问题都给解决掉,它只是提供了一种语言,将硬件配置从Linux内核源码中提取出来 。设备树使得目标板和设备编程数据驱动的,它们必须基于传递给内核的数据进行初始化,而不是像以前一样采用硬编码的方式。理论上,这种方式可以带来较少的代码重复率,使得单个内核镜像能够支持很多硬件平台。
Linux使用设备树的目的主要由以下三个:
1. 平台识别
最重要的是,内核使用设备树去识别特定机器(目标板,在内核中称为machine)。最完美的情况是,内核应该与特定的硬件平台无关,然而,因为所有硬件平台的并不都是完美的。所以内核必须在早期初始化阶段识别机器,这样内核才有机会运行与特定机器相关的初始化序列。
2. 实时配置
多数情况下,设备树是固件与内核之间进行数据通信的唯一方式,所以也用于传递实时配置数据给内核,比如内核参数、initrd镜像的地址等。
大多数这种数据结构被包含在设备树的/chosen节点,比如:
chosen { bootargs = "ttyS0,115200,loglevel=8"
initrd-start = <0xc8000000>;
initrd-end = <0xc8200000>;
};
@bootargs属性包含内核参数
@initrd-*属性定义了initrd文件首地址大小。
chosen节点也有可能包含任意数量的描述平台特殊配置的属性。
在ARM平台,setup_machine_fdt函数负责在选取到正确的machine_desc结构之后进行早期的设备树遍历。
3. 设备植入
经过板子早期配置数据解析之后,内核进一步进行初始化。期间,unflatten_device_tree函数被调用,将设备树的数据转成一种更有效的实时形式。同时机器特殊的启动钩子函数也会被调用。例如init_machine函数,在/arch/arm/XXXX/xxxx-dt.c文件中,该函数中有如下一句:
of_platform_populate(NULL,of_default_bus_match_table,NULL,NULL);
of_platform_populate函数的作用是遍历设备树中的节点,把匹配的的节点转换成平台设备,然后注册到内核中。
3. Linux设备树的使用
基本数据格式
接下来我们来看一下,设备树的基本数据格式。在Linux中,设备树文件类型有.dts、.dtsi、和.dtb。其中.dtsi是被包含的设备树源文件,类似于C语言中的头文件;.dts是设备树源文件,可以包含其他.dtsi文件,由dts编译生成.dtb文件。
设备树是一个包含节点和属性的简单树状结构。属性就是键值对,而节点可以同时包含属性和子节点。下面是一个简单的.dts的简单设备树:
/{
nodel{
a-string-property = "A string";
a-string-list-property = "first string","second string";
a-byte-data-property = [0x01 0x23 0x34 00x56];
child-nodel{
first-child-property;
second-child-property = <1>;
a-string-property = "Hello,world";
};
child-node2{
};
};
nodel2{
an-empty-protery;
a-cell-property = <1>;
child-nodel1{
};
};
};
下面来分析一下该设备树,该设备树主要包含了一下信息:
-
一个单独的根节点: /
-
两个子节点nodel1和nodel2
-
两个nodel的子节点:child-node1和child-node2
-
一堆分散在设备树中的属性。属性也就是键值对,它的值可以为空或办函一个任意字节流在设备树源文件中有以下介个基本的数据表示形式:
文本字符串(无结束符):可以用双引号表示,如a-string-property = “A string ”。
cells:32位无符号整数,用角括号限定,如second-child-property = <1>
二进制数据: 用方括号限定,如a-byte-data-property = [0x01 0x23 0x34 0x56]
混合表示:使用括号限定,如mixed-property = “a-string”, [0x01 0x23 0x45 0x67] ,<0x12345678>
字符串列表:使用逗号连在一起,如string-list = “red fish”, “blue fish”。
那么一个完整的设备树文件应该包含以下内容:
-
包含其他的"dtsi"文件,比如
#include "skelenton.dtsi"
-
节点名称,是一个"<名称>[@<设备地址>]"形式的名字。方括号中的内容不是必需的。"名称"是一个不超过31位的简单ASCII码的字符串,应根据它所选择的设备来进行命名。如果该节点描述的设备有一个地址就应该加上单元地址,通常,设备地址就是用来访问该设备的主地址。并且该地址也在接待的reg属性中列出。同级节点命名必须是唯一的,但是只要地址不同,多个节点也可以使用一样的通用名称。节点名称示例:
serial@13800000{ ... };
-
系统中每个设备都表示为一个设备树节点,每个设备节点都拥有一个compatible属性。
-
compatible属性是操作系统用来决定使用哪个设备驱动来绑定到一个设备上的关键因素。compatible是一个字符串列表,第一个字符串指定了这个节点所表示的确切的设备,该字符串的格式为:"<制造商>,<型号>",其余的字符串则表示其他与之兼容的设备。比如:
compatible = "arm,p1330","arm,primecell";
-
可编址设备使用属性将地址信息编码进设备树:
reg #address-cells #size-cells
compatible是一个字符串列表,第一个字符串指定了这个节点所表示的确切的设备,该字符串的格式为:
"<制造商>,<型号>"
其余的字符串则表示其他与之兼容的设备。比如:
```c
compatible = "arm,p1330","arm,primecell";
```
-
可编址设备使用属性将地址信息编码进设备树:
reg #address-cells #size-cells
每一个可编址设备都有一个reg,它是一个元组表,形式为reg = <地址 1 长度1[地址2 长度2][地址3 长度3]…>每个元组都表示该设备使用的一个地址范围。每个地址值是一个或多个32位整型数列表,称为cell。
同样,长度值也可以是一个cell列表或者为空。由于地址和长度字段都是可变大小的变量,那么父节点的#address-cells和#size-cell属性就用来申明各个字段的cell的数量。换句话说就是正确解释一个reg属性需要用到父节点的#address-cells和#size-cells的值。