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) 机制。设备树的核心思想是:将硬件描述从内核代码中剥离出来,形成一个独立的数据结构文件。内核启动时解析该文件,动态获取硬件拓扑和资源分配信息,从而彻底告别硬编码。

里程碑事件:2012 年,Linux 内核主线要求所有新的 ARM 平台必须使用设备树,否则不予合入。自此,设备树成为嵌入式 Linux 的标配。

1.2 为什么需要设备树?—— 三大核心驱动力

设备树的引入并非偶然,它解决了嵌入式 Linux 开发中三个最根本的痛点:

痛点 传统板级文件时代 设备树时代
硬件变更成本 修改硬件 → 修改 C 代码 → 重新编译内核 → 烧录整个镜像 修改 DTS → 重新编译 DTB → 替换设备树二进制文件即可
内核与板卡耦合 一个内核镜像只支持一种板卡,不同板卡需要不同内核 同一内核镜像通过加载不同 DTB 支持多种板卡
驱动复用性 驱动中混杂板级信息,移植到新板卡需大量修改 驱动只关心设备类型,板级信息由 DTS 提供,驱动完全通用

除了上述三点,设备树还带来了以下关键收益:

  • 标准化硬件描述语言:DTS 语法简洁、可读性强,硬件工程师也能直接参与编写。
  • 支持设备树覆盖 (Overlay):运行时动态添加或修改设备节点,非常适合模块化硬件设计。
  • 与 U-Boot 无缝配合:Bootloader 负责加载 DTB 并传递给内核,形成完整的启动链路。
一句话总结:设备树让嵌入式 Linux 从“面向板卡编程”进化到“面向设备描述编程”,实现了硬件描述与内核代码的彻底解耦。

1.3 设备树在嵌入式Linux中的角色

设备树在整个嵌入式 Linux 系统中扮演着 “硬件注册表” 和 “资源地图” 的双重角色。它位于 Bootloader 与内核之间,是系统启动过程中硬件信息传递的唯一官方通道。

下图展示了设备树在嵌入式 Linux 启动流程中的核心位置:

设备树在嵌入式 Linux 启动流程中的角色 硬件电路 SoC / 外设 / 内存 DTS 源文件 .dts / .dtsi DTC 编译器 设备树编译器 DTB 二进制 扁平设备树 Bootloader U-Boot / Barebox Linux 内核 驱动匹配 / 资源注册 关键流程 ① 硬件设计 → DTS 编写 ② DTC 编译 → DTB 生成 ③ Bootloader 加载 DTB ④ 内核解析 DTB → 驱动匹配

从图中可以清晰看到设备树的完整生命周期:

  1. 编写阶段:硬件工程师或驱动开发者根据电路原理图编写 .dts 或 .dtsi 源文件。
  2. 编译阶段:使用 DTC (Device Tree Compiler) 将源文件编译为二进制 .dtb 文件。
  3. 加载阶段:Bootloader (如 U-Boot) 在启动内核前,将 DTB 从存储介质 (eMMC/NAND/SD) 加载到内存中,并将 DTB 地址传递给内核。
  4. 解析阶段:Linux 内核在启动早期解析 DTB,遍历所有节点,为每个匹配的节点创建设备对象,并调用对应的驱动进行初始化。

1.4 设备树的核心文件体系

设备树源文件由两种文件类型组成,它们共同构成了完整的硬件描述体系:

文件类型 扩展名 作用 示例
设备树源文件 .dts 描述一个具体板卡的完整硬件拓扑,是编译的入口文件 imx6ull-myboard.dts
设备树头文件 .dtsi 描述 SoC 公共部分或模块通用配置,通过 #include 被 .dts 引用 imx6ull.dtsi
常见误区:初学者常把 .dtsi 当作可独立编译的文件。实际上,.dtsi 必须被 .dts 包含才能参与编译,它本身不是编译单元。

下图展示了 .dts 与 .dtsi 之间的引用关系:

DTS 与 DTSI 的引用关系 myboard.dts 板级特有硬件描述 soc.dtsi SoC 公共外设定义 pinctrl.dtsi 引脚复用配置 display.dtsi 显示接口参数 #include "soc.dtsi" / #include "pinctrl.dtsi" / #include "display.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 等资源信息传递给驱动。

核心思想:驱动不再关心“我在哪块板卡上”,只关心“我匹配哪个 compatible 值”。板级差异全部由 DTS 文件隔离,驱动实现真正的跨平台复用。

1.6 本章小结

设备树是嵌入式 Linux 开发中不可或缺的基础设施。通过本章的学习,你应该掌握以下要点:

  • 设备树诞生的历史背景:解决板级文件硬编码带来的维护灾难。
  • 设备树的三大价值:降低硬件变更成本、解耦内核与板卡、提升驱动复用性。
  • 设备树在启动流程中的角色:从 DTS → DTC → DTB → Bootloader → 内核的完整链路。
  • 文件体系:.dts 是板级入口,.dtsi 是 SoC 公共描述,通过 #include 组合。
  • 驱动匹配机制:compatible 属性是设备与驱动之间的“握手协议”。