2. DMA基础原理

DMA(Direct Memory Access,直接存储器访问)的本质,是让数据在外设与内存之间搬运时不再逐字节经过 CPU 寄存器。CPU 只需配置好“从哪来、到哪去、搬多少、搬完通知谁”,剩下的搬运工作由 DMA 控制器(DMAC)独立完成。理解 DMAC 的架构、传输模式、通道与请求线、描述符机制,是后续使用 dmaengine 接口和做性能调优的基础。

一、DMA控制器架构:谁在搬、搬什么、怎么通知

一个典型的 DMAC 内部由若干关键部件组成:通道引擎负责按描述符执行搬运;源/目的地址寄存器记录当前读写位置;传输计数寄存器记录剩余字节数;请求仲裁器决定多个外设请求同时到来时谁先获得总线;总线主接口以主设备身份访问内存和外设;中断/完成逻辑在传输结束或出错时通知 CPU。DMAC 与 CPU 共享系统总线,因此它既是“帮手”,也是“竞争者”。

从系统视角看,DMAC 位于内存与外设之间,形成一条独立于 CPU 执行流的数据通路。CPU 通过寄存器或内存映射方式配置 DMAC,外设通过请求线向 DMAC 申请服务,DMAC 再通过总线读写完成搬运。这条通路的效率,直接决定了 DMA 传输的吞吐和延迟。

CPU 配置寄存器 响应中断 内存 源缓冲区 目的缓冲区 DMA控制器 DMAC 通道引擎 请求仲裁 地址/计数 总线主接口 中断/完成逻辑 外设 UART / SPI / ADC 请求线 DREQ / 握手 配置 读写 数据 请求 完成中断

二、传输模式:单次、块、突发与循环

DMAC 的传输模式决定了它如何占用总线和响应请求。常见模式包括:

  • 单次传输(Single):每次请求搬运一个数据单元,适合低速外设,总线占用碎片化,但实时性好。
  • 块传输(Block):一次请求搬运一整块数据,DMAC 连续占用总线,吞吐高,但可能阻塞 CPU 访问内存。
  • 突发传输(Burst):在总线允许的突发长度内连续搬运多个单元,兼顾效率与公平性,是高性能场景的常用模式。
  • 循环传输(Circular):描述符链首尾相连,搬完自动回到起点,适合音频、ADC 连续采样等流式场景。

选择模式时要在吞吐、延迟和 CPU 干扰之间权衡。例如音频播放要求周期稳定,循环模式配合半满/全满中断最合适;而大块文件拷贝则更适合块传输或突发传输。

三、通道与请求线:硬件资源的映射关系

DMAC 通常提供多个通道,每个通道可独立配置源、目的、宽度和传输模式。外设通过请求线(DREQ)向 DMAC 申请服务,请求线到通道的映射由 SoC 的DMA 路由矩阵决定。同一请求线可能被多个外设复用,配置错误会导致“请求来了但通道不动”或“搬错数据”。

在设备树中,DMA 通道和请求线通常以 phandle + 通道号 + 请求号的形式描述。调试时应先确认外设节点引用的 DMA 控制器、通道号和请求号与手册一致,再检查通道是否被其他驱动占用。

四、描述符机制:把“搬运任务”变成链表

描述符(Descriptor)是 DMAC 可执行的搬运任务单元,通常包含源地址、目的地址、传输长度、控制字段和下一个描述符指针。多个描述符可以组成描述符链,DMAC 按链依次执行,实现分散-聚集(Scatter-Gather)传输。这样一次配置就能完成多段不连续内存的搬运,减少 CPU 干预。

struct dma_desc {
    u32 src_addr;      // 源地址
    u32 dst_addr;      // 目的地址
    u32 length;        // 传输长度
    u32 ctrl;          // 控制字段:宽度、突发、中断使能
    u32 next;          // 下一个描述符地址,0 表示结束
};

在 Linux dmaengine 框架中,描述符由 DMA 驱动内部管理,上层通过 dma_async_tx_descriptor 和 dma_cookie 跟踪传输状态。理解描述符链,有助于分析“为什么传输被拆成多段”以及“中断为什么在链中间触发”。

五、核心参数与常见误区

参数/概念 含义 调优关注点
传输宽度 单次搬运的数据位宽(8/16/32/64 位) 与外设 FIFO 宽度匹配,避免拆包
突发长度 一次总线突发搬运的单元数 过大影响 CPU 延迟,过小降低吞吐
通道优先级 多通道竞争总线时的仲裁权重 实时流应设高优先级
请求线映射 外设 DREQ 到 DMAC 通道的连接关系 确认设备树与手册一致
描述符链 多个搬运任务的链表结构 注意链尾中断和循环模式配置
常见误区:认为“配置了 DMA 就一定会快”。如果传输宽度与外设不匹配、突发长度设置不当、通道优先级过低,DMA 反而可能因为频繁仲裁和总线争用导致性能下降。另一个误区是忽略请求线映射,导致外设请求无法触发通道,表现为“DMA 传输超时”。

六、案例:一次 UART 接收的 DMA 搬运

以 UART 接收为例:UART 每收到一个字节,通过 DREQ 请求线向 DMAC 发出请求;DMAC 在对应通道上按描述符把数据从 UART 数据寄存器搬到内存环形缓冲区;当搬够设定长度后,DMAC 产生完成中断,CPU 在中断中处理数据并重新挂载描述符。若使用循环模式,则缓冲区首尾相连,CPU 只需处理半满和全满事件,大幅降低中断频率。

这个案例中,通道、请求线、描述符和传输模式四者缺一不可:请求线触发搬运,通道执行搬运,描述符定义搬运内容,传输模式决定搬运节奏。后续使用 dmaengine 接口时,这些底层概念会以 dma_slave_config、dma_prep_dma_cyclic 等形式再次出现,理解它们才能写出正确的驱动配置。