DMA 引擎 API 指南

Vinod Koul <vinod dot koul at intel.com>

注意

有关 async_tx 中 DMA 引擎的用法,请参见:Documentation/crypto/async-tx-api.rst

以下是为设备驱动程序编写者提供的关于如何使用 DMA 引擎的从属 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 可以使用的大多数通用信息都在结构体 dma_slave_config 中。这允许客户端为外围设备指定 DMA 方向、DMA 地址、总线宽度、DMA 突发长度等。

    如果某些 DMA 控制器有更多参数要发送,那么它们应尝试在其控制器特定结构中嵌入结构体 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 从外围设备读取/写入的散布/聚集缓冲区列表

  • peripheral_dma_vec:DMA 从外围设备读取/写入的散布/聚集缓冲区数组。与 slave_sg 类似,但使用 dma_vec 结构体数组而不是散布列表。

  • dma_cyclic:执行从外围设备读取/写入的循环 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(),则此函数的行为是未定义的。