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 中,這在現代架構中可能非常罕見。