static void input_pass_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
struct input_handler *handler;
struct input_handle *handle; rcu_read_lock(); handle = rcu_dereference(dev->grab);
if (handle)
handle->handler->event(handle, type, code, value);
else {
bool filtered = false; list_for_each_entry_rcu(handle, &dev->h_list, d_node) {
if (!handle->open)
continue; handler = handle->handler;
if (!handler->filter) {
if (filtered)
break; handler->event(handle, type, code, value); } else if (handler->filter(handle, type, code, value))
filtered = true;
}
} rcu_read_unlock();
}

84至86行,如果有为Input设备指定handle,那么就执行该handle的handler的event函数,这里默认是没有指定的,用户程序可以通过ioctl函数来指定。

90行,遍历Input设备的handle链表,经过一些检查后会执行handler->event函数,即,drivers/input/evdev.c里的evdev_event函数,这时Input消息已经传递到事件驱动程序层了,暂时先不说evdev_event函数,但是要记得Input消息已经来到了这里。我们知道,应用程序使用设备之前要先open它(这里忽略了网络设备),最终会调用驱动程序里的open函数,那么下面看drivers/input/evdev.c里定义的open函数evdev_open。

 static int evdev_open(struct inode *inode, struct file *file)
{
struct evdev *evdev;
struct evdev_client *client;
int i = iminor(inode) - EVDEV_MINOR_BASE;
unsigned int bufsize;
int error; if (i >= EVDEV_MINORS)
return -ENODEV; error = mutex_lock_interruptible(&evdev_table_mutex);
if (error)
return error;
evdev = evdev_table[i];
if (evdev)
get_device(&evdev->dev);
mutex_unlock(&evdev_table_mutex); if (!evdev)
return -ENODEV; bufsize = evdev_compute_buffer_size(evdev->handle.dev); client = kzalloc(sizeof(struct evdev_client) +
bufsize * sizeof(struct input_event),
GFP_KERNEL);
if (!client) {
error = -ENOMEM;
goto err_put_evdev;
} client->bufsize = bufsize;
spin_lock_init(&client->buffer_lock);
client->evdev = evdev;
evdev_attach_client(evdev, client); error = evdev_open_device(evdev);
if (error)
goto err_free_client; file->private_data = client;
nonseekable_open(inode, file); return ; err_free_client:
evdev_detach_client(evdev, client);
kfree(client);
err_put_evdev:
put_device(&evdev->dev);
return error;
}

264行,获取次设备号。

268行,如果次设备号大于EVDEV_MINORS,就不用往下走了,返回出错。

274行,从evdev_table数组中取出对应的struct evdev实例。

282行,调用evdev_compute_buffer_size函数计算环形缓冲区的长度。

 static unsigned int evdev_compute_buffer_size(struct input_dev *dev)
{
unsigned int n_events =
max(dev->hint_events_per_packet * EVDEV_BUF_PACKETS,
EVDEV_MIN_BUFFER_SIZE); return roundup_pow_of_two(n_events);
}

254行,EVDEV_BUF_PACKETS的值为8,hint_events_per_packet为0,因此n_events的值就为EVDEV_MIN_BUFFER_SIZE,即64。

257行,将n_events的值调整到2的幂次方。

回到evdev_open函数,284至290行,为struct evdev_client实例分配内存,注意所分配内存的大小。

295行,调用evdev_attach_client函数,定义如下:

 static void evdev_attach_client(struct evdev *evdev,
struct evdev_client *client)
{
spin_lock(&evdev->client_lock);
list_add_tail_rcu(&client->node, &evdev->client_list);
spin_unlock(&evdev->client_lock);
synchronize_rcu();
}

172行,就是将client加入到struct evdev实例的链表尾部。

297行,evdev_open_device函数的定义:

 static int evdev_open_device(struct evdev *evdev)
{
int retval; retval = mutex_lock_interruptible(&evdev->mutex);
if (retval)
return retval; if (!evdev->exist)
retval = -ENODEV;
else if (!evdev->open++) {
retval = input_open_device(&evdev->handle);
if (retval)
evdev->open--;
} mutex_unlock(&evdev->mutex);
return retval;
}

194行,exist在evdev_connect函数里已经设置为true。

196行,open计数加1。

197行,input_open_device函数在input core里定义:

 int input_open_device(struct input_handle *handle)
{
struct input_dev *dev = handle->dev;
int retval; retval = mutex_lock_interruptible(&dev->mutex);
if (retval)
return retval; if (dev->going_away) {
retval = -ENODEV;
goto out;
} handle->open++; if (!dev->users++ && dev->open)
retval = dev->open(dev); if (retval) {
dev->users--;
if (!--handle->open) {
/*
00000525 * Make sure we are not delivering any more events
00000526 * through this handle
00000527 */
synchronize_rcu();
}
} out:
mutex_unlock(&dev->mutex);
return retval;
}

511至514行,如果Input设备还没准备好,那么就返回。

516行,将handle的open计数加1,表示此handle已经打开。

519行,调用Input设备里的open函数,对于本文,里面啥事也没做,直接返回0,因此521至530行不用管。

退回到evdev_open函数,301行,将文件的私有数据指针指向此client。

302行,nonseekable_open函数不说了,就是设置文件的模式,定义很简单,如下:

int nonseekable_open(struct inode *inode, struct file *filp)
{
filp->f_mode &= ~(FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE);
return ;
}

下面是一张简单的描述evdev是怎么把client连接起来的图。

此时用户程序已经open了设备,假设接下来用户程序执行read函数读取Input消息,下面看此驱动里read函数evdev_read的定义:

 static ssize_t evdev_read(struct file *file, char __user *buffer,
size_t count, loff_t *ppos)
{
struct evdev_client *client = file->private_data;
struct evdev *evdev = client->evdev;
struct input_event event;
int retval; if (count < input_event_size())
return -EINVAL; if (client->head == client->tail && evdev->exist &&
(file->f_flags & O_NONBLOCK))
return -EAGAIN; retval = wait_event_interruptible(evdev->wait,
client->head != client->tail || !evdev->exist);
if (retval)
return retval; if (!evdev->exist)
return -ENODEV; while (retval + input_event_size() <= count &&
evdev_fetch_next_event(client, &event)) { if (input_event_to_user(buffer + retval, &event))
return -EFAULT; retval += input_event_size();
} return retval;
}

374行,input_event_size函数的定义有两种,一种是为了考虑兼容性的,这里为了简单起见,只看不需要兼容性那种的定义,在drivers/input/input-compat.h中:

 static inline size_t input_event_size(void)
{
return sizeof(struct input_event);
}

很简单,直接返回struct input_event结构体的大小。那么374行的意思就很明显了,就是说用户程序read的数据不能小于struct input_event结构体的大小。

377至379行,此时,第一和第二个条件都会成立,关键是看第三个条件,这个条件是否成立取决于用户程序是以怎样的方式打开设备文件的,是阻塞还是非阻塞,这里很明显,不能以非阻塞方式打开设备文件,否则返回出错。

381至384行,等待,有承若才会有等待,这个等待可能是无了期的,现实也是这样,承若不一定都能够实现,算了,说得有点伤感了。

接下来我们要看这个承若是怎么实现的。前面说过,Input消息已经进入了evdev_event函数,下面就看它的定义:

 static void evdev_event(struct input_handle *handle,
unsigned int type, unsigned int code, int value)
{
struct evdev *evdev = handle->private;
struct evdev_client *client;
struct input_event event; do_gettimeofday(&event.time);
event.type = type;
event.code = code;
event.value = value; rcu_read_lock(); client = rcu_dereference(evdev->grab);
if (client)
evdev_pass_event(client, &event);
else
list_for_each_entry_rcu(client, &evdev->client_list, node)
evdev_pass_event(client, &event); rcu_read_unlock(); wake_up_interruptible(&evdev->wait);
}

82至85行,填充struct input_event的实例evevt。从这里也看以知道,struct input_event表示消息的内容。

89至91行,同样可以在用户空间通过ioctl为struct evdev实例指定client,默认是没有指定的。

93、94行,遍历client_list链表,每找到一个client就调用evdev_pass_event函数,evdev_pass_event函数的定义如下:

 static void evdev_pass_event(struct evdev_client *client,
struct input_event *event)
{
/*
00000057 * Interrupts are disabled, just acquire the lock.
00000058 * Make sure we don't leave with the client buffer
00000059 * "empty" by having client->head == client->tail.
00000060 */
spin_lock(&client->buffer_lock);
do {
client->buffer[client->head++] = *event;
client->head &= client->bufsize - ;
} while (client->head == client->tail);
spin_unlock(&client->buffer_lock); if (event->type == EV_SYN)
kill_fasync(&client->fasync, SIGIO, POLL_IN);
}

63行,将Input消息放入client的环形缓冲区,接着client->head加1。

64行,client->head的值不能大于client->bufsize – 1,也就是当client->head的值大于client->bufsize – 1时,让client->head的值回到0,从0开始再往上递增,为什么说是环形缓冲区也就是这个原因。

68行,因为此时event->type的值是EV_KEY,所以条件不成立。

回到evdev_event函数,98行,唤醒,到这里承若已经实现了,等待是值得的,不应该为暂时的美好而停下脚本,继续往下走吧。回到evdev_read剩下的内容,这里把它贴出来好了,省得再回去看。

     retval = wait_event_interruptible(evdev->wait,
client->head != client->tail || !evdev->exist);
if (retval)
return retval; if (!evdev->exist)
return -ENODEV; while (retval + input_event_size() <= count &&
evdev_fetch_next_event(client, &event)) { if (input_event_to_user(buffer + retval, &event))
return -EFAULT; retval += input_event_size();
} return retval;
}

382行,唤醒的条件是因为client->head != client->tail,顺便提一下,此时client->head=1,client->tail=0。

390行,evdev_fetch_next_event函数的定义:

 static int evdev_fetch_next_event(struct evdev_client *client,
struct input_event *event)
{
int have_event; spin_lock_irq(&client->buffer_lock); have_event = client->head != client->tail;
if (have_event) {
*event = client->buffer[client->tail++];
client->tail &= client->bufsize - ;
} spin_unlock_irq(&client->buffer_lock); return have_event;
}

355行,因为client->head != client->tail,所以have_event的值不为0,356行的if条件成立。

357行,从环形缓冲区中取出一个Input消息,接着client->tail的值加1。

358行,同样是为了保证client->tail的值不会大于client->bufsize – 1。

回到evdev_read函数,此时389行的while条件成立。

392行,为了保持兼容性,input_event_to_user函数同样有两种定义,看不考虑兼容性那一种,在drivers/input/input-compat.c中。

 int input_event_to_user(char __user *buffer,
const struct input_event *event)
{
if (copy_to_user(buffer, event, sizeof(struct input_event)))
return -EFAULT; return ;
}

就是对copy_to_user函数的封装,这样Input消息就传递到用户空间了,接下来应用程序想对它干嘛就干嘛。但是别忘了gpio_keys_report_event函数里还有一条语句没有执行,下面把gpio_keys_report_event函数的定义在贴一遍:

 static void gpio_keys_report_event(struct gpio_button_data *bdata)
{
struct gpio_keys_button *button = bdata->button;
struct input_dev *input = bdata->input;
unsigned int type = button->type ?: EV_KEY;
int state = (gpio_get_value(button->gpio) ? : ) ^ button->active_low; input_event(input, type, button->code, !!state);
input_sync(input);
}

上面的内容是从327行的input_event函数一直讲的,下面把最后一个函数的调用过程讲完,看328行的input_sync函数的定义:

 static inline void input_sync(struct input_dev *dev)
{
input_event(dev, EV_SYN, SYN_REPORT, );
}

其实就是input_event函数的封装,只是参数值不一样而已,这里需要记住它的后三个参数。

在input_event函数里会调用input_handle_event函数,这里有必要把input_handle_event函数的定义再贴一遍:

 static void input_handle_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
int disposition = INPUT_IGNORE_EVENT; switch (type) { case EV_SYN:
switch (code) {
case SYN_CONFIG:
disposition = INPUT_PASS_TO_ALL;
break; case SYN_REPORT:
if (!dev->sync) {
dev->sync = true;
disposition = INPUT_PASS_TO_HANDLERS;
}
break;
case SYN_MT_REPORT:
dev->sync = false;
disposition = INPUT_PASS_TO_HANDLERS;
break;
}
break; case EV_KEY:
if (is_event_supported(code, dev->keybit, KEY_MAX) &&
!!test_bit(code, dev->key) != value) { if (value != ) {
__change_bit(code, dev->key);
if (value)
input_start_autorepeat(dev, code);
else
input_stop_autorepeat(dev);
} disposition = INPUT_PASS_TO_HANDLERS;
}
break; case EV_SW:
if (is_event_supported(code, dev->swbit, SW_MAX) &&
!!test_bit(code, dev->sw) != value) { __change_bit(code, dev->sw);
disposition = INPUT_PASS_TO_HANDLERS;
}
break; case EV_ABS:
if (is_event_supported(code, dev->absbit, ABS_MAX))
disposition = input_handle_abs_event(dev, code, &value); break; case EV_REL:
if (is_event_supported(code, dev->relbit, REL_MAX) && value)
disposition = INPUT_PASS_TO_HANDLERS; break; case EV_MSC:
if (is_event_supported(code, dev->mscbit, MSC_MAX))
disposition = INPUT_PASS_TO_ALL; break; case EV_LED:
if (is_event_supported(code, dev->ledbit, LED_MAX) &&
!!test_bit(code, dev->led) != value) { __change_bit(code, dev->led);
disposition = INPUT_PASS_TO_ALL;
}
break; case EV_SND:
if (is_event_supported(code, dev->sndbit, SND_MAX)) { if (!!test_bit(code, dev->snd) != !!value)
__change_bit(code, dev->snd);
disposition = INPUT_PASS_TO_ALL;
}
break; case EV_REP:
if (code <= REP_MAX && value >= && dev->rep[code] != value) {
dev->rep[code] = value;
disposition = INPUT_PASS_TO_ALL;
}
break; case EV_FF:
if (value >= )
disposition = INPUT_PASS_TO_ALL;
break; case EV_PWR:
disposition = INPUT_PASS_TO_ALL;
break;
} if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)
dev->sync = false; if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
dev->event(dev, type, code, value); if (disposition & INPUT_PASS_TO_HANDLERS)
input_pass_event(dev, type, code, value);
}

这时,进入的是222行的case,接着进入228行的case,229行,dev->sync的值到现在一直没有被设置过,因此if条件成立。

230行,dev->sync = true。

231行,disposition = INPUT_PASS_TO_HANDLERS。

接下来两次break,跳出switch到319行,319和322行的if条件都不成立,因此又是执行326行的input_pass_event函数,这个过程前面已经讲过了,最后也是会到达evdev_event函数,唯一不同的是,在evdev_pass_event函数会执行下面的语句。

     if (event->type == EV_SYN)
kill_fasync(&client->fasync, SIGIO, POLL_IN);

这是异步通知机制发送信号到用户空间,如果用户程序有设置使用这种机制,那么用户程序中指定的函数就会被调用。

到这里,Linux Input子系统的工作过程已经说完了,当然还有其他很多内容没有分析到,不过已经对其工作原理有一个比较深入的了解了,由于本人知识水平和精力有限,没办法做到面面俱到,当中有什么错误,望不吝指出。

总结

Linux的SPI、IIC和Input子系统都已经分析过了,马上就要找工作了,如果时间允许的话,接下来我会结合实际硬件写几篇关于它们具体使用方法的文章。

Linux设备驱动剖析之Input(四)的更多相关文章

  1. Linux设备驱动剖析之Input(二)

    分别是总线类型.厂商号.产品号和版本号. 1156行,evbit,设备支持的事件类型的位图,每一位代表一种事件,比如EV_KEY.EV_REL事件等等.BITS_TO_LONGS(nr)是一个宏,假设 ...

  2. Linux设备驱动剖析之Input(三)

    /* get current state of buttons */ ; i < pdata->nbuttons; i++) gpio_keys_report_event(&dda ...

  3. Linux设备驱动剖析之Input(一)

    前言 以前在移植Qt到开发板上时只知道在配置文件中需要指定触摸屏的设备文件/dev/input/event0,仅此而已.直到一年半前突然想到用红外遥控器控制Tiny6410开发板上的Android系统 ...

  4. linux设备驱动归纳总结(四):5.多处理器下的竞态和并发【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-67673.html linux设备驱动归纳总结(四):5.多处理器下的竞态和并发 xxxxxxxxxx ...

  5. linux设备驱动归纳总结(四):4.单处理器下的竞态和并发【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-67005.html linux设备驱动归纳总结(四):4.单处理器下的竞态和并发 xxxxxxxxxx ...

  6. linux设备驱动归纳总结(四):3.抢占和上下文切换【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-65711.html linux设备驱动归纳总结(四):3.抢占和上下文切换 xxxxxxxxxxxxx ...

  7. linux设备驱动归纳总结(四):2.进程调度的相关概念【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-65555.html linux设备驱动归纳总结(四):2.进程调度的相关概念 xxxxxxxxxxxx ...

  8. linux设备驱动归纳总结(四):1.进程管理的相关概念【转】

    本文转载自;http://blog.chinaunix.net/uid-25014876-id-64866.html linux设备驱动归纳总结(四):1.进程管理的相关概念 xxxxxxxxxxxx ...

  9. 【Linux开发】linux设备驱动归纳总结(四):5.多处理器下的竞态和并发

    linux设备驱动归纳总结(四):5.多处理器下的竞态和并发 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

随机推荐

  1. .NET Socket服务编程之-高效连接接入编

    在.NET上编写网络服务深入都有2,3年了,而这些时间时如何在.NET里实现网络服务积累了一些经验.在接下来的时间里会把这方面的经验通过博客的方式分享出来.而这一章主要是讲解在如果提高服务连接接入的效 ...

  2. WP8:在Cocos2d-x中使用OpenXLive

    一.    Cocos2d-x for Windows Phone 到2013年底,几大手游引擎都陆续支持WP8了,特别是Unity3D和Cocos2d-x.有过游戏开发经验的朋友们应该对这两个引擎不 ...

  3. Android开发笔记

    Android 中国SDK: http://wear.techbrood.com/ Android SDK Manager 代理设置: http://www.cnblogs.com/sunzn/p/4 ...

  4. Android布局中涉及的一些属性

    Android:gravity属性 线性布局常见的就是利用LinearLayout进行布局,其中有个比较重要的属性就是android:gravity,在官方文档中是这么描述这个属性的:指定一个元素怎么 ...

  5. EDA系列学习

    发布这系列的EDA课程VHDL实验是因为有着和单片机系列同样的理由,另外,这个系列的文档只进行过波形图仿真,部分的程序可能不能在硬件上运行. 目录 实验二 8位加法器设计 实验三 组合逻辑电路的VHD ...

  6. 移动端浏览器隐私模式/无痕模式使用本地存储localStorage/sessionStorage的问题

    移动端浏览器隐私模式/无痕模式使用本地存储localStorage/sessionStorage的问题 开发H5 webapp时经常需要使用本地存储,如localStorage和sessionStor ...

  7. Nutz Dao实体中索引注解的使用(@TableIndexes@Index)

    Nutz是一组轻便小型的框架的集合, 各个部分可以被独立使用,把SSH的精华封装在一个1M左右的jar包中,Nutz不对其他任何第三方库产生依赖,如果不考虑数据库链接和日志的话,创建完美的Web应用只 ...

  8. 备份MYSQL出现:mysqldump: Got error: 1049: Unknown database 'test 'when selecting the data

    解决办法 后面不要加分号: 如: mysqldump -uroot -p test>test.sql 不加分号 直接回车

  9. celery与mangodb搭配应用

    写作背景介绍 在celery简单应用中已经介绍了如何去配置一个celery应用,也知道怎么分离任务逻辑代码与客户端代码了.我们现在的任务是怎么把计算结果保存到数据库中,这种数据持久化是非常重要的.你一 ...

  10. paip.java 开发中web server的选择jboss resin tomcat比较..

    paip.java 开发中web server的选择jboss resin tomcat比较.. 作者Attilax  艾龙, EMAIL:1466519819@qq.com 来源:attilax的专 ...