天天看点

input 系统接口及分析

http://blog.csdn.net/lmm670/article/details/6081019

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

闲聊linux中的input设备(1-10)

接口

前面说了,linux内核input子系统中已经实现了input设备的接口函数,这使得我们工作量大大的减轻了。我们以akm8973芯片(用于智能手机指南针的主功能芯片,实际上就一电子罗盘)为例,来简单看一下写一个input设备我们需要做的工作。

首先,在驱动模块加载函数中申请一个input设备,并告知input子系统它支持哪些事件,如下所示:

akm->input_dev = input_allocate_device();

set_bit(EV_ABS, akm->input_dev->evbit);

input_set_abs_params(akm->input_dev, ABS_RX, 0, 23040, 0, 0);

input_set_abs_params(akm->input_dev, ABS_RY, -11520, 11520, 0, 0);

input_set_abs_params(akm->input_dev, ABS_RZ, -5760, 5760, 0, 0);

input_set_abs_params(akm->input_dev, ABS_THROTTLE, -30, 85, 0, 0);

input_set_abs_params(akm->input_dev, ABS_RUDDER, 0, 3, 0, 0);

input_set_abs_params(akm->input_dev, ABS_HAT0X, -2048, 2032, 0, 0);

input_set_abs_params(akm->input_dev, ABS_HAT0Y, -2048, 2032, 0, 0);

input_set_abs_params(akm->input_dev, ABS_BRAKE, -2048, 2032, 0, 0);

以上这些都是为让input子系统支持的某些参数而设置的,EV_ABS表示支持绝对值坐标,后面都是针对这些坐标的一些参数访问范围设置。至于为什么这样设置,我们继续往下走,到后面我们就明白了。

接着,在驱动模块函数中注册输入设备:

err = input_register_device(akm->input_dev);

然后,报告发生的一些事件以及对应的坐标。

input_report_abs(data->input_dev, ABS_RX, rbuf[0]);

input_report_abs(data->input_dev, ABS_RY, rbuf[1]);

input_report_abs(data->input_dev, ABS_RZ, rbuf[2]);

对应的三个方向的坐标值就被驱动记录下来了。

深入里面跟踪一下:

static inline void input_set_abs_params(struct input_dev *dev, int axis, int min, int max, int fuzz, int flat)

{

       dev->absmin[axis] = min;

       dev->absmax[axis] = max;

       dev->absfuzz[axis] = fuzz;

       dev->absflat[axis] = flat;

       dev->absbit[BIT_WORD(axis)] |= BIT_MASK(axis);

}

这个函数用来干嘛的呢?这个留到以后讲,不过你得多个心眼,后面用得到的。

添加一个input设备,我们要做的工作就这些了。接下来我们就可以通过input内核子系统提供的接口函数来处理这些坐标值,把把他们传到用户空间。

我们这些比较懒的家伙为了避免去完善那些复杂的设备接口函数集,所以干脆把它注册成一个input设备,所以你就得先申请它,注册它等一系列预备工作,做好这些之后,我们就实行鲁迅先生的拿来主义,直接使用input子系统的的接口函数”。

原理

继续我们的input设备之旅。

从前一节来看,在linux内核中添加一个input设备变得很简单了。我们再也不必须去动手写那些该死的接口函数了。可是你有没有想过,是谁让我们的工作变得这么简单了呢?答案是linux内核中的input core。她总是那么痴情,默默地不求回报地为你做许许多多的事情,在你背后默默的支持你爱着你。是的,你所想到的大多数事情,我们的input core都已经为你做好。除了感动,我们还能说什么呢?(input core对应的实体在linux内核源码目录linux-2.6.29/drivers/input/input.c文件)

在正式接触我们可爱的input core之前,有必要了解一下几个重要的结构体,这几个结构体是我们这个故事的主体。

第一个数据结构 struct input_dev。悟性高的哥们马上就会想到,它就是我们input 设备在linux内核中的模拟,即里面记录了一个input设备的所有信息。定义于linux-2.6.29/include/linux/input.h中

struct input_dev {

 const char *name;

       const char *phys;

       const char *uniq;

       struct input_id id;

       unsigned long evbit[BITS_TO_LONGS(EV_CNT)];       //表示此input设备支持的事件

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

       unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];

       unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];

       unsigned long swbit[BITS_TO_LONGS(SW_CNT)];

       unsigned int keycodemax;

       unsigned int keycodesize;

       void *keycode;

       int (*setkeycode)(struct input_dev *dev, int scancode, int keycode);

       int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode);

       struct ff_device *ff;

       unsigned int repeat_key;

       struct timer_list timer;

       int sync;

       int abs[ABS_MAX + 1];

       int rep[REP_MAX + 1];

       unsigned long key[BITS_TO_LONGS(KEY_CNT)];

       unsigned long led[BITS_TO_LONGS(LED_CNT)];

       unsigned long snd[BITS_TO_LONGS(SND_CNT)];

       unsigned long sw[BITS_TO_LONGS(SW_CNT)]; 

       int absmax[ABS_MAX + 1];

       int absmin[ABS_MAX + 1];

       int absfuzz[ABS_MAX + 1];

       int absflat[ABS_MAX + 1]; 

       int (*open)(struct input_dev *dev);

       void (*close)(struct input_dev *dev);

       int (*flush)(struct input_dev *dev, struct file *file);

       int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);

       struct input_handle *grab;

       spinlock_t event_lock;

       struct mutex mutex;

       unsigned int users;

       int going_away;

       struct device dev;

       struct list_head  h_list;          //struct list_head h_list;表示的是和该设备相关的所有input handle的结构体链表

       struct list_head  node;          //struct list_head node;所有input设备组成的链表结构(后面将会知道它对应于input_dev_list)。

};

很强大的一个结构体,因为她把所有的input设备的信息都考虑到了,真的是很无私。不过对于我们的akm驱动来说只需要关注几个小细节,结构中的加粗部分。

unsigned long evbit [BITS_TO_LONGS(EV_CNT)]  表示此input设备支持的事件,

     比如前面的第二节中的set_bit(EV_ABS, akm->input_dev->evbit)设置input_dev->evbit中的相应位让它支持绝对值坐标。

     类似的还有以下这些事件:EV_KEY -按键, EV_REL -相对坐标  EV_ABS -绝对坐标,EV_LED -  LED,EV_FF- 力反馈。

unsigned long absbit [BITS_TO_LONGS(ABS_CNT)];设置相应的位以支持某一类绝对值坐标。

     比如第二节中的input_set_abs_params(akm->input_dev, ABS_RX, 0, 23040, 0, 0);它的函数体如下:

static inline void input_set_abs_params(struct input_dev *dev, int axis, int min, int max, int fuzz, int flat)

{       dev->absmin[axis] = min;

       dev->absmax[axis] = max;

       dev->absfuzz[axis] = fuzz;

       dev->absflat[axis] = flat; 

       dev->absbit[BIT_WORD(axis)] |= BIT_MASK(axis);

}

表示支持绝对值x坐标,并设置它在坐标系中的最大值和最小值,以及干扰值和平焊位置等。

Ok 马上进入第二个结构体struct input_handler(还是来自linux-2.6.29/include/linux/input.h)

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;

};

顾名思义:用来处理input设备的一个结构体。

struct list_head h_list表示的是和该设备相关的所有input handle的结构体链表和前面那个一样;

struct list_head  node所有input_handle组成的结构体连链表(后面将会知道它对应于input_handler_list)。每一个input设备在注册时,他都会遍历input_handler_list链表上的每一个input_handler,去找寻他心中的那个她,同理每一个input_handler在注册时,他也会去input_dev_list上找寻那个属于他的她。有时候事情往往不会那么尽如人意,当input_handler还没出生时,你这个input_dev就一直在那等吧,等到天荒地老,等到海枯石烂。最后来一句,我等到花儿也谢了,你丫到底还来不来啊。注意这里的input_handler和input_dev并不是一一对应的关系,有时一个input_handler对应好几个input_dev。于是乎,作为看代码的我就在想,linux内核开发者的思想怎么这么不单纯呢,这不明摆着教育我们搞一夫多妻制吗,不过管怎样,还是得记住公司的企业文化,本分点。如果你丫说你同时拥有两个马子,我将会无情的向你抛出那句话:“出来混,迟早要还的!”。

前面多次提到那个input_handle(注意区别input_handler),她到底是何方神圣。好吧,就让我们来一层一层揭开她那神秘的面纱,当你第一次看到她完完全全展现在你面前时,那时候你的满足感和兴奋度和她的害羞度是成正比的。

同样来自linux-2.6.29/include/linux/input.h

struct input_handle { 

       void *private; 

       int open;

       const char *name; 

       struct input_dev *dev;

       struct input_handler *handler; 

       struct list_head  d_node;

       struct list_head  h_node;

};

怎么啦?是不是很失望,原来就这么回事啊。嗯,没错,兄弟,就这么回事。人往往都这样,得到了某样东西,想想觉得就那么回事,没得到呢,那叫一个好奇,那叫一个盼望。好了既然看到她的庐山真面目了,就坦然面对她,作为一个负责的男人,我还是来好好研究一下。

Input_Handle其实也好理解,它就是input_dev和 input_handler粘合剂,通过Input_Handle这么一搅和,input_dev就和 input_handler发生了点关系,至于什么样的关系我们后文将会知道的一清二楚。struct input_dev *dev对应的input设备。struct input_handler *handler对应该设备的handler。struct list_head d_node和struct list_head h_node则分别关联上前面两个结构体中的struct list_head h_list。

终于告一段落了,休息,休息一下!

======================================================================================================

2.6内核输入子系统分析 前面对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_dev 、 input_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);         //  id为0的选项
		MATCH_BIT(keybit, KEY_MAX);        //  id为1的选项 
		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 loadedkbdinput_match_devicemousedevinput_match_devicemousedev_connectjoydevinput_match_deviceevdevinput_match_deviceevdev_connecttsdevinput_match_devicetsdev_connectevbuginput_match_deviceevbug_connect可以看到,对于s3c2410-ts.c里面定义的input_dev设备,同时与其匹配的就有mousedev、evdev、tsdev、evbug等众多接口(不知道我的理解是否正确,如果理解错了,还望指正^_^)###########################################################################################      
找到匹配的选项以后,就可以开始着手把input_dev、input_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_dev和input_handler中h_list为Hash头的链中。好了,到此,input_dev、input_handle、input_handler这三者总算是联系起来了^_^