You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

AMD IOMMU IO_PAGE_FAULT故障排查及驱动适配咨询

AMD-Vi IO_PAGE_FAULT 错误原因及驱动适配方案

错误原因分析

  • 无效DMA地址传递:报错地址0xfffffffffffffffc是一个无效的64位IOVA(IO虚拟地址),说明驱动直接传递了内核虚拟地址、未映射的物理地址,或者未初始化的指针给设备DMA控制器。当IOMMU启用时,设备只能访问经过IOMMU映射的IOVA,无法直接解析内核虚拟地址或裸物理地址,因此触发页错误。
  • 未适配DMA API:禁用IOMMU时,设备可以直接访问物理内存,驱动的错误操作不会暴露;但启用IOMMU后,必须通过内核DMA API完成地址映射,否则IOMMU无法识别设备发起的地址请求。
  • DMA掩码不匹配:设备DMA控制器的地址宽度与IOMMU配置的地址宽度不匹配,导致地址被截断或溢出,生成无效地址。

驱动需要添加的适配代码(基于Linux标准DMA API)

1. 设置DMA地址掩码

在驱动probe函数中,必须设置设备支持的DMA地址宽度,确保IOMMU使用正确的地址范围:

int ret;
// 设置64位DMA掩码(根据设备实际支持的宽度调整)
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
if (ret) {
    dev_err(&pdev->dev, "Failed to set DMA mask\n");
    return ret;
}

2. 分配DMA可用内存(替代直接物理内存分配)

如果驱动需要分配内存供设备DMA访问,使用dma_alloc_coherent()自动完成IOMMU映射:

// 分配4KB连续内存,返回内核虚拟地址(供CPU访问)和IOVA(供设备访问)
void *cpu_addr = dma_alloc_coherent(&pdev->dev, 4096, &dma_addr, GFP_KERNEL);
if (!cpu_addr) {
    dev_err(&pdev->dev, "Failed to allocate DMA coherent memory\n");
    return -ENOMEM;
}
// 使用dma_addr作为设备DMA控制器的目标地址
// 释放时调用dma_free_coherent(&pdev->dev, 4096, cpu_addr, dma_addr);

3. 映射分散聚集(Scatter-Gather)表项

针对设备的SG能力,使用dma_map_sg()映射每个SG表项,获取设备可访问的IOVA:

struct scatterlist sg[NUM_SG_ENTRIES];
int sg_count;
dma_addr_t sg_dma_addrs[NUM_SG_ENTRIES];
int i;

// 初始化SG表(假设已经填充了物理页或内核虚拟地址)
sg_init_table(sg, NUM_SG_ENTRIES);
// 填充SG表项(示例)
sg_set_buf(&sg[0], cpu_addr1, len1);
sg_set_buf(&sg[1], cpu_addr2, len2);

// 映射SG表到IOVA,返回映射的表项数量
sg_count = dma_map_sg(&pdev->dev, sg, NUM_SG_ENTRIES, DMA_TO_DEVICE);
if (sg_count != NUM_SG_ENTRIES) {
    dev_err(&pdev->dev, "Failed to map SG list\n");
    return -EIO;
}

// 提取每个SG表项的IOVA,传给设备DMA控制器
for (i = 0; i < sg_count; i++) {
    sg_dma_addrs[i] = sg_dma_address(&sg[i]);
}

// DMA操作完成后,解除映射
dma_unmap_sg(&pdev->dev, sg, NUM_SG_ENTRIES, DMA_TO_DEVICE);

4. 映射用户空间缓冲区

如果测试程序从用户空间传递缓冲区,需要用DMA API映射用户空间内存:

// 用户空间缓冲区地址和长度
void __user *user_buf;
size_t buf_len;
dma_addr_t dma_addr;

// 映射用户空间缓冲区到IOVA(DMA_TO_DEVICE表示设备向缓冲区写入)
dma_addr = dma_map_single(&pdev->dev, user_buf, buf_len, DMA_TO_DEVICE);
if (dma_mapping_error(&pdev->dev, dma_addr)) {
    dev_err(&pdev->dev, "Failed to map user buffer\n");
    return -EFAULT;
}

// 使用dma_addr作为设备DMA目标地址
// 操作完成后解除映射
dma_unmap_single(&pdev->dev, dma_addr, buf_len, DMA_TO_DEVICE);

5. 缓存同步(可选但重要)

当设备完成DMA写入后,需要同步缓存确保CPU能正确读取数据:

// 同步单一缓冲区
dma_sync_single_for_cpu(&pdev->dev, dma_addr, buf_len, DMA_FROM_DEVICE);
// 同步SG表
dma_sync_sg_for_cpu(&pdev->dev, sg, sg_count, DMA_FROM_DEVICE);

关键注意事项

  • 所有传给设备DMA控制器的地址必须是dma_addr_t类型的IOVA,而非内核虚拟地址或phys_addr_t物理地址。
  • 必须在DMA操作完成后解除地址映射,避免内存泄漏或IOMMU表项耗尽。
  • 如果设备支持32位DMA,需将DMA掩码设置为DMA_BIT_MASK(32),避免生成64位无效地址。

内容的提问来源于stack exchange,提问作者gyuyoun

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 20:22:43