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 统一设备驱动模型。
设备树在 Linux 内核中的位置与数据流 DTS 源文件 arch/arm/boot/dts/xxx.dts DTC 编译器 scripts/dtc/ DTB 二进制 xxx.dtb Bootloader U-Boot 加载 Linux 内核启动 drivers/of/ 解析层 struct device_node → platform_device 关键路径 DTS → DTC → DTB → Bootloader → 内核 内核内部接口 of_* API 系列 device_node → platform_device

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 驱动开发的 第一道关卡。驱动开发者必须理解设备树节点如何映射到内核设备模型:

  1. 节点匹配:驱动通过 of_match_table 中的 compatible 字符串与设备树节点匹配。
  2. 资源获取:驱动使用 of_property_read_* 系列 API 从设备树节点读取寄存器地址、中断号、时钟频率等资源。
  3. 设备创建:内核在启动过程中遍历设备树,为每个匹配的节点创建 platform_device,进而触发驱动的 probe 函数。
  4. 调试手段:通过 /proc/device-tree/ 或 /sys/firmware/devicetree/ 可以查看运行时设备树内容,是驱动调试的重要入口。