3、设备树语法基础:节点、属性、标签、包含文件、编译与反编译
设备树(Device Tree)是一种描述硬件资源的数据结构,它使用树形节点层次结构将非可枚举的硬件细节从内核代码中分离出来。本章将系统讲解设备树的语法核心要素,包括节点定义、属性赋值、标签引用、包含文件机制以及编译/反编译工具链的使用。
3.1 设备树节点(Node)
节点是设备树的基本组成单元,每个节点代表一个硬件设备、总线或控制器。节点通过路径唯一标识,并可以嵌套形成父子层次关系。
节点语法格式:
[label:] node-name[@unit-address] {
[property-list];
[child-node1 {
...
}];
[child-node2 {
...
}];
};
节点命名规则:
- node-name:通用名称,如
cpu、memory、i2c,长度 1~31 字符。 - unit-address:可选,表示设备在总线上的地址(寄存器基址、I2C 从机地址等),与
reg属性对应。 - label:可选,为节点起别名,便于其他节点通过
&label引用。
最佳实践:节点名称应使用小写字母、数字和连字符,避免使用下划线。例如
serial@ff010000 优于 Serial_0@ff010000。
3.2 属性(Property)
属性用于描述节点的特征,包括硬件地址、中断信息、时钟频率、兼容性等。属性由键值对组成,值可以是多种数据类型。
| 属性类型 | 语法示例 | 说明 |
|---|---|---|
| 空属性 | status = "disabled"; |
布尔标志,存在即表示真 |
| 字符串 | compatible = "arm,cortex-a9"; |
双引号包裹,用于设备匹配 |
| 32位整数 | reg = <0x10000000 0x1000>; |
尖括号包裹,空格分隔 |
| 64位整数 | clock-frequency = <0x1 0xe4e1c0>; |
两个 32 位单元表示 64 位值 |
| 字节数组 | local-mac-address = [00 11 22 33 44 55]; |
方括号包裹,十六进制字节 |
| 字符串列表 | interrupt-names = "rx", "tx"; |
逗号分隔多个字符串 |
| 混合类型 | gpios = <&gpio0 17 0>; |
可包含 phandle 引用 |
常见错误:属性值类型不匹配是设备树编译错误的常见原因。例如将字符串值写为整数格式(
status = disabled; 缺少引号)会导致编译失败。
3.3 标签(Label)与引用
标签为节点提供符号名称,便于在设备树的其他位置通过 &label 语法引用该节点,实现节点内容的追加或覆盖。
// 定义标签
uart0: serial@ff010000 {
compatible = "snps,dw-apb-uart";
reg = <0xff010000 0x1000>;
interrupts = <0 32 4>;
status = "okay";
};
// 在其他位置引用标签,追加属性
&uart0 {
clock-frequency = <24000000>;
current-speed = <115200>;
};
标签使用场景:
- 板级覆盖:在
.dtsi文件中定义通用节点,在.dts文件中通过标签引用并定制。 - phandle 引用:在属性值中使用
&label作为句柄,建立节点间的关联关系。 - 条件编译:配合
/delete-node/或/delete-property/实现节点裁剪。
3.4 包含文件机制
设备树源文件支持 #include 预处理指令,用于引入头文件(.dtsi)或宏定义文件(.h)。这是实现代码复用和分层设计的关键机制。
// board.dts 文件
/dts-v1/;
#include "soc.dtsi" // 引入 SoC 通用定义
#include "pinmux.h" // 引入引脚复用宏定义
#include "memory-layout.h" // 引入内存布局常量
/ {
model = "Custom Board v1.0";
compatible = "vendor,custom-board";
chosen {
bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2";
};
&uart0 {
pinctrl-0 = <&uart0_pins>;
pinctrl-names = "default";
};
};
包含文件最佳实践:
| 文件类型 | 命名约定 | 内容范围 |
|---|---|---|
| SoC 定义文件 | soc.dtsi |
CPU、中断控制器、时钟、总线等 SoC 级硬件 |
| 板级定义文件 | board.dts |
板级外设、GPIO 配置、内存布局 |
| 宏定义头文件 | pinmux.h |
引脚功能编号、时钟 ID、中断号宏 |
| 内存布局文件 | memory-layout.h |
DRAM 基址、外设寄存器基址常量 |
注意:
#include 是 C 预处理指令,设备树编译器(dtc)会先调用 C 预处理器展开所有 #include、#define 和 #ifdef 宏。因此可以在 .dts 文件中使用 #define 定义常量,提高可维护性。
3.5 编译与反编译
设备树编译器(dtc)是设备树源文件与二进制文件之间转换的核心工具。理解编译和反编译流程对于调试和验证设备树配置至关重要。
3.5.1 编译流程
将 .dts 源文件编译为 .dtb 二进制文件,供 bootloader 或内核使用。
# 基本编译
dtc -I dts -O dtb -o board.dtb board.dts
# 保留符号信息(用于调试)
dtc -I dts -O dtb -o board.dtb -@ board.dts
# 输出汇编形式的设备树(.dts 转 .dtb 再转 .S)
dtc -I dts -O dtb -o board.dtb board.dts
dtc -I dtb -O asm -o board.S board.dtb
3.5.2 反编译流程
将 .dtb 二进制文件还原为可读的 .dts 源文件,用于逆向分析或验证编译结果。
# 基本反编译
dtc -I dtb -O dts -o board_decompiled.dts board.dtb
# 反编译并保留 phandle 信息
dtc -I dtb -O dts -o board_decompiled.dts -@ board.dtb
# 检查 dtb 文件有效性
dtc -I dtb -O dtb -o /dev/null board.dtb
3.5.3 编译选项对照表
| 选项 | 功能 | 使用场景 |
|---|---|---|
-I dts |
指定输入格式为源文件 | 编译 .dts → .dtb |
-I dtb |
指定输入格式为二进制 | 反编译 .dtb → .dts |
-O dtb |
指定输出格式为二进制 | 生成内核可加载的 dtb |
-O dts |
指定输出格式为源文件 | 生成可读的 dts 文本 |
-@ |
保留符号和 phandle 信息 | 支持设备树叠加层(overlay) |
-H epapr |
生成 ePAPR 标准 dtb | 兼容旧版 bootloader |
-W no-unit_address_vs_reg |
忽略地址警告 | 临时屏蔽非关键警告 |
3.6 实战案例:完整设备树文件解析
以下是一个完整的设备树文件示例,综合展示了节点、属性、标签、包含文件的使用方法。
// memory-layout.h
#define UART0_BASE 0xFF010000
#define UART0_SIZE 0x1000
#define IRQ_UART0 32
// soc.dtsi
/ {
#address-cells = <1>;
#size-cells = <1>;
uart0: serial@ff010000 {
compatible = "snps,dw-apb-uart";
reg = <UART0_BASE UART0_SIZE>;
interrupts = <0 IRQ_UART0 4>;
interrupt-parent = <&gic>;
status = "disabled";
};
gic: interrupt-controller@fff00000 {
compatible = "arm,cortex-a9-gic";
reg = <0xfff00000 0x1000>;
interrupt-controller;
#interrupt-cells = <3>;
};
};
// board.dts
/dts-v1/;
#include "soc.dtsi"
#include "memory-layout.h"
/ {
model = "Embedded Learning Board v2.0";
compatible = "edu,emb-board-v2";
chosen {
bootargs = "console=ttyS0,115200 earlyprintk";
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; // 512MB
};
&uart0 {
pinctrl-0 = <&uart0_pins>;
pinctrl-names = "default";
clock-frequency = <24000000>;
status = "okay";
};
};
关键要点总结:
- 节点是设备树的骨架,通过路径和标签唯一标识。
- 属性描述节点的具体硬件特征,支持多种数据类型。
- 标签实现节点引用和覆盖,是分层设计的核心机制。
- 包含文件通过
#include实现代码复用,支持宏定义。 - 编译/反编译使用 dtc 工具,
-@选项保留符号信息用于调试。