1. 程式人生 > >wince串列埠驅動

wince串列埠驅動

歡迎加入Wince技術討論群QQ#326444254

 

雖然串列埠通訊已經是普遍的標準而且廣為大家熟知,但驅動中涉及的部分內容也可能在平時的應用中並不是很常用到,在這裡做一個簡單的介紹待後面說明到具體程式碼的時候可以連貫一些。 


        序列通訊介面是目前十分流行的通訊介面之一。由於其電氣介面的簡單性使其在計算機領域的應用相當的廣泛。在這裡提到的序列通訊介面主要是指UART(通用序列)和IRDA兩種。通常的序列連線電氣連線上有3wire和9wire兩種。3wire的接線方式下定義了傳送、接收和地三根連線。其用途就如名稱一樣分別用於傳送、接收。

       通常在序列介面控制器上會有兩個FIFO用作接收和傳送的緩衝,當接收到資料後會直接將接收到的資料置入該緩衝器,並同時由控制電路向本地匯流排發出通知,以便讓本地匯流排將緩衝器內的資料讀走,這樣在響應(等待和讀取)的過程中仍然能通過緩衝器來接收資料。而傳送傳送的過程剛剛相反,本地匯流排可一直向傳送緩衝寫入資料直到器填滿為止,而無需對每個資料的傳送進行等待。這就是基本的收發流程(這部分邏輯流程相信大家是最熟悉的)。這一點在3wire和9wire中都是相同的。但是我們考慮下面的情況,如果接收一方的響應由於某種原因的干擾(如處理器被其他中斷服務佔用)的時候可能就來不及相應之前ReceiveFIFO就可能被填滿了,這樣後續傳送過來的資料就會丟失,這樣在需要資料可靠傳輸的情況下序列通訊的弊端也就顯示出來了。如需要資料的可靠傳輸就需要對資料流的收發進行控制。在9wire中將序列連線定義為如下形式。 


針號 1 2 3 4 5 6 7 8 9
縮寫 DCD RXD TXD DTR GND DSR RTS CTS DELL
功能說明 資料載波檢測 接收資料 傳送資料 資料終端就緒 訊號地 資料裝置就緒 請求傳送 清除傳送 振鈴指示

       也就是說在原3wire的基礎上增加了DCD,DTR,DSR,RTS,CTS,DELL六個控制線。其中RTS/CTS用於流控制,另外的DCD和DELL則留作連線modem使用。有了專門的硬體流控制引腳也就使得流控制成為可能,以完成收發兩端的匹配使得資料可以可靠的傳輸。用RTS/CTS(請求傳送/清除傳送)流控制時,應將通訊兩端的RTS、CTS線對應相連).在傳送端準備傳送資料之前設定RTS(Request to send)也就使傳送請求線,若接收端以作好接收準備,就啟動響應的CTS(Clear to send)引線。這樣,收發雙發就進入資料傳輸狀態,在此過程中如若接收端處理資料的速度低於傳送端的傳送速度,接收一端還可以設定CTS引線恢復原來阻塞得狀態以暫時中斷資料傳輸,之後若需要恢復資料傳輸恢復CTS狀態即可。這樣UART的傳輸即實現了流控制,保障了資料傳輸的完備性。 


       在這裡還要說一下軟體流控制,雖然硬體已經可以完成流控制的任務但很多少時候受到連線數的限制不能使用硬體流控制也就設計了專門的軟體流控制的方法。現在回到3線傳輸的情景,若接收端接收資料過程中緩衝器的負載到達某一限制(也就是留出一定的緩衝空間)時接收端向傳送端傳送一個特殊的標示位(接收停止位),當傳送端收到該標示的時候就停止傳送,直到接收端緩衝器低於另一限制後傳送標示(接收許可位)給傳送端,這樣就可以控制資料流的傳輸起停。這種軟體流控制是在給緩衝器留餘量來完成的,在收發雙端處理器速度差很大的時候就不太適用了,就必須要用硬體流控制。 

       其他幾個引腳都是與modem相關的,DSR資料裝置準備好(Data set ready)用於表明MODEM處於可以使用的狀態。DTR資料終端準備好(Data terminal ready)表明資料終端可以使用。這兩個訊號用於檢查Modem是否連線。DELL腳當有電話撥入時Modem將會設定這個引腳。DCD訊號是當Modem接收到數字載波訊號的時候被設定,用於瞭解Modem接收訊號的情況。 

至於剩下的奇偶效驗和停止位設定就只是需要針對暫存器設定無需軟體干涉就可以完成了。下面我們來看具體的驅動程式。 

架構

       在wince中串列埠的驅動實現是有固定模型的,ce中的串列埠模型遵循ISO/OSI網路通訊模型(7層),就是說串列埠屬於CE網路模組的一個部分。其中rs232介面(或其它的物理介質)實現網路的物理層,而驅動和serialAPI共同組成資料鏈路層,其它部分都沒有做定義。在典型的應用中,serialAPI與間接通過TAPI或直接與ActiveSync互動,組成CE網路的一部分。而紅外本身的協議就相對複雜的多,它有專門的一套模型來描述其使用規則,對紅外裝置本身瞭解不多也就不能深入下去。在串列埠的這一側,整個驅動模型也是相當的複雜的,但所幸的是驅動僅僅使用到SerialAPI這一層,在這個層次上串列埠的行為還是相對簡單的。


       我們這裡僅僅涉及上面所提到的Serial/irda Driver這部分(綠色部分)。在wince提供的驅動例程中串列埠/紅外驅動採用分層結構設計,MDD提供框架性的實現,負責提供OS所需的基本實現,並將程式碼設計與具體的硬體設計無關。而PDD提供了對硬體操作相應的程式碼。這些程式碼通過結構HWOBJ來相互聯絡。對於MDD+PDD的整體驅動來看,串列埠驅動模型是作為Stream來實現的。 兩者合一以達到實現驅動的目的。DDSI就是指這兩個部分之間的介面,這個介面並非受到強制的物理/邏輯關係來約束,而是人為的規定的。在涉及到一種特定硬體我們進行鍼對實現的時候往往需要的是瞭解硬體的物理特性和控制邏輯,然後根據DDSI的約束就來進行實現。對於這裡描述的驅動模型而言結合關鍵在於結構指標HWOBJ的使用和具體實現。在實際的驅動應用中僅僅需要實現HWOBJ相關的一系列函式,而無需從驅動頂層完全開發。串列埠驅動模型作為一種常用驅動模型在windowsCE中常常用於串列埠/紅外/USB Client的具體實現。該驅動模型中對全功能的串列埠進行了定義,除了常用的TX和RX引線定義以外,針對DTR、RTS等功能引腳都進行了支援,使得用該模型設計的串列埠驅動支援流控制、具備驅動Modem等裝置的能力。 
事實上,如果需要的話完全可以將該驅動一體化設計(拋開PDD-MDD的劃分,也就無須DDSI)。也就是不使用現有的驅動架構來進行實現。考慮到串列埠驅動的使用頻率和執行效率要求都不是很苛刻的情況下拋棄驅動架構另外實現的就沒有多大必要了。 
對於驅動本身而言,序列驅動從功能和實現上相當的簡單,確具被相當全面的成分,對該驅動的分析和了解無疑是學習流式驅動程式很好的典範。 
程式碼分析 

       在開始具體程式碼之前我們先來看看,相關的一些結構。 HWOBJ是相應的硬體裝置操作的抽象集合。結構的定義後的註釋與實際的用途有點點出入,BandFlags指定IST的啟動時間,可選為在初始化過程啟動或是在開啟裝置的時候起動ISR.而第二個引數則是指定攔截的具體的系統中斷號。最後一個引數是一個結構,該結構定義了硬體操作的各式行為函式的指標,MDD正是通過這些函式來訪問具體的PDD操作。 
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則是代表具體硬體操作函式指標的集合,該結構所指向的函式包括了初始化、開啟、關閉、接收、傳送、設定Baudrate等一系列操作。結構存在就像紐帶一樣聯絡著PDD中的具體實現和MDD中的抽象操作。PDD的實現必須遵循HW_VTBL中所描述的函式形式,並構造出相應的HW_VTBL例項。驅動的編寫就是針對這些函式來一一進行實現。 
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;交待了上述兩個結構以後我們來看看具體的程式碼,為保障對系統架構的清晰認識,我們將MDD的程式碼和PDD的程式碼分開進行分析。 

MDD部分

       由於串列埠驅動由Device.exe直接呼叫,所以MDD部分是以完整的Stream介面給出的. 也就具備基於Stream介面的驅動程式所需的函式實現,包括COM_Init,COM_Deinit 
,COM_Open,COM_Close ,COM_Read ,COM_Write, COM_Seek,, COM_PowerUp, COM_PowerDown, COM_IOControl幾個基本實現。由於串列埠傳送/接收的資訊並不能定位,而僅僅是簡單的傳送,所以COM_Seek僅僅是形式上實現了一下。 

COM_Init
       COM_Init是該驅動的初始化函式,在裝置管理器載入該驅動後首先呼叫,用於初始化所需的變數,硬體裝置等資源。該過程分配代表裝置硬體例項的資料結構,並通過硬體抽象介面HWInit初始化硬體。同時該函式會呼叫InterruptInitialize為接收核心中的邏輯中斷建立相應事件並初始化臨界區。該函式還需要得到硬體緩衝器的實體地址和獲知該緩衝器的大小(該衝器最小為2K)。最後它將建立相應的緩衝作為接收的中介。下面我們來看這個函式的實現過程。 
在函式中定義了兩個重要的變數。pSerialHead和pHWHead.前者用於描述相應的串列埠的狀態,後者則是對應硬體的資料抽象。首先為pSerialHead分配空間和初始化連結串列和臨界區等資料並同時為接收和傳送中斷建立事件。然後再從登錄檔中獲得當前的註冊項值(由於device.exe是根據登錄檔鍵值來呼叫驅動的,當前鍵登錄檔項指的就是與上述鍵值在同一根下的註冊項)。得到DeviceArrayIndex、Priority256鍵下的具體值後就可以呼叫GetSerialObject(在PDD中實現)來獲得具體的HWObj物件,並通過該物件來呼叫硬體初始化函數了。(由於在這裡已經對硬體進行了初始化,之後的返回都需要呼叫COM_Deinit來完成。)由於硬體初始化(實際的驅動初始化程式碼)已經得到執行這個時候就只有分配和初始化緩衝的工作需要做了。所以呼叫HWGetRxBufferSize(PDD程式碼)來獲取PDD中設定的緩衝大小,並根據返回的具體值分配緩衝。最後如果BindFlags被設定為THREAD_AT_INIT就再呼叫StartDispatchThread啟動分發執行緒(實際的IST)。這樣就完成了系統初始化的操作。 
COM_Deinit
當驅動被稱被卸下的時候該事件啟動,用作與COM_Init相反的操作。這個過程大致會釋放驅動中所使用的資源,停止期間建立的執行緒等操作。具體說來,大致為停止在MDD中的所有IST,和釋放記憶體資源和臨界區等系統資源。同時還需呼叫HWDeinit來釋放PDD中所使用到的系統資源。 
COM_Open
COM_Oepn在CreateFile後被呼叫,用於以讀/寫模式開啟裝置,並初始化所需要的空間/資源等,建立相應的例項,為後面的操作做好準備。這裡的程式碼相對比較容易,下面就簡單講一下。既然是初始化,肯定就免不了對引數的檢查。首先檢查通過COM_Init返回的pHead結構是否有效,這裡雖然沒有顯式的在這兩個函式之間傳遞引數,而是在裝置管理器的內部傳遞這個引數的。 
之後是檢查檔案系統傳遞過來的Open控制代碼中的Open模式是否有效,這個引數由應用程式產生,通過檔案系統直接傳遞到驅動。之後就開始初始化的操作,在這裡將會建立相應的HW_OPEN_INFO實體。下面和為該結構的定義。 
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 
結構中的第一個引數指向我們前面提到的HW_INDEP_INFO結構,第二個引數為操作許可權碼,也就是READ/WRITE這類的許可權。第三個引數為共享模式,以確定是否支援獨佔。這兩個引數都是與檔案系統的內容對應的。而CommEvent則對應於本例項的事件。由於驅動架構支援多個OPEN操作例項的存在,所以這裡維護了一個連結串列來聯絡這些結構。在這裡由於IST的啟動可以在COM_Init和COM_Open中進行,還有處理器啟動IST的內容。準備好HW_OPEN_INFO結構後就可以呼叫HWOpen(PDD)來進行PDD所需的Open操作了。Open操作完成後呼叫HWPurgeComm(PDD)來處理(取消或等待)當前仍在通訊狀態的任務。然後重置軟體FIFO就基本完成了COM_Open的動作了。 
事實上這裡主要是對所需的資料結構進行處理,對於硬體的具體操作都留給PDD去做了,MDD所維護的僅僅是一個架構性的程式碼。Open操作完成後,驅動就進入了工作狀態這個時候。 
COM_Close
COM_Close為與COM_Open相對應的操作。這期間的目的是釋放COM_Open所使用的系統資源,除此以外如果在COM_Open期間建立了相應的IST還需要停止該執行緒,在最後將該HW_OPEN_INFO脫鏈。這樣一來驅動狀態就得以恢復。當然這期間還做了一寫避免執行緒競爭的處理,使得程式碼看起來不是那麼簡單。 
StartDispatchThread/StopDispatchThread 
這兩個函式都不是Stream所需要的標準介面,但卻是中斷服務程式所需的IST啟動和關閉的手段,所以在這裡順便說一下。 
StartDispatchThread函式用於啟動IST,主要的工作為呼叫InterruptInitialize將系統中斷與相應的事件聯絡起來。並啟動SerialDispatchThread作為IST.其中呼叫了叫做 InterruptDone的函式,該函式會呼叫OAL中的OEMInterruptDone來完成中斷的響應。 
StopDispatchThread用於與StartDispatchThread相反的操作。停止的過程相對要複雜一些,該函式首先設定當前執行緒的優先順序與分發執行緒相同,以便於在停止該執行緒的動作不會比釋放記憶體的動作快以避免出錯。停止的動作是讓執行緒主動完成的,具體的方法是提交表示位KillRxThread然後通過Sleep請求排程,待到IST自己停止。這個時候由於IST已經停止所以在程式的最後呼叫InterruptDisable來遮蔽中斷。 
SerialDispatchThread/ SerialEventHandler 
SerialDispatchThread/ SerialEventHandler就是串列埠驅動的中斷分發程式(也就是IST的所在)。整個IST被分開寫成兩個部分---迴圈主體和事件處理程式。迴圈主體SerialDispatchThread內容相對比較簡單,反覆等待串列埠事件並呼叫SerialEventHandler對具體的中斷進行處理,直到pSerialHead->KillRxThread被設定後退出。SerialEventHandler為中斷處理的具體實現,程式在獲得串列埠事件後執行,目的在於對中斷進行進一步的判斷並執行相應的處理。 
下面參考兩個結構體來完成接受和傳送中斷服務的分析。我們先來看RX_BUFFER_INFO結構。 
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; 
       用該結構的原因是在驅動內部維護了一個緩衝器用作驅動和應用程式之間的緩衝. 
       在硬體內部已經有一個FIFO緩衝器,這保障了驅動的資料接收,但由於應用不一定能保障在驅動獲得資料後及時將資料取走,因此在驅動內部維護了另外一個緩衝器。在RX_BUFFER_FIFO結構中的read成員為MDD取資料時在FIFO的位置標誌,而PDD向軟體寫入資料的位標則對應被稱作Write,DataAvail用作表示緩衝器內的資料是否有效。而RxCharBuffer則是指向軟體FIFO的指標。當收到資料的時候就將write標示往上遞增,而程式向驅動取數得時候Read遞增,這樣就可以根據Read和Write成員來維護這個FIFO。有了這個基本思路墊底我們接著看RX的中斷服務具體如何實現。這間還會涉及到流控制的成分。 
接收分支:在接收分支的開始計算軟體緩衝器的剩餘空間,如果有剩餘的空間的話直接呼叫HWRxIntrHandler(PDDa實現)來從硬體緩衝區獲取剩餘空間大小的資料,若已無剩餘空間則建立一個16byte的臨時緩衝區,將資料讀入該區域,實際上這個緩衝區在後面根本就不會被讀取所以這些資料全部丟失掉了這也就自然需要統計硬體/軟體緩衝導致的資料丟失(接收不及時導致)。接下來就是所謂XFlow的流程了,所謂XFlow就是我們上面提到的軟體流控制。也就是用軟體的方法來協調發送和接收端的收發,保障資料的完整接收。當接收到XOFF/XON標記的時候由於這個標記本身不資料資料而是控制標誌,所以要講後面的資料全部前置一位,覆蓋掉XON/XOFF的位置。同時還需要根據標示的具體狀態來設定DCB結構中的控制標示來控制資料收發流程。如果是XON標誌,還需要在處理完接收流程後恢復傳送流程。接收的動作會改變write標記的位置,這裡需要重新計算該標示。至於硬體流控制的流程中該驅動模型是以緩衝器的75%為分位點來起停收發的,可用的硬體連線可以是DTR,也可以是RTS(模式相同僅僅是連線不同),這裡的操作很簡單,僅僅是通過計算緩衝器的儲存狀態來清除該標誌就完成了硬體流控制的操作。由於在此過程中IST與其他部分是同步執行的,所以這個時候如果使用XFlow可能還會需要做一次安全檢查。這樣接收的流程就結束了。 
傳送分支: 我們同樣來看看TX_BUFFER_INFO結構,看樣子似乎該結構維護了一個和TX緩衝類似的緩衝區,但事實上這個緩衝區域是不獨立存在的,傳送的流程因為可以直接使用所需傳送的資料的儲存區域來作為傳送緩衝,所以這個緩衝沒有獨立存在的必要。由於使用其它程序的資料區域,所以這裡增加了許可權控制項的成分,用於突破程序間的訪問限制。 
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; 
下面來看看程式碼的具體內容。首先是對OpenCnt的檢查,也就是裝置是否被開啟。若當會話已經關閉也就沒有必要繼續將資料送出了,並同時重置緩衝器的各個標誌位。整個流程比較簡單,在需要流控制時設定RTS或檢查Xflow的情況後將資料送入硬體緩衝器.如果在沒有資料需要傳送的情況下簡單的清除中斷標示併發出發送結束事件就可以了。至於SetProcPermissions設定的目的在於獲得訪問其它執行緒資料空間的手段。 
至於所謂的Modem和Line的情況則全部交給PDD來處理,我們後面再討論。在這些全部都處理完了以後如果前面處理了接收,則發出接收(有可用的資料用於接收)的訊息,讓程式開始接收。後面還跟進了一個EvaluateEventFlag 函式,這個函式用於產生標準的Communication Events EV_RXFLAG,而且由於該驅動程式本身支援mult-open模式,所以需要將該事件送發到所有的例項中去。在ist期間有一些互鎖、臨界區的操作,因為不影響流程,同步化的內容這裡我沒有提。中斷服務的分析大致就是如此,沒有涉及到邏輯環節在後面的PDD部分再進行討論。 

COM_Read 
COM_Read是獲取串列埠所接收到資料的操作,在前面的IST中沒有看到對RX buffer進行修改Read標記的操作,也就是這兒來完成的。該函式有三個引數,第一個引數是從上面的COM_OPEN通過裝置管理器交換來的,後兩個引數與檔案系統的使用方法完全一樣,一個是接受緩衝指標,另一個是長度。程式碼的開始照樣是例行公事的引數檢查,包括對存取許可權,OpenCnt等。之後計算超時時間,如果設定了超時讀取動作會在超時後返回,不管是否讀到了足夠長度的資料。隨後就是簡單對軟體緩衝進行讀取的操作了,讀取的操作是在RX_CS中完成的。下面要處理器的主要就是幾種異常的情形,讀取過程中裝置被關閉/取消讀取和超時。最後在讀取的過程中需要處理的就只是流控制的成本了。首先是軟體流的情形,如果緩衝的狀態由高於分位點至分位點以下就發出XON標記,啟動傳送端的傳送。而硬體流的情形無論是RTS還是DTR與軟體流的相類似,同樣由一個分為點(50%)來決定發出啟動傳送端的訊號,僅僅是這裡使用的具體措施的不同。這些硬體訊號的發出都是由PDD來完成的,其中包括HWSetRTS和HWSetDTR(2選一)。至此Read的流程就結束了。 
COM_Write 
COM_Write是與COM_Read相對應的操作。所傳遞的引數的形式也是很相似的,僅僅是資料流向的不同。在程式的開始,同樣也是引數檢查,內容與COM_Read一致。在資料檢查完成之後進入臨界區(保障多執行緒下的獨佔)將送入的目標地址和長度設定為TX buffer,待到資料傳送完成事件後呼叫DoTxData來啟動傳送。這裡啟動傳送的目的在於獲得硬體中斷維持傳送流程。在這裡DoTxData是作為兩種狀態來執行的,在通過COM_Write的執行的過程中是在device.exe所建立的執行緒空間內執行的,但由系統中斷事件主動啟動的過程中屬於IST本身的的程序空間,這樣在COM_Write中呼叫DoTxData之前設定的許可權程式碼(由GetCurrentPermissions獲得)就可以由TxBufferInfo傳遞到IST中去使得中斷過程也具備了訪問緩衝的許可權(結合前面說明IST的流程)。當提交中斷處理髮送後待到pSerialHead->hTransmitEvent被設定或是異常或超時後就結束了傳送流程,在這部分的最後。與COM_Read類似需要處理一些異常情況,當然如果使用了硬體流控制還需要在這裡清除掉髮送請求訊號,當這些狀態處理完成以後傳送EV_TXEMPTY事件通告所有open的控制代碼傳送結束就完成了該部分的流程。 
COM_PowerUp/ COM_PowerDown
這兩個函式的呼叫都由CE的電源事件來引發,MDD並沒有對這兩個函式進行處理,僅僅是將其傳遞給PDD。 
COM_IOControl
該函式用於實現向裝置傳送命令的功能。由於程式碼本身沒有什麼流程或邏輯性可言,全都是單獨的實現,下面就用列表的方式大致的說一下這些命令字和其實現。 
Command Note
IOCTL_PSL_NOTIFY 在呼叫驅動的程序退出時產生,並不是序列驅動專有的IO命令。這裡會呼叫 ProcessExiting函式進行處理。這個函式的內容放到後面來看。 
IOCTL_SERIAL_SET_BREAK_ON 中斷(暫停)serial當前的傳送或是接收,具體實現在PDD中 
IOCTL_SERIAL_SET_BREAK_OFF 從中斷(暫停)狀態恢復,具體實現在PDD中 
IOCTL_SERIAL_SET_DTR 將DTR引線拉高。(直接呼叫PDD實現) 
IOCTL_SERIAL_CLR_DTR 將DTR引線拉低。(直接呼叫PDD實現) 
IOCTL_SERIAL_SET_RTS 將RTS引線拉高。(直接呼叫PDD實現) 
IOCTL_SERIAL_CLR_RTS 將RTS引線拉低。(直接呼叫PDD實現) 
IOCTL_SERIAL_SET_XOFF 軟體流模式下中止資料傳送(Xflow控制) 
IOCTL_SERIAL_SET_XON 軟體流模式下啟動資料傳送(XFlow控制) 
IOCTL_SERIAL_GET_WAIT_MASK 獲取當前的事件物件 
IOCTL_SERIAL_SET_WAIT_MASK 設定事件物件,這個過程相對比較麻煩,要將當前獲得的事件物件mask設定到所有的Open例項中,這和前面的 EvaluateEventFlag過程相似。 
IOCTL_SERIAL_WAIT_ON_MASK 等待與提供的事件相同的事件發生,實現實體是 WaitCommEvent後面再討論。 
IOCTL_SERIAL_GET_COMMSTATUS 清除異常並返回當前狀態(由PDD實現) 
IOCTL_SERIAL_GET_MODEMSTATUS 獲取modem狀態(由PDD實現) 
IOCTL_SERIAL_GET_PROPERTIES 獲取通訊************************(由PDD實現) 
IOCTL_SERIAL_SET_TIMEOUTS 設定超時時間(包含PDD實現) 
IOCTL_SERIAL_GET_TIMEOUTS 獲取超時時間 
IOCTL_SERIAL_PURGE 清除制定的傳送或接收緩衝內的資料(含PDD實現) 
IOCTL_SERIAL_SET_QUEUE_SIZE 不明,若知道請告知 
IOCTL_SERIAL_IMMEDIATE_CHAR 為擴充套件功能,在傳送資料前設定一個標誌數 
IOCTL_SERIAL_GET_DCB 獲取DCB資料結構 
IOCTL_SERIAL_SET_DCB 設定DCB資料結構 
IOCTL_SERIAL_ENABLE_IR 啟動紅外模式(由PDD實現) 
IOCTL_SERIAL_DISABLE_IR 禁用紅外模式(由PDD實現) 
到這裡MDD的主要函式都已經介紹過了,下面幾個函式是在DeviceIOControl中用到的。這裡順便也來看一下: 
ProcessExiting
該函式在IOCTL_PSL_NOTIFY命令的執行過程中被呼叫,之前的情景是使用驅動的程序在被取消的過程中,在這裡主要是清除所有正在會話中的執行緒。以便直接kill掉該程序。 
WaitCommEvent 
事實上該函式為SerialAPI WaitCommEvent在驅動內的實現,其作用為阻塞執行緒直道某一固定的串列埠通告(事件訊息)發生。在具體的實現中,是用WaitForSingleObject來實現阻塞。在進入阻塞之前,函式適用一個迴圈主體首先查詢是否存在已有的通告與等待通告相符,若沒有就等待下一次事件發生,待事件發生再次進行檢查。如此迴圈達到阻塞的目的。 
ApplyDCB
DCB資料結構是描述序列口波特率,流控制,奇偶效驗等資料的載體。該函式是MDD設定DCB資料結構至驅動內部和硬體的手段,這裡使用了大量的PDD操作來完成硬體設定。 
總結:
在驅動實現方面,除去所謂Multi-Open的處理外,串列埠的MDD並沒有什麼特別的之處,在掌握了硬體行為和應用軟體行為後很容易能讀懂其間的程式碼。