輸入子系統分析 [input event evdev]

轉自:http://blog.csdn.net/colorant/archive/2007/04/12/1561837.aspx

1 輸入子系統架構Overview

        輸入子系統(Input Subsystem)的架構如下圖所示

 

        輸入子系統由 輸入子系統核心層( Input Core ),驅動層和事件處理層(Event Handler)三部份組成。一個輸入事件,如鼠標移動,鍵盤按鍵按下,joystick的移動等等通過 Driver -> InputCore -> Eventhandler -> userspace 的順序到達用戶空間傳給應用程序。
        其中Input Core 即 Input Layer 由 driver/input/input.c及相關頭文件實現。對下提供了設備驅動的接口,對上提供了Event Handler層的編程接口。

1.1 主要數據結構

                                                                                                                          表 1     Input Subsystem main data structure

數據結構

用途

定義位置

具體數據結構的分配和初始化

Struct input_dev

驅動層物理Input設備的基本數據結構

Input.h

通常在具體的設備驅動中分配和填充具體的設備結構

Struct Evdev

Struct Mousedev

Struct Keybdev…

Event Handler層邏輯Input設備的數據結構

Evdev.c

Mousedev.c

Keybdev.c

Evdev.c/Mouedev.c …中分配

Struct Input_handler

Event Handler的結構

Input.h

Event Handler層,定義一個具體的Event Handler

Struct Input_handle

用來創建驅動層DevHandler鏈表的鏈表項結構

Input.h

Event Handler層中分配,包含在Evdev/Mousedev…中。

 

1.2 輸入子系統架構示例圖

圖2 輸入子系統架構示例圖


2 輸入鏈路的創建過程

        由於input子系統通過分層將一個輸入設備的輸入過程分隔爲獨立的兩部份:驅動到Input Core,Input Core到Event Handler。所以整個鏈路的這兩部分的接口的創建是獨立的。

2.1 硬件設備的註冊

        驅動層負責和底層的硬件設備打交道,將底層硬件對用戶輸入的響應轉換爲標準的輸入事件以後再向上發送給Input Core。
        驅動層通過調用Input_register_device函數和Input_unregister_device函數來向輸入子系統中註冊和註銷輸入設備。
這兩個函數調用的參數是一個Input_dev結構,這個結構在driver/input/input.h中定義。驅動層在調用Input_register_device之前需要填充該結構中的部分字段

#include <linux/input.h>
#include <linux/module.h>
#include <linux/init.h>

MODULE_LICENSE("GPL");

struct input_dev ex1_dev;

static int __init ex1_init(void)
{
    /* extra safe initialization */
    memset(&ex1_dev, 0, sizeof(struct input_dev));
    init_input_dev(&ex1_dev);

    /* set up descriptive labels */
    ex1_dev.name = "Example 1 device";
    /* phys is unique on a running system */
    ex1_dev.phys = "A/Fake/Path";
    ex1_dev.id.bustype = BUS_HOST;
    ex1_dev.id.vendor = 0x0001;
    ex1_dev.id.product = 0x0001;
    ex1_dev.id.version = 0x0100;
   
    /* this device has two keys (A and B) */
    set_bit(EV_KEY, ex1_dev.evbit);
    set_bit(KEY_B, ex1_dev.keybit);
    set_bit(KEY_A, ex1_dev.keybit);
   
    /* and finally register with the input core */
    input_register_device(&ex1_dev);
   
    return 0;
}

        其中比較重要的是evbit字段用來定義該輸入設備可以支持的(產生和響應)的事件的類型。
包括:

Ø EV_RST    0x00   Reset
Ø EV_KEY    0x01   按鍵
Ø EV_REL    0x02   相對座標
Ø EV_ABS    0x03   絕對座標
Ø EV_MSC    0x04   其它
Ø EV_LED    0x11    LED
Ø EV_SND    0x12   聲音
Ø EV_REP    0x14   Repeat
Ø EV_FF    0x15   力反饋

一個設備可以支持一個或多個事件類型。每個事件類型下面還需要設置具體的觸發事件,比如EV_KEY事件,支持哪些按鍵等。

2.2 Event Handler層

2.2.1 註冊Input Handler

        驅動層只是把輸入設備註冊到輸入子系統中,在驅動層的代碼中本身並不創建設備結點。應用程序用來與設備打交道的設備結點的創建由Event Handler層調用Input core中的函數來實現。而在創建具體的設備節點之前,Event Handler層需要先註冊一類設備的輸入事件處理函數及相關接口
        以MouseDev Handler爲例:

static struct input_handler mousedev_handler = {
event:   mousedev_event,
connect:   mousedev_connect,
disconnect: mousedev_disconnect,
fops:   &mousedev_fops,
minor:   MOUSEDEV_MINOR_BASE,
};

static int __init mousedev_init(void)
{
input_register_handler(&mousedev_handler);

memset(&mousedev_mix, 0, sizeof(struct mousedev));z
init_waitqueue_head(&mousedev_mix.wait);
mousedev_table[MOUSEDEV_MIX] = &mousedev_mix;
mousedev_mix.exist = 1;
mousedev_mix.minor = MOUSEDEV_MIX;
mousedev_mix.devfs = input_register_minor("mice", MOUSEDEV_MIX, MOUSEDEV_MINOR_BASE);

printk(KERN_INFO "mice: PS/2 mouse device common for all mice/n");

return 0;
}

        在Mousedev_init中調用input.c中定義的input_register_handler來註冊一個鼠標類型的Handler. 這裏的Handler不是具體的用戶可以操作的設備,而是鼠標類設備的統一的處理函數接口。

2.2.2 設備節點的創建

        接下來,mousedev_init函數調用input_register_minor註冊一個通用mice設備,這纔是與用戶相關聯的具體的設備接口。 然而這裏在init函數中創建一個通用的Mice設備只是鼠標類Event Handler層的特例。在其它類型的EventHandler層中,並不一定會創建一個通用的設備。
        標準的流程見是硬件驅動向Input子系統註冊一個硬件設備後,在input_register_device中調用已經註冊的所有類型的Input Handler的connect函數,每一個具體的Connect函數會根據註冊設備所支持的事件類型判斷是否與自己相關,如果相關就調用 input_register_minor創建一個具體的設備節點。

void input_register_device(struct input_dev *dev)
{
……
while (handler) {
   if ((handle = handler->connect(handler, dev)))
    input_link_handle(handle);
   handler = handler->next;
}
}

        此外如果已經註冊了一些硬件設備,此後再註冊一類新的Input Handler,則同樣會對所有已註冊的Device調用新的Input Handler的Connect函數已確定是否需要創建新的設備節點:

void input_register_handler(struct input_handler *handler)
{
……
while (dev) {
   if ((handle = handler->connect(handler, dev)))
    input_link_handle(handle);
   dev = dev->next;
}
}

        從上面的分析中可以看到一類Input Handler可以和多個硬件設備相關聯,創建多個設備節點。而一個設備也可能與多個Input Handler相關聯,創建多個設備節點。
        直觀起見,物理設備,Input Handler,邏輯設備之間的多對多關係可見下圖:

圖3 物理設備,Input Handler,邏輯設備關係圖

3 設備的打開和讀寫

    用戶程序通過Input Handler層創建的設備節點的Open,read,write等函數打開和讀寫輸入設備。

3.1 Open

    設備節點的Open函數,首先會調用一類具體的Input Handler的Open函數,處理一些和該類型設備相關的通用事務,比如初始化事件緩衝區等。然後通過Input.c中的 input_open_device函數調用驅動層中具體硬件設備的Open函數。

3.2 Read

    大多數Input Handler的Read函數等待在Event Layer層邏輯設備的wait隊列上。當設備驅動程序通過調用Input_event函數將輸入以事件的形式通知給輸入子系統的時候,相關的Input Handler的event函數被調用,該event函數填充事件緩衝區後將等待隊列喚醒。
    在驅動層中,讀取設備輸入的一種可能的實現機制是掃描輸入的函數睡眠在驅動設備的等待隊列上,在設備驅動的中斷函數中喚醒等待隊列,而後掃描輸入函數將設備輸入包裝成事件的形式通知給輸入子系統。

3.3 Write

    2.4內核中沒有固定的模式,根據具體的Input Handler,可能不實現,也可能通過調用Input_event將寫入的數據以事件的形式再次通知給輸入子系統,或者調用設備驅動的Write函數等等。
    2.6內核的代碼中,通過調用Input_event將寫入的數據以事件的形式再次通知給輸入子系統,而後在Input.c中根據事件的類型,將需要反饋給物理設備的事件通過調用物理設備的Event函數傳給設備驅動處理,如EV_LED事件:

void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
......
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;
......
}

4 其它

    本文中對Input子系統架構的分析主要是基於2.4.20內核,在2.6內核中對Input子系統做了很大的擴充增加了對許多設備的支持(如觸摸屏,鍵盤等)。不過整體的框架還是一致的。

    另,參考了linux journal上的兩篇文章:

 

The Linux USB Input Subsystem, Part I | Linux Journal
Using the Input Subsystem, Part II | Linux Journal

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章