ZYQN CDMA DRIVER AND APP USE CASE

@dma zynq use case

特别注意: dma 搬运超时,和dma的配置时加上锁和关闭中断,否则会出现偶尔失败。
Zynq DMA Use demo

DMA App

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>


#define DRIVER_NAME 		"/dev/xxxxx"

#define AXIDMA_IOC_MAGIC 			'A'
#define DMA_PS_TO_PL 		_IO(AXIDMA_IOC_MAGIC, 1)
#define DMA_PL_TO_PS 		_IO(AXIDMA_IOC_MAGIC, 2)

typedef struct
{
	unsigned long long int src_addr;		/* 8 Bytes*/
	unsigned long long int dst_addr;		/* 8 Bytes*/
	size_t data_len;
}apg_cdma_config_t;


unsigned int read_dma(unsigned int high_addr, unsigned int base_addr, unsigned int ReadValue[], unsigned int num_bytes)
{
	int fd = -1;
	apg_cdma_config_t config = {0U};
	int ret = 0;

	/* open dev */
	fd = open(DRIVER_NAME, O_RDWR);
	if(fd < 0){
		printf("open %s failed\n", DRIVER_NAME);
		return -1;
	}

	config.data_len = num_bytes;
	config.dst_addr = 0;
	config.src_addr = (unsigned long long int)(((unsigned long long int)high_addr << 32) | ((unsigned long long int)base_addr << 0));

	printf("%s: src %llx dst %llx len %d\r\n"
			, __FUNCTION__
			,config.src_addr
			,config.dst_addr
			,config.data_len);

	/* get channel */
	ret = ioctl(fd, DMA_PL_TO_PS, &config);
	if(ret){
		perror("ioctl");
		printf("ioctl: get channel failed <%d>\n", ret);
		goto error;
	}

	ret = lseek(fd, 0, SEEK_SET);
	if (ret < 0){
	   printf("lseek: %d\n", ret);
	   return -1U;
	}

	ret = read(fd, ReadValue, num_bytes);
	if (-1 == ret){
		perror("write");
	}

//	printf("data: %x %x %x %x\r\n", ReadValue[0], ReadValue[1], ReadValue[2], ReadValue[3]);
	close(fd);

	/* for test */
    return ReadValue[num_bytes/4];
error:
	close(fd);
	return -1;
}

unsigned int write_dma(unsigned int high_addr, unsigned int base_addr, unsigned int WriteValue[], unsigned int num_bytes) {
	int fd = -1;
	int ret;
	apg_cdma_config_t config = {0U};

	/* open dev */
	fd = open(DRIVER_NAME, O_RDWR);
	if(fd < 0){
		printf("open %s failed\n", DRIVER_NAME);
		return -1;
	}

	ret = write(fd, WriteValue, num_bytes);
	if (-1 == ret){
		perror("write");
	}

	config.data_len = num_bytes;
	config.dst_addr = (unsigned long long int)(((unsigned long long int)high_addr << 32) | ((unsigned long long int)base_addr << 0));
	config.src_addr = 0;

	printf("%s: src %llx dst %llx len %d\r\n"
			, __FUNCTION__
			,config.src_addr
			,config.dst_addr
			,config.data_len);

	/* get channel */
	ret = ioctl(fd, DMA_PS_TO_PL, &config);
	if(ret){
		perror("ioctl");
		printf("ioctl: get channel failed <%d>\n", ret);
		goto error;
	}

	close(fd);

error:
	close(fd);
	return -1;
}


#define DMA_MEMCPY_LEN 	16

unsigned int datadst[4] = {0};

/*
 * input: HEX string, The HEX string need have 0x[] or 0X[] perfix.
 * return: return error will value -eq 0xffffffffffffffff.
 * */
unsigned long long int str2hex(char *hexstr)
{
	unsigned long long int hexvalue = 0;
	unsigned char value = 0;

	if ( ((hexstr[0] == '0') && (hexstr[1] == 'x')) || \
		 ((hexstr[0] == '0') && (hexstr[1] == 'X')) )
	{
		for(unsigned char i=2; (i<=17 && hexstr[i] != '\0'); i++){
			if ((hexstr[i] >= '0') && (hexstr[i] <= '9')){
				value = hexstr[i] - '0' + 0;
			}else if ((hexstr[i] >= 'a') && (hexstr[i] <= 'f')){
				value = hexstr[i] - 'a' + 10;
			}else if ((hexstr[i] >= 'A') && (hexstr[i] <= 'F')) {
				value = hexstr[i] - 'A' + 10;
			}else{
				printf("hex string error: %s\r\n", hexstr);
				return -1;
			}

			hexvalue = hexvalue << 4;
			hexvalue |= value;
		}
	}else{
		printf("hex string error: %s\r\n", hexstr);
		return -1;
	}

	return hexvalue;
}

int main(const int argc, char * argv[])
{
	int fd = -1;
	int ret;
	int i=0, j=0;
	apg_cdma_config_t config = {0U};
	unsigned long long int pl_addr = 0u;

	/* for test */
	printf("RUN: %s %s\r\n",__FILE__ ,__TIME__);

	switch(argc){
		case 2:{
			pl_addr = str2hex(argv[1]);
			if (pl_addr == 0xffffffffffffffff){
				goto error;
			}
//			printf("str: %s addr: 0x%llx\n\r", argv[1], pl_addr);

			read_dma((0xffffffff & (pl_addr>>32))
					,(0xffffffff & (pl_addr>>0))
					,datadst
					,DMA_MEMCPY_LEN);
		}break;
		case 3:
		case 4:
		case 5:
		case 6:{
			pl_addr = str2hex(argv[1]);
			if (pl_addr == 0xffffffffffffffff){
				goto error;
			}
//			printf("str: %s addr: 0x%x\n\r", argv[1], pl_addr);

			for (i=2, j=0; i<argc; i++, j++){
				datadst[j] = str2hex(argv[i]);
				if (datadst[j] == 0xffffffffffffffff){
					goto error;
				}
			}

			write_dma((0xffffffff & (pl_addr>>32))
					,(0xffffffff & (pl_addr>>0))
					,datadst
					,4*j);

			read_dma((0xffffffff & (pl_addr>>32))
					,(0xffffffff & (pl_addr>>0))
					,datadst
					,4*j);
		}break;
		default:
			goto error;
			break;
	}

	printf("end data: %08x %08x %08x %08x\r\n", datadst[0], datadst[1], datadst[2], datadst[3]);
	return 0;
error:
	printf("Usage: %s [0x0000_0010_0000_0000] <0x0000_0000> <0x0000_0000> <0x0000_0000> <0x0000_0000>\r\n", argv[0]);
	return -1;
}

DMA Driver

申请dma 并配置

// An highlighted block
/*
* date: 2022-11-23
*/

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

#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/of_irq.h>
#include <linux/module.h>
#include <linux/uaccess.h>

#include <linux/cdev.h>


typedef struct
{
	dma_addr_t src_addr;		/* 8 Bytes*/
	dma_addr_t dst_addr;		/* 8 Bytes*/
	size_t data_len;
}apg_cdma_config_t;

typedef struct
{
	unsigned int *src;	/* dma_alloc_coherent src vir addr */
	unsigned int *dst;	/* dma_alloc_coherent dst vir addr */
	dma_addr_t dma_src;	/* dma_alloc_coherent src phy addr */
	dma_addr_t dma_dst;	/* dma_alloc_coherent dst phy addr */
	enum dma_ctrl_flags flags;
	struct dma_chan *dma_chan;
	wait_queue_head_t wait;
	unsigned int callbacked:1;
}apg_cdma_info_t;

typedef struct
{
	dev_t devno;
	struct cdev cdev;
	struct class *class;
	struct device *device;
	struct platform_device* pdev;
	struct fasync_struct *fasync;
	apg_cdma_info_t info;
	unsigned char dev_count;
}apg_cdma_dev_t;

apg_cdma_dev_t apg_cdma_dev = {0};
static DEFINE_MUTEX(apg_cdma_mutex);
spinlock_t apg_cdma_lock;

#define BUF_SIZE        4096

#define AXIDMA_IOC_MAGIC 			'A'
#define DMA_PS_TO_PL 		_IO(AXIDMA_IOC_MAGIC, 1)
#define DMA_PL_TO_PS 		_IO(AXIDMA_IOC_MAGIC, 2)
#if 0
=>setenv bootargs "root=/dev/ram0 ramdisk_size=0x10000000 cma=64M@0x0-0xa0000000"
Device Drivers -> Generic Driver Options -> Default contiguous memory area size 的 Size in Mega Bytes修改为25

===========> system-user.dtsi
&amba {
	apg: apg-dma@0 {
		#address-cells = <2>;
		#size-cells = <2>;
		compatible = "xxxxxxxxxx";

		reg = <0x00000000 0x00000000 0x00000000 0x00080000
		       0x00000000 0x00000000 0x00000000 0x00000000>;
		num-channel=<1>;
	};
};
#endif


static bool filter(struct dma_chan *chan, void *param)
{
	if (!strcmp(dma_chan_name(chan), "dma1chan0")){
//		printk("filter: %s\r\n", dma_chan_name(chan));
		return true;
	}
	else{
		return false;
	}
}

static int axidma_open(struct inode *inode, struct file *file)
{
	dma_cap_mask_t mask;

//    memset(apg_cdma_dev.info.src, 0xAA, BUF_SIZE);
//    memset(apg_cdma_dev.info.dst, 0xBB, BUF_SIZE);

//    printk("open src: 0x%llx dst: 0x%llx\r\n", *((unsigned long long *)(apg_cdma_dev.info.src)), *((unsigned long long *)(apg_cdma_dev.info.dst)));


	mutex_lock(&apg_cdma_mutex);
	dma_cap_zero(mask);
	dma_cap_set(DMA_MEMCPY, mask);
	//dma_cap_set(DMA_SLAVE, mask);
	mutex_unlock(&apg_cdma_mutex);
	apg_cdma_dev.info.dma_chan = dma_request_channel(mask, filter, &apg_cdma_dev);
	if(!apg_cdma_dev.info.dma_chan){
		printk("cdma request channel failed\n");
		return -1;
	}

	apg_cdma_dev.info.src = dma_alloc_coherent(NULL, BUF_SIZE, &(apg_cdma_dev.info.dma_src), GFP_KERNEL);
	apg_cdma_dev.info.dst = dma_alloc_coherent(NULL, BUF_SIZE, &(apg_cdma_dev.info.dma_dst), GFP_KERNEL);

	return 0;
}

static ssize_t axidma_read(struct file *filp, char __user *buf, size_t count,
        loff_t *pos)
{
	int ret = 0;

	mutex_lock(&apg_cdma_mutex);

	ret = simple_read_from_buffer(buf, count, pos, apg_cdma_dev.info.dst,
			BUF_SIZE);
//	printk("axidma_read ret: %d\r\n", ret);

	mutex_unlock(&apg_cdma_mutex);
    return ret;
}

static ssize_t axidma_write(struct file *filp, const char __user *buf,
            size_t count, loff_t *pos)
{
	int ret = 0;

	mutex_lock(&apg_cdma_mutex);

	ret = simple_write_to_buffer(apg_cdma_dev.info.src, BUF_SIZE, pos, buf, count);
//	printk("axidma_write ret: %d\r\n", ret);

	mutex_unlock(&apg_cdma_mutex);

    return ret;
}

static loff_t axidma_llseek(struct file *filp, loff_t offset, int whence)
{
	int ret = -EFAULT;

	if ((offset%4) == 0){
		ret = default_llseek(filp, offset, whence);
	}

	return ret;
}


static int axidma_release(struct inode *inode, struct file *file)
{
//   printk("close src: 0x%llx dst: 0x%llx\r\n", *((unsigned long long *)(apg_cdma_dev.info.src)), *((unsigned long long *)(apg_cdma_dev.info.dst)));
	mutex_unlock(&apg_cdma_mutex);
	dma_release_channel(apg_cdma_dev.info.dma_chan);

    dma_free_coherent(NULL, BUF_SIZE, apg_cdma_dev.info.src, apg_cdma_dev.info.dma_src);
    dma_free_coherent(NULL, BUF_SIZE, apg_cdma_dev.info.dst, apg_cdma_dev.info.dma_dst);
	mutex_unlock(&apg_cdma_mutex);
	return 0;
}

static void dma_complete_func(void *info)
{
	apg_cdma_info_t *cdma_info = info;
	unsigned long flags;

//    printk("complete: 0x%llx dst: 0x%llx\r\n", *((unsigned long long *)(apg_cdma_dev.info.src)), *((unsigned long long *)(apg_cdma_dev.info.dst)));
//	printk("dma_complete_func cdma\r\n");
	spin_lock_irqsave(&apg_cdma_lock, flags);
	cdma_info->callbacked = 1;
	wake_up_interruptible(&(cdma_info->wait));
	spin_unlock_irqrestore(&apg_cdma_lock, flags);
}

static long axidma_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	int ret = 0U;
	struct dma_device *dma_dev;
	struct dma_async_tx_descriptor *tx = NULL;
	dma_cookie_t cookie;
	enum dma_ctrl_flags dma_flags;
	static apg_cdma_config_t config = {0U};
	unsigned long flags;

	if (arg == 0){
		goto error;
	}
	if (copy_from_user(&config, (void __user *)arg, sizeof (apg_cdma_config_t))) {
		goto error;
	}else{
		if(config.data_len%4){ // 对齐
			goto error;
		}
	}
//	printk("cpy: dst: %llx, src: %llx, len: %d\r\n"
//			, config.dst_addr
//			, config.src_addr
//			, config.data_len);

	switch(cmd)
	{
		case DMA_PS_TO_PL:
		{
			spin_lock_irqsave(&apg_cdma_lock, flags);
			init_waitqueue_head(&apg_cdma_dev.info.wait);

			apg_cdma_dev.info.flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
			dma_dev = apg_cdma_dev.info.dma_chan->device;
        	spin_unlock_irqrestore(&apg_cdma_lock, flags);
			// phy addr
			tx = dma_dev->device_prep_dma_memcpy(apg_cdma_dev.info.dma_chan, config.dst_addr, apg_cdma_dev.info.dma_src, config.data_len, dma_flags);
            if(!tx){
                printk("%s failed to prepare DMA memcpy\n", __func__);
                goto error;
            }

			spin_lock_irqsave(&apg_cdma_lock, flags);
            apg_cdma_dev.info.callbacked = 0;	// init sleep value for wait_event_interruptible_timeout
            tx->callback = dma_complete_func;      // set call back function
            tx->callback_param = &apg_cdma_dev.info;
            cookie = tx->tx_submit(tx);   // submit the desc
        	spin_unlock_irqrestore(&apg_cdma_lock, flags);

            if(dma_submit_error(cookie)) {
                printk("failed to do DMA tx_submit");
            }

            dma_async_issue_pending(apg_cdma_dev.info.dma_chan);         // begin dma transfer

        	ret = wait_event_interruptible_timeout(apg_cdma_dev.info.wait,
        			apg_cdma_dev.info.callbacked, (HZ*10));
        	if (ret > 0 && apg_cdma_dev.info.callbacked) {
//        		printk("normal exit cdma %d %d\r\n", ret, apg_cdma_dev.info.callbacked);
        		ret = 0;
        	} else {
        		if (!ret) {
        			printk("cdma: write timeout exit, PS -> 0x%llx, %d\r\n", config.dst_addr, config.data_len);
        			ret = -ETIMEDOUT;
        		}
    			printk("terminate exit cdma\r\n");
        		dmaengine_terminate_all(apg_cdma_dev.info.dma_chan);
        	}

            break;
		}
		break;
		case DMA_PL_TO_PS:
		{
			spin_lock_irqsave(&apg_cdma_lock, flags);
			init_waitqueue_head(&apg_cdma_dev.info.wait);

			apg_cdma_dev.info.flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
			dma_dev = apg_cdma_dev.info.dma_chan->device;
        	spin_unlock_irqrestore(&apg_cdma_lock, flags);

			// phy addr
			tx = dma_dev->device_prep_dma_memcpy(apg_cdma_dev.info.dma_chan, apg_cdma_dev.info.dma_dst, config.src_addr, config.data_len, dma_flags);
            if(!tx){
                printk("%s failed to prepare DMA memcpy\n", __func__);
                goto error;
            }

			spin_lock_irqsave(&apg_cdma_lock, flags);
            apg_cdma_dev.info.callbacked = 0;	// init sleep value for wait_event_interruptible_timeout
            tx->callback = dma_complete_func;      // set call back function
            tx->callback_param = &apg_cdma_dev.info;
        	spin_unlock_irqrestore(&apg_cdma_lock, flags);

            cookie = tx->tx_submit(tx);   // submit the desc
            if(dma_submit_error(cookie)) {
                printk("failed to do DMA tx_submit");
            }

            dma_async_issue_pending(apg_cdma_dev.info.dma_chan);         // begin dma transfer

        	ret = wait_event_interruptible_timeout(apg_cdma_dev.info.wait,
        			apg_cdma_dev.info.callbacked, (HZ*10));
        	if (ret > 0 && apg_cdma_dev.info.callbacked) {
//        		printk("normal exit cdma %d %d\r\n", ret, apg_cdma_dev.info.callbacked);
        		ret = 0;
        	} else {
        		if (!ret) {
        			printk("cdma: read timeout exit, 0x%llx -> PS, %d\r\n", config.src_addr, config.data_len);
        			ret = -ETIMEDOUT;
        		}
    			printk("terminate exit cdma\r\n");
        		dmaengine_terminate_all(apg_cdma_dev.info.dma_chan);
        	}

            break;
		}
		break;

	default:
			printk(KERN_ERR "Don't support cmd [%d]\n", cmd);
			break;
	}
	return ret;
error:
	return -EINVAL;
}

/*
 *    Kernel Interfaces
 */

static struct file_operations apg_cdma_fops = {
    .owner        = THIS_MODULE,
    .llseek        = no_llseek,
    .write        = axidma_write,
    .read        = axidma_read,
	.llseek		= axidma_llseek,
    .unlocked_ioctl = axidma_unlocked_ioctl,
    .open        = axidma_open,
    .release    = axidma_release,
};


static int apg_cdma_probe(struct platform_device *pdev)
{
	int result;
	int major;
	int i;
	int dev_count = 1;
	unsigned long long arrary[8] = {0U};


	result = of_property_read_u64_array(pdev->dev.of_node, "reg", arrary, 4);
    if(result) {
        printk (KERN_ERR "cannot register miscdev (err=%d)\n", result);
		return result;
    }
	apg_cdma_dev.dev_count = dev_count;

	result = alloc_chrdev_region(&apg_cdma_dev.devno, 0, dev_count, "dma"); // 注册设备号
	printk("MAJOR = %d MINOR = %d\r\n", MAJOR(apg_cdma_dev.devno), MINOR(apg_cdma_dev.devno));
	if(result < 0){
		printk("alloc_chrdev_region error\r\n");
		result =  -EBUSY;
		goto fail;
	}

	major = MAJOR(apg_cdma_dev.devno);

	cdev_init(&apg_cdma_dev.cdev, &apg_cdma_fops); // 绑定字符设备操作函数集
	result = cdev_add(&apg_cdma_dev.cdev, apg_cdma_dev.devno, dev_count);   // 添加字符设备
	if(result < 0){
		printk("cdev_add error\r\n");
		result =  -EBUSY;
		goto unregister_chrdev_region;
	}

	apg_cdma_dev.class = class_create(THIS_MODULE, "dma");
	if (IS_ERR(apg_cdma_dev.class)) {
		result = PTR_ERR(apg_cdma_dev.class);
		goto cdev_del;
	}

	for(i=0U; i<dev_count; i++){
		apg_cdma_dev.device = device_create(apg_cdma_dev.class, NULL, MKDEV(major, i), NULL,"dma%d",i);
		if(IS_ERR(apg_cdma_dev.device)){
			result = PTR_ERR(apg_cdma_dev.device);
			goto destroy_class;
		}
	}

	dev_set_drvdata(&pdev->dev, &apg_cdma_dev);
	spin_lock_init(&apg_cdma_lock);
	return 0;

destroy_class:
	class_destroy(apg_cdma_dev.class);

cdev_del:
	cdev_del(&(apg_cdma_dev.cdev));
unregister_chrdev_region:
	unregister_chrdev_region(apg_cdma_dev.devno, dev_count);
fail:
	return result;
}

static int apg_cdma_remove(struct platform_device *pdev)
{
	int i;
	int dev_count;


	printk("%s\r\n", __FUNCTION__);

	dev_count = apg_cdma_dev.dev_count;
	for (i=0U; i< dev_count; i++){
		device_destroy(apg_cdma_dev.class, apg_cdma_dev.devno + i);
	}

	class_destroy(apg_cdma_dev.class);

	cdev_del(&(apg_cdma_dev.cdev));

	unregister_chrdev_region(apg_cdma_dev.devno, dev_count);

    return 0;
}


static const struct of_device_id apg_cdma_of_match[] = {
	{ .compatible = "XXXXXXX", },
	{}
};

static struct platform_driver apg_cdma_driver = {
    .probe = apg_cdma_probe,
    .remove = apg_cdma_remove,
    .driver = {
    	.owner = THIS_MODULE,
        .name = "dma_driver",
        .of_match_table = apg_cdma_of_match,
    },
};

static int __init apg_cdma_init(void)
{
	printk("%s\r\n", __FUNCTION__);
    return platform_driver_register(&apg_cdma_driver);
}
static void __exit apg_cdma_exit(void)
{
    printk("%s\r\n", __FUNCTION__);
    return platform_driver_unregister(&apg_cdma_driver);
}

module_init(apg_cdma_init);
module_exit(apg_cdma_exit);

//module_platform_driver(apg_cdma_driver);

MODULE_ALIAS("platform: XXXXXXX");
MODULE_DESCRIPTION("CDMA driver");
MODULE_AUTHOR("XXXXXXXXXXXX");
MODULE_LICENSE("GPL");
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值