1. 设备树概述:起源、作用、与ACPI的区别、在Linux内核中的位置
1.1 设备树的起源:从“硬编码”到“数据驱动”
在早期的嵌入式Linux系统中(如ARM Linux 2.6时代),内核通过大量板级文件(board files)来描述硬件。每个开发板都对应一个 arch/arm/mach-xxx/board-xxx.c 文件,其中硬编码了内存地址、中断号、GPIO映射、外设寄存器基址等信息。这种方式的弊端随着硬件种类的爆炸式增长而迅速暴露:
- 代码冗余:同一款SoC搭配不同外设时,需要复制粘贴大量相似代码。
- 维护困难:每增加一个板子,就需要提交一个新的board文件,导致内核源码树急剧膨胀。
- 二进制耦合:内核镜像与特定硬件绑定,无法实现通用内核启动。
关键转折点:PowerPC架构率先引入设备树(Device Tree)概念,将硬件描述从内核代码中剥离为独立的数据结构。ARM社区在2011年(Linux 3.0左右)开始大规模迁移,到2014年(Linux 3.16)基本完成过渡。如今,设备树已成为ARM、RISC-V、MIPS等嵌入式架构的标准硬件描述方式。
1.2 设备树的作用:硬件描述的“桥梁”
设备树(Device Tree,简称DT)是一种树形数据结构,用于描述不可枚举的硬件信息。它的核心作用可以概括为以下三点:
| 作用维度 | 具体说明 | 典型示例 |
|---|---|---|
| 硬件拓扑描述 | 描述CPU、内存、总线、外设之间的连接关系和地址映射 | CPU核心数、内存基址与大小、SPI/I2C总线上的设备 |
| 资源配置传递 | 向驱动程序传递中断号、寄存器地址、时钟频率、DMA通道等参数 | interrupts = <0 31 4>; 表示SPI中断31,高电平触发 |
| 板级差异抽象 | 同一SoC的不同开发板通过不同设备树文件区分,内核代码无需修改 | imx6ull-14x14-evk.dts vs imx6ull-mini.dts |
一句话总结:设备树是连接硬件板级设计与内核驱动程序的“契约文档”。Bootloader(如U-Boot)将编译后的设备树二进制(DTB)传递给内核,内核解析后自动匹配驱动并完成初始化。
1.3 设备树与ACPI的区别:嵌入式 vs 桌面/服务器
ACPI(Advanced Configuration and Power Interface)是x86/ARM64服务器领域的主流硬件描述标准。两者虽然目标相似,但设计哲学和应用场景截然不同:
| 对比维度 | 设备树(DT) | ACPI |
|---|---|---|
| 起源 | Open Firmware,PowerPC/嵌入式领域 | PC/服务器领域,Intel主导 |
| 数据格式 | 纯文本源文件(.dts)→ 二进制(.dtb) | ASL源文件 → AML字节码(二进制表) |
| 运行时修改 | Bootloader可修改/叠加(overlay) | 操作系统通过AML解释器动态评估 |
| 电源管理 | 基本支持,依赖驱动实现 | 原生深度集成(C状态、P状态、热管理) |
| 热插拔 | 有限支持(通过overlay) | 原生支持(PCIe、USB等) |
| 适用场景 | 嵌入式、IoT、ARM/RISC-V单板机 | 桌面、服务器、笔记本、x86/ARM64服务器 |
| 复杂度 | 低,学习曲线平缓 | 高,需要理解AML虚拟机 |
| 内核代码量 | 约5万行(驱动+核心框架) | 约30万行(解释器+表+驱动) |
重要提示:虽然ACPI功能更强大,但设备树在嵌入式领域具有不可替代的优势——轻量、透明、易于调试。对于嵌入式开发者而言,设备树是必须掌握的核心技能。即使ARM64服务器开始支持ACPI,设备树在嵌入式领域的统治地位在未来10年内不会改变。
1.4 设备树在Linux内核中的位置
设备树在Linux内核源码树中占据明确的物理位置和逻辑层级:
1.4.1 源码目录结构
linux/
├── arch/
│ ├── arm/boot/dts/ # ARM架构设备树源文件(.dts/.dtsi)
│ ├── arm64/boot/dts/ # ARM64架构设备树源文件
│ ├── riscv/boot/dts/ # RISC-V架构设备树源文件
│ ├── microblaze/boot/dts/ # MicroBlaze软核设备树
│ └── ...
├── drivers/
│ ├── of/ # Device Tree核心框架(of_* API)
│ │ ├── base.c # 基础解析API
│ │ ├── address.c # 地址转换(#address-cells / #size-cells)
│ │ ├── irq.c # 中断映射
│ │ ├── device.c # 设备创建与匹配
│ │ ├── platform.c # platform_device创建
│ │ └── ...
│ └── ...
├── include/
│ ├── linux/of.h # 设备树核心头文件
│ ├── linux/of_address.h # 地址相关API
│ ├── linux/of_irq.h # 中断相关API
│ ├── linux/of_platform.h # platform设备相关API
│ └── ...
└── scripts/
├── dtc/ # Device Tree Compiler(DTC)工具
│ ├── dtc.c # 编译器主程序
│ ├── dtc-lexer.l # 词法分析器
│ ├── dtc-parser.y # 语法分析器
│ ├── checks.c # 语法/语义检查
│ └── ...
└── ...
1.4.2 设备树在内核启动流程中的位置
1.4.3 设备树与内核子系统的交互关系
| 内核子系统 | 设备树提供的核心信息 | 对应API/框架 |
|---|---|---|
| 中断子系统 | 中断控制器类型、中断号、触发类型、级联关系 | of_irq_parse_one()、irq_domain |
| 时钟框架 | 时钟源、频率、时钟门控、父时钟关系 | of_clk_get()、clk-provider.h |
| GPIO子系统 | GPIO控制器、引脚号、复用功能、上拉/下拉 | of_get_named_gpio()、pinctrl |
| DMA引擎 | DMA控制器、通道号、传输方向、请求线 | of_dma_request_slave_channel() |
| Pinctrl子系统 | 引脚复用配置、电气特性、组定义 | pinctrl-bindings.txt、pinconf |
| Regulator框架 | 电压调节器、输出电压、负载开关 | of_regulator_match() |
| 输入子系统 | 按键、触摸屏、ADC输入映射 | input.h + 设备树键值绑定 |
核心理解:设备树不是驱动程序,而是硬件配置数据库。驱动程序通过
of_* 系列API从设备树中读取配置,然后执行硬件初始化。这种“数据与逻辑分离”的设计,使得同一份驱动代码可以适配不同板级配置,极大提高了代码复用率。
1.5 本章小结
- 起源:设备树从PowerPC的Open Firmware演化而来,解决了ARM Linux板级文件泛滥的问题。
- 作用:作为硬件描述的标准格式,向内核传递拓扑结构、资源配置和板级差异信息。
- 与ACPI的区别:设备树轻量、透明、适合嵌入式;ACPI功能全面但复杂,适合桌面/服务器。
- 内核位置:源文件位于
arch/*/boot/dts/,核心框架位于drivers/of/,编译工具为scripts/dtc/。