异步传输/转换 API¶
1. 简介¶
async_tx API 提供了描述一系列异步批量内存传输/转换的方法,支持事务间依赖关系。它被实现为 dmaengine 客户端,平滑了不同硬件卸载引擎实现的细节。为该 API 编写的代码可以针对异步操作进行优化,并且 API 将使操作链适应可用的卸载资源。
2. 渊源¶
该 API 最初旨在卸载 md-raid5 驱动程序的内存复制和异或奇偶校验计算,使用 Intel(R) Xscale 系列 I/O 处理器中的卸载引擎。它还构建在为网络堆栈中卸载内存复制而开发的“dmaengine”层之上,使用 Intel(R) I/OAT 引擎。因此,以下设计特性浮出水面
隐式同步路径:API 的用户不需要知道他们运行的平台是否具有卸载功能。当引擎可用时,操作将被卸载,否则将在软件中执行。
跨通道依赖链:API 允许提交一系列相关的操作,例如 raid5 中的异或->复制->异或。API 自动处理从一个操作到另一个操作的转换意味着硬件通道切换的情况。
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 并且指向一个“描述符”。描述符是可回收资源,受卸载引擎驱动程序的控制,以便在操作完成时重复使用。当应用程序需要提交一系列操作时,它必须保证描述符不会在提交依赖项之前自动回收。这需要应用程序确认所有描述符,然后才允许卸载引擎驱动程序回收(或释放)描述符。可以通过以下方法之一确认描述符
如果没有要提交的子操作,则设置 ASYNC_TX_ACK 标志
将未经确认的描述符作为依赖项提交给另一个 async_tx 调用将隐式设置确认状态。
在描述符上调用 async_tx_ack()。
3.4 操作何时执行?¶
从 async_
3.5 操作何时完成?¶
应用程序可以通过两种方法了解操作的完成情况。
调用 dma_wait_for_async_tx()。此调用导致 CPU 在轮询操作完成时旋转。它处理依赖链并发出挂起的操作。
指定完成回调。如果卸载引擎驱动程序支持中断,则回调例程在 tasklet 上下文中运行,如果操作在软件中同步执行,则在应用程序上下文中调用它。回调可以在调用 async_
时设置,或者当应用程序需要提交长度未知的链时,它可以使用 async_trigger_callback() 例程在链的末尾设置完成中断/回调。
3.6 约束¶
不允许在 IRQ 上下文中调用 async_
。如果未违反约束 #2,则允许其他上下文。 完成回调例程无法提交新操作。这导致同步情况下的递归和异步情况下两次获取自旋锁。
3.7 示例¶
执行异或->复制->异或操作,其中每个操作都依赖于前一个操作的结果
#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 的应用程序所做的假设
完成回调应发生在 tasklet 上下文中
dma_async_tx_descriptor 字段永远不会在 IRQ 上下文中操作
在描述符清理路径中使用 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() 找到未使用的“公共”通道时,将设置它。
实施驱动程序和使用者时需要注意以下几点
一旦私下分配了通道,即使在调用 dma_release_channel() 后,通用分配器也不再考虑该通道。
由于功能是在设备级别指定的,因此具有多个通道的 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
异或和异或零和卸载