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 典型使用流程

  1. 准备散聚表: 使用 sg_init_table 初始化,sg_dma_address / sg_dma_len 设置物理地址和长度。
  2. 映射缓冲区: 调用 dma_map_sg 将虚拟地址转换为DMA可访问的物理地址。
  3. 创建描述符: 调用 dmaengine_prep_slave_sg 获得 struct dma_async_tx_descriptor *。
  4. 提交与触发: 调用 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 返回。
dmaengine_prep_slave_sg 传输流程 散聚表 (SG List) dma_map_sg prep_slave_sg dmaengine_submit issue_pending DMA硬件执行 注意事项 • sg_nents上限 • 映射一致性 • 回调注册 • 错误处理 • 描述符复用

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),每个周期对应一个硬件描述符。描述符链的最后一个节点指向第一个节点,形成环形结构。当传输进行时,硬件自动加载下一个描述符,无需软件干预。

循环传输描述符链结构 Period 0 buf[0..1023] Period 1 buf[1024..2047] Period 2 buf[2048..3071] Period 3 buf[3072..4095] 自动回绕(硬件循环) 每个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字节),以利用硬件预取。
交错传输数据布局(图像行提取示例) 源缓冲区(640像素/行) size=320 (有效数据) icg=320 (跳过) size=320 icg=320 ...... 共480行 ...... 目标缓冲区(连续320像素/行) 行0: 320像素 行1: 320像素 交错传输优势 • 一次描述符完成多行提取 • 无需CPU逐行拷贝 • 硬件自动处理间隔跳过

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 描述符生命周期与资源管理

无论使用哪种创建接口,描述符的生命周期都遵循以下阶段:

  1. 创建(Prep): 分配描述符对象,填充传输参数。
  2. 提交(Submit): 将描述符加入通道的等待队列。
  3. 触发(Issue): 通知DMA控制器开始处理队列中的描述符。
  4. 执行(Running): 硬件正在传输数据。
  5. 完成(Complete): 传输结束,回调函数被调用。
  6. 释放(Free): 描述符被框架回收或显式释放。
资源泄漏风险: 在描述符提交后但未完成前,如果调用 dmaengine_terminate_sync 终止传输,必须确保所有未完成的描述符都被正确释放。建议在终止后调用 dmaengine_synchronize 等待所有回调执行完毕。
描述符生命周期状态机 Created Submitted Issued Running Done Terminated ← 上一章 📖 返回目录 下一章 →