/*++
 *
 *  WOW v1.0
 *
 *  Copyright (c) 1991, Microsoft Corporation
 *
 *  WUCOMM.C
 *  WOW32 16-bit User API support
 *
 *  History:
 *  Created   07-Mar-1991 by Jeff Parsons (jeffpar)
 *  made real Dec-1992 by Craig Jones (v-cjones)
 *  made work Apr-1993 by Craig Jones (v-cjones)
 *  made fast Jun-1993 by Craig Jones (v-cjones)
--*/

#include "precomp.h"
#pragma hdrstop
#include <ntddser.h>

MODNAME(wucomm.c);

/* Define the table for mapping Win3.1 idComDev's to 32-bit comm HFILE's. */
/* This table is indexed by the 16-bit idComDev that we return to the app */
/* which is assigned based on the device name (see wucomm.h).  You can    */
/* use GETPWOWPTR(idComDev) to get the ptr to the corresponding WOWPort   */
/* struct from PortTab[].                                                 */

/* This table must contain NUMPORTS (def'd in wucomm.h) entries */
PORTTAB PortTab[] = { {"COM1", NULL},
                      {"COM2", NULL},
                      {"COM3", NULL},
                      {"COM4", NULL},
                      {"COM5", NULL},
                      {"COM6", NULL},
                      {"COM7", NULL},
                      {"COM8", NULL},
                      {"COM9", NULL},
                      {"LPT1", NULL},
                      {"LPT2", NULL},
                      {"LPT3", NULL}
                    };


/* function prototypes for local support functions */
DWORD    Baud16toBaud32(UINT BaudRate);
WORD     Baud32toBaud16(DWORD BaudRate);
void     DCB16toDCB32(PWOWPORT pWOWPort, LPDCB lpdcb32, PDCB16 pdcb16);
void     DCB32toDCB16(PDCB16 pdcb16,  LPDCB lpdcb32,  UINT idComDev,  BOOL fChEvt);
BOOL     DeletePortTabEntry(PWOWPORT pWOWPort);
ULONG    WOWCommWriterThread(LPVOID pWOWPortStruct);
USHORT   EnqueueCommWrite(PWOWPORT pwp, PUCHAR pch, USHORT cb);
UINT     GetModePortTabIndex(PSZ pszModeStr);
BOOL     GetPortName(LPSTR pszMode, LPSTR pszPort);
UINT     GetStrPortTabIndex(PSZ szPort);
BOOL     InitDCB32(LPDCB pdcb32, LPSTR pszModeStr);
VOID     InitDEB16(PCOMDEB16 pComDEB16,  UINT iTab,  WORD QInSize,  WORD QOutSize);
PSZ      StripPortName(PSZ psz);
PSZ      GetPortStringToken(PSZ pszSrc, PSZ pszToken);
BOOL     MSRWait(PWOWPORT pwp);
BOOL     IsQLinkGold(WORD wTDB);

/* prototypes for Modem interrupt emulation thread support */
VOID  WOWModemIntThread(PWOWPORT pWOWPortStruct);
BOOL  WOWStartModemIntThread(PWOWPORT pWOWPort);
DWORD WOWGetCommError(PWOWPORT pWOWPort);



// Win3.1 returns:
//    0 on success OR LPT.
//    -1 on ANY error.
ULONG FASTCALL WU32BuildCommDCB(PVDMFRAME pFrame)
{
    ULONG    ul = (ULONG)-1;
    UINT     len, iTab;
    PSZ      psz1;
    PDCB16   pdcb16;
    DCB      dcb32;
    register PBUILDCOMMDCB16 parg16;

    GETARGPTR(pFrame, sizeof(BUILDCOMMDCB16), parg16);
    GETPSZPTR(parg16->f1, psz1);

    // if valid device name...
    if((INT)(iTab = GetModePortTabIndex(psz1)) >= 0) {

        // Initialize a Win3.1 compatible 32-bit DCB
        if(InitDCB32(&dcb32, psz1)) {

            GETMISCPTR(parg16->f2, pdcb16);

            if(pdcb16) {
                // copy the psz1 fields to the 16-bit struct
                iTab = (VALIDCOM(iTab) ? iTab : TABIDTOLPT(iTab));
                DCB32toDCB16(pdcb16, &dcb32, iTab, FALSE);

                // set timeouts for COMx ports only
                if(VALIDCOM(iTab)) {

                    // 'P' is the only "retry" option supported in Win3.1
                    len = strlen(psz1) - 1;
                    while(psz1[len] != ' ') {  // delete trailing spaces
                        len--;
                    }
                    if((psz1[len] == 'P') || (psz1[len] == 'p')) {
                        pdcb16->RlsTimeout = INFINITE_TIMEOUT;
                        pdcb16->CtsTimeout = INFINITE_TIMEOUT;
                        pdcb16->DsrTimeout = INFINITE_TIMEOUT;
                    }
                }

                FLUSHVDMPTR(parg16->f2, sizeof(DCB16), pdcb16);
                FREEMISCPTR(pdcb16);

                ul = 0; // Win3.1 returns 0 if success
            }
        }
        FREEPSZPTR(psz1);
    }

#ifdef DEBUG
    if(!(ul==0)) {
        LOGDEBUG(0,("WOW::WU32BuildCommDCB: failed\n"));
    }
#endif

    FREEARGPTR(parg16);
    RETURN(ul);
}




// Win3.1 returns:
//    Error word on success OR LPTx.
//    0x8000 on bad idComDev.
ULONG FASTCALL WU32ClearCommBreak(PVDMFRAME pFrame)
{
    ULONG    ul = 0x00008000;
    UINT     idComDev;
    PWOWPORT pWOWPort;
    register PCLEARCOMMBREAK16 parg16;

    GETARGPTR(pFrame, sizeof(CLEARCOMMBREAK16), parg16);

    idComDev = UINT32(parg16->f1);
    if (pWOWPort = GETPWOWPTR(idComDev)) {

        if (VALIDCOM(idComDev)) {
            if(!ClearCommBreak(pWOWPort->h32)) {
                WOWGetCommError(pWOWPort);
            }
        }
        ul = pWOWPort->dwErrCode;
    }

#ifdef DEBUG
    if(!(ul!=0x00008000)) {
        LOGDEBUG(0,("WOW::WU32ClearCommBreak: failed\n"));
    }
#endif

    FREEARGPTR(parg16);
    RETURN(ul);
}




// Win3.1 returns:
//    0 if success OR if LPTx.
//    -1 for bad idComDev OR port not open.
//    -2 for Timeout error.
// We pass back (as a 2nd parameter) the DWORD obtained from the call to
// GlobalDosAlloc() in IOpenComm() in user.exe.  (WOWModemIntThread() support)
ULONG FASTCALL WU32CloseComm(PVDMFRAME pFrame)
{
    ULONG    ul = (ULONG)-1;
    UINT     idComDev;
    PDWORD16 lpdwDEB16;
    PWOWPORT pWOWPort = NULL;
    register PCLOSECOMM16 parg16;

    GETARGPTR(pFrame, sizeof(CLOSECOMM16), parg16);

    idComDev = UINT32(parg16->f1);
    if (pWOWPort = GETPWOWPTR(idComDev)) {

        // pass back the 16:16 ptr for the WOWModemIntThread() support
        GETMISCPTR(parg16->f2, lpdwDEB16);
        if (lpdwDEB16) {
            *lpdwDEB16 = pWOWPort->dwComDEB16;
            FLUSHVDMPTR(parg16->f2, sizeof(DWORD), lpdwDEB16);
            FREEMISCPTR(lpdwDEB16);
        }

        // clean up the PortTab[] entry
        if (DeletePortTabEntry(pWOWPort)) {
            ul = (ULONG)-2; // return Win3.1 timeOut error
        }
        else {
            ul = 0;
        }
    }
    else {
        LOGDEBUG (0, ("WOW::WU32CloseComm: Not a valid COM or LPT\n"));
    }

#ifdef DEBUG
    if(!(ul==0)) {
        LOGDEBUG(0,("WOW::WU32CloseComm: failed\n"));
    }
#endif

    FREEARGPTR(parg16);
    RETURN(ul);
}




// Win3.1 returns:
//    TRUE on success.
//    FALSE if error OR if EnableCommNotification() not supported.
//          User16 validation layer returns 0 for bad hwnd.
ULONG FASTCALL WU32EnableCommNotification(PVDMFRAME pFrame)
{
    ULONG     ul = (ULONG)FALSE;
    UINT      idComDev;
    WORD      cbQue;
    BOOL      fOK = TRUE;
    PWOWPORT  pWOWPort;
    PCOMDEB16 lpComDEB16;
    register  PENABLECOMMNOTIFICATION16 parg16;

    GETARGPTR(pFrame, sizeof(ENABLECOMMNOTIFICATION16), parg16);

    idComDev = UINT32(parg16->f1);
    if ((VALIDCOM(idComDev)) && (pWOWPort = PortTab[idComDev].pWOWPort)) {

        lpComDEB16 = pWOWPort->lpComDEB16;

        // if they are trying to disable notifcation (HWND == NULL)
        if(WORD32(parg16->f2) == 0) {
            lpComDEB16->NotifyHandle = 0;
            lpComDEB16->NotifyFlags  = CN_TRANSMITHI;
            lpComDEB16->RecvTrigger  = (WORD)-1;
            lpComDEB16->SendTrigger  = 0;
            ul = (ULONG)TRUE;
        }

        // Validate non-null hwnd's since hwnd validation is disabled in
        // user16 validation layer
        else if(!IsWindow(HWND32(parg16->f2))) {
            ul = (ULONG)FALSE;
        }

        // else set up the notification mechanisms
        else {

            // if the Modem interrupt thread hasn't started yet -- go start it
            if(pWOWPort->hMiThread == NULL) {

                if(!WOWStartModemIntThread(pWOWPort)) {
                    fOK = FALSE;
                }
            }

            // update the DEB to reflect notification
            if(fOK) {

                lpComDEB16->NotifyHandle = WORD32(parg16->f2);
                lpComDEB16->NotifyFlags  = CN_TRANSMITHI | CN_NOTIFYHI;

                // set trigger values the same way Win3.1 does
                cbQue = WORD32(parg16->f3);
                if((cbQue < lpComDEB16->QInSize) || ((SHORT)cbQue == -1)) {
                    lpComDEB16->RecvTrigger = cbQue;
                }
                else {
                    lpComDEB16->RecvTrigger = lpComDEB16->QInSize - 10;
                }
                cbQue = WORD32(parg16->f4);
                if((cbQue < lpComDEB16->QOutSize) || ((SHORT)cbQue == -1)) {
                    lpComDEB16->SendTrigger = cbQue;
                }
                else {
                    lpComDEB16->SendTrigger = lpComDEB16->QOutSize - 10;
                }

                ul = (ULONG)TRUE;
            }
        }
    }
    // else there is no notification for LPT in Win3.1
    else {
        ul = (ULONG)FALSE;
    }

#ifdef DEBUG
    if(!(ul==1)) {
        LOGDEBUG(0,("WOW::WU32EnableCommNotification: failed\n"));
    }
#endif

    FREEARGPTR(parg16);
    RETURN(ul);
}




// Win3.1 returns:
//    The value from the specified function.
//    The error word for: the line & signal state functions,
//    function not implemented, OR LPTx where function != (RESETDEV||GETMAXLPT).
//    0x8000 for bad idComDev.
ULONG FASTCALL WU32EscapeCommFunction(PVDMFRAME pFrame)
{
    ULONG    ul = 0x00008000;
    UINT     idComDev;
    UINT     nFunction;
    WORD     IRQ;
    VPVOID   vpBiosData;
    PWORD16  pwBiosData;
    PWOWPORT pWOWPort;
    register PESCAPECOMMFUNCTION16 parg16;

    GETARGPTR(pFrame, sizeof(ESCAPECOMMFUNCTION16), parg16);

    // this construct is set up this way because Win3.1 will allow GETMAXCOM
    // & GETMAXLPT to succeed as long as the idComDev is in the valid range.
    // (ie: the app doesn't have to call OpenComm() first to set up the PortTab)
    // for RESETDEV we tell them that we reset the printer. (we're such liars!)

    nFunction = WORD32(parg16->f2);
    idComDev  = UINT32(parg16->f1);
    if (VALIDCOM(idComDev)) {

        if (nFunction == GETMAXCOM) {
            ul = NUMCOMS-1;
        } else if (nFunction == GETBASEIRQ || nFunction == GETBASEIRQ+1) {
            ul = 0xFFFFFFFF;
            if (idComDev < COM5) {
                vpBiosData = (VPVOID) (RM_BIOS_DATA + (idComDev * sizeof(WORD)));
                if (pwBiosData = (PWORD16)GetRModeVDMPointer(vpBiosData)) {
                    if (idComDev == COM1 || idComDev == COM3) {
                        IRQ = IRQ4;
                    } else {
                        IRQ = IRQ3;
                    }
                    ul = MAKELONG((WORD)(*pwBiosData), IRQ);
                    FREEVDMPTR(pwBiosData);
                }
            }
        } else {
            // for the other functions they must have called OpenComm()
            if (pWOWPort = PortTab[idComDev].pWOWPort) {

                switch(nFunction) {

                // line & signal state functions
                case    SETXOFF:
                case    SETXON:
                case    SETRTS:
                case    CLRRTS:
                case    SETDTR:
                case    CLRDTR:
                    if(!EscapeCommFunction(pWOWPort->h32, nFunction)) {
                        WOWGetCommError(pWOWPort);
                    }
                    ul = pWOWPort->dwErrCode;
                    break;

                // 0:
                case         0:
                    ul = 0;  // like WFW
                    break;

                // any other value...
                default:

                    // non-zero is error: use dwErrcode if there is one
                    if(pWOWPort->dwErrCode)
                        ul = pWOWPort->dwErrCode;

                    // else use what WFW seems inclined to return
                    else
                        ul = CE_OVERRUN | CE_RXPARITY;
                    break;
                }
            }
        }
    } else if (VALIDLPT(idComDev)) {
        if(nFunction == RESETDEV) {
            ul = 0;  // no error (ie. "just tell them we did it" - TonyE)
        }
        else if(nFunction == GETMAXLPT) {
            ul = LPTLAST;
        }
        else if (pWOWPort = PortTab[GETLPTID(idComDev)].pWOWPort) {
            ul = pWOWPort->dwErrCode;
        }
        else {
            ul = 0;
        }
    }

    FREEARGPTR(parg16);

    RETURN(ul);
}




// Win3.1 returns:
//    0 on success.
//    0x8000 if bad idComDev.
//    Error word on error or LPTx.
ULONG FASTCALL WU32FlushComm(PVDMFRAME pFrame)
{
    ULONG    ul = 0x00008000;
    UINT     idComDev;
    DWORD    dwAction;
    PWOWPORT pWOWPort;
    register PFLUSHCOMM16 parg16;

    GETARGPTR(pFrame, sizeof(FLUSHCOMM16), parg16);

    idComDev = UINT32(parg16->f1);
    if (pWOWPort = GETPWOWPTR(idComDev)) {

        // is a COMx?
        if (VALIDCOM(idComDev)) {

            // if flush transmit buffer specified
            dwAction = PURGE_RXCLEAR;
            if(parg16->f2 == 0) {
                dwAction = PURGE_TXCLEAR | PURGE_TXABORT;

                //
                // Flush the local writers buffer
                //

                EnterCriticalSection(&pWOWPort->csWrite);
                pWOWPort->pchWriteHead =
                pWOWPort->pchWriteTail = pWOWPort->pchWriteBuf;
                pWOWPort->cbWriteFree  = pWOWPort->cbWriteBuf - 1;
                pWOWPort->cbWritePending = 0;
                LeaveCriticalSection(&pWOWPort->csWrite);
            }


            if(PurgeComm(pWOWPort->h32, dwAction)) {

                if(dwAction == PURGE_RXCLEAR) {
                    pWOWPort->fUnGet = FALSE;
                }

                ul = 0;  // Win3.1 returns 0 on success
            }
            else {
                WOWGetCommError(pWOWPort);
                ul = pWOWPort->dwErrCode;
            }
        }
        // else just return current error code for LPTx
        else {
            ul = pWOWPort->dwErrCode;
        }
    }

#ifdef DEBUG
    if(!(ul==0)) {
        LOGDEBUG(0,("WOW::WU32FlushComm: failed\n"));
    }
#endif

    FREEARGPTR(parg16);
    RETURN(ul);
}




// Win3.1 returns:
//    0x8000 for bad idComDev.
//    The error word for all other cases.
ULONG FASTCALL WU32GetCommError(PVDMFRAME pFrame)
{
    ULONG       ul = 0x00008000;
    UINT        idComDev;
    PWOWPORT    pWOWPort;
    PCOMSTAT16  pcs16;
    register    PGETCOMMERROR16 parg16;

    GETARGPTR(pFrame, sizeof(GETCOMMERROR16), parg16);
    GETMISCPTR(parg16->f2, pcs16);

    idComDev = UINT32(parg16->f1);
    if (pWOWPort = GETPWOWPTR (idComDev)) {

        if (VALIDCOM(idComDev) && pcs16) {

            WOWGetCommError(pWOWPort);


            // Always update the COMSTAT status byte, DynComm depends on it.
            pcs16->status = 0;
            if(pWOWPort->cs.fCtsHold)  pcs16->status |= W31CS_fCtsHold;
            if(pWOWPort->cs.fDsrHold)  pcs16->status |= W31CS_fDsrHold;
            // Note: RlsdHold is zero'd out on Win3.1
            if(pWOWPort->cs.fRlsdHold) pcs16->status |= W31CS_fRlsdHold;
            if(pWOWPort->cs.fXoffHold) pcs16->status |= W31CS_fXoffHold;
            if(pWOWPort->cs.fXoffSent) pcs16->status |= W31CS_fSentHold;
            if(pWOWPort->cs.fEof)      pcs16->status |= W31CS_fEof;
            if(pWOWPort->cs.fTxim)     pcs16->status |= W31CS_fTxim;

            pcs16->cbInQue  = (WORD)pWOWPort->cs.cbInQue;
            pcs16->cbOutQue = (WORD)pWOWPort->cs.cbOutQue;

            // account for the UnGot char (if any)
            if(pWOWPort->fUnGet) {
                pcs16->cbInQue++;
            }
        }

        // if an LPT OR pcs16 == NULL, Win3.1 returns the error code
        else {
            // for LPT's Win3.1 just zero's the COMSTAT & returns the error code
            if(VALIDLPT(idComDev)) {
                if(pcs16) {
                    RtlZeroMemory((PVOID)pcs16, sizeof(COMSTAT16));
                }
            }
        }

        ul = (ULONG)pWOWPort->dwErrCode;

        // clear the error now that the app has got it (but maintain queues)
        pWOWPort->dwErrCode = 0;
        pWOWPort->lpComDEB16->ComErr = 0;
        RtlZeroMemory((PVOID)&(pWOWPort->cs), sizeof(COMSTAT));
        if(pcs16) {
            pWOWPort->cs.cbInQue  = pcs16->cbInQue;
            pWOWPort->cs.cbOutQue = pcs16->cbOutQue;
        }
    }

    FLUSHVDMPTR(parg16->f2, sizeof(COMSTAT16), pcs16);
    FREEMISCPTR(pcs16);
    FREEARGPTR(parg16);
    RETURN(ul);
}




// Win3.1 returns:
//    EvtWord on success.
//    0 for bad idComDev OR LPTx.
ULONG FASTCALL WU32GetCommEventMask(PVDMFRAME pFrame)
{
    ULONG     ul=0;
    DWORD     dwEvtMask;
    UINT      idComDev;
    PWOWPORT  pWOWPort;
    PCOMDEB16 pDEB16;
    register  PGETCOMMEVENTMASK16 parg16;

    GETARGPTR(pFrame, sizeof(GETCOMMEVENTMASK16), parg16);

    idComDev = UINT32(parg16->f1);
    if (VALIDCOM(idComDev)) {
        if(pWOWPort = PortTab[idComDev].pWOWPort) {

            if(pDEB16 = pWOWPort->lpComDEB16) {

                // in Win3.1 the app gets current event word (NOT the EvtMask!!)
                ul = (ULONG)pDEB16->EvtWord;

                // clear event word like Win3.1 does
                dwEvtMask = (DWORD)WORD32(parg16->f2);
                pDEB16->EvtWord = LOWORD((~dwEvtMask) & (DWORD)ul);
            }
        }
    }

    FREEARGPTR(parg16);
    RETURN(ul);
}




// Win3.1 returns:
//    0 for success.
//    -1 for bad idComDev.
//    IE_NOPEN for not opened.
ULONG FASTCALL WU32GetCommState(PVDMFRAME pFrame)
{
    ULONG    ul = (ULONG)-1;
    UINT     idComDev;
    DCB      dcb32;
    PWOWPORT pWOWPort;
    PDCB16   pdcb16;
    register PGETCOMMSTATE16 parg16;

    GETARGPTR(pFrame, sizeof(GETCOMMSTATE16), parg16);

    idComDev = UINT32(parg16->f1);
    if (pWOWPort = GETPWOWPTR(idComDev)) {

        GETMISCPTR(parg16->f2, pdcb16);

        if(pdcb16) {
            if(VALIDCOM(idComDev)) {
                if(GetCommState(pWOWPort->h32, &dcb32)) {

                    DCB32toDCB16(pdcb16, &dcb32, idComDev, pWOWPort->fChEvt);
                    ul = 0; // Win3.1 returns 0 if success
                }
            }

            // else get DCB for LPT's
            else {
                RtlCopyMemory((PVOID)pdcb16,
                              (PVOID)pWOWPort->pdcb16,
                              sizeof(DCB16));
                ul = 0; // Win3.1 returns 0 if success
            }

            FLUSHVDMPTR(parg16->f2, sizeof(DCB16), pdcb16);
            FREEMISCPTR(pdcb16);
        }
    }
    // else if they got a handle that looks good but they didn't open the port
    else if(VALIDCOM(idComDev) || VALIDLPT(idComDev)) {
        ul = (ULONG)IE_NOPEN;
    }

#ifdef DEBUG
    if(!(ul==0)) {
        LOGDEBUG(0,("WOW::WU32GetCommState: failed\n"));
    }
#endif

    FREEARGPTR(parg16);
    RETURN(ul);
}




// Win3.1 returns:
//    An idComDev on success.
//    IE_BADID for bad port name.
//    IE_OPEN if port already open.
//    IE_HARDWARE if hardware in use (ie. by mouse) OR port doesn't exist.
//    IE_MEMORY if both cbInQueue & cbOutQueue == 0 OR can't allocate a queue.
//    IE_NOPEN if can't open port.
//    IE_DEFAULT if initialization fails for various reasons.
// We pass an additional (4th) parameter from IOpenComm() for SetCommEventMask()
// support.  It's a DWORD that is obtained by a call to GlobalDosAlloc().
ULONG FASTCALL WU32OpenComm(PVDMFRAME pFrame)
{
    INT          ret;
    UINT         iTab, idComDev;
    CHAR         COMbuf[] = "COMx:9600,E,7,1";  // Win3.1 default
    CHAR         szPort[MAXCOMNAMENULL];
    DWORD        dwDEBAddr;
    DWORD        cbInQ  = 0;
    DWORD        cbOutQ;
    HANDLE       h32     = 0;
    HANDLE       hREvent = 0;
    DCB          dcb32;
    PSZ          psz1;
    PDCB16       pdcb16  = NULL;
    PWOWPORT     pWOWPort;
    PCOMDEB16    lpComDEB16;
    COMMTIMEOUTS ct;
    PUCHAR       pchWriteBuf = NULL;
    UINT         cbWriteBuf = 0;
    HANDLE       hWriteEvent = 0;
    DWORD        dwWriteThreadId;
    BOOL         fIsLPTPort;
    register     POPENCOMM16 parg16;

    GETARGPTR(pFrame, sizeof(OPENCOMM16), parg16);
    GETPSZPTR(parg16->f1, psz1);

    // see if valid com device name...
    if((iTab = GetModePortTabIndex(psz1)) == (UINT)IE_BADID) {
        ret = IE_BADID;
        goto ErrorExit0;
    }

    // check if named port is already in use
    if(PortTab[iTab].pWOWPort != NULL) {
        ret = IE_OPEN;
        goto ErrorExit0;
    }

    if ( VALIDCOM(iTab) ) {
        idComDev = iTab;
        fIsLPTPort = FALSE;
    } else {
        idComDev = TABIDTOLPT(iTab);
        fIsLPTPort = TRUE;
    }

    // get port name: app may pass in a full mode string in Win3.1
    GetPortName(psz1, szPort);

    // try to open the port
    if((h32 = CreateFile(szPort,
                         GENERIC_READ | GENERIC_WRITE,
                         0,
                         NULL,
                         OPEN_EXISTING,
                         FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
                         NULL)) == INVALID_HANDLE_VALUE) {

        if(GetLastError() == ERROR_FILE_NOT_FOUND) {
            ret = IE_HARDWARE;
        }
        else {
            LOGDEBUG (LOG_ERROR,("WOW::WU32OpenComm CreateFile failed, lasterror=0x%x\n",GetLastError()));
            ret = IE_NOPEN;
        }
        goto ErrorExit0;
    }



    // ignore LPT's for this check like Win3.1 does
    if( !fIsLPTPort ) {

        // common method apps use to see if a COM port is already open
        if((WORD32(parg16->f2) == 0) &&
           (WORD32(parg16->f3) == 0)) {
            ret = IE_MEMORY;
            goto ErrorExit1;
        }

        // set up the I/O queues
        cbInQ = (DWORD)WORD32(parg16->f2);
        cbOutQ = (DWORD)WORD32(parg16->f3);

        //
        // Allocate write buffer to emulate Win3.1's transmit queue.
        // We allocate one extra byte because the last byte of the
        // buffer is never filled.  If it were, then the head and
        // tail pointers would be equal, which we use to indicate
        // an *empty* buffer.
        //

        cbWriteBuf = cbOutQ + 1;

        if (!(pchWriteBuf = malloc_w(cbWriteBuf))) {
            ret = IE_MEMORY;
            goto ErrorExit1;
        }

        //
        // IO buffers must be a multiple of 2 for SetupComm().
        // Note that SetupComm may ignore the write buffer size
        // entirely, but TonyE says that we should still pass
        // down the size requested, since in any case writes
        // will complete only when the bits are irretrievably
        // sent, I.E. in the UART or other hardware, out of
        // the control of the device driver.
        //

        cbInQ = (cbInQ + 1) & ~1;
        cbOutQ = (cbOutQ + 1) & ~1;
        if(!SetupComm(h32, cbInQ, cbOutQ)) {
            ret = IE_MEMORY;
            goto ErrorExit2;
        }

        //
        // Create an event used by the app thread to wake up
        // the writer thread when the write buffer is
        // empty and the app writes something.  The event
        // is auto-reset, meaning it is reset when the
        // writer wakes up.  The event is initially not
        // signaled, it will be signaled when the first
        // write occurs.
        //

        if (!(hWriteEvent = CreateEvent(NULL, FALSE, FALSE, NULL))) {
            ret = IE_MEMORY;
            goto ErrorExit2;
        }

        //
        // create an event for ReadComm()'s overlapped structure
        //

        if(!(hREvent = CreateEvent(NULL, TRUE, FALSE, NULL))) {
            ret = IE_NOPEN;
            goto ErrorExit3;
        }

        // set the timeout values
        ct.ReadIntervalTimeout = (DWORD)INFINITE;  // == MAXDWORD
        ct.ReadTotalTimeoutMultiplier=0;
        ct.ReadTotalTimeoutConstant=0;
        ct.WriteTotalTimeoutMultiplier=0;
        ct.WriteTotalTimeoutConstant=WRITE_TIMEOUT;
        if(!SetCommTimeouts(h32, &ct)) {
            ret = IE_DEFAULT;
            goto ErrorExit3;
        }

        // make sure the DCB is Win3.1 compatible
        // NOTE: app can pass in a full mode string in Win3.1
        if((strlen(psz1) < 4) || !InitDCB32(&dcb32, psz1)) {
            if(!InitDCB32(&dcb32, COMbuf)) {
                ret = IE_DEFAULT;
                goto ErrorExit3;
            }
        }

        // set current DCB to Win3.1 compatibility
        if(!SetCommState(h32, &dcb32)) {
            ret = IE_DEFAULT;
            goto ErrorExit3;
        }

        // purge the I/O buffers just to be sure
        PurgeComm(h32, PURGE_TXCLEAR);
        PurgeComm(h32, PURGE_RXCLEAR);

    }

    // we need to set up a default DCB for LPT's
    else {

        if((pdcb16 = malloc_w(sizeof(DCB16))) == NULL) {
            ret = IE_DEFAULT;
            goto ErrorExit1;
        }

        // initialize everything to 0
        RtlZeroMemory((PVOID)pdcb16, sizeof(DCB16));

        // save the idComDev only in the DCB
        pdcb16->Id = LOBYTE(LOWORD(idComDev));
    }

    // allocate the WOWPort structure for this port
    if((pWOWPort = malloc_w(sizeof(WOWPORT))) == NULL) {
        ret = IE_DEFAULT;
        goto ErrorExit3;
    }

    // get seg:sel dword returned by GlobalDosAlloc for the DEB struct
    // we'll treat the 16:16 pDEB as real mode on 32-bit side due to
    // some MIPS issues: v-simonf
    if (!(dwDEBAddr = DWORD32(parg16->f4))) {
        ret = IE_MEMORY;
        goto ErrorExit4;
    }

    // Isolate the segment value
    dwDEBAddr &= 0xFFFF0000;

    // save flat pointer to DEB for use in Modem interrupt thread
    lpComDEB16 = (PCOMDEB16) GetRModeVDMPointer(dwDEBAddr);

    // init the DEB
    InitDEB16(lpComDEB16, iTab, WORD32(parg16->f2), WORD32(parg16->f3));

    // init the support struct
    RtlZeroMemory((PVOID)pWOWPort, sizeof(WOWPORT));

    pWOWPort->h32            = h32;
    pWOWPort->idComDev       = idComDev;
    pWOWPort->dwComDEB16     = DWORD32(parg16->f4);
    pWOWPort->lpComDEB16     = lpComDEB16;
    pWOWPort->dwThreadID     = CURRENTPTD()->dwThreadID;
    pWOWPort->hREvent        = hREvent;
    pWOWPort->cbWriteBuf     = (WORD)cbWriteBuf;
    pWOWPort->cbWriteFree    = cbWriteBuf - 1;  // never use byte before head.
    pWOWPort->pchWriteBuf    = pchWriteBuf;
    pWOWPort->pchWriteHead   = pchWriteBuf;
    pWOWPort->pchWriteTail   = pchWriteBuf;
    pWOWPort->hWriteEvent    = hWriteEvent;
    pWOWPort->cbWritePending = 0;
    InitializeCriticalSection(&pWOWPort->csWrite);
    pWOWPort->pdcb16         = pdcb16;
    pWOWPort->cbInQ          = cbInQ;
    // hack for QuickLink Gold 1.3 -- See bug #398011
    // save QL stack sel in hiword, ComDEB16 seg in the loword
    if(IsQLinkGold(pFrame->wTDB)) {
        pWOWPort->QLStackSeg     = (DWORD32(parg16->f1) & 0xFFFF0000) |
                                   (pWOWPort->dwComDEB16 & 0x0000FFFF);
    }
    // else pWOWPort->QLStackSeg implicitly set to 0 by RtlZeroMemory above.

    if (!(pWOWPort->olWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) {
        LOGDEBUG(0, ("%s", "WU32OpenComm unable to create overlapped write event, failing.\n"));
        ret = IE_MEMORY;
        goto ErrorExit4;
    }

    PortTab[iTab].pWOWPort = pWOWPort;

    //
    // Create the writer thread and pass it pWOWPort as its
    // parameter.
    //

    if (!fIsLPTPort) {
        pWOWPort->hWriteThread = CreateThread(
            NULL,                // lpsa
            0,                   // stack size (default)
            WOWCommWriterThread, // start address
            pWOWPort,            // lpvThreadParm
            0,                   // fdwCreate
            &dwWriteThreadId
            );

        if (!pWOWPort->hWriteThread) {
            ret = IE_MEMORY;
            goto ErrorExit5;
        }
    }

    ret = idComDev;   // return the idComDev
    goto CleanExit;

// this is the error code path
ErrorExit5:
    CloseHandle(pWOWPort->olWrite.hEvent);

ErrorExit4:
    free_w(pWOWPort);

ErrorExit3:
    if (hREvent) { CloseHandle(hREvent); }
    if (hWriteEvent) { CloseHandle(hWriteEvent); }
    if (fIsLPTPort) { free_w(pdcb16); }

ErrorExit2:
    if(pchWriteBuf) { free_w(pchWriteBuf); }

ErrorExit1:
    CloseHandle(h32);

ErrorExit0:
    LOGDEBUG (0, ("WOW::WU32OpenComm failed\n"));

CleanExit:
    FREEVDMPTR(psz1);
    FREEARGPTR(parg16);
    RETURN((ULONG)ret); // return error
}


//
// WriteComm()
//
// Win3.1 returns:
//    # bytes written on success (*= -1 on error).
//    0 for bad idComDev OR if app specifies to write 0 bytes.
//    -1 if port hasn't been opened,
//

ULONG FASTCALL WU32WriteComm(PVDMFRAME pFrame)
{
    register      PWRITECOMM16 parg16;
    LONG          i = -1;
    PSZ           psz2;
    PWOWPORT      pwp;
    UINT          idComDev;
    PWOWPORT      pWOWPort;
    DWORD         cbWritten;


    GETARGPTR(pFrame, sizeof(WRITECOMM16), parg16);
    GETPSZPTR(parg16->f2, psz2);

    idComDev = UINT32(parg16->f1);
    // this will be true only if the (valid) port has been opened
    if (pWOWPort = GETPWOWPTR(idComDev)) {

        if(VALIDCOM(idComDev)) {

            if ((pwp = GETPWOWPTR(UINT32(parg16->f1))) && psz2) {

                // if the app is interested in timeouts...
                if(pwp->lpComDEB16->MSRMask) {

                    // ...see if RLSD, CTS, & DSR timeout before going high
                    if(MSRWait(pwp)) {
                        FREEPSZPTR(psz2);
                        FREEARGPTR(parg16);
                        return(0);  // this is what Win3.1 does for Timeouts
                    }
                }

                i = EnqueueCommWrite(pwp, psz2, parg16->f3);
                if (i != parg16->f3) {
                     i = -i;
                     pwp->dwErrCode |= CE_TXFULL;
                }
            }
        }

        // else LPT's go this way...
        else {

            //
            // This call to WriteFile could block, but I don't think
            // that's a problem.  - DaveHart
            //
            if ((pwp = GETPWOWPTR(UINT32(parg16->f1))) && psz2) {

                if (!WriteFile(pwp->h32, psz2, parg16->f3, &cbWritten, &pwp->olWrite)) {

                    if (ERROR_IO_PENDING == GetLastError() ) {

                        //
                        // Wait for the write to complete or for us to
                        // be alerted that the port is closing.
                        //

                        if (GetOverlappedResult(pwp->h32,
                                                &pwp->olWrite,
                                                &cbWritten,
                                                TRUE
                                                )) {
                            i = cbWritten;
                            goto WriteSuccess;
                        }
                    }
                    LOGDEBUG(0, ("WU32WriteComm: WriteFile to id %u fails (error %u)\n",
                                 pwp->idComDev, GetLastError()));
                    if (cbWritten) {
                        i = cbWritten;
                        i = -i;
                    }
                }
                else {
                    i = cbWritten;
                }
            }
        }
    }
    else if(!(VALIDCOM(idComDev) || VALIDLPT(idComDev))) {
        i = 0;
    }
WriteSuccess:

    FREEPSZPTR(psz2);
    FREEARGPTR(parg16);
    RETURN((ULONG)i);
}


// Win3.1 returns:
//    # chars read on success.
//    0 for: bad idComDev, cbRead == 0, LPTx, port not open, 0 chars read,
//    OR for general comm error.
ULONG FASTCALL WU32ReadComm(PVDMFRAME pFrame)
{
    ULONG      ul = 0;
    ULONG      cb;
    BOOL       fUnGet = FALSE;
    UINT       idComDev;
    PBYTE      pb2;
    PWOWPORT   pWOWPort;
    OVERLAPPED Rol;
    register   PREADCOMM16 parg16;

    GETARGPTR(pFrame, sizeof(READCOMM16), parg16);
    GETMISCPTR(parg16->f2, pb2);

    cb = (ULONG)UINT32(parg16->f3);
    if((cb != 0) && pb2) {

        idComDev = UINT32(parg16->f1);
        if (VALIDCOM(idComDev) && (pWOWPort = PortTab[idComDev].pWOWPort)) {

            // if an UnGot char is pending
            if (pWOWPort->fUnGet) {
                fUnGet = TRUE;
                pWOWPort->fUnGet = FALSE;
                *pb2++ = pWOWPort->cUnGet;

                // this line commented out 8/3/95
                // cb--;  // we now need one less char

                // In order to make this work correctly we should cb-- above
                // to reflect the ungot char, unfortunately Win3.1 & Win95
                // don't do that so we will maintain this bug for "ouch!"
                // compatibility. a-craigj 8/3/95

            }

            // TonyE claims we should do this before each read to avoid problems
            Rol.Internal     = 0;
            Rol.InternalHigh = 0;
            Rol.Offset       = 0;
            Rol.OffsetHigh   = 0;
            Rol.hEvent       = pWOWPort->hREvent;

            if (!ReadFile(pWOWPort->h32,
                          pb2,
                          cb,
                          (LPDWORD)&ul,
                          &Rol)) {

                if (ERROR_IO_PENDING == GetLastError()) {

                    if (!GetOverlappedResult(pWOWPort->h32,
                                             &Rol,
                                             &ul,
                                             TRUE
                                             )) {

                        LOGDEBUG(0, ("WOW::WU32ReadComm:GetOverlappedResult failed, error = 0x%x\n",
                                     GetLastError()));
                        ul = 0;

                    }

                } else {

                    LOGDEBUG(0, ("WOW::WU32ReadComm:ReadFile failed, error = 0x%x\n",
                                 GetLastError()));
                    ul = 0;
                }
            }

            if(fUnGet) {
                ul++;   // account for ungot char
                pb2--;  // accounts for previous pb2++ for FREEVDMPTR
            }

            FLUSHVDMPTR(parg16->f2, (USHORT)ul, pb2);

        }

        FREEVDMPTR(pb2);
    }

    FREEARGPTR(parg16);
    RETURN(ul);
}




// Win3.1 returns:
//    Error word on success OR LPTx.
//    0x8000 on bad idComDev.
ULONG FASTCALL WU32SetCommBreak(PVDMFRAME pFrame)
{
    ULONG    ul = 0x00008000;
    UINT     idComDev;
    PWOWPORT pWOWPort;
    register PSETCOMMBREAK16 parg16;

    GETARGPTR(pFrame, sizeof(SETCOMMBREAK16), parg16);

    idComDev = UINT32(parg16->f1);
    if (pWOWPort = GETPWOWPTR(idComDev)) {
        if(VALIDCOM(idComDev)) {
            if(!SetCommBreak(pWOWPort->h32)) {
                WOWGetCommError(pWOWPort);
            }
        }
        ul = pWOWPort->dwErrCode; // Win3.1 returns last err
    }

#ifdef DEBUG
    if(!(ul!=CE_MODE)) {
        LOGDEBUG(0,("WOW::WU32SetCommBreak: failed\n"));
    }
#endif

    FREEARGPTR(parg16);
    RETURN(ul);
}




// Win3.1 returns:
//    A 16:16 ptr into the DEB struct on success.
//    0 on any error OR LPT.
// The 16:16 ptr that we return to the app was actually obtained in
// IOpenComm() in user.exe.
ULONG FASTCALL WU32SetCommEventMask(PVDMFRAME pFrame)
{
    ULONG      ul = 0;
    BOOL       fOK = TRUE;
    UINT       idComDev;
    DWORD      dwDEBAddr;
    PWOWPORT   pWOWPort;
    register   PSETCOMMEVENTMASK16 parg16;

    GETARGPTR(pFrame, sizeof(SETCOMMEVENTMASK16), parg16);

    idComDev  = UINT32(parg16->f1);
    if ((VALIDCOM(idComDev)) && (pWOWPort = PortTab[idComDev].pWOWPort)) {

        // if the Modem interrupt thread hasn't been started yet -- go start it
        if(pWOWPort->hMiThread == NULL) {

            // start our Modem interrupt thread
            if(!WOWStartModemIntThread(pWOWPort)) {
                fOK = FALSE;
            }
        }

        // if everything is hunky-dory...
        if(fOK) {

            // success: Win3.1 returns 16:16 protect mode ptr to
            // DEB->EvtWord (some apps subtract offset of EvtWord
            // from ptr to get start of DEB).
            dwDEBAddr  = LOWORD(pWOWPort->dwComDEB16) << 16;
            ul = dwDEBAddr + FIELD_OFFSET(COMDEB16, EvtWord);

            // save the mask the app requested
            pWOWPort->lpComDEB16->EvtMask = (WORD)(parg16->f2);
        }
    }

#ifdef DEBUG
    if(!(ul!=0)) {
        LOGDEBUG(0,("WOW::WU32SETCOMMEVENTMASK: failed\n"));
    }
#endif

    FREEARGPTR(parg16);
    RETURN(ul);
}




// Win3.1 returns:
//    0 on success OR LPTx.
//    IE_BADID for bad idComDev.
//    IE_NOPEN if file hasn't been opened.
//    IE_BAUDRATE for bad baud rate.
//    IE_BYTESIZE for bad byte size.
//    IE_DEFAULT for bad parity or stop bits.
ULONG FASTCALL WU32SetCommState(PVDMFRAME pFrame)
{
    ULONG    ul = (ULONG)IE_BADID;
    UINT     idComDev;
    PDCB16   pdcb16;
    DCB      dcb32;
    PWOWPORT pWOWPort;
    register PSETCOMMSTATE16 parg16;
    DWORD    dwMSR;

    GETARGPTR(pFrame, sizeof(SETCOMMSTATE16), parg16);
    GETMISCPTR(parg16->f1, pdcb16);

    if(pdcb16) {

        idComDev = pdcb16->Id;
        if(pWOWPort = GETPWOWPTR(idComDev)) {

            if(VALIDCOM(idComDev)) {
                DCB16toDCB32(pWOWPort, &dcb32, pdcb16);

                if(SetCommState(pWOWPort->h32, &dcb32)) {
                    ul = 0;

                    // Win 3.1 initializes the MSRShadow during SetCommState
                    // so we will too. InterNet in a Box Dialer depends on it.
                    GetCommModemStatus(pWOWPort->h32, &dwMSR);
                    dwMSR &= MSR_STATEONLY;
                    pWOWPort->lpComDEB16->MSRShadow = LOBYTE(LOWORD(dwMSR));
                }
                else {
                    ul = (ULONG)IE_DEFAULT; // we just say something's wrong
                }

            }
            else {
                RtlCopyMemory((PVOID)pWOWPort->pdcb16,
                              (PVOID)pdcb16,
                              sizeof(DCB16));
                ul = 0;
            }
        }
        // else if they got a handle that looks good but they didn't open port
        else if (VALIDCOM(idComDev) || VALIDLPT(idComDev)) {
            ul = (ULONG)IE_NOPEN;
        }

        FREEMISCPTR(pdcb16);
    }

    FREEARGPTR(parg16);
    RETURN(ul);
}




// Win3.1 returns:
//    0 for success.
//    0x8000 for bad idComDev.
//    0x4000 if char can't be sent.
ULONG FASTCALL WU32TransmitCommChar(PVDMFRAME pFrame)
{
    ULONG        ul = 0x8000;
    UINT         idComDev;
    CHAR         ch;
    PWOWPORT     pWOWPort;
    DWORD        cbWritten;
    register PTRANSMITCOMMCHAR16 parg16;

    GETARGPTR(pFrame, sizeof(TRANSMITCOMMCHAR16), parg16);

    idComDev = UINT32(parg16->f1);
    if (pWOWPort = GETPWOWPTR(idComDev)) {

        if(VALIDCOM(idComDev)) {
            if(TransmitCommChar(pWOWPort->h32, CHAR32(parg16->f2))) {
                ul = 0;  // Win3.1 returns 0 on success
            }
            else {
                ul = (ULONG)ERR_XMIT;
            }
        }

        // else LPT's go this way...
        else {

            //
            // This call to WriteFile could block, but I don't think
            // that's a problem.  - DaveHart
            //

            ch = CHAR32(parg16->f2);
            ul = ERR_XMIT;
            if (pWOWPort = GETPWOWPTR(UINT32(parg16->f1))) {

                if (!WriteFile(pWOWPort->h32, &ch, 1, &cbWritten, &pWOWPort->olWrite)) {

                    if (ERROR_IO_PENDING == GetLastError() ) {

                        //
                        // Wait for the write to complete or for us to
                        // be alerted that the port is closing.
                        //

                        if (GetOverlappedResult(pWOWPort->h32,
                                                &pWOWPort->olWrite,
                                                &cbWritten,
                                                TRUE
                                                )) {
                            ul = 0;
                            goto TransmitSuccess;
                        }
                    }
                    LOGDEBUG(0, ("WU32TransmitCommChar: WriteFile to id %u fails (error %u)\n",
                                 pWOWPort->idComDev, GetLastError()));
                }
                else {
                    ul = 0;
                }
            }

        }
    }
TransmitSuccess:

    FREEARGPTR(parg16);
    RETURN(ul);
}


// Win3.1 returns:
//    0 on success OR bad idComDev OR LPTx.
//    -1 if port not open OR if ungot char already pending.
ULONG FASTCALL WU32UngetCommChar(PVDMFRAME pFrame)
{
    ULONG    ul = (ULONG)-1;
    UINT     idComDev;
    PWOWPORT pWOWPort;
    register PUNGETCOMMCHAR16 parg16;

    GETARGPTR(pFrame, sizeof(UNGETCOMMCHAR16), parg16);

    // see if port open...
    idComDev = UINT32(parg16->f1);
    if (VALIDCOM(idComDev)) {

        if (pWOWPort = PortTab[idComDev].pWOWPort) {

            // if ungot char already pending return -1
            if(pWOWPort->fUnGet == FALSE) {
                pWOWPort->fUnGet = TRUE;
                pWOWPort->cUnGet = CHAR32(parg16->f2);
                ul = 0;
            }
        }
    }
    else {
        ul = 0;
    }

#ifdef DEBUG
    if(!(ul==0)) {
        LOGDEBUG(0,("WOW::WU32UngetCommChar: failed\n"));
    }
#endif

    FREEARGPTR(parg16);
    RETURN(ul);
}


DWORD Baud16toBaud32(UINT BaudRate)
{
    UINT DLatch;

    // this function is set up this way on purpose (see SetCom300 ibmsetup.asm)

    // get the equivalent baud
    switch(BaudRate) {

        // it they specified the baud rate directly
        case           CBR_110:
        case           CBR_300:
        case           CBR_600:
        case          CBR_1200:
        case          CBR_2400:
        case          CBR_4800:
        case          CBR_9600:
        case         CBR_19200:
        case         CBR_14400:
        case         CBR_38400:
        case         CBR_56000:   return(BaudRate);

        // Win3.1 baud rate constants
        case        W31CBR_110:   return(CBR_110);
        case        W31CBR_300:   return(CBR_300);
        case        W31CBR_600:   return(CBR_600);
        case       W31CBR_1200:   return(CBR_1200);
        case       W31CBR_2400:   return(CBR_2400);
        case       W31CBR_4800:   return(CBR_4800);
        case       W31CBR_9600:   return(CBR_9600);
        case      W31CBR_19200:   return(CBR_19200);
        case      W31CBR_14400:   return(CBR_14400);
        case      W31CBR_38400:   return(CBR_38400);
        case      W31CBR_56000:   return(CBR_56000);

        // start special cases
        // SmartCom uses this to get 115200
        case     W31CBR_115200:   return(CBR_115200);

        // Win3.1 fails these two (even though they're defined in windows.h)
        // but they just might work on NT
        case     W31CBR_128000:   return(CBR_128000);
        case     W31CBR_256000:   return(CBR_256000);
        // end special cases

        // handle the blank table entries for "reserved"
        case  W31CBR_reserved1:
        case  W31CBR_reserved2:
        case  W31CBR_reserved3:
        case  W31CBR_reserved4:
        case  W31CBR_reserved5:   return(0);

        // avoid divide by zero
        case                 0:
        case                 1:   return(0);

        // handle obscure specifications that will work in Win3.1
        default:

            // get the integer divisor latch value
            DLatch = CBR_115200 / BaudRate;

            switch(DLatch) {
                case    W31_DLATCH_110:   return(CBR_110);
                case    W31_DLATCH_300:   return(CBR_300);
                case    W31_DLATCH_600:   return(CBR_600);
                case   W31_DLATCH_1200:   return(CBR_1200);
                case   W31_DLATCH_2400:   return(CBR_2400);
                case   W31_DLATCH_4800:   return(CBR_4800);
                case   W31_DLATCH_9600:   return(CBR_9600);
                case  W31_DLATCH_19200:   return(CBR_19200);
                case  W31_DLATCH_14400:   return(CBR_14400);
                case  W31_DLATCH_38400:   return(CBR_38400);
                case  W31_DLATCH_56000:   return(CBR_56000);
                case W31_DLATCH_115200:   return(CBR_115200);

                // Win3.1, anything else returns whatever DLatch happens to be
                // since we're mapping to baud we return the specified baud
                default:   return(BaudRate);
            }
    }
}




WORD Baud32toBaud16(DWORD BaudRate)
{
    if(BaudRate >= CBR_115200) {
        switch(BaudRate) {
            case CBR_256000: return(W31CBR_256000);
            case CBR_128000: return(W31CBR_128000);
            case CBR_115200:
            default:         return(W31CBR_115200);
        }
    }
    else {
        return(LOWORD(BaudRate));
    }
}





void DCB16toDCB32(PWOWPORT pWOWPort, LPDCB lpdcb32, PDCB16 pdcb16)
{

    // zero 32-bit struct -> any flags and fields not explicitly set will be 0
    RtlZeroMemory((PVOID)lpdcb32, sizeof(DCB));

    lpdcb32->DCBlength         = sizeof(DCB);
    lpdcb32->BaudRate          = Baud16toBaud32(pdcb16->BaudRate);

    // 16-bit bitfields may align differently with 32-bit compilers
    // we use this mechanism to align them the way Win3.1 expects them
    if(pdcb16->wFlags & W31DCB_fBinary)       lpdcb32->fBinary      = 1;
    if(pdcb16->wFlags & W31DCB_fParity)       lpdcb32->fParity      = 1;
    if(pdcb16->wFlags & W31DCB_fOutxCtsFlow)  lpdcb32->fOutxCtsFlow = 1;
    if(pdcb16->wFlags & W31DCB_fOutxDsrFlow)  lpdcb32->fOutxDsrFlow = 1;

    // set up mechanism for handling event char notification
    if(pdcb16->wFlags & W31DCB_fChEvt) pWOWPort->fChEvt = TRUE;

    if(pdcb16->wFlags & W31DCB_fDtrFlow) {
        lpdcb32->fDtrControl = DTR_CONTROL_HANDSHAKE;
    }
    else if(pdcb16->wFlags & W31DCB_fDtrDisable) {
        lpdcb32->fDtrControl = DTR_CONTROL_DISABLE;
    }
    else {
        lpdcb32->fDtrControl = DTR_CONTROL_ENABLE;
    }

    if(pdcb16->wFlags & W31DCB_fOutX)         lpdcb32->fOutX        = 1;
    if(pdcb16->wFlags & W31DCB_fInX)          lpdcb32->fInX         = 1;
    if(pdcb16->wFlags & W31DCB_fPeChar)       lpdcb32->fErrorChar   = 1;
    if(pdcb16->wFlags & W31DCB_fNull)         lpdcb32->fNull        = 1;

    if(pdcb16->wFlags & W31DCB_fRtsFlow) {
        lpdcb32->fRtsControl = RTS_CONTROL_HANDSHAKE;
    }
    else if(pdcb16->wFlags & W31DCB_fRtsDisable) {
        lpdcb32->fRtsControl = RTS_CONTROL_DISABLE;
    }
    else {
        lpdcb32->fRtsControl = RTS_CONTROL_ENABLE;
    }

    if(pdcb16->wFlags & W31DCB_fDummy2)       lpdcb32->fDummy2      = 1;

    // Check the passed in XonLim & XoffLim values against the cbInQ value.
    // Prodigy's modem detector leaves these values uninitialized.
    if ((pdcb16->XonLim  >= pWOWPort->cbInQ) ||
        (pdcb16->XoffLim >  pWOWPort->cbInQ) ||
        (pdcb16->XonLim  >= pdcb16->XoffLim)) {
        lpdcb32->XonLim = 0;
        lpdcb32->XoffLim = (WORD)(pWOWPort->cbInQ - (pWOWPort->cbInQ >> 2));
    }
    else {
        lpdcb32->XonLim  = pdcb16->XonLim;
        lpdcb32->XoffLim = pdcb16->XoffLim;
    }

    lpdcb32->ByteSize          = pdcb16->ByteSize;
    lpdcb32->Parity            = pdcb16->Parity;
    lpdcb32->StopBits          = pdcb16->StopBits;

    // Digiboard driver doesn't want to see XonChar == XoffChar even if
    // xon/xoff is disabled.
    if ((pdcb16->XonChar == '\0') && (lpdcb32->XoffChar == '\0')) {
        lpdcb32->XonChar = pdcb16->XonChar+1;
    }
    else {
        lpdcb32->XonChar = pdcb16->XonChar;
    }

    lpdcb32->XoffChar          = pdcb16->XoffChar;
    lpdcb32->ErrorChar         = pdcb16->PeChar;
    lpdcb32->EofChar           = pdcb16->EofChar;
    lpdcb32->EvtChar           = pdcb16->EvtChar;

#ifdef FE_SB
// for MSKKBUG #3213 by v-kenich
// MYTALK for Win set NULL these two fields at transfering binary file
// If call SetCommstate as it is, SetCommState return error (Invalid parameter)
// I think this fix doesn't occur any bad thing without condition of MYTALK
// Really correcting parameter check is better. but I don't know where it is.

    if (!lpdcb32->XonChar) lpdcb32->XonChar = 0x11;
    if (!lpdcb32->XoffChar) lpdcb32->XoffChar = 0x13;
#endif // FE_SB

    // set up for RLSD, CTS, and DSR timeout support (not supported on NT)
    pWOWPort->lpComDEB16->MSRMask = 0;

    pWOWPort->RLSDTimeout = pdcb16->RlsTimeout;
    if(pWOWPort->RLSDTimeout != IGNORE_TIMEOUT)
        pWOWPort->lpComDEB16->MSRMask |= LOBYTE(MS_RLSD_ON);

    pWOWPort->CTSTimeout = pdcb16->CtsTimeout;
    if(pWOWPort->CTSTimeout != IGNORE_TIMEOUT)
        pWOWPort->lpComDEB16->MSRMask |= LOBYTE(MS_CTS_ON);

    pWOWPort->DSRTimeout = pdcb16->DsrTimeout;
    if(pWOWPort->DSRTimeout != IGNORE_TIMEOUT)
        pWOWPort->lpComDEB16->MSRMask |= LOBYTE(MS_DSR_ON);

    // these fields remain 0
    //lpdcb32->fDsrSensitivity   = 0;
    //lpdcb32->fTXContinueOnXoff = 0;
    //lpdcb32->fAbortOnError     = 0;
    //lpdcb32->wReserved         = 0;

}



void DCB32toDCB16(PDCB16 pdcb16, LPDCB lpdcb32, UINT idComDev, BOOL fChEvt)
{

    // zero 16-bit struct -> any flags and fields not explicitly set will be 0
    RtlZeroMemory((PVOID)pdcb16, sizeof(DCB16));

    // set this field no matter what
    pdcb16->Id = (BYTE)idComDev;

    // if a COMx (Win3.1 leaves the rest 0 for LPT's)
    if(VALIDCOM(idComDev)) {
        pdcb16->Id = (BYTE)idComDev;

        // these are the "ComX:96,n,8,1" fields
        pdcb16->BaudRate        = Baud32toBaud16(lpdcb32->BaudRate);
        pdcb16->ByteSize        = lpdcb32->ByteSize;
        pdcb16->Parity          = lpdcb32->Parity;
        pdcb16->StopBits        = lpdcb32->StopBits;

        // 16-bit bitfields may align differently with 32-bit compilers
        // we use this mechanism to align them the way Win3.1 expects them
        if(lpdcb32->fBinary)      pdcb16->wFlags |= W31DCB_fBinary;

        if(lpdcb32->fRtsControl == RTS_CONTROL_DISABLE) {
            pdcb16->wFlags |= W31DCB_fRtsDisable;
        }

        if(lpdcb32->fParity)      pdcb16->wFlags |= W31DCB_fParity;
        if(lpdcb32->fOutxCtsFlow) pdcb16->wFlags |= W31DCB_fOutxCtsFlow;
        if(lpdcb32->fOutxDsrFlow) pdcb16->wFlags |= W31DCB_fOutxDsrFlow;

        if(lpdcb32->fDtrControl == DTR_CONTROL_DISABLE) {
            pdcb16->wFlags |= W31DCB_fDtrDisable;
        }

        if(lpdcb32->fOutX)        pdcb16->wFlags |= W31DCB_fOutX;
        if(lpdcb32->fInX)         pdcb16->wFlags |= W31DCB_fInX;
        if(lpdcb32->fErrorChar)   pdcb16->wFlags |= W31DCB_fPeChar;
        if(lpdcb32->fNull)        pdcb16->wFlags |= W31DCB_fNull;

        if(fChEvt)                pdcb16->wFlags |= W31DCB_fChEvt;

        if(lpdcb32->fDtrControl == DTR_CONTROL_HANDSHAKE) {
            pdcb16->wFlags |= W31DCB_fDtrFlow;
        }

        if(lpdcb32->fRtsControl == RTS_CONTROL_HANDSHAKE) {
            pdcb16->wFlags |= W31DCB_fRtsFlow;
        }

        if(lpdcb32->fDummy2)      pdcb16->wFlags |= W31DCB_fDummy2;

        pdcb16->XonChar         = lpdcb32->XonChar;
        pdcb16->XoffChar        = lpdcb32->XoffChar;
        pdcb16->XonLim          = lpdcb32->XonLim;
        pdcb16->XoffLim         = lpdcb32->XoffLim;
        pdcb16->PeChar          = lpdcb32->ErrorChar;
        pdcb16->EofChar         = lpdcb32->EofChar;
        pdcb16->EvtChar         = lpdcb32->EvtChar;

    }

    // these fields remain 0
    //pdcb16->fDummy  = 0;
    //pdcb16->TxDelay = 0;

}




BOOL DeletePortTabEntry(PWOWPORT pWOWPort)
{
    INT      iTab;
    BOOL     fTimeOut;

    iTab = pWOWPort->idComDev;
    if(VALIDLPT(iTab)) {
        iTab = GETLPTID(iTab);
    }

    // flush I/O buffers & attempt to wake up Modem Interrupt thread (if any)
    pWOWPort->fClose = TRUE;
    if(VALIDCOM(iTab)) {
        PurgeComm(pWOWPort->h32, PURGE_TXCLEAR);
        PurgeComm(pWOWPort->h32, PURGE_RXCLEAR);
        SetCommMask(pWOWPort->h32, 0); // this should wake up the Mi thread

        // wake up WOWModemIntThread & tell it to exit
        // (we attempt to block (1.5 second max.) until it does)
        if(pWOWPort->hMiThread) {
            WaitForSingleObject(pWOWPort->hMiThread, 1500);
            CloseHandle(pWOWPort->hMiThread);

            // zero COMDEB
            RtlZeroMemory((PVOID)pWOWPort->lpComDEB16, sizeof(COMDEB16));
        }

        //
        // Wake up WOWCommWriterThread so it will exit, wait up to
        // 5 sec for it to go away.
        //

        SetEvent(pWOWPort->hWriteEvent);

        fTimeOut = (WaitForSingleObject(pWOWPort->hWriteThread, 5000) ==
                    WAIT_TIMEOUT);

#ifdef DEBUG
        if (fTimeOut) {
            LOGDEBUG(LOG_ALWAYS,
                ("WOW DeletePortTabEntry: Comm writer thread for port %d refused\n"
                 "    to die when asked nicely.\n", (int)pWOWPort->idComDev));
        }
#endif

        CloseHandle(pWOWPort->hWriteThread);
        CloseHandle(pWOWPort->hWriteEvent);
        free_w(pWOWPort->pchWriteBuf);

        CloseHandle(pWOWPort->hREvent);
    }
    // else free the LPT DCB support struct
    else {
        free_w(pWOWPort->pdcb16);
        CloseHandle(pWOWPort->olWrite.hEvent);
        fTimeOut = FALSE;
    }

    DeleteCriticalSection(&pWOWPort->csWrite);
    CloseHandle(pWOWPort->h32);

    // QuickLink Gold 1.3 hack.  Bug #398011
    // The app calls OpenComm(), & then SetCommEventMask() to get the ptr to the
    // comdeb16 struct.  It saves the ptr at offset 0xf36 on its stack.  The
    // problem is that the app holds onto the comdeb16 ptr after it calls
    // CloseComm() (when we free the comdeb16 memory) to be able to peek at a
    // status byte from time to time.  This works OK on Win 3.1 but not with
    // our model on NT.  Fortunately, the app tests to see if it has a comdeb16
    // ptr before dereferencing it.  Also, we're lucky because the ptr for
    // lpszDevControl in its call to OpenComm() is from its stack thus allowing
    // us to obtain the stack selector and zero out the comdeb16 ptr stored at
    // stack ss:0xf36 when the app calls CloseComm().
    if(pWOWPort->QLStackSeg) {
        LPDWORD lpQLS;
        VPVOID  vpQLS, vpCD16;

        // construct the 16:16 ptr to where the app saved the ptr to the
        // COMDEB16 struct on its stack at offset 0xf36
        vpQLS = pWOWPort->QLStackSeg & 0xFFFF0000;
        vpQLS = vpQLS | 0x00000f36;

        GETMISCPTR(vpQLS, lpQLS);

        // construct realmode 16:16 ptr of the COMDEB16 struct + 0x38 (seg:0x38)
        vpCD16 = pWOWPort->QLStackSeg & 0x0000FFFF;
        vpCD16 = (vpCD16 << 16) | 0x00000038;

        if(lpQLS) {

            // sanity check to see if everything is still what & where we
            // think it is

            // if seg:0x38 is still stored at offset 0xf36 on the apps stack...
            if(*lpQLS == (DWORD)vpCD16) {

                // zero it out -- forcing app to avoid checking the status byte
                *lpQLS = 0;

                FLUSHVDMPTR(vpQLS, sizeof(DWORD), lpQLS);
                FREEMISCPTR(lpQLS);
            }
        }
    }

    free_w(pWOWPort);
    PortTab[iTab].pWOWPort = NULL;

    return(fTimeOut);
}



UINT GetModePortTabIndex(PSZ pszModeStr)
{

    CHAR  szPort[MAXCOMNAMENULL*2];

    if(pszModeStr) {
        if(GetPortName(pszModeStr, szPort)) {
            return(GetStrPortTabIndex(szPort));
        }
    }

    return((UINT)IE_BADID);

}



BOOL GetPortName(LPSTR pszMode, LPSTR pszPort)
{

    INT   len;
    CHAR  szTemp[80];  // max len we'll take for DOS style MODE command
    BOOL  bRet = FALSE;

    len = strlen(pszMode);
    if((len >= 3) && (len < 80)) {

        // Get the first token from the mode string.
        GetPortStringToken(pszMode, szTemp);

        // map "AUX" or "PRN" to "COM1" or "LPT1" if necessary
        len = strlen(szTemp);
        if((len >= 3) && (len <= MAXCOMNAME)) {  //  "AUX" <= len <= "COMx"

            strcpy(pszPort, szTemp);
            CharUpper(pszPort);

            // filter out duplicate names for the same thing
            if(!WOW32_strcmp(pszPort, "PRN")) {
                strcpy(pszPort, "LPT1");
            }
            else if(!WOW32_strcmp(pszPort, "AUX")) {
                strcpy(pszPort, "COM1");
            }

            bRet = TRUE;
        }
    }

    return(bRet);

}

PSZ StripPortName(PSZ psz)
{
    CHAR dummy[80];  // max len we'll take for DOS style MODE command

    return(GetPortStringToken(psz, dummy));
}

//
// Copy first token to pszToken. Return pointer to next token or NULL if none.
// This code cloned from Win 3.1, COMDEV.C, field(). HGW 3.0 modem registration
// passes "COMx,,," instead of "COMx:,,," so we need to handle all seperators.
//

PSZ GetPortStringToken(PSZ pszSrc, PSZ pszToken)
{
    char   c;

    // While not the end of the string.
    while (c = *pszSrc) {
        pszSrc++;

        //Look for seperators.
        if ((c == ' ') || (c == ':') || (c == ',')) {
            *pszToken = '\0';

            while (*pszSrc == ' ') {
                pszSrc++;
            }

            if (*pszSrc) {
                return(pszSrc);
            }

            return(NULL);
        }

      *pszToken++ = c;
    }

    *pszToken = '\0';

    return(NULL);
}


UINT GetStrPortTabIndex(PSZ szPort)
{
    UINT  iTab;

    for(iTab = COM1; iTab < NUMPORTS; iTab++) {
        if(!WOW32_strcmp((LPCTSTR)PortTab[iTab].szPort, (LPCTSTR)szPort)) {
            return(iTab);
        }
    }

    return((UINT)IE_BADID);
}



BOOL InitDCB32(LPDCB pdcb32, LPSTR pszModeStr)
{
    BOOL   bRet = FALSE;
    LPSTR  pszParams;

    // eliminate "COMx:" from mode string leaving ptr to parameters string
    pszParams = StripPortName(pszModeStr);

    // if there are params...  (some apps pass "com1:" -- hence 2nd test)
    if(pszParams) {

        // initialize everything to 0 (especially the flags)
        RtlZeroMemory((PVOID)pdcb32, sizeof(DCB));

        // NOTE: 32-bit BuildCommDCB ONLY touches fields associated with psz1
        if(BuildCommDCB(pszParams, pdcb32)) {

            pdcb32->DCBlength = sizeof(DCB);

            // fill in specific fields a la Win3.1
            // NOTE: fields are 0 unless explicitly set
            pdcb32->fBinary     = 1;
            pdcb32->fDtrControl = DTR_CONTROL_ENABLE; //same as fDTRDisable == 0
            pdcb32->fRtsControl = RTS_CONTROL_ENABLE; //same as fRTSDisable == 0

            pdcb32->XonLim     = 10;
            pdcb32->XoffLim    = 10;
            pdcb32->XonChar    = 0x11;      // Ctrl-Q
            pdcb32->XoffChar   = 0x13;      // Ctrl-S

            bRet = TRUE;
        }
    }

    return(bRet);
}



VOID InitDEB16(PCOMDEB16 pComDEB16, UINT iTab, WORD QInSize, WORD QOutSize)
{
    VPVOID  vpBiosData;
    PWORD16 pwBiosData;

    // Win3.1 init's most the stuff to zero except as handled below
    RtlZeroMemory((PVOID)pComDEB16, sizeof(COMDEB16));

    // get the I/O base address for the port
    vpBiosData = (VPVOID)(RM_BIOS_DATA + (iTab * sizeof(WORD)));
    if(pwBiosData = (PWORD16)GetRModeVDMPointer(vpBiosData)) {
        pComDEB16->Port = (WORD)*pwBiosData;
        FREEVDMPTR(pwBiosData);
    }

    pComDEB16->RecvTrigger = (WORD)-1;
    pComDEB16->QInSize     = QInSize;
    pComDEB16->QOutSize    = QOutSize;

}

/* start thread for Modem interrupt emulation */
BOOL WOWStartModemIntThread(PWOWPORT pWOWPort)
{
    BOOL       ret = FALSE;
    DWORD      dwUnused;
    HANDLE     hEvent, hMiThread;

    // set up temporary semaphore to sync with Modem interrupt thread
    if((hEvent = CreateEvent(NULL, TRUE, FALSE, NULL)) == NULL) {
        goto ErrorExit0;
    }

    // use pWOWPort->hMiThread temporarily to help start the thread
    pWOWPort->hMiThread = hEvent;

    // create the MSR thread
    if((hMiThread = CreateThread(NULL,
                                 8192,
                                 (LPTHREAD_START_ROUTINE)WOWModemIntThread,
                                 (PWOWPORT)pWOWPort,
                                 0,
                                 (LPDWORD)&dwUnused))  == NULL) {
        goto ErrorExit1;
    }

    // block until thread notifies us that it has started
    WaitForSingleObject(hEvent, INFINITE);

    pWOWPort->hMiThread = hMiThread;

    CloseHandle(hEvent);
    ret = TRUE;

    goto FunctionExit;


// this is the error code path
ErrorExit1:
    CloseHandle(hEvent);

ErrorExit0:
    pWOWPort->hMiThread  = NULL;


FunctionExit:
#ifdef DEBUG
    if(!(ret)) {
        LOGDEBUG(0,("WOW::W32StartModemIntThread failed\n"));
    }
#endif
    return(ret);

}



// Modem Interrupt thread for SetCommEventMask/EnableCommNotification support
// Tries to emulate the interrupt handling in ibmint.asm of Win3.1 comm.drv.
// Our "interrupts" here are the events from the NT serial comm stuff
VOID WOWModemIntThread(PWOWPORT pWOWPort)
{
    BOOL       fRing     = FALSE;
    UINT       iTab;
    DWORD      dwRing;
    DWORD      dwEvts    = 0;
    DWORD      dwEvtOld  = 0;
    DWORD      dwEvtWord = 0;
    DWORD      dwMSR     = 0;
    DWORD      dwErrCode = 0;
    DWORD      cbTransfer;
    HANDLE     h32;
    PCOMDEB16  lpComDEB16;
    OVERLAPPED ol;

    iTab       = pWOWPort->idComDev;
    lpComDEB16 = pWOWPort->lpComDEB16;
    h32        = pWOWPort->h32;

    // set the current modem status & Event word
    lpComDEB16->MSRShadow    = (BYTE)0;
    lpComDEB16->EvtWord      = (WORD)0;
    lpComDEB16->ComErr       = (WORD)0;
    lpComDEB16->QInCount     = (WORD)0;
    lpComDEB16->QOutCount    = (WORD)0;

    if(VALIDLPT(iTab)) {
        iTab = GETLPTID(iTab);
    }

    ol.Internal     = 0;
    ol.InternalHigh = 0;
    ol.Offset       = 0;
    ol.OffsetHigh   = 0;
    ol.hEvent       = CreateEvent(NULL,
                                  TRUE,
                                  FALSE,
                                  (LPTSTR)PortTab[iTab].szPort);

    // activate modem events in the mask, we want to emulate all the interrupts
    SetCommMask(h32, EV_NTEVENTS);

    // initialize the shadow MSR
    GetCommModemStatus(h32, &dwMSR);
    dwMSR &= MSR_STATEONLY;
    lpComDEB16->MSRShadow = LOBYTE(LOWORD(dwMSR));

    // wake up the thread that created this thread in WOWStartModemIntThread()
    SetEvent(pWOWPort->hMiThread);

    while(!pWOWPort->fClose) {

        // wait for an event - hopefully this will be somewhat similar to
        // the TimerProc in ibmint.asm which gets called every 100ms
        if(!WaitCommEvent(h32, &dwEvts, &ol)) {

            if(GetLastError() == ERROR_IO_PENDING) {

                // ...block here 'til event specified in WaitCommEvent() occurs
                if(!GetOverlappedResult(h32, &ol, &cbTransfer, TRUE)) {
                    LOGDEBUG(0, ("WOW::WUCOMM: WOWModemIntThread: Wait failed\n"));
                }
            }
            else {
                LOGDEBUG(0, ("WOW::WUCOMM: WOWModemIntThread : Overlap failed\n"));
            }
        }
        ResetEvent(ol.hEvent);

        // Get current MSR state, current state of delta bits isn't accurate for us
        GetCommModemStatus(h32, &dwMSR);

        dwMSR &= MSR_STATEONLY;  // throw away delta bits


        // set the DELTA bits in the shadow MSR
        if(dwEvts & EV_CTS)  dwMSR |= MSR_DCTS;

        if(dwEvts & EV_DSR)  dwMSR |= MSR_DDSR;

        if(dwEvts & EV_RLSD) dwMSR |= MSR_DDCD;

        if(dwEvts & EV_RING) {
            fRing      = TRUE;
            dwRing = EV_RING;
        }
        else if(fRing) {
            fRing      = FALSE;
            dwMSR     |= MSR_TERI;
            dwRing = EV_RingTe;
        }
        else {
            dwRing = 0;
        }

        // Form the events
        dwEvtOld  = (DWORD)lpComDEB16->EvtWord;
        dwEvtWord = 0;
        dwEvtWord = dwRing | (dwEvts & (EV_ERR | EV_BREAK | EV_RXCHAR | EV_TXEMPTY | EV_CTS | EV_DSR | EV_RLSD | EV_RXFLAG));

        // we have to figure the state bits out from the MSR

        if(dwMSR & MS_CTS_ON) dwEvtWord |= EV_CTSS;
        if(dwMSR & MS_DSR_ON) dwEvtWord |= EV_DSRS;
        if(dwMSR & MS_RLSD_ON) dwEvtWord |= EV_RLSDS;

        // One of the major tasks of this routine is to update the MSRShadow
        // and EvtWord in the COMDEB16 structure.
        //

        //apply the msr as well
        lpComDEB16->MSRShadow = LOBYTE(LOWORD(dwMSR));

        // apply the event mask the app specified
        lpComDEB16->EvtWord |= LOWORD(dwEvtWord) & lpComDEB16->EvtMask;

        // The following code simluates the COM Notifcation functionality of
        // Win 3.1.
        //
        // Notifications:
        //
        // if they want receive transmit notification & it's time to notify
        // if there wasn't an Rx overflow continue...

        if( lpComDEB16->NotifyHandle ) {

            // get current error code & queue counts
            WOWGetCommError(pWOWPort);

            if((dwEvtWord & ( EV_RXCHAR | EV_RXFLAG )) &&
               !(pWOWPort->dwErrCode & CE_RXOVER)) {

                // if they want receive notification & it's time to notify
                // apps should set RecvTrigger to -1 if they don't want notification
                if((((SHORT)lpComDEB16->RecvTrigger) != -1) &&
                   (lpComDEB16->QInCount >= lpComDEB16->RecvTrigger)) {

                    // if the app hasn't already been notified of this ...
                    if(!(lpComDEB16->NotifyFlags & CN_RECEIVEHI)) {

                        PostMessage(HWND32(lpComDEB16->NotifyHandle),
                                    WOW_WM_COMMNOTIFY,
                                    MAKEWPARAM((WORD)pWOWPort->idComDev, 0),
                                    MAKELPARAM(CN_RECEIVE, 0));

                        lpComDEB16->NotifyFlags |= CN_RECEIVEHI;
                    }
                }
                else {
                    lpComDEB16->NotifyFlags &= ~CN_RECEIVEHI;
                }
            }

            // if they want receive transmit notification & it's time to notify
            if(lpComDEB16->QOutCount < (SHORT)lpComDEB16->SendTrigger) {

                // if the app hasn't already been notified of this ...
                if(!(lpComDEB16->NotifyFlags & CN_TRANSMITHI)) {

                    PostMessage(HWND32(lpComDEB16->NotifyHandle),
                                WOW_WM_COMMNOTIFY,
                                MAKEWPARAM((WORD)pWOWPort->idComDev, 0),
                                MAKELPARAM(CN_TRANSMIT, 0));

                    lpComDEB16->NotifyFlags |= CN_TRANSMITHI;
                }
            }
            else {
                lpComDEB16->NotifyFlags &= ~CN_TRANSMITHI;
            }

            // if we are notifying the app of EV_ event's
            if((lpComDEB16->NotifyFlags & CN_NOTIFYHI) &&
               ((DWORD)lpComDEB16->EvtWord != dwEvtOld)) {

                PostMessage(HWND32(lpComDEB16->NotifyHandle),
                            WOW_WM_COMMNOTIFY,
                            MAKEWPARAM((WORD)pWOWPort->idComDev, 0),
                            MAKELPARAM(CN_EVENT, 0));
            }

            // Now that we've processed all the interrupts, do the TimerProc.
            // if we are notifying the app of anything in Rx queue
            // this mimics the notification in the TimerProc (see ibmint.asm)
            if(((SHORT)lpComDEB16->RecvTrigger != -1) &&
                    (lpComDEB16->QInCount != 0)            &&
                    (!(lpComDEB16->NotifyFlags & CN_RECEIVEHI))) {

                PostMessage(HWND32(lpComDEB16->NotifyHandle),
                            WOW_WM_COMMNOTIFY,
                            MAKEWPARAM((WORD)pWOWPort->idComDev, 0),
                            MAKELPARAM(CN_RECEIVE, 0));

                lpComDEB16->NotifyFlags |= CN_RECEIVEHI;
            }
        }

        // we've handled all interrupts, give control back to app
        Sleep(0);

    } // end thread loop

    CloseHandle(ol.hEvent);

    ExitThread(0);
}



DWORD WOWGetCommError(PWOWPORT pwp)
{
    COMSTAT  cs;
    DWORD    dwErr;

    ClearCommError(pwp->h32, &dwErr, &cs);

    EnterCriticalSection(&pwp->csWrite);

    //
    // We do our own write buffering so we ignore
    // the cbOutQue returned by ClearCommError, which
    // only reflects pending writes.
    //
    // Number of bytes in our write queue is calculated
    // using the size of the queue and the amount free
    // in the queue, minus one.  Minus one because
    // there's one slot in the queue which is never used.
    //

    cs.cbOutQue = (pwp->cbWriteBuf - pwp->cbWriteFree) - 1;


    LeaveCriticalSection(&pwp->csWrite);


    // always update the status & preserve any error condition
    pwp->cs                  = cs;
    pwp->dwErrCode          |= dwErr;
    pwp->lpComDEB16->ComErr |= LOWORD(dwErr);

    // always update the queue counts in the DEB
    pwp->lpComDEB16->QInCount  = LOWORD(cs.cbInQue);
    pwp->lpComDEB16->QOutCount = LOWORD(cs.cbOutQue);

    return(dwErr);
}



/* for hung/crashed app support */
VOID FreeCommSupportResources(DWORD dwThreadID)
{
    UINT     iTab;
    PWOWPORT pWOWPort;

    for(iTab = 0; iTab < NUMPORTS; iTab++) {
        if(pWOWPort = PortTab[iTab].pWOWPort) {
            if(pWOWPort->dwThreadID == dwThreadID) {
                DeletePortTabEntry(pWOWPort);
                break;
            }
        }
    }
}



/* functions exported to the VDM */
/* NOTE: idComDev: COM1 == 0, LPT1 == 0x80 */
BYTE GetCommShadowMSR(WORD idComDev)
{
    BYTE      MSR=0;
    DWORD     dwModemStatus;
    PWOWPORT  pWOWPort;

    if (pWOWPort = GETPWOWPTR (idComDev)) {

        if(pWOWPort->hMiThread) {
            MSR = (BYTE)pWOWPort->lpComDEB16->MSRShadow;
        }

        // get it the slow way if SetCommEventMask() hasn't been called
        else if ( GetCommModemStatus(pWOWPort->h32, &dwModemStatus) ) {              
            MSR = (BYTE)LOBYTE(LOWORD(dwModemStatus));
        }
    }

    return(MSR);
}



/* NOTE: idComDev: COM1 == 0, LPT1 == 0x80 */
HANDLE GetCommHandle(WORD idComDev)
{
    PWOWPORT  pWOWPort;

    if (pWOWPort = GETPWOWPTR (idComDev)) {
        return(pWOWPort->h32);
    }

    else {
        return(NULL); // will return NULL if bad range of idComDev or if the
    }                 // port wasn't initialized through an OpenComm() API call
}



BOOL IsQLinkGold(WORD wTDB)
{
   PTDB  pTDB;

   pTDB = (PVOID)SEGPTR(wTDB,0);
   if(WOW32_stricmp(pTDB->TDB_ModName, "QLGOLD")) {
       return(FALSE);
   }

   return(TRUE);
}


//
// EnqueueCommWrite - stuff characters into the Comm Write queue
//                    assoicated with pWOWPort.
//
// Returns number of characters queued.
//
// This function takes care of entering/leaving the critsec.
//

USHORT EnqueueCommWrite(PWOWPORT pwp, PUCHAR pch, USHORT cb)
{
    USHORT cbWritten = 0;
    USHORT cbToCopy;
    USHORT cbChunk;
    BOOL   fQueueEmpty;
    BOOL   fDelay = FALSE;

    // WinFax Lite 3 calls WriteFile("AT+FCLASS=1") to set the modem to fax mode
    // when it is recieving a fax.  Some modems appear to be slow to repsond
    // with the "OK" string (especially since we enqueue the "AT+FCLASS=1" write
    // and then write it in overlapped mode) -- so, when we tell the app we sent
    // it, it then follows with a "ATA" string without waiting for the modem's
    // response to the previous command.  This confuses several different modems
    // and so they never answer.  This mechanism allows us to synchronize the
    // "AT+FCLASS=1" command so that it works more like Win3.1.  See bug #9479
    if(cb == 12) {

        // Handy way to say:
        // if(pch[0]=='A' && pch[1]=='T' && pch[2]=='+' && pch[3]=='F') {
        if((*(DWORD *)pch) == 0x462b5441) {

            // if(pch[0]=='C' && pch[1]=='L' && pch[2]=='A' && pch[3]=='S') {
            if((*(DWORD *)(pch+sizeof(DWORD))) == 0x53414c43) {

                // if(pch[0]=='S' && pch[1]=='=') {
                if((*(WORD *)(pch+(2*sizeof(DWORD)))) == 0x3D53) {
                    fDelay = TRUE;
    }   }   }   }

    EnterCriticalSection(&pwp->csWrite);

    fQueueEmpty = (pwp->pchWriteHead == pwp->pchWriteTail);

    //
    // cbToCopy is the total number of bytes that we are going to enqueue
    //

    cbToCopy = min(cb, pwp->cbWriteFree);

    //
    // Any write can be accomplished in at most two chunks.
    // The first writes up until the buffer wraps, while
    // the second starts at the beginning of the buffer.
    //
    // Do the first half, which may do it all.
    //
    // Number of bytes for the first chunk is the smaller of
    // the total number of bytes free in the write buffer and
    // the number of bytes free before the end of the buffer.
    //

    cbChunk = min(cbToCopy,
                  (pwp->pchWriteBuf + pwp->cbWriteBuf) - pwp->pchWriteTail);

    RtlCopyMemory(pwp->pchWriteTail, pch, cbChunk);
    pwp->cbWriteFree -= cbChunk;
    pwp->pchWriteTail += cbChunk;
    cbWritten += cbChunk;

    //
    // Tail pointer may have moved to point just beyond the buffer.
    //

    if (pwp->pchWriteTail >= pwp->pchWriteBuf + pwp->cbWriteBuf) {
        WOW32ASSERT(pwp->pchWriteTail == pwp->pchWriteBuf + pwp->cbWriteBuf);
        pwp->pchWriteTail = pwp->pchWriteBuf;
    }

    //
    // Are we done?
    //

    if (cbWritten < cbToCopy) {

        //
        // I think this case should only be taken when we've wrapped, so
        // be sure.
        //
        WOW32ASSERT(pwp->pchWriteTail == pwp->pchWriteBuf);

        //
        // Nope, do the second half.
        //

        cbChunk = min((cbToCopy - cbWritten), pwp->cbWriteFree);

        RtlCopyMemory(pwp->pchWriteTail, pch + cbWritten, cbChunk);
        pwp->cbWriteFree -= cbChunk;
        pwp->pchWriteTail += cbChunk;
        cbWritten += cbChunk;

        WOW32ASSERT(pwp->pchWriteTail < pwp->pchWriteBuf + pwp->cbWriteBuf);

    }


    //
    // If the buffer was empty to start with and we made it
    // non-empty, issue the first WriteFile and signal the
    // writer thread to wake up.
    //

    if (fQueueEmpty && cbWritten) {

        pwp->cbWritePending = CALC_COMM_WRITE_SIZE(pwp);

        if (!WriteFile(pwp->h32, pwp->pchWriteHead, pwp->cbWritePending,
                       &pwp->cbWritten, &pwp->olWrite)) {

            if (ERROR_IO_PENDING == GetLastError()) {
                pwp->fWriteDone = FALSE;
            } else {
                pwp->fWriteDone = TRUE;
                LOGDEBUG(0, ("WOW EnqueueCommWrite: WriteFile to id %u fails (error %u)\n",
                             pwp->idComDev, GetLastError()));
            }

        } else {
            pwp->fWriteDone = TRUE;
        }

        //
        // Leave the critical section before setting the event.  Otherwise
        // the other thread could wake up when the event is set and immediately
        // block on the critical section.
        //

        LeaveCriticalSection(&pwp->csWrite);

        // avoid setting the event twice
        if(!fDelay) {
            SetEvent(pwp->hWriteEvent);
        }

    } else {
        LeaveCriticalSection(&pwp->csWrite);
    }

    // this gives the writer thread a chance to write out "AT+FCLASS=1" strings
    if(fDelay) {
        SetEvent(pwp->hWriteEvent);
        Sleep(1000);
    }

    return cbWritten;
}



//
// WOWCommWriteThread created for COM ports only.  This thread dequeues
// characters from the write buffer and writes them to the COM port.
// This thread uses pwp->hWriteEvent for two purposes:
//
//   1. The event is signalled by EnqueueCommWrite when the write
//      buffer had been empty but is not now.  This wakes us up
//      so we can write to the port.  Note that we will always
//      be in the WaitForSingleObject at the top of the function
//      in this case, since that's where we sleep when the buffer
//      is empty.
//
//   2. DeletePortTabEntry signals the event after setting
//      pwp->fClose to tell us the port is closing and we
//      need to clean up and terminate this thread.  This
//      thread might be doing anything in this case, but
//      it is careful to check pwp->fClose before sleeping
//      again.
//
//   3. wu32FlushComm() signals the event and marks the queue empty

ULONG WOWCommWriterThread(LPVOID pWOWPortStruct)
{
    PWOWPORT   pwp = (PWOWPORT)pWOWPortStruct;
    HANDLE     ah[2];

    //
    // Copy event handles into array for WaitForMultipleObjects.
    //

    ah[0] = pwp->hWriteEvent;
    ah[1] = pwp->olWrite.hEvent;

WaitForWriteOrder:

    //
    // pwp->fClose is TRUE when the port is closed.
    //

    while (!pwp->fClose) {

        //
        // First wait for something to be written to the buffer.
        //

        WaitForSingleObject(pwp->hWriteEvent, INFINITE);

        //
        // Critical section protects write buffer.
        //

        EnterCriticalSection(&pwp->csWrite);

        //
        // The buffer is empty when head == tail.
        //

        while (pwp->pchWriteHead != pwp->pchWriteTail) {

            //
            // pwp->cbWritePending will be nonzero if
            // the application thread queued a write to
            // an empty buffer and then issued the first
            // WriteFile call.
            //

            if (pwp->cbWritePending) {
                if (!pwp->fWriteDone) {
                    LeaveCriticalSection(&pwp->csWrite);
                    goto WaitForWriteCompletion;
                } else {
                    goto CleanupAfterWriteComplete;
                }
            }

            pwp->cbWritePending = CALC_COMM_WRITE_SIZE(pwp);

            //
            // Leave the critical section before writing.  This is
            // safe because the app thread doesn't change the
            // head pointer.   (Not true if wu32FlushComm was called)
            //

            LeaveCriticalSection(&pwp->csWrite);

            if (!WriteFile(pwp->h32, pwp->pchWriteHead, pwp->cbWritePending,
                           &pwp->cbWritten, &pwp->olWrite)) {

                if (ERROR_IO_PENDING == GetLastError() ) {

WaitForWriteCompletion:
                    //
                    // Wait for the write to complete or for us to
                    // be alerted that the port is closing.
                    //

                    while (WAIT_OBJECT_0 == WaitForMultipleObjects(2, ah, FALSE, INFINITE)) {

                        //
                        // pwp->hWriteEvent was signaled.  This probably
                        // means that the port was closed.
                        //

                        if (pwp->fClose) {
                            goto PortClosed;
                        }
                    }

                    if (GetOverlappedResult(pwp->h32,
                                             &pwp->olWrite,
                                             &pwp->cbWritten,
                                             TRUE
                                             ) )
                       {
                        goto WriteSuccess;
                     }
                }


                LOGDEBUG(0, ("WOWCommWriterThread: WriteFile to id %u fails (error %u)\n",
                             pwp->idComDev, GetLastError()));
                pwp->cbWritePending = 0;
                goto WaitForWriteOrder;

            }


WriteSuccess:

            //
            // Update head pointer to reflect portion written.
            //

            EnterCriticalSection(&pwp->csWrite);

CleanupAfterWriteComplete:
            WOW32ASSERT(pwp->cbWritten == (WORD)pwp->cbWritten);

            pwp->pchWriteHead += pwp->cbWritten;
            pwp->cbWriteFree += (WORD)pwp->cbWritten;
            pwp->cbWritePending = 0;

            //
            // The following is a sanity check on our buffer manipulations.
            //

#ifdef DEBUG
            if (pwp->pchWriteHead >= pwp->pchWriteBuf + pwp->cbWriteBuf) {
                WOW32ASSERT(pwp->pchWriteHead == pwp->pchWriteBuf + pwp->cbWriteBuf);
            }
#endif

            if (pwp->pchWriteHead == pwp->pchWriteBuf + pwp->cbWriteBuf) {
                pwp->pchWriteHead = pwp->pchWriteBuf;
            }
        }

        //
        // We have exhausted the write buffer, leave the critical section
        // and loop back to the wait for the buffer to become non-empty.
        //

        LeaveCriticalSection(&pwp->csWrite);
    }

PortClosed:
    CloseHandle(pwp->olWrite.hEvent);

    return 0;
}



// Checks status on RLSD, CTS, and DSR for timeout support
// see MSRWait() in win3.1 comm.drv code
BOOL MSRWait(PWOWPORT pwp)
{
    DWORD dwStartTime, dwElapsedTime, dwLineStatus;
    DWORD dwErr = 0;


    // start the timeout clock (returns msec)
    dwStartTime = GetTickCount();

    // loop until either all lines are high or a timeout occurs
    while(!dwErr) {

        // get the current status of the lines
        if ( !GetCommModemStatus(pwp->h32, &dwLineStatus) ) {
             //can't rely on third party drivers not to mess with dwLineStatus on failure
             dwLineStatus = 0;
        }

        // if all the required lines are up -- we're done
        if((pwp->lpComDEB16->MSRMask & LOBYTE(dwLineStatus)) == pwp->lpComDEB16->MSRMask)
            break;

        // get the elapsed time
        dwElapsedTime = GetTickCount() - dwStartTime;

        if(pwp->RLSDTimeout != IGNORE_TIMEOUT) {
            // if line is low
            if(!(dwLineStatus & MS_RLSD_ON)) {
                if(dwElapsedTime > UINT32(pwp->RLSDTimeout))
                    dwErr |= CE_RLSDTO;
            }
        }

        if(pwp->CTSTimeout != IGNORE_TIMEOUT) {
            // if line is low
            if(!(dwLineStatus & MS_CTS_ON)) {
                if(dwElapsedTime > UINT32(pwp->CTSTimeout))
                    dwErr |= CE_CTSTO;
            }
        }

        if(pwp->DSRTimeout != IGNORE_TIMEOUT) {
            // if line is low
            if(!(dwLineStatus & MS_DSR_ON)) {
                if(dwElapsedTime > UINT32(pwp->DSRTimeout))
                    dwErr |= CE_DSRTO;
            }
        }
    }

    pwp->dwErrCode |= dwErr;
    pwp->lpComDEB16->ComErr |= LOWORD(dwErr);

    if(dwErr)
       return(TRUE);
    else
       return(FALSE);

}
