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 驱动的基础。
2.1 DMA Engine 框架整体架构
DMA Engine 框架采用经典的三层架构,从下至上依次为:硬件抽象层、核心管理层、用户/驱动 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 机制。
2.5 虚拟通道(Virtual Channel)机制
对于硬件通道数量有限的 DMA 控制器,DMA Engine 提供了虚拟通道(vchan)支持。虚拟通道允许在单个物理通道上复用多个逻辑通道,由核心层自动处理排队与调度。
使用虚拟通道的典型步骤:
- 在
dma_device中设置struct virt_dma_chan替代普通的dma_chan。 - 调用
vchan_init()初始化虚拟通道。 - 在
device_issue_pending()回调中调用vchan_issue_pending()触发调度。 - 在中断处理函数中调用
vchan_cookie_complete()通知上层传输完成。