在linux中,函数
dma_alloc_coherent()
具有这种形式(在include/linux/dma-mapping.h中声明)
static inline void *dma_alloc_coherent(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t gfp)
返回的地址是分配的缓冲区的内核地址,dma_handle 由内核用设备看到的 DMA 地址填充。 document说,在某些情况下返回的 dma_handle 只是物理地址,但是当有 IOMMU 连接到设备时,它是另一个虚拟地址,应该转换为物理地址。
我最近为应该与 IOMMU 一起使用的设备编写了一个驱动程序(临时或测试驱动程序),但因为我们的硬件没有配备它,所以我将(用户空间虚拟地址)到(物理地址)的转换放在驱动程序,以便设备可以按原样使用“地址”。传递到设备的数据结构包含一些“地址”。稍后此地址转换将被删除,取而代之的是添加 IOMMU 驱动程序,以便由 IOMMU 完成“某些虚拟”到“物理”地址转换。
我的问题是,什么情况下dma_handle物理地址是?在我的测试驱动程序中,我使用了
get_user_pages()
、kmap()
和 virt_to_phys()
等函数,或者在某些情况下映射大页面,但我想到我可以只使用 dma_alloc_coherent()
并使用 dma_handle 如果在某些情况下,该值是物理地址。 (实际上小缓冲区是由一些数据指向的,我想我应该使用dma_pool_alloc()
,这是针对小缓冲区的。)
这纯粹是我的阅读理解和一些代码浏览。看起来返回到 dma_handle 的任何内容都应该按原样提供给设备,并且它是正确的。不要自己进行任何地址转换。
我正在阅读你指出的内核文档。
第一节似乎表明 kmalloc() + dma_map_single() == dma_alloc_coherent()
在 VA = kmalloc() 中,你得到 VA,当然内核也知道它是 PA。然后将 VA 传递给 dma_map_single(),您将获得 dma VA(如果没有 IOMMU,则获得 dma PA),本页将其称为总线地址空间
我浏览了 dma_alloc_coherent() 的代码,发现了一些线索:
// no IOMMU: this seems to be returning physical
if (dma_alloc_direct(dev, ops))
cpu_addr = dma_direct_alloc(dev, size, dma_handle, flag, attrs);
// IOMMU or possibly some other dma_map_ops ??? not sure why we need an abstraction for DMA device
else if (ops->alloc)
cpu_addr = ops->alloc(dev, size, dma_handle, flag, attrs);
ops->alloc() 可以是
drivers/iommu/dma-iommu.c:iommu_dma_ops
中的 iommu_dma_alloc()
继续沿着这条路走,你确实会发现以下任意一个:
return iommu_dma_alloc_remap(dev, size, handle, gfp,
dma_pgprot(dev, PAGE_KERNEL, attrs), attrs);
*handle = __iommu_dma_map(dev, page_to_phys(page), size, ioprot,
dev->coherent_dma_mask);
回到 dma_map_single()。
您还可以在这里发现直接与操作(PA 与 IOMMU)的类似分歧:
if (dma_map_direct(dev, ops) ||
arch_dma_map_page_direct(dev, page_to_phys(page) + offset + size))
addr = dma_direct_map_page(dev, page, offset, size, dir, attrs);
else
addr = ops->map_page(dev, page, offset, size, dir, attrs);
ops->map_page() 有一种可能是 iommu_dma_map_page() 现在我们回到了
drivers/iommu/dma-iommu.c
最后,这为您提供了设备应在总线地址空间中使用的地址
iova = __iommu_dma_map(dev, phys, size, prot, dma_mask);