3. Overlay 语法详解:核心语法元素

设备树 Overlay 的语法基于标准 DTS(Device Tree Source)格式,但引入了若干关键语法元素,用于描述“如何将一段设备树片段动态地附加到基础设备树(Base Device Tree)上”。理解 /plugin/、target、__overlay__ 和 fragment 这四个核心元素,是掌握 Overlay 编写的基础。

3.1 语法元素总览

下表列出了 Overlay 语法中的四个核心元素及其作用:

语法元素 作用 使用位置
/plugin/ 声明该 DTS 文件是一个 Overlay 插件,而非独立的完整设备树 文件头部
target 指定 Overlay 要附加到基础设备树中的哪个节点(通过路径或标签) 每个 fragment 内部
__overlay__ 包含要附加或修改的设备树内容(属性、子节点) 每个 fragment 内部,紧跟在 target 之后
fragment 将 target 和 __overlay__ 封装为一个独立的操作单元 Overlay 根节点下的子节点

3.2 /plugin/ —— Overlay 文件标识

/plugin/ 是一个特殊的编译器指令,必须放在 Overlay 源文件的第一行。它告诉 DTC(Device Tree Compiler)该文件是一个插件,需要以 Overlay 模式编译,而不是编译为完整的设备树二进制(.dtb)。

关键点: 没有 /plugin/ 的 DTS 文件会被视为独立设备树,编译时不会生成 Overlay 特有的符号表和 fragment 结构。
// 正确的 Overlay 文件开头
/dts-v1/;
/plugin/;

/ {
    fragment@0 {
        target = &i2c1;
        __overlay__ {
            status = "okay";
        };
    };
};

3.3 target —— 指定附加目标

target 属性用于指定 Overlay 要操作的基础设备树节点。它有两种常用写法:

  • 标签引用(推荐): 使用 &label 语法,如 target = &i2c1;。这种方式可读性好,且编译时会自动解析为正确的路径。
  • 绝对路径: 使用字符串路径,如 target = &{/soc/i2c@7e804000};。适用于没有标签的节点。
最佳实践: 优先使用标签引用。在基础设备树中为关键节点(如 I2C、SPI、GPIO 控制器)添加标签,可以大幅提高 Overlay 的可维护性。

3.4 __overlay__ —— 定义附加内容

__overlay__ 节点内部包含要附加到目标节点的具体内容。这些内容可以是:

  • 新增属性: 如 status = "okay";、compatible = "my-device";
  • 修改属性: 覆盖目标节点中已有的属性值
  • 新增子节点: 在目标节点下添加新的设备节点
  • 删除节点/属性: 使用 /delete-node/ 或 /delete-property/ 指令
fragment@1 {
    target = &spi0;
    __overlay__ {
        #address-cells = <1>;
        #size-cells = <0>;
        
        sensor@0 {
            compatible = "bosch,bme280";
            reg = <0>;
            spi-max-frequency = <10000000>;
        };
    };
};

3.5 fragment —— 操作单元封装

fragment 节点将 target 和 __overlay__ 组合成一个完整的操作单元。每个 fragment 对应一次独立的设备树修改操作。fragment 的命名规则为 fragment@N,其中 N 从 0 开始递增。

注意: fragment 的编号顺序不影响加载顺序。加载顺序由 Overlay 的加载顺序决定,而非 fragment 编号。但保持编号连续和有序是良好的编码习惯。

3.6 完整语法结构图

下图展示了 Overlay 文件的完整语法结构及各元素之间的关系:

Overlay 语法结构图 /plugin/ 声明(文件头部) / { ... } 根节点 fragment@0 { ... } —— 第一个操作单元 target = &i2c1; —— 指定目标 __overlay__ { ... } —— 附加内容 status = "okay"; / 新子节点 { } 一个 Overlay 可包含多个 fragment,每个 fragment 独立操作一个目标节点 fragment 编号(@0, @1, ...)仅用于区分,不影响加载顺序

3.7 多 fragment 示例

实际工程中,一个 Overlay 文件常包含多个 fragment,分别操作不同的目标节点。以下示例同时使能 I2C1 控制器并在 SPI0 总线上添加一个传感器:

/dts-v1/;
/plugin/;

/ {
    fragment@0 {
        target = &i2c1;
        __overlay__ {
            status = "okay";
            clock-frequency = <400000>;
        };
    };

    fragment@1 {
        target = &spi0;
        __overlay__ {
            #address-cells = <1>;
            #size-cells = <0>;
            
            temperature_sensor: sensor@0 {
                compatible = "maxim,max31865";
                reg = <0>;
                spi-max-frequency = <5000000>;
                status = "okay";
            };
        };
    };
};

3.8 常见错误与避坑

  • 遗漏 /plugin/: 编译时不会报错,但生成的 .dtbo 文件无法被内核正确识别为 Overlay。
  • target 路径错误: 如果 target 指向的节点在基础设备树中不存在,加载 Overlay 时会失败。
  • __overlay__ 拼写错误: 注意是双下划线 + overlay + 双下划线,容易误写为 _overlay_ 或 __overlay。
  • fragment 编号重复: 虽然不影响加载,但会导致编译警告,且不利于调试。
调试技巧: 使用 dtc -@ -I dts -O dtb -o output.dtbo input.dts 编译 Overlay,-@ 选项确保生成符号表。加载失败时,通过 dmesg | grep overlay 查看内核日志定位问题。