Linux-3.0.8 input subsystem代码阅读笔记
先乱序记录一下阅读Linux input subsystem代码的笔记。
在input device driver的入口代码部分,需要分配并初始化input device结构,内核提供的API是input_allocate_device(),代码如下:
struct input_dev *input_allocate_device(void)
{
struct input_dev *dev; dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);
if (dev) {
dev->dev.type = &input_dev_type;
dev->dev.class = &input_class;
device_initialize(&dev->dev);
mutex_init(&dev->mutex);
spin_lock_init(&dev->event_lock);
INIT_LIST_HEAD(&dev->h_list);
INIT_LIST_HEAD(&dev->node); __module_get(THIS_MODULE);
} return dev;
}
此API的工作就是分配内存并初始化结构,这里调用了device_initialize,在input_device_register中还会调用device_add,这两个API合起来就是device_register要做的工作。有了constructor还要有destructor,就是input_free_device,代码如下:
void input_free_device(struct input_dev *dev)
{
if (dev)
input_put_device(dev);
}
初始化input device支持事件的时候可以使用input_set_capability(),这个函数的代码如下:
void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code)
{
switch (type) {
case EV_KEY:
__set_bit(code, dev->keybit);
break; case EV_REL:
__set_bit(code, dev->relbit);
break; case EV_ABS:
__set_bit(code, dev->absbit);
break; case EV_MSC:
__set_bit(code, dev->mscbit);
break; case EV_SW:
__set_bit(code, dev->swbit);
break; case EV_LED:
__set_bit(code, dev->ledbit);
break; case EV_SND:
__set_bit(code, dev->sndbit);
break; case EV_FF:
__set_bit(code, dev->ffbit);
break; case EV_PWR:
/* do nothing */
break; default:
pr_err("input_set_capability: unknown type %u (code %u)\n",
type, code);
dump_stack();
return;
} __set_bit(type, dev->evbit);
}
通过代码可以看到,如果一个设备需要支持多种事件的话,需要多次调用该API,而不能使用按位或的形式传参。
现在input device初始化完成了,应该向系统注册了,使用的API是input_register_device(),代码如下:
int input_register_device(struct input_dev *dev)
{
static atomic_t input_no = ATOMIC_INIT();
struct input_handler *handler;
const char *path;
int error; /* Every input device generates EV_SYN/SYN_REPORT events. */
__set_bit(EV_SYN, dev->evbit); /* KEY_RESERVED is not supposed to be transmitted to userspace. */
__clear_bit(KEY_RESERVED, dev->keybit); /* Make sure that bitmasks not mentioned in dev->evbit are clean. */
input_cleanse_bitmasks(dev); if (!dev->hint_events_per_packet)
dev->hint_events_per_packet =
input_estimate_events_per_packet(dev); /*
* If delay and period are pre-set by the driver, then autorepeating
* is handled by the driver itself and we don't do it in input.c.
*/
init_timer(&dev->timer);
if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
dev->timer.data = (long) dev;
dev->timer.function = input_repeat_key;
dev->rep[REP_DELAY] = ;
dev->rep[REP_PERIOD] = ;
} if (!dev->getkeycode)
dev->getkeycode = input_default_getkeycode; if (!dev->setkeycode)
dev->setkeycode = input_default_setkeycode; dev_set_name(&dev->dev, "input%ld",
(unsigned long) atomic_inc_return(&input_no) - ); error = device_add(&dev->dev);
if (error)
return error; path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
pr_info("%s as %s\n",
dev->name ? dev->name : "Unspecified device",
path ? path : "N/A");
kfree(path); error = mutex_lock_interruptible(&input_mutex);
if (error) {
device_del(&dev->dev);
return error;
} list_add_tail(&dev->node, &input_dev_list); list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler); input_wakeup_procfs_readers(); mutex_unlock(&input_mutex); return ;
}
这个函数做了以下几件事:
第一,因为所有的输入设备都需要支持EV_SYN,所以有必要明确置位一下,而KEY_RESERVED事件是不被支持的,所以清除掉。而对于当前设备没有明确表示支持的事件默认不支持,要清除掉相关的bitmap,代码如下:
static void input_cleanse_bitmasks(struct input_dev *dev)
{
INPUT_CLEANSE_BITMASK(dev, KEY, key);
INPUT_CLEANSE_BITMASK(dev, REL, rel);
INPUT_CLEANSE_BITMASK(dev, ABS, abs);
INPUT_CLEANSE_BITMASK(dev, MSC, msc);
INPUT_CLEANSE_BITMASK(dev, LED, led);
INPUT_CLEANSE_BITMASK(dev, SND, snd);
INPUT_CLEANSE_BITMASK(dev, FF, ff);
INPUT_CLEANSE_BITMASK(dev, SW, sw);
}
#define INPUT_CLEANSE_BITMASK(dev, type, bits) \
do { \
if (!test_bit(EV_##type, dev->evbit)) \
memset(dev->bits##bit, , \
sizeof(dev->bits##bit)); \
} while ()
清除bitmap的时候,首先看dev->evbit[]中是否指定EV_KEY EV_REL EV_ABS EV_MSC EV_LED EV_SND EV_FF EV_SW,若不指定,就把相关的xxxbit数组中所有元素清零,逻辑很简单,但是实现的时候,注意一下宏的使用方式,dev->bits##bit,以前接触的##连接符,都是变动的部分放在最后,变动部分的前面加上##,但是这里变动部分在前面,反而在其后面加##,注意使用方式。
第二,初始化定时器,设置重复事件,可见,如果用户没有明确指定重复的延迟和周期的话,输入子系统将延迟初始化为默认值250ms,周期是33ms,目前还不知道这部分的意义,估计需要结合具体的输入设备才能理解。
第三,设置设备的名字,这里的device_add将向系统注册一个设备,如果用户空间有udev或者mdev的话,将在/dev下产生相应的设备节点。
第四,list_add_tail(&dev->node, &input_dev_list),在操作系统中所谓的注册,其实就是将某单独的数据结构,通过插入链表或者填充数组的方式让内核可以通过某链表头或者数组找到它,因此,这里的input_dev_list显然就是一个全局变量,链表头。
第五,这是十分重要的一步,list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
简而言之,这一步逐一将系统中存在的struct input_handler与当前要注册的input_device进行匹配,若匹配成功,则调用handler->match(),进一步还会调用handler->connect(),显然这些函数指针指向的回调函数都是事件处理层决定的,例如evdev.c中向系统注册的evdev_handler指定的相关函数是evdev_connect,这里就不展开了,input_attach_handler代码如下:
static const struct input_device_id *input_match_device(struct input_handler *handler,
struct input_dev *dev)
{
const struct input_device_id *id;
int i; for (id = handler->id_table; id->flags || id->driver_info; id++) { if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
if (id->bustype != dev->id.bustype)
continue; if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
if (id->vendor != dev->id.vendor)
continue; if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
if (id->product != dev->id.product)
continue; if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
if (id->version != dev->id.version)
continue; MATCH_BIT(evbit, EV_MAX);
MATCH_BIT(keybit, KEY_MAX);
MATCH_BIT(relbit, REL_MAX);
MATCH_BIT(absbit, ABS_MAX);
MATCH_BIT(mscbit, MSC_MAX);
MATCH_BIT(ledbit, LED_MAX);
MATCH_BIT(sndbit, SND_MAX);
MATCH_BIT(ffbit, FF_MAX);
MATCH_BIT(swbit, SW_MAX); if (!handler->match || handler->match(handler, dev))
return id;
} return NULL;
} static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
const struct input_device_id *id;
int error; id = input_match_device(handler, dev);
if (!id)
return -ENODEV; error = handler->connect(handler, dev, id);
if (error && error != -ENODEV)
pr_err("failed to attach handler %s to device %s, error: %d\n",
handler->name, kobject_name(&dev->dev.kobj), error); return error;
}
匹配时,检查handler中的id_table与input_device->id,比如bustype、vendor、product以及version,除此之外,还要比较xxxbit数组,MATCH_BIT宏定义如下:
#define MATCH_BIT(bit, max) \
for (i = ; i < BITS_TO_LONGS(max); i++) \
if ((id->bit[i] & dev->bit[i]) != id->bit[i]) \
break; \
if (i != BITS_TO_LONGS(max)) \
continue;
BITS_TO_LONGS的定义:
#define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(long))
如果当前要注册的device不能完全满足匹配条件,那么就换下一个id_table中的表项进行匹配,若当前handler->id_table中所有表项都不能与device的id匹配,那么就返回NULL,返回NULL就会使得input_attach_handler返回-ENODEV,这样在input_register_handler中会从input_handler_list中再找到下一个handler重新开始匹配。
以上API都是和input device相关的,现在看一下注册input_handler的API,input_register_handler(),代码如下:
int input_register_handler(struct input_handler *handler)
{
struct input_dev *dev;
int retval; retval = mutex_lock_interruptible(&input_mutex);
if (retval)
return retval; INIT_LIST_HEAD(&handler->h_list); if (handler->fops != NULL) {
if (input_table[handler->minor >> ]) {
retval = -EBUSY;
goto out;
}
input_table[handler->minor >> ] = handler;
} list_add_tail(&handler->node, &input_handler_list); list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler); input_wakeup_procfs_readers(); out:
mutex_unlock(&input_mutex);
return retval;
}
这个函数主要做三件事:
第一,将要注册的handler填充到input_table数组中,填充的时候,数组的下标取决于handler的minor,minor除以32即可得到input_table数组的下标,当然填充数组前需要检查下当前位置是否已经被占用,若占用则直接宣告注册失败。
第二,将要注册的handler加入到全局链表头input_handler_list所指向的链表。
第三,和input_register_device最后一步一样,将handler和device进行匹配,如果系统中已经存在了一些input_device,它们在注册阶段并没有匹配到合适的handler,那么注册handler的时候就又检查了一下handler和device的对应关系,这一次可能有些device就得到了匹配的handler。其实,input_device、input_handler、input_handle与设备驱动模型中的device、driver、bus的关系很类似,注册input_device、input_driver任意一方都会进行匹配检查。在设备驱动模型中,device和driver匹配成功后,device结构中会有一个成员表示自己的driver,而driver中有一链表成员,表示自己能够支持的各个设备,这一点在input subsystem中的体现为,匹配成功后,在handler->connect()函数中input_handler和input_device会同时出现在input_handle结构体中,该结构体的定义如下:
struct input_handle { void *private; int open;
const char *name; struct input_dev *dev;
struct input_handler *handler; struct list_head d_node;
struct list_head h_node;
};
dev成员指向配对成功的input_device,handler成员指向对应的handler,这样来看handle结构的一大重要作用就是记录匹配成功的input_device和input_handler。
以上是input subsystem的中间层,在中间层以上还有事件处理层,这一层的实现就是evdev.c、mousedev.c、joydev.c等文件构建的,接下来简单分析下evdev。
evdev.c实现了对evdev的管理,根据Docuentation/input/input.txt的描述,evdev is the generic input event interface. It passes the events generated in the kernel straight to the program, with timestamps.因此,evdev只是对输入设备这一类设备的管理,并不涉及具体如鼠标、键盘以及游戏摇杆等设备的管理,但是驱动中完全可以使用关于evdev的API直接给用户空间程序上报事件。evdev模块代码入口如下:
static struct input_handler evdev_handler = {
.event = evdev_event,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.fops = &evdev_fops,
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
.id_table = evdev_ids,
}; static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
} static void __exit evdev_exit(void)
{
input_unregister_handler(&evdev_handler);
} module_init(evdev_init);
module_exit(evdev_exit);
模块入口函数evdev_init()使用上面分析过的函数input_register_handler向内核注册了自己的handler,evdev_handler,这个结构体就是evdev的核心了,以下所有内容都将围绕这个结构体展开。name成员的含义显而易见,minor成员初始化为EVDEV_MINOR_BASE,此宏的定义是64,id_table则指向了evdev_ids数组的起始地址,该数组定义如下:
static const struct input_device_id evdev_ids[] = {
{ .driver_info = }, /* Matches all devices */
{ }, /* Terminating zero entry */
};
这里只初始化了driver_info,注释说匹配所有设备,结合上面的input_match_device函数看一下,在for循环中由于id->driver_info成员是1,所以这一层循环是一定能够跑完的,但是flags成员没有初始化,默认为0,那么匹配时将不会对bustype、vendor、product以及version检查,由于这里的evbit、keybit、relbit、absbit、mscbit等数组成员也都是0,那么id->xxxbit数组与dev->xxxbit数组位与后一定是全零,也就是等于id->xxxbit,这一部分的检查也会完全通过,接着就是handler->match,这个函数指针在evdev结构体中没有初始化,是NULL,所以直接返回当前id就完成了一次匹配,这样分析来看evdev确实会匹配所有设备。
匹配成功后,input_attach_handler()会调用handler->connect(),也就是这里的evdev_connect,代码如下:
/*
* Create new evdev device. Note that input core serializes calls
* to connect and disconnect so we don't need to lock evdev_table here.
*/
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
struct evdev *evdev;
int minor;
int error; for (minor = ; minor < EVDEV_MINORS; minor++)
if (!evdev_table[minor])
break; if (minor == EVDEV_MINORS) {
pr_err("no more free evdev devices\n");
return -ENFILE;
} evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
if (!evdev)
return -ENOMEM; INIT_LIST_HEAD(&evdev->client_list);
spin_lock_init(&evdev->client_lock);
mutex_init(&evdev->mutex);
init_waitqueue_head(&evdev->wait); dev_set_name(&evdev->dev, "event%d", minor);
evdev->exist = true;
evdev->minor = minor; evdev->handle.dev = input_get_device(dev);
evdev->handle.name = dev_name(&evdev->dev);
evdev->handle.handler = handler;
evdev->handle.private = evdev; evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev); error = input_register_handle(&evdev->handle);
if (error)
goto err_free_evdev; error = evdev_install_chrdev(evdev);
if (error)
goto err_unregister_handle; error = device_add(&evdev->dev);
if (error)
goto err_cleanup_evdev; return ; err_cleanup_evdev:
evdev_cleanup(evdev);
err_unregister_handle:
input_unregister_handle(&evdev->handle);
err_free_evdev:
put_device(&evdev->dev);
return error;
}
evdev_connect()主要是创建一个新的event设备,相应的设备节点文件是/dev/input/eventX。evdev_table是全局数组,里面存放着所有的evdev的地址,它的容量是EVDEV_MINORS,也就是32。要注册一个新的evdev设备,首先要检查一下evdev数量是不是已经达到了最大值,这里采用的方式就是直接从头遍历evdev_table数组,第一个为NULL的元素就是能够存放新evdev的地方,如果数组已经满了,那么此函数即返回-ENFILE。如果有空间,那么就申请内存来存放新的evdev,接着就是初始化新的evdev了:一个evdev可以有多个client,所以初始化client_list,而操作client的时候需要互斥,所以初始化client_lock,等待队列用于实现用户空间读取时的阻塞等待,exist成员置为true表示新的evdev已经存在,minor是新的evdev在evdev_table中的索引,而设备节点文件/dev/input/eventx中的x就是这里minor决定的。
evdev_connect一项重要工作就是绑定一组匹配成功的input_device和input_handler,这个工作是跟evdev->handle成员息息相关的,handle成员handler指向了配对成功的handler,成员dev指向了配对成功的device,而private成员则指向了evdev设备本身。
接着,初始化evdev->dev,这就是设备驱动模型中的struct device了,设备号有INPUT_MAJOR, EVDEV_MINOR_BASE,minor生成,class指向了input_class,说明在class/input/目录下会出现新evdev的信息。接着,device_initialize()函数各种初始化。input_register_handle,注意这里是注册handle而不是上面已经分析过的handler,它的代码如下:
int input_register_handle(struct input_handle *handle)
{
struct input_handler *handler = handle->handler;
struct input_dev *dev = handle->dev;
int error; /*
* We take dev->mutex here to prevent race with
* input_release_device().
*/
error = mutex_lock_interruptible(&dev->mutex);
if (error)
return error; /*
* Filters go to the head of the list, normal handlers
* to the tail.
*/
if (handler->filter)
list_add_rcu(&handle->d_node, &dev->h_list);
else
list_add_tail_rcu(&handle->d_node, &dev->h_list); mutex_unlock(&dev->mutex); /*
* Since we are supposed to be called from ->connect()
* which is mutually exclusive with ->disconnect()
* we can't be racing with input_unregister_handle()
* and so separate lock is not needed here.
*/
list_add_tail_rcu(&handle->h_node, &handler->h_list); if (handler->start)
handler->start(handle); return ;
}
这个函数主要做两件事,将handle挂接到当前device的h_list上,h_list中的h就代表handle的意思,另一件事是将handle挂接到当前handler的h_list上,如果当前handler有start方法的话就调用,显然evdev_handler没有。这个函数将handle同时挂到了device和handler的链表上,那么两者都可用通过相关的数据结构成员找到它们,这一点的用意后面遇到再说吧。
接着,evdev_install_chrdev(),看名字意思是注册evdev相关的字符设备,但是struct evdev结构体中并没有struct cdev结构体,所以这里的注册并不是像cdev_init()、cdev_add()这样的注册,而是直接操作struct evdev,代码如下:
static int evdev_install_chrdev(struct evdev *evdev)
{
/*
* No need to do any locking here as calls to connect and
* disconnect are serialized by the input core
*/
evdev_table[evdev->minor] = evdev;
return ;
}
evdev_disconnect函数则是释放evdev_connect所占用的资源以及创建的条目,代码如下:
static void evdev_disconnect(struct input_handle *handle)
{
struct evdev *evdev = handle->private; device_del(&evdev->dev);
evdev_cleanup(evdev);
input_unregister_handle(handle);
put_device(&evdev->dev);
}
这里再展开一下evdev_cleanup()和input_unregister_handle(),代码如下:
/*
* Mark device non-existent. This disables writes, ioctls and
* prevents new users from opening the device. Already posted
* blocking reads will stay, however new ones will fail.
*/
static void evdev_mark_dead(struct evdev *evdev)
{
mutex_lock(&evdev->mutex);
evdev->exist = false;
mutex_unlock(&evdev->mutex);
} static void evdev_cleanup(struct evdev *evdev)
{
struct input_handle *handle = &evdev->handle; evdev_mark_dead(evdev);
evdev_hangup(evdev);
evdev_remove_chrdev(evdev); /* evdev is marked dead so no one else accesses evdev->open */
if (evdev->open) {
input_flush_device(handle, NULL);
input_close_device(handle);
}
}
大致过一下disconnect的流程,清除exist,既然这个设备已经失效,那么需要通知所有的evdev_client。这里插一句,后面分析evdev_open()的时候会看到,每open一次,便会新建一个struct_client,对于每个struct_client而言,它们不知道evdev已经disconnect,这是一个异步事件,所以evdev_disconnect()中使用异步信号通知机制,即kill_fasync()。evdev_disconnect()还会清掉之前注册在evdev_table[]中的元素,如果此时还有在使用endev的,就将其强行关闭。最后,把此handle从dev和handler的h_list上删除。
下面分析evdev_open,当应用程序执行open("/dev/input/eventX", O_RDWR)时,系统调用经过文件系统一系列操作后就会执行这里的evdev_open,因为它是file_operations中的成员。此函数会从事件处理层到input core层再到驱动程序的input_dev对应的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;
}
evdev_open()做的第一件事,是找到evdev设备,前面evdev_install_chrdev()中将注册的evdev设备都放在了evdev_table[]数组中,所以找到evdev的关键在于找到数组下标,evdev_open是file_operations的成员,它的接口中有一个是inode,我们可以通过宏iminor获得设备的minor,然后减去EVDEV_MINOR_BASE就得到了数组索引,这样就完成了第一步。
第二步,给evdev_client结构分配内存空间。这里非常有必要说一下evdev_client结构,此结构定义如下:
struct evdev_client {
unsigned int head;
unsigned int tail;
unsigned int packet_head; /* [future] position of the first element of next packet */
spinlock_t buffer_lock; /* protects access to buffer, head and tail */
struct fasync_struct *fasync;
struct evdev *evdev;
struct list_head node;
unsigned int bufsize;
struct input_event buffer[];
};
可以看到这个结构中多数成员都是关于buffer的,这完全可以理解,一个输入设备要上报输入事件,从代码上来说,肯定有存储事件的缓冲区,而且有时候一个设备会上报多个事件,那么我们需要能够存储多个事件的缓冲区。bufsize成员表示当前输入设备的输入事件缓冲区最多可以存放事件的个数。值的注意的是,输入事件缓冲区的大小不是固定的,这里采用的是零长数组的方式,我们可以借此学习一下变长数组在C语言中的使用方法。
由代码可以看到,client结构的内存分为两部分,一部分是client结构本身的内存空间,另一部分就是变长数组的长度。计算变长数组的长度时,需要知道当前输入设备的输入事件缓冲区能存放事件的数量,也就是bufsize,计算bufsize的方法endev_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);
}
dev->hint_events_per_packet是在input_register_device()中确定的,如果我们没有初始化这个成员,那么它将由input_estimate_events_per_packet()决定,这块代码还没看懂,先跳过。bufsize最终会向上取整到2的幂,后面读写函数中操作client_struct时会看到这样做的好处。
第二步还没说完,变长数组的长度就是bufsize * sizeof(struct input_event),这一点显而易见。接着初始化client->bufsize。
第三步,将client的evdev指向当前evdev,并且将client挂接到evdev的链表上。既然这里有挂接链表的操作,说明一个evdev对应着多个client,这一点在evdev_disconnect()中已经体现:evdev需要遍历自己的client_list,给所有的client发信号。
第四步,调用evdev_open_device(),并且将file的private_data初始化为client。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;
}
此函数比较简单,上锁、检查参数后调用input_open_device(),代码如下:
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) {
/*
* Make sure we are not delivering any more events
* through this handle
*/
synchronize_rcu();
}
} out:
mutex_unlock(&dev->mutex);
return retval;
}
input_open_device()的核心是对handle->open和dev->users成员自增,调用dev->open()。但是我没搞懂这里的逻辑,为什么只有dev->users为零的时候才会调用dev->open()?还有,dev->open是在哪里操作的?dev->open就是我们使用input_register_device()之前自己赋值操作的方法。
到这里就分析完了evdev_open(),evdev_poll()就是调用poll_wait(),evdev_fasync()就是调用fasync_helper(),这里不多说了,这两个函数就说明我们可以在应用层使用poll()或者select()实现输入设备的异步阻塞IO以及异步非阻塞IO操作。
接下来分析evdev_read(),它的作用是从已经打开的evdev中读取输入事件,如果缓冲区中有事件则传递给用户空间,否则阻塞等待,代码如下:
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->packet_head == client->tail && evdev->exist &&
(file->f_flags & O_NONBLOCK))
return -EAGAIN; retval = wait_event_interruptible(evdev->wait,
client->packet_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;
}
第一步,从file->private_data中获取evdev_client结构,接着根据client获取当前的evdev设备。如果用户空间要读取的长度小于一个事件的长度,那么直接返回,如果当前事件缓冲区为空,并且设备存在,同时当前文件的操作为非阻塞IO方式,那么直接返回-EAGAIN即可。
第二步,调用wait_event_interruptible(),condition为输入事件缓冲区非空或者evdev设备不存在了,如果是因为设备不存在了,那么直接返回-ENODEV即可,否则读取缓冲区中的事件,传递给用户空间即可。读取输入事件缓冲区的视线内代码是一个循环,它的停止条件是已经传递给用户空间的数据长度已经不小于count或者输入事件缓冲区已经空了,这里看一下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;
}
此函数就是从输入事件缓冲区中取出一个事件,首先对client加锁,根据client->head与client->tail判断缓冲区是否有事件,若两者不相等那么有,否则没有。由于tail指向将要处理的事件,若要取事件,那么就根据tail就可以得到之。而input_event_to_user()就是copy_to_user(),把取出来的事件拷贝给用户空间。
evdev_read()是读取事件,读取时使用wait_event_interruptible()实现阻塞IO,如果读取操作阻塞在了wait队列上,那么我们在哪里将其唤醒呢?是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(); if (type == EV_SYN && code == SYN_REPORT)
wake_up_interruptible(&evdev->wait);
}
简而言之,此函数就是把type code value代表的事件发送给当前evdev对应的client。首先定义一个input_event结构,把传入的事件信息一次填入,完成一个事件的描述,接着看一下当前evdev的grab成员,这个成员代表exclusice access的含义,也就是说,如果这个成员不是NULL,那么这个evdev只能被一个client访问使用,如果没有grab成员,那么此事件就需要给evdev->client_list上所有成员发消息,发消息的函数是evdev_pass_event(),最后,如果type是EV_SYN且code是SYN_REPORT,那么我们将调用wake_up_interruptible(),实现读取操作的同步,这也意味着,驱动程序中要显式调用report相关的函数才能解锁读取操作。
这里重点看evdev_pass_event(),代码如下:
static void evdev_pass_event(struct evdev_client *client,
struct input_event *event)
{
/* Interrupts are disabled, just acquire the lock. */
spin_lock(&client->buffer_lock); client->buffer[client->head++] = *event;
client->head &= client->bufsize - ; if (unlikely(client->head == client->tail)) {
/*
* This effectively "drops" all unconsumed events, leaving
* EV_SYN/SYN_DROPPED plus the newest event in the queue.
*/
client->tail = (client->head - ) & (client->bufsize - ); client->buffer[client->tail].time = event->time;
client->buffer[client->tail].type = EV_SYN;
client->buffer[client->tail].code = SYN_DROPPED;
client->buffer[client->tail].value = ; client->packet_head = client->tail;
} if (event->type == EV_SYN && event->code == SYN_REPORT) {
client->packet_head = client->head;
kill_fasync(&client->fasync, SIGIO, POLL_IN);
} spin_unlock(&client->buffer_lock);
}
该函数备调用的时机为产生了一个输入事件以后,那么这个函数肯定要将产生的事件存储到事件队列缓冲区中,上面代码对于client->head的操作即是,存储了事件队列以后,还要确保head没超过bufsize,如果超过了bufsize,那么应该从零开始,也就是覆盖第一个,这种逻辑的实现就是通过按位与操作实现的,能够使用按位与操作实现的原因是evdev_open()时bufsize被向上取整为2的幂。如果存储了当前事件后,事件队列缓冲区满了,内核代码采用的机制是,只留下两个事件,最新的一个事件其实不是事件,它的code为SYN_DROPPED,而另一个就是newest事件,接着把packet_head成员更新为tail,代表一系列事件的头部。接着有一项很重要的操作,如果我们的事件type=EV_SYN,code=SYN_REPORT,那么通过kill_fasync()发送SIGIO,那么如果我们的输入设备文件支持异步IO操作的话,应用层应该能通过异步通知的方式接收SIGIO,从而在信号回调函数中读取到输入事件,这一点后面我需要写程序验证一下。
Linux-3.0.8 input subsystem代码阅读笔记的更多相关文章
- linux输入子系统(input subsystem)之evdev.c事件处理过程
1.代码 input_subsys.drv.c 在linux输入子系统(input subsystem)之按键输入和LED控制的基础上有小改动,input_subsys_test.c不变. input ...
- Linux协议栈代码阅读笔记(二)网络接口的配置
Linux协议栈代码阅读笔记(二)网络接口的配置 (基于linux-2.6.11) (一)用户态通过C库函数ioctl进行网络接口的配置 例如,知名的ifconfig程序,就是通过C库函数sys_io ...
- [置顶] Linux协议栈代码阅读笔记(一)
Linux协议栈代码阅读笔记(一) (基于linux-2.6.21.7) (一)用户态通过诸如下面的C库函数访问协议栈服务 int socket(int domain, int type, int p ...
- [置顶] Linux协议栈代码阅读笔记(二)网络接口的配置
Linux协议栈代码阅读笔记(二)网络接口的配置 (基于linux-2.6.11) (一)用户态通过C库函数ioctl进行网络接口的配置 例如,知名的ifconfig程序,就是通过C库函数sys_io ...
- Spark0.9.0机器学习包MLlib-Optimization代码阅读
基于Spark的一个生态产品--MLlib,实现了经典的机器学算法,源码分8个文件夹,classification文件夹下面包含NB.LR.SVM的实现,clustering文件夹下面包 ...
- linux输入子系统(input subsystem)之按键输入和LED控制
实验现象:在控制台打印按键值,并且通过按键控制相应的LED亮灭. 1.代码 input_subsys_drv.c #include <linux/module.h> #include &l ...
- Spark0.9.0机器学习包MLlib-Classification代码阅读
本章主要讲述MLlib包里面的分类算法实现,目前实现的有LogisticRegression.SVM.NaiveBayes ,前两种算法针对各自的目标优化函数跟正则项,调用了Optimization模 ...
- Typecho 代码阅读笔记(三) - 插件机制
转载请注明出处:http://blog.csdn.net/jh_zzz 以 index.php 为例: /** 初始化组件 */ Typecho_Widget:: widget('Widget_Ini ...
- Typecho 代码阅读笔记(二) - 数据库访问
转载请注明出处:http://blog.csdn.net/jh_zzz 这一块比较复杂,我还没有完全理解为什么要把 SQL 语句的组装搞这么复杂. 从一个普通皮肤页面开始 themes/default ...
随机推荐
- centos7编译安装nginx
一.安装依赖包 yum install gcc gcc-c++ autoconf automake zlib zlib-devel openssl openssl-devel pcre-devel 二 ...
- grep -iq 与grep -qi 意思
就是有的时候你不需要直接打印出结果,比如在shell脚本中,你只需要知道grep有没有找到指定的字符串,而不需要满屏幕打印出来,因为那样会很难看.这只可以加-q选项,执行结果是:如果找到了,会返回0, ...
- VXLAN实验
拓扑图: SPINE配置: hostname SPINE-1vdc SPINE-1 id 1 limit-resource vlan minimum 16 maximum 4094 limit-res ...
- 验证当前启动APP的Package 和 Activity
1. 打开手机USB 调试开关 2. 执行: adb devices 3.启动你的测试APP (如手机QQ) 4.执行:adb shell dumpsys window |findstr mCurre ...
- scrapy 的log功能
只需要在配置文件 setting.py文件中加入LOG_FILE = "mySpider.log"LOG_LEVEL = "INFO" Scrapy提供5层lo ...
- python note 08 文件操作
1.相对路径与绝对路径比较 #绝对路径 f = open('d:\pzw.txt',mode='r',encoding='UTF-8') content = f.read() print(conten ...
- Python基础-python流程控制之顺序结构和分支结构(五)
流程控制 流程:计算机执行代码的顺序,就是流程 流程控制:对计算机代码执行顺序的控制,就是流程控制 流程分类:顺序结构.选择结构(分支结构).循环结构 顺序结构 一种代码自上而下执行的结构,是pyth ...
- Error starting Tomcat context. Exception: org.springframework.beans.factory.BeanCreationException
Error starting Tomcat context. Exception: org.springframework.beans.factory.BeanCreationException. M ...
- Java-Oracle数据库连接
Oracle数据库先创建一个表和添加一些数据,下面是连接数据库的具体实现.(导入jar包:ojdbc14.jar) import java.sql.Connection; import java.sq ...
- web自动化上传附件 2
当我们进行某一项web自动化脚本编写时,有上传附件操作,点击附件直接打开了windows窗口,而有的点击添加附件打开一个小窗体,再点击‘浏览’才打开windows窗口, 中间多了这么一个小窗体的操作, ...