《Linux设备模型之input子系统详解.pdf》由会员分享,可在线阅读,更多相关《Linux设备模型之input子系统详解.pdf(19页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、Linux 设备模型之 input 子系统详解 一:前言 在键盘驱动代码分析的笔记中,接触到了 input 子系统.键盘驱动,键盘驱动将检测到的所有按键都上报给了 input 子系统。Input 子系统是所有 I/O 设备驱动的中间层,为上层提供了一个统一的界面。例如,在终端系统中,我们不需要去管有多少个键盘,多少个鼠标。它只要从 input 子系统中去取对应的事件(按键,鼠标移位等)就可以了。今天就对 input子系统做一个详尽的分析.下面的代码是基于 linux kernel 2.6.25.分析的代码主要位于 kernel2.6.25/drivers/input 下面.二:使用 input
2、 子系统的例子 在内核自带的文档 Documentation/input/input-programming.txt 中。有一个使用 input 子系统的例子,并附带相应的说明。以此为例分析如下:#include#include#include#include#include static void button_interrupt(int irq,void*dummy,struct pt_regs*fp)input_report_key(&button_dev,BTN_1,inb(BUTTON_PORT)&1);input_sync(&button_dev);static int _init
3、 button_init(void)if(request_irq(BUTTON_IRQ,button_interrupt,0,button,NULL)printk(KERN_ERR button.c:Cant allocate irq%dn,button_irq);return-EBUSY;button_dev.evbit0=BIT(EV_KEY);button_dev.keybitLONG(BTN_0)=BIT(BTN_0);input_register_device(&button_dev);static void _exit button_exit(void)input_unregist
4、er_device(&button_dev);free_irq(BUTTON_IRQ,button_interrupt);module_init(button_init);module_exit(button_exit);这个示例 module 代码还是比较简单,在初始化函数里注册了一个中断处理例程。然后注册了一个 input device.在中断处理程序里,将接收到的按键上报给 input 子系统。文档的作者在之后的分析里又对这个 module 作了优化。主要是在注册中断处理的时序上。在修改过后的代码里,为 input device 定义了 open 函数,在 open 的时候再去注册中断处
5、理例程。具体的信息请自行参考这篇文档。在资料缺乏的情况下,kernel 自带的文档就是剖析 kernel 相关知识的最好资料.文档的作者还分析了几个 api 函数。列举如下:1):set_bit(EV_KEY,button_dev.evbit);set_bit(BTN_0,button_dev.keybit);分别用来设置设备所产生的事件以及上报的按键值。Struct iput_dev 中有两个成员,一个是 evbit.一个是 keybit.分别用表示设备所支持的动作和按键类型。2):input_register_device(&button_dev);用来注册一个 input device.
6、3):input_report_key()用于给上层上报一个按键动作 4):input_sync()用来告诉上层,本次的事件已经完成了.5):NBITS(x)-returns the length of a bitfield array in longs for x bits LONG(x)-returns the index in the array in longs for bit x BIT(x)-returns the index in a long for bit x 这几个宏在 input 子系统中经常用到。上面的英文解释已经很清楚了。三:input 设备注册分析.Input 设备
7、注册的接口为:input_register_device()。代码如下: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;_set_bit(EV_SYN,dev-evbit);init_timer(&dev-timer);if(!dev-repREP_DELAY&!dev-repREP_PERIOD)dev-timer.data=(long)dev;dev-tim
8、er.function=input_repeat_key;dev-repREP_DELAY=250;dev-repREP_PERIOD=33;在前面的分析中曾分析过。Input_device 的 evbit 表示该设备所支持的事件。在这里将其 EV_SYN 置位,即所有设备都支持这个事件.如果 dev-repREP_DELAY和 dev-repREP_PERIOD没有设值,则将其赋默认值。这主要是处理重复按键的.if(!dev-getkeycode)dev-getkeycode=input_default_getkeycode;if(!dev-setkeycode)dev-setkeycode
9、=input_default_setkeycode;snprintf(dev-dev.bus_id,sizeof(dev-dev.bus_id),input%ld,(unsigned long)atomic_inc_return(&input_no)-1);error=device_add(&dev-dev);if(error)return error;path=kobject_get_path(&dev-dev.kobj,GFP_KERNEL);printk(KERN_INFO input:%s as%sn,dev-name?dev-name:Unspecified device,path?
10、path:N/A);kfree(path);error=mutex_lock_interruptible(&input_mutex);if(error)device_del(&dev-dev);return error;如果 input device 没有定义 getkeycode 和 setkeycode.则将其赋默认值。还记得在键盘驱动中的分析吗?这两个操作函数就可以用来取键的扫描码和设置键的扫描码。然后调用 device_add()将 input_dev 中封装的 device 注册到 sysfs list_add_tail(&dev-node,&input_dev_list);list
11、_for_each_entry(handler,&input_handler_list,node)input_attach_handler(dev,handler);input_wakeup_procfs_readers();mutex_unlock(&input_mutex);return 0;这里就是重点了。将 input device 挂到 input_dev_list 链表上.然后,对每一个挂在 input_handler_list 的 handler 调用input_attach_handler().在这里的情况有好比设备模型中的 device 和 driver 的匹配。所有的 in
12、put device 都挂在input_dev_list 链上。所有的 handle 都挂在 input_handler_list 上。看一下这个匹配的详细过程。匹配是在 input_attach_handler()中完成的。代码如下:static int input_attach_handler(struct input_dev*dev,struct input_handler*handler)const struct input_device_id*id;int error;if(handler-blacklist&input_match_device(handler-blacklist,
13、dev)return-ENODEV;id=input_match_device(handler-id_table,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:%dn,handler-name,kobject_name(&dev-dev.kobj),error);return error;如果handle的blacklist被赋值。要
14、先匹配blacklist中的数据跟dev-id的数据是否匹配。匹配成功过后再来匹配handle-id和 dev-id 中的数据。如果匹配成功,则调用 handler-connect().来看一下具体的数据匹配过程,这是在 input_match_device()中完成的。代码如下:static const struct input_device_id*input_match_device(const struct input_device_id*id,struct input_dev*dev)int i;for(;id-flags|id-driver_info;id+)if(id-flags&
15、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)cont
16、inue;MATCH_BIT(evbit,EV_MAX);MATCH_BIT(,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);return id;return NULL;MATCH_BIT 宏的定义如下:#define MATCH_BIT(bit,max)for(i=0;
17、i biti&dev-biti)!=id-biti)break;if(i!=BITS_TO_LONGS(max)continue;由此看到。在 id-flags 中定义了要匹配的项。定义 INPUT_DEVICE_ID_MATCH_BUS。则是要比较 input device 和 input handler的 总 线 类 型。INPUT_DEVICE_ID_MATCH_VENDOR,INPUT_DEVICE_ID_MATCH_PRODUCT,INPUT_DEVICE_ID_MATCH_VERSION 分别要求设备厂商。设备号和设备版本.如果 id-flags 定义的类型匹配成功。或者是 id-
18、flags 没有定义,就会进入到 MATCH_BIT 的匹配项了.从 MATCH_BIT宏的定义可以看出。只有当 iput device 和 input handler 的 id 成员在 evbit,keybit,swbit 项相同才会匹配成功。而且匹配的顺序是从 evbit,keybit 到 swbit.只要有一项不同,就会循环到 id 中的下一项进行比较.简而言之,注册 input device 的过程就是为 input device 设置默认值,并将其挂以 input_dev_list.与挂载在 input_handler_list中的 handler 相匹配。如果匹配成功,就会调用 h
19、andler 的 connect 函数.四:handler 注册分析 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_tablehandler-minor 5)retva
20、l=-EBUSY;goto out;input_tablehandler-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);return retval;handler-minor 表示对应 input 设备节点的次设备号.以 handler
21、-minor 右移五位做为索引值插入到 input_table 中.之后再来分析 input_talbe 的作用.然后将 handler 挂到 input_handler_list 中.然后将其与挂在 input_dev_list 中的 input device 匹配.这个过程和 input device的注册有相似的地方.都是注册到各自的链表,.然后与另外一条链表的对象相匹配.五:handle 的注册 int input_register_handle(struct input_handle*handle)struct input_handler*handler=handle-handler
22、;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;list_add_tail_rcu(&handle-d_node,&dev-h_list);mutex_unlock(&dev-mutex);synchronize_rcu();list_add_tail(&handle-h_node,&h
23、andler-h_list);if(handler-start)handler-start(handle);return 0;在这个函数里所做的处理其实很简单.将 handle 挂到所对应 input device 的 h_list 链表上.还将 handle 挂到对应的 handler的 hlist 链表上.如果 handler 定义了 start 函数,将调用之.到 这 里,我 们 已 经 看 到 了 input device,handler 和 handle 是 怎 么 关 联 起 来 的 了.以 图 的 方 式 总 结 如 下:六:event 事件的处理 我们在开篇的时候曾以 linu
24、x kernel 文档中自带的代码作分析.提出了几个事件上报的 API.这些 API 其实都是input_event()的封装.代码如下: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_r
25、andomness(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_EV
26、ENT;switch(type)case EV_SYN:switch(code)case SYN_CONFIG:disposition=INPUT_PASS_TO_ALL;break;case SYN_REPORT:if(!dev-sync)dev-sync=1;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
27、_bit(code,dev-key);if(value)input_start_autorepeat(dev,code);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-abs
28、bit,ABS_MAX)value=input_defuzz_abs_event(value,dev-abscode,dev-absfuzzcode);if(dev-abscode!=value)dev-abscode=value;disposition=INPUT_PASS_TO_HANDLERS;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(cod
29、e,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)!=!valu
30、e)_change_bit(code,dev-snd);disposition=INPUT_PASS_TO_ALL;break;case EV_REP:if(code=0&dev-repcode!=value)dev-repcode=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(type!=EV_SYN)dev-sync=0;if(dispo
31、sition&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);在这里,我们忽略掉具体事件的处理.到最后,如果该事件需要 input device 来完成的,就会将 disposition 设置成INPUT_PASS_TO_DEVICE.如果需要handler来完成的,就将 dispostion 设为 INPUT_PASS_TO_DEVICE.如果需要两者都参与,将 d
32、isposition 设置为 INPUT_PASS_TO_ALL.需要输入设备参与的,回调设备的 event 函数.如果需要 handler 参与的.调用 input_pass_event().代码如下:static void input_pass_event(struct input_dev*dev,unsigned int type,unsigned int code,int value)struct input_handle*handle;rcu_read_lock();handle=rcu_dereference(dev-grab);if(handle)handle-handler-e
33、vent(handle,type,code,value);else list_for_each_entry_rcu(handle,&dev-h_list,d_node)if(handle-open)handle-handler-event(handle,type,code,value);rcu_read_unlock();如果 input device 被强制指定了 handler,则调用该 handler 的 event 函数.结合 handle 注册的分析.我们知道.会将 handle 挂到 input device 的 h_list 链表上.如果没有为 input device 强制指定
34、 handler.就会遍历 input device-h_list 上的 handle 成员.如果该 handle 被打开,则调用与输入设备对应的 handler 的 event()函数.注意,只有在 handle 被打开的情况下才会接收到事件.另外,输入设备的 handler 强制设置一般是用带 EVIOCGRAB 标志的 ioctl 来完成的.如下是发图的方示总结 evnet 的处理过程:我们已经分析了 input device,handler 和 handle 的注册过程以及事件的上报和处理.下面以 evdev 为实例做分析.来贯穿理解一下整个过程.七:evdev 概述 Evdev对应的
35、设备节点一般位于/dev/input/event0 /dev/input/event4.理论上可以对应32个设备节点.分别代表被handler匹配的 32 个 input device.可以用 cat/dev/input/event0.然后移动鼠标或者键盘按键就会有数据输出(两者之间只能选一.因为一个设备文件只能关能一个输入设备).还可以往这个文件里写数据,使其产生特定的事件.这个过程我们之后再详细分析.为了分析这一过程,必须从 input 子系统的初始化说起.八:input 子系统的初始化 Input 子系统的初始化函数为 input_init().代码如下:static int _init
36、 input_init(void)int err;err=class_register(&input_class);if(err)printk(KERN_ERR input:unable to register input_dev classn);return err;err=input_proc_init();if(err)goto fail1;err=register_chrdev(INPUT_MAJOR,input,&input_fops);if(err)printk(KERN_ERR input:unable to register char major%d,INPUT_MAJOR);
37、goto fail2;return 0;fail2:input_proc_exit();fail1:class_unregister(&input_class);return err;在这个初始化函数里,先注册了一个名为”input”的类.所有 input device 都属于这个类.在 sysfs 中表现就是所有 input device 所代表的目录都位于/dev/class/input 下面.然后调用 input_proc_init()在/proc 下面建立相关的交互文件.再后调用 register_chrdev()注册了主设备号为 INPUT_MAJOR(13).次设备号为 0255
38、的字符设备.它的操作指针为input_fops.在 这 里,我 们 看 到.所 有 主 设 备 号 13 的 字 符 设 备 的 操 作 最 终 都 会 转 入 到 input_fops 中.在 前 面 分 析 的/dev/input/event0/dev/input/event4 的主设备号为 13.操作也不例外的落在了 input_fops 中.Input_fops 定义如下:static const struct file_operations input_fops=.owner=THIS_MODULE,.open=input_open_file,;打开文件所对应的操作函数为 input
39、_open_file.代码如下示:static int input_open_file(struct inode*inode,struct file*file)struct input_handler*handler=input_tableiminor(inode)5;const struct file_operations*old_fops,*new_fops=NULL;int err;if(!handler|!(new_fops=fops_get(handler-fops)return-ENODEV;iminor(inode)为打开文件所对应的次设备号.input_table是一个stru
40、ct input_handler全局数组.在这里.它先设备结点的次设备号右移 5 位做为索引值到 input_table 中取对应项.从这里我们也可以看到.一个 handle 代表 1open)fops_put(new_fops);return-ENODEV;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);return err;然后将 handler 中的 f
41、ops 替换掉当前的 fops.如果新的 fops 中有 open()函数,则调用它.九:evdev 的初始化 Evdev 的模块初始化函数为 evdev_init().代码如下:static int _init evdev_init(void)return input_register_handler(&evdev_handler);它调用了 input_register_handler 注册了一个 handler.注意到,在这里 evdev_handler 中定义的 minor 为 EVDEV_MINOR_BASE(64).也就是说 evdev_handler 所表示的设备文件范围为(13
42、,64)(13,64+32).从之前的分析我们知道.匹配成功的关键在于 handler 中的 blacklist 和 id_talbe.Evdev_handler 的 id_table 定义如下:static const struct input_device_id evdev_ids=.driver_info=1,;它没有定义 flags.也没有定义匹配属性值.这个 handler 是匹配所有 input device 的.从前面的分析我们知道.匹配成功之后会调用 handler-connect 函数.在 Evdev_handler 中,该成员函数如下所示:static int evdev_
43、connect(struct input_handler*handler,struct input_dev*dev,const struct input_device_id*id)struct evdev*evdev;int minor;int error;for(minor=0;minor client_list);spin_lock_init(&evdev-client_lock);mutex_init(&evdev-mutex);init_waitqueue_head(&evdev-wait);snprintf(evdev-name,sizeof(evdev-name),event%d,
44、minor);evdev-exist=1;evdev-minor=minor;evdev-handle.dev=input_get_device(dev);evdev-handle.name=evdev-name;evdev-handle.handler=handler;evdev-handle.private=evdev;接下来,分配了一个 evdev 结构,并对这个结构进行初始化.在这里我们可以看到,这个结构封装了一个 handle 结构,这结构与我们之前所讨论的 handler 是不相同的.注意有一个字母的差别哦.我们可以把 handle 看成是 handler 和 input devi
45、ce 的信息集合体.在这个结构里集合了匹配成功的 handler 和 input device strlcpy(evdev-dev.bus_id,evdev-name,sizeof(evdev-dev.bus_id);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);在这段代码里主要完成 evdev 封装的
46、 device 的初始化.注意在这里,使它所属的类指向 input_class.这样在/sysfs 中创建的设备目录就会在/sys/class/input/下面显示.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 0;err_c
47、leanup_evdev:evdev_cleanup(evdev);err_unregister_handle:input_unregister_handle(&evdev-handle);err_free_evdev:put_device(&evdev-dev);return error;注册 handle,如果是成功的,那么调用 evdev_install_chrdev 将 evdev_table 的 minor 项指向 evdev.然后将 evdev-device 注册到 sysfs.如果失败,将进行相关的错误处理.万事俱备了,但是要接收事件,还得要等”东风”.这个”东风”就是要打开相应
48、的 handle.这个打开过程是在文件的 open()中完成的.十:evdev 设备结点的 open()操作 我们知道.对主设备号为INPUT_MAJOR的设备节点进行操作,会将操作集转换成handler的操作集.在evdev中,这个操作集就是 evdev_fops.对应的 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;int error;
49、if(i=EVDEV_MINORS)return-ENODEV;error=mutex_lock_interruptible(&evdev_table_mutex);if(error)return error;evdev=evdev_tablei;if(evdev)get_device(&evdev-dev);mutex_unlock(&evdev_table_mutex);if(!evdev)return-ENODEV;client=kzalloc(sizeof(struct evdev_client),GFP_KERNEL);if(!client)error=-ENOMEM;goto er
50、r_put_evdev;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;return 0;err_free_client:evdev_detach_client(evdev,client);kfree(client);err_put_evdev:put_device(&evdev-dev);ret