USB DMA¶
在 Linux 2.5 内核(以及更高版本)中,USB 设备驱动程序可以更好地控制如何使用 DMA 执行 I/O 操作。 API 详细信息请参见内核 USB 编程指南(源自源代码的 kerneldoc)。
API 概述¶
总体情况是,USB 驱动程序可以继续忽略大多数 DMA 问题,但它们仍然必须提供 DMA 就绪缓冲区(请参阅动态 DMA 映射指南)。这是它们在 2.4(和更早版本)内核中工作的方式,或者它们现在可以了解 DMA。
DMA 感知 USB 驱动程序
新的调用启用了 DMA 感知驱动程序,允许它们分配 DMA 缓冲区并管理现有 DMA 就绪缓冲区的 DMA 映射(见下文)。
URB 具有一个额外的“transfer_dma”字段,以及一个 transfer_flags 位,指示其是否有效。(控制请求也有“setup_dma”,但驱动程序不得使用它。)
如果 DMA 感知驱动程序没有首先执行此操作并设置
URB_NO_TRANSFER_DMA_MAP
,“usbcore”将映射此 DMA 地址。 HCD 不管理 URB 的 DMA 映射。有一个新的“通用 DMA API”,其中一部分可供 USB 设备驱动程序使用。 永远不要在任何 USB 接口或设备上使用 dma_set_mask(); 这可能会破坏共享该总线的所有设备。
消除复制¶
最好避免让 CPU 不必要地复制数据。 成本会累加,并且像缓存刷新这样的影响会带来细微的惩罚。
如果您一直从同一缓冲区进行大量小数据传输,那么在系统上使用 IOMMU 来管理 DMA 映射会真正耗尽资源。 为每个请求设置和拆除 IOMMU 映射的成本可能比执行 I/O 高得多!
对于这些特定情况,USB 具有分配成本较低内存的原语。 它们的工作方式类似于 kmalloc 和 kfree 版本,为您提供正确的地址类型以存储在 urb->transfer_buffer 和 urb->transfer_dma 中。 您还可以在 urb->transfer_flags 中设置
URB_NO_TRANSFER_DMA_MAP
void *usb_alloc_coherent (struct usb_device *dev, size_t size, int mem_flags, dma_addr_t *dma); void usb_free_coherent (struct usb_device *dev, size_t size, void *addr, dma_addr_t dma);
大多数驱动程序应该不使用这些原语; 它们不需要使用这种类型的内存(“dma-coherent”),并且从
kmalloc()
返回的内存就可以正常工作。返回的内存缓冲区是“dma-coherent”; 有时您可能需要通过使用内存屏障来强制执行一致的内存访问顺序。 它不使用流式 DMA 映射,因此它适用于在 I/O 否则会刷新 IOMMU 映射的系统上的小传输。(有关“coherent”和“streaming” DMA 映射的定义,请参阅 动态 DMA 映射指南。)
请求 1/N 页(以及请求 N 页)在空间上是相当有效的。
在大多数系统上,返回的内存将是未缓存的,因为 dma-coherent 内存的语义要求绕过 CPU 缓存或使用具有总线窥探支持的缓存硬件。 虽然 x86 硬件具有这种总线窥探功能,但许多其他系统使用软件来刷新缓存行以防止 DMA 冲突。
某些 EHCI 控制器上的设备可以处理与高位内存的 DMA。
不幸的是,当前的 Linux DMA 基础设施没有一种合理的方式来暴露这些功能... 并且在任何情况下,HIGHMEM 主要是 x86_32 特有的设计缺陷。 因此,最好的办法是确保您永远不要将高位内存缓冲区传递到 USB 驱动程序中。 这很容易; 这是默认行为。 只是不要覆盖它; 例如使用
NETIF_F_HIGHDMA
。这可能会迫使您的调用者进行一些反弹缓冲,从高位内存复制到“正常”DMA 内存。 如果您可以提出一个解决此问题的好方法(对于具有超过 1 GB 内存的 x86_32 机器),请随时提交补丁。
使用现有缓冲区¶
在首先将其映射到设备的 DMA 地址空间之前,现有缓冲区不可用于 DMA。 但是,传递给您驱动程序的大多数缓冲区都可以安全地用于此类 DMA 映射。(请参阅动态 DMA 映射指南的第一部分,标题为“哪些内存是 DMA 可用的?”)
当您拥有已为 USB 控制器映射的 scatterlists 时,您可以使用新的
usb_sg_*()
调用,该调用会将 scatterlist 转换为 URBint usb_sg_init(struct usb_sg_request *io, struct usb_device *dev, unsigned pipe, unsigned period, struct scatterlist *sg, int nents, size_t length, gfp_t mem_flags); void usb_sg_wait(struct usb_sg_request *io); void usb_sg_cancel(struct usb_sg_request *io);
当 USB 控制器不支持 DMA 时,
usb_sg_init()
将尝试以 PIO 方式提交 URB,只要 scatterlists 中的页面不在 Highmem 中,这在现代架构中可能非常罕见。