Linux输入设备总类繁杂,常见的包括有按键、键盘、触摸屏、鼠标、摇杆等等,他们本身就是字符设备,
而linux内核将这些设备的共同性抽象出来,简化驱动开发建立了一个input子系统。 驱动层和硬件相关,
直接捕捉和获取硬件设备的数据信息等(包括触摸屏被按下、按下位置、鼠标移动、键盘按下等等),然
后将数据信息报告到核心层。核心层负责连接驱动层和事件处理层,设备驱动(device driver)和处理程序
(handler)的注册需要通过核心层来完成,核心层接收来自驱动层的数据信息,并将数据信息选择对应的
handler去处理,最终handler将数据复制到用户空间。
1.先了解三个定义在/linux/input.h下重要的结构体input_dev、input_handler、input_handle
file:kernel-3.18/include/linux/input.h
struct input_dev {
const char *name; //input 设备名称
...
struct input_id id; //与input_handler匹配用的id
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 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;
...
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; //当前占有该设备的handle
struct device dev;
struct list_head h_list; //该链表头用于链接该设备所关联的input_handle
struct list_head node; //该链表头用于将设备链接到input_dev_list
...
};
struct input_handler {
...
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 (*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);
const struct input_device_id *id_table; //与input_dev匹配用的id
struct list_head h_list; //用于链接和该handler相关的handle
struct list_head node; //用于将该handler链入input_handler_list
...
};
struct input_handle {
int open;
const char *name;
struct input_dev *dev; //指向所属的input_dev
struct input_handler *handler; //指向所属的input_handler
struct list_head d_node; //用于链入所指向的input_dev的handle链表
struct list_head h_node; //用于链入所指向的input_handler的handle链表
};
三个结构体可以这样理解,input_dev可以作为输入设备的描述,input_handler可作为事件的处理函数(类似如中断处理)
,input_handle作为 input_dev和input_handler挂接的桥梁。因为一个device可以对应多个handler,而一个handler也可
处理多个input_device。就如一个触摸屏设备可以对应event handler也可以对应tseve handler,所以input_handle必不可
少。
2. input_dev和input_handler的匹配
file:kernel-3.18/drivers/input/input.c
int input_register_device(struct input_dev *dev)
{
struct input_devres *devres = NULL;
struct input_handler *handler;
/* Every input device generates EV_SYN/SYN_REPORT events. */
__set_bit(EV_SYN, dev->evbit); //设置支持同步事件,input设备全部默认支持同步事件
error = device_add(&dev->dev);
...
list_add_tail(&dev->node, &input_dev_list); //将设备添加到input_dev_list中
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
input_wakeup_procfs_readers();
...
}
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
...
id = input_match_device(handler, dev);
error = handler->connect(handler, dev, id);
...
}
static const struct input_device_id *input_match_device(struct input_handler *handler,
struct input_dev *dev)
{
const struct input_device_id *id;
for (id = handler->id_table; id->flags || id->driver_info; id++) {
if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
if (id->bustype != dev->id.bustype)
continue;
if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
if (id->vendor != dev->id.vendor)
continue;
if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
if (id->product != dev->id.product)
continue;
if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
if (id->version != dev->id.version)
continue;
if (!bitmap_subset(id->evbit, dev->evbit, EV_MAX))
continue;
if (!bitmap_subset(id->keybit, dev->keybit, KEY_MAX))
continue;
if (!bitmap_subset(id->relbit, dev->relbit, REL_MAX))
continue;
if (!bitmap_subset(id->absbit, dev->absbit, ABS_MAX))
continue;
if (!bitmap_subset(id->mscbit, dev->mscbit, MSC_MAX))
continue;
if (!bitmap_subset(id->ledbit, dev->ledbit, LED_MAX))
continue;
if (!bitmap_subset(id->sndbit, dev->sndbit, SND_MAX))
continue;
if (!bitmap_subset(id->ffbit, dev->ffbit, FF_MAX))
continue;
if (!bitmap_subset(id->swbit, dev->swbit, SW_MAX))
continue;
//将设备id与handler的id进行匹配,成功直接返回ID
if (!handler->match || handler->match(handler, dev))
return id;
}
return NULL;
}
file:kernel-3.18/drivers/input/evdev.c
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,
};
从上面input_handler的初始化可以看出handler->match为null,所以上面的input_match_device如果前面的条件
都满足的话直接返回ID。接下来在观察handler->connect即evdev_connect函数:
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
struct evdev *evdev; //定义一个evdev结构体指针
minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);
INIT_LIST_HEAD(&evdev->client_list);
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); //设置设备名为eventxx
evdev->handle.dev = input_get_device(dev);
evdev->handle.name = dev_name(&evdev->dev);
//这步最关键将handler赋值为evdev->handle.handler,完成input_dev和hander的挂接
evdev->handle.handler = handler;
evdev->handle.private = evdev;
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); //绑定file操作函数
evdev->cdev.kobj.parent = &evdev->dev.kobj;
error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);
error = device_add(&evdev->dev);//注册一个设备到内核
...
return 0;
}
3.上报键值的实现
在input子系统中上报键值一般使用input_report_xxx接口,先观察一个常用的input_report_key
file:kernel-3.18/drivers/input/input.h
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_KEY, code, !!value);
}
file:kernel-3.18/drivers/input/input.c
void input_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
input_handle_event(dev, type, code, value);
}
static void input_handle_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
int disposition;
disposition = input_get_disposition(dev, type, code, &value);
if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
dev->event(dev, type, code, value);
if (!dev->vals)
return;
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;
}
if (disposition & INPUT_FLUSH) {
if (dev->num_vals >= 2)
input_pass_values(dev, dev->vals, dev->num_vals);
dev->num_vals = 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;
}
}
static void input_pass_values(struct input_dev *dev,
struct input_value *vals, unsigned int count)
{
struct input_handle *handle;
struct input_value *v;
if (!count)
return;
rcu_read_lock();
handle = rcu_dereference(dev->grab);
if (handle) {
count = input_to_handler(handle, vals, count);
} else {
list_for_each_entry_rcu(handle, &dev->h_list, d_node)
if (handle->open)
count = input_to_handler(handle, vals, count);
}
rcu_read_unlock();
add_input_randomness(vals->type, vals->code, vals->value);
/* trigger auto repeat for key events */
for (v = vals; v != vals + count; v++) {
if (v->type == EV_KEY && v->value != 2) {
if (v->value)
input_start_autorepeat(dev, v->code);
else
input_stop_autorepeat(dev);
}
}
}
static unsigned int input_to_handler(struct input_handle *handle,
struct input_value *vals, unsigned int count)
{
struct input_handler *handler = handle->handler;
struct input_value *end = vals;
struct input_value *v;
for (v = vals; v != vals + count; v++) {
if (handler->filter &&
handler->filter(handle, v->type, v->code, v->value))
continue;
if (end != v)
*end = *v;
end++;
}
count = end - vals;
if (!count)
return 0;
if (handler->events)
handler->events(handle, vals, count);
else if (handler->event)
for (v = vals; v != end; v++)
handler->event(handle, v->type, v->code, v->value);
return count;
}
file:kernel-3.18/drivers/input/evdev.c
static void evdev_event(struct input_handle *handle,
unsigned int type, unsigned int code, int value)
{
struct input_value vals[] = { { type, code, value } };
evdev_events(handle, vals, 1);
}
static void evdev_pass_values(struct evdev_client *client,
const struct input_value *vals, unsigned int count,
ktime_t mono, ktime_t real)
{
struct evdev *evdev = client->evdev;
const struct input_value *v;
struct input_event event;
bool wakeup = false;
if (client->revoked)
return;
event.time = ktime_to_timeval(client->clkid == CLOCK_MONOTONIC ?
mono : real);
/* Interrupts are disabled, just acquire the lock. */
spin_lock(&client->buffer_lock);
for (v = vals; v != vals + count; v++) {
event.type = v->type;
event.code = v->code;
event.value = v->value;
__pass_event(client, &event);
if (v->type == EV_SYN && v->code == SYN_REPORT)
wakeup = true;
}
spin_unlock(&client->buffer_lock);
if (wakeup)
wake_up_interruptible(&evdev->wait);
}
static void __pass_event(struct evdev_client *client,
const struct input_event *event)
{
client->buffer[client->head++] = *event;
client->head &= client->bufsize - 1;
if (unlikely(client->head == client->tail)) {
/*
* This effectively "drops" all unconsumed events, leaving
* EV_SYN/SYN_DROPPED plus the newest event in the queue.
*/
client->tail = (client->head - 2) & (client->bufsize - 1);
//将需要上报的数据存放在buffer中
client->buffer[client->tail].time = event->time;
client->buffer[client->tail].type = EV_SYN;
client->buffer[client->tail].code = SYN_DROPPED;
client->buffer[client->tail].value = 0;
client->packet_head = client->tail;
if (client->use_wake_lock)
wake_unlock(&client->wake_lock);
}
if (event->type == EV_SYN && event->code == SYN_REPORT) {
client->packet_head = client->head;
if (client->use_wake_lock)
wake_lock(&client->wake_lock);
kill_fasync(&client->fasync, SIGIO, POLL_IN);
}
}
4.读取键值的实现
当上层进行read后,调用该设备的read函数:
static ssize_t evdev_read(struct file *file, char __user *buffer,
size_t count, loff_t *ppos)
{
struct evdev_client *client = file->private_data;
struct evdev *evdev = client->evdev;
struct input_event event;
size_t read = 0;
int error;
if (count != 0 && count < input_event_size())
return -EINVAL;
for (;;) {
if (!evdev->exist || client->revoked)
return -ENODEV;
if (client->packet_head == client->tail &&
(file->f_flags & O_NONBLOCK))
return -EAGAIN;
/*
* count == 0 is special - no IO is done but we check
* for error conditions (see above).
*/
if (count == 0)
break;
while (read + input_event_size() <= count &&
evdev_fetch_next_event(client, &event)) {
if (input_event_to_user(buffer + read, &event))
return -EFAULT;
read += input_event_size();
}
if (read)
break;
if (!(file->f_flags & O_NONBLOCK)) {
error = wait_event_interruptible(evdev->wait,
client->packet_head != client->tail ||
!evdev->exist || client->revoked);
if (error)
return error;
}
}
return read;
}
file:kernel-3.18/drivers/input/input-compat.c
int input_event_to_user(char __user *buffer,
const struct input_event *event)
{
if (INPUT_COMPAT_TEST && !COMPAT_USE_64BIT_TIME) {
struct input_event_compat compat_event;
compat_event.time.tv_sec = event->time.tv_sec;
compat_event.time.tv_usec = event->time.tv_usec;
compat_event.type = event->type;
compat_event.code = event->code;
compat_event.value = event->value;
if (copy_to_user(buffer, &compat_event,
sizeof(struct input_event_compat)))
return -EFAULT;
} else {
//最终使用copy_to_user将数据上传到用户空间
if (copy_to_user(buffer, event, sizeof(struct input_event)))
return -EFAULT;
}
return 0;
}
5.小结
通过上面4点分析,我们大致可以看出,输入子系统的本质就是利用input_dev、input_handler、input_handle
三个数据结构进行数据的转换和传递,其中input_dev用于数据的描述,input_handler用户事件的处理,input_handle
用于沟通input_dev、input_handler,当input_dev与input_handler挂接上了后,会创建相应的字符设备,并绑定相应的
读写方法,当底层有数据后,上报的数据存放在buffer数组中,当用户空间调用open 打开/dev/input/eventxx 然后调用
read 读取键值时,底层调用evdev_read并最终利用copy_to_user将内核空间数据传递到用户空间。