1. 设备树概述:设备树(DTS)的起源、为什么需要设备树、设备树在嵌入式Linux中的角色
1.1 设备树的起源:从“硬编码”到“描述”的进化
在早期的嵌入式 Linux 系统中,内核通过大量 板级文件 (Board File) 来静态描述硬件信息。每个开发板都需要在 arch/arm/mach-xxx 目录下编写一个 board-xxx.c 文件,其中充斥着对寄存器地址、中断号、GPIO 引脚、时钟频率等参数的硬编码。
这种模式带来了严重的维护灾难:
- 代码冗余:同一款 SoC 的不同板卡,90% 的硬件描述代码重复。
- 耦合过高:硬件变更必须重新编译内核,无法实现“一次编译,多板运行”。
- 社区分裂:大量板级补丁涌入主线内核,导致 ARM Linux 社区维护成本急剧上升。
为了解决这一困境,Linux 内核社区在 2011 年(Linux 3.x 时代)正式引入了 设备树 (Device Tree) 机制。设备树的核心思想是:将硬件描述从内核代码中剥离出来,形成一个独立的数据结构文件。内核启动时解析该文件,动态获取硬件拓扑和资源分配信息,从而彻底告别硬编码。
1.2 为什么需要设备树?—— 三大核心驱动力
设备树的引入并非偶然,它解决了嵌入式 Linux 开发中三个最根本的痛点:
| 痛点 | 传统板级文件时代 | 设备树时代 |
|---|---|---|
| 硬件变更成本 | 修改硬件 → 修改 C 代码 → 重新编译内核 → 烧录整个镜像 | 修改 DTS → 重新编译 DTB → 替换设备树二进制文件即可 |
| 内核与板卡耦合 | 一个内核镜像只支持一种板卡,不同板卡需要不同内核 | 同一内核镜像通过加载不同 DTB 支持多种板卡 |
| 驱动复用性 | 驱动中混杂板级信息,移植到新板卡需大量修改 | 驱动只关心设备类型,板级信息由 DTS 提供,驱动完全通用 |
除了上述三点,设备树还带来了以下关键收益:
- 标准化硬件描述语言:DTS 语法简洁、可读性强,硬件工程师也能直接参与编写。
- 支持设备树覆盖 (Overlay):运行时动态添加或修改设备节点,非常适合模块化硬件设计。
- 与 U-Boot 无缝配合:Bootloader 负责加载 DTB 并传递给内核,形成完整的启动链路。
1.3 设备树在嵌入式Linux中的角色
设备树在整个嵌入式 Linux 系统中扮演着 “硬件注册表” 和 “资源地图” 的双重角色。它位于 Bootloader 与内核之间,是系统启动过程中硬件信息传递的唯一官方通道。
下图展示了设备树在嵌入式 Linux 启动流程中的核心位置:
从图中可以清晰看到设备树的完整生命周期:
- 编写阶段:硬件工程师或驱动开发者根据电路原理图编写
.dts或.dtsi源文件。 - 编译阶段:使用 DTC (Device Tree Compiler) 将源文件编译为二进制
.dtb文件。 - 加载阶段:Bootloader (如 U-Boot) 在启动内核前,将 DTB 从存储介质 (eMMC/NAND/SD) 加载到内存中,并将 DTB 地址传递给内核。
- 解析阶段:Linux 内核在启动早期解析 DTB,遍历所有节点,为每个匹配的节点创建设备对象,并调用对应的驱动进行初始化。
1.4 设备树的核心文件体系
设备树源文件由两种文件类型组成,它们共同构成了完整的硬件描述体系:
| 文件类型 | 扩展名 | 作用 | 示例 |
|---|---|---|---|
| 设备树源文件 | .dts |
描述一个具体板卡的完整硬件拓扑,是编译的入口文件 | imx6ull-myboard.dts |
| 设备树头文件 | .dtsi |
描述 SoC 公共部分或模块通用配置,通过 #include 被 .dts 引用 |
imx6ull.dtsi |
.dtsi 当作可独立编译的文件。实际上,.dtsi 必须被 .dts 包含才能参与编译,它本身不是编译单元。
下图展示了 .dts 与 .dtsi 之间的引用关系:
1.5 设备树在驱动模型中的角色
在 Linux 内核的 平台驱动模型 (Platform Driver Model) 中,设备树起到了“媒人”的作用。内核通过设备树节点中的 compatible 属性与驱动进行匹配:
// DTS 中的设备节点示例
&uart1 {
compatible = "fsl,imx6ul-uart", "fsl,imx6q-uart";
reg = <0x02020000 0x4000>;
interrupts = <0 26 IRQ_TYPE_LEVEL_HIGH>;
status = "okay";
};
// 内核驱动中的匹配表
static const struct of_device_id imx_uart_dt_ids[] = {
{ .compatible = "fsl,imx6q-uart", },
{ .compatible = "fsl,imx6ul-uart", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_uart_dt_ids);
当内核启动时,会遍历 DTB 中的所有节点,对于每个节点,内核会查找驱动中注册的 of_device_id 匹配表。一旦 compatible 属性匹配成功,内核就会调用驱动的 probe() 函数,并将节点中的 reg、interrupts 等资源信息传递给驱动。
1.6 本章小结
设备树是嵌入式 Linux 开发中不可或缺的基础设施。通过本章的学习,你应该掌握以下要点:
- 设备树诞生的历史背景:解决板级文件硬编码带来的维护灾难。
- 设备树的三大价值:降低硬件变更成本、解耦内核与板卡、提升驱动复用性。
- 设备树在启动流程中的角色:从 DTS → DTC → DTB → Bootloader → 内核的完整链路。
- 文件体系:
.dts是板级入口,.dtsi是 SoC 公共描述,通过#include组合。 - 驱动匹配机制:
compatible属性是设备与驱动之间的“握手协议”。