/*++

Copyright (C) Microsoft Corporation, 1991 - 2010

Module Name:

    class.c

Abstract:

    SCSI class driver routines

Environment:

    kernel mode only

Notes:


Revision History:

--*/

#define CLASS_INIT_GUID 1
#define DEBUG_MAIN_SOURCE 1

#include "classp.h"
#include "debug.h"
#include <process.h>
#include <devpkey.h>
#include <ntiologc.h>


#ifdef DEBUG_USE_WPP
#include "class.tmh"
#endif

#ifdef ALLOC_PRAGMA
    #pragma alloc_text(INIT, DriverEntry)
    #pragma alloc_text(PAGE, ClassAddDevice)
    #pragma alloc_text(PAGE, ClassClaimDevice)
    #pragma alloc_text(PAGE, ClassCreateDeviceObject)
    #pragma alloc_text(PAGE, ClassDispatchPnp)
    #pragma alloc_text(PAGE, ClassGetDescriptor)
    #pragma alloc_text(PAGE, ClassGetPdoId)
    #pragma alloc_text(PAGE, ClassInitialize)
    #pragma alloc_text(PAGE, ClassInitializeEx)
    #pragma alloc_text(PAGE, ClassInvalidateBusRelations)
    #pragma alloc_text(PAGE, ClassMarkChildMissing)
    #pragma alloc_text(PAGE, ClassMarkChildrenMissing)
    #pragma alloc_text(PAGE, ClassModeSense)
    #pragma alloc_text(PAGE, ClassPnpQueryFdoRelations)
    #pragma alloc_text(PAGE, ClassPnpStartDevice)
    #pragma alloc_text(PAGE, ClassQueryPnpCapabilities)
    #pragma alloc_text(PAGE, ClassQueryTimeOutRegistryValue)
    #pragma alloc_text(PAGE, ClassRemoveDevice)
    #pragma alloc_text(PAGE, ClassRetrieveDeviceRelations)
    #pragma alloc_text(PAGE, ClassUpdateInformationInRegistry)
    #pragma alloc_text(PAGE, ClassSendDeviceIoControlSynchronous)
    #pragma alloc_text(PAGE, ClassUnload)
    #pragma alloc_text(PAGE, ClasspAllocateReleaseRequest)
    #pragma alloc_text(PAGE, ClasspFreeReleaseRequest)
    #pragma alloc_text(PAGE, ClasspInitializeHotplugInfo)
    #pragma alloc_text(PAGE, ClasspRegisterMountedDeviceInterface)
    #pragma alloc_text(PAGE, ClasspScanForClassHacks)
    #pragma alloc_text(PAGE, ClasspScanForSpecialInRegistry)
    #pragma alloc_text(PAGE, ClasspModeSense)
    #pragma alloc_text(PAGE, ClasspIsPortable)
    #pragma alloc_text(PAGE, ClassAcquireChildLock)
    #pragma alloc_text(PAGE, ClassDetermineTokenOperationCommandSupport)
    #pragma alloc_text(PAGE, ClassDeviceProcessOffloadRead)
    #pragma alloc_text(PAGE, ClassDeviceProcessOffloadWrite)
    #pragma alloc_text(PAGE, ClasspServicePopulateTokenTransferRequest)
    #pragma alloc_text(PAGE, ClasspServiceWriteUsingTokenTransferRequest)
#if (NTDDI_VERSION >= NTDDI_WINBLUE)
    #pragma alloc_text(PAGE, ClassModeSenseEx)
#endif
#endif

#ifdef _MSC_VER
#pragma prefast(disable:28159, "There are certain cases when we have to bugcheck...")
#endif

IO_COMPLETION_ROUTINE ClassCheckVerifyComplete;


ULONG ClassPnpAllowUnload = TRUE;
ULONG ClassMaxInterleavePerCriticalIo = CLASS_MAX_INTERLEAVE_PER_CRITICAL_IO;
CONST LARGE_INTEGER Magic10000 = {{0xe219652c, 0xd1b71758}};
GUID StoragePredictFailureDPSGuid = WDI_STORAGE_PREDICT_FAILURE_DPS_GUID;

#define FirstDriveLetter 'C'
#define LastDriveLetter  'Z'

BOOLEAN UseQPCTime = FALSE;

//
// Keep track of whether security cookie is initialized or not. This is
// required by SDL.
//

BOOLEAN InitSecurityCookie = FALSE;

//
// List Identifier for offload data transfer operations
//
ULONG MaxTokenOperationListIdentifier = MAX_TOKEN_LIST_IDENTIFIERS;
volatile ULONG TokenOperationListIdentifier = (ULONG)-1;

//
// List of FDOs that have enabled idle power management.
//
LIST_ENTRY IdlePowerFDOList = {0};
KGUARDED_MUTEX IdlePowerFDOListMutex;

//
// Handle used to register for power setting notifications.
//
PVOID PowerSettingNotificationHandle;

//
// Handle used to register for screen state setting notifications.
//
PVOID ScreenStateNotificationHandle;

//
// Disk idle timeout in milliseconds.
// We default this to 0xFFFFFFFF as this is what the power manager considers
// "never" and ensures we do not set a disk idle timeout until the power
// manager calls us back with a different value.
//
ULONG DiskIdleTimeoutInMS = 0xFFFFFFFF;


NTSTATUS DllUnload(VOID)
{
    DbgPrintEx(DPFLTR_CLASSPNP_ID, DPFLTR_INFO_LEVEL, "classpnp.sys is now unloading\n");

    if (PowerSettingNotificationHandle) {
        PoUnregisterPowerSettingCallback(PowerSettingNotificationHandle);
        PowerSettingNotificationHandle = NULL;
    }

    if (ScreenStateNotificationHandle) {
        PoUnregisterPowerSettingCallback(ScreenStateNotificationHandle);
        ScreenStateNotificationHandle = NULL;
    }


    return STATUS_SUCCESS;
}


/*++////////////////////////////////////////////////////////////////////////////

DriverEntry()

Routine Description:

    Temporary entry point needed to initialize the class system dll.
    It doesn't do anything.

Arguments:

    DriverObject - Pointer to the driver object created by the system.

Return Value:

   STATUS_SUCCESS

--*/
NTSTATUS
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
DriverEntry(
    IN PDRIVER_OBJECT DriverObject,
    IN PUNICODE_STRING RegistryPath
    )
{
    UNREFERENCED_PARAMETER(DriverObject);
    UNREFERENCED_PARAMETER(RegistryPath);

    return STATUS_SUCCESS;
}



/*++////////////////////////////////////////////////////////////////////////////

ClassInitialize()

Routine Description:

    This routine is called by a class driver during its
    DriverEntry routine to initialize the driver.

Arguments:

    Argument1          - Driver Object.
    Argument2          - Registry Path.
    InitializationData - Device-specific driver's initialization data.

Return Value:

    A valid return code for a DriverEntry routine.

--*/
_IRQL_requires_max_(PASSIVE_LEVEL)
_Must_inspect_result_
ULONG
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassInitialize(
    _In_  PVOID            Argument1,
    _In_  PVOID            Argument2,
    _In_  PCLASS_INIT_DATA InitializationData
    )
{
    PDRIVER_OBJECT  DriverObject = Argument1;
    PUNICODE_STRING RegistryPath = Argument2;

    PCLASS_DRIVER_EXTENSION driverExtension;

    NTSTATUS        status;



    PAGED_CODE();

    //
    // Initialize the security cookie if needed.
    //
#ifndef __REACTOS__
    if (InitSecurityCookie == FALSE) {
        __security_init_cookie();
        InitSecurityCookie = TRUE;
    }
#endif


    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "\n\nSCSI Class Driver\n"));

    ClasspInitializeDebugGlobals();

    //
    // Validate the length of this structure. This is effectively a
    // version check.
    //

    if (InitializationData->InitializationDataSize != sizeof(CLASS_INIT_DATA)) {

        //
        // This DebugPrint is to help third-party driver writers
        //

        TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClassInitialize: Class driver wrong version\n"));
        return (ULONG) STATUS_REVISION_MISMATCH;
    }

    //
    // Check that each required entry is not NULL. Note that Shutdown, Flush and Error
    // are not required entry points.
    //

    if ((!InitializationData->FdoData.ClassDeviceControl) ||
        (!((InitializationData->FdoData.ClassReadWriteVerification) ||
           (InitializationData->ClassStartIo))) ||
        (!InitializationData->ClassAddDevice) ||
        (!InitializationData->FdoData.ClassStartDevice)) {

        //
        // This DebugPrint is to help third-party driver writers
        //

        TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT,
            "ClassInitialize: Class device-specific driver missing required "
            "FDO entry\n"));

        return (ULONG) STATUS_REVISION_MISMATCH;
    }

    if ((InitializationData->ClassEnumerateDevice) &&
        ((!InitializationData->PdoData.ClassDeviceControl) ||
         (!InitializationData->PdoData.ClassStartDevice) ||
         (!((InitializationData->PdoData.ClassReadWriteVerification) ||
            (InitializationData->ClassStartIo))))) {

        //
        // This DebugPrint is to help third-party driver writers
        //

        TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT,  "ClassInitialize: Class device-specific missing "
                       "required PDO entry\n"));

        return (ULONG) STATUS_REVISION_MISMATCH;
    }

    if((InitializationData->FdoData.ClassStopDevice == NULL) ||
        ((InitializationData->ClassEnumerateDevice != NULL) &&
         (InitializationData->PdoData.ClassStopDevice == NULL))) {

        //
        // This DebugPrint is to help third-party driver writers
        //

        TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT,  "ClassInitialize: Class device-specific missing "
                       "required PDO entry\n"));
        NT_ASSERT(FALSE);
        return (ULONG) STATUS_REVISION_MISMATCH;
    }

    //
    // Setup the default power handlers if the class driver didn't provide
    // any.
    //

    if(InitializationData->FdoData.ClassPowerDevice == NULL) {
        InitializationData->FdoData.ClassPowerDevice = ClassMinimalPowerHandler;
    }

    if((InitializationData->ClassEnumerateDevice != NULL) &&
       (InitializationData->PdoData.ClassPowerDevice == NULL)) {
        InitializationData->PdoData.ClassPowerDevice = ClassMinimalPowerHandler;
    }

    //
    // warn that unload is not supported
    //
    // ISSUE-2000/02/03-peterwie
    // We should think about making this a fatal error.
    //

    if(InitializationData->ClassUnload == NULL) {

        //
        // This DebugPrint is to help third-party driver writers
        //

        TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT,  "ClassInitialize: driver does not support unload %wZ\n",
                    RegistryPath));
    }

    //
    // Create an extension for the driver object
    //
#ifdef _MSC_VER
#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
#endif
    status = IoAllocateDriverObjectExtension(DriverObject, CLASS_DRIVER_EXTENSION_KEY, sizeof(CLASS_DRIVER_EXTENSION), (PVOID *)&driverExtension);

    if(NT_SUCCESS(status)) {

        //
        // Copy the registry path into the driver extension so we can use it later
        //

        driverExtension->RegistryPath.Length = RegistryPath->Length;
        driverExtension->RegistryPath.MaximumLength = RegistryPath->MaximumLength;

        driverExtension->RegistryPath.Buffer =
            ExAllocatePoolWithTag(PagedPool,
                                  RegistryPath->MaximumLength,
                                  '1CcS');

        if(driverExtension->RegistryPath.Buffer == NULL) {

            status = STATUS_INSUFFICIENT_RESOURCES;
            return status;
        }

        RtlCopyUnicodeString(
            &(driverExtension->RegistryPath),
            RegistryPath);

        //
        // Copy the initialization data into the driver extension so we can reuse
        // it during our add device routine
        //

        RtlCopyMemory(
            &(driverExtension->InitData),
            InitializationData,
            sizeof(CLASS_INIT_DATA));

        driverExtension->DeviceCount = 0;

        ClassInitializeDispatchTables(driverExtension);

    } else if (status == STATUS_OBJECT_NAME_COLLISION) {

        //
        // The extension already exists - get a pointer to it
        //
#ifdef _MSC_VER
#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
#endif
        driverExtension = IoGetDriverObjectExtension(DriverObject, CLASS_DRIVER_EXTENSION_KEY);

        NT_ASSERT(driverExtension != NULL);

    } else {

        TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT,  "ClassInitialize: Class driver extension could not be "
                       "allocated %lx\n", status));
        return status;
    }


    //
    // Update driver object with entry points.
    //

#ifdef _MSC_VER
#pragma prefast(push)
#pragma prefast(disable:28175, "Accessing DRIVER_OBJECT fileds is OK here since this function " \
                               "is supposed to be invoked from DriverEntry only")
#endif
    DriverObject->MajorFunction[IRP_MJ_CREATE]          = ClassGlobalDispatch;
    DriverObject->MajorFunction[IRP_MJ_CLOSE]           = ClassGlobalDispatch;
    DriverObject->MajorFunction[IRP_MJ_READ]            = ClassGlobalDispatch;
    DriverObject->MajorFunction[IRP_MJ_WRITE]           = ClassGlobalDispatch;
    DriverObject->MajorFunction[IRP_MJ_SCSI]            = ClassGlobalDispatch;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]  = ClassGlobalDispatch;
    DriverObject->MajorFunction[IRP_MJ_SHUTDOWN]        = ClassGlobalDispatch;
    DriverObject->MajorFunction[IRP_MJ_FLUSH_BUFFERS]   = ClassGlobalDispatch;
    DriverObject->MajorFunction[IRP_MJ_PNP]             = ClassGlobalDispatch;
    DriverObject->MajorFunction[IRP_MJ_POWER]           = ClassGlobalDispatch;
    DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL]  = ClassGlobalDispatch;

    if (InitializationData->ClassStartIo) {
        DriverObject->DriverStartIo = ClasspStartIo;
    }

    if ((InitializationData->ClassUnload) && (ClassPnpAllowUnload == TRUE)) {
        DriverObject->DriverUnload = ClassUnload;
    } else {
        DriverObject->DriverUnload = NULL;
    }

    DriverObject->DriverExtension->AddDevice = ClassAddDevice;
#ifdef _MSC_VER
#pragma prefast(pop)
#endif


    //
    // Register for event tracing
    //
    if (driverExtension->EtwHandle == 0) {
        status = EtwRegister(&StoragePredictFailureDPSGuid,
                             NULL,
                             NULL,
                             &driverExtension->EtwHandle);
        if (!NT_SUCCESS(status)) {
            driverExtension->EtwHandle = 0;
        }
        WPP_INIT_TRACING(DriverObject, RegistryPath);
    }


    //
    // Ensure these are only initialized once.
    //
    if (IdlePowerFDOList.Flink == NULL) {
        InitializeListHead(&IdlePowerFDOList);
        KeInitializeGuardedMutex(&IdlePowerFDOListMutex);
    }

    status = STATUS_SUCCESS;
    return status;
} // end ClassInitialize()

/*++////////////////////////////////////////////////////////////////////////////

ClassInitializeEx()

Routine Description:

    This routine is allows the caller to do any extra initialization or
    setup that is not done in ClassInitialize. The operation is
    controlled by the GUID that is passed and the contents of the Data
    parameter is dependent upon the GUID.

    This is the list of supported operations:

    GUID_CLASSPNP_QUERY_REGINFOEX == CLASS_QUERY_WMI_REGINFO_EX_LIST

        Initialized classpnp to callback a PCLASS_QUERY_WMI_REGINFO_EX
        callback instead of a PCLASS_QUERY_WMI_REGINFO callback. The
        former callback allows the driver to specify the name of the
        mof resource.

    GUID_CLASSPNP_SENSEINFO2      == CLASS_INTERPRET_SENSE_INFO2

        Initialize classpnp to callback into class drive for interpretation
        of all sense info, and to indicate the count of "history" to keep
        for each packet.

    GUID_CLASSPNP_WORKING_SET     == CLASS_WORKING_SET

        Allow class driver to override the min and max working set transfer
        packet value used in classpnp.

    GUID_CLASSPNP_SRB_SUPPORT     == ULONG

        Allow class driver to provide supported SRB types.

Arguments:

    DriverObject
    Guid
    Data

Return Value:

    Status Code

--*/
_IRQL_requires_max_(PASSIVE_LEVEL)
_Must_inspect_result_
ULONG
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassInitializeEx(
    _In_  PDRIVER_OBJECT   DriverObject,
    _In_  LPGUID           Guid,
    _In_  PVOID            Data
    )
{
    PCLASS_DRIVER_EXTENSION driverExtension;

    NTSTATUS        status;

    PAGED_CODE();

#ifdef _MSC_VER
#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
#endif
    driverExtension = IoGetDriverObjectExtension( DriverObject, CLASS_DRIVER_EXTENSION_KEY );

    if (driverExtension == NULL)
    {
        NT_ASSERT(FALSE);
        return (ULONG)STATUS_UNSUCCESSFUL;
    }

    if (IsEqualGUID(Guid, &ClassGuidQueryRegInfoEx))
    {
        PCLASS_QUERY_WMI_REGINFO_EX_LIST List;

        //
        // Indicate the device supports PCLASS_QUERY_REGINFO_EX
        // callback instead of PCLASS_QUERY_REGINFO callback.
        //
        List = (PCLASS_QUERY_WMI_REGINFO_EX_LIST)Data;

        if (List->Size == sizeof(CLASS_QUERY_WMI_REGINFO_EX_LIST))
        {
            driverExtension->ClassFdoQueryWmiRegInfoEx = List->ClassFdoQueryWmiRegInfoEx;
            driverExtension->ClassPdoQueryWmiRegInfoEx = List->ClassPdoQueryWmiRegInfoEx;
            status = STATUS_SUCCESS;
        } else {
            status = STATUS_INVALID_PARAMETER;
        }
    }
    else if (IsEqualGUID(Guid, &ClassGuidWorkingSet))
    {
        PCLASS_WORKING_SET infoOriginal = (PCLASS_WORKING_SET)Data;
        PCLASS_WORKING_SET info = NULL;

        // only try to allocate memory for cached copy if size is correct
        if (infoOriginal->Size != sizeof(CLASS_WORKING_SET))
        {
            // incorrect size -- client programming error
            status = STATUS_INVALID_PARAMETER;
        }
        else
        {
            info = ExAllocatePoolWithTag(NonPagedPoolNx,
                                         sizeof(CLASS_WORKING_SET),
                                         CLASS_TAG_WORKING_SET
                                         );
            if (info == NULL)
            {
                status = STATUS_INSUFFICIENT_RESOURCES;
            }
            else
            {
                // cache the structure internally
                RtlCopyMemory(info, infoOriginal, sizeof(CLASS_WORKING_SET));
                status = STATUS_SUCCESS;
            }
        }
        // if we successfully cached a copy, validate all the data within
        if (NT_SUCCESS(status))
        {
            if (info->Size != sizeof(CLASS_WORKING_SET))
            {
                // incorrect size -- client programming error
                status = STATUS_INVALID_PARAMETER;
            }
            else if (info->XferPacketsWorkingSetMaximum > CLASS_WORKING_SET_MAXIMUM)
            {
                // too many requested in the working set
                status = STATUS_INVALID_PARAMETER;
            }
            else if (info->XferPacketsWorkingSetMinimum > CLASS_WORKING_SET_MAXIMUM)
            {
                // too many requested in the working set
                status = STATUS_INVALID_PARAMETER;
            }
            else if (driverExtension->InitData.FdoData.DeviceType != FILE_DEVICE_CD_ROM)
            {
                // classpnp developer wants to restrict this code path
                // for now to CDROM devices only.
                status = STATUS_INVALID_DEVICE_REQUEST;
            }
            else if (driverExtension->WorkingSet != NULL)
            {
                // not allowed to change it once it is set for a driver
                status = STATUS_INVALID_PARAMETER;
                NT_ASSERT(FALSE);
            }
        }
        // save results or cleanup
        if (NT_SUCCESS(status))
        {
            driverExtension->WorkingSet = info; info = NULL;
        }
        else
        {
            FREE_POOL( info );
        }
    }
    else if (IsEqualGUID(Guid, &ClassGuidSenseInfo2))
    {
        PCLASS_INTERPRET_SENSE_INFO2 infoOriginal = (PCLASS_INTERPRET_SENSE_INFO2)Data;
        PCLASS_INTERPRET_SENSE_INFO2 info = NULL;

        // only try to allocate memory for cached copy if size is correct
        if (infoOriginal->Size != sizeof(CLASS_INTERPRET_SENSE_INFO2))
        {
            // incorrect size -- client programming error
            status = STATUS_INVALID_PARAMETER;
        }
        else
        {
            info = ExAllocatePoolWithTag(NonPagedPoolNx,
                                         sizeof(CLASS_INTERPRET_SENSE_INFO2),
                                         CLASS_TAG_SENSE2
                                         );
            if (info == NULL)
            {
                status = STATUS_INSUFFICIENT_RESOURCES;
            }
            else
            {
                // cache the structure internally
                RtlCopyMemory(info, infoOriginal, sizeof(CLASS_INTERPRET_SENSE_INFO2));
                status = STATUS_SUCCESS;
            }
        }

        // if we successfully cached a copy, validate all the data within
        if (NT_SUCCESS(status))
        {
            if (info->Size != sizeof(CLASS_INTERPRET_SENSE_INFO2))
            {
                // incorrect size -- client programming error
                status = STATUS_INVALID_PARAMETER;
            }
            else if (info->HistoryCount > CLASS_INTERPRET_SENSE_INFO2_MAXIMUM_HISTORY_COUNT)
            {
                // incorrect count -- client programming error
                status = STATUS_INVALID_PARAMETER;
            }
            else if (info->Compress == NULL)
            {
                // Compression of the history is required to be supported
                status = STATUS_INVALID_PARAMETER;
            }
            else if (info->HistoryCount == 0)
            {
                // History count cannot be zero
                status = STATUS_INVALID_PARAMETER;
            }
            else if (info->Interpret == NULL)
            {
                // Updated interpret sense info function is required
                status = STATUS_INVALID_PARAMETER;
            }
            else if (driverExtension->InitData.FdoData.DeviceType != FILE_DEVICE_CD_ROM)
            {
                // classpnp developer wants to restrict this code path
                // for now to CDROM devices only.
                status = STATUS_INVALID_DEVICE_REQUEST;
            }
            else if (driverExtension->InterpretSenseInfo != NULL)
            {
                // not allowed to change it once it is set for a driver
                status = STATUS_INVALID_PARAMETER;
                NT_ASSERT(FALSE);
            }
        }

        // save results or cleanup
        if (NT_SUCCESS(status))
        {
            driverExtension->InterpretSenseInfo = info; info = NULL;
        }
        else
        {
            FREE_POOL( info );
        }
    }
    else if (IsEqualGUID(Guid, &ClassGuidSrbSupport))
    {
        ULONG srbSupport = *((PULONG)Data);

        //
        // Validate that at least one of the supported bit flags is set. Assume
        // all class drivers support SCSI_REQUEST_BLOCK as a class driver that
        // supports only extended SRB is not feasible.
        //
        if ((srbSupport &
             (CLASS_SRB_SCSI_REQUEST_BLOCK | CLASS_SRB_STORAGE_REQUEST_BLOCK)) != 0) {
            driverExtension->SrbSupport = srbSupport;
            status = STATUS_SUCCESS;

            //
            // Catch cases of a class driver reporting only extended SRB support
            //
            if ((driverExtension->SrbSupport & CLASS_SRB_SCSI_REQUEST_BLOCK) == 0) {
                NT_ASSERT(FALSE);
            }
        } else {
            status = STATUS_INVALID_PARAMETER;
        }
    }
    else
    {
        status = STATUS_NOT_SUPPORTED;
    }

    return status;

} // end ClassInitializeEx()

/*++////////////////////////////////////////////////////////////////////////////

ClassUnload()

Routine Description:

    called when there are no more references to the driver.  this allows
    drivers to be updated without rebooting.

Arguments:

    DriverObject - a pointer to the driver object that is being unloaded

Status:

--*/
VOID
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassUnload(
    IN PDRIVER_OBJECT DriverObject
    )
{
    PCLASS_DRIVER_EXTENSION driverExtension;

    PAGED_CODE();

    NT_ASSERT( DriverObject->DeviceObject == NULL );

#ifdef _MSC_VER
#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
#endif
    driverExtension = IoGetDriverObjectExtension( DriverObject, CLASS_DRIVER_EXTENSION_KEY );


    if (driverExtension == NULL)
    {
        NT_ASSERT(FALSE);
        return;
    }

    NT_ASSERT(driverExtension->RegistryPath.Buffer != NULL);
    NT_ASSERT(driverExtension->InitData.ClassUnload != NULL);

    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,  "ClassUnload: driver unloading %wZ\n",
                &driverExtension->RegistryPath));

    //
    // attempt to process the driver's unload routine first.
    //

    driverExtension->InitData.ClassUnload(DriverObject);

    //
    // free own allocated resources and return
    //

    FREE_POOL( driverExtension->WorkingSet          );
    FREE_POOL( driverExtension->InterpretSenseInfo  );
    FREE_POOL( driverExtension->RegistryPath.Buffer );
    driverExtension->RegistryPath.Length = 0;
    driverExtension->RegistryPath.MaximumLength = 0;


    //
    // Unregister ETW
    //
    if (driverExtension->EtwHandle != 0) {
        EtwUnregister(driverExtension->EtwHandle);
        driverExtension->EtwHandle = 0;

        WPP_CLEANUP(DriverObject);
    }


    return;
} // end ClassUnload()

/*++////////////////////////////////////////////////////////////////////////////

ClassAddDevice()

Routine Description:

    SCSI class driver add device routine.  This is called by pnp when a new
    physical device come into being.

    This routine will call out to the class driver to verify that it should
    own this device then will create and attach a device object and then hand
    it to the driver to initialize and create symbolic links

Arguments:

    DriverObject - a pointer to the driver object that this is being created for
    PhysicalDeviceObject - a pointer to the physical device object

Status: STATUS_NO_SUCH_DEVICE if the class driver did not want this device
    STATUS_SUCCESS if the creation and attachment was successful
    status of device creation and initialization

--*/
NTSTATUS
#ifdef _MSC_VER
#pragma prefast(suppress:28152, "We expect the class driver to clear the DO_DEVICE_INITIALIZING flag in its AddDevice routine.")
#endif
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassAddDevice(
    IN PDRIVER_OBJECT DriverObject,
    IN PDEVICE_OBJECT PhysicalDeviceObject
    )
{
#ifdef _MSC_VER
#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
#endif
    PCLASS_DRIVER_EXTENSION driverExtension = IoGetDriverObjectExtension(DriverObject, CLASS_DRIVER_EXTENSION_KEY);

    NTSTATUS status;

    PAGED_CODE();


    status = driverExtension->InitData.ClassAddDevice(DriverObject,
                                              PhysicalDeviceObject);

    return status;
} // end ClassAddDevice()

/*++////////////////////////////////////////////////////////////////////////////

ClassDispatchPnp()

Routine Description:

    Storage class driver pnp routine.  This is called by the io system when
    a PNP request is sent to the device.

Arguments:

    DeviceObject - pointer to the device object

    Irp - pointer to the io request packet

Return Value:

    status

--*/
NTSTATUS
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassDispatchPnp(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
{
    PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
    BOOLEAN isFdo = commonExtension->IsFdo;

    PCLASS_DRIVER_EXTENSION driverExtension;
    PCLASS_INIT_DATA initData;
    PCLASS_DEV_INFO devInfo;

    PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);

    NTSTATUS status = Irp->IoStatus.Status;
    BOOLEAN completeRequest = TRUE;
    BOOLEAN lockReleased = FALSE;


    PAGED_CODE();

    //
    // Extract all the useful information out of the driver object
    // extension
    //

#ifdef _MSC_VER
#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
#endif
    driverExtension = IoGetDriverObjectExtension(DeviceObject->DriverObject, CLASS_DRIVER_EXTENSION_KEY);

    if (driverExtension){

        initData = &(driverExtension->InitData);

        if(isFdo) {
            devInfo = &(initData->FdoData);
        } else {
            devInfo = &(initData->PdoData);
        }

        ClassAcquireRemoveLock(DeviceObject, Irp);

        TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP,  "ClassDispatchPnp (%p,%p): minor code %#x for %s %p\n",
                       DeviceObject, Irp,
                       irpStack->MinorFunction,
                       isFdo ? "fdo" : "pdo",
                       DeviceObject));
        TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP,  "ClassDispatchPnp (%p,%p): previous %#x, current %#x\n",
                       DeviceObject, Irp,
                       commonExtension->PreviousState,
                       commonExtension->CurrentState));


        switch(irpStack->MinorFunction) {

            case IRP_MN_START_DEVICE: {

                //
                // if this is sent to the FDO we should forward it down the
                // attachment chain before we start the FDO.
                //

                if (isFdo) {
                    status = ClassForwardIrpSynchronous(commonExtension, Irp);
                }
                else {
                    status = STATUS_SUCCESS;
                }

                if (NT_SUCCESS(status)){
                    status = Irp->IoStatus.Status = ClassPnpStartDevice(DeviceObject);
                }

                break;
            }


            case IRP_MN_QUERY_DEVICE_RELATIONS: {

                DEVICE_RELATION_TYPE type =
                    irpStack->Parameters.QueryDeviceRelations.Type;

                PDEVICE_RELATIONS deviceRelations = NULL;


                if(!isFdo) {

                    if(type == TargetDeviceRelation) {

                        //
                        // Device relations has one entry built in to it's size.
                        //

                        status = STATUS_INSUFFICIENT_RESOURCES;

                        deviceRelations = ExAllocatePoolWithTag(PagedPool,
                                                         sizeof(DEVICE_RELATIONS),
                                                         '2CcS');

                        if(deviceRelations != NULL) {

                            RtlZeroMemory(deviceRelations,
                                          sizeof(DEVICE_RELATIONS));

                            Irp->IoStatus.Information = (ULONG_PTR) deviceRelations;

                            deviceRelations->Count = 1;
                            deviceRelations->Objects[0] = DeviceObject;
                            ObReferenceObject(deviceRelations->Objects[0]);

                            status = STATUS_SUCCESS;
                        }

                    } else {
                        //
                        // PDO's just complete enumeration requests without altering
                        // the status.
                        //

                        status = Irp->IoStatus.Status;
                    }

                    break;

                } else if (type == BusRelations) {

                    NT_ASSERT(commonExtension->IsInitialized);

                    //
                    // Make sure we support enumeration
                    //

                    if(initData->ClassEnumerateDevice == NULL) {

                        //
                        // Just send the request down to the lower driver.  Perhaps
                        // It can enumerate children.
                        //

                    } else {

                        //
                        // Re-enumerate the device
                        //

                        status = ClassPnpQueryFdoRelations(DeviceObject, Irp);

                        if(!NT_SUCCESS(status)) {
                            completeRequest = TRUE;
                            break;
                        }
                    }
                }


                IoCopyCurrentIrpStackLocationToNext(Irp);
                ClassReleaseRemoveLock(DeviceObject, Irp);
                status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
                completeRequest = FALSE;

                break;
            }

            case IRP_MN_QUERY_ID: {

                BUS_QUERY_ID_TYPE idType = irpStack->Parameters.QueryId.IdType;
                UNICODE_STRING unicodeString;


                if(isFdo) {


                    //
                    // FDO's should just forward the query down to the lower
                    // device objects
                    //

                    IoCopyCurrentIrpStackLocationToNext(Irp);
                    ClassReleaseRemoveLock(DeviceObject, Irp);

                    status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
                    completeRequest = FALSE;
                    break;
                }

                //
                // PDO's need to give an answer - this is easy for now
                //

                RtlInitUnicodeString(&unicodeString, NULL);

                status = ClassGetPdoId(DeviceObject,
                                       idType,
                                       &unicodeString);

                if(status == STATUS_NOT_IMPLEMENTED) {
                    //
                    // The driver doesn't implement this ID (whatever it is).
                    // Use the status out of the IRP so that we don't mangle a
                    // response from someone else.
                    //

                    status = Irp->IoStatus.Status;
                } else if(NT_SUCCESS(status)) {
                    Irp->IoStatus.Information = (ULONG_PTR) unicodeString.Buffer;
                } else {
                    Irp->IoStatus.Information = (ULONG_PTR) NULL;
                }

                break;
            }

            case IRP_MN_QUERY_STOP_DEVICE:
            case IRP_MN_QUERY_REMOVE_DEVICE: {


                TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP,  "ClassDispatchPnp (%p,%p): Processing QUERY_%s irp\n",
                            DeviceObject, Irp,
                            ((irpStack->MinorFunction == IRP_MN_QUERY_STOP_DEVICE) ?
                             "STOP" : "REMOVE")));

                //
                // If this device is in use for some reason (paging, etc...)
                // then we need to fail the request.
                //

                if(commonExtension->PagingPathCount != 0) {

                    TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP,  "ClassDispatchPnp (%p,%p): device is in paging "
                                "path and cannot be removed\n",
                                DeviceObject, Irp));
                    status = STATUS_DEVICE_BUSY;
                    break;
                }


                //
                // Check with the class driver to see if the query operation
                // can succeed.
                //

                if(irpStack->MinorFunction == IRP_MN_QUERY_STOP_DEVICE) {
                    status = devInfo->ClassStopDevice(DeviceObject,
                                                      irpStack->MinorFunction);
                } else {
                    status = devInfo->ClassRemoveDevice(DeviceObject,
                                                        irpStack->MinorFunction);
                }

                if(NT_SUCCESS(status)) {

                    //
                    // ASSERT that we never get two queries in a row, as
                    // this will severly mess up the state machine
                    //
                    NT_ASSERT(commonExtension->CurrentState != irpStack->MinorFunction);
                    commonExtension->PreviousState = commonExtension->CurrentState;
                    commonExtension->CurrentState = irpStack->MinorFunction;

                    if(isFdo) {
                        TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP,  "ClassDispatchPnp (%p,%p): Forwarding QUERY_"
                                    "%s irp\n", DeviceObject, Irp,
                                    ((irpStack->MinorFunction == IRP_MN_QUERY_STOP_DEVICE) ?
                                     "STOP" : "REMOVE")));
                        status = ClassForwardIrpSynchronous(commonExtension, Irp);
                    }
                }


                TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP,  "ClassDispatchPnp (%p,%p): Final status == %x\n",
                            DeviceObject, Irp, status));

                break;
            }

            case IRP_MN_CANCEL_STOP_DEVICE:
            case IRP_MN_CANCEL_REMOVE_DEVICE: {


                //
                // Check with the class driver to see if the query or cancel
                // operation can succeed.
                //

                if(irpStack->MinorFunction == IRP_MN_CANCEL_STOP_DEVICE) {
                    status = devInfo->ClassStopDevice(DeviceObject,
                                                      irpStack->MinorFunction);
                    NT_ASSERTMSG("ClassDispatchPnp !! CANCEL_STOP_DEVICE should never be failed\n", NT_SUCCESS(status));
                } else {
                    status = devInfo->ClassRemoveDevice(DeviceObject,
                                                        irpStack->MinorFunction);
                    NT_ASSERTMSG("ClassDispatchPnp !! CANCEL_REMOVE_DEVICE should never be failed\n", NT_SUCCESS(status));
                }

                Irp->IoStatus.Status = status;

                //
                // We got a CANCEL - roll back to the previous state only
                // if the current state is the respective QUERY state.
                //

                if(((irpStack->MinorFunction == IRP_MN_CANCEL_STOP_DEVICE) &&
                    (commonExtension->CurrentState == IRP_MN_QUERY_STOP_DEVICE)
                    ) ||
                   ((irpStack->MinorFunction == IRP_MN_CANCEL_REMOVE_DEVICE) &&
                    (commonExtension->CurrentState == IRP_MN_QUERY_REMOVE_DEVICE)
                    )
                   ) {

                    commonExtension->CurrentState =
                        commonExtension->PreviousState;
                    commonExtension->PreviousState = 0xff;

                }


                if(isFdo) {


                    IoCopyCurrentIrpStackLocationToNext(Irp);
                    ClassReleaseRemoveLock(DeviceObject, Irp);
                    status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
                    completeRequest = FALSE;
                } else {
                    status = STATUS_SUCCESS;
                }

                break;
            }

            case IRP_MN_STOP_DEVICE: {


                //
                // These all mean nothing to the class driver currently.  The
                // port driver will handle all queueing when necessary.
                //

                TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP,  "ClassDispatchPnp (%p,%p): got stop request for %s\n",
                            DeviceObject, Irp,
                            (isFdo ? "fdo" : "pdo")
                            ));

                NT_ASSERT(commonExtension->PagingPathCount == 0);

                //
                // ISSUE-2000/02/03-peterwie
                // if we stop the timer here then it means no class driver can
                // do i/o in its ClassStopDevice routine.  This is because the
                // retry (among other things) is tied into the tick handler
                // and disabling retries could cause the class driver to deadlock.
                // Currently no class driver we're aware of issues i/o in its
                // Stop routine but this is a case we may want to defend ourself
                // against.
                //

                ClasspDisableTimer((PFUNCTIONAL_DEVICE_EXTENSION)commonExtension);


                status = devInfo->ClassStopDevice(DeviceObject, IRP_MN_STOP_DEVICE);

                NT_ASSERTMSG("ClassDispatchPnp !! STOP_DEVICE should never be failed\n", NT_SUCCESS(status));

                if(isFdo) {
                    status = ClassForwardIrpSynchronous(commonExtension, Irp);
                }

                if(NT_SUCCESS(status)) {
                    commonExtension->CurrentState = irpStack->MinorFunction;
                    commonExtension->PreviousState = 0xff;
                }


                break;
            }

            case IRP_MN_REMOVE_DEVICE:
            case IRP_MN_SURPRISE_REMOVAL: {
                UCHAR removeType = irpStack->MinorFunction;

                //
                // Log a sytem event when non-removable disks are surprise-removed.
                //
                if (isFdo &&
                    (removeType == IRP_MN_SURPRISE_REMOVAL)) {

                    PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
                    PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
                    BOOLEAN logSurpriseRemove = TRUE;
                    STORAGE_BUS_TYPE busType = fdoExtension->DeviceDescriptor->BusType;

                    //
                    // Don't log an event for VHDs
                    //
                    if (busType == BusTypeFileBackedVirtual) {
                        logSurpriseRemove = FALSE;

                    } else if (fdoData->HotplugInfo.MediaRemovable) {
                        logSurpriseRemove = FALSE;

                    } else if (fdoData->HotplugInfo.DeviceHotplug && ( busType == BusTypeUsb || busType == BusType1394)) {

                        /*
                        This device is reported as DeviceHotplug but since the busType is Usb or 1394, don't log an event
                        Note that some storage arrays may report DeviceHotplug and we would like to log an event in those cases
                        */

                        logSurpriseRemove = FALSE;
                    }

                    if (logSurpriseRemove) {

                        ClasspLogSystemEventWithDeviceNumber(DeviceObject, IO_WARNING_DISK_SURPRISE_REMOVED);
                    }

                }

                if (commonExtension->PagingPathCount != 0) {
                    TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): paging device is getting removed!", DeviceObject, Irp));
                }

                //
                // Release the lock for this IRP before calling in.
                //
                ClassReleaseRemoveLock(DeviceObject, Irp);
                lockReleased = TRUE;

                /*
                 *  Set IsRemoved before propagating the REMOVE down the stack.
                 *  This keeps class-initiated I/O (e.g. the MCN irp) from getting sent
                 *  after we propagate the remove.
                 */
                commonExtension->IsRemoved = REMOVE_PENDING;

                /*
                 *  If a timer was started on the device, stop it.
                 */
                 ClasspDisableTimer((PFUNCTIONAL_DEVICE_EXTENSION)commonExtension);

                /*
                 *  "Fire-and-forget" the remove irp to the lower stack.
                 *  Don't touch the irp (or the irp stack!) after this.
                 */
                if (isFdo) {


                    IoCopyCurrentIrpStackLocationToNext(Irp);
                    status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
                    NT_ASSERT(NT_SUCCESS(status));
                    completeRequest = FALSE;
                }
                else {
                    status = STATUS_SUCCESS;
                }

                /*
                 *  Do our own cleanup and call the class driver's remove
                 *  cleanup routine.
                 *  For IRP_MN_REMOVE_DEVICE, this also deletes our device object,
                 *  so don't touch the extension after this.
                 */
                commonExtension->PreviousState = commonExtension->CurrentState;
                commonExtension->CurrentState = removeType;
                ClassRemoveDevice(DeviceObject, removeType);

                break;
            }

            case IRP_MN_DEVICE_USAGE_NOTIFICATION: {

                DEVICE_USAGE_NOTIFICATION_TYPE type = irpStack->Parameters.UsageNotification.Type;
                BOOLEAN setPagable;


                switch(type) {

                    case DeviceUsageTypePaging: {

                        if ((irpStack->Parameters.UsageNotification.InPath) &&
                            (commonExtension->CurrentState != IRP_MN_START_DEVICE)) {

                            //
                            // Device isn't started.  Don't allow adding a
                            // paging file, but allow a removal of one.
                            //

                            status = STATUS_DEVICE_NOT_READY;
                            break;
                        }

                        NT_ASSERT(commonExtension->IsInitialized);

                        /*
                         *  Ensure that this user thread is not suspended while we are holding the PathCountEvent.
                         */
                        KeEnterCriticalRegion();

                        (VOID)KeWaitForSingleObject(&commonExtension->PathCountEvent,
                                                    Executive, KernelMode,
                                                    FALSE, NULL);
                        status = STATUS_SUCCESS;

                        //
                        // If the volume is removable we should try to lock it in
                        // place or unlock it once per paging path count
                        //

                        if (commonExtension->IsFdo){
                            status = ClasspEjectionControl(
                                            DeviceObject,
                                            Irp,
                                            InternalMediaLock,
                                            (BOOLEAN)irpStack->Parameters.UsageNotification.InPath);
                        }

                        if (!NT_SUCCESS(status)){
                            KeSetEvent(&commonExtension->PathCountEvent, IO_NO_INCREMENT, FALSE);
                            KeLeaveCriticalRegion();
                            break;
                        }

                        //
                        // if removing last paging device, need to set DO_POWER_PAGABLE
                        // bit here, and possible re-set it below on failure.
                        //

                        setPagable = FALSE;

                        if ((!irpStack->Parameters.UsageNotification.InPath) &&
                            (commonExtension->PagingPathCount == 1)) {

                            //
                            // removing last paging file
                            // must have DO_POWER_PAGABLE bits set, but only
                            // if none set the DO_POWER_INRUSH bit and no other special files
                            //

                            if (TEST_FLAG(DeviceObject->Flags, DO_POWER_INRUSH)) {
                                TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP,  "ClassDispatchPnp (%p,%p): Last "
                                            "paging file removed, but "
                                            "DO_POWER_INRUSH was set, so NOT "
                                            "setting DO_POWER_PAGABLE\n",
                                            DeviceObject, Irp));
                            } else if ((commonExtension->HibernationPathCount == 0) &&
                                       (commonExtension->DumpPathCount == 0)) {
                                TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP,  "ClassDispatchPnp (%p,%p): Last "
                                            "paging file removed, "
                                            "setting DO_POWER_PAGABLE\n",
                                            DeviceObject, Irp));
                                SET_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE);
                                setPagable = TRUE;
                            }

                        }

                        //
                        // forward the irp before finishing handling the
                        // special cases
                        //

                        status = ClassForwardIrpSynchronous(commonExtension, Irp);

                        //
                        // now deal with the failure and success cases.
                        // note that we are not allowed to fail the irp
                        // once it is sent to the lower drivers.
                        //

                        if (NT_SUCCESS(status)) {

                            IoAdjustPagingPathCount(
                                (volatile LONG *)&commonExtension->PagingPathCount,
                                irpStack->Parameters.UsageNotification.InPath);

                            if (irpStack->Parameters.UsageNotification.InPath) {
                                if (commonExtension->PagingPathCount == 1) {
                                    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP,  "ClassDispatchPnp (%p,%p): "
                                                "Clearing PAGABLE bit\n",
                                                DeviceObject, Irp));
                                    CLEAR_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE);


                                }

                            }

                        } else {

                            //
                            // cleanup the changes done above
                            //

                            if (setPagable == TRUE) {
                                TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP,  "ClassDispatchPnp (%p,%p): Unsetting "
                                            "PAGABLE bit due to irp failure\n",
                                            DeviceObject, Irp));
                                CLEAR_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE);
                                setPagable = FALSE;
                            }

                            //
                            // relock or unlock the media if needed.
                            //

                            if (commonExtension->IsFdo) {

                                ClasspEjectionControl(
                                        DeviceObject,
                                        Irp,
                                        InternalMediaLock,
                                        (BOOLEAN)!irpStack->Parameters.UsageNotification.InPath);
                            }
                        }

                        //
                        // set the event so the next one can occur.
                        //

                        KeSetEvent(&commonExtension->PathCountEvent,
                                   IO_NO_INCREMENT, FALSE);
                        KeLeaveCriticalRegion();
                        break;
                    }

                    case DeviceUsageTypeHibernation: {

                        //
                        // if removing last hiber device, need to set DO_POWER_PAGABLE
                        // bit here, and possible re-set it below on failure.
                        //

                        setPagable = FALSE;

                        if ((!irpStack->Parameters.UsageNotification.InPath) &&
                            (commonExtension->HibernationPathCount == 1)) {

                            //
                            // removing last hiber file
                            // must have DO_POWER_PAGABLE bits set, but only
                            // if none set the DO_POWER_INRUSH bit and no other special files
                            //

                            if (TEST_FLAG(DeviceObject->Flags, DO_POWER_INRUSH)) {
                                TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP,  "ClassDispatchPnp (%p,%p): Last "
                                            "hiber file removed, but "
                                            "DO_POWER_INRUSH was set, so NOT "
                                            "setting DO_POWER_PAGABLE\n",
                                            DeviceObject, Irp));
                            } else if ((commonExtension->PagingPathCount == 0) &&
                                       (commonExtension->DumpPathCount == 0)) {
                                TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP,  "ClassDispatchPnp (%p,%p): Last "
                                            "hiber file removed, "
                                            "setting DO_POWER_PAGABLE\n",
                                            DeviceObject, Irp));
                                SET_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE);
                                setPagable = TRUE;
                            }

                        }

                        status = ClassForwardIrpSynchronous(commonExtension, Irp);
                        if (!NT_SUCCESS(status)) {

                            if (setPagable == TRUE) {
                                TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP,  "ClassDispatchPnp (%p,%p): Unsetting "
                                            "PAGABLE bit due to irp failure\n",
                                            DeviceObject, Irp));
                                CLEAR_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE);
                                setPagable = FALSE;
                            }

                        } else {

                            IoAdjustPagingPathCount(
                                (volatile LONG *)&commonExtension->HibernationPathCount,
                                irpStack->Parameters.UsageNotification.InPath
                                );

                            if ((irpStack->Parameters.UsageNotification.InPath) &&
                                (commonExtension->HibernationPathCount == 1)) {
                                TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP,  "ClassDispatchPnp (%p,%p): "
                                            "Clearing PAGABLE bit\n",
                                            DeviceObject, Irp));
                                CLEAR_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE);
                            }
                        }

                        break;
                    }

                    case DeviceUsageTypeDumpFile: {

                        //
                        // if removing last dump device, need to set DO_POWER_PAGABLE
                        // bit here, and possible re-set it below on failure.
                        //

                        setPagable = FALSE;

                        if ((!irpStack->Parameters.UsageNotification.InPath) &&
                            (commonExtension->DumpPathCount == 1)) {

                            //
                            // removing last dump file
                            // must have DO_POWER_PAGABLE bits set, but only
                            // if none set the DO_POWER_INRUSH bit and no other special files
                            //

                            if (TEST_FLAG(DeviceObject->Flags, DO_POWER_INRUSH)) {
                                TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP,  "ClassDispatchPnp (%p,%p): Last "
                                            "dump file removed, but "
                                            "DO_POWER_INRUSH was set, so NOT "
                                            "setting DO_POWER_PAGABLE\n",
                                            DeviceObject, Irp));
                            } else if ((commonExtension->PagingPathCount == 0) &&
                                       (commonExtension->HibernationPathCount == 0)) {
                                TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP,  "ClassDispatchPnp (%p,%p): Last "
                                            "dump file removed, "
                                            "setting DO_POWER_PAGABLE\n",
                                            DeviceObject, Irp));
                                SET_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE);
                                setPagable = TRUE;
                            }

                        }

                        status = ClassForwardIrpSynchronous(commonExtension, Irp);
                        if (!NT_SUCCESS(status)) {

                            if (setPagable == TRUE) {
                                TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP,  "ClassDispatchPnp (%p,%p): Unsetting "
                                            "PAGABLE bit due to irp failure\n",
                                            DeviceObject, Irp));
                                CLEAR_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE);
                                setPagable = FALSE;
                            }

                        } else {

                            IoAdjustPagingPathCount(
                                (volatile LONG *)&commonExtension->DumpPathCount,
                                irpStack->Parameters.UsageNotification.InPath
                                );

                            if ((irpStack->Parameters.UsageNotification.InPath) &&
                                (commonExtension->DumpPathCount == 1)) {
                                TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP,  "ClassDispatchPnp (%p,%p): "
                                            "Clearing PAGABLE bit\n",
                                            DeviceObject, Irp));
                                CLEAR_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE);
                            }
                        }

                        break;
                    }

                    case DeviceUsageTypeBoot: {

                        if (isFdo) {
                            PCLASS_PRIVATE_FDO_DATA fdoData;

                            fdoData = ((PFUNCTIONAL_DEVICE_EXTENSION)(DeviceObject->DeviceExtension))->PrivateFdoData;


                            //
                            // If boot disk has removal policy as RemovalPolicyExpectSurpriseRemoval (e.g. disk is hotplug-able),
                            // change the removal policy to RemovalPolicyExpectOrderlyRemoval.
                            // This will cause the write cache of disk to be enabled on subsequent start Fdo (next boot).
                            //
                            if ((fdoData != NULL) &&
                                fdoData->HotplugInfo.DeviceHotplug) {

                                fdoData->HotplugInfo.DeviceHotplug = FALSE;
                                fdoData->HotplugInfo.MediaRemovable = FALSE;

                                ClassSetDeviceParameter((PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension,
                                                        CLASSP_REG_SUBKEY_NAME,
                                                        CLASSP_REG_REMOVAL_POLICY_VALUE_NAME,
                                                        RemovalPolicyExpectOrderlyRemoval);
                            }
                        }

                        status = ClassForwardIrpSynchronous(commonExtension, Irp);
                        break;
                    }

                    default: {
                        status = STATUS_INVALID_PARAMETER;
                        break;
                    }
                }
                break;
            }

            case IRP_MN_QUERY_CAPABILITIES: {

                TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP,  "ClassDispatchPnp (%p,%p): QueryCapabilities\n",
                            DeviceObject, Irp));

                if(!isFdo) {

                    status = ClassQueryPnpCapabilities(
                                DeviceObject,
                                irpStack->Parameters.DeviceCapabilities.Capabilities
                                );

                    break;

                } else {

                    PDEVICE_CAPABILITIES deviceCapabilities;
                    PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
                    PCLASS_PRIVATE_FDO_DATA fdoData;

                    fdoExtension = DeviceObject->DeviceExtension;
                    fdoData = fdoExtension->PrivateFdoData;
                    deviceCapabilities =
                        irpStack->Parameters.DeviceCapabilities.Capabilities;

                    //
                    // forward the irp before handling the special cases
                    //

                    status = ClassForwardIrpSynchronous(commonExtension, Irp);
                    if (!NT_SUCCESS(status)) {
                        break;
                    }

                    //
                    // we generally want to remove the device from the hotplug
                    // applet, which requires the SR-OK bit to be set.
                    // only when the user specifies that they are capable of
                    // safely removing things do we want to clear this bit
                    // (saved in WriteCacheEnableOverride)
                    //
                    // setting of this bit is done either above, or by the
                    // lower driver.
                    //
                    // note: may not be started, so check we have FDO data first.
                    //

                    if (fdoData &&
                        fdoData->HotplugInfo.WriteCacheEnableOverride) {
                        if (deviceCapabilities->SurpriseRemovalOK) {
                            TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP,  "Classpnp: Clearing SR-OK bit in "
                                        "device capabilities due to hotplug "
                                        "device or media\n"));
                        }
                        deviceCapabilities->SurpriseRemovalOK = FALSE;
                    }
                    break;

                } // end QUERY_CAPABILITIES for FDOs

                break;


            } // end QUERY_CAPABILITIES

            default: {

                if (isFdo){


                    IoCopyCurrentIrpStackLocationToNext(Irp);

                    ClassReleaseRemoveLock(DeviceObject, Irp);
                    status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);

                    completeRequest = FALSE;
                }

                break;
            }
        }
    }
    else {
        NT_ASSERT(driverExtension);
        status = STATUS_INTERNAL_ERROR;
    }

    if (completeRequest){
        Irp->IoStatus.Status = status;

        if (!lockReleased){
            ClassReleaseRemoveLock(DeviceObject, Irp);
        }

        ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);

        TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): leaving with previous %#x, current %#x.", DeviceObject, Irp, commonExtension->PreviousState, commonExtension->CurrentState));
    }
    else {
        /*
         *  The irp is already completed so don't touch it.
         *  This may be a remove so don't touch the device extension.
         */
        TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): leaving.", DeviceObject, Irp));
    }

    return status;
} // end ClassDispatchPnp()


/*++////////////////////////////////////////////////////////////////////////////

ClassPnpStartDevice()

Routine Description:

    Storage class driver routine for IRP_MN_START_DEVICE requests.
    This routine kicks off any device specific initialization

Arguments:

    DeviceObject - a pointer to the device object

    Irp - a pointer to the io request packet

Return Value:

    none

--*/
NTSTATUS ClassPnpStartDevice(IN PDEVICE_OBJECT DeviceObject)
{
    PCLASS_DRIVER_EXTENSION driverExtension;
    PCLASS_INIT_DATA initData;

    PCLASS_DEV_INFO devInfo;

    PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
    PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
    BOOLEAN isFdo = commonExtension->IsFdo;

    BOOLEAN isMountedDevice = TRUE;
    BOOLEAN isPortable = FALSE;

    NTSTATUS status = STATUS_SUCCESS;
    PDEVICE_POWER_DESCRIPTOR powerDescriptor = NULL;


    PAGED_CODE();

#ifdef _MSC_VER
#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
#endif
    driverExtension = IoGetDriverObjectExtension(DeviceObject->DriverObject, CLASS_DRIVER_EXTENSION_KEY);

    initData = &(driverExtension->InitData);
    if(isFdo) {
        devInfo = &(initData->FdoData);
    } else {
        devInfo = &(initData->PdoData);
    }

    NT_ASSERT(devInfo->ClassInitDevice != NULL);
    NT_ASSERT(devInfo->ClassStartDevice != NULL);

    if (!commonExtension->IsInitialized){

        //
        // perform FDO/PDO specific initialization
        //

        if (isFdo){
            STORAGE_PROPERTY_ID propertyId;

            //
            // allocate a private extension for class data
            //

            if (fdoExtension->PrivateFdoData == NULL) {
                fdoExtension->PrivateFdoData = ExAllocatePoolWithTag(NonPagedPoolNx,
                                                                     sizeof(CLASS_PRIVATE_FDO_DATA),
                                                                     CLASS_TAG_PRIVATE_DATA
                                                                     );
            }

            if (fdoExtension->PrivateFdoData == NULL) {
                TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP,  "ClassPnpStartDevice: Cannot allocate for private fdo data\n"));
                return STATUS_INSUFFICIENT_RESOURCES;
            }

            RtlZeroMemory(fdoExtension->PrivateFdoData, sizeof(CLASS_PRIVATE_FDO_DATA));


#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
            //
            // Allocate a structure to hold more data than what we can put in FUNCTIONAL_DEVICE_EXTENSION.
            // This structure's memory is managed by classpnp, so it is more extensible.
            //
            if (fdoExtension->AdditionalFdoData == NULL) {
                fdoExtension->AdditionalFdoData = ExAllocatePoolWithTag(NonPagedPoolNx,
                                                                        sizeof(ADDITIONAL_FDO_DATA),
                                                                        CLASSPNP_POOL_TAG_ADDITIONAL_DATA
                                                                        );
            }

            if (fdoExtension->AdditionalFdoData == NULL) {
                TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP, "ClassPnpStartDevice: Cannot allocate memory for the additional data structure.\n"));
                return STATUS_INSUFFICIENT_RESOURCES;
            }

            RtlZeroMemory(fdoExtension->AdditionalFdoData, sizeof(ADDITIONAL_FDO_DATA));
#endif

            status = ClasspInitializeTimer(fdoExtension);
            if (NT_SUCCESS(status) == FALSE) {
                FREE_POOL(fdoExtension->PrivateFdoData);
#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
                FREE_POOL(fdoExtension->AdditionalFdoData);
#endif
                TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP, "ClassPnpStartDevice: Failed to initialize tick timer\n"));
                return STATUS_INSUFFICIENT_RESOURCES;
            }

            //
            // allocate LowerLayerSupport for class data
            //

            if (fdoExtension->FunctionSupportInfo == NULL) {
                fdoExtension->FunctionSupportInfo = (PCLASS_FUNCTION_SUPPORT_INFO)ExAllocatePoolWithTag(NonPagedPoolNx,
                                                                                                        sizeof(CLASS_FUNCTION_SUPPORT_INFO),
                                                                                                        '3BcS'
                                                                                                        );
            }

            if (fdoExtension->FunctionSupportInfo == NULL) {
                ClasspDeleteTimer(fdoExtension);
                FREE_POOL(fdoExtension->PrivateFdoData);
#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
                FREE_POOL(fdoExtension->AdditionalFdoData);
#endif
                TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP,  "ClassPnpStartDevice: Cannot allocate for FunctionSupportInfo\n"));
                return STATUS_INSUFFICIENT_RESOURCES;
            }

            //
            // initialize the struct's various fields.
            //
            RtlZeroMemory(fdoExtension->FunctionSupportInfo, sizeof(CLASS_FUNCTION_SUPPORT_INFO));
            KeInitializeSpinLock(&fdoExtension->FunctionSupportInfo->SyncLock);

            //
            // intialize the CommandStatus to -1 indicates that no effort made yet to retrieve the info.
            // Possible values of CommandStatus (data type: NTSTATUS):
            //     -1:             It's not attempted yet to retrieve the information.
            //     success:        Command sent and succeeded, information cached in FdoExtension.
            //     failed/warning: Command is either not supported or failed by device or lower level driver.
            //                     The command should not be attempted again.
            //
            fdoExtension->FunctionSupportInfo->BlockLimitsData.CommandStatus = -1;
            fdoExtension->FunctionSupportInfo->DeviceCharacteristicsData.CommandStatus = -1;
            fdoExtension->FunctionSupportInfo->LBProvisioningData.CommandStatus = -1;
            fdoExtension->FunctionSupportInfo->ReadCapacity16Data.CommandStatus = -1;
            fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus = -1;


            KeInitializeTimer(&fdoExtension->PrivateFdoData->Retry.Timer);
            KeInitializeDpc(&fdoExtension->PrivateFdoData->Retry.Dpc,
                            ClasspRetryRequestDpc,
                            DeviceObject);
            KeInitializeSpinLock(&fdoExtension->PrivateFdoData->Retry.Lock);
            fdoExtension->PrivateFdoData->Retry.Granularity = KeQueryTimeIncrement();
            commonExtension->Reserved4 = (ULONG_PTR)(' GPH'); // debug aid
            InitializeListHead(&fdoExtension->PrivateFdoData->DeferredClientIrpList);

            KeInitializeSpinLock(&fdoExtension->PrivateFdoData->SpinLock);

            //
            // keep a pointer to the senseinfo2 stuff locally also (used in every read/write).
            //
            fdoExtension->PrivateFdoData->InterpretSenseInfo = driverExtension->InterpretSenseInfo;

            fdoExtension->PrivateFdoData->MaxNumberOfIoRetries = NUM_IO_RETRIES;

            //
            // Initialize release queue extended SRB
            //
            status = InitializeStorageRequestBlock(&(fdoExtension->PrivateFdoData->ReleaseQueueSrb.SrbEx),
                                                   STORAGE_ADDRESS_TYPE_BTL8,
                                                   sizeof(fdoExtension->PrivateFdoData->ReleaseQueueSrb.ReleaseQueueSrbBuffer),
                                                   0);
            if (!NT_SUCCESS(status)) {
                NT_ASSERT(FALSE);
                TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP,
                            "ClassPnpStartDevice: fail to initialize release queue extended SRB 0x%x\n", status));
                return status;
            }


            /*
             *  Anchor the FDO in our static list.
             *  Pnp is synchronized, so we shouldn't need any synchronization here.
             */
            InsertTailList(&AllFdosList, &fdoExtension->PrivateFdoData->AllFdosListEntry);

            //
            // NOTE: the old interface allowed the class driver to allocate
            // this.  this was unsafe for low-memory conditions. allocate one
            // unconditionally now, and modify our internal functions to use
            // our own exclusively as it is the only safe way to do this.
            //

            status = ClasspAllocateReleaseQueueIrp(fdoExtension);
            if (!NT_SUCCESS(status)) {
                TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP,  "ClassPnpStartDevice: Cannot allocate the private release queue irp\n"));
                return status;
            }

            status = ClasspAllocatePowerProcessIrp(fdoExtension);
            if (!NT_SUCCESS(status)) {
                TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP,  "ClassPnpStartDevice: Cannot allocate the power process irp\n"));
                return status;
            }

            //
            // Call port driver to get miniport properties for disk devices
            // It's ok for this call to fail
            //

            if ((DeviceObject->DeviceType == FILE_DEVICE_DISK) &&
                (!TEST_FLAG(DeviceObject->Characteristics, FILE_FLOPPY_DISKETTE))) {

                propertyId = StorageMiniportProperty;

                status = ClassGetDescriptor(fdoExtension->CommonExtension.LowerDeviceObject,
                                            &propertyId,
                                            (PVOID *)&fdoExtension->MiniportDescriptor);

                //
                // function ClassGetDescriptor returns succeed with buffer "fdoExtension->MiniportDescriptor" allocated.
                //
                if ( NT_SUCCESS(status) &&
                     (fdoExtension->MiniportDescriptor->Portdriver != StoragePortCodeSetStorport &&
                      fdoExtension->MiniportDescriptor->Portdriver != StoragePortCodeSetUSBport) ) {
                    //
                    // field "IoTimeoutValue" supported for either Storport or USBStor
                    //
                    fdoExtension->MiniportDescriptor->IoTimeoutValue = 0;
                }



            }

            //
            // Call port driver to get adapter capabilities.
            //

            propertyId = StorageAdapterProperty;

            status = ClassGetDescriptor(
                        commonExtension->LowerDeviceObject,
                        &propertyId,
                        (PVOID *)&fdoExtension->AdapterDescriptor);
            if (!NT_SUCCESS(status)) {
                TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP,  "ClassPnpStartDevice: ClassGetDescriptor [ADAPTER] failed %lx\n", status));
                return status;
            }

            //
            // Call port driver to get device descriptor.
            //

            propertyId = StorageDeviceProperty;

            status = ClassGetDescriptor(
                        commonExtension->LowerDeviceObject,
                        &propertyId,
                        (PVOID *)&fdoExtension->DeviceDescriptor);
            if (NT_SUCCESS(status)){

                ClasspScanForSpecialInRegistry(fdoExtension);
                ClassScanForSpecial(fdoExtension, ClassBadItems, ClasspScanForClassHacks);

                //
                // allow perf to be re-enabled after a given number of failed IOs
                // require this number to be at least CLASS_PERF_RESTORE_MINIMUM
                //

                {
                    ULONG t = CLASS_PERF_RESTORE_MINIMUM;

                    ClassGetDeviceParameter(fdoExtension,
                                            CLASSP_REG_SUBKEY_NAME,
                                            CLASSP_REG_PERF_RESTORE_VALUE_NAME,
                                            &t);
                    if (t >= CLASS_PERF_RESTORE_MINIMUM) {
                        fdoExtension->PrivateFdoData->Perf.ReEnableThreshhold = t;
                    }
                }

                //
                // compatibility comes first.  writable cd media will not
                // get a SYNCH_CACHE on power down.
                //
                if (fdoExtension->DeviceObject->DeviceType != FILE_DEVICE_DISK) {
                    SET_FLAG(fdoExtension->PrivateFdoData->HackFlags, FDO_HACK_NO_SYNC_CACHE);
                }


                //
                // Test if the device is portable and updated the characteristics if so
                //
                status = ClasspIsPortable(fdoExtension,
                                          &isPortable);

                if (NT_SUCCESS(status) && (isPortable == TRUE)) {
                    DeviceObject->Characteristics |= FILE_PORTABLE_DEVICE;
                }

                //
                // initialize the hotplug information only after the ScanForSpecial
                // routines, as it relies upon the hack flags.
                //
                status = ClasspInitializeHotplugInfo(fdoExtension);
                if (NT_SUCCESS(status)){
                    /*
                     *  Allocate/initialize TRANSFER_PACKETs and related resources.
                     */
                    status = InitializeTransferPackets(DeviceObject);
                }
                else {
                    TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP,  "ClassPnpStartDevice: Could not initialize hotplug information %lx\n", status));
                }
            }
            else {
                TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP,  "ClassPnpStartDevice: ClassGetDescriptor [DEVICE] failed %lx\n", status));
                return status;
            }


            if (NT_SUCCESS(status)) {

                //
                // Retrieve info on whether async notification is supported by port drivers
                //
                propertyId = StorageDevicePowerProperty;

                status = ClassGetDescriptor(fdoExtension->CommonExtension.LowerDeviceObject,
                                            &propertyId,
                                            (PVOID *)&powerDescriptor);
                if (NT_SUCCESS(status) && (powerDescriptor != NULL)) {
                    fdoExtension->FunctionSupportInfo->AsynchronousNotificationSupported = powerDescriptor->AsynchronousNotificationSupported;
                    fdoExtension->FunctionSupportInfo->IdlePower.D3ColdSupported = powerDescriptor->D3ColdSupported;
                    fdoExtension->FunctionSupportInfo->IdlePower.NoVerifyDuringIdlePower = powerDescriptor->NoVerifyDuringIdlePower;
                    FREE_POOL(powerDescriptor);
                } else {
                    TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP,  "ClassPnpStartDevice: ClassGetDescriptor [DevicePower] failed %lx\n", status));

                    //
                    // Ignore error as device power property is optional
                    //
                    status = STATUS_SUCCESS;
                }
            }
        }

        //
        // ISSUE - drivers need to disable write caching on the media
        //         if hotplug and !useroverride.  perhaps we should
        //         allow registration of a callback to enable/disable
        //         write cache instead.
        //

        if (NT_SUCCESS(status)){
            status = devInfo->ClassInitDevice(DeviceObject);
        }

        if (commonExtension->IsFdo) {
            fdoExtension->PrivateFdoData->Perf.OriginalSrbFlags = fdoExtension->SrbFlags;

            //
            // initialization for disk device
            //
            if ((DeviceObject->DeviceType == FILE_DEVICE_DISK) &&
                (!TEST_FLAG(DeviceObject->Characteristics, FILE_FLOPPY_DISKETTE))) {

                ULONG accessAlignmentNotSupported = 0;
                ULONG qerrOverrideMode = QERR_SET_ZERO_ODX_OR_TP_ONLY;
                ULONG legacyErrorHandling = FALSE;

                TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP,
                           "ClassPnpStartDevice: Enabling idle timer for %p\n", DeviceObject));
                // Initialize idle timer for disk devices
                ClasspInitializeIdleTimer(fdoExtension);

                if (ClasspIsObsoletePortDriver(fdoExtension) == FALSE) {
                    // get INQUIRY VPD support information. It's safe to send command as everything is ready in ClassInitDevice().
                    ClasspGetInquiryVpdSupportInfo(fdoExtension);

                    // Query and cache away Logical Block Provisioning info in the FDO extension.
                    // The cached information will be used in responding to some IOCTLs
                    ClasspGetLBProvisioningInfo(fdoExtension);

                    //
                    // Query and cache away Block Device ROD Limits info in the FDO extension.
                    //
                    if (fdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits) {
                        ClassDetermineTokenOperationCommandSupport(DeviceObject);
                    }

                    //
                    // See if the user has specified a particular QERR override
                    // mode. "Override" meaning setting QERR = 0 via Mode Select.
                    //  0 = Only when ODX or Thin Provisioning are supported (default)
                    //  1 = Always
                    //  2 = Never (or any value >= 2)
                    //
                    ClassGetDeviceParameter(fdoExtension,
                                            CLASSP_REG_SUBKEY_NAME,
                                            CLASSP_REG_QERR_OVERRIDE_MODE,
                                            &qerrOverrideMode);

                    //
                    // If this device is thinly provisioned or supports ODX, we
                    // may need to force QERR to zero.  The user may have also
                    // specified that we should always or never do this.
                    //
                    if (qerrOverrideMode == QERR_SET_ZERO_ALWAYS ||
                        (qerrOverrideMode == QERR_SET_ZERO_ODX_OR_TP_ONLY &&
                         (ClasspIsThinProvisioned(fdoExtension->FunctionSupportInfo) ||
                          NT_SUCCESS(ClasspValidateOffloadSupported(DeviceObject, NULL))))) {

                        ClasspZeroQERR(DeviceObject);
                    }

                } else {

                    //
                    // Since this device has been exposed by a legacy miniport (e.g. SCSIPort miniport)
                    // set its LB Provisioning command status to an error status that will be surfaced
                    // up to the caller of a TRIM/Unmap command.
                    //
                    fdoExtension->FunctionSupportInfo->LBProvisioningData.CommandStatus = STATUS_UNSUCCESSFUL;
                    fdoExtension->FunctionSupportInfo->BlockLimitsData.CommandStatus = STATUS_UNSUCCESSFUL;
                    fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus = STATUS_UNSUCCESSFUL;
                }

                // Get registry setting of failing the IOCTL for AccessAlignment Property.
                ClassGetDeviceParameter(fdoExtension,
                                        CLASSP_REG_SUBKEY_NAME,
                                        CLASSP_REG_ACCESS_ALIGNMENT_NOT_SUPPORTED,
                                        &accessAlignmentNotSupported);

                if (accessAlignmentNotSupported > 0) {
                    fdoExtension->FunctionSupportInfo->RegAccessAlignmentQueryNotSupported = TRUE;
                }

#if (NTDDI_VERSION >= NTDDI_WINBLUE)


                //
                // See if the user has specified legacy error handling.
                //
                ClassGetDeviceParameter(fdoExtension,
                                        CLASSP_REG_SUBKEY_NAME,
                                        CLASSP_REG_LEGACY_ERROR_HANDLING,
                                        &legacyErrorHandling);

                if (legacyErrorHandling) {
                    //
                    // Legacy error handling means that the maximum number of
                    // retries allowd for an IO request is 8 instead of 4.
                    //
                    fdoExtension->PrivateFdoData->MaxNumberOfIoRetries = LEGACY_NUM_IO_RETRIES;
                    fdoExtension->PrivateFdoData->LegacyErrorHandling = TRUE;
                }
#else
                UNREFERENCED_PARAMETER(legacyErrorHandling);
#endif


                //
                // Get the copy offload max target duration value.
                // This function will set the default value if one hasn't been
                // specified in the registry.
                //
                ClasspGetCopyOffloadMaxDuration(DeviceObject,
                                                REG_DISK_CLASS_CONTROL,
                                                &(fdoExtension->PrivateFdoData->CopyOffloadMaxTargetDuration));

            }

        }
    }

    if (!NT_SUCCESS(status)){

        //
        // Just bail out - the remove that comes down will clean up the
        // initialized scraps.
        //

        return status;
    } else {
        commonExtension->IsInitialized = TRUE;
    }

    //
    // If device requests autorun functionality or a once a second callback
    // then enable the once per second timer. Exception is if media change
    // detection is desired but device supports async notification.
    //
    // NOTE: This assumes that ClassInitializeMediaChangeDetection is always
    //       called in the context of the ClassInitDevice callback. If called
    //       after then this check will have already been made and the
    //       once a second timer will not have been enabled.
    //
    if ((isFdo) &&
        ((initData->ClassTick != NULL) ||
         ((fdoExtension->MediaChangeDetectionInfo != NULL) &&
          (fdoExtension->FunctionSupportInfo != NULL) &&
          (fdoExtension->FunctionSupportInfo->AsynchronousNotificationSupported == FALSE)) ||
         ((fdoExtension->FailurePredictionInfo != NULL) &&
          (fdoExtension->FailurePredictionInfo->Method != FailurePredictionNone))))
    {
        ClasspEnableTimer(fdoExtension);

        //
        // In addition, we may change our polling behavior when the screen is
        // off so register for screen state notification if we haven't already
        // done so.
        //
        if (ScreenStateNotificationHandle == NULL) {
            PoRegisterPowerSettingCallback(DeviceObject,
                                            &GUID_CONSOLE_DISPLAY_STATE,
                                            &ClasspPowerSettingCallback,
                                            NULL,
                                            &ScreenStateNotificationHandle);
        }
    }

    //
    // NOTE: the timer looks at commonExtension->CurrentState now
    //       to prevent Media Change Notification code from running
    //       until the device is started, but allows the device
    //       specific tick handler to run.  therefore it is imperative
    //       that commonExtension->CurrentState not be updated until
    //       the device specific startdevice handler has finished.
    //

    status = devInfo->ClassStartDevice(DeviceObject);

    if (NT_SUCCESS(status)){
        commonExtension->CurrentState = IRP_MN_START_DEVICE;

        if((isFdo) && (initData->ClassEnumerateDevice != NULL)) {
            isMountedDevice = FALSE;
        }

        if (DeviceObject->DeviceType != FILE_DEVICE_CD_ROM) {

            isMountedDevice = FALSE;
        }

        //
        // Register for mounted device interface if this is a
        // sfloppy device.
        //
        if ((DeviceObject->DeviceType == FILE_DEVICE_DISK) &&
            (TEST_FLAG(DeviceObject->Characteristics, FILE_FLOPPY_DISKETTE))) {

            isMountedDevice = TRUE;
        }

        if(isMountedDevice) {
            ClasspRegisterMountedDeviceInterface(DeviceObject);
        }

        if(commonExtension->IsFdo) {
            IoWMIRegistrationControl(DeviceObject, WMIREG_ACTION_REGISTER);

            //
            // Tell Storport (Usbstor or SD) to enable idle power management for this
            // device, assuming the user hasn't turned it off in the registry.
            //
            if (fdoExtension->FunctionSupportInfo != NULL &&
                fdoExtension->FunctionSupportInfo->IdlePower.IdlePowerEnabled == FALSE &&
                fdoExtension->MiniportDescriptor != NULL &&
                (fdoExtension->MiniportDescriptor->Portdriver == StoragePortCodeSetStorport ||
                 fdoExtension->MiniportDescriptor->Portdriver == StoragePortCodeSetSDport ||
                 fdoExtension->MiniportDescriptor->Portdriver == StoragePortCodeSetUSBport)) {
                ULONG disableIdlePower= 0;
                ClassGetDeviceParameter(fdoExtension,
                                        CLASSP_REG_SUBKEY_NAME,
                                        CLASSP_REG_DISBALE_IDLE_POWER_NAME,
                                        &disableIdlePower);

                if (!disableIdlePower) {
                    ClasspEnableIdlePower(DeviceObject);
                }
            }
        }
    }
    else {
        ClasspDisableTimer(fdoExtension);
    }


    return status;
}


/*++////////////////////////////////////////////////////////////////////////////

ClassReadWrite()

Routine Description:

    This is the system entry point for read and write requests. The
    device-specific handler is invoked to perform any validation necessary.

    If the device object is a PDO (partition object) then the request will
    simply be adjusted for Partition0 and issued to the lower device driver.

    IF the device object is an FDO (paritition 0 object), the number of bytes
    in the request are checked against the maximum byte counts that the adapter
    supports and requests are broken up into
    smaller sizes if necessary.

Arguments:

    DeviceObject - a pointer to the device object for this request

    Irp - IO request

Return Value:

    NT Status

--*/
NTSTATUS
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassReadWrite(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
    PDEVICE_OBJECT      lowerDeviceObject = commonExtension->LowerDeviceObject;
    PIO_STACK_LOCATION  currentIrpStack = IoGetCurrentIrpStackLocation(Irp);
    ULONG               transferByteCount = currentIrpStack->Parameters.Read.Length;
    ULONG               isRemoved;
    NTSTATUS            status;

    /*
     *  Grab the remove lock.  If we can't acquire it, bail out.
     */
    isRemoved = ClassAcquireRemoveLock(DeviceObject, Irp);
    if (isRemoved) {
        Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST;
        Irp->IoStatus.Information = 0;
        ClassReleaseRemoveLock(DeviceObject, Irp);
        ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
        status = STATUS_DEVICE_DOES_NOT_EXIST;
    }
    else if (TEST_FLAG(DeviceObject->Flags, DO_VERIFY_VOLUME) &&
             (currentIrpStack->MinorFunction != CLASSP_VOLUME_VERIFY_CHECKED) &&
             !TEST_FLAG(currentIrpStack->Flags, SL_OVERRIDE_VERIFY_VOLUME)){

        /*
         *  DO_VERIFY_VOLUME is set for the device object,
         *  but this request is not itself a verify request.
         *  So fail this request.
         */
        if (Irp->Tail.Overlay.Thread != NULL) {
            IoSetHardErrorOrVerifyDevice(Irp, DeviceObject);
        }
        Irp->IoStatus.Status = STATUS_VERIFY_REQUIRED;
        Irp->IoStatus.Information = 0;
        ClassReleaseRemoveLock(DeviceObject, Irp);
        ClassCompleteRequest(DeviceObject, Irp, 0);
        status = STATUS_VERIFY_REQUIRED;
    }
    else {

        /*
         *  Since we've bypassed the verify-required tests we don't need to repeat
         *  them with this IRP - in particular we don't want to worry about
         *  hitting them at the partition 0 level if the request has come through
         *  a non-zero partition.
         */
        currentIrpStack->MinorFunction = CLASSP_VOLUME_VERIFY_CHECKED;

        /*
         *  Call the miniport driver's pre-pass filter to check if we
         *  should continue with this transfer.
         */
        NT_ASSERT(commonExtension->DevInfo->ClassReadWriteVerification);
        status = commonExtension->DevInfo->ClassReadWriteVerification(DeviceObject, Irp);
        // Code Analysis cannot analyze the code paths specific to clients.
        _Analysis_assume_(status != STATUS_PENDING);
        if (!NT_SUCCESS(status)){
            NT_ASSERT(Irp->IoStatus.Status == status);
            Irp->IoStatus.Information = 0;
            ClassReleaseRemoveLock(DeviceObject, Irp);
            ClassCompleteRequest (DeviceObject, Irp, IO_NO_INCREMENT);
        }
        else if (status == STATUS_PENDING){
            /*
             *  ClassReadWriteVerification queued this request.
             *  So don't touch the irp anymore.
             */
        }
        else {

            if (transferByteCount == 0) {
                /*
                 *  Several parts of the code turn 0 into 0xffffffff,
                 *  so don't process a zero-length request any further.
                 */
                Irp->IoStatus.Status = STATUS_SUCCESS;
                Irp->IoStatus.Information = 0;
                ClassReleaseRemoveLock(DeviceObject, Irp);
                ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
                status = STATUS_SUCCESS;
            }
            else {
                /*
                 *  If the driver has its own StartIo routine, call it.
                 */
                if (commonExtension->DriverExtension->InitData.ClassStartIo) {
                    IoMarkIrpPending(Irp);
                    IoStartPacket(DeviceObject, Irp, NULL, NULL);
                    status = STATUS_PENDING;
                }
                else {
                    /*
                     *  The driver does not have its own StartIo routine.
                     *  So process this request ourselves.
                     */

                    /*
                     *  Add partition byte offset to make starting byte relative to
                     *  beginning of disk.
                     */
                    currentIrpStack->Parameters.Read.ByteOffset.QuadPart +=
                        commonExtension->StartingOffset.QuadPart;

                    if (commonExtension->IsFdo){

                        PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
                        PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;

                        /*
                         *  Add in any skew for the disk manager software.
                         */
                        currentIrpStack->Parameters.Read.ByteOffset.QuadPart +=
                             commonExtension->PartitionZeroExtension->DMByteSkew;

                        //
                        // In case DEV_USE_16BYTE_CDB flag is not set, R/W request will be translated into READ/WRITE 10 SCSI command.
                        // These SCSI commands have 4 bytes in "Starting LBA" field.
                        // Requests cannot be represented in these SCSI commands should be failed.
                        //
                        if (!TEST_FLAG(fdoExtension->DeviceFlags, DEV_USE_16BYTE_CDB)) {
                            LARGE_INTEGER startingLba;

                            startingLba.QuadPart = currentIrpStack->Parameters.Read.ByteOffset.QuadPart >> fdoExtension->SectorShift;

                            if (startingLba.QuadPart > MAXULONG) {
                                Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
                                Irp->IoStatus.Information = 0;
                                ClassReleaseRemoveLock(DeviceObject, Irp);
                                ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
                                status = STATUS_INVALID_PARAMETER;
                                goto Exit;
                            }
                        }

#if DBG
                        //
                        // Record the caller if:
                        // 1. the disk is currently off
                        // 2. the operation is a read, (likely resulting in disk spinnage)
                        // 3. the operation is marked WT (likely resulting in disk spinnage)
                        //
                        if((fdoExtension->DevicePowerState == PowerDeviceD3) && // disk is off
                            ((currentIrpStack->MajorFunction == IRP_MJ_READ) ||                       // It's a read.
                             (TEST_FLAG(currentIrpStack->Flags, SL_WRITE_THROUGH))) ) {               // they *really* want it to go to disk.

                            SnapDiskStartup();
                        }
#endif

                        /*
                         *  Perform the actual transfer(s) on the hardware
                         *  to service this request.
                         */
                        if (ClasspIsIdleRequestSupported(fdoData, Irp)) {
                            ClasspMarkIrpAsIdle(Irp, TRUE);
                            status = ClasspEnqueueIdleRequest(DeviceObject, Irp);
                        } else {
                            UCHAR uniqueAddr = 0;

                            //
                            // Since we're touching fdoData after servicing the transfer packet, this opens us up to
                            // a potential window, where the device may be removed between the time that
                            // ServiceTransferPacket completes and we've had a chance to access fdoData. In order
                            // to guard against this, we acquire the removelock an additional time here. This
                            // acquire is guaranteed to succeed otherwise we wouldn't be here (because of the
                            // outer acquire).
                            // The sequence of events we're guarding against with this remLock acquire is:
                            // 1. This UL IRP acquired the lock.
                            // 2. Device gets surprised removed, then gets IRP_MN_REMOVE_DEVICE; ClassRemoveDevice
                            //    waits for the above RemoveLock.
                            // 3. ServiceTransferRequest breaks the UL IRP into DL IRPs.
                            // 4. DL IRPs complete with STATUS_NO_SUCH_DEVICE and TransferPktComplete completes the UL
                            //    IRP with STATUS_NO_SUCH_DEVICE; releases the RemoveLock.
                            // 5. ClassRemoveDevice is now unblocked, continues running and frees resources (including
                            //    fdoData).
                            // 6. Finally ClassReadWrite gets to run again and accesses a freed fdoData when trying to
                            //    check/update idle-related fields.
                            //
                            ClassAcquireRemoveLock(DeviceObject, (PVOID)&uniqueAddr);

                            ClasspMarkIrpAsIdle(Irp, FALSE);
                            status = ServiceTransferRequest(DeviceObject, Irp, FALSE);
                            if (fdoData->IdlePrioritySupported == TRUE) {
                                fdoData->LastNonIdleIoTime = ClasspGetCurrentTime();
                            }

                            ClassReleaseRemoveLock(DeviceObject, (PVOID)&uniqueAddr);
                        }
                    }
                    else {
                        /*
                         *  This is a child PDO enumerated for our FDO by e.g. disk.sys
                         *  and owned by e.g. partmgr.  Send it down to the next device
                         *  and the same irp will come back to us for the FDO.
                         */
                        IoCopyCurrentIrpStackLocationToNext(Irp);
                        ClassReleaseRemoveLock(DeviceObject, Irp);
                        status = IoCallDriver(lowerDeviceObject, Irp);
                    }
                }
            }
        }
    }

Exit:
    return status;
}


VOID InterpretCapacityData(PDEVICE_OBJECT Fdo, PREAD_CAPACITY_DATA_EX ReadCapacityData)
{
    PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Fdo->DeviceExtension;
    ULONG cylinderSize;
    ULONG bytesPerSector;
    LARGE_INTEGER lastSector;
    LARGE_INTEGER largeInt;

    bytesPerSector = ClasspCalculateLogicalSectorSize(Fdo, ReadCapacityData->BytesPerBlock);

    fdoExt->DiskGeometry.BytesPerSector = bytesPerSector;
    WHICH_BIT(fdoExt->DiskGeometry.BytesPerSector, fdoExt->SectorShift);

    /*
     *  LogicalBlockAddress is the last sector of the logical drive, in big-endian.
     *  It tells us the size of the drive (#sectors is lastSector+1).
     */

    largeInt = ReadCapacityData->LogicalBlockAddress;
    REVERSE_BYTES_QUAD(&lastSector, &largeInt);

    if (fdoExt->DMActive){
        TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT,  "ClassReadDriveCapacity: reducing number of sectors by %d\n", fdoExt->DMSkew));
        lastSector.QuadPart -= fdoExt->DMSkew;
    }

    /*
     *  Check to see if we have a geometry we should be using already.
     *  If not, we set part of the disk geometry to garbage values that will be filled in by the caller (e.g. disk.sys).
     *
     *  So the first call to ClassReadDriveCapacity always sets a meaningless geometry.
     *  TracksPerCylinder and SectorsPerTrack are kind of meaningless anyway wrt I/O,
     *  because I/O is always targeted to a logical sector number.
     *  All that really matters is BytesPerSector and the number of sectors.
     */
    cylinderSize = fdoExt->DiskGeometry.TracksPerCylinder * fdoExt->DiskGeometry.SectorsPerTrack;
    if (cylinderSize == 0){
        fdoExt->DiskGeometry.TracksPerCylinder = 0xff;
        fdoExt->DiskGeometry.SectorsPerTrack = 0x3f;
        cylinderSize = fdoExt->DiskGeometry.TracksPerCylinder * fdoExt->DiskGeometry.SectorsPerTrack;
    }

    /*
     *  Calculate number of cylinders.
     *  If there are zero cylinders, then the device lied AND it's
     *  smaller than 0xff*0x3f (about 16k sectors, usually 8 meg)
     *  this can fit into a single LONGLONG, so create another usable
     *  geometry, even if it's unusual looking.
     *  This allows small, non-standard devices, such as Sony's Memory Stick, to show up as having a partition.
     */
    fdoExt->DiskGeometry.Cylinders.QuadPart = (LONGLONG)((lastSector.QuadPart + 1)/cylinderSize);
    if (fdoExt->DiskGeometry.Cylinders.QuadPart == (LONGLONG)0) {
        fdoExt->DiskGeometry.SectorsPerTrack    = 1;
        fdoExt->DiskGeometry.TracksPerCylinder  = 1;
        fdoExt->DiskGeometry.Cylinders.QuadPart = lastSector.QuadPart + 1;
    }

    /*
     *  Calculate media capacity in bytes.
     *  For this purpose we treat the entire LUN as is if it is one partition.  Disk will deal with actual partitioning.
     */
    fdoExt->CommonExtension.PartitionLength.QuadPart =
        ((LONGLONG)(lastSector.QuadPart + 1)) << fdoExt->SectorShift;

    /*
     *  Is this removable or fixed media
     */
    if (TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA)){
        fdoExt->DiskGeometry.MediaType = RemovableMedia;
    }
    else {
        fdoExt->DiskGeometry.MediaType = FixedMedia;
    }

}

/*++////////////////////////////////////////////////////////////////////////////

ClassReadDriveCapacity()

Routine Description:

    This routine sends a READ CAPACITY to the requested device, updates
    the geometry information in the device object and returns
    when it is complete.  This routine is synchronous.

    This routine must be called with the remove lock held or some other
    assurance that the Fdo will not be removed while processing.

Arguments:

    DeviceObject - Supplies a pointer to the device object that represents
        the device whose capacity is to be read.

Return Value:

    Status is returned.

--*/
_Must_inspect_result_
NTSTATUS
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassReadDriveCapacity(_In_ PDEVICE_OBJECT Fdo)
{
    PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Fdo->DeviceExtension;
    PCOMMON_DEVICE_EXTENSION commonExtension = Fdo->DeviceExtension;
    PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
    READ_CAPACITY_DATA_EX PTRALIGN readCapacityData = {0};
    PTRANSFER_PACKET pkt;
    NTSTATUS status;
    PMDL driveCapMdl = NULL;
    KEVENT event;
    IRP pseudoIrp = {0};
    ULONG readCapacityDataSize;
    BOOLEAN use16ByteCdb;
    BOOLEAN match = TRUE;

    use16ByteCdb = TEST_FLAG(fdoExt->DeviceFlags, DEV_USE_16BYTE_CDB);

RetryRequest:

    if (use16ByteCdb) {
        readCapacityDataSize = sizeof(READ_CAPACITY_DATA_EX);
    } else {
        readCapacityDataSize = sizeof(READ_CAPACITY_DATA);
    }

    if (driveCapMdl != NULL) {
        FreeDeviceInputMdl(driveCapMdl);
        driveCapMdl = NULL;
    }

    //
    // Allocate the MDL based on the Read Capacity command.
    //
    driveCapMdl = BuildDeviceInputMdl(&readCapacityData, readCapacityDataSize);
    if (driveCapMdl == NULL) {
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto SafeExit;
    }

    pkt = DequeueFreeTransferPacket(Fdo, TRUE);
    if (pkt == NULL) {
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto SafeExit;
    }

    //
    //  Our engine needs an "original irp" to write the status back to
    //  and to count down packets (one in this case).
    //  Just use a pretend irp for this.
    //

    pseudoIrp.Tail.Overlay.DriverContext[0] = LongToPtr(1);
    pseudoIrp.IoStatus.Status = STATUS_SUCCESS;
    pseudoIrp.IoStatus.Information = 0;
    pseudoIrp.MdlAddress = driveCapMdl;

    //
    //  Set this up as a SYNCHRONOUS transfer, submit it,
    //  and wait for the packet to complete.  The result
    //  status will be written to the original irp.
    //

    KeInitializeEvent(&event, SynchronizationEvent, FALSE);
    SetupDriveCapacityTransferPacket(pkt,
                                     &readCapacityData,
                                     readCapacityDataSize,
                                     &event,
                                     &pseudoIrp,
                                     use16ByteCdb);
    SubmitTransferPacket(pkt);
    (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);

    status = pseudoIrp.IoStatus.Status;

    //
    //  If we got an UNDERRUN, retry exactly once.
    //  (The transfer_packet engine didn't retry because the result
    //   status was success).
    //

    if (NT_SUCCESS(status) &&
       (pseudoIrp.IoStatus.Information < readCapacityDataSize)) {

        TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClassReadDriveCapacity: read len (%xh) < %xh, retrying ...",
                (ULONG)pseudoIrp.IoStatus.Information, readCapacityDataSize));

        pkt = DequeueFreeTransferPacket(Fdo, TRUE);
        if (pkt) {
            pseudoIrp.Tail.Overlay.DriverContext[0] = LongToPtr(1);
            pseudoIrp.IoStatus.Status = STATUS_SUCCESS;
            pseudoIrp.IoStatus.Information = 0;
            KeInitializeEvent(&event, SynchronizationEvent, FALSE);
            SetupDriveCapacityTransferPacket(pkt,
                                             &readCapacityData,
                                             readCapacityDataSize,
                                             &event,
                                             &pseudoIrp,
                                             use16ByteCdb);
            SubmitTransferPacket(pkt);
            (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
            status = pseudoIrp.IoStatus.Status;
            if (pseudoIrp.IoStatus.Information < readCapacityDataSize){
                status = STATUS_DEVICE_BUSY;
            }
        } else {
            status = STATUS_INSUFFICIENT_RESOURCES;
        }
    }

    if (NT_SUCCESS(status)) {
        //
        //  The request succeeded. Check for 8 byte LBA support.
        //

        if (use16ByteCdb == FALSE) {

            PREAD_CAPACITY_DATA readCapacity;

            //
            // Check whether the device supports 8 byte LBA. If the device supports
            // it then retry the request using 16 byte CDB.
            //

            readCapacity = (PREAD_CAPACITY_DATA) &readCapacityData;

            if (readCapacity->LogicalBlockAddress == 0xFFFFFFFF) {
                //
                // Device returned max size for last LBA. Need to send
                // 16 byte request to get the size.
                //
                use16ByteCdb = TRUE;
                goto RetryRequest;

            } else {
                //
                // Convert the 4 byte LBA (READ_CAPACITY_DATA) to 8 byte LBA (READ_CAPACITY_DATA_EX)
                // format for ease of use. This is the only format stored in the device extension.
                //

                RtlMoveMemory((PUCHAR)(&readCapacityData) + sizeof(ULONG), readCapacity, sizeof(READ_CAPACITY_DATA));
                RtlZeroMemory((PUCHAR)(&readCapacityData), sizeof(ULONG));

            }
        } else {
            //
            // Device completed 16 byte command successfully, it supports 8-byte LBA.
            //

            SET_FLAG(fdoExt->DeviceFlags, DEV_USE_16BYTE_CDB);
        }

        //
        // Read out and store the drive information.
        //

        InterpretCapacityData(Fdo, &readCapacityData);

        //
        // Before caching the new drive capacity, compare it with the
        // cached capacity for any change.
        //

        if (fdoData->IsCachedDriveCapDataValid == TRUE) {

            match = (BOOLEAN) RtlEqualMemory(&fdoData->LastKnownDriveCapacityData,
                                    &readCapacityData, sizeof(READ_CAPACITY_DATA_EX));
        }

        //
        // Store the readCapacityData in private FDO data.
        // This is so that runtime memory failures don't cause disk.sys to put
        // the paging disk in an error state. Also this is used in
        // IOCTL_STORAGE_READ_CAPACITY.
        //
        fdoData->LastKnownDriveCapacityData = readCapacityData;
        fdoData->IsCachedDriveCapDataValid = TRUE;

        if (match == FALSE) {
            if (commonExtension->CurrentState != IRP_MN_START_DEVICE)
            {
                //
                // This can happen if a disk reports Parameters Changed / Capacity Data Changed sense data.
                // NT_ASSERT(!"Drive capacity has changed while the device wasn't started!");
                //
            } else {
                //
                // state of (commonExtension->CurrentState == IRP_MN_START_DEVICE) indicates that the device has been started.
                // UpdateDiskPropertiesWorkItemActive is used as a flag to ensure we only have one work item updating the disk
                // properties at a time.
                //
                if (InterlockedCompareExchange((volatile LONG *)&fdoData->UpdateDiskPropertiesWorkItemActive, 1, 0) == 0)
                {
                    PIO_WORKITEM workItem;

                    workItem = IoAllocateWorkItem(Fdo);

                    if (workItem) {
                        //
                        // The disk capacity has changed, send notification to the disk driver.
                        // Start a work item to notify the disk class driver asynchronously.
                        //
                        IoQueueWorkItem(workItem, ClasspUpdateDiskProperties, DelayedWorkQueue, workItem);
                    } else {
                        InterlockedExchange((volatile LONG *)&fdoData->UpdateDiskPropertiesWorkItemActive, 0);
                    }
                }
            }
        }

    } else {
        //
        //  The request failed.
        //

        //
        // ISSUE - 2000/02/04 - henrygab - non-512-byte sector sizes and failed geometry update
        //    what happens when the disk's sector size is bigger than
        //    512 bytes and we hit this code path?  this is untested.
        //
        // If the read capacity fails, set the geometry to reasonable parameter
        // so things don't fail at unexpected places.  Zero the geometry
        // except for the bytes per sector and sector shift.
        //

        //
        //  This request can sometimes fail legitimately
        //  (e.g. when a SCSI device is attached but turned off)
        //  so this is not necessarily a device/driver bug.
        //

        TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, "ClassReadDriveCapacity on Fdo %p failed with status %ul.", Fdo, status));

        //
        //  Write in a default disk geometry which we HOPE is right (??).
        //

        RtlZeroMemory(&fdoExt->DiskGeometry, sizeof(DISK_GEOMETRY));
        fdoExt->DiskGeometry.BytesPerSector = 512;
        fdoExt->SectorShift = 9;
        fdoExt->CommonExtension.PartitionLength.QuadPart = (LONGLONG) 0;

        //
        //  Is this removable or fixed media
        //

        if (TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA)){
            fdoExt->DiskGeometry.MediaType = RemovableMedia;
        } else {
            fdoExt->DiskGeometry.MediaType = FixedMedia;
        }
    }

SafeExit:


    //
    // In case DEV_USE_16BYTE_CDB flag is not set, classpnp translates R/W request into READ/WRITE 10 SCSI command.
    // These SCSI commands have 2 bytes in "Transfer Blocks" field.
    // Make sure this max length (0xFFFF * sector size) is respected during request split.
    //
    if (!TEST_FLAG(fdoExt->DeviceFlags, DEV_USE_16BYTE_CDB)) {
        ULONG cdb10MaxBlocks = ((ULONG)USHORT_MAX) << fdoExt->SectorShift;

        fdoData->HwMaxXferLen = min(cdb10MaxBlocks, fdoData->HwMaxXferLen);
    }

    if (driveCapMdl != NULL) {
        FreeDeviceInputMdl(driveCapMdl);
    }

    //
    // If the request failed for some reason then invalidate the cached
    // capacity data for removable devices. So that we won't return
    // wrong capacity in IOCTL_STORAGE_READ_CAPACITY
    //

    if (!NT_SUCCESS(status) && (fdoExt->DiskGeometry.MediaType == RemovableMedia)) {
        fdoData->IsCachedDriveCapDataValid = FALSE;
    }

    //
    //  Don't let memory failures (either here or in the port driver) in the ReadDriveCapacity call
    //  put the paging disk in an error state such that paging fails.
    //  Return the last known drive capacity (which may possibly be slightly out of date, even on
    //  fixed media, e.g. for storage cabinets that can grow a logical disk).
    //
    if ((status == STATUS_INSUFFICIENT_RESOURCES) &&
        (fdoData->IsCachedDriveCapDataValid) &&
        (fdoExt->DiskGeometry.MediaType == FixedMedia)) {
        TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, "ClassReadDriveCapacity: defaulting to cached DriveCapacity data"));
        InterpretCapacityData(Fdo, &fdoData->LastKnownDriveCapacityData);
        status = STATUS_SUCCESS;
    }

    return status;
}


/*++////////////////////////////////////////////////////////////////////////////

ClassSendStartUnit()

Routine Description:

    Send command to SCSI unit to start or power up.
    Because this command is issued asynchronounsly, that is, without
    waiting on it to complete, the IMMEDIATE flag is not set. This
    means that the CDB will not return until the drive has powered up.
    This should keep subsequent requests from being submitted to the
    device before it has completely spun up.

    This routine is called from the InterpretSense routine, when a
    request sense returns data indicating that a drive must be
    powered up.

    This routine may also be called from a class driver's error handler,
    or anytime a non-critical start device should be sent to the device.

Arguments:

    Fdo - The functional device object for the stopped device.

Return Value:

    None.

--*/
VOID
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassSendStartUnit(
    _In_ PDEVICE_OBJECT Fdo
    )
{
    PIO_STACK_LOCATION irpStack;
    PIRP irp;
    PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
    PSCSI_REQUEST_BLOCK srb;
    PCOMPLETION_CONTEXT context;
    PCDB cdb;
    NTSTATUS status;
    PSTORAGE_REQUEST_BLOCK srbEx;

    //
    // Allocate Srb from nonpaged pool.
    //

    context = ExAllocatePoolWithTag(NonPagedPoolNx,
                             sizeof(COMPLETION_CONTEXT),
                             '6CcS');

    if (context == NULL) {

        //
        // ISSUE-2000/02/03-peterwie
        // This code path was inheritted from the NT 4.0 class2.sys driver.
        // It needs to be changed to survive low-memory conditions.
        //

        KeBugCheck(SCSI_DISK_DRIVER_INTERNAL);
    }

    //
    // Save the device object in the context for use by the completion
    // routine.
    //

    context->DeviceObject = Fdo;

    srb = &context->Srb.Srb;
    if (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
        srbEx = &context->Srb.SrbEx;
        status = InitializeStorageRequestBlock(srbEx,
                                               STORAGE_ADDRESS_TYPE_BTL8,
                                               sizeof(context->Srb.SrbExBuffer),
                                               1,
                                               SrbExDataTypeScsiCdb16);
        if (!NT_SUCCESS(status)) {
            FREE_POOL(context);
            NT_ASSERT(FALSE);
            return;
        }

        srbEx->SrbFunction = SRB_FUNCTION_EXECUTE_SCSI;

    } else {

        //
        // Zero out srb.
        //

        RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK));

        //
        // Write length to SRB.
        //

        srb->Length = sizeof(SCSI_REQUEST_BLOCK);

        srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
    }

    //
    // Set timeout value large enough for drive to spin up.
    //

    SrbSetTimeOutValue(srb, START_UNIT_TIMEOUT);

    //
    // Set the transfer length.
    //

    SrbAssignSrbFlags(srb,
                      (SRB_FLAGS_NO_DATA_TRANSFER  |
                       SRB_FLAGS_DISABLE_AUTOSENSE |
                       SRB_FLAGS_DISABLE_SYNCH_TRANSFER));

    //
    // Build the start unit CDB.
    //

    SrbSetCdbLength(srb, 6);
    cdb = SrbGetCdb(srb);

    cdb->START_STOP.OperationCode = SCSIOP_START_STOP_UNIT;
    cdb->START_STOP.Start = 1;
    cdb->START_STOP.Immediate = 0;
    cdb->START_STOP.LogicalUnitNumber = srb->Lun;

    //
    // Build the asynchronous request to be sent to the port driver.
    // Since this routine is called from a DPC the IRP should always be
    // available.
    //

    irp = IoAllocateIrp(Fdo->StackSize, FALSE);

    if (irp == NULL) {

        //
        // ISSUE-2000/02/03-peterwie
        // This code path was inheritted from the NT 4.0 class2.sys driver.
        // It needs to be changed to survive low-memory conditions.
        //

        KeBugCheck(SCSI_DISK_DRIVER_INTERNAL);

    }

    ClassAcquireRemoveLock(Fdo, irp);

    IoSetCompletionRoutine(irp,
                           (PIO_COMPLETION_ROUTINE)ClassAsynchronousCompletion,
                           context,
                           TRUE,
                           TRUE,
                           TRUE);

    irpStack = IoGetNextIrpStackLocation(irp);
    irpStack->MajorFunction = IRP_MJ_SCSI;
    SrbSetOriginalRequest(srb, irp);

    //
    // Store the SRB address in next stack for port driver.
    //

    irpStack->Parameters.Scsi.Srb = srb;

    //
    // Call the port driver with the IRP.
    //

    IoCallDriver(fdoExtension->CommonExtension.LowerDeviceObject, irp);

    return;

} // end StartUnit()

/*++////////////////////////////////////////////////////////////////////////////

ClassAsynchronousCompletion() ISSUE-2000/02/18-henrygab - why public?!

Routine Description:

    This routine is called when an asynchronous I/O request
    which was issused by the class driver completes.  Examples of such requests
    are release queue or START UNIT. This routine releases the queue if
    necessary.  It then frees the context and the IRP.

Arguments:

    DeviceObject - The device object for the logical unit; however since this
        is the top stack location the value is NULL.

    Irp - Supplies a pointer to the Irp to be processed.

    Context - Supplies the context to be used to process this request.

Return Value:

    None.

--*/
NTSTATUS
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassAsynchronousCompletion(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp,
    PVOID Context
    )
{
    PCOMPLETION_CONTEXT context = Context;
    PSCSI_REQUEST_BLOCK srb;
    ULONG srbFunction;
    ULONG srbFlags;

    if (context == NULL) {
        return STATUS_INVALID_PARAMETER;
    }

    if (DeviceObject == NULL) {

        DeviceObject = context->DeviceObject;
    }

    srb = &context->Srb.Srb;

    if (srb->Function == SRB_FUNCTION_STORAGE_REQUEST_BLOCK) {
        srbFunction = ((PSTORAGE_REQUEST_BLOCK)srb)->SrbFunction;
        srbFlags = ((PSTORAGE_REQUEST_BLOCK)srb)->SrbFlags;
    } else {
        srbFunction = srb->Function;
        srbFlags = srb->SrbFlags;
    }

    //
    // If this is an execute srb, then check the return status and make sure.
    // the queue is not frozen.
    //

    if (srbFunction == SRB_FUNCTION_EXECUTE_SCSI) {

        //
        // Check for a frozen queue.
        //

        if (srb->SrbStatus & SRB_STATUS_QUEUE_FROZEN) {

            //
            // Unfreeze the queue getting the device object from the context.
            //

            ClassReleaseQueue(context->DeviceObject);
        }
    }

    { // free port-allocated sense buffer if we can detect

        if (((PCOMMON_DEVICE_EXTENSION)(DeviceObject->DeviceExtension))->IsFdo) {

            PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
            if (PORT_ALLOCATED_SENSE(fdoExtension, srb)) {
                FREE_PORT_ALLOCATED_SENSE_BUFFER(fdoExtension, srb);
            }

        } else {

            NT_ASSERT(!TEST_FLAG(srbFlags, SRB_FLAGS_FREE_SENSE_BUFFER));

        }
    }


    //
    // Free the context and the Irp.
    //

    if (Irp->MdlAddress != NULL) {
        MmUnlockPages(Irp->MdlAddress);
        IoFreeMdl(Irp->MdlAddress);

        Irp->MdlAddress = NULL;
    }

    ClassReleaseRemoveLock(DeviceObject, Irp);

    FREE_POOL(context);

    IoFreeIrp(Irp);

    //
    // Indicate the I/O system should stop processing the Irp completion.
    //

    return STATUS_MORE_PROCESSING_REQUIRED;

} // end ClassAsynchronousCompletion()


NTSTATUS
ServiceTransferRequest(
    PDEVICE_OBJECT Fdo,
    PIRP Irp,
    BOOLEAN PostToDpc
    )

/*++

Routine description:

    This routine processes Io requests, splitting them if they
    are larger than what the hardware can handle at a time. If
    there isn't enough memory available, the request is placed
    in a queue, to be processed at a later time

    If this is a high priority  paging request, all regular Io
    are throttled to provide Mm with better thoroughput

Arguments:

    Fdo - The functional device object processing the request
    Irp - The Io request to be processed
    PostToDpc - Flag that indicates that this IRP must be posted to a DPC

Return Value:

    STATUS_SUCCESS if successful, an error code otherwise

--*/

{
    PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Fdo->DeviceExtension;
    PCOMMON_DEVICE_EXTENSION commonExtension = Fdo->DeviceExtension;
    PSTORAGE_ADAPTER_DESCRIPTOR adapterDesc = commonExtension->PartitionZeroExtension->AdapterDescriptor;
    PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
    IO_PAGING_PRIORITY priority = (TEST_FLAG(Irp->Flags, IRP_PAGING_IO)) ? IoGetPagingIoPriority(Irp) : IoPagingPriorityInvalid;
    BOOLEAN deferClientIrp = FALSE;
    BOOLEAN driverUsesStartIO = (commonExtension->DriverExtension->InitData.ClassStartIo != NULL);
    KIRQL oldIrql;
    NTSTATUS status = STATUS_UNSUCCESSFUL;
    PIO_STACK_LOCATION  currentIrpStack = IoGetCurrentIrpStackLocation(Irp);

    /*
     * Initialize IRP status for the master IRP to
     * - STATUS_FT_READ_FROM_COPY if it's a copy-specific read
     * - STATUS_SUCCESS otherwise.
     *
     * This is required. When Classpnp determines the status for the master IRP
     * when completing child IRPs, the call to IoSetMasterIrpStatus
     * will be functioning properly (See TransferPktComplete function)
     *
     * Note:
     * If the IRP is a copy-specific read, File System already initialized the IRP status
     * to be STATUS_FT_READ_FROM_COPY. However, this can be changed when the IRP arrives
     * at Classpnp. It's possible that other drivers in the stack may initialize the
     * IRP status field to other values before forwarding the IRP down the stack.
     * To be defensive, we initialize the IRP status to either STATUS_FT_READ_FROM_COPY
     * if it's a copy-specific read, or STATUS_SUCCESS otherwise.
     */
    if (currentIrpStack->MajorFunction == IRP_MJ_READ &&
        TEST_FLAG(currentIrpStack->Flags, SL_KEY_SPECIFIED) &&
        IsKeyReadCopyNumber(currentIrpStack->Parameters.Read.Key)) {
        Irp->IoStatus.Status = STATUS_FT_READ_FROM_COPY;
    } else {
        Irp->IoStatus.Status = STATUS_SUCCESS;
    }

    //
    // If this is a high priority request, hold off all other Io requests
    //

    if (priority == IoPagingPriorityHigh)
    {
        KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);

        if (fdoData->NumHighPriorityPagingIo == 0)
        {
            //
            // Entering throttle mode
            //

            KeQuerySystemTime(&fdoData->ThrottleStartTime);
        }

        fdoData->NumHighPriorityPagingIo++;
        fdoData->MaxInterleavedNormalIo += ClassMaxInterleavePerCriticalIo;

        KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
    }
    else
    {
        if (fdoData->NumHighPriorityPagingIo != 0)
        {
            //
            // This request wasn't flagged as critical and atleast one critical request
            // is currently outstanding. Queue this request until all of those are done
            // but only if the interleave threshold has been reached
            //

            KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);

            if (fdoData->NumHighPriorityPagingIo != 0)
            {
                if (fdoData->MaxInterleavedNormalIo == 0)
                {
                    deferClientIrp = TRUE;
                }
                else
                {
                    fdoData->MaxInterleavedNormalIo--;
                }
            }

            KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
        }
    }

    if (!deferClientIrp)
    {
        PIO_STACK_LOCATION currentSp = IoGetCurrentIrpStackLocation(Irp);
        ULONG entireXferLen = currentSp->Parameters.Read.Length;
        PUCHAR bufPtr = MmGetMdlVirtualAddress(Irp->MdlAddress);
        LARGE_INTEGER targetLocation = currentSp->Parameters.Read.ByteOffset;
        PTRANSFER_PACKET pkt;
        SINGLE_LIST_ENTRY pktList;
        PSINGLE_LIST_ENTRY slistEntry;
        ULONG hwMaxXferLen;
        ULONG numPackets;
        ULONG i;


        /*
         *  We precomputed fdoData->HwMaxXferLen using (MaximumPhysicalPages-1).
         *  If the buffer is page-aligned, that's one less page crossing so we can add the page back in.
         *  Note: adapters that return MaximumPhysicalPages=0x10 depend on this to
         *           transfer aligned 64K requests in one piece.
         *  Also note:  make sure adding PAGE_SIZE back in doesn't wrap to zero.
         */
        if (((ULONG_PTR)bufPtr & (PAGE_SIZE-1)) || (fdoData->HwMaxXferLen > 0xffffffff-PAGE_SIZE)){
            hwMaxXferLen = fdoData->HwMaxXferLen;
        }
        else {
            NT_ASSERT((PAGE_SIZE%fdoExt->DiskGeometry.BytesPerSector) == 0);
            hwMaxXferLen = min(fdoData->HwMaxXferLen+PAGE_SIZE, adapterDesc->MaximumTransferLength);
        }

        /*
         *  Compute the number of hw xfers we'll have to do.
         *  Calculate this without allowing for an overflow condition.
         */
        NT_ASSERT(hwMaxXferLen >= PAGE_SIZE);
        numPackets = entireXferLen/hwMaxXferLen;
        if (entireXferLen % hwMaxXferLen){
            numPackets++;
        }

        /*
         *  Use our 'simple' slist functions since we don't need interlocked.
         */
        SimpleInitSlistHdr(&pktList);

        if (driverUsesStartIO) {
            /*
             * special case: StartIO-based writing must stay serialized, so just
             * re-use one packet.
             */
            pkt = DequeueFreeTransferPacket(Fdo, TRUE);
            if (pkt) {
                SimplePushSlist(&pktList, (PSINGLE_LIST_ENTRY)&pkt->SlistEntry);
                i = 1;
            } else {
                i = 0;
            }
        } else {
            /*
             *  First get all the TRANSFER_PACKETs that we'll need at once.
             */
            for (i = 0; i < numPackets; i++){
                pkt = DequeueFreeTransferPacket(Fdo, TRUE);
                if (pkt){
                    SimplePushSlist(&pktList, (PSINGLE_LIST_ENTRY)&pkt->SlistEntry);
                }
                else {
                    break;
                }
            }
        }


        if ((i == numPackets) &&
            (!driverUsesStartIO)) {
            NTSTATUS pktStat;

            /*
             *  The IoStatus.Information field will be incremented to the
             *  transfer length as the pieces complete.
             */
            Irp->IoStatus.Information = 0;

            /*
             *  Store the number of transfer pieces inside the original IRP.
             *  It will be used to count down the pieces as they complete.
             */
            Irp->Tail.Overlay.DriverContext[0] = LongToPtr(numPackets);

            /*
             *  For the common 1-packet case, we want to allow for an optimization by BlkCache
             *  (and also potentially synchronous storage drivers) which may complete the
             *  downward request synchronously.
             *  In that synchronous completion case, we want to _not_ mark the original irp pending
             *  and thereby save on the top-level APC.
             *  It's critical to coordinate this with the completion routine so that we mark the original irp
             *  pending if-and-only-if we return STATUS_PENDING for it.
             */
            if (numPackets > 1){
                IoMarkIrpPending(Irp);
                status = STATUS_PENDING;
            }
            else {
                status = STATUS_SUCCESS;
            }

            /*
             *  Transmit the pieces of the transfer.
             */
            while (entireXferLen > 0){
                ULONG thisPieceLen = MIN(hwMaxXferLen, entireXferLen);

                /*
                 *  Set up a TRANSFER_PACKET for this piece and send it.
                 */
                slistEntry = SimplePopSlist(&pktList);
                NT_ASSERT(slistEntry);
                pkt = CONTAINING_RECORD(slistEntry, TRANSFER_PACKET, SlistEntry);
                SetupReadWriteTransferPacket(   pkt,
                                            bufPtr,
                                            thisPieceLen,
                                            targetLocation,
                                            Irp);

                //
                // If the IRP needs to be split, then we need to use a partial MDL.
                // This prevents problems if the same MDL is mapped multiple times.
                //
                if (numPackets > 1) {
                    pkt->UsePartialMdl = TRUE;
                }

                /*
                 *  When an IRP is completed, the completion routine checks to see if there
                 *  is a deferred IRP ready to sent down (assuming that there are no non-idle
                 *  requests waiting to be serviced). If such a deferred IRP is available, it
                 *  is sent down using this routine. However, if the lower driver completes
                 *  the request inline, there is a potential for multiple deferred IRPs being
                 *  sent down in the context of the same completion thread, thus exhausting
                 *  the call stack.
                 *  In order to prevent this from happening, we need to ensure that deferred
                 *  IRPs that are dequeued in the context of a request's completion routine
                 *  get posted to a DPC.
                 */
                if (PostToDpc) {

                    pkt->RetryIn100nsUnits = 0;
                    TransferPacketQueueRetryDpc(pkt);
                    status = STATUS_PENDING;

                } else {

                    pktStat = SubmitTransferPacket(pkt);

                    /*
                     *  If any of the packets completes with pending, we MUST return pending.
                     *  Also, if a packet completes with an error, return pending; this is because
                     *  in the completion routine we mark the original irp pending if the packet failed
                     *  (since we may retry, thereby switching threads).
                     */
                    if (pktStat != STATUS_SUCCESS){
                        status = STATUS_PENDING;
                    }
                }

                entireXferLen -= thisPieceLen;
                bufPtr += thisPieceLen;
                targetLocation.QuadPart += thisPieceLen;
            }
            NT_ASSERT(SimpleIsSlistEmpty(&pktList));
        }
        else if (i >= 1){
            /*
             *  We were unable to get all the TRANSFER_PACKETs we need,
             *  but we did get at least one.
             *  That means that we are in extreme low-memory stress.
             *  We'll try doing this transfer using a single packet.
             *  The port driver is certainly also in stress, so use one-page
             *  transfers.
             */

            /*
             *  Free all but one of the TRANSFER_PACKETs.
             */
            while (i-- > 1){
                slistEntry = SimplePopSlist(&pktList);
                NT_ASSERT(slistEntry);
                pkt = CONTAINING_RECORD(slistEntry, TRANSFER_PACKET, SlistEntry);
                EnqueueFreeTransferPacket(Fdo, pkt);
            }

            /*
             *  Get the single TRANSFER_PACKET that we'll be using.
             */
            slistEntry = SimplePopSlist(&pktList);
            NT_ASSERT(slistEntry);
            NT_ASSERT(SimpleIsSlistEmpty(&pktList));
            pkt = CONTAINING_RECORD(slistEntry, TRANSFER_PACKET, SlistEntry);

            if (!driverUsesStartIO) {
                TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_RW, "Insufficient packets available in ServiceTransferRequest - entering lowMemRetry with pkt=%p.", pkt));
            }

            /*
             *  Set the number of transfer packets (one)
             *  inside the original irp.
             */
            Irp->IoStatus.Information = 0;
            Irp->Tail.Overlay.DriverContext[0] = LongToPtr(1);
            IoMarkIrpPending(Irp);

            /*
             *  Set up the TRANSFER_PACKET for a lowMem transfer and launch.
             */
            SetupReadWriteTransferPacket(  pkt,
                                        bufPtr,
                                        entireXferLen,
                                        targetLocation,
                                        Irp);

            InitLowMemRetry(pkt, bufPtr, entireXferLen, targetLocation);
            StepLowMemRetry(pkt);
            status = STATUS_PENDING;
        }
        else {
            /*
             *  We were unable to get ANY TRANSFER_PACKETs.
             *  Defer this client irp until some TRANSFER_PACKETs free up.
             */
            TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_RW, "No packets available in ServiceTransferRequest - deferring transfer (Irp=%p)...", Irp));

            if (priority == IoPagingPriorityHigh)
            {
                KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);

                if (fdoData->MaxInterleavedNormalIo < ClassMaxInterleavePerCriticalIo)
                {
                    fdoData->MaxInterleavedNormalIo = 0;
                }
                else
                {
                    fdoData->MaxInterleavedNormalIo -= ClassMaxInterleavePerCriticalIo;
                }

                fdoData->NumHighPriorityPagingIo--;

                if (fdoData->NumHighPriorityPagingIo == 0)
                {
                    LARGE_INTEGER period;

                    //
                    // Exiting throttle mode
                    //

                    KeQuerySystemTime(&fdoData->ThrottleStopTime);

                    period.QuadPart = fdoData->ThrottleStopTime.QuadPart - fdoData->ThrottleStartTime.QuadPart;
                    fdoData->LongestThrottlePeriod.QuadPart = max(fdoData->LongestThrottlePeriod.QuadPart, period.QuadPart);
                }

                KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
            }

            deferClientIrp = TRUE;
        }
    }
    _Analysis_assume_(deferClientIrp);
    if (deferClientIrp)
    {
        IoMarkIrpPending(Irp);
        EnqueueDeferredClientIrp(Fdo, Irp);
        status = STATUS_PENDING;
    }

    NT_ASSERT(status != STATUS_UNSUCCESSFUL);

    return status;
}


/*++////////////////////////////////////////////////////////////////////////////

ClassIoComplete()

Routine Description:

    This routine executes when the port driver has completed a request.
    It looks at the SRB status in the completing SRB and if not success
    it checks for valid request sense buffer information. If valid, the
    info is used to update status with more precise message of type of
    error. This routine deallocates the SRB.

    This routine should only be placed on the stack location for a class
    driver FDO.

Arguments:

    Fdo - Supplies the device object which represents the logical
        unit.

    Irp - Supplies the Irp which has completed.

    Context - Supplies a pointer to the SRB.

Return Value:

    NT status

--*/
NTSTATUS
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassIoComplete(
    IN PDEVICE_OBJECT Fdo,
    IN PIRP Irp,
    IN PVOID Context
    )
{
    PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
    PSCSI_REQUEST_BLOCK srb = Context;
    PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
    PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
    NTSTATUS status;
    BOOLEAN retry;
    BOOLEAN callStartNextPacket;
    ULONG srbFlags;
    ULONG srbFunction;

    NT_ASSERT(fdoExtension->CommonExtension.IsFdo);

    if (srb->Function == SRB_FUNCTION_STORAGE_REQUEST_BLOCK) {
        srbFlags = ((PSTORAGE_REQUEST_BLOCK)srb)->SrbFlags;
        srbFunction = ((PSTORAGE_REQUEST_BLOCK)srb)->SrbFunction;
    } else {
        srbFlags = srb->SrbFlags;
        srbFunction = srb->Function;
    }

    #if DBG
        if (srbFunction == SRB_FUNCTION_FLUSH) {
            DBGLOGFLUSHINFO(fdoData, FALSE, FALSE, TRUE);
        }
    #endif

    //
    // Check SRB status for success of completing request.
    //

    if (SRB_STATUS(srb->SrbStatus) != SRB_STATUS_SUCCESS) {
        LONGLONG retryInterval;

        TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,  "ClassIoComplete: IRP %p, SRB %p\n", Irp, srb));

        //
        // Release the queue if it is frozen.
        //

        if (srb->SrbStatus & SRB_STATUS_QUEUE_FROZEN) {
            ClassReleaseQueue(Fdo);
        }
        retry = InterpretSenseInfoWithoutHistory(
            Fdo,
            Irp,
            srb,
            irpStack->MajorFunction,
            ((irpStack->MajorFunction == IRP_MJ_DEVICE_CONTROL) ?
             irpStack->Parameters.DeviceIoControl.IoControlCode :
             0),
            MAXIMUM_RETRIES -
                ((ULONG)(ULONG_PTR)irpStack->Parameters.Others.Argument4),
            &status,
            &retryInterval);

        //
        // For Persistent Reserve requests, make sure user gets back partial data.
        //

        if (irpStack->MajorFunction == IRP_MJ_DEVICE_CONTROL &&
            (irpStack->Parameters.DeviceIoControl.IoControlCode == IOCTL_STORAGE_PERSISTENT_RESERVE_IN ||
             irpStack->Parameters.DeviceIoControl.IoControlCode == IOCTL_STORAGE_PERSISTENT_RESERVE_OUT) &&
            status == STATUS_DATA_OVERRUN) {

            status = STATUS_SUCCESS;
            retry = FALSE;
        }

        //
        // If the status is verified required and the this request
        // should bypass verify required then retry the request.
        //

        if (TEST_FLAG(irpStack->Flags, SL_OVERRIDE_VERIFY_VOLUME) &&
            status == STATUS_VERIFY_REQUIRED) {

            status = STATUS_IO_DEVICE_ERROR;
            retry = TRUE;
        }

#ifndef __REACTOS__
#pragma warning(suppress:4213) // okay to cast Arg4 as a ulong for this use case
        if (retry && ((ULONG)(ULONG_PTR)irpStack->Parameters.Others.Argument4)--) {
#else
        if (retry && (*(ULONG *)&irpStack->Parameters.Others.Argument4)--) {
#endif


            //
            // Retry request.
            //

            TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL,  "Retry request %p\n", Irp));

            if (PORT_ALLOCATED_SENSE(fdoExtension, srb)) {
                FREE_PORT_ALLOCATED_SENSE_BUFFER(fdoExtension, srb);
            }

            RetryRequest(Fdo, Irp, srb, FALSE, retryInterval);
            return STATUS_MORE_PROCESSING_REQUIRED;
        }

    } else {

        //
        // Set status for successful request
        //
        fdoData->LoggedTURFailureSinceLastIO = FALSE;
        ClasspPerfIncrementSuccessfulIo(fdoExtension);
        status = STATUS_SUCCESS;
    } // end if (SRB_STATUS(srb->SrbStatus) == SRB_STATUS_SUCCESS)


    //
    // ensure we have returned some info, and it matches what the
    // original request wanted for PAGING operations only
    //

    if ((NT_SUCCESS(status)) && TEST_FLAG(Irp->Flags, IRP_PAGING_IO)) {
        NT_ASSERT(Irp->IoStatus.Information != 0);
        NT_ASSERT(irpStack->Parameters.Read.Length == Irp->IoStatus.Information);
    }

    //
    // remember if the caller wanted to skip calling IoStartNextPacket.
    // for legacy reasons, we cannot call IoStartNextPacket for IoDeviceControl
    // calls.  this setting only affects device objects with StartIo routines.
    //

    callStartNextPacket = !TEST_FLAG(srbFlags, SRB_FLAGS_DONT_START_NEXT_PACKET);
    if (irpStack->MajorFunction == IRP_MJ_DEVICE_CONTROL) {
        callStartNextPacket = FALSE;
    }

    //
    // Free MDL if allocated.
    //

    if (TEST_FLAG(srbFlags, SRB_CLASS_FLAGS_FREE_MDL)) {
        SrbClearSrbFlags(srb, SRB_CLASS_FLAGS_FREE_MDL);
        IoFreeMdl(Irp->MdlAddress);
        Irp->MdlAddress = NULL;
    }


    //
    // Free the srb
    //

    if (!TEST_FLAG(srbFlags, SRB_CLASS_FLAGS_PERSISTANT)) {

        if (PORT_ALLOCATED_SENSE(fdoExtension, srb)) {
            FREE_PORT_ALLOCATED_SENSE_BUFFER(fdoExtension, srb);
        }

        if (fdoExtension->CommonExtension.IsSrbLookasideListInitialized){
            ClassFreeOrReuseSrb(fdoExtension, srb);
        }
        else {
            TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassIoComplete is freeing an SRB (possibly) on behalf of another driver."));
            FREE_POOL(srb);
        }

    } else {

        TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,  "ClassIoComplete: Not Freeing srb @ %p because "
                    "SRB_CLASS_FLAGS_PERSISTANT set\n", srb));
        if (PORT_ALLOCATED_SENSE(fdoExtension, srb)) {
            TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,  "ClassIoComplete: Not Freeing sensebuffer @ %p "
                        " because SRB_CLASS_FLAGS_PERSISTANT set\n",
                        srb->SenseInfoBuffer));
        }

    }

    //
    // Set status in completing IRP.
    //

    Irp->IoStatus.Status = status;

    //
    // Set the hard error if necessary.
    //

    if (!NT_SUCCESS(status) &&
        IoIsErrorUserInduced(status) &&
        (Irp->Tail.Overlay.Thread != NULL)
        ) {

        //
        // Store DeviceObject for filesystem, and clear
        // in IoStatus.Information field.
        //

        IoSetHardErrorOrVerifyDevice(Irp, Fdo);
        Irp->IoStatus.Information = 0;
    }

    //
    // If disk firmware update succeeded, log a system event.
    //

    if (NT_SUCCESS(status) &&
        (irpStack->MajorFunction == IRP_MJ_DEVICE_CONTROL) &&
        (irpStack->Parameters.DeviceIoControl.IoControlCode == IOCTL_STORAGE_FIRMWARE_ACTIVATE)) {

        ClasspLogSystemEventWithDeviceNumber(Fdo, IO_WARNING_DISK_FIRMWARE_UPDATED);
    }

    //
    // If pending has be returned for this irp then mark the current stack as
    // pending.
    //

    if (Irp->PendingReturned) {
        IoMarkIrpPending(Irp);
    }

    if (fdoExtension->CommonExtension.DriverExtension->InitData.ClassStartIo) {
        if (callStartNextPacket) {
            KIRQL oldIrql;
            KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
            IoStartNextPacket(Fdo, TRUE); // Yes, some IO must now be cancellable.
            KeLowerIrql(oldIrql);
        }
    }

    ClassReleaseRemoveLock(Fdo, Irp);

    return status;

} // end ClassIoComplete()


/*++////////////////////////////////////////////////////////////////////////////

ClassSendSrbSynchronous()

Routine Description:

    This routine is called by SCSI device controls to complete an
    SRB and send it to the port driver synchronously (ie wait for
    completion). The CDB is already completed along with the SRB CDB
    size and request timeout value.

Arguments:

    Fdo - Supplies the functional device object which represents the target.

    Srb - Supplies a partially initialized SRB. The SRB cannot come from zone.

    BufferAddress - Supplies the address of the buffer.

    BufferLength - Supplies the length in bytes of the buffer.

    WriteToDevice - Indicates the data should be transfer to the device.

Return Value:

    NTSTATUS indicating the final results of the operation.

    If NT_SUCCESS(), then the amount of usable data is contained in the field
       Srb->DataTransferLength

--*/
NTSTATUS
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassSendSrbSynchronous(
    _In_ PDEVICE_OBJECT Fdo,
    _Inout_ PSCSI_REQUEST_BLOCK _Srb,
    _In_reads_bytes_opt_(BufferLength) PVOID BufferAddress,
    _In_ ULONG BufferLength,
    _In_ BOOLEAN WriteToDevice
    )
{

    PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
    PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
    IO_STATUS_BLOCK ioStatus = {0};
    PIRP irp;
    PIO_STACK_LOCATION irpStack;
    KEVENT event;
    PVOID senseInfoBuffer = NULL;
    ULONG senseInfoBufferLength = SENSE_BUFFER_SIZE_EX;
    ULONG retryCount = MAXIMUM_RETRIES;
    NTSTATUS status;
    BOOLEAN retry;
    PSTORAGE_REQUEST_BLOCK_HEADER Srb = (PSTORAGE_REQUEST_BLOCK_HEADER)_Srb;

    //
    // NOTE: This code is only pagable because we are not freezing
    //       the queue.  Allowing the queue to be frozen from a pagable
    //       routine could leave the queue frozen as we try to page in
    //       the code to unfreeze the queue.  The result would be a nice
    //       case of deadlock.  Therefore, since we are unfreezing the
    //       queue regardless of the result, just set the NO_FREEZE_QUEUE
    //       flag in the SRB.
    //

    NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
    NT_ASSERT(fdoExtension->CommonExtension.IsFdo);

    if (Srb->Function != SRB_FUNCTION_STORAGE_REQUEST_BLOCK) {
        //
        // Write length to SRB.
        //

        Srb->Length = sizeof(SCSI_REQUEST_BLOCK);

        //
        // Set SCSI bus address.
        //

        Srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
    }

    //
    // The Srb->Function should have been set corresponding to SrbType.
    //

    NT_ASSERT( ((fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_SCSI_REQUEST_BLOCK) && (Srb->Function == SRB_FUNCTION_EXECUTE_SCSI)) ||
               ((fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) && (Srb->Function == SRB_FUNCTION_STORAGE_REQUEST_BLOCK)) );


    //
    // Sense buffer is in aligned nonpaged pool.
    //

#if defined(_ARM_) || defined(_ARM64_)

    //
    // ARM has specific alignment requirements, although this will not have a functional impact on x86 or amd64
    // based platforms. We are taking the conservative approach here.
    //
    senseInfoBufferLength = ALIGN_UP_BY(senseInfoBufferLength,KeGetRecommendedSharedDataAlignment());

#endif

    senseInfoBuffer = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned,
                                            senseInfoBufferLength,
                                            '7CcS');

    if (senseInfoBuffer == NULL) {

        TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,  "ClassSendSrbSynchronous: Can't allocate request sense "
                       "buffer\n"));
        return(STATUS_INSUFFICIENT_RESOURCES);
    }


    //
    // Enable auto request sense.
    //
    SrbSetSenseInfoBufferLength(Srb, SENSE_BUFFER_SIZE_EX);
    SrbSetSenseInfoBuffer(Srb, senseInfoBuffer);

    SrbSetDataBuffer(Srb, BufferAddress);


    //
    // Start retries here.
    //

retry:

    //
    // use fdoextension's flags by default.
    // do not move out of loop, as the flag may change due to errors
    // sending this command.
    //

    SrbAssignSrbFlags(Srb, fdoExtension->SrbFlags);

    if (BufferAddress != NULL) {
        if (WriteToDevice) {
            SrbSetSrbFlags(Srb, SRB_FLAGS_DATA_OUT);
        } else {
            SrbSetSrbFlags(Srb, SRB_FLAGS_DATA_IN);
        }
    }


    //
    // Initialize the QueueAction field.
    //

    SrbSetRequestAttribute(Srb, SRB_SIMPLE_TAG_REQUEST);

    //
    // Disable synchronous transfer for these requests.
    //
    SrbSetSrbFlags(Srb, SRB_FLAGS_DISABLE_SYNCH_TRANSFER | SRB_FLAGS_NO_QUEUE_FREEZE);

    //
    // Set the event object to the unsignaled state.
    // It will be used to signal request completion.
    //

    KeInitializeEvent(&event, NotificationEvent, FALSE);

    //
    // Build device I/O control request with METHOD_NEITHER data transfer.
    // We'll queue a completion routine to cleanup the MDL's and such ourself.
    //

    irp = IoAllocateIrp(
            (CCHAR) (fdoExtension->CommonExtension.LowerDeviceObject->StackSize + 1),
            FALSE);

    if (irp == NULL) {
        FREE_POOL(senseInfoBuffer);
        SrbSetSenseInfoBuffer(Srb, NULL);
        SrbSetSenseInfoBufferLength(Srb, 0);
        TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,  "ClassSendSrbSynchronous: Can't allocate Irp\n"));
        return(STATUS_INSUFFICIENT_RESOURCES);
    }

    //
    // Get next stack location.
    //

    irpStack = IoGetNextIrpStackLocation(irp);

    //
    // Set up SRB for execute scsi request. Save SRB address in next stack
    // for the port driver.
    //

    irpStack->MajorFunction = IRP_MJ_SCSI;
    irpStack->Parameters.Scsi.Srb = (PSCSI_REQUEST_BLOCK)Srb;

    IoSetCompletionRoutine(irp,
                           ClasspSendSynchronousCompletion,
                           Srb,
                           TRUE,
                           TRUE,
                           TRUE);

    irp->UserIosb = &ioStatus;
    irp->UserEvent = &event;

    if (BufferAddress) {
        //
        // Build an MDL for the data buffer and stick it into the irp.  The
        // completion routine will unlock the pages and free the MDL.
        //

        irp->MdlAddress = IoAllocateMdl( BufferAddress,
                                         BufferLength,
                                         FALSE,
                                         FALSE,
                                         irp );
        if (irp->MdlAddress == NULL) {
            FREE_POOL(senseInfoBuffer);
            SrbSetSenseInfoBuffer(Srb, NULL);
            SrbSetSenseInfoBufferLength(Srb, 0);
            IoFreeIrp( irp );
            TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,  "ClassSendSrbSynchronous: Can't allocate MDL\n"));
            return STATUS_INSUFFICIENT_RESOURCES;
        }

        _SEH2_TRY {

            //
            // the io manager unlocks these pages upon completion
            //

            MmProbeAndLockPages( irp->MdlAddress,
                                 KernelMode,
                                 (WriteToDevice ? IoReadAccess :
                                                  IoWriteAccess));

#ifdef _MSC_VER
        #pragma warning(suppress: 6320) // We want to handle any exception that MmProbeAndLockPages might throw
#endif
        } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
            status = _SEH2_GetExceptionCode();

            FREE_POOL(senseInfoBuffer);
            SrbSetSenseInfoBuffer(Srb, NULL);
            SrbSetSenseInfoBufferLength(Srb, 0);
            IoFreeMdl(irp->MdlAddress);
            IoFreeIrp(irp);

            TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,  "ClassSendSrbSynchronous: Exception %lx "
                           "locking buffer\n", status));
            _SEH2_YIELD(return status);
        } _SEH2_END;
    }

    //
    // Set the transfer length.
    //

    SrbSetDataTransferLength(Srb, BufferLength);

    //
    // Zero out status.
    //

    SrbSetScsiStatus(Srb, 0);
    Srb->SrbStatus = 0;
    SrbSetNextSrb(Srb, NULL);

    //
    // Set up IRP Address.
    //

    SrbSetOriginalRequest(Srb, irp);

    //
    // Call the port driver with the request and wait for it to complete.
    //

    status = IoCallDriver(fdoExtension->CommonExtension.LowerDeviceObject, irp);

    if (status == STATUS_PENDING) {
        (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
        status = ioStatus.Status;
    }

//    NT_ASSERT(SRB_STATUS(Srb->SrbStatus) != SRB_STATUS_PENDING);
    NT_ASSERT(status != STATUS_PENDING);
    NT_ASSERT(!(Srb->SrbStatus & SRB_STATUS_QUEUE_FROZEN));

    //
    // Clear the IRP address in SRB as IRP has been freed at this time
    // and don't want to leave any references that may be accessed.
    //

    SrbSetOriginalRequest(Srb, NULL);

    //
    // Check that request completed without error.
    //

    if (SRB_STATUS(Srb->SrbStatus) != SRB_STATUS_SUCCESS) {

        LONGLONG retryInterval;

        TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassSendSrbSynchronous - srb %ph failed (op=%s srbstat=%s(%xh), irpstat=%xh, sense=%s/%s/%s)", Srb,
                    DBGGETSCSIOPSTR(Srb),
                    DBGGETSRBSTATUSSTR(Srb), (ULONG)Srb->SrbStatus, status,
                    DBGGETSENSECODESTR(Srb),
                    DBGGETADSENSECODESTR(Srb),
                    DBGGETADSENSEQUALIFIERSTR(Srb)));

        //
        // assert that the queue is not frozen
        //

        NT_ASSERT(!TEST_FLAG(Srb->SrbStatus, SRB_STATUS_QUEUE_FROZEN));

        //
        // Update status and determine if request should be retried.
        //

        retry = InterpretSenseInfoWithoutHistory(Fdo,
                                                 NULL, // no valid irp exists
                                                 (PSCSI_REQUEST_BLOCK)Srb,
                                                 IRP_MJ_SCSI,
                                                 0,
                                                 MAXIMUM_RETRIES  - retryCount,
                                                 &status,
                                                 &retryInterval);

        if (retry) {

            BOOLEAN validSense = FALSE;
            UCHAR additionalSenseCode = 0;

            if (status == STATUS_DEVICE_NOT_READY) {

                validSense = ScsiGetSenseKeyAndCodes(senseInfoBuffer,
                                                     SrbGetSenseInfoBufferLength(Srb),
                                                     SCSI_SENSE_OPTIONS_FIXED_FORMAT_IF_UNKNOWN_FORMAT_INDICATED,
                                                     NULL,
                                                     &additionalSenseCode,
                                                     NULL);
            }

            if ((validSense && additionalSenseCode == SCSI_ADSENSE_LUN_NOT_READY) ||
                (SRB_STATUS(Srb->SrbStatus) == SRB_STATUS_SELECTION_TIMEOUT)) {

                LARGE_INTEGER delay;

                //
                // Delay for at least 2 seconds.
                //

                if (retryInterval < 2*1000*1000*10) {
                    retryInterval = 2*1000*1000*10;
                }

                delay.QuadPart = -retryInterval;

                //
                // Stall for a while to let the device become ready
                //

                KeDelayExecutionThread(KernelMode, FALSE, &delay);

            }


            //
            // If retries are not exhausted then retry this operation.
            //

            if (retryCount--) {

                if (PORT_ALLOCATED_SENSE_EX(fdoExtension, Srb)) {
                    FREE_PORT_ALLOCATED_SENSE_BUFFER_EX(fdoExtension, Srb);
                }

                goto retry;
            }
        }


    } else {
        fdoData->LoggedTURFailureSinceLastIO = FALSE;
        status = STATUS_SUCCESS;
    }

    //
    // required even though we allocated our own, since the port driver may
    // have allocated one also
    //

    if (PORT_ALLOCATED_SENSE_EX(fdoExtension, Srb)) {
        FREE_PORT_ALLOCATED_SENSE_BUFFER_EX(fdoExtension, Srb);
    }

    FREE_POOL(senseInfoBuffer);
    SrbSetSenseInfoBuffer(Srb, NULL);
    SrbSetSenseInfoBufferLength(Srb, 0);

    return status;
}


/*++////////////////////////////////////////////////////////////////////////////

ClassInterpretSenseInfo()

Routine Description:

    This routine interprets the data returned from the SCSI
    request sense. It determines the status to return in the
    IRP and whether this request can be retried.

Arguments:

    Fdo - Supplies the device object associated with this request.

    Srb - Supplies the scsi request block which failed.

    MajorFunctionCode - Supplies the function code to be used for logging.

    IoDeviceCode - Supplies the device code to be used for logging.

    RetryCount - Number of times that the request has been retried.

    Status - Returns the status for the request.

    RetryInterval - Number of seconds before the request should be retried.
                    Zero indicates the request should be immediately retried.

Return Value:

    BOOLEAN TRUE: Drivers should retry this request.
            FALSE: Drivers should not retry this request.

--*/
BOOLEAN
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassInterpretSenseInfo(
    _In_ PDEVICE_OBJECT Fdo,
    _In_ PSCSI_REQUEST_BLOCK _Srb,
    _In_ UCHAR MajorFunctionCode,
    _In_ ULONG IoDeviceCode,
    _In_ ULONG RetryCount,
    _Out_ NTSTATUS *Status,
    _Out_opt_ _Deref_out_range_(0,100) ULONG *RetryInterval
    )
{
    PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
    PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
    PSTORAGE_REQUEST_BLOCK_HEADER Srb = (PSTORAGE_REQUEST_BLOCK_HEADER)_Srb;
    PVOID senseBuffer = SrbGetSenseInfoBuffer(Srb);
    BOOLEAN retry = TRUE;
    BOOLEAN logError = FALSE;
    BOOLEAN unhandledError = FALSE;
    BOOLEAN incrementErrorCount = FALSE;

    //
    // NOTE: This flag must be used only for read/write requests that
    // fail with a unexpected retryable error.
    //
    BOOLEAN logRetryableError = TRUE;

    //
    // Indicates if we should log this error in our internal log.
    //
    BOOLEAN logErrorInternal = TRUE;

    ULONGLONG badSector = 0;
    ULONG uniqueId = 0;

    NTSTATUS logStatus;

    ULONGLONG readSector;
    ULONG index;

    ULONG retryInterval = 0;
    KIRQL oldIrql;
    PCDB cdb = SrbGetCdb(Srb);
    UCHAR cdbOpcode = 0;
    ULONG cdbLength = SrbGetCdbLength(Srb);

#if DBG
    BOOLEAN isReservationConflict = FALSE;
#endif

    if (cdb) {
        cdbOpcode = cdb->CDB6GENERIC.OperationCode;
    }

    *Status = STATUS_IO_DEVICE_ERROR;
    logStatus = -1;

    if (TEST_FLAG(SrbGetSrbFlags(Srb), SRB_CLASS_FLAGS_PAGING)) {

        //
        // Log anything remotely incorrect about paging i/o
        //

        logError = TRUE;
        uniqueId = 301;
        logStatus = IO_WARNING_PAGING_FAILURE;
    }

    //
    // Check that request sense buffer is valid.
    //

    NT_ASSERT(fdoExtension->CommonExtension.IsFdo);


    //
    // must handle the SRB_STATUS_INTERNAL_ERROR case first,
    // as it has  all the flags set.
    //

    if (SRB_STATUS(Srb->SrbStatus) == SRB_STATUS_INTERNAL_ERROR) {

        TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
                    "ClassInterpretSenseInfo: Internal Error code is %x\n",
                    SrbGetSystemStatus(Srb)));

        retry = FALSE;
        *Status = SrbGetSystemStatus(Srb);

    } else if (SrbGetScsiStatus(Srb) == SCSISTAT_RESERVATION_CONFLICT) {

        //
        // Need to reserve STATUS_DEVICE_BUSY to convey reservation conflict
        // for read/write requests as there are upper level components that
        // have built-in assumptions that STATUS_DEVICE_BUSY implies reservation
        // conflict.
        //
        *Status = STATUS_DEVICE_BUSY;
        retry = FALSE;
        logError = FALSE;
#if DBG
        isReservationConflict = TRUE;
#endif

    } else {

        BOOLEAN validSense = FALSE;

        if ((Srb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID) && senseBuffer)  {

            UCHAR errorCode = 0;
            UCHAR senseKey = 0;
            UCHAR addlSenseCode = 0;
            UCHAR addlSenseCodeQual = 0;
            BOOLEAN isIncorrectLengthValid = FALSE;
            BOOLEAN incorrectLength = FALSE;
            BOOLEAN isInformationValid = FALSE;
            ULONGLONG information = 0;


            validSense = ScsiGetSenseKeyAndCodes(senseBuffer,
                                                 SrbGetSenseInfoBufferLength(Srb),
                                                 SCSI_SENSE_OPTIONS_NONE,
                                                 &senseKey,
                                                 &addlSenseCode,
                                                 &addlSenseCodeQual);

            if (!validSense && !IsSenseDataFormatValueValid(senseBuffer)) {

                NT_ASSERT(FALSE);

                validSense = ScsiGetFixedSenseKeyAndCodes(senseBuffer,
                                                          SrbGetSenseInfoBufferLength(Srb),
                                                          &senseKey,
                                                          &addlSenseCode,
                                                          &addlSenseCodeQual);
            }

            if (!validSense) {
                goto __ClassInterpretSenseInfo_ProcessingInvalidSenseBuffer;
            }

            errorCode = ScsiGetSenseErrorCode(senseBuffer);

            TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: Error code is %x\n", errorCode));
            TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: Sense key is %x\n", senseKey));
            TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: Additional sense code is %x\n", addlSenseCode));
            TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: Additional sense code qualifier is %x\n", addlSenseCodeQual));

            if (IsDescriptorSenseDataFormat(senseBuffer)) {

                //
                // Sense data in Descriptor format
                //

                PVOID startBuffer = NULL;
                UCHAR startBufferLength = 0;


                if (ScsiGetSenseDescriptor(senseBuffer,
                                           SrbGetSenseInfoBufferLength(Srb),
                                           &startBuffer,
                                           &startBufferLength)) {
                    UCHAR outType;
                    PVOID outBuffer = NULL;
                    UCHAR outBufferLength = 0;
                    BOOLEAN foundBlockCommandType = FALSE;
                    BOOLEAN foundInformationType = FALSE;
                    UCHAR descriptorLength = 0;

                    UCHAR typeList[2] = {SCSI_SENSE_DESCRIPTOR_TYPE_INFORMATION,
                                         SCSI_SENSE_DESCRIPTOR_TYPE_BLOCK_COMMAND};

                    while ((!foundBlockCommandType || !foundInformationType) &&
                           ScsiGetNextSenseDescriptorByType(startBuffer,
                                                            startBufferLength,
                                                            typeList,
                                                            ARRAYSIZE(typeList),
                                                            &outType,
                                                            &outBuffer,
                                                            &outBufferLength)) {

                        descriptorLength = ScsiGetSenseDescriptorLength(outBuffer);

                        if (outBufferLength < descriptorLength) {

                            // Descriptor data is truncated.
                            // Complete searching descriptors. Exit the loop now.
                            break;
                        }

                        if (outType == SCSI_SENSE_DESCRIPTOR_TYPE_BLOCK_COMMAND) {

                            //
                            // Block Command type
                            //

                            if (!foundBlockCommandType) {

                                foundBlockCommandType = TRUE;

                                if (ScsiValidateBlockCommandSenseDescriptor(outBuffer, outBufferLength)) {
                                    incorrectLength = ((PSCSI_SENSE_DESCRIPTOR_BLOCK_COMMAND)outBuffer)->IncorrectLength;
                                    isIncorrectLengthValid = TRUE;
                                }
                            } else {

                                //
                                // A Block Command descriptor is already found earlier.
                                //
                                // T10 SPC specification only allows one descriptor for Block Command Descriptor type.
                                // Assert here to catch devices that violate this rule. Ignore this descriptor.
                                //
                                NT_ASSERT(FALSE);
                            }

                        } else if (outType == SCSI_SENSE_DESCRIPTOR_TYPE_INFORMATION) {

                            //
                            // Information type
                            //

                            if (!foundInformationType) {

                                foundInformationType = TRUE;

                                if (ScsiValidateInformationSenseDescriptor(outBuffer, outBufferLength)) {
                                    REVERSE_BYTES_QUAD(&information, &(((PSCSI_SENSE_DESCRIPTOR_INFORMATION)outBuffer)->Information));
                                    isInformationValid = TRUE;
                                }
                            } else {

                                //
                                // A Information descriptor is already found earlier.
                                //
                                // T10 SPC specification only allows one descriptor for Information Descriptor type.
                                // Assert here to catch devices that violate this rule. Ignore this descriptor.
                                //
                                NT_ASSERT(FALSE);
                            }

                        } else {

                            //
                            // ScsiGetNextDescriptorByType should only return a type that is specified by us.
                            //
                            NT_ASSERT(FALSE);
                            break;
                        }

                        //
                        // Advance to start address of next descriptor
                        //
                        startBuffer = (PUCHAR)outBuffer + descriptorLength;
                        startBufferLength = outBufferLength - descriptorLength;
                    }
                }
            } else {

                //
                // Sense data in Fixed format
                //

                incorrectLength = ((PFIXED_SENSE_DATA)(senseBuffer))->IncorrectLength;
                REVERSE_BYTES(&information, &(((PFIXED_SENSE_DATA)senseBuffer)->Information));
                isInformationValid = TRUE;
                isIncorrectLengthValid = TRUE;
            }


            switch (senseKey) {

                case SCSI_SENSE_NO_SENSE: {

                    //
                    // Check other indicators.
                    //

                    if (isIncorrectLengthValid && incorrectLength) {

                        TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,  "ClassInterpretSenseInfo: "
                                    "Incorrect length detected.\n"));
                        *Status = STATUS_INVALID_BLOCK_LENGTH ;
                        retry   = FALSE;

                    } else {

                        TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL,  "ClassInterpretSenseInfo: "
                                    "No specific sense key\n"));
                        *Status = STATUS_IO_DEVICE_ERROR;
                        retry   = TRUE;
                    }

                    break;
                } // end SCSI_SENSE_NO_SENSE

                case SCSI_SENSE_RECOVERED_ERROR: {

                    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,  "ClassInterpretSenseInfo: "
                                "Recovered error\n"));
                    *Status = STATUS_SUCCESS;
                    retry = FALSE;
                    logError = TRUE;
                    uniqueId = 258;

                    switch(addlSenseCode) {
                        case SCSI_ADSENSE_TRACK_ERROR:
                        case SCSI_ADSENSE_SEEK_ERROR: {
                            logStatus = IO_ERR_SEEK_ERROR;
                            break;
                        }

                        case SCSI_ADSENSE_REC_DATA_NOECC:
                        case SCSI_ADSENSE_REC_DATA_ECC: {
                            logStatus = IO_RECOVERED_VIA_ECC;
                            break;
                        }

                        case SCSI_ADSENSE_FAILURE_PREDICTION_THRESHOLD_EXCEEDED: {

                            UCHAR wmiEventData[sizeof(ULONG)+sizeof(UCHAR)] = {0};

                            *((PULONG)wmiEventData) = sizeof(UCHAR);
                            wmiEventData[sizeof(ULONG)] = addlSenseCodeQual;

                            //
                            // Don't log another eventlog if we have already logged once
                            // NOTE: this should have been interlocked, but the structure
                            //       was publicly defined to use a BOOLEAN (char).  Since
                            //       media only reports these errors once per X minutes,
                            //       the potential race condition is nearly non-existant.
                            //       the worst case is duplicate log entries, so ignore.
                            //

                            logError = FALSE;
                            if (fdoExtension->FailurePredicted == 0) {
                                logError = TRUE;
                            }
                            fdoExtension->FailureReason = addlSenseCodeQual;
                            logStatus = IO_WRN_FAILURE_PREDICTED;

                            ClassNotifyFailurePredicted(fdoExtension,
                                                        (PUCHAR)wmiEventData,
                                                        sizeof(wmiEventData),
                                                        FALSE,   // do not log error
                                                        4,          // unique error value
                                                        SrbGetPathId(Srb),
                                                        SrbGetTargetId(Srb),
                                                        SrbGetLun(Srb));

                            fdoExtension->FailurePredicted = TRUE;
                            break;
                        }

                        default: {
                            logStatus = IO_ERR_CONTROLLER_ERROR;
                            break;
                        }

                    } // end switch(addlSenseCode)

                    if (isIncorrectLengthValid && incorrectLength) {

                        TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,  "ClassInterpretSenseInfo: "
                                    "Incorrect length detected.\n"));
                        *Status = STATUS_INVALID_BLOCK_LENGTH ;
                    }


                    break;
                } // end SCSI_SENSE_RECOVERED_ERROR

                case SCSI_SENSE_NOT_READY: {

                    TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
                                "Device not ready\n"));
                    *Status = STATUS_DEVICE_NOT_READY;

                    switch (addlSenseCode) {

                        case SCSI_ADSENSE_LUN_NOT_READY: {

                            TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
                                        "Lun not ready\n"));

                            retryInterval = NOT_READY_RETRY_INTERVAL;

                            switch (addlSenseCodeQual) {

                                case SCSI_SENSEQ_BECOMING_READY: {
                                    DEVICE_EVENT_BECOMING_READY notReady = {0};

                                    logRetryableError = FALSE;
                                    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
                                                "In process of becoming ready\n"));

                                    notReady.Version = 1;
                                    notReady.Reason = 1;
                                    notReady.Estimated100msToReady = retryInterval * 10;
                                    ClassSendNotification(fdoExtension,
                                                           &GUID_IO_DEVICE_BECOMING_READY,
                                                           sizeof(DEVICE_EVENT_BECOMING_READY),
                                                           &notReady);
                                    break;
                                }

                                case SCSI_SENSEQ_MANUAL_INTERVENTION_REQUIRED: {
                                    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
                                                "Manual intervention required\n"));
                                    *Status = STATUS_NO_MEDIA_IN_DEVICE;
                                    retry = FALSE;
                                    break;
                                }

                                case SCSI_SENSEQ_FORMAT_IN_PROGRESS: {
                                    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
                                                "Format in progress\n"));
                                    retry = FALSE;
                                    break;
                                }

                                case SCSI_SENSEQ_OPERATION_IN_PROGRESS: {
                                    DEVICE_EVENT_BECOMING_READY notReady = {0};

                                    logRetryableError = FALSE;
                                    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
                                                "Operation In Progress\n"));

                                    notReady.Version = 1;
                                    notReady.Reason = 2;
                                    notReady.Estimated100msToReady = retryInterval * 10;
                                    ClassSendNotification(fdoExtension,
                                                           &GUID_IO_DEVICE_BECOMING_READY,
                                                           sizeof(DEVICE_EVENT_BECOMING_READY),
                                                           &notReady);

                                    break;
                                }

                                case SCSI_SENSEQ_LONG_WRITE_IN_PROGRESS: {
                                    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
                                                "Long write in progress\n"));
                                    //
                                    // This has been seen as a transcient failure on some cdrom
                                    // drives. The cdrom class driver is going to override this
                                    // setting but has no way of dropping the retry interval
                                    //
                                    retry = FALSE;
                                    retryInterval = 1;
                                    break;
                                }

                                case SCSI_SENSEQ_SPACE_ALLOC_IN_PROGRESS: {
                                    logRetryableError = FALSE;
                                    TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
                                                "The device (%p) is busy allocating space.\n",
                                                Fdo));

                                    //
                                    // This indicates that a thinly-provisioned device has hit
                                    // a temporary resource exhaustion and is busy allocating
                                    // more space.  We need to retry the request as the device
                                    // will eventually be able to service it.
                                    //
                                    *Status = STATUS_RETRY;
                                    retry = TRUE;

                                    break;
                                }

                                case SCSI_SENSEQ_CAUSE_NOT_REPORTABLE: {

                                    if (!TEST_FLAG(fdoExtension->ScanForSpecialFlags,
                                                   CLASS_SPECIAL_CAUSE_NOT_REPORTABLE_HACK)) {

                                        TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,
                                                    "ClassInterpretSenseInfo: "
                                                    "not ready, cause unknown\n"));
                                        /*
                                        Many non-WHQL certified drives (mostly CD-RW) return
                                        this when they have no media instead of the obvious
                                        choice of:

                                        SCSI_SENSE_NOT_READY/SCSI_ADSENSE_NO_MEDIA_IN_DEVICE

                                        These drives should not pass WHQL certification due
                                        to this discrepency.

                                        */
                                        retry = FALSE;
                                        break;

                                    } else {

                                        //
                                        // Treat this as init command required and fall through.
                                        //
                                    }
                                }

                                case SCSI_SENSEQ_INIT_COMMAND_REQUIRED:
                                default: {
                                    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
                                                "Initializing command required\n"));
                                    retryInterval = 0; // go back to default
                                    logRetryableError = FALSE;

                                    //
                                    // This sense code/additional sense code
                                    // combination may indicate that the device
                                    // needs to be started.  Send an start unit if this
                                    // is a disk device.
                                    //
                                    if (TEST_FLAG(fdoExtension->DeviceFlags, DEV_SAFE_START_UNIT) &&
                                        !TEST_FLAG(SrbGetSrbFlags(Srb), SRB_CLASS_FLAGS_LOW_PRIORITY)){

                                        ClassSendStartUnit(Fdo);
                                    }
                                    break;
                                }

                            } // end switch (addlSenseCodeQual)
                            break;
                        }

                        case SCSI_ADSENSE_NO_MEDIA_IN_DEVICE: {
                            TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
                                        "No Media in device.\n"));
                            *Status = STATUS_NO_MEDIA_IN_DEVICE;
                            retry = FALSE;

                            //
                            // signal MCN that there isn't any media in the device
                            //
                            if (!TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA)) {
                                TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
                                            "No Media in a non-removable device %p\n",
                                            Fdo));
                            }

                            if (addlSenseCodeQual == 0xCC){
                                /*
                                 *  The IMAPI filter returns this ASCQ value when it is burning CD-R media.
                                 *  We want to indicate that the media is not present to most applications;
                                 *  but RSM has to know that the media is still in the drive (i.e. the drive is not free).
                                 */
                                ClassSetMediaChangeState(fdoExtension, MediaUnavailable, FALSE);
                            }
                            else {
                                ClassSetMediaChangeState(fdoExtension, MediaNotPresent, FALSE);
                            }

                            break;
                        }
                    } // end switch (addlSenseCode)

                    break;
                } // end SCSI_SENSE_NOT_READY

                case SCSI_SENSE_MEDIUM_ERROR: {
                    TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
                                "Medium Error (bad block)\n"));
                    *Status = STATUS_DEVICE_DATA_ERROR;

                    retry = FALSE;
                    logError = TRUE;
                    uniqueId = 256;
                    logStatus = IO_ERR_BAD_BLOCK;

                    //
                    // Check if this error is due to unknown format
                    //
                    if (addlSenseCode == SCSI_ADSENSE_INVALID_MEDIA) {

                        switch (addlSenseCodeQual) {

                            case SCSI_SENSEQ_UNKNOWN_FORMAT: {

                                *Status = STATUS_UNRECOGNIZED_MEDIA;

                                //
                                // Log error only if this is a paging request
                                //
                                if (!TEST_FLAG(SrbGetSrbFlags(Srb), SRB_CLASS_FLAGS_PAGING)) {
                                    logError = FALSE;
                                }
                                break;
                            }

                            case SCSI_SENSEQ_CLEANING_CARTRIDGE_INSTALLED: {

                                *Status = STATUS_CLEANER_CARTRIDGE_INSTALLED;
                                logError = FALSE;
                                break;

                            }
                            default: {
                                break;
                            }
                        } // end switch addlSenseCodeQual

                    } // end SCSI_ADSENSE_INVALID_MEDIA

                    break;

                } // end SCSI_SENSE_MEDIUM_ERROR

                case SCSI_SENSE_HARDWARE_ERROR: {
                    BOOLEAN logHardwareError = TRUE;

                    TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
                                "Hardware error\n"));

                    if (fdoData->LegacyErrorHandling == FALSE) {
                        //
                        // Hardware errors indicate something has seriously gone
                        // wrong with the device and retries are very unlikely to
                        // succeed so fail this request back immediately.
                        //
                        retry = FALSE;
                        *Status = STATUS_DEVICE_HARDWARE_ERROR;
                        logError = FALSE;

                    } else {
                        //
                        // Revert to legacy behavior.  That is, retry everything by default.
                        //
                        retry = TRUE;
                        *Status = STATUS_IO_DEVICE_ERROR;
                        logError = TRUE;
                        uniqueId = 257;
                        logStatus = IO_ERR_CONTROLLER_ERROR;
                        logHardwareError = FALSE;

                        //
                        // This indicates the possibility of a dropped FC packet.
                        //
                        if ((addlSenseCode == SCSI_ADSENSE_LOGICAL_UNIT_ERROR && addlSenseCodeQual == SCSI_SENSEQ_TIMEOUT_ON_LOGICAL_UNIT) ||
                            (addlSenseCode == SCSI_ADSENSE_DATA_TRANSFER_ERROR && addlSenseCodeQual == SCSI_SENSEQ_INITIATOR_RESPONSE_TIMEOUT)) {
                            //
                            // Fail requests that report this error back to the application.
                            //
                            retry = FALSE;

                            //
                            // Log a more descriptive error and avoid a second
                            // error message (IO_ERR_CONTROLLER_ERROR) being logged.
                            //
                            logHardwareError = TRUE;
                            logError = FALSE;
                        }
                    }

                    //
                    // If CRC error was returned, retry after a slight delay.
                    //
                    if (addlSenseCode == SCSI_ADSENSE_LUN_COMMUNICATION &&
                        addlSenseCodeQual == SCSI_SESNEQ_COMM_CRC_ERROR) {
                        retry = TRUE;
                        retryInterval = 1;
                        logHardwareError = FALSE;
                        logError = FALSE;
                    }

                    //
                    // Hardware errors warrant a more descriptive error.
                    // Specifically, we need to ensure this disk is easily
                    // identifiable.
                    //
                    if (logHardwareError) {
                        UCHAR senseInfoBufferLength = SrbGetSenseInfoBufferLength(Srb);
                        UCHAR senseBufferSize = 0;

                        if (ScsiGetTotalSenseByteCountIndicated(senseBuffer,
                                                                senseInfoBufferLength,
                                                                &senseBufferSize)) {

                            senseBufferSize = min(senseBufferSize, senseInfoBufferLength);

                        } else {
                            //
                            // it's smaller than required to read the total number of
                            // valid bytes, so just use the SenseInfoBufferLength field.
                            //
                            senseBufferSize = senseInfoBufferLength;
                        }

                        ClasspQueueLogIOEventWithContextWorker(Fdo,
                                                               senseBufferSize,
                                                               senseBuffer,
                                                               SRB_STATUS(Srb->SrbStatus),
                                                               SrbGetScsiStatus(Srb),
                                                               (ULONG)IO_ERROR_IO_HARDWARE_ERROR,
                                                               cdbLength,
                                                               cdb,
                                                               NULL);
                    }

                    break;
                } // end SCSI_SENSE_HARDWARE_ERROR

                case SCSI_SENSE_ILLEGAL_REQUEST: {

                    TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
                                "Illegal SCSI request\n"));
                    *Status = STATUS_INVALID_DEVICE_REQUEST;
                    retry = FALSE;

                    switch (addlSenseCode) {

                        case SCSI_ADSENSE_NO_SENSE: {

                            switch (addlSenseCodeQual) {

                                //
                                // 1. Duplicate List Identifier
                                //
                                case SCSI_SENSEQ_OPERATION_IS_IN_PROGRESS: {

                                    //
                                    // XCOPY, READ BUFFER and CHANGE ALIASES return this sense combination under
                                    // certain conditions. Since these commands aren't sent down natively by the
                                    // Windows OS, return the default error for them and only handle this sense
                                    // combination for offload data transfer commands.
                                    //
                                    if (ClasspIsOffloadDataTransferCommand(cdb)) {

                                        TracePrint((TRACE_LEVEL_ERROR,
                                                    TRACE_FLAG_IOCTL,
                                                    "ClassInterpretSenseInfo (%p): Duplicate List Identifier (command %x, parameter field offset 0x%016llx)\n",
                                                    Fdo,
                                                    cdbOpcode,
                                                    information));

                                        NT_ASSERTMSG("Duplicate list identifier specified", FALSE);

                                        //
                                        // The host should ensure that it uses unique list id for each TokenOperation request.
                                        //
                                        *Status = STATUS_OPERATION_IN_PROGRESS;
                                    }
                                    break;
                                }
                            }
                            break;
                        }

                        case SCSI_ADSENSE_LUN_COMMUNICATION: {

                            switch (addlSenseCodeQual) {

                                //
                                // 1. Source/Destination pairing can't communicate with each other or the copy manager.
                                //
                                case SCSI_SENSEQ_UNREACHABLE_TARGET: {

                                    TracePrint((TRACE_LEVEL_ERROR,
                                                TRACE_FLAG_IOCTL,
                                                "ClassInterpretSenseInfo (%p): Source-Destination LUNs can't communicate (command %x)\n",
                                                Fdo,
                                                cdbOpcode));

                                    *Status = STATUS_DEVICE_UNREACHABLE;
                                    break;
                                }
                            }
                            break;
                        }

                        case SCSI_ADSENSE_COPY_TARGET_DEVICE_ERROR: {

                            switch (addlSenseCodeQual) {

                                //
                                // 1. Sum of logical block fields in all block device range descriptors is greater than number
                                //    of logical blocks in the ROD minus block offset into ROD
                                //
                                case SCSI_SENSEQ_DATA_UNDERRUN: {

                                    TracePrint((TRACE_LEVEL_ERROR,
                                                TRACE_FLAG_IOCTL,
                                                "ClassInterpretSenseInfo (%p): Host specified a transfer length greater than what is represented by the token (considering the offset) [command %x]\n",
                                                Fdo,
                                                cdbOpcode));

                                    NT_ASSERTMSG("Host specified blocks to write beyond what is represented by the token", FALSE);

                                    *Status = STATUS_DATA_OVERRUN;
                                    break;
                                }
                            }
                            break;
                        }

                        //
                        // 1. Parameter data truncation (e.g. last descriptor was not fully specified)
                        //
                        case SCSI_ADSENSE_PARAMETER_LIST_LENGTH: {

                            TracePrint((TRACE_LEVEL_ERROR,
                                        TRACE_FLAG_IOCTL,
                                        "ClassInterpretSenseInfo (%p): Target truncated the block device range descriptors in the parameter list (command %x)\n",
                                        Fdo,
                                        cdbOpcode));

                            NT_ASSERTMSG("Parameter data truncation", FALSE);

                            *Status = STATUS_DATA_OVERRUN;
                            break;
                        }

                        case SCSI_ADSENSE_ILLEGAL_COMMAND: {
                            TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
                                        "Illegal command\n"));
                            break;
                        }

                        case SCSI_ADSENSE_ILLEGAL_BLOCK: {

                            LARGE_INTEGER logicalBlockAddr;
                            LARGE_INTEGER lastLBA;
                            ULONG numTransferBlocks = 0;

                            logicalBlockAddr.QuadPart = 0;

                            TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: Illegal block address\n"));

                            *Status = STATUS_NONEXISTENT_SECTOR;

                            if (Fdo->DeviceType == FILE_DEVICE_DISK) {

                                if (IS_SCSIOP_READWRITE(cdbOpcode) && cdb) {

                                    if (TEST_FLAG(fdoExtension->DeviceFlags, DEV_USE_16BYTE_CDB)) {
                                        REVERSE_BYTES_QUAD(&logicalBlockAddr, &cdb->CDB16.LogicalBlock);
                                        REVERSE_BYTES(&numTransferBlocks, &cdb->CDB16.TransferLength);
                                    } else {
                                        REVERSE_BYTES(&logicalBlockAddr.LowPart, &cdb->CDB10.LogicalBlockByte0);
                                        REVERSE_BYTES_SHORT((PUSHORT)&numTransferBlocks, &cdb->CDB10.TransferBlocksMsb);
                                    }

                                    REVERSE_BYTES_QUAD(&lastLBA, &fdoData->LastKnownDriveCapacityData.LogicalBlockAddress);

                                    if ((logicalBlockAddr.QuadPart > lastLBA.QuadPart) ||
                                        ((logicalBlockAddr.QuadPart + numTransferBlocks - 1) > lastLBA.QuadPart)) {

                                        TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL,  "ClassInterpretSenseInfo: "
                                                    "Request beyond boundary. Last LBA: 0x%I64X Read LBA: 0x%I64X Length: 0x%X\n",
                                                    (__int64) lastLBA.QuadPart, (__int64) logicalBlockAddr.QuadPart, numTransferBlocks));
                                    } else {
                                        //
                                        // Should only retry these if the request was
                                        // truly within our expected size.
                                        //
                                        // Fujitsu IDE drives have been observed to
                                        // return this error transiently for a legal LBA;
                                        // manual retry in the debugger then works, so
                                        // there is a good chance that a programmed retry
                                        // will also work.
                                        //

                                        retry = TRUE;
                                        retryInterval = 5;
                                    }
                                } else if (ClasspIsOffloadDataTransferCommand(cdb)) {

                                    //
                                    // 1. Number of logical blocks of block device range descriptor exceeds capacity of the medium
                                    //
                                    TracePrint((TRACE_LEVEL_ERROR,
                                                TRACE_FLAG_IOCTL,
                                                "ClassInterpretSenseInfo (%p): LBA out of range (command %x, parameter field offset 0x%016llx)\n",
                                                Fdo,
                                                cdbOpcode,
                                                information));

                                    NT_ASSERTMSG("Number of blocks specified exceeds LUN capacity", FALSE);
                                }
                            }
                            break;
                        }

                        //
                        //  1. Generic error - cause not reportable
                        //  2. Insufficient resources to create ROD
                        //  3. Insufficient resources to create Token
                        //  4. Max number of tokens exceeded
                        //  5. Remote Token creation not supported
                        //  6. Token expired
                        //  7. Token unknown
                        //  8. Unsupported Token type
                        //  9. Token corrupt
                        // 10. Token revoked
                        // 11. Token cancelled
                        // 12. Remote Token usage not supported
                        //
                        case SCSI_ADSENSE_INVALID_TOKEN: {

                            TracePrint((TRACE_LEVEL_ERROR,
                                        TRACE_FLAG_IOCTL,
                                        "ClassInterpretSenseInfo (%p): Invalid/Expired/Modified token specified (command %x, parameter field offset 0x%016llx)\n",
                                        Fdo,
                                        cdbOpcode,
                                        information));

                            *Status = STATUS_INVALID_TOKEN;
                            break;
                        }

                        case SCSI_ADSENSE_INVALID_CDB: {
                            if (ClasspIsOffloadDataTransferCommand(cdb)) {

                                //
                                // 1. Mismatched I_T nexus and list identifier
                                //
                                TracePrint((TRACE_LEVEL_ERROR,
                                            TRACE_FLAG_IOCTL,
                                            "ClassInterpretSenseInfo (%p): Incorrect I_T nexus likely used (command %x)\n",
                                            Fdo,
                                            cdbOpcode));

                                //
                                // The host should ensure that it sends TokenOperation and ReceiveTokenInformation for the same
                                // list Id using the same I_T nexus.
                                //
                                *Status = STATUS_INVALID_INITIATOR_TARGET_PATH;

                            } else {

                                TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,  "ClassInterpretSenseInfo: "
                                            "Invalid CDB\n"));

                                //
                                // Note: the retry interval is not typically used.
                                // it is set here only because a ClassErrorHandler
                                // cannot set the retryInterval, and the error may
                                // require a few commands to be sent to clear whatever
                                // caused this condition (i.e. disk clears the write
                                // cache, requiring at least two commands)
                                //
                                // hopefully, this shortcoming can be changed for
                                // blackcomb.
                                //

                                retryInterval = 3;
                            }
                            break;
                        }

                        case SCSI_ADSENSE_INVALID_LUN: {
                            TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,  "ClassInterpretSenseInfo: "
                                        "Invalid LUN\n"));
                            *Status = STATUS_NO_SUCH_DEVICE;
                            break;
                        }

                        case SCSI_ADSENSE_INVALID_FIELD_PARAMETER_LIST: {

                            switch (addlSenseCodeQual) {

                                //
                                // 1. Alignment violation (e.g. copy manager is unable to copy because destination offset is NOT aligned to LUN's granularity/alignment)
                                //
                                case SCSI_SENSEQ_INVALID_RELEASE_OF_PERSISTENT_RESERVATION: {

                                    TracePrint((TRACE_LEVEL_ERROR,
                                                TRACE_FLAG_IOCTL,
                                                "ClassInterpretSenseInfo (%p): Alignment violation for command %x.\n",
                                                Fdo,
                                                cdbOpcode));

                                    NT_ASSERTMSG("Specified offset is not aligned to LUN's granularity", FALSE);

                                    *Status = STATUS_INVALID_OFFSET_ALIGNMENT;
                                    break;
                                }

                                //
                                // 1. Number of block device range descriptors is greater than maximum range descriptors
                                //
                                case SCSI_SENSEQ_TOO_MANY_SEGMENT_DESCRIPTORS: {

                                    TracePrint((TRACE_LEVEL_ERROR,
                                                TRACE_FLAG_IOCTL,
                                                "ClassInterpretSenseInfo (%p): Too many descriptors in parameter list for command %x (parameter field offset 0x%016llx)\n",
                                                Fdo,
                                                cdbOpcode,
                                                information));

                                    NT_ASSERTMSG("Too many descriptors specified", FALSE);

                                    *Status = STATUS_TOO_MANY_SEGMENT_DESCRIPTORS;
                                    break;
                                }

                                default: {

                                    if (ClasspIsOffloadDataTransferCommand(cdb)) {

                                        //
                                        // 1. (Various) Invalid parameter length
                                        // 2. Requested inactivity timeout is greater than maximum inactivity timeout
                                        // 3. Same LBA is included in more than one block device range descriptor (overlapping LBAs)
                                        // 4. Total number of logical blocks of all block range descriptors is greater than the maximum transfer size
                                        // 5. Total number of logical blocks of all block range descriptors is greater than maximum token transfer size
                                        //    (e.g. WriteUsingToken descriptors specify a cumulative total block count that exceeds the PopulateToken that created the token)
                                        // 6. Block offset into ROD specified an offset that is greater than or equal to the number of logical blocks in the ROD
                                        // 7. Number of logical blocks in a block device range descriptor is greater than maximum transfer length in blocks
                                        //
                                        TracePrint((TRACE_LEVEL_ERROR,
                                                    TRACE_FLAG_IOCTL,
                                                    "ClassInterpretSenseInfo (%p): Illegal field in parameter list for command %x (parameter field offset 0x%016llx) [AddSense %x, AddSenseQ %x]\n",
                                                    Fdo,
                                                    cdbOpcode,
                                                    information,
                                                    addlSenseCode,
                                                    addlSenseCodeQual));

                                        NT_ASSERTMSG("Invalid field in parameter list", FALSE);

                                        *Status = STATUS_INVALID_FIELD_IN_PARAMETER_LIST;
                                    }

                                    break;
                                }
                            }
                            break;
                        }

                        case SCSI_ADSENSE_COPY_PROTECTION_FAILURE: {
                            TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,  "ClassInterpretSenseInfo: "
                                        "Copy protection failure\n"));

                            *Status = STATUS_COPY_PROTECTION_FAILURE;

                            switch (addlSenseCodeQual) {
                                case SCSI_SENSEQ_AUTHENTICATION_FAILURE:
                                    TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
                                                "ClassInterpretSenseInfo: "
                                                "Authentication failure\n"));
                                    *Status = STATUS_CSS_AUTHENTICATION_FAILURE;
                                    break;
                                case SCSI_SENSEQ_KEY_NOT_PRESENT:
                                    TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
                                                "ClassInterpretSenseInfo: "
                                                "Key not present\n"));
                                    *Status = STATUS_CSS_KEY_NOT_PRESENT;
                                    break;
                                case SCSI_SENSEQ_KEY_NOT_ESTABLISHED:
                                    TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
                                                "ClassInterpretSenseInfo: "
                                                "Key not established\n"));
                                    *Status = STATUS_CSS_KEY_NOT_ESTABLISHED;
                                    break;
                                case SCSI_SENSEQ_READ_OF_SCRAMBLED_SECTOR_WITHOUT_AUTHENTICATION:
                                    TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
                                                "ClassInterpretSenseInfo: "
                                                "Read of scrambled sector w/o "
                                                "authentication\n"));
                                    *Status = STATUS_CSS_SCRAMBLED_SECTOR;
                                    break;
                                case SCSI_SENSEQ_MEDIA_CODE_MISMATCHED_TO_LOGICAL_UNIT:
                                    TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
                                                "ClassInterpretSenseInfo: "
                                                "Media region does not logical unit "
                                                "region\n"));
                                    *Status = STATUS_CSS_REGION_MISMATCH;
                                    break;
                                case SCSI_SENSEQ_LOGICAL_UNIT_RESET_COUNT_ERROR:
                                    TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
                                                "ClassInterpretSenseInfo: "
                                                "Region set error -- region may "
                                                "be permanent\n"));
                                    *Status = STATUS_CSS_RESETS_EXHAUSTED;
                                    break;
                            } // end switch of ASCQ for COPY_PROTECTION_FAILURE

                            break;
                        }

                        case SCSI_ADSENSE_MUSIC_AREA: {
                            TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,  "ClassInterpretSenseInfo: "
                                        "Music area\n"));
                            break;
                        }

                        case SCSI_ADSENSE_DATA_AREA: {
                            TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,  "ClassInterpretSenseInfo: "
                                        "Data area\n"));
                            break;
                        }

                        case SCSI_ADSENSE_VOLUME_OVERFLOW: {
                            TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,  "ClassInterpretSenseInfo: "
                                        "Volume overflow\n"));
                            break;
                        }

                    } // end switch (addlSenseCode)

                    break;
                } // end SCSI_SENSE_ILLEGAL_REQUEST

                case SCSI_SENSE_UNIT_ATTENTION: {

                    ULONG count;

                    //
                    // A media change may have occured so increment the change
                    // count for the physical device
                    //

                    count = InterlockedIncrement((volatile LONG *)&fdoExtension->MediaChangeCount);
                    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,  "ClassInterpretSenseInfo: "
                                "Media change count for device %d incremented to %#lx\n",
                                fdoExtension->DeviceNumber, count));


                    switch (addlSenseCode) {
                        case SCSI_ADSENSE_MEDIUM_CHANGED: {
                            logRetryableError = FALSE;
                            TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,  "ClassInterpretSenseInfo: "
                                        "Media changed\n"));

                            if (!TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA)) {
                                TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_MCN, "ClassInterpretSenseInfo: "
                                            "Media Changed on non-removable device %p\n",
                                            Fdo));
                            }
                            ClassSetMediaChangeState(fdoExtension, MediaPresent, FALSE);
                            break;
                        }

                        case SCSI_ADSENSE_BUS_RESET: {
                            TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,  "ClassInterpretSenseInfo: "
                                        "Bus reset\n"));
                            break;
                        }

                        case SCSI_ADSENSE_PARAMETERS_CHANGED: {
                            logRetryableError = FALSE;
                            if (addlSenseCodeQual == SCSI_SENSEQ_CAPACITY_DATA_CHANGED) {
                                TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,  "ClassInterpretSenseInfo: "
                                            "Device capacity changed (e.g. thinly provisioned LUN). Retry the request.\n"));

                                ClassQueueCapacityChangedEventWorker(Fdo);

                                //
                                // Retry with 1 second delay as ClassQueueCapacityChangedEventWorker may trigger a couple of commands sent to disk.
                                //
                                retryInterval = 1;
                                retry = TRUE;
                            }
                            break;
                        }

                        case SCSI_ADSENSE_LB_PROVISIONING: {

                            switch (addlSenseCodeQual) {

                                case SCSI_SENSEQ_SOFT_THRESHOLD_REACHED: {

                                    logRetryableError = FALSE;
                                    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,  "ClassInterpretSenseInfo: "
                                            "Device (%p) has hit a soft threshold.\n",
                                            Fdo));

                                    //
                                    // This indicates that a resource provisioned or thinly
                                    // provisioned device has hit a soft threshold.  Queue a
                                    // worker thread to log a system event and then retry the
                                    // original request.
                                    //
                                    ClassQueueThresholdEventWorker(Fdo);
                                    break;
                                }
                                default: {
                                    retry = FALSE;
                                    break;
                                }

                            } // end  switch (addlSenseCodeQual)
                            break;
                        }

                        case SCSI_ADSENSE_OPERATING_CONDITIONS_CHANGED: {

                            if (addlSenseCodeQual == SCSI_SENSEQ_MICROCODE_CHANGED) {
                                //
                                // Device firmware has been changed. Retry the request.
                                //
                                logRetryableError = TRUE;
                                TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,  "ClassInterpretSenseInfo: "
                                            "Device firmware has been changed.\n"));

                                retryInterval = 1;
                                retry = TRUE;
                            } else {
                                //
                                // Device information has changed, we need to rescan the
                                // bus for changed information such as the capacity.
                                //
                                logRetryableError = FALSE;
                                TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,  "ClassInterpretSenseInfo: "
                                            "Device information changed. Invalidate the bus\n"));

                                if (addlSenseCodeQual == SCSI_SENSEQ_INQUIRY_DATA_CHANGED) {

                                    ClassQueueProvisioningTypeChangedEventWorker(Fdo);
                                }

                                if (addlSenseCodeQual == SCSI_SENSEQ_INQUIRY_DATA_CHANGED ||
                                    addlSenseCodeQual == SCSI_SENSEQ_OPERATING_DEFINITION_CHANGED) {

                                    //
                                    // Since either the LB provisioning type changed, or the block/slab size
                                    // changed, next time anyone trying to query the FunctionSupportInfo, we
                                    // will requery the device.
                                    //
                                    InterlockedIncrement((volatile LONG *)&fdoExtension->FunctionSupportInfo->ChangeRequestCount);
                                }

                                IoInvalidateDeviceRelations(fdoExtension->LowerPdo, BusRelations);
                                retryInterval = 5;
                            }
                            break;
                        }

                        case SCSI_ADSENSE_OPERATOR_REQUEST: {
                            switch (addlSenseCodeQual) {

                                case SCSI_SENSEQ_MEDIUM_REMOVAL: {
                                    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,  "ClassInterpretSenseInfo: "
                                                "Ejection request received!\n"));
                                    ClassSendEjectionNotification(fdoExtension);
                                    break;
                                }

                                case SCSI_SENSEQ_WRITE_PROTECT_ENABLE: {
                                    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,  "ClassInterpretSenseInfo: "
                                                "Operator selected write permit?! "
                                                "(unsupported!)\n"));
                                    break;
                                }

                                case SCSI_SENSEQ_WRITE_PROTECT_DISABLE: {
                                    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,  "ClassInterpretSenseInfo: "
                                                "Operator selected write protect?! "
                                                "(unsupported!)\n"));
                                    break;
                                }
                            }
                        }

                        case SCSI_ADSENSE_FAILURE_PREDICTION_THRESHOLD_EXCEEDED: {

                            UCHAR wmiEventData[sizeof(ULONG)+sizeof(UCHAR)] = {0};

                            *((PULONG)wmiEventData) = sizeof(UCHAR);
                            wmiEventData[sizeof(ULONG)] = addlSenseCodeQual;

                            //
                            // Don't log another eventlog if we have already logged once
                            // NOTE: this should have been interlocked, but the structure
                            //       was publicly defined to use a BOOLEAN (char).  Since
                            //       media only reports these errors once per X minutes,
                            //       the potential race condition is nearly non-existant.
                            //       the worst case is duplicate log entries, so ignore.
                            //

                            logError = FALSE;
                            if (fdoExtension->FailurePredicted == 0) {
                                logError = TRUE;
                            }
                            fdoExtension->FailureReason = addlSenseCodeQual;
                            logStatus = IO_WRN_FAILURE_PREDICTED;

                            ClassNotifyFailurePredicted(fdoExtension,
                                                        (PUCHAR)wmiEventData,
                                                        sizeof(wmiEventData),
                                                        FALSE,   // do not log error
                                                        4,          // unique error value
                                                        SrbGetPathId(Srb),
                                                        SrbGetTargetId(Srb),
                                                        SrbGetLun(Srb));

                            fdoExtension->FailurePredicted = TRUE;

                            //
                            // Since this is a Unit Attention we need to make
                            // sure we retry this request.
                            //
                            retry = TRUE;

                            break;
                        }

                        default: {
                            TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,  "ClassInterpretSenseInfo: "
                                        "Unit attention\n"));
                            break;
                        }

                    } // end  switch (addlSenseCode)

                    if (TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA))
                    {

                        if ((ClassGetVpb(Fdo) != NULL) && (ClassGetVpb(Fdo)->Flags & VPB_MOUNTED))
                        {
                            //
                            // Set bit to indicate that media may have changed
                            // and volume needs verification.
                            //

                            SET_FLAG(Fdo->Flags, DO_VERIFY_VOLUME);

                            *Status = STATUS_VERIFY_REQUIRED;
                            retry = FALSE;
                        }
                        else {
                            *Status = STATUS_IO_DEVICE_ERROR;
                        }
                    }
                    else
                    {
                        *Status = STATUS_IO_DEVICE_ERROR;
                    }

                    break;

                } // end SCSI_SENSE_UNIT_ATTENTION

                case SCSI_SENSE_DATA_PROTECT: {

                    retry = FALSE;

                    if (addlSenseCode == SCSI_ADSENSE_WRITE_PROTECT)
                    {
                        switch (addlSenseCodeQual) {

                            case SCSI_SENSEQ_SPACE_ALLOC_FAILED_WRITE_PROTECT: {

                                TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
                                        "Device's (%p) resources are exhausted.\n",
                                        Fdo));

                                ClassQueueResourceExhaustionEventWorker(Fdo);

                                //
                                // This indicates that a thinly-provisioned device has
                                // hit a permanent resource exhaustion.  We need to
                                // return this status code so that patmgr can take the
                                // disk offline.
                                //
                                *Status = STATUS_DISK_RESOURCES_EXHAUSTED;
                                break;
                            }
                            default:
                            {
                                break;
                            }

                        } // end switch addlSenseCodeQual
                    }
                    else
                    {
                        if (IS_SCSIOP_WRITE(cdbOpcode)) {
                            TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
                                        "Media write protected\n"));
                            *Status = STATUS_MEDIA_WRITE_PROTECTED;
                        } else {
                            TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
                                        "Access denied\n"));
                            *Status = STATUS_ACCESS_DENIED;
                        }
                    }
                    break;
                } // end SCSI_SENSE_DATA_PROTECT

                case SCSI_SENSE_BLANK_CHECK: {
                    TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL,  "ClassInterpretSenseInfo: "
                                "Media blank check\n"));
                    retry = FALSE;
                    *Status = STATUS_NO_DATA_DETECTED;
                    break;
                } // end SCSI_SENSE_BLANK_CHECK

                case SCSI_SENSE_COPY_ABORTED: {

                    switch (addlSenseCode) {

                        case SCSI_ADSENSE_COPY_TARGET_DEVICE_ERROR: {

                            switch (addlSenseCodeQual) {

                                //
                                // 1. Target truncated the data transfer.
                                //
                                case SCSI_SENSEQ_DATA_UNDERRUN: {

                                    TracePrint((TRACE_LEVEL_WARNING,
                                                TRACE_FLAG_IOCTL,
                                                "ClassInterpretSenseInfo (%p): Data transfer was truncated (command %x)\n",
                                                Fdo,
                                                cdbOpcode));

                                    *Status = STATUS_SUCCESS;
                                    retry = FALSE;
                                    break;
                                }
                            }
                            break;
                        }
                    }
                    break;
                }

                case SCSI_SENSE_ABORTED_COMMAND: {
                    if (ClasspIsOffloadDataTransferCommand(cdb)) {

                        switch (addlSenseCode) {

                            case SCSI_ADSENSE_COPY_TARGET_DEVICE_ERROR: {

                                switch (addlSenseCodeQual) {

                                    //
                                    // 1. Target truncated the data transfer.
                                    //
                                    case SCSI_SENSEQ_DATA_UNDERRUN: {

                                        TracePrint((TRACE_LEVEL_WARNING,
                                                    TRACE_FLAG_IOCTL,
                                                    "ClassInterpretSenseInfo (%p): Target has truncated the data transfer (command %x)\n",
                                                    Fdo,
                                                    cdbOpcode));

                                        *Status = STATUS_SUCCESS;
                                        retry = FALSE;
                                        break;
                                    }
                                }
                                break;
                            }

                            case SCSI_ADSENSE_RESOURCE_FAILURE: {

                                switch (addlSenseCodeQual) {

                                    //
                                    // 1. Copy manager wasn't able to finish the operation because of insuffient resources
                                    //    (e.g. microsnapshot failure on read, no space on write, etc.)
                                    //
                                    case SCSI_SENSEQ_INSUFFICIENT_RESOURCES: {

                                        TracePrint((TRACE_LEVEL_ERROR,
                                                    TRACE_FLAG_IOCTL,
                                                    "ClassInterpretSenseInfo (%p): Target has insufficient resources (command %x)\n",
                                                    Fdo,
                                                    cdb->CDB6GENERIC.OperationCode));

                                        *Status = STATUS_INSUFFICIENT_RESOURCES;
                                        retry = FALSE;
                                        break;
                                    }
                                }
                                break;
                            }
                        }
                    } else {
                        TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,  "ClassInterpretSenseInfo: "
                                    "Command aborted\n"));
                        *Status = STATUS_IO_DEVICE_ERROR;
                        retryInterval = 1;
                    }
                    break;
                } // end SCSI_SENSE_ABORTED_COMMAND

                default: {
                    TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,  "ClassInterpretSenseInfo: "
                                "Unrecognized sense code\n"));
                    *Status = STATUS_IO_DEVICE_ERROR;
                    break;
                }

            } // end switch (senseKey)



            //
            // Try to determine bad sector information from sense data
            //

            if (((IS_SCSIOP_READWRITE(cdbOpcode))   ||
                (cdbOpcode == SCSIOP_VERIFY)        ||
                (cdbOpcode == SCSIOP_VERIFY16)) && cdb) {

                if (isInformationValid)
                {
                    readSector = 0;
                    badSector = information;

                    if (cdbOpcode == SCSIOP_READ16 || cdbOpcode == SCSIOP_WRITE16 || cdbOpcode == SCSIOP_VERIFY16) {
                        REVERSE_BYTES_QUAD(&readSector, &(cdb->AsByte[2]));
                    } else {
                        REVERSE_BYTES(&readSector, &(cdb->AsByte[2]));
                    }

                    if (cdbOpcode == SCSIOP_READ || cdbOpcode == SCSIOP_WRITE || cdbOpcode == SCSIOP_VERIFY) {
                        REVERSE_BYTES_SHORT(&index, &(cdb->CDB10.TransferBlocksMsb));
                    } else if (cdbOpcode == SCSIOP_READ6 ||  cdbOpcode == SCSIOP_WRITE6) {
                        index = cdb->CDB6READWRITE.TransferBlocks;
                    } else if(cdbOpcode == SCSIOP_READ12 || cdbOpcode == SCSIOP_WRITE12) {
                        REVERSE_BYTES(&index, &(cdb->CDB12.TransferLength));
                    } else {
                        REVERSE_BYTES(&index, &(cdb->CDB16.TransferLength));
                    }

                    //
                    // Make sure the bad sector is within the read sectors.
                    //

                    if (!(badSector >= readSector && badSector < readSector + index)) {
                        badSector = readSector;
                    }
                }
            }
        }

__ClassInterpretSenseInfo_ProcessingInvalidSenseBuffer:

        if (!validSense) {

            //
            // Request sense buffer not valid. No sense information
            // to pinpoint the error. Return general request fail.
            //

            TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,  "ClassInterpretSenseInfo: "
                        "Request sense info not valid. SrbStatus %2x\n",
                        SRB_STATUS(Srb->SrbStatus)));
            retry = TRUE;


            switch (SRB_STATUS(Srb->SrbStatus)) {
                case SRB_STATUS_ABORTED: {

                    //
                    // Update the error count for the device.
                    //

                    incrementErrorCount = TRUE;
                    *Status = STATUS_IO_TIMEOUT;
                    retryInterval = 1;
                    retry = TRUE;
                    break;
                }

                case SRB_STATUS_ERROR: {

                    *Status = STATUS_IO_DEVICE_ERROR;
                    if (SrbGetScsiStatus(Srb) == SCSISTAT_GOOD) {

                        //
                        // This is some strange return code.  Update the error
                        // count for the device.
                        //

                        incrementErrorCount = TRUE;

                    } else if (SrbGetScsiStatus(Srb) == SCSISTAT_BUSY) {

                        *Status = STATUS_DEVICE_NOT_READY;
                        logRetryableError = FALSE;
                    }

                    break;
                }

                case SRB_STATUS_INVALID_REQUEST: {
                    *Status = STATUS_INVALID_DEVICE_REQUEST;
                    retry = FALSE;
                    break;
                }

                case SRB_STATUS_INVALID_PATH_ID:
                case SRB_STATUS_NO_DEVICE:
                case SRB_STATUS_NO_HBA:
                case SRB_STATUS_INVALID_LUN:
                case SRB_STATUS_INVALID_TARGET_ID: {
                    *Status = STATUS_NO_SUCH_DEVICE;
                    retry = FALSE;
                    break;
                }

                case SRB_STATUS_SELECTION_TIMEOUT: {
                    logError = TRUE;
                    logStatus = IO_ERR_NOT_READY;
                    uniqueId = 260;
                    *Status = STATUS_DEVICE_NOT_CONNECTED;
                    retry = FALSE;
                    break;
                }

                case SRB_STATUS_TIMEOUT:
                case SRB_STATUS_COMMAND_TIMEOUT: {

                    //
                    // Update the error count for the device.
                    //
                    incrementErrorCount = TRUE;
                    *Status = STATUS_IO_TIMEOUT;
                    break;
                }

                case SRB_STATUS_PARITY_ERROR:
                case SRB_STATUS_UNEXPECTED_BUS_FREE:

                    //
                    // Update the error count for the device
                    // and fall through to below
                    //
                    incrementErrorCount = TRUE;

                case SRB_STATUS_BUS_RESET: {

                    *Status = STATUS_IO_DEVICE_ERROR;
                    logRetryableError = FALSE;
                    break;
                }

                case SRB_STATUS_DATA_OVERRUN: {

                    *Status = STATUS_DATA_OVERRUN;
                    retry = FALSE;

                    //
                    // For some commands, we allocate a buffer that may be
                    // larger than necessary.  In these cases, the SRB may be
                    // returned with SRB_STATUS_DATA_OVERRUN to indicate a
                    // buffer *underrun*.  However, the command was still
                    // successful so we ensure STATUS_SUCCESS is returned.
                    // We will also prevent these errors from causing noise in
                    // the error logs.
                    //
                    if ((cdbOpcode == SCSIOP_MODE_SENSE && SrbGetDataTransferLength(Srb) <= cdb->MODE_SENSE.AllocationLength) ||
                        (cdbOpcode == SCSIOP_INQUIRY && SrbGetDataTransferLength(Srb) <= cdb->CDB6INQUIRY.AllocationLength)) {
                        *Status = STATUS_SUCCESS;
                        logErrorInternal = FALSE;
                        logError = FALSE;
                    } else if (cdbOpcode == SCSIOP_MODE_SENSE10) {
                        USHORT allocationLength;
                        REVERSE_BYTES_SHORT(&(cdb->MODE_SENSE10.AllocationLength), &allocationLength);
                        if (SrbGetDataTransferLength(Srb) <= allocationLength) {
                            *Status = STATUS_SUCCESS;
                            logErrorInternal = FALSE;
                            logError = FALSE;
                        }
                    } else if (ClasspIsReceiveTokenInformation(cdb)) {
                        ULONG allocationLength;
                        REVERSE_BYTES(&(cdb->RECEIVE_TOKEN_INFORMATION.AllocationLength), &allocationLength);
                        if (SrbGetDataTransferLength(Srb) <= allocationLength) {
                            *Status = STATUS_SUCCESS;
                            logErrorInternal = FALSE;
                            logError = FALSE;
                        }
                    }

                    break;
                }

                case SRB_STATUS_PHASE_SEQUENCE_FAILURE: {

                    //
                    // Update the error count for the device.
                    //

                    incrementErrorCount = TRUE;
                    *Status = STATUS_IO_DEVICE_ERROR;

                    //
                    // If there was  phase sequence error then limit the number of
                    // retries.
                    //

                    if (RetryCount > 1 ) {
                        retry = FALSE;
                    }

                    break;
                }

                case SRB_STATUS_REQUEST_FLUSHED: {

                    //
                    // If the status needs verification bit is set.  Then set
                    // the status to need verification and no retry; otherwise,
                    // just retry the request.
                    //

                    if (TEST_FLAG(Fdo->Flags, DO_VERIFY_VOLUME)) {

                        *Status = STATUS_VERIFY_REQUIRED;
                        retry = FALSE;

                    } else {
                        *Status = STATUS_IO_DEVICE_ERROR;
                        logRetryableError = FALSE;
                    }

                    break;
                }


                default: {
                    logError = TRUE;
                    logStatus = IO_ERR_CONTROLLER_ERROR;
                    uniqueId = 259;
                    *Status = STATUS_IO_DEVICE_ERROR;
                    unhandledError = TRUE;
                    logRetryableError = FALSE;
                    break;
                }
            }


            //
            // NTRAID #183546 - if we support GESN subtype NOT_READY events, and
            // we know from a previous poll when the device will be ready (ETA)
            // we should delay the retry more appropriately than just guessing.
            //
            /*
            if (fdoExtension->MediaChangeDetectionInfo &&
                fdoExtension->MediaChangeDetectionInfo->Gesn.Supported &&
                TEST_FLAG(fdoExtension->MediaChangeDetectionInfo->Gesn.EventMask,
                          NOTIFICATION_DEVICE_BUSY_CLASS_MASK)
                ) {
                // check if Gesn.ReadyTime if greater than current tick count
                // if so, delay that long (from 1 to 30 seconds max?)
                // else, leave the guess of time alone.
            }
            */

        }

    }

    if (incrementErrorCount) {

        //
        // if any error count occurred, delay the retry of this io by
        // at least one second, if caller supports it.
        //

        if (retryInterval == 0) {
            retryInterval = 1;
        }
        ClasspPerfIncrementErrorCount(fdoExtension);
    }

    //
    // If there is a class specific error handler call it.
    //

    if (fdoExtension->CommonExtension.DevInfo->ClassError != NULL) {

        SCSI_REQUEST_BLOCK tempSrb = {0};
        PSCSI_REQUEST_BLOCK srbPtr = (PSCSI_REQUEST_BLOCK)Srb;

        //
        // If class driver does not support extended SRB and this is
        // an extended SRB, convert to legacy SRB and pass to class
        // driver.
        //
        if ((Srb->Function == SRB_FUNCTION_STORAGE_REQUEST_BLOCK) &&
            ((fdoExtension->CommonExtension.DriverExtension->SrbSupport &
              CLASS_SRB_STORAGE_REQUEST_BLOCK) == 0)) {
            ClasspConvertToScsiRequestBlock(&tempSrb, (PSTORAGE_REQUEST_BLOCK)Srb);
            srbPtr = &tempSrb;
        }

        fdoExtension->CommonExtension.DevInfo->ClassError(Fdo,
                                                          srbPtr,
                                                          Status,
                                                          &retry);
    }

    //
    // If the caller wants to know the suggested retry interval tell them.
    //

    if (ARGUMENT_PRESENT(RetryInterval)) {
        *RetryInterval = retryInterval;
    }

    //
    // The RESERVE(6) / RELEASE(6) commands are optional. So
    // if they aren't supported, try the 10-byte equivalents
    //

    cdb = SrbGetCdb(Srb);
    if (cdb) {
        cdbOpcode = cdb->CDB6GENERIC.OperationCode;
    }

    if ((cdbOpcode == SCSIOP_RESERVE_UNIT ||
         cdbOpcode == SCSIOP_RELEASE_UNIT) && cdb)
    {
        if (*Status == STATUS_INVALID_DEVICE_REQUEST)
        {
            SrbSetCdbLength(Srb, 10);
            cdb->CDB10.OperationCode = (cdb->CDB6GENERIC.OperationCode == SCSIOP_RESERVE_UNIT) ? SCSIOP_RESERVE_UNIT10 : SCSIOP_RELEASE_UNIT10;

            SET_FLAG(fdoExtension->PrivateFdoData->HackFlags, FDO_HACK_NO_RESERVE6);
            retry = TRUE;
        }
    }

#if DBG

    //
    // Ensure that for read/write requests, only return STATUS_DEVICE_BUSY if
    // reservation conflict.
    //
    if (IS_SCSIOP_READWRITE(cdbOpcode) && (*Status == STATUS_DEVICE_BUSY)) {
        NT_ASSERT(isReservationConflict == TRUE);
    }

#endif

    /*
     *  LOG the error:
     *      If logErrorInternal is set, log the error in our internal log.
     *      If logError is set, also log the error in the system log.
     */
    if (logErrorInternal || logError) {
        ULONG totalSize;
        ULONG senseBufferSize = 0;
        IO_ERROR_LOG_PACKET staticErrLogEntry = {0};
        CLASS_ERROR_LOG_DATA staticErrLogData = {0};
        SENSE_DATA convertedSenseBuffer = {0};
        UCHAR convertedSenseBufferLength = 0;
        BOOLEAN senseDataConverted = FALSE;

        //
        // Logic below assumes that IO_ERROR_LOG_PACKET + CLASS_ERROR_LOG_DATA
        // is less than ERROR_LOG_MAXIMUM_SIZE which is not true for extended SRB.
        // Given that classpnp currently does not use >16 byte CDB, we'll convert
        // an extended SRB to SCSI_REQUEST_BLOCK instead of changing this code.
        // More changes will need to be made when classpnp starts using >16 byte
        // CDBs.
        //

        //
        // Calculate the total size of the error log entry.
        // add to totalSize in the order that they are used.
        // the advantage to calculating all the sizes here is
        // that we don't have to do a bunch of extraneous checks
        // later on in this code path.
        //
        totalSize = sizeof(IO_ERROR_LOG_PACKET)  // required
                  + sizeof(CLASS_ERROR_LOG_DATA);// struct for ease

        //
        // also save any available extra sense data, up to the maximum errlog
        // packet size .  WMI should be used for real-time analysis.
        // the event log should only be used for post-mortem debugging.
        //
        if ((TEST_FLAG(Srb->SrbStatus, SRB_STATUS_AUTOSENSE_VALID)) && senseBuffer) {

            UCHAR validSenseBytes = 0;
            UCHAR senseInfoBufferLength = 0;

            senseInfoBufferLength = SrbGetSenseInfoBufferLength(Srb);

            //
            // If sense data is in Descriptor format, convert it to Fixed format
            // for the private log.
            //

            if (IsDescriptorSenseDataFormat(senseBuffer)) {

                convertedSenseBufferLength = sizeof(convertedSenseBuffer);

                senseDataConverted = ScsiConvertToFixedSenseFormat(senseBuffer,
                                                                   senseInfoBufferLength,
                                                                   (PVOID)&convertedSenseBuffer,
                                                                   convertedSenseBufferLength);
            }

            //
            // For System Log, copy the maximum amount of available sense data
            //

            if (ScsiGetTotalSenseByteCountIndicated(senseBuffer,
                                                    senseInfoBufferLength,
                                                    &validSenseBytes)) {

                //
                // If it is able to determine number of valid bytes,
                // copy the maximum amount of available
                // sense data that can be saved into the the errlog.
                //

                //
                // set to save the most sense buffer possible
                //

                senseBufferSize = max(validSenseBytes, sizeof(staticErrLogData.SenseData));
                senseBufferSize = min(senseBufferSize, senseInfoBufferLength);

            } else {
                //
                // it's smaller than required to read the total number of
                // valid bytes, so just use the SenseInfoBufferLength field.
                //
                senseBufferSize = senseInfoBufferLength;
            }

            /*
             *  Bump totalSize by the number of extra senseBuffer bytes
             *  (beyond the default sense buffer within CLASS_ERROR_LOG_DATA).
             *  Make sure to never allocate more than ERROR_LOG_MAXIMUM_SIZE.
             */
            if (senseBufferSize > sizeof(staticErrLogData.SenseData)){
                totalSize += senseBufferSize-sizeof(staticErrLogData.SenseData);
                if (totalSize > ERROR_LOG_MAXIMUM_SIZE){
                    senseBufferSize -= totalSize-ERROR_LOG_MAXIMUM_SIZE;
                    totalSize = ERROR_LOG_MAXIMUM_SIZE;
                }
            }
        }

        //
        // If we've used up all of our retry attempts, set the final status to
        // reflect the appropriate result.
        //
        // ISSUE: the test below should also check RetryCount to determine if we will actually retry,
        //            but there is no easy test because we'd have to consider the original retry count
        //            for the op; besides, InterpretTransferPacketError sometimes ignores the retry
        //            decision returned by this function.  So just ErrorRetried to be true in the majority case.
        //
        if (retry){
            staticErrLogEntry.FinalStatus = STATUS_SUCCESS;
            staticErrLogData.ErrorRetried = TRUE;
        } else {
            staticErrLogEntry.FinalStatus = *Status;
        }

        //
        // Don't log generic IO_WARNING_PAGING_FAILURE message if either the
        // I/O is retried, or it completed successfully.
        //
        if (logStatus == IO_WARNING_PAGING_FAILURE &&
            (retry || NT_SUCCESS(*Status)) ) {
            logError = FALSE;
        }

        if (TEST_FLAG(SrbGetSrbFlags(Srb), SRB_CLASS_FLAGS_PAGING)) {
            staticErrLogData.ErrorPaging = TRUE;
        }
        if (unhandledError) {
            staticErrLogData.ErrorUnhandled = TRUE;
        }

        //
        // Calculate the device offset if there is a geometry.
        //
        staticErrLogEntry.DeviceOffset.QuadPart = (LONGLONG)badSector;
        staticErrLogEntry.DeviceOffset.QuadPart *= (LONGLONG)fdoExtension->DiskGeometry.BytesPerSector;
        if (logStatus == -1){
            staticErrLogEntry.ErrorCode = STATUS_IO_DEVICE_ERROR;
        } else {
            staticErrLogEntry.ErrorCode = logStatus;
        }

        /*
         *  The dump data follows the IO_ERROR_LOG_PACKET
         */
        staticErrLogEntry.DumpDataSize = (USHORT)totalSize - sizeof(IO_ERROR_LOG_PACKET);

        staticErrLogEntry.SequenceNumber = 0;
        staticErrLogEntry.MajorFunctionCode = MajorFunctionCode;
        staticErrLogEntry.IoControlCode = IoDeviceCode;
        staticErrLogEntry.RetryCount = (UCHAR) RetryCount;
        staticErrLogEntry.UniqueErrorValue = uniqueId;

        KeQueryTickCount(&staticErrLogData.TickCount);
        staticErrLogData.PortNumber = (ULONG)-1;

        /*
         *  Save the entire contents of the SRB.
         */
        if (Srb->Function == SRB_FUNCTION_STORAGE_REQUEST_BLOCK) {
            ClasspConvertToScsiRequestBlock(&staticErrLogData.Srb, (PSTORAGE_REQUEST_BLOCK)Srb);
        } else {
            staticErrLogData.Srb = *(PSCSI_REQUEST_BLOCK)Srb;
        }

        /*
         *  For our private log, save just the default length of the SENSE_DATA.
         */

        if ((senseBufferSize != 0) && senseBuffer) {

            //
            // If sense buffer is in Fixed format, put it in the private log
            //
            // If sense buffer is in Descriptor format, put it in the private log if conversion to Fixed format
            // succeeded. Otherwise, do not put it in the private log.
            //
            // If sense buffer is in unknown format, the device or the driver probably does not populate
            // the first byte of sense data, we probably still want to log error in this case assuming
            // it's fixed format, so that its sense key, its additional sense code, and its additional sense code
            // qualifier would be shown in the debugger extension output. By doing so, it minimizes any potential
            // negative impacts to our ability to diagnose issue.
            //
            if (IsDescriptorSenseDataFormat(senseBuffer)) {
                if (senseDataConverted) {
                    RtlCopyMemory(&staticErrLogData.SenseData, &convertedSenseBuffer, min(convertedSenseBufferLength, sizeof(staticErrLogData.SenseData)));
                }
            } else {
                RtlCopyMemory(&staticErrLogData.SenseData, senseBuffer, min(senseBufferSize, sizeof(staticErrLogData.SenseData)));
            }
        }

        /*
         *  Save the error log in our context.
         *  We only save the default sense buffer length.
         */
        if (logErrorInternal) {
            KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
            fdoData->ErrorLogs[fdoData->ErrorLogNextIndex] = staticErrLogData;
            fdoData->ErrorLogNextIndex++;
            fdoData->ErrorLogNextIndex %= NUM_ERROR_LOG_ENTRIES;
            KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
        }

        /*
         *  Log an event if an IO is being retried for reasons that may indicate
         *  a transient/permanent problem with the I_T_L nexus. But log the event
         *  only once per retried IO.
         */
        if (!IS_SCSIOP_READWRITE(cdbOpcode) ||
            !retry ||
            (RetryCount != 0)) {

            logRetryableError = FALSE;
        }

        if (logRetryableError) {

            logError = TRUE;
        }

        /*
         *  If logError is set, also save this log in the system's error log.
         *  But make sure we don't log TUR failures over and over
         *  (e.g. if an external drive was switched off and we're still sending TUR's to it every second).
         */

        if (logError)
        {
            //
            // We do not want to log certain system events repetitively
            //

            cdb = SrbGetCdb(Srb);
            if (cdb) {
                switch (cdb->CDB10.OperationCode)
                {
                    case SCSIOP_TEST_UNIT_READY:
                    {
                        if (fdoData->LoggedTURFailureSinceLastIO)
                        {
                            logError = FALSE;
                        }
                        else
                        {
                            fdoData->LoggedTURFailureSinceLastIO = TRUE;
                        }

                        break;
                    }

                    case SCSIOP_SYNCHRONIZE_CACHE:
                    {
                        if (fdoData->LoggedSYNCFailure)
                        {
                            logError = FALSE;
                        }
                        else
                        {
                            fdoData->LoggedSYNCFailure = TRUE;
                        }

                        break;
                    }
                }
            }
        }

        if (logError){

            if (logRetryableError) {

                NT_ASSERT(IS_SCSIOP_READWRITE(cdbOpcode));

                //
                // A large Disk TimeOutValue (like 60 seconds) results in giving a command a
                // large window to complete in. However, if the target returns a retryable error
                // just prior to the command timing out, and if multiple retries kick in, it may
                // take a significantly long time for the request to complete back to the
                // application, leading to a user perception of a hung system. So log an event
                // for retried IO so that an admin can help explain the reason for this behavior.
                //
                ClasspQueueLogIOEventWithContextWorker(Fdo,
                                                       senseBufferSize,
                                                       senseBuffer,
                                                       SRB_STATUS(Srb->SrbStatus),
                                                       SrbGetScsiStatus(Srb),
                                                       (ULONG)IO_WARNING_IO_OPERATION_RETRIED,
                                                       cdbLength,
                                                       cdb,
                                                       NULL);

            } else {

                PIO_ERROR_LOG_PACKET errorLogEntry;
                PCLASS_ERROR_LOG_DATA errlogData;

                errorLogEntry = (PIO_ERROR_LOG_PACKET)IoAllocateErrorLogEntry(Fdo, (UCHAR)totalSize);
                if (errorLogEntry){
                    errlogData = (PCLASS_ERROR_LOG_DATA)errorLogEntry->DumpData;

                    *errorLogEntry = staticErrLogEntry;
                    *errlogData = staticErrLogData;

                    /*
                     *  For the system log, copy as much of the sense buffer as possible.
                     */
                    if ((senseBufferSize != 0) && senseBuffer) {
                        RtlCopyMemory(&errlogData->SenseData, senseBuffer, senseBufferSize);
                    }

                    /*
                     *  Write the error log packet to the system error logging thread.
                     *  It will be freed by the kernel.
                     */
                    IoWriteErrorLogEntry(errorLogEntry);
                }
            }
        }
    }

    return retry;

} // end ClassInterpretSenseInfo()


/*++////////////////////////////////////////////////////////////////////////////

ClassModeSense()

Routine Description:

    This routine sends a mode sense command to a target ID and returns
    when it is complete.

Arguments:

    Fdo - Supplies the functional device object associated with this request.

    ModeSenseBuffer - Supplies a buffer to store the sense data.

    Length - Supplies the length in bytes of the mode sense buffer.

    PageMode - Supplies the page or pages of mode sense data to be retrived.

Return Value:

    Length of the transferred data is returned.

--*/
ULONG
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassModeSense(
    _In_ PDEVICE_OBJECT Fdo,
    _In_reads_bytes_(Length) PCHAR ModeSenseBuffer,
    _In_ ULONG Length,
    _In_ UCHAR PageMode
    )
{
    PAGED_CODE();

    return ClasspModeSense(Fdo,
                           ModeSenseBuffer,
                           Length,
                           PageMode,
                           MODE_SENSE_CURRENT_VALUES);
}


ULONG
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassModeSenseEx(
    _In_ PDEVICE_OBJECT Fdo,
    _In_reads_bytes_(Length) PCHAR ModeSenseBuffer,
    _In_ ULONG Length,
    _In_ UCHAR PageMode,
    _In_ UCHAR PageControl
    )
{
    PAGED_CODE();
#if (NTDDI_VERSION >= NTDDI_WINBLUE)
    return ClasspModeSense(Fdo,
                           ModeSenseBuffer,
                           Length,
                           PageMode,
                           PageControl);
#else
    UNREFERENCED_PARAMETER(Fdo);
    UNREFERENCED_PARAMETER(ModeSenseBuffer);
    UNREFERENCED_PARAMETER(Length);
    UNREFERENCED_PARAMETER(PageMode);
    UNREFERENCED_PARAMETER(PageControl);
    return 0;
#endif
}

ULONG ClasspModeSense(
    _In_ PDEVICE_OBJECT Fdo,
    _In_reads_bytes_(Length) PCHAR ModeSenseBuffer,
    _In_ ULONG Length,
    _In_ UCHAR PageMode,
    _In_ UCHAR PageControl
    )
/*
Routine Description:

    This routine sends a mode sense command to a target ID and returns
    when it is complete.

Arguments:

    Fdo - Supplies the functional device object associated with this request.

    ModeSenseBuffer - Supplies a buffer to store the sense data.

    Length - Supplies the length in bytes of the mode sense buffer.

    PageMode - Supplies the page or pages of mode sense data to be retrived.

    PageControl - Supplies the page control value of the request, which is
        one of the following:
        MODE_SENSE_CURRENT_VALUES
        MODE_SENSE_CHANGEABLE_VALUES
        MODE_SENSE_DEFAULT_VAULES
        MODE_SENSE_SAVED_VALUES

Return Value:

    Length of the transferred data is returned.

--*/
{
    ULONG lengthTransferred = 0;
    PMDL senseBufferMdl;

    PAGED_CODE();

    senseBufferMdl = BuildDeviceInputMdl(ModeSenseBuffer, Length);
    if (senseBufferMdl){

        TRANSFER_PACKET *pkt = DequeueFreeTransferPacket(Fdo, TRUE);
        if (pkt){
            KEVENT event;
            IRP pseudoIrp = {0};

            /*
             *  Store the number of packets servicing the irp (one)
             *  inside the original IRP.  It will be used to counted down
             *  to zero when the packet completes.
             *  Initialize the original IRP's status to success.
             *  If the packet fails, we will set it to the error status.
             */
            pseudoIrp.Tail.Overlay.DriverContext[0] = LongToPtr(1);
            pseudoIrp.IoStatus.Status = STATUS_SUCCESS;
            pseudoIrp.IoStatus.Information = 0;
            pseudoIrp.MdlAddress = senseBufferMdl;

            /*
             *  Set this up as a SYNCHRONOUS transfer, submit it,
             *  and wait for the packet to complete.  The result
             *  status will be written to the original irp.
             */
            NT_ASSERT(Length <= 0x0ff);
            KeInitializeEvent(&event, SynchronizationEvent, FALSE);
            SetupModeSenseTransferPacket(pkt, &event, ModeSenseBuffer, (UCHAR)Length, PageMode, 0, &pseudoIrp, PageControl);
            SubmitTransferPacket(pkt);
            (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);

            if (NT_SUCCESS(pseudoIrp.IoStatus.Status)){
                lengthTransferred = (ULONG)pseudoIrp.IoStatus.Information;
            }
            else {
                /*
                 *  This request can sometimes fail legitimately
                 *  (e.g. when a SCSI device is attached but turned off)
                 *  so this is not necessarily a device/driver bug.
                 */
                TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClasspModeSense on Fdo %ph failed with status %xh.", Fdo, pseudoIrp.IoStatus.Status));
            }
        }

        FreeDeviceInputMdl(senseBufferMdl);
    }

    return lengthTransferred;
}

/*++////////////////////////////////////////////////////////////////////////////

ClassFindModePage()

Routine Description:

    This routine scans through the mode sense data and finds the requested
    mode sense page code.

Arguments:
    ModeSenseBuffer - Supplies a pointer to the mode sense data.

    Length - Indicates the length of valid data.

    PageMode - Supplies the page mode to be searched for.

    Use6Byte - Indicates whether 6 or 10 byte mode sense was used.

Return Value:

    A pointer to the the requested mode page.  If the mode page was not found
    then NULL is return.

--*/
PVOID
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassFindModePage(
    _In_reads_bytes_(Length) PCHAR ModeSenseBuffer,
    _In_ ULONG Length,
    _In_ UCHAR PageMode,
    _In_ BOOLEAN Use6Byte
    )
{
    PUCHAR limit;
    ULONG  parameterHeaderLength;
    PVOID result = NULL;

    limit = (PUCHAR)ModeSenseBuffer + Length;
    parameterHeaderLength = (Use6Byte) ? sizeof(MODE_PARAMETER_HEADER) : sizeof(MODE_PARAMETER_HEADER10);

    if (Length >= parameterHeaderLength) {

        PMODE_PARAMETER_HEADER10 modeParam10;
        ULONG blockDescriptorLength;

        /*
         *  Skip the mode select header and block descriptors.
         */
        if (Use6Byte){
            blockDescriptorLength = ((PMODE_PARAMETER_HEADER) ModeSenseBuffer)->BlockDescriptorLength;
        }
        else {
            modeParam10 = (PMODE_PARAMETER_HEADER10) ModeSenseBuffer;
            blockDescriptorLength = modeParam10->BlockDescriptorLength[1];
        }

        ModeSenseBuffer += parameterHeaderLength + blockDescriptorLength;

        //
        // ModeSenseBuffer now points at pages.  Walk the pages looking for the
        // requested page until the limit is reached.
        //

        while (ModeSenseBuffer +
               RTL_SIZEOF_THROUGH_FIELD(MODE_DISCONNECT_PAGE, PageLength) < (PCHAR)limit) {

            if (((PMODE_DISCONNECT_PAGE) ModeSenseBuffer)->PageCode == PageMode) {

                /*
                 * found the mode page.  make sure it's safe to touch it all
                 * before returning the pointer to caller
                 */

                if (ModeSenseBuffer + ((PMODE_DISCONNECT_PAGE)ModeSenseBuffer)->PageLength > (PCHAR)limit) {
                    /*
                     *  Return NULL since the page is not safe to access in full
                     */
                    result = NULL;
                }
                else {
                    result = ModeSenseBuffer;
                }
                break;
            }

            //
            // Advance to the next page which is 4-byte-aligned offset after this page.
            //
            ModeSenseBuffer +=
                ((PMODE_DISCONNECT_PAGE) ModeSenseBuffer)->PageLength +
                RTL_SIZEOF_THROUGH_FIELD(MODE_DISCONNECT_PAGE, PageLength);

        }
    }

    return result;
} // end ClassFindModePage()


NTSTATUS
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassModeSelect(
    _In_ PDEVICE_OBJECT Fdo,
    _In_reads_bytes_(Length) PCHAR ModeSelectBuffer,
    _In_ ULONG Length,
    _In_ BOOLEAN SavePages
    )
{
#if (NTDDI_VERSION >= NTDDI_WINBLUE)
    return ClasspModeSelect(Fdo,
                            ModeSelectBuffer,
                            Length,
                            SavePages);
#else
    UNREFERENCED_PARAMETER(Fdo);
    UNREFERENCED_PARAMETER(ModeSelectBuffer);
    UNREFERENCED_PARAMETER(Length);
    UNREFERENCED_PARAMETER(SavePages);
    return STATUS_NOT_SUPPORTED;
#endif
}

/*++
ClasspModeSelect()

Routine Description:

    This routine sends a mode select command to a target ID and returns
    when it is complete.

Arguments:

    Fdo - Supplies the functional device object associated with this request.

    ModeSelectBuffer - Supplies a buffer to the select data.

    Length - Supplies the length in bytes of the mode select buffer.

    SavePages - Specifies the value of the save pages (SP) bit in the mode
        select command.

Return Value:

    NTSTATUS code of the request.

--*/
NTSTATUS
ClasspModeSelect(
    _In_ PDEVICE_OBJECT Fdo,
    _In_reads_bytes_(Length) PCHAR ModeSelectBuffer,
    _In_ ULONG Length,
    _In_ BOOLEAN SavePages
    )
{

    PMDL senseBufferMdl;
    NTSTATUS status = STATUS_UNSUCCESSFUL;

    senseBufferMdl = BuildDeviceInputMdl(ModeSelectBuffer, Length);
    if (senseBufferMdl) {

        TRANSFER_PACKET *pkt = DequeueFreeTransferPacket(Fdo, TRUE);
        if (pkt){
            KEVENT event;
            IRP pseudoIrp = {0};

            /*
             *  Store the number of packets servicing the irp (one)
             *  inside the original IRP.  It will be used to counted down
             *  to zero when the packet completes.
             *  Initialize the original IRP's status to success.
             *  If the packet fails, we will set it to the error status.
             */
            pseudoIrp.Tail.Overlay.DriverContext[0] = LongToPtr(1);
            pseudoIrp.IoStatus.Status = STATUS_SUCCESS;
            pseudoIrp.IoStatus.Information = 0;
            pseudoIrp.MdlAddress = senseBufferMdl;

            /*
             *  Set this up as a SYNCHRONOUS transfer, submit it,
             *  and wait for the packet to complete.  The result
             *  status will be written to the original irp.
             */
            NT_ASSERT(Length <= 0x0ff);
            KeInitializeEvent(&event, SynchronizationEvent, FALSE);
            SetupModeSelectTransferPacket(pkt, &event, ModeSelectBuffer, (UCHAR)Length, SavePages, &pseudoIrp);
            SubmitTransferPacket(pkt);
            (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);

            if (!NT_SUCCESS(pseudoIrp.IoStatus.Status)){
                /*
                 *  This request can sometimes fail legitimately
                 *  (e.g. when a SCSI device is attached but turned off)
                 *  so this is not necessarily a device/driver bug.
                 */
                TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassModeSelect on Fdo %ph failed with status %xh.", Fdo, pseudoIrp.IoStatus.Status));
            }

            status = pseudoIrp.IoStatus.Status;
        }

        FreeDeviceInputMdl(senseBufferMdl);
    } else {
        status = STATUS_INSUFFICIENT_RESOURCES;
    }

    return status;
}

/*++////////////////////////////////////////////////////////////////////////////

ClassSendSrbAsynchronous()

Routine Description:

    This routine takes a partially built Srb and an Irp and sends it down to
    the port driver.

    This routine must be called with the remove lock held for the specified
    Irp.

Arguments:

    Fdo - Supplies the functional device object for the orginal request.

    Srb - Supplies a paritally build ScsiRequestBlock.  In particular, the
        CDB and the SRB timeout value must be filled in.  The SRB must not be
        allocated from zone.

    Irp - Supplies the requesting Irp.

    BufferAddress - Supplies a pointer to the buffer to be transfered.

    BufferLength - Supplies the length of data transfer.

    WriteToDevice - Indicates the data transfer will be from system memory to
        device.

Return Value:

    Returns STATUS_PENDING if the request is dispatched (since the
    completion routine may change the irp's status value we cannot simply
    return the value of the dispatch)

    or returns a status value to indicate why it failed.

--*/
_Success_(return == STATUS_PENDING)
NTSTATUS
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassSendSrbAsynchronous(
    _In_ PDEVICE_OBJECT Fdo,
    _Inout_ __on_failure(__drv_freesMem(Mem)) __drv_aliasesMem PSCSI_REQUEST_BLOCK _Srb,
    _In_ PIRP Irp,
    _In_reads_bytes_opt_(BufferLength) __drv_aliasesMem PVOID BufferAddress,
    _In_ ULONG BufferLength,
    _In_ BOOLEAN WriteToDevice
    )
{

    PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
    PIO_STACK_LOCATION irpStack;
    PSTORAGE_REQUEST_BLOCK_HEADER Srb = (PSTORAGE_REQUEST_BLOCK_HEADER)_Srb;

    ULONG savedFlags;

    if (Srb->Function != SRB_FUNCTION_STORAGE_REQUEST_BLOCK) {
        //
        // Write length to SRB.
        //

        Srb->Length = sizeof(SCSI_REQUEST_BLOCK);

        //
        // Set SCSI bus address.
        //

        Srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
    }

    //
    // This is a violation of the SCSI spec but it is required for
    // some targets.
    //

    // Srb->Cdb[1] |= deviceExtension->Lun << 5;

    //
    // Indicate auto request sense by specifying buffer and size.
    //

    SrbSetSenseInfoBuffer(Srb, fdoExtension->SenseData);
    SrbSetSenseInfoBufferLength(Srb, GET_FDO_EXTENSON_SENSE_DATA_LENGTH(fdoExtension));

    SrbSetDataBuffer(Srb, BufferAddress);

    //
    // Set the transfer length.
    //
    SrbSetDataTransferLength(Srb, BufferLength);

    //
    // Save the class driver specific flags away.
    //

    savedFlags = SrbGetSrbFlags(Srb) & SRB_FLAGS_CLASS_DRIVER_RESERVED;

    //
    // Allow the caller to specify that they do not wish
    // IoStartNextPacket() to be called in the completion routine.
    //

    SET_FLAG(savedFlags, (SrbGetSrbFlags(Srb) & SRB_FLAGS_DONT_START_NEXT_PACKET));

    //
    // If caller wants to this request to be tagged, save this fact.
    //

    if ( TEST_FLAG(SrbGetSrbFlags(Srb), SRB_FLAGS_QUEUE_ACTION_ENABLE) &&
         ( SRB_SIMPLE_TAG_REQUEST == SrbGetRequestAttribute(Srb) ||
           SRB_HEAD_OF_QUEUE_TAG_REQUEST == SrbGetRequestAttribute(Srb) ||
           SRB_ORDERED_QUEUE_TAG_REQUEST == SrbGetRequestAttribute(Srb) ) ) {

        SET_FLAG(savedFlags, SRB_FLAGS_QUEUE_ACTION_ENABLE);
        if (TEST_FLAG(SrbGetSrbFlags(Srb), SRB_FLAGS_NO_QUEUE_FREEZE)) {
            SET_FLAG(savedFlags, SRB_FLAGS_NO_QUEUE_FREEZE);
        }
    }

    if (BufferAddress != NULL) {

        //
        // Build Mdl if necessary.
        //

        if (Irp->MdlAddress == NULL) {

            PMDL mdl;

            mdl = IoAllocateMdl(BufferAddress,
                                BufferLength,
                                FALSE,
                                FALSE,
                                Irp);

            if ((mdl == NULL) || (Irp->MdlAddress == NULL)) {

                Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;

                //
                // ClassIoComplete() would have free'd the srb
                //

                if (PORT_ALLOCATED_SENSE_EX(fdoExtension, Srb)) {
                    FREE_PORT_ALLOCATED_SENSE_BUFFER_EX(fdoExtension, Srb);
                }
                ClassFreeOrReuseSrb(fdoExtension, (PSCSI_REQUEST_BLOCK)Srb);
                ClassReleaseRemoveLock(Fdo, Irp);
                ClassCompleteRequest(Fdo, Irp, IO_NO_INCREMENT);

                return STATUS_INSUFFICIENT_RESOURCES;
            }

            SET_FLAG(savedFlags, SRB_CLASS_FLAGS_FREE_MDL);

            MmBuildMdlForNonPagedPool(Irp->MdlAddress);

        } else {

            //
            // Make sure the buffer requested matches the MDL.
            //

            NT_ASSERT(BufferAddress == MmGetMdlVirtualAddress(Irp->MdlAddress));
        }

        //
        // Set read flag.
        //

        SrbAssignSrbFlags(Srb, WriteToDevice ? SRB_FLAGS_DATA_OUT : SRB_FLAGS_DATA_IN);

    } else {

        //
        // Clear flags.
        //

        SrbAssignSrbFlags(Srb, SRB_FLAGS_NO_DATA_TRANSFER);
    }

    //
    // Restore saved flags.
    //

    SrbSetSrbFlags(Srb, savedFlags);

    //
    // Disable synchronous transfer for these requests.
    //

    SrbSetSrbFlags(Srb, SRB_FLAGS_DISABLE_SYNCH_TRANSFER);

    //
    // Zero out status.
    //

    SrbSetScsiStatus(Srb, 0);
    Srb->SrbStatus = 0;

    SrbSetNextSrb(Srb, NULL);

    //
    // Save a few parameters in the current stack location.
    //

    irpStack = IoGetCurrentIrpStackLocation(Irp);

    //
    // Save retry count in current Irp stack.
    //

    irpStack->Parameters.Others.Argument4 = (PVOID)MAXIMUM_RETRIES;

    //
    // Set up IoCompletion routine address.
    //

    IoSetCompletionRoutine(Irp, ClassIoComplete, Srb, TRUE, TRUE, TRUE);

    //
    // Get next stack location and
    // set major function code.
    //

    irpStack = IoGetNextIrpStackLocation(Irp);

    irpStack->MajorFunction = IRP_MJ_SCSI;

    //
    // Save SRB address in next stack for port driver.
    //

    irpStack->Parameters.Scsi.Srb = (PSCSI_REQUEST_BLOCK)Srb;

    //
    // Set up Irp Address.
    //

    SrbSetOriginalRequest(Srb, Irp);

    //
    // Call the port driver to process the request.
    //

    IoMarkIrpPending(Irp);

    IoCallDriver(fdoExtension->CommonExtension.LowerDeviceObject, Irp);

    return STATUS_PENDING;

} // end ClassSendSrbAsynchronous()

/*++////////////////////////////////////////////////////////////////////////////

ClassDeviceControlDispatch()

Routine Description:

    The routine is the common class driver device control dispatch entry point.
    This routine is invokes the device-specific drivers DeviceControl routine,
    (which may call the Class driver's common DeviceControl routine).

Arguments:

    DeviceObject - Supplies a pointer to the device object for this request.

    Irp - Supplies the Irp making the request.

Return Value:

   Returns the status returned from the device-specific driver.

--*/
NTSTATUS
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassDeviceControlDispatch(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp
    )
{

    PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
    ULONG isRemoved;

    isRemoved = ClassAcquireRemoveLock(DeviceObject, Irp);
    _Analysis_assume_(isRemoved);
    if(isRemoved) {

        ClassReleaseRemoveLock(DeviceObject, Irp);

        Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST;
        ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
        return STATUS_DEVICE_DOES_NOT_EXIST;
    }

    //
    // Call the class specific driver DeviceControl routine.
    // If it doesn't handle it, it will call back into ClassDeviceControl.
    //

    NT_ASSERT(commonExtension->DevInfo->ClassDeviceControl);

    return commonExtension->DevInfo->ClassDeviceControl(DeviceObject,Irp);
} // end ClassDeviceControlDispatch()


/*++////////////////////////////////////////////////////////////////////////////

ClassDeviceControl()

Routine Description:

    The routine is the common class driver device control dispatch function.
    This routine is called by a class driver when it get an unrecognized
    device control request.  This routine will perform the correct action for
    common requests such as lock media.  If the device request is unknown it
    passed down to the next level.

    This routine must be called with the remove lock held for the specified
    irp.

Arguments:

    DeviceObject - Supplies a pointer to the device object for this request.

    Irp - Supplies the Irp making the request.

Return Value:

   Returns back a STATUS_PENDING or a completion status.

--*/
NTSTATUS
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassDeviceControl(
    _In_ PDEVICE_OBJECT DeviceObject,
    _Inout_ PIRP Irp
    )
{
    PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;

    PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
    PIO_STACK_LOCATION nextStack = NULL;

    ULONG controlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;

    PSCSI_REQUEST_BLOCK srb = NULL;
    PCDB cdb = NULL;

    NTSTATUS status;
    ULONG modifiedIoControlCode = 0;
    GUID activityId = {0};


    //
    // If this is a pass through I/O control, set the minor function code
    // and device address and pass it to the port driver.
    //

    if ( (controlCode == IOCTL_SCSI_PASS_THROUGH) ||
         (controlCode == IOCTL_SCSI_PASS_THROUGH_DIRECT) ||
         (controlCode == IOCTL_SCSI_PASS_THROUGH_EX) ||
         (controlCode == IOCTL_SCSI_PASS_THROUGH_DIRECT_EX) ) {



        //
        // Validiate the user buffer for SCSI pass through.
        // For pass through EX: as the handler will validate the size anyway,
        //                      do not apply the similar check and leave the work to the handler.
        //
        if ( (controlCode == IOCTL_SCSI_PASS_THROUGH) ||
             (controlCode == IOCTL_SCSI_PASS_THROUGH_DIRECT) ) {

        #if BUILD_WOW64_ENABLED && defined(_WIN64)

            if (IoIs32bitProcess(Irp)) {

                if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(SCSI_PASS_THROUGH32)){

                    Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;

                    ClassReleaseRemoveLock(DeviceObject, Irp);
                    ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);

                    status = STATUS_INVALID_PARAMETER;
                    goto SetStatusAndReturn;
                }
            }
            else

        #endif

            {
                if (irpStack->Parameters.DeviceIoControl.InputBufferLength <
                    sizeof(SCSI_PASS_THROUGH)) {

                    Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;

                    ClassReleaseRemoveLock(DeviceObject, Irp);
                    ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);

                    status = STATUS_INVALID_PARAMETER;
                    goto SetStatusAndReturn;
                }
            }
        }


        IoCopyCurrentIrpStackLocationToNext(Irp);

        nextStack = IoGetNextIrpStackLocation(Irp);
        nextStack->MinorFunction = 1;

        ClassReleaseRemoveLock(DeviceObject, Irp);

        status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
        goto SetStatusAndReturn;

    }

    Irp->IoStatus.Information = 0;


    switch (controlCode) {

        case IOCTL_MOUNTDEV_QUERY_UNIQUE_ID: {

            PMOUNTDEV_UNIQUE_ID uniqueId;

            if (!commonExtension->MountedDeviceInterfaceName.Buffer) {
                status = STATUS_INVALID_PARAMETER;
                break;
            }

            if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
                sizeof(MOUNTDEV_UNIQUE_ID)) {

                status = STATUS_BUFFER_TOO_SMALL;
                Irp->IoStatus.Information = sizeof(MOUNTDEV_UNIQUE_ID);
                break;
            }

            uniqueId = Irp->AssociatedIrp.SystemBuffer;
            RtlZeroMemory(uniqueId, sizeof(MOUNTDEV_UNIQUE_ID));
            uniqueId->UniqueIdLength =
                    commonExtension->MountedDeviceInterfaceName.Length;

            if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
                sizeof(USHORT) + uniqueId->UniqueIdLength) {

                status = STATUS_BUFFER_OVERFLOW;
                Irp->IoStatus.Information = sizeof(MOUNTDEV_UNIQUE_ID);
                break;
            }

            RtlCopyMemory(uniqueId->UniqueId,
                          commonExtension->MountedDeviceInterfaceName.Buffer,
                          uniqueId->UniqueIdLength);

            status = STATUS_SUCCESS;
            Irp->IoStatus.Information = sizeof(USHORT) +
                                        uniqueId->UniqueIdLength;
            break;
        }

        case IOCTL_MOUNTDEV_QUERY_DEVICE_NAME: {

            PMOUNTDEV_NAME name;

            NT_ASSERT(commonExtension->DeviceName.Buffer);

            if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
                sizeof(MOUNTDEV_NAME)) {

                status = STATUS_BUFFER_TOO_SMALL;
                Irp->IoStatus.Information = sizeof(MOUNTDEV_NAME);
                break;
            }

            name = Irp->AssociatedIrp.SystemBuffer;
            RtlZeroMemory(name, sizeof(MOUNTDEV_NAME));
            name->NameLength = commonExtension->DeviceName.Length;

            if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
                sizeof(USHORT) + name->NameLength) {

                status = STATUS_BUFFER_OVERFLOW;
                Irp->IoStatus.Information = sizeof(MOUNTDEV_NAME);
                break;
            }

            RtlCopyMemory(name->Name, commonExtension->DeviceName.Buffer,
                          name->NameLength);

            status = STATUS_SUCCESS;
            Irp->IoStatus.Information = sizeof(USHORT) + name->NameLength;
            break;
        }

        case IOCTL_MOUNTDEV_QUERY_SUGGESTED_LINK_NAME: {

            PMOUNTDEV_SUGGESTED_LINK_NAME suggestedName;
            WCHAR driveLetterNameBuffer[10] = {0};
            RTL_QUERY_REGISTRY_TABLE queryTable[2] = {0};
            PWSTR valueName;
            UNICODE_STRING driveLetterName;

            if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
                sizeof(MOUNTDEV_SUGGESTED_LINK_NAME)) {

                status = STATUS_BUFFER_TOO_SMALL;
                Irp->IoStatus.Information = sizeof(MOUNTDEV_SUGGESTED_LINK_NAME);
                break;
            }

            valueName = ExAllocatePoolWithTag(
                            PagedPool,
                            commonExtension->DeviceName.Length + sizeof(WCHAR),
                            '8CcS');

            if (!valueName) {
                status = STATUS_INSUFFICIENT_RESOURCES;
                break;
            }

            RtlCopyMemory(valueName, commonExtension->DeviceName.Buffer,
                          commonExtension->DeviceName.Length);
            valueName[commonExtension->DeviceName.Length/sizeof(WCHAR)] = 0;

            driveLetterName.Buffer = driveLetterNameBuffer;
            driveLetterName.MaximumLength = sizeof(driveLetterNameBuffer);
            driveLetterName.Length = 0;

            queryTable[0].Flags = RTL_QUERY_REGISTRY_REQUIRED |
                                  RTL_QUERY_REGISTRY_DIRECT |
                                  RTL_QUERY_REGISTRY_TYPECHECK;
            queryTable[0].Name = valueName;
            queryTable[0].EntryContext = &driveLetterName;
            queryTable->DefaultType = (REG_SZ << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_NONE;

            status = RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE,
                                            L"\\Registry\\Machine\\System\\DISK",
                                            queryTable, NULL, NULL);

            if (!NT_SUCCESS(status)) {
                FREE_POOL(valueName);
                break;
            }

            if (driveLetterName.Length == 4 &&
                driveLetterName.Buffer[0] == '%' &&
                driveLetterName.Buffer[1] == ':') {

                driveLetterName.Buffer[0] = 0xFF;

            } else if (driveLetterName.Length != 4 ||
                driveLetterName.Buffer[0] < FirstDriveLetter ||
                driveLetterName.Buffer[0] > LastDriveLetter ||
                driveLetterName.Buffer[1] != ':') {

                status = STATUS_NOT_FOUND;
                FREE_POOL(valueName);
                break;
            }

            suggestedName = Irp->AssociatedIrp.SystemBuffer;
            RtlZeroMemory(suggestedName, sizeof(MOUNTDEV_SUGGESTED_LINK_NAME));
            suggestedName->UseOnlyIfThereAreNoOtherLinks = TRUE;
            suggestedName->NameLength = 28;

            Irp->IoStatus.Information =
                    FIELD_OFFSET(MOUNTDEV_SUGGESTED_LINK_NAME, Name) + 28;

            if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
                Irp->IoStatus.Information) {

                Irp->IoStatus.Information =
                        sizeof(MOUNTDEV_SUGGESTED_LINK_NAME);
                status = STATUS_BUFFER_OVERFLOW;
                FREE_POOL(valueName);
                break;
            }

            RtlDeleteRegistryValue(RTL_REGISTRY_ABSOLUTE,
                                   L"\\Registry\\Machine\\System\\DISK",
                                   valueName);

            FREE_POOL(valueName);

            RtlCopyMemory(suggestedName->Name, L"\\DosDevices\\", 24);
            suggestedName->Name[12] = driveLetterName.Buffer[0];
            suggestedName->Name[13] = ':';

            //
            // NT_SUCCESS(status) based on RtlQueryRegistryValues
            //
            status = STATUS_SUCCESS;

            break;
        }

        default:
            status = STATUS_PENDING;
            break;
    }

    if (status != STATUS_PENDING) {
        ClassReleaseRemoveLock(DeviceObject, Irp);
        Irp->IoStatus.Status = status;


        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        return status;
    }

    if (commonExtension->IsFdo){

        PULONG_PTR function;
        PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)commonExtension;
        size_t sizeNeeded;

        //
        // Allocate a SCSI SRB for handling various IOCTLs.
        // NOTE - there is a case where an IOCTL is sent to classpnp before AdapterDescriptor
        // is initialized. In this case, default to legacy SRB.
        //
        if ((fdoExtension->AdapterDescriptor != NULL) &&
            (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK)) {
            sizeNeeded = CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE;
        } else {
            sizeNeeded = sizeof(SCSI_REQUEST_BLOCK);
        }

        srb = ExAllocatePoolWithTag(NonPagedPoolNx,
                             sizeNeeded +
                             (sizeof(ULONG_PTR) * 2),
                             '9CcS');

        if (srb == NULL) {

            Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
            ClassReleaseRemoveLock(DeviceObject, Irp);
            ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
            status = STATUS_INSUFFICIENT_RESOURCES;
            goto SetStatusAndReturn;
        }

        if ((fdoExtension->AdapterDescriptor != NULL) &&
            (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK)) {
            status = InitializeStorageRequestBlock((PSTORAGE_REQUEST_BLOCK)srb,
                                                   STORAGE_ADDRESS_TYPE_BTL8,
                                                   CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE,
                                                   1,
                                                   SrbExDataTypeScsiCdb16);
            if (NT_SUCCESS(status)) {
                ((PSTORAGE_REQUEST_BLOCK)srb)->SrbFunction = SRB_FUNCTION_EXECUTE_SCSI;
                function = (PULONG_PTR)((PCHAR)srb + sizeNeeded);
            } else {
                //
                // Should not occur.
                //
                NT_ASSERT(FALSE);
                goto SetStatusAndReturn;
            }
        } else {
            RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK));
            srb->Length = sizeof(SCSI_REQUEST_BLOCK);
            srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
            function = (PULONG_PTR) ((PSCSI_REQUEST_BLOCK) (srb + 1));
        }

        //
        // Save the function code and the device object in the memory after
        // the SRB.
        //

        *function = (ULONG_PTR) DeviceObject;
        function++;
        *function = (ULONG_PTR) controlCode;

    } else {
        srb = NULL;
    }

    //
    // Change the device type to storage for the switch statement, but only
    // if from a legacy device type
    //

    if (((controlCode & 0xffff0000) == (IOCTL_DISK_BASE  << 16)) ||
        ((controlCode & 0xffff0000) == (IOCTL_TAPE_BASE  << 16)) ||
        ((controlCode & 0xffff0000) == (IOCTL_CDROM_BASE << 16))
        ) {

        modifiedIoControlCode = (controlCode & ~0xffff0000);
        modifiedIoControlCode |= (IOCTL_STORAGE_BASE << 16);

    } else {

        modifiedIoControlCode = controlCode;

    }

    TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_GENERAL, "> ioctl %xh (%s)", modifiedIoControlCode, DBGGETIOCTLSTR(modifiedIoControlCode)));


    switch (modifiedIoControlCode) {

        case IOCTL_STORAGE_GET_HOTPLUG_INFO: {

            FREE_POOL(srb);

            if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
                sizeof(STORAGE_HOTPLUG_INFO)) {

                //
                // Indicate unsuccessful status and no data transferred.
                //

                Irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL;
                Irp->IoStatus.Information = sizeof(STORAGE_HOTPLUG_INFO);

                ClassReleaseRemoveLock(DeviceObject, Irp);
                ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
                status = STATUS_BUFFER_TOO_SMALL;

            } else if (!commonExtension->IsFdo) {


                //
                // Just forward this down and return
                //

                IoCopyCurrentIrpStackLocationToNext(Irp);

                ClassReleaseRemoveLock(DeviceObject, Irp);
                status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);

            } else {

                PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
                PSTORAGE_HOTPLUG_INFO info;

                fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)commonExtension;
                info = Irp->AssociatedIrp.SystemBuffer;

                *info = fdoExtension->PrivateFdoData->HotplugInfo;
                Irp->IoStatus.Status = STATUS_SUCCESS;
                Irp->IoStatus.Information = sizeof(STORAGE_HOTPLUG_INFO);
                ClassReleaseRemoveLock(DeviceObject, Irp);
                ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
                status = STATUS_SUCCESS;
            }
            break;
        }

        case IOCTL_STORAGE_SET_HOTPLUG_INFO: {

            FREE_POOL(srb);

            if (irpStack->Parameters.DeviceIoControl.InputBufferLength <
                sizeof(STORAGE_HOTPLUG_INFO)) {

                //
                // Indicate unsuccessful status and no data transferred.
                //

                Irp->IoStatus.Status = STATUS_INFO_LENGTH_MISMATCH;

                ClassReleaseRemoveLock(DeviceObject, Irp);
                ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
                status = STATUS_INFO_LENGTH_MISMATCH;
                goto SetStatusAndReturn;

            }

            if (!commonExtension->IsFdo) {


                //
                // Just forward this down and return
                //

                IoCopyCurrentIrpStackLocationToNext(Irp);

                ClassReleaseRemoveLock(DeviceObject, Irp);
                status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);

            } else {

                PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)commonExtension;
                PSTORAGE_HOTPLUG_INFO info = Irp->AssociatedIrp.SystemBuffer;

                status = STATUS_SUCCESS;

                if (info->Size != fdoExtension->PrivateFdoData->HotplugInfo.Size)
                {
                    status = STATUS_INVALID_PARAMETER_1;
                }

                if (info->MediaRemovable != fdoExtension->PrivateFdoData->HotplugInfo.MediaRemovable)
                {
                    status = STATUS_INVALID_PARAMETER_2;
                }

                if (info->MediaHotplug != fdoExtension->PrivateFdoData->HotplugInfo.MediaHotplug)
                {
                    status = STATUS_INVALID_PARAMETER_3;
                }

                if (NT_SUCCESS(status))
                {
                    if (info->WriteCacheEnableOverride != fdoExtension->PrivateFdoData->HotplugInfo.WriteCacheEnableOverride)
                    {
                        fdoExtension->PrivateFdoData->HotplugInfo.WriteCacheEnableOverride = info->WriteCacheEnableOverride;

                        //
                        // Store the user-defined override in the registry
                        //

                        ClassSetDeviceParameter(fdoExtension,
                                                CLASSP_REG_SUBKEY_NAME,
                                                CLASSP_REG_WRITE_CACHE_VALUE_NAME,
                                                info->WriteCacheEnableOverride);
                    }

                    fdoExtension->PrivateFdoData->HotplugInfo.DeviceHotplug = info->DeviceHotplug;

                    //
                    // Store the user-defined override in the registry
                    //

                    ClassSetDeviceParameter(fdoExtension,
                                            CLASSP_REG_SUBKEY_NAME,
                                            CLASSP_REG_REMOVAL_POLICY_VALUE_NAME,
                                            (info->DeviceHotplug) ? RemovalPolicyExpectSurpriseRemoval : RemovalPolicyExpectOrderlyRemoval);
                }

                Irp->IoStatus.Status = status;

                ClassReleaseRemoveLock(DeviceObject, Irp);
                ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
            }

            break;
        }

        case IOCTL_STORAGE_CHECK_VERIFY:
        case IOCTL_STORAGE_CHECK_VERIFY2: {

            PIRP irp2 = NULL;
            PIO_STACK_LOCATION newStack;

            PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = NULL;

            TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "DeviceIoControl: Check verify\n"));

            //
            // If a buffer for a media change count was provided, make sure it's
            // big enough to hold the result
            //

            if (irpStack->Parameters.DeviceIoControl.OutputBufferLength) {

                //
                // If the buffer is too small to hold the media change count
                // then return an error to the caller
                //

                if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
                    sizeof(ULONG)) {

                    TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, "DeviceIoControl: media count "
                                                                       "buffer too small\n"));

                    Irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL;
                    Irp->IoStatus.Information = sizeof(ULONG);

                    FREE_POOL(srb);

                    ClassReleaseRemoveLock(DeviceObject, Irp);
                    ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);

                    status = STATUS_BUFFER_TOO_SMALL;
                    goto SetStatusAndReturn;
                }
            }

            if (!commonExtension->IsFdo) {


                //
                // If this is a PDO then we should just forward the request down
                //
                NT_ASSERT(!srb);

                IoCopyCurrentIrpStackLocationToNext(Irp);

                ClassReleaseRemoveLock(DeviceObject, Irp);

                status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);

                goto SetStatusAndReturn;

            } else {

                fdoExtension = DeviceObject->DeviceExtension;

            }

            if (irpStack->Parameters.DeviceIoControl.OutputBufferLength) {

                //
                // The caller has provided a valid buffer.  Allocate an additional
                // irp and stick the CheckVerify completion routine on it.  We will
                // then send this down to the port driver instead of the irp the
                // caller sent in
                //

                TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "DeviceIoControl: Check verify wants "
                                                                       "media count\n"));

                //
                // Allocate a new irp to send the TestUnitReady to the port driver
                //

                irp2 = IoAllocateIrp((CCHAR) (DeviceObject->StackSize + 3), FALSE);

                if (irp2 == NULL) {
                    Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
                    Irp->IoStatus.Information = 0;
                    FREE_POOL(srb);
                    ClassReleaseRemoveLock(DeviceObject, Irp);
                    ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
                    status = STATUS_INSUFFICIENT_RESOURCES;
                    goto SetStatusAndReturn;

                    break;
                }

                //
                // Make sure to acquire the lock for the new irp.
                //

                ClassAcquireRemoveLock(DeviceObject, irp2);

                irp2->Tail.Overlay.Thread = Irp->Tail.Overlay.Thread;
                IoSetNextIrpStackLocation(irp2);

                //
                // Set the top stack location and shove the master Irp into the
                // top location
                //

                newStack = IoGetCurrentIrpStackLocation(irp2);
                newStack->Parameters.Others.Argument1 = Irp;
                newStack->DeviceObject = DeviceObject;

                //
                // Stick the check verify completion routine onto the stack
                // and prepare the irp for the port driver
                //

                IoSetCompletionRoutine(irp2,
                                       ClassCheckVerifyComplete,
                                       NULL,
                                       TRUE,
                                       TRUE,
                                       TRUE);

                IoSetNextIrpStackLocation(irp2);
                newStack = IoGetCurrentIrpStackLocation(irp2);
                newStack->DeviceObject = DeviceObject;
                newStack->MajorFunction = irpStack->MajorFunction;
                newStack->MinorFunction = irpStack->MinorFunction;
                newStack->Flags = irpStack->Flags;


                //
                // Mark the master irp as pending - whether the lower level
                // driver completes it immediately or not this should allow it
                // to go all the way back up.
                //

                IoMarkIrpPending(Irp);

                Irp = irp2;

            }

            //
            // Test Unit Ready
            //

            SrbSetCdbLength(srb, 6);
            cdb = SrbGetCdb(srb);
            cdb->CDB6GENERIC.OperationCode = SCSIOP_TEST_UNIT_READY;

            //
            // Set timeout value.
            //

            SrbSetTimeOutValue(srb, fdoExtension->TimeOutValue);

            //
            // If this was a CV2 then mark the request as low-priority so we don't
            // spin up the drive just to satisfy it.
            //

            if (controlCode == IOCTL_STORAGE_CHECK_VERIFY2) {
                SrbSetSrbFlags(srb, SRB_CLASS_FLAGS_LOW_PRIORITY);
            }

            //
            // Since this routine will always hand the request to the
            // port driver if there isn't a data transfer to be done
            // we don't have to worry about completing the request here
            // on an error
            //

            //
            // This routine uses a completion routine so we don't want to release
            // the remove lock until then.
            //

            status = ClassSendSrbAsynchronous(DeviceObject,
                                              srb,
                                              Irp,
                                              NULL,
                                              0,
                                              FALSE);

            break;
        }

        case IOCTL_STORAGE_MEDIA_REMOVAL:
        case IOCTL_STORAGE_EJECTION_CONTROL: {

            PPREVENT_MEDIA_REMOVAL mediaRemoval = Irp->AssociatedIrp.SystemBuffer;

            TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL,  "DiskIoControl: ejection control\n"));

            FREE_POOL(srb);

            if (irpStack->Parameters.DeviceIoControl.InputBufferLength <
                sizeof(PREVENT_MEDIA_REMOVAL)) {

                //
                // Indicate unsuccessful status and no data transferred.
                //

                Irp->IoStatus.Status = STATUS_INFO_LENGTH_MISMATCH;

                ClassReleaseRemoveLock(DeviceObject, Irp);
                ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
                status = STATUS_INFO_LENGTH_MISMATCH;
                goto SetStatusAndReturn;
            }

            if (!commonExtension->IsFdo) {


                //
                // Just forward this down and return
                //

                IoCopyCurrentIrpStackLocationToNext(Irp);

                ClassReleaseRemoveLock(DeviceObject, Irp);
                status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
            }
            else {

                // i don't believe this assertion is valid.  this is a request
                // from user-mode, so they could request this for any device
                // they want?  also, we handle it properly.
                // NT_ASSERT(TEST_FLAG(DeviceObject->Characteristics, FILE_REMOVABLE_MEDIA));
                status = ClasspEjectionControl(
                            DeviceObject,
                            Irp,
                            ((modifiedIoControlCode ==
                            IOCTL_STORAGE_EJECTION_CONTROL) ? SecureMediaLock :
                                                              SimpleMediaLock),
                            mediaRemoval->PreventMediaRemoval);

                Irp->IoStatus.Status = status;
                ClassReleaseRemoveLock(DeviceObject, Irp);
                ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
            }

            break;
        }

        case IOCTL_STORAGE_MCN_CONTROL: {

            TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL,  "DiskIoControl: MCN control\n"));

            if (irpStack->Parameters.DeviceIoControl.InputBufferLength <
                sizeof(PREVENT_MEDIA_REMOVAL)) {

                //
                // Indicate unsuccessful status and no data transferred.
                //

                Irp->IoStatus.Status = STATUS_INFO_LENGTH_MISMATCH;
                Irp->IoStatus.Information = 0;

                FREE_POOL(srb);

                ClassReleaseRemoveLock(DeviceObject, Irp);
                ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
                status = STATUS_INFO_LENGTH_MISMATCH;
                goto SetStatusAndReturn;
            }

            if (!commonExtension->IsFdo) {


                //
                // Just forward this down and return
                //

                FREE_POOL(srb);

                IoCopyCurrentIrpStackLocationToNext(Irp);

                ClassReleaseRemoveLock(DeviceObject, Irp);
                status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);

            } else {

                //
                // Call to the FDO - handle the ejection control.
                //

                status = ClasspMcnControl(DeviceObject->DeviceExtension,
                                          Irp,
                                          srb);
            }
            goto SetStatusAndReturn;
        }

        case IOCTL_STORAGE_RESERVE:
        case IOCTL_STORAGE_RELEASE: {

            //
            // Reserve logical unit.
            //

            PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = NULL;

            if (!commonExtension->IsFdo) {


                IoCopyCurrentIrpStackLocationToNext(Irp);

                ClassReleaseRemoveLock(DeviceObject, Irp);
                status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
                goto SetStatusAndReturn;

            } else {
                fdoExtension = DeviceObject->DeviceExtension;
            }

            if (TEST_FLAG(fdoExtension->PrivateFdoData->HackFlags, FDO_HACK_NO_RESERVE6))
            {
                SrbSetCdbLength(srb, 10);
                cdb = SrbGetCdb(srb);
                cdb->CDB10.OperationCode = (modifiedIoControlCode == IOCTL_STORAGE_RESERVE) ? SCSIOP_RESERVE_UNIT10 : SCSIOP_RELEASE_UNIT10;
            }
            else
            {
                SrbSetCdbLength(srb, 6);
                cdb = SrbGetCdb(srb);
                cdb->CDB6GENERIC.OperationCode = (modifiedIoControlCode == IOCTL_STORAGE_RESERVE) ? SCSIOP_RESERVE_UNIT : SCSIOP_RELEASE_UNIT;
            }

            //
            // Set timeout value.
            //

            SrbSetTimeOutValue(srb, fdoExtension->TimeOutValue);

            //
            // Send reserves as tagged requests.
            //

            if ( IOCTL_STORAGE_RESERVE == modifiedIoControlCode ) {
                SrbSetSrbFlags(srb, SRB_FLAGS_QUEUE_ACTION_ENABLE);
                SrbSetRequestAttribute(srb, SRB_SIMPLE_TAG_REQUEST);
            }

            status = ClassSendSrbAsynchronous(DeviceObject,
                                              srb,
                                              Irp,
                                              NULL,
                                              0,
                                              FALSE);

            break;
        }

        case IOCTL_STORAGE_PERSISTENT_RESERVE_IN:
        case IOCTL_STORAGE_PERSISTENT_RESERVE_OUT: {

            if (!commonExtension->IsFdo) {

                IoCopyCurrentIrpStackLocationToNext(Irp);

                ClassReleaseRemoveLock(DeviceObject, Irp);
                status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
                goto SetStatusAndReturn;
            }

            //
            // Process Persistent Reserve
            //

            status = ClasspPersistentReserve(DeviceObject, Irp, srb);

            break;

        }

        case IOCTL_STORAGE_EJECT_MEDIA:
        case IOCTL_STORAGE_LOAD_MEDIA:
        case IOCTL_STORAGE_LOAD_MEDIA2:{

            //
            // Eject media.
            //

            PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = NULL;

            if (!commonExtension->IsFdo) {


                IoCopyCurrentIrpStackLocationToNext(Irp);

                ClassReleaseRemoveLock(DeviceObject, Irp);

                status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
                goto SetStatusAndReturn;
            } else {
                fdoExtension = DeviceObject->DeviceExtension;
            }

            if (commonExtension->PagingPathCount != 0) {

                TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL,  "ClassDeviceControl: call to eject paging device - "
                                                                    "failure\n"));

                status = STATUS_FILES_OPEN;
                Irp->IoStatus.Status = status;

                Irp->IoStatus.Information = 0;

                FREE_POOL(srb);

                ClassReleaseRemoveLock(DeviceObject, Irp);
                ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
                goto SetStatusAndReturn;
            }

            //
            // Synchronize with ejection control and ejection cleanup code as
            // well as other eject/load requests.
            //

            KeEnterCriticalRegion();
            (VOID)KeWaitForSingleObject(&(fdoExtension->EjectSynchronizationEvent),
                                        UserRequest,
                                        KernelMode,
                                        FALSE,
                                        NULL);

            if (fdoExtension->ProtectedLockCount != 0) {

                TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL,  "ClassDeviceControl: call to eject protected locked "
                                                                    "device - failure\n"));

                status = STATUS_DEVICE_BUSY;
                Irp->IoStatus.Status = status;
                Irp->IoStatus.Information = 0;

                FREE_POOL(srb);

                ClassReleaseRemoveLock(DeviceObject, Irp);
                ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);

                KeSetEvent(&fdoExtension->EjectSynchronizationEvent,
                           IO_NO_INCREMENT,
                           FALSE);
                KeLeaveCriticalRegion();

                goto SetStatusAndReturn;
            }

            SrbSetCdbLength(srb, 6);
            cdb = SrbGetCdb(srb);

            cdb->START_STOP.OperationCode = SCSIOP_START_STOP_UNIT;
            cdb->START_STOP.LoadEject = 1;

            if (modifiedIoControlCode == IOCTL_STORAGE_EJECT_MEDIA) {
                cdb->START_STOP.Start = 0;
            } else {
                cdb->START_STOP.Start = 1;
            }

            //
            // Set timeout value.
            //

            SrbSetTimeOutValue(srb, fdoExtension->TimeOutValue);
            status = ClassSendSrbAsynchronous(DeviceObject,
                                                  srb,
                                                  Irp,
                                                  NULL,
                                                  0,
                                                  FALSE);

            KeSetEvent(&fdoExtension->EjectSynchronizationEvent, IO_NO_INCREMENT, FALSE);
            KeLeaveCriticalRegion();

            break;
        }

        case IOCTL_STORAGE_FIND_NEW_DEVICES: {

            FREE_POOL(srb);

            if (commonExtension->IsFdo) {

                IoInvalidateDeviceRelations(
                    ((PFUNCTIONAL_DEVICE_EXTENSION) commonExtension)->LowerPdo,
                    BusRelations);

                status = STATUS_SUCCESS;
                Irp->IoStatus.Status = status;

                ClassReleaseRemoveLock(DeviceObject, Irp);
                ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
            }
            else {


                IoCopyCurrentIrpStackLocationToNext(Irp);

                ClassReleaseRemoveLock(DeviceObject, Irp);
                status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
            }
            break;
        }

        case IOCTL_STORAGE_GET_DEVICE_NUMBER: {

            FREE_POOL(srb);

            if (irpStack->Parameters.DeviceIoControl.OutputBufferLength >=
                sizeof(STORAGE_DEVICE_NUMBER)) {

                PSTORAGE_DEVICE_NUMBER deviceNumber =
                    Irp->AssociatedIrp.SystemBuffer;
                PFUNCTIONAL_DEVICE_EXTENSION fdoExtension =
                    commonExtension->PartitionZeroExtension;

                deviceNumber->DeviceType = fdoExtension->CommonExtension.DeviceObject->DeviceType;
                deviceNumber->DeviceNumber = fdoExtension->DeviceNumber;
                deviceNumber->PartitionNumber = commonExtension->PartitionNumber;

                status = STATUS_SUCCESS;
                Irp->IoStatus.Information = sizeof(STORAGE_DEVICE_NUMBER);

            } else {
                status = STATUS_BUFFER_TOO_SMALL;
                Irp->IoStatus.Information = sizeof(STORAGE_DEVICE_NUMBER);
            }

            Irp->IoStatus.Status = status;
            ClassReleaseRemoveLock(DeviceObject, Irp);
            ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);

            break;
        }


        case IOCTL_STORAGE_READ_CAPACITY: {

            FREE_POOL(srb);

            if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
                sizeof(STORAGE_READ_CAPACITY)) {

                //
                // Indicate unsuccessful status and no data transferred.
                //

                Irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL;
                Irp->IoStatus.Information = sizeof(STORAGE_READ_CAPACITY);

                ClassReleaseRemoveLock(DeviceObject, Irp);
                ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
                status = STATUS_BUFFER_TOO_SMALL;
                break;
            }

            if (!commonExtension->IsFdo) {


                //
                // Just forward this down and return
                //

                IoCopyCurrentIrpStackLocationToNext(Irp);

                ClassReleaseRemoveLock(DeviceObject, Irp);
                status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
            }
            else {

                PFUNCTIONAL_DEVICE_EXTENSION fdoExt = DeviceObject->DeviceExtension;
                PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
                PSTORAGE_READ_CAPACITY readCapacity = Irp->AssociatedIrp.SystemBuffer;
                LARGE_INTEGER diskLength;

                status = ClassReadDriveCapacity(DeviceObject);
                if (NT_SUCCESS(status) && fdoData->IsCachedDriveCapDataValid) {

                    readCapacity->Version = sizeof(STORAGE_READ_CAPACITY);
                    readCapacity->Size = sizeof(STORAGE_READ_CAPACITY);

                    REVERSE_BYTES(&readCapacity->BlockLength,
                                        &fdoData->LastKnownDriveCapacityData.BytesPerBlock);
                    REVERSE_BYTES_QUAD(&readCapacity->NumberOfBlocks,
                                       &fdoData->LastKnownDriveCapacityData.LogicalBlockAddress);
                    readCapacity->NumberOfBlocks.QuadPart++;

                    readCapacity->DiskLength = fdoExt->CommonExtension.PartitionLength;

                    //
                    // Make sure the lengths are equal.
                    // Remove this after testing.
                    //
                    diskLength.QuadPart = readCapacity->NumberOfBlocks.QuadPart *
                                            readCapacity->BlockLength;

                    Irp->IoStatus.Status = STATUS_SUCCESS;
                    Irp->IoStatus.Information = sizeof(STORAGE_READ_CAPACITY);

                } else {
                    //
                    // Read capacity request failed.
                    //
                    TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL,  "ClassDeviceControl: ClassReadDriveCapacity failed: 0x%X IsCachedDriveCapDataValid: %d\n",
                                   status, fdoData->IsCachedDriveCapDataValid));
                    Irp->IoStatus.Status = status;
                    Irp->IoStatus.Information = 0;
                }
                ClassReleaseRemoveLock(DeviceObject, Irp);
                ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
            }

            break;
        }

        case IOCTL_STORAGE_QUERY_PROPERTY: {

            PSTORAGE_PROPERTY_QUERY query = Irp->AssociatedIrp.SystemBuffer;

            if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(STORAGE_PROPERTY_QUERY)) {

                status = STATUS_INFO_LENGTH_MISMATCH;
                Irp->IoStatus.Status = status;
                ClassReleaseRemoveLock(DeviceObject, Irp);
                ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
                FREE_POOL(srb);
                break;
            }

            if (!commonExtension->IsFdo) {


                IoCopyCurrentIrpStackLocationToNext(Irp);

                ClassReleaseRemoveLock(DeviceObject, Irp);
                status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
                FREE_POOL(srb);
                break;
            }

            //
            // Determine PropertyId type and either call appropriate routine
            // or pass request to lower drivers.
            //

            switch ( query->PropertyId ) {

                case StorageDeviceUniqueIdProperty: {

                    status = ClasspDuidQueryProperty(DeviceObject, Irp);
                    break;
                }

                case StorageDeviceWriteCacheProperty: {

                    status = ClasspWriteCacheProperty(DeviceObject, Irp, srb);
                    break;
                }

                // these propertyId has been implemented in some port driver and filter drivers.
                // to keep the backwards compatibility, classpnp will send the request down if it's supported by lower layer.
                // otherwise, classpnp sends SCSI command and then interprets the result.
                case StorageAccessAlignmentProperty: {

                    status = ClasspAccessAlignmentProperty(DeviceObject, Irp, srb);
                    break;
                }

                case StorageDeviceSeekPenaltyProperty: {

                    status = ClasspDeviceSeekPenaltyProperty(DeviceObject, Irp, srb);
                    break;
                }

                case StorageDeviceTrimProperty: {

                    status = ClasspDeviceTrimProperty(DeviceObject, Irp, srb);
                    break;
                }

                case StorageDeviceLBProvisioningProperty: {

                    status = ClasspDeviceLBProvisioningProperty(DeviceObject, Irp, srb);
                    break;
                }

                case StorageDeviceCopyOffloadProperty: {

                    status = ClasspDeviceCopyOffloadProperty(DeviceObject, Irp, srb);
                    break;
                }

                case StorageDeviceMediumProductType: {

                    status = ClasspDeviceMediaTypeProperty(DeviceObject, Irp, srb);
                    break;
                }

                default: {

                    //
                    // Copy the Irp stack parameters to the next stack location.
                    //

                    IoCopyCurrentIrpStackLocationToNext(Irp);

                    ClassReleaseRemoveLock(DeviceObject, Irp);
                    status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
                    break;
                }
            }   // end switch

            FREE_POOL(srb);
            break;
        }

        case IOCTL_STORAGE_CHECK_PRIORITY_HINT_SUPPORT: {

            FREE_POOL(srb);

            if (!commonExtension->IsFdo) {


                IoCopyCurrentIrpStackLocationToNext(Irp);

                ClassReleaseRemoveLock(DeviceObject, Irp);
                status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
                break;
            }

            //
            // Process priority hit request
            //

            status = ClasspPriorityHint(DeviceObject, Irp);
            break;
        }

        case IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES: {

            PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes = Irp->AssociatedIrp.SystemBuffer;

            if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES)) {

                status = STATUS_INVALID_PARAMETER;
                Irp->IoStatus.Status = status;
                ClassReleaseRemoveLock(DeviceObject, Irp);
                ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
                FREE_POOL(srb);
                break;
            }

            if (irpStack->Parameters.DeviceIoControl.InputBufferLength <
                (sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES) + dsmAttributes->ParameterBlockLength + dsmAttributes->DataSetRangesLength)) {

                status = STATUS_INVALID_PARAMETER;
                Irp->IoStatus.Status = status;
                ClassReleaseRemoveLock(DeviceObject, Irp);
                ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
                FREE_POOL(srb);
                break;
            }

            if (!commonExtension->IsFdo) {


                IoCopyCurrentIrpStackLocationToNext(Irp);

                ClassReleaseRemoveLock(DeviceObject, Irp);
                status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
                FREE_POOL(srb);
                break;
            }

            switch(dsmAttributes->Action) {

                // only process Trim action in class layer if possible.
                case DeviceDsmAction_Trim: {
                    status = ClasspDeviceTrimProcess(DeviceObject, Irp, &activityId, srb);
                    break;
                }

                case DeviceDsmAction_OffloadRead: {
                    status = ClassDeviceProcessOffloadRead(DeviceObject, Irp, srb);
                    break;
                }

                case DeviceDsmAction_OffloadWrite: {
                    status = ClassDeviceProcessOffloadWrite(DeviceObject, Irp, srb);
                    break;
                }

                case DeviceDsmAction_Allocation: {
                    status = ClasspDeviceGetLBAStatus(DeviceObject, Irp, srb);
                    break;
                }


                default: {


                    //
                    // Copy the Irp stack parameters to the next stack location.
                    //

                    IoCopyCurrentIrpStackLocationToNext(Irp);

                    ClassReleaseRemoveLock(DeviceObject, Irp);
                    status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
                    break;
                }
            }   // end switch

            FREE_POOL(srb);
            break;
        }

        case IOCTL_STORAGE_GET_LB_PROVISIONING_MAP_RESOURCES: {

            if (commonExtension->IsFdo) {

                status = ClassDeviceGetLBProvisioningResources(DeviceObject, Irp, srb);

            } else {

                IoCopyCurrentIrpStackLocationToNext(Irp);

                ClassReleaseRemoveLock(DeviceObject, Irp);
                status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
            }

            FREE_POOL(srb);

            break;
        }

        case IOCTL_STORAGE_EVENT_NOTIFICATION: {

            FREE_POOL(srb);

            status = ClasspStorageEventNotification(DeviceObject, Irp);
            break;
        }

#if (NTDDI_VERSION >= NTDDI_WINTRHESHOLD)
        case IOCTL_STORAGE_FIRMWARE_GET_INFO: {
            FREE_POOL(srb);

            status = ClassDeviceHwFirmwareGetInfoProcess(DeviceObject, Irp);
            break;
        }

        case IOCTL_STORAGE_FIRMWARE_DOWNLOAD: {
            if (!commonExtension->IsFdo) {

                IoCopyCurrentIrpStackLocationToNext(Irp);

                ClassReleaseRemoveLock(DeviceObject, Irp);
                status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
                goto SetStatusAndReturn;
            }

            status = ClassDeviceHwFirmwareDownloadProcess(DeviceObject, Irp, srb);
            break;
        }

        case IOCTL_STORAGE_FIRMWARE_ACTIVATE: {
            if (!commonExtension->IsFdo) {

                IoCopyCurrentIrpStackLocationToNext(Irp);

                ClassReleaseRemoveLock(DeviceObject, Irp);
                status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
                goto SetStatusAndReturn;
            }

            status = ClassDeviceHwFirmwareActivateProcess(DeviceObject, Irp, srb);
            break;
        }
#endif


        default: {

            TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_IOCTL, "IoDeviceControl: Unsupported device IOCTL %x for %p\n",
                        controlCode, DeviceObject));


            //
            // Pass the device control to the next driver.
            //

            FREE_POOL(srb);

            //
            // Copy the Irp stack parameters to the next stack location.
            //

            IoCopyCurrentIrpStackLocationToNext(Irp);

            ClassReleaseRemoveLock(DeviceObject, Irp);
            status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
            break;
        }

    } // end switch( ...

SetStatusAndReturn:

    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "< ioctl %xh (%s): status %xh.", modifiedIoControlCode, DBGGETIOCTLSTR(modifiedIoControlCode), status));

    return status;
} // end ClassDeviceControl()

/*++////////////////////////////////////////////////////////////////////////////

ClassShutdownFlush()

Routine Description:

    This routine is called for a shutdown and flush IRPs.  These are sent by the
    system before it actually shuts down or when the file system does a flush.
    If it exists, the device-specific driver's routine will be invoked. If there
    wasn't one specified, the Irp will be completed with an Invalid device request.

Arguments:

    DriverObject - Pointer to device object to being shutdown by system.

    Irp - IRP involved.

Return Value:

    NT Status

--*/
NTSTATUS
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassShutdownFlush(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
{
    PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;

    ULONG isRemoved;

    isRemoved = ClassAcquireRemoveLock(DeviceObject, Irp);
    _Analysis_assume_(isRemoved);
    if(isRemoved) {

        ClassReleaseRemoveLock(DeviceObject, Irp);

        Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST;

        ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);

        return STATUS_DEVICE_DOES_NOT_EXIST;
    }

    if (commonExtension->DevInfo->ClassShutdownFlush) {

        //
        // Call the device-specific driver's routine.
        //

        return commonExtension->DevInfo->ClassShutdownFlush(DeviceObject, Irp);
    }

    //
    // Device-specific driver doesn't support this.
    //

    Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;

    ClassReleaseRemoveLock(DeviceObject, Irp);
    ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);

    return STATUS_INVALID_DEVICE_REQUEST;
} // end ClassShutdownFlush()

/*++////////////////////////////////////////////////////////////////////////////

ClasspIsPortable()

Routine Description:

    This routine is called during start device to determine whether the PDO
    for a stack reports itself as portable.

Arguments:

    FdoExtension - Pointer to FDO whose PDO we check for portability.

    IsPortable - Boolean pointer in which to store result.

Return Value:

    NT Status

--*/
NTSTATUS
ClasspIsPortable(
    _In_ PFUNCTIONAL_DEVICE_EXTENSION   FdoExtension,
    _Out_ PBOOLEAN                      IsPortable
    )
{
    DEVPROP_BOOLEAN            isInternal = DEVPROP_FALSE;
    BOOLEAN                    isPortable = FALSE;
    ULONG                      size       = 0;
    NTSTATUS                   status     = STATUS_SUCCESS;
    DEVPROPTYPE                type       = DEVPROP_TYPE_EMPTY;

    PAGED_CODE();

    *IsPortable = FALSE;

    //
    // Check to see if the underlying device
    // object is in local machine container
    //

    status = IoGetDevicePropertyData(FdoExtension->LowerPdo,
                                     &DEVPKEY_Device_InLocalMachineContainer,
                                     0,
                                     0,
                                     sizeof(isInternal),
                                     &isInternal,
                                     &size,
                                     &type);

    if (!NT_SUCCESS(status)) {
        goto cleanup;
    }

    NT_ASSERT(size == sizeof(isInternal));
    NT_ASSERT(type == DEVPROP_TYPE_BOOLEAN);

    //
    // Volume is hot-pluggable if the disk pdo
    // container id differs from that of root device
    //

    if (isInternal == DEVPROP_TRUE) {
        goto cleanup;
    }

    isPortable = TRUE;

    //
    // Examine the  bus type to  ensure
    // that this really is a fixed disk
    //

    if (FdoExtension->DeviceDescriptor->BusType == BusTypeFibre ||
        FdoExtension->DeviceDescriptor->BusType == BusTypeiScsi ||
        FdoExtension->DeviceDescriptor->BusType == BusTypeRAID) {

        isPortable = FALSE;
    }

    *IsPortable = isPortable;

cleanup:

    return status;
}

/*++////////////////////////////////////////////////////////////////////////////

ClassCreateDeviceObject()

Routine Description:

    This routine creates an object for the physical device specified and
    sets up the deviceExtension's function pointers for each entry point
    in the device-specific driver.

Arguments:

    DriverObject - Pointer to driver object created by system.

    ObjectNameBuffer - Dir. name of the object to create.

    LowerDeviceObject - Pointer to the lower device object

    IsFdo - should this be an fdo or a pdo

    DeviceObject - Pointer to the device object pointer we will return.

Return Value:

    NTSTATUS

--*/

_IRQL_requires_max_(PASSIVE_LEVEL)
_Must_inspect_result_
_Post_satisfies_(return <= 0)
SCSIPORT_API
NTSTATUS
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassCreateDeviceObject(
    _In_ PDRIVER_OBJECT     DriverObject,
    _In_z_ PCCHAR           ObjectNameBuffer,
    _In_ PDEVICE_OBJECT     LowerDevice,
    _In_ BOOLEAN            IsFdo,
    _Outptr_result_nullonfailure_
    _At_(*DeviceObject, __drv_allocatesMem(Mem) __drv_aliasesMem)
    PDEVICE_OBJECT          *DeviceObject
    )
{
    BOOLEAN        isPartitionable;
    STRING         ntNameString;
    UNICODE_STRING ntUnicodeString;
    NTSTATUS       status;
    PDEVICE_OBJECT deviceObject = NULL;

    ULONG          characteristics;
    SIZE_T         rundownSize = ExSizeOfRundownProtectionCacheAware();
    PCHAR          rundownAddr = NULL;
    ULONG          devExtSize;

#ifdef _MSC_VER
#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
#endif
    PCLASS_DRIVER_EXTENSION driverExtension = IoGetDriverObjectExtension(DriverObject, CLASS_DRIVER_EXTENSION_KEY);

    PCLASS_DEV_INFO devInfo;

    PAGED_CODE();

    _Analysis_assume_(driverExtension != NULL);

    *DeviceObject = NULL;
    RtlInitUnicodeString(&ntUnicodeString, NULL);

    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,  "ClassCreateFdo: Create device object\n"));

    NT_ASSERT(LowerDevice);

    //
    // Make sure that if we're making PDO's we have an enumeration routine
    //

    isPartitionable = (driverExtension->InitData.ClassEnumerateDevice != NULL);

    NT_ASSERT(IsFdo || isPartitionable);

    //
    // Grab the correct dev-info structure out of the init data
    //

    if (IsFdo) {
        devInfo = &(driverExtension->InitData.FdoData);
    } else {
        devInfo = &(driverExtension->InitData.PdoData);
    }

    characteristics = devInfo->DeviceCharacteristics;

    if (ARGUMENT_PRESENT(ObjectNameBuffer)) {
        TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,  "ClassCreateFdo: Name is %s\n", ObjectNameBuffer));

        RtlInitString(&ntNameString, ObjectNameBuffer);

        status = RtlAnsiStringToUnicodeString(&ntUnicodeString, &ntNameString, TRUE);

        if (!NT_SUCCESS(status)) {

            TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT,
                        "ClassCreateFdo: Cannot convert string %s\n",
                        ObjectNameBuffer));

            ntUnicodeString.Buffer = NULL;
            return status;
        }
    } else {
        TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,  "ClassCreateFdo: Object will be unnamed\n"));

        if (IsFdo == FALSE) {

            //
            // PDO's have to have some sort of name.
            //

            SET_FLAG(characteristics, FILE_AUTOGENERATED_DEVICE_NAME);
        }

        RtlInitUnicodeString(&ntUnicodeString, NULL);
    }

    devExtSize = devInfo->DeviceExtensionSize +
        (ULONG)sizeof(CLASS_PRIVATE_COMMON_DATA) + (ULONG)rundownSize;
    status = IoCreateDevice(DriverObject,
                            devExtSize,
                            &ntUnicodeString,
                            devInfo->DeviceType,
                            characteristics,
                            FALSE,
                            &deviceObject);

    if (!NT_SUCCESS(status)) {

        TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT,  "ClassCreateFdo: Can not create device object %lx\n",
                    status));
        NT_ASSERT(deviceObject == NULL);

        //
        // buffer is not used any longer here.
        //

        if (ntUnicodeString.Buffer != NULL) {
            TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT,  "ClassCreateFdo: Freeing unicode name buffer\n"));
            FREE_POOL(ntUnicodeString.Buffer);
            RtlInitUnicodeString(&ntUnicodeString, NULL);
        }

    } else {

        PCOMMON_DEVICE_EXTENSION commonExtension = deviceObject->DeviceExtension;

        RtlZeroMemory(
            deviceObject->DeviceExtension,
            devExtSize);

        //
        // Setup version code
        //

        commonExtension->Version = 0x03;

        //
        // Setup the remove lock and event
        //

        commonExtension->IsRemoved = NO_REMOVE;

#if DBG

        commonExtension->RemoveLock = 0;

#endif

        KeInitializeEvent(&commonExtension->RemoveEvent,
                          SynchronizationEvent,
                          FALSE);


        ClasspInitializeRemoveTracking(deviceObject);

        //
        // Initialize the PrivateCommonData
        //

        commonExtension->PrivateCommonData = (PCLASS_PRIVATE_COMMON_DATA)
            ((PCHAR)deviceObject->DeviceExtension + devInfo->DeviceExtensionSize);
        rundownAddr = (PCHAR)commonExtension->PrivateCommonData + sizeof(CLASS_PRIVATE_COMMON_DATA);
        ExInitializeRundownProtectionCacheAware((PEX_RUNDOWN_REF_CACHE_AWARE)rundownAddr, rundownSize);
        commonExtension->PrivateCommonData->RemoveLockFailAcquire = 0;

        //
        // Acquire the lock once.  This reference will be released when the
        // remove IRP has been received.
        //

        ClassAcquireRemoveLock(deviceObject, (PIRP) deviceObject);

        //
        // Store a pointer to the driver extension so we don't have to do
        // lookups to get it.
        //

        commonExtension->DriverExtension = driverExtension;

        //
        // Fill in entry points
        //

        commonExtension->DevInfo = devInfo;

        //
        // Initialize some of the common values in the structure
        //

        commonExtension->DeviceObject = deviceObject;

        commonExtension->LowerDeviceObject = NULL;

        commonExtension->DispatchTable = driverExtension->DeviceMajorFunctionTable;

        if(IsFdo) {

            PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PVOID) commonExtension;

            commonExtension->PartitionZeroExtension = deviceObject->DeviceExtension;

            //
            // Set the initial device object flags.
            //

            SET_FLAG(deviceObject->Flags, DO_POWER_PAGABLE);
            //
            // Clear the PDO list
            //

            commonExtension->ChildList = NULL;

            commonExtension->DriverData =
                ((PFUNCTIONAL_DEVICE_EXTENSION) deviceObject->DeviceExtension + 1);

            //
            // The disk class driver creates only FDO. The partition number
            // for the FDO must be 0.
            //

            if ((isPartitionable == TRUE) ||
                (devInfo->DeviceType == FILE_DEVICE_DISK)) {

                commonExtension->PartitionNumber = 0;
            } else {
                commonExtension->PartitionNumber = (ULONG) (-1L);
            }

            fdoExtension->DevicePowerState = PowerDeviceD0;

            KeInitializeEvent(&fdoExtension->EjectSynchronizationEvent,
                              SynchronizationEvent,
                              TRUE);

            KeInitializeEvent(&fdoExtension->ChildLock,
                              SynchronizationEvent,
                              TRUE);

            status = ClasspAllocateReleaseRequest(deviceObject);

            if(!NT_SUCCESS(status)) {
                IoDeleteDevice(deviceObject);
                *DeviceObject = NULL;

                if (ntUnicodeString.Buffer != NULL) {
                    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,  "ClassCreateFdo: Freeing unicode name buffer\n"));
                    FREE_POOL(ntUnicodeString.Buffer);
                    RtlInitUnicodeString(&ntUnicodeString, NULL);
                }

                return status;
            }

        } else {

            PPHYSICAL_DEVICE_EXTENSION pdoExtension =
                deviceObject->DeviceExtension;

            PFUNCTIONAL_DEVICE_EXTENSION p0Extension =
                LowerDevice->DeviceExtension;

            SET_FLAG(deviceObject->Flags, DO_POWER_PAGABLE);

            commonExtension->PartitionZeroExtension = p0Extension;

            //
            // Stick this onto the PDO list
            //

            ClassAddChild(p0Extension, pdoExtension, TRUE);

            commonExtension->DriverData = (PVOID) (pdoExtension + 1);

            //
            // Get the top of stack for the lower device - this allows
            // filters to get stuck in between the partitions and the
            // physical disk.
            //

            commonExtension->LowerDeviceObject =
                IoGetAttachedDeviceReference(LowerDevice);

            //
            // Pnp will keep a reference to the lower device object long
            // after this partition has been deleted.  Dereference now so
            // we don't have to deal with it later.
            //

            ObDereferenceObject(commonExtension->LowerDeviceObject);
        }

        KeInitializeEvent(&commonExtension->PathCountEvent, SynchronizationEvent, TRUE);

        commonExtension->IsFdo = IsFdo;

        commonExtension->DeviceName = ntUnicodeString;

        commonExtension->PreviousState = 0xff;

        InitializeDictionary(&(commonExtension->FileObjectDictionary));

        commonExtension->CurrentState = IRP_MN_STOP_DEVICE;

        if (commonExtension->DriverExtension->InitData.ClassStartIo) {
            IoSetStartIoAttributes(deviceObject, TRUE, TRUE);
        }
    }

    *DeviceObject = deviceObject;

    return status;
} // end ClassCreateDeviceObject()

/*++////////////////////////////////////////////////////////////////////////////

ClassClaimDevice()

Routine Description:

    This function claims a device in the port driver.  The port driver object
    is updated with the correct driver object if the device is successfully
    claimed.

Arguments:

    LowerDeviceObject - Supplies the base port device object.

    Release - Indicates the logical unit should be released rather than claimed.

Return Value:

    Returns a status indicating success or failure of the operation.

--*/
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassClaimDevice(
    _In_ PDEVICE_OBJECT LowerDeviceObject,
    _In_ BOOLEAN Release
    )
{
    IO_STATUS_BLOCK    ioStatus;
    PIRP               irp;
    PIO_STACK_LOCATION irpStack;
    KEVENT             event;
    NTSTATUS           status;
    SCSI_REQUEST_BLOCK srb = {0};

    PAGED_CODE();

    //
    // WORK ITEM - MPIO related. Need to think about how to handle.
    //

    srb.Length = sizeof(SCSI_REQUEST_BLOCK);

    srb.Function = Release ? SRB_FUNCTION_RELEASE_DEVICE :
        SRB_FUNCTION_CLAIM_DEVICE;

    //
    // Set the event object to the unsignaled state.
    // It will be used to signal request completion
    //

    KeInitializeEvent(&event, SynchronizationEvent, FALSE);

    //
    // Build synchronous request with no transfer.
    //

    irp = IoBuildDeviceIoControlRequest(IOCTL_SCSI_EXECUTE_NONE,
                                        LowerDeviceObject,
                                        NULL,
                                        0,
                                        NULL,
                                        0,
                                        TRUE,
                                        &event,
                                        &ioStatus);

    if (irp == NULL) {
        TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT,  "ClassClaimDevice: Can't allocate Irp\n"));
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    irpStack = IoGetNextIrpStackLocation(irp);

    //
    // Save SRB address in next stack for port driver.
    //

    irpStack->Parameters.Scsi.Srb = &srb;

    //
    // Set up IRP Address.
    //

    srb.OriginalRequest = irp;

    //
    // Call the port driver with the request and wait for it to complete.
    //

    status = IoCallDriver(LowerDeviceObject, irp);
    if (status == STATUS_PENDING) {

        (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
        status = ioStatus.Status;
    }

    //
    // If this is a release request, then just decrement the reference count
    // and return.  The status does not matter.
    //

    if (Release) {

        // ObDereferenceObject(LowerDeviceObject);
        return STATUS_SUCCESS;
    }

    if (!NT_SUCCESS(status)) {
        return status;
    }

    NT_ASSERT(srb.DataBuffer != NULL);
    NT_ASSERT(!TEST_FLAG(srb.SrbFlags, SRB_FLAGS_FREE_SENSE_BUFFER));

    return status;
} // end ClassClaimDevice()

/*++////////////////////////////////////////////////////////////////////////////

ClassInternalIoControl()

Routine Description:

    This routine passes internal device controls to the port driver.
    Internal device controls are used by higher level drivers both for ioctls
    and to pass through scsi requests.

    If the IoControlCode does not match any of the handled ioctls and is
    a valid system address then the request will be treated as an SRB and
    passed down to the lower driver.  If the IoControlCode is not a valid
    system address the ioctl will be failed.

    Callers must therefore be extremely cautious to pass correct, initialized
    values to this function.

Arguments:

    DeviceObject - Supplies a pointer to the device object for this request.

    Irp - Supplies the Irp making the request.

Return Value:

   Returns back a STATUS_PENDING or a completion status.

--*/
NTSTATUS
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassInternalIoControl(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
{
    PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;

    PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
    PIO_STACK_LOCATION nextStack = IoGetNextIrpStackLocation(Irp);

    ULONG isRemoved;

    PSCSI_REQUEST_BLOCK srb;

    isRemoved = ClassAcquireRemoveLock(DeviceObject, Irp);
    _Analysis_assume_(isRemoved);
    if(isRemoved) {

        Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST;

        ClassReleaseRemoveLock(DeviceObject, Irp);

        ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);

        return STATUS_DEVICE_DOES_NOT_EXIST;
    }

    //
    // Get a pointer to the SRB.
    //

    srb = irpStack->Parameters.Scsi.Srb;

    //
    // Set the parameters in the next stack location.
    //

    if(commonExtension->IsFdo) {
        nextStack->Parameters.Scsi.Srb = srb;
        nextStack->MajorFunction = IRP_MJ_SCSI;
        nextStack->MinorFunction = IRP_MN_SCSI_CLASS;

    } else {

        IoCopyCurrentIrpStackLocationToNext(Irp);
    }

    ClassReleaseRemoveLock(DeviceObject, Irp);

    return IoCallDriver(commonExtension->LowerDeviceObject, Irp);
} // end ClassInternalIoControl()

/*++////////////////////////////////////////////////////////////////////////////

ClassQueryTimeOutRegistryValue()

Routine Description:

    This routine determines whether a reg key for a user-specified timeout
    value exists.  This should be called at initialization time.

Arguments:

    DeviceObject - Pointer to the device object we are retrieving the timeout
                   value for

Return Value:

    None, but it sets a new default timeout for a class of devices.

--*/
_IRQL_requires_max_(PASSIVE_LEVEL)
ULONG
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassQueryTimeOutRegistryValue(
    _In_ PDEVICE_OBJECT DeviceObject
    )
{
    //
    // Find the appropriate reg. key
    //

#ifdef _MSC_VER
#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
#endif
    PCLASS_DRIVER_EXTENSION driverExtension = IoGetDriverObjectExtension(DeviceObject->DriverObject, CLASS_DRIVER_EXTENSION_KEY);

    PUNICODE_STRING registryPath = &(driverExtension->RegistryPath);

    PRTL_QUERY_REGISTRY_TABLE parameters = NULL;
    PWSTR path;
    NTSTATUS status;
    LONG     timeOut = 0;
    ULONG    zero = 0;
    ULONG    size;

    PAGED_CODE();

    if (!registryPath) {
        return 0;
    }

    parameters = ExAllocatePoolWithTag(NonPagedPoolNx,
                                sizeof(RTL_QUERY_REGISTRY_TABLE)*2,
                                '1BcS');

    if (!parameters) {
        return 0;
    }

    size = registryPath->MaximumLength + sizeof(WCHAR);
    path = ExAllocatePoolWithTag(NonPagedPoolNx, size, '2BcS');

    if (!path) {
        FREE_POOL(parameters);
        return 0;
    }

    RtlZeroMemory(path,size);
    RtlCopyMemory(path, registryPath->Buffer, size - sizeof(WCHAR));


    //
    // Check for the Timeout value.
    //

    RtlZeroMemory(parameters,
                  (sizeof(RTL_QUERY_REGISTRY_TABLE)*2));

    parameters[0].Flags         = RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_TYPECHECK;
    parameters[0].Name          = L"TimeOutValue";
    parameters[0].EntryContext  = &timeOut;
    parameters[0].DefaultType   = (REG_DWORD << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_DWORD;
    parameters[0].DefaultData   = &zero;
    parameters[0].DefaultLength = sizeof(ULONG);

    status = RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE | RTL_REGISTRY_OPTIONAL,
                                    path,
                                    parameters,
                                    NULL,
                                    NULL);

    if (!(NT_SUCCESS(status))) {
        timeOut = 0;
    }

    FREE_POOL(parameters);
    FREE_POOL(path);

    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,
                "ClassQueryTimeOutRegistryValue: Timeout value %d\n",
                timeOut));


    return timeOut;

} // end ClassQueryTimeOutRegistryValue()


/*++////////////////////////////////////////////////////////////////////////////

ClassCheckVerifyComplete() ISSUE-2000/02/18-henrygab - why public?!

Routine Description:

    This routine executes when the port driver has completed a check verify
    ioctl.  It will set the status of the master Irp, copy the media change
    count and complete the request.

Arguments:

    Fdo - Supplies the functional device object which represents the logical unit.

    Irp - Supplies the Irp which has completed.

    Context - NULL

Return Value:

    NT status

--*/
NTSTATUS
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassCheckVerifyComplete(
    IN PDEVICE_OBJECT Fdo,
    IN PIRP Irp,
    IN PVOID Context
    )
{
    PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
    PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;

    PIRP originalIrp;

    UNREFERENCED_PARAMETER(Context);

    ASSERT_FDO(Fdo);

    originalIrp = irpStack->Parameters.Others.Argument1;

    //
    // Copy the media change count and status
    //

    *((PULONG) (originalIrp->AssociatedIrp.SystemBuffer)) =
        fdoExtension->MediaChangeCount;

    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "ClassCheckVerifyComplete - Media change count for"
                   "device %d is %lx - saved as %lx\n",
                fdoExtension->DeviceNumber,
                fdoExtension->MediaChangeCount,
                *((PULONG) originalIrp->AssociatedIrp.SystemBuffer)));

    originalIrp->IoStatus.Status = Irp->IoStatus.Status;
    originalIrp->IoStatus.Information = sizeof(ULONG);

    ClassReleaseRemoveLock(Fdo, originalIrp);
    ClassCompleteRequest(Fdo, originalIrp, IO_DISK_INCREMENT);

    IoFreeIrp(Irp);

    return STATUS_MORE_PROCESSING_REQUIRED;

} // end ClassCheckVerifyComplete()


/*++////////////////////////////////////////////////////////////////////////////

ClassGetDescriptor()

Routine Description:

    This routine will perform a query for the specified property id and will
    allocate a non-paged buffer to store the data in.  It is the responsibility
    of the caller to ensure that this buffer is freed.

    This routine must be run at IRQL_PASSIVE_LEVEL

Arguments:

    DeviceObject - the device to query
    DeviceInfo - a location to store a pointer to the buffer we allocate

Return Value:

    status
    if status is unsuccessful *DeviceInfo will be set to NULL, else the
    buffer allocated on behalf of the caller.

--*/
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassGetDescriptor(
    _In_ PDEVICE_OBJECT DeviceObject,
    _In_ PSTORAGE_PROPERTY_ID PropertyId,
    _Outptr_ PVOID *Descriptor
    )
{
    STORAGE_PROPERTY_QUERY query = {0};
    IO_STATUS_BLOCK ioStatus;

    PSTORAGE_DESCRIPTOR_HEADER descriptor = NULL;
    ULONG length;

    PAGED_CODE();

    //
    // Set the passed-in descriptor pointer to NULL as default
    //

    *Descriptor = NULL;

    query.PropertyId = *PropertyId;
    query.QueryType = PropertyStandardQuery;

    //
    // On the first pass we just want to get the first few
    // bytes of the descriptor so we can read it's size
    //

    descriptor = (PVOID)&query;

    NT_ASSERT(sizeof(STORAGE_PROPERTY_QUERY) >= (sizeof(ULONG)*2));

    ClassSendDeviceIoControlSynchronous(
        IOCTL_STORAGE_QUERY_PROPERTY,
        DeviceObject,
        &query,
        sizeof(STORAGE_PROPERTY_QUERY),
        sizeof(ULONG) * 2,
        FALSE,
        &ioStatus
        );

    if(!NT_SUCCESS(ioStatus.Status)) {

        TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT,  "ClassGetDescriptor: error %lx trying to "
                       "query properties #1\n", ioStatus.Status));
        return ioStatus.Status;
    }

    if (descriptor->Size == 0) {

        //
        // This DebugPrint is to help third-party driver writers
        //

        TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT,  "ClassGetDescriptor: size returned was zero?! (status "
                    "%x\n", ioStatus.Status));
        return STATUS_UNSUCCESSFUL;

    }

    //
    // This time we know how much data there is so we can
    // allocate a buffer of the correct size
    //

    length = descriptor->Size;
    NT_ASSERT(length >= sizeof(STORAGE_PROPERTY_QUERY));
    length = max(length, sizeof(STORAGE_PROPERTY_QUERY));

    descriptor = ExAllocatePoolWithTag(NonPagedPoolNx, length, '4BcS');

    if(descriptor == NULL) {

        TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT,  "ClassGetDescriptor: unable to memory for descriptor "
                    "(%d bytes)\n", length));
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // setup the query again, as it was overwritten above
    //

    RtlZeroMemory(&query, sizeof(STORAGE_PROPERTY_QUERY));
    query.PropertyId = *PropertyId;
    query.QueryType = PropertyStandardQuery;

    //
    // copy the input to the new outputbuffer
    //

    RtlZeroMemory(descriptor, length);

    RtlCopyMemory(descriptor,
                  &query,
                  sizeof(STORAGE_PROPERTY_QUERY)
                  );

    ClassSendDeviceIoControlSynchronous(
        IOCTL_STORAGE_QUERY_PROPERTY,
        DeviceObject,
        descriptor,
        sizeof(STORAGE_PROPERTY_QUERY),
        length,
        FALSE,
        &ioStatus
        );

    if(!NT_SUCCESS(ioStatus.Status)) {

        TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT,  "ClassGetDescriptor: error %lx trying to "
                       "query properties #1\n", ioStatus.Status));
        FREE_POOL(descriptor);
        return ioStatus.Status;
    }

    //
    // return the memory we've allocated to the caller
    //

    *Descriptor = descriptor;
    return ioStatus.Status;
} // end ClassGetDescriptor()


/*++////////////////////////////////////////////////////////////////////////////

ClassSignalCompletion()

Routine Description:

    This completion routine will signal the event given as context and then
    return STATUS_MORE_PROCESSING_REQUIRED to stop event completion.  It is
    the responsibility of the routine waiting on the event to complete the
    request and free the event.

Arguments:

    DeviceObject - a pointer to the device object

    Irp - a pointer to the irp

    Event - a pointer to the event to signal

Return Value:

    STATUS_MORE_PROCESSING_REQUIRED

--*/
NTSTATUS
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassSignalCompletion(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PVOID Context
    )
{
    PKEVENT Event = (PKEVENT)Context;

    UNREFERENCED_PARAMETER(DeviceObject);
    UNREFERENCED_PARAMETER(Irp);

    if (Context == NULL) {
        NT_ASSERT(Context != NULL);
        return STATUS_INVALID_PARAMETER;
    }

    KeSetEvent(Event, IO_NO_INCREMENT, FALSE);

    return STATUS_MORE_PROCESSING_REQUIRED;
} // end ClassSignalCompletion()

/*++////////////////////////////////////////////////////////////////////////////

ClassPnpQueryFdoRelations()

Routine Description:

    This routine will call the driver's enumeration routine to update the
    list of PDO's.  It will then build a response to the
    IRP_MN_QUERY_DEVICE_RELATIONS and place it into the information field in
    the irp.

Arguments:

    Fdo - a pointer to the functional device object we are enumerating

    Irp - a pointer to the enumeration request

Return Value:

    status

--*/
NTSTATUS
ClassPnpQueryFdoRelations(
    IN PDEVICE_OBJECT Fdo,
    IN PIRP Irp
    )
{
    PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
#ifdef _MSC_VER
#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
#endif
    PCLASS_DRIVER_EXTENSION driverExtension = IoGetDriverObjectExtension(Fdo->DriverObject, CLASS_DRIVER_EXTENSION_KEY);

    PAGED_CODE();

    _Analysis_assume_(driverExtension != NULL);

    //
    // If there's already an enumeration in progress then don't start another
    // one.
    //

    if(InterlockedIncrement((volatile LONG *)&(fdoExtension->EnumerationInterlock)) == 1) {
        driverExtension->InitData.ClassEnumerateDevice(Fdo);
    }

    Irp->IoStatus.Status = ClassRetrieveDeviceRelations(
                                Fdo,
                                BusRelations,
                                (PDEVICE_RELATIONS *) &Irp->IoStatus.Information);
    InterlockedDecrement((volatile LONG *)&(fdoExtension->EnumerationInterlock));

    return Irp->IoStatus.Status;
} // end ClassPnpQueryFdoRelations()

/*++////////////////////////////////////////////////////////////////////////////

ClassMarkChildrenMissing()

Routine Description:

    This routine will call ClassMarkChildMissing() for all children.
    It acquires the ChildLock before calling ClassMarkChildMissing().

Arguments:

    Fdo - the "bus's" device object, such as the disk FDO for non-removable
        disks with multiple partitions.

Return Value:

    None

--*/
_IRQL_requires_max_(PASSIVE_LEVEL)
VOID
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassMarkChildrenMissing(
    _In_ PFUNCTIONAL_DEVICE_EXTENSION Fdo
    )
{
    PCOMMON_DEVICE_EXTENSION commonExtension = &(Fdo->CommonExtension);
    PPHYSICAL_DEVICE_EXTENSION nextChild = commonExtension->ChildList;

    PAGED_CODE();

    ClassAcquireChildLock(Fdo);

    while (nextChild){
        PPHYSICAL_DEVICE_EXTENSION tmpChild;

        /*
         *  ClassMarkChildMissing will also dequeue the child extension.
         *  So get the next pointer before calling ClassMarkChildMissing.
         */
        tmpChild = nextChild;
        nextChild = tmpChild->CommonExtension.ChildList;
        ClassMarkChildMissing(tmpChild, FALSE);
    }
    ClassReleaseChildLock(Fdo);
    return;
} // end ClassMarkChildrenMissing()

/*++////////////////////////////////////////////////////////////////////////////

ClassMarkChildMissing()

Routine Description:

    This routine will make an active child "missing."  If the device has never
    been enumerated then it will be deleted on the spot.  If the device has
    not been enumerated then it will be marked as missing so that we can
    not report it in the next device enumeration.

Arguments:

    Child - the child device to be marked as missing.

    AcquireChildLock - TRUE if the child lock should be acquired before removing
                       the missing child.  FALSE if the child lock is already
                       acquired by this thread.

Return Value:

    returns whether or not the child device object has previously been reported
    to PNP.

--*/
_IRQL_requires_max_(PASSIVE_LEVEL)
BOOLEAN
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassMarkChildMissing(
    _In_ PPHYSICAL_DEVICE_EXTENSION Child,
    _In_ BOOLEAN AcquireChildLock
    )
{
    BOOLEAN returnValue = Child->IsEnumerated;

    PAGED_CODE();
    ASSERT_PDO(Child->DeviceObject);

    Child->IsMissing = TRUE;

    //
    // Make sure this child is not in the active list.
    //

    ClassRemoveChild(Child->CommonExtension.PartitionZeroExtension,
                     Child,
                     AcquireChildLock);

    if(Child->IsEnumerated == FALSE) {
        PCOMMON_DEVICE_EXTENSION commonExtension = Child->DeviceObject->DeviceExtension;
        commonExtension->IsRemoved = REMOVE_PENDING;
        ClassRemoveDevice(Child->DeviceObject, IRP_MN_REMOVE_DEVICE);
    }

    return returnValue;
} // end ClassMarkChildMissing()

/*++////////////////////////////////////////////////////////////////////////////

ClassRetrieveDeviceRelations()

Routine Description:

    This routine will allocate a buffer to hold the specified list of
    relations.  It will then fill in the list with referenced device pointers
    and will return the request.

Arguments:

    Fdo - pointer to the FDO being queried

    RelationType - what type of relations are being queried

    DeviceRelations - a location to store a pointer to the response

Return Value:

    status

--*/
NTSTATUS
ClassRetrieveDeviceRelations(
    IN PDEVICE_OBJECT Fdo,
    IN DEVICE_RELATION_TYPE RelationType,
    OUT PDEVICE_RELATIONS *DeviceRelations
    )
{
    PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;

    ULONG count = 0;
    ULONG i;

    PPHYSICAL_DEVICE_EXTENSION nextChild;

    ULONG relationsSize;
    PDEVICE_RELATIONS deviceRelations = NULL;
    PDEVICE_RELATIONS oldRelations = *DeviceRelations;

    NTSTATUS status;

    UNREFERENCED_PARAMETER(RelationType);

    PAGED_CODE();

    ClassAcquireChildLock(fdoExtension);

    nextChild = fdoExtension->CommonExtension.ChildList;

    //
    // Count the number of PDO's attached to this disk
    //

    while (nextChild != NULL) {
        PCOMMON_DEVICE_EXTENSION commonExtension;

        commonExtension = &(nextChild->CommonExtension);

        NT_ASSERTMSG("ClassPnp internal error: missing child on active list\n",
                  (nextChild->IsMissing == FALSE));

        nextChild = commonExtension->ChildList;

        count++;
    };

    //
    // If relations already exist in the QDR, adjust the current count
    // to include the previous list.
    //

    if (oldRelations) {
        count += oldRelations->Count;
    }

    relationsSize = (sizeof(DEVICE_RELATIONS) +
                     (count * sizeof(PDEVICE_OBJECT)));

    deviceRelations = ExAllocatePoolWithTag(PagedPool, relationsSize, '5BcS');

    if (deviceRelations == NULL) {

        TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_ENUM,  "ClassRetrieveDeviceRelations: unable to allocate "
                       "%d bytes for device relations\n", relationsSize));

        ClassReleaseChildLock(fdoExtension);

        return STATUS_INSUFFICIENT_RESOURCES;
    }

    RtlZeroMemory(deviceRelations, relationsSize);

    if (oldRelations) {

        //
        // Copy the old relations to the new list and free the old list.
        //

        for (i = 0; i < oldRelations->Count; i++) {
            deviceRelations->Objects[i] = oldRelations->Objects[i];
        }

        FREE_POOL(oldRelations);
    }

    nextChild = fdoExtension->CommonExtension.ChildList;
    i = count;

    while (nextChild != NULL) {
        PCOMMON_DEVICE_EXTENSION commonExtension;

        commonExtension = &(nextChild->CommonExtension);

        NT_ASSERTMSG("ClassPnp internal error: missing child on active list\n",
                  (nextChild->IsMissing == FALSE));

    _Analysis_assume_(i >= 1);
    deviceRelations->Objects[--i] = nextChild->DeviceObject;

        status = ObReferenceObjectByPointer(
                    nextChild->DeviceObject,
                    0,
                    NULL,
                    KernelMode);
        if (!NT_SUCCESS(status)) {
            NT_ASSERT(!"Error referencing child device by pointer");
            TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_ENUM,
                        "ClassRetrieveDeviceRelations: Error referencing child "
                        "device %p by pointer\n", nextChild->DeviceObject));

        }
        nextChild->IsEnumerated = TRUE;
        nextChild = commonExtension->ChildList;
    }

    NT_ASSERTMSG("Child list has changed: ", i == 0);

    deviceRelations->Count = count;
    *DeviceRelations = deviceRelations;

    ClassReleaseChildLock(fdoExtension);
    return STATUS_SUCCESS;
} // end ClassRetrieveDeviceRelations()

/*++////////////////////////////////////////////////////////////////////////////

ClassGetPdoId()

Routine Description:

    This routine will call into the driver to retrieve a copy of one of it's
    id strings.

Arguments:

    Pdo - a pointer to the pdo being queried

    IdType - which type of id string is being queried

    IdString - an allocated unicode string structure which the driver
               can fill in.

Return Value:

    status

--*/
NTSTATUS
ClassGetPdoId(
    IN PDEVICE_OBJECT Pdo,
    IN BUS_QUERY_ID_TYPE IdType,
    IN PUNICODE_STRING IdString
    )
{
#ifdef _MSC_VER
#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
#endif
    PCLASS_DRIVER_EXTENSION driverExtension = IoGetDriverObjectExtension(Pdo->DriverObject, CLASS_DRIVER_EXTENSION_KEY);

    _Analysis_assume_(driverExtension != NULL);

    ASSERT_PDO(Pdo);
    NT_ASSERT(driverExtension->InitData.ClassQueryId);

    PAGED_CODE();

    return driverExtension->InitData.ClassQueryId( Pdo, IdType, IdString);
} // end ClassGetPdoId()

/*++////////////////////////////////////////////////////////////////////////////

ClassQueryPnpCapabilities()

Routine Description:

    This routine will call into the class driver to retrieve it's pnp
    capabilities.

Arguments:

    PhysicalDeviceObject - The physical device object to retrieve properties
                           for.

Return Value:

    status

--*/
NTSTATUS
ClassQueryPnpCapabilities(
    IN PDEVICE_OBJECT DeviceObject,
    IN PDEVICE_CAPABILITIES Capabilities
    )
{
    PCLASS_DRIVER_EXTENSION driverExtension =
        ClassGetDriverExtension(DeviceObject->DriverObject);
    PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;

    PCLASS_QUERY_PNP_CAPABILITIES queryRoutine = NULL;

    PAGED_CODE();

    NT_ASSERT(DeviceObject);
    NT_ASSERT(Capabilities);

    if(commonExtension->IsFdo) {
        queryRoutine = driverExtension->InitData.FdoData.ClassQueryPnpCapabilities;
    } else {
        queryRoutine = driverExtension->InitData.PdoData.ClassQueryPnpCapabilities;
    }

    if(queryRoutine) {
        return queryRoutine(DeviceObject,
                            Capabilities);
    } else {
        return STATUS_NOT_IMPLEMENTED;
    }
} // end ClassQueryPnpCapabilities()

/*++////////////////////////////////////////////////////////////////////////////

ClassInvalidateBusRelations()

Routine Description:

    This routine re-enumerates the devices on the "bus".  It will call into
    the driver's ClassEnumerate routine to update the device objects
    immediately.  It will then schedule a bus re-enumeration for pnp by calling
    IoInvalidateDeviceRelations.

Arguments:

    Fdo - a pointer to the functional device object for this bus

Return Value:

    none

--*/
_IRQL_requires_max_(PASSIVE_LEVEL)
VOID
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassInvalidateBusRelations(
    _In_ PDEVICE_OBJECT Fdo
    )
{
    PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
#ifdef _MSC_VER
#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
#endif
    PCLASS_DRIVER_EXTENSION driverExtension = IoGetDriverObjectExtension(Fdo->DriverObject, CLASS_DRIVER_EXTENSION_KEY);

    NTSTATUS status = STATUS_SUCCESS;

    PAGED_CODE();

    _Analysis_assume_(driverExtension != NULL);

    ASSERT_FDO(Fdo);
    NT_ASSERT(driverExtension->InitData.ClassEnumerateDevice != NULL);

    if(InterlockedIncrement((volatile LONG *)&(fdoExtension->EnumerationInterlock)) == 1) {
        status = driverExtension->InitData.ClassEnumerateDevice(Fdo);
    }
    InterlockedDecrement((volatile LONG *)&(fdoExtension->EnumerationInterlock));

    if(!NT_SUCCESS(status)) {

        TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_ENUM,  "ClassInvalidateBusRelations: EnumerateDevice routine "
                       "returned %lx\n", status));
    }

    IoInvalidateDeviceRelations(fdoExtension->LowerPdo, BusRelations);

    return;
} // end ClassInvalidateBusRelations()

/*++////////////////////////////////////////////////////////////////////////////

ClassRemoveDevice() ISSUE-2000/02/18-henrygab - why public?!

Routine Description:

    This routine is called to handle the "removal" of a device.  It will
    forward the request downwards if necesssary, call into the driver
    to release any necessary resources (memory, events, etc) and then
    will delete the device object.

Arguments:

    DeviceObject - a pointer to the device object being removed

    RemoveType - indicates what type of remove this is (regular or surprise).

Return Value:

    status

--*/
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassRemoveDevice(
    _In_ PDEVICE_OBJECT DeviceObject,
    _In_ UCHAR RemoveType
    )
{
#ifdef _MSC_VER
#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
#endif
    PCLASS_DRIVER_EXTENSION driverExtension = IoGetDriverObjectExtension(DeviceObject->DriverObject, CLASS_DRIVER_EXTENSION_KEY);
    PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
    PDEVICE_OBJECT lowerDeviceObject = commonExtension->LowerDeviceObject;
    BOOLEAN proceedWithRemove = TRUE;
    NTSTATUS status;
    PEX_RUNDOWN_REF_CACHE_AWARE removeLockRundown = NULL;


    PAGED_CODE();

    _Analysis_assume_(driverExtension != NULL);

    /*
     *  Deregister from WMI.
     */
    if (commonExtension->IsFdo ||
        driverExtension->InitData.PdoData.ClassWmiInfo.GuidRegInfo) {
        status = IoWMIRegistrationControl(DeviceObject, WMIREG_ACTION_DEREGISTER);
        TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_WMI, "ClassRemoveDevice: IoWMIRegistrationControl(%p, WMI_ACTION_DEREGISTER) --> %lx", DeviceObject, status));
    }

    /*
     *  If we exposed a "shingle" (a named device interface openable by CreateFile)
     *  then delete it now.
     */
    if (commonExtension->MountedDeviceInterfaceName.Buffer){
        (VOID)IoSetDeviceInterfaceState(&commonExtension->MountedDeviceInterfaceName, FALSE);
        RtlFreeUnicodeString(&commonExtension->MountedDeviceInterfaceName);
        RtlInitUnicodeString(&commonExtension->MountedDeviceInterfaceName, NULL);
    }

    //
    // If this is a surprise removal we leave the device around - which means
    // we don't have to (or want to) drop the remove lock and wait for pending
    // requests to complete.
    //

    if (RemoveType == IRP_MN_REMOVE_DEVICE) {

        //
        // Release the lock we acquired when the device object was created.
        //

        ClassReleaseRemoveLock(DeviceObject, (PIRP) DeviceObject);

        TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP,  "ClasspRemoveDevice - Reference count is now %d\n",
                    commonExtension->RemoveLock));

        //
        // The RemoveLockRundown allows fast protection of the device object
        // structure that is torn down by a single thread. While in the rundown process (the call to
        // ExWaitForRundownProtectionReleaseCacheAware returns), the rundown object becomes
        // invalid and the subsequent calls to ExAcquireRundownProtectionCacheAware will return FALSE.
        // ExReInitializeRundownProtectionCacheAware needs to be called to re-initialize the
        // RemoveLockRundown protection.
        //

        removeLockRundown = (PEX_RUNDOWN_REF_CACHE_AWARE)
            ((PCHAR)commonExtension->PrivateCommonData +
             sizeof(CLASS_PRIVATE_COMMON_DATA));
        ExWaitForRundownProtectionReleaseCacheAware(removeLockRundown);

        KeSetEvent(&commonExtension->RemoveEvent,
                   IO_NO_INCREMENT,
                   FALSE);

        TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP,  "ClasspRemoveDevice - removing device %p\n", DeviceObject));

        if (commonExtension->IsFdo) {

            TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP,  "ClasspRemoveDevice - FDO %p has received a "
                           "remove request.\n", DeviceObject));

        } else {
            PPHYSICAL_DEVICE_EXTENSION pdoExtension = DeviceObject->DeviceExtension;

            if (pdoExtension->IsMissing) {
                /*
                 *  The child partition PDO is missing, so we are going to go ahead
                 *  and delete it for the remove.
                 */
                TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP, "ClasspRemoveDevice - PDO %p is missing and will be removed", DeviceObject));
            } else {
                /*
                 *  We got a remove for a child partition PDO which is not actually missing.
                 *  So we will NOT actually delete it.
                 */
                TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP, "ClasspRemoveDevice - PDO %p still exists and will be removed when it disappears", DeviceObject));

                ExReInitializeRundownProtectionCacheAware(removeLockRundown);

                //
                // Reacquire the remove lock for the next time this comes around.
                //

                ClassAcquireRemoveLock(DeviceObject, (PIRP) DeviceObject);

                //
                // the device wasn't missing so it's not really been removed.
                //

                commonExtension->IsRemoved = NO_REMOVE;

                IoInvalidateDeviceRelations(
                    commonExtension->PartitionZeroExtension->LowerPdo,
                    BusRelations);

                proceedWithRemove = FALSE;
            }
        }
    }


    if (proceedWithRemove) {

        /*
         *  Call the class driver's remove handler.
         *  All this is supposed to do is clean up its data and device interfaces.
         */
        NT_ASSERT(commonExtension->DevInfo->ClassRemoveDevice);
        status = commonExtension->DevInfo->ClassRemoveDevice(DeviceObject, RemoveType);
        NT_ASSERT(NT_SUCCESS(status));
        status = STATUS_SUCCESS;
        UNREFERENCED_PARAMETER(status); // disables prefast warning; defensive coding...

        if (commonExtension->IsFdo) {
            PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;

            ClasspDisableTimer(fdoExtension);

            if (RemoveType == IRP_MN_REMOVE_DEVICE) {

                PPHYSICAL_DEVICE_EXTENSION child;

                //
                // If this FDO is idle power managed, remove it from the
                // list of idle power managed FDOs.
                //
                if (fdoExtension->FunctionSupportInfo &&
                    fdoExtension->FunctionSupportInfo->IdlePower.IdlePowerEnabled) {
                    PIDLE_POWER_FDO_LIST_ENTRY fdoEntry;

                    KeAcquireGuardedMutex(&IdlePowerFDOListMutex);
                    fdoEntry = (PIDLE_POWER_FDO_LIST_ENTRY)IdlePowerFDOList.Flink;
                    while ((PLIST_ENTRY)fdoEntry != &IdlePowerFDOList) {
                        PIDLE_POWER_FDO_LIST_ENTRY nextEntry = (PIDLE_POWER_FDO_LIST_ENTRY)fdoEntry->ListEntry.Flink;
                        if (fdoEntry->Fdo == DeviceObject) {
                            RemoveEntryList(&(fdoEntry->ListEntry));
                            ExFreePool(fdoEntry);
                            break;
                        }
                        fdoEntry = nextEntry;
                    }
                    KeReleaseGuardedMutex(&IdlePowerFDOListMutex);
                }

                //
                // Cleanup the media detection resources now that the class driver
                // has stopped it's timer (if any) and we can be sure they won't
                // call us to do detection again.
                //

                ClassCleanupMediaChangeDetection(fdoExtension);

                //
                // Cleanup any Failure Prediction stuff
                //
                FREE_POOL(fdoExtension->FailurePredictionInfo);

                /*
                 *  Ordinarily all child PDOs will be removed by the time
                 *  that the parent gets the REMOVE_DEVICE.
                 *  However, if a child PDO has been created but has not
                 *  been announced in a QueryDeviceRelations, then it is
                 *  just a private data structure unknown to pnp, and we have
                 *  to delete it ourselves.
                 */
                ClassAcquireChildLock(fdoExtension);
                child = ClassRemoveChild(fdoExtension, NULL, FALSE);
                while (child) {
                    PCOMMON_DEVICE_EXTENSION childCommonExtension = child->DeviceObject->DeviceExtension;

                    //
                    // Yank the pdo.  This routine will unlink the device from the
                    // pdo list so NextPdo will point to the next one when it's
                    // complete.
                    //
                    child->IsMissing = TRUE;
                    childCommonExtension->IsRemoved = REMOVE_PENDING;
                    ClassRemoveDevice(child->DeviceObject, IRP_MN_REMOVE_DEVICE);
                    child = ClassRemoveChild(fdoExtension, NULL, FALSE);
                }
                ClassReleaseChildLock(fdoExtension);
            }
            else if (RemoveType == IRP_MN_SURPRISE_REMOVAL){
                /*
                 *  This is a surprise-remove on the parent FDO.
                 *  We will mark the child PDOs as missing so that they
                 *  will actually get deleted when they get a REMOVE_DEVICE.
                 */
                ClassMarkChildrenMissing(fdoExtension);
            }

            if (RemoveType == IRP_MN_REMOVE_DEVICE) {

                ClasspFreeReleaseRequest(DeviceObject);

                //
                // Free FDO-specific data structs
                //
                if (fdoExtension->PrivateFdoData) {
                    //
                    // Only remove the entry if the list has been initialized, or
                    // else we will access invalid memory.
                    //
                    PLIST_ENTRY allFdosListEntry = &fdoExtension->PrivateFdoData->AllFdosListEntry;
                    if (allFdosListEntry->Flink && allFdosListEntry->Blink) {
                        //
                        //  Remove the FDO from the static list.
                        //  Pnp is synchronized so this shouldn't need any synchronization.
                        //
                        RemoveEntryList(allFdosListEntry);
                    }
                    InitializeListHead(allFdosListEntry);

                    DestroyAllTransferPackets(DeviceObject);

                    //
                    // Delete the tick timer now.
                    //
                    ClasspDeleteTimer(fdoExtension);


                    FREE_POOL(fdoExtension->PrivateFdoData->PowerProcessIrp);
                    FREE_POOL(fdoExtension->PrivateFdoData->FreeTransferPacketsLists);
                    FREE_POOL(fdoExtension->PrivateFdoData);
                }

#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
                FREE_POOL(fdoExtension->AdditionalFdoData);
#endif

                if (commonExtension->DeviceName.Buffer) {
                    FREE_POOL(commonExtension->DeviceName.Buffer);
                    RtlInitUnicodeString(&commonExtension->DeviceName, NULL);
                }

#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
                if (fdoExtension->FunctionSupportInfo != NULL) {
                    FREE_POOL(fdoExtension->FunctionSupportInfo->HwFirmwareInfo);
                }
#endif
                FREE_POOL(fdoExtension->FunctionSupportInfo);

                FREE_POOL(fdoExtension->MiniportDescriptor);

                FREE_POOL(fdoExtension->AdapterDescriptor);

                FREE_POOL(fdoExtension->DeviceDescriptor);

                //
                // Detach our device object from the stack - there's no reason
                // to hold off our cleanup any longer.
                //

                IoDetachDevice(lowerDeviceObject);
            }
        }
        else {
            /*
             *  This is a child partition PDO.
             *  We have already determined that it was previously marked
             *  as missing.  So if this is a REMOVE_DEVICE, we will actually
             *  delete it.
             */
            if (RemoveType == IRP_MN_REMOVE_DEVICE) {
                PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = commonExtension->PartitionZeroExtension;
                PPHYSICAL_DEVICE_EXTENSION pdoExtension = (PPHYSICAL_DEVICE_EXTENSION)commonExtension;

                //
                // See if this device is in the child list (if this was a suprise
                // removal it might be) and remove it.
                //
                ClassRemoveChild(fdoExtension, pdoExtension, TRUE);
            }
        }

        commonExtension->PartitionLength.QuadPart = 0;

        if (RemoveType == IRP_MN_REMOVE_DEVICE) {

            ClasspUninitializeRemoveTracking(DeviceObject);

            IoDeleteDevice(DeviceObject);
        }
    }

    return STATUS_SUCCESS;
} // end ClassRemoveDevice()

/*++////////////////////////////////////////////////////////////////////////////

ClassGetDriverExtension()

Routine Description:

    This routine will return the classpnp's driver extension.

Arguments:

    DriverObject - the driver object for which to get classpnp's extension

Return Value:

    Either NULL if none, or a pointer to the driver extension

--*/
__drv_aliasesMem
_IRQL_requires_max_(DISPATCH_LEVEL)
PCLASS_DRIVER_EXTENSION
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassGetDriverExtension(
    _In_ PDRIVER_OBJECT DriverObject
    )
{
#ifdef _MSC_VER
#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
#endif
    return IoGetDriverObjectExtension(DriverObject, CLASS_DRIVER_EXTENSION_KEY);
} // end ClassGetDriverExtension()

/*++////////////////////////////////////////////////////////////////////////////

ClasspStartIo()

Routine Description:

    This routine wraps the class driver's start io routine.  If the device
    is being removed it will complete any requests with
    STATUS_DEVICE_DOES_NOT_EXIST and fire up the next packet.

Arguments:

Return Value:

    none

--*/
VOID
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClasspStartIo(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
{
    PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;

    //
    // We're already holding the remove lock so just check the variable and
    // see what's going on.
    //

    if(commonExtension->IsRemoved) {

        Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST;

#ifdef _MSC_VER
#pragma warning(suppress:4054) // okay to type cast function pointer to PIRP for this use case
#endif
        ClassAcquireRemoveLock(DeviceObject, (PIRP) ClasspStartIo);

        ClassReleaseRemoveLock(DeviceObject, Irp);
        ClassCompleteRequest(DeviceObject, Irp, IO_DISK_INCREMENT);
        IoStartNextPacket(DeviceObject, TRUE); // Some IO is cancellable

#ifdef _MSC_VER
#pragma warning(suppress:4054) // okay to type cast function pointer to PIRP for this use case
#endif
        ClassReleaseRemoveLock(DeviceObject, (PIRP) ClasspStartIo);

        return;
    }

    commonExtension->DriverExtension->InitData.ClassStartIo(
        DeviceObject,
        Irp);

    return;
} // ClasspStartIo()

/*++////////////////////////////////////////////////////////////////////////////

ClassUpdateInformationInRegistry()

Routine Description:

    This routine has knowledge about the layout of the device map information
    in the registry.  It will update this information to include a value
    entry specifying the dos device name that is assumed to get assigned
    to this NT device name.  For more information on this assigning of the
    dos device name look in the drive support routine in the hal that assigns
    all dos names.

    Since some versions of some device's firmware did not work and some
    vendors did not bother to follow the specification, the entire inquiry
    information must also be stored in the registry so than someone can
    figure out the firmware version.

Arguments:

    DeviceObject - A pointer to the device object for the tape device.

Return Value:

    None

--*/
_IRQL_requires_max_(PASSIVE_LEVEL)
VOID
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassUpdateInformationInRegistry(
    _In_ PDEVICE_OBJECT   Fdo,
    _In_ PCHAR            DeviceName,
    _In_ ULONG            DeviceNumber,
    _In_reads_bytes_opt_(InquiryDataLength) PINQUIRYDATA InquiryData,
    _In_ ULONG            InquiryDataLength
    )
{
    NTSTATUS          status;
    SCSI_ADDRESS      scsiAddress = {0};
    OBJECT_ATTRIBUTES objectAttributes = {0};
    STRING            string;
    UNICODE_STRING    unicodeName = {0};
    UNICODE_STRING    unicodeRegistryPath = {0};
    UNICODE_STRING    unicodeData = {0};
    HANDLE            targetKey;
    IO_STATUS_BLOCK   ioStatus;
    UCHAR buffer[256] = {0};

    PAGED_CODE();

    NT_ASSERT(DeviceName);
    targetKey = NULL;

    _SEH2_TRY {

        //
        // Issue GET_ADDRESS Ioctl to determine path, target, and lun information.
        //

        ClassSendDeviceIoControlSynchronous(
            IOCTL_SCSI_GET_ADDRESS,
            Fdo,
            &scsiAddress,
            0,
            sizeof(SCSI_ADDRESS),
            FALSE,
            &ioStatus
            );

        if (!NT_SUCCESS(ioStatus.Status)) {

            status = ioStatus.Status;
            TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT,
                        "UpdateInformationInRegistry: Get Address failed %lx\n",
                        status));
            _SEH2_LEAVE;

        } else {

            TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT,
                        "GetAddress: Port %x, Path %x, Target %x, Lun %x\n",
                        scsiAddress.PortNumber,
                        scsiAddress.PathId,
                        scsiAddress.TargetId,
                        scsiAddress.Lun));

        }

        status = RtlStringCchPrintfA((NTSTRSAFE_PSTR)buffer,
                sizeof(buffer)-1,
                "\\Registry\\Machine\\Hardware\\DeviceMap\\Scsi\\Scsi Port %d\\Scsi Bus %d\\Target Id %d\\Logical Unit Id %d",
                scsiAddress.PortNumber,
                scsiAddress.PathId,
                scsiAddress.TargetId,
                scsiAddress.Lun);

        if (!NT_SUCCESS(status)) {
            _SEH2_LEAVE;
        }

        RtlInitString(&string, (PCSZ)buffer);

        status = RtlAnsiStringToUnicodeString(&unicodeRegistryPath,
                                              &string,
                                              TRUE);

        if (!NT_SUCCESS(status)) {
            _SEH2_LEAVE;
        }

        //
        // Open the registry key for the scsi information for this
        // scsibus, target, lun.
        //

        InitializeObjectAttributes(&objectAttributes,
                                   &unicodeRegistryPath,
                                   OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
                                   NULL,
                                   NULL);

        status = ZwOpenKey(&targetKey,
                           KEY_READ | KEY_WRITE,
                           &objectAttributes);

        if (!NT_SUCCESS(status)) {
            _SEH2_LEAVE;
        }

        //
        // Now construct and attempt to create the registry value
        // specifying the device name in the appropriate place in the
        // device map.
        //

        RtlInitUnicodeString(&unicodeName, L"DeviceName");

        status = RtlStringCchPrintfA((NTSTRSAFE_PSTR)buffer, sizeof(buffer)-1, "%s%d", DeviceName, DeviceNumber);
        if (!NT_SUCCESS(status)) {
            _SEH2_LEAVE;
        }

        RtlInitString(&string, (PCSZ)buffer);
        status = RtlAnsiStringToUnicodeString(&unicodeData,
                                              &string,
                                              TRUE);
        if (NT_SUCCESS(status)) {
            status = ZwSetValueKey(targetKey,
                                   &unicodeName,
                                   0,
                                   REG_SZ,
                                   unicodeData.Buffer,
                                   unicodeData.Length);
        }

        //
        // if they sent in data, update the registry
        //

        if (NT_SUCCESS(status) && InquiryDataLength) {

            NT_ASSERT(InquiryData);

            RtlInitUnicodeString(&unicodeName, L"InquiryData");
            status = ZwSetValueKey(targetKey,
                                   &unicodeName,
                                   0,
                                   REG_BINARY,
                                   InquiryData,
                                   InquiryDataLength);
        }

        // that's all, except to clean up.

    } _SEH2_FINALLY {

        if (!NT_SUCCESS(status)) {
            TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT,
                        "Failure to update information in registry: %08x\n",
                        status
                        ));
        }

        if (unicodeData.Buffer) {
            RtlFreeUnicodeString(&unicodeData);
        }
        if (unicodeRegistryPath.Buffer) {
            RtlFreeUnicodeString(&unicodeRegistryPath);
        }
        if (targetKey) {
            ZwClose(targetKey);
        }

    } _SEH2_END;

} // end ClassUpdateInformationInRegistry()

/*++////////////////////////////////////////////////////////////////////////////

ClasspSendSynchronousCompletion()

Routine Description:

    This completion routine will set the user event in the irp after
    freeing the irp and the associated MDL (if any).

Arguments:

    DeviceObject - the device object which requested the completion routine

    Irp - the irp being completed

    Context - unused

Return Value:

    STATUS_MORE_PROCESSING_REQUIRED

--*/
NTSTATUS
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClasspSendSynchronousCompletion(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PVOID Context
    )
{
    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,  "ClasspSendSynchronousCompletion: %p %p %p\n",
                   DeviceObject, Irp, Context));
    //
    // First set the status and information fields in the io status block
    // provided by the caller.
    //

    *(Irp->UserIosb) = Irp->IoStatus;

    //
    // Unlock the pages for the data buffer.
    //

    if(Irp->MdlAddress) {
        MmUnlockPages(Irp->MdlAddress);
        IoFreeMdl(Irp->MdlAddress);
    }

    //
    // Signal the caller's event.
    //

    KeSetEvent(Irp->UserEvent, IO_NO_INCREMENT, FALSE);

    //
    // Free the MDL and the IRP.
    //

    IoFreeIrp(Irp);

    return STATUS_MORE_PROCESSING_REQUIRED;
} // end ClasspSendSynchronousCompletion()

/*++

    ISSUE-2000/02/20-henrygab Not documented ClasspRegisterMountedDeviceInterface

--*/
VOID
ClasspRegisterMountedDeviceInterface(
    IN PDEVICE_OBJECT DeviceObject
    )
{

    PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
    BOOLEAN isFdo = commonExtension->IsFdo;
    PDEVICE_OBJECT pdo;
    UNICODE_STRING interfaceName;

    NTSTATUS status;

    PAGED_CODE();

    if(isFdo) {

        PFUNCTIONAL_DEVICE_EXTENSION functionalExtension;

        functionalExtension =
            (PFUNCTIONAL_DEVICE_EXTENSION) commonExtension;
        pdo = functionalExtension->LowerPdo;
    } else {
        pdo = DeviceObject;
    }

#ifdef _MSC_VER
#pragma prefast(suppress:6014, "The allocated memory that interfaceName points to will be freed in ClassRemoveDevice().")
#endif
    status = IoRegisterDeviceInterface(
                pdo,
                &MOUNTDEV_MOUNTED_DEVICE_GUID,
                NULL,
                &interfaceName
                );

    if(NT_SUCCESS(status)) {

        //
        // Copy the interface name before setting the interface state - the
        // name is needed by the components we notify.
        //

        commonExtension->MountedDeviceInterfaceName = interfaceName;
        status = IoSetDeviceInterfaceState(&interfaceName, TRUE);

        if(!NT_SUCCESS(status)) {
            RtlFreeUnicodeString(&interfaceName);
        }
    }

    if(!NT_SUCCESS(status)) {
        RtlInitUnicodeString(&(commonExtension->MountedDeviceInterfaceName),
                             NULL);
    }
    return;
} // end ClasspRegisterMountedDeviceInterface()

/*++////////////////////////////////////////////////////////////////////////////

ClassSendDeviceIoControlSynchronous()

Routine Description:

    This routine is based upon IoBuildDeviceIoControlRequest().  It has been
    modified to reduce code and memory by not double-buffering the io, using
    the same buffer for both input and output, allocating and deallocating
    the mdl on behalf of the caller, and waiting for the io to complete.

    This routine also works around the rare cases in which APC's are disabled.
    Since IoBuildDeviceIoControl() used APC's to signal completion, this had
    led to a number of difficult-to-detect hangs, where the irp was completed,
    but the event passed to IoBuild..() was still being waited upon by the
    caller.

Arguments:

    IoControlCode - the IOCTL to send

    TargetDeviceObject - the device object that should handle the ioctl

    Buffer - the input and output buffer, or NULL if no input/output

    InputBufferLength - the number of bytes prepared for the IOCTL in Buffer

    OutputBufferLength - the number of bytes to be filled in upon success

    InternalDeviceIoControl - if TRUE, uses IRP_MJ_INTERNAL_DEVICE_CONTROL

    IoStatus - the status block that contains the results of the operation

Return Value:

--*/
VOID
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassSendDeviceIoControlSynchronous(
    _In_ ULONG IoControlCode,
    _In_ PDEVICE_OBJECT TargetDeviceObject,
    _Inout_updates_opt_(_Inexpressible_(max(InputBufferLength, OutputBufferLength))) PVOID Buffer,
    _In_ ULONG InputBufferLength,
    _In_ ULONG OutputBufferLength,
    _In_ BOOLEAN InternalDeviceIoControl,
    _Out_ PIO_STATUS_BLOCK IoStatus
    )
{
    PIRP irp;
    PIO_STACK_LOCATION irpSp;
    ULONG method;

    PAGED_CODE();

    irp = NULL;
    method = IoControlCode & 3;

    #if DBG // Begin Argument Checking (nop in fre version)

    NT_ASSERT(ARGUMENT_PRESENT(IoStatus));

    if ((InputBufferLength != 0) || (OutputBufferLength != 0)) {
        NT_ASSERT(ARGUMENT_PRESENT(Buffer));
    }
    else {
        NT_ASSERT(!ARGUMENT_PRESENT(Buffer));
    }
    #endif

    //
    // Begin by allocating the IRP for this request.  Do not charge quota to
    // the current process for this IRP.
    //

    irp = IoAllocateIrp(TargetDeviceObject->StackSize, FALSE);
    if (!irp) {
        IoStatus->Information = 0;
        IoStatus->Status = STATUS_INSUFFICIENT_RESOURCES;
        return;
    }

    //
    // Get a pointer to the stack location of the first driver which will be
    // invoked.  This is where the function codes and the parameters are set.
    //

    irpSp = IoGetNextIrpStackLocation(irp);

    //
    // Set the major function code based on the type of device I/O control
    // function the caller has specified.
    //

    if (InternalDeviceIoControl) {
        irpSp->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
    } else {
        irpSp->MajorFunction = IRP_MJ_DEVICE_CONTROL;
    }

    //
    // Copy the caller's parameters to the service-specific portion of the
    // IRP for those parameters that are the same for all four methods.
    //

    irpSp->Parameters.DeviceIoControl.OutputBufferLength = OutputBufferLength;
    irpSp->Parameters.DeviceIoControl.InputBufferLength = InputBufferLength;
    irpSp->Parameters.DeviceIoControl.IoControlCode = IoControlCode;

    //
    // Get the method bits from the I/O control code to determine how the
    // buffers are to be passed to the driver.
    //

    switch (method)
    {
        //
        // case 0
        //
        case METHOD_BUFFERED:
        {
            if ((InputBufferLength != 0) || (OutputBufferLength != 0))
            {
                irp->AssociatedIrp.SystemBuffer = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned,
                                                                        max(InputBufferLength, OutputBufferLength),
                                                                        CLASS_TAG_DEVICE_CONTROL);
                if (irp->AssociatedIrp.SystemBuffer == NULL)
                {
                    IoFreeIrp(irp);

                    IoStatus->Information = 0;
                    IoStatus->Status = STATUS_INSUFFICIENT_RESOURCES;
                    return;
                }

                if (InputBufferLength != 0)
                {
                    RtlCopyMemory(irp->AssociatedIrp.SystemBuffer, Buffer, InputBufferLength);
                }
            }

            irp->UserBuffer = Buffer;

            break;
        }

        //
        // case 1, case 2
        //
        case METHOD_IN_DIRECT:
        case METHOD_OUT_DIRECT:
        {
            if (InputBufferLength != 0)
            {
                irp->AssociatedIrp.SystemBuffer = Buffer;
            }

            if (OutputBufferLength != 0)
            {
                irp->MdlAddress = IoAllocateMdl(Buffer,
                                                OutputBufferLength,
                                                FALSE,
                                                FALSE,
                                                (PIRP) NULL);
                if (irp->MdlAddress == NULL)
                {
                    IoFreeIrp(irp);

                    IoStatus->Information = 0;
                    IoStatus->Status = STATUS_INSUFFICIENT_RESOURCES;
                    return;
                }

                _SEH2_TRY
                {
                    MmProbeAndLockPages(irp->MdlAddress,
                                        KernelMode,
                                        (method == METHOD_IN_DIRECT) ? IoReadAccess : IoWriteAccess);
                }
#ifdef _MSC_VER
                #pragma warning(suppress: 6320) // We want to handle any exception that MmProbeAndLockPages might throw
#endif
                _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
                {
                    IoFreeMdl(irp->MdlAddress);
                    IoFreeIrp(irp);

                    IoStatus->Information = 0;
                    IoStatus->Status = _SEH2_GetExceptionCode();
                    _SEH2_YIELD(return);
                } _SEH2_END;
            }

            break;
        }

        //
        // case 3
        //
        case METHOD_NEITHER:
        {
            NT_ASSERT(!"ClassSendDeviceIoControlSynchronous does not support METHOD_NEITHER Ioctls");

            IoFreeIrp(irp);

            IoStatus->Information = 0;
            IoStatus->Status = STATUS_NOT_SUPPORTED;
            return;
        }
    }

    irp->Tail.Overlay.Thread = PsGetCurrentThread();

    //
    // send the irp synchronously
    //

    ClassSendIrpSynchronous(TargetDeviceObject, irp);

    //
    // copy the iostatus block for the caller
    //

    *IoStatus = irp->IoStatus;

    //
    // free any allocated resources
    //

    switch (method) {
        case METHOD_BUFFERED: {

            NT_ASSERT(irp->UserBuffer == Buffer);

            //
            // first copy the buffered result, if any
            // Note that there are no security implications in
            // not checking for success since only drivers can
            // call into this routine anyways...
            //

            if (OutputBufferLength != 0) {
#ifdef _MSC_VER
                #pragma warning(suppress: 6386) // Buffer's size is max(InputBufferLength, OutputBufferLength)
#endif
                RtlCopyMemory(Buffer, // irp->UserBuffer
                              irp->AssociatedIrp.SystemBuffer,
                              OutputBufferLength
                              );
            }

            //
            // then free the memory allocated to buffer the io
            //

            if ((InputBufferLength !=0) || (OutputBufferLength != 0)) {
                FREE_POOL(irp->AssociatedIrp.SystemBuffer);
            }
            break;
        }

        case METHOD_IN_DIRECT:
        case METHOD_OUT_DIRECT: {

            //
            // we alloc a mdl if there is an output buffer specified
            // free it here after unlocking the pages
            //

            if (OutputBufferLength != 0) {
                NT_ASSERT(irp->MdlAddress != NULL);
                MmUnlockPages(irp->MdlAddress);
                IoFreeMdl(irp->MdlAddress);
                irp->MdlAddress = (PMDL) NULL;
            }
            break;
        }

        case METHOD_NEITHER: {
            NT_ASSERT(!"Code is out of date");
            break;
        }
    }

    //
    // we always have allocated an irp.  free it here.
    //

    IoFreeIrp(irp);
    irp = (PIRP) NULL;

    //
    // return the io status block's status to the caller
    //

    return;
} // end ClassSendDeviceIoControlSynchronous()

/*++////////////////////////////////////////////////////////////////////////////

ClassForwardIrpSynchronous()

Routine Description:

    Forwards a given irp to the next lower device object.

Arguments:

    CommonExtension - the common class extension

    Irp - the request to forward down the stack

Return Value:

--*/
NTSTATUS
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassForwardIrpSynchronous(
    _In_ PCOMMON_DEVICE_EXTENSION CommonExtension,
    _In_ PIRP Irp
    )
{
    IoCopyCurrentIrpStackLocationToNext(Irp);
    return ClassSendIrpSynchronous(CommonExtension->LowerDeviceObject, Irp);
} // end ClassForwardIrpSynchronous()

/*++////////////////////////////////////////////////////////////////////////////

ClassSendIrpSynchronous()

Routine Description:

    This routine sends the given irp to the given device object, and waits for
    it to complete.  On debug versions, will print out a debug message and
    optionally assert for "lost" irps based upon classpnp's globals

Arguments:

    TargetDeviceObject - the device object to handle this irp

    Irp - the request to be sent

Return Value:

--*/
NTSTATUS
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassSendIrpSynchronous(
    _In_ PDEVICE_OBJECT TargetDeviceObject,
    _In_ PIRP Irp
    )
{
    KEVENT event;
    NTSTATUS status;

    NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
    NT_ASSERT(TargetDeviceObject != NULL);
    NT_ASSERT(Irp != NULL);
    NT_ASSERT(Irp->StackCount >= TargetDeviceObject->StackSize);

    //
    // ISSUE-2000/02/20-henrygab   What if APCs are disabled?
    //    May need to enter critical section before IoCallDriver()
    //    until the event is hit?
    //

    KeInitializeEvent(&event, SynchronizationEvent, FALSE);
    IoSetCompletionRoutine(Irp, ClassSignalCompletion, &event,
                           TRUE, TRUE, TRUE);

    status = IoCallDriver(TargetDeviceObject, Irp);
    _Analysis_assume_(status!=STATUS_PENDING);
    if (status == STATUS_PENDING) {

        #if DBG
            LARGE_INTEGER timeout;

            timeout.QuadPart = (LONGLONG)(-1 * 10 * 1000 * (LONGLONG)1000 *
                                          ClasspnpGlobals.SecondsToWaitForIrps);

            do {
                status = KeWaitForSingleObject(&event,
                                               Executive,
                                               KernelMode,
                                               FALSE,
                                               &timeout);


                if (status == STATUS_TIMEOUT) {

                    //
                    // This DebugPrint should almost always be investigated by the
                    // party who sent the irp and/or the current owner of the irp.
                    // Synchronous Irps should not take this long (currently 30
                    // seconds) without good reason.  This points to a potentially
                    // serious problem in the underlying device stack.
                    //

                    TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,  "ClassSendIrpSynchronous: (%p) irp %p did not "
                                "complete within %x seconds\n",
                                TargetDeviceObject, Irp,
                                ClasspnpGlobals.SecondsToWaitForIrps
                                ));

                    if (ClasspnpGlobals.BreakOnLostIrps != 0) {
                        NT_ASSERT(!" - Irp failed to complete within 30 seconds - ");
                    }
                }


            } while (status==STATUS_TIMEOUT);
        #else
            (VOID)KeWaitForSingleObject(&event,
                                        Executive,
                                        KernelMode,
                                        FALSE,
                                        NULL);
        #endif

        status = Irp->IoStatus.Status;
    }

    return status;
} // end ClassSendIrpSynchronous()

/*++////////////////////////////////////////////////////////////////////////////

ClassGetVpb()

Routine Description:

    This routine returns the current VPB (Volume Parameter Block) for the
    given device object.
    The Vpb field is only visible in the ntddk.h (not the wdm.h) definition
    of DEVICE_OBJECT; hence this exported function.

Arguments:

    DeviceObject - the device to get the VPB for

Return Value:

    the VPB, or NULL if none.

--*/
PVPB
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassGetVpb(
    _In_ PDEVICE_OBJECT DeviceObject
    )
{
#ifdef _MSC_VER
#pragma prefast(suppress:28175)
#endif
    return DeviceObject->Vpb;
} // end ClassGetVpb()

/*++

    ISSUE-2000/02/20-henrygab Not documented ClasspAllocateReleaseRequest

--*/
NTSTATUS
ClasspAllocateReleaseRequest(
    IN PDEVICE_OBJECT Fdo
    )
{
    PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;

    PAGED_CODE();

    KeInitializeSpinLock(&(fdoExtension->ReleaseQueueSpinLock));

    fdoExtension->ReleaseQueueNeeded = FALSE;
    fdoExtension->ReleaseQueueInProgress = FALSE;
    fdoExtension->ReleaseQueueIrpFromPool = FALSE;

    //
    // The class driver is responsible for allocating a properly sized irp,
    // or ClassReleaseQueue will attempt to do it on the first error.
    //

    fdoExtension->ReleaseQueueIrp = NULL;

    //
    // Write length to SRB.
    //

    fdoExtension->ReleaseQueueSrb.Length = sizeof(SCSI_REQUEST_BLOCK);

    return STATUS_SUCCESS;
} // end ClasspAllocateReleaseRequest()

/*++

    ISSUE-2000/02/20-henrygab Not documented ClasspFreeReleaseRequest

--*/
VOID
ClasspFreeReleaseRequest(
    IN PDEVICE_OBJECT Fdo
    )
{
    PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;

    PAGED_CODE();

    //KIRQL oldIrql;

    NT_ASSERT(fdoExtension->CommonExtension.IsRemoved != NO_REMOVE);

    //
    // free anything the driver allocated
    //

    if (fdoExtension->ReleaseQueueIrp) {
        if (fdoExtension->ReleaseQueueIrpFromPool) {
            FREE_POOL(fdoExtension->ReleaseQueueIrp);
        } else {
            IoFreeIrp(fdoExtension->ReleaseQueueIrp);
        }
        fdoExtension->ReleaseQueueIrp = NULL;
    }

    //
    // free anything that we allocated
    //

    if ((fdoExtension->PrivateFdoData) &&
        (fdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated)) {

        FREE_POOL(fdoExtension->PrivateFdoData->ReleaseQueueIrp);
        fdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated = FALSE;
    }

    return;
} // end ClasspFreeReleaseRequest()

/*++////////////////////////////////////////////////////////////////////////////

ClassReleaseQueue()

Routine Description:

    This routine issues an internal device control command
    to the port driver to release a frozen queue. The call
    is issued asynchronously as ClassReleaseQueue will be invoked
    from the IO completion DPC (and will have no context to
    wait for a synchronous call to complete).

    This routine must be called with the remove lock held.

Arguments:

    Fdo - The functional device object for the device with the frozen queue.

Return Value:

    None.

--*/
VOID
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassReleaseQueue(
    _In_ PDEVICE_OBJECT Fdo
    )
{
    ClasspReleaseQueue(Fdo, NULL);
    return;
} // end ClassReleaseQueue()

/*++////////////////////////////////////////////////////////////////////////////

ClasspAllocateReleaseQueueIrp()

Routine Description:

    This routine allocates the release queue irp held in classpnp's private
    extension.  This was added to allow no-memory conditions to be more
    survivable.

Return Value:

    NT_SUCCESS value.

Notes:

    Does not grab the spinlock.  Should only be called from StartDevice()
    routine.  May be called elsewhere for poorly-behaved drivers that cause
    the queue to lockup before the device is started.  This should *never*
    occur, since it's illegal to send a request to a non-started PDO.  This
    condition is checked for in ClasspReleaseQueue().

--*/
NTSTATUS
ClasspAllocateReleaseQueueIrp(
    PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
    )
{
    UCHAR lowerStackSize;

    //
    // do an initial check w/o the spinlock
    //

    if (FdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated) {
        return STATUS_SUCCESS;
    }


    lowerStackSize = FdoExtension->CommonExtension.LowerDeviceObject->StackSize;

    //
    // don't allocate one if one is in progress!  this means whoever called
    // this routine didn't check if one was in progress.
    //

    NT_ASSERT(!(FdoExtension->ReleaseQueueInProgress));

    FdoExtension->PrivateFdoData->ReleaseQueueIrp =
        ExAllocatePoolWithTag(NonPagedPoolNx,
                              IoSizeOfIrp(lowerStackSize),
                              CLASS_TAG_RELEASE_QUEUE
                              );

    if (FdoExtension->PrivateFdoData->ReleaseQueueIrp == NULL) {
        TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP,  "ClassPnpStartDevice: Cannot allocate for "
                    "release queue irp\n"));
        return STATUS_INSUFFICIENT_RESOURCES;
    }
    IoInitializeIrp(FdoExtension->PrivateFdoData->ReleaseQueueIrp,
                    IoSizeOfIrp(lowerStackSize),
                    lowerStackSize);
    FdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated = TRUE;

    return STATUS_SUCCESS;
}

/*++////////////////////////////////////////////////////////////////////////////

ClasspAllocatePowerProcessIrp()

Routine Description:

    This routine allocates the power process irp.
    This routine should be called after PrivateFdoData is allocated.

Return Value:

    NTSTATUS value.

Notes:

    Should only be called from StartDevice()

--*/
NTSTATUS
ClasspAllocatePowerProcessIrp(
    PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
    )
{
    UCHAR stackSize;

    NT_ASSERT(FdoExtension->PrivateFdoData != NULL);

    stackSize = FdoExtension->CommonExtension.LowerDeviceObject->StackSize + 1;

    FdoExtension->PrivateFdoData->PowerProcessIrp = ExAllocatePoolWithTag(NonPagedPoolNx,
                                                                          IoSizeOfIrp(stackSize),
                                                                          CLASS_TAG_POWER
                                                                          );

    if (FdoExtension->PrivateFdoData->PowerProcessIrp == NULL) {

        return STATUS_INSUFFICIENT_RESOURCES;
    } else {

        IoInitializeIrp(FdoExtension->PrivateFdoData->PowerProcessIrp,
                        IoSizeOfIrp(stackSize),
                        stackSize);

        return STATUS_SUCCESS;
    }
}


/*++////////////////////////////////////////////////////////////////////////////

ClasspReleaseQueue()

Routine Description:

    This routine issues an internal device control command
    to the port driver to release a frozen queue. The call
    is issued asynchronously as ClassReleaseQueue will be invoked
    from the IO completion DPC (and will have no context to
    wait for a synchronous call to complete).

    This routine must be called with the remove lock held.

Arguments:

    Fdo - The functional device object for the device with the frozen queue.

    ReleaseQueueIrp - If this irp is supplied then the test to determine whether
                      a release queue request is in progress will be ignored.
                      The irp provided must be the IRP originally allocated
                      for release queue requests (so this parameter can only
                      really be provided by the release queue completion
                      routine.)

Return Value:

    None.

--*/
VOID
ClasspReleaseQueue(
    IN PDEVICE_OBJECT Fdo,
    IN PIRP ReleaseQueueIrp OPTIONAL
    )
{
    PIO_STACK_LOCATION irpStack;
    PIRP irp;
    PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
    PDEVICE_OBJECT lowerDevice;
    PSTORAGE_REQUEST_BLOCK_HEADER srb;
    KIRQL currentIrql;
    ULONG function;

    lowerDevice = fdoExtension->CommonExtension.LowerDeviceObject;

    //
    // we raise irql seperately so we're not swapped out or suspended
    // while holding the release queue irp in this routine.  this lets
    // us release the spin lock before lowering irql.
    //

    KeRaiseIrql(DISPATCH_LEVEL, &currentIrql);

    KeAcquireSpinLockAtDpcLevel(&(fdoExtension->ReleaseQueueSpinLock));

    //
    // make sure that if they passed us an irp, it matches our allocated irp.
    //

    NT_ASSERT((ReleaseQueueIrp == NULL) ||
           (ReleaseQueueIrp == fdoExtension->PrivateFdoData->ReleaseQueueIrp));

    //
    // ASSERT that we've already allocated this. (should not occur)
    // try to allocate it anyways, then finally bugcheck if
    // there's still no memory...
    //

    NT_ASSERT(fdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated);
    if (!fdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated) {
        ClasspAllocateReleaseQueueIrp(fdoExtension);
    }
    if (!fdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated) {
        KeBugCheckEx(SCSI_DISK_DRIVER_INTERNAL, 0x12, (ULONG_PTR)Fdo, 0x0, 0x0);
    }

    if ((fdoExtension->ReleaseQueueInProgress) && (ReleaseQueueIrp == NULL)) {

        //
        // Someone is already using the irp - just set the flag to indicate that
        // we need to release the queue again.
        //

        fdoExtension->ReleaseQueueNeeded = TRUE;
        KeReleaseSpinLockFromDpcLevel(&(fdoExtension->ReleaseQueueSpinLock));
        KeLowerIrql(currentIrql);
        return;

    }

    //
    // Mark that there is a release queue in progress and drop the spinlock.
    //

    fdoExtension->ReleaseQueueInProgress = TRUE;
    if (ReleaseQueueIrp) {
        irp = ReleaseQueueIrp;
    } else {
        irp = fdoExtension->PrivateFdoData->ReleaseQueueIrp;
    }

    if (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
        srb = (PSTORAGE_REQUEST_BLOCK_HEADER)&(fdoExtension->PrivateFdoData->ReleaseQueueSrb.SrbEx);
    } else {
        srb = (PSTORAGE_REQUEST_BLOCK_HEADER)&(fdoExtension->ReleaseQueueSrb);
    }

    KeReleaseSpinLockFromDpcLevel(&(fdoExtension->ReleaseQueueSpinLock));

    NT_ASSERT(irp != NULL);

    irpStack = IoGetNextIrpStackLocation(irp);

    irpStack->MajorFunction = IRP_MJ_SCSI;

    SrbSetOriginalRequest(srb, irp);

    //
    // Store the SRB address in next stack for port driver.
    //

    irpStack->Parameters.Scsi.Srb = (PSCSI_REQUEST_BLOCK)srb;

    //
    // If this device is removable then flush the queue.  This will also
    // release it.
    //

    if (TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA)){
       function = SRB_FUNCTION_FLUSH_QUEUE;
    }
    else {
       function = SRB_FUNCTION_RELEASE_QUEUE;
    }

    if (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
        ((PSTORAGE_REQUEST_BLOCK)srb)->SrbFunction = function;
    } else {
        srb->Function = (UCHAR)function;
    }

    ClassAcquireRemoveLock(Fdo, irp);

    IoSetCompletionRoutine(irp,
                           ClassReleaseQueueCompletion,
                           Fdo,
                           TRUE,
                           TRUE,
                           TRUE);

    IoCallDriver(lowerDevice, irp);

    KeLowerIrql(currentIrql);

    return;

} // end ClassReleaseQueue()

/*++////////////////////////////////////////////////////////////////////////////

ClassReleaseQueueCompletion()

Routine Description:

    This routine is called when an asynchronous I/O request
    which was issused by the class driver completes.  Examples of such requests
    are release queue or START UNIT. This routine releases the queue if
    necessary.  It then frees the context and the IRP.

Arguments:

    DeviceObject - The device object for the logical unit; however since this
        is the top stack location the value is NULL.

    Irp - Supplies a pointer to the Irp to be processed.

    Context - Supplies the context to be used to process this request.

Return Value:

    None.

--*/
NTSTATUS
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassReleaseQueueCompletion(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp,
    PVOID Context
    )
{
    PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
    KIRQL oldIrql;

    BOOLEAN releaseQueueNeeded;

    if (Context == NULL) {
        NT_ASSERT(Context != NULL);
        return STATUS_INVALID_PARAMETER;
    }

    DeviceObject = Context;

    fdoExtension = DeviceObject->DeviceExtension;

    ClassReleaseRemoveLock(DeviceObject, Irp);

    //
    // Grab the spinlock and clear the release queue in progress flag so others
    // can run.  Save (and clear) the state of the release queue needed flag
    // so that we can issue a new release queue outside the spinlock.
    //

    KeAcquireSpinLock(&(fdoExtension->ReleaseQueueSpinLock), &oldIrql);

    releaseQueueNeeded = fdoExtension->ReleaseQueueNeeded;

    fdoExtension->ReleaseQueueNeeded = FALSE;
    fdoExtension->ReleaseQueueInProgress = FALSE;

    KeReleaseSpinLock(&(fdoExtension->ReleaseQueueSpinLock), oldIrql);

    //
    // If we need a release queue then issue one now.  Another processor may
    // have already started one in which case we'll try to issue this one after
    // it is done - but we should never recurse more than one deep.
    //

    if(releaseQueueNeeded) {
        ClasspReleaseQueue(DeviceObject, Irp);
    }

    //
    // Indicate the I/O system should stop processing the Irp completion.
    //

    return STATUS_MORE_PROCESSING_REQUIRED;

} // ClassAsynchronousCompletion()


/*++////////////////////////////////////////////////////////////////////////////

ClassAcquireChildLock()

Routine Description:

    This routine acquires the lock protecting children PDOs.  It may be
    acquired recursively by the same thread, but must be release by the
    thread once for each acquisition.

Arguments:

    FdoExtension - the device whose child list is protected.

Return Value:

    None

--*/
_IRQL_requires_max_(PASSIVE_LEVEL)
VOID
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassAcquireChildLock(
    _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
    )
{
    PAGED_CODE();

    if(FdoExtension->ChildLockOwner != KeGetCurrentThread()) {
        (VOID)KeWaitForSingleObject(&FdoExtension->ChildLock,
                                    Executive, KernelMode,
                                    FALSE, NULL);

        NT_ASSERT(FdoExtension->ChildLockOwner == NULL);
        NT_ASSERT(FdoExtension->ChildLockAcquisitionCount == 0);

        FdoExtension->ChildLockOwner = KeGetCurrentThread();
    } else {
        NT_ASSERT(FdoExtension->ChildLockAcquisitionCount != 0);
    }

    FdoExtension->ChildLockAcquisitionCount++;
    return;
}

/*++////////////////////////////////////////////////////////////////////////////

ClassReleaseChildLock() ISSUE-2000/02/18-henrygab - not documented

Routine Description:

    This routine releases the lock protecting children PDOs.  It must be
    called once for each time ClassAcquireChildLock was called.

Arguments:

    FdoExtension - the device whose child list is protected

Return Value:

    None.

--*/
VOID
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClassReleaseChildLock(
    _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
    )
{
    NT_ASSERT(FdoExtension->ChildLockOwner == KeGetCurrentThread());
    NT_ASSERT(FdoExtension->ChildLockAcquisitionCount != 0);

    FdoExtension->ChildLockAcquisitionCount -= 1;

    if(FdoExtension->ChildLockAcquisitionCount == 0) {
        FdoExtension->ChildLockOwner = NULL;
        KeSetEvent(&FdoExtension->ChildLock, IO_NO_INCREMENT, FALSE);
    }

    return;
} // end ClassReleaseChildLock(

/*++////////////////////////////////////////////////////////////////////////////

ClassAddChild()

Routine Description:

    This routine will insert a new child into the head of the child list.

Arguments:

    Parent - the child's parent (contains the head of the list)
    Child - the child to be inserted.
    AcquireLock - whether the child lock should be acquired (TRUE) or whether
                  it's already been acquired by or on behalf of the caller
                  (FALSE).

Return Value:

    None.

--*/
VOID
ClassAddChild(
    _In_ PFUNCTIONAL_DEVICE_EXTENSION Parent,
    _In_ PPHYSICAL_DEVICE_EXTENSION Child,
    _In_ BOOLEAN AcquireLock
    )
{
    if(AcquireLock) {
        ClassAcquireChildLock(Parent);
    }

    #if DBG
        //
        // Make sure this child's not already in the list.
        //
        {
            PPHYSICAL_DEVICE_EXTENSION testChild;

            for (testChild = Parent->CommonExtension.ChildList;
                 testChild != NULL;
                 testChild = testChild->CommonExtension.ChildList) {

                NT_ASSERT(testChild != Child);
            }
        }
    #endif

    Child->CommonExtension.ChildList = Parent->CommonExtension.ChildList;
    Parent->CommonExtension.ChildList = Child;

    if(AcquireLock) {
        ClassReleaseChildLock(Parent);
    }
    return;
} // end ClassAddChild()

/*++////////////////////////////////////////////////////////////////////////////

ClassRemoveChild()

Routine Description:

    This routine will remove a child from the child list.

Arguments:

    Parent - the parent to be removed from.

    Child - the child to be removed or NULL if the first child should be
            removed.

    AcquireLock - whether the child lock should be acquired (TRUE) or whether
                  it's already been acquired by or on behalf of the caller
                  (FALSE).

Return Value:

    A pointer to the child which was removed or NULL if no such child could
    be found in the list (or if Child was NULL but the list is empty).

--*/
PPHYSICAL_DEVICE_EXTENSION
ClassRemoveChild(
    IN PFUNCTIONAL_DEVICE_EXTENSION Parent,
    IN PPHYSICAL_DEVICE_EXTENSION Child,
    IN BOOLEAN AcquireLock
    )
{
    if(AcquireLock) {
        ClassAcquireChildLock(Parent);
    }

    TRY {
        PCOMMON_DEVICE_EXTENSION previousChild = &Parent->CommonExtension;

        //
        // If the list is empty then bail out now.
        //

        if(Parent->CommonExtension.ChildList == NULL) {
            Child = NULL;
            LEAVE;
        }

        //
        // If the caller specified a child then find the child object before
        // it.  If none was specified then the FDO is the child object before
        // the one we want to remove.
        //

        if(Child != NULL) {

            //
            // Scan through the child list to find the entry which points to
            // this one.
            //

            do {
                NT_ASSERT(previousChild != &Child->CommonExtension);

                if(previousChild->ChildList == Child) {
                    break;
                }

                previousChild = &previousChild->ChildList->CommonExtension;
            } while(previousChild != NULL);

            if(previousChild == NULL) {
                Child = NULL;
                LEAVE;
            }
        }

        //
        // Save the next child away then unlink it from the list.
        //

        Child = previousChild->ChildList;
        previousChild->ChildList = Child->CommonExtension.ChildList;
        Child->CommonExtension.ChildList = NULL;

    } FINALLY {
        if(AcquireLock) {
            ClassReleaseChildLock(Parent);
        }
    }
    return Child;
} // end ClassRemoveChild()


/*++

    ISSUE-2000/02/20-henrygab Not documented ClasspRetryRequestDpc

--*/
VOID
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClasspRetryRequestDpc(
    IN PKDPC Dpc,
    IN PVOID DeferredContext,
    IN PVOID Arg1,
    IN PVOID Arg2
    )
{
    PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
    PCOMMON_DEVICE_EXTENSION commonExtension;
    PCLASS_PRIVATE_FDO_DATA fdoData;
    PCLASS_RETRY_INFO retryList;
    KIRQL irql;
    PDEVICE_OBJECT DeviceObject = (PDEVICE_OBJECT)DeferredContext;

    UNREFERENCED_PARAMETER(Dpc);
    UNREFERENCED_PARAMETER(Arg1);
    UNREFERENCED_PARAMETER(Arg2);

    if (DeferredContext == NULL) {
        NT_ASSERT(DeferredContext != NULL);
        return;
    }

    commonExtension = DeviceObject->DeviceExtension;
    NT_ASSERT(commonExtension->IsFdo);
    fdoExtension = DeviceObject->DeviceExtension;
    fdoData = fdoExtension->PrivateFdoData;


    KeAcquireSpinLock(&fdoData->Retry.Lock, &irql);
    {
        LARGE_INTEGER now;
        KeQueryTickCount(&now);

        //
        // if CurrentTick is less than now
        //      fire another DPC
        // else
        //      retry entire list
        // endif
        //

        if (now.QuadPart < fdoData->Retry.Tick.QuadPart) {

            ClasspRetryDpcTimer(fdoData);
            retryList = NULL;

        } else {

            retryList = fdoData->Retry.ListHead;
            fdoData->Retry.ListHead = NULL;
            fdoData->Retry.Delta.QuadPart = (LONGLONG)0;
            fdoData->Retry.Tick.QuadPart  = (LONGLONG)0;

        }
    }
    KeReleaseSpinLock(&fdoData->Retry.Lock, irql);

    while (retryList != NULL) {

        PIRP irp;


        irp = CONTAINING_RECORD(retryList, IRP, Tail.Overlay.DriverContext[0]);
        TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassRetry:  -- %p\n", irp));
        retryList = retryList->Next;
        #if DBG
            irp->Tail.Overlay.DriverContext[0] = ULongToPtr(0xdddddddd); // invalidate data
            irp->Tail.Overlay.DriverContext[1] = ULongToPtr(0xdddddddd); // invalidate data
            irp->Tail.Overlay.DriverContext[2] = ULongToPtr(0xdddddddd); // invalidate data
            irp->Tail.Overlay.DriverContext[3] = ULongToPtr(0xdddddddd); // invalidate data
        #endif


        if (NO_REMOVE == InterlockedCompareExchange((volatile LONG *)&commonExtension->IsRemoved, REMOVE_PENDING, REMOVE_PENDING)) {

            IoCallDriver(commonExtension->LowerDeviceObject, irp);

        } else {

            PIO_STACK_LOCATION irpStack;

            //
            // Ensure that we don't skip a completion routine (equivalent of sending down a request
            // to a device after it has received a remove and it completes the request. We need to
            // mimic that behavior here).
            //
            IoSetNextIrpStackLocation(irp);

            irpStack = IoGetCurrentIrpStackLocation(irp);

            if (irpStack->MajorFunction == IRP_MJ_SCSI) {

                PSCSI_REQUEST_BLOCK srb = irpStack->Parameters.Scsi.Srb;

                if (srb) {
                    srb->SrbStatus = SRB_STATUS_NO_DEVICE;
                }
            }

            //
            // Ensure that no retries will take place. This takes care of requests that are either
            // not IRP_MJ_SCSI or for ones where the SRB was passed in to the completion routine
            // as a context as opposed to an argument on the IRP stack location.
            //
            irpStack->Parameters.Others.Argument4 = (PVOID)0;

            irp->IoStatus.Status = STATUS_NO_SUCH_DEVICE;
            irp->IoStatus.Information = 0;
            IoCompleteRequest(irp,  IO_NO_INCREMENT);
        }

    }
    return;

} // end ClasspRetryRequestDpc()

/*++

    ISSUE-2000/02/20-henrygab Not documented ClassRetryRequest

--*/
VOID
ClassRetryRequest(
    IN PDEVICE_OBJECT SelfDeviceObject,
    IN PIRP           Irp,
    _In_ _In_range_(0,MAXIMUM_RETRY_FOR_SINGLE_IO_IN_100NS_UNITS) // 100 seconds; already an assert on this...
    IN LONGLONG       TimeDelta100ns // in 100ns units
    )
{
    PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
    PCLASS_PRIVATE_FDO_DATA fdoData;
    PCLASS_RETRY_INFO  retryInfo;
    LARGE_INTEGER      delta;
    KIRQL irql;

    //
    // this checks we aren't destroying irps
    //
    NT_ASSERT(sizeof(CLASS_RETRY_INFO) <= (4*sizeof(PVOID)));

    fdoExtension = SelfDeviceObject->DeviceExtension;

    if (!fdoExtension->CommonExtension.IsFdo) {

        //
        // this debug print/assertion should ALWAYS be investigated.
        // ClassRetryRequest can currently only be used by FDO's
        //

        TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassRetryRequestEx: LOST IRP %p\n", Irp));
        NT_ASSERT(!"ClassRetryRequestEx Called From PDO? LOST IRP");
        return;

    }

    fdoData = fdoExtension->PrivateFdoData;

    if (TimeDelta100ns < 0) {
        NT_ASSERT(!"ClassRetryRequest - must use positive delay");
        TimeDelta100ns *= -1;
    }

    /*
     *  We are going to queue the irp and send it down in a timer DPC.
     *  This means that we may be causing the irp to complete on a different thread than the issuing thread.
     *  So mark the irp pending.
     */
    IoMarkIrpPending(Irp);

    //
    // prepare what we can out of the loop
    //

    retryInfo = (PCLASS_RETRY_INFO)(&Irp->Tail.Overlay.DriverContext[0]);
    RtlZeroMemory(retryInfo, sizeof(CLASS_RETRY_INFO));

    delta.QuadPart = (TimeDelta100ns / fdoData->Retry.Granularity);
    if (TimeDelta100ns % fdoData->Retry.Granularity) {
        delta.QuadPart ++; // round up to next tick
    }
    if (delta.QuadPart == (LONGLONG)0) {
        delta.QuadPart = MINIMUM_RETRY_UNITS;
    }

    //
    // now determine if we should fire another DPC or not
    //

    KeAcquireSpinLock(&fdoData->Retry.Lock, &irql);

    //
    // always add request to the list
    //

    retryInfo->Next = fdoData->Retry.ListHead;
    fdoData->Retry.ListHead = retryInfo;

    if (fdoData->Retry.Delta.QuadPart == (LONGLONG)0) {

        TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassRetry: +++ %p\n", Irp));

        //
        // must be exactly one item on list
        //

        NT_ASSERT(fdoData->Retry.ListHead       != NULL);
        NT_ASSERT(fdoData->Retry.ListHead->Next == NULL);

        //
        // if currentDelta is zero, always fire a DPC
        //

        KeQueryTickCount(&fdoData->Retry.Tick);
        fdoData->Retry.Tick.QuadPart  += delta.QuadPart;
        fdoData->Retry.Delta.QuadPart  = delta.QuadPart;
        ClasspRetryDpcTimer(fdoData);

    } else if (delta.QuadPart > fdoData->Retry.Delta.QuadPart) {

        //
        // if delta is greater than the list's current delta,
        // increase the DPC handling time by difference
        // and update the delta to new larger value
        // allow the DPC to re-fire itself if needed
        //

        TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassRetry:  ++ %p\n", Irp));

        //
        // must be at least two items on list
        //

        NT_ASSERT(fdoData->Retry.ListHead       != NULL);
        NT_ASSERT(fdoData->Retry.ListHead->Next != NULL);

        fdoData->Retry.Tick.QuadPart  -= fdoData->Retry.Delta.QuadPart;
        fdoData->Retry.Tick.QuadPart  += delta.QuadPart;

        fdoData->Retry.Delta.QuadPart  = delta.QuadPart;

    } else {

        //
        // just inserting it on the list was enough
        //

        TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassRetry:  ++ %p\n", Irp));

    }


    KeReleaseSpinLock(&fdoData->Retry.Lock, irql);


} // end ClassRetryRequest()

/*++

    ISSUE-2000/02/20-henrygab Not documented ClasspRetryDpcTimer

--*/
VOID
ClasspRetryDpcTimer(
    IN PCLASS_PRIVATE_FDO_DATA FdoData
    )
{
    LARGE_INTEGER fire;

    NT_ASSERT(FdoData->Retry.Tick.QuadPart != (LONGLONG)0);
    NT_ASSERT(FdoData->Retry.ListHead      != NULL);  // never fire an empty list

    //
    // fire == (CurrentTick - now) * (100ns per tick)
    //
    // NOTE: Overflow is nearly impossible and is ignored here
    //

    KeQueryTickCount(&fire);
    fire.QuadPart =  FdoData->Retry.Tick.QuadPart - fire.QuadPart;
    fire.QuadPart *= FdoData->Retry.Granularity;

    //
    // fire is now multiples of 100ns until should fire the timer.
    // if timer should already have expired, or would fire too quickly,
    // fire it in some arbitrary number of ticks to prevent infinitely
    // recursing.
    //

    if (fire.QuadPart < MINIMUM_RETRY_UNITS) {
        fire.QuadPart = MINIMUM_RETRY_UNITS;
    }

    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,
                "ClassRetry: ======= %I64x ticks\n",
                fire.QuadPart));

    //
    // must use negative to specify relative time to fire
    //

    fire.QuadPart = fire.QuadPart * ((LONGLONG)-1);

    //
    // set the timer, since this is the first addition
    //

    KeSetTimerEx(&FdoData->Retry.Timer, fire, 0, &FdoData->Retry.Dpc);

    return;
} // end ClasspRetryDpcTimer()

NTSTATUS
ClasspInitializeHotplugInfo(
    IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
    )
{
    PCLASS_PRIVATE_FDO_DATA fdoData = FdoExtension->PrivateFdoData;
    DEVICE_REMOVAL_POLICY deviceRemovalPolicy = 0;
    NTSTATUS status;
    ULONG resultLength = 0;
    ULONG writeCacheOverride;

    PAGED_CODE();

    //
    // start with some default settings
    //
    RtlZeroMemory(&(fdoData->HotplugInfo), sizeof(STORAGE_HOTPLUG_INFO));

    //
    // set the size (aka version)
    //

    fdoData->HotplugInfo.Size = sizeof(STORAGE_HOTPLUG_INFO);

    //
    // set if the device has removable media
    //

    if (FdoExtension->DeviceDescriptor->RemovableMedia) {
        fdoData->HotplugInfo.MediaRemovable = TRUE;
    } else {
        fdoData->HotplugInfo.MediaRemovable = FALSE;
    }

    //
    // this refers to devices which, for reasons not yet understood,
    // do not fail PREVENT_MEDIA_REMOVAL requests even though they
    // have no way to lock the media into the drive.  this allows
    // the filesystems to turn off delayed-write caching for these
    // devices as well.
    //

    if (TEST_FLAG(FdoExtension->PrivateFdoData->HackFlags,
                  FDO_HACK_CANNOT_LOCK_MEDIA)) {
        fdoData->HotplugInfo.MediaHotplug = TRUE;
    } else {
        fdoData->HotplugInfo.MediaHotplug = FALSE;
    }

    //
    // Query the default removal policy from the kernel
    //

    status = IoGetDeviceProperty(FdoExtension->LowerPdo,
                                 DevicePropertyRemovalPolicy,
                                 sizeof(DEVICE_REMOVAL_POLICY),
                                 (PVOID)&deviceRemovalPolicy,
                                 &resultLength);
    if (!NT_SUCCESS(status)) {
        return status;
    }

    if (resultLength != sizeof(DEVICE_REMOVAL_POLICY)) {
        return STATUS_UNSUCCESSFUL;
    }

    //
    // Look into the registry to see if the user has chosen
    // to override the default setting for the removal policy.
    // User can override only if the default removal policy is
    // orderly or suprise removal.

    if ((deviceRemovalPolicy == RemovalPolicyExpectOrderlyRemoval) ||
        (deviceRemovalPolicy == RemovalPolicyExpectSurpriseRemoval)) {

        DEVICE_REMOVAL_POLICY userRemovalPolicy = 0;

        ClassGetDeviceParameter(FdoExtension,
                                CLASSP_REG_SUBKEY_NAME,
                                CLASSP_REG_REMOVAL_POLICY_VALUE_NAME,
                                (PULONG)&userRemovalPolicy);

        //
        // Validate the override value and use it only if it is an
        // allowed value.
        //
        if ((userRemovalPolicy == RemovalPolicyExpectOrderlyRemoval) ||
            (userRemovalPolicy == RemovalPolicyExpectSurpriseRemoval)) {
            deviceRemovalPolicy = userRemovalPolicy;
        }
    }

    //
    // use this info to set the DeviceHotplug setting
    // don't rely on DeviceCapabilities, since it can't properly
    // determine device relations, etc.  let the kernel figure this
    // stuff out instead.
    //

    if (deviceRemovalPolicy == RemovalPolicyExpectSurpriseRemoval) {
        fdoData->HotplugInfo.DeviceHotplug = TRUE;
    } else {
        fdoData->HotplugInfo.DeviceHotplug = FALSE;
    }

    //
    // this refers to the *filesystem* caching, but has to be included
    // here since it's a per-device setting.  this may change to be
    // stored by the system in the future.
    //

    writeCacheOverride = FALSE;
    ClassGetDeviceParameter(FdoExtension,
                            CLASSP_REG_SUBKEY_NAME,
                            CLASSP_REG_WRITE_CACHE_VALUE_NAME,
                            &writeCacheOverride);

    if (writeCacheOverride) {
        fdoData->HotplugInfo.WriteCacheEnableOverride = TRUE;
    } else {
        fdoData->HotplugInfo.WriteCacheEnableOverride = FALSE;
    }

    return STATUS_SUCCESS;
}

VOID
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClasspScanForClassHacks(
    IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
    IN ULONG_PTR Data
    )
{
    PAGED_CODE();

    //
    // remove invalid flags and save
    //

    CLEAR_FLAG(Data, FDO_HACK_INVALID_FLAGS);
    SET_FLAG(FdoExtension->PrivateFdoData->HackFlags, Data);
    return;
}

VOID
ClasspScanForSpecialInRegistry(
    IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
    )
{
    HANDLE             deviceParameterHandle; // device instance key
    HANDLE             classParameterHandle; // classpnp subkey
    OBJECT_ATTRIBUTES  objectAttributes = {0};
    UNICODE_STRING     subkeyName;
    NTSTATUS           status;

    //
    // seeded in the ENUM tree by ClassInstaller
    //
    ULONG deviceHacks;
    RTL_QUERY_REGISTRY_TABLE queryTable[2] = {0}; // null terminated array

    PAGED_CODE();

    deviceParameterHandle = NULL;
    classParameterHandle = NULL;
    deviceHacks = 0;

    status = IoOpenDeviceRegistryKey(FdoExtension->LowerPdo,
                                     PLUGPLAY_REGKEY_DEVICE,
                                     KEY_WRITE,
                                     &deviceParameterHandle
                                     );

    if (!NT_SUCCESS(status)) {
        goto cleanupScanForSpecial;
    }

    RtlInitUnicodeString(&subkeyName, CLASSP_REG_SUBKEY_NAME);
    InitializeObjectAttributes(&objectAttributes,
                               &subkeyName,
                               OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
                               deviceParameterHandle,
                               NULL
                               );

    status = ZwOpenKey( &classParameterHandle,
                        KEY_READ,
                        &objectAttributes
                        );

    if (!NT_SUCCESS(status)) {
        goto cleanupScanForSpecial;
    }

    //
    // Setup the structure to read
    //

    queryTable[0].Flags         = RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_TYPECHECK;
    queryTable[0].Name          = CLASSP_REG_HACK_VALUE_NAME;
    queryTable[0].EntryContext  = &deviceHacks;
    queryTable[0].DefaultType   = (REG_DWORD << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_DWORD;
    queryTable[0].DefaultData   = &deviceHacks;
    queryTable[0].DefaultLength = 0;

    //
    // read values
    //

    status = RtlQueryRegistryValues(RTL_REGISTRY_HANDLE,
                                    (PWSTR)classParameterHandle,
                                    &queryTable[0],
                                    NULL,
                                    NULL
                                    );
    if (!NT_SUCCESS(status)) {
        goto cleanupScanForSpecial;
    }

    //
    // remove unknown values and save...
    //

    CLEAR_FLAG(deviceHacks, FDO_HACK_INVALID_FLAGS);
    SET_FLAG(FdoExtension->PrivateFdoData->HackFlags, deviceHacks);


cleanupScanForSpecial:

    if (deviceParameterHandle) {
        ZwClose(deviceParameterHandle);
    }

    if (classParameterHandle) {
        ZwClose(classParameterHandle);
    }

    //
    // we should modify the system hive to include another key for us to grab
    // settings from.  in this case:  Classpnp\HackFlags
    //
    // the use of a DWORD value for the HackFlags allows 32 hacks w/o
    // significant use of the registry, and also reduces OEM exposure.
    //
    // definition of bit flags:
    //   0x00000001 -- Device succeeds PREVENT_MEDIUM_REMOVAL, but
    //                 cannot actually prevent removal.
    //   0x00000002 -- Device hard-hangs or times out for GESN requests.
    //   0xfffffffc -- Currently reserved, may be used later.
    //

    return;
}

/*++////////////////////////////////////////////////////////////////////////////

ClasspUpdateDiskProperties()

Routine Description:

    This routine will send IOCTL_DISK_UPDATE_PROPERTIES to top of stack - Partition Manager
    to invalidate the cached geometry.

Arguments:

    Fdo - The device object whose capacity needs to be verified.

Return Value:

    NTSTATUS

--*/

VOID
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
ClasspUpdateDiskProperties(
    IN PDEVICE_OBJECT Fdo,
    IN PVOID Context
    )
{
    PDEVICE_OBJECT topOfStack;
    IO_STATUS_BLOCK ioStatus;
    NTSTATUS status = STATUS_SUCCESS;
    KEVENT event;
    PIRP irp = NULL;
    PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Fdo->DeviceExtension;
    PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
    PIO_WORKITEM WorkItem = (PIO_WORKITEM)Context;

    KeInitializeEvent(&event, SynchronizationEvent, FALSE);

    topOfStack = IoGetAttachedDeviceReference(Fdo);

    //
    // Send down irp to update properties
    //

    irp = IoBuildDeviceIoControlRequest(
                    IOCTL_DISK_UPDATE_PROPERTIES,
                    topOfStack,
                    NULL,
                    0,
                    NULL,
                    0,
                    FALSE,
                    &event,
                    &ioStatus);

    if (irp != NULL) {


        status = IoCallDriver(topOfStack, irp);
        if (status == STATUS_PENDING) {
            (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
            status = ioStatus.Status;
        }

    } else {
        status = STATUS_INSUFFICIENT_RESOURCES;
    }

    InterlockedExchange((volatile LONG *)&fdoData->UpdateDiskPropertiesWorkItemActive, 0);

    if (!NT_SUCCESS(status)) {
        TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, "ClasspUpdateDiskProperties: Disk property update for fdo %p failed with status 0x%X.\n", Fdo, status));
    } else {
        TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "ClasspUpdateDiskProperties: Drive capacity has changed for %p.\n", Fdo));
    }
    ObDereferenceObject(topOfStack);

    if (WorkItem != NULL) {
        IoFreeWorkItem(WorkItem);
    }

    return;
}

BOOLEAN
InterpretSenseInfoWithoutHistory(
    _In_ PDEVICE_OBJECT Fdo,
    _In_opt_ PIRP OriginalRequest,
    _In_ PSCSI_REQUEST_BLOCK Srb,
         UCHAR MajorFunctionCode,
         ULONG IoDeviceCode,
         ULONG PreviousRetryCount,
    _Out_ NTSTATUS * Status,
    _Out_opt_ _Deref_out_range_(0,MAXIMUM_RETRY_FOR_SINGLE_IO_IN_100NS_UNITS)
         LONGLONG * RetryIn100nsUnits
    )
{
    PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
    PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
    LONGLONG tmpRetry = 0;
    BOOLEAN retry = FALSE;

    if (fdoData->InterpretSenseInfo != NULL)
    {
        SCSI_REQUEST_BLOCK tempSrb = {0};
        PSCSI_REQUEST_BLOCK srbPtr = Srb;

        // SAL annotations and ClassInitializeEx() both validate this
        NT_ASSERT(fdoData->InterpretSenseInfo->Interpret != NULL);

        //
        // If class driver does not support extended SRB and this is
        // an extended SRB, convert to legacy SRB and pass to class
        // driver.
        //
        if ((Srb->Function == SRB_FUNCTION_STORAGE_REQUEST_BLOCK) &&
            ((fdoExtension->CommonExtension.DriverExtension->SrbSupport &
              CLASS_SRB_STORAGE_REQUEST_BLOCK) == 0)) {
            ClasspConvertToScsiRequestBlock(&tempSrb, (PSTORAGE_REQUEST_BLOCK)Srb);
            srbPtr = &tempSrb;
        }

        retry = fdoData->InterpretSenseInfo->Interpret(Fdo,
                                                       OriginalRequest,
                                                       srbPtr,
                                                       MajorFunctionCode,
                                                       IoDeviceCode,
                                                       PreviousRetryCount,
                                                       NULL,
                                                       Status,
                                                       &tmpRetry);
    }
    else
    {
        ULONG seconds = 0;

        retry = ClassInterpretSenseInfo(Fdo,
                                        Srb,
                                        MajorFunctionCode,
                                        IoDeviceCode,
                                        PreviousRetryCount,
                                        Status,
                                        &seconds);
        tmpRetry = ((LONGLONG)seconds) * 1000 * 1000 * 10;
    }


    if (RetryIn100nsUnits != NULL)
    {
        *RetryIn100nsUnits = tmpRetry;
    }
    return retry;
}

VOID
ClasspGetInquiryVpdSupportInfo(
    _Inout_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
    )
{
    NTSTATUS                  status;
    PCOMMON_DEVICE_EXTENSION  commonExtension = (PCOMMON_DEVICE_EXTENSION)FdoExtension;
    SCSI_REQUEST_BLOCK        srb = {0};
    PCDB                      cdb;
    PVPD_SUPPORTED_PAGES_PAGE supportedPages = NULL;
    UCHAR                     bufferLength = VPD_MAX_BUFFER_SIZE;
    ULONG                     allocationBufferLength = bufferLength;
    UCHAR srbExBuffer[CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE] = {0};
    PSTORAGE_REQUEST_BLOCK_HEADER srbHeader;

#if defined(_ARM_) || defined(_ARM64_)
    //
    // ARM has specific alignment requirements, although this will not have a functional impact on x86 or amd64
    // based platforms. We are taking the conservative approach here.
    //
    allocationBufferLength = ALIGN_UP_BY(allocationBufferLength,KeGetRecommendedSharedDataAlignment());
    supportedPages = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned,
                                           allocationBufferLength,
                                           '3CcS'
                                           );

#else
    supportedPages = ExAllocatePoolWithTag(NonPagedPoolNx,
                                           bufferLength,
                                           '3CcS'
                                           );
#endif

    if (supportedPages == NULL) {
        // memory allocation failure.
        return;
    }

    RtlZeroMemory(supportedPages, allocationBufferLength);

    // prepare the Srb
    if (FdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {

#ifdef _MSC_VER
        #pragma prefast(suppress:26015, "InitializeStorageRequestBlock ensures buffer access is bounded")
#endif
        status = InitializeStorageRequestBlock((PSTORAGE_REQUEST_BLOCK)srbExBuffer,
                                               STORAGE_ADDRESS_TYPE_BTL8,
                                               sizeof(srbExBuffer),
                                               1,
                                               SrbExDataTypeScsiCdb16);

        if (!NT_SUCCESS(status)) {
            //
            // Should not happen. Revert to legacy SRB.
            NT_ASSERT(FALSE);
            srb.Length = SCSI_REQUEST_BLOCK_SIZE;
            srb.Function = SRB_FUNCTION_EXECUTE_SCSI;
            srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)&srb;
        } else {
            ((PSTORAGE_REQUEST_BLOCK)srbExBuffer)->SrbFunction = SRB_FUNCTION_EXECUTE_SCSI;
            srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)srbExBuffer;
        }

    } else {
        srb.Length = SCSI_REQUEST_BLOCK_SIZE;
        srb.Function = SRB_FUNCTION_EXECUTE_SCSI;
        srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)&srb;
    }

    SrbSetTimeOutValue(srbHeader, FdoExtension->TimeOutValue);
    SrbSetRequestTag(srbHeader, SP_UNTAGGED);
    SrbSetRequestAttribute(srbHeader, SRB_SIMPLE_TAG_REQUEST);
    SrbAssignSrbFlags(srbHeader, FdoExtension->SrbFlags);
    SrbSetCdbLength(srbHeader, 6);

    cdb = SrbGetCdb(srbHeader);
    cdb->CDB6INQUIRY3.OperationCode = SCSIOP_INQUIRY;
    cdb->CDB6INQUIRY3.EnableVitalProductData = 1;       //EVPD bit
    cdb->CDB6INQUIRY3.PageCode = VPD_SUPPORTED_PAGES;
    cdb->CDB6INQUIRY3.AllocationLength = bufferLength;  //AllocationLength field in CDB6INQUIRY3 is only one byte.

    status = ClassSendSrbSynchronous(commonExtension->DeviceObject,
                                     (PSCSI_REQUEST_BLOCK)srbHeader,
                                     supportedPages,
                                     allocationBufferLength,
                                     FALSE);

    //
    // Handle the case where we get back STATUS_DATA_OVERRUN b/c the input
    // buffer was larger than necessary.
    //
    if (status == STATUS_DATA_OVERRUN && SrbGetDataTransferLength(srbHeader) < bufferLength)
    {
        status = STATUS_SUCCESS;
    }

    if ( NT_SUCCESS(status) &&
         (supportedPages->PageLength > 0) &&
         (supportedPages->SupportedPageList[0] > 0) ) {
        // ataport treats all INQUIRY command as standard INQUIRY command, thus fills invalid info
        // If VPD INQUIRY is supported, the first page reported (additional length field for standard INQUIRY data) should be '00'
        status = STATUS_NOT_SUPPORTED;
    }

    if (NT_SUCCESS(status)) {
        int i;

        for (i = 0; i < supportedPages->PageLength; i++) {
            if ( (i > 0) && (supportedPages->SupportedPageList[i] <= supportedPages->SupportedPageList[i - 1]) ) {
                // shall be in ascending order beginning with page code 00h.
                FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits = FALSE;
                FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockLimits = FALSE;
                FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceCharacteristics = FALSE;
                FdoExtension->FunctionSupportInfo->ValidInquiryPages.LBProvisioning = FALSE;


                break;
            }
            switch (supportedPages->SupportedPageList[i]) {
                case VPD_THIRD_PARTY_COPY:
                    FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits = TRUE;
                    break;

                case VPD_BLOCK_LIMITS:
                    FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockLimits = TRUE;
                    break;

                case VPD_BLOCK_DEVICE_CHARACTERISTICS:
                    FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceCharacteristics = TRUE;
                    break;

                case VPD_LOGICAL_BLOCK_PROVISIONING:
                    FdoExtension->FunctionSupportInfo->ValidInquiryPages.LBProvisioning = TRUE;
                    break;

            }
        }
    }

    ExFreePool(supportedPages);

    return;
}

//
// ClasspGetLBProvisioningInfo
//
//  Description: This function does the work to get Logical Block Provisioning
//      (LBP) info for a given LUN, including UNMAP support and parameters.  It
//      will attempt to get both the Logical Block Provisioning (0xB2) VPD page
//      and the Block Limits (0xB0) VPD page and cache the relevant values in
//      the given FDO extension.
//
//      After calling this function, you can use the ClasspIsThinProvisioned()
//      function to determine if the device is thinly provisioned and the
//      ClasspSupportsUnmap() function to determine if the device supports the
//      UNMAP command.
//
//  Arguments:
//      - FdoExtension: The FDO extension associated with the LUN for which Thin
//          Provisioning info is desired.  The Thin Provisioning info is stored
//          in the FunctionSupportInfo member of this FDO extension.
//
// Returns:
//      - STATUS_INVALID_PARAMETER if the given FDO extension has not been
//          allocated properly.
//      - STATUS_INSUFFICIENT_RESOURCES if this function was unable to allocate
//          an SRB used to get the necessary VPD pages.
//      - STATUS_SUCCESS in all other cases.  If any of the incidental functions
//          don't return STATUS_SUCCESS this function will just assume Thin
//          Provisioning is not enabled and return STATUS_SUCCESS.
//
NTSTATUS
ClasspGetLBProvisioningInfo(
    _Inout_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
    )
{
    PSCSI_REQUEST_BLOCK srb = NULL;
    ULONG               srbSize = 0;

    //
    // Make sure we actually have data structures to work with.
    //
    if (FdoExtension == NULL ||
        FdoExtension->FunctionSupportInfo == NULL) {
        return STATUS_INVALID_PARAMETER;
    }

    //
    // Allocate an SRB for querying the device for LBP-related info if either
    // the Logical Block Provisioning (0xB2) or Block Limits (0xB0) VPD page
    // exists.
    //
    if ((FdoExtension->FunctionSupportInfo->ValidInquiryPages.LBProvisioning == TRUE) ||
        (FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockLimits == TRUE)) {

        if ((FdoExtension->AdapterDescriptor != NULL) &&
            (FdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK)) {
            srbSize = CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE;
        } else {
            srbSize = sizeof(SCSI_REQUEST_BLOCK);
        }

        srb = ExAllocatePoolWithTag(NonPagedPoolNx, srbSize, '0DcS');

        if (srb == NULL) {
            return STATUS_INSUFFICIENT_RESOURCES;
        }
    }

    //
    // Get the Logical Block Provisioning VPD page (0xB2).  This function will
    // set some default values if the LBP VPD page is not supported.
    //
    ClasspDeviceGetLBProvisioningVPDPage(FdoExtension->DeviceObject, srb);


    //
    // Get the Block Limits VPD page (0xB0), which may override the default
    // UNMAP parameter values set above.
    //
    ClasspDeviceGetBlockLimitsVPDPage(FdoExtension,
                                      srb,
                                      srbSize,
                                      &FdoExtension->FunctionSupportInfo->BlockLimitsData);

    FREE_POOL(srb);

    return STATUS_SUCCESS;
}



_IRQL_requires_(PASSIVE_LEVEL)
_IRQL_requires_same_
NTSTATUS
ClassDetermineTokenOperationCommandSupport(
    _In_ PDEVICE_OBJECT DeviceObject
    )

/*++

Routine Description:

    This routine determines if Token Operation and Receive ROD Token Information commands
    are supported by this device and updates the internal structures to reflect this.
    In addition, it also queries the registry to determine the maximum listIdentifier
    to use for TokenOperation commands.

    This function must be called at IRQL == PASSIVE_LEVEL.

Arguments:

    DeviceObject - The device object for which we want to determine command support.

Return Value:

    Nothing

--*/

{
    NTSTATUS status = STATUS_SUCCESS;

    PAGED_CODE();

    TracePrint((TRACE_LEVEL_VERBOSE,
                TRACE_FLAG_PNP,
                "ClassDetermineTokenOperationCommandSupport (%p): Entering function.\n",
                DeviceObject));

    //
    // Send down Inquiry for VPD_THIRD_PARTY_COPY_PAGE and cache away the device parameters
    // from WINDOWS_BLOCK_DEVICE_TOKEN_LIMITS_DESCRIPTOR.
    //
    status = ClasspGetBlockDeviceTokenLimitsInfo(DeviceObject);

    if (NT_SUCCESS(status)) {

        ULONG maxListIdentifier = MaxTokenOperationListIdentifier;

        //
        // Query the maximum list identifier to use for TokenOperation commands.
        //
        if (NT_SUCCESS(ClasspGetMaximumTokenListIdentifier(DeviceObject, REG_DISK_CLASS_CONTROL, &maxListIdentifier))) {
            if (maxListIdentifier >= MIN_TOKEN_LIST_IDENTIFIERS) {

                NT_ASSERT(maxListIdentifier <= MAX_TOKEN_LIST_IDENTIFIERS);
                MaxTokenOperationListIdentifier = maxListIdentifier;
            }
        }

    }

    TracePrint((TRACE_LEVEL_VERBOSE,
                TRACE_FLAG_PNP,
                "ClassDetermineTokenOperationCommandSupport (%p): Exiting function with status %x.\n",
                DeviceObject,
                status));

    return status;
}


_IRQL_requires_same_
NTSTATUS
ClasspGetBlockDeviceTokenLimitsInfo(
    _Inout_ PDEVICE_OBJECT DeviceObject
    )

/*++

Routine Description:

    This routine does the work to get the Block Device Token Limits info for a given LUN.
    This is done by sending Inquiry for the Third Party Copy VPD page.

Arguments:

    DeviceObject - The FDO associated with the LUN for which Block Device Token
                       limits info is desired.

Return Value:

    STATUS_DEVICE_FEATURE_NOT_SUPPORTED if either the Inquiry fails or validations fail.
    STATUS_SUCCESS otherwise.

--*/

{
    PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
    NTSTATUS status;
    PSCSI_REQUEST_BLOCK srb = NULL;
    ULONG srbSize = 0;
    USHORT pageLength = 0;
    USHORT descriptorLength = 0;
    PVOID dataBuffer = NULL;
    UCHAR bufferLength = VPD_MAX_BUFFER_SIZE;
    ULONG allocationBufferLength = bufferLength;
    PCDB cdb;
    ULONG dataTransferLength = 0;
    PVPD_THIRD_PARTY_COPY_PAGE operatingParameters = NULL;
    PWINDOWS_BLOCK_DEVICE_TOKEN_LIMITS_DESCRIPTOR blockDeviceTokenLimits = NULL;

    TracePrint((TRACE_LEVEL_VERBOSE,
                TRACE_FLAG_PNP,
                "ClasspGetBlockDeviceTokenLimitsInfo (%p): Entering function.\n",
                DeviceObject));

    //
    // Allocate an SRB for querying the device for LBP-related info.
    //
    if ((fdoExtension->AdapterDescriptor != NULL) &&
        (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK)) {
        srbSize = CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE;
    } else {
        srbSize = sizeof(SCSI_REQUEST_BLOCK);
    }

    srb = ExAllocatePoolWithTag(NonPagedPoolNx, srbSize, CLASSPNP_POOL_TAG_SRB);

    if (!srb) {

        TracePrint((TRACE_LEVEL_ERROR,
                    TRACE_FLAG_PNP,
                    "ClasspGetBlockDeviceTokenLimitsInfo (%p): Couldn't allocate SRB.\n",
                    DeviceObject));

        status = STATUS_INSUFFICIENT_RESOURCES;
        goto __ClasspGetBlockDeviceTokenLimitsInfo_Exit;
    }

#if defined(_ARM_) || defined(_ARM64_)
    //
    // ARM has specific alignment requirements, although this will not have a functional impact on x86 or amd64
    // based platforms. We are taking the conservative approach here.
    //
    allocationBufferLength = ALIGN_UP_BY(allocationBufferLength, KeGetRecommendedSharedDataAlignment());
    dataBuffer = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned, allocationBufferLength, CLASSPNP_POOL_TAG_VPD);
#else
    dataBuffer = ExAllocatePoolWithTag(NonPagedPoolNx, bufferLength, CLASSPNP_POOL_TAG_VPD);
#endif

    if (!dataBuffer) {

        TracePrint((TRACE_LEVEL_ERROR,
                    TRACE_FLAG_PNP,
                    "ClasspGetBlockDeviceTokenLimitsInfo (%p): Couldn't allocate dataBuffer.\n",
                    DeviceObject));

        status = STATUS_INSUFFICIENT_RESOURCES;
        goto __ClasspGetBlockDeviceTokenLimitsInfo_Exit;
    }

    operatingParameters = (PVPD_THIRD_PARTY_COPY_PAGE)dataBuffer;

    RtlZeroMemory(dataBuffer, allocationBufferLength);

    if ((fdoExtension->AdapterDescriptor != NULL) &&
        (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK)) {
        status = InitializeStorageRequestBlock((PSTORAGE_REQUEST_BLOCK)srb,
                                               STORAGE_ADDRESS_TYPE_BTL8,
                                               CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE,
                                               1,
                                               SrbExDataTypeScsiCdb16);
        if (NT_SUCCESS(status)) {

            ((PSTORAGE_REQUEST_BLOCK)srb)->SrbFunction = SRB_FUNCTION_EXECUTE_SCSI;

        } else {

            //
            // Should not occur. Revert to legacy SRB.
            //
            NT_ASSERT(FALSE);

            TracePrint((TRACE_LEVEL_WARNING,
                        TRACE_FLAG_PNP,
                        "ClasspGetBlockDeviceTokenLimitsInfo (%p): Falling back to using SRB (instead of SRB_EX).\n",
                        DeviceObject));

            RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK));
            srb->Length = sizeof(SCSI_REQUEST_BLOCK);
            srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
        }
    } else {

        RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK));
        srb->Length = sizeof(SCSI_REQUEST_BLOCK);
        srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
    }

    SrbSetTimeOutValue(srb, fdoExtension->TimeOutValue);
    SrbSetRequestTag(srb, SP_UNTAGGED);
    SrbSetRequestAttribute(srb, SRB_SIMPLE_TAG_REQUEST);
    SrbAssignSrbFlags(srb, fdoExtension->SrbFlags);

    SrbSetCdbLength(srb, 6);

    cdb = SrbGetCdb(srb);
    cdb->CDB6INQUIRY3.OperationCode = SCSIOP_INQUIRY;
    cdb->CDB6INQUIRY3.EnableVitalProductData = 1;
    cdb->CDB6INQUIRY3.PageCode = VPD_THIRD_PARTY_COPY;
    cdb->CDB6INQUIRY3.AllocationLength = bufferLength;

    status = ClassSendSrbSynchronous(fdoExtension->DeviceObject,
                                     srb,
                                     dataBuffer,
                                     allocationBufferLength,
                                     FALSE);

    //
    // Handle the case where we get back STATUS_DATA_OVERRUN because the input
    // buffer was larger than necessary.
    //
    dataTransferLength = SrbGetDataTransferLength(srb);

    if (status == STATUS_DATA_OVERRUN && dataTransferLength < bufferLength) {

        status = STATUS_SUCCESS;
    }

    if (NT_SUCCESS(status)) {

        REVERSE_BYTES_SHORT(&pageLength, &operatingParameters->PageLength);

    } else {
        TracePrint((TRACE_LEVEL_ERROR,
                    TRACE_FLAG_PNP,
                    "ClasspGetBlockDeviceTokenLimitsInfo (%p): Inquiry for TPC VPD failed with %x.\n",
                    DeviceObject,
                    status));
    }

    if ((NT_SUCCESS(status)) &&
        (pageLength >= sizeof(WINDOWS_BLOCK_DEVICE_TOKEN_LIMITS_DESCRIPTOR))) {

        USHORT descriptorType;

        blockDeviceTokenLimits = (PWINDOWS_BLOCK_DEVICE_TOKEN_LIMITS_DESCRIPTOR)operatingParameters->ThirdPartyCopyDescriptors;
        REVERSE_BYTES_SHORT(&descriptorType, &blockDeviceTokenLimits->DescriptorType);
        REVERSE_BYTES_SHORT(&descriptorLength, &blockDeviceTokenLimits->DescriptorLength);

        if ((descriptorType == BLOCK_DEVICE_TOKEN_LIMITS_DESCRIPTOR_TYPE_WINDOWS) &&
            (VPD_PAGE_HEADER_SIZE + descriptorLength == sizeof(WINDOWS_BLOCK_DEVICE_TOKEN_LIMITS_DESCRIPTOR))) {

            fdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits = TRUE;

            REVERSE_BYTES_SHORT(&fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumRangeDescriptors, &blockDeviceTokenLimits->MaximumRangeDescriptors);
            REVERSE_BYTES(&fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumInactivityTimer, &blockDeviceTokenLimits->MaximumInactivityTimer);
            REVERSE_BYTES(&fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.DefaultInactivityTimer, &blockDeviceTokenLimits->DefaultInactivityTimer);
            REVERSE_BYTES_QUAD(&fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumTokenTransferSize, &blockDeviceTokenLimits->MaximumTokenTransferSize);
            REVERSE_BYTES_QUAD(&fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount, &blockDeviceTokenLimits->OptimalTransferCount);

            TracePrint((TRACE_LEVEL_INFORMATION,
                        TRACE_FLAG_PNP,
                        "ClasspGetBlockDeviceTokenLimitsInfo (%p): %s %s (rev %s) reported following parameters: \
                        \n\t\t\tMaxRangeDescriptors: %u\n\t\t\tMaxIAT: %u\n\t\t\tDefaultIAT: %u \
                        \n\t\t\tMaxTokenTransferSize: %I64u\n\t\t\tOptimalTransferCount: %I64u\n\t\t\tOptimalTransferLengthGranularity: %u \
                        \n\t\t\tOptimalTransferLength: %u\n\t\t\tMaxTransferLength: %u\n",
                        DeviceObject,
                        (PCSZ)(((PUCHAR)fdoExtension->DeviceDescriptor) + fdoExtension->DeviceDescriptor->VendorIdOffset),
                        (PCSZ)(((PUCHAR)fdoExtension->DeviceDescriptor) + fdoExtension->DeviceDescriptor->ProductIdOffset),
                        (PCSZ)(((PUCHAR)fdoExtension->DeviceDescriptor) + fdoExtension->DeviceDescriptor->ProductRevisionOffset),
                        fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumRangeDescriptors,
                        fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumInactivityTimer,
                        fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.DefaultInactivityTimer,
                        fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumTokenTransferSize,
                        fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount,
                        fdoExtension->FunctionSupportInfo->BlockLimitsData.OptimalTransferLengthGranularity,
                        fdoExtension->FunctionSupportInfo->BlockLimitsData.OptimalTransferLength,
                        fdoExtension->FunctionSupportInfo->BlockLimitsData.MaximumTransferLength));
        } else {

            TracePrint((TRACE_LEVEL_WARNING,
                        TRACE_FLAG_PNP,
                        "ClasspGetBlockDeviceTokenLimitsInfo (%p): ThirdPartyCopy VPD data doesn't have Windows OffloadDataTransfer descriptor.\n",
                        DeviceObject));

            fdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits = FALSE;
            status = STATUS_DEVICE_FEATURE_NOT_SUPPORTED;
        }
    } else {

        TracePrint((TRACE_LEVEL_WARNING,
                    TRACE_FLAG_PNP,
                    "ClasspGetBlockDeviceTokenLimitsInfo (%p): TPC VPD data didn't return TPC descriptors of interest.\n",
                    DeviceObject));

        fdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits = FALSE;
        status = STATUS_DEVICE_FEATURE_NOT_SUPPORTED;
    }

__ClasspGetBlockDeviceTokenLimitsInfo_Exit:

    FREE_POOL(dataBuffer);
    FREE_POOL(srb);
    fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus = status;

    TracePrint((TRACE_LEVEL_VERBOSE,
                TRACE_FLAG_PNP,
                "ClasspGetBlockDeviceTokenLimitsInfo (%p): Exiting function with status %x.\n",
                DeviceObject,
                status));

    return status;
}


_IRQL_requires_max_(APC_LEVEL)
_IRQL_requires_min_(PASSIVE_LEVEL)
_IRQL_requires_same_
NTSTATUS
ClassDeviceProcessOffloadRead(
    _In_ PDEVICE_OBJECT DeviceObject,
    _In_ PIRP Irp,
    _Inout_ PSCSI_REQUEST_BLOCK Srb
    )

/*++

Routine Description:

    This routine services IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES for CopyOffload
    Read. If the device supports copy offload, it performs the translation of the
    IOCTL into the appropriate SCSI commands to complete the operation.

    This function must be called at IRQL < DISPATCH_LEVEL.

Arguments:

    DeviceObject - Supplies the device object associated with this request
    Irp - The IRP to be processed
    Srb - An SRB that can be optimally used to process this request

Return Value:

    NTSTATUS code

--*/

{
    PIO_STACK_LOCATION irpStack;
    NTSTATUS status;
    PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes;
    PDEVICE_DSM_OFFLOAD_READ_PARAMETERS offloadReadParameters;
    PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;

    UNREFERENCED_PARAMETER(Srb);

    //
    // This function must be called at less than dispatch level.
    // Fail if IRQL >= DISPATCH_LEVEL.
    //
    PAGED_CODE();

    TracePrint((TRACE_LEVEL_VERBOSE,
                TRACE_FLAG_IOCTL,
                "ClassDeviceProcessOffloadRead (%p): Entering function. Irp %p\n",
                DeviceObject,
                Irp));


    irpStack = IoGetCurrentIrpStackLocation (Irp);

    //
    // Validations
    //
    status = ClasspValidateOffloadSupported(DeviceObject, Irp);
    if (!NT_SUCCESS(status)) {
        goto __ClassDeviceProcessOffloadRead_CompleteAndExit;
    }

    if (KeGetCurrentIrql() >= DISPATCH_LEVEL) {

        NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
        TracePrint((TRACE_LEVEL_ERROR,
                    TRACE_FLAG_IOCTL,
                    "ClassDeviceProcessOffloadRead (%p): Called at raised IRQL.\n",
                    DeviceObject));

        status = STATUS_INVALID_LEVEL;
        goto __ClassDeviceProcessOffloadRead_CompleteAndExit;
    }

    //
    // Ensure that this DSM IOCTL was generated in kernel
    //
    if (Irp->RequestorMode != KernelMode) {

        TracePrint((TRACE_LEVEL_ERROR,
                    TRACE_FLAG_IOCTL,
                    "ClassDeviceProcessOffloadRead (%p): Called from user mode.\n",
                    DeviceObject));

        status = STATUS_ACCESS_DENIED;
        goto __ClassDeviceProcessOffloadRead_CompleteAndExit;
    }

    status = ClasspValidateOffloadInputParameters(DeviceObject, Irp);
    if (!NT_SUCCESS(status)) {
        goto __ClassDeviceProcessOffloadRead_CompleteAndExit;
    }

    dsmAttributes = Irp->AssociatedIrp.SystemBuffer;

    //
    // Validate that we were passed in correct sized parameter block.
    //
    if (dsmAttributes->ParameterBlockLength < sizeof(DEVICE_DSM_OFFLOAD_READ_PARAMETERS)) {

        TracePrint((TRACE_LEVEL_ERROR,
                    TRACE_FLAG_IOCTL,
                    "ClassDeviceProcessOffloadRead (%p): Parameter block size (%u) too small. Required %u.\n",
                    DeviceObject,
                    dsmAttributes->ParameterBlockLength,
                    sizeof(DEVICE_DSM_OFFLOAD_READ_PARAMETERS)));

        status = STATUS_INVALID_PARAMETER;
        goto __ClassDeviceProcessOffloadRead_CompleteAndExit;
    }

    offloadReadParameters = Add2Ptr(dsmAttributes, dsmAttributes->ParameterBlockOffset);

    fdoExtension = DeviceObject->DeviceExtension;

    //
    // If the request TTL is greater than the max supported by this storage, the target will
    // end up failing this command, so might as well do the check up front.
    //
    if ((fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumInactivityTimer > 0) &&
        (offloadReadParameters->TimeToLive > fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumInactivityTimer)) {

        TracePrint((TRACE_LEVEL_ERROR,
                    TRACE_FLAG_IOCTL,
                    "ClassDeviceProcessOffloadRead (%p): Requested TTL (%u) greater than max supported (%u).\n",
                    DeviceObject,
                    offloadReadParameters->TimeToLive,
                    fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumInactivityTimer));

        status = STATUS_INVALID_PARAMETER;
        goto __ClassDeviceProcessOffloadRead_CompleteAndExit;
    }

    if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(STORAGE_OFFLOAD_READ_OUTPUT)) {

        TracePrint((TRACE_LEVEL_ERROR,
                    TRACE_FLAG_IOCTL,
                    "ClassDeviceProcessOffloadRead (%p): Output buffer size (%u) too small.\n",
                    DeviceObject,
                    irpStack->Parameters.DeviceIoControl.OutputBufferLength));

        status = STATUS_BUFFER_TOO_SMALL;
        goto __ClassDeviceProcessOffloadRead_CompleteAndExit;
    }



    status = ClasspServicePopulateTokenTransferRequest(DeviceObject, Irp);

    if (status == STATUS_PENDING) {
        goto __ClassDeviceProcessOffloadRead_Exit;
    }

__ClassDeviceProcessOffloadRead_CompleteAndExit:
    ClasspCompleteOffloadRequest(DeviceObject, Irp, status);
__ClassDeviceProcessOffloadRead_Exit:
    TracePrint((TRACE_LEVEL_VERBOSE,
                TRACE_FLAG_IOCTL,
                "ClassDeviceProcessOffloadRead (%p): Exiting function Irp %p with status %x.\n",
                DeviceObject,
                Irp,
                status));

    return status;
}

VOID
ClasspCompleteOffloadRequest(
    _In_ PDEVICE_OBJECT DeviceObject,
    _In_ PIRP Irp,
    _In_ NTSTATUS CompletionStatus)
{
    NTSTATUS status = CompletionStatus;


    Irp->IoStatus.Status = status;

    ClassReleaseRemoveLock(DeviceObject, Irp);
    ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);

    return;
}


_IRQL_requires_max_(APC_LEVEL)
_IRQL_requires_min_(PASSIVE_LEVEL)
_IRQL_requires_same_
NTSTATUS
ClassDeviceProcessOffloadWrite(
    _In_ PDEVICE_OBJECT DeviceObject,
    _In_ PIRP Irp,
    _Inout_ PSCSI_REQUEST_BLOCK Srb
    )

/*++

Routine Description:

    This routine services IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES for CopyOffload
    Write. If the device supports copy offload, it performs the translation of the
    IOCTL into the appropriate SCSI commands to complete the operation.

    This function must be called at IRQL < DISPATCH_LEVEL.

Arguments:

    DeviceObject - Supplies the device object associated with this request
    Irp - The IRP to be processed
    Srb - An SRB that can be optinally used to process this request

Return Value:

    NTSTATUS code

--*/

{
    PIO_STACK_LOCATION irpStack;
    NTSTATUS status;
    PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes;

    UNREFERENCED_PARAMETER(Srb);

    //
    // This function must be called at less than dispatch level.
    //
    PAGED_CODE();

    TracePrint((TRACE_LEVEL_VERBOSE,
                TRACE_FLAG_IOCTL,
                "ClassDeviceProcessOffloadWrite (%p): Entering function. Irp %p\n",
                DeviceObject,
                Irp));


    irpStack = IoGetCurrentIrpStackLocation(Irp);

    //
    // Validations
    //
    status = ClasspValidateOffloadSupported(DeviceObject, Irp);
    if (!NT_SUCCESS(status)) {
        goto __ClassDeviceProcessOffloadWrite_CompleteAndExit;
    }

    if (KeGetCurrentIrql() >= DISPATCH_LEVEL) {

        NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
        TracePrint((TRACE_LEVEL_ERROR,
                    TRACE_FLAG_IOCTL,
                    "ClassDeviceProcessOffloadWrite (%p): Called at raised IRQL.\n",
                    DeviceObject));

        status = STATUS_INVALID_LEVEL;
        goto __ClassDeviceProcessOffloadWrite_CompleteAndExit;
    }

    //
    // Ensure that this DSM IOCTL was generated in kernel
    //
    if (Irp->RequestorMode != KernelMode) {

        TracePrint((TRACE_LEVEL_ERROR,
                    TRACE_FLAG_IOCTL,
                    "ClassDeviceProcessOffloadWrite (%p): Called from user mode.\n",
                    DeviceObject));

        status = STATUS_ACCESS_DENIED;
        goto __ClassDeviceProcessOffloadWrite_CompleteAndExit;
    }

    status = ClasspValidateOffloadInputParameters(DeviceObject, Irp);
    if (!NT_SUCCESS(status)) {
        goto __ClassDeviceProcessOffloadWrite_CompleteAndExit;
    }

    dsmAttributes = Irp->AssociatedIrp.SystemBuffer;

    //
    // Validate that we were passed in correct sized parameter block.
    //
    if (dsmAttributes->ParameterBlockLength < sizeof(DEVICE_DSM_OFFLOAD_WRITE_PARAMETERS)) {

        TracePrint((TRACE_LEVEL_ERROR,
                    TRACE_FLAG_IOCTL,
                    "ClassDeviceProcessOffloadWrite (%p): Parameter block size (%u) too small. Required %u.\n",
                    DeviceObject,
                    dsmAttributes->ParameterBlockLength,
                    sizeof(DEVICE_DSM_OFFLOAD_WRITE_PARAMETERS)));

        status = STATUS_INVALID_PARAMETER;
        goto __ClassDeviceProcessOffloadWrite_CompleteAndExit;
    }

    if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(STORAGE_OFFLOAD_WRITE_OUTPUT)) {

        TracePrint((TRACE_LEVEL_ERROR,
                    TRACE_FLAG_IOCTL,
                    "ClassDeviceProcessOffloadWrite (%p): Output buffer size (%u) too small.\n",
                    DeviceObject,
                    irpStack->Parameters.DeviceIoControl.OutputBufferLength));

        status = STATUS_BUFFER_TOO_SMALL;
        goto __ClassDeviceProcessOffloadWrite_CompleteAndExit;
    }



    status = ClasspServiceWriteUsingTokenTransferRequest(DeviceObject, Irp);

    if (status == STATUS_PENDING) {
        goto __ClassDeviceProcessOffloadWrite_Exit;
    }

__ClassDeviceProcessOffloadWrite_CompleteAndExit:
    ClasspCompleteOffloadRequest(DeviceObject, Irp, status);
__ClassDeviceProcessOffloadWrite_Exit:
    TracePrint((TRACE_LEVEL_VERBOSE,
                TRACE_FLAG_IOCTL,
                "ClassDeviceProcessOffloadWrite (%p): Exiting function Irp %p with status %x\n",
                DeviceObject,
                Irp,
                status));

    return status;
}


_IRQL_requires_max_(APC_LEVEL)
_IRQL_requires_min_(PASSIVE_LEVEL)
_IRQL_requires_same_
NTSTATUS
ClasspServicePopulateTokenTransferRequest(
    _In_ PDEVICE_OBJECT Fdo,
    _In_ PIRP Irp
    )

/*++

Routine description:

    This routine processes offload read requests by building the SRB
    for PopulateToken and its result (i.e. token).

Arguments:

    Fdo - The functional device object processing the request
    Irp - The Io request to be processed

Return Value:

    STATUS_SUCCESS if successful, an error code otherwise

--*/

{
    BOOLEAN allDataSetRangeFullyConverted;
    UINT32 allocationSize;
    ULONG blockDescrIndex;
    PBLOCK_DEVICE_RANGE_DESCRIPTOR blockDescrPointer;
    PVOID buffer;
    ULONG bufferLength;
    ULONG dataSetRangeIndex;
    PDEVICE_DATA_SET_RANGE dataSetRanges;
    ULONG dataSetRangesCount;
    PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes;
    ULONGLONG entireXferLen;
    PFUNCTIONAL_DEVICE_EXTENSION fdoExt;
    ULONG i;
    ULONG lbaCount;
    ULONG listIdentifier;
    ULONG maxBlockDescrCount;
    ULONGLONG maxLbaCount;
    POFFLOAD_READ_CONTEXT offloadReadContext;
    PDEVICE_DSM_OFFLOAD_READ_PARAMETERS offloadReadParameters;
    PTRANSFER_PACKET pkt;
    USHORT populateTokenDataLength;
    USHORT populateTokenDescriptorsLength;
    PMDL populateTokenMdl;
    PIRP pseudoIrp;
    ULONG receiveTokenInformationBufferLength;
    NTSTATUS status;
    DEVICE_DATA_SET_RANGE tempDataSetRange;
    BOOLEAN tempDataSetRangeFullyConverted;
    PUCHAR token;
    ULONG tokenLength;
    ULONG tokenOperationBufferLength;
    ULONGLONG totalSectorCount;
    ULONGLONG totalSectorsToProcess;
    ULONG transferSize;

    PAGED_CODE();

    TracePrint((TRACE_LEVEL_VERBOSE,
                TRACE_FLAG_IOCTL,
                "ClasspServicePopulateTokenTransferRequest (%p): Entering function. Irp %p\n",
                Fdo,
                Irp));

    fdoExt = Fdo->DeviceExtension;
    status = STATUS_SUCCESS;
    dsmAttributes = Irp->AssociatedIrp.SystemBuffer;
    buffer = NULL;
    pkt = NULL;
    populateTokenMdl = NULL;
    offloadReadParameters = Add2Ptr(dsmAttributes, dsmAttributes->ParameterBlockOffset);
    dataSetRanges = Add2Ptr(dsmAttributes, dsmAttributes->DataSetRangesOffset);
    dataSetRangesCount = dsmAttributes->DataSetRangesLength / sizeof(DEVICE_DATA_SET_RANGE);
    totalSectorsToProcess = 0;
    token = NULL;
    tokenLength = 0;
    bufferLength = 0;
    offloadReadContext = NULL;

    NT_ASSERT(fdoExt->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits &&
              NT_SUCCESS(fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus));


    for (i = 0, entireXferLen = 0; i < dataSetRangesCount; i++) {
        entireXferLen += dataSetRanges[i].LengthInBytes;
    }

    //
    // We need to truncate the read request based on the following hardware limitations:
    // 1. The size of the data buffer containing the TokenOperation command's parameters must
    //    not exceed the MaximumTransferLength (and max physical pages) of the underlying
    //    adapter.
    // 2. The number of descriptors specified in the TokenOperation command must not exceed
    //    the MaximumRangeDescriptors.
    // 3. The cumulative total of the number of transfer blocks in all the descriptors in
    //    the TokenOperation command must not exceed the MaximumTokenTransferSize.
    // 4. If the TTL has been set to the 0 (i.e. indicates the use of the default TLT),
    //    limit the cumulative total of the number of transfer blocks in all the descriptors
    //    in the TokenOperation command to the OptimalTransferCount.
    //    NOTE: only a TTL of 0 has an implicit indication that the initiator of the
    //    request would like the storage stack to be smart about the amount of data
    //    transfer.
    //
    // In addition to the above, we need to ensure that for each of the descriptors in the
    // TokenOperation command:
    // 1. The number of blocks specified is an exact multiple of the OptimalTransferLengthGranularity.
    // 2. The number of blocks specified is limited to the MaximumTransferLength. (We shall
    //    however, limit the number of blocks specified in each descriptor to be a maximum of
    //    OptimalTransferLength or MaximumTransferLength, whichever is lesser).
    //
    // Finally, we shall always send down the PopulateToken command using IMMED = 0 for this
    // release. This makes it simpler to handle multi-initiator scenarios since we won't need
    // to deal with the PopulateToken (with IMMED = 1) succeeding but ReceiveRODTokenInformation
    // failing due to the path/node failing, thus making it impossible to retrieve the token.
    //
    // The LBA ranges is in DEVICE_DATA_SET_RANGE format, it needs to be converted into
    // WINDOWS_RANGE_DESCRIPTOR Block Descriptors.
    //

    ClasspGetTokenOperationCommandBufferLength(Fdo,
                                               SERVICE_ACTION_POPULATE_TOKEN,
                                               &bufferLength,
                                               &tokenOperationBufferLength,
                                               &receiveTokenInformationBufferLength);

    allocationSize = sizeof(OFFLOAD_READ_CONTEXT) + bufferLength;

    offloadReadContext = ExAllocatePoolWithTag(
        NonPagedPoolNx,
        allocationSize,
        CLASSPNP_POOL_TAG_TOKEN_OPERATION);

    if (!offloadReadContext) {

        TracePrint((TRACE_LEVEL_ERROR,
                    TRACE_FLAG_IOCTL,
                    "ClasspServicePopulateTokenTransferRequest (%p): Failed to allocate buffer for PopulateToken operations.\n",
                    Fdo));

        status = STATUS_INSUFFICIENT_RESOURCES;
        goto __ClasspServicePopulateTokenTransferRequest_ErrorExit;
    }

    RtlZeroMemory(offloadReadContext, allocationSize);

    offloadReadContext->Fdo = Fdo;
    offloadReadContext->OffloadReadDsmIrp = Irp;

    //
    // The buffer for the commands is after the offloadReadContext.
    //
    buffer = (offloadReadContext + 1);

    //
    // No matter how large the offload read request, we'll be sending it down in one shot.
    // So we need only one transfer packet.
    //
    pkt = DequeueFreeTransferPacket(Fdo, TRUE);
    if (!pkt){

        TracePrint((TRACE_LEVEL_ERROR,
                    TRACE_FLAG_IOCTL,
                    "ClasspServicePopulateTokenTransferRequest (%p): Failed to retrieve transfer packet for TokenOperation (PopulateToken) operation.\n",
                    Fdo));

        status = STATUS_INSUFFICIENT_RESOURCES;
        goto __ClasspServicePopulateTokenTransferRequest_ErrorExit;
    }

    offloadReadContext->Pkt = pkt;

    ClasspGetTokenOperationDescriptorLimits(Fdo,
                                            SERVICE_ACTION_POPULATE_TOKEN,
                                            tokenOperationBufferLength,
                                            &maxBlockDescrCount,
                                            &maxLbaCount);

    //
    // We will limit the maximum data transfer in an offload read operation to:
    // - OTC if TTL was specified as 0
    // - MaximumTransferTransferSize if lesser than above, or if TTL was specified was non-zero
    //
    if ((offloadReadParameters->TimeToLive == 0) && (fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount > 0)) {

        maxLbaCount = MIN(maxLbaCount, fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount);
    }

    //
    // Since we do not want very fragmented files to end up causing the PopulateToken command to take
    // too long (and potentially timeout), we will limit the max number of descriptors sent down in a
    // command.
    //
    maxBlockDescrCount = MIN(maxBlockDescrCount, MAX_NUMBER_BLOCK_DEVICE_DESCRIPTORS);

    TracePrint((TRACE_LEVEL_INFORMATION,
                TRACE_FLAG_IOCTL,
                "ClasspServicePopulateTokenTransferRequest (%p): Using MaxBlockDescrCount %u and MaxLbaCount %I64u.\n",
                Fdo,
                maxBlockDescrCount,
                maxLbaCount));

    allDataSetRangeFullyConverted = FALSE;
    tempDataSetRangeFullyConverted = TRUE;
    dataSetRangeIndex = (ULONG)-1;

    blockDescrPointer = (PBLOCK_DEVICE_RANGE_DESCRIPTOR)
        &((PPOPULATE_TOKEN_HEADER)buffer)->BlockDeviceRangeDescriptor[0];

    RtlZeroMemory(&tempDataSetRange, sizeof(tempDataSetRange));

    blockDescrIndex = 0;
    lbaCount = 0;

    //
    // Send PopulateToken command when the buffer is full or all input entries are converted.
    //
    while (!((blockDescrIndex == maxBlockDescrCount) ||   // buffer full or block descriptor count reached
             (lbaCount == maxLbaCount) ||                 // block LBA count reached
             (allDataSetRangeFullyConverted))) {          // all DataSetRanges have been converted

        //
        // If the previous entry conversion completed, continue the next one;
        // Otherwise, still process the left part of the un-completed entry.
        //
        if (tempDataSetRangeFullyConverted) {
            dataSetRangeIndex++;
            tempDataSetRange.StartingOffset = dataSetRanges[dataSetRangeIndex].StartingOffset;
            tempDataSetRange.LengthInBytes = dataSetRanges[dataSetRangeIndex].LengthInBytes;
        }

        totalSectorCount = 0;

        ClasspConvertDataSetRangeToBlockDescr(Fdo,
                                              blockDescrPointer,
                                              &blockDescrIndex,
                                              maxBlockDescrCount,
                                              &lbaCount,
                                              maxLbaCount,
                                              &tempDataSetRange,
                                              &totalSectorCount);

        tempDataSetRangeFullyConverted = (tempDataSetRange.LengthInBytes == 0) ? TRUE : FALSE;

        allDataSetRangeFullyConverted = tempDataSetRangeFullyConverted && ((dataSetRangeIndex + 1) == dataSetRangesCount);

        totalSectorsToProcess += totalSectorCount;
    }

    //
    // Calculate transfer size, including the header
    //
    transferSize = (blockDescrIndex * sizeof(BLOCK_DEVICE_RANGE_DESCRIPTOR)) + FIELD_OFFSET(POPULATE_TOKEN_HEADER, BlockDeviceRangeDescriptor);

    NT_ASSERT(transferSize <= MAX_TOKEN_OPERATION_PARAMETER_DATA_LENGTH);

    populateTokenDataLength = (USHORT)transferSize - RTL_SIZEOF_THROUGH_FIELD(POPULATE_TOKEN_HEADER, PopulateTokenDataLength);
    REVERSE_BYTES_SHORT(((PPOPULATE_TOKEN_HEADER)buffer)->PopulateTokenDataLength, &populateTokenDataLength);

    ((PPOPULATE_TOKEN_HEADER)buffer)->Immediate = 0;

    REVERSE_BYTES(((PPOPULATE_TOKEN_HEADER)buffer)->InactivityTimeout, &offloadReadParameters->TimeToLive);

    populateTokenDescriptorsLength = (USHORT)transferSize - FIELD_OFFSET(POPULATE_TOKEN_HEADER, BlockDeviceRangeDescriptor);
    REVERSE_BYTES_SHORT(((PPOPULATE_TOKEN_HEADER)buffer)->BlockDeviceRangeDescriptorListLength, &populateTokenDescriptorsLength);

    //
    // Reuse a single buffer for both TokenOperation and ReceiveTokenInformation. This has the one disadvantage
    // that we'll be marking the page(s) as IoWriteAccess even though we only need read access for token
    // operation command. However, the advantage is that we eliminate the possibility of any potential failures
    // when trying to allocate an MDL for the ReceiveTokenInformation later on.
    //
    bufferLength = max(transferSize, receiveTokenInformationBufferLength);

    populateTokenMdl = ClasspBuildDeviceMdl(buffer, bufferLength, FALSE);
    if (!populateTokenMdl) {

        TracePrint((TRACE_LEVEL_ERROR,
                    TRACE_FLAG_IOCTL,
                    "ClasspServicePopulateTokenTransferRequest (%p): Failed to allocate MDL for PopulateToken operations.\n",
                    Fdo));

        status = STATUS_INSUFFICIENT_RESOURCES;
        goto __ClasspServicePopulateTokenTransferRequest_ErrorExit;
    }

    offloadReadContext->PopulateTokenMdl = populateTokenMdl;

    pseudoIrp = &offloadReadContext->PseudoIrp;


    pseudoIrp->IoStatus.Status = STATUS_SUCCESS;
    pseudoIrp->IoStatus.Information = 0;
    pseudoIrp->Tail.Overlay.DriverContext[0] = LongToPtr(1);
    pseudoIrp->MdlAddress = populateTokenMdl;

    InterlockedCompareExchange((volatile LONG *)&TokenOperationListIdentifier, -1, MaxTokenOperationListIdentifier);
    listIdentifier = InterlockedIncrement((volatile LONG *)&TokenOperationListIdentifier);

    ClasspSetupPopulateTokenTransferPacket(
        offloadReadContext,
        pkt,
        transferSize,
        (PUCHAR)buffer,
        pseudoIrp,
        listIdentifier);

    TracePrint((TRACE_LEVEL_INFORMATION,
                TRACE_FLAG_IOCTL,
                "ClasspServicePopulateTokenTransferRequest (%p): Generate token for %I64u bytes (versus %I64u) [via %u descriptors]. \
                \n\t\t\tDataLength: %u, DescriptorsLength: %u. Pkt %p (list id %x). Requested TTL: %u secs.\n",
                Fdo,
                totalSectorsToProcess * fdoExt->DiskGeometry.BytesPerSector,
                entireXferLen,
                blockDescrIndex,
                populateTokenDataLength,
                populateTokenDescriptorsLength,
                pkt,
                listIdentifier,
                offloadReadParameters->TimeToLive));

    //
    // Save (into the offloadReadContext) any remaining things that
    // ClasspPopulateTokenTransferPacketDone() will need.
    //

    offloadReadContext->ListIdentifier = listIdentifier;
    offloadReadContext->BufferLength = bufferLength;
    offloadReadContext->ReceiveTokenInformationBufferLength = receiveTokenInformationBufferLength;
    offloadReadContext->TotalSectorsToProcess = totalSectorsToProcess; // so far
    offloadReadContext->EntireXferLen = entireXferLen;

    NT_ASSERT(status == STATUS_SUCCESS); // so far.

    IoMarkIrpPending(Irp);
    SubmitTransferPacket(pkt);

    status = STATUS_PENDING;
    goto __ClasspServicePopulateTokenTransferRequest_Exit;

    //
    // Error cleanup label only - not used in success case:
    //

__ClasspServicePopulateTokenTransferRequest_ErrorExit:

    NT_ASSERT(status != STATUS_PENDING);

    if (offloadReadContext != NULL) {
        ClasspCleanupOffloadReadContext(offloadReadContext);
        offloadReadContext = NULL;
    }

__ClasspServicePopulateTokenTransferRequest_Exit:

    TracePrint((TRACE_LEVEL_VERBOSE,
                TRACE_FLAG_IOCTL,
                "ClasspServicePopulateTokenTransferRequest (%p): Exiting function (Irp %p) with status %x.\n",
                Fdo,
                Irp,
                status));

    return status;
}


VOID
ClasspPopulateTokenTransferPacketDone(
    _In_ PVOID Context
    )

/*++

Routine description:

    This routine continues an offload read operation on completion of the
    populate token transfer packet.

    This function is responsible for continuing or completing the offload read
    operation.

Arguments:

    Context - Pointer to the OFFLOAD_READ_CONTEXT for the offload read
        operation.

Return Value:

    None.

--*/

{
    PDEVICE_OBJECT fdo;
    ULONG listIdentifier;
    POFFLOAD_READ_CONTEXT offloadReadContext;
    PTRANSFER_PACKET pkt;
    PIRP pseudoIrp;
    NTSTATUS status;

    offloadReadContext = Context;
    pseudoIrp = &offloadReadContext->PseudoIrp;
    pkt = offloadReadContext->Pkt;
    fdo = offloadReadContext->Fdo;
    listIdentifier = offloadReadContext->ListIdentifier;

    offloadReadContext->Pkt = NULL;


    status = pseudoIrp->IoStatus.Status;
    NT_ASSERT(status != STATUS_PENDING);

    if (!NT_SUCCESS(status)) {
        TracePrint((TRACE_LEVEL_ERROR,
                    TRACE_FLAG_IOCTL,
                    "ClasspPopulateTokenTransferPacketDone (%p): Generate token for list Id %x failed with %x (Pkt %p).\n",
                    fdo,
                    listIdentifier,
                    status,
                    pkt));
        goto __ClasspPopulateTokenTransferPacketDone_ErrorExit;
    }

    //
    // If a token was successfully generated, it is now time to retrieve the token.
    // The called function is responsible for completing the offload read DSM IRP.
    //
    ClasspReceivePopulateTokenInformation(offloadReadContext);

    //
    // ClasspReceivePopulateTokenInformation() takes care of completing the IRP,
    // so this function is done regardless of success or failure in
    // ClasspReceivePopulateTokenInformation().
    //

    return;

    //
    // Error cleanup label only - not used in success case:
    //

__ClasspPopulateTokenTransferPacketDone_ErrorExit:

    NT_ASSERT(!NT_SUCCESS(status));

    //
    // ClasspCompleteOffloadRead also cleans up offloadReadContext.
    //

    ClasspCompleteOffloadRead(offloadReadContext, status);

    return;
}


VOID
ClasspCompleteOffloadRead(
    _In_ POFFLOAD_READ_CONTEXT OffloadReadContext,
    _In_ NTSTATUS CompletionStatus
    )

/*++

Routine description:

    This routine completes an offload read operation with given status, and
    cleans up the OFFLOAD_READ_CONTEXT.

Arguments:

    OffloadReadContext - Pointer to the OFFLOAD_READ_CONTEXT for the offload
        read operation.

    CompletionStatus - The completion status for the offload read operation.

Return Value:

    None.

--*/

{
    PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes;
    ULONGLONG entireXferLen;
    PDEVICE_OBJECT fdo;
    PFUNCTIONAL_DEVICE_EXTENSION fdoExt;
    PIRP irp;
    NTSTATUS status;
    PUCHAR token;
    ULONGLONG totalSectorsProcessed;

    status = CompletionStatus;
    dsmAttributes = OffloadReadContext->OffloadReadDsmIrp->AssociatedIrp.SystemBuffer;
    totalSectorsProcessed = OffloadReadContext->TotalSectorsProcessed;
    fdoExt = OffloadReadContext->Fdo->DeviceExtension;
    entireXferLen = OffloadReadContext->EntireXferLen;
    token = OffloadReadContext->Token;
    irp = OffloadReadContext->OffloadReadDsmIrp;
    fdo = OffloadReadContext->Fdo;

    ((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->OffloadReadFlags = 0;
    ((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->Reserved = 0;

    if (NT_SUCCESS(status)) {
        ULONGLONG totalBytesProcessed = totalSectorsProcessed * fdoExt->DiskGeometry.BytesPerSector;

        TracePrint((totalBytesProcessed == entireXferLen ? TRACE_LEVEL_INFORMATION : TRACE_LEVEL_WARNING,
                    TRACE_FLAG_IOCTL,
                    "ClasspCompleteOffloadRead (%p): Successfully populated token with %I64u (out of %I64u) bytes (list Id %x).\n",
                    fdo,
                    totalBytesProcessed,
                    entireXferLen,
                    OffloadReadContext->ListIdentifier));

        if (totalBytesProcessed < entireXferLen) {
            SET_FLAG(((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->OffloadReadFlags, STORAGE_OFFLOAD_READ_RANGE_TRUNCATED);
        }
        ((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->LengthProtected = totalBytesProcessed;
        ((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->TokenLength = STORAGE_OFFLOAD_MAX_TOKEN_LENGTH;
        RtlCopyMemory(&((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->Token, token, STORAGE_OFFLOAD_MAX_TOKEN_LENGTH);
    } else {
        ((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->LengthProtected = 0;
        ((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->TokenLength = 0;
    }

    irp->IoStatus.Information = sizeof(STORAGE_OFFLOAD_READ_OUTPUT);

    ClasspCompleteOffloadRequest(fdo, irp, status);
    ClasspCleanupOffloadReadContext(OffloadReadContext);
    OffloadReadContext = NULL;

    return;
}


VOID
ClasspCleanupOffloadReadContext(
    _In_ __drv_freesMem(mem) POFFLOAD_READ_CONTEXT OffloadReadContext
    )

/*++

Routine description:

    This routine cleans up an OFFLOAD_READ_CONTEXT.

Arguments:

    OffloadReadContext - Pointer to the OFFLOAD_READ_CONTEXT for the offload
        read operation.

Return Value:

    None.

--*/

{
    PMDL populateTokenMdl;

    populateTokenMdl = OffloadReadContext->PopulateTokenMdl;

    NT_ASSERT(OffloadReadContext != NULL);

    if (populateTokenMdl) {
        ClasspFreeDeviceMdl(populateTokenMdl);
    }
    FREE_POOL(OffloadReadContext);

    return;
}


_IRQL_requires_same_
VOID
ClasspReceivePopulateTokenInformation(
    _In_ POFFLOAD_READ_CONTEXT OffloadReadContext
    )

/*++

Routine description:

    This routine retrieves the token after a PopulateToken command
    has been sent down.

    Can also be called repeatedly for a single offload op when a previous
    RECEIVE ROD TOKEN INFORMATION for that op indicated that the operation was
    not yet complete.

    This function is responsible for continuing or completing the offload read
    operation.

Arguments:

    OffloadReadContext - Pointer to the OFFLOAD_READ_CONTEXT for the offload
        read operation.

Return Value:

    None.

--*/

{
    PVOID buffer;
    ULONG bufferLength;
    ULONG cdbLength;
    PDEVICE_OBJECT fdo;
    PIRP irp;
    ULONG listIdentifier;
    PTRANSFER_PACKET pkt;
    PIRP pseudoIrp;
    ULONG receiveTokenInformationBufferLength;
    PSCSI_REQUEST_BLOCK srb;
    NTSTATUS status;
    ULONG tempSizeUlong;
    PULONGLONG totalSectorsProcessed;

    totalSectorsProcessed = &OffloadReadContext->TotalSectorsProcessed;
    buffer = OffloadReadContext + 1;
    bufferLength = OffloadReadContext->BufferLength;
    fdo = OffloadReadContext->Fdo;
    irp = OffloadReadContext->OffloadReadDsmIrp;
    receiveTokenInformationBufferLength = OffloadReadContext->ReceiveTokenInformationBufferLength;
    listIdentifier = OffloadReadContext->ListIdentifier;

    TracePrint((TRACE_LEVEL_VERBOSE,
                TRACE_FLAG_IOCTL,
                "ClasspReceivePopulateTokenInformation (%p): Entering function. Irp %p\n",
                fdo,
                irp));

    srb = &OffloadReadContext->Srb;
    *totalSectorsProcessed = 0;

    pkt = DequeueFreeTransferPacket(fdo, TRUE);
    if (!pkt){

        TracePrint((TRACE_LEVEL_ERROR,
                    TRACE_FLAG_IOCTL,
                    "ClasspReceivePopulateTokenInformation (%p): Failed to retrieve transfer packet for ReceiveTokenInformation (PopulateToken) operation.\n",
                    fdo));

        status = STATUS_INSUFFICIENT_RESOURCES;
        goto __ClasspReceivePopulateTokenInformation_ErrorExit;
    }

    OffloadReadContext->Pkt = pkt;

    RtlZeroMemory(buffer, bufferLength);

    tempSizeUlong = receiveTokenInformationBufferLength - 4;
    REVERSE_BYTES(((PRECEIVE_TOKEN_INFORMATION_HEADER)buffer)->AvailableData, &tempSizeUlong);

    pseudoIrp = &OffloadReadContext->PseudoIrp;
    RtlZeroMemory(pseudoIrp, sizeof(IRP));


    pseudoIrp->IoStatus.Status = STATUS_SUCCESS;
    pseudoIrp->IoStatus.Information = 0;
    pseudoIrp->Tail.Overlay.DriverContext[0] = LongToPtr(1);
    pseudoIrp->MdlAddress = OffloadReadContext->PopulateTokenMdl;

    ClasspSetupReceivePopulateTokenInformationTransferPacket(
        OffloadReadContext,
        pkt,
        receiveTokenInformationBufferLength,
        (PUCHAR)buffer,
        pseudoIrp,
        listIdentifier);

    //
    // Cache away the CDB as it may be required for forwarded sense data
    // after this command completes.
    //
    RtlZeroMemory(srb, sizeof(*srb));
    cdbLength = SrbGetCdbLength(pkt->Srb);
    if (cdbLength <= 16) {
        RtlCopyMemory(&srb->Cdb, SrbGetCdb(pkt->Srb), cdbLength);
    }

    SubmitTransferPacket(pkt);

    return;

    //
    // Error cleanup label only - not used in success case:
    //

__ClasspReceivePopulateTokenInformation_ErrorExit:

    NT_ASSERT(!NT_SUCCESS(status));

    //
    // ClasspCompleteOffloadRead also cleans up offloadReadContext.
    //

    ClasspCompleteOffloadRead(OffloadReadContext, status);

    return;
}


VOID
ClasspReceivePopulateTokenInformationTransferPacketDone(
    _In_ PVOID Context
    )

/*++

Routine description:

    This routine continues an offload read operation on completion of the
    RECEIVE ROD TOKEN INFORMATION transfer packet.

    This routine is responsible for continuing or completing the offload read
    operation.

Arguments:

    Context - Pointer to the OFFLOAD_READ_CONTEXT for the offload read
        operation.

Return Value:

    None.

--*/

{
    ULONG availableData;
    PVOID buffer;
    UCHAR completionStatus;
    ULONG estimatedRetryInterval;
    PDEVICE_OBJECT fdo;
    PFUNCTIONAL_DEVICE_EXTENSION fdoExt;
    PIRP irp;
    ULONG listIdentifier;
    POFFLOAD_READ_CONTEXT offloadReadContext;
    BOOLEAN operationCompleted;
    UCHAR operationStatus;
    PIRP pseudoIrp;
    USHORT segmentsProcessed;
    PSENSE_DATA senseData;
    ULONG senseDataFieldLength;
    UCHAR senseDataLength;
    PSCSI_REQUEST_BLOCK srb;
    NTSTATUS status;
    PUCHAR token;
    PVOID tokenAscii;
    PBLOCK_DEVICE_TOKEN_DESCRIPTOR tokenDescriptor;
    ULONG tokenDescriptorLength;
    PRECEIVE_TOKEN_INFORMATION_HEADER tokenInformationResults;
    PRECEIVE_TOKEN_INFORMATION_RESPONSE_HEADER tokenInformationResultsResponse;
    ULONG tokenLength;
    ULONG tokenSize;
    PULONGLONG totalSectorsProcessed;
    ULONGLONG totalSectorsToProcess;
    ULONGLONG transferBlockCount;

    offloadReadContext = Context;
    fdo = offloadReadContext->Fdo;
    fdoExt = fdo->DeviceExtension;
    buffer = offloadReadContext + 1;
    listIdentifier = offloadReadContext->ListIdentifier;
    irp = offloadReadContext->OffloadReadDsmIrp;
    totalSectorsToProcess = offloadReadContext->TotalSectorsToProcess;
    totalSectorsProcessed = &offloadReadContext->TotalSectorsProcessed;
    srb = &offloadReadContext->Srb;
    tokenAscii = NULL;
    tokenSize = BLOCK_DEVICE_TOKEN_SIZE;
    tokenInformationResults = (PRECEIVE_TOKEN_INFORMATION_HEADER)buffer;
    senseData = (PSENSE_DATA)((PUCHAR)tokenInformationResults + FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_HEADER, SenseData));
    transferBlockCount = 0;
    tokenInformationResultsResponse = NULL;
    tokenDescriptor = NULL;
    operationCompleted = FALSE;
    tokenDescriptorLength = 0;
    tokenLength = 0;
    token = NULL;
    pseudoIrp = &offloadReadContext->PseudoIrp;

    status = pseudoIrp->IoStatus.Status;
    NT_ASSERT(status != STATUS_PENDING);

    //
    // The buffer we hand allows for the max sizes for all the fields whereas the returned
    // data may be lesser (e.g. sense data info will almost never be MAX_SENSE_BUFFER_SIZE, etc.
    // so handle underrun "error"
    //
    if (status == STATUS_DATA_OVERRUN) {

        status = STATUS_SUCCESS;
    }

    if (!NT_SUCCESS(status)) {
        TracePrint((TRACE_LEVEL_ERROR,
                    TRACE_FLAG_IOCTL,
                    "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): Token retrieval failed for list Id %x with %x.\n",
                    fdo,
                    listIdentifier,
                    status));
        goto __ClasspReceivePopulateTokenInformationTransferPacketDone_Exit;
    }

    REVERSE_BYTES(&availableData, &tokenInformationResults->AvailableData);

    NT_ASSERT(availableData <= FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_HEADER, SenseData) + MAX_SENSE_BUFFER_SIZE +
                               FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_RESPONSE_HEADER, TokenDescriptor) + BLOCK_DEVICE_TOKEN_SIZE);

    NT_ASSERT(tokenInformationResults->ResponseToServiceAction == SERVICE_ACTION_POPULATE_TOKEN);

    operationStatus = tokenInformationResults->OperationStatus;
    operationCompleted = ClasspIsTokenOperationComplete(operationStatus);
    NT_ASSERT(operationCompleted);

    REVERSE_BYTES(&estimatedRetryInterval, &tokenInformationResults->EstimatedStatusUpdateDelay);

    completionStatus = tokenInformationResults->CompletionStatus;

    NT_ASSERT(tokenInformationResults->TransferCountUnits == TRANSFER_COUNT_UNITS_NUMBER_BLOCKS);
    REVERSE_BYTES_QUAD(&transferBlockCount, &tokenInformationResults->TransferCount);

    REVERSE_BYTES_SHORT(&segmentsProcessed, &tokenInformationResults->SegmentsProcessed);
    NT_ASSERT(segmentsProcessed == 0);

    if (operationCompleted) {

        if (transferBlockCount > totalSectorsToProcess) {

            //
            // Buggy or hostile target.  Don't let it claim more was procesed
            // than was requested.  Since this is likely a bug and it's unknown
            // how much was actually transferred, assume no data was
            // transferred.
            //

            NT_ASSERT(transferBlockCount <= totalSectorsToProcess);
            transferBlockCount = 0;
        }

        if (operationStatus != OPERATION_COMPLETED_WITH_SUCCESS &&
            operationStatus != OPERATION_COMPLETED_WITH_RESIDUAL_DATA) {

            //
            // Assert on buggy response from target, but in any case, make sure not
            // to claim that any data was written.
            //

            NT_ASSERT(transferBlockCount == 0);
            transferBlockCount = 0;
        }

        //
        // Since the TokenOperation was sent down synchronously, the operation is complete as soon as the command returns.
        //

        senseDataFieldLength = tokenInformationResults->SenseDataFieldLength;
        senseDataLength = tokenInformationResults->SenseDataLength;
        NT_ASSERT(senseDataFieldLength >= senseDataLength);

        tokenInformationResultsResponse = (PRECEIVE_TOKEN_INFORMATION_RESPONSE_HEADER)((PUCHAR)tokenInformationResults +
                                                                                               FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_HEADER, SenseData) +
                                                                                               tokenInformationResults->SenseDataFieldLength);

        REVERSE_BYTES(&tokenDescriptorLength, &tokenInformationResultsResponse->TokenDescriptorsLength);

        if (tokenDescriptorLength > 0) {

            NT_ASSERT(tokenDescriptorLength == sizeof(BLOCK_DEVICE_TOKEN_DESCRIPTOR));

            if (tokenDescriptorLength != sizeof(BLOCK_DEVICE_TOKEN_DESCRIPTOR)) {

                TracePrint((TRACE_LEVEL_ERROR,
                            TRACE_FLAG_IOCTL,
                            "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): Bad firmware, token descriptor length %u.\n",
                            fdo,
                            tokenDescriptorLength));

                NT_ASSERT((*totalSectorsProcessed) == 0);
                NT_ASSERT(tokenLength == 0);

            } else {

                USHORT restrictedId;

                tokenDescriptor = (PBLOCK_DEVICE_TOKEN_DESCRIPTOR)tokenInformationResultsResponse->TokenDescriptor;

                REVERSE_BYTES_SHORT(&restrictedId, &tokenDescriptor->TokenIdentifier);
                NT_ASSERT(restrictedId == 0);

                tokenLength = BLOCK_DEVICE_TOKEN_SIZE;
                token = tokenDescriptor->Token;

                *totalSectorsProcessed = transferBlockCount;

                if (transferBlockCount < totalSectorsToProcess) {

                    NT_ASSERT(operationStatus == OPERATION_COMPLETED_WITH_RESIDUAL_DATA ||
                              operationStatus == OPERATION_COMPLETED_WITH_ERROR ||
                              operationStatus == OPERATION_TERMINATED);

                    if (transferBlockCount == 0) {
                        //
                        // Treat the same as not getting a token.
                        //

                        tokenLength = 0;
                    }

                } else {

                    NT_ASSERT(operationStatus == OPERATION_COMPLETED_WITH_SUCCESS);
                    NT_ASSERT(transferBlockCount == totalSectorsToProcess);
                }

                    //
                    // Need to convert to ascii.
                    //
                    tokenAscii = ClasspBinaryToAscii((PUCHAR)token,
                                                     tokenSize,
                                                     &tokenSize);

                    TracePrint((transferBlockCount == totalSectorsToProcess ? TRACE_LEVEL_INFORMATION : TRACE_LEVEL_WARNING,
                                TRACE_FLAG_IOCTL,
                                "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): %wsToken %s generated successfully for list Id %x for data size %I64u bytes.\n",
                                fdo,
                                transferBlockCount == totalSectorsToProcess ? L"" : L"Target truncated read. ",
                                (tokenAscii == NULL) ? "" : tokenAscii,
                                listIdentifier,
                                (*totalSectorsProcessed) * fdoExt->DiskGeometry.BytesPerSector));

                    FREE_POOL(tokenAscii);
            }
        } else {

            TracePrint((TRACE_LEVEL_ERROR,
                        TRACE_FLAG_IOCTL,
                        "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): Target failed to generate a token for list Id %x for data size %I64u bytes (requested %I64u bytes).\n",
                        fdo,
                        listIdentifier,
                        transferBlockCount * fdoExt->DiskGeometry.BytesPerSector,
                        totalSectorsToProcess * fdoExt->DiskGeometry.BytesPerSector));

            *totalSectorsProcessed = 0;

            NT_ASSERT(operationStatus == OPERATION_COMPLETED_WITH_ERROR);
        }

        //
        // Operation that completes with success can have sense data (for target to pass on some extra info)
        // but we don't care about such sense info.
        // Operation that complete but not with success, may not have sense data associated, but may
        // have valid CompletionStatus.
        //
        // The "status" may be overriden by ClassInterpretSenseInfo().  Final
        // status is determined a bit later - this is just the default status
        // when ClassInterpretSenseInfo() doesn't get to run here.
        //
        status = STATUS_SUCCESS;
        if (operationStatus == OPERATION_COMPLETED_WITH_ERROR ||
            operationStatus == OPERATION_COMPLETED_WITH_RESIDUAL_DATA ||
            operationStatus == OPERATION_TERMINATED) {

            SrbSetScsiStatus((PSTORAGE_REQUEST_BLOCK_HEADER)srb, completionStatus);

            if (senseDataLength) {

                ULONG retryInterval;

                NT_ASSERT(senseDataLength <= sizeof(SENSE_DATA));

                srb->Function = SRB_FUNCTION_EXECUTE_SCSI;

                srb->SrbStatus = SRB_STATUS_AUTOSENSE_VALID | SRB_STATUS_ERROR;
                SrbSetSenseInfoBuffer((PSTORAGE_REQUEST_BLOCK_HEADER)srb, senseData);
                SrbSetSenseInfoBufferLength((PSTORAGE_REQUEST_BLOCK_HEADER)srb, senseDataLength);

                ClassInterpretSenseInfo(fdo,
                                        srb,
                                        IRP_MJ_SCSI,
                                        0,
                                        0,
                                        &status,
                                        &retryInterval);

                TracePrint((TRACE_LEVEL_WARNING,
                            TRACE_FLAG_IOCTL,
                            "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): Reason for truncation/failure: %x - for list Id %x for data size %I64u bytes.\n",
                            fdo,
                            status,
                            listIdentifier,
                            transferBlockCount * fdoExt->DiskGeometry.BytesPerSector));
            } else {

                TracePrint((TRACE_LEVEL_WARNING,
                            TRACE_FLAG_IOCTL,
                            "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): No sense data available but reason for truncation/failure, possibly: %x - for list Id %x for data size %I64u bytes.\n",
                            fdo,
                            completionStatus,
                            listIdentifier,
                            transferBlockCount * fdoExt->DiskGeometry.BytesPerSector));
            }
        }

        if (tokenLength > 0) {

            offloadReadContext->Token = token;

            //
            // Even if target returned an error, from the OS upper layers' perspective,
            // it is a success (with truncation) if any data at all was read.
            //
            status = STATUS_SUCCESS;

        } else {

            if (NT_SUCCESS(status)) {
                //
                // Make sure status is a failing status, without throwing away an
                // already-failing status obtained from sense data.
                //
                status = STATUS_UNSUCCESSFUL;
            }
        }

        //
        // Done with the operation.
        //

        NT_ASSERT(status != STATUS_PENDING);
        goto __ClasspReceivePopulateTokenInformationTransferPacketDone_Exit;

    } else {

        status = STATUS_UNSUCCESSFUL;

        TracePrint((TRACE_LEVEL_ERROR,
                    TRACE_FLAG_IOCTL,
                    "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): Token retrieval failed for list Id %x with %x.\n",
                    fdo,
                    listIdentifier,
                    status));

        NT_ASSERT(*totalSectorsProcessed == 0);
        goto __ClasspReceivePopulateTokenInformationTransferPacketDone_Exit;
    }

__ClasspReceivePopulateTokenInformationTransferPacketDone_Exit:

    if (status != STATUS_PENDING) {

        //
        // The "status" value can be success or failure at this point, as
        // appropriate.
        //

        ClasspCompleteOffloadRead(offloadReadContext, status);
    }

    //
    // Due to tracing a potentially freed pointer value "Irp", this trace could
    // be delayed beyond another offload op picking up the same pointer value
    // for its Irp.  This function exits after the operation is complete when
    // status != STATUS_PENDING.
    //

    TracePrint((TRACE_LEVEL_VERBOSE,
                TRACE_FLAG_IOCTL,
                "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): Exiting function (Irp %p) with internal status %x.\n",
                fdo,
                irp,
                status));

    return;
}


_IRQL_requires_max_(APC_LEVEL)
_IRQL_requires_min_(PASSIVE_LEVEL)
_IRQL_requires_same_
NTSTATUS
ClasspServiceWriteUsingTokenTransferRequest(
    _In_ PDEVICE_OBJECT Fdo,
    _In_ PIRP Irp
    )

/*++

Routine description:

    This routine processes offload write requests by building the SRB
    for WriteUsingToken.

Arguments:

    Fdo - The functional device object processing the request
    Irp - The Io request to be processed

Return Value:

    STATUS_SUCCESS if successful, an error code otherwise

--*/

{
    ULONG allocationSize;
    PVOID buffer;
    ULONG bufferLength;
    PDEVICE_DATA_SET_RANGE dataSetRanges;
    ULONG dataSetRangesCount;
    PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes;
    ULONGLONG entireXferLen;
    PFUNCTIONAL_DEVICE_EXTENSION fdoExt;
    ULONG i;
    ULONGLONG logicalBlockOffset;
    ULONG maxBlockDescrCount;
    ULONGLONG maxLbaCount;
    POFFLOAD_WRITE_CONTEXT offloadWriteContext;
    PDEVICE_DSM_OFFLOAD_WRITE_PARAMETERS offloadWriteParameters;
    ULONG receiveTokenInformationBufferLength;
    NTSTATUS status;
    NTSTATUS tempStatus;
    BOOLEAN tokenInvalidated;
    ULONG tokenOperationBufferLength;
    PMDL writeUsingTokenMdl;

    PAGED_CODE();

    TracePrint((TRACE_LEVEL_VERBOSE,
                TRACE_FLAG_IOCTL,
                "ClasspServiceWriteUsingTokenTransferRequest (%p): Entering function. Irp %p.\n",
                Fdo,
                Irp));

    fdoExt = Fdo->DeviceExtension;
    status = STATUS_SUCCESS;
    tempStatus = STATUS_SUCCESS;
    dsmAttributes = Irp->AssociatedIrp.SystemBuffer;
    buffer = NULL;
    writeUsingTokenMdl = NULL;
    offloadWriteParameters = Add2Ptr(dsmAttributes, dsmAttributes->ParameterBlockOffset);
    dataSetRanges = Add2Ptr(dsmAttributes, dsmAttributes->DataSetRangesOffset);
    dataSetRangesCount = dsmAttributes->DataSetRangesLength / sizeof(DEVICE_DATA_SET_RANGE);
    logicalBlockOffset = offloadWriteParameters->TokenOffset / fdoExt->DiskGeometry.BytesPerSector;
    tokenInvalidated = FALSE;
    bufferLength = 0;


    NT_ASSERT(fdoExt->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits &&
              NT_SUCCESS(fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus));

    for (i = 0, entireXferLen = 0; i < dataSetRangesCount; i++) {
        entireXferLen += dataSetRanges[i].LengthInBytes;
    }

    //
    // We need to split the write request based on the following hardware limitations:
    // 1. The size of the data buffer containing the TokenOperation command's parameters must
    //    not exceed the MaximumTransferLength (and max physical pages) of the underlying
    //    adapter.
    // 2. The number of descriptors specified in the TokenOperation command must not exceed
    //    the MaximumRangeDescriptors.
    // 3. The cumulative total of the number of transfer blocks in all the descriptors in
    //    the TokenOperation command must not exceed the MaximumTokenTransferSize.
    //
    // In addition to the above, we need to ensure that for each of the descriptors in the
    // TokenOperation command:
    // 1. The number of blocks specified is an exact multiple of the OptimalTransferLengthGranularity.
    // 2. The number of blocks specified is limited to the MaximumTransferLength. (We shall
    //    however, limit the number of blocks specified in each descriptor to be a maximum of
    //    OptimalTransferLength or MaximumTransferLength, whichever is lesser).
    //
    // Finally, we shall always send down the WriteUsingToken command using IMMED = 0 for this
    // release. This makes it simpler to handle multi-initiator scenarios since we won't need
    // to deal with the WriteUsingToken (with IMMED = 1) succeeding but ReceiveRODTokenInformation
    // failing due to the path/node failing, thus making it impossible to retrieve results of
    // the data transfer operation, or to cancel the data transfer via CopyOperationAbort.
    // Since a write data transfer of a large amount of data may take a long time when sent
    // down IMMED = 0, we shall limit the size to a maximum of 256MB even if it means truncating
    // the original requested size to this capped size. The application is expected to deal with
    // this truncation.
    // (NOTE: the cap of 256MB is chosen to match with the Copy Engine's chunk size).
    //
    // The LBA ranges is in DEVICE_DATA_SET_RANGE format, it needs to be converted into
    // WINDOWS_BLOCK_DEVICE_RANGE_DESCRIPTOR Block Descriptors.
    //

    ClasspGetTokenOperationCommandBufferLength(Fdo,
                                               SERVICE_ACTION_WRITE_USING_TOKEN,
                                               &bufferLength,
                                               &tokenOperationBufferLength,
                                               &receiveTokenInformationBufferLength);

    allocationSize = sizeof(OFFLOAD_WRITE_CONTEXT) + bufferLength;

    offloadWriteContext = ExAllocatePoolWithTag(
        NonPagedPoolNx,
        allocationSize,
        CLASSPNP_POOL_TAG_TOKEN_OPERATION);

    if (!offloadWriteContext) {

        TracePrint((TRACE_LEVEL_ERROR,
                    TRACE_FLAG_IOCTL,
                    "ClasspServiceWriteUsingTokenTransferRequest (%p): Failed to allocate buffer for WriteUsingToken operations.\n",
                    Fdo));

        status = STATUS_INSUFFICIENT_RESOURCES;
        goto __ClasspServiceWriteUsingTokenTransferRequest_ErrorExit;
    }

    //
    // Only zero the context portion here.  The buffer portion is zeroed for
    // each sub-request.
    //
    RtlZeroMemory(offloadWriteContext, sizeof(OFFLOAD_WRITE_CONTEXT));

    offloadWriteContext->Fdo = Fdo;
    offloadWriteContext->OffloadWriteDsmIrp = Irp;
    offloadWriteContext->OperationStartTime = KeQueryInterruptTime();

    //
    // The buffer for the commands is after the offloadWriteContext.
    //
    buffer = (offloadWriteContext + 1);

    //
    // Set up fields that allow iterating through whole request, by issuing sub-
    // requests which each do some of the writing.  Because of truncation by the
    // target, it's not known exactly how many bytes will be written by the
    // target in each sub-request (can be less than requested), so the
    // progress through the outer request must only commit the move through the
    // upper DSM ranges when a lower request is done and the number of written
    // sectors is known.
    //

    NT_ASSERT(offloadWriteContext->TotalSectorsProcessedSuccessfully == 0);
    offloadWriteContext->TotalRequestSizeSectors = entireXferLen / fdoExt->DiskGeometry.BytesPerSector;
    NT_ASSERT(offloadWriteContext->DataSetRangeIndex == 0);
    NT_ASSERT(offloadWriteContext->DataSetRangeByteOffset == 0);
    offloadWriteContext->DataSetRangesCount = dataSetRangesCount;

    offloadWriteContext->DsmAttributes = dsmAttributes;
    offloadWriteContext->OffloadWriteParameters = offloadWriteParameters;
    offloadWriteContext->DataSetRanges = dataSetRanges;
    offloadWriteContext->LogicalBlockOffset = logicalBlockOffset;

    ClasspGetTokenOperationDescriptorLimits(Fdo,
                                            SERVICE_ACTION_WRITE_USING_TOKEN,
                                            tokenOperationBufferLength,
                                            &maxBlockDescrCount,
                                            &maxLbaCount);

    if (fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount && fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumTokenTransferSize) {

        NT_ASSERT(fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount <= fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumTokenTransferSize);
    }

    //
    // We will limit the maximum data transfer in an offload write operation to:
    // - 64MB if OptimalTransferCount = 0
    // - OptimalTransferCount if < 256MB
    // - 256MB if OptimalTransferCount >= 256MB
    // - MaximumTokenTransferSize if lesser than above chosen size
    //
    if (0 == fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount) {

        maxLbaCount = MIN(maxLbaCount, (DEFAULT_MAX_NUMBER_BYTES_PER_SYNC_WRITE_USING_TOKEN / (ULONGLONG)fdoExt->DiskGeometry.BytesPerSector));

    } else if (fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount < (MAX_NUMBER_BYTES_PER_SYNC_WRITE_USING_TOKEN / (ULONGLONG)fdoExt->DiskGeometry.BytesPerSector)) {

        maxLbaCount = MIN(maxLbaCount, fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount);

    } else {

        maxLbaCount = MIN(maxLbaCount, (MAX_NUMBER_BYTES_PER_SYNC_WRITE_USING_TOKEN / (ULONGLONG)fdoExt->DiskGeometry.BytesPerSector));
    }

    //
    // Since we do not want very fragmented files to end up causing the WriteUsingToken command to take
    // too long (and potentially timeout), we will limit the max number of descriptors sent down in a
    // command.
    //
    maxBlockDescrCount = MIN(maxBlockDescrCount, MAX_NUMBER_BLOCK_DEVICE_DESCRIPTORS);

    TracePrint((TRACE_LEVEL_INFORMATION,
                TRACE_FLAG_IOCTL,
                "ClasspServiceWriteUsingTokenTransferRequest (%p): Using MaxBlockDescrCount %u and MaxLbaCount %I64u.\n",
                Fdo,
                maxBlockDescrCount,
                maxLbaCount));

    offloadWriteContext->MaxBlockDescrCount = maxBlockDescrCount;
    offloadWriteContext->MaxLbaCount = maxLbaCount;

    //
    // Reuse a single buffer for both TokenOperation and ReceiveTokenInformation. This has the one disadvantage
    // that we'll be marking the page(s) as IoWriteAccess even though we only need read access for token
    // operation command and we may not even need to send down a ReceiveTokenInformation (in case of a successful
    // synchronous transfer). However, the advantage is that we eliminate the possibility of any potential
    // failures when trying to allocate an MDL for the ReceiveTokenInformation later on if we need to send it.
    //
    writeUsingTokenMdl = ClasspBuildDeviceMdl(buffer, bufferLength, FALSE);
    if (!writeUsingTokenMdl) {

        TracePrint((TRACE_LEVEL_ERROR,
                    TRACE_FLAG_IOCTL,
                    "ClasspServiceWriteUsingTokenTransferRequest (%p): Failed to allocate MDL for WriteUsingToken operations.\n",
                    Fdo));

        status = STATUS_INSUFFICIENT_RESOURCES;
        goto __ClasspServiceWriteUsingTokenTransferRequest_ErrorExit;
    }

    offloadWriteContext->WriteUsingTokenMdl = writeUsingTokenMdl;

    //
    // There are potentially two approaches that we can take:
    // 1. Determine how many transfer packets we need (in case we need to split the request), get
    //    them all up-front and then send down all the split WriteUsingToken commands in parallel.
    //    The benefit of this approach is that the performance will be improved in the success case.
    //    But error case handling becomes very complex to handle, since if one of the intermediate
    //    write fails, there is no way to cancel the remaining writes that were sent. Waiting for
    //    such requests to complete in geo-distributed source and target cases can be very time
    //    consuming. The complexity gets worse in the case that the target succeeds only a partial
    //    amount of data for one the of intermediate split commands.
    //                                       [OR]
    // 2. Until the entire data set range is processed, build the command for as much of the range as
    //    possible, send down a packet, and once it completes, repeat sequentially in a loop.
    //    The advantage of this approach is its simplistic nature. In the success case, it will
    //    be less performant as compared to the previous approach, but since the gain of offload
    //    copy is so significant compared to native buffered-copy, the tradeoff is acceptable.
    //    In the failure case the simplicity offers the following benefit - if any command fails,
    //    there is no further processing that is needed. And the cumulative total bytes that succeeded
    //    (until the failing split WriteUsingToken command) is easily tracked.
    //
    // Given the above, we're going with the second approach.
    //

    NT_ASSERT(status == STATUS_SUCCESS); // so far

    //
    // Save (into the offloadReadContext) any remaining things that
    // ClasspPopulateTokenTransferPacketDone() will need.
    //

    offloadWriteContext->BufferLength = bufferLength;
    offloadWriteContext->ReceiveTokenInformationBufferLength = receiveTokenInformationBufferLength;
    offloadWriteContext->EntireXferLen = entireXferLen;

    IoMarkIrpPending(Irp);
    ClasspContinueOffloadWrite(offloadWriteContext);

    status = STATUS_PENDING;
    goto __ClasspServiceWriteUsingTokenTransferRequest_Exit;

    //
    // Error label only - not used in success case:
    //

__ClasspServiceWriteUsingTokenTransferRequest_ErrorExit:

    NT_ASSERT(status != STATUS_PENDING);

    if (offloadWriteContext != NULL) {
        ClasspCleanupOffloadWriteContext(offloadWriteContext);
        offloadWriteContext = NULL;
    }

__ClasspServiceWriteUsingTokenTransferRequest_Exit:

    TracePrint((TRACE_LEVEL_VERBOSE,
                TRACE_FLAG_IOCTL,
                "ClasspServiceWriteUsingTokenTransferRequest (%p): Exiting function (Irp %p) with status %x.\n",
                Fdo,
                Irp,
                status));

    return status;
}


VOID
#ifdef _MSC_VER
#pragma warning(suppress: 28194) // This function will either alias or free OffloadWriteContext
#endif
ClasspContinueOffloadWrite(
    _In_ __drv_aliasesMem POFFLOAD_WRITE_CONTEXT OffloadWriteContext
    )

/*++

Routine description:

    This routine continues an offload write operation.  This routine expects the
    offload write operation to be set up and ready to start a WRITE USING TOKEN,
    but with no WRITE USING TOKEN currently in flight.

    This routine is responsible for continuing or completing the offload write
    operation.

Arguments:

    OffloadWriteContext - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload
        write operation.

Return Value:

    None.

--*/

{
    BOOLEAN allDataSetRangeFullyConverted;
    ULONG blockDescrIndex;
    PBLOCK_DEVICE_RANGE_DESCRIPTOR blockDescrPointer;
    PVOID buffer;
    ULONG bufferLength;
    ULONGLONG dataSetRangeByteOffset;
    ULONG dataSetRangeIndex;
    PDEVICE_DATA_SET_RANGE dataSetRanges;
    ULONG dataSetRangesCount;
    ULONGLONG entireXferLen;
    PDEVICE_OBJECT fdo;
    PFUNCTIONAL_DEVICE_EXTENSION fdoExt;
    PIRP irp;
    ULONG lbaCount;
    ULONG listIdentifier;
    ULONGLONG logicalBlockOffset;
    ULONG maxBlockDescrCount;
    ULONGLONG maxLbaCount;
    PDEVICE_DSM_OFFLOAD_WRITE_PARAMETERS offloadWriteParameters;
    PTRANSFER_PACKET pkt;
    PIRP pseudoIrp;
    NTSTATUS status;
    DEVICE_DATA_SET_RANGE tempDataSetRange;
    BOOLEAN tempDataSetRangeFullyConverted;
    PVOID tokenAscii;
    ULONG tokenSize;
    ULONGLONG totalSectorCount;
    ULONGLONG totalSectorsProcessedSuccessfully;
    ULONGLONG totalSectorsToProcess;
    ULONG transferSize;
    USHORT writeUsingTokenDataLength;
    USHORT writeUsingTokenDescriptorsLength;
    PMDL writeUsingTokenMdl;

    tokenAscii = NULL;
    tokenSize = BLOCK_DEVICE_TOKEN_SIZE;
    tempDataSetRangeFullyConverted = FALSE;
    allDataSetRangeFullyConverted = FALSE;
    fdo = OffloadWriteContext->Fdo;
    fdoExt = fdo->DeviceExtension;
    irp = OffloadWriteContext->OffloadWriteDsmIrp;
    buffer = OffloadWriteContext + 1;
    bufferLength = OffloadWriteContext->BufferLength;
    dataSetRanges = OffloadWriteContext->DataSetRanges;
    offloadWriteParameters = OffloadWriteContext->OffloadWriteParameters;
    pseudoIrp = &OffloadWriteContext->PseudoIrp;
    writeUsingTokenMdl = OffloadWriteContext->WriteUsingTokenMdl;
    entireXferLen = OffloadWriteContext->EntireXferLen;

    RtlZeroMemory(buffer, bufferLength);

    pkt = DequeueFreeTransferPacket(fdo, TRUE);
    if (!pkt){

        TracePrint((TRACE_LEVEL_ERROR,
                    TRACE_FLAG_IOCTL,
                    "ClasspContinueOffloadWrite (%p): Failed to retrieve transfer packet for TokenOperation (WriteUsingToken) operation.\n",
                    fdo));

        status = STATUS_INSUFFICIENT_RESOURCES;
        goto __ClasspContinueOffloadWrite_ErrorExit;
    }

    OffloadWriteContext->Pkt = pkt;

    blockDescrPointer = (PBLOCK_DEVICE_RANGE_DESCRIPTOR)
        &((PWRITE_USING_TOKEN_HEADER)buffer)->BlockDeviceRangeDescriptor[0];

    blockDescrIndex = 0;
    lbaCount = 0;

    totalSectorsToProcess = 0;

    maxBlockDescrCount = OffloadWriteContext->MaxBlockDescrCount;
    maxLbaCount = OffloadWriteContext->MaxLbaCount;

    //
    // The OffloadWriteContext->DataSetRangeIndex, DataSetRangeByteOffset, and
    // TotalSectorsProcessedSuccessfully don't move forward until RRTI has
    // reported the actual amount written.
    //
    // For that reason, this function only updates
    // OffloadWriteContext->TotalSectorsProcessed, which tracks the number of
    // sectors requested to be written by the current WRITE USING TOKEN command
    // (not all will necessarily be written).
    //

    dataSetRangeIndex = OffloadWriteContext->DataSetRangeIndex;
    dataSetRangesCount = OffloadWriteContext->DataSetRangesCount;
    dataSetRangeByteOffset = OffloadWriteContext->DataSetRangeByteOffset;
    totalSectorsProcessedSuccessfully = OffloadWriteContext->TotalSectorsProcessedSuccessfully;

    //
    // Send WriteUsingToken commands when the buffer is full or all input entries are converted.
    //
    while (!((blockDescrIndex == maxBlockDescrCount) ||        // buffer full or block descriptor count reached
             (lbaCount == maxLbaCount) ||                      // block LBA count reached
             (allDataSetRangeFullyConverted))) {               // all DataSetRanges have been converted

        NT_ASSERT(dataSetRangeIndex < dataSetRangesCount);
        NT_ASSERT(dataSetRangeByteOffset < dataSetRanges[dataSetRangeIndex].LengthInBytes);

        tempDataSetRange.StartingOffset = dataSetRanges[dataSetRangeIndex].StartingOffset + dataSetRangeByteOffset;
        tempDataSetRange.LengthInBytes = dataSetRanges[dataSetRangeIndex].LengthInBytes - dataSetRangeByteOffset;

        totalSectorCount = 0;

        ClasspConvertDataSetRangeToBlockDescr(fdo,
                                              blockDescrPointer,
                                              &blockDescrIndex,
                                              maxBlockDescrCount,
                                              &lbaCount,
                                              maxLbaCount,
                                              &tempDataSetRange,
                                              &totalSectorCount);

        tempDataSetRangeFullyConverted = (tempDataSetRange.LengthInBytes == 0) ? TRUE : FALSE;

        allDataSetRangeFullyConverted = tempDataSetRangeFullyConverted && ((dataSetRangeIndex + 1) == dataSetRangesCount);

        if (tempDataSetRangeFullyConverted) {
            dataSetRangeIndex += 1;
            dataSetRangeByteOffset = 0;
            NT_ASSERT(dataSetRangeIndex <= dataSetRangesCount);
        } else {
            dataSetRangeByteOffset += totalSectorCount * fdoExt->DiskGeometry.BytesPerSector;
            NT_ASSERT(dataSetRangeByteOffset < dataSetRanges[dataSetRangeIndex].LengthInBytes);
        }

        totalSectorsToProcess += totalSectorCount;
    }

    //
    // Save the number of sectors being attempted in this WRITE USING TOKEN
    // command, so that a success return from the command will know how much
    // was written, without needing to issue a RECEIVE ROD TOKEN INFORMATION
    // command.
    //
    OffloadWriteContext->TotalSectorsToProcess = totalSectorsToProcess;
    OffloadWriteContext->TotalSectorsProcessed = 0;

    //
    // Calculate transfer size, including the header
    //
    transferSize = (blockDescrIndex * sizeof(BLOCK_DEVICE_RANGE_DESCRIPTOR)) + FIELD_OFFSET(WRITE_USING_TOKEN_HEADER, BlockDeviceRangeDescriptor);

    NT_ASSERT(transferSize <= MAX_TOKEN_OPERATION_PARAMETER_DATA_LENGTH);

    writeUsingTokenDataLength = (USHORT)transferSize - RTL_SIZEOF_THROUGH_FIELD(WRITE_USING_TOKEN_HEADER, WriteUsingTokenDataLength);
    REVERSE_BYTES_SHORT(((PWRITE_USING_TOKEN_HEADER)buffer)->WriteUsingTokenDataLength, &writeUsingTokenDataLength);

    ((PWRITE_USING_TOKEN_HEADER)buffer)->Immediate = 0;

    logicalBlockOffset = OffloadWriteContext->LogicalBlockOffset + totalSectorsProcessedSuccessfully;
    REVERSE_BYTES_QUAD(((PWRITE_USING_TOKEN_HEADER)buffer)->BlockOffsetIntoToken, &logicalBlockOffset);

    RtlCopyMemory(((PWRITE_USING_TOKEN_HEADER)buffer)->Token,
                  &offloadWriteParameters->Token,
                  BLOCK_DEVICE_TOKEN_SIZE);

    writeUsingTokenDescriptorsLength = (USHORT)transferSize - FIELD_OFFSET(WRITE_USING_TOKEN_HEADER, BlockDeviceRangeDescriptor);
    REVERSE_BYTES_SHORT(((PWRITE_USING_TOKEN_HEADER)buffer)->BlockDeviceRangeDescriptorListLength, &writeUsingTokenDescriptorsLength);

    RtlZeroMemory(pseudoIrp, sizeof(IRP));

    pseudoIrp->IoStatus.Status = STATUS_SUCCESS;
    pseudoIrp->IoStatus.Information = 0;
    pseudoIrp->Tail.Overlay.DriverContext[0] = LongToPtr(1);
    pseudoIrp->MdlAddress = writeUsingTokenMdl;

    InterlockedCompareExchange((volatile LONG *)&TokenOperationListIdentifier, -1, MaxTokenOperationListIdentifier);
    listIdentifier = InterlockedIncrement((volatile LONG *)&TokenOperationListIdentifier);

    ClasspSetupWriteUsingTokenTransferPacket(
        OffloadWriteContext,
        pkt,
        transferSize,
        (PUCHAR)buffer,
        pseudoIrp,
        listIdentifier);

    tokenAscii = ClasspBinaryToAscii((PUCHAR)(((PWRITE_USING_TOKEN_HEADER)buffer)->Token),
                                     tokenSize,
                                     &tokenSize);

    TracePrint((TRACE_LEVEL_INFORMATION,
                TRACE_FLAG_IOCTL,
                "ClasspContinueOffloadWrite (%p): Offloading write for %I64u bytes (versus %I64u) [via %u descriptors]. \
                \n\t\t\tDataLength: %u, DescriptorsLength: %u. Pkt %p (list id %x) [Token: %s]\n",
                fdo,
                totalSectorsToProcess * fdoExt->DiskGeometry.BytesPerSector,
                entireXferLen,
                blockDescrIndex,
                writeUsingTokenDataLength,
                writeUsingTokenDescriptorsLength,
                pkt,
                listIdentifier,
                (tokenAscii == NULL) ? "" : tokenAscii));

    FREE_POOL(tokenAscii);

    OffloadWriteContext->ListIdentifier = listIdentifier;

    SubmitTransferPacket(pkt);

    //
    // ClasspWriteUsingTokenTransferPacketDone() takes care of completing the
    // IRP, so this function is done.
    //

    return;

    //
    // Error cleaup label only - not used in success case:
    //

__ClasspContinueOffloadWrite_ErrorExit:

    NT_ASSERT(!NT_SUCCESS(status));

    //
    // ClasspCompleteOffloadWrite also cleans up offloadWriteContext.
    //

    ClasspCompleteOffloadWrite(OffloadWriteContext, status);

    return;
}


VOID
ClasspAdvanceOffloadWritePosition(
    _In_ POFFLOAD_WRITE_CONTEXT OffloadWriteContext,
    _In_ ULONGLONG SectorsToAdvance
    )

/*++

Routine description:

    After the target has responded to WRITE USING TOKEN with success, or RRTI
    with a specific TRANSFER COUNT, this routine is used to update the relative
    position within the overall offload write request.  This position includes
    the TotalSectorsProcessedSuccessfully, the DataSetRangeIndex, and the
    DataSetRangeByteOffset.

    The caller is responsible for continuing or completing the offload write
    operation (this routine doesn't do that).

Arguments:

    OffloadWriteContext - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload
        write operation.

    SectorsToAdvance - The number of sectors which were just written
        successfully (not the total for the offload write operation overall,
        just the number done by the most recent WRITE USING TOKEN).

Return Value:

    None.

--*/

{
    ULONGLONG bytesToAdvance;
    ULONGLONG bytesToDo;
    PULONGLONG dataSetRangeByteOffset;
    PULONG dataSetRangeIndex;
    PDEVICE_DATA_SET_RANGE dataSetRanges;
    PDEVICE_OBJECT fdo;
    PFUNCTIONAL_DEVICE_EXTENSION fdoExt;
    PULONGLONG totalSectorsProcessedSuccessfully;

    fdo = OffloadWriteContext->Fdo;
    fdoExt = fdo->DeviceExtension;
    dataSetRanges = OffloadWriteContext->DataSetRanges;
    dataSetRangeByteOffset = &OffloadWriteContext->DataSetRangeByteOffset;
    dataSetRangeIndex = &OffloadWriteContext->DataSetRangeIndex;
    totalSectorsProcessedSuccessfully = &OffloadWriteContext->TotalSectorsProcessedSuccessfully;
    bytesToAdvance = SectorsToAdvance * fdoExt->DiskGeometry.BytesPerSector;

    (*totalSectorsProcessedSuccessfully) += SectorsToAdvance;
    NT_ASSERT((*totalSectorsProcessedSuccessfully) <= OffloadWriteContext->TotalRequestSizeSectors);

    while (bytesToAdvance != 0) {
        bytesToDo = dataSetRanges[*dataSetRangeIndex].LengthInBytes - *dataSetRangeByteOffset;
        if (bytesToDo > bytesToAdvance) {
            bytesToDo = bytesToAdvance;
        }
        (*dataSetRangeByteOffset) += bytesToDo;
        bytesToAdvance -= bytesToDo;
        if ((*dataSetRangeByteOffset) == dataSetRanges[*dataSetRangeIndex].LengthInBytes) {
            (*dataSetRangeIndex) += 1;
            (*dataSetRangeByteOffset) = 0;
        }
    }

    NT_ASSERT((*dataSetRangeIndex) <= OffloadWriteContext->DataSetRangesCount);

    return;
}


VOID
ClasspWriteUsingTokenTransferPacketDone(
    _In_ PVOID Context
    )

/*++

Routine description:

    This routine continues an offload write operation when the WRITE USING
    TOKEN transfer packet completes.

    This routine may be able to determine that all requested sectors were
    written if the WRITE USING TOKEN completed with success, or may need to
    issue a RECEIVE ROD TOKEN INFORMATION if the WRITE USING TOKEN indicated
    check condition.

    This routine is responsible for continuing or completing the offload write
    operation.

Arguments:

    Context - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload write
        operation.

Return Value:

    None.

--*/

{
    ULONGLONG entireXferLen;
    PDEVICE_OBJECT fdo;
    PFUNCTIONAL_DEVICE_EXTENSION fdoExt;
    ULONG listIdentifier;
    POFFLOAD_WRITE_CONTEXT offloadWriteContext;
    PTRANSFER_PACKET pkt;
    PIRP pseudoIrp;
    NTSTATUS status;
    PBOOLEAN tokenInvalidated;
    ULONGLONG totalSectorsToProcess;

    offloadWriteContext = Context;
    pseudoIrp = &offloadWriteContext->PseudoIrp;
    fdo = offloadWriteContext->Fdo;
    fdoExt = fdo->DeviceExtension;
    listIdentifier = offloadWriteContext->ListIdentifier;
    totalSectorsToProcess = offloadWriteContext->TotalSectorsToProcess;
    entireXferLen = offloadWriteContext->EntireXferLen;
    tokenInvalidated = &offloadWriteContext->TokenInvalidated;
    pkt = offloadWriteContext->Pkt;

    offloadWriteContext->Pkt = NULL;


    status = pseudoIrp->IoStatus.Status;
    NT_ASSERT(status != STATUS_PENDING);

    //
    // If the request failed with any of the following errors, then it is meaningless to send
    // down a ReceiveTokenInformation (regardless of whether the transfer was requested as sync
    // or async), since the target has no saved information about the command:
    // - STATUS_INVALID_TOKEN
    // - STATUS_INVALID_PARAMETER
    //
    if (status == STATUS_INVALID_PARAMETER ||
        status == STATUS_INVALID_TOKEN) {

        TracePrint((TRACE_LEVEL_ERROR,
                    TRACE_FLAG_IOCTL,
                    "ClasspWriteUsingTokenTransferPacketDone (%p): Write failed with %x (list id %x).\n",
                    fdo,
                    status,
                    listIdentifier));

        //
        // If the token isn't valid any longer, we should let the upper layers know so that
        // they don't waste time retrying the write with the same token.
        //
        if (status == STATUS_INVALID_TOKEN) {

            *tokenInvalidated = TRUE;
        }

        NT_ASSERT(status != STATUS_PENDING && !NT_SUCCESS(status));
        goto __ClasspWriteUsingTokenTransferPacketDone_Exit;

    } else if ((NT_SUCCESS(status)) &&
               (pkt->Srb->SrbStatus == SRB_STATUS_SUCCESS || pkt->TransferCount != 0)) {

        //
        // If the TokenOperation command was sent to the target requesting synchronous data
        // transfer, a success indicates that the command is complete.
        // This could either be because of a successful completion of the entire transfer
        // or because of a partial transfer due to target truncation. If it is the latter,
        // and the information field of the sense data has returned the TransferCount, we
        // can avoid sending down an RRTI.
        //
        if (pkt->Srb->SrbStatus == SRB_STATUS_SUCCESS) {

            //
            // The entire transfer has completed successfully.
            //
            offloadWriteContext->TotalSectorsProcessed = totalSectorsToProcess;
            TracePrint((TRACE_LEVEL_INFORMATION,
                        TRACE_FLAG_IOCTL,
                        "ClasspWriteUsingTokenTransferPacketDone (%p): Successfully wrote using token %I64u (out of %I64u) bytes (list Id %x).\n",
                        fdo,
                        totalSectorsToProcess * fdoExt->DiskGeometry.BytesPerSector,
                        entireXferLen,
                        listIdentifier));
        } else {

            //
            // The target has returned how much data it transferred in the response to the
            // WUT command itself, allowing us to optimize by removing the necessaity for
            // sending down an RRTI to query the TransferCount.
            //
            NT_ASSERT(pkt->TransferCount);

            offloadWriteContext->TotalSectorsProcessed = totalSectorsToProcess = pkt->TransferCount;
            TracePrint((TRACE_LEVEL_INFORMATION,
                        TRACE_FLAG_IOCTL,
                        "ClasspWriteUsingTokenTransferPacketDone (%p): Target truncated write using token %I64u (out of %I64u) bytes (list Id %x).\n",
                        fdo,
                        totalSectorsToProcess * fdoExt->DiskGeometry.BytesPerSector,
                        entireXferLen,
                        listIdentifier));
        }

        ClasspAdvanceOffloadWritePosition(offloadWriteContext, totalSectorsToProcess);

        NT_ASSERT(status != STATUS_PENDING);

        //
        // ClasspReceiveWriteUsingTokenInformationDone() takes care of
        // completing the operation (eventually), so pending from point of view
        // of this function.
        //

        ClasspReceiveWriteUsingTokenInformationDone(offloadWriteContext, status);
        status = STATUS_PENDING;

        goto __ClasspWriteUsingTokenTransferPacketDone_Exit;

    } else {

        //
        // Since the TokenOperation was failed (or the target truncated the transfer but
        // didn't indicate the amount), we need to send down ReceiveTokenInformation.
        //
        TracePrint((TRACE_LEVEL_ERROR,
                    TRACE_FLAG_IOCTL,
                    "ClasspWriteUsingTokenTransferPacketDone (%p): Write failed with status %x, %x (list id %x).\n",
                    fdo,
                    status,
                    pkt->Srb->SrbStatus,
                    listIdentifier));

        ClasspReceiveWriteUsingTokenInformation(offloadWriteContext);

        status = STATUS_PENDING;
        goto __ClasspWriteUsingTokenTransferPacketDone_Exit;
    }

__ClasspWriteUsingTokenTransferPacketDone_Exit:

    if (status != STATUS_PENDING) {
        ClasspCompleteOffloadWrite(offloadWriteContext, status);
    }

    return;
}


VOID
ClasspReceiveWriteUsingTokenInformationDone(
    _In_ POFFLOAD_WRITE_CONTEXT OffloadWriteContext,
    _In_ NTSTATUS CompletionCausingStatus
    )

/*++

Routine description:

    This routine continues an offload write operation when a WRITE USING TOKEN
    and possible associated RECEIVE ROD TOKEN INFORMATION have both fully
    completed and the RRTI has indicated completion of the WUT.

    This routine checks to see if the total sectors written is already equal to
    the overall total requested sector count, and if so, completes the offload
    write operation.  If not, this routine continues the operation by issuing
    another WUT.

    This routine is responsible for continuing or completing the offload write
    operation.

Arguments:

    OffloadWriteContext - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload
        write operation.

Return Value:

    None.

--*/

{
    PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = OffloadWriteContext->Fdo->DeviceExtension;
    PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;

    //
    // Time taken in 100ns units.
    //
    ULONGLONG durationIn100ns = (KeQueryInterruptTime() - OffloadWriteContext->OperationStartTime);
    ULONGLONG maxTargetDuration = fdoData->CopyOffloadMaxTargetDuration * 10ULL * 1000 * 1000;

    NT_ASSERT(
        OffloadWriteContext->TotalSectorsProcessedSuccessfully <=
        OffloadWriteContext->TotalRequestSizeSectors);


    if (OffloadWriteContext->TotalSectorsProcessedSuccessfully == OffloadWriteContext->TotalRequestSizeSectors) {

        ClasspCompleteOffloadWrite(OffloadWriteContext, CompletionCausingStatus);

        goto __ClasspReceiveWriteUsingTokenInformationDone_Exit;
    }

    //
    // Since we don't want a layered timeout mechanism (e.g. guest and parent OS in Hyper-V scenarios)
    // to cause a SCSI timeout for the higher layer token operations.
    //
    if (maxTargetDuration <= durationIn100ns) {

        TracePrint((TRACE_LEVEL_WARNING,
                    TRACE_FLAG_IOCTL,
                    "ClasspReceiveWriteUsingTokenInformationDone (%p): Truncating write (list id %x) because of max-duration-rule.\n",
                    OffloadWriteContext->Fdo,
                    OffloadWriteContext->ListIdentifier));

        //
        // We could technically pass in STATUS_IO_OPERATION_TIMEOUT, but ClasspCompleteOffloadWrite
        // won't end up doing anything (useful) with this status, since some bytes would already
        // have been transferred.
        //
        ClasspCompleteOffloadWrite(OffloadWriteContext, STATUS_UNSUCCESSFUL);

        goto __ClasspReceiveWriteUsingTokenInformationDone_Exit;
    }

    NT_ASSERT(
        OffloadWriteContext->TotalSectorsProcessedSuccessfully <
        OffloadWriteContext->TotalRequestSizeSectors);

    //
    // Keep going with the next sub-request.
    //

    ClasspContinueOffloadWrite(OffloadWriteContext);

__ClasspReceiveWriteUsingTokenInformationDone_Exit:

    return;
}


VOID
ClasspCompleteOffloadWrite(
    _In_ __drv_freesMem(Mem) POFFLOAD_WRITE_CONTEXT OffloadWriteContext,
    _In_ NTSTATUS CompletionCausingStatus
    )

/*++

Routine description:

    This routine is used to complete an offload write operation.

    The input CompletionCausingStatus doesn't necessarily drive the completion
    status of the offload write operation overall, if the offload write
    operation overall has previously written some sectors successfully.

    This routine completes the offload write operation.

Arguments:

    OffloadWriteContext - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload
        write operation.

    CompletionCausingStatus - Status code indicating a reason that the offload
        write operation is completing.  For success this will be STATUS_SUCCESS,
        but for a failure, this status will indicate what failure occurred.
        This status doesn't directly propagate to the completion status of the
        overall offload write operation if this status is failure and the
        overall offload write operation has already previously written some
        sectors successfully.

Return Value:

    None.

--*/

{
    PDEVICE_OBJECT fdo;
    PFUNCTIONAL_DEVICE_EXTENSION fdoExt;
    PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes;
    PULONGLONG totalSectorsProcessedSuccessfully;
    ULONGLONG entireXferLen;
    PIRP irp;
    PBOOLEAN tokenInvalidated;
    ULONG listIdentifier;
    ULONGLONG totalSectorsProcessed;
    NTSTATUS status;
    ULONGLONG totalBytesProcessed;

    fdo = OffloadWriteContext->Fdo;
    fdoExt = fdo->DeviceExtension;
    dsmAttributes = OffloadWriteContext->DsmAttributes;
    totalSectorsProcessedSuccessfully = &OffloadWriteContext->TotalSectorsProcessedSuccessfully;
    entireXferLen = OffloadWriteContext->EntireXferLen;
    irp = OffloadWriteContext->OffloadWriteDsmIrp;
    tokenInvalidated = &OffloadWriteContext->TokenInvalidated;
    listIdentifier = OffloadWriteContext->ListIdentifier;
    totalSectorsProcessed = OffloadWriteContext->TotalSectorsProcessed;
    status = CompletionCausingStatus;

    ((PSTORAGE_OFFLOAD_WRITE_OUTPUT)dsmAttributes)->OffloadWriteFlags = 0;
    ((PSTORAGE_OFFLOAD_WRITE_OUTPUT)dsmAttributes)->Reserved = 0;

    totalBytesProcessed = (*totalSectorsProcessedSuccessfully) * fdoExt->DiskGeometry.BytesPerSector;

    TracePrint((totalBytesProcessed == entireXferLen ? TRACE_LEVEL_INFORMATION : TRACE_LEVEL_WARNING,
                TRACE_FLAG_IOCTL,
                "ClasspCompleteOffloadWrite (%p): %ws wrote using token %I64u (out of %I64u) bytes (Irp %p).\n",
                fdo,
                NT_SUCCESS(status) ? L"Successful" : L"Failed",
                totalBytesProcessed,
                entireXferLen,
                irp));

    if (totalBytesProcessed > 0 && totalBytesProcessed < entireXferLen) {
        SET_FLAG(((PSTORAGE_OFFLOAD_WRITE_OUTPUT)dsmAttributes)->OffloadWriteFlags, STORAGE_OFFLOAD_WRITE_RANGE_TRUNCATED);
    }
    if (*tokenInvalidated) {
        SET_FLAG(((PSTORAGE_OFFLOAD_WRITE_OUTPUT)dsmAttributes)->OffloadWriteFlags, STORAGE_OFFLOAD_TOKEN_INVALID);
    }
    ((PSTORAGE_OFFLOAD_WRITE_OUTPUT)dsmAttributes)->LengthCopied = totalBytesProcessed;


    if (!NT_SUCCESS(status)) {

        TracePrint((TRACE_LEVEL_WARNING,
                    TRACE_FLAG_IOCTL,
                    "ClasspCompleteOffloadWrite (%p): TokenOperation for WriteUsingToken (list Id %u) completed with %x writing %I64u blocks (currentTotal %I64u blocks).\n",
                    fdo,
                    listIdentifier,
                    status,
                    totalSectorsProcessed,
                    *totalSectorsProcessedSuccessfully));

        //
        // Even if target returned an error, from the OS upper layers' perspective,
        // it is a success (with truncation) if any data at all was written.
        //
        if (*totalSectorsProcessedSuccessfully) {
            status = STATUS_SUCCESS;
        }
    }

    irp->IoStatus.Information = sizeof(STORAGE_OFFLOAD_WRITE_OUTPUT);

    ClasspCompleteOffloadRequest(fdo, irp, status);
    ClasspCleanupOffloadWriteContext(OffloadWriteContext);
    OffloadWriteContext = NULL;

    return;
}


VOID
ClasspCleanupOffloadWriteContext(
    _In_ __drv_freesMem(mem) POFFLOAD_WRITE_CONTEXT OffloadWriteContext
    )

/*++

Routine description:

    This routine cleans up an offload write context.

Arguments:

    OffloadWriteContext - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload
        write operation.

Return Value:

    None.

--*/

{
    PMDL writeUsingTokenMdl = OffloadWriteContext->WriteUsingTokenMdl;

    if (writeUsingTokenMdl) {
        ClasspFreeDeviceMdl(writeUsingTokenMdl);
    }
    FREE_POOL(OffloadWriteContext);

    return;
}


_IRQL_requires_same_
VOID
ClasspReceiveWriteUsingTokenInformation(
    _In_ POFFLOAD_WRITE_CONTEXT OffloadWriteContext
    )

/*++

Routine description:

    This routine retrieves the token after a WriteUsingToken command
    has been sent down in case of an error or if there is a need to
    poll for the result.

    This routine is responsible for continuing or completing the offload write
    operation.

Arguments:

    OffloadWriteContext - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload
        write operation.

Return Value:

    None.

--*/

{
    PVOID buffer;
    ULONG bufferLength;
    ULONG cdbLength;
    PDEVICE_OBJECT fdo;
    PIRP irp;
    ULONG listIdentifier;
    PTRANSFER_PACKET pkt;
    PIRP pseudoIrp;
    ULONG receiveTokenInformationBufferLength;
    PSCSI_REQUEST_BLOCK srb;
    NTSTATUS status;
    ULONG tempSizeUlong;
    PMDL writeUsingTokenMdl;

    fdo = OffloadWriteContext->Fdo;
    irp = OffloadWriteContext->OffloadWriteDsmIrp;
    pseudoIrp = &OffloadWriteContext->PseudoIrp;
    buffer = OffloadWriteContext + 1;
    bufferLength = OffloadWriteContext->BufferLength;
    receiveTokenInformationBufferLength = OffloadWriteContext->ReceiveTokenInformationBufferLength;
    writeUsingTokenMdl = OffloadWriteContext->WriteUsingTokenMdl;
    listIdentifier = OffloadWriteContext->ListIdentifier;
    srb = &OffloadWriteContext->Srb;
    status = STATUS_SUCCESS;

    TracePrint((TRACE_LEVEL_VERBOSE,
                TRACE_FLAG_IOCTL,
                "ClasspReceiveWriteUsingTokenInformation (%p): Entering function. Irp %p\n",
                fdo,
                irp));

    //
    // The WRITE USING TOKEN wasn't immediately fully successful, so that means
    // the only way to find out how many sectors were processed by the WRITE
    // USING TOKEN is to get a successful RECEIVE ROD TOKEN INFORMATION that
    // indicates the operation is complete.
    //

    NT_ASSERT(OffloadWriteContext->TotalSectorsProcessed == 0);

    pkt = DequeueFreeTransferPacket(fdo, TRUE);

    if (!pkt) {

        TracePrint((TRACE_LEVEL_ERROR,
                    TRACE_FLAG_IOCTL,
                    "ClasspReceiveWriteUsingTokenInformation (%p): Failed to retrieve transfer packet for ReceiveTokenInformation (WriteUsingToken) operation.\n",
                    fdo));

        status = STATUS_INSUFFICIENT_RESOURCES;

        goto __ClasspReceiveWriteUsingTokenInformation_ErrorExit;
    }

    RtlZeroMemory(buffer, bufferLength);

    tempSizeUlong = receiveTokenInformationBufferLength - 4;
    REVERSE_BYTES(((PRECEIVE_TOKEN_INFORMATION_HEADER)buffer)->AvailableData, &tempSizeUlong);

    RtlZeroMemory(pseudoIrp, sizeof(IRP));

    pseudoIrp->IoStatus.Status = STATUS_SUCCESS;
    pseudoIrp->IoStatus.Information = 0;
    pseudoIrp->Tail.Overlay.DriverContext[0] = LongToPtr(1);
    pseudoIrp->MdlAddress = writeUsingTokenMdl;

    ClasspSetupReceiveWriteUsingTokenInformationTransferPacket(
        OffloadWriteContext,
        pkt,
        bufferLength,
        (PUCHAR)buffer,
        pseudoIrp,
        listIdentifier);

    //
    // Cache away the CDB as it may be required for forwarded sense data
    // after this command completes.
    //
    RtlZeroMemory(srb, sizeof(*srb));
    cdbLength = SrbGetCdbLength(pkt->Srb);
    if (cdbLength <= 16) {
        RtlCopyMemory(&srb->Cdb, SrbGetCdb(pkt->Srb), cdbLength);
    }

    SubmitTransferPacket(pkt);

    return;

    //
    // Error label only - not used by success cases:
    //

__ClasspReceiveWriteUsingTokenInformation_ErrorExit:

    NT_ASSERT(!NT_SUCCESS(status));

    //
    // ClasspCompleteOffloadWrite also cleans up OffloadWriteContext.
    //

    ClasspCompleteOffloadWrite(OffloadWriteContext, status);

    return;
}


VOID
ClasspReceiveWriteUsingTokenInformationTransferPacketDone(
    _In_ POFFLOAD_WRITE_CONTEXT OffloadWriteContext
    )

/*++

Routine description:

    This routine continues an offload write operation when a RECEIVE ROD TOKEN
    INFORMATION transfer packet is done.

    This routine may need to send another RRTI, or it may be able to indicate
    that this WUT is done via call to
    ClasspReceiveWriteUsingTokenInformationDone().

    This routine is responsible for continuing or completing the offload write
    operation.

Arguments:

    OffloadWriteContext - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload
        write operation.

Return Value:

    None.

--*/

{
    ULONG availableData;
    PVOID buffer;
    UCHAR completionStatus;
    ULONG estimatedRetryInterval;
    PDEVICE_OBJECT fdo;
    PFUNCTIONAL_DEVICE_EXTENSION fdoExt;
    PIRP irp;
    ULONG listIdentifier;
    BOOLEAN operationCompleted;
    UCHAR operationStatus;
    PIRP pseudoIrp;
    USHORT segmentsProcessed;
    PSENSE_DATA senseData;
    ULONG senseDataFieldLength;
    UCHAR senseDataLength;
    PSCSI_REQUEST_BLOCK srb;
    NTSTATUS status;
    ULONG tokenDescriptorLength;
    PRECEIVE_TOKEN_INFORMATION_HEADER tokenInformationResults;
    PRECEIVE_TOKEN_INFORMATION_RESPONSE_HEADER tokenInformationResponsePadding;
    PBOOLEAN tokenInvalidated;
    PULONGLONG totalSectorsProcessed;
    ULONGLONG totalSectorsToProcess;
    ULONGLONG transferBlockCount;

    fdo = OffloadWriteContext->Fdo;
    fdoExt = fdo->DeviceExtension;
    listIdentifier = OffloadWriteContext->ListIdentifier;
    totalSectorsProcessed = &OffloadWriteContext->TotalSectorsProcessed;
    totalSectorsToProcess = OffloadWriteContext->TotalSectorsToProcess;
    irp = OffloadWriteContext->OffloadWriteDsmIrp;
    pseudoIrp = &OffloadWriteContext->PseudoIrp;
    tokenInvalidated = &OffloadWriteContext->TokenInvalidated;
    srb = &OffloadWriteContext->Srb;
    operationCompleted = FALSE;
    buffer = OffloadWriteContext + 1;
    tokenInformationResults = (PRECEIVE_TOKEN_INFORMATION_HEADER)buffer;
    senseData = (PSENSE_DATA)((PUCHAR)tokenInformationResults + FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_HEADER, SenseData));
    transferBlockCount = 0;
    tokenInformationResponsePadding = NULL;
    tokenDescriptorLength = 0;

    NT_ASSERT((*totalSectorsProcessed) == 0);

    OffloadWriteContext->Pkt = NULL;


    status = pseudoIrp->IoStatus.Status;
    NT_ASSERT(status != STATUS_PENDING);

    //
    // The buffer we hand allows for the max sizes for all the fields whereas the returned
    // data may be lesser (e.g. sense data info will almost never be MAX_SENSE_BUFFER_SIZE, etc.
    // so handle underrun "error"
    //
    if (status == STATUS_DATA_OVERRUN) {

        status = STATUS_SUCCESS;
    }

    if (!NT_SUCCESS(status)) {
        TracePrint((TRACE_LEVEL_ERROR,
                    TRACE_FLAG_IOCTL,
                    "ClasspReceiveWriteUsingTokenInformationTransferPacketDone (%p): Failed with %x to retrieve write results for list Id %x for data size %I64u bytes.\n",
                    fdo,
                    status,
                    listIdentifier,
                    totalSectorsToProcess * fdoExt->DiskGeometry.BytesPerSector));

        NT_ASSERT((*totalSectorsProcessed) == 0);

        goto __ClasspReceiveWriteUsingTokenInformationTransferPacketDone_ErrorExit;
    }

    REVERSE_BYTES(&availableData, &tokenInformationResults->AvailableData);

    NT_ASSERT(availableData <= FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_HEADER, SenseData) + MAX_SENSE_BUFFER_SIZE);

    NT_ASSERT(tokenInformationResults->ResponseToServiceAction == SERVICE_ACTION_WRITE_USING_TOKEN);

    operationStatus = tokenInformationResults->OperationStatus;
    operationCompleted = ClasspIsTokenOperationComplete(operationStatus);
    NT_ASSERT(operationCompleted);

    REVERSE_BYTES(&estimatedRetryInterval, &tokenInformationResults->EstimatedStatusUpdateDelay);

    completionStatus = tokenInformationResults->CompletionStatus;

    senseDataFieldLength = tokenInformationResults->SenseDataFieldLength;
    senseDataLength = tokenInformationResults->SenseDataLength;
    NT_ASSERT(senseDataFieldLength >= senseDataLength);

    tokenInformationResponsePadding = (PRECEIVE_TOKEN_INFORMATION_RESPONSE_HEADER)((PUCHAR)tokenInformationResults +
                                                                                           FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_HEADER, SenseData) +
                                                                                           tokenInformationResults->SenseDataFieldLength);

    REVERSE_BYTES(&tokenDescriptorLength, &tokenInformationResponsePadding->TokenDescriptorsLength);
    NT_ASSERT(tokenDescriptorLength == 0);

    NT_ASSERT(tokenInformationResults->TransferCountUnits == TRANSFER_COUNT_UNITS_NUMBER_BLOCKS);
    REVERSE_BYTES_QUAD(&transferBlockCount, &tokenInformationResults->TransferCount);

    REVERSE_BYTES_SHORT(&segmentsProcessed, &tokenInformationResults->SegmentsProcessed);
    NT_ASSERT(segmentsProcessed == 0);

    if (operationCompleted) {

        if (transferBlockCount > totalSectorsToProcess) {

            //
            // Buggy or hostile target.  Don't let it claim more was procesed
            // than was requested.  Since this is likely a bug and it's unknown
            // how much was actually transferred, assume no data was
            // transferred.
            //

            NT_ASSERT(transferBlockCount <= totalSectorsToProcess);
            transferBlockCount = 0;
        }

        if (operationStatus != OPERATION_COMPLETED_WITH_SUCCESS &&
            operationStatus != OPERATION_COMPLETED_WITH_RESIDUAL_DATA) {

            //
            // Assert on buggy response from target, but in any case, make sure not
            // to claim that any data was written.
            //

            NT_ASSERT(transferBlockCount == 0);
            transferBlockCount = 0;
        }

        //
        // Since the TokenOperation was sent down synchronously but failed, the operation is complete as soon as the
        // ReceiveTokenInformation command returns.
        //

        NT_ASSERT((*totalSectorsProcessed) == 0);
        *totalSectorsProcessed = transferBlockCount;
        ClasspAdvanceOffloadWritePosition(OffloadWriteContext, transferBlockCount);

        if (transferBlockCount < totalSectorsToProcess) {

            NT_ASSERT(operationStatus == OPERATION_COMPLETED_WITH_RESIDUAL_DATA ||
                      operationStatus == OPERATION_COMPLETED_WITH_ERROR ||
                      operationStatus == OPERATION_TERMINATED);

        } else {

            NT_ASSERT(operationStatus == OPERATION_COMPLETED_WITH_SUCCESS);
        }

        TracePrint((transferBlockCount == totalSectorsToProcess ? TRACE_LEVEL_INFORMATION : TRACE_LEVEL_WARNING,
                    TRACE_FLAG_IOCTL,
                    "ClasspReceiveWriteUsingTokenInformationTransferPacketDone (%p): %wsSuccessfully wrote (for list Id %x) for data size %I64u bytes\n",
                    fdo,
                    transferBlockCount == totalSectorsToProcess ? L"" : L"Target truncated write. ",
                    listIdentifier,
                    (*totalSectorsProcessed) * fdoExt->DiskGeometry.BytesPerSector));

        //
        // Operation that completes with success can have sense data (for target to pass on some extra info)
        // but we don't care about such sense info.
        // Operation that complete but not with success, may not have sense data associated, but may
        // have valid CompletionStatus.
        //
        // The "status" may be overriden by ClassInterpretSenseInfo().  Final
        // status is determined a bit later - this is just the default status
        // when ClassInterpretSenseInfo() doesn't get to run here.
        //
        status = STATUS_SUCCESS;
        if (operationStatus == OPERATION_COMPLETED_WITH_ERROR ||
            operationStatus == OPERATION_COMPLETED_WITH_RESIDUAL_DATA ||
            operationStatus == OPERATION_TERMINATED) {

            SrbSetScsiStatus((PSTORAGE_REQUEST_BLOCK_HEADER)srb, completionStatus);

            if (senseDataLength) {

                ULONG retryInterval;

                NT_ASSERT(senseDataLength <= sizeof(SENSE_DATA));

                srb->Function = SRB_FUNCTION_EXECUTE_SCSI;

                srb->SrbStatus = SRB_STATUS_AUTOSENSE_VALID | SRB_STATUS_ERROR;
                SrbSetSenseInfoBuffer((PSTORAGE_REQUEST_BLOCK_HEADER)srb, senseData);
                SrbSetSenseInfoBufferLength((PSTORAGE_REQUEST_BLOCK_HEADER)srb, senseDataLength);

                ClassInterpretSenseInfo(fdo,
                                        srb,
                                        IRP_MJ_SCSI,
                                        0,
                                        0,
                                        &status,
                                        &retryInterval);

                TracePrint((TRACE_LEVEL_WARNING,
                            TRACE_FLAG_IOCTL,
                            "ClasspReceiveWriteUsingTokenInformationTransferPacketDone (%p): Reason for truncation/failure: %x - for list Id %x for data size %I64u bytes.\n",
                            fdo,
                            status,
                            listIdentifier,
                            transferBlockCount * fdoExt->DiskGeometry.BytesPerSector));

                //
                // If the token isn't valid any longer, we should let the upper layers know so that
                // they don't waste time retrying the write with the same token.
                //
                if (status == STATUS_INVALID_TOKEN) {

                    *tokenInvalidated = TRUE;
                }
            } else {

                TracePrint((TRACE_LEVEL_WARNING,
                            TRACE_FLAG_IOCTL,
                            "ClasspReceiveWriteUsingTokenInformationTransferPacketDone (%p): No sense data available but reason for truncation/failure, possibly: %x - for list Id %x for data size %I64u bytes.\n",
                            fdo,
                            completionStatus,
                            listIdentifier,
                            transferBlockCount * fdoExt->DiskGeometry.BytesPerSector));
            }
        }

        //
        // Initialize status. Upper layer needs to know if command failed because it
        // timed out without doing any writing.  ClasspCompleteOffloadWrite() will
        // force status to success if any data was written, so it's this function's
        // job to set the status appropriately based on the outcome of this
        // WRITE USING TOKEN command, and then ClasspCompleteOffloadWrite()
        // can override with success if previos WRITE USING TOKEN commands
        // issued for the same upper request were able to write some data.
        //
        if (transferBlockCount != 0) {
            status = STATUS_SUCCESS;
        } else {

            if (NT_SUCCESS(status)) {
                //
                // Make sure status is a failing status, without throwing away an
                // already-failing status obtained from sense data.
                //
                status = STATUS_UNSUCCESSFUL;
            }
        }

        NT_ASSERT(status != STATUS_PENDING);

        if (!NT_SUCCESS(status)) {
            goto __ClasspReceiveWriteUsingTokenInformationTransferPacketDone_ErrorExit;
        }

        ClasspReceiveWriteUsingTokenInformationDone(OffloadWriteContext, status);
        status = STATUS_PENDING;
        goto __ClasspReceiveWriteUsingTokenInformationTransferPacketDone_Exit;

    } else {

        status = STATUS_UNSUCCESSFUL;

        goto __ClasspReceiveWriteUsingTokenInformationTransferPacketDone_ErrorExit;
    }

    //
    // Error label only - not used in success case:
    //

__ClasspReceiveWriteUsingTokenInformationTransferPacketDone_ErrorExit:

    NT_ASSERT(!NT_SUCCESS(status));

    ClasspCompleteOffloadWrite(OffloadWriteContext, status);

__ClasspReceiveWriteUsingTokenInformationTransferPacketDone_Exit:

    //
    // Due to tracing a potentially freed pointer value "Irp", this trace could
    // be delayed beyond another offload op picking up the same pointer value
    // for its Irp.
    //

    TracePrint((TRACE_LEVEL_VERBOSE,
                TRACE_FLAG_IOCTL,
                "ClasspReceiveWriteUsingTokenInformationTransferPacketDone (%p): Exiting function (Irp %p) with status %x.\n",
                fdo,
                irp,
                status));

    return;
}


NTSTATUS
ClasspRefreshFunctionSupportInfo(
    _Inout_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
    _In_ BOOLEAN ForceQuery
    )
/*
Routine Description:

    This function is to update various properties described in FDO extension's
    CLASS_FUNCTION_SUPPORT_INFO structure by requerying the device's VPD pages.
    Although this function is capable of updating any properties in the
    CLASS_FUNCTION_SUPPORT_INFO structure, it will initially support only
    a small number of proporties in the block limit data

Arguments:

    FdoExtension : FDO extension

    ForceQuery : TRUE if the caller wants to force a query of the device's
        VPD pages.  Otherwise, the function may use cached data.

Return Value:

    STATUS_SUCCESS or an error status

--*/
{
    NTSTATUS            status;
    PSCSI_REQUEST_BLOCK srb = NULL;
    ULONG               srbSize;
    CLASS_VPD_B0_DATA   blockLimitsDataNew;
    PCLASS_VPD_B0_DATA  blockLimitsDataOriginal;
    KLOCK_QUEUE_HANDLE  lockHandle;
    ULONG               generationCount;
    ULONG               changeRequestCount;

    //
    // ChangeRequestCount is incremented every time we get an unit attention with
    // SCSI_ADSENSE_OPERATING_CONDITIONS_CHANGED.  GenerationCount will be set to
    // ChangeRequestCount after CLASS_FUNCTION_SUPPORT_INFO is refreshed with the latest
    // VPD data.  i.e.  if both values are the same, data in
    // CLASS_FUNCTION_SUPPORT_INFO is current
    //

    generationCount = FdoExtension->FunctionSupportInfo->GenerationCount;
    changeRequestCount = FdoExtension->FunctionSupportInfo->ChangeRequestCount;
    if (!ForceQuery && generationCount == changeRequestCount) {
        return STATUS_SUCCESS;
    }

    //
    // Allocate an SRB for querying the device for LBP-related info if either
    // the Logical Block Provisioning (0xB2) or Block Limits (0xB0) VPD page
    // exists.
    //
    if ((FdoExtension->AdapterDescriptor != NULL) &&
        (FdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK)) {
        srbSize = CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE;
    } else {
        srbSize = sizeof(SCSI_REQUEST_BLOCK);
    }

    srb = ExAllocatePoolWithTag(NonPagedPoolNx, srbSize, '1DcS');
    if (srb == NULL) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    status = ClasspDeviceGetBlockLimitsVPDPage(FdoExtension,
                                               srb,
                                               srbSize,
                                               &blockLimitsDataNew);

    if (NT_SUCCESS(status)) {

        KeAcquireInStackQueuedSpinLock(&FdoExtension->FunctionSupportInfo->SyncLock, &lockHandle);

        //
        // If the generationCount didn't change since we looked at it last time, it means
        // no one has tried to update the CLASS_FUNCTION_SUPPORT_INFO data; otherwise, someone
        // else has beat us to it.
        //
        if (generationCount == FdoExtension->FunctionSupportInfo->GenerationCount) {

            blockLimitsDataOriginal = &FdoExtension->FunctionSupportInfo->BlockLimitsData;
            if (blockLimitsDataOriginal->CommandStatus == -1) {
                //
                // CommandStatus == -1 means this is the first time we have
                // gotten the block limits data.
                //
                *blockLimitsDataOriginal = blockLimitsDataNew;
            } else {
                //
                // We only expect the Optimal Unmap Granularity (and alignment)
                // to change, so those are the only parameters we update.
                //
                blockLimitsDataOriginal->UGAVALID = blockLimitsDataNew.UGAVALID;
                blockLimitsDataOriginal->UnmapGranularityAlignment = blockLimitsDataNew.UnmapGranularityAlignment;
                blockLimitsDataOriginal->OptimalUnmapGranularity = blockLimitsDataNew.OptimalUnmapGranularity;
            }
            FdoExtension->FunctionSupportInfo->GenerationCount = changeRequestCount;
        }

        KeReleaseInStackQueuedSpinLock(&lockHandle);
    }

    FREE_POOL(srb);
    return status;
}

NTSTATUS
ClasspBlockLimitsDataSnapshot(
    _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
    _In_ BOOLEAN ForceQuery,
    _Out_ PCLASS_VPD_B0_DATA BlockLimitsData,
    _Out_ PULONG GenerationCount
    )
/*
Routine Description:

    This function is to get a copy of the latest block limits data.

    When this function is called multiple times, GenerationCount can change (value always goes up)
    while BlockLimitsData stays the same.  In this case, the caller should assume BlockLimitsData
    has changed to different values and eventually changed back to the same state when the first
    call to this function was made.

Arguments:

    FdoExtension : FDO extension

    ForceQuery : TRUE if the caller wants to force a query of the device's
        VPD pages.  Otherwise, the function may use cached data.

    BlockLimitsData : pointer to memory that will receive the block limits data

    GenerationCount : generation count of the block limit data.

    DataIsOutdated: set to TRUE if the BlockLimitsData is old but this function fails to
    query the latest data from the device due to insufficient resources

Return Value:

    STATUS_SUCCESS or an error status

--*/
{
    NTSTATUS            status;
    KLOCK_QUEUE_HANDLE  lockHandle;

    status = ClasspRefreshFunctionSupportInfo(FdoExtension, ForceQuery);

    KeAcquireInStackQueuedSpinLock(&FdoExtension->FunctionSupportInfo->SyncLock, &lockHandle);
    *BlockLimitsData = FdoExtension->FunctionSupportInfo->BlockLimitsData;
    *GenerationCount = FdoExtension->FunctionSupportInfo->GenerationCount;
    KeReleaseInStackQueuedSpinLock(&lockHandle);

    return status;
}

