4. Linux DMA引擎框架:dmaengine API概述、dma_device结构体、dma_chan通道管理

Linux内核自2.6.32版本起引入了统一的DMA引擎框架(dmaengine),旨在解决不同DMA控制器驱动接口碎片化的问题。该框架为设备驱动开发者提供了一套标准化的API,用于申请DMA通道、配置传输参数、提交传输请求以及异步等待传输完成。本章将深入剖析dmaengine框架的核心组件:API体系、dma_device结构体以及dma_chan通道管理机制。

4.1 dmaengine API概述

dmaengine API分为两个层次:消费者API(供外设驱动使用)和提供者API(供DMA控制器驱动实现)。消费者API是驱动开发者最常接触的接口,其核心操作流程如下:

  1. 通道申请:通过dma_request_chan()或dma_request_slave_channel()获取一个DMA通道。
  2. 描述符准备:调用dmaengine_prep_slave_sg()、dmaengine_prep_dma_cyclic()或dmaengine_prep_interleaved_dma()创建传输描述符。
  3. 回调绑定:通过dmaengine_submit()将描述符提交到硬件队列前,设置callback和callback_param。
  4. 传输启动:调用dma_async_issue_pending()触发硬件开始搬运。
  5. 同步等待:使用dma_sync_wait()或DMA完成回调获取传输结果。
关键API速查表: 下表列出了显示驱动开发中最常用的dmaengine消费者API及其用途。
API函数 功能描述 典型使用场景
dma_request_chan() 通过设备树或平台数据请求DMA通道 驱动初始化时获取通道
dmaengine_prep_slave_sg() 准备分散/聚合传输描述符 帧缓冲与显示控制器之间的数据搬运
dmaengine_prep_dma_cyclic() 准备循环传输描述符 音频流、连续刷新显示缓冲区
dmaengine_submit() 将描述符提交到DMA引擎的待处理队列 提交传输请求
dma_async_issue_pending() 启动队列中所有待处理的传输 触发DMA控制器开始工作
dma_sync_wait() 同步等待DMA传输完成 阻塞等待传输结束
dma_release_channel() 释放DMA通道 驱动卸载或通道不再使用时
最佳实践: 在显示驱动中,推荐使用dmaengine_prep_slave_sg()配合DMA_MEM_TO_DEV方向,将帧缓冲数据搬运到显示控制器的FIFO。对于需要连续刷新的场景(如高帧率显示),dmaengine_prep_dma_cyclic()可以避免重复提交描述符的开销。

4.2 dma_device结构体深度解析

dma_device是DMA控制器驱动向内核注册的核心结构体,它描述了DMA控制器的硬件能力和操作函数集。该结构体定义在include/linux/dmaengine.h中,是连接硬件与框架的桥梁。

struct dma_device {
    unsigned int chancnt;           /* 通道数量 */
    unsigned int max_burst;         /* 最大突发传输长度 */
    u32 src_addr_widths;            /* 支持的源地址宽度位掩码 */
    u32 dst_addr_widths;            /* 支持的目的地址宽度位掩码 */
    u32 directions;                 /* 支持的数据传输方向位掩码 */
    u32 residue_granularity;        /* 剩余数据量粒度 */
    struct list_head channels;      /* 通道链表头 */
    struct dma_cap_mask_t cap_mask; /* 能力掩码 */

    /* 操作函数指针 */
    int (*device_alloc_chan_resources)(struct dma_chan *chan);
    void (*device_free_chan_resources)(struct dma_chan *chan);
    struct dma_async_tx_descriptor *(*device_prep_slave_sg)(
        struct dma_chan *chan, struct scatterlist *sgl,
        unsigned int sg_len, enum dma_transfer_direction direction,
        unsigned long flags, void *context);
    struct dma_async_tx_descriptor *(*device_prep_dma_cyclic)(
        struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
        size_t period_len, enum dma_transfer_direction direction,
        unsigned long flags);
    int (*device_config)(struct dma_chan *chan,
                         struct dma_slave_config *config);
    int (*device_terminate_all)(struct dma_chan *chan);
    enum dma_status (*device_tx_status)(struct dma_chan *chan,
                                        dma_cookie_t cookie,
                                        struct dma_tx_state *txstate);
    void (*device_issue_pending)(struct dma_chan *chan);
};
结构体关键字段说明:
  • cap_mask:使用dma_cap_set()宏设置,声明该DMA设备支持的操作类型(如DMA_SLAVE、DMA_CYCLIC)。
  • directions:位掩码表示支持DMA_MEM_TO_MEM、DMA_MEM_TO_DEV、DMA_DEV_TO_MEM、DMA_DEV_TO_DEV中的哪些方向。
  • src_addr_widths / dst_addr_widths:定义支持的地址宽度,如DMA_SLAVE_BUSWIDTH_4_BYTES。
  • device_config:配置DMA通道的从设备参数,包括源/目的地址、突发大小、地址宽度等。
dma_device 结构体与操作函数关系图 dma_device 结构体与操作函数关系 struct dma_device 硬件能力描述 + 操作函数指针集 硬件能力字段 • chancnt: 通道数量 • cap_mask: 能力掩码 • directions: 传输方向 • src/dst_addr_widths 操作函数指针 • device_alloc_chan • device_prep_slave_sg • device_config • device_issue_pending struct list_head channels 管理所有 dma_chan 实例 DMA控制器驱动需实现所有操作函数指针,并通过 dma_async_device_register() 注册

4.3 dma_chan通道管理

dma_chan是DMA通道的抽象,代表一个物理DMA通道或虚拟通道。每个dma_chan实例关联到一个dma_device,并维护传输状态、回调函数和客户端私有数据。通道管理涉及分配、配置、使用和释放的全生命周期。

4.3.1 通道状态与生命周期

阶段 状态 关键操作 说明
空闲 IDLE 通道未被使用 等待客户端申请
已分配 ALLOCATED dma_request_chan() 客户端获得通道所有权
已配置 CONFIGURED device_config() 设置从设备参数(地址、宽度、突发大小)
传输中 PAUSED / ACTIVE dma_async_issue_pending() 硬件正在执行数据传输
完成 COMPLETED 回调或dma_sync_wait() 传输结束,可重新配置或释放
释放 RELEASED dma_release_channel() 通道归还给系统

4.3.2 通道管理核心流程

以下代码片段展示了显示驱动中典型的DMA通道管理流程:

/* 1. 申请DMA通道 */
struct dma_chan *dma_chan;
dma_cap_mask_t mask;
dma_cap_zero(mask);
dma_cap_set(DMA_SLAVE, mask);
dma_chan = dma_request_channel(mask, filter_fn, &filter_param);
if (IS_ERR(dma_chan)) {
    dev_err(dev, "Failed to request DMA channel\n");
    return PTR_ERR(dma_chan);
}

/* 2. 配置从设备参数 */
struct dma_slave_config config = {
    .direction = DMA_MEM_TO_DEV,
    .src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
    .dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
    .src_maxburst = 16,
    .dst_maxburst = 16,
    .dst_addr = display_fifo_phys_addr,
};
dmaengine_slave_config(dma_chan, &config);

/* 3. 准备传输描述符 */
struct dma_async_tx_descriptor *tx;
tx = dmaengine_prep_slave_sg(dma_chan, sgl, sg_nents,
                             DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT, NULL);
if (!tx) {
    dev_err(dev, "Failed to prepare slave sg\n");
    dma_release_channel(dma_chan);
    return -ENOMEM;
}

/* 4. 设置完成回调 */
tx->callback = dma_done_callback;
tx->callback_param = completion;

/* 5. 提交并启动传输 */
dma_cookie_t cookie = dmaengine_submit(tx);
dma_async_issue_pending(dma_chan);

/* 6. 等待传输完成(同步方式) */
dma_sync_wait(dma_chan, cookie);

/* 7. 释放通道 */
dma_release_channel(dma_chan);
注意事项:
  • 在显示驱动中,DMA通道通常在probe()函数中申请,在remove()函数中释放,避免频繁申请/释放带来的开销。
  • 对于多缓冲区显示场景,建议使用dmaengine_prep_dma_cyclic()配合DMA完成中断,实现无CPU干预的连续帧刷新。
  • 通道配置(dmaengine_slave_config)应在每次传输前根据当前显示参数(分辨率、像素格式)重新配置,确保地址宽度和突发大小匹配。
dma_chan 通道管理状态机 dma_chan 通道管理状态机 空闲 (IDLE) 已分配 已配置 传输中 完成 释放 dma_request_chan device_config issue_pending 传输完成 dma_release_channel 重新申请 虚线表示通道释放后可重新进入空闲状态,实现通道复用

4.4 虚拟通道与多通道调度

在支持多通道的DMA控制器中,dma_device的channels链表管理着所有可用通道。dmaengine框架引入了虚拟通道(virtual channel)的概念,允许将多个DMA请求映射到有限的物理通道上。虚拟通道通过virt-dma辅助模块实现,核心数据结构为virt_dma_chan:

struct virt_dma_chan {
    struct dma_chan chan;           /* 基类通道 */
    struct tasklet_struct task;     /* 任务软中断 */
    struct list_head desc_allocated; /* 已分配描述符链表 */
    struct list_head desc_submitted; /* 已提交描述符链表 */
    struct list_head desc_issued;   /* 已下发描述符链表 */
    struct list_head desc_completed; /* 已完成描述符链表 */
    spinlock_t lock;                /* 自旋锁保护链表操作 */
};
虚拟通道优势:
  • 解耦:将传输描述符的管理从硬件中断上下文中解耦到tasklet中处理。
  • 排队:支持多个传输描述符排队,由底层调度器决定下一个执行哪个传输。
  • 周期传输:对于显示刷新场景,虚拟通道可以无缝支持dmaengine_prep_dma_cyclic()的循环描述符管理。

在显示驱动开发中,理解虚拟通道的链表管理机制至关重要。当使用dmaengine_submit()提交描述符时,描述符首先进入desc_submitted链表;调用dma_async_issue_pending()后,描述符移至desc_issued链表;硬件传输完成后,描述符移至desc_completed链表,并在tasklet中调用用户注册的回调函数。

性能优化提示: 对于高分辨率显示(如4K@60fps),建议在DMA驱动中启用描述符预分配池,避免在传输热路径中动态分配内存。可以通过dmaengine_desc_pool_create()在通道初始化时预分配固定数量的描述符,减少内存碎片和分配延迟。