1. 设备树基础概念
1.1 什么是设备树(Device Tree)
设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它使用树形节点(Node)和属性(Property)来记录系统中所有可寻址设备、总线、中断控制器、时钟、GPIO 等硬件信息。设备树源文件(.dts)经过设备树编译器(DTC)编译为二进制设备树 Blob(.dtb),在系统启动时由 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 0x10000000>; /* 256 MB */
};
uart0: serial@44e09000 {
compatible = "ti,am3352-uart", "ti,omap3-uart";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
status = "okay";
};
};
每个节点由 节点名@地址 标识,compatible 属性用于内核匹配驱动,reg 描述地址空间,interrupts 描述中断号。这种结构使得硬件描述与驱动代码完全解耦。
1.2 为什么需要设备树
在设备树出现之前,嵌入式 Linux 内核维护着大量板级文件(board-*.c),每个开发板都需要在 arch/arm/mach-xxx 下硬编码硬件信息。这种模式导致以下问题:
| 问题 | 传统板级文件方式 | 设备树方式 |
|---|---|---|
| 代码冗余 | 每块板子一份 board.c,大量重复代码 | 一份 .dts 文件描述硬件,内核通用 |
| 维护成本 | 修改硬件需重新编译内核 | 只需更新 .dtb,内核不变 |
| 社区合入 | 新板子需大量修改内核源码 | 提交 .dts 文件即可,驱动复用 |
| 多平台支持 | 内核镜像与板级绑定 | 同一内核镜像支持多板 |
| 启动灵活性 | 硬件信息编译进内核 | Bootloader 动态选择 .dtb |
工程经验:在 NXP i.MX6Q 平台上,传统方式需要维护 20+ 个 board-*.c 文件;迁移到设备树后,所有板级差异集中在 5 个 .dts 文件中,内核主线代码减少约 40%。
1.3 设备树与 ACPI 的区别
ACPI(Advanced Configuration and Power Interface)是 x86 平台的标准硬件描述规范,与设备树在架构、使用场景和灵活性上有本质区别:
| 对比维度 | 设备树(DT) | ACPI |
|---|---|---|
| 起源平台 | 嵌入式 ARM、RISC-V、PowerPC | x86、x86_64 |
| 描述方式 | 纯文本 .dts → 二进制 .dtb | ASL 源码 → AML 字节码 |
| 运行时能力 | 静态描述,启动后只读 | 支持动态热插拔、电源管理方法 |
| 驱动匹配 | compatible 字符串匹配 | HID/CID + _DSD 方法 |
| 资源描述 | reg、interrupts、clocks 等属性 | _CRS、_PRS、_SRS 资源模板 |
| 电源管理 | 依赖驱动实现,DT 不参与 | 内置 _PS0/_PS3、_CST 等对象 |
| 复杂度 | 轻量,适合资源受限设备 | 重量级,适合 PC/服务器 |
| 工具链 | dtc 编译器,简单高效 | iasl 编译器,ACPI 表生成 |
关键区别:设备树是“静态硬件清单”,ACPI 是“可执行硬件抽象层”。在嵌入式领域,设备树因其轻量、透明、易调试的特点成为绝对主流;ACPI 仅在部分高端 ARM 服务器(如 Ampere Altra)上使用。
1.4 设备树在嵌入式 Linux 中的角色
设备树在嵌入式 Linux 系统中扮演着“硬件描述层”的核心角色,贯穿从 Bootloader 到内核驱动的完整启动链路:
- Bootloader 阶段:U-Boot 从存储介质(eMMC、SD、NAND)加载 .dtb 文件到内存,在跳转内核前将 .dtb 地址通过寄存器
r2(ARM)或a1(RISC-V)传递给内核。 - 内核初始化阶段:内核启动早期调用
unflatten_device_tree()将 .dtb 解析为device_node树,建立全局设备链表。 - 驱动匹配阶段:总线驱动(platform、i2c、spi)遍历设备树节点,通过
of_match_table匹配 compatible 属性,触发 probe 回调。 - 资源获取阶段:驱动通过
of_*API(如of_iomap()、of_irq_get())从设备树节点读取寄存器地址、中断号、DMA 通道等资源。 - 运行时阶段:设备树在系统运行后保持只读,但支持
overlay机制动态添加/移除节点(如 FPGA 加载外设)。
角色总结:设备树是嵌入式 Linux 的“硬件适配层”,它让内核与板级硬件解耦,使得 SoC 厂商只需维护通用驱动,板级厂商只需编写 .dts 文件,双方通过 compatible 字符串达成契约。在 NXP、TI、Rockchip、Allwinner 等主流嵌入式平台上,设备树已成为硬件描述的唯一标准。