input输入子系统框架分析

input子系统的搭建要点

核心层为事件驱动层和设备驱动层的注册提供API的实现、核心层为设备驱动层上报事件提供API的实现 、事件驱动层为应用层提供API的实现 。

(1)核心层:提供事件驱动层和设备驱动层所需的函数接口(为input dev和input handler建立联)

drivers/input/input.c:

##主要接口函数一览:##

为事件驱动层提供的:

注册API:

int input_register_handler(struct input_handler *handler);

void input_unregister_handler(struct input_handler *handler);

为设备驱动层提供的:

注册API:

struct
input_dev *input_allocate_device(void);

void input_free_device(struct input_dev *dev);

int input_register_device(struct input_dev *dev);

void input_unregister_device(struct input_dev *dev);

设备驱动要用到的上报输入事件函数API:

void input_event(struct input_dev *dev,unsigned int type, unsigned int code, int value);//下面是这个函数的特殊定义

static inline void input_report_key(struct input_dev *dev, unsigned int code, int value);

static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value);

static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value);

##上面这些接口函数的实现过程几乎就是input子系统的框架搭建过程:##

先来分析input_register_handler()接口函数实现注册input_handler:

input_register_handler(struct input_handler *handler)
{
......
	// ①放入数组:根据次设备号就可以查找到,方便应用层快速调用
	input_table[handler->minor >> 5] = handler;//把传进来的handler结构体根据他自身的minor次设备号放到input_table[]数组对应的位置

	// ②链入链表:方便遍历
	list_add_tail(&handler->node, &input_handler_list);

	// ③在input_dev_list链表中主动找匹配的input_dev:
	list_for_each_entry(dev, &input_dev_list, node) //for循环的宏定义
		input_attach_handler(dev, handler);
			id = input_match_device(handler->id_table, dev); // 根据input_handler的id_table判断能否支持这个input_dev
			error = handler->connect(handler, dev, id); //match成功的话调用
......
}	

完全可以想象只要有调用input_register_handler()就会根据minor来填充input_table[]对应的项,这点必须值得肯定!

在内核源码里搜一搜input_register_handler在哪里被调用:仅仅就下面这几个文件,only!

Evdev.c (drivers\input): return input_register_handler(&evdev_handler);

Joydev.c (drivers\input): return input_register_handler(&joydev_handler);

Keyboard.c (drivers\char): error = input_register_handler(&kbd_handler);

Mousedev.c (drivers\input): error = input_register_handler(&mousedev_handler);

这个函数的功绩:把input_handler结构体填入input_table[],同形成链表-->为下一步寻找他的天生一对“dev”做准备

现在input子系统就有了存放input_handler结构体数组,并且具备主动去寻找handler对应的input device的能力。

分析input_register_device()接口函数实现注册输入设备:

input_register_device(struct input_dev *dev)
{
......
	// 链入链表
	list_add_tail(&dev->node, &input_dev_list);

	// 在input_handler_list链表中主动找匹配的input_handler:
	list_for_each_entry(handler, &input_handler_list, node)
		input_attach_handler(dev, handler);
			id = input_match_device(handler->id_table, dev);// 根据input_handler的id_table判断能否支持这个input_dev
			error = handler->connect(handler, dev, id); //如果能支持,则调用input_handler的connect函数建立"连接"
......
}

也在内核源码里边来搜一搜input_register_device函数在哪些地方被调用:

结果就不列出来了,实在太多文件中都有调用。从这个事实也可以察觉到:input_dev远远多于input_handler。因为很多输入设备的事件处理过程是一样的,这样input_handler的代码复用率就大大提高了!

这函数的功绩:让输入设备形成链表,新增加input_dev时能够主动去寻找匹配的input_handler。

③handler和dev是如何建立联系的?

容易发现input_register_handler()和input_register_device()如果match成功最后都是调用handler->connect函数,因此非常有必要来分析这个函数的实现过程,由于上面这两个函数都是通过connect函数指针来调用的,要继续分析下去就必须要分析这个指针具体指向的函数实体,毋庸置疑函数指针是搭建框架的利器!

connect函数指针是input_handler结构体的成员,可以猜测,每一类handler就会有他独有的connect函数实体!

既然这样就到evdev.c (drivers\input)文件中找找看:果不其然,找到了!

evdev_connect(struct input_handler *handler, struct input_dev *dev,const struct input_device_id *id)
{
	......
	//1. 分配一个input_handle结构体
	evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
	//2.设置这个结构体
	evdev->minor = minor;
	evdev->handle.dev = input_get_device(dev); // 指向input_dev
	evdev->handle.name = evdev->name;
	evdev->handle.handler = handler; //指向input_handler
	evdev->handle.private = evdev;
	//3.把这个结构体注册到input_handler和input_dev结构体
	error = input_register_handle(&evdev->handle);//下面进一步分析这个函数就知道究竟是怎样让handler和dev建立联系的!
	......
}

input_register_handle函数实现:

input_register_handle();
{
......
	list_add_tail_rcu(&handle->d_node, &dev->h_list);
	list_add_tail(&handle->h_node, &handler->h_list);
......
}

小结一下这一点的内容:

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;

这样一来connect之后,只要知道input_dev可以用他的h_list指针访问到对应的input_handle结构体的handler成员,从而也就找到了他对应的事件驱动handler,反过来也一样。

④设备驱动层上报事件input_event()接口函数分析:

input_event(struct input_dev *dev,unsigned int type, unsigned int code, int value)
{
	input_handle_event(dev, type, code, value);
	switch (type) {
		case EV_SYN:
		...
		case EV_KEY:
		...
		case EV_SW:
		...
		case EV_ABS:
		...
		case EV_REL:
		...
	}
	...
	//分析到这里要小心了,一开始以为是这个if条件成立,因为第一感觉就是input_device结构体的这个成员会在哪里被初始化,
	//然后就去直奔input_register_device函数里找啊找,半毛钱都没找到,原来本来就没有进行定义,所以dev->event为空,条件不成立!
	if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
		dev->event(dev, type, code, value);

	if (disposition & INPUT_PASS_TO_HANDLERS)//擦!从sourceInsi也可以发现INPUT_PASS_TO_HANDLERS这个宏是有定义的
		input_pass_event(dev, type, code, value);//这个就是设备驱动层上报事件的关键工作!
}
input_pass_event(struct input_dev *dev,unsigned int type, unsigned int code, int value)
{
	struct input_handle *handle;
	...
	list_for_each_entry_rcu(handle, &dev->h_list, d_node)
		if (handle->open)
			handle->handler->event(handle,type, code, value);//最终调用的是匹配的input_handler结构体的event成员
			//这就好办了,handler是事件驱动层,我们就拿event.c文件里的例子来继续分析
}
void evdev_event(struct input_handle *handle,unsigned int type, unsigned int code, int value)
{
	......
	wake_up_interruptible(&evdev->wait);//唤醒工作
}

小结:

核心层为设备驱动层提供上报事件接口input_event()函数,实际上就是实现了让设备驱动能够重定向到匹配自己的事件驱动层的事件处理函数xxx_event(),到这里核心层的工作基本上就完成。了。

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

(2)事件驱动层:为应用层提供API

这些API就是file_operation结构体成员的实现,这一层得以具体的某种handler来进行分析,因为这一层是一类事件处理方法,方法是具体的。

以evdev.c文件内容为例来进行分析:

①evdev_init()

input_register_handler(&evdev_handler)//通过核心层,可以和input_dev建立联系

②input_handler结构体的fops结构成员就是事件驱动层为应用层提供的API

static struct input_handler evdev_handler = {
	.event		= evdev_event,
	.connect	= evdev_connect,
	.disconnect	= evdev_disconnect,
	.fops		= &evdev_fops,
	.minor		= EVDEV_MINOR_BASE,
	.name		= "evdev",
	.id_table	= evdev_ids,
};
static const struct file_operations evdev_fops = {
	.owner		= THIS_MODULE,
	.read		= evdev_read, //以这个接口函数的实现进行分析
	......
};
evdev_read()
{
	.......
	// 无数据并且是非阻塞方式打开,则立刻返回
	if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))
		return -EAGAIN;
	// 否则进入休眠,上面刚刚提到:这里的休眠就是设备驱动层通过调用input_event()函数最终重定向到事件驱动层的evdev_event()函数来唤醒的
	retval = wait_event_interruptible(evdev->wait,
		client->head != client->tail || !evdev->exist);
	.......
}

小结:

对于事件驱动层,Linux内核已经整理了几套常用的handler,分别定义在上面我们有说到的这几个文件里:

Evdev.c (drivers\input)、Joydev.c (drivers\input)、Keyboard.c (drivers\char)、Mousedev.c (drivers\input),也就是说,对于驱动人员来说,添加输入设备的驱动,基本所有的工作都落在设备驱动层里!

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

(3)设备驱动层:负责和硬件输入设备通信,然后把情况上报给核心层

主要工作:

1. 分配一个input_dev结构体

2. 设置:能产生哪类事件、能产生这类操作里的具体哪些事件

比如说,我申请的一个GPIO引脚中断能够产生按键事件,那究竟是什么按键事件,哦,比如可以是我按下这个按键时是26个字母中的具体哪一个

3. 注册:把这个input_dev结构体链入输入设备链表,并遍历input_handler_list看看是否有匹配的驱动

4. 硬件相关的代码,比如在中断服务程序里上报事件

软件设计流程如下所示:

分配一个输入设备 --> 设置输入设备 --> 注册输入设备 --> 上报输入事件 --> 注销输入设备 --> 释放输入设备

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

总体概括:

事件驱动层和核心层都是通用的,我们需要实现的是设备驱动层。输入设备驱动会把硬件产生的事件信息用统一的格式(struct input_event)上报给核心层,然后核心层进行分类后,再上报给相应的事件处理驱动程序,最后通过事件层传递到用户空间,应用程序可以通过设备节点来获取事件信息。比如底层报上来的是一个按键事件,核心层会报给evdev来处理;如果报的是一个鼠标事件,核心层会报给mousedev来处理。最后附上一张经典的输入子系统的框架图。

时间: 2024-08-28 17:29:03

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

Linux输入子系统框架分析(1)

在Linux下的输入设备键盘.触摸屏.鼠标等都可以用输入子系统来实现驱动.输入子系统分为三层,核心层和设备驱动层,事件层.核心层和事件层由Linux输入子系统本身实现,设备驱动层由我们实现.我们在设备驱动层将输入事件上报给核心层input.c,核心层找到匹配的事件层,将事件交给事件层处理,事件层处理完后传递到用户空间. 我们最终要搞清楚的是在用户空间调用open和read最终在内核中是怎样处理的,向内核上报的事件又是谁处理的,处理完后是怎样传递到用户空间的? 上面两个图是输入子系统的框架. 下面

input_subsys 输入子系统框架分析

在linux内核中 已做好各类驱动的框架,驱动程序也属于内核的一部分,我们可以在原有的驱动上修改,来匹配我们自已的硬件,也可以自已编写符合内核驱动框架的驱动程序.出于学习的目的,便于更好的理解各类驱动的框架和编程思想,先分析内核自带的驱动框架和流程,再自已编写符合内核框架的驱动程序.下面开始,从输入子系统开始学习分析,后面一步一步涉及各类驱动. 一.输入子系统 从 drivers/input/input.c 这个文件开始分析,分析驱动程序的时候,先从其入口函数开始,因为每当加载一个驱动程序的时候

linux input输入子系统应用分析

输入设备(如按键.键盘.触摸屏.鼠标等)是典型的字符设备,其一般的工作机理是底层在按键.触摸等动作发送时产生一个中断(或驱动通过timer定时查询),然后CPU通过SPI.I2 C或外部存储器总线读取键值.坐标等数据,放入1个缓冲区,字符设备驱动管理该缓冲区,而驱动的read()接口让用户可以读取键值.坐标等数据. 显然,在这些工作中,只是中断.读值是设备相关的,而输入事件的缓冲区管理以及字符设备驱动的file_operations接口则对输入设备是通用的.基于此,内核设计了输入子系统,由核心层

linux input输入子系统分析《四》:input子系统整体流程全面分析

1      input输入子系统整体流程 本节分析input子系统在内核中的实现,包括输入子系统(Input Core),事件处理层(Event Handler)和设备驱动层.由于上节代码讲解了设备驱动层的写法,因此在开头部分会从设备驱动层做为线索,分析输入子系统和事件处理层是如何配合的,最后从用户角度出发,从"/dev/input/*"接口如何使用输入子系统提供的服务. 既然需要详细分析,有一个这样的流程图能够帮助我们在被绕进代码的过程中,找到出口,你能够知道你现在位于代码框架的什

input 输入子系统分析

//input 输入子系统分析//刘术河2016.08.22 linux-2.6.39-at91-2016.08.11-lsh\drivers\input\Input.c该文件下有input_register_device 和 input_register_handler 函数接口0.先分析input.c的核心层架构 input_init(void) class_register(&input_class); //注册一个类 register_chrdev(INPUT_MAJOR, "i

输入子系统 框架

目录 回顾引入 简介 框架小结 次设备号 框架结构图 数据管理结构 关键函数 框架分析 input_init input_open_file input_register_handler input_register_device input_attach_handler connect read 程序设计 无框架驱动 框架架构 测试 hexdump分析 tty读取分析 按键连发 title: 输入子系统 框架 tags: linux date: 2018-11-28 15:39:22 toc:

Linux驱动之输入子系统框架

    好记性不如烂笔头,整理一下笔记~ Linux驱动之输入子系统框架 输入子系统将该类驱动划分为3部分 1.核心层 input.c 2.设备层 Gpio_keys.c ... 3.事件处理层 Evdev.c 事件处理层为纯软件的东西,设备层涉及底层硬件,它们通过核心层建立联系,对外提供open write等接口. 1.我们首先来看,核心层 input.c如何向外界提供接口 在 input_init 中注册了字符设备驱动 register_chrdev(INPUT_MAJOR, "input&

输入子系统之按键驱动

上一篇博文<input输入子系统框架分析>,尝试使用这种驱动模型来看一个按键驱动程序. 下面的程序是根据韦东山老师写的代码进行修改的,我的开发板是tq2440. button.c文件: #include <linux/module.h> #include <linux/version.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/interrupt.h>

输入子系统分析 (二)

输入子系统是由输入子系统核心层( Input Core ),驱动层和事件处理层(Event Handler)三部份组成.一个输入事件,如鼠标移动,键盘按键按下,joystick的移动等等通过 input driver -> Input core -> Event handler -> userspace 到达用户空间传给应用程序. input子系统的3个基本的数据结构struct input_dev { struct list_head    h_list;    //h_list是一个