ZYNQ系列(十二)linux的DMA使用

本文档详细记录了在ZYNQ平台的Linux系统中,如何使用DMA进行数据传输,特别是如何利用xilinx_axidma库实现用户空间的零拷贝DMA操作。首先介绍了Linux DMA的复杂性,然后逐步讲解了从建立PetaLinux工程、配置内核和CMA、修改设备树,到编译Github上的DMA代码、加载驱动模块以及运行测试程序的全过程。过程中遇到了CMA空间不足的问题,并给出了解决方案。最后,列举了一些参考博客链接,供读者进一步学习。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ZYNQ系列(十二)linux的DMA使用




前言

linux的DMA对于新手而言一直是一个噩梦,先不谈如何实现用户空间的零拷贝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的配置操作,但是,其也不可避免的从内核空间通过copy_to_usr来拷贝数据到用户空间,在大批量的数据时,这是很缓慢的一个过程。

幸好,有一个开源项目xilinx_axidma,实现了从用户空间使用AXI-DMA的零拷贝,并且将其封装为了库,这篇文章主要就是记录如何使用这个库的。

开发环境

开发板:黑金的zynq7010。
petalinux版本:2017.4。
vivado版本:2017.4。

准备工作

  1. 下载dma的源码:https://github.com/bperez77/xilinx_axidma/tree/master
  2. 黑金的dma回环例程的*.HDF文件或者自己根据其他博客搭建dma硬件环境。

petalinux工程建立

建立工程

根据第一章的内容,建立petalinux工程,hdf文件需要选择准备工作中第二条产生的hdf(有需要的直接评论留言给我)。

配置内核

1. 配置DMA

我使用的是黑金自带的内核,我检查DMA的项都已开启,就没有动。

如果使用自己编译的内核,可能需要查看几个CONFIG是否开启。(别的博客都这么说)

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

根据petalinux的版本不同,好像有几个配置也不需要了,自己网上看吧,我用的黑金的内核,直接Petalinux-config -c kernel,查看DMA的项都开启了,就没搭理。

2. 配置CMA

CMA是什么我也不知道,具体为什么配置CMA我也不知道,我只看到网上有人说没有配CMA,运行的时候会出错。
根据其他博主的文章现在知道了。

  1. 报错如下为CMA空间不够导致的,重新设置CMA大小可解决。
    在这里插入图片描述

  2. 原因:因为transfer_file函数中对发送和接收都分配了cma空间,所以发送和接收的文件大小不能超过设置的cma空间的一半。

  3. 后面根据别人的博客出一个错误的说明。

  4. 配置方式
    在这里插入图片描述

修改设备树

打开设备树文件

$  vi project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi 

增加dma相关内容,这里使用的设备树的引用覆盖的方法来修改device-id。也有直接去pl.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>;
	};
};

生成BOOT.BIN烧录

不做过多解释。

编译Github的DMA代码

  1. 代码路径截图如下
    在这里插入图片描述

  2. 修改config_template.mk
    将config_template.mk 名字改为confi.mk,里面内容修改如下

     第21行改为    CROSS_COMPILE = arm-linux-gnueabihf-
     第25行改为    ARCH = arm
     第35行改为    KBUILD_DIR = /home/workzc/alinx/50-dma_test/dma_test/build/tmp/work/plnx_arm-xilinx-linux-gnueabi/linux-xlnx/4.9-xilinx-v2017.4+git999-r0/linux-xlnx-4.9-xilinx-v2017.4+git999
     第41行改为    OUTPUT_DIR = outputs
    

    第35行 是petalinux-build -c kernel以后才产生的,路径可能和这个不一样,自己查找。

  3. 将include中的两个.h文件移动到driver目录中

  4. 编译驱动程序

     $ make driver
    
  5. 编译应用程序

     $ make examples
    
  6. 查看outputs文件夹,有以下文件说明安装成功。
    在这里插入图片描述

  7. 该步骤可以使用petalinux module那种方式将ko文件弄到文件系统,或者直接编译进内核,方法网上看吧。

运行

接下来就是挂载,运行了。

  1. 使用insmod加载模块,出现以下信息说明加载成功。
    在这里插入图片描述

  2. 运行axidma_benchmark,查看dma速率。
    在这里插入图片描述

  3. 运行axidma_transfer,回环测试。

     创建两个txt文件,1.txt和2.txt。
     在1.txt文件中胡乱输入点内容。
     运行axidma_transfer,2.txt中会出现和1.txt一样的内容。
    

在这里插入图片描述
在这里插入图片描述

收工

笔者也是小白一枚,都是按照网上别人博客来的,下面将我看到几个不错的博客贴出来,供大家欣赏。

这几篇博客都是根据 github上的那个dma代码来做解释的。

1. ZYNQ Linux应用层 利用 AXI DMA 进行数据传输
该文章有错误说明,第二个错误我就碰上了,我是用自己创建的vivado工程产生的hdf,结果不中用,出了第二个问题,还没仔细分析原因。改为黑金的DMA回环的hdf好用了。

我关于config.mk的修改来源此文章,就是他没说KBUILD_DIR = /home/osrc/Projects/zynq-mz7100fa/osrc-lab-linux-4.19/sources/kernel/ 这东西的来源是哪让我费了点功夫。

2. ZYNQ #3 - Linux环境下在用户空间使用AXI-DMA进行传输
该文章还有个后续#4 讲的是PS使用直接采集PL外接的ADC数据,相当不错。

3. ZYNQ跑系统 系列(三) AXI-DMA的linux下运行
他们都可以改了设备树,直接弄到sd卡里启动就行,我不会啊。我都是改了设备树在重新编译,重新烧录,相当麻烦,会的大佬留言教教小弟。这篇开始cma没有配置,然后改了设备树,扔SD卡里就直接用了,好神奇。

4. Zynq7000学习 1.如何在Linux平台上运行DMA模块
这篇是使用petalinux module 方式将ko编译文件系统了

5. zynq操作系统:Linux驱动开发AXIDMA补充篇 多路DMA
多路DMA的使用。

出错集锦

<template> <div class="box bgc3 sku-sale-rank"> <!-- TODO: 分辨大怎么展示问UI --> <div class="header"> <div class="title"> 商品热榜<span class="sub-title">{{ start }} ~ {{ end }}</span> </div> </div> <div class="body"> <el-row v-for="(item, index) in skuSaleRank" :key="index"> <el-col :span="5"> <div :class="'top top' + (index + 1)"> {{ index + 1 }} </div> </el-col> <el-col :span="13"> <div class="sku-name" :title="item.skuName"> {{ item.skuName }} </div> </el-col> <el-col :span="6"> <div class="count">{{ item.count }}单</div> </el-col> </el-row> </div> </div> </template> <script setup> import dayjs from "dayjs"; // 定义变量 const skuSaleRank = ref([ { skuId: "0", skuName: "可口可乐", count: 9, amount: 0, }, { skuId: "0", skuName: "营养快线", count: 8, amount: 0, }, { skuId: "0", skuName: "阿萨姆奶茶", count: 6, amount: 0, }, { skuId: "0", skuName: "星巴克摩卡味", count: 2, amount: 0, }, { skuId: "0", skuName: "美汁源果粒橙", count: 1, amount: 0, }, { skuId: "0", skuName: "怡宝", count: 1, amount: 0, }, ]); const start = dayjs().startOf("month").format("YYYY.MM.DD"); const end = dayjs().endOf("day").format("YYYY.MM.DD"); </script> <style lang="scss" scoped> @import "@/assets/styles/variables.module.scss"; .sku-sale-rank { display: flex; flex-direction: column; height: calc((100vh - 120px) * 0.6); min-height: 538px; background: #ffffff; border-radius: 20px; .body { flex: 1; display: flex; flex-direction: column; justify-content: space-between; margin-top: 20px; .top { display: inline-block; width: 16px; height: 20px; margin-left: 10px; background: url("@/assets/user-task-stats/top.png"); text-align: center; font-size: 12px; font-weight: normal; color: #e9b499; line-height: 14px; } .top1 { width: 21px; height: 20px; background: url("@/assets/user-task-stats/top1.png"); color: #8e5900; } .top2 { width: 21px; height: 20px; background: url("@/assets/user-task-stats/top2.png"); color: #494949; } .top3 { width: 21px; height: 20px; background: url("@/assets/user-task-stats/top3.png"); color: #cf6d3d; } .sku-name { height: 20px; font-size: 14px; font-weight: 500; color: $--color-text-primary; line-height: 20px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .count { height: 20px; font-size: 14px; font-family: PingFangSC-Regular, PingFang SC; font-weight: 400; color: #737589; line-height: 20px; text-align: right; } } } </style> 基于后端接口export function getTopSelling() { return request({ url: "/manage/order/top-selling", method: "get", }); }改造前端代码,使其调用接口动态获取数据而不是写死
03-30
<think>嗯,用户给了一个Vue组件的代码,需要根据后端接口改造前端,动态获取数据而不是写死的。首先,我得先理解现有的代码结构。看起来这是一个显示商品销售排名的组件,模板部分用了Element UI的el-row和el-col来布局,数据是写死在ref里的。目标是要调用getTopSelling接口获取真实数据。 首先,我需要确定如何在Vue的setup函数中发起请求。用户已经导入了dayjs,所以日期处理应该没问题。接下来,需要引入API方法,比如从某个api模块导入getTopSelling。然后,使用onMounted钩子来在组件挂载时调用接口。 然后,数据方面,原本的skuSaleRank是ref里的静态数据,现在需要替换成接口返回的数据。因此,应该将skuSaleRank初始化为空数组,然后在接口成功返回后更新它。可能需要处理加载状态和错误情况,但用户没提到,可能暂时不需要,但最好加上加载中和错误提示会更友好。 接下来,接口返回的数据结构是否和现有结构匹配?比如,后端返回的数据是否有skuId、skuName、count这些字段?如果不同,需要进行映射。假设接口返回的数据结构一致,那直接赋值即可;否则需要调整字段名。 另外,日期部分,start是当月第一天,end是当天,这个逻辑应该保留,因为用户可能希望显示当前时间段的数据。但需要确认后端是否需要这些日期作为参数,或者接口是否已经处理了时间范围。根据接口定义,getTopSelling是GET请求,可能不需要参数,但可能需要传递start和end。需要检查接口文档,但用户提供的接口函数没有参数,所以可能不需要。 然后,模板部分,v-for循环skuSaleRank,显示排名、商品名和订单数。如果接口返回的数据正确,这部分应该不需要修改,只需要确保数据正确加载即可。 样式方面,可能不需要改动,但需要注意如果数据为空时的展示情况。比如,如果接口没有返回数据,可能需要显示“暂无数据”的提示,提升用户体验。 另外,需要考虑异步加载时的状态,比如在数据加载前显示加载中的提示,避免页面空白。可以使用v-if或v-show来控制显示。 还有错误处理,比如网络请求失败时,应该捕获错误并提示用户,但用户可能暂时不需要,但最好加上。 综上,步骤大概是:引入API,使用onMounted调用接口,处理响应数据,替换静态数据,处理加载和错误状态,确保模板正确渲染。同时检查字段映射是否正确,是否需要参数传递时间范围。</think>我将分步骤为您讲解如何改造这个组件为动态获取数据模式: $$ 改造步骤 = [接口调用, 数据绑定, 状态处理, 错误处理]^T $$ 一、接口调用层改造 1. 添加接口引入 ```javascript import { getTopSelling } from '@/api/order' // 假设接口文件路径 ``` 2. 创建数据获取逻辑 ```javascript const loading = ref(false) const fetchData = async () => { try { loading.value = true const { data } = await getTopSelling() skuSaleRank.value = data // 假设接口返回数组结构一致 } catch (e) { console.error('获取热销数据失败:', e) // 可添加ElMessage错误提示 } finally { loading.value = false } } ``` 二、生命周期处理 ```javascript onMounted(() => { fetchData() }) ``` 三、模板优化(添加加载状态) ```html <template> <div class="box ..."> <!-- 原有header保持不变 --> <div class="body"> <template v-if="loading"> <el-skeleton :rows="6" animated /> </template> <template v-else> <el-row v-for="(item, index) in skuSaleRank" :key="item.skuId"> <!-- 原有内容 --> </el-row> </template> </div> </div> </template> ``` 四、数据映射建议(若接口字段不一致) ```javascript // 如果接口返回字段不同,例如返回salesCount而不是count: skuSaleRank.value = data.map(item => ({ skuId: item.productId, skuName: item.name, count: item.salesCount, amount: item.totalAmount })) ``` 五、日期参数传递(根据接口需求) ```javascript // 如果接口需要时间参数: const params = { start: dayjs().startOf('month').format('YYYY-MM-DD'), end: dayjs().endOf('day').format('YYYY-MM-DD') } const { data } = await getTopSelling(params) ``` 六、最终代码结构示意图: $$ \begin{array}{ccc} \text{组件初始化} & \rightarrow & \text{onMounted} \\ & \downarrow & \\ \text{API调用} & \leftrightarrow & \text{数据处理} \\ & \downarrow & \\ \text{UI渲染} & \leftarrow & \text{状态更新} \\ \end{array} $$ 改造要点: 1. 使用`ref`保持响应式数据 2. 添加加载状态提升用户体验 3. 需要验证接口返回数据结构 4. 建议添加错误边界处理 5. 如果时间范围需要作为参数,需与后端确认格式 请根据实际接口返回数据结构调整字段映射,推荐使用Postman先验证接口返回格式。如果遇到跨域问题需要配置代理,可在vue.config.js中添加代理设置。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胖茄子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值