我也来分析Windows CE串口驱动----基于Windows CE 5.0 S3C2440 BSP
一、Windows CE 5.0串口驱动总体框架
先看下串口驱动的源码分布,在S3C2440的BSP的Src/Drivers/Serial目录下:
pdds3c2440_ser.h
pdds3c2440_ser.cpp
ser_smdk2440.cpp
把WINCE500/PUBLIC/COMMON/OAK/CSP/ARM/SAMSUNG/S3C2410X/SERIAL下的串口样本驱动程序代码文件pdds3c2410_ser.cpp跟BSP中的pdds3c2440_ser.cpp做了对比发现代码基本上一致,只是把函数名中的2410改成了2440,并把对应的寄存器名字什么的改成了2440的。这也是做驱动移植的时候通用的手段,那就是将样本驱动中的源码文件复制到BSP对应驱动目录下,只需在sources文件中加入编译,然后只在BSP对应驱动目录下作修改就可以了。
SOURCES= ser_smdk2440.cpp /
pdds3c2440_ser.cpp
串口驱动属于流式接口驱动,它对应的流式接口名称在{$BSP}/Src/Drivers/Serial/serial.def中导出。
…
EXPORTS COM_Init
COM_Deinit
COM_PreDeinit
COM_Open
COM_Close
COM_PreClose
COM_Read
COM_Write
COM_Seek
COM_PowerDown
COM_PowerUp
COM_IOControl
COM_XXX函数的实现在MDD库com_mdd2.lib中:
/WINCE500/PUBLIC/COMMON/OAK/DRIVERS/SERIAL/COM_MDD2/mdd.c
同样还有一个PDD的库serpddcm.lib:
/WINCE500/PUBLIC/COMMON/OAK/DRIVERS/SERIAL/SERPDDCM/cserpdd.cpp
这个实际上相当于PDD COMMON层,也是平台无关的。
BSP目录下的SERIAL驱动实际上是真正的PDD层,与实际硬件平台相关的代码。
由MDD,PDD COMMON,PDD三层一起构成Windows CE 5.0的串口驱动。整个驱动代码的分析从COM_Init入手,MDD层调用了PDD COMMON层,PDD COMMON层调用了PDD层。
先看/WINCE500/PUBLIC/COMMON/OAK/DRIVERS/SERIAL/COM_MDD2/mdd.c:
HANDLE
COM_Init(
ULONG Identifier
)
{
……
// Initialize hardware dependent data.
pSerialHead->pHWObj = GetSerialObject( DevIndex );
if ( !pSerialHead->pHWObj ) {
DEBUGMSG(ZONE_ERROR | ZONE_INIT,
(TEXT("Error in GetSerialObject, COM_Init failed/n/r")));
LocalFree(pSerialHead);
return(NULL);
}
……
DEBUGMSG (ZONE_INIT, (TEXT("About to call HWInit(%s,0x%X)/r/n"),
Identifier, pSerialHead));
pHWHead = pSerialHead->pHWObj->pFuncTbl->HWInit(Identifier, pSerialHead, pSerialHead->pHWObj);
pSerialHead->pHWHead = pHWHead;
}
pSerialHead定义在
/WINCE500/PUBLIC/COMMON/OAK/DRIVERS/SERIAL/COM_MDD2/serpriv.h中,包含串口设备的各项信息:
// @struct HW_INDEP_INFO | Hardware Independent Serial Driver Head Information.
typedef struct __HW_INDEP_INFO {
CRITICAL_SECTION TransmitCritSec1; // @field Protects tx action
CRITICAL_SECTION ReceiveCritSec1; // @field Protects rx action
PHWOBJ pHWObj; // @field Represents PDD object.
PVOID pHWHead; // @field Device context for PDD.
HANDLE hSerialEvent; // @field Serial event, both rx and tx
HANDLE hReadEvent; // @field Serial event, both rx and tx
HANDLE hKillDispatchThread; // @field Synchonize thread end
HANDLE hTransmitEvent; // @field transmit event, both rx and tx
HANDLE pDispatchThread;// @field ReceiveThread
ULONG Priority256; // @field CeThreadPriority of Dispatch Thread.
ULONG DroppedBytesMDD;// @field Record of bytes dropped by MDD.
ULONG DroppedBytesPDD;// @field Record of bytes dropped by PDD.
ULONG RxBytes; // @field Record of total bytes received.
ULONG TxBytes; // @field Record of total bytes transmitted.
ULONG TxBytesPending; // @field Record of total bytes awaiting transmit.
ULONG TxBytesSent; // @field Record of bytes sent in one transmission
DCB DCB; // @field DCB (see Win32 Documentation.
COMMTIMEOUTS CommTimeouts; // @field Time control field.
DWORD OpenCnt; // @field Protects use of this port
DWORD KillRxThread:1; // @field Flag to terminate SerialDispatch thread.
DWORD XFlow:1; // @field True if Xon/Xoff flow ctrl.
DWORD StopXmit:1; // @field Stop transmission flag.
DWORD SentXoff:1; // @field True if XOFF sent.
DWORD DtrFlow:1; // @field True if currently DTRFlowed
DWORD RtsFlow:1; // @field True if currently RTSFlowed
DWORD fAbortRead:1; // @field Used for PURGE
DWORD fAbortTransmit:1;// @field Used for PURGE
DWORD Reserved:24; // @field remaining bits.
ULONG fEventMask; // @field Sum of event mask for all opens
RX_BUFFER_INFO RxBufferInfo; // @field rx buffer info.
TX_BUFFER_INFO TxBufferInfo; // @field tx buffer info.
LIST_ENTRY OpenList; // @field Head of linked list of OPEN_INFOs
CRITICAL_SECTION OpenCS; // @field Protects Open Linked List + ref counts
PHW_OPEN_INFO pAccessOwner; // @field Points to whichever open has acess permissions
} HW_INDEP_INFO, *PHW_INDEP_INFO;
pHWObj是HWOBJ结构的指针。HWOBJ是HW_INDEP_INFO成员,被称作硬件相关数据.在serhw.h中定义如下:
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结构体声明在Serhw.h中:
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;
所以pFuncTbl指向的函数指针数组HW_VTBL就是PDD COMMON层(cserpdd.cpp)中实现的各个函数,如SerInit,SerOpen等,如下代码所示:
HW_VTBL IoVTbl = {
SerInit,
SerPostInit,
SerDeinit,
SerOpen,
SerClose,
SerGetInterruptType,
SerRxIntr,
SerTxIntrEx,
SerModemIntr,
SerLineIntr,
SerGetRxBufferSize,
SerPowerOff,
SerPowerOn,
SerClearDTR,
SerSetDTR,
SerClearRTS,
SerSetRTS,
SerEnableIR,
SerDisableIR,
SerClearBreak,
SerSetBreak,
SerXmitComChar,
SerGetStatus,
SerReset,
SerGetModemStatus,
SerGetCommProperties,
SerPurgeComm,
SerSetDCB,
SerSetCommTimeouts,
SerIoctl
};
COM_Init通过调用GetSerialObject来获得相应PDD的HWOBJ结构,GetSerialObject是MDD连接PDD的关键函数,而DeviceArrayIndex对应的就是串口的端口序号,一个端口序号对应一个UART端口,也对应一个串口驱动程序PDD层运行时代码,这个值可以从注册表中DeviceArrayIndex项获得.
COM_Init函数调用GetSerialObject将DeviceArrayIndex传递到HWOBJ的dwIntID成员再传给CreateSerialObject函数.这样COM_Init通过适用不同的参数调用GetSerialObject就可以获得同一平台上同时存在不同串口的PDD对象,可以使多个PDD对象共享一个MDD.
以下是GetSerialObject的代码,位于cserpdd.cpp,获得了PDD COMMON中的HW_VTBL结构的函数指针,同时将DeviceArrayIndex传递给dwIntID.
extern "C" PHWOBJ
GetSerialObject(
DWORD DeviceArrayIndex
)
{
PHWOBJ pSerObj;
// Unlike many other serial samples, we do not have a statically allocated
// array of HWObjs. Instead, we allocate a new HWObj for each instance
// of the driver. The MDD will always call GetSerialObj/HWInit/HWDeinit in
// that order, so we can do the alloc here and do any subsequent free in
// HWDeInit.
// Allocate space for the HWOBJ.
pSerObj=(PHWOBJ)LocalAlloc( LPTR ,sizeof(HWOBJ) );
if ( !pSerObj )
return (NULL);
// Fill in the HWObj structure that we just allocated.
pSerObj->BindFlags = THREAD_IN_PDD; // PDD create thread when device is first attached.
pSerObj->dwIntID = DeviceArrayIndex; // Only it is useful when set set THREAD_AT_MDD. We use this to transfer DeviceArrayIndex
pSerObj->pFuncTbl = (HW_VTBL *) &IoVTbl; // Return pointer to appropriate functions
// Now return this structure to the MDD.
return (pSerObj);
}
COM_Init中的:
pHWHead =
pSerialHead->pHWObj->pFuncTbl->HWInit(Identifier, pSerialHead, pSerialHead->pHWObj);
pSerialHead->pHWHead = pHWHead;
HWInit实际上调用的就是cserpdd.cpp中的SerInit,它将调用CreateSerialObject函数来获得串口驱动的PDD层.实际上就是返回PDD中需要实现的串口类的指针.
PVOID
SerInit(
ULONG Identifier, // @parm Device identifier.
PVOID pMddHead, // @parm First argument to mdd callbacks.
PHWOBJ pHWObj // @parm Pointer to our own HW OBJ for this device
)
{
DEBUGMSG (ZONE_CLOSE,(TEXT("+SerInit, 0x%X/r/n"), Identifier));
CSerialPDD * pSerialPDD = NULL;
if (pHWObj) {
DWORD dwIndex= pHWObj->dwIntID;
pHWObj->dwIntID = 0;
pSerialPDD = CreateSerialObject((LPTSTR)Identifier,pMddHead, pHWObj,dwIndex);
}
if (pSerialPDD==NULL) {
ASSERT(FALSE);
LocalFree(pHWObj);
}
DEBUGMSG (ZONE_CLOSE,(TEXT("-SerInit, 0x%X/r/n"), pSerialPDD));
return pSerialPDD;
}
CreateSerialObject定义在PDD层,ser_smdk2440.cpp中:
CSerialPDD * CreateSerialObject(LPTSTR lpActivePath, PVOID pMdd,PHWOBJ pHwObj, DWORD DeviceArrayIndex)
{
CSerialPDD * pSerialPDD = NULL;
switch (DeviceArrayIndex) {
case 0:
pSerialPDD = new CPdd2440Serial1(lpActivePath,pMdd, pHwObj);
RETAILMSG(1, (TEXT("INFO: CPdd2440Serial0 (%x)./r/n"), pSerialPDD));
break;
case 1:
pSerialPDD = new CPdd2440Serial2(lpActivePath,pMdd, pHwObj);
RETAILMSG(1, (TEXT("INFO: CPdd2440Serial1 (%x)./r/n"), pSerialPDD));
break;
case 2:
pSerialPDD = new CPdd2440Serial3(lpActivePath,pMdd, pHwObj);
RETAILMSG(1, (TEXT("INFO: CPdd2440Serial2 (%x)./r/n"), pSerialPDD));
break;
}
if (pSerialPDD && !pSerialPDD->Init()) {
delete pSerialPDD;
pSerialPDD = NULL;
}
return pSerialPDD;
}
这样就得到了PDD层具体的CPdd2440Serial1,CPdd2440Serial2,CPdd2440Serial3类实例。
再回顾下调用过程:
Mdd.c:COM_Init()
---- Csrpdd.cpp:GetSerialObject()
---- Cserpdd.cpp: SerInit()//通过函数指针HWInit
---- ser_smdk2440.cpp: CreateSerialObject()
---- 获取CPdd2440Serial1,CPdd2440Serial2,CPdd2440Serial3
二、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并没有什么特别的之处,在掌握了硬件行为和应用软件行为后很容易能读懂其间的代码。
三、PDD部分
1. PDD架构
MDD层和PDD COMMON层都是由微软提供的,一般情况下我们基本无须改动.微软为开发者提供了一个CSerialPDD类作为开发工作的起点.CSerialPDD是个纯虚类,我们需要将驱动程序的PDD层定义成CSerialPDD类的继承类,其成员必须准确全面的反应目标硬件平台的特定类型串口的属性.
MDD和PDD COMMON层不需要知道用户的CSerialPDD继承类的命名和具体实现,以CSerialPDD类指针引用其继承类实例的功能.
CSerialPDD的定义如下(位于/WINCE500/PUBLIC/COMMON/OAK/INC/cserpdd.h):
class CSerialPDD : public CRegistryEdit {
public :
//
// Interface For Initialization
CSerialPDD(LPTSTR lpActivePath, PVOID pMdd, PHWOBJ pHwObj );
virtual ~CSerialPDD();
virtual BOOL Init();
virtual void PostInit() ;
PHWOBJ GetHwObject() { return m_pHwObj; };
protected:
PVOID const m_pMdd;
PHWOBJ const m_pHwObj;
HANDLE m_hParent;
//
//Device Operation
public:
virtual BOOL Open();
virtual BOOL Close();
BOOL IsOpen() { return m_lOpenCount!=0; };
void Reset();
virtual BOOL Ioctl(DWORD dwCode,PBYTE pBufIn,DWORD dwLenIn,PBYTE pBufOut,DWORD dwLenOut,PDWORD pdwActualOut);
private:
LONG m_lOpenCount;
//
//Power Managment Operation
public:
virtual BOOL InitialPower(BOOL bInit);
virtual BOOL PowerOff();
virtual BOOL PowerOn();
virtual BOOL SetDevicePowerState(CEDEVICE_POWER_STATE pwrState);
virtual void SerialRegisterBackup()=0;
virtual void SerialRegisterRestore()=0;
virtual CEDEVICE_POWER_STATE GetDevicePowerState() { return m_PowerState; };
virtual POWER_CAPABILITIES GetPowerCapabilities() { return m_PowerCapabilities; };
BOOL IsThisPowerManaged() { return m_IsThisPowerManaged; };
BOOL IsPowerResumed ( ) { return (InterlockedExchange( &m_IsResumed,0)!=0); };
protected:
CEDEVICE_POWER_STATE m_PowerState;
BOOL m_IsThisPowerManaged;
POWER_CAPABILITIES m_PowerCapabilities;
LONG m_IsResumed;
CSerialPDDPowerUpCallback * m_PowerCallbackThread;
HANDLE m_PowerHelperHandle;
HANDLE m_hPowerLock;
static CEDEVICE_POWER_STATE SetPowerStateStatic( DWORD, CEDEVICE_POWER_STATE );
//
protected:
CLockObject m_HardwareLock;
//
// Interrupt Interface
public:
virtual BOOL InitialEnableInterrupt(BOOL bEnable ) ; // Enable All the interrupt may include Xmit Interrupt.
virtual BOOL NotifyPDDInterrupt(INTERRUPT_TYPE interruptType);
virtual INTERRUPT_TYPE GetInterruptType();
virtual BOOL EventCallback(ULONG fdwEventMask, ULONG fdwModemStatus = 0 );
virtual BOOL DataReplaced(PBYTE puData,BOOL isBadData);
private:
DWORD m_dwInterruptFlag;
CLockObject m_InterruptLock;
protected:
DWORD m_dwPriority256;
//
//
//
// Special Cancellation
public:
virtual BOOL PurgeComm( DWORD fdwAction);
//
// Tx Function.
virtual BOOL InitXmit(BOOL bInit) = 0;
virtual void XmitInterruptHandler(PUCHAR pTxBuffer, ULONG *pBuffLen) = 0;
virtual void XmitComChar(UCHAR ComChar) = 0;
virtual BOOL EnableXmitInterrupt(BOOL bEnable)= 0;
virtual BOOL CancelXmit() = 0 ;
//
// Rx Function.
virtual BOOL InitReceive(BOOL bInit) = 0;
virtual ULONG ReceiveInterruptHandler(PUCHAR pRxBuffer,ULONG *pBufflen) = 0;
virtual ULONG CancelReceive() = 0;
//
// Modem
virtual BOOL InitModem(BOOL bInit) = 0;
virtual void ModemInterruptHandler()= 0; // This is Used to Indicate Modem Signal Changes.
virtual ULONG GetModemStatus() = 0;
virtual void SetDTR(BOOL bSet)= 0;
virtual void SetRTS(BOOL bSet)= 0;
virtual BOOL IsCTSOff() { return ((GetModemStatus() & MS_CTS_ON)==0) ; };
virtual BOOL IsDSROff() { return ((GetModemStatus() & MS_DSR_ON)==0) ; };
// Line Function
virtual BOOL InitLine(BOOL bInit) = 0;
virtual void LineInterruptHandler() = 0;
virtual void SetBreak(BOOL bSet) = 0 ;
virtual BOOL SetBaudRate(ULONG BaudRate,BOOL bIrModule) = 0;
virtual BOOL SetByteSize(ULONG ByteSize) = 0;
virtual BOOL SetParity(ULONG Parity)= 0;
virtual BOOL SetStopBits(ULONG StopBits)= 0;
virtual BOOL SetDCB(LPDCB lpDCB);
DCB GetDCB() { return m_DCB; };
protected:
DCB m_DCB;
//
// Configuration
public:
virtual void SetDefaultConfiguration();
virtual BOOL GetDivisorOfRate(ULONG BaudRate,PULONG pulDivisor);
COMMPROP GetCommProperties() { return m_CommPorp; };
protected:
COMMPROP m_CommPorp;
//
//
//
// IR Special Handle
public:
virtual void SetOutputMode(BOOL UseIR, BOOL Use9Pin) {
m_fIREnable = UseIR;
m_f9PinEnable=Use9Pin;
}
virtual void GetOutputMode(BOOL* pUseIR, BOOL* pUse9Pin) {
if (pUseIR) *pUseIR = m_fIREnable;
if (pUse9Pin) *pUse9Pin = m_f9PinEnable;
}
protected:
BOOL m_fIREnable;
BOOL m_f9PinEnable;
//
// Error Handling
public:
virtual void SetReceiveError(ULONG);
virtual ULONG GetReceivedError() ;
protected:
ULONG m_ulCommErrors;
};
CSerialPDD类以CRegistryEdit类作为它的父类.CRegistryEdit向它的继承类提供了访问系统注册表的能力.成员函数定义在cserpdd.cpp中.
CSerailPDD类的数据成员和成员函数可以划分为以下几个部分:
(1) 初始化部分
(2) 串口设备操作部分
(3) 电源管理部分
(4) 中断接口部分
(5) 数据发送与接收部分
(6) MODEM功能部分
(7) 线路功能部分
(8) 串口配置部分
(9) IR特殊处理部分
(10) 错误处理部分
S3C2440串口驱动PDD中CSerialPDD的继承类就是CPdd2440Uart,这是2440平台上3个UART端口都拥有的共同一个抽象类,里面实现了串口大部分操作与具体哪个UART无关.
而CPdd2440Uart的继承类CPdd2440Serial1,CPdd2440Serial2,CPdd2440Serial3则实际对应于具体串口,分别为UART0,UART1,UART2.
CPdd2440Uart实现在pdds3c2440_ser.cpp,CPdd2440Serial1,CPdd2440Serial2,CPdd2440Serial3实现在ser_smdk2440.cpp.
下面就先来看看CPdd2440Uart类.
2. CPdd2440Uart
(1) CPdd2440Uart原型
该类原型如下,继承自CSerialPDD和CMiniThread类.
class CPdd2440Uart: public CSerialPDD, public CMiniThread {
public:
CPdd2440Uart (LPTSTR lpActivePath, PVOID pMdd, PHWOBJ pHwObj);
virtual ~CPdd2440Uart();
virtual BOOL Init();
virtual void PostInit();
virtual BOOL MapHardware();
virtual BOOL CreateHardwareAccess();
// Power Manager Required Function.
virtual void SerialRegisterBackup() { m_pReg2440Uart->Backup(); };
virtual void SerialRegisterRestore() { m_pReg2440Uart->Restore(); };
// Implement CPddSerial Function.
// Interrupt
virtual BOOL InitialEnableInterrupt(BOOL bEnable ) ; // Enable All the interrupt may include Xmit Interrupt.
private:
virtual DWORD ThreadRun(); // IST
// Tx Function.
public:
virtual BOOL InitXmit(BOOL bInit);
virtual void XmitInterruptHandler(PUCHAR pTxBuffer, ULONG *pBuffLen);
virtual void XmitComChar(UCHAR ComChar);
virtual BOOL EnableXmitInterrupt(BOOL bEnable);
virtual BOOL CancelXmit();
virtual DWORD GetWriteableSize();
protected:
BOOL m_XmitFifoEnable;
HANDLE m_XmitFlushDone;
//
// Rx Function.
public:
virtual BOOL InitReceive(BOOL bInit);
virtual ULONG ReceiveInterruptHandler(PUCHAR pRxBuffer,ULONG *pBufflen);
virtual ULONG CancelReceive();
virtual DWORD GetWaterMark();
virtual BYTE GetWaterMarkBit();
virtual void Rx_Pause(BOOL bSet) {;};
protected:
BOOL m_bReceivedCanceled;
DWORD m_dwWaterMark;
//
// Modem
public:
virtual BOOL InitModem(BOOL bInit);
virtual void ModemInterruptHandler() { GetModemStatus();};
virtual ULONG GetModemStatus();
virtual void SetDTR(BOOL bSet) {;};
virtual void SetRTS(BOOL bSet);
//
// Line Function.
virtual BOOL InitLine(BOOL bInit) ;
virtual void LineInterruptHandler() { GetLineStatus();};
virtual void SetBreak(BOOL bSet) ;
virtual BOOL SetBaudRate(ULONG BaudRate,BOOL bIrModule) ;
virtual BOOL SetByteSize(ULONG ByteSize);
virtual BOOL SetParity(ULONG Parity);
virtual BOOL SetStopBits(ULONG StopBits);
//
// Line Internal Function
BYTE GetLineStatus();
virtual void SetOutputMode(BOOL UseIR, BOOL Use9Pin) ;
protected:
CReg2440Uart * m_pReg2440Uart;
PVOID m_pRegVirtualAddr;
volatile S3C2440A_INTR_REG * m_pINTregs;
DWORD m_dwIntShift;
public:
void DisableInterrupt(DWORD dwInt) {
m_pINTregs->INTSUBMSK |= (dwInt<<m_dwIntShift);
}
void EnableInterrupt(DWORD dwInt) {
m_pINTregs->INTSUBMSK &= ~(dwInt<<m_dwIntShift);
}
void ClearInterrupt(DWORD dwInt) {
m_pINTregs->SUBSRCPND = (dwInt<<m_dwIntShift);
}
DWORD GetInterruptStatus() { return ((m_pINTregs->SUBSRCPND) >> m_dwIntShift);};
DWORD GetIntrruptMask () { return ((~(m_pINTregs->INTSUBMSK) )>> m_dwIntShift); };
protected:
CRegistryEdit m_ActiveReg;
// Interrupt Handler
DWORD m_dwSysIntr;
HANDLE m_hISTEvent;
// Optional Parameter
DWORD m_dwDevIndex;
DWORD m_dwISTTimeout;
};
(2)CReg2440Uart
在详细看CPdd2440Uart的成员函数之前,我们先来看看CPdd2440Uart会用到的另一个类CReg2440Uart.这个类封装了对串口寄存器的操作函数.原型如下:
class CReg2440Uart {
public:
CReg2440Uart(PULONG pRegAddr);
virtual ~CReg2440Uart() { ; };
virtual BOOL Init() ;
// We do not virtual Read & Write data because of Performance Concern.
void Write_ULCON(ULONG uData) { WRITE_REGISTER_ULONG( m_pReg, (uData)); };
ULONG Read_ULCON() { return (READ_REGISTER_ULONG(m_pReg)); } ;
void Write_UCON (ULONG uData) { WRITE_REGISTER_ULONG(m_pReg+1 , uData); };
ULONG Read_UCON() { return READ_REGISTER_ULONG(m_pReg+1 ); };
void Write_UFCON(ULONG uData) { WRITE_REGISTER_ULONG( m_pReg+2, uData);};
ULONG Read_UFCON() { return READ_REGISTER_ULONG(m_pReg + 2); };
void Write_UMCON(ULONG uData) { WRITE_REGISTER_ULONG(m_pReg + 3, uData);};
ULONG Read_UMCON() { return READ_REGISTER_ULONG(m_pReg + 3);};
ULONG Read_UTRSTAT() { return READ_REGISTER_ULONG(m_pReg + 4);};
ULONG Read_UERSTAT() { return READ_REGISTER_ULONG(m_pReg + 5);};
ULONG Read_UFSTAT() { return READ_REGISTER_ULONG(m_pReg + 6);};
ULONG Read_UMSTAT() { return READ_REGISTER_ULONG(m_pReg + 7);};
void Write_UTXH (UINT8 uData) { WRITE_REGISTER_ULONG( (m_pReg + 8), uData) ; };
UINT8 Read_URXH() { return (UINT8) READ_REGISTER_ULONG(m_pReg + 9); };
void Write_UBRDIV(ULONG uData) { WRITE_REGISTER_ULONG( m_pReg + 10, uData );};
ULONG Read_UBRDIV() { return READ_REGISTER_ULONG(m_pReg + 10); };
virtual BOOL Write_BaudRate(ULONG uData);
PULONG GetRegisterVirtualAddr() { return m_pReg; };
virtual void Backup();
virtual void Restore();
#ifdef DEBUG
virtual void DumpRegister();
#endif
protected:
volatile PULONG const m_pReg;
BOOL m_fIsBackedUp;
private:
ULONG m_ULCONBackup;
ULONG m_UCONBackup;
ULONG m_UFCONBackup;
ULONG m_UMCOMBackup;
ULONG m_UBRDIVBackup;
ULONG m_BaudRate;
ULONG m_s3c2440_pclk;
};
其中Write_ULCON,Read_ULCON等就是读写相应的UART寄存器,后面的m_pReg就是操作寄存器的虚拟地址.使用的READ_REGISTER_ULONG和WRITE_REGISTER_ULONG是CEDDK函数,用来读写寄存器.
下面来看看其他几个成员函数.
[1] 构造函数CReg2440Uart
CReg2440Uart::CReg2440Uart(PULONG pRegAddr)
: m_pReg(pRegAddr)
{
m_fIsBackedUp = FALSE;
PROCESSOR_INFO procInfo;
DWORD dwBytesReturned;
if (!KernelIoControl(IOCTL_PROCESSOR_INFORMATION, NULL, 0, &procInfo, sizeof(PROCESSOR_INFO), &dwBytesReturned))
{
m_s3c2440_pclk = DEFAULT_S3C2440A_PCLK;
RETAILMSG(0, (TEXT("WARNING: CReg2440Uart::CReg2440Uart failed to obtain processor frequency - using default value (%d)./r/n"), m_s3c2440_pclk));
}
else
{
m_s3c2440_pclk = procInfo.dwClockSpeed;
RETAILMSG(0, (TEXT("INFO: CReg2440Uart::CReg2440Uart using processor frequency reported by the OAL (%d)./r/n"), m_s3c2440_pclk));
}
}
这个函数只是用来获得CPU时钟,同时给m_pReg附初值,也就是对应的UART寄存器地址.
[2] Init
用来初始化UART寄存器初值,都设为0.
BOOL CReg2440Uart::Init()
{
if (m_pReg) { // Set Value to default.
Write_ULCON(0);
Write_UCON(0);
Write_UFCON(0);
Write_UMCON(0);
return TRUE;
}
else
return FALSE;
}
[3] Backup,Restore
用来备份和回复UART寄存器内容.
void CReg2440Uart::Backup()
{
m_fIsBackedUp = TRUE;
m_ULCONBackup = Read_ULCON();
m_UCONBackup = Read_UCON();
m_UFCONBackup = Read_UFCON();
m_UMCOMBackup = Read_UMCON();
m_UBRDIVBackup = Read_UBRDIV();
}
void CReg2440Uart::Restore()
{
if (m_fIsBackedUp) {
Write_ULCON(m_ULCONBackup );
Write_UCON( m_UCONBackup );
Write_UFCON( m_UFCONBackup );
Write_UMCON( m_UMCOMBackup );
Write_UBRDIV( m_UBRDIVBackup);
m_fIsBackedUp = FALSE;
}
}
[4] Write_BaudRate
写波特率,首先判断UCON(UART控制寄存器)BIT10选择分频时钟源,然后写UBRDIV(波特率分频寄存器)来设置波特率.
CReg2440Uart::Write_BaudRate(ULONG BaudRate)
{
DEBUGMSG(ZONE_INIT, (TEXT("SetBaudRate -> %d/r/n"), BaudRate));
if ( (Read_UCON() & CS_MASK) == CS_PCLK ) {
Write_UBRDIV( (int)(m_s3c2440_pclk/16.0/BaudRate) -1 );
return TRUE;
}
else {
// TODO: Support external UART clock.
//OUTREG(pHWHead,UBRDIV,( (int)(S2440UCLK/16.0/BaudRate) -1 ));
RETAILMSG(TRUE, (TEXT("ERROR: The s3c2440a serial driver doesn't support an external UART clock./r/n")));
ASSERT(FALSE);
return(FALSE);
}
}
另外还有个DumpRegister,用来输出寄存器值,供调试使用,这里就不多说了.
#ifdef DEBUG
void CReg2440Uart::DumpRegister()
{
NKDbgPrintfW(TEXT("DumpRegister (ULCON=%x, UCON=%x, UFCON=%x, UMCOM = %x, UBDIV =%x)/r/n"),
Read_ULCON(),Read_UCON(),Read_UFCON(),Read_UMCON(),Read_UBRDIV());
}
#endif
下面就来看看CPdd2440Uart的成员函数.
(3) CPdd2440Uart构造函数
初始化了一些变量,如CReg2440Uart类对象,事件及其他标志等.注释如下:
CPdd2440Uart::CPdd2440Uart (LPTSTR lpActivePath, PVOID pMdd, PHWOBJ pHwObj )
: CSerialPDD(lpActivePath,pMdd, pHwObj)
, m_ActiveReg(HKEY_LOCAL_MACHINE,lpActivePath)
, CMiniThread (0, TRUE)
{
m_pReg2440Uart = NULL;//CReg2440Uart类对象
m_pINTregs = NULL;//中断寄存器地址
m_dwIntShift = 0;//INTSUBMSK寄存器的掩码位,从注册表中读得(InterruptBitsShift项),这里和注册表都设置为0,表明UART0的RXD中断
m_dwSysIntr = MAXDWORD;//逻辑中断号,初始为MAXDWORD,0xFFFF
m_hISTEvent = NULL;//IST事件
m_dwDevIndex = 0;//注册表中的DeviceArrayIndex,编号
m_pRegVirtualAddr = NULL;//UART寄存器虚拟地址
m_XmitFlushDone = CreateEvent(0, FALSE, FALSE, NULL);//发送完成事件
m_XmitFifoEnable = FALSE;//发送fifo是否使能
m_dwWaterMark = 8 ;//接收FIFO触发中断的字节数,为什么叫WaterMark?应该是用来保证不出错,所以叫水印?
}
(4)析构函数~CPdd2440Uart()
主要就是关闭线程,关闭事件,禁止中断,释放地址空间资源,如CReg2440Uart类对象空间,寄存器地址空间等.
CPdd2440Uart::~CPdd2440Uart()
{
InitModem(FALSE);
if (m_hISTEvent) {
m_bTerminated=TRUE;
ThreadStart();
SetEvent(m_hISTEvent);
ThreadTerminated(1000);
InterruptDisable( m_dwSysIntr );
CloseHandle(m_hISTEvent);
};
if (m_pReg2440Uart)
delete m_pReg2440Uart;
if (m_XmitFlushDone)
CloseHandle(m_XmitFlushDone);
if (m_pRegVirtualAddr != NULL) {
MmUnmapIoSpace((PVOID)m_pRegVirtualAddr,0UL);
}
if (m_pINTregs!=NULL) {
MmUnmapIoSpace((PVOID)m_pINTregs,0UL);
}
}
(5)Init
该函数首先调用基类的Init函数,CSerialPDD的Init成员函数主要负责初始化串口驱动程序的电源管理.Init同时并检查注册表是否打开和m_XmitFlushDone,都为TRUE则进一步初始化.
然后通过GetIsrInfo获得逻辑中断号,创建IST事件(m_hISTEvent),初始化m_dwSysIntr对应的中断.触发事件为m_hISTEvent.
接着读注册表获取如DeviceArrayIndex,InterruptBitsShift,ISTTimeouts等信息.
最后调用MapHardware和CreateHardwareAccess来映射硬件寄存器和创建特定UART的CReg2440Uart类对象.
BOOL CPdd2440Uart::Init()
{
if ( CSerialPDD::Init() && IsKeyOpened() && m_XmitFlushDone!=NULL) {
// IST Setup .
DDKISRINFO ddi;
if (GetIsrInfo(&ddi)!=ERROR_SUCCESS) {
return FALSE;
}
m_dwSysIntr = ddi.dwSysintr;
if (m_dwSysIntr != MAXDWORD && m_dwSysIntr!=0 )
m_hISTEvent= CreateEvent(0,FALSE,FALSE,NULL);
if (m_hISTEvent!=NULL)
InterruptInitialize(m_dwSysIntr,m_hISTEvent,0,0);
else
return FALSE;
// Get Device Index.
if (!GetRegValue(PC_REG_DEVINDEX_VAL_NAME, (PBYTE)&m_dwDevIndex, PC_REG_DEVINDEX_VAL_LEN)) {
m_dwDevIndex = 0;
}
if (!GetRegValue(PC_REG_SERIALWATERMARK_VAL_NAME,(PBYTE)&m_dwWaterMark,sizeof(DWORD))) {
m_dwWaterMark = 8;
}
if (!GetRegValue(PC_REG_2440UART_INTBIT_VAL_NAME,(PBYTE)&m_dwIntShift,sizeof(DWORD))) {
RETAILMSG(1,(TEXT("Registery does not have %s set. Drivers fail!!!/r/n"),PC_REG_2440UART_INTBIT_VAL_NAME));
m_dwIntShift =0;
return FALSE;
}
if (!GetRegValue(PC_REG_2440UART_IST_TIMEOUTS_VAL_NAME,(PBYTE)&m_dwISTTimeout, PC_REG_2440UART_IST_TIMEOUTS_VAL_LEN)) {
m_dwISTTimeout = INFINITE;
}
if (!MapHardware() || !CreateHardwareAccess()) {
return FALSE;
}
return TRUE;
}
return FALSE;
}
接下来就来看看MapHardware和CreateHardwareAccess这两个函数.
[1] MapHardware
该函数首先判断m_pRegVirtualAddr是否为NULL,如果为NULL说明未初始化,第一次运行时应该为NULL,接着使用DDKWINDOWINFO的结构通过读注册表还获得InterfaceType和MemBase,MemLen(注册表中设置的是50000000,即UART0寄存器物理地址)然后调用TranslateBusAddr将一个总线相对地址转换成系统物理地址.使用MmMapIoSpace映射到IO虚拟地址空间.最后将2440中断寄存器物理地址映射到IO虚拟地址空间.之所以有这样的转换过程,是因为2440串口的母总线是Busenum.
BOOL CPdd2440Uart::MapHardware()
{
if (m_pRegVirtualAddr !=NULL)
return TRUE;
// Get IO Window From Registry
DDKWINDOWINFO dwi;
if ( GetWindowInfo( &dwi)!=ERROR_SUCCESS ||
dwi.dwNumMemWindows < 1 ||
dwi.memWindows[0].dwBase == 0 ||
dwi.memWindows[0].dwLen < 0x2c)
return FALSE;
DWORD dwInterfaceType;
if (m_ActiveReg.IsKeyOpened() &&
m_ActiveReg.GetRegValue( DEVLOAD_INTERFACETYPE_VALNAME, (PBYTE)&dwInterfaceType,sizeof(DWORD))) {
dwi.dwInterfaceType = dwInterfaceType;
}
// Translate to System Address.
PHYSICAL_ADDRESS ioPhysicalBase = { dwi.memWindows[0].dwBase, 0};
ULONG inIoSpace = 0;
if (TranslateBusAddr(m_hParent,(INTERFACE_TYPE)dwi.dwInterfaceType,dwi.dwBusNumber, ioPhysicalBase,&inIoSpace,&ioPhysicalBase)) {
// Map it if it is Memeory Mapped IO.
m_pRegVirtualAddr = MmMapIoSpace(ioPhysicalBase, dwi.memWindows[0].dwLen,FALSE);
}
ioPhysicalBase.LowPart = S3C2440A_BASE_REG_PA_INTR ;
ioPhysicalBase.HighPart = 0;
inIoSpace = 0;
if (TranslateBusAddr(m_hParent,(INTERFACE_TYPE)dwi.dwInterfaceType,dwi.dwBusNumber, ioPhysicalBase,&inIoSpace,&ioPhysicalBase)) {
m_pINTregs = (S3C2440A_INTR_REG *) MmMapIoSpace(ioPhysicalBase,sizeof(S3C2440A_INTR_REG),FALSE);
}
return (m_pRegVirtualAddr!=NULL && m_pINTregs!=NULL);
}
[2] CreateHardwareAccess
这个函数就是生成CReg2440Uart类对象,针对具体的串口(注册表中设置的是UART0),这样调用该对象的成员函数就是对UART0寄存器进行读写操作了。这里没看明白,注册表中的值是由m_pRegVirtualAddr决定的,怎么知道设置的是UART0而不是UART1或UART2?
BOOL CPdd2440Uart::CreateHardwareAccess()
{
if (m_pReg2440Uart)
return TRUE;
if (m_pRegVirtualAddr!=NULL) {
m_pReg2440Uart = new CReg2440Uart((PULONG)m_pRegVirtualAddr);
if (m_pReg2440Uart && !m_pReg2440Uart->Init()) { // FALSE.
delete m_pReg2440Uart ;
m_pReg2440Uart = NULL;
}
}
return (m_pReg2440Uart!=NULL);
}
(6)PostInit
该函数也是初始化函数,在Init之后调用,首先设置一个CRITICAL_SECTION防止重入写冲突,然后设置UCON为0,禁止UART中断.如果此时SUBSRCPND有中断请求,则调用InitReceive和InitLine进行接收并使能中断结束后清除中断标志位.InitReceive和InitLine留在后面的内容分析.当确定SUBSRCPND无中断请求时,调用基类的PostInit和设置线程优先级(从注册表项Priority获得),并启动线程.
CSerialPDD::PostInit功能是初始化中断处理和初始化MODEM.
#define MAX_RETRY 0x1000
void CPdd2440Uart::PostInit()
{
DWORD dwCount=0;
m_HardwareLock.Lock();
m_pReg2440Uart->Write_UCON(0); // Set to Default;
DisableInterrupt(S2440UART_INT_RXD|S2440UART_INT_TXD|S2440UART_INT_ERR);
// Mask all interrupt.
while ((GetInterruptStatus() & (S2440UART_INT_RXD|S2440UART_INT_TXD|S2440UART_INT_ERR))!=0 &&
dwCount <MAX_RETRY) { // Interrupt.
InitReceive(TRUE);
InitLine(TRUE);
ClearInterrupt(S2440UART_INT_RXD|S2440UART_INT_TXD|S2440UART_INT_ERR);
dwCount++;
}
ASSERT((GetInterruptStatus() & (S2440UART_INT_RXD|S2440UART_INT_TXD|S2440UART_INT_ERR))==0);
// IST Start to Run.
m_HardwareLock.Unlock();
CSerialPDD::PostInit();
CeSetPriority(m_dwPriority256);
#ifdef DEBUG
if ( ZONE_INIT )
m_pReg2440Uart->DumpRegister();
#endif
ThreadStart(); // Start IST.
}
(7)ThreadRun
这个就是IST线程,创建自CMiniThread类,CMiniThread类封装了对线程的基本操作,如ThreadStart,ThreadStop等.详情可参考/WINCE500/PUBLIC/COMMON/OAK/INC/Cmthread.h.
像通常的驱动IST一样,首先等待事件m_hISTEvent的发生,发生后去获取中断状态来判断是哪个中断发生了,根据不同的状态设置不同的标志.然后调用NotifyPDDInterrupt(基类CSerailPDD函数实现)向串口驱动程序报告串口中断事件,接着清除中断标志位.最后调用InterruptDone结束本次中断IST过程.
当其他中断(RX,TX)没有发生时,IST会默认INTR_MODEM来轮询MODEM.也就是最后else里做的工作.代码如下:
DWORD CPdd2440Uart::ThreadRun()
{
while ( m_hISTEvent!=NULL && !IsTerminated()) {
if (WaitForSingleObject( m_hISTEvent,m_dwISTTimeout)==WAIT_OBJECT_0) {
m_HardwareLock.Lock();
while (!IsTerminated() ) {
DWORD dwData = (GetInterruptStatus() & (S2440UART_INT_RXD|S2440UART_INT_TXD|S2440UART_INT_ERR));
DWORD dwMask = (GetIntrruptMask() & (S2440UART_INT_RXD|S2440UART_INT_TXD|S2440UART_INT_ERR));
DEBUGMSG(ZONE_THREAD,
(TEXT(" CPdd2440Uart::ThreadRun INT=%x, MASK =%x/r/n"),dwData,dwMask));
dwMask &= dwData;
if (dwMask) {
DEBUGMSG(ZONE_THREAD,
(TEXT(" CPdd2440Uart::ThreadRun Active INT=%x/r/n"),dwMask));
DWORD interrupts=INTR_MODEM; // Always check Modem when we have change. It may work at polling mode.
if ((dwMask & S2440UART_INT_RXD)!=0)
interrupts |= INTR_RX;
if ((dwMask & S2440UART_INT_TXD)!=0)
interrupts |= INTR_TX;
if ((dwMask & S2440UART_INT_ERR)!=0)
interrupts |= INTR_LINE|INTR_RX;
NotifyPDDInterrupt((INTERRUPT_TYPE)interrupts);
ClearInterrupt(dwData);
}
else
break;
}
m_HardwareLock.Unlock();
InterruptDone(m_dwSysIntr);
}
else { // Polling Modem.
NotifyPDDInterrupt(INTR_MODEM);
DEBUGMSG(ZONE_THREAD,(TEXT(" CPdd2440Uart::ThreadRun timeout INT=%x,MASK=%d/r/n"),m_pINTregs->SUBSRCPND,m_pINTregs->INTSUBMSK));
#ifdef DEBUG
if ( ZONE_THREAD )
m_pReg2440Uart->DumpRegister();
#endif
}
}
return 1;
}
(8) InitialEnableInterrupt
这个函数比较简单,用来初始化允许或者禁止RXD和ERR中断.
BOOL CPdd2440Uart::InitialEnableInterrupt(BOOL bEnable )
{
m_HardwareLock.Lock();
if (bEnable)
EnableInterrupt(S2440UART_INT_RXD | S2440UART_INT_ERR );
else
DisableInterrupt(S2440UART_INT_RXD | S2440UART_INT_ERR );
m_HardwareLock.Unlock();
return TRUE;
}
(9)InitXmit
InitXmit负责初始化串口的数据发送,该函数有一个bool型参数bEnable,TRUE表示初始化数据发送,FALSE表示解除初始化串口数据发送.
如果为TRUE,则初始化UART寄存器,设置中断方式(level触发,传输模式-中断请求或轮询),复位发送FIFO,禁止FIFO,Tx FIFO中断触发方式(4字节),最后重新使能FIFO.
如果为FALSE,则等待UTRSTA寄存器FIFO状态为空,即数据都已发送完毕.
代码如下:
BOOL CPdd2440Uart::InitXmit(BOOL bInit)
{
if (bInit) {
m_HardwareLock.Lock();
DWORD dwBit = m_pReg2440Uart->Read_UCON();
// Set TxINterrupt To Pulse.
dwBit &= ~(1<<9);
// Set Interrupt Tx Mode.
dwBit &= ~(3<<2);
dwBit |= (1<<2);
m_pReg2440Uart->Write_UCON(dwBit );
dwBit = m_pReg2440Uart->Read_UFCON();
// Reset Xmit Fifo.
dwBit |= (1<<2);
dwBit &= ~(1<<0);
m_pReg2440Uart->Write_UFCON( dwBit);
// Set Trigger level to 16.
dwBit &= ~(3<<6);//empty
dwBit |= (1<<6);//16
m_pReg2440Uart->Write_UFCON(dwBit);
// Enable Xmit FIFO.
dwBit &= ~(1<<2);
dwBit |= (1<<0);
m_pReg2440Uart->Write_UFCON(dwBit); // Xmit Fifo Reset Done..
m_HardwareLock.Unlock();
}
else { // Make Sure data has been trasmit out.
// We have to make sure the xmit is complete because MDD will shut donw the device after this return
DWORD dwTicks = 0;
DWORD dwUTRState;
while (dwTicks < 1000 &&
(((dwUTRState = m_pReg2440Uart->Read_UTRSTAT())>>1) & 3)!=3 ) { // Transmitter empty is not true
DEBUGMSG(ZONE_THREAD|ZONE_WRITE,(TEXT("CPdd16550::InitXmit! Wait for UTRSTAT=%x clear./r/n"), dwUTRState));
Sleep(5);
dwTicks +=5;
}
}
return TRUE;
}
(10)GetWriteableSize
GetWriteableSize用来获取FIFO中可以写入的字节数.首先读取UFSTAT来获得当前FIFO状态,如果为FULL则返回0,无可写字节数.如果不为0,则计算出当前可写字节数,并返回.
代码如下:
DWORD CPdd2440Uart::GetWriteableSize()
{
DWORD dwWriteSize = 0;
DWORD dwUfState = m_pReg2440Uart->Read_UFSTAT() ;
if ((dwUfState& (1<<14))==0) { // It is not full.
dwUfState = ((dwUfState>>8) & 0x3f); // It is fifo count.
if (dwUfState < SER2440_FIFO_DEPTH_TX-1)
dwWriteSize = SER2440_FIFO_DEPTH_TX-1 - dwUfState;
}
return dwWriteSize;
}
(11)XmitInterruptHandler
XmitInterruptHandler被串口驱动程序的IST线程调用用以处理串口的数据发送.
中断发生后,调用的过程是这样的:中断发生后,m_hISTEvent事件触发,IST线程ThreadRun获取中断类型调用NotifyPDDInterrupt(cserpdd.c)通知PDD,NotifyPDDInterrupt会调用MDD层中的SerialEventHandler(mdd.c),该函数会根据不同的状态调用相应的事件处理程序,如发送数据处理XmitInterruptHandler.
XmitInterruptHandler首先判断是否有发送数据,如果没有直接关闭中断返回.如果有则判断是否需要流控制,没有流控制仅仅关闭中断;如果需要流控制,则FIFO可写字节数,将该数目的字节写入UTXH(发送Buffer寄存器)中,写满后打开中断返回.最后清除中断标志位.
void CPdd2440Uart::XmitInterruptHandler(PUCHAR pTxBuffer, ULONG *pBuffLen)
{
PREFAST_DEBUGCHK(pBuffLen!=NULL);
m_HardwareLock.Lock();
if (*pBuffLen == 0) {
EnableXmitInterrupt(FALSE);
}
else {
DEBUGCHK(pTxBuffer);
PulseEvent(m_XmitFlushDone);
DWORD dwDataAvaiable = *pBuffLen;
*pBuffLen = 0;
Rx_Pause(TRUE);
if ((m_DCB.fOutxCtsFlow && IsCTSOff()) ||(m_DCB.fOutxDsrFlow && IsDSROff())) { // We are in flow off
DEBUGMSG(ZONE_THREAD|ZONE_WRITE,(TEXT("CPdd16550::XmitInterruptHandler! Flow Off, Data Discard./r/n")));
EnableXmitInterrupt(FALSE);
}
else {
DWORD dwWriteSize = GetWriteableSize();
DEBUGMSG(ZONE_THREAD|ZONE_WRITE,(TEXT("CPdd16550::XmitInterruptHandler! WriteableSize=%x to FIFO,dwDataAvaiable=%x/r/n"),
dwWriteSize,dwDataAvaiable));
for (DWORD dwByteWrite=0; dwByteWrite<dwWriteSize && dwDataAvaiable!=0;dwByteWrite++) {
m_pReg2440Uart->Write_UTXH(*pTxBuffer);
pTxBuffer ++;
dwDataAvaiable--;
}
DEBUGMSG(ZONE_THREAD|ZONE_WRITE,(TEXT("CPdd16550::XmitInterruptHandler! Write %d byte to FIFO/r/n"),dwByteWrite));
*pBuffLen = dwByteWrite;
EnableXmitInterrupt(TRUE);
}
ClearInterrupt(S2440UART_INT_TXD);
if (m_pReg2440Uart->Read_ULCON() & (0x1<<6))
while( (m_pReg2440Uart->Read_UFSTAT() >> 0x8 ) & 0x3f );
Rx_Pause(FALSE);
}
m_HardwareLock.Unlock();
}
(12)XmitComChar
XmitComChar供驱动程序发送单个字符,主要是软件流控制情况下的X-ON和X-OFF流控字符,以及应用程序以IOCTL_SERIAL_IMMEDIATE_CHAR为操作码调用COM_IOControl函数向串口发送立即字符.
如果FIFO not full,则向UTXH发送字符数据,否则允许发送中断,等FIFO中数据发送完毕后在发送(等待m_XmitFlushDone事件).
void CPdd2440Uart::XmitComChar(UCHAR ComChar)
{
// This function has to poll until the Data can be sent out.
BOOL bDone = FALSE;
do {
m_HardwareLock.Lock();
if ( GetWriteableSize()!=0 ) { // If not full
m_pReg2440Uart->Write_UTXH(ComChar);
bDone = TRUE;
}
else {
EnableXmitInterrupt(TRUE);
}
m_HardwareLock.Unlock();
if (!bDone)
WaitForSingleObject(m_XmitFlushDone, (ULONG)1000);
}
while (!bDone);
}
(13) EnableXmitInterrupt
EnableXmitInterrupt是暂时地关闭及重新开启串口的发送中断.
BOOL CPdd2440Uart::EnableXmitInterrupt(BOOL fEnable)
{
m_HardwareLock.Lock();
if (fEnable)
EnableInterrupt(S2440UART_INT_TXD);
else
DisableInterrupt(S2440UART_INT_TXD);
m_HardwareLock.Unlock();
return TRUE;
}
(14)CancelXmit
CancelXmit取消串口的数据发送,直接调用InitXmit(TRUE)重新初始化,如reset fifo等.
BOOL CPdd2440Uart::CancelXmit()
{
return InitXmit(TRUE);
}
(15)GetWaterMarkBit和GetWaterMark
WaterMark存储的实际上是接收中断模式,对应与UFCON的第4,5位:
这两个函数用了s_HighWaterPairs这样一个结构数组来保存对应关系:
static PAIRS s_HighWaterPairs[] = {
{0, 4 },
{1, 8 },
{2, 12 },
{3, 16 }
};
GetWaterMarkBit用来获得s_HighWaterPairs的key值,也就是0,1,2,3中的一个,用m_dwWaterMark与这个数组成员的AssociatedValue的比较来获得.
BYTE CPdd2440Uart::GetWaterMarkBit()
{
BYTE bReturnKey = (BYTE)s_HighWaterPairs[0].Key;
for (DWORD dwIndex=dim(s_HighWaterPairs)-1;dwIndex!=0; dwIndex --) {
if (m_dwWaterMark>=s_HighWaterPairs[dwIndex].AssociatedValue) {
bReturnKey = (BYTE)s_HighWaterPairs[dwIndex].Key;
break;
}
}
return bReturnKey;
}
GetWaterMark用来获得s_HighWaterPairs的AssociatedValue值,也就是0,4,8,16中的一个,m_dwWaterMark必须为这4个值中的一个,如不是则取最较小相近的.
DWORD CPdd2440Uart::GetWaterMark()
{
BYTE bReturnValue = (BYTE)s_HighWaterPairs[0].AssociatedValue;
for (DWORD dwIndex=dim(s_HighWaterPairs)-1;dwIndex!=0; dwIndex --) {
if (m_dwWaterMark>=s_HighWaterPairs[dwIndex].AssociatedValue) {
bReturnValue = (BYTE)s_HighWaterPairs[dwIndex].AssociatedValue;
break;
}
}
return bReturnValue;
}
(16)InitReceive
InitReceive负责初始化串口的数据接收功能. 和InitXmit一样有bool型一个参数,表示初始化还是解除初始化串口接收.
如果为TRUE,则先reset RX FIFO,设置UFCON Tx Trigger Level,写uWarterMarkBit,使能RX FIFO,清除error status,最后使能RX Timeout Interrupt,中断触发方式为level,接收模式为中断请求或轮询.
BOOL CPdd2440Uart::InitReceive(BOOL bInit)
{
m_HardwareLock.Lock();
if (bInit) {
BYTE uWarterMarkBit = GetWaterMarkBit();
if (uWarterMarkBit> 3)
uWarterMarkBit = 3;
// Setup Receive FIFO.
// Reset Receive Fifo.
DWORD dwBit = m_pReg2440Uart->Read_UFCON();
dwBit |= (1<<1);
dwBit &= ~(1<<0);
m_pReg2440Uart->Write_UFCON( dwBit);
// Set Trigger level to WaterMark.
dwBit &= ~(3<<4);
dwBit |= (uWarterMarkBit<<4);
m_pReg2440Uart->Write_UFCON(dwBit);
// Enable Receive FIFO.
dwBit &= ~(1<<1);
dwBit |= (1<<0);
m_pReg2440Uart->Write_UFCON(dwBit); // Xmit Fifo Reset Done..
m_pReg2440Uart->Read_UERSTAT(); // Clean Line Interrupt.
dwBit = m_pReg2440Uart->Read_UCON();
dwBit &= ~(3<<0);
dwBit |= (1<<0)|(1<<7); // Enable Rx Timeout.
// Set RxINterrupt To Pulse.
dwBit &= ~(1<<8);
m_pReg2440Uart->Write_UCON(dwBit);
EnableInterrupt(S2440UART_INT_RXD | S2440UART_INT_ERR );
}
else {
DisableInterrupt(S2440UART_INT_RXD | S2440UART_INT_ERR );
}
m_HardwareLock.Unlock();
return TRUE;
}
(17)ReceiveInterruptHandler
同XmitInterruptHandler一样还有个处理接收中断的ReceiveInterruptHandler
ReceiveInterruptHandler首先检查buffer的有效性,然后读取UFSTAT获得当前接收FIFO字节数,如满则设置dwNumRxInFifo为16字节,然后从URXH寄存器读取dwNumRxInFifo字节数的数据到接收buffer,在读数据的过程中会调用GetLineStatus获取线路状态,并调用DataReplaced进行判断如果无错误进读数据.
ULONG CPdd2440Uart::ReceiveInterruptHandler(PUCHAR pRxBuffer,ULONG *pBufflen)
{
DEBUGMSG(ZONE_THREAD|ZONE_READ,(TEXT("+CPdd2440Uart::ReceiveInterruptHandler pRxBuffer=%x,*pBufflen=%x/r/n"),
pRxBuffer,pBufflen!=NULL?*pBufflen:0));
DWORD dwBytesDropped = 0;
if (pRxBuffer && pBufflen ) {
DWORD dwBytesStored = 0 ;
DWORD dwRoomLeft = *pBufflen;
m_bReceivedCanceled = FALSE;
m_HardwareLock.Lock();
while (dwRoomLeft && !m_bReceivedCanceled) {
ULONG ulUFSTATE = m_pReg2440Uart->Read_UFSTAT();
DWORD dwNumRxInFifo = (ulUFSTATE & (0x3f<<0));
if ((ulUFSTATE & (1<<6))!=0) // Overflow. Use FIFO depth (16);
dwNumRxInFifo = SER2440_FIFO_DEPTH_RX;
DEBUGMSG(ZONE_THREAD|ZONE_READ,(TEXT("CPdd2440Uart::ReceiveInterruptHandler ulUFSTATE=%x,UTRSTAT=%x, dwNumRxInFifo=%X/r/n"),
ulUFSTATE, m_pReg2440Uart->Read_UTRSTAT(), dwNumRxInFifo));
if (dwNumRxInFifo) {
ASSERT((m_pReg2440Uart->Read_UTRSTAT () & (1<<0))!=0);
while (dwNumRxInFifo && dwRoomLeft) {
UCHAR uLineStatus = GetLineStatus();
UCHAR uData = m_pReg2440Uart->Read_URXH();
if (DataReplaced(&uData,(uLineStatus & UERSTATE_PARITY_ERROR)!=0)) {
*pRxBuffer++ = uData;
dwRoomLeft--;
dwBytesStored++;
}
dwNumRxInFifo --;
}
}
else
break;
}
if (m_bReceivedCanceled)
dwBytesStored = 0;
m_HardwareLock.Unlock();
*pBufflen = dwBytesStored;
}
else {
ASSERT(FALSE);
}
DEBUGMSG(ZONE_THREAD|ZONE_READ,(TEXT("-CPdd2440Uart::ReceiveInterruptHandler pRxBuffer=%x,*pBufflen=%x,dwBytesDropped=%x/r/n"),
pRxBuffer,pBufflen!=NULL?*pBufflen:0,dwBytesDropped));
return dwBytesDropped;
}
(18)CancelReceive
CancelReceive供驱动程序取消串口的数据接收.通过调用InitReceive(TRUE)来重新重新初始化串口,reset FIFO等.
ULONG CPdd2440Uart::CancelReceive()
{
m_bReceivedCanceled = TRUE;
m_HardwareLock.Lock();
InitReceive(TRUE);
m_HardwareLock.Unlock();
return 0;
}
下面再来看看和MODEM相关的部分.
(19)InitModem
InitModem初始化串口的MODEM功能.设置UMCON寄存器禁止AFC(Auto Flow Control)和使能RTS(Request To Send).
BOOL CPdd2440Uart::InitModem(BOOL bInit)
{
m_HardwareLock.Lock();
m_pReg2440Uart->Write_UMCON((1<<0)); // Disable AFC and Set RTS as default.
m_HardwareLock.Unlock();
return TRUE;
}
(20)GetModemStatus
GetModemStatus供驱动程序获取串口的MODEM状态. 通过读取UMSTAT寄存器来获取MODEM状态,进行事件通知或报告状态.
ULONG CPdd2440Uart::GetModemStatus()
{
m_HardwareLock.Lock();
ULONG ulReturn =0 ;
ULONG Events = 0;
UINT8 ubModemStatus = (UINT8) m_pReg2440Uart->Read_UMSTAT();
m_HardwareLock.Unlock();
// Event Notification.
if (ubModemStatus & (1<<2))
Events |= EV_CTS;
if (Events!=0)
EventCallback(Events);
// Report Modem Status;
if ( ubModemStatus & (1<<0) )
ulReturn |= MS_CTS_ON;
return ulReturn;
}
(21)SetRTS
设置RTS,根据输入参数来使能或禁止RTS.
void CPdd2440Uart::SetRTS(BOOL bSet)
{
m_HardwareLock.Lock();
ULONG ulData = m_pReg2440Uart->Read_UMCON();
if (bSet) {
ulData |= (1<<0);
}
else
ulData &= ~(1<<0);
m_pReg2440Uart->Write_UMCON(ulData);
m_HardwareLock.Unlock();
}
下面是线路相关处理函数:
(22)InitLine
InitLine初始化串口的数据收发属性.这里就是使能或禁止串口ERR中断.
BOOL CPdd2440Uart::InitLine(BOOL bInit)
{
m_HardwareLock.Lock();
if (bInit) {
// Set 8Bit,1Stop,NoParity,Normal Mode.
//m_pReg2440Uart->Write_ULCON( (0x3<<0) | (0<<1) | (0<<3) | (0<<6) );
EnableInterrupt( S2440UART_INT_ERR );
}
else {
DisableInterrupt(S2440UART_INT_ERR );
}
m_HardwareLock.Unlock();
return TRUE;
}
(23)GetLineStatus
GetLineStatus用来获取线路状态.读取UERSTAT寄存器来获得错误状态并返回或调用回调事件(Break Receive).
BYTE CPdd2440Uart::GetLineStatus()
{
m_HardwareLock.Lock();
ULONG ulData = m_pReg2440Uart->Read_UERSTAT();
m_HardwareLock.Unlock();
ULONG ulError = 0;
if (ulData & (1<<0) ) {
ulError |= CE_OVERRUN;
}
if (ulData & (1<<1)) {
ulError |= CE_RXPARITY;
}
if (ulData & (1<<2)) {
ulError |= CE_FRAME;
}
if (ulError)
SetReceiveError(ulError);
if (ulData & (1<<3)) {
EventCallback(EV_BREAK);
}
return (UINT8)ulData;
}
(24)SetBreak
SetBreak用来向对端发出一个间断信号(指串口的发送数据引脚上持续一个数据帧事件的低电平).这里通过设置UCON的bit4.
CPdd2440Uart::SetBreak(BOOL bSet)
{
m_HardwareLock.Lock();
ULONG ulData = m_pReg2440Uart->Read_UCON();
if (bSet)
ulData |= (1<<4);
else
ulData &= ~(1<<4);
m_pReg2440Uart->Write_UCON(ulData);
m_HardwareLock.Unlock();
}
(25)SetBaudRate
SetBaudRate设置波特率,调用CReg2440Uart类对象的Write_BaudRate来实现.
BOOL CPdd2440Uart::SetBaudRate(ULONG BaudRate,BOOL /*bIrModule*/)
{
m_HardwareLock.Lock();
BOOL bReturn = m_pReg2440Uart->Write_BaudRate(BaudRate);
m_HardwareLock.Unlock();
return TRUE;
}
(26)SetByteSize
SetByteSize设置串口的数据帧中的数据位的位数,设置ULCON寄存器的0,1位来设置.
BOOL CPdd2440Uart::SetByteSize(ULONG ByteSize)
{
BOOL bRet = TRUE;
m_HardwareLock.Lock();
ULONG ulData = m_pReg2440Uart->Read_ULCON() & (~0x3);
switch ( ByteSize ) {
case 5:
break;
case 6:
ulData|= (1<<0);
break;
case 7:
ulData |= (2<<0);
break;
case 8:
ulData |= (3<<0);
break;
default:
bRet = FALSE;
break;
}
if (bRet) {
m_pReg2440Uart->Write_ULCON(ulData);
}
m_HardwareLock.Unlock();
return bRet;
}
(27)SetParity
SetParity设置传输数据的校验方式(奇偶).通过设置ULCON的3,4,5位来实现.
BOOL CPdd2440Uart::SetParity(ULONG Parity)
{
BOOL bRet = TRUE;
m_HardwareLock.Lock();
ULONG ulData = m_pReg2440Uart->Read_ULCON() & (~(0x7<<3));
switch ( Parity ) {
case ODDPARITY:
ulData |= (4<<3);
break;
case EVENPARITY:
ulData |= (5<<3);
break;
case MARKPARITY:
ulData |= (6<<3);
break;
case SPACEPARITY:
ulData |= (7<<3);
break;
case NOPARITY:
break;
default:
bRet = FALSE;
break;
}
if (bRet) {
m_pReg2440Uart->Write_ULCON(ulData);
}
m_HardwareLock.Unlock();
return bRet;
}
(28)SetStopBits
SetStopBits用来设置停止位.通过设置ULCON的第2位来实现.
BOOL CPdd2440Uart::SetStopBits(ULONG StopBits)
{
BOOL bRet = TRUE;
m_HardwareLock.Lock();
ULONG ulData = m_pReg2440Uart->Read_ULCON() & (~(0x1<<2));
switch ( StopBits ) {
case ONESTOPBIT :
break;
case TWOSTOPBITS :
ulData |= (0x1<<2);
break;
default:
bRet = FALSE;
break;
}
if (bRet) {
m_pReg2440Uart->Write_ULCON(ulData);
}
m_HardwareLock.Unlock();
return bRet;
}
(29)SetOutputMode
最后还有个IR处理函数SetOutputMode,这个函数有两个bool输入参数,UseIR表示是否启用红外输出模式,Use9Pin表示是否启用9针引脚工作模式.
SetOutputMode首先调用基类的SetOutputMode,然后根据UseIR来设置ULCON的第6位.
void CPdd2440Uart::SetOutputMode(BOOL UseIR, BOOL Use9Pin)
{
m_HardwareLock.Lock();
CSerialPDD::SetOutputMode(UseIR, Use9Pin);
ULONG ulData = m_pReg2440Uart->Read_ULCON() & (~(0x1<<6));
ulData |= (UseIR?(0x1<<6):0);
m_pReg2440Uart->Write_ULCON(ulData);
m_HardwareLock.Unlock();
}
这里附上ULCON的具体位的含义:
更多对寄存器的操作请参考s3c2440A的datasheet.
CPdd2440Uart类基本介绍完了,这里介绍的都是需要实现的成员函数,还有些函数在基类CSerialPDD类中实现了,如SetReceiveError和GetReceiveError等.具体内容可以参考cserpdd.c中的CSerialPDD实现.
最后就需要实现针对特定串口的CPdd2440Uart的继承类CPdd2440Serial1和CPdd2440Serial2.分别对应于串口UART0和UART2.
3. CPdd2440Serial1
CPdd2440Serial1主要进行了端口初始化,针对UART0进行IO Port的设置,同时还实现了Modem的一些函数.
(1)构造函数CPdd2440Serial1和析构函数~ CPdd2440Serial1
CPdd2440Serial1初始化m_pIOPregs(IO控制寄存器虚拟地址)和fIsDSRSet(DSR属性).
~ CPdd2440Serial1释放 m_pIOPregs.
CPdd2440Serial1(LPTSTR lpActivePath, PVOID pMdd, PHWOBJ pHwObj)
: CPdd2440Uart(lpActivePath, pMdd, pHwObj)
{
m_pIOPregs = NULL;
m_fIsDSRSet = FALSE;
}
~CPdd2440Serial1() {
if (m_pIOPregs!=NULL)
MmUnmapIoSpace((PVOID)m_pIOPregs,0);
}
(2)Init
获取UART0的串口寄存器基址,映射IO地址空间,获取逻辑中断号,获取DSR,DTR地址,DSR/DTR端口号,最后调用基类Init函数.
virtual BOOL Init() {
PHYSICAL_ADDRESS ioPhysicalBase = { S3C2440A_BASE_REG_PA_IOPORT, 0};
ULONG inIoSpace = 0;
if (TranslateBusAddr(m_hParent,Internal,0, ioPhysicalBase,&inIoSpace,&ioPhysicalBase)) {
// Map it if it is Memeory Mapped IO.
m_pIOPregs = (S3C2440A_IOPORT_REG *)MmMapIoSpace(ioPhysicalBase, sizeof(S3C2440A_IOPORT_REG),FALSE);
}
if (m_pIOPregs) {
DDKISRINFO ddi;
if (GetIsrInfo(&ddi)== ERROR_SUCCESS &&
KernelIoControl(IOCTL_HAL_REQUEST_SYSINTR, &ddi.dwIrq, sizeof(UINT32), &ddi.dwSysintr, sizeof(UINT32), NULL))
{
RegSetValueEx(DEVLOAD_SYSINTR_VALNAME,REG_DWORD,(PBYTE)&ddi.dwSysintr, sizeof(UINT32));
}
else
return FALSE;
m_pDTRPort = (volatile ULONG *)&(m_pIOPregs->GPJDAT);
m_dwDTRPortNum = 10;
m_pDSRPort = (volatile ULONG *)&(m_pIOPregs->GPJDAT);
m_dwDSRPortNum = 11;
m_pDCDPort = (volatile ULONG *)&(m_pIOPregs->GPJDAT);
m_dwDCDPortNum = 12;
m_pRIPort = (volatile ULONG *)&(m_pIOPregs->GPGDAT);
m_dwRIPortNum = 2;
m_pIOPregs->GPHCON &= ~(0x3<<0 | 0x3<<2 | 0x3<<4 | 0x3<<6 );//tx,rx,rts,cts
m_pIOPregs->GPHCON |= (0x2<<0 | 0x2<<2 | 0x2<<4 | 0x2<<6 );
m_pIOPregs->GPHCON |= (0x2<<0 | 0x2<<2);
m_pIOPregs->GPHDN |= 0xf;
/* DTR --- GPJ10 | DSR --- GPJ11 | DCD --- GPJ12 */
m_pIOPregs->GPJCON &= ~(0x3<<20 | 0x3<<22 | 0x3<<24);
m_pIOPregs->GPDCON |= (0x1<<20 | 0x0<<22 | 0x0<<24);
m_pIOPregs->GPDDN |= (0x1<<10 | 0x1<<11 | 0x1<<12);
/* RI --- GPG2 */
m_pIOPregs->GPGCON &= (~0x3<<4);
m_pIOPregs->GPGDN |= (0x1<<2);
return CPdd2440Uart::Init();
}
return FALSE;
};
(3)SetDefaultConfiguration
SetDefaultConfiguration用来配置PORT H,设置引脚功能为UART0的RXD0,TXD0,GPH6为DTR,GPH7为DSR,使能RTS0,CTS0等.
virtual void SetDefaultConfiguration() {
CPdd2440Uart::SetDefaultConfiguration();
}
(4)InitModem
InitModem直接调用基类InitModem函数.
virtual BOOL InitModem(BOOL bInit) {
SetDTR(bInit);
return CPdd2440Uart::InitModem(bInit);
}
(5)GetModemStatus
GetModemStatus获取Modem状态,首先调用基类GetModemStatus,然后读取DSR端口状态获取相应状态调用事件回调函数.
virtual ULONG GetModemStatus() {
ULONG ulReturn = CPdd2440Uart::GetModemStatus();
ULONG ulEvent = 0;
m_HardwareLock.Lock();
BOOL fIsDSRSet = (((*m_pDSRPort) & (1<<m_dwDSRPortNum))==0);
if (fIsDSRSet != m_fIsDSRSet) {
ulEvent |= EV_DSR;
}
ulReturn |= (fIsDSRSet?(MS_DSR_ON):0);
m_fIsDSRSet = fIsDSRSet;
BOOL fIsDCDSet = (((*m_pDCDPort ) & (1<<m_dwDCDPortNum))==0);
if (fIsDCDSet != m_fIsDCDSet) {
ulEvent |= EV_RLSD;
}
ulReturn |= (fIsDCDSet?( MS_RLSD_ON):0);
m_fIsDCDSet = fIsDCDSet;
BOOL fIsRiSet = (((*m_pRIPort) & (1<<m_dwRIPortNum))==0);
if (fIsRiSet != m_fIsRISet) {
ulEvent |= EV_RING;
}
ulReturn |= (fIsRiSet?(MS_RING_ON):0);
m_fIsRISet = fIsRiSet;
m_HardwareLock.Unlock();
if (ulEvent!=0)
EventCallback(ulEvent,ulReturn);
return ulReturn;
}
(6)SetDTR
SetDTR用来设置DTR引脚.
virtual void SetDTR(BOOL bSet) {
if (bSet)
*m_pDTRPort &= ~(1<<m_dwDTRPortNum);
else
*m_pDTRPort |= (1<<m_dwDTRPortNum);
};
4. 附上CPdd2440Serial2,和CPdd2440Serial3的代码:
// CPdd2440Serial2 is only use for UART 1 which
class CPdd2440Serial2: public CPdd2440Uart {
public:
CPdd2440Serial2(LPTSTR lpActivePath, PVOID pMdd, PHWOBJ pHwObj)
: CPdd2440Uart(lpActivePath, pMdd, pHwObj)
{
m_pIOPregs = NULL;
}
~CPdd2440Serial2() {
if (m_pIOPregs!=NULL)
MmUnmapIoSpace((PVOID)m_pIOPregs,0);
}
virtual BOOL Init() {
PHYSICAL_ADDRESS ioPhysicalBase = { S3C2440A_BASE_REG_PA_IOPORT, 0};
ULONG inIoSpace = 0;
if (TranslateBusAddr(m_hParent,Internal,0, ioPhysicalBase,&inIoSpace,&ioPhysicalBase)) {
// Map it if it is Memeory Mapped IO.
m_pIOPregs =(S3C2440A_IOPORT_REG *) MmMapIoSpace(ioPhysicalBase, sizeof(S3C2440A_IOPORT_REG),FALSE);
}
if (m_pIOPregs) {
DDKISRINFO ddi;
if (GetIsrInfo(&ddi)== ERROR_SUCCESS &&
KernelIoControl(IOCTL_HAL_REQUEST_SYSINTR, &ddi.dwIrq, sizeof(UINT32), &ddi.dwSysintr, sizeof(UINT32), NULL))
{
RegSetValueEx(DEVLOAD_SYSINTR_VALNAME,REG_DWORD,(PBYTE)&ddi.dwSysintr, sizeof(UINT32));
}
else
return FALSE;
m_pIOPregs->GPHCON &= ~(0x3<<8 | 0x3<<10); // uart 1 - rx, tx
m_pIOPregs->GPHCON |= (0x2<<8 | 0x2<<10);
m_pIOPregs->GPHDN |= (0x1<<4 | 0x1<<5);
return CPdd2440Uart::Init();
}
return FALSE;
};
virtual void SetDefaultConfiguration() {
CPdd2440Uart::SetDefaultConfiguration();
}
virtual ULONG GetModemStatus() {
return (CPdd2440Uart::GetModemStatus() | MS_CTS_ON);
}
virtual void Rx_Pause(BOOL bSet) {
if(bSet)
m_pIOPregs->GPHCON = (m_pIOPregs->GPHCON & ~(0x3<<10)) | 0x0<<10;
else
m_pIOPregs->GPHCON = (m_pIOPregs->GPHCON & ~(0x3<<10)) | 0x2<<10;
}
volatile S3C2440A_IOPORT_REG * m_pIOPregs;
};
// CPdd2440Serial2 is only use for UART 2 which
class CPdd2440Serial3 : public CPdd2440Uart {
public:
CPdd2440Serial3(LPTSTR lpActivePath, PVOID pMdd, PHWOBJ pHwObj)
: CPdd2440Uart(lpActivePath, pMdd, pHwObj)
{
m_pIOPregs = NULL;
}
~CPdd2440Serial3() {
if (m_pIOPregs!=NULL)
MmUnmapIoSpace((PVOID)m_pIOPregs,0);
}
virtual BOOL Init() {
PHYSICAL_ADDRESS ioPhysicalBase = { S3C2440A_BASE_REG_PA_IOPORT, 0};
ULONG inIoSpace = 0;
if (TranslateBusAddr(m_hParent,Internal,0, ioPhysicalBase,&inIoSpace,&ioPhysicalBase)) {
// Map it if it is Memeory Mapped IO.
m_pIOPregs =(S3C2440A_IOPORT_REG *) MmMapIoSpace(ioPhysicalBase, sizeof(S3C2440A_IOPORT_REG),FALSE);
}
if (m_pIOPregs) {
DDKISRINFO ddi;
if (GetIsrInfo(&ddi)== ERROR_SUCCESS &&
KernelIoControl(IOCTL_HAL_REQUEST_SYSINTR, &ddi.dwIrq, sizeof(UINT32), &ddi.dwSysintr, sizeof(UINT32), NULL))
{
RegSetValueEx(DEVLOAD_SYSINTR_VALNAME,REG_DWORD,(PBYTE)&ddi.dwSysintr, sizeof(UINT32));
}
else
return FALSE;
m_pIOPregs->GPHCON &= ~(0x3<<12 | 0x3<<14); // uart 1 - rx, tx
m_pIOPregs->GPHCON |= (0x2<<12 | 0x2<<14);
m_pIOPregs->GPHDN |= (0x1<<6 | 0x1<<7);
return CPdd2440Uart::Init();
}
return FALSE;
};
virtual void SetDefaultConfiguration() {
CPdd2440Uart::SetDefaultConfiguration();
}
virtual ULONG GetModemStatus() {
return (CPdd2440Uart::GetModemStatus() | MS_CTS_ON);
}
virtual void Rx_Pause(BOOL bSet) {
if(bSet)
m_pIOPregs->GPHCON = (m_pIOPregs->GPHCON & ~(0x3<<14)) | 0x0<<14;
else
m_pIOPregs->GPHCON = (m_pIOPregs->GPHCON & ~(0x3<<14)) | 0x2<<14;
}
volatile S3C2440A_IOPORT_REG * m_pIOPregs;
};
5. CSerialPDD * CreateSerialObject
CreateSerialObject创建了UART0或UART1或UART2的实例,返回给MDD层调用
CSerialPDD * CreateSerialObject(LPTSTR lpActivePath, PVOID pMdd,PHWOBJ pHwObj, DWORD DeviceArrayIndex)
{
CSerialPDD * pSerialPDD = NULL;
switch (DeviceArrayIndex) {
case 0:
pSerialPDD = new CPdd2440Serial1(lpActivePath,pMdd, pHwObj);
RETAILMSG(1, (TEXT("INFO: CPdd2440Serial0 (%x)./r/n"), pSerialPDD));
break;
case 1:
pSerialPDD = new CPdd2440Serial2(lpActivePath,pMdd, pHwObj);
RETAILMSG(1, (TEXT("INFO: CPdd2440Serial1 (%x)./r/n"), pSerialPDD));
break;
case 2:
pSerialPDD = new CPdd2440Serial3(lpActivePath,pMdd, pHwObj);
RETAILMSG(1, (TEXT("INFO: CPdd2440Serial2 (%x)./r/n"), pSerialPDD));
break;
}
if (pSerialPDD && !pSerialPDD->Init()) {
delete pSerialPDD;
pSerialPDD = NULL;
}
return pSerialPDD;
}
6. DeleteSerialObject
对应的还有一个删除串口实例的函数DeleteSerialObject.
void DeleteSerialObject(CSerialPDD * pSerialPDD)
{
if (pSerialPDD)
delete pSerialPDD;
}
参考文献(参考以下两个bolg的内容和S3C2440的BSP源码而写的,感谢原创者shevsten,nasiry):
http://blog.csdn.net/shevsten/archive/2010/06/08/5655749.aspx