1. 设备树基础概念:设备树(DTS)的起源、作用与在Linux内核中的位置
1.1 设备树的起源:从“硬编码”到“描述性”的演进
在早期的 Linux 内核(如 ARM 架构)中,板级硬件信息(内存基址、外设地址、中断号、时钟频率等)被直接硬编码在 arch/arm/mach-xxx 目录下的 C 语言板级文件中。这种方式的弊端随着 SoC 和板卡种类的爆炸式增长而愈发严重:
- 代码冗余:每增加一块新板卡,就需要新增或修改大量 C 代码。
- 维护困难:内核版本升级时,板级文件需要同步适配,极易引入回归。
- 耦合度高:驱动代码与硬件配置信息深度绑定,无法实现“一次编译,多板运行”。
为了解决上述问题,PowerPC 社区率先引入了 Open Firmware 设备树(Device Tree) 的概念,随后被 ARM、RISC-V、x86 等架构广泛采纳。设备树的核心思想是:将硬件描述从内核源码中剥离出来,形成一份独立的数据结构文件,内核在启动时解析该文件,动态构建硬件拓扑。
关键转折点: Linux 3.x 内核开始强制要求 ARM 架构使用设备树,板级文件(board-*.c)被逐步废弃。这一决策使得 Linux 内核的硬件适配方式发生了根本性变革。
1.2 设备树的作用:硬件拓扑的“静态数据库”
设备树在 Linux 系统中扮演着 硬件描述语言(HDL) 的角色,其核心作用可归纳为以下四点:
| 作用维度 | 具体说明 | 典型示例 |
|---|---|---|
| 硬件拓扑描述 | 定义 CPU、内存、总线、外设的层级连接关系 | cpu@0 → soc → serial@ff000000 |
| 资源属性配置 | 提供地址、中断、时钟、DMA、GPIO 等资源编号 | reg = <0xff000000 0x1000> |
| 驱动匹配标识 | 通过 compatible 属性将设备节点与驱动绑定 |
compatible = "fsl,imx6q-uart", "ns16550" |
| 运行时参数传递 | 向内核传递启动参数、内存布局、板级定制信息 | chosen { bootargs = "console=ttymxc0,115200"; }; |
理解要点: 设备树是 静态的——它在内核启动时被一次性解析,之后不再动态变化。这与 ACPI(高级配置与电源管理接口)的运行时动态枚举机制有本质区别。
1.3 设备树在 Linux 内核中的位置:从源文件到二进制 Blob
设备树在 Linux 内核源码树中占据明确的物理路径和编译流程位置。下图展示了从 .dts 源文件到内核使用的 .dtb 二进制文件的完整链路:
1.4 设备树相关文件在内核源码树中的分布
Linux 内核源码中,设备树相关文件主要分布在以下目录:
| 路径 | 内容说明 | 典型文件 |
|---|---|---|
scripts/dtc/ |
DTC 编译器源码及 libfdt 库 | dtc.c, fdt.c, dtc-lexer.l |
arch/arm/boot/dts/ |
ARM 架构板级 .dts / .dtsi 文件 | imx6q-sabresd.dts, sun8i-h3.dtsi |
arch/arm64/boot/dts/ |
ARM64 架构板级 .dts / .dtsi 文件 | rk3399.dtsi, mt8183.dtsi |
arch/riscv/boot/dts/ |
RISC-V 架构板级 .dts / .dtsi 文件 | sifive-hifive-unleashed.dts |
include/dt-bindings/ |
设备树绑定头文件(宏定义、中断类型等) | interrupt-controller/irq.h, clock/imx6qdl-clock.h |
Documentation/devicetree/bindings/ |
设备树绑定规范文档(YAML 格式) | serial/fsl-imx-uart.yaml |
1.5 设备树在内核启动流程中的角色
设备树并非内核启动的“可选配件”,而是现代 ARM/RISC-V 内核启动的 核心输入参数。其在内核启动阶段的参与流程如下:
- Bootloader 阶段:U-Boot 从存储介质(eMMC、SD 卡、Flash)读取
.dtb文件到内存,并在跳转到内核前将 DTB 物理地址写入r2寄存器(ARM 约定)。 - 早期解析阶段:内核入口函数
start_kernel()调用setup_arch(),其中unflatten_device_tree()将扁平 DTB 展开为内核可遍历的树形结构。 - 驱动匹配阶段:总线驱动(如 platform bus)在注册设备时,通过
of_match_table匹配设备树节点中的compatible属性,完成驱动与设备的绑定。 - 资源获取阶段:驱动通过
of_*API(如of_iomap()、irq_of_parse_and_map())从设备树节点中读取reg、interrupts等属性,完成硬件初始化。
常见误区: 设备树不是设备驱动,也不是内核模块。它只是硬件配置信息的“静态快照”。驱动代码仍然需要编写,但驱动不再关心具体板级细节——这些细节全部由设备树提供。
1.6 设备树的核心语法结构速览
一个典型的设备树文件由以下基本元素构成:
/dts-v1/;
/ {
model = "Freescale i.MX6 Quad SABRE Smart Device Board";
compatible = "fsl,imx6q-sabresd", "fsl,imx6q";
memory@10000000 {
device_type = "memory";
reg = <0x10000000 0x40000000>;
};
chosen {
bootargs = "console=ttymxc0,115200 root=/dev/mmcblk0p2";
};
soc {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
ranges;
uart1: serial@02020000 {
compatible = "fsl,imx6q-uart", "fsl,imx21-uart";
reg = <0x02020000 0x4000>;
interrupts = <0 26 4>;
clocks = <&clks 32>;
status = "okay";
};
};
};
上述代码展示了设备树的几个关键语法特征:
- 根节点
/:整个设备树的起点,包含model和compatible属性。 - 节点命名:格式为
node-name@unit-address,如serial@02020000。 - 属性:键值对形式,值可以是字符串、32 位整数数组(
<>)、字节数组([])等。 - 标签(Label):如
uart1:,用于在.dtsi中引用或覆盖节点。 - 引用:
&clks表示引用其他节点(通常定义在.dtsi中)。