4. DTS 标签与引用:标签(label)的定义、&引用机制、节点复用与覆盖技术
在设备树源文件(DTS)中,标签(Label)与引用(Reference)机制是构建模块化、可复用设备树的核心语法特性。通过标签,开发者可以为任意节点定义别名;通过 & 引用,可以在不破坏原始树结构的前提下,对节点进行属性追加、覆盖或状态修改。本章将深入剖析标签定义、引用语法、节点复用与覆盖技术的完整流程。
核心概念速览: 标签是节点的“别名”,
& 是引用操作符,覆盖(Override)是编译期的节点合并行为。三者结合实现了设备树的“一次定义,多处修改”的工程化能力。
4.1 标签(Label)的定义与作用域
标签是附加在设备树节点上的标识符,用于在 DTS 文件内部或跨文件引用该节点。标签定义在节点名称之前,使用冒号 : 与节点名分隔。
4.1.1 基本语法
// 标签定义语法
[标签名:] 节点名[@单元地址] {
属性列表;
};
示例:
/dts-v1/;
/ {
model = "Example Board";
compatible = "example,board";
soc {
#address-cells = <1>;
#size-cells = <1>;
uart0: serial@101f0000 {
compatible = "ns16550";
reg = <0x101f0000 0x1000>;
interrupts = <0 5 4>;
status = "okay";
};
gpio0: gpio@101e0000 {
compatible = "brcm,bcm2835-gpio";
reg = <0x101e0000 0x100>;
gpio-controller;
#gpio-cells = <2>;
};
};
};
上述代码中,uart0 和 gpio0 分别是 serial@101f0000 和 gpio@101e0000 节点的标签。
4.1.2 标签的作用域规则
| 规则项 | 说明 |
|---|---|
| 文件内唯一性 | 同一 DTS 文件(含 #include 展开后)中,标签名必须唯一,不可重复定义。 |
| 跨文件可见性 | 通过 #include 或 /include/ 包含的文件中定义的标签,在包含文件中可见。 |
| 标签与节点路径 | 标签是节点路径的别名,编译后标签信息会保留在 DTB 中,供后续引用。 |
| 标签命名规范 | 建议使用小写字母、数字和下划线,以字母开头,避免与关键字冲突。 |
最佳实践: 为所有需要被外部引用的节点(如外设控制器、时钟、GPIO、中断控制器)定义有意义的标签名,避免在多个文件中使用绝对路径引用,提高代码可读性和可维护性。
4.2 &引用机制:通过标签访问节点
& 符号是设备树中的引用操作符,用于通过标签名访问已定义的节点。引用可以在任何需要节点路径的位置使用,包括属性赋值、节点覆盖等场景。
4.2.1 引用语法
&标签名 {
属性列表;
};
引用块会被编译器解析为对原节点的追加操作,而非创建新节点。
4.2.2 引用应用场景
- 属性追加: 为已有节点添加新属性。
- 属性覆盖: 修改已有属性的值。
- 子节点添加: 在已有节点下增加子节点。
- 状态修改: 修改
status属性启用或禁用设备。
示例:
// 引用 uart0 节点,追加 pinmux 属性
&uart0 {
pinctrl-0 = <&uart0_pins>;
pinctrl-names = "default";
};
// 引用 gpio0 节点,修改状态为禁用
&gpio0 {
status = "disabled";
};
4.2.3 引用与路径的等价性
| 引用方式 | 示例 | 说明 |
|---|---|---|
| 标签引用 | &uart0 |
通过标签名直接引用,简洁高效。 |
| 绝对路径引用 | &{/soc/serial@101f0000} |
使用花括号包裹完整路径,适用于无标签的节点。 |
| 相对路径引用 | 不支持 | 设备树引用必须使用标签或绝对路径,不支持相对路径。 |
注意: 引用操作符
& 后必须紧跟标签名或花括号路径,中间不能有空格。错误的写法如 & uart0 会导致编译错误。
4.3 节点复用与覆盖技术
节点复用与覆盖是设备树实现模块化设计的核心技术。通过 & 引用,可以在多个文件中复用同一节点定义,并根据具体硬件配置进行差异化覆盖。
4.3.1 覆盖(Override)的编译行为
当 DTC 编译器遇到 &标签名 { ... }; 结构时,执行以下合并规则:
- 属性合并: 新属性追加到原节点;同名属性以覆盖块中的值为准。
- 子节点合并: 新子节点追加到原节点;同名子节点递归合并。
- 数组与字符串覆盖: 标量属性直接替换;数组属性整体替换,不支持部分修改。
- 删除操作: 使用
/delete-property/或/delete-node/指令可删除属性或子节点。
示例:
// 基础定义文件:base.dtsi
/ {
soc {
uart0: serial@101f0000 {
compatible = "ns16550";
reg = <0x101f0000 0x1000>;
interrupts = <0 5 4>;
clock-frequency = <24000000>;
status = "disabled";
};
};
};
// 板级覆盖文件:board.dts
#include "base.dtsi"
&uart0 {
clock-frequency = <48000000>; // 覆盖时钟频率
status = "okay"; // 覆盖状态为启用
pinctrl-0 = <&uart0_pins>; // 追加属性
};
编译后,uart0 节点的最终属性为:
serial@101f0000 {
compatible = "ns16550";
reg = <0x101f0000 0x1000>;
interrupts = <0 5 4>;
clock-frequency = <48000000>; // 被覆盖
status = "okay"; // 被覆盖
pinctrl-0 = <&uart0_pins>; // 新增
};
4.3.2 节点复用模式
| 复用模式 | 描述 | 适用场景 |
|---|---|---|
| 基础定义 + 板级覆盖 | SoC 级 DTSI 定义外设基础属性,板级 DTS 通过引用覆盖差异化配置。 | 多款板卡共享同一 SoC,外设配置略有不同。 |
| 分层覆盖链 | 多级 DTSI 文件逐层覆盖,形成“SoC → 核心板 → 底板”的覆盖链。 | 复杂硬件平台,支持多种核心板与底板组合。 |
| 条件编译 + 覆盖 | 结合 C 预处理器宏,根据宏定义选择性包含覆盖块。 | 同一 DTS 文件支持多种硬件变体。 |
4.3.3 覆盖优先级
当多个覆盖块引用同一节点时,编译器按以下优先级处理:
- 最后出现的覆盖块优先级最高(后覆盖优先)。
- 同一文件内,靠后的
&引用覆盖靠前的。 - 跨文件时,
#include顺序决定覆盖顺序:后包含的文件中的覆盖优先级更高。
工程经验: 在多层覆盖架构中,建议将最通用的配置放在最早包含的文件中,将最具体的板级配置放在最后包含的文件中,确保板级配置具有最高优先级。
4.4 标签与引用流程 SVG 图
下图展示了从标签定义、引用覆盖到编译合并的完整数据流:
4.5 高级覆盖技巧与避坑指南
4.5.1 删除属性与节点
在覆盖块中,可以使用特殊指令删除已存在的属性或子节点:
// 删除属性
&uart0 {
/delete-property/ clock-frequency;
};
// 删除子节点
&soc {
/delete-node/ &gpio0;
};
4.5.2 覆盖中的数组操作
设备树数组属性(如 reg、interrupts)在覆盖时是整体替换,不支持部分修改。若需要修改数组中的某个元素,必须重新定义整个数组:
// 错误:无法部分修改数组
&uart0 {
reg = <0x101f0000 0x2000>; // 整体替换,第二个元素从 0x1000 改为 0x2000
};
4.5.3 常见错误与解决方案
| 错误类型 | 错误示例 | 解决方案 |
|---|---|---|
| 标签未定义 | &undefined_label { ... }; |
确保标签在引用前已定义,或使用绝对路径引用。 |
| 标签重复定义 | 两个文件定义了同名标签 | 使用唯一命名前缀,或通过 /delete-node/ 移除冲突节点。 |
| 覆盖顺序错误 | 板级覆盖被基础覆盖覆盖 | 调整 #include 顺序,确保板级文件最后包含。 |
| 数组部分修改 | 试图只修改数组中的一个元素 | 整体重新定义数组属性。 |
避坑提示: 在多层覆盖架构中,务必使用
dtc -I dts -O dts 反编译生成的 DTB 文件,检查最终合并结果是否符合预期。反编译命令:dtc -I dtb -O dts -o output.dts input.dtb
4.6 本章小结
- 标签(Label) 是节点的别名,定义在节点名前,使用冒号分隔,作用域为整个编译单元。
- &引用机制 通过标签名访问节点,支持属性追加、覆盖、子节点添加等操作。
- 节点复用与覆盖 实现了设备树的模块化设计,通过分层覆盖链支持复杂硬件平台。
- 覆盖优先级 遵循“后出现优先”原则,数组属性必须整体替换。
- 删除操作 使用
/delete-property/和/delete-node/指令。
课后练习: 编写一个包含两个 DTSI 文件和一个 DTS 文件的设备树示例,实现“SoC 基础定义 → 核心板覆盖 → 底板覆盖”的三层覆盖架构,并使用
dtc 编译后反编译验证覆盖结果。