前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ZYNQ XC7Z020的PL PS中断驱动程序编写测试(linux4.14版本下)

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

作者头像
用户9736681
发布2022-12-05 14:18:18
1.1K0
发布2022-12-05 14:18:18
举报
文章被收录于专栏:嵌入式随笔嵌入式随笔

设计目的

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 = %d\n", 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 = %d\n", 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 = %d\n", 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!\r\n");
	/* 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 !\r\n", 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!\r\n", 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 = %d\n", *(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/hello\n");
		return -1;
	}
	read(fd, str, 10);
	for(int i=0;i<10;i++)
	{
		printf("read = %d\n", 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);

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-12-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 嵌入式随笔 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 设计目的
  • Vivado中增加BRAM和中断
  • 应用程序测试
相关产品与服务
轻量应用服务器
轻量应用服务器(TencentCloud Lighthouse)是新一代开箱即用、面向轻量应用场景的云服务器产品,助力中小企业和开发者便捷高效的在云端构建网站、Web应用、小程序/小游戏、游戏服、电商应用、云盘/图床和开发测试环境,相比普通云服务器更加简单易用且更贴近应用,以套餐形式整体售卖云资源并提供高带宽流量包,将热门开源软件打包实现一键构建应用,提供极简上云体验。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档