2. Linux DMA 子系统架构:DMA Engine 框架、dmaengine API 分层、dma_device 与 dma_chan 结构体解析

Linux DMA 子系统(DMA Engine)是内核中为 DMA 控制器驱动提供统一抽象的核心框架。它屏蔽了不同 SoC 厂商 DMA 控制器的硬件差异,为外设驱动(如 SPI、I2S、MMC)提供了一套标准化的 API 接口。理解 DMA Engine 的架构分层、核心数据结构 dma_device 与 dma_chan 是编写高效 DMA 驱动的基础。

设计哲学: DMA Engine 框架借鉴了 Linux 块设备层与网络协议栈的分层思想,将“通道管理”、“传输描述符”、“硬件寄存器操作”解耦,使得上层驱动无需关心底层 DMA 控制器的具体实现细节。

2.1 DMA Engine 框架整体架构

DMA Engine 框架采用经典的三层架构,从下至上依次为:硬件抽象层、核心管理层、用户/驱动 API 层。每一层职责清晰,通过标准接口进行交互。

DMA Engine 三层架构图 DMA Engine 框架三层架构 用户/驱动 API 层 dmaengine_*() 系列 API:dmaengine_prep_slave_sg()、dmaengine_submit()、dma_async_issue_pending() 核心管理层 dma_device 注册/注销、dma_chan 分配/释放、传输描述符生命周期管理、虚拟通道支持 硬件抽象层 DMA 控制器驱动实现:device_alloc_chan_resources()、device_prep_slave_sg()、device_issue_pending() 说明:上层 API 通过核心管理层调用底层硬件驱动,底层驱动通过回调函数向上层反馈传输完成状态

上图清晰地展示了 DMA Engine 的三层协作关系。上层驱动(如 ALSA、SPI 驱动)通过标准 API 发起 DMA 请求;核心管理层负责通道的分配与描述符的管理;硬件抽象层则由具体的 DMA 控制器驱动实现,直接操作寄存器。

2.2 dmaengine API 分层详解

dmaengine API 按照功能可以分为三个层次:通道管理 API、传输控制 API 和 回调与状态 API。下表总结了各层的关键函数及其用途。

API 层次 关键函数 功能描述
通道管理 dma_request_channel() 根据掩码(mask)请求一个满足条件的 DMA 通道
dma_release_channel() 释放不再使用的 DMA 通道
dma_get_slave_channel() 获取已指定从设备的专用通道(用于特定外设)
传输控制 dmaengine_prep_slave_sg() 准备一个分散/聚集(SG)传输描述符
dmaengine_prep_dma_cyclic() 准备一个循环传输描述符(常用于音频流)
dmaengine_prep_dma_memcpy() 准备一个内存到内存的拷贝描述符
dmaengine_submit() 将描述符提交到传输队列
dma_async_issue_pending() 触发 DMA 控制器开始处理队列中的待处理传输
回调与状态 dma_async_is_tx_complete() 查询特定传输描述符是否已完成
dmaengine_tx_status() 获取通道的当前传输状态(已完成字节数、错误标志等)
dmaengine_desc_set_callback() 为传输描述符注册完成回调函数
关键点: dmaengine_submit() 只是将描述符放入队列,并不会立即启动传输。真正的启动由 dma_async_issue_pending() 触发。这种“提交-触发”分离机制允许驱动批量提交多个描述符,然后一次性启动,提高效率。

2.3 dma_device 结构体解析

struct dma_device 是 DMA 控制器驱动的核心抽象,它描述了一个 DMA 控制器的全部能力。该结构体定义在 include/linux/dmaengine.h 中,主要包含以下关键字段:

  • 通道列表: dev.channels、dev.global_lock — 管理该控制器下的所有 DMA 通道。
  • 能力掩码: cap_mask — 使用 dma_cap_set() 设置,表明该控制器支持哪些传输类型(如 DMA_SLAVE、DMA_MEMCPY、DMA_CYCLIC)。
  • 操作函数集: 一系列 device_* 回调函数指针,由具体驱动实现。

以下是 dma_device 中最重要的操作回调函数及其作用:

回调函数 作用 必须实现?
device_alloc_chan_resources() 分配通道资源(如硬件描述符环、中断等) 是
device_free_chan_resources() 释放通道资源 是
device_prep_slave_sg() 准备从设备 SG 传输 若支持 DMA_SLAVE
device_prep_dma_cyclic() 准备循环传输 若支持 DMA_CYCLIC
device_prep_dma_memcpy() 准备内存拷贝 若支持 DMA_MEMCPY
device_issue_pending() 触发硬件开始处理待处理描述符 是
device_tx_status() 查询传输状态 是
device_terminate_all() 终止通道上的所有传输 推荐实现
注意: 在实现 device_prep_slave_sg() 时,必须正确解析 struct scatterlist 链表,并将每个 SG 条目映射到 DMA 控制器的硬件描述符。如果硬件支持链接描述符(LLI),应充分利用以减少中断次数。

2.4 dma_chan 结构体解析

struct dma_chan 代表一个具体的 DMA 通道实例。它是上层驱动与底层硬件之间的桥梁。每个 dma_chan 都关联到一个 dma_device,并包含通道的运行时状态。

dma_chan 的核心字段包括:

  • device:指向所属的 dma_device 结构体。
  • chan_id:通道 ID,用于在系统内唯一标识该通道。
  • slave:指向使用该通道的从设备配置(如 struct dma_slave_config)。
  • private:驱动私有数据指针,通常指向硬件通道寄存器基址或描述符池。
  • completed_cookie 与 last_completed:用于跟踪传输完成状态,实现 cookie 机制。
dma_device 与 dma_chan 关系图 dma_device 与 dma_chan 关系图 struct dma_device cap_mask: DMA_SLAVE | DMA_CYCLIC channels: list_head device_prep_slave_sg() device_issue_pending() device_tx_status() ... struct dma_chan (通道 0) device = &dma_dev chan_id = 0, private = hw_regs_0 struct dma_chan (通道 1) device = &dma_dev chan_id = 1, private = hw_regs_1 关键关系说明 1. 一个 dma_device 可以管理多个 dma_chan,每个通道对应一个硬件 DMA 通道。 2. dma_chan->device 指针指向其所属的 dma_device,通过该指针调用回调函数。

2.5 虚拟通道(Virtual Channel)机制

对于硬件通道数量有限的 DMA 控制器,DMA Engine 提供了虚拟通道(vchan)支持。虚拟通道允许在单个物理通道上复用多个逻辑通道,由核心层自动处理排队与调度。

使用虚拟通道的典型步骤:

  1. 在 dma_device 中设置 struct virt_dma_chan 替代普通的 dma_chan。
  2. 调用 vchan_init() 初始化虚拟通道。
  3. 在 device_issue_pending() 回调中调用 vchan_issue_pending() 触发调度。
  4. 在中断处理函数中调用 vchan_cookie_complete() 通知上层传输完成。
优势: 虚拟通道机制极大地简化了多客户端场景下的 DMA 驱动开发。驱动开发者只需关注单个物理通道的启动与中断处理,通道复用与描述符排队由 vchan 层自动完成。

2.6 本章小结