linux输入子系统

前言

这段时间学了学韦东山基于s3c2440的驱动开发视频教程,其中关于输入子系统的框架理起来比较复杂,做成笔记帮助回忆和参考,内容主要来自韦东山的视频,也有从一些博客中做的摘录。

输入子系统框架

输入子系统是一种驱动框架,适合一些典型的输入设备比如鼠标、键盘等,输入子系统由三大部分组成,它们是核心层、事件处理层和设备驱动层。

从input_init开始

加载输入子系统模块的入口函数完成了简单的注册和创建设备节点等任务,从这里可以关注到它的file_operations结构体中只有.open,说明在open操作函数中实现了更复杂的任务,继续进入input_open_file函数分析:

static int input_open_file(struct inode *inode, struct file *file)
{
	/* 根据次设备号在input_table数组中索引到一个元素并由handler指向 */
	struct input_handler *handler = input_table[iminor(inode) >> 5];
	const struct file_operations *old_fops, *new_fops = NULL;

    ···
    ···
    ···

	/* handler中有实际设备驱动的file_operations结构体,
	   找到它后将它赋值给new_fops,并调用新的open */
	old_fops = file->f_op;
	file->f_op = new_fops;
    /* 最终调用了实际驱动中的open函数 */
	err = new_fops->open(inode, file);

    ···
    ···
}

可见,虽然一开始调用的是input设备的open函数,但这只是中转一下,在这个open函数中会根据次设备号在一个input_handler类型的结构体指针数组中找到一个元素,将其中的open函数拿来调用。之后跟踪这个数组input_table继续分析。

跟踪input_table的构造

在input_register_handler函数中找到了构造input_table函数的代码:

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

	INIT_LIST_HEAD(&handler->h_list);

	if (handler->fops != NULL) {
		if (input_table[handler->minor >> 5])
			return -EBUSY;
    /* 根据传入的参数handler中次设备号作为数组索引将handler放入对应位置 */ 
		input_table[handler->minor >> 5] = handler;
	}
    /* 同时还构建了一个handler为节点的链表 */
	list_add_tail(&handler->node, &input_handler_list);

	list_for_each_entry(dev, &input_dev_list, node)
		input_attach_handler(dev, handler);

	input_wakeup_procfs_readers();
	return 0;
}

这里构造了数组input_table,之前分析,数组input_table中的元素会以次设备号为索引被引用,而元素又是以次设备号为索引添加进去的,可以猜测这个函数在会在有次设备号的设备被安装的入口函数中执行,这样就可以在该设备文件被open的时候找到对应的元素并调用open。

关于设备文件的打开就先到这里,之后以evdev为例更进一步的探讨。

evdev是什么

evdev.c文件在/drivers/input文件夹下,安装它的入口函数为:

static int __init evdev_init(void)
{
	return input_register_handler(&evdev_handler);
}

根据以上分析,evdev_handler就是之前input_table数组的一员了,那么它就有open之类的操作函数,他就是一个输入子系统下的一个带有次设备号的驱动了。

那么怎么使用它?可以知道在open之后,应用程序需要调用write、read等等接口函数,这些函数在evdev中是通用的,他们一般不随意更改,所以一般只支持一类特定的设备。

支持的设备维护在evdev_table数组中,设备何时被放入?evdev_connect函数。

evdev_connect函数

这个函数可以展开很多,它也是事件处理层和设备驱动层联系起来的关键。之前分析到evdev.c文件中的open、write、read提供的是通用的支持,我们必须为它们提供支持对象,也就是硬件设备,一个驱动只支持一类设备。

设备是我们写的,需要相关的硬件操作,描述一个设备需要使用input_dev类型的结构体,其中有该设备特有的input_device_id信息,这个信息也在evdev_handler结构体中被维护:

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

在运行input_register_handler函数即注册事件处理器时(在init入口函数中)或在我们运行input_register_device函数注册设备时,都会有:

list_for_each_entry(dev, &input_dev_list, node)
		input_attach_handler(dev, handler);

这样类似的代码来对比该设备/事件处理器是否已经有被支持/支持,如果有,像我们这个例子中就会调用evdev_handler结构体中的evdev_connect函数进行匹配工作。evdev_connect函数代码:

static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
			 const struct input_device_id *id)
{
	struct evdev *evdev;
	struct class_device *cdev;
	dev_t devt;
	int minor;
	int error;
    ···
	evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);//分配空间,evdev结构体类型

    ···

	evdev->exist = 1;
	evdev->minor = minor;
	evdev->handle.dev = dev;          //evdev中纳入设备驱动层信息
	evdev->handle.name = evdev->name;
	evdev->handle.handler = handler;  //evdev中纳入事件处理层信息
	evdev->handle.private = evdev;
	sprintf(evdev->name, "event%d", minor);

	evdev_table[minor] = evdev;       //放入数组evdev_table

    ···
}

可以看到,evdev_connect函数中为一个evdev类型的数组evdev_table添加了一位元素,这个结构体的handle项中同时有设备驱动层、事件处理层的信息,通过这个数组,事件处理层的各接口函数都可以找到对应的硬件设备进行操作。

posted @ 2021-01-21 21:52  星灯盏  阅读(133)  评论(0)    收藏  举报