赞
踩
最近做项目需要用到DMA驱动,网上资料大部分是关于ZYNQ裸机的,Xilinx关于Linux系统DMA驱动的资料少之又少,所以踩了不少坑,所以想着记录一下。这里我使用的是github上的开源项目 xilinx_axidma,项目地址:https://github.com/bperez77/xilinx_axidma
该项目使用兼容ZYNQ7000系列的芯片,测试例程可以使用DMA环回测试,但是由于我项目使用的ZYNQ MPSOC系列的3eg芯片,并且主要实现PS端通过DMA读取PL端数据,只需要使用DMA的接收通道即可,因此需要对源码做一些修改。
测试可以直接使用正点原子DMA环回测试例程Block Design,这里根据项目需求我使用的是自己重新搭建的测试工程;
该Block Design 主要实现dds向stream fifo写入自加的4097个自加1的32位数据,DMA将fifo中的数据写入到ZYNQMP 的PS端DDR供用户使用,DDS需要一个上升沿信号触发,这里使用gpio控制,模拟PL端数据采集并通过DMA传入PS端的过程;
建议使能packet mode,确保每次传输数据的完整性
使用DMA的简单模式;
Width of Buffer Length Register :DMA的缓存区大小,必须大于你每次DMA传输的大小,若使用回环测试建议设置为26(64M);
回环测试需要使能两个通道,单向传输(我这里只用到了写通道)根据情况使能单个通道即可;
Max Burst Size: 最大突发长度,我理解的是传输多少个数据把 last信号拉高一次,这个大小会影响DMA传输的速率,我没有实际测试过,这里我设置的是64,默认是16;
Allow Unaligned Transfers: 是否允许字节非对齐传输,不勾选PS端读数据必须以4字节为一个单位,这里我们数据是32位,所以勾不勾没有影响;若数据位8位,建议勾选,否则读数据时必须四字节对齐,实际我们只有低8位有数据,高24位自动补零了,不勾浪费资源;
`timescale 1ns / 1ps // // Company: // Engineer: // // Create Date: 2024/02/27 16:35:14 // Design Name: // Module Name: data_gen // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // // module data_gen #( parameter TRANS_NUM = 32'd4096 //1514*1024 ) ( input clk , input i_rstn , input trans_start, input M_AXIS_tready , output [31 : 0] M_AXIS_tdata , output M_AXIS_tvalid , output M_AXIS_tlast , output [3 : 0] M_AXIS_tkeep ); reg [31 : 0] r_M_AXIS_tdata ; reg r_M_AXIS_tvalid ; reg r_M_AXIS_tlast ; reg [3 : 0] r_M_AXIS_tkeep ; reg [1 : 0] r_current_state ; reg [1 : 0] r_next_state; reg trans_start_0, trans_start_1; wire pos_trans_start; assign pos_trans_start = trans_start_0 & (~trans_start_1); always @(posedge clk) begin if(!i_rstn) begin trans_start_0 <= 1'd0; trans_start_1 <= 1'd0; end else begin trans_start_0 <= trans_start; trans_start_1 <= trans_start_0; end end localparam IDLE = 2'd0; localparam TRAN = 2'd1; localparam LAST = 2'd2; always @(posedge clk ) begin if(!i_rstn) r_current_state <= IDLE; else r_current_state <= r_next_state; end always @(*) begin case(r_current_state) IDLE : r_next_state = (pos_trans_start && M_AXIS_tready) ? TRAN : IDLE; TRAN : r_next_state = (r_M_AXIS_tdata == TRANS_NUM) ? LAST : TRAN; LAST : r_next_state = M_AXIS_tready ? IDLE : LAST; default : r_next_state = IDLE; endcase end always @(posedge clk ) begin case(r_current_state) IDLE : begin r_M_AXIS_tdata <= 32'd0; r_M_AXIS_tvalid <= 1'd0; r_M_AXIS_tlast <= 1'd0; r_M_AXIS_tkeep <= 4'b1111; end TRAN : begin r_M_AXIS_tvalid <= 1'd1; if(M_AXIS_tready)begin r_M_AXIS_tdata <= r_M_AXIS_tdata + 32'd1; if(r_M_AXIS_tdata == TRANS_NUM) r_M_AXIS_tlast <= 1'd1; else r_M_AXIS_tlast <= 1'd0; end else r_M_AXIS_tdata <= r_M_AXIS_tdata; end LAST : begin if(!M_AXIS_tready)begin r_M_AXIS_tvalid <= 1'd1; r_M_AXIS_tlast <= 1'd1; r_M_AXIS_tdata <= r_M_AXIS_tdata; end else begin r_M_AXIS_tvalid <= 1'd0; r_M_AXIS_tlast <= 1'd0; r_M_AXIS_tdata <= 32'd0; end end default : begin r_M_AXIS_tdata <= 32'd0; r_M_AXIS_tvalid <= 1'd0; r_M_AXIS_tlast <= 1'd0; r_M_AXIS_tkeep <= 4'b1111; end endcase end assign M_AXIS_tdata = r_M_AXIS_tdata ; assign M_AXIS_tvalid = r_M_AXIS_tvalid ; assign M_AXIS_tlast = r_M_AXIS_tlast ; assign M_AXIS_tkeep = r_M_AXIS_tkeep ; endmodule
其余步骤这里不再赘述,主要提一下设备树需要修改的地方,
device-id 设备树自动生成时默认都是0,这里需要将两个通道设置不同的id加以区分
添加节点
&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";
enable-gpios = <&axi_gpio_0 0 0 GPIO_ACTIVE_HIGH>;
};
};
其中 属性 enable-gpios 对应Block Design添加的gpio,(回环测试不需要添加)
其余步骤按正常的制卡流程走即可
源码下载地址:https://github.com/bperez77/xilinx_axidma
驱动源码使用的是4.x版本的内核源码,我这里使用的是5.4版本,因此需要对驱动源码做一些修改;
参考:https://github.com/bperez77/xilinx_axidma/pull/139/files
根据源码readme操作不修改也可,本人觉得每次都要指定内核源码路径过于麻烦,所以直接在Makefile指定了
KBUILD_DIR = 你的内核源码路径
若添加了GPIO,则需要再驱动源码中添加对GPIO的控制,或另写一个GPIO驱动,这里为了方便起见,我直接在xilinx_axidma驱动源码中添加了GPIO控制:
添加
#define AXIDMA_GPIO_CTRL _IO(AXIDMA_IOCTL_MAGIC, 11)
修改
#define AXIDMA_NUM_IOCTLS 10 -> 12
添加定义
struct gpio_desc *fifo_enable_gpio;
在static long axidma_ioctl(struct file *file, unsigned int cmd, unsigned long arg)函数中增加io_ctrl
case AXIDMA_GPIO_CTRL:
rc = copy_from_user(&flag, arg_ptr, sizeof(flag));
if(rc < 0){
printk("falied cpoy data from user\r\n");
}
gpiod_set_value_cansleep(fifo_enable_gpio, flag);
break;
52行添加
fifo_enable_gpio = devm_gpiod_get_optional(&pdev->dev, "enable", GPIOD_OUT_LOW);
添加gpio头文件和变量声明
#include <linux/gpio.h>
extern struct gpio_desc *fifo_enable_gpio; //控制DDS往FIFO写数据的触发信号,上升沿触发
修改axidma_oneway_transfer函数,在460行添加gpio控制产生上升沿
int gpio = 0;
ioctl(dev->fd, AXIDMA_GPIO_CTRL, &gpio);
gpio = 1;
ioctl(dev->fd, AXIDMA_GPIO_CTRL, &gpio);
加载环境变量设置交叉编译工具后,直接在顶层文件夹make,生成的可执行文件在outputs文件夹
axidma_benchmark:读写测速文件,环回可用
axidma_display_imag:vdma传输图像数据,没测过
axidma_transfer:文件传输,输入文件大小必须大于4K,否则输入文件没有数据(这应该驱动的一个bug)
axi_dma.ko:axi_dma的驱动文件,直接insmod 即可使用
libaxidma.so:axidma的动态链接库,上述三个可执行文件都依赖此文件,执行时必须放置同一路径或者放置系统的动态链接库 /usr/lib
启动系统,将output下所有文件拷贝到系统;
若dma成功配置,系统启动日志会打印DMA相关信息:
加载驱动,成功加载日志会打印一下信息:
注:这里两个测试都是基于DMA环回的工程才能测试成功,1.txt的文件大小必须大于4KB,传输成功2.txt的内容会和1.txt一致;
根据项目需求,编写了自己的测试代码,主要根据axidma_transfer.c文件修改而来,这里我们读取的是DDS往FIFO写的数据,4097个由1累加的32位数据,这里我们只打印出前两个和后两个;
该过程主要记录了移植过程比较重要的几个步骤,按照步骤来应该都能成功,时间关系移植过程遇到的很多问题和技术细节并没有记录,所以就没有写进博客,后面有时间再详细整理。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。