设计目的
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);