? ? ? Linux的設(shè)備驅(qū)動(dòng)模型,或者說,Linux的設(shè)備驅(qū)動(dòng)框架,都是同一個(gè)意思。應(yīng)該這樣理解,(Linux的設(shè)備)驅(qū)動(dòng)框架,即某類設(shè)備對(duì)應(yīng)的驅(qū)動(dòng)的框架。
這里是“Linux總線設(shè)備驅(qū)動(dòng)框架”,應(yīng)該這樣理解,(Linux的總線設(shè)備)驅(qū)動(dòng)框架,即總線式設(shè)備對(duì)應(yīng)的驅(qū)動(dòng)的框架。
以下內(nèi)容源于微信公眾號(hào):嵌入式企鵝圈。有格式內(nèi)容上的修改,如有侵權(quán),請(qǐng)告知?jiǎng)h除。
1、總線
總線代表著同類設(shè)備需要共同遵守的工作時(shí)序,不同的總線對(duì)于物理電平的要求是不一樣的,對(duì)于每個(gè)比特的電平維持寬度也是不一樣,而總線上傳遞的命令也會(huì)有自己的格式約束。
以I2C總線為例,在同一組I2C總線上連接著不同的I2C設(shè)備。
2、設(shè)備
設(shè)備代表真實(shí)的、具體的物理器件,在軟件上用器件的獨(dú)特的參數(shù)屬性來代表該器件。
如I2C總線上連接的I2C從設(shè)備都有一個(gè)標(biāo)識(shí)自己的設(shè)備地址,由這個(gè)設(shè)備地址來確定主設(shè)備發(fā)過來的命令是否該由它來響應(yīng)。
3、驅(qū)動(dòng)
(1)驅(qū)動(dòng)代表著操作設(shè)備的方式和流程。對(duì)于應(yīng)用來說,應(yīng)用程序open打開設(shè)備后,接著就read訪問這個(gè)設(shè)備,驅(qū)動(dòng)就是如何實(shí)現(xiàn)這個(gè)訪問的具體的過程。
(2)驅(qū)動(dòng)主要包括兩部分
第一是通過對(duì)SOC的控制寄存器進(jìn)行編程,按總線要求輸出時(shí)序和命令,成功地與外圍設(shè)備進(jìn)行交互;
第二是對(duì)第一步中得到的數(shù)據(jù)進(jìn)行處理,并向應(yīng)用層提供特定格式的數(shù)據(jù)。
(3)注意點(diǎn)
不同總線的設(shè)備的驅(qū)動(dòng)過程是不一樣的,比如,USB鼠標(biāo)的驅(qū)動(dòng)和I2C EEPROM的讀時(shí)序肯定是不一樣的,訪問時(shí)序的產(chǎn)生和控制也是驅(qū)動(dòng)的一部分。
同種總線不同設(shè)備類型的設(shè)備驅(qū)動(dòng)也是不一樣的。如I2C電容屏設(shè)備,對(duì)于讀read來說就是在datasheet規(guī)定的地址上去讀觸摸點(diǎn)的X和Y坐標(biāo),而I2C EEPROM的讀操作是讀取存儲(chǔ)的內(nèi)容,兩種設(shè)備的datasheet是不一樣的,驅(qū)動(dòng)自然是不一樣的。
同種總線的同類設(shè)備的設(shè)備驅(qū)動(dòng)也可能是不一樣的。例如對(duì)于觸摸屏,TSC2003只支持單點(diǎn)觸控,而FT5X06支持多點(diǎn)觸摸。在獲取觸控坐標(biāo)時(shí),前者只需要獲得一個(gè)點(diǎn)的數(shù)據(jù)就返回,而后者則需要先獲得當(dāng)前有幾個(gè)點(diǎn)的數(shù)據(jù),然后再把所有點(diǎn)的坐標(biāo)都讀出來。
(3)在驅(qū)動(dòng)的操作中,一般都會(huì)用到GPIO和中斷等硬件資源。
如SDA和SCL會(huì)連接到SOC芯片的具體的兩個(gè)GPIO引腳,而I2C讀寫時(shí)一般都采用中斷控制的方式(查詢讀寫是否完成比較低效,浪費(fèi)CPU)。
如果我們?cè)隍?qū)動(dòng)中直接針對(duì)具體的引腳來編程,那這個(gè)驅(qū)動(dòng)的平臺(tái)可移植性就比較差,因?yàn)椴煌?a target="_blank">產(chǎn)品設(shè)計(jì)可能引腳不一樣。
為了提高驅(qū)動(dòng)的可移植性,Linux把驅(qū)動(dòng)要用到的GPIO和中斷等資源剝離給設(shè)備去管理。
即在設(shè)備里面包含其自己的設(shè)備屬性,還包括了其連接到SOC所用到的資源。而驅(qū)動(dòng)重點(diǎn)關(guān)注操作的流程和方法。
4、再談總線
第1點(diǎn)中談到的總線只是物理意義上的表述,即總線就是在行業(yè)中制定出標(biāo)準(zhǔn),明確規(guī)定時(shí)序的格式。我們?cè)诘?點(diǎn)中談到,在軟件層面上,時(shí)序的產(chǎn)生和控制由驅(qū)動(dòng)負(fù)責(zé)。那我們要思考在軟件層面上,總線的職責(zé)是什么?
總線在軟件層面主要是負(fù)責(zé)管理設(shè)備和驅(qū)動(dòng)。
設(shè)備要讓系統(tǒng)感知自己的存在,設(shè)備需要向總線注冊(cè)自己;同樣地,驅(qū)動(dòng)要讓系統(tǒng)感知自己的存在,也需要向總線注冊(cè)自己。設(shè)備和驅(qū)動(dòng)在初始化時(shí)必須要明確自己是哪種總線的,I2C設(shè)備和驅(qū)動(dòng)不能向USB總線注冊(cè)吧。
多個(gè)設(shè)備和多個(gè)驅(qū)動(dòng)都注冊(cè)到同一個(gè)總線上,那設(shè)備怎么找到最適合自己的驅(qū)動(dòng)呢,或者說驅(qū)動(dòng)怎么找到其所支持的設(shè)備呢?
這個(gè)也是由總線負(fù)責(zé),總線就像是一個(gè)紅娘,負(fù)責(zé)在設(shè)備和驅(qū)動(dòng)中牽線。
設(shè)備會(huì)向總線提出自己對(duì)驅(qū)動(dòng)的條件(最簡(jiǎn)單的也是最精確的就是指定對(duì)方的名字了),而驅(qū)動(dòng)也會(huì)向總線告知自己能夠支持的設(shè)備的條件(一般是型號(hào)ID等,最簡(jiǎn)單的也可以是設(shè)備的名字)。
設(shè)備在注冊(cè)的時(shí)候,總線就會(huì)遍歷注冊(cè)在它上面的驅(qū)動(dòng),找到最適合這個(gè)設(shè)備的驅(qū)動(dòng),然后填入設(shè)備的結(jié)構(gòu)成員中;驅(qū)動(dòng)注冊(cè)的時(shí)候,總線也會(huì)遍歷注冊(cè)在其之上的設(shè)備,找到其支持的設(shè)備(可以是多個(gè),驅(qū)動(dòng)和設(shè)備的關(guān)系是1:N),并將設(shè)備填入驅(qū)動(dòng)的支持列表中。我們稱總線這個(gè)牽線的行為是match。牽好線之后,設(shè)備和驅(qū)動(dòng)之間的交互紅娘可不管了。
總線在匹配設(shè)備和驅(qū)動(dòng)之后驅(qū)動(dòng)要考慮一個(gè)這樣的問題,設(shè)備對(duì)應(yīng)的軟件數(shù)據(jù)結(jié)構(gòu)代表著靜態(tài)的信息,真實(shí)的物理設(shè)備此時(shí)是否正常還不一定,因此驅(qū)動(dòng)需要探測(cè)這個(gè)設(shè)備是否正常。我們稱這個(gè)行為為probe,至于如何探測(cè),那是驅(qū)動(dòng)才知道干的事情,總線只管吩咐得了。所以我們可以猜測(cè)在總線的管理代碼中會(huì)有這樣的邏輯:
[cpp]?view plain?copy
if(match(device,?driver)?==?OK)??
driver->probe();??
5、再談驅(qū)動(dòng)
假設(shè)設(shè)備正常,探測(cè)成功,這時(shí)就代表應(yīng)用程序可以通過驅(qū)動(dòng)來訪問操作這個(gè)設(shè)備了。事實(shí)上是這樣嗎?仔細(xì)想想還少了什么東西。應(yīng)用層通過什么來訪問操作這個(gè)設(shè)備?想起來嗎?“嵌入式企鵝圈”的第一篇文章《Linux字符設(shè)備驅(qū)動(dòng)剖析》中曾清晰地分析了Linux字符設(shè)備驅(qū)動(dòng)的開發(fā)和訪問過程,在開篇即提到應(yīng)用程序如何訪問設(shè)備:
[cpp]?view plain?copy
int?fd?=?open(“設(shè)備文件名”);??
read(fd,?buf,?len);??
write(fd,?buf,?len);??
這個(gè)應(yīng)用程序涉及的驅(qū)動(dòng)兩個(gè)問題,一是設(shè)備文件名從何而來;二是應(yīng)用層的API如open、read和write等對(duì)應(yīng)驅(qū)動(dòng)的哪些接口,是如何對(duì)應(yīng)的。這些都是驅(qū)動(dòng)要解決的問題。
總線匹配設(shè)備和驅(qū)動(dòng)之后,驅(qū)動(dòng)探測(cè)到設(shè)備正常,這時(shí)驅(qū)動(dòng)已經(jīng)做好準(zhǔn)備讓應(yīng)用層來差遣,但是設(shè)備文件名如果沒有創(chuàng)建,應(yīng)用程序也不知從何入手。所以在驅(qū)動(dòng)的probe探測(cè)成功之后,立即創(chuàng)建設(shè)備文件是最合適的時(shí)機(jī)。通過sysfs文件系統(tǒng)、uevent事件通知機(jī)制、后臺(tái)應(yīng)用服務(wù)mdev程序,三者的配合,在/dev目錄創(chuàng)建對(duì)應(yīng)的設(shè)備文件。
驅(qū)動(dòng)要提供應(yīng)用層API如open、read、write、ioctl等操作的對(duì)應(yīng)接口,而且這些接口要向系統(tǒng)報(bào)備(注冊(cè))自己,否則系統(tǒng)也不知道怎么調(diào)用驅(qū)動(dòng),因?yàn)樵谏厦娴拿枋鲋袕氖贾两K都是設(shè)備、驅(qū)動(dòng)和總線三個(gè)東西在唱戲,它們跟系統(tǒng),嚴(yán)格意義是跟Linux的虛擬文件系統(tǒng)和設(shè)備文件系統(tǒng)還沒建立起關(guān)系來。
對(duì)于第二個(gè)問題,驅(qū)動(dòng)要包括以下步驟:
設(shè)備要提供(struct file_operation結(jié)構(gòu)所定義的)接口(即函數(shù)的具體實(shí)現(xiàn))。這些接口將會(huì)對(duì)應(yīng)到應(yīng)用層的設(shè)備訪問操作(即應(yīng)用層的API)。在這些接口中,其會(huì)根據(jù)第3點(diǎn)中提到的需求去完成自己的操作任務(wù)。
[cpp]?view plain?copy
struct?file_operations???
{??
int?(*open)?(struct?inode?*,?struct?file?*);??
int?(*ioctl)?(struct?inode?*,?struct?file?*,?...);??
ssize_t?(*read)?(struct?file?*,?char?__user?*,...);??
ssize_t?(*write)?(struct?file?*,?const?char?__user?*,?...);??
…??
}??
應(yīng)用層正常的訪問流程是:應(yīng)用層操作->虛擬文件系統(tǒng)操作->具體文件系統(tǒng)操作->具體設(shè)備驅(qū)動(dòng)的操作。
虛擬文件系統(tǒng)VFS系統(tǒng)已經(jīng)存在;
具體文件系統(tǒng)操作對(duì)于字符設(shè)備來說非常簡(jiǎn)單,我們姑且認(rèn)為是字符設(shè)備文件系統(tǒng)devfs;
此時(shí)字符設(shè)備驅(qū)動(dòng)要做的是,將自己的struct file_operations向devfs注冊(cè)(對(duì)于字符設(shè)備驅(qū)動(dòng),使用的是cdev_add函數(shù))。
詳細(xì)的分析過程可以參考《Linux字符設(shè)備驅(qū)動(dòng)剖析》。
所以我們可以想象在驅(qū)動(dòng)driver的結(jié)構(gòu)體中有一個(gè)probe接口(即Driver->probe()),驅(qū)動(dòng)要實(shí)現(xiàn)這個(gè)接口。這個(gè)probe接口要完成的工作包括:
探測(cè)設(shè)備是否正常;
cdev_add(struct file_operations),注冊(cè)操作接口;
device_create(),創(chuàng)建設(shè)備文件。
6、繼續(xù)談驅(qū)動(dòng)
做好以上準(zhǔn)備,剩下的就是等著應(yīng)用程序來訪問操作了。這里闡述一下設(shè)備驅(qū)動(dòng)的struct file_operations中的接口都要做什么。挑幾個(gè)主要的來講講。
(1)open一般會(huì)進(jìn)行驅(qū)動(dòng)的初始化
可能包括硬件的初始化、軟件的初始化。
我們?cè)诘?點(diǎn)談驅(qū)動(dòng)的時(shí)候,曾說明為了讓驅(qū)動(dòng)更具移植性,會(huì)將驅(qū)動(dòng)driver過程中使用到的具體GPIO和IRQ中斷等資源列入設(shè)備device的屬性內(nèi)容。這時(shí)device數(shù)據(jù)結(jié)構(gòu)中斷的GPIO和IRQ的標(biāo)識(shí)都來源于SOC datasheet的物理地址定義。
Linux在運(yùn)行過程中,會(huì)使用SOC的MMU內(nèi)存管理單元來管理自己的內(nèi)存,會(huì)將內(nèi)存分為兩部分,內(nèi)核空間(3G-4G)和用戶空間(0-3G)。
這兩塊地址空間都是虛擬線性地址空間,即程序編譯鏈接之后對(duì)應(yīng)的地址空間;
虛擬地址空間需要通過MMU和頁(yè)表來映射到實(shí)際的物理內(nèi)存空間,才能最終訪問到物理內(nèi)存和物理IO等資源。
而驅(qū)動(dòng)操作硬件都處在內(nèi)核空間,在open函數(shù)中主要包括以下操作:
(通過系統(tǒng)提供的資源獲取接口)獲取到GPIO和IRQ等資源;
通過ioremap接口將GPIO和IRQ從物理地址空間映射到3G-4G中的虛擬地址空間;
根據(jù)具體的控制規(guī)格設(shè)置GPIO和IRQ相關(guān)的寄存器。
以上初始化的動(dòng)作可能會(huì)出現(xiàn)在驅(qū)動(dòng)probe探測(cè)的代碼中,那open的接口可以什么都不做。
(2)read
驅(qū)動(dòng)的open如果成功,那整個(gè)訪問流程已經(jīng)成功一大半了,因?yàn)閛pen的流程足夠漫長(zhǎng)和復(fù)雜。
而read只是從(用戶空間的fd文件句柄中)找到所屬進(jìn)程的file文件結(jié)構(gòu),然后即可找出file_operations->read,其即是驅(qū)動(dòng)的read接口。
那就按著外網(wǎng)設(shè)備的規(guī)格和總線的時(shí)候進(jìn)行操作,達(dá)到read設(shè)備的目的。Write也一樣。
(3)ioctl一般是對(duì)設(shè)備進(jìn)行參數(shù)設(shè)置。
?
評(píng)論
查看更多