標籤:
Technorati 標籤: Kernel 輸入子系統 Input
在Linux中,輸入裝置(如按鍵、鍵盤、觸控螢幕、滑鼠等)是典型的字元裝置,其一般的工作機理,是底層在按鍵、觸摸時,觸發一個中斷,或者驅動通過定時器定時查詢,通過這兩種方式通知CPU,CPU然後通過SPI、I2C或I/O介面讀取索引值、座標等資料,放入緩衝區,字元裝置驅動管理該緩衝區,向上提供read介面供應用程式使用。
在上述的工作流程中,只有終端、讀取數值是根具體硬體裝置相關,而輸入事件的緩衝區管理以及字元裝置驅動的介面函數,都是通用的,因此,有必要統一這些不同的輸入裝置,提煉出通用部分。
Linux的Input子系統整體架構如下:
先介紹核心資料結構體,再介紹一個簡單的例子,然後引入準系統函數。
核心資料結構體
Input子系統有三層,比較核心的結構體有四個,分別為 輸入事件input_event,輸入裝置input_dev,核心處理input_handle,事件處理input_handler,分屬於Input的不同層級,在上面這些結構體中,input_handle處於核心地位。如所示:
整個Input子系統有 兩個全域鏈表,一個是input_dev_list 鏈表,裡面有當前系統下,所有的底層輸入裝置,一個是input_handler_list鏈表,裡面有當前系統下,所有的事件處理函數。
輸入裝置
struct input_dev {
…..
struct input_id id;//與input_handler匹配用的id,包括 匯流排類型、生產廠商、產品類型、版本
unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; //裝置所支援的事件類型 ,如按鍵事件 EV_KEY
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; //裝置所支援的子事件類型,如 按索引值
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
unsigned long key[BITS_TO_LONGS(KEY_CNT)];//反應裝置當前的按鍵狀態
struct input_handle *grab;//當前佔有該裝置的input_handle
struct list_head h_list;//該鏈表頭用於連結此裝置所關聯的input_handle
struct list_head node; //用於將此裝置連結到input_dev_list
}
事件處理器
struct input_handler{
…..
int minor; //表示裝置的次裝置號
/*event用於處理事件*/
void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
/*connect用於建立handler和device的聯絡*/
int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
const struct file_operations *fops;//handler的一些處理函數
const struct input_device_id *id_table;//用於和device匹配 ,這個是事件處理器所支援的input裝置
const struct input_device_id *blacklist;//匹配黑名單,這個是事件處理器應該忽略的input裝置
struct list_head h_list;//這個鏈表用來連結他所支援的input_handle結構,input_dev與input_handler配對之後就會產生一個input_handle結構
struct list_head node; //連結到input_handler_list,這個鏈錶鏈接了所有註冊到核心的事件處理器
}
串連結構體
每一個 input_handle 結構體代表一個成功配對的 input_dev 和 input_handler。
struct input_handle {
void *private; //每個配對的事件處理器都會分配一個對應的裝置結構,如evdev事件處理器的evdev結構,注意這個結構與裝置驅動層的input_dev不同,初始化handle時,儲存到這裡。
int open; //開啟標誌,每個input_handle 開啟後才能操作,這個一般通過事件處理器的open方法間接設定
const char *name;
struct input_dev *dev; //關聯的input_dev結構
struct input_handler *handler; //關聯的input_handler結構
struct list_head d_node; //input_handle通過d_node串連到了input_dev上的h_list鏈表上
struct list_head h_node; //input_handle通過h_node串連到了input_handler的h_list鏈表上
};
資料結構之間的關係
struct input_dev物理輸入裝置的基本資料結構,包含裝置相關的一些資訊
struct input_handler 事件處理結構體,定義怎麼處理事件的邏輯
struct input_handle用來建立 input_dev 和 input_handler 之間關係的結構體
input_dev 通過全域的input_dev_list連結在一起。裝置註冊的時候實現這個操作。
input_handler 通過全域的input_handler_list連結在一起。事件處理器註冊的時候實現這個操作(事件處理器一般核心內建,一般不需要我們來寫)
input_hande 沒有一個全域的鏈表,它註冊的時候將自己分別掛在了input_dev 和 input_handler 的h_list上了。通過input_dev 和input_handler就可以找到input_handle在裝置註冊和事件處理器,註冊的時候都要進行配對工作,配對後就會實現連結。通過input_handle也可以找到input_dev和input_handler。
那麼為什麼一個input_device和input_handler中擁有的是h_list而不是一個handle呢?因為一個device可能對應多個handler,而一個handler也不能只處理一個device,比如說一個滑鼠,它可以對應even handler,也可以對應mouse handler,因此當其註冊時與系統中的handler進行匹配,就有可能產生兩個執行個體,一個是evdev,另一個是mousedev,而任何一個執行個體中都只有一個handle。至於以何種方式來傳遞事件,就由使用者程式開啟哪個執行個體來決定。後面一個情況很容易理解,一個事件驅動不能只為一個甚至一種裝置服務,系統中可能有多種裝置都能使用這類handler,比如event handler就可以匹配所有的裝置。在input子系統中,有8種事件驅動,每種事件驅動最多可以對應32個裝置,因此dev執行個體總數最多可以達到256個。
Input Driver例子
下面以一個簡單驅動為例子來介紹
#include <asm/irq.h>
#include <asm/io.h>
static struct input_dev *button_dev; /*輸入裝置結構體*/
static irqreturn_t button_interrupt(int irq, void *dummy) /*中斷處理函數*/
{
input_report_key(button_dev, BTN_0, inb(BUTTON_PORT) & 1); /*向輸入子系統報告產生按鍵事件*/
input_sync(button_dev); /*通知接收者,一個報告發送完畢*/
return IRQ_HANDLED;
}
static int __init button_init(void) /*載入函數*/
{
int error;
if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button", NULL)) /*申請中斷,綁定中斷處理函數*/
{
printk(KERN_ERR "button.c: Can‘t allocate irq %d\n", button_irq);
return -EBUSY;
}
button_dev = input_allocate_device(); /*分配一個裝置結構體*/
//input_allocate_device()函數在記憶體中為輸入裝置結構體分配一個空間,並對其主要的成員進行了初始化.
if (!button_dev)
{
printk(KERN_ERR "button.c: Not enough memory\n");
error = -ENOMEM;
goto err_free_irq;
}
button_dev->evbit[0] = BIT_MASK(EV_KEY); /*設定按鍵資訊*/
button_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);
//分別用來設定裝置所產生的事件以及上報的按索引值。Struct iput_dev中有兩個成員,一個是evbit.一個是keybit.分別用
//表示裝置所支援的動作和索引值。
error = input_register_device(button_dev); /*註冊一個輸入裝置*/
if (error)
{
printk(KERN_ERR "button.c: Failed to register device\n");
goto err_free_dev;
}
return 0;
err_free_dev:
input_free_device(button_dev);
err_free_irq:
free_irq(BUTTON_IRQ, button_interrupt);
return error;
}
static void __exit button_exit(void) /*卸載函數*/
{
input_unregister_device(button_dev); /*登出按鍵裝置*/
free_irq(BUTTON_IRQ, button_interrupt); /*釋放按鍵佔用的中斷線*/
}
module_init(button_init);
module_exit(button_exit);
這個demo代碼,在button_init()中,首先註冊了中斷處理函數,然後調用input_allocate_device()函數分配一個input_dev結構體,並調用input_register_device函數對其進行註冊。在中斷處理函數中,demo將接受到的按鍵資訊上報給Input子系統,Input子系統向使用者態程式提供按鍵輸入資訊。
在上述這個簡單的驅動裡面,涉及到幾個問題
1. 輸入裝置如何傳遞事件到核心層
2. 核心層如何找到對應事件的事件處理函數
3. 底層輸入裝置驅動是如何和事件處理層聯絡上的
準系統函數
下面從使用流程入手,簡要介紹以下input的整體流程,這裡只會標註出主要代碼流程。
1. 分配一個輸入裝置
從注釋中可知,釋放一個還未註冊的輸入裝置,使用input_free_device,釋放一個已經註冊的裝置,使用input_unregister_device。
由於沒有輸入參數,因此,可以猜測出這個分配出來的input_dev的一些配置都是預設配置。
這裡面,比較重要的有兩個鏈表h_list和node,分配後,我們需要在預設配置的基礎上,添加自己的配置資訊。
2. 註冊一個輸入裝置
從注釋上,可以知道,傳入參數必須為input_allocate_device的傳回值。
這個函數裡面會設定input_dev所支援的基本事件類型,注意,一個裝置可以支援一種或者多種事件類型。Input子系統需要在sysfs檔案系統中體現出現,因此,input在sysfs中的device名稱會在這裡面設定。
然後將底層輸入裝置input_dev添加到全域裝置鏈表input_dev_list中,對全域鏈表input_handler_list中的每一個handler函數,調用
input_attach_handler()。
每一次input_dev的註冊,都會遍曆事件處理鏈表input_handler_list,尋找輸入裝置對應的事件處理常式。
每一次input_hanlder的註冊,都會遍曆裝置鏈表input_dev_list,尋找事件處理常式對應的輸入裝置。
上面這兩個操作幾乎是對稱的,機制同platform中的device和device driver的相互尋找類型。
具體代碼如下:
platform機制中的尋找,是根據裝置名稱和裝置驅動名稱來匹配的,這裡也不例外,匹配的過程,通過對比input_dev和input_handler的id成員,具體為id成員的匯流排類型、裝置廠商、裝置號、裝置版本是否一致來判斷是否匹配成功。
3. 輸入裝置找到事件處理常式
正常情況下,使用 事件處理常式中的 id_table和輸入裝置input_dev.id成員進行匹配。id_table指向該事件處理常式支援的裝置列表。
匹配成功後,調用handler->connect,將handler和input_dev串連起來。
4. 向Input核心層報告輸入事件
這裡面的核心函數為 input_handle_event(dev,type,code,value),裡面是一個大的switch,一級為事件類型type,二級switch為event code,這裡只分析按鍵相關:
disposition 的取值有如下幾種,它表示使用什麼樣的方式處理該輸入事件。
#define INPUT_IGNORE_EVENT 0 // 表示忽略事件,不對其進行處理
#define INPUT_PASS_TO_HANDLERS 1 // 表示將事件交給 handler 處理
#define INPUT_PASS_TO_DEVICE 2 // 表示將事件交給 input_dev 處理
#define INPUT_PASS_TO_ALL (INPUT_PASS_TO_HANDLERS | INPUT_PASS_TO_DEVICE)
如果該事件是傳遞給裝置自身,則調用裝置驅動自身的event函數來處理事件。
如果該事件時傳遞給上層事件處理函數,則調用input_pass_event來傳遞事件,將調用輸入裝置對於的handler的event()函數來處理輸入事件。
注意:只有在handle被開啟的情況下,才會接收到事件。
5. Input子系統輸入事件處理層
輸入事件處理層是在系統初始化時,註冊進系統的。
輸入子系統的事件處理層核心資料結構為 input_handler,所有輸入子系統的事件處理常式都掛在input_handler_list中。在
6. 輸入事件處理層註冊
系統定義了8個輸入事件處理層, 這些事件處理層通過handler->h_list串連起來,同時,也儲存在全域input_table數組(他們在數組中的索引為裝置號右移5位的值)和全域input_handler_list鏈表中。
同輸入裝置註冊一樣,輸入事件處理註冊時,需要尋找對應的輸入裝置。代碼如下:
在input_attach_handler中,最後會調用error = handler->connect(handler, dev, id);也就是evdev_handler->connect,也就是
evdev_connect函數,在這裡面初始化input handle,並且註冊到系統。
在這裡面,會將handle掛到所對應input device的h_list鏈表上.還將handle掛到對應的handler的hlist鏈表上,因此,可以把handle看成是 handler和 input device的資訊集合體 .在這個結構裡集合了匹配成功的 handler和 input device。就這樣,handler和input dev匹配到一起。
7. 事件層處理來自核心層的事件
這裡,會呼叫事件處理層的event函數,也就是evdev_event。每當input dev上報一個事件時,會將其交給和它匹配的handler的event函數來處理,在這裡,又會通過遍曆鏈表來調用evdev_pass_event來處理。
這裡的操作,就是將event上傳的資料儲存到client->buffer中。client->head是當前的資料位元置,這裡是一個環形緩衝區,
寫資料是從client->head寫.而讀資料則是從client->tail中讀.
寫完之後,通過向上層發起SIGIO訊號來通知有事件發生,可以從緩衝區中讀取資料了。
7. 輸入事件處理函數的檔案提供者
輸入裝置在上層表現為主裝置號為INPUT_MAJOR的裝置檔案,對他的讀寫會通過VFS,最後傳遞到evdev_fops的檔案操作結構體中去。
1: static ssize_t evdev_read(struct file *file, char __user *buffer,
2: size_t count, loff_t *ppos)
3: {
4: struct evdev_client *client = file->private_data;
5: struct evdev *evdev = client->evdev;
6: struct input_event event;
7: int retval;
8:
9: if (count < input_event_size())
10: return -EINVAL;
11:
12: if (client->head == client->tail && evdev->exist &&
13: (file->f_flags & O_NONBLOCK))
14: return -EAGAIN;
15:
16: retval = wait_event_interruptible(evdev->wait,
17: client->head != client->tail || !evdev->exist);
18: if (retval)
19: return retval;
20:
21: if (!evdev->exist)
22: return -ENODEV;
23:
24: while (retval + input_event_size() <= count &&
25: evdev_fetch_next_event(client, &event)) {
26:
27: if (input_event_to_user(buffer + retval, &event))
28: return -EFAULT;
29:
30: retval += input_event_size();
31: }
32:
33: return retval;
34: }
首先,它判斷緩衝區大小是否足夠.在讀取資料的情況下,可能當前緩衝區內沒有資料可讀.在這裡先睡眠等待緩衝
區中有資料.如果在睡眠的時候,.條件滿足.是不會進行睡眠狀態而直接返回的. 然後根據read()提夠的緩衝區大小.將
client中的資料寫入到使用者空間的緩衝區中.
參考文獻:
http://blog.csdn.net/lbmygf/article/details/7360084
http://blog.chinaunix.net/uid-27717694-id-3758334.html
Linux 輸入子系統