WinCE下的串口驅(qū)動(dòng)分析
串口驅(qū)動(dòng)分析
Auth:nasiry
date: 2005年4月12日
abort: windowsCE.net 420串口驅(qū)動(dòng)分析
abort: windowsCE.net 420串口驅(qū)動(dòng)分析
相關(guān)資料 |
雖然串口通訊已經(jīng)是普遍的標(biāo)準(zhǔn)而且廣為大家熟知,但驅(qū)動(dòng)中涉及的部分內(nèi)容也可能在平時(shí)的應(yīng)用中并不是很常用到,在這里做一個(gè)簡(jiǎn)單的介紹待后面說明到具體代碼的時(shí)候可以連貫一些。
串行通訊接口是目前十分流行的通訊接口之一。由于其電氣界面的簡(jiǎn)單性使其在計(jì)算機(jī)領(lǐng)域的應(yīng)用相當(dāng)?shù)膹V泛。在這里提到的串行通訊接口主要是指UART(通用串行)和IRDA兩種。通常的串行連接電氣連接上有3wire和9wire兩種。3wire的接線方式下定義了發(fā)送、接收和地三根連接。其用途就如名稱一樣分別用于發(fā)送、接收。下面是通常3wire連接的結(jié)構(gòu)框圖
通常在串行接口控制器上會(huì)有兩個(gè)FIFO用作接收和發(fā)送的緩沖,當(dāng)接收到數(shù)據(jù)后會(huì)直接將接收到的數(shù)據(jù)置入該緩沖器,并同時(shí)由控制電路向本地總線發(fā)出通知,以便讓本地總線將緩沖器內(nèi)的數(shù)據(jù)讀走,這樣在響應(yīng)(等待和讀取)的過程中仍然能通過緩沖器來接收數(shù)據(jù)。而發(fā)送發(fā)送的過程剛剛相反,本地總線可一直向發(fā)送緩沖寫入數(shù)據(jù)直到器填滿為止,而無需對(duì)每個(gè)數(shù)據(jù)的發(fā)送進(jìn)行等待。這就是基本的收發(fā)流程(這部分邏輯流程相信大家是最熟悉的)。這一點(diǎn)在3wire和9wire中都是相同的。但是我們考慮下面的情況,如果接收一方的響應(yīng)由于某種原因的干擾(如處理器被其他中斷服務(wù)占用)的時(shí)候可能就來不及相應(yīng)之前ReceiveFIFO就可能被填滿了,這樣后續(xù)發(fā)送過來的數(shù)據(jù)就會(huì)丟失,這樣在需要數(shù)據(jù)可靠傳輸?shù)那闆r下串行通訊的弊端也就顯示出來了。如需要數(shù)據(jù)的可靠傳輸就需要對(duì)數(shù)據(jù)流的收發(fā)進(jìn)行控制。在9wire中將串行連接定義為如下形式。
針號(hào) | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
縮寫 | DCD | RXD | TXD | DTR | GND | DSR | RTS | CTS | DELL |
功能說明 | 數(shù)據(jù)載波檢測(cè) | 接收數(shù)據(jù) | 發(fā)送數(shù)據(jù) | 數(shù)據(jù)終端就緒 | 信號(hào)地 | 數(shù)據(jù)設(shè)備就緒 | 請(qǐng)求發(fā)送 | 清除發(fā)送 | 振鈴指示 |
也就是說在原3wire的基礎(chǔ)上增加了DCD,DTR,DSR,RTS,CTS,DELL六個(gè)控制線。其中RTS/CTS用于流控制,另外的DCD和DELL則留作連接modem使用。有了專門的硬件流控制引腳也就使得流控制成為可能,以完成收發(fā)兩端的匹配使得數(shù)據(jù)可以可靠的傳輸。用RTS/CTS(請(qǐng)求發(fā)送/清除發(fā)送)流控制時(shí),應(yīng)將通訊兩端的RTS、CTS線對(duì)應(yīng)相連).在發(fā)送端準(zhǔn)備發(fā)送數(shù)據(jù)之前設(shè)置RTS(Request to send)也就使發(fā)送請(qǐng)求線,若接收端以作好接收準(zhǔn)備,就啟動(dòng)響應(yīng)的CTS(Clear to send)引線。這樣,收發(fā)雙發(fā)就進(jìn)入數(shù)據(jù)傳輸狀態(tài),在此過程中如若接收端處理數(shù)據(jù)的速度低于發(fā)送端的發(fā)送速度,接收一端還可以設(shè)置CTS引線恢復(fù)原來阻塞得狀態(tài)以暫時(shí)中斷數(shù)據(jù)傳輸,之后若需要恢復(fù)數(shù)據(jù)傳輸恢復(fù)CTS狀態(tài)即可。這樣UART的傳輸即實(shí)現(xiàn)了流控制,保障了數(shù)據(jù)傳輸?shù)耐陚湫浴?
在這里還要說一下軟件流控制,雖然硬件已經(jīng)可以完成流控制的任務(wù)但很多少時(shí)候受到連線數(shù)的限制不能使用硬件流控制也就設(shè)計(jì)了專門的軟件流控制的方法?,F(xiàn)在回到3線傳輸?shù)那榫?,若接收端接收?shù)據(jù)過程中緩沖器的負(fù)載到達(dá)某一限制(也就是留出一定的緩沖空間)時(shí)接收端向發(fā)送端發(fā)送一個(gè)特殊的標(biāo)示位(接收停止位),當(dāng)發(fā)送端收到該標(biāo)示的時(shí)候就停止發(fā)送,直到接收端緩沖器低于另一限制后發(fā)送標(biāo)示(接收許可位)給發(fā)送端,這樣就可以控制數(shù)據(jù)流的傳輸起停。這種軟件流控制是在給緩沖器留余量來完成的,在收發(fā)雙端處理器速度差很大的時(shí)候就不太適用了,就必須要用硬件流控制。
其他幾個(gè)引腳都是與modem相關(guān)的,DSR數(shù)據(jù)裝置準(zhǔn)備好(Data set ready)用于表明MODEM處于可以使用的狀態(tài)。DTR數(shù)據(jù)終端準(zhǔn)備好(Data terminal ready)表明數(shù)據(jù)終端可以使用。這兩個(gè)信號(hào)用于檢查Modem是否連接。DELL腳當(dāng)有電話撥入時(shí)Modem將會(huì)設(shè)置這個(gè)引腳。DCD信號(hào)是當(dāng)Modem接收到數(shù)字載波信號(hào)的時(shí)候被設(shè)置,用于了解Modem接收信號(hào)的情況。
其他幾個(gè)引腳都是與modem相關(guān)的,DSR數(shù)據(jù)裝置準(zhǔn)備好(Data set ready)用于表明MODEM處于可以使用的狀態(tài)。DTR數(shù)據(jù)終端準(zhǔn)備好(Data terminal ready)表明數(shù)據(jù)終端可以使用。這兩個(gè)信號(hào)用于檢查Modem是否連接。DELL腳當(dāng)有電話撥入時(shí)Modem將會(huì)設(shè)置這個(gè)引腳。DCD信號(hào)是當(dāng)Modem接收到數(shù)字載波信號(hào)的時(shí)候被設(shè)置,用于了解Modem接收信號(hào)的情況。
至于剩下的奇偶效驗(yàn)和停止位設(shè)置就只是需要針對(duì)寄存器設(shè)置無需軟件干涉就可以完成了。下面我們來看具體的驅(qū)動(dòng)程序。
架構(gòu) |
在wince中串口的驅(qū)動(dòng)實(shí)現(xiàn)是有固定模型的,ce中的串口模型遵循ISO/OSI網(wǎng)絡(luò)通訊模型(7層),就是說串口屬于CE網(wǎng)絡(luò)模塊的一個(gè)部分。其中rs232界面(或其它的物理介質(zhì))實(shí)現(xiàn)網(wǎng)絡(luò)的物理層,而驅(qū)動(dòng)和serialAPI共同組成數(shù)據(jù)鏈路層,其它部分都沒有做定義。在典型的應(yīng)用中,serialAPI與間接通過TAPI或直接與ActiveSync交互,組成CE網(wǎng)絡(luò)的一部分。而紅外本身的協(xié)議就相對(duì)復(fù)雜的多,它有專門的一套模型來描述其使用規(guī)則,對(duì)紅外設(shè)備本身了解不多也就不能深入下去。在串口的這一側(cè),整個(gè)驅(qū)動(dòng)模型也是相當(dāng)?shù)膹?fù)雜的,但所幸的是驅(qū)動(dòng)僅僅使用到SerialAPI這一層,在這個(gè)層次上串口的行為還是相對(duì)簡(jiǎn)單的。
我們這里僅僅涉及上面所提到的Serial/irda Driver這部分(綠色部分)。在wince提供的驅(qū)動(dòng)例程中串口/紅外驅(qū)動(dòng)采用分層結(jié)構(gòu)設(shè)計(jì),MDD提供框架性的實(shí)現(xiàn),負(fù)責(zé)提供OS所需的基本實(shí)現(xiàn),并將代碼設(shè)計(jì)與具體的硬件設(shè)計(jì)無關(guān)。而PDD提供了對(duì)硬件操作相應(yīng)的代碼。這些代碼通過結(jié)構(gòu)HWOBJ來相互聯(lián)系。對(duì)于MDD PDD的整體驅(qū)動(dòng)來看,串口驅(qū)動(dòng)模型是作為Stream來實(shí)現(xiàn)的。
兩者合一以達(dá)到實(shí)現(xiàn)驅(qū)動(dòng)的目的。DDSI就是指這兩個(gè)部分之間的接口,這個(gè)接口并非受到強(qiáng)制的物理/邏輯關(guān)系來約束,而是人為的規(guī)定的。在涉及到一種特定硬件我們進(jìn)行針對(duì)實(shí)現(xiàn)的時(shí)候往往需要的是了解硬件的物理特性和控制邏輯,然后根據(jù)DDSI的約束就來進(jìn)行實(shí)現(xiàn)。對(duì)于這里描述的驅(qū)動(dòng)模型而言結(jié)合關(guān)鍵在于結(jié)構(gòu)指針HWOBJ的使用和具體實(shí)現(xiàn)。在實(shí)際的驅(qū)動(dòng)應(yīng)用中僅僅需要實(shí)現(xiàn)HWOBJ相關(guān)的一系列函數(shù),而無需從驅(qū)動(dòng)頂層完全開發(fā)。串口驅(qū)動(dòng)模型作為一種常用驅(qū)動(dòng)模型在windowsCE中常常用于串口/紅外/USB Client的具體實(shí)現(xiàn)。該驅(qū)動(dòng)模型中對(duì)全功能的串口進(jìn)行了定義,除了常用的TX和RX引線定義以外,針對(duì)DTR、RTS等功能引腳都進(jìn)行了支持,使得用該模型設(shè)計(jì)的串口驅(qū)動(dòng)支持流控制、具備驅(qū)動(dòng)Modem等設(shè)備的能力。
事實(shí)上,如果需要的話完全可以將該驅(qū)動(dòng)一體化設(shè)計(jì)(拋開PDD-MDD的劃分,也就無須DDSI)。也就是不使用現(xiàn)有的驅(qū)動(dòng)架構(gòu)來進(jìn)行實(shí)現(xiàn)??紤]到串口驅(qū)動(dòng)的使用頻率和執(zhí)行效率要求都不是很苛刻的情況下拋棄驅(qū)動(dòng)架構(gòu)另外實(shí)現(xiàn)的就沒有多大必要了。
對(duì)于驅(qū)動(dòng)本身而言,串行驅(qū)動(dòng)從功能和實(shí)現(xiàn)上相當(dāng)?shù)暮?jiǎn)單,確具被相當(dāng)全面的成分,對(duì)該驅(qū)動(dòng)的分析和了解無疑是學(xué)習(xí)流式驅(qū)動(dòng)程序很好的典范。
代碼分析 |
在開始具體代碼之前我們先來看看,相關(guān)的一些結(jié)構(gòu)。 HWOBJ是相應(yīng)的硬件設(shè)備操作的抽象集合。結(jié)構(gòu)的定義后的注釋與實(shí)際的用途有點(diǎn)點(diǎn)出入,BandFlags指定IST的啟動(dòng)時(shí)間,可選為在初始化過程啟動(dòng)或是在打開設(shè)備的時(shí)候起動(dòng)ISR.而第二個(gè)參數(shù)則是指定攔截的具體的系統(tǒng)中斷號(hào)。最后一個(gè)參數(shù)是一個(gè)結(jié)構(gòu),該結(jié)構(gòu)定義了硬件操作的各式行為函數(shù)的指針,MDD正是通過這些函數(shù)來訪問具體的PDD操作。
typedef struct __HWOBJ {
typedef struct __HWOBJ {
ULONG BindFlags; / Flags controlling MDD behaviour. Se above.
DWORD dwIntID; / Interrupt Identifier used if THREAD_AT_INIT or THREAD_AT_OPEN
PHW_VTBL pFuncTbl;
} HWOBJ, *PHWOBJ;
而HW_VTBL則是代表具體硬件操作函數(shù)指針的集合,該結(jié)構(gòu)所指向的函數(shù)包括了初始化、打開、關(guān)閉、接收、發(fā)送、設(shè)置Baudrate等一系列操作。結(jié)構(gòu)存在就像紐帶一樣聯(lián)系著PDD中的具體實(shí)現(xiàn)和MDD中的抽象操作。PDD的實(shí)現(xiàn)必須遵循HW_VTBL中所描述的函數(shù)形式,并構(gòu)造出相應(yīng)的HW_VTBL實(shí)例。驅(qū)動(dòng)的編寫就是針對(duì)這些函數(shù)來一一進(jìn)行實(shí)現(xiàn)。
而HW_VTBL則是代表具體硬件操作函數(shù)指針的集合,該結(jié)構(gòu)所指向的函數(shù)包括了初始化、打開、關(guān)閉、接收、發(fā)送、設(shè)置Baudrate等一系列操作。結(jié)構(gòu)存在就像紐帶一樣聯(lián)系著PDD中的具體實(shí)現(xiàn)和MDD中的抽象操作。PDD的實(shí)現(xiàn)必須遵循HW_VTBL中所描述的函數(shù)形式,并構(gòu)造出相應(yīng)的HW_VTBL實(shí)例。驅(qū)動(dòng)的編寫就是針對(duì)這些函數(shù)來一一進(jìn)行實(shí)現(xiàn)。
typedef struct __HW_VTBL {
PVOID (*HWInit)(ULONG Identifier, PVOID pMDDContext, PHWOBJ pHWObj);
BOOL (*HWPostInit)(PVOID pHead);
ULONG (*HWDeinit)(PVOID pHead);
BOOL (*HWOpen)(PVOID pHead);
ULONG (*HWClose)(PVOID pHead);
INTERRUPT_TYPE (*HWGetIntrType)(PVOID pHead);
ULONG (*HWRxIntrHandler)(PVOID pHead, PUCHAR pTarget, PULONG pBytes);
VOID (*HWTxIntrHandler)(PVOID pHead, PUCHAR pSrc, PULONG pBytes);
VOID (*HWModemIntrHandler)(PVOID pHead);
VOID (*HWLineIntrHandler)(PVOID pHead);
ULONG (*HWGetRxBufferSize)(PVOID pHead);
BOOL (*HWPowerOff)(PVOID pHead);
BOOL (*HWPowerOn)(PVOID pHead);
VOID (*HWClearDTR)(PVOID pHead);
VOID (*HWSetDTR)(PVOID pHead);
VOID (*HWClearRTS)(PVOID pHead);
VOID (*HWSetRTS)(PVOID pHead);
BOOL (*HWEnableIR)(PVOID pHead, ULONG BaudRate);
BOOL (*HWDisableIR)(PVOID pHead);
VOID (*HWClearBreak)(PVOID pHead);
VOID (*HWSetBreak)(PVOID pHead);
BOOL (*HWXmitComChar)(PVOID pHead, UCHAR ComChar);
ULONG (*HWGetStatus)(PVOID pHead, LPCOMSTAT lpStat);
VOID (*HWReset)(PVOID pHead);
VOID (*HWGetModemStatus)(PVOID pHead, PULONG pModemStatus);
VOID (*HWGetCommProperties)(PVOID pHead, LPCOMMPROP pCommProp);
VOID (*HWPurgeComm)(PVOID pHead, DWORD fdwAction);
BOOL (*HWSetDCB)(PVOID pHead, LPDCB pDCB);
BOOL (*HWSetCommTimeouts)(PVOID pHead, LPCOMMTIMEOUTS lpCommTO);
BOOL (*HWIoctl)(PVOID pHead, DWORD dwCode,PBYTE pBufIn,DWORD dwLenIn,
PBYTE pBufOut,DWORD dwLenOut,PDWORD pdwActualOut);
} HW_VTBL, *PHW_VTBL;交待了上述兩個(gè)結(jié)構(gòu)以后我們來看看具體的代碼,為保障對(duì)系統(tǒng)架構(gòu)的清晰認(rèn)識(shí),我們將MDD的代碼和PDD的代碼分開進(jìn)行分析。
MDD部分 |
由于串口驅(qū)動(dòng)由Device.exe直接調(diào)用,所以MDD部分是以完整的Stream接口給出的. 也就具備基于Stream接口的驅(qū)動(dòng)程序所需的函數(shù)實(shí)現(xiàn),包括COM_Init,COM_Deinit
,COM_Open,COM_Close ,COM_Read ,COM_Write, COM_Seek,, COM_PowerUp, COM_PowerDown, COM_IOControl幾個(gè)基本實(shí)現(xiàn)。由于串口發(fā)送/接收的信息并不能定位,而僅僅是簡(jiǎn)單的傳送,所以COM_Seek僅僅是形式上實(shí)現(xiàn)了一下。
COM_Init
COM_Init是該驅(qū)動(dòng)的初始化函數(shù),在設(shè)備管理器加載該驅(qū)動(dòng)后首先調(diào)用,用于初始化所需的變量,硬件設(shè)備等資源。該過程分配代表設(shè)備硬件實(shí)例的數(shù)據(jù)結(jié)構(gòu),并通過硬件抽象接口HWInit初始化硬件。同時(shí)該函數(shù)會(huì)調(diào)用InterruptInitialize為接收內(nèi)核中的邏輯中斷創(chuàng)建相應(yīng)事件并初始化臨界區(qū)。該函數(shù)還需要得到硬件緩沖器的物理地址和獲知該緩沖器的大小(該沖器最小為2K)。最后它將建立相應(yīng)的緩沖作為接收的中介。下面我們來看這個(gè)函數(shù)的實(shí)現(xiàn)過程。
在函數(shù)中定義了兩個(gè)重要的變量。pSerialHead和pHWHead.前者用于描述相應(yīng)的串口的狀態(tài),后者則是對(duì)應(yīng)硬件的數(shù)據(jù)抽象。首先為pSerialHead分配空間和初始化鏈表和臨界區(qū)等數(shù)據(jù)并同時(shí)為接收和發(fā)送中斷創(chuàng)建事件。然后再從注冊(cè)表中獲得當(dāng)前的注冊(cè)項(xiàng)值(由于device.exe是根據(jù)注冊(cè)表鍵值來調(diào)用驅(qū)動(dòng)的,當(dāng)前鍵注冊(cè)表項(xiàng)指的就是與上述鍵值在同一根下的注冊(cè)項(xiàng))。得到DeviceArrayIndex、Priority256鍵下的具體值后就可以調(diào)用GetSerialObject(在PDD中實(shí)現(xiàn))來獲得具體的HWObj對(duì)象,并通過該對(duì)象來調(diào)用硬件初始化函數(shù)了。(由于在這里已經(jīng)對(duì)硬件進(jìn)行了初始化,之后的返回都需要調(diào)用COM_Deinit來完成。)由于硬件初始化(實(shí)際的驅(qū)動(dòng)初始化代碼)已經(jīng)得到執(zhí)行這個(gè)時(shí)候就只有分配和初始化緩沖的工作需要做了。所以調(diào)用HWGetRxBufferSize(PDD代碼)來獲取PDD中設(shè)定的緩沖大小,并根據(jù)返回的具體值分配緩沖。最后如果BindFlags被設(shè)定為THREAD_AT_INIT就再調(diào)用StartDispatchThread啟動(dòng)分發(fā)線程(實(shí)際的IST)。這樣就完成了系統(tǒng)初始化的操作。
COM_Deinit
當(dāng)驅(qū)動(dòng)被稱被卸下的時(shí)候該事件啟動(dòng),用作與COM_Init相反的操作。這個(gè)過程大致會(huì)釋放驅(qū)動(dòng)中所使用的資源,停止期間創(chuàng)建的線程等操作。具體說來,大致為停止在MDD中的所有IST,和釋放內(nèi)存資源和臨界區(qū)等系統(tǒng)資源。同時(shí)還需調(diào)用HWDeinit來釋放PDD中所使用到的系統(tǒng)資源。
COM_Open
COM_Oepn在CreateFile后被調(diào)用,用于以讀/寫模式打開設(shè)備,并初始化所需要的空間/資源等,創(chuàng)建相應(yīng)的實(shí)例,為后面的操作做好準(zhǔn)備。這里的代碼相對(duì)比較容易,下面就簡(jiǎn)單講一下。既然是初始化,肯定就免不了對(duì)參數(shù)的檢查。首先檢查通過COM_Init返回的pHead結(jié)構(gòu)是否有效,這里雖然沒有顯式的在這兩個(gè)函數(shù)之間傳遞參數(shù),而是在設(shè)備管理器的內(nèi)部傳遞這個(gè)參數(shù)的。
之后是檢查文件系統(tǒng)傳遞過來的Open句柄中的Open模式是否有效,這個(gè)參數(shù)由應(yīng)用程序產(chǎn)生,通過文件系統(tǒng)直接傳遞到驅(qū)動(dòng)。之后就開始初始化的操作,在這里將會(huì)建立相應(yīng)的HW_OPEN_INFO實(shí)體。下面和為該結(jié)構(gòu)的定義。
typedef struct __HW_OPEN_INFO {
PHW_INDEP_INFO pSerialHead; / @field Pointer back to our HW_INDEP_INFO
DWORD AccessCode; / @field What permissions was this opened with
DWORD ShareMode; / @field What Share Mode was this opened with
DWORD StructUsers; / @field Count of threads currently using struct.
COMM_EVENTS CommEvents; / @field Contains all in…. handling
LIST_ENTRY llist; / @field Linked list of OPEN_INFOs
} HW_OPEN_INFO, *PH
結(jié)構(gòu)中的第一個(gè)參數(shù)指向我們前面提到的HW_INDEP_INFO結(jié)構(gòu),第二個(gè)參數(shù)為操作權(quán)限碼,也就是READ/WRITE這類的權(quán)限。第三個(gè)參數(shù)為共享模式,以確定是否支持獨(dú)占。這兩個(gè)參數(shù)都是與文件系統(tǒng)的內(nèi)容對(duì)應(yīng)的。而CommEvent則對(duì)應(yīng)于本實(shí)例的事件。由于驅(qū)動(dòng)架構(gòu)支持多個(gè)OPEN操作實(shí)例的存在,所以這里維護(hù)了一個(gè)鏈表來聯(lián)系這些結(jié)構(gòu)。在這里由于IST的啟動(dòng)可以在COM_Init和COM_Open中進(jìn)行,還有處理器啟動(dòng)IST的內(nèi)容。準(zhǔn)備好HW_OPEN_INFO結(jié)構(gòu)后就可以調(diào)用HWOpen(PDD)來進(jìn)行PDD所需的Open操作了。Open操作完成后調(diào)用HWPurgeComm(PDD)來處理(取消或等待)當(dāng)前仍在通訊狀態(tài)的任務(wù)。然后重置軟件FIFO就基本完成了COM_Open的動(dòng)作了。
事實(shí)上這里主要是對(duì)所需的數(shù)據(jù)結(jié)構(gòu)進(jìn)行處理,對(duì)于硬件的具體操作都留給PDD去做了,MDD所維護(hù)的僅僅是一個(gè)架構(gòu)性的代碼。Open操作完成后,驅(qū)動(dòng)就進(jìn)入了工作狀態(tài)這個(gè)時(shí)候。
COM_Close
COM_Close為與COM_Open相對(duì)應(yīng)的操作。這期間的目的是釋放COM_Open所使用的系統(tǒng)資源,除此以外如果在COM_Open期間創(chuàng)建了相應(yīng)的IST還需要停止該線程,在最后將該HW_OPEN_INFO脫鏈。這樣一來驅(qū)動(dòng)狀態(tài)就得以恢復(fù)。當(dāng)然這期間還做了一寫避免線程競(jìng)爭(zhēng)的處理,使得代碼看起來不是那么簡(jiǎn)單。
StartDispatchThread/StopDispatchThread
這兩個(gè)函數(shù)都不是Stream所需要的標(biāo)準(zhǔn)接口,但卻是中斷服務(wù)程序所需的IST啟動(dòng)和關(guān)閉的手段,所以在這里順便說一下。
StartDispatchThread函數(shù)用于啟動(dòng)IST,主要的工作為調(diào)用InterruptInitialize將系統(tǒng)中斷與相應(yīng)的事件聯(lián)系起來。并啟動(dòng)SerialDispatchThread作為IST.其中調(diào)用了叫做 InterruptDone的函數(shù),該函數(shù)會(huì)調(diào)用OAL中的OEMInterruptDone來完成中斷的響應(yīng)。
StopDispatchThread用于與StartDispatchThread相反的操作。停止的過程相對(duì)要復(fù)雜一些,該函數(shù)首先設(shè)定當(dāng)前線程的優(yōu)先級(jí)與分發(fā)線程相同,以便于在停止該線程的動(dòng)作不會(huì)比釋放內(nèi)存的動(dòng)作快以避免出錯(cuò)。停止的動(dòng)作是讓線程主動(dòng)完成的,具體的方法是提交表示位KillRxThread然后通過Sleep請(qǐng)求調(diào)度,待到IST自己停止。這個(gè)時(shí)候由于IST已經(jīng)停止所以在程序的最后調(diào)用InterruptDisable來屏蔽中斷。
SerialDispatchThread/ SerialEventHandler
SerialDispatchThread/ SerialEventHandler就是串口驅(qū)動(dòng)的中斷分發(fā)程序(也就是IST的所在)。整個(gè)IST被分開寫成兩個(gè)部分---循環(huán)主體和事件處理程序。循環(huán)主體SerialDispatchThread內(nèi)容相對(duì)比較簡(jiǎn)單,反復(fù)等待串口事件并調(diào)用SerialEventHandler對(duì)具體的中斷進(jìn)行處理,直到pSerialHead->KillRxThread被設(shè)置后退出。SerialEventHandler為中斷處理的具體實(shí)現(xiàn),程序在獲得串口事件后運(yùn)行,目的在于對(duì)中斷進(jìn)行進(jìn)一步的判斷并執(zhí)行相應(yīng)的處理。
下面參考兩個(gè)結(jié)構(gòu)體來完成接受和發(fā)送中斷服務(wù)的分析。我們先來看RX_BUFFER_INFO結(jié)構(gòu)。
typedef struct __RX_BUFFER_INFO {
ULONG Read; /* @field Current Read index. */
ULONG Write; /* @field Current Write index. */
ULONG Length; /* @field Length of buffer */
BOOL DataAvail; /* @field BOOL reflecting existence of data. */
PUCHAR RxCharBuffer; /* @field Start of buffer */
CRITICAL_SECTION CS; /* @field Critical section */
} RX_BUFFER_INFO, *PRX_BUFFER_INFO;
用該結(jié)構(gòu)的原因是在驅(qū)動(dòng)內(nèi)部維護(hù)了一個(gè)緩沖器用作驅(qū)動(dòng)和應(yīng)用程序之間的緩沖見下圖.
可以看到在硬件內(nèi)部已經(jīng)有一個(gè)FIFO緩沖器,這保障了驅(qū)動(dòng)的數(shù)據(jù)接收,但由于應(yīng)用不一定能保障在驅(qū)動(dòng)獲得數(shù)據(jù)后及時(shí)將數(shù)據(jù)取走,因此在驅(qū)動(dòng)內(nèi)部維護(hù)了另外一個(gè)緩沖器。在RX_BUFFER_FIFO結(jié)構(gòu)中的read成員為MDD取數(shù)據(jù)時(shí)在FIFO的位置標(biāo)志,而PDD向軟件寫入數(shù)據(jù)的位標(biāo)則對(duì)應(yīng)被稱作Write,DataAvail用作表示緩沖器內(nèi)的數(shù)據(jù)是否有效。而RxCharBuffer則是指向軟件FIFO的指針。當(dāng)收到數(shù)據(jù)的時(shí)候就將write標(biāo)示往上遞增,而程序向驅(qū)動(dòng)取數(shù)得時(shí)候Read遞增,這樣就可以根據(jù)Read和Write成員來維護(hù)這個(gè)FIFO。有了這個(gè)基本思路墊底我們接著看RX的中斷服務(wù)具體如何實(shí)現(xiàn)。這間還會(huì)涉及到流控制的成分。
接收分支:在接收分支的開始計(jì)算軟件緩沖器的剩余空間,如果有剩余的空間的話直接調(diào)用HWRxIntrHandler(PDDa實(shí)現(xiàn))來從硬件緩沖區(qū)獲取剩余空間大小的數(shù)據(jù),若已無剩余空間則建立一個(gè)16byte的臨時(shí)緩沖區(qū),將數(shù)據(jù)讀入該區(qū)域,實(shí)際上這個(gè)緩沖區(qū)在后面根本就不會(huì)被讀取所以這些數(shù)據(jù)全部丟失掉了這也就自然需要統(tǒng)計(jì)硬件/軟件緩沖導(dǎo)致的數(shù)據(jù)丟失(接收不及時(shí)導(dǎo)致)。接下來就是所謂XFlow的流程了,所謂XFlow就是我們上面提到的軟件流控制。也就是用軟件的方法來協(xié)調(diào)發(fā)送和接收端的收發(fā),保障數(shù)據(jù)的完整接收。當(dāng)接收到XOFF/XON標(biāo)記的時(shí)候由于這個(gè)標(biāo)記本身不數(shù)據(jù)數(shù)據(jù)而是控制標(biāo)志,所以要講后面的數(shù)據(jù)全部前置一位,覆蓋掉XON/XOFF的位置。同時(shí)還需要根據(jù)標(biāo)示的具體狀態(tài)來設(shè)置DCB結(jié)構(gòu)中的控制標(biāo)示來控制數(shù)據(jù)收發(fā)流程。如果是XON標(biāo)志,還需要在處理完接收流程后恢復(fù)發(fā)送流程。接收的動(dòng)作會(huì)改變write標(biāo)記的位置,這里需要重新計(jì)算該標(biāo)示。至于硬件流控制的流程中該驅(qū)動(dòng)模型是以緩沖器的75%為分位點(diǎn)來起停收發(fā)的,可用的硬件連線可以是DTR,也可以是RTS(模式相同僅僅是連線不同),這里的操作很簡(jiǎn)單,僅僅是通過計(jì)算緩沖器的存儲(chǔ)狀態(tài)來清除該標(biāo)志就完成了硬件流控制的操作。由于在此過程中IST與其他部分是同步執(zhí)行的,所以這個(gè)時(shí)候如果使用XFlow可能還會(huì)需要做一次安全檢查。這樣接收的流程就結(jié)束了。
發(fā)送分支: 我們同樣來看看TX_BUFFER_INFO結(jié)構(gòu),看樣子似乎該結(jié)構(gòu)維護(hù)了一個(gè)和TX緩沖類似的緩沖區(qū),但事實(shí)上這個(gè)緩沖區(qū)域是不獨(dú)立存在的,發(fā)送的流程因?yàn)榭梢灾苯邮褂盟璋l(fā)送的數(shù)據(jù)的存儲(chǔ)區(qū)域來作為發(fā)送緩沖,所以這個(gè)緩沖沒有獨(dú)立存在的必要。由于使用其它進(jìn)程的數(shù)據(jù)區(qū)域,所以這里增加了權(quán)限控制項(xiàng)的成分,用于突破進(jìn)程間的訪問限制。
typedef struct __TX_BUFFER_INFO {
DWORD Permissions; /* @field Current permissions */
ULONG Read; /* @field Current Read index. */
ULONG Length; /* @field Length of buffer */
PUCHAR TxCharBuffer; /* @field Start of buffer */
CRITICAL_SECTION CS; /* @field Critical section */
} TX_BUFFER_INFO, *PTX_BUFFER_INFO;
下面來看看代碼的具體內(nèi)容。首先是對(duì)OpenCnt的檢查,也就是設(shè)備是否被打開。若當(dāng)會(huì)話已經(jīng)關(guān)閉也就沒有必要繼續(xù)將數(shù)據(jù)送出了,并同時(shí)重置緩沖器的各個(gè)標(biāo)志位。整個(gè)流程比較簡(jiǎn)單,在需要流控制時(shí)設(shè)置RTS或檢查Xflow的情況后將數(shù)據(jù)送入硬件緩沖器.如果在沒有數(shù)據(jù)需要發(fā)送的情況下簡(jiǎn)單的清除中斷標(biāo)示并發(fā)出發(fā)送結(jié)束事件就可以了。至于SetProcPermissions設(shè)置的目的在于獲得訪問其它線程數(shù)據(jù)空間的手段。
至于所謂的Modem和Line的情況則全部交給PDD來處理,我們后面再討論。在這些全部都處理完了以后如果前面處理了接收,則發(fā)出接收(有可用的數(shù)據(jù)用于接收)的消息,讓程序開始接收。后面還跟進(jìn)了一個(gè)EvaluateEventFlag 函數(shù),這個(gè)函數(shù)用于產(chǎn)生標(biāo)準(zhǔn)的Communication Events EV_RXFLAG,而且由于該驅(qū)動(dòng)程序本身支持mult-open模式,所以需要將該事件送發(fā)到所有的實(shí)例中去。在ist期間有一些互鎖、臨界區(qū)的操作,因?yàn)椴挥绊懥鞒?,同步化的?nèi)容這里我沒有提。中斷服務(wù)的分析大致就是如此,沒有涉及到邏輯環(huán)節(jié)在后面的PDD部分再進(jìn)行討論。
COM_Read
COM_Read是獲取串口所接收到數(shù)據(jù)的操作,在前面的IST中沒有看到對(duì)RX buffer進(jìn)行修改Read標(biāo)記的操作,也就是這兒來完成的。該函數(shù)有三個(gè)參數(shù),第一個(gè)參數(shù)是從上面的COM_OPEN通過設(shè)備管理器交換來的,后兩個(gè)參數(shù)與文件系統(tǒng)的使用方法完全一樣,一個(gè)是接受緩沖指針,另一個(gè)是長(zhǎng)度。代碼的開始照樣是例行公事的參數(shù)檢查,包括對(duì)存取權(quán)限,OpenCnt等。之后計(jì)算超時(shí)時(shí)間,如果設(shè)定了超時(shí)讀取動(dòng)作會(huì)在超時(shí)后返回,不管是否讀到了足夠長(zhǎng)度的數(shù)據(jù)。隨后就是簡(jiǎn)單對(duì)軟件緩沖進(jìn)行讀取的操作了,讀取的操作是在RX_CS中完成的。下面要處理器的主要就是幾種異常的情形,讀取過程中設(shè)備被關(guān)閉/取消讀取和超時(shí)。最后在讀取的過程中需要處理的就只是流控制的成本了。首先是軟件流的情形,如果緩沖的狀態(tài)由高于分位點(diǎn)至分位點(diǎn)以下就發(fā)出XON標(biāo)記,啟動(dòng)發(fā)送端的發(fā)送。而硬件流的情形無論是RTS還是DTR與軟件流的相類似,同樣由一個(gè)分為點(diǎn)(50%)來決定發(fā)出啟動(dòng)發(fā)送端的信號(hào),僅僅是這里使用的具體措施的不同。這些硬件信號(hào)的發(fā)出都是由PDD來完成的,其中包括HWSetRTS和HWSetDTR(2選一)。至此Read的流程就結(jié)束了。
COM_Write
COM_Write是與COM_Read相對(duì)應(yīng)的操作。所傳遞的參數(shù)的形式也是很相似的,僅僅是數(shù)據(jù)流向的不同。在程序的開始,同樣也是參數(shù)檢查,內(nèi)容與COM_Read一致。在數(shù)據(jù)檢查完成之后進(jìn)入臨界區(qū)(保障多線程下的獨(dú)占)將送入的目標(biāo)地址和長(zhǎng)度設(shè)置為TX buffer,待到數(shù)據(jù)發(fā)送完成事件后調(diào)用DoTxData來啟動(dòng)發(fā)送。這里啟動(dòng)發(fā)送的目的在于獲得硬件中斷維持發(fā)送流程。在這里DoTxData是作為兩種狀態(tài)來執(zhí)行的,在通過COM_Write的執(zhí)行的過程中是在device.exe所創(chuàng)建的線程空間內(nèi)執(zhí)行的,但由系統(tǒng)中斷事件主動(dòng)啟動(dòng)的過程中屬于IST本身的的進(jìn)程空間,這樣在COM_Write中調(diào)用DoTxData之前設(shè)置的權(quán)限代碼(由GetCurrentPermissions獲得)就可以由TxBufferInfo傳遞到IST中去使得中斷過程也具備了訪問緩沖的權(quán)限(結(jié)合前面說明IST的流程)。當(dāng)提交中斷處理發(fā)送后待到pSerialHead->hTransmitEvent被設(shè)置或是異?;虺瑫r(shí)后就結(jié)束了發(fā)送流程,在這部分的最后。與COM_Read類似需要處理一些異常情況,當(dāng)然如果使用了硬件流控制還需要在這里清除掉發(fā)送請(qǐng)求信號(hào),當(dāng)這些狀態(tài)處理完成以后發(fā)送EV_TXEMPTY事件通告所有open的句柄發(fā)送結(jié)束就完成了該部分的流程。
COM_PowerUp/ COM_PowerDown
這兩個(gè)函數(shù)的調(diào)用都由CE的電源事件來引發(fā),MDD并沒有對(duì)這兩個(gè)函數(shù)進(jìn)行處理,僅僅是將其傳遞給PDD。
COM_IOControl
該函數(shù)用于實(shí)現(xiàn)向設(shè)備發(fā)送命令的功能。由于代碼本身沒有什么流程或邏輯性可言,全都是單獨(dú)的實(shí)現(xiàn),下面就用列表的方式大致的說一下這些命令字和其實(shí)現(xiàn)。
Command | Note |
IOCTL_PSL_NOTIFY | 在調(diào)用驅(qū)動(dòng)的進(jìn)程退出時(shí)產(chǎn)生,并不是串行驅(qū)動(dòng)專有的IO命令。這里會(huì)調(diào)用 ProcessExiting函數(shù)進(jìn)行處理。這個(gè)函數(shù)的內(nèi)容放到后面來看。 |
IOCTL_SERIAL_SET_BREAK_ON | 中斷(暫停)serial當(dāng)前的發(fā)送或是接收,具體實(shí)現(xiàn)在PDD中 |
IOCTL_SERIAL_SET_BREAK_OFF | 從中斷(暫停)狀態(tài)恢復(fù),具體實(shí)現(xiàn)在PDD中 |
IOCTL_SERIAL_SET_DTR | 將DTR引線拉高。(直接調(diào)用PDD實(shí)現(xiàn)) |
IOCTL_SERIAL_CLR_DTR | 將DTR引線拉低。(直接調(diào)用PDD實(shí)現(xiàn)) |
IOCTL_SERIAL_SET_RTS | 將RTS引線拉高。(直接調(diào)用PDD實(shí)現(xiàn)) |
IOCTL_SERIAL_CLR_RTS | 將RTS引線拉低。(直接調(diào)用PDD實(shí)現(xiàn)) |
IOCTL_SERIAL_SET_XOFF | 軟件流模式下中止數(shù)據(jù)發(fā)送(Xflow控制) |
IOCTL_SERIAL_SET_XON | 軟件流模式下啟動(dòng)數(shù)據(jù)發(fā)送(XFlow控制) |
IOCTL_SERIAL_GET_WAIT_MASK | 獲取當(dāng)前的事件對(duì)象 |
IOCTL_SERIAL_SET_WAIT_MASK | 設(shè)置事件對(duì)象,這個(gè)過程相對(duì)比較麻煩,要將當(dāng)前獲得的事件對(duì)象mask設(shè)置到所有的Open實(shí)例中,這和前面的 EvaluateEventFlag過程相似。 |
IOCTL_SERIAL_WAIT_ON_MASK | 等待與提供的事件相同的事件發(fā)生,實(shí)現(xiàn)實(shí)體是 WaitCommEvent后面再討論。 |
IOCTL_SERIAL_GET_COMMSTATUS | 清除異常并返回當(dāng)前狀態(tài)(由PDD實(shí)現(xiàn)) |
IOCTL_SERIAL_GET_MODEMSTATUS | 獲取modem狀態(tài)(由PDD實(shí)現(xiàn)) |
IOCTL_SERIAL_GET_PROPERTIES | 獲取通訊************************(由PDD實(shí)現(xiàn)) |
IOCTL_SERIAL_SET_TIMEOUTS | 設(shè)置超時(shí)時(shí)間(包含PDD實(shí)現(xiàn)) |
IOCTL_SERIAL_GET_TIMEOUTS | 獲取超時(shí)時(shí)間 |
IOCTL_SERIAL_PURGE | 清除制定的發(fā)送或接收緩沖內(nèi)的數(shù)據(jù)(含PDD實(shí)現(xiàn)) |
IOCTL_SERIAL_SET_QUEUE_SIZE | 不明,若知道請(qǐng)告知 |
IOCTL_SERIAL_IMMEDIATE_CHAR | 為擴(kuò)展功能,在發(fā)送數(shù)據(jù)前設(shè)置一個(gè)標(biāo)志數(shù) |
IOCTL_SERIAL_GET_DCB | 獲取DCB數(shù)據(jù)結(jié)構(gòu) |
IOCTL_SERIAL_SET_DCB | 設(shè)置DCB數(shù)據(jù)結(jié)構(gòu) |
IOCTL_SERIAL_ENABLE_IR | 啟動(dòng)紅外模式(由PDD實(shí)現(xiàn)) |
IOCTL_SERIAL_DISABLE_IR | 禁用紅外模式(由PDD實(shí)現(xiàn)) |
到這里MDD的主要函數(shù)都已經(jīng)介紹過了,下面幾個(gè)函數(shù)是在DeviceIOControl中用到的。這里順便也來看一下:
ProcessExiting
該函數(shù)在IOCTL_PSL_NOTIFY命令的執(zhí)行過程中被調(diào)用,之前的情景是使用驅(qū)動(dòng)的進(jìn)程在被取消的過程中,在這里主要是清除所有正在會(huì)話中的線程。以便直接kill掉該進(jìn)程。
WaitCommEvent
事實(shí)上該函數(shù)為SerialAPI WaitCommEvent在驅(qū)動(dòng)內(nèi)的實(shí)現(xiàn),其作用為阻塞線程直道某一固定的串口通告(事件消息)發(fā)生。在具體的實(shí)現(xiàn)中,是用WaitForSingleObject來實(shí)現(xiàn)阻塞。在進(jìn)入阻塞之前,函數(shù)適用一個(gè)循環(huán)主體首先查詢是否存在已有的通告與等待通告相符,若沒有就等待下一次事件發(fā)生,待事件發(fā)生再次進(jìn)行檢查。如此循環(huán)達(dá)到阻塞的目的。
ApplyDCB
DCB數(shù)據(jù)結(jié)構(gòu)是描述串行口波特率,流控制,奇偶效驗(yàn)等資料的載體。該函數(shù)是MDD設(shè)置DCB數(shù)據(jù)結(jié)構(gòu)至驅(qū)動(dòng)內(nèi)部和硬件的手段,這里使用了大量的PDD操作來完成硬件設(shè)置。
總結(jié):
在驅(qū)動(dòng)實(shí)現(xiàn)方面,除去所謂Multi-Open的處理外,串口的MDD并沒有什么特別的之處,在掌握了硬件行為和應(yīng)用軟件行為后很容易能讀懂其間的代碼。
linux操作系統(tǒng)文章專題:linux操作系統(tǒng)詳解(linux不再難懂)
評(píng)論