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” 字段,以及一个传输标志位,指示它是否有效。(控制请求也有 “setup_dma”,但驱动程序不得使用它。)
如果感知 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 一致”),并且从
kmalloc()
返回的内存将工作得很好。返回的内存缓冲区是“DMA 一致”的;有时您可能需要通过使用内存屏障来强制执行一致的内存访问顺序。它不使用流式 DMA 映射,因此对于在 I/O 会以其他方式颠簸 IOMMU 映射的系统上进行小传输非常有用。(有关“一致”和“流式”DMA 映射的定义,请参阅动态 DMA 映射指南。)
请求页面的 1/N(以及请求 N 个页面)在空间上是相当高效的。
在大多数系统上,返回的内存将是未缓存的,因为 DMA 一致内存的语义要求要么绕过 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 控制器映射的散列表时,您可以使用新的
usb_sg_*()
调用,这将把散列表转换为 URB。int 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 时,只要散列表中的页面不在 Highmem 中,
usb_sg_init()
就会尝试以 PIO 方式提交 URB,这在现代架构中可能非常罕见。