kernel version: linux-4.9.13
Linux内核input子系统分为三层:
设备驱动层包含各类输入设备驱动(如触摸屏、鼠标、键盘等等),获取输入事件并上报;
核心层根据输入设备种类,分发事件至不同的事件处理器;
事件处理层包含:通用事件处理器(evdev)、鼠标事件处理器(mousedev)、摇杆事件处理器(joydev),缓存事件并提供接口等待用户获取。
每个输入设备驱动中都实例化了一个input_dev对象,用于记录设备硬件相关信息、事件位图设置(支持的事件类型)以及其他参数。
struct input_dev {
const char *name; // 设备名称
/*
struct input_id {
__u16 bustype; // 总线
__u16 vendor; // 厂商
__u16 product; // 产品
__u16 version; // 版本
};
*/
struct input_id id; // 设备id,用于与input_handler匹配
......
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)]; // 绝对位移事件位图
......
struct device dev;
struct list_head h_list; // 内核链表头
struct list_head node; // 内核链表节点
......
};
同样,每个事件处理器也都实例化了input_handler对象,如evdev_handler、mousedev_handler、joydev_handler等等,input_handler类中提供了事件处理、与输入设备匹配连接相关的接口。
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;
/* 输入设备支持列表,用于与input_dev匹配 */
const struct input_device_id *id_table;
struct list_head h_list; // 内核链表头
struct list_head node; // 内核链表节点
};
input子系统维护了两条重要链表:
两条链表在核心层input.c中静态声明并初始化:
static LIST_HEAD(input_dev_list);
static LIST_HEAD(input_handler_list);
当一个输入设备注册时,它会被加入到输入设备链表(input_dev_list),同时遍历事件处理器链表(input_handler_list)进行匹配:
int input_register_device(struct input_dev *dev)
{
......
// 将输入设备加入内核链表
list_add_tail(&dev->node, &input_dev_list);
// 遍历事件处理器链接匹配
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
......
}
匹配过程以事件处理器的id_table为判断条件1,如果输入设备与事件处理器匹配成功,则调用事件处理器的connect()方法进一步处理,以通用事件处理器(evdev)为例,最终调用evdev_connect():
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
......
int minor;
int dev_no;
// 获取次设备号
minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);
if (minor < 0) {
error = minor;
pr_err("failed to reserve new minor: %d\n", error);
return error;
}
// 设置设备节点名字,根据注册顺序依次为event0、event1......
dev_no = minor;
/* Normalize device number if it falls into legacy range */
if (dev_no < EVDEV_MINOR_BASE + EVDEV_MINORS)
dev_no -= EVDEV_MINOR_BASE;
dev_set_name(&evdev->dev, "event%d", dev_no);
// 以主、次设备号合成设备号
evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);
// 关联input设备类
evdev->dev.class = &input_class;
// 继承input_dev父类
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev);
// 字符设备初始化及注册
cdev_init(&evdev->cdev, &evdev_fops);
evdev->cdev.kobj.parent = &evdev->dev.kobj;
error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);
if (error)
goto err_unregister_handle;
error = device_add(&evdev->dev);
if (error)
goto err_cleanup_evdev;
return 0;
}
在上述代码中可以看到,输入设备调用input_register_device()函数注册时,如果与通用事件处理器(evdev)成功配对,最后就会生成字符设备节点(/dev/input/eventN),这就是用户空间获取内核输入事件的接口。
输入事件的传递以input_event为基本单位:
struct input_event {
struct timeval time; // 时间戳
__u16 type; // 事件总类型
__u16 code; // 事件子类型
__s32 value; // 事件值
};
在输入设备驱动(input_dev)中,一般通过轮询或中断方式获取输入事件的原始值(raw value),经过处理后再使用input_event()函数上报;核心层将事件数据(type、code、value)打包、分发至事件处理器;事件处理器给输入事件加上时间戳,最后传递至上层用户空间。
整个上报流程的关键点在于事件处理器与用户空间的交互,以通用事件处理器(evdev)为例,其与用户程序组成了一对多的C/S2通信模型:
通用事件处理器(evdev)代表服务器,每一个打开设备节点(/dev/input/eventN)的用户程序则代表一个连接的客户端。
在evdev.c中有着对应描述的结构体:
// 服务器
struct evdev {
int open;
struct input_handle handle;
wait_queue_head_t wait;
struct evdev_client __rcu *grab;
struct list_head client_list; // 客户端连接列表
spinlock_t client_lock;
struct mutex mutex;
struct device dev;
struct cdev cdev;
bool exist;
};
// 客户端
struct evdev_client {
unsigned int head;
unsigned int tail;
unsigned int packet_head;
spinlock_t buffer_lock;
struct wake_lock wake_lock;
bool use_wake_lock;
char name[28];
struct fasync_struct *fasync;
struct evdev *evdev;
struct list_head node;
int clkid;
unsigned int bufsize;
struct input_event buffer[]; // 输入事件环形缓冲区
};
设备驱动上报事件并不是直接传递给用户程序,在通用事件处理器(evdev)中,事件被缓存在缓冲区中,当缓冲区不为空时,用户程序主动读取缓冲区获取事件。
这个缓冲区就是在evdev_client中定义的环形缓冲区,也就是说每个打开设备节点的用户程序,在建立客户端连接的同时,也构建了自己的缓冲区用以获取事件,不同的客户端之间互不干涉。
关于缓冲区的详细分析请参考:input子系统事件处理层(evdev)的环形缓冲区
通用事件处理器(evdev)的id_table设置为匹配所有输入设备 ↩︎
Client/Server,客户端与服务器架构 ↩︎