Linux输入子系统

    今天看代码的时候看到了输入子系统,就来复习了一下输入子系统的框架,顺便记录一下。
    Linux输入子系统其实是Linux内核代码里针对输入设备做了一个统一的驱动管理,采用了分层分离的思想,对上层应用软件提供了统一的标准接口。
    输入的设备很多,这里我用tsc2007的驱动作为例子,大概地说一下输入子系统。tsc2007是一个iic接口的触摸屏芯片,IIC相关的驱动这里不做深入。
    触摸屏作为一个输入设备,tsc2007自然也少不了使用输入子系统作为框架。

    首先看tsc2007的驱动入口:

    static struct i2c_driver tsc2007_driver = {
        .driver = {
            .name    = "tsc2007",
            .of_match_table = of_match_ptr(tsc2007_of_match),
        },
        .id_table    = tsc2007_idtable,
        .probe        = tsc2007_probe,
    };

    驱动采用module_i2c_driver(tsc2007_driver);这个宏直接向内核注册了IIC驱动,这里不去深入IIC驱动的框架,我们只关心输入子系统部分。
    假设IIC驱动注册成功,tsc2007_probe()函数会被调用。

static int tsc2007_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    const struct tsc2007_platform_data *pdata = dev_get_platdata(&client->dev);
    struct tsc2007 *ts;
    struct input_dev *input_dev;
    int err;

    if (!i2c_check_functionality(client->adapter,I2C_FUNC_SMBUS_READ_WORD_DATA))
        return -EIO;

    ts = devm_kzalloc(&client->dev, sizeof(struct tsc2007), GFP_KERNEL);
    if (!ts)
        return -ENOMEM;

    if (pdata)
        err = tsc2007_probe_pdev(client, ts, pdata, id);
    else
        err = tsc2007_probe_dt(client, ts);

    if (err)
        return err;

    input_dev = devm_input_allocate_device(&client->dev);
    if (!input_dev)
        return -ENOMEM;

    i2c_set_clientdata(client, ts);

    ts->client = client;
    ts->irq = client->irq;
    ts->input = input_dev;

    init_waitqueue_head(&ts->wait);
    mutex_init(&ts->mlock);

    snprintf(ts->phys, sizeof(ts->phys), "%s/input0", dev_name(&client->dev));

    input_dev->name = "TSC2007 Touchscreen";
    input_dev->phys = ts->phys;
    input_dev->id.bustype = BUS_I2C;

    input_dev->open = tsc2007_open;
    input_dev->close = tsc2007_close;

    input_set_drvdata(input_dev, ts);

    input_set_capability(input_dev, EV_KEY, BTN_TOUCH);

    input_set_abs_params(input_dev, ABS_X, 0, MAX_12BIT, ts->fuzzx, 0);
    input_set_abs_params(input_dev, ABS_Y, 0, MAX_12BIT, ts->fuzzy, 0);
    input_set_abs_params(input_dev, ABS_PRESSURE, 0, MAX_12BIT, ts->fuzzz, 0);

    if (pdata) {
        if (pdata->exit_platform_hw) {
            err = devm_add_action(&client->dev, tsc2007_call_exit_platform_hw, &client->dev);
            if (err) {
                dev_err(&client->dev,"Failed to register exit_platform_hw action, %d\n",err);
                return err;
            }
        }

        if (pdata->init_platform_hw)
            pdata->init_platform_hw();
    }

    err = devm_request_threaded_irq(&client->dev, ts->irq, tsc2007_hard_irq, tsc2007_soft_irq, IRQF_ONESHOT, client->dev.driver->name, ts);
    if (err) {
        dev_err(&client->dev, "Failed to request irq %d: %d\n", ts->irq, err);
        return err;
    }

    tsc2007_stop(ts);

    /* power down the chip (TSC2007_SETUP does not ACK on I2C) */
    err = tsc2007_xfer(ts, PWRDOWN);
    if (err < 0) {
        dev_err(&client->dev, "Failed to setup chip: %d\n", err);
        return err;    /* chip does not respond */
    }

    err = input_register_device(input_dev);
    if (err) {
        dev_err(&client->dev, "Failed to register input device: %d\n", err);
        return err;
    }

    err =  tsc2007_iio_configure(ts);
    if (err) {
        dev_err(&client->dev, "Failed to register with IIO: %d\n", err);
        return err;
    }

    return 0;
}

    很明显,在tsc2007_probe()中,驱动会采用devm_input_allocate_device(&client->dev);申请分配一个input_dev结构体。
    input_dev->open = tsc2007_open;
    input_dev->close = tsc2007_close;
    然后设置了该结构体的open成员和close成员。
    input_set_capability(input_dev, EV_KEY, BTN_TOUCH);   // 设置支持触摸事件
    input_set_abs_params(input_dev, ABS_X, 0, MAX_12BIT, ts->fuzzx, 0);  // 设置绝对位移事件,以及位移的范围
    input_set_abs_params(input_dev, ABS_Y, 0, MAX_12BIT, ts->fuzzy, 0);
    input_set_abs_params(input_dev, ABS_PRESSURE, 0, MAX_12BIT, ts->fuzzz, 0);  //设置压力事件,以及压力范围
    甚至设置了该结构体的一些属性和参数,这些参数的意思就是用了描述这个设备能产生什么样的事件,能产生什么样的参数
    比如说,产生触摸事件,X坐标,Y坐标,压力值等
    然后,驱动会调用err = input_register_device(input_dev);方法注册这个结构体,意思就是说,告诉它我这个驱动有这样一个能力做这样的事。

    我们继续深入源码, 看看input_register_device这个函数到底做了什么高大上的事情。input_register_device的源码位于/driver/inout/input.c中。

    int input_register_device(struct input_dev *dev)
    {
        struct input_devres *devres = NULL;
        struct input_handler *handler;
        unsigned int packet_size;
        const char *path;
        int error;

        if (test_bit(EV_ABS, dev->evbit) && !dev->absinfo) {
            dev_err(&dev->dev,
                "Absolute device without dev->absinfo, refusing to register\n");
            return -EINVAL;
        }

        if (dev->devres_managed) {
            devres = devres_alloc(devm_input_device_unregister,
                          sizeof(*devres), GFP_KERNEL);
            if (!devres)
                return -ENOMEM;

            devres->input = dev;
        }

        /* Every input device generates EV_SYN/SYN_REPORT events. */
        __set_bit(EV_SYN, dev->evbit);

        /* KEY_RESERVED is not supposed to be transmitted to userspace. */
        __clear_bit(KEY_RESERVED, dev->keybit);

        /* Make sure that bitmasks not mentioned in dev->evbit are clean. */
        input_cleanse_bitmasks(dev);

        packet_size = input_estimate_events_per_packet(dev);
        if (dev->hint_events_per_packet < packet_size)
            dev->hint_events_per_packet = packet_size;

        dev->max_vals = dev->hint_events_per_packet + 2;
        dev->vals = kcalloc(dev->max_vals, sizeof(*dev->vals), GFP_KERNEL);
        if (!dev->vals) {
            error = -ENOMEM;
            goto err_devres_free;
        }

        /*
         * If delay and period are pre-set by the driver, then autorepeating
         * is handled by the driver itself and we don't do it in input.c.
         */
        if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD])
            input_enable_softrepeat(dev, 250, 33);

        if (!dev->getkeycode)
            dev->getkeycode = input_default_getkeycode;

        if (!dev->setkeycode)
            dev->setkeycode = input_default_setkeycode;

        error = device_add(&dev->dev);
        if (error)
            goto err_free_vals;

        path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
        pr_info("%s as %s\n",
            dev->name ? dev->name : "Unspecified device",
            path ? path : "N/A");
        kfree(path);

        error = mutex_lock_interruptible(&input_mutex);
        if (error)
            goto err_device_del;

        list_add_tail(&dev->node, &input_dev_list);

        list_for_each_entry(handler, &input_handler_list, node)
            input_attach_handler(dev, handler);

        input_wakeup_procfs_readers();

        mutex_unlock(&input_mutex);

        if (dev->devres_managed) {
            dev_dbg(dev->dev.parent, "%s: registering %s with devres.\n",
                __func__, dev_name(&dev->dev));
            devres_add(dev->dev.parent, devres);
        }
        return 0;

    err_device_del:
        device_del(&dev->dev);
    err_free_vals:
        kfree(dev->vals);
        dev->vals = NULL;
    err_devres_free:
        devres_free(devres);
        return error;
    }

    首先, 使用test_bit方法验证一下你想要注册的参数是否合法。
    然后驱动会使用__set_bit/__clear_bit等方法帮你做一些额外的设置。
    最后调用list_add_tail(&dev->node, &input_dev_list);将你要注册的input_dev结构体添加到input_dev_list这个链表中去。
    然后调用list_for_each_entry(handler, &input_handler_list, node)  ,input_attach_handler(dev, handler);
    意思就是说, 从input_handler_list链表中取出每一个handler结构体,分别调用input_attach_handler(dev, handler);
    这样,整个注册input_dev的流程就完成了。说白了,注册设备的流程就只做了两件重要的事, 一个是将这个设备对应的input_dev结构体放入内核的input_dev_list链表中,另一个就是,遍历内核中input_handler_list这个链表,对于每一个handler调用一次input_attach_handler。

    那么问题是,input_handler_list这个链表是谁设置的呢?
    继续看源码。
    我们在内核代码中搜索input_handler_list这个链表, 发现只有一个函数会使用list_add_tail(&handler->node, &input_handler_list);这个方式添加这个链表。这个函数恰好就是input.c中的input_register_handler(),源码如下:

    int input_register_handler(struct input_handler *handler)
    {
        struct input_dev *dev;
        int error;

        error = mutex_lock_interruptible(&input_mutex);
        if (error)
            return error;

        INIT_LIST_HEAD(&handler->h_list);

        list_add_tail(&handler->node, &input_handler_list);

        list_for_each_entry(dev, &input_dev_list, node)
            input_attach_handler(dev, handler);

        input_wakeup_procfs_readers();

        mutex_unlock(&input_mutex);
        return 0;
    }

    从源码中可以看出,这个函数注册的原理其实也是将某个input_handler结构体放入到input_handler_list这个链表中,更有趣的是,和之前input_dev结构体注册的时候一样,也会有一个遍历链表的操作,不过这次是反过来。就是在刚刚添加input_dev的链表input_dev_list上,一个一个取出input_dev结构体,然后调用input_attach_handler函数。
    为什么都去调用同一个函数呢?input_attach_handler这个函数到底有什么奥秘?继续看源码。

    static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
    {
        const struct input_device_id *id;
        int error;

        id = input_match_device(handler, dev);
        if (!id)
            return -ENODEV;

        error = handler->connect(handler, dev, id);
        if (error && error != -ENODEV)
            pr_err("failed to attach handler %s to device %s, error: %d\n",
                   handler->name, kobject_name(&dev->dev.kobj), error);

        return error;
    }

    函数中调用了input_match_device将input_dev结构体和input_handler结构体进行了比较,其实本质上就是看看input_handler中的id_table是否能匹配上。

    static const struct input_device_id *input_match_device(struct input_handler *handler,
							struct input_dev *dev)
    {
        const struct input_device_id *id;

        for (id = handler->id_table; id->flags || id->driver_info; id++) {
            if (input_match_device_id(dev, id) &&
                (!handler->match || handler->match(handler, dev))) {
                return id;
            }
        }

        return NULL;
    }

    如果匹配上了,就返回非NULL的id值。然后直接调用input_handler提供的connect函数。
    分析到这里可以看出,无论是注册input_handler还是注册input_dev, 都会遍历一次对方的链表,看看是否有能和自己匹配的"对象",如果能匹配,就会直接调用input_handler提供的connect函数进行所谓的连接。所以,如果要注册input_handler的话,必须要设置这个结构体的connect函数。
    
    那问题来了, 内核中有谁会注册input_handler这个东西呢?
    我们直接在内核的源码中搜索input_register_handler这个函数,可以很明显的看出有好几个文件都调用了这个函数注册了相应的input_handler.
    比如说,keyboard.c, mac_hid.c, mousedev.c, evdev.c等等。
    这些都是内核提供的纯软件上的驱动,代表着某一类的输入设备。其中evdev.c是极具代表性的event字符驱动。刚才说到,id_table可以意味着着个驱动能和什么input_dev匹配,我们看一下evdev.c的id_table就可以很明显的知道它支持什么设备了。

    static const struct input_device_id evdev_ids[] = {
        { .driver_info = 1 },	/* Matches all devices */
        { },			/* Terminating zero entry */
    };

    它的driver_info=1,从注释中可以看出,它支持所有的输入设备。
    也就是说,tsc2007_core.c中注册的input_dev结构体也会被这个驱动所支持,当tsc2007_core.c向内核调用input_register_device注册input_dev的时候,在遍历input_handler_list的时候就会匹配上,然后调用evdev.c中input_handler提供的connect函数进行连接。
    那这个连接又是做了什么神奇的操作呢?哈哈,还是那句话,看源码。

    static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
                 const struct input_device_id *id)
    {
        struct evdev *evdev;
        int minor;
        int dev_no;
        int error;

        minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);
        if (minor < 0) {
            error = minor;
            pr_err("failed to reserve new minor: %d\n", error);
            return error;
        }

        evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
        if (!evdev) {
            error = -ENOMEM;
            goto err_free_minor;
        }

        INIT_LIST_HEAD(&evdev->client_list);
        spin_lock_init(&evdev->client_lock);
        mutex_init(&evdev->mutex);
        init_waitqueue_head(&evdev->wait);
        evdev->exist = true;

        dev_no = minor;
        /* Normalize device number if it falls into legacy range */
        if (dev_no < EVDEV_MINOR_BASE + EVDEV_MINORS)
            dev_no -= EVDEV_MINOR_BASE;
        dev_set_name(&evdev->dev, "event%d", dev_no);

        evdev->handle.dev = input_get_device(dev);
        evdev->handle.name = dev_name(&evdev->dev);
        evdev->handle.handler = handler;
        evdev->handle.private = evdev;

        evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);
        evdev->dev.class = &input_class;
        evdev->dev.parent = &dev->dev;
        evdev->dev.release = evdev_free;
        device_initialize(&evdev->dev);

        error = input_register_handle(&evdev->handle);
        if (error)
            goto err_free_evdev;

        cdev_init(&evdev->cdev, &evdev_fops);

        error = cdev_device_add(&evdev->cdev, &evdev->dev);
        if (error)
            goto err_cleanup_evdev;

        return 0;

     err_cleanup_evdev:
        evdev_cleanup(evdev);
        input_unregister_handle(&evdev->handle);
     err_free_evdev:
        put_device(&evdev->dev);
     err_free_minor:
        input_free_minor(minor);
        return error;
    }

    源码中很重要的几个地方:
    evdev->handle.dev = input_get_device(dev);
    evdev->handle.handler = handler;
    error = input_register_handle(&evdev->handle);
    cdev_init(&evdev->cdev, &evdev_fops);
    error = cdev_device_add(&evdev->cdev, &evdev->dev);
    首先,这个函数会分配一个叫做input_handle的东西,注意,这个和上面提到的input_handler不一样,哪里不一样?对,单词不一样,认真看清楚。
    然后会设置这个input_handle结构体,分别设置它的dev成员和handler成员,是不是发现了什么?
    是的,其实真正起了连接作用的就是这个结构体了,它将之前提到的id_table匹配的两个结构体真正意义上的“连接”起来了,也就是说,通过这个结构体,就能从input_dev找到对应的input_handler,也能从input_handler找到input_dev结构体。
    然后呢,调用input_register_handle这个函数将这个input_handle结构体注册到内核中。
    是不是想问,这个注册又干了什么神奇的操作?哈哈,这个注册就真的只是将这个结构体放入到dev->h_list这个链表而已啦。

    int input_register_handle(struct input_handle *handle)
    {
        struct input_handler *handler = handle->handler;
        struct input_dev *dev = handle->dev;
        int error;

        /*
         * We take dev->mutex here to prevent race with
         * input_release_device().
         */
        error = mutex_lock_interruptible(&dev->mutex);
        if (error)
            return error;

        /*
         * Filters go to the head of the list, normal handlers
         * to the tail.
         */
        if (handler->filter)
            list_add_rcu(&handle->d_node, &dev->h_list);
        else
            list_add_tail_rcu(&handle->d_node, &dev->h_list);

        mutex_unlock(&dev->mutex);

        /*
         * Since we are supposed to be called from ->connect()
         * which is mutually exclusive with ->disconnect()
         * we can't be racing with input_unregister_handle()
         * and so separate lock is not needed here.
         */
        list_add_tail_rcu(&handle->h_node, &handler->h_list);

        if (handler->start)
            handler->start(handle);

        return 0;
    }

    注册了input_handle这个结构体后呢,connect函数还做了一件重要的事,那就是用cdev_init和cdev_device_add函数创建设备,而这个设备的名字,在connect函数的代码中可以看出,就是"event%d", 也就对应于/dev/input/eventX, 这个X就是设备的次设备号了。
    也就是说,tsc2007_core.c通过注册input_dev结构体,一系列反应之后,就会在/dev/input/目录下生成一个eventX的设备节点。

    我画了个巨丑的图。

输入子系统
巨丑的输入子系统

    那驱动是怎么工作的呢?排开tsc2007_core.c中IIC的部分来讲,tsc2007驱动应该是从IIC总线上获取触摸的数据,然后将这些数据上报给系统。在tsc2007_core.c中有这样的一部分代码:

    static irqreturn_t tsc2007_soft_irq(int irq, void *handle)
    {
        struct tsc2007 *ts = handle;
        struct input_dev *input = ts->input;
        struct ts_event tc;
        u32 rt;

        while (!ts->stopped && tsc2007_is_pen_down(ts)) {

            /* pen is down, continue with the measurement */

            mutex_lock(&ts->mlock);
            tsc2007_read_values(ts, &tc);
            mutex_unlock(&ts->mlock);

            rt = tsc2007_calculate_resistance(ts, &tc);

            if (!rt && !ts->get_pendown_state) {
                /*
                 * If pressure reported is 0 and we don't have
                 * callback to check pendown state, we have to
                 * assume that pen was lifted up.
                 */
                break;
            }

            if (rt <= ts->max_rt) {
                dev_dbg(&ts->client->dev,
                    "DOWN point(%4d,%4d), resistance (%4u)\n",
                    tc.x, tc.y, rt);

                rt = ts->max_rt - rt;

                input_report_key(input, BTN_TOUCH, 1);
                input_report_abs(input, ABS_X, tc.x);
                input_report_abs(input, ABS_Y, tc.y);
                input_report_abs(input, ABS_PRESSURE, rt);

                input_sync(input);

            } else {
                /*
                 * Sample found inconsistent by debouncing or pressure is
                 * beyond the maximum. Don't report it to user space,
                 * repeat at least once more the measurement.
                 */
                dev_dbg(&ts->client->dev, "ignored pressure %d\n", rt);
            }

            wait_event_timeout(ts->wait, ts->stopped, ts->poll_period);
        }

        dev_dbg(&ts->client->dev, "UP\n");

        input_report_key(input, BTN_TOUCH, 0);
        input_report_abs(input, ABS_PRESSURE, 0);
        input_sync(input);

        if (ts->clear_penirq)
            ts->clear_penirq();

        return IRQ_HANDLED;
    }

    很显然,这个是tsc2007的中断服务函数,当有触摸笔触摸屏幕的时候,tsc2007芯片触发中断信号,导致中断服务函数被调用,函数中,驱动会通过IIC总线读取芯片的触摸数据,当然包括X/Y/Z等数据,具体不做分析。然后会有一系列的计算和校验。最后,会调用input_report_key/input_report_abs/input_sync这些接口,将计算后的坐标值上报给系统。
    那上报这些接口是怎么将数据传出去的呢?
    这些接口都统一调用了input_event这个函数,input_event这个函数在input.c中实现,可以也是输入子系统中的核心的函数,源码如下:

    void input_event(struct input_dev *dev,
             unsigned int type, unsigned int code, int value)
    {
        unsigned long flags;

        if (is_event_supported(type, dev->evbit, EV_MAX)) {

            spin_lock_irqsave(&dev->event_lock, flags);
            input_handle_event(dev, type, code, value);
            spin_unlock_irqrestore(&dev->event_lock, flags);
        }
    }

    这里采用了自旋锁的同步机制,然后调用了input_handle_event这个函数。然后又调用了input_pass_values(dev, dev->vals, dev->num_vals);这个函数。
    然后再这个函数里通过handle = rcu_dereference(dev->grab);找到对应的input_handle结构体, 然后调用了count = input_to_handler(handle, vals, count);这个函数。在这个函数里通过struct input_handler *handler = handle->handler;的方式找到了input_handler这个结构体,其实就是evdev_handler这个东西。

    static unsigned int input_to_handler(struct input_handle *handle,
                struct input_value *vals, unsigned int count)
    {
        struct input_handler *handler = handle->handler;
        struct input_value *end = vals;
        struct input_value *v;

        if (handler->filter) {
            for (v = vals; v != vals + count; v++) {
                if (handler->filter(handle, v->type, v->code, v->value))
                    continue;
                if (end != v)
                    *end = *v;
                end++;
            }
            count = end - vals;
        }

        if (!count)
            return 0;

        if (handler->events)
            handler->events(handle, vals, count);
        else if (handler->event)
            for (v = vals; v != vals + count; v++)
                handler->event(handle, v->type, v->code, v->value);

        return count;
    }

    最后,函数会调用input_handler提供的events和event函数。就是evdev_handler结构体中的evdev_events和evdev_event函数。
    这里就仅仅拿evdev_events函数来简单的分析一下。

    static void evdev_events(struct input_handle *handle,
                 const struct input_value *vals, unsigned int count)
    {
        struct evdev *evdev = handle->private;
        struct evdev_client *client;
        ktime_t ev_time[EV_CLK_MAX];

        ev_time[EV_CLK_MONO] = ktime_get();
        ev_time[EV_CLK_REAL] = ktime_mono_to_real(ev_time[EV_CLK_MONO]);
        ev_time[EV_CLK_BOOT] = ktime_mono_to_any(ev_time[EV_CLK_MONO],
                             TK_OFFS_BOOT);

        rcu_read_lock();

        client = rcu_dereference(evdev->grab);

        if (client)
            evdev_pass_values(client, vals, count, ev_time);
        else
            list_for_each_entry_rcu(client, &evdev->client_list, node)
                evdev_pass_values(client, vals, count, ev_time);

        rcu_read_unlock();
    }

    当有函数调用这个函数的时候,它会找到一个evdev_client结构体,然后调用evdev_pass_values函数。
    其实这个evdev_client本质上就是一个环形缓存区,从它的结构体上就可以看出来了。

    struct evdev_client {
        unsigned int head;
        unsigned int tail;
        unsigned int packet_head; /* [future] position of the first element of next packet */
        spinlock_t buffer_lock; /* protects access to buffer, head and tail */
        struct fasync_struct *fasync;
        struct evdev *evdev;
        struct list_head node;
        unsigned int clk_type;
        bool revoked;
        unsigned long *evmasks[EV_CNT];
        unsigned int bufsize;
        struct input_event buffer[];
    };

    再看看evdev_pass_values的源码。

    static void evdev_pass_values(struct evdev_client *client,
                const struct input_value *vals, unsigned int count,
                ktime_t *ev_time)
    {
        struct evdev *evdev = client->evdev;
        const struct input_value *v;
        struct input_event event;
        bool wakeup = false;

        if (client->revoked)
            return;

        event.time = ktime_to_timeval(ev_time[client->clk_type]);

        /* Interrupts are disabled, just acquire the lock. */
        spin_lock(&client->buffer_lock);

        for (v = vals; v != vals + count; v++) {
            if (__evdev_is_filtered(client, v->type, v->code))
                continue;

            if (v->type == EV_SYN && v->code == SYN_REPORT) {
                /* drop empty SYN_REPORT */
                if (client->packet_head == client->head)
                    continue;

                wakeup = true;
            }

            event.type = v->type;
            event.code = v->code;
            event.value = v->value;
            __pass_event(client, &event);
        }

        spin_unlock(&client->buffer_lock);

        if (wakeup)
            wake_up_interruptible(&evdev->wait);
    }

    在evdev_pass_values函数里面,程序会使用ktime_to_timeval记录一个时间。然后使用__pass_event函数将数据放入环形缓冲区。
    最后使用wake_up_interruptible唤醒等待队列。
    
    总的来说,tsc2007读数据的过程就是由中断触发的,然后通过IIC总线将数据取出来,利用input_event函数将数据上报到内核的环形缓冲区中。
    其他应用程序会从这个环形缓冲区中将数据拿走。
    整一个输入子系统的框架大概就是这样的了,能力有限,如果有错的地方,可以指出哦。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值