Linux驱动 fasync异步通知方法

本文章来源于正点原子资料,记录下来,以后参考。

1.1 驱动中的信号处理

1 、fasync_struct 结构体
首先我们需要在驱动程序中定义一个 fasync_struct 结构体指针变量,fasync_struct 结构体内容如下:

struct fasync_struct {
	spinlock_t fa_lock;
	int magic;
	int fa_fd;
	struct fasync_struct *fa_next;
	struct file *fa_file;
	struct rcu_head fa_rcu;
};

一般将 fasync_struct 结构体指针变量定义到设备结构体中,比如

struct irq_dev{
    dev_t devid;          /* 设备号*/
    struct cdev cdev;     /* cdev*/
    struct class *class;    /* 类*/
    struct device *device;  /* 设备*/
    int major;              /* 主设备号*/
    int minor;              /* 次设备号*/
    struct device_node *nd; /* 设备树设备节点*/
    
    atomic_t keyvalue;      /* 有效的按键键值*/
    atomic_t releasekey;    /* 标记是否完成一次完整的按键,包括按下和释放*/

    struct timer_list timer;  /* 定义一个定时器*/
    struct irq_keydesc irqkeydesc[KEY_NUM]; /*按键描述数组*/
    unsigned char curkeynum; /* 当前的按键号*/

    wait_queue_head_t r_wait;  /* 读等待队列头*/
    struct fasync_struct *async_queue;  /* 异步相关结构体*/
};

async_queue就是在 xxx_dev 中添加了一个 fasync_struct 结构体指针变量。
2 、fasync 函数
如果要使用异步通知,需要在设备驱动中实现 file_operations 操作集中的 fasync 函数,此函数格式如下所示:

int (*fasync) (int fd, struct file *filp, int on)

fasync 函数里面一般通过调用 fasync_helper 函数来初始化前面定义的 fasync_struct 结构体指针,fasync_helper 函数原型如下:

int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp) 

fasync_helper 函数的前三个参数就是 fasync 函数的那三个参数,第四个参数就是要初始化的 fasync_struct 结构体指针变量。

当 应 用 程 序 通 过 “ f c n t l ( f d , F S E T F L , f l a g s ∣ F A S Y N C ) ” 改 变 f a s y n c 标 记 的 时 候 , \color{#00FF00}{当应用程序通过“fcntl(fd, F_SETFL, flags | FASYNC)”改变fasync 标记的时候,} fcntl(fd,FSETFL,flagsFASYNC)fasync
驱 动 程 序 f i l e o p e r a t i o n s 操 作 集 中 的 f a s y n c 函 数 就 会 执 行 。 \color{#00FF00}{驱动程序 file_operations 操作集中的 fasync 函数就会执行。} fileoperationsfasync

驱动程序中的 fasync 函数参考示例如下:

1 struct xxx_dev {
2		......
3		struct fasync_struct *async_queue; /* 异步相关结构体 */
4 };
5
6 static int xxx_fasync(int fd, struct file *filp, int on)
7 {
8 		struct xxx_dev *dev = (xxx_dev)filp->private_data;
9
10		if (fasync_helper(fd, filp, on, &dev->async_queue) < 0)
11			 return -EIO;
12 		return 0;
13 }
14
15 static struct file_operations xxx_ops = {
16		 ......
17 		.fasync = xxx_fasync,
18		 ......
19 };

在关闭驱动文件的时候需要在 file_operations 操作集中的 release 函数中释放 fasync_struct,fasync_struct 的释放函数同样为 fasync_helper,release 函数参数参考实例如下:

1 static int xxx_release(struct inode *inode, struct file *filp)
2 {
3 		return xxx_fasync(-1, filp, 0); /*  删除异步通知 */
4 }
5
6 static struct file_operations xxx_ops = {
7 		......
8 		.release = xxx_release,
9 };

第 3 行通过调用上面代码中的 xxx_fasync 函数来完成 fasync_struct 的释放工作,
但是,其最终还是通过 fasync_helper 函数完成释放工作。

1.2 驱动通知应用程序

那 么 驱 动 层 怎 么 通 知 用 户 程 序 呢 ? \color{#FF0000}{那么驱动层怎么通知用户程序呢?} kill_fasync函数。
1 、kill_fasync 函数

当 设 备 可 以 访 问 的 时 候 , 驱 动 程 序 需 要 向 应 用 程 序 发 出 信 号 , 相 当 于 产 生 “ 中 断 ” \color{#00FF00}{当设备可以访问的时候,驱动程序需要向应用程序发出信号,相当于产生“中断”} 访
此 时 a p p 中 注 册 的 信 号 处 理 函 数 将 会 执 行 \color{#00FF00}{此时app中注册的信号处理函数将会执行} app
kill_fasync函数负责发送指定的信号,kill_fasync 函数原型如下所示:

void kill_fasync(struct fasync_struct **fp, int sig, int band)

函数参数和返回值含义如下:
fp:要操作的 fasync_struct。
sig :要发送的信号。
band :可读时设置为 POLL_IN,可写时设置为 POLL_OUT。

1.3 应用程序对异步通知的处理

应用程序对异步通知的处理包括以下三步:
1 、注册信号处理函数
应用程序根据驱动程序所使用的信号来设置信号的处理函数,应用程序使用 signal 函数来设置信号的处理函数。

static void  sigio_signal_func(int signum){
	int err = 0;
	unsigned int keyvalue = 0;	
	
	err = read(g_fd,&keyvalue,sizeof(keyvalue));
	if(err < 0){
			/* 读取错误*/
			printf("read error\n");
	}else{
			printf("sigio signal! keyvalue=%d\n",keyvalue);
	}
}
signal(SIGIO,sigio_signal_func);  /* 设置信号SIGIO的处理函数*/

2 、将本应用程序的进程号告诉给内核

使用 fcntl(fd, F_SETOWN, getpid())将本应用程序的进程号告诉给内核。

3 、开启异步 通知
使用如下两行程序开启异步通知:

flags = fcntl(fd, F_GETFL); /* 获取当前的进程状态  */
fcntl(fd, F_SETFL, flags | FASYNC); /* 开启当前进程异步通知功能 */

重点就是通过 fcntl 函数设置进程状态为 FASYNC,经过这一步,驱动程序中的 fasync 函数就会执行。

完整代码如下:
驱动部分:

#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/wait.h>
#include <linux/poll.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define IRQ_CNT         1
#define IRQ_NAME        "asyncnoti-test"
#define KEY0VALUE       0x01
#define INVAKEY          0xFF
#define KEY_NUM         1

struct irq_keydesc{
   int gpio;
   int irqnum;
   unsigned char value;
   char name[12];
   irqreturn_t (*handler)(int,void *);
};

struct irq_dev{
   dev_t devid;          /* 设备号*/
   struct cdev cdev;     /* cdev*/
   struct class *class;    /* 类*/
   struct device *device;  /* 设备*/
   int major;              /* 主设备号*/
   int minor;              /* 次设备号*/
   struct device_node *nd; /* 设备树设备节点*/
   
   atomic_t keyvalue;      /* 有效的按键键值*/
   atomic_t releasekey;    /* 标记是否完成一次完整的按键,包括按下和释放*/

   struct timer_list timer;  /* 定义一个定时器*/
   struct irq_keydesc irqkeydesc[KEY_NUM]; /*按键描述数组*/
   unsigned char curkeynum; /* 当前的按键号*/

   wait_queue_head_t r_wait;  /* 读等待队列头*/
   struct fasync_struct *async_queue;  /* 异步相关结构体*/
};

struct irq_dev testirq;

static ssize_t irq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt){

   int ret = 0;
   unsigned char keyvalue = 0;
   unsigned char releasekey = 0;
   struct irq_dev *dev = (struct irq_dev*)filp->private_data;

   
   if(filp->f_flags & O_NONBLOCK){     /* 非阻塞的访问*/
       if(atomic_read(&dev->releasekey) == 0){
           return -EAGAIN;
       }
   }else{                             /*  阻塞访问*/
       /*加入等待队列,等待被唤醒,也就是有按键按下*/
       ret = wait_event_interruptible(dev->r_wait,atomic_read(&dev->releasekey));
       if(ret){
           goto wait_error;
       }
   }    
   
   keyvalue = atomic_read(&dev->keyvalue);
   releasekey = atomic_read(&dev->releasekey);
   
   if(releasekey){
       if(keyvalue & 0x80){
           keyvalue &= ~0x80;
           ret = copy_to_user(buf,&keyvalue,sizeof(keyvalue));        
       }else{
           
           goto data_error;
       }
       atomic_set(&dev->releasekey,0);/*按下标志清零*/
   }else{
       goto data_error;
   }    
   return 0;

wait_error:    
   return ret;
data_error:
   return -EINVAL;
}

static int irq_open(struct inode *inode, struct file *filp){
   printk("irq_open in\n");
   filp->private_data = &testirq;
   return 0;
}

static unsigned int irq_poll(struct file *filp, struct poll_table_struct *wait){
   unsigned int mask = 0;
   struct irq_dev *dev = (struct irq_dev *)filp->private_data;    
   poll_wait(filp,&dev->r_wait,wait);    /* 将等待队列头添加到poll_table中*/
   if(atomic_read(&dev->releasekey)){     /* 按键按下*/
       mask = POLLIN | POLLRDNORM;        /* 返回PLLIN*/
   }
   return mask;
}

static int irq_fasync(int fd, struct file *filp, int on){
   struct irq_dev *dev = (struct irq_dev *)filp->private_data;
   
   if( fasync_helper(fd,filp,on,&dev->async_queue) < 0){
       printk("irq_fasync out < 0\n ");
       return -EIO;
   }
   return 0;
}

static int irq_release(struct inode *inode, struct file *filp){
   return  irq_fasync(-1,filp,0);      /* 删除异步通知*/
}

static struct file_operations fops = {
   .owner = THIS_MODULE,
   .open = irq_open,
   .read = irq_read,
   .poll = irq_poll,
   .fasync = irq_fasync,
   .release = irq_release,
};

static irqreturn_t key0_handler(int irq,void *dev_id){
   struct irq_dev *dev = (struct irq_dev *)dev_id;

   dev->curkeynum = 0;
   dev->timer.data = (volatile long)dev_id;
   mod_timer(&dev->timer,jiffies + msecs_to_jiffies(10)); /* 10ms定时*/
   printk("key0_handler--\n");
   return IRQ_RETVAL(IRQ_HANDLED);
}

void timer_function(unsigned long arg){
   unsigned char value;
   unsigned char num;
   struct irq_keydesc *keydesc;
   struct irq_dev *dev = (struct irq_dev*)arg;

   num = dev->curkeynum;
   keydesc = &dev->irqkeydesc[num];

   value = gpio_get_value(keydesc->gpio);  /* 读取IO值*/
   if(value == 0){                         /*  按键还是按下的*/            
       atomic_set(&dev->keyvalue,keydesc->value);
   }else{                                   /* 按键松开*/
       atomic_set(&dev->keyvalue,keydesc->value | 0x80);
       atomic_set(&dev->releasekey,1);       /*标记按键松开,即完成了一次完整的按键*/
   }

   printk("timer func=---1---%d\n",&dev->releasekey);
   if(atomic_read(&dev->releasekey)){  /*一次完整的按键过程*/
       //wake_up_interruptible(&dev->r_wait);
       if(dev->async_queue){
           printk("send SIGIO to app\n");
           kill_fasync(&dev->async_queue,SIGIO,POLLIN); /* 释放SIGIO信号,运行应用层的信号SIGIO的处理函数*/
       }
   }
}

static int keyio_init(void){
   unsigned char i = 0;
   int ret = 0;

   testirq.nd = of_find_node_by_path("/key");
   if(testirq.nd == NULL){
       printk("key node not find\n");
       return -EINVAL;
   }

   /* 提取GPIO*/
   for(i = 0;i < KEY_NUM;i++){
       testirq.irqkeydesc[i].gpio = of_get_named_gpio(testirq.nd,"key-gpio",i); 
       if(testirq.irqkeydesc[i].gpio < 0){
           printk("can't get key%d\n",i);
       }
   }

   /* 初始化key使用的IO,并且设置成中断模式*/
   for(i = 0;i < KEY_NUM;i++){
       memset(testirq.irqkeydesc[i].name,0,sizeof(testirq.irqkeydesc[i].name));
       sprintf(testirq.irqkeydesc[i].name,"KEY%d",i);
       gpio_request(testirq.irqkeydesc[i].gpio,testirq.irqkeydesc[i].name);
       gpio_direction_input(testirq.irqkeydesc[i].gpio);
       testirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(testirq.nd,i);
#if 0
       testirq.irqkeydesc[i].irqnum = gpio_to_irq(testirq.irqkeydesc[i].gpio);
#endif
       printk("key%d:gpio=%d,irqnum=%d\n",i,testirq.irqkeydesc[i].gpio,testirq.irqkeydesc[i].irqnum);
   }
   
   /* 申请中断*/
   testirq.irqkeydesc[0].handler = key0_handler;
   testirq.irqkeydesc[0].value =   KEY0VALUE;

   for(i = 0;i < KEY_NUM; i++){
       ret = request_irq(testirq.irqkeydesc[i].irqnum,testirq.irqkeydesc[i].handler,
       IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,testirq.irqkeydesc[i].name,&testirq);

       if(ret < 0){
           printk("irq %d request failed\n",testirq.irqkeydesc[i].irqnum);
           return -EFAULT;
       }
   }

   /* 创建定时器*/
   init_timer(&testirq.timer);
   testirq.timer.function = timer_function;

   /* 初始化等待队列头*/
   init_waitqueue_head(&testirq.r_wait);
   return 0;
}

static int __init test_irq_init(void){

   /* 1、构建设备号*/
   if(testirq.major){
       testirq.devid = MKDEV(testirq.major,0);
       register_chrdev_region(testirq.devid,IRQ_CNT,IRQ_NAME);
   }else{
       alloc_chrdev_region(&testirq.devid,0,IRQ_CNT,IRQ_NAME);
       testirq.major = MAJOR(testirq.devid);
       testirq.minor = MINOR(testirq.devid);
   }

   /* 2、注册字符设备*/
   cdev_init(&testirq.cdev,&fops);
   cdev_add(&testirq.cdev,testirq.devid,IRQ_CNT);

   /* 3、创建类*/
   testirq.class = class_create(THIS_MODULE,IRQ_NAME);
   if(IS_ERR(testirq.class)){
       return PTR_ERR(testirq.class);
   }

   /* 4、创建设备节点*/
   testirq.device = device_create(testirq.class,NULL,testirq.devid,NULL,IRQ_NAME);
   if(IS_ERR(testirq.device)){
       return PTR_ERR(testirq.device);
   }
   
   /* 5、初始化按键*/
   atomic_set(&testirq.keyvalue,INVAKEY);
   atomic_set(&testirq.releasekey,0);
   keyio_init();
   return 0;
}

static void __exit test_irq_exit(void){

   unsigned char i = 0;
   
   /* 删除定时器*/
   del_timer_sync(&testirq.timer);

   /* 释放中断*/
   for(i = 0;i< KEY_NUM;i++){
       free_irq(testirq.irqkeydesc[i].irqnum,&testirq);
   }

   cdev_del(&testirq.cdev);
   unregister_chrdev_region(testirq.devid,IRQ_CNT);

   device_destroy(testirq.class,testirq.devid);
   class_destroy(testirq.class);
}

module_init(test_irq_init);
module_exit(test_irq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("jiang");

app代码如下:

#include "stdio.h"	
#include "unistd.h"	
#include "sys/types.h"	
#include "sys/stat.h"	
#include "fcntl.h"	
#include "stdlib.h"	
#include "string.h"	
#include "linux/ioctl.h"	
#include "poll.h"	
#include "sys/select.h"	
#include "sys/time.h"	
#include "signal.h"

static int g_fd;	
static void  sigio_signal_func(int signum){	
	int err = 0;	
	unsigned int keyvalue = 0;
	
	printf("read signum = %d\n",signum);	
	err = read(g_fd,&keyvalue,sizeof(keyvalue));	
	if(err < 0){	
		/* 读取错误*/	
		printf("read error\n");	
	}else{	
		printf("sigio signal! keyvalue=%d\n",keyvalue);	
	}	
}

int main(int argc, char *argv[])	
{
	
	int fd;		
	int ret = 0;		
	//int data = 0;		
	char *filename;		
	unsigned char data;		
	int flags = 0;	
	
	if (argc != 2) {		
		printf("Error Usage!\r\n");		
		return -1;		
	}	
	
	filename = argv[1];		
	fd = open(filename, O_RDWR);		
	if (fd < 0) {		
		printf("Can't open file %s\r\n", filename);		
		return -1;		
	}		

	g_fd = fd;	
	
	signal(SIGIO,sigio_signal_func);  /* 设置信号SIGIO的处理函数*/			
	fcntl(fd,F_SETOWN,getpid());  /* 设置当前进程接受SIGIO信号*/		
	flags = fcntl(fd,F_GETFD);	/* 获取当前进程的状态*/			
	fcntl(fd,F_SETFL,flags | FASYNC); /* 设置进程启用异步通知功能,经过这一步,驱动程序中的 fasync 函数就会执行。*/		
	while(1){
		sleep(2);		
	}
	
	close(fd);		
	return ret;		
}
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值