ZYNQ7000 AXI DMA 接收中断(S2MM_introut)全解析:从硬件原理到Linux驱动开发

ZYNQ7000 AXI DMA 接收中断(S2MM_introut)全解析:从硬件原理到Linux驱动开发

文章目录

  • [ZYNQ7000 AXI DMA 接收中断(S2MM_introut)全解析:从硬件原理到Linux驱动开发](#ZYNQ7000 AXI DMA 接收中断(S2MM_introut)全解析:从硬件原理到Linux驱动开发)
    • 前言
    • [1. AXI DMA `S2MM_introut` 的作用](#1. AXI DMA S2MM_introut 的作用)
    • [2. 硬件映射:从 `IRQ_F2P` 到 GIC](#2. 硬件映射:从 IRQ_F2P 到 GIC)
    • [3. Linux 端如何检测该中断?](#3. Linux 端如何检测该中断?)
      • [方式 A:使用 Linux 内核自带的 Xilinx DMA 驱动](#方式 A:使用 Linux 内核自带的 Xilinx DMA 驱动)
      • [方式 B:使用 UIO 驱动(推荐自定义开发使用)](#方式 B:使用 UIO 驱动(推荐自定义开发使用))
    • [4. 进阶:如何判断中断产生的原因?](#4. 进阶:如何判断中断产生的原因?)
      • [4.1 关键寄存器 `S2MM_DMASR` (偏移量 `0x34`)](#4.1 关键寄存器 S2MM_DMASR (偏移量 0x34))
      • [4.2 用户空间判断与清除代码](#4.2 用户空间判断与清除代码)
    • [5. 总结](#5. 总结)

前言

在 ZYNQ7000 的软硬件协同设计中,AXI DMA(Direct Memory Access)是实现 PL(FPGA)与 PS(ARM CPU)之间高速数据传输的核心 IP。而在处理海量流式数据(如 ADC 采集、图像传输)时,S2MM(Stream to Memory Map,即接收通道) 的中断机制是保证 CPU 实时处理数据的关键。

本文将深入解析 AXI DMA 的 S2MM_introut 中断:它的作用是什么?接入 IRQ_F2P 后如何在 Linux 端检测?以及如何判断具体是哪种原因触发了该中断?


1. AXI DMA S2MM_introut 的作用

在 AXI DMA 中,S2MM 代表数据从 PL 端(Axi-Stream)流向 PS 端 DDR 内存(Axi Memory Map)。

S2MM_introut 就是该通道的硬件中断输出信号。当 S2MM 通道发生特定事件时,它会拉高该信号,请求 CPU 介入。默认情况下,它由以下三种事件触发(可在软件中配置使能):

  1. IOC (Interrupt on Complete - 传输完成中断) :设置的一帧数据或一个 Scatter-Gather 描述符成功写入 DDR 内存后触发。这是最常用的中断,用于通知 CPU"数据已就绪"。
  2. Dly (Delay Interrupt - 延迟/超时中断):当数据传输中途暂停,且在设定的时间窗口内没有新数据到达时触发。常用于接收未知长度的流数据。
  3. Err (Error Interrupt - 错误中断):当 AXI 总线发生响应错误、地址解码错误或内部 FIFO 溢出时触发。

2. 硬件映射:从 IRQ_F2P 到 GIC

在 Vivado 中,我们通常将 S2MM_introut 连接到 ZYNQ PS 端的 IRQ_F2P[15:0] 接口。

  • 硬件路由IRQ_F2P 是共享外设中断(SPI)。IRQ_F2P[15:0] 对应的硬件中断号在 ZYNQ 内部被映射为 61~6884~91
  • GIC 控制器 :当 PL 端拉高 S2MM_introut 时,信号通过 IRQ_F2P 触达 PS 端的 GIC(通用中断控制器),GIC 再向 ARM 核心派发中断。

3. Linux 端如何检测该中断?

在 Linux 系统中,有传统官方内核驱动UIO(用户空间IO) 两种检测方式。

方式 A:使用 Linux 内核自带的 Xilinx DMA 驱动

内核源码 drivers/dma/xilinx/xilinx_dma.c 已经实现了完整的中断底半部机制。

  1. 设备树配置:Vivado 导出的设备树会自动包含中断信息。
devicetree 复制代码
axi_dma_0: dma@40400000 {
    compatible = "xlnx,axi-dma-1.00.a";
    reg = <0x40400000 0x10000>;
    interrupt-parent = <&intc>;
    interrupts = <0 29 4>, <0 30 4>; /* 30 对应 S2MM 的中断号 */
    ...
};
  1. 检测方式 :在此模式下,驱动程序会自动申请中断。开发者在应用层只需通过标准的 DMA Engine API(如 dmaengine_submit)提交传输请求,并注册一个回调函数 。当 S2MM_introut 触发时,内核会自动调用你的回调函数。

方式 B:使用 UIO 驱动(推荐自定义开发使用)

如果不想写复杂的内核驱动,可以通过 UIO 机制将中断直接透传到用户空间。

在设备树中将 DMA 节点的 compatible 改为 "generic-uio",Linux 会在 /dev/ 下生成 uio0(或类似)设备。

用户空间 C 语言检测代码(阻塞读取):

c 复制代码
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>

int main() {
    int fd = open("/dev/uio0", O_RDWR);
    if (fd < 0) return -1;

    // 1. 使能 UIO 中断
    uint32_t unmask = 1;
    write(fd, &unmask, sizeof(unmask));

    printf("等待 S2MM 硬件中断...\n");

    // 2. 阻塞读取,当 S2MM_introut 触发时,read 会被唤醒返回
    uint32_t irq_count;
    read(fd, &irq_count, sizeof(irq_count));

    printf("捕获到中断!当前累计触发次数: %u\n", irq_count);

    close(fd);
    return 0;
}

4. 进阶:如何判断中断产生的原因?

当 Linux 端捕获到中断后,我们如何知道是 传输完成(IOC)超时(Dly) 还是 错误(Err) 导致的呢?

方法是:读取 AXI DMA 的状态寄存器 S2MM_DMASR

4.1 关键寄存器 S2MM_DMASR (偏移量 0x34)

该寄存器映射在 AXI DMA 基地址偏移 0x34 处。其 Bit 12、13、14 对应了三种中断状态:

Bit 位 名称 掩码 含义描述
Bit 12 IOC_Irq 0x00001000 传输完成。如果是1,说明一帧数据成功写入内存。
Bit 13 Dly_Irq 0x00002000 延迟/超时。如果是1,说明超时计数器溢出(无新数据)。
Bit 14 Err_Irq 0x00004000 错误。如果是1,说明发生了严重的 AXI 总线或内部错误。

4.2 用户空间判断与清除代码

在使用 UIO 的情况下,通过 mmap 映射 DMA 寄存器基地址后,可以通过如下逻辑进行判断。

⚠️ 注意:AXI DMA 的中断标志位是"写 1 清零"(W1C),判断完必须手动清零,否则会导致中断风暴死机!

c 复制代码
// 假设 dma_virtual_base 是通过 mmap 得到的 DMA 基虚拟地址
volatile uint32_t *s2mm_dmasr = (uint32_t *)((uint8_t *)dma_virtual_base + 0x34);

// 1. 读取当前状态寄存器的值
uint32_t status = *s2mm_dmasr;

// 2. 判断具体原因
if (status & 0x1000) {
    printf("-> 中断原因: 传输完成 (IOC)\n");
    // TODO: 此时可以安全读取 DDR 中的数据
}
if (status & 0x2000) {
    printf("-> 中断原因: 延迟超时 (Delay)\n");
}
if (status & 0x4000) {
    printf("-> 中断原因: 硬件错误 (Error)\n");
    // 进阶:可以继续判断 Bit[6:4] 确定是 Slave 错误还是 Decode 错误
}

// 3. 极其重要:清除中断标志(W1C - 写 1 清零)
// 只清除这三个中断位,不影响其他状态位
*s2mm_dmasr = (status & 0x7000); 

5. 总结

ZYNQ7000 AXI DMA 的 S2MM_introut 构成了一条从 PL 到 Linux 应用层的完整通知链:

  1. PL端硬件 产生事件 → \rightarrow →
  2. 拉高 S2MM_introut 通过 IRQ_F2P 送达 GIC → \rightarrow →
  3. Linux 内核 捕获并唤醒等待的进程 → \rightarrow →
  4. 应用层/驱动 读取 0x34 状态寄存器识别具体原因 → \rightarrow →
  5. 处理数据并 写1清零 中断位。
相关推荐
camellias_2 小时前
ubuntu(二)ubuntu18.04安装mysql8
linux·ubuntu·adb
藤谷性能2 小时前
Ubuntu 22.04:安装串口调试助手CoolTerm
linux·运维·ubuntu·串口·coolterm
路溪非溪2 小时前
如何使用sysfs来排查驱动问题
linux·arm开发·驱动开发
丶伯爵式2 小时前
Ubuntu 新装后常用设置
linux·运维·ubuntu
哼?~2 小时前
Socket编程准备
linux·网络
羌俊恩2 小时前
Vim modeline 命令执行漏洞(CVE-2026-34714)修复指导
linux·编辑器·vim·漏洞·cve-2026-34714
wang09072 小时前
Linux性能优化之中断
linux·运维·性能优化
bukeyiwanshui2 小时前
20260410 系统启动原理
linux
huanmieyaoseng10032 小时前
Linux 安装配置 Tomcat超详细2026新(附安装包)
linux·运维·tomcat