Sample Code

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

/*--

Copyright (C) Microsoft Corporation. All rights reserved.

Module Name:

    scratch.c

Abstract:

    Functions for using common scratch buffer 

Environment:

    kernel mode only

Notes:


Revision History:

--*/

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

#include "ntddk.h"
#include "ntddstor.h"
#include "cdrom.h"
#include "ioctl.h"
#include "scratch.h"
#include "mmc.h"

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

// Forward declarations
EVT_WDF_REQUEST_COMPLETION_ROUTINE  ScratchBuffer_ReadWriteCompletionRoutine;

#ifdef ALLOC_PRAGMA

#pragma alloc_text(PAGE, ScratchBuffer_Deallocate)
#pragma alloc_text(PAGE, ScratchBuffer_Allocate)
#pragma alloc_text(PAGE, ScratchBuffer_SetupSrb)
#pragma alloc_text(PAGE, ScratchBuffer_ExecuteCdbEx)

#endif

_IRQL_requires_max_(APC_LEVEL)
VOID
ScratchBuffer_Deallocate(
    _Inout_ PCDROM_DEVICE_EXTENSION DeviceExtension
    )
/*++

Routine Description:

    release all resources allocated for scratch.

Arguments:

    DeviceExtension - device extension

Return Value:

    none

--*/
{
    PAGED_CODE ();

    NT_ASSERT(DeviceExtension->ScratchContext.ScratchInUse == 0);

    if (DeviceExtension->ScratchContext.ScratchHistory != NULL)
    {
        ExFreePool(DeviceExtension->ScratchContext.ScratchHistory);
        DeviceExtension->ScratchContext.ScratchHistory = NULL;
    }
    if (DeviceExtension->ScratchContext.ScratchSense != NULL)
    {
        ExFreePool(DeviceExtension->ScratchContext.ScratchSense);
        DeviceExtension->ScratchContext.ScratchSense = NULL;
    }
    if (DeviceExtension->ScratchContext.ScratchSrb != NULL)
    {
        ExFreePool(DeviceExtension->ScratchContext.ScratchSrb);
        DeviceExtension->ScratchContext.ScratchSrb = NULL;
    }
    if (DeviceExtension->ScratchContext.ScratchBufferSize != 0)
    {
        DeviceExtension->ScratchContext.ScratchBufferSize = 0;
    }
    if (DeviceExtension->ScratchContext.ScratchBufferMdl != NULL)
    {
        IoFreeMdl(DeviceExtension->ScratchContext.ScratchBufferMdl);
        DeviceExtension->ScratchContext.ScratchBufferMdl = NULL;
    }
    if (DeviceExtension->ScratchContext.ScratchBuffer != NULL)
    {
        ExFreePool(DeviceExtension->ScratchContext.ScratchBuffer);
        DeviceExtension->ScratchContext.ScratchBuffer = NULL;
    }

    if (DeviceExtension->ScratchContext.PartialMdl != NULL)
    {
        IoFreeMdl(DeviceExtension->ScratchContext.PartialMdl);
        DeviceExtension->ScratchContext.PartialMdl = NULL;
    }

    if (DeviceExtension->ScratchContext.ScratchRequest != NULL)
    {
        PIRP irp = WdfRequestWdmGetIrp(DeviceExtension->ScratchContext.ScratchRequest);
        if (irp->MdlAddress)
        {
            irp->MdlAddress = NULL;
        }
        WdfObjectDelete(DeviceExtension->ScratchContext.ScratchRequest);
        DeviceExtension->ScratchContext.ScratchRequest = NULL;
    }

    return;
}

_IRQL_requires_max_(APC_LEVEL)
BOOLEAN
ScratchBuffer_Allocate(
    _Inout_ PCDROM_DEVICE_EXTENSION DeviceExtension
    )
/*++

Routine Description:

    allocate resources allocated for scratch.

Arguments:

    DeviceExtension - device extension

Return Value:

    none

--*/
{
    NTSTATUS status = STATUS_SUCCESS;

    PAGED_CODE ();

    NT_ASSERT(DeviceExtension->ScratchContext.ScratchInUse == 0);

    // quick-exit if already allocated
    if ((DeviceExtension->ScratchContext.ScratchBuffer     != NULL) &&
        (DeviceExtension->ScratchContext.ScratchBufferMdl  != NULL) &&
        (DeviceExtension->ScratchContext.ScratchBufferSize != 0)    &&
        (DeviceExtension->ScratchContext.ScratchRequest    != NULL) &&
        (DeviceExtension->ScratchContext.ScratchSrb        != NULL) &&
        (DeviceExtension->ScratchContext.ScratchHistory    != NULL) &&
        (DeviceExtension->ScratchContext.PartialMdl  != NULL)
        )
    {
        return TRUE;
    }

    // validate max transfer already determined
    NT_ASSERT(DeviceExtension->DeviceAdditionalData.MaxPageAlignedTransferBytes != 0);

    // validate no partially-saved state
    NT_ASSERT(DeviceExtension->ScratchContext.ScratchBuffer     == NULL);
    NT_ASSERT(DeviceExtension->ScratchContext.ScratchBufferMdl  == NULL);
    NT_ASSERT(DeviceExtension->ScratchContext.ScratchBufferSize == 0);
    NT_ASSERT(DeviceExtension->ScratchContext.ScratchRequest    == NULL);
    NT_ASSERT(DeviceExtension->ScratchContext.PartialMdl == NULL);

    // limit the scratch buffer to between 4k and 64k (so data length fits into USHORT -- req'd for many commands)
    DeviceExtension->ScratchContext.ScratchBufferSize = min(DeviceExtension->DeviceAdditionalData.MaxPageAlignedTransferBytes, (64*1024));

    // allocate the buffer
    if (NT_SUCCESS(status))
    {
        DeviceExtension->ScratchContext.ScratchBuffer = ExAllocatePoolWithTag(NonPagedPoolNx,
                                                                              DeviceExtension->ScratchContext.ScratchBufferSize,
                                                                              CDROM_TAG_SCRATCH);
        if (DeviceExtension->ScratchContext.ScratchBuffer == NULL)
        {
            status = STATUS_INSUFFICIENT_RESOURCES;
            TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT,
                        "Failed to allocate scratch buffer of %x bytes\n",
                        DeviceExtension->ScratchContext.ScratchBufferSize
                        ));
        }
        else if (BYTE_OFFSET(DeviceExtension->ScratchContext.ScratchBuffer) != 0)
        {
            status = STATUS_INTERNAL_ERROR;
            TracePrint((TRACE_LEVEL_FATAL, TRACE_FLAG_INIT,
                        "Allocation of %x bytes non-paged pool was not "
                        "allocated on page boundary?  STATUS_INTERNAL_ERROR\n",
                        DeviceExtension->ScratchContext.ScratchBufferSize
                        ));
        }
    }

    // allocate the MDL
    if (NT_SUCCESS(status))
    {
        DeviceExtension->ScratchContext.ScratchBufferMdl = IoAllocateMdl(DeviceExtension->ScratchContext.ScratchBuffer,
                                                                         DeviceExtension->ScratchContext.ScratchBufferSize,
                                                                         FALSE, FALSE, NULL);
        if (DeviceExtension->ScratchContext.ScratchBufferMdl == NULL)
        {
            status = STATUS_INSUFFICIENT_RESOURCES;
            TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT,
                        "Failed to allocate MDL for %x byte buffer\n",
                        DeviceExtension->ScratchContext.ScratchBufferSize
                        ));
        }
        else
        {
            MmBuildMdlForNonPagedPool(DeviceExtension->ScratchContext.ScratchBufferMdl);
        }
    }

    // create the request
    if (NT_SUCCESS(status))
    {
        WDF_OBJECT_ATTRIBUTES attributes;
        WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, 
                                                CDROM_REQUEST_CONTEXT);

        status =  WdfRequestCreate(&attributes,
                                   DeviceExtension->IoTarget,
                                   &DeviceExtension->ScratchContext.ScratchRequest);

        if ((!NT_SUCCESS(status)) ||
            (DeviceExtension->ScratchContext.ScratchRequest == NULL))
        {
            status = STATUS_INSUFFICIENT_RESOURCES;
            TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT,
                        "Failed to allocate scratch MDL \n"));
        }
    }

    // allocate the srb
    if (NT_SUCCESS(status))
    {
        DeviceExtension->ScratchContext.ScratchSrb = ExAllocatePoolWithTag(NonPagedPoolNx,
                                                                           sizeof(SCSI_REQUEST_BLOCK),
                                                                           CDROM_TAG_SCRATCH);

        if (DeviceExtension->ScratchContext.ScratchSrb == NULL)
        {
            status = STATUS_INSUFFICIENT_RESOURCES;
            TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT,
                        "Failed to allocate scratch SRB\n"));
        }
    }

    // allocate the sense buffer
    if (NT_SUCCESS(status))
    {
        DeviceExtension->ScratchContext.ScratchSense = ExAllocatePoolWithTag(NonPagedPoolNx,
                                                                             sizeof(SENSE_DATA),
                                                                             CDROM_TAG_SCRATCH);

        if (DeviceExtension->ScratchContext.ScratchSense == NULL)
        {
            status = STATUS_INSUFFICIENT_RESOURCES;
            TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT,
                        "Failed to allocate scratch sense data\n"
                        ));
        }
    }

    // allocate the SRB history data
    if (NT_SUCCESS(status))
    {
        size_t allocationSize = sizeof(SRB_HISTORY) - sizeof(SRB_HISTORY_ITEM);
        allocationSize += 20 * sizeof(SRB_HISTORY_ITEM);

        DeviceExtension->ScratchContext.ScratchHistory = ExAllocatePoolWithTag(NonPagedPoolNx,
                                                                               allocationSize,
                                                                               CDROM_TAG_SCRATCH);
        if (DeviceExtension->ScratchContext.ScratchHistory == NULL)
        {
            status = STATUS_INSUFFICIENT_RESOURCES;
            TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT,
                        "Failed to allocate scratch history buffer\n"
                        ));
        }
        else
        {
            // must be initialized here...
            RtlZeroMemory(DeviceExtension->ScratchContext.ScratchHistory, allocationSize);
            DeviceExtension->ScratchContext.ScratchHistory->TotalHistoryCount = 20;
        }
    }

    // allocate the MDL
    if (NT_SUCCESS(status))
    {
        ULONG transferLength = 0;

        status = RtlULongAdd(DeviceExtension->DeviceAdditionalData.MaxPageAlignedTransferBytes, PAGE_SIZE, &transferLength);
        if (NT_SUCCESS(status))
        {
            DeviceExtension->ScratchContext.PartialMdlIsBuilt = FALSE;
            DeviceExtension->ScratchContext.PartialMdl = IoAllocateMdl(NULL,
                                                                       transferLength,
                                                                       FALSE,
                                                                       FALSE,
                                                                       NULL);
            if (DeviceExtension->ScratchContext.PartialMdl == NULL)
            {
                status = STATUS_INSUFFICIENT_RESOURCES;
                TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT,
                            "Failed to allocate MDL for %x byte buffer\n",
                            DeviceExtension->ScratchContext.ScratchBufferSize
                            ));
            }
            else 
            {
                NT_ASSERT(DeviceExtension->ScratchContext.PartialMdl->Size >= 
                       (CSHORT)(sizeof(MDL) + BYTES_TO_PAGES(DeviceExtension->DeviceAdditionalData.MaxPageAlignedTransferBytes) * sizeof(PFN_NUMBER)));
            }
        }
        else
        {
            status = STATUS_INTEGER_OVERFLOW;
        }
    }

    // cleanup on failure
    if (!NT_SUCCESS(status))
    {
        ScratchBuffer_Deallocate(DeviceExtension);
    }

    return NT_SUCCESS(status);
}


VOID
ScratchBuffer_ResetItems(
    _Inout_ PCDROM_DEVICE_EXTENSION DeviceExtension,
    _In_ BOOLEAN                    ResetRequestHistory
    )
/*++

Routine Description:

    reset scratch items for reuse.

Arguments:

    DeviceExtension - device extension
    ResetRequestHistory - reset history fields or not

Return Value:

    none

--*/
{
    NTSTATUS                 status = STATUS_SUCCESS;
    WDF_REQUEST_REUSE_PARAMS reuseParams;
    PIRP                     irp = NULL;

    NT_ASSERT(DeviceExtension->ScratchContext.ScratchHistory    != NULL);
    NT_ASSERT(DeviceExtension->ScratchContext.ScratchSense      != NULL);
    NT_ASSERT(DeviceExtension->ScratchContext.ScratchSrb        != NULL);
    NT_ASSERT(DeviceExtension->ScratchContext.ScratchRequest    != NULL);
    NT_ASSERT(DeviceExtension->ScratchContext.ScratchBufferSize != 0);
    NT_ASSERT(DeviceExtension->ScratchContext.ScratchBuffer     != NULL);
    NT_ASSERT(DeviceExtension->ScratchContext.ScratchBufferMdl  != NULL);
    NT_ASSERT(DeviceExtension->ScratchContext.ScratchInUse      != 0);

    irp = WdfRequestWdmGetIrp(DeviceExtension->ScratchContext.ScratchRequest);

    if (ResetRequestHistory)
    {
        PSRB_HISTORY history = DeviceExtension->ScratchContext.ScratchHistory;
        RtlZeroMemory(history->History, sizeof(SRB_HISTORY_ITEM) * history->TotalHistoryCount);
        history->ClassDriverUse[0] = 0;
        history->ClassDriverUse[1] = 0;
        history->ClassDriverUse[2] = 0;
        history->ClassDriverUse[3] = 0;
        history->UsedHistoryCount  = 0;
    }

    // re-use the KMDF request object

    // deassign the MdlAddress, this is the value we assign explicitly.
    // this is to prevent WdfRequestReuse to release the Mdl unexpectly.
    if (irp->MdlAddress)
    {
        irp->MdlAddress = NULL;
    }

    WDF_REQUEST_REUSE_PARAMS_INIT(&reuseParams, WDF_REQUEST_REUSE_NO_FLAGS, STATUS_NOT_SUPPORTED);
    status = WdfRequestReuse(DeviceExtension->ScratchContext.ScratchRequest, &reuseParams);
    // WDF request to format the request befor sending it
    if (NT_SUCCESS(status))
    {
        // clean up completion routine.
        WdfRequestSetCompletionRoutine(DeviceExtension->ScratchContext.ScratchRequest, NULL, NULL);

        status = WdfIoTargetFormatRequestForInternalIoctlOthers(DeviceExtension->IoTarget, 
                                                                DeviceExtension->ScratchContext.ScratchRequest,
                                                                IOCTL_SCSI_EXECUTE_IN,
                                                                NULL, NULL,
                                                                NULL, NULL,
                                                                NULL, NULL);
        if (!NT_SUCCESS(status))
        {
            TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,  
                       "ScratchBuffer_ResetItems: WdfIoTargetFormatRequestForInternalIoctlOthers failed, %!STATUS!\n",
                       status));
        }
    }

    RtlZeroMemory(DeviceExtension->ScratchContext.ScratchSense, sizeof(SENSE_DATA));
    RtlZeroMemory(DeviceExtension->ScratchContext.ScratchSrb, sizeof(SCSI_REQUEST_BLOCK));
    
    return;
}


NTSTATUS
ScratchBuffer_PerformNextReadWrite(
    _In_ PCDROM_DEVICE_EXTENSION  DeviceExtension,
    _In_ BOOLEAN                  FirstTry
    )
/*++

Routine Description:

    This function asynchronously sends the next read/write SRB down the stack.

Arguments:

    DeviceExtension - Device extension

Return Value:

    none  

--*/
{
    PCDROM_SCRATCH_READ_WRITE_CONTEXT   readWriteContext = &DeviceExtension->ScratchContext.ScratchReadWriteContext;
    PCDROM_REQUEST_CONTEXT              requestContext = RequestGetContext(DeviceExtension->ScratchContext.ScratchRequest);
    WDFREQUEST                          originalRequest = requestContext->OriginalRequest;
    NTSTATUS                            status = STATUS_SUCCESS;

    ULONG       transferSize;
    BOOLEAN     usePartialMdl;

    transferSize = min((readWriteContext->EntireXferLen - readWriteContext->TransferedBytes), readWriteContext->MaxLength);

    if (FirstTry)
    {
        DeviceExtension->ScratchContext.NumRetries = 0;
    }

    ScratchBuffer_ResetItems(DeviceExtension, FALSE);

    usePartialMdl = (readWriteContext->PacketsCount > 1 || readWriteContext->TransferedBytes > 0);

    ScratchBuffer_SetupReadWriteSrb(DeviceExtension,
                                    originalRequest,
                                    readWriteContext->StartingOffset,
                                    transferSize,
                                    readWriteContext->DataBuffer,
                                    readWriteContext->IsRead,
                                    usePartialMdl
                                    );

    WdfRequestSetCompletionRoutine(DeviceExtension->ScratchContext.ScratchRequest,
            ScratchBuffer_ReadWriteCompletionRoutine, DeviceExtension);

    status = ScratchBuffer_SendSrb(DeviceExtension, FALSE, (FirstTry ? &readWriteContext->SrbHistoryItem : NULL));

    return status;
}


VOID
ScratchBuffer_ReadWriteTimerRoutine(
    struct _KDPC *Dpc,
    PVOID         DeferredContext,
    PVOID         SystemArgument1,
    PVOID         SystemArgument2
    )
/*++

Routine Description:

    Timer routine for retrying read and write requests.

Arguments:

    Timer - WDF timer

Return Value:

    none  

--*/
{
    PCDROM_DEVICE_EXTENSION             deviceExtension = NULL;
    PCDROM_SCRATCH_READ_WRITE_CONTEXT   readWriteContext = NULL;
    WDFREQUEST                          originalRequest = NULL;
    PCDROM_REQUEST_CONTEXT              requestContext = NULL;
    NTSTATUS                            status = STATUS_SUCCESS;
    KIRQL                               oldIrql;

    UNREFERENCED_PARAMETER(Dpc);
    UNREFERENCED_PARAMETER(SystemArgument1);
    UNREFERENCED_PARAMETER(SystemArgument2);  

    if (DeferredContext == NULL)
    {
        // This is impossible, but definition of KDEFERRED_ROUTINE allows optional argument,
        // and thus OACR will complain.

        return;
    }

    originalRequest = (WDFREQUEST) DeferredContext;
    requestContext = RequestGetContext(originalRequest);

    KeAcquireSpinLock(&requestContext->ReadWriteCancelSpinLock, &oldIrql);

    if (!requestContext->ReadWriteIsCompleted)
    {
        // As the first step, unregister the cancellation routine
        status = WdfRequestUnmarkCancelable(originalRequest);
    }
    else
    {
        status = STATUS_CANCELLED;
    }

    KeReleaseSpinLock(&requestContext->ReadWriteCancelSpinLock, oldIrql);

    if (status != STATUS_CANCELLED)
    {
        deviceExtension = requestContext->DeviceExtension;
        readWriteContext = &deviceExtension->ScratchContext.ScratchReadWriteContext;

        // We use timer only for retries, that's why the second parameter is always FALSE
        status = ScratchBuffer_PerformNextReadWrite(deviceExtension, FALSE);

        if (!NT_SUCCESS(status))
        {
            ScratchBuffer_EndUse(deviceExtension);
            RequestCompletion(deviceExtension, originalRequest, status, readWriteContext->TransferedBytes);
        }
    }

    //
    // Drop the extra reference
    //
    WdfObjectDereference(originalRequest);
}


EVT_WDF_REQUEST_CANCEL  ScratchBuffer_ReadWriteEvtRequestCancel;

VOID 
ScratchBuffer_ReadWriteEvtRequestCancel(
    _In_ WDFREQUEST  Request
    )
/*++

Routine Description:

    Cancels a request waiting for the read/write timer to expire. This function does not
    support cancellation of requests that have already been sent down.

Arguments:

    Request - WDF request

Return Value:

    none  

--*/
{
    PCDROM_REQUEST_CONTEXT             requestContext = RequestGetContext(Request);
    PCDROM_DEVICE_EXTENSION            deviceExtension = requestContext->DeviceExtension;
    PCDROM_SCRATCH_READ_WRITE_CONTEXT  readWriteContext = &deviceExtension->ScratchContext.ScratchReadWriteContext;
    KIRQL                              oldIrql;

    KeAcquireSpinLock(&requestContext->ReadWriteCancelSpinLock, &oldIrql);

    if (KeCancelTimer(&requestContext->ReadWriteTimer))
    {
       //
       // Timer is canceled, we own the request.  Drop the reference we took before
       // queueing the timer.
       //
       WdfObjectDereference(Request);
    }
    else
    {
        //
        // Timer will run and drop the reference but it won't complete the request
        // because we set IsCompleted to TRUE
        //
    }

    requestContext->ReadWriteIsCompleted = TRUE;

    KeReleaseSpinLock(&requestContext->ReadWriteCancelSpinLock, oldIrql);

    ScratchBuffer_EndUse(deviceExtension);

    // If WdfTimerStop returned TRUE, it means this request was scheduled for a retry
    // and the retry has not happened yet. We just need to cancel it and release the scratch buffer.
    RequestCompletion(deviceExtension, Request, STATUS_CANCELLED, readWriteContext->TransferedBytes);
}

VOID
ScratchBuffer_ReadWriteCompletionRoutine(
    _In_ WDFREQUEST  Request,
    _In_ WDFIOTARGET  Target,
    _In_ PWDF_REQUEST_COMPLETION_PARAMS  Params,
    _In_ WDFCONTEXT  Context
    )
/*++

Routine Description:

    Read/write request completion routine.

Arguments:
    Request - WDF request
    Target - The IO target the request was completed by.
    Params - the request completion parameters
    Context - context

Return Value:

    none  

--*/
{
    PCDROM_DEVICE_EXTENSION             deviceExtension = (PCDROM_DEVICE_EXTENSION) Context;
    PCDROM_SCRATCH_READ_WRITE_CONTEXT   readWriteContext = &deviceExtension->ScratchContext.ScratchReadWriteContext;
    NTSTATUS                            status = STATUS_SUCCESS;
    PCDROM_REQUEST_CONTEXT              requestContext = RequestGetContext(deviceExtension->ScratchContext.ScratchRequest);
    WDFREQUEST                          originalRequest = requestContext->OriginalRequest;

    if (!NT_SUCCESS(WdfRequestGetStatus(Request)))
    {
        TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT,
                   "WdfRequestSend: %lx\n",
                    WdfRequestGetStatus(Request)
                    ));
    }

    UNREFERENCED_PARAMETER(Params);
    UNREFERENCED_PARAMETER(Target);

    // We are not calling ScratchBuffer_BeginUse / ScratchBuffer_EndUse in this function, because we already own
    // the scratch buffer if this function is being called.

    if ((deviceExtension->ScratchContext.ScratchSrb->SrbStatus == SRB_STATUS_ABORTED) &&
        (deviceExtension->ScratchContext.ScratchSrb->InternalStatus == STATUS_CANCELLED))
    {
        // The request has been cancelled, just need to complete it
    }
    else if (SRB_STATUS(deviceExtension->ScratchContext.ScratchSrb->SrbStatus) != SRB_STATUS_SUCCESS)
    {
        // The SCSI command that we sent down has failed, retry it if necessary
        BOOLEAN shouldRetry = TRUE;
        LONGLONG retryIn100nsUnits = 0;

        shouldRetry = RequestSenseInfoInterpretForScratchBuffer(deviceExtension,
                                                                deviceExtension->ScratchContext.NumRetries,
                                                                &status,
                                                                &retryIn100nsUnits);

        if (shouldRetry)
        {
            deviceExtension->ScratchContext.NumRetries++;

            if (retryIn100nsUnits == 0)
            {
                // We take a shortcut here by calling ScratchBuffer_PerformNextReadWrite directly:
                // this helps to avoid unnecessary context switch.
                status = ScratchBuffer_PerformNextReadWrite(deviceExtension, FALSE);

                if (NT_SUCCESS(status))
                {
                    // We're not done with the request yet, no need to complete it now
                    return;
                }
            }
            else
            {
                PCDROM_REQUEST_CONTEXT originalRequestContext = RequestGetContext(originalRequest);
                KIRQL                  oldIrql;

                //
                // Initialize the spin lock and timer local to the original request.
                //
                if (!originalRequestContext->ReadWriteRetryInitialized)
                {
                    KeInitializeSpinLock(&originalRequestContext->ReadWriteCancelSpinLock);
                    KeInitializeTimer(&originalRequestContext->ReadWriteTimer);
                    KeInitializeDpc(&originalRequestContext->ReadWriteDpc, ScratchBuffer_ReadWriteTimerRoutine, originalRequest);
                    originalRequestContext->ReadWriteRetryInitialized = TRUE;
                }

                KeAcquireSpinLock(&requestContext->ReadWriteCancelSpinLock, &oldIrql);

                status = WdfRequestMarkCancelableEx(originalRequest, ScratchBuffer_ReadWriteEvtRequestCancel);

                if (status == STATUS_CANCELLED)
                {
                    requestContext->ReadWriteIsCompleted = TRUE;

                    KeReleaseSpinLock(&requestContext->ReadWriteCancelSpinLock, oldIrql);
                }
                else
                {
                    LARGE_INTEGER t;

                    t.QuadPart = -retryIn100nsUnits;

                    WdfObjectReference(originalRequest);

                    // Use negative time to indicate that we want a relative delay
                    KeSetTimer(&originalRequestContext->ReadWriteTimer,
                               t,
                               &originalRequestContext->ReadWriteDpc
                               );

                    KeReleaseSpinLock(&requestContext->ReadWriteCancelSpinLock, oldIrql);

                    return;
                }
            }
        }
    }
    else
    {
        // The SCSI command has succeeded
        readWriteContext->DataBuffer += deviceExtension->ScratchContext.ScratchSrb->DataTransferLength;
        readWriteContext->StartingOffset.QuadPart += deviceExtension->ScratchContext.ScratchSrb->DataTransferLength;
        readWriteContext->TransferedBytes += deviceExtension->ScratchContext.ScratchSrb->DataTransferLength;
        readWriteContext->PacketsCount--;

        // Update the SRB history item
        if (readWriteContext->SrbHistoryItem)
        {
            ULONG senseSize;

            // Query the tick count and store in the history
            KeQueryTickCount(&readWriteContext->SrbHistoryItem->TickCountCompleted);
        
            // Copy the SRB Status...
            readWriteContext->SrbHistoryItem->SrbStatus = deviceExtension->ScratchContext.ScratchSrb->SrbStatus;
        
            // Determine the amount of valid sense data
            if (deviceExtension->ScratchContext.ScratchSrb->SenseInfoBufferLength >=
                    RTL_SIZEOF_THROUGH_FIELD(SENSE_DATA, AdditionalSenseLength))
            {
                PSENSE_DATA sense = (PSENSE_DATA)deviceExtension->ScratchContext.ScratchSrb->SenseInfoBuffer;
                senseSize = RTL_SIZEOF_THROUGH_FIELD(SENSE_DATA, AdditionalSenseLength) +
                            sense->AdditionalSenseLength;
                senseSize = min(senseSize, sizeof(SENSE_DATA));
            }
            else
            {
                senseSize = deviceExtension->ScratchContext.ScratchSrb->SenseInfoBufferLength;
            }

            // Normalize the sense data copy in the history
            RtlZeroMemory(&(readWriteContext->SrbHistoryItem->NormalizedSenseData), sizeof(SENSE_DATA));
            RtlCopyMemory(&(readWriteContext->SrbHistoryItem->NormalizedSenseData),
                    deviceExtension->ScratchContext.ScratchSrb->SenseInfoBuffer, senseSize);
        }

        // Check whether we need to send more SCSI commands to complete the request
        if (readWriteContext->PacketsCount > 0)
        {
            status = ScratchBuffer_PerformNextReadWrite(deviceExtension, TRUE);

            if (NT_SUCCESS(status))
            {
                // We're not done with the request yet, no need to complete it now
                return;
            }
        }
    }

    ScratchBuffer_EndUse(deviceExtension);


    RequestCompletion(deviceExtension, originalRequest, status, readWriteContext->TransferedBytes);
}

_IRQL_requires_max_(APC_LEVEL)
VOID
ScratchBuffer_SetupSrb(
    _Inout_ PCDROM_DEVICE_EXTENSION DeviceExtension,
    _In_opt_ WDFREQUEST             OriginalRequest,
    _In_ ULONG                      MaximumTransferLength,
    _In_ BOOLEAN                    GetDataFromDevice
    )
/*++

Routine Description:

    setup scratch SRB for sending out.

Arguments:

    DeviceExtension - device extension
    OriginalRequest - original request delivered by WDF
    MaximumTransferLength - transfer length
    GetDataFromDevice - TRUE (get data from device); FALSE (send data to device)

Return Value:

    none

--*/
{
    WDFREQUEST              request = DeviceExtension->ScratchContext.ScratchRequest;
    PIRP                    irp = WdfRequestWdmGetIrp(request);
    PSCSI_REQUEST_BLOCK     srb = DeviceExtension->ScratchContext.ScratchSrb;
    PIO_STACK_LOCATION      irpStack = NULL;
    PCDROM_REQUEST_CONTEXT  requestContext = RequestGetContext(request);

    PAGED_CODE ();

    requestContext->OriginalRequest = OriginalRequest;

    // set to use the full scratch buffer via the scratch SRB
    irpStack = IoGetNextIrpStackLocation(irp);
    irpStack->MajorFunction = IRP_MJ_SCSI;
    if (MaximumTransferLength == 0)
    {
        irpStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_SCSI_EXECUTE_NONE;
    }
    else if (GetDataFromDevice)
    {
        irpStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_SCSI_EXECUTE_IN;
    }
    else
    {
        irpStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_SCSI_EXECUTE_OUT;
    }
    irpStack->Parameters.Scsi.Srb = srb;

    if (MaximumTransferLength > 0)
    {
        // the Irp must show the MDL's address for the transfer
        irp->MdlAddress = DeviceExtension->ScratchContext.ScratchBufferMdl;

        srb->DataBuffer = DeviceExtension->ScratchContext.ScratchBuffer;
    }

    // prepare the SRB with default values
    srb->Length = SCSI_REQUEST_BLOCK_SIZE;
    srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
    srb->QueueAction = SRB_SIMPLE_TAG_REQUEST;
    srb->SrbStatus = 0;
    srb->ScsiStatus = 0;
    srb->NextSrb = NULL;
    srb->OriginalRequest = irp;
    srb->SenseInfoBufferLength = SENSE_BUFFER_SIZE;
    srb->SenseInfoBuffer = DeviceExtension->ScratchContext.ScratchSense;

    srb->CdbLength = 16; // to cause failures if not set correctly -- CD devices limited to 12 bytes for now...

    srb->DataTransferLength = min(DeviceExtension->ScratchContext.ScratchBufferSize, MaximumTransferLength);
    srb->TimeOutValue = DeviceExtension->TimeOutValue;
    srb->SrbFlags = DeviceExtension->SrbFlags;
    SET_FLAG(srb->SrbFlags, SRB_FLAGS_DISABLE_SYNCH_TRANSFER);
    SET_FLAG(srb->SrbFlags, SRB_FLAGS_NO_QUEUE_FREEZE);

    if (MaximumTransferLength == 0)
    {
        SET_FLAG(srb->SrbFlags, SRB_FLAGS_NO_DATA_TRANSFER);
    }
    else if (GetDataFromDevice)
    {
        SET_FLAG(srb->SrbFlags, SRB_FLAGS_DATA_IN);
    }
    else
    {
        SET_FLAG(srb->SrbFlags, SRB_FLAGS_DATA_OUT);
    }
}


NTSTATUS
ScratchBuffer_SendSrb(
    _Inout_     PCDROM_DEVICE_EXTENSION DeviceExtension,
    _In_        BOOLEAN                 SynchronousSrb,
    _When_(SynchronousSrb, _Pre_null_)
    _When_(!SynchronousSrb, _In_opt_)
                PSRB_HISTORY_ITEM       *SrbHistoryItem
    )
/*++

Routine Description:

    Send the command from the scratch SRB to lower driver and retry if necessary.

Arguments:

    DeviceExtension - device extension
    SynchronousSrb - indicates whether the SRB needs to be sent synchronously or nor
    SrbHistoryItem - storage for SRB history item, if this is an asynchronous request

Return Value:

    none

--*/
{
    NTSTATUS                 status  = STATUS_SUCCESS;
    PSCSI_REQUEST_BLOCK      srb = DeviceExtension->ScratchContext.ScratchSrb;
    PSRB_HISTORY             history = DeviceExtension->ScratchContext.ScratchHistory;
    PSRB_HISTORY_ITEM        item = NULL;
    BOOLEAN                  requestCancelled = FALSE;

    srb->InternalStatus = 0;
    srb->SrbStatus = 0;

    // allocate/update history pre-command, if it is a synchronous request or we were supplied
    // with a storage for the history item
    if (SynchronousSrb || SrbHistoryItem != NULL)
    {
        // sending a packet implies a new history unit is to be used.
        NT_ASSERT( history->UsedHistoryCount <= history->TotalHistoryCount );
    
        // if already all used up, remove at least one history unit
        if (history->UsedHistoryCount == history->TotalHistoryCount )
        {
            CompressSrbHistoryData(history);
            NT_ASSERT( history->UsedHistoryCount < history->TotalHistoryCount );
        }
    
        // thus, since we are about to increment the count, it must now be less...
        NT_ASSERT( history->UsedHistoryCount < history->TotalHistoryCount );
    
        // increment the number of history units in use
        history->UsedHistoryCount++;
    
        // determine index to use
        item = &( history->History[ history->UsedHistoryCount-1 ] );

        if (SrbHistoryItem != NULL)
        {
            *SrbHistoryItem = item;
        }
    
        // zero out the history item
        RtlZeroMemory(item, sizeof(SRB_HISTORY_ITEM));
    
        // Query the tick count and store in the history
        KeQueryTickCount(&item->TickCountSent);
    }

    // get cancellation status;
    {
        PCDROM_REQUEST_CONTEXT  requestContext = RequestGetContext(DeviceExtension->ScratchContext.ScratchRequest);

        if (requestContext->OriginalRequest != NULL)
        {
            requestCancelled = WdfRequestIsCanceled(requestContext->OriginalRequest);
        }
    }

    if (!requestCancelled)
    {
        status = RequestSend(DeviceExtension,
                             DeviceExtension->ScratchContext.ScratchRequest,
                             DeviceExtension->IoTarget,
                             SynchronousSrb ? WDF_REQUEST_SEND_OPTION_SYNCHRONOUS : 0,
                             NULL);

        // If this is a synchronous request, update the history item immediately, including "normalized" sense data
        if (SynchronousSrb)
        {
            ULONG senseSize;

            // Query the tick count and store in the history
            KeQueryTickCount(&item->TickCountCompleted);
        
            // Copy the SRB Status
            item->SrbStatus = srb->SrbStatus;
        
            // Determine the amount of valid sense data
            if (srb->SenseInfoBufferLength >= RTL_SIZEOF_THROUGH_FIELD(SENSE_DATA, AdditionalSenseLength))
            {
                PSENSE_DATA sense = (PSENSE_DATA)srb->SenseInfoBuffer;
                senseSize = RTL_SIZEOF_THROUGH_FIELD(SENSE_DATA, AdditionalSenseLength) +
                            sense->AdditionalSenseLength;
                senseSize = min(senseSize, sizeof(SENSE_DATA));
            }
            else
            {
                senseSize = srb->SenseInfoBufferLength;
            }

            // Normalize the sense data copy in the history
            RtlZeroMemory(&(item->NormalizedSenseData), sizeof(SENSE_DATA));
            RtlCopyMemory(&(item->NormalizedSenseData), srb->SenseInfoBuffer, senseSize);
        }
    }
    else
    {
        DeviceExtension->ScratchContext.ScratchSrb->SrbStatus = SRB_STATUS_ABORTED;
        DeviceExtension->ScratchContext.ScratchSrb->InternalStatus = (ULONG)STATUS_CANCELLED;
        status = STATUS_CANCELLED;
    }

    return status;
}

VOID
CompressSrbHistoryData(
    _Inout_  PSRB_HISTORY   RequestHistory
    )
/*++

Routine Description:

    compress the SRB history data.

Arguments:

    RequestHistory - SRB history data

Return Value:

    RequestHistory - compressed history data

--*/
{
    ULONG i;
    NT_ASSERT( RequestHistory->UsedHistoryCount == RequestHistory->TotalHistoryCount );
    ValidateSrbHistoryDataPresumptions(RequestHistory);

    for (i=0; i < RequestHistory->UsedHistoryCount; i++)
    {
        // for each item...
        PSRB_HISTORY_ITEM toMatch = &( RequestHistory->History[i] );
        // hint: read const qualifiers backwards.  i.e. srbstatus is a const UCHAR
        // so, "UCHAR const * const x" is read "x is a const pointer to a const UCHAR"
        // unfortunately, "const UCHAR" is equivalent to "UCHAR const", which causes
        // people no end of confusion due to its widespread use.
        UCHAR const srbStatus = toMatch->SrbStatus;
        UCHAR const sense     = toMatch->NormalizedSenseData.SenseKey;
        UCHAR const asc       = toMatch->NormalizedSenseData.AdditionalSenseCode;
        UCHAR const ascq      = toMatch->NormalizedSenseData.AdditionalSenseCodeQualifier;
        ULONG j;

        // see if there are any at higher indices with identical Sense/ASC/ASCQ
        for (j = i+1; (toMatch->ClassDriverUse != 0xFF) && (j < RequestHistory->UsedHistoryCount); j++)
        {
            PSRB_HISTORY_ITEM found = &( RequestHistory->History[j] );
            // close enough match?
            if ((srbStatus == found->SrbStatus) &&
                (sense     == found->NormalizedSenseData.SenseKey) &&
                (asc       == found->NormalizedSenseData.AdditionalSenseCode) &&
                (ascq      == found->NormalizedSenseData.AdditionalSenseCodeQualifier)) {

                // add the fields to keep reasonable track of delay times.
                if (toMatch->MillisecondsDelayOnRetry + found->MillisecondsDelayOnRetry < toMatch->MillisecondsDelayOnRetry) {
                    toMatch->MillisecondsDelayOnRetry = MAXULONG;
                } else {
                    toMatch->MillisecondsDelayOnRetry += found->MillisecondsDelayOnRetry;
                }

                // this found item cannot contain any compressed entries because
                // the first entry with a given set of sense/asc/ascq will always
                // either be full (0xFF) or be the only partially-full entry with
                // that sense/asc/ascq.
                NT_ASSERT(found->ClassDriverUse == 0);
                // add the counts so we still know how many retries total
                toMatch->ClassDriverUse++;


                // if not the last entry, need to move later entries earlier in the array
                if (j != RequestHistory->UsedHistoryCount-1) {
                    // how many entries remain?
                    SIZE_T remainingBytes = RequestHistory->UsedHistoryCount - 1 - j;
                    remainingBytes *= sizeof(SRB_HISTORY_ITEM);

                    // note that MOVE is required due to overlapping entries
                    RtlMoveMemory(found, found+1, remainingBytes);

                    // Finally, decrement the number of used history count and
                    // decrement j to rescan the current location again
                    --RequestHistory->UsedHistoryCount;
                    --j;
                } // end moving of array elements around
            } // end of close enough match
        } // end j loop
    } // end i loop 

    // unable to compress duplicate sense/asc/ascq, so just lose the most recent data
    if (RequestHistory->UsedHistoryCount == RequestHistory->TotalHistoryCount)
    {
        PSRB_HISTORY_ITEM item = &( RequestHistory->History[ RequestHistory->TotalHistoryCount-1 ] );
        RequestHistory->ClassDriverUse[0] += item->ClassDriverUse; // how many did we "lose"?
        RequestHistory->UsedHistoryCount--;
    }

    // finally, zero any that are no longer in use
    NT_ASSERT( RequestHistory->UsedHistoryCount != RequestHistory->TotalHistoryCount);
    {
        SIZE_T bytesToZero = RequestHistory->TotalHistoryCount - RequestHistory->UsedHistoryCount;
        bytesToZero *= sizeof(SRB_HISTORY_ITEM);
        RtlZeroMemory(&(RequestHistory->History[RequestHistory->UsedHistoryCount]), bytesToZero);
    }

    ValidateSrbHistoryDataPresumptions(RequestHistory);
    return;
}

VOID
ValidateSrbHistoryDataPresumptions(
    _In_     SRB_HISTORY const * RequestHistory
    )
{
#if DBG
    // validate that all fully-compressed items are before any non-fully-compressed items of any particular sense/asc/ascq
    // validate that there is at most one partially-compressed item of any particular sense/asc/ascq
    // validate that all items of any particular sense/asc/ascq that are uncompressed are at the end
    // THUS: A(255) A(255) A( 40) A(  0) A(  0) is legal for all types with A as sense/asc/ascq
    //       A(0)   B(255) A(  0) B( 17) B(  0) is also legal because A/B are different types of error

    ULONG i;
    for (i = 0; i < RequestHistory->UsedHistoryCount; i++)
    {
        SRB_HISTORY_ITEM const * toMatch = &( RequestHistory->History[i] );
        UCHAR const srbStatus = toMatch->SrbStatus;
        UCHAR const sense     = toMatch->NormalizedSenseData.SenseKey;
        UCHAR const asc       = toMatch->NormalizedSenseData.AdditionalSenseCode;
        UCHAR const ascq      = toMatch->NormalizedSenseData.AdditionalSenseCodeQualifier;
        ULONG j;

        BOOLEAN foundPartiallyCompressedItem =
            (toMatch->ClassDriverUse !=    0) &&
            (toMatch->ClassDriverUse != 0xFF) ;
        BOOLEAN foundUncompressedItem =
            (toMatch->ClassDriverUse ==    0) ;

        for (j = i+1; j < RequestHistory->UsedHistoryCount; j++)
        {
            SRB_HISTORY_ITEM const * found = &( RequestHistory->History[j] );
            if ((srbStatus == found->SrbStatus) &&
                (sense     == found->NormalizedSenseData.SenseKey) &&
                (asc       == found->NormalizedSenseData.AdditionalSenseCode) &&
                (ascq      == found->NormalizedSenseData.AdditionalSenseCodeQualifier)
                )
            {
                // found a matching type, so validate ordering rules
                if (foundUncompressedItem && (found->ClassDriverUse != 0))
                {
                    DbgPrintEx(DPFLTR_CDROM_ID, DPFLTR_ERROR_LEVEL,
                               "History data has compressed history following uncompressed history "
                               "for srbstatus/sense/asc/ascq of %02x/%02x/%02x/%02x at indices %d (%08x) and %d (%08x)\n",
                               srbStatus, sense, asc, ascq,
                               i,i, j,j
                               );
                    NT_ASSERT(FALSE);
                }
                else if (foundPartiallyCompressedItem && (found->ClassDriverUse == 0xFF))
                {
                    DbgPrintEx(DPFLTR_CDROM_ID, DPFLTR_ERROR_LEVEL,
                               "History data has fully compressed history following partially compressed history "
                               "for srbstatus/sense/asc/ascq of %02x/%02x/%02x/%02x at indices %d (%08x) and %d (%08x)\n",
                               srbStatus, sense, asc, ascq,
                               i,i, j,j
                               );
                    NT_ASSERT(FALSE);
                }

                // update if we have now found partially compressed and/or uncompressed items
                if (found->ClassDriverUse == 0)
                {
                    foundUncompressedItem = TRUE;
                }
                else if (found->ClassDriverUse != 0xFF)
                {
                    foundPartiallyCompressedItem = TRUE;
                }
            } // end match of (toMatch,found)
        } // end loop j
    } // end loop i
#else
    UNREFERENCED_PARAMETER(RequestHistory);
#endif
    return;
}

VOID
ScratchBuffer_SetupReadWriteSrb(
    _Inout_ PCDROM_DEVICE_EXTENSION     DeviceExtension,
    _In_    WDFREQUEST                  OriginalRequest,
    _In_    LARGE_INTEGER               StartingOffset,
    _In_    ULONG                       RequiredLength,
    _Inout_updates_bytes_(RequiredLength) UCHAR* DataBuffer,
    _In_    BOOLEAN                     IsReadRequest,
    _In_    BOOLEAN                     UsePartialMdl
    )
/*++

Routine Description:

    setup SRB for read/write request.

Arguments:

    DeviceExtension - device extension
    OriginalRequest - read/write request
    StartingOffset - read/write starting offset
    DataBuffer - buffer for read/write
    IsReadRequest - TRUE (read); FALSE (write)

Return Value:

    none

--*/
{
    //NOTE: R/W request not use the ScratchBuffer, instead, it uses the buffer associated with IRP.

    PSCSI_REQUEST_BLOCK srb = DeviceExtension->ScratchContext.ScratchSrb;
    PCDB                cdb = (PCDB)srb->Cdb;
    LARGE_INTEGER       logicalBlockAddr;
    ULONG               numTransferBlocks;

    PIRP                originalIrp = WdfRequestWdmGetIrp(OriginalRequest);

    PIRP                irp = WdfRequestWdmGetIrp(DeviceExtension->ScratchContext.ScratchRequest);
    PIO_STACK_LOCATION  irpStack = NULL;

    PCDROM_REQUEST_CONTEXT  requestContext = RequestGetContext(DeviceExtension->ScratchContext.ScratchRequest);

    requestContext->OriginalRequest = OriginalRequest;


    logicalBlockAddr.QuadPart = Int64ShrlMod32(StartingOffset.QuadPart, DeviceExtension->SectorShift);
    numTransferBlocks = RequiredLength >> DeviceExtension->SectorShift;

    // set to use the full scratch buffer via the scratch SRB
    irpStack = IoGetNextIrpStackLocation(irp);
    irpStack->MajorFunction = IRP_MJ_SCSI;
    if (IsReadRequest)
    {
        irpStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_SCSI_EXECUTE_IN;
    }
    else
    {
        irpStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_SCSI_EXECUTE_OUT;
    }
    irpStack->Parameters.Scsi.Srb = srb;

    // prepare the SRB with default values
    srb->Length = SCSI_REQUEST_BLOCK_SIZE;
    srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
    srb->QueueAction = SRB_SIMPLE_TAG_REQUEST;
    srb->SrbStatus = 0;
    srb->ScsiStatus = 0;
    srb->NextSrb = NULL;
    srb->SenseInfoBufferLength = SENSE_BUFFER_SIZE;
    srb->SenseInfoBuffer = DeviceExtension->ScratchContext.ScratchSense;

    srb->DataBuffer = DataBuffer;
    srb->DataTransferLength = RequiredLength;

    srb->QueueSortKey = logicalBlockAddr.LowPart;
    if (logicalBlockAddr.QuadPart > 0xFFFFFFFF) 
    {
        //
        // If the requested LBA is more than max ULONG set the
        // QueueSortKey to the maximum value, so that these
        // requests can be added towards the end of the queue.
        //
        srb->QueueSortKey = 0xFFFFFFFF;
    }

    srb->OriginalRequest = irp;
    srb->TimeOutValue = DeviceExtension->TimeOutValue;

    if (RequestIsRealtimeStreaming(OriginalRequest, IsReadRequest) &&
        !TEST_FLAG(DeviceExtension->PrivateFdoData->HackFlags, FDO_HACK_NO_STREAMING))
    {
        if (IsReadRequest)
        {
            RtlZeroMemory(&cdb->READ12, sizeof(cdb->READ12));
            REVERSE_BYTES(&cdb->READ12.LogicalBlock, &logicalBlockAddr.LowPart);
            REVERSE_BYTES(&cdb->READ12.TransferLength, &numTransferBlocks);
            cdb->READ12.Streaming = 1;
            cdb->READ12.OperationCode = SCSIOP_READ12;
            srb->CdbLength = sizeof(cdb->READ12);
        }
        else
        {
            RtlZeroMemory(&cdb->WRITE12, sizeof(cdb->WRITE12));
            REVERSE_BYTES(&cdb->WRITE12.LogicalBlock, &logicalBlockAddr.LowPart);
            REVERSE_BYTES(&cdb->WRITE12.TransferLength, &numTransferBlocks);
            cdb->WRITE12.Streaming = 1;
            cdb->WRITE12.OperationCode = SCSIOP_WRITE12;
            srb->CdbLength = sizeof(cdb->WRITE12);
        }
    }
    else
    {
        RtlZeroMemory(&cdb->CDB10, sizeof(cdb->CDB10));
        cdb->CDB10.LogicalBlockByte0 = ((PFOUR_BYTE)&logicalBlockAddr.LowPart)->Byte3;
        cdb->CDB10.LogicalBlockByte1 = ((PFOUR_BYTE)&logicalBlockAddr.LowPart)->Byte2;
        cdb->CDB10.LogicalBlockByte2 = ((PFOUR_BYTE)&logicalBlockAddr.LowPart)->Byte1;
        cdb->CDB10.LogicalBlockByte3 = ((PFOUR_BYTE)&logicalBlockAddr.LowPart)->Byte0;
        cdb->CDB10.TransferBlocksMsb = ((PFOUR_BYTE)&numTransferBlocks)->Byte1;
        cdb->CDB10.TransferBlocksLsb = ((PFOUR_BYTE)&numTransferBlocks)->Byte0;
        cdb->CDB10.OperationCode = (IsReadRequest) ? SCSIOP_READ : SCSIOP_WRITE;
        srb->CdbLength = sizeof(cdb->CDB10);
    }

    //  Set SRB and IRP flags
    srb->SrbFlags = DeviceExtension->SrbFlags;
    if (TEST_FLAG(originalIrp->Flags, IRP_PAGING_IO) ||
        TEST_FLAG(originalIrp->Flags, IRP_SYNCHRONOUS_PAGING_IO))
    {
        SET_FLAG(srb->SrbFlags, SRB_CLASS_FLAGS_PAGING);
    }

    SET_FLAG(srb->SrbFlags, (IsReadRequest) ? SRB_FLAGS_DATA_IN : SRB_FLAGS_DATA_OUT);
    SET_FLAG(srb->SrbFlags, SRB_FLAGS_ADAPTER_CACHE_ENABLE);

    //
    // If the request is not split, we can use the original IRP MDL.  If the
    // request needs to be split, we need to use a partial MDL.  The partial MDL
    // is needed because more than one driver might be mapping the same MDL
    // and this causes problems.
    //
    if (UsePartialMdl == FALSE) 
    {
        irp->MdlAddress = originalIrp->MdlAddress;
    } 
    else 
    {
        if (DeviceExtension->ScratchContext.PartialMdlIsBuilt != FALSE)
        {
            MmPrepareMdlForReuse(DeviceExtension->ScratchContext.PartialMdl);
        }

        IoBuildPartialMdl(originalIrp->MdlAddress, DeviceExtension->ScratchContext.PartialMdl, srb->DataBuffer, srb->DataTransferLength);
        DeviceExtension->ScratchContext.PartialMdlIsBuilt = TRUE;
        irp->MdlAddress = DeviceExtension->ScratchContext.PartialMdl;
    }

    //DBGLOGSENDPACKET(Pkt);
    //HISTORYLOGSENDPACKET(Pkt);

    //
    // Set the original irp here for SFIO.
    //
    srb->SrbExtension = (PVOID)(originalIrp);

    return;
}

_IRQL_requires_max_(APC_LEVEL)
NTSTATUS
ScratchBuffer_ExecuteCdbEx(
    _Inout_ PCDROM_DEVICE_EXTENSION DeviceExtension,
    _In_opt_ WDFREQUEST             OriginalRequest,
    _In_ ULONG                      TransferSize,
    _In_ BOOLEAN                    GetDataFromDevice,
    _In_ PCDB                       Cdb,
    _In_ UCHAR                      OprationLength,
    _In_ ULONG                      TimeoutValue
    )
/*++

Routine Description:

    Use Scratch buffer to send the Cdb, check error and retry if necessary.

Arguments:

    DeviceExtension - device context
    OriginalRequest - original request that requires this CDB operation
    TransferSize - Data transfer size required
    GetFromDevice - TRUE if getting data from device.
    Cdb - SCSI command
    OprationLength - SCSI command length: 6, 10 or 12
    TimeoutValue - if > 0, use it as timeout value for command
                   if 0, use the default device timeout value

Return Value:

    NTSTATUS

--*/
{
    NTSTATUS            status = STATUS_SUCCESS;
    PSCSI_REQUEST_BLOCK srb = DeviceExtension->ScratchContext.ScratchSrb;
    PCDB                cdb = (PCDB)(srb->Cdb);

    BOOLEAN             shouldRetry = TRUE;
    ULONG               timesAlreadyRetried = 0;
    LONGLONG            retryIn100nsUnits = 0;

    PAGED_CODE ();

    while (shouldRetry)
    {
        ScratchBuffer_SetupSrb(DeviceExtension, OriginalRequest, TransferSize, GetDataFromDevice);
    
        // Set up the SRB/CDB
        RtlCopyMemory(cdb, Cdb, sizeof(CDB));

        srb->CdbLength = OprationLength;

        if (TimeoutValue > 0)
        {
            srb->TimeOutValue = TimeoutValue;
        }
    
        ScratchBuffer_SendSrb(DeviceExtension, TRUE, NULL);
    
        if ((DeviceExtension->ScratchContext.ScratchSrb->SrbStatus == SRB_STATUS_ABORTED) &&
            (DeviceExtension->ScratchContext.ScratchSrb->InternalStatus == STATUS_CANCELLED))
        {
            shouldRetry = FALSE;
            status = STATUS_CANCELLED;
        }
        else
        {
            shouldRetry = RequestSenseInfoInterpretForScratchBuffer(DeviceExtension,
                                                                    timesAlreadyRetried,
                                                                    &status,
                                                                    &retryIn100nsUnits);
            if (shouldRetry)
            {
                LARGE_INTEGER t; 
                t.QuadPart = -retryIn100nsUnits;
                timesAlreadyRetried++;
                KeDelayExecutionThread(KernelMode, FALSE, &t);
                // keep items clean
                ScratchBuffer_ResetItems(DeviceExtension, FALSE);
            }
        }
    }

    return status;
}


Our Services

  • What our customers say about us?

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

Privacy Policy. Terms of use. Valid XHTML & CSS