DMA控制器编程与PCIe数据搬运优化工程案例 - 第1节

1.1 DMA 工作原理

直接存储器访问(Direct Memory Access, DMA)是一种允许硬件子系统(外设或 DMA 控制器)直接读写系统内存,而无需 CPU 参与数据搬运的机制。DMA 控制器是系统中的专用硬件引擎,负责管理数据传输的全过程。

核心思想:将数据搬运任务从 CPU 卸载到专用硬件,使 CPU 可以专注于计算和控制逻辑。

DMA 传输的基本流程

  1. CPU 配置 DMA 控制器:CPU 设置源地址、目的地址、传输长度、传输方向等参数。
  2. DMA 控制器请求总线控制权:DMA 向总线仲裁器发出总线请求信号(HRQ/BREQ)。
  3. 总线授权:CPU 或总线仲裁器释放总线控制权,DMA 获得总线主控能力。
  4. DMA 执行数据传输:DMA 控制器直接驱动地址总线和数据总线,完成数据搬运。
  5. 传输完成通知:DMA 发送中断信号(INT)通知 CPU 传输结束。
DMA 传输基本流程 CPU 配置 DMA DMA 请求总线 总线仲裁授权 DMA 执行数据传输 传输完成 → 触发中断 整个传输过程中 CPU 可以执行其他任务,仅在配置和中断处理阶段介入

1.2 DMA 与 CPU 数据搬运对比

理解 DMA 与 CPU 搬运数据的本质差异,是掌握 DMA 编程的基础。下表从多个维度对比两种方式:

对比维度 CPU 直接搬运 DMA 控制器搬运
执行主体 CPU 核心(执行 load/store 指令) DMA 控制器(专用硬件状态机)
CPU 占用率 100%(搬运期间无法执行其他任务) 仅配置和中断处理阶段占用(通常 < 5%)
数据传输速率 受限于 CPU 指令执行速度(通常 1~5 GB/s) 接近总线理论带宽(PCIe Gen4 x16 可达 32 GB/s)
适用场景 小数据量(< 64 KB)、非连续地址、频繁修改 大数据块(≥ 64 KB)、连续地址、周期性传输
功耗效率 高(CPU 核心全速运行) 低(专用硬件,能效比高 10~100 倍)
编程复杂度 简单(memcpy / 循环赋值) 中等(需配置描述符、处理中断、管理链表)
工程经验:在 PCIe 高速数据采集系统中,CPU 搬运 1 MB 数据约需 50 μs,而 DMA 仅需 2~3 μs(含配置开销),性能提升 15~25 倍。

CPU 搬运 vs DMA 搬运的代码对比

// CPU 直接搬运(使用 memcpy)
void cpu_memcpy(uint32_t *src, uint32_t *dst, size_t size) {
    memcpy(dst, src, size);  // CPU 100% 占用,无法响应其他中断
}

// DMA 搬运(以 Xilinx AXI DMA 为例)
void dma_transfer(uint32_t *src, uint32_t *dst, size_t size) {
    // 1. 配置 DMA 描述符
    dma_desc_t desc;
    desc.src_addr = (uint32_t)src;
    desc.dst_addr = (uint32_t)dst;
    desc.length   = size;
    desc.next     = NULL;  // 单次传输

    // 2. 启动 DMA(CPU 仅需 3 条写操作)
    write_reg(DMA_SRC_ADDR,  desc.src_addr);
    write_reg(DMA_DST_ADDR,  desc.dst_addr);
    write_reg(DMA_CTRL,      DMA_START | DMA_IE);  // 启动并使能中断

    // 3. CPU 立即返回,可执行其他任务
    // DMA 完成后触发中断,在 ISR 中处理结果
}

1.3 DMA 传输类型

根据数据源和目的地的不同,DMA 传输分为三种基本类型。在 PCIe 系统中,这三种类型对应不同的应用场景。

三种传输类型详解

DMA 三种传输类型 类型1:内存 → 内存 源内存 目的内存 应用:内存拷贝、数据搬移、缓冲区刷新 特点:无需外设参与,纯内存操作 类型2:外设 → 内存 外设(PCIe) 系统内存 应用:ADC 采样数据采集、网卡收包、存储读 特点:外设主动产生数据,DMA 搬运至内存 类型3:内存 → 外设 系统内存 外设(PCIe) 应用:DAC 波形输出、网卡发包、存储写 特点:内存数据发送至外设,外设消费数据 PCIe 系统中的 DMA 传输类型映射 • Memory-to-Memory (M2M): 用于 FPGA 内部 BRAM 与 DDR 之间的数据搬移 • Device-to-Memory (D2M): PCIe 从设备(如 ADC 卡)向主机内存写入数据(DMA Read) • Memory-to-Device (M2D): 主机内存向 PCIe 从设备发送数据(DMA Write) ⚠ 注意:PCIe 术语中 "DMA Read" 指从外设读取数据到内存(D2M),"DMA Write" 指从内存写入外设(M2D)

传输类型选择指南

传输类型 典型场景 PCIe 方向 DMA 描述符配置要点
内存 → 内存 FPGA 内部数据重排、缓冲区交换 不经过 PCIe(片内) src/dst 均为内存地址,无需 PCIe TLP
外设 → 内存 高速 ADC 数据采集、雷达信号捕获 PCIe 上行(Endpoint → Root Complex) src 为 PCIe BAR 地址,dst 为系统物理地址
内存 → 外设 任意波形发生器、GPU 纹理上传 PCIe 下行(Root Complex → Endpoint) src 为系统物理地址,dst 为 PCIe BAR 地址
常见误区:初学者常将 "DMA 读/写" 与 "PCIe 读/写" 混淆。在 PCIe 语境下,DMA Read 是指 DMA 控制器从外设读取数据到内存(外设→内存),而非从内存读取数据。务必根据数据流动方向而非操作名称判断。

DMA 传输类型与中断处理

三种传输类型在中断处理上也有差异:

  • 内存→内存:通常只需一次完成中断,无需考虑数据溢出。
  • 外设→内存:需处理"数据可用"中断和"传输完成"中断,防止 FIFO 溢出。
  • 内存→外设:需处理"传输完成"中断和"外设就绪"握手,防止 FIFO 下溢。
// 外设→内存 DMA 中断服务程序示例(伪代码)
void dma_isr(void) {
    uint32_t status = read_reg(DMA_STATUS);

    if (status & DMA_DONE) {
        // 当前传输完成,处理已接收数据
        process_buffer(current_desc->dst_addr, current_desc->length);

        // 提交下一个描述符(循环缓冲)
        submit_next_descriptor();
    }

    if (status & DMA_ERROR) {
        // 处理总线错误或超时
        handle_dma_error();
    }

    // 清除中断标志
    write_reg(DMA_STATUS, status);
}