阻塞和非阻塞
IO
是
Linux
驱动开发里面很常见的两种设备访问模式,在编写驱动的时候
一定要考虑到阻塞和非阻塞。本章我们就来学习一下阻塞和非阻塞
IO
,以及如何在驱动程序中
处理阻塞与非阻塞,如何在驱动程序使用等待队列和
poll
机制。
这里的IO是对文件的IO操作
当应用程序对设备驱动进行操作的时候,如果不能获取到设备资源,
那么阻塞式 IO 就会将应用程序对应的线程挂起,直到设备资源可以获取为止。
应用程序使用open
打开驱动文件,默认是阻塞方式打开
对于非阻塞 IO,应用程序对应的线程不会挂起,它要么一直轮询等待,直到设备资源可以使用,要么就直接放弃。
应用程序使用open 函数打开“/dev/xxx_dev”设备文件的时候添加了参数“O_NONBLOCK”,表示以非阻塞方式打开设备,这样从设备中读取数据的时候就是非阻塞方式的
等待队列(步骤):这一节主要讲非阻塞
1、等待队列头
阻塞访问最大的好处就是当设备文件不可操作的时候进程可以进入休眠态,这样可以将
CPU
资源让出来。但是,当设备文件可以操作的时候就必须唤醒进程,一般在中断函数里面完
成唤醒工作。
Linux 内核提供了等待队列(wait queue)
来实现阻塞进程的唤醒工作,如果我们要
在驱动中使用等待队列,必须创建并初始化一个
等待队列头
,等待队列头使用结构体
wait_queue_head_t
表示,
wait_queue_head_t
结构体定义在文件
include/linux/wait.h
中,结构体内容如下所示:
2
、等待队列项
等待队列头就是一个等待队列的头部,每个访问设备的进程都是一个队列项,当设备不可
用的时候就要将这些进程对应的等待队列项添加到等待队列里面。结构体
wait_queue_t
表示等
待队列项,结构体内容如下:
3
、将队列项添加
/
移除等待队列头
当设备不可访问的时候就需要将进程对应的等待队列项添加到前面创建的等待队列头中,
只有添加到等待队列头中以后进程才能进入休眠态
。当设备可以访问以后再将进程对应的等待
队列项从等待队列头中移除即可,等待队列项添加
API
函数如下:
4
、等待唤醒(主动唤醒)
当设备可以使用的时候就要唤醒进入休眠态的进程,唤醒可以使用如下两个函数:
void wake_up(wait_queue_head_t *q)
void wake_up_interruptible(wait_queue_head_t *q)
参数
q
就是要唤醒的等待队列头,这两个函数会将这个等待队列头中的所有进程都唤醒。
wake_up
函数可以唤醒处于
TASK_INTERRUPTIBLE
和
TASK_UNINTERRUPTIBLE
状态的进
程,而
wake_up_interruptible
函数只能唤醒处于
TASK_INTERRUPTIBLE
状态的进程。
5
、等待事件(被动唤醒)
除了主动唤醒以外,也可以设置等待队列等待某个事件,当这个事件满足以后就自动唤醒
等待队列中的进程,和等待事件有关的
API
函数如表
52.1.2.1
所示:
轮询(POLL函数)
应用程序的调用的函数都可以看man手册,很重要
如果用户应用程序以非阻塞的方式访问设备,设备驱动程序就要提供非阻塞的处理方式,
也就是轮询。
poll
、
epoll
和
select
可以用于处理轮询,应用程序通过
select
、
epoll
或
poll
函数来
查询设备是否可以操作,如果可以操作的话就从设备读取或者向设备写入数据。当应用程序调
用
select
、
epoll
或
poll
函数的时候设备驱动程序中的
poll
函数就会执行,因此需要在设备驱动
程序中编写
poll
函数。我们先来看一下应用程序中使用的
select、poll 和 epoll
这三个函数。
但是统一对应到驱动程序中时就一个poll函数
poll
函数
在单个线程中,
select
函数能够监视的文件描述符数量有最大的限制,一般为
1024
,可以
修改内核将监视的文件描述符数量改大,但是这样会降低效率!这个时候就可以使用
poll
函数,
poll
函数本质上和
select
没有太大的差别,
但是 poll 函数没有最大文件描述符限制
,
Linux
应用
程序中
poll
函数原型如下所示:
=========================================================================
Linux
驱动下的
poll
操作函数
当应用程序调用
select
或
poll
函数来对驱动程序进行非阻塞访问的时候,驱动程序
file_operations
操作集中的
poll
函数就会执行。所以驱动程序的编写者需要提供对应的
poll
函
数,
poll
函数原型如下所示:
unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait)
函数参数和返回值含义如下:
filp
:
要打开的设备文件
(
文件描述符
)
。
wait
:
结构体
poll_table_struct
类型指针,由应用程序传递进来的。一般将此参数传递给
poll_wait
函数。
返回值
:
向应用程序返回设备或者资源状态,可以返回的资源状态如下:
POLLIN 有数据可以读取。
POLLPRI 有紧急的数据需要读取。
POLLOUT 可以写数据。
POLLERR 指定的文件描述符发生错误。
POLLHUP 指定的文件描述符挂起。
POLLNVAL 无效的请求。
POLLRDNORM
等同于
POLLIN
,普通数据可读
我们需要在驱动程序的
poll
函数中调用
poll_wait
函数,
poll_wait
函数不会引起阻塞,只是
将应用程序添加到
poll_table
中,
poll_wait
函数原型如下:
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
参数
wait_address
是要添加到
poll_table
中的等待队列头,参数
p
就是
poll_table
,就是
file_operations
中
poll
函数的
wait
参数。
在中断实验基础上完成
1、阻塞式访问使CPU占用率降低
非阻塞(应用反复问驱动直到可读为止),阻塞(驱动让应用先滚一边,等可读了在过来读)
阻塞实验例程
#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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : block.c
作者 : 左忠凯
版本 : V1.0
描述 : 阻塞IO访问
其他 : 无
论坛 : www.openedv.com
日志 : 初版V1.0 2019/7/26 左忠凯创建
***************************************************************/
#define IMX6UIRQ_CNT 1 /* 设备号个数 */
#define IMX6UIRQ_NAME "blockio" /* 名字 */
#define KEY0VALUE 0X01 /* KEY0按键值 */
#define INVAKEY 0XFF /* 无效的按键值 */
#define KEY_NUM 1 /* 按键数量 */
/* 中断IO描述结构体 */
struct irq_keydesc {
int gpio; /* gpio */
int irqnum; /* 中断号 */
unsigned char value; /* 按键对应的键值 */
char name[10]; /* 名字 */
irqreturn_t (*handler)(int, void *); /* 中断服务函数 */
};
/* imx6uirq设备结构体 */
struct imx6uirq_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]; /* 按键init述数组 */
unsigned char curkeynum; /* 当前init按键号 */
//
wait_queue_head_t r_wait; /* 读等待队列头 */
};
struct imx6uirq_dev imx6uirq; /* irq设备 */
/* @description : 中断服务函数,开启定时器
* 定时器用于按键消抖。
* @param - irq : 中断号
* @param - dev_id : 设备结构。
* @return : 中断执行结果
*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{
struct imx6uirq_dev *dev = (struct imx6uirq_dev*)dev_id;
dev->curkeynum = 0;
dev->timer.data = (volatile long)dev_id;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10)); /* 10ms定时 */
return IRQ_RETVAL(IRQ_HANDLED);
}
/* @description : 定时器服务函数,用于按键消抖,定时器到了以后
* 再次读取按键值,如果按键还是处于按下状态就表示按键有效。
* @param - arg : 设备结构变量
* @return : 无
*/
void timer_function(unsigned long arg)
{
unsigned char value;
unsigned char num;
struct irq_keydesc *keydesc;
struct imx6uirq_dev *dev = (struct imx6uirq_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, 0x80 | keydesc->value);
atomic_set(&dev->releasekey, 1); /* 标记松开按键,即完成一次完整的按键过程 */
}
///
/* 唤醒进程 */
if(atomic_read(&dev->releasekey)) { /* 完成一次按键过程 */
/* wake_up(&dev->r_wait); */
wake_up_interruptible(&dev->r_wait);
}
}
/*
* @description : 按键IO初始化(我发现了 这里面基本都是一些初始化操作)
* @param : 无
* @return : 无
*/
static int keyio_init(void)
{
unsigned char i = 0;
char name[10];
int ret = 0;
imx6uirq.nd = of_find_node_by_path("/key");
if (imx6uirq.nd== NULL){
printk("key node not find!\r\n");
return -EINVAL;
}
/* 提取GPIO */
for (i = 0; i < KEY_NUM; i++) {
imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd ,"key-gpio", i);
if (imx6uirq.irqkeydesc[i].gpio < 0) {
printk("can't get key%d\r\n", i);
}
}
/* 初始化key所使用的IO,并且设置成中断模式 */
for (i = 0; i < KEY_NUM; i++) {
memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(name)); /* 缓冲区清零 */
sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i); /* 组合名字 */
gpio_request(imx6uirq.irqkeydesc[i].gpio, name);
gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);
imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);
#if 0
imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);
#endif
}
/* 申请中断 */
imx6uirq.irqkeydesc[0].handler = key0_handler;
imx6uirq.irqkeydesc[0].value = KEY0VALUE;
for (i = 0; i < KEY_NUM; i++) {
ret = request_irq(imx6uirq.irqkeydesc[i].irqnum, imx6uirq.irqkeydesc[i].handler,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, imx6uirq.irqkeydesc[i].name, &imx6uirq);
if(ret < 0){
printk("irq %d request failed!\r\n", imx6uirq.irqkeydesc[i].irqnum);
return -EFAULT;
}
}
/* 创建定时器 */
init_timer(&imx6uirq.timer);
imx6uirq.timer.function = timer_function;
/* 初始化等待队列头 */
init_waitqueue_head(&imx6uirq.r_wait);
return 0;
}
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
filp->private_data = &imx6uirq; /* 设置私有数据 */
return 0;
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t imx6uirq_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 imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
#if 0
/* 加入等待队列,等待被唤醒,也就是有按键按下
此函数会将进程设置为TASK——UNINTERRUPTIBLE状态
等待按键有效-有效-唤醒
wait_event_interruptible 是被动等待事件发生*/
ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey));
if (ret) {
goto wait_error;
}
#endif
// 当设备不可访问的时候需要将进程对应的 等待队列项 添加到 等待队列头 中
DECLARE_WAITQUEUE(wait, current); /* 定义一个等待队列 */
if(atomic_read(&dev->releasekey) == 0) { /* 没有按键按下 则置为休眠状态*/
add_wait_queue(&dev->r_wait, &wait); /* 将等待队列添加到等待队列头(休眠) */
__set_current_state(TASK_INTERRUPTIBLE);/* 设置任务状态为可被打断的状态 */
schedule(); /* 进行一次任务切换 */
// 唤醒以后从这运行
// 由于设置了可被中断唤醒,所以需要判断
// 1.被按键中断唤醒。2.被信号唤醒
if(signal_pending(current)) { /* 判断是否为信号引起的唤醒 */
// 无效的信号量
ret = -ERESTARTSYS;
goto wait_error; //goto的意思是直接跳转至wait_error即247行 中间的不执行了
}
// 有效的按键值
__set_current_state(TASK_RUNNING); /* 将当前任务设置为运行状态 */
remove_wait_queue(&dev->r_wait, &wait); /* 将对应的队列项从等待队列头删除 */
}
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:
set_current_state(TASK_RUNNING); /* 设置任务为运行态 */
remove_wait_queue(&dev->r_wait, &wait); /* 将等待队列移除 */
return ret;
data_error:
return -EINVAL;
}
/* 设备操作函数 */
static struct file_operations imx6uirq_fops = {
.owner = THIS_MODULE,
.open = imx6uirq_open,
.read = imx6uirq_read,
};
/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init imx6uirq_init(void)
{
/* 1、构建设备号 */
if (imx6uirq.major) {
imx6uirq.devid = MKDEV(imx6uirq.major, 0);
register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
} else {
alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
imx6uirq.major = MAJOR(imx6uirq.devid);
imx6uirq.minor = MINOR(imx6uirq.devid);
}
/* 2、注册字符设备 */
cdev_init(&imx6uirq.cdev, &imx6uirq_fops);
cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
/* 3、创建类 */
imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
if (IS_ERR(imx6uirq.class)) {
return PTR_ERR(imx6uirq.class);
}
/* 4、创建设备 */
imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
if (IS_ERR(imx6uirq.device)) {
return PTR_ERR(imx6uirq.device);
}
/* 5、始化按键 */
atomic_set(&imx6uirq.keyvalue, INVAKEY);
atomic_set(&imx6uirq.releasekey, 0);
keyio_init();
return 0;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit imx6uirq_exit(void)
{
unsigned i = 0;
/* 删除定时器 */
del_timer_sync(&imx6uirq.timer); /* 删除定时器 */
/* 释放中断 */
for (i = 0; i < KEY_NUM; i++) {
free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq);
gpio_free(imx6uirq.irqkeydesc[i].gpio);
}
cdev_del(&imx6uirq.cdev);
unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
device_destroy(imx6uirq.class, imx6uirq.devid);
class_destroy(imx6uirq.class);
}
module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
非阻塞实验例程
#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>
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : noblock.c
作者 : 左忠凯
版本 : V1.0
描述 : 非阻塞IO访问
其他 : 无
论坛 : www.openedv.com
日志 : 初版V1.0 2019/7/26 左忠凯创建
***************************************************************/
#define IMX6UIRQ_CNT 1 /* 设备号个数 */
#define IMX6UIRQ_NAME "noblockio" /* 名字 */
#define KEY0VALUE 0X01 /* KEY0按键值 */
#define INVAKEY 0XFF /* 无效的按键值 */
#define KEY_NUM 1 /* 按键数量 */
/* 中断IO描述结构体 */
struct irq_keydesc {
int gpio; /* gpio */
int irqnum; /* 中断号 */
unsigned char value; /* 按键对应的键值 */
char name[10]; /* 名字 */
irqreturn_t (*handler)(int, void *); /* 中断服务函数 */
};
/* imx6uirq设备结构体 */
struct imx6uirq_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]; /* 按键init述数组 */
unsigned char curkeynum; /* 当前init按键号 */
wait_queue_head_t r_wait; /* 读等待队列头 */
};
struct imx6uirq_dev imx6uirq; /* irq设备 */
/* @description : 中断服务函数,开启定时器
* 定时器用于按键消抖。
* @param - irq : 中断号
* @param - dev_id : 设备结构。
* @return : 中断执行结果
*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{
struct imx6uirq_dev *dev = (struct imx6uirq_dev*)dev_id;
dev->curkeynum = 0;
dev->timer.data = (volatile long)dev_id;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10)); /* 10ms定时 */
return IRQ_RETVAL(IRQ_HANDLED);
}
/* @description : 定时器服务函数,用于按键消抖,定时器到了以后
* 再次读取按键值,如果按键还是处于按下状态就表示按键有效。
* @param - arg : 设备结构变量
* @return : 无
*/
void timer_function(unsigned long arg)
{
unsigned char value;
unsigned char num;
struct irq_keydesc *keydesc;
struct imx6uirq_dev *dev = (struct imx6uirq_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, 0x80 | keydesc->value);
atomic_set(&dev->releasekey, 1); /* 标记松开按键,即完成一次完整的按键过程 */
}
//
/* 唤醒进程 */
// 唤醒和初始化操作和代码位置与阻塞例程相同
if(atomic_read(&dev->releasekey)) { /* 完成一次按键过程 */
/* wake_up(&dev->r_wait); */
wake_up_interruptible(&dev->r_wait);
}
}
/*
* @description : 按键IO初始化
* @param : 无
* @return : 无
*/
static int keyio_init(void)
{
unsigned char i = 0;
char name[10];
int ret = 0;
imx6uirq.nd = of_find_node_by_path("/key");
if (imx6uirq.nd== NULL){
printk("key node not find!\r\n");
return -EINVAL;
}
/* 提取GPIO */
for (i = 0; i < KEY_NUM; i++) {
imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd ,"key-gpio", i);
if (imx6uirq.irqkeydesc[i].gpio < 0) {
printk("can't get key%d\r\n", i);
}
}
/* 初始化key所使用的IO,并且设置成中断模式 */
for (i = 0; i < KEY_NUM; i++) {
memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(name)); /* 缓冲区清零 */
sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i); /* 组合名字 */
gpio_request(imx6uirq.irqkeydesc[i].gpio, name);
gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);
imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);
#if 0
imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);
#endif
}
/* 申请中断 */
imx6uirq.irqkeydesc[0].handler = key0_handler;
imx6uirq.irqkeydesc[0].value = KEY0VALUE;
for (i = 0; i < KEY_NUM; i++) {
ret = request_irq(imx6uirq.irqkeydesc[i].irqnum, imx6uirq.irqkeydesc[i].handler,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, imx6uirq.irqkeydesc[i].name, &imx6uirq);
if(ret < 0){
printk("irq %d request failed!\r\n", imx6uirq.irqkeydesc[i].irqnum);
return -EFAULT;
}
}
/* 创建定时器 */
init_timer(&imx6uirq.timer);
imx6uirq.timer.function = timer_function;
/* 初始化等待队列头 */
init_waitqueue_head(&imx6uirq.r_wait);
return 0;
}
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
filp->private_data = &imx6uirq; /* 设置私有数据 */
return 0;
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t imx6uirq_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 imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
///
if (filp->f_flags & O_NONBLOCK) { /* 非阻塞访问 */
if(atomic_read(&dev->releasekey) == 0) /* 没有按键按下,返回-EAGAIN */
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;
}
/* 非阻塞的关键,在驱动层定义poll函数
以非阻塞方式读取,还需要改APP.c文件
* @description : poll函数,用于处理非阻塞访问
* @param - filp : 要打开的设备文件(文件描述符)
* @param - wait : 等待列表(poll_table)
* @return : 设备或者资源状态,
*/
unsigned int imx6uirq_poll(struct file *filp, struct poll_table_struct *wait)
{
unsigned int mask = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_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 struct file_operations imx6uirq_fops = {
.owner = THIS_MODULE,
.open = imx6uirq_open,
.read = imx6uirq_read,
.poll = imx6uirq_poll, //注意这里,多了一个poll函数
};
/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init imx6uirq_init(void)
{
/* 1、构建设备号 */
if (imx6uirq.major) {
imx6uirq.devid = MKDEV(imx6uirq.major, 0);
register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
} else {
alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
imx6uirq.major = MAJOR(imx6uirq.devid);
imx6uirq.minor = MINOR(imx6uirq.devid);
}
/* 2、注册字符设备 */
cdev_init(&imx6uirq.cdev, &imx6uirq_fops);
cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
/* 3、创建类 */
imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
if (IS_ERR(imx6uirq.class)) {
return PTR_ERR(imx6uirq.class);
}
/* 4、创建设备 */
imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
if (IS_ERR(imx6uirq.device)) {
return PTR_ERR(imx6uirq.device);
}
/* 5、始化按键 */
atomic_set(&imx6uirq.keyvalue, INVAKEY);
atomic_set(&imx6uirq.releasekey, 0);
keyio_init();
return 0;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit imx6uirq_exit(void)
{
unsigned i = 0;
/* 删除定时器 */
del_timer_sync(&imx6uirq.timer); /* 删除定时器 */
/* 释放中断 */
for (i = 0; i < KEY_NUM; i++) {
free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq);
gpio_free(imx6uirq.irqkeydesc[i].gpio);
}
cdev_del(&imx6uirq.cdev);
unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
device_destroy(imx6uirq.class, imx6uirq.devid);
class_destroy(imx6uirq.class);
}
module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");