2. 设备树语法入门:节点与属性、标签与引用、包含文件与dtsi组织
设备树(Device Tree)是嵌入式Linux中用于描述硬件拓扑结构的扁平化数据格式。它使用节点(node)表示硬件设备,使用属性(property)描述设备特性,通过标签(label)与phandle实现跨节点引用,并借助#include与.dtsi文件实现模块化组织。掌握这些基础语法是编写正确设备树的前提。
2.1 节点(node)与属性(property)的定义
设备树的基本单元是节点。每个节点代表一个硬件设备或总线,节点内可包含子节点和属性。属性是键值对,用于描述设备的地址、中断、时钟、兼容性等信息。
2.1.1 节点语法
/dts-v1/;
/ {
model = "TI AM335x BeagleBone Black";
compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am335x";
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a8";
device_type = "cpu";
reg = <0x00>;
};
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; /* 512 MB */
};
};
| 语法元素 | 说明 | 示例 |
|---|---|---|
| 节点名 | 节点名称,可包含单元地址(@后跟地址) | cpu@0、memory@80000000 |
| 属性 | 键值对,值可为字符串、32位整数数组、字节数组等 | compatible = "arm,cortex-a8"; |
| 子节点 | 节点内可嵌套子节点,形成树形结构 | cpus 内含 cpu@0 |
| 根节点 | 用 / 表示,是设备树的顶层节点 | / { ... }; |
提示:节点名中的
@ 后跟的地址仅用于区分同名节点,不一定是物理地址。例如 i2c@44e0b000 表示该I2C控制器的寄存器基地址为0x44E0B000。
2.1.2 常用属性类型
| 属性类型 | 格式 | 示例 |
|---|---|---|
| 字符串 | "string" | model = "TI AM335x BeagleBone Black"; |
| 32位整数 | <value> | reg = <0x80000000 0x20000000>; |
| 64位整数 | <high low> | reg = <0x00000000 0x80000000>; |
| 字节数组 | [byte1 byte2 ...] | local-mac-address = [00 11 22 33 44 55]; |
| 字符串列表 | "str1", "str2", ... | compatible = "ti,am335x-bone", "ti,am335x"; |
| 布尔值 | 存在即真 | status = "disabled"; 或 status = "okay"; |
2.2 标签(label)与引用(phandle)
设备树中,节点可以通过标签定义别名,并通过phandle(指针句柄)实现跨节点引用。这是设备树实现“覆盖”和“复用”的核心机制。
2.2.1 标签定义
标签是节点前的标识符,使用冒号分隔:
uart0: serial@44e09000 {
compatible = "ti,am3352-uart", "ti,omap3-uart";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
};
此处 uart0 即为标签,可在其他位置通过 &uart0 引用该节点。
2.2.2 phandle 引用
phandle 是设备树内部自动生成的唯一整数标识符,用于在属性中引用其他节点。开发者通常使用 &label 语法,编译器会自动转换为 phandle:
/* 引用 uart0 节点 */
chosen {
stdout-path = &uart0;
};
/* 在属性中引用 */
clocks = <&clk_uart 0>;
关键区别:标签(label)是给开发者使用的符号名;phandle 是编译器生成的数字标识符。在 .dts 源文件中,始终使用
&label 语法,不要直接写 phandle 数字。
2.2.3 标签与引用的典型应用场景
- 引脚复用配置:在 pinctrl 节点中引用 GPIO 控制器
- 时钟/中断连接:引用时钟源或中断控制器
- 设备覆盖(overlay):通过标签向已有节点追加属性或子节点
- chosen 节点:设置内核启动参数和控制台路径
2.3 包含文件(#include)与 dtsi 文件组织
设备树源文件使用 #include 预处理指令引入公共定义,实现代码复用。扩展名为 .dtsi 的文件是设备树包含文件,通常存放 SoC 公共定义、外设模板和头文件宏。
2.3.1 文件类型与作用
| 扩展名 | 类型 | 作用 |
|---|---|---|
.dts | 设备树源文件 | 板级设备树,描述具体硬件板卡 |
.dtsi | 设备树包含文件 | SoC 公共定义、IP 核模板、头文件 |
.dtb | 编译后的二进制 | 内核引导时加载的设备树二进制 |
.dtbo | 设备树覆盖文件 | 运行时动态加载的设备树片段 |
2.3.2 典型文件组织层次
/* 1. SoC 级公共定义:am33xx.dtsi */
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/gpio/gpio.h>
/ {
compatible = "ti,am33xx";
cpus { ... };
ocp {
uart0: serial@44e09000 { ... };
i2c0: i2c@44e0b000 { ... };
gpio0: gpio@44e07000 { ... };
};
};
/* 2. 板级文件:am335x-boneblack.dts */
#include "am33xx.dtsi"
#include "am335x-bone-common.dtsi"
/ {
model = "TI AM335x BeagleBone Black";
compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am335x";
&uart0 {
status = "okay";
};
&i2c0 {
status = "okay";
clock-frequency = <400000>;
tps65217: tps@24 { ... };
};
};
注意:在 .dts 文件中使用
&label 语法引用 .dtsi 中定义的节点时,不能重复定义根节点 / 内的相同节点名,否则会导致编译冲突。正确的做法是使用 &label { ... }; 追加属性或子节点。
2.3.3 头文件与宏定义
设备树支持通过 #include 引入 C 语言头文件,使用宏定义提高可读性:
/* dt-bindings/gpio/gpio.h */
#define GPIO_ACTIVE_HIGH 0
#define GPIO_ACTIVE_LOW 1
/* 在 dts 中使用 */
reset-gpios = <&gpio0 7 GPIO_ACTIVE_LOW>;
enable-gpios = <&gpio1 15 GPIO_ACTIVE_HIGH>;
2.4 语法避坑与调试技巧
常见错误:
- 忘记分号:每个属性值后必须加分号
;,节点结束用}; - 标签重复:同一 .dts/.dtsi 中标签名必须唯一
- 引用未定义节点:使用
&label前必须确保标签已定义 - 地址/大小单元不匹配:
#address-cells和#size-cells必须与reg属性中的数值个数一致
调试技巧:
- 使用
dtc -I dts -O dtb -o output.dtb input.dts编译设备树 - 使用
dtc -I dtb -O dts -o output.dts input.dtb反编译查看二进制内容 - 使用
fdtdump input.dtb快速查看设备树结构 - 内核启动时添加
earlycon和dtoc参数可输出设备树解析日志
2.5 本章小结
| 知识点 | 核心要点 |
|---|---|
| 节点与属性 | 节点表示硬件设备,属性描述设备特性;根节点用 / 表示 |
| 标签与 phandle | 标签是节点别名,通过 &label 引用;编译器自动生成 phandle |
| 包含文件 | #include 引入 .dtsi 和头文件,实现 SoC 级复用 |
| 文件组织 | .dts 板级文件 → .dtsi SoC 公共定义 → dt-bindings 头文件宏 |