本页目录

IOMMU 与 DMA API

覆盖: IOMMU 硬件 → DMA API (coherent/streaming) → swiotlb → IOMMU domain → VFIO → 虚拟化中的 IOMMU 内核版本: 2.6 ~ 6.x

IOMMU 原理

IOMMU 为设备提供 IO 地址转换 (类比 CPU 的 MMU):
  设备视角地址 (IOVA) → IOMMU 页表 → 物理地址

功能:
  1. 地址转换: 设备只能"看到" IOVA → 不知道真实物理地址
  2. 隔离: 每个设备/domain 有独立页表 → A 不能访问 B 的内存
  3. 中断重映射: MSI 地址经 IOMMU 翻译 → 防止 MSI 注入攻击
  4. 大块连续: 分散物理页 → 映射为设备视角的连续 IOVA → 简化 DMA 缓冲分配

实现:
  Intel: VT-d (DMAR table)
  AMD:   AMD-Vi (IVRS table)
  ARM:   SMMU (System MMU)

DMA API

// include/linux/dma-mapping.h
// 设备驱动不应手动分配/释放物理内存用于 DMA → 必须使用 DMA API

// === Coherent DMA (一致性) ===
// 用途: descriptor ring, 设备状态页 (CPU 和设备频繁读写同一区域)
// 特性: CPU 和设备看到的永远一致 → 缓存自动同步
void *dma_alloc_coherent(struct device *dev, size_t size,
                          dma_addr_t *dma_handle, gfp_t gfp);
void dma_free_coherent(struct device *dev, size_t size,
                        void *cpu_addr, dma_addr_t dma_handle);

// === Streaming DMA (流式) ===
// 用途: 数据 buffer (一次性, 方向固定)
// 特性: 需要显式 sync → 性能优于 coherent
dma_addr_t dma_map_single(struct device *dev, void *cpu_addr,
                           size_t size, enum dma_data_direction dir);
void dma_unmap_single(struct device *dev, dma_addr_t dma_handle,
                       size_t size, enum dma_data_direction dir);

// direction: DMA_TO_DEVICE (写), DMA_FROM_DEVICE (读), DMA_BIDIRECTIONAL

// === Scatter-Gather ===
int dma_map_sg(struct device *dev, struct scatterlist *sg,
                int nents, enum dma_data_direction dir);

内部实现: 根据平台选择

有 IOMMU:
  dma_map_single → iommu_dma_map_page() → 在 IOMMU domain 中建映射
  → 零拷贝 (IOVA 到物理地址的映射, 不需要拷贝数据)

无 IOMMU:
  dma_map_single → 如果 DMA 可寻址范围 < 物理地址范围 → SWIOTLB bounce buffer
  → 在低地址区分配临时 buffer → 拷贝数据 (性能损失)

SWIOTLB: 软件 Bounce Buffer

当设备 DMA 寻址范围有限 (如 32-bit PCI 设备, 只能访问 0~4GB)...:
  SWIOTLB 在低地址区 (DMA zone) 预留 bounce buffer
    dma_map:  数据从高地址 → copy → bounce buffer (在 DMA 可及范围)
    dma_unmap: bounce buffer → copy → 用户 buffer (如果是 DMA_FROM_DEVICE)

  启动参数: swiotlb=65536 (分配 64MB bounce buffer)

IOMMU Domain 与 Group

// drivers/iommu/
// domain: IOMMU 页表的抽象 (一组 IOVA → 物理的映射)
// group: 硬件隔离单元 (同一 group 的设备可以互相访问)

// IOMMU group 是 VFIO 的隔离边界:
//   一个 group 内的设备不能互相隔离 → 必须一起分配给同一个 VM
//   /sys/kernel/iommu_groups/*/devices/*

VFIO: 用户态设备驱动

// drivers/vfio/
// 允许用户态程序直接 (但安全地) 访问硬件:
//   1. IOMMU 隔离: 设备映射到自己的 IOMMU domain → 不能访问 host 内存
//   2. MMIO 直通: 映射设备的 BAR → 用户态可直接 mmap
//   3. 中断转发: 设备中断 → IOMMU interrupt remapping → eventfd → QEMU/user

// QEMU: vfio-pci → PCI passthrough → guest 直接访问硬件
// SPDK:  用户态 NVMe 驱动 → 数据路径零拷贝

参考

  • 源码⁠: kernel/dma/, drivers/iommu/intel/, drivers/iommu/arm/, drivers/vfio/
  • 内核文档⁠: Documentation/core-api/dma-api.rst
  • LWN: "The DMA API", "IOMMU groups and VFIO"

关键词: IOMMU, DMA API, dma_map_single, coherent DMA, swiotlb, IOMMU domain, VFIO