標籤:linux輸入子系統 輸入子系統 input子系統
在Linux下的輸入裝置鍵盤、觸控螢幕、滑鼠等都可以用輸入子系統來實現驅動。輸入子系統分為三層,核心層和裝置驅動層,事件層。核心層和事件層由Linux輸入子系統本身實現,裝置驅動層由我們實現。我們在裝置驅動層將輸入事件上報給核心層input.c,核心層找到匹配的事件層,將事件交給事件層處理,事件層處理完後傳遞到使用者空間。
我們最終要搞清楚的是在使用者空間調用open和read最終在核心中是怎樣處理的,向核心上報的事件又是誰處理的,處理完後是怎樣傳遞到使用者空間的?
上面兩個圖是輸入子系統的架構。
下面以按鍵驅動為例分析輸入子系統的工作流程。
裝置驅動層:
在裝置驅動層的init入口函數中調用input_allocate_device(),分配返回一個input_dev結構體,填充該結構體,調用input_register_device(button_dev),註冊裝置。跟蹤input_register_device如下:
list_add_tail(&dev->node, &input_dev_list);將input_dev結構體放進input_dev_list鏈表中
list_for_each_entry(handler, &input_handler_list, node)遍曆input_handler_list鏈表中的每一個handler
input_attach_handler(dev, handler);將input_dev和handler比較
input_match_device(handler->id_table, dev);<*input.c*>
handler->connect(handler, dev, id);<*input.c*>比較的方式是通過對比handler的id_table和input_dev,若找 到匹配的handler,則調用handler的connect方法。
在evdev_connect(struct input_handler *handler, struct input_dev *dev,const struct input_device_id *id)中調用了input_register_handle,調用handle之前填充handle中成員dev和handler結構體。
evdev->handle.dev = input_get_device(dev);
evdev->handle.handler = handler;
在input_register_handle中list_add_tail_rcu(&handle->d_node, &dev->h_list)list_add_tail(&handle->h_node, &h andler->h_list)兩個函數將handle加入進handler和dev的h_list鏈表中。通過dev->h_list可以找到handle,通過handle找到handle.handler,同理,通過handler->h_list可以找到handle,再找到handle.dev。
關鍵的問題來了?handler到底是什嗎?由誰註冊的?handler是事件層調用核心層的函數註冊的。
事件層:(evdev.c,keyboard.c,ts.c)
static struct input_handler evdev_handler = {//handler結構體
.event = evdev_event,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.fops = &evdev_fops,
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
.id_table = evdev_ids,
};
在evdev_init(void)入口函數中調用input_register_handler(&evdev_handler)來註冊handler,註冊的handler會放入input_table[ ]數組中。
核心層:(input.c)
核心層的入口函數input_init(void)註冊裝置input。
當在應用程式層調用open時會在核心中調用input_open_file(struct inode *inode, struct file *file),根據handler =input_table[iminor(inode) >> 5];根據開啟的檔案的次裝置號從數組input_table中得到登入的對應的handler。
new_fops = fops_get(handler->fops)從handler中得到新的fop。
file->f_op = new_fops;
err = new_fops->open(inode, file);open最終調用的是handler->fops->open。原來的裝置驅動的open等方法是自己寫的,現在輸入子系統中的事件層幫我們寫好了open等裝置方法。同理在應用程式層調用read會呼叫事件層中的read,即handler->fops->read,即evdev_fops.read,在read中會阻塞。直到在裝置驅動層中過input_report_key上報事件:
input_event(dev, EV_KEY, code, !!value);
input_handle_event(dev, type, code, value);
input_pass_event(dev, type, code, value);
handle->handler->event(handle,type, code, value);調用handler中的event事件方法,處理完上報的事件後,喚醒休眠的read,再read出事件的處理結果。
總結:
在使用者空間調用open將最終呼叫事件層中handler的fops的open裝置方法,具體是匹配到evdev.c還是keyboard.c的handler要根據裝置驅動init入口函數中填充的input_dev結構體的id_table。使用者空間調用read將調用handler的read。在裝置驅動中通過input_event上報事件到核心層,最終調用對應的handler的event方法來處理事件,處理完後通過read傳遞到使用者空間。這樣就搞清楚了open是誰調用的?read是誰調用的?