4. dmaengine API详解(中):传输描述符的创建
在DMA驱动开发中,传输描述符(Descriptor)是连接上层业务与底层硬件通道的关键桥梁。Linux dmaengine框架提供了三种核心的描述符创建接口:dmaengine_prep_slave_sg、dmaengine_prep_dma_cyclic 和 dmaengine_prep_interleaved_dma。本章将深入剖析这三个API的语义、适用场景及参数细节,帮助你在实际调优中做出正确的接口选择。
核心认知: 描述符创建并不立即启动传输,它只是将传输参数打包成一个可提交的事务对象。真正的传输触发依赖于后续的
dmaengine_submit 和 dma_async_issue_pending 调用。
4.1 dmaengine_prep_slave_sg:散聚表传输
dmaengine_prep_slave_sg 是最常用的描述符创建接口,适用于非连续内存区域的批量数据传输。它接收一个散聚表(Scatter-Gather List),将多个不连续的缓冲区组织成一次DMA传输。
4.1.1 函数原型与参数
struct dma_async_tx_descriptor *dmaengine_prep_slave_sg(
struct dma_chan *chan,
struct scatterlist *sgl,
unsigned int sg_nents,
enum dma_transfer_direction dir,
unsigned long flags,
void *context
);
| 参数 | 类型 | 说明 |
|---|---|---|
chan |
struct dma_chan * |
已申请成功的DMA通道句柄 |
sgl |
struct scatterlist * |
散聚表首地址,描述内存缓冲区物理地址与长度 |
sg_nents |
unsigned int |
散聚表条目数,即sg_nents个不连续段 |
dir |
enum dma_transfer_direction |
传输方向:DMA_MEM_TO_DEV 或 DMA_DEV_TO_MEM |
flags |
unsigned long |
描述符控制标志,如 DMA_PREP_INTERRUPT |
context |
void * |
回调上下文指针,通常传 NULL |
性能要点:
sg_nents 的值直接影响硬件描述符链的长度。对于支持硬件散聚的DMA控制器,建议将多个小缓冲区合并为少量大段,以减少描述符数量,降低总线占用。
4.1.2 典型使用流程
- 准备散聚表: 使用
sg_init_table初始化,sg_dma_address/sg_dma_len设置物理地址和长度。 - 映射缓冲区: 调用
dma_map_sg将虚拟地址转换为DMA可访问的物理地址。 - 创建描述符: 调用
dmaengine_prep_slave_sg获得struct dma_async_tx_descriptor *。 - 提交与触发: 调用
dmaengine_submit入队,dma_async_issue_pending启动传输。
/* 示例:从设备到内存的SG传输 */
struct scatterlist sg;
struct dma_async_tx_descriptor *tx;
sg_init_table(&sg, 1);
sg_dma_address(&sg) = dma_map_single(dev, buf, len, DMA_FROM_DEVICE);
sg_dma_len(&sg) = len;
tx = dmaengine_prep_slave_sg(chan, &sg, 1, DMA_DEV_TO_MEM,
DMA_PREP_INTERRUPT, NULL);
if (!tx) {
dev_err(dev, "Failed to prepare slave sg descriptor\n");
return -ENOMEM;
}
tx->callback = my_dma_callback;
dmaengine_submit(tx);
dma_async_issue_pending(chan);
常见陷阱: 描述符创建失败时,务必检查
dma_map_sg 返回的映射条目数是否与预期一致。部分DMA控制器对 sg_nents 有上限限制(如最大256段),超出会导致 NULL 返回。
4.2 dmaengine_prep_dma_cyclic:循环缓冲传输
循环传输(Cyclic Transfer)专为音频、视频等需要连续数据流的场景设计。它创建一组循环的描述符链,当最后一个描述符执行完毕后自动回绕到第一个,实现无限循环传输。
4.2.1 函数原型与参数
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_transfer_direction dir,
unsigned long flags
);
| 参数 | 说明 |
|---|---|
buf_addr |
循环缓冲区的DMA物理地址,必须连续 |
buf_len |
循环缓冲区总长度(字节) |
period_len |
周期长度(字节),硬件每完成一个周期触发一次中断 |
flags |
支持 DMA_PREP_INTERRUPT 等标志 |
关键约束:
buf_len 必须是 period_len 的整数倍。例如,音频驱动中 buf_len = 4096,period_len = 1024,则硬件将缓冲区划分为4个周期,每个周期完成时触发回调。
4.2.2 循环传输的内部机制
DMA控制器内部将循环缓冲区划分为多个周期(Period),每个周期对应一个硬件描述符。描述符链的最后一个节点指向第一个节点,形成环形结构。当传输进行时,硬件自动加载下一个描述符,无需软件干预。
4.2.3 音频驱动中的典型用法
/* 音频播放场景:循环传输 */
struct dma_async_tx_descriptor *tx;
dma_addr_t dma_buf;
dma_buf = dma_alloc_coherent(dev, AUDIO_BUF_SIZE, &dma_phys, GFP_KERNEL);
tx = dmaengine_prep_dma_cyclic(chan, dma_phys, AUDIO_BUF_SIZE,
PERIOD_SIZE, DMA_MEM_TO_DEV,
DMA_PREP_INTERRUPT);
if (!tx) {
dev_err(dev, "Failed to prepare cyclic dma\n");
dma_free_coherent(dev, AUDIO_BUF_SIZE, dma_buf, dma_phys);
return -ENOMEM;
}
tx->callback = audio_period_done;
tx->callback_param = my_audio_dev;
dmaengine_submit(tx);
dma_async_issue_pending(chan);
性能陷阱: 循环传输一旦启动,将持续占用DMA通道直到显式调用
dmaengine_terminate_sync 停止。在停止前修改缓冲区内容可能导致数据不一致,建议使用双缓冲(Double Buffering)策略。
4.3 dmaengine_prep_interleaved_dma:交错传输
交错传输(Interleaved DMA)是dmaengine框架中最高级的描述符创建接口,适用于需要同时操作多个数据帧、多个通道或复杂数据布局的场景,如图像处理、矩阵转置、多通道音频混音等。
4.3.1 函数原型与参数
struct dma_async_tx_descriptor *dmaengine_prep_interleaved_dma(
struct dma_chan *chan,
struct dma_interleaved_template *xt,
unsigned long flags
);
struct dma_interleaved_template 是核心数据结构,定义了源和目标的数据布局:
struct dma_interleaved_template {
dma_addr_t src_start; /* 源起始物理地址 */
dma_addr_t dst_start; /* 目标起始物理地址 */
enum dma_transfer_direction dir;
unsigned int numf; /* 帧数(Frame Count) */
size_t frame_size; /* 每帧大小(字节) */
struct data_chunk sgl[]; /* 可变长数据块描述数组 */
};
struct data_chunk {
size_t size; /* 本块大小 */
size_t icg; /* Inter-Chunk Gap,块间间隔 */
dma_addr_t dst_sgl; /* 目标端偏移(可选) */
dma_addr_t src_sgl; /* 源端偏移(可选) */
};
4.3.2 交错传输的应用场景
| 场景 | 描述 | numf / frame_size 示例 |
|---|---|---|
| 图像行提取 | 从图像缓冲区中提取指定行,跳过行间空白 | numf=480, frame_size=640, icg=1280 |
| 多通道音频混音 | 将多个音频通道的数据交织为单个流 | numf=1024, frame_size=4, icg=0 |
| 矩阵转置 | 按列读取矩阵数据,按行写入 | numf=N, frame_size=M, icg=跨度 |
| 数据打包/解包 | 将稀疏数据打包为连续数据,或反之 | numf=可变, frame_size=可变 |
4.3.3 交错传输配置示例
/* 图像处理:从640x480图像中提取每行前320像素 */
struct dma_interleaved_template *xt;
struct dma_async_tx_descriptor *tx;
xt = kzalloc(sizeof(*xt) + sizeof(struct data_chunk), GFP_KERNEL);
xt->src_start = src_phys;
xt->dst_start = dst_phys;
xt->dir = DMA_MEM_TO_MEM;
xt->numf = 480; /* 480行 */
xt->frame_size = 320; /* 每行取320像素 */
xt->sgl[0].size = 320; /* 块大小 */
xt->sgl[0].icg = 320; /* 跳过剩余320像素(640-320) */
tx = dmaengine_prep_interleaved_dma(chan, xt, DMA_PREP_INTERRUPT);
if (!tx) {
dev_err(dev, "Interleaved DMA prep failed\n");
kfree(xt);
return -ENOMEM;
}
tx->callback = image_line_done;
dmaengine_submit(tx);
dma_async_issue_pending(chan);
调优建议: 交错传输的
icg(块间间隔)参数直接影响DMA控制器的总线效率。过大的 icg 会导致总线空闲周期增加,建议将 icg 对齐到缓存行大小(通常64字节),以利用硬件预取。
4.4 三种描述符创建接口对比
| 特性 | prep_slave_sg | prep_dma_cyclic | prep_interleaved_dma |
|---|---|---|---|
| 适用场景 | 非连续内存批量传输 | 连续数据流(音频/视频) | 复杂数据布局(图像/矩阵) |
| 缓冲区要求 | 散聚表,可非连续 | 物理连续缓冲区 | 物理连续或散聚表 |
| 自动循环 | 否 | 是(硬件回绕) | 否 |
| 中断频率 | 传输完成一次 | 每个Period完成一次 | 传输完成一次 |
| 描述符数量 | 等于 sg_nents | buf_len / period_len | 等于 numf |
| 硬件要求 | 支持SG的DMA控制器 | 支持循环模式的DMA | 支持交错传输的DMA |
| 典型延迟 | 中等(取决于SG数量) | 低(周期中断) | 高(帧处理) |
选型指南: 对于大多数外设(UART、SPI、I2C),
prep_slave_sg 是最通用的选择。音频驱动必须使用 prep_dma_cyclic。图像处理或需要复杂数据重排的场景,优先考虑 prep_interleaved_dma,它可以在一次DMA操作中完成CPU需要多次循环才能完成的任务。
4.5 描述符生命周期与资源管理
无论使用哪种创建接口,描述符的生命周期都遵循以下阶段:
- 创建(Prep): 分配描述符对象,填充传输参数。
- 提交(Submit): 将描述符加入通道的等待队列。
- 触发(Issue): 通知DMA控制器开始处理队列中的描述符。
- 执行(Running): 硬件正在传输数据。
- 完成(Complete): 传输结束,回调函数被调用。
- 释放(Free): 描述符被框架回收或显式释放。
资源泄漏风险: 在描述符提交后但未完成前,如果调用
dmaengine_terminate_sync 终止传输,必须确保所有未完成的描述符都被正确释放。建议在终止后调用 dmaengine_synchronize 等待所有回调执行完毕。