platform按键驱动学习一:https://blog.youkuaiyun.com/caijiwyj/article/details/90182865
一、等待队列
(1) 定义并初始化"等待队列头"
wait_queue_head_t w_queue;
init_waitqueue_head(&w_queue); //会将自旋锁初始化为未锁,等待队列初始化为空的双向循环链表。
//宏名用于定义并初始化,相当于"快捷方式"
DECLARE_WAIT_QUEUE_HEAD (w_queue);
定义"等待队列"
/*定义并初始化一个名为name的等待队列 ,注意此处是定义一个wait_queue_t类型的变量name,并将其private与设置为tsk*/
DECLARE_WAITQUEUE(name,tsk);
(2)等待事件
#define wait_event(wq, condition)
do {
if (condition)
break;
__wait_event(wq, condition);
} while (0)
wait_event(queue,condition);等待以queue为等待队列头等待队列被唤醒,condition必须满足,否则阻塞
wait_event_interruptible(queue,condition);可被信号打断
wait_event_timeout(queue,condition,timeout);阻塞等待的超时时间,时间到了,不论condition是否满足,都要返回
wait_event_interruptible_timeout(queue,condition,timeout)
(3)唤醒队列
void __wake_up(wait_queue_head_t *q, unsigned int mode,
int nr_exclusive, void *key)
{
unsigned long flags;
spin_lock_irqsave(&q->lock, flags);
__wake_up_common(q, mode, nr_exclusive, 0, key);
spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL(__wake_up);
//唤醒等待队列.可唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERUPTIBLE状态的进程,和wait_event/wait_event_timeout成对使用
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
//和wake_up()唯一的区别是它只能唤醒TASK_INTERRUPTIBLE状态的进程.,与wait_event_interruptible、wait_event_interruptible_timeout、wait_event_interruptible_exclusive成对使用。
wake_up()与wake_event()或者wait_event_timeout成对使用,
wake_up_intteruptible()与wait_event_intteruptible()或者wait_event_intteruptible_timeout()成对使用。
#define wake_up_all(x) __wake_up(x, TASK_NORMAL, 0, NULL)
#define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)
#define wake_up_interruptible_all(x) __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)
//这些也基本都和wake_up/wake_up_interruptible一样
sleep_on(wait_queue_head_t *q);
interruptible_sleep_on(wait_queue_head_t *q);
sleep_on作用是把目前进程的状态置成TASK_UNINTERRUPTIBLE,并定义一个等待队列,之后把他附属到等待队列头q,直到资源可用,q引导的等待队列被唤醒。interruptible_sleep_on作用是一样的, 只不过它把进程状态置为TASK_INTERRUPTIBLE.
等待队列的流程:首先,定义并初始化等待队列,把进程的状态置成TASK_UNINTERRUPTIBLE或TASK_INTERRUPTIBLE,并将对待队列添加到等待队列头。最后,当进程被其他地方唤醒,将等待队列移除等待队列头。
在Linux内核中,使用set_current_state()和__add_wait_queue()函数来实现目前进程状态的改变,直接使用current->state = TASK_UNINTERRUPTIBLE类似的语句也是可以的。
二、驱动POLL()函数
应用程序使用select()或poll()会调用设备驱动程序的poll()函数,poll函数里poll_wait负责将当前进程放入wait_queue,就是上面说到的等待队列,直到有事件发生,poll_wait返回,最后根据返回的事件给用户返回可读写出错等标志的掩码。
驱动中的poll()函数原型:
unsigned int (*poll)(struct file *filp, struct poll_table *wait);
这个函数要进行下面两项工作。首先,对可能引起设备文件状态变化的等待队列调用poll_wait(),将对应的等待队列头添加到poll_table.最后返回表示是否能对设备进行无阻塞读写访问的掩码。在上面提到了一个poll_wait()函数,它的原型:
void poll_wait(struct file *filp, wait_queue_head_t *queue, poll_table *wait);
poll()函数最后应该返回设备资源的可获取状态
常量 | 说明 |
POLLIN | 普通或优先级带数据可读 |
POLLRDNORM | 普通数据可读 |
POLLRDBAND | 优先级带数据可读 |
POLLPRI | 高优先级数据可读 |
POLLOUT | 普通数据可写 |
POLLWRNORM | 普通数据可写 |
POLLWRBAND | 优先级带数据可写 |
POLLERR | 发生错误 |
POLLHUP | 发生挂起 |
POLLNVAL | 描述字不是一个打开的文件 |
下面是Poll驱动的模板:
static unsigned int XXX_poll(struct file *filp, poll_table *wait)
{
unsigned int mask = 0;
struct XXX_dev *dev = filp->private_data; //获得设备结构指针
...
poll_wait(filp, &dev->r_wait, wait); //加读等待对列头
poll_wait(filp ,&dev->w_wait, wait); //加写等待队列头
if(...)//可读
{
mask |= POLLIN | POLLRDNORM; //标识数据可获得
}
if(...)//可写
{
mask |= POLLOUT | POLLRDNORM; //标识数据可写入
}
..
return mask;
}
参考:https://www.cnblogs.com/hanyan225/archive/2010/10/13/1850497.html
三、Platform按键驱动测试代码
测试代码的功能是按键按下LED灯亮:
/*********************************************************************************
* Copyright: (C) 2019 WuYujun<540726307@qq.com>
* All rights reserved.
*
* Filename: test_plat_buttom_ctl_LED.c
* Description: This file test platform buttom driver
*
* Version: 1.0.0(2019年05月14日)
* Author: WuYujun <540726307@qq.com>
* ChangeLog: 1, Release initial version on "2019年05月14日 15时44分40秒"
*
********************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <stdarg.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <string.h>
#define PLATDRV_MAGIC 0x60
#define LED_OFF _IO (PLATDRV_MAGIC, 0x18)
#define LED_ON _IO (PLATDRV_MAGIC, 0x19)
/* read() 读到的是返回的值对应的位就是对应的按键状态 */
#define KEY1 0x1
#define KEY2 0x2
#define KEY3 0x4
#define KEY4 0x8
#define DEV_LED "/dev/led"
#define DEV_KEY "/dev/key"
int main(int argc, char **argv)
{
int led_fd ;
int key_fd ;
fd_set rset;
int rv = -1 ;
int key_status = 0 ;
int flag1 = 0 ;
int flag2 = 0 ;
int flag3 = 0 ;
int flag4 = 0 ;
led_fd = open(DEV_LED,O_RDWR) ;
key_fd = open(DEV_KEY,O_RDWR) ;
if(led_fd < 0)
{
printf("open %s failed: %s\n", DEV_LED, strerror(errno)) ;
return -1 ;
}
if(key_fd < 0)
{
printf("open %s failed: %s\n",DEV_KEY, strerror(errno)) ;
return -2 ;
}
while(1)
{
FD_ZERO(&rset) ;
FD_SET(key_fd, &rset) ;
rv = select(key_fd+1, &rset, NULL, NULL, NULL) ;
if(rv < 0)
{
printf("select() failed: %s\n", strerror(errno)) ;
goto cleanup ;
}
if(rv == 0)
{
printf("select() time out!\n") ;
goto cleanup ;
}
if( FD_ISSET(key_fd, &rset) )
{
rv = read(key_fd,&key_status,sizeof(key_status)) ;
if(rv < 0)
{
printf("read from %s failed:%s\n", DEV_KEY, strerror(errno)) ;
return -2 ;
}
if(KEY1==key_status)
{
if(flag1 == 0)
{
ioctl(led_fd, LED_ON,0) ;
flag1 = 1 ;
}
else
{
ioctl(led_fd, LED_OFF,0) ;
flag1 = 0 ;
}
}
if(KEY2==key_status)
{
if(flag2 == 0)
{
ioctl(led_fd, LED_ON,1) ;
flag2 = 1 ;
}
else
{
ioctl(led_fd, LED_OFF,1) ;
flag2 = 0 ;
}
}
if(KEY3==key_status)
{
if(flag3 == 0)
{
ioctl(led_fd, LED_ON,2) ;
flag3 = 1 ;
}
else
{
ioctl(led_fd, LED_OFF,2) ;
flag3 = 0 ;
}
}
if(KEY4==key_status)
{
if(flag4 == 0)
{
ioctl(led_fd, LED_ON,3) ;
flag4 = 1 ;
}
else
{
ioctl(led_fd, LED_OFF,3) ;
flag4 = 0 ;
}
}
}
key_status = 0 ;
}
cleanup:
close(key_fd) ;
close(led_fd) ;
return 0 ;
}
运行效果: