AXI_DMA回环测试

简介: 本文详解ZYNQ(xc7z020)+ Petalinux 2018.3下AXI DMA高速回环实现:从Vivado硬件搭建(DMA+FIFO、双时钟域配置)、设备树自动生成、内核驱动适配,到Linux DMA Engine API测试驱动开发,并总结4个典型坑(通道申请失败、16384字节丢1字节、大包超时、terminate_all误用)及根因解决。

ZYNQ AXI DMA 高速数据传输实战:从 Vivado 硬件搭建到 Linux 回环测试(含 4 个踩坑)

适用场景:ZYNQ(xc7z020)+ Petalinux 2018.3 开发,想在 PS 和 PL 之间实现高速数据传输。本文从 Vivado 硬件搭建开始,完整记录 AXI DMA + AXI Stream Data FIFO 回环实验的全过程,包括设备树配置、内核驱动、Linux DMA Engine API 测试驱动,以及 4 个真实踩坑的排查与解决。

前言

在 ZYNQ 开发中,PS 和 PL 之间的数据传输是核心课题。GPIO、UART 这些低速接口显然不够用,AXI DMA 才是高速数据搬运的标配。

最近我在 AC880 开发板上做了 AXI DMA 回环实验,从 Vivado 硬件搭建到 Linux 驱动测试完整走通,256 字节到 1MB 数据全部传输正确。过程中踩了不少坑,特别是 dma_request_channel 拿不到通道、16384 字节差 1 字节、大传输超时这几个问题,折腾了挺久。

本文把完整流程和踩坑记录分享出来,希望能帮到正在做 AXI DMA 的朋友。

环境信息:

|
项
|
值

|
| --- | --- |
|
开发板
|
AC880 ZYNQ(xc7z020clg484-2)
|
|
Vivado 版本
|
2018.3
|
|
Petalinux 版本
|
2018.3
|
|
内核版本
|
4.14-xilinx-v2018.3
|
|
AXI DMA 模式
|
Simple(Scatter Gather 关闭)
|

一、AXI DMA 硬件架构速览

AXI DMA 有两个数据通道:

  • MM2S(Memory Map to Stream):从 DDR 读数据 → 输出 AXI Stream

  • S2MM(Stream to Memory Map):接收 AXI Stream → 写入 DDR

回环实验的数据通路:

DDR(tx_buf) ──MM2S──> AXI Stream ──FIFO──> AXI Stream ──S2MM──> DDR(rx_buf)

中间用一个 AXI Stream Data FIFO 把 MM2S 的输出连到 S2MM 的输入,形成回环。

二、Vivado 硬件搭建

2.1 添加 IP

在 Block Design 中添加:

  • AXI DMA:高速数据搬运

  • AXI Stream Data FIFO:回环缓冲

2.2 AXI DMA 配置

|
参数
|
值
|
说明

|
| --- | --- | --- |
|
Scatter Gather Engine
|
关闭
|
Simple 模式
|
|
Width of Buffer Length Register
|
14
|
决定单次最大传输 = 2^14 - 1 = 16383 字节
|
|
Memory Map Data Width
|
32
| |
|
Stream Data Width
|
32
| |
|
启用通道
|
MM2S + S2MM
| |

2.3 AXI Stream Data FIFO 配置

|
参数
|
值
|
说明

|
| --- | --- | --- |
|
TDATA Width (bytes)
|
4
|
32 位,必须和 DMA Stream 宽度一致
|
|
FIFO Depth
|
默认
|
经测试 4096 字节以内传输稳定
|

2.4 时钟域配置(关键!)

AXI DMA 的控制口和数据口必须用不同时钟域:

|
信号
|
连接到
|
时钟

|
| --- | --- | --- |
|
s_axi_lite_aclk(控制)
|
FCLK_CLK0
|
100 MHz
|
|
m_axi_mm2s_aclk(数据读)
|
FCLK_CLK1
|
200 MHz
|
|
m_axi_s2mm_aclk(数据写)
|
FCLK_CLK1
|
200 MHz
|
|
s_axis_aclk(FIFO)
|
FCLK_CLK1
|
200 MHz
|

控制口走 GP0(100MHz),数据口走 HP0(200MHz)。如果时钟域搞错,Vivado 会报一堆时钟不匹配错误。

2.5 HP 端口共享

如果 HP0 已经被 VDMA 占用,不需要启用 HP1,复用现有 axi_smc(AXI SmartConnect)即可:

  • S00: axi_vdma_0

  • S01: axi_dma_0 MM2S

  • S02: axi_dma_0 S2MM

配置为 3 Slave → 1 Master → S_AXI_HP0。

2.6 地址分配

|
接口
|
地址范围
|
大小

|
| --- | --- | --- |
|
S_AXI_LITE(控制)
|
0x4040_0000 - 0x4040_FFFF
|
64K
|
|
Data_MM2S(HP0)
|
0x0000_0000 - 0x3FFF_FFFF
|
1G DDR
|
|
Data_S2MM(HP0)
|
0x0000_0000 - 0x3FFF_FFFF
|
1G DDR
|

地址无冲突,无需手动分配。

2.7 时序分析

生成 bitstream 后时序报告显示 WNS = -0.174 ns(174ps 违例),仅 1/33712 端点失败。

这种程度的违例对功能无影响:

  • 174ps 是皮秒级,远小于 1ns

  • 是 AXI DMA IP 内部路径,无法修改 RTL

  • FCLK_CLK1 = 200MHz 不能降频

  • 实际芯片性能通常比 STA 悲观估计好

直接 Generate Bitstream 即可。

2.8 导出 HDF

File → Export → Export Hardware,勾选 Include bitstream,导出到 ~/ZYNQ/zynq_linux/hdf/。

三、Petalinux 配置

3.1 导入 HDF

cd ~/ZYNQ/zynq_linux
petalinux-config --get-hw-description=./hdf/
# menuconfig 直接 Save → Exit

3.2 内核配置

petalinux-config -c kernel
Device Drivers  --->
  [*] DMA Engine support  --->
        <*> Xilinx DMA Configuration Engine    ← CONFIG_XILAXIDMA

3.3 设备树(重要踩坑!)

HDF 导入后,Petalinux 会自动在 pl.dtsi 中生成 axi_dma_0 节点:

axi_dma_0: dma@40400000 {
   
    #dma-cells = <1>;
    clock-names = "s_axi_lite_aclk", "m_axi_mm2s_aclk", "m_axi_s2mm_aclk";
    clocks = <&clkc 15>, <&clkc 16>, <&clkc 16>;
    compatible = "xlnx,axi-dma-7.1", "xlnx,axi-dma-1.00.a";
    interrupt-names = "mm2s_introut", "s2mm_introut";
    interrupt-parent = <&intc>;
    interrupts = <0 32 4 0 33 4>;
    reg = <0x40400000 0x10000>;
    ...
};

⚠️ 千万不要在 system-user.dtsi 里手动覆盖 axi_dma_0 节点!

我一开始手动写了一段,结果编译报错 Reference to non-existent node or label "gic"。原因是 ZYNQ 7000 的 GIC 标签是 intc 不是 gic,而且中断号我猜错了(写成 59/60,实际是 32/33)。

自动生成的节点完全正确,手动覆盖只会引入错误。

3.4 编译 + 打包

petalinux-build

petalinux-package --boot --fsbl images/linux/zynq_fsbl.elf \
  --fpga images/linux/system.bit \
  --u-boot images/linux/u-boot.elf \
  --output images/linux/BOOT.BIN --force

四、部署到 SD 卡(U-Boot + TFTP)

用 TFTP 更新 system.dtb、system.bit、zImage,BOOT.BIN 建议用读卡器直接拷贝(风险最高):

# U-Boot 命令行
tftpboot 0x2000000 system.dtb  && fatwrite mmc 0:1 0x2000000 system.dtb  ${filesize}
tftpboot 0x3000000 system.bit  && fatwrite mmc 0:1 0x3000000 system.bit  ${filesize}
tftpboot 0x2080000 zImage      && fatwrite mmc 0:1 0x2080000 zImage      ${filesize}

五、板端验证

# 1. DMA 通道设备节点
ls /sys/class/dma/
# 期望: dma1chan0 (MM2S), dma1chan1 (S2MM)

# 2. 驱动加载日志
dmesg | grep -i "xilinx\|dma"
# 期望: xilinx-vdma 40400000.dma: Xilinx AXI DMA Engine Driver Probed!!

# 3. 中断注册
cat /proc/interrupts | grep xilinx-dma
# 期望: mm2s(IRQ64), s2mm(IRQ65)

六、回环测试驱动(DMA Engine API)

6.1 核心 API 流程

// 1. 申请通道 (DMA_SLAVE mask, Xilinx DMA 只注册此能力)
dma_cap_zero(mask);
dma_cap_set(DMA_SLAVE, mask);
tx_chan = dma_request_channel(mask, xilinx_dma_filter, NULL);  // MM2S
rx_chan = dma_request_channel(mask, xilinx_dma_filter, NULL);  // S2MM

// 2. 分配一致性内存
tx_buf = dma_alloc_coherent(dev, size, &tx_dma, GFP_KERNEL);
rx_buf = dma_alloc_coherent(dev, size, &rx_dma, GFP_KERNEL);

// 3. 先启动 S2MM (接收), 再启动 MM2S (发送)
rx_desc = dmaengine_prep_slave_single(rx_chan, rx_dma, len, DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT);
rx_desc->callback = dma_rx_callback;
dmaengine_submit(rx_desc);
dma_async_issue_pending(rx_chan);

udelay(10);  // 确保 S2MM 就绪

tx_desc = dmaengine_prep_slave_single(tx_chan, tx_dma, len, DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT);
tx_desc->callback = dma_tx_callback;
dmaengine_submit(tx_desc);
dma_async_issue_pending(tx_chan);

// 4. 等待完成
wait_for_completion_timeout(&tx_done, 5*HZ);
wait_for_completion_timeout(&rx_done, 5*HZ);

// 5. 数据校验
memcmp(tx_buf, rx_buf, size);

6.2 通道过滤函数

static bool xilinx_dma_filter(struct dma_chan *chan, void *param)
{
   
    struct device *dev = chan->device->dev;
    const char *name;
    bool match = false;

    if (!dev) return false;
    name = dev_name(dev);

    // 策略 1: compatible 匹配
    if (dev->of_node && of_device_is_compatible(dev->of_node, "xlnx,axi-dma-1.00.a"))
        match = true;

    // 策略 2: 设备名含基地址 40400000
    if (name && strstr(name, "40400000"))
        match = true;

    return match;
}

6.3 大传输拆分(sg-length-width 限制)

设备树中 xlnx,sg-length-width = <0xe>(14 位),单次传输最大 2^14 - 1 = 16383 字节。超过必须拆分:

#define AXI_DMA_MAX_XFER  4096  // 不用 16383, 用 4096 避免 FIFO 溢出

for (offset = 0; offset < size; offset += chunk_len) {
   
    remaining = size - offset;
    chunk_len = (remaining > AXI_DMA_MAX_XFER) ? AXI_DMA_MAX_XFER : remaining;
    dma_xfer_chunk(tx_dma + offset, rx_dma + offset, chunk_len);
}

七、踩坑记录(4 个坑)

坑 1:failed to request MM2S channel

现象:dma_request_channel(DMA_MEM_TO_DEV) 返回 NULL。

原因:Xilinx AXI DMA 驱动只注册 DMA_SLAVE 能力,不注册 DMA_MEM_TO_DEV / DMA_DEV_TO_MEM。

解决:改用 DMA_SLAVE mask 申请通道。AXI DMA 通道顺序固定:chan0=MM2S,chan1=S2MM。

坑 2:16384 字节差 1 字节

现象:传输 16384 字节,最后 1 字节(index 16383)丢失,tx=0xFF,rx=0x00。

原因:sg-length-width=14 限制单次传输最大 16383 字节,驱动截断了最后 1 字节。

解决:拆分传输,每块 ≤ 16383 字节。

坑 3:65536+ 字节超时 + Channel has errors 10

现象:chunk 大小用 16383 时,大传输间歇性超时,DMA 通道报错误。

原因:AXI Stream Data FIFO 深度有限,16383 字节 chunk 可能导致 FIFO 溢出(MM2S 写入快于 S2MM 读取,共享 HP0 端口竞争)。

解决:chunk 大小改为 4096 字节(远小于 FIFO 深度,稳定可靠)。

坑 4:chunk 开头 terminate_all 导致 4096 也超时

现象:在每个 chunk 传输前调用 dmaengine_terminate_all() 重置通道,结果 4096 字节也超时。

原因:terminate_all 重置通道后,立即 prep_slave_single + submit 会失败,通道未就绪。

解决:去掉 chunk 开头的 terminate_all,只在出错时才调用清理。

八、测试结果

|
缓冲区大小
|
结果

|
| --- | --- |
|
256 字节
|
✅ PASSED
|
|
1024 字节
|
✅ PASSED
|
|
4096 字节
|
✅ PASSED
|
|
8192 字节
|
✅ PASSED
|
|
16384 字节
|
✅ PASSED(拆分为 4×4096)
|
|
65536 字节
|
✅ PASSED(拆分为 16×4096)
|
|
1,048,576 字节 (1MB)
|
✅ PASSED(拆分为 256×4096)
|
|
4096 字节 × 10 次连续
|
✅ 全部 PASSED
|

九、总结

AXI DMA 回环实验的核心要点:

  1. 时钟域分离:控制口 100MHz,数据口 200MHz,不能搞错

  2. 设备树不手动覆盖:pl.dtsi 自动生成的 axi_dma_0 节点完全正确

  3. 用 DMA_SLAVE 申请通道:Xilinx DMA 驱动只注册此能力

  4. 大传输拆分成 4096 字节 chunk:同时规避 sg-length-width 限制和 FIFO 溢出

  5. 不要在 chunk 开头 terminate_all:会导致通道未就绪

完整驱动源码在 apps/axi_dma_test/ 目录下,可直接编译使用。

相关文章
|
NoSQL Redis Docker
【从零开始】Docker Desktop:听说你小子要玩我
本文旨在帮助读者重新掌握Docker知识,特别是在Windows 10环境下安装与使用Docker Desktop的过程。文章详细介绍了从下载安装到配置镜像源的具体步骤,并通过Redis实例演示了如何拉取镜像和启动容器。
12561 1
【从零开始】Docker Desktop:听说你小子要玩我
|
1月前
|
人工智能 自然语言处理 运维
医疗设备售后智能客服部署模式对比:SaaS还是私有化,医院和器械AI客服怎么选
医疗设备售后 AI 客服选 SaaS 还是私有化,核心取决于数据敏感度、业务系统连接深度、AI 执行权限和企业 IT 运维能力。只承担知识问答和基础咨询的 AI 客服可优先考虑 SaaS;涉及核心业务系统和深度售后流程时,应进一步评估私有化或混合部署。
170 3
|
28天前
|
存储 弹性计算 运维
用于建站的阿里云服务器需要花费多少钱?搭建个人博客成本分析(轻量+ECS)
阿里云个人建站服务器年费低至38元!轻量应用服务器(2核2G/40G/200M峰值带宽/不限流量/支持5个ICP备案)秒杀价38元/年;ECS经济型99元/年(2核2G/3M固定带宽);企业款199元/年(2核4G/5M/80G)。新老用户均可参与,续费同价。
|
27天前
|
人工智能 自然语言处理 调度
减少AI视频抽卡的7种3D预演技术,从人物走位到镜头调度一次讲透
抖知书提出AI视频生成困局源于自然语言的空间歧义,首创“DZS空间预演协议”,以三维坐标替代模糊提示词,通过7种预演技术与五步工作流,将抽卡式生成升级为精准空间导演。(239字)
|
29天前
|
人工智能 自然语言处理 小程序
阿里云万小智3.0版本AI建站产品详细介绍:产品能力提升、适用场景、价格与最新活动参考
万小智3.0是阿里云推出的AI建站升级产品,支持通过自然语言生成包含前端、后端、数据库的完整网站与微信小程序,提供一键部署能力,覆盖企业官网、内容站点、轻量业务应用等场景。产品按体验版、轻量版、标准版、高级版分档订阅,以灵感值计量AI功能调用,新用户可享限时赠2000灵感值、组合套餐低至15元/月等优惠。
|
28天前
|
IDE 开发工具 芯片
【STM32 HAL库】01 - LED闪烁实验(GPIO输出)
本实验基于STM32F407ZGT6(野火霸天虎板),使用STM32CubeMX配置PF6为开漏输出驱动共阳LED,结合HAL库实现1秒周期闪烁。涵盖时钟树设置、GPIO模式选择、SWD调试配置及标准代码结构,是HAL开发入门必做实践。(239字)
|
存储 人工智能 运维
阿里云助力东软集团让数字时代运维系统“灵动”起来
2020年,东软集团与阿里云原生多模数据库Lindorm达成合作,围绕运营商、政务云和汽车等行业的新一代数字信息系统IT运维场景,在海量监控数据上存在存储、分析的痛点,进一步扩展应用智能运维产品RealSight APM和云管理产品SaCa Aclome能力,解决了大数据场景下的IT运维难题。
阿里云助力东软集团让数字时代运维系统“灵动”起来
|
存储 编译器 Linux
小白学习c++的的第一节课
小白学习c++的的第一节课
小白学习c++的的第一节课
|
供应链 监控 物联网
蒙牛:云上养牛记
2019年是蒙牛创业20周年,这家成立于内蒙古的公司是全球增长最快的乳企,2018年蒙牛乳业实现收入689.77亿元,同比增长14.66%。随着业务的迅速发展,蒙牛也和其他国际领先的乳业公司一样,面临着商品销售费用迅速增加、利润摊薄等问题。如何通过改善内部运营效率、降低经销成本,是蒙牛目前面临的重要挑战之一。
856 0
蒙牛:云上养牛记