0%

Linux驱动之Input子系统

概述

按键输入、键盘、鼠标、触摸屏等等这些都属于输入设备,按键和键盘就是代表按键信息,鼠标和触摸屏代表坐标信息,因此在应用层的处理就不同。为此 input 子系统分为 input 驱动层、 input 核心层、 input 事件处理层,最终给用户空间提供可访问的设备节点, input 子系统框架如图 所示:

  • 驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容
  • 核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理
  • 事件层:主要和用户空间进行交互

数据结构

Input子系统实现的数据结构如下:原图

  • input_dev
    input_dev结构体用于描述一个input设备,例如按键、鼠标、键盘等,我们编写设备驱动主要就是构造这个结构体。该结构体包含设备类型,消息类型,事件code等信息。该数据结构在内核里会挂载到一个全局链表input_dev_list,所有注册的input设备都会挂载到该链表中

  • input_handler
    input_handler用于表示一个input事件,用于上报input事件,系统中所有注册的input_handler也会挂载到一个全局链表input_handler_list中。input_handler可以类比设备驱动模型中driver方法,用于驱动device,这里input_handler用于处理input_dev设备产生的事件。input_dev和input_handler之间也是通过一种匹配规则来进行匹配建立联系,当input_dev注册的时候会遍历input_handler_list链表,通过id_table或input_handler的match方法进行匹配,匹配成功后两者之间会建立联系。同样对于input_handler的注册也是一样,当一个input_handler注册时会遍历input_dev_list链表,当两个结构体满足匹配关系时则建立联系

  • input_handle
    在上面的介绍中当input_handler和input_dev匹配成功后会建立联系,这个联系就是通过该结构体来建立的。当input_dev和input_handler匹配成功后会创建一个evdev结构体,evdev结构体下有一个input_handle结构体,input_handle结构体会指向匹配成功的input_dev和input_handler,而且将自己挂载到input_dev和input_handler结构体中

  • evdev_client
    evdev_client结构体表示一个input客户,也就是app用户会使用input_dev,那么这个app用户就用evdev_client结构体来描述,可以看到,该结构体挂载在evdev结构体下,且是以链表的形式组织,也就说明一个input设备可以被多个client打开使用

  • evdev
    通过对input_handler和evdev_client的介绍其实也就已经将evdev介绍完了,evdev_client使用设备,那么需要知道使用的是哪个设备input_dev以及如何驱动这个设备input_handler,这都是通过input_handler结构体完成的。evdev是将input设备,input设备的驱动方法和用户client联系起来的一个结构体

通过对input结构体的介绍,其实我们就已经大致了解了驱动的整体框架。其实跟设备模型中device和device_driver之间的关系很像,device和driver之间有一个匹配规则,当匹配成功后,两者之间建立联系,完成驱动设备的功能。但是为什么这里不直接使用设备驱动模型呢,不理解。

实现

input驱动的实现无非就是input_dev、input_handler的注册等流程,下面一个个看下具体相关流程吧:

input_dev注册流程

input_register_device函数用于注册一个input_dev设备:

1
int input_register_device(struct input_dev *dev)

该函数主要完成以下工作:

1、为input_dev创建struct device类型的变量,并设置其所属的class为input_class,并调用device_add完成device类型变量的注册,至此完成strcut device类型变量注册至input_class中,且完成了input_dev对应的sysfs属性文件的创建 
2、将该input_device注册至input_dev_list      
3、遍历input_handler_list链表,针对input_handler_list上每一个input_handler,均与注册的input_device进行匹配检测,若匹配检测成功,则调用  input_handler->connect接口,进行input_handler与input_device的绑定(通过调用input_register_handle实现,也可以理解为在input_handler、input_device上 完成input_handler、input_device的事件消息的订阅操作)。因input_handler和input_device可实现多对多的绑定,因此,会遍历input_handler_list链表上所有已注册的input_handler,而不是检测到一个input_handler的匹配即返回   

input_handler的注册流程

input_handler的注册函数是input_register_handler:

1
int input_register_handler(struct input_handler *handler)

该函数主要完成以下工作:

1、将该input_device注册至input_handler_list      
2、遍历input_dev_list链表,针对input_dev_list上每一个input_dev,均与注册的input_device进行匹配检测,若匹配检测成功,则调用input_handler->connect接口,进行input_handler与input_device的绑定(通过调用input_register_handle实现,也可以理解为在input_handler、input_device上完成input_handler、input_device的事件消息的订阅操作)。因input_handler和input_device可实现多对多的绑定,因此,会遍历input_dev_list链表上所有已注册的input_dev,而不是检测到一个input_dev的匹配即返回

input_handle的注册流程

input_handle的注册函数是input_register_handle:

1
int input_register_handle(struct input_handle *handle)

该函数主要完成以下工作:

1、 完成input_handle与input_handler的关联
2、完成input_handle与input_dev的关联

一个完整的事件上报流程

应用打开并读设备

当应用打开一个input设备时会通过vfs调用到驱动程序的file_operations,对于input设备就是evdev.c下的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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,
.llseek = no_llseek,
};

这个就是input_dev注册的时候会向系统注册的字符设备,该字符设备的注册时机是什么时候呢,先来看下input_handle的初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);
}

static void __exit evdev_exit(void)
{
input_unregister_handler(&evdev_handler);
}

evdev_handler里面的成员connect的指针指向evdev_connect,当input_dev和input_handler匹配成功后就会调用connect,函数,而evdev_connect里面会调用

1
2
3
4
5
...
cdev_init(&evdev->cdev, &evdev_fops);

cdev_device_add(&evdev->cdev, &evdev->dev);
...

evdev_fops就是上面那个file_operations。然后应用调用open就会调用驱动的evdev_open函数,应用调用read就会调用驱动的evdev_read函数,下面说明下open和read函数主要的工作: evdev_open的工作:

1、 创建并分配一个evdev_client结构体   
2、 将evdev_client放入evdev的链表中   

evdev_read的工作:

如果没有数据等待休眠,调用wait_event_interruptible函数,将当前工作挂载在evdev_client的wait等待队列中

事件产生上报数据

一般采用中断方式处理外部事件,当按键或其他输入事件发生时,产生中断,执行中断服务程序,中断服务程序一般主要就是干两件事,读取数据和上报数据,对于读取数据没什么好说的,下面看下上报数据流程:上报数据通过input core层提供的input_event函数完成,函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
void input_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
unsigned long flags;

if (is_event_supported(type, dev->evbit, EV_MAX)) {

spin_lock_irqsave(&dev->event_lock, flags);
input_handle_event(dev, type, code, value);
spin_unlock_irqrestore(&dev->event_lock, flags);
}
}

函数调用流程如下:

1
2
3
4
input_event()
-->input_handle_event()
-->input_pass_values()
-->input_to_handler()

input_to_handler函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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;

/* 1、 使用filter过滤事件 */
if (handler->filter) {
for (v = vals; v != vals + count; v++) {
if (handler->filter(handle, v->type, v->code, v->value))
continue;
if (end != v)
*end = *v;
end++;
}
count = end - vals;
}

if (!count)
return 0;

/* 2、调用handler的events处理多个事件或者调用event一个一个处理事件 */
if (handler->events)
handler->events(handle, vals, count);
else if (handler->event)
for (v = vals; v != vals + count; v++)
handler->event(handle, v->type, v->code, v->value);

return count;
}

以evdev为例,在上面的evdev_handler中提供了events函数evdev_handler,该函数调用流程如下:

1
2
3
4
5
evdev_events()
-->evdev_pass_values()
-->__pass_event()
-->wake_up_interruptible_poll(&client->wait,
EPOLLIN | EPOLLOUT | EPOLLRDNORM | EPOLLWRNORM);

__pass_event函数将读取的事件传递给client结构体,wake_up_interruptible_poll将应用程序唤醒(前面提到读取数据的时候如果没有数据就休眠)。

总结

1、应用open一个input设备,通过vfs调用到驱动字符设备的open函数,该函数创建构造一个evdev_client结构体并将该结构体放入打开的input设备的evdev结构体链表中
2、应用read读取一个input设备,通过vfs调用到驱动的字符设备read函数,如果没有数据就休眠
3、事件发生产生中断,在中断函数里读取数据并上报事件,上报事件流程是通过input_dev结构体找到驱动该结构体的input_handler结构体,然后调用input_handler结构体的events函数,在events函数里,主要干两件事,一是将外部事件传递给evdev_client结构体,二是将应用程序唤醒

参考文献

https://ld246.com/article/1598093237235
https://www.cnblogs.com/deng-tao/p/6094049.html
https://blog.csdn.net/lickylin/article/details/106449104?spm=1001.2014.3001.5501
《韦东山老师相关课程和文档》