2. Linux DMA子系统概述

DMA(Direct Memory Access)子系统是Linux内核中负责管理直接内存访问的核心基础设施。它向上为设备驱动提供统一的数据传输接口,向下屏蔽不同硬件DMA控制器的差异。理解DMA子系统的架构层次、dmaengine框架以及关键配置结构体,是编写正确、高效DMA驱动的先决条件。

2.1 DMA引擎架构

Linux内核的DMA子系统采用分层架构设计,从底层的硬件寄存器操作到上层的驱动API,共分为三个主要层次:

层次 组件 职责
硬件层 DMA控制器(如PL330、EDMA、Intel IOMMU) 执行实际的内存搬运、链式传输、硬件同步
核心框架层 dmaengine 核心(drivers/dma/dmaengine.c) 提供统一的API、通道管理、事务队列调度
驱动使用层 外设驱动(SPI、I2C、Audio、GPU等) 通过标准接口申请通道、配置传输、提交事务
架构要点: 硬件层与驱动使用层完全解耦。外设驱动无需关心底层DMA控制器是挂在AMBA总线还是PCIe总线上,只需通过dmaengine API操作抽象的DMA通道(dma_chan)。
DMA引擎分层架构 DMA引擎分层架构 驱动使用层(外设驱动) SPI驱动 | I2C驱动 | Audio驱动 | 其他外设驱动 核心框架层(dmaengine核心) 通道管理 | 事务队列 | 回调机制 | 统一API 硬件抽象层(DMA控制器驱动) PL330 | EDMA | Intel IOMMU | 其他DMA引擎 解耦关键 驱动不直接操作硬件寄存器

2.2 dmaengine框架

dmaengine 是Linux内核自2.6.38版本引入的通用DMA框架,旨在替代旧式的、与平台强绑定的DMA API。它定义了一套标准化的操作集合,使得DMA引擎驱动(provider)和DMA使用方(consumer)可以独立开发。

2.2.1 核心数据结构

框架围绕以下几个核心对象构建:

  • struct dma_device:描述一个DMA控制器硬件设备,包含其支持的能力(如设备树匹配、通道数量、传输类型)。
  • struct dma_chan:代表一个抽象的DMA通道,是驱动使用方操作DMA的句柄。
  • struct dma_async_tx_descriptor:描述一次DMA传输事务的描述符,包含源地址、目的地址、传输宽度、回调函数等。
  • struct dma_slave_config:用于配置从设备(slave)与DMA控制器之间的接口参数,是本章重点。

2.2.2 典型使用流程

外设驱动使用 dmaengine 的典型步骤分为以下五个阶段:

  1. 请求通道:调用 dma_request_chan() 或 dma_request_slave_channel(),根据设备树或平台数据获取一个可用的DMA通道。
  2. 配置从设备参数:填充 struct dma_slave_config,并通过 dmaengine_slave_config() 下发到DMA引擎驱动。
  3. 准备传输描述符:调用 dmaengine_prep_slave_sg()(SG列表模式)或 dmaengine_prep_dma_cyclic()(循环模式)等函数,获得传输描述符。
  4. 提交并启动:通过 dmaengine_submit() 将描述符提交到硬件队列,再调用 dma_async_issue_pending() 启动传输。
  5. 等待完成:注册回调函数或使用 dma_wait_for_async_tx() 等待传输结束。
最佳实践: 在步骤2中,务必确保 dma_slave_config 的 direction 字段与后续使用的传输方向一致。配置错误是DMA驱动中最常见的故障原因之一。
dmaengine典型使用流程 dmaengine 典型使用流程 1. 请求DMA通道 2. 配置 dma_slave_config 3. 准备传输描述符 4. submit + issue_pending 5. 等待传输完成 关键配置 direction src_addr / dst_addr src_maxburst / dst_maxburst

2.3 dma_slave_config 结构体

struct dma_slave_config 是连接外设驱动与DMA引擎驱动的桥梁。它定义了从设备(如SPI控制器、I2C控制器)与DMA控制器之间的硬件接口参数。该结构体定义在 include/linux/dmaengine.h 中。

2.3.1 结构体定义与字段解析

struct dma_slave_config {
    enum dma_transfer_direction direction;
    phys_addr_t src_addr;
    phys_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 src_port_window_size;
    u32 dst_port_window_size;
    bool device_fc;
    unsigned int slave_id;
};
字段 类型 说明
direction enum dma_transfer_direction 传输方向:DMA_MEM_TO_DEV、DMA_DEV_TO_MEM、DMA_DEV_TO_DEV
src_addr phys_addr_t 源端物理地址(通常是从设备的FIFO地址)
dst_addr phys_addr_t 目的端物理地址
src_addr_width enum dma_slave_buswidth 源端总线宽度:DMA_SLAVE_BUSWIDTH_1_BYTE、_2_BYTES、_4_BYTES、_8_BYTES
dst_addr_width enum dma_slave_buswidth 目的端总线宽度
src_maxburst u32 源端最大突发传输长度(以 src_addr_width 为单位)
dst_maxburst u32 目的端最大突发传输长度
device_fc bool 硬件流控标志:true 表示由外设硬件控制DMA请求
slave_id unsigned int 从设备标识符(用于某些DMA控制器区分外设请求线)

2.3.2 配置示例:SPI发送场景

以下代码展示了一个典型的SPI主控制器使用DMA进行内存到设备传输的配置过程:

#include <linux/dmaengine.h>

struct dma_slave_config config;
struct dma_chan *tx_chan;

/* 1. 请求DMA通道 */
tx_chan = dma_request_slave_channel(dev, "tx");
if (IS_ERR(tx_chan))
    return PTR_ERR(tx_chan);

/* 2. 清空并填充配置结构体 */
memset(&config, 0, sizeof(config));
config.direction = DMA_MEM_TO_DEV;
config.dst_addr = spi->phys_base + SPI_TX_FIFO;   /* SPI发送FIFO物理地址 */
config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; /* 32位总线 */
config.dst_maxburst = 4;                           /* 每次突发4个字 */

/* 3. 下发配置到DMA引擎 */
ret = dmaengine_slave_config(tx_chan, &config);
if (ret < 0) {
    dev_err(dev, "failed to configure tx channel\n");
    dma_release_channel(tx_chan);
    return ret;
}
常见错误: 许多开发者混淆了 src_addr 和 dst_addr 的赋值规则。对于 DMA_MEM_TO_DEV 方向,src_addr 是内存缓冲区地址(由DMA引擎自动处理),dst_addr 才是外设FIFO地址。反之,对于 DMA_DEV_TO_MEM,src_addr 是外设FIFO地址。不要手动填写内存侧的物理地址,否则会导致地址映射错误。

2.3.3 突发传输与总线宽度匹配

src_maxburst 和 dst_maxburst 是影响DMA传输效率的关键参数。它们定义了DMA控制器在一次硬件握手信号触发后,连续传输的数据单元个数。

  • 匹配规则:maxburst 的值必须与外设FIFO的深度和DMA控制器的能力相匹配。例如,一个深度为8的32位FIFO,可以设置 dst_maxburst = 8,dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES。
  • 性能影响:过小的burst值会导致频繁的DMA请求和总线仲裁开销;过大的burst值可能造成外设FIFO溢出或欠载。
  • 硬件限制:某些DMA控制器要求burst值必须是2的幂(1、2、4、8、16等),且不能超过硬件FIFO深度。
dma_slave_config 配置映射关系 dma_slave_config 配置映射关系(MEM→DEV) 内存缓冲区 src_addr(自动管理) DMA控制器 src_addr_width / src_maxburst 外设FIFO dst_addr(物理地址) 配置要点 direction = DMA_MEM_TO_DEV dst_addr = 外设FIFO物理地址(如 SPI_TX_FIFO) dst_addr_width = 外设总线宽度(1/2/4/8字节) dst_maxburst = 每次触发传输的数据单元个数

2.4 本章小结

Linux DMA子系统通过 dmaengine 框架实现了硬件无关的DMA操作抽象。本章重点介绍了:

  • 三层架构:硬件层、核心框架层、驱动使用层,层间通过标准接口解耦。
  • dmaengine框架:提供了从通道请求到传输完成的完整生命周期管理。
  • dma_slave_config:作为配置核心,其字段的准确填写直接决定了DMA传输的正确性与效率。