2. Linux DMA子系统架构

Linux DMA 子系统通过一套统一的框架——DMA Engine,将不同厂商、不同硬件特性的 DMA 控制器抽象为标准的软件接口。本章深入剖析该框架的核心数据结构与分层设计,帮助开发者理解 DMA 通道的申请、配置与传输控制机制。

2.1 DMA Engine 框架分层

DMA Engine 框架位于内核 drivers/dma/ 目录下,采用经典的三层架构:

  • DMA Engine 核心层:提供通用 API(如 dma_request_channel()、dmaengine_prep_slave_sg()),管理通道生命周期与传输描述符。
  • DMA 控制器驱动层:实现 struct dma_device 中的回调函数,直接操作硬件寄存器。
  • DMA 用户层:包括 ALSA、MMC、SPI 等子系统,通过核心层 API 发起 DMA 传输请求。
关键设计原则: DMA Engine 将“通道分配”与“传输控制”解耦。用户驱动只需关注通道句柄和描述符,无需了解底层寄存器细节。
DMA Engine 三层架构 用户层(ALSA / MMC / SPI / UART 等) 调用 dma_request_channel() / dmaengine_prep_slave_sg() / dmaengine_submit() DMA Engine 核心层 通道管理 / 描述符池 / 回调分发 / 虚拟通道支持 DMA 控制器驱动层 实现 struct dma_device 回调:device_alloc_chan_resources / device_prep_slave_sg / device_issue_pending 核心层维护全局 dma_list 链表,驱动层注册时自动加入

2.2 核心结构体:dma_device

struct dma_device 是 DMA 控制器驱动的“身份证”,定义在 include/linux/dmaengine.h 中。它描述了控制器的能力、支持的传输类型以及所有操作回调。

字段 类型 说明
dev struct device * 指向 DMA 控制器的 struct device,用于设备模型关联
channels struct list_head 该控制器下所有 DMA 通道的链表头
cap_mask dma_cap_mask_t 位掩码,声明支持的传输类型(如 MEMCPY、SLAVE、CYCLIC)
directions u32 位掩码,声明支持的传输方向(DEV_TO_MEM / MEM_TO_DEV / MEM_TO_MEM)
max_burst u32 单次突发传输的最大字节数
device_alloc_chan_resources 回调函数指针 分配通道硬件资源,返回描述符数量
device_prep_slave_sg 回调函数指针 准备散聚(SG)传输描述符
device_issue_pending 回调函数指针 将待处理描述符提交给硬件执行
device_tx_status 回调函数指针 查询传输状态(已完成/进行中/错误)
驱动注册示例: 在 probe 函数中,驱动填充 struct dma_device 后调用 dma_async_device_register() 将其注册到核心层。核心层自动将 channels 链表中的每个通道加入全局通道池。

2.3 通道抽象:dma_chan

struct dma_chan 代表一个已分配的 DMA 通道实例。用户驱动通过 dma_request_channel() 获得该结构体指针,后续所有传输操作都基于它进行。

struct dma_chan {
    struct dma_device *device;      /* 所属 DMA 控制器 */
    dma_cookie_t cookie;            /* 当前传输的 cookie 值 */
    struct list_head global_node;   /* 全局通道链表节点 */
    void *private;                  /* 控制器驱动私有数据 */
    const char *name;               /* 通道名称,用于调试 */
    int chan_id;                    /* 通道 ID,在控制器内唯一 */
    struct dma_chan_dev *dev;       /* 通道对应的字符设备,用于 sysfs */
};

通道的生命周期管理遵循以下流程:

  1. 申请:用户驱动调用 dma_request_channel(),核心层遍历全局通道池,匹配 cap_mask 和过滤函数。
  2. 配置:通过 dmaengine_slave_config() 设置源/目标地址、数据宽度、突发大小等参数。
  3. 使用:准备描述符 → 提交 → 等待完成回调。
  4. 释放:调用 dma_release_channel(),核心层将通道归还到空闲池。
dma_chan 生命周期与关键操作 dma_request_channel() dmaengine_slave_config() dmaengine_prep_*() dmaengine_submit() dma_async_issue_pending() dma_release_channel() 关键说明 • 过滤函数在 dma_request_channel() 中匹配通道能力(如 DMA_SLAVE、DMA_CYCLIC) • dmaengine_submit() 返回 cookie,用于后续 dmaengine_tx_status() 查询 • 释放通道前必须确保所有描述符已完成或已终止

2.4 DMA 控制器抽象层:统一硬件差异

不同 SoC 的 DMA 控制器在寄存器布局、通道数量、传输粒度上差异巨大。DMA Engine 通过以下机制实现抽象:

  • 能力掩码(cap_mask):用户驱动通过 dma_has_cap() 查询控制器是否支持 MEMCPY、SLAVE、CYCLIC 等操作。
  • 虚拟通道(virt-dma):核心层提供 struct virt_dma_chan 辅助实现,将硬件通道与软件描述符队列解耦,简化驱动开发。
  • 描述符重用:通过 dmaengine_desc_set_reuse() 和 dmaengine_desc_get_reuse() 减少内存分配开销,适用于环形缓冲区场景。
常见陷阱: 部分 DMA 控制器不支持 SG 散聚传输,此时 device_prep_slave_sg 回调返回 NULL。驱动必须检查返回值并回退到逐个缓冲区传输,否则会导致内核崩溃。

2.5 本章小结