Linux input 子系统

早在几年前,我第一次翻开 Linux 0.11 源代码完全注释那本书的时候,我就折服于这神奇的计算机世界,其实这里面的知识不是有多难,而是沉淀了太久,让上面的人琢磨不透,这也是封装的弊端,总得有人知道轮子是怎么造出来的。

后来涉及到驱动的架构的时候,读了挺多的 PCI 架构的一些知识,当时是用打印机把相关代码打印出来,然后用笔写一些注释,但是看懂已经很吃力了,还得发到网上分享,其实是一件非常累的事情,所以我很佩服在网络上能分享知识的人,因为都是无偿的,和出书不同。

这几天又看了一些输入系统的相关的文章,特在此处写一下关键的部分,不然像之前阅读的代码那时,很多东西其实没有记下来,有点后悔。

驱动的初始化

设备驱动的特点

这里都是说一些源代码,还有自己的一些理解,其实不同的子系统(总线,USB),都有一个显著的特点,就是有俩个全局变量,分别是一条链把所有的设备连接,另外一条就连接所有的驱动,然后设备和驱动之间如果匹配成功(所谓匹配成功,以 PCI设备和驱动为例,PCI 设备在Rom区会标识自己的ID,及生产厂商等信息,驱动加载的时候也会提供这些信息,然后不管挂载新驱动还是设备,都会遍历所有的设备(如果是挂载设备,就遍历驱动),然后比对他们的ID是否相同,如果相同就会挂钩,挂钩就是指一些指针存储着对方的信息,这时候我们就叫匹配成功),就会有相关的指针存储对方的信息。

重点: 1. 程序有办法知道目前挂载的所有设备还有驱动 2. 驱动和设备都有一个相同的id用来辨识

如果有读者看过文件系统和超级块(vfs 实现),其实也有全局变量把它们串联在一起,目的就是方便遍历

input 子系统

了解上面这个特点,现在看 input,一个全局的 List 头,用来挂载所有的 输入设备,还有一个挂载的就是驱动,上面一个宏定义了内核能支持的最大输入设备数,另外还有一个表,用来快速获取对应的驱动,等下们可以看到它的用处。( linux -2.6.39 )

#define INPUT_DEVICES    256

static LIST_HEAD(input_dev_list);
static LIST_HEAD(input_handler_list);

static struct input_handler *input_table[8];

下面来看input子系统的初始化

static const struct file_operations input_fops = {
    .owner = THIS_MODULE,
    .open = input_open_file,
    .llseek = noop_llseek,
};

static int __init input_init(void)
{
    int err;

    err = class_register(&input_class);
    if (err) {
        pr_err("unable to register input_dev class\n");
        return err;
    }

    err = input_proc_init();
    if (err)
        goto fail1;

    err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
    if (err) {
        pr_err("unable to register char major %d", INPUT_MAJOR);
        goto fail2;
    }

    return 0;

 fail2:    input_proc_exit();
 fail1:    class_unregister(&input_class);
    return err;
}
...
subsys_initcall(input_init);

关键在,注册一个字符设备,它的设备号就是 13(INPUT_MAJOR),设备号分为主次设备号的原因就是分类,其实本质就是一个数字,多嘴一句,如何通过设备号找到对应的设备很简单,就是一个映射的表,最简单的办法就是创建一个数组大小就是 设备号的最大值,里面存放设备的地址,但是这样太浪费了,所以我们就用数组加链表,本质其实还是一样的。

这里还很多谜团没有解开,比如 open 函数的作用(参考块设备的open函数也不难猜到),还有input设备/驱动的注册

input设备的注册

/drivers/input/input.c
int input_register_handler(struct input_handler *handler)
{
    struct input_dev *dev;
    int retval;

    retval = mutex_lock_interruptible(&input_mutex);

    INIT_LIST_HEAD(&handler->h_list);

    if (handler->fops != NULL) {
        ... // minor/32得到它的位置,便于快速访问驱动
        input_table[handler->minor >> 5] = handler;   
    }
    // 添加到驱动的链表尾端
    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();

 out:
    mutex_unlock(&input_mutex);
    return retval;
}

这个函数是内核暴露出来的驱动接口,供驱动模块调用,这里省略了错误的判断,为了更直观的显示它的作用。

有一点读者肯定很好奇,为什么 minor 要除以32,得到了它的位置,也就是设备号的低5位是被忽略了,也就是说 一个输入驱动(input handler),可以处理32个设备。这里注意的 input handler 和 input handle 是俩个结构,最开始我也弄错了

/include/linux/input.h
struct input_handler {
    void *private;

    void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
    bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
    bool (*match)(struct input_handler *handler, struct input_dev *dev);
    int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
    void (*disconnect)(struct input_handle *handle);
    void (*start)(struct input_handle *handle);

    const struct file_operations *fops;
    int minor;
    const char *name;

    const struct input_device_id *id_table;

    struct list_head    h_list;  // handle_list 连接它的handle会挂载于这里
    struct list_head    node;    // 挂载到全局驱动表上
};

/**
 * struct input_handle - links input device with an input handler
 * @private: handler-specific data
 * @open: counter showing whether the handle is 'open', i.e. should deliver
 *    events from its device
 * @name: name given to the handle by handler that created it
 * @dev: input device the handle is attached to
 * @handler: handler that works with the device through this handle
 * @d_node: used to put the handle on device's list of attached handles
 * @h_node: used to put the handle on handler's list of handles from which
 *    it gets events
 */
struct input_handle {

    void *private;

    int open;
    const char *name;

    struct input_dev *dev;
    struct input_handler *handler;

    struct list_head    d_node;
    struct list_head    h_node;
};

所有的设计都是为了方便操作,驱动里面的这些函数大部分会被底层的设备调用,我们先以具体的一个输入设备来举例子。

evdev 事件设备

drivers/input/evdev.c
static int __init evdev_init(void)
{
    return input_register_handler(&evdev_handler);
}

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 const struct input_device_id evdev_ids[] = {
    { .driver_info = 1 },    /* Matches all devices */
    { },            /* Terminating zero entry */
};

id_table 就是我们上面所说的和设备匹配的一个表,注释里说明了一个事实,就是所有的输入设备都会和这个evdev的驱动匹配

drivers/input/input.c
static const struct input_device_id *input_match_device(struct input_handler *handler,
                            struct input_dev *dev)
{
    const struct input_device_id *id;
    int i;
    // 注意到,evdev的id_table->driver_info = 1
    // 这个循环可以一直进行下去
    for (id = handler->id_table; id->flags || id->driver_info; id++) {

        ...    ....

        if (!handler->match || handler->match(handler, dev))
            return id;
    }

    return NULL;
}

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

所以最后会调用,evdev的 connect 函数

drivers/input/evdev.c
/*
 * Create new evdev device. Note that input core serializes calls
 * to connect and disconnect so we don't need to lock evdev_table here.
 */
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
             const struct input_device_id *id)
{
    struct evdev *evdev;
    int minor;
    int error;

    for (minor = 0; minor < EVDEV_MINORS; minor++)
        if (!evdev_table[minor])
            break;

    evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
    ...
    dev_set_name(&evdev->dev, "event%d", minor);
    evdev->exist = true;
    evdev->minor = minor;

    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, EVDEV_MINOR_BASE + 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);
    error = evdev_install_chrdev(evdev);
    error = device_add(&evdev->dev);
    ...
    return 0;
}

也是省略了部分出错的判断,我们来看看关键的几个地方

  • 首先,自己计算一个minor,在自己管理的32个minor的基础上

  • 分配一个新的,evdev结构,初始化(包括了设备初始化)

  • 注册一个新的 handle 注意不是handler

  • 存储 evdev,就是放在一个数组中,方便索引

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

    /*
     * 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 就是连接 handler 和 input device 一个媒介,这里就是添加其到相应的设备链表上。

换言之,只要是输入设备,一定会与 evdev 的驱动匹配,从而创建一个 evdev设备(名字 event%d(0~31)),其父设备就是 Input device,然后 evdev 的 minor 是计算出来的,只要数组里的元素没有赋值,那就是可用的

struct evdev {
    int open;
    int minor;
    struct input_handle handle;
    wait_queue_head_t wait;
    struct evdev_client __rcu *grab;
    struct list_head client_list;
    spinlock_t client_lock; /* protects client_list */
    struct mutex mutex;
    struct device dev;
    bool exist;
};

具体的 input device 例子 - usb kbd

USB 键盘下接 USB总线,这里我先忽略,上接 input 子系统,我们关注它的这一部分

drivers/hid/usbhid/usbkbd.c
struct usb_kbd {
    struct input_dev *dev;
    struct usb_device *usbdev;
    unsigned char old[8];
    struct urb *irq, *led;
    unsigned char newleds;
    char name[128];
    char phys[64];

    unsigned char *new;
    struct usb_ctrlrequest *cr;
    unsigned char *leds;
    dma_addr_t new_dma;
    dma_addr_t leds_dma;
};

......
static int usb_kbd_probe(struct usb_interface *iface,
             const struct usb_device_id *id)
{
    struct usb_device *dev = interface_to_usbdev(iface);
    struct usb_host_interface *interface;
    struct usb_endpoint_descriptor *endpoint;
    struct usb_kbd *kbd;
    struct input_dev *input_dev;
    int i, pipe, maxp;
    int error = -ENOMEM;

    interface = iface->cur_altsetting;


    kbd = kzalloc(sizeof(struct usb_kbd), GFP_KERNEL);
    input_dev = input_allocate_device();
    if (!kbd || !input_dev)
        goto fail1;

    if (usb_kbd_alloc_mem(dev, kbd))
        goto fail2;

    kbd->usbdev = dev;
    kbd->dev = input_dev;
    ...
    input_dev->name = kbd->name;
    input_dev->phys = kbd->phys;
    usb_to_input_id(dev, &input_dev->id);
    input_dev->dev.parent = &iface->dev;

    input_set_drvdata(input_dev, kbd);

    input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_LED) |
        BIT_MASK(EV_REP);
    input_dev->ledbit[0] = BIT_MASK(LED_NUML) | BIT_MASK(LED_CAPSL) |
        BIT_MASK(LED_SCROLLL) | BIT_MASK(LED_COMPOSE) |
        BIT_MASK(LED_KANA);

    for (i = 0; i < 255; i++)
        set_bit(usb_kbd_keycode[i], input_dev->keybit);
    clear_bit(0, input_dev->keybit);

    input_dev->event = usb_kbd_event;
    input_dev->open = usb_kbd_open;
    input_dev->close = usb_kbd_close;
    ...
    error = input_register_device(kbd->dev);
    ...
    return 0;
}

usb 特有的部分都可以忽略,关键在于分配了一个 input device 并注册了它,这就是关键了,联系上面,这个设备肯定会和 evdev 驱动匹配,(intput_register_device 同样会遍历驱动的链表,逐项比对id,最终连接双方),然后建立一个新的 evdev 设备,分配一个 Input handle 结构,连接驱动和现在分配的这个设备。

然后,当我们按下按键的时候,就会触发中断,最终来到这里。这里我们还可以发现,input device 也有自己open close 它们会在某个地方被调用,先卖个关子。

static void usb_kbd_irq(struct urb *urb)
{
    struct usb_kbd *kbd = urb->context;
    int i;

    switch (urb->status) {
    case 0:            /* success */
        break;
    case -ECONNRESET:    /* unlink */
    case -ENOENT:
    case -ESHUTDOWN:
        return;
    /* -EPIPE:  should clear the halt */
    default:        /* error */
        goto resubmit;
    }

    for (i = 0; i < 8; i++)
        input_report_key(kbd->dev, usb_kbd_keycode[i + 224], (kbd->new[0] >> i) & 1);
    ..
    ..
    ..
    input_sync(kbd->dev);    
    ....
}

有一些按键的继续按下,这个我们可以忽略,关键在于调用了一个 input_report_key

include/linux/input.h
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
    input_event(dev, EV_KEY, code, !!value);
}

drivers/input/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);
        add_input_randomness(type, code, value);
        input_handle_event(dev, type, code, value);
        spin_unlock_irqrestore(&dev->event_lock, flags);
    }
}

drivers/input/input.c
static void input_handle_event(struct input_dev *dev,
                   unsigned int type, unsigned int code, int value)
{
    int disposition = INPUT_IGNORE_EVENT;

    switch (type) {
    ....
    case EV_KEY:
        if (is_event_supported(code, dev->keybit, KEY_MAX) &&
            !!test_bit(code, dev->key) != value) {

            if (value != 2) {
                __change_bit(code, dev->key);
                if (value)
                    input_start_autorepeat(dev, code);
                else
                    input_stop_autorepeat(dev);
            }

            disposition = INPUT_PASS_TO_HANDLERS;
        }
        break;
    ....
    }
    if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
        dev->event(dev, type, code, value);

    if (disposition & INPUT_PASS_TO_HANDLERS)
        input_pass_event(dev, type, code, value);
}
对于 ENV_KEY 是可以传递给其他驱动的,就会来到 input_pass_event 了

这个函数读者肯定都可以猜到,就是遍历设备里的那个监听链表,我们在注册handle的时候放在了设备和驱动那儿,而且对于有 filter 的设备还是放在头部的,就是为了快速处理

/*
 * Pass event first through all filters and then, if event has not been
 * filtered out, through all open handles. This function is called with
 * dev->event_lock held and interrupts disabled.
 */
static void input_pass_event(struct input_dev *dev,
                 unsigned int type, unsigned int code, int value)
{
    struct input_handler *handler;
    struct input_handle *handle;

    rcu_read_lock();

    handle = rcu_dereference(dev->grab);
    if (handle)
        handle->handler->event(handle, type, code, value);
    else {
        bool filtered = false;

        list_for_each_entry_rcu(handle, &dev->h_list, d_node) {
            if (!handle->open)
                continue;
            handler = handle->handler;
            if (!handler->filter) {
                if (filtered)
                    break;
                handler->event(handle, type, code, value);
            } else if (handler->filter(handle, type, code, value))
                filtered = true;
        }
    }
    rcu_read_unlock();
}

调用 handler的 event 函数,最终就会来到, evdev_event()

static struct input_handler evdev_handler = {
    .event        = evdev_event,
    ....
}    

/*
 * Pass incoming event to all connected clients.
 */
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;
    struct input_event event;

    do_gettimeofday(&event.time);
    event.type = type;
    event.code = code;
    event.value = value;

    rcu_read_lock();

    client = rcu_dereference(evdev->grab);
    if (client)
        evdev_pass_event(client, &event);
    else
        list_for_each_entry_rcu(client, &evdev->client_list, node)
            evdev_pass_event(client, &event);

    rcu_read_unlock();

    wake_up_interruptible(&evdev->wait);
}

evdev 又会和监听它的上层挂钩,这里可以看出, evdev的存在就是监听所有的 Input device,每个设备会有自己的驱动,但是 evdev 就像是网卡的混淆模式,可以监听到所有的包,它可以监听到所有input device可传递的事件。

为什么要注册一个字符设备

最开始在子系统初始化的过程中,我们看到注册了一个主设备号是 INPUT_MAJOR 的一个字符设备,并给它提供了一个文件操作函数,到目前为止我们都没有解释它存在的意义,注意,我们之前说的注册输入设备,本质就是挂载到了那个全局的链表,如果存在和它匹配的驱动,当然 evdev 驱动必然匹配,然后还会创建一个新的 evdev 设备,但是注意一点,input device 设备并没有被打开。即在那时,它提供的 open 函数并没有被调用。

事实上,当驱动想要注册一个设备,不管是 Input,还是 evdev 设备,都一定调用一个接口
这个接口就是 device_add 它会根据设备的父设备在 sysfs 创建接口,根据设备的 class
和名字创建设备接口 

register_input_handler 
                                -> device_add
register_input_device

这里说的情况都是有 devtmpfs 机制内核,即每次注册一个设备都会
自动创建一个节点,关键是这个函数

int devtmpfs_create_node(struct device *dev)
{
    ....
    if (mode == 0)
        mode = 0600;
    if (is_blockdev(dev))
        mode |= S_IFBLK;
    else
        mode |= S_IFCHR; // 重点就在于此,这个设备是字符设备

    curr_cred = override_creds(&init_cred);

    err = vfs_path_lookup(dev_mnt->mnt_root, dev_mnt,
                  nodename, LOOKUP_PARENT, &nd);
    if (err == -ENOENT) {
        create_path(nodename);
        err = vfs_path_lookup(dev_mnt->mnt_root, dev_mnt,
                      nodename, LOOKUP_PARENT, &nd);
    }
    if (err)
        goto out;

    dentry = lookup_create(&nd, 0);
    if (!IS_ERR(dentry)) {
        err = vfs_mknod(nd.path.dentry->d_inode,
                dentry, mode, dev->devt);
        if (!err) {
            struct iattr newattrs;

            /* fixup possibly umasked mode */
            newattrs.ia_mode = mode;
            newattrs.ia_valid = ATTR_MODE;
            mutex_lock(&dentry->d_inode->i_mutex);
            notify_change(dentry, &newattrs);
            mutex_unlock(&dentry->d_inode->i_mutex);

            /* mark as kernel-created inode */
            dentry->d_inode->i_private = &dev_mnt;
        }
        dput(dentry);
    }
}

注意到,这些设备都被认为是字符设备,但是我们并没有调用 register_chardev ,读者估计已经猜到了,肯定就是为了和input初始化那里对接上,试想想,我们给设备认为是字符设备,最终内核就会根据它的 MAJOR 和 MINOR找到最合适的一个 fops即文件操作函数,这个关键就是 kobj_map 函数,其实就是一个映射表。总之,这些 input device,包括 evdev device,都是拥有相同的major,所以当用户打开这个设备的时候,就会调用到初始化的那个open函数。

static const struct file_operations input_fops = {
    .owner = THIS_MODULE,
    .open = input_open_file,
    .llseek = noop_llseek,
};

static int input_open_file(struct inode *inode, struct file *file)
{
    struct input_handler *handler;
    const struct file_operations *old_fops, *new_fops = NULL;
    int err;

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

    /* No load-on-demand here? */
    handler = input_table[iminor(inode) >> 5];
    if (handler)
        new_fops = fops_get(handler->fops);

    mutex_unlock(&input_mutex);

    /*
     * That's _really_ odd. Usually NULL ->open means "nothing special",
     * not "no device". Oh, well...
     */
    if (!new_fops || !new_fops->open) {
        fops_put(new_fops);
        err = -ENODEV;
        goto out;
    }

    old_fops = file->f_op;
    file->f_op = new_fops;

    err = new_fops->open(inode, file);
    if (err) {
        fops_put(file->f_op);
        file->f_op = fops_get(old_fops);
    }
    fops_put(old_fops);
out:
    return err;
}

当用户第一次调用 open("event0", RW) 的时候,因为它是字符设备,同时 major 又跟 最开始注册 input设备一样,所以内核认为input设备注册是 fops 就是能适用,所以调用。

上面的代码不难理解,就是调用了handler的open函数,对于 evdev,它的open即为

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,
    .llseek        = no_llseek,
};

这个函数由input设备的注册那个 open调用,紧接着它调用了自己的open
static int evdev_open(struct inode *inode, struct file *file)
{
    ...

    error = evdev_open_device(evdev);
    ...
}

这里最关键的一点,又调用了 input架构提供的open函数
static int evdev_open_device(struct evdev *evdev)
{
    int retval;

    retval = mutex_lock_interruptible(&evdev->mutex);
    if (retval)
        return retval;

    if (!evdev->exist)
        retval = -ENODEV;
    else if (!evdev->open++) {
        retval = input_open_device(&evdev->handle);
        if (retval)
            evdev->open--;
    }

    mutex_unlock(&evdev->mutex);
    return retval;
}

这里读者知道,刚才再说 usb 键盘驱动那里我们卖了一关子,usb 设备对 input 设备注册了自己 open 函数,但是没说哪里调用。

/**
 * input_open_device - open input device
 * @handle: handle through which device is being accessed
 *
 * This function should be called by input handlers when they
 * want to start receive events from given input device.
 */
int input_open_device(struct input_handle *handle)
{
    struct input_dev *dev = handle->dev;
    int retval;
   ...
    handle->open++;

    if (!dev->users++ && dev->open)
        retval = dev->open(dev);
     // 就在这里调用了
   ....
 out:
    mutex_unlock(&dev->mutex);
    return retval;
}

这里设备的 open 函数,以 刚才 usb 键盘为例,就会来到 usb_kbd_open 了,这里与 USB 子系统有关,就不继续分析了。

Summary

最后我们总结一下,最开始 input 设备注册的函数,只是一个分发器,为的就是调用它们的handler->open ,因为每个handler可能有自己自带的设备,注意,以 evdev 为例,它注册了一个新的设备号拥有不同的 minor, 名称为 event%d,但是它的父设备,又是与这个 handler 匹配成功的 input device,这里有点绕,但是必须理解清楚。

evdev 的handler 最终调用了 input device 的 open 函数,其实内核的代码注释里面也说了,这里直接引用吧。

"

/**
 * input_open_device - open input device
 * @handle: handle through which device is being accessed
 *
 * This function should be called by input handlers when they
 * want to start receive events from given input device.
 */

"

这是一种懒惰的思想,只有当驱动想要使用设备,也就是上层需要读取设备信息的时候,我们才打开设备,目的就是为了节省系统资源。

这篇文章得理解一下几点

  • 为什么要注册那个字符设备

  • 为什么字符设备的open函数可以最终到达目标输入设备的open

  • 驱动的特点,即俩个链表,还有匹配的特点

  • 设备信息如何传递到监听它的 handler(利用设备的 h_list)

  • evdev 可以自由分配自己的 32 个minor

Last updated