我试图弄清楚 Linux 如何以及何时授予对 PCIe 上的外围设备执行直接内存访问 (DMA) 的权限。我已经读过 DMA 是如何在内核中启动的《DMA API 指南》,但仍想澄清几点。
这个问题与这个问题相关如何强化Linux抵御DMA攻击?或许关于 IOMMU 的评论就是答案。 HOWTO 顺便提到了 IOMMU,说
I/O 设备使用第三种地址:“总线地址”。如果设备在 MMIO 地址处有寄存器,或者执行 DMA 来读取或写入系统内存,则设备使用的地址是总线地址。在某些系统中,总线地址与 CPU 物理地址相同,但通常情况不同。 IOMMU 和主桥可以在物理地址和总线地址之间生成任意映射。
但由于有关 DMA 攻击的问题的答案建议在不参考 IOMMU 的情况下减轻攻击,我想知道带有 IOMMU 的系统有多常见?这是服务器的常见架构,但 PC 却不是这样?
如果访问是由 IOMMU 映射定义的,那么它们何时设置?
在“哪些内存支持 DMA?”中他们写的 HOWTO
您必须了解的第一条信息是 DMA 映射工具可以使用哪些内核内存。对此有一套不成文的规则,本文试图最终将它们写下来。
如果您通过页面分配器(即 __get_free_page*())或通用内存分配器(即 kmalloc() 或 kmem_cache_alloc())获取内存,那么您可以使用从这些例程返回的地址与该内存进行 DMA 传输。
但他们没有提到向设备授予一些权限并实际启用 DMA。这让我想知道设备是否可以在没有任何许可的情况下立即写入该内存。如果是这样,则意味着设备也可以写入任何其他内存,因为分配是操作系统中的软件业务。
问题是何时可以从 PCIe 总线写入主内存块?
但接下来可能是答案:
为了正确操作,您必须设置 DMA 掩码以告知内核您的设备 DMA 寻址功能。
这是通过调用
dma_set_mask_and_coherent()
::来执行的int dma_set_mask_and_coherent(struct device *dev, u64 mask);
这将为流式 API 和一致性 API 一起设置掩码。
这些调用通常返回零,表示您的设备可以在给定您提供的地址掩码的机器上正确执行 DMA,但如果掩码太小而无法在给定系统上支持,则它们可能会返回错误。如果它返回非零,则您的设备无法在此平台上正确执行 DMA,并且尝试这样做将导致未定义的行为。除非该
dma_set_mask
函数系列返回成功,否则不得在此设备上使用 DMA 。
啊哈!那么,DMA 掩码是否定义了对内存的访问?函数系列是否是dma_set_mask
启用主内存 DMA 的 API,无论其分配方式如何?基本上,“为了正确操作,您必须设置 DMA 掩码以通知内核您的设备 DMA 寻址功能”是什么意思,您用 DMA 掩码通知内核什么?它是否通过某些软件或硬件技术(如IOMMU)定义了从PCIe总线对物理内存的访问,或者它只是通知内核一块内存可以被外部设备使用,因此内核不能使用它出于其自身目的,例如系统的虚拟内存。最后一种情况似乎更有可能,因为您只是“告知..您的设备 DMA 寻址功能”。然后系统仍然对外部设备的随机 DMA 访问开放。
另外,我是否理解正确,IOMMU 的工作方式就像 MMU:它为每个设备设置内存映射?因此,IOMMU 将阻止任何尝试访问允许映射之外的内存的设备。它是否向 CPU 发送一些中断,例如段错误?在这种情况下,不存在“未定义的行为”,只是阻止访问,仅此而已。
也许,这个问题也与无法从 PCIe 设备访问 RAM,当 CPU 运行 Windows 或另一台 PC 上的 Linux 下时,可以从 PCIe 上的 FPGA 访问 RAM,但在 Linux 下的主 PC 上无法访问。因此,Windows 以某种方式允许访问 RAM,但 Linux 不允许。 Linux 必须做什么才能允许 DMA?