device tree 简介

设备树是一种描述硬件资源的数据结构,用于在Linux内核启动时传递硬件信息。它包含DTS(设备树源文件)、DTC(设备树编译器)和DTB(设备树blob)。DTS文件以ASCII文本形式描述硬件,DTC将其编译为二进制DTB,供bootloader加载并传递给内核。设备树通过别名、内存节点和选定节点等结构简化了硬件初始化过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、什么是设备树(device tree)

它是一种描述硬件资源的数据结构,可以通过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,标识一个节点结束。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值