Linux驱动 fasync异步通知方法

  • Post author:
  • Post category:linux




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



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 标记的时候,}































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






























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 函数就会执行。}



















f


i


l



e










o


















p


e


r


a


t


i


o


n


s

















f


a


s


y


n


c




























驱动程序中的 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中注册的信号处理函数将会执行}













a


p


p

















































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;		
}



版权声明:本文为qq_31487199原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。