3. 设备树基础概念

在 Android 内核与设备树平台适配中,设备树(Device Tree)承担着“把硬件描述从内核代码中剥离出来”的关键角色。它让同一份内核镜像可以适配不同主板、不同外设组合,而无需为每种硬件重新编译内核。要真正读懂并修改设备树,必须先掌握 DTS、DTSI、DTB、DTC、节点、属性、phandle、compatible 这八个核心术语。它们不是孤立概念,而是从源码到二进制、从描述到匹配的一条完整链路。

一、核心术语与整体关系

设备树的处理流程可以概括为:开发者编写 DTS/DTSI 源文件,由 DTC 编译成 DTB 二进制文件,Bootloader 将 DTB 传给内核,内核解析后依据 节点、属性、compatible 和 phandle 完成驱动匹配与设备初始化。下面这张图展示了它们之间的数据流与依赖关系。

设备树从源码到内核匹配的完整链路 DTS 板级设备树源文件 board.dts DTSI SoC 公共头文件 soc.dtsi DTC 设备树编译器 dtc -I dts -O dtb DTB 二进制设备树 board.dtb Linux 内核 解析 DTB 匹配驱动 DTB 内部结构要素 节点 node 属性 property phandle compatible 节点构成树形层级;属性描述节点特征; phandle 实现节点间引用;compatible 用于驱动匹配。

二、DTS 与 DTSI:源码层的分工

DTS(Device Tree Source)是板级设备树源文件,通常一个具体产品对应一个 .dts 文件,例如 rk3568-evb.dts。它描述这块板子上实际存在的外设、引脚、时钟、电源等。DTSI(Device Tree Source Include)则是可被包含的公共描述文件,通常由 SoC 厂商提供,例如 rk3568.dtsi,里面定义了 CPU、GIC、UART、I2C 等 SoC 内部控制器。板级 DTS 通过 #include 引入 DTSI,再对差异部分做覆盖或追加。

// rk3568-evb.dts
#include "rk3568.dtsi"
#include "rk3568-android.dtsi"

/ {
    model = "Rockchip RK3568 EVB";
    compatible = "rockchip,rk3568-evb", "rockchip,rk3568";

    chosen {
        bootargs = "console=ttyFIQ0 androidboot.hardware=rk30board";
    };
};

&uart2 {
    status = "okay";
    pinctrl-0 = <&uart2m0_xfer>;
};

这种“DTSI 提供共性、DTS 描述差异”的分层方式,是 Android 设备树适配中最常见的组织模式。它既减少了重复,又让板级差异一目了然。

三、DTC 与 DTB:从源码到二进制

DTC(Device Tree Compiler)是设备树编译器,负责把人类可读的 DTS/DTSI 编译成内核可解析的 DTB(Device Tree Blob)二进制格式。Android 构建系统中,DTC 通常由内核构建脚本自动调用,也可以手动执行:

# 手动编译
dtc -I dts -O dtb -o rk3568-evb.dtb rk3568-evb.dts

# 反编译 DTB 为 DTS,便于排查
dtc -I dtb -O dts -o dump.dts rk3568-evb.dtb

DTB 不是普通二进制,它采用扁平化设备树(Flattened Device Tree)结构,包含头部、内存保留区、结构块和字符串块。Bootloader 将 DTB 加载到内存并传给内核,内核在启动早期解析它,进而创建 platform_device 并触发驱动 probe。

提示:Android 中 DTB 可能被附加在内核镜像之后,也可能独立分区存放。不同平台(高通、瑞芯微、联发科)的打包方式不同,适配时要先确认 DTB 的实际加载位置。

四、节点、属性、phandle 与 compatible

节点(node)是设备树的基本单元,用花括号表示层级,例如 i2c@fe5c0000 { ... }。节点可以嵌套,形成树形结构,根节点是 /。属性(property)是节点内的键值对,描述该节点的特征,例如 reg、interrupts、status、clocks。属性值可以是字符串、整数、数组、空值或字节序列。

phandle 是节点的一个唯一数字标识,用于让其他节点引用它。设备树中常通过 phandle 或 linux,phandle 实现跨节点引用,例如一个设备节点引用它的时钟控制器或中断控制器。compatible 是最重要的属性之一,它是一个字符串列表,格式通常为 "厂商,型号",内核用它来匹配对应的驱动。匹配时从最具体到最通用依次尝试。

术语 作用 典型示例
DTS 板级设备树源文件,描述具体产品硬件 rk3568-evb.dts
DTSI SoC 公共描述文件,被 DTS 包含 rk3568.dtsi
DTC 设备树编译器,DTS 转 DTB dtc -I dts -O dtb
DTB 二进制设备树,由 Bootloader 传给内核 rk3568-evb.dtb
节点 设备树的基本单元,可嵌套形成层级 i2c@fe5c0000
属性 节点内的键值对,描述硬件特征 status = "okay"
phandle 节点唯一标识,用于跨节点引用 phandle = <0x1a>
compatible 驱动匹配关键字,决定加载哪个驱动 "rockchip,rk3568-i2c"

五、案例场景:一个 I2C 触摸屏的适配

假设要在 RK3568 平台上适配一款 I2C 触摸屏,典型设备树片段如下:

&i2c3 {
    status = "okay";
    clock-frequency = <400000>;

    touchscreen@38 {
        compatible = "goodix,gt911";
        reg = <0x38>;
        interrupt-parent = <&gpio3>;
        interrupts = <RK_PB5 IRQ_TYPE_EDGE_FALLING>;
        reset-gpios = <&gpio3 RK_PB6 GPIO_ACTIVE_LOW>;
        irq-gpios = <&gpio3 RK_PB5 GPIO_ACTIVE_HIGH>;
        status = "okay";
    };
};

这里 compatible = "goodix,gt911" 会触发内核中 goodix 触摸驱动 probe;reg = <0x38> 告诉 I2C 控制器从机地址;interrupt-parent 和 interrupts 通过 phandle 引用 GPIO 控制器,完成中断映射。如果 compatible 写错,驱动不会加载,触摸屏就不会工作。

常见误区:很多初学者只改 DTS 却忘记确认 DTSI 中对应控制器是否已使能,或者把 status 写成 "ok" 而不是 "okay"。设备树中只有 "okay" 和 "disabled" 是标准状态值,拼写错误会导致节点被忽略。

六、复盘与排查思路

当设备树适配出现问题时,可以按以下顺序排查:

  1. 确认 DTS 是否被正确包含进构建,DTB 是否重新生成。
  2. 用 dtc -I dtb -O dts 反编译实际 DTB,检查节点和属性是否与预期一致。
  3. 检查 compatible 是否与驱动中的 of_device_id 表匹配。
  4. 检查 status 是否为 "okay",父节点是否使能。
  5. 检查 phandle 引用是否指向正确的控制器,GPIO、时钟、中断是否配置完整。
  6. 通过 /proc/device-tree 或 /sys/firmware/devicetree 查看内核实际解析结果。

掌握这八个术语,就掌握了设备树适配的“语言”。后续无论是添加外设、修改引脚,还是排查驱动不加载的问题,都能沿着 DTS → DTC → DTB → 内核解析 → compatible 匹配这条链路快速定位。