/*****************************************************************************
 * Copyright (C) Cinterion Wireless Modules GmbH
 *****************************************************************************
 * Project: CE-MUX-Driver
 *****************************************************************************
 * Author : Siemens AG Berlin COM WM RD SW1
 *****************************************************************************
 * $Workfile: O:/ctna0450_RIL/z_ms-d/src/working/Classic/MuxDrv/BufferedParentPort.cpp $
 * $Revision: /main/ms-d/48 $
 * $Date: 2008-10-17 12:04:45 $
 * $Author: ctna0450 $
 *****************************************************************************
 * Target:
 *  Microsoft Windows CE 4.2 and above
 *****************************************************************************
 * Description:
 *  Cinterion Multiplex Driver buffered parent port implementation
 *****************************************************************************
 *
 * $Log: O:/ctna0450_RIL/z_ms-d/src/working/Classic/MuxDrv/BufferedParentPort.cpp$
 *
*/

//////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2009 Cinterion Wireless Modules GmbH
//
// Version: 1.0
// Date:    dummy
//
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
//
// BufferedParentPort.cpp
//
// This file contains the implementation of the class "CBufferedParentPort"
// handling.
// The mutliplex protocol implementation requires a buffering of the data
// read from and written to the physical COM port. Unfortunately there do
// exist UART drivers without internal buffer handling using only the
// hardware fifos of the UARTs. For these rare cases the Cinterion multiplex
// driver provides an additional virtual COM port abstracting access to the
// physical COM port implementing its own data buffering. So in these cases
// where a serial driver of a physical COM port doesn't provide data
// buffering the parent port of the multiplex instance must be connected to
// an internal bufferd port instance and that instance must be connected to
// the physical COM port.
// This code is experimental and hadn't been put into comprehensive testing.
// It should therefore only be used for test purposes.
//
//////////////////////////////////////////////////////////////////////////////

#include "precomp.h"

//////////////////////////////////////////////////////////////////////////////
//
// Public interface
//
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
//
// Constructor
//
// Parameters:
// szRegKey: Registry path of port instance section.
//
//////////////////////////////////////////////////////////////////////////////
CBufferedParentPort::CBufferedParentPort(LPCTSTR szRegKey) {
  m_hPort = INVALID_HANDLE_VALUE;
  m_dwOpenCount = 0;
  m_hThreadReadData = NULL;
  m_hThreadWriteData = NULL;
  m_hThreadWaitEvent = NULL;
  m_hCloseEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
  m_dwDefaultThreadPrio = MUX_DEFAULT_TASK_PRIORITY;

  memset(m_szRegKey, 0 , sizeof(m_szRegKey));
  if (szRegKey) _tcsncpy(m_szRegKey, szRegKey, MAX_PATH);
}

//////////////////////////////////////////////////////////////////////////////
//
// Destructor
//
//////////////////////////////////////////////////////////////////////////////
CBufferedParentPort::~CBufferedParentPort() {
  // Close all existing instances
  Close(0);
  CloseHandle(m_hCloseEvent);
}

//////////////////////////////////////////////////////////////////////////////
//
// Open a virtual port device.
//
// Parameters:
// pInstance: This pointer of port instance.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CBufferedParentPort::Open(LPVOID pInstance) {
TCHAR szBasePort[16];
DWORD  i;

  MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: CBufferedParentPort::Open() instance count %d\r\n"), m_dwOpenCount + 1));

  // No open possible without missing resources
  if (!m_hCloseEvent) {
    return FALSE;
  }

  // Check if we are not already open
  if (m_dwOpenCount) {
    m_dwOpenCount++;
    m_WaitCommEvent.AddInstance(pInstance);
    return TRUE;
  }

  // Initialize read and write buffer buffer
  if (!m_ReadBuffer.Resize(DEFAULT_BUFFER_SIZE) || !m_WriteBuffer.Resize(DEFAULT_BUFFER_SIZE)) {
    return FALSE;
  }

  // Read parent port from registry
  if (!RegGetDeviceString(m_szRegKey, REGVAL_MUXDRV_BASEPORT, szBasePort, sizeof(szBasePort))) {
    return FALSE;
  }

  // Open the parent port
  MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: CBufferedParentPort::Open() -> Opening parent port %s\r\n"), szBasePort));
  for (i = 0; (i < BUFPORT_REOPENS) && (m_hPort == INVALID_HANDLE_VALUE); i++) {
    m_hPort = CreateFile(szBasePort, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
    if (m_hPort == INVALID_HANDLE_VALUE) {
      Sleep(BUFPORT_REOPEN_DELAY);
    }
  }
  if (m_hPort == INVALID_HANDLE_VALUE) {
    MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: CBufferedParentPort::Open() -> Failed to open parent port!\r\n")));
    return FALSE;
  }

  // Set timeouts to our internal requirements
  m_Timeouts.ReadIntervalTimeout          = MAXDWORD;
  m_Timeouts.ReadTotalTimeoutMultiplier   = MAXDWORD;
  m_Timeouts.ReadTotalTimeoutConstant     = MAXDWORD - 1;
  m_Timeouts.WriteTotalTimeoutMultiplier  = 0;
  m_Timeouts.WriteTotalTimeoutConstant    = 3000;
  if (!SetCommTimeouts(m_hPort, &m_Timeouts)) {
    CloseHandle(m_hPort);
    m_hPort = INVALID_HANDLE_VALUE;
    return FALSE;
  }

  // Setup com mask we handle EV_RXCHAR & EV_TXEMPTY internally, the rest is simply passed through.
  if (!SetCommMask(m_hPort, BUFPORT_COM_MASK)) {
    CloseHandle(m_hPort);
    m_hPort = INVALID_HANDLE_VALUE;
    return FALSE;
  }

  // Reset our internal notification events
  ResetEvent(m_hCloseEvent);

  // Increase counter for open instances
  m_dwOpenCount++;

  // Start read and write thread
  m_hThreadWriteData = CreateThread(NULL, 0, ThreadWriteData, this, 0, NULL);
  m_hThreadReadData = CreateThread(NULL, 0, ThreadReadData, this, 0, NULL);
  m_hThreadWaitEvent = CreateThread(NULL, 0, ThreadWaitEvent, this, 0, NULL);
  if (!m_hThreadWriteData || !m_hThreadReadData || !m_hThreadWaitEvent) {
    Close(0);
    return FALSE;
  }

  // Apply task priorities, if defined in the registry.
  if (!RegGetDeviceDWORD(m_szRegKey, REGVAL_MUXDRV_TASKPRIORITY, &m_dwDefaultThreadPrio)) {
    m_dwDefaultThreadPrio = MUX_DEFAULT_TASK_PRIORITY;
  }
  if (m_dwDefaultThreadPrio > 0) {
    CeSetThreadPriority(m_hThreadWriteData, m_dwDefaultThreadPrio);
    CeSetThreadPriority(m_hThreadReadData, m_dwDefaultThreadPrio);
    CeSetThreadPriority(m_hThreadWaitEvent, m_dwDefaultThreadPrio);
  }

  m_WaitCommEvent.AddInstance(pInstance);

  return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Close a virtual port device.
//
// Parameters:
// pInstance: This pointer of port instance.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CBufferedParentPort::Close(LPVOID pInstance) {
  MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: CBufferedParentPort::Close() instance count %d\r\n"), m_dwOpenCount));

  // Check if we are really open
  if (!m_dwOpenCount) {
    return FALSE;
  }

  // Release pending WaitCommEvent() calls
  m_WaitCommEvent.RemoveInstance(pInstance);

  // Tidy up only if the last instance is closed
  if (!pInstance || (m_dwOpenCount == 1)) {
    // Signal the close event and abort all pending waits
    SetEvent(m_hCloseEvent);

    // Clear ring buffers
    m_ReadBuffer.Resize(0);
    m_WriteBuffer.Resize(0);

    // Close parent port
    if (m_hPort != INVALID_HANDLE_VALUE) {
      SetCommMask(m_hPort, 0);
      CloseHandle(m_hPort);
      m_hPort = INVALID_HANDLE_VALUE;
    }

    // Stop the read data thread
    if (m_hThreadReadData) {
      WaitForSingleObject(m_hThreadReadData, 5000);
      CloseHandle(m_hThreadReadData);
      m_hThreadReadData = NULL;
    }

    // Stop the write data thread
    if (m_hThreadWriteData) {
      WaitForSingleObject(m_hThreadWriteData, 5000);
      CloseHandle(m_hThreadWriteData);
      m_hThreadWriteData = NULL;
    }

    // Stop the wait event thread
    if (m_hThreadWaitEvent) {
      WaitForSingleObject(m_hThreadWaitEvent, 5000);
      CloseHandle(m_hThreadWaitEvent);
      m_hThreadWaitEvent = NULL;
    }

    m_dwOpenCount = 0;
    return TRUE;
  }

  m_dwOpenCount--;

  return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Read data from the virtual port.
//
// Parameters:
// pData: The buffer receiving the data.
// dwLen: The max. number of bytes to read.
//
// Return:
// The number of actually read bytes, -1 indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
DWORD CBufferedParentPort::ReadData(PBYTE pData, DWORD dwLen) {
DWORD dwWaitTime;
DWORD dwTotalTimeout;
DWORD dwTime;
DWORD dwRead;
DWORD dwRet;

  if ((m_Timeouts.ReadIntervalTimeout == MAXDWORD) && !m_Timeouts.ReadTotalTimeoutConstant && !m_Timeouts.ReadTotalTimeoutMultiplier) {
    // Documented special handling:
    // A value of MAXDWORD for ReadIntervalTimeout, combined with zero values for both the
    // ReadTotalTimeoutConstant and ReadTotalTimeoutMultiplier members, specifies that the
    // read operation is to return immediately with the characters that have already been
    // received, even if no characters have been received.
    return m_ReadBuffer.Read(pData, dwLen);
  } else if ((m_Timeouts.ReadIntervalTimeout == MAXDWORD) && (m_Timeouts.ReadTotalTimeoutMultiplier == MAXDWORD) &&
             (m_Timeouts.ReadTotalTimeoutConstant != MAXDWORD) && m_Timeouts.ReadTotalTimeoutConstant) {
    // Documented special handling:
    // If an application sets ReadIntervalTimeout and ReadTotalTimeoutMultiplier to
    // MAXDWORD and sets ReadTotalTimeoutConstant to a value greater than zero and
    // less than MAXDWORD, one of the following occurs when the ReadFile function
    // is called:
    //  - If there are any characters in the input buffer, ReadFile returns
    //    immediately with the characters in the buffer.
    //  - If there are no characters in the input buffer, ReadFile waits until a
    //    character arrives and then returns immediately.
    //  - If no characters arrive within the time specified by
    //    ReadTotalTimeoutConstant, ReadFile times out.
    if (m_ReadBuffer.WaitForData(m_Timeouts.ReadTotalTimeoutConstant) == (DWORD)-1) {
      return (DWORD)-1;
    }
    return m_ReadBuffer.Read(pData, dwLen);
  }

  // We've catched the special cases, now we can calculate the given timouts
  if ((m_Timeouts.ReadTotalTimeoutConstant == MAXDWORD) || (m_Timeouts.ReadTotalTimeoutMultiplier == MAXDWORD)) {
    dwTotalTimeout = INFINITE;
  } else {
    dwTotalTimeout = (m_Timeouts.ReadTotalTimeoutMultiplier * dwLen) + m_Timeouts.ReadTotalTimeoutConstant;
  }
  dwWaitTime = m_Timeouts.ReadIntervalTimeout;
  if (!dwWaitTime) {
    dwWaitTime = dwTotalTimeout;
  }

  dwRet = 0;
  while (dwLen && dwTotalTimeout) {
    dwTime = m_ReadBuffer.WaitForData(dwWaitTime);
    if (dwTime == (DWORD)-1) return (DWORD)-1;
    dwRead = m_ReadBuffer.Read(pData, dwLen);
    if (dwRead == (DWORD)-1) return (DWORD)-1;
    if (!dwRead) return dwRet;
    if (dwTotalTimeout != INFINITE) {
      if (dwTime < dwTotalTimeout) {
        dwTotalTimeout = dwTotalTimeout - dwTime;
      } else {
        dwTotalTimeout = 0;
      }
    }
    dwLen = dwLen - dwRead;
    pData += dwRead;
    dwRet += dwRead;
  }

  return dwRet;
}

//////////////////////////////////////////////////////////////////////////////
//
// Write data to the virtual port.
//
// Parameters:
// pData: The buffer receiving the data.
// dwLen: The max. number of bytes to read.
//
// Return:
// The number of actually read bytes, -1 indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
DWORD CBufferedParentPort::WriteData(PBYTE pData, DWORD dwLen) {
DWORD dwWaitTime;
DWORD dwTime;
DWORD dwWritten;
DWORD dwRet;

  dwWaitTime = (m_Timeouts.WriteTotalTimeoutMultiplier * dwLen) + m_Timeouts.WriteTotalTimeoutConstant;
  if (!dwWaitTime) {
    dwWaitTime = INFINITE;
  }
  dwRet = 0;
  while (dwLen && dwWaitTime) {
    dwWritten = m_WriteBuffer.Write(pData, dwLen);
    if (dwWritten == (DWORD)-1) return (DWORD)-1;
    dwLen = dwLen - dwWritten;
    pData += dwWritten;
    dwRet += dwWritten;
    if (dwLen) {
      dwTime = m_WriteBuffer.WaitForSpace(dwWaitTime);
      if (dwTime == (DWORD)-1) return (DWORD)-1;
      if (dwWaitTime != INFINITE) {
        dwWaitTime = dwWaitTime - dwTime;
      }
    }
  }
  return dwRet;
}

//////////////////////////////////////////////////////////////////////////////
//
// DeviceIoControl function of the virtual port.
//
// Parameters:
// pInstance   : This pointer of port instance.
// hOpenContext: Handle to the open context of the device. The BPP_Open
//               function creates and returns this identifier.
// dwCode      : Specifies a value indicating the I/O control operation to
//               perform. These codes are device-specific and are usually
//               exposed to programmers by means of a header file.
// pBufIn      : Pointer to the buffer containing data to be transferred to
//               the device.
// dwLenIn     : Specifies the number of bytes of data in the buffer specified
//               for pBufIn.
// pBufOut     : Pointer to the buffer used to transfer the output data from
//               the device.
// dwLenOut    : Specifies the maximum number of bytes in the buffer specified
//               by pBufOut.
// pdwActualOut: Pointer to the DWORD buffer that this function uses to return
//               the actual number of bytes received from the device.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CBufferedParentPort::IoControl(LPVOID pInstance, DWORD dwCode, PBYTE pBufIn, DWORD dwLenIn, PBYTE pBufOut, DWORD dwLenOut, PDWORD pdwActualOut) {
BOOL fRet = TRUE;

  switch(dwCode) {
    case IOCTL_SERIAL_GET_COMMSTATUS:
      if ((dwLenOut >= sizeof(SERIAL_DEV_STATUS)) && pBufOut) {
        fRet = DeviceIoControl(m_hPort, dwCode, pBufIn, dwLenIn, pBufOut, dwLenOut, pdwActualOut, NULL);
        if (fRet) {
          ((PSERIAL_DEV_STATUS)pBufOut)->ComStat.cbInQue   = m_ReadBuffer.GetLevel();
          ((PSERIAL_DEV_STATUS)pBufOut)->ComStat.cbOutQue  = m_WriteBuffer.GetLevel();
        }
        *pdwActualOut = sizeof(SERIAL_DEV_STATUS);
      } else {
        SetLastError(ERROR_INVALID_PARAMETER);
        fRet = FALSE;
      }
      break;

    case IOCTL_SERIAL_GET_PROPERTIES:
      if ((dwLenOut >= sizeof(COMMPROP)) && pBufOut) {
        fRet = DeviceIoControl(m_hPort, dwCode, pBufIn, dwLenIn, pBufOut, dwLenOut, pdwActualOut, NULL);
        if (fRet) {
          ((LPCOMMPROP)pBufOut)->dwMaxTxQueue = 0;
          ((LPCOMMPROP)pBufOut)->dwMaxRxQueue = 0;
          ((LPCOMMPROP)pBufOut)->dwCurrentTxQueue = m_WriteBuffer.GetSize();
          ((LPCOMMPROP)pBufOut)->dwCurrentRxQueue = m_ReadBuffer.GetSize();
        }
        *pdwActualOut = sizeof(COMMPROP);
      } else {
        SetLastError(ERROR_INVALID_PARAMETER);
        fRet = FALSE;
      }
      break;

    case IOCTL_SERIAL_PURGE:
      if (pBufIn && (dwLenIn >= sizeof(DWORD))) {
        fRet = Purge(*((PDWORD)pBufIn));
      } else {
        SetLastError(ERROR_INVALID_PARAMETER);
        fRet = FALSE;
      }
      break;

    case IOCTL_SERIAL_SET_QUEUE_SIZE:
      if ((dwLenIn >= sizeof(SERIAL_QUEUE_SIZES)) && pBufIn) {
        if (!m_ReadBuffer.Resize(((SERIAL_QUEUE_SIZES*)pBufIn)->cbInQueue)) {
          fRet = FALSE;
        }
        if (!m_WriteBuffer.Resize(((SERIAL_QUEUE_SIZES*)pBufIn)->cbOutQueue)) {
          fRet = FALSE;
        }
      } else {
        SetLastError(ERROR_INVALID_PARAMETER);
        fRet = FALSE;
      }
      break;

    case IOCTL_SERIAL_SET_TIMEOUTS:
      if (pBufIn && (dwLenIn >= sizeof(COMMTIMEOUTS))) {
        memcpy(&m_Timeouts, pBufIn, sizeof(COMMTIMEOUTS));
      } else {
        SetLastError(ERROR_INVALID_PARAMETER);
        fRet = FALSE;
      }
      break;

    case IOCTL_SERIAL_GET_TIMEOUTS:
      if ((dwLenOut >= sizeof(COMMTIMEOUTS)) && pBufOut) {
        memcpy(pBufOut, &m_Timeouts, sizeof(COMMTIMEOUTS));
        *pdwActualOut = sizeof(COMMTIMEOUTS);
      } else {
        SetLastError(ERROR_INVALID_PARAMETER);
        fRet = FALSE;
      }
      break;

    case IOCTL_SERIAL_GET_WAIT_MASK:
      if ((dwLenOut >= sizeof(DWORD)) && pBufOut) {
        fRet = m_WaitCommEvent.GetWaitMask(pInstance, (PDWORD)pBufOut);
        *pdwActualOut = sizeof(DWORD);
      } else {
        SetLastError(ERROR_INVALID_PARAMETER);
        fRet = FALSE;
      }
      break;

    case IOCTL_SERIAL_SET_WAIT_MASK:
      if (pBufIn && (dwLenIn >= sizeof(DWORD))) {
        fRet = m_WaitCommEvent.SetWaitMask(pInstance, *(PDWORD)pBufIn);
      } else {
        SetLastError(ERROR_INVALID_PARAMETER);
        fRet = FALSE;
      }
      break;

    case IOCTL_SERIAL_WAIT_ON_MASK:
      if ((dwLenOut >= sizeof(DWORD)) && pBufOut) {
        fRet = m_WaitCommEvent.WaitForEvent(pInstance, (PDWORD)pBufOut);
        *pdwActualOut = sizeof(DWORD);
      } else {
        SetLastError(ERROR_INVALID_PARAMETER);
        fRet = FALSE;
      }
      break;

    default:
      fRet = DeviceIoControl(m_hPort, dwCode, pBufIn, dwLenIn, pBufOut, dwLenOut, pdwActualOut, NULL);
      break;
  }

  return fRet;
}

//////////////////////////////////////////////////////////////////////////////
//
// Private implementation
//
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
//
// Purge the virtual port's buffers correspodnig to the given flags.
//
// Parameters:
// dwFlags: The purge flags.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CBufferedParentPort::Purge(DWORD dwFlags) {
BOOL fRet = TRUE;

  if (!PurgeComm(m_hPort, dwFlags)) {
    fRet = FALSE;
  }
  if (dwFlags & PURGE_RXCLEAR) {
    if (!m_ReadBuffer.Empty()) {
      fRet = FALSE;
    }
  }
  if (dwFlags & PURGE_TXCLEAR) {
    if (!m_WriteBuffer.Empty()) {
      fRet = FALSE;
    }
  }
  return fRet;
}

//////////////////////////////////////////////////////////////////////////////
//
// Thread reading data from the physical parent port.
//
// Parameters:
// lpParameter: Pointer to the corresponding class instance.
//
// Return:
// 0 indicates success, any other value indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
DWORD WINAPI CBufferedParentPort::ThreadReadData(LPVOID lpParameter) {
CBufferedParentPort*  pInstance = (CBufferedParentPort*)lpParameter;
DWORD                 dwRead, dwWritten;
BYTE                  Buf[DEFAULT_BUFFER_SIZE];
PBYTE                 pBuf;

  // Read all data from port until we're stopped
  while (WaitForSingleObject(pInstance->m_hCloseEvent, 0) == WAIT_TIMEOUT) {
    if (ReadFile(pInstance->m_hPort, Buf, sizeof(Buf), &dwRead, NULL)) {
      pBuf = Buf;
      if (!pInstance->m_ReadBuffer.GetSpace()) {
        pInstance->m_WaitCommEvent.SetWaitEvent(EV_RXCHAR);
      }
      while (dwRead && (pInstance->m_ReadBuffer.WaitForSpace(INFINITE) != (DWORD)-1)) {
        dwWritten = pInstance->m_ReadBuffer.Write(pBuf, dwRead);
        pBuf += dwWritten;
        dwRead = dwRead - dwWritten;
        if (dwRead) {
          pInstance->m_WaitCommEvent.SetWaitEvent(EV_RXCHAR);
        }
      }
      pInstance->m_WaitCommEvent.SetWaitEvent(EV_RXCHAR);
    } else {
      MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: CBufferedParentPort::ThreadReadData() -> ReadFile() on parent port failed\r\n")));
    }
  }

  return 0;
}

//////////////////////////////////////////////////////////////////////////////
//
// Thread writing data to the physical parent port.
//
// Parameters:
// lpParameter: Pointer to the corresponding class instance.
//
// Return:
// 0 indicates success, any other value indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
DWORD WINAPI CBufferedParentPort::ThreadWriteData(LPVOID lpParameter) {
CBufferedParentPort*  pInstance = (CBufferedParentPort*)lpParameter;
DWORD                 dwToWrite, dwWritten;
BYTE                  Buf[DEFAULT_BUFFER_SIZE];
PBYTE                 pBuf;

  // Write all data to port until we're stopped
  while (WaitForSingleObject(pInstance->m_hCloseEvent, 0) == WAIT_TIMEOUT) {
    if (pInstance->m_WriteBuffer.WaitForData(INFINITE) != (DWORD)-1) {
      pBuf = Buf;
      dwToWrite = pInstance->m_WriteBuffer.Read(Buf, sizeof(Buf));
      while (dwToWrite) {
        if (WriteFile(pInstance->m_hPort, pBuf, dwToWrite, &dwWritten, NULL)) {
          dwToWrite = dwToWrite - dwWritten;
          pBuf += dwWritten;
        } else {
          MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: CBufferedParentPort::ThreadWriteData() -> WriteFile() on parent port failed\r\n")));
          break;
        }
      }
      if (!pInstance->m_WriteBuffer.GetLevel()) {
        pInstance->m_WaitCommEvent.SetWaitEvent(EV_TXEMPTY);
      }
    }
  }

  return 0;
}

//////////////////////////////////////////////////////////////////////////////
//
// Thread waiting for com events on the physical parent port.
//
// Parameters:
// lpParameter: Pointer to the corresponding class instance.
//
// Return:
// 0 indicates success, any other value indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
DWORD WINAPI CBufferedParentPort::ThreadWaitEvent(LPVOID lpParameter) {
CBufferedParentPort*  pInstance = (CBufferedParentPort*)lpParameter;
DWORD                 dwEvent;

  // Write all data to port until we're stopped
  while (WaitForSingleObject(pInstance->m_hCloseEvent, 0) == WAIT_TIMEOUT) {
    if (WaitCommEvent(pInstance->m_hPort, &dwEvent, NULL)) {
      dwEvent &= BUFPORT_COM_MASK;
      if (dwEvent) {
        pInstance->m_WaitCommEvent.SetWaitEvent(dwEvent);
      }
    }
  }

  return 0;
}

