Part15: 内核的输入子系统(input-subsystem)【待完善】

0 明确输入子系统组成、目的
对于内核的输入子系统,网上一堆文章。但是很少提及到输入子系统的目的何在?
此外,输入子系统三大层之间的划分缘由?(网上很多只是介绍是什么,而没提及分层的目的)
因此,在讲解之前,先阐明以上两点。
1)输入子系统的目的
	站在三个角度考虑:
	1.1)站在设备驱动程序角度
	设备驱动程序的目的很简单,它只想轻松融入进内核贡献它自己对硬件的操作功能,另外,它肯定不想
	与应用程序打交道(如数据交互),它只想专注操作自己的硬件。在这种要求下,内核肯定不能因为
	自身的复杂性给设备驱动和应用程序带去麻烦,因此内核实现一个子系统,管理这些繁琐事。
	
	1.2)站在应用程序角度
	众所周知,Linux的“一切皆文件”的抽象,硬件设备也不例外。为做到操作设备也像操作文件一样,必须
	抽象出一个接口屏蔽硬件操作的细节(类似VFS),这点很好理解。
	
	1.3)站在内核角度
	在设备驱动种类繁多的情况下,内核必须抽象出这些设备驱动的共性,即为驱动提供一个接口,方便
	注册进内核,实现动态加载以拓展内核功能。另外,在用户程序不想理众多繁琐的硬件操作,内核也必须
	对自己的承诺("一切皆文件")负责。因此在这两难的情形下,内核必须接管这些事情。而这也难不倒内核,
	毕竟抽象可是内核擅长的呀!(如虚拟内存/VFS等),弄一个抽象的接口不就完事了吗?
	对的,输入子系统就诞生了!
	
2)输入子系统三层划分依据
	上面理清了输入子系统的目的/任务之后,开始干活!
	鉴于上面三个角度,需考虑几个问题:
	a. 如何方便内核驱动程序注册到内核?—— 设备驱动层(Input Driver)
	b. 如何通知用户程序?—— 事件处理层(Event Handler)
	c. 如何连接驱动程序与应用程序?—— 输入核心层(Input core)
	因此,输入子系统由三层组成:设备驱动层、输入核心层、事件处理层
	它们三个的目的也很明显,就是提问的内容。
	三者的联系一句话可理解:设备驱动层注册到内核后,把设备的输入(如按键/滑动/点击)当作事件处理,
	在输入核心层的传导下,交给事件处理层,由它负责通知用户程序,完成数据交互。
	(用户程序到硬件的数据传输反过来理解即可)

	接着,下面详细介绍这三层的作用、联系(重点)
1 输入子系统详解

对于输入子系统的讲解,我打算用问题引导的方式,以理清子系统整个系统。
1.输入子系统是什么?
源码组成:在drivers/input目录下,看下组成
输入子系统源码组成
2.直观理解
/dev/input&/sys/class/input
从上面两张图可以看出,输入子系统不仅支持很多设备,而且会自动在/dev创建设备节点。
其实,输入子系统提供了一种上传下达的机制,方便设备驱动程序注册到输入子系统,且给应用程序提供一套
抽象的接口(open/read/write/poll等)以屏蔽不同的硬件操作。这种机制在看完下文会有更深的体会,暂且记住。

2.输入子系统其实也是一种驱动,那输入子系统如何屏蔽硬件操作,提供一套抽象接口给用户呢 ?
在drivers/input/input.c 核心文件中,有如下函数(备注:err返回值还需有判断的,我去掉了,免得影响理解)
输入子系统的组成
备注:moduel_init 和 subsys_initcall本质都是一样的,即都是linux内核段属性机制,用于把input_init / input_exit放在
不同段而已。可参考https://blog.youkuaiyun.com/TongxinV/article/details/54754901

很奇怪的是,之前写的led/按键驱动程序的file_operations还有其它成员,如 .read .write .poll 等等,为啥这里只有 .open
那这样的话,应用程序如何通过open/read/write等接口访问硬件呢?

别急,先看看 input_open_file 函数做了什么
input_oepn_file
从上图可以看出,虽然前面的file_operations只有open函数,但最终调用的是input_table[8]这个数组里成员(类型:input_handler)的fops指向的open函数
可见,接下来肯定要看看input_table数组是哪里初始化的?成员有谁?
input_register_handler
接着,看看谁调用了input_register_handler就清楚 input_table[] 数组的成员有谁了
谁调用input_register_handler
好了,从上面几张图可以总结出一些东西了,这里理清一下前面讲的
1.输入子系统是什么?
答:不过是一个驱动模块,跟之前写字符/按键驱动套路大体相同,区别在于不会立马创建设备节点(有驱动设备注册时才创建,如上面提到的input_table的成员,它们(evdev/mousedev/joydev/kbd等)通过input_register_handler注册时,就会创建
设备节点,如上图在 dev/input 目录下看到的event0/1/2/3/4 这些就是)

2.输入子系统其实也是一种驱动,那输入子系统如何屏蔽硬件操作,提供一套抽象接口给用户呢 ?
答:输入子系统–>input_init–>1.注册input类(在/sys/class/input) 2.在/proc目录下生成一堆文件
3. register_chrdev注册字符设备
在file_operations 成员.open = input_open_file()->input_table[x].handler->fops->open()
即input子系统的open函数最终会调用 input_table[x]结构体指针数组某个成员的fops所指向的open函数
好吧,有点绕 :)
另外,input_table[]数组有8个元素,这个数组在input_register_handler被初始化,被谁呢?
evdev/mousedev/joydev/kbd等
即,input_table[] 的成员有evbug_handler、evdev_handler、joydev_handler、kbd_handler、mousedev_handler
、rfkill_handler、tsdev_handler7个,它们类型都是 input_handler结构体,如下
在这里插入图片描述
备注:红框的都是重点,理解了谁使用/调用它们,整个输入子系统就理解了。

其实,上面7个成员就是跟用户程序打交道的!只是它们作用不同
evdev_handler——事件处理层,它支持所有设备,并将输入事情通知用户程序,或反过来,通知驱动程序
joydev_handler——游戏杆设备处理,游戏杆有不同类型,它只处理支持joydev_handler类型的设备,并由它
负责与应用程序数据交互
mousedev_handler——同理,鼠标也有不同类型呀,它支持的鼠标类型设备才能处理,上传给用户程序
其它的一样,只是针对不同类型设备,处理不同而已。
这时候就可以引入输入子系统框架了,如下图
输入子系统框架
可见,evdev.c tsdev.c joydev.c…等等这些一同构成 事件处理层,负责与用户程序打交道
声明一点:不要嫌我废话多,哈哈,因为我想讲清楚,讲解网上其它输入子系统博文没讲到的或讲得不够详细的
当然,这些都是我的理解,但我是在阅读源码的基础上才有信心说的,望理解:)

既然,输入子系统分了3层,那解决三个问题就可以理解整个子系统了。

  1. 事件处理层 如何注册到核心层,及 如何与用户程序进行数据交互
  2. 输入核心层如何连接设备驱动层与事件处理层
  3. 设备驱动层如何注册到核心层,及 如何通知事件处理层

下面主要依次回答这三个问题
1)事件处理层 如何注册到核心层,及 如何与用户程序进行数据交互
前面不是说,evbug_handler、evdev_handler、joydev_handler、kbd_handler、mousedev_handler
、rfkill_handler、tsdev_handler通过input_register_handler()初始了input_table()表
这里以 evdev.c 注册为例,讲解最终如何与用户程序交互
evdev

这里,以一个例子回答上面三个问题把!

#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <asm/gpio.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>

struct pin_desc{
	int irq;
	char *name;
	unsigned int pin;
	unsigned int key_val;
};

struct pin_desc pins_desc[4] = {
 	{IRQ_EINT0,  "S2", S3C2410_GPF0,   KEY_L},
	{IRQ_EINT2,  "S3", S3C2410_GPF2,   KEY_S},
	{IRQ_EINT11, "S4", S3C2410_GPG3,   KEY_ENTER},
        {IRQ_EINT19, "S5",  S3C2410_GPG11, KEY_LEFTSHIFT},
};

static struct input_dev *buttons_dev;
static struct pin_desc *irq_pd;
static struct timer_list buttons_timer;

static irqreturn_t buttons_irq(int irq, void *dev_id)
{
 	/* 10ms后启动定时器 */
 	irq_pd = (struct pin_desc *)dev_id;
 	mod_timer(&buttons_timer, jiffies+HZ/100);
 	return IRQ_RETVAL(IRQ_HANDLED);
}
static void buttons_timer_function(unsigned long data)
{
 	struct pin_desc * pindesc = irq_pd;
	unsigned int pinval;
 	if (!pindesc)
  		return;
	pinval = s3c2410_gpio_getpin(pindesc->pin);
 	if (pinval)
 	{
 	 	/* 松开 : 最后一个参数: 0-松开, 1-按下 */
 	 	input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);
 	 	input_sync(buttons_dev);
 	}
 	else
 	{
  		/* 按下 */
  		input_event(buttons_dev, EV_KEY, pindesc->key_val, 1);
  		input_sync(buttons_dev);
 	}
}
static int buttons_init(void)
{
 	int i;
 	/* 1. 分配一个input_dev结构体 */
 	buttons_dev = input_allocate_device();
 	
 	/* 2. 设置 */
 	/* 2.1 能产生哪类事件 */
 	set_bit(EV_KEY, buttons_dev->evbit);
 	set_bit(EV_REP, buttons_dev->evbit);
 	
 	/* 2.2 能产生这类操作里的哪些事件: L,S,ENTER,LEFTSHIT */
 	set_bit(KEY_L, buttons_dev->keybit);
 	set_bit(KEY_S, buttons_dev->keybit);
 	set_bit(KEY_ENTER, buttons_dev->keybit);
 	set_bit(KEY_LEFTSHIFT, buttons_dev->keybit);
 	
 	/* 3. 注册 */
 	input_register_device(buttons_dev);
 	
 	/* 4. 硬件相关的操作 */
 	init_timer(&buttons_timer);
 	buttons_timer.function = buttons_timer_function;
 	add_timer(&buttons_timer);
 	for (i = 0; i < 4; i++)
 	{
  		request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, 
  		pins_desc[i].name, &pins_desc[i]);
 	}
 	return 0;
}

static void buttons_exit(void)
{
	int i;
 	for (i = 0; i < 4; i++)
 	{
  		free_irq(pins_desc[i].irq, &pins_desc[i]);
 	}
 	del_timer(&buttons_timer);
 	input_unregister_device(buttons_dev);
 	input_free_device(buttons_dev); 
}

module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL");

备注:以上代码来自韦老师第2期的源码

测试结果如下
在这里插入图片描述
下面分析这个例子
例子分析1
接下来,两个结构体input_dev 与 input_handler 通过中间体 input_handle结构体 建立连接
粘合剂
input_handle就是个粘合剂,连接了Input_dev 和 input_handler
这样不管是input_dev还是input_hander就可以通过input_handle找到彼此,
对于input_dev, 通过input_handle找到input_handler结构体,然后就可以调用它fops所指向的函数(与用户程序交互的函数)
对于input_handler, 通过input_handle找到input_dev即可调用input_dev结构体里函数(硬件相关函数)

以上解析暂时讲到这把,输入子系统不是一般的复杂。
我找到一篇根据韦老师教程视频而整理的博文,我讲的不好。
请参考:https://www.cnblogs.com/lifexy/p/7542989.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值