2. Linux DMA子系统概述
在嵌入式Linux系统中,DMA(Direct Memory Access)子系统是连接外设与内存之间高速数据通道的核心基础设施。本章将深入剖析Linux内核DMA API的架构层次、dmaengine框架的设计哲学以及关键数据结构,帮助开发者建立从硬件寄存器到内核抽象层的完整认知。
2.1 Linux内核DMA API架构
Linux内核的DMA API采用分层设计,从上到下依次为:设备驱动层、通用DMA API层、DMA引擎层和硬件抽象层。这种分层结构使得上层驱动无需关心底层DMA控制器的具体实现细节。
2.2 dmaengine框架介绍
dmaengine是Linux内核中专门为DMA控制器设计的通用框架,位于drivers/dma/目录下。它提供了一套标准化的接口,使得不同厂商的DMA控制器(如TI的EDMA、ARM的PL330、Intel的IDMA等)能够以统一的方式被上层驱动使用。
2.2.1 dmaengine核心组件
| 组件 | 角色 | 关键函数/回调 |
|---|---|---|
| DMA设备 | 代表一个DMA控制器硬件实例 | dma_async_device_register() |
| DMA通道 | 代表一个独立的DMA传输通道 | dma_request_channel() |
| 传输描述符 | 描述一次DMA传输的所有参数 | dmaengine_prep_slave_sg() |
| 传输操作 | 控制传输的执行与状态 | dma_async_issue_pending() |
2.2.2 dmaengine工作流程
典型的dmaengine使用流程包含以下步骤:
- 通道申请: 调用
dma_request_channel()获取一个满足需求的DMA通道。 - 配置参数: 通过
dmaengine_slave_config()设置源/目的地址、数据宽度、突发长度等。 - 准备描述符: 使用
dmaengine_prep_slave_sg()或dmaengine_prep_dma_memcpy()创建传输描述符。 - 提交传输: 调用
dmaengine_submit()将描述符加入通道的待处理队列。 - 启动传输: 调用
dma_async_issue_pending()通知DMA控制器开始执行。 - 等待完成: 通过回调函数或
dma_wait_for_async_tx()等待传输结束。
2.3 主要数据结构
dmaengine框架的核心数据结构定义了DMA设备、通道、传输描述符等关键实体的属性和操作接口。理解这些数据结构是进行DMA驱动开发的基础。
2.3.1 struct dma_device
struct dma_device 代表一个DMA控制器设备,包含设备能力描述、通道列表和操作函数集。
struct dma_device {
unsigned int chancnt; /* 通道数量 */
unsigned int max_burst; /* 最大突发长度 */
u32 src_addr_widths; /* 支持的源地址宽度位图 */
u32 dst_addr_widths; /* 支持的目的地址宽度位图 */
u32 directions; /* 支持的传输方向位图 */
u32 cap_mask; /* 设备能力掩码 */
struct list_head channels; /* 通道链表头 */
struct list_head global_node; /* 全局设备链表节点 */
/* 操作函数集 */
int (*device_alloc_chan_resources)(struct dma_chan *chan);
void (*device_free_chan_resources)(struct dma_chan *chan);
struct dma_async_tx_descriptor *(*device_prep_dma_memcpy)(
struct dma_chan *chan, dma_addr_t dst, dma_addr_t src,
size_t len, unsigned long flags);
struct dma_async_tx_descriptor *(*device_prep_slave_sg)(
struct dma_chan *chan, struct scatterlist *sgl,
unsigned int sg_len, enum dma_transfer_direction direction,
unsigned long flags, void *context);
int (*device_config)(struct dma_chan *chan,
struct dma_slave_config *config);
enum dma_status (*device_tx_status)(struct dma_chan *chan,
dma_cookie_t cookie,
struct dma_tx_state *txstate);
void (*device_issue_pending)(struct dma_chan *chan);
};
2.3.2 struct dma_chan
struct dma_chan 代表一个已分配的DMA通道,是驱动与dmaengine交互的主要句柄。
struct dma_chan {
struct dma_device *device; /* 所属DMA设备 */
dma_cookie_t cookie; /* 当前传输的cookie */
dma_cookie_t completed_cookie; /* 已完成传输的cookie */
void *private; /* 通道私有数据 */
struct list_head device_node; /* 设备通道链表节点 */
struct list_head global_node; /* 全局通道链表节点 */
/* 回调相关 */
dma_async_tx_callback callback;
void *callback_param;
/* 通道属性 */
enum dma_transfer_direction dir;
struct dma_slave_config slave_config;
};
2.3.3 struct dma_async_tx_descriptor
传输描述符是dmaengine框架中最核心的数据结构,它封装了一次DMA传输的所有信息,包括源/目的地址、传输长度、回调函数等。
struct dma_async_tx_descriptor {
dma_cookie_t cookie; /* 传输cookie,用于跟踪状态 */
enum dma_ctrl_flags flags; /* 控制标志位 */
dma_addr_t src; /* 源物理地址 */
dma_addr_t dst; /* 目的物理地址 */
size_t len; /* 传输长度(字节) */
struct dma_chan *chan; /* 所属通道 */
/* 回调函数 */
dma_async_tx_callback callback;
void *callback_param;
/* 描述符操作 */
struct dma_async_tx_descriptor *(*tx_submit)(
struct dma_async_tx_descriptor *tx);
int (*desc_free)(struct dma_async_tx_descriptor *tx);
/* 链表节点,用于描述符队列 */
struct list_head tx_list;
struct list_head node;
};
2.3.4 struct dma_slave_config
该结构体用于配置从设备(外设)与DMA控制器之间的传输参数,通常在调用 device_config() 时使用。
struct dma_slave_config {
enum dma_transfer_direction direction;
dma_addr_t src_addr; /* 源外设地址 */
dma_addr_t dst_addr; /* 目的外设地址 */
enum dma_slave_buswidth src_addr_width; /* 源数据宽度 */
enum dma_slave_buswidth dst_addr_width; /* 目的数据宽度 */
u32 src_maxburst; /* 源最大突发长度 */
u32 dst_maxburst; /* 目的最大突发长度 */
u32 slave_id; /* 从设备ID(平台相关) */
};
dma_slave_config 时,src_addr 和 dst_addr 必须是物理地址(总线地址),而非内核虚拟地址。对于MMU使能的系统,需要使用 dma_map_single() 或 dma_alloc_coherent() 获取正确的DMA地址。
2.4 数据结构关系图
以下SVG图展示了dmaengine框架中主要数据结构之间的关联关系:
2.5 DMA传输类型与能力描述
dmaengine框架支持多种DMA传输类型,通过 dma_cap_mask_t 位图来描述设备的能力:
| 能力标志 | 传输类型 | 典型应用场景 |
|---|---|---|
DMA_MEMCPY |
内存到内存拷贝 | 数据搬移、缓冲区复制 |
DMA_SLAVE |
外设到内存/内存到外设 | SPI、I2C、UART等外设数据收发 |
DMA_CYCLIC |
循环传输 | 音频流、连续数据采集 |
DMA_INTERLEAVE |
交错传输 | 多通道数据交织处理 |
DMA_PRIVATE |
私有通道 | 特定外设独占使用 |
dma_get_any_slave_channel() 或 dma_request_chan() 来获取通道,而不是直接操作 dma_device 的通道链表。这样可以确保代码与未来内核版本的兼容性。