1. 设备树基础概念
1.1 什么是设备树 (Device Tree)
设备树(Device Tree,简称 DT)是一种描述硬件资源的数据结构,它采用树状层次结构来表述嵌入式系统中 CPU、内存、外设、总线、中断控制器等硬件组件及其连接关系。设备树源文件(.dts)经过设备树编译器(dtc)编译后生成设备树二进制文件(.dtb),由 Bootloader 在引导内核时传递给操作系统。
设备树的核心思想是:将硬件描述从内核代码中剥离出来,形成独立的数据文件,使得同一份内核镜像可以支持多种硬件平台,无需为每块开发板重新编译内核。
关键术语速查:
- DTS (Device Tree Source) — 设备树源文件,文本格式,由开发者编写
- DTC (Device Tree Compiler) — 设备树编译器,将 DTS 编译为 DTB
- DTB (Device Tree Blob) — 设备树二进制文件,内核解析的最终格式
- DTSI (Device Tree Source Include) — 设备树头文件,用于公共硬件描述复用
1.2 为什么需要设备树
在设备树出现之前,嵌入式 Linux 内核维护着大量 board-*.c 和 mach-*.c 文件,每个开发板都需要在源码中硬编码硬件信息。这种模式带来了严重问题:
| 问题维度 | 传统硬编码方式 | 设备树方式 |
|---|---|---|
| 内核维护 | 每块板子需独立 board.c,代码冗余 |
一份内核镜像适配多板,硬件描述外置 |
| 硬件变更 | 修改硬件需改内核源码并重新编译 | 仅修改 .dts 文件,重新编译 DTB |
| 社区协作 | 厂商代码难以合入主线内核 | 设备树标准化,主线合入门槛降低 |
| 启动灵活性 | Bootloader 无法动态调整硬件配置 | Bootloader 可修改 DTB 或叠加 Overlay |
| 驱动开发 | 驱动与板级信息耦合,复用性差 | 驱动通过设备树匹配,与板级解耦 |
实战经验: 在 ARM 嵌入式项目中,设备树已成为硬件描述的事实标准。从 Linux 3.x 开始,ARM 架构强制要求使用设备树,不再接受新的
board-*.c 文件。掌握 DTS 编写是嵌入式驱动开发者的必备技能。
1.3 设备树与 ACPI 的区别
ACPI(Advanced Configuration and Power Interface)是 x86 平台广泛使用的硬件描述和电源管理规范,与设备树在理念和实现上有显著差异。两者对比如下:
| 对比维度 | 设备树 (Device Tree) | ACPI |
|---|---|---|
| 主要应用平台 | ARM、RISC-V、PowerPC 等嵌入式架构 | x86、x86_64 服务器与 PC 平台 |
| 描述方式 | 树状结构,纯文本 DTS 源文件 | 表结构,ASL 源码编译为 AML 字节码 |
| 运行时能力 | 静态描述,Bootloader 传递 DTB | 支持动态枚举、热插拔、电源管理 |
| 复杂度 | 轻量级,语法简洁,学习曲线平缓 | 重量级,规范庞大,实现复杂 |
| 内核集成 | 驱动通过 compatible 字符串匹配 | 驱动通过 ACPI ID 匹配 |
| 修改方式 | 修改 DTS 文件,重新编译 DTB | 修改 ASL 表,重新编译 AML |
| 社区生态 | 嵌入式社区主导,主线内核广泛支持 | Windows/Linux 服务器生态,UEFI 绑定 |
注意: 虽然 ARM 服务器领域开始出现 ACPI 支持(如 SBSA/SBBR 规范),但在绝大多数 ARM 嵌入式 Linux 项目中,设备树仍然是唯一选择。学习 DTS 编写是进入嵌入式 Linux 驱动开发的必经之路。
1.4 设备树的基本语法结构
一个典型的设备树源文件包含以下核心元素:
/dts-v1/;
/ {
model = "TI AM335x BeagleBone Black";
compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";
chosen {
stdout-path = &uart0;
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>; /* 256 MB */
};
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a8";
reg = <0>;
};
};
uart0: serial@44e09000 {
compatible = "ti,am3352-uart", "ti,omap3-uart";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
status = "okay";
};
};
语法要点:
/dts-v1/;— 版本标识,必须位于文件头部/ { };— 根节点,所有设备节点都是其子节点node-name@unit-address— 节点命名规范,@后跟寄存器基地址compatible— 驱动匹配的关键属性,格式为"厂商,型号"reg— 描述地址空间,格式为<基址 长度>&label— 引用已定义的节点标签,用于追加或修改属性
1.5 设备树在嵌入式 Linux 启动中的角色
设备树在系统启动过程中扮演着硬件信息传递者的角色,其完整流程如下:
- Bootloader 阶段: U-Boot 等 Bootloader 从存储介质(eMMC、SD卡、Flash)加载 DTB 文件到内存
- DTB 调整: Bootloader 可根据环境变量或硬件检测修改 DTB(如修改
mac-address、使能/禁用节点) - 内核引导: Bootloader 将 DTB 物理地址通过寄存器传递给内核(ARM 架构使用
r2寄存器) - 内核解析: 内核在启动早期调用
unflatten_device_tree()将 DTB 展开为device_node树 - 驱动匹配: 总线驱动遍历设备树,通过
of_match_table匹配compatible属性,触发驱动 probe - 资源获取: 驱动通过
of_*API 读取设备树中的reg、interrupts、gpios等资源信息
调试技巧: 在 Linux 内核启动时,可以通过
earlycon 和 printk 日志观察设备树解析过程。使用 dtc -I dtb -O dts -o dump.dts board.dtb 命令可以将 DTB 反编译为可读的 DTS 格式,方便调试。
1.6 本章小结
设备树是 ARM 嵌入式 Linux 开发中不可或缺的硬件描述机制,它将硬件信息从内核源码中解耦出来,实现了"一次编译,多处运行"的目标。与 ACPI 相比,设备树更加轻量、简洁,更适合资源受限的嵌入式场景。掌握设备树的基本概念、编译流程和语法结构,是后续深入学习 DTS 编写和驱动开发的基础。