本篇是AXI DMA在linux下使用的例子。
包括PL端设计,基于vivado 2015.4,petalinux 2016.1,基于linux 4.4内核。
我在git hub 上托管了代码,https://github.com/shichaog/zynq-dma,如果想加入github上这个项目,给我邮箱发信息shichaog@126.com,方便为你配权限。
1.PL端设计:PL端设计包括四个AXI DMA IP,它们分别和zynq处理IP的HP口相连接。
这个设计是基于Avnet-Digilent-ZedBoard-v2016.1-final.bsp,由于其它的ip都是xilinx开发环境开发环境就有,所以这里就不详细每一步设计过程了。
这些IP包括AXI interconnect, system reset,axi dma,concat。
注意concat是用来将AXI DMA的中断传递给zynq之用的,这是必须有的,否则在hdf导入时,会出现如下错误:
2 接下来是创建module和app了
这两个命令执行后,会分配在components/apps和components/modules目录下生成dmaBench和ds_axidma两个文件夹,该文件下的两个文件内容使用如下的文件替换。
《dmaBench.c》#include <stdio.h>#include <fcntl.h>#include <string.h>#include <stdlib.h>#include <sys/time.h>unsigned long tStart, tEnd;unsigned long data;unsigned long getTime(){ struct timeval temp; gettimeofday(&temp, NULL); return temp.tv_sec * 1000 * 1000 + temp.tv_usec;}void report(char *msg, unsigned long data, unsigned long time, unsigned long dmaUsed){ printf("%s\t%ld\t%ld\t%f\t%d\n", msg, data, time, data * 1.0 / time, dmaUsed); FILE *f = fopen("report.dat", "a"); fprintf(f, "%s\t%ld\t%ld\t%f\t%d\n", msg, data, time, data * 1.0 / time, dmaUsed); fclose(f);}#define REPORT(f, timeStart, timeEnd, dataPtr, msg, dmaUsed) *timeStart = getTime(); *dataPtr = f; *timeEnd = getTime(); report(msg, *dataPtr, *timeEnd - *timeStart, dmaUsed);void checkData(char *bufferIn, char *bufferOut, unsigned int elems){ int i; if(!memcmp(bufferIn, bufferOut, elems*sizeof(char))){ printf("DMA Ok!\n"); } else{ for(i=0;i<elems;i++) printf("%d\t%d\t%d\t%d\n", i, bufferIn, bufferOut, (i==0 ? 0 : bufferOut - bufferOut[i-1])); }}unsigned long memCpy_ARM(char *bufferIn, char *bufferOut, unsigned long elems, size_t size){ int i; for(i=0; i<elems; i++) bufferOut = bufferIn; return elems * size;}unsigned long memCpy_DMA(char *bufferIn, char *bufferOut, unsigned long elems, size_t size, int dmaToUse){#define FIFO_LEN 4000#define DMA_NUM 4 int fd[DMA_NUM]; fd[0] = open("/dev/axi_dma_0", O_RDWR); fd[1] = open("/dev/axi_dma_1", O_RDWR); fd[2] = open("/dev/axi_dma_2", O_RDWR); fd[3] = open("/dev/axi_dma_3", O_RDWR); unsigned long byteMoved = 0; unsigned long byteToMove = 0; int i; while(byteMoved!=size * elems){ byteToMove = size * elems - byteMoved > FIFO_LEN ? FIFO_LEN : size * elems - byteMoved; for(i=0; i<dmaToUse; i++){ write(fd, &bufferIn[byteMoved], byteToMove); } for(i=0; i<dmaToUse; i++) read(fd, &bufferOut[byteMoved], byteToMove); byteMoved += byteToMove; } close(fd[0]); close(fd[1]); close(fd[2]); close(fd[3]); return elems * size * dmaToUse;}int main(int argc, char **argv){ char *bufferIn, *bufferOut_ARM, *bufferOut_DMA; if(argc!=3){ printf("Usage: ./dmaBench DATA DMA_TO_USE\n"); exit(0); } unsigned long DATA = atoi(argv[1]); unsigned int DMA_TO_USE = atoi(argv[2]); bufferIn = (char *) malloc(sizeof(char) * DATA); bufferOut_ARM = (char *) malloc(sizeof(char) * DATA); bufferOut_DMA = (char *) malloc(sizeof(char) * DATA); int i; for(i=0; i<DATA; i++){ bufferIn = i; } memset(bufferOut_ARM, 0, sizeof(char) * DATA); memset(bufferOut_DMA, 0, sizeof(char) * DATA); REPORT(memCpy_ARM(bufferIn, bufferOut_ARM, DATA, sizeof(char)), &tStart, &tEnd, &data, "ARM", 0); for(i=0; i<DMA_TO_USE; i++){ REPORT(memCpy_DMA(bufferIn, bufferOut_DMA, DATA/(i+1), sizeof(char), (i+1)), &tStart, &tEnd, &data, "DMA", (i+1)); } checkData(bufferIn, bufferOut_ARM, DATA); checkData(bufferIn, bufferOut_DMA, DATA); return 0;}和<ds_axidma>
/* * Xilinx AXI DMA Driver * * Authors: * Fabrizio Spada - fabrizio.spada@mail.polimi.it * Gianluca Durelli - durelli@elet.polimi.it * Politecnico di Milano * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */#include <linux/module.h>#include <linux/version.h>#include <linux/kernel.h>#include <linux/types.h>#include <linux/kdev_t.h>#include <linux/fs.h>#include <linux/list.h>#include <linux/device.h>#include <linux/cdev.h>#include <linux/dma-mapping.h>#include <linux/pm_runtime.h>#include <linux/slab.h>#include <linux/of.h>#include <linux/of_platform.h>#include <linux/of_address.h>#include <linux/mm.h>#include <asm/io.h>#define MM2S_DMACR 0x00#define MM2S_DMASR 0x04#define MM2S_SA 0x18#define MM2S_LENGTH 0x28#define S2MM_DMACR 0x30#define S2MM_DMASR 0x34#define S2MM_DA 0x48#define S2MM_LENGTH 0x58#define DRIVER_NAME "ds_axidma_pdrv"#define MODULE_NAME "ds_axidma"#define DMA_LENGTH (32*1024)static struct class *cl; // Global variable for the device class struct ds_axidma_device{ phys_addr_t bus_addr; unsigned long bus_size; char *virt_bus_addr; dev_t dev_num; const char *dev_name; struct cdev c_dev; char *ds_axidma_addr; dma_addr_t ds_axidma_handle; struct list_head dev_list;};LIST_HEAD( full_dev_list );static struct ds_axidma_device *get_elem_from_list_by_inode(struct inode *i){ struct list_head *pos; struct ds_axidma_device *obj_dev = NULL; list_for_each( pos, &full_dev_list ) { struct ds_axidma_device *tmp; tmp = list_entry( pos, struct ds_axidma_device, dev_list ); if (tmp->dev_num == i->i_rdev) { obj_dev = tmp; break; } } return obj_dev; }// static void dmaHalt(void){// unsigned long mm2s_halt = ioread32(virt_bus_addr + MM2S_DMASR) & 0x1;// unsigned long s2mm_halt = ioread32(virt_bus_addr + S2MM_DMASR) & 0x1;// int count = 0;// printk(KERN_INFO "Halting...\n");// iowrite32(0, virt_bus_addr + S2MM_DMACR);// iowrite32(0, virt_bus_addr + MM2S_DMACR);// while( !mm2s_halt || !s2mm_halt){// // mm2s_halt = ioread32(virt_bus_addr + MM2S_DMASR) & 0x1;// mm2s_halt = virt_bus_addr[MM2S_DMASR] & 0x1;// //s2mm_halt = ioread32(virt_bus_addr + S2MM_DMASR) & 0x1;// s2mm_halt = virt_bus_addr[S2MM_DMASR] & 0x1;// count++;// if (count>100 )// {// break;// }// }// printk(KERN_INFO "DMA Halted!\n");// }static int my_strcmp(const char *str1, const char *str2){ int i; i = 0; while (str1 || str2) { if (str1 != str2) return (str1 - str2); i++; } return (0);}static int dmaSynchMM2S(struct ds_axidma_device *obj_dev){ // sleep(6); // return; unsigned int mm2s_status = ioread32(obj_dev->virt_bus_addr + MM2S_DMASR); while(!(mm2s_status & 1<<12) || !(mm2s_status & 1<<1) ){ mm2s_status = ioread32(obj_dev->virt_bus_addr + MM2S_DMASR); } return 0;}static int dmaSynchS2MM(struct ds_axidma_device *obj_dev){ unsigned int s2mm_status = ioread32(obj_dev->virt_bus_addr + S2MM_DMASR); while(!(s2mm_status & 1<<12) || !(s2mm_status & 1<<1)){ s2mm_status = ioread32(obj_dev->virt_bus_addr + S2MM_DMASR); } return 0;}static int ds_axidma_open(struct inode *i, struct file *f){ /* printk(KERN_INFO "<%s> file: open()\n", MODULE_NAME); */ struct ds_axidma_device *obj_dev = get_elem_from_list_by_inode(i); if (!request_mem_region(obj_dev->bus_addr, obj_dev->bus_size, MODULE_NAME)) { return -1; } obj_dev->virt_bus_addr = (char *) ioremap_nocache(obj_dev->bus_addr, obj_dev->bus_size); return 0;}static int ds_axidma_close(struct inode *i, struct file *f){ /* printk(KERN_INFO "<%s> file: close()\n", MODULE_NAME); */ struct ds_axidma_device *obj_dev = get_elem_from_list_by_inode(i); iounmap(obj_dev->virt_bus_addr); release_mem_region(obj_dev->bus_addr, obj_dev->bus_size); return 0;}static ssize_t ds_axidma_read(struct file *f, char __user * buf, size_t len, loff_t * off){ /* printk(KERN_INFO "<%s> file: read()\n", MODULE_NAME); */ struct ds_axidma_device *obj_dev; if (len >= DMA_LENGTH) { return 0; } obj_dev = get_elem_from_list_by_inode(f->f_inode); iowrite32(1, obj_dev->virt_bus_addr + S2MM_DMACR); iowrite32(obj_dev->ds_axidma_handle, obj_dev->virt_bus_addr + S2MM_DA); iowrite32(len, obj_dev->virt_bus_addr + S2MM_LENGTH); dmaSynchS2MM(obj_dev); memcpy(buf, obj_dev->ds_axidma_addr, len); return len;}static ssize_t ds_axidma_write(struct file *f, const char __user * buf, size_t len, loff_t * off){ /* printk(KERN_INFO "<%s> file: write()\n", MODULE_NAME); */ struct ds_axidma_device *obj_dev; if (len >= DMA_LENGTH) { return 0; } obj_dev = get_elem_from_list_by_inode(f->f_inode); memcpy(obj_dev->ds_axidma_addr, buf, len); // printk(KERN_INFO "%X\n", ioread32(virt_bus_addr + MM2S_DMASR)); // printk(KERN_INFO "%X\n", ioread32(virt_bus_addr + MM2S_DMACR)); // printk(KERN_INFO "%X\n", ioread32(virt_bus_addr + S2MM_DMASR)); // printk(KERN_INFO "%X\n", ioread32(virt_bus_addr + S2MM_DMACR)); iowrite32(1, obj_dev->virt_bus_addr + MM2S_DMACR); iowrite32(obj_dev->ds_axidma_handle, obj_dev->virt_bus_addr + MM2S_SA); iowrite32(len, obj_dev->virt_bus_addr + MM2S_LENGTH); // dmaSynchMM2S(obj_dev); // printk(KERN_INFO "%X\n", ioread32(virt_bus_addr + MM2S_DMASR)); // printk(KERN_INFO "%X\n", ioread32(virt_bus_addr + MM2S_DMACR)); // printk(KERN_INFO "%X\n", ioread32(virt_bus_addr + S2MM_DMASR)); // printk(KERN_INFO "%X\n", ioread32(virt_bus_addr + S2MM_DMACR)); // printk(KERN_INFO "%X\n", bus_addr); // printk(KERN_INFO "%lu\n", bus_size); return len;}static struct file_operations fops = { .owner = THIS_MODULE, .open = ds_axidma_open, .release = ds_axidma_close, .read = ds_axidma_read, .write = ds_axidma_write, /* .mmap = ds_axidma_mmap, */ /* .unlocked_ioctl = ds_axidma_ioctl, */};static int ds_axidma_pdrv_probe(struct platform_device *pdev){ /* device constructor */ struct ds_axidma_device *obj_dev = (struct ds_axidma_device *) kmalloc( sizeof(struct ds_axidma_device), GFP_KERNEL ); obj_dev->bus_addr = pdev->resource[0].start; obj_dev->bus_size = pdev->resource[0].end - pdev->resource[0].start + 1; obj_dev->dev_name = pdev->name + 9; printk(KERN_INFO "<%s> init: registered\n", obj_dev->dev_name); if (alloc_chrdev_region(&(obj_dev->dev_num), 0, 1, obj_dev->dev_name) < 0) { return -1; } if (cl == NULL && (cl = class_create(THIS_MODULE, "chardrv")) == NULL) { unregister_chrdev_region(obj_dev->dev_num, 1); return -1; } if (device_create(cl, NULL, obj_dev->dev_num, NULL, obj_dev->dev_name) == NULL) { class_destroy(cl); unregister_chrdev_region(obj_dev->dev_num, 1); return -1; } cdev_init(&(obj_dev->c_dev), &fops); if (cdev_add(&(obj_dev->c_dev), obj_dev->dev_num, 1) == -1) { device_destroy(cl, obj_dev->dev_num); class_destroy(cl); unregister_chrdev_region(obj_dev->dev_num, 1); return -1; } printk(KERN_INFO "DMA_LENGTH = %u \n", DMA_LENGTH); /* allocate mmap area */ obj_dev->ds_axidma_addr = dma_zalloc_coherent(NULL, DMA_LENGTH, &(obj_dev->ds_axidma_handle), GFP_KERNEL); list_add( &obj_dev->dev_list, &full_dev_list ); return 0;}static int ds_axidma_pdrv_remove(struct platform_device *pdev){ /* device destructor */ struct list_head *pos, *q; list_for_each_safe( pos, q, &full_dev_list ) { struct ds_axidma_device *obj_dev; obj_dev = list_entry( pos, struct ds_axidma_device, dev_list ); if (!my_strcmp(obj_dev->dev_name, pdev->name + 9)) { list_del( pos ); cdev_del(&(obj_dev->c_dev)); device_destroy(cl, obj_dev->dev_num); unregister_chrdev_region(obj_dev->dev_num, 1); /* free mmap area */ if (obj_dev->ds_axidma_addr) { dma_free_coherent(NULL, DMA_LENGTH, obj_dev->ds_axidma_addr, obj_dev->ds_axidma_handle); } kfree(obj_dev); break; } } if (list_empty(&full_dev_list)) { class_destroy(cl); } printk(KERN_INFO "<%s> exit: unregistered\n", MODULE_NAME); return 0;}static int ds_axidma_pdrv_runtime_nop(struct device *dev){ /* Runtime PM callback shared between ->runtime_suspend() * and ->runtime_resume(). Simply returns success. * * In this driver pm_runtime_get_sync() and pm_runtime_put_sync() * are used at open() and release() time. This allows the * Runtime PM code to turn off power to the device while the * device is unused, ie before open() and after release(). * * This Runtime PM callback does not need to save or restore * any registers since user space is responsbile for hardware * register reinitialization after open(). */ return 0;}static const struct dev_pm_ops ds_axidma_pdrv_dev_pm_ops = { .runtime_suspend = ds_axidma_pdrv_runtime_nop, .runtime_resume = ds_axidma_pdrv_runtime_nop,};static struct of_device_id ds_axidma_of_match[] = { { .compatible = "ds_axidma", }, { /* This is filled with module_parm */ }, { /* Sentinel */ },};MODULE_DEVICE_TABLE(of, ds_axidma_of_match);module_param_string(of_id, ds_axidma_of_match[1].compatible, 128, 0);MODULE_PARM_DESC(of_id, "Openfirmware id of the device to be handled by uio");static struct platform_driver ds_axidma_pdrv = { .probe = ds_axidma_pdrv_probe, .remove = ds_axidma_pdrv_remove, .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, .pm = &ds_axidma_pdrv_dev_pm_ops, .of_match_table = of_match_ptr(ds_axidma_of_match), },};module_platform_driver(ds_axidma_pdrv);MODULE_AUTHOR("Fabrizio Spada, Gianluca Durelli");MODULE_DESCRIPTION("AXI DMA driver");MODULE_LICENSE("GPL v2");
3.编译,生成BOOT.BIN文件
images/linux/目录下将生成的BOOT.BIN和image.ub文件拷贝到SD卡,插上SD卡。启动串口敲入用户名和密码(均root):
这里可以看到ds_axidma.ko这个内核module。同时可以看到apps
测试方法如下:
至此,DMA的简单实例就完成了,PS侧的DMA可以参考Audio侧,另外,如果有一些文件挂载分区,则如下:
https://blog.csdn.net/shichaog/article/details/51771247
|