Linux2.6 input子系统分析

研读了一下input subsystem,分成三部分,

输入子系统核心层( Input Core ),驱动层和事件处理层(Event Handler)三部份组成。一个输入事件,如鼠标移动,键盘按键按下,joystick的移动等等通过 Driver -> InputCore -> Eventhandler -> userspace 的顺序到达用户空间传给应用程序。其中Input Core 即 Input Layer 由 driver/input/input.c及相关头文件实现。对下提供了设备驱动的接口,对上提供了Event Handler层的编程接口。

input_handle, input_handler, input_dev是input子系统中最重要的3个数据结构

 转两篇这方面文章,后面再了解一下andriod使用input subsystem的代码。

 

http://blog.csdn.net/walkingman321/article/details/5967890

Linux的input子系统提供了输入设备的驱动框架,比如鼠标、键盘、触摸屏等就属于输入设备。Linux中关于input子系统的文档在Documentation/input目录,input的核心代码在input.c和input.h中。

本文没有涉及input的一些细节实现,比如input_dev->grab,以及按键的定时事件等。

1.     input_handle, input_handler, input_dev

input_handle, input_handler, input_dev是input子系统中最重要的3个数据结构。

l         input_handler用于上层应用获取输入事件。上层应用打开输入设备的设备节点,然后对节点进行读写操作以获得鼠标移动信息,或者键盘信息等等。这里对设备节点的文件操作函数就是由input_handler提供。

l         input_dev代表的是具体的设备,比如鼠标、键盘等等。

l         对于一台Linux电脑,可能会连着多个鼠标、多个键盘。每一个鼠标都能控制光标的运动,每一个键盘也都能正常使用。这在input子系统中,体现为一个input_handler关联多个input_dev,能够同时从多个input_dev获取输入消息。与此同时,linux中可能会有多个input_handler同时与一个input_dev关联,这样,应用程序通过任何一个input_handler,都可以获得例如鼠标、键盘等具体设备的输入信息。所以,input_dev和input_handler之间是多对多的关联关系,而这些关联就是由input_handle表示。

1.1           handler与dev之间关联的建立

input_handle中包含一个input_dev的指针,和一个input_handler的指针,所以能建立handler和dev之间的一个一对一的关联。在input_handler中,有一个链表h_list,指向和这个handler关联的所有input_handle,通过这些handle就可以找到与handler关联的所有dev。同样的,在input_dev中,也有一个链表h_list,指向与dev关联的所有input_handle,通过这些handle可以找到与dev相关的所有handler。通过这两个链表和input_handle,input_handler和input_dev之间建立了一个复杂的网状结构。

那么,input_handler和input_dev之间建立关联的规则是什么?即在什么情况下需要建立关联,什么时候不需要建立关联?这就需要handler和dev之间有一个匹配机制。

input_handler中有两个指针,id_table和blacklist,其中blacklist是黑名单,凡是与之匹配的dev都将被强制过滤;而与id_table中任意一项匹配的dev才能与handler建立关联。

       const struct input_device_id *id_table;

       const struct input_device_id *blacklist;

       struct input_device_id {

              kernel_ulong_t flags;

              __u16 bustype;

              __u16 vendor;

              __u16 product;

              __u16 version;

              kernel_ulong_t evbit[INPUT_DEVICE_ID_EV_MAX / BITS_PER_LONG + 1];

              kernel_ulong_t keybit[INPUT_DEVICE_ID_KEY_MAX / BITS_PER_LONG + 1];

              kernel_ulong_t relbit[INPUT_DEVICE_ID_REL_MAX / BITS_PER_LONG + 1];

              kernel_ulong_t absbit[INPUT_DEVICE_ID_ABS_MAX / BITS_PER_LONG + 1];

              kernel_ulong_t mscbit[INPUT_DEVICE_ID_MSC_MAX / BITS_PER_LONG + 1];

              kernel_ulong_t ledbit[INPUT_DEVICE_ID_LED_MAX / BITS_PER_LONG + 1];

              kernel_ulong_t sndbit[INPUT_DEVICE_ID_SND_MAX / BITS_PER_LONG + 1];

              kernel_ulong_t ffbit[INPUT_DEVICE_ID_FF_MAX / BITS_PER_LONG + 1];

              kernel_ulong_t swbit[INPUT_DEVICE_ID_SW_MAX / BITS_PER_LONG + 1];

              kernel_ulong_t driver_info;

       };

在这个结构体中,flags中包含一些掩码:

#define INPUT_DEVICE_ID_MATCH_BUS 1

#define INPUT_DEVICE_ID_MATCH_VENDOR       2

#define INPUT_DEVICE_ID_MATCH_PRODUCT     4

#define INPUT_DEVICE_ID_MATCH_VERSION      8

这些掩码用来匹配bus类型、厂商、产品号、版本号等。如果选择不匹配这些信息,那么driver_info需要置位,以跳过flags的匹配过程。

evbit[INPUT_DEVICE_ID_EV_MAX / BITS_PER_LONG + 1]中包含支持的事件掩码。input子系统支持以下事件:

#define EV_SYN                  0x00              // EV_SYN用于标记一系列输入的结束

#define EV_KEY                  0x01              // 键盘输入

#define EV_REL                   0x02              // 鼠标的相对位移

#define EV_ABS                   0x03              // 触摸屏的绝对坐标

#define EV_MSC                  0x04

#define EV_SW                    0x05

#define EV_LED                  0x11              // 比如键盘的LED控制

#define EV_SND                  0x12

#define EV_REP                   0x14

#define EV_FF                     0x15

#define EV_PWR                 0x16

#define EV_FF_STATUS              0x17

如果handler支持某一种事件,那么evbit的对应位会置位。如果evbit的某一位置位了,那么结构体中与具体事件对应的掩码数组就会有效。例如,EV_KEY置位了,那么对应的keybit数组就有效,该数组里面定义了handler支持的具体key的类型。如果handler和dev需要匹配,那么dev必须能支持所有handler支持的事件;但是,handler却不一定要处理所有dev能提供的事件。

#define MATCH_BIT(bit, max) /

              for (i = 0; i < BITS_TO_LONGS(max); i++) /

                     if ((id->bit[i] & dev->bit[i]) != id->bit[i]) /

                            break; /

              if (i != BITS_TO_LONGS(max)) /

                     continue;

关于匹配的具体过程,可以参考函数input_match_device。

2.     input_dev

所有的input_dev都是虚拟设备,其class名为”input”,class的登记函数:

       class_register(&input_class);

所以,需要先注册一个物理设备,已此物理设备为父设备注册input_dev。

2.1           input_dev的注册

input_dev的注册由input_register_device函数完成。

int input_register_device(struct input_dev *dev)

       __set_bit(EV_SYN, dev->evbit);   // 强制设置EV_SYN位

       // 初始化定时器,REP_DELAY是第一次延时的超时值,REP_PERIOD是第一次延时            // 之后的超时值

       init_timer(&dev->timer);

       if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {

              dev->timer.data = (long) dev;

              dev->timer.function = input_repeat_key;

              dev->rep[REP_DELAY] = 250;

              dev->rep[REP_PERIOD] = 33;

       }

       // 设置默认的get key和set key函数

       if (!dev->getkeycode)    dev->getkeycode = input_default_getkeycode;

       if (!dev->setkeycode)    dev->setkeycode = input_default_setkeycode;

       // 设置设备名称,然后注册设备

       snprintf(dev->dev.bus_id, sizeof(dev->dev.bus_id),

               "input%ld", (unsigned long) atomic_inc_return(&input_no) - 1);

       device_add(&dev->dev);

       list_add_tail(&dev->node, &input_dev_list);       // 将设备加入input_dev_list链表

       // 对每一个已注册的handler,调用input_attach_handler

       list_for_each_entry(handler, &input_handler_list, node)

              input_attach_handler(dev, handler);      // 见下节

       // proc有关的操作

       input_wakeup_procfs_readers();

3        input_handler

struct input_handler {

       void *private;

       void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);

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

       const struct file_operations *fops;

       int minor;

       const char *name;

       const struct input_device_id *id_table;

       const struct input_device_id *blacklist;

       struct list_head       h_list;

       struct list_head       node;

};

3.1           input_handler的注册

int input_register_handler(struct input_handler *handler)

       // 将fops域登记到全局变量input_table

       input_table[handler->minor >> 5] = handler;

       // 将handler登记到链表input_handler_list

       list_add_tail(&handler->node, &input_handler_list);

       // 遍历已经登记的dev,对每个dev调用input_attach_handler(chapter 2.2)

       list_for_each_entry(dev, &input_dev_list, node)

              input_attach_handler(dev, handler);

       // proc有关的操作

       input_wakeup_procfs_readers();

3.2           input_attach_handler

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)

       // 匹配黑名单,如果设备在handler的黑名单里,返回

       …

       // 匹配handler中的id_table,如果匹配,则需要建立handler与dev之间的关联,于              // 是调用handler->connect

handler的connect函数中,会malloc一个handle,把handler和dev填到handle中对应的域,然后调用input_register_handle函数,以及input_open_file函数。

4.   input_handle

struct input_handle {

       void *private;

       int open;

       const char *name;                       // handle的名称

       struct input_dev *dev;                 // 关联的dev

       struct input_handler *handler;       // 关联的handler

       struct list_head       d_node;          // 通过这个域挂到dev的handle链表

       struct list_head       h_node;          // 通过这个域挂到handler的handle链表

};

4.1           input_handle的注册

int input_register_handle(struct input_handle *handle);

input_register_handle会在handler的connect函数中被调用,它会将handler挂到dev和handler的链表中。最后会调用handler->start函数。

5.   设备层

上层应用通过设备节点访问input子系统提供的服务,而input_handler为上层应用提供了接口,所以设备节点的文件访问函数理所当然由handler提供,这就是handler中的fops域。

 

input子系统本身也提供了一组文件读写操作函数,input_fops。

       register_chrdev(INPUT_MAJOR, "input", &input_fops);

这里的input_fops中仅包含open函数,open函数的作用是根据子设备号重新设置设备文件的file->f_op域(这个机制在LDD的书中有提及),被更新的fops保存在全局数组input_table中。

 

在注册handler时,会将handler登记到一个全局数组input_table,具体代码在input_register_handler中:

       input_table[handler->minor >> 5] = handler;

这个数组主要是保存了handler中的fops。另外,数组的下标是handler->minor >> 5,也就是说,一个handler可以占有32个子设备号(1 << 5)。

6.   事件的传递

input子系统的主要作用就是控制输入事件在handler和dev之间的传递。输入事件在input系统中的定义如下:

struct input_event {

       struct timeval time;

       __u16 type;

       __u16 code;

       __s32 value;

};

input_dev可以通过函数input_event传递消息。

void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)

       // 加锁、消息检验等

       …

       input_handle_event(dev, type, code, value);

 

void input_handle_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)

       判断消息是否需要传给handler和dev

       如果需要传给dev,调用dev->event(dev, type, code, value);  

       如果需要传给handler,调用input_pass_event(dev, type, code, value);

       input_pass_event遍历dev中所有handle列表,对每一个handle,调用event函数。

 

 

 

http://www.cnitblog.com/luofuchong/archive/2007/08/24/32382.html

前面对s3c2410的触摸屏驱动进行了分析,现深入一层,对其所在的输入子系统进行刺探。

首先引用一个不错的帖子,对2.6内核的输入子系统进行一个大致的描述:

引:

在做触摸屏?对于输入子系统,相信你也早看了网上一些介绍文章文章了,
读一下就可了解对其基本架构,剩下的只是一些源码细节阅读。

输入子系统的3层间的联系是很简单的,驱动层的核心结构为struct input_dev:
struct input_dev {
    ...
    struct list_head        h_list;
    ...
};
在input_register_device时就会将input_dev与input_handle联系起来;
所谓联系就是将有关的input_handle链入以input_dev中h_list为Hash头的链中;

而事件处理层的核心结构是struct input_handler:
struct input_handler {
    ...
    struct list_head        h_list;
    ...
};
在input_register_handler时同样会将input_handler与input_handle联系起来,
所谓联系就是将有关的input_handle链入以input_handler中h_list为Hash头的链中;

由上可见input_handle即是一个用于关联驱动层input_dev和事件处理
层input_handler的中间结构:
struct input_handle {
    ...
    struct input_dev *dev;
    struct input_handler *handler;
    struct list_head        d_node;
    struct list_head        h_node;
};
其中d_node用于input_dev链,h_node用于input_handler链,有了input_handle,
就把相关dev与handler联系起来,相互能容易的找到。

注:
    原文请看一下网址:
http://bbs.ustc.edu.cn/cgi/bbstcon?board=Kernel&file=M.1179398612.A

看了以上的内容,相信你对2.6内核的输入子系统应该有个大概的了解了,
现在我就input_devinput_handle、input_handler这三者建立联系的过程进行详细的分析:

触摸屏驱动中,s3c2410ts_probe函数的最后一步,调用input_register_device函数开始进入三者建立联系的过程:

void input_register_device(struct input_dev *dev)
{
	struct input_handle *handle;
	struct input_handler *handler;
	struct input_device_id *id;
........................................................................................
	INIT_LIST_HEAD(&dev->h_list);
	list_add_tail(&dev->node, &input_dev_list);

	list_for_each_entry(handler, &input_handler_list, node)
		if (!handler->blacklist || !input_match_device(handler->blacklist, dev))
			if ((id = input_match_device(handler->id_table, dev)))
				if ((handle = handler->connect(handler, dev, id)))
					input_link_handle(handle);
..........................................................................................

}
注:
	我只保留重要的部分,省略号部分不是我关心的,以下同。

list_for_each_entry(handler, &input_handler_list, node)的作用在于:
从input_handler_list的链表中提取input_handler的指针。

##################################################################################
那这个input_handler的指针又是何时存放在input_handler_list链表里面的呢?
答案是像tsdev.c这些接口驱动里面调用input_register_handler
进而调用list_add_tail(&handler->node, &input_handler_list);
把其input_handler指针加进input_handler_list里面,详细请查看源码,在此不做详细分析。
###################################################################################

获取了input_handler指针后通过input_match_device进行匹配选择:
static struct input_device_id *input_match_device(struct input_device_id *id, struct input_dev *dev)
{
	int i;

	for (; id->flags || id->driver_info; id++) {
................................................................

		MATCH_BIT(evbit,  EV_MAX);
		MATCH_BIT(keybit, KEY_MAX);
		MATCH_BIT(relbit, REL_MAX);
		MATCH_BIT(absbit, ABS_MAX);
		MATCH_BIT(mscbit, MSC_MAX);
		MATCH_BIT(ledbit, LED_MAX);
		MATCH_BIT(sndbit, SND_MAX);
		MATCH_BIT(ffbit,  FF_MAX);
		MATCH_BIT(swbit,  SW_MAX);

		return id;
	}

	return NULL;
}

该函数拿刚才获得的input_handler指针所拥有的特性表handler->id_table
与我们所注册的input_dev的特性表dev.id进行对照。

仍以触摸屏驱动s3c2410-ts.c与触摸屏接口tsdev.c为例:
s3c2410-ts.c:
	ts.dev.evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);
	ts.dev.keybit[LONG(BTN_TOUCH)] = BIT(BTN_TOUCH);
	input_set_abs_params(&ts.dev, ABS_X, 0, 0x3FF, 0, 0);
	input_set_abs_params(&ts.dev, ABS_Y, 0, 0x3FF, 0, 0);
	input_set_abs_params(&ts.dev, ABS_PRESSURE, 0, 1, 0, 0);
	ts.dev.id.bustype = BUS_RS232;
	ts.dev.id.vendor = 0xDEAD;
	ts.dev.id.product = 0xBEEF;
	ts.dev.id.version = S3C2410TSVERSION;

tsdev.c:
static struct input_device_id tsdev_ids[] = {
	{
	      .flags	= INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT 
			| INPUT_DEVICE_ID_MATCH_RELBIT,
	      .evbit	= { BIT(EV_KEY) | BIT(EV_REL) },
	      .keybit	= { [LONG(BTN_LEFT)] = BIT(BTN_LEFT) },
	      .relbit	= { BIT(REL_X) | BIT(REL_Y) },
	 },/* A mouse like device, at least one button, two relative axes */

	{
	      .flags	= INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT 
			| INPUT_DEVICE_ID_MATCH_ABSBIT,
	      .evbit	= { BIT(EV_KEY) | BIT(EV_ABS) },
	      .keybit	= { [LONG(BTN_TOUCH)] = BIT(BTN_TOUCH) },
	      .absbit	= { BIT(ABS_X) | BIT(ABS_Y) },
	 },/* A tablet like device, at least touch detection, two absolute axes */

	{
	      .flags	= INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_ABSBIT,
	      .evbit	= { BIT(EV_ABS) },
	      .absbit	= { BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE) },
	 },/* A tablet like device with several gradations of pressure */

	{},/* Terminating entry */
};

可以看到,tsdev.c接口定义了三项特性,对应id为0、1、2,
input_match_device函数依次取出其中的选项与s3c2410-ts.c里面定义的input_dev的选项进行对比。
这里对比的标准是tsdev.c里面定义的选项s3c2410-ts.c里面必须满足,否则continue,继续判断下一个id号的选项。
详细请看MATCH_BIT这个宏的定义:
#define MATCH_BIT(bit, max) \
		for (i = 0; i < NBITS(max); i++) \
			if ((id->bit[i] & dev->bit[i]) != id->bit[i]) \
				break; \
		if (i != NBITS(max)) \
			continue;

例如:
	在这里,tsdev.c定义的id为0的选项里面定义的BIT(EV_REL)这一项
在s3c2410-ts.c里面定义的input_dev设备上是不具备的,
所以,执行到MATCH_BIT(evbit,  EV_MAX);后直接continue,
继续判断tsdev.c里面id为1的选项,直到找到合适的,然后返回真,否则返回NULL。

###########################################################################################
在list_for_each_entry(handler, &input_handler_list, node)
这个大循环里与我们所注册的input_dev所匹配的不限于一个接口,
例如,以下是我的调试记录:
s3c2410 TouchScreen successfully loaded
kbd
input_match_device

mousedev
input_match_device
mousedev_connect

joydev
input_match_device

evdev
input_match_device
evdev_connect

tsdev
input_match_device
tsdev_connect

evbug
input_match_device
evbug_connect
可以看到,对于s3c2410-ts.c里面定义的input_dev设备,同时与其匹配的就有
mousedev、evdev、tsdev、evbug等众多接口(不知道我的理解是否正确,如果理解错了,还望指正^_^)
###########################################################################################

找到匹配的选项以后,就可以开始着手把input_devinput_handle、input_handler这三者联系齐来了,具体调用
handle = handler->connect(handler, dev, id)函数,
主要的目的是填充input_handle结构,然后接着调用
input_link_handle(handle)函数:

static void input_link_handle(struct input_handle *handle)
{
	list_add_tail(&handle->d_node, &handle->dev->h_list);
	list_add_tail(&handle->h_node, &handle->handler->h_list);
}

看到吧,就是上面那位大侠提到的,把input_handle分别链入input_devinput_handler中h_list为Hash头的链中。

好了,到此,input_devinput_handle、input_handler这三者总算是联系起来了^_^

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值