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

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

在早期 Linux 内核(如 ARM 架构)中,每块开发板或 SoC 的硬件信息(内存基址、中断号、时钟频率、外设寄存器地址等)都直接硬编码在 arch/arm/mach-xxx 下的 C 语言板级文件中。这种方式的弊端随着设备种类爆炸式增长而暴露:

对比维度 传统板级文件方式 设备树方式
硬件描述位置 分散在多个 board-*.c 和 mach-*.c 中 集中在一个 .dts 源文件中
内核与硬件耦合度 高耦合,换板子必须重新编译内核 低耦合,同一内核镜像可启动不同板子
维护成本 每增加一款 SoC 或板卡,需新增大量 C 代码 只需编写或修改 .dts 文件,无需改动内核源码
启动流程 Bootloader 传递 ATAG 或 machine_type 数字 Bootloader 加载 .dtb 二进制并传递给内核
核心痛点: ARM 社区在 Linux 3.x 时代面临“每块板子一个内核”的碎片化危机。设备树(Device Tree, DT)正是为了解决这一危机而引入的标准化硬件描述机制。

1.2 设备树的起源与标准化历程

  • 起源: 设备树的概念最早源于 Open Firmware(IEEE 1275),用于 PowerPC 和 SPARC 架构。
  • 引入 Linux: 2011 年,Linux 3.0 开始正式在 ARM 架构中推广设备树;2012 年 Linux 3.7 起,ARM 平台强制要求使用设备树。
  • 标准化组织: devicetree-specification(由 Linaro 和内核社区维护)定义了 .dts 语法、.dtb 二进制格式以及绑定规则。
  • 当前状态: 设备树已成为 ARM、RISC-V、MIPS、X86(部分)等架构的标准硬件描述方式。

1.3 设备树的基本概念与核心文件

设备树由三个层级的文件组成,形成从“SoC 通用描述”到“具体板卡描述”的继承关系:

文件类型 扩展名 作用 示例
设备树源文件 .dts 描述具体板卡的完整硬件拓扑 imx6ull-evk.dts
设备树头文件 .dtsi 描述 SoC 公共部分(CPU、中断控制器、时钟等),可被多个 .dts 包含 imx6ull.dtsi
设备树二进制 .dtb 编译后的二进制文件,由 Bootloader 加载并传递给内核 imx6ull-evk.dtb
类比理解: .dtsi 相当于 C 语言的 .h 头文件,定义 SoC 的“公共硬件接口”;.dts 相当于 .c 源文件,描述“这块板子具体用了哪些外设、接在哪个引脚上”。

1.4 设备树的核心作用——硬件描述与解耦

设备树在系统启动流程中扮演“硬件配置清单”的角色,其核心作用可归纳为:

  1. 描述硬件拓扑: 以树形结构描述 CPU、内存、总线、外设、中断、GPIO、时钟等资源。
  2. 传递配置参数: 将板级差异(如内存大小、MAC 地址、引脚复用)从内核源码中剥离,放入 .dts。
  3. 驱动匹配: 内核通过 compatible 属性将设备节点与驱动进行匹配,实现“设备即插即用”。
  4. 减少内核分支: 同一内核镜像通过更换 .dtb 即可支持不同硬件,大幅降低维护成本。
设备树在系统启动流程中的位置与作用 设备树在系统启动流程中的位置与作用 Bootloader 加载 .dtb 内核解析 DTB 驱动匹配 设备树(.dtb)是 Bootloader 与内核之间的“硬件配置契约” 内核不再硬编码硬件信息,而是从 DTB 中动态获取内存基址、中断号、外设寄存器等 硬件拓扑描述 板级参数传递 驱动匹配解耦

1.5 设备树的基本语法结构预览

一个典型的 .dts 文件以树形结构组织节点,每个节点包含属性(键值对)。以下是一个简化的示例:

/dts-v1/;

/ {
    model = "Embedded Device Tree Demo Board";
    compatible = "vendor,board-rev1", "vendor,board";

    #address-cells = <1>;
    #size-cells = <1>;

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x10000000>;  /* 256MB 内存 */
    };

    soc {
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;

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

        gpio0: gpio@10001000 {
            compatible = "vendor,gpio-ctrl";
            reg = <0x10001000 0x100>;
            gpio-controller;
            #gpio-cells = <2>;
            interrupts = <0 34 4>;
        };
    };
};
常见误区: 初学者容易混淆 reg 属性中的地址与大小。注意 #address-cells 和 #size-cells 决定了 reg 中每个地址/大小占用的 32 位单元数。例如 #address-cells = <2> 表示地址用两个 32 位整数表示(64 位地址)。

1.6 设备树的关键概念速查表

概念 说明 示例
节点(Node) 树形结构中的硬件单元,用 { } 包裹 uart0: serial@10000000 { ... };
属性(Property) 节点的配置参数,键值对形式 compatible = "vendor,uart-ip";
compatible 驱动匹配的关键字符串,格式为 "厂商,设备型号" "ti,omap3-uart"
reg 描述设备地址空间(基址 + 长度) reg = <0x10000000 0x1000>;
interrupts 描述中断号、触发类型 interrupts = <0 33 4>;
status 设备启用状态:"okay" 或 "disabled" status = "okay";
标签(Label) 节点别名,方便在其他地方引用 uart0: serial@10000000
设备树节点与属性的树形结构示意 设备树节点与属性的树形结构示意 / (根节点) memory@80000000 soc chosen serial@10000000 gpio@10001000 每个节点可包含属性:compatible、reg、interrupts、status、clock-frequency 等 标签(如 uart0:)用于在其他节点中通过 &uart0 引用该节点

1.7 本章小结

  • 设备树是 Linux 内核的硬件描述语言,将板级差异从内核源码中剥离,实现“一份内核,多板适配”。
  • 核心文件链: .dtsi(SoC 公共描述)→ .dts(板级描述)→ .dtb(二进制,Bootloader 加载)。
  • 关键属性: compatible(驱动匹配)、reg(地址空间)、interrupts(中断)、status(启用状态)。
  • 学习路径: 理解设备树是后续学习 .dts 编译、.dtb 生成、驱动开发以及错误排查的基础。