1. 设备树(DTS)概述:什么是设备树、为什么需要设备树、设备树在Android系统中的角色
1.1 什么是设备树
设备树(Device Tree)是一种描述硬件资源的数据结构,它使用树形节点来记录系统中所有可访问的设备、总线、中断控制器、时钟、GPIO 等硬件信息。设备树源文件(.dts)经过编译生成设备树二进制文件(.dtb),由 Bootloader 传递给内核,内核解析后即可获知硬件拓扑,无需在源码中硬编码硬件细节。
核心定义: 设备树 = 硬件描述语言 + 数据传递协议。它把“板级硬件配置”从内核源码中剥离,变成一份独立的数据文件。
一个典型的设备树节点结构如下:
/dts-v1/;
/ {
model = "Qualcomm Technologies, Inc. MSM8953 SoC";
compatible = "qcom,msm8953";
soc {
#address-cells = <2>;
#size-cells = <2>;
uart@78af000 {
compatible = "qcom,msm-uartdm-v1.4", "qcom,msm-uartdm";
reg = <0x0 0x078af000 0x0 0x200>;
interrupts = <0 107 0>;
status = "okay";
};
};
};
上述代码中:
- 根节点
/描述整个板级模型和兼容字符串。 - soc 节点 作为总线容器,定义地址和大小单元。
- uart@78af000 是具体外设节点,包含驱动匹配所需的
compatible、寄存器地址reg、中断号interrupts和状态status。
1.2 为什么需要设备树
在设备树出现之前,Linux 内核通过 板级文件(Board File) 来定义硬件。每个 ARM 板卡都需要在 arch/arm/mach-xxx 下编写大量 C 结构体和注册函数。这种方式的弊端日益突出:
| 对比维度 | 传统板级文件 | 设备树方式 |
|---|---|---|
| 硬件变更影响 | 需修改内核源码并重新编译 | 只需修改 .dts 文件,重新编译 DTB |
| 多板卡支持 | 每个板卡一套独立代码,冗余严重 | 同一内核镜像 + 不同 DTB 即可支持多板 |
| 代码维护成本 | 高,板级代码与驱动代码耦合 | 低,硬件描述与驱动逻辑分离 |
| 社区接受度 | ARM 社区逐渐弃用 | ARM64/ARM 官方推荐标准 |
关键转折: 2011 年 Linux 3.0 之后,ARM 架构开始强制要求使用设备树。到 2014 年 Linux 3.16,ARM 平台基本完成设备树化迁移。Android 从 5.0(Lollipop)开始全面采用设备树管理硬件。
设备树带来的直接收益:
- 解耦硬件与内核: 同一份内核镜像可以运行在不同硬件平台上,只需更换 DTB。
- 减少内核启动参数: 硬件信息通过 DTB 传递,不再依赖 ATAGS 或命令行参数。
- 标准化硬件描述: 设备树绑定文档(
Documentation/devicetree/bindings/)成为硬件厂商和驱动开发者的共同语言。 - 支持设备热插拔: 设备树节点可以动态覆盖(Overlay),适应模块化硬件设计。
1.3 设备树在 Android 系统中的角色
在 Android 系统中,设备树贯穿从 Bootloader 到内核再到 HAL 层的整个硬件管理链路。其核心角色可以用下图概括:
在 Android 启动流程中,设备树的具体参与环节如下:
| 启动阶段 | 设备树操作 | 说明 |
|---|---|---|
| Bootloader | 加载 DTB 到内存,传递地址给内核 | ABL(Android Bootloader)或 U-Boot 从 boot 分区读取 .dtb 文件 |
| Kernel 早期启动 | 解析 DTB 头部,建立设备树结构 | 在 start_kernel 之前完成 unflatten_device_tree() |
| Kernel 设备模型 | 遍历设备树节点,创建 platform_device |
每个节点对应一个设备,挂接到平台总线 |
| 驱动匹配 | 驱动 of_match_table 与节点 compatible 比对 |
匹配成功后调用 probe() 函数 |
| Android HAL | 通过 /proc/device-tree 或内核接口读取设备信息 |
HAL 层可获取 GPIO、时钟等资源分配 |
Android 特有注意: 与标准 Linux 不同,Android 设备树通常包含 碎片化(Fragment) 和 覆盖(Overlay) 机制。高通平台使用
dtbTool 将多个 DTB 打包成 dt.img,由 Bootloader 根据硬件 ID 选择加载。Google 从 Android 9 开始推荐使用 设备树覆盖(DTO) 来分离 SoC 和板级配置。
1.4 设备树文件体系
一个完整的 Android 设备树项目通常包含以下文件类型:
| 文件后缀 | 全称 | 作用 |
|---|---|---|
.dts |
Device Tree Source | 设备树源文件,开发者编写的主要文件 |
.dtsi |
Device Tree Source Include | 设备树头文件,用于 SoC 公共定义,被 .dts 包含 |
.dtb |
Device Tree Blob | 编译后的二进制文件,Bootloader 加载使用 |
.dtbo |
Device Tree Blob Overlay | 设备树覆盖文件,用于运行时叠加修改 |
.dts.orig |
原始备份 | 编译过程中自动生成的备份文件 |
典型的 Android 设备树目录结构(以 Qualcomm SM8250 为例):
kernel/msm-5.4/arch/arm64/boot/dts/qcom/
├── sm8250.dtsi # SoC 级定义(CPU、GIC、时钟等)
├── sm8250-pinctrl.dtsi # 引脚复用定义
├── sm8250-camera.dtsi # 摄像头相关节点
├── sm8250-display.dtsi # 显示相关节点
├── sm8250-audio.dtsi # 音频相关节点
├── sm8250-mtp.dts # MTP 参考板
├── sm8250-cdp.dts # CDP 参考板
├── sm8250-qrd.dts # QRD 参考板
└── sm8250-xiaomi-alioth.dts # 小米 K40 具体机型
最佳实践:
.dtsi 文件只定义 SoC 级硬件,不包含板级差异。板级差异(如 GPIO 分配、外设型号、电源管理配置)放在具体 .dts 文件中。Android 厂商通常会在 kernel/msm-5.4/arch/arm64/boot/dts/vendor/ 下维护自己的设备树。
1.5 设备树与驱动匹配的桥梁
设备树节点中的 compatible 属性是连接硬件描述与驱动代码的关键。驱动通过 of_match_table 声明自己支持的设备,内核在设备枚举时自动完成匹配:
// 驱动侧:定义匹配表
static const struct of_device_id msm_uart_dt_match[] = {
{ .compatible = "qcom,msm-uartdm-v1.4", },
{ .compatible = "qcom,msm-uartdm", },
{},
};
// 驱动侧:注册平台驱动
static struct platform_driver msm_uart_driver = {
.probe = msm_uart_probe,
.remove = msm_uart_remove,
.driver = {
.name = "msm_uart",
.of_match_table = msm_uart_dt_match,
},
};
module_platform_driver(msm_uart_driver);
当内核解析到 uart@78af000 节点时,会遍历平台总线上的驱动,比对 compatible 字符串。一旦匹配成功,调用驱动的 probe() 函数,并将设备树节点中的 reg、interrupts 等资源传递给驱动。
匹配流程总结: 设备树节点 → 内核创建设备 → 平台总线匹配 → 驱动 probe() → 硬件初始化。整个过程无需修改内核源码,只需调整
.dts 文件即可适配新硬件。