4. DMA描述符链(Descriptor Chain)
DMA描述符链是高性能嵌入式系统中实现批量、连续、零CPU干预数据传输的核心机制。它将多个传输任务组织成链表,由DMA控制器硬件自动遍历并执行,彻底解放处理器。
4.1 描述符结构定义
每个DMA描述符(Descriptor)是一个固定格式的内存结构,通常包含以下关键字段:
| 字段 | 宽度(典型) | 描述 |
|---|---|---|
| Next Descriptor Pointer | 32/64 bit | 指向下一个描述符的物理地址;链尾通常为0或回环指针 |
| Source Address | 32/64 bit | 本次传输的源地址(内存或外设FIFO) |
| Destination Address | 32/64 bit | 本次传输的目的地址 |
| Transfer Length | 16/32 bit | 本次传输的字节数(或数据项个数) |
| Control / Status | 16/32 bit | 控制标志(中断使能、传输类型)和完成状态位 |
设计要点:描述符必须对齐到缓存行边界(通常32或64字节),且驻留在非缓存(Non-cacheable)或一致性(Coherent)内存区域,避免CPU与DMA之间的缓存一致性问题。
以下是一个典型的描述符结构体定义(以ARM PL080为例):
/* DMA描述符结构体 */
struct dma_descriptor {
uint32_t next; /* 下一个描述符物理地址 */
uint32_t src_addr; /* 源地址 */
uint32_t dst_addr; /* 目的地址 */
uint32_t length; /* 传输长度(字节) */
uint32_t ctrl; /* 控制字:中断使能、传输宽度等 */
uint32_t status; /* 状态字:完成标志、错误标志 */
uint32_t reserved[2]; /* 保留,对齐到32字节 */
} __attribute__((aligned(32)));
4.2 链表组织方式
描述符链采用单向链表结构,每个描述符的next字段指向下一个描述符的物理地址。DMA控制器内部维护一个当前描述符指针寄存器(Current Descriptor Pointer, CDP),初始指向链首。
链表组织规则:
- 线性链:最后一个描述符的
next置为0或NULL,传输完成后DMA停止。 - 环形链:最后一个描述符的
next指向链首,实现无限循环传输(常用于音频、视频流)。 - 中断插入:在任意描述符的控制字中设置中断标志,DMA完成该段后触发中断,允许CPU动态修改后续链表。
4.3 硬件自动加载机制
DMA控制器通过硬件状态机自动遍历描述符链,整个过程无需CPU参与:
- 初始化:CPU将链首描述符的物理地址写入DMA的
CDP寄存器,并启动DMA。 - 加载描述符:DMA控制器通过总线主控(Bus Master)从内存读取当前描述符到内部寄存器。
- 执行传输:根据描述符中的源地址、目的地址和长度,启动一次DMA传输。
- 更新状态:传输完成后,DMA硬件将
status字段置为完成标志(通常写回内存)。 - 加载下一个:读取当前描述符的
next字段,若非0则跳转到步骤2;否则停止或触发中断。
关键时序约束:硬件加载描述符需要占用总线带宽。在高速传输场景(如PCIe、GigE)中,描述符加载时间可能成为瓶颈。建议将描述符链放在高速SRAM或紧耦合内存(TCM)中,并尽量使用突发读取(Burst Read)来减少加载延迟。
4.4 链式传输的优势
相比单次DMA传输,描述符链在性能、灵活性和CPU利用率方面具有显著优势:
| 对比维度 | 单次DMA传输 | 描述符链传输 |
|---|---|---|
| CPU干预次数 | 每次传输都需要CPU配置和启动 | 一次配置,硬件自动遍历整个链表 |
| 传输连续性 | 段间存在CPU处理延迟 | 硬件无缝衔接,零延迟切换 |
| 内存碎片处理 | 需要CPU合并分散缓冲区 | 每个描述符可指向不同内存区域 |
| 中断频率 | 每段传输都产生中断 | 仅在链尾或指定节点产生中断 |
| 适用场景 | 简单、低频、小数据量 | 高速流、大数据块、实时系统 |
典型应用场景:
- 网络数据包处理:将多个不连续的Socket缓冲区(SKB)组织成描述符链,实现零拷贝收发包。
- 音频/视频流:使用环形描述符链实现双缓冲或多缓冲,消除画面撕裂和音频卡顿。
- 存储控制器:将文件系统的多个分散块通过描述符链一次性提交给存储设备,提升IOPS。
- FPGA加速器:通过描述符链向FPGA发送批量任务描述符,实现流水线处理。
描述符链的另一个关键优势是动态可扩展性。CPU可以在DMA运行过程中,通过修改链尾描述符的next指针来追加新的传输任务,实现运行时链表扩展。这种机制在不确定数据量的流式处理中尤为重要。