3. 设备树语法入门
设备树源文件(DTS)本质上是一种结构化的文本描述语言,它用树形层级表达硬件拓扑,用属性键值对描述硬件参数。掌握其语法规则,是读懂和编写板级资源描述的第一步。本节从节点、属性、标签、引用、数据类型、字符串和单元地址七个维度,系统梳理 DTS 的书写规则,并给出可直接编译的简单示例。
一、核心原理:DTS 的树形结构与基本组成
设备树以根节点 / 为起点,向下派生子节点,形成一棵有向树。每个节点代表一个硬件设备或总线,每个属性代表该设备的一项配置。DTS 编译后生成 DTB(二进制设备树),由内核在启动时解析并展开为 device_node 与 property 结构。
一个节点的完整书写形式如下:
label: node-name@unit-address {
property-name = value;
child-node {
...
};
};
其中 label 是可选的标签,node-name 是节点名,@unit-address 是单元地址,花括号内是属性与子节点。分号 ; 是属性赋值的结束符,不可省略。
二、节点命名与单元地址规则
节点名应体现设备类型或功能,采用小写字母、数字和连字符,例如 uart0、i2c1、gpio-keys。单元地址用于区分同一总线上的多个同类设备,通常取自设备的物理基地址或片选编号,写在 @ 之后。
- 节点名与单元地址共同构成节点的唯一路径,如
/soc/uart@10000000。 - 若同一层级存在多个同类节点,单元地址必须不同,否则编译报错。
- 单元地址使用十六进制时通常不加
0x前缀,如@10000000;也可使用十进制。 - 根节点下的
cpus、memory、chosen等为约定节点名,具有特殊语义。
三、属性、数据类型与字符串书写
属性是节点的键值对,值可以是空、字符串、整数数组、字节数组、混合数组或引用。不同类型在 DTS 中的书写方式不同,编译后生成的 DTB 编码也不同。
| 数据类型 | DTS 书写示例 | 说明 |
|---|---|---|
| 空属性 | interrupt-controller; | 仅表示存在,无值 |
| 字符串 | compatible = "vendor,uart"; | 双引号包裹,可多个字符串 |
| 32 位整数 | reg = <0x10000000 0x1000>; | 尖括号内为 cell 数组 |
| 字节数组 | mac = [00 11 22 33 44 55]; | 方括号内为十六进制字节 |
| 混合值 | data = "str", <1 2>, [aa]; | 逗号分隔多种类型 |
| 引用 | clocks = <&clk_uart>; | 使用 & 引用标签 |
字符串属性使用双引号,多个字符串用逗号分隔,例如 compatible = "fsl,imx6ull-uart", "fsl,imx6ul-uart";。整数属性使用尖括号,每个数占一个 32 位 cell。字节数组使用方括号,每个字节两位十六进制。混合属性则按顺序拼接,内核解析时按属性定义的类型逐段读取。
"vendor,device",这是 compatible 属性的通用约定,不是 DTS 语法分隔符。
四、标签与引用:让设备树可拼接、可复用
标签(label)写在节点名之前,以冒号结尾,例如 uart1: serial@021e8000 { ... };。标签的作用是给节点起一个短名字,便于在其他位置通过 &label 引用该节点。引用可以出现在属性值中,也可以用于在根节点外追加或覆盖节点内容。
/* 定义带标签的节点 */
uart1: serial@021e8000 {
compatible = "fsl,imx6ul-uart";
status = "disabled";
};
/* 在别处引用并覆盖属性 */
&uart1 {
status = "okay";
pinctrl-0 = <&pinctrl_uart1>;
};
这种“定义 + 引用覆盖”的机制是设备树支持板级差异化的关键。SoC 级 DTSI 定义完整节点并默认关闭,板级 DTS 通过 &label 打开并补充引脚、时钟等资源,从而实现同一 SoC 适配多块板卡。
五、简单示例与编译验证
下面是一个最小可编译的 DTS 片段,包含根节点、chosen、memory、soc 以及一个带标签的 UART 节点:
/dts-v1/;
/ {
model = "Demo Board";
compatible = "demo,board";
chosen {
bootargs = "console=ttyS0,115200";
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>;
};
soc {
uart0: serial@10000000 {
compatible = "demo,uart";
reg = <0x10000000 0x1000>;
interrupts = <5>;
status = "okay";
};
};
};
&uart0 {
clocks = <&clk_uart0>;
};
编译与反编译验证命令如下:
# 编译 DTS 为 DTB
dtc -I dts -O dtb -o demo.dtb demo.dts
# 反编译 DTB 为 DTS,检查语法与属性
dtc -I dtb -O dts -o demo_check.dts demo.dtb
若编译报错,常见原因包括:缺少分号、括号不匹配、单元地址重复、引用标签未定义。使用 dtc 的 -W 选项可开启更多警告,帮助定位问题。
六、误区与复盘
- 把节点名写成大写或包含下划线,虽然部分工具能解析,但不符合约定,易导致匹配失败。
- 在整数属性中混入字符串而不加引号,编译会直接报错。
- 认为
@unit-address必须与reg完全一致;实际上单元地址只需在同级唯一,但强烈建议与 reg 首地址保持一致以便阅读。 - 在板级 DTS 中重复定义整个节点,而不是用
&label覆盖,导致 SoC 级配置丢失。
复盘要点:先确认节点路径唯一,再检查属性类型与书写符号是否匹配,最后用 dtc 反编译核对最终 DTB 中的实际内容。掌握这些规则后,后续学习中断、时钟、GPIO、pinctrl 等复杂属性时,就能把注意力放在语义而非语法上。