Input子系統分析

Linux  Input 設備驅動
1認識和使用 input 事件:
1.1Linux input 驅動分類
Input驅動程序是Linux輸入設備的驅動程序,分成遊戲杆(joystick)、鼠標(mouse和mice)和事件設備(Event queue)3種驅動程序。其中事件驅動程序是目前通用的驅動程序,可支持鍵盤、鼠標、觸摸屏等多種輸入設備。
Input驅動程序的主設備號是13,驅動程序的設備號分配如下所示。
  joystick遊戲杆:0~31 
  mouse鼠標:32~62    
  mice鼠標:63
 事件(Event)設備:64~95 
1.2查看 input 驅動
 cat /proc/bus/input/devices
 例如顯示以下信息:
I: Bus=0017 Vendor=0001 Product=0001 Version=0100
N: Name="Macintosh mouse button emulation"
P: Phys=
S: Sysfs=/devices/virtual/input/input0
U: Uniq=
H: Handlers=mouse0 event0
B: EV=7
B: KEY=70000 0 0 0 0 0 0 0 0
B: REL=3:
則說明 /dev/input/mouse0  和 /dev/input/event0 都對應着同一個鼠標設備。 
2.理解Input 子系統的模型:
2.1  input 子系統概述
Linux input  子系統將一個輸入設備的輸入過程分成了設備驅動(input device driver)和事件驅動(input event driver)兩個層。前者負責從底層硬件採集數據;後者負責與用戶程序接口,將採集到的數據分發給不同的用戶接口。通過這樣的設計,將千差萬別的設備統一到了爲數不多的幾種驅動接口上。同一種事件驅動可以用來處理多個同類設備;同一個設備也可以和多種事件驅動相銜接。
設備驅動並不創建文件節點,它只負責將採集到數據通過input.c中的函數input_event() 向上一層彙報;而各個事件驅動則分別將他們各自感興趣的事件信息提取出來,通過文件節點,提供給用戶。在這個過程中,input 子系統的核心負責着這兩層的交互工作。並管理和維護着記錄了他們各自信息的鏈表。具體過程如下圖所示:
更進一步理解Input 子系統,需要理解以下4個對象:
Input_device , handler,  handle , client
Input_device:  代表着具體的輸入設備,它直接從硬件中讀取數據,並以事件的形式轉發
Hanler:  代表接收某一類事件的上層接口,對應於一類事件設備文件
Handle : 用於將input_device 和  handler 連接起來的膠水,對應於某1個具體的設備文件。
Client:  對應於用戶程序對文件的訪問接口,每open一次事件驅動,就創建一個client.
Input 子系統維護者有兩條重要的鏈表:
static LIST_HEAD(input_dev_list);
static LIST_HEAD(input_handler_list);
一條記錄着系統中所有的輸入設備,另一條記錄着所有的事件驅動。輸入設備是通過input_dev 表示,而事件驅動是通過handler 表示。每當一個新的設備(如鍵盤,鼠標等)或者一個新的事件驅動被系統加載(調用input_register_device()或 input_register_driver()),都會掃描整個連標,並調用函數input_match_device(struct input_handler *handler, struct input_dev *dev)  嘗試配對工作。Input_handler-->id_table   記錄了需要匹配的特徵。
比如eventX事件驅動它的匹配特徵存在於
static const struct input_device_id evdev_ids[] = {
            { .driver_info = 1 },       /* Matches all devices */
            { },                                /* Terminating zero entry */
};
就像代碼中註釋的一樣,任何輸入設備都可以和他匹配成功,也就是說任何輸入設備都可以映射到/input/dev/eventX 下。 一旦匹配成功就會調用函數handler->connect(handler, dev, id);將具體的設備(input_dev)和事件驅動(handler)邦定到一起,形成一個handle,將handle 註冊到它相關聯的input_dev 設備的鏈表h_list中。設備編號在input_init() 的時候通過調用創建。
2.2  文件節點的建立
Input 子系統在初始化時,首先調用register_chrdev(INPUT_MAJOR, "input", &input_fops); 爲自己註冊了256個字符設備節點。(這也代表着系統中最多可以存在256個輸入設備)這256個設備會被分爲8類,分別對應於數組input_table[8]中存放的8個 handler.
static struct input_handler *input_table[8];  // kernel/input/input.c
其中數第1個句柄管理此設備號0-31,第2個句柄管理設備號32-63。。。
每一個句柄,都可以用來實現一類事件驅動(但每類事件驅動最多隻能管理32個設備)。
例如:/dev/input/eventX所表示的設備  和dev /input/mou*** 所表示的設備 就分別使用了最常見的兩種事件驅動。以/dev/input/eventX  爲例,他們都使用同一個event事件驅動,從對象的角度看,擁有同一個handler。而這個handler 所管理的設備,擁有次設備號64-95,每一個次設備號又可以對應到一個handle.
2.3 事件驅動註冊
事件驅動的方法被抽象到特定的 handler結構體中,驅動在初始化時並通過調用input_register_handler()把handler 註冊的系統中。
input_register_handler()具體完成3件事情:
1. 把handler 存放到input_table[]中
2. 把 handler 存放到input 核心的鏈表中
3. 掃描input核心鏈表上的device ,並試着匹配
2.4 設備驅動
具體設備是用input_dev 表示的,它通過函數input_register_device()註冊。
input_register_device()主要做了兩件事情:
1.  把 input_dev 存放到input 核心的鏈表中
2. 掃描input核心鏈表上的handler ,並試着匹配
2.5 匹配
Dev 和handler 是通過input_match_device(struct input_handler *handler, struct input_dev *dev)  嘗試配的。每當查找到可以相互匹配的dev 和 handler 便回調handler->connect() ,生成一個handle結構體,並 通過handle  把某一個具體的dev 和另一個具體的handler被邦定到一起。最後handle 通過函數input _register_handle()註冊到dev 的鏈表中。以後dev 上有數據要發送時,便可以在dev 中搜索這個鏈表,找到handle,再調用handle->handler->event() 把數據傳到了event 驅動中。
2.6數據傳送
當有數據上傳時,數據通過input_pass_event () 函數向handle 發送, 這個函數的核心內容如下:
list_for_each_entry_rcu(handle, &dev->h_list, d_node) {
            handler = handle->handler;
            handler->event(handle, type, code, value);
  }
根據input_dev 設備的h_list 鏈表查找與他相關的handle,然後調用event 處理方法,將數據傳遞給上層 的client。
2.7 clinet
每一個事件驅動(handler),可能對應多個文件節點(handle),而每一個文件節點又可以有多個client, 底層的數據直接複製到上層的client中。對文件句柄的操作則實際上是對這些個client 中的數據進行操作。
以驅動evdev.c 爲例,根據次設備號取值範圍64-95。可以分別生成/input/event0 ,input/event1,一直到/input/event31總共32個設備文件。
每一個設備文件又可以同時對應多個client,當有多個應用程序同時調用設備文件時,他們會從不同的client 中取數據。每當應用程序調用open 時,便會調用evdev_open()創建一個client,並被與設備節點邦定  file->private_data = client;
設備被應用層當作文件操作時,通過struct evdev_client *client = file->private_data; 再將client 找到。直接讀/寫 client 中的數據。而當底層有數據要傳過來時則會通過handler->event() 把數據複製到client 中,如evdev_event()通過client 鏈便,對每一個client 進行evdev_pass_event()調用。而在evdev_pass_event()中實現數據向client 節點的複製。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章