ZYNQ #3 - Linux环境下在用户空间使用AXI-DMA进行传输
本文使用Petalinux搭建相关linux环境,在vivado中搭建了一个简单的PS -> AXI-DMA -> AXI-FIFO -> AXI-DMA -> PS的测试环路。使用了国外开源的 xilinx_axidma 操作库,完成了用户空间上的AXI-DMA传输。使用库相对来说更加方便容易上手,不需要过多的了解linux设备驱动中如何调用DMA进行传输目录...
本文使用Petalinux搭建相关linux环境,在vivado中搭建了一个简单的PS -> AXI-DMA -> AXI-FIFO -> AXI-DMA -> PS的测试环路。使用了国外开源的 xilinx_axidma 操作库,完成了用户空间上的AXI-DMA传输。使用库相对来说更加方便容易上手,不需要过多的了解linux设备驱动中如何调用DMA进行传输
目录
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=yCONFIG_XILINX_AXIDMA=yCONFIG_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 文件末尾加入下面的代码
/* Due to a bug where having u-boot load dtb from SD card causes the boot
* command to default to using booti instead of bootm on Zynq, the defult build
* fails to boot. This boot command override is a temporary workaround.
*/
#ifdef CONFIG_BOOTCOMMAND
#undef CONFIG_BOOTCOMMAND
#define CONFIG_BOOTCOMMAND "run uenvboot; run cp_kernel2ram && run cp_dtb2ram && bootm ${netstart} - ${dtbnetstart}"
#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中加入
/include/ "system-conf.dtsi"
/{
};
&amba_pl{
axidma_chrdev: axidma_chrdev@0 {
compatible = "xlnx,axidma-chrdev";
dmas = <&axi_dma_0 0 &axi_dma_0 1>;
dma-names = "tx_channel", "rx_channel";
};
};
&axi_dma_0{
dma-channel@40400000 {
xlnx,device-id = <0x0>;
};
dma-channel@40400030 {
xlnx,device-id = <0x1>;
};
};
这里使用的设备树的引用覆盖的方法来修改device-id
注意:直接在网页复制上面的代码可能会存在符号问题,强烈建议手动输入,或者复制之后确认所有的缩进均是使用TAB键键入的制表符
6 - 生成
$ petalinux-build -c kernel
$ petalinux-build -c device-tree
$ petalinux-build -c fsbl
$ petalinux-build -c u-boot
$ 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
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)