4. 地址空间与映射模型
在 DMA 驱动开发中,设备发起访问时使用的地址并不等同于 CPU 看到的虚拟地址,也不一定等于最终内存颗粒上的物理地址。IOMMU/SMMU 的核心价值,就是在设备地址与系统物理地址之间建立可控、可隔离、可验证的转换关系。理解 PA、VA、IOVA、IPA 这几个地址空间,以及它们之间的转换链路,是排查 DMA 映射失败、SMMU fault、地址越界和性能异常的基础。
一、四个地址空间的基本含义
先明确四个最容易混淆的地址概念:
- VA(Virtual Address):CPU 侧虚拟地址,由 MMU 通过页表转换为 PA。驱动代码中
kmalloc()、dma_alloc_coherent()返回的指针通常是 VA。 - PA(Physical Address):系统物理地址,是内存控制器和总线最终访问 DRAM 或外设寄存器的地址。
- IOVA(I/O Virtual Address):设备侧虚拟地址,也叫 DMA 地址。设备发起 DMA 时使用 IOVA,由 IOMMU/SMMU 转换为 PA。
- IPA(Intermediate Physical Address):中间物理地址,主要出现在虚拟化场景。Guest 看到的“物理地址”实际是 IPA,需要 Stage-2 转换才能得到真正的 PA。
二、地址转换关系总览
下图给出从 CPU 侧和设备侧两条路径出发的地址转换模型。CPU 路径是 VA → PA;设备路径是 IOVA → PA。虚拟化场景下,设备路径可拆成 IOVA → IPA → PA 两级转换。
三、IOMMU/SMMU 的地址转换流程
以 ARM SMMU 为例,设备发起 DMA 写请求时,典型转换流程如下:
- 设备将 IOVA 放到总线请求中,SMMU 根据 Stream ID 或 Requester ID 找到对应的转换上下文。
- 若启用 Stage-1,SMMU 用 Stream Table Entry 指向的 CD 表,以 IOVA 为索引查 Stage-1 页表,得到 IPA。
- 若启用 Stage-2,继续用 IPA 查 Stage-2 页表,得到最终 PA。
- 若页表项无效、权限不足或地址越界,SMMU 产生 fault,记录 FSR、FAR 等寄存器,并可能触发中断。
- 转换成功后,PA 被送到内存控制器,完成实际读写。
Linux DMA API 中,驱动通常不直接操作 IOVA,而是通过 DMA 映射接口获得 DMA 地址:
/* 一致性映射:返回 VA,同时得到 DMA 地址 */
void *cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);
/* dma_handle 即设备侧 IOVA,设备用该地址发起 DMA */
/* 流式映射:已有缓冲区,建立临时映射 */
dma_addr_t dma_addr = dma_map_single(dev, cpu_addr, size, DMA_TO_DEVICE);
/* 使用完成后必须解除映射 */
dma_unmap_single(dev, dma_addr, size, DMA_TO_DEVICE);
四、典型场景:虚拟化下的两级转换
在 KVM 或 Xen 等虚拟化环境中,Guest 内的驱动看到的是 Guest 物理地址,即 IPA。设备直通给 Guest 后,设备发出的 IOVA 需要先经过 Stage-1 转换为 IPA,再经过 Stage-2 转换为 Host 的 PA。此时常见问题包括:
- Stage-1 页表由 Guest 内核维护,Stage-2 页表由 Hypervisor 维护,两者粒度或属性不一致会导致 fault。
- Guest 的 IOVA 范围与 Host 的 IPA 范围未对齐,造成映射空洞。
- 设备直通时 Stream ID 配置错误,SMMU 找不到正确的 CD 表,直接报 translation fault。
dmesg 中的 SMMU fault 日志,可以快速定位是哪一级页表缺失。
五、地址空间对照与常见误区
| 地址类型 | 使用方 | 转换机构 | 典型来源 | 常见误区 |
|---|---|---|---|---|
| VA | CPU | MMU | kmalloc、mmap | 把 VA 直接当 DMA 地址传给设备 |
| IOVA | 设备 | IOMMU/SMMU | dma_alloc_coherent 的 dma_handle | 认为 IOVA 一定等于 PA |
| IPA | Guest 设备 | Stage-1 | Guest 页表 | 把 IPA 当成 Host 的 PA 使用 |
| PA | 内存控制器 | 无(最终地址) | 物理内存布局 | 忽略保留内存与地址空洞 |
virt_to_phys() 把 CPU VA 转成 PA,再把这个 PA 直接写入设备寄存器当作 DMA 地址。在使能 IOMMU/SMMU 的系统中,设备需要的是 IOVA,而不是 PA。正确做法是使用 DMA API 获取 dma_handle,并保证映射方向、长度和生命周期匹配。
六、复盘要点
理解地址空间与映射模型,可以归纳为三句话:CPU 走 VA→PA,设备走 IOVA→PA,虚拟化多一层 IPA。IOMMU/SMMU 是设备侧地址转换和隔离的执行者,Stage-1 管 Guest 内部,Stage-2 管 Guest 到 Host。驱动开发中应始终通过 DMA API 获取设备地址,避免手工转换;遇到 SMMU fault 时,先看 Stream ID 和页表层级,再核对 IOVA 范围与权限属性。掌握这条转换链路,后续配置 SMMU 页表、调试 DMA 映射失败和优化大页映射时,才能有的放矢。