驱动之路#11:Input子系统数据上报流程

汇聚之精 2026-03-10 4719人围观

题图:日本东京有一家专门生产电梯按钮的公司,该公司把生产过的1000多种电梯按钮,做成了一面展示墙,按上去每个都会亮。孩子们和大人都很喜欢。

欢迎关注,每周更新!

本合集分享的是,我当初学习Linux驱动的来时路——《《驱动之路》开篇:自序&前言》。

正文

Input 子系统框架的复杂程度有三四层楼那么高。幸运的是,复杂那部分代码大佬前辈们已经实现,我们只需搞懂 Input 子系统的框架,然后套用框架实现具体输入设备 driver 端驱动程序即可。因此,可以说 ai 时代搞懂框架比理解每行代码更重要。

但是对于 Input 子系统这样复杂的框架,要彻底理解从硬件底层到用户空间的数据上报流程谈何容易,必须要找到一个抓手作为切入点。

然而,分析gpio_keys.c驱动正是掌握 Linux Input 子系统数据上报流程的绝佳途径,因为它是一个典型且相对简单的 Input 驱动实例。

在分析前,请先回顾《驱动之路#01:Hello World!》和 Input 子系统的三层架构:驱动层、核心层以及事件处理层。

wKgZO2mvZN2AbU7PAAIG2CMGLgw758.png

下面是一个 step-by-step 的指南,带你从gpio_keys.c出发,彻底理解 Input 子系统的工作机制。

特别说明:本文重点在于理解 Input 子系统数据上报流程,而非gpio_keys.c驱动分析。

从module_init开始:驱动入口函数

阅读一个字符设备驱动程序从入口函数开始,在 gpio_keys.c 中可以看到 gpio_keys_init 注册了 gpio_keys_device_driver。当 driver 与 device 匹配后,gpio_keys_probe 函数就会被调用,接下来重点分析gpio_keys_probe 函数。

驱动层到核心层

驱动核心: gpio_keys_probe 函数分析

probe函数在驱动与设备匹配成功后被调用,可以看到 probe函数有 4 步关键操作。

步骤 1:获取设备配置信息

首先通过dev_get_platdata(dev)从平台设备中获取静态定义的平台数据。如果没有静态平台数据,则再通过gpio_keys_get_devtree_pdata(dev)从设备树中解析配置。总之,无论是设备树还是传统平台数据,最终都解析到pdata中。

wKgZO2mvZN2AeJ07AAEDJMVRvDE707.png

步骤 2:分配和初始化struct input_dev

通过devm_input_allocate_device()创建一个输入设备对象input_dev,这是驱动层与核心层交互的第一步。

步骤 3:申请 GPIO 和中断

probe 函数没有直接包含中断处理函数的实现,而是通过调用 gpio_keys_setup_key() 完成了中断函数的注册、中断触发方式等配置。

wKgZO2mvZN6Abl0WAAA-eoy6v4c995.png

步骤 4:注册输入设备

通过 input_register_device()将 input_dev 注册到 Input 子系统核心层,调用此函数后,Input 核心层会接手管理这个输入设备并尝试为它匹配合适的事件处理器(Handler),如 evdev。

注册成功后,用户空间就可以看到/dev/input/eventX 设备节点了。

wKgZO2mvZN6AQLLqAABLPDcqWHY584.png

以上是 probe 函数关键操作的分析。

中断处理函数分析

中断处理函数是数据上报流程的起点,当用户按下或松开按键时,GPIO 电平变化触发中断,此函数被执行。

经过前面分析知道,中断服务函数相关配置在gpio_keys_setup_key() 中完成,接下来分析 gpio_keys_setup_key() 。

可以看到有两种IRQ函数

gpio_keys_gpio_isr:设备树中的用gpios描述引脚时调用;

gpio_keys_irq_isr:设备树中的用interrupts描述引脚时调用。

它们有各自的优缺点,但不是本文的重点,这里不展开分析。

wKgZO2mvZN6AGUc_AANaoLNwqDQ103.png

我们分析相对简单的 gpio_keys_irq_isr 中断函数的处理流程,其中,

核心:调用input_event(input, EV_KEY, *bdata->code,1)和input_sync(input)进行上报数据。

input_event():向 Input 核心层上报一个原始事件。这个事件包含了事件类型(EV_KEY)、事件码(如 KEY_POWER)和值(1 或 0)。

input_sync():上报一个同步事件,告诉核心层 “这一组事件已经完整上报完毕”。

wKgZO2mvZN6AMdO2AATY92t5pNQ363.png

至此,数据已经从硬件驱动层上报到核心层。

核心层到事件层数据流

数据从核心层传递到事件层函数调用关系比较复杂,调用关系如下。

其中,input_handle_event函数是 Input 核心层的事件分发中心,它会将事件传递给所有与该input_dev关联的input_handler(事件处理器)。

而数据从核心层传递到事件层,是调用evdev_events 函数来实现,然后通过evdev_pass_values函数被分发到各个客户端。

wKgZO2mvZN-AZFU2AAWPtQGk01k574.png

当用户空间读取/dev/input/eventX时,实际上是从对应客户端的环形缓冲区中读取数据。数据最后保存在每个打开设备文件的进程所对应的evdev_client的环形缓冲区中。

wKgZO2mvZN-AaTuOAAGpAHHV4Kw875.png

数据流:gpio_keys.c驱动->input_event -> input_handle_event -> input_pass_values -> evdev_events -> evdev_pass_values ->写入evdev_client的 buffer ->用户空间read读取。

总结整个流程

硬件:用户按下按键 -> GPIO 电平变化 -> 触发中断。

驱动层 (gpio_keys.c):

gpio_keys_irq_isr 或gpio_keys_gpio_isr 中断服务程序被调用。

调用input_event()和input_sync()向上层(核心层)上报事件。

核心层 (input.c):

input_event()->input_handle_event()接收事件。

核心层将事件分发给所有匹配的 input_handler。

事件处理层 (evdev.c):

evdev_event()接收事件。

将事件打包成struct input_event并写入内核缓冲区(buffer)。

唤醒正在等待数据的用户空间应用程序。

用户空间:应用程序的 read()调用返回,读取到 struct input_event 数据并进行解析。

看完本文,可自行阅读源码分析,关键代码阅读顺序:

drivers/input/keyboard/gpio_keys.c- 具体输入设备驱动程序

drivers/input/input.c- 核心框架

drivers/input/evdev.c- 事件处理

include/linux/input.h- 数据结构和 API

(完)

本人专注 Linux 驱动 & Linux/Android BSP 开发调试,可接外包项目/技术支持/问题定位。有需求或交个朋友可加微信:【Chen_WeChat2026】。

更多原创技术文章:《README 2026》。

审核编辑 黄宇

Powered By Z-BlogPHP