3. DMA控制器核心寄存器详解

做DMA驱动开发,说白了就是在跟几个寄存器打交道。源地址、目的地址、传输长度、控制状态、中断状态——这五个寄存器几乎涵盖了所有DMA控制器的核心功能。不管你用的是什么平台,是ARM、RISC-V还是x86,这套寄存器逻辑基本是通用的。我个人习惯把这几组寄存器称为DMA的"五件套",搞懂了它们,驱动开发就稳了一半。

3.1 源地址寄存器

源地址寄存器用来存放数据的起始来源地址。如果是内存到外设的传输,源地址就是内存缓冲区的地址;如果是外设到内存,源地址就是外设的FIFO或数据寄存器的地址。

配置时要注意几点:

  • 地址对齐:很多DMA控制器要求源地址按传输宽度对齐。比如传输宽度是32位,地址就得4字节对齐。我遇到过调试一块FPGA加速卡,数据老是错位,折腾了两天才发现是源地址没有32字节对齐——这是个血的教训。
  • 地址递增模式:大部分DMA支持地址递增或固定。内存侧一般递增,外设侧一般固定。配置错了,数据就会乱掉。
  • 物理地址:DMA看到的是总线地址,不是虚拟地址。驱动里要做地址转换。
⚠️ 我曾经遇到过:源地址寄存器写了一个非法的物理地址范围,结果DMA直接读到了未映射的内存区域,系统硬挂。配置源地址前,一定要确认地址范围是合法的、可访问的。

3.2 目的地址寄存器

目的地址寄存器存放数据要写到哪里去。它的配置逻辑和源地址类似,但是方向相反。你想想看,源地址和目的地址就像是一条水管的两端,一端进水一端出水,搞反了数据就倒流了——当然硬件不会让你倒流,但传输会失败。

配置要点:

  • 同样注意对齐要求,目的地址不对齐可能触发总线错误。
  • 如果源和目的都是内存(比如内存拷贝场景),两个地址都要正确配置递增模式。
  • 目的地址区域不能和其他任务重叠。我记得有一次,DDR目标地址被别的驱动占用了,DMA写过去直接把别人的数据冲了,排查了很久才发现是地址冲突。

3.3 传输长度寄存器

传输长度寄存器告诉DMA要搬多少数据。单位通常是字节,也有些控制器支持按字或按块计数。

我建议你注意以下几点:

  • 不要设为0:长度设为0,大多数DMA控制器不会启动传输,或者直接报错。我曾经在一段代码里漏了长度判断,传了个0进去,DMA直接卡死在那,超时中断都救不回来。
  • 不要超过最大长度:每个DMA通道都有一个最大传输长度限制,超过了就得拆分成多次传输。
  • 递增还是递减:有些DMA控制器内部用递减计数器,初始值设传输长度,每传一个单元减1,减到0就完成。了解你的控制器用哪种模式,配置时心里才有底。
💡 小技巧:如果你不确定传输长度寄存器的单位是字节还是字,去翻一下数据手册的位定义。实在不行,配置一个已知长度的传输,量一下实际写入的数据量就能反向确认。

3.4 控制状态寄存器

这个寄存器是DMA的"大脑"。它控制传输方向、数据宽度、突发长度、传输类型(内存到内存还是外设到内存)、中断使能、以及启动和停止。

关键位域通常包括:

  • 传输方向位:读外设写内存,还是读内存写外设。
  • 数据宽度:8位、16位、32位、64位。
  • 突发长度:一次总线突发传输多少个数据单元。
  • 中断使能:传输完成、传输错误等中断的开关。
  • 启动位:写1启动传输,硬件传输完成后自动清0。

配置顺序上,我建议你先把其他寄存器(源地址、目的地址、长度)都设好,最后再配控制状态寄存器。因为一旦控制状态寄存器里的启动位被置位,DMA马上就按当前寄存器内容开始干活了。

🎯 实践建议:每次配置控制状态寄存器前,先做一次软件复位。很多DMA控制器都有一个复位位,写1清除所有内部状态。别偷懒,这个步骤能避免很多"灵异现象"。

3.5 中断状态寄存器

中断状态寄存器用来反映DMA传输的完成状态和错误状态。常见的中断标志有:

  • 传输完成标志:DMA把指定长度的数据搬完了。
  • 传输错误标志:总线错误、地址错误、长度错误等。
  • 半传输标志:传输长度过半时触发,常用于乒乓缓冲切换。

中断服务程序(ISR)的处理流程一般是:

  1. 读中断状态寄存器,确认是哪个事件触发了中断。
  2. 处理对应事件(比如更新描述符、通知上层、处理错误)。
  3. 写中断状态寄存器来清除中断标志——注意,大多数控制器是写1清0的机制,写0无效。
⚠️ 踩坑提醒:我曾经在ISR里忘了清中断标志,结果中断一直触发,CPU被卡死在中断上下文中,系统完全失去响应。ISR里第一件事就是读并清除中断标志,然后再做业务处理——这个顺序不要乱。

3.6 寄存器配置顺序与实战要点

配置DMA寄存器,顺序很重要。下面这张表总结了我常用的配置步骤:

步骤 寄存器 配置内容 注意事项
1 控制状态寄存器 软件复位,清除所有配置 确保DMA处于空闲状态
2 源地址寄存器 写入数据源物理地址 检查对齐要求和地址合法性
3 目的地址寄存器 写入数据目标物理地址 避免地址重叠,对齐检查
4 传输长度寄存器 写入传输字节数 不能为0,不能超过最大长度
5 控制状态寄存器 配置方向、宽度、中断使能等 此时先不置位启动位
6 控制状态寄存器 置位启动位开始传输 确认状态寄存器中的启动位已置位

下面是一个简化的寄存器配置代码片段(以某款常用DMA控制器为例):

/* 软件复位 */
dma->CR |= DMA_CR_SWRST;
udelay(10);
dma->CR &= ~DMA_CR_SWRST;

/* 配置源地址和目的地址 */
dma->SAR = src_phys_addr;
dma->DAR = dst_phys_addr;

/* 配置传输长度 */
dma->NDTR = length;

/* 配置控制寄存器:方向、宽度、使能中断 */
dma->CR = DMA_CR_DIR_M2P | DMA_CR_MSIZE_32 |
          DMA_CR_PSIZE_32 | DMA_CR_TCIE;

/* 启动传输 */
dma->CR |= DMA_CR_EN;
💡 我的习惯:每配置一个寄存器之后,都回读一次确认写入成功。有些总线上有写缓冲,写入不一定会立即生效。回读虽然多花几个时钟,但排查问题时能省下一整天。

下面这张图展示了五个核心寄存器之间的配置与数据流向关系,帮你从整体上理解它们如何协同工作:

DMA核心寄存器配置流程与数据流向 软件配置入口 控制状态寄存器 配置方向、模式、中断使能 源地址寄存器 数据源起始地址 传输长度寄存器 传输字节数 目的地址寄存器 数据目标起始地址 DMA传输引擎 执行数据搬移 中断状态寄存器 传输完成/错误标志 启动控制 控制与状态 地址参数 ← 上一章 📖 返回目录 下一章 →