目录
一,input子系统简介
1、Linux系统支持的输入设备繁多,例如键盘、鼠标、触摸屏、手柄或者是一些输入设备像体感输入等等,Linux系统是如何管理如此之多的不同类型、不同原理、不同的输入信息的输入设备的呢?其实就是通过input输入子系统这套软件体系来完成的。从整体上来说,input输入子系统分为3层:上层(输入事件驱动层)、中层(输入核心层)、下层(输入设备驱动层),如下图所示:
联系之前学过的驱动框架做对比,input输入子系统其实就是input输入设备的驱动框架,与之前的学过的驱动框架不同的是,input输入子系统分为3层:上、中、下,所以他的复杂度要高于之前讲的lcd、misc、fb等的驱动框架。
2、图中Drivers对应的就是下层设备驱动层,对应各种各样不同的输入设备,Input Core对应的就是中层核心层,Handlers对应的就是上层输入事件驱动层,最右边的代表的是用户空间。
(1)从图中可以看出,系统中可以注册多个输入设备,每个输入设备的可以是不同的,例如一台电脑上可以带有鼠标,键盘…。
(2)上层中的各个handler(Keyboard/Mouse/Joystick/Event)是属于平行关系,他们都是属于上层。不同的handler下对应的输入设备在应用层中的接口命名方式不一样,例如Mouse下的输入设备在应用层的接口是 /dev/input/mousen (n代表0、1、2…),Joystick下的输入设备在应用层的接口是 /dev/input/jsn(n代表0、1、2…),Event下的输入设备在应用层的接口是 /dev/input/eventn(n代表0、1、2…),这个是在input输入子系统中实现的,下面会分析其中的原由。
(3)输入核心层其实是负责协调上层和下层,使得上层和下层之间能够完成数据传递。当下层发生输入事件的时候,整个系统就被激活了,事件就会通过核心层把产生的数据结构传递到上层对应的一个/多个handler中,最终会传递到应用空间。
3、输入子系统解决了什么问题?
(1)在GUI界面中,用户的自由度太大了,可以做的事情太多了,可以响应不同的输入类设备,而且还能够对不同的输入类设备的输入做出不同的动作。例如window中的一个软件既可以响应鼠标输入事件,也可以相应键盘输入事件,而且这些事件都是预先不知道的。
(2)input子系统解决了不同的输入类设备的输入事件与应用层之间的数据传输,使得应用层能够获取到各种不同的输入设备的输入事件,input输入子系统能够囊括所有的不同种类的输入设备,在应用层都能够感知到所有发生的输入事件。
4、input输入子系统如何工作?
例如以一次鼠标按下事件为例子来说明我们的input输入子系统的工作过程:
当我们按下鼠标左键的时候就会触发中断(中断是早就注册好的),就会去执行中断所绑定的处理函数,在函数中就会去读取硬件寄存器来判断按下的是哪个按键和状态 ---->将按键信息上报给input core层 —> input core层处理好了之后就会上报给input event层,在这里会将我们的输入事件封装成一个input_event结构体放入一个缓冲区中 —> 应用层read就会将缓冲区中的数据读取出去。
二,input设备应用层编程实践
1、确定设备文件名
(1)应用层操作驱动有2条路:/dev目录下的设备文件,/sys目录下的属性文件
(2)input子系统用的/dev目录下的设备文件,具体一般都是在 /dev/input/eventn
(3)用cat命令来确认某个设备文件名对应哪个具体设备。我在自己的ubuntu中实测的键盘是event1,而鼠标是event3.
2、标准接口打开并读取文件
3、解析struct input_event
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
4、解析键盘事件数据和鼠标事件数据
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/input.h>
#include <string.h>
#define DEVICE_KEY "/dev/input/event1"
#define DEVICE_MOUSE "/dev/input/event3"
int main(void)
{
int fd = -1, ret = -1;
struct input_event ev;
// 第1步:打开设备文件
fd = open(DEVICE_KEY, O_RDONLY);
if (fd < 0)
{
perror("open");
return -1;
}
while (1)
{
// 第2步:读取一个event事件包
memset(&ev, 0, sizeof(struct input_event));
ret = read(fd, &ev, sizeof(struct input_event));
if (ret != sizeof(struct input_event))
{
perror("read");
close(fd);
return -1;
}
// 第3步:解析event包,才知道发生了什么样的输入事件
printf("-------------------------\n");
printf("type: %hd\n", ev.type);
printf("code: %hd\n", ev.code);
printf("value: %d\n", ev.value);
printf("\n");
}
// 第4步:关闭设备
close(fd);
return 0;
}
三,input子系统源码分析
1.input子系统架构总览
【1】input子系统分为三层
- 最上层:输入事件驱动层,evdev.c和mousedev.c和joydev.c属于这一层
- 中间层:输入核心层,input.c属于这一层
- 最下层:输入设备驱动层,drivers/input/xxx 文件夹下
【2】input类设备驱动开发方法
- 输入事件驱动层和输入核心层不需要动,只需要编写设备驱动层
- 设备驱动层编写的接口和调用模式已定义好,驱动工程师的核心工作量是对具体输入设备硬件的操作和性能调优。
2.输入核心层源码分析-------- input.c
【1】核心层模块注册input_init
- class_register
- input_proc_init
- register_chrdev
【2】核心层向设备驱动层提供的接口函数
- input_allocate_device:分配一块input_dev结构体类型大小的内存
- input_set_capability:设置输入设备可以上报哪些输入事件
- 函数原型:
input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code)
- dev就是设备的input_dev结构体变量
- type表示设备可以上报的事件类型
- code表示上报这类事件中的那个事件
注意:input_set_capability函数一次只能设置一个具体事件,如果设备可以上报多个事件,则需要重复调用这个函数来进行设置
例如:
input_set_capability(dev, EV_KEY, KEY_Q); // 至于函数内部是怎么设置的,将会在后面进行分析。
input_set_capability(dev, EV_KEY, KEY_W);
input_set_capability(dev, EV_KEY, KEY_E);
- input_register_device:下层的设备驱动层向input核心层注册设备
input_attach_handler就是input_register_device函数中用来对下层的设备驱动和上层的handler进行匹配的一个函数,只有匹配成功之后就会调用上层handler中的connect函数进行连接绑定。
【】核心层向handler层即事件驱动层的接口函数
-
input_register_handler
-
input_register_handle:简单的挂接一些数据结构,注意区分即可
3.输入事件驱动层(handler层)源码分析------- evdev.c
【1】input_handler
【2】evdev_connect
【3】evdev_event
4.输入设备驱动层源码分析
【1】先找到bsp中按键驱动源码
- 锁定目标:板载按键驱动
- 确认厂家提供的BSP是否已经有驱动
- 找到bsp中的驱动源码
【2】按键驱动源码初步分析
-
模块装载分析
-
平台总线相关分析
-
确定重点:probe函数
【3】源码细节实现分析
- gpio_request
-
input_allocate_device
-
input_register_device
-
timer
参考博客input子系统