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 上。

DTB 是“完整硬件描述”,DTBO 是“增量补丁”。DTBO 不能独立启动,必须依附于一个基础 DTB。

五、核心概念对照表

概念全称/含义作用典型形式
DTSDevice Tree Source板级顶层源文件board.dts
DTSIDevice Tree Source Include可复用公共片段soc.dtsi
DTBDevice Tree Blob编译后的完整设备树board.dtb
DTBODevice 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 验证节点是否生效。把“源文件—编译—加载—验证”四步固定成流程,能规避绝大多数设备树问题。

设备树源文件 → 编译 → DTB / DTBO → 运行时叠加 DTS 板级顶层源文件 board.dts DTSI 可复用公共片段 soc.dtsi DTC 设备树编译器 dtc / make dtbs DTB 完整设备树二进制 board.dtb DTBO 增量补丁二进制 uart-overlay.dtbo 运行时叠加(configfs) DTB 已展开 + DTBO 增量合并 → 生效设备树 node / property 节点与属性 phandle 节点引用句柄 compatible 驱动匹配字符串 status / reg 状态与寄存器