/*****************************************************************************
 * Copyright (C) Cinterion Wireless Modules GmbH
 *****************************************************************************
 * Project: CE-MUX-Driver
 *****************************************************************************
 * Author : Siemens AG Berlin COM WM RD SW1
 *****************************************************************************
 * $Workfile: O:/mwohler_z_ms-d/z_ms-d/src/working/Classic/MuxDrv/BasePort.cpp $
 * $Revision: /main/ms-d/65 $
 * $Date: 2014-03-14 11:53:39 $
 * $Author: mwohler $
 *****************************************************************************
 * Target:
 *  Microsoft Windows CE 4.2 or 5.0
 *****************************************************************************
 * Description:
 *  Cinterion Multiplex Driver hardware base port class CBasePort implementation
 *****************************************************************************
 *
 * $Log: O:/mwohler_z_ms-d/z_ms-d/src/working/Classic/MuxDrv/BasePort.cpp$ 
 * 
 * /main/ms-d/65   2014-03-14 11:53:39   mwohler 
 * Use latest version of mux protocol implementation based on development for Miami client driver 
 * 
 * /main/ms-d/64   2014-02-05 13:41:00   pmackent 
 * New registry keys to set number of mux channels
 *
 * /main/ms-d/63   2013-10-24 13:35:53   pmackent
 * Introduce Windows Embedded Compact 2013
 *
 * /main/ms-d/62   2011-05-12 13:33:14   ctna0450
 * Added short delay between AT+CMUX=0 and opening the DLCIs
 *
 * /main/ms-d/61   2011-03-03 10:51:28   ctna0450
 * Fixed problem with mux protocol negotiation
 *
 * /main/ms-d/60   2011-01-07 14:09:40   ctna0450
 * Improved CommTimeout handling in SendATCmd()
 *
 * /main/ms-d/59   2011-01-05 15:05:25   ctna0219
 * special COMMTIMEOUTS settigns for SendATCmd()
 *
 * /main/ms-d/58   2010-10-18 11:39:35   ctna0256
 * Set AT timeout to 3 seconds for all AT commands (timeout problems on MC75i)
 *
 * /main/ms-d/57   2009-09-04 10:44:16   ctna0450
 * Avoid "unexpected shutdown" trace during normal shutdown on USB driver
 *
 * /main/ms-d/56   2009-09-02 16:02:04   ctna0450
 * Separated module shutdown detection from data reading
 *
 * /main/ms-d/55   2009-07-09 10:10:40   ctna0256
 * Configure task priorities via registry (IPIS100056661)
 *
 * /main/ms-d/54   2009-06-17 14:54:07   ctna0219
 * timeout  is increased for waiting for the start of the mux protocol
 *
 * /main/ms-d/53   2009-06-17 14:39:30   ctna0219
 * no timeout for termination of the ReadThread, if the call SetCommMask() is failed
 *
 * /main/ms-d/52   2009-06-12 14:31:33   ctna0450
 * Fixed possible deadlock when closing read thread after power loss
 *
 * /main/ms-d/51   2009-02-20 11:11:36   ctna0450
 * Bugfix from Intermec:
 * Increase thread priority in StartMuxMode() and not inside ThreadMuxMsg() to allow thread to kick in even if system is busy
 *
 * /main/ms-d/50   2009-02-18 11:58:00   ctna0256
 * Set copyright text
 *
 * /main/ms-d/49   2009-02-13 09:58:05   ctna0450
 * Fixed occasional lock (just in emulator) after module death
 *
 * /main/ms-d/48   2008-10-17 12:04:45   ctna0450
 * Fixed problem with ATC parsing in case of enabled cmd echo.
 *
 * /main/ms-d/47   2008-08-20 11:38:50   ctna0450
 * Prepared for new disclaimer handling
 *
 * /main/ms-d/46   2008-05-08 13:35:25   kr36712
 * error: base port reservation in ThreadCheckSendQueue()
 *
 * /main/ms-d/45   2008-03-03 14:35:38   kr36712
 * IPIS100045708
 * IPIS100045710
 *
 * /main/ms-d/44   2008-01-23 11:35:51   kr36712
 * CBasePort::EnableAllMuxChannels()
 * If there is an error with the CSCS command, then the function is to be
 * terminated not with an error.
 *
 * /main/ms-d/43   2008-01-16 14:51:47   kr36712
 * correction for critical suspend
 *
 * /main/ms-d/42   2007-10-19 14:53:38   ht37010
 * set default values in MuxDevSetQueueSizes() if SetupComm() fails
 *
 * /main/ms-d/41   2007-07-11 11:20:05   kr36712
 * Einarbeitung von Review-Kommentaren
 * siehe
 * /minutes/code_reviews/sw_rvw_code_cemux_jakob2.doc
 *
 * /main/ms-d/40   2007-06-07 17:53:07   kr36712
 * changes for DlciOpen():
 * pepetition of the DlciOpen(), if the answer is lost from the module on
 * the serial interface.
 *
 * /main/ms-d/39   2007-05-08 16:47:06   kr36712
 * IPIS100034485
 * reopen implemented for suspend/resume over usb-driver
 *
 * /main/ms-d/38   2006-08-24 11:32:45   wm36264
 * Added debug trace for raw data
 *
 * /main/ms-d/37   2006-08-23 17:15:07   wm36264
 * Added modifications necessary for new HDLC framing (Mux Step 2)
 *
 * /main/ms-d/36   2006-07-20 14:56:23   wm36264
 * Ensure writing all data to hardware port to avoid problems in environments not able to return correct send buffer fill level.
 *
 * /main/ms-d/35   2006-07-12 15:46:21   wm36264
 * Renamend Mux_Tick() -> Mux_MsgTick() and Mux_ResyncTick() -> Mux_TimerTick()
 *
 * /main/ms-d/34   2006-06-22 18:16:51   wm36264
 * Use local variable in ThreadResyncTimebase() consequently
 *
 * /main/ms-d/33   2006-06-22 17:35:18   kr36712
 * 0xF9 resynchronisation integrated
 *
 * /main/ms-d/32   2006-06-16 12:21:14   wm36264
 * Intruduced utility functions for active device registry access
 *
 * /main/ms-d/31   2006-06-16 12:16:10   wm36264
 * Fixed problem with registry controled part of deinit sequence
 *
 * /main/ms-d/30   2006-05-18 13:32:56   wm36264
 * Fixed all useful lint warnings
 *
 * /main/ms-d/29   2006-05-18 10:54:02   wm36264
 * Removed useless sizeof() usage
 *
 * /main/ms-d/28   2006-05-11 14:01:21   wm36264
 * Avoid false power off detection while testing with DSB board
 *
 * /main/ms-d/27   2006-05-02 13:29:52   wm36264
 * Removed obsolete queue size setting
 *
 * /main/ms-d/26   2006-04-27 14:51:53   wm36264
 * Introduced callback to adjust hardware queue sizes from outside the mux protocol handling
 *
 * /main/ms-d/25   2006-04-20 10:49:49   wm36264
 * Allow increase of DLCIs after mux restart
 *
 * /main/ms-d/24   2006-03-14 12:54:12   wm36264
 * Increase message thread priority
 *
 * /main/ms-d/23   2006-03-03 13:42:58   wm36264
 * Fixed deadlock in case of unexpected module shutdown
 *
 * /main/ms-d/22   2006-03-03 12:57:10   wm36264
 * Treat failure of comm function like module loss to detect USB detach
 *
 * /main/ms-d/21   2005-11-29 10:53:06   wm36264
 * Use "DTR_CONTROL_ENABLE" instaed of "DTR_CONTROL_DISABLE"
 *
 * /main/ms-d/20   2005-11-28 15:01:13   wm36264
 * Improved module power off detection
 *
 * /main/ms-d/19   2005-11-24 16:40:28   wm36264
 * Do not execute shutdown sequence after a power loss
 *
 * /main/ms-d/18   2005-11-23 15:38:27   wm36264
 * Call Mux_Deregister() not before driver removal to avoid exceptions after module power loss
 *
 * /main/ms-d/17   2005-11-21 14:13:49   wm36264
 * Avoid sending of unnecessary AT commands
 *
 * /main/ms-d/16   2005-11-21 11:46:31   wm36264
 * Enumerate the number of used mux channels in the registry
 *
 * /main/ms-d/15   2005-11-09 12:00:29   wm36264
 * INcreased some AT timeouts to avoid problems with CFUN modes
 *
 * /main/ms-d/14   2005-11-09 10:59:24   wm36264
 * Switched to new instancable mux interface
 *
 * /main/ms-d/13   2005-11-04 14:27:36   wm36264
 * Ensure that module is on fixed baud rate
 *
 * /main/ms-d/12   2005-10-24 14:22:41   WM36264
 * Added file descriptions
 *
 * /main/ms-d/11   2005-10-23 14:48:47   WM36264
 * Removed obsolete pointer checks
 *
 * /main/ms-d/10   2005-10-23 14:44:17   WM36264
 * Added customer power on/off support
 *
 * /main/ms-d/9   2005-10-20 13:13:46   WM36264
 * Added proper copyright header
 *
 * /main/ms-d/8   2005-10-19 11:26:22   WM36264
 * Removed obsolete synchronization in private methods
 *
 * /main/ms-d/7   2005-10-19 10:49:29   kr36712
 * bug in StartDataFlow()
 * if(m_fFlowOn) -> if(!m_fFlowOn)
 *
 * /main/ms-d/6   2005-10-13 11:00:53   wm36264
 * Added some info traces
 *
 * /main/ms-d/5   2005-10-12 12:07:50   wm36264
 * Adapted usage of Mux_SendPowerCmd() to interface modification
 *
 * /main/ms-d/4   2005-10-11 15:42:49   wm36264
 * Modified logic for module initialization and deinitialization
 *
 * /main/ms-d/3   2005-10-10 15:35:50   wm36264
 * Reworked macros for debug output
 *
 * /main/ms-d/2   2005-10-07 16:50:32   wm36264
 * Use püositive regfistry values to enable init deinit and shutdown for compatibility with Intrinsyc mux driver.
 *
 * /main/ms-d/1   2005-10-07 16:30:24   wm36264
 * First version of Windows CE Muxer
 *
*/

//////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2008 Cinterion Wireless Modules GmbH
// Copyright (C) 2005-2008 Siemens AG
//
// Version: 1.0
// Date:    dummy
//
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
//
// BasePort.cpp
//
// This file contains the implementation of the class "CBasePort" handling
// access to the module via the hardware base port, it's initialization and
// deintialization as well as the management of the opening and closing
// virtual ports.
//
//////////////////////////////////////////////////////////////////////////////

#include "precomp.h"


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

//////////////////////////////////////////////////////////////////////////////
//
// Constructor
//
// Parameters:
// None.
//
//////////////////////////////////////////////////////////////////////////////
CBasePort::CBasePort(LPCTSTR szRegKey) {
  InitializeCriticalSection(&m_csLock);
  InitializeCriticalSection(&m_csLockMuxLib);
  InitializeCriticalSection(&m_csCheckSendQueueThread);
  m_hPort = INVALID_HANDLE_VALUE;
  m_hReadThread = NULL;
  m_hIsAliveThread = NULL;
  m_hMuxMsgThread = NULL;
  m_hCheckSendQueueThread = NULL;
  m_hMuxTimebaseThread = NULL;
  m_hCloseEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
  m_hStartStopEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
  m_hMuxMsgSemaphore = CreateSemaphore(NULL, 0, MAXLONG, NULL);
  m_dwMaxDlci = 0;
  m_dwLastMaxDlci = 0;
  m_dwTimeLastOff = 0;
  m_dwMuxInstance = 0;
  m_dwTxQueueSize = 0;
  m_dwDefaultThreadPrio = MUX_DEFAULT_TASK_PRIORITY;
  m_dwInitThreadPrio = MUX_DEFAULT_INIT_TASK_PRIORITY;
  m_fFlowOn = TRUE;
  m_fMuxShutdown = FALSE;
  m_fMonitorDsr = FALSE;
  m_dwVComs = 0;
  m_PowerState = D0;
  m_fSuspend = FALSE;
  memset(m_szRegKey, 0 , sizeof(m_szRegKey));
  if (szRegKey) _tcsncpy(m_szRegKey, szRegKey, MAX_PATH);
  m_dwMaxChannels = GetMaxChannels();
}

//////////////////////////////////////////////////////////////////////////////
//
// Destructor
//
//////////////////////////////////////////////////////////////////////////////
CBasePort::~CBasePort() {
  Mux_Deregister(m_dwMuxInstance);
  EnterCriticalSection(&m_csLock);
  m_dwVComs = 0;
  RemoveVCom();
  LeaveCriticalSection(&m_csLock);
  CloseHandle(m_hMuxMsgSemaphore);
  CloseHandle(m_hStartStopEvent);
  CloseHandle(m_hCloseEvent);
  DeleteCriticalSection(&m_csCheckSendQueueThread);
  DeleteCriticalSection(&m_csLockMuxLib);
  DeleteCriticalSection(&m_csLock);
}

//////////////////////////////////////////////////////////////////////////////
//
// Increment the number of registered virtual ports. If the first virtual
// port is registered the base port is opened and initialized and the mux
// mode is started.
//
// Parameters:
// dwDlci: The number of the channel to be added.
//
// Return:
// The handle of the connected mux instance, 0 in case of an error.
//
//////////////////////////////////////////////////////////////////////////////
DWORD CBasePort::AddVCom(DWORD dwDlci) {
DWORD   dwBaudRate;
DWORD   dwSleep;
TCHAR   szBasePort[16];

  SYNCBLOCK(m_csLock);

  // Check the number of the channel
  if (dwDlci > m_dwMaxChannels) {
    return 0;
  }

  // Check if we are not already open
  if ((m_hPort == INVALID_HANDLE_VALUE) && !m_dwVComs) {
    // Check necessary resources
    if (!m_hCloseEvent) return 0;
    if (!m_hStartStopEvent) return 0;
    if (!m_hMuxMsgSemaphore) return 0;

    if (!RegGetDeviceString(m_szRegKey, REGVAL_MUXDRV_BASEPORT, szBasePort, sizeof(szBasePort))) {
      return 0;
    }
    if (!RegGetDeviceDWORD(m_szRegKey, REGVAL_MUXDRV_BAUDRATE, &dwBaudRate)) {
      dwBaudRate = CBR_115200;
    }

    // Read registry settings for task priorities.
    if (!RegGetDeviceDWORD(m_szRegKey, REGVAL_MUXDRV_INITTASKPRIO, &m_dwInitThreadPrio)) {
      m_dwInitThreadPrio = MUX_DEFAULT_INIT_TASK_PRIORITY;
    }
    if (!RegGetDeviceDWORD(m_szRegKey, REGVAL_MUXDRV_TASKPRIORITY, &m_dwDefaultThreadPrio)) {
      m_dwDefaultThreadPrio = MUX_DEFAULT_TASK_PRIORITY;
    }

    // Wait the required module refresh time
    dwSleep = GetTickCount() - m_dwTimeLastOff;
    if (dwSleep < TIME_MODULE_OFF_MIN) {
      Sleep(TIME_MODULE_OFF_MIN - dwSleep);
    }

    // Open the parent port
    if (!OpenPort(szBasePort, dwBaudRate)) {
      return 0;
    }

    ResetEvent(m_hCloseEvent);
    m_fMuxShutdown = FALSE;
    m_fMonitorDsr = FALSE;

    // Now initialize the module if requested
    if (!InitModule(dwBaudRate)) {
      CloseHandle(m_hPort);
      m_hPort = INVALID_HANDLE_VALUE;
      SetEvent(m_hCloseEvent);
      return 0;
    }

    // And finally start the mux mode
    if (!StartMuxMode(dwBaudRate, szBasePort)) {
      CloseHandle(m_hPort);
      m_hPort = INVALID_HANDLE_VALUE;
      SetEvent(m_hCloseEvent);
      return 0;
    }
  }

  m_dwVComs++;

  return m_dwMuxInstance;
}

//////////////////////////////////////////////////////////////////////////////
//
// Close the hardware base port.
//
// Parameters:
// None.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CBasePort::RemoveVCom(void) {
BOOL  fRet = TRUE;

  SYNCBLOCK(m_csLock);

  if (m_dwVComs) {
    m_dwVComs--;
  }

  // Check if we are really open
  if ((m_hPort != INVALID_HANDLE_VALUE) && !m_dwVComs) {
    // Shutdown mux mode
    if (!StopMuxMode()) {
      fRet = FALSE;
    }

    // Now deinitialize the module if requested
    if (!DeinitModule()) {
      fRet = FALSE;
    }

    // Close port handle
    CloseHandle(m_hPort);
    m_hPort = INVALID_HANDLE_VALUE;
  }

  return fRet;
}

//////////////////////////////////////////////////////////////////////////////
//
// Emergency shutdown of multiplex handling.
//
// Parameters:
// fWaitForReadThread: Flag indicating if we have to wait until read thread
//                     is left.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CBasePort::ShutdownMux(BOOL fWaitForAliveThread) {
  SYNCBLOCK(m_csLock);
  if (!IsModuleLost()) {
    m_fModuleDied = TRUE;
    StopThreads(fWaitForAliveThread);
    Mux_Shutdown(m_dwMuxInstance);
    CloseHandle(m_hPort);
    m_hPort = INVALID_HANDLE_VALUE;
  }
  m_fMuxShutdown = TRUE;
  return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Switch to the dedicated system power state.
//
// Parameters:
// PowerSate: The new requested power state.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CBasePort::SetPowerState(CEDEVICE_POWER_STATE PowerState) {
DWORD   dwSleep;
DWORD   dwShutdown;
DWORD   dwReopen;
BOOL    fRet = TRUE;

  SYNCBLOCK(m_csLock);

  MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: -> CBasePort::SetPowerState(old %d, new %d)\r\n"), m_PowerState, PowerState));

  m_PowerState = PowerState;

    if (m_PowerState == D4) {
      // Check registry setting enabling module shutdwon
      dwShutdown = 0;
      if (RegGetDeviceDWORD(m_szRegKey, REGVAL_MUXDRV_SHUTDOWN, &dwShutdown) && dwShutdown) {
        StartDataFlow();
        if (!IsModuleLost()) {
          MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: System shutdown, switching off module ...\r\n")));
          if (Mux_SendPowerCmd(m_dwMuxInstance, MUX_POWER_MODE_SWITCH_OFF)) {
            fRet = FALSE;
          } else {
            // Wait for the read thread to detect that the module went off
            UNSYNCBLOCK(m_csLock);
            if (WaitForSingleObject(m_hCloseEvent, TIMEOUT_MODULE_STOP) == WAIT_TIMEOUT) {
              // We timed out, tidy up nevertheless
              ShutdownMux();
              fRet = FALSE;
            }
          }
        }
        // Physically switch off power
        if (!PowerModuleOff()) {
          fRet = FALSE;
        }
      }
      return fRet;
    }

    if (m_PowerState >= D3) {

      // check registry key
      if (RegGetDeviceDWORD(m_szRegKey, REGVAL_MUXDRV_BASEPORT_REOPEN, &dwReopen) && dwReopen) {
        Suspend();
      } else {
        return StopDataFlow();
      }
    }

    if (m_PowerState < D3) {
      // check registry key
      if (RegGetDeviceDWORD(m_szRegKey, REGVAL_MUXDRV_BASEPORT_REOPEN, &dwReopen) && dwReopen) {
        Resume();
      } else {
        if (RegGetDeviceDWORD(m_szRegKey, REGVAL_MUXDRV_WAIT_RESUME, &dwSleep)) {
          Sleep(dwSleep);
        }
      }
      return StartDataFlow();
    }

  return TRUE;
}

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

//////////////////////////////////////////////////////////////////////////////
//
// Get the number of used multiplex channels by enumerating the virtual port
// sections in the registry.
//
// Parameters:
// None.
//
// Return:
// The number of used multiplex channels (0 in case of an error).
//
//////////////////////////////////////////////////////////////////////////////
DWORD CBasePort::GetMaxChannels(void) {
TCHAR  szEnumKey[MAX_PATH + 1];
TCHAR  szSubKey[MAX_PATH + 1];
TCHAR  szValue[32];
TCHAR  szMpxIndex[32];
HKEY   hBaseKey;
HKEY   hSubKey;
DWORD  dwMpxIndex;
DWORD  dwIndex;
DWORD  dwSize;
DWORD  dwType;
DWORD  dwChannel;
DWORD  dwRet;

  hBaseKey = OpenDeviceKey(m_szRegKey);
  if (!hBaseKey || (hBaseKey == INVALID_HANDLE_VALUE)) {
    return 0;
  }
  dwSize = sizeof(DWORD);
  if (RegQueryValueEx(hBaseKey, TEXT("Index"), NULL, &dwType, (LPBYTE)&dwMpxIndex, &dwSize)) {
    RegCloseKey(hBaseKey);
    return 0;
  }
  RegCloseKey(hBaseKey);

  if (FAILED(RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("Drivers\\BuiltIn"), 0, 0, &hBaseKey))) {
    return 0;
  }

#if (WINCEOSVER >= 600)
  _stprintf_s(szMpxIndex, sizeof(szMpxIndex), TEXT("MPX%d:"), dwMpxIndex);
#else
  _stprintf(szMpxIndex, TEXT("MPX%d:"), dwMpxIndex);
#endif
  dwRet = 0;
  dwSize = MAX_PATH + 1;
  dwIndex = 0;
  while (!RegEnumKeyEx(hBaseKey, dwIndex, szEnumKey, &dwSize, NULL, NULL, NULL, NULL)) {
    _tcscpy(szSubKey, TEXT("Drivers\\BuiltIn"));
    _tcscat(szSubKey, TEXT("\\"));
    _tcscat(szSubKey, szEnumKey);
    if (SUCCEEDED(RegOpenKeyEx(HKEY_LOCAL_MACHINE, szSubKey, 0, 0, &hSubKey))) {
      dwSize = sizeof(szValue);
      if (!RegQueryValueEx(hSubKey, TEXT("Prefix"), NULL, &dwType, (LPBYTE)szValue, &dwSize)) {
        if (!_tcscmp(szValue, TEXT("VCA"))) {
          dwSize = sizeof(szValue);
          if (!RegQueryValueEx(hSubKey, REGVAL_VCOM_MUXINSTANCE, NULL, &dwType, (LPBYTE)szValue, &dwSize)) {
            if (!_tcscmp(szValue, szMpxIndex)) {
              dwSize = sizeof(DWORD);
              if (!RegQueryValueEx(hSubKey, REGVAL_VCOM_DLCI, NULL, &dwType, (LPBYTE)&dwChannel, &dwSize)) {
                if (dwChannel > dwRet) {
                  dwRet = dwChannel;
                }
              } else {
                if (dwMpxIndex == 1) {
                  dwSize = sizeof(DWORD);
                  if (!RegQueryValueEx(hSubKey, TEXT("Index"), NULL, &dwType, (LPBYTE)&dwChannel, &dwSize)) {
                    if (dwChannel > dwRet) {
                      dwRet = dwChannel;
                    }
                  }
                }
              }
            }
          } else {
            if (dwMpxIndex == 1) {
              dwSize = sizeof(DWORD);
              if (!RegQueryValueEx(hSubKey, REGVAL_VCOM_DLCI, NULL, &dwType, (LPBYTE)&dwChannel, &dwSize)) {
                if (dwChannel > dwRet) {
                  dwRet = dwChannel;
                }
              } else {
                dwSize = sizeof(DWORD);
                if (!RegQueryValueEx(hSubKey, TEXT("Index"), NULL, &dwType, (LPBYTE)&dwChannel, &dwSize)) {
                  if (dwChannel > dwRet) {
                    dwRet = dwChannel;
                  }
                }
              }
            }
          }
        }
      }
      RegCloseKey(hSubKey);
    }
    dwSize = MAX_PATH + 1;
    dwIndex++;
  }

  RegCloseKey(hBaseKey);

  return dwRet;;
}

//////////////////////////////////////////////////////////////////////////////
//
// Open and initialize the hardware base port, initialze module, start mux
// mode.
//
// Parameters:
// szPort    : The name of the port to open.
// dwBaudRate: The baud rate to use.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CBasePort::OpenPort(LPCTSTR szPort, DWORD dwBaudRate) {
DCB           Dcb;
COMMTIMEOUTS  CommTimeouts;
DWORD   dwErrors;

  // Open the parent port
  m_hPort = CreateFile(szPort, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
  if (m_hPort == INVALID_HANDLE_VALUE) {
    return FALSE;
  }

  // Get the parent port's DCB
  if (!GetCommState(m_hPort, &Dcb)) {
    CloseHandle(m_hPort);
    m_hPort = INVALID_HANDLE_VALUE;
    return FALSE;
  }

  // Set our default settings
  Dcb.DCBlength          = sizeof(DCB);
  Dcb.BaudRate           = dwBaudRate;
  Dcb.fBinary            = TRUE;
  Dcb.fParity            = TRUE;
  Dcb.fOutxCtsFlow       = TRUE;
  Dcb.fOutxDsrFlow       = FALSE;
  Dcb.fDtrControl        = DTR_CONTROL_ENABLE;
  Dcb.fDsrSensitivity    = FALSE;
  Dcb.fTXContinueOnXoff  = TRUE;
  Dcb.fOutX              = FALSE;
  Dcb.fInX               = FALSE;
  Dcb.fErrorChar         = FALSE;
  Dcb.fNull              = FALSE;
  Dcb.fRtsControl        = RTS_CONTROL_HANDSHAKE;
  Dcb.fAbortOnError      = FALSE;
  Dcb.ByteSize           = 8;
  Dcb.Parity             = NOPARITY;
  Dcb.StopBits           = ONESTOPBIT;

  m_fFlowOn = TRUE;

  // Set the parent port's DCB
  if (!SetCommState(m_hPort, &Dcb)) {
    CloseHandle(m_hPort);
    m_hPort = INVALID_HANDLE_VALUE;
    return FALSE;
  }

  // Set the parent port's timeouts
  CommTimeouts.ReadIntervalTimeout          = MAXDWORD;
  CommTimeouts.ReadTotalTimeoutMultiplier   = MAXDWORD;
  CommTimeouts.ReadTotalTimeoutConstant     = MAXDWORD - 1;
  CommTimeouts.WriteTotalTimeoutMultiplier  = 0;
  CommTimeouts.WriteTotalTimeoutConstant    = 3000;
  if (!SetCommTimeouts(m_hPort, &CommTimeouts)) {
    CloseHandle(m_hPort);
    m_hPort = INVALID_HANDLE_VALUE;
    return FALSE;
  }

  PurgeComm(m_hPort, PURGE_RXCLEAR | PURGE_TXCLEAR);
  ClearCommError(m_hPort, &dwErrors, NULL);


  return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Close and reopen base port. This function is called, if the Mux-Driver works
// ower virtual USB Com port.
//
// Parameters:
// None.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CBasePort::Suspend() {
COMSTAT  ComStat;
DWORD    dwError;
DWORD    dwRetryCnt;

  MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: -> CBasePort::Suspend()\r\n")));

  StopDataFlow();

  // wait, until all receive data are processed
  dwRetryCnt = 3;
  do {
    if (!ClearCommError(m_hPort, &dwError, &ComStat)) {
      MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: CBasePort::Suspend() -> ERR ClearCommError() for received data\r\n")));
      return FALSE;
    }
    if (ComStat.cbInQue == 0) {
      break;
    }
    dwRetryCnt--;
    Sleep(100);
  } while (dwRetryCnt);

  // block com port access
  LockReservation();

  // break WaitCommEvent() in ThreadReadData()
  SetCommMask(m_hPort, 0);

  // wait, until all written data are processed
  dwRetryCnt = 3;
  do {
    if (!ClearCommError(m_hPort, &dwError, &ComStat)) {
      MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: CBasePort::Suspend() -> ERR ClearCommError() for written data\r\n")));
      return FALSE;
    }
    if (ComStat.cbOutQue == 0) {
      break;
    }
    dwRetryCnt--;
    Sleep(100);
  } while (dwRetryCnt);

  if (TRUE != WaitForUnreserved (1000)) {
    MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: CBasePort::Suspend() -> ERR WaitForUnreserved() returns FALSE\r\n")));
  }

  m_fSuspend = TRUE;

  MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: <- CBasePort::Suspend()\r\n")));
  return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Close and reopen base port. This function is called, if the Mux-Driver works
// ower virtual USB Com port.
//
// Parameters:
// None.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
#define TIMEOUT_REOPEN          10000
BOOL CBasePort::Resume() {
DWORD           dwStart;
COMMPROP        CommProp;
COMMTIMEOUTS    CommTiemouts;
DCB             Dcb;
LPCOMMPROP      pCommProp;
LPCOMMTIMEOUTS  pCommTiemouts;
LPDCB           pDcb;
TCHAR           szBasePort[16];
BOOL            fRet = TRUE;

  MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: -> CBasePort::Resume()\r\n")));

  if (!m_fSuspend) {
    MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: <- CBasePort::Resume() is resumed\r\n")));
    return TRUE;
  }

  if (m_hPort != INVALID_HANDLE_VALUE) {
    memset((void *)&CommProp, 0x00, sizeof(CommProp));
    memset((void *)&CommTiemouts, 0x00, sizeof(CommTiemouts));
    memset((void *)&Dcb, 0x00, sizeof(Dcb));
    pCommProp       = NULL;
    pCommTiemouts   = NULL;
    pDcb            = NULL;

    while (1) {
      // save current port configuration
      if (!GetCommProperties (m_hPort, &CommProp)) {
        MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: CBasePort::Resume() -> ERR GetCommProperties() returns FALSE\r\n")));
      } else {
        pCommProp = &CommProp;
      }

      if (!GetCommTimeouts (m_hPort, &CommTiemouts)) {
        MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: CBasePort::Resume() -> ERR GetCommTimeouts() returns FALSE\r\n")));
      } else {
        pCommTiemouts = &CommTiemouts;
      }

      if (!GetCommState (m_hPort, &Dcb)) {
        MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: CBasePort::Resume() -> ERR GetCommState() returns FALSE\r\n")));
      } else {
        pDcb = &Dcb;
      }

      // close com port
      CloseHandle(m_hPort);
      Sleep(500);

      if (!RegGetDeviceString(m_szRegKey, REGVAL_MUXDRV_BASEPORT, szBasePort, sizeof(szBasePort))) {
        fRet = FALSE;
        break;
      }

      dwStart = GetTickCount();
      do {
        // reopen the parent port
        m_hPort = CreateFile(szBasePort, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
        Sleep(200);

        MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: CBasePort::Resume() -> CreateFile() \r\n")));
      } while((m_hPort == INVALID_HANDLE_VALUE) && ((GetTickCount() - dwStart) < TIMEOUT_REOPEN));

      if (m_hPort == INVALID_HANDLE_VALUE) {
        MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: CBasePort::Resume() -> ERR cannot reopen port !!!\r\n")));
        fRet = FALSE;
        break;
      }

      // restore com state
      if (pDcb) {
        if (!SetCommState(m_hPort, pDcb)) {
          MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: CBasePort::Resume() -> ERR SetCommState() returns FALSE\r\n")));
          fRet = FALSE;
        }
      }

      // restore com timeout
      if (pCommTiemouts) {
        if (!SetCommTimeouts (m_hPort, pCommTiemouts)) {
          MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: CBasePort::Resume() -> ERR SetCommTimeouts() returns FALSE\r\n")));
          fRet = FALSE;
        }
      }

      // restore com queue
      if (pCommProp) {
        if (!SetupComm(m_hPort, pCommProp->dwMaxRxQueue, pCommProp->dwMaxTxQueue)) {
          MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: CBasePort::Resume() -> ERR SetupComm() returns FALSE\r\n")));
          fRet = FALSE;
        }
      }

      // set SetCommMask for check alive thread
      if (!SetCommMask(m_hPort, ISALIVE_COMM_MASK)) {
        MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: CBasePort::Resume() -> ERR SetCommMask() returns FALSE\r\n")));
        fRet = FALSE;
      }
      break;
    }
    // allow com port access
    UnlockReservation();

    StartDataFlow();
  } else {
    // allow com port access
    UnlockReservation();
  }
  m_fSuspend = FALSE;

  MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: <- CBasePort::Resume() %s\r\n"), (fRet)?_T("TRUE"):_T("FALSE")));

  return fRet;
}

//////////////////////////////////////////////////////////////////////////////
//
// Stop the data flow on the base port by clearing the RTS signal.
//
// Parameters:
// None.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CBasePort::StopDataFlow(void) {
DCB   Dcb;
BOOL  fRet = TRUE;

  if (m_fFlowOn) {
    MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: Stopping data flow on parent port\r\n")));
    if (!GetCommState(m_hPort, &Dcb)) fRet = FALSE;
    Dcb.fRtsControl = RTS_CONTROL_DISABLE;
    if (!SetCommState(m_hPort, &Dcb)) fRet = FALSE;
    if (!EscapeCommFunction(m_hPort, CLRRTS)) fRet = FALSE;
    m_fFlowOn = FALSE;
  }
  return fRet;
}

//////////////////////////////////////////////////////////////////////////////
//
// Start the data flow on the base port by enabling RTS handshake.
//
// Parameters:
// None.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CBasePort::StartDataFlow(void) {
DCB   Dcb;
BOOL  fRet = TRUE;

  if (!m_fFlowOn) {
    MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: (Re)Starting data flow on parent port\r\n")));
    if (!GetCommState(m_hPort, &Dcb)) fRet = FALSE;
    Dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
    if (!SetCommState(m_hPort, &Dcb)) fRet = FALSE;
    m_fFlowOn = TRUE;
  }
  return fRet;
}

//////////////////////////////////////////////////////////////////////////////
//
// Send an AT command to the COM port and check response for OK or ERROR.
//
// Parameters:
// pszATCommand: The AT command to send.
// dwTimeout   : Timeout for the command in ms.
// dwRetries   : Number of retries.
// szRsp       : External response buffer, only required if the response
//               is needed outside this function.
// dwRspLen    : Length of external response buffer.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CBasePort::SendATCmd(char* pszATCommand, DWORD dwTimeout, DWORD dwRetries, char* szRsp, DWORD dwRspLen) {
const char*     szOkRsps[] = { "OK\r\n", "" };
const char*     szErrorRsps[] = { "ERROR\r\n", "+CME ERROR: 21\r\n", "" };
char            szResponse[128];
char*           pszResponse;
char*           pRsp;
DWORD           dwRead;
DWORD           dwResponsePos;
DWORD           dwStartTime;
DWORD           dwRspLength;
DWORD           dwWritten;
DWORD           i;

  if (!strlen(pszATCommand)) return TRUE;

  // Set timeouts required for AT command handling
  COMMTIMEOUTS CommTimeouts;
  CommTimeouts.ReadIntervalTimeout          = MAXDWORD;
  CommTimeouts.ReadTotalTimeoutMultiplier   = MAXDWORD;
  CommTimeouts.ReadTotalTimeoutConstant     = 100;
  CommTimeouts.WriteTotalTimeoutMultiplier  = 0;
  CommTimeouts.WriteTotalTimeoutConstant    = 3000;
  CCommTimeoutSection TimoutBlock(m_hPort, &CommTimeouts);

  for (i = 0; i <= dwRetries; i++) {
    if (i) {
      Sleep(100);
    }

    // Check if module is still alive
    if (!IsModulePowerOn()) {
      MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: Module shutdown during initialization detected!\r\n")));
      return FALSE;
    }

    MUXDBGCRLF(ZONE_AT_CMDS, TEXT("MuxDrv: Sending AT command: "), pszATCommand);

    if (!WriteFile(m_hPort, pszATCommand, strlen(pszATCommand), &dwWritten, NULL)) {
      return FALSE;
    }
    // Init and clear the response buffer
    if (szRsp && dwRspLen) {
      dwRspLength = dwRspLen - 1;
      pszResponse = szRsp;
    } else {
      dwRspLength = (sizeof(szResponse) / sizeof(char)) - 1;
      pszResponse = szResponse;
    }
    pRsp = pszResponse;
    memset(pszResponse, 0, (dwRspLength + 1) * sizeof(char));

    dwStartTime = GetTickCount();
    while (TRUE) {
      // Check if module is still alive
      if (!IsModulePowerOn()) {
        MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: Module shutdown during initialization detected!\r\n")));
        return FALSE;
      }

      // Wait for some data
      if (!ReadFile(m_hPort, pszResponse, dwRspLength, &dwRead, NULL)) {
        break;
      }

      // Check if we have received some data
      if (dwRead > 0) {
        // Scan for possible responses
        for (dwResponsePos = 0; strlen(szOkRsps[dwResponsePos]); dwResponsePos++) {
          if (strstr(pRsp, (char*)szOkRsps[dwResponsePos])) {
            MUXDBGCRLF(ZONE_AT_CMDS, TEXT("MuxDrv: Received OK response: "), pRsp);
            return TRUE;
          }
        }

        for (dwResponsePos = 0; strlen(szErrorRsps[dwResponsePos]); dwResponsePos++) {
          if (strstr(pRsp, (char*)szErrorRsps[dwResponsePos])) {
            MUXDBGCRLF(ZONE_AT_CMDS, TEXT("MuxDrv: Received ERROR response: "), pRsp);
            return FALSE;
          }
        }

        pszResponse += dwRead;
        dwRspLength -= dwRead;
        if (!dwRspLength) {
          break;
        }
      }

      if ((GetTickCount() - dwStartTime) >= dwTimeout) {
        MUXDBG(ZONE_AT_CMDS, (TEXT("MuxDrv: AT command timeout\r\n")));
        break;
      }
    };
  }

  return FALSE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Switch module on and wait until it has started.
// On DSB development boards the module's ignition pin is connected
// with the COM DTR signal and there is no specific circuit to physically
// switch the power supply on or off.
// If your hardware wiring requires special function calls for powering the
// module on the code in this function has to be expanded or replaced with
// the hardware dependant code.
//
// Parameters:
// None.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CBasePort::PowerModuleOn(void) {
DWORD  dwMask;
DWORD  dwStartTime;

  // On DSB boards iginition is connected with DTR
  EscapeCommFunction(m_hPort, SETDTR);

  // Now wait for the module to start
  dwStartTime = GetTickCount();

  // Initial delay to give signals some time to settle down
  Sleep(500);

  // Now wait for the CTS signal to become signaled
  while (TRUE) {
    if (!GetCommModemStatus(m_hPort, &dwMask)) {
      return FALSE;
    }
    if (dwMask & MS_CTS_ON) {
      // Active CTS signal detected -> module is on now
      break;
    }
    if ((GetTickCount() - dwStartTime) >= TIMEOUT_MODULE_START) {
      // We timed out
      return FALSE;
    }
    Sleep(50);
  }

  // Final delay to give module last chance to settle things
  Sleep(200);

  return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Switch off module power.
// If your hardware contains special circuits to physically switch off the
// module's power supply this hardware dependant code hs to be added to this
// function.
//
// Parameters:
// None.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CBasePort::PowerModuleOff(void) {
  return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Check if the connected module is powered on.
// Currently the DSR signal on the hardware port is used to check if the
// module is powered on or off. If the DSR signal cannot be used for that
// module life check for any reason (e.g. because the hardware DSR signal
// isn't connected) you need to modify that function to reliably detect if
// the module is on or off. Additionally you might need to add a thread
// detecting a module power loss and notifying the rest of the driver as it
// done in "CBasePort::ThreadReadData()".
//
// Parameters:
// None.
//
// Return:
// TRUE if the connected module is powerd on, FALSE otherwise.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CBasePort::IsModulePowerOn(void) {
DWORD  dwStatus;

  // Check if we have been switched off externally
  if (m_fMuxShutdown) {
    return FALSE;
  }

  // Check if DSR monitoring is enabled
  if (!m_fMonitorDsr) {
    return TRUE;
  }

  // Check if module is still alive
  if (!GetCommModemStatus(m_hPort, &dwStatus) || !(dwStatus & MS_DSR_ON)) {
    return FALSE;
  }

  return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Switch off module and wait until it has shut down.
//
// Parameters:
// None.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CBasePort::SwitchModuleOff(void) {
DWORD  dwMask;
DWORD  dwStartTime;
BOOL   fRet = TRUE;

  if (IsModulePowerOn()) {
    // Send the switch off command
    if (!SendATCmd(ATCMD_SHUTDOWN, 3000, 0)) {
      fRet = FALSE;
    } else {
      // Now wait for the CTS signal to become low
      dwStartTime = GetTickCount();
      while (TRUE) {
        if (!GetCommModemStatus(m_hPort, &dwMask)) {
          fRet = FALSE;
          break;
        }
        if (!(dwMask & MS_CTS_ON)) {
          // Inactive CTS signal detected -> module is off now
          break;
        }
        if ((GetTickCount() - dwStartTime) >= TIMEOUT_MODULE_STOP) {
          // We timed out
          fRet = FALSE;
          break;
        }
        Sleep(50);
      }
    }
  }

  // Physically switch power off
  if (!PowerModuleOff()) {
    fRet = FALSE;
  }

  m_dwTimeLastOff = GetTickCount();

  return fRet;
}

//////////////////////////////////////////////////////////////////////////////
//
// Synchronize the port's baud rate setting with the module.
//
// Params:
// dwBaudRate: The baud rate to use.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CBasePort::SyncBaudRate(DWORD dwBaudRate) {
DWORD  dwBaudRates[] = BAUDRATES_TO_SCAN;
DWORD  dwNumOfBaudRates = sizeof(dwBaudRates) / sizeof(dwBaudRates[0]);
DWORD  i, k;
DWORD  dwErrors;
char   szBaudCmd[32];
DCB    Dcb;
DCB    DcbOrg;

  // Fetch the required baud rate
  dwBaudRates[0] = dwBaudRate;

  // Construct the command
  sprintf(szBaudCmd, "AT+IPR=%d\r", dwBaudRate);

  // Init the com port for the following baud rate scan
  ClearCommError(m_hPort, &dwErrors, NULL);
  GetCommState(m_hPort, &DcbOrg);
  memcpy(&Dcb, &DcbOrg, sizeof(DCB));
  Dcb.fBinary            = TRUE;
  Dcb.fParity            = FALSE;
  Dcb.fOutxCtsFlow       = FALSE;
  Dcb.fOutxDsrFlow       = FALSE;
  Dcb.fDtrControl        = DTR_CONTROL_ENABLE;
  Dcb.fDsrSensitivity    = FALSE;
  Dcb.fTXContinueOnXoff  = TRUE;
  Dcb.fOutX              = FALSE;
  Dcb.fInX               = FALSE;
  Dcb.fErrorChar         = FALSE;
  Dcb.fNull              = FALSE;
  Dcb.fRtsControl        = RTS_CONTROL_ENABLE;
  Dcb.fAbortOnError      = FALSE;
  Dcb.ByteSize           = 8;
  Dcb.Parity             = NOPARITY;
  Dcb.StopBits           = ONESTOPBIT;
  EscapeCommFunction(m_hPort, SETDTR);
  EscapeCommFunction(m_hPort, SETRTS);
  ClearCommError(m_hPort, &dwErrors, NULL);
  Sleep(1000);

  // Scan through the list of baud rates and attempt to fix the modem to the required rate
  i = k = 0;
  do {
    Dcb.BaudRate = dwBaudRates[i];
    if ((i == 0) || (Dcb.BaudRate != dwBaudRates[0])) {
      // Reset the port
      ClearCommError(m_hPort, &dwErrors, NULL);
      PurgeComm(m_hPort, PURGE_RXCLEAR | PURGE_TXCLEAR);

      // Try the new baud rate
      if (SetCommState(m_hPort, &Dcb)) {
        // Send the command to fix the baudrate
        if (SendATCmd(szBaudCmd, 1000, 0)) {
          // If we get an intelligable response we can leave the loop as we have managed to set the baudrate
          break;
        }
      }
    }

    // Prepare next baud rate
    i++;
    if (i >= dwNumOfBaudRates) {
      // If we're at the end of our baud rate list restart the scan
      i = 0;
      k++;
    }
  } while(k < BAUDRATES_RETRIES);

  if (k == BAUDRATES_RETRIES) {
    return FALSE;
  }

  // Restore the original port settings with the new baud rate
  DcbOrg.BaudRate = dwBaudRate;
  SetCommState(m_hPort, &DcbOrg);
  PurgeComm(m_hPort, PURGE_RXCLEAR | PURGE_TXCLEAR);
  ClearCommError(m_hPort, &dwErrors, NULL);

  // Give system some time for the baud rate switch
  if (Dcb.BaudRate != dwBaudRate) {
    Sleep(1000);
  }

  return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Enable all available mux channels.
//
// Parameters:
// None.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CBasePort::EnableAllMuxChannels(void) {
char   szCmd[128];
char   szRsp[128];
DWORD  i;
BOOL   fUcs2 = FALSE;

  // Check if there is anything to change (3 channels are always available)
  if (m_dwMaxChannels <= 3) {
    // no need to change anything inside the module
    m_dwMaxDlci = m_dwMaxChannels;
    return TRUE;
  }

  // Unless a maximum numbers of DLCIs is explicitly set via registry,
  // enable them via the AT^SCFG command.
  // Reason for this key: some module support more than 3 channels, but
  // don't support the corresponding AT^SCFG option.
  if (!RegGetDeviceDWORD(m_szRegKey, REGVAL_MUXDRV_SET_DLCI, &m_dwMaxDlci)) {
    // Get the CSCS setting
    if (SendATCmd("AT+CSCS?\r", 3000, 0, szRsp, sizeof(szRsp))) {
      if (strstr(szRsp, "\"UCS2\"")) {
        fUcs2 = TRUE;
      }
    }

    // Send commands to enable additional mux channels until we fail
    m_dwMaxDlci = 3;
    for (i = 4; i <= m_dwMaxChannels; i++) {
      if (fUcs2) {
        if (i < 10) {
          sprintf(szCmd, "AT^SCFG=\"MEopMode/Mux/Instances\",\"%04X\"\r", 0x30 + i);
        } else {
          sprintf(szCmd, "AT^SCFG=\"MEopMode/Mux/Instances\",\"%04X%04X\"\r", 0x30 + ((i / 10) * 10), 0x30 + (i % 10));
        }
      } else {
        sprintf(szCmd, "AT^SCFG=\"MEopMode/Mux/Instances\",\"%i\"\r", i);
      }
      if (!SendATCmd(szCmd, DEFAULT_AT_TIMEOUT, 0)) {
        // We received an ERROR or timed out -> the additional channel is not available
        break;
      }
      m_dwMaxDlci++;
    }
  }

  return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Initialize the module we're connected to.
//
// Parameters:
// dwBaudRate: The baud rate to use.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CBasePort::InitModule(DWORD dwBaudRate) {
char   szCmd[512];
TCHAR  szTmpCmd[512];
TCHAR  szRegName[64];
DWORD  i;
DWORD  dwInit = 0;
DWORD  dwDisableDsrMonitor = 0;
BOOL   fRet = TRUE;

  // Check registry setting enabling module initialization
  RegGetDeviceDWORD(m_szRegKey, REGVAL_MUXDRV_INIT, &dwInit);
  if (dwInit) {
    if (!PowerModuleOn()) {
      return FALSE;
    }
    if (!SyncBaudRate(dwBaudRate)) {
      return FALSE;
    }
  }

  // Send the mandatory module initialization setting
  if (!SendATCmd(ATCMD_MANDATORY_INIT, DEFAULT_AT_TIMEOUT, 0)) {
    // In emulator with DSB board the module switches on by simply opening the port.
    // If the customer init phase is disabled the module might have just been powered
    // on by opening the port which would cause the first AT command to fail.
    // The following retry simplifies testing with emulator and DSB board by avoiding
    // a false power off detection.
    Sleep(2000);
    if (!SendATCmd(ATCMD_MANDATORY_INIT, DEFAULT_AT_TIMEOUT, 0)) {
      return FALSE;
    }
  }

  // The CEMux monitors the DSR line to be able to detect an unexpected module shutdown.
  // This monitoring can be disabled using the registry key "DisableDsrMonitor". Warning:
  // it is highly recommended to use DSR monitoring. The only reason why this option
  // exists is, that some development debug boards don't have DSR. For the use in a
  // customer device the DSR monitoring must be on.
  if (!RegGetDeviceDWORD(m_szRegKey, REGVAL_MUXDRV_DISABLE_DSR, &dwDisableDsrMonitor)) dwDisableDsrMonitor = 0;
  // From here we can use DSR to monitor if module is alive because we've just sent AT&S0
  if (dwDisableDsrMonitor != 0)
    m_fMonitorDsr = TRUE;

  // For mux mode we must not be in autobauding mode
  if (!dwInit) {
    sprintf(szCmd, "AT+IPR=%d\r", dwBaudRate);
    if (!SendATCmd(szCmd, DEFAULT_AT_TIMEOUT, 0)) {
      return FALSE;
    }
  }

  // Send the module initialization commands from registry
  i = 0;
  while (TRUE) {
#if (WINCEOSVER >= 600)
    _stprintf_s(szRegName, sizeof(szRegName), REGVAL_MUXDRV_INIT_CMD, i);
#else
    _stprintf(szRegName, REGVAL_MUXDRV_INIT_CMD, i);
#endif
    if (!RegGetDeviceString(m_szRegKey, szRegName, szTmpCmd, sizeof(szTmpCmd))) {
      if (i > 0) {
        break;
      }
    } else {
      szCmd[sizeof(szCmd) - 1] = 0;
      _snprintf(szCmd, sizeof(szCmd) - 1, "%S\r", szTmpCmd);
      if (!SendATCmd(szCmd, DEFAULT_AT_TIMEOUT, 0)) {
        fRet = FALSE;
      }
    }
    i++;
  }

  // Enable all available mux channels
  if (fRet) {
    fRet = EnableAllMuxChannels();
  }

  return fRet;
}

//////////////////////////////////////////////////////////////////////////////
//
// Uninitialize the module we're connected to.
//
// Parameters:
// None.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CBasePort::DeinitModule(void) {
char   szCmd[512];
TCHAR  szTmpCmd[512];
TCHAR  szRegName[64];
DWORD  i;
DWORD  dwShutdown = 0;
BOOL   fRet = TRUE;

  if (IsModulePowerOn()) {
    // Send the module deinitialization commands
    i = 0;
    while (TRUE) {
#if (WINCEOSVER >= 600)
      _stprintf_s(szRegName, sizeof(szRegName), REGVAL_MUXDRV_DEINIT_CMD, i);
#else
      _stprintf(szRegName, REGVAL_MUXDRV_DEINIT_CMD, i);
#endif
      if (!RegGetDeviceString(m_szRegKey, szRegName, szTmpCmd, sizeof(szTmpCmd))) {
        if (i > 0) {
          break;
        }
      } else {
        szCmd[sizeof(szCmd) - 1] = 0;
        _snprintf(szCmd, sizeof(szCmd) - 1, "%S\r", szTmpCmd);
        if (!SendATCmd(szCmd, DEFAULT_AT_TIMEOUT, 0)) {
          fRet = FALSE;
        }
      }
      i++;
    }
  }

  // Check registry setting enabling module shutdwon
  if (RegGetDeviceDWORD(m_szRegKey, REGVAL_MUXDRV_SHUTDOWN, &dwShutdown) && dwShutdown) {
    // Shutdown module
    if (!SwitchModuleOff()) {
      fRet = FALSE;
    }
  }

  return fRet;
}

//////////////////////////////////////////////////////////////////////////////
//
// Stop the running threads.
//
// Parameters:
// fWaitForReadThread: Flag indicating if we have to wait until read thread
//                     is left.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CBasePort::StopThreads(BOOL fWaitForAliveThread) {
  SetEvent(m_hCloseEvent);

  // Stop the timer thread for the mux resync feature
  if (m_hMuxTimebaseThread) {
    WaitForSingleObject(m_hMuxTimebaseThread, TIMEBASE_INTERVAL + 1000);
    CloseHandle(m_hMuxTimebaseThread);
    m_hMuxTimebaseThread = NULL;
  }

  // Stop the check send queue thread
  EnterCriticalSection(&m_csCheckSendQueueThread);
  HANDLE hCheckSendQueueThread = m_hCheckSendQueueThread;
  LeaveCriticalSection(&m_csCheckSendQueueThread);
  // This thread is closing it's own handle so we cannot use WaitForSingleObject()
  for (DWORD i = 0; (i < 100) && hCheckSendQueueThread; i++) {
    Sleep(10);
    EnterCriticalSection(&m_csCheckSendQueueThread);
    hCheckSendQueueThread = m_hCheckSendQueueThread;
    LeaveCriticalSection(&m_csCheckSendQueueThread);
  }

  // Stop the mux message thread
  if (m_hMuxMsgThread) {
    // Close port to stop pending WriteFile() calls
    CloseHandle(m_hPort);
    m_hPort = INVALID_HANDLE_VALUE;
    WaitForSingleObject(m_hMuxMsgThread, 1000);
    CloseHandle(m_hMuxMsgThread);
    m_hMuxMsgThread = NULL;
  }

  // Stop the IsAlive thread
  if (fWaitForAliveThread && m_hIsAliveThread) {
    if (SetCommMask(m_hPort, 0)) {
    	// wait for termination of the IsAliveThread only, if the call SetCommMask()is successful
      WaitForSingleObject(m_hIsAliveThread, 1000);
    }
  }
  // Tidy up the IsAlive thread
  if (m_hIsAliveThread) {
    CloseHandle(m_hIsAliveThread);
    m_hIsAliveThread = NULL;
  }

  // Stop the read thread
  if (m_hReadThread) {
    // Close port to stop pending ReadFile() calls
    CloseHandle(m_hPort);
    m_hPort = INVALID_HANDLE_VALUE;
    WaitForSingleObject(m_hReadThread, 1000);
    CloseHandle(m_hReadThread);
    m_hReadThread = NULL;
  }

  return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Start the multiplex mode.
//
// Parameters:
// dwBaudRate: The baud rate to be used.
// szBasePort: The name o f the base port.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CBasePort::StartMuxMode(DWORD dwBaudRate, LPCTSTR szBasePort) {
MUX_INTERFACE_t  CallBacks;
DWORD            dwDisableResync;
DWORD            dwMaxMuxVersion;
DWORD            dwHdlcWindowSize;
DWORD            dwHdlcFrameSize;

  // Store old task priority.
  if (m_dwDefaultThreadPrio == 0 && m_dwInitThreadPrio != 0)
    m_dwDefaultThreadPrio = CeGetThreadPriority(GetCurrentThread());
  // If configured, set task priority.
  if (m_dwInitThreadPrio > 0)
    CeSetThreadPriority(GetCurrentThread(), m_dwInitThreadPrio);

  if (!SendATCmd(ATCMD_START_MUX, DEFAULT_AT_TIMEOUT, 0)) {
    return FALSE;
  }

  m_fModuleDied = FALSE;

  // Reset the mux message semaphore
  while(WaitForSingleObject(m_hMuxMsgSemaphore, 0) != WAIT_TIMEOUT);

  // Start the required threads
  m_hReadThread = CreateThread(NULL, 0, ThreadReadData, this, 0, NULL);
  if (!m_hReadThread) {
    return FALSE;
  }
  if (m_dwInitThreadPrio > 0)
    CeSetThreadPriority(m_hReadThread, m_dwInitThreadPrio);

  m_hIsAliveThread = CreateThread(NULL, 0, ThreadIsAlive, this, 0, NULL);
  if (!m_hIsAliveThread) {
    StopThreads();
    return FALSE;
  }

  m_hMuxMsgThread = CreateThread(NULL, 0, ThreadMuxMsg, this, 0, NULL);
  if (!m_hMuxMsgThread) {
    StopThreads();
    return FALSE;
  }
  // Increase thread priority to prioritize mux message handling.
  // Minimum task priority is 100, even if configured otherwise
  // in registry.
  if (m_dwInitThreadPrio == 0 || m_dwInitThreadPrio > 100)
    CeSetThreadPriority(m_hMuxMsgThread, 100);
  else
    CeSetThreadPriority(m_hMuxMsgThread, m_dwInitThreadPrio);

  dwDisableResync = 0;
  RegGetDeviceDWORD(m_szRegKey, REGVAL_MUXDRV_DISABLE_RESYNC, &dwDisableResync);
  if (!dwDisableResync) {
    m_hMuxTimebaseThread = CreateThread(NULL, 0, ThreadMuxTimebase, this, 0, NULL);
    if (m_dwInitThreadPrio > 0)   // Set thread priority, if configured.
      CeSetThreadPriority(m_hMuxTimebaseThread, m_dwInitThreadPrio);
  }

  // Check if we have to handle more mux channels than last time
  if (m_dwMuxInstance && (m_dwMaxDlci > m_dwLastMaxDlci)) {
    Mux_Deregister(m_dwMuxInstance);
    m_dwMuxInstance = 0;
  }

  dwMaxMuxVersion  = MAXDWORD;
  dwHdlcWindowSize = DEFAULT_HDLC_WINDOW_SIZE;
  dwHdlcFrameSize  = MAXFRAMESIZE;
  RegGetDeviceDWORD(m_szRegKey, REGVAL_MUXDRV_MAX_MUXVERSION, &dwMaxMuxVersion);
  RegGetDeviceDWORD(m_szRegKey, REGVAL_MUXDRV_HDLC_WND_SIZE, &dwHdlcWindowSize);
  RegGetDeviceDWORD(m_szRegKey, REGVAL_MUXDRV_HDLC_FRM_SIZE, &dwHdlcFrameSize);
  dwHdlcWindowSize = MINMAX(dwHdlcWindowSize, 1, 7);
  dwHdlcFrameSize  = MINMAX(dwHdlcFrameSize, DEF_MAXFRAMESIZE, MAXFRAMESIZE);

  // Init the mux protocol handling
  if (!m_dwMuxInstance) {
    CallBacks.pMuxGetMem             = MuxGetMem;
    CallBacks.pMuxFreeMem            = MuxFreeMem;
    CallBacks.pMuxStartResult        = MuxStartResult;
    CallBacks.pMuxMsgAvailable       = MuxMsgAvailable;
    CallBacks.pMuxReset              = MuxReset;
    CallBacks.pMuxGetTickCount       = MuxGetTickCount;
    CallBacks.pMuxLock               = MuxLock;
    CallBacks.pMuxUnlock             = MuxUnlock;
    CallBacks.pMuxDevSendData        = MuxDevSendData;
    CallBacks.pMuxDevActivate        = MuxDevActivate;
    CallBacks.pMuxDevGetFreeTxBytes  = MuxDevGetFreeTxBytes;
    CallBacks.pMuxDevSetQueueSizes   = MuxDevSetQueueSizes;
    if (Mux_Register((DWORD)this, &CallBacks, m_dwMaxDlci, dwMaxMuxVersion, &m_dwMuxInstance) || !m_dwMuxInstance) {
      // Something went wrong
      StopThreads();
      return FALSE;
    }
    m_dwLastMaxDlci = m_dwMaxDlci;
  }

  // Give module some time to prepare the mux mode
  Sleep(25);

  // Start the mux protocol handling
  ResetEvent(m_hStartStopEvent);
  m_dwStartResult = 0;
  Mux_Init(m_dwMuxInstance, dwHdlcWindowSize, dwHdlcFrameSize, IsUsbPort(szBasePort) ? 0 : dwBaudRate);
  if (Mux_Start(m_dwMuxInstance)) {
    // Something went wrong
    StopThreads();
    return FALSE;
  }

  // Wait for the mux protocol to be started (20 sec.)
  if (WaitForSingleObject(m_hStartStopEvent, 20000) != WAIT_OBJECT_0) {
    // We timed out
    StopThreads();
    return FALSE;
  }

  // Check if everything was ok
  if (m_dwStartResult) {
    StopMuxMode();
    return FALSE;
  }

  // If necessary, set task priorities back to the default value.
  if (m_dwDefaultThreadPrio > 0) {
    CeSetThreadPriority(m_hReadThread, m_dwDefaultThreadPrio);
    if (!dwDisableResync) {
      CeSetThreadPriority(m_hMuxTimebaseThread, m_dwDefaultThreadPrio);
    }
    CeSetThreadPriority(GetCurrentThread(), m_dwDefaultThreadPrio);
  }

  // We need a short delay here to allow mux version negotiation to finish to avoid
  // that mux channels opened too early are using the wrong version number.
  Sleep(100);

  return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
// Stop the multiplex mode.
//
// Parameters:
// None.
//
// Return:
// TRUE indicates success. FALSE indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
BOOL CBasePort::StopMuxMode(void) {
BOOL fRet = TRUE;

  if (!IsModuleLost()) {
    // Close the mux protocol
    ResetEvent(m_hStartStopEvent);
    Mux_Stop(m_dwMuxInstance);

    // Wait for the mux protocol to be stopped
    if (WaitForSingleObject(m_hStartStopEvent, 5000) != WAIT_OBJECT_0) {
      // We timed out
      fRet = FALSE;
    }
  } else {
    fRet = FALSE;
  }

  // Finally stop the running threads
  if (!StopThreads()) {
    fRet = FALSE;
  }

  // From here we cannot use DSR to monitor if module is alive because we don't know
  // if somebody has sent AT&S1 on the first mux channel which is used on the hardware
  // port as well after closing mux mode.
  m_fMonitorDsr = FALSE;

  return fRet;
}

//////////////////////////////////////////////////////////////////////////////
//
// Callback function from the multiplex protocol implementation.
// Called if the mux start has finished.
//
// Parameters:
// dwMuxInstance: Handle identifying the multiplex instance.
// dwUserData   : User parameter used for access to our class instance.
// dwResult     : The result of the mux start operation.
//
// Return:
// None.
//
//////////////////////////////////////////////////////////////////////////////
void CBasePort::MuxStartResult(DWORD dwMuxInstance, DWORD dwUserData, DWORD dwResult) {
  ((CBasePort*)dwUserData)->m_dwStartResult = dwResult;
  SetEvent(((CBasePort*)dwUserData)->m_hStartStopEvent);
}

//////////////////////////////////////////////////////////////////////////////
//
// Callback function from the multiplex protocol implementation.
// Called to send data out to the hardware port.
//
// Parameters:
// dwMuxInstance: Handle identifying the multiplex instance.
// dwUserData   : User parameter used for access to our class instance.
// pData        : Pointer to the data to write.
// dwDataLen    : The number of bytes to write.
//
// Return:
// The number of bytes written.
//
//////////////////////////////////////////////////////////////////////////////
DWORD CBasePort::MuxDevSendData(DWORD dwMuxInstance, DWORD dwUserData, PBYTE pData, DWORD dwDataLen) {
DWORD  dwWritten;
DWORD  dwRet = 0;

  if (dwUserData == NULL) {
    MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: !!! FATAL !!! CBasePort::MuxDevSendData() dwUserData == 0 \r\n")));
    return 0;
  }

  if (!((CBasePort*)dwUserData)->IsModuleLost()) {
    // reserve com port access
    ((CBasePort*)dwUserData)->Reserve();

    while (dwDataLen) {
      if (!WriteFile(((CBasePort*)dwUserData)->m_hPort, pData, dwDataLen, &dwWritten, NULL)) {
        // release com port access
        ((CBasePort*)dwUserData)->Release();

        return 0;
      }
      MUXDBG(ZONE_MUX_RAW_DATA, (TEXT("MuxDrv: %d bytes written:\r\n"), dwWritten));
      MUXDBGHEX(ZONE_MUX_RAW_DATA, pData, dwWritten);
      dwDataLen -= dwWritten;
      pData += dwWritten;
      dwRet += dwWritten;
    }

    // release com port access
    ((CBasePort*)dwUserData)->Release();
  } else {
    dwRet = dwDataLen;
  }

  return dwRet;
}

//////////////////////////////////////////////////////////////////////////////
//
// Callback function from the multiplex protocol implementation.
// Activate the mux message handling after the amount of data in the hardware
// ports tx queue has fallen below the given border.
//
// Parameters:
// dwMuxInstance: Handle identifying the multiplex instance.
// dwUserData   : User parameter used for access to our class instance.
// dwLen        : Number of bytes in the tx queue to reactivate the mux
//                message handling.
//
// Return:
// None.
//
//////////////////////////////////////////////////////////////////////////////
void CBasePort::MuxDevActivate(DWORD dwMuxInstance, DWORD dwUserData, DWORD dwLen) {
CBasePort*  pInstance = (CBasePort*)dwUserData;

  SYNCBLOCK(pInstance->m_csCheckSendQueueThread);
  if (!pInstance->m_hCheckSendQueueThread) {
    if (!pInstance->IsModuleLost()) {
      pInstance->m_dwLenRestart = dwLen;
      pInstance->m_hCheckSendQueueThread = CreateThread(NULL, 0, pInstance->ThreadCheckSendQueue, (LPVOID)dwUserData, 0, NULL);
      if (pInstance->m_dwDefaultThreadPrio > 0) {
        CeSetThreadPriority(pInstance->m_hCheckSendQueueThread, pInstance->m_dwDefaultThreadPrio);
      }
    }
  }
}

//////////////////////////////////////////////////////////////////////////////
//
// Callback function from the multiplex protocol implementation.
// Return the data available in the hardware port's transmit queue.
//
// Parameters:
// dwMuxInstance: Handle identifying the multiplex instance.
// dwUserData   : User parameter used for access to our class instance.
//
// Return:
// The number of free bytes in the tx buffer.
//
//////////////////////////////////////////////////////////////////////////////
DWORD CBasePort::MuxDevGetFreeTxBytes(DWORD dwMuxInstance, DWORD dwUserData) {
DWORD    dwError;
COMSTAT  ComStat;

  if (dwUserData == NULL) {
    MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: !!! FATAL !!! CBasePort::MuxDevGetFreeTxBytes() dwUserData == 0 \r\n")));
    return 0;
  }

  // reserve com port access
  ((CBasePort*)dwUserData)->Reserve();

  if (!((CBasePort*)dwUserData)->IsModuleLost()) {
    if (ClearCommError(((CBasePort*)dwUserData)->m_hPort, &dwError, &ComStat)) {
      // release com port access
      ((CBasePort*)dwUserData)->Release();

      return (((CBasePort*)dwUserData)->m_dwTxQueueSize - ComStat.cbOutQue);
    }
    MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv -> CBasePort::MuxDevGetFreeTxBytes() ClearCommError() failed \r\n")));
  }

  // release com port access
  ((CBasePort*)dwUserData)->Release();

  return 0;
}

//////////////////////////////////////////////////////////////////////////////
//
// Callback function from the multiplex protocol implementation.
// Set the queue sizes of the hardware port.
//
// Parameters:
// dwMuxInstance: Handle identifying the multiplex instance.
// dwUserData   : User parameter used for access to our class instance.
// dwRxSize     : New size of the serial hardware devices in queue.
// dwTxSize     : New size of the serial hardware devices out queue.
//
// Return:
// None.
//
//////////////////////////////////////////////////////////////////////////////
void CBasePort::MuxDevSetQueueSizes(DWORD hMuxInstance, DWORD dwUserData, DWORD dwRxSize, DWORD dwTxSize) {
  if (dwUserData == NULL) {
    MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: !!! FATAL !!! CBasePort::MuxDevSetQueueSizes() dwUserData == 0 \r\n")));
    return;
  }

  // reserve com port access
  ((CBasePort*)dwUserData)->Reserve();

  if (SetupComm(((CBasePort*)dwUserData)->m_hPort, dwRxSize, dwTxSize)) {
    ((CBasePort*)dwUserData)->m_dwTxQueueSize = dwTxSize;
  } else {
    COMMPROP cCommProp;
    DWORD dwLastError = GetLastError();

    memset( &cCommProp, 0, sizeof( COMMPROP ) );

    if ( GetCommProperties(((CBasePort*)dwUserData)->m_hPort, &cCommProp ) == TRUE ) {
      if ( cCommProp.dwCurrentTxQueue == 0 && cCommProp.dwMaxTxQueue == 0 ) {
        MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: !!! FATAL !!! CBasePort::MuxDevSetQueueSizes() m_dwTxQueueSize requires default value \r\n")));
      } else {
        if ( cCommProp.dwCurrentTxQueue < cCommProp.dwMaxTxQueue ) {
          ((CBasePort*)dwUserData)->m_dwTxQueueSize = cCommProp.dwCurrentTxQueue;
        } else {
          ((CBasePort*)dwUserData)->m_dwTxQueueSize = cCommProp.dwMaxTxQueue;
        }
        // ASSERT( pMux->MP_uiUsedProtVersion != MP_REVISION_04 && ((CBasePort*)dwUserData)->m_dwTxQueueSize < 64 );
      }
    } else {
      MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: !!! FATAL !!! CBasePort::MuxDevSetQueueSizes() m_dwTxQueueSize not initialized \r\n")));
    }
  }
  // release com port access
  ((CBasePort*)dwUserData)->Release();
}

//////////////////////////////////////////////////////////////////////////////
//
// Read data from port and feed it into the mux protocol handling.
//
// Parameters:
// lpParameter: Pointer to the corresponding class instance.
//
// Return:
// 0 indicates success, any other value indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
DWORD WINAPI CBasePort::ThreadReadData(LPVOID lpParameter) {
CBasePort*  pInstance = (CBasePort*)lpParameter;
DWORD       dwRead;
DWORD       dwLastRead;
BYTE        bBuf[READ_BUFFER_SIZE];

  // Read all data from port until we're stopped
  while (WaitForSingleObject(pInstance->m_hCloseEvent, 0) == WAIT_TIMEOUT) {
    // reserve com port access
    pInstance->Reserve();
    dwRead = 0;
    if (ReadFile(pInstance->m_hPort, bBuf, READ_BUFFER_SIZE, &dwRead, NULL)) {
      if (dwRead) {
        MUXDBG(ZONE_MUX_RAW_DATA, (TEXT("MuxDrv: %d bytes read:\r\n"), dwRead));
        MUXDBGHEX(ZONE_MUX_RAW_DATA, bBuf, dwRead);
        // Feed the read data into the mux protocol handling
        Dev_ReceiveData(pInstance->m_dwMuxInstance, bBuf, dwRead);
        dwLastRead = dwRead;
      }
    }
    // release com port access
    pInstance->Release();
  }

  if (pInstance->m_fModuleDied) {
    MUXDBG(ZONE_MODULE_EXIT, (TEXT("MuxDrv: Module went off, last received data:\r\n")));
    MUXDBGHEX(ZONE_MODULE_EXIT, bBuf, dwRead ? dwRead : dwLastRead);
  }

  return 0;
}

//////////////////////////////////////////////////////////////////////////////
//
// Monitor the DSR signal to detect unexpected module shutdown.
//
// Parameters:
// lpParameter: Pointer to the corresponding class instance.
//
// Return:
// 0 indicates success, any other value indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
DWORD WINAPI CBasePort::ThreadIsAlive(LPVOID lpParameter) {
CBasePort*  pInstance = (CBasePort*)lpParameter;
DWORD       dwEvent;

  // Initialize the COM wait mask
  SetCommMask(pInstance->m_hPort, ISALIVE_COMM_MASK);

  // Check modem signal until we're stopped
  while (WaitForSingleObject(pInstance->m_hCloseEvent, 0) == WAIT_TIMEOUT) {
    // reserve com port access
    pInstance->Reserve();

    // Wait for event on the COM port
    if (!WaitCommEvent(pInstance->m_hPort, &dwEvent, NULL) || dwEvent) {
      // Check if we're not already in shutdown process
      if (!pInstance->IsModuleLost()) {
        // Check if we lost the DSR signal and together with it the module
        if (!pInstance->IsModuleLost() && !pInstance->IsModulePowerOn()) {
          // We lost the DSR signal which can only mean that the module went off!
          MUXDBG(ZONE_GENERAL_INFO, (TEXT("MuxDrv: Unexpected module shutdown detected!\r\n")));

          // Shutdown mux protocol handling
          pInstance->ShutdownMux(FALSE);

          // release com port access
          pInstance->Release();

          return (DWORD)-1;
        }
      }
    }

    // release com port access
    pInstance->Release();
  }

  return 0;
}

//////////////////////////////////////////////////////////////////////////////
//
// Restart the data send process if there is again enough space availble in
// the send queue.
//
// Parameters:
// lpParameter: Pointer to the corresponding class instance.
//
// Return:
// 0 indicates success, any other value indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
DWORD WINAPI CBasePort::ThreadCheckSendQueue(LPVOID lpParameter) {
CBasePort*  pInstance = (CBasePort*)lpParameter;
DWORD       dwError;
DWORD       dwLenRestart;
DWORD       dwRet;
COMSTAT     ComStat;

  dwRet = 0;
  dwLenRestart = pInstance->m_dwLenRestart;
  while (TRUE) {
    ComStat.cbOutQue = 0;

    // reserve com port access
    pInstance->Reserve();

    if (!ClearCommError(pInstance->m_hPort, &dwError, &ComStat)) {
      // release com port access
      pInstance->Release();

      dwRet = (DWORD)-1;
      break;
    }

    // release com port access
    pInstance->Release();

    if (ComStat.cbOutQue <= dwLenRestart) {
      break;
    }
    if (WaitForSingleObject(pInstance->m_hCloseEvent, 2) != WAIT_TIMEOUT) {
      dwRet = (DWORD)-2;
      break;
    }
  }

  EnterCriticalSection(&pInstance->m_csCheckSendQueueThread);
  CloseHandle(pInstance->m_hCheckSendQueueThread);
  pInstance->m_hCheckSendQueueThread = NULL;
  LeaveCriticalSection(&pInstance->m_csCheckSendQueueThread);

  if (!dwRet) Dev_SendDataContinue(pInstance->m_dwMuxInstance, ComStat.cbOutQue);

  return dwRet;
}

//////////////////////////////////////////////////////////////////////////////
//
// Keep the message loop from the mux protocol handling alive.
//
// Parameters:
// lpParameter: Pointer to the corresponding class instance.
//
// Return:
// 0 indicates success, any other value indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
DWORD WINAPI CBasePort::ThreadMuxMsg(LPVOID lpParameter) {
CBasePort*  pInstance = (CBasePort*)lpParameter;
HANDLE      hEvents[2];

  hEvents[0] = pInstance->m_hCloseEvent;
  hEvents[1] = pInstance->m_hMuxMsgSemaphore;

  while (WaitForMultipleObjects(2, hEvents, FALSE, INFINITE) != WAIT_OBJECT_0) {
    Mux_MsgTick(pInstance->m_dwMuxInstance);
  }

  return 0;
}

//////////////////////////////////////////////////////////////////////////////
//
// Provide timebase for multiplex protocol handling.
//
// Parameters:
// lpParameter: Pointer to the corresponding class instance.
//
// Return:
// 0 indicates success, any other value indicates failure.
//
//////////////////////////////////////////////////////////////////////////////
DWORD WINAPI CBasePort::ThreadMuxTimebase(LPVOID lpParameter) {
CBasePort*  pInstance = (CBasePort*)lpParameter;

  while (WaitForSingleObject(pInstance->m_hCloseEvent, TIMEBASE_INTERVAL) != WAIT_OBJECT_0) {
    Mux_TimerTick(pInstance->m_dwMuxInstance);
  }
  return 0;
}
