_Out_opt_ _Deref_out_range_()

in storage/class/classpnp/src/class.c [4415:6564]


    _Out_opt_ _Deref_out_range_(0,100) ULONG *RetryInterval
    )
{
    PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
    PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
    PSTORAGE_REQUEST_BLOCK_HEADER Srb = (PSTORAGE_REQUEST_BLOCK_HEADER)_Srb;
    PVOID senseBuffer = SrbGetSenseInfoBuffer(Srb);
    BOOLEAN retry = TRUE;
    BOOLEAN logError = FALSE;
    BOOLEAN unhandledError = FALSE;
    BOOLEAN incrementErrorCount = FALSE;

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

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

    ULONGLONG badSector = 0;
    ULONG uniqueId = 0;

    NTSTATUS logStatus;

    ULONGLONG readSector;
    ULONG index;

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

#if DBG
    BOOLEAN isReservationConflict = FALSE;
#endif

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

    *Status = STATUS_IO_DEVICE_ERROR;
    logStatus = -1;

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

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

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

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

    NT_ASSERT(fdoExtension->CommonExtension.IsFdo);


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

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

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

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

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

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

    } else {

        BOOLEAN validSense = FALSE;

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

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


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

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

                NT_ASSERT(FALSE);

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

            if (!validSense) {
                goto __ClassInterpretSenseInfo_ProcessingInvalidSenseBuffer;
            }

            errorCode = ScsiGetSenseErrorCode(senseBuffer);

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

            if (IsDescriptorSenseDataFormat(senseBuffer)) {

                //
                // Sense data in Descriptor format
                //

                PVOID startBuffer = NULL;
                UCHAR startBufferLength = 0;


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

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

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

                        descriptorLength = ScsiGetSenseDescriptorLength(outBuffer);

                        if (outBufferLength < descriptorLength) {

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

                        if (outType == SCSI_SENSE_DESCRIPTOR_TYPE_BLOCK_COMMAND) {

                            //
                            // Block Command type
                            //

                            if (!foundBlockCommandType) {

                                foundBlockCommandType = TRUE;

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

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

                        } else if (outType == SCSI_SENSE_DESCRIPTOR_TYPE_INFORMATION) {

                            //
                            // Information type
                            //

                            if (!foundInformationType) {

                                foundInformationType = TRUE;

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

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

                        } else {

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

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

                //
                // Sense data in Fixed format
                //

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


            switch (senseKey) {

                case SCSI_SENSE_NO_SENSE: {

                    //
                    // Check other indicators.
                    //

                    if (isIncorrectLengthValid && incorrectLength) {

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

                    } else {

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

                    break;
                } // end SCSI_SENSE_NO_SENSE

                case SCSI_SENSE_RECOVERED_ERROR: {

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

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

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

                        case SCSI_ADSENSE_FAILURE_PREDICTION_THRESHOLD_EXCEEDED: {

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

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

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

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

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

                            fdoExtension->FailurePredicted = TRUE;
                            break;
                        }

                        default: {
                            logStatus = IO_ERR_CONTROLLER_ERROR;
                            break;
                        }

                    } // end switch(addlSenseCode)

                    if (isIncorrectLengthValid && incorrectLength) {

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


                    break;
                } // end SCSI_SENSE_RECOVERED_ERROR

                case SCSI_SENSE_NOT_READY: {

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

                    switch (addlSenseCode) {

                        case SCSI_ADSENSE_LUN_NOT_READY: {

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

                            retryInterval = NOT_READY_RETRY_INTERVAL;

                            switch (addlSenseCodeQual) {

                                case SCSI_SENSEQ_BECOMING_READY: {
                                    DEVICE_EVENT_BECOMING_READY notReady = {0};

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

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

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

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

                                case SCSI_SENSEQ_OPERATION_IN_PROGRESS: {
                                    DEVICE_EVENT_BECOMING_READY notReady = {0};

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

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

                                    break;
                                }

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

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

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

                                    break;
                                }

                                case SCSI_SENSEQ_CAUSE_NOT_REPORTABLE: {

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

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

                                        SCSI_SENSE_NOT_READY/SCSI_ADSENSE_NO_MEDIA_IN_DEVICE

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

                                        */
                                        retry = FALSE;
                                        break;

                                    } else {

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

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

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

                                        ClassSendStartUnit(Fdo);
                                    }
                                    break;
                                }

                            } // end switch (addlSenseCodeQual)
                            break;
                        }

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

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

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

                            break;
                        }
                    } // end switch (addlSenseCode)

                    break;
                } // end SCSI_SENSE_NOT_READY

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

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

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

                        switch (addlSenseCodeQual) {

                            case SCSI_SENSEQ_UNKNOWN_FORMAT: {

                                *Status = STATUS_UNRECOGNIZED_MEDIA;

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

                            case SCSI_SENSEQ_CLEANING_CARTRIDGE_INSTALLED: {

                                *Status = STATUS_CLEANER_CARTRIDGE_INSTALLED;
                                logError = FALSE;
                                break;

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

                    } // end SCSI_ADSENSE_INVALID_MEDIA

                    break;

                } // end SCSI_SENSE_MEDIUM_ERROR

                case SCSI_SENSE_HARDWARE_ERROR: {
                    BOOLEAN logHardwareError = TRUE;

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

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

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

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

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

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

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

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

                            senseBufferSize = min(senseBufferSize, senseInfoBufferLength);

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

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

                    break;
                } // end SCSI_SENSE_HARDWARE_ERROR

                case SCSI_SENSE_ILLEGAL_REQUEST: {

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

                    switch (addlSenseCode) {

                        case SCSI_ADSENSE_NO_SENSE: {

                            switch (addlSenseCodeQual) {

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

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

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

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

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

                        case SCSI_ADSENSE_LUN_COMMUNICATION: {

                            switch (addlSenseCodeQual) {

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

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

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

                        case SCSI_ADSENSE_COPY_TARGET_DEVICE_ERROR: {

                            switch (addlSenseCodeQual) {

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

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

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

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

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

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

                            NT_ASSERTMSG("Parameter data truncation", FALSE);

                            *Status = STATUS_DATA_OVERRUN;
                            break;
                        }

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

                        case SCSI_ADSENSE_ILLEGAL_BLOCK: {

                            LARGE_INTEGER logicalBlockAddr;
                            LARGE_INTEGER lastLBA;
                            ULONG numTransferBlocks = 0;

                            logicalBlockAddr.QuadPart = 0;

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

                            *Status = STATUS_NONEXISTENT_SECTOR;

                            if (Fdo->DeviceType == FILE_DEVICE_DISK) {

                                if (IS_SCSIOP_READWRITE(cdbOpcode) && cdb) {

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

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

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

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

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

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

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

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

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

                            *Status = STATUS_INVALID_TOKEN;
                            break;
                        }

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

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

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

                            } else {

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

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

                                retryInterval = 3;
                            }
                            break;
                        }

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

                        case SCSI_ADSENSE_INVALID_FIELD_PARAMETER_LIST: {

                            switch (addlSenseCodeQual) {

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

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

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

                                    *Status = STATUS_INVALID_OFFSET_ALIGNMENT;
                                    break;
                                }

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

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

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

                                    *Status = STATUS_TOO_MANY_SEGMENT_DESCRIPTORS;
                                    break;
                                }

                                default: {

                                    if (ClasspIsOffloadDataTransferCommand(cdb)) {

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

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

                                        *Status = STATUS_INVALID_FIELD_IN_PARAMETER_LIST;
                                    }

                                    break;
                                }
                            }
                            break;
                        }

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

                            *Status = STATUS_COPY_PROTECTION_FAILURE;

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

                            break;
                        }

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

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

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

                    } // end switch (addlSenseCode)

                    break;
                } // end SCSI_SENSE_ILLEGAL_REQUEST

                case SCSI_SENSE_UNIT_ATTENTION: {

                    ULONG count;

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

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


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

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

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

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

                                ClassQueueCapacityChangedEventWorker(Fdo);

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

                        case SCSI_ADSENSE_LB_PROVISIONING: {

                            switch (addlSenseCodeQual) {

                                case SCSI_SENSEQ_SOFT_THRESHOLD_REACHED: {

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

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

                            } // end  switch (addlSenseCodeQual)
                            break;
                        }

                        case SCSI_ADSENSE_OPERATING_CONDITIONS_CHANGED: {

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

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

                                if (addlSenseCodeQual == SCSI_SENSEQ_INQUIRY_DATA_CHANGED) {

                                    ClassQueueProvisioningTypeChangedEventWorker(Fdo);
                                }

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

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

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

                        case SCSI_ADSENSE_OPERATOR_REQUEST: {
                            switch (addlSenseCodeQual) {

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

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

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

                        case SCSI_ADSENSE_FAILURE_PREDICTION_THRESHOLD_EXCEEDED: {

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

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

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

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

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

                            fdoExtension->FailurePredicted = TRUE;

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

                            break;
                        }

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

                    } // end  switch (addlSenseCode)

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

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

                            SET_FLAG(Fdo->Flags, DO_VERIFY_VOLUME);

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

                    break;

                } // end SCSI_SENSE_UNIT_ATTENTION

                case SCSI_SENSE_DATA_PROTECT: {

                    retry = FALSE;

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

                            case SCSI_SENSEQ_SPACE_ALLOC_FAILED_WRITE_PROTECT: {

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

                                ClassQueueResourceExhaustionEventWorker(Fdo);

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

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

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

                case SCSI_SENSE_COPY_ABORTED: {

                    switch (addlSenseCode) {

                        case SCSI_ADSENSE_COPY_TARGET_DEVICE_ERROR: {

                            switch (addlSenseCodeQual) {

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

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

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

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

                        switch (addlSenseCode) {

                            case SCSI_ADSENSE_COPY_TARGET_DEVICE_ERROR: {

                                switch (addlSenseCodeQual) {

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

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

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

                            case SCSI_ADSENSE_RESOURCE_FAILURE: {

                                switch (addlSenseCodeQual) {

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

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

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

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

            } // end switch (senseKey)



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

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

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

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

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

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

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

__ClassInterpretSenseInfo_ProcessingInvalidSenseBuffer:

        if (!validSense) {

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

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


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

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

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

                case SRB_STATUS_ERROR: {

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

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

                        incrementErrorCount = TRUE;

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

                        *Status = STATUS_DEVICE_NOT_READY;
                        logRetryableError = FALSE;
                    }

                    break;
                }

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

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

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

                case SRB_STATUS_TIMEOUT:
                case SRB_STATUS_COMMAND_TIMEOUT: {

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

                case SRB_STATUS_PARITY_ERROR:
                case SRB_STATUS_UNEXPECTED_BUS_FREE:

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

                case SRB_STATUS_BUS_RESET: {

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

                case SRB_STATUS_DATA_OVERRUN: {

                    *Status = STATUS_DATA_OVERRUN;
                    retry = FALSE;

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

                    break;
                }

                case SRB_STATUS_PHASE_SEQUENCE_FAILURE: {

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

                    incrementErrorCount = TRUE;
                    *Status = STATUS_IO_DEVICE_ERROR;

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

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

                    break;
                }

                case SRB_STATUS_REQUEST_FLUSHED: {

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

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

                        *Status = STATUS_VERIFY_REQUIRED;
                        retry = FALSE;

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

                    break;
                }


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


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

        }

    }

    if (incrementErrorCount) {

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

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

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

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

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

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

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

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

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

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

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

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

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

#if DBG

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

#endif

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

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

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

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

            UCHAR validSenseBytes = 0;
            UCHAR senseInfoBufferLength = 0;

            senseInfoBufferLength = SrbGetSenseInfoBufferLength(Srb);

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

            if (IsDescriptorSenseDataFormat(senseBuffer)) {

                convertedSenseBufferLength = sizeof(convertedSenseBuffer);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            logRetryableError = FALSE;
        }

        if (logRetryableError) {

            logError = TRUE;
        }

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

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

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

                        break;
                    }

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

                        break;
                    }
                }
            }
        }

        if (logError){

            if (logRetryableError) {

                NT_ASSERT(IS_SCSIOP_READWRITE(cdbOpcode));

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

            } else {

                PIO_ERROR_LOG_PACKET errorLogEntry;
                PCLASS_ERROR_LOG_DATA errlogData;

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

                    *errorLogEntry = staticErrLogEntry;
                    *errlogData = staticErrLogData;

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

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

    return retry;

} // end ClassInterpretSenseInfo()