一、input子系統(tǒng)介紹
1.1 系統(tǒng)介紹
本文是基于linux-2.6.32內(nèi)核進(jìn)行分析的,如果使用的是其他版本的內(nèi)核,其內(nèi)核調(diào)用的函數(shù)可能有所不同,但是其實(shí)現(xiàn)原理是相通的。
1.2 input子系統(tǒng)的引入
以前我們寫一些輸入設(shè)備(鍵盤、鼠標(biāo)等)的驅(qū)動(dòng)都是采用字符設(shè)備、混雜設(shè)備處理的。問題由此而來,Linux開源社區(qū)的大神們看到了這大量輸入設(shè)備如此分散不堪,有木有可以實(shí)現(xiàn)一種機(jī)制,可以對(duì)分散的、不同類別的輸入設(shè)備進(jìn)行統(tǒng)一的驅(qū)動(dòng),所以才出現(xiàn)了輸入子系統(tǒng)。
輸入設(shè)備(如按鍵、鍵盤,觸摸屏,鼠標(biāo)等)是典型的字符設(shè)備,其主設(shè)備號(hào)固定為13,其一般的工作機(jī)制是在底層在按鍵、觸摸、鼠標(biāo)點(diǎn)擊等動(dòng)作發(fā)生時(shí)產(chǎn)生一個(gè)中斷(或驅(qū)動(dòng)通過timer定時(shí)查詢),然后CPU通過SPI、IIC或者外部存儲(chǔ)器總線讀取鍵值、坐標(biāo)等數(shù)據(jù),放在一個(gè)緩沖區(qū),字符設(shè)備驅(qū)動(dòng)管理該緩沖區(qū),而驅(qū)動(dòng)的read()接口讓用戶可以讀取鍵值、坐標(biāo)等數(shù)據(jù),其過程如圖1.1所示。
在Linux中,輸入子系統(tǒng)是由輸入子系統(tǒng)設(shè)備驅(qū)動(dòng)層、輸入子系統(tǒng)核心層(input core)和輸入子系統(tǒng)事件處理層(Event handler)組成,如圖1.2所示。其中設(shè)備驅(qū)動(dòng)層提供對(duì)硬件各寄存器的讀寫訪問和將底層硬件對(duì)用戶輸入訪問的響應(yīng)轉(zhuǎn)換為標(biāo)準(zhǔn)的輸入事件,再通過核心層提交給事件處理層;而核心層對(duì)下提供了設(shè)備驅(qū)動(dòng)層的編程接口,對(duì)上又提供了事件處理層的編程接口;而事件處理層就為我們用戶空間的應(yīng)用程序提供了統(tǒng)一訪問設(shè)備的接口和驅(qū)動(dòng)層提交來的事件處理。所以這使得我們輸入設(shè)備的驅(qū)動(dòng)部分不在用戶關(guān)心對(duì)設(shè)備文件的操作,而是要關(guān)心對(duì)各硬件寄存器的操作和提交的輸入事件。
1.3 input子系統(tǒng)的優(yōu)點(diǎn)
輸入子系統(tǒng)的引入,也為我們帶來了許多的好處:
- 統(tǒng)一了物理形態(tài)各異的相似的輸入設(shè)備的處理功能。例如,各種鼠標(biāo),不論P(yáng)S/2、USB、還是藍(lán)牙,都被同樣處理。
- 提供了用于分發(fā)輸入報(bào)告給用戶應(yīng)用程序的簡(jiǎn)單的事件(event)接口。你的驅(qū)動(dòng)不必創(chuàng)建、管理/dev節(jié)點(diǎn)以及相關(guān)的訪問方法。因此它能夠很方便的調(diào)用輸入API以發(fā)送鼠標(biāo)移動(dòng)、鍵盤按鍵,或觸摸事件給用戶空間。X windows這樣的應(yīng)用程序能夠無縫地運(yùn)行于輸入子系統(tǒng)提供的event接口之上。
- 抽取出了輸入驅(qū)動(dòng)的通用部分,簡(jiǎn)化了驅(qū)動(dòng),并提供了一致性。例如,輸入子系統(tǒng)提供了一個(gè)底層驅(qū)動(dòng)(稱為serio)的集合,支持對(duì)串口和鍵盤控制器等硬件輸入的訪問。
注:更多詳細(xì)描述可參見《精通Linux設(shè)備驅(qū)動(dòng)程序開發(fā)》這本書。
更重要的是,input子系統(tǒng)的引入,使我們在具體的開發(fā)中可以在輸入硬件設(shè)備更換的情況下保持應(yīng)用不做任何修改。
1.4 input子系統(tǒng)與字符設(shè)備實(shí)現(xiàn)比較
在進(jìn)行字符設(shè)備驅(qū)動(dòng)程序開發(fā)的過程中,我們的實(shí)現(xiàn)步驟如下:
- 申請(qǐng)一個(gè)字符設(shè)備號(hào):可以自己指定,也可系統(tǒng)自動(dòng)分配;
- 構(gòu)造一個(gè)file_operations結(jié)構(gòu)體,其包含對(duì)設(shè)備的所有操作;
- 實(shí)現(xiàn)file_operations結(jié)構(gòu)體中的成員函數(shù);
- 將字符設(shè)備注冊(cè)進(jìn)系統(tǒng)中:register_chrdev();
- 創(chuàng)建設(shè)備類和設(shè)備節(jié)點(diǎn):class_create()、device_create();
- 告訴內(nèi)核入口與出口函數(shù):module_init()、module_exit();
輸入子系統(tǒng)與混雜設(shè)備驅(qū)動(dòng)一樣,也是一個(gè)典型的字符設(shè)備,那么其注冊(cè)的過程與字符設(shè)備驅(qū)動(dòng)一樣,也必須經(jīng)過上面的這些步驟。只是輸入子系統(tǒng)中的輸入設(shè)備一般只接收輸入設(shè)備的中斷和獲取輸入設(shè)備的數(shù)據(jù),而不輸出數(shù)據(jù)到輸入設(shè)備而已。
在輸入子系統(tǒng)中,將字符設(shè)備驅(qū)動(dòng)分為了三個(gè)部分:與硬件操作相關(guān)的設(shè)備驅(qū)動(dòng)層(由驅(qū)動(dòng)工程師實(shí)現(xiàn))、輸入子系統(tǒng)核心層(input core)和輸入子系統(tǒng)事件處理層(Event handler),其中后面兩個(gè)部分都已經(jīng)由系統(tǒng)幫我們實(shí)現(xiàn)了。其實(shí)后面兩個(gè)部分可以把它看作是一個(gè)整體,就是與硬件操作無關(guān)的軟件部分。那么我們實(shí)現(xiàn)input設(shè)備驅(qū)動(dòng)的過程為:
- 申請(qǐng)一個(gè)輸入設(shè)備結(jié)構(gòu)體:input_allocate_device();
- 設(shè)置輸入設(shè)備支持的事務(wù)類型:set_bit(xxx,devp->evbit);
- 設(shè)置輸入設(shè)備支持的具體哪些事務(wù):set_bit(xxx,devp->xxxbit);
- 注冊(cè)輸入設(shè)備到輸入子系統(tǒng):input_register_device(devp);
- 實(shí)現(xiàn)具體的硬件相關(guān)操作,如注冊(cè)中斷等,并在中斷處理函數(shù)中通知事務(wù)已發(fā)生:input_event()、input_sync();
也就是說:無論輸入子系統(tǒng)多么強(qiáng)大、封裝的多么的好,與硬件相關(guān)的操作還是得我們親自實(shí)現(xiàn)。
1.5 input子系統(tǒng)與字符設(shè)備與應(yīng)用層數(shù)據(jù)交互比較
編寫過字符設(shè)備驅(qū)動(dòng)的人都知道,應(yīng)用程序與驅(qū)動(dòng)之間實(shí)現(xiàn)數(shù)據(jù)交互就是通過應(yīng)用API的read()、write()調(diào)用,從而產(chǎn)生一個(gè)SWI軟件中斷,然后通過主設(shè)備號(hào)找到對(duì)應(yīng)的struct cdev結(jié)構(gòu)體實(shí)體,從而找到具體硬件設(shè)備的struct file_operations結(jié)構(gòu)體,然后具體調(diào)用底層的drv_read()、drv_write(),我們就是在具體的drv_read()和drv_write()中實(shí)現(xiàn)對(duì)硬件的操作的,其過程如下:
read()—>swi_read()—>drv_read()—>硬件操作
那么對(duì)應(yīng)到輸入子系統(tǒng)呢?前面已經(jīng)說了,輸入子系統(tǒng)也是字符設(shè)備,那么它也必須經(jīng)歷上面的這些步驟,只是中間穿插了幾個(gè)查找具體輸入設(shè)備的過程(畢竟將所有的輸入設(shè)備都加入到輸入子系統(tǒng),就不止一個(gè)設(shè)備了)而已。那么是如何穿插的呢:
首先在input.c(輸入子系統(tǒng)的核心)文件的打開函數(shù)中找到具體的input_handler,然后取出具體input_handler中的fops(也即struct file_operations結(jié)構(gòu)體)填充struct file中的f_op成員,那么之后應(yīng)用調(diào)用read()、write()函數(shù)就是調(diào)用具體input_handler指向的struct file_operations結(jié)構(gòu)體中的成員了;最后再調(diào)用fops中的open()函數(shù)打開具體的函數(shù):
struct input_handler *handler;
handler = input_table[iminor(inode) >> 5];
old_fops = file->f_op;
file->f_op = new_fops;
err = new_fops->open(inode, file);
這里說明一下:input.c是input子系統(tǒng)的核心,內(nèi)核已經(jīng)實(shí)現(xiàn),各種input_handler(包含open、release、read、write、ioctl、fasync、poll等,即硬件處理函數(shù))也由系統(tǒng)抽象出來幫我們實(shí)現(xiàn)了,后面會(huì)講解其具體實(shí)現(xiàn)過程。
通過前面的介紹,不太理解也沒有關(guān)系。你只需要記住,其實(shí)輸入子系統(tǒng)就是一個(gè)典型的字符設(shè)備,它也逃不過字符設(shè)備的框架,其應(yīng)用與驅(qū)動(dòng)交互的流程也和字符設(shè)備驅(qū)動(dòng)一樣,沒有什么不同就是了(要從心里小瞧它)。
要想搞明白輸入子系統(tǒng)的框架,只需要弄明白應(yīng)用程序是如何與具體的硬件設(shè)備驅(qū)動(dòng)進(jìn)行交互的就行了。而這個(gè)過程如下其實(shí)就是主設(shè)備號(hào)13的字符設(shè)備、input_handler、input_handle和input_dev幾個(gè)的關(guān)系:
- Linux系統(tǒng)啟動(dòng)時(shí)注冊(cè)輸入子系統(tǒng)(注冊(cè)主設(shè)備號(hào)為13的字符設(shè)備);
- 應(yīng)用程序調(diào)用open(),對(duì)應(yīng)調(diào)用輸入子系統(tǒng)的input_open_file();
- input_open_file()找到對(duì)應(yīng)的input_handler,并調(diào)用其中的open();
- 應(yīng)用程序調(diào)用read()函數(shù),對(duì)應(yīng)調(diào)用open()中找到的input_handler中的read()函數(shù),阻塞;
- 驅(qū)動(dòng)收到硬件訪問需求,進(jìn)入中斷處理函數(shù),對(duì)應(yīng)到input_dev;
- 驅(qū)動(dòng)調(diào)用input_event()上報(bào)事件,上報(bào)過程為:通過input_dev找到input_handle,再通過input_handle找到匹配的input_handler,然后調(diào)用該input_handler的event()函數(shù),該函數(shù)即是喚醒對(duì)應(yīng)read()、write()函數(shù)的實(shí)現(xiàn);
詳細(xì)過程如下三條線路所示:
- open()—>input_open_file()—>input_handler->fops->open()
- read()/write()/ioctl()—>input_handler->fops->read()/write()/ioctl()
- 硬件—>input_dev的中斷處理程序—>input_event()—>input_handle->input_handler->event()
注意:->是指針;—>是下一步調(diào)用。
從應(yīng)用到底層的匹配過程是通過input_handler,具體驅(qū)動(dòng)中是通過靜態(tài)全局指針數(shù)組變量input_table[]實(shí)現(xiàn)的;而硬件到應(yīng)用程序的匹配過程是通過input_handle結(jié)構(gòu)體找到對(duì)應(yīng)的input_handler,從而實(shí)現(xiàn)數(shù)據(jù)傳輸?shù)模唧w到代碼就是通過input_handler->connect()函數(shù)將input_dev、input_handler和input_handle三者進(jìn)行綁定的,三者綁定的關(guān)系如圖1.3所示。
二、input子系統(tǒng)實(shí)現(xiàn)
本章節(jié)將詳細(xì)講解input子系統(tǒng)的框架,也就是input子系統(tǒng)中如何實(shí)現(xiàn)應(yīng)用層數(shù)據(jù)與底層硬件之間的數(shù)據(jù)交互、底層硬件(input_dev)如何與系統(tǒng)實(shí)現(xiàn)的驅(qū)動(dòng)(input_handler)關(guān)聯(lián)等。
2.1 input子系統(tǒng)框架
正如前面的介紹,input子系統(tǒng)將所有的輸入設(shè)備統(tǒng)稱為像鼠標(biāo)輸入、鍵盤輸入、joydev輸入等,將輸入的數(shù)據(jù)封裝成統(tǒng)一的事務(wù)格式(struct input_event)上傳到應(yīng)用,而將具體的硬件設(shè)備分離出來(這才是我們要做的事),如圖2.1所示。
在如上的系統(tǒng)分層結(jié)構(gòu)下,我們的應(yīng)用程序就可以不用關(guān)心獲取到的數(shù)據(jù)是來源于SPI的鍵盤、還是IIC的鍵盤,它只需要關(guān)心獲取到數(shù)據(jù)的具體含義就行了,這樣就保證了更換不同的硬件設(shè)備,而不用修改一行應(yīng)用程序代碼,如圖2.2所示。