简介
Linux系统支持的输入设备繁多,例如键盘、鼠标、触摸屏、手柄或者是一些输入设备像体感输入等等,Linux系统是如何管理如此之多的不同类型、不同原理、不同的输入信息的输入设备的呢?其实就是通过input输入子系统这套软件体系来完成的。
一、input整体框架分析
input子系统中有三个重要的对象input_device、input_handler、input.c 核心层。
- input_device:需要用户自己实现具体输入设备的代码。
- input_handler:由linux抽象出通用的几个输入事件代码。内核提供。
- input.c 核心层 :搭建输入子系统,并提供输入设备与输入驱动需要的注册API。内核提供。
input.c 是核心层,input核心层维护一个input_device链表(input_dev_list)和一个input_handler链表(input_handler_list)。当注册一个input_device时,会遍历input_hander链表(input_handler_list),查看是否匹配,匹配则调用 input_handler 的 .connect ,就会创建一个input_hanle连接器,然后分别存放到对应的input_devide和input_handler中的input_handle 链表中,从而建立连接。注册input_handler的时候也是同理。
此时,input_device调用input_event()上报事件,就会调用handler->event,此时就完成一个事件上报。内核就会根据此事件作出响应。
应用程序处理过程:
input.c核心层 register_chrdev() 注册 字符设备input_fops,而 input_fops 只有 .open = input_open_file,input_open_file起中转作用。input_open_file()会根据次设备号获取 input_table[] 中的 input_handler,并调用 ->fops->open。
当应用层操作input设备节点时,会先到达input核心层,input核心层根据input设备的次设备号从它维护的input_handler链表中找到相应的input_handler,调用其提供的fops。
当硬件设备产生中断的时候,input_device会向input核心层上报事件,input核心层再通知到input_handler,然后再将结果返回给用户空间。
二、input源码分析
1、input.c 核心层,路径:driver/input/input.c
input_init()函数,其中class_register和class_create类似,只是class_create注册的是一个动态创建的cls,而class_register注册的是全局的一个cls对象,注册完成后可以在/sys/class/input可看到注册信息。input_proc_init主要是创建了devices和handlers,创建完成后可在/proc/bus/input下查看已经注册devices和handlers,呈现给用户查看。
subsys_initcall(input_init); //initcall 机制,内核启动时调用
static int __init input_init(void)
{
int err;
/* 注册类,类似于class_create() */
err = class_register(&input_class);
if (err) {
pr_err("unable to register input_dev class\n");
return err;
}
/* 在/proc创建 bus/input/devices handlers */
err = input_proc_init();
if (err)
goto fail1;
/* 申请设备号类似register_chrdev,不同内核版本有差异,3.0以前是
register_chrdev */
err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
INPUT_MAX_CHAR_DEVICES, "input");
if (err) {
pr_err("unable to register char major %d", INPUT_MAJOR);
goto fail2;
}
return 0;
fail2: input_proc_exit();
fail1: class_unregister(&input_class);
return err;
}
class_register、class_create(路径: include\linux\device.h):
#define class_register(class) \
({ \
static struct lock_class_key __key; \
__class_register(class, &__key); \
})
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
struct class *__class_create(struct module *owner, const char *name,
struct lock_class_key *key)
{
struct class *cls;
int retval;
cls = kzalloc(sizeof(*cls), GFP_KERNEL);
if (!cls) {
retval = -ENOMEM;
goto error;
}
cls->name = name;
cls->owner = owner;
cls->class_release = class_create_release;
retval = __class_register(cls, key);//最终调用 __class_register
if (retval)
goto error;
return cls;
error:
kfree(cls);
return ERR_PTR(retval);
}
input_proc_init(路径: drivers\input\input.c):
static int __init input_proc_init(void)
{
struct proc_dir_entry *entry;
proc_bus_input_dir = proc_mkdir("bus/input", NULL);
if (!proc_bus_input_dir)
return -ENOMEM;
/* 在/proc创建 bus/input/devices */
entry = proc_create("devices", 0, proc_bus_input_dir,
&input_devices_fileops);
if (!entry)
goto fail1;
/* 在/proc创建 bus/input/handlers */
entry = proc_create("handlers", 0, proc_bus_input_dir,
&input_handlers_fileops);
if (!entry)
goto fail2;
return 0;
fail2: remove_proc_entry("devices", proc_bus_input_dir);
fail1: remove_proc_entry("bus/input", NULL);
return -ENOMEM;
}
这就是核心层主要做的内容,这个驱动是编译在内核中去了的。
2.input_handler层
内核中有几个input_handler的实现(evdev.c、keyboard.c、mousedev.c等)
函数调用过程:
input_register_handler()
input_table[handler->minor >> 5] = handler; //input_handler放入 input_table[]
list_add_tail() // input_handler 放入链表 input_handler_list
list_for_each_entry() // 对input_dev_list中每个input_dev调用input_attach_handler
input_attach_handler()
input_match_device() //比较input_handler的id_table 和 input_dev_list 中的dev
handler->connect() //匹配,则调用input_handler->connect
connect过程
evdev.c的 .connect如下:
evdev_connect()
evdev->handle.dev = dev; //handle.dev指向input_device
evdev->handle.handler = handler; //handle.handler指向input_handler
sprintf(evdev->name, "event%d", minor); //生成 /dev/event1
input_register_handle() //input_handler和 input_device 的 .h_list 又都指向 input_handle,从而建立连接
以evdev.c为例:
static struct input_handler evdev_handler = {
.event = evdev_event, //用于上报事件
.events = evdev_events,
.connect = evdev_connect, //用于和input_device建立连接
.disconnect = evdev_disconnect,
.legacy_minors = true,
.minor = EVDEV_MINOR_BASE, //次设备号基址
.name = "evdev",
.id_table = evdev_ids, //用于与input_device匹配
};
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler); //注册一个input_handler
}
static void __exit evdev_exit(void)
{
input_unregister_handler(&evdev_handler);
}
module_init(evdev_init);
module_exit(evdev_exit);
evdev_init() 里面主要注册了一个input_handler,这个handler是个结构体,包括以下成员其中例如 EVDEV_MINOR_BASE,表示是次设备号基准值,用"ls /dev/input/event* -l"中可以查看到次设备号都是从基准值递增的。而input_handler中主要是一些函数接口包括connect,event等。
input_register_handler() 中遍历input_dev_list链表中的dev,调用 input_attach_handler() 匹配成功后会调用 evdev_handler 的 .connect 方法完成设备节点创建,如 /dev/input/event0..1..2。
int input_register_handler(struct input_handler *handler)
{
struct input_dev *dev;
int error;
/* 上锁,保护临界资源,保护全局链表 */
error = mutex_lock_interruptible(&input_mutex);
if (error)
return error;
INIT_LIST_HEAD(&handler->h_list);//初始化内部h_list
/* 将当前的 handler->node 注册到 input_handler_list 中 */
list_add_tail(&handler->node, &input_handler_list);
/* 遍历链表 input_dev_list 中的节点 dev 去和 handler 做关联 */
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler);
/* 将当前的 handler加入到 /proc/bus/input/handlers 中 */
input_wakeup_procfs_readers();
/* 释放锁 */
mutex_unlock(&input_mutex);
return 0;
}
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
const struct input_device_id *id;
int error;
/* 比较匹配 */
id = input_match_device(handler, dev);
if (!id)
return -ENODEV;
/* 匹配则调用 handler 的 .connect */
error = handler->connect(handler, dev, id);
if (error && error != -ENODEV)
pr_err("failed to attach handler %s to device %s, error: %d\n",
handler->name, kobject_name(&dev->dev.kobj), error);
return error;
}
3.input_device层(我们自己实现的驱动层)
函数调用过程:
input_register_device()
list_add_tail() //将input_device 放入链表 input_dev_list
list_for_each_entry() //轮询 input_handler_list 链表
input_attach_handler() // 对input_handler_list中每个input_handler调用input_attach_handler
input_match_device() //比较input_handler_list 中input_handler的 id_table 和 dev
handler->connect() //匹配,则调用input_handler->connect
和 input_register_handler() 类似,input_register_device() 构建一个 input_dev,然后注册到input_dev_list中,同时遍历handler的链表:input_handler_list,然后进行匹配,匹配成功都会调用handler中的.connect。
4. input_handler->connect 分析
.connect 创建一个input_hanle连接器,然后分别存放到对应的input_devide和input_handler中的input_handle 链表中,从而建立连接。
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
struct evdev *evdev;
int minor;
int dev_no;
int error;
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;
}
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
if (!evdev) {
error = -ENOMEM;
goto err_free_minor;
}
INIT_LIST_HEAD(&evdev->client_list);
spin_lock_init(&evdev->client_lock);
mutex_init(&evdev->mutex);
init_waitqueue_head(&evdev->wait);
evdev->exist = true;
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;
/* 设置evdev中device的名字,它也将出现在/class/input/下,但是他和
input_dev下面的device是有区别的,evdev配对以后的虚拟设备结构,
没有对应的硬件,但是可以通过它找到相应的硬件 */
dev_set_name(&evdev->dev, "event%d", dev_no);
/* 设置handle */
evdev->handle.dev = input_get_device(dev);
evdev->handle.name = dev_name(&evdev->dev);
evdev->handle.handler = handler;
evdev->handle.private = evdev;
/* minor不是真正的次设备号,还要加上EVDEV_MINOR_BASE */
evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);
evdev->dev.class = &input_class;
/* 配对生成新的device,父设备是与他相关联的input_dev */
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev);
/* 注册handle结构体 */
error = input_register_handle(&evdev->handle);
if (error)
goto err_free_evdev;
/* 将 file_operation 注册到内核 */
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);//创建设备节点,把 evdev 添加到内核
if (error)
goto err_cleanup_evdev;
return 0;
err_cleanup_evdev:
evdev_cleanup(evdev);
err_unregister_handle:
input_unregister_handle(&evdev->handle);
err_free_evdev:
put_device(&evdev->dev);
err_free_minor:
input_free_minor(minor);
return error;
}
三、应用程序使用
应用层调用read函数时,就会调用到 evdev_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;
}
那么谁来唤醒呢?
我们写的 input_device 驱动调用了 input_event()函数,实际是 handler->event,最终会调用到evdev_pass_values(),而此函数调用了 wake_up_interruptible() 唤醒。
static void evdev_pass_values(struct evdev_client *client,
const struct input_value *vals, unsigned int count,
ktime_t *ev_time)
{
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(ev_time[client->clk_type]);
/* 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);
}
此时,整个流程已介绍完毕。