/*+
THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
Copyright (c) 2001. Samsung Electronics, co. ltd  All rights reserved.

Module Name:  

Abstract:

	S3C2440 USB function(ACTIVE SYNC) device driver (Wrapper layer)

rev:
	2003.3.19	: updated for USB-DMA (purnnamu)
	2002.5.7	: Add to s3c2410_code (Seung-han, Lim)
	2002.1.22	: First release/no error recovery (kwangyoon LEE, kwangyoon@samsung.com)

Notes: 
*/

#include <windows.h>
#include <types.h>
#include <ceddk.h>
#include <memory.h>
#include <notify.h>
#include <serhw.h>
#include "S2440.h"
#include <SC2440_usb_hw.h>
#include <SC2440_usb_ser.h>
#include <nkintr.h>
#include <oalintr.h>
#include <devload.h>
#include <windev.h>
#undef ZONE_INIT
#include <serdbg.h>
#include <celog.h> 
#include <drv_glob.h> //:-)

//#define DEBUGMSG RETAILMSG
#define POLL_FOR_DISCONNECT
#define USE_NEW_SERIAL_MODEL

extern VOID SerialEventHandler(PVOID pHead);

extern void msWait(unsigned msVal);

extern volatile USBD_GLOBALS *usbdShMem;	//:-)


__inline static VOID
SignalRemoval( PSER_INFO pHWHead )
{
	pHWHead->ModemStatus &= ~MS_RLSD_ON;
	EvaluateEventFlag(pHWHead->pMddHead, EV_RLSD);
}


static
DWORD WINAPI SerEventThread(PSER_INFO pHWHead)
{
	ULONG WaitReturn;
	static HWND fgp=NULL, prev_fgp	= NULL; 	
	static HWND dialog_hwnd			= NULL;
	static ULONG firstplug			= 0, toretry = 0;
	static PROCESS_INFORMATION pi_usbcnect, pi_repllog;
	
	DEBUGMSG(1, (TEXT("++SerEventThread\r\n")));
	DEBUGMSG(1, (TEXT("Spinning in dispatch thread %X\n\r"), pHWHead));
	
	while (!pHWHead->pDispatchThread)
		Sleep(20);

	while (!pHWHead->KillRxThread) {
		DEBUGMSG (1, (TEXT("Event %X, Index %d, Int %d\r\n"), 
				pHWHead->hSerialEvent,
				pHWHead->dwDevIndex,
				pHWHead->pHWObj->dwIntID ));

	// Ugly.  We don't get an interrupt when we are unplugged from the
	// bus, but we'd like to know when that happens.  So we use a timeout
	// on our interrupt wait.  This effectively allows me to poll the SOF
	// status, and if SOFs stop arriving, I'll assume we have been
	// disconnected from the bus.  Wait is last, allowing us to essentially
	// spoof one interrupt right at startup.

	// Only poll if we are opened and active.  Otherwise no one cares

#ifdef POLL_FOR_DISCONNECT
		if (pHWHead->cOpenCount && pHWHead->dConfIdx)
		{
			// DEBUGMSG(1, (TEXT("waitforsingleobject in up\r\n")));
      
			WaitReturn = WaitForSingleObject(pHWHead->hSerialEvent, SC2440_USB_POLL_RATE);
			// DEBUGMSG(1, (TEXT("waitforsingleobject in\r\n")));
		}
		else
#endif        
		{
		  	//DEBUGMSG(1, (TEXT("waitforsingleobject out up\r\n"))); 
	      	WaitReturn = WaitForSingleObject(pHWHead->hSerialEvent, INFINITE);

/*
	      	DEBUGMSG(1, (TEXT("waitforsingleobject out\r\n")));  
	
		  	if (WaitReturn == WAIT_ABANDONED)
		  	{
				DEBUGMSG(1, (TEXT("WAIT_ABANDONED\r\n")));
			}
	
			if (WaitReturn == WAIT_OBJECT_0)
			{
				DEBUGMSG(1, (TEXT("WAIT_OBJECT_0\r\n")));
			}
	
			if (WaitReturn == WAIT_TIMEOUT)
			{
				DEBUGMSG(1, (TEXT("WAIT_TIMEOUT\r\n")));
			}
	
			if (WaitReturn == WAIT_FAILED)
			{
				DEBUGMSG(1, (TEXT("WAIT_FAILED\r\n")));
			}
*/
		}
	
		SerialEventHandler(pHWHead->pMddHead);
		InterruptDone(pHWHead->pHWObj->dwIntID);
	}
	
	DEBUGMSG (1, (TEXT("--SerEventThread %x exiting\r\n"), pHWHead));

	return (0);
}

static
BOOL StartEventThread(PSER_INFO pHWHead)
{
    
	// Initialize the interrupt to be associated with the hSerialEvent
	// event. If this call fails, then another process has registered 
	// for the interrupt, so fail the init and set the hSerialEvent to NULL.
	DEBUGMSG(1, (TEXT("Initializing interrupt 0x%X, 0x%X\n\r"),
			pHWHead->pHWObj->dwIntID, pHWHead->hSerialEvent));

	if (!InterruptInitialize(pHWHead->pHWObj->dwIntID, pHWHead->hSerialEvent, 0, 0)) 
	{

		DEBUGMSG(1, (TEXT("Error initializing interrupt\n\r")));

		return (FALSE);
	}

	InterruptDone(pHWHead->pHWObj->dwIntID);
    
	// Now set up the dispatch thread 
	pHWHead->KillRxThread = 0;

	DEBUGMSG(1, (TEXT("Spinning thread%X\n\r"), pHWHead));

	pHWHead->pDispatchThread = CreateThread(NULL,0, SerEventThread,
							pHWHead, 0,NULL);
	if (pHWHead->pDispatchThread == NULL) 
	{
		DEBUGMSG(1, (TEXT("Error creating Event thread (%d)\n\r"), GetLastError()));
		return (FALSE);
	}

	DEBUGMSG (1, (TEXT("Created Event thread %X\r\n"), pHWHead->pDispatchThread));
	DEBUGMSG (1, (TEXT("About to set priority\r\n")));

	CeSetThreadPriority(pHWHead->pDispatchThread, DEFAULT_THREAD_PRIO);
                          
	DEBUGMSG (1, (TEXT("Back from setting priority\r\n")));

	return (TRUE);
}

/***************************************************************
 @doc OEM
 @func VOID | SerSetBaudRate |
 * This routine sets the baud rate of the device.
 *  Not exported to users, only to driver.
 *
 @rdesc None.
 ****************************************************************/
static
BOOL SerSetBaudRate( PVOID pHead,	// @parm PVOID returned by SerInit
		     ULONG BaudRate )	// @parm ULONG representing decimal baud rate.
{
	PSER_INFO   pHWHead = (PSER_INFO)pHead;

	DEBUGMSG(1, (TEXT("SerSetBaudRate\r\n")));

	// Baudrate is meaningless.  Just be agreeable and say we did it.
	return (TRUE);    
}

/****************************************************************
	@doc INTERNAL

	@func VOID | SerUSB_InternalMapRegisterAddresses |
		This routine maps the ASIC registers. 
		It's an artifact of this
		implementation.

	@rdesc None.
****************************************************************/
static
void SerUSBInternalMapRegisterAddresses( PSER_INFO   pHWHead )
{
	PUCHAR  pVMem;
	BOOL    bMapReturn;
	ULONG   err			= 0;
  
	DEBUGMSG(1, (TEXT("++SerUSBInternalMapRegisterAddresses\r\n")));

	// Map CSR registers.
	pVMem = (PUCHAR)VirtualAlloc(0, PAGE_SIZE*2, MEM_RESERVE, PAGE_NOACCESS);
	
	if (pVMem) 
	{
		DEBUGMSG(1, (TEXT("VirtualAlloc Succeeded\r\n")));

		bMapReturn = VirtualCopy( pVMem,
					  (LPVOID)pHWHead->dwIOBase,
					  PAGE_SIZE,
					  PAGE_READWRITE | PAGE_NOCACHE );
		if (!bMapReturn) 
		{
			err = GetLastError();
			DEBUGMSG(1, (TEXT("Virtual Copy: Serial SPACE FAILED\r\n")));
		}

		pVMem = (BYTE *)pVMem;
		pHWHead->pUSBCtrlAddr=(volatile struct udcreg *)((BYTE *)pVMem + 0x140); // offset

		bMapReturn = VirtualCopy( pVMem+PAGE_SIZE,
					  (LPVOID)IOP_BASE,
					  PAGE_SIZE,
					  PAGE_READWRITE | PAGE_NOCACHE );
		if (!bMapReturn) 
		{
			err = GetLastError();
			DEBUGMSG(1, (TEXT("Virtual Copy: IOP SPACE FAILED\r\n")));
		}

		pHWHead->pIrqCtrlAddr=(volatile IOPreg *)(pVMem+PAGE_SIZE);

		bMapReturn = VirtualCopy( pVMem+PAGE_SIZE,
					  (LPVOID)CLKPWR_BASE,
					  PAGE_SIZE,
					  PAGE_READWRITE | PAGE_NOCACHE );
		if (!bMapReturn) 
		{
			err = GetLastError();
			DEBUGMSG(1, (TEXT("Virtual Copy: CLKPWR SPACE FAILED\r\n")));
		}

		pHWHead->pCLKPWR = (volatile CLKPWRreg *)(pVMem+PAGE_SIZE);
		
	} else {
    		DEBUGMSG(1, (TEXT("Virtual Alloc: FAILED\r\n")));
	}
  
		DEBUGMSG(1, (TEXT("VirtualCopy Succeeded, pVMem:%x\r\n"), pVMem));

		DEBUGMSG(1, (TEXT("--SerUSBInternalMapRegisterAddresses\r\n")));
}

static
BOOL SerSetIRBaudRate( PSER_INFO pHWHead, ULONG baud )	// @parm baud rate - ignored
{
	DEBUGMSG(1, (TEXT("Serial set IR Baud %d\r\n"), baud));

	// We don't support IR
	return (FALSE);
}



/*******************************************************************************
Routine:

    Ser_GetRegistryData

Description:

    Take the registry path provided to COM_Init and use it to find this 
    requested comm port's DeviceArrayIndex, the IOPort Base Address, and the
    Interrupt number.
   
Arguments:

    LPCTSTR regKeyPath	the registry path passed in to COM_Init.

Return Value:

    -1 if there is an error.

******************************************************************************/

static
BOOL Ser_GetRegistryData(PSER_INFO pHWHead,	LPCTSTR regKeyPath)
{
#define GCI_BUFFER_SIZE 256   

	LONG    regError;
	HKEY    hKey;
	TCHAR   devKeyPath[GCI_BUFFER_SIZE];
	DWORD   dwDataSize = GCI_BUFFER_SIZE;

	DEBUGMSG(1,
			(TEXT("Ser_GetRegistryData Try to open %s\r\n"),regKeyPath));

  // We've been handed the name of a key in the registry that was generated
  // on the fly by device.exe.  We're going to open that key and pull from it
  // a value that is the name of this serial port's real key.  That key
  // will have the DeviceArrayIndex that we're trying to find.  

	regError = RegOpenKeyEx(HKEY_LOCAL_MACHINE, 
				regKeyPath,
				0, 
				KEY_ALL_ACCESS, 
				&hKey);

	if (regError != ERROR_SUCCESS) 
	{
		DEBUGMSG(1,(TEXT("Failed to open %s, Error 0x%X\r\n"),regKeyPath,regError));
		return (FALSE);                
	}

	regError = RegQueryValueEx( hKey, 
				    DEVLOAD_DEVKEY_VALNAME,
				    NULL, 
				    NULL, 
				    (LPBYTE)devKeyPath,
				    &dwDataSize);

	// We're done with that registry key, so close it.
	RegCloseKey (hKey);

	if (regError != ERROR_SUCCESS) 
	{
		DEBUGMSG(1, (TEXT("Failed to find data at %s\\%s, Error 0x%X\r\n"), 
				regKeyPath, DEVLOAD_DEVKEY_VALNAME, regError));
		return (FALSE);                
	}

	DEBUGMSG(1, (TEXT("Try to open %s\r\n"), devKeyPath));

	regError = RegOpenKeyEx(HKEY_LOCAL_MACHINE, 
				devKeyPath, 
				0,
				KEY_ALL_ACCESS, 
				&hKey);

	if (regError != ERROR_SUCCESS) 
	{
		DEBUGMSG(1,(TEXT("Failed to open %s, Error 0x%X\r\n"), devKeyPath, regError));
		return (FALSE);        
	}

	// Okay, we're finally ready to try and load our registry data.
	dwDataSize = SC2440USB_REG_DEVINDEX_VAL_LEN;

	regError   = RegQueryValueEx(hKey, 
				     SC2440USB_REG_DEVINDEX_VAL_NAME, 
				     NULL, 
				     NULL,
				     (LPBYTE)(&pHWHead->dwDevIndex), 
				     &dwDataSize);

	if (regError == ERROR_SUCCESS) 
	{
		dwDataSize = SC2440USB_REG_IRQ_VAL_LEN;

		regError = RegQueryValueEx( hKey, 
					    SC2440USB_REG_IRQ_VAL_NAME, 
					    NULL, 
					    NULL,
					    (LPBYTE)(&pHWHead->dwIRQ), 
					    &dwDataSize );
	}

	if (regError == ERROR_SUCCESS) 
	{
		dwDataSize = SC2440USB_REG_IOBASE_VAL_LEN;

		regError = RegQueryValueEx( hKey, 
					    SC2440USB_REG_IOBASE_VAL_NAME, 
					    NULL, 
					    NULL,
					    (LPBYTE)(&pHWHead->dwIOBase), 
					    &dwDataSize);
	}

	if (regError == ERROR_SUCCESS) 
	{
		dwDataSize = SC2440USB_REG_IOLEN_VAL_LEN;

		regError = RegQueryValueEx( hKey, 
					    SC2440USB_REG_IOLEN_VAL_NAME, 
					    NULL, 
					    NULL,
					    (LPBYTE)(&pHWHead->dwIOLen), 
					    &dwDataSize);
	}

	RegCloseKey (hKey);

	if (regError != ERROR_SUCCESS) 
	{
		DEBUGMSG(1,(TEXT("Failed to get serial registry values, Error 0x%X\r\n"),
																		regError));
		return (FALSE);
	}

	DEBUGMSG(1,(TEXT("SerInit - Devindex %d, IRQ %d, IOB %X, IOLen %X \r\n"),
					pHWHead->dwDevIndex,
					pHWHead->dwIRQ,
					pHWHead->dwIOBase,
					pHWHead->dwIOLen));

	return (TRUE); 
}



/*************************************************************************
 @doc OEM 
 @func PVOID | SerInit | Initializes device identified by argument.
 *  This routine sets information controlled by the user
 *  such as Line control and baud rate. It can also initialize events and
 *  interrupts, thereby indirectly managing initializing hardware buffers.
 *  Exported only to driver, called only once per process.
 *
 @rdesc The return value is a PVOID to be passed back into the HW
 dependent layer when HW functions are called.
 ************************************************************************/
static
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

{
	PSER_INFO pHWHead;
	PHYSICAL_ADDRESS PhysicalAddress = {0,0};

#ifdef DEBUG
	dpCurSettings.ulZoneMask = 0;
#endif

	DEBUGMSG (1, (TEXT("++SerInit %X\r\n"), pMddHead ));

	// Allocate for our main data structure and one of it's fields.
	pHWHead = (PSER_INFO)LocalAlloc( LPTR, sizeof(SER_INFO) );
	if ( !pHWHead )
		goto ALLOCFAILED;

	if (!Ser_GetRegistryData(pHWHead, (LPCTSTR)Identifier)) 
	{
		DEBUGMSG (1,(TEXT("SerInit - Unable to read registry data. Failing Init !!! \r\n")));
		goto ALLOCFAILED;
	}

	// Create our interrupt event.
	pHWHead->hSerialEvent = CreateEvent(0,FALSE,FALSE,NULL);

	// Initialize our critical sections
	InitializeCriticalSection(&(pHWHead->TransmitCritSec));
	InitializeCriticalSection(&(pHWHead->HwRegCritSec));

	SerUSBInternalMapRegisterAddresses(pHWHead);
	if(!InitUsbdDriverGlobals())  //:-)
		goto ALLOCFAILED;
	if(!UsbdAllocateVm())  //:-)
		goto ALLOCFAILED;
	

	pHWHead->pMddHead		= pMddHead;
	pHWHead->cOpenCount		= 0;
	pHWHead->pHWObj			= pHWObj;
	pHWHead->wSOFStableCnt	= 0; 				
    
	// Set up our Comm Properties data    
	pHWHead->CommProp.wPacketLength      = 0xffff;
	pHWHead->CommProp.wPacketVersion     = 0xffff;
	pHWHead->CommProp.dwServiceMask      = SP_SERIALCOMM;
	pHWHead->CommProp.dwReserved1        = 0;
	pHWHead->CommProp.dwMaxTxQueue       = 64;
	pHWHead->CommProp.dwMaxRxQueue       = 64;
	pHWHead->CommProp.dwMaxBaud          = BAUD_115200;
	pHWHead->CommProp.dwProvSubType      = PST_RS232;
	pHWHead->CommProp.dwProvCapabilities =  PCF_RLSD | 
						PCF_SETXCHAR |
						PCF_INTTIMEOUTS |
						PCF_SPECIALCHARS |
						PCF_TOTALTIMEOUTS |
						PCF_XONXOFF;
	pHWHead->CommProp.dwSettableBaud      =	BAUD_075 | BAUD_110 | BAUD_150 | 
						BAUD_300 | BAUD_600 |
						BAUD_1200 | BAUD_1800 | 
						BAUD_2400 | BAUD_4800 |
						BAUD_7200 | BAUD_9600 | BAUD_14400 |
						BAUD_19200 | BAUD_38400 | 
						BAUD_56K | BAUD_128K |
						BAUD_115200 | BAUD_57600 | BAUD_USER;
	pHWHead->CommProp.dwSettableParams    =	SP_BAUD | SP_RLSD ;
	pHWHead->CommProp.wSettableData       =	DATABITS_8;
	pHWHead->CommProp.wSettableStopParity =	STOPBITS_10 | STOPBITS_20 |
						PARITY_NONE | PARITY_ODD | 
						PARITY_EVEN | PARITY_SPACE |
						PARITY_MARK;

	pHWHead->fIRMode  = FALSE;   // Select wired by default

    pHWHead->State = IDLE;
    
	// Here is where we do any actual init for the hardware.  In the case of
	// USB function, we mostly just validate the hardware and then enable
 	// interrupts.  When we detect the presence of the bus, GetIntr will take
	// care of identify us to the host and kicking things off.

	SC2440_USB_Init(pHWHead);			

	DEBUGMSG (1, (TEXT("--SerInit - %X\r\n"), pHWHead ));

	return (pHWHead);

ALLOCFAILED:
	// Unmap any memory areas that we may have mapped.
	if ( pHWHead->pUSBCtrlAddr)
		VirtualFree((PVOID)pHWHead->pUSBCtrlAddr, 0, MEM_RELEASE);

	if ( pHWHead->pIrqCtrlAddr)
		VirtualFree((PVOID)pHWHead->pIrqCtrlAddr, 0, MEM_RELEASE);

	if ( pHWHead->pCLKPWR)
		VirtualFree((PVOID)pHWHead->pCLKPWR, 0, MEM_RELEASE);

	LocalFree(pHWHead);

	// Free any critical sections we have allocated
	DeleteCriticalSection(&(pHWHead->TransmitCritSec));
	DeleteCriticalSection(&(pHWHead->HwRegCritSec));

	// And free the context data structure
	LocalFree(pHWHead);

	DEBUGMSG (1,(TEXT("--SerInit - %X\r\n"), pHWHead ));

	return (NULL);
}

/*************************************************************************
 @doc OEM 
 @func PVOID | SerPostInit | Performs final hardware initialization.
 ************************************************************************/
static
BOOL SerPostInit(PVOID   pHead)   // @parm PVOID returned by SerInit.
{
	PSER_INFO pHWHead = (PSER_INFO)pHead;

	DEBUGMSG(1, (TEXT("+++SerPostInit\r\n")));

	// We use a PDD specific thread, rather than the default thread provided
	// by the MDD.
	StartEventThread( pHWHead );

	// Enable interrupts after thread is started - DAL
	// ....
	
	return TRUE;
}

/*************************************************************************
 @doc OEM 
 @func PVOID | SerDeinit | Deinitializes device identified by argument.
 *  This routine frees any memory allocated by SerInit.
 ************************************************************************/
static
BOOL SerDeinit(PVOID   pHead)   // @parm PVOID returned by SerInit.
{
	PSER_INFO pHWHead = (PSER_INFO)pHead;

	DEBUGMSG(1, (TEXT("+SerDeinit\r\n")));

	// Disable interrupts
	// ...

	if ( !pHWHead )
		return (FALSE);

	// Make sure device is closed before doing DeInit
	if ( pHWHead->cOpenCount )
		SerClose( pHead );

	if ( pHWHead->pUSBCtrlAddr)
		VirtualFree((PVOID)pHWHead->pUSBCtrlAddr, 0, MEM_RELEASE);

	if ( pHWHead->pIrqCtrlAddr)
		VirtualFree((PVOID)pHWHead->pIrqCtrlAddr, 0, MEM_RELEASE);

	if ( pHWHead->pCLKPWR)
		VirtualFree((PVOID)pHWHead->pCLKPWR, 0, MEM_RELEASE);

	UsbdDeallocateVm(); //:-)

	// Free any critical sections we have allocated
	DeleteCriticalSection(&(pHWHead->TransmitCritSec));
	DeleteCriticalSection(&(pHWHead->HwRegCritSec));

	// Free the HWObj allocated in GetSerialObject
	LocalFree(pHWHead->pHWObj);

	LocalFree(pHWHead);

	DEBUGMSG(1, (TEXT("-SerDeinit\r\n")));

	return (TRUE);
}

/*************************************************************************
 @doc OEM
 @func BOOL | SerOpen | This routine is called when the port is opened.
 *  Not exported to users, only to driver.
 *
 @rdesc Returns TRUE if successful, FALSEotherwise.
 ************************************************************************/
static
BOOL SerOpen(PVOID   pHead) /*@parm PVOID returned by Serinit. */
{
	PSER_INFO pHWHead = (PSER_INFO)pHead;

	DEBUGMSG(1,
			(TEXT("SerOpen+\r\n")));

	// Disallow multiple simultaneous opens
	if (pHWHead->cOpenCount)
		return (FALSE);
    
	pHWHead->cOpenCount++;

#ifdef POLL_FOR_DISCONNECT
	// Yuck.  We want to poll for detaches when the device is open.
	// But right now, the IST is in a wait infinite.  Spoof an interrupt
	// to let him know we have been opened and he needs to start polling.
	SetEvent(pHWHead->hSerialEvent);
#endif

	return (TRUE);
}

/*************************************************************************
 @doc OEM
 @func ULONG | SerClose | This routine closes the device identified by the 
 *  PVOID returned by SerInit.
 *  Not exported to users, only to driver.
 *
 @rdesc The return value is 0.
 ************************************************************************/
static
ULONG SerClose(PVOID   pHead)   // @parm PVOID returned by SerInit.
{
	PSER_INFO pHWHead = (PSER_INFO)pHead;

	DEBUGMSG (1, (TEXT("++SerClose\r\n")));

	if (pHWHead->cOpenCount) 
	{
		DEBUGMSG (1, (TEXT("SerClose, closing device\r\n")));
		pHWHead->cOpenCount--;

#ifdef TODO
		// Do we need something similar on USB???
		// while we are still transmitting, sleep.
		uTries = 0;

		while ( ((pHWHead->ser16550.IER = READ_PORT_UCHAR(pHWHead->ser16550.pIER)) 
					& SERIAL_IER_THR) &&	// indicates TX in progress
					(uTries++ < 100) &&     // safety net
								// indicates FIFO not yet empty
					!(pHWHead->ser16550.LSR & SERIAL_LSR_TEMT)) 
		{

				DEBUGMSG(1, (TEXT("SerClose, TX in progress, IER 0x%X, LSR 0x%X\r\n"),
						*pHWHead->ser16550.pIER, pHWHead->ser16550.LSR));
				Sleep(10);
		}
#endif

		// TODO - When the device is closed, should power it down or somehow try to
		// let the desktop know that we aren't doing anything with any data that it
		// might be sending our way..
    
		// Mask interrupts? - No, we wont see any more traffic - MBE.
		//pHWHead->pIrqCtrlAddr->icmr.sp0 = 0;
	}

	DEBUGMSG(1,(TEXT("--SerClose\r\n")));

	return (0);
}

/*************************************************************************
 @doc OEM
 @func ULONG | SerRxIntr | This routine gets several characters from the 
	hardware receive buffer and puts them in a buffer provided via the se-
	cond argument. It returns the number of bytes lost to overrun.

 @rdesc The return value indicates the number of overruns detected.
	The actual number of dropped characters may be higher.
**************************************************************************/
static
ULONG SerRxIntr(PVOID pHead,                // @parm Pointer to hardware head
		PUCHAR pRxBuffer,           // @parm Pointer to receive buffer
		ULONG *pBufflen )           // @parm In = max bytes to read, out = bytes read
{
	PSER_INFO   pHWHead			 = (PSER_INFO)pHead;
	ULONG       RetVal			 = 0;
	ULONG       TargetRoom			 = *pBufflen;
	BOOL        fRXFlag			 = FALSE;
	BOOL        fReplaceparityErrors = FALSE;
	BOOL        fNull;
	UCHAR       cEvtChar;
	PUCHAR      pRxOrig			 = pRxBuffer;

	DEBUGMSG(1, (TEXT("++SerRxIntr %d\r\n"), *pBufflen));

	cEvtChar = pHWHead->dcb.EvtChar;
	fNull	 = pHWHead->dcb.fNull;

	if( pHWHead->dcb.fErrorChar && pHWHead->dcb.fParity )
		fReplaceparityErrors = TRUE;
  
	// Protect the following from collision on hardware registers.
	// In particular, we can't let anyone else do SC2440_USB_ accesses between
	// time we set the read address and time we do the actual read in 
	// the loop below.
	EnterCriticalSection(&(pHWHead->HwRegCritSec));
	
	fRXFlag = SC2440_USB_RxIntHandler(pHWHead, pRxBuffer, pBufflen);

	LeaveCriticalSection(&(pHWHead->HwRegCritSec));

	// if we saw one (or more) EVT chars, then generate an event
	if( fRXFlag )
		EvaluateEventFlag( pHWHead->pMddHead, EV_RXFLAG );

#ifdef DEBUG
	if( ZONE_RXDATA )
		CELOGDATA(1, CELID_RAW_UCHAR, pRxOrig, (WORD)*pBufflen, 1, CELZONE_MISC);
#endif

	DEBUGMSG(1, (TEXT("--SerRxIntr - rx %d, drop %d.\r\n"),
			*pBufflen,
			pHWHead->DroppedBytes));

	RetVal		      = pHWHead->DroppedBytes;
	pHWHead->DroppedBytes = 0;

	return RetVal;
}

/*************************************************************************
	@doc OEM
	@func PVOID | SerGetRxStart | This routine returns the start of the 
			hardware receive buffer.  See SerGetRxBufferSize.
 
	@rdesc The return value is a pointer to the start of the device 
			receive buffer.
*************************************************************************/ 
static
PVOID SerGetRxStart( PVOID   pHead )	// @parm PVOID returned by Serinit.
{
	DEBUGMSG(1, (TEXT("SerGetRxStart\r\n")));   

	return  NULL;
}

/*************************************************************************
	@doc OEM
	@func ULONG | SerGetInterruptType | This function is called
			by the MDD whenever an interrupt occurs.  The return code
			is then checked by the MDD to determine which of the four
			interrupt handling routines are to be called.

	@rdesc This routine returns a bitmask indicating which interrupts
			are currently pending.
*************************************************************************/ 
static
INTERRUPT_TYPE SerGetInterruptType( PVOID pHead ) // Pointer to hardware head
{
	PSER_INFO	pHWHead		= (PSER_INFO)pHead;
	INTERRUPT_TYPE	interrupts=0;
    
	DEBUGMSG(1, (TEXT("++SerGetInterruptType 0x%X\r\n"), pHead));	

	interrupts = SC2440_USB_GetInterruptType(pHWHead);

	if (INTR_LINE & interrupts)
	{
		pHWHead->dConfIdx = 0;
		SignalRemoval(pHWHead);
	}

#ifdef POLL_FOR_DISCONNECT

	//interrupts = HW_GetInterruptType(pHWHead);

	//RETAILMSG(1, (TEXT("interrupts = 0x%x\r\n"), interrupts));

	if (interrupts) 
	{
		pHWHead->wSOFStableCnt = 0;
		//RETAILMSG(1, (TEXT("pHWHead->wSOFStableCnt = 0x%x\r\n"), pHWHead->wSOFStableCnt));
	} 
	else if (pHWHead->dConfIdx && (pHWHead->wPrevSOF == pHWHead->wSOF))
	{
		if (++pHWHead->wSOFStableCnt > SOF_STABLE_MAX)
		{
			//RETAILMSG(1, (TEXT("DCD Dropped due to: wSOFStableCnt: %u\r\n"), pHWHead->wSOFStableCnt));
			pHWHead->dConfIdx = 0;
			SignalRemoval(pHWHead);
		}
	}

#endif

	DEBUGMSG (1, (TEXT("--SerGetInterruptType 0x%X, 0x%X\r\n"),
					pHead, interrupts));
	return interrupts;
}

/*************************************************************************
	@doc OEM
	@func ULONG | SerModemIntr | This routine is called from the MDD
			whenever INTR_MODEM is returned by SerGetInterruptType.

	@rdesc None
*************************************************************************/
static
VOID SerModemIntr( PVOID pHead )               // Hardware Head
{
	PSER_INFO	pHWHead = (PSER_INFO)pHead;
	DWORD		dwModemStatus;

	dwModemStatus = pHWHead->ModemStatus;

	DEBUGMSG(1, (TEXT("+++SerModemIntr\r\n")));  
    
	SC2440_USB_DoEndpoint0(pHWHead, &dwModemStatus);

	if ( pHWHead->dConfIdx ) 
	{
		if ( pHWHead->cOpenCount) 
		{
			// If open, notify app of any control line changes.
			DEBUGMSG(1, (TEXT("Modem Status %2.2X <> %2.2X\r\n"),
					pHWHead->ModemStatus, dwModemStatus));

			if( (pHWHead->ModemStatus & MS_DSR_ON) != (dwModemStatus & MS_DSR_ON) )
				EvaluateEventFlag(pHWHead->pMddHead, EV_DSR);

			if( (pHWHead->ModemStatus & MS_CTS_ON) != (dwModemStatus & MS_CTS_ON) )
				EvaluateEventFlag(pHWHead->pMddHead, EV_CTS);

			if( (pHWHead->ModemStatus & MS_RLSD_ON) != (dwModemStatus & MS_RLSD_ON) )
				EvaluateEventFlag(pHWHead->pMddHead, EV_RLSD);
	
		} else {
			// If RLSD transitioned to active, we need to generate event.
			if( (dwModemStatus & MS_RLSD_ON) && !(pHWHead->ModemStatus & MS_RLSD_ON) ) 
			{

				if ( IsAPIReady(SH_WMGR) ) 
				{
						CeEventHasOccurred (NOTIFICATION_EVENT_RS232_DETECTED, NULL);
				}
				DEBUGMSG (0, (TEXT("Indicated RS232 Cable Event\r\n")));
			}
		}
  	}
  	pHWHead->ModemStatus = dwModemStatus;

	DEBUGMSG(1, (TEXT("---SerModemIntr\r\n")));   
}

/*************************************************************************
	@doc OEM
	@func ULONG | SerLineIntr | This routine is called from the MDD
			whenever INTR_LINE is returned by SerGetInterruptType.

	@rdesc None
*************************************************************************/
static
VOID SerLineIntr( PVOID pHead )               // Hardware Head
{
	PSER_INFO pHWHead = (PSER_INFO)pHead;

	DEBUGMSG(1, (TEXT("SerLineIntr\r\n")));

 	// USB serial doesn't have a concept of anything like the LSR.
	// But since a bus reset is what we sort of equate to DCD, I use
	// this routine to handle USB reset conditions.

	// Note that by the time we get here with an SC2440_USB , we are actually
	//plugged back into the bus. I added some polling code to try and detect
	// detaches sooner
	SC2440_USB_LineIntHandler(pHWHead);  // Re-initialize the hardware    
}

/*************************************************************************
	@doc OEM
	@func ULONG | SerGetRxBufferSize | This function returns
			the size of the hardware buffer passed to the interrupt
			initialize function.  It would be used only for devices
			which share a buffer between the MDD/PDD and an ISR.

	@rdesc This routine always returns 0 for 16550 UARTS.
**************************************************************************/
static
ULONG SerGetRxBufferSize( PVOID pHead )
{
	DEBUGMSG(1, (TEXT("SerGetRxBufferSize\r\n")));  
	return 10280;
}

/*************************************************************************
	@doc OEM
	@func ULONG | SerTXIntr | This routine is called from the new MDD
			whenever INTR_TX is returned by SerGetInterruptType.  It is 
			responsible for loading up the TX FIFO with next block of data.

	@rdesc None
**************************************************************************/
static
VOID SerTxIntr(	PVOID pHead,            // Hardware Head
				PUCHAR pTxBuffer,       // @parm Pointer to receive buffer
				ULONG *pBuffLen	)		// @parm In = max bytes to transmit,
							            //       out = bytes transmitted
{
	PSER_INFO pHWHead = (PSER_INFO)pHead;

	DEBUGMSG(1, (TEXT("SerTxIntr+\r\n")));

	pHWHead->CommErrors &= ~CE_TXFULL;

	SC2440_USB_TxIntHandler(pHWHead, pTxBuffer, pBuffLen);

	DEBUGMSG(1, (TEXT("SerTxIntr-, %d bytes\r\n"), *pBuffLen));  
}

/*************************************************************************
	@doc OEM
	@func BOOL | SerPowerOff | Called by driver to turn off power to 
			serial port.
			Not exported to users, only to driver.
 
	@rdesc This routine returns a status.
 ************************************************************************/
static
BOOL SerPowerOff( PVOID   pHead )      // @parm	PVOID returned by SerInit.
{
	PSER_INFO pHWHead = (PSER_INFO)pHead;
	pHWHead->cOpenCount = 0;

	//RETAILMSG(1,(TEXT("USB : SerPowerOff \r\n")));

/*	
	if ( pHWHead->cOpenCount )
	{
		EvaluateEventFlag( pHWHead->pMddHead, EV_RXFLAG );
	}
	
	return (TRUE);
*/
    return HW_PowerOff(pHead);
	
}

/*************************************************************************
	@doc OEM
	@func BOOL | SerPowerOn | 
			Called by driver to turn on power to serial port.
			Not exported to users, only to driver.
 
	@rdesc This routine returns a status.
 ************************************************************************/
static
BOOL SerPowerOn( PVOID   pHead )      // @parm	PVOID returned by SerInit.
{
	PSER_INFO pHWHead = (PSER_INFO)pHead;

	//RETAILMSG(1,(TEXT("USB : SerPowerOn \r\n")));

    return HW_PowerOn(pHead);
}

/*************************************************************************
	@doc OEM
	@func void | SerClearDtr | This routine clears DTR.

	@rdesc None.
**************************************************************************/
static
VOID SerClearDTR( PVOID   pHead )		// @parm PVOID returned by HWinit.
{
	DEBUGMSG(1, (TEXT("SerClearDTR, 0x%X\r\n"), pHead));
	
	// We don't support DTR emulation.
}

/*************************************************************************
	@doc OEM
	@func VOID | SerSetDTR | This routine sets DTR.
 
	@rdesc None.
**************************************************************************/
static
VOID SerSetDTR( PVOID   pHead )			// @parm PVOID returned by HWinit.
{    
	DEBUGMSG(1, (TEXT("SerSetDTR, 0x%X\r\n"), pHead));

	// We don't support DTR emulation.
}

/*************************************************************************
	@doc OEM
	@func VOID | SerClearRTS | This routine clears RTS.
 
	@rdesc None.
**************************************************************************/
static
VOID SerClearRTS( PVOID   pHead )		// @parm PVOID returned by HWinit.
{
	DEBUGMSG(1, (TEXT("SerClearRTS, 0x%X\r\n"), pHead));

	// We don't support RTS emulation.
}

/*************************************************************************
	@doc OEM
	@func VOID | SerSetRTS | This routine sets RTS.

	@rdesc None.
**************************************************************************/
static
VOID SerSetRTS( PVOID   pHead )			// @parm PVOID returned by HWinit.
{
	DEBUGMSG(1, (TEXT("SerSetRTS, 0x%X\r\n"), pHead));

	// We don't support RTS emulation.
}

/*************************************************************************
	@doc OEM
	@func BOOL | SerEnableIR | This routine enables ir.
			Not exported to users, only to driver.
	
	@rdesc Returns TRUE if successful, FALSEotherwise.
**************************************************************************/
static
BOOL SerEnableIR( PVOID   pHead,		// @parm PVOID returned by Serinit.
				  ULONG   BaudRate )	// @parm PVOID returned by HWinit.
{
	PSER_INFO pHWHead = (PSER_INFO)pHead;

	DEBUGMSG(1, (TEXT("SerEnableIR, 0x%X\r\n"), pHead));

	// We don't support an IR mode, so fail.
	return (FALSE);
}

/*************************************************************************
	@doc OEM
	@func BOOL | SerDisableIR | This routine disable the ir.
			Not exported to users, only to driver.
 
	@rdesc Returns TRUE if successful, FALSEotherwise.
**************************************************************************/
static
BOOL SerDisableIR( PVOID   pHead )	// @parm PVOID returned by Serinit. 
{
	PSER_INFO pHWHead = (PSER_INFO)pHead;

	DEBUGMSG(1, (TEXT("SerDisableIR, 0x%X\r\n"), pHead));

	// We don't support an IR mode.  But don't fail, in case
	// someone calls this redundantly to ensure that we are
	// in wired mode, which is what we support.
	return (TRUE);
}

/*************************************************************************
	@doc OEM
	@func VOID | SerClearBreak | This routine clears break.
 
	@rdesc None.
*************************************************************************/ 
static
VOID SerClearBreak( PVOID   pHead )		// @parm PVOID returned by HWinit.
{
	DEBUGMSG(1, (TEXT("SerClearBreak, 0x%X\r\n"), pHead));

	// We don't have a concept of break over USB serial
}

/*************************************************************************
	@doc OEM
	@func VOID | SerSetBreak | This routine sets break.

	@rdesc None.
*************************************************************************/
static
VOID SerSetBreak( PVOID   pHead )		// @parm PVOID returned by HWinit.
{
	DEBUGMSG(1, (TEXT("SerSetBreak, 0x%X\r\n"), pHead));

	// We don't have a concept of break over USB serial
}

/*************************************************************************
	@doc OEM
	@func	BOOL | SerXmitComChar | Transmit a char immediately
 
	@rdesc	TRUE if succesful
*************************************************************************/
static
BOOL SerXmitComChar( PVOID   pHead,     // @parm PVOID returned by HWInit.
		     UCHAR   ComChar )  // @parm Character to transmit. 
{
	PSER_INFO pHWHead = (PSER_INFO)pHead;
    
	DEBUGMSG(1, (TEXT("+SerXmitComChar 0x%X\r\n"), pHead));

	// Get critical section, then transmit when buffer empties
	DEBUGMSG(1, (TEXT("XmitComChar wait for CritSec %x.\r\n"),
					&(pHWHead->TransmitCritSec)));

	EnterCriticalSection(&(pHWHead->TransmitCritSec));

	DEBUGMSG(1, (TEXT("XmitComChar got CritSec %x.\r\n"),
					&(pHWHead->TransmitCritSec)));
	try
    {

		// TODO - We need to wait for the current transmit to finish and
		// then sneak this data in ahead of whatever else is queued.
#ifdef TODO
		while( TRUE )  // We know THR will eventually empty
		{
			// Write the character if we can
			ReadLSR( pHWHead );
			if( pHWHead->LSR & SERIAL_LSR_THRE ) 
			{
				OUTB(pHWHead, pData, ComChar);
				DEBUGMSG(1, (TEXT("XmitComChar wrote x%X\r\n"), ComChar));
				break;
			}

			// If we couldn't write the data yet, then wait for a
			// TXINTR to come in and try it again.
            
			// Enable xmit intr.
			OUTB(pHWHead, pIER, IER_NORMAL_INTS | SERIAL_IER_THR);
			// Wait until the txintr has signalled.
			DEBUGMSG(1, (TEXT("XmitComChar WaitIntr x%X\r\n"),
							pHWHead->FlushDone));
			WaitForSingleObject(pHWHead->FlushDone, (ULONG)1000);
		}
#else
		DEBUGMSG (0, (TEXT("!!! SerXmitComChar 0x%X not implemented\r\n"),
		 				 pHead));
#endif        
	}
	except (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ?
		EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) 
	{
		// Just exit
	}
    
	LeaveCriticalSection(&(pHWHead->TransmitCritSec));

	DEBUGMSG(1, (TEXT("XmitComChar released CritSec %x.\r\n"),
					&(pHWHead->TransmitCritSec)));
    
	DEBUGMSG(1, (TEXT("-SerXmitComChar 0x%X\r\n"), pHead));
    
	return TRUE;
}
        

/*************************************************************************
	@doc OEM
	@func	ULONG | SerGetStatus | This structure is called by the MDD
				to retrieve the contents of a COMSTAT structure.

	@rdesc	The return is a ULONG, representing success (0) or failure (-1).
*************************************************************************/
static
ULONG SerGetStatus( PVOID		pHead,	// @parm PVOID returned by HWInit.
					LPCOMSTAT	lpStat )// Pointer to LPCOMMSTAT to hold status.
{
	PSER_INFO	pHWHead	 = (PSER_INFO)pHead;
	ULONG		RetVal	 = pHWHead->CommErrors;
    
	DEBUGMSG(1, (TEXT("++SerGetStatus 0x%X\r\n"), pHead));

	pHWHead->CommErrors = 0; // Clear old errors each time

	// We don't emulate any of this, so always return a fixed result.
	if (lpStat) 
	{
		// TODO - Make sure we return reasonable results here.
		pHWHead->Status.fCtsHold = 0;
		pHWHead->Status.fDsrHold = 0;
		pHWHead->Status.cbInQue  = 0;
		pHWHead->Status.cbOutQue = 0;
	}
	else
		RetVal = (ULONG)-1;

	DEBUGMSG(1, (TEXT("--SerGetStatus 0x%X\r\n"), pHead));

	return RetVal;
}

/*************************************************************************
	@doc OEM
	@func ULONG | SerReset | Perform any operations associated
			with a device reset

	@rdesc	None.
*************************************************************************/
static
VOID SerReset( PVOID   pHead )	// @parm PVOID returned by HWInit.
{
	PSER_INFO pHWHead = (PSER_INFO)pHead;

	DEBUGMSG(1, (TEXT("++SerReset 0x%X\r\n"), pHead));

	memset(&pHWHead->Status, 0, sizeof(COMSTAT));

	// TODO - Is there anything special we need to do here.
    
	DEBUGMSG(1, (TEXT("--SerReset 0x%X\r\n"), pHead));
}

/*************************************************************************
	@doc OEM
	@func	VOID | SerGetModemStatus | Retrieves modem status.

	@rdesc	None.
*************************************************************************/
static
VOID SerGetModemStatus( PVOID   pHead,	       // @parm PVOID returned by HWInit.
						PULONG  pModemStatus ) // @parm PULONG passed in by user.
{
	PSER_INFO pHWHead = (PSER_INFO)pHead;
    
	DEBUGMSG(1, (TEXT("++SerGetModemStatus 0x%X\r\n"), pHead));

	*pModemStatus = pHWHead->ModemStatus;

	DEBUGMSG (0, (TEXT("--SerGetModemStatus 0x%X (stat x%X) \r\n"),
						pHead,
						*pModemStatus));
	return;
}

/*************************************************************************
	@doc OEM
	@func	VOID | SerGetCommProperties | Retrieves Comm Properties.
 
	@rdesc	None.
*************************************************************************/
static
VOID SerGetCommProperties( 
			PVOID   pHead,		// @parm PVOID returned by SerInit. 
			LPCOMMPROP  pCommProp )	// @parm Pointer to receive COMMPROP structure. 
{
	PSER_INFO pHWHead = (PSER_INFO)pHead;

	DEBUGMSG(1, (TEXT("SerGetCommProperties 0x%X\r\n"), pHead));

	*pCommProp = pHWHead->CommProp;
	return;
}

/*************************************************************************
	@doc OEM
	@func	VOID | SerPurgeComm | Purge RX and/or TX
 
	@rdesc	None.
*************************************************************************/
static
VOID SerPurgeComm( PVOID   pHead,		 // @parm PVOID returned by HWInit.
				   DWORD   fdwAction )	 // @parm Action to take. 
{
	PSER_INFO pHWHead = (PSER_INFO)pHead;
    
	DEBUGMSG(1, (TEXT("++SerPurgeComm 0x%X\r\n"), pHead));

	EnterCriticalSection(&(pHWHead->HwRegCritSec));

	if ( (fdwAction & PURGE_TXCLEAR) || (fdwAction & PURGE_TXABORT) ) 
	{
		// Abort any pending TX data.  The MDD takes care of aborting
		// data pended in the driver.  All I can do to stop an IN that
		// has already been queued is to Un-Arm this endpoint, causing
		// subsequent INs to get naked.  If the IN transfer has started, 
		// its not clear I can stop it in progress.
		// In addition to NAKing host, clearing ARM tells TxIntr that he
		// can use the buffer for the next write that comes down.

#if TODO
		ucECR = SC2440_USB_Read(pHWHead, EP1AControl);
		ucECR &= 0xFE;   // Clear the ARM bit.

		SC2440_USB_Write(pHWHead, EP1AControl, ucECR );
#endif
	}

	if ( (fdwAction & PURGE_RXCLEAR) || (fdwAction & PURGE_RXABORT) ) {
#if TODO
		// Abort any pending RX data.
		SC2440_USB_Write(pHWHead, IntStatus, 0x04 );   // Clear the EP2 interrupt
		SC2440_USB_Write(pHWHead, EP2AControl, 0x03);  // Reenable the endpoint
#endif
	}
    
	LeaveCriticalSection(&(pHWHead->HwRegCritSec));

	DEBUGMSG(1,
			(TEXT("--SerPurgeComm 0x%X\r\n"), pHead));
	return;
}

/************************************************************************
	@doc OEM
	@func	BOOL | SerSetDCB | Sets new values for DCB.  This
		routine gets a DCB from the MDD.  It must then compare
		this to the current DCB, and if any fields have changed take
		appropriate action.
 
	@rdesc	BOOL
************************************************************************/
static
BOOL SerSetDCB( PVOID   pHead,	    // @parm	PVOID returned by HWInit.
		LPDCB   lpDCB )     // @parm    Pointer to DCB structure
{
	PSER_INFO pHWHead = (PSER_INFO)pHead;
	BOOL bRet;
    
	DEBUGMSG(1, (TEXT("++SerSetDCB 0x%X\r\n"), pHead));

	bRet = TRUE;

	// If the device is open, scan for changes and do whatever
	// is needed for the changed fields.  if the device isn't
	// open yet, just save the DCB for later use by the open.
	if( pHWHead->OpenCount ) 
	{
#ifdef TODO
		// We don't really support any of this.  Ignore for now
		// Note, fparity just says whether we should check
		// receive parity.  And the 16550 won't let us NOT
		// check parity if we generate it.  So this field
		// has no effect on the hardware.
    
		if ( lpDCB->BaudRate != pHWHead->dcb.BaudRate ) 
		{
			bRet = SerSetBaudRate( pHWHead, lpDCB->BaudRate );
		}
    
		if ( bRet && (lpDCB->ByteSize != pHWHead->dcb.ByteSize )) 
		{
			bRet = SerSetByteSize( pHWHead, lpDCB->ByteSize );
		}
    
		if ( bRet && (lpDCB->Parity != pHWHead->dcb.Parity )) 
		{
			bRet = SerSetParity( pHWHead, lpDCB->Parity );
		}
    
		if ( bRet && (lpDCB->StopBits != pHWHead->dcb.StopBits )) 
		{
			bRet = SerSetStopBits( pHWHead, lpDCB->StopBits );
		}

		// Don't worry about fOutxCtsFlow.  It is a flag which
		// will be examined every time we load the TX buffer.
		// No special action required here.
#endif         
	}
    
	if (bRet) 
	{
		// Now that we have done the right thing, store this DCB
		pHWHead->dcb = *lpDCB;
	}

	DEBUGMSG(1, (TEXT("--SerSetDCB 0x%X\r\n"), pHead));

	return bRet;
}

/*************************************************************************
	@doc OEM
	@func	BOOL | SerSetCommTimeouts | Sets new values for the
		CommTimeouts structure. routine gets a DCB from the MDD.  It
		must then compare this to the current DCB, and if any fields
		have changed take appropriate action.

	@rdesc	ULONG
*************************************************************************/
static
ULONG SerSetCommTimeouts( 
	PVOID   pHead,			  // @parm	PVOID returned by HWInit.
	LPCOMMTIMEOUTS   lpCommTimeouts ) // @parm Pointer to CommTimeout structure
{
	PSER_INFO pHWHead = (PSER_INFO)pHead;
	ULONG retval      = 0;
    
	DEBUGMSG(1, (TEXT("++SerSetCommTimeout 0x%X\r\n"), pHead));

	// OK, first check for any changes and act upon them
	if( lpCommTimeouts->WriteTotalTimeoutMultiplier !=
		pHWHead->CommTimeouts.WriteTotalTimeoutMultiplier )
	{

	}
    
	// Now that we have done the right thing, store this DCB
	pHWHead->CommTimeouts = *lpCommTimeouts;

	DEBUGMSG(1, (TEXT("--SerSetCommTimeout 0x%X\r\n"), pHead));

	return retval;
}

/*************************************************************************
    @doc OEM
    @func    BOOL | SerIoctl | Device IO control routine.  
    @parm DWORD | dwOpenData | value returned from COM_Open call
    @parm DWORD | dwCode | io control code to be performed
    @parm PBYTE | pBufIn | input data to the device
    @parm DWORD | dwLenIn | number of bytes being passed in
    @parm PBYTE | pBufOut | output data from the device
    @parm DWORD | dwLenOut |maximum number of bytes to receive from device
    @parm PDWORD | pdwActualOut | actual number of bytes received from device

    @rdesc        Returns TRUE for success, FALSE for failure

    @remark  The MDD will pass any unrecognized IOCTLs through to this function.
*************************************************************************/
static
BOOL SerIoctl(PVOID pHead, DWORD dwCode,PBYTE pBufIn,DWORD dwLenIn,
         PBYTE pBufOut,DWORD dwLenOut,PDWORD pdwActualOut)
{
	PSER_INFO pHWHead = (PSER_INFO)pHead;
 	BOOL RetVal = TRUE;

	DEBUGMSG(1, (TEXT("++SerIoctl 0x%X\r\n"), pHead));

	switch (dwCode) 
	{
		// Currently, no defined IOCTLs
		default:
			RetVal = FALSE;

			DEBUGMSG(1, (TEXT(" Unsupported ioctl 0x%X\r\n"), dwCode));
			break;            
	}

	DEBUGMSG(1, (TEXT("--SerIoctl 0x%X\r\n"), pHead));

	return(RetVal);
}

const
HW_VTBL IoVTbl = {
    SerInit,
    SerPostInit,
    SerDeinit,
    SerOpen,
    SerClose,
    SerGetInterruptType,
    SerRxIntr,
    SerTxIntr,
    SerModemIntr,
    SerLineIntr,
    SerGetRxBufferSize,
    SerPowerOff,
    SerPowerOn,
    SerClearDTR,
    SerSetDTR,
    SerClearRTS,
    SerSetRTS,
    SerEnableIR,
    SerDisableIR,
    SerClearBreak,
    SerSetBreak,
    SerXmitComChar,
    SerGetStatus,
    SerReset,
    SerGetModemStatus,
    SerGetCommProperties,
    SerPurgeComm,
    SerSetDCB,
    SerSetCommTimeouts,
    SerIoctl};

extern const HW_VTBL SerCardIoVTbl;

PHWOBJ
GetSerialObject( DWORD DeviceArrayIndex )
{
	PHWOBJ pSerObj;

	// 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;      // We take care of our own IST
	pSerObj->dwIntID = SYSINTR_USBD;   // SysIntr 
	pSerObj->pFuncTbl = (HW_VTBL *) &IoVTbl; // Return pointer to functions

	// Now return this structure to the MDD.
	return (pSerObj);
}

