PCIe驱动中dma_alloc_coherent内存无法被用户态mmap访问的问题
PCIe驱动dma_alloc_coherent内存mmap失效问题排查与解决
针对你遇到的dma_alloc_coherent()分配的内存mmap后用户态读到0xFFFF(IOMMU占位数据)、内核态正常的问题,结合6.1.0-25-amd64环境和FPGA PCIe回环场景,给出以下排查和解决思路:
1. 确认dma_mmap_coherent调用参数正确性
dma_mmap_coherent()是映射一致性内存的标准接口,参数错误会直接导致映射失败。需确保:
- 传入的
struct device*是PCIe设备的&pdev->dev,而非其他设备指针(比如NULL或平台设备指针) - 缓冲区的
cpu_addr、dma_addr、size完全匹配dma_alloc_coherent()的返回值 - 虚拟内存区域(vma)的权限和属性设置正确,必须添加
VM_SHARED标记,并设置非缓存页属性
示例正确实现:
static int pcie_dev_mmap(struct file *filp, struct vm_area_struct *vma) { struct pcie_dev *dev = filp->private_data; size_t map_size = vma->vm_end - vma->vm_start; if (map_size > dev->coherent_buf_size) return -EINVAL; // 设置共享属性与非缓存页保护 vma->vm_flags |= VM_SHARED; vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); return dma_mmap_coherent(&dev->pdev->dev, vma, dev->coherent_cpu_addr, dev->coherent_dma_addr, dev->coherent_buf_size); }
2. 排查IOMMU配置影响
6.1内核x86_64默认可能开启IOMMU,此时dma_alloc_coherent()返回的dma_addr是IO虚拟地址(IOVA),而非物理内存的真实物理地址。若驱动未正确关联设备的IOMMU域,dma_mmap_coherent()无法建立正确的映射关系,导致用户态读到占位数据。
排查要点:
- 执行
dmesg | grep -i iommu确认系统是否开启IOMMU - 驱动probe阶段必须设置正确的DMA掩码,确保设备支持64位地址:
int 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; }
- 确保
dma_alloc_coherent()调用时传入的是&pdev->dev,让内核关联到正确的IOMMU域
3. 理解kmalloc+dma_map_single的临时映射特性
你用kmalloc()+dma_map_single()配合virt_to_phys()能成功,是因为kmalloc()分配的是内核常规内存,virt_to_phys()获取的是真实物理地址,且你的系统可能关闭了CONFIG_STRICT_DEVMEM,允许用户态直接映射物理页。但这种方式不符合DMA规范:在IOMMU开启时,设备访问的是IOVA,用户态访问的是物理地址,存在缓存一致性风险,仅适合临时测试。
4. 检查内存对齐与页属性
dma_alloc_coherent()要求分配的size是PAGE_SIZE的整数倍,若未对齐,dma_mmap_coherent()可能返回错误- 用户态映射必须使用非缓存页属性,否则缓存会导致用户态读到过期数据或占位值
5. 内核版本特定问题验证
6.1内核中dma_mmap_coherent()对超大页、IOMMU域的处理有细节变化,可尝试:
- 缩小缓冲区大小到单个页(4KB)测试,排除超大页映射问题
- 检查内核配置是否开启
CONFIG_DMA_DIRECT_REMAP,该选项会影响一致性内存的映射路径
验证步骤
- 在
dma_alloc_coherent()调用后打印cpu_addr、dma_addr、virt_to_phys(cpu_addr),若dma_addr与真实物理地址不同,说明IOMMU处于开启状态 - 在mmap函数中打印
vma->vm_pgoff、map_size,确认偏移和大小参数正确 - 检查
dma_mmap_coherent()的返回值,若为负数,通过pr_err()打印错误码针对性排查
内容的提问来源于stack exchange,提问作者lazerbeam
相关产品推荐
相关产品推荐

