它是一种描述硬件资源的数据结构,可以通过bootloader将它传给内核,内核(driver)使用它对硬件进行初始化,好处是使得内核和硬件资源描述相对独立,不需要太多的硬编码。
2、设备树的相关名词
1)DTS(device tree source)
.dts文件是一种ASCII文本对Device Tree的描述,位于linux-4.10//arch/arm64/boot/dts目录下。
2)DTC(device tree compiler)
DTC为编译工具,它可以将.dts文件编译成.dtb文件,DTC的源码位于linux-4.10/scripts/dtc目录下。
3)DTB(device tree blob)
DTC编译.dts生成的二进制文件(.dtb),bootloader在加载内核时,也会同时把.dtb加载到内存,后面传递给内核使用。
3、DTS 文件格式
例如 linux-4.10/arch/arm64/boot/dts/arm64-demo.dts
16 #include "arm64-demo.dtsi"
17
18 / {
19 model = "arm64-demo Board";
20 compatible = "arm,arm64-demo";
21
22 aliases {
23 serial0 = &uart0;
24 serial1 = &uart1;
25 serial2 = &uart2;
26 serial3 = &uart3;
27 };
28
29 memory@40000000 {
30 device_type = "memory";
31 reg = <0 0x40000000 0 0x1e800000>;
32 };
33
34 chosen {
35 stdout-path = "serial0:921600n8";
36 };
37 };
38
39 &uart0 {
40 status = "okay";
41 };
dts目录下并没有 arm64-demo.dts 这样的文件,这里只是为了举例,dts目录下有其他arm芯片厂商的dts 文件可以参考一下
"/"为root节点,在一个.dts文件中,有且仅有一个root节点,#include "arm64-demo.dtsi",跟代码中的include 头文件的作用差不多,也就是把rm64-demo.dtsi定义的device tree节点包含到arm64-demo.dts中,虽然arm64-demo.dtsi 文件中也会有一个"/",但是dtc编译时,会把它们合并成一个。
1)aliases node
22 aliases {
23 serial0 = &uart0;
24 serial1 = &uart1;
25 serial2 = &uart2;
26 serial3 = &uart3;
27 };
aliases 节点定义了一些别名。为何要定义这个node呢?因为Device tree是树状结构,当要引用一个node的时候要指明相对于root node的full path。例如
linux-4.10/arch/arm64/boot/dts/arm64-demo.dtsi
140 uart0: serial@11002000 {
141 compatible = "mediatek,mt6795-uart",
142 "mediatek,mt6577-uart";
143 reg = <0 0x11002000 0 0x400>;
144 interrupts = <GIC_SPI 91 IRQ_TYPE_LEVEL_LOW>;
145 clocks = <&uart_clk>;
146 status = "disabled";
147 };
serial0 = &uart0; 所以serial0 就是/serial@11002000 的一个别名,uart0 是一个lable,也是/serial@11002000,使用lable需要在前面加上& 。例如
39 &uart0 {
40 status = "okay";
41 };
就是把/serial@11002000 节点里面的status 属性改成okay
2)memory node
29 memory@40000000 {
30 device_type = "memory";
31 reg = <0 0x40000000 0 0x1e800000>;
32
对于memory node,device_type必须为memory,memory device node是所有设备树文件的必备节点,它定义了系统物理内存的layout。reg描述了memory-mapped IO register的offset和length。对于memory node,定义了该memory的起始地址和长度,这里的0 0x40000000 是起始地址,0 0x1e800000 是内存的大小(长度)。linux-4.10/arch/arm64/boot/dts/arm64-demo.dtsi
20 #address-cells = <2>;
21 #size-cells = <2>;
为什么是0 0x40000000 表示起始地址,因为root 节点 #address-cells = <2>; 表示用两个cell (32位),同样的#size-cells = <2> 表示用两个cell (32位)。
每个node用节点名字(node name)标识,节点名字的格式是node-name@unit-address。如果该node没有reg属性,那么该节点名字中必须不能包括@和unit-address。unit-address的具体格式是和设备挂在那个bus上相关。
所以上面memory 的描述是,起始地址是 0x 80000000
3)chosen node
34 chosen {
35 stdout-path = "serial0:921600n8";
36 };
chosen node 主要用来描述由系统指定的runtime parameter,它并没有描述任何硬件设备节点信息。原先通过tag list传递的一些linux kernel运行的参数,可以通过chosen节点来传递。如command line可以通过bootargs这个property来传递。如果存在chosen node,它的parent节点必须为“/”根节点。4、编译后的dtb 格式
1)fdt_header
定义在 linux-4.10/scripts/dtc/libfdt/fdt.h
57 struct fdt_header {
58 fdt32_t magic; /* magic word FDT_MAGIC */
59 fdt32_t totalsize; /* total size of DT block */
60 fdt32_t off_dt_struct; /* offset to structure */
61 fdt32_t off_dt_strings; /* offset to strings */
62 fdt32_t off_mem_rsvmap; /* offset to memory reserve map */
63 fdt32_t version; /* format version */
64 fdt32_t last_comp_version; /* last compatible version */
65
66 /* version 2 fields below */
67 fdt32_t boot_cpuid_phys; /* Which physical CPU id we're
68 booting on */
69 /* version 3 fields below */
70 fdt32_t size_dt_strings; /* size of the strings block */
71
72 /* version 17 fields below */
73 fdt32_t size_dt_struct; /* size of the structure block */
74 };
off_dt_struct 是到dt_struct 结构块的偏移量(相对于文件起始位置),off_dt_strings 是到dt_strings字符串块的偏移量(相对于文件起始位置),off_mem_rsvmap 是到memory reserve map 区域的偏移量(相对于文件起始位置)。2)memory reserve map
该区域保存的数据会4字节对齐
3)dt_struct
结构块里面保存了dts 里面描述的设备信息,和dts 里面写的内容一致,只不过转成了另一种数据格式。节点的开始和结束,属性的开始用下面定义的标识。
linux-4.10/scripts/dtc/libfdt/fdt.h
98 #define FDT_BEGIN_NODE 0x1 /* Start node: full name */
99 #define FDT_END_NODE 0x2 /* End node */
100 #define FDT_PROP 0x3 /* Property: name off,
101 size, content */
102 #define FDT_NOP 0x4 /* nop */
103 #define FDT_END 0x9
4)off_dt_strings字符串块 保存的是dts中属性的名字,因为在不同的节点中会用到相同的属性名,为了减少保存重复的属性名字符串,所以把它们放在字符串块中,每个字符串是以\0为结束标识。
详细的dtb格式如上图,我们以memory 节点为例,由dts转成dtb 是怎么样的
29 memory {
30 device_type = "memory";
31 reg = <0 0x40000000 0 0x1e800000>;
32 };
00 00 00 01 6d 65 6d 6f 72 79 00 00 00 00 00 03
00 00 00 07 00 00 00 10 6d 65 6d 6f 72 79 00 00
00 00 00 03 00 00 00 10 00 00 00 20 00 00 00 00
40 00 00 00 00 00 00 00 00 1e 80 00 00 00 00 02
第一行的00 00 00 01 就是 FDT_BEGIN_NODE,紧接着的6d 65 6d 6f 72 79 00 00 就是节点的名称 memory(也会做4字节对齐),在往后面00 00 00 03 就是FDT_PROP,标志这属性,第二行的00 00 00 07 指示了属性值的大小,后面的 00 00 00 10 是属性名称在dt_strings中的偏移,这里我是随便写的,device_type = "memory"; 属性值memory 的大小明明是6,为什么是7呢,因为要加上\0,所以是7,后面还有00 是为了保证4字节对齐,接下来第三行又是00 00 00 03,又是一个属性的开始,也就是reg = <0 0x40000000 0 0x1e800000>; 接着一样是属性值的大小,0 0x40000000 0 0x1e800000 会占用16个字节,所以是 0x00000010,后面就是对应的值,最后的00 00 00 02 就是FDT_END_NODE,标识一个节点结束。