Linux驱动-分析input输入子系统工作流程

本文围绕Linux input子系统展开,介绍其是管理输入的内核框架。从input设备驱动层注册角度,分析了struct input_dev结构体及相关注册函数;又从应用层角度,探讨事件接受与处理过程及三层配合机制,最后为不同开发者给出使用建议。

前言

写文章的目的是想通过记录自己的学习过程,以便以后使用到相关的知识点可以回顾和参考。

一、简介

input 就是输入的意思,因此 input 子系统就是管理输入的子系统,和 pinctrl 和 gpio 子系统一样,都是 Linux 内核针对某一类设备而创建的框架。比如按键输入、键盘、鼠标、触摸屏等等这些都属于输入设备,不同的输入设备所代表的含义不同,按键和键盘就是代表按键信息,鼠标和触摸屏代表坐标信息,因此在应用层的处理就不同,对于驱动编写者而言不需要去关心应用层的事情,我们只需要按照要求上报这些输入事件即可。为此 input 子系统分为 input 设备驱动层、input 核心层(Input Core)、input 事件处理层(Event Handler),最终给用户空间提供可访问的设备节点。input 子系统流程框架如下图所示:
在这里插入图片描述

二、从input设备驱动层(注册)看input子系统

1、struct input_dev 结构体

任何驱动设备如果想标明自己是输入设备,都应该通过初始化 struct input_dev 结构体,并且调用input_allocate_device()函数进行注册。先看一下struct input_dev结构体的内容,它定义在include/linux/input.h 文件中,内容如下:

struct input_dev {
	const char *name;			//提供给用户的输入设备的名称
	const char *phys;			//提供给编程者的设备节点的名称
	const char *uniq;			//指定唯一的ID号,就像MAC地址一样
	struct input_id id;			//输入设备标识ID,用于和事件处理层进行匹配

	unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];

	unsigned long evbit[BITS_TO_LONGS(EV_CNT)];		//位图,记录设备支持的事件类型 
	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)];	//位图,记录设备支持的LED 
	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 hint_events_per_packet;

	unsigned int keycodemax;
	unsigned int keycodesize;
	void *keycode;

	int (*setkeycode)(struct input_dev *dev,
			  const struct input_keymap_entry *ke,
			  unsigned int *old_keycode);
	int (*getkeycode)(struct input_dev *dev,
			  struct input_keymap_entry *ke);

	struct ff_device *ff;

	unsigned int repeat_key;
	struct timer_list timer;

	int rep[REP_CNT];

	struct input_mt_slot *mt;
	int mtsize;
	int slot;
	int trkid;

	struct input_absinfo *absinfo;

	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 (*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 __rcu *grab;

	spinlock_t event_lock;
	struct mutex mutex;

	unsigned int users;
	unsigned int users_private;
	bool going_away;
	bool disabled;

	bool sync;

	struct device dev;

	struct list_head	h_list;			//handle链表
	struct list_head	node;			//input_dev链表
};

就这样赤裸裸的看上面的结构体,会觉得摸不着头脑,但是有一点是确定的,我们在写输入设备驱动时会定义这样一个输入设备结构体,并调用input_allocate_device()函数,这个函数的功能是为新添加的输入设备分配内存,如果成功,将返回input_dev *的指针结构,因此在写驱动的时候应该接受返回值,作为驱动层获得了一个新的输入设备操作的接口,例:

struct input_dev *inputdev;
inputdev = input_allocate_device();

下面来看看input_allocate_device() 函数的内容

2、input_allocate_device 函数

input_allocate_device() 函数定义在/drivers/input/input.c文件中,内容如下:

struct input_dev *input_allocate_device(void)
{
	struct input_dev *dev;
	////动态申请内存,使用GFP_KERNEL方式,注意GFP_KERNEL可能导致睡眠,不能在中断中调用这个函数 
	dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);
	if (dev) {
		dev->dev.type = &input_dev_type;
		dev->dev.class = &input_class;		//添加进input类设备模型中
		device_initialize(&dev->dev);
		mutex_init(&dev->mutex);			//初始化互斥锁 
		spin_lock_init(&dev->event_lock);	//初始化自旋锁
		INIT_LIST_HEAD(&dev->h_list);		//初始化handle链表 
		INIT_LIST_HEAD(&dev->node);			//初始化输入设备链表

		__module_get(THIS_MODULE);
	}

	return dev;
}

可以看出 input_allocate_device 函数的作用就是为 input_dev 结构体申请一块内存,然后初始化 input_dev 结构体的部分成员,通过input_allocate_device()函数,我们设备驱动现在持有的input_dev里面就被赋予了input的“形象”,但是还需要我们去充实一下“内在”,因此,设备驱动程序,还需要为自己的设备增加自己的特性,才能创造独有的设备“形象”。
例如下面的例子:

	struct input_dev *inputdev = input_allocate_device();
	inputdev->name = "key_input";
	inputdev->id.bustype =0x0000;
	inputdev->id.vendor  =0x0001;
	inputdev->id.product =0x0002;
	inputdev->id.version =0x1001;
    inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);  /* 按键及重复事件类型 */
    inputdev->keybit[BIT_WORD(KEY_A)] |= BIT_MASK(KEY_A);      /* 事件码 */
    inputdev->keybit[BIT_WORD(KEY_B)] |= BIT_MASK(KEY_B);
    inputdev->keybit[BIT_WORD(KEY_C)] |= BIT_MASK(KEY_C);
    inputdev->keybit[BIT_WORD(KEY_D)] |= BIT_MASK(KEY_D);

通过以上的代码完成了输入设备的初始化工作。但是这仅是初始化自己的“特点”,还需要通知输入子系统有这样一个新设备诞生了,这就需要调用输入子系统的注册函数input_register_device(struct input_dev *dev) 来完成。

3、input_register_device 函数

input_register_device() 用于注册一个输入设备,它定义在drivers/input/input.c文件中,内容如下:

int input_register_device(struct input_dev *dev)
{
	static atomic_t input_no = ATOMIC_INIT(0);
	struct input_handler *handler;
	const char *path;
	int error;

	/* Every input device generates EV_SYN/SYN_REPORT events. */
	__set_bit(EV_SYN, dev->evbit);

	/* KEY_RESERVED is not supposed to be transmitted to userspace. */
	__clear_bit(KEY_RESERVED, dev->keybit);

	/* Make sure that bitmasks not mentioned in dev->evbit are clean. */
	input_cleanse_bitmasks(dev);
	
	/* 以下四个dev成员,如果设备驱动没有指定的函数,将赋予系统默认的函数 */
	if (!dev->hint_events_per_packet)
		dev->hint_events_per_packet =
				input_estimate_events_per_packet(dev);

	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;
	}
	
	if (!dev->getkeycode)
		dev->getkeycode = input_default_getkeycode;

	if (!dev->setkeycode)
		dev->setkeycode = input_default_setkeycode;
	
	/* 动态获取input设备的ID号,名称为input*, *为id号 */
	/* 例如:input5 */
	dev_set_name(&dev->dev, "input%ld",
		     (unsigned long) atomic_inc_return(&input_no) - 1);
	
	/* 在/sys目录下创建设备目录和文件 */
	/* 例如:/sys/devices/virtual/input/input5/ */
	error = device_add(&dev->dev);
	if (error)
		return error;
		
	/* 在终端上打印设备的绝对路径名称 */ 
	/* 例如:input: keyinput as /devices/virtual/input/input5 */
	path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
	pr_info("%s as %s\n",
		dev->name ? dev->name : "Unspecified device",
		path ? path : "N/A");
	kfree(path);

	error = mutex_lock_interruptible(&input_mutex);
	if (error) {
		device_del(&dev->dev);
		return error;
	}
	/* 把设备挂到全局的input子系统设备链表input_dev_list上 */
	list_add_tail(&dev->node, &input_dev_list);
	/* 核心重点,input设备在增加到input_dev_list链表上之后,会查找 
     * input_handler_list事件处理链表上的handler进行匹配,这里的匹配 
     * 方式与设备模型的device和driver匹配过程很相似,所有的input 
     * 都挂在input_dev_list上,所有类型的事件都挂在input_handler_list 
     * 上,进行“匹配相亲” */  
	list_for_each_entry(handler, &input_handler_list, node)
		input_attach_handler(dev, handler);

	input_wakeup_procfs_readers();

	mutex_unlock(&input_mutex);

	return 0;
}

input_register_device(struct input_dev *dev) 函数里一些重要的工作我已经注释到上述代码行中,下面概括一下 input_register_device 函数主要的功能,也是设备驱动注册为输入设备委托内核做的事情:

1、进一步初始化输入设备,例如设备驱动没有指定的一些函数,赋予了系统默认的函数;
2、注册输入设备到input类中;
3、把输入设备挂到输入设备链表input_dev_list中;
4、查找并匹配输入设备对应的事件处理层,通过input_handler_list链表

3、input_attach_handler函数

input_attach_handler 函数定义在drivers/input/input.c文件中,内容如下:

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
	const struct input_device_id *id;
	int error;

	id = input_match_device(handler, dev);		//匹配事件驱动,事件驱动一共有三种evdev,mousedev,joydev
	if (!id)
		return -ENODEV;

	error = handler->connect(handler, dev, id);	//调用事件驱动的connect函数进行匹配
	if (error && error != -ENODEV)
		pr_err("failed to attach handler %s to device %s, error: %d\n",
		       handler->name, kobject_name(&dev->dev.kobj), error);

	return error;
}

input_attach_handler 函数里面有两个函数比较重要,input_match_device 和 handler->connect,它们的大概作用我注释在代码行中了,下面来看看具体是怎样实现的,先看一下input_match_device 函数。

4、input_match_device 函数

input_match_device 函数它定义在drivers/input/input.c文件,内容如下:

static const struct input_device_id *input_match_device(struct input_handler *handler,
							struct input_dev *dev)
{
	const struct input_device_id *id;
	int i;

	for (id = handler->id_table; id->flags || id->driver_info; id++) {
	
		/* 以下通过flags中设置的位来匹配设备的总线类型、经销商、生产ID和版本ID 
         * 如果没有匹配上将进行MATCH_BIT匹配 */  
		if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
			if (id->bustype != dev->id.bustype)
				continue;

		if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
			if (id->vendor != dev->id.vendor)
				continue;

		if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
			if (id->product != dev->id.product)
				continue;

		if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
			if (id->version != dev->id.version)
				continue;
		/* MATCH_BIT用于匹配设备驱动中是否设置了这些位,MATCH_BIT的宏 
         * 被定义在input.c中,我们在设备驱动中设置的事件类型会与事件链表中的 
         * 所有事件类型进行比较,匹配成功了将返回id,证明真的很合适,否则NULL 
         */  
		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);

		if (!handler->match || handler->match(handler, dev))
			return id;
	}

	return NULL;
}

handler 是事件驱动结构体的指针,事件驱动一般有三种evdev,mousedev,joydev,分别对应三个结构体evdev_handler ,mousedev_handler ,joydev_handler ,如果匹配成功了会返回id,再回看 input_attach_handler 函数,若id不为NULL,就会调用 handler->connect 函数,假如现在匹配到 evdev_handler 这个事件驱动,然后就会调用evdev_handler->connect 函数,下面来看看evdev_handler->connect 函数做了什么。

5、evdev_connect 函数

drivers/input/evdev.c文件中有如下内容:

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,
};

在注册evdev驱动时,evdev_handler结构体的connect成员指向evdev_connect函数,所以evdev_handler->connect 就是 evdev_connect 函数了,evdev_connect 函数也是定义在drivers/input/evdev.c文件中,内容如下:

static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
			 const struct input_device_id *id)
{
	struct evdev *evdev;
	int minor;
	int error;

	for (minor = 0; minor < EVDEV_MINORS; minor++)
		if (!evdev_table[minor])
			break;

	if (minor == EVDEV_MINORS) {
		pr_err("no more free evdev devices\n");
		return -ENFILE;
	}
	
	/* 给evdev事件层驱动分配空间 ,
	 * 可以不要关心 evdev ,只看 evdev->handle 即可,这里构建了一个 handle ,
	 * 注意不是handler, handle 就是个中间件,可以理解成胶带,
	 * 它把 hander 与 dev 连在一起 */
	evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
	if (!evdev)
		return -ENOMEM;

	INIT_LIST_HEAD(&evdev->client_list);		
	spin_lock_init(&evdev->client_lock);		
	mutex_init(&evdev->mutex);					
	init_waitqueue_head(&evdev->wait);			

	dev_set_name(&evdev->dev, "event%d", minor);	
	evdev->exist = true;
	evdev->minor = minor;
	evdev->hw_ts_sec = -1;
	evdev->hw_ts_nsec = -1;

	/* 第一次建立联系,在 handle 中记录 dev 与 handle 的信息,
	 * 这样通过handle就可以找到dev与handler, 即是实现
	 * handle -> dev ,  handle -> hander 的联系 */
	evdev->handle.dev = input_get_device(dev);
	evdev->handle.name = dev_name(&evdev->dev);
	evdev->handle.handler = handler;
	evdev->handle.private = evdev;

	evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);		// 申请设备号
	evdev->dev.class = &input_class;
	evdev->dev.parent = &dev->dev;
	evdev->dev.release = evdev_free;
	device_initialize(&evdev->dev);

	error = input_register_handle(&evdev->handle);		// 注册 handle
	if (error)
		goto err_free_evdev;

	error = evdev_install_chrdev(evdev);
	if (error)
		goto err_unregister_handle;

	error = device_add(&evdev->dev);	//在 /dev/input 类下面创建设备节点, 名字为enevt%d
	if (error)
		goto err_cleanup_evdev;

	return 0;

 err_cleanup_evdev:
	evdev_cleanup(evdev);
 err_unregister_handle:
	input_unregister_handle(&evdev->handle);
 err_free_evdev:
	put_device(&evdev->dev);
	return error;
}

内容里面比较重要就是evdev->handle这个成员了,它的作用就是把 hander 和 dev 连在一起,hander 表示事件驱动层,dev表示设备驱动层,这样 事件驱动层 和 设备驱动层 之间就通过handle这个 ”红娘“ 建立了关系了。现在可以通过 handle 找到 hander 或者 dev,不过还差一步,就是实现双向性,通过hander 或者 dev 也能找到 handle ,这样才能实现 handle —hander — dev 三者之间畅通无阻,而这一实现就体现在 handle 注册函数input_register_handle里面。

6、input_register_handle 函数

input_register_handle 函数定义在drivers/input/input.c文件中,内容如下:

int input_register_handle(struct input_handle *handle)
{
	/* 第二次建立联系	*/
	struct input_handler *handler = handle->handler;
	struct input_dev *dev = handle->dev;
	int error;

	error = mutex_lock_interruptible(&dev->mutex);
	if (error)
		return error;

	if (handler->filter)
		list_add_rcu(&handle->d_node, &dev->h_list);
	else
		list_add_tail_rcu(&handle->d_node, &dev->h_list);	// 将handle 记录在 dev->h_list 链表中

	mutex_unlock(&dev->mutex);

	list_add_tail_rcu(&handle->h_node, &handler->h_list);	// 将handle 记录在 handler->h_list 链表中


	if (handler->start)
		handler->start(handle);

	return 0;
}

input_register_handle函数注册handle时,把handle作为节点分别加入到dev->h_list 链表和handler->h_list 链表中去,至此,dev 与 hander 也可以找到handle了,dev <-> handle <-> handler 之间畅通无阻了。

小结:

通过上面分析的 input输入设备驱动注册的全过程得出以下过程图:
以上是输入设备驱动注册的全过程,纵观整个过程,
通过上图我们可以看到input输入设备匹配关联的关键过程以及涉及到的关键函数和数据。
以上主要是从input设备驱动程序的角度去看输入子系统的注册过程和三层之间的关联。
下面将从应用层的角度分析事件的接受过程和处理过程以及三层之间是如何配合处理输入事件的。

三、从应用层的角度出发看input子系统

1、input_open_file 函数

通过上面分析,在input设备驱动注册的时候,会创建设备节点/dev/input/event5(内核中已经有4个其他的event类型的输入设备,所以新建了event5)
在这里插入图片描述
我们知道,应用层使用设备的第一步,是open(“/dev/event5”),因此这里event0的主设备号成为关键,因为主设备号将表明你是什么设备,我们ls -l查看/dev/event5发现:
在这里插入图片描述

由此可见主设备是13,输入命令cat /proc/devices查看主设备为13的是input设备,因此可以确定当我们执行open函数打开event5设备的时候,会调用input设备的open驱动函数,这个函数在input.c中,为了说明这一问题,需要从input驱动注册过程开始,在drivers/input/input.c文件中,有如下内容:

static const struct file_operations input_fops = {	//input设备的操作接口集合
	.owner = THIS_MODULE,
	.open = input_open_file,
	.llseek = noop_llseek,
};

static int __init input_init(void)
{
	int err;

	err = class_register(&input_class);
	if (err) {
		pr_err("unable to register input_dev class\n");
		return err;
	}

	err = input_proc_init();
	if (err)
		goto fail1;

	err = register_chrdev(INPUT_MAJOR, "input", &input_fops);	//注册input设备
	if (err) {
		pr_err("unable to register char major %d", INPUT_MAJOR);
		goto fail2;
	}

	return 0;

 fail2:	input_proc_exit();
 fail1:	class_unregister(&input_class);
	return err;
}

可以看到,输入设备初始化的过程首先建立了input类,初始化input在proc下的节点,然后注册input设备,设备名称为input,操作接口是input_fops,主设备号是INPUT_MAJOR=13。
只要是主设备号为13的设备驱动程序,都是用input_fops接口,即当event5设备使用open函数打开时,会调用到input_fops接口中的open驱动函数,即调用 input_open_file 这个函数,他的内容如下:

static int input_open_file(struct inode *inode, struct file *file)
{
	struct input_handler *handler;
	const struct file_operations *old_fops, *new_fops = NULL;
	int err;

	err = mutex_lock_interruptible(&input_mutex);
	if (err)
		return err;
		
	handler = input_table[iminor(inode) >> 5]; // 根据次设备号,从 input_table 数组中取出对应的 handler
	if (handler)
		new_fops = fops_get(handler->fops);	//取出 handler->fops 

	mutex_unlock(&input_mutex);
	
	if (!new_fops || !new_fops->open) {
		fops_put(new_fops);
		err = -ENODEV;
		goto out;
	}

	old_fops = file->f_op;
	file->f_op = new_fops;		//把 handler->fops 赋值给 file->f_op 

	err = new_fops->open(inode, file);	//然后调用新的 open 函数,即调用了handler->fops.open 函数
	if (err) {
		fops_put(file->f_op);
		file->f_op = fops_get(old_fops);
	}
	fops_put(old_fops);
out:
	return err;
}

那么,应用层调用用open打开设备节点,其实就是调用了匹配到的事件处理驱动的open函数。但是有一个问题,input_table 它里面存放的 handler 是在什么地方存进去的呢?
那么,必定有个地方创建了handler并对它进行一定的设置,并提供fops函数,将它放入input_table。

2、evdev_handler 结构体

这里以事件处理层evdev设备注册为例,查看一下它的注册过程,打开drivers/input/evdev.c文件,找到如下内容:

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 int __init evdev_init(void)
{
	return input_register_handler(&evdev_handler);
}

由以上的内容可以知道 evdev_handler 也被作为一个设备来操作,但是它属于input handler事件处理设备,然而我们在evdev_handler结构体的.fops字段又发现它的驱动接口为字符设备类型,在input中,如果input_table匹配到了evdev_handler,将会把file->f_op=&evdev_fops,那么如果使用read、write等函数操作,将会调用到evdev_fops中的read、write。
完成对 evdev_handler 结构体成员初始化后,然后通过 input_register_handler 函数注册evdev这个handler。

3、input_register_handler函数

input_register_handler 函数定义在 drivers/input/input.c 文件中,内容如下:

int input_register_handler(struct input_handler *handler)
{
	struct input_dev *dev;
	int retval;

	retval = mutex_lock_interruptible(&input_mutex);
	if (retval)
		return retval;

	INIT_LIST_HEAD(&handler->h_list);

	if (handler->fops != NULL) {
		if (input_table[handler->minor >> 5]) {
			retval = -EBUSY;
			goto out;
		}
		input_table[handler->minor >> 5] = handler;	// 将 handler 放入 input_table
	}

	list_add_tail(&handler->node, &input_handler_list);	// 将 handler 放入 input_handler_list 链表

	/* 取出 input_dev_list 链表中的每一个 dev 与 该 handler 进行匹配
	 * 这段代码是不是似曾相识,没错,这跟上面输入设备注册流程里的匹配过程是一样的,
	 * 只不过上面的是dev找handler进行匹配,
	 * 而这里是handler找dev进行匹配
	*/
	list_for_each_entry(dev, &input_dev_list, node)
		input_attach_handler(dev, handler);

	input_wakeup_procfs_readers();

 out:
	mutex_unlock(&input_mutex);
	return retval;
}

其中 input_table[handler->minor >> 5] = handler; 就解析了前面留下的那个问题—handler 是在什么地方存进去 input_table 的?就是在注册handler 的时候存放进去的,那为什么要>> 5呢?
其实evdev 的次设备号在初始化的时候就设置成了64,在这里插入图片描述

input_table[handler->minor >> 5] = handler
—>>
input_table[64 >> 5] = handler
—>>
input _table[2] = &evdev_handler

右移5位这说明一个问题,次设备号的低5位被忽略,说明evdev的最大支持的输入设备驱动个数为2^5次方等于32个,你可能会看到你的/dev目录下面有event0、event1、event2等设备,他们的次设备号分别为64、65、66等等。但最大是64+32-1,因此input_table为这些输入设备增加的一个统一接口,通过上层打开设备时,只要次设备号在64+32-1之间的设备都会重新定位到evdev_handler中,即event*设备打开后执行的底层函数将被重新定义到evdev_handler中。
例子:
比如我注册的event类型输入设备的设备节点是event5,其次设备号是69,
在这里插入图片描述
那么它在调用上面分析过的 input_open_file 函数时,

handler = input_table[iminor(inode) >> 5];
—>>
handler = input_table[69 >> 5];
—>>
handler = input_table[2];

由这个规律可以看到次设备号在64 — (64+32-1)之间的输入设备,一律都是匹配到 evdev 这个事件处理设备。

回想到上面的open 函数,也能得出一个规律:

应用层:open("/dev/event5", O_RDWR)
—>>input_open_file();
—>>new_fops->open(inode, file);
—>>evdev_handler. evdev_fops. open(inode, file);

到这里就应该知道 input设备中的open函数只是一个接口,通过次设备号才找到了真正的事件处理接口。同理,在evdev的操作接口函数集合里,同样存在read,write等函数,应用层如果想要进行读写操作,也会相应地调用到evdev里的操作接口函数,具体如下图所示:

static const struct file_operations evdev_fops = {
	.owner		= THIS_MODULE,
	.read		= evdev_read,
	.write		= evdev_write,
	.poll		= evdev_poll,
	.open		= evdev_open,
	.release	= evdev_release,
	.unlocked_ioctl	= evdev_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl	= evdev_ioctl_compat,
#endif
	.fasync		= evdev_fasync,
	.flush		= evdev_flush,
	.llseek		= no_llseek,
};

假如现在以应用层读按键为例,调用 read 函数,把 type,code,value (struct input_event),按键输入事件信息读到应用层,流程是怎样的呢?

4、evdev_read函数

因为是按键事件,应用层的read函数最终会调用 evdev_read函数,evdev_read 函数定义在drivers/input/evdev.c文件中,内容(有删减)如下:

static ssize_t evdev_read(struct file *file, char __user *buffer,
			  size_t count, loff_t *ppos)
{
	struct evdev_client *client = file->private_data;
	struct evdev *evdev = client->evdev;
	struct input_event event;
	int retval = 0;


	if (!(file->f_flags & O_NONBLOCK)) {
		/* 进入休眠状态,等待底层驱动层上报事件到client->buffer中 */ 
		retval = wait_event_interruptible(evdev->wait,
				client->packet_head != client->tail ||
				!evdev->exist);
		if (retval)
			return retval;
	}

	/* 循环读取数据 ,数据存放到input_event 类型的event变量里,
	 * 然后通过input_event_to_user函数发送到应用层 */
	while (retval + input_event_size() <= count &&
	       evdev_fetch_next_event(client, &event)) {

		if (input_event_to_user(buffer + retval, &event))
			return -EFAULT;

		retval += input_event_size();
	}

	return retval;
}

可以看出evdev_read函数跟标准的字符设备里的xxx_read函数其实差不多,只不过这里加了一个休眠过程,那么是在谁来唤醒休眠呢?猜测可能是事件上报函数。

5、input_event 函数

input_event 函数就是上报输入事件函数,在编写input设备驱动时需要用到这个函数进行输入事件上报,比如按键,我们需要在按键中断处理函数,或者消抖定时器中断函数中将按键值上报给 Linux 内核,这样 Linux 内核才能获取到正确的输入值。
input_event 函数定义在drivers/input/input.c文件中,其内容如下:

void input_event(struct input_dev *dev,
		 unsigned int type, unsigned int code, int value)
{
	unsigned long flags;

	if (is_event_supported(type, dev->evbit, EV_MAX)) {

		spin_lock_irqsave(&dev->event_lock, flags);
		add_input_randomness(type, code, value);
		input_handle_event(dev, type, code, value);
		spin_unlock_irqrestore(&dev->event_lock, flags);
	}
}

貌似还没找到关键的信息来唤醒等待队列,进入input_handle_event 函数看看。

5、input_handle_event 函数

input_handle_event 定义在drivers/input/input.c文件中,内容(有删减)如下:

static void input_handle_event(struct input_dev *dev,
			       unsigned int type, unsigned int code, int value)
{
	int disposition = INPUT_IGNORE_EVENT;

	switch (type) {

	case EV_SYN:
	case EV_KEY:	//按键类型事件
		if (is_event_supported(code, dev->keybit, KEY_MAX) &&
		    !!test_bit(code, dev->key) != value) {

			if (value != 2) {
				__change_bit(code, dev->key);
				if (value)
					input_start_autorepeat(dev, code);
				else
					input_stop_autorepeat(dev);
			}

			disposition = INPUT_PASS_TO_HANDLERS;	//这里注意disposition 的值,下面会用到
		}
		break;

	case EV_SW:
	case EV_ABS:
	case EV_REL:
	case EV_MSC:
	case EV_LED:
	case EV_SND:
	case EV_REP:
	case EV_FF:
	case EV_PWR:
	if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)
		dev->sync = false;

	if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
		dev->event(dev, type, code, value);

	if (disposition & INPUT_PASS_TO_HANDLERS)	//这里成立
		input_pass_event(dev, type, code, value);	//执行
}

还没找到关键的信息来唤醒等待队列,继续进入 input_pass_event 函数看看。

6、input_pass_event 函数

input_pass_event 函数内容(有删减)如下:

static void input_pass_event(struct input_dev *dev,
			     unsigned int type, unsigned int code, int value)
{
	struct input_handler *handler;
	struct input_handle *handle;

	rcu_read_lock();

	handle = rcu_dereference(dev->grab);
	if (handle)
		handle->handler->event(handle, type, code, value);
	else {
		bool filtered = false;

		list_for_each_entry_rcu(handle, &dev->h_list, d_node) {	//通过&dev->h_list链表找到handle
			if (!handle->open)
				continue;

			handler = handle->handler;	//然后通过 handle 找到 handler 
			if (!handler->filter) {
				if (filtered)
					break;

				handler->event(handle, type, code, value); //最终执行handler->event

			} else if (handler->filter(handle, type, code, value))
				filtered = true;
		}
	}

	rcu_read_unlock();
}

从函数的内容我们可以知道:

通过&dev->h_list链表找到 handle
然后通过 handle 找到 handler
最终执行handler->event

dev <-> handle <-> handler 的关系已经在上面分析得很明白了,接下来就去看看handler->event(即evdev_handler.event)这个函数的内容,看看是不是在里面进行唤醒操作的。

7、evdev_event函数

evdev_handler.event 指向的是 evdev_event 函数,evdev_event 定义在drivers/input/evdev.c文件中,内容(有删减)如下:

static void evdev_event(struct input_handle *handle,
			unsigned int type, unsigned int code, int value)
{
	struct evdev *evdev = handle->private;
	struct evdev_client *client;
	struct input_event event;
	ktime_t time_mono, time_real;

    ......
	event.type = type;
	event.code = code;
	event.value = value;

	......

	wake_up_interruptible(&evdev->wait);	//唤醒进程,继续执行evdev_read

}

可以看到最后通过wake_up_interruptible 唤醒进程,继续执行 evdev_read 把输入事件数据发送到应用层。

四、总结

对input子系统的整个过程做了分析,并从两个角度进行考虑。

对于写输入设备驱动程序的来说,需要掌握的是设备应该上报事件的类型,这样才能匹配到对应的事件层驱动帮助你保存对应的数据。

而对于设备上层开发者来说,应该先使用cat /proc/bus/input/devices查看你操作的设备类型和处理接口,以帮助你更好的对设备操作。

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值