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等等)。
點擊(此處)摺疊或打開
- struct input_dev *input_allocate_device(void)
- {
- struct input_dev *dev;
- dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);
- if (dev) {
- ...//I deleted many lines here.
- INIT_LIST_HEAD(&dev->h_list);//this is the head of the list which consists of related input_handles.
- ...
-
}
-
return dev;
- }
點擊(此處)摺疊或打開
- struct input_dev {
- ...//detailed info of this device
- struct list_head h_list;//這是與input_dev相關聯的input_handle的鏈表的表頭
- struct list_head node;//鏈入全局鏈表
- };
點擊(此處)摺疊或打開
- int input_register_device(struct input_dev *dev)
- {
- static atomic_t input_no = ATOMIC_INIT(0);
- struct input_handler *handler;
- ...//此次省略一千行O(∩_∩)O~
- dev_set_name(&dev->dev, "input%ld",
- (unsigned long) atomic_inc_return(&input_no) - 1);//set the dev name here: input0,input1,...
- list_add_tail(&dev->node, &input_dev_list);//input_dev_list is a global list! So every input_dev will be listed.
- list_for_each_entry(handler, &input_handler_list, node)
- input_attach_handler(dev, handler);//dev and handler, we fould it.
- ...//同上
- return 0;
- }
到了這裏不得介紹下input_handler_list與input_handler,它是系統中所有handler掛載的鏈表的表頭,自定義的handler必須掛載到該鏈表纔有可能被系統所用(調用input_register_handler)。input_handler的作用上面簡單提了,定義如下
點擊(此處)摺疊或打開
- struct input_handler {
- ...
- void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);//important
- bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
- bool (*match)(struct input_handler *handler, struct input_dev *dev);
- int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
- void (*disconnect)(struct input_handle *handle);
- void (*start)(struct input_handle *handle);
- ...
- const struct input_device_id *id_table;
- struct list_head h_list;//這是與input_handler相關聯的input_handle的鏈表的表頭
- struct list_head node;//鏈入全局鏈表 h_list node~是否注意到這與input_dev的最後兩個一模一樣呢,事實上他們名字與作用都一樣
- };
點擊(此處)摺疊或打開
- static struct input_handler evdev_handler = {
- .event = evdev_event,
- .connect = evdev_connect,
- .disconnect = evdev_disconnect,
- .fops = &evdev_fops,
- .minor = EVDEV_MINOR_BASE,
- .name = "evdev",
- .id_table = evdev_ids,
- };
點擊(此處)摺疊或打開
- static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
- {
- const struct input_device_id *id;
- int error;
- id = input_match_device(handler, dev);//判斷handler是否與dev match.通過handler的id_table、match等實現,比較簡單,不做展開。
- if (!id)
- return -ENODEV;
- error = handler->connect(handler, dev, id);//這之前dev與handler還是彼此獨立的,connect直接產生我們關注的三方關係
- if (error && error != -ENODEV)
- pr_err("failed to attach handler %s to device %s, error: %d\n",
- handler->name, kobject_name(&dev->dev.kobj), error);
- return error;
- }
點擊(此處)摺疊或打開
- static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
- const struct input_device_id *id)
- {
- struct evdev *evdev;
- ...
- evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
- if (!evdev)
- return -ENOMEM;
- ...
- evdev->handle.dev = input_get_device(dev);//這裏的handle就是input_handle,它的dev成員指向input_dev
- evdev->handle.name = dev_name(&evdev->dev);
- evdev->handle.handler = handler;//它的handler成員指向input_handler
- evdev->handle.private = evdev;
- ...
- error = input_register_handle(&evdev->handle);//10、12行完成了handle到dev和handler,這個函數完善了所有的三方關係
- return 0;
- ...//error handling
- }
點擊(此處)摺疊或打開
- struct input_handle {
- ...
- struct input_dev *dev;//上段代碼的第10行
- struct input_handler *handler;//上段代碼的第12行
- struct list_head d_node;//鏈入input_dev的h_list代表的鏈表
- struct list_head h_node;//鏈入input_handler的h_list代表的鏈表
- };
點擊(此處)摺疊或打開
- int input_register_handle(struct input_handle *handle)
- {
- struct input_handler *handler = handle->handler;
- struct input_dev *dev = handle->dev;
- ...
- if (handler->filter)
- list_add_rcu(&handle->d_node, &dev->h_list);//上段代碼的第5行的註釋
- else
- list_add_tail_rcu(&handle->d_node, &dev->h_list);
- ...
- list_add_tail_rcu(&handle->h_node, &handler->h_list);//上段代碼的第6行的註釋
- ...
-
return 0;
- }
圖解如下:圖中單向箭頭表示指針,雙向箭頭表示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子系統的功能,而且理解起來更加簡單;將他們合併起來節省了空間。
希望本文能讓看了它的人,遇到多對多模型的類似問題時,能夠記起這個三方關係。