1. 程式人生 > >input子系統的實現

input子系統的實現

    //////////////////////////////////////////////////////////////////////////////////////////////////////////////

    input子系統的實現

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////

    一、問: 為何要基於input子系統?

    答: 因為linux下所有GUI都可以接收input子系統的訊息。寫基於GUI的程式時可以用GUI實現的按鍵機制,而不是微控制器的思維,一個死迴圈來處理使用者輸入。

    二、input子系統運用場景初體驗

    接下來我們先做一個測試:看看input子系統在虛擬機器的linux系統底下運用場景:

    將附件中的檔案../usr_key/button_input/usr/button_testc.c,做如下兩個操作

    1、先直接linuxgcc執行生成測試檔案

    計算機生成了可選文字: #gccinputbuttontest.c一0Test

    2、利用linux下的事件完成測試,event1

    事件是我們的鍵盤事件,也可以用mouse0事件

    計算機生成了可選文字: ./Test/dev/input/eventl

    3、現象如下圖所示:

    計算機生成了可選文字: rOOtOIZh:一/lzh/work/kernel/buttonS_input/use降·/t/devlinpUt/eventltype1code28valueotype1code3valuelZtype1code3valueo

    三、用input子系統於ARM2440開發板實現按鍵中斷

    例程:如附件:../usr_key/button_input/..

    具體的下載操作指令:見附件../交叉編譯的使用

    另外新增如下知識點:

    指令:cat /proc/bus/input/devices     檢視掛載在input子系統下的裝置

       cd /sys/class/input/input_lzh  檢視我們掛載的按鍵子裝置(在這說明input_lzh 是我當前源程式中對應的裝置名稱)

    四、淺析input子系統

    雜,包括按鍵,鍵盤,觸控式螢幕,滑鼠,搖桿等等,它們本身都是字元裝置,不過核心為了能將這些裝置的

    共性抽象出來,簡化驅動的開發,建立了一個Input子系統。Input子系統分為三層,從下至上分別是輸入裝置驅動層,輸入核心層以及輸入事件驅動層。這三層中的輸入核心層和輸入事件驅動層都是核心已經完成了的,因此需要我們完成的只有輸入裝置驅動層。考慮輸入裝置主要的工作過程都是 動作產生(按鍵,觸屏……)-->產生中斷-->讀取數值(鍵值,座標……)-->將數值傳遞給應用程式。最後一個步驟就屬於事件的處理,對於同一類裝置,他們的處理方式都是相同的,因此核心已在事件驅動層為我們做好了,不需我們操心,而產生中斷-->讀取數值是因裝置而異的,需要我們根據具體的裝置來編寫驅動。一個大致的工作流程就是,
    input device向上層報告-->input core接收報告,並根據在註冊input device時建立好的連線選擇哪一類handler來處理事件-->通過handler將資料存放在相應的dev(evdev,mousedev…)例項的緩衝區中,等待應用程式來讀取。當然,有時候也需要從應用層向裝置層逆向傳遞,比如控制一些和裝置相關的LED,蜂鳴器等。裝置驅動層,輸入核心層和事件處理層之間的

    關係可以用下圖來闡釋:

    計算機生成了可選文字: OFIVeFsHand!ersUSBPS幾Keybo。川Hand!crConsol亡SubsyslemInpulCoreMoU沁H三IndlCrU陽作pacc(ldc話npu口“lou沁X)(ldevlinputj,nice)JOystick11andletU、c僕p淞c(Idevlinpu巧、X)000!一一阮tial!。,:'L一HJI、d!et門日卜--一U、cop淞亡'ldevlinpuUcvcntX)

    接下來讓我們進入原始碼中

    一切盡在原始碼中!

    下面我們分析一下輸入子系統的資料結構(即貫穿整個系統的三個連結串列),具體結構如下圖

    1.1input_dev,即我們裝置輸入層

  1. struct input_dev {  
  1. constchar *name;  
  2. constchar *phys;  
  3. constchar *uniq;  
  4. struct input_id id;//input_handler匹配用的id,包括
  5.     unsigned long evbit[BITS_TO_LONGS(EV_CNT)];    //裝置支援的事件型別
  6.     unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];  //按鍵事件支援的子事件型別
  7.     unsigned long relbit[BITS_TO_LONGS(REL_CNT)];  //相對座標事件支援的子事件型別
  8.     unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];  //絕對座標事件支援的子事件型別
  9.     unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];    
  10.     unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];    
  11.     unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];    
  12.     unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];       
  13.     unsigned long swbit[BITS_TO_LONGS(SW_CNT)];  
  14.     unsigned int keycodemax;  
  15.     unsigned int keycodesize;  
  16. void *keycode;  
  17. int (*setkeycode)(struct input_dev *dev, int scancode, int keycode);  
  18. int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode);  
  19. struct ff_device *ff;  
  20.     unsigned int repeat_key; //最近一次的按鍵值
  21. struct timer_list timer;  
  22. int sync;  
  23. int abs[ABS_MAX + 1];  
  24. int rep[REP_MAX + 1];  
  25.     unsigned long key[BITS_TO_LONGS(KEY_CNT)];//反應裝置當前的按鍵狀態
  26.     unsigned long led[BITS_TO_LONGS(LED_CNT)];//反應裝置當前的led狀態
  27.     unsigned long snd[BITS_TO_LONGS(SND_CNT)];//反應裝置當前的聲音輸入狀態
  28.     unsigned long sw[BITS_TO_LONGS(SW_CNT)];  //反應裝置當前的開關狀態
  29. int absmax[ABS_MAX + 1];//來自絕對座標事件的最大鍵值
  30. int absmin[ABS_MAX + 1];//來自絕對座標事件的最小鍵值
  31. int absfuzz[ABS_MAX + 1];  
  32. int absflat[ABS_MAX + 1];  
  33. int (*open)(struct input_dev *dev);  //第一次開啟裝置時呼叫,初始化裝置用
  34. void (*close)(struct input_dev *dev);//最後一個應用程式釋放裝置時用,關閉裝置
  35. int (*flush)(struct input_dev *dev, struct file *file);  
  36. /*用於處理傳遞給裝置的事件,如LED事件和聲音事件*/
  37. int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);  
  38. struct input_handle *grab;//當前佔有該裝置的input_handle
  39.     spinlock_t event_lock;  
  40. struct mutex mutex;  
  41.     unsigned int users;//開啟該裝置的使用者數量(input handlers)
  42. int going_away;  
  43. struct device dev;  
  44. struct list_head    h_list;//該連結串列頭用於連結此裝置所關聯的input_handle
  45. struct list_head    node;  //用於將此裝置連結到input_dev_list
  46. };  

1.2input_handler事件驅動層

  1. struct input_handler {  
  1. void *private;  
  2. /*event用於處理事件*/
  3. void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);  
  4. /*connect用於建立handlerdevice的聯絡*/
  5. int (*connect)(struct input_handler *handler, struct input_dev *dev, conststruct input_device_id *id);  
  6. /*disconnect用於解除handlerdevice的聯絡*/
  7. void (*disconnect)(struct input_handle *handle);  
  8. void (*start)(struct input_handle *handle);  
  9. conststruct file_operations *fops;//handler的一些處理函式
  10. int minor;//次裝置號
  11. constchar *name;  
  12. conststruct input_device_id *id_table;//用於和device匹配
  13. conststruct input_device_id *blacklist;//匹配黑名單
  14. struct list_head    h_list;//用於連結和此handler相關的handle
  15. struct list_head    node;  //用於將該handler鏈入input_handler_list
  16. };  

1.3input_handle中間層(handle,動詞,把兩個物件(事件層驅動層)匹配在一起)

  1. Struct input_handle
  2.  {    
  1. void *private;  
  2. int open;//記錄裝置的開啟次數(有多少個應用程式訪問裝置)
  3. constchar *name;  
  4. struct input_dev *dev;//指向所屬的device
  5. struct input_handler *handler;//指向所屬的handler
  6. struct list_head    d_node;//用於鏈入所屬devicehandle連結串列
  7. struct list_head    h_node;//用於鏈入所屬handlerhandle連結串列
  8. };  

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;//用於鏈入所屬devicehandle連結串列

struct list_head    h_node;//用於鏈入所屬handlerhandle連結串列

我們可以看到,input_deviceinput_handler中都有一個h_list,input_handle擁有指向input_devinput_handler的指標,也就是說input_handle是用來關聯input_devinput_handler的,那麼為什麼一個input_deviceinput_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()函式來完成

  1. staticint __init input_init(void)  
  1. {  
  2. int err;  
  3.     input_init_abs_bypass();  
  4.     err = class_register(&input_class);//註冊input
  5. if (err) {  
  6.         printk(KERN_ERR "input: unable to register input_dev class\n");  
  7. return err;  
  8.     }  
  9.     err = input_proc_init();//建立/proc中的項
  10. if (err)  
  11. goto fail1;  
  12. /*註冊裝置,裝置號為INPUT_MAJOR(13),後面註冊的輸入裝置都使用該主裝置號*/
  13.     err = register_chrdev(INPUT_MAJOR, "input", &input_fops);  
  14. if (err) {  
  15.         printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);  
  16. goto fail2;  
  17.     }  
  18. return 0;  
  19.  fail2: input_proc_exit();  
  20.  fail1: class_unregister(&input_class);  
  21. return err;  
  22. }  

input_fops中只定義了open函式

  1. staticconststructfile_operations input_fops = {  
  1.     .owner = THIS_MODULE,  
  2.     .open = input_open_file,  
  3. };  

我們需要在裝置驅動層中完成輸入裝置的註冊,通過呼叫input_register_device()函式來完成,該函式的一個重要任務就是完成裝置與事件驅動的匹配。

  1. int input_register_device(struct input_dev *dev)  
  2. {  
  3. static atomic_t input_no = ATOMIC_INIT(0);  
  4. struct input_handler *handler;  
  5. constchar *path;  
  6. int error;  
  7.     __set_bit(EV_SYN, dev->evbit);  
  8. /*
  1.      * If delay and period are pre-set by the driver, then autorepeating
  2.      * is handled by the driver itself and we don't do it in input.c.
  3.      */
  1.     init_timer(&dev->timer);  
  2. if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {  
  3.         dev->timer.data = (long) dev;  
  4.         dev->timer.function = input_repeat_key;