Linux的input子系統提供了輸入設備的驅動框架,比如鼠標、鍵盤、觸摸屏等就屬於輸入設備。Linux中關於input子系統的文檔在Documentation/input目錄,input的核心代碼在input.c和input.h中。
本文沒有涉及input的一些細節實現,比如input_dev->grab,以及按鍵的定時事件等。
input_handle, input_handler, input_dev是input子系統中最重要的3個數據結構。
l input_handler用於上層應用獲取輸入事件。上層應用打開輸入設備的設備節點,然後對節點進行讀寫操作以獲得鼠標移動信息,或者鍵盤信息等等。這裡對設備節點的文件操作函數就是由input_handler提供。
l input_dev代表的是具體的設備,比如鼠標、鍵盤等等。
l 對於一台Linux電腦,可能會連著多個鼠標、多個鍵盤。每一個鼠標都能控制光標的運動,每一個鍵盤也都能正常使用。這在input子系統中,體現為一個input_handle關聯多個input_dev,能夠同時從多個input_dev獲取輸入消息。與此同時,linux中可能會有多個device節點同時與一個input具體設備關聯,這樣,應用程序通過任何一個設備節點,都可以獲得例如鼠標、鍵盤等具體設備的輸入信息。所以,input_dev和input_handler之間是多對多的關聯關系,而這些關聯就是由input_handle表示。
input_handle中包含一個input_dev的指針,和一個input_handler的指針,所以能建立handler和dev之間的一個一對一的關聯。在input_handler中,有一個鏈表h_list,指向和這個handler關聯的所有input_handle,通過這些handle就可以找到與handler關聯的所有dev。同樣的,在input_dev中,也有一個鏈表h_list,指向與dev關聯的所有input_handle,通過這些handle可以找到與dev相關的所有handler。通過這兩個鏈表和input_handle,input_handler和input_dev之間建立了一個復雜的網狀結構。
那麼,input_handler和input_dev之間建立關聯的規則是什麼?即在什麼情況下需要建立關聯,什麼時候不需要建立關聯?這就需要handler和dev之間有一個匹配機制。
input_handler中有兩個指針,id_table和blacklist,其中blacklist是黑名單,凡是與之匹配的dev都將被強制過濾;而與id_table中任意一項匹配的dev才能與handler建立關聯。
const struct input_device_id *id_table;
const struct input_device_id *blacklist;
struct input_device_id {
kernel_ulong_t flags;
__u16 bustype;
__u16 vendor;
__u16 product;
__u16 version;
kernel_ulong_t evbit[INPUT_DEVICE_ID_EV_MAX / BITS_PER_LONG + 1];
kernel_ulong_t keybit[INPUT_DEVICE_ID_KEY_MAX / BITS_PER_LONG + 1];
kernel_ulong_t relbit[INPUT_DEVICE_ID_REL_MAX / BITS_PER_LONG + 1];
kernel_ulong_t absbit[INPUT_DEVICE_ID_ABS_MAX / BITS_PER_LONG + 1];
kernel_ulong_t mscbit[INPUT_DEVICE_ID_MSC_MAX / BITS_PER_LONG + 1];
kernel_ulong_t ledbit[INPUT_DEVICE_ID_LED_MAX / BITS_PER_LONG + 1];
kernel_ulong_t sndbit[INPUT_DEVICE_ID_SND_MAX / BITS_PER_LONG + 1];
kernel_ulong_t ffbit[INPUT_DEVICE_ID_FF_MAX / BITS_PER_LONG + 1];
kernel_ulong_t swbit[INPUT_DEVICE_ID_SW_MAX / BITS_PER_LONG + 1];
kernel_ulong_t driver_info;
};
在這個結構體中,flags中包含一些掩碼:
#define INPUT_DEVICE_ID_MATCH_BUS 1
#define INPUT_DEVICE_ID_MATCH_VENDOR 2
#define INPUT_DEVICE_ID_MATCH_PRODUCT 4
#define INPUT_DEVICE_ID_MATCH_VERSION 8
這些掩碼用來匹配bus類型、廠商、產品號、版本號等。如果選擇不匹配這些信息,那麼driver_info需要置位,以跳過flags的匹配過程。
evbit[INPUT_DEVICE_ID_EV_MAX / BITS_PER_LONG + 1]中包含支持的事件掩碼。input子系統支持以下事件:
#define EV_SYN 0x00 // EV_SYN用於標記一系列輸入的結束
#define EV_KEY 0x01 // 鍵盤輸入
#define EV_REL 0x02 // 鼠標的相對位移
#define EV_ABS 0x03 // 觸摸屏的絕對坐標
#define EV_MSC 0x04
#define EV_SW 0x05
#define EV_LED 0x11 // 比如鍵盤的LED控制
#define EV_SND 0x12
#define EV_REP 0x14
#define EV_FF 0x15
#define EV_PWR 0x16
#define EV_FF_STATUS 0x17
如果handler支持某一種事件,那麼evbit的對應位會置位。如果evbit的某一位置位了,那麼結構體中與具體事件對應的掩碼數組就會有效。例如,EV_KEY置位了,那麼對應的keybit數組就有效,該數組裡面定義了handler支持的具體key的類型。如果handler和dev需要匹配,那麼dev必須能支持所有handler支持的事件;但是,handler卻不一定要處理所有dev能提供的事件。
#define MATCH_BIT(bit, max) \
for (i = 0; i < BITS_TO_LONGS(max); i++) \
if ((id->bit[i] & dev->bit[i]) != id->bit[i]) \
break; \
if (i != BITS_TO_LONGS(max)) \
continue;
關於匹配的具體過程,可以參考函數input_match_device。
所有的input_dev都是虛擬設備,其class名為”input”,class的登記函數:
class_register(&input_class);
所以,需要先注冊一個物理設備,已此物理設備為父設備注冊input_dev。
input_dev的注冊由input_register_device函數完成。
int input_register_device(struct input_dev *dev)
__set_bit(EV_SYN, dev->evbit); // 強制設置EV_SYN位
// 初始化定時器,REP_DELAY是第一次延時的超時值,REP_PERIOD是第一次延時 // 之後的超時值
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;
}
// 設置默認的get key和set key函數
if (!dev->getkeycode) dev->getkeycode = input_default_getkeycode;
if (!dev->setkeycode) dev->setkeycode = input_default_setkeycode;
// 設置設備名稱,然後注冊設備
snprintf(dev->dev.bus_id, sizeof(dev->dev.bus_id),
"input%ld", (unsigned long) atomic_inc_return(&input_no) - 1);
device_add(&dev->dev);
list_add_tail(&dev->node, &input_dev_list); // 將設備加入input_dev_list鏈表
// 對每一個已注冊的handler,調用input_attach_handler
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler); // 見下節
// proc有關的操作
input_wakeup_procfs_readers();