Linux驱动

Linux驱动之输入子系统(张栖银详谈)

2019-03-10  本文已影响15人  konishi5202

一、input子系统介绍

1.1 系统介绍

本文是基于linux-2.6.32内核进行分析的,如果使用的是其他版本的内核,其内核调用的函数可能有所不同,但是其实现原理是相通的。

1.2 input子系统的引入

以前我们写一些输入设备(键盘、鼠标等)的驱动都是采用字符设备、混杂设备处理的。问题由此而来,Linux开源社区的大神们看到了这大量输入设备如此分散不堪,有木有可以实现一种机制,可以对分散的、不同类别的输入设备进行统一的驱动,所以才出现了输入子系统。

输入设备(如按键、键盘,触摸屏,鼠标等)是典型的字符设备,其主设备号固定为13,其一般的工作机制是在底层在按键、触摸、鼠标点击等动作发生时产生一个中断(或驱动通过timer定时查询),然后CPU通过SPI、IIC或者外部存储器总线读取键值、坐标等数据,放在一个缓冲区,字符设备驱动管理该缓冲区,而驱动的read()接口让用户可以读取键值、坐标等数据,其过程如图1.1所示。

imageimage

在Linux中,输入子系统是由输入子系统设备驱动层、输入子系统核心层(input core)和输入子系统事件处理层(Event handler)组成,如图1.2所示。其中设备驱动层提供对硬件各寄存器的读写访问和将底层硬件对用户输入访问的响应转换为标准的输入事件,再通过核心层提交给事件处理层;而核心层对下提供了设备驱动层的编程接口,对上又提供了事件处理层的编程接口;而事件处理层就为我们用户空间的应用程序提供了统一访问设备的接口和驱动层提交来的事件处理。所以这使得我们输入设备的驱动部分不在用户关心对设备文件的操作,而是要关心对各硬件寄存器的操作和提交的输入事件。

imageimage

1.3 input子系统的优点

输入子系统的引入,也为我们带来了许多的好处:

注:更多详细描述可参见《精通Linux设备驱动程序开发》这本书。

更重要的是,input子系统的引入,使我们在具体的开发中可以在输入硬件设备更换的情况下保持应用不做任何修改

1.4 input子系统与字符设备实现比较

在进行字符设备驱动程序开发的过程中,我们的实现步骤如下:

输入子系统与混杂设备驱动一样,也是一个典型的字符设备,那么其注册的过程与字符设备驱动一样,也必须经过上面的这些步骤。只是输入子系统中的输入设备一般只接收输入设备的中断和获取输入设备的数据,而不输出数据到输入设备而已。

在输入子系统中,将字符设备驱动分为了三个部分:与硬件操作相关的设备驱动层(由驱动工程师实现)、输入子系统核心层(input core)和输入子系统事件处理层(Event handler),其中后面两个部分都已经由系统帮我们实现了。其实后面两个部分可以把它看作是一个整体,就是与硬件操作无关的软件部分。那么我们实现input设备驱动的过程为:

也就是说:无论输入子系统多么强大、封装的多么的好,与硬件相关的操作还是得我们亲自实现。

1.5 input子系统与字符设备与应用层数据交互比较

编写过字符设备驱动的人都知道,应用程序与驱动之间实现数据交互就是通过应用API的read()、write()调用,从而产生一个SWI软件中断,然后通过主设备号找到对应的struct cdev结构体实体,从而找到具体硬件设备的struct file_operations结构体,然后具体调用底层的drv_read()、drv_write(),我们就是在具体的drv_read()和drv_write()中实现对硬件的操作的,其过程如下:

read()—>swi_read()—>drv_read()—>硬件操作

那么对应到输入子系统呢?前面已经说了,输入子系统也是字符设备,那么它也必须经历上面的这些步骤,只是中间穿插了几个查找具体输入设备的过程(毕竟将所有的输入设备都加入到输入子系统,就不止一个设备了)而已。那么是如何穿插的呢:

首先在input.c(输入子系统的核心)文件的打开函数中找到具体的input_handler,然后取出具体input_handler中的fops(也即struct file_operations结构体)填充struct file中的f_op成员,那么之后应用调用read()、write()函数就是调用具体input_handler指向的struct file_operations结构体中的成员了;最后再调用fops中的open()函数打开具体的函数:

struct input_handler *handler;
handler = input_table[iminor(inode) >> 5];
old_fops = file->f_op;
file->f_op = new_fops;
err = new_fops->open(inode, file);

这里说明一下:input.c是input子系统的核心,内核已经实现,各种input_handler(包含open、release、read、write、ioctl、fasync、poll等,即硬件处理函数)也由系统抽象出来帮我们实现了,后面会讲解其具体实现过程。

通过前面的介绍,不太理解也没有关系。你只需要记住,其实输入子系统就是一个典型的字符设备,它也逃不过字符设备的框架,其应用与驱动交互的流程也和字符设备驱动一样,没有什么不同就是了(要从心里小瞧它)。

要想搞明白输入子系统的框架,只需要弄明白应用程序是如何与具体的硬件设备驱动进行交互的就行了。而这个过程如下其实就是主设备号13的字符设备、input_handler、input_handle和input_dev几个的关系

详细过程如下三条线路所示:

  1. open()—>input_open_file()—>input_handler->fops->open()
  2. read()/write()/ioctl()—>input_handler->fops->read()/write()/ioctl()
  3. 硬件—>input_dev的中断处理程序—>input_event()—>input_handle->input_handler->event()

注意:->是指针;—>是下一步调用。

从应用到底层的匹配过程是通过input_handler,具体驱动中是通过静态全局指针数组变量input_table[]实现的;而硬件到应用程序的匹配过程是通过input_handle结构体找到对应的input_handler,从而实现数据传输的,具体到代码就是通过input_handler->connect()函数将input_dev、input_handler和input_handle三者进行绑定的,三者绑定的关系如图1.3所示。


imageimage

二、input子系统实现

本章节将详细讲解input子系统的框架,也就是input子系统中如何实现应用层数据与底层硬件之间的数据交互、底层硬件(input_dev)如何与系统实现的驱动(input_handler)关联等

2.1 input子系统框架

正如前面的介绍,input子系统将所有的输入设备统称为像鼠标输入、键盘输入、joydev输入等,将输入的数据封装成统一的事务格式(struct input_event)上传到应用,而将具体的硬件设备分离出来(这才是我们要做的事),如图2.1所示。


imageimage

在如上的系统分层结构下,我们的应用程序就可以不用关心获取到的数据是来源于SPI的键盘、还是IIC的键盘,它只需要关心获取到数据的具体含义就行了,这样就保证了更换不同的硬件设备,而不用修改一行应用程序代码,如图2.2所示。

上一篇 下一篇

猜你喜欢

热点阅读