IOMMU 与 DMA API

本页目录

设备描述符中的地址不能直接由 CPU 指针强转而来;unmap 也不是停止硬件的命令。DMA 需要同时管理地址、方向、可见性与使用权交接⁠。

查阅目标阅读位置
地址与 coherent/streaming第 1~2 节
方向、完成与 SG 计数第 3~4 节
IOMMU、bounce 与隔离第 5~6 节

1. 三种地址不能互相强转

地址谁使用典型类型
CPU 虚拟地址内核代码访问缓冲区void *
CPU 物理地址内存与资源管理phys_addr_t
DMA 地址写入设备描述符,供设备寻址dma_addr_t
两个地址视角,一份受协议约束的缓冲区 CPU 虚拟地址 CPU 通过普通内存映射访问 RAM 中的缓冲区 底层物理页 设备 DMA 地址 dma_addr_t,不是 CPU 指针 CPU MMU 直映 / IOMMU / bounce CPU 准备 / 读取 map / sync / unmap 设备读写 DMA API 建立映射与缓存约定;驱动仍须等待完成并遵守所有权交接。

IOMMU 可将设备发出的 IOVA 转换成物理页地址;没有 IOMMU 时,DMA 地址也可能受主桥偏移等影响。dma_addr_t 既不能直接解引用,也不能通过随意强转 CPU 指针得到。即使某台机器数值恰好相同,也不是可移植保证。

驱动按照硬件能力设置 DMA mask。描述符与数据载荷的地址位宽可以不同,必要时分别设置 streaming mask 与 coherent mask。较窄的 mask 可能无法在平台上得到支持,应处理 API 返回值;不要根据 CPU 是 64 位就声称设备支持 64 位 DMA。

2. Coherent 与 streaming 解决不同访问模式

模型常见用途驱动仍要负责
coherent allocation长期存在、双方频繁访问的描述符和状态区访问顺序、互斥、硬件完成与释放时机
streaming mapping网络包、存储数据等批量传输方向、所有权交接、同步与映射寿命

dma_alloc_coherent() 返回 CPU 地址,并输出供设备使用的 DMA 地址。释放时使用匹配的设备、大小、CPU 地址与 DMA handle。coherent 免除这块内存常规访问所需的显式缓存维护,不代表所有写入立即按源代码顺序对设备可见,也不使复合操作自动原子化。

例如发布描述符时,先写地址和长度,再执行适当的 dma_wmb(),最后按设备协议设置有效位;设备完成后,CPU 也可能需要 dma_rmb() 才能安全读取随完成标志发布的数据。MMIO doorbell 的访问顺序还需满足 I/O 访问器和硬件要求。屏障不等于等待 DMA 完成。

streaming 的“流式”不意味着只能映射一次,也不承诺一定比 coherent 快;性能依赖平台、缓存、映射开销和访问模式。概念与地址规则见 DMA HOWTO,具体接口见 DMA API。

3. 方向以设备和内存的数据流为准

  • DMA_TO_DEVICE:设备读取内存,例如发送网络包。
  • DMA_FROM_DEVICE:设备写入内存,例如接收网络包。
  • DMA_BIDIRECTIONAL:双方都要写,并仍需遵守所有权和同步协议。

不要只标“读”或“写”,否则 CPU 与设备两个视角正好相反。能确定方向时使用准确方向;map、sync、unmap 使用的设备、范围和方向必须符合 API 的配对要求。

一次接收可这样理解:CPU 分配合适缓冲区并映射为 FROM_DEVICE,将返回的 DMA 地址提交给设备;确认设备真正完成后解除映射,再由 CPU 读取。解除映射不是停止硬件的命令,必须先确认设备不再访问。

映射不撤销时,CPU 与设备仍要交接访问权

长期保留映射时,在确认设备完成后调用 dma_sync_single_for_cpu(),CPU 才进入访问阶段;交还设备前调用 dma_sync_single_for_device()。这些调用可能涉及缓存维护或 bounce buffer 拷贝,但不会替驱动阻止另一方同时访问。

在非一致性平台的简化模型里,把 CPU 缓存与 RAM 内容分开显示。通过同步动作转移所有权,观察设备完成与 CPU 已可读取之间还缺哪一步。

正在呈现知识画面
DMA 已写 RAM,CPU 的旧缓存为什么还不能直接用?

设备完成、缓存可见性和映射寿命是不同条件。sync 不等待仍在进行的 DMA,unmap 不停止硬件;CPU VA、物理地址和 DMA 地址也不能互换。

什么内存可以直接映射

dma_map_single() 适用于满足约束的连续内存,例如适当分配的 kmalloc 缓冲区。栈内存、内核映像地址,以及 vmalloc 返回的虚拟连续地址不能直接当通用单段 DMA 缓冲区使用。vmalloc 底层页面需要通过合适的逐页/SG 路径处理。

还应避免 CPU 字段与设备写入区域共享缓存行,在非一致性平台上这种共享可能造成数据覆盖。dma_mapping_error(dev, addr) 判断映射是否失败;DMA 地址为零并不自动代表失败。

4. Scatter-gather 的两种数量

SG 允许用多个内存片段表达一次数据传输。DMA 层可以合并相邻片段,设备看到的段数可能少于输入条目数。

以下是局部 API 用法,假设 sgl 已正确建立,且 orig_nents > 0:

int mapped_nents = dma_map_sg(dev, sgl, orig_nents, direction);
if (mapped_nents == 0)
    return -EIO;

/* 向硬件描述 DMA 段:使用映射后的数量和 sg_dma_* 字段。 */
struct scatterlist *sg;
int i;
for_each_sg(sgl, sg, mapped_nents, i) {
    dma_addr_t addr = sg_dma_address(sg);
    unsigned int len = sg_dma_len(sg);
    /* 将 addr/len 填入实际硬件描述符;此处不启动硬件。 */
    dev_dbg(dev, "DMA segment %d: %pad / %u\n", i, &addr, len);
}

/* 真正驱动必须先确认硬件已停止使用,才执行下句。 */
dma_unmap_sg(dev, sgl, orig_nents, direction);
使用位置应传哪种数量
给硬件设置 DMA 段mapped_nents
dma_unmap_sg原始 orig_nents
dma_sync_sg_for_*原始 orig_nents

映射层会生成设备视角的段,也要保留还原原始映射所需的信息。不能把合并后的计数用于全部接口。见 SG API 说明。

5. IOMMU 与 SWIOTLB 可以同时存在

IOMMU 通过页表提供地址转换、权限与隔离,还可能把离散物理页映射到连续 IOVA。它不保证任意 SG 都变成单段,仍受段长、边界和映射资源约束。中断重映射是平台提供的相关但独立能力,不能仅因存在 DMA 翻译就断言中断安全条件全部满足。

SWIOTLB 提供 bounce buffer:设备操作可访问的中间缓冲区,由 DMA 层在相应同步点搬运数据。除了设备寻址受限,机密计算中的共享内存约束等也可能使用 bounce;因此不是简单的“有 IOMMU 就无拷贝,无 IOMMU 才 SWIOTLB”。

拷贝方向与 DMA 方向有关:TO_DEVICE 需要把 CPU 准备的数据送到设备可读区域,FROM_DEVICE 则要在 CPU 接收数据时取回设备结果。是否需要拷贝、发生在哪个同步点由映射与同步路径决定,不能假设每次 map/unmap 都双向复制。

SWIOTLB 池大小参数有自己的单位,不能把 slot 数直接当 KiB。例如常见 2 KiB slot 下,65536 个 slot 是 128 MiB,而不是 64 MiB。具体参数、动态池及限制以目标内核的 SWIOTLB 文档为准。

6. Domain、group 与 VFIO

对象回答的问题
IOMMU domain使用哪套地址空间与映射策略?多个设备可共享
IOMMU group系统最小能可靠隔离哪些设备?

group 受拓扑、ACS 与设备行为影响,不是固定一项 PCI function 一组。

将一个 group 中某功能交给不受信任的用户态时,还必须控制组内其他相关设备,不能留下能够绕过隔离边界的驱动访问。这不等于要求每个 function 都实际呈现给同一 VM;具体绑定与所有权约束由 VFIO/IOMMU 接口管理。

VFIO 允许受控暴露设备资源、DMA 映射和中断。设备访问的 guest 内存底层依然是宿主物理页,保护目标是让它只能访问授权映射,不能写成“完全不能访问 host 内存”。无 IOMMU 的特殊模式也不能视为具有相同隔离保证。见 VFIO 文档。

查阅顺序

地址异常先查 DMA mask、返回值和写入描述符的地址;数据损坏再查方向、完成时机、缓存行共享及屏障;释放后损坏则查是否在硬件和回调结束前 unmap/free。IOMMU fault 能说明某次设备访问违反了映射条件,但不能仅凭 fault 就断定页表实现有错。

关联阅读:PCIe 与 USB 驱动模型、DRM 与图形。