Linux内核DMA控制器驱动开发从入门到精通 - 第3节

3.1 DMA控制器内部组成

DMA(Direct Memory Access)控制器是系统中负责在无需CPU干预的情况下,直接在外设与内存之间、内存与内存之间搬运数据的硬件模块。理解其内部组成是驱动开发的基础。

3.1.1 核心组件架构

一个典型的DMA控制器内部包含以下关键单元:

  • 通道(Channel):每个通道独立管理一条数据传输链路,支持并发传输。
  • 请求线(Request Line):外设通过硬件信号线向DMA控制器发起传输请求。
  • 地址寄存器(Address Register):存储源地址和目标地址,支持递增/递减模式。
  • 控制寄存器(Control Register):配置传输方向、数据宽度、突发长度、中断使能等参数。
  • 状态寄存器(Status Register):指示传输完成、错误、通道忙等状态。
  • 仲裁器(Arbiter):当多个通道同时请求时,决定优先级顺序。
驱动开发要点: 在Linux内核中,每个DMA通道对应一个 dma_chan 结构体,通过 dma_request_channel() 申请。地址寄存器和控制寄存器的操作封装在 dma_slave_config 和 dma_async_tx_descriptor 中。
DMA控制器内部组成架构图 DMA控制器内部组成架构 外设请求线 外设请求线 外设请求线 通道 0 通道 1 通道 2 通道 3 仲裁器 优先级调度 并发控制 地址寄存器 源地址 / 目标地址 控制寄存器 方向 / 宽度 / 突发 状态寄存器 完成 / 错误 / 忙 系统总线 说明 每个通道独立拥有地址寄存器和控制寄存器,仲裁器根据优先级决定哪个通道获得总线控制权。

3.1.2 通道与请求线映射

每个DMA通道可以连接一个或多个外设请求线。在硬件设计上,请求线通过多路选择器(MUX)连接到通道。驱动开发时需要配置通道与请求线的映射关系。

通道编号 默认请求线 可配置外设 典型用途
Ch 0 REQ 0 UART TX 串口发送
Ch 1 REQ 1 UART RX 串口接收
Ch 2 REQ 2 SPI TX SPI发送
Ch 3 REQ 3 SPI RX SPI接收
Ch 4 REQ 4 I2C / 定时器 内存到内存
驱动提示: 在设备树中通过 dma-requests 属性指定请求线数量,通过 dmas 和 dma-names 属性绑定通道与外设。例如:dmas = <&dma0 0 0x10> 表示使用DMA0的通道0,请求线为0x10。

3.2 地址寄存器与控制寄存器详解

3.2.1 地址寄存器

每个DMA通道包含两个地址寄存器:

  • 源地址寄存器(SAR):存放数据源的内存地址或外设地址。
  • 目标地址寄存器(DAR):存放数据目标的内存地址或外设地址。

地址寄存器支持以下模式:

模式 描述 适用场景
递增模式 每次传输后地址自动增加数据宽度 内存到内存、内存到外设(内存端)
递减模式 每次传输后地址自动减少数据宽度 特殊缓冲区处理
固定模式 地址保持不变 外设FIFO寄存器访问

3.2.2 控制寄存器

控制寄存器是DMA通道配置的核心,典型位域定义如下:

/* 控制寄存器位域定义示例 */
#define DMA_CTRL_ENABLE        (1 << 0)   /* 通道使能 */
#define DMA_CTRL_DIR_MEM2MEM   (0 << 1)   /* 内存到内存 */
#define DMA_CTRL_DIR_MEM2DEV   (1 << 1)   /* 内存到外设 */
#define DMA_CTRL_DIR_DEV2MEM   (2 << 1)   /* 外设到内存 */
#define DMA_CTRL_WIDTH_8BIT    (0 << 3)   /* 8位数据宽度 */
#define DMA_CTRL_WIDTH_16BIT   (1 << 3)   /* 16位数据宽度 */
#define DMA_CTRL_WIDTH_32BIT   (2 << 3)   /* 32位数据宽度 */
#define DMA_CTRL_BURST_1       (0 << 5)   /* 突发长度1 */
#define DMA_CTRL_BURST_4       (1 << 5)   /* 突发长度4 */
#define DMA_CTRL_BURST_8       (2 << 5)   /* 突发长度8 */
#define DMA_CTRL_IE_COMPLETE   (1 << 8)   /* 传输完成中断使能 */
#define DMA_CTRL_IE_ERROR      (1 << 9)   /* 错误中断使能 */
常见错误: 配置控制寄存器时,必须先清除使能位(ENABLE),再修改其他配置位,最后重新使能。否则可能导致硬件状态机紊乱,产生不可预期的传输行为。

3.3 传输类型

DMA控制器支持三种基本传输类型,每种类型对应不同的硬件配置和驱动接口。

3.3.1 内存到内存(Memory-to-Memory)

源和目标都是内存地址,通常用于数据拷贝、缓冲区移动、内存初始化等场景。不需要外设请求线触发,由软件启动传输。

  • 特点:无需外设参与,CPU只需设置源地址和目标地址。
  • 触发方式:软件写通道使能位立即启动。
  • 典型应用:memcpy 加速、帧缓冲拷贝、加密/校验预处理。
  • Linux驱动接口:dma_async_memcpy_buf_to_buf()

3.3.2 内存到外设(Memory-to-Device)

数据从内存传输到外设的FIFO或寄存器,通常用于发送数据。外设通过请求线通知DMA控制器需要数据。

  • 特点:内存地址递增,外设地址固定。
  • 触发方式:外设断言请求线,DMA控制器响应后从内存读取数据写入外设。
  • 典型应用:UART发送、SPI发送、I2C发送、LCD显示数据传输。
  • Linux驱动接口:dmaengine_prep_slave_sg() 方向设置为 DMA_MEM_TO_DEV

3.3.3 外设到内存(Device-to-Memory)

数据从外设的FIFO或寄存器传输到内存,通常用于接收数据。外设收到数据后通过请求线通知DMA控制器取走数据。

  • 特点:外设地址固定,内存地址递增。
  • 触发方式:外设断言请求线,DMA控制器从外设读取数据写入内存。
  • 典型应用:UART接收、SPI接收、I2C接收、ADC采样数据采集。
  • Linux驱动接口:dmaengine_prep_slave_sg() 方向设置为 DMA_DEV_TO_MEM
DMA三种传输类型对比 DMA三种传输类型对比 类型1: 内存 → 内存 源内存 目标内存 软件触发,无外设参与 地址均递增 类型2: 内存 → 外设 内存 外设FIFO 外设请求线触发 内存地址递增,外设地址固定 类型3: 外设 → 内存 外设FIFO 内存 外设请求线触发 外设地址固定,内存地址递增 传输类型对比总结 内存→内存: 源和目标均为内存,软件触发,地址均递增 内存→外设: 源为内存(递增),目标为外设(固定),外设请求触发 外设→内存: 源为外设(固定),目标为内存(递增),外设请求触发

3.3.4 传输类型在Linux驱动中的配置

在Linux DMA引擎框架中,通过 dma_slave_config 结构体配置传输参数:

struct dma_slave_config config = {
    .direction = DMA_MEM_TO_DEV,        /* 传输方向 */
    .src_addr = (dma_addr_t)buf_phys,   /* 源物理地址 */
    .dst_addr = (dma_addr_t)uart_base,  /* 目标物理地址 */
    .src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,  /* 源数据宽度 */
    .dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,  /* 目标数据宽度 */
    .src_maxburst = 4,                  /* 源突发长度 */
    .dst_maxburst = 4,                  /* 目标突发长度 */
};

/* 应用配置到DMA通道 */
dmaengine_slave_config(chan, &config);

/* 准备传输描述符 */
desc = dmaengine_prep_slave_sg(chan, sgt, nents, DMA_MEM_TO_DEV,
                               DMA_PREP_INTERRUPT | DMA_CTRL_ACK);

/* 提交并启动传输 */
dmaengine_submit(desc);
dma_async_issue_pending(chan);
关键点:
  • src_addr 和 dst_addr 必须是物理地址,不能是虚拟地址。
  • 对于内存到内存传输,使用 dma_async_memcpy_buf_to_buf() 简化操作。
  • 突发长度(maxburst)应与外设FIFO深度匹配,避免溢出或欠载。
  • 传输完成回调函数中必须调用 dma_cookie_complete() 更新状态。

3.4 本章小结

DMA控制器的硬件原理是驱动开发的理论基础。核心要点包括:

  • 每个通道独立管理一条传输链路,包含地址寄存器、控制寄存器和状态寄存器。
  • 请求线是外设与DMA控制器之间的硬件握手信号,驱动中通过设备树配置映射关系。
  • 三种传输类型(内存到内存、内存到外设、外设到内存)对应不同的硬件触发方式和地址模式。
  • Linux DMA引擎框架通过 dma_slave_config 和 dmaengine_prep_slave_sg() 封装了硬件寄存器的操作。