4. Overlay语法详解

设备树 Overlay 的本质,是“把一段增量 DTS 片段在运行时合并到已加载的基设备树上”。它之所以能精确落到某个节点,靠的不是路径字符串的模糊匹配,而是一套由 fragment、target、target-path、__overlay__、__symbols__、__fixups__ 共同构成的“定位—挂载—重定位”机制。理解这些关键字的语义,是写出可编译、可加载、可维护 Overlay 的前提。

一、核心原理:fragment 是容器,target 是锚点

一个 Overlay 源文件(.dts)在结构上由若干 fragment 子节点组成,每个 fragment 代表一次独立的“修改动作”。fragment 内部必须包含两个关键信息:改哪里(target 或 target-path)和改什么(__overlay__)。

/dts-v1/;
/plugin/;

/ {
    fragment@0 {
        target = <&uart0>;
        __overlay__ {
            status = "okay";
            pinctrl-0 = <&uart0_pins>;
        };
    };

    fragment@1 {
        target-path = "/soc/i2c@40012000";
        __overlay__ {
            clock-frequency = <400000>;
        };
    };
};

其中 /plugin/ 是 Overlay 的编译标记,告诉 dtc 这是一个插件而非完整设备树。fragment 的编号(@0、@1)只是节点名,不参与语义,但建议保持连续以便阅读。

二、target 与 target-path:两种定位方式

target 使用标签引用(phandle 形式),例如 target = <&uart0>;。它依赖基设备树在编译时导出的标签,最终通过 __symbols__ 解析为绝对路径。优点是路径变化时 Overlay 无需修改,可移植性强。

target-path 使用绝对路径字符串,例如 target-path = "/soc/i2c@40012000";。它不依赖标签,但一旦基设备树节点路径或地址发生变化,Overlay 就会失效。两者对比如下:

对比项 target(标签引用) target-path(绝对路径)
书写形式 target = <&uart0>; target-path = "/soc/uart@...";
依赖条件 基 DTB 含 __symbols__ 路径必须完全一致
可移植性 高,路径变化不影响 低,路径变化即失效
适用场景 常规板级扩展、量产维护 调试、临时验证、无标签场景
优先使用 target。只有当基设备树未导出所需标签,或需要临时定位一个没有标签的节点时,才使用 target-path。

三、__overlay__:真正被合并的内容

__overlay__ 是 fragment 的“载荷节点”。它下面的所有属性与子节点,会被合并到 target 指向的节点中。合并规则遵循设备树通用语义:

  • 同名属性:Overlay 中的值覆盖基树中的值;
  • 新属性:直接追加;
  • 同名子节点:递归合并;
  • 新子节点:追加到目标节点下。

因此 __overlay__ 不是普通节点名,而是 dtc 与内核 Overlay 解析器约定的特殊标记。若把内容直接写在 fragment 下而不放入 __overlay__,编译或加载会失败。

四、__symbols__ 与 __fixups__:标签如何变成路径

基设备树编译时若使用 dtc -@,会生成 __symbols__ 节点,记录“标签 → 绝对路径”的映射。Overlay 编译时,dtc 会把 target = <&uart0> 这类引用转换为 __fixups__ 节点中的占位记录,例如:

__fixups__ {
    uart0 = "/fragment@0:target:0";
};

加载 Overlay 时,内核先读取 __fixups__,再查基树的 __symbols__,把标签解析为真实 phandle 或路径,最后完成 fragment 的合并。这条链路可以概括为:

Overlay 标签解析与合并链路 Overlay .dtbo fragment + __fixups__ 基设备树 DTB __symbols__ 标签表 解析 fixup 标签 → 路径/phandle 合并 __overlay__ 写入目标节点 关键约束:基 DTB 必须用 dtc -@ 编译以保留 __symbols__; Overlay 必须用 /plugin/ 标记;target 与 target-path 二选一。

五、典型场景与实操片段

以启用一块扩展板上的 SPI 屏为例,Overlay 需要同时修改 SPI 控制器状态、添加屏幕子节点、并引用 GPIO 标签:

/dts-v1/;
/plugin/;

/ {
    fragment@0 {
        target = <&spi1>;
        __overlay__ {
            status = "okay";
            #address-cells = <1>;
            #size-cells = <0>;

            panel@0 {
                compatible = "vendor,spi-panel";
                reg = <0>;
                reset-gpios = <&gpio1 12 0>;
                dc-gpios = <&gpio1 13 0>;
            };
        };
    };
};

编译与加载命令如下:

dtc -@ -I dts -O dtb -o panel.dtbo panel-overlay.dts
mkdir -p /sys/kernel/config/device-tree/overlays/panel
cat panel.dtbo > /sys/kernel/config/device-tree/overlays/panel/dtbo
cat /sys/kernel/config/device-tree/overlays/panel/status

若加载失败,dmesg 通常会给出 OF: overlay: failed to apply 或 find target node 相关提示,可据此判断是标签缺失还是路径错误。

六、常见误区与复盘

误区一:基 DTB 未加 -@,导致没有 __symbols__,所有 target = <&xxx> 全部解析失败。

误区二:把属性直接写在 fragment 下,忘记包一层 __overlay__,编译虽过但加载无效。

误区三:同时写 target 和 target-path,语义冲突,应二选一。

误区四:用 target-path 时路径写错一个字符,节点定位失败,且错误信息不如标签方式直观。

复盘建议:先确认基 DTB 是否带 __symbols__,再检查 Overlay 是否带 /plugin/,最后核对 fragment 内是否严格使用 __overlay__。这三步能覆盖绝大多数 Overlay 语法类故障。

一句话总结:fragment 定义“改哪一次”,target/target-path 定义“改哪个节点”,__overlay__ 定义“改成什么”,__symbols__ 与 __fixups__ 负责把标签翻译成真实路径。四者配合,Overlay 才能在运行时安全、精确地合并进基设备树。