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 系统中的角色 设备树在 Android 系统中的角色 硬件设备 设备树二进制 (DTB) Bootloader (ABL/U-Boot) Linux Kernel 设备驱动 (匹配 & 初始化) Android 特有流程 • Bootloader 从 boot 分区 加载 DTB 到内存 • Kernel 解析 DTB 创建设备 • 驱动通过 compatible 匹配

在 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 文件即可适配新硬件。