Linux设备驱动剖析之Input(四)
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(四)的更多相关文章
- Linux设备驱动剖析之Input(二)
分别是总线类型.厂商号.产品号和版本号. 1156行,evbit,设备支持的事件类型的位图,每一位代表一种事件,比如EV_KEY.EV_REL事件等等.BITS_TO_LONGS(nr)是一个宏,假设 ...
- Linux设备驱动剖析之Input(三)
/* get current state of buttons */ ; i < pdata->nbuttons; i++) gpio_keys_report_event(&dda ...
- Linux设备驱动剖析之Input(一)
前言 以前在移植Qt到开发板上时只知道在配置文件中需要指定触摸屏的设备文件/dev/input/event0,仅此而已.直到一年半前突然想到用红外遥控器控制Tiny6410开发板上的Android系统 ...
- linux设备驱动归纳总结(四):5.多处理器下的竞态和并发【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-67673.html linux设备驱动归纳总结(四):5.多处理器下的竞态和并发 xxxxxxxxxx ...
- linux设备驱动归纳总结(四):4.单处理器下的竞态和并发【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-67005.html linux设备驱动归纳总结(四):4.单处理器下的竞态和并发 xxxxxxxxxx ...
- linux设备驱动归纳总结(四):3.抢占和上下文切换【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-65711.html linux设备驱动归纳总结(四):3.抢占和上下文切换 xxxxxxxxxxxxx ...
- linux设备驱动归纳总结(四):2.进程调度的相关概念【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-65555.html linux设备驱动归纳总结(四):2.进程调度的相关概念 xxxxxxxxxxxx ...
- linux设备驱动归纳总结(四):1.进程管理的相关概念【转】
本文转载自;http://blog.chinaunix.net/uid-25014876-id-64866.html linux设备驱动归纳总结(四):1.进程管理的相关概念 xxxxxxxxxxxx ...
- 【Linux开发】linux设备驱动归纳总结(四):5.多处理器下的竞态和并发
linux设备驱动归纳总结(四):5.多处理器下的竞态和并发 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
随机推荐
- Nim教程【一】
这应该是国内第一个关于Nim入门的系列教程 什么是Nim 我们先来引述网友 Luikore的一段话: Nim 不是函数式的, 但 Nim 支持卫生宏, 可以做 AST 重写, 可以自定编译规则, 是静 ...
- Python模拟HTTP Post上传文件
使用urllib2模块构造http post数据结构,提交有文件的表单(multipart/form-data),本示例提交的post表单带有两个参数及一张图片,代码如下: #buld post bo ...
- Python编码/文件读取/多线程
Python编码/文件读取/多线程 个人笔记~~记录才有成长 编码/文件读取/多线程 编码 常用的一般是gbk.utf-8,而在python中字符串一般是用Unicode来操作,这样才能按照单个字 ...
- Windows Phone 8 解锁提示IpOverUsbSvc问题——IpOverUsbEnum返回No connected partners found解决方案
我的1520之前总是无法解锁,提示:IpOverUsbSvc服务没有开启什么的. 根据网上网友的各种解决方案: 1. 把手机时间设置为当前时间,并且关闭“自动设置” 2. 确保手机接入了互联网 3.确 ...
- VS2015详细安装步骤
亲身经历记录下来,以备后用.也希望能够帮助到有需要的朋友们! 1.安装之前首先下载VS2015,下载地址: [VS2015社区版官方中文版下载]:http://download.microsoft.c ...
- django with mysql (part-4)
step01: write the ( views.py ) again .. vim views.py step02: configure your (urls.py) step03: check ...
- Spark Scala 读取GBK文件的方法
1. 在生产环境下,很多文件是GBK编码格式的,而SPARK 常用的textFile方法默认是写死了读UTF-8格式的文件,其他格式文件会显示乱码 用如下代码实现读取GBK文件的方法 import o ...
- python中x的平方
x ** 2 sqdEvens = [x ** 2 for x in range(8) if not x % 2] for i in sqdEvens: print(i) 0 4 16 36 > ...
- VS2015 Apache Cordova第一个Android和IOS应用
前言 本人个人博客原文链接地址为http://aehyok.com/Blog/Detail/75.html. 个人网站地址:aehyok.com QQ 技术群号:206058845,验证码为:aehy ...
- mysqld.exe 占了400M内存的问题
最近遇到了服务器总是停机的问题,虽然它自己只有2G的内存,不过实际部署的应用访问量非常小,也不至于2G就不够用,所以开始了给服务器瘦身的计划. 看着任务管理器里面的各个进程,发现吃内存最厉害的是mys ...