1. 设备树基础:DTS 语法与关键属性详解

设备树(Device Tree,DT)是描述硬件拓扑与资源分配的一种树形数据结构,在 Linux 内核中用于将平台设备信息从 Bootloader 传递给内核,替代了传统的 board-*.c 硬编码方式。对于 DRM/GPU 驱动移植而言,设备树节点是驱动与硬件之间的“契约”——GPU 的寄存器基址、中断号、时钟、复位、电源域等信息全部通过设备树节点描述。

核心认知: 设备树不是驱动代码,而是硬件描述语言。驱动通过 of_match_table 匹配 compatible 属性来绑定设备节点,再通过 of_iomap、irq_of_parse_and_map 等 API 解析节点中的资源。

1.1 DTS 文件结构与基本语法

设备树源文件(.dts)采用树形缩进结构,每个节点以 节点名@地址 的形式定义,节点内包含属性和子节点。一个典型的 DTS 文件结构如下:

/dts-v1/;

/ {
    model = "My GPU Development Board";
    compatible = "vendor,board-rev1", "vendor,board";

    chosen {
        bootargs = "console=ttyS0,115200";
    };

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;
        cpu@0 {
            compatible = "arm,cortex-a72";
            reg = <0x0>;
        };
    };

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

    gpu: gpu@fbc00000 {
        compatible = "vendor,awesome-gpu", "simple-bus";
        reg = <0xfbc00000 0x10000>;
        interrupts = <0 42 4>;
        clocks = <&clkc 15>, <&clkc 16>;
        clock-names = "core", "mem";
        resets = <&rstc 7>;
        status = "okay";
    };
};
语法要点:
  • /dts-v1/; 必须位于文件头部,标识 DTS 版本。
  • 根节点 / 包含整个系统描述。
  • 节点名 gpu@fbc00000 中的 @fbc00000 是单元地址(unit-address),通常与 reg 属性的首地址一致。
  • 标签(label)如 gpu: 可在其他文件中通过 &gpu 引用。

1.2 关键属性详解

设备树属性是节点携带的键值对,驱动通过解析这些属性获取硬件资源。下表列出 GPU 节点最常用的属性:

属性名 类型 说明 GPU 节点示例
compatible 字符串列表 驱动匹配标识,内核通过此属性查找对应的 of_device_id 表 "vendor,awesome-gpu", "simple-bus"
reg u32/u64 数组 描述设备地址空间,格式由 #address-cells 和 #size-cells 决定 <0xfbc00000 0x10000>
interrupts u32 数组 中断描述,格式依赖 #interrupt-cells(通常为 3:中断类型、编号、触发方式) <0 42 4>
clocks phandle 数组 引用时钟控制器提供的时钟源 <&clkc 15>, <&clkc 16>
clock-names 字符串列表 与 clocks 一一对应,驱动通过名字获取时钟 "core", "mem"
resets phandle 数组 引用复位控制器,用于 GPU 的复位/解复位 <&rstc 7>
status 字符串 设备状态:"okay" 启用,"disabled" 禁用 "okay"
dma-coherent 空属性 声明设备支持 DMA 一致性(无值,仅存在即生效) dma-coherent;
常见陷阱: reg 属性的 #address-cells 和 #size-cells 在父节点中定义,GPU 节点本身不定义这两个属性。如果父节点设置 #address-cells = <2>,则 reg 中的地址和长度都必须使用 64 位(两个 u32 组合)。

1.3 compatible 属性与驱动匹配机制

compatible 是设备树中最关键的属性,它建立了硬件节点与内核驱动之间的桥梁。内核在启动时遍历设备树,对每个节点的 compatible 值与驱动注册的 of_device_id 表进行匹配。

匹配优先级:列表中的第一个字符串优先级最高,后续字符串作为兼容降级。例如:

compatible = "vendor,awesome-gpu-v2", "vendor,awesome-gpu", "simple-bus";

内核会优先尝试匹配 "vendor,awesome-gpu-v2",若没有驱动支持,则尝试 "vendor,awesome-gpu",最后尝试 "simple-bus"。

命名规范: 推荐使用 "厂商名,设备名" 格式,如 "arm,mali-g72"、"vivante,gc2000"。避免使用纯数字或过于通用的名称,防止匹配冲突。

1.4 reg 属性与地址空间描述

reg 属性描述设备占用的地址范围,格式为 <基址1 长度1 基址2 长度2 ...>。对于 GPU 节点,通常只有一个地址段(寄存器空间),但部分 GPU 可能包含多个独立地址区域(如控制寄存器 + 显存窗口)。

地址和长度的位数由父节点的 #address-cells 和 #size-cells 决定:

父节点设置 reg 格式 示例
#address-cells = <1>; #size-cells = <1>; <addr size> <0xfbc00000 0x10000>
#address-cells = <2>; #size-cells = <2>; <addr_hi addr_lo size_hi size_lo> <0x0 0xfbc00000 0x0 0x10000>

驱动中通过 platform_get_resource(pdev, IORESOURCE_MEM, 0) 获取第一个 reg 段对应的内存资源。

1.5 interrupts 属性与中断描述

GPU 通常需要处理多种中断:渲染完成、页面翻转(VBlank)、错误异常等。interrupts 属性描述设备使用的中断线,其格式由中断控制器的 #interrupt-cells 决定。

ARM GIC(Generic Interrupt Controller)的典型格式为 <中断类型 中断号 触发方式>:

字段 值 说明
中断类型 0 = SPI(共享外设中断)
1 = PPI(私有外设中断)
GPU 通常使用 SPI(类型 0)
中断号 32~1019(SPI 范围) 硬件连接的中断线编号
触发方式 1 = 上升沿触发
4 = 高电平触发
GPU 常用高电平触发(4)

示例:interrupts = <0 42 4>; 表示 SPI 类型、中断号 42、高电平触发。

多中断场景: 如果 GPU 需要多个中断线,可以写成 interrupts = <0 42 4>, <0 43 4>, <0 44 4>;,驱动通过 platform_get_irq(pdev, index) 按索引获取。

1.6 时钟与复位属性

现代 GPU 通常需要多个时钟源(核心时钟、存储器时钟、总线时钟)和复位信号。设备树通过 clocks 和 resets 属性引用时钟/复位控制器中的特定输出。

gpu: gpu@fbc00000 {
    compatible = "vendor,awesome-gpu";
    reg = <0xfbc00000 0x10000>;
    interrupts = <0 42 4>;

    /* 时钟:clkc 是时钟控制器节点标签,15 和 16 是时钟输出索引 */
    clocks = <&clkc 15>, <&clkc 16>, <&clkc 17>;
    clock-names = "core", "mem", "bus";

    /* 复位:rstc 是复位控制器节点标签,7 是复位输出索引 */
    resets = <&rstc 7>;
    reset-names = "gpu";

    /* 电源域:引用 power-controller 节点 */
    power-domains = <&pd_gpu>;

    status = "okay";
};

驱动中通过 devm_clk_get(dev, "core") 获取名为 "core" 的时钟,通过 devm_reset_control_get(dev, "gpu") 获取复位控制句柄。

1.7 设备树层级关系与 GPU 节点定位

下面的 SVG 图展示了一个典型 SoC 中 GPU 节点在设备树中的位置,以及它与时钟控制器、中断控制器、复位控制器、内存控制器之间的关系。

设备树 GPU 节点层级关系 / (根节点) soc@0 (SoC 内部总线) clkc (时钟控制器) gic (中断控制器) rstc (复位控制器) gpu@fbc00000 compatible = "vendor,awesome-gpu" reg 属性 基址 0xfbc00000 长度 0x10000 (64KB) interrupts 属性 SPI 类型, 中断号 42 高电平触发 clocks / resets 引用 clkc 输出 15/16 引用 rstc 输出 7 驱动匹配与资源获取流程 ① 内核遍历设备树 → ② 匹配 compatible → ③ 调用 probe 函数 ④ platform_get_resource 获取 reg → ⑤ platform_get_irq 获取中断 ⑥ devm_clk_get / devm_reset_control_get 获取时钟/复位

1.8 设备树包含与覆盖机制

实际项目中,设备树通常拆分为多个文件,通过 #include 和 /include/ 组合:

  • .dtsi:SoC 级描述文件,包含 CPU、中断控制器、时钟控制器、GPU 等所有内部外设的默认节点。
  • .dts:板级描述文件,#include 对应的 .dtsi,然后通过 &gpu 引用节点并覆盖属性(如修改 status、调整时钟频率)。
/* soc.dtsi - SoC 级定义 */
/ {
    gpu: gpu@fbc00000 {
        compatible = "vendor,awesome-gpu";
        reg = <0xfbc00000 0x10000>;
        interrupts = <0 42 4>;
        clocks = <&clkc 15>, <&clkc 16>;
        clock-names = "core", "mem";
        resets = <&rstc 7>;
        status = "disabled";  /* 默认禁用 */
    };
};

/* board.dts - 板级定义 */
#include "soc.dtsi"

&gpu {
    status = "okay";  /* 板级使能 GPU */
    /* 可在此处添加板级特有的属性,如 operating-points */
};
最佳实践: 将 GPU 的通用描述(寄存器基址、中断、时钟索引)放在 .dtsi 中,板级文件只负责使能设备和添加板级特性(如 DVFS 表、电源管理策略)。这样当 SoC 用于不同板卡时,无需重复编写 GPU 节点基础信息。

1.9 本章小结

设备树是 GPU 驱动移植的“第一道关卡”——一个错误的 reg 地址会导致内核崩溃,遗漏 interrupts 会使 VBlank 中断无法触发,缺失 clocks 则 GPU 核心无法上电。掌握 DTS 语法与关键属性,是后续编写 GPU 驱动 probe 函数、实现 DRM 框架对接的必备前提。

关键记忆点:
  • compatible 是驱动匹配的唯一标识,命名必须与驱动中的 of_device_id 一致。
  • reg 的地址/长度位数由父节点 #address-cells / #size-cells 决定。
  • interrupts 格式依赖中断控制器类型,ARM GIC 通常为 <类型 编号 触发方式>。
  • 时钟和复位通过 phandle + 索引引用,驱动使用名字(clock-names / reset-names)获取。
  • 使用 .dtsi + .dts 分离 SoC 通用描述与板级配置,提高复用性。