[toc]
在这里插入图片描述

drivers/input/input.c 输入子系统核心(Input Subsystem Core) 统一所有输入设备的事件处理框架

历史与背景

这项技术是为了解决什么特定问题而诞生的?

这项技术以及它所构建的整个输入子系统,是为了解决在Linux早期一个极其混乱的问题:缺乏一个统一的、标准化的方式来处理各种各樣的输入设备

  • 接口不统一:在输入子系统出现之前,每一种输入设备(PS/2鼠标、串口鼠标、AT键盘等)都有其自己独特的驱动程序,这些驱动会创建各自不同的设备文件(如/dev/psaux, /dev/mouse),并使用自己私有的数据协议。
  • 应用程序复杂性:这意味着用户空间的应用程序(尤其是X Window System)必须编写大量复杂的、针对特定硬件的代码,才能从这些不同的设备文件中读取和解析输入数据。更换一个鼠标,就可能需要重新配置X Window。
  • 可扩展性差:每当出现一种新的输入设备,不仅需要编写内核驱动,还需要修改上层的应用程序才能支持它。

输入子系统(由Vojtech Pavlik创建)的诞生,就是为了彻底解决这个乱局。它的核心目标是:在内核中创建一个通用的事件处理层,将所有物理输入设备的硬件差异完全屏蔽掉,并向用户空间提供一个单一的、标准的、统一的事件流接口

它的发展经历了哪些重要的里程碑或版本迭代?

  • 诞生与架构确立:输入子系统的创建本身就是最重要的里程碑。它确立了延续至今的三层核心架构:设备驱动 -> 输入核心 -> 事件处理器
  • evdev的普及:事件处理器(Event Handler)中的evdev(Event Device)成为了事实上的标准。它为每个输入设备创建了一个/dev/input/eventX设备文件,并提供了一个包含struct input_event的原始事件流。这使得用户空间(如X.Org, Wayland, libinput)有了一个稳定、通用的API。
  • 支持新设备类型:输入子系统的设计极具前瞻性。它不仅支持传统的键盘鼠标,还能轻松地扩展以支持新的设备类型,如触摸屏、触摸板、數位板、游戏手柄、加速计、旋轉編碼器等,而无需修改核心框架。
  • 多点触控(Multitouch)协议:为了支持现代智能手机和平板电脑的多点触摸屏,输入子系统标准化了一套多点触控(MT)协议,使得驱动程序可以用一种标准的方式上报多个触控点的信息。

目前该技术的社区活跃度和主流应用情况如何?

输入子系统是Linux内核中最成熟、最稳定、应用最广泛的子系统之一。

  • 社区活跃度:其核心代码(input.c)非常稳定,很少有大的改动。社区的开发活动主要集中在为层出不穷的新硬件编写新的设备驱动,以及改进libinput等用户空间库。
  • 主流应用:它是所有现代Linux系统的基石。
    • 桌面环境:X.Org和Wayland都通过libinput库与evdev接口交互,来获取所有输入事件。
    • Android:Android的整个输入框架(InputFlinger)都构建在内核输入子系统之上。
    • 嵌入式系统:任何带有按钮、触摸屏或键盘的嵌入式Linux设备,都在使用这个子系统。

核心原理与设计

它的核心工作原理是什么?

输入子系统的核心是一个分层的、事件驱动的“集线器-分发器”模型。

  1. 底层:设备驱动 (Device Drivers)

    • 这是与具体硬件打交道的一层(例如drivers/input/mouse/psmouse.c, drivers/hid/usbhid/)。
    • 驱动程序的唯一职责是:读取硬件产生的信号(如鼠标移动、按键按下),并将其翻译成标准的、与硬件无关的**struct input_event**事件。
    • 一个input_event结构体非常简单,只包含三个部分:type(事件类型,如EV_KEY按键, EV_REL相对位移), code(事件代码,如KEY_A, REL_X), 和 value(事件的值,如1表示按下,-5表示向左移动5个单位)。
    • 驱动程序通过调用input_report_*()函数(如input_report_key())将这些事件“提交”给输入核心,并通过input_sync()表示一批相关事件(如一次鼠标移动的X和Y值)已发送完毕。
  2. 中层:输入核心 (Input Core)

    • 这是整个子系统的心脏,主要由drivers/input/input.c实现。
    • 它维护着一个已注册的输入设备列表input_dev)和一个已注册的事件处理器列表input_handler)。
    • 当一个设备驱动注册自己时,输入核心会尝试将其与所有兼容的事件处理器进行“配对”和“连接”。
    • 它的主要工作是接收来自所有设备驱动的input_event事件,然后像一个交换机一样,将这些事件分发给所有与该设备连接的事件处理器。
  3. 上层:事件处理器 (Event Handlers)

    • 这是连接内核与用户空间的桥梁(例如drivers/input/evdev.c, mousedev.c)。
    • 最重要和最常用的是evdev。当它与一个输入设备连接后,会创建一个对应的字符设备文件,如/dev/input/event0
    • evdev从输入核心接收到事件时,它就把这些input_event结构体原封不动地写入到对应的设备文件中。
    • 用户空间的程序(如libinput)通过read()这个设备文件,就可以获得一个统一的、原始的、包含所有输入设备信息的事件流。

它的主要优势体现在哪些方面?

  • 终极的硬件抽象:应用程序开发者无需关心输入设备是USB的、蓝牙的还是PS/2的,他们面对的是完全相同的evdev接口。
  • 高度模块化:编写一个新的硬件驱动变得非常简单,因为它只需要关注如何生成标准的input_event,而无需关心如何与用户空间交互。
  • 灵活性和可组合性:一个物理设备可以上报多种类型的事件。例如,一个带有多媒体键的键盘,可以同时上报EV_KEY(普通按键)和EV_MSC(杂项)事件。
  • “机制,而非策略”:内核只负责可靠地传递原始事件(机制),而将所有复杂的解释和处理(策略),如手势识别、指针加速、掌压误触消除等,都留给了用户空间的库(如libinput)去实现。

它存在哪些已知的劣劣势、局限性或在特定场景下的不适用性?

  • 接口过于底层evdev提供的是非常原始的事件流,用户空间需要做大量的工作来解释它们,才能得到有意义的结果。但这通常被认为是一个优点,因为它保持了内核的简洁。
  • 不适用于非输入设备:它的设计完全是针对“人机交互”事件的。对于非交互式的传感器数据(如温度、气压),应该使用IIO(Industrial I/O)子系统。

使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?

在Linux系统中,任何需要处理旨在与用户或应用程序进行交互的事件的硬件,都必须通过输入子系统。它是唯一且标准的解决方案。

  • 图形用户界面(GUI):Wayland合成器或X Server通过libinput/dev/input/eventX读取事件,来移动鼠标指针、响应键盘输入、处理触摸屏手势。
  • 游戏:游戏程序直接或通过SDL等库读取游戏手柄、摇杆的事件,来控制游戏角色。
  • 嵌入式控制面板:一个带有物理按钮和旋钮的工业设备,其内核驱动将按钮的按下/弹起和旋钮的旋转转换为EV_KEYEV_REL事件,上层的控制程序读取这些事件来调整设备参数。

是否有不推荐使用该技术的场景?为什么?

  • 传感器数据采集:一个温度传感器周期性地产生测量值。这种数据不是“事件”,而是一种连续的测量。它应该使用IIO(Industrial I/O)子系统,该子系统专为处理这类传感器数据而设计。
  • 纯数据通信:一个用于数据传输的串口或USB端点,它传输的是通用的数据流,而不是结构化的用户输入事件。它应该使用TTY或USB核心提供的标准接口。

对比分析

请将其 与 其他相似技术 进行详细对比。

对比一:输入子系统 vs. IIO (Industrial I/O) 子系统

特性 输入子系统 (Input Subsystem) IIO (Industrial I/O) 子系统
设计目标 处理异步的、由用户触发的事件,用于人机交互。 处理传感器测量数据,通常是物理世界的量化值。
数据模型 事件驱动 (type, code, value),例如“A键按下”。 数据通道驱动,例如“通道0的值是25.3”(摄氏度)。
交互模式 用户产生输入,系统响应。 系统读取传感器,获取关于环境的信息。
用户空间接口 /dev/input/eventX /sys/bus/iio/devices/iio:deviceX/ (sysfs接口)
典型设备 键盘、鼠标、触摸屏、游戏手柄、按钮。 加速计、陀螺仪、温度传感器、光线传感器、ADC。
核心问题 “发生了什么?” “数值是多少?”

对比二:输入子系统 vs. “前输入子系统时代” (The Old Way)

特性 输入子系统 “前输入子系统时代”
内核驱动 驱动程序只需生成标准input_event,与Input Core对话。 驱动程序需要自己创建设备文件,并定义私有协议。
用户空间接口 统一的/dev/input/eventX,标准的read()接口。 混乱的设备文件 (/dev/psaux, /dev/ttyS0等),需要特定的库或代码来解析。
应用程序 只需支持evdev协议即可支持所有设备。 需要为不同类型的设备编写或配置不同的后端。
可维护性 。清晰的分层,模块化。 极低。紧耦合,代码重复,难以支持新硬件。

Input Subsystem Procfs Interface:提供设备与处理器的诊断视图

本代码片段是Linux Input子系统procfs接口的完整实现。其核心功能是在/proc/bus/input/目录下创建deviceshandlers两个虚拟文件。这两个文件为开发者和系统管理员提供了一个强大且人类可读的实时诊断工具,用于:1) 查看系统中所有已注册的输入设备(键盘、鼠标、触摸屏等)的详细属性、能力和当前状态;2) 列出所有可用的输入事件处理器(handlers),如evdev。这个接口是调试输入设备问题的关键入口点。

实现原理分析

该功能的实现完全基于内核的seq_file接口,这是一种为生成大型虚拟文件而设计的、高效且内存友好的标准机制。seq_file避免了一次性在内核中分配巨大缓冲区,而是通过迭代器的方式按需生成文件内容。

  1. Procfs文件创建 (input_proc_init):

    • 在子系统初始化时,此函数首先创建/proc/bus/input/目录。
    • 接着,它调用proc_create两次,分别创建deviceshandlers文件。关键在于,它将每个文件名与一个proc_ops结构体(input_devices_proc_opsinput_handlers_proc_ops)关联起来。
  2. 文件打开与Seq_file绑定:

    • 当用户空间程序(如cat /proc/bus/input/devices)打开这个文件时,VFS会调用proc_ops中的.proc_open方法(例如input_proc_devices_open)。
    • 这个open函数的核心工作是调用seq_open_private。此函数会分配一个seq_file实例,并将其与一个seq_operations结构体(例如input_devices_seq_ops)绑定。seq_file框架从此接管了对该文件的所有读操作。
  3. 迭代式内容生成:

    • 当用户read文件时,seq_file框架会调用seq_operations中定义的一系列回调函数来生成内容:
      a. .start: 被首次调用。它负责准备迭代,通常是锁定保护数据链表的互斥锁(input_mutex),并返回链表中的第一个(或指定位置的)元素。
      b. .next: 在处理完一个元素后被调用,用于获取链表中的下一个元素。
      c. .show: 这是核心函数,为.start.next返回的每一个元素生成其文本表示。例如,input_devices_seq_show会打印一个input_dev的所有属性。
      d. .stop: 在迭代完成或中断时被调用,负责清理工作,主要是释放互斥锁。
    • 这种“按需生成”的方式,使得即使有成百上千个输入设备,读取/proc文件也只会占用极小的内核内存。
  4. 数据一致性:

    • input_devices_seq_startinput_handlers_seq_start在开始迭代前都会获取全局的input_mutex。这个锁保护了input_dev_listinput_handler_list两个全局链表。这确保了从用户开始读取文件到读取结束的整个过程中,所看到的是一个一致的、不会被并发修改的设备和处理器快照。

代码分析

devices文件实现

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
// input_devices_seq_start: seq_file迭代器的start回调,用于/proc/bus/input/devices。
static void *input_devices_seq_start(struct seq_file *seq, loff_t *pos)
{
struct input_seq_state *state = seq->private;
int error;

// 以可中断的方式锁定全局input_mutex,以保护input_dev_list。
error = mutex_lock_interruptible(&input_mutex);
if (error) {
state->mutex_acquired = false;
return ERR_PTR(error);
}
state->mutex_acquired = true;

// 使用seq_list_start辅助函数开始遍历全局设备链表input_dev_list。
return seq_list_start(&input_dev_list, *pos);
}

// input_devices_seq_next: seq_file迭代器的next回调。
static void *input_devices_seq_next(struct seq_file *seq, void *v, loff_t *pos)
{
// 使用seq_list_next辅助函数获取链表中的下一个元素。
return seq_list_next(v, &input_dev_list, pos);
}

// input_seq_print_bitmap: 一个辅助函数,用于将能力位图格式化打印出来。
static void input_seq_print_bitmap(struct seq_file *seq, const char *name,
unsigned long *bitmap, int max)
{
// ... 实现细节 ...
// 它将一个大的位图(如keybit, evbit)转换成紧凑的十六进制字符串表示。
int i;
bool skip_empty = true;
char buf[18];

seq_printf(seq, "B: %s=", name);

for (i = BITS_TO_LONGS(max) - 1; i >= 0; i--) {
if (input_bits_to_string(buf, sizeof(buf),
bitmap[i], skip_empty)) {
skip_empty = false;
seq_printf(seq, "%s%s", buf, i > 0 ? " " : "");
}
}

/*
* If no output was produced print a single 0.
*/
if (skip_empty)
seq_putc(seq, '0');

seq_putc(seq, '\n');
}

// input_devices_seq_show: seq_file迭代器的show回调,为每个设备生成输出。
static int input_devices_seq_show(struct seq_file *seq, void *v)
{
struct input_dev *dev = container_of(v, struct input_dev, node);
const char *path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
struct input_handle *handle;

// 打印设备的ID信息。
seq_printf(seq, "I: Bus=%04x Vendor=%04x Product=%04x Version=%04x\n",
dev->id.bustype, dev->id.vendor, dev->id.product, dev->id.version);

// 打印设备名称、物理路径、sysfs路径和唯一ID。
seq_printf(seq, "N: Name=\"%s\"\n", dev->name ? dev->name : "");
seq_printf(seq, "P: Phys=%s\n", dev->phys ? dev->phys : "");
seq_printf(seq, "S: Sysfs=%s\n", path ? path : "");
seq_printf(seq, "U: Uniq=%s\n", dev->uniq ? dev->uniq : "");

// 遍历并打印所有连接到此设备的处理器(handler)。
seq_puts(seq, "H: Handlers=");
list_for_each_entry(handle, &dev->h_list, d_node)
seq_printf(seq, "%s ", handle->name);
seq_putc(seq, '\n');

// 打印设备的所有能力位图,如支持的属性、事件、按键、轴等。
input_seq_print_bitmap(seq, "PROP", dev->propbit, INPUT_PROP_MAX);
input_seq_print_bitmap(seq, "EV", dev->evbit, EV_MAX);
// ... (根据支持的事件类型,打印更详细的位图) ...

kfree(path);
return 0;
}

// input_devices_seq_ops: 将上述回调函数绑定到seq_operations结构体中。
static const struct seq_operations input_devices_seq_ops = {
.start = input_devices_seq_start,
.next = input_devices_seq_next,
.stop = input_seq_stop, // 通用的stop函数,见下文
.show = input_devices_seq_show,
};

handlers文件与通用逻辑实现

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
// input_seq_stop: 通用的stop回调,用于devices和handlers文件。
static void input_seq_stop(struct seq_file *seq, void *v)
{
struct input_seq_state *state = seq->private;

// 如果成功获取了锁,则释放它。
if (state->mutex_acquired)
mutex_unlock(&input_mutex);
}

// input_handlers_seq_start: /proc/bus/input/handlers的start回调。
static void *input_handlers_seq_start(struct seq_file *seq, loff_t *pos)
{
// ... 逻辑与devices的start类似,但遍历的是input_handler_list ...
return seq_list_start(&input_handler_list, *pos);
}

// input_handlers_seq_next: /proc/bus/input/handlers的next回调。
static void *input_handlers_seq_next(struct seq_file *seq, void *v, loff_t *pos)
{
// ... 逻辑与devices的next类似 ...
return seq_list_next(v, &input_handler_list, pos);
}

// input_handlers_seq_show: /proc/bus/input/handlers的show回调。
static int input_handlers_seq_show(struct seq_file *seq, void *v)
{
struct input_handler *handler = container_of(v, struct input_handler, node);
struct input_seq_state *state = seq->private;

// 打印处理器的编号、名称和一些属性。
seq_printf(seq, "N: Number=%u Name=%s", state->pos, handler->name);
if (handler->filter)
seq_puts(seq, " (filter)");
if (handler->legacy_minors)
seq_printf(seq, " Minor=%d", handler->minor);
seq_putc(seq, '\n');

return 0;
}

// input_handlers_seq_ops: 绑定handlers文件的seq_operations。
static const struct seq_operations input_handlers_seq_ops = {
.start = input_handlers_seq_start,
.next = input_handlers_seq_next,
.stop = input_seq_stop,
.show = input_handlers_seq_show,
};

Procfs文件注册

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
// input_proc_devices_open: "devices"文件的open回调。
static int input_proc_devices_open(struct inode *inode, struct file *file)
{
// 使用seq_open_private将文件与对应的seq_operations绑定。
return seq_open_private(file, &input_devices_seq_ops,
sizeof(struct input_seq_state));
}

// input_devices_proc_ops: 定义了"devices"文件的proc操作。
static const struct proc_ops input_devices_proc_ops = {
.proc_open = input_proc_devices_open,
.proc_read = seq_read, // 使用通用的seq_file读函数
.proc_lseek = seq_lseek, // 使用通用的seq_file lseek函数
.proc_release = seq_release_private, // 使用通用的seq_file close函数
};


// input_proc_handlers_open: "handlers"文件的open回调。
static int input_proc_handlers_open(struct inode *inode, struct file *file)
{
return seq_open_private(file, &input_handlers_seq_ops,
sizeof(struct input_seq_state));
}

// input_handlers_proc_ops: 定义了"handlers"文件的proc操作。
static const struct proc_ops input_handlers_proc_ops = {
.proc_open = input_proc_handlers_open,
.proc_read = seq_read,
.proc_lseek = seq_lseek,
.proc_release = seq_release_private,
};

Input Subsystem Initialization:为键盘、鼠标和触摸屏创建驱动框架

本代码片段是Linux内核Input子系统的核心初始化模块。其主要功能是在内核启动时,注册并建立Input子系统所需的所有基础软件设施。这包括创建一个名为input的设备类(class)、在/proc文件系统中建立调试接口,以及预留一个字符设备主设备号。它本身不驱动任何具体硬件,而是构建了一个所有具体输入设备驱动(如键盘、鼠标、触摸屏驱动)都必须依赖的、统一的、总线无关的框架。

实现原理分析

input_init函数的实现遵循了标准内核子系统的初始化模式,通过向不同的内核核心服务注册自身,来构建一个完整的功能层。

  1. 设备类注册 (class_register):

    • 它定义了一个struct class实例——input_classclass_register(&input_class)是第一步也是最关键的一步。此调用会在sysfs中创建/sys/class/input/目录。
    • 这个class的作用是为所有输入设备提供一个统一的视图,无论它们连接在哪个物理总线(USB, I2C, Serio等)上。当一个具体的输入设备驱动注册一个input_dev时,设备核心会自动在/sys/class/input/下创建一个符号链接指向该设备的物理路径,并创建一个以inputX命名的设备目录。
    • input_class还提供了一个.devnode回调函数——input_devnode。这个函数负责为每个输入设备动态生成它在/dev目录下的名字。例如,它会返回input/event0, input/mouse1等字符串,udev等用户空间工具会根据这些名字创建实际的设备节点。
  2. Procfs接口创建 (input_proc_init):

    • 此函数通过proc_mkdirproc_create/proc文件系统中创建/proc/bus/input/目录,以及deviceshandlers两个文件。
    • /proc/bus/input/devices: 这个文件提供了一个人类可读的列表,列出了当前系统中所有已注册的输入设备及其基本信息(ID,名称,物理路径等),是调试和系统诊断的重要工具。
    • /proc/bus/input/handlers: 这个文件列出了所有正在处理输入事件的“处理器”(handler),如evdev(事件设备处理器)、kbd(键盘处理器)等,以及它们与具体设备的连接关系。
  3. 字符设备区域注册 (register_chrdev_region):

    • 这是将输入事件传递给用户空间的核心机制。register_chrdev_region函数以INPUT_MAJOR为主设备号,预留了INPUT_MAX_CHAR_DEVICES个次设备号,并命名为”input”。
    • 这个操作确保了Input子系统拥有一个专属的字符设备号范围。当evdev等处理器与一个输入设备连接时,它会动态地分配一个未使用的次设备号,并使用cdev_add创建一个字符设备实例。这最终体现在用户空间就是我们熟悉的设备节点,如/dev/input/event0(主设备号为INPUT_MAJOR,次设备号为0)。用户空间程序(如X Server, Wayland, libinput)通过openread这些设备节点来接收底层的输入事件。

代码分析

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
// input_devnode: 为input类设备生成/dev下的设备节点名。
// @dev: 设备指针。
// @mode: 指向文件模式的指针(此函数不修改它)。
static char *input_devnode(const struct device *dev, umode_t *mode)
{
// 使用kasprintf动态分配内存并格式化字符串为 "input/<设备名>"。
// 例如,对于名为 "event0" 的设备,它会返回 "input/event0"。
// udev会据此在/dev目录下创建 /dev/input/event0。
return kasprintf(GFP_KERNEL, "input/%s", dev_name(dev));
}

// input_class: 定义了 "input" 设备类。
const struct class input_class = {
.name = "input", // 类的名字,对应/sys/class/input/
.devnode = input_devnode, // 指定创建设备节点名的回调函数。
};
// 导出input_class符号,使得输入设备驱动可以引用它。
EXPORT_SYMBOL_GPL(input_class);

// input_proc_init: 初始化input子系统的/proc接口。
static int __init input_proc_init(void)
{
struct proc_dir_entry *entry;

// 创建 /proc/bus/input/ 目录。
proc_bus_input_dir = proc_mkdir("bus/input", NULL);
if (!proc_bus_input_dir)
return -ENOMEM;

// 在/proc/bus/input/下创建 "devices" 文件。
entry = proc_create("devices", 0, proc_bus_input_dir,
&input_devices_proc_ops);
if (!entry)
goto fail1;

// 在/proc/bus/input/下创建 "handlers" 文件。
entry = proc_create("handlers", 0, proc_bus_input_dir,
&input_handlers_proc_ops);
if (!entry)
goto fail2;

return 0;

fail2: // 如果创建 "handlers" 失败,则清理 "devices"。
remove_proc_entry("devices", proc_bus_input_dir);
fail1: // 如果创建 "devices" 失败,则清理 "bus/input" 目录。
remove_proc_entry("bus/input", NULL);
return -ENOMEM;
}

// input_init: input子系统的总初始化函数。
static int __init input_init(void)
{
int err;

// 1. 向内核注册 "input" 设备类。
err = class_register(&input_class);
if (err) {
pr_err("unable to register input_dev class\n");
return err;
}

// 2. 初始化/proc接口。
err = input_proc_init();
if (err)
goto fail1;

// 3. 注册一个字符设备主设备号区域,用于创建/dev/input/*设备节点。
err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
INPUT_MAX_CHAR_DEVICES, "input");
if (err) {
pr_err("unable to register char major %d", INPUT_MAJOR);
goto fail2;
}

return 0;

fail2: // 如果注册字符设备失败,则清理/proc接口。
input_proc_exit();
fail1: // 如果/proc初始化失败,则注销设备类。
class_unregister(&input_class);
return err;
}

// input_exit: input子系统的退出/清理函数。
static void __exit input_exit(void)
{
// 以与初始化相反的顺序进行清理。
input_proc_exit();
unregister_chrdev_region(MKDEV(INPUT_MAJOR, 0),
INPUT_MAX_CHAR_DEVICES);
class_unregister(&input_class);
}

// 将input_init注册为子系统初始化调用。
subsys_initcall(input_init);
// 将input_exit注册为模块退出调用。
module_exit(input_exit);

输入子系统处理器管理:input_register_handler 与 input_unregister_handler

本代码片段展示了 Linux 内核输入子系统(Input Subsystem)的核心注册机制input_register_handler 允许一个新的事件处理器(如键盘驱动、鼠标驱动或 SysRq 处理器)向子系统注册自己,并自动发现并连接到所有与其兼容的输入设备。input_unregister_handler 则执行相反的操作,安全地断开所有连接并将处理器从系统中移除。这一对函数是实现输入子系统“可插拔、自动匹配”架构的基石。

实现原理分析

此机制的核心是两个全局链表(input_handler_listinput_dev_list)以及一个全局互斥锁(input_mutex),通过它们来协调处理器与设备之间的 N:M 匹配关系。

  1. 注册过程 (input_register_handler):

    • 全局锁定: 函数的核心逻辑被 scoped_cond_guard(mutex_intr, ..., &input_mutex) 包围。这是一个基于 RAII 风格的、可被信号中断的互斥锁获取。它确保了在整个注册过程中,没有其他任务可以并发地添加/删除设备或处理器,从而保证了全局链表的完整性。
    • 加入链表: list_add_tail(&handler->node, &input_handler_list) 将新的处理器添加到全局处理器链表的尾部。这样,当未来有新设备注册时,输入子系统就能找到这个新的处理器。
    • 初始匹配与连接: 最关键的一步是遍历现有的全局设备链表 input_dev_list。对于每一个已存在的设备 dev,调用 input_attach_handler(dev, handler)。这个函数会检查设备的 ID 是否与处理器的 id_table 匹配。如果匹配,就会调用处理器的 .connect 回调(如前文的 sysrq_connect)来建立连接(即创建一个 input_handle)。
    • 通知机制: input_wakeup_procfs_readers() 用于唤醒任何正在读取 /proc/bus/input/handlers 等文件的进程,告知它们系统状态已改变。
  2. 注销过程 (input_unregister_handler):

    • 全局锁定: 同样使用 guard(mutex)(&input_mutex) 来获取全局锁,保证操作的原子性。
    • 断开连接: 处理器内部维护了一个 h_list,记录了所有与其连接的 input_handle。函数遍历这个链表,并对每一个 handle 调用 handler->disconnect 回调(如 sysrq_disconnect)。这个回调负责清理特定于连接的资源(如私有数据、定时器等)。
    • 健全性检查: WARN_ON(!list_empty(&handler->h_list)) 是一个断言。理论上,在所有 disconnect 调用完成后,h_list 应该为空。如果不为空,说明存在资源泄漏或逻辑错误。
    • 移除链表: list_del_init(&handler->node) 将处理器从全局处理器链表中物理移除。

代码分析

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
103
static struct proc_dir_entry *proc_bus_input_dir;
static DECLARE_WAIT_QUEUE_HEAD(input_devices_poll_wait);
static int input_devices_state;

static inline void input_wakeup_procfs_readers(void)
{
input_devices_state++;
wake_up(&input_devices_poll_wait);
}

static int input_handler_check_methods(const struct input_handler *handler)
{
int count = 0;

if (handler->filter)
count++;
if (handler->events)
count++;
if (handler->event)
count++;

if (count > 1) {
pr_err("%s: only one event processing method can be defined (%s)\n",
__func__, handler->name);
return -EINVAL;
}

return 0;
}

/**
* @brief input_register_handler - 注册一个新的输入处理器。
* @param handler: 指向要注册的 input_handler 结构体的指针。
* @return int: 成功返回0,被信号中断返回 -EINTR,验证失败返回其他错误码。
*
* 此函数将新的处理器添加到系统中,并自动将其连接到所有
* 目前已存在且与其兼容的输入设备。
*/
int input_register_handler(struct input_handler *handler)
{
struct input_dev *dev;
int error;

// 检查处理器定义的方法是否合法。
error = input_handler_check_methods(handler);
if (error)
return error;

/*
* 使用 scoped_cond_guard 尝试获取全局的 input_mutex。
* mutex_intr 表示获取是一个可被信号中断的操作。
* 如果被中断,则直接返回 -EINTR。
* 如果成功获取,大括号内的代码将被执行,并在退出作用域时自动释放锁。
*/
scoped_cond_guard(mutex_intr, return -EINTR, &input_mutex) {
// 初始化处理器内部用于维护已连接 handle 的链表头。
INIT_LIST_HEAD(&handler->h_list);

// 将处理器添加到全局处理器链表的尾部。
list_add_tail(&handler->node, &input_handler_list);

// 遍历所有已注册的输入设备。
list_for_each_entry(dev, &input_dev_list, node)
// 尝试将新的处理器附着到每个设备上 (如果匹配)。
input_attach_handler(dev, handler);

// 唤醒任何正在等待 procfs 信息更新的任务。
input_wakeup_procfs_readers();
}

return 0;
}
EXPORT_SYMBOL(input_register_handler);

/**
* @brief input_unregister_handler - 注销一个输入处理器。
* @param handler: 指向要注销的 input_handler 结构体的指针。
*
* 此函数将断开处理器与所有输入设备的连接,并将其从系统的已知处理器列表中移除。
*/
void input_unregister_handler(struct input_handler *handler)
{
struct input_handle *handle, *next;

// 使用 guard 获取全局的 input_mutex,保证操作原子性。
// 锁会在函数返回时自动释放。
guard(mutex)(&input_mutex);

// 安全地遍历处理器当前连接的所有 handle 列表。
list_for_each_entry_safe(handle, next, &handler->h_list, h_node)
// 调用处理器的 disconnect 回调来断开每个连接并清理资源。
handler->disconnect(handle);

// 断言:在断开所有连接后,handle 列表应该为空。
WARN_ON(!list_empty(&handler->h_list));

// 将处理器从全局处理器链表中物理移除。
list_del_init(&handler->node);

// 唤醒 procfs 读取者。
input_wakeup_procfs_readers();
}
EXPORT_SYMBOL(input_unregister_handler);

输入设备与处理器的匹配与连接:input_attach_handler, input_match_device, input_match_device_id

本代码片段是 Linux 输入子系统“即插即用”特性的核心匹配与连接引擎。其主要功能是:当一个新的设备或处理器出现时,提供一套分层的、基于规则的机制来判断一个输入处理器(handler)是否与一个输入设备(dev)兼容。如果兼容,input_attach_handler 就会调用该处理器的 .connect 回调函数,建立一个正式的连接input_handle),从而使事件可以从设备流向处理器。

实现原理分析

此机制的设计体现了清晰的层次化和高度的灵活性,通过将匹配逻辑分解为多个函数来实现。

  1. 最底层的原子匹配 (input_match_device_id):

    • 这是最基础的匹配函数。它只负责比较一个具体的设备 (dev) 和一条具体的匹配规则 (id)。
    • 基于 flags 的多维度匹配: id->flags 是一个位掩码,它决定了这条规则需要检查哪些字段。这使得规则可以非常灵活,可以只匹配总线类型,也可以同时匹配厂商、产品ID等。
    • 能力子集检查 (bitmap_subset): 这是最强大的一部分。它检查 id 中定义的能力位图是否是 dev 提供的能力位图的子集。例如,sysrq_handlerid 要求设备必须支持 EV_KEY 事件和 KEY_LEFTALT 按键。bitmap_subset(id->keybit, dev->keybit, ...) 就会检查设备的 keybit 是否包含了 id->keybit 中设置的所有位。这确保了处理器只会被连接到那些至少提供了其所需全部功能的设备上。
  2. 处理器级的规则迭代 (input_match_device):

    • 这个函数负责将一个处理器与其设备进行匹配。
    • 遍历规则表: 它通过一个 for 循环,遍历 handler->id_table 数组中的每一条 input_device_id 规则。
    • 两阶段匹配:
      a. 它首先调用 input_match_device_id 进行静态的、基于 ID 的匹配。
      b. 如果静态匹配成功,它还会进行一个可选的动态匹配!handler->match || handler->match(...)。处理器可以提供一个自定义的 .match 函数,用于执行更复杂的、无法用静态 id 结构描述的匹配逻辑。
    • “首个匹配”原则: 一旦找到第一个同时满足静态和(可选的)动态匹配的规则,循环就会立即停止,并返回指向该规则的指针。
  3. 顶层的连接调度 (input_attach_handler):

    • 这是由 input_register_handler(当新处理器注册时)或 input_register_device(当新设备注册时)调用的顶层函数。
    • 调度逻辑:
      a. 它首先调用 input_match_device 来判断是否存在匹配。
      b. 如果返回 NULL(不匹配),则直接返回 -ENODEV
      c. 如果找到了匹配的 id,它就调用 handler->connect(handler, dev, id),将控制权交给处理器,由处理器自己来完成最终的连接操作(创建 input_handle 等)。
    • 错误处理: 它会检查 .connect 的返回值。返回 -ENODEV 被认为是一种“优雅的失败”(例如,connect 函数内部进行了更精细的检查后决定不连接),而其他错误则被认为是真正的失败并会被打印到内核日志中。

代码分析

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
/**
* @brief input_match_device_id - 检查一个设备是否匹配一个特定的设备ID规则。
* @param dev: 要检查的输入设备。
* @param id: 要应用的匹配规则。
* @return bool: 如果匹配则返回 true,否则返回 false。
*/
bool input_match_device_id(const struct input_dev *dev,
const struct input_device_id *id)
{
// 检查总线类型是否匹配 (如果规则要求)。
if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
if (id->bustype != dev->id.bustype)
return false;

// 检查厂商ID是否匹配 (如果规则要求)。
if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
if (id->vendor != dev->id.vendor)
return false;

// 检查产品ID是否匹配 (如果规则要求)。
if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
if (id->product != dev->id.product)
return false;

// 检查版本号是否匹配 (如果规则要求)。
if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
if (id->version != dev->id.version)
return false;

// 检查设备的能力位图是否包含了规则要求的所有能力。
// bitmap_subset(A, B) 检查 A 是否是 B 的子集。
if (!bitmap_subset(id->evbit, dev->evbit, EV_MAX) ||
!bitmap_subset(id->keybit, dev->keybit, KEY_MAX) ||
!bitmap_subset(id->relbit, dev->relbit, REL_MAX) ||
!bitmap_subset(id->absbit, dev->absbit, ABS_MAX) ||
!bitmap_subset(id->mscbit, dev->mscbit, MSC_MAX) ||
!bitmap_subset(id->ledbit, dev->ledbit, LED_MAX) ||
!bitmap_subset(id->sndbit, dev->sndbit, SND_MAX) ||
!bitmap_subset(id->ffbit, dev->ffbit, FF_MAX) ||
!bitmap_subset(id->swbit, dev->swbit, SW_MAX) ||
!bitmap_subset(id->propbit, dev->propbit, INPUT_PROP_MAX)) {
return false;
}

// 所有检查都通过,则匹配成功。
return true;
}
EXPORT_SYMBOL(input_match_device_id);

/**
* @brief input_match_device - 查找一个处理器中与给定设备匹配的第一个ID规则。
* @param handler: 包含ID规则表的输入处理器。
* @param dev: 要匹配的输入设备。
* @return const struct input_device_id*: 成功则返回匹配的ID规则指针,否则返回 NULL。
*/
static const struct input_device_id *input_match_device(struct input_handler *handler,
struct input_dev *dev)
{
const struct input_device_id *id;

// 遍历处理器的ID规则表,直到遇到一个 flags 为0的结束条目。
for (id = handler->id_table; id->flags; id++) {
// 如果设备匹配当前ID规则,并且...
if (input_match_device_id(dev, id) &&
// ...处理器没有自定义的 match 函数,或者自定义的 match 函数也返回成功...
(!handler->match || handler->match(handler, dev))) {
// ...则返回这个匹配的ID。
return id;
}
}

// 遍历完所有规则都未找到匹配项。
return NULL;
}

/**
* @brief input_attach_handler - 尝试将一个处理器附着到一个设备上。
* @param dev: 目标输入设备。
* @param handler: 要附着的输入处理器。
* @return int: 成功则返回0或正数,不匹配返回-ENODEV,连接失败返回其他负数错误码。
*/
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
const struct input_device_id *id;
int error;

// 调用匹配函数,检查设备和处理器是否兼容。
id = input_match_device(handler, dev);
if (!id)
return -ENODEV; // 如果不匹配,则返回 "No such device"。

// 如果匹配,则调用处理器的 connect 回调函数来建立连接。
error = handler->connect(handler, dev, id);
// 如果 connect 失败,但不是因为 ENODEV (即不是优雅的拒绝连接),则打印错误信息。
if (error && error != -ENODEV)
pr_err("failed to attach handler %s to device %s, error: %d\n",
handler->name, kobject_name(&dev->dev.kobj), error);

return error;
}

输入设备关闭与资源释放:input_close_device 与 __input_release_device

本代码片段展示了 Linux 内核输入子系统中用于关闭设备句柄(handle)并释放相关资源的核心函数。其主要功能是:为输入处理器(handler)提供一个标准的 input_close_device 接口,用于表示它不再希望接收来自某个设备的事件。这个函数通过精细的引用计数管理和 RCU 同步,安全地拆除设备与处理器之间的连接,并在最后一个使用者关闭设备时,触发底层物理设备的关闭(例如停止轮询或断电)。

实现原理分析

此机制是输入子系统生命周期管理的“后半段”,其设计的核心是多级引用计数RCU 同步屏障,以确保在高度并发的事件处理环境中,资源可以被安全、无误地释放。

  1. 两级引用计数:

    • handle->open: 这是一个 per-handle 的引用计数。一个 handle(代表一个设备和一个处理器的连接)可能被“打开”多次(例如,evdev 处理器允许用户空间多次 open 同一个 /dev/input/eventX 文件)。input_close_device 每被调用一次,handle->open 就减一。
    • dev->users: 这是一个 per-device 的引用计数。它只统计那些“主动”的使用者(即 handler->passive_observerfalse 的处理器)。当 dev->users 从 1 降为 0 时,意味着没有任何主动处理器再需要这个设备了。
  2. 分阶段的资源释放:

    • input_close_device 的逻辑是分阶段的:
      a. 首先,它调用 __input_release_device 来处理“抓取”(grab)的释放。如果当前 handle 正独占性地“抓取”设备,这个抓取会被释放,并可能通知其他等待的 handle。
      b. 然后,它递减 dev->users 计数。如果计数降为 0,并且设备未被抑制(inhibited),它就会关闭物理设备:停止轮询 (input_dev_poller_stop) 并调用设备驱动提供的 .close 回调。
      c. 最后,它递减 handle->open 计数。
  3. RCU 同步屏障 (synchronize_rcu):

    • 这是此机制中最关键的安全保障。输入事件的分发路径(input_pass_values 等)通常是在 RCU 读侧临界区(rcu_read_lock)中执行的。这意味着,即使 input_close_device 正在执行,也可能有其他 CPU 或被抢占的任务正在处理一个通过当前 handle 分发的事件。
    • synchronize_rcu() 是一个阻塞函数,它会等待,直到在它被调用之前开始的所有 RCU 读侧临界区全部结束
    • 它被用在两个地方:
      a. 在释放 grab 后:确保所有事件分发代码都看到了 grab 已经被释放。
      b. 在 handle->open 降为 0 后:确保在 handle 可能被释放(由上层调用者 disconnect 函数)之前,绝对没有任何事件处理代码还在使用这个 handle。这从根本上杜绝了 use-after-free 的风险。

代码分析

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
/**
* @brief __input_release_device - 释放一个 handle 对设备的 "抓取" (grab)。
* @param handle: 正在被关闭的 input_handle。
*/
static void __input_release_device(struct input_handle *handle)
{
struct input_dev *dev = handle->dev;
struct input_handle *grabber;

// 在 dev->mutex 锁的保护下,安全地解引用 RCU 保护的 dev->grab 指针。
grabber = rcu_dereference_protected(dev->grab,
lockdep_is_held(&dev->mutex));
// 如果当前 handle 正是那个独占性抓取设备的 handle...
if (grabber == handle) {
// ...则将 grab 指针原子地设为 NULL,释放抓取。
rcu_assign_pointer(dev->grab, NULL);
/* 确保 input_pass_values() 能看到 grab 已经消失 */
synchronize_rcu(); // 等待所有现存的 RCU 读临界区结束。

// 遍历设备的所有 handle。
list_for_each_entry(handle, &dev->h_list, d_node)
// 如果某个 handle 是打开的,并且其处理器有关联的 start 方法...
if (handle->open && handle->handler->start)
// ...则调用 start 方法,通知它现在可以开始接收事件了。
handle->handler->start(handle);
}
}

/**
* @brief input_dev_poller_stop - 停止一个输入设备的轮询器。
* @param poller: 指向设备轮询器的指针。
*/
void input_dev_poller_stop(struct input_dev_poller *poller)
{
// 同步地取消轮询工作队列。如果工作正在运行,则会等待其完成。
cancel_delayed_work_sync(&poller->work);
}

/**
* @brief input_close_device - 关闭一个输入设备句柄。
* @param handle: 正在被关闭的 input_handle。
* @note 此函数应由输入处理器调用,当它希望停止接收来自给定设备的事件时。
*/
void input_close_device(struct input_handle *handle)
{
struct input_dev *dev = handle->dev;

// 使用 RAII 风格的 guard 获取设备互斥锁,保证整个函数的原子性。
guard(mutex)(&dev->mutex);

// 处理可能的 "抓取" 释放。
__input_release_device(handle);

// 如果这个 handle 不是一个“被动观察者”...
if (!handle->handler->passive_observer) {
// ...则将设备的主动使用者计数减一。如果计数降为0,并且设备未被抑制...
if (!--dev->users && !dev->inhibited) {
// ...则关闭物理设备:停止轮询器 (如果存在)...
if (dev->poller)
input_dev_poller_stop(dev->poller);
// ...并调用设备驱动提供的 close 回调 (如果存在)。
if (dev->close)
dev->close(dev);
}
}

// 将此 handle 的打开计数减一。如果计数降为0...
if (!--handle->open) {
/*
* synchronize_rcu() 确保 input_pass_values() 已完成,
* 并且没有更多的输入事件会通过此 handle 传递。
*/
synchronize_rcu();
}
}
EXPORT_SYMBOL(input_close_device);

输入事件处理与分发流水线:input_event, input_handle_event, input_event_dispose

本代码片段展示了 Linux 内核输入子系统的核心事件处理流水线。其主要功能是:为设备驱动提供一个标准的 input_event 接口来报告硬件产生的输入事件。这一系列函数通过一个多阶段的、受锁保护的流程,对事件进行预处理、缓冲和最终分发input_event 是外部入口,input_handle_event 是核心处理逻辑,而 input_event_dispose 则是最终的执行与分发单元。

实现原理分析

此机制是输入子系统的心脏,其设计的核心在于将来自不同驱动、可能以极高频率产生的事件,高效、安全地送达上层的事件处理器。

  1. 统一入口与锁定 (input_event):

    • input_event 是所有输入设备驱动调用的标准 API。
    • 能力检查: 它的第一步是 is_event_supported(...)。这是一个关键的过滤,它会检查设备在注册时是否声明过自己能够产生这种类型的事件。这可以防止驱动发送无效或意外的事件。
    • 核心锁定: guard(spinlock_irqsave)(&dev->event_lock) 获取一个 per-device 的自旋锁并禁用中断。这个锁是至关重要的,它确保了在处理一个设备的事件流水线时,不会被该设备产生的另一个中断所打断,也不会被来自其他 CPU 的调用并发访问,从而保证了事件处理的序列化和原子性
  2. 事件预处理与决策 (input_handle_event):

    • 获取处置(Disposition): input_get_disposition(dev, type, code, &value) 是一个关键的预处理步骤。它会根据事件类型和设备当前的状态(例如,对于 ABS 绝对坐标轴,它会过滤掉与上次值相同的重复事件),决定这个事件应该如何被处理。disposition 是一个掩码,可能包含以下标志:
      • INPUT_PASS_TO_HANDLERS: 事件应被传递给上层处理器。
      • INPUT_PASS_TO_DEVICE: 事件应被传递回设备驱动自己的 .event 回调(用于特殊设备,如力反馈手柄)。
      • INPUT_FLUSH: 这是一个 EV_SYN 事件,表示一批事件已结束,需要“刷新”(flush)。
    • 贡献随机数: add_input_randomness(...) 将事件的时间戳、类型和码值作为熵源,贡献给内核的随机数生成器。
  3. 缓冲与分发 (input_event_dispose):

    • 这是事件流水线的最后一站。它不是立即将事件传递出去,而是将其缓冲在一个 per-device 的小数组 dev->vals 中。
    • 缓冲: 对于非 EV_SYN 事件,它只是简单地将 type, code, value 存入 dev->vals 数组,并递增 dev->num_vals 计数器。
    • 刷新(Flush): 缓冲的事件在两种情况下会被“刷新”:
      a. 当收到一个 disposition 包含 INPUT_FLUSHEV_SYN 事件时。
      b. 当缓冲区 dev->vals 即将填满时 (dev->num_vals >= dev->max_vals - 2),它会自动在末尾添加一个 EV_SYN 事件,然后强制刷新。
    • 最终分发: 刷新操作通过调用 input_pass_values(dev, dev->vals, dev->num_vals) 来完成。这个函数(未在此处展示)会在 RCU 读锁的保护下,遍历 dev->h_list(所有连接到此设备的 handle),并将缓冲的事件数组传递给每个 handle 对应的处理器的 .filter.event 回调。

代码分析

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/**
* @brief input_event_dispose - 根据处置指令处理并分发事件。
* @param dev: 输入设备。
* @param disposition: 事件的处置掩码。
* @param type: 事件类型。
* @param code: 事件代码。
* @param value: 事件值。
*/
static void input_event_dispose(struct input_dev *dev, int disposition,
unsigned int type, unsigned int code, int value)
{
// 如果需要将事件传递回设备自己的回调...
if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
dev->event(dev, type, code, value);

// 如果需要将事件传递给上层处理器...
if (disposition & INPUT_PASS_TO_HANDLERS) {
struct input_value *v;

// 如果是多点触摸的 SLOT 事件,先添加一个 SLOT 变更事件。
if (disposition & INPUT_SLOT) {
v = &dev->vals[dev->num_vals++];
v->type = EV_ABS;
v->code = ABS_MT_SLOT;
v->value = dev->mt->slot;
}

// 将当前事件缓冲到 dev->vals 数组中。
v = &dev->vals[dev->num_vals++];
v->type = type;
v->code = code;
v->value = value;
}

// 如果需要刷新缓冲区 (通常是收到了 EV_SYN 事件)...
if (disposition & INPUT_FLUSH) {
if (dev->num_vals >= 2) // 至少有一个事件和一个 SYN
// ...则将缓冲区中的所有事件传递给处理器。
input_pass_values(dev, dev->vals, dev->num_vals);
dev->num_vals = 0; // 清空缓冲区计数器。
/*
* 在刷新时重置时间戳,这样我们就不会得到一个过时的戳。
*/
dev->timestamp[INPUT_CLK_MONO] = ktime_set(0, 0);
} else if (dev->num_vals >= dev->max_vals - 2) { // 如果缓冲区即将溢出...
// ...则自动添加一个 SYN 事件,并强制刷新。
dev->vals[dev->num_vals++] = input_value_sync;
input_pass_values(dev, dev->vals, dev->num_vals);
dev->num_vals = 0;
}
}

/**
* @brief input_handle_event - 输入事件的核心处理逻辑 (在锁内调用)。
* @param dev: 输入设备。
* @param type: 事件类型。
* @param code: 事件代码。
* @param value: 事件值。
*/
void input_handle_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
int disposition;

// 确保调用时已持有 event_lock。
lockdep_assert_held(&dev->event_lock);

// 获取事件的处置指令 (过滤、传递、刷新等)。
disposition = input_get_disposition(dev, type, code, &value);
if (disposition != INPUT_IGNORE_EVENT) { // 如果事件不应被忽略...

// 对于非同步事件,将其信息贡献给内核随机数池。
if (type != EV_SYN)
add_input_randomness(type, code, value);

// 调用下一阶段的处理和分发函数。
input_event_dispose(dev, disposition, type, code, value);
}
}

/**
* @brief input_event - 报告一个新的输入事件。
* @param dev: 产生事件的设备。
* @param type: 事件类型。
* @param code: 事件代码。
* @param value: 事件值。
* @note 这是输入设备驱动应该使用的标准 API。
*/
void input_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
// 检查设备是否声明过支持此类型的事件。
if (is_event_supported(type, dev->evbit, EV_MAX)) {
// 获取设备的自旋锁并禁用中断。
guard(spinlock_irqsave)(&dev->event_lock);
// 调用核心处理逻辑。
input_handle_event(dev, type, code, value);
}
}
EXPORT_SYMBOL(input_event);

/**
* @brief input_inject_event - 从输入处理器注入一个事件。
* @param handle: 要通过其注入事件的输入句柄。
* @param type: 事件类型。
* @param code: 事件代码。
* @param value: 事件值。
* @note 与 input_event() 类似,但会检查设备的 "抓取" (grab) 状态。
*/
void input_inject_event(struct input_handle *handle,
unsigned int type, unsigned int code, int value)
{
struct input_dev *dev = handle->dev;
struct input_handle *grab;

if (is_event_supported(type, dev->evbit, EV_MAX)) {
guard(spinlock_irqsave)(&dev->event_lock);
guard(rcu)(); // 进入 RCU 读临界区。

// 获取当前抓取设备的 handle。
grab = rcu_dereference(dev->grab);
// 如果没有设备被抓取,或者抓取者就是当前 handle,则允许注入。
if (!grab || grab == handle)
input_handle_event(dev, type, code, value);
}
}
EXPORT_SYMBOL(input_inject_event);

输入事件分发流水线与自动重复控制:input_pass_values

本代码片段展示了 Linux 内核输入子系统事件处理流水线的**“分发”阶段**。其核心功能是:接收一个已缓冲的事件数组 (vals),并以一种 RCU 安全的方式,将其广播给所有已连接并打开的事件处理器(handler)。它实现了两个关键特性:一是**独占性“抓取”(grab)模式,允许一个处理器临时垄断所有事件;二是集成的按键自动重复(auto-repeat)**逻辑,在事件分发后,根据按键的按下和释放来启动或停止自动重复定时器。

实现原理分析

input_pass_valuesinput_event 流水线的核心,是连接事件产生和事件消费的桥梁。其实现原理精妙地结合了 RCU 无锁读取、可选的独占模式以及内置的状态机(用于自动重复)。

  1. RCU 安全的处理器遍历:

    • 执行上下文: 函数注释和 lockdep_assert_held 明确指出,此函数必须在持有 dev->event_lock(一个禁用了中断的自旋锁)的情况下被调用。这保证了事件缓冲区的完整性。
    • 无锁读取: 整个事件分发循环被 scoped_guard(rcu) 包围,并使用 rcu_dereferencelist_for_each_entry_rcu。这是一个关键的性能设计。它允许 input_pass_values不获取任何会与处理器连接/断开操作冲突的锁的情况下,安全地遍历设备的所有句柄(dev->h_list)。处理器(handler)的注册和注销(input_register/unregister_handler)可以在其他 CPU 上并发进行,它们会使用 synchronize_rcu() 来等待所有像 input_pass_values 这样的“读者”完成,然后才安全地释放内存。
  2. 独占抓取(Grab)模式:

    • handle = rcu_dereference(dev->grab): 这是循环的第一步。它检查设备是否被某个 handle“抓取”了。
    • 如果 dev->grab 非空,事件数组只会被发送给这个唯一的 grabber,然后函数立即退出分发循环。这实现了一种独占访问模式,常用于虚拟终端(VT)切换或某些游戏手柄的特殊模式,以确保在特定状态下,键盘输入只被一个特定的处理器接收。
  3. 事件过滤链(Filter Chain):

    • count = handle->handle_events(handle, vals, count): 每个 handle 的回调函数 handle_events(对于 sysrq_handler,它就是 sysrq_filter 的一个包装)可以返回它未处理的事件数量。
    • if (!count) break;: 如果一个处理器处理(“消费”)了所有的事件,它会返回 0,分发循环会立即中止。这实现了一个过滤链:在 dev->h_list 中位置靠前的处理器有优先权,可以阻止事件被后续的处理器看到。
  4. 集成的自动重复逻辑:

    • 时机: 自动重复逻辑在事件分发给所有处理器之后执行。它只处理那些未被任何过滤器消费掉EV_KEY 事件。
    • input_start_autorepeat: 当一个“按下”(v->value == 1)事件被检测到时,此函数被调用。它会设置一个定时器,该定时器将在 dev->rep[REP_DELAY] 毫秒后到期。
    • input_stop_autorepeat: 当一个“释放”(v->value == 0)事件被检测到时,此函数被调用,它会立即删除(取消)任何正在等待的自动重复定时器。
    • 定时器回调: 当 REP_DELAY 定时器到期时,dev->timer.function 会被调用。这个回调函数(未在此处展示)会生成一个值为 2(重复)的 EV_KEY 事件,并再次调用 input_event 将其注入流水线。然后,它会重新设置一个 dev->rep[REP_PERIOD] 毫秒的定时器,以实现连续的按键重复。

代码分析

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
/**
* @brief input_start_autorepeat - 启动一个按键的自动重复。
* @param dev: 输入设备。
* @param code: 要重复的按键码。
*/
static void input_start_autorepeat(struct input_dev *dev, int code)
{
// 检查设备是否支持自动重复(EV_REP),并且重复周期和延迟都已设置,
// 并且定时器回调函数也已设置。
if (test_bit(EV_REP, dev->evbit) &&
dev->rep[REP_PERIOD] && dev->rep[REP_DELAY] &&
dev->timer.function) {
// 记录要重复的按键码。
dev->repeat_key = code;
// 修改(启动)定时器,使其在 REP_DELAY 毫秒后超时。
mod_timer(&dev->timer,
jiffies + msecs_to_jiffies(dev->rep[REP_DELAY]));
}
}

/**
* @brief input_stop_autorepeat - 停止按键的自动重复。
* @param dev: 输入设备。
*/
static void input_stop_autorepeat(struct input_dev *dev)
{
// 删除(取消)任何正在等待的自动重复定时器。
timer_delete(&dev->timer);
}

/*
* 首先将值传递给所有过滤器,然后,如果事件未被过滤掉,
* 再传递给所有打开的句柄。
*
* 此函数在持有 dev->event_lock 并禁用中断的情况下被调用。
*/
/**
* @brief input_pass_values - 将一批缓冲的事件分发给处理器。
* @param dev: 输入设备。
* @param vals: 指向事件值数组的指针。
* @param count: 数组中的事件数量。
*/
static void input_pass_values(struct input_dev *dev,
struct input_value *vals, unsigned int count)
{
struct input_handle *handle;
struct input_value *v;

// 确保调用时已持有 event_lock。
lockdep_assert_held(&dev->event_lock);

// 进入 RCU 读侧临界区,以安全地遍历 dev->h_list。
scoped_guard(rcu) {
// 检查是否有 handle "抓取" 了设备。
handle = rcu_dereference(dev->grab);
if (handle) {
// 如果有,则只将事件发送给这个 grabber。
count = handle->handle_events(handle, vals, count);
break; // 立即退出循环。
}

// RCU 安全地遍历设备的所有 handle。
list_for_each_entry_rcu(handle, &dev->h_list, d_node) {
// 只将事件发送给已打开的 handle。
if (handle->open) {
// 调用 handle 的事件处理函数,并更新剩余事件数。
count = handle->handle_events(handle, vals,
count);
// 如果所有事件都已被处理,则提前退出循环。
if (!count)
break;
}
}
}

/* 为按键事件触发自动重复 */
if (test_bit(EV_REP, dev->evbit) && test_bit(EV_KEY, dev->evbit)) {
// 遍历所有未被过滤器消耗的事件。
for (v = vals; v != vals + count; v++) {
// 只关心非重复的按键事件。
if (v->type == EV_KEY && v->value != 2) {
if (v->value) // 如果是“按下”事件...
input_start_autorepeat(dev, v->code);
else // 如果是“释放”事件...
input_stop_autorepeat(dev);
}
}
}
}

input_handle_events_default / input_handle_events_filter / input_handle_events_null / input_handle_setup_event_handler / input_register_handle:输入事件分发策略选择与 handle 注册挂接

input_handle_events_default:逐个事件调用 handler->event

作用与原理:将批量事件数组 vals[0..count-1] 逐个展开为单事件回调,调用 handler->event(handle, type, code, value)。适用于 handler 只实现了单事件接口 event 的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @brief 默认事件处理:逐个调用 input_handler::event。
*
* @param handle 当前 input_handle。
* @param vals 事件数组(input_value)。
* @param count 事件数量。
* @return 实际处理/消费的事件数量(此实现恒等于 count)。
*/
static unsigned int input_handle_events_default(struct input_handle *handle,
struct input_value *vals,
unsigned int count)
{
struct input_handler *handler = handle->handler; /**< 事件归属的处理器,提供 event/filter/events 等回调。 */
struct input_value *v; /**< 遍历用指针:依次指向 vals 中的每个事件。 */

for (v = vals; v != vals + count; v++) /**< 指针区间遍历:避免额外索引计算。 */
handler->event(handle, v->type, v->code, v->value); /**< 单事件分发:由 handler 实现具体语义。 */

return count;
}

input_handle_events_filter:逐个调用 handler->filter,并原地压缩数组

作用与原理:对每个事件调用 handler->filter(...);若返回“需要过滤”,则丢弃该事件;否则把事件写回到 end 指向的位置,从而在原数组中保持 未过滤事件的相对顺序 并实现原地压缩。返回值是压缩后的事件数。

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
/**
* @brief 过滤事件处理:逐个调用 input_handler::filter,并在 vals 数组内原地移除被过滤事件。
*
* @param handle 当前 input_handle。
* @param vals 输入事件数组;返回时其前 N 项为未过滤事件。
* @param count 输入事件数量。
* @return 未被过滤的事件数量(即压缩后的长度)。
*/
static unsigned int input_handle_events_filter(struct input_handle *handle,
struct input_value *vals,
unsigned int count)
{
struct input_handler *handler = handle->handler; /**< 提供 filter 回调的处理器。 */
struct input_value *end = vals; /**< “写指针”:指向下一个保留事件应该写入的位置。 */
struct input_value *v; /**< “读指针”:遍历输入事件。 */

for (v = vals; v != vals + count; v++) {
if (handler->filter(handle, v->type, v->code, v->value))
continue; /**< 被过滤事件:直接跳过,不推进 end。 */

if (end != v)
*end = *v; /**< 原地压缩:把保留事件搬运到 end 位置。 */
end++; /**< 保留一个事件后,写指针前移。 */
}

return end - vals; /**< 指针差:得到保留事件数量。 */
}

input_handle_events_null:空处理策略

作用与原理:不进行任何处理但返回 count,表示“该批事件被消费”。用于 handler 既没有 filter/event/events 时的兜底策略,避免上层必须对空回调做额外分支。

1
2
3
4
5
6
7
8
9
/**
* @brief 空事件处理:不处理事件但表示已消费。
*/
static unsigned int input_handle_events_null(struct input_handle *handle,
struct input_value *vals,
unsigned int count)
{
return count;
}

input_handle_setup_event_handler:根据 handler 能力选择 handle->handle_events

作用与原理:按优先级把 handle->handle_events 指向合适的实现:

  1. 若存在 filter,优先采用 filter 压缩策略(因为过滤需要在最前面生效);
  2. 否则若存在 event,采用默认逐事件分发;
  3. 否则若存在 events(批处理接口),直接使用 handler 自己的批处理实现;
  4. 都没有则使用空处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* @brief 为 input_handle 选择合适的 handle_events 实现。
*
* @param handle 当前 input_handle。
*/
static void input_handle_setup_event_handler(struct input_handle *handle)
{
struct input_handler *handler = handle->handler; /**< 选择依据来自 handler 的回调集。 */

if (handler->filter)
handle->handle_events = input_handle_events_filter; /**< 过滤优先:保证过滤在事件分发前生效。 */
else if (handler->event)
handle->handle_events = input_handle_events_default; /**< 单事件接口:由默认展开器逐个调用。 */
else if (handler->events)
handle->handle_events = handler->events; /**< 批处理接口:直接交给 handler 自己实现。 */
else
handle->handle_events = input_handle_events_null; /**< 兜底:无回调时仍“消费”事件。 */
}

input_register_handle:把 handle 挂到 dev 与 handler 的 RCU 链表,使事件可流经该 handle

作用与原理

  • 先调用 input_handle_setup_event_handler(),确保 handle->handle_events 已绑定为可用实现。

  • 进入 dev->mutex 临界区,把 handle->d_node 挂到 dev->h_list

    • 过滤器放在链表头(list_add_rcu),使过滤器先于普通处理器看到事件;
    • 普通处理器放在链表尾(list_add_tail_rcu)。
  • 再把 handle->h_node 挂到 handler->h_list,便于按 handler 维度管理/断开。

  • 若 handler 实现了 start,在注册完成后回调 handler->start(handle),用于做“开始接收事件前”的准备动作。

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
/**
* @brief 注册一个 input_handle,使输入事件在打开设备后可通过该 handle 流向对应 handler。
*
* @param handle 待注册的 input_handle(其 dev/handler/name/private 等需由 connect 路径预先填写)。
* @return 0 成功;负 errno 失败。
*/
int input_register_handle(struct input_handle *handle)
{
struct input_handler *handler = handle->handler; /**< 将要接收事件的处理器。 */
struct input_dev *dev = handle->dev; /**< 产生事件的输入设备。 */

input_handle_setup_event_handler(handle); /**< 选择并绑定 handle->handle_events 分发策略。 */

/**
* scoped_cond_guard(mutex_intr, ...):
* - 以“可被信号打断”的方式获取 dev->mutex
* - 若等待过程中被打断,按给定动作返回 -EINTR
* 该风格用于减少显式 unlock 的错误路径复杂度。
*/
scoped_cond_guard(mutex_intr, return -EINTR, &dev->mutex) {
/**
* dev->h_list:
* - 输入设备维度的 handle 链表
* - 使用 RCU 链表插入,允许事件分发读路径进行 RCU 遍历
*/
if (handler->filter)
list_add_rcu(&handle->d_node, &dev->h_list); /**< 过滤器置于头部:先过滤再分发。 */
else
list_add_tail_rcu(&handle->d_node, &dev->h_list); /**< 普通处理器置于尾部:维持处理顺序策略。 */
}

/**
* handler->h_list:
* - 处理器维度的 handle 链表
* - 用于断开/遍历该 handler 关联的所有设备连接
*/
list_add_tail_rcu(&handle->h_node, &handler->h_list);

if (handler->start)
handler->start(handle); /**< 可选:注册完成后的启动钩子。 */

return 0;
}
EXPORT_SYMBOL(input_register_handle);

input_open_device:启动 input_handle 的事件接收并在需要时打开底层 input_dev(含 RCU 同步防并发分发)

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
/**
* @brief 打开一个 input_handle,使其开始接收来自 input_dev 的事件;必要时触发底层设备 open。
*
* @param handle 访问设备的句柄。
* @return 0 成功;负 errno 失败。
*/
int input_open_device(struct input_handle *handle)
{
struct input_dev *dev = handle->dev; /**< 句柄所连接的输入设备。 */
int error;

/**
* scoped_cond_guard(mutex_intr, ...):
* 在可被信号中断的方式下持有 dev->mutex;等待过程中若被打断则返回 -EINTR。
* dev->mutex 保护 going_away/users/inhibited/open 等状态及其组合判断。
*/
scoped_cond_guard(mutex_intr, return -EINTR, &dev->mutex) {
if (dev->going_away)
return -ENODEV; /**< 设备正在移除/注销过程中,不允许再 open。 */

handle->open++; /**< 标记该 handle 处于打开状态:事件分发路径据此决定是否投递。 */

if (handle->handler->passive_observer)
return 0; /**< 被动观察者:不推动底层设备 open,仅表示该 handle 接收事件语义成立。 */

if (dev->users++ || dev->inhibited) {
/**
* dev->users++:
* - 若原值非 0,说明设备已被其他 handle 打开,无需重复调用 dev->open。
* dev->inhibited:
* - 设备被禁止(例如策略/电源管理),即便计数变化也不触发真正 open。
*/
return 0;
}

if (dev->open) {
error = dev->open(dev); /**< 调用底层驱动的 open:通常会启用中断/DMA/上报通道等。 */
if (error) {
dev->users--; /**< 回滚 users:因为底层 open 未成功,不能算一个有效用户。 */
handle->open--; /**< 回滚 handle 打开计数:避免后续仍向该 handle 投递事件。 */

/**
* synchronize_rcu:
* 等待所有正在进行的 RCU 读侧临界区结束。
* 目的:确保事件分发路径不再持有旧视图而继续通过该 handle 投递事件。
* 单核下同样需要:因为读侧可能在中断关闭/自旋锁环境下进行,必须等其退出。
*/
synchronize_rcu();
return error;
}
}

if (dev->poller)
input_dev_poller_start(dev->poller); /**< 若设备以轮询方式产生事件,则在首次打开时启动轮询器。 */
}

return 0;
}
EXPORT_SYMBOL(input_open_device);

drivers/input/input-leds.c 输入子系统到 LED 子系统的桥接

input_led_info / struct input_led / struct input_leds:静态映射表与对象布局

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
#include <linux/leds.h>
#include <linux/input.h>

#if IS_ENABLED(CONFIG_VT)
#define VT_TRIGGER(_name) .trigger = _name /**< 仅在启用 VT 触发器时,为键盘锁定灯提供默认触发器名。 */
#else
#define VT_TRIGGER(_name) .trigger = NULL
#endif

#if IS_ENABLED(CONFIG_SND_CTL_LED)
#define AUDIO_TRIGGER(_name) .trigger = _name /**< 仅在启用音频 LED 控制时,为静音灯提供默认触发器名。 */
#else
#define AUDIO_TRIGGER(_name) .trigger = NULL
#endif

/** @brief 输入层 LED code(LED_*)到 LED 子系统名称/默认触发器的映射表。 */
static const struct {
const char *name; /**< LED 子系统中用于组成设备名的后缀(如 "capslock")。 */
const char *trigger; /**< LED 子系统默认触发器名(可选)。 */
} input_led_info[LED_CNT] = {
[LED_NUML] = { "numlock", VT_TRIGGER("kbd-numlock") },
[LED_CAPSL] = { "capslock", VT_TRIGGER("kbd-capslock") },
[LED_SCROLLL] = { "scrolllock", VT_TRIGGER("kbd-scrolllock") },
[LED_COMPOSE] = { "compose" },
[LED_KANA] = { "kana", VT_TRIGGER("kbd-kanalock") },
[LED_SLEEP] = { "sleep" } ,
[LED_SUSPEND] = { "suspend" },
[LED_MUTE] = { "mute", AUDIO_TRIGGER("audio-mute") },
[LED_MISC] = { "misc" },
[LED_MAIL] = { "mail" },
[LED_CHARGING] = { "charging" },
};

/** @brief 单个“输入 LED”对应的 LED classdev 实例。 */
struct input_led {
struct led_classdev cdev; /**< LED 子系统对象,提供 brightness_get/set 与触发器等。 */
struct input_handle *handle; /**< 绑定到 input 设备的句柄,用于注入 EV_LED 事件。 */
unsigned int code; /**< input 层 LED code,取值为 LED_* 常量之一。 */
};

/** @brief 绑定到某个 input_dev 的桥接对象(一个 input_handle + 若干个 input_led)。 */
struct input_leds {
struct input_handle handle; /**< 输入子系统句柄:建立与具体 input_dev 的连接。 */
unsigned int num_leds; /**< 当前设备上可桥接的 LED 数量。 */
struct input_led leds[] __counted_by(num_leds); /**< 柔性数组:紧随结构体尾部存放多个 input_led。 */
};

input_leds_brightness_get:从 input 设备 LED 位图读取当前亮度

作用与原理

该函数不直接访问硬件灯,而是读取 input_dev->led 位图中对应 bit。该位图由 input 核心在处理 EV_LED 事件时更新,因此它代表“输入设备认为的 LED 状态”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @brief 获取 LED 当前亮度(通过 input_dev->led 位图反映的状态)。
*/
static enum led_brightness input_leds_brightness_get(struct led_classdev *cdev)
{
/** container_of:由 cdev 指针反推出包含它的 struct input_led。 */
struct input_led *led = container_of(cdev, struct input_led, cdev);
struct input_dev *input = led->handle->dev;

/**
* test_bit:读取 input->led 位图中 led->code 对应的 bit。
* 这里的语义是“状态桥接”:input 子系统的 LED 状态 -> LED 子系统亮度读接口。
*/
return test_bit(led->code, input->led) ? cdev->max_brightness : 0;
}

input_leds_brightness_set:向 input 子系统注入 EV_LED 事件以改变状态

作用与原理

LED 子系统希望“设置亮度”,这里把它翻译成 input 子系统可理解的 EV_LED 事件,通过 input_inject_event() 注入到对应输入设备,使设备驱动(或上层处理链)执行实际的 LED 控制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @brief 设置 LED 亮度(通过 input_inject_event 注入 EV_LED 事件)。
*/
static void input_leds_brightness_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct input_led *led = container_of(cdev, struct input_led, cdev);

/**
* input_inject_event:
* - type=EV_LED 表示 LED 类事件
* - code=led->code 指定具体 LED_*(如 LED_CAPSL)
* - value=!!brightness 折算为 0/1
* 该路径的关键语义是:LED 框架的“写” -> input 设备的“事件输入”。
*/
input_inject_event(led->handle, EV_LED, led->code, !!brightness);
}

input_leds_event:输入层事件回调(本实现为空)

作用与原理

该回调是 input_handler 的事件入口。此处留空表示该桥接模块不需要在事件到来时做额外动作;LED 状态由 input 核心维护在 input_dev->led 位图,读取时由 brightness_get 查询即可。

1
2
3
4
5
6
7
/**
* @brief 输入事件回调(当前实现不处理事件)。
*/
static void input_leds_event(struct input_handle *handle, unsigned int type,
unsigned int code, int value)
{
}

input_leds_get_count:统计设备上可桥接的 LED 数量

作用与原理

遍历 dev->ledbit(表示“设备支持哪些 LED code”),只对 input_led_info[code].name 非空的项计数,从而决定后续需要分配多少 struct input_led

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @brief 统计 input_dev 支持且本模块有映射名称的 LED 数量。
*/
static int input_leds_get_count(struct input_dev *dev)
{
unsigned int led_code;
int count = 0;

/** for_each_set_bit:遍历 dev->ledbit 中置位的 LED code(0..LED_CNT-1)。 */
for_each_set_bit(led_code, dev->ledbit, LED_CNT)
if (input_led_info[led_code].name)
count++;

return count;
}

input_leds_connect:连接 input 设备并为每个 LED_* 注册一个 led_classdev

作用与原理

这是桥接的核心:当匹配到具备 EV_LED 能力的输入设备时,

  1. 计算 LED 数量并分配 struct input_leds(柔性数组一次性分配)
  2. 注册并打开 input_handle,使得后续可以注入事件
  3. 为每个支持的 LED code 创建 led_classdev:生成名称、配置 get/set、设置默认触发器、注册到 LED 子系统
  4. 任意一步失败都按 goto 路径回滚,保证不泄漏资源
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
/**
* @brief input_handler 的 connect 回调:为一个 input_dev 建立 LED 桥接并注册多个 led_classdev。
*/
static int input_leds_connect(struct input_handler *handler,
struct input_dev *dev,
const struct input_device_id *id)
{
struct input_leds *leds;
struct input_led *led;
unsigned int num_leds;
unsigned int led_code;
int led_no;
int error;

num_leds = input_leds_get_count(dev);
if (!num_leds)
return -ENXIO;

/**
* struct_size:按“结构体 + 柔性数组 num_leds 个元素”的总大小分配。
* kzalloc:清零分配,避免未初始化字段导致回滚路径判断出错。
*/
leds = kzalloc(struct_size(leds, leds, num_leds), GFP_KERNEL);
if (!leds)
return -ENOMEM;

leds->num_leds = num_leds;

/** 初始化 input_handle:把该 handle 绑定到 dev,并用 private 回指到 leds 以便 disconnect 释放。 */
leds->handle.dev = dev;
leds->handle.handler = handler;
leds->handle.name = "leds";
leds->handle.private = leds;

error = input_register_handle(&leds->handle);
if (error)
goto err_free_mem;

/** 打开 input 设备:使得 handle 生效并允许注入/接收路径被建立。 */
error = input_open_device(&leds->handle);
if (error)
goto err_unregister_handle;

led_no = 0;
for_each_set_bit(led_code, dev->ledbit, LED_CNT) {
if (!input_led_info[led_code].name)
continue;

led = &leds->leds[led_no];
led->handle = &leds->handle;
led->code = led_code;

/**
* kasprintf:构造 LED 设备名 "inputdev::ledname"。
* 该命名策略确保同类 LED 在不同 input 设备下不会发生名称冲突。
*/
led->cdev.name = kasprintf(GFP_KERNEL, "%s::%s",
dev_name(&dev->dev),
input_led_info[led_code].name);
if (!led->cdev.name) {
error = -ENOMEM;
goto err_unregister_leds;
}

led->cdev.max_brightness = 1;
led->cdev.brightness_get = input_leds_brightness_get;
led->cdev.brightness_set = input_leds_brightness_set;
led->cdev.default_trigger = input_led_info[led_code].trigger;

error = led_classdev_register(&dev->dev, &led->cdev);
if (error) {
dev_err(&dev->dev, "failed to register LED %s: %d\n",
led->cdev.name, error);
kfree(led->cdev.name);
goto err_unregister_leds;
}

led_no++;
}

return 0;

err_unregister_leds:
while (--led_no >= 0) {
struct input_led *led = &leds->leds[led_no];

led_classdev_unregister(&led->cdev);
kfree(led->cdev.name);
}

input_close_device(&leds->handle);

err_unregister_handle:
input_unregister_handle(&leds->handle);

err_free_mem:
kfree(leds);
return error;
}

input_leds_disconnect:断开 input 设备并注销全部 led_classdev

作用与原理

按 connect 的反向顺序释放:先注销 LED 设备并释放名称,再关闭 input 设备并注销 handle,最后释放 struct input_leds

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @brief input_handler 的 disconnect 回调:注销桥接创建的 LED 并释放所有资源。
*/
static void input_leds_disconnect(struct input_handle *handle)
{
struct input_leds *leds = handle->private;
int i;

for (i = 0; i < leds->num_leds; i++) {
struct input_led *led = &leds->leds[i];

led_classdev_unregister(&led->cdev);
kfree(led->cdev.name);
}

input_close_device(handle);
input_unregister_handle(handle);

kfree(leds);
}

input_leds_ids / input_leds_handler:匹配规则与处理器回调表

作用与原理

  • input_leds_ids:仅匹配具备 EV_LED 事件类型的输入设备。
  • input_leds_handler:声明 connect/disconnect/event 入口,使 input 核心在匹配时调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/** @brief 匹配具备 EV_LED 能力的输入设备。 */
static const struct input_device_id input_leds_ids[] = {
{
.flags = INPUT_DEVICE_ID_MATCH_EVBIT,
.evbit = { BIT_MASK(EV_LED) },
},
{ },
};

static struct input_handler input_leds_handler = {
.event = input_leds_event,
.connect = input_leds_connect,
.disconnect = input_leds_disconnect,
.name = "leds",
.id_table = input_leds_ids,
};

input_leds_init / input_leds_exit:注册与注销 input_handler

作用与原理

模块加载时注册 handler,使得后续 input 设备枚举/热插拔会触发匹配并调用 connect;模块卸载时注销 handler 并触发已连接设备的 disconnect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @brief 模块初始化:向 input 核心注册本 handler。
*/
static int __init input_leds_init(void)
{
return input_register_handler(&input_leds_handler);
}
module_init(input_leds_init);

/**
* @brief 模块退出:从 input 核心注销本 handler。
*/
static void __exit input_leds_exit(void)
{
input_unregister_handler(&input_leds_handler);
}
module_exit(input_leds_exit);