DMA 引擎 API 指南

Vinod Koul <vinod dot koul at intel.com>

注意

對於 async_tx 中的 DMA 引擎用法,請參見:Documentation/crypto/async-tx-api.rst

以下是裝置驅動程式編寫者如何使用 DMA 引擎的 Slave-DMA API 的指南。這僅適用於從屬 DMA 用法。

DMA 用法

從屬 DMA 用法包括以下步驟

  • 分配一個 DMA 從屬通道

  • 設定從屬和控制器特定的引數

  • 獲取事務的描述符

  • 提交事務

  • 發出掛起的請求並等待回撥通知

這些操作的詳細資訊如下

  1. 分配一個 DMA 從屬通道

    通道分配在從屬 DMA 上下文中略有不同,客戶端驅動程式通常只需要來自特定 DMA 控制器的通道,甚至在某些情況下需要特定的通道。要請求通道,請使用 dma_request_chan() API。

    介面

    struct dma_chan *dma_request_chan(struct device *dev, const char *name);
    

    這將查詢並返回與“dev”裝置關聯的 name DMA 通道。關聯是透過基於 DT、ACPI 或板載檔案的 dma_slave_map 匹配表完成的。

    透過此介面分配的通道是呼叫者專有的,直到呼叫 dma_release_channel() 為止。

  2. 設定從屬和控制器特定的引數

    下一步始終是將一些特定資訊傳遞給 DMA 驅動程式。從屬 DMA 可以使用的大部分通用資訊都在 struct dma_slave_config 中。這允許客戶端為外圍裝置指定 DMA 方向、DMA 地址、匯流排寬度、DMA 突發長度等。

    如果某些 DMA 控制器有更多引數要傳送,那麼它們應該嘗試將 struct dma_slave_config 嵌入到它們控制器特定的結構中。這為客戶端提供了靈活性,可以在需要時傳遞更多引數。

    介面

    int dmaengine_slave_config(struct dma_chan *chan,
                      struct dma_slave_config *config)
    

    有關結構成員的詳細說明,請參見 dmaengine.h 中的 dma_slave_config 結構定義。請注意,“direction”成員將會消失,因為它複製了 prepare 呼叫中給出的方向。

  3. 獲取事務的描述符

對於從屬用法,DMA 引擎支援的各種從屬傳輸模式是

  • slave_sg:從外圍裝置 DMA 傳輸/向外圍裝置 DMA 傳輸散點收集緩衝區列表

  • peripheral_dma_vec:從外圍裝置 DMA 傳輸/向外圍裝置 DMA 傳輸散點收集緩衝區陣列。類似於 slave_sg,但使用 dma_vec 結構陣列而不是散列表。

  • dma_cyclic:執行從外圍裝置 DMA 傳輸/向外圍裝置 DMA 傳輸的迴圈 DMA 操作,直到顯式停止該操作。

  • interleaved_dma:這對於從屬客戶端和 M2M 客戶端都是通用的。對於從屬裝置,裝置 FIFO 的地址可能已經為驅動程式所知。可以透過為“dma_interleaved_template”成員設定適當的值來表達各種型別的操作。如果通道支援,也可以透過設定 DMA_PREP_REPEAT 傳輸標誌來進行迴圈交錯 DMA 傳輸。

此傳輸 API 的非 NULL 返回值表示給定事務的“描述符”。

介面

struct dma_async_tx_descriptor *dmaengine_prep_slave_sg(
           struct dma_chan *chan, struct scatterlist *sgl,
           unsigned int sg_len, enum dma_data_direction direction,
           unsigned long flags);

struct dma_async_tx_descriptor *dmaengine_prep_peripheral_dma_vec(
           struct dma_chan *chan, const struct dma_vec *vecs,
           size_t nents, enum dma_data_direction direction,
           unsigned long flags);

struct dma_async_tx_descriptor *dmaengine_prep_dma_cyclic(
           struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
           size_t period_len, enum dma_data_direction direction);

struct dma_async_tx_descriptor *dmaengine_prep_interleaved_dma(
           struct dma_chan *chan, struct dma_interleaved_template *xt,
           unsigned long flags);

外圍裝置驅動程式應在呼叫 dmaengine_prep_slave_sg() 之前對映 DMA 操作的散列表,並且必須保持散列表的對映狀態,直到 DMA 操作完成。必須使用 DMA struct device 對映散列表。如果以後需要同步對映,則還必須使用 DMA struct device 呼叫 dma_sync_*_for_*()。因此,正常的設定應如下所示

struct device *dma_dev = dmaengine_get_dma_device(chan);

nr_sg = dma_map_sg(dma_dev, sgl, sg_len);
   if (nr_sg == 0)
           /* error */

   desc = dmaengine_prep_slave_sg(chan, sgl, nr_sg, direction, flags);

獲得描述符後,可以添加回調資訊,然後必須提交該描述符。某些 DMA 引擎驅動程式可能會在成功準備和提交之間保持一個自旋鎖,因此這兩個操作必須緊密配對。

注意

雖然 async_tx API 指定完成回撥例程不能提交任何新操作,但對於從屬/迴圈 DMA 而言,情況並非如此。

對於從屬 DMA,後續事務可能在呼叫回撥函式之前不可用於提交,因此允許從屬 DMA 回調準備和提交新事務。

對於迴圈 DMA,回撥函式可能希望透過 dmaengine_terminate_async() 終止 DMA。

因此,重要的是 DMA 引擎驅動程式在呼叫可能導致死鎖的回撥函式之前刪除任何鎖。

請注意,回撥將始終從 DMA 引擎的 tasklet 中呼叫,而不是從中斷上下文中呼叫。

可選:每個描述符的元資料

DMAengine 提供兩種元資料支援方式。

DESC_METADATA_CLIENT

元資料緩衝區由客戶端驅動程式分配/提供,並附加到描述符。

int dmaengine_desc_attach_metadata(struct dma_async_tx_descriptor *desc,
                              void *data, size_t len);

DESC_METADATA_ENGINE

元資料緩衝區由 DMA 驅動程式分配/管理。客戶端驅動程式可以請求指標、最大大小和當前使用的元資料大小,並可以直接更新或讀取它。

由於 DMA 驅動程式管理包含元資料的記憶體區域,因此客戶端必須確保它們在描述符的傳輸完成回撥執行後,不會嘗試訪問或獲取指標。如果未為傳輸定義任何完成回撥,則在 issue_pending 之後不得訪問元資料。換句話說:如果目的是在傳輸完成後讀回元資料,則客戶端必須使用完成回撥。

void *dmaengine_desc_get_metadata_ptr(struct dma_async_tx_descriptor *desc,
           size_t *payload_len, size_t *max_len);

int dmaengine_desc_set_metadata_len(struct dma_async_tx_descriptor *desc,
           size_t payload_len);

客戶端驅動程式可以使用以下命令查詢是否支援給定的模式

bool dmaengine_is_metadata_mode_supported(struct dma_chan *chan,
           enum dma_desc_metadata_mode mode);

根據使用的模式,客戶端驅動程式必須遵循不同的流程。

DESC_METADATA_CLIENT

  • DMA_MEM_TO_DEV / DEV_MEM_TO_MEM

    1. 準備描述符 (dmaengine_prep_*) 在客戶端的緩衝區中構造元資料

    2. 使用 dmaengine_desc_attach_metadata() 將緩衝區附加到描述符

    3. 提交傳輸

  • DMA_DEV_TO_MEM

    1. 準備描述符 (dmaengine_prep_*)

    2. 使用 dmaengine_desc_attach_metadata() 將緩衝區附加到描述符

    3. 提交傳輸

    4. 傳輸完成後,元資料應在附加的緩衝區中可用

DESC_METADATA_ENGINE

  • DMA_MEM_TO_DEV / DEV_MEM_TO_MEM

    1. 準備描述符 (dmaengine_prep_*)

    2. 使用 dmaengine_desc_get_metadata_ptr() 獲取指向引擎元資料區域的指標

    3. 在指標處更新元資料

    4. 使用 dmaengine_desc_set_metadata_len() 告訴 DMA 引擎客戶端已放入元資料緩衝區中的資料量

    5. 提交傳輸

  • DMA_DEV_TO_MEM

    1. 準備描述符 (dmaengine_prep_*)

    2. 提交傳輸

    3. 在傳輸完成時,使用 dmaengine_desc_get_metadata_ptr() 獲取指向引擎元資料區域的指標

    4. 從指標中讀取元資料

注意

當使用 DESC_METADATA_ENGINE 模式時,描述符的元資料區域在傳輸完成後不再有效(如果使用,則有效期到完成回撥返回的點為止)。

不允許混合使用 DESC_METADATA_CLIENT / DESC_METADATA_ENGINE,客戶端驅動程式必須對每個描述符使用其中一種模式。

  1. 提交事務

    準備好描述符並添加回調資訊後,必須將其放置在 DMA 引擎驅動程式的掛起佇列中。

    介面

    dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc)
    

    這將返回一個 cookie,該 cookie 可用於透過本文件中未涵蓋的其他 DMA 引擎呼叫來檢查 DMA 引擎活動的進度。

    dmaengine_submit() 不會啟動 DMA 操作,它只是將其新增到掛起佇列中。為此,請參見步驟 5,dma_async_issue_pending。

    注意

    在呼叫 dmaengine_submit() 之後,提交的傳輸描述符 (struct dma_async_tx_descriptor) 屬於 DMA 引擎。因此,客戶端必須將指向該描述符的指標視為無效。

  2. 發出掛起的 DMA 請求並等待回撥通知

    可以透過呼叫 issue_pending API 來啟用掛起佇列中的事務。如果通道空閒,則啟動佇列中的第一個事務,並將後續事務排隊。

    在完成每個 DMA 操作時,將啟動佇列中的下一個事務,並觸發一個 tasklet。然後,tasklet 將呼叫客戶端驅動程式完成回撥例程以進行通知(如果已設定)。

    介面

    void dma_async_issue_pending(struct dma_chan *chan);
    

其他 API

  1. 終止 API

    int dmaengine_terminate_sync(struct dma_chan *chan)
    int dmaengine_terminate_async(struct dma_chan *chan)
    int dmaengine_terminate_all(struct dma_chan *chan) /* DEPRECATED */
    

    這會導致 DMA 通道的所有活動停止,並且可能會丟棄 DMA FIFO 中尚未完全傳輸的資料。不會為任何未完成的傳輸呼叫回撥函式。

    此函式有兩個變體可用。

    dmaengine_terminate_async() 可能不會等到 DMA 完全停止或任何正在執行的完整回撥完成。但是可以從原子上下文或從完整回撥中呼叫 dmaengine_terminate_async()。必須在可以安全地釋放 DMA 傳輸訪問的記憶體或從完整回撥中訪問的自由資源之前呼叫 dmaengine_synchronize()。

    dmaengine_terminate_sync() 將等待傳輸和任何正在執行的完整回撥完成後再返回。但是不得從原子上下文或從完整回撥中呼叫該函式。

    dmaengine_terminate_all() 已棄用,不應在新程式碼中使用。

  2. 暫停 API

    int dmaengine_pause(struct dma_chan *chan)
    

    這會暫停 DMA 通道上的活動,而不會丟失資料。

  3. 恢復 API

    int dmaengine_resume(struct dma_chan *chan)
    

    恢復先前暫停的 DMA 通道。恢復當前未暫停的通道是無效的。

  4. 檢查 Txn 完成

    enum dma_status dma_async_is_tx_complete(struct dma_chan *chan,
              dma_cookie_t cookie, dma_cookie_t *last, dma_cookie_t *used)
    

    這可用於檢查通道的狀態。有關此 API 的更完整描述,請參見 include/linux/dmaengine.h 中的文件。

    這可以與 dma_async_is_complete() 和從 dmaengine_submit() 返回的 cookie 結合使用,以檢查特定 DMA 事務的完成情況。

    注意

    並非所有 DMA 引擎驅動程式都可以為正在執行的 DMA 通道返回可靠的資訊。建議 DMA 引擎使用者在使用此 API 之前暫停或停止(透過 dmaengine_terminate_all())通道。

  5. 同步終止 API

    void dmaengine_synchronize(struct dma_chan *chan)
    

    將 DMA 通道的終止與當前上下文同步。

    應在 dmaengine_terminate_async() 之後使用此函式,以將 DMA 通道的終止與當前上下文同步。該函式將等待傳輸和任何正在執行的完整回撥完成後再返回。

    如果使用 dmaengine_terminate_async() 停止 DMA 通道,則必須在可以安全地釋放先前提交的描述符訪問的記憶體或釋放先前提交的描述符的完整回撥中訪問的任何資源之前呼叫此函式。

    如果在 dmaengine_terminate_async() 和此函式之間呼叫了 dma_async_issue_pending(),則此函式的行為未定義。