4. GPU IOMMU/MMU 配置:iommus 属性、dma-ranges、IOMMU 与 SMMU 在设备树中的描述

在 DRM 与 GPU 驱动移植中,IOMMU(Input-Output Memory Management Unit)与 SMMU(System MMU)是连接 GPU 与系统内存的关键硬件单元。设备树中必须正确描述这些内存管理单元,否则 GPU 无法进行 DMA 操作,导致驱动初始化失败或内存访问异常。本章节深入解析 iommus 属性、dma-ranges 属性以及 IOMMU/SMMU 在设备树中的完整描述方法。

4.1 IOMMU/SMMU 在 GPU 驱动中的角色

GPU 作为高性能 DMA 设备,需要直接访问系统内存中的帧缓冲、纹理数据、命令缓冲区等。IOMMU/SMMU 提供以下关键能力:

  • 地址翻译:将 GPU 发出的设备虚拟地址(DVA)翻译为系统物理地址(PA)。
  • 内存保护:防止 GPU 访问未授权的内存区域,提高系统安全性。
  • 连续映射:将物理上不连续的内存页映射为 GPU 视角的连续地址空间。
  • 大页支持:减少 TLB 缺失,提升 GPU 内存访问性能。
关键概念:在 ARM 平台上,SMMU 是 IOMMU 的具体实现。设备树中 iommus 属性用于将 GPU 设备绑定到特定的 SMMU 实例。

4.2 iommus 属性详解

iommus 属性是设备树中用于将设备连接到 IOMMU 的标准属性。其语法格式如下:

iommus = <phandle stream-id> [, <phandle stream-id> ...];

参数说明:

参数 描述 示例值
phandle 指向 IOMMU/SMMU 节点的句柄 &smmu
stream-id 设备在 IOMMU 中的唯一标识符(Stream ID) 0x100

以下是一个典型的 GPU 节点 iommus 配置示例:

gpu: gpu@f0000000 {
    compatible = "arm,mali-g72";
    reg = <0x0 0xf0000000 0x0 0x10000>;
    interrupts = <0 42 4>;
    iommus = <&smmu 0x100>;
    #iommu-cells = <1>;
};
移植要点:Stream ID 必须与 SMMU 硬件配置一致。在多 GPU 或多上下文场景下,可以指定多个 Stream ID,例如 iommus = <&smmu 0x100 &smmu 0x101>。

4.3 dma-ranges 属性解析

dma-ranges 属性用于描述设备能够进行 DMA 操作的内存地址范围。它定义了从设备地址空间到 CPU 地址空间的映射关系。语法格式如下:

dma-ranges = <child-bus-addr parent-bus-addr length> [, ...];

参数说明:

参数 描述 示例值
child-bus-addr 设备总线地址(GPU 视角的地址) 0x0
parent-bus-addr 父总线地址(CPU 物理地址) 0x80000000
length 映射的内存范围大小 0x80000000

实际配置示例:

gpu: gpu@f0000000 {
    compatible = "arm,mali-g72";
    reg = <0x0 0xf0000000 0x0 0x10000>;
    interrupts = <0 42 4>;
    iommus = <&smmu 0x100>;
    dma-ranges = <0x0 0x80000000 0x80000000>;
};
常见错误:dma-ranges 与 iommus 同时使用时,IOMMU 会覆盖 dma-ranges 的地址映射。如果 IOMMU 配置正确,dma-ranges 可以省略;但在没有 IOMMU 的场景下,dma-ranges 是必需的。

4.4 SMMU 节点在设备树中的描述

SMMU 节点本身也需要在设备树中完整描述。以下是一个标准的 ARM SMMU v3 节点示例:

smmu: iommu@f0100000 {
    compatible = "arm,smmu-v3";
    reg = <0x0 0xf0100000 0x0 0x20000>;
    interrupts = <0 48 4>,
                 <0 49 4>,
                 <0 50 4>;
    interrupt-names = "eventq", "gerror", "priq";
    #iommu-cells = <1>;
    dma-coherent;
};

SMMU 节点关键属性说明:

属性 描述 是否必需
compatible SMMU 兼容性字符串,如 "arm,smmu-v3" 是
reg SMMU 寄存器基地址和大小 是
interrupts SMMU 中断号列表 是
#iommu-cells 指定 iommus 属性中 Stream ID 的 cell 数量 是
dma-coherent 指示 SMMU 与 CPU 之间缓存一致性 否

4.5 GPU 与 SMMU 连接关系图

下图展示了 GPU 节点、SMMU 节点与系统内存之间的 IOMMU 连接关系:

GPU 与 SMMU 连接关系图 GPU 设备节点 gpu@f0000000 SMMU 节点 iommu@f0100000 系统内存 DDR 区域 iommus = &smmu 0x100 地址翻译 dma-ranges 属性 dma-ranges = <0x0 0x80000000 0x80000000> // GPU 可访问 0x80000000~0xFFFFFFFF Stream ID 配置 iommus = <&smmu 0x100> // Stream ID = 0x100,标识 GPU 的 DMA 事务

4.6 IOMMU 配置流程

在 GPU 驱动初始化过程中,IOMMU 的配置遵循以下步骤:

  1. 解析设备树:驱动读取 GPU 节点的 iommus 属性,获取 SMMU 句柄和 Stream ID。
  2. 获取 SMMU 实例:通过 of_parse_phandle() 获取 SMMU 设备节点。
  3. 创建 IOMMU 映射:调用 iommu_domain_alloc() 分配 IOMMU 域。
  4. 绑定设备:使用 iommu_attach_device() 将 GPU 设备绑定到 IOMMU 域。
  5. 配置地址映射:通过 iommu_map() 建立 GPU 虚拟地址到物理地址的映射。
  6. 启用 DMA:完成映射后,GPU 即可通过 IOMMU 进行 DMA 操作。
内核 API 对应关系:
of_parse_phandle(dev, "iommus", 0) → 获取 SMMU 节点
iommu_domain_alloc(bus) → 分配 IOMMU 域
iommu_attach_device(domain, dev) → 绑定设备
iommu_map(domain, iova, phys, size, prot) → 建立映射

4.7 常见问题与排查方法

问题现象 可能原因 排查方法
GPU DMA 超时 Stream ID 配置错误 检查 iommus 属性中的 Stream ID 是否与硬件一致
IOMMU 页错误 地址映射未建立或权限不足 使用 iommu_debug 内核参数查看详细错误
驱动 probe 失败 SMMU 节点未正确描述 检查 SMMU 节点的 compatible 和 reg 属性
内存访问越界 dma-ranges 范围过小 确认 dma-ranges 覆盖 GPU 所需的所有内存区域

4.8 多 GPU 场景下的 IOMMU 配置

在异构计算或多 GPU 系统中,每个 GPU 可能需要独立的 IOMMU 配置。以下是一个双 GPU 配置示例:

gpu0: gpu@f0000000 {
    compatible = "arm,mali-g72";
    reg = <0x0 0xf0000000 0x0 0x10000>;
    iommus = <&smmu 0x100>;
};

gpu1: gpu@f0010000 {
    compatible = "arm,mali-g72";
    reg = <0x0 0xf0010000 0x0 0x10000>;
    iommus = <&smmu 0x200>;
};
注意事项:在多 GPU 场景下,每个 GPU 必须使用不同的 Stream ID,否则会导致 IOMMU 配置冲突。同时,SMMU 硬件必须支持多个 Stream ID 的独立配置。

4.9 本章小结

IOMMU/SMMU 配置是 GPU 驱动移植中的关键环节。设备树中的 iommus 属性将 GPU 设备绑定到 SMMU 实例,dma-ranges 属性定义 DMA 地址范围,而 SMMU 节点本身需要完整描述其寄存器、中断和兼容性。正确配置这些属性是 GPU 驱动正常工作的前提。