ZYNQMPSOC DMA Linux 驱动移植,实现PL到PS的数据传输

ZYNQMPSOC DMA Linux 驱动移植,实现PL到PS的数据传输

前言

最近做项目需要用到DMA驱动,网上资料大部分是关于ZYNQ裸机的,Xilinx关于Linux系统DMA驱动的资料少之又少,所以踩了不少坑,所以想着记录一下。这里我使用的是github上的开源项目 xilinx_axidma,项目地址:https://github.com/bperez77/xilinx_axidma
该项目使用兼容ZYNQ7000系列的芯片,测试例程可以使用DMA环回测试,但是由于我项目使用的ZYNQ MPSOC系列的3eg芯片,并且主要实现PS端通过DMA读取PL端数据,只需要使用DMA的接收通道即可,因此需要对源码做一些修改。

1.Block Design

测试可以直接使用正点原子DMA环回测试例程Block Design,这里根据项目需求我使用的是自己重新搭建的测试工程;

在这里插入图片描述

该Block Design 主要实现dds向stream fifo写入自加的4097个自加1的32位数据,DMA将fifo中的数据写入到ZYNQMP 的PS端DDR供用户使用,DDS需要一个上升沿信号触发,这里使用gpio控制,模拟PL端数据采集并通过DMA传入PS端的过程;

1.1 FIFO配置:

在这里插入图片描述

建议使能packet mode,确保每次传输数据的完整性

1.2 DMA配置:

在这里插入图片描述

使用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位自动补零了,不勾浪费资源;

1.3 DDS模块源码:


`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


2 制作linux启动镜像

其余步骤这里不再赘述,主要提一下设备树需要修改的地方,

2.1 pl.dtsi

在这里插入图片描述

device-id 设备树自动生成时默认都是0,这里需要将两个通道设置不同的id加以区分

2.2 system-user.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";
		enable-gpios = <&axi_gpio_0 0 0 GPIO_ACTIVE_HIGH>;
    };
};

其中 属性 enable-gpios 对应Block Design添加的gpio,(回环测试不需要添加)

其余步骤按正常的制卡流程走即可

3 编译 xilinx_axidma驱动

源码下载地址:https://github.com/bperez77/xilinx_axidma

3.1 修改源码

驱动源码使用的是4.x版本的内核源码,我这里使用的是5.4版本,因此需要对驱动源码做一些修改;
参考:https://github.com/bperez77/xilinx_axidma/pull/139/files

3.1.1 修改顶层Makefile

根据源码readme操作不修改也可,本人觉得每次都要指定内核源码路径过于麻烦,所以直接在Makefile指定了

KBUILD_DIR = 你的内核源码路径

若添加了GPIO,则需要再驱动源码中添加对GPIO的控制,或另写一个GPIO驱动,这里为了方便起见,我直接在xilinx_axidma驱动源码中添加了GPIO控制:

3.1.2 axidma_ioctl.h

添加

#define AXIDMA_GPIO_CTRL        _IO(AXIDMA_IOCTL_MAGIC, 11)

修改

#define AXIDMA_NUM_IOCTLS               10 -> 12
3.1.3 axidma_chrdev.c

添加定义

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;
3.1.4 axidma.c

52行添加

fifo_enable_gpio = devm_gpiod_get_optional(&pdev->dev, "enable", GPIOD_OUT_LOW);
3.1.5 axidma.h

添加gpio头文件和变量声明

#include <linux/gpio.h>

extern struct gpio_desc *fifo_enable_gpio; //控制DDS往FIFO写数据的触发信号,上升沿触发
3.1.6 libaxidma.c

修改axidma_oneway_transfer函数,在460行添加gpio控制产生上升沿

    int gpio = 0;
    ioctl(dev->fd, AXIDMA_GPIO_CTRL, &gpio);
    gpio = 1;
    ioctl(dev->fd, AXIDMA_GPIO_CTRL, &gpio);

3.2 编译驱动

加载环境变量设置交叉编译工具后,直接在顶层文件夹make,生成的可执行文件在outputs文件夹

axidma_benchmark:读写测速文件,环回可用
axidma_display_imag:vdma传输图像数据,没测过
axidma_transfer:文件传输,输入文件大小必须大于4K,否则输入文件没有数据(这应该驱动的一个bug)
axi_dma.ko:axi_dma的驱动文件,直接insmod 即可使用
libaxidma.so:axidma的动态链接库,上述三个可执行文件都依赖此文件,执行时必须放置同一路径或者放置系统的动态链接库 /usr/lib

3.3 驱动测试

启动系统,将output下所有文件拷贝到系统;
若dma成功配置,系统启动日志会打印DMA相关信息:

在这里插入图片描述

加载驱动,成功加载日志会打印一下信息:

在这里插入图片描述

3.3.1 读写速率测试

在这里插入图片描述

3.3.2 文件传输测试

在这里插入图片描述

注:这里两个测试都是基于DMA环回的工程才能测试成功,1.txt的文件大小必须大于4KB,传输成功2.txt的内容会和1.txt一致;

3.3.3 自定义App测试

根据项目需求,编写了自己的测试代码,主要根据axidma_transfer.c文件修改而来,这里我们读取的是DDS往FIFO写的数据,4097个由1累加的32位数据,这里我们只打印出前两个和后两个;
在这里插入图片描述

总结

该过程主要记录了移植过程比较重要的几个步骤,按照步骤来应该都能成功,时间关系移植过程遇到的很多问题和技术细节并没有记录,所以就没有写进博客,后面有时间再详细整理。

  • 23
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值