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(),则此函数的行为未定义。