1.按键、鼠标、键盘、触摸屏等都属于输入(input)设备, Linux 内核为此专门做了一个叫做 input
子系统的框架来处理输入事件。
2.输入设备本质上还是字符设备,只是在此基础上套上了 input 框架,用户只需要负责上报输入事件,比如按键值、坐标等信息, input 核心层负责处理这些事件。
3.input 就是输入的意思,因此 input 子系统就是管理输入的子系统,和 pinctrl、 gpio 子系统
一样,都是 Linux 内核针对某一类设备而创建的框架。
4.input 子系统分为 input 驱动层、 input 核心层、 input 事件处理层,最终给用户空间提供可访问的设备节点。
左边就是最底层的具体设备,比如按键、 USB 键盘/鼠标等;
中间部分属于Linux 内核空间,分为驱动层、核心层和事件层;
最右边的就是用户空间,所有的输入设备以文件的形式供用户应用程序使用。
input 子系统用到了我们前面讲解的驱动分层模型,我们编写驱动程序的时候只需要关注中间的驱动层、核心层和事件层,这三个层的分工如下:
驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。
核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理。
事件层:主要和用户空间进行交互。
5.input_dev 注册过程:
①、使用 input_allocate_device 函数申请一个 input_dev。
②、初始化 input_dev 的事件类型以及事件值。
③、使用 input_register_device 函数向 Linux 系统注册前面初始化好的 input_dev。
④、卸载input驱动的时候需要先使用input_unregister_device函数注销掉注册的input_dev,
然后使用 input_free_device 函数释放掉前面申请的 input_dev。
input_dev 注册流程
struct input_dev *inputdev; /* input 结构体变量 */
/* 驱动入口函数 */
static int __init xxx_init(void)
{
......
inputdev = input_allocate_device(); /* 申请 input_dev */
inputdev->name = "test_inputdev"; /* 设置 input_dev 名字 */
/*********第一种设置事件和事件值的方法***********/
__set_bit(EV_KEY, inputdev->evbit); /* 设置产生按键事件 */
__set_bit(EV_REP, inputdev->evbit); /* 重复事件 */
__set_bit(KEY_0, inputdev->keybit); /*设置产生哪些按键值 */
/************************************************/
/*********第二种设置事件和事件值的方法***********/
keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | T_MASK(EV_REP);
keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |= T_MASK(KEY_0);
/************************************************/
/*********第三种设置事件和事件值的方法***********/
keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | T_MASK(EV_REP);
input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
/************************************************/
/* 注册 input_dev */
input_register_device(inputdev);
......
return 0;
}
/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
input_unregister_device(inputdev); /* 注销 input_dev */
input_free_device(inputdev); /* 删除 input_dev */
}
6.上报输入事件
事件上报参考代码
/* 用于按键消抖的定时器服务函数 */
void timer_function(unsigned long arg)
{
unsigned char value;
value = gpio_get_value(keydesc->gpio); /* 读取 IO 值 */
if(value == 0) /* 按下按键 */
{
/* 上报按键值
dev:需要上报的 input_dev。
type: 上报的事件类型,比如 EV_KEY。
code: 事件码,也就是我们注册的按键值,比如 KEY_0、 KEY_1 等等。
value:事件值,比如 1 表示按键按下, 0 表示按键松开。
*/
input_report_key(inputdev, KEY_0, 1); /* 最后一个参数 1, 按下 */
input_sync(inputdev); /* 同步事件 */
}
else /* 按键松开 */
{
input_report_key(inputdev, KEY_0, 0); /* 最后一个参数 0, 松开 */
input_sync(inputdev); /* 同步事件 */
}
}
7.驱动测试:
按下按键就会有数据输出:
EV_KEY 事件值为 1, EV_SYN 事件值为0。因此第 1 行表示 EV_KEY 事件(按键事件),第 2 行表示 EV_SYN 事件(同步事件)。
code 为事件编码,也就是按键号,查看示例代码 58.1.2.4 可以, KEY_0 这个按键编号为 11,对应的十六进制为 0xb,因此第1 行表示 KEY_0 这个按键事件。
最后的 value 就是按键值,为 1 表示按下,为 0 的话表示松开。
8.应用层:
获取按键输入信息,那么必须借助于 input_event 结构体。所有的输入设备最终都是按照 input_event 结构体呈现给用户的,用户应用程序可以通过 input_event 来获取到具体的输入事件或相关的值。
input_event 结构体
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
time:时间,也就是此事件发生的时间。
type: 事件类型,比如 EV_KEY,表示此次事件为按键事件,此成员变量为 16 位。
code: 事件码,比如在 EV_KEY 事件中 code 就表示具体的按键码,如: KEY_0、 KEY_1等等这些按键。此成员变量为 16 位。
value: 值,比如 EV_KEY 事件中 value 就是按键值,表示按键有没有被按下,如果为 1 的话说明按键按下,如果为 0 的话说明按键没有被按下或者按键松开了。
按键驱动对应的文件就是/dev/input/eventX,(X=0,1,2,3。。。),应用程序读取/dev/input/event1来得到按键信息,也就是按键有没有被按下。
通过/dev/input/event1读到的信息是input_event结构体形式的。
使用read函数读取输入设备文件,也就是/dev/input/eventX,读取到的数据按照 input_event 结构体组织起来。