input 子系统的详解讲解到evdev

error= input_register_device(mxt->input);


*/

intinput_register_device(structinput_dev *dev)

{

staticatomic_tinput_no = ATOMIC_INIT(0);

structinput_handler *handler;

constchar*path;

interror;


/*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 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]= 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",

(unsignedlong)atomic_inc_return(&input_no) - 1);


error= device_add(&dev->dev);

if(error){

printk("input_register_deviceerror 1\n");

returnerror;

}


path= kobject_get_path(&dev->dev.kobj,GFP_KERNEL);

printk(KERN_INFO"input: %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);

printk("input_register_deviceerror 2\n");

returnerror;

}


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);


return0;

}

EXPORT_SYMBOL(input_register_device);

这个函数一直在前半部分在完成设备的成员的赋值和设备的注册,我们主要需要关注的就是:


list_add_tail(&dev->node,&input_dev_list);


list_for_each_entry(handler,&input_handler_list, node)

input_attach_handler(dev,handler);

需要注意的就是上面的两个链表都是静态链表,

staticLIST_HEAD(input_dev_list);

staticLIST_HEAD(input_handler_list);


/*

我们在evdev里面也需要使用这两个链表的



static__inline__voidlist_add_tail(structlist_head *_new, structlist_head *head)

{

__list_add(_new,head->prev,head);

}

/*

*Insert a new entry between two known consecutive entries.

*

*This is only for internal list manipulation where we know

*the prev/next entries already!

*/

static__inline__void__list_add(structlist_head * _new,

structlist_head * prev,

structlist_head * next)

{

next->prev= _new;

_new->next= next;

_new->prev= prev;

prev->next= _new;

}

以上的函数其实在链表的左边插入一个新的结点,这是一个双向链表


开始的时候,我们只知道有一个一个头节点,structlist_head *head,然后将next->prev= _new;
new->next = next;
这就是将new节点添加到head节点的左边,那么接_new->prev= prev;
prev->next =_new
这两个是什么意思呢??/因为开始head->pre->next是指向head的,因为现在添加了一个新的节点了,而且是在左边添加的,所以现在就需要将head->pre->next指向新的节点,而将新的节点的pre,也就是new->pre指向以前指向head的节点


接下来看下第二个链表在做什么事情:

list_for_each_entry(handler,&input_handler_list, node)

input_attach_handler(dev,handler);


这是一个for循环语句,是循环遍历input_handler_list这个链表,得到这个链表里面的每一个handler,将得到的每一handler,进行执行 input_attach_handler(dev,handler);,这里需要提醒下的是这个链表是一个静态的链表,我们注册的每一个handler都会添加到这个链表里面,下面我在讲解evdevhandler的时候会进行详细的讲解


看下执行的函数在干嘛


staticintinput_attach_handler(structinput_dev *dev, structinput_handler *handler)

{

conststructinput_device_id *id;

interror;


id= input_match_device(handler, dev);

if(!id)

return-ENODEV;


error= handler->connect(handler,dev, id);

if(error && error != -ENODEV)

printk(KERN_ERR

"input:failed to attach handler %s to device %s, "

"error:%d\n",

handler->name,kobject_name(&dev->dev.kobj),error);


returnerror;

}

看下id= input_match_device(handler, dev);这个函数在匹配devhandler是否匹配

staticconst structinput_device_id *input_match_device(structinput_handler *handler,

structinput_dev *dev)

{

conststructinput_device_id *id;

inti;


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))

returnid;

}


returnNULL;

}

这里进行匹配的原则是将handlerid_tableinputdev里面的id_table进行一一的匹配,有一样不匹配的话,那么就会退出,我们evdevhandler是适合所有的inputdev的,因为它的id_table里面没有这些匹配的项目

如果匹配的话,那么会直接调用

error= handler->connect(handler,dev, id);

我们所有的inputdev都是可以使用evdev里面的handler的,除非你自己定义和注册另外的handler,不然就直接使用内核为我们实现好的handler



下面我们进入evdev.c的讲解,这个文件的路进在/kernel/driver/input/evdev.c里面



staticstructinput_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,

};



staticint__init evdev_init(void)

{

returninput_register_handler(&evdev_handler);

}

这是在注册一个handler


intinput_register_handler(structinput_handler *handler)

{

structinput_dev *dev;

intretval;


retval= mutex_lock_interruptible(&input_mutex);

if(retval)

returnretval;


INIT_LIST_HEAD(&handler->h_list);


if(handler->fops!= NULL) {

if(input_table[handler->minor>> 5]) {

retval= -EBUSY;

gotoout;

}

input_table[handler->minor>> 5] = 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);

returnretval;

}

EXPORT_SYMBOL(input_register_handler);

input_table[handler->minor>> 5] =handler;这是根据自己的所注册的handlerminor>>5位,将handler赋值在input_tablez这个静态的数组里面staticstructinput_handler *input_table[8];

接下来是list_add_tail(&handler->node,&input_handler_list);将每一个注册的handler添加到input_handler_list这个链表里面去,你还记得我在上面讲解的在注册inputdev的时候,里面遍历的那个链表了,这就是那个链表,intinput_register_device(structinput_dev *dev),不记得的话,回头再去看下


接下来是list_for_each_entry(dev,&input_dev_list, node),这个是在遍历inputdev,因为我们每一个添加的inputdev也会添加到一个静态链表里面去,和handler的添加是一样的道理,我在注册设备的时候,专门讲解了下添加到链表里面的函数的用法,不明白可以回去看下

list_add_tail(&dev->node,&input_dev_list);


list_for_each_entry(handler,&input_handler_list, node)

input_attach_handler(dev,handler);

接下来是

input_attach_handler(dev,handler);

这个函数我们已经讲解过了,

不知道发现没有,无论我们注册一个inputdev还是一个handler都会调用input_attach_handler(dev,handler);,这个的道理就像设备模型里面架在bus上的devicedriver一样,不管谁先后都会调用match,然后probe一样

如果匹配成功的话,那么就会执行error= handler->connect(handler,dev, id);

这个是handlerconnect

看下evdev.c是如何实现的

/*

* Create new evdev device. Notethat input core serializes calls

* to connect and disconnect so wedon't need to lock evdev_table here.

*/

staticintevdev_connect(structinput_handler *handler, structinput_dev *dev,

conststructinput_device_id *id)

{

structevdev *evdev;

intminor;

interror;


for(minor = 0; minor < EVDEV_MINORS; minor++)

if(!evdev_table[minor])

break;


if(minor == EVDEV_MINORS) {

printk(KERN_ERR"evdev: no more free evdevdevices\n");

return-ENFILE;

}


evdev= kzalloc(sizeof(structevdev), 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)

gotoerr_free_evdev;


error= evdev_install_chrdev(evdev);

if(error)

gotoerr_unregister_handle;


error= device_add(&evdev->dev);

if(error)

gotoerr_cleanup_evdev;


return0;


err_cleanup_evdev:

evdev_cleanup(evdev);

err_unregister_handle:

input_unregister_handle(&evdev->handle);

err_free_evdev:

put_device(&evdev->dev);

returnerror;

}

前面的部分是在dedev的一些成员的初始化,

初始化完成后就会error= input_register_handle(&evdev->handle);进行handle的注册

intinput_register_handle(structinput_handle *handle)

{

structinput_handler *handler = handle->handler;

structinput_dev *dev = handle->dev;

interror;


/*

* We take dev->mutex here to prevent race with

* input_release_device().

*/

error= mutex_lock_interruptible(&dev->mutex);

if(error)

returnerror;


/*

* 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);


return0;

}

EXPORT_SYMBOL(input_register_handle);


看下这个分支*/

if(handler->filter)

list_add_rcu(&handle->d_node,&dev->h_list);

else

list_add_tail_rcu(&handle->d_node,&dev->h_list);

因为evdev里面并没有filter,所以执行下面的分支

list_add_tail_rcu(&handle->d_node,&dev->h_list);

其实这就是将handle添加到inputdevh_list链表里面来,这个很重要,我们在上报事件的时候,就通过这个来找到对应的devhandle->handler->event来上报事件的




看下调用input上报事件的函数执行的过程:

staticinlinevoidinput_report_abs(structinput_dev*dev, unsignedintcode, intvalue)

{

input_event(dev,EV_ABS, code, value);

}



voidinput_event(structinput_dev *dev,

unsignedinttype, unsignedintcode, intvalue)

{

unsignedlongflags;


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);

}

}

EXPORT_SYMBOL(input_event);


我们进入input_handle_event(dev,type, code, value);函数

staticvoid input_handle_event(structinput_dev *dev,

unsignedinttype, unsignedintcode, intvalue)

{

intdisposition = INPUT_IGNORE_EVENT;


switch(type) {


caseEV_SYN:

switch(code) {

caseSYN_CONFIG:

disposition= INPUT_PASS_TO_ALL;

break;


caseSYN_REPORT:

if(!dev->sync){

dev->sync= true;

disposition= INPUT_PASS_TO_HANDLERS;

}

break;

caseSYN_MT_REPORT:

dev->sync= false;

disposition= INPUT_PASS_TO_HANDLERS;

break;

}

break;


caseEV_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);

else

input_stop_autorepeat(dev);

}


disposition= INPUT_PASS_TO_HANDLERS;

}

break;


caseEV_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;


caseEV_ABS:

if(is_event_supported(code, dev->absbit,ABS_MAX))

disposition= input_handle_abs_event(dev, code, &value);


break;


caseEV_REL:

if(is_event_supported(code, dev->relbit,REL_MAX) && value)

disposition= INPUT_PASS_TO_HANDLERS;


break;


caseEV_MSC:

if(is_event_supported(code, dev->mscbit,MSC_MAX))

disposition= INPUT_PASS_TO_ALL;


break;


caseEV_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;


caseEV_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;


caseEV_REP:

if(code <= REP_MAX && value >= 0 &&dev->rep[code]!= value) {

dev->rep[code]= value;

disposition= INPUT_PASS_TO_ALL;

}

break;


caseEV_FF:

if(value >= 0)

disposition= INPUT_PASS_TO_ALL;

break;


caseEV_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);

}

因为讲解的touchdriver

caseEV_ABS:

if(is_event_supported(code, dev->absbit,ABS_MAX))

disposition= input_handle_abs_event(dev, code, &value);


返回INPUT_PASS_TO_HANDLERS

break;


if(disposition & INPUT_PASS_TO_HANDLERS)

input_pass_event(dev,type, code, value);


input_pass_event函数的原型是:


staticvoidinput_pass_event(structinput_dev *dev,

unsignedinttype, unsignedintcode, intvalue)

{

structinput_handler *handler;

structinput_handle *handle;


rcu_read_lock();


handle= rcu_dereference(dev->grab);

if(handle)

handle->handler->event(handle,type, code, value);

else{

boolfiltered = 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);


}elseif(handler->filter(handle,type, code, value))

filtered= true;

}

}


rcu_read_unlock();

}

我们在Inputdev里面没有设置grab成员,grab成员就是为了为inputdev设置一个属于自己的一个handle,而这个handle里面又有handlser,最后去调用hansleer里面的event去传递事件event


但是我们没有去设置grab成员,所以就会执行


list_for_each_entry_rcu(handle,&dev->h_list, d_node){

if(!handle->open)

continue;


handler->event(handle,type, code, value);




这里还需要讲解一点的是:你们觉得这个handle->open的值是哪里来到呢??

这里我们会插入一个插曲,那么就是在kernel/driver/input.c里面的



staticint __init input_init(void)

{

interr;


err= class_register(&input_class);

if(err) {

printk(KERN_ERR"input: unable to registerinput_dev class\n");

returnerr;

}


err= input_proc_init();

if(err)

gotofail1;


err= register_chrdev(INPUT_MAJOR, "input",&input_fops);

if(err) {

printk(KERN_ERR"input: unable to register charmajor %d", INPUT_MAJOR);

gotofail2;

}


return0;


fail2: input_proc_exit();

fail1: class_unregister(&input_class);

returnerr;

}

上面注册一个设备,这个设备的名字会在/proc/devices下面找到

我们看下这个注册的时候所使用的fops



staticconst structfile_operations input_fops = {

.owner= THIS_MODULE,

.open= input_open_file,

}

这个fops里面只有一个open函数

看下这个open函数







staticint input_open_file(structinode *inode, struct file *file)

{

structinput_handler *handler;

conststructfile_operations *old_fops, *new_fops = NULL;

interr;


err= mutex_lock_interruptible(&input_mutex);

if(err)

returnerr;


/*No load-on-demand here? */

handler= input_table[iminor(inode) >> 5];

if(handler)

new_fops= fops_get(handler->fops);


mutex_unlock(&input_mutex);


/*

* That's _really_ odd. Usually NULL ->open means "nothingspecial",

* not "no device". Oh, well...

*/

if(!new_fops || !new_fops->open){

fops_put(new_fops);

err= -ENODEV;

gotoout;

}


old_fops= file->f_op;

file->f_op= new_fops;


err= new_fops->open(inode, file);

if(err) {

fops_put(file->f_op);

file->f_op= fops_get(old_fops);

}

fops_put(old_fops);

out:

returnerr;

}


上面的函数主要是根据次设备号,在静态的input_table里面获得handler


接下来调用handler里面的fops->open函数


handler里面的fops是定义在evdev.c里面




进入这个文件找到这个file_openration



staticint evdev_open(structinode *inode, struct file *file)

{

structevdev *evdev;

structevdev_client *client;

inti = iminor(inode) - EVDEV_MINOR_BASE;

unsignedintbufsize;

interror;


if(i >= EVDEV_MINORS)

return-ENODEV;


error= mutex_lock_interruptible(&evdev_table_mutex);

if(error)

returnerror;

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(structevdev_client) +

bufsize* sizeof(structinput_event),

GFP_KERNEL);

if(!client) {

error= -ENOMEM;

gotoerr_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));

wake_lock_init(&client->wake_lock,WAKE_LOCK_SUSPEND,client->name);

client->evdev= evdev;



error= evdev_open_device(evdev);

if(error)

gotoerr_free_client;


file->private_data= client;

nonseekable_open(inode,file);


return0;


err_free_client:

evdev_detach_client(evdev,client);

wake_lock_destroy(&client->wake_lock);

kfree(client);

err_put_evdev:

put_device(&evdev->dev);

returnerror;

}

接着看

staticint evdev_open_device(structevdev *evdev)

{

intretval;


retval= mutex_lock_interruptible(&evdev->mutex);

if(retval)

returnretval;


if(!evdev->exist)

retval= -ENODEV;

elseif(!evdev->open++){

retval= input_open_device(&evdev->handle);

if(retval)

evdev->open--;

}


mutex_unlock(&evdev->mutex);

returnretval;

}

主要看接下来

intinput_open_device(structinput_handle *handle)

{

structinput_dev *dev = handle->dev;

intretval;


retval= mutex_lock_interruptible(&dev->mutex);

if(retval)

returnretval;


if(dev->going_away){

retval= -ENODEV;

gotoout;

}


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);

returnretval;

}

EX

使用同样的方法,根据次设备号在静态数组里面获得evdev,创建client,将client添加到链表里面。,使用evdev_attach_client(evdev,client);

我们这时候看intinput_open_device(structinput_handle *handle)函数,这个里面就进行了对handle-->open进行了赋值,代码设备已经打开了




这下明白了,input整个执行的过程了吧



我们看下evdev里面的event的是如何实现的


.event =evdev_event,


/*

* Pass incoming event to allconnected clients.

*/

staticvoidevdev_event(structinput_handle *handle,

unsignedinttype, unsignedintcode, intvalue)

{

structevdev *evdev = handle->private;

structevdev_client *client;

structinput_event event;

structtimespec ts;


ktime_get_ts(&ts);

event.time.tv_sec= ts.tv_sec;

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)

evdev_pass_event(client,&event);


rcu_read_unlock();


wake_up_interruptible(&evdev->wait);

}

前半部分进行event的成员变量的赋值

接下来上报事件

staticvoidevdev_pass_event(structevdev_client *client,

structinput_event *event)

{

/*

* Interrupts are disabled, just acquire the lock.

* Make sure we don't leave with the client buffer

* "empty" by having client->head == client->tail.

*/

spin_lock(&client->buffer_lock);

wake_lock_timeout(&client->wake_lock,5 * HZ);

do{

client->buffer[client->head++]= *event;

client->head&= client->bufsize- 1;

}while(client->head== client->tail);

spin_unlock(&client->buffer_lock);


if(event->type== EV_SYN)

kill_fasync(&client->fasync,SIGIO, POLL_IN);

}


这个主要是在将event事件赋值到clientbuffer里面中去





那么上报事件过后我们该干嘛呢??/当我们去读写的时候,就会调用evdev里面的file_operation函数里面的读写的方法




我选一个写的函数吧



staticssize_t evdev_write(structfile *file, const char__user *buffer,

size_tcount, loff_t*ppos)

{

structevdev_client *client = file->private_data;

structevdev *evdev = client->evdev;

structinput_event event;

intretval;


retval= mutex_lock_interruptible(&evdev->mutex);

if(retval)

returnretval;


if(!evdev->exist){

retval= -ENODEV;

gotoout;

}


while(retval < count) {


if(input_event_from_user(buffer + retval, &event)) {

retval= -EFAULT;

gotoout;

}


input_inject_event(&evdev->handle,

event.type,event.code,event.value);

retval+= input_event_size();

}


out:

mutex_unlock(&evdev->mutex);

returnretval;

}

最终会调用input_event_from_user(buffer+ retval, &event)


intinput_event_from_user(constchar __user *buffer,

structinput_event *event)

{

if(copy_from_user(event, buffer, sizeof(structinput_event)))

return-EFAULT;


return0;

}

最后调用了我们字符设备里面的将用户空间的数据写到内核空间的函数copy_from_user,赋值到用户空间的buffer中去,所以用户空间最后得到的就是event里面的成员里面的值

比如structinput_event {

structtimeval time;

__u16type;

__u16code;

__s32value;

};

这里面的值,



inputsubsystem基本是讲解完了,你看明白了吗??


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值