1. 设备树基础概念:设备树(DTS)的起源、作用与基本语法结构

1.1 设备树的起源与设计动机

在早期的 Linux 内核中,板级硬件信息(如内存基址、外设寄存器地址、中断号、GPIO 引脚分配)被硬编码在 arch/arm/mach-xxx 目录下的 C 语言板文件中。这种“代码即配置”的方式带来了严重的维护灾难:

  • 代码膨胀:每增加一块新板卡,就需要新增或修改大量 C 代码。
  • 耦合度高:硬件描述与内核驱动逻辑紧密绑定,无法实现“一次编译,多板运行”。
  • 碎片化严重:厂商的板级补丁难以合入主线,导致内核版本分裂。

为了解决上述问题,PowerPC 社区率先引入 设备树(Device Tree) 机制,随后被 ARM、RISC-V、X86(ACPI 之外)等架构广泛采纳。设备树的核心思想是:将硬件描述从内核源码中剥离,以结构化数据文件的形式传递给内核,由内核在启动时解析并生成平台设备。

关键转变: 从“代码描述硬件” → “数据描述硬件”。设备树使得同一份内核镜像可以支持多种硬件平台,只需更换对应的设备树二进制文件(.dtb)。

1.2 设备树的作用与工作流程

设备树在 Linux 系统启动过程中扮演着“硬件清单”的角色。其完整工作流程如下:

  1. 编写 DTS 源文件:开发者根据硬件原理图,编写 .dts 文件,描述 CPU、内存、外设、中断控制器、GPIO 控制器等硬件节点。
  2. 编译为 DTB:使用 dtc(Device Tree Compiler)将 .dts 编译为二进制 .dtb 文件。
  3. Bootloader 加载:U-Boot 等引导程序将 .dtb 文件加载到内存中,并将地址传递给内核。
  4. 内核解析:Linux 内核在启动早期调用 unflatten_device_tree() 函数,将 DTB 展开为内存中的 设备树结构体(Device Tree Structure)。
  5. 驱动匹配:总线驱动(如 platform bus)遍历设备树节点,根据 compatible 属性匹配对应的驱动,并完成资源(中断、GPIO、内存地址)的映射。
注意: 设备树是静态的硬件描述,不处理热插拔设备(如 USB、PCIe)。热插拔设备通常由 ACPI 或设备树覆盖(overlay)机制处理。
设备树工作流程 DTS 源文件 .dts / .dtsi DTC 编译器 dtc 工具 DTB 二进制 .dtb 文件 Bootloader U-Boot 加载 内核解析 unflatten 驱动匹配 platform bus 关键文件 .dts: 板级设备树 .dtsi: 芯片级包含 .dtb: 编译后二进制 .dtbo: 覆盖层 dtc: 设备树编译器

1.3 设备树的基本语法结构

设备树源文件(.dts)采用一种层次化的、类似 C 语言风格的语法。一个完整的设备树由以下核心元素构成:

语法元素 表示方式 说明
节点(Node) node-name@unit-address 表示一个硬件设备或总线,unit-address 通常为寄存器基址
属性(Property) property-name = value; 描述节点的特性,如寄存器地址、中断号、时钟频率等
根节点(Root Node) / { ... }; 设备树的顶层容器,所有设备节点都挂载在根节点下
标签(Label) label: node-name 为节点定义别名,方便在其他地方引用或覆盖
包含文件 #include "file.dtsi" 引入芯片级或板级通用定义,支持 C 预处理器

1.4 标准属性详解

设备树规范定义了一系列标准属性,用于描述硬件资源的分配与关联。以下是中断控制器和 GPIO 映射中最重要的几个属性:

属性名 值类型 作用描述
compatible 字符串列表 驱动匹配的关键,格式为 "厂商,设备型号",如 "ti,omap3-gpio"
reg 32 位整数对 描述寄存器地址范围,格式为 <基址 长度>
interrupt-parent phandle 指定该设备连接的中断控制器节点引用
interrupts 32 位整数数组 描述中断号、触发类型等,格式由中断控制器定义
gpio-controller 空属性 标记该节点是一个 GPIO 控制器
#gpio-cells 32 位整数 指定引用 GPIO 时需要多少个 cell(通常为 2:引脚号 + 标志)
gpios / cs-gpios phandle + cells 引用外部 GPIO 引脚,如 <&gpio1 15 GPIO_ACTIVE_HIGH>

1.5 完整设备树示例

以下是一个简化的设备树片段,展示了一个 SoC 内部的中断控制器(GIC)和 GPIO 控制器的定义,以及一个外部设备(按键)如何引用这些资源:

/dts-v1/;

#include "soc-common.dtsi"

/ {
    model = "Example Board v1.0";
    compatible = "example,board-v1", "example,board";

    /* 内存节点 */
    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x40000000>;  /* 1GB RAM */
    };

    /* 中断控制器:GIC v2 */
    gic: interrupt-controller@f1001000 {
        compatible = "arm,cortex-a7-gic";
        interrupt-controller;           /* 标记为中断控制器 */
        #interrupt-cells = <3>;        /* 格式: <类型 编号 触发方式> */
        reg = <0xf1001000 0x1000>,
              <0xf1002000 0x2000>;
    };

    /* GPIO 控制器 */
    gpio1: gpio@f2000000 {
        compatible = "snps,dw-apb-gpio";
        reg = <0xf2000000 0x1000>;
        gpio-controller;                /* 标记为 GPIO 控制器 */
        #gpio-cells = <2>;             /* 格式: <引脚号 标志> */
        interrupt-parent = <&gic>;     /* 连接到 GIC */
        interrupts = <0 32 4>;         /* SPI 32, 高电平触发 */
    };

    /* 外部设备:按键,使用 GPIO 并产生中断 */
    button@0 {
        compatible = "gpio-keys";
        interrupt-parent = <&gpio1>;   /* 连接到 GPIO 控制器 */
        interrupts = <15 2>;           /* GPIO1 引脚15, 下降沿触发 */
        gpios = <&gpio1 15 1>;        /* GPIO1 引脚15, 低电平有效 */
        label = "User Button";
        linux,code = <116>;            /* KEY_POWER */
    };
};
常见错误: 初学者容易混淆 interrupt-parent 和 gpios 中的 phandle 引用。前者指向中断控制器节点,后者指向 GPIO 控制器节点。两者虽然都是引用,但语义完全不同:中断是事件信号,GPIO 是通用输入输出引脚。

1.6 设备树节点与硬件设备的映射关系

设备树的层次结构直接反映了 SoC 内部的总线拓扑和外设连接关系。以下 SVG 图展示了一个典型的嵌入式系统设备树结构:

设备树层次结构 / (根节点) cpus { } memory@80000000 gic: interrupt-controller gpio1: gpio@f2000000 apb-bus@f0000000 uart@f1000000 i2c@f2000000 button@0 temp-sensor@48 图例 中断连接 父子关系 叶子设备 核心节点

1.7 设备树覆盖(Overlay)机制

设备树覆盖允许在系统运行时动态添加或修改设备树节点,常用于 FPGA 重配置、扩展板热插拔等场景。覆盖文件(.dtbo)的语法与基础 DTS 相同,但使用 /plugin/ 关键字标识:

/dts-v1/;
/plugin/;

/ {
    fragment@0 {
        target = <&i2c1>;
        __overlay__ {
            status = "okay";
            eeprom@50 {
                compatible = "atmel,24c02";
                reg = <0x50>;
            };
        };
    };
};
实践建议: 在编写设备树时,优先使用 .dtsi 文件复用 SoC 级定义,板级 .dts 只做引脚复用、外设使能和属性覆盖。这可以大幅减少重复代码,并提高设备树的可维护性。

1.8 本章小结

  • 设备树将硬件描述从内核源码中解耦,实现了“数据驱动硬件发现”。
  • 设备树的工作流程为:DTS → DTC → DTB → Bootloader → 内核解析 → 驱动匹配。
  • 基本语法包括节点、属性、根节点、标签和包含文件,其中 compatible 是驱动匹配的核心属性。
  • 中断控制器和 GPIO 控制器通过 interrupt-parent、interrupts、gpio-controller、#gpio-cells 和 gpios 属性建立映射关系。
  • 设备树覆盖机制支持运行时动态修改硬件拓扑,适用于可扩展平台。