ZYNQ XC7Z020的PL PS中断驱动程序编写测试(linux4.14版本下)

2022-12-05 14:18:18 浏览数 (2)

设计目的

ARM和FPGA的交互是这个芯片最重要的部分,PL和PS的交互使用中断是较为快捷的方法,本文使用bram存储数据并通过外部pl端发出中断通知ps端读写数据。程序思路是按键产生中断,按键是直接连到pl端的,驱动产生异步通知,应用开始往BRAM写数据,然后再读取数据(阻塞读取),均打印出来比较

Vivado中增加BRAM和中断

这里只写我增加的部分,大家试验可以随便找一个可运行的程序在其基础上修改即可。

首先增加BRAM控制器和BRAM,然后增加中断,本文使用第11个中断,连接至IRQ_F2P

修改Linux设备树

代码语言:javascript复制
/include/ "system-conf.dtsi"
/ {
	irq: irq@0{
		compatible = "plpsirq";
		interrupt-parent = <&intc>;
		interrupts = <0 54 1>;
	};
};

需要在设备树中增加相应的中断,上一级中断是intc,中断号需要查手册,第11个中断号(本文使用)是86,减去32(前面其他功能的中断),是54, 1表示的是中断触发形式,上升沿触发

中断程序

中断程序如下

代码语言:javascript复制
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/fcntl.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/sched/signal.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/mm.h>
/***************************************************************
***************************************************************/

#define PLPS_CNT		1				/* 设备号长度 	*/
#define PLPS_NAME		"plpsirq"	/* 设备名字 	*/

/* 中断描述结构体 */
struct irq_plpsdesc {
	int irqnum;								/* 中断号     */
	char name[10];							/* 名字 */
	irqreturn_t (*handler)(int, void *);	/* 中断服务函数 */
};
/* leddev设备结构体 */
struct plpsirqdev_dev{
	dev_t devid;				/* 设备号	*/
	struct cdev cdev;			/* cdev		*/
	struct class *class;		/* 类 		*/
	struct device *device;		/* 设备		*/
	int major;					/* 主设备号	*/	
	int irq;
	struct device_node *node;	/* plps设备节点 */
	struct irq_plpsdesc irq_plpsdesc;	/* 按键描述数组 */
	struct fasync_struct *async_queue;		/* 异步相关结构体 */
	wait_queue_head_t w_wait;				/* 读等待队列头 */
};
struct plpsirqdev_dev plpsirq; 		/* plps设备 */
/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int plpsirq_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &plpsirq; /* 设置私有数据  */
	return 0;
}

/*
 * @description		: 向设备写数据
 * @param - filp 	: 设备文件,表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t plpsirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	int err;
	int ret;
	unsigned char *test;

	DECLARE_WAITQUEUE(wait, current);	/* 定义一个等待队列 */

	test = kzalloc(1024, GFP_KERNEL);
	if (filp->f_flags & O_NONBLOCK)	{ /* 非阻塞访问 */
			return -EAGAIN;
	} else {	
		printk("KER11");						/* 阻塞访问 */
			add_wait_queue(&plpsirq.w_wait, &wait);	/* 将等待队列添加到等待队列头 */
			__set_current_state(TASK_INTERRUPTIBLE);/* 设置任务状态 */
			schedule();							/* 进行一次任务切换 */
			printk("KER22");
			if(signal_pending(current))	{			/* 判断是否为信号引起的唤醒 */
			printk("KE44");
				set_current_state(TASK_RUNNING);		/* 设置任务为运行态 */
				remove_wait_queue(&plpsirq.w_wait, &wait);	/* 将等待队列移除 */
			test = ioremap(0x40000000, 10);
			err=copy_to_user(buf, test, cnt);
			printk("cnt = %dn", cnt);
			return cnt; 

	//		ret = -ERESTARTSYS;
	//		goto wait_error;
		}
			set_current_state(TASK_RUNNING);		/* 设置任务为运行态 */
			remove_wait_queue(&plpsirq.w_wait, &wait);	/* 将等待队列移除 */
		printk("KER33");
		test = ioremap(0x40000000, 10);
		err=copy_to_user(buf, test, cnt);
		printk("cnt = %dn", cnt);
		return cnt;
	}
wait_error:
	set_current_state(TASK_RUNNING);		/* 设置任务为运行态 */
	remove_wait_queue(&plpsirq.w_wait, &wait);	/* 将等待队列移除 */
	return ret;
}
/* @description		: 中断服务函数,开启定时器,延时10ms,
 *				  	  定时器用于按键消抖。
 * @param - irq 	: 中断号
 * @param - dev_id	: 设备结构。
 * @return 			: 中断执行结果
 */
static irqreturn_t plps_handler(int irq, void *dev_id)
{
	struct plpsirqdev_dev *dev = (struct plpsirqdev_dev *)dev_id;
	printk("irq = %dn", irq);
	kill_fasync (&dev->async_queue, SIGIO, POLL_IN);
	wake_up_interruptible(&dev->w_wait);
	return IRQ_RETVAL(IRQ_HANDLED);
}

/*
 * @description     : fasync函数,用于处理异步通知
 * @param - fd		: 文件描述符
 * @param - filp    : 要打开的设备文件(文件描述符)
 * @param - on      : 模式
 * @return          : 负数表示函数执行失败
 */
static int plpsirq_fasync(int fd, struct file *filp, int on)
{
	struct plpsirqdev_dev *dev = (struct plpsirqdev_dev *)filp->private_data;
	return fasync_helper(fd, filp, on, &dev->async_queue);
}

/*
 * @description     : release函数,应用程序调用close关闭驱动文件的时候会执行
 * @param - inode	: inode节点
 * @param - filp    : 要打开的设备文件(文件描述符)
 * @return          : 负数表示函数执行失败
 */
static int plpsirq_release(struct inode *inode, struct file *filp)
{
	return plpsirq_fasync(-1, filp, 0);
}
/*mmap系统调用函数 */
static int plpsirq_mmap(struct file *file, struct vm_area_struct *vma)
{
    vma->vm_flags |= VM_IO;//表示对设备IO空间的映射
    vma->vm_flags |= (VM_DONTEXPAND | VM_DONTDUMP);//标志该内存区不能被换出,在设备驱动中虚拟页和物理页的关系应该是长期的,应该保留起来,不能随便被别的虚拟页换出
  	vma->vm_page_prot =pgprot_noncached(vma->vm_page_prot);
    if(remap_pfn_range(vma,//虚拟内存区域,即设备地址将要映射到这里
                       vma->vm_start,//虚拟空间的起始地址
                       vma->vm_pgoff,//与物理内存对应的页帧号,物理地址右移12位
                       vma->vm_end - vma->vm_start,//映射区域大小,一般是页大小的整数倍
                       vma->vm_page_prot))//保护属性,
    {
        return -EAGAIN;
    }
    return 0;
}
/* 设备操作函数 */
static struct file_operations plpsirq_fops = {
	.owner = THIS_MODULE,
	.open = plpsirq_open,
	.read = plpsirq_read,
	.fasync = plpsirq_fasync,
	.release = plpsirq_release,
	.mmap = plpsirq_mmap,
};

/*
 * @description		: flatform驱动的probe函数,当驱动与
 * 					  设备匹配以后此函数就会执行
 * @param - dev 	: platform设备
 * @return 			: 0,成功;其他负值,失败
 */
static int plpsirq_probe(struct platform_device *dev)
{	
	int ret = 0;
	struct device_node *node;
	unsigned int irqnumplps[3];
	strcpy(plpsirq.irq_plpsdesc.name, "plpszd"); /*给数组赋字符串*/
	printk("plps driver and device was matched!rn");
	/* 1、设置设备号 */
/*	if (plpsirq.major) {
		plpsirq.devid = MKDEV(plpsirq.major, 0);
		register_chrdev_region(plpsirq.devid, PLPS_CNT, PLPS_NAME);
	} else {
		alloc_chrdev_region(&plpsirq.devid, 0, PLPS_CNT, PLPS_NAME);
		plpsirq.major = (plpsirq.devid);
	}
	*/
	/* 2、注册设备      */
/*	cdev_init(&plpsirq.cdev, &plpsirq_fops);
	cdev_add(&plpsirq.cdev, plpsirq.devid, PLPS_CNT);
*/
	plpsirq.major = register_chrdev(0, PLPS_NAME, &plpsirq_fops);  /* /dev/gpio_key */
	/* 3、创建类      */
	plpsirq.class = class_create(THIS_MODULE, PLPS_NAME);
	if (IS_ERR(plpsirq.class)) {
		return PTR_ERR(plpsirq.class);
	}
	/* 4、创建设备 */
	plpsirq.device = device_create(plpsirq.class, NULL,  MKDEV(plpsirq.major, 0), NULL, PLPS_NAME);
	if (IS_ERR(plpsirq.device)) {
		return PTR_ERR(plpsirq.device);
	}
	/* 5、初始化IO */	
	plpsirq.irq = platform_get_irq(dev,0);
	printk("num %d !rn", plpsirq.irq);
	plpsirq.irq_plpsdesc.handler = plps_handler;
	ret = devm_request_irq(plpsirq.device, plpsirq.irq, plpsirq.irq_plpsdesc.handler,
		                 IRQF_TRIGGER_RISING, plpsirq.irq_plpsdesc.name, &plpsirq);
	if(ret < 0){
			printk("irq %d request failed!rn", 86);
			return -EFAULT;
		}
	init_waitqueue_head(&plpsirq.w_wait);
	return 0;
}
/*
 * @description		: platform驱动的remove函数,移除platform驱动的时候此函数会执行
 * @param - dev 	: platform设备
 * @return 			: 0,成功;其他负值,失败
 */
static int plpsirq_remove(struct platform_device *dev)
{
	device_destroy(plpsirq.class, plpsirq.devid);
	class_destroy(plpsirq.class);
	cdev_del(&plpsirq.cdev);				/*  删除cdev */
	unregister_chrdev_region(plpsirq.devid, PLPS_CNT); /* 注销设备号 */
	return 0;
}
/* 匹配列表 */
static const struct of_device_id plpsirq_of_match[] = {
	{ .compatible = "plpsirq" },
	{ /* Sentinel */ }
};
/* platform驱动结构体 */
static struct platform_driver plpsirq_driver = {
	.driver		= {
		.name	= "plpsirq",			/* 驱动名字,用于和设备匹配 */
		.of_match_table	= plpsirq_of_match, /* 设备树匹配表 		 */
	},
	.probe		= plpsirq_probe,
	.remove		= plpsirq_remove,
};		
/*
 * @description	: 驱动模块加载函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init plpsirq_init(void)
{
	return platform_driver_register(&plpsirq_driver);
}
/*
 * @description	: 驱动模块卸载函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit plpsirq_exit(void)
{
	platform_driver_unregister(&plpsirq_driver);
}

module_init(plpsirq_init);
module_exit(plpsirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zd");

首先注册平台驱动,然后注册字符驱动,注册中断。由于交互的数量可能比较大,因此编写mmap函数用于读写大量数据。

中断函数中产生异步通知信号和阻塞的等待队列清除信号。

应用程序测试

代码语言:javascript复制
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <sys/mman.h>
#include <signal.h>
#include <fcntl.h>
/***************************************************************
***************************************************************/
unsigned char *buf = NULL;
unsigned int count = 0;
void my_signal_fun(int signum)
{
	printf("irq app printf!n");
	*buf=0x1   count;
	*(buf 1)=0x2   count;
	*(buf 2)=0x3   count;
	*(buf 3)=0x4   count;
	*(buf 4)=0x5   count;
	for(int i=0;i<10;i  )
	{
	    printf("write = %dn", *(buf i));;
	}
	count  ;
}

int main(int argc, char **argv)
{
	int fd;
	int len;
	char str[2048];
	
	unsigned char key_val;
	int ret;
	int Oflags;
	signal(SIGIO, my_signal_fun);
	fd = open("/dev/plpsirq", O_RDWR);
	if (fd < 0)
	{
		printf("can't open!n");
	}
	fcntl(fd, F_SETOWN, getpid());
	Oflags = fcntl(fd, F_GETFL);
	fcntl(fd, F_SETFL, Oflags | FASYNC);
	buf =  mmap(NULL, 1024*8, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0x40000000);
	*buf=0x1;
	if (buf == MAP_FAILED)
	{
		printf("can not mmap file /dev/hellon");
		return -1;
	}
	read(fd, str, 10);
	for(int i=0;i<10;i  )
	{
		printf("read = %dn", str[i]);;
	}	
	munmap(buf, 1024*8);
	close(fd);
	return 0;
}

接收中断的触发信号后,通过mmap将数据写入BRAM,然后读取数据进入阻塞态,唤醒后读取数据并打印出来。打印数据如下:

代码语言:javascript复制
irq = 62
KER22
KE44
cnt = 10
irq app printf!
write = 2
write = 3
write = 4
write = 5
write = 6
write = 0
write = 0
write = 0
write = 0
write = 0
read = 1
read = 2
read = 3
read = 4
read = 5
read = 0
read = 0
read = 0
read = 0
read = 0

通过printk的数据可以看出中断触发的数据处理流程如下:

第一步:中断的plps_handler函数;

第二步:中断的之前阻塞的plpsirq_read函数(异步通知会唤醒等待队列,所以中断中将读取放在signal_pending这里,就是为了判断数据);

第三步:应用程序的异步通知函数my_signal_fun;

第四步:完成之前的阻塞读取函数read(fd, str, 10);

0 人点赞