24. Linux 输入子系统
Linux 输入子系统(Linux Input Subsystem)是 Linux 内核中负责处理各种输入设备(如键盘、鼠标、触摸屏、游戏控制器等)事件的核心组件。它为用户空间提供了统一的输入接口,使得不同类型的输入设备能够以一致的方式进行管理和使用。
24.1 概述
Linux 输入子系统负责管理和处理所有输入设备生成的事件。它提供了一套标准化的接口,使得驱动开发者无需关心具体硬件的细节,只需按照规范将事件报告给内核,用户空间的应用程序便能够统一处理这些事件。
主要功能包括:
24.2 输入子系统的框架
Linux 输入子系统的架构可以分为以下几个主要组件:
层次框图如下:
+-------------------------------------------------------------+
| 应用层 |
| (如 GNOME、KDE、Qt、GTK、浏览器等与用户交互的应用程序) |
+-------------------------------------------------------------+
↑
|
+-------------------------------------------------------------+
| 图形服务器(Display Server) |
| (如 X11 Server, Wayland, Mir,负责与显示设备交互) |
+-------------------------------------------------------------+
↑
|
+-------------------------------------------------------------+
| libinput |
| (用户空间库,处理触摸屏、鼠标、键盘等输入事件) |
+-------------------------------------------------------------+
↑
|
+-------------------------------------------------------------+
| evdev (Event Device) |
| (Linux 内核的 evdev 接口,通过 /dev/input/eventX 传递事件) |
+-------------------------------------------------------------+
↑
|
+-------------------------------------------------------------+
| Linux Input 子系统 (Input Subsystem) |
| (处理来自输入设备的事件,如触摸屏、鼠标、键盘等输入事件) |
+-------------------------------------------------------------+
↑
|
+-------------------------------------------------------------+
| 触摸屏驱动 (Touchscreen Driver) |
| (通过 I²C、SPI、USB 等与硬件通信,将原始数据转化为输入事件) |
+-------------------------------------------------------------+
↑
|
+-------------------------------------------------------------+
| 硬件层 (Touchscreen Hardware) |
| (如电容式或电阻式触摸屏控制器和触摸面板) |
+-------------------------------------------------------------+
24.2.1 硬件层 (Touchscreen Hardware)
触摸屏硬件是最底层的物理设备,包括电容式或电阻式触摸屏面板和控制器。触摸屏控制器通过 I²C、SPI 或 USB 与设备主板通信,传递触摸输入数据(如触摸位置、压力等)。
24.2.2 触摸屏驱动 (Touchscreen Driver)
触摸屏驱动 是在 Linux 内核中运行的代码,通过内核接口与触摸屏硬件通信。驱动程序将硬件传递的原始数据(如坐标、压力等)转换为标准的输入事件(如 EV_ABS 事件),并通过 Linux 输入子系统传递这些事件。
24.2.3 Linux 输入子系统 (Input Subsystem)
比如在设备上可以查看到如下输入设备(不同设备有差别, 如插入键盘鼠标等):
neardi@LPA3588:~/drivers/input$ ls /dev/input/
by-id by-path event0 event1 event2 event3 event4 event5 event6 event7
neardi@LPA3588:~/drivers/input$ ls /dev/input/by-id -l
total 0
lrwxrwxrwx 1 root root 9 Sep 29 18:04 usb-RAPOO_Rapoo_2.4G_Wireless_Device-event-if01 -> ../event4
lrwxrwxrwx 1 root root 9 Sep 29 18:04 usb-RAPOO_Rapoo_2.4G_Wireless_Device-event-mouse -> ../event3
lrwxrwxrwx 1 root root 9 Sep 29 18:04 usb-RAPOO_Rapoo_2.4G_Wireless_Device-if02-event-kbd -> ../event6
neardi@LPA3588:~/drivers/input$ ls /dev/input/by-path -l
total 0
lrwxrwxrwx 1 root root 9 Sep 29 18:04 platform-es8388-sound-event -> ../event2
lrwxrwxrwx 1 root root 9 Sep 29 18:04 platform-feb20000.spi-platform-rk805-pwrkey.3.auto-event -> ../event0
lrwxrwxrwx 1 root root 9 Sep 29 18:04 platform-hdmi0-sound-event -> ../event7
lrwxrwxrwx 1 root root 9 Sep 29 18:04 platform-xhci-hcd.6.auto-usb-0:1.3:1.0-event-mouse -> ../event3
lrwxrwxrwx 1 root root 9 Sep 29 18:04 platform-xhci-hcd.6.auto-usb-0:1.3:1.1-event -> ../event4
lrwxrwxrwx 1 root root 9 Sep 29 18:04 platform-xhci-hcd.6.auto-usb-0:1.3:1.2-event-kbd -> ../event6
24.2.4 evdev (Event Device)
evdev 是 Linux 内核中的输入设备事件接口。触摸屏、鼠标、键盘等设备的输入事件都会通过 /dev/input/eventX 的文件节点传递给用户空间程序。开发者可以使用工具(如 evtest)读取这个设备文件,直接查看输入事件。
主要功能:
下面的代码是获取输入设备的名称和事件类型:
#include <linux/input.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main() {
int fd = open("/dev/input/event0", O_RDONLY);
if (fd < 0) {
perror("Cannot open device");
return 1;
}
char name[256] = "Unknown";
ioctl(fd, EVIOCGNAME(sizeof(name)), name);
printf("Device name: %s\n", name);
close(fd);
return 0;
}
编译及运行此app, 如下:
neardi@LPA3588:~/drivers/input$ aarch64-linux-gnu-g++ -o app app.cpp
neardi@LPA3588:~/drivers/input$ sudo ./app
Device name: rk805 pwrkey
24.2.5 libinput
libinput 是一个用户空间库,用于处理来自输入设备的事件。它封装了 evdev,并提供了更高级别的输入事件处理功能,如手势识别、多点触控、设备配置等。
主要功能:
统一管理多种输入设备。
提供手势识别和多点触控支持。
支持设备配置和属性管理。
下面代码是使用 libinput 监听输入事件, 创建testinput.cpp, 如下:
#include <libinput.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
static int open_restricted(const char *path, int flags, void *user_data)
{
int fd = open(path, flags);
if (fd < 0)
fprintf(stderr, "Failed to open %s: %m\n", path);
return fd;
}
static void close_restricted(int fd, void *user_data)
{
close(fd);
}
int main()
{
struct libinput *li;
struct libinput_event *event;
struct libinput_interface interface = {
.open_restricted = open_restricted,
.close_restricted = close_restricted,
};
li = libinput_path_create_context(&interface, NULL);
if (!li)
{
fprintf(stderr, "Failed to create libinput context\n");
return -1;
}
if (libinput_path_add_device(li, "/dev/input/event6") == NULL)
{
fprintf(stderr, "Failed to add input device\n");
libinput_unref(li);
return -1;
}
libinput_dispatch(li);
while (1)
{
libinput_dispatch(li);
while ((event = libinput_get_event(li)) != NULL)
{
enum libinput_event_type type = libinput_event_get_type(event);
switch (type)
{
case LIBINPUT_EVENT_KEYBOARD_KEY:
{
struct libinput_event_keyboard *kbevent = libinput_event_get_keyboard_event(event);
uint32_t key = libinput_event_keyboard_get_key(kbevent);
enum libinput_key_state state = libinput_event_keyboard_get_key_state(kbevent);
printf("Keyboard event: key %u, state %s\n", key, state == LIBINPUT_KEY_STATE_PRESSED ? "PRESSED" : "RELEASED");
break;
}
case LIBINPUT_EVENT_POINTER_MOTION:
{
struct libinput_event_pointer *ptevent = libinput_event_get_pointer_event(event);
double dx = libinput_event_pointer_get_dx(ptevent);
double dy = libinput_event_pointer_get_dy(ptevent);
printf("Pointer motion: dx=%.2f, dy=%.2f\n", dx, dy);
break;
}
case LIBINPUT_EVENT_POINTER_BUTTON:
{
struct libinput_event_pointer *ptevent = libinput_event_get_pointer_event(event);
uint32_t button = libinput_event_pointer_get_button(ptevent);
enum libinput_button_state state = libinput_event_pointer_get_button_state(ptevent);
printf("Pointer button: button %u, state %s\n", button, state == LIBINPUT_BUTTON_STATE_PRESSED ? "PRESSED" : "RELEASED");
break;
}
case LIBINPUT_EVENT_TOUCH_DOWN:
{
struct libinput_event_touch *touchevent = libinput_event_get_touch_event(event);
double x = libinput_event_touch_get_x(touchevent);
double y = libinput_event_touch_get_y(touchevent);
printf("Touch down at (%.2f, %.2f)\n", x, y);
break;
}
case LIBINPUT_EVENT_TOUCH_UP:
{
printf("Touch up event\n");
break;
}
default:
printf("Other event type: %d\n", type);
break;
}
libinput_event_destroy(event);
}
usleep(10000); // sleep a moment to avoid 100% CPU performance.
}
libinput_unref(li);
return 0;
}
编译及运行测试
首先安装依赖库, 如下:
neardi@LPA3588:~/drivers/input$ sudo apt-get install libinput-dev libudev-dev
编译及运行测试, 如下:
neardi@LPA3588:~/drivers/input$ aarch64-linux-gnu-g++ -o test testinput.cpp -linput -ludev
neardi@LPA3588:~/drivers/input$ sudo ./test
Other event type: 1
Keyboard event: key 18, state PRESSED
Keyboard event: key 18, state RELEASED
Keyboard event: key 19, state PRESSED
Keyboard event: key 19, state RELEASED
Keyboard event: key 28, state PRESSED
Keyboard event: key 28, state RELEASED
24.2.6 图形服务器 (Display Server)
图形服务器
如 X11 Server 或 Wayland负责管理显示设备,并处理来自输入设备的事件。它从 libinput 获取处理后的事件,将其传递给用户空间的应用程序。
24.2.7 应用层 (Application Layer)
应用程序是最终使用这些输入事件的地方。例如:
24.3 总结
Linux 输入子系统作为内核中处理输入设备的核心组件,提供了统一和标准化的接口,使得不同类型的输入设备能够高效地进行管理和使用。理解其框架、工作原理和数据流,对于开发和调试输入设备驱动至关重要。