Linux 输入子系统分析(一)
Linux 输入子系统分析(二)
分析一个内核提供的input_handler
Linux内核输入子系统
输入设备(如按键、键盘、触摸屏、鼠标等)是典型的字符设备,其一般的工作机理是底层在按键、触摸等动作发送时产生一个中断,然后CPU通过USB、I2C等读取键值、坐标等数据,并将它们放入一个缓冲区,字符设备驱动管理该缓冲区,而驱动的read接口让用户可以读取键值、坐标等数据。
显然,在这些工作中,只是中断、读键值/坐标值是与设备相关的,而输入事件的缓冲区管理以及字符设备驱动的file_operations接口则对输入设备是通用的。基于此,内核设计了输入子系统,由核心层处理公共的工作。Linux内核输入子系统的框架如下图所示。
linux输入子系统从上到下由三层实现,分别为:输入子系统事件处理层(Event handler)、输入子系统核心层(Input core)和输入子系统设备驱动层(Input driver)。
驱动层
对于输入子系统设备驱动层而言,主要实现对硬件设备的读写访问,中断设置,并把硬件产生的事件转换为核心层定义的规范提交给事件处理层。将底层的硬件输入转化为统一事件形式,想输入核心汇报。
输入子系统核心层
对于核心层而言,为设备驱动层、事件处理层提供了规范和接口。设备驱动层只要关心如何驱动硬件并获得硬件数据,然后调用核心层提供的接口,核心层会自动把数据提交给事件处理层,它起到承上启下的作用。
事件处理层
对于事件处理层而言,提供用户编程的接口(设备节点),并处理驱动层提交的数据处理。
主要数据结构
input_dev结构体,描述一个输入设备,定义如下:
struct input_dev {
//设备name
const char *name;
......
//用于与input_handler匹配
struct input_id id;
......
/* evbit位图,表示设备支持的输入事件类型
如:设备支持按键事件类型,则set_bit(EV_KEY, input_dev ->evbit);
*/
unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
/* keybit位图,表示设备有哪些键值
如,设备支持'L'这个键值,则通过set_bit(KEY_L, key_input_dev->keybit)设置键值
*/
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
//relbit、absbit等与keybit类似
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 repeat_key;
//用于实现长按的定时器(对于EV_KEY)
struct timer_list timer;
......
/* 用于表示当前设备按键的状态的位图
调用input_report_key函数上报按键时,会设置该状态位图
*/
unsigned long key[BITS_TO_LONGS(KEY_CNT)];
//led、snd等与key类似
unsigned long led[BITS_TO_LONGS(LED_CNT)];
unsigned long snd[BITS_TO_LONGS(SND_CNT)];
unsigned long sw[BITS_TO_LONGS(SW_CNT)];
//应用层第一次open设备时,会调用该函数
int (*open)(struct input_dev *dev);
//应用层close设备时,会调用该函数
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);
//如果设置了该项,表示设备上报的事件,全都由该input_handle来处理
struct input_handle __rcu *grab;
.......
//链接与该设备相关的input_handle
struct list_head h_list;
//用于链入全局链表input_dev_list的节点
struct list_head node;
......
}
input_handler结构体,描述一个输入事件处理接口。怎么理解呢?输入设备会上传事件,输入核心层会把这个事件交给事件处理层来处理,最终交给的对象就是这个input_handler,input_handler里面有许多的成员函数,通过这些成员函数来处理事件。交给一个哪些input_handler处理呢?这里就涉及到匹配的问题,即input_handler与input_dev的匹配。内核为一些常用的输入设备提供了对应的input_handler,如keyboard.c、mousedev.c等。
input_handler结构体的定义如下:
struct input_handler {
void *private;
//设备上报的event,最终由该函数处理处理
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);
//用于过滤某些event
bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
//用于进一步匹配设备
bool (*match)(struct input_handler *handler, struct input_dev *dev);
//与设备匹配成功,调用connect函数
int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
void (*disconnect)(struct input_handle *handle);
//注册handle时,会调用start
void (*start)(struct input_handle *handle);
......
//id表,用于匹配input_dev
const struct input_device_id *id_table;
//链接与该input_handler相关的input_handle
struct list_head h_list;
struct list_head node;
};
input_handle结构体,内核设计这一数据结构,用于为匹配的input_handler与input_dev建立桥梁。通过这一结构体,input_handler或input_dev可以找到匹配的双方,结构体定义如下:
struct input_handle {
void *private;
int open;
const char *name;
struct input_dev *dev;
struct input_handler *handler;
//通过该节点,链入input_dev的h_list链表
struct list_head d_node;
//通过该节点,链入input_handler的h_list链表
struct list_head h_node;
};
input_dev、input_handle与input_handler之间的关系如下图:
可以通过遍历input_dev的h_list链表找个与该input_dev匹配的input_handler。
当然,对于input_handler,也可以遍历其h_list链表,找到与之匹配的input_dev。
input_event结构体,描述一个事件,定义如下:
struct input_event {
//时间戳
struct timeval time;
//输入事件类型
__u16 type;
__u16 code;
__s32 value;
};
输入设备各种各样,对应的输入事件类型也有所不同,内核定义了几乎所有能涵盖的输入事件类型:
#define EV_SYN 0x00 //同步事件
#define EV_KEY 0x01 //按键事件
#define EV_REL 0x02 //相对坐标(如:鼠标移动,报告相对最后一次位置的偏移)
#define EV_ABS 0x03 //绝对坐标(如:触摸屏或操作杆,报告绝对的坐标位置)
#define EV_MSC 0x04 //其它
#define EV_SW 0x05 //开关
#define EV_LED 0x11 //按键/设备灯
#define EV_SND 0x12 //声音/警报
#define EV_REP 0x14 //重复
#define EV_FF 0x15 //力反馈
#define EV_PWR 0x16 //电源
#define EV_FF_STATUS 0x17 //力反馈状态