1. 设备树概述:起源、作用与核心地位
1.1 设备树的起源:从“硬编码”到“描述”的范式转变
在设备树(Device Tree, DT)出现之前,嵌入式 Linux 内核中充斥着大量针对特定单板(Board)的 “硬编码” 代码。每个板级文件(如 arch/arm/mach-xxx.c)都需要手动注册平台设备、定义内存地址、中断号、GPIO 映射等。这种模式导致内核代码冗余、维护困难,且每更换一款硬件,就必须重新编译内核。
设备树的概念源于 Open Firmware(IEEE 1275),由 PowerPC 架构率先采用。ARM 架构在 Linux 3.x 时代全面引入设备树,彻底改变了硬件描述方式。其核心思想是:将硬件配置信息从内核代码中剥离,以结构化数据的形式传递给内核,实现“一次编译,多处运行”。
关键转折点: 设备树的引入使得同一份内核镜像可以支持不同硬件配置的板卡,Bootloader 只需加载对应的 DTB(Device Tree Blob)文件即可。
1.2 设备树的作用:硬件描述与内核解耦
设备树在嵌入式 Linux 系统中承担着 “硬件配置清单” 的角色。它使用节点(Node)和属性(Property)来描述系统中的所有可发现硬件资源,包括但不限于:
- CPU 核心:数量、架构、缓存大小。
- 内存布局:起始地址、大小、DMA 区域。
- 总线拓扑:AMBA、PCIe、I2C、SPI、USB 等。
- 外设寄存器:基地址、中断号、时钟频率。
- 引脚复用:GPIO 功能配置、电气特性。
- 电源管理:电压域、时钟门控、休眠状态。
内核在启动阶段解析 DTB,根据节点信息动态创建 platform_device、i2c_client、spi_device 等内核设备对象,并触发对应驱动程序的 probe() 回调。这一机制使得驱动代码不再依赖具体的板级配置,而是通过 compatible 属性与设备节点进行匹配。
| 功能维度 | 设备树实现方式 | 传统硬编码方式 |
|---|---|---|
| 硬件描述 | DTS 源文件 + DTB 二进制 | C 结构体 + 宏定义 |
| 设备注册 | 内核解析 DTB 自动注册 | 手动调用 platform_device_register() |
| 资源传递 | 节点属性(reg, interrupts, clocks) | 硬编码 resource 结构体 |
| 驱动匹配 | compatible 字符串匹配 | 设备 ID 表 + 板级检查 |
| 多板支持 | 更换 DTB 文件即可 | 重新编译内核或添加板级文件 |
实践提示: 在调试设备树时,可以使用
dtc -I dtb -O dts 命令将 DTB 反编译为可读的 DTS 格式,快速验证 Bootloader 传递的 DTB 内容是否正确。
1.3 设备树与 ACPI 的区别:嵌入式 vs 通用计算
ACPI(Advanced Configuration and Power Interface)是 x86 架构的标准硬件抽象层,主要用于 PC 和服务器领域。设备树与 ACPI 虽然目标相似(硬件描述与操作系统解耦),但在设计哲学、使用场景和实现细节上存在显著差异。
| 对比维度 | 设备树 (DT) | ACPI |
|---|---|---|
| 主要应用架构 | ARM、RISC-V、PowerPC | x86、x86_64、IA64 |
| 描述语言 | DTS(文本)+ DTB(二进制) | ASL(文本)+ AML(字节码) |
| 运行时能力 | 静态,启动后不变 | 动态,支持热插拔、电源事件 |
| 电源管理 | 依赖驱动 + 独立 PM 框架 | 内建 P-state/C-state 控制 |
| 固件交互 | Bootloader 传递 DTB 地址 | UEFI 提供 ACPI 表根指针 |
| 复杂度 | 较低,易于理解和调试 | 较高,AML 解释器复杂 |
| 社区/标准 | Linux 内核社区维护 | UEFI 论坛标准化 |
注意: 虽然设备树和 ACPI 在功能上有重叠,但在嵌入式 Linux 领域,设备树是绝对的主流。ACPI 在 ARM 服务器领域(如 Ampere、华为鲲鹏)也有应用,但嵌入式场景几乎全部使用设备树。
1.4 设备树在嵌入式 Linux 中的核心地位
在嵌入式 Linux 系统中,设备树已经从一个可选的辅助工具演变为 不可或缺的核心组件。它贯穿了从 Bootloader 到内核、再到驱动加载的整个启动流程:
- Bootloader 阶段:U-Boot 或 Barebox 从存储介质(eMMC、NAND Flash、SD 卡)读取 DTB 文件,或根据硬件跳线动态生成 DTB,将其加载到内存中,并将 DTB 物理地址传递给内核。
- 内核启动早期:内核解压后,首先调用
early_init_dt_scan()解析 DTB,获取内存大小、CPU 数量、chosen 节点(如 bootargs)等关键信息。 - 设备注册阶段:内核遍历 DTB 中的节点树,为每个匹配驱动的节点创建设备对象,并调用驱动的
probe()函数。 - 运行时阶段:驱动通过
of_*系列 API(如of_property_read_u32())动态读取设备树属性,获取硬件配置参数。
核心结论: 设备树是嵌入式 Linux 系统中连接 Bootloader 与内核、硬件与驱动的“中间语言”。掌握设备树的加载阶段与 DTB 参数配置,是嵌入式系统工程师的必备技能。后续章节将深入分析 Bootloader 如何传递 DTB、内核如何解析 DTB,以及常见的配置错误与调试方法。