/*++
*****************************************************************************
* Copyright (c) 1997 Digi International Inc., All Rights Reserved
* 
* This software contains proprietary and confidential information of Digi
* International Inc.  By accepting transfer of this copy, Recipient agrees
* to retain this software in confidence, to prevent disclosure to others,
* and to make no use of this software other than that for which it was
* delivered.  This is an unpublished copyrighted work of Digi International
* Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly
* prohibited.
* 
* Restricted Rights Legend
*
* Use, duplication, or disclosure by the Government is subject to
* restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in
* Technical Data and Computer Software clause at DFARS 252.227-7031 or
* subparagraphs (c)(1) and (2) of the Commercial Computer Software -
* Restricted Rights at 48 CFR 52.227-19, as applicable.
*
* Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343
*
*****************************************************************************

Module Name:

  DxbShim.c

Abstract:

  This Win32 module provides a user mode abstraction of the IOCTL interface
  into the Windows DXB driver. All Windows IOCTL operations are encapsulated.
  Methods are exposed that facilitate driver enumeration and operations for
  DXB streams. This module is the user mode version the kernel mode DSAPI.

Terminology:
  
  Streams Subsystem -
				Streams engine module running on a processor. This may be
				embedded on an adapter or reside on the host.

  Driver -	Windows DXB Driver, low level driver which is responsible for
				adapter startup and DXB implementation. This driver exposes
				devices which upper	layers use to communicate with the streams
				subsystems. A management device is exposed by the driver to
				facilitate enumeration of devices, kme operations, etc.

  Device -	Streams subsystem exposed by the driver as an IOCTL device
				(DOS Device Name). One device name for each available streams
				subsystem is exposed. For DXB, the number translates to the
				number of installed and loaded adapters.

  Stream -	An open communication channel between a client and an endpoint
				within a streams subsystem. A stream is opened and manipulated
				via the device's IOCTL interface.
--*/

#include <windows.h>
#include <winbase.h>
#include <process.h>				// __beginthread
#include <assert.h>				// assert()

#include "macros.h"				// Useful macros from the T1 Modem Bank projects

// The NT Dxb Driver packs to 4
#pragma pack(push,4)
#include "DsIoctl.h"				// DSAPI definitions, shared header
#include "DigiKme.h"				// KME definitions, shared header
#pragma pack(pop)

#if defined (MAKE_DLL)
	#define API_DECL __declspec( dllexport ) WINAPI
#else
	#if 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_STATIC_LIB)
#endif // #if defined (MAKE_DLL)

#include "DxbShim.h"				// "public" definitions
#include "DxbShimInternal.h"	// "private" definitions

///////////////////////////////////////////////////////////////////////////////
// Local Data ("private")
//

static HANDLE gShutdownEvent = NULL;

#if defined(_DEBUG)
static DWORD nMallocs = 0;	// For memory allocation debug
static DWORD nEvents = 0;	// For memory allocation debug
#define MALLOC(x) (nMallocs x)	// param is ++ or --
#define EVENTS(x) (nEvents x)		// param is ++ or --
DWORD API_DECL ShimDbgGetMallocs(void)
{	return nMallocs;	}
DWORD API_DECL ShimDbgGetEvents(void)
{	return nEvents;	}
#else // #if defined(_DEBUG)
#define MALLOC(x)
#define EVENTS(x)
#endif // #if defined(_DEBUG)

#ifdef TRACK_THREAD_COUNT
static DWORD nThreadCount= 0;
#endif // #ifdef TRACK_THREAD_COUNT

///////////////////////////////////////////////////////////////////////////////
// Local Functions ("private")
//

static __inline SHIM_STATUS
DriverClose(IN HANDLE const hDriver)
{
	SHIM_STATUS Result = SHIM_STATUS_SUCCESS;

	assert(NULL != hDriver);

	if (!CloseHandle(hDriver))
	{
		Result = SHIM_ERROR_DRIVER_CLOSE;
		DebugMessage1("Device failed to close, last error: 0x%x\n", GetLastError());
	}

	return Result;
}

static __inline SHIM_STATUS
DriverOpen(IN TCHAR const * const pszDriverName,
			  OUT HANDLE * const phDriver)
{
	SHIM_STATUS Result = SHIM_STATUS_SUCCESS;
	HANDLE hDriver;
	size_t cbPrefix;
	size_t cbName;

	TCHAR szFullName[MAX_DOS_DEVICE_NAME];
	assert(NULL != pszDriverName);
	assert(NULL != phDriver);

	// Always pre-pend the slashes
#define DEVICE_PREFIX "\\\\.\\"
	cbPrefix = (sizeof DEVICE_PREFIX) - 1;	// Includes the NULL
	memcpy( &szFullName[0], DEVICE_PREFIX, cbPrefix);
	cbName = strlen(pszDriverName);
	assert( (MAX_DOS_DEVICE_NAME + 1) > (cbPrefix + cbName) );
	memcpy( &szFullName[cbPrefix], pszDriverName, cbName +1);	// +1 is NULL term
	hDriver = CreateFile(	szFullName,
									GENERIC_READ | GENERIC_WRITE,
									FILE_SHARE_READ | FILE_SHARE_WRITE,
									NULL,
									OPEN_EXISTING,
									FILE_FLAG_OVERLAPPED,
									NULL);

	if (INVALID_HANDLE_VALUE == hDriver)
	{
		Result = SHIM_ERROR_DRIVER_OPEN;
		DebugMessage2("Device open failed on %s, last error: 0x%x\n", pszDriverName, GetLastError());
		goto Exit;
	}

	// Global shutdown event, only do this once
	if (NULL == gShutdownEvent)
	{
		if (NULL == (gShutdownEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
		{
			Result = SHIM_ERROR_CREATE_EVENT;
			DebugMessage1("Failure creating event in DriverOpen(), last error: 0x%x\n", GetLastError());
			DriverClose(hDriver);	// Subsequent calls require the shutdown event to be valid
			goto Exit;
		}
		EVENTS(++);
	}
	ResetEvent(gShutdownEvent);	// We're initializing again, make sure the event is reset

	*phDriver = hDriver;

Exit:
	return Result;
}

static void
FreeIoctl(DRIVER_IOCTL * const pIoctl)
{
	if (NULL == pIoctl)
		goto Exit;

	if (NULL != pIoctl->Ol.hEvent)
	{
		if (!CloseHandle(pIoctl->Ol.hEvent))
			DebugMessage1("Error closing event handle, last error 0x%x\n", GetLastError());
		EVENTS(--);
	}

	if (NULL != pIoctl->pIoctlData)
	{
		free(pIoctl->pIoctlData);
		MALLOC(--);
	}
	// Could be the same, don't free twice
	if ( (NULL != pIoctl->pResultData) && (pIoctl->pIoctlData != pIoctl->pResultData) )
	{
		free(pIoctl->pResultData);
		MALLOC(--);
	}

	free(pIoctl);
	MALLOC(--);

Exit:
	return;
}

static SHIM_STATUS
ConstructIoctl(	IN HANDLE const hDriver,
						IN DWORD const IoctlCmd,
						IN size_t const cbIoctlData,
						IN size_t const cbResultData,
						IN DWORD const	TimeoutMs,
						IN HANDLE const hEvent,
						IN SHIM_STATUS * const pStatus,
						OUT DRIVER_IOCTL * * const ppIoctl
						)
{
	SHIM_STATUS Result = SHIM_STATUS_SUCCESS;
	PDRIVER_IOCTL pIoctl = NULL;

	assert(NULL != ppIoctl);

	pIoctl = (PDRIVER_IOCTL) malloc(sizeof(DRIVER_IOCTL));
	if (NULL == pIoctl)
	{
		Result = SHIM_ERROR_MEMORY_ALLOC;
		DebugMessage0("malloc() failed in ConstructIoctl()\n");
		goto ErrorExit;
	}
	MALLOC(++);
	ZeroMemory(pIoctl, sizeof(DRIVER_IOCTL));

	if (0 != cbIoctlData)
	{
		if (NULL == (pIoctl->pIoctlData = malloc(cbIoctlData)))
		{
			Result = SHIM_ERROR_MEMORY_ALLOC;
			DebugMessage0("malloc() failed in ConstructIoctl()\n");
			goto ErrorExit;
		}
		MALLOC(++);
		ZeroMemory(pIoctl->pIoctlData, cbIoctlData);
	}
	pIoctl->cbIoctlData = (unsigned long)cbIoctlData;

	if (0 != cbResultData)
	{
		if (NULL == (pIoctl->pResultData = malloc(cbResultData)))
		{
			Result = SHIM_ERROR_MEMORY_ALLOC;
			DebugMessage0("malloc() failed in ConstructIoctl()\n");
			goto ErrorExit;
		}
		MALLOC(++);
		ZeroMemory(pIoctl->pResultData, cbResultData);
	}
	pIoctl->cbResultData = (unsigned long)cbResultData;

	// manual reset event
	if (NULL == (pIoctl->Ol.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
	{
		Result = SHIM_ERROR_CREATE_EVENT;
		DebugMessage1("Failure creating event in ConstructIoctl(), last error: 0x%x\n", GetLastError());
		goto ErrorExit;
	}
	EVENTS(++);

	// Fill in the members we have
	pIoctl->hDriver = hDriver;
	pIoctl->IoctlCmd = IoctlCmd;
	pIoctl->TimeoutMs = TimeoutMs;
	pIoctl->hEvent = hEvent;
	pIoctl->pStatus = pStatus;

	// Calculate operation type
	if (NULL != hEvent)
		pIoctl->Type = OP_TYPE_ASYNC;	// Can be async or async-time-out
	else
		pIoctl->Type = (0 != TimeoutMs ? OP_TYPE_TIMEOUT : OP_TYPE_BLOCKING);

	// set the caller's ptr
	*ppIoctl = pIoctl;

	goto Exit; // Success

ErrorExit:
	*ppIoctl = NULL;
	if (NULL != pIoctl)
		FreeIoctl(pIoctl);

Exit:
	return Result;
}

static _inline SHIM_STATUS
FillContext(IN DRIVER_IOCTL const * const pIoctl)
{
	// Call only when the IOCTL return has been verified

	SHIM_STATUS Result = SHIM_STATUS_SUCCESS;

	assert(NULL != pIoctl);

	switch (pIoctl->IoctlCmd)
	{
		default:
			Result = SHIM_ERROR_INVALID_DS_IOCTL;
			DebugMessage0("SHIM_ERROR_INVALID_DS_IOCTL in FillContext()\n");
			break;

		case IOCTL_DS_CLOSE:
		case IOCTL_DS_PUTMSG:
		case IOCTL_DS_PURGE:
			// No associated context
			break;

		case IOCTL_DS_OPEN:
		{
			PDS_OPEN pOpen = (PDS_OPEN)pIoctl->pResultData;
			if (NULL != pIoctl->Context.DsOpen.phStream)
				*pIoctl->Context.DsOpen.phStream = pOpen->hStream;
			if (NULL != pIoctl->Context.DsOpen.pErrorCode)
				*pIoctl->Context.DsOpen.pErrorCode = pOpen->ErrorCode;
			if (0 != pOpen->ErrorCode)
				Result = SHIM_ERROR_DS_OPEN_INTERNAL;
			break;
		}

		case IOCTL_DS_IOCTL:
		{
			PDS_IOCTL pDsIoctl = (PDS_IOCTL)pIoctl->pResultData;
			if (NULL != pIoctl->Context.DsIoctl.pcbDataOut)
				*pIoctl->Context.DsIoctl.pcbDataOut = pDsIoctl->cbDataOut;
			if (NULL != pIoctl->Context.DsIoctl.pReturnValue)
				*pIoctl->Context.DsIoctl.pReturnValue = pDsIoctl->ReturnValue;
			if (NULL != pIoctl->Context.DsIoctl.pErrorCode)
				*pIoctl->Context.DsIoctl.pErrorCode = pDsIoctl->ErrorCode;
			// Assume that the output size is fixed, error if board outputs unexpected size
			if (	((0 != pDsIoctl->ReturnValue) || (0 != pDsIoctl->ErrorCode)) ||
					(pDsIoctl->cbDataOut != pDsIoctl->cbMaxDataOut)
				)
				Result = SHIM_ERROR_DS_IOCTL_INTERNAL;
			break;
		}

		case IOCTL_DS_LINK:
		{
			PDS_LINK pLink = (PDS_LINK)pIoctl->pResultData;
			if (NULL != pIoctl->Context.DsLink.pMuxId)
				*pIoctl->Context.DsLink.pMuxId = pLink->MuxId;
			if (NULL != pIoctl->Context.DsLink.pErrorCode)
				*pIoctl->Context.DsLink.pErrorCode = pLink->ErrorCode;
			if (0 != pLink->ErrorCode)
				Result = SHIM_ERROR_DS_LINK_INTERNAL;
			break;
		}

		case IOCTL_DS_GETMSG:
		{
			PDS_GETMSG pGetMsg = (PDS_GETMSG)pIoctl->pResultData;
			if (NULL != pIoctl->Context.DsGetMsg.pcbControl)
				*pIoctl->Context.DsGetMsg.pcbControl = pGetMsg->cbControl;
			if (NULL != pIoctl->Context.DsGetMsg.pcbData)
				*pIoctl->Context.DsGetMsg.pcbData = pGetMsg->cbData;
			break;
		}

		case IOCTL_KME_WRITE:
		{
			PKME_WRITE pKmeWrite = (PKME_WRITE)pIoctl->pResultData;
			*pIoctl->Context.KmeReadWrite.pcbBuffer = pKmeWrite->BytesWritten;
		}

		case IOCTL_KME_READ:
		{
			PKME_READ pKmeRead = (PKME_READ)pIoctl->pResultData;
			*pIoctl->Context.KmeReadWrite.pcbBuffer = pKmeRead->BytesRead;
		}
	}

	return Result;
}

static __inline BOOL
ValidDsIoctl(IN DWORD const IoctlCmd)
{
	BOOL Success;
	switch (IoctlCmd)
	{
		default:
			Success = FALSE;
			DebugMessage1("Invalid DS IOCTL: %d\n", IoctlCmd);
			break;
		case IOCTL_DS_OPEN:
		case IOCTL_DS_CLOSE:
		case IOCTL_DS_IOCTL:
		case IOCTL_DS_LINK:
		case IOCTL_DS_PUTMSG:
		case IOCTL_DS_GETMSG:
		case IOCTL_DS_PURGE:
		case IOCTL_KME_WRITE:
		case IOCTL_KME_READ:
			Success = TRUE;
			break;
	}

	return Success;
}

// REMOVED #define CHECK_TIME_TICKS

static SHIM_STATUS
DoIoctlAndWait(IN OUT DRIVER_IOCTL * const pIoctl)
{
	SHIM_STATUS	Result;
#define MAX_EVENTS 3
	HANDLE		pEvents[MAX_EVENTS] = {NULL};	// IOCTL's event, User's event, global shutdown
	DWORD			nEventIndex, nEvents, cbReturned = 0;
	BOOL			fStatus;

#ifdef CHECK_TIME_TICKS
	DWORD nTickIn = 0;
#endif // #ifdef CHECK_TIME_TICKS
	// Verify we're being called correctly
	if ( (NULL == gShutdownEvent) && (NULL == pIoctl) && (!ValidDsIoctl(pIoctl->IoctlCmd)) && (NULL == pIoctl->pIoctlData) )
	{
		assert(FALSE);
		Result = SHIM_ERROR_INVALID_PARAMETER;
		goto Exit;
	}

#ifdef CHECK_TIME_TICKS
	nTickIn = GetTickCount();
#endif // #ifdef CHECK_TIME_TICKS
	// Do the IOCTL right away
	fStatus = DeviceIoControl(	pIoctl->hDriver,
										pIoctl->IoctlCmd,
										pIoctl->pIoctlData,
										pIoctl->cbIoctlData,
										pIoctl->pResultData,
										pIoctl->cbResultData,
										&cbReturned,
										&pIoctl->Ol);
#ifdef CHECK_TIME_TICKS
	DebugMessage1("DoIoctlAndWait:DeviceIoControl() ticks:%d\n",GetTickCount() - nTickIn);
#endif // #ifdef CHECK_TIME_TICKS

	if (!fStatus)
	{
		DWORD LastError = GetLastError();
		if (ERROR_IO_PENDING != LastError)
		{
			Result = SHIM_ERROR_IO_NOT_PENDING;
			DebugMessage1("DeviceIoControl failure, last error: 0x%x\n", LastError);
			goto Exit;
		}
	}
	else
	{
		if (cbReturned != pIoctl->cbResultData)
		{
			Result = SHIM_ERROR_IO_RESULT_COUNT;
			DebugMessage2("DeviceIoControl returned incorrect byte count, expected: %d, got: %d\n", pIoctl->cbResultData, cbReturned);
			goto Exit;
		}
		Result = SHIM_STATUS_SUCCESS;
		goto Exit;
	}

#define SHUTDOWN_EVENT_INDEX	0
#define IOCTL_EVENT_INDEX		1
#define USER_EVENT_INDEX		2
#define MAX_EVENT_INDEX			USER_EVENT_INDEX
	// The IOCTL is pending...
	pEvents[SHUTDOWN_EVENT_INDEX] = gShutdownEvent;
	pEvents[IOCTL_EVENT_INDEX] = pIoctl->Ol.hEvent;
	// Optional user event, we signal when operation completes, signaled by user to cancel...
	pEvents[USER_EVENT_INDEX] = pIoctl->hEvent;
	nEvents = (NULL == pIoctl->hEvent) ? MAX_EVENT_INDEX : MAX_EVENT_INDEX + 1;

	// Allow IOCTL to complete as specified by the timeout
#ifdef CHECK_TIME_TICKS
	nTickIn = GetTickCount();
#endif // #ifdef CHECK_TIME_TICKS
	nEventIndex = WaitForMultipleObjects(nEvents, pEvents, FALSE, (0 == pIoctl->TimeoutMs ? INFINITE : pIoctl->TimeoutMs));
#ifdef CHECK_TIME_TICKS
	DebugMessage1("DoIoctlAndWait:WaitForMultipleObjects() ticks:%d\n",GetTickCount() - nTickIn);
#endif // #ifdef CHECK_TIME_TICKS

	if ( (WAIT_ABANDONED_0 <= nEventIndex) && (WAIT_ABANDONED_0 + MAX_EVENT_INDEX >= nEventIndex) )
	{
		// App is shutting down, bail
		DebugMessage0("Abandon wait, app shutdown\n");
		Result = SHIM_ERROR_TIME_TO_BAIL;
		goto Exit;
	}

	if (WAIT_OBJECT_0 + SHUTDOWN_EVENT_INDEX == nEventIndex)
	{
		Result = SHIM_ERROR_SHUTDOWN;
		DebugMessage0("Shutdown event detected\n");
		goto Exit;
	}

	if ((NULL != pIoctl->hEvent) && (WAIT_OBJECT_0 + USER_EVENT_INDEX == nEventIndex))
	{
		Result = SHIM_ERROR_USER_CANCEL;
		DebugMessage0("User event, cancel request\n");
		goto Exit;
	}

	if (WAIT_TIMEOUT == nEventIndex)
	{
		Result = SHIM_ERROR_OPERATION_TIMEOUT;
		DebugMessage0("Wait timed out\n");
		goto Exit;
	}

	// Ignore this test: (WAIT_OBJECT_0 + IOCTL_EVENT_INDEX == nEventIndex),
	// Instead, this is the result we're interested in... no wait call
#ifdef CHECK_TIME_TICKS
	nTickIn = GetTickCount();
#endif // #ifdef CHECK_TIME_TICKS
	if (!GetOverlappedResult(pIoctl->hDriver, &pIoctl->Ol, &cbReturned, FALSE))
	{
		DWORD LastError = GetLastError();
		if (ERROR_IO_INCOMPLETE == LastError)
		{
			DebugMessage0("IOCTL still pending after time-out\n");
			Result = SHIM_ERROR_OPERATION_TIMEOUT;
		}
		else
		{
			// Severe error... Bail now
			Result = SHIM_ERROR_TIME_TO_BAIL;
			DebugMessage1("GetOverlappedResult() failure, last error: 0x%x\n", LastError);
		}
		goto Exit;
	}
#ifdef CHECK_TIME_TICKS
	DebugMessage1("DoIoctlAndWait:GetOverlappedResult() ticks:%d\n",GetTickCount() - nTickIn);
#endif // #ifdef CHECK_TIME_TICKS

	// Phew! We got the IOCTL, do a sanity check
	if (cbReturned != pIoctl->cbResultData)
	{
		Result = SHIM_ERROR_OL_RESULT_COUNT;
		DebugMessage2("GetOverlappedResult returned incorrect byte count, expected: %d, got: %d\n", pIoctl->cbResultData, cbReturned);
		goto Exit;
	}

	Result = SHIM_STATUS_SUCCESS;

Exit:
	if (SHIM_ERROR_TIME_TO_BAIL != Result)
	{
		if (SHIM_STATUS_SUCCESS == Result)
			Result = FillContext(pIoctl);		// Move the result data where the user specified

	    if (NULL != pIoctl->pStatus)
		    *pIoctl->pStatus = Result;
	}
	return Result;
}

static __inline SHIM_STATUS
BlockingIoctl(IN OUT DRIVER_IOCTL * const pIoctl)
{
	SHIM_STATUS Result;

	// Verify we're being called correctly
	assert(NULL != pIoctl);
	assert(NULL == pIoctl->hEvent);
	assert(0 == pIoctl->TimeoutMs);

	Result = DoIoctlAndWait(pIoctl);
	FreeIoctl(pIoctl);
	return Result;
}

static void __cdecl
TimeoutIoctlThread(void * arglist)
{
	(void) DoIoctlAndWait((PDRIVER_IOCTL)arglist);
#ifdef TRACK_THREAD_COUNT
	DebugMessage1("nThreadCount:%d\n", --nThreadCount);
#endif // #ifdef TRACK_THREAD_COUNT
}

#ifdef USE_TIMEOUT_IOCTL_THREAD
static __inline SHIM_STATUS
TimeoutIoctl(IN OUT DRIVER_IOCTL * const pIoctl)
{
	SHIM_STATUS Result;

	DWORD Status;
	HANDLE hThread;

#ifdef CHECK_TIME_TICKS
	DWORD nTickIn = 0;
#endif // #ifdef CHECK_TIME_TICKS

	assert(NULL != pIoctl);
	assert(0 != pIoctl->TimeoutMs);
	assert(NULL == pIoctl->hEvent);

	if (NULL == pIoctl->pStatus)
		pIoctl->pStatus = &Status;

#ifdef CHECK_TIME_TICKS
	nTickIn = GetTickCount();
#endif // #ifdef CHECK_TIME_TICKS
	hThread = (HANDLE)_beginthread( TimeoutIoctlThread, 0, pIoctl );
#ifdef CHECK_TIME_TICKS
	DebugMessage1("TimeoutIoctl:_beginthread() ticks:%d\n",GetTickCount() - nTickIn);
#endif // #ifdef CHECK_TIME_TICKS

	if (INVALID_HANDLE_VALUE == hThread)
	{
		Result = SHIM_ERROR_THREAD_START;
		DebugMessage1("TimeoutIoctlThread start failure, last error 0x%x\n", GetLastError());
		goto Exit;
	}

#ifdef TRACK_THREAD_COUNT
	DebugMessage1("nThreadCount:%d\n", ++nThreadCount);
#endif // #ifdef TRACK_THREAD_COUNT

	{	// Make sure the request can be terminated
#define SHUTDOWN_INDEX	0
#define THREAD_INDEX		1
#define MAX_INDEX			THREAD_INDEX
#define INDEX_COUNT		(MAX_INDEX+1)
		DWORD nEventIndex;
		HANDLE pEvents[INDEX_COUNT];
		pEvents[SHUTDOWN_INDEX] = gShutdownEvent;
		pEvents[THREAD_INDEX] = hThread;
		// Allow IOCTL to complete as specified by the timeout
#ifdef CHECK_TIME_TICKS
	nTickIn = GetTickCount();
#endif // #ifdef CHECK_TIME_TICKS
		nEventIndex = WaitForMultipleObjects(INDEX_COUNT, pEvents, FALSE, INFINITE);
#ifdef CHECK_TIME_TICKS
	DebugMessage1("TimeoutIoctl:WaitForMultipleObjects() ticks:%d\n",GetTickCount() - nTickIn);
#endif // #ifdef CHECK_TIME_TICKS
		if (THREAD_INDEX + WAIT_OBJECT_0 != nEventIndex)
		{
			Result = SHIM_ERROR_TIME_TO_BAIL;	// Thread didn't complete... bail
			goto Exit;
		}
	}

	Result = *pIoctl->pStatus;
Exit:
	FreeIoctl(pIoctl);
	return Result;
}
#else // #ifdef USE_TIMEOUT_IOCTL_THREAD
static __inline SHIM_STATUS
TimeoutIoctl(IN OUT DRIVER_IOCTL * const pIoctl)
{
	// Do like blocking.........
	// This limits the amount of time we spend in WaitForMultipleObjects
	SHIM_STATUS Result;

	// Verify we're being called correctly
	assert(NULL != pIoctl);
	assert(NULL == pIoctl->hEvent);
	assert(0 != pIoctl->TimeoutMs);

	Result = DoIoctlAndWait(pIoctl);
	FreeIoctl(pIoctl);
	return Result;
}
#endif // #ifdef USE_TIMEOUT_IOCTL_THREAD

static void __cdecl
AsyncIoctlThread(void * arglist)
{
	PDRIVER_IOCTL pIoctl = (PDRIVER_IOCTL)arglist;

#ifdef CHECK_TIME_TICKS
	DWORD nTickIn = 0;
#endif // #ifdef CHECK_TIME_TICKS

	// Verify we're being called correctly
	assert(NULL != pIoctl);
	assert(NULL != pIoctl->hEvent);

	if (SHIM_ERROR_TIME_TO_BAIL == DoIoctlAndWait(pIoctl))
	{
		DebugMessage0("Got Bail...\n");
		return; // Don't touch anything, it's gone
	}

	// Signal the user's event, not much can be done on failure
	if (!SetEvent(pIoctl->hEvent))
		DebugMessage1("SetEvent() failure, last error: 0x%x\n", GetLastError());

	FreeIoctl(pIoctl);

#ifdef TRACK_THREAD_COUNT
	DebugMessage1("nThreadCount:%d\n", --nThreadCount);
#endif // #ifdef TRACK_THREAD_COUNT
}

static __inline SHIM_STATUS
AsyncIoctl(IN OUT DRIVER_IOCTL * const pIoctl)
{
	SHIM_STATUS Result;

#ifdef CHECK_TIME_TICKS
	DWORD nTickIn = 0;
#endif // #ifdef CHECK_TIME_TICKS

	// Verify we're being called correctly
	assert(NULL != pIoctl);
	assert(NULL != pIoctl->hEvent);

	// Hand off the IOCTL to the thread
#ifdef CHECK_TIME_TICKS
	nTickIn = GetTickCount();
#endif // #ifdef CHECK_TIME_TICKS
	if (INVALID_HANDLE_VALUE == (HANDLE)_beginthread( AsyncIoctlThread, 0, pIoctl ))
	{
		Result = SHIM_ERROR_THREAD_START;
		DebugMessage1("AsyncIoctlThread start failure, last error 0x%x\n", GetLastError());
		// Must clean up here
		FreeIoctl(pIoctl);
	}
	else
	{
#ifdef TRACK_THREAD_COUNT
		DebugMessage1("nThreadCount:%d\n", ++nThreadCount);
#endif // #ifdef TRACK_THREAD_COUNT
		Result = SHIM_STATUS_PENDING_OPERATION;
	}
#ifdef CHECK_TIME_TICKS
	DebugMessage1("TimeoutIoctl:WaitForMultipleObjects() ticks:%d\n",GetTickCount() - nTickIn);
#endif // #ifdef CHECK_TIME_TICKS


	return Result;
}

static __inline SHIM_STATUS
PerformIoctl(IN OUT DRIVER_IOCTL * const pIoctl)
{
	SHIM_STATUS Result = SHIM_STATUS_SUCCESS;

	switch (pIoctl->Type)
	{
		default:
			// no break

		case OP_TYPE_INVALID:
			Result = SHIM_ERROR_INVALID_OP_TYPE;
			DebugMessage1("Invalid operation type in PerformIoctl(), type: 0x%x\n",pIoctl->Type);
			FreeIoctl(pIoctl);	// Must clean up
			break;

		case OP_TYPE_BLOCKING:
			Result = BlockingIoctl(pIoctl);
			break;

		case OP_TYPE_TIMEOUT:
			Result = TimeoutIoctl(pIoctl);
			break;

		case OP_TYPE_ASYNC:
			Result = AsyncIoctl(pIoctl);
			break;
	}

	return Result;
}

///////////////////////////////////////////////////////////////////////////////
// Global Operations
//

void API_DECL
DxbShutdown()
{
//REMOVED	DebugMessage0("Enter DxbShutdown()\n");
	// Signal shutdown event so pending threads terminate
	if ( (NULL != gShutdownEvent) &&  (!SetEvent(gShutdownEvent)) )
	{
		DebugMessage1("SetEvent() failure in DxbShutdown(), last error: 0x%x\n", GetLastError());
	}
}

///////////////////////////////////////////////////////////////////////////////
// Device Operations
//

SHIM_STATUS API_DECL
DxbDeviceOpen(	IN TCHAR const * const pszDeviceName,	// temp
					OUT HANDLE * const phDevice				// persisitent
					)
{
//REMOVED	DebugMessage0("Enter DxbDeviceOpen()\n");
	return DriverOpen(	pszDeviceName,
								phDevice);
}

SHIM_STATUS API_DECL
DxbDeviceClose(IN HANDLE const hDevice)
{
//REMOVED	DebugMessage0("Enter DxbDeviceClose()\n");
	return DriverClose(hDevice);
}

///////////////////////////////////////////////////////////////////////////////
// Wrapped Device IOCTLs which facilitate streams operations
//

SHIM_STATUS API_DECL
DxbStreamOpen(IN HANDLE const hDevice,
				  IN TCHAR const * const pszStreamName,
				  OUT HANDLE * const phStream,
				  OUT LONG * const pErrorCode,
				  IN DWORD const TimeoutMs,
				  IN HANDLE const hEvent,
				  OUT SHIM_STATUS * const pStatus
				  )
{
	SHIM_STATUS Result;
	PDRIVER_IOCTL pIoctl = NULL;

//REMOVED	DebugMessage0("Enter DxbStreamOpen()\n");

	Result = ConstructIoctl(hDevice,
									IOCTL_DS_OPEN,
									sizeof (DS_OPEN),
									0,
									TimeoutMs,
									hEvent,
									pStatus,
									&pIoctl);

	if (SHIM_STATUS_SUCCESS != Result)
		goto Exit;

	// For DS_OPEN, in and out buffers are the same
	pIoctl->pResultData = pIoctl->pIoctlData;
	pIoctl->cbResultData = pIoctl->cbIoctlData;

	// DS_OPEN specific
	{
		// Copy in the string and zero the rest (sprintf+)
		DWORD nCount;
		BOOL fHitNull = FALSE;
		for (nCount = 0; nCount < DS_DEVICENAME_SIZE; nCount++)
		{
			if (fHitNull)
				((PDS_OPEN)pIoctl->pIoctlData)->DeviceName[nCount] = 0;
			else
				fHitNull = (0 == (((PDS_OPEN)pIoctl->pIoctlData)->DeviceName[nCount] = pszStreamName[nCount]));
		}
		if (!fHitNull)
		{
			Result = SHIM_ERROR_STRING_ERROR;
			DebugMessage0("Did not find NULL terminator in string\n");
			FreeIoctl(pIoctl);
			goto Exit;
		}
	}
	((PDS_OPEN)pIoctl->pIoctlData)->hStream = NULL;
	((PDS_OPEN)pIoctl->pIoctlData)->ErrorCode = 0;

	// We don't use these, set them explicitly anyway
	((PDS_OPEN)pIoctl->pIoctlData)->FixedCost = 0;
	((PDS_OPEN)pIoctl->pIoctlData)->pUserData = NULL;
	((PDS_OPEN)pIoctl->pIoctlData)->cbUserData = 0;

	// DS_OPEN_CONTEXT specific
	pIoctl->Context.DsOpen.phStream = phStream;
	pIoctl->Context.DsOpen.pErrorCode = pErrorCode;

	// responsibility for storage is passed here
	Result = PerformIoctl(pIoctl);

Exit:
	return Result;
}

SHIM_STATUS API_DECL
DxbStreamClose(IN HANDLE const hDevice,
					IN HANDLE const hStream,
					IN DWORD const TimeoutMs,
					IN HANDLE const hEvent,
					OUT SHIM_STATUS * const pStatus
					)
{
	SHIM_STATUS Result;
	PDRIVER_IOCTL pIoctl = NULL;

//REMOVED	DebugMessage0("Enter DxbStreamClose()\n");

	if ((NULL == hDevice) || (NULL == hStream))
	{
		Result = SHIM_ERROR_INVALID_HANDLE;
		DebugMessage0("NULL input handle in DxbStreamClose()\n");
		goto Exit;
	}

	Result = ConstructIoctl(hDevice,
									IOCTL_DS_CLOSE,
									sizeof (DS_CLOSE),
									0,
									TimeoutMs,
									hEvent,
									pStatus,
									&pIoctl);

	if (SHIM_STATUS_SUCCESS != Result)
		goto Exit;

	// For DS_CLOSE, there is no output buffer

	// DS_CLOSE specific
	((PDS_CLOSE)pIoctl->pIoctlData)->hStream = hStream;

	// No DS_CLOSE_CONTEXT

	// responsibility for storage is passed here
	Result = PerformIoctl(pIoctl);

Exit:
	return Result;
}

SHIM_STATUS API_DECL
DxbStreamIoctl(IN HANDLE const hDevice,
					IN HANDLE const hStream,
					IN ULONG const Command,
					IN LONG const Timeout,
					IN OUT VOID * const pDataIn,
					IN ULONG const cbDataIn,
					OUT VOID * const pDataOut,
					IN ULONG const cbMaxDataOut,
					OUT ULONG * const pcbDataOut,
					OUT LONG * const pReturnValue,
					OUT LONG * const pErrorCode,
					IN DWORD const TimeoutMs,
					IN HANDLE const hEvent,
					OUT SHIM_STATUS * const pStatus
					)
{
	SHIM_STATUS Result;
	PDRIVER_IOCTL pIoctl = NULL;

//REMOVED	DebugMessage0("Enter DxbStreamIoctl()\n");

	if ((NULL == hDevice) || (NULL == hStream))
	{
		Result = SHIM_ERROR_INVALID_HANDLE;
		DebugMessage0("NULL input handle in DxbStreamIoctl()\n");
		goto Exit;
	}

	Result = ConstructIoctl(hDevice,
									IOCTL_DS_IOCTL,
									sizeof (DS_IOCTL),
									0,
									TimeoutMs,
									hEvent,
									pStatus,
									&pIoctl);

	if (SHIM_STATUS_SUCCESS != Result)
		goto Exit;

	// For DS_IOCTL, NT's IOCTL in and out buffers are the same
	pIoctl->pResultData = pIoctl->pIoctlData;
	pIoctl->cbResultData = pIoctl->cbIoctlData;

	// DS_IOCTL specific
	((PDS_IOCTL)pIoctl->pIoctlData)->hStream = hStream;
	((PDS_IOCTL)pIoctl->pIoctlData)->Command = Command;
	((PDS_IOCTL)pIoctl->pIoctlData)->Timeout = Timeout;
	((PDS_IOCTL)pIoctl->pIoctlData)->pDataIn = pDataIn;
	((PDS_IOCTL)pIoctl->pIoctlData)->cbDataIn = cbDataIn;
	((PDS_IOCTL)pIoctl->pIoctlData)->pDataOut = pDataOut;
	((PDS_IOCTL)pIoctl->pIoctlData)->cbMaxDataOut = cbMaxDataOut;
	((PDS_IOCTL)pIoctl->pIoctlData)->cbDataOut = 0;			// OUT data, init
	((PDS_IOCTL)pIoctl->pIoctlData)->ReturnValue = 0;		// OUT data, init
	((PDS_IOCTL)pIoctl->pIoctlData)->ErrorCode = 0;			// OUT data, init
	((PDS_IOCTL)pIoctl->pIoctlData)->pProviderDataIn = NULL;// Reserved data, init
	((PDS_IOCTL)pIoctl->pIoctlData)->pProviderDataOut = NULL;// Reserved data, init

	// DS_IOCTL_CONTEXT specific
	pIoctl->Context.DsIoctl.pcbDataOut = pcbDataOut;
	pIoctl->Context.DsIoctl.pReturnValue = pReturnValue;
	pIoctl->Context.DsIoctl.pErrorCode = pErrorCode;

	// responsibility for storage is passed here
	Result = PerformIoctl(pIoctl);

Exit:
	return Result;
}

SHIM_STATUS API_DECL
DxbStreamLink(	IN HANDLE const hDevice,			// temp
					IN HANDLE const hStreamUpper,		// temp
					IN HANDLE const hStreamLower,		// temp
					OUT LONG * const pMuxId,			// persistent
					OUT LONG * const pErrorCode,		// persistent
					IN DWORD const TimeoutMs,			// optional, temp
					IN HANDLE const hEvent,				// optional, temp (Event must persist)
					OUT SHIM_STATUS * const pStatus	// optional, persistent
					)
{
	SHIM_STATUS Result;
	PDRIVER_IOCTL pIoctl = NULL;

//REMOVED	DebugMessage0("Enter DxbStreamLink()\n");

	if ((NULL == hDevice) || (NULL == hStreamUpper) || (NULL == hStreamLower))
	{
		Result = SHIM_ERROR_INVALID_HANDLE;
		DebugMessage0("NULL input handle in DxbStreamLink()\n");
		goto Exit;
	}


	Result = ConstructIoctl(hDevice,
									IOCTL_DS_LINK,
									sizeof (DS_LINK),
									0,
									TimeoutMs,
									hEvent,
									pStatus,
									&pIoctl);

	if (SHIM_STATUS_SUCCESS != Result)
		goto Exit;

	// For DS_LINK, in and out buffers are the same
	pIoctl->pResultData = pIoctl->pIoctlData;
	pIoctl->cbResultData = pIoctl->cbIoctlData;

	// DS_LINK specific
	((PDS_LINK)pIoctl->pIoctlData)->hStreamUpper = hStreamUpper;
	((PDS_LINK)pIoctl->pIoctlData)->hStreamLower = hStreamLower;
	((PDS_LINK)pIoctl->pIoctlData)->MuxId = 0;		// OUT data, init
	((PDS_LINK)pIoctl->pIoctlData)->ErrorCode = 0;	// OUT data, init

	// DS_LINK_CONTEXT specific
	pIoctl->Context.DsLink.pMuxId = pMuxId;
	pIoctl->Context.DsLink.pErrorCode = pErrorCode;

	// responsibility for storage is passed here
	Result = PerformIoctl(pIoctl);

Exit:
	return Result;
}

// High maintainence priority mapping
#define	MAP_PRIORITY(x, y) {y = (MSG_PRIORITY_HIGH == x ? DS_PRIORITY_HIGH : DS_PRIORITY_LOW);}

SHIM_STATUS API_DECL
DxbStreamPutMsg(	IN HANDLE const hDevice,		// temp
						IN HANDLE const hStream,		// temp
						IN void const * const pControl,	// persistent
						IN ULONG const cbControl,	// temp
						IN void const * const pData,	// persistent
						IN ULONG const cbData,		// temp
						IN MSG_PRIORITY_T const Priority,	// temp
						IN DWORD const TimeoutMs,		// optional, temp
						IN HANDLE const hEvent,			// optional, temp (Event must persist)
						OUT SHIM_STATUS * const pStatus	// optional, persistent
						)
{
	SHIM_STATUS Result;
	PDRIVER_IOCTL pIoctl = NULL;

	// REMOVED DebugMessage0("Enter DxbStreamPutMsg()\n");

	if ((NULL == hDevice) || (NULL == hStream))
	{
		Result = SHIM_ERROR_INVALID_HANDLE;
		DebugMessage0("NULL input handle in DxbStreamPutMsg()\n");
		goto Exit;
	}

	Result = ConstructIoctl(hDevice,
									IOCTL_DS_PUTMSG,
									sizeof (DS_PUTMSG),
									0,
									TimeoutMs,
									hEvent,
									pStatus,
									&pIoctl);

	if (SHIM_STATUS_SUCCESS != Result)
		goto Exit;

	// For DS_PUTMSG, there is no output buffer

	// DS_PUTMSG specific
	((PDS_PUTMSG)pIoctl->pIoctlData)->hStream = hStream;
	(const *)(((PDS_PUTMSG)pIoctl->pIoctlData)->pControl) = pControl;
	((PDS_PUTMSG)pIoctl->pIoctlData)->cbControl = cbControl;
	(const *)(((PDS_PUTMSG)pIoctl->pIoctlData)->pData) = pData;
	((PDS_PUTMSG)pIoctl->pIoctlData)->cbData = cbData;

	{ // MAPPING!
		DWORD MappedPriority;
		MAP_PRIORITY(Priority, MappedPriority);
		((PDS_PUTMSG)pIoctl->pIoctlData)->Priority = MappedPriority;
	}

	((PDS_PUTMSG)pIoctl->pIoctlData)->pProviderControl = NULL;	// reserved data, init
	((PDS_PUTMSG)pIoctl->pIoctlData)->pProviderData = NULL;		// reserved data, init

	// No DS_PUTMSG_CONTEXT

	// responsibility for storage is passed here
	Result = PerformIoctl(pIoctl);

Exit:
	return Result;
}

SHIM_STATUS API_DECL
DxbStreamGetMsg(	IN HANDLE const hDevice,		// temp
						IN HANDLE const hStream,		// temp
						IN OUT void * const  pControl, // persistent
						IN ULONG const cbMaxControl,	// temp
						OUT ULONG * const	pcbControl,// persistent
						IN OUT void * const pData,	// persistent
						IN ULONG const cbMaxData,	// temp
						OUT ULONG * const pcbData,// persistent
						IN MSG_PRIORITY_T const Priority,	// temp
						IN DWORD const TimeoutMs,		// optional, temp
						IN HANDLE const hEvent,			// optional, temp (Event must persist)
						OUT SHIM_STATUS * const pStatus	// optional, persistent
						)
{
	SHIM_STATUS Result;
	PDRIVER_IOCTL pIoctl = NULL;

	// REMOVED DebugMessage0("Enter DxbStreamGetMsg()\n");

	if ((NULL == hDevice) || (NULL == hStream))
	{
		Result = SHIM_ERROR_INVALID_HANDLE;
		DebugMessage0("NULL input handle in DxbStreamGetMsg()\n");
		goto Exit;
	}

	Result = ConstructIoctl(hDevice,
									IOCTL_DS_GETMSG,
									sizeof (DS_GETMSG),
									0,
									TimeoutMs,
									hEvent,
									pStatus,
									&pIoctl);

	if (SHIM_STATUS_SUCCESS != Result)
		goto Exit;

	// For DS_GETMSG, in and out buffers are the same
	pIoctl->pResultData = pIoctl->pIoctlData;
	pIoctl->cbResultData = pIoctl->cbIoctlData;

	// DS_GETMSG specific
	((PDS_GETMSG)pIoctl->pIoctlData)->hStream = hStream;
	((PDS_GETMSG)pIoctl->pIoctlData)->pControl = pControl;
	((PDS_GETMSG)pIoctl->pIoctlData)->cbMaxControl = cbMaxControl;
	((PDS_GETMSG)pIoctl->pIoctlData)->cbControl = 0;			// OUT data, init
	((PDS_GETMSG)pIoctl->pIoctlData)->pData = pData;
	((PDS_GETMSG)pIoctl->pIoctlData)->cbMaxData = cbMaxData;
	((PDS_GETMSG)pIoctl->pIoctlData)->cbData = 0;				// OUT data, init
	((PDS_GETMSG)pIoctl->pIoctlData)->pProviderControl = NULL;	// reserved data, init
	((PDS_GETMSG)pIoctl->pIoctlData)->pProviderData = NULL;		// reserved data, init

	
	{ // MAPPING!
		DWORD MappedPriority;
		MAP_PRIORITY(Priority, MappedPriority);
		((PDS_GETMSG)pIoctl->pIoctlData)->Priority = MappedPriority;
	}

	// DS_GETMSG_CONTEXT specific
	pIoctl->Context.DsGetMsg.pcbControl = pcbControl;
	pIoctl->Context.DsGetMsg.pcbData = pcbData;

	// responsibility for storage is passed here
	Result = PerformIoctl(pIoctl);

Exit:
	return Result;
}

// High maintainence mapping for purge flags
#define	PURGE_FLAGS_MAP(x, y)		{DWORD temp;y=0;temp=\
				PURGE_WRITE					;if(temp==(temp&x)){y|=\
					DS_PURGE_WRITE			;}temp=\
				PURGE_READ					;if(temp==(temp&x)){y|=\
					DS_PURGE_READ			;}temp=\
				PURGE_PUTMSG				;if(temp==(temp&x)){y|=\
					DS_PURGE_PUTMSG		;}temp=\
				PURGE_GETMSG				;if(temp==(temp&x)){y|=\
					DS_PURGE_GETMSG		;}temp=\
				PURGE_ALL					;if(temp==(temp&x)){y|=\
					DS_PURGE_ALL			;}}

SHIM_STATUS API_DECL
DxbStreamPurge(IN HANDLE const hDevice,			// temp
					IN HANDLE const hStream,			// temp
					IN ULONG  const Flags,				// temp
					IN DWORD const TimeoutMs,			// optional, temp
					IN HANDLE const hEvent,				// optional, temp (Event must persist)
					OUT SHIM_STATUS * const pStatus	// optional, persistent
					)
{
	SHIM_STATUS Result;
	PDRIVER_IOCTL pIoctl = NULL;

	DebugMessage0("Enter DxbStreamPurge()\n");

	if ((NULL == hDevice) || (NULL == hStream))
	{
		Result = SHIM_ERROR_INVALID_HANDLE;
		DebugMessage0("NULL input handle in DxbStreamPurge()\n");
		goto Exit;
	}

	Result = ConstructIoctl(hDevice,
									IOCTL_DS_PURGE,
									sizeof (DS_PURGE),
									0,
									TimeoutMs,
									hEvent,
									pStatus,
									&pIoctl);

	if (SHIM_STATUS_SUCCESS != Result)
		goto Exit;

	// For DS_PURGE, there is no output buffer

	// DS_PURGE specific
	((PDS_PURGE)pIoctl->pIoctlData)->hStream = hStream;

	{ // MAPPING!
		DWORD MappedFlags;
		PURGE_FLAGS_MAP(Flags, MappedFlags);
		((PDS_PURGE)pIoctl->pIoctlData)->Flags = Flags;
	}

	// No DS_PURGE_CONTEXT

	// responsibility for storage is passed here
	Result = PerformIoctl(pIoctl);

Exit:
	return Result;
}

SHIM_STATUS API_DECL
CancelAsyncOperation(HANDLE hAsyncOperation)
{
	SHIM_STATUS Return = SHIM_STATUS_SUCCESS;

	DebugMessage0("Enter CancelAsyncOperation()\n");

	if ( (NULL != hAsyncOperation) && (0 == SetEvent(hAsyncOperation)) )
	{
		DebugMessage1("SetEvent failed in CancelAsyncOperation(), last error: 0x%x\n", GetLastError());
		Return = SHIM_ERROR_CANCEL_IO;
	}
	return Return;
}

// High maintainence mapping for KME flags
#define	KME_FLAGS_MAP(x, y)							{switch(x){default:y=0;break;case \
				KME_MEMORY_FLAG_ADAPTER_MEMORY		:y=\
					KME_FLAG_ADAPTER_MEMORY				;break;case\
				KME_MEMORY_FLAG_HOST_MEMORY			:y=\
					KME_FLAG_HOST_MEMORY					;break;case\
				KME_MEMORY_FLAG_PCI_INTERFACE_CHIP	:y=\
					KME_FLAG_PCI_INTERFACE_CHIP		;break;case\
				KME_MEMORY_FLAG_PCI_MEMORY_OTHER		:y=\
					KME_FLAG_PCI_MEMORY_OTHER			;break;}}

SHIM_STATUS API_DECL
DxbKmeWrite(	IN HANDLE const hDevice,
					IN DWORD const Address,
					IN KME_MEMORY_FLAG_T const MemoryFlag,
					IN void * const pBuffer,
					IN DWORD const cbBuffer,
					OUT DWORD * const pcbWritten,
					IN DWORD const TimeoutMs,
					IN HANDLE const hEvent,
					OUT SHIM_STATUS * const pStatus
				)
{
	SHIM_STATUS Result;
	PDRIVER_IOCTL pIoctl = NULL;

	if (NULL == hDevice)
	{
		Result = SHIM_ERROR_INVALID_HANDLE;
		DebugMessage0("NULL input handle in DxbKmeWrite()\n");
		goto Exit;
	}

	Result = ConstructIoctl(hDevice,
									IOCTL_KME_WRITE,
									sizeof (KME_WRITE),
									0,
									TimeoutMs,
									hEvent,
									pStatus,
									&pIoctl);

	if (SHIM_STATUS_SUCCESS != Result)
		goto Exit;

	// In and out buffers are the same
	pIoctl->pResultData = pIoctl->pIoctlData;
	pIoctl->cbResultData = pIoctl->cbIoctlData;

	((PKME_WRITE)pIoctl->pIoctlData)->Address = Address;	// Address to be written
	((PKME_WRITE)pIoctl->pIoctlData)->Length = cbBuffer;	// Length of buffer to write
	((PKME_WRITE)pIoctl->pIoctlData)->pBuffer = pBuffer;	// Input buffer to write

	{ // MAPPING!
		DWORD MappedFlag = 0;
		KME_FLAGS_MAP(MemoryFlag, MappedFlag);
		((PKME_WRITE)pIoctl->pIoctlData)->Flags = MappedFlag;	// Indicates adapter, host or PCI I/F chip
	}
	// ((PKME_WRITE)pIoctl->pIoctlData)->BytesWritten = 0;	// Output count of bytes written
	// ((PKME_WRITE)pIoctl->pIoctlData)->pLockedBuffer = 0;	// Driver private locked pointer to data buffer

	pIoctl->Context.KmeReadWrite.pcbBuffer = pcbWritten;

	// responsibility for storage is passed here
	Result = PerformIoctl(pIoctl);

Exit:
	return Result;
}

SHIM_STATUS API_DECL
DxbKmeRead(	IN HANDLE const hDevice,
				IN DWORD const Address,
				IN KME_MEMORY_FLAG_T const MemoryFlag,
				OUT void * pBuffer,
				IN DWORD const cbBufferMax,
				OUT DWORD * const pcbRead,
				IN DWORD const TimeoutMs,
				IN HANDLE const hEvent,
				OUT SHIM_STATUS * const pStatus
			)
{
	SHIM_STATUS Result;
	PDRIVER_IOCTL pIoctl = NULL;

	if (NULL == hDevice)
	{
		Result = SHIM_ERROR_INVALID_HANDLE;
		DebugMessage0("NULL input handle in DxbKmeRead()\n");
		goto Exit;
	}

	Result = ConstructIoctl(hDevice,
									IOCTL_KME_READ,
									sizeof (KME_READ),
									0,
									TimeoutMs,
									hEvent,
									pStatus,
									&pIoctl);

	if (SHIM_STATUS_SUCCESS != Result)
		goto Exit;

	// In and out buffers are the same
	pIoctl->pResultData = pIoctl->pIoctlData;
	pIoctl->cbResultData = pIoctl->cbIoctlData;

	((PKME_READ)pIoctl->pIoctlData)->Address = Address;	// Address to be written
	((PKME_READ)pIoctl->pIoctlData)->Length = cbBufferMax;// Length of output buffer, and of requested read
	((PKME_READ)pIoctl->pIoctlData)->pBuffer = pBuffer;	// Input buffer to write
	{ // MAPPING!
		DWORD MappedFlag = 0;
		KME_FLAGS_MAP(MemoryFlag, MappedFlag);
		((PKME_READ)pIoctl->pIoctlData)->Flags = MappedFlag;	// Indicates adapter, host or PCI I/F chip
	}
	// ((PKME_READ)pIoctl->pIoctlData)->BytesRead = 0;		// Output count of bytes read
	// ((PKME_READ)pIoctl->pIoctlData)->pLockedBuffer = 0;// Driver private locked pointer to data buffer

	pIoctl->Context.KmeReadWrite.pcbBuffer = pcbRead;

	// responsibility for storage is passed here
	Result = PerformIoctl(pIoctl);

Exit:
	return Result;
}
