按键驱动:poll机制
所有的系统调用,基于都可以在它的名字前加上“sys_”前缀,这就是它在内核中对应的函数。比如系统调用open、read、write、poll,与之对应的内核函数为:sys_open、sys_read、sys_write、sys_poll。
内核框架
对于系统调用poll或select,它们对应的内核函数都是sys_poll。分析sys_poll,即可理解poll机制。
sys_poll():
Linux 2.6内核中,位于fs/select.c。在linux 3.10中,没有找到,只找到声明,在/include/linux/syscalls.h中。
这个函数只对传入的参数稍作处理,然后调用do_sys_poll();
do_sys_poll():位于/fs/select.c
int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout)
{
struct poll_wqueues table;
...
poll_initwait(&table);
fdcount = do_poll(nfds, head, &table, end_time);
...
}
poll_initwait(&table)结果:table->pt->qproc = __pollwait,即注册了一个回调函数。
__pollwait将在驱动的poll函数里用到。
问:怎么用?
答:驱动里的poll需要调用poll_wait,这个函数最终会调用到__pollwait。
问:为什么?
答:查阅do_poll()函数源码,函数退出的条件是do_pollfd(pfd, pt)非零值,或超时。若这2个条件都不满足,那么进程就会休眠,直到超时唤醒。若进程休眠后,等待的事件到来怎么办?显然,还得有另外的办法来唤醒进程,那就是驱动程序来唤醒。故驱动调用poll_wait的意义就是:让驱动程序知道该唤醒谁。这也是为什么poll_wait()不会休眠的原因,它只是用来把当前进程挂到某一队列,以便驱动程序知道唤醒谁。
真正的休眠发生在:应用程序调用poll > sys_poll > do_sys_poll > poll_initwait,do_poll > do_pollfd > 我们自己写的poll函数后,再调用schedule_timeout进入休眠。
do_poll():
static int do_poll(unsigned int nfds, struct poll_list *list,
struct poll_wqueues *wait, struct timespec *end_time)
{
...
for (;;) {
...
for (; pfd != pfd_end; pfd++) {
/*
* Fish for events. If we found one, record it
* and kill poll_table->_qproc, so we don't
* needlessly register any other waiters after
* this. They'll get immediately deregistered
* when we break out and return.
*/
if (do_pollfd(pfd, pt)) {
count++;
pt->_qproc = NULL;
}
}
...
if (count || timed_out) // 若条件满足,在这里跳出循环
break;
...
// 进程休眠,一切正常的话将会超时唤醒。
// 若期间期望的事件到来,将由驱动程序唤醒
if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack))
timed_out = 1;
}
return count;
}
do_pollfd():
static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait)
{
...
if (f.file->f_op && f.file->f_op->poll) {
pwait->_key = pollfd->events|POLLERR|POLLHUP;
// 此处调用驱动里注册的poll
mask = f.file->f_op->poll(f.file, pwait);
}
...
return mask;
}
可见,此处将会调用驱动里注册的poll函数。
问:由此引发驱动的poll函数怎么写?
答:根据已知的结果推断:
A. 把进程挂到某一队列,让驱动知道事件就绪时,唤醒谁。
B. 看一下此刻事件有没有就绪,若就绪则返回非零值,否则返回0。此处就对应着上述do_poll()里的15行,这种情况下,进程没机会休眠就直接获得结果了。
现在来总结一下poll机制:
1. poll > sys_poll > do_sys_poll > poll_initwait,poll_initwait函数注册一下回调函数__pollwait,它就是我们的驱动程序执行poll_wait时,真正被调用的函数。
2. 接下来执行file->f_op->poll,即我们驱动程序里自己实现的poll函数
它会调用poll_wait把自己挂入某个队列,这个队列也是我们的驱动自己定义的;
它还判断一下设备是否就绪。
3. 如果设备未就绪,do_sys_poll里会让进程休眠一定时间
4. 进程被唤醒的条件有2:一是上面说的“一定时间”到了,二是被驱动程序唤醒。驱动程序发现条件就绪时,就把“某个队列”上挂着的进程唤醒,这个队列,就是前面通过poll_wait把本进程挂过去的队列。
5. 如果驱动程序没有去唤醒进程,直到应用程序的poll调用传入的时间到达也会被唤醒。
驱动:
/*
* 引脚:PI0,1,2
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/leds.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/module.h>
#include <linux/pinctrl/consumer.h>
#include <linux/err.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/uaccess.h>
#include <linux/interrupt.h>
#include <asm/io.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
struct pin_desc{
int pin;
int val;
};
/*
* 按下时,返回:0x81, 0x82, 0x83
*
*/
static struct pin_desc pins_desc[3] = {
{NUC970_PI0, 0x1},
{NUC970_PI1, 0x2},
{NUC970_PI2, 0x3},
};
static unsigned char val;
static DECLARE_WAIT_QUEUE_HEAD(buttons_waitq);
int evpress = 0;
static irqreturn_t buttons_handler(int irq, void *dev_id)
{
struct pin_desc *pin = (struct pin_desc *)dev_id;
//int res;
//res = gpio_get_value(pin->pin);
val = 0x80 | pin->val;
evpress = 1;
wake_up_interruptible(&buttons_waitq);
return IRQ_HANDLED;
}
static int buttons_open(struct inode *inode, struct file *filp)
{
// request_irq会自动设置引脚,此处不再配置
request_irq(gpio_to_irq(pins_desc[0].pin), buttons_handler, IRQF_TRIGGER_FALLING, "S1", &pins_desc[0]);
request_irq(gpio_to_irq(pins_desc[1].pin), buttons_handler, IRQF_TRIGGER_FALLING, "S2", &pins_desc[1]);
request_irq(gpio_to_irq(pins_desc[2].pin), buttons_handler, IRQF_TRIGGER_FALLING, "S3", &pins_desc[2]);
return 0;
}
static ssize_t buttons_read (struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
if (count != 1)
{
return -EINVAL;
}
wait_event_interruptible(buttons_waitq, evpress);
evpress = 0;
copy_to_user(buf, &val, 1);
return 1;
}
int buttons_release (struct inode *inode, struct file *filp)
{
free_irq(gpio_to_irq(pins_desc[0].pin), &pins_desc[0]);
free_irq(gpio_to_irq(pins_desc[1].pin), &pins_desc[1]);
free_irq(gpio_to_irq(pins_desc[2].pin), &pins_desc[2]);
return 0;
}
static unsigned int buttons_poll(struct file *file, poll_table *wait)
{
unsigned int mask = 0;
poll_wait(file, &buttons_waitq, wait); // 不会立即休眠,只是把进程挂到buttons_waitq队列
if (evpress)
{
mask = POLLIN | POLLRDNORM;
}
return mask;
}
static struct file_operations buttons_fops = {
.owner = THIS_MODULE,
.open = buttons_open,
.read = buttons_read,
.release = buttons_release,
.poll = buttons_poll,
};
static int major;
static struct class *buttons_class;
static struct device *button_device;
static int buttons_init(void)
{
major = register_chrdev(0, "buttons", &buttons_fops);
buttons_class = class_create(THIS_MODULE, "buttons");
button_device = device_create(buttons_class, NULL, MKDEV(major, 0), NULL, "buttons");
return 0;
}
static void buttons_exit(void)
{
device_destroy(buttons_class, MKDEV(major, 0));
class_destroy(buttons_class);
unregister_chrdev(major, "buttons");
}
module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL");
测试程序:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <poll.h>
int main(void)
{
int fd;
unsigned char key_val;
struct pollfd fds[1];
int res;
fd = open("/dev/buttons", O_RDONLY);
if (fd < 0)
{
printf("Can't open /dev/buttons\n");
return -1;
}
fds[0].fd = fd;
fds[0].events = POLLIN;
while (1)
{
res = poll(fds, 1, 5000);
if (res > 0)
{
read(fd, &key_val, 1);
printf("key_val = 0x%x\n", key_val);
}
else if (res == 0)
{
printf("time out\n");
}
}
close(fd);
return 0;
}