1. 设备树概述:设备树(DTS)的起源、作用与基本概念

1.1 为什么需要设备树?——从“硬编码”到“数据驱动”的演进

在早期的 Linux 内核中,每块开发板或 SoC 的硬件信息(内存基址、外设寄存器地址、中断号、时钟频率等)都被直接硬编码在 C 语言源文件中。这种“板级文件”方式导致内核源码充斥着大量平台相关的 arch/arm/mach-xxx 代码,每增加一块新板卡就需要修改内核源码并重新编译,严重阻碍了内核的通用性和维护效率。

痛点总结:
  • 代码膨胀:同一份内核需要为每块板卡维护独立的板级文件。
  • 耦合度高:硬件描述与内核驱动逻辑深度绑定,修改硬件必须修改内核。
  • 启动僵化:Bootloader 无法向内核传递灵活的硬件拓扑信息。

设备树(Device Tree)正是为解决上述问题而生。它将硬件描述从内核源码中剥离,形成一份独立的数据结构文件(.dts),由 Bootloader 传递给内核。内核只需解析这份数据,即可动态获知硬件布局,实现“一套内核,多板通用”。

核心思想:设备树是“硬件配置数据”,而非“硬件驱动代码”。驱动代码仍然在内核中,但硬件拓扑、地址、中断等参数由设备树提供。

1.2 设备树的起源与历史

时间 事件 影响
2005 年 PowerPC 架构率先引入设备树(Open Firmware 衍生) 解决了 PowerPC 平台板级文件泛滥问题
2011 年 ARM Linux 社区开始强制要求使用设备树 Linux 3.x 系列内核逐步淘汰 ARM 板级文件
2012 年 设备树成为 ARM 架构的唯一硬件描述方式 所有新 ARM SoC 必须提供 .dts 文件
至今 RISC-V、X86(ACPI 替代)、MIPS 等架构广泛采用 设备树成为嵌入式 Linux 的事实标准

设备树的规范源自 Open Firmware(IEEE 1275),但 Linux 社区对其进行了精简和扩展,形成了当前广泛使用的 Flattened Device Tree (FDT) 格式。

1.3 设备树的核心概念与文件体系

设备树涉及三种主要文件类型,它们之间的关系可以用下图清晰表达:

设备树文件体系与编译流程 设备树文件体系与编译流程 DTS 源文件 可读的文本描述 DTSI 头文件 公共 SoC 定义 DTC 编译器 dtc 工具 DTB 二进制 内核解析的 blob 反编译 DTS 调试/逆向用 流程:DTS + DTSI → DTC 编译 → DTB → Bootloader 加载 → 内核解析
  • .dts (Device Tree Source): 设备树源文件,文本格式,描述具体板卡的硬件树结构。
  • .dtsi (Device Tree Source Include): 设备树头文件,通常存放 SoC 公共定义,被 .dts 通过 #include 引用。
  • .dtb (Device Tree Blob): 设备树二进制文件,由 DTC 编译 .dts 生成,是内核实际加载的格式。
  • DTC (Device Tree Compiler): 设备树编译器,负责将 .dts 编译为 .dtb,也支持从 .dtb 反编译为 .dts。

1.4 设备树的基本语法结构

一个典型的 .dts 文件采用树形层次结构,根节点 / 下包含若干子节点,每个节点描述一个硬件设备或总线。以下是一个精简示例:

/dts-v1/;

#include "imx6ull.dtsi"

/ {
    model = "Embedded Linux Board v1.0";
    compatible = "vendor,board-name";

    chosen {
        stdout-path = &uart1;
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x20000000>;  /* 512 MB */
    };

    &uart1 {
        status = "okay";
        pinctrl-0 = <&pinctrl_uart1>;
    };
};
语法元素 说明 示例
/dts-v1/; 版本标识,必须位于文件开头 表示使用设备树 v1 规范
/ { }; 根节点,所有设备树的起点 包含 model、compatible 等属性
node-name@unit-address 节点名 + 单元地址(可选) memory@80000000
property = value; 属性赋值,描述设备参数 reg = <0x80000000 0x20000000>;
&label { }; 引用已定义的节点并追加/覆盖属性 &uart1 { status = "okay"; };
注意:设备树不是编程语言,它没有循环、条件分支或函数调用。它纯粹是描述性数据,所有逻辑处理由内核驱动完成。

1.5 设备树的作用与优势

设备树在嵌入式 Linux 启动流程中扮演着关键角色,其核心作用可以归纳为以下四点:

  1. 硬件拓扑描述:向内核传递 CPU、内存、总线、外设的层次关系。
  2. 资源参数配置:提供寄存器地址、中断号、DMA 通道、时钟频率等驱动所需参数。
  3. 板级差异隔离:同一 SoC 的不同板卡只需更换 .dts 文件,无需修改内核源码。
  4. Bootloader 与内核的契约:U-Boot 等 Bootloader 加载 DTB 到内存,并将地址通过寄存器传递给内核。
设备树在启动流程中的位置 设备树在嵌入式 Linux 启动流程中的位置 Boot ROM 固化启动代码 Bootloader U-Boot 加载 DTB Linux 内核 解析 DTB 枚举设备 设备驱动 匹配 compatible 加载 关键点:Bootloader 负责将 DTB 放入内存,内核启动早期通过 FDT 解析器读取设备树 驱动通过 compatible 属性与设备节点匹配,实现硬件与驱动的解耦

1.6 设备树与传统板级文件的对比

对比维度 传统板级文件(Board File) 设备树(Device Tree)
描述方式 C 语言结构体 + 宏定义 树形文本文件(.dts)
修改成本 需修改内核源码,重新编译内核 仅修改 .dts,重新编译 DTB,无需重编内核
可移植性 差,每块板卡需要独立内核镜像 强,同一内核镜像可适配多块板卡
维护性 内核源码膨胀,社区维护困难 硬件描述集中,社区协作高效
启动时传递 Bootloader 通过 ATAG 传递少量参数 Bootloader 传递完整 DTB 二进制
实践建议:在开发新板卡时,优先查找同系列 SoC 的参考 .dts 文件(位于内核源码 arch/arm/boot/dts/ 或 arch/arm64/boot/dts/),在其基础上修改,而不是从零编写。

1.7 本章小结