嵌入式Linux系統中MMC卡驅動(dòng)管理技術(shù)研究
關(guān)鍵詞 Linux MMC卡 底層驅動(dòng) 集群讀寫(xiě) 熱拔插
引 言
MMC(Multitmedia Card)是一種體積小巧、容量大、使用方便的存儲器,目前在手機等嵌入式系統中有著(zhù)廣泛的應用。MMC通過(guò)卡內的一個(gè)集成片內控制器對MMC卡進(jìn)行控制和管理,當主機正確地驅動(dòng)MMC卡后,就可以像磁盤(pán)一樣方便地存取數據。本文所研究與實(shí)現的Linux驅動(dòng)程序,以Intel XScale的PXA250為硬件平臺,在遵循MMC卡通信協(xié)議規范的基礎上,實(shí)現了卡的底層讀寫(xiě)。然后對傳統的塊設備驅動(dòng)程序中的單塊讀寫(xiě)進(jìn)行了改進(jìn),實(shí)現了集群讀寫(xiě)技術(shù),提高了卡的讀寫(xiě)速度;同時(shí)增加了電源管理功能,滿(mǎn)足了嵌入式系統低功耗的需求;增加了即插即用功能,方便了用戶(hù)的使用。
1 MMC卡驅動(dòng)程序的體系結構
MMC卡僅通過(guò)5個(gè)引腳與主機的控制器相連,通過(guò)串行協(xié)議與主機通信。MMC卡在硬件上的簡(jiǎn)單構造必然導致在實(shí)現驅動(dòng)程序上的復雜。依據MMC卡的通信擲議規范和Linux驅動(dòng)程序的結構,把驅動(dòng)程序原有的底層驅動(dòng)、守護線(xiàn)程、單塊讀寫(xiě)進(jìn)行改進(jìn)和擴展,其結構層次再劃分為底層驅動(dòng)、守護線(xiàn)程、集群讀寫(xiě)、電源管理及熱拔插管理5個(gè)部分,如圖l所示。
圖1中各部分的功能為:
①底層驅動(dòng)――處理直接涉及與MMC卡硬件寄存器端口的操作,包括:命令的發(fā)布和響應、中斷響應和處理、PIO或者DMA通道數據傳輸等。
②集群讀寫(xiě)――將磁盤(pán)相鄰數據塊的讀寫(xiě)請求合并起來(lái)一起發(fā)布讀寫(xiě)命令,以加快數據讀寫(xiě),并在讀寫(xiě)中實(shí)現并發(fā)控制。
③電源管理――實(shí)現MMC卡的低功耗管理。
④熱拔插管理――實(shí)現MMC卡的即插即用功能。
⑤守護線(xiàn)程――響應文件系統的讀寫(xiě)請求并啟動(dòng)對卡的1/O。
2 MMC卡驅動(dòng)程序的實(shí)現
2.1 底層驅動(dòng)
底層驅動(dòng)指的是直接對MMC卡進(jìn)行操作。MMC卡采用串行的數據傳輸方式;是一種比較“精細”的卡,對它的操作比較復雜而且必須有準確的時(shí)序安排。以下從命令的發(fā)布和響應、中斷響應和處理、DMA數據傳輸3個(gè)方面講述如何進(jìn)行底層讀寫(xiě)驅動(dòng)。
(1)命令發(fā)布和響應
MMC卡的操作是通過(guò)對其18個(gè)控制寄存器的讀寫(xiě)實(shí)現的。首先,設置時(shí)鐘起停寄存器MMC_STRCPL的最低兩位為01.關(guān)閉MMC卡內部時(shí)鐘。然后,設置中斷屏蔽寄存器MMC_LMASK的最低7位都為1,屏蔽所有對MMC控制器的中斷,再向指定的MMC控制寄存器中寫(xiě)入命令參數,如時(shí)鐘頻率設置寄存器MMC_CLKRT,讀寫(xiě)塊數寄存器MMC_NOB,命令寄存器MMC_CMD等。最后,打開(kāi)內部時(shí)鐘,解除屏蔽的中斷。這時(shí),當前讀寫(xiě)進(jìn)程進(jìn)入睡眠狀態(tài),等待中斷處理程序的喚醒。
(2)中斷響應和處理
MMC卡在數據傳輸請求、內部時(shí)鐘關(guān)閉、命令發(fā)布完畢、數據傳輸完畢的情況下都會(huì )產(chǎn)生中斷,但足MMC卡的控制器只通過(guò)1裉GPIO23的引腳與CPU相連,用于中斷信號線(xiàn)的復用;因此在中斷處理程序中,必須首先判斷到底是哪種原因產(chǎn)生的中斷,然后再進(jìn)行相應的處理。這里,MMC卡在正確發(fā)布讀寫(xiě)命令以后,系統會(huì )產(chǎn)生1次中斷,中斷處理程序中讀取MMC_IREG的值,判斷命令已經(jīng)發(fā)布成功,同時(shí)喚醒等待命令完成的進(jìn)程。
讀寫(xiě)進(jìn)程被中斷喚醒后,首先讀取MMC卡響應寄存器MMC_RES中的狀態(tài)信息,再根據這些狀態(tài)信息判斷命令是否發(fā)布成功和卡的當前狀態(tài)。如果這些狀態(tài)信息表示命令執行成功,則通過(guò)讀寫(xiě)緩沖寄存器MMC_RXFIFO和MMC_TXFIFO進(jìn)行數據的讀寫(xiě)(這里使用DMA進(jìn)行數據傳輸,提高了數據的傳輸速度);如果返回的狀態(tài)信息表明命令執行不成功,則根據狀念信息進(jìn)行相應的出錯處理。
(3)DMA數據傳輸
驅動(dòng)程序中對MMC卡的數據讀寫(xiě)是通過(guò)DMA通道進(jìn)行傳輸的。為了保汪操作的連續性,驅動(dòng)程序對MMC卡的輸入和輸出緩沖各設置1個(gè)DMA通道,在進(jìn)行實(shí)際數據傳輸時(shí),讀寫(xiě)進(jìn)程也進(jìn)入睡眠狀態(tài),等待DMA數據傳輸完畢后,被DMA中斷喚醒。實(shí)現一次讀操作的偽代碼如下:
Pxa_read_mmc(){
關(guān)閉時(shí)鐘,屏蔽中斷;
設置讀寫(xiě)寄存器的內容; /*讀寫(xiě)塊數,起始塊數,讀寫(xiě)速度等*/
打開(kāi)時(shí)鐘,發(fā)布讀寫(xiě)命令;
Interruptible_sleep_on(); /*進(jìn)入可打斷睡眠狀態(tài),等待中斷程序的喚醒*/
被中斷程序喚醒,打開(kāi)DMA通道,進(jìn)行數據傳輸,再次進(jìn)入可打斷睡眠狀態(tài);
被DMA傳輸完畢中斷喚醒,發(fā)布結束傳輸命令,結束數據傳輸;
2.2 集群(clustering)讀寫(xiě)和并發(fā)控制
2.2.1 傳統的塊設備驅動(dòng)程序結構和不足
塊沒(méi)備驅動(dòng)程序是Linux系統中最復雜的驅動(dòng)程序之一,參閱文獻[3,4]可以詳細了解Linux塊設備驅動(dòng)程序。這里簡(jiǎn)單介紹與集群讀寫(xiě)相關(guān)的數據結構和操作。扇區(seetor)是塊設備硬件傳輸數據的基本單位,而塊(block)是塊設備請求1次I/O操作所涉及的一組相鄰扇區,每個(gè)塊都需要有自己的內存緩沖區。緩沖區首部(buffer_head)是與每個(gè)緩沖區相關(guān)的數據結構,每次對塊沒(méi)備的I/O傳輸都必須經(jīng)過(guò)塊的緩沖區。
Linux塊沒(méi)備驅動(dòng)程序采取一種延遲I/O策略。當進(jìn)程有I/O請求時(shí),驅動(dòng)程序延遲一段時(shí)間,把塊設備上相連續的buffer_head結構關(guān)聯(lián)在一起形成一個(gè)I/O請求描述符(struct request),再把request結構按照電梯算法排隊到設備的請求隊列(request_queue_t)。這樣實(shí)際執行I/O傳輸時(shí),順次處理對應塊設備的請求隊列。
對于request結構的電梯排隊算法,避免由于頻繁的移動(dòng)磁頭而導致塊設備性能下降;然而,目前在Linux塊設備驅動(dòng)程序中,對一個(gè)request結構中的各個(gè)buffer_head結構分別發(fā)布I/O讀寫(xiě)命令,會(huì )導致每次對一個(gè)buffer_head的輸入/輸出時(shí),磁頭都會(huì )停頓一段時(shí)間,進(jìn)行DMA數據讀寫(xiě)。這樣頻繁的磁頭啟停會(huì )導致磁盤(pán)性能下降。
2.2.2 集群讀寫(xiě)的實(shí)現
傳統的塊設備驅動(dòng)程序中每次發(fā)布讀寫(xiě)命令都只對一個(gè)buffer_head緩沖而導致塊設備性能下降。針對這一問(wèn)題,我們對傳統塊設備進(jìn)行改進(jìn),實(shí)現了集群讀寫(xiě)。由于每一個(gè)request結構的buffer_head結構鏈對應的物理塊都是相鄰的,因此為進(jìn)行集群讀寫(xiě)創(chuàng )造了條件。request結構中的nr_sectors表示該request結構需要讀寫(xiě)的塊數。進(jìn)行讀寫(xiě)時(shí),一次性發(fā)布讀寫(xiě)塊數為nr_seetors,讀入塊設備內容到requem結構指向的第一個(gè)buffer_head結構對應的內存區域。在一個(gè)buffer_head結構的緩沖區讀寫(xiě)滿(mǎn)了以后,就調整讀寫(xiě)緩沖區地址為下一個(gè)buffer_head所指向的緩沖區,同時(shí)配合DMA進(jìn)行數據傳輸,提高了讀寫(xiě)速度。對一個(gè)request結構操作完成以后,釋放request結構資源。實(shí)現集群讀操作偽碼如下:
Read_mmc(){
發(fā)布讀寫(xiě)命令,讀入的數據塊數為一個(gè)rcquest一>nr_sectors的塊數;
緩沖區的指針指向第1個(gè)bh結構所指的緩沖區;
while(數據還沒(méi)有讀完){
讀入數據到buffer_head結構所指定的緩沖區;/*調用Pxa_read_mmc()*/
調整緩沖區的指針到下一個(gè)buffer_head結構所指向的緩沖區;
}
}
2.2.3集群讀寫(xiě)中的并發(fā)控制
如果I/O請求隊列request_queue_t是在內核中的許多地方都被訪(fǎng)問(wèn)的,則該隊列就成為了臨界資源。為了對該隊列進(jìn)行互斥保護,Linux2.4中所有的請求隊列都受一個(gè)單獨的全局自旋鎖io_request_lock的保護。所有對清求隊列的操作必須要求擁有該鎖并禁止中斷,然而,在驅動(dòng)程序擁有這個(gè)鎖的同時(shí),其他任何讀寫(xiě)請求不能排隊到系統的任何塊設備上,其他讀寫(xiě)處理函數也不能運行。為了盡量減輕由于驅動(dòng)程序長(cháng)期的擁有該鎖而導致系統性能下降的問(wèn)題,在實(shí)現集群讀寫(xiě)時(shí)必須遵循以下原則:
①對請求隊列進(jìn)行讀寫(xiě)操作時(shí)要獲得鎖;
②對請求隊列操作完畢后釋放請求鎖;
③為了減少占用鎖的時(shí)間,可先把隊列中的request結構從隊列中取下來(lái),再打開(kāi)鎖,然后在開(kāi)鎖的情況下對取下的request結構進(jìn)行操作。
基于以上原則,讀/寫(xiě)處理函數的偽碼如下所示:
mmc_request_fn()
whilc(1){
加鎖io_request_lock;
讀取當前mmc卡請求隊列的第一個(gè)請求結構request;
釋放鎖io_request_lock;
if(request為空)
cxit(O); /*沒(méi)有可以處理的隊列,返回*/
read_mmc(); /*調用集群讀寫(xiě)函數*/
加鎖io_request_lock;
在queue結構中取處理完畢的request結構,釋放request資源;
釋放鎖io_request_lock;
}
}
2.3 守護線(xiàn)程
在MMC卡驅動(dòng)程序初始化的時(shí)候,啟動(dòng)守護線(xiàn)程mme_block_thread。它平時(shí)處于睡眠狀態(tài),當有對MMC卡的讀/寫(xiě)請求時(shí),mmc_blok_thread被喚醒。該線(xiàn)程調用上述讀/寫(xiě)處理函數mmc_request_fn(),處理完畢后再進(jìn)入睡眠狀態(tài)。
2.4 電源管理
嵌入式系統一般有低功耗要求,當某設備長(cháng)期沒(méi)有運行時(shí),就應該停止給該設備供電,以減少電能消耗。在內核中有一個(gè)需要注冊的電源管理設備的隊列pm_list,同時(shí)也有電源管理線(xiàn)程kpowered,它的優(yōu)先級是所有運行進(jìn)程中最低的。當系統長(cháng)時(shí)間沒(méi)有進(jìn)程運行時(shí),kpowered被喚醒,掃描pm_list隊列各個(gè)注冊的設備。如果發(fā)現該設備長(cháng)期沒(méi)有運行,則向該設備發(fā)出PM_SUSPEND事件;而當設備重新開(kāi)始使用時(shí),則向pm_list隊列發(fā)出:PM_RESUME事件。
在MMC卡驅動(dòng)模塊中注冊了電源管理的回調函數mme_block_callback,即pm_register(PM_UNKNOWN_DEV,0,mme_pm_callback)。這樣MMC卡就注冊到了pm_list隊列中去了。當有電源事件時(shí),就觸發(fā)mmc_pm_callback函數。該函數處理各種電源事件。
程序中的電源事件有兩種:
①PM_SUSPEND事件。該事件使MMC卡進(jìn)入省電模式。這時(shí)驅動(dòng)程序保存MMC卡的當前狀態(tài)和重要寄存器的內容,如時(shí)鐘寄存器MMC_CLKRT和狀態(tài)寄存器MMC_STAT等。然后,設置MMC卡的供電GPIO為高電平,關(guān)閉MMC卡的電源供應,沒(méi)置MMC卡在時(shí)鐘使能寄存器CKEN的相應位為O,關(guān)閉MMC卡的時(shí)鐘脈沖。這時(shí),MMC卡就進(jìn)入了省電模式。
②PM_RESUME事件。該事件使MMC卡進(jìn)入正常工作模式。這時(shí)程序恢復在進(jìn)入省電模式前保存的寄存器,打開(kāi)電源供應和時(shí)鐘脈沖,MMC卡恢復到正常的工作模式。
當然電源事件也可以由用戶(hù)進(jìn)程自愿觸發(fā)。在文件系統的接口file_operaion io_control中留有電源理管理接口,用戶(hù)可以通過(guò)io_contol向卡發(fā)送電源事件請求。
2.5 熱插拔管理
在手機、PDA等嵌入式系統中,都要求提供對設備的即插即用功能,使用戶(hù)無(wú)須安裝驅動(dòng)程序就可以即時(shí)使用設備。Linux在系統層和應用層都要對熱插拔事件進(jìn)行處理。在系統層,一方面要探測MMC卡的熱插拔事件,分配或釋放系統資源,并驅動(dòng)MMC卡;另一方面,要將此事件準確及時(shí)地通知給應用層,應用層則根據熱插拔事件作相應的處理。
在操作系統層,需要注冊一個(gè)字符型設備mmc_plug文件,用于應用層探測MMC卡的熱插拔事什。CPU通過(guò)GPIO12引腳與MMC卡相連,用于卡插拔的中斷探測。同時(shí)驅動(dòng)程序巾設置一個(gè)信號量MMC_EVENT,它取MMC_INSERT和MMC_REMOVAL兩個(gè)值。當卡插入和或者拔出時(shí),在中斷處理程序中被分別設置為MMC_INSERT和MMC_REMCOVAL;并同時(shí)傳給字符設備mmc_plug,供上層的應用程序使用。為了讓?xiě)脤幽軌蛑獣钥ǖ陌尾迨录?,在字符設備mmc_plug使用異步I/O機制poll,需要接收內核拔插事件的進(jìn)程通過(guò)poll在一個(gè)等待隊列上睡眠,當有卡拔插事件時(shí)產(chǎn)生中斷,中斷處理程序喚醒在隊列上等待的進(jìn)程。上層進(jìn)程在被喚醒后就讀取字符設備,獲取所發(fā)生的事件。
在應用層,進(jìn)程通過(guò)select機制監聽(tīng)MMC卡所發(fā)生的熱插拔事件,在沒(méi)有拔插事件的時(shí)候,進(jìn)程進(jìn)入阻塞狀態(tài),讓出CPU資源;當發(fā)生熱拔插事件時(shí),系統喚醒通過(guò)poll加入到等待隊列中的進(jìn)程,然后應用層通過(guò)read函數得到MMC卡的熱插拔事件,進(jìn)行相應的應用層處理。當然,應用層也可以通過(guò)write方法通知系統層對卡進(jìn)行處理。
結語(yǔ)
本文研究實(shí)現的MMC卡驅動(dòng)程序,其實(shí)現的集群讀寫(xiě)證明有穩定而較高的讀/寫(xiě)速度;增加了電源管理功能,降低了電源的功耗,滿(mǎn)足了嵌入式系統低功耗的要求;增加的即插即用功能,大大方便了用戶(hù)的使用。驅動(dòng)程序的體系結構是實(shí)現嵌入式系統塊設備驅動(dòng)的一種好方法。
評論