一、input子系统
input子系统处理Linux下的输入事件。
驱动层:输入设备的驱动程序,负责检测和接收输入设备的输入事件,将输入事件上报给核心层;
核心层:提供设备驱动、事件 handler 注册和操作的接口;接收驱动层的输入事件并上报给事件处理层;
事件处理层:通过提供 sysfs 接口等方式和用户空间交互,例如用户空间打开特定设备,当有输入数据时就会上传给用户空间。
input子系统框架结构图(总结来自这里):
input driver 接收到硬件的输入事件 ==> 发送到input core,input core 根据事件类型 ==> 将事件交给对应的input handler处理 ==> input handler 上报用户空间,用户空间收收到事件后进行对应的处理。
二、关键数据结构和api
2.1 数据结构
2.1.1 input_dev
input_dev 描述输入设备,结构体中的多个 bitmap 描述了输入设备的类型和支持的输入事件。这些事件类型相关的宏定义在 input-event-codes.h 头文件中。
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)]; // 设备支持的事件类型的bitmap
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)]; // led
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;
struct input_dev_poller *poller;
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];
};
2.1.2 input_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;
const struct input_device_id *id_table;
struct list_head h_list;
struct list_head node;
};
以 evdev handler 为例,
connect 接口,通过 input_register_handle 接口,实现将 input_dev 和 input_handler 绑定,并创建对应输入设备的字符设备;
event/events 接口,将设备的输入拷贝到 buffer 中,当用户空间调用字符设备的 read 接口时,就可以从 buffer 中读取输入信息;
2.1.3 input_event
handler 上报事件到用户层的时候,以 input_event 格式进行上报。
struct input_event {
struct timeval time; // 事件发生事件
__u16 type; // 事件类型,例如 EV_KEY 按键类型
__u16 code; // 事件编码,例如 KEY_0 按键
__s32 value; // 事件值
};
2.1.4 input_handle
input_handle 实现将 input_device 和 input_handler 绑定的功能,上面已经介绍到,evdev handler 的 connect 接口中,会调用 input_register_handle 接口,实现将 input_dev 和 input_handler 绑定。
struct input_handle {
void *private;
int open; // 当前handle是否open
const char *name;
struct input_dev *dev;
struct input_handler *handler;
struct list_head d_node;
struct list_head h_node;
};
open 记录了当前 handle 是否被 open,以 evdev 为例,当用户空间 open 字符设备的时候,会调用到input_open_device 接口,接口内部实现 input_handle->open++。
2.2 api接口
2.2.1 input_device 相关接口
input核心层提供了如下一系列input device相关的接口,事件input device的注册、事件的上报等功能:
// 申请
struct input_dev *input_allocate_device(void);
struct input_dev *devm_input_allocate_device(struct device *dev);
// 设置支持的事件类型
void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code);
// 注册、注销
int input_register_device(struct input_dev *dev);
void input_unregister_device(struct input_dev *dev);
// 释放
void input_free_device(struct input_dev *dev);
// 事件上报
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
void input_inject_event(struct input_handle *handle, unsigned int type, unsigned int code, int value);
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value);
static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value);
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
static inline void input_report_ff_status(struct input_dev *dev, unsigned int code, int value);
static inline void input_report_switch(struct input_dev *dev, unsigned int code, int value);
static inline void input_sync(struct input_dev *dev); // 同步通知事件发送完成
static inline void input_mt_sync(struct input_dev *dev);
input_device 注册流程
注册接口中主要做了以下动作:
- 检查 bitmap 等参数设置是否正确;
- 将 device 添加到 input_device_list 链表中;
- 对于 input_handler_list 中的每一个 handler,调用 input_attach_handler 接口尝试将 device 和 handler 绑定,在接口内部会检查 device 和 handler 是否 match,match 的话则调用 handler 的 connect 接口完成绑定动作。
int input_register_device(struct input_dev *dev)
{
// 检查bitmap等参数、配置input_dev部分参数
/* Every input device generates EV_SYN/SYN_REPORT events. */
__set_bit(EV_SYN, dev->evbit);
/* KEY_RESERVED is not supposed to be transmitted to userspace. */
__clear_bit(KEY_RESERVED, dev->keybit);
/* Make sure that bitmasks not mentioned in dev->evbit are clean. */
input_cleanse_bitmasks(dev);
dev->max_vals = dev->hint_events_per_packet + 2;
dev->vals = kcalloc(dev->max_vals, sizeof(*dev->vals), GFP_KERNEL);
// device_add
error = device_add(&dev->dev);
// device 和 handler绑定
error = mutex_lock_interruptible(&input_mutex);
list_add_tail(&dev->node, &input_dev_list);
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
input_wakeup_procfs_readers();
mutex_unlock(&input_mutex);
if (dev->devres_managed) {
dev_dbg(dev->dev.parent, "%s: registering %s with devres.\n",
__func__, dev_name(&dev->dev));
devres_add(dev->dev.parent, devres);
}
return 0;
}
EXPORT_SYMBOL(input_register_device);
事件上报
input 子系统中封装了针对不同类型事件的上报接口,例如 input_report_key\input_report_abs 等,这些接口实际都是调用 input_event 接口完成事件上报,只不过接口参数中的 type 类型不同,以 input_report_key 为例:
input_report_key(struct input_dev *dev, unsigned int code, int value)
-> input_event(dev, EV_KEY, code, !!value);
-> input_handle_event(dev, type, code, value);
-> input_get_disposition(dev, type, code, &value); // 获取事件类型
-> input_pass_values(dev, dev->vals, dev->num_vals);
-> input_to_handler(handle, vals, count);
-> handler->events(handle, vals, count); // 通知handler处理事件
在 input_handle_event 接口中,会将事件缓存在 dev->vals 中,并记录事件数目到 dev-num_vals,当检测到 dev->num_vals >= dev->max_vals - 2 或者 input_sync 事件时,将所有缓存事件通知 handler 处理。
static void input_handle_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
// 获取事件类型
int disposition = input_get_disposition(dev, type, code, &value);
if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)
add_input_randomness(type, code, value);
// 如果 INPUT_PASS_TO_DEVICE并且device实现了event,则通知device
if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
dev->event(dev, type, code, value);
if (!dev->vals)
return;
// 记录要通知给handler的事件
if (disposition & INPUT_PASS_TO_HANDLERS) {
struct input_value *v;
if (disposition & INPUT_SLOT) {
v = &dev->vals[dev->num_vals++];
v->type = EV_ABS;
v->code = ABS_MT_SLOT;
v->value = dev->mt->slot;
}
v = &dev->vals[dev->num_vals++];
v->type = type;
v->code = code;
v->value = value;
}
// sync事件或者要超出缓存,则将缓存的vals flush到handler
if (disposition & INPUT_FLUSH) {
if (dev->num_vals >= 2)
input_pass_values(dev, dev->vals, dev->num_vals);
dev->num_vals = 0;
dev->timestamp[INPUT_CLK_MONO] = ktime_set(0, 0);
} else if (dev->num_vals >= dev->max_vals - 2) {
dev->vals[dev->num_vals++] = input_value_sync;
input_pass_values(dev, dev->vals, dev->num_vals);
dev->num_vals = 0;
}
}
2.2.2 input handle 相关接口
int input_register_handle(struct input_handle *handle);
void input_unregister_handle(struct input_handle *handle);
注册 handle
input_register_handle 接口实现注册一个input_handle,将 device 和 handler 绑定,例如在 evdev handler 的 connect 接口中,就调用了 input_register_handle 接口。
接口流程:
int input_register_handle(struct input_handle *handle)
{
// 将 handle->d_node 加入到 dev->h_list,实现遍历dev->h_list就能找到所有关联的input_handle,进而找到input_handler
if (handler->filter)
list_add_rcu(&handle->d_node, &dev->h_list);
else
list_add_tail_rcu(&handle->d_node, &dev->h_list);
// 将 handle->h_node 加入到 handler->h_list,实现遍历handler->h_list就能找到所有关联的input_handler,进而找到input_device
list_add_tail_rcu(&handle->h_node, &handler->h_list);
if (handler->start)
handler->start(handle);
return 0;
}
在 input_register_handle 接口中,会将 handle->d_node 加入到 dev->h_list,实现遍历dev->h_list就能找到所有关联的input_handle,进而找到input_handler。
实际上在 input_pass_values 中,如果未指定 input_device 的 input_handle, 就是通过遍历列表的方式,将事件通过所有关联的 input_handle 发送到 input_handler 中。
也就是说默认input_event的事件上报是一个广播行为:
handle = rcu_dereference(dev->grab);
if (handle) {
count = input_to_handler(handle, vals, count); // 指定handle
} else {
list_for_each_entry_rcu(handle, &dev->h_list, d_node) // 广播
if (handle->open) {
count = input_to_handler(handle, vals, count);
if (!count)
break;
}
}
指定 handle
在 input_grab_device 接口中,实现了 dev->grab 与 handle 的绑定:
int input_grab_device(struct input_handle *handle)
{
if (dev->grab) {
retval = -EBUSY;
goto out;
}
rcu_assign_pointer(dev->grab, handle);
}
以 evdev handler 为例,ioctl 中实现了 EVIOCGRAB,用于 input_device 指定 input_handle:
// ioctl接口中,调用evdev_grab或evdev_ungrab事件绑定和解绑:
case EVIOCGRAB:
if (p)
return evdev_grab(evdev, client);
else
return evdev_ungrab(evdev, client);
// evcev_grab中调用 input_grab_device 实现 dev->grab 与 handle 的绑定
static int evdev_grab(struct evdev *evdev, struct evdev_client *client)
{
int error;
if (evdev->grab)
return -EBUSY;
error = input_grab_device(&evdev->handle);
if (error)
return error;
rcu_assign_pointer(evdev->grab, client);
return 0;
}
2.2.3 input handler 相关接口
// 注册、注销
int input_register_handler(struct input_handler *handler);
void input_unregister_handler(struct input_handler *handler);
注册handler
input_register_handler 接口中,将 handler 添加到 input_handler_list 中,遍历 input_dev_list,执行input_attach_handler(dev, handler):
int input_register_handler(struct input_handler *handler)
{
INIT_LIST_HEAD(&handler->h_list);
list_add_tail(&handler->node, &input_handler_list);
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler);
return 0;
}
三、input handler
3.1 evdev handler
3.1.1 handler 注册
在evdev_init中调用 input_register_handler 实现 handler 的注册。
static struct input_handler evdev_handler = {
.event = evdev_event,
.events = evdev_events,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.legacy_minors = true,
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
.id_table = evdev_ids,
};
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}
3.1.2 evdev_connect
evdev_connect 接口在 input_attach_handler 中被调用,接口实现以下功能:
- 以 evdev_fops 为 file_operations 创建 cdev,设备名称为 event%d;
- 调用 input_register_handle 实现 input_device 与 input_handler 的绑定;
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);
INIT_LIST_HEAD(&evdev->client_list);
dev_no = minor;
dev_set_name(&evdev->dev, "event%d", dev_no);
evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev);
error = input_register_handle(&evdev->handle);
cdev_init(&evdev->cdev, &evdev_fops);
error = cdev_device_add(&evdev->cdev, &evdev->dev);
}
3.1.3 evdev_events
evdev_events 接口负责处理 input_device 上报的事件,并上报给用户层:
handler->events(handle, vals, count); // evdev_events,读时间
--> evdev_pass_values(client, vals, count, ev_time); // 组input_event
--> __pass_event(client, &event); // 将event存在evdev_client的buffer中
--> kill_fasync(&client->fasync, SIGIO, POLL_IN); // 异步信号通知用户层
--> wake_up_interruptible(&evdev->wait); // 唤醒等待队列
3.1.4 file_operations
evdev handler 的 file_operations 提供了 fasync\poll\read 等接口,供用户层读取 input event。
static const struct file_operations evdev_fops = {
.owner = THIS_MODULE,
.read = evdev_read,
.write = evdev_write,
.poll = evdev_poll,
.open = evdev_open,
.release = evdev_release,
.unlocked_ioctl = evdev_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = evdev_ioctl_compat,
#endif
.fasync = evdev_fasync,
.flush = evdev_flush,
.llseek = no_llseek,
};
evdev_fasync
接口实现异步通知处理函数,当有input_event事件时,在evdev_events接口中,最终会调用 kill_fasync
实现发送异步通知信号,用户层接收到状态变化后,可知晓有input_event事件需要处理。
evdev_read
接口为用户空间提供了读input_event事件的接口,实际是将evdev_events接口中缓存在buffer中的数据copy到用户空间。当缓存中没有数据是,调用wait_event_interruptible
等待 evdev_events 唤醒。
3.2 mousedev handler
TODO