輸入子系統分析
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的結構 | Event Handler層,定義一個具體的Event Handler。 | |
Struct Input_handle | 用來建立驅動層Dev和Handler連結清單的連結清單項結構 | 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) {
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
轉自:http://blog.csdn.net/colorant/archive/2007/04/12/1561837.aspx