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 才是高性能零拷贝的王道。