嵌入式Linux输入子系统驱动框架

1,772 阅读3分钟

前言

与之前的简单的字符设备驱动不同的是,输入子系统采用分层分离模型,将一个驱动分为设备层,应用层和核心层,设备层只关心硬件的,应用层是纯软件的东西,而核心层则是建立起两者之间的联系。

应用层

应用层的主要功能如下图所示:

首先定义一个handler结构体,

初始化里面的信息,包括readwriteevent等操作函数等,具体的内容当用到时再做介绍。

然后通过input_register_handler函数注册进内核。

函数主要作用如下:

int input_register_handler(struct input_handler *handler)
{
	input_table[handler->minor >> 5] = handler;  		 //#1
    list_add_tail(&handler->node, &input_handler_list);  //#2
    list_for_each_entry(dev, &input_dev_list, node)		 //#3
		     input_attach_handler(dev, handler);		 //#4

}
  • #1: 将handler结构体告诉核心层,核心层就能通过这个数组直接找到相应的handler
  • #2: 将handler加入input_handler_list链表中。
  • #3: 这是一个宏定义,作用时等同于for循环,意思是对于每一个handler都调用#4
  • #4: 通过input_attach_handler函数建立两者的联系。

input_attach_handler函数内容如下:

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)	
{
	id = input_match_device(handler->id_table, dev); 	//#1
    error = handler->connect(handler, dev, id); 		//#2
}
  • #1: 判断handlerid是否与设备一致。
  • #2: 若一致,则通过connect函数将两者建立联系。

以evdev.c中的connect函数为例介绍函数内容:

static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
			 const struct input_device_id *id)
{
	evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);   //#1
	init_waitqueue_head(&evdev->wait);					 //#2
	devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor); //#3
	cdev = class_device_create(&input_class, &dev->cdev, devt,
				   dev->cdev.dev, evdev->name);			 //#4
    error = input_register_handle(&evdev->handle);       //#5
}
int input_register_handle(struct input_handle *handle)
{
	struct input_handler *handler = handle->handler;
	list_add_tail(&handle->d_node, &handle->dev->h_list); //#6
	list_add_tail(&handle->h_node, &handler->h_list);	  //#7
}
  • #1: 创建一个handle结构体,该结构体功能就是连接设备层和应用层。
  • #2: 初始化一个等待队列,这个对列在read函数中唤醒。
  • #3和#4: 这部分就是字符设备驱动中的创建结点,所以只有连接成功了之后才会真正的在系统中产生一个完整的驱动。
  • #5: 这个函数将主要完成连接的工作。
  • #6: 将handle和设备层相连
  • #7: 将handle和应用层相连
  • 连接成功后,就可以通过handle来完成设备层向应用层的访问,应用层向设备层的访问。

read函数

当应用程序调用read函数时,就会调用应用层中相应的read函数。该read函数的主要功能就是使进程休眠。

event函数

该函数由设备层根据需求触发,在本例中,该函数主要就是唤醒进程。

设备层

设备层的主要功能如下图所示:

首先定义一个dev结构体,然后初始化

然后通过input_register_device函数注册进内核。

该函数主要功能如下:

int input_register_device(struct input_dev *dev)
{
	list_add_tail(&dev->node, &input_dev_list);	  			//#1
    list_for_each_entry(handler, &input_handler_list, node) //#2
		   input_attach_handler(dev, handler);				//#3
}
  • #1: 将dev结构体,加到input_dev_list链表中,用于和应用层进行匹配,用途在应用层中已经介绍过。
  • #2和#3: 这个与应用层中的一样,这段代码出现两次的作用就是,这两个文件驱动可以不分先后的安装,当先安装设备层驱动的时候,执行该段代码,不会找到相应的应用层,所以就不会建立起连接,之后安装当应用层驱动,这时候就可以匹配到相应的设备层,然后创建出结点。

触发event函数

根据不同的需求,在相应的设备达到某种状态的这时候调用input_event()input_sync()函数触发应用层中的event()函数。

核心层

  • 核心层定义了相应的数据结构以及函数。
  • 定义了file_operations结构体。但该结构体里只有open()函数。
  • open()函数内可以根据打开的文件,调用相应应用层中的操作函数,如read()write()函数等。

输入子系统数据结构

该图转自如何理解linux input输入子系统