6. Linux内核输入子系统¶
在Linux内核驱动开发中,输入子系统是一套专门用于管理各类输入设备的标准化框架, 其核心作用是统一处理键盘、鼠标、按键、触摸屏、红外遥控等各类输入设备的事件, 为驱动开发者提供标准化的接口,简化输入设备驱动的开发流程,同时为应用层提供统一的事件读取接口,实现输入事件的规范化传输。
不同于异步通知、阻塞/非阻塞IO等通用IO机制,输入子系统是针对输入设备的专用框架,它屏蔽了不同输入设备的硬件差异, 将各类输入设备的操作抽象为统一的事件(如按键按下/松开、坐标移动等),使得驱动开发者无需关注应用层的事件处理逻辑, 只需按照子系统规范实现硬件相关的事件采集,即可完成输入设备的驱动开发。
6.1. 输入子系统概念¶
输入子系统(Input Subsystem)是Linux内核中一套标准化的框架,用于统一管理所有输入设备,抽象输入设备的共性, 提供标准化的驱动开发接口和应用层访问接口。整体工作流程可概括为:硬件触发输入信号->驱动层采集并转换为标准事件->核心层接收并转发事件->事件处理层将事件传递给应用层->应用层处理事件。 具体工作流程如下:
Drivers(驱动层):最底层,直接与硬件设备交互,负责初始化输入硬件、采集硬件输入信号(如按键按下/松开、鼠标移动等),并将采集到的硬件信号转换为输入子系统定义的标准事件,提交给核心层。驱动层的核心任务是“硬件适配”,无需关注事件的后续处理与转发。
Input Core(输入子系统核心层):中间层,是输入子系统的核心枢纽,负责管理所有输入驱动和事件处理程序,提供标准化的接口供驱动层和事件处理层调用。核心层接收驱动层提交的事件,进行统一管理、过滤和转发,同时负责维护输入设备的信息,协调驱动层与事件处理层的交互。
handlers(事件处理层):最上层,负责将核心层转发的标准事件传递给应用层,提供应用层可访问的设备节点(如/dev/input/eventX),应用层通过读取该设备节点,即可获取输入事件。事件处理层屏蔽了底层硬件差异,为应用层提供了统一的事件读取接口。
6.2. 输入子系统结构体与按键码¶
输入子系统的核心功能通过一系列内核结构体实现,这些结构体由内核定义, 驱动开发者只需声明和使用这些结构体,无需手动定义。
6.2.1. 输入设备结构体(input_dev)¶
该结构体是输入驱动层的核心结构体,用于描述一个输入设备的基本信息,其具体定义位于内核源码/include/linux/input.h中, 核心源码片段如下:
1 2 3 4 5 6 7 8 9 | struct input_dev {
const char *name; // 输入设备名称
const char *phys; // 设备物理地址
unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; // 设备支持的事件类型位图
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];// 设备支持的按键码位图
struct input_id id; // 设备标识信息
struct list_head node; // 用于将设备加入输入子系统链表
// 省略其他辅助成员
};
|
每个输入设备都需要创建一个该结构体,并将其注册到输入子系统核心层,核心层通过该结构体管理输入设备。 其中 evbit 和 keybit 是核心成员,分别通过位图标识设备支持的事件类型(如EV_KEY按键事件)和具体按键码。
6.2.2. 输入事件结构体(input_event)¶
该结构体是输入子系统中事件的标准化表示,用于存储输入事件的相关信息,其具体定义位于内核源码/include/uapi/linux/input.h中, 源码定义如下:
1 2 3 4 5 6 | struct input_event {
struct timeval time; // 事件发生的时间戳
__u16 type; // 事件类型
__u16 code; // 事件代码
__s32 value; // 事件值
};
|
驱动层采集到硬件输入信号后,会将其转换为该结构体表示的标准事件,提交给核心层; 应用层读取输入事件时,也是通过读取该结构体的数据,获取输入设备的操作信息。
6.2.3. 输入事件处理结构体(input_handler)¶
该结构体用于描述事件处理程序的基本信息,其具体定义位于内核源码/include/linux/input.h中,核心源码片段如下:
1 2 3 4 5 6 7 8 | struct input_handler {
const char *name; // 事件处理程序名称
const struct input_device_id *id_table; // 匹配规则
void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value); // 事件处理函数
int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id); // 连接函数
void (*disconnect)(struct input_handle *handle); // 断开连接函数
struct list_head node; // 用于将处理程序加入输入子系统链表
};
|
事件处理层通过该结构体向核心层注册,核心层将驱动层提交的事件,转发给匹配的事件处理程序, 由事件处理程序将事件传递给应用层。最常用的evdev事件处理程序,其event函数会将事件写入设备节点,供应用层读取。
6.2.4. 按键码定义¶
输入子系统中,各类输入事件都通过标准化的按键码(Key Code)来标识,按键码由内核统一定义,确保不同驱动和应用层之间的兼容性。 按键码的核心定义位于内核头文件内核源码/include/dt-bindings/input/linux-event-codes.h中,采用宏定义方式实现, 根据输入设备的类型,分为不同的类别,常用的按键码分类及源码定义示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // 键盘数字键
#define KEY_0 11
#define KEY_1 2
#define KEY_2 3
...
// 键盘字母键
#define KEY_A 30
#define KEY_B 48
...
// 控制键
#define KEY_ENTER 28
#define KEY_LEFTCTRL 29
...
// 鼠标按键
#define BTN_LEFT 0x110
#define BTN_RIGHT 0x111
|
驱动层和应用层可直接使用这些宏定义,无需手动定义按键码值,确保按键码的统一性和兼容性。 例如驱动层设置GPIO按键对应KEY_1,直接引用KEY_1宏即可,无需硬编码数值2。
6.3. 输入子系统常用函数¶
Linux内核为输入子系统提供了一系列标准化的函数,涵盖输入设备的初始化、注册、注销,事件的提交, 以及驱动和事件处理程序的注册、注销等核心操作。
6.3.1. 输入设备分配¶
6.3.1.1. devm_input_allocate_device函数¶
基于devm(设备资源管理)机制,为输入设备结构体(input_dev)分配内存并初始化默认值, 该函数分配的input_dev结构体,会与指定的设备绑定,当设备被卸载或内核释放资源时, 会自动释放该input_dev结构体占用的内存,无需开发者手动调用释放函数,可有效避免内存泄漏。
函数原型:
1 | struct input_dev *devm_input_allocate_device(struct device *dev);
|
参数说明:
dev:指向与该输入设备绑定的设备结构体指针。
返回值:成功返回分配好的input_dev结构体指针;失败返回NULL,通常是由于内存分配失败或参数无效导致。
6.3.2. 输入设备注册¶
6.3.2.1. input_register_device函数¶
input_register_device函数用于:将分配并配置好的输入设备结构体(input_dev)注册到输入子系统核心层, 核心层会对设备进行管理,并将其与对应的事件处理程序匹配。 注册成功后,输入设备即可正常工作,驱动层可向核心层提交输入事件。
函数原型:
1 | int input_register_device(struct input_dev *dev);
|
参数说明:
dev:指向已分配并配置完成的input_dev结构体指针,是需要注册到核心层的输入设备。
返回值:成功返回0;失败返回负整数的错误码,不同错误码对应不同的失败原因,如设备注册重复、参数无效等。
6.3.3. 输入设备注销¶
6.3.3.1. input_unregister_device函数¶
input_unregister_device函数用于将已注册的输入设备从核心层注销,释放输入设备结构体占用的内存,终止输入设备的工作。 该函数会自动清理输入设备的相关资源,避免内存泄漏。
函数原型:
1 | void input_unregister_device(struct input_dev *dev);
|
参数说明:
dev:指向已注册的input_dev结构体指针,是需要从核心层注销的输入设备。
6.3.4. 输入设备事件类型设置¶
6.3.4.1. set_bit函数¶
set_bit函数 根据传入参数不同,可用于将已注册的输入设备从核心层注销,释放输入设备结构体占用的内存,终止输入设备的工作。 该函数会自动清理输入设备的相关资源,避免内存泄漏。
函数原型:
1 | static inline void set_bit(unsigned int bit, unsigned long *addr);
|
参数说明:
bit:需要设置的事件类型宏(如EV_KEY、EV_REL),对应事件类型的标识值;
addr:指向input_dev结构体中evbit成员的指针,用于存储设备支持的事件类型位图。
6.3.5. 输入设备按键码设置¶
6.3.5.1. set_bit函数¶
set_bit函数 根据传入参数不同,可用于设置输入设备支持的事件类型,如按键事件、坐标事件、相对位移事件等。 输入子系统支持多种事件类型,驱动层需通过该函数明确告知核心层,当前输入设备支持哪些事件类型。
函数原型:
1 | static inline void set_bit(unsigned int bit, unsigned long *addr);
|
参数说明:
bit:需要设置的按键码宏(如KEY_1、BTN_LEFT),对应按键的标准化标识值;
addr:指向input_dev结构体中keybit成员的指针,用于存储设备支持的按键码位图。
6.3.6. 输入事件提交¶
6.3.6.1. input_event函数¶
input_event函数用于将驱动层采集到的硬件输入信号,转换为标准的输入事件(input_event结构体),并提交给输入子系统核心层。 核心层接收该事件后,会将其转发给对应的事件处理程序,最终传递给应用层。
函数原型:
1 | void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
|
参数说明:
dev:指向当前输入设备的input_dev结构体指针;
type:事件类型(如EV_KEY、EV_REL);
code:事件代码(对应按键码或事件细分类型,如KEY_1、REL_X);
value:事件值(按键事件中1为按下、0为松开,位移事件中为具体位移量)。
6.4. Linux输入子系统实验¶
本实验在Linux中断子系统实验基础上进行修改, 实现“按键触发中断->驱动上报输入事件->应用层读取事件”的完整流程,无需自定义字符设备的read/write操作, 依托输入子系统框架完成按键事件的标准化上报与读取,理解输入子系统的核心工作机制、设备注册流程及事件上报规范。
本章的示例代码目录为: linux_driver/23_input_subsystem
6.4.1. 设备树插件详解¶
设备树插件完整代码如下:
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | /dts-v1/;
/plugin/;
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/pinctrl/rockchip.h>
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/input/linux-event-codes.h>
/ {
fragment@0 {
target-path = "/";
__overlay__ {
led_test: led_test{
compatible = "fire,led_test";
led-gpios = <&gpio0 RK_PC7 GPIO_ACTIVE_HIGH>;
button-gpios= <&gpio1 RK_PB2 GPIO_ACTIVE_HIGH>;
interrupt-parent = <&gpio1>;
interrupts = <RK_PB2 IRQ_TYPE_EDGE_BOTH>;
input-name = "lubancat-key-input";
key-code = <KEY_1>;
pinctrl-names = "default";
pinctrl-0 = <&led_button_pin>;
};
};
};
fragment@1 {
target = <&pinctrl>;
__overlay__ {
led_button_test {
led_button_pin: led_button_pin {
rockchip,pins =
<0 RK_PC7 RK_FUNC_GPIO &pcfg_pull_none>,
<1 RK_PB2 RK_FUNC_GPIO &pcfg_pull_up>;
};
};
};
};
fragment@2 {
target = <&leds>;
__overlay__ {
status = "disabled";
};
};
};
|
第7行:引入linux-event-codes.h,用于使用标准的按键事件编码;
第20行:input-name:输入设备的名称,驱动会将该名称赋值给input_dev结构体,应用层可通过该名称识别设备;
第21行:key-code:按键对应的事件编码,由linux-event-codes.h定义(如KEY_1=2),驱动会根据该编码上报按键事件,应用层通过编码识别按键类型.
6.4.2. 驱动代码详解¶
核心数据结构定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | /* 定义 LED 字符设备结构体 */
struct led_chrdev {
/* 字符设备结构体 */
struct cdev dev;
/* 自旋锁 */
spinlock_t spinlock;
/* LED 状态 */
int led_state;
/* LED 的 GPIO 描述符 */
struct gpio_desc *led_gpio;
/* 按钮的 GPIO 描述符 */
struct gpio_desc *button_gpio;
/* 消抖定时器 */
struct timer_list debounce_timer;
/* 中断号 */
int irq;
/* 输入设备结构体指针 */
struct input_dev *input_dev;
/* 从设备树读取的按键码 */
u32 key_code;
};
/* 定义 led_chrdev 结构体指针,用于动态分配内存管理 LED 硬件信息 */
static struct led_chrdev *led_cdev;
|
第18行:新增输入子系统的核心结构体,用于描述输入设备的属性;
第20行:存储从设备树读取的按键编码,用于事件上报时指定按键类型。
驱动初始化
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | static int pdrv_led_probe(struct platform_device *pdev)
{
// 内存分配(省略重复代码)
/* 第一步:提取平台设备提供的资源 */
if (pdev->dev.of_node) {
/* 获取 LED 的 GPIO 描述符,GPIOD_OUT_HIGH 表示设置为输出模式 + 输出高电平 */
led_cdev->led_gpio = devm_gpiod_get(&pdev->dev, "led", GPIOD_OUT_HIGH);
if (IS_ERR(led_cdev->led_gpio)) {
ret = PTR_ERR(led_cdev->led_gpio);
printk(KERN_ERR "Failed to get LED GPIO: %d\n", ret);
return ret;
}
/* 获取按钮的 GPIO 描述符,GPIOD_IN 表示设置为输入模式 */
led_cdev->button_gpio = devm_gpiod_get(&pdev->dev, "button", GPIOD_IN);
if (IS_ERR(led_cdev->button_gpio)) {
ret = PTR_ERR(led_cdev->button_gpio);
printk(KERN_ERR "Failed to get button GPIO: %d\n", ret);
return ret;
}
/* 从设备树读取输入设备名称 */
ret = of_property_read_string(pdev->dev.of_node, "input-name", &input_name);
if (ret < 0) {
printk(KERN_ERR "Failed to get input-name from devicetree\n");
return ret;
}
/* 从设备树读取按键码 */
ret = of_property_read_u32(pdev->dev.of_node, "key-code", &led_cdev->key_code);
if (ret < 0) {
printk(KERN_ERR "Failed to get key-code from devicetree\n");
return ret;
}
} else {
printk("Platform device matching is not supported in this driver\n");
return -ENOMEM;
}
//字符设备注册(省略重复代码)
/* 初始化自旋锁 */
spin_lock_init(&led_cdev->spinlock);
/* 初始化消抖定时器 */
timer_setup(&led_cdev->debounce_timer, button_debounce_callback, 0);
/* 默认关闭 LED */
led_cdev->led_state = 0;
/* 输入子系统初始化 */
/* 1. 分配输入设备 */
led_cdev->input_dev = devm_input_allocate_device(&pdev->dev);
if (!led_cdev->input_dev) {
printk(KERN_ERR "Failed to allocate input device\n");
ret = -ENOMEM;
goto device_err;
}
/* 2. 使用设备树的输入设备名称 */
led_cdev->input_dev->name = input_name;
/* 3. 设置支持的事件类型:EV_KEY(按键事件) */
set_bit(EV_KEY, led_cdev->input_dev->evbit);
/* 4. 使用设备树读取的按键码 */
set_bit(led_cdev->key_code, led_cdev->input_dev->keybit);
/* 5. 注册输入设备到内核 */
ret = input_register_device(led_cdev->input_dev);
if (ret < 0) {
printk(KERN_ERR "Failed to register input device: %d\n", ret);
goto device_err;
}
/* 获取按键GPIO对应的中断号 */
led_cdev->irq = gpiod_to_irq(led_cdev->button_gpio);
if (led_cdev->irq < 0) {
ret = led_cdev->irq;
/* 打印获取中断号失败 */
printk(KERN_ERR "Failed to get button IRQ: %d\n", ret);
/* 跳转到错误处理标签 */
goto device_err;
}
/* 申请中断,双边沿触发,匹配设备树IRQ_TYPE_EDGE_BOTH */
ret = devm_request_irq(&pdev->dev, led_cdev->irq, button_irq_handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
"button_irq", led_cdev);
if (ret < 0) {
/* 打印申请中断失败 */
printk(KERN_ERR "Failed to request IRQ: %d\n", ret);
/* 跳转到错误处理标签 */
goto device_err;
}
return 0;
// 错误处理(省略重复代码)
}
|
输入设备初始化步骤:
第55行:分配输入设备:使用devm_input_allocate_device()带资源自动释放的接口,避免内存泄漏;
第63行:设置设备名称:用于标识输入设备;
第66行:设置支持的事件类型:通过set_bit()设置evbit,告知内核该设备支持的事件;
第69行:设置支持的按键编码:通过set_bit()设置keybit,告知内核该设备支持的按键;
第71行:注册输入设备:调用input_register_device()将设备注册到输入子系统,注册成功后,内核会自动创建/dev/input/eventX设备节点。
输入事件上报逻辑
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 32 33 34 35 36 37 | /* 消抖定时器回调函数:确认按键稳定后翻转LED + 上报输入事件 */
static void button_debounce_callback(struct timer_list *t)
{
/* 反向找到包含这个定时器的设备结构体 led_chrdev 指针 */
struct led_chrdev *led_cdev = from_timer(led_cdev, t, debounce_timer);
/* 用于保存中断状态信息 */
unsigned long flags;
/* 存储按键状态 */
int btn_val;
/* 获取自旋锁并保存中断状态 */
spin_lock_irqsave(&led_cdev->spinlock, flags);
/* 读取稳定后的按键电平 */
btn_val = gpiod_get_value(led_cdev->button_gpio);
/* 低电平表示按键稳定按下 */
if(btn_val == 0){
/* 翻转LED电平状态 */
gpiod_set_value(led_cdev->led_gpio, !gpiod_get_value(led_cdev->led_gpio));
/* 上报按键按下事件 */
input_event(led_cdev->input_dev, EV_KEY, led_cdev->key_code, 1);
/* 同步事件,告知应用层事件完整 */
input_sync(led_cdev->input_dev);
} else {
/* 上报按键松开事件 */
input_event(led_cdev->input_dev, EV_KEY, led_cdev->key_code, 0);
/* 同步事件,告知应用层事件完整 */
input_sync(led_cdev->input_dev);
}
/* 释放自旋锁并恢复中断状态 */
spin_unlock_irqrestore(&led_cdev->spinlock, flags);
}
|
第23行和第29行:调用input_event,上报具体的输入事件,按下上报1,松开上报0
第26行和第32行:调用input_sync同步事件,必须在每次事件上报完成后调用,告知应用层“本次事件已完整”,避免事件错乱。
6.4.3. 应用代码详解¶
应用层无需适配具体驱动,通过输入子系统提供的标准接口(/dev/input/eventX)读取按键事件,解析事件类型、编码和值, 即可获取按键状态,完整代码如下:
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | #include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <linux/input.h>
int main(int argc, char *argv[]) {
int fd;
int ret;
// 输入事件结构体,用于存储读取到的输入事件
struct input_event ev;
// 检查参数,需传入输入设备event节点路径
if (argc != 2) {
printf("Usage: ./input_app /dev/input/eventX\n");
return -1;
}
// 打开输入设备节点
fd = open(argv[1], O_RDONLY);
if (fd < 0) {
perror("open input device failed");
return -1;
}
printf("Start reading input event...\n");
// 循环读取输入事件
while (1) {
// 读取输入事件,struct input_event 是输入子系统的标准事件格式
ret = read(fd, &ev, sizeof(struct input_event));
if (ret < 0) {
perror("read input event failed");
close(fd);
return -1;
}
// 解析事件:仅处理按键事件 EV_KEY
if (ev.type == EV_KEY) {
// 打印事件信息:时间戳、事件类型、按键编码、事件值
printf("Event: type=%d, code=%d value=%d\n",
ev.type, ev.code, ev.value);
// 根据事件值判断按键状态
if (ev.value == 1) {
printf("=== 按键按下 ===\n");
} else if (ev.value == 0) {
printf("=== 按键松开 ===\n");
}
}
}
close(fd);
return 0;
}
|
第15行:输入子系统的标准事件结构体,包含事件类型(type)、事件编码(code)、事件值(value)和时间戳,内核会将按键事件封装成该结构体上报;
第43行:仅处理EV_KEY类型事件,根据value值判断按键按下(1)或松开(0),code值对应KEY_1(编码为2)。
6.4.4. 编译设备树和驱动¶
此部分和Linux中断子系统实验完全一致不作过多说明。
编译得到设备树插件lubancat-led-overlay.dtb、驱动模块input_subsystem.ko和应用程序input_subsystem_app。
6.4.5. 程序运行结果¶
如出现 Permission denied 或类似字样,请注意用户权限,大部分操作硬件外设的功能,几乎都需要root用户权限,简单的解决方案是在执行语句前加入sudo或以root用户运行程序。
6.4.5.1. 实验操作¶
设备树插件加载方法和设备树插件实验完全一致,加载设备树插件并重启板卡后会发现系统心跳灯默认没有闪烁, 是因为我们使用设备树插件关闭了leds节点,释放了引脚。
使用以下命令加载驱动:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #加载驱动
sudo insmod input_subsystem.ko
#信息输出如下
[ 5846.950693] led platform driver init
[ 5846.951231] led platform driver probe
[ 5846.951367] major=236, minor=0
[ 5846.952030] input: lubancat-key-input as /devices/platform/led_test/input/input3
#查看输入设备对应的事件
ls /dev/input/by-path/ -l
#信息输出如下
total 0
lrwxrwxrwx 1 root root 9 Jun 18 2024 platform-fdd40000.i2c-platform-rk805-pwrkey-event -> ../event1
lrwxrwxrwx 1 root root 9 Jun 18 2024 platform-fdd70030.pwm-event -> ../event0
lrwxrwxrwx 1 root root 9 Apr 3 16:26 platform-led_test-event -> ../event3
lrwxrwxrwx 1 root root 9 Jun 18 2024 platform-rk-headset-event -> ../event2
|
此处event3就是input_subsystem驱动注册的事件,使用evtest监控系统事件上报:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #执行命令
sudo evtest
#信息输出如下,event3对应的设备名字是lubancat-key-input,就是设备树配置的输入设备名字
No device specified, trying to scan all of /dev/input/event*
Available devices:
/dev/input/event0: fdd70030.pwm
/dev/input/event1: rk805 pwrkey
/dev/input/event2: rk-headset
/dev/input/event3: lubancat-key-input
Select the device event number [0-3]:
#键盘输入 3 选择event3,信息输入如下
Select the device event number [0-3]: 3
Input driver version is 1.0.1
Input device ID: bus 0x0 vendor 0x0 product 0x0 version 0x0
Input device name: "lubancat-key-input"
Supported events:
Event type 0 (EV_SYN) //表示同步事件
Event type 1 (EV_KEY) //表示按键事件
Event code 2 (KEY_1) //按键编码为2对应KEY_1
Properties:
Testing ... (interrupt to exit)
|
可以从输出信息看到,此处event3对应的设备名字是lubancat-key-input,就是设备树配置的输入设备名字, 事件编号各系统各板卡可能不一样,需根据实际情况而定。
使用杜邦线一端连接按键引脚,另一端多次连接和断开连接GND引脚模拟按键按下和松开,信息打印如下:
1 2 3 4 5 6 7 8 | Event: time 1775206083.362980, type 1 (EV_KEY), code 2 (KEY_1), value 1
Event: time 1775206083.362980, -------------- SYN_REPORT ------------
Event: time 1775206083.689373, type 1 (EV_KEY), code 2 (KEY_1), value 0
Event: time 1775206083.689373, -------------- SYN_REPORT ------------
Event: time 1775206084.992803, type 1 (EV_KEY), code 2 (KEY_1), value 1
Event: time 1775206084.992803, -------------- SYN_REPORT ------------
Event: time 1775206085.369374, type 1 (EV_KEY), code 2 (KEY_1), value 0
Event: time 1775206085.369374, -------------- SYN_REPORT ------------
|
按键按下时会上报value 1,松开会上报value 0,验证输入子系统驱动功能正常。
使用应用程序进行测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 | #运行应用程序,event编号根据实际而定
sudo ./input_subsystem_app /dev/input/event3
#信息输出如下
Start reading input event...
Event: type=1, code=2 value=1
=== 按键按下 ===
Event: type=1, code=2 value=0
=== 按键松开 ===
Event: type=1, code=2 value=1
=== 按键按下 ===
Event: type=1, code=2 value=0
=== 按键松开 ===
|
结合evtest的打印信息,可以确认type=1就对应Event type 1 (EV_KEY) 表示按键事件,code=2就对应Event code 2 (KEY_1),按键按下value值为1,按键松开value值为0。
6.4.5.2. 系统对上报事件处理¶
如果使用屏幕终端或者桌面打开一个终端会发现,每次短接杜邦线模拟按键按下终端上都会有“1”打印, 该效果与外接USB键盘按下数字“1”键完全一致, 说明输入子系统的硬件无关性——上层应用无需区分事件来自GPIO按键还是USB键盘。
如果我们将设备树配置的key-code = <KEY_1>改为key-code = <KEY_POWER>, 那么我们注册的GPIO按键就变成了一个货真价实的系统电源键,长按按键系统将会关机!
如果希望我们注册的按键不受系统默认处理,可以注册一个空事件,可以观察到内核源码/include/dt-bindings/input/linux-event-codes.h中 编号249到255是空事件,如果使用这些空事件系统将不作处理,如设备树配置为key-code = <250>。
6.4.6. 实验注意事项¶
输入事件上报规范:每次按键事件(按下/松开)都需调用input_event()上报,且上报完成后必须调用input_sync()同步,否则应用层无法正确解析事件;
输入设备节点识别:若应用层无法读取事件,需通过
ls /dev/input/by-path/ -l确认输入设备名称对应的event节点,避免使用错事件;事件编码正确:key-code需使用Linux内核定义的标准编码(如KEY_1),需包含linux-event-codes.h头文件,否则会出现编码错误。