Linux输入设备
Linux将按键、键盘、鼠标、触摸屏等设备统称为输入设备,其本质还是字符设备。在一般情况下,这些输入设备在被按下时会产生一个中断或者由CPU定时轮询这些输入设备,如果输入有效,那么cpu会将读到的键值存在缓冲区,驱动的read接口就可以让用户层获取到这些键值。 对于上述流程,在cpu读到有效键值之后的处理都是一样的,也就是read接口只会读取键值,不会关心具体是哪个设备。因此,Linux就设计了一个input框架来处理输入事件,这就是——input子系统。 总结起来就是——Linux内核输入子系统是对不同类别的输入设备进行抽象,随后调用统一的服务函数进行处理。
Linux 输入子系统架构
Linux input子系统框架可以分为以下三个部分: 1.驱动层(输入设备驱动):获取底层硬件(按键、键盘、鼠标等)的输入,然后向input核心层报告输入内容。 2.核心层:向下为驱动层提供设备注册与操作接口;向上通知事件层对相应的输入事件进行处理;在/proc目录下下产生相应的设备信息。核心层源码位置drivers/input/input.c 3.事件层(输入事件驱动):与应用层交互,实现了open、read、write这些文件接口,同时在/dev下生成对应的设备文件。
Linux自带如下输入事件驱动程序:
输入事件驱动程序 | 说明 |
---|
evdev.c | 通用输入事件驱动,能处理大多数输入事件 | joydev.c | 游戏杆,操纵杆设备 | keyboard.c | 键盘设备 | mousedev.c | 鼠标设备 | keychrod.c | 组合按键设备 |
input device
input device 处于驱动层,表示每一个真实的输入设备,如鼠标、键盘、按键等。
struct input_dev {
const char *name;
const char *phys;
const char *uniq;
struct input_id id;
unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
unsigned long relbit[BITS_TO_LONGS(REL_CNT)];
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];
unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
unsigned int hint_events_per_packet;
unsigned int keycodemax;
unsigned int keycodesize;
void *keycode;
int (*setkeycode)(struct input_dev *dev,
const struct input_keymap_entry *ke,
unsigned int *old_keycode);
int (*getkeycode)(struct input_dev *dev,
struct input_keymap_entry *ke);
struct ff_device *ff;
unsigned int repeat_key;
struct timer_list timer;
int rep[REP_CNT];
struct input_mt *mt;
struct input_absinfo *absinfo;
unsigned long key[BITS_TO_LONGS(KEY_CNT)];
unsigned long led[BITS_TO_LONGS(LED_CNT)];
unsigned long snd[BITS_TO_LONGS(SND_CNT)];
unsigned long sw[BITS_TO_LONGS(SW_CNT)];
int (*open)(struct input_dev *dev);
void (*close)(struct input_dev *dev);
int (*flush)(struct input_dev *dev, struct file *file);
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
struct input_handle __rcu *grab;
spinlock_t event_lock;
struct mutex mutex;
unsigned int users;
bool going_away;
struct device dev;
struct list_head h_list;
struct list_head node;
unsigned int num_vals;
unsigned int max_vals;
struct input_value *vals;
bool devres_managed;
ktime_t timestamp[INPUT_CLK_MAX];
};
input handler
input handler 表示一个或者一类输入设备的事件处理程序,它处理来自底层input 设备的输入事件,同时对应用层提供file_operations 接口,用户层可以通过open 、read 系统调用获取input 设备的输入事件。
struct input_handler {
void *private;
void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
void (*events)(struct input_handle *handle,
const struct input_value *vals, unsigned int count);
bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
bool (*match)(struct input_handler *handler, struct input_dev *dev);
int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
void (*disconnect)(struct input_handle *handle);
void (*start)(struct input_handle *handle);
bool legacy_minors;
int minor;
const char *name;
const struct input_device_id *id_table;
struct list_head h_list;
struct list_head node;
};
input handler 关联到input device 并创建input handle 。 可能有多个input handler 同时关联到任何给定的input device 。 它们都将获得由设备生成的输入事件的副本。 filter() 和event() 使用的参数完全一致。input core 允许filter() 首先执行,并且如果任何过滤器指示应过滤事件(通过从其 filter() 方法返回 %true ),则不会将事件传递给常规input handlers 。
input handle
input handle 用来关联input device 和input handler
struct input_handle {
void *private;
int open;
const char *name;
struct input_dev *dev;
struct input_handler *handler;
struct list_head d_node;
struct list_head h_node;
};
input core
input core 维护两个全局链表,分别是存放input 设备的input_dev_list 和input handler的input_handler_list ,设备和事件的注册需要向这两个链表中添加新的节点,反之则需要删除节点。
不论是设备还是事件注册的过程中,都会进行匹配,从而将设备和事件关联起来。匹配过程如下,以input设备注册为例说明:
当新的input设备进行注册时,会遍历所有的input_handler_list 中的事件驱动一一进行匹配,匹配的条件有bustype ,vendor ,product ,version ,evbit 等。当匹配成功后,再接着执行handler->connect() 执行剩下的操作。 实际上,如果是input事件驱动进行注册,流程也是差不多的,会遍历input_dev_list 中所有的input 设备依次进行匹配,匹配成功后, 依然会执行handler->connect() 执行剩下的操作。
connect
前文提到的设备和事件驱动匹配只是说它们互相找到了对方,但是还没有建立任何联系,handler->connect() 是最终实现input设备和事件驱动绑定的函数。为什么需要connect 函数进行绑定,而不是直接在注册阶段把driver 的函数指针赋值给device 的某个结构体成员?或者把device 的函数指针赋值给driver 的某个结构体成员?这是因为input设备是支持一对多或者多对一的,如果是一对一的关系,那么完全可以按照上述的做。 于是Linux 内核引入了另一个成员来完成此工作——struct handle (注意和struct handler的区别) 当device 和handler 匹配后,connect 函数中会将device 和handler 都存入handle 结构体,然后再将handle 结构体分别添加到device 和handler 的h_list 链表中,这样device 和handler 不管是一对一还是一对多,都可以找到所有的handler 或者device 。 本质上就是将device 对应的handler 用链表保存起来。
connect 函数不仅实现了input handle 的注册,还完成了input cdev 的注册,每一次connect 都会触发注册一个input handle 和input cdev ,而这个cdev 就对应/dev/input/eventX .
|