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 用户空间判断与清除代码)
- [4.1 关键寄存器 `S2MM_DMASR` (偏移量 `0x34`)](#4.1 关键寄存器
- [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 介入。默认情况下,它由以下三种事件触发(可在软件中配置使能):
- IOC (Interrupt on Complete - 传输完成中断) :设置的一帧数据或一个 Scatter-Gather 描述符成功写入 DDR 内存后触发。这是最常用的中断,用于通知 CPU"数据已就绪"。
- Dly (Delay Interrupt - 延迟/超时中断):当数据传输中途暂停,且在设定的时间窗口内没有新数据到达时触发。常用于接收未知长度的流数据。
- 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~68 和 84~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 已经实现了完整的中断底半部机制。
- 设备树配置: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 的中断号 */
...
};
- 检测方式 :在此模式下,驱动程序会自动申请中断。开发者在应用层只需通过标准的
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 应用层的完整通知链:
- PL端硬件 产生事件 → \rightarrow →
- 拉高
S2MM_introut通过IRQ_F2P送达 GIC → \rightarrow → - Linux 内核 捕获并唤醒等待的进程 → \rightarrow →
- 应用层/驱动 读取
0x34状态寄存器识别具体原因 → \rightarrow → - 处理数据并 写1清零 中断位。