/*-- 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 NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */ 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 NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */ 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 NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */ 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; }