002.13.01输入子系统概念介绍

Posted

tags:

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

可以将输入子系统看做由三大部分组成,体现了一种分离分层思想。分别为:

  • 核心层
  • 事件处理层
  • 设备驱动层

技术图片

核心层:这部分主要由input.c来实现,它为事件处理层和设备驱动层提供统一接口,这里我们先列出几个重要的函数。

static int __init input_init(void)

int input_register_device(struct input_dev *dev)

int input_register_handler(struct input_handler *handler)

int input_register_handle(struct input_handle *handle)

这几个函数几乎就实现了整个输入子系统的运作过程。

input.c
    subsys_initcall(input_init);/*是整个输入子系统的入口函数,它是被编译进内核的,也就是说一开机就会被执行的*/
input.c
    static int __init input_init(void)
    {
        err = class_register(&input_class);  /*注册类,放在/sys/class*/

        err = register_chrdev(INPUT_MAJOR, "input", &input_fops);/*注册一个设备名为input且主设备号为INPUT_MAJOR(13)的字符设备,
                                                                    可以通过命令 cat /proc/devices 查看*/
    }

接下来把重点放在input_fops

input.c
    static const struct file_operations input_fops = {
        .owner = THIS_MODULE,
        .open = input_open_file,
    };

不可能只有.open函数的,肯定在input_open_file函数里实现了其它应用函数。

input.c
    static int input_open_file(struct inode *inode, struct file *file)
    {
        struct input_handler *handler = input_table[iminor(inode) >> 5];/*根据次设备号从input_table取出handler*/

        new_fops = fops_get(handler->fops),file->f_op = new_fops;/*从handler结构中获取一个fops结构并将替换原来的fops。*/

        err = new_fops->open(inode, file); /*然后调用新的file_operations结构体里面的open函数*/
        ... ...
    }

也就是说应用程序以后执行读写等操作(比如读按键)是调用新的fops里面相应的函数,而新的fops是在数组input_table[]里面,根据所打开的文件(或者说设备)的次设备号找出来的其中一个handler,在这个handler里面就有个新的fops,而input.c只起到一个中转作用。

那到底新的fops是什么时候注册进input_table[]数组里面的?

input_open_file函数一开始就从input_table获得一项handler,接下来我们会一步步追踪它的由来。

搜索下,找到tatic struct input_handler *input_table[8]发现它是一个静态变量

input.c
    static struct input_handler *input_table[8];

一步步找下来最终发现它在int input_register_handler函数中定义,但input_register_handler又是由谁调用呢?

input.c
    int input_register_handler(struct input_handler *handler)
    {
        input_table[handler->minor >> 5] = handler;/*input_table[]被赋值*/
    }

同样搜索下,找到了几个c文件调用了它,分别有evdev.c、joydev.c、mousedev.c等等。

分析到这里,就已经体现了一开始说的分离分层思想的分离思想,evdev.c、游戏手柄joydev.c、鼠标mousedev.c这些就是事件处理层,这部分的代码稳定,一般我们不用修改。

接着上面我们说的input_table在input_register_handler中定义的,我们现在要分析的就是input_register_handler函数做了哪些具体的事情。

以evdev.c为例,看看evdev_handler里面都有哪些内容:

evdev.c
    static int __init evdev_init(void)
    {
        return input_register_handler(&evdev_handler); /*调用input.c--->input_register_handler,注册evdev_handler*/
    }

    static struct input_handler evdev_handler = {
    .event =    evdev_event,
    .connect =  evdev_connect,
    .disconnect =   evdev_disconnect,
    .fops =     &evdev_fops,      /*.fops:文件操作结构体,其中evdev_fops函数就是自己的写的操作函数,然后赋到.fops中*/
    .minor =    EVDEV_MINOR_BASE, /*.minor:用来存放次设备号,EVDEV_MINOR_BASE=64,然后调用
                                     input_register_handler(&evdev_handler)后,
                                     由于EVDEV_MINOR_BASE/32=2,所以存到input_table[2]中*/
    .name =     "evdev",
    .id_table = evdev_ids, /*一个存放该handler所支持的设备id的表(其实内部存放的是EV_xxx事件,用于判断device是否支持该事件)*/
    };

    static const struct file_operations evdev_fops = {
    .owner =    THIS_MODULE,
    .read =     evdev_read,
    .write =    evdev_write,
    .poll =     evdev_poll,
    .open =     evdev_open,
    .release =  evdev_release,
    .unlocked_ioctl = evdev_ioctl,
    #ifdef CONFIG_COMPAT
    .compat_ioctl = evdev_ioctl_compat,
    #endif
    .fasync =   evdev_fasync,
    .flush =    evdev_flush
    }

我们看input.c--->input_register_handler,是如何注册handler的

这个函数主要做了三件事情:

  • 1、将handler放进input_table表中,而不同的处理层该input_handler的结构不同,比如evdev.c与joydev.c的input_handler的结构就可能不一样
  • 2、将handler放进input_handler_list链表中
  • 3、从input_dev_list链表中找出每个input_dev并执行input_attach_handler(dev, handler);
input.c
    int input_register_handler(struct input_handler *handler)
    {
        input_table[handler->minor >> 5];  /*① 这里就是将handler注册进input_table[]数组,而不同的处理层该input_handler的结构不同*/
        list_add_tail(&handler->node, &input_handler_list);  /*②将handler放进input_handler_list链表中*/
        list_for_each_entry(dev, &input_dev_list, node) /*③从input_dev_list链表中找出每个input_dev并执行input_attach_handler*/
              input_attach_handler(dev, handler);
    }

继续跟踪 input_attach_handler

input.c
    static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
    {
        id = input_match_device(handler->id_table, dev);  /*①对两者的id进行匹配,同样若有不同则匹配不成功。*/
        error = handler->connect(handler, dev, id);   /*②当两者匹配成功后执行handler->connect(handler, dev, id)*/
    }

内核中input_device_id的数据结构如下:

路径:includelinuxMod_devicetable.h

struct input_device_id {

    kernel_ulong_t flags;   /* 这个flag 表示我们的这个 input_device_id 是用来匹配下面的4个情况的哪一项*/
                            /* flag == 1表示匹配总线  2表示匹配供应商   4表示匹配产品  8表示匹配版本*/

    __u16 bustype;  /*对应input_id的四个数据域*/
    __u16 vendor;
    __u16 product;
    __u16 version;

     /*存储支持事件的位图,与input_dev中的同名数据成员功能一致*/
     /*(其实内部存放的是EV_xxx事件,用于判断device是否支持该事件)*/
    kernel_ulong_t evbit[INPUT_DEVICE_ID_EV_MAX / BITS_PER_LONG + 1];  
    kernel_ulong_t keybit[INPUT_DEVICE_ID_KEY_MAX / BITS_PER_LONG + 1]; 
    kernel_ulong_t relbit[INPUT_DEVICE_ID_REL_MAX / BITS_PER_LONG + 1];
    kernel_ulong_t absbit[INPUT_DEVICE_ID_ABS_MAX / BITS_PER_LONG + 1];
    kernel_ulong_t mscbit[INPUT_DEVICE_ID_MSC_MAX / BITS_PER_LONG + 1];
    kernel_ulong_t ledbit[INPUT_DEVICE_ID_LED_MAX / BITS_PER_LONG + 1];
    kernel_ulong_t sndbit[INPUT_DEVICE_ID_SND_MAX / BITS_PER_LONG + 1];
    kernel_ulong_t ffbit[INPUT_DEVICE_ID_FF_MAX / BITS_PER_LONG + 1];
    kernel_ulong_t swbit[INPUT_DEVICE_ID_SW_MAX / BITS_PER_LONG + 1];

    kernel_ulong_t driver_info;
};

而evdev_handler的.id_table = evdev_ids, 如下:

evdev.c
    static const struct input_device_id evdev_ids[] = {
        { .driver_info = 1 },   /* Matches all devices */  /*所有设备都支持,所以就不会存在id不匹配的情况*/
        { },            /* Terminating zero entry */ /*终止0头目*/
    };

匹配过程如下:

input.c
    static const struct input_device_id *input_match_device(const struct input_device_id *id,
                                struct input_dev *dev)
    {
        int i;

        for (; id->flags || id->driver_info; id++) {

            if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
                if (id->bustype != dev->id.bustype)
                    continue;

            if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
                if (id->vendor != dev->id.vendor)
                    continue;

            if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
                if (id->product != dev->id.product)
                    continue;

            if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
                if (id->version != dev->id.version)
                    continue;

            MATCH_BIT(evbit,  EV_MAX);
            MATCH_BIT(keybit, KEY_MAX);
            MATCH_BIT(relbit, REL_MAX);
            MATCH_BIT(absbit, ABS_MAX);
            MATCH_BIT(mscbit, MSC_MAX);
            MATCH_BIT(ledbit, LED_MAX);
            MATCH_BIT(sndbit, SND_MAX);
            MATCH_BIT(ffbit,  FF_MAX);
            MATCH_BIT(swbit,  SW_MAX);

            return id;
        }

        return NULL;
    }

两者匹配成功后执行 evdev_connect:

evdev.c
    static int evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id)
    {
        evdev->handle.dev = dev;
        evdev->handle.handler = handler; /*这两句,handle连接了dev、handle*/

        class_device_create(&input_class, &dev->cdev, devt, dev->cdev.dev, evdev->name); /*创建了一个设备节点*/

        input_register_handle(&evdev->handle); /*注册一个handle,注意不是handler*/
    }

nput_handle结构体的原型如下,只有dev、handler、d_node、h_node几个成员。

input.h
    struct input_handle {

        void *private;

        int open;
        const char *name;

        struct input_dev *dev;
        struct input_handler *handler;

        struct list_head    d_node;/*input_handle通过d_node连接到了input_dev上的h_list链表上*/
        struct list_head    h_node;/*input_handle通过h_node连接到了input_handler的h_list链表上*/
    };

input_register_handle

input.c
    int input_register_handle(struct input_handle *handle)
    {
        struct input_handler *handler = handle->handler;/*将handle里保存的handler拿出来*/

        /*
            下面两句,这样就可以将dev和handler建立起连接了
        */
        list_add_tail(&handle->d_node, &handle->dev->h_list);/*把input_handle的d_node加入input_dev的h_list*/
        list_add_tail(&handle->h_node, &handler->h_list); /*把input_handle的h_node加入input_handler的h_list*/

    }

由以上可以得出handler、handle、input_dev的关系如下:
技术图片

建立起连接以后:
①就可以通过input_handler里面的h_list找到input_handle,然后通过input_handle里面的dev找到input_dev。
②也可以通过inpu_dev的h_list找到input_handle,然后通过input_handle的handler找到input_handler

至此我们对事件处理层的分析基本完成了,我们接下来再分析设备驱动层

那么input_dev在哪里注册,input_dev结构体又有哪些内容?

先看input_dev结构体,有哪些内容:

input.h
    struct input_dev 
    {
        void *private;
        const char *name;  /*这4个不用管,不重要。*/
        const char *phys;
        const char *uniq;
        struct input_id id;

        unsigned long evbit[NBITS(EV_MAX)];   /*evbit: 表示能产生哪类事件看EV_MAX这个宏*/
        unsigned long keybit[NBITS(KEY_MAX)]; /*表示能产生哪些按键*/
        unsigned long relbit[NBITS(REL_MAX)]; /*表示能产生哪些相对位移事件,x,y,滚轮(比如鼠标)*/
        unsigned long absbit[NBITS(ABS_MAX)]; /*表示能产生哪些绝对位移事件,x,y(比如触摸屏)*/
        unsigned long mscbit[NBITS(MSC_MAX)];
        unsigned long ledbit[NBITS(LED_MAX)];
        unsigned long sndbit[NBITS(SND_MAX)];
        unsigned long ffbit[NBITS(FF_MAX)];
        unsigned long swbit[NBITS(SW_MAX)];
        ... ... 
        struct list_head    h_list;
        struct list_head    node;
    }

再看看input_dev注册函数input_register_device():

input.c
    int input_register_device(struct input_dev *dev)
    {
        list_add_tail(&dev->node, &input_dev_list);

        list_for_each_entry(handler, &input_handler_list, node)  /*主要的事情还是这两件,这和上面讲的事件处理层很对称。*/
            input_attach_handler(dev, handler);
    }

Linux内核已经自带有一些事件处理器,可以支持大部分输入设备,比如evdev.c、mousedev.c、joydev.c等,这些.c文件所在的层为handler层。linux内核自带的input_handler中,evdev_handler是最常见的,因为它可以匹配任何的input_dev设备。一个事件处理器用struct input_handler结构体来表示,在evdev.c文件中的定义如下。
static struct input_handler evdev_handler。我们在evdev.c里面搜索不到input_register_device函数,是因为evdev.c是用作事件处理,并不支持输入设备。

搜索:input_register_device,发现存在于下面这些文件中,里面是各种鼠标、按键、触摸屏等等驱动,在这些驱动里面构造:
技术图片

一般设备驱动层都是由用户根据设备的硬件情况自己编写代码的,最终要调用input_register_device将设备注册进内核。

我们以driversinputkeyboardGpio_keys.c 作为示例,看看是如何注册input_dev的:

Gpio_keys.c /*注意linux内核的Gpio_keys.c只是一个参考范例,没实际用途。*/
    static int __devinit gpio_keys_probe(struct platform_device *pdev)
    {
        struct input_dev *input;  /*定义一个input_dev结构体指针input*/

        /*①分配一个input_dev结构体input*/
        input = input_allocate_device(); 

        /*②设置(其实这里设置的就是dev的id,跟handler里面的.id_table里各项内容对比要对应才能建立起连接)*/
        /*1.能产生哪类事件?*/
        input->evbit[0] = BIT(EV_KEY); /*设置nput->evbit[0]这个数组中的0位为EV_KEY,这个驱动就能产生按键类事件*/
        //set_bit(EV_KEY, input->evbit); /*这样写会更好,功能是一样的。*/
        //set_bit(EV_REP, input->evbit); /*使之能产生重复类事件*/

        /*2 能产生这类操作里的哪些事件?*/
        //set_bit(KEY_L, input->keybit);/*设置这个数组里面的某一位,能够产生L这个键*/

        ... ... 

        /* ③注册input_dev结构体buttons_dev */
        error = input_register_device(input); 
        /*
            · 注册之后,它就会把buttons_dev这个设备(结构体)放到input_dev_list链表里面去
            · 把input_handler_list链表里面的每一个input_handler(结构体),一个个拿出来跟buttons_dev这个input_dev结构体的id作比较
              如果input_handler的id_table表示说能匹配这个buttons_dev的话,就会调用input_handler的connect函数,connect函数就会建立
              一个input_handle。
            · input_handle结构体有: handler、dev、d_node、h_node成员
            · input_dev的h_list链表指向handle->d_node;同样地,input_handler的h_list也指向了handle->h_node,这样三者之间就建立
              起了连接,handle在input_dev和input_handler之前起到了桥梁般的连接作用。
            · 简单点可以理解为:input_handler里面的h_list指向input_handle结构体,inpu_dev里面的h_list同样指向input_handle结构体

            建立起连接以后: 
            就可以通过input_handler里面的h_list找到input_handle,然后通过input_handle里面的dev找到input_dev。
            也可以通过inpu_dev的h_list找到input_handle,然后通过input_handle的handler找到input_handler
        */

        if (error) {/*判断返回值*/}
    }

在linux内核中evdev.c中的evdev_handler的.id_table内容为:支持所有输入设备,所以connect函数就会建立一个input_handle,然后使inputv和evdev_handler建立连接。

至此设备处理层也将完了。

接下来讲讲从应用程序的角度如何使用输入子系统。

其中一个重要的函数为input_event

那如果没有按键按下的话,你读按键,最终会调用evdev.c的evdev_handler->.fops->.read函数,没有数据产生就进入休眠状态。在哪里唤醒呢?看下一部分。

我们分析一下evdev.c事件驱动的.read函数是evdev_read()函数:

evdev.c
    static ssize_t evdev_read(struct file *file, char __user * buffer, size_t count, loff_t *ppos)
    {
         ... ...

        /*判断应用层要读取的数据是否正确*/
        if (count < evdev_event_size()) return -EINVAL; 

        /*无数据并且是非阻塞方式打开,立刻返回。*/
        if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK)) 
            return -EAGAIN;

        /*否则休眠*/
        retval = wait_event_interruptible(evdev->wait,client->head != client->tail || !evdev->exist);

          ... ...           //上传数据
    }

例如有按键中断了,我们在中断服务函数里通过input_event告知内核有哪个事件发生了,比如(更详细搜索源码:buttons.c):

buttons.c
    xxx 中断处理函数
    {
        /*参考以前的poll代码,一般进入中断会先读出键值,然后再唤醒。唤醒之后应用程序就马上可以通过read函数读取键值*/

        /* buttons_dev输入设备,EV_KEY按钮类事件,pindesc->key_val哪个引脚 ?,0是松开(看原理图判断)*/
        input_event(buttons_dev, EV_KEY, pindesc->key_val, 0); /*告知内核有哪个事件发生了,同时会唤醒,往下看就知道*/

        input_sync(buttons_dev); /*上报完事件之后,还需要上报一个同步事件,表示这个事件已经上报完了*/
    }

input_event 这个通知函数最终会调用handler的event处理函数并唤醒等待事件。

input.c
    void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
    {
        list_for_each_entry(handle, &dev->h_list, d_node);/*通过input_dev ->h_list链表找到input_handle驱动处理结构体*/

        if (handle->open) /*如果input_handle之前open 过,那么这个就是我们的驱动处理结构体*/
            handle->handler->event(handle, type, code, value); /*调用.event的evdev_event()事件函数*/
    }

evdev_event函数如下:

evdev.c
    /*
        evdev_event为evdev_handler->.event函数,当有事件发生了,有按键按下时,就会进入.event函数中处理事件
    */
    static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
    {
        ... ...

         wake_up_interruptible(&evdev->wait);/*有事件触发,便唤醒等待中断,和之前写的驱动程序一样*/
    }

以上就是整个输入子系统大概的框架。

最后,如何写一个符合input子系统框架的驱动程序?

  • 1、分配一个input_dev结构体
  • 2、设置
  • 3、注册
  • 4、硬件相关的代码,比如在中断服务程序里上报事件。

具体代码,看下一节。

以上是关于002.13.01输入子系统概念介绍的主要内容,如果未能解决你的问题,请参考以下文章

python 模块的概念介绍

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

shell-脚本概念介绍01

sublime text3 添加新片段

组件&Props

前端开发必备!Emmet使用手册