ZYNQ7000 #3 - Linux环境下在用户空间使用AXI-DMA进行传输

2024-01-17 04:40

本文主要是介绍ZYNQ7000 #3 - Linux环境下在用户空间使用AXI-DMA进行传输,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文使用Petalinux搭建相关linux环境,在vivado中搭建了一个简单的PS -> AXI-DMA -> AXI-FIFO -> AXI-DMA -> PS的测试环路。使用了国外开源的 xilinx_axidma 操作库,完成了用户空间上的AXI-DMA传输。使用库相对来说更加方便容易上手,不需要过多的了解linux设备驱动中如何调用DMA进行传输

目录

0 - 引言

1 - 准备工作

2 - 建立petalinux工程

3 - 配置Linux内核

4 - 避免U-boot从sd卡载入dtb时报错的问题

5 - 设备树的修改

6 - 生成

7 - 生成xilinx_axidma模块

8 - 运行

X - 附录

Ⅰ - 关于设备树中dma通道的device-id的实验


0 - 引言

先不谈如何实现用户空间的零拷贝DMA传输,光是Linux环境下的DMA传输就已经感觉比较棘手,一方面是对Linux了解不够深入,另一方面则是Linux在相关的使用说明方面的确没有比较好的官方支持。

Xilinx提供了一个AXI-DMA的IP核,其可以通过AXI-Lite进行配置,命令其从AXI高性能总线(HP)上直接的对内存数据进行读取存储,这一切在PS使用裸机时感觉是那么的简单,就如同之前在MCU上一般,调用库函数对DMA配置好起始、结束地址、传输大小及相关的即可。但是这一切到linux上则变得狰狞起来。复杂的基于DMA engine 的操作机制使得刚开始上手在zynq上使用linux系统操作AXI-DMA变得不那么简洁明了。

而到了实际应用中,用户往往是在用户空间申请一块内存区域,想要从PL端读一些数据到这个内存区域,或者是从这块内存区域写到PL端,如果直接的使用cpu进行搬运,则会耗费大量的时间,DMA是不可或缺的。

为了“避免”繁杂的linux下dma engine的操作,有的用户想到了是否可以只把AXI-DMA这个IP核的寄存器(挂载在AXI-Lite总线上)通过mmap的方式映射到内存中,然后像之前裸机上一样,对这块内存读写就直接配置AXI-DMA寄存器,完成了对AXI-DMA的配置操作(参考这个:https://forums.xilinx.com/t5/Embedded-Linux/AXI-DMA-with-Zynq-Running-Linux/m-p/522755?advanced=false&collapse_discussion=true&q=linux%20axi%20dma&search_type=thread)

但是,其也不可避免的从内核空间通过copy_to_usr来拷贝数据到用户空间,在大批量的数据时,这是很缓慢的一个过程。

幸好,有一个开源项目xilinx_axidma,实现了从用户空间使用AXI-DMA的零拷贝,并且将其封装为了库,这篇文章主要就是记录如何使用这个库的(https://github.com/bperez77/xilinx_axidma/tree/master)

要使用这个库,有几个需要注意的地方

  • 确保linux内核中,DMA相关项已开启
  • 配置CMA(continues memory area)空间大于25M(视项目需求决定)
  • 修改设备树引入axidma_chrdev,并确保各个dma通道id不重复

1 - 准备工作

  • 下载xilinx_axidma源文件:https://github.com/bperez77/xilinx_axidma/tree/master,并好好看看它的README
  • 已编译过的linux kernel,用于生成model(或者也可以用petalinux的module方式自己将xilinx_axidma添加进去,在petalinux生成时会自动编译生成module)

2 - 建立petalinux工程

建立一个petalinux工程,设置根文件系统从sd卡载入,使用外部linux。这些我之前的博客已经有记录,就这里不赘述了

然后,设置设备树dtb从sd卡里载入。这是为了调试时方便我们修改设备树后直接替换,默认是dtb会打包在uImage中。

Subsystem AUTO Hardware Settings -> Advanced boot...... -> dtb image settings ->选择primary sd

3 - 配置Linux内核

这里面需要确保DMA相关项开启。一般如果vivado工程中含有AXI-DMA 的IP核,在petalinux-config -c kernel的时候会发现基本相关项都已经开启。

这里用一个小技巧,我们在menuconfig中选保存,自己定一个保存名(例如alinx_sgdma_linux_defconfig),保存一下,不要退出,去你petalinux工程项目文件下搜索这个文件名,将其复制出来(我们之后为了编译模块也会用到它),按照github上的要求检查以下项目是否选y了(删除线的不需要检查,这个库是17年写的,但是现在xilinx的linux代码分支已经使用到2018,这些相关配置项已经不在了)

  • CONFIG_CMA=y
  • CONFIG_DMA_CMA=y
  • CONFIG_XILINX_DMAENGINES=y
  • CONFIG_XILINX_AXIDMA=y
  • CONFIG_XILINX_AXIVDMA=y
  • CONFIG_DMA_SHARED_BUFFER=y

记得,在menuconfig中再选保存,将文件名命名回.config,以供petalinux正确生成linux

DMA相关设置完毕后,我们还需要配置CMA

Device Drivers -> Generic Driver Options -> Default contiguous memory area size 的 Size in Mega Bytes修改为25

CMA的修改不要在petalinux-config这个总的对于petalinux工程配置中修改bootargs,这个是自动生成的,手动修改是不会保存的(NO EDIT!)

DTG Settings -> Kernel Bootargs

4 - 避免U-boot从sd卡载入dtb时报错的问题

如果不修改这里,在uboot启动时可能会出现下面的警告

Unknown command 'booti' - try 'help'

解决方法可以参考这篇文章:https://forums.xilinx.com/t5/Embedded-Linux/Zedboard-Unknown-command-booti-PetaLinux/m-p/899108

The problem is the bootm becoming booti. As a workaround, I tried redefining default_bootcmdin a uEnv.txt on my SD card. I can see the variable has updated by running printenv in U-Boot, but the original default still seems to get loaded at startup and the booti error appears. Annoyingly, just doing run default_bootcmd after the initial error results in a normal boot using the default defined in uEnv.txt. I'm guessing there is some kind of env loading order problem. If anyone can let me know why uEnv.txt gets ignored, I would be keen to know!

在petalinux的工作目录下 alinx_sgdma_linux/project-spec/meta-user/recipes-bsp/u-boot/files/platform-top.h 文件末尾加入下面的代码

  1. /* Due to a bug where having u-boot load dtb from SD card causes the boot
  2. * command to default to using booti instead of bootm on Zynq, the defult build
  3. * fails to boot. This boot command override is a temporary workaround.
  4. */
  5. #ifdef CONFIG_BOOTCOMMAND
  6. #undef CONFIG_BOOTCOMMAND
  7. #define CONFIG_BOOTCOMMAND "run uenvboot; run cp_kernel2ram && run cp_dtb2ram && bootm ${netstart} - ${dtbnetstart}"
  8. #endif

5 - 设备树的修改

先运行一下生成pl相关的设备树(这是可选项,只是为了方便修改dtsi时看看pl.dtsi里的节点名)

$ petalinux-config -c device-tree

我们需要修改设备树的主要有两个点:1.加入axidma_chardev 2.修改各个dma通道的device-id不重复。

我这里有两个dma通道(一个发到FIFO,一个从FIFO接回来),我把他们的device-id分别修改为0和1

在project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi中加入

  1. /include/ "system-conf.dtsi"
  2. /{
  3. };
  4. &amba_pl{
  5. axidma_chrdev: axidma_chrdev@0 {
  6. compatible = "xlnx,axidma-chrdev";
  7. dmas = <&axi_dma_0 0 &axi_dma_0 1>;
  8. dma-names = "tx_channel", "rx_channel";
  9. };
  10. };
  11. &axi_dma_0{
  12. dma-channel@40400000 {
  13. xlnx,device-id = <0x0>;
  14. };
  15. dma-channel@40400030 {
  16. xlnx,device-id = <0x1>;
  17. };
  18. };

这里使用的设备树的引用覆盖的方法来修改device-id

6 - 生成

  1. $ petalinux-build -c kernel
  2. $ petalinux-build -c device-tree
  3. $ petalinux-build -c fsbl
  4. $ petalinux-build -c u-boot
  5. $ petalinux-package --boot --fsbl --fpga --u-boot --force

这样在images目录下就会生成我们需要的uImage、system.dtb以及BOOT.BIN,为了确保设备树修改完好,我们这里先反编译一下设备树,生成system.dts,查看里面是否我们要修改的东西都已经修改好了(需要device-tree-compiler)。

在system.dtb文件的目录下运行

dtc -I dtb -O dts -o system.dts system.dtb

打开ststem.dts,我们可以看到已经修改完毕

将 uImage、system.dtb、BOOT.BIN拷到SD卡的FAT分区(从SD卡启动,根文件系统已经部署好在SD卡,参考我前面的文章)待用。

7 - 生成xilinx_axidma模块

如果你是将xilinx_axidma作为了petalinux的自定义方式module生成的话,可以跳过这个步骤。(如何在petalinux中编译linux时直接生成所需的模块或者应用程序参考UG1144,针对xilinx_axidma,可以看这篇文章https://github.com/bperez77/xilinx_axidma/issues/24)

petalinux编译linux时是在一个临时文件夹中编译的,编译完毕之后立刻便清除了生成的文件,这也导致我们无法利用其来编译模块。还记得我们前面在第3步里抢救保存下来的deconfig吗?我们可以到linux源码文件夹下,将alinx_sgdma_linux_defconfig放置在 arch/arm/configs中,运行下面代码生成 .config

make ARCH=arm alinx_sgdma_linux_defconfig

然后 运行下面代码,编译linux,这样我们就能够得到能编译模块的工具了。

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j8

进入到下载的xilinx_axidma源码目录,使用交叉编译链,定位到kernel(已经编译好的)的路径,编译xilinx_axidma的driver

make CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm KBUILD_DIR=已编译好的kernel的路径 driver

再编译xilinx_axidma的例程

make CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm examples

编译完成后,生成的文件都在xilinx_axidma的output文件夹下

将生成的文件拷贝到开发板的根文件系统中。

8 - 运行

启动开发板,进入到我们存放的xilinx_axidma的output文件夹下

这里testsrc.txt和testdst.txt是用来做测试的文本文件,testdst.txt是空的,testsrc.txt是我之前写的流水灯脚本文件,这里拿来测试一下,不需要运行。

X - 附录

Ⅰ - 关于设备树中dma通道的device-id的实验

讲实话,我并没有很看懂xilinx_axidma的README.md中关于 dmas 和 dma-names 的描述

  • dmas - A list of phandles (references to other device tree nodes) of Xilinx AXI DMA or VDMA device tree nodes, followed by either 0 or 1. This refers to the child node inside of the Xilinx AXI DMA/VDMA device tree node, 0 of course being the first child node.

而我在运行样例测试 axidma_transfer 的时候,发现其代码中通过 axidma_get_dma_tx 和 axidma_get_dma_rx 获得的通道id显示的是0和1。这是因为我设备树中设置这两个为0和1导致的还是因为什么,于是我做了一下修改设备树的实验。

将发送通道的id修改为1,接收通道的id修改为0,发现代码中获取正常,传输文件正常

将发送通道的id修改为4,接收通道的id修改为3,代码中获取正常,传输文件失败。

 

AXI DMA File Transfer Info:
Transmit Channel: 4
Receive Channel: 3
Input File Size: 0.00 MiB
Output File Size: 0.00 MiB

axidma_transfer: library/libaxidma.c:193: axidma_callback: Assertion `0 <= siginfo->si_int && siginfo->si_int < axidma_dev.num_channels' failed.

这说明当我们的硬件设计中出现多个dma(例如有vdma到hdmi接口,又有两个dma来进行数据交互)的时候,我们是可以通过id来选择通道的。但是,依照xilinx_axidma的github中README的关于device-id的介绍,说其可以取任何值,只要不重复就可以,但是我设置发送和接收通道分别为4和3时,却提示错误,

参考这个:https://github.com/bperez77/xilinx_axidma/issues/78

I use AXI-DMA(rx&tx) and VDMA(rx&tx).writting device tree as:
axivdma_chrdev: axivdma_chrdev@0 {/* github */
compatible = "xlnx,axidma-chrdev";
dmas = <&axi_vdma_0 0
&axi_vdma_0 1
&axi_dma_0 0
&axi_dma_0 1>;
dma-names = "vdma_tx_channel", "vdma_rx_channel", "dma_tx_channel", "dma_rx_channel";
xlnx,num-fstores = <0x3>;
};
And I have to change "xlnx,device-id" in &axi_vdma_0 and &axi_dma_0.

fix:更加详细的说明https://github.com/bperez77/xilinx_axidma/issues/57

 

 

这篇关于ZYNQ7000 #3 - Linux环境下在用户空间使用AXI-DMA进行传输的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/614896

相关文章

Java使用ANTLR4对Lua脚本语法校验详解

《Java使用ANTLR4对Lua脚本语法校验详解》ANTLR是一个强大的解析器生成器,用于读取、处理、执行或翻译结构化文本或二进制文件,下面就跟随小编一起看看Java如何使用ANTLR4对Lua脚本... 目录什么是ANTLR?第一个例子ANTLR4 的工作流程Lua脚本语法校验准备一个Lua Gramm

Java Optional的使用技巧与最佳实践

《JavaOptional的使用技巧与最佳实践》在Java中,Optional是用于优雅处理null的容器类,其核心目标是显式提醒开发者处理空值场景,避免NullPointerExce... 目录一、Optional 的核心用途二、使用技巧与最佳实践三、常见误区与反模式四、替代方案与扩展五、总结在 Java

Linux内核参数配置与验证详细指南

《Linux内核参数配置与验证详细指南》在Linux系统运维和性能优化中,内核参数(sysctl)的配置至关重要,本文主要来聊聊如何配置与验证这些Linux内核参数,希望对大家有一定的帮助... 目录1. 引言2. 内核参数的作用3. 如何设置内核参数3.1 临时设置(重启失效)3.2 永久设置(重启仍生效

使用Java将DOCX文档解析为Markdown文档的代码实现

《使用Java将DOCX文档解析为Markdown文档的代码实现》在现代文档处理中,Markdown(MD)因其简洁的语法和良好的可读性,逐渐成为开发者、技术写作者和内容创作者的首选格式,然而,许多文... 目录引言1. 工具和库介绍2. 安装依赖库3. 使用Apache POI解析DOCX文档4. 将解析

QT进行CSV文件初始化与读写操作

《QT进行CSV文件初始化与读写操作》这篇文章主要为大家详细介绍了在QT环境中如何进行CSV文件的初始化、写入和读取操作,本文为大家整理了相关的操作的多种方法,希望对大家有所帮助... 目录前言一、CSV文件初始化二、CSV写入三、CSV读取四、QT 逐行读取csv文件五、Qt如何将数据保存成CSV文件前言

Qt中QUndoView控件的具体使用

《Qt中QUndoView控件的具体使用》QUndoView是Qt框架中用于可视化显示QUndoStack内容的控件,本文主要介绍了Qt中QUndoView控件的具体使用,具有一定的参考价值,感兴趣的... 目录引言一、QUndoView 的用途二、工作原理三、 如何与 QUnDOStack 配合使用四、自

C++使用printf语句实现进制转换的示例代码

《C++使用printf语句实现进制转换的示例代码》在C语言中,printf函数可以直接实现部分进制转换功能,通过格式说明符(formatspecifier)快速输出不同进制的数值,下面给大家分享C+... 目录一、printf 原生支持的进制转换1. 十进制、八进制、十六进制转换2. 显示进制前缀3. 指

使用Python构建一个Hexo博客发布工具

《使用Python构建一个Hexo博客发布工具》虽然Hexo的命令行工具非常强大,但对于日常的博客撰写和发布过程,我总觉得缺少一个直观的图形界面来简化操作,下面我们就来看看如何使用Python构建一个... 目录引言Hexo博客系统简介设计需求技术选择代码实现主框架界面设计核心功能实现1. 发布文章2. 加

Python如何自动生成环境依赖包requirements

《Python如何自动生成环境依赖包requirements》:本文主要介绍Python如何自动生成环境依赖包requirements问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑... 目录生成当前 python 环境 安装的所有依赖包1、命令2、常见问题只生成当前 项目 的所有依赖包1、

kali linux 无法登录root的问题及解决方法

《kalilinux无法登录root的问题及解决方法》:本文主要介绍kalilinux无法登录root的问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,... 目录kali linux 无法登录root1、问题描述1.1、本地登录root1.2、ssh远程登录root2、