4、设备树增量更新:使用dt-overlay实现运行时设备树更新

设备树覆盖(Device Tree Overlay),说白了就是给系统内核“打补丁”。

我刚开始接触这个概念时,也觉得有点绕。你想想看,设备树不是编译好就固定了吗?怎么还能运行时更新?

嗯,这里的关键在于:设备树覆盖允许你在不重新编译整个设备树、不重启系统的情况下,动态添加或修改硬件节点。这对于那些需要热插拔外设、FPGA动态重配置、或者产品迭代中硬件小改的场景,简直是救命稻草。

4.1 为什么需要设备树覆盖?

我在一个工业控制项目里遇到过这样的问题:产品已经量产了,但客户要求增加一个外接的温湿度传感器。按照传统做法,我得修改整个设备树源文件,重新编译,再刷写固件——这中间涉及版本管理、固件升级、设备重启,麻烦得很。

有了设备树覆盖,事情就简单了。我只需要写一个很小的overlay文件,描述新增的传感器节点,然后在系统运行时动态加载它。整个过程不需要重启,不影响系统其他功能。

设备树覆盖的典型应用场景包括:

  • FPGA动态重配置:FPGA加载不同IP核时,对应的设备树节点需要动态变化
  • 热插拔外设:像PCIe、USB设备,插入时动态添加节点
  • 产品变体管理:同一款主控板,不同型号的外设差异用overlay来管理
  • 调试与开发:快速验证新硬件,不用反复烧写固件

核心思想:设备树覆盖 = 基础设备树 + 增量补丁。基础设备树描述板上固定的硬件,overlay描述可变或新增的部分。

4.2 设备树覆盖的工作原理

设备树覆盖的底层机制,其实和内核里的“符号链接”有点像。每个overlay文件都包含一个__symbols__节点,用来标记它要“挂载”到基础设备树的哪个位置。

加载overlay时,内核的of_overlay_apply()函数会做三件事:

  1. 解析overlay的target引用,找到基础设备树中要修改的节点
  2. 合并节点属性,新增的属性直接添加,同名的属性会被覆盖
  3. 触发设备热插拔,让内核重新扫描新增的硬件节点,加载对应的驱动

我画了一张流程图,帮你理清这个过程:

设备树覆盖加载流程 基础设备树(.dtb) Overlay文件(.dtbo) 加载命令 内核 of_overlay_apply() 解析 步骤1:解析target引用 找到要修改的节点 步骤2:合并节点属性 新增/覆盖属性 步骤3:触发热插拔 加载对应驱动 更新后的设备树生效,硬件正常工作

4.3 编写设备树覆盖文件

写overlay文件,和写普通设备树源文件很像,但有几个关键区别。我直接拿一个实际例子来说:

// my_sensor_overlay.dts
/dts-v1/;
/plugin/;

/ {
    fragment@0 {
        target = &i2c1;
        __overlay__ {
            #address-cells = <1>;
            #size-cells = <0>;

            temp_sensor: tmp102@48 {
                compatible = "ti,tmp102";
                reg = <0x48>;
                interrupt-parent = <&gpio1>;
                interrupts = <15 IRQ_TYPE_EDGE_FALLING>;
            };
        };
    };
};

注意几个要点:

  • /plugin/; 是overlay文件的标志,告诉编译器这是个增量补丁
  • fragment@0 是片段编号,一个overlay可以包含多个fragment
  • target = &i2c1; 指定要挂载到基础设备树的哪个节点
  • __overlay__ 里面写要新增或修改的内容

个人习惯:我一般会在overlay文件里加上#address-cells和#size-cells,虽然基础设备树里可能已经定义了。这样做的好处是,overlay独立编译时不会报错,而且语义更清晰。

4.4 编译与加载设备树覆盖

编译overlay文件,和编译普通设备树类似,但输出后缀是.dtbo:

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

# 加载overlay(方法一:通过configfs)
mkdir /sys/kernel/config/device-tree/overlays/my_sensor
cat my_sensor_overlay.dtbo > /sys/kernel/config/device-tree/overlays/my_sensor/dtbo

# 加载overlay(方法二:通过设备树二进制接口)
echo my_sensor_overlay.dtbo > /sys/devices/platform/bone_capemgr/slots

这里有个坑,我曾经踩过:编译时一定要加-@参数。这个参数的作用是保留符号信息,让overlay能正确找到基础设备树里的节点。不加这个参数,加载时就会报“symbols not found”的错误。

我曾经遇到过:在某个项目中,overlay加载成功了,但设备就是不工作。查了半天,发现是overlay里的reg地址和实际硬件地址对不上。后来我养成了一个习惯:加载overlay后,先到/sys/firmware/devicetree/base/下面检查节点是否真的创建成功,属性值是否正确。

4.5 设备树覆盖的版本管理策略

在产品迭代中,overlay文件的管理其实比基础设备树更复杂。我总结了一套经验:

场景 基础设备树 Overlay文件
硬件大改(换主控) 重新编译 全部废弃
增加外设模块 不变 新增overlay文件
外设参数调整 不变 修改对应overlay
外设移除 不变 删除或禁用overlay

我个人建议,把overlay文件也纳入版本管理,并且和基础设备树分开维护。每个overlay文件只描述一个独立的功能模块,命名要清晰,比如spi1_ads1256.dts、i2c2_tmp102.dts。这样,产品升级时,只需要替换对应的overlay文件,基础设备树可以保持不变。

4.6 调试与排错

设备树覆盖的调试,说难不难,说简单也不简单。我常用的几个方法:

  • 查看内核日志:dmesg | grep overlay,看有没有加载错误
  • 检查设备树节点:ls /sys/firmware/devicetree/base/,确认节点是否创建
  • 验证属性值:cat /sys/firmware/devicetree/base/.../reg,用hexdump看原始数据
  • 卸载overlay:rmdir /sys/kernel/config/device-tree/overlays/my_sensor,看能否正常卸载

避坑指南:overlay加载失败最常见的原因有三个——target引用错误、符号信息缺失、属性格式不匹配。遇到问题,先检查这三项,能解决90%的问题。

设备树覆盖这个机制,说白了就是给嵌入式系统增加了一层“动态适配”的能力。在版本升级的场景下,它让你不用动基础设备树,就能灵活应对硬件变化。我个人觉得,这是设备树体系里最实用的设计之一。