Linux Input子系統之第一篇(input_dev/input_handle/input_handler)

Input子系統是linux kernel中與部分外圍器件驅動聯繫比較緊密的模塊,常用於Sensor,TP(touch panel),power key等器件的驅動。這類模塊有個共同特點:字符設備,且數據量都不大,比如sensor一般最多隻有xyz三個維度的數據。
      整體來看,Input子系統有一個主線,那就是題目中這三個結構體的關係(下面簡稱爲三方關係),input_dev對應於實際的device端,input_handler從名字也可以猜出來是對device的處理。“處理”這個詞語不單單指的是對device數據的處理,比如report等;它其實可以包括系統在該device事件發生時想做的任何動作。至於input_handle,它是連接input_dev與input_handler的,該設計後面也會詳細分析。在這裏請記住,我們最終的目的是,通過input_dev,可以遍歷所有與它有關的input_handler;通過input_handler,也可以遍歷所有與它有關的input_dev。
       爲了更加透徹地講述Input子系統,本博文將分兩篇介紹,第一篇就來分析上面這個主線,第二篇分析Input子系統的A/B兩個協議(B協議又稱爲Slot Protocol)。下面從input_device說起。
      驅動端通過input_allocate_device來allocate對應的input_dev結構體,之後持有該指針,並完成對應的初始化(name, set_bit等等)。

點擊(此處)摺疊或打開

  1. struct input_dev *input_allocate_device(void)
  2. {
  3.     struct input_dev *dev;
  4.     dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);
  5.     if (dev) {
  6.         ...//I deleted many lines here.
  7.         INIT_LIST_HEAD(&dev->h_list);//this is the head of the list which consists of related input_handles.
  8.         ...
  9.     }
  10.     return dev;
  11. }
        結合這段代碼,看下input_dev結構體(這裏只關心主線相關的結構體成員)

點擊(此處)摺疊或打開

  1. struct input_dev {
  2.     ...//detailed info of this device
  3.     struct list_head         h_list;//這是與input_dev相關聯的input_handle的鏈表的表頭
  4.     struct list_head node;//鏈入全局鏈表
  5. };
        驅動完成初始化後,調用input_register_device來註冊已經初始化的input_dev,這個函數是三方關係的核心,它調用了input_attach_handler,而恰恰是在input_attach_handler這個函數內dev, handler和handle這三者確定了關係。看下這個函數究竟做了哪些關鍵的事情,

點擊(此處)摺疊或打開

  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.     ...//此次省略一千行O(∩_∩)O~
  6.     dev_set_name(&dev->dev, "input%ld",
  7.          (unsigned long) atomic_inc_return(&input_no) - 1);//set the dev name here: input0,input1,...    
  8.     list_add_tail(&dev->node, &input_dev_list);//input_dev_list is a global list! So every input_dev will be listed.
  9.     list_for_each_entry(handler, &input_handler_list, node)
  10.         input_attach_handler(dev, handler);//dev and handler, we fould it.
  11.     ...//同上    
  12.     return 0;
  13. }
        上面的代碼,對dev做進一步的設置,同時也將dev鏈接到全局鏈表中;但它最重要的功能還是體現在11、12行。
        到了這裏不得介紹下input_handler_list與input_handler,它是系統中所有handler掛載的鏈表的表頭,自定義的handler必須掛載到該鏈表纔有可能被系統所用(調用input_register_handler)。input_handler的作用上面簡單提了,定義如下

點擊(此處)摺疊或打開

  1. struct input_handler {
  2.     ...
  3.     void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);//important
  4.     bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
  5.     bool (*match)(struct input_handler *handler, struct input_dev *dev);
  6.     int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
  7.     void (*disconnect)(struct input_handle *handle);
  8.     void (*start)(struct input_handle *handle);
  9.     ...
  10.     const struct input_device_id *id_table;
  11.     struct list_head    h_list;//這是與input_handler相關聯的input_handle的鏈表的表頭
  12.     struct list_head    node;//鏈入全局鏈表   h_list node~是否注意到這與input_dev的最後兩個一模一樣呢,事實上他們名字與作用都一樣
  13. };
        完成了必要的背景介紹,繼續input_attach_handler,這個函數的邏輯與具體的handler是強相關的,下面就以input子系統默認的evdev_handler爲例進行分析。

點擊(此處)摺疊或打開

  1. static struct input_handler evdev_handler = {
  2.     .event        = evdev_event,
  3.     .connect    = evdev_connect,
  4.     .disconnect    = evdev_disconnect,
  5.     .fops        = &evdev_fops,
  6.     .minor        = EVDEV_MINOR_BASE,
  7.     .name        = "evdev",
  8.     .id_table    = evdev_ids,
  9. };

點擊(此處)摺疊或打開

  1. static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
  2. {
  3.     const struct input_device_id *id;
  4.     int error;
  5.     id = input_match_device(handler, dev);//判斷handler是否與dev match.通過handler的id_table、match等實現,比較簡單,不做展開。
  6.     if (!id)
  7.         return -ENODEV;
  8.     error = handler->connect(handler, dev, id);//這之前dev與handler還是彼此獨立的,connect直接產生我們關注的三方關係
  9.     if (error && error != -ENODEV)
  10.         pr_err("failed to attach handler %s to device %s, error: %d\n",
  11.                handler->name, kobject_name(&dev->dev.kobj), error);
  12.     return error;
  13. }
        看看evdev_handler的connect究竟爲這個三方關係做了什麼吧,

點擊(此處)摺疊或打開

  1. static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
  2.              const struct input_device_id *id)
  3. {
  4.     struct evdev *evdev;
  5.     ...
  6.     evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
  7.     if (!evdev)
  8.         return -ENOMEM;
  9.     ...
  10.     evdev->handle.dev = input_get_device(dev);//這裏的handle就是input_handle,它的dev成員指向input_dev
  11.     evdev->handle.name = dev_name(&evdev->dev);
  12.     evdev->handle.handler = handler;//它的handler成員指向input_handler
  13.     evdev->handle.private = evdev;
  14.     ...
  15.     error = input_register_handle(&evdev->handle);//10、12行完成了handle到dev和handler,這個函數完善了所有的三方關係
  16.     return 0;
  17.     ...//error handling
  18. }

點擊(此處)摺疊或打開

  1. struct input_handle {
  2.     ...
  3.     struct input_dev *dev;//上段代碼的第10行
  4.     struct input_handler *handler;//上段代碼的第12行
  5.     struct list_head    d_node;//鏈入input_dev的h_list代表的鏈表
  6.     struct list_head    h_node;//鏈入input_handler的h_list代表的鏈表
  7. };

點擊(此處)摺疊或打開

  1. int input_register_handle(struct input_handle *handle)
  2. {
  3.     struct input_handler *handler = handle->handler;
  4.     struct input_dev *dev = handle->dev;
  5.     ...
  6.     if (handler->filter)
  7.         list_add_rcu(&handle->d_node, &dev->h_list);//上段代碼的第5行的註釋
  8.     else
  9.         list_add_tail_rcu(&handle->d_node, &dev->h_list);
  10.     ...
  11.     list_add_tail_rcu(&handle->h_node, &handler->h_list);//上段代碼的第6行的註釋
  12.     ...
  13.     return 0;
  14. }
        至此,三方關係形成完畢。我們實現了最終的目的,通過input_dev,可以遍歷所有與它有關的input_handler;通過input_handler,也可以遍歷所有與它有關的input_dev。
        圖解如下:圖中單向箭頭表示指針,雙向箭頭表示list_head。可以看出,從任何一個雙向箭頭出發,通過handle的過度,完全實現了我們的最終目標。掌握了這點,再看input_report那些流程的時候就非常容易了,dev想要report數據的時候無非是調用了handler的event函數指針指向的函數,我們可以在這個函數裏定義任何想讓系統去做的任務,比如cpu調頻等,而不僅限於數據上報。熟悉面向對象編程的人可能想到了,其實這個設計運用了面向對象的observer設計模式。
 
        至此,本文主要內容完結。掌握了這些知識固然重要,但還需要簡單分析下這個三方關係的設計思路。
        從本質上講,input_dev與input_handler是一個多對多的關係,一個dev可以對應多個handler,一個handler也可以對應多個dev(參考上圖)。
       針對這種多對多的關係,也許有人會想,爲什麼不將input_handle的dnode,hnode分別內嵌到dev和handler內,這樣也可以節省空間;實際上,內嵌的方式最終實現的是混亂的一對多的關係,因爲指針的指向是唯一的,所以當兩個不同的dev有一個共同的handler的時候,兩個鏈表相交,那麼後註冊的dev會改變前一個dev的鏈表,導致混亂。
        實際上,input_handle可以拆成兩部分,dnode一部分,hnode一部分,dnode來表達一個dev可以對應多個handler,hnode來表達一個handler也可以對應多個dev。這兩部分獨立存在也一樣可以實現input子系統的功能,而且理解起來更加簡單;將他們合併起來節省了空間。
        希望本文能讓看了它的人,遇到多對多模型的類似問題時,能夠記起這個三方關係。
發佈了31 篇原創文章 · 獲贊 5 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章