一、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)的引入
以前我們寫(xiě)一些輸入設(shè)備(鍵盤(pán)砸西、鼠標(biāo)等)的驅(qū)動(dòng)都是采用字符設(shè)備、混雜設(shè)備處理的址儒。問(wèn)題由此而來(lái)芹枷,Linux開(kāi)源社區(qū)的大神們看到了這大量輸入設(shè)備如此分散不堪,有木有可以實(shí)現(xiàn)一種機(jī)制莲趣,可以對(duì)分散的鸳慈、不同類(lèi)別的輸入設(shè)備進(jìn)行統(tǒng)一的驅(qū)動(dòng),所以才出現(xiàn)了輸入子系統(tǒng)喧伞。
輸入設(shè)備(如按鍵走芋、鍵盤(pán)绩郎,觸摸屏,鼠標(biāo)等)是典型的字符設(shè)備翁逞,其主設(shè)備號(hào)固定為13肋杖,其一般的工作機(jī)制是在底層在按鍵、觸摸挖函、鼠標(biāo)點(diǎn)擊等動(dòng)作發(fā)生時(shí)產(chǎn)生一個(gè)中斷(或驅(qū)動(dòng)通過(guò)timer定時(shí)查詢)状植,然后CPU通過(guò)SPI、IIC或者外部存儲(chǔ)器總線讀取鍵值怨喘、坐標(biāo)等數(shù)據(jù)津畸,放在一個(gè)緩沖區(qū),字符設(shè)備驅(qū)動(dòng)管理該緩沖區(qū)必怜,而驅(qū)動(dòng)的read()接口讓用戶可以讀取鍵值肉拓、坐標(biāo)等數(shù)據(jù),其過(guò)程如圖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ì)硬件各寄存器的讀寫(xiě)訪問(wèn)和將底層硬件對(duì)用戶輸入訪問(wèn)的響應(yīng)轉(zhuǎn)換為標(biāo)準(zhǔn)的輸入事件,再通過(guò)核心層提交給事件處理層胧后;而核心層對(duì)下提供了設(shè)備驅(qū)動(dòng)層的編程接口芋浮,對(duì)上又提供了事件處理層的編程接口;而事件處理層就為我們用戶空間的應(yīng)用程序提供了統(tǒng)一訪問(wèn)設(shè)備的接口和驅(qū)動(dòng)層提交來(lái)的事件處理壳快。所以這使得我們輸入設(shè)備的驅(qū)動(dòng)部分不在用戶關(guān)心對(duì)設(shè)備文件的操作纸巷,而是要關(guān)心對(duì)各硬件寄存器的操作和提交的輸入事件。
1.3 input子系統(tǒng)的優(yōu)點(diǎn)
輸入子系統(tǒng)的引入眶痰,也為我們帶來(lái)了許多的好處:
- 統(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)的訪問(wèn)方法修肠。因此它能夠很方便的調(diào)用輸入API以發(fā)送鼠標(biāo)移動(dòng)、鍵盤(pán)按鍵户盯,或觸摸事件給用戶空間嵌施。X windows這樣的應(yīng)用程序能夠無(wú)縫地運(yùn)行于輸入子系統(tǒng)提供的event接口之上饲化。
- 抽取出了輸入驅(qū)動(dòng)的通用部分,簡(jiǎn)化了驅(qū)動(dòng)艰管,并提供了一致性滓侍。例如,輸入子系統(tǒng)提供了一個(gè)底層驅(qū)動(dòng)(稱為serio)的集合牲芋,支持對(duì)串口和鍵盤(pán)控制器等硬件輸入的訪問(wèn)。
注:更多詳細(xì)描述可參見(jiàn)《精通Linux設(shè)備驅(qū)動(dòng)程序開(kāi)發(fā)》這本書(shū)捺球。
更重要的是缸浦,input子系統(tǒng)的引入,使我們在具體的開(kāi)發(fā)中可以在輸入硬件設(shè)備更換的情況下保持應(yīng)用不做任何修改氮兵。
1.4 input子系統(tǒng)與字符設(shè)備實(shí)現(xiàn)比較
在進(jìn)行字符設(shè)備驅(qū)動(dòng)程序開(kāi)發(fā)的過(guò)程中裂逐,我們的實(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è)備類(lèi)和設(shè)備節(jié)點(diǎn):class_create()、device_create()疼进;
- 告訴內(nèi)核入口與出口函數(shù):module_init()薪缆、module_exit();
輸入子系統(tǒng)與混雜設(shè)備驅(qū)動(dòng)一樣伞广,也是一個(gè)典型的字符設(shè)備拣帽,那么其注冊(cè)的過(guò)程與字符設(shè)備驅(qū)動(dòng)一樣,也必須經(jīng)過(guò)上面的這些步驟嚼锄。只是輸入子系統(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è)整體既们,就是與硬件操作無(wú)關(guān)的軟件部分。那么我們實(shí)現(xiàn)input設(shè)備驅(qū)動(dòng)的過(guò)程為:
- 申請(qǐng)一個(gè)輸入設(shè)備結(jié)構(gòu)體:input_allocate_device()正什;
- 設(shè)置輸入設(shè)備支持的事務(wù)類(lèi)型: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()盾致;
也就是說(shuō):無(wú)論輸入子系統(tǒng)多么強(qiáng)大、封裝的多么的好荣暮,與硬件相關(guān)的操作還是得我們親自實(shí)現(xiàn)庭惜。
1.5 input子系統(tǒng)與字符設(shè)備與應(yīng)用層數(shù)據(jù)交互比較
編寫(xiě)過(guò)字符設(shè)備驅(qū)動(dòng)的人都知道,應(yīng)用程序與驅(qū)動(dòng)之間實(shí)現(xiàn)數(shù)據(jù)交互就是通過(guò)應(yīng)用API的read()穗酥、write()調(diào)用护赊,從而產(chǎn)生一個(gè)SWI軟件中斷,然后通過(guò)主設(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ì)硬件的操作的判耕,其過(guò)程如下:
read()—>swi_read()—>drv_read()—>硬件操作
那么對(duì)應(yīng)到輸入子系統(tǒng)呢?前面已經(jīng)說(shuō)了翘骂,輸入子系統(tǒng)也是字符設(shè)備壁熄,那么它也必須經(jīng)歷上面的這些步驟,只是中間穿插了幾個(gè)查找具體輸入設(shè)備的過(guò)程(畢竟將所有的輸入設(shè)備都加入到輸入子系統(tǒng)碳竟,就不止一個(gè)設(shè)備了)而已草丧。那么是如何穿插的呢:
首先在input.c(輸入子系統(tǒng)的核心)文件的打開(kāi)函數(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ù)打開(kāi)具體的函數(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);
這里說(shuō)明一下:input.c是input子系統(tǒng)的核心统翩,內(nèi)核已經(jīng)實(shí)現(xiàn)仙蚜,各種input_handler(包含open、release厂汗、read委粉、write、ioctl娶桦、fasync贾节、poll等,即硬件處理函數(shù))也由系統(tǒng)抽象出來(lái)幫我們實(shí)現(xiàn)了衷畦,后面會(huì)講解其具體實(shí)現(xiàn)過(guò)程栗涂。
通過(guò)前面的介紹,不太理解也沒(méi)有關(guān)系祈争。你只需要記住斤程,其實(shí)輸入子系統(tǒng)就是一個(gè)典型的字符設(shè)備,它也逃不過(guò)字符設(shè)備的框架菩混,其應(yīng)用與驅(qū)動(dòng)交互的流程也和字符設(shè)備驅(qū)動(dòng)一樣忿墅,沒(méi)有什么不同就是了(要從心里小瞧它)扁藕。
要想搞明白輸入子系統(tǒng)的框架,只需要弄明白應(yīng)用程序是如何與具體的硬件設(shè)備驅(qū)動(dòng)進(jìn)行交互的就行了疚脐。而這個(gè)過(guò)程如下其實(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)收到硬件訪問(wèn)需求聪廉,進(jìn)入中斷處理函數(shù)瞬痘,對(duì)應(yīng)到input_dev;
- 驅(qū)動(dòng)調(diào)用input_event()上報(bào)事件板熊,上報(bào)過(guò)程為:通過(guò)input_dev找到input_handle框全,再通過(guò)input_handle找到匹配的input_handler,然后調(diào)用該input_handler的event()函數(shù)干签,該函數(shù)即是喚醒對(duì)應(yīng)read()津辩、write()函數(shù)的實(shí)現(xiàn);
詳細(xì)過(guò)程如下三條線路所示:
- 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)用到底層的匹配過(guò)程是通過(guò)input_handler,具體驅(qū)動(dòng)中是通過(guò)靜態(tài)全局指針數(shù)組變量input_table[]實(shí)現(xiàn)的竭贩;而硬件到應(yīng)用程序的匹配過(guò)程是通過(guò)input_handle結(jié)構(gòu)體找到對(duì)應(yīng)的input_handler蚜印,從而實(shí)現(xiàn)數(shù)據(jù)傳輸?shù)模唧w到代碼就是通過(guò)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)輸入、鍵盤(pán)輸入青柄、joydev輸入等伐债,將輸入的數(shù)據(jù)封裝成統(tǒng)一的事務(wù)格式(struct input_event)上傳到應(yīng)用预侯,而將具體的硬件設(shè)備分離出來(lái)(這才是我們要做的事),如圖2.1所示峰锁。
在如上的系統(tǒng)分層結(jié)構(gòu)下萎馅,我們的應(yīng)用程序就可以不用關(guān)心獲取到的數(shù)據(jù)是來(lái)源于SPI的鍵盤(pán)、還是IIC的鍵盤(pán)虹蒋,它只需要關(guān)心獲取到數(shù)據(jù)的具體含義就行了糜芳,這樣就保證了更換不同的硬件設(shè)備,而不用修改一行應(yīng)用程序代碼魄衅,如圖2.2所示峭竣。