1:概述
上一篇文章对input子系统中的数据结构做了总结,本篇通过对代码的分析,总结输入子系统的整体架构;本文中所涉及的文件有:input.c和evdev.c文件;
input.c:是输入子系统的核心层,提供了一个input_dev设备注册的方法,以及input_handler处理层的注册方法,并且注册了input字符设备节点;
evdev.c:注册了input_handler,实现具体的输入设备的读写方法,上层应用对输入设备的访问都是通过这些方法实现的;
当然,还有用户实现的驱动文件,其中完成input_dev的申请和注册;本篇文章篇幅会比较长,首先,会分析input.c和evdev.c文件;然后从input_dev设备的注册入手,分析input_dev注册的过程;最后分析一个事件的上报过程以及用户空间访问输入设备的过程;希望我能总结的比较清晰;
2:input.c文件分析
input.c是作为驱动被加载进内核,内核加载后,会执行input_init函数,这个函数完成的工作有:
(1):创建input_class类,面向对象的思想,我们的每一个input_dev都是input_class的一个实体,这个类用来在device模型中创建对于这个类的节点;完成后,在/sys/class目录下会创建input目录;
(2):创建/proc下的目录和节点,通过这些节点,我们可以获知整个input子系统中的所有input_dev和input_handler;
(3):申请256个字符设备,这些字符设备主设备号13,次设备号0~255,共用input_fops。一个input_handler可以对应多个设备 (比如:一个input_handler可以为次设备为0~31的设备提供事件处理曾的方法),而一个设备只能对应一个input_handler;
static int __init input_init(void)
{
int err;
err = class_register(&input_class); /*在sys/class下创建input目录*/
if (err) {
pr_err("unable to register input_dev class\n");
return err;
}
err = input_proc_init(); /*创建/proc/bus/input/devices和/proc/bus/input/handlers节点*/
if (err)
goto fail1;
err = register_chrdev(INPUT_MAJOR, "input", &input_fops); /*申请了256个设备号,主设备号都是13,次设备号从0开始,256个设备共用相同的文件操作*/
if (err) {
pr_err("unable to register char major %d", INPUT_MAJOR);
goto fail2;
}
#ifdef CONFIG_CPU_FREQ_USR_EVNT_NOTIFY
cpufreq_usrevent = create_workqueue("cpufreq_uevent");
if (!cpufreq_usrevent) {
printk(KERN_ERR "Creation of cpufreq_usrevent failed\n");
goto fail3;
}
#endif
return 0;
#ifdef CONFIG_CPU_FREQ_USR_EVNT_NOTIFY
fail3: unregister_chrdev(INPUT_MAJOR, "input");
#endif
fail2: input_proc_exit();
fail1: class_unregister(&input_class);
return err;
}
static void __exit input_exit(void)
{
#ifdef CONFIG_CPU_FREQ_USR_EVNT_NOTIFY
destroy_workqueue(cpufreq_usrevent);
#endif
input_proc_exit();
unregister_chrdev(INPUT_MAJOR, "input");
class_unregister(&input_class);
}
上面分析到,所有的输入设备对应同一个
input_fops,其实这个文件操作集中只提供了open和llseek方法,那读写方法呢?看完下面就明白了;
static int input_open_file(struct inode *inode, struct file *file)
{
struct input_handler *handler;
const struct file_operations *old_fops, *new_fops = NULL;
int err;
err = mutex_lock_interruptible(&input_mutex);
if (err)
return err;
/* No load-on-demand here? */
handler = input_table[iminor(inode) >> 5]; /*根据节点的次设备号找到input_handler*/
if (handler)
new_fops = fops_get(handler->fops); /*得到input_handler中的fops*/
mutex_unlock(&input_mutex);
/*
* That's _really_ odd. Usually NULL ->open means "nothing special",
* not "no device". Oh, well...
*/
if (!new_fops || !new_fops->open) {
fops_put(new_fops);
err = -ENODEV;
goto out;
}
old_fops = file->f_op; /*替换f_op指针,接下来对设备的操作使用new_fops中的方法*/
file->f_op = new_fops;
err = new_fops->open(inode, file); /*调用input_handler->fops->open*/
if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
fops_put(old_fops);
out:
return err;
}
上书代码中根据应用层具体访问的哪个设备,获得这个设备的设备号,然后得知这个设备对应的input_handler,最后把input_handler->fops赋给file->f_op,这样,以后对这个设备的读写都是使用input_handler中提供的方法,这也就是我们接下来分析的input_handler存在的意义;
2:evdev.c文件分析
这个文件也是作为驱动被加载进内核,evdev_fops即使input_handler实现的文件操作集,用户空间对设备的访问都是调用这些方法;
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);
}
接下来我们看input_register_handler函数,这个函数在核心层(input.c)提供;
这个函数将注册一个input_handler至内核中,完成的工作主要有:
(1):填充input_table数组,这个数组中存放input_handler;
(2):将这个input_handler添加到input_handler_list链表中去;
(3):遍历inpit_dev_list中的每个input_dev,与此input_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 >> 5]) {
retval = -EBUSY;
goto out;
}
input_table[handler->minor >> 5] = handler; /*input_table8个数组*/
}
list_add_tail(&handler->node, &input_handler_list); /*将这个input_handler添加到input_handler_list链表中去*/
list_for_each_entry(dev, &input_dev_list, node) /*遍历inpit_dev_list中的每个input_dev,与次input_handler进行匹配*/
input_attach_handler(dev, handler);
input_wakeup_procfs_readers();
out:
mutex_unlock(&input_mutex);
return retval;
}
接下来,看看input_dev和input_handler的匹配,这是在注册input_handler中需要做的,其实在注册input_dev设备的时候,也会进行同样的匹配过程,总之,在注册input_dev和input_handler的时候,都需要匹配的过程,这个过程关系到编写驱动的时候,怎样去构建我们的input_dev,来看看匹配的过程;
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
const struct input_device_id *id;
int error;
/*将input_dev与所有handler进行匹配,如果匹配成功,调用connect函数进行连接*/
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;
}
上述代码,先进行匹配,如果匹配成功后,就会进行connect进行连接,先看匹配过程:
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;
/*只有当id->flags没设置或者上面匹配成功后才进行MATCH_BIT,我们分析的evdev这个文件中的handler属于第一种情况,flags没有设置*/
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;
}
上述代码,先匹配flasg,再匹配事件类型和子事件类型,如果input_hander中有设置match函数,还需使用match继续匹配,这些全部匹配成功后,返回input_device_id指针;成功匹配后,会执行handler->connect函数,下面继续追:
这个函数会创建evdev实体,我们可以认为input_dev是输入设备的实体,而evdev是事件处理层的实体,它是input_dev和input_handler爱情的结晶,只有两者匹配成功后,才会常见evdev,一个input_dev实体对应一个evdev实体,evdev中有input_handle,这是input_dev和input_handler的桥梁,为什么是桥梁?
(1):input_handle->dev指向input_dev,同时input_handle->handler指向input_handler;
(2):list_add_rcu(&handle->d_node, &dev->h_list); //将handle加入input_dev->h_list中
list_add_tail_rcu(&handle->h_node, &handler->h_list); //将handle加入input_handler->h_list中
这两行代码的执行保证了,input_dev通过遍历h_list链表,可以找到handle,然后通过handle下的handler就可以找到input_handler,相反,input_handler找input_dev也是一样道理,这样,才真正的将input_dev和input_handler对应起来,这就是桥梁;
(3):桥梁架起后,代码创建了具体的字符设备,对应目录为/dev/input/event0,同时在/sys/class/input/下创建event0目录;
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;
/*找到evdev_table为空的那一项*/
for (minor = 0; minor < EVDEV_MINORS; minor++)
if (!evdev_table[minor])
break;
if (minor == EVDEV_MINORS) {
pr_err("no more free evdev devices\n");
return -ENFILE;
}
//分配struct evdev,里面包含input_handle结构
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
if (!evdev)
return -ENOMEM;
INIT_LIST_HEAD(&evdev->client_list); /*初始化client_list链表*/
spin_lock_init(&evdev->client_lock); /*初始化client_lock自旋锁*/
mutex_init(&evdev->mutex); /*初始化互斥锁*/
init_waitqueue_head(&evdev->wait); /*初始化等待队列*/
dev_set_name(&evdev->dev, "event%d", minor); /*对evdev命名,会出现在/dev/input/event%d*/
evdev->exist = true;
evdev->minor = minor;
evdev->handle.dev = input_get_device(dev); /*给handle下的input_dev赋值*/
evdev->handle.name = dev_name(&evdev->dev); /*"event0"*/
evdev->handle.handler = handler; /*给handle下的input_handler赋值*/
evdev->handle.private = evdev;
/*初始化evdev->dev这个设备驱动模型*/
evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor); /*/sys/class/input/event0节点的设备号,次设备号是从64开始,evdev事件驱动最大支持32个设备*/
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev); /*device模型初始化*/
error = input_register_handle(&evdev->handle); /*注册evdev->handle*/
if (error)
goto err_free_evdev;
error = evdev_install_chrdev(evdev); /*将evdev_table[minor]指向evdev*/
if (error)
goto err_unregister_handle;
error = device_add(&evdev->dev); /*注册设备模型,会在/sys/class/input/目录下创建字符设备节点event0*/
if (error)
goto err_cleanup_evdev;
return 0;
err_cleanup_evdev:
evdev_cleanup(evdev);
err_unregister_handle:
input_unregister_handle(&evdev->handle);
err_free_evdev:
put_device(&evdev->dev);
return error;
}
到此,内核代码已经创建了具体的字符设备和节点,然后注册handler的时候,也有了文件操作函数,也分析了匹配的过程,接下里,就是我们驱动里面应该做的,申请input_dev实体,然后注册input_dev进内核;下面我们继续分析一个具体的input_dev怎么注册;
3:input_dev的注册过程
int input_register_device(struct input_dev *dev)
{
static atomic_t input_no = ATOMIC_INIT(0); /*定义原子变量,用于对输入设备计数*/
struct input_handler *handler;
const char *path;
int error;
/* Every input device generates EV_SYN/SYN_REPORT events. */
__set_bit(EV_SYN, dev->evbit); /*所有的输入设备都支持sync事件*/
/* KEY_RESERVED is not supposed to be transmitted to userspace. */
__clear_bit(KEY_RESERVED, dev->keybit); /*清空按键类型的reserved类*/
/* 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.
*/
/*初始化定时器,用于连续按键事件的上报,连续按键上报的值为2*/
init_timer(&dev->timer);
if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
dev->timer.data = (long) dev; /*当定时器溢出,调用input_repeat_key函数,dev作为input_repeat_key的参数传递*/
dev->timer.function = input_repeat_key;
dev->rep[REP_DELAY] = 250;
dev->rep[REP_PERIOD] = 33;
}
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) - 1); /*设置input_dev中的设备名,input_no全局变量,用于input_dev设备的计数*/
error = device_add(&dev->dev); /*将input_dev中的device注册到设备模型中去*/
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_handler_list链表中遍历每个handler*/
input_attach_handler(dev, handler); /*如果输入设备和handler匹配,进行连接*/
input_wakeup_procfs_readers();
mutex_unlock(&input_mutex);
return 0;
}
到这里,看input_dev的注册已经不困难了,填充填充,然后匹配,匹配成功后connect,创建爱情结晶evdev结构,架起桥梁,不同的是,这一会在/sys/class/input/下创建input0节点,其他的过程和上一次的匹配完全一样,只不过这两个匹配只有一个执行成功,另外一个会执行失败;现在input_dev已经注册进内核,内核代码已经准备好,将硬件的数据上报给事件处理层,在这里,我们会分析用户程序打开设备,然后读取/dev/input/event0节点和我们的设备驱动检测到一个按键中断后将按键事件上报的两个过程,其实这两个过程就是完整的一次事件上报的过程,先来看用户空间打开一个设备节点的过程:
前面分析,当open一个设备节点的时候,会调用这个节点对应的input_handler中的方法,所以先看input_handler中的open函数:
这个创建client结构,这里又是server和client的思想,当多个进程打开节点的时候,每个进程都会对应一个客户端(client),最终,我们上报的数据都是会保存在这个client中,然后用户进程去读这个client。为什么一个节点可以被多次打开,并且读取,这是RCU模式,读-拷贝-更新,我也不懂。同时client采用环形缓冲的机制,类似串口环形缓冲,这是最简单和实用的防止数据互斥的方法;
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]; /*依据节点的次设备号找到对应的evdev*/
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) + //申请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);
snprintf(client->name, sizeof(client->name), "%s-%d",
dev_name(&evdev->dev), task_tgid_vnr(current));
client->evdev = evdev; //将client->evdev指向evdev实体
evdev_attach_client(evdev, client); //将client加入client_list链表
error = evdev_open_device(evdev); //次函数最准会调用input_dev中的open函数,用户可以将这个作为硬件的enable功能
if (error)
goto err_free_client;
file->private_data = client; //文件指针的私有数据为client,将供其他方法读取
nonseekable_open(inode, file);
return 0;
err_free_client:
evdev_detach_client(evdev, client);
kfree(client);
err_put_evdev:
put_device(&evdev->dev);
return error;
}
然后接下来的其他操作方法,最终都是去操作client结构,我不贴代码了。下面来看,我们驱动主动上报时间的流程,其实也是填充client;
所有的事件上报都是通过input_event(dev, EV_KEY, code, !!value)函数来完成的;
void input_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
unsigned long flags;
if (is_event_supported(type, dev->evbit, EV_MAX)) { /*检查设备是否支持此类型*/
spin_lock_irqsave(&dev->event_lock, flags); /*自旋锁锁定*/
add_input_randomness(type, code, value);
input_handle_event(dev, type, code, value);
spin_unlock_irqrestore(&dev->event_lock, flags);
}
}
调用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 != 2) {
__change_bit(code, dev->key);
if (value)
input_start_autorepeat(dev, code); //定时器调用input_repeat_key函数
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 >= 0 && dev->rep[code] != value) {
dev->rep[code] = value;
disposition = INPUT_PASS_TO_ALL;
}
break;
case EV_FF:
if (value >= 0)
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); //事件交给input_dev处理
if (disposition & INPUT_PASS_TO_HANDLERS)
input_pass_event(dev, type, code, value); //事件交给input_handler处理
}
然后调用input_pass_event函数:
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;
#ifdef CONFIG_CPU_FREQ_USR_EVNT_NOTIFY
int i;
bool not_gsensor = true;
for(i = 0; i < ARRAY_SIZE(gsensor_name_list) - 1; i++) {
if(!strcmp(gsensor_name_list[i], dev->name)) {
not_gsensor = false;
break;
}
}
/* notify cpu-freq sub-system that some user event happend */
if(not_gsensor) {
queue_work(cpufreq_usrevent, &request_cpufreq_notify);
}
#endif
rcu_read_lock();
handle = rcu_dereference(dev->grab); //handle指针被RCU模式保护
if (handle)
handle->handler->event(handle, type, code, value); //我们分析的代码为填充grap指针,所以执行下面
else {
bool filtered = false;
list_for_each_entry_rcu(handle, &dev->h_list, d_node) { //从dev->h_list找到handle,然后从handle找到handler,桥梁的作用
if (!handle->open)
continue;
handler = handle->handler;
if (!handler->filter) {
if (filtered)
break;
handler->event(handle, type, code, value); //调用event函数,这个函数在注册iniput_handler时赋值
} else if (handler->filter(handle, type, code, value))
filtered = true;
}
}
rcu_read_unlock();
}
最终会调用input_handler中的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;
struct timespec ts;
ktime_get_ts(&ts); //得到时间
event.time.tv_sec = ts.tv_sec; //填充input_event结构
event.time.tv_usec = ts.tv_nsec / NSEC_PER_USEC;
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) //遍历所有的client,然后去填充client结构
evdev_pass_event(client, &event);
rcu_read_unlock();
if (type == EV_SYN && code == SYN_REPORT)
wake_up_interruptible(&evdev->wait);
}
接下来看看,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 - 1;
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 - 2) & (client->bufsize - 1);
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 = 0;
client->packet_head = client->tail;
if (client->use_wake_lock)
wake_unlock(&client->wake_lock);
}
if (event->type == EV_SYN && event->code == SYN_REPORT) {
client->packet_head = client->head;
if (client->use_wake_lock)
wake_lock(&client->wake_lock);
kill_fasync(&client->fasync, SIGIO, POLL_IN);
}
spin_unlock(&client->buffer_lock);
}
至此,client填充结束,客户端读取的就是client中的数据;
4:总结
以上对input子系统做了大概的流程分析,大概了解了子系统的模型,其实只是冰山一脚。下一篇将会参考内核中提供的/drivers/input/keyboard/gpio_keys.c实现开发板上四个按键的驱动,进行模块驱动的开发分析;