IOMMU和RK3588 RGA浅谈

4 阅读7分钟

IOMMU 与 RGA 硬件代际:彻底理清地址连续性的底层逻辑

在嵌入式 Linux 图形与视频开发中,dma_fd 是实现零拷贝流水线的核心。但许多开发者对它的理解存在一个根本性困惑:为什么有时候要求物理连续内存,有时候又可以接受物理离散内存?RGA 到底要什么?

本文将 IOMMU 硬件原理与 Rockchip RGA 的版本演进结合,解答关于“连续地址”的疑惑。


1. 无 IOMMU 的世界:RGA2 的物理连续之痛

1.1 硬件视角

RGA2(如 RK3288、RK3399 搭载)是一个没有 IOMMU 保护的 DMA 主设备。它的 DMA 引擎地址寄存器接收的是物理地址(Physical Address,PA)

┌─────────────────────────────────────────────────────────┐
│                    RGA2 硬件 (无 IOMMU)                   │
│  ┌─────────────────────────────────────────────────┐   │
│  │  源地址寄存器: 0x80000000 (这是一个物理地址)       │   │
│  │  目标地址寄存器: 0x90000000 (物理地址)            │   │
│  └─────────────────────────────────────────────────┘   │
│                          │                               │
│                          ▼                               │
│                   直接驱动 AXI 总线                        │
│                          │                               │
│                          ▼                               │
│                 DDR 物理地址空间 (必须连续)                │
└─────────────────────────────────────────────────────────┘

1.2 致命缺陷

问题后果
寻址能力受限RGA2 的 DMA 引擎通常只有 32 位地址线,最大寻址4GB。RK3588 有 8GB 内存,超过 4GB 的内存 RGA2 根本无法直接访问。
必须物理连续如果一帧 1920x1080 的图像由 500 个离散的 4KB 物理页组成,RGA2 无法自动跳转。必须分配一整块连续的物理内存(通常从预留的 CMA 区域)。
安全隔离为零RGA2 可以读写任意物理地址。一个错误的 width_stride 配置,可能让它踩踏到内核的核心数据结构,导致系统崩溃。

1.3 dma_fd 在无 IOMMU 时的本质

// 无 IOMMU 场景下,dma_fd 指向的 dma_buf
struct dma_buf {
    struct sg_table *sg;  // 通常只有一个条目,描述一整块连续物理内存
    // sg->sgl[0].page_link → 物理页 A
    // sg->sgl[0].length   → 整个 buffer 的大小
};

此时的 dma_fd 只是一个指向物理连续内存的句柄。RGA2 驱动读取 sg->sgl[0] 的物理地址,直接写入硬件寄存器。


2. 有 IOMMU 的世界:RGA3 的 IOVA 解放

2.1 硬件视角

RGA3(RK3568、RK3588 搭载)挂载在 IOMMU 之后。IOMMU 是一个位于总线和内存控制器之间的硬件单元,对称于 CPU 侧的 MMU。

┌─────────────────────────────────────────────────────────────────┐
│                         RGA3 硬件                                 │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  源地址寄存器: 0x10000000 (这是 IOVA,不是物理地址!)      │    │
│  │  目标地址寄存器: 0x20000000 (IOVA)                        │    │
│  └─────────────────────────────────────────────────────────┘    │
│                          │                                       │
│                          ▼                                       │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                     IOMMU 硬件                            │    │
│  │  实时翻译:IOVA → 物理地址                                 │    │
│  │  ┌──────────────────────────────────────────────────┐   │    │
│  │  │ IOVA 0x10000000 → 物理页 A (0x87654000)           │   │    │
│  │  │ IOVA 0x10001000 → 物理页 B (0x12345000)  ← 跨页自动翻译│   │    
│  │  │ IOVA 0x10002000 → 物理页 C (0x9ABC0000)           │   │    │
│  │  └──────────────────────────────────────────────────┘   │    │
│  └─────────────────────────────────────────────────────────┘    │
│                          │                                       │
│                          ▼                                       │
│            DDR 物理地址空间 (物理页可以完全离散!)                  │
└─────────────────────────────────────────────────────────────────┘

2.2 IOMMU 解决的核心问题

问题IOMMU 的解决方案
寻址能力受限RGA3 看到的 IOVA 可以是 32 位或 40 位。IOMMU 页表可以将 IOVA 映射到任意物理地址,包括 8GB 以上的内存。
物理离散操作系统分配离散的物理页。IOMMU 建立连续的 IOVA 映射,RGA3 看到的是完美的“连续内存”。
安全隔离每个设备被分配独立的 Protection Domain(保护域) 。RGA3 只能访问其 IOMMU 页表中授权的 IOVA 范围。越界访问会触发 IOMMU Fault 中断,内存数据毫发无损。

2.3 dma_fd 在有 IOMMU 时的本质

// 有 IOMMU 场景下,dma_fd 指向的 dma_buf
struct dma_buf {
    struct sg_table *sg;          // 记录离散的物理页表
    struct iommu_domain *domain;  // IOMMU 保护域
    dma_addr_t iova_base;         // 连续的 IOVA 基地址
};

// sg 表可以是这样的(完全离散):
// sg->sgl[0] → 物理页 A (0x87654000)
// sg->sgl[1] → 物理页 B (0x12345000)
// sg->sgl[2] → 物理页 C (0x9ABC0000)
// ...

// 但 IOMMU 页表将它们映射为连续的 IOVA:
// IOVA 0x10000000 → 物理页 A
// IOVA 0x10001000 → 物理页 B
// IOVA 0x10002000 → 物理页 C

此时 dma_fd 的本质是一个指向 IOMMU 页表的句柄。RGA3 驱动获取的是 iova_base,而非物理地址。


3. RGA2 vs RGA3:一张表彻底区分

对比维度RGA2 (无 IOMMU)RGA3 (有 IOMMU)
设备访问的地址类型物理地址(PA)I/O 虚拟地址(IOVA)
内存连续性要求物理连续(必须从 CMA 分配)物理可离散,IOVA 连续
> 4GB 内存访问无法直接访问,需 swiotlb 反弹完美支持,IOVA 可映射任意物理地址
dma_fd 封装的内容指向物理连续内存的句柄指向 IOMMU 页表映射的句柄
DMA 越界后果直接踩踏物理内存,系统崩溃触发 IOMMU Fault,内核可控处理
编程复杂度需配置 Scatterlist(若支持)单一连续 IOVA 基地址,简单
在 RK3588 上的使用存在,但不推荐用于大内存场景默认首选,librga 自动调度

4. RK3588 的特殊性:双核心共存

RK3588 同时搭载 RGA2 和 RGA3 核心:

# 查看硬件核心
cat /sys/kernel/debug/rkrga/hardware

# 典型输出
rga3_core0, core 1: version: 3.0.76831
    input range: 68x2 ~ 8176x8176
    ......
    mmu: RK_IOMMU                    # ← 有 IOMMU
rga3_core1, core 2: version: 3.0.76831
    input range: 68x2 ~ 8176x8176
    ......
    mmu: RK_IOMMU                    # ← 有 IOMMU
rga2, core 4: version: 3.2.63318
    input range: 2x2 ~ 8192x8192
    ......
    mmu: RGA_MMU                     # ← 老款 MMU,仅支持 32 位

4.1 librga 的调度逻辑

  • 默认行为:librga 会优先将任务调度到 RGA3
  • 降级条件:如果任务使用了 RGA3 不支持的功能(如 Color Fill、某些 YUV 格式),则会降级到 RGA2。
  • 报错场景:如果任务被分配到 RGA2,但传入的 dma_fd 指向的内存超过 4GB,会报错:
  • rga_policy: RGA2 only support under 4G memory! 对应核心不支持的原因日志,标识当前
  • 不匹配原因为该核心不支持4G内存空间以外的内存。
  • rga_policy: optional_cores = 0
  • rga_policy: invalid function policy rga_policy: assign core: -1 报匹配失败错误。
  • rga_job: job assign failed

4.2 最佳实践

// 1.建议使用 importbuffer_xx提前将外部内存导入到RGA驱动内部,避免该问题。

// 2.依旧存在该问题: 可以检查一下配置的图像任务的参数,
确认是否配置了仅有RGA2核心(内存访问受限制的核心) 支持的功能或格式,以RK3588为例,
color fill功能和YUV422/420 planar格式均是RGA2核心特有的 功能和格式,
因此该场景下必须分配4G以内内存空间的内存调用RGA。 
常见的分配4G内存方式可以查看SDK中rga部分以下示例代码: 
/samples/allocator_demo/src/rga_allocator_dma32_demo.cpp 
/samples/allocator_demo/src/rga_allocator_graphicbuffer_demo.cpp 
如果使用的其他分配器,例如mpp_buffer、v4l2_buffer、drm_buffer等,
请查询对应分配器是否支持 限制分配4G以内内存空间内存,
并按照对应方式申请复合RGA硬件要求的内存。

// 3. 如果你明确只需要 RGA3,可以通过环境变量或 API 指定核心

5. 地址连续性的四层视角

┌─────────────────────────────────────────────────────────────────┐
│                      用户态应用 (CPU 视角)                         │
│           连续虚拟地址 (VA) ← mmap(dma_fd)                        │
│           CPU 通过 MMU 看到连续的 VA,无论底层如何                  │
└─────────────────────────────────────────────────────────────────┘
                                 │
                                 ▼
┌─────────────────────────────────────────────────────────────────┐
│                    内核 dma_buf 核心层                            │
│  封装 sg_table (离散物理页表) + iommu_domain (IOMMU 页表)         │
│  dma_fd 就是指向这个内核对象的句柄                                  │
└─────────────────────────────────────────────────────────────────┘
          │                                      │
          │ (RGA2 路径)                           │ (RGA3 路径)
          ▼                                      ▼
┌──────────────────────────┐        ┌──────────────────────────────┐
│  无 IOMMU / RGA2 场景     │        │   有 IOMMU / RGA3 场景         │
│  设备视角: 物理地址 (PA)   │        │  设备视角: I/O 虚拟地址 (IOVA)  │
│  要求: 物理连续           │        │  要求: IOVA 连续,物理可离散     │
│  dma_fd = 物理内存句柄    │        │  dma_fd = IOMMU 页表句柄        │
│  越界 = 系统崩溃          │        │  越界 = IOMMU Fault             │
└──────────────────────────┘        └──────────────────────────────┘

dma_fd 为设备提供的不是“内存本身”,而是访问内存的连续视图许可证。在没有 IOMMU 时,这个视图必须由物理连续内存直接提供;在有 IOMMU 时,IOMMU 硬件动态生成连续视图,底层物理内存可以任意离散。RGA2 要求前者,RGA3 拥抱后者。RK3588 二者兼备,但 RGA3 才是高性能零拷贝的王道。