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

描述

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

欢迎关注,每周更新!☞

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

正文

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

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

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

在分析前,请先回顾《驱动之路#01:Hello 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.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》。

审核编辑 黄宇

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分