input输入子系统


前言

本文仅作为学习笔记总结


提示:以下是本篇文章正文内容,下面案例可供参考

一、输入子系统的作用

  1. 兼容所有输入设备——Linux系统支持的输入设备繁多,例如键盘、鼠标、触摸屏、手柄或者是一些输入设备像体感输入等等,Linux系统为了管理如此之多的不同类型、不同原理、不同的输入信息的,引入了输入子系统,统一了物理形态各异的相似的输入设备的处理功能。
  2. 统一的应用操作接口——提供了用于分发输入报告给用户应用程序的简单的事件(event)接口。你的驱动不必创建、管理/dev节点以及相关的访问方法。因此它能够很方便的调用输入API以发送鼠标移动、键盘按键,或触摸事件给用户空间。
  3. 统一的编程驱动方法——抽取出了输入驱动的通用部分,简化了驱动,并提供了一致性。

二、输入子系统框架分析

1.输入子系统架构图

在这里插入图片描述

在这里插入图片描述

2.输入子系统分析

由上面的图我们可以看到,输入子系统由三部分组成:Event handler、input core、Input driver,我们接下来会主要通过查看源码来分析这三部分各自的功能及之间的联系。

2.1 核心层分析

核心层代码的实现是在drivers/input/input.c中,我们从该文件开始分析:
首先我们看到整个文件是一个驱动模块,很显然输入子系统是作为一个模块存在的,然后我们找到入口函数:
在这里插入图片描述
在这里插入图片描述
由上图可知,入口函数注册了一个input的类,然后在proc下创建了相关的文件,之后与input_fops绑定,注册了驱动,这就是入口函数做的工作,我们发现这里并没有在该类下面创建设备文件,那以后我们需要读取一个输入的时候,比如说按键输入,我们怎么获取信息呢?先不着急,肯定会在后面创建一个设备文件的。而当我们打开相应的设备文件获取信息的时候,就会调用这里的input_fops中的.open函数。我们分析一下.open函数的作用:

在这里插入图片描述
从截图中的四步可以看到,.open主要是根据次设备号在一个input_table【】的全局数组中找到一个对应的handler,然后执行该handler的fops赋值给file_f_op,然后执行新的open函数。从上面的步骤我们可以分析出来,input.c中的fops.open函数其实就是起一个承接的作用,并没有另外实质的作用。
分析到这里有两个疑惑:

  1. handler哪来的?input_table数组成员哪来的?
  2. 什么时候创建设备文件呢?主次设备号哪来的?

接下来我们分析解答这两个问题。

2.2 事件层分析

我们知道输入子系统的其中一个作用是提供统一的应用操作接口,事件层就实现了该部分的功能,这部分是纯软件的实现。就是说我们的应用层读取输入设备的时候,所有的接口都是在这里实现的。
上面的第一个疑问handler哪来的?input_table数组成员哪来的?这里我们解释一下,input_table初始化之后是一个空数组,然后我们在注册一个handler的时候,会把该handler放到该数组中。注册函数如下:

在这里插入图片描述
如下图所示,有evdev.c(事件设备),tsdev.c(触摸屏设备),joydev.c(joystick操作杆设备),keyboard.c(键盘设备),mousedev.c(鼠标设备) 这5个内核自带的设备处理函数注册到input子系统中,我们以evdev为例说明。
在这里插入图片描述
打开evdev.c文件,看到也是一个模块文件,可知当模块加载的时候,就用调用初始化函数,我们这里来看一下,加载函数会直接调用input_register_handler()向input中注册一个handler,即会根据.minor将该handler保存到handler_table【】中,然后将该handler加入到内核系统中的handler链表中,加入到链表中干什么呢?他会根据.id_table与设备进行匹配。至于怎么匹配,我们介绍了设备添加之后再分析。
在这里插入图片描述
到此,我们知道,当打开一个设备文件的时候调用.open函数,最终就会把fops替换成handler.fos,这里即evdev_fops,我们在应用层open,read等操作就都对应这些接口了。
在这里插入图片描述

2.3 input device driver层分析

设备层其实就是说一个外部的输入设备怎么加入到系统中来,并且怎么通过系统将它的数据传给应用层去。之前我们分析handler的时候说过,每注册一个handler的时候,都会将这个handler与一个设备链表中的每一项进行匹配。这个设备链表怎么来的呢?我们这里先分析一下input_dev的注册函数,由于内容比较多,我们这里只借去出这三个函数,可以看到,注册一个dev其实也会将这个dev加入到输入子系统的链表中,然后遍里handler的链表,进行匹配。上面没有介绍怎么匹配,这里介绍一下,下面三个图是匹配用的到结构体,input_dev中的id与handler->id_table进行匹配。如果匹配上了,就会进行一些操作,但是不会停止遍历链表继续匹配,所以说,可能会一个设备匹配到多个事件,当然反过来也有可能。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

int input_register_device(struct input_dev *dev)
{
	list_add_tail(&dev->node, &input_dev_list);
	list_for_each_entry(handler, &input_handler_list, node)
	input_attach_handler(dev, handler);

}

上面提到,匹配之后会进行一些操作,到底进行什么操作呢?其实匹配成功之后会调用如下函数,即handler对应的connect。该函数我就不列出源码来分析了,只是说一下该函数的功能:

  1. 分配一个input_handle结构体。
  2. input_handle.dev = input_dev; // 指向左边的input_dev
    input_handle.handler = input_handler; // 指向右边的input_handler
  3. input_handler->h_list = &input_handle;
    inpu_dev->h_list = &input_handle;

其实简单一点说,就是弄一个链表,把包含了handler与对应的dev成员的结构体变量handle保存在该链表中,并且把原来的handler与dev中的一个指针指向该handle,这样做的目的就是让任何一个handler或者dev能找到对应的handle,以至于进一步找到对应的handler或者dev。并且该函数还会调用class_device_create来创建字符设备文件,这也就解释了我的第二个疑惑,主次设备号哪来的?其实主设备号是在事件层写死的,次设备号事件层也规定好了范围,会自动找到还没有用到的次设备号,然后生成一个字符设备文件,当想要访问设备的时候,直接打开该文件就行了。
在这里插入图片描述

3. input事件分析

其实说了这么多,输入子系统的出现其实目的很简单,就是为各种各样的输入设备提供一个统一的上传数据的接口,这个统一的接口类型是定义在input.c中的。数据类型如下:
在这里插入图片描述

在这里插入图片描述

上面这个结构体就是input子系统中用来传递信息的结构体,可以看到,每个信息都有一个时间,及该事件的类型,事件的健值及值。现在可能会又有疑惑,什么是类型?什么是健值?好,下面解释:
(1)type:即事件的类型
input系统中你定义了如下的事件类型
在这里插入图片描述
一个设备可以有多个设备类型,比如鼠标,移动即出发了相对坐标事件,点击出发按键事件。

(2)code:即事件的健值
code指事件的键值,在事件类型中的子事件,每一个事件类型都有其对应的一系列键值
例如按键事件,那么你这个按键表示按键1还是按键2还是鼠标左键
例如绝对位置事件,那么你上报的这个事件是指X轴还是Y轴
input.h中定义了如下健值:
按键类型的
在这里插入图片描述
相对位置
在这里插入图片描述

绝对位置
在这里插入图片描述

(3)value
value是code(键值)对应的值,其解释随code类型的变化而变化
例如在按键事件中,如果code表示按键1,那么value等于1表示按键1按下,等于0等于按键1未按下
在绝对位置事件中,如果code表示X轴坐标,那么value就表示触摸点在X轴坐标的位置


三、按键驱动实例

上面分析了很多,但是有的地方也很笼统,这里就举个简单的按键例子。
其实向输入子系统注册一个设备很简单,分为如下几步:

  1. 分配一个input_dev结构体
  2. 设置
  3. 注册
  4. 硬件相关的代码,比如在中断服务程序里上报事件

下面列出jz2440_v3的按键加入到出入子系统中的代码:

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");

这里再介绍一下input_event函数:在这里插入图片描述

总结

一个输入子系统弄了整整两天,路漫漫其悠远兮啊

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值