ZYNQ DMA Linux驱动实验

ZYNQ DMA Linux驱动实验

简介

在PL中调用AXI-DMA向PS的内存中写入数据,数据源为自己造的一个递增数,在PS中可通过驱动控制DMA的传输。

搭建Vivado工程

主要调用了自定义的数据发生器模块、FIFO转AXI-Stream模块、AXI-DMA模块和ZYNQ PS模块,数据流向为数据发生器->FIFO->FIFO转AXI-Stream->AXI-DMA->PS。PS需要配置FIFO转AXI-Stream的数据包长度和启动发送2个寄存器。需要使用到的寄存器如下:

PL复位寄存器

PL内所有模块的复位信号采用PS输出的FCLK_RESET0来控制,需要使用到的寄存器如下:

物理地址 寄存器 读写 默认值 说明
0xF8000008 SLCR_UNLOCK wo 0x00000000 SCLR解锁寄存器,向该寄存器写入0xDF0D解锁SCLR寄存器的写保护
0xF800000C SLCR_LOCKSTA ro 0x00000001 SLCR锁定状态寄存器,读为0代表SLCR寄存器为可写状态,读为1代表SLCR为写保护状态
0x00000240 FPGA_RST_CTRL rw 0x01F33F0F 低4位分别为FCLK_RESET0~3的复位控制,写1为复位,写0为解复位

FIFO转AXI-Stream模块寄存器定义

基地址:0x60000000

偏移地址 寄存器 读写 默认值 说明
0x0 数据包长度寄存器 R/W 0x00000000 每一个AXI-Stream数据包的长度
0x8 数据包数量寄存器 R/W 0x00000000 每一次启动发送后产生的AXI-Stream数据包数量
0x4 启动寄存器 R/W 0x00000000 寄存器bit0从0变为1后启动一次发送

AXI-DMA寄存器定义

见Xilinx IP手册PG021

image-20240824222420763

编写Linux驱动

总的ADC采集数据这个功能的驱动可以分为2个部分来完成,一个是AXI-DMA的驱动,通过AXI-DMA驱动可以向Linux注册一个DMA设备。另一个才是我们想实现的ADC数据采集驱动,在这个驱动中调用AXI-DMA驱动实现PL到PS的数据传输,再使用字符设备的驱动框架实现其他接口,比如控制采集长度、开始采集等功能,

AXI-DMA驱动

AX-DMA使用Linux内核自带的驱动,我们只需要提供设备树给驱动获取具体的硬件信息就行了。

  1. 编辑设备树文件,添加以下内容
//从Vitis生成的AXI DMA设备树,与Linxu内核中现成的AXI-DMA驱动匹配
amba_pl: amba_pl {
		ranges;
		compatible = "simple-bus";
		#address-cells = <1>;
		#size-cells = <1>;
		axi_dma_1: axi_dma@40400000 {
      #dma-cells = <1>;
			xlnx,dlytmr-resolution = <125>;
			xlnx,num-s2mm-channels = <1>;
			xlnx,rable = <0>;
			xlnx,sg-length-width = <14>;
			xlnx,ip-name = "axi_dma";
			reg = <0x40400000 0x10000>;
			xlnx,sg-use-stsapp-length = <0>;
			xlnx,s2mm-burst-size = <256>;
			xlnx,c-dlytmr-resolution = <125>;
			xlnx,c-num-s2mm-channels = <1>;
			xlnx,enable-multi-channel = <0>;
			xlnx,num-mm2s-channels = <1>;
			xlnx,c-sg-length-width = <14>;
			interrupt-names = "s2mm_introut";
			compatible = "xlnx,axi-dma-7.1" , "xlnx,axi-dma-1.00.a";
			xlnx,c-m-axis-mm2s-tdata-width = <32>;
			xlnx,c-sg-use-stsapp-length = <0>;
			xlnx,c-s2mm-burst-size = <256>;
			xlnx,mm2s-burst-size = <16>;
			xlnx,c-enable-multi-channel = <0>;
			xlnx,c-num-mm2s-channels = <1>;
			interrupt-parent = <&intc>;
			xlnx,include-s2mm-dre = <0>;
			xlnx,c-mm2s-burst-size = <16>;
			status = "okay";
			xlnx,c-include-s2mm-dre = <0>;
			xlnx,include-mm2s-dre = <0>;
			xlnx,name = "axi_dma_0";
			interrupts = < 0 29 4 >;
			xlnx,c-include-sg = <0>;
			xlnx,c-m-axi-s2mm-data-width = <32>;
			xlnx,include-s2mm-sf = <1>;
			xlnx,c-include-mm2s-dre = <0>;
			xlnx,include-s2mm = <1>;
			clocks = <&clkc 15>, <&clkc 15>;
			xlnx,addrwidth = <0x20>;
			xlnx,c-m-axi-mm2s-data-width = <32>;
			xlnx,edk-iptype = "PERIPHERAL";
			xlnx,c-include-s2mm-sf = <1>;
			xlnx,include-mm2s-sf = <1>;
			clock-names = "m_axi_s2mm_aclk" , "s_axi_lite_aclk";
			xlnx,c-include-s2mm = <1>;
			xlnx,c-addr-width = <32>;
			xlnx,c-single-interface = <0>;
			xlnx,include-mm2s = <0>;
			xlnx,c-s-axis-s2mm-tdata-width = <16>;
			xlnx,s2mm-data-width = <0x20>;
			xlnx,c-include-mm2s-sf = <1>;
			xlnx,prmry-is-aclk-async = <0>;
			xlnx,c-include-mm2s = <0>;
			xlnx,increase-throughput = <0>;
			xlnx,micro-dma = <0>;
			xlnx,mm2s-data-width = <0x20>;
			xlnx,c-prmry-is-aclk-async = <0>;
			xlnx,c-sg-include-stscntrl-strm = <0>;
			xlnx,c-increase-throughput = <0>;
			xlnx,c-micro-dma = <0>;
			dma_channel_40400030: dma-channel@40400030 {
				interrupts = <0 29 4>;
				xlnx,datawidth = <0x10>;
				xlnx,device-id = <0x0>;
				compatible = "xlnx,axi-dma-s2mm-channel";
				dma-channels = <0x1>;
			};
		};
	};

AXI-DMA部分的设备树采用的是Vitis根据xsa文件生成的,具体生成步骤这儿不详细记录,可以网上查找相关步骤。

  1. 配置Linux驱动,使能XILINX_DMA驱动编译为模块

    image-20240824230450387

  2. 编译Linux源码工程,生成设备树 xxx.dtb、Linux内核镜像zImage和驱动模块xilinx_dma.ko

    make -j32
    
  3. 将设备树、Linux内核和驱动模块拷贝到ZYNQ板子上,上电启动后使用insmod命令插入编译好的axi-dma驱动模块,查看/sys/class/dma目录下的DMA设备,可以看到新增的AXI-DMA通道dma1chan0

    image-20240824230858130

    再往目录里面看到of_node文件夹内,可以看到dma通道对于的基地址就是0x40400000,与设备树新增的设备基地址是一样的,证明dma1chan0就是对应设备树新增的axi-dma的s2mm通道。

    image-20240824231015739

    查看/proc/interrupts中使能的中断设备,可以看到设备树中指定的AXI-DMA设备的中断已经注册到Linux中断系统了。

    image-20240826000350827

  4. 到这一步只是把AXI-DMA设备添加到Linux内核的DMA子系统的驱动框架中了,后面就可以使用DMA子系统提供的标准接口函数控制DMA通道接收数据。根据Vivado搭建的工程可以看到,除了DMA需要使用DMA子系统来控制之外,还需要配置一下其他的自定义寄存器才能完整实现数据采集功能。因此还需要写一个把整个PL的逻辑当成一个设备的驱动,在驱动中实现PL逻辑复位、配置初始化寄存器和使用DMA采集数据的功能。

编写ADC数据采集设备驱动

  1. 设备树增加自定义设备,指定了AXI基地址和使用的DMA通道

    //自定义的ADC数据设备,后面会用到
    //指定了AXI基地址0x60000000
    //指定了使用axi_dma_1 DMA设备
    adc_axi_dma_0: adc_axi_dma@1 {
        #address-cells = <1>;
        #size-cells = <1>;
        status = "okay";
        compatible ="adc_axi_dma";
        dmas = <&axi_dma_1 1>;
        dma-names = "axidma0";
        reg = <0x60000000 0x20>;
    } ;
    
  2. 新建驱动文件

    • 在driver/dma/xilinx/目录下新建一个驱动源文件,命名为adc_axi_dma_demo.c,文件内容如下:
    #include <linux/module.h>
    #include <linux/dma-mapping.h>
    #include <linux/dmaengine.h>
    #include <linux/platform_device.h>
    #include <linux/dmaengine.h>
    #include <linux/slab.h>
    #include <linux/ioctl.h>
    #include <linux/fs.h>
    #include <linux/uaccess.h>
    #include <linux/types.h>
    #include <linux/kernel.h>
    #include <linux/delay.h>
    #include <linux/init.h>
    #include <linux/errno.h>
    #include <asm/mach/map.h>
    #include <asm/io.h>
    #include <linux/cdev.h>
    #include <linux/platform_device.h>
    #include <linux/ioport.h>
    #include <linux/of_address.h>
    
    #define DEV_NAME "my_adc_axi_dma"
    #define DEV_NUM 1
    #define CLASS_NAME "ADC_AXI_DMA"
    #define DMA_CHANNEL "axidma0"
    #define IOCTL_START_DMA _IO('d', 1)
    // 控制寄存器基地址和寄存器偏移
    #define FIFO2AXIS_BASE 0X60000000
    #define PACKAGE_LENGTH 0x0
    #define PACKAGE_NUMBER 0X8
    #define AXIS_START 0X4
    // PL复位信号控制寄存器
    #define ZYNQ_FCLK_RESET_BASE 0XF8000240
    //储存控制寄存器和复位寄存器映射的虚拟地址
    struct adc_axi_dma_reg {
    	void __iomem *package_length;
    	void __iomem *package_number;
    	void __iomem *axis_start;
    	void __iomem *fclk_reset;
    };
    // 自定义的设备结构体,包含DMA的信息和设备相关的结构体
    struct adc_axi_dma_dev {
    	struct dma_chan *dma_chan;
    	struct device *dev;
    	struct platform_device *pdev;
    
    	dma_addr_t dma_src;
    	dma_addr_t dma_dst;
    	void *src_buf;
    	void *dst_buf;
    	size_t buf_size;
    	struct adc_axi_dma_reg reg;
    	int major;
    	int minor;
    	struct class *class;
    	struct cdev cdev;
    	dev_t devid;
    };
    
    // 映射控制寄存器和复位寄存器到虚拟地址,存储到设备结构体中,用于注册驱动时获取虚拟地址,方便后续函数操作寄存器
    static void adc_axi_dma_remap(struct adc_axi_dma_dev *dev)
    {
    	dev->reg.fclk_reset = ioremap(ZYNQ_FCLK_RESET_BASE, 4);
    	if (!dev->reg.fclk_reset) {
    		printk(KERN_ERR "Failed to remap fclk_reset\n");
    		// Handle error if needed
    	}
    	dev->reg.package_length = ioremap(FIFO2AXIS_BASE + PACKAGE_LENGTH, 4);
    	if (!dev->reg.package_length) {
    		printk(KERN_ERR "Failed to remap package_length\n");
    		// Handle error if needed
    	}
    	dev->reg.package_number = ioremap(FIFO2AXIS_BASE + PACKAGE_NUMBER, 4);
    	if (!dev->reg.package_number) {
    		printk(KERN_ERR "Failed to remap package_number\n");
    		// Handle error if needed
    	}
    	dev->reg.axis_start = ioremap(FIFO2AXIS_BASE + AXIS_START, 4);
    	if (!dev->reg.axis_start) {
    		printk(KERN_ERR "Failed to remap axis_start\n");
    		// Handle error if needed
    	}
    }
    // 取消控制寄存器和复位寄存器的地址映射,用于注销驱动时清除映射
    static void adc_axi_dma_unmap(struct adc_axi_dma_dev *dev)
    {
    	if (dev->reg.fclk_reset)
    		iounmap(dev->reg.fclk_reset);
    	if (dev->reg.package_length)
    		iounmap(dev->reg.package_length);
    	if (dev->reg.package_number)
    		iounmap(dev->reg.package_number);
    	if (dev->reg.axis_start)
    		iounmap(dev->reg.axis_start);
    }
    // 复位PL逻辑
    static void adc_axi_dma_reset(struct adc_axi_dma_dev *dev)
    {
    	writel(0xf, dev->reg.fclk_reset);
    	udelay(10);
    	writel(0x0, dev->reg.fclk_reset);
    }
    // 字符设备的标准open函数
    static int simple_dma_open(struct inode *inode, struct file *file)
    {
    	struct adc_axi_dma_dev *dev;
    	dev = container_of(inode->i_cdev, struct adc_axi_dma_dev, cdev);
    	file->private_data = dev; // 将设备结构体指针存储到文件私有数据中
    	return 0;
    }
    // 字符设备的release函数
    static int simple_dma_release(struct inode *inode, struct file *file)
    {
    	return 0;
    }
    // 字符设备的read函数,主要功能是当DMA传输完成后,DMA写到DDR的内存地址存储在dma_dev->dst_buf指针中,使用read函数从dst_buf中获取传输的数据,传给应用程序
    static ssize_t adc_axi_dma_read(struct file *file, char __user *buf,
    				size_t count, loff_t *ppos)
    {
    	struct adc_axi_dma_dev *dma_dev = file->private_data;
    	size_t bytes_to_copy;
    	pr_info("adc_axi_dma read\n");
    	pr_info("%lx\n", (unsigned long)dma_dev->dst_buf);
    	pr_info("%d\n", *(unsigned int *)(dma_dev->dst_buf));
    	if (*ppos >= dma_dev->buf_size) {
    		return 0; // 已经读取完所有数据
    	}
    
    	bytes_to_copy = min(count, dma_dev->buf_size - *ppos);
    	if (copy_to_user(buf, dma_dev->dst_buf + *ppos, bytes_to_copy)) {
    		return -EFAULT;
    	}
    
    	*ppos += bytes_to_copy;
    	// 读完buf中的数据后释放空间,方便下次申请
    	if (*ppos == dma_dev->buf_size) {
    		dma_free_coherent(dma_dev->dev, dma_dev->buf_size,
    				  dma_dev->dst_buf, dma_dev->dma_dst);
    	}
    	return bytes_to_copy;
    }
    // DMA完成中断回调函数
    static void adc_axi_dma_callback(void *data)
    {
    	struct adc_axi_dma_dev *dma_dev = (struct adc_axi_dma_dev *)data;
    
    	pr_info("DMA transfer completed callback\n");
    }
    
    struct dma_ioctl_args {
    	unsigned int rx_buf_size; // DMA buffer size
    	unsigned int package_length;
    	unsigned int package_number;
    	unsigned long
    		dst_addr; // Address to store the DMA destination buffer address
    };
    // 字符设备的IOCTL函数,实现了一个启动传输的命令,应用程序需要传递一个dma_ioctl_args类型结构体指针,设置AXI-STREAM数据包的长度、接收BUF的大小
    static long simple_dma_ioctl(struct file *file, unsigned int cmd,
    			     unsigned long arg)
    {
    	struct dma_async_tx_descriptor *tx;
    	struct adc_axi_dma_dev *dma_dev = file->private_data;
    	dma_cookie_t cookie;
    	enum dma_status status;
    	struct dma_ioctl_args args;
    
    	if (cmd == IOCTL_START_DMA) {
    		// 获取传递的dma_ioctl_args类型参数
    		pr_info("adc_axi_dma ioctl\n");
    		if (copy_from_user(&args, (void __user *)arg, sizeof(args))) {
    			pr_err("Failed to copy arguments from user space\n");
    			return -EFAULT;
    		}
    		// 给控制寄存器写入长度参数
    		writel(args.package_length, dma_dev->reg.package_length);
    		writel(args.package_number, dma_dev->reg.package_number);
    		// 请求名为axidma0的DMA通道,这个名字和设备树是对应的
    		dma_dev->dma_chan =
    			dma_request_chan(&(dma_dev->pdev->dev), "axidma0");
    		if (IS_ERR(dma_dev->dma_chan)) {
    			pr_err("Failed to request DMA channel\n");
    			return PTR_ERR(dma_dev->dma_chan);
    		}
    		// 申请一个接收buffer的指针给dma_dev->dst_buf,接收到的数据存储在这个指针指向的内存中
    		// Prepare the destination buffer only
    		dma_dev->buf_size = args.rx_buf_size;
    		dma_dev->dst_buf =
    			dma_alloc_coherent(dma_dev->dev, dma_dev->buf_size,
    					   &dma_dev->dma_dst, GFP_KERNEL);
    		if (!dma_dev->dst_buf) {
    			pr_err("Failed to allocate DMA destination buffer\n");
    			return -ENOMEM;
    		}
    
    		// Prepare the DMA transaction without specifying a source address
    		tx = dmaengine_prep_slave_single(
    			dma_dev->dma_chan, dma_dev->dma_dst, dma_dev->buf_size,
    			DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
    		if (!tx) {
    			pr_err("Failed to prepare DMA transaction\n");
    			return -ENOMEM;
    		}
    		// 绑定DMA完成中断回调函数
    		tx->callback = adc_axi_dma_callback;
    		tx->callback_param = dma_dev;
    
    		// 提交DMA传输任务
    		cookie = dmaengine_submit(tx);
    		dma_async_issue_pending(dma_dev->dma_chan);
    		// 配置控制寄存器开始从AXI-Stream发送数据到AXI-DMA
    		writel(0x1, dma_dev->reg.axis_start);
    		writel(0x0, dma_dev->reg.axis_start);
    		// 轮询等待DMA传输完成
    		do {
    			status = dma_async_is_tx_complete(dma_dev->dma_chan,
    							  cookie, NULL, NULL);
    		} while (status == DMA_IN_PROGRESS);
    
    		if (status == DMA_COMPLETE) {
    			pr_info("DMA transfer completed successfully\n");
    		} else {
    			pr_err("DMA transaction failed with status: %d\n",
    			       status);
    			return -EIO;
    		}
    		pr_info("DMA transfer completed successfully\n");
    		// Copy the DMA destination buffer address back to user space
    		// 这个返回去好像用不了,应用程序中没用这个传回去的地址
    		args.dst_addr = (unsigned long)dma_dev->dst_buf;
    		if (copy_to_user((void __user *)arg, &args, sizeof(args))) {
    			pr_err("Failed to copy DMA destination buffer address to user space\n");
    			return -EFAULT;
    		}
    		// Clean up
    		dma_release_channel(dma_dev->dma_chan);
    	}
    	return 0;
    }
    // 绑定字符设备的标准函数
    static struct file_operations fops = {
    	.open = simple_dma_open,
    	.read = adc_axi_dma_read,
    	.release = simple_dma_release,
    	.unlocked_ioctl = simple_dma_ioctl,
    };
    // 平台驱动的probe函数,当设备树与驱动的compatible匹配时该函数被执行,驱动编译为模块的话则在insmod时若发现匹配的设备就执行该函数
    static int adc_axi_dma_probe(struct platform_device *pdev)
    {
    	int ret = 0;
    	struct resource *res;
    	struct adc_axi_dma_dev *dma_dev;
    	// 申请一个adc_axi_dma_dev结构体的空间
    	dma_dev = kzalloc(sizeof(*dma_dev), GFP_KERNEL);
    	if (!dma_dev) {
    		pr_err("Failed to allocate memory for device structure\n");
    		return -ENOMEM;
    	}
    	dma_dev->pdev = pdev;
    	// 获取设备树中指定的寄存器地址资源,向内核申请资源
    	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    	if (!res) {
    		pr_err("Failed to get platform resource\n");
    		return -ENODEV;
    	}
    	if (!request_mem_region(res->start, resource_size(res), pdev->name)) {
    		pr_err("Failed to request memory region\n");
    		return -EBUSY;
    	}
    	// 映射寄存器地址到虚拟地址
    	adc_axi_dma_remap(dma_dev);
    	adc_axi_dma_reset(dma_dev);
    
    	// 创建字符设备标准操作,申请主设备号、创建字符设备
    	if (dma_dev->major) {
    		dma_dev->devid = MKDEV(dma_dev->major, 0);
    		ret = register_chrdev_region(dma_dev->devid, DEV_NUM, DEV_NAME);
    		if (ret) {
    			printk(KERN_ERR "Failed to register chardev region\n");
    			goto err_chrdev_region;
    		}
    	} else {
    		ret = alloc_chrdev_region(&dma_dev->devid, 0, DEV_NUM,
    					  DEV_NAME);
    		if (ret) {
    			printk(KERN_ERR "Failed to allocate chardev region\n");
    			goto err_chrdev_alloc;
    		}
    		dma_dev->major = MAJOR(dma_dev->devid);
    		dma_dev->minor = MINOR(dma_dev->devid);
    	}
    	// 在linux的/dev目录下创建设备
    	dma_dev->class = class_create(DEV_NAME);
    	if (IS_ERR(dma_dev->class)) {
    		ret = PTR_ERR(dma_dev->class);
    		printk(KERN_ERR "Failed to create device class\n");
    		goto err_class_create;
    	}
    	// 将驱动实现的open、write等函数绑定到设备,函数在led_fops结构体中
    	cdev_init(&dma_dev->cdev, &fops);
    	ret = cdev_add(&dma_dev->cdev, dma_dev->devid, 1);
    	if (ret < 0) {
    		printk(KERN_ERR "Failed to add cdev\n");
    		goto err_cdev_add;
    	}
    
    	dma_dev->dev = device_create(dma_dev->class, NULL, dma_dev->devid, NULL,
    				     DEV_NAME);
    	if (IS_ERR(dma_dev->dev)) {
    		ret = PTR_ERR(dma_dev->dev);
    		printk(KERN_ERR "Failed to create device\n");
    		goto err_device_create;
    	}
    
    	platform_set_drvdata(pdev, dma_dev);
    	pr_info("ADC AXI-DMA device probed\n");
    	return 0;
    // 错误处理,主要是之前初始化步骤失败后释放相关资源
    err_device_create:
    	cdev_del(&dma_dev->cdev);
    err_cdev_add:
    	class_destroy(dma_dev->class);
    err_class_create:
    	unregister_chrdev_region(dma_dev->devid, 1);
    err_chrdev_alloc:
    err_chrdev_region:
    	adc_axi_dma_unmap(dma_dev);
    err_ioremap:
    	return ret;
    }
    // 平台驱动的remove函数,在rmmod时被执行,释放使用的资源
    static int adc_axi_dma_remove(struct platform_device *pdev)
    {
    	struct resource *res;
    	struct adc_axi_dma_dev *dma_dev = platform_get_drvdata(pdev);
    	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    	if (res) {
    		adc_axi_dma_unmap(dma_dev);
    		release_mem_region(res->start,
    				   resource_size(res)); // 释放内存区域
    	}
    
    	device_destroy(dma_dev->class, dma_dev->devid);
    	class_unregister(dma_dev->class);
    	class_destroy(dma_dev->class);
    	unregister_chrdev(dma_dev->major, DEV_NAME);
    	kfree(dma_dev);
    	pr_info("ADC AXI-DMA device exited\n");
    	return 0;
    }
    // 驱动匹配表,与设备树中设备节点的compatible属性匹配
    static const struct of_device_id adc_axi_dma_of_match[] = {
    	{ .compatible = "adc_axi_dma" },
    	{}
    };
    // 平台驱动结构体
    static struct platform_driver adc_axi_dma_driver = {
    	.driver = { .name = "adc_axi_dma_driver",
    		    .of_match_table = adc_axi_dma_of_match },
    	.probe = adc_axi_dma_probe,
    	.remove = adc_axi_dma_remove,
    };
    // 注册一个平台驱动
    module_platform_driver(adc_axi_dma_driver);
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("Your Name");
    MODULE_DESCRIPTION("A simple DMA device driver");
    MODULE_VERSION("0.1");
    
    
    • 修改Makefile和Kconfig

      修改/driver/dma/xilinx/Makefile文件,新增一行内容

      obj-$(CONFIG_ADC_AXI_DMA_DEMO) += adc_axi_dma_demo.o
      

      修改/driver/dma/Kconfig文件,新增以下内容

      config ADC_AXI_DMA_DEMO
        tristate "MY ADC TO AXI-DMA DEMO"
      	depends on HAS_IOMEM
      	select DMA_ENGINE
      	help
          ADC data trsanmited to ps ddr by axi-dma
      
    • 使能内核编译驱动为模块

      image-20240826003352354

    • 编译驱动

      make modules -j32
      

      编译完后生成adc_axi_dma_demo.ko驱动模块

    • 加载驱动

      将adc_axi_dma_demo.ko复制到ZYNQ板子Linux系统内,通过insmod命令插入模块,插入成功后在/dev目录下可以看到新增的设备my_adc_axi_dma

      image-20240826003656497

编写应用程序

  1. 在虚拟机中新建一个应用程序源文件demo.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <sys/ioctl.h>
    
    #define IOCTL_START_DMA _IO('d', 1)
    struct dma_ioctl_args
    {
      unsigned int rx_buf_size; // DMA buffer size
      unsigned int package_length;
      unsigned int package_number;
      unsigned long
          dst_addr; // Address to store the DMA destination buffer address
    };
    
    int main()
    {
      int fd = open("/dev/my_adc_axi_dma", O_RDWR);
      struct dma_ioctl_args args;
      unsigned char *user_buffer;
      args.rx_buf_size = 1024 * 1024;
      args.package_length = 200;
      args.package_number = 1;
      args.dst_addr = 0;
      if (fd < 0)
      {
        perror("Failed to open device");
        return EXIT_FAILURE;
      }
    
      if (ioctl(fd, IOCTL_START_DMA, &args) < 0)
      {
        perror("Failed to start DMA");
        close(fd);
        return EXIT_FAILURE;
      }
      // 分配用户空间缓冲区
      user_buffer = (unsigned char *)malloc(args.rx_buf_size);
      if (!user_buffer)
      {
        perror("Failed to allocate user buffer");
        close(fd);
        return -1;
      }
      // 使用read函数从内核空间复制数据到用户空间
      if (read(fd, user_buffer, args.rx_buf_size) != args.rx_buf_size)
      {
        perror("Failed to read data from device");
        free(user_buffer);
        close(fd);
        return -1;
      }
      printf("DMA transfer completed successfully\n");
      printf("rx data in 0x%x\n", (unsigned short)user_buffer);
      int i;
      for (i = 0; i < args.package_length * 2; i = i + 2)
      {
        printf("addr:0x%lx,\tdata:%d\n", (unsigned long)(user_buffer + i), *(unsigned short *)(user_buffer + i));
      }
      close(fd);
      return EXIT_SUCCESS;
    }
    
    
  2. 编译为可执行文件

    arm-linux-gnueabihf-gcc demo.c
    

    生成a.out文件

  3. 运行应用程序

    将a.out拷贝到ZYNQ Linux系统,添加可执行权限并运行

    sudo chmod +x a.out
    sudo ./a.out
    

    image-20240826004610354

​ 根据打印信息可以看到程序调用了驱动中的ioctl函数并完成了传输,传输完成后进入了自定义的中断回调函数,最终通过驱动的read函数将接收端DDR的数据打印了出来,接收数据为一个递增数,与Vivado端设计的数据发生器是对应的。

image-20240826005129390

在Vivado生成比特流之前我把AXI-Stream接口抓了下信号,当PS给PL加载bin文件后也可以连接仿真器参看debug的信号,这儿是执行了4次测试应用程序抓取的波形,可以看到PL中也发生了4次AXI-DMA的传输过程,AXI-Stream总线上的数据与应用程序打印的数据也是对应的。

posted @ 2024-11-08 01:14  LM358  阅读(1996)  评论(0)    收藏  举报