3. 设备树基础概念
在 Android 内核与设备树平台适配中,设备树(Device Tree)承担着“把硬件描述从内核代码中剥离出来”的关键角色。它让同一份内核镜像可以适配不同主板、不同外设组合,而无需为每种硬件重新编译内核。要真正读懂并修改设备树,必须先掌握 DTS、DTSI、DTB、DTC、节点、属性、phandle、compatible 这八个核心术语。它们不是孤立概念,而是从源码到二进制、从描述到匹配的一条完整链路。
一、核心术语与整体关系
设备树的处理流程可以概括为:开发者编写 DTS/DTSI 源文件,由 DTC 编译成 DTB 二进制文件,Bootloader 将 DTB 传给内核,内核解析后依据 节点、属性、compatible 和 phandle 完成驱动匹配与设备初始化。下面这张图展示了它们之间的数据流与依赖关系。
二、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。
四、节点、属性、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 写错,驱动不会加载,触摸屏就不会工作。
status 写成 "ok" 而不是 "okay"。设备树中只有 "okay" 和 "disabled" 是标准状态值,拼写错误会导致节点被忽略。
六、复盘与排查思路
当设备树适配出现问题时,可以按以下顺序排查:
- 确认 DTS 是否被正确包含进构建,DTB 是否重新生成。
- 用
dtc -I dtb -O dts反编译实际 DTB,检查节点和属性是否与预期一致。 - 检查
compatible是否与驱动中的of_device_id表匹配。 - 检查
status是否为"okay",父节点是否使能。 - 检查 phandle 引用是否指向正确的控制器,GPIO、时钟、中断是否配置完整。
- 通过
/proc/device-tree或/sys/firmware/devicetree查看内核实际解析结果。
掌握这八个术语,就掌握了设备树适配的“语言”。后续无论是添加外设、修改引脚,还是排查驱动不加载的问题,都能沿着 DTS → DTC → DTB → 内核解析 → compatible 匹配这条链路快速定位。