1. 设备树基础概念:起源、作用、与 ACPI 的区别、在 Linux 内核中的位置
1.1 设备树的起源与背景
在早期的 Linux 内核中(2.6 及更早版本),嵌入式平台(如 ARM、PowerPC)的硬件描述信息被硬编码在 arch/arm/mach-xxx 目录下的 C 语言板级文件中。每个开发板都需要维护一份 board-xxx.c,其中包含寄存器地址、中断号、时钟频率、GPIO 映射等硬件细节。这种模式导致以下问题:
- 代码冗余:同一 SoC 的不同板卡需要重复定义大量相同硬件信息。
- 维护困难:每次硬件变更都需要修改内核源码并重新编译。
- 社区分裂:大量板级补丁涌入内核主线,导致 ARM 子系统的维护成本急剧上升。
为了解决上述问题,Linux 社区借鉴了 PowerPC 平台的成功经验,引入了 设备树(Device Tree,简称 DT) 机制。设备树是一种描述硬件资源的数据结构,它将硬件配置从内核源码中剥离出来,以独立的数据文件形式存在。Linux 3.x 系列内核开始大规模在 ARM 架构上推行设备树,到 4.x 版本后,几乎所有主流嵌入式架构都已强制要求使用设备树。
关键转折点:Linus Torvalds 在 2011 年对 ARM 板级代码泛滥问题表达了强烈不满,直接推动了设备树在 ARM 架构上的全面落地。设备树从此成为嵌入式 Linux 硬件描述的事实标准。
1.2 设备树的核心作用
设备树在嵌入式 Linux 系统中承担以下核心职责:
| 作用维度 | 具体说明 | 典型示例 |
|---|---|---|
| 硬件枚举 | 描述系统中所有可访问的硬件设备及其拓扑关系 | CPU、内存、I2C 控制器、SPI 设备、GPIO 控制器 |
| 资源描述 | 定义设备的寄存器地址、中断、DMA、时钟等资源 | reg = <0x10000000 0x1000> |
| 配置传递 | 向驱动程序传递非标准化的板级参数 | LCD 屏的时序参数、传感器 I2C 地址 |
| 设备匹配 | 提供 compatible 字符串,供内核驱动进行设备匹配 |
compatible = "ti,omap3-i2c" |
| 运行时探测 | Bootloader(如 U-Boot)在启动时加载 DTB 并传递给内核 | bootm <kernel_addr> - <dtb_addr> |
设备树的核心价值在于实现了 硬件描述与内核代码的解耦。同一份内核镜像可以通过更换不同的设备树二进制文件(DTB)来适配不同的硬件平台,无需重新编译内核。
1.3 设备树与 ACPI 的区别
ACPI(Advanced Configuration and Power Interface)是 x86/x86_64 平台上主流的硬件描述与电源管理标准。设备树与 ACPI 在多个维度存在显著差异:
| 对比维度 | 设备树(DT) | ACPI |
|---|---|---|
| 起源平台 | 嵌入式(ARM、PowerPC、RISC-V) | PC/服务器(x86、x86_64) |
| 数据格式 | 文本源文件(DTS)编译为二进制(DTB) | AML(ACPI Machine Language)字节码 |
| 描述能力 | 侧重硬件拓扑与资源静态描述 | 硬件描述 + 电源管理 + 热管理 + 设备状态控制 |
| 运行时行为 | 静态只读,内核解析后不再修改 | 支持动态命名空间修改、方法调用(_DSM、_PS0 等) |
| 可编程性 | 无脚本/方法能力,纯声明式 | 支持 AML 字节码方法,可执行控制逻辑 |
| 内核集成 | 通过 drivers/of/ 子系统解析 |
通过 drivers/acpi/ 子系统解析 |
| 适用场景 | 嵌入式板级硬件固定、资源受限 | 复杂平台、热插拔、高级电源管理需求 |
常见误区:设备树并非 ACPI 的简化版,两者设计哲学完全不同。设备树假设硬件拓扑在启动时固定不变,而 ACPI 设计时考虑了运行时设备添加、移除和状态转换。在嵌入式领域强行移植 ACPI 会带来不必要的复杂度,反之在 x86 嵌入式场景下使用设备树也会缺失电源管理能力。
1.4 设备树在 Linux 内核中的位置
设备树在 Linux 内核源码树中占据多个关键位置,涉及编译、解析、驱动匹配等多个环节:
- 源文件存放路径:
arch/<架构>/boot/dts/,例如arch/arm/boot/dts/、arch/arm64/boot/dts/、arch/riscv/boot/dts/。 - 编译系统:内核顶层 Makefile 通过
dtbs目标调用scripts/dtc/目录下的设备树编译器(DTC),将.dts源文件编译为.dtb二进制文件。 - 内核解析核心:
drivers/of/目录是设备树的核心实现,包含fdt.c(扁平设备树解析)、base.c(设备树核心框架)、address.c(地址翻译)、irq.c(中断映射)等关键文件。 - 驱动接口:
include/linux/of.h、include/linux/of_device.h、include/linux/of_platform.h等头文件提供了驱动开发所需的 API。 - 设备模型集成:设备树节点在启动时被转换为
struct device_node,并最终与struct platform_device关联,融入 Linux 统一设备驱动模型。
1.5 设备树文件体系
设备树在 Linux 内核中形成了一套完整的文件体系,从源文件到二进制再到运行时结构:
| 文件类型 | 扩展名 | 说明 | 示例 |
|---|---|---|---|
| 源文件 | .dts |
设备树源文件,人类可读的文本格式 | imx6ull.dts |
| 头文件 | .dtsi |
设备树包含文件,类似 C 语言的 .h,用于 SoC 级公共定义 |
imx6ull.dtsi |
| 二进制 | .dtb |
编译后的设备树二进制文件,Bootloader 加载并传递给内核 | imx6ull-14x14-evk.dtb |
| 叠加层 | .dtbo |
设备树叠加层二进制,支持运行时动态修改设备树 | mcp2515-can0.dtbo |
工程实践要点:在嵌入式开发中,
.dtsi 文件通常由 SoC 厂商提供,描述芯片内部集成的外设控制器;.dts 文件由板卡厂商编写,描述板级外设和引脚复用。驱动开发者需要重点关注 .dts 文件中与自身驱动相关的节点属性。
1.6 设备树与驱动开发的关联
设备树是嵌入式 Linux 驱动开发的 第一道关卡。驱动开发者必须理解设备树节点如何映射到内核设备模型:
- 节点匹配:驱动通过
of_match_table中的compatible字符串与设备树节点匹配。 - 资源获取:驱动使用
of_property_read_*系列 API 从设备树节点读取寄存器地址、中断号、时钟频率等资源。 - 设备创建:内核在启动过程中遍历设备树,为每个匹配的节点创建
platform_device,进而触发驱动的probe函数。 - 调试手段:通过
/proc/device-tree/或/sys/firmware/devicetree/可以查看运行时设备树内容,是驱动调试的重要入口。