Linux教程

Linux input子系统(一)

本文主要是介绍Linux input子系统(一),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

Linux输入设备

Linux将按键、键盘、鼠标、触摸屏等设备统称为输入设备,其本质还是字符设备。在一般情况下,这些输入设备在被按下时会产生一个中断或者由CPU定时轮询这些输入设备,如果输入有效,那么cpu会将读到的键值存在缓冲区,驱动的read接口就可以让用户层获取到这些键值。
对于上述流程,在cpu读到有效键值之后的处理都是一样的,也就是read接口只会读取键值,不会关心具体是哪个设备。因此,Linux就设计了一个input框架来处理输入事件,这就是——input子系统。
总结起来就是——Linux内核输入子系统是对不同类别的输入设备进行抽象,随后调用统一的服务函数进行处理。

Linux 输入子系统架构

Linux input子系统框架可以分为以下三个部分:
1.驱动层(输入设备驱动):获取底层硬件(按键、键盘、鼠标等)的输入,然后向input核心层报告输入内容。
2.核心层:向下为驱动层提供设备注册与操作接口;向上通知事件层对相应的输入事件进行处理;在/proc目录下下产生相应的设备信息。核心层源码位置drivers/input/input.c
3.事件层(输入事件驱动):与应用层交互,实现了open、read、write这些文件接口,同时在/dev下生成对应的设备文件。

Linux自带如下输入事件驱动程序:

输入事件驱动程序说明
evdev.c通用输入事件驱动,能处理大多数输入事件
joydev.c游戏杆,操纵杆设备
keyboard.c键盘设备
mousedev.c鼠标设备
keychrod.c组合按键设备

在这里插入图片描述

input device

input device处于驱动层,表示每一个真实的输入设备,如鼠标、键盘、按键等。

struct input_dev {
	const char *name; //对应/sys/class/input/inputX/name 设备名
	const char *phys; //对应/sys/class/input/inputX/phys  系统层次结构中设备的物理路径
	const char *uniq; //对应/sys/class/input/inputX/uniq 设备的唯一识别码(如果设备有) 
	struct input_id id;//设备id 用于匹配 input handler
  
  // 设备属性和怪癖的位图
	unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
  
	unsigned long evbit[BITS_TO_LONGS(EV_CNT)];//记录事件类型的位图 (EV_KEY,EV_REL 等)
	unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];//记录按键值位图
	unsigned long relbit[BITS_TO_LONGS(REL_CNT)];//记录支持的相对坐标的位图
	unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];//记录支持的绝对坐标的位图
	unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];//记录杂项事件的位图
	unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];//LED位图
	unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];//音效位图
	unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
	unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
  
  //设备在数据包中生成的平均事件数(在 EV_SYN/SYN_REPORT 事件之间)由事件处理程序用来估计需要保存的缓冲区大小
	unsigned int hint_events_per_packet;

	unsigned int keycodemax;//支持按键个数
	unsigned int keycodesize;//单个按键码占用多少个字节
	void *keycode;//储存按键值数组=keycodemax*keycodesize
  
  //改变当前键盘映射的可选方法,用于实现稀疏键盘映射。如果未提供,将使用默认机制。该方法在持有event_lock时被调用,因此不能休眠
	int (*setkeycode)(struct input_dev *dev,
			  const struct input_keymap_entry *ke,
			  unsigned int *old_keycode);
	//可选的旧方法来检索当前的键盘映射。		  
	int (*getkeycode)(struct input_dev *dev,
			  struct input_keymap_entry *ke);

	//如果设备支持力反馈效果,则与设备关联的力反馈结构
	struct ff_device *ff;  
	unsigned int repeat_key;//存储最后按下的键的键码; 用于实现软件自动重复
	struct timer_list timer;//用于软件自动重复的定时器
	int rep[REP_CNT];//自动重复参数的当前值(延迟、速率)

	struct input_mt *mt;//指向多点触控状态的指针
  
  //&struct input_absinfo元素数组,保存有关绝对坐标的信息(当前值、最小值、最大值、平面、模糊、分辨率)
	struct input_absinfo *absinfo;

	unsigned long key[BITS_TO_LONGS(KEY_CNT)];//反映设备按键/按钮的当前状态
	unsigned long led[BITS_TO_LONGS(LED_CNT)];//反映当前LED的状态
	unsigned long snd[BITS_TO_LONGS(SND_CNT)];//反映当前音效状态
	unsigned long sw[BITS_TO_LONGS(SW_CNT)];//反映当前设备开关状态
	
	//当第一个用户调用 input_open_device()时调用此方法。驱动程序必须准备设备以开始生成事件(启动轮询线程、请求 IRQ、提交 URB 等)
	int (*open)(struct input_dev *dev);
	//当最后一个用户调用 input_close_device()时调用此方法。
	void (*close)(struct input_dev *dev);
	//清除设备。 最常用于在与设备断开连接时摆脱加载到设备中的力反馈效果
	int (*flush)(struct input_dev *dev, struct file *file);
	//发送到设备的事件的事件处理程序,如EV_LED或EV_SND。设备应执行请求的操作(打开LED,播放声音等)。调用受@event_lock 保护,不得休眠 最常见的例子是键盘上面的大写灯
	int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
 //当前已抓取设备的input handle(通过EVIOCGRAB ioctl),当handle抓取设备时,它成为来自设备的所有输入事件的唯一接收者
	struct input_handle __rcu *grab;

 //当输入核心接收并处理设备的新事件(在 input_event()中)时,将使用此自旋锁。 在设备向输入核心注册后访问和、或修改设备参数(例如 keymap 或 absmin、absmax、absfuzz 等)的代码必须获得此锁。
	spinlock_t event_lock;
	//序列化对 open()、close() 和 flush() 方法的调用
	struct mutex mutex;

//存储打开此设备的用户数(输入处理程序)。 input_open_device() 和 input_close_device() 使用它来确保仅在第一个用户打开设备时调用 dev->open() 并在最后一个用户关闭设备时调用 dev->close()
	unsigned int users;
//标记处于注销过程中并导致input_open_device*()与-ENODEV 故障的设备。
	bool going_away;

	struct device dev;//此设备的驱动程序模型视图

	struct list_head	h_list;//与设备关联的input handle 链表。 访问链表时必须持有dev->mutex
	struct list_head	node;//用于将设备放到 input_dev_list 上

	unsigned int num_vals;//当前帧中排队的值的数量
	unsigned int max_vals;//帧中排队的最大值数
	struct input_value *vals;//当前帧中排队的值数组

	bool devres_managed;//表示设备由devres框架管理,不需要明确取消注册或释放。

	ktime_t timestamp[INPUT_CLK_MAX];//存储由驱动程序调用的input_set_timestamp设置的时间戳
};

input handler

input handler表示一个或者一类输入设备的事件处理程序,它处理来自底层input 设备的输入事件,同时对应用层提供file_operations接口,用户层可以通过openread系统调用获取input 设备的输入事件。

struct input_handler {

	void *private;//驱动私有数据
 
 //事件处理程序。这个方法被input core调用,中断被禁用并且dev->event_lock自旋锁保持,因此它可能不会休眠
	void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
	
	//事件序列处理程序。 这个方法被input core调用,中断被禁用并且dev->event_lock自旋锁保持,因此它可能不会休眠
	void (*events)(struct input_handle *handle,
		       const struct input_value *vals, unsigned int count);
	//类似于@event; 将普通事件处理程序与“过滤器”分开。	       
	bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
	
	//在将设备的id 与处理程序的id_table 进行匹配后调用,进行更精确的匹配,一般此函数为空
	bool (*match)(struct input_handler *handler, struct input_dev *dev);
	
	//在将设备的id 与处理程序的id_table 进行匹配成功后调用,作用是将input dev和handler建立联系
	int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
	
	//设备或者驱动程序取消注册时调用,取消input 设备和handler之间的关联
	void (*disconnect)(struct input_handle *handle);
	
	//启动给定句柄的处理程序。 该函数在 connect() 方法之后以及当“抓取”设备的进程释放它时由输入核心调用
	void (*start)(struct input_handle *handle);
	
  //当使用旧版小范围的驱动程序时设置为%true
	bool legacy_minors;
	
	int minor;
	const char *name;//驱动程序的名称,显示在 /proc/bus/input/handlers 中

	const struct input_device_id *id_table;//指向该驱动程序可以处理的 input_device_ids 表的指针

	struct list_head	h_list;//与该驱动程序关联的input handle列表
	struct list_head	node;//用于将驱动程序放到 input_handler_list 上
};

input handler关联到input device并创建input handle。 可能有多个input handler同时关联到任何给定的input device。 它们都将获得由设备生成的输入事件的副本。
filter()event()使用的参数完全一致。input core允许filter()首先执行,并且如果任何过滤器指示应过滤事件(通过从其 filter() 方法返回 %true ),则不会将事件传递给常规input handlers

input handle

input handle用来关联input deviceinput handler

struct input_handle {

	void *private;//私有数据

	int open;//显示该handle是否被打开
	const char *name;//handler创建此handle时指定的名字
 
	struct input_dev *dev;//需要关联的input dev
	struct input_handler *handler;//与dev匹配的handler

	struct list_head	d_node;//用于将此handle添加到dev的h_list
	struct list_head	h_node;//用于将此handle添加到handler的h_list
};

input core

input core 维护两个全局链表,分别是存放input 设备的input_dev_list和input handler的input_handler_list,设备和事件的注册需要向这两个链表中添加新的节点,反之则需要删除节点。

在这里插入图片描述不论是设备还是事件注册的过程中,都会进行匹配,从而将设备和事件关联起来。匹配过程如下,以input设备注册为例说明:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

当新的input设备进行注册时,会遍历所有的input_handler_list中的事件驱动一一进行匹配,匹配的条件有bustype,vendor,product,version,evbit等。当匹配成功后,再接着执行handler->connect()执行剩下的操作。
实际上,如果是input事件驱动进行注册,流程也是差不多的,会遍历input_dev_list中所有的input 设备依次进行匹配,匹配成功后,
依然会执行handler->connect()执行剩下的操作。

connect

前文提到的设备和事件驱动匹配只是说它们互相找到了对方,但是还没有建立任何联系,handler->connect()是最终实现input设备和事件驱动绑定的函数。为什么需要connect函数进行绑定,而不是直接在注册阶段把driver的函数指针赋值给device的某个结构体成员?或者把device的函数指针赋值给driver的某个结构体成员?这是因为input设备是支持一对多或者多对一的,如果是一对一的关系,那么完全可以按照上述的做。
于是Linux 内核引入了另一个成员来完成此工作——struct handle(注意和struct handler的区别)
在这里插入图片描述
devicehandler匹配后,connect函数中会将devicehandler都存入handle结构体,然后再将handle结构体分别添加到devicehandlerh_list链表中,这样devicehandler不管是一对一还是一对多,都可以找到所有的handler或者device
本质上就是将device对应的handler用链表保存起来。

connect函数不仅实现了input handle的注册,还完成了input cdev的注册,每一次connect都会触发注册一个input handleinput cdev,而这个cdev就对应/dev/input/eventX.

这篇关于Linux input子系统(一)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!