2. DTS 基础语法:节点、属性、标签、包含文件与注释
设备树源文件(.dts)采用树形结构描述硬件,其语法简洁、层次分明。掌握节点、属性、标签、包含文件和注释这五大基础元素,是编写和理解 DTS 文件的起点。本章将逐一解析这些语法单元,并通过对比和图示帮助建立直观认知。
2.1 节点(Node)—— 设备树的骨架
节点是设备树的核心结构单元,每个节点代表一个硬件设备、总线或控制器。节点从根节点 / 开始,层层嵌套,形成一棵完整的硬件树。
| 概念 | 说明 | 示例 |
|---|---|---|
| 根节点 | 设备树的起点,用 / 表示 |
/ { ... }; |
| 子节点 | 挂在父节点下的设备或总线 | cpu { ... }; |
| 节点名称 | 格式为 name@unit-address |
uart@fe001000 |
| 节点内容 | 用花括号 { } 包裹,以分号 ; 结束 |
uart@fe001000 { ... }; |
// 一个典型的节点结构
/ {
model = "Example Board v1.0";
compatible = "vendor,example-board";
soc {
#address-cells = <1>;
#size-cells = <1>;
uart@fe001000 {
compatible = "vendor,uart";
reg = <0xfe001000 0x1000>;
interrupts = <33>;
status = "okay";
};
};
};
命名规范:节点名称应尽量使用通用名称(如
uart、i2c、mmc),@unit-address 部分通常填写该设备在总线上的基地址或片选号,用于区分同类型节点。
2.2 属性(Property)—— 描述设备的特征
属性是附着在节点上的键值对,用于描述设备的配置信息,如地址、中断号、时钟频率、驱动兼容性等。属性值支持多种数据类型。
| 数据类型 | 语法格式 | 示例 | 说明 |
|---|---|---|---|
| 字符串 | "string" |
compatible = "vendor,device"; |
用于匹配驱动 |
| 32位整数 | <value> |
reg = <0x1000 0x100>; |
地址、大小、频率等 |
| 64位整数 | <0x00000000 0x00000000> |
reg = <0x0 0x80000000>; |
高32位 + 低32位 |
| 字节数组 | [hex bytes] |
local-mac-address = [00 11 22 33 44 55]; |
MAC 地址、UUID 等 |
| 布尔值 | 仅属性名,无值 | status = "disabled"; 或 status; |
存在即 true |
| 字符串列表 | "str1", "str2" |
compatible = "vendor,dev1", "vendor,dev2"; |
多个兼容驱动 |
// 属性综合示例
ethernet@20000 {
compatible = "vendor,eth", "simple-bus";
reg = <0x20000 0x1000>;
interrupts = <0 35 4>;
local-mac-address = [00 0a 35 01 02 03];
max-speed = <1000>;
status = "okay";
};
2.3 标签(Label)—— 节点的别名引用
标签为节点提供一个全局唯一的别名,便于在其他位置引用该节点。标签通常写在节点名称之前,用冒号分隔。
// 定义标签
uart0: uart@fe001000 {
compatible = "vendor,uart";
reg = <0xfe001000 0x1000>;
};
// 通过 & 引用标签,追加或修改属性
&uart0 {
status = "okay";
clock-frequency = <24000000>;
};
| 语法元素 | 作用 | 示例 |
|---|---|---|
| 标签定义 | 为节点起别名 | i2c1: i2c@f000 |
| 标签引用 | 通过 &label 访问节点 |
&i2c1 { ... }; |
| 引用用途 | 覆盖属性、添加子节点、引用 phandle | &i2c1 { clock = <&clk_24m>; }; |
标签 vs 节点路径:标签是编译期别名,编译后转换为
phandle(指针句柄),比字符串路径更高效。在 .dtsi 文件中定义标签,在 .dts 文件中通过 &label 引用,是设备树复用的核心手段。
2.4 包含文件(#include)—— 模块化与复用
设备树支持 C 预处理器指令 #include,用于引入头文件(.dtsi、.h)或其它 DTS 片段。这是实现 SoC 级设备树复用的关键机制。
// 包含 SoC 公共定义
#include "soc-common.dtsi"
// 包含 GPIO 宏定义头文件
#include <dt-bindings/gpio/gpio.h>
/ {
model = "My Board";
compatible = "vendor,my-board";
// 使用头文件中定义的宏
led-gpios = <&gpio0 5 GPIO_ACTIVE_HIGH>;
};
| 包含方式 | 文件类型 | 典型用途 |
|---|---|---|
#include "file.dtsi" |
设备树包含文件 | SoC 公共节点、时钟、中断控制器定义 |
#include <file.h> |
C 头文件 | 宏定义(GPIO 电平、中断类型、时钟索引) |
#include "board.dts" |
另一个 DTS | 较少用,通常用 /include/ 替代 |
注意:设备树编译器(DTC)默认支持
#include,但需要启用预处理(-E 或通过 cpp 预处理)。部分老旧工具链可能只支持 /include/ 指令,建议统一使用 #include 以兼容现代构建系统。
2.5 注释格式—— 文档化与可读性
DTS 文件支持两种注释风格,与 C 语言完全一致,用于解释节点用途、属性含义或记录修改历史。
// 单行注释:用于简短说明
/*
* 多行注释:用于详细描述
* 例如解释 reg 地址段的含义
*/
/ {
/* 根节点:描述整个板级硬件 */
compatible = "vendor,board";
// 内存节点
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x40000000>; /* 1GB DDR3 */
};
};
注释最佳实践:在
.dtsi 文件中使用注释说明 SoC 公共节点的默认状态;在 .dts 文件中注释说明板级定制修改的原因。避免在属性值内部插入注释,这会导致编译错误。
2.6 语法结构总览
下图展示了 DTS 文件的完整语法结构,从根节点到子节点、属性、标签和引用的关系。
2.7 常见错误与避坑指南
- 缺少分号:每个属性定义和节点结束必须加分号
;,漏掉会导致编译失败。 - 标签重复:同一编译单元内标签必须唯一,重复标签会导致覆盖或编译错误。
- 属性值类型不匹配:如将字符串赋给期望整数的属性,DTC 会报类型错误。
- 路径引用错误:使用
&label时确保标签已定义,使用路径字符串时确保路径正确。 - 包含文件循环依赖:
#include链中避免 A 包含 B、B 又包含 A 的死循环。
调试技巧:使用
dtc -I dts -O dtb -o output.dtb input.dts 编译 DTS 文件,错误信息会精确到行号和列号。使用 dtc -I dtb -O dts -o decompiled.dts input.dtb 反编译 DTB 检查最终生成的设备树结构。