驱动之路#11:Input 子系统数据上报流程 本系列分享的是我当初学习 Linux 驱动的来时路。Input 子系统框架的复杂程度有三四层楼那么高。幸运的是复杂那部分代码大佬前辈们已经实现我们只需搞懂 Input 子系统的框架然后套用框架实现具体输入设备 driver 端驱动程序即可。因此可以说 ai 时代搞懂框架比理解每行代码更重要。但是对于 Input 子系统这样复杂的框架要彻底理解从硬件底层到用户空间的数据上报流程谈何容易必须要找到一个抓手作为切入点。然而分析gpio_keys.c驱动正是掌握 Linux Input 子系统数据上报流程的绝佳途径因为它是一个典型且相对简单的 Input 驱动实例。在分析前请先回顾本系列《驱动之路#01Hello World》和 Input 子系统的三层架构驱动层、核心层以及事件处理层。下面是一个 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中。步骤 2分配和初始化struct input_dev通过devm_input_allocate_device()创建一个输入设备对象input_dev这是驱动层与核心层交互的第一步。步骤 3申请 GPIO 和中断probe 函数没有直接包含中断处理函数的实现而是通过调用 gpio_keys_setup_key() 完成了中断函数的注册、中断触发方式等配置。步骤 4注册输入设备通过 input_register_device()将 input_dev 注册到 Input 子系统核心层调用此函数后Input 核心层会接手管理这个输入设备并尝试为它匹配合适的事件处理器(Handler)如 evdev。注册成功后用户空间就可以看到/dev/input/eventX 设备节点了。以上是 probe 函数关键操作的分析。中断处理函数分析中断处理函数是数据上报流程的起点当用户按下或松开按键时GPIO 电平变化触发中断此函数被执行。经过前面分析知道中断服务函数相关配置在 gpio_keys_setup_key() 中完成接下来分析 gpio_keys_setup_key() 。可以看到有两种 IRQ 函数gpio_keys_gpio_isr设备树中用gpios描述引脚时调用gpio_keys_irq_isr设备树中用interrupts描述引脚时调用。它们有各自的优缺点但不是本文的重点这里不展开分析。我们分析相对简单的 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()上报一个同步事件告诉核心层“这一组事件已经完整上报完毕”。至此数据已经从硬件驱动层上报到核心层。核心层到事件层数据流数据从核心层传递到事件层函数调用关系比较复杂调用关系如下。其中input_handle_event函数是 Input 核心层的事件分发中心它会将事件传递给所有与该input_dev关联的input_handler事件处理器。而数据从核心层传递到事件层是调用evdev_events 函数来实现然后通过evdev_pass_values函数被分发到各个客户端。当用户空间读取/dev/input/eventX时实际上是从对应客户端的环形缓冲区中读取数据。数据最后保存在每个打开设备文件的进程所对应的evdev_client的环形缓冲区中。数据流gpio_keys.c 驱动 - input_event - input_handle_event - input_pass_values - evdev_events - evdev_pass_values - 写入 evdev_client 的 buffer - 用户空间 read 读取总结整个流程硬件用户按下按键 - GPIO 电平变化 - 触发中断。驱动层gpio_keys.cgpio_keys_irq_isr或gpio_keys_gpio_isr中断服务程序被调用调用input_event()和input_sync()向上层核心层上报事件。核心层input.cinput_event()-input_handle_event()接收事件核心层将事件分发给所有匹配的input_handler。事件处理层evdev.cevdev_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完