一、AOSP设备树概述:什么是设备树、设备树在Android系统中的角色、设备树与内核的关系

1.1 设备树到底是什么?

设备树,英文叫 Device Tree,简称 DT。说白了,它就是一个描述硬件信息的配置文件。

我刚开始接触 Android 底层时,总觉得这玩意儿很玄乎。后来做了一次平台移植,才真正搞明白——设备树就是一张「硬件清单」。它告诉内核:你的板子上有哪些外设、它们挂在哪条总线上、地址是多少、中断号是什么。

举个例子,你写一个 LED 驱动,传统做法是在驱动代码里硬编码 GPIO 号。换一块板子,GPIO 变了,你得改代码重新编译。有了设备树,你只需要改 dts 文件里的描述,驱动代码完全不用动。

核心要点:设备树实现了「驱动代码」与「硬件配置」的解耦。这是 Android 系统能够适配上千种不同硬件的关键设计。

1.2 设备树在 Android 系统中的角色

在 Android 系统里,设备树扮演着「硬件抽象层」的角色。它位于 Bootloader 和 Kernel 之间,是系统启动时第一个被加载的硬件描述。

我参与过一个项目,客户要求在一款老平台上跑 Android 14。硬件没变,但内核版本从 4.9 升到了 5.10。你猜怎么着?设备树文件几乎没动,只改了几个 compatible 字符串。这就是设备树的威力。

具体来说,设备树在 Android 系统中有三个关键作用:

  • 启动阶段硬件枚举:Bootloader 将设备树二进制文件(dtb)传递给内核,内核解析后就知道有哪些硬件可用。
  • 驱动匹配与参数传递:驱动通过 compatible 属性匹配设备节点,并从节点中读取寄存器地址、中断号、时钟频率等参数。
  • 硬件差异化配置:同一套内核代码,通过不同的设备树文件,可以适配不同型号的手机、平板、车机。

个人经验:我习惯在设备树里把 GPIO 的命名做得规范一些。比如用 gpio-key-power 而不是 gpio-123。这样调试时一眼就能看出哪个引脚是干什么的,省了不少排查时间。

1.3 设备树与内核的关系

这个问题,我经常被刚入行的同事问到。设备树和内核到底是什么关系?

简单来说:设备树是内核的「硬件说明书」。内核本身不关心你用的是哪款 SoC、哪个传感器,它只负责执行逻辑。设备树告诉它:「嘿,这里有个 I2C 控制器,地址是 0x1c00000,你把它初始化一下。」

从代码层面看,关系是这样的:

// 内核驱动中匹配设备树节点
static const struct of_device_id my_i2c_driver_of_match[] = {
    { .compatible = "vendor,my-sensor" },
    { /* sentinel */ }
};

static struct platform_driver my_i2c_driver = {
    .probe = my_i2c_probe,
    .driver = {
        .name = "my_sensor",
        .of_match_table = my_i2c_driver_of_match,
    },
};

// 设备树中的描述
i2c@1c00000 {
    my_sensor@48 {
        compatible = "vendor,my-sensor";
        reg = <0x48>;
        interrupt-parent = <&gpio1>;
        interrupts = <5 IRQ_TYPE_EDGE_RISING>;
    };
};

内核启动时,会遍历设备树中的所有节点。每个节点都有一个 compatible 属性,内核拿着这个字符串去驱动列表里找匹配项。找到了,就调用驱动的 probe 函数。

为什么会这样设计?你想想看,如果没有设备树,每换一个硬件就要改内核代码、重新编译。有了设备树,内核可以做成通用的,硬件差异全交给 dts 文件去描述。

注意:设备树不是万能的。它只描述「静态硬件信息」,比如寄存器地址、中断号、时钟频率。动态信息(比如传感器当前读数、电池电量)不归设备树管,那是驱动运行时的事情。

1.4 设备树的文件结构

在 AOSP 源码里,设备树文件通常放在 kernel/device/ 目录下。常见的文件类型有:

文件后缀 说明 用途
.dts 设备树源文件 人类可读的文本描述,类似 C 语言源码
.dtsi 设备树包含文件 公共部分的描述,类似 C 语言的 .h 头文件
.dtb 设备树二进制文件 编译后的二进制,内核直接解析
.dtbo 设备树叠加层 用于动态修改设备树,Android 9 之后引入

我记得第一次编译设备树时,直接用 make dtbs 命令,结果报了一堆语法错误。后来才发现,dts 文件的语法非常严格,少一个分号、多一个括号都不行。

避坑指南:我曾经在 dts 文件里漏写了一个 #address-cells 属性,结果内核解析 reg 属性时全乱了,外设一个都没认出来。排查了整整两天。所以,写设备树时一定要检查这些基础属性是否完整。

1.5 设备树在 Android 启动流程中的位置

Android 系统的启动流程,大致是:Bootloader → Kernel → Init → Zygote → System Server。设备树在 Bootloader 阶段就参与进来了。

具体流程是这样的:

  1. Bootloader 从存储分区(通常是 boot 分区或 dtb 分区)加载 dtb 文件。
  2. Bootloader 将 dtb 文件在内存中的地址传递给内核(通过 r2 寄存器或设备树 blob 地址)。
  3. 内核启动后,第一时间解析 dtb,建立设备树结构。
  4. 内核根据设备树信息,初始化各个子系统(中断控制器、时钟、GPIO、I2C 等)。
  5. 驱动 probe 时,从设备树中读取硬件参数。

这里有个细节:Android 从 9.0 开始引入了「设备树叠加层」(DTBO)。它的作用是让 Bootloader 可以在不修改原始 dtb 的情况下,动态添加或修改设备树节点。比如,同一款 SoC 用在两种不同屏幕的手机上,可以通过 DTBO 来切换显示相关的配置。

关键点:设备树是 Android 系统硬件适配的基石。没有它,内核就无法知道它运行在什么硬件上。你可以把设备树理解为「硬件与软件之间的翻译官」。

1.6 小结

这一章我们聊了设备树的基本概念、在 Android 系统中的作用,以及它和内核的关系。说白了,设备树就是一张硬件配置清单,让内核能够灵活适配不同的硬件平台。

下一章,我会带你手把手写一个完整的设备树文件,从零开始描述一个简单的开发板。到时候你会看到,那些看似复杂的 dts 文件,其实都是有规律可循的。

嗯,先消化一下今天的内容。有什么问题,欢迎随时交流。