/*++

Copyright (C) Microsoft Corporation, 1991 - 2010

Module Name:

    debug.c

Abstract:

    CLASSPNP debug code and data

Environment:

    kernel mode only

Notes:


Revision History:

--*/


#include "classp.h"
#include "debug.h"

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

#if DBG

    //
    // default to not breaking in for lost irps, five minutes before we even
    // bother checking for lost irps, using standard debug print macros, and
    // using a 64k debug print buffer
    //

    #ifndef     CLASS_GLOBAL_BREAK_ON_LOST_IRPS
        #error "CLASS_GLOBAL_BREAK_ON_LOST_IRPS undefined"
        #define CLASS_GLOBAL_BREAK_ON_LOST_IRPS 0
    #endif   // CLASS_GLOBAL_BREAK_ON_LOST_IRPS

    #ifndef     CLASS_GLOBAL_SECONDS_TO_WAIT_FOR_SYNCHRONOUS_SRB
        #error "CLASS_GLOBAL_SECONDS_TO_WAIT_FOR_SYNCHRONOUS_SRB undefined"
        #define CLASS_GLOBAL_SECONDS_TO_WAIT_FOR_SYNCHRONOUS_SRB 300
    #endif   // CLASS_GLOBAL_SECONDS_TO_WAIT_FOR_SYNCHRONOUS_SRB

    #ifndef     CLASS_GLOBAL_BUFFERED_DEBUG_PRINT
        #error "CLASS_GLOBAL_BUFFERED_DEBUG_PRINT undefined"
        #define CLASS_GLOBAL_BUFFERED_DEBUG_PRINT 0
    #endif   // CLASS_GLOBAL_BUFFERED_DEBUG_PRINT

    #ifndef     CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFER_SIZE
        #error "CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFER_SIZE undefined"
        #define CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFER_SIZE 512
    #endif   // CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFER_SIZE

    #ifndef     CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFERS
        #error "CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFERS undefined"
        #define CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFERS 512
    #endif   // CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFERS

#ifdef _MSC_VER
    #pragma data_seg("NONPAGE")
#endif



    CLASSPNP_GLOBALS ClasspnpGlobals;

    //
    // the low sixteen bits are used to see if the debug level is high enough
    // the high sixteen bits are used to singly enable debug levels 1-16
    //
    LONG ClassDebug = 0x00000000;

    BOOLEAN DebugTrapOnWarn = FALSE;

    //
    // Used to track callers when we receive an access and the disk
    // is powered down.
    //
    ULONG DiskSpinupIndex = 0;
    DISK_SPINUP_TRACES DiskSpinupTraces[NUMBER_OF_DISK_SPINUP_TRACES];

    VOID ClasspInitializeDebugGlobals()
    {
        KIRQL irql;

        if (InterlockedCompareExchange(&ClasspnpGlobals.Initializing, 1, 0) == 0) {

            KeInitializeSpinLock(&ClasspnpGlobals.SpinLock);

            KeAcquireSpinLock(&ClasspnpGlobals.SpinLock, &irql);

            TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "CLASSPNP.SYS => Initializing ClasspnpGlobals...\n"));

            ClasspnpGlobals.Buffer = NULL;
            ClasspnpGlobals.Index = (ULONG)-1;
            ClasspnpGlobals.BreakOnLostIrps = CLASS_GLOBAL_BREAK_ON_LOST_IRPS;
            ClasspnpGlobals.EachBufferSize = CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFER_SIZE;
            ClasspnpGlobals.NumberOfBuffers = CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFERS;
            ClasspnpGlobals.SecondsToWaitForIrps = CLASS_GLOBAL_SECONDS_TO_WAIT_FOR_SYNCHRONOUS_SRB;

            //
            // this should be the last item set
            //

            ClasspnpGlobals.UseBufferedDebugPrint = CLASS_GLOBAL_BUFFERED_DEBUG_PRINT;

            KeReleaseSpinLock(&ClasspnpGlobals.SpinLock, irql);

            InterlockedExchange(&ClasspnpGlobals.Initialized, 1);

        }
    }

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

    ClassDebugPrint()

    Routine Description:

        Debug print for all class drivers, NOOP on FRE versions.
        Allows printing to a debug buffer (with auto fallback to kdprint) by
        properly setting the Globals in classpnp on CHK versions.

    Arguments:

        Debug print level, or from 0 to 3 for legacy drivers.

    Return Value:

        None

    --*/
    VOID ClassDebugPrint(_In_ CLASS_DEBUG_LEVEL DebugPrintLevel, _In_z_ PCCHAR DebugMessage, ...)
    {
        va_list ap;
        va_start(ap, DebugMessage);

        if ((DebugPrintLevel <= (ClassDebug & 0x0000ffff)) ||
            ((1 << (DebugPrintLevel + 15)) & ClassDebug)) {

            if (ClasspnpGlobals.UseBufferedDebugPrint &&
                ClasspnpGlobals.Buffer == NULL) {

                //
                // this double-check prevents always taking
                // a spinlock just to ensure we have a buffer
                //

                KIRQL irql;

                KeAcquireSpinLock(&ClasspnpGlobals.SpinLock, &irql);
                if (ClasspnpGlobals.Buffer == NULL) {

                    SIZE_T bufferSize;
                    if (NT_SUCCESS(
                            RtlSIZETMult(ClasspnpGlobals.NumberOfBuffers,
                                         ClasspnpGlobals.EachBufferSize,
                                         &bufferSize))) {

                        DbgPrintEx(DPFLTR_CLASSPNP_ID, DPFLTR_ERROR_LEVEL,
                                   "ClassDebugPrint: Allocating %x bytes for "
                                   "classdebugprint buffer\n", (ULONG)bufferSize);
                        ClasspnpGlobals.Index = (ULONG)-1;
                        ClasspnpGlobals.Buffer =
                            ExAllocatePoolWithTag(NonPagedPoolNx, bufferSize, 'bDcS');
                        DbgPrintEx(DPFLTR_CLASSPNP_ID, DPFLTR_ERROR_LEVEL,
                                   "ClassDebugPrint: Allocated buffer at %p\n",
                                   ClasspnpGlobals.Buffer);

                        if (ClasspnpGlobals.Buffer) {
                            RtlZeroMemory(ClasspnpGlobals.Buffer, bufferSize);
                        }
                    }

                }
                KeReleaseSpinLock(&ClasspnpGlobals.SpinLock, irql);

            }

            if (ClasspnpGlobals.UseBufferedDebugPrint &&
                ClasspnpGlobals.Buffer != NULL) {

                //
                // we never free the buffer, so once it exists,
                // we can just print to it with immunity
                //

                ULONG index;
                PUCHAR buffer;
                NTSTATUS status;
                index = InterlockedIncrement((volatile LONG *)&ClasspnpGlobals.Index);
                index %= ClasspnpGlobals.NumberOfBuffers;
                index *= (ULONG)ClasspnpGlobals.EachBufferSize;

                buffer = ClasspnpGlobals.Buffer;
                buffer += index;

                RtlZeroMemory(buffer, ClasspnpGlobals.EachBufferSize);

                status = RtlStringCchVPrintfA((NTSTRSAFE_PSTR)buffer, ClasspnpGlobals.EachBufferSize, DebugMessage, ap);
                if (!NT_SUCCESS(status))
                {
                    *buffer = 0; // force-null on failure
                }

            } else {

                //
                // either we could not allocate a buffer for debug prints
                // or buffered debug prints are disabled
                //

                vDbgPrintEx(DPFLTR_CLASSPNP_ID, DPFLTR_INFO_LEVEL, DebugMessage, ap);

            }

        }

        va_end(ap);

    }


    /*
     *  DbgCheckReturnedPkt
     *
     *      Check a completed TRANSFER_PACKET for all sorts of error conditions
     *      and warn/trap appropriately.
     */
    VOID DbgCheckReturnedPkt(TRANSFER_PACKET *Pkt)
    {
        PCDB pCdb = ClasspTransferPacketGetCdb(Pkt);

        NT_ASSERT(SrbGetOriginalRequest(Pkt->Srb) == Pkt->Irp);
        NT_ASSERT(SrbGetDataBuffer(Pkt->Srb) == Pkt->BufPtrCopy);
        NT_ASSERT(SrbGetDataTransferLength(Pkt->Srb) <= Pkt->BufLenCopy);
        NT_ASSERT(!Pkt->Irp->CancelRoutine);

        if (SRB_STATUS(Pkt->Srb->SrbStatus) == SRB_STATUS_PENDING){
            TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_RW, "SRB completed with status PENDING in packet %ph: (op=%s srbstat=%s(%xh), irpstat=%xh)",
                        Pkt,
                        DBGGETSCSIOPSTR(Pkt->Srb),
                        DBGGETSRBSTATUSSTR(Pkt->Srb),
                        (ULONG)Pkt->Srb->SrbStatus,
                        Pkt->Irp->IoStatus.Status));
        }
        else if (SRB_STATUS(Pkt->Srb->SrbStatus) == SRB_STATUS_SUCCESS){
            /*
             *  Make sure SRB and IRP status match.
             */
            if (!NT_SUCCESS(Pkt->Irp->IoStatus.Status)){
                TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_RW, "SRB and IRP status don't match in packet %ph: (op=%s srbstat=%s(%xh), irpstat=%xh)",
                            Pkt,
                            DBGGETSCSIOPSTR(Pkt->Srb),
                            DBGGETSRBSTATUSSTR(Pkt->Srb),
                            (ULONG)Pkt->Srb->SrbStatus,
                            Pkt->Irp->IoStatus.Status));
            }

            if (Pkt->Irp->IoStatus.Information != SrbGetDataTransferLength(Pkt->Srb)){
                TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_RW, "SRB and IRP result transfer lengths don't match in succeeded packet %ph: (op=%s, SrbStatus=%s, Srb.DataTransferLength=%xh, Irp->IoStatus.Information=%Ixh).",
                            Pkt,
                            DBGGETSCSIOPSTR(Pkt->Srb),
                            DBGGETSRBSTATUSSTR(Pkt->Srb),
                            SrbGetDataTransferLength(Pkt->Srb),
                            Pkt->Irp->IoStatus.Information));
            }
        }
        else {
            if (NT_SUCCESS(Pkt->Irp->IoStatus.Status)){
                TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_RW, "SRB and IRP status don't match in packet %ph: (op=%s srbstat=%s(%xh), irpstat=%xh)",
                            Pkt,
                            DBGGETSCSIOPSTR(Pkt->Srb),
                            DBGGETSRBSTATUSSTR(Pkt->Srb),
                            (ULONG)Pkt->Srb->SrbStatus,
                            Pkt->Irp->IoStatus.Status));
            }
            TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_RW, "Packet %ph failed (op=%s srbstat=%s(%xh), irpstat=%xh, sense=%s/%s/%s)",
                            Pkt,
                            DBGGETSCSIOPSTR(Pkt->Srb),
                            DBGGETSRBSTATUSSTR(Pkt->Srb),
                            (ULONG)Pkt->Srb->SrbStatus,
                            Pkt->Irp->IoStatus.Status,
                            DBGGETSENSECODESTR(Pkt->Srb),
                            DBGGETADSENSECODESTR(Pkt->Srb),
                            DBGGETADSENSEQUALIFIERSTR(Pkt->Srb)));

            /*
             *  If the SRB failed with underrun or overrun, then the actual
             *  transferred length should be returned in both SRB and IRP.
             *  (SRB's only have an error status for overrun, so it's overloaded).
             */
            if ((SRB_STATUS(Pkt->Srb->SrbStatus) == SRB_STATUS_DATA_OVERRUN) &&
               (Pkt->Irp->IoStatus.Information != SrbGetDataTransferLength(Pkt->Srb))){
                TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_RW, "SRB and IRP result transfer lengths don't match in failed packet %ph: (op=%s, SrbStatus=%s, Srb.DataTransferLength=%xh, Irp->IoStatus.Information=%Ixh).",
                            Pkt,
                            DBGGETSCSIOPSTR(Pkt->Srb),
                            DBGGETSRBSTATUSSTR(Pkt->Srb),
                            SrbGetDataTransferLength(Pkt->Srb),
                            Pkt->Irp->IoStatus.Information));
            }
        }

        /*
         *  If the port driver returned STATUS_INSUFFICIENT_RESOURCES,
         *  make sure this is also the InternalStatus in the SRB so that we process it correctly.
         */
        if (Pkt->Irp->IoStatus.Status == STATUS_INSUFFICIENT_RESOURCES){
            NT_ASSERT(SRB_STATUS(Pkt->Srb->SrbStatus) == SRB_STATUS_INTERNAL_ERROR);
            NT_ASSERT(SrbGetSystemStatus(Pkt->Srb) == STATUS_INSUFFICIENT_RESOURCES);
        }

        /*
         *  Some miniport drivers have been caught changing the SCSI operation
         *  code in the SRB.  This is absolutely disallowed as it breaks our error handling.
         */
        switch (pCdb->CDB10.OperationCode){
            case SCSIOP_MEDIUM_REMOVAL:
            case SCSIOP_MODE_SENSE:
            case SCSIOP_READ_CAPACITY:
            case SCSIOP_READ:
            case SCSIOP_WRITE:
            case SCSIOP_START_STOP_UNIT:
            case SCSIOP_READ_CAPACITY16:
            case SCSIOP_READ16:
            case SCSIOP_WRITE16:
                break;
            default:
                TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_RW, "Miniport illegally changed Srb.Cdb.OperationCode in packet %ph failed (op=%s srbstat=%s(%xh), irpstat=%xh, sense=%s/%s/%s)",
                                Pkt,
                                DBGGETSCSIOPSTR(Pkt->Srb),
                                DBGGETSRBSTATUSSTR(Pkt->Srb),
                                (ULONG)Pkt->Srb->SrbStatus,
                                Pkt->Irp->IoStatus.Status,
                                DBGGETSENSECODESTR(Pkt->Srb),
                                DBGGETADSENSECODESTR(Pkt->Srb),
                                DBGGETADSENSEQUALIFIERSTR(Pkt->Srb)));
                break;
        }

    }


    VOID DbgLogSendPacket(TRANSFER_PACKET *Pkt)
    {
        PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Pkt->Fdo->DeviceExtension;
        PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
        KIRQL oldIrql;

        if (Pkt->OriginalIrp){
            Pkt->DbgOriginalIrpCopy = *Pkt->OriginalIrp;
            if (Pkt->OriginalIrp->MdlAddress){
                Pkt->DbgMdlCopy = *Pkt->OriginalIrp->MdlAddress;
            }
        }

        KeQueryTickCount(&Pkt->DbgTimeSent);
        Pkt->DbgTimeReturned.QuadPart = 0L;

        KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
        fdoData->DbgPacketLogs[fdoData->DbgPacketLogNextIndex] = *Pkt;
        fdoData->DbgPacketLogNextIndex++;
        fdoData->DbgPacketLogNextIndex %= DBG_NUM_PACKET_LOG_ENTRIES;
        KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
    }

    VOID DbgLogReturnPacket(TRANSFER_PACKET *Pkt)
    {
        PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Pkt->Fdo->DeviceExtension;
        PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
        KIRQL oldIrql;

        KeQueryTickCount(&Pkt->DbgTimeReturned);

        #if 0
            // ISSUE: there are some problems with this check (e.g. multiproc), so don't include it yet
            if (Pkt->OriginalIrp){
                /*
                 *  No one should have touched the original irp while the packet was outstanding,
                 *  except for a couple fields that we ourselves update during the transfer
                 *  or that are allowed to change;
                 *  make those couple fields the same and then to a bytewise compare
                 */
                ULONG lenSame;

                Pkt->DbgOriginalIrpCopy.IoStatus.Status = Pkt->OriginalIrp->IoStatus.Status;
                Pkt->DbgOriginalIrpCopy.IoStatus.Information = Pkt->OriginalIrp->IoStatus.Information;
                Pkt->DbgOriginalIrpCopy.Tail.Overlay.DriverContext[0] = Pkt->OriginalIrp->Tail.Overlay.DriverContext[0];
                Pkt->DbgOriginalIrpCopy.ThreadListEntry = Pkt->OriginalIrp->ThreadListEntry;
                Pkt->DbgOriginalIrpCopy.Cancel = Pkt->OriginalIrp->Cancel;

                lenSame = (ULONG)RtlCompareMemory(Pkt->OriginalIrp, &Pkt->DbgOriginalIrpCopy, sizeof(IRP));
                NT_ASSERT(lenSame == sizeof(IRP));
            }
        #endif

        KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
        fdoData->DbgPacketLogs[fdoData->DbgPacketLogNextIndex] = *Pkt;
        fdoData->DbgPacketLogNextIndex++;
        fdoData->DbgPacketLogNextIndex %= DBG_NUM_PACKET_LOG_ENTRIES;
        KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
    }


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

    DbgSafeInc()

    Routine Description:

        Safely increments a ULONG. If the increment would result in an overflow,
        the value is unchanged.

    Arguments:

        A pointer to the value to be incremented.

    --*/
    static VOID DbgSafeInc(PULONG pValue)
    {
        ULONG incrementResult;
        if(NT_SUCCESS(RtlULongAdd(*pValue, 1, &incrementResult))) {
            *pValue = incrementResult;
        } else {
            //
            // Leave *pValue unchanged (i.e. at ULONG_MAX).
            //
        }
    }

    VOID DbgLogFlushInfo(PCLASS_PRIVATE_FDO_DATA FdoData, BOOLEAN IsIO, BOOLEAN IsFUA, BOOLEAN IsFlush)
    {

        /*
         *  Reset all FUA/Flush logging fields.
         */
        if (FdoData->DbgInitFlushLogging){
            FdoData->DbgNumIORequests = 0;
            FdoData->DbgNumFUAs = 0;
            FdoData->DbgNumFlushes = 0;
            FdoData->DbgIOsSinceFUA = 0;
            FdoData->DbgIOsSinceFlush = 0;
            FdoData->DbgAveIOsToFUA = 0;
            FdoData->DbgAveIOsToFlush = 0;
            FdoData->DbgMaxIOsToFUA = 0;
            FdoData->DbgMaxIOsToFlush = 0;
            FdoData->DbgMinIOsToFUA = 0xffffffff;
            FdoData->DbgMinIOsToFlush = 0xffffffff;
            FdoData->DbgInitFlushLogging = FALSE;
        }

        //
        // Using DbgSafeInc for all increments (instead of ++) guarantees
        // that there will be no overflow hence no division by 0. All counters
        // are capped at ULONG_MAX.
        //

        if (IsIO){
            DbgSafeInc(&FdoData->DbgNumIORequests);
            DbgSafeInc(&FdoData->DbgIOsSinceFlush);
            if (IsFUA){
                if (FdoData->DbgNumFUAs > 0){
                    FdoData->DbgMinIOsToFUA = min(FdoData->DbgMinIOsToFUA, FdoData->DbgIOsSinceFUA);
                }
                DbgSafeInc(&FdoData->DbgNumFUAs);
                FdoData->DbgAveIOsToFUA =  FdoData->DbgNumIORequests/FdoData->DbgNumFUAs;
                FdoData->DbgIOsSinceFUA = 0;
            }
            else {
                DbgSafeInc(&FdoData->DbgIOsSinceFUA);
                FdoData->DbgMaxIOsToFUA = max(FdoData->DbgMaxIOsToFUA, FdoData->DbgIOsSinceFUA);
            }
            FdoData->DbgMaxIOsToFlush = max(FdoData->DbgMaxIOsToFlush, FdoData->DbgIOsSinceFlush);
        }
        else if (IsFlush){
            if (FdoData->DbgNumFlushes > 0){
                FdoData->DbgMinIOsToFlush = min(FdoData->DbgMinIOsToFlush, FdoData->DbgIOsSinceFlush);
            }
            DbgSafeInc(&FdoData->DbgNumFlushes);
            FdoData->DbgAveIOsToFlush =  FdoData->DbgNumIORequests/FdoData->DbgNumFlushes;
            FdoData->DbgIOsSinceFlush = 0;
        }

    }


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

    SnapDiskStartup()

    Routine Description:

        This function will attempt to record the caller responsible for spinning
        up the disk.

    Arguments:

        NONE.

    Return Value:

        NONE.

    --*/
    VOID
    SnapDiskStartup(
        VOID
    )
    {
        ULONG       Index;
        PDISK_SPINUP_TRACES Entry;
        LARGE_INTEGER   SpinUpTime;

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:4210) // nonstandard extension used : function given file scope
#endif
        extern NTSYSAPI USHORT NTAPI RtlCaptureStackBackTrace(
           _In_         ULONG FramesToSkip,
           _In_         ULONG FramesToCapture,
           _Out_writes_to_(FramesToCapture, return) PVOID * BackTrace,
           _Out_opt_    PULONG BackTraceHash );
#ifdef _MSC_VER
#pragma warning(pop)
#endif

        //
        // Grab the current count, then mod it so that it
        // becomes an index into the DiskSpinupTraces array.
        //
        Index = InterlockedIncrement( (volatile LONG *)&DiskSpinupIndex );
        Index = Index & (NUMBER_OF_DISK_SPINUP_TRACES - 1);
        Entry = &DiskSpinupTraces[Index];

        //
        // Timestamp the instance.
        //
        KeQueryTickCount(&SpinUpTime);
        SpinUpTime.QuadPart = (SpinUpTime.QuadPart * KeQueryTimeIncrement())/(10000000);


        //
        // Ask the kernel to read back up our stack by
        // DISK_SPINUP_BACKTRACE_LENGTH frames.
        //
        Entry->TimeStamp.QuadPart = SpinUpTime.QuadPart;
        RtlZeroMemory( &Entry->StackTrace[0], DISK_SPINUP_BACKTRACE_LENGTH * sizeof(PVOID) );
        RtlCaptureStackBackTrace( 5,                           // stacks to skip
                                  DISK_SPINUP_BACKTRACE_LENGTH, // buffer size
                                  Entry->StackTrace,
                                  &Index );
    }

#else

    // We have to keep this in the retail build for legacy.
    VOID ClassDebugPrint(_In_ CLASS_DEBUG_LEVEL DebugPrintLevel, _In_z_ PCCHAR DebugMessage, ...)
    {
        UNREFERENCED_PARAMETER(DebugPrintLevel);
        UNREFERENCED_PARAMETER(DebugMessage);
    }

#endif

    char *DbgGetIoctlStr(ULONG ioctl)
    {
        char *ioctlStr = "?";

        switch (ioctl){

            #undef MAKE_CASE
            #define MAKE_CASE(ioctlCode) case ioctlCode: ioctlStr = #ioctlCode; break;

            MAKE_CASE(IOCTL_STORAGE_CHECK_VERIFY)
            MAKE_CASE(IOCTL_STORAGE_CHECK_VERIFY2)
            MAKE_CASE(IOCTL_STORAGE_MEDIA_REMOVAL)
            MAKE_CASE(IOCTL_STORAGE_EJECT_MEDIA)
            MAKE_CASE(IOCTL_STORAGE_LOAD_MEDIA)
            MAKE_CASE(IOCTL_STORAGE_LOAD_MEDIA2)
            MAKE_CASE(IOCTL_STORAGE_RESERVE)
            MAKE_CASE(IOCTL_STORAGE_RELEASE)
            MAKE_CASE(IOCTL_STORAGE_PERSISTENT_RESERVE_IN)
            MAKE_CASE(IOCTL_STORAGE_PERSISTENT_RESERVE_OUT)
            MAKE_CASE(IOCTL_STORAGE_FIND_NEW_DEVICES)
            MAKE_CASE(IOCTL_STORAGE_EJECTION_CONTROL)
            MAKE_CASE(IOCTL_STORAGE_MCN_CONTROL)
            MAKE_CASE(IOCTL_STORAGE_GET_MEDIA_TYPES)
            MAKE_CASE(IOCTL_STORAGE_GET_MEDIA_TYPES_EX)
            MAKE_CASE(IOCTL_STORAGE_GET_MEDIA_SERIAL_NUMBER)
            MAKE_CASE(IOCTL_STORAGE_GET_HOTPLUG_INFO)
            MAKE_CASE(IOCTL_STORAGE_RESET_BUS)
            MAKE_CASE(IOCTL_STORAGE_RESET_DEVICE)
            MAKE_CASE(IOCTL_STORAGE_GET_DEVICE_NUMBER)
            MAKE_CASE(IOCTL_STORAGE_PREDICT_FAILURE)
            MAKE_CASE(IOCTL_STORAGE_QUERY_PROPERTY)
            MAKE_CASE(OBSOLETE_IOCTL_STORAGE_RESET_BUS)
            MAKE_CASE(OBSOLETE_IOCTL_STORAGE_RESET_DEVICE)
        }

        return ioctlStr;
    }

    char *DbgGetScsiOpStr(PSTORAGE_REQUEST_BLOCK_HEADER Srb)
    {
        PCDB pCdb = SrbGetCdb(Srb);
        char *scsiOpStr = "?";

        if (pCdb) {

            switch (pCdb->CDB6GENERIC.OperationCode){

                #undef MAKE_CASE
                #define MAKE_CASE(scsiOpCode) case scsiOpCode: scsiOpStr = #scsiOpCode; break;

                MAKE_CASE(SCSIOP_TEST_UNIT_READY)
                MAKE_CASE(SCSIOP_REWIND)    // aka SCSIOP_REZERO_UNIT
                MAKE_CASE(SCSIOP_REQUEST_BLOCK_ADDR)
                MAKE_CASE(SCSIOP_REQUEST_SENSE)
                MAKE_CASE(SCSIOP_FORMAT_UNIT)
                MAKE_CASE(SCSIOP_READ_BLOCK_LIMITS)
                MAKE_CASE(SCSIOP_INIT_ELEMENT_STATUS)   // aka SCSIOP_REASSIGN_BLOCKS
                MAKE_CASE(SCSIOP_RECEIVE)       // aka SCSIOP_READ6
                MAKE_CASE(SCSIOP_SEND)  // aka SCSIOP_WRITE6, SCSIOP_PRINT
                MAKE_CASE(SCSIOP_SLEW_PRINT)    // aka SCSIOP_SEEK6, SCSIOP_TRACK_SELECT
                MAKE_CASE(SCSIOP_SEEK_BLOCK)
                MAKE_CASE(SCSIOP_PARTITION)
                MAKE_CASE(SCSIOP_READ_REVERSE)
                MAKE_CASE(SCSIOP_FLUSH_BUFFER)      // aka SCSIOP_WRITE_FILEMARKS
                MAKE_CASE(SCSIOP_SPACE)
                MAKE_CASE(SCSIOP_INQUIRY)
                MAKE_CASE(SCSIOP_VERIFY6)
                MAKE_CASE(SCSIOP_RECOVER_BUF_DATA)
                MAKE_CASE(SCSIOP_MODE_SELECT)
                MAKE_CASE(SCSIOP_RESERVE_UNIT)
                MAKE_CASE(SCSIOP_RELEASE_UNIT)
                MAKE_CASE(SCSIOP_COPY)
                MAKE_CASE(SCSIOP_ERASE)
                MAKE_CASE(SCSIOP_MODE_SENSE)
                MAKE_CASE(SCSIOP_START_STOP_UNIT)   // aka SCSIOP_STOP_PRINT, SCSIOP_LOAD_UNLOAD
                MAKE_CASE(SCSIOP_RECEIVE_DIAGNOSTIC)
                MAKE_CASE(SCSIOP_SEND_DIAGNOSTIC)
                MAKE_CASE(SCSIOP_MEDIUM_REMOVAL)
                MAKE_CASE(SCSIOP_READ_FORMATTED_CAPACITY)
                MAKE_CASE(SCSIOP_READ_CAPACITY)
                MAKE_CASE(SCSIOP_READ)
                MAKE_CASE(SCSIOP_WRITE)
                MAKE_CASE(SCSIOP_SEEK)  // aka SCSIOP_LOCATE, SCSIOP_POSITION_TO_ELEMENT
                MAKE_CASE(SCSIOP_WRITE_VERIFY)
                MAKE_CASE(SCSIOP_VERIFY)
                MAKE_CASE(SCSIOP_SEARCH_DATA_HIGH)
                MAKE_CASE(SCSIOP_SEARCH_DATA_EQUAL)
                MAKE_CASE(SCSIOP_SEARCH_DATA_LOW)
                MAKE_CASE(SCSIOP_SET_LIMITS)
                MAKE_CASE(SCSIOP_READ_POSITION)
                MAKE_CASE(SCSIOP_SYNCHRONIZE_CACHE)
                MAKE_CASE(SCSIOP_COMPARE)
                MAKE_CASE(SCSIOP_COPY_COMPARE)
                MAKE_CASE(SCSIOP_WRITE_DATA_BUFF)
                MAKE_CASE(SCSIOP_READ_DATA_BUFF)
                MAKE_CASE(SCSIOP_CHANGE_DEFINITION)
                MAKE_CASE(SCSIOP_READ_SUB_CHANNEL)
                MAKE_CASE(SCSIOP_READ_TOC)
                MAKE_CASE(SCSIOP_READ_HEADER)
                MAKE_CASE(SCSIOP_PLAY_AUDIO)
                MAKE_CASE(SCSIOP_GET_CONFIGURATION)
                MAKE_CASE(SCSIOP_PLAY_AUDIO_MSF)
                MAKE_CASE(SCSIOP_PLAY_TRACK_INDEX)
                MAKE_CASE(SCSIOP_PLAY_TRACK_RELATIVE)
                MAKE_CASE(SCSIOP_GET_EVENT_STATUS)
                MAKE_CASE(SCSIOP_PAUSE_RESUME)
                MAKE_CASE(SCSIOP_LOG_SELECT)
                MAKE_CASE(SCSIOP_LOG_SENSE)
                MAKE_CASE(SCSIOP_STOP_PLAY_SCAN)
                MAKE_CASE(SCSIOP_READ_DISK_INFORMATION)
                MAKE_CASE(SCSIOP_READ_TRACK_INFORMATION)
                MAKE_CASE(SCSIOP_RESERVE_TRACK_RZONE)
                MAKE_CASE(SCSIOP_SEND_OPC_INFORMATION)
                MAKE_CASE(SCSIOP_MODE_SELECT10)
                MAKE_CASE(SCSIOP_MODE_SENSE10)
                MAKE_CASE(SCSIOP_CLOSE_TRACK_SESSION)
                MAKE_CASE(SCSIOP_READ_BUFFER_CAPACITY)
                MAKE_CASE(SCSIOP_SEND_CUE_SHEET)
                MAKE_CASE(SCSIOP_PERSISTENT_RESERVE_IN)
                MAKE_CASE(SCSIOP_PERSISTENT_RESERVE_OUT)
                MAKE_CASE(SCSIOP_REPORT_LUNS)
                MAKE_CASE(SCSIOP_BLANK)
                MAKE_CASE(SCSIOP_SEND_KEY)
                MAKE_CASE(SCSIOP_REPORT_KEY)
                MAKE_CASE(SCSIOP_MOVE_MEDIUM)
                MAKE_CASE(SCSIOP_LOAD_UNLOAD_SLOT)  // aka SCSIOP_EXCHANGE_MEDIUM
                MAKE_CASE(SCSIOP_SET_READ_AHEAD)
                MAKE_CASE(SCSIOP_READ_DVD_STRUCTURE)
                MAKE_CASE(SCSIOP_REQUEST_VOL_ELEMENT)
                MAKE_CASE(SCSIOP_SEND_VOLUME_TAG)
                MAKE_CASE(SCSIOP_READ_ELEMENT_STATUS)
                MAKE_CASE(SCSIOP_READ_CD_MSF)
                MAKE_CASE(SCSIOP_SCAN_CD)
                MAKE_CASE(SCSIOP_SET_CD_SPEED)
                MAKE_CASE(SCSIOP_PLAY_CD)
                MAKE_CASE(SCSIOP_MECHANISM_STATUS)
                MAKE_CASE(SCSIOP_READ_CD)
                MAKE_CASE(SCSIOP_SEND_DVD_STRUCTURE)
                MAKE_CASE(SCSIOP_INIT_ELEMENT_RANGE)
                MAKE_CASE(SCSIOP_READ16)
                MAKE_CASE(SCSIOP_WRITE16)
                MAKE_CASE(SCSIOP_VERIFY16)
                MAKE_CASE(SCSIOP_SYNCHRONIZE_CACHE16)
                MAKE_CASE(SCSIOP_READ_CAPACITY16)
            }
        }

        return scsiOpStr;
    }


    char *DbgGetSrbStatusStr(PSTORAGE_REQUEST_BLOCK_HEADER Srb)
    {
        char *srbStatStr = "?";

        switch (Srb->SrbStatus){

            #undef MAKE_CASE
            #define MAKE_CASE(srbStat) \
                        case srbStat: \
                            srbStatStr = #srbStat; \
                            break; \
                        case srbStat|SRB_STATUS_QUEUE_FROZEN: \
                            srbStatStr = #srbStat "|SRB_STATUS_QUEUE_FROZEN"; \
                            break; \
                        case srbStat|SRB_STATUS_AUTOSENSE_VALID: \
                            srbStatStr = #srbStat "|SRB_STATUS_AUTOSENSE_VALID"; \
                            break; \
                        case srbStat|SRB_STATUS_QUEUE_FROZEN|SRB_STATUS_AUTOSENSE_VALID: \
                            srbStatStr = #srbStat "|SRB_STATUS_QUEUE_FROZEN|SRB_STATUS_AUTOSENSE_VALID"; \
                            break;

            MAKE_CASE(SRB_STATUS_PENDING)
            MAKE_CASE(SRB_STATUS_SUCCESS)
            MAKE_CASE(SRB_STATUS_ABORTED)
            MAKE_CASE(SRB_STATUS_ABORT_FAILED)
            MAKE_CASE(SRB_STATUS_ERROR)
            MAKE_CASE(SRB_STATUS_BUSY)
            MAKE_CASE(SRB_STATUS_INVALID_REQUEST)
            MAKE_CASE(SRB_STATUS_INVALID_PATH_ID)
            MAKE_CASE(SRB_STATUS_NO_DEVICE)
            MAKE_CASE(SRB_STATUS_TIMEOUT)
            MAKE_CASE(SRB_STATUS_SELECTION_TIMEOUT)
            MAKE_CASE(SRB_STATUS_COMMAND_TIMEOUT)
            MAKE_CASE(SRB_STATUS_MESSAGE_REJECTED)
            MAKE_CASE(SRB_STATUS_BUS_RESET)
            MAKE_CASE(SRB_STATUS_PARITY_ERROR)
            MAKE_CASE(SRB_STATUS_REQUEST_SENSE_FAILED)
            MAKE_CASE(SRB_STATUS_NO_HBA)
            MAKE_CASE(SRB_STATUS_DATA_OVERRUN)
            MAKE_CASE(SRB_STATUS_UNEXPECTED_BUS_FREE)
            MAKE_CASE(SRB_STATUS_PHASE_SEQUENCE_FAILURE)
            MAKE_CASE(SRB_STATUS_BAD_SRB_BLOCK_LENGTH)
            MAKE_CASE(SRB_STATUS_REQUEST_FLUSHED)
            MAKE_CASE(SRB_STATUS_INVALID_LUN)
            MAKE_CASE(SRB_STATUS_INVALID_TARGET_ID)
            MAKE_CASE(SRB_STATUS_BAD_FUNCTION)
            MAKE_CASE(SRB_STATUS_ERROR_RECOVERY)
            MAKE_CASE(SRB_STATUS_NOT_POWERED)
            MAKE_CASE(SRB_STATUS_INTERNAL_ERROR)
        }

        return srbStatStr;
    }


    char *DbgGetSenseCodeStr(PSTORAGE_REQUEST_BLOCK_HEADER Srb)
    {
        char *senseCodeStr = "?";

        if (Srb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID){

            PVOID senseData;
            UCHAR senseCode;
            BOOLEAN validSense;

            senseData = SrbGetSenseInfoBuffer(Srb);
            NT_ASSERT(senseData);

            validSense = ScsiGetSenseKeyAndCodes(senseData,
                                                 SrbGetSenseInfoBufferLength(Srb),
                                                 SCSI_SENSE_OPTIONS_FIXED_FORMAT_IF_UNKNOWN_FORMAT_INDICATED,
                                                 &senseCode,
                                                 NULL,
                                                 NULL);
            if (validSense) {
                switch (senseCode){

                    #undef MAKE_CASE
                    #define MAKE_CASE(snsCod) case snsCod: senseCodeStr = #snsCod; break;

                    MAKE_CASE(SCSI_SENSE_NO_SENSE)
                    MAKE_CASE(SCSI_SENSE_RECOVERED_ERROR)
                    MAKE_CASE(SCSI_SENSE_NOT_READY)
                    MAKE_CASE(SCSI_SENSE_MEDIUM_ERROR)
                    MAKE_CASE(SCSI_SENSE_HARDWARE_ERROR)
                    MAKE_CASE(SCSI_SENSE_ILLEGAL_REQUEST)
                    MAKE_CASE(SCSI_SENSE_UNIT_ATTENTION)
                    MAKE_CASE(SCSI_SENSE_DATA_PROTECT)
                    MAKE_CASE(SCSI_SENSE_BLANK_CHECK)
                    MAKE_CASE(SCSI_SENSE_UNIQUE)
                    MAKE_CASE(SCSI_SENSE_COPY_ABORTED)
                    MAKE_CASE(SCSI_SENSE_ABORTED_COMMAND)
                    MAKE_CASE(SCSI_SENSE_EQUAL)
                    MAKE_CASE(SCSI_SENSE_VOL_OVERFLOW)
                    MAKE_CASE(SCSI_SENSE_MISCOMPARE)
                    MAKE_CASE(SCSI_SENSE_RESERVED)
                }
            }
        }

        return senseCodeStr;
    }


    char *DbgGetAdditionalSenseCodeStr(PSTORAGE_REQUEST_BLOCK_HEADER Srb)
    {
        char *adSenseCodeStr = "?";

        if (Srb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID){
            PVOID senseData;
            UCHAR adSenseCode;
            BOOLEAN validSense;

            senseData = SrbGetSenseInfoBuffer(Srb);
            NT_ASSERT(senseData);

            validSense = ScsiGetSenseKeyAndCodes(senseData,
                                                 SrbGetSenseInfoBufferLength(Srb),
                                                 SCSI_SENSE_OPTIONS_FIXED_FORMAT_IF_UNKNOWN_FORMAT_INDICATED,
                                                 NULL,
                                                 &adSenseCode,
                                                 NULL);

            if (validSense) {
                switch (adSenseCode){

                    #undef MAKE_CASE
                    #define MAKE_CASE(adSnsCod) case adSnsCod: adSenseCodeStr = #adSnsCod; break;

                    MAKE_CASE(SCSI_ADSENSE_NO_SENSE)
                    MAKE_CASE(SCSI_ADSENSE_LUN_NOT_READY)
                    MAKE_CASE(SCSI_ADSENSE_TRACK_ERROR)
                    MAKE_CASE(SCSI_ADSENSE_SEEK_ERROR)
                    MAKE_CASE(SCSI_ADSENSE_REC_DATA_NOECC)
                    MAKE_CASE(SCSI_ADSENSE_REC_DATA_ECC)
                    MAKE_CASE(SCSI_ADSENSE_ILLEGAL_COMMAND)
                    MAKE_CASE(SCSI_ADSENSE_ILLEGAL_BLOCK)
                    MAKE_CASE(SCSI_ADSENSE_INVALID_CDB)
                    MAKE_CASE(SCSI_ADSENSE_INVALID_LUN)
                    MAKE_CASE(SCSI_ADSENSE_WRITE_PROTECT)   // aka SCSI_ADWRITE_PROTECT
                    MAKE_CASE(SCSI_ADSENSE_MEDIUM_CHANGED)
                    MAKE_CASE(SCSI_ADSENSE_BUS_RESET)
                    MAKE_CASE(SCSI_ADSENSE_INVALID_MEDIA)
                    MAKE_CASE(SCSI_ADSENSE_NO_MEDIA_IN_DEVICE)
                    MAKE_CASE(SCSI_ADSENSE_POSITION_ERROR)
                    MAKE_CASE(SCSI_ADSENSE_OPERATOR_REQUEST)
                    MAKE_CASE(SCSI_ADSENSE_FAILURE_PREDICTION_THRESHOLD_EXCEEDED)
                    MAKE_CASE(SCSI_ADSENSE_COPY_PROTECTION_FAILURE)
                    MAKE_CASE(SCSI_ADSENSE_VENDOR_UNIQUE)
                    MAKE_CASE(SCSI_ADSENSE_MUSIC_AREA)
                    MAKE_CASE(SCSI_ADSENSE_DATA_AREA)
                    MAKE_CASE(SCSI_ADSENSE_VOLUME_OVERFLOW)
                }
            }
        }

        return adSenseCodeStr;
    }


    char *DbgGetAdditionalSenseCodeQualifierStr(PSTORAGE_REQUEST_BLOCK_HEADER Srb)
    {
        char *adSenseCodeQualStr = "?";

        if (Srb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID){
            PVOID senseData;
            UCHAR adSenseCode;
            UCHAR adSenseCodeQual;
            BOOLEAN validSense;

            senseData = SrbGetSenseInfoBuffer(Srb);
            NT_ASSERT(senseData);

            validSense = ScsiGetSenseKeyAndCodes(senseData,
                                                 SrbGetSenseInfoBufferLength(Srb),
                                                 SCSI_SENSE_OPTIONS_FIXED_FORMAT_IF_UNKNOWN_FORMAT_INDICATED,
                                                 NULL,
                                                 &adSenseCode,
                                                 &adSenseCodeQual);
            if (validSense) {
                switch (adSenseCode){

                    #undef MAKE_CASE
                    #define MAKE_CASE(adSnsCodQual) case adSnsCodQual: adSenseCodeQualStr = #adSnsCodQual; break;

                    case SCSI_ADSENSE_LUN_NOT_READY:
                        switch (adSenseCodeQual){
                            MAKE_CASE(SCSI_SENSEQ_CAUSE_NOT_REPORTABLE)
                            MAKE_CASE(SCSI_SENSEQ_BECOMING_READY)
                            MAKE_CASE(SCSI_SENSEQ_INIT_COMMAND_REQUIRED)
                            MAKE_CASE(SCSI_SENSEQ_MANUAL_INTERVENTION_REQUIRED)
                            MAKE_CASE(SCSI_SENSEQ_FORMAT_IN_PROGRESS)
                            MAKE_CASE(SCSI_SENSEQ_REBUILD_IN_PROGRESS)
                            MAKE_CASE(SCSI_SENSEQ_RECALCULATION_IN_PROGRESS)
                            MAKE_CASE(SCSI_SENSEQ_OPERATION_IN_PROGRESS)
                            MAKE_CASE(SCSI_SENSEQ_LONG_WRITE_IN_PROGRESS)
                        }
                        break;
                    case SCSI_ADSENSE_NO_SENSE:
                        switch (adSenseCodeQual){
                            MAKE_CASE(SCSI_SENSEQ_FILEMARK_DETECTED)
                            MAKE_CASE(SCSI_SENSEQ_END_OF_MEDIA_DETECTED)
                            MAKE_CASE(SCSI_SENSEQ_SETMARK_DETECTED)
                            MAKE_CASE(SCSI_SENSEQ_BEGINNING_OF_MEDIA_DETECTED)
                        }
                        break;
                    case SCSI_ADSENSE_ILLEGAL_BLOCK:
                        switch (adSenseCodeQual){
                            MAKE_CASE(SCSI_SENSEQ_ILLEGAL_ELEMENT_ADDR)
                        }
                        break;
                    case SCSI_ADSENSE_POSITION_ERROR:
                        switch (adSenseCodeQual){
                            MAKE_CASE(SCSI_SENSEQ_DESTINATION_FULL)
                            MAKE_CASE(SCSI_SENSEQ_SOURCE_EMPTY)
                        }
                        break;
                    case SCSI_ADSENSE_INVALID_MEDIA:
                        switch (adSenseCodeQual){
                            MAKE_CASE(SCSI_SENSEQ_INCOMPATIBLE_MEDIA_INSTALLED)
                            MAKE_CASE(SCSI_SENSEQ_UNKNOWN_FORMAT)
                            MAKE_CASE(SCSI_SENSEQ_INCOMPATIBLE_FORMAT)
                            MAKE_CASE(SCSI_SENSEQ_CLEANING_CARTRIDGE_INSTALLED)
                        }
                        break;
                    case SCSI_ADSENSE_OPERATOR_REQUEST:
                        switch (adSenseCodeQual){
                            MAKE_CASE(SCSI_SENSEQ_STATE_CHANGE_INPUT)
                            MAKE_CASE(SCSI_SENSEQ_MEDIUM_REMOVAL)
                            MAKE_CASE(SCSI_SENSEQ_WRITE_PROTECT_ENABLE)
                            MAKE_CASE(SCSI_SENSEQ_WRITE_PROTECT_DISABLE)
                        }
                        break;
                    case SCSI_ADSENSE_COPY_PROTECTION_FAILURE:
                        switch (adSenseCodeQual){
                            MAKE_CASE(SCSI_SENSEQ_AUTHENTICATION_FAILURE)
                            MAKE_CASE(SCSI_SENSEQ_KEY_NOT_PRESENT)
                            MAKE_CASE(SCSI_SENSEQ_KEY_NOT_ESTABLISHED)
                            MAKE_CASE(SCSI_SENSEQ_READ_OF_SCRAMBLED_SECTOR_WITHOUT_AUTHENTICATION)
                            MAKE_CASE(SCSI_SENSEQ_MEDIA_CODE_MISMATCHED_TO_LOGICAL_UNIT)
                            MAKE_CASE(SCSI_SENSEQ_LOGICAL_UNIT_RESET_COUNT_ERROR)
                        }
                        break;
                }
            }
        }

        return adSenseCodeQualStr;
    }