Sample Code

Windows Driver Samples/ CDROM Storage Class Driver/ C++/ src/ sense.c/

/*--

Copyright (C) Microsoft Corporation. All rights reserved.

Module Name:

    sense.c

Abstract:

    This file contains the methods needed to accurately
    determine how to retry requests on CDROM device types.

Environment:

    kernel mode only

Revision History:

--*/

#include "stddef.h"
#include "string.h"

#include "ntddk.h"
#include "ntddstor.h"
#include "cdrom.h"
#include "ntstrsafe.h"


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

// Forward declarations
VOID
SenseInfoInterpretRefineByIoControl(
    _In_      PCDROM_DEVICE_EXTENSION   DeviceExtension,
    _In_      ULONG                     IoControlCode,
    _In_      BOOLEAN                   OverrideVerifyVolume,
    _Inout_   BOOLEAN*                  Retry,
    _Inout_   NTSTATUS*                 Status
    );


#ifdef ALLOC_PRAGMA

#pragma alloc_text(PAGE, SenseInfoInterpretRefineByIoControl)

#endif


//
// FROM CLASSPNP\CLASSP.H
//  Lots of retries of synchronized SCSI commands that devices may not
//  even support really slows down the system (especially while booting).
//  (Even GetDriveCapacity may be failed on purpose if an external disk is powered off).
//  If a disk cannot return a small initialization buffer at startup
//  in two attempts (with delay interval) then we cannot expect it to return
//  data consistently with four retries.
//  So don't set the retry counts as high here as for data SRBs.
//
//  If we find that these requests are failing consecutively,
//  despite the retry interval, on otherwise reliable media,
//  then we should either increase the retry interval for
//  that failure or (by all means) increase these retry counts as appropriate.
//

#define TOTAL_COUNT_RETRY_DEFAULT       4
#define TOTAL_COUNT_RETRY_LOCK_MEDIA    1
#define TOTAL_COUNT_RETRY_MODESENSE     1
#define TOTAL_COUNT_RETRY_READ_CAPACITY 1


#define TOTAL_SECONDS_RETRY_TIME_WRITE          160
#define TOTAL_SECONDS_RETRY_TIME_MEDIUM_REMOVAL 120

typedef struct _ERROR_LOG_CONTEXT {
    BOOLEAN     LogError;
    BOOLEAN     ErrorUnhandled;
    NTSTATUS    ErrorCode;
    ULONG       UniqueErrorValue;
    ULONG       BadSector;
} ERROR_LOG_CONTEXT, *PERROR_LOG_CONTEXT;

NTSTATUS
DeviceErrorHandlerForMmc(
    _In_ PCDROM_DEVICE_EXTENSION  DeviceExtension,
    _In_ PSCSI_REQUEST_BLOCK      Srb,
    _Inout_ PNTSTATUS             Status,
    _Inout_ PBOOLEAN              Retry
    )
/*++

Routine Description:

    this routine will be used for error handler for all MMC devices.
    it's invoked by DeviceErrorHandler()that invoked by SenseInfoInterpret() or GESN

    This routine just checks for media change sense/asc/ascq and
    also for other events, such as bus resets.  this is used to
    determine if the device behaviour has changed, to allow for
    read and write operations to be allowed and/or disallowed.

Arguments:

    DeviceExtension - device context
    Srb - SRB structure for analyze

Return Value:

    NTSTATUS
    Status - 
    Retry - 

--*/
{
    BOOLEAN mediaChange = FALSE;
    PCDB    cdb = (PCDB)Srb->Cdb;

    if (TEST_FLAG(Srb->SrbStatus, SRB_STATUS_AUTOSENSE_VALID)) 
    {
        PSENSE_DATA senseBuffer = Srb->SenseInfoBuffer;

        // the following sense keys could indicate a change in capabilities.

        // we used to expect this to be serialized, and only hit from our
        // own routine. we now allow some requests to continue during our
        // processing of the capabilities update in order to allow
        // IoReadPartitionTable() to succeed.
        switch (senseBuffer->SenseKey & 0xf) 
        {

        case SCSI_SENSE_NOT_READY: 
        {
            if (senseBuffer->AdditionalSenseCode == SCSI_ADSENSE_NO_MEDIA_IN_DEVICE) 
            {
                if (DeviceExtension->DeviceAdditionalData.Mmc.WriteAllowed) 
                {
                    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,
                               "DeviceErrorHandlerForMmc: media removed, writes will be "
                               "failed until new media detected\n"));
                }

                // NOTE - REF #0002
                DeviceExtension->DeviceAdditionalData.Mmc.WriteAllowed = FALSE;
            } 
            else if (senseBuffer->AdditionalSenseCode == SCSI_ADSENSE_LUN_NOT_READY) 
            {
                if (senseBuffer->AdditionalSenseCodeQualifier == SCSI_SENSEQ_BECOMING_READY) 
                {
                    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,
                               "DeviceErrorHandlerForMmc: media becoming ready, "
                               "SHOULD notify shell of change time by sending "
                               "GESN request immediately!\n"));
                } 
                else if (((senseBuffer->AdditionalSenseCodeQualifier == SCSI_SENSEQ_OPERATION_IN_PROGRESS) ||
                            (senseBuffer->AdditionalSenseCodeQualifier == SCSI_SENSEQ_LONG_WRITE_IN_PROGRESS)
                            ) &&
                           ((Srb->Cdb[0] == SCSIOP_READ) ||
                            (Srb->Cdb[0] == SCSIOP_READ6) ||
                            (Srb->Cdb[0] == SCSIOP_READ_CAPACITY) ||
                            (Srb->Cdb[0] == SCSIOP_READ_CD) ||
                            (Srb->Cdb[0] == SCSIOP_READ_CD_MSF) ||
                            (Srb->Cdb[0] == SCSIOP_READ_TOC) ||
                            (Srb->Cdb[0] == SCSIOP_WRITE) ||
                            (Srb->Cdb[0] == SCSIOP_WRITE6) ||
                            (Srb->Cdb[0] == SCSIOP_READ_TRACK_INFORMATION) ||
                            (Srb->Cdb[0] == SCSIOP_READ_DISK_INFORMATION)
                            )
                           ) 
                {
                    TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL,
                               "DeviceErrorHandlerForMmc: LONG_WRITE or "
                               "OP_IN_PROGRESS for limited subset of cmds -- "
                               "setting retry to TRUE\n"));
                    *Retry = TRUE;
                    *Status = STATUS_DEVICE_BUSY;
                }
            }
            break;
        } // end SCSI_SENSE_NOT_READY

        case SCSI_SENSE_UNIT_ATTENTION: 
        {
            switch (senseBuffer->AdditionalSenseCode) 
            {
            case SCSI_ADSENSE_MEDIUM_CHANGED: 
            {
                // always update if the medium may have changed

                // NOTE - REF #0002
                DeviceExtension->DeviceAdditionalData.Mmc.WriteAllowed = FALSE;
                DeviceExtension->DeviceAdditionalData.Mmc.UpdateState = CdromMmcUpdateRequired;

                TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,
                           "DeviceErrorHandlerForMmc: media change detected, need to "
                           "update drive capabilities\n"));
                mediaChange = TRUE;
                break;

            } // end SCSI_ADSENSE_MEDIUM_CHANGED

            case SCSI_ADSENSE_BUS_RESET:
            {
                // NOTE - REF #0002
                DeviceExtension->DeviceAdditionalData.Mmc.WriteAllowed = FALSE;
                DeviceExtension->DeviceAdditionalData.Mmc.UpdateState = CdromMmcUpdateRequired;

                TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,
                           "DeviceErrorHandlerForMmc: bus reset detected, need to "
                           "update drive capabilities\n"));
                break;

            } // end SCSI_ADSENSE_BUS_RESET

            case SCSI_ADSENSE_OPERATOR_REQUEST: 
            {

                BOOLEAN b = FALSE;

                switch (senseBuffer->AdditionalSenseCodeQualifier)
                {
                case SCSI_SENSEQ_MEDIUM_REMOVAL: 
                {
                    // eject notification currently handled by classpnp
                    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,
                               "DeviceErrorHandlerForMmc: Eject requested by user\n"));
                    *Retry = TRUE;
                    *Status = STATUS_DEVICE_BUSY;
                    break;
                }

                case SCSI_SENSEQ_WRITE_PROTECT_DISABLE:
                    b = TRUE;
                case SCSI_SENSEQ_WRITE_PROTECT_ENABLE: 
                {
                    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,
                               "DeviceErrorHandlerForMmc: Write protect %s requested "
                               "by user\n",
                               (b ? "disable" : "enable")));
                    *Retry = TRUE;
                    *Status = STATUS_DEVICE_BUSY;
                    // NOTE - REF #0002
                    DeviceExtension->DeviceAdditionalData.Mmc.WriteAllowed = FALSE;
                    DeviceExtension->DeviceAdditionalData.Mmc.UpdateState = CdromMmcUpdateRequired;

                    break;
                }

                } // end of AdditionalSenseCodeQualifier switch


                break;

            } // end SCSI_ADSENSE_OPERATOR_REQUEST

            default: 
            {
                TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL,
                           "DeviceErrorHandlerForMmc: Unit attention %02x/%02x\n",
                           senseBuffer->AdditionalSenseCode,
                           senseBuffer->AdditionalSenseCodeQualifier));
                break;
            }

            } // end of AdditionSenseCode switch
            break;

        } // end SCSI_SENSE_UNIT_ATTENTION

        case SCSI_SENSE_ILLEGAL_REQUEST: 
        {
            if (senseBuffer->AdditionalSenseCode == SCSI_ADSENSE_WRITE_PROTECT) 
            {
                if (DeviceExtension->DeviceAdditionalData.Mmc.WriteAllowed)
                {
                    TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL,
                               "DeviceErrorHandlerForMmc: media was writable, but "
                               "failed request with WRITE_PROTECT error...\n"));
                }
                // NOTE - REF #0002
                // do not update all the capabilities just because
                // we can't write to the disc.
                DeviceExtension->DeviceAdditionalData.Mmc.WriteAllowed = FALSE;
            }
            break;
        } // end SCSI_SENSE_ILLEGAL_REQUEST

        } // end of SenseKey switch

        // Check if we failed to set the DVD region key and send appropriate error
        if (cdb->CDB16.OperationCode == SCSIOP_SEND_KEY) 
        {
            if (cdb->SEND_KEY.KeyFormat == DvdSetRpcKey) 
            {
                if (senseBuffer->AdditionalSenseCode == SCSI_ADSENSE_NO_MEDIA_IN_DEVICE) 
                {
                    // media of appropriate region required
                    *Status = STATUS_NO_MEDIA_IN_DEVICE;
                    *Retry = FALSE;
                } 
                else if ((senseBuffer->SenseKey == SCSI_SENSE_ILLEGAL_REQUEST) &&
                           (senseBuffer->AdditionalSenseCode == SCSI_ADSENSE_COPY_PROTECTION_FAILURE) &&
                           (senseBuffer->AdditionalSenseCodeQualifier == SCSI_SENSEQ_MEDIA_CODE_MISMATCHED_TO_LOGICAL_UNIT)) 
                {
                    // media of appropriate region required
                    *Status = STATUS_CSS_REGION_MISMATCH;
                    *Retry = FALSE;
                } 
                else if ((senseBuffer->SenseKey == SCSI_SENSE_ILLEGAL_REQUEST) &&
                           (senseBuffer->AdditionalSenseCode == SCSI_ADSENSE_INVALID_MEDIA) &&
                           (senseBuffer->AdditionalSenseCodeQualifier == SCSI_SENSEQ_INCOMPATIBLE_FORMAT)) 
                {
                    // media of appropriate region required
                    *Status = STATUS_CSS_REGION_MISMATCH;
                    *Retry = FALSE;
                }
            }
        }
    } // end of SRB_STATUS_AUTOSENSE_VALID

    // On media change, if device speed should be reset to default then
    // queue a workitem to send the commands to the device. Do this on
    // media arrival as some device will fail this command if no media
    // is present. Ignore the fake media change from classpnp driver.
    if ((mediaChange == TRUE) && (*Status != STATUS_MEDIA_CHANGED)) 
    {
        if (DeviceExtension->DeviceAdditionalData.RestoreDefaults == TRUE) 
        {
            NTSTATUS                status = STATUS_SUCCESS;
            WDF_OBJECT_ATTRIBUTES   attributes;
            WDF_WORKITEM_CONFIG     workitemConfig;
            WDFWORKITEM             workItem;

            WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
            attributes.ParentObject = DeviceExtension->Device;

            WDF_WORKITEM_CONFIG_INIT(&workitemConfig,
                                     DeviceRestoreDefaultSpeed);

            status = WdfWorkItemCreate(&workitemConfig,
                                       &attributes,
                                       &workItem);
            if (!NT_SUCCESS(status))
            {
                return STATUS_SUCCESS;
            }

            WdfWorkItemEnqueue(workItem);

            TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, 
                        "DeviceErrorHandlerForMmc: Restore device default speed for %p\n",
                        DeviceExtension->DeviceObject));
        }
    }
    return STATUS_SUCCESS;
}

NTSTATUS
DeviceErrorHandlerForHitachiGD2000(
    _In_ PCDROM_DEVICE_EXTENSION  DeviceExtension,
    _In_ PSCSI_REQUEST_BLOCK      Srb,
    _Inout_ PNTSTATUS             Status,
    _Inout_ PBOOLEAN              Retry
    )
/*++

Routine Description:

   error handler for HITACHI CDR-1750S, CDR-3650/1650S

   This routine checks the type of error.  If the error suggests that the
   drive has spun down and cannot reinitialize itself, send a
   START_UNIT or READ to the device.  This will force the drive to spin
   up.  This drive also loses the AGIDs it has granted when it spins down,
   which may result in playback failure the first time around.

Arguments:

    DeviceExtension - the device object.

    Srb - Supplies a pointer to the failing Srb.

    Status - return the final status for this command?

    Retry - return if the command should be retried.

Return Value:

    None.

--*/
{
    PSENSE_DATA senseBuffer = Srb->SenseInfoBuffer;

    if (!TEST_FLAG(Srb->SrbStatus, SRB_STATUS_AUTOSENSE_VALID)) 
    {
        return STATUS_SUCCESS; //nobody cares about this return value yet.
    }

    if (((senseBuffer->SenseKey & 0xf) == SCSI_SENSE_HARDWARE_ERROR) &&
        (senseBuffer->AdditionalSenseCode == 0x44)) 
    {
        TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
                    "DeviceErrorHandlerForHitachiGD2000 (%p) => Internal Target "
                    "Failure Detected -- spinning up drive\n", DeviceExtension->Device));

        // the request should be retried because the device isn't ready
        *Retry = TRUE;
        *Status = STATUS_DEVICE_NOT_READY;

        // send a START_STOP unit to spin up the drive
        // NOTE: this temporarily violates the StartIo serialization
        //       mechanism, but the completion routine on this will NOT
        //       call StartNextPacket(), so it's a temporary disruption
        //       of the serialization only.
        DeviceSendStartUnit(DeviceExtension->Device);
    }

    return STATUS_SUCCESS;
}


VOID
SenseInfoRequestGetInformation(
    _In_  WDFREQUEST  Request,
    _Out_ UCHAR*      MajorFunctionCode,
    _Out_ ULONG*      IoControlCode,
    _Out_ BOOLEAN*    OverrideVerifyVolume,
    _Out_ ULONGLONG*  Total100nsSinceFirstSend
    )
{
    PCDROM_REQUEST_CONTEXT requestContext = RequestGetContext(Request);

    *MajorFunctionCode = 0;
    *IoControlCode = 0;
    *OverrideVerifyVolume = FALSE;
    *Total100nsSinceFirstSend = 0;

    if (requestContext->OriginalRequest != NULL)
    {
        PIO_STACK_LOCATION originalIrpStack = NULL;
        
        PIRP originalIrp = WdfRequestWdmGetIrp(requestContext->OriginalRequest);

        if (originalIrp != NULL)
        {
            originalIrpStack = IoGetCurrentIrpStackLocation(originalIrp);
        }

        if (originalIrpStack != NULL)
        {
            *MajorFunctionCode = originalIrpStack->MajorFunction;

            if (*MajorFunctionCode == IRP_MJ_DEVICE_CONTROL)
            {
                *IoControlCode = originalIrpStack->Parameters.DeviceIoControl.IoControlCode;
            }

            *OverrideVerifyVolume = TEST_FLAG(originalIrpStack->Flags, SL_OVERRIDE_VERIFY_VOLUME);
        }
    }

    // Calculate time past since the request was first time sent.
    if (requestContext->TimeSentDownFirstTime.QuadPart > 0)
    {
        LARGE_INTEGER tmp;
        KeQueryTickCount(&tmp);
        tmp.QuadPart -= requestContext->TimeSentDownFirstTime.QuadPart;
        tmp.QuadPart *= KeQueryTimeIncrement();
        *Total100nsSinceFirstSend = tmp.QuadPart;
    }
    else
    {
        // set to -1 if field TimeSentDownFirstTime not set.
        *Total100nsSinceFirstSend = (ULONGLONG) -1;
    }

    return;
}

BOOLEAN
SenseInfoInterpretByAdditionalSenseCode(
    _In_      PCDROM_DEVICE_EXTENSION   DeviceExtension,
    _In_      PSCSI_REQUEST_BLOCK       Srb,
    _In_      UCHAR                     AdditionalSenseCode,
    _In_      UCHAR                     AdditionalSenseCodeQual,
    _Inout_   NTSTATUS*                 Status,
    _Inout_   BOOLEAN*                  Retry,
    _Out_ _Deref_out_range_(0,100) ULONG*         RetryIntervalInSeconds,  
    _Inout_   PERROR_LOG_CONTEXT        LogContext
    )
/*
    This function will interpret error based on ASC/ASCQ. 

    If the error code is not processed in this function, e.g. return value is TRUE, 
    caller needs to call SenseInfoInterpretBySenseKey() for further interpret.
*/
{
    BOOLEAN     needFurtherInterpret = TRUE;
    PSENSE_DATA senseBuffer = (PSENSE_DATA)Srb->SenseInfoBuffer;

    // set default values for retry fields.
    *Status = STATUS_IO_DEVICE_ERROR;
    *Retry = TRUE;
    *RetryIntervalInSeconds = 0;

    switch (AdditionalSenseCode) 
    {
    case SCSI_ADSENSE_LUN_NOT_READY: 
        {
            TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, 
                        "SenseInfoInterpretByAdditionalSenseCode: Lun not ready\n"));

            //
            //  Many non-WHQL certified drives (mostly CD-RW) return
            //  2/4/0 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.
            //
            //  However, we have to retry on 2/4/0 (Not ready, LUN not ready, no info) 
            //  and also 3/2/0 (no seek complete).
            //
            //  These conditions occur when the shell tries to examine an
            //  injected CD (e.g. for autoplay) before the CD is spun up.
            //
            //  The drive should be returning an ASCQ of SCSI_SENSEQ_BECOMING_READY
            //  (0x01) in order to comply with WHQL standards.
            //
            //  The default retry timeout of one second is acceptable to balance
            //  these discrepencies.  don't modify the status, though....
            //
            
            switch (AdditionalSenseCodeQual) 
            {
            case SCSI_SENSEQ_OPERATION_IN_PROGRESS: 
                {
                    DEVICE_EVENT_BECOMING_READY notReady = {0};

                    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, 
                                "SenseInfoInterpretByAdditionalSenseCode: Operation In Progress\n"));

                    needFurtherInterpret = FALSE;
            
                    *Retry = TRUE;
                    *RetryIntervalInSeconds = NOT_READY_RETRY_INTERVAL;
                    *Status = STATUS_DEVICE_NOT_READY;

                    notReady.Version = 1;
                    notReady.Reason = 2;
                    notReady.Estimated100msToReady = *RetryIntervalInSeconds * 10;
                    DeviceSendNotification(DeviceExtension,
                                           &GUID_IO_DEVICE_BECOMING_READY,
                                           sizeof(DEVICE_EVENT_BECOMING_READY),
                                           &notReady);

                    break;
                }

            case SCSI_SENSEQ_BECOMING_READY: 
                {
                    DEVICE_EVENT_BECOMING_READY notReady = {0};

                    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,
                                "SenseInfoInterpretByAdditionalSenseCode: In process of becoming ready\n"));

                    needFurtherInterpret = FALSE;
            
                    *Retry = TRUE;
                    *RetryIntervalInSeconds = NOT_READY_RETRY_INTERVAL;
                    *Status = STATUS_DEVICE_NOT_READY;

                    notReady.Version = 1;
                    notReady.Reason = 1;
                    notReady.Estimated100msToReady = *RetryIntervalInSeconds * 10;
                    DeviceSendNotification(DeviceExtension,
                                           &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, 
                                "SenseInfoInterpretByAdditionalSenseCode: Long write in progress\n"));

                    needFurtherInterpret = FALSE;
            
                    // This has been seen as a transcient failure on some drives
                    *Status = STATUS_DEVICE_NOT_READY;
                    *Retry = TRUE;
                    // Set retry interval to be 0 as the drive can be ready at anytime.
                    *RetryIntervalInSeconds = 0;

                    break;
                }

            case SCSI_SENSEQ_MANUAL_INTERVENTION_REQUIRED: 
                {
                    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, 
                                "SenseInfoInterpretByAdditionalSenseCode: Manual intervention required\n"));

                    needFurtherInterpret = FALSE;

                    *Status = STATUS_NO_MEDIA_IN_DEVICE;
                    *Retry = FALSE;
                    *RetryIntervalInSeconds = NOT_READY_RETRY_INTERVAL;

                    break;
                }

            case SCSI_SENSEQ_FORMAT_IN_PROGRESS: 
                {
                    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,
                                "SenseInfoInterpretByAdditionalSenseCode: Format in progress\n"));
            
                    needFurtherInterpret = FALSE;

                    *Status = STATUS_DEVICE_NOT_READY;
                    *Retry = FALSE;
                    *RetryIntervalInSeconds = NOT_READY_RETRY_INTERVAL;

                    break;
                }

            case SCSI_SENSEQ_CAUSE_NOT_REPORTABLE: 
            case SCSI_SENSEQ_INIT_COMMAND_REQUIRED:
            default:
                {
                    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, 
                                "SenseInfoInterpretByAdditionalSenseCode: Initializing command required\n"));

                    needFurtherInterpret = FALSE;

                    *Status = STATUS_DEVICE_NOT_READY;
                    *Retry = TRUE;
                    *RetryIntervalInSeconds = 0;

                    // This sense code/additional sense code combination may indicate 
                    // that the device needs to be started. 
                    if (TEST_FLAG(DeviceExtension->DeviceFlags, DEV_SAFE_START_UNIT) &&
                        !TEST_FLAG(Srb->SrbFlags, SRB_CLASS_FLAGS_LOW_PRIORITY))
                    {
                        DeviceSendStartUnit(DeviceExtension->Device);
                    }

                    break;
                }
            } // end switch (AdditionalSenseCodeQual)
            break;

        } // end case (SCSI_ADSENSE_LUN_NOT_READY)

    case SCSI_ADSENSE_NO_MEDIA_IN_DEVICE: 
        {
            TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_GENERAL, 
                        "SenseInfoInterpretByAdditionalSenseCode: No Media in device.\n"));

            needFurtherInterpret = FALSE;

            *Status = STATUS_NO_MEDIA_IN_DEVICE;
            *Retry = FALSE;

            if (AdditionalSenseCodeQual == 0xCC)
            {
                //  The IMAPIv1 filter returns this ASCQ value while it is burning CD media, and we want
                //  to preserve this functionality for compatibility reasons.
                //  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).
                DeviceSetMediaChangeStateEx(DeviceExtension, MediaUnavailable, NULL);
            }
            else 
            {
                DeviceSetMediaChangeStateEx(DeviceExtension, MediaNotPresent, NULL);
            }

            break;
        } // end case SCSI_ADSENSE_NO_MEDIA_IN_DEVICE

    case SCSI_ADSENSE_INVALID_MEDIA: 
        {
        switch (AdditionalSenseCodeQual) 
        {

        case SCSI_SENSEQ_UNKNOWN_FORMAT: 
            {
                needFurtherInterpret = FALSE;
            
                // Log error only if this is a paging request
                *Status = STATUS_UNRECOGNIZED_MEDIA;
                *Retry = FALSE;

                LogContext->LogError = TEST_FLAG(Srb->SrbFlags, SRB_CLASS_FLAGS_PAGING);
                LogContext->UniqueErrorValue = 256;
                LogContext->ErrorCode = IO_ERR_BAD_BLOCK;

                break;
            }

        case SCSI_SENSEQ_INCOMPATIBLE_FORMAT: 
            {
                needFurtherInterpret = FALSE;
            
                *Status = STATUS_UNRECOGNIZED_MEDIA;
                *Retry = FALSE;

                LogContext->LogError = FALSE;

                break;
            }   

        case SCSI_SENSEQ_CLEANING_CARTRIDGE_INSTALLED:
            {
                needFurtherInterpret = FALSE;
            
                *Status = STATUS_CLEANER_CARTRIDGE_INSTALLED;
                *Retry = FALSE;

                LogContext->LogError = FALSE;
                LogContext->UniqueErrorValue = 256;
                LogContext->ErrorCode = IO_ERR_BAD_BLOCK;
                break;
            }

        default:
            {
                needFurtherInterpret = TRUE;
                break;
            }
        } // end case AdditionalSenseCodeQual

        break;
        } // end case SCSI_ADSENSE_NO_MEDIA_IN_DEVICE

    case SCSI_ADSENSE_NO_SEEK_COMPLETE: 
        {
        switch (AdditionalSenseCodeQual) 
        {

        case 0x00: 
            {
                needFurtherInterpret = FALSE;
            
                *Status = STATUS_DEVICE_DATA_ERROR;
                *Retry = TRUE;
                *RetryIntervalInSeconds = 0;
                LogContext->LogError = TRUE;
                LogContext->UniqueErrorValue = 256;
                LogContext->ErrorCode = IO_ERR_BAD_BLOCK;
                break;
            }

        default:
            {
                needFurtherInterpret = TRUE;
                break;
            }
        }

        break;
        } // end case SCSI_ADSENSE_NO_SEEK_COMPLETE

    case SCSI_ADSENSE_LUN_COMMUNICATION: 
        {
        switch (AdditionalSenseCodeQual) 
        {

        case SCSI_SESNEQ_COMM_CRC_ERROR: 
            {
                needFurtherInterpret = FALSE;
            
                *Status = STATUS_IO_DEVICE_ERROR;
                *Retry = TRUE;
                *RetryIntervalInSeconds = 1;
                LogContext->LogError = TRUE;
                LogContext->UniqueErrorValue = 257;
                LogContext->ErrorCode = IO_ERR_CONTROLLER_ERROR;
                break;
            }

        default:
            {
                needFurtherInterpret = TRUE;
                break;
            }
        }

        break;
        } // end case SCSI_ADSENSE_LUN_COMMUNICATION

    case SCSI_ADSENSE_ILLEGAL_BLOCK: 
        {
            needFurtherInterpret = FALSE;

            TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, 
                        "SenseInfoInterpretByAdditionalSenseCode: Illegal block address\n"));
            *Status = STATUS_NONEXISTENT_SECTOR;
            *Retry = FALSE;
            break;
        }

    case SCSI_ADSENSE_INVALID_LUN: 
        {
            needFurtherInterpret = FALSE;

            TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,  
                        "SenseInfoInterpretByAdditionalSenseCode: Invalid LUN\n"));
            *Status = STATUS_NO_SUCH_DEVICE;
            *Retry = FALSE;
            break;
        }

    case SCSI_ADSENSE_COPY_PROTECTION_FAILURE: 
        {
            needFurtherInterpret = FALSE;

            *Retry = FALSE;

            TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, 
                        "SenseInfoInterpretByAdditionalSenseCode: Key - Copy protection failure\n"));

            switch (AdditionalSenseCodeQual) 
            {
            case SCSI_SENSEQ_AUTHENTICATION_FAILURE:
                TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
                            "SenseInfoInterpretByAdditionalSenseCode: Authentication failure\n"));
                *Status = STATUS_CSS_AUTHENTICATION_FAILURE;
                break;
            case SCSI_SENSEQ_KEY_NOT_PRESENT:
                TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
                            "SenseInfoInterpretByAdditionalSenseCode: Key not present\n"));
                *Status = STATUS_CSS_KEY_NOT_PRESENT;
                break;
            case SCSI_SENSEQ_KEY_NOT_ESTABLISHED:
                TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
                            "SenseInfoInterpretByAdditionalSenseCode: 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,
                            "SenseInfoInterpretByAdditionalSenseCode: 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,
                            "SenseInfoInterpretByAdditionalSenseCode: 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,
                            "SenseInfoInterpretByAdditionalSenseCode: Region set error -- region may be permanent\n"));
                *Status = STATUS_CSS_RESETS_EXHAUSTED;
                break;

            default:
                *Status = STATUS_COPY_PROTECTION_FAILURE;
                break;
            } // end switch of ASCQ for COPY_PROTECTION_FAILURE

            break;
        }

    case SCSI_ADSENSE_INVALID_CDB: 
        {
            needFurtherInterpret = FALSE;

            TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,  
                        "SenseInfoInterpretByAdditionalSenseCode: Key - Invalid CDB\n"));

            *Status = STATUS_INVALID_DEVICE_REQUEST;
            *Retry = FALSE;

            // Note: the retry interval is not typically used.
            // it is set here only because a ClassErrorHandler
            // cannot set the RetryIntervalInSeconds, 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.
            *RetryIntervalInSeconds = 3;

            break;
        }

    case SCSI_ADSENSE_MEDIUM_CHANGED: 
        {
            needFurtherInterpret = FALSE;
            *RetryIntervalInSeconds = 0;

            TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,  
                        "SenseInfoInterpretByAdditionalSenseCode: Media changed\n"));

            DeviceSetMediaChangeStateEx(DeviceExtension, MediaPresent, NULL);

            // special process for Media Change
            if (IsVolumeMounted(DeviceExtension->DeviceObject))
            {
                // Set bit to indicate that media may have changed and volume needs verification.
                SET_FLAG(DeviceExtension->DeviceObject->Flags, DO_VERIFY_VOLUME);

                *Status = STATUS_VERIFY_REQUIRED;
                *Retry = FALSE;
            }
            else 
            {
                *Status = STATUS_IO_DEVICE_ERROR;
                *Retry = TRUE;
            }
            break;
        }

    case SCSI_ADSENSE_OPERATOR_REQUEST: 
        {
            switch (AdditionalSenseCodeQual) 
            {
            case SCSI_SENSEQ_MEDIUM_REMOVAL: 
                {
                    needFurtherInterpret = FALSE;
                    *RetryIntervalInSeconds = 0;
            
                    InterlockedIncrement((PLONG)&DeviceExtension->MediaChangeCount);
                    
                    TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,  
                        "SenseInfoInterpretByAdditionalSenseCode: Ejection request received!\n"));
                    //Send eject notification.
                    DeviceSendNotification(DeviceExtension,
                                           &GUID_IO_MEDIA_EJECT_REQUEST,
                                           0,
                                           NULL);
                    // special process for Media Change
                    if (IsVolumeMounted(DeviceExtension->DeviceObject))
                    {
                        // Set bit to indicate that media may have changed and volume needs verification.
                        SET_FLAG(DeviceExtension->DeviceObject->Flags, DO_VERIFY_VOLUME);

                        *Status = STATUS_VERIFY_REQUIRED;
                        *Retry = FALSE;
                    }
                    else 
                    {
                        *Status = STATUS_IO_DEVICE_ERROR;
                        *Retry = TRUE;
                    }
                    break;
                }
            default:
                {
                    needFurtherInterpret = TRUE;
                    break;
                }
            }
            break;
        }

    case SCSI_ADSENSE_OPERATING_CONDITIONS_CHANGED: 
        {
            needFurtherInterpret = FALSE;
            *RetryIntervalInSeconds = 5;

            InterlockedIncrement((PLONG)&DeviceExtension->MediaChangeCount);
                    
            // Device information has changed, we need to rescan the
            // bus for changed information such as the capacity.
            TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,  
                        "SenseInfoInterpretByAdditionalSenseCode: Device information changed. Invalidate the bus\n"));

            IoInvalidateDeviceRelations(DeviceExtension->LowerPdo, BusRelations);

            // special process for Media Change
            if (IsVolumeMounted(DeviceExtension->DeviceObject))
            {
                // Set bit to indicate that media may have changed and volume needs verification.
                SET_FLAG(DeviceExtension->DeviceObject->Flags, DO_VERIFY_VOLUME);

                *Status = STATUS_VERIFY_REQUIRED;
                *Retry = FALSE;
            }
            else 
            {
                *Status = STATUS_IO_DEVICE_ERROR;
                *Retry = TRUE;
            }
            break;
        } //end Case SCSI_ADSENSE_OPERATING_CONDITIONS_CHANGED


    case SCSI_ADSENSE_REC_DATA_NOECC:
    case SCSI_ADSENSE_REC_DATA_ECC: 
        {
            needFurtherInterpret = FALSE;

            *Status = STATUS_SUCCESS;
            *Retry = FALSE;
            LogContext->LogError = TRUE;
            LogContext->UniqueErrorValue = 258;
            LogContext->ErrorCode = IO_RECOVERED_VIA_ECC;

            if (senseBuffer->IncorrectLength) 
            {
                TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, 
                            "SenseInfoInterpretByAdditionalSenseCode: Incorrect length detected.\n"));
                *Status = STATUS_INVALID_BLOCK_LENGTH ;
            }
            break;
        }

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

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

            needFurtherInterpret = FALSE;

            // 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.

            *Status = STATUS_SUCCESS;
            *Retry = FALSE;
            LogContext->UniqueErrorValue = 258;
            LogContext->LogError = TRUE;
            LogContext->ErrorCode = IO_WRN_FAILURE_PREDICTED;

            if (senseBuffer->IncorrectLength) 
            {
                TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, 
                            "SenseInfoInterpretByAdditionalSenseCode: Incorrect length detected.\n"));
                *Status = STATUS_INVALID_BLOCK_LENGTH ;
            }
            break;
        }

    case 0x57: 
        {
            // UNABLE_TO_RECOVER_TABLE_OF_CONTENTS
            // the Matshita CR-585 returns this for all read commands
            // on blank CD-R and CD-RW media, and we need to handle
            // this for READ_CD detection ability.
            switch (AdditionalSenseCodeQual) 
            {
            case 0x00: 
                {
                    needFurtherInterpret = FALSE;

                    *Status = STATUS_UNRECOGNIZED_MEDIA;
                    *Retry = FALSE;
                    break;
                }
            default:
                {
                    needFurtherInterpret = TRUE;
                    break;
                }
            }
            break;
        }   //end case Matshita specific error 0x57

    default:
        {
            needFurtherInterpret = TRUE;
            break;
        }
    }

    return needFurtherInterpret;
}

VOID
SenseInfoInterpretBySenseKey(
    _In_      PCDROM_DEVICE_EXTENSION   DeviceExtension,
    _In_      PSENSE_DATA               SenseData,
    _In_      UCHAR                     SenseKey,
    _Inout_   NTSTATUS*                 Status,
    _Inout_   BOOLEAN*                  Retry,
    _Out_ _Deref_out_range_(0,100) ULONG*         RetryIntervalInSeconds,
    _Inout_   PERROR_LOG_CONTEXT        LogContext
    )
{
    // set default values for retry fields.
    *Status = STATUS_IO_DEVICE_ERROR;
    *Retry = TRUE;
    *RetryIntervalInSeconds = 0;

    switch (SenseKey)
    {
    case SCSI_SENSE_NOT_READY:
        {
            TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
                        "SenseInfoInterpretBySenseKey: Key - Not Ready (bad block)\n"));

            *Status = STATUS_DEVICE_NOT_READY;
            *Retry = TRUE;

            // for unprocessed "not ready" codes, retry the command immediately.
            *RetryIntervalInSeconds = 0;
            break;
        }

    case SCSI_SENSE_DATA_PROTECT:
        {
            TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL,
                        "SenseInfoInterpretBySenseKey: Key - Media write protected\n"));
            *Status = STATUS_MEDIA_WRITE_PROTECTED;
            *Retry = FALSE;
            break;
        } 

    case SCSI_SENSE_MEDIUM_ERROR:
        {
            TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
                        "SenseInfoInterpretBySenseKey: Key - Medium Error (bad block)\n"));

            *Status = STATUS_DEVICE_DATA_ERROR;
            *Retry = FALSE;
            LogContext->LogError = TRUE;
            LogContext->UniqueErrorValue = 256;
            LogContext->ErrorCode = IO_ERR_BAD_BLOCK;

            break;
        } // end SCSI_SENSE_MEDIUM_ERROR

    case SCSI_SENSE_HARDWARE_ERROR:
        {
            TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
                        "SenseInfoInterpretBySenseKey: Key - Hardware error\n"));

            *Status = STATUS_IO_DEVICE_ERROR;
            *Retry = TRUE;
            LogContext->LogError = TRUE;
            LogContext->UniqueErrorValue = 257;
            LogContext->ErrorCode = IO_ERR_CONTROLLER_ERROR;

            break;
        } // end SCSI_SENSE_HARDWARE_ERROR

    case SCSI_SENSE_ILLEGAL_REQUEST:
        {
            TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
                        "SenseInfoInterpretBySenseKey: Key - Illegal SCSI request\n"));
            *Status = STATUS_INVALID_DEVICE_REQUEST;
            *Retry = FALSE;

            break;
        } // end SCSI_SENSE_ILLEGAL_REQUEST

    case SCSI_SENSE_UNIT_ATTENTION:
        {
            TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
                        "SenseInfoInterpretBySenseKey: Key - Unit Attention\n"));

            // A media change may have occured so increment the change
            // count for the physical device
            InterlockedIncrement((PLONG)&DeviceExtension->MediaChangeCount);

            if (IsVolumeMounted(DeviceExtension->DeviceObject))
            {
                // Set bit to indicate that media may have changed
                // and volume needs verification.
                SET_FLAG(DeviceExtension->DeviceObject->Flags, DO_VERIFY_VOLUME);

                *Status = STATUS_VERIFY_REQUIRED;
                *Retry = FALSE;
            }
            else
            {
                *Status = STATUS_IO_DEVICE_ERROR;
                *Retry = TRUE;
            }

            break;

        } // end SCSI_SENSE_UNIT_ATTENTION

    case SCSI_SENSE_ABORTED_COMMAND:
        {
            TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
                        "SenseInfoInterpretBySenseKey: Command aborted\n"));
            *Status = STATUS_IO_DEVICE_ERROR;
            *Retry = TRUE;
            *RetryIntervalInSeconds = 1;
            break;
        } // end SCSI_SENSE_ABORTED_COMMAND

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

    case SCSI_SENSE_RECOVERED_ERROR:
        {
            TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,
                        "SenseInfoInterpretBySenseKey: Recovered error\n"));
            *Status = STATUS_SUCCESS;
            *Retry = FALSE;
            LogContext->LogError = TRUE;
            LogContext->UniqueErrorValue = 258;
            LogContext->ErrorCode = IO_ERR_CONTROLLER_ERROR;

            if (SenseData->IncorrectLength)
            {
                TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,
                            "SenseInfoInterpretBySenseKey: Incorrect length detected.\n"));
                *Status = STATUS_INVALID_BLOCK_LENGTH ;
            }

            break;
        } // end SCSI_SENSE_RECOVERED_ERROR

    case SCSI_SENSE_NO_SENSE:
        {
            // Check other indicators.
            if (SenseData->IncorrectLength)
            {
                TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,
                            "SenseInfoInterpretBySenseKey: Incorrect length detected.\n"));
                *Status = STATUS_INVALID_BLOCK_LENGTH ;
                *Retry   = FALSE;
            }
            else
            {
                TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL,
                            "SenseInfoInterpretBySenseKey: No specific sense key\n"));
                *Status = STATUS_IO_DEVICE_ERROR;
                *Retry = TRUE;
            }

            break;
        } // end SCSI_SENSE_NO_SENSE

    default:
        {
            TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
                        "SenseInfoInterpretBySenseKey: Unrecognized sense code\n"));
            *Status = STATUS_IO_DEVICE_ERROR;
            *Retry = TRUE;
            *RetryIntervalInSeconds = 0;

            break;
        }

    } // end switch (SenseKey)

    return;
}

VOID
SenseInfoInterpretBySrbStatus(
    _In_      PCDROM_DEVICE_EXTENSION   DeviceExtension,
    _In_      PSCSI_REQUEST_BLOCK       Srb,
    _In_ ULONG                          RetriedCount,
    _Inout_   NTSTATUS*                 Status,
    _Inout_   BOOLEAN*                  Retry,
    _Out_ _Deref_out_range_(0,100) ULONG*         RetryIntervalInSeconds,
    _Inout_   PERROR_LOG_CONTEXT        LogContext
    )
{
    BOOLEAN incrementErrorCount = FALSE;

    // set default values for retry fields.
    *Status = STATUS_IO_DEVICE_ERROR;
    *Retry = TRUE;
    *RetryIntervalInSeconds = 0;

    switch (SRB_STATUS(Srb->SrbStatus)) 
    {
    case SRB_STATUS_INVALID_LUN:
    case SRB_STATUS_INVALID_TARGET_ID:
    case SRB_STATUS_NO_DEVICE:
    case SRB_STATUS_NO_HBA:
    case SRB_STATUS_INVALID_PATH_ID: 
    {
        *Status = STATUS_NO_SUCH_DEVICE;
        *Retry = FALSE;
        break;
    }

    case SRB_STATUS_COMMAND_TIMEOUT:
    case SRB_STATUS_TIMEOUT: 
    {
        // Update the error count for the device.
        *Status = STATUS_IO_TIMEOUT;
        *Retry = TRUE;
        *RetryIntervalInSeconds = 0;
        incrementErrorCount = TRUE;
        break;
    }

    case SRB_STATUS_ABORTED:
    {
        // Update the error count for the device.
        *Status = STATUS_IO_TIMEOUT;
        *Retry = TRUE;
        *RetryIntervalInSeconds = 1;
        incrementErrorCount = TRUE;
        break;
    }

    case SRB_STATUS_SELECTION_TIMEOUT: 
    {
        *Status = STATUS_DEVICE_NOT_CONNECTED;
        *Retry = FALSE;
        *RetryIntervalInSeconds = 2;
        LogContext->LogError = TRUE;
        LogContext->ErrorCode = IO_ERR_NOT_READY;
        LogContext->UniqueErrorValue = 260;
        break;
    }

    case SRB_STATUS_DATA_OVERRUN:
    {
        *Status = STATUS_DATA_OVERRUN;
        *Retry = 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.
        *Retry = (RetriedCount <= 1);

        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(DeviceExtension->DeviceObject->Flags, DO_VERIFY_VOLUME)) 
        {
            *Status = STATUS_VERIFY_REQUIRED;
            *Retry = FALSE;
        } 
        else 
        {
            *Status = STATUS_IO_DEVICE_ERROR;
            *Retry = TRUE;
        }

        break;
    }

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

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

    case SRB_STATUS_BUS_RESET:
    {
        *Status = STATUS_IO_DEVICE_ERROR;
        *Retry = TRUE;
        break;
    }

    case SRB_STATUS_ERROR: 
    {
        *Status = STATUS_IO_DEVICE_ERROR;
        *Retry = TRUE;

        if (Srb->ScsiStatus == 0) 
        {
            // This is some strange return code.  Update the error
            // count for the device.
            incrementErrorCount = TRUE;
        } 

        if (Srb->ScsiStatus == SCSISTAT_BUSY) 
        {
            *Status = STATUS_DEVICE_NOT_READY;
        }

        break;
    }

    default: 
    {
        *Status = STATUS_IO_DEVICE_ERROR;
        *Retry = TRUE;
        LogContext->LogError = TRUE;
        LogContext->ErrorCode = IO_ERR_CONTROLLER_ERROR;
        LogContext->UniqueErrorValue = 259;
        LogContext->ErrorUnhandled = TRUE;
        break;
    }

    } //end of (SRB_STATUS(Srb->SrbStatus))  
    
    if (incrementErrorCount) 
    {
        // if any error count occurred, delay the retry of this io by
        // at least one second, if caller supports it.
        if (*RetryIntervalInSeconds == 0) 
        {
            *RetryIntervalInSeconds = 1;
        }

        DevicePerfIncrementErrorCount(DeviceExtension);
    }
 
    return;
}

VOID
SenseInfoLogError(
    _In_    PCDROM_DEVICE_EXTENSION DeviceExtension,
    _In_    PSCSI_REQUEST_BLOCK     Srb,
    _In_    UCHAR                   MajorFunctionCode,
    _In_    ULONG                   IoControlCode,
    _In_    ULONG                   RetriedCount,
    _In_    NTSTATUS*               Status,
    _In_    BOOLEAN*                Retry,
    _Inout_ PERROR_LOG_CONTEXT      LogContext
    )
{
    //      Always log the error in our internal log.
    //      If logError is set, also log the error in the system log.
    PSENSE_DATA          senseBuffer = (PSENSE_DATA)Srb->SenseInfoBuffer;
    ULONG                totalSize = 0;
    ULONG                senseBufferSize = 0;
    IO_ERROR_LOG_PACKET  staticErrLogEntry = {0};
    CDROM_ERROR_LOG_DATA staticErrLogData = {0};

    // 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(CDROM_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))
    {
        ULONG       validSenseBytes;
        BOOLEAN     validSense;

        // make sure we can at least access the AdditionalSenseLength field
        validSense = RTL_CONTAINS_FIELD(senseBuffer,
                                        Srb->SenseInfoBufferLength,
                                        AdditionalSenseLength);
        if (validSense) 
        {
            // if extra info exists, copy the maximum amount of available
            // sense data that is safe into the the errlog.
            validSenseBytes = senseBuffer->AdditionalSenseLength
                                + offsetof(SENSE_DATA, AdditionalSenseLength);

            // this is invalid because it causes overflow!
            // whoever sent this type of request would cause
            // a system crash.
            NT_ASSERT(validSenseBytes < MAX_ADDITIONAL_SENSE_BYTES);

            // set to save the most sense buffer possible
            senseBufferSize = max(validSenseBytes, sizeof(SENSE_DATA));
            senseBufferSize = min(senseBufferSize, Srb->SenseInfoBufferLength);
        }
        else 
        {
            // it's smaller than required to read the total number of
            // valid bytes, so just use the SenseInfoBufferLength field.
            senseBufferSize = Srb->SenseInfoBufferLength;
        }

        //  Bump totalSize by the number of extra senseBuffer bytes
        //  (beyond the default sense buffer within CDROM_ERROR_LOG_DATA).
        //  Make sure to never allocate more than ERROR_LOG_MAXIMUM_SIZE.
        if (senseBufferSize > sizeof(SENSE_DATA))
        {
            totalSize += senseBufferSize-sizeof(SENSE_DATA);
            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 RetriedCount 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 ((LogContext->ErrorCode == IO_WARNING_PAGING_FAILURE) &&
        (*Retry || NT_SUCCESS(*Status)) ) 
    {
        LogContext->LogError = FALSE;
    }

    if (TEST_FLAG(Srb->SrbFlags, SRB_CLASS_FLAGS_PAGING)) 
    {
        staticErrLogData.ErrorPaging = TRUE;
    }

    staticErrLogData.ErrorUnhandled = LogContext->ErrorUnhandled;

    // Calculate the device offset if there is a geometry.
    staticErrLogEntry.DeviceOffset.QuadPart = (LONGLONG)LogContext->BadSector;
    staticErrLogEntry.DeviceOffset.QuadPart *= (LONGLONG)DeviceExtension->DiskGeometry.BytesPerSector;

    if (LogContext->ErrorCode == -1)
    {
        staticErrLogEntry.ErrorCode = STATUS_IO_DEVICE_ERROR;
    }
    else 
    {
        staticErrLogEntry.ErrorCode = LogContext->ErrorCode;
    }

    //  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 = IoControlCode;
    staticErrLogEntry.RetryCount = (UCHAR)RetriedCount;
    staticErrLogEntry.UniqueErrorValue = LogContext->UniqueErrorValue;

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

    //  Save the entire contents of the SRB.
    staticErrLogData.Srb = *Srb;

    //  For our private log, save just the default length of the SENSE_DATA.
    if (senseBufferSize != 0)
    {
        RtlCopyMemory(&staticErrLogData.SenseData, senseBuffer, min(senseBufferSize, sizeof(SENSE_DATA)));
    }

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

    //  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 (LogContext->LogError)
    {
        // We do not want to log certain system events repetitively
        switch (((PCDB)Srb->Cdb)->CDB10.OperationCode)
        {
            case SCSIOP_TEST_UNIT_READY:
            {
                if (DeviceExtension->PrivateFdoData->LoggedTURFailureSinceLastIO)
                {
                    LogContext->LogError = FALSE;
                }
                else
                {
                    DeviceExtension->PrivateFdoData->LoggedTURFailureSinceLastIO = TRUE;
                }

                break;
            }

            case SCSIOP_SYNCHRONIZE_CACHE:
            {
                if (DeviceExtension->PrivateFdoData->LoggedSYNCFailure)
                {
                    LogContext->LogError = FALSE;
                }
                else
                {
                    DeviceExtension->PrivateFdoData->LoggedSYNCFailure = TRUE;
                }

                break;
            }
        }

        // Do not log 5/21/00 LOGICAL BLOCK ADDRESS OUT OF RANGE if the disc is blank,
        // it is known to litter the Event Log with repetitive errors
        // Do not log this error for READ, as it's known that File System mount process reads different sectors from media.
        if (senseBufferSize > RTL_SIZEOF_THROUGH_FIELD(SENSE_DATA, AdditionalSenseCodeQualifier) &&
            senseBuffer->SenseKey == SCSI_SENSE_ILLEGAL_REQUEST &&
            senseBuffer->AdditionalSenseCode == SCSI_ADSENSE_ILLEGAL_BLOCK &&
            senseBuffer->AdditionalSenseCodeQualifier == 0 &&
            IS_SCSIOP_READ(((PCDB)Srb->Cdb)->CDB10.OperationCode))
        {
            LogContext->LogError = FALSE;
        }
    }

    //  Write the error log packet to the system error logging thread.
    if (LogContext->LogError)
    {
        PIO_ERROR_LOG_PACKET    errorLogEntry;
        PCDROM_ERROR_LOG_DATA   errlogData;

        errorLogEntry = (PIO_ERROR_LOG_PACKET)IoAllocateErrorLogEntry(DeviceExtension->DeviceObject, (UCHAR)totalSize);
        if (errorLogEntry)
        {
            errlogData = (PCDROM_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) 
            {
                RtlCopyMemory(&errlogData->SenseData, senseBuffer, senseBufferSize);
            }

            //  errorLogEntry - It will be freed by the kernel.
            IoWriteErrorLogEntry(errorLogEntry);
        }
    }

    return;
}

VOID
SenseInfoInterpretRefineByScsiCommand(
    _In_      PCDROM_DEVICE_EXTENSION   DeviceExtension,
    _In_      PSCSI_REQUEST_BLOCK       Srb,
    _In_      ULONG                     RetriedCount,
    _In_      LONGLONG                  Total100nsSinceFirstSend,
    _In_      BOOLEAN                   OverrideVerifyVolume,
    _Inout_   BOOLEAN*                  Retry,
    _Inout_   NTSTATUS*                 Status,
    _Inout_   _Deref_out_range_(0, MAXIMUM_RETRY_FOR_SINGLE_IO_IN_100NS_UNITS)
              LONGLONG*                 RetryIntervalIn100ns
    )
/*++

Routine Description:

    Based on SCSI command, modify the interpretion result.

Arguments:

    DeviceExtension - device extension.
    Srb - Supplies the scsi request block which failed.
    RetriedCount - retried count.
    Total100nsUnitsSinceFirstSend - time spent after the request was sent down first time.
    OverrideVerifyVolume - should override verify volume request.

Return Value:

    Retry - the reques should be retried or not.
    Status - Returns the status for the request.
    RetryInterval - waiting time (in 100ns) before the request should be retried.
                    Zero indicates the request should be immediately retried.

--*/
{
    UCHAR const opCode = Srb->Cdb[0];
    CDB const*  cdb = (CDB const*)(Srb->Cdb);
    PSENSE_DATA senseBuffer = (PSENSE_DATA)Srb->SenseInfoBuffer;

    if (opCode == SCSIOP_MEDIUM_REMOVAL)
    {
        if (( cdb->AsByte[1]         == 0) &&
            ( cdb->AsByte[2]         == 0) &&
            ( cdb->AsByte[3]         == 0) &&
            ((cdb->AsByte[4] & 0xFC) == 0)
            )
        {
            // byte[4] == 0x3 or byte[4] == 0x1  ==  UNLOCK OF MEDIA
            if ((cdb->AsByte[4] & 0x01) == 0)
            {
                if (RetriedCount < TOTAL_COUNT_RETRY_DEFAULT)
                {
                    // keep retrying unlock operation for several times
                    *Retry = TRUE;
                }
            }
            else // LOCK REQUEST
            {
                // do not retry LOCK requests more than once (per CLASSPNP code)
                if (RetriedCount > TOTAL_COUNT_RETRY_LOCK_MEDIA)
                {
                    *Retry = FALSE;
                }
            }
        }

        // want a minimum time to retry of 2 seconds
        if ((*Status == STATUS_DEVICE_NOT_READY) &&
            (senseBuffer->AdditionalSenseCode == SCSI_ADSENSE_LUN_NOT_READY))
        {
            *RetryIntervalIn100ns = max(*RetryIntervalIn100ns, SECONDS_TO_100NS_UNITS(2));
        }
        else if (SRB_STATUS(Srb->SrbStatus) == SRB_STATUS_SELECTION_TIMEOUT)
        {
            *RetryIntervalIn100ns = max(*RetryIntervalIn100ns, SECONDS_TO_100NS_UNITS(2));
        }
    }
    else if ((opCode == SCSIOP_MODE_SENSE) || (opCode == SCSIOP_MODE_SENSE10))
    {
        // want a minimum time to retry of 2 seconds
        if ((*Status == STATUS_DEVICE_NOT_READY) &&
            (senseBuffer->AdditionalSenseCode == SCSI_ADSENSE_LUN_NOT_READY))
        {
            *RetryIntervalIn100ns = max(*RetryIntervalIn100ns, SECONDS_TO_100NS_UNITS(2));
        }
        else if (SRB_STATUS(Srb->SrbStatus) == SRB_STATUS_SELECTION_TIMEOUT)
        {
            *RetryIntervalIn100ns = max(*RetryIntervalIn100ns, SECONDS_TO_100NS_UNITS(2));
        }

        // Want to ignore a STATUS_VERIFY_REQUIRED error because it either
        // doesn't make sense or is required to satisfy the VERIFY.
        if (*Status == STATUS_VERIFY_REQUIRED)
        {
            *Retry = TRUE;
        }
        else if (SRB_STATUS(Srb->SrbStatus) == SRB_STATUS_DATA_OVERRUN)
        {
            /*
             *  This is a HACK.
             *  Atapi returns SRB_STATUS_DATA_OVERRUN when it really means
             *  underrun (i.e. success, and the buffer is longer than needed).
             *  So treat this as a success.
             *  When the caller of this function sees that the status was changed to success,
             *  it will add the transferred length to the original irp.
             */
            *Status = STATUS_SUCCESS;
            *Retry = FALSE;
        }

        // limit the count of retries
        if (RetriedCount > TOTAL_COUNT_RETRY_MODESENSE)
        {
            *Retry = FALSE;
        }
    }
    else if ((opCode == SCSIOP_READ_CAPACITY) || (opCode == SCSIOP_READ_CAPACITY16))
    {
        // Want to ignore a STATUS_VERIFY_REQUIRED error because it either
        // doesn't make sense or is required to satisfy the VERIFY.
        if (*Status == STATUS_VERIFY_REQUIRED)
        {
            *Retry = TRUE;
        }

        if (RetriedCount > TOTAL_COUNT_RETRY_READ_CAPACITY)
        {
            *Retry = FALSE;
        }
    }
    else if ((opCode == SCSIOP_RESERVE_UNIT) || (opCode == SCSIOP_RELEASE_UNIT))
    {
        // The RESERVE(6) / RELEASE(6) commands are optional. 
        // So if they aren't supported, try the 10-byte equivalents
        if (*Status == STATUS_INVALID_DEVICE_REQUEST)
        {
            PCDB cdb = (PCDB)Srb->Cdb;

            Srb->CdbLength = 10;
            cdb->CDB10.OperationCode = (cdb->CDB6GENERIC.OperationCode == SCSIOP_RESERVE_UNIT) 
                                        ? SCSIOP_RESERVE_UNIT10 
                                        : SCSIOP_RELEASE_UNIT10;

            SET_FLAG(DeviceExtension->PrivateFdoData->HackFlags, FDO_HACK_NO_RESERVE6);
            *Retry = TRUE;
        }
    }
    else if (IS_SCSIOP_READWRITE(opCode))
    {
        // Retry if still verifying a (possibly) reloaded disk/cdrom.
        if (OverrideVerifyVolume && (*Status == STATUS_VERIFY_REQUIRED))
        {
            *Status = STATUS_IO_DEVICE_ERROR;
            *Retry = TRUE;
        }

        // Special case for streaming READ/WRITE commands
        if (((opCode == SCSIOP_READ12) && (cdb->READ12.Streaming == 1)) ||
            ((opCode == SCSIOP_WRITE12) && (cdb->WRITE12.Streaming == 1)))
        {
            // We've got a failure while performing a streaming operation and now need to guess if
            // it's likely to be a permanent error because the drive does not support streaming at all
            // (in which case we're going to fall back to normal reads/writes), or a transient error
            // (in which case we quickly fail the request but contrinue to use streaming).
            //
            // We analyze the sense information to make that decision. Bus resets and device timeouts
            // are treated as permanent errors, because some non-compliant devices may even hang when
            // they get a command that they do not expect.

            BOOLEAN     disableStreaming = FALSE;

            if (SRB_STATUS(Srb->SrbStatus) == SRB_STATUS_TIMEOUT || 
                SRB_STATUS(Srb->SrbStatus) == SRB_STATUS_COMMAND_TIMEOUT ||
                SRB_STATUS(Srb->SrbStatus) == SRB_STATUS_SELECTION_TIMEOUT ||
                SRB_STATUS(Srb->SrbStatus) == SRB_STATUS_BUS_RESET)
            {
                disableStreaming = TRUE;
            }
            else if ((senseBuffer->SenseKey &0xf) == SCSI_SENSE_UNIT_ATTENTION)
            {
                if (senseBuffer->AdditionalSenseCode == SCSI_ADSENSE_BUS_RESET ||
                    senseBuffer->AdditionalSenseCode == SCSI_ADSENSE_INSUFFICIENT_TIME_FOR_OPERATION)
                {
                    disableStreaming = TRUE;
                }
            }
            else if ((senseBuffer->SenseKey &0xf) == SCSI_SENSE_ILLEGAL_REQUEST)
            {
                // LBA Out of Range is an exception, as it's more likely to be caused by 
                // upper layers attempting to read/write a wrong LBA.
                if (senseBuffer->AdditionalSenseCode != SCSI_ADSENSE_ILLEGAL_BLOCK)
                {
                    disableStreaming = TRUE;
                }
            }

            if (disableStreaming)
            {
                // if the failure looks permanent, we disable streaming for all future reads/writes
                // and retry the command immediately
                SET_FLAG(DeviceExtension->PrivateFdoData->HackFlags, FDO_HACK_NO_STREAMING);
                *Retry = TRUE;
                *RetryIntervalIn100ns = 0;
            }
            else
            {
                // if the failure looks transient, we simply fail the current request without retries
                // to minimize the time of processing
                *Retry = FALSE;
            }
        }

        // Special-case handling of READ/WRITE commands.  These commands now have a 120 second timeout, 
        // but the preferred behavior (and that taken by many drives) is to immediately report 2/4/x 
        // on OPC and similar commands.  Thus, retries must occur for at least 160 seconds 
        // (120 seconds + four 10 second retries) as a conservative guess.
        // Note: 160s retry time is also a result of discussion with OEMs for case of 2/4/7 and 2/4/8. 
        if (*Retry)
        {
            if ((Total100nsSinceFirstSend < 0) ||
                (((senseBuffer->SenseKey &0xf) == SCSI_SENSE_HARDWARE_ERROR) && (senseBuffer->AdditionalSenseCode == 0x09)))
            {
                // time information is not valid. use default retry count.
                // or if it's SERVO FAILURE, use retry count instead of 160s retry.
                *Retry = (RetriedCount <= TOTAL_COUNT_RETRY_DEFAULT);
            }
            else if (Total100nsSinceFirstSend > SECONDS_TO_100NS_UNITS(TOTAL_SECONDS_RETRY_TIME_WRITE))
            {
                *Retry = FALSE;
            }

            // How long should we request a delay for during writing?  This depends entirely on 
            // the current write speed of the drive.  If we request retries too quickly,
            // we can overload the processor on the drive (resulting in garbage being written),
            // but too slowly results in lesser performance.
            //
            *RetryIntervalIn100ns = DeviceExtension->DeviceAdditionalData.ReadWriteRetryDelay100nsUnits;

        } // end retry for 160 seconds modification
    }
    else if (opCode == SCSIOP_GET_PERFORMANCE)
    {
        if (SRB_STATUS(Srb->SrbStatus) == SRB_STATUS_DATA_OVERRUN)
        {
            //  This is a HACK.
            //  Atapi returns SRB_STATUS_DATA_OVERRUN when it really means
            //  underrun (i.e. success, and the buffer is longer than needed).
            //  So treat this as a success.
            //  When the caller of this function sees that the status was changed to success,
            //  it will add the transferred length to the original irp.
            *Status = STATUS_SUCCESS;
            *Retry = FALSE;
        }

        if ((Srb->SenseInfoBufferLength < RTL_SIZEOF_THROUGH_FIELD(SENSE_DATA,AdditionalSenseCodeQualifier)) ||
            !TEST_FLAG(Srb->SrbStatus, SRB_STATUS_AUTOSENSE_VALID)) 
        {
            // If get configuration command is failing and if the request type is TYPE ONE
            // then most likely the device does not support this request type. Set the
            // flag so that the TYPE ONE requests will be tried as TYPE ALL requets.
            if ((SRB_STATUS(Srb->SrbStatus) != SRB_STATUS_SUCCESS) &&
                (SRB_STATUS(Srb->SrbStatus) != SRB_STATUS_DATA_OVERRUN) &&
                (((PCDB)Srb->Cdb)->GET_CONFIGURATION.RequestType == SCSI_GET_CONFIGURATION_REQUEST_TYPE_ONE)) 
            {

                TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL,
                           "TYPE ONE GetConfiguration failed. Set hack flag and retry.\n"));
                SET_FLAG(DeviceExtension->DeviceAdditionalData.HackFlags, CDROM_HACK_BAD_TYPE_ONE_GET_CONFIG);
                *Retry = TRUE;
            }
        }

        // limit retries to GET_PERFORMANCE commands to default retry count
        if (RetriedCount > TOTAL_COUNT_RETRY_DEFAULT)
        {
            *Retry = FALSE;
        }
    }
    else // default handler -- checks for retry count only.
    {
        if (RetriedCount > TOTAL_COUNT_RETRY_DEFAULT)
        {
            *Retry = FALSE;
        }
    }

    return;
}


VOID
SenseInfoInterpretRefineByIoControl(
    _In_      PCDROM_DEVICE_EXTENSION   DeviceExtension,
    _In_      ULONG                     IoControlCode,
    _In_      BOOLEAN                   OverrideVerifyVolume,
    _Inout_   BOOLEAN*                  Retry,
    _Inout_   NTSTATUS*                 Status
    )
/*++

Routine Description:

    Based on IOCTL code, modify the interpretion result.

Arguments:

    Device - Supplies the device object associated with this request.
    OriginalRequest - the irp that error occurs on.
    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.
    PreviousRetryCount - retried count.
    RequestHistory_DoNotUse - the history list

Return Value:

    BOOLEAN TRUE: Drivers should retry this request.
            FALSE: Drivers should not retry this request.
    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.

--*/
{
    PAGED_CODE();

    if ((IoControlCode == IOCTL_CDROM_GET_LAST_SESSION) ||
        (IoControlCode == IOCTL_CDROM_READ_TOC)         ||
        (IoControlCode == IOCTL_CDROM_READ_TOC_EX)      ||
        (IoControlCode == IOCTL_CDROM_GET_CONFIGURATION)||
        (IoControlCode == IOCTL_CDROM_GET_VOLUME)) 
    {
        if (*Status == STATUS_DATA_OVERRUN) 
        {
            *Status = STATUS_SUCCESS;
            *Retry = FALSE;
        }
    }

    if (IoControlCode == IOCTL_CDROM_READ_Q_CHANNEL) 
    {
        PLAY_ACTIVE(DeviceExtension) = FALSE;
    }

    // If the status is verified required and the this request
    // should bypass verify required then retry the request.
    if (OverrideVerifyVolume && (*Status == STATUS_VERIFY_REQUIRED)) 
    {
        // note: status gets overwritten here
        *Status = STATUS_IO_DEVICE_ERROR;
        *Retry = TRUE;

        if ((IoControlCode == IOCTL_CDROM_CHECK_VERIFY) ||
            (IoControlCode == IOCTL_STORAGE_CHECK_VERIFY) ||
            (IoControlCode == IOCTL_STORAGE_CHECK_VERIFY2) ||
            (IoControlCode == IOCTL_DISK_CHECK_VERIFY)
           ) 
        {
            // Update the geometry information, as the media could have changed.
            (VOID) MediaReadCapacity(DeviceExtension->Device);
        } // end of ioctls to update capacity
    }

    if (!NT_SUCCESS(*Status) && (IoControlCode == IOCTL_CDROM_SET_SPEED))
    {
        // If set speed request fails then we should disable the restore speed option.
        // Otherwise we will try to restore to default speed on next media change,
        // if requested by the caller.
        DeviceExtension->DeviceAdditionalData.RestoreDefaults = FALSE;
        TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "Disable restore default\n"));
    }

    return;
}

BOOLEAN
SenseInfoInterpret(
    _In_    PCDROM_DEVICE_EXTENSION DeviceExtension,
    _In_    WDFREQUEST              Request, 
    _In_    PSCSI_REQUEST_BLOCK     Srb,
    _In_    ULONG                   RetriedCount,
    _Out_   NTSTATUS*               Status,
    _Out_ _Deref_out_range_(0, MAXIMUM_RETRY_FOR_SINGLE_IO_IN_100NS_UNITS)
              LONGLONG*             RetryIntervalIn100ns
    )
/*++

SenseInfoInterpret()

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:

    Device - 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.

Return Value:

    BOOLEAN TRUE: Drivers should retry this request.
            FALSE: Drivers should not retry this request.
    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.

--*/
{
    ULONG               retryIntervalInSeconds = 0;
    BOOLEAN             retry = TRUE;
    PSENSE_DATA         senseBuffer = Srb->SenseInfoBuffer;
    ULONG               readSector = 0;
    ERROR_LOG_CONTEXT   logContext;

    UCHAR                   majorFunctionCode = 0;
    ULONG                   ioControlCode = 0;
    BOOLEAN                 overrideVerifyVolume = FALSE;
    ULONGLONG               total100nsSinceFirstSend = 0;
    PZERO_POWER_ODD_INFO    zpoddInfo = DeviceExtension->ZeroPowerODDInfo;

    //
    *Status = STATUS_IO_DEVICE_ERROR;

    RtlZeroMemory(&logContext, sizeof(ERROR_LOG_CONTEXT));
    logContext.ErrorCode = -1;

    // Get Original Request related information
    SenseInfoRequestGetInformation(Request,
                                   &majorFunctionCode,
                                   &ioControlCode,
                                   &overrideVerifyVolume,
                                   &total100nsSinceFirstSend);

    if(TEST_FLAG(Srb->SrbFlags, SRB_CLASS_FLAGS_PAGING)) 
    {
        // Log anything remotely incorrect about paging i/o
        logContext.LogError = TRUE;
        logContext.UniqueErrorValue = 301;
        logContext.ErrorCode = IO_WARNING_PAGING_FAILURE;
    }

    // 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,
                    "SenseInfoInterpret: Internal Error code is %x\n",
                    Srb->InternalStatus));

        retry = FALSE;
        *Status = Srb->InternalStatus;
    } 
    else if (Srb->ScsiStatus == SCSISTAT_RESERVATION_CONFLICT) 
    {
        retry = FALSE;
        *Status = STATUS_DEVICE_BUSY;
        logContext.LogError = FALSE;
    } 
    else if ((Srb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID) &&
             (Srb->SenseInfoBufferLength >= RTL_SIZEOF_THROUGH_FIELD(SENSE_DATA, AdditionalSenseLength))) 
    {
        UCHAR   senseKey = (UCHAR)(senseBuffer->SenseKey & 0x0f);
        UCHAR   additionalSenseCode = 0;
        UCHAR   additionalSenseCodeQual = 0;

        // Zero the additional sense code and additional sense code qualifier
        // if they were not returned by the device.
        readSector = senseBuffer->AdditionalSenseLength + offsetof(SENSE_DATA, AdditionalSenseLength);
        if (readSector > Srb->SenseInfoBufferLength) 
        {
            readSector = Srb->SenseInfoBufferLength;
        }

        additionalSenseCode = (readSector >= RTL_SIZEOF_THROUGH_FIELD(SENSE_DATA, AdditionalSenseCode)) ?
                               senseBuffer->AdditionalSenseCode : 0;
        additionalSenseCodeQual = (readSector >= RTL_SIZEOF_THROUGH_FIELD(SENSE_DATA, AdditionalSenseCodeQualifier)) ?
                                   senseBuffer->AdditionalSenseCodeQualifier : 0;

        TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, 
                    "SCSI Error - \n"
                    "\tcdb: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n"
                    "\tsrb status: %X; sense: %02X/%02X/%02X; Retried count: %d\n\n",
                    Srb->Cdb[0], Srb->Cdb[1], Srb->Cdb[2], Srb->Cdb[3], Srb->Cdb[4], Srb->Cdb[5], 
                    Srb->Cdb[6], Srb->Cdb[7], Srb->Cdb[8], Srb->Cdb[9], Srb->Cdb[10], Srb->Cdb[11], 
                    Srb->Cdb[12], Srb->Cdb[13], Srb->Cdb[14], Srb->Cdb[15], 
                    SRB_STATUS(Srb->SrbStatus), 
                    senseKey, 
                    additionalSenseCode, 
                    additionalSenseCodeQual,
                    RetriedCount));

        if (senseKey == SCSI_SENSE_UNIT_ATTENTION)
        {
            ULONG   mediaChangeCount;

            // A media change may have occured so increment the change count for the physical device
            mediaChangeCount = InterlockedIncrement((PLONG)&DeviceExtension->MediaChangeCount);
            TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,  
                       "SenseInfoInterpret: Media change count for device %d incremented to %#lx\n",
                       DeviceExtension->DeviceNumber, mediaChangeCount));
        }

        if ((zpoddInfo != NULL) &&
            (((PCDB)Srb->Cdb)->CDB6GENERIC.OperationCode == SCSIOP_TEST_UNIT_READY))
        {
            // This sense code is in response to the Test Unit Ready sent during delayed power down
            // request. Copy the sense data into the zpoddInfo structure for later processing.
            zpoddInfo->SenseKey = senseKey;
            zpoddInfo->AdditionalSenseCode = additionalSenseCode;
            zpoddInfo->AdditionalSenseCodeQualifier = additionalSenseCodeQual;
        }

        // Interpret error by specific ASC & ASCQ first,
        // If the error is not handled, interpret using f
        {
            BOOLEAN notHandled = FALSE;
            notHandled = SenseInfoInterpretByAdditionalSenseCode(DeviceExtension,
                                                                 Srb,
                                                                 additionalSenseCode, 
                                                                 additionalSenseCodeQual,
                                                                 Status,
                                                                 &retry,
                                                                 &retryIntervalInSeconds,
                                                                 &logContext);
            
            if (notHandled)
            {
                SenseInfoInterpretBySenseKey(DeviceExtension,
                                             senseBuffer,
                                             senseKey,
                                             Status,
                                             &retry,
                                             &retryIntervalInSeconds,
                                             &logContext);
            }
        }

        // Try to determine the bad sector from the inquiry data.
        if ((IS_SCSIOP_READWRITE(((PCDB)Srb->Cdb)->CDB10.OperationCode)) ||
            (((PCDB)Srb->Cdb)->CDB10.OperationCode == SCSIOP_VERIFY)     ||
            (((PCDB)Srb->Cdb)->CDB10.OperationCode == SCSIOP_VERIFY16)) 
        {
            ULONG index;
            readSector = 0;

            for (index = 0; index < 4; index++) 
            {
                logContext.BadSector = (logContext.BadSector << 8) | senseBuffer->Information[index];
            }

            for (index = 0; index < 4; index++) 
            {
                readSector = (readSector << 8) | Srb->Cdb[index+2];
            }

            index = (((PCDB)Srb->Cdb)->CDB10.TransferBlocksMsb << 8) |
                    ((PCDB)Srb->Cdb)->CDB10.TransferBlocksLsb;

            // Make sure the bad sector is within the read sectors.
            if (!(logContext.BadSector >= readSector && logContext.BadSector < (readSector + index)))
            {
                logContext.BadSector = readSector;
            }
        }
    } 
    else 
    {
        // Request sense buffer not valid. No sense information
        // to pinpoint the error. Return general request fail.
        TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL,  
                    "SCSI Error - \n"
                    "\tcdb: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n"
                    "\tsrb status: %X; sense info not valid; Retried count: %d\n\n",
                    Srb->Cdb[0], Srb->Cdb[1], Srb->Cdb[2], Srb->Cdb[3], Srb->Cdb[4], Srb->Cdb[5], 
                    Srb->Cdb[6], Srb->Cdb[7], Srb->Cdb[8], Srb->Cdb[9], Srb->Cdb[10], Srb->Cdb[11], 
                    Srb->Cdb[12], Srb->Cdb[13], Srb->Cdb[14], Srb->Cdb[15], 
                    SRB_STATUS(Srb->SrbStatus),
                    RetriedCount));

        SenseInfoInterpretBySrbStatus(DeviceExtension,
                                      Srb, 
                                      RetriedCount,
                                      Status,
                                      &retry,
                                      &retryIntervalInSeconds,
                                      &logContext);
    }

    // all functions using unit - seconds for retry Interval already be called.
    *RetryIntervalIn100ns = SECONDS_TO_100NS_UNITS(retryIntervalInSeconds);

    // call the device specific error handler if it has one.
    // DeviceErrorHandlerForMmmc() for all MMC devices
    // or DeviceErrorHandlerForHitachiGD2000() for HITACHI GD-2000, HITACHI DVD-ROM GD-2000
    if (DeviceExtension->DeviceAdditionalData.ErrorHandler) 
    {
        DeviceExtension->DeviceAdditionalData.ErrorHandler(DeviceExtension, Srb, Status, &retry);
    }

    // Refine retry based on SCSI command
    SenseInfoInterpretRefineByScsiCommand(DeviceExtension,
                                          Srb,
                                          RetriedCount,
                                          total100nsSinceFirstSend,
                                          overrideVerifyVolume,
                                          &retry,
                                          Status,
                                          RetryIntervalIn100ns);

    // Refine retry based on IOCTL code. 
    if (majorFunctionCode == IRP_MJ_DEVICE_CONTROL)
    {
        SenseInfoInterpretRefineByIoControl(DeviceExtension,
                                            ioControlCode, 
                                            overrideVerifyVolume,
                                            &retry,
                                            Status);
    }
    
    // LOG the error:
    //  Always log the error in our internal log.
    //  If logError is set, also log the error in the system log.
    SenseInfoLogError(DeviceExtension,
                      Srb,
                      majorFunctionCode,
                      ioControlCode,
                      RetriedCount,
                      Status,
                      &retry,
                      &logContext);

    // all process about the error done. check if the irp was cancelled.
    if ((!NT_SUCCESS(*Status)) && retry)
    {
        PCDROM_REQUEST_CONTEXT requestContext = RequestGetContext(Request);

        if ((requestContext->OriginalRequest != NULL) &&
            WdfRequestIsCanceled(requestContext->OriginalRequest)
            )
        {
            TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL,  
                       "Request %p was cancelled when it would have been retried\n",
                       requestContext->OriginalRequest));
            
            *Status = STATUS_CANCELLED;
            retry = FALSE;
            *RetryIntervalIn100ns = 0;
        }
    }

    // now, all decisions are made. display trace information.
    if (retry)
    {
        TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,  
                   "Command shall be retried in %2I64d.%03I64d seconds\n",
                   (*RetryIntervalIn100ns / UNIT_100NS_PER_SECOND),
                   (*RetryIntervalIn100ns / 10000) % 1000
                   ));
    }
    else
    {
        TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL,  
                   "Will not retry; Sense/ASC/ASCQ of %02x/%02x/%02x\n",
                   senseBuffer->SenseKey,
                   senseBuffer->AdditionalSenseCode,
                   senseBuffer->AdditionalSenseCodeQualifier
                   ));
    }

    return retry;

} // end SenseInfoInterpret()


BOOLEAN
SenseInfoInterpretForZPODD(
    _In_    PCDROM_DEVICE_EXTENSION DeviceExtension,
    _In_    PSCSI_REQUEST_BLOCK     Srb,
    _Out_   NTSTATUS*               Status,
    _Out_ _Out_range_(0, MAXIMUM_RETRY_FOR_SINGLE_IO_IN_100NS_UNITS)
              LONGLONG*             RetryIntervalIn100ns
    )
/*++

SenseInfoInterpretForZPODD()

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:

    Device - Supplies the device object associated with this request.
    Srb - Supplies the scsi request block which failed.

Return Value:

    BOOLEAN TRUE: Drivers should retry this request.
            FALSE: Drivers should not retry this request.
    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.

--*/
{
    BOOLEAN                 retry = FALSE;
    PSENSE_DATA             senseBuffer = Srb->SenseInfoBuffer;
    ULONG                   readSector = 0;
    PZERO_POWER_ODD_INFO    zpoddInfo = DeviceExtension->ZeroPowerODDInfo;

    *Status = STATUS_IO_DEVICE_ERROR;
    *RetryIntervalIn100ns = 0;

    if (zpoddInfo->RetryFirstCommand != FALSE)
    {
        // The first command to the logical unit after power resumed will be terminated
        // with CHECK CONDITION Status, 6/29/00 POWER ON, RESET, OR BUS DEVICE RESET OCCURRED

        // We have observed some devices return a different sense code, and thus as long as
        // the first command after power resume fails, we just retry one more time.
        zpoddInfo->RetryFirstCommand = FALSE;

        retry = TRUE;
    }
    else if ((Srb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID) &&
             (Srb->SenseInfoBufferLength >= RTL_SIZEOF_THROUGH_FIELD(SENSE_DATA, AdditionalSenseLength))) 
    {
        UCHAR   senseKey = (UCHAR)(senseBuffer->SenseKey & 0x0f);
        UCHAR   additionalSenseCode = 0;
        UCHAR   additionalSenseCodeQual = 0;

        // Zero the additional sense code and additional sense code qualifier
        // if they were not returned by the device.
        readSector = senseBuffer->AdditionalSenseLength + offsetof(SENSE_DATA, AdditionalSenseLength);
        if (readSector > Srb->SenseInfoBufferLength) 
        {
            readSector = Srb->SenseInfoBufferLength;
        }

        additionalSenseCode = (readSector >= RTL_SIZEOF_THROUGH_FIELD(SENSE_DATA, AdditionalSenseCode)) ?
                               senseBuffer->AdditionalSenseCode : 0;
        additionalSenseCodeQual = (readSector >= RTL_SIZEOF_THROUGH_FIELD(SENSE_DATA, AdditionalSenseCodeQualifier)) ?
                                   senseBuffer->AdditionalSenseCodeQualifier : 0;

        // If sense code is 2/4/1, device is becoming ready from ZPODD mode. According to Mt Fuji, device
        // could take up to 800msec to be fully operational.
        if ((senseKey == SCSI_SENSE_NOT_READY) &&
            (additionalSenseCode == SCSI_ADSENSE_LUN_NOT_READY) &&
            (additionalSenseCodeQual == SCSI_SENSEQ_BECOMING_READY))
        {
            TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,
                        "SenseInfoInterpretForZPODD: In process of becoming ready\n"));

            zpoddInfo->BecomingReadyRetryCount--;

            if (zpoddInfo->BecomingReadyRetryCount > 0)
            {
                DEVICE_EVENT_BECOMING_READY notReady = {0};

                retry = TRUE;
                *Status = STATUS_DEVICE_NOT_READY;
                *RetryIntervalIn100ns = BECOMING_READY_RETRY_INTERNVAL_IN_100NS;

                notReady.Version = 1;
                notReady.Reason = 1;
                notReady.Estimated100msToReady = (ULONG) *RetryIntervalIn100ns / (1000 * 1000);
                DeviceSendNotification(DeviceExtension,
                                       &GUID_IO_DEVICE_BECOMING_READY,
                                       sizeof(DEVICE_EVENT_BECOMING_READY),
                                       &notReady);
            }
        }
    }

    // now, all decisions are made. display trace information.
    if (retry)
    {
        TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,  
                   "Command shall be retried in %2I64d.%03I64d seconds\n",
                   (*RetryIntervalIn100ns / UNIT_100NS_PER_SECOND),
                   (*RetryIntervalIn100ns / 10000) % 1000
                   ));
    }
    else
    {
        TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL,  
                   "Will not retry; Sense/ASC/ASCQ of %02x/%02x/%02x\n",
                   senseBuffer->SenseKey,
                   senseBuffer->AdditionalSenseCode,
                   senseBuffer->AdditionalSenseCodeQualifier
                   ));
    }

    return retry;

} // end SenseInfoInterpret()


BOOLEAN
RequestSenseInfoInterpret(
    _In_      PCDROM_DEVICE_EXTENSION   DeviceExtension,
    _In_      WDFREQUEST                Request, 
    _In_      PSCSI_REQUEST_BLOCK       Srb,
    _In_      ULONG                     RetriedCount,
    _Out_     NTSTATUS*                 Status,
    _Out_opt_ _Deref_out_range_(0, MAXIMUM_RETRY_FOR_SINGLE_IO_IN_100NS_UNITS)
              LONGLONG*                 RetryIntervalIn100ns
    )
/*++

Routine Description:

Interpret the error, process it.
    1. Release device queue if it's frozen.
    2. Interpret and process the error.

Arguments:

    DeviceExtension - Supplies the device object associated with this request.
    Request - the Request that error occurs on.
    Srb - Supplies the scsi request block which failed.
    RetriedCount - retried count.

Return Value:

    BOOLEAN TRUE:  Drivers should retry this request.
            FALSE: Drivers should not retry this request.
    Status - Returns the status for the request.
    RetryIntervalIn100nsUnits - Number of 100ns before the request should be retried.
                                Zero indicates the request should be immediately retried.

--*/
{
    BOOLEAN                 retry = FALSE;
    LONGLONG                retryIntervalIn100ns = 0;
    PZERO_POWER_ODD_INFO    zpoddInfo = DeviceExtension->ZeroPowerODDInfo;

    if (SRB_STATUS(Srb->SrbStatus) == SRB_STATUS_SUCCESS)
    {
        // request succeeded. 
        if ((zpoddInfo != NULL) &&
            (zpoddInfo->BecomingReadyRetryCount > 0))
        {
            zpoddInfo->BecomingReadyRetryCount = 0;
        }

        *Status = STATUS_SUCCESS;
        retry = FALSE;
    }
    else
    {
        // request failed. We need to process the error.
        
        // 1. Release the queue if it is frozen.
        if (Srb->SrbStatus & SRB_STATUS_QUEUE_FROZEN) 
        {
            DeviceReleaseQueue(DeviceExtension->Device);
        }

        if ((zpoddInfo != NULL) &&
            ((zpoddInfo->RetryFirstCommand != FALSE) || (zpoddInfo->BecomingReadyRetryCount > 0)))
        {
            retry = SenseInfoInterpretForZPODD(DeviceExtension,
                                               Srb,
                                               Status,
                                               &retryIntervalIn100ns);
        }

        if (retry == FALSE)
        {
            // 2. Error Processing
            if ((zpoddInfo != NULL) &&
                (zpoddInfo->BecomingReadyRetryCount > 0))
            {
                zpoddInfo->BecomingReadyRetryCount = 0;
            }

            retry = SenseInfoInterpret(DeviceExtension,
                                       Request,
                                       Srb,
                                       RetriedCount,
                                       Status,
                                       &retryIntervalIn100ns);
        }
    }

    if (RetryIntervalIn100ns != NULL)
    {
        *RetryIntervalIn100ns = retryIntervalIn100ns;
    }

    return retry;
}


BOOLEAN
RequestSenseInfoInterpretForScratchBuffer(
    _In_    PCDROM_DEVICE_EXTENSION DeviceExtension,
    _In_    ULONG                   RetriedCount,
    _Out_   NTSTATUS*               Status,
    _Out_ _Deref_out_range_(0, MAXIMUM_RETRY_FOR_SINGLE_IO_IN_100NS_UNITS)
            LONGLONG*               RetryIntervalIn100ns
    )
/*++

Routine Description:

    to analyze the error occurred and set the status, retry interval and decide to retry or not.

Arguments:

    DeviceExtension - device extension
    RetriedCount - already retried count.

Return Value:

    BOOLEAN - TRUE (should retry)
    Status - NTSTATUS
    RetryIntervalIn100nsUnits - retry interval

--*/
{
    NT_ASSERT(DeviceExtension->ScratchContext.ScratchInUse != 0);

    return RequestSenseInfoInterpret(DeviceExtension,
                                     DeviceExtension->ScratchContext.ScratchRequest,
                                     DeviceExtension->ScratchContext.ScratchSrb,
                                     RetriedCount,
                                     Status,
                                     RetryIntervalIn100ns);
}


Our Services

  • What our customers say about us?

© 2011-2024 All Rights Reserved. Joya Systems. 4425 South Mopac Building II Suite 101 Austin, TX 78735 Tel: 800-DEV-KERNEL

Privacy Policy. Terms of use. Valid XHTML & CSS