input子系统

Posted 四季帆

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了input子系统相关的知识,希望对你有一定的参考价值。

1. Linux中input子系统介绍

input驱动程序是Linux输入设备的驱动程序,分成游戏杆(joystick)、鼠标(mouse和mice)、键盘(keyboard)、事件设备(event)。其中事件设备驱动程序是目前通用的驱动程序,可支持键盘、鼠标、触摸屏等多种输入设备。

事件设备驱动程序(event)是通用的,可以包括所有类型的输入设备,也是目前的主流。

Linux input子系统将一个输入设备的输入过程分成了设备驱动(input device driver)和事件驱动(input event driver)两个层。前者负责从硬件设备采集数据,后者负责与用户程序对接,将采集的数据分发给不同的用户接口。通过这样的设计,将千差万别的输入设备统一到了为数不多的几种驱动接口上。同一种事件驱动可以用来处理多个同类设备,同一个设备也可以和多种使劲按驱动相衔接。事件驱动和设备驱动则由输入核心层进行连接、匹配。

上:输入事件驱动层         (打包数据,面向应用)
中:输入核心层             (向下提供注册接口,向上给具体的hander发送数据)
下:输入设备驱动层         (底层驱动,面向硬件)

2. input输入子系统工作过程

以一次鼠标按下事件为例来说明:

设备驱动层:鼠标左键按下,触发中断(中断是早就注册好的),在中断服务函数中读取硬件寄存器来判断按下的是哪个按键和状态--->
input core层:中断服务函数中得到的按键信息会上报给input core层,input core层处理好了之后就会上报给input event层--->
input event层:input event层将收到的输入事件封装成一个input_event结构体放入一个缓冲区中--->
应用层:应用层read就会将缓冲区的数据读取出去

3. input子系统中的四个对象

input_device:代表着具体的输入设备,它直接从硬件中读取数据,并以事件的形式转发
handle:用于将input_device和handler连接起来,对应于某一个具体的设备文件
client:对应于用户程序对文件的访问接口,每open一次事件驱动,就创建一个client

4. input子系统的核心层维护着两条重要的链表

static LIST_HEAD(input_dev_list);      //记录所有的输入设备
static LIST_HEAD(input_handler_list);  //记录所有的事件驱动

每当一个新的设备或者一个新的事件驱动被系统加载(调用input_register_device()或input_register_driver()),都会扫描整个链表,并调用函数input_match_device尝试配对工作,input_handler->id_table记录了需要匹配的特征。

 

5. input子系统的代码框架

5.1 input子系统本身的注册

input_init(void)
    class_register(&input_class);        /* sysfs的class下出现input文件夹 */
    input_proc_init();                   /* 初始化input的proc文件系统 */
    register_chrdev(INPUT_MAJOR, "input", &input_fops);    /* 注册字符设备 */

5.2 input的dev注册过程

input = input_allocate_device();  /* 申请一个设备空间 */
......                            /* 初始化input里面的数据 */
input_register_device(input);     /* 注册该设备 */
    ......                        /* 各种填充input里面的变量 */
    device_add(&dev->dev);       /* 经过此步骤后sysfs的class里面的input下面会多出来一个inputx,里面展示的name等就是前面填充和初始化的 */
    list_add_tail(&dev->node, &input_dev_list);  /* 增加该dev到核心层维护的dev链表中 */
    list_for_each_entry(handler, &input_handler_list, node)    
        input_attach_handler(dev, handler);      /* 尝试匹配handler */ 
            input_match_device(handler, dev)     /* 匹配过程(算法) */   
            handler->connect(handler, dev, id);  /* 匹配上了则连接两者到handle,并创建设备(假设匹配到了evdev) */
            .......                              /* 分配次设备号,申请一个struct evdev,并初始化里面的链表,等待队列等等 */
            input_register_handle(&evdev->handle);    /* 绑定dev到handle,handler在初始化这里以及绑定了 */
            evdev_install_chrdev(evdev);         /* 把该evdev添加到evdev维护的数组中 */
            device_add(&evdev->dev);             /* 在sysfs的 class里的input文件夹下创建eventx,为应用层提供接口 */

5.3 input的handler注册过程

input_register_handler(&evdev_handler);
    input_table[handler->minor >> 5] = handler;            /* 注册该handler类型 */
    list_add_tail(&handler->node, &input_handler_list);    /* 把该handler加入到核心层维护的handler链表 */
    list_for_each_entry(dev, &input_dev_list, node)
	input_attach_handler(dev, handler);                /* 尝试匹配dev */ 
            input_match_device(handler, dev)               /* 匹配过程(算法) */   
            handler->connect(handler, dev, id);            /* 匹配上了则连接两者到handle */
                .......                          /* 分配次设备号,申请一个struct evdev,并初始化里面的链表,等待队列等等 */
                input_register_handle(&evdev->handle);    /* 绑定dev到handle,handler在初始化这里以及绑定了 */
                evdev_install_chrdev(evdev);         /* 把该evdev添加到evdev维护的数组中 */
                device_add(&evdev->dev);             /* 在sysfs的 class里的input文件夹下创建eventx,为应用层提供接口 */

5.4  事件如何从设备驱动层传递到应用层(以按键事件为例)

input_report_key(button_dev, KEY_LEFT, !gpio_get_value(S5PV210_GPH0(2)));
    input_event(dev, EV_KEY, code, !!value);
        input_handle_event(dev, type, code, value);
            .....                                            /* 各种分析按键类型等 */
            input_pass_event(dev, type, code, value);
            /* 通过dev->hlist找到handle */
            handle->handler->event(handle, type, code, value);    /* 进而通过handle找到handler以及里面的event函数 */
                .......                                            /* event里面打包数据成input_event格式 */
                evdev_pass_event(client, &event);                  /* 把数据放到环形数组,并通知上层 */  
                client->buffer[client->head++] = *event;      /* 数据放到缓冲区 */
                kill_fasync(&client->fasync, SIGIO, POLL_IN)  /* 异步通知应用层 */
        wake_up_interruptible(&evdev->wait);                  /* 唤醒等待队列 */           

5.5 应用层如何读取一个事件

evdev_read
    ......                /* 错误分析 */
    retval = wait_event_interruptible(evdev->wait,client->head != client->tail || !evdev->exist);    /* 睡眠,自己会进入等待队列,等待被唤醒 */    
    /* 执行到这里说明发生了事件,是被上面的等待队列唤醒的 */
    evdev_fetch_next_event(client, &event));
        *event = client->buffer[client->tail++];                    /* 将buffer中数据取走*/
    input_event_to_user(buffer + retval, &event);
        copy_to_user(buffer, event, sizeof(struct input_event));    /* 将数据拷贝到用户空间 */    

5.6 应用层如何打开一个设备

evdev_open
    evdev = evdev_table[i];          /* 通过次设备号得到该event,event是在connect中放的 */
    ......                           /* 申请一个client并初始化,即使是同一个event,打开多次也要申请多个event */
    evdev_attach_client(evdev, client);  /* 把该client加入到该evdev的打开链表中 */
    evdev_open_device(evdev);
        ......                           /* 判断是不是已经打开了,已经打开的就单纯的把evdev里的open打开次数+1,如果打开次数是0,测需要条用核心层的open */
        input_open_device(&evdev->handle);
            ......                       /* 核心层则通过handlr里面的open计数来打开次数 ,同时该设备也有自己的user打开次数统计*/ 
            dev->open(dev);              /* 核心层检查发现确实是第一次打开,并且设备驱动层有定义open函数,则继续调用设备驱动层的open函数 */
    nonseekable_open(inode, file);      /* 打开完后,把该打开模式设置为不能使用seek读取(输入只能一包一包读,不能跳着读) */

7. 个人总结(现有水平有限,可能总结得不对):

7.1 浅谈绑定机制

dev和handler匹配以后,将两者都添加到另一个数据结构handle中(称之为绑定),上报事件的过程中先找到handle,然后在handle中找到相应的dev和handler。其实,Linux下绑定机制都是类似的,匹配成功之后就将两者放到另一个结构体,我记得,platform总线下的绑定机制也是如此。

7.2 驱动中等待队列的惯用手法

应用层读操作的时候很可能会遇到要读的内容还没准备好,所有这个时候读进程需要休眠,使用等待队列入队;

驱动层上报事件完毕之后(缓冲区中有了内容),唤醒等待队列(应用层的读进程被唤醒)。

 

 

 

以上是关于input子系统的主要内容,如果未能解决你的问题,请参考以下文章

系统入门深度学习,直击算法工程师m

有人可以解释以下 R 代码片段吗? [关闭]

为啥尽管源代码没有变化,但从一个系统到另一个系统的片段数量却有很大差异?

引用向量的部分片段?

C#程序员经常用到的10个实用代码片段 - 操作系统

使用带有渲染功能的 Vue.js 3 片段