1. 设备树基础:起源、作用与基本概念
1.1 为什么需要设备树?——从“硬编码”到“数据驱动”
在早期 Linux 内核(2.6 及更早版本)中,嵌入式板级支持包(BSP)通常采用 “硬编码” 方式描述硬件:每个板子对应一个 board-xxx.c 文件,其中包含大量 platform_device 结构体、ioremap 地址、中断号、GPIO 号等。这种模式存在严重问题:
- 代码膨胀:每增加一块新板卡,内核源码树就增加数千行板级代码。
- 维护灾难:同一 SoC 的不同板卡,硬件资源描述大量重复,修改一处可能影响多板。
- 内核与硬件耦合:更换外设或调整内存映射必须重新编译内核。
- 无法通用:一个内核镜像无法同时支持多个硬件平台。
核心痛点: 硬件描述与内核代码深度绑定,导致嵌入式 Linux 的移植成本极高,社区难以维护。
设备树(Device Tree,DT)正是为解决上述问题而生。它将硬件资源描述从内核源码中剥离,形成一份独立的数据结构文件(.dts),由 bootloader 传递给内核。内核在启动时解析该结构,动态注册平台设备、分配资源、匹配驱动。从此,“一份内核镜像,多块板卡共用”成为可能。
1.2 设备树的起源与演进
| 时间 | 事件 | 影响 |
|---|---|---|
| 2005 | PowerPC 架构率先引入设备树(源于 Open Firmware) | 解决 PowerPC 平台板级代码混乱问题 |
| 2011 | ARM Linux 社区开始大规模采用设备树(Linux 3.0~3.4) | ARM 板级代码从 arch/arm/mach-* 逐步迁移到 .dts 文件 |
| 2012 | 设备树编译器(DTC)成为内核标准工具 | .dts → .dtb 编译流程标准化 |
| 2014+ | 设备树覆盖 ARM64、RISC-V、X86(ACPI 替代)、MIPS 等架构 | 成为嵌入式 Linux 硬件描述的事实标准 |
关键转折: 2011~2012 年,ARM 社区提交了“Device Tree for ARM”系列补丁,标志着嵌入式 Linux 从“代码描述硬件”正式进入“数据描述硬件”时代。
1.3 设备树的核心概念
设备树本质上是一种 树形数据结构,用于描述硬件平台中所有可寻址的设备及其连接关系。其核心要素包括:
- 节点(Node):树中的每个元素,代表一个硬件设备或总线。节点名称通常为
device-name@address格式。 - 属性(Property):节点的键值对,描述设备的特性,如
reg(地址/大小)、interrupts(中断号)、compatible(驱动匹配字符串)。 - 根节点(Root Node):树的起点,通常为
/,包含model、compatible等全局属性。 - 标签(Label):为节点起别名,便于在
.dtsi文件中引用或覆盖。 - 包含文件(.dtsi):SoC 级通用描述文件,被具体板级
.dts文件#include,实现复用。
// 一个简化的设备树片段示例
/dts-v1/;
/ {
model = "TI AM335x EVM";
compatible = "ti,am335x-evm", "ti,am335x";
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a8";
reg = <0x0>;
};
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>; // 256MB 内存
};
uart0: serial@44e09000 {
compatible = "ti,am3352-uart", "ti,omap3-uart";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
status = "okay";
};
};
1.4 设备树的工作流程
从源码到内核识别设备,设备树经历以下阶段:
- 编写:开发者编写
.dts源文件,#include对应的.dtsi头文件。 - 编译:设备树编译器(DTC)将
.dts编译为二进制.dtb文件。 - 传递:bootloader(如 U-Boot)将
.dtb加载到内存,并将地址传递给内核。 - 解析:内核启动早期,
unflatten_device_tree()将.dtb展开为内存中的device_node树。 - 注册:内核遍历设备树节点,为每个节点创建
platform_device,并尝试匹配驱动。 - 绑定:驱动通过
of_match_table匹配compatible属性,完成设备初始化。
常见误区: 设备树不是“配置内核”的工具,而是“描述硬件”的数据结构。内核的编译选项(如
CONFIG_*)仍然控制驱动是否编译进内核,设备树只决定哪些设备被激活。
1.5 设备树 vs 传统板级代码:对比一览
| 对比维度 | 传统板级代码(board-*.c) | 设备树(.dts/.dtb) |
|---|---|---|
| 硬件描述位置 | 内核源码 arch/arm/mach-* |
独立 .dts 文件,与内核源码分离 |
| 修改硬件配置 | 需修改 C 代码,重新编译内核 | 仅修改 .dts,重新编译 .dtb,无需重编内核 |
| 多板卡支持 | 每个板卡一个 board-*.c,内核镜像不通用 |
同一内核镜像 + 不同 .dtb 即可支持多板 |
| 驱动匹配方式 | 静态注册 platform_device,或通过 arch_initcall |
动态解析设备树,通过 compatible 属性匹配驱动 |
| 可维护性 | 差:代码重复多,社区难以协作 | 好:SoC 级 .dtsi 复用,板级差异最小化 |
| 调试手段 | 依赖内核日志、printk | 提供 dtc -I dtb -O dts 反编译、/proc/device-tree 运行时查看 |
1.6 设备树在嵌入式系统中的位置
下图展示了设备树在嵌入式 Linux 启动流程中的核心位置:
1.7 设备树带来的核心价值
- 硬件描述与内核解耦:更换板卡只需更换
.dtb文件,内核镜像保持不变。 - 驱动通用化:驱动通过
compatible字符串匹配设备,不再依赖板级宏定义。 - 社区协作效率提升:SoC 厂商提供
.dtsi,板卡厂商只需编写少量板级差异。 - 调试与验证便利:可通过
/proc/device-tree实时查看设备树状态,支持dtc反编译对比。 - 启动参数灵活:通过
chosen节点传递内核启动参数(如bootargs),无需修改内核。
一句话总结: 设备树让嵌入式 Linux 的硬件描述从“写代码”变成了“写数据”,是嵌入式系统走向标准化、通用化的关键基石。