/******************************************************************************/
/*                                 SCRAMNet GT                                */
/******************************************************************************/
/*                                                                            */
/* Copyright (c) 2021 Curtiss-Wright Controls Electronic System, Inc.         */
/*     dtn_support@curtisswright.com 800-252-5601 (U.S. only) 937-252-5601    */
/*                                                                            */
/* This program is free software; you can redistribute it and/or              */
/* modify it under the terms of the GNU General Public License                */
/* as published by the Free Software Foundation; either version 2             */
/* of the License, or (at your option) any later version.                     */
/*                                                                            */
/* See the GNU General Public License for more details.                       */
/*                                                                            */
/******************************************************************************/

/******************************************************************************/
/*                                                                            */
/*    Module      : scgt.c                                                    */
/*    Description : SCRAMNet GT driver entry points                           */
/*    Platform    : Windows                                                   */
/*                                                                            */
/******************************************************************************/

/**
@file scgt.c
@brief SCRAMNet GT driver entry points
**/

/************************************************************************/
/**************************  I N C L U D E S  ***************************/
/************************************************************************/

#include <stdio.h>
#include "scgt.h"
#include "gtcore.h"
#include "gtcoreIoctl.h"
#include "ksys.h"

#include "scgtdrv.h"

//#define NTSTRSAFE_LIB
WARNING_PUSH
WARNING_DISABLE(4514)
#include <ntstrsafe.h>
WARNING_POP
#include "scgtDebug.h"

/* The pointer argument 'arg' for function 'foo' can be marked as a pointer to const (con.3).*/
WARNING_DISABLE(26461)

/************************************************************************/
/***************************  D E F I N E S  ****************************/
/************************************************************************/

/** @addtogroup DrvCode
@{ */

/** Driver revision constant. Used by @ref scgtInitDriverRev */
#define SCGT_DRIVER_VERSION     "1.7.7.2Win"  
/** scgt.cpp driver file revision number  2/Nov/2021 */
char *FILE_REV_SCGT_C = "C";

/** Base name of device. Unit number is appended to this name when calling IoCreateDevice */
#define SCGT_DEVICE_BASE_NAME   "\\Device\\scgt"

/** Base symbolic link name. Unit number is appended to symbolic link name when calling IoCreateSymbolicLink */
#define SCGT_SYMLINK_BASE_NAME   "\\??\\scgt"
 /** @} */

/** @addtogroup DMACodes
* @{ */
/** Semaphore timeout value ( 400 ms) */
#define SCGT_TIMEOUT_TIME    400  
/** @} */

/** @addtogroup IOCTLCode
* @{ */
/** Timer in 100ns per tick. Convert to 1/1000 second ticks. '-' indicates relative time difference. */
#define SCGT_GET_INTR_TIMER_REL_TIME (ULONG)(-100 * 10000)
/** @} */

/** Number of ticks per millisecond */
#define SCGT_GET_INTR_TIMER_MILLISEC 100


/* System cache line size */
#ifndef KSYS_CACHE_LINE_SIZE
/** Default GTCORE cache line size */
#define GTCORE_CACHE_LINE_SIZE  256
#else
/** user supplied  cache line size must be >= 16  */
#define GTCORE_CACHE_LINE_SIZE  KSYS_CACHE_LINE_SIZE    
#endif

/** @brief Number of byte needed for one DMA chain list. 
    Number of chain entries per list * size of one entry */
#define GTCORE_CHAIN_BYTES   (SCGT_DMA_CHAIN_LEN * GTCORE_CE_SIZE)

/*
 * define SCGT_EXCH_MAP_PER_CHAIN in scgtdrv.h in order to have
 * gtcoreExchChain() allocate and map 1 chain per exchange.  If this is
 * not defined, there will be an allocation and map per exchange direction 
 * which is shared across chains.  1 allocation and map per direction can
 * be beneficial on some systems by limiting the number of maps required 
 * (some machines have a limited number of maps).  The memory must be 
 * physically contiguous for this to work.
 */
 
#ifndef SCGT_EXCH_CHAIN_MAP_PER_EXCH
 /** @brief Flag to control chain list size */
#define SCGT_EXCH_CHAIN_ONE_MAP
#endif

#ifdef SCGT_EXCH_CHAIN_ONE_MAP
/** @brief  Chain list size  */
#define CHAIN_LIST_SIZE  GTCORE_CHAIN_BYTES * GTCORE_EXCH_CNT + GTCORE_CACHE_LINE_SIZE * 2
#else
/** @brief  Chain list size */
#define CHAIN_LIST_SIZE  (GTCORE_CHAIN_BYTES + GTCORE_CACHE_LINE_SIZE * 2 ) * GTCORE_EXCH_CNT
#endif

#define useNewDMA 1

/******************************************************************/
/********************* FUNCTION PROTOTYPES ************************/
/******************************************************************/

/* -------------------------------*/
/* Utility functions */
/* -------------------------------*/
PSTR scgtErrorCodeToString( _In_ const uint32 error);
PSTR NtStatusName(_In_ const NTSTATUS status);
PCSTR IrpMajorNameStr(const int irpMjNum);

static NTSTATUS scgtRequestIncrement( _Inout_ scgtDevice*  devExt);
static void scgtRequestDecrement( _Inout_ scgtDevice* dev);
#ifdef __cplusplus
int isQueueStalled(_In_ scgtDevice* dev) noexcept;
#else
int isQueueStalled( _In_ scgtDevice* dev) ;
#endif
static NTSTATUS scgtStallQueues( _In_ scgtDevice * dev );
static NTSTATUS scgtUnstallQueues( _Inout_ scgtDevice * dev );
static NTSTATUS scgtStopIO(_Inout_ scgtDevice * dev, _In_ const long long msTimeout);
void scgtWaitForRemove(_In_ scgtDevice* dev);

/* -------------------------------*/
/* Main Driver Code */
/* -------------------------------*/

/// @cond DDKDEF 
#ifdef __cplusplus
extern "C"    DRIVER_INITIALIZE DriverEntry;
#else
DRIVER_INITIALIZE DriverEntry;
#endif

__drv_dispatchType(IRP_MJ_SYSTEM_CONTROL)
DRIVER_DISPATCH scgtPnpDispatchSystemControl;
__drv_dispatchType(IRP_MJ_READ)
DRIVER_DISPATCH scgtDispatchRead;

__drv_dispatchType(IRP_MJ_WRITE)
DRIVER_DISPATCH scgtDispatchWrite;

__drv_dispatchType(IRP_MJ_CLEANUP)
DRIVER_DISPATCH scgtDispatchCleanup;
__drv_dispatchType(IRP_MJ_FLUSH_BUFFERS)
DRIVER_DISPATCH scgtDispatchFlushBuffers;
DRIVER_UNLOAD scgtExitDriver;
static void scgtInitDriverRev(void);
static ULONG scgtBusNum(_In_ PDEVICE_OBJECT physDevObj);
static ULONG scgtSlotNum(_In_ PDEVICE_OBJECT pDO);
DRIVER_ADD_DEVICE scgtAddDevice;
static NTSTATUS scgtBuildDevNameString(_In_ const PSTR baseStr,
    _In_ const uint32 unit,
    _Out_ PUNICODE_STRING outStr);
__drv_dispatchType(IRP_MJ_CREATE)
DRIVER_DISPATCH scgtOpen;
__drv_dispatchType(IRP_MJ_CLOSE)
DRIVER_DISPATCH scgtClose;

#ifdef NOT_USED
static int scgtGiveIntrSem(scgtDevice *dev);
#endif
static uint32 scgtGiveIntrSemDPC(_In_ scgtDevice *dev);

// DRIVER_STARTIO scgtStartIo();

/* ---------------------------------------*/
/*  GT IOCT code (IRP_MJ_DEVICE_CONTROL) */
/* ---------------------------------------*/
char* sctgIoctlCodeToStr(_In_ const int ioCode);

__drv_dispatchType(IRP_MJ_DEVICE_CONTROL)
DRIVER_DISPATCH scgtIoctl;

void scgtPrintStateName(_In_ const ULONG stateID);
static NTSTATUS scgtIoctl2(_In_ DEVICE_OBJECT *deviceObject, _In_ IRP *irp,
    _In_ const IO_STACK_LOCATION *irpStackLoc);
static int scgtIoctlGetStats(_In_ scgtDevice *dev, _Inout_ scgtStats *stats);
static NTSTATUS scgtIoctlMapMem(_In_ scgtDevice *dev, _In_ scgtMemMapInfo *mapInfo);
static NTSTATUS scgtIoctlUnmapMem(_In_ scgtDevice *dev, _In_ scgtMemMapInfo *mapInfo);
static void scgtGetIntrTimerStart(_In_ scgtDevice *dev);
static uint32 scgtIoctlGetIntr(_In_ scgtDevice *dev, _In_ scgtGetIntrBuf *gibuf);
static NTSTATUS scgtIoctlGetDeviceInfo(_In_ scgtDevice *dev, _In_ scgtDeviceInfo *devInfo);

/* ---------------------------------------*/
/*/ PNP code ( IRP_MJ_PNP ) */
/* ---------------------------------------*/


KDEFERRED_ROUTINE scgtGetIntrTimerCallback;
IO_COMPLETION_ROUTINE scgtCompletion;

static NTSTATUS NotifyBusDriver(PDEVICE_OBJECT TargetDevice, PIRP Irp);
static void scgtInitGetIntrTimer(_In_ scgtDevice *dev);
static void scgtDestroyGetIntrTimer(_In_ scgtDevice *dev);
void ShowPnpIrpName(_In_ const int function);
static NTSTATUS scgtInitDMA( IN scgtDevice *dev);
void scgtRemoveDevice(_In_ PDEVICE_OBJECT deviceObject);
char* scgtSystemStateToStr(_In_ const scgtDevice * dev);
#ifdef __cplusplus
static NTSTATUS scgtCanStopDevice(_In_ scgtDevice * dev) noexcept;
#else
static NTSTATUS scgtCanStopDevice(_In_ scgtDevice * dev);
#endif
static NTSTATUS scgtCanRemoveDevice( IN scgtDevice * dev, IN PIRP irp);
VOID scgtProcessQueuedRequests( IN scgtDevice* devExt);
static void scgtWaitForStop( _In_ scgtDevice* dev);

__drv_dispatchType(IRP_MJ_PNP)
DRIVER_DISPATCH scgtIoctlPNP;
IO_COMPLETION_ROUTINE scgtDeferedIrpCompletion;

static NTSTATUS scgtStartDevice(_In_ PDEVICE_OBJECT deviceObject, _In_ PIO_STACK_LOCATION IoStackLocation);
static void scgtStopDevice(_In_ PDEVICE_OBJECT deviceObject);

/* ---------------------------------------*/
/* Power management code (IRP_MJ_POWER) */
/* ---------------------------------------*/
void scgtPrintShutdownType(_In_ const int ShutdownType);

ULONG SupportedPowerState(_In_ const PIO_STACK_LOCATION irpStackLoc);
IO_COMPLETION_ROUTINE scgtDevicePowerCompletion;
__drv_dispatchType(IRP_MJ_POWER)
DRIVER_DISPATCH scgtIoctlPOWER;
REQUEST_POWER_COMPLETE DevicePowerCompletionRoutine;
IO_COMPLETION_ROUTINE DevicePowerIoCompletionRoutine;
IO_COMPLETION_ROUTINE SystemPowerIoCompletionRoutine;
/// @endcond
#ifdef NOT_USED
static void scgtCopyToUser(void *userPtrDest, void *src, uint32 numBytes);
#endif
static void scgtCopyFromUser(_Out_writes_bytes_all_(numBytes) void *dst,
    _In_reads_bytes_(numBytes) void *userPtrSrc,
    uint32 numBytes);

/* ---------------------------------------*/
/* DMA CODE */
/* ---------------------------------------*/
static int scgtInitDMATools(_In_ scgtDevice *dev);
static void scgtDestroyDMATools(_In_ const scgtDevice *dev);
static uint32 scgtIoctlXfer(_In_ scgtDevice *dev,
    _In_ PIRP irp,
    _In_ scgtXfer *xfer,
    _In_opt_ scgtInterrupt *intr,
    _In_ const uint8 direction);

static uint32 scgtXferChunk(_In_ scgtDevice *dev,
    _In_ PIRP irp,
    _In_ uint32 gtMemoryOffset,
    _Inout_ uint8  *pBuf,
    _In_ uint32 chunkSize,
    _In_ uint8 lastTransfer,
    _In_ uint32 flags,
    _In_opt_ scgtInterrupt *intr,
    _In_ const uint8  direction,
    _Inout_ scgtDMATools *tools,
    _In_ uint32 *bytesTransferred,
    _Inout_ ksysSemS **semToGive);
static uint32 scgtBuildChainList(_In_ scgtDevice *dev, _In_ PMDL mdl, _In_ gtcoreExch *exch,
    _In_ uint32 const direction, _In_ uint32 chainLenBytes);

/* ---------------------------------------*/
/* INTERRUPT SERVICE */
/* ---------------------------------------*/
/// @cond DDKDEF 
KSERVICE_ROUTINE scgtISR;
IO_DPC_ROUTINE scgtDPC;
KSTART_ROUTINE scgtInterruptThread;
/// @endcond
NTSTATUS scgtStartThread(_In_ scgtDevice *dev);
void scgtStopThread(_In_ scgtDevice *dev);
uint32 scgtDrvExchChain(scgtDevice *dev, gtcoreExchMgrData *exchMgrData,
    uint32 exchNum, uint8 direction, uint8 doAlloc);
uint32 scgtDrvTrQueue(scgtDevice *dev, gtcoreExchMgrData *exchMgrData,
    uint8 direction, uint8 doAlloc);


extern void * gtcoreAlignAddr(void *ptr, uint32 alignSize);
extern void gtcoreCancelTransfer(scgtDevice *dev, gtcoreExch *exch, const uint8 direction);


/************************************************************************/
/***************************  G L O B A L S  ****************************/
/************************************************************************/

/** Number of GT Cards found  */
uint32 numDevices = 0;
/** Get interrupt timer exit flag */
int getIntrTimerExit = 0;
/**  Driver revision string  */
char driverRevStr[128];

/** Driver registry string max length */
#define  regPathBufferSize  128
/** Driver registry path buffer */
WCHAR wdmRegistryBuffer[regPathBufferSize]; 
/** Driver registry path Unicode string */
UNICODE_STRING scgtWdmRegistryPath;

/** External file revision values */
extern char* FILE_REV_GTCORE_C;
extern char* FILE_REV_GTCOREXFER_C;
extern char* FILE_REV_KSYS_C;

/** GUID (generated with guidgen) **/
/** {EFEFE9A1-A66A-11d7-A8F4-005004166373} */
static const GUID scgtGUID =
{ 0xefefe9a1, 0xa66a, 0x11d7, { 0xa8, 0xf4, 0x0, 0x50, 0x4, 0x16, 0x63, 0x73 } };

/** flag to indicate which debug messages to display */
ULONG32 ScgtDrvDebugLevel;  

/************************************************************************/
/******************************** CODE **********************************/
/************************************************************************/

/** @defgroup UtilCode Utility functions
SCRAMNet Gt Utility functions
@{
**/


/*************************************************************************//**
* @fn ULONG ScgtDbgPrint( _In_ ULONG flag, _In_ LPSTR Format, ...)
* @brief Send string to kernel debugger if flag and scgtDebugLevel not equal 
* to zero. Prefix  "SCGT:" added start of string.
* @param flag  value used to determine if sting printed.
* @param Format printf style format string 
* @return returns STATUS_SUCCESS if the operation succeeds. 
* Otherwise, this routine returns the appropriate error code.
**//*************************************************************************/
NTSTATUS ScgtDbgPrint( _In_ const ULONG flag, _In_ const LPSTR Format, ...)
{
    va_list arglist = NULLPTR;    
    LARGE_INTEGER timeStamp = { 0,0 };
    char prefix[25] = { 0 };
    timeStamp.QuadPart = 0;

    if ((flag & ScgtDrvDebugLevel) == 0)
        return STATUS_SUCCESS;

    //can be call at IRQL <= DIRQL
    if (KeGetCurrentIrql() >= CLOCK_LEVEL)
        return STATUS_UNSUCCESSFUL;

    va_start(arglist, Format);

    if ((ScgtDrvDebugLevel & SCGT_TIMESTAMP) != 0)
    {
        timeStamp = KeQueryPerformanceCounter(NULLPTR);
        sprintf_s(prefix,25,"%012I64x SCGT: ", timeStamp.QuadPart); 
    }
    else
        sprintf_s(prefix,25, "SCGT: ");

    vDbgPrintExWithPrefix(prefix, DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, Format, arglist);

    // Ignore waring C26477  in va_arg macro. 
    // Warning C26477 : Use 'nullptr' rather than 0 or NULL(es.47).
    WARNING_SUPPRESS(26477)
    va_end(arglist);

    

    return STATUS_SUCCESS;
}

/*************************************************************************//**
*@fn NTSTATUS ScgtDbgPrintNoPrefix( _In_ ULONG flag, _In_ LPSTR Format, ...)
* @brief Send string to kernel debugger if flag and scgtDebugLevel not equal
* to zero.
* @param flag  value used to determine if sting printed.
* @param Format Printf style format string
* @return returns STATUS_SUCCESS if the operation succeeds.
* Otherwise, this routine returns the appropriate error code.
**//*************************************************************************/
NTSTATUS ScgtDbgPrintNoPrefix( _In_ ULONG flag, _In_ LPSTR Format, ...)
{
    va_list arglist=NULLPTR;
    NTSTATUS status= STATUS_SUCCESS;    

    if ((flag & ScgtDrvDebugLevel) == 0)
        return STATUS_SUCCESS;

    va_start(arglist, Format);

    status = (NTSTATUS) vDbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, Format, arglist);

    // Ignore waring C26477  in va_arg macro. 
    // Warning C26477 : Use 'nullptr' rather than 0 or NULL(es.47).
    WARNING_SUPPRESS( 26477)
    va_end(arglist);

    return status;
}

/**************************************************************************//**
 * @fn char * scgtErrorCodeToString( _In_ uint32 error )
 * @brief  Convert GT error code to string.
 * @param [in] error GT error code
 * @return  String representation of GT code.
**//**************************************************************************/
PSTR scgtErrorCodeToString( _In_ const uint32 error)
{
    static char str[30];
    switch (error)
    {
        case SCGT_SUCCESS:
            RtlStringCbPrintfA(str, 30, "SCGT_SUCCESS");
            break;
        case SCGT_SYSTEM_ERROR:
            RtlStringCbPrintfA(str, 30, "SCGT_SYSTEM_ERROR");
            break;
        case SCGT_BAD_PARAMETER:
            RtlStringCbPrintfA(str, 30, "SCGT_BAD_PARAMETER");
            break;
        case SCGT_DRIVER_ERROR:
            RtlStringCbPrintfA(str, 30, "SCGT_DRIVER_ERROR");
            break;
        case SCGT_TIMEOUT:
            RtlStringCbPrintfA(str, 30, "SCGT_TIMEOUT");
            break;
        case SCGT_CALL_UNSUPPORTED:
            RtlStringCbPrintfA(str, 30, "SCGT_CALL_UNSUPPORTED");
            break;
        case SCGT_INSUFFICIENT_RESOURCES:
            RtlStringCbPrintfA(str, 30, "SCGT_INSUFFICIENT_RESOURCES");
            break;
        case SCGT_LINK_ERROR:
            RtlStringCbPrintfA(str, 30, "SCGT_LINK_ERROR");
            break;
        case SCGT_MISSED_INTERRUPTS:
            RtlStringCbPrintfA(str, 30, "SCGT_MISSED_INTERRUPTS");
            break;
        case SCGT_DRIVER_MISSED_INTERRUPTS:
            RtlStringCbPrintfA(str, 30, "SCGT_DRIVER_MISSED_INTERRUPTS");
            break;
        case SCGT_DMA_UNSUPPORTED:
            RtlStringCbPrintfA(str, 30, "SCGT_DMA_UNSUPPORTED");
            break;
        case SCGT_HARDWARE_ERROR:
            RtlStringCbPrintfA(str, 30, "SCGT_HARDWARE_ERROR ");
            break;
        default:
            RtlStringCbPrintfA(str, 30, "Unknown 0x%x\n", error);
            break;
    }
    return str;
} /* scgtErrorCodeToString */

/**************************************************************************//**
* @fn char * NtStatusName( _In_ NTSTATUS status)
* @brief Return string name representing NTSTATUS code
* @param [in] status NTSTATUS value
* @return String representation of NTSTATUS value.
**//**************************************************************************/
PSTR NtStatusName( _In_ const NTSTATUS status )
{
    /** Maximum NtStatusName string length */
    #define StatusNameLen 30
    static char buffer[StatusNameLen];
    RtlZeroMemory(buffer, StatusNameLen);

    switch (status)
    {
    case STATUS_WAIT_2:
        RtlStringCbPrintfA(buffer, StatusNameLen, "STATUS_WAIT_2 ");
        break;
    case  STATUS_INSUFFICIENT_RESOURCES:
        RtlStringCbPrintfA(buffer, StatusNameLen, "STATUS_INSUFFICIENT_RESOURCES ");
        break;
    case  STATUS_NOT_SUPPORTED:
        RtlStringCbPrintfA(buffer, StatusNameLen, "STATUS_NOT_SUPPORTED ");
        break;
    case STATUS_PENDING:
        RtlStringCbPrintfA(buffer, StatusNameLen, "STATUS_PENDING ");
        break;
    case STATUS_UNSUCCESSFUL:
        RtlStringCbPrintfA(buffer, StatusNameLen, "STATUS_UNSUCCESSFUL ");
        break;
    case STATUS_NOT_IMPLEMENTED:
        RtlStringCbPrintfA(buffer, StatusNameLen, "STATUS_NOT_IMPLEMENTED ");
        break;
    case STATUS_INVALID_HANDLE:
        RtlStringCbPrintfA(buffer, StatusNameLen, "STATUS_INVALID_HANDLE ");
        break;
    case STATUS_INVALID_PARAMETER:
        RtlStringCbPrintfA(buffer, StatusNameLen, "STATUS_INVALID_PARAMETER ");
        break;
    case STATUS_NO_SUCH_FILE:
        RtlStringCbPrintfA(buffer, StatusNameLen, "STATUS_NO_SUCH_FILE ");
        break;
    case STATUS_INVALID_DEVICE_REQUEST:
        RtlStringCbPrintfA(buffer, StatusNameLen, "STATUS_INVALID_DEVICE_REQUEST ");
        break;
    case STATUS_MORE_PROCESSING_REQUIRED:
        RtlStringCbPrintfA(buffer, StatusNameLen, "STATUS_MORE_PROCESSING_REQUIRED ");
        break;
    case STATUS_CONFLICTING_ADDRESSES:
        RtlStringCbPrintfA(buffer, StatusNameLen, "STATUS_CONFLICTING_ADDRESSES ");
        break;
    case STATUS_DEVICE_NOT_READY:
        RtlStringCbPrintfA(buffer, StatusNameLen, "STATUS_DEVICE_NOT_READY ");
        break;
    case STATUS_DEVICE_DOES_NOT_EXIST:
        RtlStringCbPrintfA(buffer, StatusNameLen, "STATUS_DEVICE_DOES_NOT_EXIST ");
        break;
    case STATUS_BAD_DEVICE_TYPE:
        RtlStringCbPrintfA(buffer, StatusNameLen, "STATUS_BAD_DEVICE_TYPE ");
        break;
    case STATUS_CANCELLED:
        RtlStringCbPrintfA(buffer, StatusNameLen, "STATUS_CANCELLED ");
        break;
    case STATUS_DLL_NOT_FOUND:
        RtlStringCbPrintfA(buffer, StatusNameLen, "STATUS_DLL_NOT_FOUND ");
        break;
    case STATUS_IO_DEVICE_ERROR:
        RtlStringCbPrintfA(buffer, StatusNameLen, "STATUS_IO_DEVICE_ERROR ");
        break;
    case STATUS_DEVICE_REMOVED:
        RtlStringCbPrintfA(buffer, StatusNameLen, "STATUS_DEVICE_REMOVED ");
        break;
    case STATUS_PNP_TRANSLATION_FAILED:
        RtlStringCbPrintfA(buffer, StatusNameLen, "STATUS_PNP_TRANSLATION_FAILED ");
        break;
    case STATUS_SUCCESS:
        RtlStringCbPrintfA(buffer, StatusNameLen, "STATUS_SUCCESS ");
        break;
    case STATUS_OBJECT_NAME_NOT_FOUND:
        RtlStringCbPrintfA(buffer, StatusNameLen, "STATUS_OBJECT_NAME_NOT_FOUND ");
        break;
    case STATUS_INVALID_DEVICE_STATE:
        RtlStringCbPrintfA(buffer, StatusNameLen, "STATUS_INVALID_DEVICE_STATE ");
        break;
    case STATUS_DEVICE_BUSY:
        RtlStringCbPrintfA(buffer, StatusNameLen, "STATUS_INVALID_DEVICE_STATE ");
        break;
    default:
        RtlStringCbPrintfA(buffer, StatusNameLen, "0x%lx ", status);
        break;
    }
    return buffer;
} /* NtStatusName */

/**
@fn PSTR IrpMajorNameStr(const int irpMjNum)
@brief Convert IRP major number value to string
@param irpMjNum irp major number
@return pointer to a string.
**/
WARNING_SUPPRESS(26440) //function 'IrpMajorNameStr' can be declared 'noexcept' (f.6).
PCSTR IrpMajorNameStr(const int irpMjNum)
{
    switch (irpMjNum)
    {
    case IRP_MJ_CREATE:                   return "IRP_MJ_CREATE"; break;
    case IRP_MJ_CREATE_NAMED_PIPE:        return "IRP_MJ_CREATE_NAMED_PIPE"; break;
    case IRP_MJ_CLOSE:                    return "IRP_MJ_CLOSE";  break;
    case IRP_MJ_READ:                     return "IRP_MJ_READ"; break;
    case IRP_MJ_WRITE:                    return "IRP_MJ_WRITE"; break;
    case IRP_MJ_QUERY_INFORMATION:        return "IRP_MJ_QUERY_INFORMATION"; break;
    case IRP_MJ_SET_INFORMATION:          return "IRP_MJ_SET_INFORMATION"; break;
    case IRP_MJ_QUERY_EA:                 return "IRP_MJ_QUERY_EA"; break;
    case IRP_MJ_SET_EA:                   return "IRP_MJ_SET_EA"; break;
    case IRP_MJ_FLUSH_BUFFERS:            return "IRP_MJ_FLUSH_BUFFERS"; break;
    case IRP_MJ_QUERY_VOLUME_INFORMATION: return "IRP_MJ_QUERY_VOLUME_INFORMATION"; break;
    case IRP_MJ_SET_VOLUME_INFORMATION:   return "IRP_MJ_SET_VOLUME_INFORMATION"; break;
    case IRP_MJ_DIRECTORY_CONTROL:        return "IRP_MJ_DIRECTORY_CONTROL"; break;
    case IRP_MJ_FILE_SYSTEM_CONTROL:      return "IRP_MJ_FILE_SYSTEM_CONTROL"; break;
    case IRP_MJ_DEVICE_CONTROL:           return "IRP_MJ_DEVICE_CONTROL"; break;
    case IRP_MJ_INTERNAL_DEVICE_CONTROL:  return "IRP_MJ_INTERNAL_DEVICE_CONTROL"; break;
    case IRP_MJ_SHUTDOWN:                 return "IRP_MJ_SHUTDOWN"; break;
    case IRP_MJ_LOCK_CONTROL:             return "IRP_MJ_LOCK_CONTROL"; break;
    case IRP_MJ_CLEANUP:                  return "IRP_MJ_CLEANUP"; break;
    case IRP_MJ_CREATE_MAILSLOT:          return "IRP_MJ_CREATE_MAILSLOT"; break;
    case IRP_MJ_QUERY_SECURITY:           return "IRP_MJ_QUERY_SECURITY"; break;
    case IRP_MJ_SET_SECURITY:             return "IRP_MJ_SET_SECURITY"; break;
    case IRP_MJ_POWER:                    return "IRP_MJ_POWER"; break;
    case IRP_MJ_SYSTEM_CONTROL:           return "IRP_MJ_SYSTEM_CONTROL"; break;
    case IRP_MJ_DEVICE_CHANGE:            return "IRP_MJ_DEVICE_CHANGE"; break;
    case IRP_MJ_QUERY_QUOTA:              return "IRP_MJ_QUERY_QUOTA"; break;
    case IRP_MJ_SET_QUOTA:                return "IRP_MJ_SET_QUOTA"; break;
    case IRP_MJ_PNP:                      return "IRP_MJ_PNP"; break;
    default:                              return "Invalid IRP_MJ code"; break;
    }
}


/**************************************************************************//**
@fn void scgtRequestIncrement( _Inout_ scgtDevice*  devExt)
@brief Increment outstanding I/O request count. Clear Remove event flag when
it is incremented from zero to one.
@param [in,out] devExt scgtDevice extension
@return NTSTATUS value. STATUS_SUCCESS or STATUS_UNSUCCESSFUL
**//**************************************************************************/
static NTSTATUS scgtRequestIncrement( _Inout_ scgtDevice*  devExt )
{
    LONG result = 0;
    NTSTATUS ret = STATUS_SUCCESS;

    result = InterlockedIncrement(&devExt->OutstandingIO);
    ScgtDbgPrint(SCGT_DBG_INC_DEC, "scgtRequestIncrement result=%d\n", result);
    if (result <= 0)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtRequestIncrement() I/O count:%d < 0\n", result);
        InterlockedExchange(&devExt->OutstandingIO, 0);
        ret = STATUS_UNSUCCESSFUL;
        return ret;
    }

    // Need to clear Remove
    if (result == 1)
    {
        //
        // The remove event is cleared when the first request is
        // added to the queue.
        //
        KeClearEvent(&devExt->RemoveEvent);
    }

#ifdef USE_REMOVE_LOCK
    ret = IoAcquireRemoveLock(&devExt->RemoveLock, (PVOID)'TGCS');
    if (!NT_SUCCESS(ret))
    {
        if (ret == STATUS_DELETE_PENDING)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, " Acquire Remove Lock call failed. driver in delete pending state. (IRP_MN_REMOVE_DEVICE).\n");
        }
        else
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, " Acquire Remove Lock call failed. return code 0x%x.\n", ret);
        }
        InterlockedDecrement(&devExt->OutstandingIO);
    }
#endif

    return ret;
} /* scgtRequestIncrement */

/**************************************************************************//**
 @fn static void scgtRequestDecrement( _Inout_ scgtDevice* dev)
 @brief Decrement outstanding I/O request count. Set Remove event flag when
 count reaches zero.
 @param [in,out] dev scgtDevice extension
**//**************************************************************************/
static void scgtRequestDecrement( _Inout_ scgtDevice* dev)
{
    LONG result;
    result = InterlockedDecrement(&dev->OutstandingIO);
    ScgtDbgPrint(SCGT_DBG_INC_DEC, "scgtRequestDecrement result=%d\n", result);
    if (result < 0)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtRequestDecrement() Error I/O count: %d < 0 ", result);
        InterlockedExchange(&dev->OutstandingIO, 0);
        result = 0;
    }

    if (result == 0)
    {
        // The remove event is set when the queue is totally EMPTY.     
        KeSetEvent(&dev->RemoveEvent,
            IO_NO_INCREMENT,
            FALSE);
    }

#ifdef USE_REMOVE_LOCK
    IoReleaseRemoveLock(&dev->RemoveLock, (PVOID)'tgcs');
#endif

} // scgtRequestDecrement

 /**************************************************************************//**
 * @fn int isQueueStalled( _In_ scgtDevice* dev )
 * @brief Look to see if queue is stalled.
 * @param [in] dev - Pointer to SCGT device object
 * @return TRUE or FALSE
**//**************************************************************************/
#ifdef __cplusplus
int isQueueStalled(_In_ scgtDevice* dev) noexcept
#else
int isQueueStalled( _In_ scgtDevice* dev) 
#endif
{
    long value = 0;
    long hold = 0;
    value = InterlockedCompareExchange((PLONG)&dev->HoldNewRequests, (LONG)TRUE, (LONG)TRUE);
    
    hold = InterlockedCompareExchange(&dev->holdNewRequests, 1, 1);
    if ( (value == TRUE) && (hold == 1) )
    {
        return TRUE;
    }
    return FALSE;
} /* isQueueStalled */

/**************************************************************************//**
   @fn static NTSTATUS scgtStallQueues( _In_ scgtDevice * dev)
   @brief Hold new request from being processed.
   @param [in,out] dev scgtDevice object
   @return NTSTATUS STATUS_SUCCESS
**//**************************************************************************/
static NTSTATUS scgtStallQueues( _In_ scgtDevice * dev) 
{
    long count = 0;
    long state = 0;
    long value = 0;
    state = InterlockedExchange((PLONG)&dev->HoldNewRequests, (LONG)TRUE);
    value = InterlockedCompareExchange(&dev->holdNewRequests, 1, 0);

    if ((state == (LONG)FALSE) || (value == 0))
    {
        ScgtDbgPrint(SCGT_DBG_QUEUE, "scgtStallQueues() Changed hold state to prevent new requests\n");
    }

    count = InterlockedCompareExchange(&dev->requestCount, 0, 0);
    if (count >= 1)
    {
        ScgtDbgPrint(SCGT_DBG_QUEUE, "scgtStallQueues() Queue stopped with %d I/O request outstanding\n", count);
    }
    else if (count < 0)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtStallQueues() request count:%d < 0. resetting to zero.\n",
            count);
        InterlockedExchange(&dev->requestCount, 0);
    }
    return STATUS_SUCCESS;
} // scgtStallQueues


/**************************************************************************//**
@fn static NTSTATUS scgtUnstallQueues( _Inout_ scgtDevice * dev )
@brief  Set flag to stop holding new request
@param dev - Pointer to SCGT device object.
@return NTSTATUS value. STATUS_SUCCESS
**//**************************************************************************/
static NTSTATUS scgtUnstallQueues(_Inout_ scgtDevice * dev)
{
    LONG state = 0;
    LONG value = 0;
    LONG count = 0;

    value = InterlockedExchange((PLONG)&dev->HoldNewRequests, (LONG)FALSE);
    state = InterlockedCompareExchange(&dev->holdNewRequests, 0, 1);
    if ((state == 1) || (value == (LONG)TRUE))
    {
        ScgtDbgPrint(SCGT_DBG_QUEUE, "scgtUnstallQueues() Changed hold state to allow new requests\n");
    }

    count = InterlockedCompareExchange(&dev->requestCount, 0, 0);
    if (count < 0)
    {
        // reset count to zero if it went below zero;
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtUnstallQueues() request count:%d < 0. resetting to zero.\n",
            count);
        InterlockedExchange(&dev->requestCount, 0);
    }
    else if (count > 0)
    {
        ScgtDbgPrint(SCGT_DBG_QUEUE, "SCGT scgtUnstallQueues() Queue un-stalled with %d entries in queue\n", count);
    }


    return STATUS_SUCCESS;
} // scgtUnstallQueues

/**************************************************************************//**
@fn static NTSTATUS scgtStopIO( _Inout_ scgtDevice * dev, _In_ const long long msTimeout)
@brief Attempt to stop IO within time out period specified.
@param dev        pointer to SCGT device object
@param msTimeout  time out period in milliseconds.
@return STATUS_SUCCESS or STATUS_TIMEOUT
**//**************************************************************************/
static NTSTATUS scgtStopIO(_Inout_ scgtDevice * dev, _In_ const long long msTimeout)
{
    long count = 0;
    NTSTATUS ret = STATUS_SUCCESS;

    if (InterlockedCompareExchange(&dev->holdNewRequests, 0, 1) != 1)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtStopIO() called with hold requests not set\n");
    }
    count = InterlockedCompareExchange(&dev->requestCount, 0, 0);
    if (count > 0)
    {
        LARGE_INTEGER timeout = { 0,0 };
        timeout.QuadPart = (1000000l / 100) * msTimeout;
        ScgtDbgPrint(SCGT_DBG_INFO, "scgtStopIO() waiting up to %lld ms for stop event\n", msTimeout);
        ret = KeWaitForSingleObject(&dev->StopEvent, Executive, KernelMode, FALSE, &timeout);
        if (ret == STATUS_TIMEOUT)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "scgtStopIO(): Timeout waiting for Request Count to reach 0\n");
        }
    }
    return ret;
}// scgtStopIO


/**************************************************************************//**
@fn VOID scgtClearQueues(_In_ scgtDevice* dev)
@brief Stop incoming and current ioctl requests.
@param dev Pointer to SCGT device object.
*//**************************************************************************/
VOID scgtClearQueues( _In_ scgtDevice* dev)
{
    NTSTATUS ret = STATUS_SUCCESS;
    scgtStallQueues(dev);
    ret=scgtStopIO(dev, 1000);
    if (ret == STATUS_TIMEOUT )
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtClearQueues() stop IO call timed out\n");
    }
}
 
/** Flag to alter functionality of scgtWaitForRemove() function. */
#define DEBUG_REMOVE 
/**************************************************************************//**
@fn void scgtWaitForRemove( _In_ scgtDevice* dev )
@brief Wait for remove lock to enter signaled state.
@param [in] dev Pointer to SCGT device object
**//**************************************************************************/
void scgtWaitForRemove(_In_ scgtDevice* dev)
{
#ifdef DEBUG_REMOVE
    LARGE_INTEGER timeout = { 0,0 };
    NTSTATUS status=STATUS_SUCCESS;
    LONG count = 0;
    timeout.QuadPart = -5 * 10 * 1000 * 1000;
    status = STATUS_TIMEOUT;
    ScgtDbgPrint(SCGT_DBG_INFO, "scgtWaitForRemove(): IRP_MN_REMOVE_DEVICE Waiting on remove event to be signaled\n");
    while ( (status == STATUS_TIMEOUT) && ( count < 20) )
    {
        status = KeWaitForSingleObject(&dev->RemoveEvent,
            Executive,
            KernelMode,
            FALSE,
            &timeout);
        if (status == STATUS_TIMEOUT)
        {
            ScgtDbgPrint(SCGT_DBG_INFO, "Still Waiting for activity to Stop on device. %d request remain\n", dev->OutstandingIO);
        }
        count++;
    }
#else
    KeWaitForSingleObject(&dev->RemoveEvent,
        Executive,
        KernelMode,
        FALSE,
        NULL);
#endif

#ifdef USE_REMOVE_LOCK
    ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_REMOVE_DEVICE Wait on remove lock\n");
    IoReleaseRemoveLockAndWait(&dev->RemoveLock, (PVOID)'TGCS');
#endif

} // scgtWaitForRemove


/** @} */

/** @defgroup DrvCode Main driver functions
SCRAMNet Gt Main driver functions.
@{
**/

/**************************************************************************//**
 * @fn  NTSTATUS DriverEntry( PDRIVER_OBJECT driverObject,
 *                            PUNICODE_STRING registryPath)
 *
 * @brief Main driver entry point.
 * @param  driverObject   Pointer to the driver object structure.
 * @param  registryPath   Full pathname of the registry file.
          RegistryPath is of the form "\Registry\Machine\System\CurrentControlSet\Services<i>DriverName"
 *
 *
 * @return STATUS_SUCCESS
 *****************************************************************************/
#ifdef __cplusplus
extern "C"
{
#endif
    _Use_decl_annotations_
        NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject,
            PUNICODE_STRING registryPath)
    {
        //DLC added UNREFERENCED_PARAMETER to prevent Warning C4100 Unreferenced formal parameter
#ifndef SAVE_REG_PATH
        UNREFERENCED_PARAMETER(registryPath);
#else
        scgtWdmRegistryPath.Buffer = wdmRegistryBuffer;
        scgtWdmRegistryPath.Length = 0;
        scgtWdmRegistryPath.MaximumLength = sizeof(wdmRegistryBuffer);
        RtlUnicodeStringCopy(scgtWdmRegistryPath, registryPath);
#endif

        ScgtDrvDebugLevel =  SCGT_DBG_ERROR | SCGT_DBG_WARNING |  SCGT_DBG_VERSION | SCGT_DBG_IS64 ;
        //ScgtDrvDebugLevel |= SCGT_DBG_POWER | SCGT_DBG_INIT | SCGT_DBG_HALT;
        //ScgtDrvDebugLevel = 0xFFFFFFFF;

        DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "ScgtDrvDebugLevel=0x%x\n", ScgtDrvDebugLevel);

        ScgtDbgPrint(SCGT_DBG_FLOW, "DriverEntry() - {\n" );

        ScgtDbgPrint( SCGT_DBG_PNP, "DriverObject = 0x%08p\n", driverObject);
        ScgtDbgPrint( SCGT_DBG_PNP, "DeviceObject = 0x%08p\n", driverObject->DeviceObject );
        
        /* If you are building a driver that targets versions of Windows prior to Windows 10, version 2004,
          you must ExInitializeDriverRuntime during driver initialization, before calling the pool allocation functions. */
        ExInitializeDriverRuntime(DrvRtPoolNxOptIn);            

        /* Setup driver entry points */
        // Unload function
        driverObject->DriverUnload                         = scgtExitDriver;
        driverObject->DriverExtension->AddDevice           = scgtAddDevice;
        driverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = scgtIoctl;
        driverObject->MajorFunction[IRP_MJ_CREATE]         = scgtOpen;
        driverObject->MajorFunction[IRP_MJ_CLOSE]          = scgtClose;
        driverObject->MajorFunction[IRP_MJ_PNP]            = scgtIoctlPNP;
        driverObject->MajorFunction[IRP_MJ_POWER]          = scgtIoctlPOWER;
 
        driverObject->MajorFunction[IRP_MJ_READ]           = scgtDispatchRead;
        driverObject->MajorFunction[IRP_MJ_WRITE]          = scgtDispatchWrite;   
        driverObject->MajorFunction[IRP_MJ_FLUSH_BUFFERS]  = scgtDispatchFlushBuffers;
        driverObject->MajorFunction[IRP_MJ_CLEANUP]        = scgtDispatchCleanup;

        driverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = scgtPnpDispatchSystemControl;

        // driverObject->DriverStartIo = scgtStartIo;
	
        scgtInitDriverRev();

        //DLC using __DATE__ leads to a driver that is not the same build to build, thus not serviceable 
        // or diffable since these values are embedded in binary itself
        // __DATE__ not allowed in vs2017 release build unless properties changed in 
        // Driver Model Settings "Driver Setting->Driver Model->Allow Date, Time, Timestamp"
#if defined DBG
        ScgtDbgPrint(SCGT_DBG_VERSION, "SCGT driver version %s. Compiled %s %s\n", driverRevStr, __DATE__, __TIME__);
#else
        ScgtDbgPrint(SCGT_DBG_VERSION, "SCGT driver version %s.\n", driverRevStr);
#endif
        ScgtDbgPrint(SCGT_DBG_FLOW, "DriverEntry() - }\n");

        return STATUS_SUCCESS;
    } // DriverEntry

#ifdef __cplusplus
}
#endif

/**************************************************************************//**
 * @fn NTSTATUS scgtPnpDispatchSystemControl(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
 * @brief dispatch function to handle IRP_MJ_SYSTEM_CONTROL calls.
 * @param DeviceObject - pointer to a device object
 * @param  Irp         - pointer to an I/O Request Packet
 * @return Value: If the routine Succeeds, it must return STATUS_SUCCESS.
 *  Otherwise it must return one of the Error states defined in ntstatus.h
**//**************************************************************************/
_Use_decl_annotations_
NTSTATUS scgtPnpDispatchSystemControl(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    scgtDevice*   DeviceExtension = NULLPTR;

    ScgtDbgPrint(SCGT_DBG_FLOW, "Enter PnpScrDispatchSystemControl  - {\n");

    IoSkipCurrentIrpStackLocation(Irp);

    // get pointer to device extension
    DeviceExtension = (scgtDevice*)DeviceObject->DeviceExtension;
    // Since we have not created a WMI interface pass this request down to 
    // the driver below us.

    ntStatus = IoCallDriver(DeviceExtension->lowerDeviceInStack, Irp);

    ScgtDbgPrint(SCGT_DBG_FLOW, "Exit scgtPnpDispatchSystemControl NtStatus:%s- }\n",
        NtStatusName(ntStatus));

    return ntStatus;
} // scgtPnpDispatchSystemControl

/**************************************************************************//**
 * @fn  NTSTATUS scgtDispatchRead( PDEVICE_OBJECT  DeviceObject, PIRP  Irp )
 * @brief   Dispatch function to handle IRP_MJ_READ calls.
 * @param DeviceObject system device object
 * @param Irp system IRP
 * @return STATUS_SUCCESS or STATUS_UNSUCCESSFUL
 *****************************************************************************/
_Use_decl_annotations_
NTSTATUS scgtDispatchRead(PDEVICE_OBJECT  DeviceObject, PIRP  Irp)
{
    //required but not used
    NTSTATUS ret = STATUS_SUCCESS;
    scgtDevice *dev = (scgtDevice *)DeviceObject->DeviceExtension;
    ScgtDbgPrint(SCGT_DBG_FLOW, "scgtDispatchRead() - {\n");
    if (DeviceObject == NULLPTR)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtDispatchRead() Error DeviceObject is NULL pointer\n");
        return STATUS_UNSUCCESSFUL;
    }
    if (Irp == NULLPTR)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "Error scgtDispatchRead()  Irp is NULL pointer\n");
        return STATUS_UNSUCCESSFUL;
    }

    dev = (scgtDevice *)DeviceObject->DeviceExtension;

    // See what sort of state we're in. In the REMOVE_PENDING state
    // we handle all Irps except for creates
    if (dev->SystemState < STATE_ALL_BELOW_FAIL)
    {
        ret = STATUS_UNSUCCESSFUL;
        Irp->IoStatus.Information = 0;
        Irp->IoStatus.Status = ret;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        ScgtDbgPrint(SCGT_DBG_FLOW, "scgtDispatchRead() NtStatus:%s- }\n", NtStatusName(ret));
        return ret;
    }

    // IN 1
    ret = scgtRequestIncrement(dev);
    if (ret != STATUS_SUCCESS)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtDispatchRead() - failed to acquire Remove lock.\n");
    }
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    // OUT 1
    scgtRequestDecrement(dev);
    ScgtDbgPrint(SCGT_DBG_FLOW, "scgtDispatchRead() NtStatus:%s- }\n",
        NtStatusName(ret));
    return ret;
} // scgtDispatchRead

/**************************************************************************//**
 * @fn  NTSTATUS scgtDispatchWrite( PDEVICE_OBJECT  DeviceObject, PIRP  Irp )
 * @brief  dispatch function to handle IRP_MJ_WRITE calls.
 * @param DeviceObject system device object
 * @param Irp system IRP
 * @return STATUS_SUCCESS or STATUS_UNSUCCESSFUL
 *****************************************************************************/
_Use_decl_annotations_
NTSTATUS scgtDispatchWrite(PDEVICE_OBJECT  DeviceObject, PIRP  Irp)
{
    //required but not used
    NTSTATUS ret = STATUS_SUCCESS;
    scgtDevice *dev = (scgtDevice *)DeviceObject->DeviceExtension;

    ScgtDbgPrint(SCGT_DBG_FLOW, "scgtDispatchWrite() - {\n");

    ASSERT((DeviceObject != NULLPTR) && (Irp != NULLPTR));

    // See what sort of state we're in. In the REMOVE_PENDING state
    // we handle all IRPs except for creates
    if (dev->SystemState < STATE_ALL_BELOW_FAIL)
    {
        ret = STATUS_UNSUCCESSFUL;
        Irp->IoStatus.Information = 0;
        Irp->IoStatus.Status = ret;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        ScgtDbgPrint(SCGT_DBG_FLOW, "scgtDispatchWrite() NtStatus:%s- }\n", NtStatusName(ret));
        return ret;
    }
    //IN 2
    ret = scgtRequestIncrement(dev);
    if (ret != STATUS_SUCCESS)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtDispatchWrite() - failed to acquire Remove lock.\n");
    }
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    //OUT 2
    scgtRequestDecrement(dev);
    ScgtDbgPrint(SCGT_DBG_FLOW, "scgtDispatchWrite() NtStatus:%s- }\n", NtStatusName(ret));
    return ret;
} // scgtDispatchWrite


/**************************************************************************//**
 * @fn  NTSTATUS scgtDispatchCleanup( PDEVICE_OBJECT  DeviceObject, PIRP  Irp )
 * @brief Dispatch function to handle IRP_MJ_CLEANUP calls.
 * @param DeviceObject system device object
 * @param Irp system IRP
 * @todo what if any clean up needs to be done in IRP_MJ_CLEANUP
 * @return STATUS_SUCCESS or STATUS_UNSUCCESSFUL
 *****************************************************************************/
_Use_decl_annotations_
NTSTATUS scgtDispatchCleanup(PDEVICE_OBJECT  DeviceObject, PIRP  Irp)
{
    // https://docs.microsoft.com/en-us/windows-hardware/drivers/ifs/irp-mj-cleanup
    // Receipt of the IRP_MJ_CLEANUP request indicates that the handle reference 
    // count on a file object has reached zero. 
    // (In other words, all handles to the file object have been closed.) 
    // Often it is sent when a user - mode application has called the Microsoft 
    // Win32 CloseHandle function (or when a kernel - mode driver has called ZwClose)
    // on the last outstanding handle to a file object.

    // It is important to note that when all handles to a file object have been closed, 
    // this does not necessarily mean that the file object is no longer being used.
    // System components, such as the Cache Manager and the Memory Manager,
    // might hold outstanding references to the file object.
    // These components can still read to or write from a file, even after an 
    // IRP_MJ_CLEANUP request is received.

    NTSTATUS ret = STATUS_SUCCESS;
    KIRQL kIrql = 0;
    scgtDevice *dev = (scgtDevice *)DeviceObject->DeviceExtension;

    ScgtDbgPrint(SCGT_DBG_FLOW, "scgtDispatchCleanup() - {\n");

    //IN 3
    ret = scgtRequestIncrement(dev);
    if (ret != STATUS_SUCCESS)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtDispatchCleanup() - failed to acquire Remove lock.\n");
        Irp->IoStatus.Status = ret;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        ScgtDbgPrint(SCGT_DBG_FLOW, "scgtDispatchCleanup() NtStatus:%s- }\n", NtStatusName(ret));
        return ret;
    }

    IoAcquireCancelSpinLock(&kIrql);
    IoSetCancelRoutine(Irp, NULLPTR);
    IoReleaseCancelSpinLock(kIrql);

    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    //OUT 3
    scgtRequestDecrement(dev);
    ScgtDbgPrint(SCGT_DBG_FLOW, "scgtDispatchCleanup() NtStatus:%s- }\n", NtStatusName(ret));
    return ret;
} // scgtDispatchCleanup


 /**************************************************************************//**
 * @fn  NTSTATUS scgtDispatchFlushBuffers( PDEVICE_OBJECT  DeviceObject, PIRP  Irp )
 * @brief Dispatch function to handle IRP_MJ_FLUSH_BUFFERS calls.
 * @param DeviceObject system device object
 * @param Irp system IRP
 * @return STATUS_SUCCESS or STATUS_UNSUCCESSFUL
 *****************************************************************************/
_Use_decl_annotations_
NTSTATUS scgtDispatchFlushBuffers(PDEVICE_OBJECT  DeviceObject, PIRP  Irp)
{
    //required but not used
    NTSTATUS ret = STATUS_SUCCESS;
    scgtDevice *dev = (scgtDevice *)DeviceObject->DeviceExtension;
    ScgtDbgPrint(SCGT_DBG_FLOW, "scgtDispatchFlushBuffers() - {\n");

    //IN 4
    ret = scgtRequestIncrement(dev);
    if (ret != STATUS_SUCCESS)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtDispatchFlushBuffers() - failed to acquire Remove lock.\n");
        Irp->IoStatus.Status = ret;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        ScgtDbgPrint(SCGT_DBG_FLOW, "scgtDispatchFlushBuffers() NtStatus:%s- }\n", NtStatusName(ret));
        return ret;
    }

    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    //OUT 4
    scgtRequestDecrement(dev);
    ScgtDbgPrint(SCGT_DBG_FLOW, "scgtDispatchFlushBuffers() NtStatus:%s- }\n", NtStatusName(ret));
    return ret;
} // scgtDispatchFlushBuffers


/***************************************************************************//**
 * @fn  VOID scgtExitDriver(_In_ PDRIVER_OBJECT driverObject)
 * @brief  SCGT driver DRIVER_UNLOAD function.
 * The PnP manager calls a PnP driver's Unload routine if the driver has no 
 * more device objects after it handles an IRP_MN_REMOVE_DEVICE request.
 * Called at PASSIVE_LEVEL.
 * @param driverObject system device object
 * @return void
**//*****************************************************************************/
_Use_decl_annotations_
void scgtExitDriver(_In_ PDRIVER_OBJECT driverObject)
{
    // The Unload routine performs any operations that are necessary before the system unloads the driver.
    // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/nc-wdm-driver_unload

    //required but not used
    UNREFERENCED_PARAMETER(driverObject);
    ScgtDbgPrint(SCGT_DBG_FLOW, "scgtExitDriver() - {\n");

    ScgtDbgPrint(SCGT_DBG_FLOW, "scgtExitDriver() - }\n");
} //scgtExitDriver


/**************************************************************************//**
 * @fn  static void scgtInitDriverRev( void )
 *
 * @brief   Initialize driver revision string.
 * This is returned to user via  GetDeviceInfo() ioctl.
 *****************************************************************************/
inline static void scgtInitDriverRev(void)
{
    char coreFiles[60];
    char driverFiles[20];
    char sysFiles[20];

    RtlStringCbPrintfA(driverFiles, 20, "%s%s%s",
        FILE_REV_SCGT_C, FILE_REV_SCGT_H, FILE_REV_SCGTDRV_H);

    RtlStringCbPrintfA(sysFiles, 20, "%s%s%s",
        FILE_REV_KSYS_C, FILE_REV_KSYS_H, FILE_REV_SYSTYPES_H);

    RtlStringCbPrintfA(coreFiles, 60, "%s%s%s%s%s%s",
        FILE_REV_GTCORE_C, FILE_REV_GTCOREXFER_C, FILE_REV_GTCORE_H,
        FILE_REV_GTCOREIOCTL_H, FILE_REV_GTCORETYPES_H, FILE_REV_GTUCORE_H);

/** System architecture x86 or x64 */
#ifdef _WIN64
#define ARCH ":x64"
#else
#define ARCH ":x86"
#endif
    RtlStringCbPrintfA(driverRevStr, 128, "%s%s-%s:%s:%s", SCGT_DRIVER_VERSION, ARCH, driverFiles,
        sysFiles, coreFiles);
} // scgtInitDriverRev

/**************************************************************************//**
 * @fn  static ULONG scgtBusNum( _In_ PDEVICE_OBJECT physDevObj)
 *
 * @brief Returns bus number of device. 
 *
 * @param  physDevObj  The phys dev object.
 *
 * @return bus number.
 *****************************************************************************/
inline static ULONG scgtBusNum(_In_ PDEVICE_OBJECT physDevObj)
{
    ULONG busNum=0;
    ULONG reslen=0;

    IoGetDeviceProperty(physDevObj,
        DevicePropertyBusNumber,
        sizeof(ULONG),
        &busNum,
        &reslen);

    return busNum;
} // scgtBusNum

/**************************************************************************//**
 * @fn  static ULONG scgtSlotNum( _In_ PDEVICE_OBJECT pDO)
 *
 * @brief   Returns slot number of device (-1 on error)
 *
 * @param  pDO   Pointer to Device object.
 *
 * @return Slot number or 255 + unit number on error.
 * @todo scgtSlotNum routine appears to fail when called from scgtAddDevice function.
 **//**************************************************************************/
inline static ULONG scgtSlotNum(_In_ PDEVICE_OBJECT pDO)
{
    ULONG UINumber = 0;
    ULONG reslen = 0;
    ULONG slotNum = 0;
    NTSTATUS ret = STATUS_SUCCESS;

    ret = IoGetDeviceProperty(pDO,
        DevicePropertyUINumber,
        sizeof(ULONG),
        &UINumber,
        &reslen);

    slotNum = UINumber;
    if (slotNum == (ULONG)-1)
    {
        slotNum = 255 + numDevices;
    }
    return slotNum;
} // scgtSlotNum


/**************************************************************************//**
 * @fn  NTSTATUS scgtAddDevice(_In_ PDRIVER_OBJECT dvrObj,
 *                             _In_ PDEVICE_OBJECT physDevObj)
 *
 * @brief  Add device entry point.  Called for every GT card found.
 *
 * @param  dvrObj      Caller-supplied pointer to a DRIVER_OBJECT structure.
 *                     This is the driver's driver object.
 * @param  physDevObj  Caller-supplied pointer to a DEVICE_OBJECT structure
 *                     representing a physical device object (PDO) created by
 *                     a lower-level driver.
 * @return NTSTATUS value. STATUS_SUCCESS, STATUS_UNSUCCESSFUL,
    STATUS_INSUFFICIENT_RESOURCES, STATUS_OBJECT_NAME_COLLISION or
    STATUS_INVALID_DEVICE_REQUEST
 **//*************************************************************************/
_Use_decl_annotations_
NTSTATUS scgtAddDevice(_In_ PDRIVER_OBJECT dvrObj, _In_ PDEVICE_OBJECT physDevObj)
{
    NTSTATUS ret = STATUS_SUCCESS;
    PDEVICE_OBJECT functionalDeviceObject = NULLPTR;
    scgtDevice *dev = NULLPTR;
    UNICODE_STRING devName = { 0,0,NULLPTR };
    UNICODE_STRING usableSymLinkName = { 0,0,NULLPTR };
    UNICODE_STRING SymbolicLinkName = { 0,0,NULLPTR };
    usableSymLinkName.Length = 0;
    devName.Length = 0;

    ScgtDbgPrint(SCGT_DBG_FLOW, "scgtAddDevice() - PDO %p \n", physDevObj);

    // Initialize the UNICODE device name.  This will be the "native NT" name for our device.
    if (scgtBuildDevNameString(SCGT_DEVICE_BASE_NAME, numDevices, &devName) != 0)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtAddDevice()  scgtBuildDevNameString call failed.\n");
        return STATUS_UNSUCCESSFUL;
    }

    // Create device object 
    // Ask the I / O Manager to create the device object and
    // device extension.  In PnP terms, this is the FUNCTIONAL
    // Device Object (FDO) for the device.
    ret = IoCreateDevice(dvrObj,
        sizeof(scgtDevice),
        &devName,
        FILE_DEVICE_UNKNOWN,
        0,
        FALSE,
        &functionalDeviceObject);

    if (!NT_SUCCESS(ret))
    {
        // free Unicode memory allocated by RtlAnsiStringToUnicodeString 
        // called inside scgtBuildDevNameString function
        RtlFreeUnicodeString(&devName);

        ScgtDbgPrint(SCGT_DBG_ERROR, "Error IoCreateDevice call failed. Status=%s\n",
            NtStatusName(ret));
        return STATUS_UNSUCCESSFUL;
    }

    // Get a pointer to our device extension
    dev = (scgtDevice *)functionalDeviceObject->DeviceExtension;

    /* initialize scgtDevice structure */
    // Zero out the Device extension.
    RtlZeroMemory(dev, sizeof(scgtDevice));

    // Save unit number 
    dev->unitNum = (uint8)numDevices;

    // Set initial state of hold new requests flag
    InterlockedExchange(&dev->holdNewRequests, 0);
    // Set initial state of value used to track number of outstanding I/O
    InterlockedExchange(&dev->requestCount, 0);
    // Set initial state of value used to track number of open and closed calls.
    InterlockedExchange(&dev->numOpenCalls, 0);
#ifdef USE_REMOVE_LOCK
    // Initialize remove lock
    IoInitializeRemoveLock(&dev->RemoveLock, 'TGCS', 6, 80);
#endif

    // Set Internal device state flags, used for managing PnP state of device
    scgtStallQueues(dev);

    // Set Initial Driver State
    dev->SystemState = STATE_NEVER_STARTED;

    // Initialize the count of in-process I/O request to zero. 
    // We use this to keep track of when we can more the device.
    InterlockedExchange(&dev->OutstandingIO, 0);

    // push onto device stack. Attach our FDO to the underlying PDO
    dev->lowerDeviceInStack = IoAttachDeviceToDeviceStack(functionalDeviceObject, physDevObj);
    if (dev->lowerDeviceInStack == NULLPTR)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "IoAttachDeviceStack returned NULL\n");
        ret = STATUS_UNSUCCESSFUL;
        RtlFreeUnicodeString(&devName);
        IoDeleteDevice(functionalDeviceObject);
        ScgtDbgPrint(SCGT_DBG_ERROR, "Error IoAttachDeviceToDeviceStack call failed. Status=0x%x\n", ret);
        return ret;
    }
    dev->physDeviceObject = physDevObj;
    dev->FunctionalDeviceObject = functionalDeviceObject;

    /* register our new device */
    ret = IoRegisterDeviceInterface(physDevObj, &scgtGUID,
        NULLPTR, &SymbolicLinkName);
    if (!NT_SUCCESS(ret))
    {
        //DLC added calls to free Unicode strings on error
        RtlFreeUnicodeString(&SymbolicLinkName);
        RtlFreeUnicodeString(&devName);
        IoDeleteDevice(functionalDeviceObject);
        ScgtDbgPrint(SCGT_DBG_ERROR, "Error IoRegisterDeviceInterface call failed. Status=0x%x\n", ret);
        return ret;
    }

    // copy symbolic name used to register the device interface to Unicode string in the device extension.
    RtlInitUnicodeString(&dev->unicodeSymLinkName, SymbolicLinkName.Buffer);

    // IF you want to create a "familiar name" for your device, to allow non-kernel mode programs 
    // (like Win32 applications) to open your device without having to go through SetupDiXxxx, you can create
    // a name that can be directly accessed using CreateFile.  This is done by creating a symbolic link in the 
    // DosDevices directory, with the name for your device.

    // create a usable symbolic name for our device 
    ret = scgtBuildDevNameString(SCGT_SYMLINK_BASE_NAME, numDevices++, &usableSymLinkName);
    if (!NT_SUCCESS(ret))
    {
        //DLC added call to free Unicode string on error 
        RtlFreeUnicodeString(&SymbolicLinkName);
        RtlFreeUnicodeString(&devName);
        IoDeleteDevice(functionalDeviceObject);
        ScgtDbgPrint(SCGT_DBG_ERROR, "Error scgtBuildDevNameString call failed. Status=0x%x\n", ret);
        return ret;
    }
    ScgtDbgPrint(SCGT_DBG_INFO, "call IoCreateSymbolicLink with Link name: %S\n", usableSymLinkName.Buffer);
    ret = IoCreateSymbolicLink(&usableSymLinkName, &devName);
    //DLC added call to RtlFreeUnicodeString to free memory allocated by 
    // RtlAnsiStringToUnicodeString  call inside scgtBuildDevNameString
    RtlFreeUnicodeString(&usableSymLinkName);
    RtlFreeUnicodeString(&devName);

    if (!NT_SUCCESS(ret))
    {
        IoDeleteDevice(functionalDeviceObject);
        ScgtDbgPrint(SCGT_DBG_ERROR, "Error IoCreateSymbolicLink call failed. Status=0x%x\n", ret);
        return ret;
    }

    /* initialize DPC */
    IoInitializeDpcRequest(functionalDeviceObject, scgtDPC);

    /* initialize board location string */
    RtlStringCbPrintfA(dev->boardLocationStr, 128, "bus %u slot %u",
        scgtBusNum(physDevObj), scgtSlotNum(physDevObj));

    // Ask the I/O Manager to use describe user read/write buffers using MDLs
    functionalDeviceObject->Flags |= DO_DIRECT_IO;
    functionalDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;

    //
   // Set up the "Remove Event" and "Stop Event".
   //
   // Note that we can't use an official "Remove Lock" here, because
   // the Remove Lock related calls are not in WDM.
   //
    KeInitializeEvent(&dev->RemoveEvent, NotificationEvent, FALSE);
    KeInitializeEvent(&dev->StopEvent, NotificationEvent, TRUE);

    ScgtDbgPrint(SCGT_DBG_FLOW, "scgtAddDevice() - returning %s }\n", NtStatusName(ret));

    return ret;
} // scgtAddDevice

/**************************************************************************//**
 * @fn  static NTSTATUS scgtBuildDevNameString(_In_ const PCHAR baseStr,
 *                                             _In_ const uint32 unit,
 *                                             _Out_ PUNICODE_STRING outStr)
 *
 * @brief  Build SCGT device name string.
 *
 * @param [in]  baseStr  If non-null, the base string.
 * @param [in]  unit              The unit number.
 * @param [out] outStr   If non-null, the out string.
 * @return STATUS_SUCCESS or STATUS_UNSUCCESSFUL;
 **//*************************************************************************/
static NTSTATUS scgtBuildDevNameString( _In_ const PSTR baseStr,
    _In_ const uint32 unit,
    _Out_ PUNICODE_STRING outStr)
{
    char buff[30] = { 0 };
    ANSI_STRING ansiString = { 0,0,NULLPTR };

    RtlStringCbPrintfA(buff, 30, "%s%u", baseStr, unit);

    ansiString.Buffer = buff;
    ansiString.Length = (unsigned short)strlen(buff);
    ansiString.MaximumLength = 29;
    if (RtlAnsiStringToUnicodeString(outStr, &ansiString, TRUE) != STATUS_SUCCESS)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, ("RtlAnsiStringToUnicodeString call failed\n"));
        return STATUS_UNSUCCESSFUL;
    }
    return STATUS_SUCCESS;
} // scgtBuildDevNameString

/**************************************************************************//**
 * @fn  NTSTATUS scgtOpen(IN PDEVICE_OBJECT deviceObject, IN PIRP irp)
 *
 * @brief  Open handle to SCGT device in response to IRP_MJ_CREATE Irp
 *
 * @param  [in] deviceObject   The device object.
 * @param  [in] irp            The IRP.
 *
 * @return STATUS_SUCCESS or STATUS_UNSUCCESSFUL
**//***************************************************************************/
_Use_decl_annotations_
NTSTATUS scgtOpen(IN PDEVICE_OBJECT deviceObject, IN PIRP irp)
{
    NTSTATUS ret = STATUS_SUCCESS;
    scgtDevice *dev = (scgtDevice *)deviceObject->DeviceExtension;
    LONG count = 0;

    PIO_STACK_LOCATION irpStackLoc = NULLPTR;

    //ScgtDbgPrint(SCGT_DBG_FLOW, "scgtOpen() - {\n");
    ScgtDbgPrint(SCGT_DBG_MAP, "scgtOpen() - {\n");

    //IN 5
    ret = scgtRequestIncrement(dev);
    if (ret != STATUS_SUCCESS)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtOpen() - failed to acquire Remove lock.\n");
        irp->IoStatus.Status = ret;
        IoCompleteRequest(irp, IO_NO_INCREMENT);
        ScgtDbgPrint(SCGT_DBG_FLOW, "scgtOpen() NtStatus:%s- }\n", NtStatusName(ret));
        return ret;
    }

    // track number of open and close calls.
    count = InterlockedIncrement(&dev->numOpenCalls);     
    ScgtDbgPrint(SCGT_DBG_MAP, "scgtOpen() Number of Open calls %d. Current Process:0x%x \n", count, PsGetCurrentProcess());       

    // set return values to something known
    irp->IoStatus.Status = STATUS_SUCCESS;
    irp->IoStatus.Information = 0;

    irpStackLoc = IoGetCurrentIrpStackLocation(irp);

    if (irpStackLoc->MajorFunction != IRP_MJ_CREATE)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtOpen() called with MAJORFUNCTION != IRP_MJ_CREATE\n");
        irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
    }

    if ((dev->SystemState < STATE_ALL_BELOW_FAIL) ||
        (dev->SystemState == STATE_REMOVE_PENDING))
    {
        irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
    }

    IoCompleteRequest(irp, IO_NO_INCREMENT);

    //OUT 5
    scgtRequestDecrement(dev);
    //ScgtDbgPrint(SCGT_DBG_FLOW, "scgtOpen() NtStatus:%s- }\n", NtStatusName(ret));
    ScgtDbgPrint(SCGT_DBG_MAP, "scgtOpen() NtStatus:%s- }\n", NtStatusName(ret));
    return ret;
} // scgtOpen

/**************************************************************************//**
 * @fn  NTSTATUS scgtClose(IN PDEVICE_OBJECT deviceObject, IN PIRP irp)
 *
 * @brief  Close handle to scgt device in response to IRP_MJ_CLOSE.
 *
 * @param  deviceObject   The device object.
 * @param  irp            The irp.
 * @return STATUS_SUCCESS
 **//*************************************************************************/
_Use_decl_annotations_
NTSTATUS scgtClose(IN PDEVICE_OBJECT deviceObject, IN PIRP irp)
{
    NTSTATUS ret = STATUS_SUCCESS;
    long count = 0;
    scgtDevice* dev = (scgtDevice*)deviceObject->DeviceExtension;
#ifndef ORIG_MODE
    PVOID pvu = NULLPTR;
    //SIZE_T length = 0;
    PMDL pMdl = NULLPTR;
    LONG index = 0;
#endif
    PEPROCESS ProcessPtr = NULLPTR;
    UNREFERENCED_PARAMETER(irp);

    ScgtDbgPrint(SCGT_DBG_MAP, "scgtClose() - {\n");
    //IN 6
    ret = scgtRequestIncrement(dev);
    if (ret != STATUS_SUCCESS)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "Error: scgtClose() - failed to acquire Remove lock.\n");
        irp->IoStatus.Status = ret;
        IoCompleteRequest(irp, IO_NO_INCREMENT);
        ScgtDbgPrint(SCGT_DBG_MAP, "scgtClose() NtStatus:%s- }\n", NtStatusName(ret));
        return ret;
    }
    ProcessPtr = PsGetCurrentProcess();
    // track number of open and close calls.
    count = InterlockedDecrement(&dev->numOpenCalls);
    ScgtDbgPrint(SCGT_DBG_MAP, "  Number of Open calls %d Current Process:0x%x\n", count, PsGetCurrentProcess());    
    if (count == 0)
    {
        //TODO Perform garbage cleanup if needed.
        // 
        // unmap all virutal memory pointer if number or open file handles 
        // dev->numOpenCalls is zero and number of memory mapped pointer 
        // dev->MappedMemory is not zero
        const long value = InterlockedCompareExchange(&dev->MappedMemory, 0, 0);
        if (value > 0)
        {
#ifndef ORIG_MODE
            ScgtDbgPrint(SCGT_DBG_MAP, "  User closed all file handles without umapping all user virtual memory pointers to GT memory\n");            
            while ( (index < value) &&
                    (index < MAX_USER_PTRS) )
            {                
                pvu = dev->userModePtrMem[index];
                pMdl = dev->userModePtrMdl[index];
                
                if ( (pvu == NULLPTR) || (pMdl == NULLPTR) )
                {
                    ScgtDbgPrint(SCGT_DBG_ERROR, "  Error: Memory pointer at array index:%u is NULL\n", index);
                    index++;
                    continue;
                }
                ScgtDbgPrint(SCGT_DBG_MAP, "  Umapping virtual memory pointer to GT memory at 0x%x. Index:%d\n", pvu, index);
                MmUnmapLockedPages(pvu, pMdl);
                IoFreeMdl(pMdl);
                InterlockedDecrement(&dev->MappedMemory);
                // Set current array entry to NULL after unmap.
                dev->userModePtrMem[index] = NULLPTR;
                dev->userModePtrMdl[index] = NULLPTR;                
                dev->userModePtrPrc[index] = NULLPTR;                
                index++;                
            }
#else                   
            if (dev->virtualAddress != 0)
            {
                ScgtDbgPrint(SCGT_DBG_ERROR, "umapping virtual pointer to GT memory at 0x%llx\n",
                    dev->virtualAddress);

                WARNING_SUPPRESS( 26477)
                _IRQL_limited_to_(PASSIVE_LEVEL);
                ret = ZwUnmapViewOfSection(NtCurrentProcess(),
                    UINT64_TO_PTR(PVOID, dev->virtualAddress));
                dev->virtualAddress = 0;
            }
#endif
        }
    }
    else
    {
        const long value = InterlockedCompareExchange(&dev->MappedMemory, 0, 0);
        if (value > 0)
        {                        
            index = 0;
            pvu = NULLPTR;
            pMdl = NULLPTR;

            /* Look in user pointer array for process value equal to current process value*/
            ScgtDbgPrint(SCGT_DBG_MAP, "  Looking to see if current process:0x%x has an active virtual memory pointer\n", ProcessPtr);
            while ( (index < value) && 
                    (index < MAX_USER_PTRS) )
            {                            
                if (dev->userModePtrPrc[index] == ProcessPtr)
                {                    
                    pvu = dev->userModePtrMem[index];
                    pMdl = dev->userModePtrMdl[index];
                    break;
                }   
                index++;
            }

            if ((index >= value) || (index >= MAX_USER_PTRS ) )
            {
                ScgtDbgPrint(SCGT_DBG_MAP, "  Unable to find an active virtual memory pointer for Current Process 0x%x in user pointer array\n", ProcessPtr);
            }
            else if ((pvu == NULLPTR) || (pMdl == NULLPTR))
            {
                ScgtDbgPrint(SCGT_DBG_ERROR, "  Error: Memory pointer for current process:0x%x at index:%u is NULL\n", ProcessPtr, index);
            }
            else
            {
                ScgtDbgPrint(SCGT_DBG_MAP, "  Unmap virtual memory pointer at 0x%x for current process:0x%x at array index:%d\n", pvu,ProcessPtr,index);
                MmUnmapLockedPages(pvu, pMdl);
                IoFreeMdl(pMdl);
                InterlockedDecrement(&dev->MappedMemory);
                /* clear out current entry and move upper entries down one in array to fill its place*/
                dev->userModePtrMem[index] = NULLPTR;
                dev->userModePtrMdl[index] = NULLPTR;
                dev->userModePtrPrc[index] = NULLPTR;

                while ( (index +1 < value) &&
                        (index +1 < MAX_USER_PTRS))                     
                {
                    dev->userModePtrMem[index] = dev->userModePtrMem[index + 1];
                    dev->userModePtrMdl[index] = dev->userModePtrMdl[index + 1];
                    dev->userModePtrPrc[index] = dev->userModePtrPrc[index + 1];
                    index++;
                }
                /* Set last unused entry to empty.*/
                if ( index + 1 < MAX_USER_PTRS)
                {
                    dev->userModePtrMem[index + 1] = NULLPTR;
                    dev->userModePtrMdl[index + 1] = NULLPTR;
                    dev->userModePtrPrc[index + 1] = NULLPTR;
                }
            }            
        }  
    }

    IoCompleteRequest(irp, IO_NO_INCREMENT);

    // OUT 6
    scgtRequestDecrement(dev);
    ScgtDbgPrint(SCGT_DBG_MAP, "scgtClose() NtStatus:%s- }\n", NtStatusName(ret));
    return ret;
} // scgtClose

#ifdef NOT_USE
/**************************************************************************//*
 * @fn  static int scgtGiveIntrSem(scgtDevice *dev)
 *
 * @brief   Release getIntrSem semaphore for threads waiting.
 *
 * @param [in,out]  dev   If non-null, the dev.
 *
 * @return .
 *//*****************************************************************************/
static int scgtGiveIntrSem(scgtDevice *dev)
{
    int count;
    ksysSpinLockFlags spinLockFlags;

    ksysSpinLockLock(&dev->getIntrWaitCountSpinLock, &spinLockFlags);
    count = dev->getIntrWaitCount;
    dev->getIntrWaitCount = 0;
    ksysSpinLockUnlock(&dev->getIntrWaitCountSpinLock, &spinLockFlags);

    if (count > 0) /* somebody waiting */
    {
        for (; count > 0; count--)
            ksysSemSGive(&dev->getIntrSem);
    }

    return count;
}
#endif

/**************************************************************************//**
 * @fn  static int scgtGiveIntrSemDPC( _In_ scgtDevice *dev)
 *
 * @brief  Release getIntrSem semaphore for threads waiting.
 *         Same as scgtGiveIntrSem() but safe to call only
 *         from DPC.  The spin lock routines used are faster
 *         when already at DPC level.
 *
 * @param [in]  dev   Pointer to SCGT device object
 *
 * @return 0
 *****************************************************************************/
static uint32 scgtGiveIntrSemDPC(_In_ scgtDevice *dev)
{
    //int count;
    uint32 count;

    KeAcquireSpinLockAtDpcLevel(&dev->getIntrWaitCountSpinLock);
    count = dev->getIntrWaitCount;
    dev->getIntrWaitCount = 0;
    KeReleaseSpinLockFromDpcLevel(&dev->getIntrWaitCountSpinLock);

    if (count > 0) /* somebody waiting */
    {
        for (; count > 0; count--)
            ksysSemSGive(&dev->getIntrSem);
    }

    return count;
} // scgtGiveIntrSemDPC

/** @} */


/** @defgroup IOCTLCode SCRAMNet GT IOCT code
SCRAMNEt GT IOCTL functions
@{
**/

/**************************************************************************//**
* @fn char* sctgIoctlCodeToStr( _In_ const int ioCode )
* @brief Convert SCGT Ioctl control code number to string form
* @param ioCode SCGT Ioctl control code number
* @return char string name of Ioctl control code.
**//**************************************************************************/
char* sctgIoctlCodeToStr(_In_ const int ioCode)
{
    /** maximum IOCTL code String length */
    #define IoctlStrLen 32
    static char str[IoctlStrLen];

    switch (ioCode)
    {
    case SCGT_IOCTL_MAP_MEM:
        sprintf_s(str, IoctlStrLen, "SCGT_IOCTL_MAP_MEM ");
        break;
    case SCGT_IOCTL_UNMAP_MEM:
        sprintf_s(str, IoctlStrLen, "SCGT_IOCTL_UNMAP_MEM ");
        break;
    case SCGT_IOCTL_WRITE:
        sprintf_s(str, IoctlStrLen, "SCGT_IOCTL_WRITE ");
        break;
    case SCGT_IOCTL_READ:
        sprintf_s(str, IoctlStrLen, "SCGT_IOCTL_READ ");
        break;
    case SCGT_IOCTL_READ_CR:
        sprintf_s(str, IoctlStrLen, "SCGT_IOCTL_READ_CR ");
        break;
    case SCGT_IOCTL_WRITE_CR:
        sprintf_s(str, IoctlStrLen, "SCGT_IOCTL_WRITE_CR ");
        break;
    case SCGT_IOCTL_MEM_MAP_INFO:
        sprintf_s(str, IoctlStrLen, "SCGT_IOCTL_MEM_MAP_INFO ");
        break;
    case SCGT_IOCTL_GET_DEVICE_INFO:
        sprintf_s(str, IoctlStrLen, "SCGT_IOCTL_GET_DEVICE_INFO ");
        break;
    case SCGT_IOCTL_GET_STATE:
        sprintf_s(str, IoctlStrLen, "SCGT_IOCTL_GET_STATE ");
        break;
    case SCGT_IOCTL_SET_STATE:
        sprintf_s(str, IoctlStrLen, "SCGT_IOCTL_SET_STATE ");
        break;
    case SCGT_IOCTL_READ_NMR:
        sprintf_s(str, IoctlStrLen, "SCGT_IOCTL_READ_NMR ");
        break;
    case SCGT_IOCTL_WRITE_NMR:
        sprintf_s(str, IoctlStrLen, "SCGT_IOCTL_WRITE_NMR ");
        break;
    case SCGT_IOCTL_GET_STATS:
        sprintf_s(str, IoctlStrLen, "SCGT_IOCTL_GET_STATS ");
        break;
    case SCGT_IOCTL_GET_INTR:
        sprintf_s(str, IoctlStrLen, "SCGT_IOCTL_GET_INTR ");
        break;
    case SCGT_IOCTL_PUT_INTR:
        sprintf_s(str, IoctlStrLen, "SCGT_IOCTL_PUT_INTR ");
        break;
    case SCGT_IOCTL_DBG_FLAG:
        sprintf_s(str, IoctlStrLen, "SCGT_IOCTL_DBG_FLAG ");
        break;
    default:
        sprintf_s(str, IoctlStrLen, "Unknown:%d ", ioCode);
        break;
    }
    return str;
} // sctgIoctlCodeToStr

/**
@fn NTSTATUS scgtVerifyUserBuffer(_In_ PIRP irp,_In_ const SIZE_T bufSize)
@brief Verify Irp input and output buffer length greater than or equal to buffer size
@param irp Pointer to IRP
@param bufSize Size of buffer for input/output.
@return NTSTATUS value STATUS_SUCCESS or STATUS_INVALID_BUFFER_SIZE.
*/
NTSTATUS scgtVerifyUserBuffer( _In_ PIRP irp, _In_ const SIZE_T bufSize)
{
    PIO_STACK_LOCATION irpStackLoc = NULLPTR;
    irpStackLoc = IoGetCurrentIrpStackLocation(irp);
    if (irpStackLoc->Parameters.DeviceIoControl.InputBufferLength < bufSize)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "Error user input buffer to Small. Buffer size:%ld < required size:%ld\n",
            irpStackLoc->Parameters.DeviceIoControl.InputBufferLength, bufSize);
        return STATUS_INVALID_BUFFER_SIZE;
    }

    if (irpStackLoc->Parameters.DeviceIoControl.OutputBufferLength < bufSize)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "Error user input buffer to Small. Buffer size:%ld < required size:%ld\n",
            irpStackLoc->Parameters.DeviceIoControl.InputBufferLength, bufSize);
        return STATUS_INVALID_BUFFER_SIZE;
    }
    return STATUS_SUCCESS;
}

/**************************************************************************//**
 * @fn  NTSTATUS scgtIoctl( IN PDEVICE_OBJECT deviceObject, IN PIRP irp)
 *
 * @brief  Handles IRP_MJ_DEVICE_CONTROL calls.
 *         Handles IOTCL write, read, and getIntr requests. Passes other
 *        IOCTL calls to scgtIoctl2().
 *
 * @param  [in] deviceObject   The device object.
 * @param  [in] irp            The irp.
 *
 * @return NTSTATUS value. STATUS_SUCCESS, STATUS_UNSUCCESSFUL,
 *         STATUS_NOT_IMPLEMENTED, STATUS_INVALID_PARAMETER,
 *         STATUS_OBJECT_TYPE_MISMATCH, STATUS_ACCESS_DENIED,
 *         STATUS_INVALID_HANDLE, STATUS_CONFLICTING_ADDRESSES,
 *         STATUS_INVALID_PAGE_PROTECTION, or STATUS_SECTION_PROTECTION
 *****************************************************************************/
_Use_decl_annotations_
NTSTATUS scgtIoctl(IN PDEVICE_OBJECT deviceObject, IN PIRP irp)
{
    NTSTATUS ret = STATUS_SUCCESS;
    PIO_STACK_LOCATION irpStackLoc = NULLPTR;
    scgtDevice *dev = NULLPTR;
    scgtXfer *xfer = NULLPTR;
    scgtInterrupt intr = { 0,0,0,0,0 };
    scgtGetIntrBuf *getIntrBuf = NULLPTR;
    LONG count = 0;

    ScgtDbgPrint(SCGT_DBG_FLOW, "scgtIoctl() - {\n");

    if (deviceObject == NULLPTR)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "Error dcfiDispatchRead()  DeviceObject is NULL pointer\n");
        return STATUS_INVALID_PARAMETER;
    }
    if (irp == NULLPTR)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "--> ERROR dcfiIoctl() irp is null\n");
        return STATUS_INVALID_PARAMETER;
    }

    dev = (scgtDevice*)deviceObject->DeviceExtension;
    irpStackLoc = IoGetCurrentIrpStackLocation(irp);

    //
    // Since IOCTLs to this device are synchronously processed,
    // we will only allow IOCTLs when we're in STARTED state.
    //
    if (dev->SystemState != STATE_STARTED)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtIoctl called while device not started.\n");
        ScgtDbgPrint(SCGT_DBG_ERROR, "failed %s ioctl call\n", sctgIoctlCodeToStr(irpStackLoc->MinorFunction));
        ret = STATUS_UNSUCCESSFUL;
        //ret = STATUS_INVALID_DEVICE_STATE;
        irp->IoStatus.Status = ret;
        IoCompleteRequest(irp, IO_NO_INCREMENT);
        ScgtDbgPrint(SCGT_DBG_FLOW, "scgtIoctl() ntStatus:%s- }\n", NtStatusName(ret));
        return(ret);
    }

    //IN 7
    ret = scgtRequestIncrement(dev);
    if (ret != STATUS_SUCCESS)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtIoctl failed to acquire remove lock\n");
        ScgtDbgPrint(SCGT_DBG_ERROR, "failed %s ioctl call\n", sctgIoctlCodeToStr(irpStackLoc->MinorFunction));
        irp->IoStatus.Status = ret;
        IoCompleteRequest(irp, IO_NO_INCREMENT);
        ScgtDbgPrint(SCGT_DBG_FLOW, "scgtIoctl() - }\n");
        return ret;
    }

    //DLC fail request if holdNewRequests flag set.
    if (isQueueStalled(dev))
    {
        ScgtDbgPrint(SCGT_DBG_IOCTL, "scgtIoctl(). Requests on hold for stop or cancel stop event.\n");
        ScgtDbgPrint(SCGT_DBG_IOCTL, "failed %s ioctl call\n", sctgIoctlCodeToStr(irpStackLoc->MinorFunction));
        ret = STATUS_UNSUCCESSFUL;
        irp->IoStatus.Status = ret;
        IoCompleteRequest(irp, IO_NO_INCREMENT);
        ScgtDbgPrint(SCGT_DBG_FLOW, "scgtIoctl() ntStatus:%s- }\n", NtStatusName(ret));
        
        // OUT 7A
        scgtRequestDecrement(dev);
        return ret;
    }
    
    count = InterlockedIncrement(&dev->requestCount);
    if (count == 1)
    {
        // clear the 
        KeClearEvent(&dev->StopEvent);
    }

    // Set some appropriate defaults
    ret = STATUS_SUCCESS;
    irp->IoStatus.Information = 0;

    irpStackLoc = IoGetCurrentIrpStackLocation(irp);

    if (irpStackLoc->MajorFunction == IRP_MJ_DEVICE_CONTROL)
    {
        switch (irpStackLoc->Parameters.DeviceIoControl.IoControlCode)
        {
        case SCGT_IOCTL_WRITE:
            ScgtDbgPrint(SCGT_DBG_WRITE, "SCGT_IOCTL_WRITE\n");
            xfer = (scgtXfer*)irp->AssociatedIrp.SystemBuffer;
            ret = scgtVerifyUserBuffer(irp, sizeof(scgtXfer) );
            if (ret != STATUS_SUCCESS)
            {
                ScgtDbgPrint(SCGT_DBG_ERROR, "SCGT_IOCTL_WRITE buffer error\n");
                break;
            }
            if (xfer->pInterrupt)
            {
                scgtCopyFromUser(&intr, UINT64_TO_PTR(void, xfer->pInterrupt), sizeof(scgtInterrupt));
                xfer->reserved = scgtIoctlXfer(dev, irp, xfer, &intr, GTCORE_WRITE);
            }
            else
            {
                xfer->reserved = scgtIoctlXfer(dev, irp, xfer, NULLPTR, GTCORE_WRITE);
            }
            irp->IoStatus.Information = sizeof(scgtXfer);
            break;

        case SCGT_IOCTL_READ:
            ScgtDbgPrint(SCGT_DBG_READ, "SCGT_IOCTL_READ\n");
            xfer = (scgtXfer*)irp->AssociatedIrp.SystemBuffer;
            ret = scgtVerifyUserBuffer(irp, sizeof(scgtXfer) );
            if (ret != STATUS_SUCCESS)
            {
                ScgtDbgPrint(SCGT_DBG_ERROR, "SCGT_IOCTL_WRITE buffer error\n");
                break;
            }
            xfer->reserved = scgtIoctlXfer(dev, irp, xfer, NULLPTR, GTCORE_READ);
            irp->IoStatus.Information = sizeof(scgtXfer);
            break;

        case SCGT_IOCTL_GET_INTR:
            ScgtDbgPrint(SCGT_DBG_INTR, "SCGT_IOCTL_GET_INTR\n");            
            /* Expression/symbol 'irp->AssociatedIrp' uses a naked union 
            'union _IRP::<unnamed-type-AssociatedIrp>' with multiple type pointers: Use variant instead (type.7). */
            WARNING_SUPPRESS(26476)
            getIntrBuf = (scgtGetIntrBuf *)irp->AssociatedIrp.SystemBuffer;
            ret = scgtVerifyUserBuffer(irp, sizeof(scgtGetIntrBuf) );
            if (ret != STATUS_SUCCESS)
            {
                ScgtDbgPrint(SCGT_DBG_ERROR, "SCGT_IOCTL_WRITE buffer error\n");
                break;
            }
            getIntrBuf->reserved = scgtIoctlGetIntr(dev, getIntrBuf);
            irp->IoStatus.Information = sizeof(scgtGetIntrBuf);
            break;

        default:
            ret = scgtIoctl2(deviceObject, irp, irpStackLoc);
            break;
        }
    }
    else
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtIoctl called with IRP major fuction %s expected IRP_MJ_DEVICE_CONTROL\n", 
            IrpMajorNameStr(irpStackLoc->MajorFunction)); 
        ret = STATUS_INVALID_PARAMETER;
    }

    irp->IoStatus.Status = ret;
    IoCompleteRequest(irp, IO_NO_INCREMENT);

    //ScgtDbgPrint(SCGT_DBG_IOCTL,"irp: status 0x%x information 0x%I64x\n", 
    //                              irp->IoStatus.Status, (long long) irp->IoStatus.Information);

    count = InterlockedDecrement(&dev->requestCount);
    if (count == 0)
    {
        KeSetEvent(&dev->StopEvent, IO_NO_INCREMENT, FALSE);
    }
    else if (count < 0)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtIoctl() Request count:%d < 0. Resetting value to zero.\n", count);
        InterlockedExchange(&dev->requestCount, 0);
        KeSetEvent(&dev->StopEvent, IO_NO_INCREMENT, FALSE);
    }
    // OUT 7b
    scgtRequestDecrement(dev);
    ScgtDbgPrint(SCGT_DBG_FLOW, "scgtIoctl() NtStatus:%s - }\n", NtStatusName(ret));

    return ret;
} // scgtIoctl

/***************************************************************************//**
* @fn void scgtPrintStateName(_In_ const ULONG stateID)
* @brief Display SCGT state id string corresponding to input stateID.
* @param stateID  Numerical state id value to convert to sting form.
**//***************************************************************************/
void scgtPrintStateName(_In_ const ULONG stateID)
{
    switch (stateID)
    {
        case SCGT_NODE_ID:
            ScgtDbgPrint(SCGT_DBG_IOCTL2, "StateId: SCGT_NODE_ID\n");
            break;
        case SCGT_ACTIVE_LINK:
            ScgtDbgPrint(SCGT_DBG_IOCTL2, "StateId: SCGT_ACTIVE_LINK\n");
            break;
        case SCGT_WRITE_ME_LAST:
            ScgtDbgPrint(SCGT_DBG_IOCTL2, "StateId: SCGT_WRITE_ME_LAST\n");
            break;
        case SCGT_NET_TIMER_VAL:
            ScgtDbgPrint(SCGT_DBG_IOCTL2, "StateId: SCGT_NET_TIMER_VAL\n");
            break;
        case SCGT_LATENCY_TIMER_VAL:
            ScgtDbgPrint(SCGT_DBG_IOCTL2, "StateId: SCGT_LATENCY_TIMER_VAL \n");
            break;
        case SCGT_SM_TRAFFIC_CNT:
            ScgtDbgPrint(SCGT_DBG_IOCTL2, "StateId: SCGT_SM_TRAFFIC_CNT\n");
            break;
        case SCGT_SPY_SM_TRAFFIC_CNT:
            ScgtDbgPrint(SCGT_DBG_IOCTL2, "StateId: SCGT_SPY_SM_TRAFFIC_CNT\n");
            break;
        case SCGT_SPY_NODE_ID:
            ScgtDbgPrint(SCGT_DBG_IOCTL2, "StateId: SCGT_SPY_NODE_ID\n");
            break;
        case SCGT_UNICAST_INT_MASK:
            ScgtDbgPrint(SCGT_DBG_IOCTL2, "StateId: SCGT_UNICAST_INT_MASK \n");
            break;
        case SCGT_BROADCAST_INT_MASK:
            ScgtDbgPrint(SCGT_DBG_IOCTL2, "StateId: SCGT_BROADCAST_INT_MASK\n");
            break;
        case SCGT_INT_SELF_ENABLE:
            ScgtDbgPrint(SCGT_DBG_IOCTL2, "StateId: SCGT_INT_SELF_ENABLE\n");
            break;
        case SCGT_EWRAP:
            ScgtDbgPrint(SCGT_DBG_IOCTL2, "StateId: SCGT_EWRAP\n");
            break;
        case SCGT_NUM_LINK_ERRS:
            ScgtDbgPrint(SCGT_DBG_IOCTL2, "StateId: SCGT_NUM_LINK_ERRS\n");
            break;
        case SCGT_TRANSMIT_ENABLE:
            ScgtDbgPrint(SCGT_DBG_IOCTL2, "StateId: SCGT_TRANSMIT_ENABLE\n");
            break;
        case SCGT_RECEIVE_ENABLE:
            ScgtDbgPrint(SCGT_DBG_IOCTL2, "StateId: SCGT_RECEIVE_ENABLE\n");
            break;
        case SCGT_RETRANSMIT_ENABLE:
            ScgtDbgPrint(SCGT_DBG_IOCTL2, "StateId: SCGT_RETRANSMIT_ENABLE\n");
            break;
        case SCGT_LASER_0_ENABLE:
            ScgtDbgPrint(SCGT_DBG_IOCTL2, "StateId: SCGT_LASER_0_ENABLE\n");
            break;
        case SCGT_LASER_1_ENABLE:
            ScgtDbgPrint(SCGT_DBG_IOCTL2, "StateId: SCGT_LASER_1_ENABLE\n");
            break;
        case SCGT_D64_ENABLE:
            ScgtDbgPrint(SCGT_DBG_IOCTL2, "StateId: SCGT_D64_ENABLE\n");
            break;
        case SCGT_BYTE_SWAP_ENABLE:
            ScgtDbgPrint(SCGT_DBG_IOCTL2, "StateId: SCGT_BYTE_SWAP_ENABLE\n");
            break;
        case SCGT_WORD_SWAP_ENABLE:
            ScgtDbgPrint(SCGT_DBG_IOCTL2, "StateId: SCGT_WORD_SWAP_ENABLE\n");
            break;
        case SCGT_LINK_ERR_INT_ENABLE:
            ScgtDbgPrint(SCGT_DBG_IOCTL2, "StateId: SCGT_LINK_ERR_INT_ENABLE\n");
            break;
        case SCGT_READ_BYPASS_ENABLE:
            ScgtDbgPrint(SCGT_DBG_IOCTL2, "StateId: SCGT_READ_BYPASS_ENABLE\n");
            break;
        default:
            ScgtDbgPrint(SCGT_DBG_IOCTL2, "StateId: Unknown: 0x%x\n", stateID);
            break;
    }
} // scgtPrintStateName

/**************************************************************************//**
 * @fn  NTSTATUS scgtIoctl2(_In_ DEVICE_OBJECT *deviceObject, _In_ IRP *irp,
 * _In_ IO_STACK_LOCATION *irpStackLoc)
 *
 * @brief  Secondary Ioctl processing function. Handle set/get state calls,
 *         Reads or writes to control and network management registers,
 *         map and umap memory, get stats and device info,
 *         and set debugging mask.
 *
 * @param [in,out]  deviceObject   If non-null, the device object.
 * @param [in,out]  irp            If non-null, the irp.
 * @param [in,out]  irpStackLoc    If non-null, the irp stack location.
 *
 * @return STATUS_SUCCESS, STATUS_INVALID_PARAMETER, STATUS_OBJECT_TYPE_MISMATCH
 *         STATUS_ACCESS_DENIED,STATUS_INVALID_HANDLE,
 *         STATUS_CONFLICTING_ADDRESSES, STATUS_INVALID_PAGE_PROTECTION,
 *         and STATUS_SECTION_PROTECTION
 *****************************************************************************/
static NTSTATUS scgtIoctl2(_In_ DEVICE_OBJECT *deviceObject, _In_ IRP *irp, _In_ const IO_STACK_LOCATION *irpStackLoc)
{
    NTSTATUS ret = STATUS_SUCCESS;
    scgtDevice *dev = NULLPTR;

    WARNING_PUSH
    /* Expression/symbol 'u' uses a naked union 'union scgtIoctl2::<unnamed-type-u>'
       with multiple type pointers: Use variant instead (type.7). */
    WARNING_DISABLE(26476) 
    union
    {
        scgtRegister *reg;
        scgtDeviceInfo *deviceInfo;
        scgtState *state;
        scgtStats *stats;
        scgtMemMapInfo *memMapInfo;
        scgtInterrupt *intr;
    } u;
    u.reg = NULLPTR;


    ScgtDbgPrint(SCGT_DBG_IOCTL2, ("scgtIoctl2() - {\n"));

    dev = (scgtDevice*)deviceObject->DeviceExtension;

    u.reg = (scgtRegister*)irp->AssociatedIrp.SystemBuffer;  /* this sets all of them up */
    if (u.reg == NULLPTR)
    {
        return STATUS_INVALID_PARAMETER;
    }
    WARNING_POP

    // C26489: Don't dereference a pointer that may be invalid: 
    // 'u.state->val'. 'u.scgtIoctl2::<unnamed - type - u>::state' may have been invalidated at line 1986
    // 'u.state->stateID'. 'u.scgtIoctl2::<unnamed - type - u>::state' may have been invalidated at line 1986
    // 'u.state->reserved'. 'u.scgtIoctl2::<unnamed - type - u>::state' may have been invalidated at line 1986
    // ' u.state->reserved'. 'u.scgtIoctl2::<unnamed - type - u>::state' may have been invalidated at line 1986 
    WARNING_PUSH
    WARNING_DISABLE(26489)
    switch (irpStackLoc->Parameters.DeviceIoControl.IoControlCode)
    {
    case SCGT_IOCTL_GET_STATE:
        ScgtDbgPrint(SCGT_DBG_STATE, ("SCGT_IOCTL_GET_STATE\n"));
        ret = scgtVerifyUserBuffer(irp, sizeof(scgtState));
        if (ret != STATUS_SUCCESS)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "SCGT_IOCTL_GET_STATE buffer error\n");
            break;
        }
        scgtPrintStateName(u.state->stateID);
        u.state->reserved = gtcoreGetState(dev, u.state->stateID, &u.state->val);
        irp->IoStatus.Information = sizeof(scgtState);
        break;

    case SCGT_IOCTL_SET_STATE:
        ScgtDbgPrint(SCGT_DBG_STATE, ("SCGT_IOCTL_SET_STATE\n"));
        ret = scgtVerifyUserBuffer(irp, sizeof(scgtState));
        if (ret != STATUS_SUCCESS)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "SCGT_IOCTL_SET_STATE buffer error\n");
            break;
        }
        scgtPrintStateName(u.state->stateID);
        u.state->reserved = gtcoreSetState(dev, u.state->stateID, u.state->val);
        irp->IoStatus.Information = sizeof(scgtState);
        break;

    case SCGT_IOCTL_READ_CR:
        ScgtDbgPrint(SCGT_DBG_REG, ("SCGT_IOCTL_READ_CR\n"));
        ret = scgtVerifyUserBuffer(irp, sizeof(scgtRegister));
        if (ret != STATUS_SUCCESS)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "SCGT_IOCTL_READ_CR buffer error\n");
            break;
        }
        irp->IoStatus.Information = sizeof(scgtRegister);
        if (u.reg->offset < GTCORE_REGISTER_SIZE)
        {
            u.reg->val = scgtReadCReg(dev, u.reg->offset);
            u.reg->reserved = SCGT_SUCCESS;
        }
        else
        {
            u.reg->reserved = SCGT_BAD_PARAMETER;
        }
        break;

    case SCGT_IOCTL_WRITE_CR:
        ScgtDbgPrint(SCGT_DBG_REG, ("SCGT_IOCTL_WRITE_CR\n"));
        ret = scgtVerifyUserBuffer(irp, sizeof(scgtRegister) );
        if (ret != STATUS_SUCCESS)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "SCGT_IOCTL_WRITE_CR buffer error\n");
            break;
        }
        irp->IoStatus.Information = sizeof(scgtRegister);
        if (u.reg->offset < GTCORE_REGISTER_SIZE)
        {
            scgtWriteCReg(dev, u.reg->offset, u.reg->val);
            u.reg->reserved = SCGT_SUCCESS;
        }
        else
        {
            u.reg->reserved = SCGT_BAD_PARAMETER;
        }
        break;

    case SCGT_IOCTL_READ_NMR:
        ScgtDbgPrint(SCGT_DBG_REG, ("SCGT_IOCTL_READ_NMR\n"));
        ret = scgtVerifyUserBuffer(irp, sizeof(scgtRegister) );
        if (ret != STATUS_SUCCESS)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "SCGT_IOCTL_READ_NMR buffer error\n");
            break;
        }
        irp->IoStatus.Information = sizeof(scgtRegister);
        if (u.reg->offset < GTCORE_NM_REGISTER_SIZE)
        {
            u.reg->val = scgtReadNMReg(dev, u.reg->offset);
            u.reg->reserved = SCGT_SUCCESS;
        }
        else
        {
            u.reg->reserved = SCGT_BAD_PARAMETER;
        }
        break;

    case SCGT_IOCTL_WRITE_NMR:
        ScgtDbgPrint(SCGT_DBG_REG, ("SCGT_IOCTL_WRITE_NMR\n"));
        ret = scgtVerifyUserBuffer(irp, sizeof(scgtRegister));
        if (ret != STATUS_SUCCESS)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "SCGT_IOCTL_WRITE_NMR buffer error\n");
            break;
        }
        irp->IoStatus.Information = sizeof(scgtRegister);
        if (u.reg->offset < GTCORE_NM_REGISTER_SIZE)
        {
            scgtWriteNMReg(dev, u.reg->offset, u.reg->val);
            u.reg->reserved = SCGT_SUCCESS;
        }
        else
        {
            u.reg->reserved = SCGT_BAD_PARAMETER;
        }
        break;

    case SCGT_IOCTL_GET_DEVICE_INFO:
        ScgtDbgPrint(SCGT_DBG_INFO, ("SCGT_IOCTL_GET_DEVICE_INFO\n"));
        ret = scgtVerifyUserBuffer(irp, sizeof(scgtDeviceInfo));
        if (ret != STATUS_SUCCESS)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "SCGT_IOCTL_GET_DEVICE_INFO buffer error\n");
            break;
        }
        ret = scgtIoctlGetDeviceInfo(dev, u.deviceInfo);
        irp->IoStatus.Information = sizeof(scgtDeviceInfo);
        break;

    case SCGT_IOCTL_GET_STATS:
        ScgtDbgPrint(SCGT_DBG_INFO, ("SCGT_IOCTL_GET_STATS\n"));
        ret = scgtVerifyUserBuffer(irp, sizeof(scgtStats));
        if (ret != STATUS_SUCCESS)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "SCGT_IOCTL_GET_STATS buffer error\n");
            break;
        }
        ret = scgtIoctlGetStats(dev, u.stats);
        irp->IoStatus.Information = sizeof(scgtStats);
        break;

    case SCGT_IOCTL_MAP_MEM:
        ScgtDbgPrint(SCGT_DBG_MAP, ("SCGT_IOCTL_MAP_MEM\n"));
        ret = scgtVerifyUserBuffer(irp, sizeof(scgtMemMapInfo));
        if (ret != STATUS_SUCCESS)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "SCGT_IOCTL_MAP_MEM buffer error\n");
            break;
        }
        ret = scgtIoctlMapMem(dev, u.memMapInfo);
        irp->IoStatus.Information = sizeof(scgtMemMapInfo);
        break;

    case SCGT_IOCTL_UNMAP_MEM:
        ScgtDbgPrint(SCGT_DBG_MAP, ("SCGT_IOCTL_UNMAP_MEM\n"));
        ret = scgtVerifyUserBuffer(irp,sizeof(scgtMemMapInfo));
        if (ret != STATUS_SUCCESS)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "SCGT_IOCTL_UNMAP_MEM buffer error\n");
            break;
        }
        ret = scgtIoctlUnmapMem(dev, u.memMapInfo);
        irp->IoStatus.Information = sizeof(scgtMemMapInfo);
        break;

    case SCGT_IOCTL_PUT_INTR:       /* used for debugging interrupt queue */
        ScgtDbgPrint(SCGT_DBG_INTR, ("SCGT_IOCTL_GET_INTR\n"));
        ret = scgtVerifyUserBuffer(irp, sizeof(scgtInterrupt));
        if (ret != STATUS_SUCCESS)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "SCGT_IOCTL_MAP_MEM buffer error\n");
            break;
        }
        gtcorePutIntr(dev, u.intr);
        irp->IoStatus.Information = 0;
        ret = STATUS_SUCCESS;
        break;

    case SCGT_IOCTL_DBG_FLAG:       /* used for controlling which debug message are shown */
        ScgtDbgPrint(SCGT_DBG_INFO, ("SCGT_IOCTL_DBG_FLAG\n"));
        ret = scgtVerifyUserBuffer(irp, sizeof(scgtState));
        if (ret != STATUS_SUCCESS)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "SCGT_IOCTL_DBG_FLAG buffer error\n");
            break;
        }
        ScgtDrvDebugLevel = u.state->val;
        u.state->reserved = ScgtDrvDebugLevel;
        ScgtDbgPrint(SCGT_DBG_INFO, "Set ScgtDrvDebugLevel=0x%x \n", ScgtDrvDebugLevel);
        irp->IoStatus.Information = sizeof(scgtState);
        ret = STATUS_SUCCESS;
        break;

    case SCGT_IOCTL_GET_DBG_FLAG:       /* used for controlling which debug message are shown */
        ScgtDbgPrint(SCGT_DBG_INFO, ("SCGT_IOCTL_GET_DBG_FLAG\n"));
        ret = scgtVerifyUserBuffer(irp, sizeof(scgtState));
        if (ret != STATUS_SUCCESS)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "SCGT_IOCTL_GET_DBG_FLAG buffer error\n");
            break;
        }
        u.state->val = ScgtDrvDebugLevel;
        u.reg->reserved = SCGT_SUCCESS;
        ScgtDbgPrint(SCGT_DBG_INFO, "Get ScgtDrvDebugLevel=0x%x \n", ScgtDrvDebugLevel);
        irp->IoStatus.Information = sizeof(scgtState);
        ret = STATUS_SUCCESS;
        break;

    default:
        ScgtDbgPrint(SCGT_DBG_WARNING, "Ioctl2() default\n");
        irp->IoStatus.Information = 0;
        //ret = STATUS_NOT_IMPLEMENTED;
        ret = STATUS_INVALID_PARAMETER;
    }
    WARNING_POP
    

    ScgtDbgPrint(SCGT_DBG_IOCTL2, "scgtIoctl2() NtStatus:%s - }\n", NtStatusName(ret));

    return ret;
} //scgtIoctl2

/**************************************************************************//**
 * @fn static int scgtIoctlGetStats(_In_ scgtDevice *dev, _Inout_ scgtStats *stats)
 *
 * @brief Get driver statistics. Read semaphore states and other core driver info.
 *
 * @param [in,out]  dev   If non-null, Pointer to SCGT device object
 * @param [in,out]  stats If non-null, the device core status values structure.
 *
 * @return STATUS_SUCCESS
 *****************************************************************************/
static int scgtIoctlGetStats(_In_ scgtDevice *dev, _Inout_ scgtStats *stats)
{
    uint32 ret = SCGT_SUCCESS;

    /* update any stats that need to be updated */
    dev->stats[SCGT_STATS_GET_INTR_WAIT_CNT] = dev->getIntrWaitCount;

    dev->stats[SCGT_STATS_W_ENTRY_SEM_1] = (uint32)KeReadStateSemaphore(&dev->writeTools.entrySem_1);
    dev->stats[SCGT_STATS_W_ENTRY_SEM_2] = (uint32)KeReadStateSemaphore(&dev->writeTools.entrySem_2);
    dev->stats[SCGT_STATS_R_ENTRY_SEM_1] = (uint32)KeReadStateSemaphore(&dev->readTools.entrySem_1);
    dev->stats[SCGT_STATS_R_ENTRY_SEM_2] = (uint32)KeReadStateSemaphore(&dev->readTools.entrySem_2);

    dev->stats[SCGT_STATS_W_EXCH_SEM_0] = dev->wexch.exchQ[0].compSem.waiting;
    dev->stats[SCGT_STATS_W_EXCH_SEM_1] = dev->wexch.exchQ[1].compSem.waiting;
    dev->stats[SCGT_STATS_W_EXCH_SEM_2] = dev->wexch.exchQ[2].compSem.waiting;
    dev->stats[SCGT_STATS_W_EXCH_SEM_3] = dev->wexch.exchQ[3].compSem.waiting;

    dev->stats[SCGT_STATS_R_EXCH_SEM_0] = dev->rexch.exchQ[0].compSem.waiting;
    dev->stats[SCGT_STATS_R_EXCH_SEM_1] = dev->rexch.exchQ[1].compSem.waiting;
    dev->stats[SCGT_STATS_R_EXCH_SEM_2] = dev->rexch.exchQ[2].compSem.waiting;
    dev->stats[SCGT_STATS_R_EXCH_SEM_3] = dev->rexch.exchQ[3].compSem.waiting;

    /* get core stats */
    ret = gtcoreGetStats(dev, stats);
    stats->reserved = ret;
    ScgtDbgPrint(SCGT_DBG_IOCTL2, "scgtIoctlGetStats() ret=0x%x stats.num:%d stats ptr:%p\n",ret,stats->num,stats);

    return STATUS_SUCCESS;
} // scgtIoctlGetStats


/**************************************************************************//**
 * @fn static NTSTATUS scgtIoctlMapMem(_In_ scgtDevice *dev, _In_ scgtMemMapInfo *mapInfo)
 *
 * @brief    Map GT memory to user space.
 *
 * @param  dev      Pointer to SCGT device object
 * @param  mapInfo  Pointer to structure describing the memory map
 *
 * @return .NTSTATUS  STATUS_SUCCESS, STATUS_OBJECT_TYPE_MISMATCH, STATUS_ACCESS_DENIED ,
 * STATUS_INVALID_HANDLE, STATUS_CONFLICTING_ADDRESSES, STATUS_INVALID_PAGE_PROTECTION,
 * STATUS_SECTION_PROTECTION
 *****************************************************************************/
static NTSTATUS scgtIoctlMapMem(_In_ scgtDevice *dev, _In_ scgtMemMapInfo *mapInfo)
{
    NTSTATUS           ntStatus = STATUS_SUCCESS;
#ifdef ORIG_MODE
    UNICODE_STRING     physicalMemoryUnicodeString;
    OBJECT_ATTRIBUTES  objectAttributes;
    HANDLE             physicalMemoryHandle = NULLPTR;
    PVOID              physicalMemorySection = NULLPTR;
    SIZE_T             viewSize = 0;
#endif
    PHYSICAL_ADDRESS   physicalAddress = { 0,0 };
    PVOID              virtualAddress = NULLPTR;
    SIZE_T             length = 0;

#ifndef ORIG_MODE
    LONG count = 0;
    PVOID pvu = NULLPTR;
    PMDL pMdl = NULLPTR;

#endif
    physicalAddress.QuadPart = 0;

    ScgtDbgPrint(SCGT_DBG_MAP, "scgtIoctlMapMem() - {\n");

    //get physical address of gt memory region
    physicalAddress = dev->gtMemPhysAddr;
    if (physicalAddress.QuadPart == 0)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "  Error: Physical address of on board memory = 0\n");
        return STATUS_INVALID_ADDRESS;
    }
    length = dev->memSize;
    if (length < PAGE_SIZE)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "  Error: device mem size less than one page\n");
        return STATUS_INVALID_PARAMETER;
    }

#ifndef ORIG_MODE        
    count = InterlockedCompareExchange(&dev->MappedMemory, 0, 0);        
    ScgtDbgPrint(SCGT_DBG_MAP, "  Number of map calls:%d Current Process: 0x%x \n",
                 count,PsGetCurrentProcess());

    if (count >= MAX_USER_PTRS)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "  Error: Only %d user memory pointers can be allocated at once. \n", MAX_USER_PTRS);
        ScgtDbgPrint(SCGT_DBG_ERROR, "  Existing entries will be overwritten. \n", MAX_USER_PTRS);
        // Start overwriting memory map entries in table.
        count = MAX_USER_PTRS-1;
    }

    /* get mapped kernel address if not mapped from earlier */
    if (dev->memPtr == NULLPTR)
    {
#if (NTDDI_VERSION < NTDDI_WIN10)
        // warning C30029 : Warning: A call was made to MmMapIoSpace().  This allocates executable memory. 
        // If executable memory is not required, please use MmMapIoSpaceEx()    
        #pragma warning(suppress : 30029)
        // MmMapIoSpace not added untill windows 10 DDK.
        dev->memPtr = MmMapIoSpace(dev->gtMemPhysAddr, dev->memSize, MmNonCached);
#else
        dev->memPtr = MmMapIoSpaceEx(dev->gtMemPhysAddr, dev->memSize, PAGE_READWRITE | PAGE_NOCACHE);
#endif
        if (dev->memPtr == NULLPTR)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "Error: kenel mode pointer to on board memory invalid\n");
            mapInfo->reserved = SCGT_DRIVER_ERROR;
            mapInfo->memVirtAddr = 0;
            ntStatus = STATUS_INSUFFICIENT_RESOURCES;
            ScgtDbgPrint(SCGT_DBG_MAP, "scgtIoctlMapMem() NtStatus:%s - }\n", NtStatusName(ntStatus));
            return ntStatus;
        }
    }
    
    /* map user pointer to on board GT memory */
    pMdl = IoAllocateMdl(dev->memPtr, (ULONG)length, FALSE, FALSE, NULLPTR);
    if (pMdl)
    {
        // build mdl to map user space.
        MmBuildMdlForNonPagedPool(pMdl);
        __try
        {
            pvu = MmMapLockedPagesSpecifyCache(pMdl, //MemoryDescriptorList
                UserMode, //AccessMode
                MmNonCached,  // CacheType
                NULLPTR, //RequestedAddress
                FALSE, // BugCheckOnFailure
                HighPagePriority | MdlMappingNoExecute); //Priority 

        }
        /*Exception-filter expression is the constant EXCEPTION_EXECUTE_HANDLER. 
          This might mask exceptions that were not intended to be handled.
        */
        WARNING_SUPPRESS(6320)
        __except (EXCEPTION_EXECUTE_HANDLER)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "  Error exception ocurred when trying to map user pointer to on board memory\n");
            mapInfo->reserved = SCGT_DRIVER_ERROR;
            mapInfo->memVirtAddr = 0;
            IoFreeMdl(pMdl);
            ntStatus=STATUS_INSUFFICIENT_RESOURCES;
            ScgtDbgPrint(SCGT_DBG_MAP, "scgtIoctlMapMem() NtStatus:%s - }\n", NtStatusName(ntStatus));
            return ntStatus;
        }

        if (pvu == NULLPTR)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "  Error function to map user pointer to on board memory failed.\n");
            mapInfo->reserved = SCGT_DRIVER_ERROR;
            mapInfo->memVirtAddr = 0;
            IoFreeMdl(pMdl);
            return STATUS_INSUFFICIENT_RESOURCES;
        }
        
        ScgtDbgPrint(SCGT_DBG_MAP, "  Map memory at physical Address: 0x%x to virtual address: 0x%x memory size size: %u Num maps: %d\n",
            physicalAddress, pvu, length ,count);
        //ScgtDbgPrint(SCGT_DBG_MAP, "  MDL describing page layout: 0x%x number of memory map calls: %u Current Process:0x%x\n", 
        //    pMdl, count, PsGetCurrentProcess() );        
        virtualAddress = pvu;
    }
    else
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "  Error allocatiing MDL to use in mapping user pointer to on board memory\n");
        mapInfo->reserved = SCGT_DRIVER_ERROR;
        mapInfo->memVirtAddr = 0;
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
        ScgtDbgPrint(SCGT_DBG_MAP, "scgtIoctlMapMem() NtStatus:%s - }\n", NtStatusName(ntStatus));
        return ntStatus;
    }

#else
    {
    /*The ADDRESS_AND_SIZE_TO_SPAN_PAGES macro returns the number of pages spanned by
     the virtual range defined by a virtual address and the size in bytes of a transfer request.*/
    viewSize = ADDRESS_AND_SIZE_TO_SPAN_PAGES(physicalAddress.LowPart, length)*PAGE_SIZE;

    /*
       Get a pointer to physical memory...

       - Create the name
       - Initialize the data to find the object
       - Open a handle to the object and check the status
       - Get a pointer to the object
       - Free the handle
    */
    RtlInitUnicodeString(&physicalMemoryUnicodeString,
        L"\\Device\\PhysicalMemory");

    memset(&objectAttributes, 0, sizeof(objectAttributes));

    // warning C26477 : Use 'nullptr' rather than 0 or NULL(es.47).
    WARNING_SUPPRESS( 26477)
    InitializeObjectAttributes(&objectAttributes,
        &physicalMemoryUnicodeString,
        OBJ_CASE_INSENSITIVE,
        (HANDLE)0,
        (PSECURITY_DESCRIPTOR)0);

    ntStatus = ZwOpenSection(&physicalMemoryHandle,
        SECTION_ALL_ACCESS,
        &objectAttributes);

    if (!NT_SUCCESS(ntStatus))
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "ZwOpenSection() failed\n");
        mapInfo->reserved = SCGT_DRIVER_ERROR;
        mapInfo->memVirtAddr = 0;
        //goto done;
        ScgtDbgPrint(SCGT_DBG_MAP, "scgtIoctlMapMem() NtStatus:%s - }\n", NtStatusName(ntStatus));
        return ntStatus;
    }


    // The ObReferenceObjectByHandle routine provides access validation on the object handle,
    // and, if access can be granted, returns the corresponding pointer to the object's body.
    ntStatus = ObReferenceObjectByHandle(physicalMemoryHandle,   // _In_ HANDLE Handle Specifies an open handle for an object.
        SECTION_ALL_ACCESS,     // _In_ ACCESS_TTYPE DesiredAccess - Specifies the requested types of access to the object.
        (POBJECT_TYPE)NULLPTR,        // _In_opt_ POBJECT_TYPE ObjectType
        KernelMode,             // _In KPROCESSOR_MODE  AccessMode (UserMode  or KernelMode)
        &physicalMemorySection, // _Out PVOID Object 
        NULLPTR);               // Out_opt_ POBJECT_HANDLE_INFORMATION HandleInformation (Drivers set this to NULL.)

    if (!NT_SUCCESS(ntStatus))
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "ObReferenceObjectByHandle() failed\n");
        mapInfo->reserved = SCGT_DRIVER_ERROR;
        mapInfo->memVirtAddr = 0;
        ZwClose(physicalMemoryHandle);
        ScgtDbgPrint(SCGT_DBG_MAP, "scgtIoctlMapMem() NtStatus:%s - }\n", NtStatusName(ntStatus));
        return ntStatus;
    }

    virtualAddress = NULLPTR;  /* let ZwMapViewOfSection pick an address */
    ScgtDbgPrint(SCGT_DBG_MAP, "physicalAddress=%08x%08x\n", physicalAddress.HighPart,
        physicalAddress.LowPart);
    ScgtDbgPrint(SCGT_DBG_MAP, "length:%I64d\n", (long long)length);
    /* Map the section */
    ntStatus = ZwMapViewOfSection(physicalMemoryHandle,   //_in     HANDLE SectionHandle
        NtCurrentProcess(),     //_in     HANDLE ProcessHandle
        &virtualAddress,         // _inout PVOID baseAddress
        0L,                     // _in    ULONG_PTR ZeroBits
        length,                 // _in    SIZE_T CommitSize
        &physicalAddress,       // _inout PLARGE_INTEGER sectionOffset
        &length,                // _inout  PSIZE_T ViewSize
        ViewShare,              // _in    SECTION_INHERIT InheritDisposition
        0,                      // _in   ULONG Allocation Type
        PAGE_READWRITE | PAGE_NOCACHE); //_IN ULONG Win32Protect
    if (!NT_SUCCESS(ntStatus))
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "ZwMapViewOfSection failed\n");
        if (ntStatus == STATUS_CONFLICTING_ADDRESSES)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "The specified address range conflicts with an address range already reserved, \n");
            ScgtDbgPrint(SCGT_DBG_ERROR, "or the specified cache attribute type conflicts with the address range's existing \n");
            ScgtDbgPrint(SCGT_DBG_ERROR, "cache attribute\n");
        }
        else if (ntStatus == STATUS_INVALID_PAGE_PROTECTION)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "The value specified for the Protect parameter is invalid.\n");
        }
        else if (ntStatus == STATUS_SECTION_PROTECTION)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "The value specified for the AllocationType parameter is incompatible with the \n \
                    protection type specified when the section was created.\n");
        }
        else
            ScgtDbgPrint(SCGT_DBG_ERROR, "Unknown status code 0x%x \n", ntStatus);

        mapInfo->reserved = SCGT_DRIVER_ERROR;
        mapInfo->memVirtAddr = 0;
        //goto close_handle;
        ZwClose(physicalMemoryHandle);
        ScgtDbgPrint(SCGT_DBG_MAP, "scgtIoctlMapMem() NtStatus:%s - }\n", NtStatusName(ntStatus));
        return ntStatus;
     }
#endif
    
    mapInfo->memVirtAddr = (uint64)virtualAddress;
    mapInfo->memSize = (uint32)length;
    mapInfo->reserved = SCGT_SUCCESS;

    // store address for garbage cleanup if umap not called.
    dev->virtualAddress = (uint64)virtualAddress;

#ifndef ORIG_MODE    
    // dev->pMdl = pMdl;
    if (count >= MAX_USER_PTRS)
    {
        ScgtDbgPrint(SCGT_DBG_WARNING, "  WARNINNG: Mapped memory counter:%d > MAX_USER_PTRS:%d \n", count, MAX_USER_PTRS);
        count = MAX_USER_PTRS - 1;
    }
    //Store the vitual address and pmdl so we can lookup pMDL unmap function.
    dev->userModePtrMem[count] = virtualAddress;
    dev->userModePtrMdl[count] = pMdl;    
    dev->userModePtrPrc[count] = PsGetCurrentProcess();
#endif

    // Track number of mapped vs unmapped calls.
    count = InterlockedIncrement(&dev->MappedMemory);        
    
    ScgtDbgPrint(SCGT_DBG_INFO, "  scgtIoctlMapMem mapped mem ptr 0x%I64x\n", mapInfo->memVirtAddr);

#ifdef ORIG_MODE
    //close_handle:
    ZwClose(physicalMemoryHandle);
    //done:
#endif

    ScgtDbgPrint(SCGT_DBG_MAP, "scgtIoctlMapMem() NtStatus:%s - }\n", NtStatusName(ntStatus));

    return ntStatus;
} // scgtIoctlMapMem

/**************************************************************************//**
 * @fn static NTSTATUS scgtIoctlUnmapMem(_In_ scgtDevice * dev, _In_ scgtMemMapInfo * mapInfo)
 *
 * @brief   Unmap GT memory.
 *
 * @param  dev      Pointer to SCGT device info
 * @param  mapInfo  pointer to structure describing the memory map
 *
 * @return .NTSTATUS value. STATUS_SUCCESS or STATUS_ACCESS_DENIED
 ******************************************************************************/
static NTSTATUS scgtIoctlUnmapMem(_In_ scgtDevice * dev, _In_ scgtMemMapInfo * mapInfo)
{
    LONG count = 0;
    NTSTATUS ntStatus = STATUS_SUCCESS;
#ifndef ORIG_MODE
    
    PVOID pvu = NULLPTR;
    PMDL pMdl = NULLPTR;
    LONG index = 0;
#endif
    //DLC added UNREFERENCED_PARAMETER to prevent Warning C4100 Unreferenced formal parameter
    //UNREFERENCED_PARAMETER(dev);
    
    ScgtDbgPrint(SCGT_DBG_MAP, "scgtIoctlUnMapMem() - {\n");

    count = InterlockedCompareExchange(&dev->MappedMemory, 0, 0);
    ScgtDbgPrint(SCGT_DBG_MAP, "  Number of map calls:%d Current Process:0x%x\n", 
                 count, PsGetCurrentProcess());
    if ( count == 0)
    {
        mapInfo->reserved = SCGT_BAD_PARAMETER;
        ScgtDbgPrint(SCGT_DBG_WARNING, "  No Mapped memory section\n");
        ntStatus = STATUS_UNSUCCESSFUL;
        ScgtDbgPrint(SCGT_DBG_MAP, "scgtIoctlUnMapMem() NtStatus:%s - }\n", NtStatusName(ntStatus));        
        return ntStatus;
    }

    if (mapInfo->memVirtAddr == 0)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR,  "  Error: scgtIoctlUnMapMem called with mapInfo->memVirtAddr == 0\n");
        ntStatus = STATUS_UNSUCCESSFUL;
        ScgtDbgPrint(SCGT_DBG_MAP, "scgtIoctlUnMapMem() NtStatus:%s - }\n", NtStatusName(ntStatus));
        return ntStatus;
    }
    /*
    if (mapInfo->memVirtAddr != dev->virtualAddress)
    {
        ScgtDbgPrint(SCGT_DBG_WARNING, "Warning: mapInfo->memVirtAddr:0x%lx != dev->virtualAddress:0x%lx\n",
            mapInfo->memVirtAddr , dev->virtualAddress);
    }
    */
#ifndef ORIG_MODE
    pMdl = NULLPTR;
    pvu = UINT64_TO_PTR(PVOID, mapInfo->memVirtAddr);
    index = 0;    
    while  ( ( index < count ) &&
             ( index < MAX_USER_PTRS ) )
    {
        if (pvu == dev->userModePtrMem[index])
        {
            ScgtDbgPrint(SCGT_DBG_MAP, "  unmap memory at index %d of user pointer array\n", index);
            pMdl = dev->userModePtrMdl[index];                        
            break;
        }
        index++;
    }
    if (pMdl == NULLPTR)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "  Error unable to locate user virtual address: 0x%x in userModePtrMem array. Num memory mapping::%lx\n", pvu, count);
        ntStatus = STATUS_UNSUCCESSFUL;
        ScgtDbgPrint(SCGT_DBG_MAP, "scgtIoctlUnMapMem() NtStatus:%s - }\n", NtStatusName(ntStatus));
        return ntStatus;
    }
    
    if ( dev->userModePtrPrc[index] != PsGetCurrentProcess() )
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "  Error: dev->userModePtrPrc[%d] != PsGetCurrentProcess:0x%x\n",index, dev->userModePtrPrc[index], PsGetCurrentProcess());
    }

    ScgtDbgPrint(SCGT_DBG_MAP, "  Unmap pages at user virtual Addr: 0x%x mapped to Kernel virtual addr: %x at index %d \n", pvu, dev->memPtr,index);
    //ScgtDbgPrint(SCGT_DBG_MAP, " MDL describing page layout: 0x%x number of memory map calls: %u Current Process:0x%x\n",
    //     pMdl, count, PsGetCurrentProcess());
    MmUnmapLockedPages(pvu, pMdl);
    IoFreeMdl(pMdl);    
    // Track number of mapped vs unmapped calls.
    InterlockedDecrement(&dev->MappedMemory);
    mapInfo->reserved = SCGT_SUCCESS;

    while ( (index + 1 < count) &&
            (index +1 < MAX_USER_PTRS) )          
    {
        //ScgtDbgPrint(SCGT_DBG_MAP, "  Move userModePtrs at index %u to %u\n", index+1 , index);
        dev->userModePtrMem[index] = dev->userModePtrMem[index + 1];
        dev->userModePtrMdl[index] = dev->userModePtrMdl[index + 1];
        dev->userModePtrPrc[index] = dev->userModePtrPrc[index + 1];
        index++;
    }
    if (index + 1 < MAX_USER_PTRS)
    {
        dev->userModePtrMem[index + 1] = NULLPTR;
        dev->userModePtrMdl[index + 1] = NULLPTR;
        dev->userModePtrPrc[index + 1] = NULLPTR;
    }
#else
    ScgtDbgPrint(SCGT_DBG_MAP, "Calling ZwUnmapViewOfSection MapCount=%d map address=%lu", count, mapInfo->memVirtAddr);

    //DLC ntStatus = ZwUnmapViewOfSection(NtCurrentProcess(),
    //                                (PVOID) mapInfo->memVirtAddr);
    _IRQL_limited_to_(PASSIVE_LEVEL);
    ntStatus = ZwUnmapViewOfSection(NtCurrentProcess(),
        UINT64_TO_PTR(PVOID, mapInfo->memVirtAddr));
    if (ntStatus != STATUS_SUCCESS)
        mapInfo->reserved = SCGT_BAD_PARAMETER;
    else
        mapInfo->reserved = SCGT_SUCCESS;
#endif

    

    dev->virtualAddress = 0;

    ScgtDbgPrint(SCGT_DBG_MAP, "scgtIoctlUnMapMem() NtStatus:%s - }\n", NtStatusName(ntStatus));
    return ntStatus;
} //scgtIoctlUnmapMem


/**************************************************************************//**
 * @fn  static void scgtGetIntrTimerStart( _In_ scgtDevice *dev)
 *
 * @brief  Start the get interrupt timer
 *
 * @param [in,out]  dev   If non-null, the dev.
 *****************************************************************************/
inline static void scgtGetIntrTimerStart(_In_ scgtDevice *dev)
{
    LARGE_INTEGER timeOffset;  /* 100 ns intervals */
    timeOffset.QuadPart = 0;
    ksysSpinLockFlags spinLockFlags;

    ksysSpinLockLock(&dev->getIntrTimerSpinLock, &spinLockFlags);

    if (!dev->getIntrTimerStarted)
    {
        dev->getIntrTimerStarted = 1;
        ksysSpinLockUnlock(&dev->getIntrTimerSpinLock, &spinLockFlags);
        timeOffset.HighPart = -1;
        timeOffset.LowPart = SCGT_GET_INTR_TIMER_REL_TIME;
        KeSetTimer(&dev->getIntrTimer,
            timeOffset,
            &dev->getIntrTimerDPC);
    }
    else
    {
        ksysSpinLockUnlock(&dev->getIntrTimerSpinLock, &spinLockFlags);
    }
} // scgtGetIntrTimerStart

/**************************************************************************//**
 * @fn static uint32 scgtIoctlGetIntr(_In_ scgtDevice *dev, 
 *                                 _In_ scgtGetIntrBuf *gibuf)
 *
 * @brief  SCGT ioctl call to get interrupt data.
   Returns when requested number of interrupts occurs or the time out period expires.
 *
 * @param [in,out]  dev   If non-null, Pointer to SCGT device object
 * @param [in,out]  gibuf If non-null, pointer to get interrupt buffer structure.
 *
 * @return SCGT_STATUS SCGT_SUCCESS,SCGT_BAD_PARAMATER,SCGT_TIMEOUT,
           or  SCGT_MISSED_INTERRTUPTS
 *****************************************************************************/
static uint32 scgtIoctlGetIntr( _In_ scgtDevice *dev, _In_ scgtGetIntrBuf *gibuf)
{
    uint32 ret = STATUS_SUCCESS;
    uint32 waitTime = 0;    /* how long I've waited */
    ksysSpinLockFlags spinLockFlags;

    ksysSpinLockLock(&dev->getIntrTimerSpinLock, &spinLockFlags);
    dev->getIntrTmrUseCnt++;
    ksysSpinLockUnlock(&dev->getIntrTimerSpinLock, &spinLockFlags);

    scgtGetIntrTimerStart(dev);

    while (1)
    {
        if ((ret = gtcoreGetIntr(dev, gibuf)) != SCGT_TIMEOUT)
        {
            break;  /* we got something */
        }

        /* if we got here.. we have to wait */

        if (waitTime >= gibuf->timeout || getIntrTimerExit)
        {
            gibuf->numInterruptsRet = 0;
            break;
        }

        ksysSpinLockLock(&dev->getIntrWaitCountSpinLock, &spinLockFlags);
        dev->getIntrWaitCount++;
        ksysSpinLockUnlock(&dev->getIntrWaitCountSpinLock, &spinLockFlags);

        ksysSemSTake(&dev->getIntrSem);

        waitTime += SCGT_GET_INTR_TIMER_MILLISEC;
    }

    ksysSpinLockLock(&dev->getIntrTimerSpinLock, &spinLockFlags);
    dev->getIntrTmrUseCnt--;
    ksysSpinLockUnlock(&dev->getIntrTimerSpinLock, &spinLockFlags);

    return ret;
} // scgtIoctlGetIntr

/**************************************************************************//**
 * @fn static NTSTATUS scgtIoctlGetDeviceInfo( _In_ scgtDevice *dev, _In_ scgtDeviceInfo *devInfo)
 *
 * @brief  Ioctl call to get SCGT device information.
 *
 * @param [in,out]  dev      If non-null, pointer to SCGT device object
 * @param [in,out]  devInfo  If non-null, information describing the device.
 *
 * @return STATUS_SUCCESS
 *****************************************************************************/
inline static NTSTATUS scgtIoctlGetDeviceInfo(_In_ scgtDevice *dev, _In_ scgtDeviceInfo *devInfo)
{
    devInfo->reserved = gtcoreGetDeviceInfo(dev, devInfo);

    RtlStringCbCopyNA(devInfo->driverRevisionStr, 128, driverRevStr, 128);
    RtlStringCbCopyNA(devInfo->boardLocationStr, 128, dev->boardLocationStr, 128);

    return STATUS_SUCCESS;
} // scgtIoctlGetDeviceInfo
/** @} **/


/** @defgroup PNPCode SCRAMNet GT PNP function code
 SCRAMNet GT PNP function code
 @{
 **/

 /**************************************************************************//**
  * @fn void scgtGetIntrTimerCallback( IN PKDPC dpc,  IN PVOID context,
  *                                    IN PVOID sysArg1,  IN PVOID sysArg2)
  *
  * @brief  Callback (DPC) function for timer interrupt used in
  *     GetInterrupt() time outing. Initialized in scgtInitGetIntrTimer()

  *
  * @param  dpc      The DPC
  * @param  context  The context pointer holding reference to SCGT device object
  * @param  sysArg1  The first system argument.
  * @param  sysArg2  The second system argument.
  *****************************************************************************/
void scgtGetIntrTimerCallback(IN PKDPC dpc, IN PVOID context,
    IN PVOID sysArg1, IN PVOID sysArg2)
{
    //DLC added UNREFERENCED_PARAMETER to prevent Warning C4100 Unreferenced formal parameter
    UNREFERENCED_PARAMETER(dpc);
    UNREFERENCED_PARAMETER(sysArg1);
    UNREFERENCED_PARAMETER(sysArg2);

    scgtDevice *dev = (scgtDevice *)context;
    LARGE_INTEGER timeOffset = { 0,0 };  /* 100 nanosecond intervals */
    timeOffset.QuadPart = 0;
    ksysSpinLockFlags spinLockFlags = 0;

    if (dev == NULLPTR)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtGetIntrTimerCallback called with null context pointer\n");
        return;
    }
    scgtGiveIntrSemDPC(dev);

    ksysSpinLockLock(&dev->getIntrTimerSpinLock, &spinLockFlags);
    if (dev->getIntrTmrUseCnt && (!getIntrTimerExit))
    {
        ksysSpinLockUnlock(&dev->getIntrTimerSpinLock, &spinLockFlags);
        timeOffset.HighPart = -1;
        timeOffset.LowPart = SCGT_GET_INTR_TIMER_REL_TIME;
        KeSetTimer(&dev->getIntrTimer, timeOffset,
            &dev->getIntrTimerDPC);
    }
    else
    {
        dev->getIntrTimerStarted = 0;
        ksysSpinLockUnlock(&dev->getIntrTimerSpinLock, &spinLockFlags);
    }

    dev->stats[SCGT_STATS_GET_INTR_TIMER]++;
} // scgtGetIntrTimerCallback

/**************************************************************************//**
 * @fn  NTSTATUS scgtCompletion(IN PDEVICE_OBJECT deviceObject, IN PIRP irp, IN PVOID context)
 *
 * @brief   IoCompletion routine used by NotifyBusDriver function
 *
 * @param  deviceObject   The device object.
 * @param  irp            The irp.
 * @param  context        Pointer to event object
 *
* @return STATUS_SUCCESS, STATUS_UNSUCCESSFUL or STATUS_MORE_PROCESSING_REQUIRED
  ****************************************************************************/
_Use_decl_annotations_
NTSTATUS scgtCompletion(IN PDEVICE_OBJECT deviceObject, IN PIRP irp, IN PVOID context)
{
    PKEVENT event = (PKEVENT)context;
    PIO_STACK_LOCATION  irpStackLoc = NULLPTR;
    UNREFERENCED_PARAMETER(deviceObject);

    irpStackLoc = IoGetCurrentIrpStackLocation(irp);

    //
    // Driver Writers, please note:
    //
    //  The following code is only necessary IF (a) WE have a completion
    //  routine, AND (b) WE return STATUS_PENDING from our dispatch entry
    //  point after re-claiming the IRP.  Since neither of these things
    //  is true... this code does not belong here.
    //
    //    if (Irp->PendingReturned) {
    //
    //        IoMarkIrpPending( Irp );
    //
    //    }
    switch (irpStackLoc->MajorFunction)
    {
        case IRP_MJ_PNP:
            if (event == NULLPTR)
            {
                ScgtDbgPrint(SCGT_DBG_ERROR, "scgtCompletion called with null event context\n");
                return STATUS_UNSUCCESSFUL;
            }

            KeSetEvent(event, 0, FALSE);

            // Take the IRP back so that we can continue using it during
            //   the IRP_MN_START_DEVICE dispatch routine.
            //  We will have to call IoCompleteRequest
            return STATUS_MORE_PROCESSING_REQUIRED;
            break;
        default:
            ScgtDbgPrint(SCGT_DBG_ERROR, "scgtComplete routine call with Major not equal IRP_MJ_PNP\n");
            break;
    }

    return STATUS_SUCCESS;
} // scgtCompletion

/**************************************************************************//**
@fn static NTSTATUS NotifyBusDriver(PDEVICE_OBJECT TargetDevice, PIRP Irp)
@brief Notify Bus Driver. Pass IRP down to bus driver and wait for response.
Used when bus driver handles the IRP before we do.
@param TargetDevice Target device object
@param Irp  System IRP
@return NTSTATUS value.
**//**************************************************************************/
static NTSTATUS NotifyBusDriver(PDEVICE_OBJECT TargetDevice, PIRP Irp)
{
    NTSTATUS            code;
    KEVENT              eventWaitLowerDrivers;

    KeInitializeEvent(&eventWaitLowerDrivers, NotificationEvent, FALSE);

    //
    // The BUS DRIVER handles this IRP before we do
    //
    IoCopyCurrentIrpStackLocationToNext(Irp);

    // register an IoCOmpletion routine, which will be called when the next lower-level driver
    // has completed the requested operation for the given IRP.
    // Call scgtCompletion() when this IRP is done...    
    IoSetCompletionRoutine(Irp,
        scgtCompletion,
        &eventWaitLowerDrivers,
        TRUE,
        TRUE,
        TRUE);

    // Send the IRP to the bus driver.
    // This IRP must be handled first by the parent bus driver for a device and
    // then by each higher driver in the device stack.
    code = IoCallDriver(TargetDevice, Irp);
    if (STATUS_PENDING == code)
    {
        // wait for lover level IoCompletion event to complete.
        KeWaitForSingleObject(&eventWaitLowerDrivers,
            Executive,
            KernelMode,
            FALSE,
            NULLPTR);

        code = Irp->IoStatus.Status;
    }

    return code;
} // NotifyBusDriver


 /**************************************************************************//**
  * @fn  static void scgtInitGetIntrTimer(_In_ scgtDevice *dev)
  *
  * @brief  Initialize GetInterrupt timer, semaphore and spin locks.
  *     This must be called before scgtIoctlGetIntr() is called.
  *
  * @param [in,out]  dev   If non-null, the dev.
  ****************************************************************************/
inline static void scgtInitGetIntrTimer(_In_ scgtDevice *dev)
{
    ksysSemSCreate(&dev->getIntrSem);

    ksysSpinLockCreate(&dev->getIntrWaitCountSpinLock);
    ksysSpinLockCreate(&dev->getIntrTimerSpinLock);

    dev->getIntrWaitCount = 0;      // Number of get interrupt calls waiting for interrupt semaphore.
    dev->getIntrTimerStarted = 0;   // Interrupt timer started
    dev->getIntrTmrUseCnt = 0;      // Number of threads using interrupt timer

    KeInitializeTimer(&dev->getIntrTimer);
    KeInitializeDpc(&dev->getIntrTimerDPC, scgtGetIntrTimerCallback, (void *)dev);
} // scgtInitGetIntrTimer

/**************************************************************************//**
 * @fn  static void scgtDestroyGetIntrTimer( _In_ scgtDevice *dev)
 *
 * @brief Destroy get interrupt timer.
 *       Stop timer and destroy semaphores and spin locks
 *
 * @param [in,out]  dev   If non-null, the dev.
 *****************************************************************************/
inline static void scgtDestroyGetIntrTimer(_In_ scgtDevice *dev)
{
    // ksys call macros below are not defined so added UNREFERENCED_PARAMETER call to prevent warnings
    UNREFERENCED_PARAMETER(dev);

    getIntrTimerExit = 1;
    //! cancel timer here..

    ksysSemSDestroy(&dev->getIntrSem);
    ksysSpinLockDestroy(&dev->getIntrWaitCountSpinLock);
    ksysSpinLockDestroy(&dev->getIntrTimerSpinLock);
} // scgtDestroyGetIntrTimer


 /**************************************************************************//**
 * @fn void void ShowPnpIrpName(_In_ const int function)
 * @brief  Show PNP minor function name (IRP_MN_XXXX).
 * @param function IRP_MN_POWER minor parameter value.
 **//**************************************************************************/
void ShowPnpIrpName(_In_ const int function)
{
    switch (function)
    {
    case IRP_MN_START_DEVICE:
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(). IRP_MN_START_DEVICE\n");
        break;
    case IRP_MN_QUERY_STOP_DEVICE:
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(). IRP_MN_QUERY_STOP_DEVICE\n");
        break;
    case IRP_MN_STOP_DEVICE:
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(). IRP_MN_QUERY_STOP_DEVICE\n");
        break;
    case IRP_MN_CANCEL_STOP_DEVICE:
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_CANCEL_STOP_DEVICE\n");
        break;
    case IRP_MN_QUERY_REMOVE_DEVICE:
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_QUERY_REMOVE_DEVICE\n");
        break;
    case IRP_MN_REMOVE_DEVICE:
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_REMOVE_DEVICE\n");
        break;
    case IRP_MN_CANCEL_REMOVE_DEVICE:
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_CANCEL_REMOVE_DEVICE\n");
        break;
    case IRP_MN_SURPRISE_REMOVAL:
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_SURPRISE_REMOVAL\n");
        break;
    case IRP_MN_QUERY_CAPABILITIES:
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_QUERY_CAPABILITIES\n");
        break;
    case IRP_MN_QUERY_PNP_DEVICE_STATE:
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_QUERY_PNP_DEVICE_STATE\n");
        break;
    case IRP_MN_FILTER_RESOURCE_REQUIREMENTS:
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_FILTER_RESOURCE_REQUIREMENTS\n");
        break;
    case IRP_MN_DEVICE_USAGE_NOTIFICATION:
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_DEVICE_USAGE_NOTIFICATION\n");
        break;
    case IRP_MN_QUERY_DEVICE_RELATIONS:
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_QUERY_DEVICE_RELATIONS\n");
        break;
    case IRP_MN_QUERY_RESOURCES:
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_QUERY_RESOURCES\n");
        break;
    case IRP_MN_QUERY_RESOURCE_REQUIREMENTS:
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_QUERY_RESOURCE_REQUIREMENTS\n");
        break;
    case IRP_MN_QUERY_ID:
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_QUERY_ID\n");
        break;
    case IRP_MN_QUERY_DEVICE_TEXT:
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_QUERY_DEVICE_TEXT\n");
        break;
    case IRP_MN_QUERY_BUS_INFORMATION:
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_QUERY_BUS_INFORMATION\n");
        break;
    case IRP_MN_QUERY_INTERFACE:
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_QUERY_INTERFACE \n");
        break;
    case IRP_MN_READ_CONFIG:
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_READ_CONFIG \n");
        break;
    case IRP_MN_WRITE_CONFIG:
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_WRITE_CONFIG \n");
        break;
    case IRP_MN_DEVICE_ENUMERATED:
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_DEVICE_ENUMERATED \n");
        break;
    case IRP_MN_SET_LOCK:
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_SET_LOCK \n");
        break;
    default:
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP():unknown Minor PNP IRP 0x%x\n", function);
        break;
    }
} // ShowPnpIrpName


/**************************************************************************//**
 * @fn static NTSTATUS scgtInitDMA( IN scgtDevice *dev)
 *
 * @brief  Initialize SCGT DMA objects.
 *
 * @param [in,out]  dev   If non-null, pointer to SCGT device object.
 * @return  STATUS_SUCCESS or STATUS_INSUFFICIENT_RESOURCES
******************************************************************************/
static NTSTATUS scgtInitDMA( IN scgtDevice *dev)
{
    INTERFACE_TYPE busType = PCIBus;
    ULONG tempVal = 0;
    WARNING_SUPPRESS(26494) /*Variable 'desc' is uninitialized.Always initialize an object(type.5). */
    DEVICE_DESCRIPTION desc;
    uint32 i = 0;
    uint32 dmaBufferSpace = 0;
    uint32 TransactionQueueSize = 0;
    uint32 numBlocks = 0;

    if (dev == NULLPTR) return STATUS_INVALID_PARAMETER;

    IoGetDeviceProperty(dev->physDeviceObject, DevicePropertyLegacyBusType,
        sizeof(busType), &busType, &tempVal);

    /* Max number of bytes in one DMA transfer*/
    dev->MaxDMABlockSize = SCGT_MAX_CHUNK_SIZE;

    /* number of bytes need to map all DMA data blocks for both directions */
    if (dev->Is64BitOS)
    {
        numBlocks = SCGT_DMA_NUM_BUFFERS * 2;
    }
    else
    {
        numBlocks = 1;
    }
    dmaBufferSpace = dev->MaxDMABlockSize * numBlocks;

    /* number of bytes needed by the Transaction Queue */
    TransactionQueueSize = (GTCORE_EXCH_CNT * GTCORE_TQE_SIZE) + (GTCORE_CACHE_LINE_SIZE * 2);

    /* Set up our device descriptor */
    RtlZeroMemory(&desc, sizeof(desc));
    desc.Version = DEVICE_DESCRIPTION_VERSION;
    desc.Master = TRUE;
    desc.ScatterGather = TRUE;
    desc.DemandMode = FALSE;
    desc.AutoInitialize = FALSE;
    desc.InterfaceType = busType;
    //desc.MaximumLength = 0x10000;
    desc.MaximumLength = dmaBufferSpace + TransactionQueueSize + CHAIN_LIST_SIZE;
    desc.Dma32BitAddresses = TRUE;

    /** number of mapping registers we are looking for */
    tempVal = (desc.MaximumLength / PAGE_SIZE) + 1; 

    do
    {
        /* Get Io DMA adapter */
        dev->dmaAdapter = IoGetDmaAdapter(dev->physDeviceObject, &desc, &tempVal);
        if (dev->dmaAdapter != NULLPTR)
        {
            if (tempVal >= desc.MaximumLength / PAGE_SIZE)
            {
                break;
            }
        }
        /* try smaller chunk size*/
        if (dev->MaxDMABlockSize % PAGE_SIZE != 0)
        {
            dev->MaxDMABlockSize = dev->MaxDMABlockSize -  (dev->MaxDMABlockSize % PAGE_SIZE );
        }
        else
            dev->MaxDMABlockSize = dev->MaxDMABlockSize - PAGE_SIZE;

        dmaBufferSpace = dev->MaxDMABlockSize * numBlocks;
        desc.MaximumLength = dmaBufferSpace + TransactionQueueSize + CHAIN_LIST_SIZE;
        tempVal = (desc.MaximumLength / PAGE_SIZE) + 1;  /** number of mapping registers we are looking for */
        ScgtDbgPrint(SCGT_DBG_ERROR, "IoGetDmaAdapter failed. desc.MaximumLenght = %u Smallest Size:%u\n", 
                     desc.MaximumLength, PAGE_SIZE * numBlocks);
    } while ((dev->MaxDMABlockSize >= (PAGE_SIZE * numBlocks) ));

    if ( (dev->dmaAdapter == NULLPTR) || ( dev->MaxDMABlockSize < (PAGE_SIZE * numBlocks)) )
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtInitDMA(): FAILED!!!\n");
        return STATUS_INSUFFICIENT_RESOURCES;
    }
    if (tempVal < (desc.MaximumLength / PAGE_SIZE) )
    {
        ScgtDbgPrint(SCGT_DBG_WARNING, "Unable to allocate %u map registers. Got %u map registes\n", desc.MaximumLength / PAGE_SIZE, tempVal);
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    ScgtDbgPrint(SCGT_DBG_DMA, "Got DMA Adapter Num Map Registers = %u PAGE_SIZE = %i\n", tempVal,PAGE_SIZE);
    ScgtDbgPrint(SCGT_DBG_DMA, "dev->MaxDMABlockSize=%ld bytes \n", dev->MaxDMABlockSize);
    ScgtDbgPrint(SCGT_DBG_DMA, "desc.MaximumLength %ld bytes \n", desc.MaximumLength);

    if (dev->Is64BitOS)
    {
        for (i = 0; i < SCGT_DMA_NUM_BUFFERS; i++)
        {
            dev->DmaRead[i].pData = (uint32*)dev->dmaAdapter->DmaOperations->AllocateCommonBuffer(
                dev->dmaAdapter, dev->MaxDMABlockSize, &dev->DmaRead[i].physAddr, FALSE);
            if (dev->DmaRead[i].pData == NULLPTR)
            {
                ScgtDbgPrint(SCGT_DBG_ERROR, "scgtInitDMA() allocated DMA buffer %u FAILED!!!\n", i);
                return STATUS_INSUFFICIENT_RESOURCES;
            }
            dev->DmaRead[i].Status = SCGT_DMA_EMPTY;
            dev->DmaRead[i].Cnt = 0;
            ScgtDbgPrint(SCGT_DBG_DMA, " dev->DmaRead[%u].physAddr = 0x%08x%08x\n", i,
                dev->DmaRead[i].physAddr.HighPart, dev->DmaRead[i].physAddr.LowPart);
            dev->DmaWrite[i].pData = (uint32*)dev->dmaAdapter->DmaOperations->AllocateCommonBuffer(
                dev->dmaAdapter, dev->MaxDMABlockSize, &dev->DmaWrite[i].physAddr, FALSE);
            if (dev->DmaWrite[i].pData == NULLPTR)
            {
                ScgtDbgPrint(SCGT_DBG_ERROR, "scgtInitDMA() allocated DMA buffer %u FAILED!!!\n", i);
                return STATUS_INSUFFICIENT_RESOURCES;
            }
            dev->DmaWrite[i].Status = SCGT_DMA_EMPTY;
            dev->DmaWrite[i].Cnt = 0;
            ScgtDbgPrint(SCGT_DBG_DMA, "dev->DmaWrite[%u].physAddr = 0x%08x%08x\n", i,
                dev->DmaWrite[i].physAddr.HighPart, dev->DmaWrite[i].physAddr.LowPart);
        }
    }
    return STATUS_SUCCESS;
} // scgtInitDMA


/*************************************************************************//**
@fn static void scgtReturnResources(_In_ scgtDevice* dev)
@brief Release resource allocated to the SCGT device.
@param dev pointer to SCGT device object
*//*************************************************************************/
static void scgtReturnResources(_In_ scgtDevice* dev)
{
    //NTSTATUS ret = STATUS_SUCCESS;
#ifndef ORIG_MODE    
    PVOID pvu = NULLPTR;    
    PMDL pMdl = NULLPTR;
    LONG index = 0;
#endif
    LONG count = 0;

    ScgtDbgPrint(SCGT_DBG_HALT, "scgtReturnResources() - {\n");

    if (dev == NULLPTR)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtReturnResources called with null device pointer\n");
        return;
    }

    /* disconnect interrupt */
    if (dev->interruptObject != NULLPTR)
    {
        ScgtDbgPrint(SCGT_DBG_HALT, "Release Interrupts\n");
        IoDisconnectInterrupt(dev->interruptObject);
        dev->interruptObject = NULLPTR;
    }

    // Stop thread used to process DMA transfers.
    scgtStopThread(dev);

    /* free allocated resources */
    gtcoreDestroy(dev);
    scgtDestroyGetIntrTimer(dev);
    scgtDestroyDMATools(dev);

    /* unmap registers */
    if (dev->cRegPtr)
    {
        ScgtDbgPrint(SCGT_DBG_HALT, "Umapping Configuration Registers.\n");
        MmUnmapIoSpace(dev->cRegPtr, GTCORE_REGISTER_SIZE);
        dev->cRegPtr = NULLPTR;
    }
    if (dev->nmRegPtr)
    {
        ScgtDbgPrint(SCGT_DBG_HALT, "Umapping Network Management Registers\n");
        MmUnmapIoSpace(dev->nmRegPtr, GTCORE_NM_REGISTER_SIZE);
        dev->nmRegPtr = NULLPTR;
    }
    count = InterlockedCompareExchange(&dev->MappedMemory, 0, 0);
    if (count != 0)
    {
        //if (dev->virtualAddress != 0)
        {
            //ScgtDbgPrint(SCGT_DBG_ERROR, "Umapping virtual pointer to GT memory at 0x%llx\n",
                //dev->virtualAddress);
#ifndef ORIG_MODE
        while ((index < count) &&
            (count < MAX_USER_PTRS))
        {
            //pvu = UINT64_TO_PTR(PVOID, dev->virtualAddress);
            //pvk = UINT64_TO_PTR(PVOID, dev->kenelVirtualAddress);
            pvu = dev->userModePtrMem[index];
            pMdl = dev->userModePtrMdl[index];
            if ((pvu == NULLPTR) || (pMdl == NULLPTR))
            {
                ScgtDbgPrint(SCGT_DBG_ERROR, "Error user mode pointer at index %d is NULL\n", index);
                index++;
                continue;
            }

            ScgtDbgPrint(SCGT_DBG_HALT, "Unmapping virtual pointer to GT memory at 0x%x\n", pvu);
            MmUnmapLockedPages(pvu, pMdl);
            IoFreeMdl(pMdl);
            InterlockedDecrement(&dev->MappedMemory);
            dev->userModePtrMem[index] = NULLPTR;
            dev->userModePtrMdl[index] = NULLPTR;
            index++;
        }
        //length = dev->memSize;
        //pvk = dev->memPtr;
        //MmUnmapIoSpace(pvk, length);
#else

        WARNING_SUPPRESS( 26477)
        _IRQL_limited_to_(PASSIVE_LEVEL);
        ZwUnmapViewOfSection(NtCurrentProcess(),
            UINT64_TO_PTR(PVOID, dev->virtualAddress));
#endif
        dev->virtualAddress = 0;
        InterlockedDecrement(&dev->MappedMemory);
    }

    }
    /* umap kernel pointer to on board memory */
    if (dev->memPtr != NULLPTR)
    {
         ScgtDbgPrint(SCGT_DBG_HALT, "Umapping on board memory pointer.\n");
         MmUnmapIoSpace(dev->memPtr, dev->memSize);
         dev->memPtr = NULLPTR;
    }
    
    /* destroy DMA adapter */
    if (dev->dmaAdapter)
    {
        if (dev->Is64BitOS)
        {
            int i;
            ScgtDbgPrint(SCGT_DBG_PNP, "Freeing DMA adapter resources\n");
            for (i = 0; i < SCGT_DMA_NUM_BUFFERS; i++)
            {
                if (dev->DmaRead[i].pData != NULLPTR)
                {
                    dev->dmaAdapter->DmaOperations->FreeCommonBuffer(dev->dmaAdapter,    //PDMA_ADAPTER 
                                                                     dev->MaxDMABlockSize,    // ULONG length
                                                                     dev->DmaRead[i].physAddr, // PHYSICAL_ADDRESS
                                                                     dev->DmaRead[i].pData,  //PVOID VirtualAddress
                                                                     FALSE); // Boolean cacheEnabled.
                    dev->DmaRead[i].pData = NULLPTR;
                }
                if (dev->DmaWrite[i].pData != NULLPTR)
                {
                    dev->dmaAdapter->DmaOperations->FreeCommonBuffer(dev->dmaAdapter,         //PDMA_ADAPTER 
                                                                    dev->MaxDMABlockSize,    // ULONG length
                                                                    dev->DmaWrite[i].physAddr, // PHYSICAL_ADDRESS
                                                                    dev->DmaWrite[i].pData,  //PVOID VirtualAddress
                                                                    FALSE); // Boolean cacheEnabled.
                    dev->DmaWrite[i].pData = NULLPTR;
                }
            }
        }

        ScgtDbgPrint(SCGT_DBG_HALT, "Release DMA adapter.\n");
        (*dev->dmaAdapter->DmaOperations->PutDmaAdapter)(dev->dmaAdapter);
        dev->dmaAdapter = NULLPTR;
    }

    if (dev->gtMemPhysAddr.QuadPart != 0)
        dev->gtMemPhysAddr.QuadPart = 0;


    ScgtDbgPrint(SCGT_DBG_HALT, "scgtReturnResources() - }\n");
} //scgtReturnResources


/**************************************************************************//**
 * @fn  void scgtRemoveDevice( _In_ PDEVICE_OBJECT deviceObject)
 *
 * @brief  Stop SCGT resources and remove device.
 *
 * @param  deviceObject   The device object.
******************************************************************************/
void scgtRemoveDevice(_In_ PDEVICE_OBJECT deviceObject)
{
    NTSTATUS ret = STATUS_SUCCESS;
    scgtDevice *dev = NULLPTR;
    UNICODE_STRING usableSymLinkName;
    usableSymLinkName.Length = 0;

    ScgtDbgPrint(SCGT_DBG_FLOW, "scgtRemoveDevice() -  {\n");

    //return SCGT device resources
    scgtStopDevice(deviceObject);

    dev = (scgtDevice *)deviceObject->DeviceExtension;

    /* delete symbolic link for our device */
    ret = scgtBuildDevNameString(SCGT_SYMLINK_BASE_NAME, dev->unitNum, &usableSymLinkName);
    if (NT_SUCCESS(ret))
    {
        IoDeleteSymbolicLink(&usableSymLinkName);
    }
    // Free UNICODE string allocated in scgtBuildDevNameString 
    RtlFreeUnicodeString(&usableSymLinkName);

    IoDetachDevice(dev->lowerDeviceInStack);
    IoDeleteDevice(deviceObject);

    ScgtDbgPrint(SCGT_DBG_FLOW, "scgtRemoveDevice() -  }\n");
} // scgtRemoveDevice


/**************************************************************************//**
@fn char* scgtSystemStateToStr(_In_ const scgtDevice * dev)
@brief Convert current system state to string form.
@param dev scgtDevice object with current system device state.
@return Character string repressing system state. Removed, surprise removal, never started,
        remove pending, started, stop pending ,stopped. or unknown.
**//**************************************************************************/
char* scgtSystemStateToStr(_In_ const scgtDevice * dev)
{
    static char str[30];
    switch (dev->SystemState)
    {
    case STATE_REMOVED:
        RtlStringCbPrintfA(str, 30, "REMOVED");
        break;
    case STATE_SURPRISE_REMOVED:
        RtlStringCbPrintfA(str, 30, "SURPRISE REMOVED");
        break;
    case STATE_NEVER_STARTED:
        RtlStringCbPrintfA(str, 30, "NEVER STARTED");
        break;
    case STATE_REMOVE_PENDING:
        RtlStringCbPrintfA(str, 30, "REMOVE PENDING");
        break;
    case STATE_STARTED:
        RtlStringCbPrintfA(str, 30, "STARTED");
        break;
    case STATE_STOP_PENDING:
        RtlStringCbPrintfA(str, 30, "STOP PENDING");
        break;
    case STATE_STOPPED:
        RtlStringCbPrintfA(str, 30, "STATE_STOPPED");
        break;
    default:
        RtlStringCbPrintfA(str, 30, "UNKNOWN:0x%x", dev->SystemState);
        break;
    }
    return str;
} // scgtSystemStateToStr

/**************************************************************************//**
 @fn static NTSTATUS scgtCanStopDevice( _In_ scgtDevice * dev)
 @brief See if we are OK with stopping the device at this point.
   We do NOT actually RETURN the resources here.
   We just affirm or deny that we're OK with returning them.
 @param dev pointer to scgtDevice device object
 @return NTSTATUS value. STATUS_SUCCESS or STATUS_UNSUCCESSFUL
**//**************************************************************************/
#ifdef __cplusplus
inline static NTSTATUS scgtCanStopDevice(_In_ scgtDevice * dev) noexcept
#else
inline static NTSTATUS scgtCanStopDevice(_In_ scgtDevice * dev) 
#endif
{
    NTSTATUS ret = STATUS_SUCCESS;
    long count =1;

    count = 1;

    // Check for in process ioctl calls
    count = InterlockedCompareExchange(&dev->requestCount, 0, 0);
    if (count <= 0)
    {
        // no outstanding call set holdNewReqest flag to block new ioctl calls.
        // InterlockedCompareExchange(&dev->holdNewRequests, 1, 0);

        //reset count to zero if count < 0
        InterlockedExchange(&dev->requestCount, 0);
    }
    else
    {
        ret = STATUS_UNSUCCESSFUL;
    }

    // check if number of scgtOpen  open handle calls > number of scgtClose close handle calls indicating
    // user application has open handle to the device.
    if (InterlockedCompareExchange(&dev->numOpenCalls, 0, 0) > 0)
    {
        // Open SCGT software handle.
        ret = STATUS_UNSUCCESSFUL;
    }
    return ret;
} // scgtCanStopDevice

/**************************************************************************//**
@fn static NTSTATUS scgtCanRemoveDevice( IN scgtDevice * dev, IN PIRP irp)
@brief See if we're OK with removing the device at this point.
 We do NOT actually RETURN the resources here... we
 just affirm or deny that we're OK with returning them.
@param dev scgtDevice object pointer
@param irp system IRP
@return NTSTATUS STATUS_SUCCESS or STATUS_UNSUCCESSFUL
**//**************************************************************************/
inline static NTSTATUS scgtCanRemoveDevice( IN scgtDevice * dev, IN PIRP irp)
{
    int count = 0;
    NTSTATUS ret = STATUS_SUCCESS;
    UNREFERENCED_PARAMETER(irp);

    //count = InterlockedDecrement(&dev->requestCount);
    count = InterlockedCompareExchange(&dev->requestCount, 0, 0);
    if (count <= 0)
    {
        //reset count to zero if count < 0
        InterlockedExchange(&dev->requestCount, 0);
        ret = STATUS_SUCCESS;
    }
    else
    {
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtCanRemoveDevice() Request count:%d > 0", count);
        ret = STATUS_UNSUCCESSFUL;
    }

    // check if number of scgtOpen  open handle calls > number of scgtClose close handle calls indicating
    // user application has open handle to the device.
    count = InterlockedCompareExchange(&dev->numOpenCalls, 0, 0);
    if (count > 0)
    {
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtCanRemoveDevice() Number of open calls :%d > 0", count);
        // Open SCGT software handle.
        ret = STATUS_UNSUCCESSFUL;
    }
    return ret;
} // scgtCanRemoveDevice

/*************************************************************************//**
 * @fn NTSTATUS scgtPnpDefaultCase(_In_ scgtDevice* dev, _In_ PIRP irp)
 * @brief Default set of operations for IRP_MJ_PNP cases
 * @param dev  Pointer to SCGT device object
 * @param irp  Pointer to IRP
 * @return NTSTATUS value.
 *//*************************************************************************/
NTSTATUS scgtPnpDefaultCase(_In_ scgtDevice* dev, _In_ PIRP irp)
{
    NTSTATUS ret = STATUS_SUCCESS;
    PIO_STACK_LOCATION  ioStackLocation = NULLPTR;

    ScgtDbgPrint(SCGT_DBG_PNP, "scgtPnpDefaultCase() \n");
    ioStackLocation = IoGetCurrentIrpStackLocation(irp);
    ShowPnpIrpName(ioStackLocation->MinorFunction);
    IoSkipCurrentIrpStackLocation(irp);
    ret = IoCallDriver(dev->lowerDeviceInStack, irp);
    return ret;
}

/*************************************************************************//**
 * @fn VOID scgtProcessQueuedRequests( IN scgtDevice* dev)
 * @brief Allow new irp requests to be processed provided system is powered 
 * up and in an appropriate system state.
 * @param dev pointer to SCGT device object.
**//*************************************************************************/
inline VOID scgtProcessQueuedRequests( IN scgtDevice* dev)
{
    //
    // Make sure we are powered up
    //
    if (dev->DevicePowerState != PowerDeviceD0)
    {
        return;
    }
    
    //
    // Make sure we are in a proper PnP state
    //
    if (dev->SystemState < STATE_ALL_BELOW_FAIL) 
    {
        return;
    }

    //
    // Allow the queues to restart.
    //
    scgtUnstallQueues(dev);
}

/**************************************************************************//**
@fn static void scgtWaitForStop( _In_ scgtDevice* dev )
@brief Stall queues and wait for stop event object to be signaled indicating
 it is safe to remove the device.
 Stop event set and cleared inside scgtIoctl function.
@param dev scgtDevice object
**//**************************************************************************/
inline static void scgtWaitForStop(_In_ scgtDevice* dev)
{
    //
    // Keep new requests from being started from the queues
    //
    scgtStallQueues(dev);
    
    if (dev->SystemState == STATE_STARTED)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "ERROR scgtWaitForStop() called in STARTED state.\n");
        return;
    }

    KeWaitForSingleObject(&dev->StopEvent,
        Executive,
        KernelMode,
        FALSE,
        NULLPTR);
}
/* scgtWaitForStop */

/*************************************************************************//**
 * @fn NTSTATUS scgtPnpStartDevice( _In_ PDEVICE_OBJECT deviceObject,
 *                                  _In_ PIRP irp)
 * @brief Process PNP minor code IRP_MN_START_DEVICE command requests.
 * The PnP manager sends this IRP after it has assigned hardware resources,
 * if any, to the device. The device may have been recently enumerated and
 * is being started for the first time, or the device may be restarting after
 * being stopped for resource re-balancing.
 *
 * @brief deviceObject Pointer to device object
 * @brief Irp          pointer to irp.
 * @return NTSTATUS value.
*//*************************************************************************/
NTSTATUS scgtPnpStartDevice(_In_ PDEVICE_OBJECT deviceObject, _In_ PIRP irp)
{
    NTSTATUS ret = STATUS_SUCCESS;
    NTSTATUS retCode = STATUS_SUCCESS;
    PIO_STACK_LOCATION irpStackLoc = NULLPTR;
    // Get a pointer to our(FUNCTIONAL) device object's device extension.
    scgtDevice *dev = (scgtDevice *)deviceObject->DeviceExtension;

    irpStackLoc = IoGetCurrentIrpStackLocation(irp);

    /* Inputs */
    /* the Parameters.StartDevice.AllocatedResources member of the IO_STACK_LOCALTION structure
       points to a CM__RESORCE_LIST describing the hardware resources assigned to the device in raw form.
       The parameter Parameters.StartDevice.AllocatedResourcesTranslated contains a translated version
       of the CM_RESOURCE_LIST_ members. */
       // https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/irp-mn-start-device

    // We're here if we've received an AddDevice() call, but we
    // do not have a set of hardware resources from the PnP Manager.
    //
    // The PnP Manager is now giving us a set of resources, and 
    // asking us to start the device.
    //
    // In this case, we pass the IRP all the way down.  When it's
    // done (and our completion routine is called) we can then
    // read the list of device resources pointed to in the IRP
    // Stack Location.
    
    // The PnP manager sends this IRP at IRQL PASSIVE_LEVEL in the context of a system thread.
    // warning C26477 : Use 'nullptr' rather than 0 or NULL(es.47).
    WARNING_SUPPRESS( 26477)
    _IRQL_limited_to_(PASSIVE_LEVEL);

    ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_START_DEVICE\n");

    if ((dev->SystemState != STATE_NEVER_STARTED) &&
        (dev->SystemState != STATE_STOPPED))
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "IRP_MN_START_DEVICE called while in %s state. Expected STOPED or not started state\n", scgtSystemStateToStr(dev));
        irp->IoStatus.Status = STATUS_INVALID_DEVICE_STATE;
        ret = scgtPnpDefaultCase(dev, irp);
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_START_DEVICE status=%x\n", NtStatusName(ret));
        return ret;
    }

    ret = NotifyBusDriver(dev->lowerDeviceInStack, irp);

    // if lower driver processed the IRP Start the device.
    if (NT_SUCCESS(ret))
    {
        ret = scgtStartDevice(deviceObject, irpStackLoc);
        // If the our StartDevice function succeeded, the
        // device is now "officially" started!
        if (NT_SUCCESS(ret))
        {
            dev->SystemState = STATE_STARTED;

            // The bus driver is required to power the device on before we receive a start
            dev->DevicePowerState = PowerDeviceD0;

            // Callers of IoSetDeviceInterfaceState must be running at  
            // IRQL = PASSIVE_LEVEL in the context of a system thread. If you 
            // registered a device interface, using  IoRegisterDeviceInterface(),
            // you'll also need to set the  interface state here to ENABLED.  
            // This is done with the following call.
            retCode = IoSetDeviceInterfaceState(&dev->unicodeSymLinkName, TRUE);
            if (retCode != STATUS_SUCCESS)
            {
                ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): linkName: '%S'\n", dev->unicodeSymLinkName.Buffer);
                if (retCode == STATUS_OBJECT_NAME_EXISTS)
                {
                    ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IoSetDeviceInterface call returned STATUS_OBJECT_NAME_EXISTS.\n");
                }
                else if (retCode == STATUS_OBJECT_NAME_NOT_FOUND)
                {
                    ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP() The caller tried to disable a device interface that was not enabled.\n");
                }
                else
                {
                    ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IoSetDeviceInterface State call failed. retCode:0x%x\n", retCode);
                }
            }

            // If we were stopped, our queues were stalled,  we need to 
            // restart them.  We are guaranteed to  be powered up here.
            // TODO
            // scgtProcessQueueRequests(dev);

            // Clear hold request if set.
            scgtUnstallQueues(dev);
        }
        else
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "Error scgtStartDevice call failed. Status:0x%x\n", ret);
        }
    }
    else
    {
        // The bus driver has declines to Start the Device
        ScgtDbgPrint(SCGT_DBG_INFO, "Bus driver declined Start request. Status: %s\n", NtStatusName(ret));
        if  ( ret != STATUS_PENDING) 
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "Error: Expected return value of STATUS_PENDING pending or Success");
            // SDV MarkStartDevice warning "Start device Irp is nod pending" if this path followed.
        }
    }

    irp->IoStatus.Status = ret;
    irp->IoStatus.Information = 0;
    IoCompleteRequest(irp, IO_NO_INCREMENT);
    ScgtDbgPrint(SCGT_DBG_PNP, "IRP_MN_START_DEVICE: status:%s\n", NtStatusName(ret));
    return ret;
}

/*************************************************************************//**
 * @fn NTSTATUS scgtPnpQueryStopDevice( _In_ scgtDevice* dev, _In_ PIRP irp)
 * @brief  Process PNP minor code IRP_MN_QUERY_STOP_DEVICE command requests.
 * The PnP manager sends this IRP to query whether a device can be stopped
 * to rebalanced resources.
 * @brief dev  Pointer to SCGT device object
 * @brief Irp  Pointer to irp.
 * @return NTStatua value.
*//*************************************************************************/
NTSTATUS scgtPnpQueryStopDevice( _In_ scgtDevice* dev, _In_ PIRP irp)
{
    NTSTATUS ret = STATUS_SUCCESS;
    // All PnP drivers must handle this IRP.
    // The PnP manager sends this IRP to query whether a device can be stopped to rebalanced resources.
    // On Windows 98 / Me, the PnP manager also sends this IRP when a device is being disabled.
    //  The PnP manager sends this IRP at IRQL PASSIVE_LEVEL in the context of a system thread.
    // This IRP is handled first by the driver at the top of the device stack and then passed down to each lower driver in the stack.                
    // https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/irp-mn-query-stop-device
    ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_QUERY_STOP_DEVICE\n");

    if (dev->SystemState != STATE_STARTED)
    {
        NTSTATUS ret1 = STATUS_SUCCESS;
        ScgtDbgPrint(SCGT_DBG_ERROR, "SCGT:IRP_MN_QUERY_STOP_DEVICE called while not in started state, State=%s\n",
            scgtSystemStateToStr(dev));
        irp->IoStatus.Status = STATUS_INVALID_DEVICE_STATE;
        //ret = STATUS_INVALID_DEVICE_STATE;
        ret1 = scgtPnpDefaultCase(dev, irp);
        if (ret1 != STATUS_SUCCESS)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "Error dcfiPnpDefault case failed with status:%s\n ", NtStatusName(ret1));
        }
        ScgtDbgPrint(SCGT_DBG_PNP, "  scgtPnpQueryStopDevice() IRP_MN_QUERY_STOP_DEVICE: status:%s }\n", NtStatusName(ret));
        //return ret;
        return STATUS_SUCCESS;
    }

    // See if we're OK with stopping the device at this point.
    // We do NOT actually RETURN the resources here... we
    // just affirm or deny that we're OK with returning them.
    ret = scgtCanStopDevice(dev);
    ScgtDbgPrint(SCGT_DBG_PNP, "    scgtCanStopDevice returned %s\n", NtStatusName(ret));

    // Replace status code that's in the IRP to indicate our
    // opinion about stopping the device.  If we're
    // OK with returning the resources, this has to be 
    // STATUS_SUCCESS. If we are not OK about stopping
    // we set status to an appropriate error status.  
    irp->IoStatus.Status = ret;

    if ( ret != STATUS_SUCCESS )
    {
        // NOPE.  Can't stop the device because, for some
        // reason (perhaps because we can't return our
        // resources).  Too bad. Tell the PnP Manager that
        // stopping right now is not an option for us.
        //
        // NOTE: NO NEED to pass IRP down if WE can't stop
        // it doesn't matter if the Bus Driver can.
        //
        ScgtDbgPrint(SCGT_DBG_PNP, "IRP_MN_QUERY_STOP_DEVICE Unable to stop at this point.\n");
        IoCompleteRequest(irp, IO_NO_INCREMENT);
    }
    else
    {
        // We CAN stop our device and return the resources.
        // Pass the IRP down to see if the bus driver is
        // equally amenable to the request.
        ScgtDbgPrint(SCGT_DBG_PNP, "Agreeing to stop device.\n");

        // Set new state. This state results in no new requests being started on the device, but incoming
        // requests are still allowed and queued.
        dev->SystemState = STATE_STOP_PENDING;

        // We meed to make sure that any new requests are
        // queued and nothing new is started.
        scgtStallQueues(dev);

        // Pass this request on down to the bus driver
        IoSkipCurrentIrpStackLocation(irp);

        ret = IoCallDriver(dev->lowerDeviceInStack, irp);
    }
    ScgtDbgPrint(SCGT_DBG_PNP, "IRP_MN_QUERY_STOP_DEVICE: status:%s\n", NtStatusName(ret));
    return ret;
}

/*************************************************************************//**
 * @fn NTSTATUS scgtPnpStopDevice( _In_ PDEVICE_OBJECT deviceObject, PIRP irp)
 * @brief Process PNP minor code IRP_MN_STOP_DEVICE command requests.
 *  The PnP manager sends this IRP to stop a device so it can reconfigure the
 *  device's hardware resources.
 * @brief deviceObject  Pointer to  device object
 * @brief Irp           Pointer to irp.
 * @return NTStatua value.
*//*************************************************************************/
NTSTATUS scgtPnpStopDevice(_In_ PDEVICE_OBJECT deviceObject, PIRP irp)
{
    NTSTATUS ret = STATUS_SUCCESS;
    // Get a pointer to our(FUNCTIONAL) device object's device extension.
    scgtDevice *dev = (scgtDevice *)deviceObject->DeviceExtension;

    // All PnP drivers must handle this IRP.

    // The PnP manager sends this IRP to stop a device so it can reconfigure the 
    // device's hardware resources.
    // On Windows 2000 and later systems, the PnP manager sends this IRP only if
    // a prior IRP_MN_QUERY_STOP_DEVICE completed successfully.
    //https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/irp-mn-stop-device


    // This IRP is handled first by the driver at the top of the device stack and then passed
    // down to each lower driver in the stack.
    //  In response to this IRP, Windows 2000 and later drivers stop the device and release any 
    // hardware resources being used by the device, such as I / O ports and interrupts.
    // On Windows 2000 and later, a stop IRP is used solely to free a device's hardware resources
    // so they can be reconfigured. Once the resources are reconfigured, the device is restarted.
    // A stop IRP is not a precursor to a remove IRP. 
    // See Plug and Play for more information about the order in which PnP IRPs are sent to devices.
    // A driver must not fail this IRP. If a driver cannot release the device's hardware resources, 
    // it must fail the preceding query-stop IRP.
    // https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/stopping-a-device
    // Handling Stop IRPs(Windows 2000 and Later)
    //https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/handling-stop-irps--windows-2000-and-later-
    // Handling an IRP_MN_STOP_DEVICE Request(Windows 2000 and later)
    // https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/handling-an-irp-mn-stop-device-request--windows-2000-and-later-

    ScgtDbgPrint(SCGT_DBG_PNP, ("scgtIoctlPNP(): IRP_MN_STOP_DEVICE\n"));

    if (dev->SystemState != STATE_STOP_PENDING)
    {
        // Should not be in this branch
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtIoctlPNP(): IRP_MN_STOP_DEVICE called in unexpected state: %s.\n",
            scgtSystemStateToStr(dev));
        ScgtDbgPrint(SCGT_DBG_ERROR, "Expected device to be in STOP pending state.\n");
        irp->IoStatus.Status = STATUS_INVALID_DEVICE_STATE;
        ret = scgtPnpDefaultCase(dev, irp);
        return ret;
    }

    // 1.Ensure that the device is paused.
    // If a driver did not completely pause the device in response to the IRP_MN_QUERY_STOP_DEVICE request, 
    // it must do so now. Set a HOLD_NEW_REQUESTS flag in the device extension and perform any other 
    // necessary operations to pause the device.

    ScgtDbgPrint(SCGT_DBG_PNP, "Waiting for in process requests to complete\n");

    // Stop new IOCTL request and wait for existing IOCTL calls to complete.
    ret = scgtStopIO(dev, 10000);
    if (ret == STATUS_TIMEOUT)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "Unable to stop IO within timeout period of 10 Sec.\n");
        // cannot fail IRP_MN_STOP_DEVICE
        // set StopEvent to prevent hangup in scgtWaitForStop Call.
        KeSetEvent(&dev->StopEvent, IO_NO_INCREMENT, FALSE);
    }

    ScgtDbgPrint(SCGT_DBG_PNP, "Waiting for stop event.\n");
    // Wait until all active requests on the device have
    // completed...
    scgtWaitForStop(dev);

    // The device might lose power during the resource - re balance 
    // operation and thus might lose device state.  Drivers for the device 
    // should save any device state information and restore it 
    // when they receive the subsequent IRP_MN_START_DEVICE request.

    dev->SystemState = STATE_STOPPED;

    // 2.Release the hardware resources for the device.
    // Return any resources we're using.
    ScgtDbgPrint(SCGT_DBG_PNP, "Return resources\n");
    scgtReturnResources(dev);

    // A driver must set Irp->IoStatus.Status to STATUS_SUCCESS.  We cannot fail
    // this IRP.If we don't want to stop we need to indicate it on the _QUERY_STOP
    // 3.Set Irp->IoStatus.Status to STATUS_SUCCESS.
    irp->IoStatus.Status = STATUS_SUCCESS;

    // Pass this request on down to the bus driver
    IoSkipCurrentIrpStackLocation(irp);
    ret = IoCallDriver(dev->lowerDeviceInStack, irp);
    ScgtDbgPrint(SCGT_DBG_PNP, "IRP_MN_STOP_DEVICE: status = %s\n", NtStatusName(ret));

    return ret;
}

/*************************************************************************//**
 * @fn NTSTATUS scgtPnpCancelStopDevice(_In_ scgtDevice* dev, _In_ PIRP irp)
 * @brief Process PNP minor code IRP_MN_CANCEL_STOP_DEVICE command requests.
 * The PnP manager sends this IRP, at some point after an IRP_MN_QUERY_STOP_DEVICE,
 * to inform the drivers for a device that the device will not be disabled
 * @brief deviceObject  Pointer to  device object
 * @brief Irp           Pointer to irp.
 * @return NTStatua value.
*//*************************************************************************/
NTSTATUS scgtPnpCancelStopDevice(_In_ scgtDevice* dev, _In_ PIRP irp)
{
    NTSTATUS ret = STATUS_SUCCESS;
    // All PnP drivers must handle this IRP.
    // The PnP manager sends this IRP, at some point after an IRP_MN_QUERY_STOP_DEVICE, 
    // to inform the drivers for a device that the device will not be disabled
    // (Windows 98 / Me only) or stopped for resource reconfiguration.
    // https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/irp-mn-cancel-stop-device

    // A driver must set Irp->IoStatus.Status to STATUS_SUCCESS for this IRP.
    // If a driver fails this IRP, the device is left in an inconsistent state.

    ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_CANCEL_STOP_DEVICE\n");

    if (dev->SystemState != STATE_STOP_PENDING)
    {
        // some upper level device must have vetoed the stop Query before it was passed to us.
        ScgtDbgPrint(SCGT_DBG_PNP, "IRP_MN_CANCEL_STOP_DEVICE received while device in state %s\n", scgtSystemStateToStr(dev));
        ScgtDbgPrint(SCGT_DBG_ERROR, "Expected device to be in STOP PENDING state\n");
        //irp->IoStatus.Status = STATUS_INVALID_DEVICE_STATE;
        ret = scgtPnpDefaultCase(dev, irp);
        return ret;
    }

    // We are here because we have already received a QUERY_STOP that
    // we have agreed to.  We have completed any pending I/O requests.
    // Now we have received a CANCEL_STOP_DEVICE IRP, that sort of
    // says never mind about that stop.
    // We restart our queues and return to the STARTED state.

    // The underlying BUS driver must handle this request before we do.
    // we need to wait for the BUS driver to get restarted before we can
    // get restarted.

    // pass IRP to parent device.
    ret = NotifyBusDriver(dev->lowerDeviceInStack, irp);
    if ( ret != STATUS_SUCCESS )
    { 
       ScgtDbgPrint(SCGT_DBG_ERROR, "Error lower driver failed IRP_MN_QUERY_STOP_DEVICE, command\n");
    }

    // We are now in the STARTED state.
    dev->SystemState = STATE_STARTED;

    // We aren't really getting stopped, so start the queues back up.
    scgtProcessQueuedRequests(dev);

    // A driver must set Irp->IoStatus.Status to STATUS_SUCCESS for this IRP.
    // If a driver fails this IRP, the device is left in an inconsistent state.
    ret = STATUS_SUCCESS;
    //SDV: The dispatch routine should not return STATUS_SUCCESS if the lower driver fails the IRP  
    irp->IoStatus.Status = ret;
    irp->IoStatus.Information = 0;
    IoCompleteRequest(irp, IO_NO_INCREMENT);
    ScgtDbgPrint(SCGT_DBG_PNP, "  scgtPnpCancelStopDevice() status:%s }\n", NtStatusName(ret));
    return ret;
}


/*************************************************************************//**
 * @fn NTSTATUS scgtPnpQueryRemoveDevice(_In_ scgtDevice* dev, _In_ PIRP irp)
 * @brief Process PNP minor code IRP_MN_QUERY_REMOVE_DEVICE command requests.
 * The PnP manager sends this IRP to inform drivers that a device is about
 * to be removed from the machine and to ask whether the device can be
 * removed without disrupting the machine.It also sends this IRP when a user
 * requests to update drivers for the device
 * @brief deviceObject  Pointer to  device object
 * @brief Irp           Pointer to irp.
 * @return NTSTATUS value.
*//*************************************************************************/
NTSTATUS scgtPnpQueryRemoveDevice(_In_ scgtDevice* dev, _In_ PIRP irp)
{
    NTSTATUS ret = STATUS_SUCCESS;
    // All PnP drivers must handle this IRP.
    // https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/irp-mn-query-remove-device

    ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_QUERY_REMOVE_DEVICE\n");

    // The PnP manager sends this IRP to inform drivers that a device is about
    // to be removed from the machine and to ask whether the device can be 
    // removed without disrupting the machine.It also sends this IRP when a user 
    // requests to update drivers for the device
    // https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/handling-an-irp-mn-query-remove-device-request

   /*
   1.Determine whether the device can be removed from the machine without disrupting operation.
     A driver must fail a query - remove IRP if any of the following are true:
       •If removing the device could result in losing data.
       •If a component has an open handle to the device. (This is an issue on Windows 98 / Me only.Windows 2000
       and later versions of Windows track open handles and fail the query if there are open handles after the
       IRP_MN_QUERY_REMOVE_DEVICE completes.)
       •If a driver has been notified(through an IRP_MN_DEVICE_USAGE_NOTIFICATION IRP) that
       the device is in the path for a paging, crash dump, or hibernation file.
       •If the driver has an outstanding interface reference against the device.That is,
       the driver provided an interface in response to an IRP_MN_QUERY_INTERFACE request and the interface has
       not been dereferenced.

    2.If the device cannot be removed, fail the query-remove IRP.
    Set Irp->IoStatus.Status to an appropriate error status (typically STATUS_UNSUCCESSFUL),
    call IoCompleteRequest with IO_NO_INCREMENT, and return from the driver's DispatchPnP routine.
    Do not pass the IRP to the next lower driver.

    3.If the driver previously sent an IRP_MN_WAIT_WAKE request to enable the device for wake-up, cancel the wait-wake IRP.

    4.Record the previous PnP state of the device.
    A driver should record the PnP state that the device was in when the driver received the IRP_MN_QUERY_REMOVE_DEVICE
    request because the driver must return the device to that state if the query is canceled (IRP_MN_CANCEL_REMOVE_DEVICE).
    The previous state is typically "started", which is the state that the device enters when the driver successfully
    completes an IRP_MN_START_DEVICE request. However, other previous states are possible. For example, the user might
    have disabled the device through Device Manager. Or, in response to an IRP_MN_QUERY_CAPABILITIES request, the parent
    bus driver (or a filter driver on the bus driver) might have reported that the device's hardware is disabled.
    In either case, the driver for the disabled device can receive an IRP_MN_QUERY_REMOVE_DEVICE request before it receives
    an IRP_MN_START_DEVICE request.

    5.Finish the IRP:
    In a function or filter driver:
       •Set Irp->IoStatus.Status to STATUS_SUCCESS.
       •Set up the next stack location with IoSkipCurrentIrpStackLocation and pass the IRP to the next lower driver with IoCallDriver.
       •Propagate the status from IoCallDriver as the return status from the DispatchPnP routine.
       •Do not complete the IRP.

    In a bus driver:
       •Set Irp->IoStatus.Status to STATUS_SUCCESS.
       •Complete the IRP (IoCompleteRequest) with IO_NO_INCREMENT.
       •Return from the DispatchPnP routine.

    If any driver in the device stack fails an IRP_MN_QUERY_REMOVE_DEVICE,
    the PnP manager sends an IRP_MN_CANCEL_REMOVE_DEVICE to the device stack.
    This prevents drivers from requiring an IoCompletion routine for a query-remove
    IRP to detect whether a lower driver failed the IRP.
    Once a driver succeeds an IRP_MN_QUERY_REMOVE_DEVICE and it considers the device to be
    in the remove-pending state, the driver must fail any subsequent create requests for the
    device. The driver processes all other IRPs as usual, until the driver receives an
    IRP_MN_CANCEL_REMOVE_DEVICE or an IRP_MN_REMOVE_DEVICE.
    */

    if ((dev->SystemState != STATE_STARTED) &&
        (dev->SystemState != STATE_STOPPED))
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "Error IRP_MN_QUERY_REMOVE_DEVICE called in system State %s\n", scgtSystemStateToStr(dev));
        ScgtDbgPrint(SCGT_DBG_ERROR, "Expected to be in STARTED or STOPED state.\n");
        // todo   What should we do here.
        irp->IoStatus.Status = STATUS_INVALID_DEVICE_STATE;
        ret = scgtPnpDefaultCase(dev, irp);
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_QUERY_REMOVE_DEVICE: status = %s\n", NtStatusName(ret));
        return ret;
    }

    // See if its OK to stop the device
    // See if we're OK with removing the device at this point.
    // We do NOT actually RETURN the resources here... we
    // just affirm or deny that we're OK with returning them.            
    ret = scgtCanRemoveDevice(dev, irp);
    ScgtDbgPrint(SCGT_DBG_PNP, "   scgtCanRemoveDevice() returned %s\n", NtStatusName(ret));

    //
    // Replace status code that's in the IRP to indicate our
    // opinion about stopping the device.  If we're
    // OK with returning the resources, this has to be
    // STATUS_SUCCESS.
    //
    irp->IoStatus.Status = ret;

    if (!NT_SUCCESS(ret))
    {
        ScgtDbgPrint(SCGT_DBG_PNP, "   Can not remove device at this time.\n");
        //
        // NOPE.  Can't remove the device because, for some reason (perhaps because we can't return our
        // resources).  Too bad. Tell the PnP Manager that stopping right now is not an option for us.
        //
        // NOTE: NO NEED to pass IRP down if WE can't stop it doesn't matter if the Bus Driver can.
        IoCompleteRequest(irp, IO_NO_INCREMENT);
        return ret;
    }

    // Save the old state in case we get a CANCEL_REMOVE, then we know what to return to.
    dev->QueryRemoveState = dev->SystemState;

    // Set new state -- This state results in any new requests received at our create 
    // dispatch entry point being REJECTED... any other IRPs are handled as usual,  however.       
    dev->SystemState = STATE_REMOVE_PENDING;

    // Decrement our reference on the device here, stall the queues and then 
    // wait until there are no requests active on the device.

    scgtRequestDecrement(dev);

    // set the holdReqest flag if not set.
    scgtStallQueues(dev);

    ScgtDbgPrint(SCGT_DBG_PNP, "  Call scgtStopIO to stop active IO\n");
    ret = scgtStopIO(dev, 1000);
    if (!NT_SUCCESS(ret))
    {
        if (ret == STATUS_TIMEOUT)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "timeout waiting for IO to stop\n");
        }
        ScgtDbgPrint(SCGT_DBG_ERROR, "Error Unable to Stop IO. scgtStopIO returned ntStatus:%s\n", NtStatusName(ret));
        scgtUnstallQueues(dev);
        //
        // NOPE.  Can't remove the device because, for some reason (perhaps because we can't return our
        // resources).  Too bad. Tell the PnP Manager that stopping right now is not an option for us.
        //
        // NOTE: NO NEED to pass IRP down if WE can't stop it doesn't matter if the Bus Driver can.
        IoCompleteRequest(irp, IO_NO_INCREMENT);

        // increment count so we don't have to skip the decrement latter.
        scgtRequestIncrement(dev);
        ScgtDbgPrint(SCGT_DBG_PNP, "  scgtPnpQueryRemoveDevice() IRP_MN_QUERY_REMOVE_DEVICE: status = %s \n", NtStatusName(ret));
        return ret;
    }

    //  DLC  scgtWaitForStop(dev);

    // OK no active requests remain. Let the bus driver know.

    // increment count so we don't have to skip the decrement latter.
    scgtRequestIncrement(dev);

    // Pass this request on down to the bus driver
    //
    IoSkipCurrentIrpStackLocation(irp);

    // Pass this request on down to the BUS driver
    ret = IoCallDriver(dev->lowerDeviceInStack, irp);
    if (!NT_SUCCESS(ret))
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "IRP_MN_QUERY_REMOVE_DEVICE IoCallDriver call failed\n");
    }
    ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP() IRP_MN_QUERY_REMOVE_DEVICE: status = %s\n", NtStatusName(ret));
    return ret;
} //scgtPnpQueryRemoveDevice

/**************************************************************************//**
 * @fn NTSTATUS scgtPnpRemoveDevice( _In_ PDEVICE_OBJECT deviceObject,
*                                   _In_ PIRP irp)
 * @brief Process PNP minor code IRP_MN_REMOVE_DEVICE command requests.
 * The PnP manager uses this IRP to direct drivers to remove a device's
 * software representation (device objects, and so forth). The PnP manager
 * sends this IRP when a device has been removed in an orderly fashion
 *  (for example, initiated by a user in the Unplug or Eject Hardware program),
 * by surprise (a user pulls the device from its slot without prior warning),
 * or when the user requests to update driver(s).
 * https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/irp-mn-remove-device
 * @param deviceObject Pointer to device object
 * @param irp          pointer to irp.
 * @return NTSTATUS value.
*//**************************************************************************/
NTSTATUS scgtPnpRemoveDevice(_In_ PDEVICE_OBJECT deviceObject, _In_ PIRP irp)
{
    // All PnP drivers must handle this IRP.  
    // On Windows 2000 and later systems, the PnP manager also sends this IRP 
    // if one of the drivers in the device stack fails an IRP_MN_START_DEVICE 
    // request for the device.

   // For an orderly device removal, the PnP manager sends an 
    // IRP_MN_QUERY_REMOVE_DEVICE prior to the remove IRP. In this case, 
    // the device is in the remove - pending state when the remove IRP 
    // arrives. For a surprise device removal on Microsoft Windows 2000 or 
    // later, the PnP manager sends an IRP_MN_SURPRISE_REMOVAL prior to the
    // remove IRP. In this case, the device is in the surprise - removed state
    // when the remove IRP arrives. Drivers can also receive a remove IRP 
    // before a device is started. In this case, the device is in the 
    // non - started state when the IRP arrives.

    NTSTATUS ret = STATUS_SUCCESS;
    NTSTATUS retCode = STATUS_SUCCESS;
    long count = 0;
    UNICODE_STRING usableSymLinkName = { 0,0,NULLPTR };
    //POWER_STATE powerState;

    // Get a pointer to our(FUNCTIONAL) device object's device extension.
    scgtDevice *dev = (scgtDevice *)deviceObject->DeviceExtension;

    ScgtDbgPrint(SCGT_DBG_PNP, "  scgtIoctlPNP(): IRP_MN_REMOVE_DEVICE. State:%s\n",
        scgtSystemStateToStr(dev));

    // The PnP manager sends this IRP at IRQL PASSIVE_LEVEL in the context of a system thread.
    // warning C26477 : Use 'nullptr' rather than 0 or NULL(es.47).
    WARNING_SUPPRESS (26477)
    _IRQL_limited_to_(PASSIVE_LEVEL);

    if ((dev->SystemState != STATE_STOPPED) &&
        (dev->SystemState != STATE_SURPRISE_REMOVED) &&
        (dev->SystemState != STATE_STOP_PENDING) &&
        (dev->SystemState != STATE_REMOVE_PENDING) &&
        (dev->SystemState != STATE_STARTED))
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtIoctlPNP(): IRP_MN_REMOVE_DEVICE called in state:%s. Expected state\n", scgtSystemStateToStr(dev));
        ScgtDbgPrint(SCGT_DBG_ERROR, "Stopped, surprise removed, stop pending, remove pending, or stated.\n");
        //TODO
        irp->IoStatus.Status = STATUS_INVALID_DEVICE_STATE;
        ret = scgtPnpDefaultCase(dev, irp);
        scgtRequestDecrement(dev);
        return ret;
    }

    // We're here if we've received an IRP_MN_REMOVE_DEVICE IRP 

    ScgtDbgPrint(SCGT_DBG_PNP, "Processing Remove device. State:%s\n", scgtSystemStateToStr(dev));

    // We handle this request before the bus drive

    // If State == STATE_SURPRISE_REMOVED
    // The only Irps outstanding are going to be power, PnP, clean up, or
    // close Irps. All I/O Irps between the _SURPRISE_REMOVAL and
    // REMOVE_DEVICE have been failed (good thing cause our hardware is gone..)

    if (dev->SystemState != STATE_SURPRISE_REMOVED)
    {
        ScgtDbgPrint(SCGT_DBG_PNP, "IRP_MN_REMOVE_DEVICE -> Disable interface (%S).\n", dev->unicodeSymLinkName.Buffer);
        // IF you registered a device interface, using 
        // IoRegi sterDeviceInterface(), you'll also need
        // to set the interface state here to DISABLED.        
        retCode = IoSetDeviceInterfaceState(&dev->unicodeSymLinkName, FALSE);
        if (retCode != STATUS_SUCCESS)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "IRP_MN_REMOVE_DEVICE -> Error IoSetDeviceInterfaceState call failed. NTSTATUS=%s\n",
                NtStatusName(ret));
        }

        // delete symbolic link for our device 
        ret = scgtBuildDevNameString(SCGT_SYMLINK_BASE_NAME, dev->unitNum, &usableSymLinkName);
        if (NT_SUCCESS(ret))
        {
            IoDeleteSymbolicLink(&usableSymLinkName);
        }
        // Free UNICODE string allocated in scgtBuildDevNameString 
        RtlFreeUnicodeString(&usableSymLinkName);

        // Cancel any pending requests... make sure the active requests get stopped within
        // a second (with no hardware access, as a result of setting the state above).
         scgtClearQueues(dev);

        //look for active requests 
        count = InterlockedCompareExchange(&dev->requestCount,0,0);
        if (count > 0)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "scgtIoctlPNP(): IRP_MN_REMOVE_DEVICE called without hold request active.\n");
            ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_REMOVE_DEVICE called with request count of %d\n", count);
        }
    }

    // Decrement our reference on the device here, 
    scgtRequestDecrement(dev);

    // Wait for all requests to be accounted for     
    count = InterlockedCompareExchange(&dev->OutstandingIO, 0, 0);
    if (count > 0)
    {
        ScgtDbgPrint(SCGT_DBG_PNP, "Waiting for pending requests to complete. Number of outstanding IO = %d\n",
                     count);
    }

    scgtWaitForRemove(dev);

    // Now stop our "watch dog" timer
     //IoStopTimer(devExt->FunctionalDeviceObject);

    // if state == Stopped or Surprise removal there are no resources to release.
    if ((dev->SystemState == STATE_STOP_PENDING) ||
        (dev->SystemState == STATE_REMOVE_PENDING) ||
        (dev->SystemState == STATE_STARTED))
    {
        ScgtDbgPrint(SCGT_DBG_PNP, "  IRP_MN_REMOVE_DEVICE-> return resources\n");
        // Return any resources we're using.
        scgtReturnResources(dev);
    }

    if (dev->SystemState != STATE_SURPRISE_REMOVED)
    {
        // A remove constitutes a power down, we change our internal power state and notify the Power manager.
        ScgtDbgPrint(SCGT_DBG_PNP, "  IRP_MN_REMOVE_DEVICE -> Notify PNP manager changing to D3 state\n");
        dev->DevicePowerState = PowerDeviceD3;
#ifdef PNP_POWER
        powerState.DeviceState = PowerDeviceD3;
        PoSetPowerState(deviceObject, DevicePowerState, powerState);
#endif
    }

    dev->SystemState = STATE_REMOVED;

    // Indicate that we've successfully processed the IRP
    irp->IoStatus.Status = STATUS_SUCCESS;

    // Don't wait for the Irp to finish, don't set a completion 
    // routine and don't complete the Irp! Just foist it off to the
    // PDO and propagate the returned status
    IoSkipCurrentIrpStackLocation(irp);

    ScgtDbgPrint(SCGT_DBG_PNP, "IRP_MN_REMOVE_DEVICE -> Pass Irp down the stack\n");
    // Pass the IRP down to new driver.
    ret = IoCallDriver(dev->lowerDeviceInStack, irp);
    if (!NT_SUCCESS(ret))
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "IRP_MN_REMOVE_DEVICE Error IoCallDriver call failed. NTSTATUS=%s\n", NtStatusName(ret));
    }

    ScgtDbgPrint(SCGT_DBG_PNP, "IRP_MN_REMOVE_DEVICE -> detach PDO and return out device object.\n");
    // Detach from the PDO
    IoDetachDevice(dev->lowerDeviceInStack);

    // Return our device object
    IoDeleteDevice(deviceObject);

    //
    // IMPORTANT: We decremented our I/O In Progress count
    // above... We don't want to decrement it again, so we
    // return right here.
    //
    ScgtDbgPrint(SCGT_DBG_PNP, "  scgtIoctlPNP(): IRP_MN_REMOVE_DEVICE NtStatus: %s\n", NtStatusName(ret));

    return ret;
}

/**************************************************************************//**
 * @fn NTSTATUS scgtPnpCancelRemoveDevice( _In_ PDEVICE_OBJECT deviceObject,
 *                                         _In_ PIRP irp)
 * @brief Process PNP minor code IRP_MN_CANCEL_REMOVE_DEVICE command requests.
 * The PnP manager sends this IRP to inform the drivers for a device that the
 * device will not be removed. The PnP manager sends this IRP at IRQL PASSIVE_LEVEL
 * in the context of a system thread.
 * @param deviceObject Pointer to device object
 * @param irp          Pointer to IRP.
 * @return NTSTATUS value.
 * @todo update cancel logic
 *//*************************************************************************/
NTSTATUS scgtPnpCancelRemoveDevice(_In_ PDEVICE_OBJECT deviceObject, _In_ PIRP irp)
{
    // All PnP drivers must handle this IRP
    // https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/irp-mn-cancel-remove-device

    NTSTATUS ret = STATUS_SUCCESS;

    // Get a pointer to our(FUNCTIONAL) device object's device extension.
    scgtDevice *dev = (scgtDevice *)deviceObject->DeviceExtension;

    ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_CANCEL_REMOVE_DEVICE\n");

    // In response to this IRP, drivers return the device to the state it was in prior to receiving 
    // the IRP_MN_QUERY_REMOVE_DEVICE request

    if (dev->SystemState != STATE_REMOVE_PENDING)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "IRP_NM_CANCEL_REMOVE_DEVICE called in state:%s expected Remove pending state.\n",
            scgtSystemStateToStr(dev));
        //irp->IoStatus.Status = STATUS_INVALID_DEVICE_STATE;
        ret = scgtPnpDefaultCase(dev, irp);
        // Must return status success
        ScgtDbgPrint(SCGT_DBG_PNP, "  scgtPnpCancelRemoveDevice() Status=%s}\n", NtStatusName(ret));
        return STATUS_SUCCESS;;
    }

    ScgtDbgPrint(SCGT_DBG_PNP, "Processing CANCEL REMOVE\n");

    // The Underlying BUS DRIVER must handle these IRPs  before we do
    ret = NotifyBusDriver(dev->lowerDeviceInStack, irp);

    if (dev->QueryRemoveState == STATE_STOPPED)
    {
        dev->SystemState = STATE_STOPPED;
    }
    else
    {
        // were in the started State
        dev->SystemState = STATE_STARTED;

        // We aren't really getting removed, so start the
        // queues back up.
        //
        scgtProcessQueuedRequests(dev);
    }


    // A driver must set Irp->IoStatus.Status to STATUS_SUCCESS for this IRP. 
    // If a driver fails this IRP, the device is left in an inconsistent state.
    // SVN  set to status success but failed driver state in notifyBusDriver call above.
    ret = STATUS_SUCCESS;
    irp->IoStatus.Status = ret;
    irp->IoStatus.Information = 0;
    IoCompleteRequest(irp, IO_NO_INCREMENT);

    ScgtDbgPrint(SCGT_DBG_PNP, "  scgtPnpCancelRemoveDevice() Status=%s}\n", NtStatusName(ret));
    return ret;
}

/************************************************************************//**
 * @fn NTSTATUS scgtPnpSurpriseRemoval( _In_ PDEVICE_OBJECT deviceObject,
 *                                     _In_ PIRP irp)
 * @brief Process PNP minor code IRP_MN_SURPRISE_REMOVAL command requests.
 * The Windows 2000 and later PnP manager sends this IRP to notify drivers
 * that a device is no longer available for I/O operations and has probably
 * been unexpectedly removed from the machine ("surprise removal").
 * @param deviceObject Pointer to device object
 * @param irp          Pointer to IRP.
 * @return NTSTATUS value.
 * @todo implement clear Ques function
*//**************************************************************************/
NTSTATUS scgtPnpSurpriseRemoval(_In_ PDEVICE_OBJECT deviceObject, _In_ PIRP irp)
{
    // All PnP drivers must handle this IRP.
    // https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/irp-mn-surprise-removal

    // https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/handling-an-irp-mn-surprise-removal-request
    // All PnP drivers must handle this IRP and must set Irp->IoStatus.Status
    // to STATUS_SUCCESS.A driver for a PnP device must be prepared to handle 
    // IRP_MN_SURPRISE_REMOVAL at any time after the driver's AddDevice routine
    // is called. 
    // Proper handling of the IRP enables the drivers and the PnP manager to:
    //  1  Disable the device, in case it is still connected.
    //
    //     If the driver stack successfully completed an IRP_MN_STOP_DEVICE 
    //     request but then, for some reason, failed a subsequent 
    //     IRP_MN_START_DEVICE request, the device must be disabled.

    // 2 Release hardware resources assigned to the device and make them available to another device.

    //  As soon as a device is no longer available, its hardware resources 
    //   should be freed.The PnP manager can then reassign the resources to
    //   another device, including the same device, which a user might 
    //   hot - plug back into the machine.

    // 3  Minimize the risk of data loss and system disruption.

    //    Devices that support hot - plugging and their drivers should be 
    //    designed to handle surprise removal.Users expect to be able to
    //    remove devices that support hot - plugging at any time.

     // The Windows 2000 and later PnP manager sends this IRP to notify drivers 
    // that a device is no longer available for I/O operations and has probably 
    // been unexpectedly removed from the machine ("surprise removal").*/
    // https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/handling-an-irp-mn-surprise-removal-request

    NTSTATUS ret = STATUS_SUCCESS;
    NTSTATUS retCode = STATUS_SUCCESS;
    UNICODE_STRING usableSymLinkName = { 0,0,NULLPTR };

    // Get a pointer to our(FUNCTIONAL) device object's device extension.
    scgtDevice *dev = (scgtDevice *)deviceObject->DeviceExtension;

    // The PnP manager sends an IRP_MN_SURPRISE_REMOVAL at IRQL = PASSIVE_LEVEL in the context of a system thread.
    // warning C26477 : Use 'nullptr' rather than 0 or NULL(es.47).
    WARNING_SUPPRESS(26477)
    _IRQL_limited_to_(PASSIVE_LEVEL);

    ScgtDbgPrint(SCGT_DBG_PNP, "scgtPnpSurpriseRemoval(): IRP_MN_SURPRISE_REMOVAL {\n");

    if ((dev->SystemState != STATE_STOPPED) &&
        (dev->SystemState != STATE_STARTED) &&
        (dev->SystemState != STATE_STOP_PENDING) &&
        (dev->SystemState != STATE_REMOVE_PENDING))
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "IRP_MN_SURPRISE_REMOVAL called in state:%s\n", scgtSystemStateToStr(dev));
        ScgtDbgPrint(SCGT_DBG_ERROR, "Expected to be in STOPPED, STARTED, STOP_PENDING, or REMOVE_PENDING\n");
        ret = scgtPnpDefaultCase(dev, irp);
        ScgtDbgPrint(SCGT_DBG_PNP, "  IRP_MN_SURPRISE_REMOVAL ntStatus=%s \n", NtStatusName(ret));
        return ret;
    }

    // We're here when the device is stopped, and a device is
    // forcibly removed.  PnP Manager will send us a remove device
    // IRP when we're supposed to actually return our resources
    // and the like.
    //
    // THIS IRP IS NOT SENT ON Win9x. Also note that we're probably 
    // never going to see this IRP in this driver since it would require
    // someone yanking out the PCI card while the machine is running.

    // A surprise removed device must still handle subsequent
    // close, clean-up, power and PnP requests. A removed device
    // handles NO subsequent requests

    // We handle this request before the bus driver

    // IF you registered a device interface, using IoRegisterDeviceInterface(), you'll also need to
    // set the interface state here to DISALBED.
    retCode = IoSetDeviceInterfaceState(&dev->unicodeSymLinkName, FALSE);
    if (!NT_SUCCESS(retCode))
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtIoctlPNP(): IRP_MN_CANCEL_REMOVE_DEVICE IoSetDeviceInterfaceState call failed \n");
    }

    if (dev->SystemState != STATE_STOPPED)
    {
        /* delete symbolic link for our device */
        ret = scgtBuildDevNameString(SCGT_SYMLINK_BASE_NAME, dev->unitNum, &usableSymLinkName);
        if (NT_SUCCESS(ret))
        {
            IoDeleteSymbolicLink(&usableSymLinkName);
        }
        // Free UNICODE string allocated in scgtBuildDevNameString 
        RtlFreeUnicodeString(&usableSymLinkName);
    }

    //
    // Cancel any pending requests... make sure the active requests get
    // stopped within a second (with no hardware access, 
    // as a result of setting the state above).
    
    // TODO
    scgtClearQueues(dev);

    //scgtStallQueues(dev);
    //scgtStopIO(dev, 10000);
    
    if (dev->SystemState != STATE_STOPPED)
    {
        // Return any resources we're using
        // Clear interrupt handlers, memory mappings, etc. symbolic link        
        scgtReturnResources(dev);
    }

    //
    // A remove constitutes a power down, we change
    // our internal power state and notify the Power manager.
    //
    dev->DevicePowerState = PowerDeviceD3;
#ifdef PNP_POWER
    powerState.DeviceState = PowerDeviceD3;
    PoSetPowerState(DeviceObject, DevicePowerState, powerState);
#endif

    dev->SystemState = STATE_SURPRISE_REMOVED;

    // All PnP drivers must handle this IRP and must set Irp->IoStatus.Status to STATUS_SUCCESS 
     // We're happy... sort of.  Note that it's not "legal" to fail this request.  After all, what would that MEAN?
    irp->IoStatus.Status = STATUS_SUCCESS;

    // Pass this request on down to the bus driver
    IoSkipCurrentIrpStackLocation(irp);

    ret = IoCallDriver(dev->lowerDeviceInStack, irp);
    ScgtDbgPrint(SCGT_DBG_PNP, "IRP_MN_SURPRISE_REMOVAL status=%s", NtStatusName(ret));
    return ret;
}

/*************************************************************************//**
 * @fn NTSTATUS scgtPnpQueryDeviceRelations( _In_ scgtDevice* dev,
 *                                           _In_ PIRP irp)
 * @brief Process PNP minor code IRP_MN_QUERY_DEVICE_RELATIONS command requests.
 * The PnP manager sends this request to determine certain relationships among devices.
 * @param dev  Pointer to SCGT device object
 * @param irp  Pointer to IRP
 * @return NTSTATUS value.
 * @todo should we handle IRP_MN_QUERY_DEVICE_RELATIONS RemovalRelation or power relations?
*//**************************************************************************/
NTSTATUS scgtPnpQueryDeviceRelations(_In_ scgtDevice* dev, _In_ PIRP irp)
{
    // The PnP manager sends this request to determine certain relationships among devices.
    // The following types of drivers handle this request :
    //  •Bus drivers must handle BusRelations requests for their adapter or controller(bus FDO).
    //   Filter drivers might handle BusRelations requests.
    //  •Bus drivers must handle TargetDeviceRelation requests for their child devices(child PDOs).
    //  •Function and filter drivers might handle RemovalRelations and PowerRelations requests.           
    //  •Bus drivers might handle EjectionRelations requests for their child devices(child PDOs).
    // https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/irp-mn-query-device-relations

    // TODO should we handle RemovalRelation or power relations?

    NTSTATUS ret = STATUS_SUCCESS;
    PIO_STACK_LOCATION irpStackLoc = NULLPTR;
    irpStackLoc = IoGetCurrentIrpStackLocation(irp);

    ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_QUERY_DEVICE_RELATIONS\n");
    switch (irpStackLoc->Parameters.QueryDeviceRelations.Type)
    {
        case BusRelations:
            ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_QUERY_DEVICE_RELATIONS BusRelations\n");
            break;
        case EjectionRelations:
            ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_QUERY_DEVICE_RELATIONS EjectionRelations\n");
            break;
        case RemovalRelations:
            ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_QUERY_DEVICE_RELATIONS RemovalRelations\n");
            break;
        case TargetDeviceRelation:
            ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_QUERY_DEVICE_RELATIONS TargetDeviceRelation\n");
            break;
        case PowerRelations:
            ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_QUERY_DEVICE_RELATIONS PowerRelations\n");
            break;
        case SingleBusRelations:
            ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_QUERY_DEVICE_RELATIONS SingleBusRelations\n");
            break;
        case TransportRelations:
            ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_QUERY_DEVICE_RELATIONS TransportRelations\n");
            break;
    }
    IoSkipCurrentIrpStackLocation(irp);
    ret = IoCallDriver(dev->lowerDeviceInStack, irp);
    if (ret != STATUS_SUCCESS)
    {
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtPnpQueryDeviceRelations. IoCallDriver returned %s \n", NtStatusName(ret));
    }
    return ret;
}

/**
 * @fn NTSTATUS scgtPnpDeviceUsageNotification(_In_ scgtDevice* dev, _In_ PIRP irp)
 * @brief Process PNP minor code IRP_MN_DEVICE_USAGE_NOTIFICATION command requests.
 *  System components send this IRP to ask the drivers for a device whether the device can support a
 * special file. Special files include paging files, dump files, and hibernation files.
 * @param dev  Pointer to SCGT device object
 * @param irp  Pointer to IRP
 * @return NTSTATUS value.
*/
inline NTSTATUS scgtPnpDeviceUsageNotification(_In_ scgtDevice* dev, _In_ PIRP irp)
{
    NTSTATUS ret = STATUS_SUCCESS;
    // https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/irp-mn-device-usage-notification
    // System components send this IRP to ask the drivers for a device whether the device can support a 
    // special file. Special files include paging files, dump files, and hibernation files.
    // If all the drivers for the device succeed the IRP, the system creates the special file.
    IoSkipCurrentIrpStackLocation(irp);
    ret = IoCallDriver(dev->lowerDeviceInStack, irp);
    return ret;
}


/**
 * @fn NTSTATUS scgtPnpQueryCapabilites( _In_ scgtDevice* dev, _In_ PIRP irp)
 * @brief Process PNP minor code IRP_MN_QUERY_CAPABILITIES command requests.
 * The PnP manager sends this IRP to get the capabilities of a device, 
 * such as whether the device can be locked or ejected.
 * @param dev  Pointer to SCGT device object
 * @param irp  Pointer to IRP
 * @return NTSTATUS value.
*/
NTSTATUS scgtPnpQueryCapabilites( _In_ scgtDevice* dev, _In_ PIRP irp)
{
    NTSTATUS ret = STATUS_SUCCESS;
    PIO_STACK_LOCATION irpStackLoc = NULLPTR;

    ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_QUERY_CAPABILITIES\n");
    // The PnP manager sends this IRP to get the capabilities of a device, 
    // such as whether the device can be locked or ejected.
    //  Function and filter drivers can handle this request if they alter the capabilities
    // supported by the bus driver. Bus drivers must handle this request for their child devices.
    // https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/irp-mn-query-capabilities

    /* INPUT
    The Parameters.DeviceCapabilities.Capabilities member of the IO_STACK_LOCATION
    structure points to a DEVICE_CAPABILITIES structure containing information about
    the capabilities of the device.
    Output:
    Parameters.DeviceCapabilities.Capabilities points to the DEVICE_CAPABILITIES
    structure that reflects any modifications made by the drivers that handle the IRP.
    */
    irpStackLoc = IoGetCurrentIrpStackLocation(irp);
    if (irpStackLoc == NULLPTR) return STATUS_INVALID_PARAMETER;


    // STATE:   STARTED
    // IRP_MN:  _QUERY_CAPABILITIES
    //
    // We're here if we're running and the PnP Manager or another
    // driver sends us a QUERY_CAPABILITIES request.  This will 
    // happen once the device is enumerated and again once all
    // drivers for the device have started.  A driver can also
    // send this request to get the capabilities for the device.
    //
    // To process this QUERY_CAPABILITIES, we just copy the system
    // to device power mappings.
    if (dev->SystemState == STATE_STARTED)
    {
        // The bus driver needs to get this IRP first.
        ret = NotifyBusDriver(dev->lowerDeviceInStack, irp);

        ScgtDbgPrint(SCGT_DBG_PNP, "Store device states supported by the bus driver\n");
        // copy over the array.  Save list of device state supported by the bus driver
        // for use in SystemPowerIoCompletionRoutine routine when requesting a power IRP.
        WARNING_SUPPRESS(26489)
        RtlCopyMemory(dev->DeviceState,irpStackLoc->Parameters.DeviceCapabilities.Capabilities->DeviceState,
            sizeof(DEVICE_POWER_STATE) * PowerSystemMaximum);

        // We must now complete the IRP, since we stopped it in the
        // completion routine with MORE_PROCESSING_REQUIRED.
        //
        irp->IoStatus.Status = ret;
        irp->IoStatus.Information = 0;
        IoCompleteRequest(irp, IO_NO_INCREMENT);
    }
    else
    {
        IoSkipCurrentIrpStackLocation(irp);
        ret = IoCallDriver(dev->lowerDeviceInStack, irp);
    }
    return ret;
}

/**************************************************************************//**
 * @fn  NTSTATUS scgtIoctlPNP(IN PDEVICE_OBJECT deviceObject, IN PIRP irp)
 *
 * @brief  Process IRP_MJ_PNP commands
 *
 * @param  deviceObject   The device object.
 * @param  irp            The irp.
 *                        Every IRP_MJ_PNP request specifies a minor function code
                          that identifies the requested PNP cation (IRP->minorFunction)
 * @todo check IRP_MN_DEVICE_USAGE_NOTIFICATION logic
 * @return NTSTATUS value. STATUS_SUCESS ,STATUS_UNSUCCESSFUL , STATUS_DELETE_PENDING,
          STATUS_INVALID_DEVICE_STATE, STATUS_TIMEOUT or other value from lower driver.
 *****************************************************************************/
_Use_decl_annotations_
NTSTATUS scgtIoctlPNP(IN PDEVICE_OBJECT deviceObject, IN PIRP irp)
{
    NTSTATUS ret = STATUS_SUCCESS;
    PIO_STACK_LOCATION irpStackLoc = NULLPTR;
    // Get a pointer to our(FUNCTIONAL) device object's device extension.
    scgtDevice *dev = (scgtDevice *)deviceObject->DeviceExtension;

    ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP() - State:%s  {\n", scgtSystemStateToStr(dev) );

    irpStackLoc = IoGetCurrentIrpStackLocation(irp);

    // IN ioctl PNP
    ret = scgtRequestIncrement(dev);
    if (ret != STATUS_SUCCESS)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtIoctlPNP failed to acquire remove lock\n ");
        ShowPnpIrpName(irpStackLoc->MinorFunction);
        irp->IoStatus.Status = ret;
        IoCompleteRequest(irp, IO_NO_INCREMENT);
        ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP() NtStatus:%s - }\n", NtStatusName(ret));
        return ret;
    }

    switch (irpStackLoc->MinorFunction)
    {
        case IRP_MN_START_DEVICE:
            ret = scgtPnpStartDevice(deviceObject, irp);
            break;

        case IRP_MN_QUERY_STOP_DEVICE:
            ret = scgtPnpQueryStopDevice(dev, irp);
            break;

        case IRP_MN_STOP_DEVICE:
            ret = scgtPnpStopDevice(deviceObject, irp);
            break;

        case IRP_MN_CANCEL_STOP_DEVICE:
            ret = scgtPnpCancelStopDevice(dev, irp);
            break;

        case IRP_MN_QUERY_REMOVE_DEVICE:
            ret = scgtPnpQueryRemoveDevice(dev, irp);
            break;

        case IRP_MN_REMOVE_DEVICE:
            ret = scgtPnpRemoveDevice(deviceObject, irp);
            ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP() - NtStatus:%s }\n", NtStatusName(ret));
            return ret;
            break;

        case IRP_MN_CANCEL_REMOVE_DEVICE:
            ret = scgtPnpCancelRemoveDevice(deviceObject, irp);
            break;

        case IRP_MN_SURPRISE_REMOVAL:
            ret = scgtPnpSurpriseRemoval(deviceObject, irp);
            break;

        case IRP_MN_QUERY_DEVICE_RELATIONS:
            ret = scgtPnpQueryDeviceRelations(dev, irp);
            break;

        case IRP_MN_DEVICE_USAGE_NOTIFICATION:
            ret = scgtPnpDeviceUsageNotification(dev, irp);        
            break;

        case IRP_MN_QUERY_INTERFACE:
            ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_QUERY_INTERFACE \n");
            // The IRP_MN_QUERY_INTERFACE request enables a driver to export a direct - call interface to other drivers.
            // https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/irp-mn-query-interface
            // Not processed by scgt driver pass the irp down the stack.
            IoSkipCurrentIrpStackLocation(irp);
            ret = IoCallDriver(dev->lowerDeviceInStack, irp);
            break;

        case IRP_MN_QUERY_CAPABILITIES:
            ret = scgtPnpQueryCapabilites(dev, irp);
            break;

        case IRP_MN_QUERY_PNP_DEVICE_STATE:
            ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_QUERY_PNP_DEVICE_STATE\n");
            // The PnP manager sends this IRP after the drivers for a device return success from the
            // IRP_MN_START_DEVICE request sent when a device is first started.
            // This IRP is not sent on a start after a stop for resource rebalancing.The PnP manager 
            // also sends this IRP when a driver for the device calls IoInvalidateDeviceState.
            // State flags PNP_DEVICE_DISABLED,PNP_DEVICE_DONT_DISPLAY_IN_U, PNP_DEVICE_FAILED,
            //  PNP_DEVICE_NOT_DISABLEABLE, PNP_DEVICE_REMOVED, and PNP_DEVICE_RESOURCE_REQUIREMENTS_CHANGED
            // https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/irp-mn-query-pnp-device-state

            // The SCGT driver does not handle this Irp. Pass it down the stack.
            //TODO 
            // SET PNP_DEVICE_NOT_DISABLEABLE BIT WHEN APPROPRIATE ??
            IoSkipCurrentIrpStackLocation(irp);
            ret = IoCallDriver(dev->lowerDeviceInStack, irp);
            break;

        case IRP_MN_QUERY_DEVICE_TEXT:
            // The PnP manager uses this IRP to get a device's description or location information.
            // Bus drivers must handle this request for their child devices if the bus supports 
            // this information.Function and filter drivers do not handle this IRP.
            // https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/irp-mn-query-device-text

            // Function and filter drivers do not handle this IRP; they pass it to the next lower
            // driver with no changes to Irp->IoStatus.
            ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_QUERY_DEVICE_TEXT\n");
            IoSkipCurrentIrpStackLocation(irp);
            ret = IoCallDriver(dev->lowerDeviceInStack, irp);
            // text string Irp->IoStatus.Information contains requested WCRAR string or zero.
            break;

        case IRP_MN_QUERY_ID:
            // Bus drivers must handle requests for BusQueryDeviceID for their child devices(child PDOs).
            // Bus drivers can handle requests for BusQueryHardwareIDs, BusQueryCompatibleIDs, and BusQueryInstanceID for their child devices.
            // Beginning with Windows 7, bus drivers must also handle requests for BusQueryContainerID for their child PDOs.
            // For more information about these identifiers(IDs), see Device Identification Strings.
            // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/nf-wdm-iogetdeviceproperty
            // Note  Function drivers and filter drivers do not handle this IRP.
            ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_QUERY_ID\n");
            IoSkipCurrentIrpStackLocation(irp);
            ret = IoCallDriver(dev->lowerDeviceInStack, irp);
            break;

        case IRP_MN_FILTER_RESOURCE_REQUIREMENTS:
            ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN_FILTER_RESOURCE_REQUIREMENTS\n");
            // The PnP manager sends this IRP to a device stack so the function driver can adjust 
            // the resources required by the device, if appropriate
            // https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/irp-mn-filter-resource-requirements
            // Not handled by scgt driver. Pass irp down the stack unchanged.
            IoSkipCurrentIrpStackLocation(irp);
            ret = IoCallDriver(dev->lowerDeviceInStack, irp);
            /* some of these include: IRP_MN_QUERY_PNP_DEVICE_STATE */
            break;

        default:
            /* not supported here.. just pass it down the device stack */
            ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP(): IRP_MN default Minor function = 0x%x\n", irpStackLoc->MinorFunction);
            IoSkipCurrentIrpStackLocation(irp);
            ret = IoCallDriver(dev->lowerDeviceInStack, irp);

            /* some of these include: IRP_MN_QUERY_PNP_DEVICE_STATE */
            break;
    };

    //OUT ioctlPNP
    scgtRequestDecrement(dev);
    ScgtDbgPrint(SCGT_DBG_PNP, "scgtIoctlPNP() - NtStatus:%s }\n", NtStatusName(ret));

    return ret;
} //scgtIoctlPNP

/**************************************************************************//**
 * @fn NTSTATUS scgtDeferedIrpCompletion(IN PDEVICE_OBJECT pDO, IN PIRP Irp,
 *                                         IN PVOID Event)
 * @brief  SCGT deferred irp IO_COMPLETION_ROUTINE.
 *
 * @param  pDO   Pointer to device object.
 * @param  Irp   Pointer to IRP.
 * @param  Event Pointer to event object.
 *
 * @return STATUS_UNSUCCESSFUL or STATUS_MORE_PROCESSING_REQUIRED
 *****************************************************************************/
_Use_decl_annotations_
NTSTATUS scgtDeferedIrpCompletion(IN PDEVICE_OBJECT pDO, IN PIRP Irp, IN PVOID Event)
{
    //DLC added UNREFERENCED_PARAMETER to prevent Warning C4100 Unreferenced formal parameter
    UNREFERENCED_PARAMETER(Irp);
    UNREFERENCED_PARAMETER(pDO);

    ScgtDbgPrint(SCGT_DBG_FLOW, "scgtDeferedIrpCompletion() - {\n");
    if (Event == NULLPTR)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtDeferedIrpCompletion called with null event pointer\n");
        return STATUS_UNSUCCESSFUL;
    }

    KeSetEvent((PKEVENT)Event, 0, FALSE);

    ScgtDbgPrint(SCGT_DBG_FLOW, "scgtDeferedIrpCompletion() - }\n");

    return STATUS_MORE_PROCESSING_REQUIRED;
} // scgtDeferedIrpCompletion

/**************************************************************************//**
 * @fn  static NTSTATUS scgtStartDevice( _In_ PDEVICE_OBJECT deviceObject,
                                         _In_ PIO_STACK_LOCATION IoStackLocation)
 *
 * @brief  Start SCGT device. Called in IRP__MN_START_DEVICE case.
 *
 * @param  deviceObject       The device object.
 * @param  IoStackLocation    Io Stack location
 *
 * @return NTSTATUS value. STATUS_SUCCESS or STATUS_UNSUCCESSFUL
 *****************************************************************************/
static NTSTATUS scgtStartDevice(_In_ PDEVICE_OBJECT deviceObject,
                                _In_ PIO_STACK_LOCATION IoStackLocation)
{
    PCM_RESOURCE_LIST pResourceList = NULLPTR;
    PCM_RESOURCE_LIST pResourceListTranslated = NULLPTR;
    PCM_FULL_RESOURCE_DESCRIPTOR  fullResourceDescriptorTranslated = NULLPTR;
    NTSTATUS ret = STATUS_SUCCESS;
    uint32 i = 0;
    int barCount = 0;
    PCM_PARTIAL_RESOURCE_DESCRIPTOR	partialDescriptor = NULLPTR;
    PCM_PARTIAL_RESOURCE_LIST partialList = NULLPTR;
    KINTERRUPT_MODE interruptMode= Latched;
    scgtDevice *dev = (scgtDevice *)deviceObject->DeviceExtension;
    ULONG index = 0;

    ScgtDbgPrint(SCGT_DBG_INIT, "scgtStartDevice() - {\n");

    pResourceList = IoStackLocation->Parameters.StartDevice.AllocatedResources;
    pResourceListTranslated = IoStackLocation->Parameters.StartDevice.AllocatedResourcesTranslated;

    //ASSERT(pResourceList && pResourceListTranslated);
    if ( (pResourceList == NULLPTR ) || ( pResourceListTranslated == NULLPTR) )
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtStartDevice() called with invalid resource list pointer\n");
        return STATUS_UNSUCCESSFUL;
    }

    if (pResourceList->Count == 0) 
    {
        return STATUS_UNSUCCESSFUL;
    }

    fullResourceDescriptorTranslated = &pResourceListTranslated->List[0];

    partialList = &fullResourceDescriptorTranslated->PartialResourceList;
    partialDescriptor = fullResourceDescriptorTranslated->PartialResourceList.PartialDescriptors;
    
    //
    // Walk through the partial resource descriptors to find the
    // hardware resources that have been allocated to us
    //

    for (i = 0; i < partialList->Count; i++)
    {
        switch (partialDescriptor->Type)
        {
            case CmResourceTypeInterrupt:
                dev->interruptVector = partialDescriptor->u.Interrupt.Vector;
                dev->interruptAffinity = partialDescriptor->u.Interrupt.Affinity;
                dev->interruptLevel = partialDescriptor->u.Interrupt.Level;
                
            ScgtDbgPrint(SCGT_DBG_INIT, "Interrupt level: 0x%x, Vector: 0x%x, Affinity: 0x%I64x\n",
                dev->interruptLevel, dev->interruptVector, (UINT64)dev->interruptAffinity);
                
                // CM_RESOURCE_INTERRUPT_LEVEL_SENSITIVE  = 0 
                // so flag & CM_RESOURCE_INTERRUPT_LEVEL_SENSITIVE always true
                // if (partialDescriptor->Flags & CM_RESOURCE_INTERRUPT_LEVEL_SENSITIVE)
                if (partialDescriptor->Flags == CM_RESOURCE_INTERRUPT_LEVEL_SENSITIVE)
                {
                ScgtDbgPrint(SCGT_DBG_INIT, "Interrupt Mode: LevelSensitive\n");
                interruptMode = LevelSensitive;
            }
            else if (partialDescriptor->Flags & CM_RESOURCE_INTERRUPT_LATCHED)
            {
                ScgtDbgPrint(SCGT_DBG_INIT, "Interrupt Mode: Latched\n");
                interruptMode = Latched;
            }
            else
            {
                ScgtDbgPrint(SCGT_DBG_ERROR, "!! Unknown Interrupt Mode !!\n");
                interruptMode = LevelSensitive;
            }

            /* attach ISR */
            ret = IoConnectInterrupt(&dev->interruptObject,       // PKINTERRUPT       *InterruptObject,
                scgtISR,                     // PKSERVICE_ROUTINE ServiceRoutine,
                deviceObject,                // PVOID             ServiceContext
                NULLPTR,                     // PKSPIN_LOCK       SpinLock,
                dev->interruptVector,        // ULONG             Vector,
                (KIRQL)dev->interruptLevel, // KIRQL             Irql,
                (KIRQL)dev->interruptLevel, // KIRQL             SynchronizeIrql
                interruptMode,               // KINTERRUPT_MODE   InterruptMode,
                TRUE,                        //  BOOLEAN          ShareVector,
                dev->interruptAffinity,      //  KAFFINITY        ProcessorEnableMask
                0);                          // BOOLEAN           FloatingSave
            if (NT_SUCCESS(ret))
            {
                ScgtDbgPrint(SCGT_DBG_INIT, "Interrupt Attached\n");
            }
            else
            {
                ScgtDbgPrint(SCGT_DBG_ERROR, "Interrupt Attach Failed!\n");
            }
            break;

        case CmResourceTypeDma:
            ScgtDbgPrint(SCGT_DBG_INIT, "DMA Channel: %x\n", partialDescriptor->u.Dma.Channel);
            break;

        case CmResourceTypeMemory:
            switch (barCount)
            {
            case GTCORE_BAR_C_REGISTERS:
                //DLC Changed to mmMapefIoSpaceEX call. Available in win10 and latter.
#if (NTDDI_VERSION < NTDDI_WIN10)
                dev->cRegPtr = MmMapIoSpace(partialDescriptor->u.Port.Start,
                    partialDescriptor->u.Port.Length, MmNonCached);
#else
                dev->cRegPtr = MmMapIoSpaceEx(partialDescriptor->u.Port.Start,
                    partialDescriptor->u.Port.Length,
                    PAGE_READWRITE | PAGE_NOCACHE);
#endif

                ScgtDbgPrint(SCGT_DBG_INIT, "BAR 1 Start:0x%x len 0x%x\n", partialDescriptor->u.Port.Start.LowPart,
                    partialDescriptor->u.Port.Length);
                break;

            case GTCORE_BAR_NM_REGISTERS:
#if (NTDDI_VERSION < NTDDI_WIN10)
                dev->nmRegPtr = MmMapIoSpace(partialDescriptor->u.Port.Start,
                    partialDescriptor->u.Port.Length,
                    MmNonCached);
#else
                dev->nmRegPtr = MmMapIoSpaceEx(partialDescriptor->u.Port.Start,
                    partialDescriptor->u.Port.Length,
                    PAGE_READWRITE | PAGE_NOCACHE);
#endif
                ScgtDbgPrint(SCGT_DBG_INIT, "BAR 2 Start 0x%x Len 0x%x\n", partialDescriptor->u.Port.Start.LowPart,
                    partialDescriptor->u.Port.Length);
                break;

            case GTCORE_BAR_MEMORY:
                dev->gtMemPhysAddr = partialDescriptor->u.Port.Start;
                dev->memSize = partialDescriptor->u.Port.Length;
                ScgtDbgPrint(SCGT_DBG_INIT, "BAR 0 Start 0x%x Len 0x%x\n", dev->gtMemPhysAddr.LowPart, dev->memSize);
                //DLC replace dev->kenrelMapPtr enrty with dev->memPtr and move maping to scgtStartDevice
#if (NTDDI_VERSION < NTDDI_WIN10)
                dev->memPtr = MmMapIoSpace(partialDescriptor->u.Port.Start,
                    partialDescriptor->u.Port.Length,
                    MmNonCached);
#else
                dev->memPtr = MmMapIoSpaceEx(partialDescriptor->u.Port.Start,
                                             partialDescriptor->u.Port.Length,
                                             PAGE_READWRITE | PAGE_NOCACHE);
#endif                
                for (index = 0; index < MAX_USER_PTRS; index++)
                {
                    dev->userModePtrMem[index] = NULLPTR;
                    dev->userModePtrMdl[index] = NULLPTR;
                    dev->userModePtrPrc[index] = NULLPTR;
                }

                break;
            default:            
                ScgtDbgPrint(SCGT_DBG_ERROR, "Error unexpected bar value. barCount=0x%x\n", barCount);
                break;

            }
            barCount++;
            break;

        case CmResourceTypePort:
            ScgtDbgPrint(SCGT_DBG_INIT, "Port Start: 0x%llx,  Port length: %lu\n",
                partialDescriptor->u.Port.Start.QuadPart, partialDescriptor->u.Port.Length);
            break;

        case CmResourceTypeBusNumber:
            ScgtDbgPrint(SCGT_DBG_INIT, "Bus Number Start:%u \n", partialDescriptor->u.BusNumber.Start);
            break;

        case CmResourceTypeDevicePrivate:
            // BUS specific resource. 
            break;
        default:
            ScgtDbgPrint(SCGT_DBG_INFO, "No handler for resource type %u\n", partialDescriptor->Type);
            break;
        }
        partialDescriptor++;
    }

    if ((dev->cRegPtr == NULLPTR) || (dev->nmRegPtr == NULLPTR) || (dev->memPtr == NULLPTR) )
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "Mapping Failed!\n");
        ret = STATUS_UNSUCCESSFUL;
    }

    ScgtDbgPrint(SCGT_DBG_INIT, "reg: 0x%x, 0x%x, 0x%x\n", scgtReadCReg(dev, 0), scgtReadCReg(dev, 4), scgtReadCReg(dev, 8));

    if (*Mm64BitPhysicalAddress == TRUE)
        dev->Is64BitOS = TRUE;
    else
        dev->Is64BitOS = FALSE;

    ScgtDbgPrint(SCGT_DBG_IS64, "OS is %s bit\n", dev->Is64BitOS ? "64" : "32");

#ifdef useNewDMA     
     dev->Is64BitOS = TRUE;
#endif

    scgtInitDMATools(dev);
    if (scgtInitDMA(dev) != STATUS_SUCCESS)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "SCGT scgtInitDma failed\n");
        ret = STATUS_UNSUCCESSFUL;
    }
    dev->mapData = dev->dmaAdapter;
    scgtInitGetIntrTimer(dev);

    scgtStartThread(dev);

    ScgtDbgPrint(SCGT_DBG_INIT, "calling gtcoreInit()\n");
    gtcoreInit(dev);

    ScgtDbgPrint(SCGT_DBG_INIT, "scgtStartDevice() Status:%s- }\n", NtStatusName(ret));

    return ret;
} // scgtStartDevice


/**************************************************************************//**
 * @fn  static void scgtStopDevice( _In_ PDEVICE_OBJECT deviceObject)
 *
 * @brief  Stop SCGT device resources. Disconnect interrupt object, stop threads,
 * destroy core resource, interrupt timer and DMA tools, umap control and
 * network registers and release DMA adapter.
 *
 * @param  deviceObject   The device object.
 *****************************************************************************/
inline static void scgtStopDevice(_In_ PDEVICE_OBJECT deviceObject)
{
    scgtDevice *dev = (scgtDevice *)deviceObject->DeviceExtension;

    ScgtDbgPrint(SCGT_DBG_FLOW, "scgtStopDevice() - {\n");

    scgtReturnResources(dev);

    ScgtDbgPrint(SCGT_DBG_FLOW, "scgtStopDevice() - }\n");
} //scgtStopDevice

/** @} */

/**************************************************************************/
/** Power management code **/
/**************************************************************************/
/** @defgroup PwrMang SCRAMNet GT Power management code
SCRAMNet GT function for processing power management events.
@{
*/

/**************************************************************************//**
* @fn void scgtPrintShutdownType( _In_ const int ShutdownType)
* @brief Print shutdown action type info
* @param ShutdownType System Shut down type
**//**************************************************************************/
void scgtPrintShutdownType(_In_ const int ShutdownType)
{
    switch (ShutdownType)
    {
    case PowerActionNone:
        ScgtDbgPrint(SCGT_DBG_POWER, "Shutdown type = PowerActionNone \n");
        /* s0 or no system power IRP active*/
        break;
    case PowerActionSleep:
        ScgtDbgPrint(SCGT_DBG_POWER, "Shutdown type = PowerActionSleep (s1,s2 or s3)\n");
        /* S1,s2 of s3*/
        break;
    case PowerActionHibernate:
        ScgtDbgPrint(SCGT_DBG_POWER, "Shutdown type = PowerActionHibernate (s4)\n");
        /* S4 */
        break;
    case PowerActionShutdown:
        ScgtDbgPrint(SCGT_DBG_POWER, "Shutdown type = PowerActionShutdown (s5)\n");
        /* S5 Win 200 and later*/
        break;
    case PowerActionShutdownReset:
        ScgtDbgPrint(SCGT_DBG_POWER, "Shutdown type = PowerActionShutdownReset (s5)\n");
        /* S5 */
        break;
    case PowerActionShutdownOff:
        ScgtDbgPrint(SCGT_DBG_POWER, "Shutdown type = PowerActionShutdownOff (s5)\n");
        /* S5 */
        break;
    default:
        ScgtDbgPrint(SCGT_DBG_WARNING, "Shutdown type = Unknown 0x%x\n", ShutdownType);
        break;
    }
} //scgtPrintShutdownType

/**************************************************************************//**
@fn ULONG SupportedPowerState(_In_ const PIO_STACK_LOCATION irpStackLoc )
@brief Determine if supported power state
@param irpStackLoc IRP stack location
@return number 255 on error. 0, 1, 2, or 3 otherwise.
**//**************************************************************************/
ULONG SupportedPowerState(_In_ const PIO_STACK_LOCATION irpStackLoc)
{
    ULONG supported = 256;
    switch (irpStackLoc->Parameters.Power.Type)
    {
    case SystemPowerState:
        switch (irpStackLoc->Parameters.Power.State.SystemState)
        {
        case PowerSystemUnspecified:
            ScgtDbgPrint(SCGT_DBG_POWER, "System Power State = PowerSystemUnspecified\n");
            /*Indicates an unspecified system power state */
            break;
        case PowerSystemWorking:
            ScgtDbgPrint(SCGT_DBG_POWER, "System Power State = PowerSystemWorking (s0)\n");
            // Indicates maximum system power, which corresponds to system working state S0.
            // CPU fully on. Devices may occupy any power state
            supported = 0;
            break;
        case PowerSystemSleeping1:
            ScgtDbgPrint(SCGT_DBG_POWER, "System Power State = PowerSystemSleeping1 (s1) \n");
            // Indicates a system sleeping state less than PowerSystemWorking and
            // greater than PowerSystemSleeping2, which corresponds to system power state S1.
            // CPU halted. Ram refreshed. 
            supported = 1;
            break;
        case PowerSystemSleeping2:
            ScgtDbgPrint(SCGT_DBG_POWER, "System Power State = PowerSystemSleeping2 (s2) \n");
            // Indicates a system sleeping state less than PowerSystemSleeping1 and
            // greater than PowerSystemSleeping3, which corresponds to system power state S2. 
            // CPU without power. RAM refreshed.
            supported = 2;
            break;
        case PowerSystemSleeping3:
            ScgtDbgPrint(SCGT_DBG_POWER, "System Power State = PowerSystemSleeping3 (s3) \n");
            // Indicates a system sleeping state less than PowerSystemSleeping2 and greater
            // than PowerSystemHibernate, which corresponds to system power state S3. 
            // CPI without power. Ram in slow refresh mode. Power supply output reduced.
            supported = 2;
            break;
        case PowerSystemHibernate:
            ScgtDbgPrint(SCGT_DBG_POWER, "System Power State = PowerSystemHibernate (s4) \n");
            // Indicates the lowest - powered sleeping state, which corresponds to system power state S4.
            // CPU without power. RAM saved to disk.
            supported = 2;
            break;
        case PowerSystemShutdown:
            ScgtDbgPrint(SCGT_DBG_POWER, "System Power State = PowerSystemShutdown (s5) \n");
            /* Indicates the system is turned off, which corresponds to system shutdown state S5.*/
            supported = 3;
            break;
        case PowerSystemMaximum:
            ScgtDbgPrint(SCGT_DBG_ERROR, "ERROR: System Power State = PowerSystemMaximum \n");
            /* The number of system power state values for this enumeration type that represents actual
            power states.This value is the number of elements in the DeviceState member of the
            DEVICE_CAPABILITIES structure for a device.The other system power state values are less
           than this value. */
        default:
            ScgtDbgPrint(SCGT_DBG_ERROR, "ERROR: SystemPowerState: Unkown systemContext: 0x%x \n",
                irpStackLoc->Parameters.Power.State.SystemState);
            ScgtDbgPrint(SCGT_DBG_ERROR, "Expected SYSTEM_POWER_STATE enumerated type value 0 to PowerSystemMaximum:%d \n", PowerSystemMaximum);
            break;
        }
        break;
    
    case DevicePowerState:
        switch (irpStackLoc->Parameters.Power.State.DeviceState)
        {
        case PowerDeviceUnspecified:
            ScgtDbgPrint(SCGT_DBG_POWER, "Device Power State = PowerDeviceUnspecified\n");
            /* Indicates an unspecified device power state */
            break;
        case PowerDeviceD0:
            ScgtDbgPrint(SCGT_DBG_POWER, "Device Power State = PowerDeviceD0\n");
            // Indicates a maximum device power state, which corresponds to device working state D0. 
            // D0 - Device fully on.
            supported = 0;
            break;
        case PowerDeviceD1:
            ScgtDbgPrint(SCGT_DBG_POWER, "Device Power State = PowerDeviceD1\n");
            // Indicates a device sleeping state less than PowerDeviceD0 and greater
            //  than PowerDeviceD2, which corresponds to device power state D1
            // D1 - Device in low power mode. Device content possible retained.
            supported = 1;
            break;
        case PowerDeviceD2:
            ScgtDbgPrint(SCGT_DBG_POWER, "Device Power State = PowerDeviceD2\n");
            // Indicates a device sleeping state less than PowerDeviceD1 and greater
            // than PowerDeviceD3, which corresponds to device power state D2.
            // D2 - Device in low-power mode. Device content probably not valid.
            supported = 2;
            break;
        case PowerDeviceD3:
            ScgtDbgPrint(SCGT_DBG_POWER, "Device Power State = PowerDeviceD3\n");
            // Indicates the lowest - powered device sleeping state, which
            // corresponds to device power state D3. 
            // D3 - Device is without power. Content lost.
            supported = 3;
            break;
        case PowerDeviceMaximum:
            /* The number of device power state values for this enumeration type that represent
            actual power states.The value of the other device power states is less than this value. */
            ScgtDbgPrint(SCGT_DBG_ERROR, "ERROR: Device Power State = PowerDeviceMaximum\n");
            break;
        default:
            ScgtDbgPrint(SCGT_DBG_ERROR, "ERROR: Device Power State = 0x%x \n", 
                         irpStackLoc->Parameters.Power.State.DeviceState );
            break;
        }
        break;
    default:
        ScgtDbgPrint(SCGT_DBG_ERROR, "ERROR: Unknown power type value: 0x%x \n", irpStackLoc->Parameters.Power.Type);
        ScgtDbgPrint(SCGT_DBG_ERROR, "Expected SystemPowerState or DevicePowerState.\n");
        break;
    }
    return supported;
} //SupportedPowerState

/*************************************************************************//**
@fn NTSTATUS scgtDevicePowerCompletion(PDEVICE_OBJECT DeviceObject,
                                 PIRP Irp, PVOID Context)
@brief Completion routine for IRP_MN_SET_POWER power IRP.
@param DeviceObject  Device Object
@param Irp           Irp
@param Context       Supplied context value
@todo verify scgtRequestDecrement needed here. enable queue on power up. process queued Irps
@return STATUS_SUCCESS or error status. STATUS_UNSUCCESSFUL
**//*************************************************************************/
NTSTATUS scgtDevicePowerCompletion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID Context)
{
    NTSTATUS ret = STATUS_SUCCESS;
    scgtDevice *dev = NULLPTR;
    PIO_STACK_LOCATION  ioStackLocation = NULLPTR;
    WARNING_SUPPRESS(26494) /* Variable 'powerState' is uninitialized.Always initialize an object(type.5) */
    POWER_STATE powerState;
    WARNING_SUPPRESS(26494) /* Variable 'scgtPowerEvent' is uninitialized. Always initialize an object (type.5).*/
    PKEVENT scgtPowerEvent;
    POWER_STATE_TYPE type= SystemPowerState;

    scgtPowerEvent = (PKEVENT)Context;

    // Get a pointer to our device extension.
    dev = (scgtDevice *)DeviceObject->DeviceExtension;

    if (scgtPowerEvent == NULLPTR)
    {
        //Should not reach here.
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtDevicePowerCompletion called with null event context\n");
        scgtRequestDecrement(dev);
        // KeSetEvent(scgtPowerEvent, 0, FALSE);
        return STATUS_UNSUCCESSFUL;
    }

    // We'll need the current I/O stack location, to determine the power state.   
    ioStackLocation = IoGetCurrentIrpStackLocation(Irp);

    // If the IRP failed for any reason, there is
    // no reason to change any state.
    ret = Irp->IoStatus.Status;

    if (!NT_SUCCESS(ret))
    {
        // Adjust in-progress request count
        scgtRequestDecrement(dev);
        KeSetEvent(scgtPowerEvent, 0, FALSE);
        return STATUS_SUCCESS;
    }

    // Modify our power state and let the power manager know
    type = DevicePowerState;
    powerState.DeviceState = ioStackLocation->Parameters.Power.State.DeviceState;

    dev->DevicePowerState = PowerDeviceD0;
    // Calling PoSetPowerState to notify the power manager that the device is in the D0 power state.
    PoSetPowerState(DeviceObject, type, powerState);

    // Restoring device power state or reinitializing the device, as required, 
    // and preparing to handle any I / O queued by drivers while the device was not in the working state

    scgtProcessQueuedRequests(dev);

    KeSetEvent(scgtPowerEvent, 0, FALSE);

    return ret;
} //scgtDevicePowerCompletion

/*************************************************************************//**
 * @fn VOID DevicePowerCompletionRoutine(PDEVICE_OBJECT     DeviceObject,
                                         UCHAR              MinorFunction,
                                         POWER_STATE        PowerState,
                                         PVOID              Context,
                                         PIO_STATUS_BLOCK   IoStatus)
 * @brief  Power request completion routine for device QUERY_POWER and
 * SET_POWER IRPs. IRQL <= DISPATCH_LEVEL, arbitrary context
 * @param [in] DeviceObject - Pointer to the target device object for the completed power IRP
 * @param [in] MinorFunction - Power IRP's minor function code
 * @param [in] PowerState    - The attempted device power state.
 * @param [in] Context       - The device extension
 * @param [in] IoStatus      - Pointer to Status block indicating the success or failure of 
 *                       the power IRP.
*//*************************************************************************/
_Use_decl_annotations_
VOID DevicePowerCompletionRoutine(PDEVICE_OBJECT     DeviceObject,
                                 UCHAR              MinorFunction,
                                 POWER_STATE        PowerState,
                                 PVOID              Context,
                                 PIO_STATUS_BLOCK   IoStatus)
{
    // https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/sending-irp-mn-query-power-or-irp-mn-set-power-for-device-power-states

    scgtDevice*         dev = NULLPTR;
    PIRP                systemPowerIrp = NULLPTR;

    UNREFERENCED_PARAMETER(DeviceObject);
    UNREFERENCED_PARAMETER(MinorFunction);
    UNREFERENCED_PARAMETER(PowerState);
    
    //
    // Get a pointer to our (FUNCTIONAL) device object's device
    // extension.
    //
    if (Context == NULLPTR)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "Error DevicePowerCompletionRoutine() passed null Context pointer.\n");
        // Need to Adjust in-progress request count, but dev not valid
        //scgtRequestDecrement(dev);
        return;
    }
    dev = (scgtDevice* ) Context;

    // Get the system power IRP from our context
    
    // Set to irp in SystemPowerIoCompletionRoutine
    // Set to NULLPTS in  DevicePowerCompletionRoutine
    // warning C26489 : Don't dereference a pointer that may be invalid: 
    // 'dev->SystemPowerIrp'. 'dev._scgtDevice::SystemPowerIrp' may have been invalidated at line 5123
    WARNING_SUPPRESS( 26489)
    systemPowerIrp = dev->SystemPowerIrp;
    
    // This should not happen
    //ASSERT(systemPowerIrp != NULLPTR );
    if (systemPowerIrp == NULLPTR)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "Error DevicePowerCompletionRoutine called with null systemPowerIrp\n");
        scgtRequestDecrement(dev);
        return;
    }

    // NULL out our temp storage for the IRP
    dev->SystemPowerIrp = NULLPTR;

    
    
    ScgtDbgPrint(SCGT_DBG_POWER, "Completing Power IRP in DevicePowerCompletionRoutine()\n");
    // Adjust in-progress request count
    scgtRequestDecrement(dev);

    // We complete the system power IRP here with the returned device
    // power IRP status.

    // warning C26489: Don't dereference a pointer that may be invalid: 
    // 'systemPowerIrp->IoStatus'. 'systemPowerIrp' may have been invalidated at line 5123 (lifetime.1).
    WARNING_SUPPRESS(26489)
    systemPowerIrp->IoStatus.Status = IoStatus->Status;
    IoCompleteRequest(systemPowerIrp, IO_NO_INCREMENT);
    
} // DevicePowerCompletionRoutine

/*************************************************************************//**
 * @fn NTSTATUS DevicePowerIoCompletionRoutine(PDEVICE_OBJECT   DeviceObject,
                                               PIRP             Irp,
                                               PVOID            Context)
 * @brief Our I/O completion routine for device SET_POWER IRPs. 
 * Completion routine registered in scgtPowerSetPower() function to handle
 * device state power down events.
 * IRQL <= DISPATCH_LEVEL, arbitrary context
 * @param DeviceObject - Pointer to device object.
 * @param Irp          - Address of the system power IRP.
 * @param Context      - the device extension
 * @return  NTSTATUS value signaling the I/O manager to continue processing 
 *          IRP stack locations or not. 
 *//*************************************************************************/
NTSTATUS DevicePowerIoCompletionRoutine(PDEVICE_OBJECT   DeviceObject,
                                        PIRP             Irp,
                                        PVOID            Context)
{
    scgtDevice*     dev=NULLPTR;
    PIO_STACK_LOCATION  ioStackLocation = NULLPTR;
    NTSTATUS            ret = STATUS_SUCCESS;
    WARNING_SUPPRESS(26494) /* Variable 'powerState' is uninitialized.Always initialize an object(type.5) */
    POWER_STATE         powerState;

    if (Context == NULLPTR)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "ERROR DevicePowerIoCompletionRoutine() called will null Context pointer\n");
        // Need to Adjust in-progress request count, but dev not valid
        //scgtRequestDecrement(dev);
        return STATUS_UNSUCCESSFUL;
    }

    // Get a pointer to our device extension.
    dev = (scgtDevice*)Context;

    // We need to propagate the pending flag here
    if (Irp->PendingReturned)
    {
        IoMarkIrpPending(Irp);
    }

    // We'll need the current I/O stack location, to determine the
    // power state.
    ioStackLocation = IoGetCurrentIrpStackLocation(Irp);
    
    // If the IRP failed for any reason, there is 
    // no reason to change any state.
    ret = Irp->IoStatus.Status;
    if (!NT_SUCCESS(ret))
    {
        // Adjust in-progress request count
        scgtRequestDecrement(dev);
        return STATUS_SUCCESS;
    }

    // Modify our power state and let the power manager know
    dev->DevicePowerState = ioStackLocation->Parameters.Power.State.DeviceState;

    powerState.DeviceState = dev->DevicePowerState;

    PoSetPowerState(DeviceObject,
        DevicePowerState,
        powerState);
 
    // If we have gone to full power, and we are in an applicable state
    // we need to restart the queues.
    scgtProcessQueuedRequests(dev);

    ScgtDbgPrint(SCGT_DBG_POWER, "Complete request in DevicePowerIoCompletionRoutine()\n");
    // Adjust in-progress request count
    scgtRequestDecrement(dev);

    return ret;
}

/*************************************************************************//**
@fn NTSTATUS SystemPowerIoCompletionRoutine(PDEVICE_OBJECT  DeviceObject,
                                        PIRP            Irp,
                                        PVOID           Context)
@brief  System Power State  completion routine.
* completion routin registered in scgtPowerSetPower and scgtPowerQueryPower
@param DeviceObject Pointer to device object
@param Irp          Pointer to IRP
@param Context      Void pointer to SCGT device object.
@return NT status value.
**//*************************************************************************/
NTSTATUS SystemPowerIoCompletionRoutine(PDEVICE_OBJECT  DeviceObject,
                                        PIRP            Irp,
                                        PVOID           Context)
{
    scgtDevice*     dev                 = NULLPTR;
    PIO_STACK_LOCATION  ioStackLocation = NULLPTR;
    NTSTATUS            ret             = STATUS_SUCCESS;
    //POWER_STATE         powerState;
    UNREFERENCED_PARAMETER(DeviceObject);

    if (Context == NULLPTR)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "Context == NULL in SystemPowerIoCompletionRoutine()\n");
        return STATUS_INVALID_PARAMETER;
    }
    //
    // Get a pointer to our device extension.
    //
    dev = (scgtDevice* )Context;

    // 
    // No need to propagate the pending flag here as
    // we already marked the IRP pending in the power
    // dispatch routine
    //

    //
    // We'll need the current I/O stack location, to determine the
    // IRP minor function code.
    //
    ioStackLocation = IoGetCurrentIrpStackLocation(Irp);

    // 
    // If the system power IRP failed for any reason, there is 
    // no reason to go on and request the device power IRP.
    // 
    ret = Irp->IoStatus.Status;

    if (!NT_SUCCESS(ret)) 
    {
        ScgtDbgPrint(SCGT_DBG_POWER, "SystemPowerIoCompletionRoutine() system power IRP failed. status:%s\n", 
                     NtStatusName(ret) );
        //
        // Adjust in-progress request count
        //
        scgtRequestDecrement(dev);
        
        return STATUS_SUCCESS;
    }

    ScgtDbgPrint(SCGT_DBG_POWER, "Saving system power irp for processing in DevicePowerCompletionRoutine()\n");

    // 
    // Save the system power IRP in the device context
    //
    dev->SystemPowerIrp = Irp;

    //
    // Setup the device power state
    // Using value saved in IRP_MN_QUERY_CAPABILITIES call.
    //
    dev->PowerState.DeviceState =
        dev->DeviceState[ioStackLocation->Parameters.Power.State.SystemState];

    ScgtDbgPrint(SCGT_DBG_POWER, "Requesting Power IRP to be processed in DevicePowerCompletionRoutine() \n");

    //
    // As the power policy owner we have to perform 
    // the system power state to device power state mappings.
    // We received these mappings in the DEVICE_CAPABILITIES 
    // structure in the PnP QUERY_CAPABILITIES request.  We
    // use these here unmolested.
    //
    PoRequestPowerIrp(dev->physDeviceObject,
        ioStackLocation->MinorFunction,
        dev->PowerState,
        DevicePowerCompletionRoutine,
        (PVOID)dev,
        NULL);

    // 
    // We complete the system power IRP in the power completion routine.  The
    // power completion routine, not to be confused with an I/O completion routine,
    // is called when the device power IRP, requested above, has been processed by
    // every device in the stack and completed.  We return 
    // STATUS_MORE_PROCESSING_REQUIRED here to let the I/O manager know we aren't 
    // done with the system power IRP.
    // 
    return STATUS_MORE_PROCESSING_REQUIRED;
} // SystemPowerIoCompletionRoutine

/**************************************************************************//**
  Power Minor code strings
**//**************************************************************************/
static PSTR powerMinorCodes[] =
{
    "IRP_MN_WAIT_WAKE",
    "IRP_MN_POWER_SEQUENCE",
    "IRP_MN_SET_POWER",
    "IRP_MN_QUERY_POWER"
};

char* powerMinorCodeStr(const LONG powerMinorCode)
{
    static char str[23];
    switch (powerMinorCode)
    {
    case IRP_MN_WAIT_WAKE:
        sprintf_s(str, 23, "IRP_MN_WAIT_WAKE");
        break;
    case IRP_MN_POWER_SEQUENCE:
        sprintf_s(str, 23, "IRP_MN_POWER_SEQUENCE");
        break;
    case IRP_MN_SET_POWER:
        sprintf_s(str, 23, "IRP_MN_SET_POWER");
        break;
    case IRP_MN_QUERY_POWER:
        sprintf_s(str, 23, "IRP_MN_QUERY_POWER");
        break;
    default:
        sprintf_s(str, 23, "0x%08lx", powerMinorCode );
        break;

    }
    return str;
}

static PSTR powerSystemStates[] =
{
    "PowerSystemUnspecified",
    "PowerSystemWorking",
    "PowerSystemSleeping1",
    "PowerSystemSleeping2",
    "PowerSystemSleeping3",
    "PowerSystemHibernate",
    "PowerSystemShutdown",
    "PowerSystemMaximum "
};

/*************************************************************************//**
@fn NTSTATUS scgtPowerSetPower(_In_  PDEVICE_OBJECT deviceObject, _In_ PIRP irp)
 @brief Code to process IRP_MN_SET_POWER events.
 @param deviceObject  Pointer to device object.
 @param irp  Pointer to irp
 @return NTSTATUS value.
 @todo add logic for handling fast startup or hibernate??
*//*************************************************************************/
NTSTATUS scgtPowerSetPower(_In_  PDEVICE_OBJECT deviceObject, _In_ PIRP irp)
{
    // This IRP notifies a driver of a change to the system power state or 
    // sets the device power state for a device.
    // https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/irp-mn-set-power

    NTSTATUS ret = STATUS_SUCCESS;
    ULONG supported = 256;
    scgtDevice* dev = NULLPTR;
    PIO_STACK_LOCATION ioStackLocation = NULLPTR;
    ULONG tarState = 0;     // The target system power state of the previous system power IRP
    ULONG effecState = 0;   // The effective previous system power state, as perceived by the user.    
    WARNING_SUPPRESS(26494) /* Variable 'powerState' is uninitialized.Always initialize an object(type.5) */
    POWER_STATE powerState;
    
    ScgtDbgPrint(SCGT_DBG_INFO, "Handling IRP_MN_SET_POWER IRP.\n");

    // Get a pointer to our (FUNCTIONAL) device object's device extension.
    dev = (scgtDevice *)deviceObject->DeviceExtension;
    ioStackLocation = IoGetCurrentIrpStackLocation(irp);
    supported = SupportedPowerState(ioStackLocation);
    
    // Todo handle fast startup https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/distinguishing-fast-startup-from-wake-from-hibernation
    // System power state def: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/ns-wdm-_system_power_state_context
    const SYSTEM_POWER_STATE_CONTEXT pContext = ioStackLocation->Parameters.Power.SystemPowerStateContext;

    // The target system power state of the previous system power IRP
    // that the driver received.This member is set to a SYSTEM_POWER_STATE
    // enumeration value. Drivers should treat this member as read - only.
    tarState = pContext.TargetSystemState;

    // The effective previous system power state, as perceived by the user.
    // This member is set to a SYSTEM_POWER_STATE enumeration value.
    // Drivers should treat this member as read - only.
    // This member value might not match the TargetSystemState member if, 
    // for example, the previous system power IRP indicated that the computer 
    // was about to enter hibernation, but a hybrid shutdown instead occurred 
    // to prepare the computer for a fast startup.For more information, see Remarks.
    effecState = pContext.EffectiveSystemState;

    // If TargetSystemState = PowerSystemHibernate and EffectiveSystemState = PowerSystemHibernate,
    // a wake - from - hibernation occurred. 
    // However, if TargetSystemState = PowerSystemHibernate and EffectiveSystemState = PowerSystemShutdown, 
    // a fast startup occurred.
    if ((tarState == PowerSystemHibernate) && (effecState == PowerSystemShutdown))
    {
        ScgtDbgPrint(SCGT_DBG_INFO, "IRP_MN_SET_POWER Fast Startup occurred.\n");
        //Fast Start
    }
    else if ((tarState == PowerSystemHibernate) && (effecState == PowerSystemHibernate))
    {
        // wake from hibernate.
        ScgtDbgPrint(SCGT_DBG_INFO, "IRP_MN_SET_POWER Wake from hibernation occurred.\n");
    }
    ScgtDbgPrint( SCGT_DBG_POWER, "tarState=%s EffectiveState =%s\n", 
        powerSystemStates[tarState], powerSystemStates[effecState]);

    // 
    // The code below assumes that this device is the power 
    // policy owner.  This is the norm for most FDO devices.
    //
    // For devices that aren't powered on or off by their driver
    // (in other words, there's no power state to manipulate) we 
    // just perform the minimum power handling code required and 
    // we don't worry about tracking power states and such.
    //

    //
    // We need to check and see if this is a system or device power
    // IRP.  For System power IRPs we send down the IRP with a 
    // completion routine.
    //

    if (SystemPowerState == ioStackLocation->Parameters.Power.Type) 
    {
        ScgtDbgPrint(SCGT_DBG_POWER, "Power type == SystemPowerState\n");
        // mark the IRP pending as it will not be completed in 
        // the completion routine
        IoMarkIrpPending(irp);

        IoCopyCurrentIrpStackLocationToNext(irp);

        ScgtDbgPrint(SCGT_DBG_POWER, "Registering SystemPowerIoCompletionRoutine completion routine \n");
        IoSetCompletionRoutine(irp,
            SystemPowerIoCompletionRoutine,
            (PVOID)dev,
            TRUE,
            TRUE,
            TRUE);

        // Send the IRP Down
        ret = IoCallDriver(dev->lowerDeviceInStack, irp);
        if (!NT_SUCCESS(ret))
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "scgtIoctlPower():  IoCallDriver call returned status: %s.\n", NtStatusName(ret));
        }

        //
        // The IRP is marked pending so we MUST return STATUS_PENDING here.
        //
        ScgtDbgPrint(SCGT_DBG_POWER, "Power type System Power state.\n");
        ScgtDbgPrint(SCGT_DBG_POWER, "Return status Pending and decrement count in completion routine.\n");
        return STATUS_PENDING;
    }

    ScgtDbgPrint(SCGT_DBG_POWER, "Power type == DevicePowerState\n");

    //
    // Handle the Device Power IRP case here.
    //
    // If we DID manage the power more completely, we would
    // manage our device state here including things like  saving the 
    // device register state on power down, and re-programming 
    // the device registers on power up.
    //

    // 
    // If this is a power down, call PoSetPowerState here, otherwise
    // call it from a completion routine.
    //
    if (dev->DevicePowerState > ioStackLocation->Parameters.Power.State.DeviceState)
    {

        ScgtDbgPrint(SCGT_DBG_POWER, "Power up event, Registering DevicePowerIoCompletionRoutine routine.\n");

        // mark the IRP pending as it will not be completed in 
        // the completion routine
        IoMarkIrpPending(irp);
        //
        // This is a power up, so we send the IRP down so the PDO can power up
        // first, then we finish our processing in an I/O completion routine.
        //
        IoSetCompletionRoutine(irp,
            DevicePowerIoCompletionRoutine,
            (PVOID)dev,
            TRUE,
            TRUE,
            TRUE);

        IoCopyCurrentIrpStackLocationToNext(irp);

        ret = IoCallDriver(dev->lowerDeviceInStack, irp);
        if (!NT_SUCCESS(ret))
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "scgtIoctlPower():  IoCallDriver call returned status: %s.\n", NtStatusName(ret));
            
        }

        return STATUS_PENDING;
    }
    else if (dev->DevicePowerState < ioStackLocation->Parameters.Power.State.DeviceState)
    {
        ScgtDbgPrint(SCGT_DBG_POWER, "Device power down event.\n");

        scgtPrintShutdownType(ioStackLocation->Parameters.Power.ShutdownType);

        //
        // Power down we just 
        // handle it, and send the IRP to the bus
        //
        powerState.DeviceState = ioStackLocation->Parameters.Power.State.DeviceState;

        PoSetPowerState(deviceObject,
            DevicePowerState,
            powerState);

        //
        // If we are not fully powered, then we need to make sure the 
        // queues are stalled.
        //
        scgtStallQueues(dev);

        IoSkipCurrentIrpStackLocation(irp);

        ret = IoCallDriver(dev->lowerDeviceInStack, irp);
        if (!NT_SUCCESS(ret))
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "scgtIoctlPower():  IoCallDriver call returned status: %s.\n", NtStatusName(ret));
        }
    }
    else
    {
        ScgtDbgPrint(SCGT_DBG_POWER, "No Change in Device State.\n");
        //
        // If we remained at full power after a query to power down, 
        // and we are in an applicable state we need to restart the queues.
        //
        //TODO
        //scgtProcessQueusedRequests(dev);

        //
        // Power staying the same, just send it down
        //

        IoSkipCurrentIrpStackLocation(irp);

        ret = IoCallDriver(dev->lowerDeviceInStack, irp);
        if (ret == STATUS_PENDING)
        {
            scgtRequestDecrement(dev);
        }
        else if (ret != STATUS_SUCCESS)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "scgtIoctlPower():  IoCallDriver call returned status: %s.\n",
                         NtStatusName(ret) );
        }
    }
    return ret;

/*
    POWER_STATE_TYPE type = DevicePowerState;
    unsigned int newState = 0;
    DEVICE_POWER_STATE newState;
    KEVENT scgtPowerEvent;

    if (type == DevicePowerState)
    {
        // Handling IRP_MN_SET_POWER for Device Power States
        // https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/handling-irp-mn-set-power-for-device-power-states

        newState = irpStackLoc->Parameters.Power.State.DeviceState;

        if (dev->DevicePowerState > newState)
        {
            // request to raise power.
            // powering up device
            // https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/handling-device-power-up-irps

            // This is a power up, so we send the IRP down so the PDO can power up
            // first, then we finish our processing in an I/O completion routine.

            // Set stack location to next lower driver.
            IoCopyCurrentIrpStackLocationToNext(irp);

            KeInitializeEvent(&scgtPowerEvent, NotificationEvent, FALSE);
            IoSetCompletionRoutine(irp, scgtDevicePowerCompletion, (PVOID)&scgtPowerEvent, TRUE, TRUE, TRUE);


            // Pass Irp to next lower Driver.
            // Beginning with Windows Vista, a driver should call IoCallDriver instead of PoCallDriver, to pass power IRPs to the next - lower driver.
            // This IRP must be handled first by the parent bus driver for a device and
            //   then by each higher driver in the device stack.
            if ((ret = IoCallDriver(dev->lowerDeviceInStack, irp)) == STATUS_PENDING)
            {
                // wait for lover level IoComplletyion even to complete.
                KeWaitForSingleObject(&scgtPowerEvent,
                    Executive,
                    KernelMode,
                    FALSE,
                    NULLPTR);

                ret = irp->IoStatus.Status;
            }

            //ret = IoCallDriver(deviceObject, irp);
            //if (!NT_SUCCESS(ret))
            //{
                //ScgtDbgPrint(SCGT_DBG_ERROR,"scgtIoctlPower():  IoCallDriver call returned 0x%x.\n", ret);
            //}
            //ret = STATUS_PENDING;
        }
        else  if (newState == dev->DevicePowerState)
        {

            // Each driver(function, filter, and bus driver) in a driver stack must call 
            // PoSetPowerState to inform the power manager of a change in the power state of its corresponding device object.
            PoSetPowerState(deviceObject, type, irpStackLoc->Parameters.Power.State);
            dev->DevicePowerState = newState;

            // Set stack location to next lower driver.
            IoSkipCurrentIrpStackLocation(irp);
            // if completion routine needed call IoCopyCurrentIrpStackLocationToNext and 
            // IosetCopletionRoutine instead of IoSkipCurrentIrpStackLocation

            // Pass Irp to next lower Driver.
            // Beginning with Windows Vista, a driver should call IoCallDriver instead of PoCallDriver, 
            // to pass power IRPs to the next - lower driver.
            ret = IoCallDriver(deviceObject, irp);
            if (!NT_SUCCESS(ret))
            {
                ScgtDbgPrint(SCGT_DBG_ERROR, "scgtIoctlPower():  IoCallDriver call returned 0x%x.\n", ret);
            }
        }
        else
        {
            // request to reduce power
            // https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/handling-device-power-down-irps

            // Perform any device - specific tasks that must be done before device power is removed, 
            // such as closing the device, completing or flushing any pending I / O, disabling interrupts, 
            // queuing subsequent incoming IRPs, and saving device context from which to restore or 
            // reinitialize the device.                    

            ret = scgtStopIO(dev, 10000);
            if (ret == STATUS_TIMEOUT)
            {
                ScgtDbgPrint(SCGT_DBG_ERROR, "Unable to stop IO within timeout period of 10 Sec.\n");
                //Todo what to do it stop IO times out?
            }

            // Each driver(function, filter, and bus driver) in a driver stack must call 
            // PoSetPowerState to inform the power manager of a change in the power state of its corresponding device object.
            PoSetPowerState(deviceObject, type, irpStackLoc->Parameters.Power.State);
            dev->DevicePowerState = newState;

            // Set stack location to next lower driver.
            IoSkipCurrentIrpStackLocation(irp);
            // if completion routine needed call IoCopyCurrentIrpStackLocationToNext and 
            // IosetCopletionRoutine instead of IoSkipCurrentIrpStackLocation

            // Pass Irp to next lower Driver.
            // Beginning with Windows Vista, a driver should call IoCallDriver instead of PoCallDriver, 
            // to pass power IRPs to the next - lower driver.
            ret = IoCallDriver(deviceObject, irp);
            if (!NT_SUCCESS(ret))
            {
                ScgtDbgPrint(SCGT_DBG_ERROR, "scgtIoctlPower():  IoCallDriver call returned 0x%x.\n", ret);
            }
        }
        dev->DevicePowerState = newState;
    }
    else
    {
        // System power state change notification
        // drive can prepare for eventual device power change latter.                
        newState = irpStackLoc->Parameters.Power.State.SystemState;
        if ((newState == PowerSystemWorking) ||
            (newState == PowerSystemShutdown) ||
            (newState == PowerSystemUnspecified))
        {

            if ((dev->DevicePowerState != PowerDeviceD0) &&
                (newState != PowerSystemWorking))
            {
                ScgtDbgPrint(SCGT_DBG_ERROR, "scgtIoctlPower(): Error unable to move to system state %x while device state = %x.\n", newState, dev->DevicePowerState);
                ScgtDbgPrint(SCGT_DBG_ERROR, "scgtIoctlPower():  IRP_MN_SET_POWER Returning not Supported.\n");
                irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
            }
            else
                irp->IoStatus.Status = STATUS_SUCCESS;
        }
        else
        {
            irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
        }
        //IoCopyCurrentIrpStackLocationToNext(irp);
        IoSkipCurrentIrpStackLocation(irp);
        // PoSetPowerState to inform the power manager of a change in the power state of its corresponding device object.
        PoSetPowerState(deviceObject, type, irpStackLoc->Parameters.Power.State);
        ret = IoCallDriver(deviceObject, irp);
        if (!NT_SUCCESS(ret))
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "scgtIoctlPower(): IRP_MN_SET_POWER IoCallDriver call returned 0x%x.\n", ret);
        }
    
    }
    return ret;
    */

}

/*************************************************************************//**
 * @fn NTSTATUS scgtPowerQueryPower( _In_ PDEVICE_OBJECT deviceObject, 
 *                                   _In_ PIRP irp)
 * @brief Handle IRP_MN_QUERY_POWER irps.
 * @param deviceObject
 * @param irp
 * @return NTSTATUS value
*//*************************************************************************/
NTSTATUS scgtPowerQueryPower(_In_ PDEVICE_OBJECT deviceObject, _In_ PIRP irp)
{
    NTSTATUS ret = STATUS_SUCCESS;
    PIO_STACK_LOCATION ioStackLocation = NULLPTR;
    scgtDevice* dev = NULLPTR;
    LONG count = 0;

    // Get a pointer to our (FUNCTIONAL) device object's device extension.
    dev = (scgtDevice *)deviceObject->DeviceExtension;

    // We'll need the current I/O stack location, to determine the
    // power IRP type and state.
    ioStackLocation = IoGetCurrentIrpStackLocation(irp);

    // 
    // The code below assumes that this device is the power 
    // policy owner.  This is the norm for most FDO devices.
    //

    //
    // If this device is something like an industrial device, 
    // that will typically be used in an industrial or other 
    // fixed setting, then it may be desirable to avoid power 
    // downs while I/O requests are in progress.  In this case 
    // the driver should keep some device state to indicate it is 
    // busy.  For example this could be done by tracking open 
    // handles to the device.  If the device is determined to be 
    // busy, the query to power down should be rejected.  

    // fail request if open handle to SCRAMNet Gt device
    count = InterlockedCompareExchange(&dev->numOpenCalls, 0, 0);
    if ( count > 0 )
    {
        ScgtDbgPrint(SCGT_DBG_POWER, "Failing IRP_MN_QUERY_POWER device busy. I/O count=%d\n",count);
        ret = STATUS_DEVICE_BUSY;

        irp->IoStatus.Information = 0;
        irp->IoStatus.Status = ret;
        //
        // Complete the request here, without passing it down...
        //
        IoCompleteRequest(irp, IO_NO_INCREMENT);
        
        ScgtDbgPrint(SCGT_DBG_POWER, "scgtPowerQueryPower() returning STATUS_DEVICE_BUSY. I/O count=%d\n",count);
        return ret;
    }

    //
   // If the device can be, and typically is run on any platform
   // including laptops, then a conservative approach should be
   // taken and the device should generally accept the query to
   // power down the device.
   //
   // We need to check and see if this is a system or device power
   // IRP.  For System power IRPs we send down the IRP with a 
   // completion routine.
   //
    if (SystemPowerState == ioStackLocation->Parameters.Power.Type)
    {
        ScgtDbgPrint(SCGT_DBG_POWER, "IRP_MN_QUERY_POWER=> Power type == SystemPowerState\n");
        //
        // mark the IRP pending as it will not be completed in 
        // the completion routine
        //
        IoMarkIrpPending(irp);

        IoCopyCurrentIrpStackLocationToNext(irp);

        ScgtDbgPrint(SCGT_DBG_POWER, "Registering system power I/O completion routine. SystemCompletionRoutine()\n");

        IoSetCompletionRoutine(irp,
            SystemPowerIoCompletionRoutine,
            dev,
            TRUE,
            TRUE,
            TRUE);

        //
        // send the IRP down
        //
        ret = IoCallDriver(dev->lowerDeviceInStack, irp);
        if (ret != STATUS_SUCCESS)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "scgtIoctlPower():  IoCallDriver call returned status %s\n", NtStatusName(ret));
        }

        ScgtDbgPrint(SCGT_DBG_POWER, "scgtPowerQueryPower() returning STATUS_PENDING\n");
        //
        // The IRP is marked pending so we MUST return STATUS_PENDING here.
        //
        return STATUS_PENDING;
    }

    ScgtDbgPrint(SCGT_DBG_POWER, "IRP_MN_QUERY_POWER-> Power type = Device power state. Power down query\n");
    
    // 
    // This is a query to power down, stall the queues
    //
    scgtStallQueues(dev);

    //
    // Handle the Device Power IRP case here.  We will always 
    // accept the device QUERY_POWER for our device.
    //
    // A device may need to start queuing I/O requests in 
    // this case.  If a device does queue requests on power down
    // then it must have some method of restarting those requests
    // when the device is returned to full power.  When and where
    // to start queuing requests is device specific and could 
    // possibly be postponed until the SET_POWER IRP arrives.  
    // However, it is typically when a QUERY_POWER IRP for the
    // device is received that a driver starts queuing requests.
    //

    IoSkipCurrentIrpStackLocation(irp);

    // Pass this request on down to the BUS driver
    ret = IoCallDriver(dev->lowerDeviceInStack, irp);
    if (ret != STATUS_SUCCESS )
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "IRP_MN_QUERY_REMOVE_DEVICE IoCallDriver call failed.Status:%s\n",NtStatusName(ret) );
    }

    return ret;
}


/**************************************************************************//**
 * @fn  NTSTATUS scgtIoctlPOWER(IN PDEVICE_OBJECT deviceObject, IN PIRP irp)
 *
 * @brief  Process IRP_MJ_POWER commands
*
 * @param  deviceObject   The device object.
 * @param  irp            The irp.
 * @return NTSTATUS value. STATUS_SUCCESS, STATUS_UNSUCCESSFUL,
           STATUS_NOT_IMPLEMENTED, STATUS_NOT_SUPPORTED, STATUS_DEVICE_BUSY,
           or error value from lower driver.
 *
 * @todo  Check IRP_MN_QUERY_POWER of type SystemPowerState. Handle fast Startup.
 *//*****************************************************************************/
_Use_decl_annotations_
NTSTATUS scgtIoctlPOWER(IN PDEVICE_OBJECT deviceObject, IN PIRP irp)
{
    NTSTATUS ret = STATUS_SUCCESS;
    PIO_STACK_LOCATION irpStackLoc = NULLPTR;
    scgtDevice *dev = NULLPTR;

    ScgtDbgPrint(SCGT_DBG_POWER, "scgtIoctlPOWER() - {\n");

    // Get a pointer to our (FUNCTIONAL) device object's device extension.
    dev = (scgtDevice *)deviceObject->DeviceExtension;

    // We'll need the current I/O stack location, to determine the
    // IRP minor function code.
    irpStackLoc = IoGetCurrentIrpStackLocation(irp);

    ScgtDbgPrint(SCGT_DBG_POWER, "Power Minor Function is %s\n",
        powerMinorCodeStr(irpStackLoc->MinorFunction) );

    //
    // See what sort of state we're in. When we are in the SURPRISED_REMOVED
    // state we still must handle all power Irps but fail all others
    //
    if ((dev->SystemState < STATE_ALL_BELOW_FAIL) &&
        (dev->SystemState != STATE_SURPRISE_REMOVED))
    {
        //ScgtDbgPrint(SCGT_DBG_POWER, "system state < STATE_ALL_BELOW_FAIL && state != SURPRISE REMOVED\n");
        ScgtDbgPrint(SCGT_DBG_POWER, "scgtIoctlPOWER called with system state set to STATE_REMOVED\n");
        
        if (irpStackLoc->MinorFunction != IRP_MN_SET_POWER)
        {
            ret = STATUS_DEVICE_REMOVED;

            irp->IoStatus.Information = 0;
            irp->IoStatus.Status = ret;

            //
            // Complete the request here, without passing it down...
            //
            IoCompleteRequest(irp, IO_NO_INCREMENT);
            ScgtDbgPrint(SCGT_DBG_FLOW, "scgtIoctlPOWER() - Status:%s}\n", NtStatusName(ret));
            return ret;
        }
    }

    // Up the count of in-progress requests
    ret = scgtRequestIncrement(dev);
    if (ret != STATUS_SUCCESS)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtIoctlPOWER() failed to acquire remove lock\n");
        ASSERTMSG("Error devExt->outstandingIO value  <=0 after adding 1 to count.", dev->OutstandingIO<=0);
        /*
        irp->IoStatus.Status = ret;
        IoCompleteRequest(irp, IO_NO_INCREMENT);
        ScgtDbgPrint(SCGT_DBG_POWER, "scgtIoctlPOWER() - Status:%s}\n", NtStatusName(ret));
        return ret;
        
         We should not reach this branch. 
         If we are here devExt->OutstandingIO+1 is less than or equal to zero.
         Set OutstandingIO to one and continue power irp. 
        */
        InterlockedExchange(&dev->OutstandingIO, 1);
    }

    switch (irpStackLoc->MinorFunction)
    {
        // For ALL power IRPs, we are REQUIRED to:       
        //  Pass the IRP down to the next lower driver, unless
        //  WE complete the request with an error.

        case IRP_MN_POWER_SEQUENCE:
            // This IRP returns the power sequence values for a device.
            // https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/irp-mn-power-sequence

            // A driver sets Irp->IoStatus.Status to STATUS_SUCCESS to indicate 
            // that it has returned the requested information, or to 
            // STATUS_NOT_IMPLEMENTED to indicate that it does not support this IRP.
            irp->IoStatus.Status = STATUS_NOT_IMPLEMENTED;

            IoSkipCurrentIrpStackLocation(irp);
            ret = IoCallDriver(deviceObject, irp);
            break;

        case IRP_MN_QUERY_POWER:
            ret = scgtPowerQueryPower(deviceObject, irp);
            if (ret == STATUS_PENDING)
            {
                ScgtDbgPrint(SCGT_DBG_POWER, "scgtIoctlPOWER() - Status:%s}\n", NtStatusName(ret));
                return ret;
            }
            break;

        case IRP_MN_SET_POWER:
            ret = scgtPowerSetPower(deviceObject, irp);
            if (ret == STATUS_PENDING)
            {
                ScgtDbgPrint(SCGT_DBG_POWER, "scgtIoctlPOWER() - Status:%s}\n", NtStatusName(ret));
                return ret;
            }
            break;

        case IRP_MN_WAIT_WAKE:
            // This IRP enables a driver to awaken a sleeping system or to awaken a sleeping device.
            // https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/irp-mn-wait-wake

            //SCRAMNet GT does not support wake.
            irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
            
            IoSkipCurrentIrpStackLocation(irp);
            ret = IoCallDriver(deviceObject, irp);
            if (!NT_SUCCESS(ret))
            {
                ScgtDbgPrint(SCGT_DBG_ERROR, "IoCallDriver call returned 0x%x.\n", ret);
            }
            break;
        default:
            if (irpStackLoc->MajorFunction != IRP_MJ_POWER)
            {
                ScgtDbgPrint(SCGT_DBG_ERROR, "Error scgtIoctlPOWER() Called with invalid Major function %s. Expected IRP_MJ_POWER\n",
                    IrpMajorNameStr(irpStackLoc->MajorFunction));
            }
            ScgtDbgPrint(SCGT_DBG_ERROR, "Error unknown power Minor Irp: 0x%x\n",
                irpStackLoc->MinorFunction);
            //IRQL_NOT_LESS_OR_EQUAL BUS occurred here
            const KIRQL irql = KeGetCurrentIrql();
            //const KIRQL newIrql = DISPATCH_LEVEL;
            //ret = STATUS_INVALID_PARAMETER;
            ret = irp->IoStatus.Status;
            if (irql > DISPATCH_LEVEL)
            {
                // warning C28138 : The constant argument '2' should instead be variable.
                WARNING_SUPPRESS(28138)
                KeLowerIrql(DISPATCH_LEVEL);
                IoCompleteRequest(irp, IO_NO_INCREMENT);
                KeLowerIrql(irql);
            }
            else
                IoCompleteRequest(irp, IO_NO_INCREMENT);
            break;

    }

    scgtRequestDecrement(dev);

    ScgtDbgPrint(SCGT_DBG_POWER, "scgtIoctlPOWER() - Status:%s}\n", NtStatusName(ret));
    return ret;

    /*
    scgtDevice *dev = (scgtDevice *)deviceObject->DeviceExtension;
    ScgtDbgPrint(SCGT_DBG_INFO,"scgtIoctlPOWER() - {\n");
    // Beginning with Windows Vista, calling PoStartNextPowerIrp is not required, and such a call performs no power management operation.
    PoStartNextPowerIrp(irp);
    IoSkipCurrentIrpStackLocation(irp);

    ScgtDbgPrint(SCGT_DBG_INFO,"scgtIoctlPOWER() - }\n");

    return PoCallDriver(dev->lowerDeviceInStack, irp);
    */
} // scgtIoctlPOWER

#ifdef NOT_USED
/**************************************************************************//*
 * @fn  static void scgtCopyToUser(void *userPtrDest, void *src, uint32 numBytes)
 *
 * @brief  Scgt copy to user.
 *
 * @param [in,out]  userPtrDest If non-null, the user pointer destination.
 * @param [in,out]  src         If non-null, source for the.
 * @param  numBytes             Number of bytes.
 *//*****************************************************************************/
static void scgtCopyToUser(void *userPtrDest, void *src, uint32 numBytes)
{
    MDL *mdl;
    //uint8 *dest;
    PVOID dest;

    if ((mdl = IoAllocateMdl(userPtrDest, numBytes, FALSE, FALSE, NULLPTR)) == NULLPTR)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtCopyToUser(): mdl allocation failed\n");
        return;
    }

    __try
    {
        MmProbeAndLockPages(mdl, UserMode, IoModifyAccess);
    }
    /* Exception - filter expression is the constant EXCEPTION_EXECUTE_HANDLER.
       This might mask exceptions that were not intended to be handled.*/
    WARNING_SUPRESS(6320)
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        // Calls to MmProbeAndLockPages must be enclosed in a try / except block.
        // If the pages do not support the specified operation, the routine raises the
        // STATUS_ACCESS_VIOLATION or other exceptions.

        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtCopyToUser(): probe and lock failed.\n");
        ScgtDbgPrint(SCGT_DBG_ERROR, "Input parameters userPtrDest:%p src:p numBytes:%u\n",
                                      userPtrDest, src, numBytes);
        IoFreeMdl(mdl);
        return;
    }
    /*
    //DLC added MdlMappingNoExecute flag
    MdlMappingNoExecute indicates that the mapped physical pages are to be configured as
    no-execute memory. Starting with Windows 8, this flag bit can be bitwise-ORed with the
    MM_PAGE_PRIORITY value to specify memory in which instruction execution is disabled.
    As a best practice, drivers written for Windows 8 and later versions of Windows should
    always specify no-execute memory unless executable memory is explicitly required.*/
//#ifdef Win7
#if (NTDDI_VERSION < NTDDI_WIN10)
    if ((dest = MmGetSystemAddressForMdlSafe(mdl, NormalPagePriority)) != NULLPTR)
#else
    if ((dest = MmGetSystemAddressForMdlSafe(mdl, NormalPagePriority | MdlMappingNoExecute)) != NULLPTR)
#endif
    {
        /* do the copy */
        memcpy(dest, src, numBytes);
    }

    MmUnlockPages(mdl);
    IoFreeMdl(mdl);
}
#endif

/**************************************************************************//**
 * @fn  static void scgtCopyFromUser(_Out_writes_bytes_all_(numBytes) void *dst,
 *                                   _In_reads_bytes_(numBytes) void *userPtrSrc,
 *                                    uint32 numBytes)
 *
 * @brief  SCGT copy data from user space.
 *
 * @param [in,out]  dst         If non-null, destination for the data.
 * @param [in,out]  userPtrSrc  If non-null, the user pointer source.
 *  @param  numBytes             Number of bytes.
 *****************************************************************************/
static void scgtCopyFromUser(_Out_writes_bytes_all_(numBytes) void *dst,
    _In_reads_bytes_(numBytes) void *userPtrSrc,
    uint32 numBytes)
{
    MDL *mdl = NULLPTR;
    PVOID src = NULLPTR;

    if ((mdl = IoAllocateMdl(userPtrSrc, numBytes, FALSE, FALSE, NULLPTR)) == NULLPTR)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtCopyFromUser(): mdl allocation failed\n");        
        return;
    }

    __try
    {
        MmProbeAndLockPages(mdl, UserMode, IoReadAccess);
    }
    /* Exception - filter expression is the constant EXCEPTION_EXECUTE_HANDLER.
       This might mask exceptions that were not intended to be handled.*/
    WARNING_SUPPRESS(6320)
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtCopyFromUser(): probe and lock failed \n");
        ScgtDbgPrint(SCGT_DBG_ERROR, "Input parameters dst:%p userPtrSrc:%p numBytes:%u\n",
                     dst, userPtrSrc, numBytes);
        IoFreeMdl(mdl);
        return;
    }
#if (NTDDI_VERSION < NTDDI_WIN10)
    if ((src = MmGetSystemAddressForMdlSafe(mdl, NormalPagePriority)) != NULLPTR)
#else        
    if ((src = MmGetSystemAddressForMdlSafe(mdl, NormalPagePriority | MdlMappingNoExecute)) != NULLPTR)
#endif

    {
        if (dst != NULLPTR)
        {
            /* do the copy */
            //memcpy(dst, src, numBytes);
            RtlCopyMemory(dst, src, numBytes);
        }
    }

    MmUnlockPages(mdl);
    IoFreeMdl(mdl);
} // scgtCopyFromUser

/** @} */

/***********************************************************************/
/**************************** DMA CODE *********************************/
/***********************************************************************/
/** @defgroup DMACodes SCRAMNet GT DMA code
SCRAMNet GT functions for processing DMA transfers.
@{*/

/**************************************************************************//**
 * @fn  static int scgtInitDMATools( _In_ scgtDevice *dev)
 *
 * @brief  Allocate and initialize DMA tools for device.
 *
 * @param [in,out]  dev   If non-null, the dev.
 * @param [in,out]  dev   If non-null, the dev.
 *
 * @return  returns 0 on success, non-zero on failure.
 *****************************************************************************/
static int scgtInitDMATools(_In_ scgtDevice *dev)
{
    ksysSemSCreate(&dev->writeTools.entrySem_1);
    ksysSemSCreate(&dev->writeTools.entrySem_2);
    ksysSemSCreate(&dev->readTools.entrySem_1);
    ksysSemSCreate(&dev->readTools.entrySem_2);

    ksysSemSGive(&dev->writeTools.entrySem_1);    /* semaphores start as taken */
    ksysSemSGive(&dev->writeTools.entrySem_2);
    ksysSemSGive(&dev->readTools.entrySem_1);
    ksysSemSGive(&dev->readTools.entrySem_2);

    dev->writeTools.pending = 0;
    dev->readTools.pending = 0;

    dev->MaxDMABlockSize = SCGT_MAX_CHUNK_SIZE;
    return 0;
} // scgtInitDMATools

/**************************************************************************//**
 * @fn  static void scgtDestroyDMATools( _In_ const scgtDevice *dev)
 *
 * @brief  Deallocate DMA tools for device.
 *
 * @param [in,out]  dev   If non-null, the dev.

 **//***************************************************************************/
inline static void scgtDestroyDMATools(_In_ const scgtDevice *dev)
{
    //DLC added (void) var to prevent Warning C4100 Unreferenced formal parameter
    UNREFERENCED_PARAMETER(dev);

    ksysSemSDestroy(&dev->writeTools.entrySem_1);
    ksysSemSDestroy(&dev->writeTools.entrySem_2);
    ksysSemSDestroy(&dev->readTools.entrySem_1);
    ksysSemSDestroy(&dev->readTools.entrySem_2);
} // scgtDestroyDMATools

/**************************************************************************//**
 * @fn static uint32 scgtIoctlXfer(_In_ scgtDevice *dev,
 *                                 _In_ PIRP irp,
 *                                 _In_ scgtXfer *xfer,
 *                                 _In_opt_ scgtInterrupt *intr,
 *                                 _In_ const uint8 direction)
 *
 * @brief  Process write or read transfer
 *
 * @param [in,out]  dev   If non-null, pointer to SCGT device object
 * @param  irp            The irp.
 * @param [in,out]  xfer  If non-null, pointer to transfer data.
 * @param [in,out]  intr  If non-null, pointer to interrupt data.
 * @param  direction      The direction (read or write).
 *
 * @return .SCGT_SUCCESS or error code.
 **//*************************************************************************/
static uint32 scgtIoctlXfer(_In_ scgtDevice *dev,
    _In_ PIRP irp,
    _In_ scgtXfer *xfer,
    _In_opt_ scgtInterrupt *intr,
    _In_ const uint8 direction)
{
    uint32 gtMemoryOffset = 0;
    uint32 bytesToTransfer = 0;
    uint32 bytesTransferred = 0;
    uint32 chunkSize = 0;
    uint32 flags = 0;
    uint8  lastTransfer = 0;
    uint32 ret = SCGT_SUCCESS;
    uint8  *pBuf = NULLPTR;
    scgtDMATools *dmaTools = NULLPTR;;
    ksysSemS *semToGive = NULLPTR;

    ScgtDbgPrint(SCGT_DBG_XFER, "scgtIoctlXfer - {\n");

    //DLC    pBuf = (uint8 *) xfer->pDataBuffer;
    // pBuf = UINT64_TO_PTR(uint8 *,xfer->pDataBuffer);
    pBuf = (uint8 *)((uintpsize)xfer->pDataBuffer);
    bytesToTransfer = xfer->bytesToTransfer;
    gtMemoryOffset = xfer->gtMemoryOffset;

    if (bytesToTransfer == 0 || pBuf == NULLPTR)
        return gtcoreSendIntr(dev, intr);

    flags = xfer->flags;
    dmaTools = (direction == GTCORE_WRITE) ? &dev->writeTools : &dev->readTools;

    ksysSemSTake(&dmaTools->entrySem_1);
    semToGive = &dmaTools->entrySem_1;

    while (bytesToTransfer > 0 && ret == SCGT_SUCCESS)
    {
        /* calculate chunk size */
        chunkSize = (bytesToTransfer >= dev->MaxDMABlockSize) ? dev->MaxDMABlockSize : bytesToTransfer;
        lastTransfer = (uint8)((chunkSize == bytesToTransfer) ? 1 : 0);

        ret = scgtXferChunk(dev, irp, gtMemoryOffset, pBuf, chunkSize, lastTransfer,
            flags, intr, direction, dmaTools, &bytesTransferred,
            &semToGive);

        bytesToTransfer -= bytesTransferred;
        pBuf += bytesTransferred;
        gtMemoryOffset += bytesTransferred;
    }

    ksysSemSGive(semToGive);

    xfer->bytesTransferred = xfer->bytesToTransfer - bytesToTransfer;

    ScgtDbgPrint(SCGT_DBG_XFER, "scgtIoctlXfer ret:%s- }\n", scgtErrorCodeToString(ret));

    return ret;
} // scgtIoctlXfer

WARNING_SUPPRESS(26429) /* Symbol 'semToGive' is never tested for nullness, it can be marked as not_null(f.23).) */
/**************************************************************************//**
 * @fn  static uint32 scgtXferChunk(_In_ scgtDevice *dev,
 *                          _In_ PIRP irp,
 *                          _In_ uint32 gtMemoryOffset,
 *                          _Inout_ uint8  *pBuf,
 *                          _In_ uint32 chunkSize,
 *                          _In_ uint8 lastTransfer,
 *                          _In_ uint32 flags,
 *                          _In_opt_ scgtInterrupt *intr,
 *                          _In_ const uint8  direction,
 *                          _Inout_ scgtDMATools *tools,
 *                          _Out_ uint32 *bytesTransferred,
 *                          _Inout_ ksysSemS **semToGive)
 *
 * @brief  Reads or writes a chunk of up to size dev->MaxDMABlockSize of the
 *     specified buffer.  bytesTransferred is filled with the number of bytes
 *     transferred.
 *
 * @param  dev                        If non-null, Pointer to SCGT device object.
 * @param  irp                        The IRP.
 * @param  gtMemoryOffset             The gt memory offset.
 * @param  pBuf                       If non-null, the buffer.
 * @param  chunkSize                  Size of the chunk.
 * @param  lastTransfer               The last transfer.
 * @param  flags                      The flags.
 * @param [in,out]  intr              If non-null, pointer to interrupt data.
 * @param  direction                  The direction. (read or write)
 * @param [in,out]  tools             If non-null, the tools.
 * @param [in,out]  bytesTransferred  If non-null, the bytes transferred.
 * @param [in,out]  semToGive         If non-null, the semaphore to give.
 *
 * @return .
 ***************************************************************************/
static uint32 scgtXferChunk(_In_ scgtDevice *dev,
    _In_ PIRP irp,
    _In_ uint32 gtMemoryOffset,
    _Inout_ uint8  *pBuf,
    _In_ uint32 chunkSize,
    _In_ uint8 lastTransfer,
    _In_ uint32 flags,
    _In_opt_ scgtInterrupt *intr,
    _In_ const uint8  direction,
    _Inout_ scgtDMATools *tools,
    _In_ uint32 *bytesTransferred,
    _Inout_ ksysSemS **semToGive)
{
    uint32 ret = SCGT_SUCCESS;
    gtcoreExch *exch = NULLPTR;
    PMDL mdl = NULLPTR;
    //DLC added UNREFERENCED_PARAMETER to prevent Warning C4100 Unreferenced formal parameter
    UNREFERENCED_PARAMETER(irp);
    //ASSERT(dev != NULLPTR);
    //ASSERT(bytesTransferred != NULLPTR);
    if ((dev == NULLPTR) || (bytesTransferred == NULLPTR))
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "Error scgtXferChunk called with NULL pointer\n");
        return SCGT_BAD_PARAMETER;
    }


    exch = gtcoreGetExchange(dev, direction);
    if (exch == NULLPTR)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "Error gtcodreGetExchange reyturned NULL excg pointer not vailid\n");
        return SCGT_INSUFFICIENT_RESOURCES;
    }
    exch->bytesTransferred = 0;

    ScgtDbgPrint(SCGT_DBG_XFER, "scgtXferChunk() begin\n");

    exch->bytesToTransfer = chunkSize;
    exch->gtMemoryOffset = gtMemoryOffset;
    exch->flags = flags;

    if (lastTransfer)     /* setup intr for last transfer */
        exch->intr = intr;
    else
        exch->intr = NULLPTR;

    if (!(flags & SCGT_RW_DMA_PHYS_ADDR))  /* not physical address */
    {
        /* map the buffer to the MDL */
        if ((mdl = IoAllocateMdl(pBuf, chunkSize, FALSE, FALSE, NULLPTR)) == NULLPTR)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "UNABLE TO IoAllocateMdl()!!\n");
            *bytesTransferred = 0;
            return SCGT_INSUFFICIENT_RESOURCES;
        }


        __try
        {
            MmProbeAndLockPages(mdl, UserMode,
                (direction == GTCORE_WRITE) ? IoWriteAccess : IoReadAccess);
        }
        /* Exception - filter expression is the constant EXCEPTION_EXECUTE_HANDLER.
           This might mask exceptions that were not intended to be handled.*/
        WARNING_SUPPRESS(6320)
        __except (EXCEPTION_EXECUTE_HANDLER)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "UNABLE TO Probe and lock pages()!!\n");
            return SCGT_SYSTEM_ERROR;
        }

        /* build the hardware chain list */
        ret = scgtBuildChainList(dev, mdl, exch, direction, chunkSize);
        if (ret != SCGT_SUCCESS)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "scgtBuildChainList call failed. ret=%u\n", ret);
        }
    }
    else
    {
        /* mode is SCGT_DMA_PHYS (physical address supplied) */
        // exch set to point to &dev->wexch : &dev->rexch; in gtcoreGetExchange
        // C26489: Don't dereference a pointer that may be invalid:
        // '(uint32 *)exch->sgList[0]'. 'exch._gtcoreExch::sgList' may have been invalidated at line 6349)
        WARNING_PUSH
        WARNING_DISABLE(26489)
        uint32 *chainEntry = (uint32 *)exch->sgList[0];
        //ASSERT(chainEntry != NULLPTR);
        if (chainEntry == NULLPTR)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR, "Error chainEntry pointer invalid\n");
            return SCGT_BAD_PARAMETER;
        }

        chainEntry[GTCORE_CE_TNS_CSR] = (chunkSize >> 2) | GTCORE_CE_LST;
        //DLC chainEntry[GTCORE_CE_BUF_ADD32] = (uint32) pBuf;
        chainEntry[GTCORE_CE_BUF_ADD32] = (uint32)((uintpsize)pBuf);
        ksysCacheFlush(NULL, chainEntry, GTCORE_CE_SIZE);
        WARNING_POP
    }

    /* do transfer */
    gtcoreTransfer(dev, exch, direction);

    if (lastTransfer)  /* last transfer */
    {
        ksysSemSTake(&tools->entrySem_2);

        tools->pending = 1;

        /* let the next thread enter */
        ksysSemSGive(&tools->entrySem_1);
        *semToGive = &tools->entrySem_2;  /* swap the exit semaphore to sem 2 */
    }
    else if (tools->pending)
    {
        ksysSemSTake(&tools->entrySem_2);
        ksysSemSGive(&tools->entrySem_2);
    }

    /* wait until its done. */
    if (ksysSemBTakeWithTimeout(&exch->compSem, SCGT_TIMEOUT_TIME))
    {
        /* hardware did not complete transfer! */
        ScgtDbgPrint(SCGT_DBG_ERROR, "scgtXferChunk timed out \n");
        ret = SCGT_TIMEOUT;
        gtcoreCancelTransfer(dev, exch, direction);
    }
    else
    {
        ret = exch->status;
    }

    *bytesTransferred = exch->bytesTransferred;

    if ((*bytesTransferred != chunkSize) && (ret == SCGT_SUCCESS))
        ret = SCGT_HARDWARE_ERROR;

    tools->pending = 0;

    if (!(flags & SCGT_RW_DMA_PHYS_ADDR))  /* not physical address */
    {
        if (dev->Is64BitOS )
        {
            const uint32 buf = exch->exchQIndex % SCGT_DMA_NUM_BUFFERS;
            if (direction == GTCORE_READ)
            {
                char * bufV;
                // warning C26489 : Don't dereference a pointer that may be invalid: 
                // '(PCHAR)(mdl->StartVa) + (__int64)(mdl->ByteOffset)'. 'mdl._MDL::StartVa' may have been invalidated at line 6355
                WARNING_SUPPRESS(26489)                
                bufV = (char*)MmGetMdlVirtualAddress(mdl);
                //memcpy(bufV,dev->DmaRead[buf].pData,exch->bytesTransferred);
                RtlCopyMemory(bufV, dev->DmaRead[buf].pData, exch->bytesTransferred);
#ifdef DBG_DMA
                ScgtDbgPrint(SCGT_DBG_XFER, "scgtXferChunk() complete READ from Buffer=%d \n", buf);
                if (dev->DmaRead[buf].Status != SCGT_DMA_FULL)
                {
                    ScgtDbgPrint(SCGT_DBG_ERROR, "SCGT error dev->DmaWrite[%d].Status = EMPTY. Cnt:%d\n", buf, dev->DmaRead[buf].Cnt);
                }
                dev->DmaRead[buf].Status = SCGT_DMA_EMPTY;
                dev->DmaRead[buf].Cnt--;
#endif
            }
#ifdef DBG_DMA
            else
            {
                ScgtDbgPrint(SCGT_DBG_XFER, "scgtXferChunk() Wrote from Buffer=%d \n", buf);
                if (dev->DmaWrite[buf].Status != SCGT_DMA_FULL)
                    ScgtDbgPrint(SCGT_DBG_ERROR, "SCGT error dev->DmaWrite[%d].Status = EMPTY\n", buf);
                dev->DmaWrite[buf].Status = SCGT_DMA_EMPTY;
                dev->DmaWrite[buf].Cnt--;
            }
#endif
        }
        /* unmap buffer */
        MmUnlockPages(mdl);
        IoFreeMdl(mdl);
    }

    ScgtDbgPrint(SCGT_DBG_XFER, "scgtXferChunk() end\n");

    return ret;
} // scgtXferChunk

/***************************************************************************//**
 * @fn  static int scgtBuildChainList( _In_ scgtDevice *dev, _In_ PMDL mdl,
 *                                     _In_ gtcoreExch *exch, _In_ const uint32 direction,
 *                                     _In_ uint32 chainLenBytes)
 *
 * @brief  Build a board-readable scatter-gather list from MDL
 *
 *
 * @param [in,out]  dev   If non-null, the dev.
 * @param  mdl            The mdl.
 * @param [in,out]  exch  If non-null, the exch.
 * @param  direction      The direction.
 * @param  chainLenBytes  The chain length in bytes.
 *
 * @return .
 *****************************************************************************/
static uint32 scgtBuildChainList(_In_ scgtDevice *dev, _In_ PMDL mdl, _In_ gtcoreExch *exch,
    _In_ const uint32 direction, _In_ uint32 chainLenBytes)
{

    ULONG mappedSize = 0;
    ULONG chainEntryNum = 0;

    uint32 *chainEntry = NULLPTR;
    PHYSICAL_ADDRESS dmaAddress = { 0,0 };
    dmaAddress.QuadPart = 0;
    char *bufV = NULLPTR;
    uint32 bufIndex = 0;
    dmaAddress.QuadPart = 0;

    //ASSERT(dev != NULLPTR);
    if (dev == NULLPTR)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "Error scgtBuildChainList() called with invalid dev pointer\n");
        return SCGT_BAD_PARAMETER;
    }

    ScgtDbgPrint(SCGT_DBG_DMA, "scgtBuildChainList() start \n");
    /*
     * Flush the cache before Writes and invalidate the cache before Reads
     * This is a null macro for x86 systems which are cache coherent with
     * respect to DMA operations.
     */
    if (dev->Is64BitOS)
    {
        KeFlushIoBuffers(mdl, (exch->direction == GTCORE_READ), TRUE);   /* our only target is x86 */
    }

    chainEntry = (uint32 *)exch->sgList[0];
    if (chainEntry == NULLPTR) return SCGT_BAD_PARAMETER;
    chainEntryNum = 0;

    bufV = (char*)MmGetMdlVirtualAddress(mdl);

    do
    {
        /*
         * map what we can.. try all that's left.
         */
        mappedSize = chainLenBytes;
        if (dev->Is64BitOS) 
        {
            bufIndex = exch->exchQIndex % SCGT_DMA_NUM_BUFFERS;
            if (bufIndex >= SCGT_DMA_NUM_BUFFERS)
            {
                ScgtDbgPrint(SCGT_DBG_ERROR, "SCGT buffIndex:%d >= SCGT_DMA_NUM_BUFFERS:%d ", bufIndex, SCGT_DMA_NUM_BUFFERS);
                bufIndex = 0;
            }
            if (exch->direction == GTCORE_WRITE)
            {
#ifdef DBG_DMA
                if (dev->DmaWrite[bufIndex].Status != SCGT_DMA_EMPTY)
                    ScgtDbgPrint(SCGT_DBG_ERROR, "SCGT error dev->DmaWrite[%d].Status = FULL\n", bufIndex);
                dev->DmaWrite[bufIndex].Status = SCGT_DMA_FULL;
#endif
                dmaAddress = dev->DmaWrite[bufIndex].physAddr;
                //memcpy(dev->DmaWrite[bufIndex].pData,bufV,mappedSize);
                RtlCopyMemory(dev->DmaWrite[bufIndex].pData, bufV, mappedSize);
                ScgtDbgPrint(SCGT_DBG_DATA, "write %u bytes  Buff Addr:%08x%08x\n",
                    mappedSize, dmaAddress.HighPart, dmaAddress.LowPart);
            }
            else
            {
#ifdef DBG_DMA
                if (dev->DmaRead[bufIndex].Status != SCGT_DMA_EMPTY)
                {
                    ScgtDbgPrint(SCGT_DBG_ERROR, "SCGT error dev->DmaRead[%d].Status = FULL\n", bufIndex);
                }
#endif
                dmaAddress = dev->DmaRead[bufIndex].physAddr;
                dev->DmaRead[bufIndex].Status = SCGT_DMA_FULL;
                dev->DmaRead[bufIndex].Cnt++;
                ScgtDbgPrint(SCGT_DBG_DATA, "Read %u bytes.  Buff Addr:%08x%08x\n",
                    mappedSize, dmaAddress.HighPart, dmaAddress.LowPart);
            }
        }
        else
        {
            ScgtDbgPrint(SCGT_DBG_DMA, " x86 MapTransfer ChanEntry %d Direction:%s mdl VirtAddr: %p\n",
                chainEntryNum, (exch->direction == GTCORE_READ) ? "Read" : "Write", bufV);
            KeFlushIoBuffers(mdl, (exch->direction == GTCORE_READ), TRUE);   /* our only target is x86 */
            ScgtDbgPrint(SCGT_DBG_DMA, "call MapTrasfer\n");
            WARNING_SUPPRESS( 6387 )
            dmaAddress = dev->dmaAdapter->DmaOperations->MapTransfer(dev->dmaAdapter, mdl, NULLPTR,
                bufV, &mappedSize,
                (BOOLEAN)(direction == GTCORE_WRITE));\
        }
        chainEntry[GTCORE_CE_TNS_CSR] = mappedSize >> 2;
        chainEntry[GTCORE_CE_BUF_ADD32] = dmaAddress.LowPart;
        chainEntry[GTCORE_CE_BUF_ADD64] = (uint32) dmaAddress.HighPart;
        chainEntry += (GTCORE_CE_SIZE / 4);

        chainEntryNum++;
        bufV += mappedSize;
        chainLenBytes -= mappedSize;
    } while ((chainLenBytes > 0) && (chainEntryNum < SCGT_DMA_CHAIN_LEN));


    /*
     * set the last chain entry bit
     */
    chainEntry -= (GTCORE_CE_SIZE / 4);
    chainEntry[GTCORE_CE_TNS_CSR] |= GTCORE_CE_LST;

    if ((chainEntryNum == SCGT_DMA_CHAIN_LEN) && (chainLenBytes > 0))
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, " chainEntryNum:%u == SCGT_DMA_CHAIN_LEN %u chainLenBytes=%u \n", \
            chainEntryNum, SCGT_DMA_CHAIN_LEN, chainLenBytes);
        ScgtDbgPrint(SCGT_DBG_ERROR, "buildChainList: out of chain entries!\n");
        return SCGT_INSUFFICIENT_RESOURCES;
    }
    ScgtDbgPrint(SCGT_DBG_DMA, "scgtBuildChainList() end\n");
    return SCGT_SUCCESS;
} // scgtBuildChainList
/** @} */

/*******************************************************************/
/************************ INTERRUPT SERVICE ************************/
/*******************************************************************/

/** @defgroup ISRCode SCRAMNet GT Interrupt service code
SCRAMNet GT Functions for processing interrupts events
@{
**/

/**************************************************************************//**
 * @fn  BOOLEAN scgtISR(IN PKINTERRUPT interrupt, IN OUT PVOID context)
 *
 * @brief  Interrupt service routine
 *
 * @param  interrupt   KInterrupt Pointer. The driver uses this pointer when acquiring or
                       releasing the interrupt spin lock for the interrupt.
                       The driver also uses this pointer when unregistering an InterruptService routine.
 * @param  context     Pointer to device  context.
 *
 * @return true if it succeeds, false if it fails.
 *****************************************************************************/
_Use_decl_annotations_
BOOLEAN scgtISR(IN PKINTERRUPT interrupt, IN OUT PVOID context)
{
    scgtDevice *dev = NULLPTR;
    PDEVICE_OBJECT deviceObject = (PDEVICE_OBJECT)context;
    uint32 intRet = 0;

    //DLC added UNREFERENCED_PARAMETER to prevent Warning C4100 Unreferenced formal parameter
    UNREFERENCED_PARAMETER(interrupt);

    //DLC added check for null device object
    if (deviceObject == NULLPTR)
    {
        return FALSE;
    }
 
    //  warning C26489: Don't dereference a pointer that may be invalid: 
    // '(scgtDevice *)(deviceObject->DeviceExtension)'. 'deviceObject._DEVICE_OBJECT::DeviceExtension' may have been invalidated at line 6668 
    WARNING_SUPPRESS( 26489 )
    dev = (scgtDevice *)deviceObject->DeviceExtension;
    if ((intRet = gtcoreHandleInterrupt(dev)) != 0)
    {
        if (intRet > 1)
        {
            /* call DPC */
            IoRequestDpc(deviceObject, NULLPTR, (PVOID)intRet);
        }

        return TRUE;
    }

    return FALSE;  /* not our interrupt */
} // scgtISR

#ifdef COMPLETE_DMA
/*
 * scgtDPC()
 *     delayed procedure call.. (aka: ISR bottom half)
 *     NOTE: more than 1 DPC can be running at once!  The call to gtcoreCompleteDMA
 *           has to be protected.
 */
void scgtDPC(IN PKDPC dpc, IN PDEVICE_OBJECT deviceObject, IN PIRP irp, IN PVOID context)
{
    scgtDevice *dev = (scgtDevice *)deviceObject->DeviceExtension;
    uint32 intRet;

    dev->stats[SCGT_STATS_DPC]++;

    intRet = (uint32)context;

    if (intRet & GTCORE_ISR_DMA)
    {
        dev->stats[SCGT_STATS_DPC_DMA]++;

        //KeAcquireSpinLockAtDpcLevel(&dev->dpcSpinLock);
        gtcoreCompleteDMA(dev);
        //KeReleaseSpinLockFromDpcLevel(&dev->dpcSpinLock);    
    }

    if (intRet & GTCORE_ISR_QUEUED_INTR)
    {
        scgtGiveIntrSemDPC(dev);
    }
}

#endif

/**************************************************************************//**
 * @fn  void scgtDPC(IN PKDPC dpc, IN PDEVICE_OBJECT deviceObject, IN PIRP irp,
 *                   IN PVOID context)
 *
 * @brief  SCGT Deferred procedure call used to release semaphores in
 * response to an Interrupts event. AKA ISR bottom half.
 *
 * @param  dpc            The dpc.
 * @param  deviceObject   The device object.
 * @param  irp            The irp.
 * @param  context        The context.
 *****************************************************************************/
_Use_decl_annotations_
void scgtDPC(IN PKDPC dpc, IN PDEVICE_OBJECT deviceObject, IN PIRP irp, IN PVOID context)
{
    scgtDevice *dev = (scgtDevice *)deviceObject->DeviceExtension;
    uint32 intRet = 0;

    //DLC added UNREFERENCED_PARAMETER to prevent Warning C4100 Unreferenced formal parameter
    UNREFERENCED_PARAMETER(irp);
    UNREFERENCED_PARAMETER(dpc);

    dev->stats[SCGT_STATS_DPC]++;

    //DLC intRet = (uint32) context;
    intRet = (uint32)((uintpsize)context);

    if (intRet & GTCORE_ISR_DMA)
    {
        ksysSemBGive(&dev->threadSem);
    }

    if (intRet & GTCORE_ISR_QUEUED_INTR)
    {
        scgtGiveIntrSemDPC(dev);
    }
} // scgtDPC

/**************************************************************************//**
 * @fn  void scgtInterruptThread( PVOID StartContext)
 *
 * @brief  SCGT interrupt thread.
 *
 * @param  StartContext   Context for the start.
 *****************************************************************************/
_Use_decl_annotations_
void scgtInterruptThread(PVOID StartContext)
{
    scgtDevice *dev=NULLPTR;
    if (StartContext == NULLPTR)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "Error scgtInterruptThread() called with invalid startContext\n");
        return;
    }
    dev = (scgtDevice *)StartContext;

    while (!dev->threadTimeToExit)
    {
        ksysSemBTake(&dev->threadSem);

        gtcoreCompleteDMA(dev);
    }

    ksysSemBDestroy(&dev->threadSem);
    PsTerminateSystemThread(STATUS_SUCCESS);
} // scgtInterruptThread

/**************************************************************************//**
 * @fn  NTSTATUS scgtStartThread( _In_ scgtDevice *dev)
 *
 * @brief  Start SCGT thread.
 *
 * @param [in,out]  dev   If non-null, the dev.
 * @return NTSTATUS STATUS_SUCCESS or error code
 *****************************************************************************/
NTSTATUS scgtStartThread(_In_ scgtDevice *dev)
{
    HANDLE hThread = NULLPTR;
    PKTHREAD thread = NULLPTR;
    NTSTATUS ret = STATUS_SUCCESS;

    ksysSemBCreate(&dev->threadSem);
    dev->threadTimeToExit = 0;

    ret = PsCreateSystemThread(&hThread, THREAD_ALL_ACCESS, NULLPTR, (HANDLE)NULLPTR, NULLPTR,
        scgtInterruptThread, dev);
    if (!NT_SUCCESS(ret))
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "PsCreateSystemThread call failed\n");
        return ret;
    }
    ObReferenceObjectByHandle(hThread, THREAD_ALL_ACCESS, NULLPTR, KernelMode,
        (PVOID*)&thread, NULLPTR);
    KeSetPriorityThread(thread, LOW_REALTIME_PRIORITY);
    return ret;
} // scgtStartThread

/**************************************************************************//**
 * @fn  void scgtStopThread( _In_ scgtDevice *dev)
 *
 * @brief  Stop  SCGT thread.
 *
 * @param [in,out]  dev   If non-null, the dev.
 *****************************************************************************/
inline void scgtStopThread( _In_ scgtDevice *dev )
{
    dev->threadTimeToExit = 1;
    ksysSemBGive(&dev->threadSem);
} // scgtStopThread


/**************************************************************************//**
 * @fn  uint32 scgtDrvExchChain(scgtDevice *dev, gtcoreExchMgrData *exchMgrData,
 *                           uint32 exchNum, uint8 direction, uint8 doAlloc)
 * @brief   Allocate and initialize or Deallocate a chain for the exchange.
 *
 * @param [in,out]  dev         If non-null, the dev.
 * @param [in,out]  exchMgrData If non-null, information describing the exch manager.
 * @param  exchNum              The exch number.
 * @param  direction            The direction.
 * @param  doAlloc              The do allocate.
 *
 * @return  Returns SCGT_SUCCESS if successful.
*****************************************************************************/
uint32 scgtDrvExchChain(scgtDevice *dev, gtcoreExchMgrData *exchMgrData,
    uint32 exchNum, uint8 direction, uint8 doAlloc)
{
    uint32 mySize = 0;
    int i = 0;
    volatile uint32 *chainEntry = NULLPTR;
    gtcoreExch *exch = NULLPTR;
    uint64 nextAddr = 0;
    PHYSICAL_ADDRESS logicalAddress;
    logicalAddress.QuadPart = 0L;

    if (dev == NULLPTR)
    {
        return SCGT_BAD_PARAMETER;
    }

    ScgtDbgPrint( SCGT_DBG_EXCH, "scgtDrvExchChain. %s %s # %d\n", 
                 (doAlloc == 0) ? " Free exchange" : "allocate exchange",
                 (direction == 0) ? "Read" : "Write", exchNum);

    exch = &exchMgrData->exchQ[exchNum];

    /* sgSpace of mySize is/will be larger than needed to cover alignment
       and cache safety */

#ifdef SCGT_EXCH_CHAIN_ONE_MAP
    mySize = GTCORE_CHAIN_BYTES * GTCORE_EXCH_CNT + GTCORE_CACHE_LINE_SIZE * 2;
#else
    mySize = GTCORE_CHAIN_BYTES + GTCORE_CACHE_LINE_SIZE * 2;
#endif

    if (doAlloc == 0)
    {
#ifdef SCGT_EXCH_CHAIN_ONE_MAP
        if (exchNum != 0)
            return SCGT_SUCCESS;
#endif
        if (exch->sgSpace != NULLPTR)
        {
            /* warning C26489: Don't dereference a pointer that may be invalid: 
             '(PCHAR)exch->sgList[0] - (PCHAR)(exch->sgSpace)'. 'exch._gtcoreExch::sgList' may have been invalidated at line 6858 (lifetime.1).*/
            WARNING_SUPPRESS( 26489 )
            logicalAddress.QuadPart = (LONGLONG) ( ((uint64)exch->sgListPhysAddr[0]) + 
                                                   ((PCHAR)exch->sgList[0] - (PCHAR)exch->sgSpace) );
            ScgtDbgPrint( SCGT_DBG_EXCH, "scgtDrvExchChain call free buffer at address 0x%08lx%08lx\n",
                logicalAddress.HighPart, logicalAddress.LowPart);
            dev->dmaAdapter->DmaOperations->FreeCommonBuffer(dev->dmaAdapter,
                mySize, logicalAddress, (PVOID)exch->sgSpace, FALSE);
        }
        return SCGT_SUCCESS;
    }
#ifdef SCGT_EXCH_CHAIN_ONE_MAP
    /**** allocate and map chain for only exchange 0
          use offsets into it for other exchanges ******/

    if (exchNum == 0)
    {
        /* allocate the space, align it, and map */
        exch->sgSpace = (uint32*)dev->dmaAdapter->DmaOperations->AllocateCommonBuffer(dev->dmaAdapter, mySize, &logicalAddress, FALSE);
        if (exch->sgSpace == NULLPTR)
            return SCGT_DRIVER_ERROR;
        ScgtDbgPrint(SCGT_DBG_EXCH, "scgtDrvExchChain call allocated buffer at address 0x%08lx%08lx\n",
            logicalAddress.HighPart, logicalAddress.LowPart);
        exch->sgList[0] = (uint32 *)gtcoreAlignAddr((void *)exch->sgSpace, GTCORE_CACHE_LINE_SIZE);

        exch->sgListPhysAddr[0] = logicalAddress.QuadPart - ((PCHAR)exch->sgList[0] - (PCHAR)exch->sgSpace)

        if (exch->sgListPhysAddr[0] == 0)
            return SCGT_DRIVER_ERROR;
    }
    else
    {
        /* here we use an offset into the already mapped address from exchange 0 */
        gtcoreExch *exch0 = &exchMgrData->exchQ[0];

        if (exch0->sgList[0] == NULLPTR)
            return SCGT_DRIVER_ERROR;

        /* sgList[0] is type uint32 * so we add num of words to get new ptr */
        exch->sgList[0] = exch0->sgList[0] + ((exchNum * GTCORE_CHAIN_BYTES) / 4);
        exch->sgListPhysAddr[0] = exch0->sgListPhysAddr[0] + (exchNum * GTCORE_CHAIN_BYTES);
        exch->sgDmaHandle = exch0->sgDmaHandle;
    }
#else
    /**** allocate and map chain for every exchange separately ******/
    exch->sgSpace = (uint32*)dev->dmaAdapter->DmaOperations->AllocateCommonBuffer(
        dev->dmaAdapter, mySize, &logicalAddress, FALSE);
    if (exch->sgSpace == NULLPTR)
        return SCGT_DRIVER_ERROR;

    exch->sgList[0] = (uint32 *)gtcoreAlignAddr((void *)exch->sgSpace, GTCORE_CACHE_LINE_SIZE);

    exch->sgListPhysAddr[0] = (uint64) logicalAddress.QuadPart - ((PCHAR)exch->sgList[0] - (PCHAR)exch->sgSpace);

    if (exch->sgListPhysAddr[0] == 0)
        return SCGT_DRIVER_ERROR;
#endif

    /* initialize each chain entry */
    chainEntry = exch->sgList[0];
    if (chainEntry == NULLPTR) return SCGT_DRIVER_ERROR;
    for (i = 0; i < GTCORE_EXCH_CNT; i++)
    {
        chainEntry[GTCORE_CE_BUF_ADD32] = 0;
        chainEntry[GTCORE_CE_TNS_CSR] = 0;
        chainEntry[GTCORE_CE_BUF_ADD64] = 0;

        chainEntry[GTCORE_CE_RESERVED_0] = direction << 24 | exchNum << 16 | i;  /* debugging purposes */
        chainEntry[GTCORE_CE_RESERVED_1] = 0;
        chainEntry[GTCORE_CE_RESERVED_2] = 0;

        /* assumes memory is physically contiguous */
        nextAddr = exch->sgListPhysAddr[0] + ((uint64)i + 1) * GTCORE_CE_SIZE;
        chainEntry[GTCORE_CE_NEXT_ADD32] = (uint32)nextAddr;
        chainEntry[GTCORE_CE_NEXT_ADD64] = (uint32)(nextAddr >> 32);

        chainEntry += (GTCORE_CE_SIZE / 4);  /* move to next chain queue entry */
    }

    return SCGT_SUCCESS;
} // scgtDrvExchChain

/**************************************************************************//**
 * @fn  uint32 scgtDrvTrQueue(scgtDevice *dev, 
                             gtcoreExchMgrData *exchMgrData,
                             uint8 direction, uint8 doAlloc)
 *
 * @brief  Setup SCGT transaction queue.
 *
 * @param [in,out]  dev         If non-null, the dev.
 * @param [in,out]  exchMgrData If non-null, information describing the exch manager.
 * @param  direction            The direction.
 * @param  doAlloc              The do allocate.
 *
 * @return SCGT_SUCCESS or error code on failure
 *****************************************************************************/
uint32 scgtDrvTrQueue(scgtDevice *dev, gtcoreExchMgrData *exchMgrData,
    uint8 direction, uint8 doAlloc)
{
    uint32 qSize = 0;
    uint32 i = 0;
    uint32 *tqe = NULLPTR;
    PHYSICAL_ADDRESS logicalAddress = { 0,0 };
    logicalAddress.QuadPart = 0;
    UNREFERENCED_PARAMETER(direction);

    //DLC added parameter check
    if (dev == NULLPTR)
    {
        return SCGT_BAD_PARAMETER;
    }

    qSize = (GTCORE_EXCH_CNT * GTCORE_TQE_SIZE) + (GTCORE_CACHE_LINE_SIZE * 2);

    ScgtDbgPrint(SCGT_DBG_TRQUEUE, "scgtDrvTrQueue. %s transfer queue.\n", (doAlloc == 0) ? " Free " : "allocate");

    if (doAlloc == 0)
    {
        if (exchMgrData->trSpace != NULLPTR)
        {
            /* free trSpace */
            if (exchMgrData->trSpace)
            {
                logicalAddress.QuadPart = (LONGLONG) (exchMgrData->trPhysical + ((PCHAR)exchMgrData->trSpace - (PCHAR)exchMgrData->trQueue));
                ScgtDbgPrint(SCGT_DBG_TRQUEUE, "Free Transaction queue. At LogicalAddress :0x%08x%08x", logicalAddress.HighPart, logicalAddress.LowPart);
                dev->dmaAdapter->DmaOperations->FreeCommonBuffer(dev->dmaAdapter,
                    qSize, logicalAddress, (PVOID)exchMgrData->trSpace, FALSE);
            }
            exchMgrData->trSpace = NULLPTR;
        }

        return SCGT_SUCCESS;
    }

    /* Allocate space for the Transaction Queues */
    /* Triple the size for alignment purposes */
    exchMgrData->trSpace = (uint32 *)dev->dmaAdapter->DmaOperations->AllocateCommonBuffer(
        dev->dmaAdapter, qSize, &logicalAddress, FALSE);
    if (exchMgrData->trSpace == NULLPTR)
        return SCGT_DRIVER_ERROR;

    memset(exchMgrData->trSpace, 0, qSize);
    exchMgrData->trQueue = (uint32*)gtcoreAlignAddr((void *)exchMgrData->trSpace, GTCORE_CACHE_LINE_SIZE);

    exchMgrData->trPhysical = (uint64)logicalAddress.QuadPart - ((PCHAR)exchMgrData->trSpace - (PCHAR)exchMgrData->trQueue);

    ScgtDbgPrint(SCGT_DBG_TRQUEUE, "Map Transaction queue. At LogicalAddress :0x%08x%08x\n", logicalAddress.HighPart, logicalAddress.LowPart);
    ScgtDbgPrint(SCGT_DBG_TRQUEUE, "exchMgrData->trQueue:0x%p  exchMgrData->trSpace:%p \n", exchMgrData->trQueue, exchMgrData->trSpace);

    if (exchMgrData->trPhysical == 0)
    {
        dev->dmaAdapter->DmaOperations->FreeCommonBuffer(dev->dmaAdapter,
            qSize, logicalAddress, (PVOID)exchMgrData->trSpace, FALSE);
        return SCGT_DRIVER_ERROR;
    }

    /* initialize some members of each transaction queue entry
       that won't change on a transfer by transfer basis */
    tqe = exchMgrData->trQueue;
    if (tqe == NULLPTR) return SCGT_DRIVER_ERROR;
    for (i = 0; i < GTCORE_EXCH_CNT; i++)
    {
        tqe[GTCORE_TQE_NI_CTL] = 0;
        tqe[GTCORE_TQE_RESERVED_0] = 0;
        tqe[GTCORE_TQE_RESERVED_1] = 0;
        tqe += (GTCORE_TQE_SIZE / 4);
    }

    return SCGT_SUCCESS;
} /* scgtDrvTrQueue*/

/** we are here*/
/** @} */