本文章来源于正点原子资料,记录下来,以后参考。
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;
}