串口驅動分析


http://nasiry.cnblogs.com/archive/2005/04/12/136175.aspx

Auth:nasiry

date: 2005年4月12日
abort: windowsCE.net 420串口驅動分析


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

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

 

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

 

針號             功能說明
 1    DCD    數據載波檢測 
 2    RXD    接收數據
 3    TXD    發送數據
 4    DTR    數據終端設備就緒
 5    GND    信號地
 6    DSR     數據通信設備就緒
 7    RTS      請求發送
 8    CTS      清除發送 
 9    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_Open在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_MCommand
 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
ASK
 獲取當前的事件對象
 
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並沒有什麼特別的之處,在掌握了硬件行爲和應用軟件行爲後很容易能讀懂其間的代碼。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章