1. 设备树基础概念
1.1 什么是设备树(Device Tree)
设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它使用节点(node)和属性(property)的树形层次来枚举系统中的所有可发现硬件设备、内存映射地址、中断号、时钟频率、GPIO 引脚等平台信息。设备树源文件(.dts)经过设备树编译器(dtc)编译为二进制设备树 Blob(.dtb),由 Bootloader 传递给内核。
核心定义:设备树是一个与体系结构无关的、描述硬件拓扑和配置的静态数据块,它替代了传统 Bootloader 与内核之间通过“硬编码”或“板级文件”传递硬件参数的方式。
一个典型的设备树节点结构如下:
/dts-v1/;
/ {
model = "TI AM335x BeagleBone Black";
compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a8";
reg = <0x0>;
};
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; /* 512 MB */
};
&uart0 {
status = "okay";
pinctrl-0 = <&uart0_pins>;
pinctrl-names = "default";
};
};
关键字段说明:
compatible:驱动匹配的核心字符串,格式为 "厂商,型号";reg:描述地址和长度信息,由 #address-cells 和 #size-cells 决定;status:设备启用状态,"okay" 表示启用,"disabled" 表示禁用。
1.2 为什么需要设备树
在设备树出现之前,嵌入式 Linux 内核维护着大量“板级文件”(board file),每个开发板对应一个 arch/arm/mach-xxx/board-xxx.c。这种模式存在严重问题:
| 问题 | 传统板级文件方式 | 设备树方式 |
|---|---|---|
| 代码冗余 | 每个板子一份 C 文件,大量重复的硬件注册代码 | 一份 DTS 文件描述硬件,内核代码与板级信息解耦 |
| 维护成本 | 新增板子需修改内核源码,社区合入周期长 | 只需提供 DTS 文件,无需修改内核核心代码 |
| 二进制兼容 | 一个内核镜像只能支持一个板子 | 同一内核镜像可通过不同 DTB 支持多个板子 |
| 启动参数传递 | ATAG 列表传递参数,扩展性差 | 设备树结构化传递内存、中断、时钟等复杂信息 |
| 驱动与平台耦合 | 驱动中硬编码地址、中断号 | 驱动通过设备树 API 获取资源,与平台无关 |
重要:自 Linux 3.x 起,ARM 架构强制要求使用设备树,传统板级文件方式已被彻底废弃。PowerPC、RISC-V、x86(嵌入式场景)等架构也广泛采用设备树。
1.3 设备树与 ACPI 的区别
ACPI(Advanced Configuration and Power Interface)是 x86/x86_64 平台的标准硬件描述规范,与设备树有本质区别:
| 对比维度 | 设备树(Device Tree) | ACPI |
|---|---|---|
| 起源 | Open Firmware,由 PowerPC 引入,ARM 推广 | Intel、Microsoft、Toshiba 联合制定,面向 PC |
| 数据格式 | 二进制 DTB(扁平化树结构) | AML(ACPI Machine Language)字节码 + 表结构 |
| 运行时能力 | 静态描述,内核启动后不再修改 | 支持运行时热插拔、电源管理、动态配置 |
| 描述粒度 | 寄存器级地址、中断、时钟、GPIO 等 | 高级抽象:设备功能、功耗状态、热区、电池 |
| 驱动匹配 | compatible 字符串匹配 | HID(Hardware ID)/ CID(Compatible ID)匹配 |
| 适用场景 | 嵌入式、SoC 平台、非 x86 架构 | x86 服务器、桌面、笔记本、UEFI 固件环境 |
| 工具链 | dtc(设备树编译器)、dt-utils | iasl(AML 编译器)、acpidump |
选择原则:如果你的平台使用 UEFI 固件且需要高级电源管理(如 S3/S4 休眠),选择 ACPI;如果你的平台是嵌入式 SoC、使用 U-Boot 或 Coreboot,选择设备树。部分 RISC-V 和 ARM 服务器也开始支持 ACPI,但嵌入式领域设备树仍是绝对主流。
1.4 设备树在嵌入式 Linux 启动流程中的角色
设备树在嵌入式 Linux 启动过程中扮演“硬件信息传递者”的关键角色,贯穿 Bootloader → 内核 → 驱动三个阶段:
- Bootloader 阶段:U-Boot 从存储介质(eMMC、SD 卡、Flash)加载
.dtb文件到内存,解析并可能修改(如填充chosen节点的bootargs),然后将 DTB 内存地址传递给内核。 - 内核早期启动阶段:内核解压后,从寄存器(ARM 的 r2 寄存器)获取 DTB 地址,调用
early_init_dt_scan()解析根节点、内存节点、chosen 节点,完成内存映射、控制台初始化。 - 设备驱动加载阶段:内核遍历设备树,为每个
status = "okay"的节点创建platform_device,驱动通过of_match_table匹配compatible属性,调用probe()函数。
启动流程关键代码路径:
start_kernel() → setup_arch() → unflatten_device_tree() → of_platform_populate() → 驱动 probe()
设备树在启动流程中的核心价值在于:它将硬件描述与内核代码完全分离。开发者只需修改 .dts 文件即可适配新硬件,无需重新编译内核。这种机制使得 SoC 厂商可以发布通用内核,板级厂商只需提供 DTS 文件,极大加速了嵌入式产品的开发周期。