zoukankan      html  css  js  c++  java
  • 输入子系统event层分析

    转自:http://www.cnitblog.com/luofuchong/archive/2007/11/12/36157.html

    #####################################################################################################
    早前曾研究了一下输入子系统的原理,给人的感觉是输入子系统很复杂.但其实内核开发者在这方面已经做得很完善了,
    输入子系统虽然错综复杂,但是只要我们领会了输入子系统的一些设计思想后,我们要使用它并非难事.

    以下以内核自带的gpio_keys驱动为例,介绍输入子系统的使用.
    主要的原因是gpio_keys驱动比较简单易懂,另外不是没个人都有触摸屏,但键盘的话相信每一块开发板上都配有吧^_^

    按照以前的习惯,先从下到上的研究底层驱动是如何提交输入事件的:
    #####################################################################################################

    drivers/input/keyboard/gpio_keys.c:

    static int __devinit gpio_keys_probe(struct platform_device *pdev)
    {
        struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;
        struct input_dev *input;
        int i, error;

        input = input_allocate_device();//申请input_dev结构
        if (!input)
            return -ENOMEM;

        platform_set_drvdata(pdev, input);//把input_dev结构放好(以后方便调用)

        input->evbit[0] = BIT(EV_KEY);//目前event的类型不操作32,所以你会看到对于evbit数组的操作都是对evbit[0]中的位来进行操作.

        input->name = pdev->name;
        input->phys = "gpio-keys/input0";
        input->dev.parent = &pdev->dev;

        input->id.bustype = BUS_HOST;
        input->id.vendor = 0x0001;
        input->id.product = 0x0001;
        input->id.version = 0x0100;

        for (i = 0; i < pdata->nbuttons; i++) {
            struct gpio_keys_button *button = &pdata->buttons[i];
            int irq = gpio_to_irq(button->gpio);
            unsigned int type = button->type ?: EV_KEY;

            set_irq_type(irq, IRQ_TYPE_EDGE_BOTH);

            /* 根据用户所指定的gpio_keys来申请中断和注册中断处理函数*/
            error = request_irq(irq, gpio_keys_isr, IRQF_SAMPLE_RANDOM,
                         button->desc ? button->desc : "gpio_keys",
                         pdev);
            if (error) {
                printk(KERN_ERR "gpio-keys: unable to claim irq %d; error %d\n",
                    irq, error);
                goto fail;
            }

            input_set_capability(input, type, button->code);
        }

        error = input_register_device(input);//注册输入设备,并和对应的handler处理函数挂钩
        if (error) {
            printk(KERN_ERR "Unable to register gpio-keys input device\n");
            goto fail;
        }

        return 0;

     fail:
        for (i = i - 1; i >= 0; i--)
            free_irq(gpio_to_irq(pdata->buttons[i].gpio), pdev);

        input_free_device(input);

        return error;
    }


    提到input_dev结构,以下谈一下我对于它的理解:
    struct input_dev {

        void *private;

        const char *name;
        const char *phys;
        const char *uniq;
        struct input_id id;

        /*
         * 根据各种输入信号的类型来建立类型为unsigned long 的数组,
         * 数组的每1bit代表一种信号类型,
         * 内核中会对其进行置位或清位操作来表示时间的发生和被处理.
         */

        unsigned long evbit[NBITS(EV_MAX)];
        unsigned long keybit[NBITS(KEY_MAX)];
        unsigned long relbit[NBITS(REL_MAX)];
        unsigned long absbit[NBITS(ABS_MAX)];
        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)];

        .........................................
    };

    /**
     * input_set_capability - mark device as capable of a certain event
     * @dev: device that is capable of emitting or accepting event
     * @type: type of the event (EV_KEY, EV_REL, etc...)
     * @code: event code
     *
     * In addition to setting up corresponding bit in appropriate capability
     * bitmap the function also adjusts dev->evbit.
     */

    /* 记录本设备对于哪些事件感兴趣(对其进行处理)*/
    void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code)
    {
        switch (type) {
        case EV_KEY:
            __set_bit(code, dev->keybit);//比如按键,应该对哪些键值的按键进行处理(对于其它按键不予理睬)
            break;

        case EV_REL:
            __set_bit(code, dev->relbit);
            break;

        case EV_ABS:
            __set_bit(code, dev->absbit);
            break;

        case EV_MSC:
            __set_bit(code, dev->mscbit);
            break;

        case EV_SW:
            __set_bit(code, dev->swbit);
            break;

        case EV_LED:
            __set_bit(code, dev->ledbit);
            break;

        case EV_SND:
            __set_bit(code, dev->sndbit);
            break;

        case EV_FF:
            __set_bit(code, dev->ffbit);
            break;

        default:
            printk(KERN_ERR
                "input_set_capability: unknown type %u (code %u)\n",
                type, code);
            dump_stack();
            return;
        }

        __set_bit(type, dev->evbit);//感觉和前面重复了(前面一经配置过一次了)
    }
    EXPORT_SYMBOL(input_set_capability);


    static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
    {
            int i;
            struct platform_device *pdev = dev_id;
            struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;
            struct input_dev *input = platform_get_drvdata(pdev);

            for (i = 0; i < pdata->nbuttons; i++) {
                    struct gpio_keys_button *button = &pdata->buttons[i];
                    int gpio = button->gpio;

                    if (irq == gpio_to_irq(gpio)) {//判断哪个键被按了?
                            unsigned int type = button->type ?: EV_KEY;
                            int state = (gpio_get_value(gpio) ? 1 : 0) ^ button->active_low;//记录按键状态

                            input_event(input, type, button->code, !!state);//汇报输入事件
                            input_sync(input);//等待输入事件处理完成
                    }
            }

            return IRQ_HANDLED;
    }


    /*
     * input_event() - report new input event
     * @dev: device that generated the event
     * @type: type of the event
     * @code: event code
     * @value: value of the event
     *
     * This function should be used by drivers implementing various input devices
     * See also input_inject_event()
     */
    void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
    {
        struct input_handle *handle;

        if (type > EV_MAX || !test_bit(type, dev->evbit))//首先判断该事件类型是否有效且为该设备所接受
            return;

        add_input_randomness(type, code, value);

        switch (type) {

            case EV_SYN:
                switch (code) {
                    case SYN_CONFIG:
                        if (dev->event)
                            dev->event(dev, type, code, value);
                        break;

                    case SYN_REPORT:
                        if (dev->sync)
                            return;
                        dev->sync = 1;
                        break;
                }
                break;

            case EV_KEY:
                /*
                 * 这里需要满足几个条件:
                 * 1: 键值有效(不超出定义的键值的有效范围)
                 * 2: 键值为设备所能接受(属于该设备所拥有的键值范围)
                 * 3: 按键状态改变了
                 */

                if (code > KEY_MAX || !test_bit(code, dev->keybit) || !!test_bit(code, dev->key) == value)
                    return;

                if (value == 2)
                    break;

                change_bit(code, dev->key);//改变对应按键的状态

                /* 如果你希望按键未释放的时候不断汇报按键事件的话需要以下这个(在简单的gpio_keys驱动中不需要这个,暂时不去分析) */
                if (test_bit(EV_REP, dev->evbit) && dev->rep[REP_PERIOD] && dev->rep[REP_DELAY] && dev->timer.data && value) {
                    dev->repeat_key = code;
                    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->rep[REP_DELAY]));
                }

                break;
    ........................................................

        if (type != EV_SYN)
            dev->sync = 0;

        if (dev->grab)
            dev->grab->handler->event(dev->grab, type, code, value);
        else
            /*
             * 循环调用所有处理该设备的handle(event,mouse,ts,joy等),
             * 如果有进程打开了这些handle(进行读写),则调用其对应的event接口向气汇报该输入事件.
             */
            list_for_each_entry(handle, &dev->h_list, d_node)
                if (handle->open)
                    handle->handler->event(handle, type, code, value);
    }
    EXPORT_SYMBOL(input_event);


    #########################################################################
    好了,下面再来研究一下event层对于input层报告的这个键盘输入事件是如何来处理的.
    #########################################################################

    drivers/input/evdev.c:

    static struct input_handler evdev_handler = {
            .event =        evdev_event,
            .connect =      evdev_connect,
            .disconnect =   evdev_disconnect,
            .fops =         &evdev_fops,
            .minor =        EVDEV_MINOR_BASE,
            .name =         "evdev",
            .id_table =     evdev_ids,
    };

    static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
    {
            struct evdev *evdev = handle->private;
            struct evdev_client *client;

            if (evdev->grab) {
                    client = evdev->grab;

                    do_gettimeofday(&client->buffer[client->head].time);
                    client->buffer[client->head].type = type;
                    client->buffer[client->head].code = code;
                    client->buffer[client->head].value = value;
                    client->head = (client->head + 1) & (EVDEV_BUFFER_SIZE - 1);

                    kill_fasync(&client->fasync, SIGIO, POLL_IN);
            } else
                      /* 遍厉client_list链表中的client结构(代表些打开evdev的进程(个人理解^_^)) */
                    list_for_each_entry(client, &evdev->client_list, node) {
                                /* 填充代表该输入信号的struct input_event结构(事件,类型,键码,键值) */
                            do_gettimeofday(&client->buffer[client->head].time);
                            client->buffer[client->head].type = type;
                            client->buffer[client->head].code = code;
                            client->buffer[client->head].value = value;
                                /* 更新写指针 */
                            client->head = (client->head + 1) & (EVDEV_BUFFER_SIZE - 1);

                            kill_fasync(&client->fasync, SIGIO, POLL_IN);//通知调用input_sync的进程:输入事件经已处理完毕(通知底层).
                    }

            wake_up_interruptible(&evdev->wait);//唤醒睡眠在evdev->wait等待队列等待输入信息的进程(通知上层).
    }

    ###################################################################################
    好了,至此一个按键的输入事件处理完毕,现在再来从上到上的来看看用户是如何获取这个输入事件的.
    ###################################################################################


    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
    };


    static int evdev_open(struct inode *inode, struct file *file)
    {
            struct evdev_client *client;
            struct evdev *evdev;
            int i = iminor(inode) - EVDEV_MINOR_BASE;
            int error;

            if (i >= EVDEV_MINORS)
                    return -ENODEV;

            evdev = evdev_table[i];

            if (!evdev || !evdev->exist)
                    return -ENODEV;

            client = kzalloc(sizeof(struct evdev_client), GFP_KERNEL);
            if (!client)
                    return -ENOMEM;

            client->evdev = evdev;
             /* 添加evdev_client结构到链表evdev->client_list中(好让输入事件到来的时候填写该结构并唤醒进程读取) */
            list_add_tail(&client->node, &evdev->client_list);

            if (!evdev->open++ && evdev->exist) {
                    error = input_open_device(&evdev->handle);
                    if (error) {
                            list_del(&client->node);
                            kfree(client);
                            return error;
                    }
            }

            file->private_data = client;//存放好evdev_client结构方便以后使用
            return 0;
    }


    static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
    {
            struct evdev_client *client = file->private_data;
            struct evdev *evdev = client->evdev;
            int retval;

            if (count < evdev_event_size())//对于每次读取的数据大小是有一定的要求.
                    return -EINVAL;

            if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))//缓存中没有数据可读且设备是存在的,
                                                          如果设置为NONBLOCK方式来读,立即返回.
                    return -EAGAIN;

            retval = wait_event_interruptible(evdev->wait,
                    client->head != client->tail || !evdev->exist);//否则等待缓存有数据可读或设备不存在(被移去)
            if (retval)
                    return retval;

            if (!evdev->exist)
                    return -ENODEV;

            while (client->head != client->tail && retval + evdev_event_size() <= count) {//下面开始读取数据

                    struct input_event *event = (struct input_event *) client->buffer + client->tail;//获取缓存中的读指针

                    if (evdev_event_to_user(buffer + retval, event))//返回数据给用户
                            return -EFAULT;

                    client->tail = (client->tail + 1) & (EVDEV_BUFFER_SIZE - 1);//更新读指针
                    retval += evdev_event_size();
            }

            return retval;
    }

    呵呵,看到了吧,应用程序就是这样获取输入事件的^_^

    ######################################################################################################################################
    本来对于gpio_keys这样的驱动程序,只要当发生按键事件的时候向上层应用程序汇报键值即可.
    不过,对于一些带输出设备(例如led灯)的输入设备来说(例如键盘),上层应用程序同样可以利用event层来读取或改变其状态.
    请看以下代码:
    ######################################################################################################################################

    static ssize_t evdev_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos)
    {
            struct evdev_client *client = file->private_data;
            struct evdev *evdev = client->evdev;
            struct input_event event;
            int retval = 0;

            if (!evdev->exist)
                    return -ENODEV;

            while (retval < count) {

                    if (evdev_event_from_user(buffer + retval, &event))//从用户处获取事件结构
                            return -EFAULT;
                    input_inject_event(&evdev->handle, event.type, event.code, event.value);//往底层发送事件
                    retval += evdev_event_size();
            }

            return retval;
    }


    /**
     * input_inject_event() - send input event from input handler
     * @handle: input handle to send event through
     * @type: type of the event
     * @code: event code
     * @value: value of the event
     *
     * Similar to input_event() but will ignore event if device is "grabbed" and handle
     * injecting event is not the one that owns the device.
     */
    void input_inject_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
    {
            if (!handle->dev->grab || handle->dev->grab == handle)
                    input_event(handle->dev, type, code, value);
    }
    EXPORT_SYMBOL(input_inject_event);

    /*
     * input_event() - report new input event
     * @dev: device that generated the event
     * @type: type of the event
     * @code: event code
     * @value: value of the event
     *
     * This function should be used by drivers implementing various input devices
     * See also input_inject_event()
     */
    void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
    {
        struct input_handle *handle;

        if (type > EV_MAX || !test_bit(type, dev->evbit))//首先判断该事件类型是否有效且为该设备所接受
            return;

        add_input_randomness(type, code, value);

        switch (type) {

            case EV_SYN:
                switch (code) {
                    case SYN_CONFIG:
                        if (dev->event)
                            dev->event(dev, type, code, value);
                        break;

                    case SYN_REPORT:
                        if (dev->sync)
                            return;
                        dev->sync = 1;
                        break;
                }
                break;

    .............................................................
            case EV_LED:

                if (code > LED_MAX || !test_bit(code, dev->ledbit) || !!test_bit(code, dev->led) == value)
                    return;

                change_bit(code, dev->led);

                if (dev->event)
                    dev->event(dev, type, code, value);

                break;


        if (type != EV_SYN)
            dev->sync = 0;

        if (dev->grab)
            dev->grab->handler->event(dev->grab, type, code, value);
        else
            /*
             * 循环调用所有处理该设备的handle(event,mouse,ts,joy等),
             * 如果有进程打开了这些handle(进行读写),则调用其对应的event接口向气汇报该输入事件.
             */
            list_for_each_entry(handle, &dev->h_list, d_node)
                if (handle->open)
                    handle->handler->event(handle, type, code, value);
    }
    EXPORT_SYMBOL(input_event);

    注:
        鉴于简单的gpio_keys驱动中没有注册自己的event接口,当然也没有对于LED灯的处理,而event层只是简单的向上层汇报输入事件(event层也不可能帮你处理你的led设备,对吧),所以这个通过输入子系统控制LED的部分暂时不去研究.
        (输出设备LED灯不属于这个输入设备gpio_key的一部分.当然,如果你想通过这个gpio_keys设备来控制led灯的话,可以修改这个gpio_keys驱动,详细可参考driver/input/keyboard目录下的驱动)

  • 相关阅读:
    [原创]在使用SDK 23(6.0)版本后org.apache.http相关的类找不到的解决办法
    [原创] Gradle DSL method not found: 'android()' 和 buildToolsVersion is not specified 的解决办法。
    [原创]Android Lollipop (5.0) 原生代码 Settings 首页加载逻辑分析
    访问https接口报错 基础连接已经关闭: 未能为 SSL/TLS 安全通道建立信任关系
    js for循环中延迟 setTimeout
    ABP框架扩展AbpSession
    web在线查看PDF文件
    web在线预览office文件
    sql server游标读取excel文件数据,更新到指定表中
    echarts图形销毁重新绘制
  • 原文地址:https://www.cnblogs.com/cnhome/p/1696589.html
Copyright © 2011-2022 走看看