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进行传输的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:https://blog.csdn.net/weixin_42278284/article/details/102467652
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/614896

相关文章

Python 字典 (Dictionary)使用详解

《Python字典(Dictionary)使用详解》字典是python中最重要,最常用的数据结构之一,它提供了高效的键值对存储和查找能力,:本文主要介绍Python字典(Dictionary)... 目录字典1.基本特性2.创建字典3.访问元素4.修改字典5.删除元素6.字典遍历7.字典的高级特性默认字典

MySQL进行数据库审计的详细步骤和示例代码

《MySQL进行数据库审计的详细步骤和示例代码》数据库审计通过触发器、内置功能及第三方工具记录和监控数据库活动,确保安全、完整与合规,Java代码实现自动化日志记录,整合分析系统提升监控效率,本文给大... 目录一、数据库审计的基本概念二、使用触发器进行数据库审计1. 创建审计表2. 创建触发器三、Java

使用Python构建一个高效的日志处理系统

《使用Python构建一个高效的日志处理系统》这篇文章主要为大家详细讲解了如何使用Python开发一个专业的日志分析工具,能够自动化处理、分析和可视化各类日志文件,大幅提升运维效率,需要的可以了解下... 目录环境准备工具功能概述完整代码实现代码深度解析1. 类设计与初始化2. 日志解析核心逻辑3. 文件处

一文详解如何使用Java获取PDF页面信息

《一文详解如何使用Java获取PDF页面信息》了解PDF页面属性是我们在处理文档、内容提取、打印设置或页面重组等任务时不可或缺的一环,下面我们就来看看如何使用Java语言获取这些信息吧... 目录引言一、安装和引入PDF处理库引入依赖二、获取 PDF 页数三、获取页面尺寸(宽高)四、获取页面旋转角度五、判断

Windows环境下解决Matplotlib中文字体显示问题的详细教程

《Windows环境下解决Matplotlib中文字体显示问题的详细教程》本文详细介绍了在Windows下解决Matplotlib中文显示问题的方法,包括安装字体、更新缓存、配置文件设置及编码調整,并... 目录引言问题分析解决方案详解1. 检查系统已安装字体2. 手动添加中文字体(以SimHei为例)步骤

C++中assign函数的使用

《C++中assign函数的使用》在C++标准模板库中,std::list等容器都提供了assign成员函数,它比操作符更灵活,支持多种初始化方式,下面就来介绍一下assign的用法,具有一定的参考价... 目录​1.assign的基本功能​​语法​2. 具体用法示例​​​(1) 填充n个相同值​​(2)

MySQL深分页进行性能优化的常见方法

《MySQL深分页进行性能优化的常见方法》在Web应用中,分页查询是数据库操作中的常见需求,然而,在面对大型数据集时,深分页(deeppagination)却成为了性能优化的一个挑战,在本文中,我们将... 目录引言:深分页,真的只是“翻页慢”那么简单吗?一、背景介绍二、深分页的性能问题三、业务场景分析四、

Spring StateMachine实现状态机使用示例详解

《SpringStateMachine实现状态机使用示例详解》本文介绍SpringStateMachine实现状态机的步骤,包括依赖导入、枚举定义、状态转移规则配置、上下文管理及服务调用示例,重点解... 目录什么是状态机使用示例什么是状态机状态机是计算机科学中的​​核心建模工具​​,用于描述对象在其生命

Linux进程CPU绑定优化与实践过程

《Linux进程CPU绑定优化与实践过程》Linux支持进程绑定至特定CPU核心,通过sched_setaffinity系统调用和taskset工具实现,优化缓存效率与上下文切换,提升多核计算性能,适... 目录1. 多核处理器及并行计算概念1.1 多核处理器架构概述1.2 并行计算的含义及重要性1.3 并

Java JDK1.8 安装和环境配置教程详解

《JavaJDK1.8安装和环境配置教程详解》文章简要介绍了JDK1.8的安装流程,包括官网下载对应系统版本、安装时选择非系统盘路径、配置JAVA_HOME、CLASSPATH和Path环境变量,... 目录1.下载JDK2.安装JDK3.配置环境变量4.检验JDK官网下载地址:Java Downloads