開發(fā)優(yōu)秀的驅(qū)動程序
作為驅(qū)動開發(fā)工程師,我們需要在每一行代碼上下功夫,因為驅(qū)動程序的效率直接影響著系統(tǒng)的性能.而新手往往不會注意到這些細節(jié)。以為功能實現(xiàn)以后就萬事大吉了,其實不然,好的驅(qū)動程序不只是能實現(xiàn)預期的功能。它同樣需要高的效率與規(guī)范的風格。用戶花錢買我們系統(tǒng)是給他/她做事的,而不是給我們做測試的,所以我們要盡可能提高效率。同時好的代碼風格能大大降低我們自己的維護成本。
高效率看似容易,但要注意到每個細節(jié)還是挺難的,我們可以從以下幾點去注意這個問題:
1, 不要使用無關(guān)的代碼,這點容易理解,尤其是調(diào)試代碼,RELEASE時一定要去除這些代碼。
2, 去掉多余的函數(shù)調(diào)用,盡可能的保存一些數(shù)據(jù)。即使是最快的函數(shù),調(diào)用它時也會引發(fā)壓棧與出棧,所以要盡量少做函數(shù)調(diào)用。當然如果一個函數(shù)返回的數(shù)據(jù)比較大,保存那些數(shù)據(jù)將占用比較多的內(nèi)在空間,保存返回值就得不償失了。比如,看到有的人每次在使用一個地址時就調(diào)用MmmapIoSpace將這個地址映射到程序地址空間,用完以后又立即Unmap這個地址,下次使用時又做MAP,這就是一種及不好的方法,每次需要多調(diào)用兩個系統(tǒng)函數(shù)。
3.如果可行,不要在循環(huán)中使用條件判斷,尤其在一個次數(shù)很多的循環(huán)中更應該如此。
比如:
For( i=0; i<1000;>
If( m==1) ..
Else if (m==2 )….
Else …..
}
這種代碼,我們可以把If 寫在for 之外,即,每一種不同的條件寫一個循環(huán)體。
If( m==1) for ...
Else if (m==2 ) for ….
Else for …..
標簽: 程序技巧
開發(fā)DMA驅(qū)動
使用DMA的好處就是它不需要CPU的干預而直接服務(wù)外設(shè),這樣CPU就可以去處理別的事務(wù),從而提高系統(tǒng)的效率,對于慢速設(shè)備,如UART,其作用只是降低CPU的使用率,但對于高速設(shè)備,如硬盤,它不只是降低CPU的使用率,而且能大大提高硬件設(shè)備的吞吐量。因為對于這種設(shè)備,CPU直接供應數(shù)據(jù)的速度太低。
因CPU只能一個總線周期最多存取一次總線,而且對于ARM,它不能把內(nèi)存中A地址的值直接搬到B地址。它只能先把A地址的值搬到一個寄存器,然后再從這個寄存器搬到B地址。也就是說,對于ARM,要花費兩個總線周期才能將A地址的值送到B地址。而DMA就不同了,一般系統(tǒng)中的DMA都有突發(fā)(Burst)傳輸?shù)哪芰Γ谶@種模式下,DMA能一次傳輸幾個甚至幾十個字節(jié)的數(shù)據(jù),所以使用DMA能使設(shè)備的吞吐能力大為增強。
使用DMA時我們必須要注意如下事實:
1. DMA使用物理地址,程序是使用虛擬地址的,所以配置DMA時必須將虛擬地址轉(zhuǎn)化成物理地址。
2. 因為程序使用虛擬地址,而且一般使用CACHED地址,所以虛擬地址中的內(nèi)容與其物理地址上的內(nèi)容不一定一致辭,所以在啟動DMA傳輸之前一定要將該地址的CACHE刷新,即寫入內(nèi)存。
3. OS并不能保證每次分配到的內(nèi)在空間在物理上是連續(xù)的。尤其是在系統(tǒng)使用過一段時間而又分配了一塊比較大的內(nèi)存時。
所以每次都需要判斷地址是不是連續(xù)的,如果不連續(xù)就需要把這段內(nèi)存分成幾段讓DMA完成傳輸。
標簽: BaseKnowledge
WINCE下USBFN驅(qū)動程序的一些概念
USBFN,即USB客戶端驅(qū)動,用來將一個WINCE設(shè)備模擬成一定的USB設(shè)備,讓主機端(如PC)訪問。目前WINCE提供的USB客戶端有存儲設(shè)備,串口設(shè)備,及RNDIS網(wǎng)絡(luò)接口設(shè)備。
存儲設(shè)備用來將WINCE設(shè)備上的存儲空間,例如FLASH,當作一塊存儲介質(zhì)給主機訪問,即將WINCE設(shè)備模擬成一個U盤。
串口設(shè)備將設(shè)備與主機的USB連線模擬成串口,WINCE和主機端都認為它們之前連接上了一根串口線,它們之間可以做串口通信,典型的應用是用來實現(xiàn)WINCE與PC機的同步連接。
RNDIS設(shè)備使兩端認為它們之間建立了網(wǎng)絡(luò)連接,通過注冊表設(shè)置可以讓主機通過WINCE設(shè)備上網(wǎng)或者使WINCE設(shè)備通過主機上網(wǎng)。
WINCE已經(jīng)提供了以上三種設(shè)備的驅(qū)動程序,在同一時刻只能使用一個設(shè)備。而我們需要做的只是提供USBFN總線控制器的驅(qū)動程序。USBFN系統(tǒng)各個模塊的關(guān)系如下:
USBFN總路線控制器作為一個總線驅(qū)動程序,被設(shè)備管理器加載,根據(jù)注冊表設(shè)置加載相應的客戶驅(qū)動程序,即存儲設(shè)備,串口設(shè)備或者RNDIS設(shè)備。客戶驅(qū)動程序即啟動USBFN,引發(fā)主機配置設(shè)備,配置完成以后即可開始工作。
而USBFN總路線控制器驅(qū)動的MDD部分WINCE本身已經(jīng)提供,PDD只需初始化硬件設(shè)備,提供傳輸即可。MDD在初始化時調(diào)用UfnPdd_Init函數(shù)得到PDD層的函數(shù)表,之后會根據(jù)需要調(diào)用各個函數(shù)。PDD還需要提供IST,用以處理各個中斷。需要注意的是USBFN有一個與其它設(shè)備不同之處,它的注冊表需要這樣一個設(shè)置:
"BusIoctl"=dword:2a0048,用以讓系統(tǒng)加載完設(shè)備之后調(diào)用值為0x2a0048的IOCTL代碼去完成初始化,其定義為IOCTL_BUS_POSTINIT。
標簽: BaseKnowledge
SOURCES文件詳解
SOURCES文件是WINCE底層開發(fā)中最重要的文件之一,主要的配置項如下:
TARGETNAME,定義模塊名稱.
TARGETTYPE,模塊的種類,可以是DYNLINK, LIBRARY,EXE.
如果TARGETTYPE是DLL,則可以定義DLLENTRY,將Dll入口定義成別的不是DLLMain的函數(shù),如果DLL的入口是DllMain,則不需要別的定義。
如果TARGETTYPE是EXE,則可以定義EXEENTRY,用于指定EXE的入口函數(shù).
如果TARGETTYPE是LIBRARY,則不需要定義入口函數(shù)。
INCLUDES,如果一個模塊需要使用非標準路徑下的頭文件時,需要定義INCLUDES,用于包含更多的頭文件路徑,用法如下:
INCLUDES=$(INCLUDES);\new directory\...,注意定義新的INCLUDES時,需要包含INCLUDES原來的值,否則就需要包含所有可能的目錄。
TARGETLIBS,SOURCELIBS用于定義該模塊需要鏈接哪些庫文件.
TARGETLIBS,如果一個庫以DLL的形式提供給調(diào)用者,就需要用TARGETLIBS,它只鏈接一個函數(shù)地址,系統(tǒng)執(zhí)行時會將被鏈接的庫加載。比如coredll.lib就是這樣的庫文件。即動態(tài)鏈接。
SOURCELIBS,將庫中的函數(shù)實體鏈接進來。即靜態(tài)鏈接,用到的函數(shù)會在我們的文件中形成一份拷貝。
注意,內(nèi)核這個執(zhí)行文件是沒有TARGETLIBS的,GIISR.DLL也不能有TARGETLIBS。
WINCECOD,如果將其定義為1,則編譯器會為每一個文件生成.cod文件,它是一個匯編文件,調(diào)試時查看匯編代碼也是一種很好的辦法。
SOURCES,定義該模塊需要哪些源文件.
標簽: BaseKnowledge
多個設(shè)備共享同一個硬件中斷
硬件中斷線總是有限的,我們可能需要在已有的系統(tǒng)上做一些擴展,比如將串口擴展成好幾個,有些硬件本身就設(shè)計成多個設(shè)備共享一條中斷線,比如我的系統(tǒng)中兩個串口就共享同一個CPU中斷,任何一個串口發(fā)生中斷以后都會觸發(fā)CPU的同一條中斷線,需要判斷別的寄存器來確定是哪個串口發(fā)生了什么中斷。
我們可以在OAL中分析各個中斷源,然后返回不同的SYSINTR值,但這種做法擴展性不好。例如,OAL中設(shè)值某個中斷源最多會產(chǎn)生三個SYSINTR,但以后擴展成了四個設(shè)備,有一個設(shè)備就無法正常工作了。
WINCE引入了可裝載中斷處理例程的概念。即在需要與別的設(shè)備共享中斷的驅(qū)動程序中加載一個ISR,一般使用WINCE提供的GIISR即成滿足需求。將其安裝到內(nèi)核。OAL中發(fā)生中斷時調(diào)用NKCallIntChain來得到SYSINTR,這個函數(shù)會引起系統(tǒng)逐個調(diào)用在該IRQ上加載的所有可裝載的ISR,當某個ISR認為這個中斷是由它引發(fā)的時就返回其SYSINTR,否則就返回SYSINTR_CHAIN,系統(tǒng)就會接著調(diào)用其它的ISR,甚至所有的ISR都被調(diào)用或者有一個ISR返回了正確的SYSINTR。
驅(qū)動程序中的調(diào)用辦法如下(CE幫助文檔):
if (InstallIsr) {
// Install ISR handler
g_IsrHandle = LoadIntChainHandler(IsrDll, IsrHandler, (BYTE)Irq);
if (!g_IsrHandle) {
DEBUGMSG(ZONE_ERROR, (L"WAVEDEV: Couldn't install ISR handler\r\n"));
} else {
GIISR_INFO Info;
PVOID PhysAddr;
DWORD inIoSpace = 1; // io space
PHYSICAL_ADDRESS PortAddress = {ulIoBase, 0};
if (!TransBusAddrToStatic(PCIBus, 0, PortAddress, ulIoLen, &inIoSpace, &PhysAddr)) {
DEBUGMSG(ZONE_ERROR, (L"WAVEDEV: Failed TransBusAddrToStatic\r\n"));
return FALSE;
}
DEBUGMSG(ZONE_PDD,
(L"WAVEDEV: Installed ISR handler, Dll = '%s', Handler = '%s', Irq =
%d, PhysAddr = 0x%x\r\n", IsrDll, IsrHandler, Irq, PhysAddr));
// Set up ISR handler
Info.SysIntr = ulSysIntr;
Info.CheckPort = TRUE;
Info.PortIsIO = TRUE;
Info.UseMaskReg = FALSE;
Info.PortAddr = (DWORD)PhysAddr + ES1371_dSTATUS_OFF;
Info.PortSize = sizeof(DWORD);
Info.Mask = ES1371_INTSTAT_PENDING;
if (!KernelLibIoControl(g_IsrHandle, IOCTL_GIISR_INFO, &Info, sizeof(Info), NULL, 0, NULL)) {
DEBUGMSG(ZONE_ERROR, (L"WAVEDEV: KernelLibIoControl call failed.\r\n"));
}
}
}
這里需要注意一下,因為ISR在內(nèi)核態(tài)運行,Info.PortAddr必須是系統(tǒng)最原始的虛擬地址,即沒有用VirtualCopy映射過的,從OEMAddressTable中計算出來的虛擬地址。在這個例子中用TransBusAddrToStatic函數(shù)可以直接把物理地址轉(zhuǎn)換成這種地址。而MmMapIoSpace得到是在當前程序空間中的地址,不能使用。而且GIIR要被加載到內(nèi)核空間,所以在加入到OS包中時需要加上K標志,否則LoadIntChainHandler函數(shù)會失敗。