輸入子系統(Input Subsystem)的架構如下圖所示
輸入子系統由 輸入子系統核心層( Input Core ),驅動(dòng)層和事件處理層(Event Handler)三部份組成。一個(gè)輸入事件,如鼠標移動(dòng),鍵盤(pán)按鍵按下,joystick的移動(dòng)等等通過(guò) Driver -> InputCore -> Eventhandler -> userspace 的順序到達用戶(hù)空間傳給應用程序。
其中Input Core 即 Input Layer 由 driver/input/input.c及相關(guān)頭文件實(shí)現。對下提供了設備驅動(dòng)的接口,對上提供了Event Handler層的編程接口。
表 1 Input Subsystem main data structure
| 數據結構 | 用途 | 定義位置 | 具體數據結構的分配和初始化 |
| Struct input_dev | 驅動(dòng)層物理Input設備的基本數據結構 | Input.h | 通常在具體的設備驅動(dòng)中分配和填充具體的設備結構 |
| 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層,定義一個(gè)具體的Event Handler。 |
| Struct Input_handle | 用來(lái)創(chuàng )建驅動(dòng)層Dev和Handler鏈表的鏈表項結構 | Input.h | Event Handler層中分配,包含在Evdev/Mousedev…中。 |
圖2 輸入子系統架構示例圖
由于input子系統通過(guò)分層將一個(gè)輸入設備的輸入過(guò)程分隔為獨立的兩部份:驅動(dòng)到Input Core,Input Core到Event Handler。所以整個(gè)鏈路的這兩部分的接口的創(chuàng )建是獨立的。
驅動(dòng)層負責和底層的硬件設備打交道,將底層硬件對用戶(hù)輸入的響應轉換為標準的輸入事件以后再向上發(fā)送給Input Core。
驅動(dòng)層通過(guò)調用Input_register_device函數和Input_unregister_device函數來(lái)向輸入子系統中注冊和注銷(xiāo)輸入設備。
這兩個(gè)函數調用的參數是一個(gè)Input_dev結構,這個(gè)結構在driver/input/input.h中定義。驅動(dòng)層在調用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字段用來(lái)定義該輸入設備可以支持的(產(chǎn)生和響應)的事件的類(lèi)型。
包括:
Ø 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 力反饋
一個(gè)設備可以支持一個(gè)或多個(gè)事件類(lèi)型。每個(gè)事件類(lèi)型下面還需要設置具體的觸發(fā)事件,比如EV_KEY事件,支持哪些按鍵等。
驅動(dòng)層只是把輸入設備注冊到輸入子系統中,在驅動(dòng)層的代碼中本身并不創(chuàng )建設備結點(diǎn)。應用程序用來(lái)與設備打交道的設備結點(diǎn)的創(chuàng )建由Event Handler層調用Input core中的函數來(lái)實(shí)現。而在創(chuàng )建具體的設備節點(diǎn)之前,Event Handler層需要先注冊一類(lèi)設備的輸入事件處理函數及相關(guān)接口
以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來(lái)注冊一個(gè)鼠標類(lèi)型的Handler. 這里的Handler不是具體的用戶(hù)可以操作的設備,而是鼠標類(lèi)設備的統一的處理函數接口。
接下來(lái),mousedev_init函數調用input_register_minor注冊一個(gè)通用mice設備,這才是與用戶(hù)相關(guān)聯(lián)的具體的設備接口。然而這里在init函數中創(chuàng )建一個(gè)通用的Mice設備只是鼠標類(lèi)Event Handler層的特例。在其它類(lèi)型的EventHandler層中,并不一定會(huì )創(chuàng )建一個(gè)通用的設備。
標準的流程見(jiàn)是硬件驅動(dòng)向Input子系統注冊一個(gè)硬件設備后,在input_register_device中調用已經(jīng)注冊的所有類(lèi)型的Input Handler的connect函數,每一個(gè)具體的Connect函數會(huì )根據注冊設備所支持的事件類(lèi)型判斷是否與自己相關(guān),如果相關(guān)就調用input_register_minor創(chuàng )建一個(gè)具體的設備節點(diǎn)。
void input_register_device(struct input_dev *dev)
{
……
while (handler) {
if ((handle = handler->connect(handler, dev)))
input_link_handle(handle);
handler = handler->next;
}
}
此外如果已經(jīng)注冊了一些硬件設備,此后再注冊一類(lèi)新的Input Handler,則同樣會(huì )對所有已注冊的Device調用新的Input Handler的Connect函數已確定是否需要創(chuàng )建新的設備節點(diǎn):
void input_register_handler(struct input_handler *handler)
{
……
while (dev) {
if ((handle = handler->connect(handler, dev)))
input_link_handle(handle);
dev = dev->next;
}
}
從上面的分析中可以看到一類(lèi)Input Handler可以和多個(gè)硬件設備相關(guān)聯(lián),創(chuàng )建多個(gè)設備節點(diǎn)。而一個(gè)設備也可能與多個(gè)Input Handler相關(guān)聯(lián),創(chuàng )建多個(gè)設備節點(diǎn)。
直觀(guān)起見(jiàn),物理設備,Input Handler,邏輯設備之間的多對多關(guān)系可見(jiàn)下圖:
圖3 物理設備,Input Handler,邏輯設備關(guān)系圖
用戶(hù)程序通過(guò)Input Handler層創(chuàng )建的設備節點(diǎn)的Open,read,write等函數打開(kāi)和讀寫(xiě)輸入設備。
設備節點(diǎn)的Open函數,首先會(huì )調用一類(lèi)具體的Input Handler的Open函數,處理一些和該類(lèi)型設備相關(guān)的通用事務(wù),比如初始化事件緩沖區等。然后通過(guò)Input.c中的input_open_device函數調用驅動(dòng)層中具體硬件設備的Open函數。
大多數Input Handler的Read函數等待在Event Layer層邏輯設備的wait隊列上。當設備驅動(dòng)程序通過(guò)調用Input_event函數將輸入以事件的形式通知給輸入子系統的時(shí)候,相關(guān)的Input Handler的event函數被調用,該event函數填充事件緩沖區后將等待隊列喚醒。
在驅動(dòng)層中,讀取設備輸入的一種可能的實(shí)現機制是掃描輸入的函數睡眠在驅動(dòng)設備的等待隊列上,在設備驅動(dòng)的中斷函數中喚醒等待隊列,而后掃描輸入函數將設備輸入包裝成事件的形式通知給輸入子系統。
2.4內核中沒(méi)有固定的模式,根據具體的Input Handler,可能不實(shí)現,也可能通過(guò)調用Input_event將寫(xiě)入的數據以事件的形式再次通知給輸入子系統,或者調用設備驅動(dòng)的Write函數等等。
2.6內核的代碼中,通過(guò)調用Input_event將寫(xiě)入的數據以事件的形式再次通知給輸入子系統,而后在Input.c中根據事件的類(lèi)型,將需要反饋給物理設備的事件通過(guò)調用物理設備的Event函數傳給設備驅動(dòng)處理,如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;
......
}
本文中對Input子系統架構的分析主要是基于2.4.20內核,在2.6內核中對Input子系統做了很大的擴充增加了對許多設備的支持(如觸摸屏,鍵盤(pán)等)。不過(guò)整體的框架還是一致的。
另,參考了linux journal上的兩篇文章:
The Linux USB Input Subsystem, Part I | Linux Journal
Using the Input Subsystem, Part II | Linux Journal
聯(lián)系客服