4. 设备树覆盖(Overlay):概念、语法、编译与动态加载

设备树覆盖(Device Tree Overlay,简称 DTO)是嵌入式 Linux 中实现硬件功能动态组合与热插拔的关键机制。它允许在不重新编译主设备树(Base DTB)的前提下,通过加载一个或多个叠加片段(Overlay DTB)来修改、添加或禁用设备节点。这一特性在 FPGA 动态重配置、扩展板热插拔、模块化 SoC 设计中尤为重要。

核心价值: 将硬件描述与硬件实例化分离。主 DTB 描述 SoC 固定资源,Overlay DTB 描述可插拔或可重配置外设,两者在运行时通过内核的 configfs 接口合并。

4.1 设备树覆盖的概念与工作原理

设备树覆盖的本质是“片段(Fragment)”的合并。每个 Overlay 由一个或多个 fragment@N 节点组成,每个片段包含一个 target(指向主 DTB 中的目标节点)和一个 __overlay__ 子节点(包含要合并的属性或子节点)。内核在加载 Overlay 时,解析这些片段,将 __overlay__ 中的内容合并到 target 指定的节点下。

设备树覆盖合并原理 主 DTB (Base) /soc/i2c@7e804000 /soc/spi@7e204000 /chosen Overlay DTB fragment@0 target = &i2c0 __overlay__ { temp@48 } 运行时合并结果 (Live Tree) /soc/i2c@7e804000/temperature@48 /soc/spi@7e204000 (不变) 动态加载/卸载

4.2 Overlay 语法与 DTS 编写规范

Overlay 的 DTS 文件使用 /dts-v1/; 和 /plugin/; 作为文件头,以标识这是一个可叠加的片段。核心语法要素如下:

语法元素说明示例
/plugin/;声明文件为 Overlay 插件/dts-v1/; /plugin/;
fragment@N每个叠加片段,N 从 0 开始fragment@0 { ... };
target指定合并目标(使用引用标签)target = &i2c0;
__overlay__包含要添加/修改的属性或子节点__overlay__ { status = "okay"; };
target-path使用字符串路径替代引用标签target-path = "/soc/i2c@7e804000";
__fixups__自动生成的符号解析表(编译时)由 dtc 编译器自动填充
编写要点: Overlay 中引用的标签(如 &i2c0)必须在主 DTB 中已定义。编译器通过 -@ 选项生成 __symbols__ 节点,使 Overlay 能够解析这些外部引用。

4.2.1 完整 Overlay DTS 示例

// temperature-sensor.dts
/dts-v1/;
/plugin/;

/ {
    fragment@0 {
        target = &i2c0;
        __overlay__ {
            #address-cells = <1>;
            #size-cells = <0>;
            temperature@48 {
                compatible = "ti,tmp102";
                reg = <0x48>;
                status = "okay";
            };
        };
    };

    fragment@1 {
        target = &gpio;
        __overlay__ {
            alert_pin {
                gpios = <&gpio 17 0>;
                interrupt-parent = <&gpio>;
                interrupts = <17 2>;
            };
        };
    };
};

4.3 编译方法:从 DTS 到 DTO

设备树覆盖的编译使用与主 DTB 相同的 dtc 工具,但需要额外指定 -@ 选项以生成符号表。编译流程如下:

  1. 编写 Overlay DTS:遵循 /plugin/ 和 fragment 语法。
  2. 编译为 DTO:dtc -@ -I dts -O dtb -o temperature-sensor.dtbo temperature-sensor.dts
  3. 验证输出:使用 fdtdump temperature-sensor.dtbo 查看结构。
  4. 反编译检查:dtc -I dtb -O dts temperature-sensor.dtbo 确认片段正确。
编译选项作用是否必须
-@生成 __symbols__ 节点,支持外部引用解析是
-I dts指定输入格式为 DTS 源码是
-O dtb指定输出格式为 DTB 二进制是
-o指定输出文件名(通常使用 .dtbo 后缀)是
-@ -@部分旧版本需要双 -@ 强制生成符号视版本而定

4.4 动态加载与卸载

Linux 内核从 4.4 版本开始,通过 configfs 提供标准的 Overlay 动态管理接口。加载路径为 /sys/kernel/config/device-tree/overlays。操作步骤如下:

4.4.1 动态加载流程

  1. 挂载 configfs(如未自动挂载):mount -t configfs configfs /sys/kernel/config
  2. 创建 Overlay 目录:mkdir /sys/kernel/config/device-tree/overlays/my-overlay
  3. 写入 DTB 数据:cat temperature-sensor.dtbo > /sys/kernel/config/device-tree/overlays/my-overlay/dtbo
  4. 检查状态:cat /sys/kernel/config/device-tree/overlays/my-overlay/status 应返回 applied。
  5. 验证设备:ls /sys/bus/i2c/devices/ 或 dmesg | tail 查看新设备枚举。

4.4.2 动态卸载流程

  1. 移除 Overlay 目录:rmdir /sys/kernel/config/device-tree/overlays/my-overlay
  2. 内核自动处理:内核会撤销合并,移除对应设备节点,并触发驱动移除回调。
  3. 验证卸载:ls /sys/bus/i2c/devices/ 确认设备已消失。
注意事项:
  • 卸载 Overlay 时,如果对应设备正在被使用(如文件打开、中断处理中),卸载可能失败或导致内核警告。建议先停用相关服务。
  • 多次加载同一 Overlay 需要不同的目录名,否则会冲突。
  • Overlay 的加载顺序可能影响资源分配,依赖关系需在设计中考虑。

4.5 调试与故障排查

当 Overlay 加载失败时,可以从以下角度排查:

现象可能原因排查方法
加载后 status 为 unappliedtarget 引用无效或符号解析失败检查主 DTB 是否包含 __symbols__;使用 dtc -@ 重新编译主 DTB
内核报 duplicate labelOverlay 中定义了与主 DTB 重复的标签避免在 Overlay 中定义全局标签,使用局部引用
设备节点未出现兼容性字符串不匹配或驱动未加载检查 compatible 值;确认驱动已编译进内核
卸载时内核崩溃设备有未释放的资源或引用计数不为零使用 lsof 检查占用;确保驱动实现了正确的 remove 回调
编译报 label not foundOverlay 引用了主 DTB 中不存在的标签检查主 DTS 中是否定义了该标签;确认编译时使用了正确的 -@ 选项

4.6 实践案例:Raspberry Pi 扩展板 Overlay

以 Raspberry Pi 上加载一个 I2C 温度传感器为例,展示完整操作链:

# 1. 编写 Overlay DTS (tmp102-overlay.dts)
/dts-v1/;
/plugin/;

/ {
    compatible = "brcm,bcm2835";

    fragment@0 {
        target = &i2c1;
        __overlay__ {
            #address-cells = <1>;
            #size-cells = <0>;
            status = "okay";
            tmp102@48 {
                compatible = "ti,tmp102";
                reg = <0x48>;
            };
        };
    };
};

# 2. 编译
dtc -@ -I dts -O dtb -o tmp102.dtbo tmp102-overlay.dts

# 3. 加载
sudo mkdir -p /sys/kernel/config/device-tree/overlays/tmp102
sudo cat tmp102.dtbo > /sys/kernel/config/device-tree/overlays/tmp102/dtbo

# 4. 验证
cat /sys/kernel/config/device-tree/overlays/tmp102/status
# 输出: applied
i2cdetect -y 1
# 应看到 0x48 地址

# 5. 卸载
sudo rmdir /sys/kernel/config/device-tree/overlays/tmp102
关键总结: 设备树覆盖通过 /plugin/ + fragment + target + __overlay__ 四要素实现运行时硬件描述扩展。编译时使用 dtc -@ 生成符号表,运行时通过 configfs 接口动态加载/卸载。掌握 Overlay 机制是调试模块化嵌入式系统、FPGA 动态重配置和热插拔外设的必备技能。