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()函数会做三件事:
- 解析overlay的target引用,找到基础设备树中要修改的节点
- 合并节点属性,新增的属性直接添加,同名的属性会被覆盖
- 触发设备热插拔,让内核重新扫描新增的硬件节点,加载对应的驱动
我画了一张流程图,帮你理清这个过程:
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可以包含多个fragmenttarget = &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%的问题。
设备树覆盖这个机制,说白了就是给嵌入式系统增加了一层“动态适配”的能力。在版本升级的场景下,它让你不用动基础设备树,就能灵活应对硬件变化。我个人觉得,这是设备树体系里最实用的设计之一。