/*
 * File: DxbMgt.cpp
 */

#include <stdio.h>
#include <windows.h>
#include <winbase.h>
#include <windef.h>
#include <winver.h>
#include <process.h>				// __beginthread

#if defined (MAKE_DLL)
#define API_DECL __declspec( dllexport ) WINAPI
#elif defined (MAKE_STATIC_LIB)
#define API_DECL WINAPI
#else
#pragma message ("ERROR: You must define one: MAKE_DLL | MAKE_STATIC_LIB")
#endif // #if defined (MAKE_DLL)

#include "dxbtypes.h"		// Data types used for cross platform development
#include "macros.h"
#include "pnpfilesandids.h"
#include "DxbShim.h"
#include "DxbMgtInternal.h"
#include "DxbMgt.h"
#include "DxbRegHndlr.h"

// global data, unique per calling process
static BOOL fInitialized = FALSE;
static DWORD gnBoards = 0;
static MGT_DEVICE_DESCRIPTOR_T MgtDevices[MAX_BOARDS] = {0};

static DWORD nProcessAttachCount = 0;	// For debugging only
static DWORD nThreadAttachCount = 0;	// For debugging only

/*
 * Trace
 */

HANDLE hListLockSemaphore = NULL;

typedef struct TRACE_DESCRIPTOR
{
	// From the client
	TRC_DATACOPY_CB	cb;
	TRCARG				hTrace;
	// Specific to this trace, MGMT device
	HANDLE				hMGMT;

	DWORD					Board;
	// Shutdown handling
	BOOL *				pfTerminate;
	// List management
	TRACE_DESCRIPTOR * pflink;
	TRACE_DESCRIPTOR * pblink;
}TRACE_DESCRIPTOR_T;

static TRACE_DESCRIPTOR_T * pHead = NULL;
static BOOL fLocked = FALSE;	// simple list lock

static inline BOOL LockList(void)
{
	BOOL Success;
	// Wait twice as long as the default, this allows a single operation to time out before we have a fail
	DWORD Result = WaitForSingleObject(hListLockSemaphore, DEFAULT_TIMEOUT_MS*2);
 	if (WAIT_OBJECT_0 != Result)
	{
		if (WAIT_ABANDONED == Result)
			DebugMessage0("Wait was abandoned while locking list\n");
		if (WAIT_TIMEOUT == Result)
			DebugMessage0("Wait timed out while locking list\n");
		Success = FALSE;
		goto Exit;
	}

	Success = TRUE;
Exit:
	return Success;
}

static inline void UnLockList(void)
{
	(void)ReleaseSemaphore(hListLockSemaphore,1,NULL);
}

static inline TRACE_DESCRIPTOR_T * const GetHeadDesc(void)
{
	return pHead;
}

static inline void SetHeadDesc(TRACE_DESCRIPTOR_T * const pDesc)
{
	pHead = pDesc;
}

static inline TRACE_DESCRIPTOR_T * GetNextDesc(TRACE_DESCRIPTOR_T * const pCurrentDesc)
{
	TRACE_DESCRIPTOR_T * pDesc = NULL;
	
	if (NULL == pCurrentDesc)
	{
		DebugMessage0("Current descriptor is NULL, cannot get next\n");
		goto Exit;
	}

	pDesc = pCurrentDesc->pflink;

	if (NULL == pDesc)
		DebugMessage0("Already at tail of trace descriptor list\n");

Exit:	
	return pDesc;
}

static inline TRACE_DESCRIPTOR_T * GetPreviousDesc(TRACE_DESCRIPTOR_T * const pCurrentDesc)
{
	TRACE_DESCRIPTOR_T * pDesc = NULL;
	if (NULL == pCurrentDesc)
	{
		DebugMessage0("Current descriptor is NULL, cannot get previous\n");
		goto Exit;
	}

	pDesc = pCurrentDesc->pblink;

	if (NULL == pDesc)
		DebugMessage0("Already at head of trace descriptor list\n");

Exit:	
	return pDesc;
}

static inline BOOL AddDesc(TRACE_DESCRIPTOR_T * pDesc)
{
	BOOL Success;
	TRACE_DESCRIPTOR_T * pHeadDesc;

	if (NULL == pDesc)
	{
		DebugMessage0("pDesc is NULL for AddDesc()\n");
		Success = FALSE;
		goto Exit;
	}

	if (NULL == (pHeadDesc = GetHeadDesc()))
	{
		// This is the first element added.
		pDesc->pflink = NULL;
	}
	else
	{
		pDesc->pflink = pHeadDesc;
		pHeadDesc->pblink = pDesc;
	}

	// Always add to the head
	SetHeadDesc(pDesc);

	// Head is element with pblink == NULL
	pDesc->pblink = NULL;

	Success = TRUE;
Exit:
	return Success;
}

static __inline BOOL RemoveDesc(TRACE_DESCRIPTOR_T * const pDesc)
{
	BOOL Success;
	if (NULL == pDesc)
	{
		DebugMessage0("Current descriptor is NULL, cannot get next\n");
		Success = FALSE;
		goto Exit;
	}

	if (NULL != pDesc->pblink)
		pDesc->pblink->pflink = pDesc->pflink;
	else
		SetHeadDesc(pDesc->pflink);

	if (NULL != pDesc->pflink)
		pDesc->pflink->pblink = pDesc->pblink;

	Success = TRUE;
Exit:	
	return Success;
}


static void __cdecl
TraceThread(void * arglist)
{
	TRACE_DESCRIPTOR_T * pTraceDescriptor = (TRACE_DESCRIPTOR_T *)arglist;
	SHIM_STATUS Status;
	BOOL fTerminate = FALSE;

	TRC_DATACOPY_CB	cb = pTraceDescriptor->cb;
	TRCARG				hTrace = pTraceDescriptor->hTrace;
	HANDLE				hMGMT = pTraceDescriptor->hMGMT;
	DWORD					Board = pTraceDescriptor->Board;

#define MAX_MESSAGE_PACKET 0x100
	unsigned char Msg[MAX_MESSAGE_PACKET];
	DWORD MsgLength;
#define MAX_TRACE_PACKET 0x1000
	unsigned char DataBuf[MAX_TRACE_PACKET];
	DWORD DataLength;

	if (NULL == arglist)
	{
		DebugMessage0("TraceThread: Argument list is NULL\n");
		goto Exit;
	}

	pTraceDescriptor->pfTerminate = &fTerminate;

	for (;;)
	{
		memset(&Msg[0], 0, sizeof Msg);
		memset(&DataBuf[0], 0, sizeof DataBuf);

		if (fTerminate)
		{
			// Don't touch anything!
			DebugMessage0("TraceThread: User terminated\n");
			goto Exit;
		}

		// Blocking call, for cancel we purge from another thread
		Status = DxbStreamGetMsg(	cfgGetBoardHandle(Board),hMGMT,&Msg,sizeof Msg,&MsgLength,DataBuf,sizeof DataBuf,&DataLength,MSG_PRIORITY_LOW,0,NULL,NULL);
		if (SHIM_STATUS_SUCCESS != Status)
		{
			DebugMessage1("TraceThread: GetMsg returned 0x%x\n", Status);
			if (!fTerminate)
			{
				pTraceDescriptor->pfTerminate = NULL;	// About to go away...
				cb(NULL, 0, hTrace);	// Let the upper layer know we're terminating.
				DebugMessage0("TraceThread: Trace terminating due to error\n");
			}
			goto Exit;
		}

		if (fTerminate)
		{
			// Don't touch anything!
			DebugMessage0("TraceThread: User terminated\n");
			goto Exit;
		}

		// Send off the data
		cb((uint8 *)(&DataBuf[0]), DataLength, hTrace);
	}

Exit:
	if (!fTerminate)
	{
		// Close the MGMT device
		DxbStreamClose(cfgGetBoardHandle(Board),hMGMT,DEFAULT_TIMEOUT_MS,NULL,NULL);
		if (LockList())
		{
			RemoveDesc(pTraceDescriptor);
			free(pTraceDescriptor); 
			UnLockList();
		}
	}

	DebugMessage0("Trace: Thread is exiting\n");
	return;
}

static inline uint32
XlateShimToMgtError(SHIM_STATUS const Status)
{
	uint32 ReturnValue;

	switch(Status)
	{
	default:
		ReturnValue = MGMT_SHIM_ERROR_UNKNOWN;
		break;

	case SHIM_STATUS_SUCCESS:
		ReturnValue = MGMT_OK;
		break;

#ifdef XLATE_IMPLEMENTED
	case UNKNOWN:
		ReturnValue = BAD_COMMAND;
		break;
	case UNKNOWN:
		ReturnValue = BAD_DFC;
		break;
	case UNKNOWN:
		ReturnValue = BAD_PARAMETER;
		break;
	case UNKNOWN:
		ReturnValue = BAD_DEVICE;
		break;
	case UNKNOWN:
		ReturnValue = OUT_OF_MEMORY;
		break;

  // MGMT device handling errors
	case UNKNOWN:
		ReturnValue = NO_MGMT_CHAN;
		break;
	case UNKNOWN:
		ReturnValue = REPLY_ORUN;
		break;
	case UNKNOWN:
		ReturnValue = NO_SUCH_BOARD;
		break;
	case UNKNOWN:
		ReturnValue = NO_SUCH_TRACE;
		break;
	case UNKNOWN:
		ReturnValue = DUPLICATE_TRACE_HANDLE;
		break;
	case UNKNOWN:
		ReturnValue = CANNOT_OPEN_TRACE_CHAN;
		break;
#else // #ifdef XLATE_IMPLEMENTED
// NOTE: NOTE: Translate message is not implemented
#endif // #ifdef XLATE_IMPLEMENTED
	}
	return ReturnValue;
}

static inline BOOL
ValidateBoardNo(DWORD const BoardNo)
{
	// Unsigned...
	return (fInitialized && (BoardNo <= gnBoards) && (BoardNo > 0));
}

void API_DECL
ShimShutdown(void)
{
	SHIM_STATUS Status;
	DWORD nCount;

	if (!fInitialized)
	{
		DebugMessage0("Running Shutdown(), but not initialized...\n");
		goto Exit;
	}

	DebugMessage0("Running Shutdown()\n");

	// Trundle through the trace list with a shutdown
	{
		TRACE_DESCRIPTOR_T * pCurrentDesc;
		TRACE_DESCRIPTOR_T * pNextDesc;

		pCurrentDesc = GetHeadDesc();
		while (NULL != pCurrentDesc)
		{
			pNextDesc = GetNextDesc(pCurrentDesc);
			if (NULL != pCurrentDesc->pfTerminate)
				*pCurrentDesc->pfTerminate = TRUE;
			free(pCurrentDesc);
			pCurrentDesc = pNextDesc;
		}
	}

	for (nCount = 0; nCount < MAX_BOARDS; nCount++)
	{
		if (NULL != MgtDevices[nCount].DosDeviceHandle)
		{
			Status = DxbDeviceClose(MgtDevices[nCount].DosDeviceHandle);
			if (Status != SHIM_STATUS_SUCCESS){
				DebugMessage1("Couldn't close DOS Device for board: %d\n",nCount+1);}
		}
		// Clear all of it out...
		ZeroMemory(&MgtDevices[nCount], sizeof (MGT_DEVICE_DESCRIPTOR_T));
	}
	DebugMessage0("Done with Shutdown()\n");
	fInitialized = FALSE;

Exit:
	return;
}

static inline BOOL
Initialize(void)
{
	uint32 nCount;
	SHIM_STATUS Status;
	TCHAR StringBuffer[MAX_DOS_DEVICE_NAME];

	// allow re-enumeration...
	if (fInitialized)
		ShimShutdown();

	gnBoards = 0;

	// determine number of adapters configured in the system (what _should_ be there)
	if (!regEnumerateBoards(&gnBoards))
	{
		gnBoards = 0;
		DebugMessage0("Couldn't enumerate boards\n");
		goto ErrorExit;
	}
	DebugMessage1("Global number of boards: %d\n", gnBoards);

	// open NT devices (via CreateFile)
	for (nCount = 0; nCount < gnBoards; nCount++)
	{
		if (regGetDosDeviceName(nCount+1, &StringBuffer[0], sizeof StringBuffer))
		{
			HANDLE hDevice;
			Status = DxbDeviceOpen(	&StringBuffer[0], &hDevice);
			if (SHIM_STATUS_SUCCESS != Status)
			{
				// Not a fatal error, other board/drivers could be alive and well
				DebugMessage2("Couldn't open %s, board:%d\n", StringBuffer, nCount+1);
			}
			else
			{
				MgtDevices[nCount].DosDeviceHandle = hDevice;
				{
					HANDLE hMgmt;
					LONG ErrorCode;
					// open board's MGMT device (via DStreams)
					Status = DxbStreamOpen(	MgtDevices[nCount].DosDeviceHandle,
													MGMT_MODULE_NAME,
													&hMgmt,
													&ErrorCode,
													DEFAULT_TIMEOUT_MS,
													NULL,
													NULL);
					if (SHIM_STATUS_SUCCESS != Status)
					{
						DebugMessage1("Couldn't open MGMT on board: %d\n", nCount+1);
						MgtDevices[nCount].MgmtDeviceHandle = NULL;
					}
					else
					{
						DebugMessage1("Opened MGMT on board: %d\n", nCount+1);
						MgtDevices[nCount].MgmtDeviceHandle = hMgmt;
					}
				}
			}
		}
	}

	// set initialized state
	fInitialized = TRUE;

ErrorExit:
	return fInitialized;
}

static inline BOOL
GetDriverVersionString(char const * const pPath, char * const pVersionString, unsigned int cbMaxVersionStringSize)
{
	BOOL Success = FALSE;

	DWORD Handle, VersionInfoSize;
	char VersionInfo[MAX_VER_INFO_SIZE];	// Buffer for resource read from file
	void * pFileVersionValue;
	void * pBuildValue;
	unsigned int cbBuffer;

	if (	(NULL == pPath) ||
			(NULL == pVersionString) ||
			(0 == cbMaxVersionStringSize)
		)
		goto Exit;

	if (	(0 != (VersionInfoSize = GetFileVersionInfoSize((LPTSTR)pPath, &Handle))) &&
			(MAX_VER_INFO_SIZE > VersionInfoSize) &&
			(GetFileVersionInfo((LPTSTR)pPath, Handle, sizeof VersionInfo, &VersionInfo[0])) &&
			(VerQueryValue(&VersionInfo[0], STRING_INFO_CODE_PAGE_VALUE  FILE_VERSION_VALUE_STRING, &pFileVersionValue, &cbBuffer)) &&
			(VerQueryValue(&VersionInfo[0], STRING_INFO_CODE_PAGE_VALUE  BUILD_NUMBER_VALUE_STRING, &pBuildValue, &cbBuffer))
		)
	{
		_snprintf(pVersionString, cbMaxVersionStringSize, VERSION_FORMAT_STRING, pFileVersionValue, pBuildValue);
		pVersionString[cbMaxVersionStringSize - 1] = '\0';
		Success = TRUE;
	}

Exit:
	return Success;
}

static inline BOOL
GetDriverVersions(uint32 const BoardNumber, mgmt_drv_vers_t * const pVers)
{
	BOOL Success = FALSE;

	typedef struct
	{
		char const * pName;
		char const * pPath;
		unsigned int type;
	} DRIVER_DATA;

	DRIVER_DATA const * pDrivers = NULL;
	int DriverCount = 0, i = 0;

	mgmt_board_info_t BoardInfo = {0};
	char VersionString[MAX_SPRINTF_BUF_SIZE];	// Final version string returned

	if (NULL == pVers)
		goto Exit;

	if (!regGetBoardInfo(BoardNumber, &BoardInfo))
		goto Exit;

	switch (BoardInfo.BoardType)
	{
		default:
		{
			DebugMessage1("Unrecognized board type: %d\n", BoardInfo.BoardType);
			goto Exit;
			break;
		}

		case ADAPTER_HOU30:		case ADAPTER_HOU60:		case ADAPTER_HOU24:		case ADAPTER_HOU48:
		case ADAPTER_HOU30cpci:	case ADAPTER_HOU60cpci:	case ADAPTER_HOU24cpci:	case ADAPTER_HOU48cpci:
		{
			static const DRIVER_DATA HoustonDrivers[] = 
			{
				{ WANLINKS_DXB_SYS,	    SYSTEM32_DRIVERS_DIR_NAME WANLINKS_DXB_SYS,	    MGMT_DXB_DRV_VER	},
				{ WANLINKS_ASYN_SYS,	SYSTEM32_DRIVERS_DIR_NAME WANLINKS_ASYN_SYS,	MGMT_SER_DRV_VER	},
				{ WANLINKS_ISDN_SYS,	SYSTEM32_DRIVERS_DIR_NAME WANLINKS_ISDN_SYS,	MGMT_ISDN_DRV_VER	},
				{ DIGI_SHIM,		    SYSTEM32_DIR_NAME DIGI_SHIM,				    MGMT_MGMT_DRV_VER	},
				{ DIGI_PAMA,	        SYSTEM32_DIR_NAME DIGI_PAMA,			        0				    },
			};
			
			pDrivers = &HoustonDrivers[0];
			DriverCount = ElementCount(HoustonDrivers);
			break;
		}

		case ADAPTER_LON_U:	case ADAPTER_LON_ST:
		{
			static const DRIVER_DATA LondonDrivers[] = 
			{
				{ WANLINKS_DXB_SYS,		SYSTEM32_DRIVERS_DIR_NAME WANLINKS_DXB_SYS,	    MGMT_DXB_DRV_VER	},
				{ WANLINKS_ASYN_SYS,	SYSTEM32_DRIVERS_DIR_NAME WANLINKS_ASYN_SYS,	MGMT_SER_DRV_VER	},
				{ WANLINKS_ISDN_SYS,	SYSTEM32_DRIVERS_DIR_NAME WANLINKS_ISDN_SYS,	MGMT_ISDN_DRV_VER	},
				{ DIGI_SHIM,		    SYSTEM32_DIR_NAME DIGI_SHIM,				    MGMT_MGMT_DRV_VER	},
				{ DIGI_PAMA,	        SYSTEM32_DIR_NAME DIGI_PAMA,			        0					},
			};

			pDrivers = &LondonDrivers[0];
			DriverCount = ElementCount(LondonDrivers);
			break;
		}

		case ADAPTER_CHASKA40:	case ADAPTER_CHASKA41:	case ADAPTER_CHASKA42:	case ADAPTER_CHASKA43:	case ADAPTER_CHASKA44:	case ADAPTER_CHASKA45:
		case ADAPTER_CHASKA80:	case ADAPTER_CHASKA81:	case ADAPTER_CHASKA82:	case ADAPTER_CHASKA83:	case ADAPTER_CHASKA84:	case ADAPTER_CHASKA85:
		{
			static const DRIVER_DATA ChaskaDrivers[] = 
			{
				{ WANLINKS_DXB_SYS,		SYSTEM32_DRIVERS_DIR_NAME WANLINKS_DXB_SYS,	    MGMT_DXB_DRV_VER	},
				{ WANLINKS_ASYN_SYS,	SYSTEM32_DRIVERS_DIR_NAME WANLINKS_ASYN_SYS,	MGMT_SER_DRV_VER	},
				{ DIGI_SHIM,		    SYSTEM32_DIR_NAME DIGI_SHIM,				    MGMT_MGMT_DRV_VER	},
				{ DIGI_PAMA,	        SYSTEM32_DIR_NAME DIGI_PAMA,			        0					},
			};

			pDrivers = &ChaskaDrivers[0];
			DriverCount = ElementCount(ChaskaDrivers);
			break;
		}

		case ADAPTER_AP2K2:	case ADAPTER_AP2K4:	case ADAPTER_AP2K8:	case ADAPTER_AP2K16:
		{
			static const DRIVER_DATA A2KDrivers[] = 
			{
				{ WANLINKS_DXB_SYS,		SYSTEM32_DRIVERS_DIR_NAME WANLINKS_DXB_SYS,	    MGMT_DXB_DRV_VER	},
				{ WANLINKS_ASYN_SYS,	SYSTEM32_DRIVERS_DIR_NAME WANLINKS_ASYN_SYS,	MGMT_SER_DRV_VER	},
				{ DIGI_SHIM,		    SYSTEM32_DIR_NAME DIGI_SHIM,				    MGMT_MGMT_DRV_VER	},
				{ DIGI_PAMA,	        SYSTEM32_DIR_NAME DIGI_PAMA,			        0				    },
			};

			pDrivers = &A2KDrivers[0];
			DriverCount = ElementCount(A2KDrivers);
			break;
		}
	}

	if (	(NULL == pDrivers) ||
			(0 == DriverCount)
		)
		goto Exit;

	ZeroMemory(pVers, sizeof (mgmt_drv_vers_t));	// Do once, only if there is at least one driver

	for (i = 0; (i < DriverCount) && (pVers->driverCount < ElementCount(pVers->drivers)); i++)
	{
		if (GetDriverVersionString(pDrivers[i].pPath, VersionString, sizeof VersionString))
		{
			strcpy((char *)pVers->drivers[pVers->driverCount].name, pDrivers[i].pName);
			strcpy((char *)pVers->drivers[pVers->driverCount].version, &VersionString[0]);
			pVers->drivers[pVers->driverCount].type = pDrivers[i].type;
			pVers->driverCount++;
		}
	}

	Success = TRUE;

Exit:
	return Success;
}

static inline uint32
LocalSatisfyRequest(uint32 BoardNumber,
						  uint16 Command,
						  void *mpkt,
						  uint16 mpktlen,
						  void *rpkt,
						  uint16	rpktlen)
{
	uint32 ReturnCode = MGMT_OK;

	if (NULL == rpkt)
	{
		DebugMessage0("NULL return packet pointer in LocalSatisfyRequest()\n");
		ReturnCode = BAD_PARAMETER;
		goto Exit;
	}


	switch (Command)
	{
	default:
		ReturnCode = BAD_COMMAND;
		break;

	case GET_MGMT_BOARD_NO:
		if (sizeof(uint32) != rpktlen)
		{
			DebugMessage0("Improper message size for GET_MGMT_BOARD_NO\n");
			ReturnCode = BAD_DFC; // Bad rpkt size
			break;
		}
		*((uint32 *)(rpkt)) = gnBoards;
		break;

	case GET_MGMT_BOARD_INFO:
		if (sizeof(mgmt_board_info_t) != rpktlen)
		{
			DebugMessage0("Bad return packet length for: GET_MGMT_BOARD_INFO\n");
			ReturnCode = BAD_DFC; // Bad rpkt size
			break;
		}
		if(!ValidateBoardNo(BoardNumber))
		{
			DebugMessage0("Invalid board number\n");
			ReturnCode = BAD_DEVICE;
			break;
		}
		ReturnCode = regGetBoardInfo(BoardNumber, rpkt) ? MGMT_OK : MGMT_SHIM_ERROR_UNKNOWN ;
		break;

	case GET_MGMT_SHIM_VER:
		if (sizeof(uint32) != rpktlen)
		{
			DebugMessage0("Bad return packet length for: GET_MGMT_SHIM_VER\n");
			ReturnCode = BAD_DFC; // Bad rpkt size
			break;
		}
		*((uint32 *)(rpkt)) = MGMT_SHIM_VERSION;
		break;

	case GET_MGMT_DRV_VERS:
		if (sizeof(mgmt_drv_vers_t) != rpktlen)
		{
			DebugMessage0("Bad return packet length for: GET_MGMT_DRV_VERS\n");
			ReturnCode = BAD_DFC; // Bad rpkt size
			break;
		}
		if (!ValidateBoardNo(BoardNumber))
		{
			DebugMessage0("Invalid board number\n");
			ReturnCode = BAD_DEVICE;
			break;
		}
		ReturnCode = GetDriverVersions(BoardNumber, (mgmt_drv_vers_t *)rpkt) ? MGMT_OK : MGMT_SHIM_ERROR_UNKNOWN ;
		break;
	}

Exit:
return ReturnCode;
}

static inline uint32
SatisfyRequest(uint32 BoardNumber,
					uint16 Command,
					void *mpkt,
					uint16 mpktlen,
					void *rpkt,
					uint16	rpktlen)
{
	SHIM_STATUS Status;
	uint32 ReturnCode;
	LONG ReturnValue;
	LONG ErrorCode;

	if (!ValidateBoardNo(BoardNumber))
	{
		ReturnCode = NO_SUCH_BOARD;
		goto Exit;
	}
	if (	(NULL == MgtDevices[BoardNumber-1].DosDeviceHandle) ||
			(NULL == MgtDevices[BoardNumber-1].MgmtDeviceHandle)
		)
	{
		DebugMessage0("Invalid handle: NO_SUCH_BOARD\n");
		ReturnCode = NO_SUCH_BOARD;
		goto Exit;
	}

	Status = DxbStreamIoctl(MgtDevices[BoardNumber-1].DosDeviceHandle,
									MgtDevices[BoardNumber-1].MgmtDeviceHandle,
									Command,
									0,		// Default time-out
									mpkt,
									mpktlen,
									rpkt,
									rpktlen,
									NULL,		// shim checks cbMaxDataOut == cbDataOut
									&ReturnValue,
									&ErrorCode,
									DEFAULT_TIMEOUT_MS,
									NULL,
									NULL
									);

	// Use the IOCTL's error code if possible
	ReturnCode = (0 != ReturnValue) ? ErrorCode : XlateShimToMgtError(Status);
Exit:
	return ReturnCode;
}

static inline uint32
StopTrace (	uint32 BoardNumber,
				uint16 Command,
				void *mpkt,
				uint16 mpktlen,
				void *rpkt,
				uint16 rpktlen,
				TRC_DATACOPY_CB cb,			/* This is the function I call when traces come in. */
				TRCARG handle )				/* cb functions "handle" */
{
	SHIM_STATUS	Status;
	uint32		ReturnCode;
	TRACE_DESCRIPTOR_T * pDesc;
	DWORD			cbDataOut;
	long			ReturnValue;
	long			ErrorCode;

	BOOL fUnLock;

	if (!LockList())
	{
		ReturnCode = NO_SUCH_TRACE;
		fUnLock = FALSE;
		goto Exit;
	}
	fUnLock = TRUE;

	pDesc = GetHeadDesc();
	while (NULL != pDesc)
	{
		if (handle == pDesc->hTrace)
			break;
		pDesc = GetNextDesc(pDesc);
	}

	if (NULL == pDesc)
	{
		ReturnCode = NO_SUCH_TRACE;
		goto Exit;
	}

	// Tell the thread to terminate
	if (NULL != pDesc->pfTerminate)
		*pDesc->pfTerminate = TRUE;

	// Purge cancels pending I/O
	Status = DxbStreamPurge(cfgGetBoardHandle(pDesc->Board),pDesc->hMGMT,PURGE_ALL,DEFAULT_TIMEOUT_MS,NULL,NULL);
	if (SHIM_STATUS_SUCCESS != Status)
		DebugMessage1("Purge failed while stopping trace, Status:0x%x\n",Status);

	Status = DxbStreamIoctl(cfgGetBoardHandle(pDesc->Board),pDesc->hMGMT,SET_MGMT_STOP_TRC,0,mpkt,mpktlen,rpkt,rpktlen,&cbDataOut,&ReturnValue,&ErrorCode,DEFAULT_TIMEOUT_MS,NULL,NULL);
	if (SHIM_STATUS_SUCCESS != Status)
		DebugMessage1("Stop trace IOCTL failed while stopping trace, Status:0x%x\n",Status);

	Status = DxbStreamClose(cfgGetBoardHandle(pDesc->Board),pDesc->hMGMT,DEFAULT_TIMEOUT_MS,NULL,NULL);
	if (SHIM_STATUS_SUCCESS != Status)
		DebugMessage1("Close MGMT device failed while stopping trace, Status:0x%x\n",Status);

	RemoveDesc(pDesc);
	free(pDesc);

	ReturnCode = MGMT_OK;
Exit:
	if (fUnLock)
		UnLockList();
	return ReturnCode;
}

static inline uint32
StartTrace (uint32 BoardNumber,
				uint16 Command,
				void *mpkt,
				uint16 mpktlen,
				void *rpkt,
				uint16 rpktlen,
				TRC_DATACOPY_CB cb,			/* This is the function I call when traces come in. */
				TRCARG handle )				/* cb functions "handle" */
{
	SHIM_STATUS	Status;
	uint32		ReturnCode;
	TRACE_DESCRIPTOR_T * pDesc;
	DWORD			cbDataOut;
	long			ReturnValue;
	long			ErrorCode;

	// Several states are possible...
	BOOL fUnLock;				// List is locked, unlock at termination
	BOOL fStartThread;		// Add to list and start thread for new descriptor
	BOOL fFree;					// Descriptor was allocated, free on failure
	BOOL fRemove;				// Descriptor was placed on list, remove on failure
	BOOL fClose;				// New MGMT was opened

	if(!ValidateBoardNo(BoardNumber))
	{
		DebugMessage1("Invalid board number:%d in StartTrace()\n", BoardNumber);
		ReturnCode = BAD_PARAMETER;
		goto Exit;
	}

	// List is locked for the complete operation
	if (!LockList())
	{
		fUnLock = FALSE;
		ReturnValue = CANNOT_OPEN_TRACE_CHAN;
		goto Exit;
	}
	fUnLock = TRUE;

	// Tracing to this handle already?
	pDesc = GetHeadDesc();
	while (NULL != pDesc)
	{
		if (handle == pDesc->hTrace)
		{
			if (pDesc->Board != BoardNumber)
			{
				DebugMessage2("Trace(): Existing trace's board number doesn't match input parameter, existing: %d, input: %d\n",pDesc->Board,BoardNumber);
				ReturnCode = BAD_PARAMETER;
				goto Exit;
			}
			break;	// Param was found and OK
		}
		pDesc = GetNextDesc(pDesc);
	}

	fFree = FALSE;
	fStartThread = FALSE;
	fRemove = FALSE;

	if (NULL == pDesc)
	{
		// New trace request, use the passed in board in this case
		HANDLE BoardHandle = cfgGetBoardHandle(BoardNumber);
		if (NULL == BoardHandle)
		{
			DebugMessage0("Bad board handle in StartTrace()\n");
			ReturnCode = BAD_PARAMETER;
			goto Exit;
		}

		pDesc = (TRACE_DESCRIPTOR_T *)malloc(sizeof (TRACE_DESCRIPTOR_T));
		fFree = TRUE;

		pDesc->cb = cb;
		pDesc->hTrace = handle;
		pDesc->hMGMT = NULL;			// Filled below by DxbStreamOpen
		pDesc->Board = BoardNumber;
		pDesc->pfTerminate = NULL;	// Filled later by TraceThread
		pDesc->pflink = NULL;		// Filled later by AddDesc()
		pDesc->pblink = NULL;		// Filled later by AddDesc()

		Status = DxbStreamOpen(	BoardHandle,MGMT_MODULE_NAME,&pDesc->hMGMT,&ErrorCode,DEFAULT_TIMEOUT_MS,NULL,NULL);
		if (SHIM_STATUS_SUCCESS != Status)
		{
			DebugMessage1("Couldn't open MGMT on board: %d\n", BoardNumber);
			ReturnCode = CANNOT_OPEN_TRACE_CHAN;
			goto Exit;
		}
		fClose = TRUE;

		AddDesc(pDesc);
		fRemove = TRUE;

		fStartThread = TRUE;
	}

	// Tell MGMT to enable the desired trace
	Status = DxbStreamIoctl(cfgGetBoardHandle(pDesc->Board),pDesc->hMGMT,Command,0,mpkt,mpktlen,rpkt,rpktlen,&cbDataOut,&ReturnValue,&ErrorCode,DEFAULT_TIMEOUT_MS,NULL,NULL);
	if (SHIM_STATUS_SUCCESS != Status)
	{
		if (0 != ReturnValue)
			DebugMessage2("Couldn't add trace for board: %d, Shim Error: 0x%x\n", BoardNumber, Status);
		else
			DebugMessage2("Couldn't add trace for board: %d, DS IOCTL Error: 0x%x\n", BoardNumber, ErrorCode);
		ReturnCode = CANNOT_OPEN_TRACE_CHAN; // NOTE: NOTE: error code needs define...
		goto Exit;
	}

	if (fStartThread)
	{
		if (NULL == _beginthread( TraceThread, 0, pDesc ))
		{
			DebugMessage2("Couldn't start trace thread for board: %d, last error 0x%x\n", BoardNumber, GetLastError());
			ReturnCode = CANNOT_OPEN_TRACE_CHAN;
			goto Exit;
		}
	}

	fRemove = FALSE;
	fClose = FALSE;
	// fUnLock = TRUE;
	fFree = FALSE;
	ReturnCode = MGMT_OK;

Exit:
	if (fRemove)
		RemoveDesc(pDesc);
	if (fUnLock)
		UnLockList();
	if (fClose)
		(void)DxbStreamClose(cfgGetBoardHandle(pDesc->Board),pDesc->hMGMT,DEFAULT_TIMEOUT_MS,NULL,NULL);
	if (fFree)
		free(pDesc);

	return ReturnCode;
}

/***********************
 * NT DLL Specific API *
 ***********************/

BOOL APIENTRY DllMain(HANDLE hModule, 
                      DWORD  ul_reason_for_call, 
                      LPVOID lpReserved)
{
	switch( ul_reason_for_call )
	{
	default:
		DebugMessage0("DllMain: Bad ul_reason_for_call\n");
		break;

	case DLL_PROCESS_ATTACH:
		DebugMessage1("Attach - nProcessAttachCount: %d\n", ++nProcessAttachCount);
		DebugMessage1("sizeof MgtDevices = 0x%x\n",sizeof MgtDevices);
		hListLockSemaphore = CreateSemaphore(NULL,1,1,NULL);
		break;

	case DLL_THREAD_ATTACH:
// REMOVED		DebugMessage1("Attach - nThreadAttachCount: %d\n", ++nThreadAttachCount);
		break;

	case DLL_THREAD_DETACH:
// REMOVED		DebugMessage1("Detach - nThreadAttachCount: %d\n", --nThreadAttachCount);
		break;

	case DLL_PROCESS_DETACH:
		DebugMessage1("Detach - nProcessAttachCount: %d\n", --nProcessAttachCount);
		(void) DxbShutdown();
		ShimShutdown();
		CloseHandle(hListLockSemaphore);
		break;
	}
	return TRUE;
}

/***************************
 * NT Config Specific APIs *
 ***************************/

HANDLE API_DECL
cfgGetBoardHandle(DWORD const nBoard)
{
	if (!ValidateBoardNo(nBoard))
		return NULL;

	return MgtDevices[nBoard-1].DosDeviceHandle;
}


DWORD API_DECL
cfgGetBoardNoFromName(TCHAR const * const szName)
{
	DWORD nBoardIndex;
	DWORD BoardNo = 0;	// Invalid
	TCHAR szDosDevName[MAX_DOS_DEVICE_NAME];

	// Brute force lookup...
	for (nBoardIndex = 0; nBoardIndex < gnBoards; nBoardIndex++)
	{
		if (!regGetDosDeviceName(nBoardIndex+1, szDosDevName, sizeof szDosDevName))
		{
			DebugMessage1("Couldn't get DosDeviceName for board %d\n", nBoardIndex+1);
			goto Exit;
		}
		if (0 == strcmp(szName, szDosDevName))
		{
			BoardNo = nBoardIndex+1;
			break;
		}
	}

Exit:
	return BoardNo;
}

BOOL API_DECL
cfgGetNameFromBoardNo(DWORD const nBoard, TCHAR * const szName, size_t const cbName)
{
	BOOL Success = FALSE;
	TCHAR szDosDevName[MAX_DOS_DEVICE_NAME];
	if (!regGetDosDeviceName(nBoard, szDosDevName, sizeof szDosDevName))
	{
		DebugMessage1("Couldn't get DosDeviceName for board %d\n", nBoard);
		goto Exit;
	}

	if (strlen(&szDosDevName[0]) > cbName)
	{
		DebugMessage1("DosDeviceName for board %d is too large for provided buffer\n", nBoard);
		goto Exit;
	}

	strcpy(szName, &szDosDevName[0]);

	Success = TRUE;
Exit:
	return Success;
}

BOOL API_DECL
cfgGetCommName(DWORD const nBoard, DWORD const nPort, DWORD const nChannel, TCHAR * const szName, size_t const cbName)
{
	BOOL Success = FALSE;

	TCHAR szComName[MAX_DOS_DEVICE_NAME];

	if (!regGetComName(nBoard, nPort, nChannel, szComName, sizeof szComName))
	{
		DebugMessage3("Couldn't get ComName for board:%d port:%d channel:%d\n", nBoard, nPort, nChannel);
		goto Exit;
	}

	if (strlen(&szComName[0]) > cbName)
	{
		DebugMessage0("ComName is too large for provided buffer\n");
		goto Exit;
	}

	strcpy(szName, &szComName[0]);

	Success = TRUE;
Exit:
	return Success;
}


/****************************
 * Management Specific APIs *
 ****************************/

uint32 MGMT_DECL
MgmtCommand (	uint32 BoardNumber,
					uint16 Command,
					void *mpkt,
					uint16 mpktlen,
					void *rpkt,
					uint16 rpktlen)
{
	uint32 ReturnCode;

	// Validate params, wait on BoardNumber as it's not always required.
	if (	((NULL == mpkt) != (0 == mpktlen)) ||
			((NULL == rpkt) != (0 == rpktlen))
		)
	{
		DebugMessage0("Invalid parameter\n");
		ReturnCode = BAD_PARAMETER;
		goto Exit;
	}

	// May or may not be initialized prior to this
	if (!fInitialized)
	{
		if (!Initialize())
		{
			DebugMessage0("No adapters could be initialized\n");
			ReturnCode = MGMT_SHIM_ERROR_NO_BOARDS;
			goto Exit;
		}
	}

	if (IS_MGMT_SHIM_COMMAND(Command))
	{
		ReturnCode = LocalSatisfyRequest(BoardNumber,Command,mpkt,mpktlen,rpkt,rpktlen);
		goto Exit;
	}

#if !defined NT5
#pragma message("NOTE: NOTE: Will want to remove serialize for NT4 as well after Xp beta\n")
	if (NEED_TO_SERIALIZE(Command))
	{
		if (!regSerialize(BoardNumber,Command,mpkt,mpktlen,rpkt,rpktlen))
		{
			// NOTE: NOTE: Aborting on serialize failure
			ReturnCode = MGMT_SHIM_ERROR_SERIALIZE_FAIL;
			goto Exit;
		}
	}
#endif // #if !defined NT5

	if (MGMT_OK != (ReturnCode = SatisfyRequest(BoardNumber,Command,mpkt,mpktlen,rpkt,rpktlen)))
		goto Exit;

	ReturnCode = MGMT_OK;
Exit:
	return ReturnCode;
}

uint32  MGMT_DECL
MgmtTrace (	uint32 BoardNumber,
				uint16 Command,
				void *mpkt,
				uint16 mpktlen,
				void *rpkt,
				uint16 rpktlen,
				TRC_DATACOPY_CB cb,			/* This is the function I call when traces come in. */
				TRCARG handle )				/* cb functions "handle" */
{
	uint32		ReturnCode;
	if (!fInitialized)
	{
		ReturnCode = MGMT_SHIM_ERROR_NOT_INITIALIZED;
		goto Exit;
	}

	switch (Command)
	{
	default:
		ReturnCode = StartTrace(BoardNumber,Command,mpkt,mpktlen,rpkt,rpktlen,cb,handle);
		break;
	case SET_MGMT_STOP_TRC:
		ReturnCode = StopTrace(BoardNumber,Command,mpkt,mpktlen,rpkt,rpktlen,cb,handle);
		break;
	}

Exit:
	return ReturnCode;
}
