2. Linux DMA 子系统架构:dmaengine 框架、dma_device 结构体、dma_chan 通道管理

在 Linux 内核中,DMA 操作并非由各设备驱动直接操作硬件寄存器,而是通过一套统一的 dmaengine 框架 进行抽象。该框架位于 drivers/dma/ 目录下,向上为各设备驱动提供标准 API,向下对接不同厂商的 DMA 控制器硬件。理解这一分层架构是进行 DMA 性能优化与缓存一致性管理的基础。

核心认知: dmaengine 框架将 DMA 控制器抽象为 dma_device,将传输通道抽象为 dma_chan。设备驱动通过 dma_request_channel() 获取通道,通过 dmaengine_prep_slave_sg() 等接口准备传输描述符,最终由 dmaengine_submit() 提交并启动传输。

2.1 dmaengine 框架分层与核心数据结构

dmaengine 框架采用经典的三层架构:设备层、通道层 和 传输描述符层。每一层都有对应的核心数据结构,它们通过指针相互关联,形成一个完整的 DMA 传输对象模型。

架构要点: 框架的核心设计思想是“通道即资源”。每个 DMA 控制器可以拥有多个物理或逻辑通道,每个通道在同一时刻只能服务于一个传输请求。通道的分配与释放由框架统一管理,驱动开发者无需关心底层硬件细节。
dmaengine 框架三层架构与核心数据结构关系 设备层:struct dma_device 代表一个 DMA 控制器硬件实例 包含:capabilities 位图、通道列表、操作函数指针集 关键字段:dev, channels, cap_mask, device_alloc_chan_resources, device_free_chan_resources 通道层:struct dma_chan 代表一个可独立调度的 DMA 传输通道 包含:所属设备指针、通道 ID、客户端信息、完成回调 关键字段:device, chan_id, client, cookie, completed_cookie 传输描述符层:struct dma_async_tx_descriptor 描述一次 DMA 传输的所有参数 包含:通道指针、传输方向、源/目的地址、长度、回调函数 关键字段:chan, tx_submit, callback, callback_param, cookie 箭头表示“包含/管理”关系

2.2 struct dma_device:DMA 控制器的抽象

struct dma_device 是 dmaengine 框架中最顶层的结构体,它完整描述了一个 DMA 控制器的能力、资源与操作接口。每个 DMA 控制器驱动在初始化时,需要填充该结构体并调用 dma_async_device_register() 将其注册到框架中。

字段/操作 类型 说明
dev struct device * 指向 DMA 控制器的通用设备结构体,用于电源管理、设备树绑定等
channels struct list_head 该设备下所有可用通道的链表头,通过 list_add_tail() 添加通道
cap_mask dma_cap_mask_t 能力位图,标记该设备支持的传输类型(如 DMA_SLAVE、DMA_CYCLIC、DMA_INTERLEAVE)
device_alloc_chan_resources 函数指针 分配通道资源,通常在 dma_request_channel() 时被调用
device_free_chan_resources 函数指针 释放通道资源,在通道被释放时调用
device_prep_slave_sg 函数指针 准备一次 slave 模式的 SG(Scatter-Gather)传输描述符
device_issue_pending 函数指针 通知 DMA 控制器开始处理已提交的传输请求
device_tx_status 函数指针 查询指定传输描述符的完成状态,返回已传输的字节数或 cookie 状态
性能陷阱: 在注册 dma_device 时,cap_mask 的设置必须精确匹配硬件能力。如果错误地声明了不支持的传输类型,框架在运行时不会进行二次校验,可能导致 DMA 传输静默失败或数据损坏。建议在 probe 函数中通过 dma_cap_set() 逐位设置,并配合硬件寄存器确认。

2.3 struct dma_chan:通道管理与生命周期

struct dma_chan 是设备驱动与 DMA 引擎交互的直接对象。每个 dma_chan 实例代表一个可独立调度的传输通道,它维护了通道的运行时状态、所属设备、客户端绑定关系以及传输完成情况。

struct dma_chan {
    struct dma_device *device;      /* 指向所属的 DMA 控制器设备 */
    dma_cookie_t cookie;            /* 当前正在传输的描述符 cookie */
    dma_cookie_t completed_cookie;  /* 最后一个完成的描述符 cookie */
    int chan_id;                    /* 通道 ID,用于调试和日志 */
    struct dma_chan_dev *dev;       /* 通道对应的字符设备,用于 sysfs 导出 */
    struct list_head device_node;   /* 用于挂入 dma_device.channels 链表 */
    struct dma_chan *slave;         /* 指向配对的 slave 通道(memcpy 模式使用) */
    struct dma_client *client;      /* 当前占用该通道的客户端 */
    void *private;                  /* 驱动私有数据,通常指向硬件通道寄存器基址 */
};

通道的生命周期管理遵循“申请 → 配置 → 使用 → 释放”的严格流程:

  1. 通道申请: 设备驱动调用 dma_request_channel(),传入能力掩码和过滤函数。框架遍历所有已注册的 dma_device,找到第一个满足条件的空闲通道。
  2. 通道配置: 申请成功后,驱动通过 dmaengine_slave_config() 设置传输参数,包括源/目的地址宽度、突发大小、传输方向等。
  3. 描述符准备与提交: 调用 dmaengine_prep_slave_sg() 准备描述符,然后通过 dmaengine_submit() 提交到通道的 pending 队列。
  4. 启动传输: 调用 dma_async_issue_pending() 通知 DMA 控制器开始处理。
  5. 通道释放: 传输完成后,调用 dma_release_channel() 将通道归还给框架。
dma_chan 通道生命周期管理流程 dma_request_channel() dmaengine_slave_config() dmaengine_prep_slave_sg() dmaengine_submit() dma_async_issue_pending() dma_release_channel() 通道状态转换说明 • IDLE(空闲):通道未被任何客户端占用,可被 dma_request_channel() 分配 • ALLOCATED(已分配):通道已被某客户端申请,但尚未配置传输参数 • CONFIGURED(已配置):传输参数已设置,可以准备描述符 • ACTIVE(活跃):描述符已提交并下发到硬件,DMA 正在传输中 • RELEASED(已释放):通道归还给框架,回到 IDLE 状态

2.4 通道管理与性能优化要点

在实际的嵌入式系统中,DMA 通道是稀缺资源。一个高性能的 DMA 驱动设计需要关注以下通道管理策略:

优化方向 具体策略 预期收益
通道复用 对于非实时性传输,多个外设共享同一个 DMA 通道,通过软件调度分时使用 减少通道占用数量,降低硬件资源竞争
描述符预分配 在通道初始化时预分配一批描述符内存池,避免运行时动态分配 消除分配延迟,提升传输启动速度
批量提交 将多个小传输合并为一个 SG 描述符,一次性提交 减少通道切换开销,提高总线利用率
中断聚合 设置 DMA 控制器在完成多个描述符后才触发一次中断 降低 CPU 中断频率,减少上下文切换
通道优先级 为实时性要求高的外设(如音频、视频)分配高优先级通道 保证关键数据的传输延迟可控
实战建议: 在调试 DMA 通道问题时,可以通过 /sys/class/dma/ 目录下的 sysfs 接口查看每个通道的状态。例如 cat /sys/class/dma/dma0chan0/bytes_transferred 可以获取该通道累计传输的字节数,用于验证传输是否正常进行。

2.5 传输描述符与 cookie 机制

dmaengine 框架使用 cookie 机制来跟踪每个传输描述符的状态。每次调用 dmaengine_submit() 时,框架会为该描述符分配一个递增的 cookie 值。驱动可以通过 dma_async_is_tx_complete() 或 dmaengine_tx_status() 查询指定 cookie 对应的传输是否完成。

/* 典型的 DMA 传输提交与完成查询流程 */
struct dma_async_tx_descriptor *tx;
dma_cookie_t cookie;
enum dma_status status;

/* 准备描述符 */
tx = dmaengine_prep_slave_sg(chan, sgt->sgl, sgt->nents, dir, DMA_PREP_INTERRUPT);
if (!tx) {
    dev_err(dev, "Failed to prepare DMA descriptor\n");
    return -ENOMEM;
}

/* 设置完成回调 */
tx->callback = my_dma_callback;
tx->callback_param = my_data;

/* 提交传输,获取 cookie */
cookie = dmaengine_submit(tx);
if (dma_submit_error(cookie)) {
    dev_err(dev, "Failed to submit DMA transaction\n");
    return -EIO;
}

/* 启动传输 */
dma_async_issue_pending(chan);

/* 轮询等待完成(在中断回调中通常不这样做) */
do {
    status = dmaengine_tx_status(chan, cookie, NULL);
} while (status == DMA_IN_PROGRESS);

if (status == DMA_COMPLETE) {
    dev_info(dev, "DMA transfer completed successfully\n");
} else {
    dev_err(dev, "DMA transfer failed with status: %d\n", status);
}
关键注意事项: cookie 值在通道生命周期内单调递增,但可能会回绕(wrap around)。驱动在比较 cookie 时,应使用 dma_async_is_complete() 辅助函数,该函数内部处理了回绕逻辑。直接使用 == 比较 cookie 在回绕时会导致错误判断。