2. DMA基础原理
DMA(Direct Memory Access,直接存储器访问)的本质,是让数据在外设与内存之间搬运时不再逐字节经过 CPU 寄存器。CPU 只需配置好“从哪来、到哪去、搬多少、搬完通知谁”,剩下的搬运工作由 DMA 控制器(DMAC)独立完成。理解 DMAC 的架构、传输模式、通道与请求线、描述符机制,是后续使用 dmaengine 接口和做性能调优的基础。
一、DMA控制器架构:谁在搬、搬什么、怎么通知
一个典型的 DMAC 内部由若干关键部件组成:通道引擎负责按描述符执行搬运;源/目的地址寄存器记录当前读写位置;传输计数寄存器记录剩余字节数;请求仲裁器决定多个外设请求同时到来时谁先获得总线;总线主接口以主设备身份访问内存和外设;中断/完成逻辑在传输结束或出错时通知 CPU。DMAC 与 CPU 共享系统总线,因此它既是“帮手”,也是“竞争者”。
从系统视角看,DMAC 位于内存与外设之间,形成一条独立于 CPU 执行流的数据通路。CPU 通过寄存器或内存映射方式配置 DMAC,外设通过请求线向 DMAC 申请服务,DMAC 再通过总线读写完成搬运。这条通路的效率,直接决定了 DMA 传输的吞吐和延迟。
二、传输模式:单次、块、突发与循环
DMAC 的传输模式决定了它如何占用总线和响应请求。常见模式包括:
- 单次传输(Single):每次请求搬运一个数据单元,适合低速外设,总线占用碎片化,但实时性好。
- 块传输(Block):一次请求搬运一整块数据,DMAC 连续占用总线,吞吐高,但可能阻塞 CPU 访问内存。
- 突发传输(Burst):在总线允许的突发长度内连续搬运多个单元,兼顾效率与公平性,是高性能场景的常用模式。
- 循环传输(Circular):描述符链首尾相连,搬完自动回到起点,适合音频、ADC 连续采样等流式场景。
选择模式时要在吞吐、延迟和 CPU 干扰之间权衡。例如音频播放要求周期稳定,循环模式配合半满/全满中断最合适;而大块文件拷贝则更适合块传输或突发传输。
三、通道与请求线:硬件资源的映射关系
DMAC 通常提供多个通道,每个通道可独立配置源、目的、宽度和传输模式。外设通过请求线(DREQ)向 DMAC 申请服务,请求线到通道的映射由 SoC 的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 通道的连接关系 | 确认设备树与手册一致 |
| 描述符链 | 多个搬运任务的链表结构 | 注意链尾中断和循环模式配置 |
六、案例:一次 UART 接收的 DMA 搬运
以 UART 接收为例:UART 每收到一个字节,通过 DREQ 请求线向 DMAC 发出请求;DMAC 在对应通道上按描述符把数据从 UART 数据寄存器搬到内存环形缓冲区;当搬够设定长度后,DMAC 产生完成中断,CPU 在中断中处理数据并重新挂载描述符。若使用循环模式,则缓冲区首尾相连,CPU 只需处理半满和全满事件,大幅降低中断频率。
这个案例中,通道、请求线、描述符和传输模式四者缺一不可:请求线触发搬运,通道执行搬运,描述符定义搬运内容,传输模式决定搬运节奏。后续使用 dmaengine 接口时,这些底层概念会以 dma_slave_config、dma_prep_dma_cyclic 等形式再次出现,理解它们才能写出正确的驱动配置。