input子系統的實現
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
input子系統的實現
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
一、問: 為何要基於input子系統?
答: 因為linux下所有GUI都可以接收input子系統的訊息。寫基於GUI的程式時可以用GUI實現的按鍵機制,而不是微控制器的思維,一個死迴圈來處理使用者輸入。
二、input子系統運用場景初體驗
接下來我們先做一個測試:看看input子系統在虛擬機器的linux系統底下運用場景:
將附件中的檔案../usr_key/button_input/usr/button_testc.c,做如下兩個操作
1、先直接linux下gcc執行生成測試檔案
2、利用linux下的事件完成測試,event1
3、現象如下圖所示:
三、用input子系統於ARM2440開發板實現按鍵中斷
例程:如附件:../usr_key/button_input/..
具體的下載操作指令:見附件../交叉編譯的使用
另外新增如下知識點:
指令:cat /proc/bus/input/devices 檢視掛載在input子系統下的裝置
cd /sys/class/input/input_lzh 檢視我們掛載的按鍵子裝置(在這說明input_lzh 是我當前源程式中對應的裝置名稱)
四、淺析input子系統
輸入設備總類繁雜,包括按鍵,鍵盤,觸控式螢幕,滑鼠,搖桿等等,它們本身都是字元裝置,不過核心為了能將這些裝置的
關係可以用下圖來闡釋:
接下來讓我們進入原始碼中
一切盡在原始碼中!
下面我們分析一下輸入子系統的資料結構(即貫穿整個系統的三個連結串列),具體結構如下圖:
1.1、input_dev,即我們裝置輸入層
- struct input_dev {
- constchar *name;
- constchar *phys;
- constchar *uniq;
- struct input_id id;//與input_handler匹配用的id,包括
- unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; //裝置支援的事件型別
- unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; //按鍵事件支援的子事件型別
- unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; //相對座標事件支援的子事件型別
- unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; //絕對座標事件支援的子事件型別
- unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
- unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
- unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
- unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
- unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
- unsigned int keycodemax;
- unsigned int keycodesize;
- void *keycode;
- int (*setkeycode)(struct input_dev *dev, int scancode, int keycode);
- int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode);
- struct ff_device *ff;
- unsigned int repeat_key; //最近一次的按鍵值
- struct timer_list timer;
- int sync;
- int abs[ABS_MAX + 1];
- int rep[REP_MAX + 1];
- unsigned long key[BITS_TO_LONGS(KEY_CNT)];//反應裝置當前的按鍵狀態
- unsigned long led[BITS_TO_LONGS(LED_CNT)];//反應裝置當前的led狀態
- unsigned long snd[BITS_TO_LONGS(SND_CNT)];//反應裝置當前的聲音輸入狀態
- unsigned long sw[BITS_TO_LONGS(SW_CNT)]; //反應裝置當前的開關狀態
- int absmax[ABS_MAX + 1];//來自絕對座標事件的最大鍵值
- int absmin[ABS_MAX + 1];//來自絕對座標事件的最小鍵值
- int absfuzz[ABS_MAX + 1];
- int absflat[ABS_MAX + 1];
- int (*open)(struct input_dev *dev); //第一次開啟裝置時呼叫,初始化裝置用
- void (*close)(struct input_dev *dev);//最後一個應用程式釋放裝置時用,關閉裝置
- int (*flush)(struct input_dev *dev, struct file *file);
- /*用於處理傳遞給裝置的事件,如LED事件和聲音事件*/
- int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
- struct input_handle *grab;//當前佔有該裝置的input_handle
- spinlock_t event_lock;
- struct mutex mutex;
- unsigned int users;//開啟該裝置的使用者數量(input handlers)
- int going_away;
- struct device dev;
- struct list_head h_list;//該連結串列頭用於連結此裝置所關聯的input_handle
- struct list_head node; //用於將此裝置連結到input_dev_list
- };
1.2、input_handler事件驅動層
- struct input_handler {
- void *private;
- /*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, conststruct input_device_id *id);
- /*disconnect用於解除handler和device的聯絡*/
- void (*disconnect)(struct input_handle *handle);
- void (*start)(struct input_handle *handle);
- conststruct file_operations *fops;//handler的一些處理函式
- int minor;//次裝置號
- constchar *name;
- conststruct input_device_id *id_table;//用於和device匹配
- conststruct input_device_id *blacklist;//匹配黑名單
- struct list_head h_list;//用於連結和此handler相關的handle
- struct list_head node; //用於將該handler鏈入input_handler_list
- };
1.3、input_handle中間層(handle,動詞,把兩個物件(事件層驅動層)匹配在一起)
- Struct input_handle
- {
- void *private;
- int open;//記錄裝置的開啟次數(有多少個應用程式訪問裝置)
- constchar *name;
- struct input_dev *dev;//指向所屬的device
- struct input_handler *handler;//指向所屬的handler
- struct list_head d_node;//用於鏈入所屬device的handle連結串列
- struct list_head h_node;//用於鏈入所屬handler的handle連結串列
- };
2、接下來我們分析一下上述三個結構體是如何關聯在一起首先我們需瞭解到,他們各自兩個
list_head結構體的用途,入下述:
dev
struct list_head h_list;//該連結串列頭用於連結此裝置所關聯的input_handle
struct list_head node; //用於將此裝置連結到input_dev_list
handler
struct list_head h_list;//用於連結和此handler相關的handle
struct list_head node; //用於將該handler鏈入input_handler_list
handle
struct list_head d_node;//用於鏈入所屬device的handle連結串列
struct list_head h_node;//用於鏈入所屬handler的handle連結串列
我們可以看到,input_device和input_handler中都有一個h_list,而input_handle擁有指向input_dev和input_handler的指標,也就是說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個。
3、接下來我們將以even handler為例介紹設備註冊以及開啟的過程。
1.1、在設備註冊之前,必須先初始化INPUT子系統,由input_init()函式來完成
- staticint __init input_init(void)
- {
- int err;
- input_init_abs_bypass();
- err = class_register(&input_class);//註冊input類
- if (err) {
- printk(KERN_ERR "input: unable to register input_dev class\n");
- return err;
- }
- err = input_proc_init();//建立/proc中的項
- if (err)
- goto fail1;
- /*註冊裝置,裝置號為INPUT_MAJOR(13),後面註冊的輸入裝置都使用該主裝置號*/
- err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
- if (err) {
- printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);
- goto fail2;
- }
- return 0;
- fail2: input_proc_exit();
- fail1: class_unregister(&input_class);
- return err;
- }
input_fops中只定義了open函式。
- staticconststructfile_operations input_fops = {
- .owner = THIS_MODULE,
- .open = input_open_file,
- };
我們需要在裝置驅動層中完成輸入裝置的註冊,通過呼叫input_register_device()函式來完成,該函式的一個重要任務就是完成裝置與事件驅動的匹配。
- int input_register_device(struct input_dev *dev)
- {
- static atomic_t input_no = ATOMIC_INIT(0);
- struct input_handler *handler;
- constchar *path;
- int error;
- __set_bit(EV_SYN, dev->evbit);
- /*
- * If delay and period are pre-set by the driver, then autorepeating
- * is handled by the driver itself and we don't do it in input.c.
- */
- init_timer(&dev->timer);
- if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
- dev->timer.data = (long) dev;
- dev->timer.function = input_repeat_key;