一、字符设备驱动3-poll机制按键驱动

按键驱动:poll机制

所有的系统调用,基于都可以在它的名字前加上sys_”前缀,这就是它在内核中对应的函数。比如系统调用openreadwritepoll,与之对应的内核函数为:sys_opensys_readsys_writesys_poll

内核框架

对于系统调用pollselect,它们对应的内核函数都是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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值