3. DMA 引擎驱动框架

Linux DMA Engine 子系统为设备驱动开发者提供了一套统一的、与硬件无关的 API,用于管理和使用 DMA 控制器。本章节将深入剖析该子系统的核心架构、关键数据结构以及数据准备接口,帮助您理解 DMA 传输的完整生命周期。

3.1 Linux DMA Engine 子系统架构

DMA Engine 子系统采用分层设计,将通用 API 与硬件特定实现解耦。其核心思想是:消费者(如网卡、声卡驱动)通过标准接口发起 DMA 请求,而提供者(DMA 控制器驱动)则负责将这些请求转化为硬件操作。

架构核心分层:
  • 上层 API 层 (dmaengine API): 提供给消费者驱动使用的通用接口,如 dmaengine_prep_slave_sg()、dmaengine_submit()、dma_async_issue_pending()。
  • 核心框架层 (dmaengine core): 负责管理 DMA 设备、通道、事务队列,以及提供注册/注销接口。核心数据结构包括 dma_device 和 dma_chan。
  • 硬件驱动层 (DMA controller driver): 实现具体的 DMA 控制器操作,填充 dma_device 结构体中的函数指针,处理中断,管理硬件描述符。
Linux DMA Engine 子系统分层架构 上层 API 层 (dmaengine API) dmaengine_prep_slave_sg() | dmaengine_submit() | dma_async_issue_pending() 核心框架层 (dmaengine core) dma_device 管理 | dma_chan 分配 | 事务队列调度 硬件驱动层 (DMA Controller Driver) 填充 dma_device 回调 | 中断处理 | 硬件描述符管理
设计意图: 这种分层架构使得一个 DMA 控制器驱动可以被多个不同类型的消费者驱动复用,同时消费者驱动无需关心底层 DMA 控制器的具体寄存器细节,极大地提高了代码的可移植性和可维护性。

3.2 核心数据结构:dma_device 与 dma_chan

在 DMA Engine 框架中,dma_device 代表一个 DMA 控制器硬件设备,而 dma_chan 代表该控制器上的一个独立 DMA 通道。理解这两个结构体是掌握 DMA 驱动开发的关键。

3.2.1 dma_device 结构体

dma_device 是 DMA 控制器驱动的核心注册单元。驱动在探测(probe)阶段需要填充该结构体,并通过 dma_async_device_register() 将其注册到内核框架中。

struct dma_device {
    unsigned int    chancnt;        /* 通道数量 */
    unsigned int    privatecnt;     /* 私有通道数量 */
    struct list_head channels;      /* 该设备管理的通道链表头 */

    /* 设备能力描述 */
    enum dma_transaction_type cap_mask; /* 支持的事务类型位掩码 */
    bool    copy_align;             /* 内存拷贝对齐要求 */
    bool    slave_align;            /* 从设备传输对齐要求 */

    /* 关键操作函数指针 */
    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_dma_memcpy)(
                struct dma_chan *chan, dma_addr_t dst, dma_addr_t src,
                size_t len, unsigned long flags);
    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);
    int     (*device_issue_pending)(struct dma_chan *chan);
    /* ... 其他回调函数 */
};
dma_device 关键字段说明
字段/回调 类型 说明
chancnt unsigned int 该 DMA 控制器拥有的物理通道总数。
cap_mask 位掩码 声明设备支持哪些 DMA 传输类型,如 DMA_MEMCPY、DMA_SLAVE、DMA_CYCLIC。
device_prep_slave_sg 函数指针 准备一个从设备(如 SPI、I2C)的分散/聚集 DMA 传输。这是最常用的回调之一。
device_issue_pending 函数指针 通知 DMA 控制器开始处理已提交(submitted)但尚未启动的事务。

3.2.2 dma_chan 结构体

dma_chan 代表一个具体的、可被消费者使用的 DMA 通道。消费者通过 dma_request_channel() 获取一个通道实例。

struct dma_chan {
    struct dma_device *device;      /* 所属的 DMA 设备 */
    dma_cookie_t cookie;            /* 事务 cookie,用于跟踪提交状态 */
    dma_cookie_t completed_cookie;  /* 最后一个完成的事务 cookie */

    /* 通道属性 */
    enum dma_transfer_type chan_type; /* 通道类型:MEMCPY, SLAVE, CYCLIC 等 */
    struct dma_chan_dev *dev;       /* 对应的设备文件,用于 sysfs */

    /* 客户端私有数据 */
    void *private;                  /* 消费者驱动可以在此存储私有上下文 */

    /* 链表节点 */
    struct list_head device_node;   /* 用于挂入 dma_device->channels 链表 */
    struct list_head global_node;   /* 用于挂入全局通道链表 */
};
关键概念: dma_chan 本身不包含硬件描述符或传输数据。它更像是一个“句柄”或“控制器”,用于管理 DMA 传输的提交、排队和完成状态。实际的传输参数(源地址、目的地址、长度等)封装在 dma_async_tx_descriptor 结构体中。
dma_device 与 dma_chan 关系模型 dma_device 代表 DMA 控制器硬件 chancnt: 4 cap_mask: DMA_SLAVE | DMA_MEMCPY channels: list_head device_prep_slave_sg: 函数指针 device_issue_pending: 函数指针 device_alloc_chan_resources: 函数指针 channels 链表 dma_chan #0 device: 指向 dma_device cookie: 当前事务 ID dma_chan #1 device: 指向 dma_device cookie: 当前事务 ID dma_chan #2 device: 指向 dma_device cookie: 当前事务 ID

3.3 dmaengine_prep_* API:准备 DMA 事务

消费者驱动在发起 DMA 传输前,必须通过 dmaengine_prep_* 系列 API 来“准备”一个事务。这些 API 会返回一个 dma_async_tx_descriptor 结构体指针,该结构体描述了传输的所有细节。

3.3.1 常用 API 一览

dmaengine_prep_* 核心 API
API 函数 适用场景 关键参数
dmaengine_prep_slave_sg() 从设备(SPI, I2C, UART)与内存之间的分散/聚集传输 chan, sgl (scatterlist 链表), sg_len, direction (DMA_MEM_TO_DEV / DMA_DEV_TO_MEM)
dmaengine_prep_dma_memcpy() 内存到内存的拷贝,常用于加速数据搬移 chan, dst (目的 DMA 地址), src (源 DMA 地址), len
dmaengine_prep_dma_cyclic() 音频流等需要循环缓冲区的场景 chan, buf_addr, buf_len, period_len, direction
dmaengine_prep_interleaved_dma() 交错(interleaved)数据传输,如图像处理中的非连续帧传输 chan, xt (interleaved template 结构体), flags

3.3.2 典型使用流程:从设备 DMA 传输

以下代码片段展示了消费者驱动如何使用 dmaengine_prep_slave_sg() 发起一次从设备到内存的 DMA 读取操作。

/* 1. 请求 DMA 通道 */
struct dma_chan *dma_chan = dma_request_slave_channel(dev, "rx");
if (!dma_chan) {
    dev_err(dev, "Failed to request DMA channel\n");
    return -ENODEV;
}

/* 2. 准备 DMA 描述符 */
struct scatterlist sg;
sg_init_one(&sg, buffer, BUFFER_SIZE);
dma_addr_t dma_addr = dma_map_single(dev, buffer, BUFFER_SIZE, DMA_FROM_DEVICE);

struct dma_async_tx_descriptor *tx_desc;
tx_desc = dmaengine_prep_slave_sg(dma_chan, &sg, 1, DMA_DEV_TO_MEM,
                                  DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
if (!tx_desc) {
    dev_err(dev, "Failed to prepare slave sg\n");
    dma_unmap_single(dev, dma_addr, BUFFER_SIZE, DMA_FROM_DEVICE);
    return -ENOMEM;
}

/* 3. 设置完成回调 */
tx_desc->callback = dma_rx_callback;
tx_desc->callback_param = my_private_data;

/* 4. 提交事务到硬件队列 */
dma_cookie_t cookie = dmaengine_submit(tx_desc);
if (dma_submit_error(cookie)) {
    dev_err(dev, "Failed to submit DMA transaction\n");
    return -EIO;
}

/* 5. 启动 DMA 传输 */
dma_async_issue_pending(dma_chan);
流程要点:
  1. 通道请求: dma_request_slave_channel() 根据设备树或 ACPI 中的 DMA 配置,获取一个合适的通道。
  2. 内存映射: 使用 dma_map_single() 或 dma_map_sg() 确保 CPU 缓存与 DMA 设备之间的一致性(详见后续章节)。
  3. 准备描述符: dmaengine_prep_slave_sg() 将 scatterlist 转换为硬件可理解的描述符链。
  4. 提交与启动: dmaengine_submit() 将描述符放入通道的等待队列,dma_async_issue_pending() 则通知硬件开始处理。
DMA 事务准备与提交流程 1. 请求通道 dma_request_slave_channel() 2. 映射 DMA 缓冲区 dma_map_single() / dma_map_sg() 3. 准备描述符 dmaengine_prep_slave_sg() 4. 设置回调 & 提交 tx_desc->callback = func; dmaengine_submit() 5. 启动传输 dma_async_issue_pending() 关键数据结构:dma_async_tx_descriptor • cookie: 事务唯一标识 • callback: 传输完成回调函数 • callback_param: 回调参数 • tx_submit: 提交描述符到硬件队列 • desc_free: 释放描述符资源 • phys: 硬件描述符的物理地址
性能提示: 在高速数据流场景(如 10GbE 网络)中,应尽量复用 dma_async_tx_descriptor 和 DMA 缓冲区,避免在每次传输时都进行分配和映射操作。可以使用 DMA_CTRL_ACK 标志来指示描述符在完成后不被自动释放,以便后续重用。