1、Linux设备模型之input子系统详解Linux设备模型之input子系统详解一:前言 在键盘驱动代码分析的笔记中,接触到了input子系统。键盘驱动,键盘驱动将检测到的所有按键都上报给了input子系统。Input子系统是所有I/O设备驱动的中间层,为上层提供了一个统一的界面。例如,在终端系统中,我们不需要去管有多少个键盘,多少个鼠标。它只要从input子系统中去取对应的事件(按键,鼠标移位等)就可以了。今天就对input子系统做一个详尽的分析。 下面的代码是基于linux kernel 2.6.25.分析的代码主要位于kernel2.6.25/drivers/input下面。 二:使用i
2、nput子系统的例子 在内核自带的文档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); s
3、tatic int _init 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 _e
4、xit button_exit(void) input_unregister_device(&button_dev); free_irq(BUTTON_IRQ, button_interrupt); module_init(button_init); module_exit(button_exit); 这个示例module代码还是比较简单,在初始化函数里注册了一个中断处理例程。然后注册了一个input device.在中断处理程序里,将接收到的按键上报给input子系统。 文档的作者在之后的分析里又对这个module作了优化。主要是在注册中断处理的时序上。在修改过后的代码里,为input de
5、vice定义了open函数,在open的时候再去注册中断处理例程。具体的信息请自行参考这篇文档。在资料缺乏的情况下,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(&but
6、ton_dev); 用来注册一个input device. 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子系统中经常
7、用到。上面的英文解释已经很清楚了。 三:input设备注册分析。 Input设备注册的接口为: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); /* * If delay and period are pre-set by th
8、e driver, then autorepeating * is handled by the driver itself and we dont do it in input.c. */ init_timer(&dev-timer); if (!dev-repREP_DELAY & !dev-repREP_PERIOD) dev-timer.data = (long) dev; dev-timer.function = input_repeat_key; dev-repREP_DELAY = 250; dev-repREP_PERIOD = 33; 在前面的分析中曾分析过。Input_de
9、vice的evbit表示该设备所支持的事件。在这里将其EV_SYN置位,即所有设备都支持这个事件。如果dev-repREP_DELAY和dev-repREP_PERIOD没有设值,则将其赋默认值。这主要是处理重复按键的。 if (!dev-getkeycode) dev-getkeycode = input_default_getkeycode; if (!dev-setkeycode) dev-setkeycode = input_default_setkeycode; snprintf(dev-dev.bus_id, sizeof(dev-dev.bus_id), input%ld, (u
10、nsigned 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 ? path : N/A); kfree(path); error = mutex_lock_interruptible(&
11、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_for_each_entry(handler, &input_handler_list, node) input_
12、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的匹配。所有的input device都挂在input_dev_list链上。所有的handle都挂在input_handler_list上。
13、 看一下这个匹配的详细过程。匹配是在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, dev) return -ENODEV; id = input_match_device(handler-id_
14、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被赋值。要先匹配blacklist中的数据跟dev-id的
15、数据是否匹配。匹配成功过后再来匹配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 & INPUT_DEVIC
16、E_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 !=
17、 dev-id.version) continue; 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
18、宏的定义如下: #define MATCH_BIT(bit, max) for (i = 0; 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
19、分别要求设备厂商。设备号和设备版本。 如果id-flags定义的类型匹配成功。或者是id-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中的
20、handler相匹配。如果匹配成功,就会调用handler的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)
21、 if (input_tablehandler-minor 5) retval = -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); ret
22、urn retval; handler-minor表示对应input设备节点的次设备号。以handler-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) s
23、truct 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; list_add_tail_rcu(&handle-d_node, &dev-h_list); mutex_unl
24、ock(&dev-mutex); synchronize_rcu(); /* * Since we are supposed to be called from -connect() * which is mutually exclusive with -disconnect() * we cant be racing with input_unregister_handle() * and so separate lock is not needed here. */ list_add_tail(&handle-h_node, &handler-h_list); if (handler-st
25、art) handler-start(handle); return 0; 在这个函数里所做的处理其实很简单。将handle挂到所对应input device的h_list链表上。还将handle挂到对应的handler的hlist链表上。如果handler定义了start函数,将调用之。 到这里,我们已经看到了input device, handler和handle是怎么关联起来的了。以图的方式总结如下: 六:event事件的处理 我们在开篇的时候曾以linux kernel文档中自带的代码作分析。提出了几个事件上报的API.这些API其实都是input_event()的封装。代码如下: v
26、oid 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
27、, 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 (c
28、ode) 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_bit(
29、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
30、_supported(code, dev-absbit, 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(co
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1