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,这在现代架构中可能非常罕见。