輸入子系統筆記!
學完第一期基礎驅動入門後,接著開始講input子系統。看著視頻,心裏有個大概對輸入子系統的認識,但是說不出來。百度一下,摘抄吧。
https://blog.csdn.net/zouleideboke/article/details/70650001
1.什麽是子系統?
內核是操作系統的核心。Linux內核提供很多基本功能,如虛擬內存、多任務、共享庫、需求加載、共享寫時拷貝(Copy-On-Write)以及網絡功能等。增加各種不同功能導致內核代碼不斷增加。
Linux內核把不同功能分成不同的子系統,通過一種整體的結構把各種功能集合在一起,提高了工作效率。同時還提供動態加載模塊的方式,為動態修改內核功能提供了靈活性。
2.linux 驅動子系統
linux內核中自帶了很多的驅動子系統,其中比較典型的有:input子系統,led子系統,framebuffer子系統(LCD),I2c子系統等,這些子系統它是通過一層一層的函數傳遞與封裝,它實現了設備驅動的註冊,以及定義了file_operations結構體裏面的各種函數操作等,不需要在單獨的設備驅動代碼中進行註冊,定義,直接調用相應的的子系統即可,
3.linux input子系統
以前學了單獨的按鍵設備驅動以及led驅動,實際上,在linux中實現這些設備驅動,有一種更為推薦的方法,就是input輸入子系統。平常我們的按鍵,觸摸屏,鼠標等輸入型設備都可以利用input接口
我對input 子系統的理解是:其實input 子系統是對linux輸入設備驅動進行了高度抽象最終分成了三層:包括input設備驅動層,核心層,事件處理層,也就是說我們之前移植的按鍵,usb,觸摸屏驅動代碼也只是子系統的一部分,起初我們自己編寫的那些驅動代碼都是分散的,按鍵是按鍵,led是led,都沒有對這些不同類別的輸入設備驅動統一起來,也就是說input 子系統這種機制的出現,最大的優點我覺得就是為內核大大的簡化了驅動程序!!!這個input子系統最神秘之處就是它的核心層部分,這個核心層做了什麽呢?它在內核中是怎麽定義的?
2.linux輸入子系統原理
linux輸入子系統(linux input subsystem)從上到下由三層實現,分別為:輸入子系統事件處理層(EventHandler)、輸入子系統核心層(InputCore)和輸入子系統設備驅動層。
設備驅動層:主要實現對硬件設備的讀寫訪問,中斷設置,並把硬件產生的事件轉換為核心層定義的規範提交給事件處理層。
核心層: 為設備驅動層提供了規範和接口。設備驅動層只要關心如何驅動硬件並獲得硬件數據(例如按下的按鍵數據),然後調用核心層提供的接口,核心層會自動把數據提交給事件處理層。子系統核心層是鏈接其他兩個層之間的紐帶與橋梁,向下提供驅動層的接口,向上提供事件處理層的接口。
事件處理層:事件處理層負責與用戶程序打交道,將硬件驅動層傳來的事件報告給用戶程序。
對於linux輸入子系統的框架結構圖:
分析:/dev/input目錄下顯示的是已經註冊在內核中的設備編程接口,用戶通過open這些設備文件來打開不同的輸入設備進行硬件操作。
事件處理層為不同硬件類型提供了用戶訪問及處理接口。例如當我們打開設備/dev/input/mice時,會調用到事件處理層的Mouse Handler來處理輸入事件,這也使得設備驅動層無需關心設備文件的操作,因為Mouse Handler已經有了對應事件處理的方法。
輸入子系統由內核代碼drivers/input/input.c構成,它的存在屏蔽了用戶到設備驅動的交互細節,為設備驅動層和事件處理層提供了相互通信的統一界面。
linux輸入子系統的事件處理機制:
分析:由上圖可知輸入子系統核心層提供的支持以及如何上報事件到input event drivers。
作為輸入設備的驅動開發者,需要做以下幾步:
1. 在驅動加載模塊中,設置你的input設備支持的事件類型。(事件類型有:EV_SYN 0x00 同步事件;
EV_KEY 0x01 按鍵事件;
EV_LED 0x11 按鍵/設備燈等)
2. 註冊中斷處理函數,例如鍵盤設備需要編寫按鍵的擡起、放下,觸摸屏設備需要編寫按下、擡起、絕對移動,鼠標設備需要編寫單擊、擡起、相對移動,並且需要在必要的時候提交硬件數據(鍵值/坐標/狀態等等)
3.將輸入設備註冊到輸入子系統中
3.input 核心層
input核心層位於/drivers/input/input.c
3.1 入口和出口
1 static const struct file_operations input_fops = { 2 .owner = THIS_MODULE, 3 .open = input_open_file, 4 }; 5 static int __init input_init(void) 6 { 7 int err; 8 err = class_register(&input_class);//註冊input類,這裏就是生成了sys/class/input目錄,在該目錄下會看到驅動鏈接文件 9 if (err) { 10 printk(KERN_ERR "input: unable to register input_dev class\n"); 11 return err; 12 } 13 err = input_proc_init();//在proc目錄下建立input子系統目錄及交互文件,即/proc/bus/input目錄下的devices文件和handlers文件 14 if (err) 15 goto fail1; 16 17 err = register_chrdev(INPUT_MAJOR, "input", &input_fops);//註冊一個主設備號為INPUT_MAJOR(13),次設備號為0~255,文件操作符為input_fops的字符設備 18 if (err) { 19 printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR); 20 goto fail2; 21 } 22 return 0; 23 fail2: input_proc_exit(); 24 fail1: class_unregister(&input_class); 25 return err; 26 } 27 static void __exit input_exit(void) 28 { 29 input_proc_exit(); 30 unregister_chrdev(INPUT_MAJOR, "input"); 31 class_unregister(&input_class); 32 } 33 subsys_initcall(input_init); 34 module_exit(input_exit);
這個初始化函數中,有一個註冊字符設備函數register_chrdev(第17行),它用到了input_fops,這個結構體為struct file_operations,但這裏他只有一個input_open_file——open成員。
3.2 input_open_file函數
1 static int input_open_file(struct inode *inode, struct file *file) 2 { 3 struct input_handler *handler = input_table[iminor(inode) >> 5];//根據所打開的驅動的次設備號inode,查找對應input_handler 4 const struct file_operations *old_fops, *new_fops = NULL; 5 int err; 6 /* No load-on-demand here? */ 7 if (!handler || !(new_fops = fops_get(handler->fops)))//new_fops賦值為該驅動的file_operations 8 return -ENODEV; ...
17 old_fops = file->f_op; 18 file->f_op = new_fops;//所打開的文件的f_op等於新的new_fops 19 err = new_fops->open(inode, file);//打開新的驅動 ...25 return err; 26 }
分析:根據inode,在input_table數組中查找的對應的input_handler,然後從中提取出.fops成員復制給new_fops,並調用其成員下的.open函數。old_fops是為了防止出錯做備份用的。
3.3 input_table的分析,input_table由誰構造?
首先input_table在Input.c中定義。
1 static struct input_handler *input_table[8];
2 構造
Evbug.c/Evdev.c/Joydev.c/Keyboard.c/Mousedev.c....的入口函數
input_register_handler //向上註冊
->input_table[handler->minor >> 5] = handler;
//以Evdev.c為例 static int __init evdev_init(void) { return input_register_handler(&evdev_handler); } static struct input_handler evdev_handler = { .event = evdev_event, .connect = evdev_connect, .disconnect = evdev_disconnect, .fops = &evdev_fops,//這個結構體裏有各種讀寫操作函數 .minor = EVDEV_MINOR_BASE,//次設備號 宏定義64,handler->minor >> 5後,就是2 所以它在第三項 .name = "evdev", .id_table = evdev_ids,//用於比較是否要打開的設備是否匹配 }; int input_register_handler(struct input_handler *handler) { ... if (handler->fops != NULL) { if (input_table[handler->minor >> 5]) return -EBUSY; input_table[handler->minor >> 5] = handler;//這裏evdev_handler添加到input_table[2]中 } ... return 0; }
分析:input_table由input_register_handler函數構造。
input_register_handler
input_table[handler->minor >> 5] = handler;//放入數組
list_add_tail(&handler->node, &input_handler_list);//放入鏈表
list_for_each_entry(dev, &input_dev_list, node)//對於每個input_dev,調用input_attach_handler
input_attach_handler(dev, handler);//根據input_handler的id_table判斷是否支持這個input_dev
以上Evbug.c/Evdev.c/Joydev.c/Keyboard.c/Mousedev.c....向input.c註冊handler,為輸入設備實現一個接口。
查看evdev_handler如下
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,//表示這個接口能夠支持那些輸入設備,如果能夠支持就調用.connect函數, };
3.4註冊輸入設備——input_register_device
input_register_device
list_add_tail(&dev->node, &input_dev_list);//放入鏈表
list_for_each_entry(handler, &input_handler_list, node)//對於每一個input_handler,都調用input_attach_handler函數
input_attach_handler(dev, handler);//根據input_handler的id_table判斷是否支持這個input_dev
3.5input_attach_handler函數作用
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler) { ... id = input_match_device(handler->id_table, dev);//根據id_table和dev判斷是否匹配 if (!id) return -ENODEV; error = handler->connect(handler, dev, id);//如果匹配,執行.conect函數 ... return error; }
總結:註冊input_dev或input_handler時,會兩兩比較,根據input_handler的id_table判斷這個input_handler能否支持這個input_dev,如果能夠支持,則調用input_handler的connect函數建立連接。
輸入子系統筆記!