DMA驱动修改代码

DMA驱动修改后提升为90% 由原read方式换为mmap方式,可能是系统调用函数更新原因

驱动代码从赛灵思xilinux下载官网版本,根据文档和现有驱动文件Linux设备数更改。

DMA驱动修改后 可能需修改相应位流文件,更换重新编译bin文件,后加载驱动 lsmod rmmod insmod

原驱动代码:

/*  axiDma.c - The simplest kernel module.

* Copyright (C) 2013 - 2016 Xilinx, Inc
*
*   This program 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.

*   This program is distributed in the hope that it will be useful,
*   but WITHOUT ANY WARRANTY; without even the implied warranty of
*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*   GNU General Public License for more details.
*
*   You should have received a copy of the GNU General Public License along
*   with this program. If not, see <http://www.gnu.org/licenses/>.

*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/interrupt.h>

#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>

#include <linux/interrupt.h>

#include <linux/version.h>

#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/workqueue.h>
#include <linux/platform_device.h>
#include <linux/of_dma.h>
#include <linux/uaccess.h>

#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
#include <linux/dma/xilinx_dma.h>



/* Standard module information, edit as appropriate */
MODULE_LICENSE("GPL");
MODULE_AUTHOR
    ("Xilinx Inc.");
MODULE_DESCRIPTION
    ("axiDma - loadable module template generated by petalinux-create -t modules");

#define DRIVER_NAME "axiDma"



/* Simple example of how to receive command line parameters to your module.
   Delete if you don't need them */
unsigned myint = 0xdeadbeef;
char *mystr = "default";

module_param(myint, int, S_IRUGO);
module_param(mystr, charp, S_IRUGO);


#define BUF_SIZE					(256*1024 * 16)
#define IOCTL_ARG1                  0xFB

#define DMA_START_RD_COMMAND         _IO(IOCTL_ARG1,1)
#define DMA_START_WR_COMMAND         _IO(IOCTL_ARG1,2)
#define DMA_WAIT_WR_FINISH_COMMAND	 _IO(IOCTL_ARG1,3)
#define DMA_WAIT_RD_FINISH_COMMAND	 _IO(IOCTL_ARG1,4)


struct axiDma_local {
	
	struct device * pdev;
	struct device * dev;
	dev_t dev_node;
	struct cdev cdev;
	struct class *class_p;

	struct dma_chan *tx_chan;	/* dma support */
	struct dma_chan *rx_chan;	/* dma support */
	struct completion tx_cmp;
	struct completion rx_cmp;
	unsigned long tx_tmo;
	unsigned long rx_tmo;
	dma_cookie_t tx_cookie;
	dma_cookie_t rx_cookie;

	char * tx_virt_addr;
	char * rx_virt_addr;
	
	dma_addr_t tx_dma_addr;
	dma_addr_t rx_dma_addr;
};


static int local_open(struct inode *ino, struct file *file);

static int local_read(struct file *file, char __user * buf, size_t len, loff_t *off);

static int local_write(struct file *file, const char __user *buf, size_t len, loff_t *off);

static long ioctl(struct file *file, unsigned int unused,unsigned long arg);

static int release(struct inode *ino, struct file *file);

static int mmap(struct file *file_p, struct vm_area_struct *vma);

static int cdevice_init(struct axiDma_local * pData);

static void cdevice_exit(struct axiDma_local *pstDrv);

static int create_channel(struct axiDma_local *pstDrv);

//
static struct file_operations drv_fops =
{
	.owner = THIS_MODULE,
	.open = local_open,
	.release = release,
	.read = local_read,
    .write = local_write,
	.unlocked_ioctl = ioctl,
	.mmap = mmap,
};


/*
static irqreturn_t axiDma_irq(int irq, void *lp)
{
	printk("axiDma interrupt\n");
	return IRQ_HANDLED;
}
*/
static int axiDma_probe(struct platform_device *pdev)
{

	struct device *dev = &pdev->dev;
	struct axiDma_local *lp = NULL;
	int rc = 0;
	dev_info(dev, "Device Tree Probing\n");
	
	
	lp = (struct axiDma_local *) kmalloc(sizeof(struct axiDma_local), GFP_KERNEL);
	if (!lp) {
		dev_err(dev, "Cound not allocate axiDma device\n");
		return -ENOMEM;
	}	
	lp->pdev = dev;
 	if(create_channel(lp)) goto error;
	dev_set_drvdata(dev, lp);
	return 0;

error:
	kfree(lp);
	dev_set_drvdata(dev, NULL);
	return rc;
}

static int axiDma_remove(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct axiDma_local *lp = dev_get_drvdata(dev);
	if(lp != NULL){
		if(lp->tx_chan != NULL) { dmaengine_terminate_all(lp->tx_chan); dma_release_channel(lp->tx_chan);}
		if(lp->rx_chan != NULL) { dmaengine_terminate_all(lp->rx_chan); dma_release_channel(lp->rx_chan);}
		cdevice_exit(lp);
		kfree(lp);
	}
	dev_set_drvdata(dev, NULL);
	return 0;
}


static int release(struct inode *ino, struct file *file)
{
	struct axiDma_local *pstdrv = (struct axiDma_local *)file->private_data;
	if (pstdrv->tx_virt_addr) {
		kfree(pstdrv->tx_virt_addr);
		pstdrv->tx_virt_addr = NULL;
	}
	if (pstdrv->rx_virt_addr) {
		kfree(pstdrv->rx_virt_addr);
		pstdrv->rx_virt_addr = NULL;
	}
	
	return 0;
}

static int mmap(struct file *file_p, struct vm_area_struct *vma)
{
		vma->vm_flags |= VM_IO;
		vma->vm_flags |= VM_DONTEXPAND;
		vma->vm_flags |= VM_DONTDUMP;
		vma->vm_flags |= VM_LOCKED;
		vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);

		if(remap_pfn_range(vma,vma->vm_start,vma->vm_pgoff,vma->vm_end - vma->vm_start,vma->vm_page_prot))
		{
			printk("mem_mmap failed\n");
			return -EAGAIN;
		}
		else
		{
			printk("mem_mmap sucessed\n");
			printk("%s,pgoff = %lx,start = %lx,end = %lx\n",__func__,vma->vm_pgoff,vma->vm_start,vma->vm_end);
		}
		return 0;

}

static int cdevice_init(struct axiDma_local * pData)
{
	printk("cdev_init starting\n");
	int rc;
	char device_name[32] = "axiDmaPoxy";
	static struct class *local_class_p = NULL;

	rc = alloc_chrdev_region(&pData->dev_node,0,1,"axiDma");
	if(rc)
	{
		dev_err(pData->pdev,"unable to get a char device number\n");
		return rc;
	}

	cdev_init(&pData->cdev,&drv_fops);
	pData->cdev.owner = THIS_MODULE;
	rc = cdev_add(&pData->cdev, pData->dev_node, 1);

	if(rc)
	{
		dev_err(pData->pdev, "unable to add char device\n");
		goto init_error1;
	}

	if(!local_class_p)
	{
		local_class_p = class_create(THIS_MODULE, DRIVER_NAME);

		if(IS_ERR(local_class_p)) 
		{
			dev_err(pData->pdev, "unable to create class\n");
			rc = -1;
			goto init_error2;
		}
	}
	pData->class_p = local_class_p;

	pData->dev = device_create(pData->class_p, NULL,pData->dev_node, NULL, device_name);

	if(IS_ERR(pData->dev)) 
	{
		dev_err(pData->pdev, "unable to create the device\n");
		goto init_error3;
	}

	return 0;

init_error3:
	class_destroy(pData->class_p);

init_error2:
	cdev_del(&pData->cdev);

init_error1:
	unregister_chrdev_region(pData->dev_node, 1);
	return rc;
}

static void cdevice_exit(struct axiDma_local *pstDrv)
{

	device_destroy(pstDrv->class_p,pstDrv->dev_node);
	class_destroy(pstDrv->class_p);
	cdev_del(&pstDrv->cdev);
	unregister_chrdev_region(pstDrv->dev_node,1);

	return;
}
static void sync_rx_callback(void *completion)
{
//	printk("RX transfer sucessed\n");
	complete(completion);
	return;
}

static void sync_tx_callback(void *completion)
{
//	printk("TX transfer sucessed\n");
	complete(completion);
	return;
}


static int local_open(struct inode *ino, struct file *file)
{
	//printk("file open sucessed\n");
	struct axiDma_local *pstdrv;
	struct xilinx_vdma_config config;
	file->private_data = container_of(ino->i_cdev, struct axiDma_local, cdev);
	pstdrv = (struct axiDma_local *)file->private_data;

	pstdrv->tx_virt_addr = kmalloc(BUF_SIZE, GFP_KERNEL);
	if (!pstdrv->tx_virt_addr) return -1;
	
	pstdrv->rx_virt_addr = kmalloc(BUF_SIZE, GFP_KERNEL);
	if (!pstdrv->rx_virt_addr) return -1;
	//dmaengine_terminate_all(pstdrv->tx_chan);
	//dmaengine_terminate_all(pstdrv->rx_chan);
#if 1
	/* reset */	
	memset(&config,0,sizeof(struct xilinx_vdma_config));

	//config.reset = 1;
	//if(xilinx_vdma_channel_set_config(pstdrv->tx_chan, (struct xilinx_vdma_config *)&config)>0) printk("tx reset fail\n");
	//config.reset = 1;
	//if(xilinx_vdma_channel_set_config(pstdrv->rx_chan, (struct xilinx_vdma_config *)&config) > 0) printk("rx reset fail\n");
	
	
	/* Only one interrupt */	
		/*config.reset = 0;
		config.coalesc = 1;
		config.delay = 0;
		xilinx_vdma_channel_set_config(pstdrv->tx_chan, (struct xilinx_vdma_config *)&config);*/


	/* Only one interrupt */	
		/*config.reset = 0;
		config.coalesc = 1;
		config.delay = 0;
	xilinx_vdma_channel_set_config(pstdrv->rx_chan, (struct xilinx_vdma_config *)&config);*/
#endif	
	
	
	return 0;
}


/*char driver*/

static long ioctl(struct file *file, unsigned int unused,unsigned long arg)
{
	//printk("ioctl-- start\n");

	struct axiDma_local *pstdrv = (struct axiDma_local *)file->private_data;
	int len;
	//struct xilinx_vdma_config config;
	
	copy_from_user(&len, arg, sizeof(int));
	
	if(DMA_START_RD_COMMAND == unused)//read
	{	
		struct dma_device *rx_dev = pstdrv->rx_chan->device;
		enum dma_ctrl_flags flags;
		struct dma_async_tx_descriptor *rxd = NULL;
		struct scatterlist rx_sg;
		
		flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
		pstdrv->rx_dma_addr = dma_map_single(rx_dev->dev, pstdrv->rx_virt_addr, len, DMA_DEV_TO_MEM);
		dma_unmap_single(rx_dev->dev, pstdrv->rx_dma_addr, len, DMA_DEV_TO_MEM);
		pstdrv->rx_dma_addr = dma_map_single(rx_dev->dev, pstdrv->rx_virt_addr, len, DMA_DEV_TO_MEM);
		
		sg_init_table(&rx_sg, 1);
		sg_dma_address(&rx_sg) = pstdrv->rx_dma_addr;
		sg_dma_len(&rx_sg) = len;

		

		rxd = rx_dev->device_prep_slave_sg(pstdrv->rx_chan, &rx_sg, 1, DMA_DEV_TO_MEM, flags, NULL);
		
	
		//rxd = dmaengine_prep_slave_single(pstdrv->rx_chan, pstdrv->rx_dma_addr, len, DMA_DEV_TO_MEM, flags);
		if (!rxd) {
			printk("RDcommand rx device_prep_slave_sg error\n");
			dma_unmap_single(rx_dev->dev, pstdrv->rx_dma_addr, len, DMA_DEV_TO_MEM);
			return -1;
		}
	
		init_completion(&pstdrv->rx_cmp);
		rxd->callback = sync_rx_callback;
		rxd->callback_param = &pstdrv->rx_cmp;
		pstdrv->rx_cookie = rxd->tx_submit(rxd);
	
	
		if (dma_submit_error(pstdrv->rx_cookie) ) {
			printk("RDcommand rx tx_submit error\n");
			dma_unmap_single(rx_dev->dev, pstdrv->rx_dma_addr, len, DMA_DEV_TO_MEM);
			return -2;
		}
	
		dma_async_issue_pending(pstdrv->rx_chan);
	}else if(DMA_START_WR_COMMAND == unused){//write
		struct dma_device *tx_dev = pstdrv->tx_chan->device;
		enum dma_ctrl_flags flags;
		struct dma_async_tx_descriptor *txd = NULL;
		struct scatterlist tx_sg;
		
		flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
		pstdrv->tx_dma_addr = dma_map_single(tx_dev->dev, pstdrv->tx_virt_addr, len, DMA_MEM_TO_DEV);
		
		sg_init_table(&tx_sg, 1);
		sg_dma_address(&tx_sg) = pstdrv->tx_dma_addr;
		sg_dma_len(&tx_sg) = len;
				
		
		txd = tx_dev->device_prep_slave_sg(pstdrv->tx_chan, &tx_sg, 1, DMA_MEM_TO_DEV, flags, NULL);
		
	
		//txd = dmaengine_prep_slave_single(pstdrv->tx_chan, pstdrv->tx_dma_addr, len, DMA_MEM_TO_DEV, flags);
		if (!txd) {
			printk("tx device_prep_slave_sg error\n");
			dma_unmap_single(tx_dev->dev, pstdrv->tx_dma_addr, len, DMA_MEM_TO_DEV);
			return -1;
		}
	
		init_completion(&pstdrv->tx_cmp);
		txd->callback = sync_tx_callback;
		txd->callback_param = &pstdrv->tx_cmp;
		pstdrv->tx_cookie = txd->tx_submit(txd);
	
	
		if (dma_submit_error(pstdrv->tx_cookie) ) {
			printk("tx tx_submit error\n");
			dma_unmap_single(tx_dev->dev, pstdrv->tx_dma_addr, len, DMA_MEM_TO_DEV);
			return -2;
		}
	
		dma_async_issue_pending(pstdrv->tx_chan);
	}else if(DMA_WAIT_WR_FINISH_COMMAND == unused){//write
		enum dma_status status;
		pstdrv->tx_tmo =	msecs_to_jiffies(5000); /* TX takes longer */

		pstdrv->tx_tmo = wait_for_completion_timeout(&pstdrv->tx_cmp, pstdrv->tx_tmo);
		status = dma_async_is_tx_complete(pstdrv->tx_chan, pstdrv->tx_cookie, NULL, NULL);

		if (pstdrv->tx_tmo == 0) {
			printk("Tx chan timeout \n");
			/* Unmap by myself */
			dma_unmap_single(pstdrv->tx_chan->device->dev, pstdrv->tx_dma_addr, len,	DMA_MEM_TO_DEV);
			dmaengine_terminate_all(pstdrv->tx_chan);
			return -1;
		} else if (status != DMA_COMPLETE) {
			printk("eer:Tx chan NO COMPLETE, status:%x \n",status);
			dma_unmap_single(pstdrv->tx_chan->device->dev, pstdrv->tx_dma_addr, len,	DMA_MEM_TO_DEV);
			dmaengine_terminate_all(pstdrv->tx_chan);
			return -2;
		}
		dmaengine_terminate_all(pstdrv->tx_chan);
		/* Unmap by myself */
		dma_unmap_single(pstdrv->tx_chan->device->dev, pstdrv->tx_dma_addr, len,	DMA_MEM_TO_DEV);
	}else if(DMA_WAIT_RD_FINISH_COMMAND == unused){
		enum dma_status status;
		pstdrv->rx_tmo = msecs_to_jiffies(100); /* RX takes longer */

		pstdrv->rx_tmo = wait_for_completion_timeout(&pstdrv->rx_cmp, pstdrv->rx_tmo);
		status = dma_async_is_tx_complete(pstdrv->rx_chan, pstdrv->rx_cookie, NULL, NULL);

		if (pstdrv->rx_tmo == 0) {
			printk("DMA_WAIT_RD Rx chan timeout \n");
			/* Unmap by myself */
			dma_unmap_single(pstdrv->rx_chan->device->dev, pstdrv->rx_dma_addr, len,	DMA_DEV_TO_MEM);
			return -1;
		} else if (status != DMA_COMPLETE) {
			printk("eer:DMA_WAIT_RD Rx chan NO COMPLETE, status:%x \n",status);
			dma_unmap_single(pstdrv->rx_chan->device->dev, pstdrv->rx_dma_addr, len,	DMA_DEV_TO_MEM);
			return -2;
		}
		/* Unmap by myself */
		dma_unmap_single(pstdrv->rx_chan->device->dev, pstdrv->rx_dma_addr, len,	DMA_DEV_TO_MEM);
	}else{
		printk("DMA cmd error:%d\n",unused);
	}

	return 0;
}

#if 0
static int local_read(struct file *file, char __user * buf, size_t len, loff_t *off)
{
	struct axiDma_local *pstdrv = (struct axiDma_local *)file->private_data;
	enum dma_status status;
	unsigned long rx_tmo =	msecs_to_jiffies(3000); /* RX takes longer */
	rx_tmo = wait_for_completion_timeout(&pstdrv->rx_cmp, rx_tmo);
	status = dma_async_is_tx_complete(pstdrv->rx_chan, pstdrv->rx_cookie, NULL, NULL);
	
	if (rx_tmo == 0) {
		printk("Rx chan timeout \n");
		goto err1;
	} else if (status != DMA_COMPLETE) {
		printk("eer:Rx chan NO COMPLETE, status:%x \n",status);
		goto err1;
	}
	/* Unmap by myself */
	dma_unmap_single(pstdrv->rx_chan->device->dev, pstdrv->rx_dma_addr, len, DMA_DEV_TO_MEM);
	copy_to_user(buf, pstdrv->rx_virt_addr, len);
	return 0;
err1:
	/* Unmap by myself */
	dma_unmap_single(pstdrv->rx_chan->device->dev, pstdrv->rx_dma_addr, len, DMA_DEV_TO_MEM);
	return -1;
}
#endif

static int local_read(struct file *file, char __user * buf, size_t len, loff_t *off)
{
	struct axiDma_local *pstdrv = (struct axiDma_local *)file->private_data;
	dmaengine_terminate_all(pstdrv->rx_chan);
	/* Unmap by myself */
	dma_unmap_single(pstdrv->rx_chan->device->dev, pstdrv->rx_dma_addr, len, DMA_DEV_TO_MEM);
	ssize_t ret = copy_to_user(buf, pstdrv->rx_virt_addr, len) ;
	return ret;
	

}

static int local_write(struct file *file, const char __user *buf, size_t len, loff_t *off)
{
	struct axiDma_local *pstdrv = (struct axiDma_local *)file->private_data;
	if(len > BUF_SIZE) return -1;
	dma_unmap_single(pstdrv->tx_chan->device->dev, pstdrv->tx_dma_addr, len, DMA_MEM_TO_DEV);
	copy_from_user(pstdrv->tx_virt_addr, buf, len);	
	return len;
}


static int create_channel(struct axiDma_local *pstDrv)
{
	struct dma_chan *tx_chan, *rx_chan;
	int err;
/*
	tx_chan = dma_request_slave_channel(pstDrv->pdev, "axidma0");
	if (IS_ERR(tx_chan)) {
		pr_err("xilinx_axiDma: No Tx channel\n");
		return PTR_ERR(tx_chan);
	}
	pstDrv->tx_chan = tx_chan;

	dev_info(pstDrv->pdev, "Found DMA Channel TX device %s\n", dma_chan_name(pstDrv->tx_chan));
*/

	rx_chan = dma_request_slave_channel(pstDrv->pdev, "axidma0");
	if (IS_ERR(rx_chan)) {
		err = PTR_ERR(rx_chan);
		pr_err("DMA Driver DMA xilinx_axiDma: No Rx channel\n");
		;//goto free_tx;
	}
	
	pstDrv->rx_chan = rx_chan;
	dev_info(pstDrv->pdev, "Found DMA Channel RX device %s\n", dma_chan_name(pstDrv->rx_chan));
	if(cdevice_init(pstDrv))
		goto free_rx;
	return 0;
	
free_rx:
		dma_release_channel(rx_chan);
/*free_tx:
		dma_release_channel(tx_chan);*/

	return err;
}







#ifdef CONFIG_OF
static struct of_device_id axiDma_of_match[] = {
	{ .compatible = "xlnx,plaxidma-1.00.a", },
	{ /* end of list */ },
};
MODULE_DEVICE_TABLE(of, axiDma_of_match);
#else
# define axiDma_of_match
#endif


static struct platform_driver axiDma_driver = {
	.driver = {
		.name = DRIVER_NAME,
		.owner = THIS_MODULE,
		.of_match_table	= axiDma_of_match,
	},
	.probe		= axiDma_probe,
	.remove		= axiDma_remove,
};

static int __init axiDma_init(void)
{
	printk("<1>axiDma init.\n");
	printk("<1>Module parameters were (0x%08x) and \"%s\"\n", myint,
	       mystr);

	return platform_driver_register(&axiDma_driver);
}


static void __exit axiDma_exit(void)
{
	platform_driver_unregister(&axiDma_driver);
	printk(KERN_ALERT "axiDma exit.\n");
}

module_init(axiDma_init);
module_exit(axiDma_exit);

更改后代码: 版本1:老师傅编辑

/**
 * Copyright (C) 2021 Xilinx, Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License"). You may
 * not use this file except in compliance with the License. A copy of the
 * License is located at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
 /* This header file is shared between the DMA Proxy test application and the DMA Proxy device driver. It defines the
 * shared interface to allow DMA transfers to be done from user space.
 *
 * A set of channel buffers are created by the driver for the transmit and receive channel. The application may choose
 * to use only a subset of the channel buffers to allow prioritization of transmit vs receive.
 *
 * Note: the buffer in the data structure should be 1st in the channel interface so that the buffer is cached aligned,
 * otherwise there may be issues when using cached memory.
 */

#define BUFFER_SIZE (128 * 1024)	 	/* must match driver exactly */
#define BUFFER_COUNT 32					/* driver only */

#define TX_BUFFER_COUNT 	1				/* app only, must be <= to the number in the driver */
#define RX_BUFFER_COUNT 	32				/* app only, must be <= to the number in the driver */
#define BUFFER_INCREMENT	1				/* normally 1, but skipping buffers (2) defeats prefetching in the CPU */
/**
 * Copyright (C) 2021 Xilinx, Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License"). You may
 * not use this file except in compliance with the License. A copy of the
 * License is located at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

/* DMA Proxy
 *
 * This module is designed to be a small example of a DMA device driver that is
 * a client to the DMA Engine using the AXI DMA / MCDMA driver. It serves as a proxy
 * for kernel space DMA control to a user space application.
 *
 * A zero copy scheme is provided by allowing user space to mmap a kernel allocated
 * memory region into user space, referred to as a set of channel buffers. Ioctl functions 
 * are provided to start a DMA transfer (non-blocking), finish a DMA transfer (blocking) 
 * previously started, or start and finish a DMA transfer blocking until it is complete.
 * An input argument which specifies a channel buffer number (0 - N) to be used for the
 * transfer is required.
 *
 * By default the kernel memory allocated for user space mapping is going to be 
 * non-cached at this time. Non-cached memory is pretty slow for the application.
 * A h/w coherent system for MPSOC has been tested and is recommended for higher
 * performance applications. 
 *
 * Hardware coherency requires the following items in the system as documented on the 
 * Xilinx wiki and summarized below::
 *   The AXI DMA read and write channels AXI signals must be tied to the correct state to
 *    generate coherent transactions.
 *   An HPC slave port on MPSOC is required
 *   The CCI of MPSOC must be initialized prior to the APU booting Linux
 *   A dma-coherent property is added in the device tree for the proxy driver.
 *
 * There is an associated user space application, dma_proxy_test.c, and dma_proxy.h
 * that works with this device driver.
 *
 * The hardware design was tested with an AXI DMA / MCDMA  with scatter gather and
 * with the transmit channel looped back to the receive channel. It should
 * work with or without scatter gather as the scatter gather mentioned in the 
 * driver is only at the s/w framework level rather than in the hw.
 *
 * This driver is character driver which creates devices that user space can
 * access for each DMA channel, such as /dev/dma_proxy_rx and /dev/dma_proxy_tx.
 * The number and names of channels are taken from the device tree.
 * Multiple instances of the driver (with multiple IPs) are also supported.

 * An internal test mode is provided to allow it to be self testing without the 
 * need for a user space application and this mode is good for making bigger
 * changes to this driver.
 *
 * This driver is designed to be simple to help users get familiar with how to 
 * use the DMA driver provided by Xilinx which uses the Linux DMA Engine. 
 *
 * To use this driver a node must be added into the device tree.  Add a 
 * node similar to the examples below adjusting the dmas property to match the
 * name of the AXI DMA / MCDMA node.
 * 
 * The dmas property contains pairs with the first of each pair being a reference
 * to the DMA IP in the device tree and the second of each pair being the
 * channel of the DMA IP. For the AXI DMA IP the transmit channel is always 0 and
 * the receive is always 1. For the AXI MCDMA IP the 1st transmit channel is
 * always 0 and receive channels start at 16 since there can be a maximum of 16
 * transmit channels. Each name in the dma-names corresponds to a pair in the dmas
 * property and is only a logical name that allows user space access to the channel
 * such that the name can be any name as long as it is unique.
 *
 *	For h/w coherent systems with MPSoC, the property dma-coherent can be added
 * to the node in the device tree. 
 * 
 * Example device tree nodes: 
 *
 * For AXI DMA with transmit and receive channels with a loopback in hardware
 * 
 * dma_proxy {
 *   compatible ="xlnx,dma_proxy";
 *   dmas = <&axi_dma_1_loopback 0  &axi_dma_1_loopback 1>;
 *   dma-names = "dma_proxy_tx", "dma_proxy_rx";
 * };
 *
 * For AXI DMA with only the receive channel
 * 
 * dma_proxy2 {
 *   compatible ="xlnx,dma_proxy";
 *   dmas = <&axi_dma_0_noloopback 1>;
 *   dma-names = "dma_proxy_rx_only";
 * };
 *
 * For AXI MCDMA with two channels 
 *
 * dma_proxy3 {
 *   compatible ="xlnx,dma_proxy";
 *   dmas = <&axi_mcdma_0 0  &axi_mcdma_0 16 &axi_mcdma_0 1 &axi_mcdma_0 17> ;
 *   dma-names = "dma_proxy_tx_0", "dma_proxy_rx_0", "dma_proxy_tx_1", "dma_proxy_rx_1";
 * };
 */

#include <linux/dmaengine.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/workqueue.h>
#include <linux/platform_device.h>
#include <linux/of_dma.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>

#include "dma-proxy.h"

MODULE_LICENSE("GPL");

#define DRIVER_NAME 			"axiDma"
#define TX_CHANNEL			0
#define RX_CHANNEL			1
#define ERROR 					-1
#define TEST_SIZE 			1024

/* The following module parameter controls if the internal test runs when the module is inserted.
 * Note that this test requires a transmit and receive channel to function and uses the first
 * transmit and receive channnels when multiple channels exist.
 */
static unsigned internal_test = 0;
module_param(internal_test, int, S_IRUGO);

/* The following data structures represent a single channel of DMA, transmit or receive in the case
 * when using AXI DMA.  It contains all the data to be maintained for the channel.
 */
struct proxy_bd {
	struct completion cmp;
	dma_cookie_t cookie;
	dma_addr_t dma_handle;
	struct scatterlist sglist;
};
struct dma_proxy_channel {
	struct channel_buffer *buffer_table_p;	/* user to kernel space interface */
	dma_addr_t buffer_phys_addr;

	struct device *proxy_device_p;				/* character device support */
	struct device *dma_device_p;
	dev_t dev_node;
	struct cdev cdev;
	struct class *class_p;

	struct proxy_bd bdtable[BUFFER_COUNT];

	struct dma_chan *channel_p;				/* dma support */
	u32 direction;						/* DMA_MEM_TO_DEV or DMA_DEV_TO_MEM */
	int bdindex;
};

struct dma_proxy {
	int channel_count;
	struct dma_proxy_channel *channels;
	char **names;
	struct work_struct work;
};

static int total_count;

/* Handle a callback and indicate the DMA transfer is complete to another
 * thread of control
 */
static void sync_callback(void *completion)
{
	/* Indicate the DMA transaction completed to allow the other
	 * thread of control to finish processing
	 */
	complete(completion);
}

/* Prepare a DMA buffer to be used in a DMA transaction, submit it to the DMA engine
 * to ibe queued and return a cookie that can be used to track that status of the
 * transaction
 */
static void start_transfer(struct dma_proxy_channel *pchannel_p)
{
	enum dma_ctrl_flags flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
	struct dma_async_tx_descriptor *chan_desc;
	struct dma_device *dma_device = pchannel_p->channel_p->device;
	int bdindex = pchannel_p->bdindex;

	/* A single entry scatter gather list is used as it's not clear how to do it with a simpler method.
	 * Get a descriptor for the transfer ready to submit
	 */
	sg_init_table(&pchannel_p->bdtable[bdindex].sglist, 1);
	sg_dma_address(&pchannel_p->bdtable[bdindex].sglist) = pchannel_p->bdtable[bdindex].dma_handle;
	sg_dma_len(&pchannel_p->bdtable[bdindex].sglist) = pchannel_p->buffer_table_p[bdindex].length;

	chan_desc = dma_device->device_prep_slave_sg(pchannel_p->channel_p, &pchannel_p->bdtable[bdindex].sglist, 1, 
						pchannel_p->direction, flags, NULL);

	if (!chan_desc) {
		printk(KERN_ERR "dmaengine_prep*() error\n");
	} else {
		chan_desc->callback = sync_callback;
		chan_desc->callback_param = &pchannel_p->bdtable[bdindex].cmp;

		/* Initialize the completion for the transfer and before using it
		 * then submit the transaction to the DMA engine so that it's queued
		 * up to be processed later and get a cookie to track it's status
		 */
		init_completion(&pchannel_p->bdtable[bdindex].cmp);

		pchannel_p->bdtable[bdindex].cookie = dmaengine_submit(chan_desc);
		if (dma_submit_error(pchannel_p->bdtable[bdindex].cookie)) {
			printk("Submit error\n");
	 		return;
		}

		/* Start the DMA transaction which was previously queued up in the DMA engine
		 */
		dma_async_issue_pending(pchannel_p->channel_p);
	}
}

/* Wait for a DMA transfer that was previously submitted to the DMA engine
 */
static void wait_for_transfer(struct dma_proxy_channel *pchannel_p)
{
	unsigned long timeout = msecs_to_jiffies(3000);
	enum dma_status status;
	int bdindex = pchannel_p->bdindex;

	pchannel_p->buffer_table_p[bdindex].status = PROXY_BUSY;

	/* Wait for the transaction to complete, or timeout, or get an error
	 */
	timeout = wait_for_completion_timeout(&pchannel_p->bdtable[bdindex].cmp, timeout);
	status = dma_async_is_tx_complete(pchannel_p->channel_p, pchannel_p->bdtable[bdindex].cookie, NULL, NULL);

	if (timeout == 0)  {
		pchannel_p->buffer_table_p[bdindex].status  = PROXY_TIMEOUT;
		printk(KERN_ERR "DMA timed out\n");
	} else if (status != DMA_COMPLETE) {
		pchannel_p->buffer_table_p[bdindex].status = PROXY_ERROR;
		printk(KERN_ERR "DMA returned completion callback status of: %s\n",
			   status == DMA_ERROR ? "error" : "in progress");
	} else
		pchannel_p->buffer_table_p[bdindex].status = PROXY_NO_ERROR;
}

/* The following functions are designed to test the driver from within the device
 * driver without any user space. It uses the first channel buffer for the transmit and receive.
 * If this works but the user application does not then the user application is at fault.
 */
static void tx_test(struct work_struct *local_work)
{
	struct dma_proxy *lp;
	lp = container_of(local_work, struct dma_proxy, work);

	/* Use the 1st buffer for the test
	 */
	lp->channels[TX_CHANNEL].buffer_table_p[0].length = TEST_SIZE;
	lp->channels[TX_CHANNEL].bdindex = 0;

	start_transfer(&lp->channels[TX_CHANNEL]);
	wait_for_transfer(&lp->channels[TX_CHANNEL]);
}

static void test(struct dma_proxy *lp)
{
	int i;

	printk("Starting internal test\n");

	/* Initialize the buffers for the test
	 */
	for (i = 0; i < TEST_SIZE / sizeof(unsigned int); i++) {
		lp->channels[TX_CHANNEL].buffer_table_p[0].buffer[i] = i;
		lp->channels[RX_CHANNEL].buffer_table_p[0].buffer[i] = 0;
	}

	/* Since the transfer function is blocking the transmit channel is started from a worker
	 * thread
	 */
	INIT_WORK(&lp->work, tx_test);
	schedule_work(&lp->work);

	/* Receive the data that was just sent and looped back
	 */
	lp->channels[RX_CHANNEL].buffer_table_p->length = TEST_SIZE;
	lp->channels[TX_CHANNEL].bdindex = 0;

	start_transfer(&lp->channels[RX_CHANNEL]);
	wait_for_transfer(&lp->channels[RX_CHANNEL]);

	/* Verify the receiver buffer matches the transmit buffer to
	 * verify the transfer was good
	 */
	for (i = 0; i < TEST_SIZE / sizeof(unsigned int); i++)
		if (lp->channels[TX_CHANNEL].buffer_table_p[0].buffer[i] !=
			lp->channels[RX_CHANNEL].buffer_table_p[0].buffer[i]) {
			printk("buffers not equal, first index = %d\n", i);
			break;
		}

	printk("Internal test complete\n");
}

/* Map the memory for the channel interface into user space such that user space can
 * access it using coherent memory which will be non-cached for s/w coherent systems
 * such as Zynq 7K or the current default for Zynq MPSOC. MPSOC can be h/w coherent
 * when set up and then the memory will be cached.
 */
static int mmap(struct file *file_p, struct vm_area_struct *vma)
{
	struct dma_proxy_channel *pchannel_p = (struct dma_proxy_channel *)file_p->private_data;

	return dma_mmap_coherent(pchannel_p->dma_device_p, vma,
					   pchannel_p->buffer_table_p, pchannel_p->buffer_phys_addr,
					   vma->vm_end - vma->vm_start);
}

/* Open the device file and set up the data pointer to the proxy channel data for the
 * proxy channel such that the ioctl function can access the data structure later.
 */
static int local_open(struct inode *ino, struct file *file)
{
	file->private_data = container_of(ino->i_cdev, struct dma_proxy_channel, cdev);

	return 0;
}

/* Close the file and there's nothing to do for it
 */
static int release(struct inode *ino, struct file *file)
{
#if 0
	struct dma_proxy_channel *pchannel_p = (struct dma_proxy_channel *)file->private_data;
	struct dma_device *dma_device = pchannel_p->channel_p->device;

	/* Stop all the activity when the channel is closed assuming this
	 * may help if the application is aborted without normal closure
	 * This is not working and causes an issue that may need investigation in the 
	 * DMA driver at the lower level.
	 */
	dma_device->device_terminate_all(pchannel_p->channel_p);
#endif
	return 0;
}

/* Perform I/O control to perform a DMA transfer using the input as an index
 * into the buffer descriptor table such that the application is in control of
 * which buffer to use for the transfer.The BD in this case is only a s/w
 * structure for the proxy driver, not related to the hw BD of the DMA.
 */
static long ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	struct dma_proxy_channel *pchannel_p = (struct dma_proxy_channel *)file->private_data;

	/* Get the bd index from the input argument as all commands require it
	 */
	if(copy_from_user(&pchannel_p->bdindex, (int *)arg, sizeof(pchannel_p->bdindex)))
		return -EINVAL;

	/* Perform the DMA transfer on the specified channel blocking til it completes
	 */
	switch(cmd) {
		case START_XFER:
			start_transfer(pchannel_p);
			break;
		case FINISH_XFER:
			wait_for_transfer(pchannel_p);
			break;
		case XFER:
			start_transfer(pchannel_p);
			wait_for_transfer(pchannel_p);
			break;
	}

	return 0;
}

static int local_read(struct file *file, char __user * buf, size_t len, loff_t *off)
{
        struct dma_proxy_channel *pchannel_p = (struct dma_proxy_channel *)file->private_data;
        dmaengine_terminate_all(pchannel_p->channel_p);
        ssize_t ret = copy_to_user(buf, pchannel_p->buffer_table_p, len) ;
        return ret;


}

static int local_write(struct file *file, const char __user *buf, size_t len, loff_t *off)
{
        struct dma_proxy_channel *pchannel_p = (struct dma_proxy_channel *)file->private_data;
        if(len > BUFFER_SIZE) return -1;
        copy_from_user(pchannel_p->buffer_table_p, buf, len);
        return len;
}

static struct file_operations dm_fops = {
	.owner    = THIS_MODULE,
	.open     = local_open,
	.release  = release,
	.unlocked_ioctl = ioctl,
	.mmap	= mmap,
        .read = local_read,
        .write = local_write,
};


/* Initialize the driver to be a character device such that is responds to
 * file operations.
 */
static int cdevice_init(struct dma_proxy_channel *pchannel_p, char *name)
{
	int rc;
	//char device_name[32] = "dma_proxy";
        char device_name[32] = "axiDmaPoxy"; 
	static struct class *local_class_p = NULL;

	/* Allocate a character device from the kernel for this driver.
	 */
	rc = alloc_chrdev_region(&pchannel_p->dev_node, 0, 1, DRIVER_NAME);

	if (rc) {
		dev_err(pchannel_p->dma_device_p, "unable to get a char device number\n");
		return rc;
	}

	/* Initialize the device data structure before registering the character 
	 * device with the kernel.
	 */
	cdev_init(&pchannel_p->cdev, &dm_fops);
	pchannel_p->cdev.owner = THIS_MODULE;
	rc = cdev_add(&pchannel_p->cdev, pchannel_p->dev_node, 1);

	if (rc) {
		dev_err(pchannel_p->dma_device_p, "unable to add char device\n");
		goto init_error1;
	}

	/* Only one class in sysfs is to be created for multiple channels,
	 * create the device in sysfs which will allow the device node
	 * in /dev to be created
	 */
	if (!local_class_p) {
		local_class_p = class_create(THIS_MODULE, DRIVER_NAME);

		if (IS_ERR(pchannel_p->dma_device_p->class)) {
			dev_err(pchannel_p->dma_device_p, "unable to create class\n");
			rc = ERROR;
			goto init_error2;
		}
	}
	pchannel_p->class_p = local_class_p;

	/* Create the device node in /dev so the device is accessible
	 * as a character device
	 */
	//strcat(device_name, name);
	pchannel_p->proxy_device_p = device_create(pchannel_p->class_p, NULL,
					  	 pchannel_p->dev_node, NULL, device_name);

	if (IS_ERR(pchannel_p->proxy_device_p)) {
		dev_err(pchannel_p->dma_device_p, "unable to create the device\n");
		goto init_error3;
	}

	return 0;

init_error3:
	class_destroy(pchannel_p->class_p);

init_error2:
	cdev_del(&pchannel_p->cdev);

init_error1:
	unregister_chrdev_region(pchannel_p->dev_node, 1);
	return rc;
}

/* Exit the character device by freeing up the resources that it created and
 * disconnecting itself from the kernel.
 */
static void cdevice_exit(struct dma_proxy_channel *pchannel_p)
{
	/* Take everything down in the reverse order
	 * from how it was created for the char device
	 */
	if (pchannel_p->proxy_device_p) {
		device_destroy(pchannel_p->class_p, pchannel_p->dev_node);

		/* If this is the last channel then get rid of the /sys/class/dma_proxy
		 */
		if (total_count == 1)
			class_destroy(pchannel_p->class_p);

		cdev_del(&pchannel_p->cdev);
		unregister_chrdev_region(pchannel_p->dev_node, 1);
	}
}

/* Create a DMA channel by getting a DMA channel from the DMA Engine and then setting
 * up the channel as a character device to allow user space control.
 */
static int create_channel(struct platform_device *pdev, struct dma_proxy_channel *pchannel_p, char *name, u32 direction)
{
	int rc, bd;

	/* Request the DMA channel from the DMA engine and then use the device from
	 * the channel for the proxy channel also.
	 */
	pchannel_p->dma_device_p = &pdev->dev;
	pchannel_p->channel_p = dma_request_chan(&pdev->dev, name);
	if (!pchannel_p->channel_p) {
		dev_err(pchannel_p->dma_device_p, "DMA channel request error\n");
		return ERROR;
	}

	/* Initialize the character device for the dma proxy channel
	 */
	rc = cdevice_init(pchannel_p, name);
	if (rc) 
		return rc;

	pchannel_p->direction = direction;

	/* Allocate DMA memory that will be shared/mapped by user space, allocating
	 * a set of buffers for the channel with user space specifying which buffer
	 * to use for a tranfer..
	 */
	pchannel_p->buffer_table_p = (struct channel_buffer *)
		dmam_alloc_coherent(pchannel_p->dma_device_p,
					sizeof(struct channel_buffer) * BUFFER_COUNT,
					&pchannel_p->buffer_phys_addr, GFP_KERNEL);
	printk(KERN_INFO "Allocating memory, virtual address: %px physical address: %px\n",
			pchannel_p->buffer_table_p, (void *)pchannel_p->buffer_phys_addr);

	/* Initialize each entry in the buffer descriptor table such that the physical address	
	 * address of each buffer is ready to use later.
	 */
	for (bd = 0; bd < BUFFER_COUNT; bd++) 
		pchannel_p->bdtable[bd].dma_handle = (dma_addr_t)(pchannel_p->buffer_phys_addr + 
						(sizeof(struct channel_buffer) * bd) + offsetof(struct channel_buffer, buffer));

	/* The buffer descriptor index into the channel buffers should be specified in each 
	 * ioctl but we will initialize it to be safe.
	 */
	pchannel_p->bdindex = 0;
	if (!pchannel_p->buffer_table_p) {
		dev_err(pchannel_p->dma_device_p, "DMA allocation error\n");
		return ERROR;
	}
	return 0;
}
/* Initialize the dma proxy device driver module.
 */
static int dma_proxy_probe(struct platform_device *pdev)
{
	int rc, i;
	struct dma_proxy *lp;
	struct device *dev = &pdev->dev;

	printk(KERN_INFO "dma_proxy module initialized\n");
	
	lp = (struct dma_proxy *) devm_kmalloc(&pdev->dev, sizeof(struct dma_proxy), GFP_KERNEL);
	if (!lp) {
		dev_err(dev, "Cound not allocate proxy device\n");
		return -ENOMEM;
	}
	dev_set_drvdata(dev, lp);

	/* Figure out how many channels there are from the device tree based
	 * on the number of strings in the dma-names property
	 */
	lp->channel_count = device_property_read_string_array(&pdev->dev,
						 "dma-names", NULL, 0);
        lp->channel_count = 1;
	if (lp->channel_count <= 0)
		return 0;

	printk("Device Tree Channel Count: %d\r\n", lp->channel_count);

	/* Allocate the memory for channel names and then get the names
    * from the device tree
	 */
	lp->names = devm_kmalloc_array(&pdev->dev, lp->channel_count, 
			sizeof(char *), GFP_KERNEL);
	if (!lp->names)
		return -ENOMEM;

	rc = device_property_read_string_array(&pdev->dev, "dma-names", 
					(const char **)lp->names, lp->channel_count);
	if (rc < 0)
		return rc;
	
	/* Allocate the memory for the channels since the number is known.
	 */
	lp->channels = devm_kmalloc(&pdev->dev,
			sizeof(struct dma_proxy_channel) * lp->channel_count, GFP_KERNEL);
	if (!lp->channels)
		return -ENOMEM;

	/* Create the channels in the proxy. The direction does not matter
	 * as the DMA channel has it inside it and uses it, other than this will not work 
	 * for cyclic mode.
	 */
	for (i = 0; i < lp->channel_count; i++) {
		printk("Creating channel %s\r\n", lp->names[i]);
		rc = create_channel(pdev, &lp->channels[i], lp->names[i], DMA_MEM_TO_DEV);

		if (rc) 
			return rc;
		total_count++;
	}

	if (internal_test)
		test(lp);
	return 0;
}
 
/* Exit the dma proxy device driver module.
 */
static int dma_proxy_remove(struct platform_device *pdev)
{
	int i;
	struct device *dev = &pdev->dev;
	struct dma_proxy *lp = dev_get_drvdata(dev);

	printk(KERN_INFO "dma_proxy module exited\n");

	/* Take care of the char device infrastructure for each
	 * channel except for the last channel. Handle the last
	 * channel seperately.
	 */
	for (i = 0; i < lp->channel_count; i++) { 
		if (lp->channels[i].proxy_device_p)
			cdevice_exit(&lp->channels[i]);
		total_count--;
	}
	/* Take care of the DMA channels and any buffers allocated
	 * for the DMA transfers. The DMA buffers are using managed
	 * memory such that it's automatically done.
	 */
	for (i = 0; i < lp->channel_count; i++)
		if (lp->channels[i].channel_p) {
			lp->channels[i].channel_p->device->device_terminate_all(lp->channels[i].channel_p);
			dma_release_channel(lp->channels[i].channel_p);
		}
	return 0;
}

static const struct of_device_id dma_proxy_of_ids[] = {
	{ //.compatible = "xlnx,dma_proxy",
           .compatible = "xlnx,plaxidma-1.00.a",},
	{}
};

static struct platform_driver dma_proxy_driver = {
	.driver = {
		.name = DRIVER_NAME,
		.owner = THIS_MODULE,
		.of_match_table = dma_proxy_of_ids,
	},
	.probe = dma_proxy_probe,
	.remove = dma_proxy_remove,
};

static int __init dma_proxy_init(void)
{
	return platform_driver_register(&dma_proxy_driver);

}

static void __exit dma_proxy_exit(void)
{
	platform_driver_unregister(&dma_proxy_driver);
}

module_init(dma_proxy_init)
module_exit(dma_proxy_exit)

MODULE_AUTHOR("Xilinx, Inc.");
MODULE_DESCRIPTION("DMA Proxy Prototype");
MODULE_LICENSE("GPL v2");

版本2:自我修改 都可正常执行

/*  axiDma.c - The simplest kernel module.

* Copyright (C) 2013 - 2016 Xilinx, Inc
*
*   This program 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.

*   This program is distributed in the hope that it will be useful,
*   but WITHOUT ANY WARRANTY; without even the implied warranty of
*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*   GNU General Public License for more details.
*
*   You should have received a copy of the GNU General Public License along
*   with this program. If not, see <http://www.gnu.org/licenses/>.

MODULE_AUTHOR
    ("Xilinx Inc.");
MODULE_DESCRIPTION
    ("axiDma - loadable module template generated by petalinux-create -t modules");

#define DRIVER_NAME "axiDma"



/* Simple example of how to receive command line parameters to your module.
   Delete if you don't need them */




#include <linux/dmaengine.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/workqueue.h>
#include <linux/platform_device.h>
#include <linux/of_dma.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>

//#include "dma-proxy.h"

MODULE_LICENSE("GPL");

#define BUFFER_SIZE (256 * 1024)	 	/* must match driver exactly */
#define BUFFER_COUNT 32	// 32 -> 1				/* driver only */

#define TX_BUFFER_COUNT 	1				/* app only, must be <= to the number in the driver */
#define RX_BUFFER_COUNT 	32	//32 -> 1			/* app only, must be <= to the number in the driver */
#define BUFFER_INCREMENT	1				/* normally 1, but skipping buffers (2) defeats prefetching in the CPU */

#define FINISH_XFER 	_IOW('a','a',int32_t*)
#define START_XFER 		_IOW('a','b',int32_t*)
#define XFER 			_IOR('a','c',int32_t*)


#define DRIVER_NAME 			"axiDma"
#define TX_CHANNEL			0
#define RX_CHANNEL			1
#define ERROR 					-1
#define TEST_SIZE 			1024

struct channel_buffer {
	unsigned int buffer[BUFFER_SIZE / sizeof(unsigned int)];
	enum proxy_status { PROXY_NO_ERROR = 0, PROXY_BUSY = 1, PROXY_TIMEOUT = 2, PROXY_ERROR = 3 } status;
	unsigned int length;
} __attribute__ ((aligned (1024)));		/* 64 byte alignment required for DMA, but 1024 handy for viewing memory */

/* The following module parameter controls if the internal test runs when the module is inserted.
 * Note that this test requires a transmit and receive channel to function and uses the first
 * transmit and receive channnels when multiple channels exist.
 */
static unsigned internal_test = 0;
module_param(internal_test, int, S_IRUGO);

/* The following data structures represent a single channel of DMA, transmit or receive in the case
 * when using AXI DMA.  It contains all the data to be maintained for the channel.
 */
struct proxy_bd {
	struct completion cmp;
	dma_cookie_t cookie;
	dma_addr_t dma_handle;
	struct scatterlist sglist;
};
struct dma_proxy_channel {
	struct channel_buffer *buffer_table_p;	/* user to kernel space interface */
	dma_addr_t buffer_phys_addr;

	struct device *proxy_device_p;				/* character device support */
	struct device *dma_device_p;
	dev_t dev_node;
	struct cdev cdev;
	struct class *class_p;

	struct proxy_bd bdtable[BUFFER_COUNT];

	struct dma_chan *channel_p;				/* dma support */
	u32 direction;						/* DMA_MEM_TO_DEV or DMA_DEV_TO_MEM */
	int bdindex;
};

struct dma_proxy {
	int channel_count;
	struct dma_proxy_channel *channels;
	char **names;
	struct work_struct work;
};

static int total_count;

/* Handle a callback and indicate the DMA transfer is complete to another
 * thread of control
 */
static void sync_callback(void *completion)
{
	/* Indicate the DMA transaction completed to allow the other
	 * thread of control to finish processing
	 */
	complete(completion);
}

/* Prepare a DMA buffer to be used in a DMA transaction, submit it to the DMA engine
 * to ibe queued and return a cookie that can be used to track that status of the
 * transaction
 */
static void start_transfer(struct dma_proxy_channel *pchannel_p)
{
	enum dma_ctrl_flags flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
	struct dma_async_tx_descriptor *chan_desc;
	struct dma_device *dma_device = pchannel_p->channel_p->device;
	int bdindex = pchannel_p->bdindex;
        printk(" bdindex_start_transfer  %i \n", bdindex);
	/* A single entry scatter gather list is used as it's not clear how to do it with a simpler method.
	 * Get a descriptor for the transfer ready to submit
	 */
	sg_init_table(&pchannel_p->bdtable[bdindex].sglist, 1);
	sg_dma_address(&pchannel_p->bdtable[bdindex].sglist) = pchannel_p->bdtable[bdindex].dma_handle;
	sg_dma_len(&pchannel_p->bdtable[bdindex].sglist) = pchannel_p->buffer_table_p[bdindex].length;
  
	chan_desc = dma_device->device_prep_slave_sg(pchannel_p->channel_p, &pchannel_p->bdtable[bdindex].sglist, 1, 
						pchannel_p->direction, flags, NULL);
	if (!chan_desc) {
		printk(KERN_ERR "dmaengine_prep*() error\n");
	} else {
		chan_desc->callback = sync_callback;
		chan_desc->callback_param = &pchannel_p->bdtable[bdindex].cmp;

		/* Initialize the completion for the transfer and before using it
		 * then submit the transaction to the DMA engine so that it's queued
		 * up to be processed later and get a cookie to track it's status
		 */
		init_completion(&pchannel_p->bdtable[bdindex].cmp);
		pchannel_p->bdtable[bdindex].cookie = dmaengine_submit(chan_desc);
		if (dma_submit_error(pchannel_p->bdtable[bdindex].cookie)) {
			printk("Submit error\n");
	 		return;
		}

		/* Start the DMA transaction which was previously queued up in the DMA engine
		 */
		dma_async_issue_pending(pchannel_p->channel_p);
	}
}

/* Wait for a DMA transfer that was previously submitted to the DMA engine
 */
static void wait_for_transfer(struct dma_proxy_channel *pchannel_p)
{
	unsigned long timeout = msecs_to_jiffies(3000);
	enum dma_status status;
	int bdindex = pchannel_p->bdindex;
        printk(" bdindex_status_wait_transfer  %i \n", bdindex);
	pchannel_p->buffer_table_p[bdindex].status = PROXY_BUSY;

        printk(" status_wait_transfer  %i \n", pchannel_p->buffer_table_p[bdindex].status);
	/* Wait for the transaction to complete, or timeout, or get an error
	 */
	timeout = wait_for_completion_timeout(&pchannel_p->bdtable[bdindex].cmp, timeout);
	status = dma_async_is_tx_complete(pchannel_p->channel_p, pchannel_p->bdtable[bdindex].cookie, NULL, NULL);
        
	if (timeout == 0)  {
		pchannel_p->buffer_table_p[bdindex].status  = PROXY_TIMEOUT;
		printk(KERN_ERR "DMA timed out\n");
	} else if (status != DMA_COMPLETE) {
		pchannel_p->buffer_table_p[bdindex].status = PROXY_ERROR;
		printk(KERN_ERR "DMA returned completion callback status of: %s\n",
			   status == DMA_ERROR ? "error" : "in progress");
	} else
		pchannel_p->buffer_table_p[bdindex].status = PROXY_NO_ERROR;
                printk(" status_wait_transfer_time  %i \n", pchannel_p->buffer_table_p[bdindex].status);
}

/* The following functions are designed to test the driver from within the device
 * driver without any user space. It uses the first channel buffer for the transmit and receive.
 * If this works but the user application does not then the user application is at fault.
 */
static void tx_test(struct work_struct *local_work)
{
	struct dma_proxy *lp;
	lp = container_of(local_work, struct dma_proxy, work);

	/* Use the 1st buffer for the test
	 */
	lp->channels[TX_CHANNEL].buffer_table_p[0].length = TEST_SIZE;
        printk(" lp->channels[TX_CHANNEL].bdindex_tx_tes  %i \n", lp->channels[TX_CHANNEL].bdindex);
	lp->channels[TX_CHANNEL].bdindex = 0;

	start_transfer(&lp->channels[TX_CHANNEL]);
	wait_for_transfer(&lp->channels[TX_CHANNEL]);
}

static void test(struct dma_proxy *lp)
{
	int i;

	printk("Starting internal test\n");

	/* Initialize the buffers for the test
	 */
	for (i = 0; i < TEST_SIZE / sizeof(unsigned int); i++) {
		lp->channels[TX_CHANNEL].buffer_table_p[0].buffer[i] = i;
		lp->channels[RX_CHANNEL].buffer_table_p[0].buffer[i] = 0;
	}

	/* Since the transfer function is blocking the transmit channel is started from a worker
	 * thread
	 */
	INIT_WORK(&lp->work, tx_test);
	schedule_work(&lp->work);

	/* Receive the data that was just sent and looped back
	 */
	lp->channels[RX_CHANNEL].buffer_table_p->length = TEST_SIZE;
        printk(" lp->channels[TX_CHANNEL].bdindex_test  %i \n", lp->channels[TX_CHANNEL].bdindex);
	lp->channels[TX_CHANNEL].bdindex = 0;

	start_transfer(&lp->channels[RX_CHANNEL]);
	wait_for_transfer(&lp->channels[RX_CHANNEL]);

	/* Verify the receiver buffer matches the transmit buffer to
	 * verify the transfer was good
	 */
	for (i = 0; i < TEST_SIZE / sizeof(unsigned int); i++)
		if (lp->channels[TX_CHANNEL].buffer_table_p[0].buffer[i] !=
			lp->channels[RX_CHANNEL].buffer_table_p[0].buffer[i]) {
			printk("buffers not equal, first index = %d\n", i);
			break;
		}

	printk("Internal test complete\n");
}

/* Map the memory for the channel interface into user space such that user space can
 * access it using coherent memory which will be non-cached for s/w coherent systems
 * such as Zynq 7K or the current default for Zynq MPSOC. MPSOC can be h/w coherent
 * when set up and then the memory will be cached.
 */
static int mmap(struct file *file_p, struct vm_area_struct *vma)
{
	struct dma_proxy_channel *pchannel_p = (struct dma_proxy_channel *)file_p->private_data;
        printk(" status_wait_transfer_time  %i \n", pchannel_p->buffer_table_p[bdindex].status);
	return dma_mmap_coherent(pchannel_p->dma_device_p, vma,
					   pchannel_p->buffer_table_p, pchannel_p->buffer_phys_addr,
					   vma->vm_end - vma->vm_start);
}

/* Open the device file and set up the data pointer to the proxy channel data for the
 * proxy channel such that the ioctl function can access the data structure later.
 */
static int local_open(struct inode *ino, struct file *file)
{
	file->private_data = container_of(ino->i_cdev, struct dma_proxy_channel, cdev);
/*  struct axiDma_local *pstdrv;
	struct xilinx_vdma_config config;
	pstdrv = (struct axiDma_local *)file->private_data;
	pstdrv->tx_virt_addr = kmalloc(BUF_SIZE, GFP_KERNEL);
	if (!pstdrv->tx_virt_addr) return -1;
	pstdrv->rx_virt_addr = kmalloc(BUF_SIZE, GFP_KERNEL);
	if (!pstdrv->rx_virt_addr) return -1;
#if 1
	
	memset(&config,0,sizeof(struct xilinx_vdma_config));
*/
	return 0;
}

/* Close the file and there's nothing to do for it
 */
static int release(struct inode *ino, struct file *file)
{
#if 0
	struct dma_proxy_channel *pchannel_p = (struct dma_proxy_channel *)file->private_data;
	struct dma_device *dma_device = pchannel_p->channel_p->device;

	/* Stop all the activity when the channel is closed assuming this
	 * may help if the application is aborted without normal closure
	 * This is not working and causes an issue that may need investigation in the 
	 * DMA driver at the lower level.
	 */
	dma_device->device_terminate_all(pchannel_p->channel_p);
#endif
	return 0;
}

/* Perform I/O control to perform a DMA transfer using the input as an index
 * into the buffer descriptor table such that the application is in control of
 * which buffer to use for the transfer.The BD in this case is only a s/w
 * structure for the proxy driver, not related to the hw BD of the DMA.
 */
static long ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	struct dma_proxy_channel *pchannel_p = (struct dma_proxy_channel *)file->private_data;

	/* Get the bd index from the input argument as all commands require it
	 */
        printk("bdindex_ioctl1 = %u  \n", pchannel_p->bdindex);
        // printk("bdindex_init_arg = %u  \n",*(int *)arg);
	if(copy_from_user(&pchannel_p->bdindex, (int *)arg, sizeof(pchannel_p->bdindex)))
		return -EINVAL;
        printk("bdindex_ioctl2 = %u  \n", pchannel_p->bdindex);
        //printk("bdindex_next_arg = %u  \n",*(int *)arg);
	/* Perform the DMA transfer on the specified channel blocking til it completes
	 */
	switch(cmd) {
		case START_XFER:
			start_transfer(pchannel_p);
			break;
		case FINISH_XFER:
			wait_for_transfer(pchannel_p);
			break;
		case XFER:
			start_transfer(pchannel_p);
			wait_for_transfer(pchannel_p);
			break;
	}

	return 0;
}

static struct file_operations dm_fops = {
	.owner    = THIS_MODULE,
	.open     = local_open,
	.release  = release,
	.unlocked_ioctl = ioctl,
	.mmap	= mmap
};


/* Initialize the driver to be a character device such that is responds to
 * file operations.
 */
static int cdevice_init(struct dma_proxy_channel *pchannel_p, char *name)
{
	int rc;
	char device_name[32] = "axiDmaPoxy";
	static struct class *local_class_p = NULL;

	/* Allocate a character device from the kernel for this driver.
	 */
	rc = alloc_chrdev_region(&pchannel_p->dev_node, 0, 1, "axiDma"); // axiDma-> axiDmaPoxy   
	if (rc) {
		dev_err(pchannel_p->dma_device_p, "unable to get a char device number\n");
		return rc;
	}

	/* Initialize the device data structure before registering the character 
	 * device with the kernel.
	 */
	cdev_init(&pchannel_p->cdev, &dm_fops);
	pchannel_p->cdev.owner = THIS_MODULE;
	rc = cdev_add(&pchannel_p->cdev, pchannel_p->dev_node, 1);

	if (rc) {
		dev_err(pchannel_p->dma_device_p, "unable to add char device\n");
		goto init_error1;
	}

	/* Only one class in sysfs is to be created for multiple channels,
	 * create the device in sysfs which will allow the device node
	 * in /dev to be created
	 */
	if (!local_class_p) {
		local_class_p = class_create(THIS_MODULE, DRIVER_NAME);

		if (IS_ERR(pchannel_p->dma_device_p->class)) {
			dev_err(pchannel_p->dma_device_p, "unable to create class\n");
			rc = ERROR;
			goto init_error2;
		}
	}
	pchannel_p->class_p = local_class_p;

	/* Create the device node in /dev so the device is accessible
	 * as a character device
	 */
	//strcat(device_name, name);
	pchannel_p->proxy_device_p = device_create(pchannel_p->class_p, NULL,
					  	 pchannel_p->dev_node, NULL, device_name);  // name -> device_name
	if (IS_ERR(pchannel_p->proxy_device_p)) {
		dev_err(pchannel_p->dma_device_p, "unable to create the device\n");
		goto init_error3;
	}

	return 0;

init_error3:
	class_destroy(pchannel_p->class_p);

init_error2:
	cdev_del(&pchannel_p->cdev);

init_error1:
	unregister_chrdev_region(pchannel_p->dev_node, 1);
	return rc;
}

/* Exit the character device by freeing up the resources that it created and
 * disconnecting itself from the kernel.
 */
static void cdevice_exit(struct dma_proxy_channel *pchannel_p)
{
	/* Take everything down in the reverse order
	 * from how it was created for the char device
	 */
	if (pchannel_p->proxy_device_p) {
		device_destroy(pchannel_p->class_p, pchannel_p->dev_node);

		/* If this is the last channel then get rid of the /sys/class/dma_proxy
		 */
		if (total_count == 1)
			class_destroy(pchannel_p->class_p);

		cdev_del(&pchannel_p->cdev);
		unregister_chrdev_region(pchannel_p->dev_node, 1);
	}
}

/* Create a DMA channel by getting a DMA channel from the DMA Engine and then setting
 * up the channel as a character device to allow user space control.
 */
static int create_channel(struct platform_device *pdev, struct dma_proxy_channel *pchannel_p, char *name, u32 direction)
{
	int rc, bd;

	/* Request the DMA channel from the DMA engine and then use the device from
	 * the channel for the proxy channel also.
	 */
	pchannel_p->dma_device_p = &pdev->dev;
	pchannel_p->channel_p = dma_request_chan(&pdev->dev, name); //name - axidma0
	if (!pchannel_p->channel_p) {
		dev_err(pchannel_p->dma_device_p, "DMA channel request error\n");
		return ERROR;
	}
        dev_info(pchannel_p->dma_device_p, "Found DMA Channel RX device %s\n", dma_chan_name(pchannel_p->channel_p));

	/* Initialize the character device for the dma proxy channel
	 */
	rc = cdevice_init(pchannel_p, name);  // name - axidma0
	if (rc) 
		return rc;

	pchannel_p->direction = direction;

	/* Allocate DMA memory that will be shared/mapped by user space, allocating
	 * a set of buffers for the channel with user space specifying which buffer
	 * to use for a tranfer..
	 */
	pchannel_p->buffer_table_p = (struct channel_buffer *)
		dmam_alloc_coherent(pchannel_p->dma_device_p,
					sizeof(struct channel_buffer) * BUFFER_COUNT,
					&pchannel_p->buffer_phys_addr, GFP_KERNEL);
	printk(KERN_INFO "Allocating memory, virtual address: %px physical address: %px\n",
			pchannel_p->buffer_table_p, (void *)pchannel_p->buffer_phys_addr);

	/* Initialize each entry in the buffer descriptor table such that the physical address	
	 * address of each buffer is ready to use later.
	 */
	for (bd = 0; bd < BUFFER_COUNT; bd++) 
		pchannel_p->bdtable[bd].dma_handle = (dma_addr_t)(pchannel_p->buffer_phys_addr + 
						(sizeof(struct channel_buffer) * bd) + offsetof(struct channel_buffer, buffer));

	/* The buffer descriptor index into the channel buffers should be specified in each 
	 * ioctl but we will initialize it to be safe.
	 */
	pchannel_p->bdindex = 0;
	if (!pchannel_p->buffer_table_p) {
		dev_err(pchannel_p->dma_device_p, "DMA allocation error\n");
		return ERROR;
	}
	return 0;
}
/* Initialize the dma proxy device driver module.
 */
static int dma_proxy_probe(struct platform_device *pdev)
{
	int rc, i;
	struct dma_proxy *lp;
	struct device *dev = &pdev->dev;

	printk(KERN_INFO "dma_proxy module initialized\n");
	
	lp = (struct dma_proxy *) devm_kmalloc(&pdev->dev, sizeof(struct dma_proxy), GFP_KERNEL);
	if (!lp) {
		dev_err(dev, "Cound not allocate proxy device\n");
		return -ENOMEM;
	}
	dev_set_drvdata(dev, lp);

	/* Figure out how many channels there are from the device tree based
	 * on the number of strings in the dma-names property
	 */
	//lp->channel_count = device_property_read_string_array(&pdev->dev,"axidma0", NULL, 0);  //dma-names -> axidma0
         lp->channel_count = 1;
	if (lp->channel_count <= 0) 
		return 0;

	printk("Device Tree Channel Count: %d\r\n", lp->channel_count);

	/* Allocate the memory for channel names and then get the names
    * from the device tree
	 */
	lp->names = devm_kmalloc_array(&pdev->dev, lp->channel_count, 
			sizeof(char *), GFP_KERNEL);
	if (!lp->names)
		return -ENOMEM;
        lp->names[0] = "axidma0";
	//rc = device_property_read_string_array(&pdev->dev, "axidma0",(const char **)lp->names, lp->channel_count);// dma-names -> axidma0
        rc = 1;
	if (rc < 0)
		return rc;
	
	/* Allocate the memory for the channels since the number is known.
	 */
	lp->channels = devm_kmalloc(&pdev->dev,
			sizeof(struct dma_proxy_channel) * lp->channel_count, GFP_KERNEL);
	if (!lp->channels)
		return -ENOMEM;

	/* Create the channels in the proxy. The direction does not matter
	 * as the DMA channel has it inside it and uses it, other than this will not work 
	 * for cyclic mode.
	 */
	for (i = 0; i < lp->channel_count; i++) {
		printk("Creating channel %s\r\n", lp->names[i]);
		rc = create_channel(pdev, &lp->channels[i], lp->names[i], DMA_MEM_TO_DEV);  // DMA_MEM_TO_DEV -> DMA_DEV_TO_MEM

		if (rc) 
			return rc;
		total_count++;
	}

	if (internal_test)
		test(lp);
	return 0;
}
 
/* Exit the dma proxy device driver module.
 */
static int dma_proxy_remove(struct platform_device *pdev)
{
	int i;
	struct device *dev = &pdev->dev;
	struct dma_proxy *lp = dev_get_drvdata(dev);

	printk(KERN_INFO "dma_proxy module exited\n");

	/* Take care of the char device infrastructure for each
	 * channel except for the last channel. Handle the last
	 * channel seperately.
	 */
	for (i = 0; i < lp->channel_count; i++) { 
		if (lp->channels[i].proxy_device_p)
			cdevice_exit(&lp->channels[i]);
		total_count--;
	}
	/* Take care of the DMA channels and any buffers allocated
	 * for the DMA transfers. The DMA buffers are using managed
	 * memory such that it's automatically done.
	 */
	for (i = 0; i < lp->channel_count; i++)
		if (lp->channels[i].channel_p) {
			lp->channels[i].channel_p->device->device_terminate_all(lp->channels[i].channel_p);
			dma_release_channel(lp->channels[i].channel_p);
		}
	return 0;
}

static const struct of_device_id dma_proxy_of_ids[] = {
	{ .compatible = "xlnx,plaxidma-1.00.a",},
	{}
};

static struct platform_driver dma_proxy_driver = {
	.driver = {
		.name = DRIVER_NAME,
		.owner = THIS_MODULE,
		.of_match_table = dma_proxy_of_ids,
//		.of_match_table	= axiDma_of_match,
	},
	.probe = dma_proxy_probe,
	.remove = dma_proxy_remove,
};

static int __init dma_proxy_init(void)
{
	return platform_driver_register(&dma_proxy_driver);

}

static void __exit dma_proxy_exit(void)
{
	platform_driver_unregister(&dma_proxy_driver);
}

module_init(dma_proxy_init)
module_exit(dma_proxy_exit)

MODULE_AUTHOR("Xilinx, Inc.");
MODULE_DESCRIPTION("DMA Proxy Prototype");
MODULE_LICENSE("GPL v2");

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值