1 输入子系统架构Overview
http://blogold.chinaunix.net/u1/58855/showart_1737481.html
输入子系统(Input Subsystem)的架构如下图所示
输入子系统由 输入子系统核心层( Input Core ),驱动层和事件处理层(EventHandler)三部份组成。一个输入事件,如鼠标移动,键盘按键按下,joystick的移动等等通过 Driver ->InputCore -> Eventhandler -> userspace 的顺序到达用户空间传给应用程序。
其中Input Core 即 Input Layer 由 driver/input/input.c及相关头文件实现。对下提供了设备驱动的接口,对上提供了Event Handler层的编程接口。
1.1 主要数据结构
表 1 Input Subsystem main data structure
数据结构 | 用途 | 定义位置 | 具体数据结构的分配和初始化 |
Struct input_dev | 驱动层物理Input设备的基本数据结构 | Input.h | 通常在具体的设备驱动中分配和填充具体的设备结构 |
Struct Evdev Struct Mousedev Struct Keybdev… | Event Handler层逻辑Input设备的数据结构 | Evdev.c Mousedev.c Keybdev.c | Evdev.c/Mouedev.c …中分配
|
Struct Input_handler | Event Handler的结构 | Input.h | Event Handler层,定义一个具体的Event Handler。 |
Struct Input_handle | 用来创建驱动层Dev和Handler链表的链表项结构 | Input.h | Event Handler层中分配,包含在Evdev/Mousedev…中。 |
1.2 输入子系统架构示例图
图2 输入子系统架构示例图
2 输入链路的创建过程
由于input子系统通过分层将一个输入设备的输入过程分隔为独立的两部份:驱动到Input Core,Input Core到Event Handler。所以整个链路的这两部分的接口的创建是独立的。
2.1 硬件设备的注册
驱动层负责和底层的硬件设备打交道,将底层硬件对用户输入的响应转换为标准的输入事件以后再向上发送给Input Core。
驱动层通过调用Input_register_device函数和Input_unregister_device函数来向输入子系统中注册和注销输入设备。
这两个函数调用的参数是一个Input_dev结构,这个结构在driver/input/input.h中定义。驱动层在调用Input_register_device之前需要填充该结构中的部分字段
#include <linux/input.h>
#include <linux/module.h>
#include <linux/init.h>
MODULE_LICENSE("GPL");
struct input_dev ex1_dev;
static int __init ex1_init(void)
{
/* extra safe initialization */
memset(&ex1_dev, 0, sizeof(struct input_dev));
init_input_dev(&ex1_dev);
/* set up descriptive labels */
ex1_dev.name = "Example 1 device";
/* phys is unique on a running system */
ex1_dev.phys = "A/Fake/Path";
ex1_dev.id.bustype = BUS_HOST;
ex1_dev.id.vendor = 0x0001;
ex1_dev.id.product = 0x0001;
ex1_dev.id.version = 0x0100;
/* this device has two keys (A and B) */
set_bit(EV_KEY, ex1_dev.evbit);
set_bit(KEY_B, ex1_dev.keybit);
set_bit(KEY_A, ex1_dev.keybit);
/* and finally register with the input core */
input_register_device(&ex1_dev);
return 0;
}
其中比较重要的是evbit字段用来定义该输入设备可以支持的(产生和响应)的事件的类型。
包括:
Ø EV_RST 0x00 Reset
Ø EV_KEY 0x01 按键
Ø EV_REL 0x02 相对坐标
Ø EV_ABS 0x03 绝对坐标
Ø EV_MSC 0x04 其它
Ø EV_LED 0x11 LED
Ø EV_SND 0x12 声音
Ø EV_REP 0x14 Repeat
Ø EV_FF 0x15 力反馈
一个设备可以支持一个或多个事件类型。每个事件类型下面还需要设置具体的触发事件,比如EV_KEY事件,支持哪些按键等。
2.2 Event Handler层
2.2.1 注册Input Handler
驱动层只是把输入设备注册到输入子系统中,在驱动层的代码中本身并不创建设备结点。应用程序用来与设备打交道的设备结点的创建由EventHandler层调用Input core中的函数来实现。而在创建具体的设备节点之前,EventHandler层需要先注册一类设备的输入事件处理函数及相关接口
以MouseDev Handler为例:
static struct input_handler mousedev_handler = {
event: mousedev_event,
connect: mousedev_connect,
disconnect: mousedev_disconnect,
fops: &mousedev_fops,
minor: MOUSEDEV_MINOR_BASE,
};
static int __init mousedev_init(void)
{
input_register_handler(&mousedev_handler);
memset(&mousedev_mix, 0, sizeof(struct mousedev));z
init_waitqueue_head(&mousedev_mix.wait);
mousedev_table[MOUSEDEV_MIX] = &mousedev_mix;
mousedev_mix.exist = 1;
mousedev_mix.minor = MOUSEDEV_MIX;
mousedev_mix.devfs = input_register_minor("mice", MOUSEDEV_MIX, MOUSEDEV_MINOR_BASE);
printk(KERN_INFO "mice: PS/2 mouse device common for all mice\n");
return 0;
}
在Mousedev_init中调用input.c中定义的input_register_handler来注册一个鼠标类型的Handler. 这里的Handler不是具体的用户可以操作的设备,而是鼠标类设备的统一的处理函数接口。
2.2.2 设备节点的创建
接下来,mousedev_init函数调用input_register_minor注册一个通用mice设备,这才是与用户相关联的具体的设备接口。然而这里在init函数中创建一个通用的Mice设备只是鼠标类EventHandler层的特例。在其它类型的EventHandler层中,并不一定会创建一个通用的设备。
标准的流程见是硬件驱动向Input子系统注册一个硬件设备后,在input_register_device中调用已经注册的所有类型的InputHandler的connect函数,每一个具体的Connect函数会根据注册设备所支持的事件类型判断是否与自己相关,如果相关就调用input_register_minor创建一个具体的设备节点。
void input_register_device(struct input_dev *dev)
{
……
while (handler) {
if ((handle = handler->connect(handler, dev)))
input_link_handle(handle);
handler = handler->next;
}
}
此外如果已经注册了一些硬件设备,此后再注册一类新的Input Handler,则同样会对所有已注册的Device调用新的Input Handler的Connect函数已确定是否需要创建新的设备节点:
void input_register_handler(struct input_handler *handler)
{
……
while (dev) {
if ((handle = handler->connect(handler, dev)))
input_link_handle(handle);
dev = dev->next;
}
}
从上面的分析中可以看到一类Input Handler可以和多个硬件设备相关联,创建多个设备节点。而一个设备也可能与多个Input Handler相关联,创建多个设备节点。
直观起见,物理设备,Input Handler,逻辑设备之间的多对多关系可见下图:
图3 物理设备,Input Handler,逻辑设备关系图
3 设备的打开和读写
用户程序通过Input Handler层创建的设备节点的Open,read,write等函数打开和读写输入设备。
3.1 Open
设备节点的Open函数,首先会调用一类具体的InputHandler的Open函数,处理一些和该类型设备相关的通用事务,比如初始化事件缓冲区等。然后通过Input.c中的input_open_device函数调用驱动层中具体硬件设备的Open函数。
3.2 Read
大多数Input Handler的Read函数等待在EventLayer层逻辑设备的wait队列上。当设备驱动程序通过调用Input_event函数将输入以事件的形式通知给输入子系统的时候,相关的InputHandler的event函数被调用,该event函数填充事件缓冲区后将等待队列唤醒。
在驱动层中,读取设备输入的一种可能的实现机制是扫描输入的函数睡眠在驱动设备的等待队列上,在设备驱动的中断函数中唤醒等待队列,而后扫描输入函数将设备输入包装成事件的形式通知给输入子系统。
3.3 Write
2.4内核中没有固定的模式,根据具体的Input Handler,可能不实现,也可能通过调用Input_event将写入的数据以事件的形式再次通知给输入子系统,或者调用设备驱动的Write函数等等。
2.6内核的代码中,通过调用Input_event将写入的数据以事件的形式再次通知给输入子系统,而后在Input.c中根据事件的类型,将需要反馈给物理设备的事件通过调用物理设备的Event函数传给设备驱动处理,如EV_LED事件:
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
......
case EV_LED:
if (code > LED_MAX || !test_bit(code, dev->ledbit) || !!test_bit(code, dev->led) == value)
return;
change_bit(code, dev->led);
if (dev->event) dev->event(dev, type, code, value);
break;
......
}
4 其它
本文中对Input子系统架构的分析主要是基于2.4.20内核,在2.6内核中对Input子系统做了很大的扩充增加了对许多设备的支持(如触摸屏,键盘等)。不过整体的框架还是一致的。
另,参考了linux journal上的两篇文章:
The Linux USB Input Subsystem, Part I | Linux Journal
Using the Input Subsystem, Part II | Linux Journal