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 转换为 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 时,usb_sg_init() 将尝试以 PIO 方式提交 URB,只要 scatterlists 中的页面不在 Highmem 中,这在现代架构中可能非常罕见。