input输入子系统分析

2023-11-02

input子系统学习

Input子系统作为linux下很重要的一个设备管理框架,同时运用了platform平台设备总线,在驱动的入门阶段,值得深入的分析。在碰壁很多次之后,小编决定对这部分的内容进行总结,也算是对platform平台总线和input设备驱动框架的回顾。(以keyboard按键驱动为例,由于工作原因源码不做展示)

总体框架分析

Linux中的输入子系统从上到下依次是:事件处理层(evdevt.c)、核心处理层(input.c)、设备驱动层(sunxi_keyboard.c)。这三层共同合作,每层负责自己所负责的部分,各司其职,很好的将设备和驱动进行了分离,也增强了其可移植性,由于设备树的引入,更使得一套驱动在经过简单的配置修改,可以适配多个硬件设备。下面分别介绍一下三部分的职责:

事件处理层(evevt.c):这是与用于交互直接相关的一层,为用户提供统一的接口(open、write、read、close),注册设备节点(/dev/iuput/evet0),处理驱动层提交上来的数据,将数据的格式进行统一的处理。 对于事件处理层相关的一个最重要的结构体是:input_handle。 与这个结构体相关的,也是事件处理层核心的一个注册函数是:input_register_handle( )。

核心处理层(input.c):为驱动层提供规范的接口(也就是注册函数、接口函数、处理函数),驱动层只需要关心硬件的驱动,采集底层的硬件数据,而最后则是通过这些接口完成数据的上报(上报至事件处理层)。 对于核心层最重要的结构体是input_handler。 与这个结构体相关的是核心处理层核心的一个注册函数:input_register_handler( )。

设备驱动层(keyboard.c):负责对硬件设备的读写访问,利用中断进行数据的采集上发,并利用核心处理层提供的接口函数将采集到的数据进行数据的规范处理。对于核心层最重要的结构体是input_dev。 与这个结构体相关的是核心处理层核心的一个注册函数:input_register_device( )。

关于这三层是如何进行协同工作的,请看下图:Linux输入子系统的事件处理机制:
input输入子系统事件处理机制

从事件处理过程(程序)看input子系统

Input子系统分成硬件驱动层(input device)、子系统核心层(input core)和事件处理层(event handler),因此它也会有相应的三个主要的数据结构:
1.struct input_dev结构体:硬件驱动层的基本数据结构体,包括设备相关的一些信息;
2.struct input_handle结构体:子系统核心层数据结构体,当input_dev与input_handler匹配成功时产生此结构体,也是连接input_dev与input_handler的桥梁;
3.struct input_handler结构体:事件处理结构体,描述事件处理的逻辑关系。
他们的具体关系如下图所示:
在这里插入图片描述
划重点 划重点 划重点
所有的input_dev结构体的node变量在注册时添加到input_dev_list链表上,再游历input_handler_list链表是否有与其匹配的input_handler结构体;

所有的input_handler数据结构体的node变量在注册时添加到input_handler_list链表上,同时也游历input_dev_list链表是否有与其匹配的input_dev结构体;

当游历input_handler_list(input_dev_list)链表是否有input_handler(input_dev)结构体与之匹配时,会调用input_match_device()函数,此函数作用是比较input_dev中的id变量是否与input_hander中id_table比较,如果相等,说明匹配成功,则通过handler->connect()函数下的input_register_handle()函数注册handle数据结构体;input_handle数据结构体中的dev变量指向匹配成功的input_dev数据结构体handler变量指向配成功的input_handler数据结构体,d_node变量添加到input_dev数据结构中的h_list链表中,h_node变量添加到input_handler数据结构体的h_list链表中。Input_handle数据结构体是一对匹配成功的input_dev与input_handler连接的桥梁。

下面对三层进行具体的分析

对设备驱动层的分析:

接下来从 sunxi_keyboard.c 设备驱动层往上根据代码分析一下驱动的工作流程。

subsys_initcall_sync(keyboard_init);

(注意:这里有一个细节问题,在sunxi_keyboard.c中的入口函数是 subsys_initcall_sync 说明这部分工作的在内核驱动的过程中就已经完成了,在驱动和设备匹配之后就已经完成后,就已经做好了一切采集数据的准备; 而在evdev.c中的入口函数是module_init(evdev_init) 这个入口函数进去后会将设备驱动层和事件处理层联系到一起,然后等待输入事件的发生,将设备驱动层采集到的数据通过处理成统一的格式交付给Linux中固定的设备节点)

subsys_initcall_sync(keyboard_init);
	
ret = platform_driver_register(&keyboard_driver);  驱动的注册

static struct platform_driver sunxi_keyboard_driver =
 {
	.probe  = keyboard_probe,
	.remove = keyboard_remove,
	.driver = 
{
		.name   = "keyboard",
		.owner  = THIS_MODULE,
#ifdef CONFIG_PM
		.pm	= SUNXI_KEYBOARD_PM_OPS,
#endif
		.of_match_table = of_match_ptr(keyboard_of_match),
	},
};

首先在这里确定了driver->name,为之后的匹配打下了基础。
Probe回调函数是关键,值得注意的是static int keyboard_probe(struct platform_device *pdev),这个函数默认的参数是 struct platform_device 用描述具体的input输入子系统硬件设备(这个就是总线、驱动、设备中的设备)。这里将完成设备信息的填充、注册、以及具体的硬件读写操作。其中常见包括:硬件的初始化、中断的初始化、input_dev类设备的申请、以及中断服务程序。最终在中断服务函数中调用input_report_key 函数完成数据的上报。

input_report_key
	input_event
		input_handle_event
			input_pass_values
				input_to_handler
					handler->events( )

到这里之后,在evdev.c中的input_event类型的event赋值给了evdev_client的数组,传送结束。数据完成上报。

其实到调用 input_report_key函数时,硬件设备驱动层的任务就已经完成,完成了数据的采集,然后 input_report_key 的具体实现是在 input.c 中完成,为事件处理层提供一个统一格式的 input_event 类型的数据。其中input_dev 是一个特别大的一个结构体类型,所有的输入子系统类设备最终上报时都是遵守这个结构体类变量的格式进行数据的传输。 而不同的设备可以有属于自己的数据描述结构体,但最后都会包含这个结构体。例如:

struct sunxi_key_data {
	struct platform_device	*pdev;
	struct clk *mclk;
	struct clk *pclk;
	struct input_dev *input_dev;
	struct adc_disc *disc;
	void __iomem *reg_base;
	u32 scankeycodes[KEY_MAX_CNT];
	int irq_num;
	u32 key_val;
	unsigned char compare_later;
	unsigned char compare_before;
	u8 key_code;
	u8 last_key_code;
	char key_name[16];
	u8 key_cnt;
	int wakeup;
};

总结:经过设备驱动层的努力工作
①最终完成了驱动 driver 的注册,设备 device 信息的填充,设备的注册,设备的初始化;
②最终在 /sys 目录下可以找到Linux驱动模型中总线、设备以及驱动的节点;
③等待中断的发生,将采集到的数据统一成 input_event 格式进行数据的上报;

对事件处理层的分析:

开始分析之前必须说一下这层中最重要的一个结构体 input_handler :事件处理结构体,描述事件处理的逻辑关系

struct input_handler {
	void *private;

	void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
	void (*events)(struct input_handle *handle,
		       const struct input_value *vals, unsigned int count);
	bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
	bool (*match)(struct input_handler *handler, struct input_dev *dev);
	int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
	void (*disconnect)(struct input_handle *handle);
	void (*start)(struct input_handle *handle);

	bool legacy_minors;
	int minor;
	const char *name;

	const struct input_device_id *id_table;

	struct list_head	h_list;
	struct list_head	node;
};

event 函数:是当事件处理器接收到了来自input设备传来的事件时调用的处理函数,负责处理事件,非常重要。

划重点 划重点 划重点Connect 函数:当input_dev与input_handler配对成功时被调用,负责配对成功后的后续工作,注册一个input_handle数据结构体。

下面我们开始从代码分析事件处理层驱动的工作流程:

module_init(evdev_init);  

注意这里是 module_init 驱动在被 insmod 安装时会被调用,而我们常见的是通过 make menuconfig 进行模块的配置,配置完内核进行编译时就会调用这个函数。

Int  input_register_handler(struct input_handler *handler)
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}

这里的 input_register_handler 负责将新设备加入input_handler_list链表中,遍历input_dev_list链表,将handler与合适的dev匹配,匹配成功的话调用handler的connect函数,将handler与dev关联起来。过程与input_register_devtce()步骤一样,只是这里是绑定dev。

static struct input_handler evdev_handler = 
{
		.event		= evdev_event,
		.events		= evdev_events,
		.connect	= evdev_connect,
		.disconnect	= evdev_disconnect,
		.legacy_minors	= true,
		.minor		= EVDEV_MINOR_BASE,
		.name		= "evdev",
		.id_table	= evdev_ids,	
};

而在这个关键的函数 evdev_connect 中:
①开辟一个 evdev 类型变量的空间,结构体内包含着需要注册的结构体 input_handle,然后进行结构体的填充,最后利用 input_register_handle() 进行 input_handle 的注册;

②用户层接回调函数的指定:file_operations evdev_fops

static const struct file_operations evdev_fops = {
	.owner		= THIS_MODULE,
	.read		= evdev_read,
	.write		= evdev_write,
	.poll		= evdev_poll,
	.open		= evdev_open,
	.release	= evdev_release,
	.unlocked_ioctl	= evdev_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl	= evdev_ioctl_compat,
#endif
	.fasync		= evdev_fasync,
	.flush		= evdev_flush,
	.llseek		= no_llseek,
};

用户调用 read 进行数据的读取,实际调用的是回调函数 evdev_read,其中ecdev_fetch_next_event 取到了一个event值,然后复制到用户层,这样整个读取数据的流程完成。

总结:经过事件处理层的努力
①在Linux下创建了具体的设备节点,用户可以通过复这个节点进行统一的文件操作来实现对设备的读写;
②最终为用户提供了统一的接口函数(open、write等)的回调函数。

对核心处理层(input.c)的分析:

subsys_initcall(input_init);

和硬件驱动层一样,在内核启动时就进行了此部分的工作:

input子系统也是字符设备驱动,在初始化时调用input_init(void)函数,在input_init()函数中主要完成三件事:
①注册一个input_class类,作用是在sysfs中将所有input device都位于/dev/class/input下:

class_register(&input_class)

②在proc下面建立相关的交互文件:

input_proc_init()

③注册设备号,主设备号为13,次设备号为0~255

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

input输入子系统分析 的相关文章

  • 如何在perl中使用O_ASYNC和fcntl?

    我想使用 O ASYNC 选项 当管道可以读取时 SIGIO 的处理程序将运行 但以下代码不起作用 任何人都可以帮助我吗 bin env perl use Fcntl SIG IO sub print catch SIGIO n my fl
  • 在非实时操作系统/内核上执行接近实时任务的最佳方法是什么?

    在一台 GNU Linux 机器上 如果想要执行 实时 亚毫秒级时间关键 任务 您几乎总是必须经历漫长 复杂且容易出现问题的内核补丁过程 以提供足够的支持 1 http en wikipedia org wiki RTLinux Backg
  • 如何在文件夹中的 xml 文件中 grep 一个单词

    我知道我可以使用 grep 在这样的文件夹中的所有文件中查找单词 grep rn core 但我当前的目录有很多子目录 我只想搜索当前目录及其所有子目录中存在的所有 xml 文件 我怎样才能做到这一点 我试过这个 grep rn core
  • 如何在特定的Java版本上运行应用程序?

    如何运行具有特定 Java 版本的应用程序 我安装了三个 Java 版本 myuser mysystem sudo update alternatives config java There are 3 choices for the al
  • 了解 Linux oom-killer 日志

    我的应用程序被 oom killer 杀死了 它是在实时 USB 上运行的 Ubuntu 11 10 无需交换 PC 具有 1 Gig 的 RAM 唯一运行的应用程序 除了所有内置的 Ubuntu 东西 是我的程序 flasherav 请注
  • Ctrl-p 和 Ctrl-n 在 Docker 下表现异常

    For the life of me I can t seem to figure out why ctrl p and ctrl n don t work like they re supposed to under the Docker
  • aarch64 Linux 硬浮点或软浮点

    linux系统有arm64 有arm架构armv8 a 如何知道 Debian 运行的是硬浮动还是软浮动 符合 AAPCS64 GNU GCC for armv8仅提供硬浮动aarch64工具链 这与 armv7 a 的 GCC 不同 后者
  • 亚马逊 Linux - 安装 openjdk-debuginfo?

    我试图使用jstack在 ec2 实例上amazon linux 所以我安装了openjdk devel包裹 sudo yum install java 1 7 0 openjdk devel x86 64 但是 jstack 引发了异常j
  • 为什么无论 -rdynamic 如何,backtrace 都不包含 Objective-C 符号?

    Update 我正在 Linux 上使用 GNU 运行时 问题是not发生在带有 Apple 运行时的 MacOS 上 更新2 我在 MacOS 上编译了 GNU 运行时并用它构建了示例 该错误确实not发生在带有 GNU 运行时的 Mac
  • 在 vimrc 中切换匹配

    我的 vimrc 文件中有以下几行 hi ExtraWhitespace cterm NONE ctermbg green ctermfg green guibg green guifg green match ExtraWhitespac
  • Linux >2.6.33:可以使用 sendfile() 来实现更快的“猫”吗?

    必须将大量大文件连接成一个更大的单个文件 我们目前使用 cat file1 file2 output file but are wondering whether it could be done faster than with that
  • grep 彩色线条

    我编写了一个简单的 PHP shell 脚本 它解析文件并输出某些元素 它产生大量的输出 采用不同的 bash 颜色 绿色表示正常 黄色表示警告 红色表示错误等 在开发过程中我想过滤掉一些行 例如 所有包含红色文本的行 我可以使用grep
  • 点击界面没有出现

    我决定添加一个点击界面并在我的代码中使用它 但我能够得到它的状态 sudo ip f link tuntap add tap10 mode tap sudo ip link set tap10 up 之后当我执行 ip link 时 tap
  • 在 Linux 服务器上创建和编辑 MS-Word 文档?

    希望开发处理文档的服务器端应用程序 源文档大多是MS Word 2003 2007 即MS版本的Docx 希望服务器应用程序能够在linux或windows上运行 想知道在linux下读写MS Word文件最好的工具或库是什么 兼容性是最重
  • 如何使用 PyAudio 选择特定的输入设备

    通过 PyAudio 录制音频时 如何指定要使用的确切输入设备 我的电脑有两个麦克风 一个内置 一个通过 USB 我想使用 USB 麦克风进行录音 这流类 https people csail mit edu hubert pyaudio
  • 使用 .htaccess 启用 PHP 短标签

    我在自己的 Centos 服务器上设置了 Apache 并具有多个虚拟 Web 服务器 并且我希望仅为位于以下位置的其中一个 Web 服务器启用 PHP 短标记 var www ostickets html 我可以通过添加成功启用短标签sh
  • PHP mail() 函数不发送邮件

    我有一个小问题 我正在使用一个工作脚本 在我的测试帐户 共享服务器上工作 使用 mail 函数通过 PHP 发送邮件 我刚刚得到了一个专用服务器 但我还无法让该功能发挥作用 在过去的 10 个小时左右的时间里 我阅读了有关 BIND 用于
  • SIGHUP 用于重新加载配置

    根据signal 7 SIGHUP用于检测控制终端的挂起或控制进程的死亡 然而 我遇到过很多 OSS 守护进程 服务 其中SIGHUP用于启动配置的重新加载 这里有一些例子 hostapd sshd snort etc 这是实现重新加载的标
  • 重新链接匿名(未链接但打开)文件

    在 Unix 中 可以创建匿名文件的句柄 例如 使用 creat 创建并打开它 然后使用 unlink 删除目录链接 留下一个带有 inode 和存储的文件 但没有可能的方法重新打开它 此类文件通常用作临时文件 通常这就是 tmpfile
  • 使用 hcitool 扫描低功耗蓝牙?

    当我运行此命令时 BLE 设备扫描仅持续 5 秒 sudo timeout 5s hcitool i hci0 lescan 输出显示在终端屏幕中 但是 当我将输出重定向到文件以保存广告设备的地址时 每次运行该命令时 我都会发现该文件是空的

随机推荐

  • 如何在VMware上安装CentOS7?

    目录 一 器材准备 二 创建一个虚拟机 三 安装Centos7系统 一 器材准备 1 Centos7及以上版本的iso镜像 链接 centos7镜像 提取码 ao3n 2 VMware15及以上版本的软件工具包 链接 VMware16安装包
  • 并行运算出现“_pickle.PicklingError: Can‘t pickle <function calculate at 0x000001ADE8C2EEE0>”

    尝试将需要并行运算的函数同并行运算结构体分开进行并行运算 例 创建一个名为calculate py的新文件 并将以下代码粘贴到该文件中 def calculate num return num 2 主程序的代码如下 import multi
  • 如何优化代码中大量的if/else,switch/case?

    前言 随着项目的迭代 代码中存在的分支判断可能会越来越多 当里面涉及到的逻辑比较复杂或者分支数量实在是多的难以维护的时候 我们就要考虑下 有办法能让这些代码变得更优雅吗 正文 使用枚举 这里我们简单的定义一个表示状态的枚举 public e
  • qq的Tencent://Message/协议

    QQ即时聊天代码 为QQ号码 以前这样写 http wpa qq com msgrd V 1 Uin Site hexun com ngdao Menu yes 现在这样写 tencent message uin Site hexun co
  • upload-master-pass8

    第八关 又是黑名单绕过 但是没有处理 DATA 引用某位大佬的解释就是 php在window的时候如果文件名 DATA 会把 DATA之后的数据当成文件流处理 不会检测后缀名 且保持 DATA 之前的文件名 他的目的就是不检查后缀名 验证
  • ASCII、Unicode、UTF-8、GBK、全角/半角

    入门小菜鸟 希望像做笔记记录自己学的东西 也希望能帮助到同样入门的人 更希望大佬们帮忙纠错啦 侵权立删 目录 一 定义 1 ASCII 2 Unicode 3 UTF 8 4 GB2312 5 GBK 6 u和 x 7 全角和半角 二 相互
  • 【IEDA】ERROR!

    一 中文乱码 public class litera public static void main String args 整数 System out println 100 小数 System out println 0 01 字符 S
  • LINUX DRM 入门一条龙

    使用nanopiM3 s5p6818 LINUX 内核4 4 172 使用HDMI接口显示图像 源码 usb2hdmi 使用usb传输FB数据 hdmi接口输出 情景分析法 使用modetest命令作为应用层的测试例程 modetest M
  • 28黑马QT笔记之QPixmap保存图片

    28黑马QT笔记之QPixmap保存图片 1 QPixmap QImage QPictrue三种绘图工具之间的区别 上一篇我们讲到QPixmap如何在窗口进行绘图 接下来本篇要讲的是QPixmap如何保存一张图片 即将已有图片再按自己方式保
  • 线性代数的本质——线性组合,张成空间和基

    本文是对BiliBili上的一个系列视频的学习记录 非常推荐大家去B站上观看 记得三连 不要白嫖 链接 https www bilibili com video av6731067 p 2 上文中说到 向量坐标中的数字 可视为对基向量的拉伸
  • TypeError: ‘DataContainer‘ object is not iterable解决

    mmsegmentation训练的过程中eval时报错 环境如下 sys platform linux Python 3 8 13 default Mar 28 2022 11 38 47 GCC 7 5 0 CUDA available
  • php assert执行命令,代码执行&命令执行&命令注入

    代码执行 命令执行 命令注入 代码执行 代码执行是靠脚本代码调用操作系统的命令 eval eval string code mixed 把字符串 code 作为PHP代码执行 eval POST c 直接蚁剑链接密码为c 中国菜刀 ckni
  • mapbox-gl图形绘制并编辑已有图层

    文章目录 前言 1 react中使用mapbox gl draw绘制 2 已有图层编辑 3 绘图控件的控制 4 数据缓存造成的一个bug 问题描述 原因 解决方案 总结 前言 相信经常和地理数据打交道的小伙伴都有过标绘地理数据的经历 使用A
  • 窗口子类化和超类化

    目录 窗口子类化 窗口超类化 窗口子类化 窗口子类化的目的是在不修改现有代码的前提下 扩展现有窗口 控件的功能 它的思路很简单 就是将窗口过程地址修改为一个新函数地址 新的窗口过程函数处理自己感兴趣的消息 将不感兴趣的消息丢给原窗口过程处理
  • 11、Iterable迭代

    3 6 Iterator es6新特性 使用iterator来遍历迭代数组 map set 遍历数组 通过for of for in 下标 var arr 3 4 5 for var x of arr console log x 遍历map
  • Python中字符串转义字符的用法----退格符\b

    鄙人第一次写博客 如果有错误 或者表达不当的 希望有大佬们多多指点 我将不甚感激 退格符 b b代表backspace 平时电脑使用这个键就是删除 可以把一个退格符看成一个backspace键 他的原理相当于把一个字符删去 用后面的字符代替
  • 第二章 索引-练习题

    第二章 索引 练习题 首先要导入对应的模块 import pandas as pd import numpy as np Ex1 公司员工数据集 现有一份公司员工数据集 df pd read csv data company csv df
  • 为什么苹果蓝牙耳机连上还是公放_如何测试蓝牙设备的延时

    蓝牙设备的延时准确测量很复杂 但是这里有个小工具 自己动手就可以做 数据一出胜过嘴炮 1 首先去下载安装superpowered latency ios 安卓版的都有 Test iOS and Android Audio Latency w
  • Javafx集成sqlite数据库

    什么是SQLite SQLite是一款非常轻量级的关系数据库系统 支持多数SQL92标准 SQLite在使用前不需要安装设置 不需要进程来启动 停止或配置 而其他大多数SQL数据库引擎是作为一个单独的服务器进程 被程序使用某种内部进程通信
  • input输入子系统分析

    input输入子系统分析 input子系统学习 总体框架分析 从事件处理过程 程序 看input子系统 对设备驱动层的分析 对事件处理层的分析 对核心处理层 input c 的分析 input子系统学习 Input子系统作为linux下很重