1. 程式人生 > >Linux驅動之輸入子系統簡析

Linux驅動之輸入子系統簡析

ans 沒有 procfs 通過 sel spa 函數 minor ifdef

輸入子系統由驅動層、輸入子系統核心、事件處理層三部分組成。一個輸入事件,如鼠標移動、鍵盤按下等通過Driver->Inputcore->Event handler->userspace的順序到達用戶控件的應用程序。

技術分享圖片

                系統框圖

假設打開一個字符設備驅動程序/dev/event0,event代表的是輸入子系統的設備文件,當應用程序調用C庫的open函數後,open函數會進入系統調用,最後定位到drivers\input\input.c文件下(這個文件就是核心層)的。這個函數的功能主要是根據設備的次設備號找到新的fops結構,然後切換到新的fops結構,然後調用它的打開函數。

static int input_open_file(struct inode *inode, struct file *file)
{
    struct input_handler *handler = input_table[iminor(inode) >> 5];//根據此設備號找到在input_table表中找到handler結構體
    const struct file_operations *old_fops, *new_fops = NULL;
    int err;

    /* No load-on-demand here? */
    if (!handler || !(new_fops = fops_get(handler->fops)))//
判斷handler結構體是否存在,存在的話將裏面的fops變量賦給new_fops return -ENODEV; /* * That‘s _really_ odd. Usually NULL ->open means "nothing special", * not "no device". Oh, well... */ if (!new_fops->open) { fops_put(new_fops); return -ENODEV; } old_fops = file->f_op; file
->f_op = new_fops;//切換f_op變量,以後調用諸如read、write等系統調用時會進入到new_fops的read、write函數 err = new_fops->open(inode, file);//調用new_fops的open函數 if (err) { fops_put(file->f_op); file->f_op = fops_get(old_fops); } fops_put(old_fops);//釋放掉老的fops結構 return err; }

接著先來看到input_table表的建立,可以看到它是一個靜態變量,在本文件(drivers\input\input.c)中搜索它,可以看到它位於input_register_handler函數,這是一個全局的函數,可以供外部的文件調用,這個函數的主要功能是註冊一個handler結構體,這個結構體中存在minor這個設備的次設備號,這個結構所在的函數對應的其實就是上述的事件層。

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

    INIT_LIST_HEAD(&handler->h_list);//初始化handler的h_list結構體,這是一個雙向鏈表

    if (handler->fops != NULL) {
        if (input_table[handler->minor >> 5])//檢查是否已經存在這個次設備號的handler結構
            return -EBUSY;

        input_table[handler->minor >> 5] = handler;//將handler結構次設備號放入input_table表
    }

    list_add_tail(&handler->node, &input_handler_list);//將handler結構根據node成員放入input_handler_list鏈表

    list_for_each_entry(dev, &input_dev_list, node)//根據node這個成員在input_dev_list鏈表中循環查找dev結構
        input_attach_handler(dev, handler);//對於每一個dev結構調用input_attach_handler函數

    input_wakeup_procfs_readers();//將這個設備信息寫入proc文件系統
    return 0;
}

接著搜索input_register_handler,抽取drivers\input\evdev.c這個文件,可以看到在這個模塊的入口函數調用了註冊函數

static int __init evdev_init(void)
{
    return input_register_handler(&evdev_handler);
}

接著看到evdev_handler這個結構體,在這個結構體裏面找到了evdev_fops這個結構

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

接著看到evdev_fops結構體,可以看到應用層調用的read、write等函數在這裏被定義

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_register_handler函數,在裏面看到如下語句,這句語句的作用是將事件層與驅動層聯系起來。

list_for_each_entry(dev, &input_dev_list, node)//根據node這個成員在input_dev_list鏈表中循環查找dev結構
        input_attach_handler(dev, handler);//對於每一個dev結構調用input_attach_handler函數

這裏可以看到一個新的結構體dev,先看一下dev結構體,它的原型為input_dev,跟抽取drivers\input\evdev.c這個文件一樣,搜索input_dev這個結構體,先列出input_dev這個結構體

struct input_dev {

    void *private;

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

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

    unsigned int keycodemax;
    unsigned int keycodesize;
    void *keycode;
    int (*setkeycode)(struct input_dev *dev, int scancode, int keycode);
    int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode);

    struct ff_device *ff;

    unsigned int repeat_key;
    struct timer_list timer;

    int state;

    int sync;

    int abs[ABS_MAX + 1];
    int rep[REP_MAX + 1];

    unsigned long key[NBITS(KEY_MAX)];
    unsigned long led[NBITS(LED_MAX)];
    unsigned long snd[NBITS(SND_MAX)];
    unsigned long sw[NBITS(SW_MAX)];

    int absmax[ABS_MAX + 1];
    int absmin[ABS_MAX + 1];
    int absfuzz[ABS_MAX + 1];
    int absflat[ABS_MAX + 1];

    int (*open)(struct input_dev *dev);
    void (*close)(struct input_dev *dev);
    int (*flush)(struct input_dev *dev, struct file *file);
    int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);

    struct input_handle *grab;

    struct mutex mutex;    /* serializes open and close operations */
    unsigned int users;

    struct class_device cdev;
    union {            /* temporarily so while we switching to struct device */
        struct device *parent;
    } dev;

    struct list_head    h_list;
    struct list_head    node;
};

接著看到drivers\input\tablet\kbtab.c這個文件,這個文件代表的就是設備驅動層,簡單分析一下,可以看到它也是一個內核的模塊,可以動態加載,一旦加載後,它會調用kbtab_init函數,最終會調用到kbtab_probe這個函數,可以看到最終又定位到了input_register_device這個註冊設備的函數,它位於核心層,即drivers\input\input.c文件下。

static int kbtab_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
    ...
    ...
    input_dev = input_allocate_device();//分配一個input_dev 結構體
    if (!kbtab || !input_dev)
        goto fail1;

    ...
    ...

    input_dev->name = "KB Gear Tablet";//初始化input_dev 結構體
    input_dev->phys = kbtab->phys;
    usb_to_input_id(dev, &input_dev->id);
    input_dev->dev.parent = &intf->dev;

    input_set_drvdata(input_dev, kbtab);

    input_dev->open = kbtab_open;
    input_dev->close = kbtab_close;

    input_dev->evbit[0] |= BIT(EV_KEY) | BIT(EV_ABS) | BIT(EV_MSC);
    input_dev->keybit[LONG(BTN_LEFT)] |= BIT(BTN_LEFT) | BIT(BTN_RIGHT) | BIT(BTN_MIDDLE);
    input_dev->keybit[LONG(BTN_DIGI)] |= BIT(BTN_TOOL_PEN) | BIT(BTN_TOUCH);
    input_dev->mscbit[0] |= BIT(MSC_SERIAL);
    input_set_abs_params(input_dev, ABS_X, 0, 0x2000, 4, 0);
    input_set_abs_params(input_dev, ABS_Y, 0, 0x1750, 4, 0);
    input_set_abs_params(input_dev, ABS_PRESSURE, 0, 0xff, 0, 0);

    ...
    ...

    error = input_register_device(kbtab->dev);//註冊input_dev結構體
    ...
    ...
}

接著看到input_register_device這個函數,它根input_register_handler相對應,一個前一個註冊設備驅動層,後一個註冊事件層。列出input_register_device函數,它同樣位於drivers\input\input.c文件中。

int input_register_device(struct input_dev *dev)
{
    static atomic_t input_no = ATOMIC_INIT(0);
    struct input_handler *handler;
    const char *path;
    int error;

    set_bit(EV_SYN, dev->evbit);//設置同步事件

    /*
     * 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.
     */

    init_timer(&dev->timer);//初始化一個定時器
    if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {//按鍵是否需要重復,如果需要設置重復函數與重復時間
        dev->timer.data = (long) dev;
        dev->timer.function = input_repeat_key;
        dev->rep[REP_DELAY] = 250;
        dev->rep[REP_PERIOD] = 33;
    }

    if (!dev->getkeycode)
        dev->getkeycode = input_default_getkeycode;//獲得按鍵值默認函數

    if (!dev->setkeycode)
        dev->setkeycode = input_default_setkeycode;//設置按鍵值默認函數

    list_add_tail(&dev->node, &input_dev_list);//將dev->node放入input_dev_list鏈表

    snprintf(dev->cdev.class_id, sizeof(dev->cdev.class_id),
         "input%ld", (unsigned long) atomic_inc_return(&input_no) - 1);

    if (!dev->cdev.dev)
        dev->cdev.dev = dev->dev.parent;

    error = class_device_add(&dev->cdev);
    if (error)
        return error;

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

    list_for_each_entry(handler, &input_handler_list, node)//根據node這個成員在input_handler_list鏈表中循環查找handler結構
        input_attach_handler(dev, handler);//對於每一個handler結構調用input_attach_handler函數

    input_wakeup_procfs_readers();//將這個設備信息寫入proc文件系統

    return 0;
}

可以看到它同樣也調用了input_attach_handler函數,將設備驅動層與事件層聯系起來。這個函數也位於drivers\input\input.c文件中。它的主要功能是

1、根據handler->id_table的值匹配dev,找到id

2、調用調用handler->connect進行匹配

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

    if (handler->blacklist && input_match_device(handler->blacklist, dev))
        return -ENODEV;

    id = input_match_device(handler->id_table, dev);//根據handler->id_table的值匹配dev,找到id
    if (!id)
        return -ENODEV;

    error = handler->connect(handler, dev, id);//調用handler->connect進行匹配
    if (error && error != -ENODEV)
        printk(KERN_ERR
            "input: failed to attach handler %s to device %s, "
            "error: %d\n",
            handler->name, kobject_name(&dev->cdev.kobj), error);

    return error;
}

接著看到input_match_device函數

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++) {//循環查找支持的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;

再看到handler->connect函數,這裏選取的是evdev_handler ->evdev_connect函數,這個函數的主要作用就是將handle、handler、evdev三者相互匹配起來

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

    for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++);//取得次設備號,如果還沒有利用則evdev_table為空
    if (minor == EVDEV_MINORS) {
        printk(KERN_ERR "evdev: no more free evdev devices\n");//沒有剩余的空間可以用了
        return -ENFILE;
    }

    evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);//分配一個evdev
    if (!evdev)
        return -ENOMEM;

    INIT_LIST_HEAD(&evdev->client_list);
    init_waitqueue_head(&evdev->wait);

    evdev->exist = 1;                               //evdev初始化
    evdev->minor = minor;
    evdev->handle.dev = dev;                  //初始化evdev->handle.dev 
    evdev->handle.name = evdev->name;
    evdev->handle.handler = handler;       //初始化evdev->handle.handler
    evdev->handle.private = evdev;
    sprintf(evdev->name, "event%d", minor);//打印次設備號,每次註冊新的設備驅動都會打印

    evdev_table[minor] = evdev;//將分配的evdev放入evdev_table[minor]

    devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),

    cdev = class_device_create(&input_class, &dev->cdev, devt,
                   dev->cdev.dev, evdev->name);//創建一個字符設備節點
    if (IS_ERR(cdev)) {
        error = PTR_ERR(cdev);
        goto err_free_evdev;
    }

    /* temporary symlink to keep userspace happy */
    error = sysfs_create_link(&input_class.subsys.kobj,
                  &cdev->kobj, evdev->name);
    if (error)
        goto err_cdev_destroy;

    /*
      llist_add_tail(&handle->d_node, &handle->dev->h_list);//將&handle->d_node  放入&handle->dev->h_list鏈表
      list_add_tail(&handle->h_node, &handler->h_list);        //將&handle->h_node  放入 &handler->h_list鏈表?
    */
    error = input_register_handle(&evdev->handle);//註冊evdev->handle   
    if (error)
        goto err_remove_link;

    return 0;

 err_remove_link:
    sysfs_remove_link(&input_class.subsys.kobj, evdev->name);
 err_cdev_destroy:
    class_device_destroy(&input_class, devt);
 err_free_evdev:
    kfree(evdev);
    evdev_table[minor] = NULL;
    return error;
}

再回過頭來看一下應用層是怎麽讀取按鍵值得:應用層調用C庫的read函數,通過前面的分析可以知道,最終會通過系統調用會定位到內核的evdev_handler ->fops

->evdev_read,下面看到evdev_read函數,它位於事件層,這個函數根據讀取方式的不同采取不同的方式,如果是阻塞方式打開的話最終會當前進程放入等待隊列,一直等到有數據才將進程喚醒。

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))//如果采用非阻塞方式讀取,並且每天數據直接返回
        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;
}

接下來的問題就是誰將進程喚醒,我們直接看到設備驅動層,即drivers\input\tablet\kbtab.c,在這個文件中有一個kbtab_irq函數,它是一個中斷處理函數,它位於設備驅動層,負責將中斷過來的按鍵數據上報調用的是input_report_key函數,input_report_key函數最終調用的是input_event函數,他們全部都屬於核心層。接著看一下input_event的核心代碼

list_for_each_entry(handle, &dev->h_list, d_node)//根據dev設備驅動層的h_list找出handle結構體
            if (handle->open)
                handle->handler->event(handle, type, code, value);//調用事件層的handle->handler->event進行處理

再回過頭看事件層的event,即evdev_event函數,可以看到在這個函數裏將按鍵的相關的值取出後,最終進程的喚醒函數在這裏調用wake_up_interruptible。

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
        list_for_each_entry(client, &evdev->client_list, node) {

            do_gettimeofday(&client->buffer[client->head].time);//時間              4字節
            client->buffer[client->head].type = type;                  //按鍵類型  1字節
            client->buffer[client->head].code = code;                 //按鍵碼        2字節
            client->buffer[client->head].value = value;               //按鍵值        1字節
            client->head = (client->head + 1) & (EVDEV_BUFFER_SIZE - 1);

            kill_fasync(&client->fasync, SIGIO, POLL_IN);//異步通知
        }

    wake_up_interruptible(&evdev->wait);//喚醒進程
}

總結一下整個輸入子系統的調用過程:

app_open->input_open_file->evdev_open

應用層 核心層 事件層

app_read->evdev_read->kbtab_irq->input_report_key->input_event->evdev_event->evdev_read

應用層 事件層 設備層 核心層 核心層 事件層 事件層

如果要自己添加一個輸入子系統的設備,只需要添加設備層的文件即可。

1、在裏面添加設備層input_dev結構並初始化

2、編寫中斷處理程序

Linux驅動之輸入子系統簡析