# Linux輸入子系統Input subsystem學習筆記 Linux系統中有許多輸入設備:鍵盤、滑鼠、觸摸屏幕…等,而若是針對每一個輸入設備撰寫相對應的驅動程序,則應用層就必須知道每個設備節點的名稱,根據不同的設備開啟不同的設備節點,只要其中一個設備修改,就必須修改應用層,對整個系統產生不小的影響。 因此Linux系統發展出一套能夠兼容所有輸入設備的系統:輸入子系統(Input subsystem),將所有輸入設備進行統一管理,抽象出三部分:軟件處理、硬件處理、連接軟件與硬件處理。由此框架得知,若新增一輸入設備,程序員只需要根據輸入子系統的框架撰寫硬件處理驅動即可,應用程序仍可以依照輸入子系統定義的API去操作硬件。 ![](https://static.coderbridge.com/img/chiyaonumber7/f8f976cf2b56456e95830b94d4ecf150.png) # Linux 4.19.x 內核解析 drivers/input/input.c: ## 1. 入口函數:input_init ## 2. 註冊 加載evdev.c時入口函數會註冊handler,加載gpio_keys.c入口函數會註冊device input_register_device ```c= int input_register_device(struct input_dev *dev) { list_add_tail(&dev->node, &input_dev_list); // 放入鏈表 list_for_each_entry(handler, &input_handler_list, node) // 對於每個handler調用input_attach_handler input_attach_handler(dev, handler); } ``` input_register_handler ```c= int input_register_handler(struct input_handler *handler) { list_add_tail(&handler->node, &input_handler_list); // 放入鏈表 list_for_each_entry(dev, &input_dev_list, node) // 對於每個dev調用input_attach_handler input_attach_handler(dev, handler); } ``` 在註冊device或handler時,會將input_dev_list列表中的每個dev與input_handler_list裡handler裡進行兩兩比對,用handler裡的id_table來判斷是否支持此input_dev,若能夠支持,則會調用handler裡的connect函數。 ![](https://static.coderbridge.com/img/chiyaonumber7/2f2759a2fcf44d879255ffc96c9ad98b.png) ## 3. 配對成功調用connect函數,底下以evdev為例 ```c= static int evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id) { evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); // 分配一個input_handle evdev->handle.dev = input_get_device(dev); // input_handle裡的dev指向左邊的dev evdev->handle.handler = handler; // input_handle裡的handler指向右邊的handler input_register_handle(&evdev->handle); //註冊handle } ``` input_register_handle函數 ```c= int input_register_handle(struct input_handle *handle) { struct input_handler *handler = handle->handler; struct input_dev *dev = handle->dev; list_add_tail_rcu(&handle->d_node, &dev->h_list); // dev的h_list指向handle裡的d_node list_add_tail_rcu(&handle->h_node, &handler->h_list); // handler的h_list指向handle裡的h_node cdev_init(&evdev->cdev, &evdev_fops); // 初始化字符設備 } ``` ![](https://static.coderbridge.com/img/chiyaonumber7/9a0316b2e1a94da0b77de1850f72b1aa.png) 其中evdev_fops的結構如下 ```c= static const struct file_operations evdev_fops = { .owner = THIS_MODULE, .read = evdev_read, .write = evdev_write, .poll = evdev_poll, .open = evdev_open, .release = evdev_release, .unlocked_ioctl = evdev_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = evdev_ioctl_compat, #endif .fasync = evdev_fasync, .llseek = no_llseek, }; ``` ## 4. APP使用read函數 最終會調用evdev_read ```c= evdev_read { //若無數據且選擇非阻塞方式打開,則直接回覆錯誤 if (client->packet_head == client->tail && (file->f_flags & O_NONBLOCK)) return -EAGAIN; //若無數據且沒有選擇非阻塞方式,則進入休眠 if (!(file->f_flags & O_NONBLOCK)) { wait_event_interruptible(evdev->wait, client->packet_head != client->tail || !evdev->exist || client->revoked); } } ``` ## 5. 進入休眠,誰來喚醒? ```c= evdev_events evdev_pass_values wake_up_interruptible(&evdev->wait); ``` ## 6. evdev_events被誰調用? 猜測應由硬件input_dev的中斷接收到資料後再調用evdev_events,底下以gpio_keys.c為例 gpio_keys_gpio_isr ```c= input_report_key(bdata->input, button->code, 1); input_event(dev, EV_KEY, code, !!value); ``` gpio_keys_irq_isr ```c= input_event(input, EV_KEY, *bdata->code, 1); input_sync(input); ``` 中斷處理函式為gpio_keys_gpio_isr或是gpio_keys_irq_isr,但皆會呼叫到input_event ```c= void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { input_handle_event(dev, type, code, value); } ``` 進入input_handle_event ```c= static void input_handle_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { input_pass_values(dev, dev->vals, dev->num_vals); } ``` 進入input_pass_values ```c= static void input_pass_values(struct input_dev *dev, struct input_value *vals, unsigned int count) { handle = rcu_dereference(dev->grab); if (handle) { count = input_to_handler(handle, vals, count); } } ``` 進入input_to_handler ```c= static unsigned int input_to_handler(struct input_handle *handle, struct input_value *vals, unsigned int count) { if (handler->events) handler->events(handle, vals, count); //由此調用handler的events函數 } ``` # 應用層 架構圖如下 ![](https://static.coderbridge.com/img/chiyaonumber7/7d9dbfb030134bd2a44e02fe46ec3f6f.png) ----- 經過上述分析初步瞭解linux輸入子系統的架構,接下來要學習 1. 怎麼寫符合输入子系统框架的驅動程序呢? 2. 應用層要如何使用輸入子系統得到輸入數據呢?