DMA引擎控制器文件

硬體介紹

大多數從屬DMA控制器具有相同的通用操作原理。

它們具有給定數量的通道用於DMA傳輸,以及給定數量的請求線路。

請求和通道幾乎是正交的。通道可用於服務多個或任何請求。 簡單來說,通道是執行復制的實體,而請求涉及哪些端點。

請求線路實際上對應於從符合DMA條件的裝置到控制器本身的物理線路。 只要裝置想要啟動傳輸,它就會透過斷言該請求線路來斷言DMA請求(DRQ)。

一個非常簡單的DMA控制器只會考慮一個引數:傳輸大小。 在每個時鐘週期,它會將一個位元組的資料從一個緩衝區傳輸到另一個緩衝區,直到達到傳輸大小。

這在現實世界中效果不佳,因為從屬裝置可能需要以單個週期傳輸特定數量的位。 例如,我們可能希望傳輸與物理匯流排允許的資料一樣多,以在執行簡單的記憶體複製操作時最大化效能,但我們的音訊裝置可能具有更窄的FIFO,需要一次寫入16或24位的資料。 這就是為什麼大多數(如果不是全部)DMA控制器都可以使用稱為傳輸寬度的引數來調整此引數。

此外,某些DMA控制器,只要RAM用作源或目標,就可以將記憶體中的讀取或寫入分組到緩衝區中,因此您將獲得幾個更大的傳輸,而不是大量的微小記憶體訪問,這效率不高。 這是使用稱為突發大小的引數完成的,該引數定義了允許執行多少次單獨的讀取/寫入,而無需控制器將傳輸拆分為更小的子傳輸。

我們理論上的DMA控制器將只能執行涉及單個連續資料塊的傳輸。 但是,我們通常擁有的一些傳輸不是,並且希望將資料從非連續緩衝區複製到連續緩衝區,這稱為分散-聚集。

DMAEngine,至少對於mem2dev傳輸,需要支援分散-聚集。 因此,我們在這裡剩下兩種情況:要麼我們有一個相當簡單的DMA控制器不支援它,我們將不得不在軟體中實現它,要麼我們有一個更高階的DMA控制器,它在硬體中實現了分散-聚集。

後者通常使用要傳輸的塊的集合進行程式設計,並且只要傳輸開始,控制器就會遍歷該集合,執行我們在那裡程式設計的任何操作。

此集合通常是表或連結列表。 然後,您將表的地址及其元素數量或列表的第一個專案推送到DMA控制器的一個通道,並且只要斷言DRQ,它將遍歷該集合以瞭解從何處獲取資料。

無論如何,此集合的格式完全取決於您的硬體。 每個DMA控制器都需要不同的結構,但是對於每個塊,所有這些都需要至少源地址和目標地址,是否應遞增這些地址以及我們之前看到的三個引數:突發大小,傳輸寬度和傳輸大小。

最後一件事是,通常,從屬裝置預設情況下不會發出DRQ,並且只要您願意使用DMA,就必須首先在從屬裝置驅動程式中啟用它。

這些只是通用記憶體到記憶體(也稱為mem2mem)或記憶體到裝置(mem2dev)的傳輸。 大多數裝置通常支援dmaengine支援的其他型別的傳輸或記憶體操作,並且將在本文件後面詳細介紹。

Linux中的DMA支援

從歷史上看,DMA控制器驅動程式已經使用async TX API實現,以解除安裝諸如記憶體複製,XOR,加密等操作,基本上是任何記憶體到記憶體的操作。

隨著時間的推移,出現了記憶體到裝置傳輸的需求,並且dmaengine得到了擴充套件。 如今,async TX API被編寫為dmaengine之上的一個層,並充當客戶端。 儘管如此,dmaengine在某些情況下會適應該API,並做出了一些設計選擇,以確保它保持相容性。

有關Async TX API的更多資訊,請檢視非同步傳輸/轉換 API中的相關文件檔案。

DMA引擎API

struct dma_device 初始化

就像任何其他核心框架一樣,整個DMAEngine註冊依賴於驅動程式填寫一個結構並針對該框架進行註冊。 在我們的例子中,該結構是dma_device。

您需要在驅動程式中做的第一件事是分配此結構。 任何常用的記憶體分配器都可以,但是您還需要初始化其中的一些欄位

  • channels:應使用INIT_LIST_HEAD宏等將其初始化為列表

  • src_addr_widths:應包含支援的源傳輸寬度的位掩碼

  • dst_addr_widths:應包含支援的目標傳輸寬度的位掩碼

  • directions:應包含支援的從屬方向的位掩碼(即,不包括mem2mem傳輸)

  • residue_granularity:報告給dma_set_residue的傳輸剩餘粒度。 這可以是

    • 描述符:您的裝置不支援任何型別的剩餘報告。 該框架僅知道已完成特定的事務描述符。

    • 段:您的裝置能夠報告已傳輸的塊

    • 突發:您的裝置能夠報告已傳輸的突發

  • dev:應儲存指向與當前驅動程式例項關聯的struct device的指標。

支援的事務型別

接下來需要做的是設定您的裝置(和驅動程式)支援的事務型別。

我們的dma_device structure有一個名為cap_mask的欄位,其中儲存了支援的各種型別的事務,您需要使用dma_cap_set函式修改此掩碼,該函式具有各種標誌作為支援的事務型別的引數。

所有這些功能都在dma_transaction_type enum中定義,在include/linux/dmaengine.h

目前,可用的型別有

  • DMA_MEMCPY

    • 該裝置能夠執行記憶體到記憶體的複製

    • 無論源和目標組合塊的總大小是多少,都只會傳輸與兩者中最小的位元組數一樣多的位元組。 這意味著兩個列表中分散-聚集緩衝區的數量和大小不必相同,並且該操作在功能上等效於strncpy,其中count引數等於兩個分散-聚集列表緩衝區的最小總大小。

    • 通常用於在主機記憶體和記憶體對映的GPU裝置記憶體之間複製畫素資料,例如在現代PCI影片圖形卡上找到的資料。 最直接的例子是OpenGL API函式glReadPixels(),它可能需要將一個巨大的幀緩衝區從本地裝置記憶體逐字複製到主機記憶體上。

  • DMA_XOR

    • 該裝置能夠對記憶體區域執行XOR運算

    • 用於加速XOR密集型任務,例如RAID5

  • DMA_XOR_VAL

    • 該裝置能夠使用XOR演算法對記憶體緩衝區執行奇偶校驗。

  • DMA_PQ

    • 該裝置能夠執行RAID6 P+Q計算,其中P是一個簡單的XOR,Q是一個Reed-Solomon演算法。

  • DMA_PQ_VAL

    • 該裝置能夠使用RAID6 P+Q演算法對記憶體緩衝區執行奇偶校驗。

  • DMA_MEMSET

    • 該裝置能夠用提供的模式填充記憶體

    • 該模式被視為單位元組帶符號值。

  • DMA_INTERRUPT

    • 該裝置能夠觸發將生成定期中斷的虛擬傳輸

    • 客戶端驅動程式使用它來註冊一個回撥,該回調將透過DMA控制器中斷定期呼叫

  • DMA_PRIVATE

    • 這些裝置僅支援從屬傳輸,因此不適用於非同步傳輸。

  • DMA_ASYNC_TX

    • 該裝置支援非同步記憶體到記憶體操作,包括memcpy、memset、xor、pq、xor_val和pq_val。

    • 此功能由DMA引擎框架自動設定,裝置驅動程式不得手動配置。

  • DMA_SLAVE

    • 該裝置可以處理裝置到記憶體的傳輸,包括分散-聚集傳輸。

    • 在mem2mem情況下,我們需要處理兩種不同的型別來複制單個塊或它們的集合,而在這裡,我們只有一個應該處理兩者的事務型別。

    • 如果要傳輸單個連續記憶體緩衝區,只需構建一個只有一個專案的分散列表。

  • DMA_CYCLIC

    • 該裝置可以處理迴圈傳輸。

    • 迴圈傳輸是一種塊集合將迴圈自身的傳輸,其中最後一個專案指向第一個專案。

    • 它通常用於音訊傳輸,在音訊傳輸中,您希望對一個環形緩衝區進行操作,您將用您的音訊資料填充該緩衝區。

  • DMA_INTERLEAVE

    • 該裝置支援交錯傳輸。

    • 這些傳輸可以將資料從非連續緩衝區傳輸到非連續緩衝區,而不是DMA_SLAVE,DMA_SLAVE可以將資料從非連續資料集傳輸到連續目標緩衝區。

    • 它通常用於2D內容傳輸,在這種情況下,您希望將未壓縮資料的一部分直接傳輸到顯示器以進行列印

  • DMA_COMPLETION_NO_ORDER

    • 該裝置不支援按順序完成。

    • 如果裝置正在設定此功能,則驅動程式應為device_tx_status返回DMA_OUT_OF_ORDER。

    • 如果裝置匯出此功能,則所有cookie跟蹤和檢查API都應視為無效。

    • 在這一點上,這與dmatest的輪詢選項不相容。

    • 如果設定了此上限,則建議使用者為傳送到DMA裝置的每個描述符提供唯一的識別符號,以便正確跟蹤完成情況。

  • DMA_REPEAT

    • 該裝置支援重複傳輸。 重複傳輸(由DMA_PREP_REPEAT傳輸標誌指示)類似於迴圈傳輸,因為它在結束時會自動重複,但也可以由客戶端替換。

    • 此功能僅限於交錯傳輸,因此,如果未設定DMA_INTERLEAVE標誌,則不應設定此標誌。 此限制基於DMA客戶端的當前需求,如果將來需要,則應新增對其他傳輸型別的支援。

  • DMA_LOAD_EOT

    • 該裝置支援在傳輸結束時(EOT)透過對設定了DMA_PREP_LOAD_EOT標誌的新傳輸進行排隊來替換重複傳輸。

    • 在另一個點(例如突發結束而不是傳輸結束)替換當前正在執行的傳輸的支援將在以後根據DMA客戶端的需求新增,如果將來需要的話。

這些各種型別也會影響源地址和目標地址隨時間的變化方式。

指向RAM的地址通常在每次傳輸後遞增(或遞減)。 對於環形緩衝區,它們可能會迴圈(DMA_CYCLIC)。 指向裝置暫存器(例如FIFO)的地址通常是固定的。

每個描述符的元資料支援

一些資料移動架構(DMA控制器和外設)使用與事務關聯的元資料。 DMA控制器的作用是並行傳輸有效載荷和元資料。 元資料本身不被DMA引擎使用,但它包含外設或來自外設的引數,金鑰,向量等。

DMAengine框架提供了一種通用的方法來促進描述符的元資料。 根據體系結構,DMA驅動程式可以實現這兩種方法中的任何一種或全部,並且由客戶端驅動程式選擇使用哪種方法。

  • DESC_METADATA_CLIENT

    元資料緩衝區由客戶端驅動程式分配/提供,並透過dmaengine_desc_attach_metadata()輔助函式附加到描述符。

    對於此模式,希望從DMA驅動程式獲得以下內容

    • DMA_MEM_TO_DEV / DEV_MEM_TO_MEM

      應該準備來自所提供的元資料緩衝區的資料,以便DMA控制器與有效載荷資料一起傳送。 透過複製到硬體描述符或高度耦合的資料包。

    • DMA_DEV_TO_MEM

      在傳輸完成時,DMA驅動程式必須將元資料複製到客戶端提供的元資料緩衝區,然後再通知客戶端有關完成情況。 在傳輸完成後,DMA驅動程式不得觸控客戶端提供的元資料緩衝區。

  • DESC_METADATA_ENGINE

    元資料緩衝區由DMA驅動程式分配/管理。 客戶端驅動程式可以詢問元資料的指標,最大大小和當前使用的元資料大小,並且可以直接更新或讀取它。 dmaengine_desc_get_metadata_ptr()和dmaengine_desc_set_metadata_len()作為輔助函式提供。

    對於此模式,希望從DMA驅動程式獲得以下內容

    • get_metadata_ptr()

      應返回元資料緩衝區的指標,元資料緩衝區的最大大小以及緩衝區中當前使用的/有效的(如果有)位元組數。

    • set_metadata_len()

      客戶端在將元資料放置到緩衝區中後呼叫它,以使DMA驅動程式知道提供的有效位元組數。

    注意:由於客戶端將在完成回撥中(在DMA_DEV_TO_MEM情況下)請求元資料指標,因此DMA驅動程式必須確保在呼叫回撥之前不會釋放描述符。

裝置操作

現在我們已經描述了我們能夠執行的操作,我們的dma_device結構還需要一些函式指標才能實現實際邏輯。

我們必須在那裡填寫的函式,因此必須實現,顯然取決於您報告為支援的事務型別。

  • device_alloc_chan_resources

  • device_free_chan_resources

    • 每當驅動程式首次/最後一次在與該驅動程式關聯的通道上呼叫dma_request_channeldma_release_channel時,將呼叫這些函式。

    • 它們負責分配/釋放所有需要的資源,以便該通道對您的驅動程式有用。

    • 這些函式可以睡眠。

  • device_prep_dma_*

    • 這些函式與您之前註冊的功能相匹配。

    • 這些函式都獲取與準備傳輸相關的緩衝區或散列表,並且應從中建立硬體描述符或硬體描述符列表

    • 可以從中斷上下文中呼叫這些函式

    • 您可能執行的任何分配都應使用GFP_NOWAIT標誌,以便不會潛在地休眠,但也不會耗盡緊急池。

    • 驅動程式應嘗試在探測時預先分配在傳輸設定期間可能需要的任何記憶體,以避免對nowait分配器施加太大的壓力。

    • 它應返回dma_async_tx_descriptor structure的唯一例項,該例項進一步表示此特定傳輸。

    • 可以使用函式dma_async_tx_descriptor_init初始化此結構。

    • 您還需要在此結構中設定兩個欄位

      • flags:TODO:它可以由驅動程式本身修改,還是應該始終是引數中傳遞的標誌

      • tx_submit:指向您必須實現的函式的指標,該函式應該將當前事務描述符推送到掛起的佇列,等待呼叫issue_pending。

    • 在此結構中,可以初始化函式指標callback_result,以便通知提交者事務已完成。 在較早的程式碼中,已經使用了函式指標callback。 但是,它不提供任何事務狀態,將被棄用。 作為dmaengine_result定義的result結構將傳遞給callback_result,該結構具有兩個欄位

      • result:這提供了由dmaengine_tx_result定義的傳輸結果。 成功或某些錯誤情況。

      • residue:為那些支援剩餘的使用者提供傳輸的剩餘位元組。

  • device_prep_peripheral_dma_vec

    • 類似於device_prep_slave_sg,但是它採用指向dma_vec結構陣列的指標,該陣列(從長遠來看)將取代散列表。

  • device_issue_pending

    • 採用掛起佇列中的第一個事務描述符,然後啟動傳輸。 每當該傳輸完成時,它應該移動到列表中的下一個事務。

    • 可以在中斷上下文中呼叫此函式

  • device_tx_status

    • 應該報告給定通道上要進行的剩餘位元組數

    • 應該只關心作為引數傳遞的事務描述符,而不是給定通道上當前活動的事務描述符

    • tx_state引數可能為NULL

    • 應該使用dma_set_residue進行報告

    • 在迴圈傳輸的情況下,它應該只考慮迴圈緩衝區的總大小。

    • 如果裝置不支援按順序完成並且正在按順序完成操作,則應返回DMA_OUT_OF_ORDER。

    • 可以在中斷上下文中呼叫此函式。

  • device_config

    • 使用作為引數給出的配置重新配置通道

    • 此命令不應同步執行,也不應在任何當前排隊的傳輸上執行,而只能在後續傳輸上執行

    • 在這種情況下,該函式將接收dma_slave_config結構指標作為引數,該引數將詳細說明要使用的配置。

    • 即使該結構包含方向欄位,但此欄位已被棄用,而贊成提供給prep_*函式的direction引數

    • 此呼叫僅對從屬操作是強制性的。 不應為memcpy操作設定或期望設定此引數。 如果驅動程式同時支援這兩種操作,則應僅將此呼叫用於從屬操作,而不用於memcpy操作。

  • device_pause

    • 暫停通道上的傳輸

    • 此命令應在通道上同步操作,立即暫停給定通道的工作

  • device_resume

    • 恢復通道上的傳輸

    • 此命令應在通道上同步操作,立即恢復給定通道的工作

  • device_terminate_all

    • 中止通道上所有掛起和正在進行的傳輸

    • 對於中止的傳輸,不應呼叫完整的回撥

    • 可以從原子上下文中呼叫,也可以從描述符的完整回撥中呼叫。 不得睡眠。 驅動程式必須能夠正確處理此問題。

    • 終止可能是非同步的。 驅動程式不必等到當前活動的傳輸完全停止。 請參閱device_synchronize。

  • device_synchronize

    • 必須將通道的終止同步到當前上下文。

    • 必須確保DMA控制器不再訪問先前提交的描述符的記憶體。

    • 必須確保先前提交的描述符的所有完整回撥都已完成執行,並且沒有計劃執行的回撥。

    • 可以睡眠。

其他說明

(應該記錄的內容,但不知道在哪裡放置它們)

dma_run_dependencies

  • 應在非同步TX傳輸結束時呼叫,並且可以在從屬傳輸情況下忽略。

  • 確保在將其標記為完成之前執行相關操作。

dma_cookie_t

  • 它是一個DMA事務ID,會隨著時間的推移而遞增。

  • 自從引入了抽象它的virt-dma以來,不再真正相關。

dma_vec

  • 一個包含DMA地址和長度的小結構。

DMA_CTRL_ACK

  • 如果清除,則在客戶端確認收到(即有機會建立任何依賴關係鏈)之前,提供程式無法重用描述符

  • 可以透過呼叫async_tx_ack()來確認

  • 如果設定,並不意味著可以重用描述符

DMA_CTRL_REUSE

  • 如果設定,則在完成後可以重用描述符。 如果設定了此標誌,則不應由提供程式釋放它。

  • 應該透過呼叫dmaengine_desc_set_reuse()來準備要重用的描述符,這將設定DMA_CTRL_REUSE。

  • dmaengine_desc_set_reuse() 只有在通道支援可重用描述符時才會成功,這由通道的功能 (capabilities) 決定。

  • 因此,如果裝置驅動程式想要在兩次傳輸之間跳過 dma_map_sg()dma_unmap_sg(),因為 DMA 的資料沒有被使用,它可以完成傳輸後立即重新提交傳輸。

  • 描述符可以通過幾種方式釋放:

    • 透過呼叫 dmaengine_desc_clear_reuse() 清除 DMA_CTRL_REUSE 並在最後一筆事務 (txn) 中提交。

    • 顯式呼叫 dmaengine_desc_free(),只有在 DMA_CTRL_REUSE 已經設定時才會成功。

    • 終止通道。

  • DMA_PREP_CMD

    • 如果設定,客戶端驅動程式告訴 DMA 控制器,DMA API 中傳遞的資料是命令資料。

    • 命令資料的解釋是 DMA 控制器特定的。它可以用於向其他外設傳送命令/暫存器讀取/暫存器寫入,此時描述符應該與普通資料描述符的格式不同。

  • DMA_PREP_REPEAT

    • 如果設定,傳輸將在結束後自動重複,直到同一通道上排隊了帶有 DMA_PREP_LOAD_EOT 標誌的新傳輸。如果通道上排隊的下一個傳輸沒有設定 DMA_PREP_LOAD_EOT 標誌,則當前傳輸將重複,直到客戶端終止所有傳輸。

    • 只有當通道報告 DMA_REPEAT 能力時,才支援此標誌。

  • DMA_PREP_LOAD_EOT

    • 如果設定,傳輸將在傳輸結束時替換當前正在執行的傳輸。

    • 這是非重複傳輸的預設行為,因此為非重複傳輸指定 DMA_PREP_LOAD_EOT 不會有任何區別。

    • 當使用重複傳輸時,DMA 客戶端通常需要在所有傳輸上設定 DMA_PREP_LOAD_EOT 標誌,否則通道將繼續重複上一次重複的傳輸,並忽略正在排隊的新傳輸。未能設定 DMA_PREP_LOAD_EOT 將表現為通道卡在上一個傳輸上。

    • 只有當通道報告 DMA_LOAD_EOT 能力時,才支援此標誌。

通用設計說明

您將看到的大多數 DMAEngine 驅動程式都基於類似的設計,該設計在處理程式中處理傳輸結束中斷,但將大部分工作推遲到 tasklet,包括啟動新的傳輸,只要前一個傳輸結束。

然而,這是一個相當低效的設計,因為傳輸間延遲不僅是中斷延遲,而且是 tasklet 的排程延遲,這將使通道在兩者之間空閒,從而減慢全域性傳輸速率。

您應該避免這種做法,並且不要在您的 tasklet 中選擇新的傳輸,而是將該部分移動到中斷處理程式中,以便擁有更短的空閒視窗(無論如何我們都無法真正避免)。

詞彙表

  • Burst: 在重新整理到記憶體之前可以排隊到緩衝區的一系列連續讀取或寫入操作。

  • Chunk: 連續的 burst 集合

  • Transfer: chunk 的集合(無論是否連續)