1. 设备树基础概念
1.1 什么是设备树(Device Tree)
设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它使用节点(node)和属性(property)的树形层次来向操作系统内核传递平台硬件信息。在嵌入式 Linux 系统中,设备树文件通常以 .dts(Device Tree Source)为扩展名,经过编译生成 .dtb(Device Tree Blob)二进制文件,由 Bootloader 加载并传递给内核。
设备树的核心思想是:将硬件描述从内核代码中剥离出来,使同一份内核镜像可以支持多种硬件平台,而无需为每种板卡重新编译内核。
关键术语速查表
| 术语 | 全称 | 说明 |
|---|---|---|
| DTS | Device Tree Source | 设备树源文件,文本格式,由开发者编写 |
| DTC | Device Tree Compiler | 设备树编译器,将 DTS 编译为 DTB |
| DTB | Device Tree Blob | 设备树二进制文件,供内核解析 |
| DTBO | Device Tree Blob Overlay | 设备树叠加层,用于运行时动态修改设备树 |
| FDT | Flattened Device Tree | 扁平化设备树,DTB 在内存中的展开形式 |
一个典型的设备树节点结构如下:
/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";
device_type = "cpu";
reg = <0x0>;
};
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; /* 512 MB */
};
leds {
compatible = "gpio-leds";
led0 {
label = "beaglebone:green:usr0";
gpios = <&gpio1 21 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "heartbeat";
};
};
};
提示:设备树中的
compatible 属性是内核匹配驱动的最关键字段,格式通常为 "厂商,型号",内核通过该字符串查找对应的 platform_driver。
1.2 为什么需要设备树
在设备树出现之前(Linux 2.6 ~ 3.x 早期),ARM 架构的嵌入式平台采用 board file 方式描述硬件。每个板卡对应一个 arch/arm/mach-xxx/board-xxx.c 文件,其中硬编码了 GPIO、中断、时钟、I2C 设备等所有硬件信息。这种方式带来了严重的问题:
- 代码冗余:同一 SoC 的不同板卡需要大量重复的 board file,内核源码树中 board file 数量爆炸式增长。
- 维护困难:每次新增板卡都需要修改内核源码,无法做到内核镜像与硬件平台解耦。
- 社区分裂:ARM 架构的板级代码占用了内核维护者大量精力,导致主线内核对新硬件的支持滞后。
- 启动参数混乱:硬件资源(如 GPIO 编号、中断号)在 board file 中硬编码,不同板卡之间无法复用。
历史教训:在 Linux 3.x 时代,ARM 架构的 board file 数量超过 2000 个,每次内核发布都有大量板级代码需要同步更新,维护成本极高。设备树的引入彻底改变了这一局面。
设备树解决了上述问题的核心路径:
- 硬件描述与内核代码分离:硬件信息写在
.dts文件中,内核只负责解析,不再硬编码板级细节。 - 单一内核镜像支持多平台:Bootloader 根据实际硬件加载对应的
.dtb文件,内核无需重新编译。 - 社区统一规范:设备树规范由 ePAPR(Embedded Power Architecture Platform Requirements)标准定义,后被 Devicetree.org 接管,成为嵌入式 Linux 硬件描述的事实标准。
- 驱动可移植性提升:驱动通过设备树中的
compatible字符串匹配硬件,不再依赖板级代码中的静态定义。
1.3 设备树与 ACPI 的区别
ACPI(Advanced Configuration and Power Interface)是 x86/x86_64 平台广泛使用的硬件描述和电源管理标准。设备树与 ACPI 虽然都承担着向操作系统传递硬件信息的职责,但二者在设计哲学、使用场景和实现方式上有本质区别。
| 对比维度 | 设备树(Device Tree) | ACPI |
|---|---|---|
| 主要平台 | ARM、RISC-V、PowerPC 等嵌入式平台 | x86/x86_64、ARM64(服务器) |
| 设计哲学 | 静态硬件描述,编译时确定,启动时加载 | 动态硬件枚举,支持热插拔、电源管理、运行时配置 |
| 数据格式 | 树形节点 + 属性,文本源文件(DTS)编译为二进制(DTB) | AML(ACPI Machine Language)字节码,由 BIOS/UEFI 生成 |
| 可编程性 | 无运行时脚本能力,纯数据描述 | AML 是一种解释执行的虚拟机语言,可执行控制方法 |
| 电源管理 | 依赖驱动和 PM 框架,设备树本身不提供电源管理逻辑 | 内置完整的电源管理状态机(C-states、P-states、D-states) |
| 热插拔支持 | 原生不支持,需借助 Overlay 或驱动层实现 | 原生支持 PCIe、USB 等总线热插拔事件处理 |
| 硬件枚举时机 | Bootloader 加载 DTB,内核启动时一次性解析 | UEFI 固件在启动时构建 ACPI 表,内核按需解析 |
| 社区与标准 | Devicetree.org 维护,ePAPR 规范 | UEFI Forum 维护,ACPI 规范 |
| 驱动匹配方式 | compatible 字符串匹配 |
HID(Hardware ID)和 CID(Compatible ID)匹配 |
核心差异总结:设备树是“静态硬件清单”,ACPI 是“动态硬件管理框架”。设备树适合硬件固定、资源有限的嵌入式系统;ACPI 适合需要复杂电源管理和热插拔的通用计算平台。
在实际工程中,选择设备树还是 ACPI 通常由平台架构决定:
- ARM 嵌入式(Cortex-A/M 系列):几乎全部使用设备树,包括 NXP i.MX、TI AM335x、Rockchip RK3588、Allwinner 等。
- x86 桌面/服务器:强制使用 ACPI,设备树在 x86 平台极少使用。
- ARM64 服务器:部分平台(如 Ampere Altra、华为鲲鹏)同时支持 ACPI 和设备树,但 ACPI 是首选。
- RISC-V:目前以设备树为主,ACPI 支持正在推进中。
1.4 设备树在嵌入式 Linux 启动流程中的位置
理解设备树在整个系统启动流程中的角色,有助于把握其重要性。一个典型的嵌入式 Linux 启动流程如下:
- ROM Bootloader:芯片上电后执行固化在 ROM 中的启动代码,加载 SPL(Secondary Program Loader)或第一阶段 Bootloader。
- SPL / U-Boot SPL:初始化 DDR 时钟和基本外设,加载完整的 U-Boot。
- U-Boot(或其它 Bootloader):完成更复杂的硬件初始化,从存储介质(eMMC、SD 卡、NAND Flash)读取内核镜像(
zImage或Image)和设备树文件(.dtb)。 - 内核启动:U-Boot 将内核镜像和设备树加载到内存中,设置启动参数,跳转到内核入口点。
- 内核解析设备树:内核在启动早期调用
unflatten_device_tree()将 DTB 展开为内核内部的 device_node 树,然后遍历节点创建 platform_device、i2c_client、spi_device 等。 - 驱动匹配与初始化:总线驱动根据设备树中的
compatible、reg、interrupts等属性匹配对应的驱动,完成硬件初始化。
关键点:设备树是内核与 Bootloader 之间的“硬件契约”。Bootloader 负责提供正确的 DTB,内核负责正确解析。如果 DTB 与硬件不匹配,内核可能无法正确初始化外设,甚至无法启动。