/*++

Copyright (c) 1995  Microsoft Corporation

Module Name:

    spt.c

Abstract:

    A user mode library that allows simple commands to be sent to a
    selected scsi device.

Environment:

    User mode only

Revision History:
    
    4/10/2000 - created

--*/

#include "sptlibp.h"


BOOL
SptUtilValidateCdbLength(
    IN PCDB Cdb,
    IN UCHAR CdbSize
    )
{
    UCHAR commandGroup = (Cdb->AsByte[0] >> 5) & 0x7;
    
    switch (commandGroup) {
    case 0:
        return (CdbSize ==  6);
    case 1:
    case 2:
        return (CdbSize == 10);
    case 5:
        return (CdbSize == 12);
    default:
        return TRUE;
    }
}

BOOL
SptSendCdbToDevice(
    IN      HANDLE  DeviceHandle,
    IN      PCDB    Cdb,
    IN      UCHAR   CdbSize,
    IN      PUCHAR  Buffer,
    IN OUT  PDWORD  BufferSize,
    IN      BOOLEAN GetDataFromDevice
    )
{
    return SptSendCdbToDeviceEx(DeviceHandle,
                             Cdb,
                             CdbSize,
                             Buffer,
                             BufferSize,
                             NULL,
                             0,
                             GetDataFromDevice,
                             SPT_DEFAULT_TIMEOUT
                             );
}

/*++

Routine Description:

Arguments:

Return Value:

--*/
BOOL
SptSendCdbToDeviceEx(
    IN      HANDLE      DeviceHandle,
    IN      PCDB        Cdb,
    IN      UCHAR       CdbSize,
    IN OUT  PUCHAR      Buffer,
    IN OUT  PDWORD      BufferSize,
       OUT  PSENSE_DATA SenseData OPTIONAL,
    IN      UCHAR       SenseDataSize,
    IN      BOOLEAN     GetDataFromDevice,
    IN      DWORD       TimeOut                    // in seconds
    )
{
    PSPT_WITH_BUFFERS p;
    DWORD packetSize;
    DWORD returnedBytes;
    BOOL returnValue;
    PSENSE_DATA senseBuffer;
    BOOL copyData;
    
    if ((SenseDataSize == 0) && (SenseData != NULL)) {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }
    
    if ((SenseDataSize != 0) && (SenseData == NULL)) {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }
    
    if (SenseData && SenseDataSize) {
        RtlZeroMemory(SenseData, SenseDataSize);
    }

    if (Cdb == NULL) {
        // cannot send NULL cdb
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    if (CdbSize < 1 || CdbSize > 16) {
        // Cdb size too large or too small for this library currently
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    if (!SptUtilValidateCdbLength(Cdb, CdbSize)) {
        // OpCode Cdb->AsByte[0] is not size CdbSize
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }


    if (BufferSize == NULL) {
        // BufferSize pointer cannot be NULL
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }
    
    if ((*BufferSize != 0) && (Buffer == NULL)) {
        // buffer cannot be NULL if *BufferSize is non-zero
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    if ((*BufferSize == 0) && (Buffer != NULL)) {
        // buffer must be NULL if *BufferSize is zero
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    if ((*BufferSize) && GetDataFromDevice) {

        //
        // pre-zero output buffer (not input buffer)
        //

        memset(Buffer, 0, (*BufferSize));
    }

    packetSize = sizeof(SPT_WITH_BUFFERS) + (*BufferSize);
    p = (PSPT_WITH_BUFFERS)LocalAlloc(LPTR, packetSize);
    if (p == NULL) {
        // could not allocate memory for pass-through
        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
        return FALSE;
    }

    //
    // this has the side effect of pre-zeroing the output buffer 
    // if DataIn is TRUE, the SenseData (always), etc.
    //

    memset(p, 0, packetSize);
    memcpy(p->Spt.Cdb, Cdb, CdbSize);

    p->Spt.Length             = sizeof(SCSI_PASS_THROUGH);
    p->Spt.CdbLength          = CdbSize;
    p->Spt.SenseInfoLength    = SENSE_BUFFER_SIZE;
    p->Spt.DataIn             = (GetDataFromDevice ? 1 : 0);
    p->Spt.DataTransferLength = (*BufferSize);
    p->Spt.TimeOutValue       = TimeOut;
    p->Spt.SenseInfoOffset =
        FIELD_OFFSET(SPT_WITH_BUFFERS, SenseInfoBuffer[0]);
    p->Spt.DataBufferOffset =
        FIELD_OFFSET(SPT_WITH_BUFFERS, DataBuffer[0]);

    if ((*BufferSize != 0) && (!GetDataFromDevice)) {
        
        //
        // if we're sending the device data, copy the user's buffer
        //

        RtlCopyMemory(&(p->DataBuffer[0]), Buffer, *BufferSize);

    }

    returnedBytes = 0;
    
    returnValue = DeviceIoControl(DeviceHandle,
                                  IOCTL_SCSI_PASS_THROUGH,
                                  p,
                                  packetSize,
                                  p,
                                  packetSize,
                                  &returnedBytes,
                                  FALSE);

    senseBuffer = (PSENSE_DATA)p->SenseInfoBuffer;
    
    copyData = FALSE;

    if (senseBuffer->SenseKey & 0xf) {

        UCHAR length;
        
        // determine appropriate length to return
        length = senseBuffer->AdditionalSenseLength;
        length += RTL_SIZEOF_THROUGH_FIELD(SENSE_DATA, AdditionalSenseLength);
        if (length > SENSE_BUFFER_SIZE) {
            length = SENSE_BUFFER_SIZE;
        }
        length = min(length, SenseDataSize);

        // copy the sense data back to the user regardless
        RtlCopyMemory(SenseData, senseBuffer, length);

        returnValue = FALSE;     // some error (possibly recovered) occurred
        copyData = TRUE;         // copy data anyways


    } else if (p->Spt.ScsiStatus != 0) {  // scsi protocol error
    
        returnValue = FALSE;     // command failed

    } else if (!returnValue) {
        
        // returnValue = returnValue;
    
    } else {

        copyData = TRUE;

    }
    
    if (copyData && GetDataFromDevice) {

        //
        // upon successful completion of a command getting data from the
        // device, copy the returned data back to the user.
        //

        if (*BufferSize > p->Spt.DataTransferLength) {
            *BufferSize = p->Spt.DataTransferLength;
        }
        memcpy(Buffer, p->DataBuffer, *BufferSize);

    }

    //
    // free our memory and return
    //

    LocalFree(p);
    return returnValue;
}

/*++

Routine Description:

Arguments:

Return Value:

--*/
VOID
SptDeleteModePageInfo(
    IN PSPT_MODE_PAGE_INFO ModePageInfo
    )
{
//    ASSERT(ModePageInfo != NULL); BUGBUG
//    ASSERT(ModePageInfo->Signature == SPT_MODE_PAGE_INFO_SIGNATURE); BUGBUG
    LocalFree(ModePageInfo);
    return;
}

/*++

Routine Description:

Arguments:

Return Value:

--*/
BOOL
SptUpdateModePageInfo(
    IN HANDLE              DeviceHandle,
    IN PSPT_MODE_PAGE_INFO ModePageInfo
    )
{
    SENSE_DATA senseData;
    BOOLEAN retry = FALSE;
    DWORD error;
    DWORD delay;

    CDB cdb;
    DWORD attemptNumber = 0;
    BOOL success;

    if ((DeviceHandle == INVALID_HANDLE_VALUE) ||
        (ModePageInfo == NULL) ||
        (!ModePageInfo->IsInitialized) ||
        (ModePageInfo->Signature != SPT_MODE_PAGE_INFO_SIGNATURE)) {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }
    if ((ModePageInfo->ModePageSize != SptModePageSizeModeSense6) &&
        (ModePageInfo->ModePageSize != SptModePageSizeModeSense10)) {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

RetryUpdateModePage:

    attemptNumber ++;
    
    RtlZeroMemory(&cdb, sizeof(CDB));
    RtlZeroMemory(&senseData, sizeof(SENSE_DATA));
    
    if (ModePageInfo->ModePageSize == SptModePageSizeModeSense6) {

        DWORD sizeToGet = sizeof(MODE_PARAMETER_HEADER);

        cdb.MODE_SENSE.OperationCode = SCSIOP_MODE_SENSE;
        cdb.MODE_SENSE.PageCode = 0x3f;
        cdb.MODE_SENSE.AllocationLength = sizeof(MODE_PARAMETER_HEADER);

        success = SptSendCdbToDeviceEx(DeviceHandle,
                                       &cdb,
                                       6,
                                       &(ModePageInfo->H.AsChar),
                                       &sizeToGet,
                                       &senseData,
                                       sizeof(SENSE_DATA),
                                       TRUE,
                                       SPT_MODE_SENSE_TIMEOUT);
            
    } else {

        DWORD sizeToGet = sizeof(MODE_PARAMETER_HEADER10);

        cdb.MODE_SENSE10.OperationCode = SCSIOP_MODE_SENSE10;
        cdb.MODE_SENSE10.PageCode = 0x3f;
        cdb.MODE_SENSE10.AllocationLength[0] = sizeof(MODE_PARAMETER_HEADER10);
    
        success = SptSendCdbToDeviceEx(DeviceHandle,
                                       &cdb,
                                       10,
                                       &(ModePageInfo->H.AsChar),
                                       &sizeToGet,
                                       &senseData,
                                       sizeof(SENSE_DATA),
                                       TRUE,
                                       SPT_MODE_SENSE_TIMEOUT);
    }
    
    if (!success) {
        return FALSE;
    }

    //
    // if the pass-through succeeded, we need to still determine if the
    // actual CDB succeeded.  use the InterpretSenseInfo() routine.
    //

    SptUtilInterpretSenseInfo(&senseData,
                              sizeof(SENSE_DATA),
                              &error,
                              &retry,
                              &delay);

    if (error != ERROR_SUCCESS) {

        if (retry && (attemptNumber < MAXIMUM_DEFAULT_RETRIES)) {

            if (delay != 0) {
                // sleep?
            }
            
            goto RetryUpdateModePage;
        }
        
        // else the error was not worth retrying, or we've retried too often.
        SetLastError(error);
        return FALSE;

    }

    //
    // the command succeeded.
    //
    
    if (ModePageInfo->ModePageSize == SptModePageSizeModeSense6) {
        ModePageInfo->H.Header6.ModeDataLength = 0;
    } else {
        ModePageInfo->H.Header10.ModeDataLength[0] = 0;
        ModePageInfo->H.Header10.ModeDataLength[1] = 0;
    }

    return TRUE;
}

/*++

Routine Description:

Arguments:

Return Value:

--*/
PSPT_MODE_PAGE_INFO
SptInitializeModePageInfo(
    IN     HANDLE                DeviceHandle
    )
{
    PSPT_MODE_PAGE_INFO localInfo;
    DWORD i;

    localInfo = LocalAlloc(LPTR, sizeof(SPT_MODE_PAGE_INFO));
    if (localInfo == NULL) {
        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
        return FALSE;
    }

    //
    // try both 10 and 6 byte mode sense commands
    //          

    for (i = 0; i < 2; i++) {

        RtlZeroMemory(localInfo, sizeof(SPT_MODE_PAGE_INFO));
        localInfo->IsInitialized = TRUE;
        localInfo->Signature = SPT_MODE_PAGE_INFO_SIGNATURE;

        if ( i == 0 ) {
            localInfo->ModePageSize = SptModePageSizeModeSense10;
        } else {
            localInfo->ModePageSize = SptModePageSizeModeSense6;
        }

        if (SptUpdateModePageInfo(DeviceHandle, localInfo)) {
            return localInfo;
        }        
    }
    LocalFree(localInfo);
    
    return NULL;
}

/*++

Routine Description:

Arguments:

Return Value:

--*/
BOOL
SptGetModePage(
    IN     HANDLE              DeviceHandle,
    IN     PSPT_MODE_PAGE_INFO ModePageInfo,
    IN     UCHAR               ModePage,
       OUT PUCHAR              Buffer,
    IN OUT PDWORD              BufferSize,
       OUT PDWORD              AvailableModePageData OPTIONAL
    )
{
    PUCHAR localBuffer = NULL;
    DWORD localBufferSize = 0;
    SENSE_DATA senseData;
    CDB cdb;
    DWORD thisAttempt = 0;
    BOOL success;

    DWORD  finalPageSize;
    PUCHAR finalPageStart;


    if ((DeviceHandle == INVALID_HANDLE_VALUE) ||
        (ModePageInfo == NULL) ||
        (!ModePageInfo->IsInitialized) ||
        (ModePageInfo->Signature != SPT_MODE_PAGE_INFO_SIGNATURE) ||
        (ModePageInfo->ModePageSize == SptModePageSizeUndefined) ||
        (ModePage & 0xC0)) {

        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
        
    }
    if (((BufferSize == 0) && (Buffer != NULL)) ||
        ((BufferSize != 0) && (Buffer == NULL))) {
        
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;

    }

    if ((ModePageInfo->ModePageSize != SptModePageSizeModeSense6) &&
        (ModePageInfo->ModePageSize != SptModePageSizeModeSense10)) {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }
    
    localBufferSize = 0;
    
    if (BufferSize != NULL) {
        localBufferSize += *BufferSize;
    }

    if (ModePageInfo->ModePageSize == SptModePageSizeModeSense6) {

        localBufferSize += sizeof(MODE_PARAMETER_HEADER);
        localBufferSize += ModePageInfo->H.Header6.BlockDescriptorLength;
        if (localBufferSize > 0xff) {
            SetLastError(ERROR_INVALID_PARAMETER);
            return FALSE;
        }

    } else {

        localBufferSize += sizeof(MODE_PARAMETER_HEADER);
        localBufferSize += ModePageInfo->H.Header10.BlockDescriptorLength[0] * 256;
        localBufferSize += ModePageInfo->H.Header10.BlockDescriptorLength[1];
        if (localBufferSize > 0xffff) {
            SetLastError(ERROR_INVALID_PARAMETER);
            return FALSE;
        }
        
    }    

    localBuffer = LocalAlloc(LPTR, localBufferSize);
    if (localBuffer == NULL) {
        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
        return FALSE;
    }

RetryGetModePage:

    thisAttempt ++;

    RtlZeroMemory(&cdb, sizeof(CDB));
    RtlZeroMemory(&senseData, sizeof(SENSE_DATA));
    RtlZeroMemory(localBuffer, localBufferSize);

    if (ModePageInfo->ModePageSize == SptModePageSizeModeSense6) {

        DWORD sizeToGet = localBufferSize;
        cdb.MODE_SENSE.OperationCode = SCSIOP_MODE_SENSE;
        cdb.MODE_SENSE.PageCode = ModePage;
        cdb.MODE_SENSE.AllocationLength = (UCHAR)localBufferSize;

        success = SptSendCdbToDeviceEx(DeviceHandle,
                                       &cdb,
                                       6,
                                       localBuffer,
                                       &sizeToGet,
                                       &senseData,
                                       sizeof(SENSE_DATA),
                                       TRUE,
                                       SPT_MODE_SENSE_TIMEOUT);

    } else {

        DWORD sizeToGet = localBufferSize;
        cdb.MODE_SENSE10.OperationCode = SCSIOP_MODE_SENSE10;
        cdb.MODE_SENSE10.PageCode = ModePage;
        cdb.MODE_SENSE10.AllocationLength[0] = (UCHAR)(localBufferSize >> 8);
        cdb.MODE_SENSE10.AllocationLength[1] = (UCHAR)(localBufferSize & 0xff);
    
        success = SptSendCdbToDeviceEx(DeviceHandle,
                                       &cdb,
                                       10,
                                       localBuffer,
                                       &sizeToGet,
                                       &senseData,
                                       sizeof(SENSE_DATA),
                                       TRUE,
                                       SPT_MODE_SENSE_TIMEOUT);
    }

    //
    // if the pass-through failed, we need to still determine if the
    // actual CDB succeeded.  use the InterpretSenseInfo() routine.
    //
    
    if (!success) {
        LocalFree( localBuffer );
        return FALSE;
    }

    {
        DWORD interpretedError;
        BOOLEAN retry;
        DWORD delay;

        SptUtilInterpretSenseInfo(&senseData,
                                  sizeof(SENSE_DATA),
                                  &interpretedError,
                                  &retry,
                                  &delay);

        if (interpretedError != ERROR_SUCCESS) {
            if ((retry == FALSE) || (thisAttempt > MAXIMUM_DEFAULT_RETRIES)){
                SetLastError(interpretedError);
                LocalFree( localBuffer );
                return FALSE;
            }

            // BUGBUG - sleep?
            goto RetryGetModePage;
        }
    }

    //
    // the transfer succeeded.  now to transfer the info back to
    // the caller.
    //
    
    if (ModePageInfo->ModePageSize == SptModePageSizeModeSense6) {

        PMODE_PARAMETER_HEADER header6 = (PMODE_PARAMETER_HEADER)localBuffer;
        finalPageSize  = header6->ModeDataLength + 1; // length field itself.   

        finalPageStart = localBuffer;        
        finalPageStart += sizeof(MODE_PARAMETER_HEADER);
        finalPageSize  -= sizeof(MODE_PARAMETER_HEADER);
        finalPageStart += header6->BlockDescriptorLength;
        finalPageSize  -= header6->BlockDescriptorLength;

    } else {

        PMODE_PARAMETER_HEADER10 header10 = (PMODE_PARAMETER_HEADER10)localBuffer;
        finalPageSize  = header10->ModeDataLength[0] * 256;
        finalPageSize += header10->ModeDataLength[1];
        finalPageSize += 2; // length field itself.

        finalPageStart = localBuffer;        
        finalPageStart += sizeof(MODE_PARAMETER_HEADER10);
        finalPageSize  -= sizeof(MODE_PARAMETER_HEADER10);
        finalPageStart += header10->BlockDescriptorLength[0] * 256;
        finalPageStart += header10->BlockDescriptorLength[1];
        finalPageSize  -= header10->BlockDescriptorLength[0] * 256;
        finalPageSize  -= header10->BlockDescriptorLength[1];
        
    }

    //
    // finalPageStart now points to the page start
    // finalPageSize  says how much of the data is valid
    //

    //
    // allow the user to distinguish between available data
    // and the amount we actually returned that was usable.
    //
    
    if (AvailableModePageData != NULL) {
        *AvailableModePageData = finalPageSize;
    }

    //
    // if the expected buffer size is greater than the device
    // returned, update the user's available size info
    //
    
    if (*BufferSize > finalPageSize) {
        *BufferSize = finalPageSize;
    }

    //
    // finally, copy whatever data is available to the user's buffer.
    //
    
    if (*BufferSize != 0) {
        RtlCopyMemory(Buffer, localBuffer, *BufferSize);
    }

    return TRUE;
}

/*++

Routine Description:

Arguments:

Return Value:

--*/
BOOL
SptSetModePage(
    IN     HANDLE              DeviceHandle,
    IN     PSPT_MODE_PAGE_INFO ModePageSize,
    IN     SPT_MODE_PAGE_TYPE  ModePageType,
    IN     UCHAR               ModePage,
       OUT PUCHAR              Buffer,
    IN OUT PDWORD              BufferSize
    )
{
    SetLastError(ERROR_INVALID_FUNCTION);
    return FALSE;
}

/*++

Routine Description:

    NOTE: we default to RETRY==TRUE except for known error classes

Arguments:

Return Value:


--*/
VOID
SptUtilInterpretSenseInfo(
    IN     PSENSE_DATA SenseData,
    IN     UCHAR       SenseDataSize,
       OUT PDWORD      ErrorValue,  // from WinError.h
       OUT PBOOLEAN    SuggestRetry OPTIONAL,
       OUT PDWORD      SuggestRetryDelay OPTIONAL
    )
{
    DWORD   error;
    DWORD   retryDelay;
    BOOLEAN retry;
    UCHAR   senseKey;
    UCHAR   asc;
    UCHAR   ascq;

    if (SenseDataSize == 0) {
        retry = FALSE;
        retryDelay = 0;
        error = ERROR_IO_DEVICE;
        goto SetAndExit;

    }

    //
    // default to suggesting a retry in 1/10 of a second,
    // with a status of ERROR_IO_DEVICE.
    //
    retry = TRUE;
    retryDelay = 1;
    error = ERROR_IO_DEVICE;

    //
    // if the device didn't provide any sense this time, return.
    //

    if ((SenseData->SenseKey & 0xf) == 0) {
        retry = FALSE;
        retryDelay = 0;
        error = ERROR_SUCCESS;
        goto SetAndExit;
    }


    //
    // if we can't even see the sense key, just return.
    // can't use bitfields in these macros, so use next field.
    //

    if (SenseDataSize < FIELD_OFFSET(SENSE_DATA, Information)) {
        goto SetAndExit;
    }
    
    senseKey = SenseData->SenseKey;


    { // set the size to what's actually useful.
        UCHAR validLength;
        // figure out what we could have gotten with a large sense buffer
        if (SenseDataSize <
            RTL_SIZEOF_THROUGH_FIELD(SENSE_DATA, AdditionalSenseLength)) {
            validLength = SenseDataSize;
        } else {
            validLength =
                RTL_SIZEOF_THROUGH_FIELD(SENSE_DATA, AdditionalSenseLength);
            validLength += SenseData->AdditionalSenseLength;
        }
        // use the smaller of the two values.
        SenseDataSize = min(SenseDataSize, validLength);
    }

    if (SenseDataSize <
        RTL_SIZEOF_THROUGH_FIELD(SENSE_DATA, AdditionalSenseCode)) {
        asc = SCSI_ADSENSE_NO_SENSE;
    } else {
        asc = SenseData->AdditionalSenseCode;
    }

    if (SenseDataSize <
        RTL_SIZEOF_THROUGH_FIELD(SENSE_DATA, AdditionalSenseCodeQualifier)) {
        ascq = SCSI_SENSEQ_CAUSE_NOT_REPORTABLE; // 0x00
    } else {
        ascq = SenseData->AdditionalSenseCodeQualifier;
    }

    //
    // interpret :P
    //

    switch (senseKey & 0xf) {
    
    case SCSI_SENSE_RECOVERED_ERROR: {  // 0x01
        if (SenseData->IncorrectLength) {
            error = ERROR_INVALID_BLOCK_LENGTH;
        } else {
            error = ERROR_SUCCESS;
        }
        retry = FALSE;
        break;
    } // end SCSI_SENSE_RECOVERED_ERROR

    case SCSI_SENSE_NOT_READY: { // 0x02
        error = ERROR_NOT_READY;

        switch (asc) {

        case SCSI_ADSENSE_LUN_NOT_READY: {
            
            switch (ascq) {

            case SCSI_SENSEQ_BECOMING_READY:
            case SCSI_SENSEQ_OPERATION_IN_PROGRESS: {
                retryDelay = SPT_NOT_READY_RETRY_INTERVAL;
                break;
            }

            case SCSI_SENSEQ_CAUSE_NOT_REPORTABLE:
            case SCSI_SENSEQ_FORMAT_IN_PROGRESS:
            case SCSI_SENSEQ_LONG_WRITE_IN_PROGRESS: {
                retry = FALSE;
                break;
            }

            case SCSI_SENSEQ_MANUAL_INTERVENTION_REQUIRED: {
                retry = FALSE;
                break;
            }
            
            } // end switch (senseBuffer->AdditionalSenseCodeQualifier)
            break;
        }

        case SCSI_ADSENSE_NO_MEDIA_IN_DEVICE: {
            error = ERROR_NOT_READY;
            retry = FALSE;
            break;
        }
        } // end switch (senseBuffer->AdditionalSenseCode)

        break;
    } // end SCSI_SENSE_NOT_READY
    
    case SCSI_SENSE_MEDIUM_ERROR: { // 0x03
        error = ERROR_CRC;
        retry = FALSE;

        //
        // Check if this error is due to unknown format
        //
        if (asc == SCSI_ADSENSE_INVALID_MEDIA) {
            
            switch (ascq) {

            case SCSI_SENSEQ_UNKNOWN_FORMAT: {
                error = ERROR_UNRECOGNIZED_MEDIA;
                break;
            }

            case SCSI_SENSEQ_CLEANING_CARTRIDGE_INSTALLED: {
                error = ERROR_UNRECOGNIZED_MEDIA;
                //error = ERROR_CLEANER_CARTRIDGE_INSTALLED;
                break;
            }
            
            } // end switch AdditionalSenseCodeQualifier

        } // end SCSI_ADSENSE_INVALID_MEDIA
        break;
    } // end SCSI_SENSE_MEDIUM_ERROR

    case SCSI_SENSE_ILLEGAL_REQUEST: { // 0x05
        error = ERROR_INVALID_FUNCTION;
        retry = FALSE;
        
        switch (asc) {

        case SCSI_ADSENSE_ILLEGAL_BLOCK: {
            error = ERROR_SECTOR_NOT_FOUND;
            break;
        }

        case SCSI_ADSENSE_INVALID_LUN: {
            error = ERROR_FILE_NOT_FOUND;
            break;
        }

        case SCSI_ADSENSE_COPY_PROTECTION_FAILURE: {
            error = ERROR_FILE_ENCRYPTED;
            //error = ERROR_SPT_LIB_COPY_PROTECTION_FAILURE;
            switch (ascq) {
                case SCSI_SENSEQ_AUTHENTICATION_FAILURE:
                    //error = ERROR_SPT_LIB_AUTHENTICATION_FAILURE;
                    break;
                case SCSI_SENSEQ_KEY_NOT_PRESENT:
                    //error = ERROR_SPT_LIB_KEY_NOT_PRESENT;
                    break;
                case SCSI_SENSEQ_KEY_NOT_ESTABLISHED:
                    //error = ERROR_SPT_LIB_KEY_NOT_ESTABLISHED;
                    break;
                case SCSI_SENSEQ_READ_OF_SCRAMBLED_SECTOR_WITHOUT_AUTHENTICATION:
                    //error = ERROR_SPT_LIB_SCRAMBLED_SECTOR;
                    break;
                case SCSI_SENSEQ_MEDIA_CODE_MISMATCHED_TO_LOGICAL_UNIT:
                    //error = ERROR_SPT_LIB_REGION_MISMATCH;
                    break;
                case SCSI_SENSEQ_LOGICAL_UNIT_RESET_COUNT_ERROR:
                    //error = ERROR_SPT_LIB_RESETS_EXHAUSTED;
                    break;
            } // end switch of ASCQ for COPY_PROTECTION_FAILURE
            break;
        }

        } // end switch (senseBuffer->AdditionalSenseCode)
        break;
        
    } // end SCSI_SENSE_ILLEGAL_REQUEST

    case SCSI_SENSE_DATA_PROTECT: { // 0x07
        error = ERROR_WRITE_PROTECT;
        retry = FALSE;
        break;
    } // end SCSI_SENSE_DATA_PROTECT

    case SCSI_SENSE_BLANK_CHECK: { // 0x08
        error = ERROR_NO_DATA_DETECTED;
        break;
    } // end SCSI_SENSE_BLANK_CHECK

    case SCSI_SENSE_NO_SENSE: { // 0x00
        if (SenseData->IncorrectLength) {    
            error = ERROR_INVALID_BLOCK_LENGTH;
            retry   = FALSE;    
        } else {
            error = ERROR_IO_DEVICE;
        }
        break;
    } // end SCSI_SENSE_NO_SENSE

    case SCSI_SENSE_HARDWARE_ERROR:  // 0x04
    case SCSI_SENSE_UNIT_ATTENTION: // 0x06
    case SCSI_SENSE_UNIQUE:          // 0x09
    case SCSI_SENSE_COPY_ABORTED:    // 0x0A
    case SCSI_SENSE_ABORTED_COMMAND: // 0x0B
    case SCSI_SENSE_EQUAL:           // 0x0C
    case SCSI_SENSE_VOL_OVERFLOW:    // 0x0D
    case SCSI_SENSE_MISCOMPARE:      // 0x0E
    case SCSI_SENSE_RESERVED:        // 0x0F
    default: {
        error = ERROR_IO_DEVICE;
        break;
    }

    } // end switch(SenseKey)

SetAndExit:

    if (ARGUMENT_PRESENT(SuggestRetry)) {
        *SuggestRetry = retry;
    }
    if (ARGUMENT_PRESENT(SuggestRetryDelay)) {
        *SuggestRetryDelay = retryDelay;
    }
    *ErrorValue = error;

    return;


}

/*++

Routine Description:

Arguments:

Return Value:

--*/