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 编译器遇到 &标签名 { ... }; 结构时,执行以下合并规则:

  1. 属性合并: 新属性追加到原节点;同名属性以覆盖块中的值为准。
  2. 子节点合并: 新子节点追加到原节点;同名子节点递归合并。
  3. 数组与字符串覆盖: 标量属性直接替换;数组属性整体替换,不支持部分修改。
  4. 删除操作: 使用 /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 覆盖优先级

当多个覆盖块引用同一节点时,编译器按以下优先级处理:

  1. 最后出现的覆盖块优先级最高(后覆盖优先)。
  2. 同一文件内,靠后的 & 引用覆盖靠前的。
  3. 跨文件时,#include 顺序决定覆盖顺序:后包含的文件中的覆盖优先级更高。
工程经验: 在多层覆盖架构中,建议将最通用的配置放在最早包含的文件中,将最具体的板级配置放在最后包含的文件中,确保板级配置具有最高优先级。

4.4 标签与引用流程 SVG 图

下图展示了从标签定义、引用覆盖到编译合并的完整数据流:

标签定义与引用覆盖流程 标签定义与引用覆盖流程 阶段一:标签定义 在 DTS/DTSI 文件中 为节点定义标签别名 例: uart0: serial@101f0000 阶段二:引用覆盖 使用 &标签名 引用节点 追加或覆盖属性/子节点 例: &uart0 { status = "okay"; } 阶段三:编译合并 DTC 编译器 合并属性与子节点 生成最终 DTB 详细合并规则 规则一:属性追加 覆盖块中的新属性 直接追加到原节点 规则二:属性覆盖 同名属性以覆盖块为准 标量/数组整体替换 规则三:子节点合并 新子节点追加到原节点 同名子节点递归合并 💡 覆盖优先级:后出现的覆盖块优先级最高,后包含的文件中的覆盖优先级更高 删除操作:使用 /delete-property/ 或 /delete-node/ 指令移除属性或子节点

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 编译后反编译验证覆盖结果。