1. 设备树概述:设备树(DTS)的起源、作用、基本语法结构,以及为什么需要设备树来描述GPIO控制器与中断资源

1.1 设备树的起源与背景

在早期的嵌入式Linux系统中(如ARM Linux 2.6及更早版本),板级硬件信息被硬编码在大量的 board-*.c 文件中。每引入一款新硬件平台,内核源码树中就需要增加数百行甚至上千行的平台设备注册代码、GPIO配置代码和中断映射代码。这种“代码即配置”的方式导致了以下严重问题:

  • 代码冗余:同一款SoC的不同板卡,其GPIO和中断配置差异极小,却需要复制大量相似代码。
  • 内核与硬件耦合:更换外设或调整引脚功能时,必须重新编译内核。
  • 主线化困难:厂商的板级补丁难以被Linux内核主线社区接受,维护成本极高。

为了解决上述问题,Linux内核社区从PowerPC架构引入了设备树(Device Tree)机制。设备树是一种描述硬件资源的数据结构,它将硬件配置信息从内核代码中剥离出来,以文本形式(.dts文件)独立存在,并在内核启动时由引导加载程序传递给内核。

关键里程碑:ARM Linux在2011年(Linux 3.0左右)开始大规模采用设备树,到2014年(Linux 3.16)后,设备树成为ARM平台的标准硬件描述方式。

1.2 设备树的作用

设备树在嵌入式Linux系统中的核心作用可以归纳为以下四点:

作用 说明 与GPIO/中断的关联
硬件描述 描述CPU、内存、总线、外设的地址、中断、时钟、GPIO等资源 定义GPIO控制器的寄存器基址、中断控制器的中断号范围
资源解耦 将硬件配置与内核驱动代码分离,同一驱动可适配不同板卡 驱动通过设备树节点获取GPIO编号和中断号,无需硬编码
启动参数传递 引导程序(如U-Boot)将设备树二进制(DTB)传递给内核 内核解析DTB后建立GPIO和中断的映射关系
设备发现 内核根据设备树节点自动创建设备,匹配驱动 GPIO控制器和中断控制器作为平台设备被注册

理解要点:设备树本质上是一个“硬件数据库”,内核在启动时读取这个数据库,就知道“哪个GPIO引脚连接了哪个外设”、“哪个中断号对应哪个功能模块”。

1.3 设备树的基本语法结构

设备树源文件(.dts)采用树形层次结构,以节点(node)和属性(property)为核心。以下是一个包含GPIO控制器和中断控制器的典型设备树片段:

/dts-v1/;

/ {
    model = "Embedded Development Board";
    compatible = "vendor,board-v1";

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

    /* 中断控制器节点 */
    intc: interrupt-controller@10000000 {
        compatible = "vendor,soc-intc";
        reg = <0x10000000 0x1000>;
        interrupt-controller;
        #interrupt-cells = <2>;
    };

    /* GPIO控制器节点 */
    gpio0: gpio@20000000 {
        compatible = "vendor,soc-gpio";
        reg = <0x20000000 0x1000>;
        gpio-controller;
        #gpio-cells = <2>;
        interrupts = <0 31>;          /* 连接到intc,类型0,中断号31 */
        interrupt-parent = <&intc>;
    };

    /* 使用GPIO和中断的外设节点 */
    uart@30000000 {
        compatible = "vendor,uart";
        reg = <0x30000000 0x1000>;
        interrupts = <0 45>;
        interrupt-parent = <&intc>;
        cts-gpios = <&gpio0 5 0>;    /* GPIO0的第5号引脚 */
        rts-gpios = <&gpio0 6 0>;
    };
};

语法要点说明:

  • 节点(Node):以 节点名@地址 形式定义,如 gpio@20000000。
  • 属性(Property):键值对,值可以是字符串、32位整数数组(< >)、字节数组([ ])等。
  • 标签(Label):如 gpio0:,用于在其它节点中引用(通过 &gpio0)。
  • 特殊属性:compatible 用于驱动匹配,reg 描述地址空间,interrupts 描述中断资源。

1.4 为什么需要设备树来描述GPIO控制器与中断资源

GPIO控制器和中断资源是嵌入式系统中最核心、最复杂的硬件资源。使用设备树来描述它们,主要基于以下三个关键原因:

  1. 资源映射的复杂性:一个SoC内部可能有多个GPIO控制器(每个控制器管理32个或更多引脚),以及多个中断控制器(如GIC、AINTC)。设备树通过 interrupt-parent 和 interrupts 属性,清晰地建立了“外设 → 中断控制器 → 中断号”的层级映射关系。
  2. 引脚复用与配置:同一物理引脚可能被配置为GPIO、UART、I2C等不同功能。设备树中的 pinctrl 子系统与GPIO控制器节点配合,可以在系统启动时自动完成引脚功能切换,避免驱动代码中硬编码寄存器操作。
  3. 板级差异隔离:同一款SoC可以用于数十种不同的板卡,每块板卡的GPIO分配和中断连接可能完全不同。设备树允许厂商只提供一个SoC的 .dtsi 文件(包含所有内部控制器节点),然后为每块板卡编写一个极小的 .dts 文件(仅描述板级差异),极大减少了重复工作。

常见误区:不要试图在设备树中描述所有硬件细节。设备树只描述“硬件拓扑”和“资源分配”,不描述“驱动行为”或“初始化时序”。例如,GPIO控制器的驱动初始化顺序由内核驱动模型管理,而不是由设备树控制。

1.5 GPIO控制器与中断资源的设备树关系图

下图展示了设备树中GPIO控制器节点、中断控制器节点以及外设节点之间的资源关联关系:

设备树GPIO与中断资源关联架构图 设备树中GPIO控制器与中断资源的关联关系 根节点 "/" intc: interrupt-controller @10000000 gpio0: gpio-controller @20000000 uart@30000000 (外设节点) interrupts = <0 31> interrupt-parent = <&intc> cts-gpios = <&gpio0 5 0> 关键属性说明 • interrupt-controller: 声明该节点是中断控制器 • gpio-controller: 声明该节点是GPIO控制器 • #interrupt-cells / #gpio-cells: 指定中断/GPIO描述符需要的单元数 设备树通过节点属性和引用关系,将GPIO控制器、中断控制器和外设 三者之间的资源依赖清晰地表达为树形结构

1.6 本章小结

设备树作为嵌入式Linux硬件描述的标准语言,其核心价值在于将GPIO控制器、中断控制器等硬件资源的配置信息从内核代码中分离出来。通过本章的学习,你应该掌握:

  • 设备树解决了早期ARM Linux中“代码即配置”导致的维护难题。
  • 设备树通过节点和属性描述硬件拓扑,interrupts、gpios 等属性建立了资源关联。
  • GPIO控制器和中断资源的设备树描述,是实现驱动与硬件解耦、支持板级差异化的基础。