2. 设备树基础回顾
在进入动态 Overlay 工程之前,必须先把设备树这套“硬件描述语言”的骨架重新梳理清楚。很多 Overlay 失败、节点找不到、属性覆盖不生效的问题,根源并不在 Overlay 语法本身,而在于对 DTS、DTSI、DTB、DTBO 之间的关系,以及节点、属性、phandle、compatible 的语义理解不到位。本节把这些核心概念串成一条完整的编译与加载链路。
一、源文件层:DTS 与 DTSI 的分工
设备树源文件分为两类:DTS(Device Tree Source)和 DTSI(Device Tree Source Include)。DTS 是面向具体板级的顶层描述文件,通常一个板子对应一个 DTS;DTSI 则是可被复用的公共片段,类似 C 语言的头文件,用于描述 SoC 内部外设、引脚控制器、时钟树等共性内容。
典型组织方式是:SoC 厂商提供 soc.dtsi,板级厂商提供 board.dts,后者通过 #include 引入前者,再在根节点或具体外设节点上做差异化覆盖。这种“公共 + 差异”的分层,是设备树可维护性的关键。
// board.dts
#include "soc.dtsi"
#include "board-pinctrl.dtsi"
/ {
model = "MyBoard V1";
compatible = "vendor,myboard", "vendor,soc";
uart0: serial@10000000 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = &uart0_pins;
};
};
二、节点与属性:设备树的基本语法单元
节点(node)用花括号表示,描述一个设备或一个逻辑分组,节点名通常写成 name@address 形式,其中 address 是寄存器基地址,用于区分同类型多个实例。属性(property)是节点内的键值对,值可以是字符串、32 位整数数组、字节序列或空值。
compatible:驱动匹配的核心依据,格式为"厂商,型号",可写多个,按从具体到通用的顺序排列。reg:描述寄存器地址与长度,常为<base size>形式。status:"okay"表示启用,"disabled"表示禁用,Overlay 常通过它开关设备。#address-cells/#size-cells:定义子节点 reg 属性的地址和长度各占几个 cell。phandle:节点的全局唯一数字标识,供其他节点引用。
三、phandle 与引用:设备树里的“指针”
设备树中经常需要“一个节点引用另一个节点”,例如 pinctrl 引用引脚配置节点、时钟节点引用时钟源。实现方式就是 phandle。在源码中我们写 &label,编译后会被替换成对应的 phandle 数值。
uart0_pins: uart0-pins {
pins = <1 2 3>;
function = "uart";
};
uart0: serial@10000000 {
pinctrl-0 = <&uart0_pins>;
};
理解 phandle 对 Overlay 尤其重要:Overlay 片段通过 target-path 或 target(phandle)定位要修改的节点,如果目标节点的 label 或 phandle 不存在,Overlay 就无法应用。
四、编译流程:从 DTS 到 DTB 与 DTBO
设备树的编译工具是 DTC(Device Tree Compiler)。普通设备树编译为 DTB(Device Tree Blob),Overlay 片段编译为 DTBO(Device Tree Blob Overlay)。两者都是二进制格式,由 Bootloader 或内核在启动阶段解析。
# 编译普通设备树
dtc -I dts -O dtb -o board.dtb board.dts
# 编译 Overlay 片段
dtc -I dts -O dtb -o uart-overlay.dtbo uart-overlay.dts
# 反编译查看内容
dtc -I dtb -O dts -o board.dts board.dtb
在 Linux 内核构建体系中,通常使用 make dtbs 生成 DTB,使用 make dtbo 或 make overlays 生成 DTBO。DTBO 在运行时由内核的 configfs 接口加载,叠加到已展开的 DTB 上。
五、核心概念对照表
| 概念 | 全称/含义 | 作用 | 典型形式 |
|---|---|---|---|
| DTS | Device Tree Source | 板级顶层源文件 | board.dts |
| DTSI | Device Tree Source Include | 可复用公共片段 | soc.dtsi |
| DTB | Device Tree Blob | 编译后的完整设备树 | board.dtb |
| DTBO | Device Tree Blob Overlay | 编译后的增量补丁 | uart-overlay.dtbo |
| node | 节点 | 描述设备或分组 | serial@10000000 |
| property | 属性 | 描述节点参数 | status = "okay" |
| phandle | 节点句柄 | 供其他节点引用 | <&uart0_pins> |
| compatible | 兼容性字符串 | 驱动匹配依据 | "vendor,uart" |
六、案例场景:一个 UART 从 DTS 到 Overlay 的完整链路
假设 SoC 的 soc.dtsi 中已定义 uart0 节点但默认 status = "disabled"。板级 DTS 中将其改为 "okay" 并配置引脚。若产品后期需要在不重新编译整棵 DTB 的前提下启用第二个 UART,就可以编写 Overlay:
/dts-v1/;
/plugin/;
/ {
fragment@0 {
target = <&uart1>;
__overlay__ {
status = "okay";
pinctrl-0 = <&uart1_pins>;
};
};
};
编译成 DTBO 后,通过 configfs 加载,内核会解析 fragment,找到 uart1 的 phandle,把 __overlay__ 内的属性合并进去。整个过程不需要重启,也不需要重新烧写 DTB。
七、常见误区与复盘
误区一:把 DTBO 当成独立 DTB 使用。DTBO 没有根节点完整描述,单独加载会失败,必须指定基础 DTB。
误区二:忽略 phandle 稳定性。Overlay 依赖 label 或 phandle 定位节点,若基础 DTB 中该节点被删除或改名,Overlay 会报 target not found。
误区三:compatible 写错导致驱动不匹配。Overlay 中新增节点的 compatible 必须与驱动 of_match_table 一致,否则设备无法 probe。
误区四:cells 数量不匹配。父节点 #address-cells 与子节点 reg 长度不一致时,dtc 会报错或生成错误地址。
复盘建议:每次编写 Overlay 前,先用 dtc -I dtb -O dts 反编译基础 DTB,确认目标节点的 label、phandle、status 和 compatible 真实存在;Overlay 应用后再用 /proc/device-tree 或 ls /sys/firmware/devicetree/base 验证节点是否生效。把“源文件—编译—加载—验证”四步固定成流程,能规避绝大多数设备树问题。