异步传输/转换 API

1. 简介

async_tx API 提供了描述一系列异步批量内存传输/转换的方法,并支持事务间的依赖关系。 它被实现为 dmaengine 客户端,平滑了不同硬件卸载引擎实现的细节。 编写为使用此 API 的代码可以针对异步操作进行优化,并且 API 会将操作链适配到可用的卸载资源。

2. 谱系

该 API 最初旨在利用 Intel(R) Xscale 系列 I/O 处理器中存在的卸载引擎来卸载 md-raid5 驱动程序的内存复制和异或奇偶校验计算。它还建立在为使用 Intel(R) I/OAT 引擎卸载网络堆栈中的内存复制而开发的“dmaengine”层之上。因此,以下设计特性显现出来

  1. 隐式同步路径:API 的用户无需知道他们运行的平台是否具有卸载功能。当引擎可用时,操作将被卸载,否则将在软件中执行。

  2. 跨通道依赖链:API 允许提交一系列依赖操作,例如 raid5 案例中的 xor->copy->xor。API 会自动处理从一个操作到另一个操作的转换意味着硬件通道切换的情况。

  3. dmaengine 扩展以支持多个客户端以及超出“memcpy”的操作类型

3. 用法

3.1 API 的通用格式

struct dma_async_tx_descriptor *
async_<operation>(<op specific parameters>, struct async_submit_ctl *submit)

3.2 支持的操作

memcpy

源缓冲区和目标缓冲区之间的内存复制

memset

用字节值填充目标缓冲区

xor

对一系列源缓冲区进行异或运算,并将结果写入目标缓冲区

xor_val

对一系列源缓冲区进行异或运算,如果结果为零,则设置一个标志。该实现尝试防止写入内存

pq

从一系列源缓冲区生成 p+q(raid6 综合征)

pq_val

验证 p 或 q 缓冲区是否与给定的一系列源同步

datap

(raid6_datap_recov) 从给定的源中恢复 raid6 数据块和 p 块

2data

(raid6_2data_recov) 从给定的源中恢复 2 个 raid6 数据块

3.3 描述符管理

当操作已排队以异步执行时,返回值非 NULL 并指向“描述符”。 描述符是循环使用的资源,在卸载引擎驱动程序的控制下,在操作完成时重复使用。 当应用程序需要提交一系列操作时,它必须保证在提交依赖项之前,描述符不会被自动回收。 这要求在允许卸载引擎驱动程序回收(或释放)描述符之前,应用程序必须确认所有描述符。 可以通过以下方法之一确认描述符

  1. 如果没有要提交的子操作,则设置 ASYNC_TX_ACK 标志

  2. 将未确认的描述符作为依赖项提交到另一个 async_tx 调用将隐式设置确认状态。

  3. 在描述符上调用 async_tx_ack()。

3.4 操作何时执行?

在 async_<operation> 调用返回后,操作不会立即发出。 卸载引擎驱动程序会批量处理操作,以通过减少管理通道所需的 mmio 周期数来提高性能。 一旦满足特定于驱动程序的阈值,驱动程序将自动发出挂起的操作。 应用程序可以通过调用 async_tx_issue_pending_all() 来强制执行此事件。 这适用于所有通道,因为应用程序不知道通道到操作的映射。

3.5 操作何时完成?

应用程序可以通过两种方法了解操作的完成情况。

  1. 调用 dma_wait_for_async_tx()。 此调用会导致 CPU 旋转,同时轮询操作的完成情况。 它处理依赖链并发出挂起的操作。

  2. 指定完成回调。 如果卸载引擎驱动程序支持中断,则回调例程将在 tasklet 上下文中运行,或者如果该操作在软件中同步执行,则在应用程序上下文中调用。 可以在调用 async_<operation> 时设置回调,或者当应用程序需要提交未知长度的链时,可以使用 async_trigger_callback() 例程在链的末尾设置完成中断/回调。

3.6 约束

  1. 不允许在 IRQ 上下文中调用 async_<operation>。 如果不违反约束 #2,则允许其他上下文。

  2. 完成回调例程不能提交新操作。 这会导致同步情况下的递归,以及在异步情况下两次获取 spin_locks。

3.7 示例

执行 xor->copy->xor 操作,其中每个操作都依赖于前一个操作的结果

#include <linux/async_tx.h>

static void callback(void *param)
{
        complete(param);
}

#define NDISKS  2

static void run_xor_copy_xor(struct page **xor_srcs,
                             struct page *xor_dest,
                             size_t xor_len,
                             struct page *copy_src,
                             struct page *copy_dest,
                             size_t copy_len)
{
        struct dma_async_tx_descriptor *tx;
        struct async_submit_ctl submit;
        addr_conv_t addr_conv[NDISKS];
        struct completion cmp;

        init_async_submit(&submit, ASYNC_TX_XOR_DROP_DST, NULL, NULL, NULL,
                        addr_conv);
        tx = async_xor(xor_dest, xor_srcs, 0, NDISKS, xor_len, &submit);

        submit.depend_tx = tx;
        tx = async_memcpy(copy_dest, copy_src, 0, 0, copy_len, &submit);

        init_completion(&cmp);
        init_async_submit(&submit, ASYNC_TX_XOR_DROP_DST | ASYNC_TX_ACK, tx,
                        callback, &cmp, addr_conv);
        tx = async_xor(xor_dest, xor_srcs, 0, NDISKS, xor_len, &submit);

        async_tx_issue_pending_all();

        wait_for_completion(&cmp);
}

有关标志的更多信息,请参阅 include/linux/async_tx.h。 有关更多实现示例,请参阅 drivers/md/raid5.c 中的 ops_run_* 和 ops_complete_* 例程。

4. 驱动程序开发注意事项

4.1 一致性点

dmaengine 驱动程序中需要一些一致性点,以适应使用 async_tx API 的应用程序所做的假设

  1. 预计完成回调会在 tasklet 上下文中发生

  2. dma_async_tx_descriptor 字段永远不会在 IRQ 上下文中操作

  3. 在描述符清理路径中使用 async_tx_run_dependencies() 来处理依赖操作的提交

4.2 “我的应用程序需要独占控制硬件通道”

此要求主要来自使用 DMA 引擎驱动程序来支持设备到内存的操作的情况。 由于许多特定于平台的原因,执行这些操作的通道无法共享。 对于这些情况,提供了 dma_request_channel() 接口。

该接口为

struct dma_chan *dma_request_channel(dma_cap_mask_t mask,
                                     dma_filter_fn filter_fn,
                                     void *filter_param);

其中 dma_filter_fn 定义为

typedef bool (*dma_filter_fn)(struct dma_chan *chan, void *filter_param);

当可选的“filter_fn”参数设置为 NULL 时,dma_request_channel 只会返回满足功能掩码的第一个通道。 否则,当掩码参数不足以指定必要的通道时,可以使用 filter_fn 例程来处理系统中的可用通道。 对于系统中的每个空闲通道,filter_fn 例程都会被调用一次。 在看到合适的通道后,filter_fn 会返回 DMA_ACK,这将标记该通道为 dma_request_channel 的返回值。 通过此接口分配的通道对调用者是独占的,直到调用 dma_release_channel()。

DMA_PRIVATE 功能标志用于标记不应由通用分配器使用的 dma 设备。 如果已知通道始终是私有的,则可以在初始化时设置它。 或者,当 dma_request_channel() 找到未使用的“公共”通道时,也会设置它。

在实现驱动程序和使用者时,需要注意以下几个警告

  1. 一旦私有分配了通道,即使在调用 dma_release_channel() 之后,通用分配器也不会再考虑它。

  2. 由于功能是在设备级别指定的,因此具有多个通道的 dma_device 要么所有通道都是公共的,要么所有通道都是私有的。

5. 源代码

include/linux/dmaengine.h

DMA 驱动程序和 api 用户的核心头文件

drivers/dma/dmaengine.c

卸载引擎通道管理例程

drivers/dma/

卸载引擎驱动程序的位置

include/linux/async_tx.h

async_tx api 的核心头文件

crypto/async_tx/async_tx.c

dmaengine 和通用代码的 async_tx 接口

crypto/async_tx/async_memcpy.c

复制卸载

crypto/async_tx/async_xor.c

异或和异或零和卸载