美文网首页Linux驱动
Linux驱动之输入子系统(张栖银详谈)

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

作者: konishi5202 | 来源:发表于2019-03-10 19:35 被阅读15次

    一、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子系统的优点

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

    • 统一了物理形态各异的相似的输入设备的处理功能。例如,各种鼠标,不论PS/2、USB、还是蓝牙,都被同样处理。
    • 提供了用于分发输入报告给用户应用程序的简单的事件(event)接口。你的驱动不必创建、管理/dev节点以及相关的访问方法。因此它能够很方便的调用输入API以发送鼠标移动、键盘按键,或触摸事件给用户空间。X windows这样的应用程序能够无缝地运行于输入子系统提供的event接口之上。
    • 抽取出了输入驱动的通用部分,简化了驱动,并提供了一致性。例如,输入子系统提供了一个底层驱动(称为serio)的集合,支持对串口和键盘控制器等硬件输入的访问。

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

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

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

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

    • 申请一个字符设备号:可以自己指定,也可系统自动分配;
    • 构造一个file_operations结构体,其包含对设备的所有操作;
    • 实现file_operations结构体中的成员函数;
    • 将字符设备注册进系统中:register_chrdev();
    • 创建设备类和设备节点:class_create()、device_create();
    • 告诉内核入口与出口函数:module_init()、module_exit();

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

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

    • 申请一个输入设备结构体:input_allocate_device();
    • 设置输入设备支持的事务类型:set_bit(xxx,devp->evbit);
    • 设置输入设备支持的具体哪些事务:set_bit(xxx,devp->xxxbit);
    • 注册输入设备到输入子系统:input_register_device(devp);
    • 实现具体的硬件相关操作,如注册中断等,并在中断处理函数中通知事务已发生:input_event()、input_sync();

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

    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几个的关系

    • Linux系统启动时注册输入子系统(注册主设备号为13的字符设备);
    • 应用程序调用open(),对应调用输入子系统的input_open_file();
    • input_open_file()找到对应的input_handler,并调用其中的open();
    • 应用程序调用read()函数,对应调用open()中找到的input_handler中的read()函数,阻塞;
    • 驱动收到硬件访问需求,进入中断处理函数,对应到input_dev;
    • 驱动调用input_event()上报事件,上报过程为:通过input_dev找到input_handle,再通过input_handle找到匹配的input_handler,然后调用该input_handler的event()函数,该函数即是唤醒对应read()、write()函数的实现;

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

    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所示。

    相关文章

      网友评论

        本文标题:Linux驱动之输入子系统(张栖银详谈)

        本文链接:https://www.haomeiwen.com/subject/skeppqtx.html