4. Linux - 輸入子系統(tǒng)框架詳解

輸入子系統(tǒng)概述

?????? Linux內(nèi)核為了能夠處理各種不同類型的輸入設(shè)備荔棉,比如 觸摸屏 闹炉,鼠標 , 鍵盤 , 操縱桿 ,設(shè)計并實現(xiàn)了為驅(qū)動層程序的實現(xiàn)提供統(tǒng)一接口函數(shù)江耀;為上層應(yīng)用提供試圖統(tǒng)一的抽象層 , 即是Linux 輸入子系統(tǒng) 剩胁。


輸入子系統(tǒng)框架

?????? 從上圖輸入子系統(tǒng)的框架圖,可以看出祥国,輸入子系統(tǒng)由Input driver(驅(qū)動層)昵观、Input core(輸入子系統(tǒng)核心)、Event handler(事件處理層)三部分組成舌稀。一個輸入事件啊犬,如鼠標移動、鍵盤按下等通過Input driver -> Input core -> Event handler -> userspace的順序到達用戶空間的應(yīng)用程序壁查。

  • Input driver :主要實現(xiàn)對硬件設(shè)備的讀寫訪問觉至,中斷設(shè)置,并把硬件產(chǎn)生的事件轉(zhuǎn)換為核心層定義的規(guī)范提交給事件處理層睡腿。
  • Input core :承上啟下语御。為設(shè)備驅(qū)動層提供了規(guī)范和接口;通知事件處理層對事件進行處理席怪;
  • Event handler :提供用戶編程的接口(設(shè)備節(jié)點)应闯,并處理驅(qū)動層提交的數(shù)據(jù)處理。

輸入子系統(tǒng)框架分析

?????? 輸入子系統(tǒng)是所有I/O設(shè)備驅(qū)動的中間層挂捻,為上層提供了一個統(tǒng)一的界面碉纺。例如,在終端系統(tǒng)中,我們不需要去管有多少個鍵盤骨田,多少個鼠標耿导。它只要從輸入子系統(tǒng)中去取對應(yīng)的事件(按鍵,鼠標移位等)就可以了态贤。
?????? 上面舱呻,我們從功能級別,描述了輸入子系統(tǒng)每一層抵卫,做了些什么狮荔。接下來,我們將從代碼級別的角度出發(fā)介粘,分析系統(tǒng)核心層、事件處理層晚树、設(shè)備驅(qū)動層姻采。

1.系統(tǒng)核心層(Input core)
  • 申請主設(shè)備號;
  • 提供input_register_device跟input_register_handler函數(shù)分別用于注冊device跟handler;
  • 提供input_register_handle函數(shù)用于注冊一個事件處理,代表一個成功配對的input_dev和input_handler;
2.事件處理層(Event handler)
  • 不涉及硬件方面的具體操作爵憎,handler層是純軟件層慨亲,包含不同的解決方案,如鍵盤宝鼓,鼠標刑棵,游戲手柄等;
  • 對于不同的解決方案愚铡,都包含一個名為input_handler的結(jié)構(gòu)體蛉签,該結(jié)構(gòu)體內(nèi)含的主要成員如下:
成員 功能
.id_table 一個存放該handler所支持的設(shè)備id的表(其實內(nèi)部存放的是EV_xxx事件,用于判斷device是否支持該事件)
.fops 該handler的file_operation
.connect 連接該handler跟所支持device的函數(shù)
.disconnect 斷開該連接
.event 事件處理函數(shù),讓device調(diào)用
h_list 是一個鏈表沥寥,該鏈表保存著該handler到所支持的所有device的中間站:handle結(jié)構(gòu)體的指針
3.設(shè)備驅(qū)動層(Input driver)
  • device是純硬件操作層碍舍,包含不同的硬件接口處理,如gpio等
  • 對于每種不同的具體硬件操作邑雅,都對應(yīng)著不同的input_dev結(jié)構(gòu)體
  • 該結(jié)構(gòu)體內(nèi)部也包含著一個h_list片橡,指向handle
4.兩條鏈表一個結(jié)構(gòu)
  • 對于handler和device,分別用鏈表input_handler_list和input_device_list進行維護淮野,
    當(dāng)handler或者device增加或減少的時候捧书,分別往這兩鏈表增加或刪除節(jié)點,這兩條都是全局鏈表骤星。
  • input_handle 結(jié)構(gòu)體代表一個成功配對的input_dev和input_handler经瓷。input_hande 沒有一個全局的鏈表,它注冊的時候?qū)⒆约悍謩e掛在了input_device_list和 input_handler_list的h_list上了妈踊;同時了嚎,input_handle的成員.*dev,關(guān)聯(lián)到input_dev結(jié)構(gòu),.*handler關(guān)聯(lián)到input_handler結(jié)構(gòu) 歪泳。從此萝勤,建立好了三者的鐵三角關(guān)系,通過任何一方呐伞,都可以找到彼此敌卓。

?????? 總結(jié)一下,輸入子系統(tǒng)作為一個模塊存在伶氢;向上,為用戶層提供調(diào)用接口趟径;向下,為驅(qū)動層程序提供統(tǒng)一的注冊接口。這樣,就能夠使輸入設(shè)備的事件通過輸入子系統(tǒng)發(fā)送給用戶層應(yīng)用程序,用戶層應(yīng)用程序也可以通過輸入子系統(tǒng)通知驅(qū)動程序完成某項功能癣防。(Linux中在用戶空間將所有的設(shè)備都當(dāng)初文件來處理蜗巧,由于在一般的驅(qū)動程序中都有提供fops接口,以及在/dev下生成相應(yīng)的設(shè)備文件nod蕾盯,這些操作在輸入子系統(tǒng)中由事件處理層完成)


輸入子系統(tǒng)分析

Input core 作為輸入子系統(tǒng)的核心幕屹,我們以他為入口,進行分析级遭。內(nèi)核所有的輸入子系統(tǒng)核心代碼在driver/input下望拖;
vim driver/input/input.c (核心層)
找到入口函數(shù):

subsys_initcall(input_init); 

input_init:分析:

static int __init input_init(void)
{
    int err;

    err = class_register(&input_class); //在/sys/class下創(chuàng)建邏輯(input)類
    if (err) {
        pr_err("unable to register input_dev class\n");
        return err;
    }

    err = input_proc_init();//在/proc下面建立相關(guān)的文件
    if (err)
        goto fail1;
        
    /*申請一個字符設(shè)備,主設(shè)備號13*/
    err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
                     INPUT_MAX_CHAR_DEVICES, "input");
    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;
}

在入口函數(shù)里面創(chuàng)建了一個input_class類,其實就在/sys/class下創(chuàng)建了一個目錄input挫鸽。另外在/proc創(chuàng)建了入口項,這樣就可以/proc目錄看到input的信息,然后就注冊設(shè)備,可以看出輸入子系統(tǒng)的主設(shè)備號是13,在這里并沒有生成設(shè)備文件.只是在/dev/目錄下創(chuàng)建了input目錄,以后所有注冊進系統(tǒng)的輸入設(shè)備文件都放在這個目錄下说敏。
到這里,輸入子系統(tǒng)的核心初始化也就完成了丢郊,完成了盔沫?你可能會有這樣的疑問:
①為什么這里代碼只創(chuàng)建邏輯(input)類,沒有使用class_device_create()函數(shù)在類下面注冊驅(qū)動設(shè)備蚂夕?
②為什么這里的代碼只是申請了一個主設(shè)備號INPUT_MAJOR的字符設(shè)備迅诬,沒有進行設(shè)備的注冊?
核心層作為一個中轉(zhuǎn)層存在婿牍,不涉及具體硬件設(shè)備的注冊侈贷,倒是更符合他存在的邏輯。那么猜測下等脂,設(shè)備的注冊到底會在Input driver 還是Event hanlder呢俏蛮?往下分析...

輸入核心為驅(qū)動層提供統(tǒng)一的接口,涉及的結(jié)構(gòu)和方法如下:

實現(xiàn)設(shè)備驅(qū)動核心工作是:向系統(tǒng)報告按鍵上遥、觸摸屏等輸入事件(event搏屑,通過input_event結(jié)構(gòu)描述),不再需要關(guān)心文件操作接口粉楚。驅(qū)動報告事件經(jīng)過inputCore和Eventhandler到達用戶空間辣恋。

  • input_dev結(jié)構(gòu)

   struct input_dev {  
        const char *name;  //提供給用戶的輸入設(shè)備的名稱  
        const char *phys;  //提供給編程者的設(shè)備節(jié)點的名稱  
        const char *uniq;  //指定唯一的ID號亮垫,就像MAC地址一樣
        struct input_id id;  //輸入設(shè)備標識ID,用于和事件處理層進行匹配
        unsigned long evbit[NBITS(EV_MAX)];   // 記錄設(shè)備支持的事件類型 
        unsigned long keybit[NBITS(KEY_MAX)]; // 記錄設(shè)備支持的按鍵類型   
        unsigned long relbit[NBITS(REL_MAX)]; // 表示能產(chǎn)生哪些相對位移事件, x,y,滾輪  
        unsigned long absbit[NBITS(ABS_MAX)]; // 表示能產(chǎn)生哪些絕對位移事件, x,y  
        unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];  
        unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];  
        unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];  
        unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];  
        unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
        unsigned int hint_events_per_packet;
        unsigned int keycodemax;
        unsigned int keycodesize;
        void *keycode;
        ...  
   }
功能 接口
分配輸入設(shè)備函數(shù) struct input_dev *input_allocate_device(void)
注冊輸入設(shè)備函數(shù) int input_register_device(struct input_dev *dev)
注銷輸入設(shè)備函數(shù) void input_unregister_device(struct input_dev *dev)
事件支持(初始化) set_bit()
告訴input輸入子系統(tǒng)支持哪些事件伟骨,哪些按鍵饮潦,例如:
set_bit(EV_KEY,button_dev.evbit) (其中button_dev是struct input_dev類型)
struct input_dev中有兩個成員為:
evbit:
事件類型(EV_RST,EV_REL,EV_MSC,EV_KEY,EV_ABS,EV_REP等)
keybit:
按鍵類型(當(dāng)事件類型為EV_KEY時包括BTN_LEFT,BTN_0,BTN_1,BTN_MIDDLE等)
報告事件 void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
在發(fā)生輸入事件時,向子系統(tǒng)報告事件携狭。
參數(shù)說明:
input_dev *dev :要上報哪個input_dev驅(qū)動設(shè)備的事件继蜡;
type : 要上報哪類事件, 比如按鍵事件,則填入: EV_KEY;
code: 對應(yīng)的事件里支持的哪個變量逛腿,比如按下按鍵L則填入: KEY_L稀并;
value:對應(yīng)的變量里的數(shù)值,比如松開按鍵則填入1,松開按鍵則填入0;
報告結(jié)束 input_sync()
同步用于告訴input core子系統(tǒng)報告結(jié)束

所以单默,對于Input driver的工作主要還是分配碘举、設(shè)置、注冊一個結(jié)構(gòu)體雕凹。input_register_device()用于注冊一個輸入設(shè)備殴俱。那么注冊過程是怎樣的呢?這是一個重點枚抵,在下面的代碼中進行注釋分析:

int input_register_device(struct input_dev *dev)
{
    struct input_devres *devres = NULL;
    /* 輸入事件的處理接口指針,用于和設(shè)備的事件類型進行匹配 */  
    struct input_handler *handler;
    unsigned int packet_size;
    const char *path;
    int error;

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

        devres->input = dev;
    }

    /* 默認所有的輸入設(shè)備都支持EV_SYN同步事件 */ 
    /* 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]) {
        dev->timer.data = (long) dev;
        dev->timer.function = input_repeat_key;
        dev->rep[REP_DELAY] = 250;
        dev->rep[REP_PERIOD] = 33;
    }

    /*沒有定義設(shè)備的getkeycode函數(shù)明场,則使用默認的獲取鍵值函數(shù)*/
    if (!dev->getkeycode)
        dev->getkeycode = input_default_getkeycode;

    /*沒有定義設(shè)備的setkeycode函數(shù)汽摹,則使用默認的設(shè)定鍵值函數(shù)*/
    if (!dev->setkeycode)
        dev->setkeycode = input_default_setkeycode;
    
    /*添加設(shè)備*/
    error = device_add(&dev->dev);
    if (error)
        goto err_free_vals;

    /* 獲取并打印設(shè)備的絕對路徑名稱 */  
    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;

    /* `重要`:把設(shè)備掛到全局的input子系統(tǒng)設(shè)備鏈表input_dev_list上 */  
    list_add_tail(&dev->node, &input_dev_list);

    /* 核心重點,input設(shè)備在增加到input_dev_list鏈表上之后苦锨,會查找 
    * input_handler_list事件處理鏈表上的handler進行匹配逼泣,這里的匹配 
    * 方式與設(shè)備模型的device和driver匹配過程很相似,所有的input devicel
    * 都掛在input_dev_list上舟舒,所有類型的事件都掛在input_handler_list 
    * 上拉庶,進行“匹配相親”*/  
    list_for_each_entry(handler, &input_handler_list, node)
        input_attach_handler(dev, handler);/*遍歷input_handler_list,試圖與每一個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;
}

上面的代碼主要的功能有以下幾個功能秃励,也是設(shè)備驅(qū)動注冊為輸入設(shè)備委托內(nèi)核做的事情:

  • 添加設(shè)備氓润;
  • 把輸入設(shè)備掛到輸入設(shè)備鏈表input_dev_list中慰于;
  • 遍歷input_handler_list鏈表,查找并匹配輸入設(shè)備對應(yīng)的事件處理層,如果匹配上了觉既,就調(diào)用handlerconnnect函數(shù)進行連接。設(shè)備就是在此時注冊的阳仔,下面分析handler就清晰了掸冤。
    (input_attach_handler放到分析handler時再做講解,更容易理解食呻。)

輸入核心為事件管理層提供主要接口:

事件處理層文件主要是用來支持輸入設(shè)備并與用戶空間交互流炕,這部分代碼一般不需要我們自己去編寫澎现,因為Linux內(nèi)核已經(jīng)自帶有一些事件處理器,可以支持大部分輸入設(shè)備每辟,比如Evdev.c剑辫、mousedev.c、joydev.c等影兽。

功能 接口
注冊一個事件處理器 input_register_handler
向內(nèi)核注冊一個handle結(jié)構(gòu) input_register_handle

對于Event handler揭斧,就是根據(jù)事件注冊一個handler,將handler掛到鏈表input_handler_list下峻堰,然后遍歷input_dev_list鏈表,查找并匹配輸入設(shè)備對應(yīng)的事件處理層讹开,如果匹配上了,就調(diào)用connect函數(shù)進行連接捐名,并創(chuàng)建input_handle結(jié)構(gòu)旦万。

下面以Evdev為例,來分析事件處理層镶蹋。
vim drivers/input/evdev.c
同樣找到入口函數(shù):

module_init(evdev_init);

evdev_init分析:

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

直接調(diào)用input_register_handler 注冊一個input_handler結(jié)構(gòu)體成艘,這下回到了輸入核心層提供的接口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);

    /* `重要`:把設(shè)備處理器掛到全局的input子系統(tǒng)設(shè)備鏈表input_handler_list上 */  
    list_add_tail(&handler->node, &input_handler_list);

    /*遍歷input_dev_list贺归,試圖與每一個input_dev進行匹配*/
    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_register_handler的注冊過程淆两,你可以能看起來覺得挺熟悉;沒錯拂酣,這個注冊過程和input_register_device極其相似秋冰;下面就重點分析匹配連接過程中,事件處理層到底做了些什么婶熬。
input_attach_handler匹配過程如下:

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

    /* 利用handler->id_table和dev進行匹配*/
    id = input_match_device(handler, dev);
    if (!id)
        return -ENODEV;
      /*匹配成功剑勾,則調(diào)用handler->connect函數(shù)進行連接*/
    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;
}

對于connect函數(shù),每種事件處理器的實現(xiàn)都有差異赵颅,但原理都相同虽另。他主要注冊input_handle結(jié)構(gòu),然后將input_device和input_handler進行關(guān)聯(lián)饺谬。
以evdev的connect的evdev_connect函數(shù)捂刺,代碼注釋分析下這個過程:

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;
    
    /*申請一個新的次設(shè)備號*/
    minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);

    /* 這說明內(nèi)核已經(jīng)沒辦法再分配這種類型的設(shè)備了 */ 
    if (minor < 0) {
        error = minor;
        pr_err("failed to reserve new minor: %d\n", error);
        return error;
    }

    /* 開始給evdev事件層驅(qū)動分配空間了 */  
    evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
    if (!evdev) {
        error = -ENOMEM;
        goto err_free_minor;
    }

        /* 初始化client_list列表和evdev_wait隊列 */  
    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;
    
    /*設(shè)置設(shè)備節(jié)點名稱,/dev/eventX 就是在此時設(shè)置*/
    dev_set_name(&evdev->dev, "event%d", dev_no);

    /* 初始化evdev結(jié)構(gòu)體商蕴,其中handle為輸入設(shè)備和事件處理的關(guān)聯(lián)接口 */  
    evdev->handle.dev = input_get_device(dev);
    evdev->handle.name = dev_name(&evdev->dev);
    evdev->handle.handler = handler;
    evdev->handle.private = evdev;

      /*設(shè)置設(shè)備號叠萍,應(yīng)用層就是通過設(shè)備號,找到該設(shè)備的*/
    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);

     /* input_dev設(shè)備驅(qū)動和handler事件處理層的關(guān)聯(lián)绪商,就在這時由handle完成 */ 
    error = input_register_handle(&evdev->handle);
    if (error)
        goto err_free_evdev;

    cdev_init(&evdev->cdev, &evdev_fops);
    evdev->cdev.kobj.parent = &evdev->dev.kobj;
    error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);
    if (error)
        goto err_unregister_handle;

    /*將設(shè)備加入到Linux設(shè)備模型苛谷,它的內(nèi)部將找到它的bus,然后讓它的bus
    給它找到它的driver格郁,在驅(qū)動或者總線的probe函數(shù)中腹殿,一般會在/dev/目錄
    先創(chuàng)建相應(yīng)的設(shè)備節(jié)點独悴,這樣應(yīng)用程序就可以通過該設(shè)備節(jié)點來使用設(shè)備了
    ,/dev/eventX 設(shè)備節(jié)點就是在此時生成
    */
    error = device_add(&evdev->dev);
    if (error)
        goto err_cleanup_evdev;

    return 0;

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

到這里锣尉,輸入子系統(tǒng)的分析就結(jié)束了刻炒;如果還不是很了解,那么我們換個角度自沧,應(yīng)用層的調(diào)用如何作用到具體的實際硬件來分析坟奥,你就會清晰了。


從應(yīng)用層的角度出發(fā)看input子系統(tǒng)

首先思考個問題拇厢,在應(yīng)用層調(diào)用read函數(shù)爱谁,是如何讀取到實際硬件的按鍵值的?
由上面分析可知孝偎,當(dāng)調(diào)用open函數(shù)讀取鍵值時访敌,將調(diào)用到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;
    struct input_event event;
    size_t read = 0;
    int error;

    if (count != 0 && count < input_event_size())
        return -EINVAL;

    for (;;) {
        if (!evdev->exist || client->revoked)
            return -ENODEV;

        /*如果client的環(huán)形緩沖區(qū)中沒有數(shù)據(jù)并且是非阻塞的,那么返回-EAGAIN衣盾,也就是try again*/
        if (client->packet_head == client->tail &&
            (file->f_flags & O_NONBLOCK))
            return -EAGAIN;

        /*
         * count == 0 is special - no IO is done but we check
         * for error conditions (see above).
         */
        if (count == 0)
            break;

            /*調(diào)用evdev_fetch_next_event寺旺,如果獲得了數(shù)據(jù)則取出來*/ 
        while (read + input_event_size() <= count &&
               evdev_fetch_next_event(client, &event)) {

            /*input_event_to_user調(diào)用copy_to_user傳入用戶程序中,這樣讀取完成*/  
            if (input_event_to_user(buffer + read, &event))
                return -EFAULT;

            read += input_event_size();
        }

        if (read)
            break;

        /*如果沒有數(shù)據(jù)势决,并且是阻塞的阻塑,則在等待隊列上等待*/  
        if (!(file->f_flags & O_NONBLOCK)) {
            error = wait_event_interruptible(evdev->wait,
                    client->packet_head != client->tail ||
                    !evdev->exist || client->revoked);
            if (error)
                return error;
        }
    }

    return read;
}

如果read函數(shù)進入了休眠狀態(tài),又是誰來喚醒果复?搞明白了這個問題叮姑,也就知道了read的數(shù)據(jù)是從哪來的了。
搜索這個evdev->wait這個等待隊列變量,找到evdev_event函數(shù)里喚醒:

static void evdev_event(struct input_handle *handle,
            unsigned int type, unsigned int code, int value)
{
    struct input_value vals[] = { { type, code, value } };

    evdev_events(handle, vals, 1);
         ---> evdev_pass_values(client, vals, count, ev_time);
                 ---> wake_up_interruptible(&evdev->wait);
}

其中evdev_event()是evdev.c(事件處理層) 的evdev_handler->event成員,如下圖所示:


猜測下,是誰調(diào)用evdev_event()這個evdev_handler->event事件驅(qū)動函數(shù)据悔,
應(yīng)該就是之前分析的input_dev設(shè)備層調(diào)用的。
input.c中試搜下handler->eventhandler.event,回溯下函數(shù)調(diào)用:
handler->event(handle, v->type, v->code, v->value)
? ???---> input_to_handler
???????? ---> input_pass_values
??? ??????? ---> input_handle_event
???????????????? ---> input_event
顯然耘沼,就是input_dev通過輸入核心為驅(qū)動層提供統(tǒng)一的接口极颓,input_event,來向事件處理層上報數(shù)據(jù)并喚醒群嗤。





參考:
https://www.cnblogs.com/lcw/p/3506110.html
input子系統(tǒng)整體流程全面分析
linux中class_create和class_regster說明
input_proc_init 參考
register_chrdev_region 參考
使用register_chrdev_region()系列來注冊字符設(shè)備
https://www.cnblogs.com/lcw/p/3294356.html
https://www.cnblogs.com/lcw/p/3293302.html
https://www.cnblogs.com/lcw/p/3506110.html
https://blog.csdn.net/qq_695538007/article/details/40456875

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末菠隆,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子狂秘,更是在濱河造成了極大的恐慌骇径,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件者春,死亡現(xiàn)場離奇詭異破衔,居然都是意外死亡,警方通過查閱死者的電腦和手機钱烟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門晰筛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嫡丙,“玉大人,你說我怎么就攤上這事读第∈锊” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵怜瞒,是天一觀的道長父泳。 經(jīng)常有香客問我,道長吴汪,這世上最難降的妖魔是什么惠窄? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮浇坐,結(jié)果婚禮上睬捶,老公的妹妹穿的比我還像新娘。我一直安慰自己近刘,他們只是感情好擒贸,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著觉渴,像睡著了一般介劫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上案淋,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天座韵,我揣著相機與錄音,去河邊找鬼踢京。 笑死誉碴,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的瓣距。 我是一名探鬼主播黔帕,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蹈丸!你這毒婦竟也來了成黄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤逻杖,失蹤者是張志新(化名)和其女友劉穎奋岁,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體荸百,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡闻伶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了管搪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片虾攻。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡铡买,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出霎箍,到底是詐尸還是另有隱情奇钞,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布漂坏,位于F島的核電站景埃,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏顶别。R本人自食惡果不足惜谷徙,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望驯绎。 院中可真熱鬧完慧,春花似錦、人聲如沸剩失。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拴孤。三九已至脾歧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間演熟,已是汗流浹背鞭执。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留芒粹,地道東北人兄纺。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像化漆,于是被迫代替她去往敵國和親囤热。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 驅(qū)動程序 1. 申請設(shè)備获三,設(shè)置input設(shè)備支持的事件類型(比如EV_ABS,EV_KEY) input_allo...
    gbmaotai閱讀 838評論 0 0
  • 1:InputChannel提供函數(shù)創(chuàng)建底層的Pipe對象 2: 1)客戶端需要新建窗口 2)new ViewRo...
    自由人是工程師閱讀 5,310評論 0 18
  • 大學(xué)的時候,幫朋友寫的操作系統(tǒng)調(diào)研的作業(yè)锨苏,最近整理過去的文檔時候偶然發(fā)現(xiàn)疙教,遂作為博客發(fā)出來。 從串口驅(qū)動到Linu...
    free_will閱讀 7,391評論 7 59
  • 本文開啟 linux 內(nèi)核 V4L2 框架部分的學(xué)習(xí)之旅伞租,本文僅先對 V4L2 的框架做一個綜述性的概括介紹贞谓,然后...
    yellowmax閱讀 7,583評論 0 13
  • 這篇是來還債的裸弦。 說好了要講你們念叨了好久的那部周末院線大片祟同。 這就來—— 《盜墓筆記》 上映四天,累計票房突破5...
    Sir電影閱讀 2,836評論 23 53