18.2 设备树的组成和结构
整个设备树牵涉面比较广,即增加了新的用于描述设备硬件信息的文本格式,又增加了编译这个文本的工具,同时Bootloader(U-boot)也需要支持将编译后的设备树传递给Linux内核。
18.2.1 DTS、DTC和DTB等
1.DTS
文件.dts是一种ASCII文本格式的设备树描述,此文本格式非常人性化,适合人类的阅读习惯。在ARM Linux中,一个.dts文件对应一个ARM的设备,一般放置在内核的arch/arm/boot/dts/目录中。在arch/powerpc/boot/dts、arch/c6x/boot/dts、arch/openrisc/boot/dts等目录中,也存在大量的.dts文件,这证明DTS绝对不是ARM的专利。
由于一个SoC可能对应多个设备(一个SoC可以对应多个产品和电路板),这些.dts文件势必须包含许多共同的部分,Linux内核为了简化,把SoC公用的部分或者多个设备共同的部分一般提炼为.dtsi,类似于C语言的头文件。其他的设备对应的.dts就包括这个.dtsi。譬如,对于VEXPRESS而言,vexpress-v2m.dtsi就被vexpress-v2p-ca9.dts所引用,vexpress-v2p-ca9.dts有如下一行代码:/include/ "vexpress-v2m.dtsi"
和C语言的头文件类似,.dtsi也可以包括其他的.dtsi,譬如几乎所有的ARM SoC的.dtsi都引用了skeleton.dtsi。文件.dts(或者其包括的.dtsi)的基本元素即为上面所述的节点和属性,代码清单18.1给出了一个设备树结构的模版。
代码清单18.1 设备树结构模版
1/ {
2 node1 {
3 a-string-property = "A string";
4 a-string-list-property = "first string", "second string";
5 a-byte-data-property = [0x01 0x23 0x34 0x56];
6 child-node1 {
7 first-child-property;
8 second-child-property = <1>;
9 a-string-property = "Hello, world";
10 };
11 child-node2 {
12 };
13 };
14 node2 {
15 an-empty-property;
16 a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */
17 child-node1 {
18 };
19 };
20};
上述.dts文件并没有什么真实的用途,但它基本表征了一个设备树源文件的结构:
1个root节点"/";root节点下面含一系列子节点,本例中为node1和node2;节点node1下又含有一系列子节点,本例中为child-node1和child-node2;各节点都有一系列属性。这些属性可能为空,如an-empty-property;可能为字符串,如a-string-property;可能为字符串数组,如a-string-list-property;可能为Cells(由u32整数组成),如second-child-property;可能为二进制数,如a-byte-data-property。
以一个最简单的设备为例来看如何写一个.dts文件。如图18.1所示,假设此设备的配置如下:
图18.1 设备树参考硬件结构图
1个双核ARM Cortex-A9 32位处理器;ARM本地总线上的内存映射区域分布有两个串口(分别位于0x101F1000和0x101F2000)、SPI控制器(位于0x10170000)、GPIO控制器(位于0x101F3000)、中断控制器(位于0x10140000)和一个外部总线桥;外部总线桥上又连接了SMC SMC91111以太网(位于0x10100000)、64MB NOR Flash(位于0x30000000)、I2C控制器(位于0x10160000);外部总线桥上连接的I2C控制器所对应的I2C总线上又连接了Maxim DS1338实时钟(I2C地址为0x58)。
对于图18.1所示硬件结构图,如果用“.dts”描述,则其对应的“.dts”文件如代码清单18.2所示。
代码清单18.2 参考硬件的设备树文件
1/ {
2 compatible = "acme,coyotes-revenge";
3 #address-cells = <1>;
4 #size-cells = <1>;
5 interrupt-parent = <&intc>;
6
7 cpus {
8 #address-cells = <1>;
9 #size-cells = <0>;
10 cpu@0 {
11 compatible = "arm,cortex-a9";
12 reg = <0>;
13 };
14 cpu@1 {
15 compatible = "arm,cortex-a9";
16 reg = <1>;
17 };
18 };
19
20 serial@101f0000 {
21 compatible = "arm,pl011";
22 reg = <0x101f0000 0x1000 >;
23 interrupts = < 1 0 >;
24 };
25
26 serial@101f2000 {
27 compatible = "arm,pl011";
28 reg = <0x101f2000 0x1000 >;
29 interrupts = < 2 0 >;
30 };
31
32 gpio@101f3000 {
33 compatible = "arm,pl061";
34 reg = <0x101f3000 0x1000
35 0x101f4000 0x0010>;
36 interrupts = < 3 0 >;
37 };
38
39 intc: interrupt-controller@10140000 {
40 compatible = "arm,pl190";
41 reg = <0x10140000 0x1000 >;
42 interrupt-controller;
43 #interrupt-cells = <2>;
44 };
45
46 spi@10115000 {
47 compatible = "arm,pl022";
48 reg = <0x10115000 0x1000 >;
49 interrupts = < 4 0 >;
50 };
51
52 external-bus {
53 #address-cells = <2>
54 #size-cells = <1>;
55 ranges = <0 0 0x10100000 0x10000 // Chipselect 1, Ethernet
56 1 0 0x10160000 0x10000 // Chipselect 2, i2c controller
57 2 0 0x30000000 0x1000000>; // Chipselect 3, NOR Flash
58
59 ethernet@0,0 {
60 compatible = "smc,smc91c111";
61 reg = <0 0 0x1000>;
62 interrupts = < 5 2 >;
63 };
64
65 i2c@1,0 {
66 compatible = "acme,a1234-i2c-bus";
67 #address-cells = <1>;
68 #size-cells = <0>;
69 reg = <1 0 0x1000>;
70 interrupts = < 6 2 >;
71 rtc@58 {
72 compatible = "maxim,ds1338";
73 reg = <58>;
74 interrupts = < 7 3 >;
75 };
76 };
77
78 flash@2,0 {
79 compatible = "samsung,k8f1315ebm", "cfi-flash";
80 reg = <2 0 0x4000000>;
81 };
82 };
83};
在上述.dts文件中,可以看出external-bus是根节点的子节点,而I2C又是external-bus的子节点,rtc又进一步是I2C的子节点。每一级节点都有一些属性信息,后续会进行详细解释。
2.DTC(Device Tree Compiler)
DTC是将.dts编译为.dtb的工具。DTC的源代码位于内核的scripts/dtc目录中,在Linux内核使能了设备树的情况下,编译内核的时候主机工具DTC会被编译出来,对应于scripts/dtc/Makefile中“hostprogs-y:=dtc”这一hostprogs的编译目标。
当然,DTC也可以在Ubuntu中单独安装,命令如下:
sudo apt-get install device-tree-compiler
在Linux内核的arch/arm/boot/dts/Makefile中,描述了当某种SoC被选中后,哪些.dtb文件会被编译出来,如与VEXPRESS对应的.dtb包括:
dtb-$(CONFIG_ARCH_VEXPRESS) += vexpress-v2p-ca5s.dtb \
vexpress-v2p-ca9.dtb \
vexpress-v2p-ca15-tc1.dtb \
vexpress-v2p-ca15_a7.dtb
在Linux下,可以单独编译设备树文件。当在Linux内核下运行make dtbs时,若之前选择了CONFIG_ARCH_VEXPRESS,上述.dtb都会由对应的.dts编译出来,因为arch/arm/Makefile中含有一个.dtbs编译目标项目。
DTC除了可以编译.dts文件以外,也可以“反汇编”.dtb文件为.dts文件,其指令格式为:
./scripts/dtc/dtc -I dtb -O dts -o xxx.dts arch/arm/boot/dts/xxx.dtb
3.DTB(Device Tree Blob)
文件.dtb是.dts被DTC编译后的二进制格式的设备树描述,可由Linux内核解析,U-Boot也是可以识别.dtb的。通常在为电路板制作NAND、SD启动映像时,会为.dtb文件单独留下一个很小的区域以存放之,之后bootloader在引导内核的过程中,会先读取该.dtb到内存。
Linux内核也支持一种变通的模式,可以不把.dtb文件单独存放,而是直接和zImage绑定在一起做成一个映像文件,类似cat zImage xxx.dtb>zImage_with_dtb的效果。内核编译时候要使能CONFIG_ARM_APPENDED_DTB这个选项,以支持“Use appended device tree blob to zImage”。
4.绑定(Binding)
对于设备树中的节点和属性具体是如何来描述设备的硬件细节的,一般需要文档来进行讲解,文档的后缀名一般为.txt。在这个.txt文件中,需要描述对应节点的兼容性、必需的属性和可选的属性。
这些文档位于内核的Documentation/devicetree/bindings目录下,其下又分为很多子目录。譬如,Documentation/devicetree/bindings/i2c/i2c-xiic.txt描述了Xilinx的I2C控制器,其内容如下:
Xilinx IIC controller:
Required properties:
- compatible : Must be "xlnx,xps-iic-2.00.a"
- reg : IIC register location and length
- interrupts : IIC controller unterrupt
- #address-cells = <1>
- #size-cells = <0>
Optional properties:
- Child nodes conforming to i2c bus binding
Example:
axi_iic_0: i2c@40800000 {
compatible = "xlnx,xps-iic-2.00.a";
interrupts = < 1 2 >;
reg = < 0x40800000 0x10000 >;
#size-cells = <0>;
#address-cells = <1>;
};
设备树绑定文档的主要内容包括:
关于该模块最基本的描述。
必需属性(Required Properties)的描述。
可选属性(Optional Properties)的描述。
一个实例。
Linux内核下的scripts/checkpatch.pl会运行一个检查,如果有人在设备树中新添加了compatible字符串,而没有添加相应的文档进行解释,checkpatch程序会报出警告:UNDOCUMENTED_DT_STRINGDT compatible string xxx appears un-documented,因此要养成及时写DT Binding文档的习惯。
5.Bootloader
U-boot设备从v1.1.3开始支持设备树,其对ARM的支持则是和ARM内核支持设备树同期完成。
为了使能设备树,需要在编译Uboot的时候在config文件中加入:#define CONFIG_OF_LIBFDT
在Uboot中,可以从NAND、SD或者TFTP等任意介质中将.dtb读入内存,假设.dtb放入的内存地址为0x71000000,之后可在Uboot中运行fdt addr命令设置.dtb的地址,如:
UBoot> fdt addr 0x71000000
fdt(flattened device tree)的其他命令就变得可以使用,如fdt resize、fdt print等。
对于ARM来讲,可以通过bootz kernel_addr initrd_address dtb_address的命令来启动内核,即dtb_address
作为bootz或者bootm的最后一次参数,第一个参数为内核映像的地址,第二个参数为initrd的地址,若不存在initrd,可以用“-”符号代替。