4. Linux DMA子系统概述

在嵌入式Linux系统中,DMA(Direct Memory Access)是一种允许硬件设备直接读写系统内存而无需CPU干预的数据传输技术。为了统一管理不同硬件平台上的DMA控制器,Linux内核提供了一个标准化的DMA引擎框架——dmaengine。本章将深入剖析该框架的核心设计理念与关键数据结构。

4.1 Linux内核DMA引擎框架(dmaengine)

dmaengine框架位于内核源码的 drivers/dma/ 目录下,它抽象了不同DMA控制器的硬件差异,为驱动开发者提供了一套统一的API。该框架采用消费者-提供者模型:

  • 提供者(Provider):DMA控制器驱动,负责注册硬件能力到框架。
  • 消费者(Consumer):其他外设驱动(如SPI、I2C、音频),通过框架API发起DMA传输请求。
设计目标: 让消费者驱动无需关心底层DMA控制器的寄存器细节,只需描述“从哪里搬到哪里”即可。

下图展示了dmaengine框架在系统中的层次位置与核心交互流程:

Linux DMA引擎框架(dmaengine)架构图 用户空间应用程序 内核空间 消费者驱动 (SPI / I2C / Audio / Crypto) dmaengine核心层 dma_device / dma_chan / tx descriptor DMA控制器驱动(提供者) PL330 / EDMA / SDMA / XDMA / HSDMA DMA控制器硬件 系统调用 / ioctl dmaengine API 回调注册 寄存器操作

4.2 dmaengine核心数据结构

dmaengine框架围绕三个核心数据结构构建:struct dma_device、struct dma_chan 和 struct dma_async_tx_descriptor。它们分别代表DMA控制器、DMA通道和一次传输事务的描述符。

4.2.1 struct dma_device —— DMA控制器抽象

struct dma_device 是DMA控制器的软件抽象,每个DMA控制器驱动在初始化时分配并注册一个该结构体实例。它包含了控制器的能力描述和操作函数指针。

字段 类型 说明
dev struct device * 关联的Linux设备模型节点
cap_mask dma_cap_mask_t 位掩码,标识支持的传输类型(如MEMCPY、SG、P2P)
directions u32 支持的DMA方向位图(DEV_TO_MEM / MEM_TO_DEV / MEM_TO_MEM)
src_addr_widths u32 源端支持的数据宽度(1/2/4/8字节)
dst_addr_widths u32 目的端支持的数据宽度
device_alloc_chan_resources 函数指针 分配并初始化一个DMA通道
device_free_chan_resources 函数指针 释放DMA通道资源
device_prep_dma_memcpy 函数指针 准备一次内存到内存的拷贝传输
device_prep_slave_sg 函数指针 准备一次散列表(SG)方式的从设备传输
device_issue_pending 函数指针 将已准备好的描述符提交给硬件执行
关键点: cap_mask 是消费者驱动选择DMA通道时的首要过滤条件。例如,一个音频驱动只会选择设置了 DMA_SLAVE 能力的通道。

4.2.2 struct dma_chan —— DMA通道抽象

struct dma_chan 代表一个已分配的DMA通道。消费者驱动通过 dma_request_channel() 获得该结构体,之后所有传输操作都通过它进行。

字段 类型 说明
device struct dma_device * 指向所属DMA控制器的反向指针
cookie dma_cookie_t 通道当前传输的cookie值,用于状态跟踪
chan_id int 通道在控制器内的索引ID
slave_config struct dma_slave_config 从设备配置(地址、宽度、突发大小等)
private void * 提供者驱动的私有数据指针
注意事项: DMA通道是稀缺资源。一个DMA控制器通常只有4~8个通道,消费者驱动在使用完毕后必须调用 dma_release_channel() 归还通道,否则其他驱动将无法使用。

4.2.3 struct dma_async_tx_descriptor —— 传输事务描述符

每次DMA传输请求被封装为一个 struct dma_async_tx_descriptor。它描述了一次完整的传输事务,包括源地址、目的地址、传输长度、回调函数等信息。

字段 类型 说明
tx_submit 函数指针 将描述符提交到通道的待处理队列
callback dma_async_tx_callback 传输完成后的回调函数
callback_param void * 回调函数的参数
cookie dma_cookie_t 该描述符的唯一标识,用于查询传输状态
phys dma_addr_t 描述符对应的硬件链表物理地址(用于链式DMA)
chan struct dma_chan * 该描述符所属的DMA通道

下图展示了从消费者发起请求到描述符被硬件执行的完整生命周期:

DMA传输描述符生命周期 1. 分配通道 dma_request_channel() 2. 准备描述符 device_prep_*() 3. 提交描述符 tx_submit() 4. 触发执行 issue_pending() 5. 完成回调 callback() 6. 释放通道 dma_release_channel() 关键流程说明 描述符的提交(tx_submit)只是将其加入待处理队列, 真正的硬件传输由 issue_pending() 触发启动。

4.3 数据结构关系总结

三个核心数据结构之间的关系可以概括为:

  • struct dma_device 是工厂,负责创建和管理通道。
  • struct dma_chan 是工人,代表一个可用的传输通道。
  • struct dma_async_tx_descriptor 是任务单,描述一次具体的传输工作。
编程模型: 消费者驱动通过 dma_device 中的函数指针创建描述符,然后将描述符提交给 dma_chan,最后调用 issue_pending 让硬件开始工作。这种设计将传输准备与传输执行解耦,支持批量提交和异步完成。