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

1.1 为什么 Linux 内核需要设备树?

在早期的 Linux 内核(2.6 及之前版本)中,嵌入式平台的硬件描述信息被直接硬编码在 arch/arm/ 或 arch/powerpc/ 等目录下的 C 语言源文件中。这种“硬编码”方式带来了严重的维护问题:

  • 代码膨胀:每增加一款开发板,就需要新增或修改大量 board-*.c 文件。
  • 内核与硬件耦合:更换外设或调整内存映射必须重新编译内核。
  • 碎片化:不同厂商的板级代码风格各异,社区难以统一维护。

为了解决上述痛点,Linux 社区从 PowerPC 架构引入并推广了 设备树(Device Tree) 机制。设备树将硬件描述从内核源码中剥离,形成一份独立的数据结构,由 bootloader 传递给内核。内核在启动时解析设备树,动态识别硬件拓扑、寄存器地址、中断号、时钟频率等关键信息。

核心思想:“描述硬件,而非编写硬件代码”。设备树让同一份内核镜像可以支持多种硬件平台,只需更换不同的 .dts 文件即可。

1.2 设备树的起源与发展

时间 里程碑 说明
2005 年 PowerPC 率先采用 Open Firmware 的 FDT(Flattened Device Tree)概念被引入 Linux PowerPC 架构。
2011 年 ARM 架构开始迁移 Linux 3.x 系列内核中,ARM 社区启动“设备树化”改造,逐步淘汰 board 文件。
2014 年 ARM64 强制要求 从 Linux 3.19 开始,ARM64(AArch64)平台必须使用设备树启动。
至今 全架构普及 RISC-V、MIPS、X86(ACPI 为主)等架构也支持或兼容设备树。

1.3 设备树的基本概念

设备树由三个层次的文件组成,它们共同描述硬件:

  • .dts(Device Tree Source):设备树源文件,人类可读的文本格式,使用 C 风格的预处理器宏。
  • .dtsi(Device Tree Source Include):设备树头文件/包含文件,通常存放 SoC 公共硬件描述,可被多个 .dts 引用。
  • .dtb(Device Tree Blob):设备树二进制文件,由 dtc(Device Tree Compiler)编译 .dts 生成,是内核实际解析的格式。
编译流程:
.dts + .dtsi → dtc 编译器 → .dtb → bootloader 加载 → 内核解析

1.4 设备树的核心数据结构

设备树在内存中以“扁平化”的树形结构存在,每个节点代表一个硬件设备或总线。关键数据结构包括:

概念 语法表示 作用
节点(Node) device_name@address { }; 描述一个硬件设备或总线控制器。
属性(Property) reg = <0x10000000 0x1000>; 描述设备的寄存器地址、中断号、时钟等参数。
兼容性(compatible) compatible = "vendor,device"; 用于内核匹配驱动,是设备树与驱动绑定的关键。
中断(interrupts) interrupts = <0 29 4>; 指定设备使用的中断控制器、中断号和触发类型。

1.5 设备树与 Linux 驱动的绑定关系

设备树是驱动与硬件之间的“桥梁”。驱动通过 of_match_table 匹配设备树中的 compatible 属性,从而获得硬件资源:

// 驱动侧:定义匹配表
static const struct of_device_id my_driver_of_match[] = {
    { .compatible = "vendor,my-device", },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_driver_of_match);

// 驱动侧:在 probe 中读取设备树资源
static int my_probe(struct platform_device *pdev) {
    struct resource *res;
    void __iomem *base;

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    base = devm_ioremap_resource(&pdev->dev, res);
    // ... 初始化硬件
    return 0;
}
注意:设备树只描述硬件“有什么”和“在哪里”,不描述硬件“怎么用”。驱动仍然需要实现具体的寄存器操作逻辑。

1.6 设备树整体架构图

设备树整体架构图 设备树整体架构图 .dts 源文件 .dtsi 包含文件 .dts 板级覆盖 dtc 编译器 .dtb 二进制文件 Bootloader(U-Boot 等) Linux 内核(解析 DTB,创建设备) .dts 描述具体板级硬件 .dtsi 描述 SoC 公共部分 板级覆盖文件 可修改或扩展 .dtsi

1.7 设备树带来的核心价值

维度 传统硬编码方式 设备树方式
硬件变更 需修改 C 代码,重新编译内核 只需修改 .dts,重新编译 DTB
内核镜像 每个板子一个专属镜像 同一镜像支持多板,通过 DTB 区分
社区维护 board 文件大量重复,难以合并 设备树文件独立,易于审查和复用
驱动开发 驱动需硬编码地址和中断号 驱动通过 API 读取设备树,自动适配

1.8 一个简单的设备树示例

// 文件:myboard.dts
/dts-v1/;

#include "soc-common.dtsi"

/ {
    model = "MyBoard v1.0";
    compatible = "vendor,myboard", "vendor,soc";

    chosen {
        stdout-path = &uart0;
    };

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

    uart0: serial@10000000 {
        compatible = "vendor,uart";
        reg = <0x10000000 0x1000>;
        interrupts = <0 33 4>;
        clock-frequency = <24000000>;
    };

    gpio0: gpio@10001000 {
        compatible = "vendor,gpio";
        reg = <0x10001000 0x100>;
        interrupts = <0 34 4>;
        gpio-controller;
        #gpio-cells = <2>;
    };
};
关键点解析:
  • /dts-v1/; 声明设备树版本,必须位于文件开头。
  • #include "soc-common.dtsi" 引入 SoC 公共描述,避免重复。
  • compatible 属性是驱动匹配的核心标识,格式通常为 "厂商,设备型号"。
  • reg 属性描述地址空间,格式为 <起始地址 长度>。
  • interrupts 属性描述中断控制器、中断号和触发类型。

1.9 设备树在 Linux 启动流程中的位置

设备树在 Linux 启动流程中的位置 设备树在 Linux 启动流程中的位置 Bootloader 加载 DTB 到内存 传递 DTB 地址 内核解压与初始化 解析 DTB 创建设备 驱动匹配与 probe DTB 地址通过 r2 寄存器或 设备树约定 方式传递

1.10 本章小结

  • 设备树解决了 Linux 内核硬件描述与代码耦合的历史问题,是嵌入式 Linux 开发的基石。
  • 设备树由 .dts / .dtsi 源文件通过 dtc 编译为 .dtb 二进制,由 bootloader 加载并传递给内核。
  • 设备树的核心是“节点 + 属性”的树形结构,compatible 属性是驱动匹配的关键。
  • 掌握设备树语法和绑定规则,是编写高质量 Linux 驱动的必备技能。