diff options
| author | Dave Wilson <[email protected]> | 2015-03-17 19:50:07 -0700 |
|---|---|---|
| committer | Dave Wilson <[email protected]> | 2015-03-17 19:50:07 -0700 |
| commit | 97cf5197cf5b882b2c689d8dc2b555f2edf8f418 (patch) | |
| tree | 46f3701832d70b420eb0fc0eb93261f9da45db3f /storage/class/classpnp/src/class.c | |
| parent | ef1905bf1e8825bb31120dfb27e0daf3154d859a (diff) | |
Initial publish
Diffstat (limited to 'storage/class/classpnp/src/class.c')
| -rw-r--r-- | storage/class/classpnp/src/class.c | 16415 |
1 files changed, 16415 insertions, 0 deletions
diff --git a/storage/class/classpnp/src/class.c b/storage/class/classpnp/src/class.c new file mode 100644 index 00000000..2f92e453 --- /dev/null +++ b/storage/class/classpnp/src/class.c @@ -0,0 +1,16415 @@ +/*++ + +Copyright (C) Microsoft Corporation, 1991 - 2010 + +Module Name: + + class.c + +Abstract: + + SCSI class driver routines + +Environment: + + kernel mode only + +Notes: + + +Revision History: + +--*/ + +#define CLASS_INIT_GUID 1 +#define DEBUG_MAIN_SOURCE 1 + +#include "classp.h" +#include "debug.h" +#include <process.h> +#include <devpkey.h> +#include <ntiologc.h> + + +#ifdef DEBUG_USE_WPP +#include "class.tmh" +#endif + +#ifdef ALLOC_PRAGMA + #pragma alloc_text(INIT, DriverEntry) + #pragma alloc_text(PAGE, ClassAddDevice) + #pragma alloc_text(PAGE, ClassClaimDevice) + #pragma alloc_text(PAGE, ClassCreateDeviceObject) + #pragma alloc_text(PAGE, ClassDispatchPnp) + #pragma alloc_text(PAGE, ClassGetDescriptor) + #pragma alloc_text(PAGE, ClassGetPdoId) + #pragma alloc_text(PAGE, ClassInitialize) + #pragma alloc_text(PAGE, ClassInitializeEx) + #pragma alloc_text(PAGE, ClassInvalidateBusRelations) + #pragma alloc_text(PAGE, ClassMarkChildMissing) + #pragma alloc_text(PAGE, ClassMarkChildrenMissing) + #pragma alloc_text(PAGE, ClassModeSense) + #pragma alloc_text(PAGE, ClassPnpQueryFdoRelations) + #pragma alloc_text(PAGE, ClassPnpStartDevice) + #pragma alloc_text(PAGE, ClassQueryPnpCapabilities) + #pragma alloc_text(PAGE, ClassQueryTimeOutRegistryValue) + #pragma alloc_text(PAGE, ClassRemoveDevice) + #pragma alloc_text(PAGE, ClassRetrieveDeviceRelations) + #pragma alloc_text(PAGE, ClassUpdateInformationInRegistry) + #pragma alloc_text(PAGE, ClassSendDeviceIoControlSynchronous) + #pragma alloc_text(PAGE, ClassUnload) + #pragma alloc_text(PAGE, ClasspAllocateReleaseRequest) + #pragma alloc_text(PAGE, ClasspFreeReleaseRequest) + #pragma alloc_text(PAGE, ClasspInitializeHotplugInfo) + #pragma alloc_text(PAGE, ClasspRegisterMountedDeviceInterface) + #pragma alloc_text(PAGE, ClasspScanForClassHacks) + #pragma alloc_text(PAGE, ClasspScanForSpecialInRegistry) + #pragma alloc_text(PAGE, ClasspModeSense) + #pragma alloc_text(PAGE, ClasspIsPortable) + #pragma alloc_text(PAGE, ClassAcquireChildLock) + #pragma alloc_text(PAGE, ClassDetermineTokenOperationCommandSupport) + #pragma alloc_text(PAGE, ClassDeviceProcessOffloadRead) + #pragma alloc_text(PAGE, ClassDeviceProcessOffloadWrite) + #pragma alloc_text(PAGE, ClasspServicePopulateTokenTransferRequest) + #pragma alloc_text(PAGE, ClasspServiceWriteUsingTokenTransferRequest) +#if (NTDDI_VERSION >= NTDDI_WINBLUE) + #pragma alloc_text(PAGE, ClassModeSenseEx) +#endif +#endif + +#pragma prefast(disable:28159, "There are certain cases when we have to bugcheck...") + +IO_COMPLETION_ROUTINE ClassCheckVerifyComplete; + + +ULONG ClassPnpAllowUnload = TRUE; +ULONG ClassMaxInterleavePerCriticalIo = CLASS_MAX_INTERLEAVE_PER_CRITICAL_IO; +CONST LARGE_INTEGER Magic10000 = {0xe219652c, 0xd1b71758}; +GUID StoragePredictFailureDPSGuid = WDI_STORAGE_PREDICT_FAILURE_DPS_GUID; + +#define FirstDriveLetter 'C' +#define LastDriveLetter 'Z' + +BOOLEAN UseQPCTime = FALSE; + +// +// Keep track of whether security cookie is initialized or not. This is +// required by SDL. +// + +BOOLEAN InitSecurityCookie = FALSE; + +// +// List Identifier for offload data transfer operations +// +ULONG MaxTokenOperationListIdentifier = MAX_TOKEN_LIST_IDENTIFIERS; +volatile ULONG TokenOperationListIdentifier = (ULONG)-1; + +// +// List of FDOs that have enabled idle power management. +// +LIST_ENTRY IdlePowerFDOList = {0}; +KGUARDED_MUTEX IdlePowerFDOListMutex; + +// +// Handle used to register for power setting notifications. +// +PVOID PowerSettingNotificationHandle; + +// +// Handle used to register for screen state setting notifications. +// +PVOID ScreenStateNotificationHandle; + +// +// Disk idle timeout in milliseconds. +// We default this to 0xFFFFFFFF as this is what the power manager considers +// "never" and ensures we do not set a disk idle timeout until the power +// manager calls us back with a different value. +// +ULONG DiskIdleTimeoutInMS = 0xFFFFFFFF; + + +NTSTATUS DllUnload(VOID) +{ + DbgPrintEx(DPFLTR_CLASSPNP_ID, DPFLTR_INFO_LEVEL, "classpnp.sys is now unloading\n"); + + if (PowerSettingNotificationHandle) { + PoUnregisterPowerSettingCallback(PowerSettingNotificationHandle); + PowerSettingNotificationHandle = NULL; + } + + if (ScreenStateNotificationHandle) { + PoUnregisterPowerSettingCallback(ScreenStateNotificationHandle); + ScreenStateNotificationHandle = NULL; + } + + + return STATUS_SUCCESS; +} + + +/*++//////////////////////////////////////////////////////////////////////////// + +DriverEntry() + +Routine Description: + + Temporary entry point needed to initialize the class system dll. + It doesn't do anything. + +Arguments: + + DriverObject - Pointer to the driver object created by the system. + +Return Value: + + STATUS_SUCCESS + +--*/ +NTSTATUS +DriverEntry( + IN PDRIVER_OBJECT DriverObject, + IN PUNICODE_STRING RegistryPath + ) +{ + UNREFERENCED_PARAMETER(DriverObject); + UNREFERENCED_PARAMETER(RegistryPath); + + return STATUS_SUCCESS; +} + + + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassInitialize() + +Routine Description: + + This routine is called by a class driver during its + DriverEntry routine to initialize the driver. + +Arguments: + + Argument1 - Driver Object. + Argument2 - Registry Path. + InitializationData - Device-specific driver's initialization data. + +Return Value: + + A valid return code for a DriverEntry routine. + +--*/ +_IRQL_requires_max_(PASSIVE_LEVEL) +_Must_inspect_result_ +ULONG +ClassInitialize( + _In_ PVOID Argument1, + _In_ PVOID Argument2, + _In_ PCLASS_INIT_DATA InitializationData + ) +{ + PDRIVER_OBJECT DriverObject = Argument1; + PUNICODE_STRING RegistryPath = Argument2; + + PCLASS_DRIVER_EXTENSION driverExtension; + + NTSTATUS status; + + + + PAGED_CODE(); + + // + // Initialize the security cookie if needed. + // + if (InitSecurityCookie == FALSE) { + __security_init_cookie(); + InitSecurityCookie = TRUE; + } + + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "\n\nSCSI Class Driver\n")); + + ClasspInitializeDebugGlobals(); + + // + // Validate the length of this structure. This is effectively a + // version check. + // + + if (InitializationData->InitializationDataSize != sizeof(CLASS_INIT_DATA)) { + + // + // This DebugPrint is to help third-party driver writers + // + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClassInitialize: Class driver wrong version\n")); + return (ULONG) STATUS_REVISION_MISMATCH; + } + + // + // Check that each required entry is not NULL. Note that Shutdown, Flush and Error + // are not required entry points. + // + + if ((!InitializationData->FdoData.ClassDeviceControl) || + (!((InitializationData->FdoData.ClassReadWriteVerification) || + (InitializationData->ClassStartIo))) || + (!InitializationData->ClassAddDevice) || + (!InitializationData->FdoData.ClassStartDevice)) { + + // + // This DebugPrint is to help third-party driver writers + // + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, + "ClassInitialize: Class device-specific driver missing required " + "FDO entry\n")); + + return (ULONG) STATUS_REVISION_MISMATCH; + } + + if ((InitializationData->ClassEnumerateDevice) && + ((!InitializationData->PdoData.ClassDeviceControl) || + (!InitializationData->PdoData.ClassStartDevice) || + (!((InitializationData->PdoData.ClassReadWriteVerification) || + (InitializationData->ClassStartIo))))) { + + // + // This DebugPrint is to help third-party driver writers + // + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClassInitialize: Class device-specific missing " + "required PDO entry\n")); + + return (ULONG) STATUS_REVISION_MISMATCH; + } + + if((InitializationData->FdoData.ClassStopDevice == NULL) || + ((InitializationData->ClassEnumerateDevice != NULL) && + (InitializationData->PdoData.ClassStopDevice == NULL))) { + + // + // This DebugPrint is to help third-party driver writers + // + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClassInitialize: Class device-specific missing " + "required PDO entry\n")); + NT_ASSERT(FALSE); + return (ULONG) STATUS_REVISION_MISMATCH; + } + + // + // Setup the default power handlers if the class driver didn't provide + // any. + // + + if(InitializationData->FdoData.ClassPowerDevice == NULL) { + InitializationData->FdoData.ClassPowerDevice = ClassMinimalPowerHandler; + } + + if((InitializationData->ClassEnumerateDevice != NULL) && + (InitializationData->PdoData.ClassPowerDevice == NULL)) { + InitializationData->PdoData.ClassPowerDevice = ClassMinimalPowerHandler; + } + + // + // warn that unload is not supported + // + // ISSUE-2000/02/03-peterwie + // We should think about making this a fatal error. + // + + if(InitializationData->ClassUnload == NULL) { + + // + // This DebugPrint is to help third-party driver writers + // + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, "ClassInitialize: driver does not support unload %wZ\n", + RegistryPath)); + } + + // + // Create an extension for the driver object + // +#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case + status = IoAllocateDriverObjectExtension(DriverObject, CLASS_DRIVER_EXTENSION_KEY, sizeof(CLASS_DRIVER_EXTENSION), &driverExtension); + + if(NT_SUCCESS(status)) { + + // + // Copy the registry path into the driver extension so we can use it later + // + + driverExtension->RegistryPath.Length = RegistryPath->Length; + driverExtension->RegistryPath.MaximumLength = RegistryPath->MaximumLength; + + driverExtension->RegistryPath.Buffer = + ExAllocatePoolWithTag(PagedPool, + RegistryPath->MaximumLength, + '1CcS'); + + if(driverExtension->RegistryPath.Buffer == NULL) { + + status = STATUS_INSUFFICIENT_RESOURCES; + return status; + } + + RtlCopyUnicodeString( + &(driverExtension->RegistryPath), + RegistryPath); + + // + // Copy the initialization data into the driver extension so we can reuse + // it during our add device routine + // + + RtlCopyMemory( + &(driverExtension->InitData), + InitializationData, + sizeof(CLASS_INIT_DATA)); + + driverExtension->DeviceCount = 0; + + ClassInitializeDispatchTables(driverExtension); + + } else if (status == STATUS_OBJECT_NAME_COLLISION) { + + // + // The extension already exists - get a pointer to it + // + +#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case + driverExtension = IoGetDriverObjectExtension(DriverObject, CLASS_DRIVER_EXTENSION_KEY); + + NT_ASSERT(driverExtension != NULL); + + } else { + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClassInitialize: Class driver extension could not be " + "allocated %lx\n", status)); + return status; + } + + + // + // Update driver object with entry points. + // + +#pragma prefast(push) +#pragma prefast(disable:28175, "Accessing DRIVER_OBJECT fileds is OK here since this function " \ + "is supposed to be invoked from DriverEntry only") + DriverObject->MajorFunction[IRP_MJ_CREATE] = ClassGlobalDispatch; + DriverObject->MajorFunction[IRP_MJ_CLOSE] = ClassGlobalDispatch; + DriverObject->MajorFunction[IRP_MJ_READ] = ClassGlobalDispatch; + DriverObject->MajorFunction[IRP_MJ_WRITE] = ClassGlobalDispatch; + DriverObject->MajorFunction[IRP_MJ_SCSI] = ClassGlobalDispatch; + DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = ClassGlobalDispatch; + DriverObject->MajorFunction[IRP_MJ_SHUTDOWN] = ClassGlobalDispatch; + DriverObject->MajorFunction[IRP_MJ_FLUSH_BUFFERS] = ClassGlobalDispatch; + DriverObject->MajorFunction[IRP_MJ_PNP] = ClassGlobalDispatch; + DriverObject->MajorFunction[IRP_MJ_POWER] = ClassGlobalDispatch; + DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = ClassGlobalDispatch; + + if (InitializationData->ClassStartIo) { + DriverObject->DriverStartIo = ClasspStartIo; + } + + if ((InitializationData->ClassUnload) && (ClassPnpAllowUnload == TRUE)) { + DriverObject->DriverUnload = ClassUnload; + } else { + DriverObject->DriverUnload = NULL; + } + + DriverObject->DriverExtension->AddDevice = ClassAddDevice; +#pragma prefast(pop) + + + // + // Register for event tracing + // + if (driverExtension->EtwHandle == 0) { + status = EtwRegister(&StoragePredictFailureDPSGuid, + NULL, + NULL, + &driverExtension->EtwHandle); + if (!NT_SUCCESS(status)) { + driverExtension->EtwHandle = 0; + } + WPP_INIT_TRACING(DriverObject, RegistryPath); + } + + + // + // Ensure these are only initialized once. + // + if (IdlePowerFDOList.Flink == NULL) { + InitializeListHead(&IdlePowerFDOList); + KeInitializeGuardedMutex(&IdlePowerFDOListMutex); + } + + status = STATUS_SUCCESS; + return status; +} // end ClassInitialize() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassInitializeEx() + +Routine Description: + + This routine is allows the caller to do any extra initialization or + setup that is not done in ClassInitialize. The operation is + controlled by the GUID that is passed and the contents of the Data + parameter is dependent upon the GUID. + + This is the list of supported operations: + + GUID_CLASSPNP_QUERY_REGINFOEX == CLASS_QUERY_WMI_REGINFO_EX_LIST + + Initialized classpnp to callback a PCLASS_QUERY_WMI_REGINFO_EX + callback instead of a PCLASS_QUERY_WMI_REGINFO callback. The + former callback allows the driver to specify the name of the + mof resource. + + GUID_CLASSPNP_SENSEINFO2 == CLASS_INTERPRET_SENSE_INFO2 + + Initialize classpnp to callback into class drive for interpretation + of all sense info, and to indicate the count of "history" to keep + for each packet. + + GUID_CLASSPNP_WORKING_SET == CLASS_WORKING_SET + + Allow class driver to override the min and max working set transfer + packet value used in classpnp. + + GUID_CLASSPNP_SRB_SUPPORT == ULONG + + Allow class driver to provide supported SRB types. + +Arguments: + + DriverObject + Guid + Data + +Return Value: + + Status Code + +--*/ +_IRQL_requires_max_(PASSIVE_LEVEL) +_Must_inspect_result_ +ULONG +ClassInitializeEx( + _In_ PDRIVER_OBJECT DriverObject, + _In_ LPGUID Guid, + _In_ PVOID Data + ) +{ + PCLASS_DRIVER_EXTENSION driverExtension; + + NTSTATUS status; + + PAGED_CODE(); + +#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case + driverExtension = IoGetDriverObjectExtension( DriverObject, CLASS_DRIVER_EXTENSION_KEY ); + + if (driverExtension == NULL) + { + NT_ASSERT(FALSE); + return (ULONG)STATUS_UNSUCCESSFUL; + } + + if (IsEqualGUID(Guid, &ClassGuidQueryRegInfoEx)) + { + PCLASS_QUERY_WMI_REGINFO_EX_LIST List; + + // + // Indicate the device supports PCLASS_QUERY_REGINFO_EX + // callback instead of PCLASS_QUERY_REGINFO callback. + // + List = (PCLASS_QUERY_WMI_REGINFO_EX_LIST)Data; + + if (List->Size == sizeof(CLASS_QUERY_WMI_REGINFO_EX_LIST)) + { + driverExtension->ClassFdoQueryWmiRegInfoEx = List->ClassFdoQueryWmiRegInfoEx; + driverExtension->ClassPdoQueryWmiRegInfoEx = List->ClassPdoQueryWmiRegInfoEx; + status = STATUS_SUCCESS; + } else { + status = STATUS_INVALID_PARAMETER; + } + } + else if (IsEqualGUID(Guid, &ClassGuidWorkingSet)) + { + PCLASS_WORKING_SET infoOriginal = (PCLASS_WORKING_SET)Data; + PCLASS_WORKING_SET info = NULL; + + // only try to allocate memory for cached copy if size is correct + if (infoOriginal->Size != sizeof(CLASS_WORKING_SET)) + { + // incorrect size -- client programming error + status = STATUS_INVALID_PARAMETER; + } + else + { + info = ExAllocatePoolWithTag(NonPagedPoolNx, + sizeof(CLASS_WORKING_SET), + CLASS_TAG_WORKING_SET + ); + if (info == NULL) + { + status = STATUS_INSUFFICIENT_RESOURCES; + } + else + { + // cache the structure internally + RtlCopyMemory(info, infoOriginal, sizeof(CLASS_WORKING_SET)); + status = STATUS_SUCCESS; + } + } + // if we successfully cached a copy, validate all the data within + if (NT_SUCCESS(status)) + { + if (info->Size != sizeof(CLASS_WORKING_SET)) + { + // incorrect size -- client programming error + status = STATUS_INVALID_PARAMETER; + } + else if (info->XferPacketsWorkingSetMaximum > CLASS_WORKING_SET_MAXIMUM) + { + // too many requested in the working set + status = STATUS_INVALID_PARAMETER; + } + else if (info->XferPacketsWorkingSetMinimum > CLASS_WORKING_SET_MAXIMUM) + { + // too many requested in the working set + status = STATUS_INVALID_PARAMETER; + } + else if (driverExtension->InitData.FdoData.DeviceType != FILE_DEVICE_CD_ROM) + { + // classpnp developer wants to restrict this code path + // for now to CDROM devices only. + status = STATUS_INVALID_DEVICE_REQUEST; + } + else if (driverExtension->WorkingSet != NULL) + { + // not allowed to change it once it is set for a driver + status = STATUS_INVALID_PARAMETER; + NT_ASSERT(FALSE); + } + } + // save results or cleanup + if (NT_SUCCESS(status)) + { + driverExtension->WorkingSet = info; info = NULL; + } + else + { + FREE_POOL( info ); + } + } + else if (IsEqualGUID(Guid, &ClassGuidSenseInfo2)) + { + PCLASS_INTERPRET_SENSE_INFO2 infoOriginal = (PCLASS_INTERPRET_SENSE_INFO2)Data; + PCLASS_INTERPRET_SENSE_INFO2 info = NULL; + + // only try to allocate memory for cached copy if size is correct + if (infoOriginal->Size != sizeof(CLASS_INTERPRET_SENSE_INFO2)) + { + // incorrect size -- client programming error + status = STATUS_INVALID_PARAMETER; + } + else + { + info = ExAllocatePoolWithTag(NonPagedPoolNx, + sizeof(CLASS_INTERPRET_SENSE_INFO2), + CLASS_TAG_SENSE2 + ); + if (info == NULL) + { + status = STATUS_INSUFFICIENT_RESOURCES; + } + else + { + // cache the structure internally + RtlCopyMemory(info, infoOriginal, sizeof(CLASS_INTERPRET_SENSE_INFO2)); + status = STATUS_SUCCESS; + } + } + + // if we successfully cached a copy, validate all the data within + if (NT_SUCCESS(status)) + { + if (info->Size != sizeof(CLASS_INTERPRET_SENSE_INFO2)) + { + // incorrect size -- client programming error + status = STATUS_INVALID_PARAMETER; + } + else if (info->HistoryCount > CLASS_INTERPRET_SENSE_INFO2_MAXIMUM_HISTORY_COUNT) + { + // incorrect count -- client programming error + status = STATUS_INVALID_PARAMETER; + } + else if (info->Compress == NULL) + { + // Compression of the history is required to be supported + status = STATUS_INVALID_PARAMETER; + } + else if (info->HistoryCount == 0) + { + // History count cannot be zero + status = STATUS_INVALID_PARAMETER; + } + else if (info->Interpret == NULL) + { + // Updated interpret sense info function is required + status = STATUS_INVALID_PARAMETER; + } + else if (driverExtension->InitData.FdoData.DeviceType != FILE_DEVICE_CD_ROM) + { + // classpnp developer wants to restrict this code path + // for now to CDROM devices only. + status = STATUS_INVALID_DEVICE_REQUEST; + } + else if (driverExtension->InterpretSenseInfo != NULL) + { + // not allowed to change it once it is set for a driver + status = STATUS_INVALID_PARAMETER; + NT_ASSERT(FALSE); + } + } + + // save results or cleanup + if (NT_SUCCESS(status)) + { + driverExtension->InterpretSenseInfo = info; info = NULL; + } + else + { + FREE_POOL( info ); + } + } + else if (IsEqualGUID(Guid, &ClassGuidSrbSupport)) + { + ULONG srbSupport = *((PULONG)Data); + + // + // Validate that at least one of the supported bit flags is set. Assume + // all class drivers support SCSI_REQUEST_BLOCK as a class driver that + // supports only extended SRB is not feasible. + // + if ((srbSupport & + (CLASS_SRB_SCSI_REQUEST_BLOCK | CLASS_SRB_STORAGE_REQUEST_BLOCK)) != 0) { + driverExtension->SrbSupport = srbSupport; + status = STATUS_SUCCESS; + + // + // Catch cases of a class driver reporting only extended SRB support + // + if ((driverExtension->SrbSupport & CLASS_SRB_SCSI_REQUEST_BLOCK) == 0) { + NT_ASSERT(FALSE); + } + } else { + status = STATUS_INVALID_PARAMETER; + } + } + else + { + status = STATUS_NOT_SUPPORTED; + } + + return status; + +} // end ClassInitializeEx() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassUnload() + +Routine Description: + + called when there are no more references to the driver. this allows + drivers to be updated without rebooting. + +Arguments: + + DriverObject - a pointer to the driver object that is being unloaded + +Status: + +--*/ +VOID +ClassUnload( + IN PDRIVER_OBJECT DriverObject + ) +{ + PCLASS_DRIVER_EXTENSION driverExtension; + + PAGED_CODE(); + + NT_ASSERT( DriverObject->DeviceObject == NULL ); + +#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case + driverExtension = IoGetDriverObjectExtension( DriverObject, CLASS_DRIVER_EXTENSION_KEY ); + + + if (driverExtension == NULL) + { + NT_ASSERT(FALSE); + return; + } + + NT_ASSERT(driverExtension->RegistryPath.Buffer != NULL); + NT_ASSERT(driverExtension->InitData.ClassUnload != NULL); + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "ClassUnload: driver unloading %wZ\n", + &driverExtension->RegistryPath)); + + // + // attempt to process the driver's unload routine first. + // + + driverExtension->InitData.ClassUnload(DriverObject); + + // + // free own allocated resources and return + // + + FREE_POOL( driverExtension->WorkingSet ); + FREE_POOL( driverExtension->InterpretSenseInfo ); + FREE_POOL( driverExtension->RegistryPath.Buffer ); + driverExtension->RegistryPath.Length = 0; + driverExtension->RegistryPath.MaximumLength = 0; + + + // + // Unregister ETW + // + if (driverExtension->EtwHandle != 0) { + EtwUnregister(driverExtension->EtwHandle); + driverExtension->EtwHandle = 0; + + WPP_CLEANUP(DriverObject); + } + + + return; +} // end ClassUnload() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassAddDevice() + +Routine Description: + + SCSI class driver add device routine. This is called by pnp when a new + physical device come into being. + + This routine will call out to the class driver to verify that it should + own this device then will create and attach a device object and then hand + it to the driver to initialize and create symbolic links + +Arguments: + + DriverObject - a pointer to the driver object that this is being created for + PhysicalDeviceObject - a pointer to the physical device object + +Status: STATUS_NO_SUCH_DEVICE if the class driver did not want this device + STATUS_SUCCESS if the creation and attachment was successful + status of device creation and initialization + +--*/ +NTSTATUS +#pragma prefast(suppress:28152, "We expect the class driver to clear the DO_DEVICE_INITIALIZING flag in its AddDevice routine.") +ClassAddDevice( + IN PDRIVER_OBJECT DriverObject, + IN PDEVICE_OBJECT PhysicalDeviceObject + ) +{ +#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case + PCLASS_DRIVER_EXTENSION driverExtension = IoGetDriverObjectExtension(DriverObject, CLASS_DRIVER_EXTENSION_KEY); + + NTSTATUS status; + + PAGED_CODE(); + + + status = driverExtension->InitData.ClassAddDevice(DriverObject, + PhysicalDeviceObject); + + return status; +} // end ClassAddDevice() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassDispatchPnp() + +Routine Description: + + Storage class driver pnp routine. This is called by the io system when + a PNP request is sent to the device. + +Arguments: + + DeviceObject - pointer to the device object + + Irp - pointer to the io request packet + +Return Value: + + status + +--*/ +NTSTATUS +ClassDispatchPnp( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP Irp + ) +{ + PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; + BOOLEAN isFdo = commonExtension->IsFdo; + + PCLASS_DRIVER_EXTENSION driverExtension; + PCLASS_INIT_DATA initData; + PCLASS_DEV_INFO devInfo; + + PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp); + + NTSTATUS status = Irp->IoStatus.Status; + BOOLEAN completeRequest = TRUE; + BOOLEAN lockReleased = FALSE; + + + PAGED_CODE(); + + // + // Extract all the useful information out of the driver object + // extension + // + +#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case + driverExtension = IoGetDriverObjectExtension(DeviceObject->DriverObject, CLASS_DRIVER_EXTENSION_KEY); + + if (driverExtension){ + + initData = &(driverExtension->InitData); + + if(isFdo) { + devInfo = &(initData->FdoData); + } else { + devInfo = &(initData->PdoData); + } + + ClassAcquireRemoveLock(DeviceObject, Irp); + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): minor code %#x for %s %p\n", + DeviceObject, Irp, + irpStack->MinorFunction, + isFdo ? "fdo" : "pdo", + DeviceObject)); + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): previous %#x, current %#x\n", + DeviceObject, Irp, + commonExtension->PreviousState, + commonExtension->CurrentState)); + + + switch(irpStack->MinorFunction) { + + case IRP_MN_START_DEVICE: { + + // + // if this is sent to the FDO we should forward it down the + // attachment chain before we start the FDO. + // + + if (isFdo) { + status = ClassForwardIrpSynchronous(commonExtension, Irp); + } + else { + status = STATUS_SUCCESS; + } + + if (NT_SUCCESS(status)){ + status = Irp->IoStatus.Status = ClassPnpStartDevice(DeviceObject); + } + + break; + } + + + case IRP_MN_QUERY_DEVICE_RELATIONS: { + + DEVICE_RELATION_TYPE type = + irpStack->Parameters.QueryDeviceRelations.Type; + + PDEVICE_RELATIONS deviceRelations = NULL; + + + if(!isFdo) { + + if(type == TargetDeviceRelation) { + + // + // Device relations has one entry built in to it's size. + // + + status = STATUS_INSUFFICIENT_RESOURCES; + + deviceRelations = ExAllocatePoolWithTag(PagedPool, + sizeof(DEVICE_RELATIONS), + '2CcS'); + + if(deviceRelations != NULL) { + + RtlZeroMemory(deviceRelations, + sizeof(DEVICE_RELATIONS)); + + Irp->IoStatus.Information = (ULONG_PTR) deviceRelations; + + deviceRelations->Count = 1; + deviceRelations->Objects[0] = DeviceObject; + ObReferenceObject(deviceRelations->Objects[0]); + + status = STATUS_SUCCESS; + } + + } else { + // + // PDO's just complete enumeration requests without altering + // the status. + // + + status = Irp->IoStatus.Status; + } + + break; + + } else if (type == BusRelations) { + + NT_ASSERT(commonExtension->IsInitialized); + + // + // Make sure we support enumeration + // + + if(initData->ClassEnumerateDevice == NULL) { + + // + // Just send the request down to the lower driver. Perhaps + // It can enumerate children. + // + + } else { + + // + // Re-enumerate the device + // + + status = ClassPnpQueryFdoRelations(DeviceObject, Irp); + + if(!NT_SUCCESS(status)) { + completeRequest = TRUE; + break; + } + } + } + + + IoCopyCurrentIrpStackLocationToNext(Irp); + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + completeRequest = FALSE; + + break; + } + + case IRP_MN_QUERY_ID: { + + BUS_QUERY_ID_TYPE idType = irpStack->Parameters.QueryId.IdType; + UNICODE_STRING unicodeString; + + + if(isFdo) { + + + // + // FDO's should just forward the query down to the lower + // device objects + // + + IoCopyCurrentIrpStackLocationToNext(Irp); + ClassReleaseRemoveLock(DeviceObject, Irp); + + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + completeRequest = FALSE; + break; + } + + // + // PDO's need to give an answer - this is easy for now + // + + RtlInitUnicodeString(&unicodeString, NULL); + + status = ClassGetPdoId(DeviceObject, + idType, + &unicodeString); + + if(status == STATUS_NOT_IMPLEMENTED) { + // + // The driver doesn't implement this ID (whatever it is). + // Use the status out of the IRP so that we don't mangle a + // response from someone else. + // + + status = Irp->IoStatus.Status; + } else if(NT_SUCCESS(status)) { + Irp->IoStatus.Information = (ULONG_PTR) unicodeString.Buffer; + } else { + Irp->IoStatus.Information = (ULONG_PTR) NULL; + } + + break; + } + + case IRP_MN_QUERY_STOP_DEVICE: + case IRP_MN_QUERY_REMOVE_DEVICE: { + + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): Processing QUERY_%s irp\n", + DeviceObject, Irp, + ((irpStack->MinorFunction == IRP_MN_QUERY_STOP_DEVICE) ? + "STOP" : "REMOVE"))); + + // + // If this device is in use for some reason (paging, etc...) + // then we need to fail the request. + // + + if(commonExtension->PagingPathCount != 0) { + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): device is in paging " + "path and cannot be removed\n", + DeviceObject, Irp)); + status = STATUS_DEVICE_BUSY; + break; + } + + + // + // Check with the class driver to see if the query operation + // can succeed. + // + + if(irpStack->MinorFunction == IRP_MN_QUERY_STOP_DEVICE) { + status = devInfo->ClassStopDevice(DeviceObject, + irpStack->MinorFunction); + } else { + status = devInfo->ClassRemoveDevice(DeviceObject, + irpStack->MinorFunction); + } + + if(NT_SUCCESS(status)) { + + // + // ASSERT that we never get two queries in a row, as + // this will severly mess up the state machine + // + NT_ASSERT(commonExtension->CurrentState != irpStack->MinorFunction); + commonExtension->PreviousState = commonExtension->CurrentState; + commonExtension->CurrentState = irpStack->MinorFunction; + + if(isFdo) { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): Forwarding QUERY_" + "%s irp\n", DeviceObject, Irp, + ((irpStack->MinorFunction == IRP_MN_QUERY_STOP_DEVICE) ? + "STOP" : "REMOVE"))); + status = ClassForwardIrpSynchronous(commonExtension, Irp); + } + } + + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): Final status == %x\n", + DeviceObject, Irp, status)); + + break; + } + + case IRP_MN_CANCEL_STOP_DEVICE: + case IRP_MN_CANCEL_REMOVE_DEVICE: { + + + // + // Check with the class driver to see if the query or cancel + // operation can succeed. + // + + if(irpStack->MinorFunction == IRP_MN_CANCEL_STOP_DEVICE) { + status = devInfo->ClassStopDevice(DeviceObject, + irpStack->MinorFunction); + NT_ASSERTMSG("ClassDispatchPnp !! CANCEL_STOP_DEVICE should never be failed\n", NT_SUCCESS(status)); + } else { + status = devInfo->ClassRemoveDevice(DeviceObject, + irpStack->MinorFunction); + NT_ASSERTMSG("ClassDispatchPnp !! CANCEL_REMOVE_DEVICE should never be failed\n", NT_SUCCESS(status)); + } + + Irp->IoStatus.Status = status; + + // + // We got a CANCEL - roll back to the previous state only + // if the current state is the respective QUERY state. + // + + if(((irpStack->MinorFunction == IRP_MN_CANCEL_STOP_DEVICE) && + (commonExtension->CurrentState == IRP_MN_QUERY_STOP_DEVICE) + ) || + ((irpStack->MinorFunction == IRP_MN_CANCEL_REMOVE_DEVICE) && + (commonExtension->CurrentState == IRP_MN_QUERY_REMOVE_DEVICE) + ) + ) { + + commonExtension->CurrentState = + commonExtension->PreviousState; + commonExtension->PreviousState = 0xff; + + } + + + if(isFdo) { + + + IoCopyCurrentIrpStackLocationToNext(Irp); + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + completeRequest = FALSE; + } else { + status = STATUS_SUCCESS; + } + + break; + } + + case IRP_MN_STOP_DEVICE: { + + + // + // These all mean nothing to the class driver currently. The + // port driver will handle all queueing when necessary. + // + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): got stop request for %s\n", + DeviceObject, Irp, + (isFdo ? "fdo" : "pdo") + )); + + NT_ASSERT(commonExtension->PagingPathCount == 0); + + // + // ISSUE-2000/02/03-peterwie + // if we stop the timer here then it means no class driver can + // do i/o in its ClassStopDevice routine. This is because the + // retry (among other things) is tied into the tick handler + // and disabling retries could cause the class driver to deadlock. + // Currently no class driver we're aware of issues i/o in its + // Stop routine but this is a case we may want to defend ourself + // against. + // + + ClasspDisableTimer((PFUNCTIONAL_DEVICE_EXTENSION)commonExtension); + + + status = devInfo->ClassStopDevice(DeviceObject, IRP_MN_STOP_DEVICE); + + NT_ASSERTMSG("ClassDispatchPnp !! STOP_DEVICE should never be failed\n", NT_SUCCESS(status)); + + if(isFdo) { + status = ClassForwardIrpSynchronous(commonExtension, Irp); + } + + if(NT_SUCCESS(status)) { + commonExtension->CurrentState = irpStack->MinorFunction; + commonExtension->PreviousState = 0xff; + } + + + break; + } + + case IRP_MN_REMOVE_DEVICE: + case IRP_MN_SURPRISE_REMOVAL: { + UCHAR removeType = irpStack->MinorFunction; + + // + // Log a sytem event when non-removable disks are surprise-removed. + // + if (isFdo && + (removeType == IRP_MN_SURPRISE_REMOVAL)) { + + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension; + PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData; + BOOLEAN logSurpriseRemove = TRUE; + STORAGE_BUS_TYPE busType = fdoExtension->DeviceDescriptor->BusType; + + // + // Don't log an event for VHDs + // + if (busType == BusTypeFileBackedVirtual) { + logSurpriseRemove = FALSE; + + } else if (fdoData->HotplugInfo.MediaRemovable) { + logSurpriseRemove = FALSE; + + } else if (fdoData->HotplugInfo.DeviceHotplug && ( busType == BusTypeUsb || busType == BusType1394)) { + + /* + This device is reported as DeviceHotplug but since the busType is Usb or 1394, don't log an event + Note that some storage arrays may report DeviceHotplug and we would like to log an event in those cases + */ + + logSurpriseRemove = FALSE; + } + + if (logSurpriseRemove) { + + ClasspLogSystemEventWithDeviceNumber(DeviceObject, IO_WARNING_DISK_SURPRISE_REMOVED); + } + + } + + if (commonExtension->PagingPathCount != 0) { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): paging device is getting removed!", DeviceObject, Irp)); + } + + // + // Release the lock for this IRP before calling in. + // + ClassReleaseRemoveLock(DeviceObject, Irp); + lockReleased = TRUE; + + /* + * Set IsRemoved before propagating the REMOVE down the stack. + * This keeps class-initiated I/O (e.g. the MCN irp) from getting sent + * after we propagate the remove. + */ + commonExtension->IsRemoved = REMOVE_PENDING; + + /* + * If a timer was started on the device, stop it. + */ + ClasspDisableTimer((PFUNCTIONAL_DEVICE_EXTENSION)commonExtension); + + /* + * "Fire-and-forget" the remove irp to the lower stack. + * Don't touch the irp (or the irp stack!) after this. + */ + if (isFdo) { + + + IoCopyCurrentIrpStackLocationToNext(Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + NT_ASSERT(NT_SUCCESS(status)); + completeRequest = FALSE; + } + else { + status = STATUS_SUCCESS; + } + + /* + * Do our own cleanup and call the class driver's remove + * cleanup routine. + * For IRP_MN_REMOVE_DEVICE, this also deletes our device object, + * so don't touch the extension after this. + */ + commonExtension->PreviousState = commonExtension->CurrentState; + commonExtension->CurrentState = removeType; + ClassRemoveDevice(DeviceObject, removeType); + + break; + } + + case IRP_MN_DEVICE_USAGE_NOTIFICATION: { + + DEVICE_USAGE_NOTIFICATION_TYPE type = irpStack->Parameters.UsageNotification.Type; + BOOLEAN setPagable; + + + switch(type) { + + case DeviceUsageTypePaging: { + + if ((irpStack->Parameters.UsageNotification.InPath) && + (commonExtension->CurrentState != IRP_MN_START_DEVICE)) { + + // + // Device isn't started. Don't allow adding a + // paging file, but allow a removal of one. + // + + status = STATUS_DEVICE_NOT_READY; + break; + } + + NT_ASSERT(commonExtension->IsInitialized); + + /* + * Ensure that this user thread is not suspended while we are holding the PathCountEvent. + */ + KeEnterCriticalRegion(); + + (VOID)KeWaitForSingleObject(&commonExtension->PathCountEvent, + Executive, KernelMode, + FALSE, NULL); + status = STATUS_SUCCESS; + + // + // If the volume is removable we should try to lock it in + // place or unlock it once per paging path count + // + + if (commonExtension->IsFdo){ + status = ClasspEjectionControl( + DeviceObject, + Irp, + InternalMediaLock, + (BOOLEAN)irpStack->Parameters.UsageNotification.InPath); + } + + if (!NT_SUCCESS(status)){ + KeSetEvent(&commonExtension->PathCountEvent, IO_NO_INCREMENT, FALSE); + KeLeaveCriticalRegion(); + break; + } + + // + // if removing last paging device, need to set DO_POWER_PAGABLE + // bit here, and possible re-set it below on failure. + // + + setPagable = FALSE; + + if ((!irpStack->Parameters.UsageNotification.InPath) && + (commonExtension->PagingPathCount == 1)) { + + // + // removing last paging file + // must have DO_POWER_PAGABLE bits set, but only + // if none set the DO_POWER_INRUSH bit and no other special files + // + + if (TEST_FLAG(DeviceObject->Flags, DO_POWER_INRUSH)) { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): Last " + "paging file removed, but " + "DO_POWER_INRUSH was set, so NOT " + "setting DO_POWER_PAGABLE\n", + DeviceObject, Irp)); + } else if ((commonExtension->HibernationPathCount == 0) && + (commonExtension->DumpPathCount == 0)) { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): Last " + "paging file removed, " + "setting DO_POWER_PAGABLE\n", + DeviceObject, Irp)); + SET_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE); + setPagable = TRUE; + } + + } + + // + // forward the irp before finishing handling the + // special cases + // + + status = ClassForwardIrpSynchronous(commonExtension, Irp); + + // + // now deal with the failure and success cases. + // note that we are not allowed to fail the irp + // once it is sent to the lower drivers. + // + + if (NT_SUCCESS(status)) { + + IoAdjustPagingPathCount( + (volatile LONG *)&commonExtension->PagingPathCount, + irpStack->Parameters.UsageNotification.InPath); + + if (irpStack->Parameters.UsageNotification.InPath) { + if (commonExtension->PagingPathCount == 1) { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): " + "Clearing PAGABLE bit\n", + DeviceObject, Irp)); + CLEAR_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE); + + + } + + } + + } else { + + // + // cleanup the changes done above + // + + if (setPagable == TRUE) { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): Unsetting " + "PAGABLE bit due to irp failure\n", + DeviceObject, Irp)); + CLEAR_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE); + setPagable = FALSE; + } + + // + // relock or unlock the media if needed. + // + + if (commonExtension->IsFdo) { + + ClasspEjectionControl( + DeviceObject, + Irp, + InternalMediaLock, + (BOOLEAN)!irpStack->Parameters.UsageNotification.InPath); + } + } + + // + // set the event so the next one can occur. + // + + KeSetEvent(&commonExtension->PathCountEvent, + IO_NO_INCREMENT, FALSE); + KeLeaveCriticalRegion(); + break; + } + + case DeviceUsageTypeHibernation: { + + // + // if removing last hiber device, need to set DO_POWER_PAGABLE + // bit here, and possible re-set it below on failure. + // + + setPagable = FALSE; + + if ((!irpStack->Parameters.UsageNotification.InPath) && + (commonExtension->HibernationPathCount == 1)) { + + // + // removing last hiber file + // must have DO_POWER_PAGABLE bits set, but only + // if none set the DO_POWER_INRUSH bit and no other special files + // + + if (TEST_FLAG(DeviceObject->Flags, DO_POWER_INRUSH)) { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): Last " + "hiber file removed, but " + "DO_POWER_INRUSH was set, so NOT " + "setting DO_POWER_PAGABLE\n", + DeviceObject, Irp)); + } else if ((commonExtension->PagingPathCount == 0) && + (commonExtension->DumpPathCount == 0)) { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): Last " + "hiber file removed, " + "setting DO_POWER_PAGABLE\n", + DeviceObject, Irp)); + SET_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE); + setPagable = TRUE; + } + + } + + status = ClassForwardIrpSynchronous(commonExtension, Irp); + if (!NT_SUCCESS(status)) { + + if (setPagable == TRUE) { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): Unsetting " + "PAGABLE bit due to irp failure\n", + DeviceObject, Irp)); + CLEAR_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE); + setPagable = FALSE; + } + + } else { + + IoAdjustPagingPathCount( + (volatile LONG *)&commonExtension->HibernationPathCount, + irpStack->Parameters.UsageNotification.InPath + ); + + if ((irpStack->Parameters.UsageNotification.InPath) && + (commonExtension->HibernationPathCount == 1)) { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): " + "Clearing PAGABLE bit\n", + DeviceObject, Irp)); + CLEAR_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE); + } + } + + break; + } + + case DeviceUsageTypeDumpFile: { + + // + // if removing last dump device, need to set DO_POWER_PAGABLE + // bit here, and possible re-set it below on failure. + // + + setPagable = FALSE; + + if ((!irpStack->Parameters.UsageNotification.InPath) && + (commonExtension->DumpPathCount == 1)) { + + // + // removing last dump file + // must have DO_POWER_PAGABLE bits set, but only + // if none set the DO_POWER_INRUSH bit and no other special files + // + + if (TEST_FLAG(DeviceObject->Flags, DO_POWER_INRUSH)) { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): Last " + "dump file removed, but " + "DO_POWER_INRUSH was set, so NOT " + "setting DO_POWER_PAGABLE\n", + DeviceObject, Irp)); + } else if ((commonExtension->PagingPathCount == 0) && + (commonExtension->HibernationPathCount == 0)) { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): Last " + "dump file removed, " + "setting DO_POWER_PAGABLE\n", + DeviceObject, Irp)); + SET_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE); + setPagable = TRUE; + } + + } + + status = ClassForwardIrpSynchronous(commonExtension, Irp); + if (!NT_SUCCESS(status)) { + + if (setPagable == TRUE) { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): Unsetting " + "PAGABLE bit due to irp failure\n", + DeviceObject, Irp)); + CLEAR_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE); + setPagable = FALSE; + } + + } else { + + IoAdjustPagingPathCount( + (volatile LONG *)&commonExtension->DumpPathCount, + irpStack->Parameters.UsageNotification.InPath + ); + + if ((irpStack->Parameters.UsageNotification.InPath) && + (commonExtension->DumpPathCount == 1)) { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): " + "Clearing PAGABLE bit\n", + DeviceObject, Irp)); + CLEAR_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE); + } + } + + break; + } + + case DeviceUsageTypeBoot: { + + if (isFdo) { + PCLASS_PRIVATE_FDO_DATA fdoData; + + fdoData = ((PFUNCTIONAL_DEVICE_EXTENSION)(DeviceObject->DeviceExtension))->PrivateFdoData; + + + // + // If boot disk has removal policy as RemovalPolicyExpectSurpriseRemoval (e.g. disk is hotplug-able), + // change the removal policy to RemovalPolicyExpectOrderlyRemoval. + // This will cause the write cache of disk to be enabled on subsequent start Fdo (next boot). + // + if ((fdoData != NULL) && + fdoData->HotplugInfo.DeviceHotplug) { + + fdoData->HotplugInfo.DeviceHotplug = FALSE; + fdoData->HotplugInfo.MediaRemovable = FALSE; + + ClassSetDeviceParameter((PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension, + CLASSP_REG_SUBKEY_NAME, + CLASSP_REG_REMOVAL_POLICY_VALUE_NAME, + RemovalPolicyExpectOrderlyRemoval); + } + } + + status = ClassForwardIrpSynchronous(commonExtension, Irp); + break; + } + + default: { + status = STATUS_INVALID_PARAMETER; + break; + } + } + break; + } + + case IRP_MN_QUERY_CAPABILITIES: { + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): QueryCapabilities\n", + DeviceObject, Irp)); + + if(!isFdo) { + + status = ClassQueryPnpCapabilities( + DeviceObject, + irpStack->Parameters.DeviceCapabilities.Capabilities + ); + + break; + + } else { + + PDEVICE_CAPABILITIES deviceCapabilities; + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension; + PCLASS_PRIVATE_FDO_DATA fdoData; + + fdoExtension = DeviceObject->DeviceExtension; + fdoData = fdoExtension->PrivateFdoData; + deviceCapabilities = + irpStack->Parameters.DeviceCapabilities.Capabilities; + + // + // forward the irp before handling the special cases + // + + status = ClassForwardIrpSynchronous(commonExtension, Irp); + if (!NT_SUCCESS(status)) { + break; + } + + // + // we generally want to remove the device from the hotplug + // applet, which requires the SR-OK bit to be set. + // only when the user specifies that they are capable of + // safely removing things do we want to clear this bit + // (saved in WriteCacheEnableOverride) + // + // setting of this bit is done either above, or by the + // lower driver. + // + // note: may not be started, so check we have FDO data first. + // + + if (fdoData && + fdoData->HotplugInfo.WriteCacheEnableOverride) { + if (deviceCapabilities->SurpriseRemovalOK) { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "Classpnp: Clearing SR-OK bit in " + "device capabilities due to hotplug " + "device or media\n")); + } + deviceCapabilities->SurpriseRemovalOK = FALSE; + } + break; + + } // end QUERY_CAPABILITIES for FDOs + + break; + + + } // end QUERY_CAPABILITIES + + default: { + + if (isFdo){ + + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + + completeRequest = FALSE; + } + + break; + } + } + } + else { + NT_ASSERT(driverExtension); + status = STATUS_INTERNAL_ERROR; + } + + if (completeRequest){ + Irp->IoStatus.Status = status; + + if (!lockReleased){ + ClassReleaseRemoveLock(DeviceObject, Irp); + } + + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): leaving with previous %#x, current %#x.", DeviceObject, Irp, commonExtension->PreviousState, commonExtension->CurrentState)); + } + else { + /* + * The irp is already completed so don't touch it. + * This may be a remove so don't touch the device extension. + */ + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): leaving.", DeviceObject, Irp)); + } + + return status; +} // end ClassDispatchPnp() + + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassPnpStartDevice() + +Routine Description: + + Storage class driver routine for IRP_MN_START_DEVICE requests. + This routine kicks off any device specific initialization + +Arguments: + + DeviceObject - a pointer to the device object + + Irp - a pointer to the io request packet + +Return Value: + + none + +--*/ +NTSTATUS ClassPnpStartDevice(IN PDEVICE_OBJECT DeviceObject) +{ + PCLASS_DRIVER_EXTENSION driverExtension; + PCLASS_INIT_DATA initData; + + PCLASS_DEV_INFO devInfo; + + PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension; + BOOLEAN isFdo = commonExtension->IsFdo; + + BOOLEAN isMountedDevice = TRUE; + BOOLEAN isPortable = FALSE; + + NTSTATUS status = STATUS_SUCCESS; + PDEVICE_POWER_DESCRIPTOR powerDescriptor = NULL; + + + PAGED_CODE(); + +#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case + driverExtension = IoGetDriverObjectExtension(DeviceObject->DriverObject, CLASS_DRIVER_EXTENSION_KEY); + + initData = &(driverExtension->InitData); + if(isFdo) { + devInfo = &(initData->FdoData); + } else { + devInfo = &(initData->PdoData); + } + + NT_ASSERT(devInfo->ClassInitDevice != NULL); + NT_ASSERT(devInfo->ClassStartDevice != NULL); + + if (!commonExtension->IsInitialized){ + + // + // perform FDO/PDO specific initialization + // + + if (isFdo){ + STORAGE_PROPERTY_ID propertyId; + + // + // allocate a private extension for class data + // + + if (fdoExtension->PrivateFdoData == NULL) { + fdoExtension->PrivateFdoData = ExAllocatePoolWithTag(NonPagedPoolNx, + sizeof(CLASS_PRIVATE_FDO_DATA), + CLASS_TAG_PRIVATE_DATA + ); + } + + if (fdoExtension->PrivateFdoData == NULL) { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP, "ClassPnpStartDevice: Cannot allocate for private fdo data\n")); + return STATUS_INSUFFICIENT_RESOURCES; + } + + RtlZeroMemory(fdoExtension->PrivateFdoData, sizeof(CLASS_PRIVATE_FDO_DATA)); + + +#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD) + // + // Allocate a structure to hold more data than what we can put in FUNCTIONAL_DEVICE_EXTENSION. + // This structure's memory is managed by classpnp, so it is more extensible. + // + if (fdoExtension->AdditionalFdoData == NULL) { + fdoExtension->AdditionalFdoData = ExAllocatePoolWithTag(NonPagedPoolNx, + sizeof(ADDITIONAL_FDO_DATA), + CLASSPNP_POOL_TAG_ADDITIONAL_DATA + ); + } + + if (fdoExtension->AdditionalFdoData == NULL) { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP, "ClassPnpStartDevice: Cannot allocate memory for the additional data structure.\n")); + return STATUS_INSUFFICIENT_RESOURCES; + } + + RtlZeroMemory(fdoExtension->AdditionalFdoData, sizeof(ADDITIONAL_FDO_DATA)); +#endif + + status = ClasspInitializeTimer(fdoExtension); + if (NT_SUCCESS(status) == FALSE) { + FREE_POOL(fdoExtension->PrivateFdoData); +#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD) + FREE_POOL(fdoExtension->AdditionalFdoData); +#endif + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP, "ClassPnpStartDevice: Failed to initialize tick timer\n")); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // + // allocate LowerLayerSupport for class data + // + + if (fdoExtension->FunctionSupportInfo == NULL) { + fdoExtension->FunctionSupportInfo = (PCLASS_FUNCTION_SUPPORT_INFO)ExAllocatePoolWithTag(NonPagedPoolNx, + sizeof(CLASS_FUNCTION_SUPPORT_INFO), + '3BcS' + ); + } + + if (fdoExtension->FunctionSupportInfo == NULL) { + ClasspDeleteTimer(fdoExtension); + FREE_POOL(fdoExtension->PrivateFdoData); +#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD) + FREE_POOL(fdoExtension->AdditionalFdoData); +#endif + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP, "ClassPnpStartDevice: Cannot allocate for FunctionSupportInfo\n")); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // + // initialize the struct's various fields. + // + RtlZeroMemory(fdoExtension->FunctionSupportInfo, sizeof(CLASS_FUNCTION_SUPPORT_INFO)); + KeInitializeSpinLock(&fdoExtension->FunctionSupportInfo->SyncLock); + + // + // intialize the CommandStatus to -1 indicates that no effort made yet to retrieve the info. + // Possible values of CommandStatus (data type: NTSTATUS): + // -1: It's not attempted yet to retrieve the information. + // success: Command sent and succeeded, information cached in FdoExtension. + // failed/warning: Command is either not supported or failed by device or lower level driver. + // The command should not be attempted again. + // + fdoExtension->FunctionSupportInfo->BlockLimitsData.CommandStatus = -1; + fdoExtension->FunctionSupportInfo->DeviceCharacteristicsData.CommandStatus = -1; + fdoExtension->FunctionSupportInfo->LBProvisioningData.CommandStatus = -1; + fdoExtension->FunctionSupportInfo->ReadCapacity16Data.CommandStatus = -1; + fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus = -1; + + + KeInitializeTimer(&fdoExtension->PrivateFdoData->Retry.Timer); + KeInitializeDpc(&fdoExtension->PrivateFdoData->Retry.Dpc, + ClasspRetryRequestDpc, + DeviceObject); + KeInitializeSpinLock(&fdoExtension->PrivateFdoData->Retry.Lock); + fdoExtension->PrivateFdoData->Retry.Granularity = KeQueryTimeIncrement(); + commonExtension->Reserved4 = (ULONG_PTR)(' GPH'); // debug aid + InitializeListHead(&fdoExtension->PrivateFdoData->DeferredClientIrpList); + + KeInitializeSpinLock(&fdoExtension->PrivateFdoData->SpinLock); + + // + // keep a pointer to the senseinfo2 stuff locally also (used in every read/write). + // + fdoExtension->PrivateFdoData->InterpretSenseInfo = driverExtension->InterpretSenseInfo; + + fdoExtension->PrivateFdoData->MaxNumberOfIoRetries = NUM_IO_RETRIES; + + // + // Initialize release queue extended SRB + // + status = InitializeStorageRequestBlock(&(fdoExtension->PrivateFdoData->ReleaseQueueSrb.SrbEx), + STORAGE_ADDRESS_TYPE_BTL8, + sizeof(fdoExtension->PrivateFdoData->ReleaseQueueSrb.ReleaseQueueSrbBuffer), + 0); + if (!NT_SUCCESS(status)) { + NT_ASSERT(FALSE); + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP, + "ClassPnpStartDevice: fail to initialize release queue extended SRB 0x%x\n", status)); + return status; + } + + + // Initialize performance counter frequency + KeQueryPerformanceCounter(&(fdoExtension->PrivateFdoData->PerfCounterFrequency)); + + if (fdoExtension->PrivateFdoData->PerfCounterFrequency.QuadPart == 0) { + fdoExtension->PrivateFdoData->PerfCounterFrequency.QuadPart = 1; + } + + /* + * Anchor the FDO in our static list. + * Pnp is synchronized, so we shouldn't need any synchronization here. + */ + InsertTailList(&AllFdosList, &fdoExtension->PrivateFdoData->AllFdosListEntry); + + // + // NOTE: the old interface allowed the class driver to allocate + // this. this was unsafe for low-memory conditions. allocate one + // unconditionally now, and modify our internal functions to use + // our own exclusively as it is the only safe way to do this. + // + + status = ClasspAllocateReleaseQueueIrp(fdoExtension); + if (!NT_SUCCESS(status)) { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP, "ClassPnpStartDevice: Cannot allocate the private release queue irp\n")); + return status; + } + + status = ClasspAllocatePowerProcessIrp(fdoExtension); + if (!NT_SUCCESS(status)) { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP, "ClassPnpStartDevice: Cannot allocate the power process irp\n")); + return status; + } + + // + // Call port driver to get miniport properties for disk devices + // It's ok for this call to fail + // + + if ((DeviceObject->DeviceType == FILE_DEVICE_DISK) && + (!TEST_FLAG(DeviceObject->Characteristics, FILE_FLOPPY_DISKETTE))) { + + propertyId = StorageMiniportProperty; + + status = ClassGetDescriptor(fdoExtension->CommonExtension.LowerDeviceObject, + &propertyId, + &fdoExtension->MiniportDescriptor); + + // + // function ClassGetDescriptor returns succeed with buffer "fdoExtension->MiniportDescriptor" allocated. + // + if ( NT_SUCCESS(status) && + (fdoExtension->MiniportDescriptor->Portdriver != StoragePortCodeSetStorport && + fdoExtension->MiniportDescriptor->Portdriver != StoragePortCodeSetUSBport) ) { + // + // field "IoTimeoutValue" supported for either Storport or USBStor + // + fdoExtension->MiniportDescriptor->IoTimeoutValue = 0; + } + + + + } + + // + // Call port driver to get adapter capabilities. + // + + propertyId = StorageAdapterProperty; + + status = ClassGetDescriptor( + commonExtension->LowerDeviceObject, + &propertyId, + &fdoExtension->AdapterDescriptor); + if (!NT_SUCCESS(status)) { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP, "ClassPnpStartDevice: ClassGetDescriptor [ADAPTER] failed %lx\n", status)); + return status; + } + + // + // Call port driver to get device descriptor. + // + + propertyId = StorageDeviceProperty; + + status = ClassGetDescriptor( + commonExtension->LowerDeviceObject, + &propertyId, + &fdoExtension->DeviceDescriptor); + if (NT_SUCCESS(status)){ + + ClasspScanForSpecialInRegistry(fdoExtension); + ClassScanForSpecial(fdoExtension, ClassBadItems, ClasspScanForClassHacks); + + // + // allow perf to be re-enabled after a given number of failed IOs + // require this number to be at least CLASS_PERF_RESTORE_MINIMUM + // + + { + ULONG t = CLASS_PERF_RESTORE_MINIMUM; + + ClassGetDeviceParameter(fdoExtension, + CLASSP_REG_SUBKEY_NAME, + CLASSP_REG_PERF_RESTORE_VALUE_NAME, + &t); + if (t >= CLASS_PERF_RESTORE_MINIMUM) { + fdoExtension->PrivateFdoData->Perf.ReEnableThreshhold = t; + } + } + + // + // compatibility comes first. writable cd media will not + // get a SYNCH_CACHE on power down. + // + if (fdoExtension->DeviceObject->DeviceType != FILE_DEVICE_DISK) { + SET_FLAG(fdoExtension->PrivateFdoData->HackFlags, FDO_HACK_NO_SYNC_CACHE); + } + + + // + // Test if the device is portable and updated the characteristics if so + // + status = ClasspIsPortable(fdoExtension, + &isPortable); + + if (NT_SUCCESS(status) && (isPortable == TRUE)) { + DeviceObject->Characteristics |= FILE_PORTABLE_DEVICE; + } + + // + // initialize the hotplug information only after the ScanForSpecial + // routines, as it relies upon the hack flags. + // + status = ClasspInitializeHotplugInfo(fdoExtension); + if (NT_SUCCESS(status)){ + /* + * Allocate/initialize TRANSFER_PACKETs and related resources. + */ + status = InitializeTransferPackets(DeviceObject); + } + else { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP, "ClassPnpStartDevice: Could not initialize hotplug information %lx\n", status)); + } + } + else { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP, "ClassPnpStartDevice: ClassGetDescriptor [DEVICE] failed %lx\n", status)); + return status; + } + + + if (NT_SUCCESS(status)) { + + // + // Retrieve info on whether async notification is supported by port drivers + // + propertyId = StorageDevicePowerProperty; + + status = ClassGetDescriptor(fdoExtension->CommonExtension.LowerDeviceObject, + &propertyId, + &powerDescriptor); + if (NT_SUCCESS(status) && (powerDescriptor != NULL)) { + fdoExtension->FunctionSupportInfo->AsynchronousNotificationSupported = powerDescriptor->AsynchronousNotificationSupported; + fdoExtension->FunctionSupportInfo->IdlePower.D3ColdSupported = powerDescriptor->D3ColdSupported; + fdoExtension->FunctionSupportInfo->IdlePower.NoVerifyDuringIdlePower = powerDescriptor->NoVerifyDuringIdlePower; + FREE_POOL(powerDescriptor); + } else { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP, "ClassPnpStartDevice: ClassGetDescriptor [DevicePower] failed %lx\n", status)); + + // + // Ignore error as device power property is optional + // + status = STATUS_SUCCESS; + } + } + } + + // + // ISSUE - drivers need to disable write caching on the media + // if hotplug and !useroverride. perhaps we should + // allow registration of a callback to enable/disable + // write cache instead. + // + + if (NT_SUCCESS(status)){ + status = devInfo->ClassInitDevice(DeviceObject); + } + + if (commonExtension->IsFdo) { + fdoExtension->PrivateFdoData->Perf.OriginalSrbFlags = fdoExtension->SrbFlags; + + // + // initialization for disk device + // + if ((DeviceObject->DeviceType == FILE_DEVICE_DISK) && + (!TEST_FLAG(DeviceObject->Characteristics, FILE_FLOPPY_DISKETTE))) { + + ULONG accessAlignmentNotSupported = 0; + ULONG qerrOverrideMode = QERR_SET_ZERO_ODX_OR_TP_ONLY; + ULONG legacyErrorHandling = FALSE; + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, + "ClassPnpStartDevice: Enabling idle timer for %p\n", DeviceObject)); + // Initialize idle timer for disk devices + ClasspInitializeIdleTimer(fdoExtension); + + if (ClasspIsObsoletePortDriver(fdoExtension) == FALSE) { + // get INQUIRY VPD support information. It's safe to send command as everything is ready in ClassInitDevice(). + ClasspGetInquiryVpdSupportInfo(fdoExtension); + + // Query and cache away Logical Block Provisioning info in the FDO extension. + // The cached information will be used in responding to some IOCTLs + ClasspGetLBProvisioningInfo(fdoExtension); + + // + // Query and cache away Block Device ROD Limits info in the FDO extension. + // + if (fdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits) { + ClassDetermineTokenOperationCommandSupport(DeviceObject); + } + + // + // See if the user has specified a particular QERR override + // mode. "Override" meaning setting QERR = 0 via Mode Select. + // 0 = Only when ODX or Thin Provisioning are supported (default) + // 1 = Always + // 2 = Never (or any value >= 2) + // + ClassGetDeviceParameter(fdoExtension, + CLASSP_REG_SUBKEY_NAME, + CLASSP_REG_QERR_OVERRIDE_MODE, + &qerrOverrideMode); + + // + // If this device is thinly provisioned or supports ODX, we + // may need to force QERR to zero. The user may have also + // specified that we should always or never do this. + // + if (qerrOverrideMode == QERR_SET_ZERO_ALWAYS || + (qerrOverrideMode == QERR_SET_ZERO_ODX_OR_TP_ONLY && + (ClasspIsThinProvisioned(fdoExtension->FunctionSupportInfo) || + NT_SUCCESS(ClasspValidateOffloadSupported(DeviceObject, NULL))))) { + + ClasspZeroQERR(DeviceObject); + } + + } else { + + // + // Since this device has been exposed by a legacy miniport (e.g. SCSIPort miniport) + // set its LB Provisioning command status to an error status that will be surfaced + // up to the caller of a TRIM/Unmap command. + // + fdoExtension->FunctionSupportInfo->LBProvisioningData.CommandStatus = STATUS_UNSUCCESSFUL; + fdoExtension->FunctionSupportInfo->BlockLimitsData.CommandStatus = STATUS_UNSUCCESSFUL; + fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus = STATUS_UNSUCCESSFUL; + } + + // Get registry setting of failing the IOCTL for AccessAlignment Property. + ClassGetDeviceParameter(fdoExtension, + CLASSP_REG_SUBKEY_NAME, + CLASSP_REG_ACCESS_ALIGNMENT_NOT_SUPPORTED, + &accessAlignmentNotSupported); + + if (accessAlignmentNotSupported > 0) { + fdoExtension->FunctionSupportInfo->RegAccessAlignmentQueryNotSupported = TRUE; + } + +#if (NTDDI_VERSION >= NTDDI_WINBLUE) + + + // + // See if the user has specified legacy error handling. + // + ClassGetDeviceParameter(fdoExtension, + CLASSP_REG_SUBKEY_NAME, + CLASSP_REG_LEGACY_ERROR_HANDLING, + &legacyErrorHandling); + + if (legacyErrorHandling) { + // + // Legacy error handling means that the maximum number of + // retries allowd for an IO request is 8 instead of 4. + // + fdoExtension->PrivateFdoData->MaxNumberOfIoRetries = LEGACY_NUM_IO_RETRIES; + fdoExtension->PrivateFdoData->LegacyErrorHandling = TRUE; + } +#else + UNREFERENCED_PARAMETER(legacyErrorHandling); +#endif + + } + + } + } + + if (!NT_SUCCESS(status)){ + + // + // Just bail out - the remove that comes down will clean up the + // initialized scraps. + // + + return status; + } else { + commonExtension->IsInitialized = TRUE; + } + + // + // If device requests autorun functionality or a once a second callback + // then enable the once per second timer. Exception is if media change + // detection is desired but device supports async notification. + // + // NOTE: This assumes that ClassInitializeMediaChangeDetection is always + // called in the context of the ClassInitDevice callback. If called + // after then this check will have already been made and the + // once a second timer will not have been enabled. + // + if ((isFdo) && + ((initData->ClassTick != NULL) || + ((fdoExtension->MediaChangeDetectionInfo != NULL) && + (fdoExtension->FunctionSupportInfo != NULL) && + (fdoExtension->FunctionSupportInfo->AsynchronousNotificationSupported == FALSE)) || + ((fdoExtension->FailurePredictionInfo != NULL) && + (fdoExtension->FailurePredictionInfo->Method != FailurePredictionNone)))) + { + ClasspEnableTimer(fdoExtension); + + // + // In addition, we may change our polling behavior when the screen is + // off so register for screen state notification if we haven't already + // done so. + // + if (ScreenStateNotificationHandle == NULL) { + PoRegisterPowerSettingCallback(DeviceObject, + &GUID_CONSOLE_DISPLAY_STATE, + &ClasspPowerSettingCallback, + NULL, + &ScreenStateNotificationHandle); + } + } + + // + // NOTE: the timer looks at commonExtension->CurrentState now + // to prevent Media Change Notification code from running + // until the device is started, but allows the device + // specific tick handler to run. therefore it is imperative + // that commonExtension->CurrentState not be updated until + // the device specific startdevice handler has finished. + // + + status = devInfo->ClassStartDevice(DeviceObject); + + if (NT_SUCCESS(status)){ + commonExtension->CurrentState = IRP_MN_START_DEVICE; + + if((isFdo) && (initData->ClassEnumerateDevice != NULL)) { + isMountedDevice = FALSE; + } + + if (DeviceObject->DeviceType != FILE_DEVICE_CD_ROM) { + + isMountedDevice = FALSE; + } + + // + // Register for mounted device interface if this is a + // sfloppy device. + // + if ((DeviceObject->DeviceType == FILE_DEVICE_DISK) && + (TEST_FLAG(DeviceObject->Characteristics, FILE_FLOPPY_DISKETTE))) { + + isMountedDevice = TRUE; + } + + if(isMountedDevice) { + ClasspRegisterMountedDeviceInterface(DeviceObject); + } + + if(commonExtension->IsFdo) { + IoWMIRegistrationControl(DeviceObject, WMIREG_ACTION_REGISTER); + + // + // Tell Storport (Usbstor or SD) to enable idle power management for this + // device, assuming the user hasn't turned it off in the registry. + // + if (fdoExtension->FunctionSupportInfo != NULL && + fdoExtension->FunctionSupportInfo->IdlePower.IdlePowerEnabled == FALSE && + fdoExtension->MiniportDescriptor != NULL && + (fdoExtension->MiniportDescriptor->Portdriver == StoragePortCodeSetStorport || + fdoExtension->MiniportDescriptor->Portdriver == StoragePortCodeSetSDport || + fdoExtension->MiniportDescriptor->Portdriver == StoragePortCodeSetUSBport)) { + ULONG disableIdlePower= 0; + ClassGetDeviceParameter(fdoExtension, + CLASSP_REG_SUBKEY_NAME, + CLASSP_REG_DISBALE_IDLE_POWER_NAME, + &disableIdlePower); + + if (!disableIdlePower) { + ClasspEnableIdlePower(DeviceObject); + } + } + } + } + else { + ClasspDisableTimer(fdoExtension); + } + + + return status; +} + + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassReadWrite() + +Routine Description: + + This is the system entry point for read and write requests. The + device-specific handler is invoked to perform any validation necessary. + + If the device object is a PDO (partition object) then the request will + simply be adjusted for Partition0 and issued to the lower device driver. + + IF the device object is an FDO (paritition 0 object), the number of bytes + in the request are checked against the maximum byte counts that the adapter + supports and requests are broken up into + smaller sizes if necessary. + +Arguments: + + DeviceObject - a pointer to the device object for this request + + Irp - IO request + +Return Value: + + NT Status + +--*/ +NTSTATUS ClassReadWrite(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) +{ + PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; + PDEVICE_OBJECT lowerDeviceObject = commonExtension->LowerDeviceObject; + PIO_STACK_LOCATION currentIrpStack = IoGetCurrentIrpStackLocation(Irp); + ULONG transferByteCount = currentIrpStack->Parameters.Read.Length; + ULONG isRemoved; + NTSTATUS status; + + /* + * Grab the remove lock. If we can't acquire it, bail out. + */ + isRemoved = ClassAcquireRemoveLock(DeviceObject, Irp); + if (isRemoved) { + Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST; + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + status = STATUS_DEVICE_DOES_NOT_EXIST; + } + else if (TEST_FLAG(DeviceObject->Flags, DO_VERIFY_VOLUME) && + (currentIrpStack->MinorFunction != CLASSP_VOLUME_VERIFY_CHECKED) && + !TEST_FLAG(currentIrpStack->Flags, SL_OVERRIDE_VERIFY_VOLUME)){ + + /* + * DO_VERIFY_VOLUME is set for the device object, + * but this request is not itself a verify request. + * So fail this request. + */ + if (Irp->Tail.Overlay.Thread != NULL) { + IoSetHardErrorOrVerifyDevice(Irp, DeviceObject); + } + Irp->IoStatus.Status = STATUS_VERIFY_REQUIRED; + Irp->IoStatus.Information = 0; + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, 0); + status = STATUS_VERIFY_REQUIRED; + } + else { + + /* + * Since we've bypassed the verify-required tests we don't need to repeat + * them with this IRP - in particular we don't want to worry about + * hitting them at the partition 0 level if the request has come through + * a non-zero partition. + */ + currentIrpStack->MinorFunction = CLASSP_VOLUME_VERIFY_CHECKED; + + /* + * Call the miniport driver's pre-pass filter to check if we + * should continue with this transfer. + */ + NT_ASSERT(commonExtension->DevInfo->ClassReadWriteVerification); + status = commonExtension->DevInfo->ClassReadWriteVerification(DeviceObject, Irp); + // Code Analysis cannot analyze the code paths specific to clients. + _Analysis_assume_(status != STATUS_PENDING); + if (!NT_SUCCESS(status)){ + NT_ASSERT(Irp->IoStatus.Status == status); + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest (DeviceObject, Irp, IO_NO_INCREMENT); + } + else if (status == STATUS_PENDING){ + /* + * ClassReadWriteVerification queued this request. + * So don't touch the irp anymore. + */ + } + else { + + if (transferByteCount == 0) { + /* + * Several parts of the code turn 0 into 0xffffffff, + * so don't process a zero-length request any further. + */ + Irp->IoStatus.Status = STATUS_SUCCESS; + Irp->IoStatus.Information = 0; + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + status = STATUS_SUCCESS; + } + else { + /* + * If the driver has its own StartIo routine, call it. + */ + if (commonExtension->DriverExtension->InitData.ClassStartIo) { + IoMarkIrpPending(Irp); + IoStartPacket(DeviceObject, Irp, NULL, NULL); + status = STATUS_PENDING; + } + else { + /* + * The driver does not have its own StartIo routine. + * So process this request ourselves. + */ + + /* + * Add partition byte offset to make starting byte relative to + * beginning of disk. + */ + currentIrpStack->Parameters.Read.ByteOffset.QuadPart += + commonExtension->StartingOffset.QuadPart; + + if (commonExtension->IsFdo){ + + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension; + PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData; + + /* + * Add in any skew for the disk manager software. + */ + currentIrpStack->Parameters.Read.ByteOffset.QuadPart += + commonExtension->PartitionZeroExtension->DMByteSkew; + + // + // In case DEV_USE_16BYTE_CDB flag is not set, R/W request will be translated into READ/WRITE 10 SCSI command. + // These SCSI commands have 4 bytes in "Starting LBA" field. + // Requests cannot be represented in these SCSI commands should be failed. + // + if (!TEST_FLAG(fdoExtension->DeviceFlags, DEV_USE_16BYTE_CDB)) { + LARGE_INTEGER startingLba; + + startingLba.QuadPart = currentIrpStack->Parameters.Read.ByteOffset.QuadPart >> fdoExtension->SectorShift; + + if (startingLba.QuadPart > MAXULONG) { + Irp->IoStatus.Status = STATUS_INVALID_PARAMETER; + Irp->IoStatus.Information = 0; + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + status = STATUS_INVALID_PARAMETER; + goto Exit; + } + } + +#if DBG + // + // Record the caller if: + // 1. the disk is currently off + // 2. the operation is a read, (likely resulting in disk spinnage) + // 3. the operation is marked WT (likely resulting in disk spinnage) + // + if((fdoExtension->DevicePowerState == PowerDeviceD3) && // disk is off + ((currentIrpStack->MajorFunction == IRP_MJ_READ) || // It's a read. + (TEST_FLAG(currentIrpStack->Flags, SL_WRITE_THROUGH))) ) { // they *really* want it to go to disk. + + SnapDiskStartup(); + } +#endif + + /* + * Perform the actual transfer(s) on the hardware + * to service this request. + */ + if (ClasspIsIdleRequestSupported(fdoData, Irp)) { + ClasspMarkIrpAsIdle(Irp, TRUE); + status = ClasspEnqueueIdleRequest(DeviceObject, Irp); + } else { + UCHAR uniqueAddr = 0; + + // + // Since we're touching fdoData after servicing the transfer packet, this opens us up to + // a potential window, where the device may be removed between the time that + // ServiceTransferPacket completes and we've had a chance to access fdoData. In order + // to guard against this, we acquire the removelock an additional time here. This + // acquire is guaranteed to succeed otherwise we wouldn't be here (because of the + // outer acquire). + // The sequence of events we're guarding against with this remLock acquire is: + // 1. This UL IRP acquired the lock. + // 2. Device gets surprised removed, then gets IRP_MN_REMOVE_DEVICE; ClassRemoveDevice + // waits for the above RemoveLock. + // 3. ServiceTransferRequest breaks the UL IRP into DL IRPs. + // 4. DL IRPs complete with STATUS_NO_SUCH_DEVICE and TransferPktComplete completes the UL + // IRP with STATUS_NO_SUCH_DEVICE; releases the RemoveLock. + // 5. ClassRemoveDevice is now unblocked, continues running and frees resources (including + // fdoData). + // 6. Finally ClassReadWrite gets to run again and accesses a freed fdoData when trying to + // check/update idle-related fields. + // + ClassAcquireRemoveLock(DeviceObject, (PVOID)&uniqueAddr); + + ClasspMarkIrpAsIdle(Irp, FALSE); + status = ServiceTransferRequest(DeviceObject, Irp, FALSE); + if (fdoData->IdlePrioritySupported == TRUE) { + fdoData->LastIoTime = ClasspGetCurrentTime(NULL); + fdoData->IdleTicks = 0; + } + + ClassReleaseRemoveLock(DeviceObject, (PVOID)&uniqueAddr); + } + } + else { + /* + * This is a child PDO enumerated for our FDO by e.g. disk.sys + * and owned by e.g. partmgr. Send it down to the next device + * and the same irp will come back to us for the FDO. + */ + IoCopyCurrentIrpStackLocationToNext(Irp); + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(lowerDeviceObject, Irp); + } + } + } + } + } + +Exit: + return status; +} + + +VOID InterpretCapacityData(PDEVICE_OBJECT Fdo, PREAD_CAPACITY_DATA_EX ReadCapacityData) +{ + PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Fdo->DeviceExtension; + ULONG cylinderSize; + ULONG bytesPerSector; + LARGE_INTEGER lastSector; + LARGE_INTEGER largeInt; + + bytesPerSector = ClasspCalculateLogicalSectorSize(Fdo, ReadCapacityData->BytesPerBlock); + + fdoExt->DiskGeometry.BytesPerSector = bytesPerSector; + WHICH_BIT(fdoExt->DiskGeometry.BytesPerSector, fdoExt->SectorShift); + + /* + * LogicalBlockAddress is the last sector of the logical drive, in big-endian. + * It tells us the size of the drive (#sectors is lastSector+1). + */ + + largeInt = ReadCapacityData->LogicalBlockAddress; + REVERSE_BYTES_QUAD(&lastSector, &largeInt); + + if (fdoExt->DMActive){ + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, "ClassReadDriveCapacity: reducing number of sectors by %d\n", fdoExt->DMSkew)); + lastSector.QuadPart -= fdoExt->DMSkew; + } + + /* + * Check to see if we have a geometry we should be using already. + * If not, we set part of the disk geometry to garbage values that will be filled in by the caller (e.g. disk.sys). + * + * So the first call to ClassReadDriveCapacity always sets a meaningless geometry. + * TracksPerCylinder and SectorsPerTrack are kind of meaningless anyway wrt I/O, + * because I/O is always targeted to a logical sector number. + * All that really matters is BytesPerSector and the number of sectors. + */ + cylinderSize = fdoExt->DiskGeometry.TracksPerCylinder * fdoExt->DiskGeometry.SectorsPerTrack; + if (cylinderSize == 0){ + fdoExt->DiskGeometry.TracksPerCylinder = 0xff; + fdoExt->DiskGeometry.SectorsPerTrack = 0x3f; + cylinderSize = fdoExt->DiskGeometry.TracksPerCylinder * fdoExt->DiskGeometry.SectorsPerTrack; + } + + /* + * Calculate number of cylinders. + * If there are zero cylinders, then the device lied AND it's + * smaller than 0xff*0x3f (about 16k sectors, usually 8 meg) + * this can fit into a single LONGLONG, so create another usable + * geometry, even if it's unusual looking. + * This allows small, non-standard devices, such as Sony's Memory Stick, to show up as having a partition. + */ + fdoExt->DiskGeometry.Cylinders.QuadPart = (LONGLONG)((lastSector.QuadPart + 1)/cylinderSize); + if (fdoExt->DiskGeometry.Cylinders.QuadPart == (LONGLONG)0) { + fdoExt->DiskGeometry.SectorsPerTrack = 1; + fdoExt->DiskGeometry.TracksPerCylinder = 1; + fdoExt->DiskGeometry.Cylinders.QuadPart = lastSector.QuadPart + 1; + } + + /* + * Calculate media capacity in bytes. + * For this purpose we treat the entire LUN as is if it is one partition. Disk will deal with actual partitioning. + */ + fdoExt->CommonExtension.PartitionLength.QuadPart = + ((LONGLONG)(lastSector.QuadPart + 1)) << fdoExt->SectorShift; + + /* + * Is this removable or fixed media + */ + if (TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA)){ + fdoExt->DiskGeometry.MediaType = RemovableMedia; + } + else { + fdoExt->DiskGeometry.MediaType = FixedMedia; + } + +} + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassReadDriveCapacity() + +Routine Description: + + This routine sends a READ CAPACITY to the requested device, updates + the geometry information in the device object and returns + when it is complete. This routine is synchronous. + + This routine must be called with the remove lock held or some other + assurance that the Fdo will not be removed while processing. + +Arguments: + + DeviceObject - Supplies a pointer to the device object that represents + the device whose capacity is to be read. + +Return Value: + + Status is returned. + +--*/ +_Must_inspect_result_ +NTSTATUS ClassReadDriveCapacity(_In_ PDEVICE_OBJECT Fdo) +{ + PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Fdo->DeviceExtension; + PCOMMON_DEVICE_EXTENSION commonExtension = Fdo->DeviceExtension; + PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData; + READ_CAPACITY_DATA_EX PTRALIGN readCapacityData = {0}; + PTRANSFER_PACKET pkt; + NTSTATUS status; + PMDL driveCapMdl = NULL; + KEVENT event; + IRP pseudoIrp = {0}; + ULONG readCapacityDataSize; + BOOLEAN use16ByteCdb; + BOOLEAN match = TRUE; + + use16ByteCdb = TEST_FLAG(fdoExt->DeviceFlags, DEV_USE_16BYTE_CDB); + +RetryRequest: + + if (use16ByteCdb) { + readCapacityDataSize = sizeof(READ_CAPACITY_DATA_EX); + } else { + readCapacityDataSize = sizeof(READ_CAPACITY_DATA); + } + + if (driveCapMdl != NULL) { + FreeDeviceInputMdl(driveCapMdl); + driveCapMdl = NULL; + } + + // + // Allocate the MDL based on the Read Capacity command. + // + driveCapMdl = BuildDeviceInputMdl(&readCapacityData, readCapacityDataSize); + if (driveCapMdl == NULL) { + status = STATUS_INSUFFICIENT_RESOURCES; + goto SafeExit; + } + + pkt = DequeueFreeTransferPacket(Fdo, TRUE); + if (pkt == NULL) { + status = STATUS_INSUFFICIENT_RESOURCES; + goto SafeExit; + } + + // + // Our engine needs an "original irp" to write the status back to + // and to count down packets (one in this case). + // Just use a pretend irp for this. + // + + pseudoIrp.Tail.Overlay.DriverContext[0] = LongToPtr(1); + pseudoIrp.IoStatus.Status = STATUS_SUCCESS; + pseudoIrp.IoStatus.Information = 0; + pseudoIrp.MdlAddress = driveCapMdl; + + // + // Set this up as a SYNCHRONOUS transfer, submit it, + // and wait for the packet to complete. The result + // status will be written to the original irp. + // + + KeInitializeEvent(&event, SynchronizationEvent, FALSE); + SetupDriveCapacityTransferPacket(pkt, + &readCapacityData, + readCapacityDataSize, + &event, + &pseudoIrp, + use16ByteCdb); + SubmitTransferPacket(pkt); + (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); + + status = pseudoIrp.IoStatus.Status; + + // + // If we got an UNDERRUN, retry exactly once. + // (The transfer_packet engine didn't retry because the result + // status was success). + // + + if (NT_SUCCESS(status) && + (pseudoIrp.IoStatus.Information < readCapacityDataSize)) { + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClassReadDriveCapacity: read len (%xh) < %xh, retrying ...", + (ULONG)pseudoIrp.IoStatus.Information, readCapacityDataSize)); + + pkt = DequeueFreeTransferPacket(Fdo, TRUE); + if (pkt) { + pseudoIrp.Tail.Overlay.DriverContext[0] = LongToPtr(1); + pseudoIrp.IoStatus.Status = STATUS_SUCCESS; + pseudoIrp.IoStatus.Information = 0; + KeInitializeEvent(&event, SynchronizationEvent, FALSE); + SetupDriveCapacityTransferPacket(pkt, + &readCapacityData, + readCapacityDataSize, + &event, + &pseudoIrp, + use16ByteCdb); + SubmitTransferPacket(pkt); + (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); + status = pseudoIrp.IoStatus.Status; + if (pseudoIrp.IoStatus.Information < readCapacityDataSize){ + status = STATUS_DEVICE_BUSY; + } + } else { + status = STATUS_INSUFFICIENT_RESOURCES; + } + } + + if (NT_SUCCESS(status)) { + // + // The request succeeded. Check for 8 byte LBA support. + // + + if (use16ByteCdb == FALSE) { + + PREAD_CAPACITY_DATA readCapacity; + + // + // Check whether the device supports 8 byte LBA. If the device supports + // it then retry the request using 16 byte CDB. + // + + readCapacity = (PREAD_CAPACITY_DATA) &readCapacityData; + + if (readCapacity->LogicalBlockAddress == 0xFFFFFFFF) { + // + // Device returned max size for last LBA. Need to send + // 16 byte request to get the size. + // + use16ByteCdb = TRUE; + goto RetryRequest; + + } else { + // + // Convert the 4 byte LBA (READ_CAPACITY_DATA) to 8 byte LBA (READ_CAPACITY_DATA_EX) + // format for ease of use. This is the only format stored in the device extension. + // + + RtlMoveMemory((PUCHAR)(&readCapacityData) + sizeof(ULONG), readCapacity, sizeof(READ_CAPACITY_DATA)); + RtlZeroMemory((PUCHAR)(&readCapacityData), sizeof(ULONG)); + + } + } else { + // + // Device completed 16 byte command successfully, it supports 8-byte LBA. + // + + SET_FLAG(fdoExt->DeviceFlags, DEV_USE_16BYTE_CDB); + } + + // + // Read out and store the drive information. + // + + InterpretCapacityData(Fdo, &readCapacityData); + + // + // Before caching the new drive capacity, compare it with the + // cached capacity for any change. + // + + if (fdoData->IsCachedDriveCapDataValid == TRUE) { + + match = (BOOLEAN) RtlEqualMemory(&fdoData->LastKnownDriveCapacityData, + &readCapacityData, sizeof(READ_CAPACITY_DATA_EX)); + } + + // + // Store the readCapacityData in private FDO data. + // This is so that runtime memory failures don't cause disk.sys to put + // the paging disk in an error state. Also this is used in + // IOCTL_STORAGE_READ_CAPACITY. + // + fdoData->LastKnownDriveCapacityData = readCapacityData; + fdoData->IsCachedDriveCapDataValid = TRUE; + + if (match == FALSE) { + if (commonExtension->CurrentState != IRP_MN_START_DEVICE) + { + // + // This can happen if a disk reports Parameters Changed / Capacity Data Changed sense data. + // NT_ASSERT(!"Drive capacity has changed while the device wasn't started!"); + // + } else { + // + // state of (commonExtension->CurrentState == IRP_MN_START_DEVICE) indicates that the device has been started. + // UpdateDiskPropertiesWorkItemActive is used as a flag to ensure we only have one work item updating the disk + // properties at a time. + // + if (InterlockedCompareExchange((volatile LONG *)&fdoData->UpdateDiskPropertiesWorkItemActive, 1, 0) == 0) + { + PIO_WORKITEM workItem; + + workItem = IoAllocateWorkItem(Fdo); + + if (workItem) { + // + // The disk capacity has changed, send notification to the disk driver. + // Start a work item to notify the disk class driver asynchronously. + // + IoQueueWorkItem(workItem, ClasspUpdateDiskProperties, DelayedWorkQueue, workItem); + } else { + InterlockedExchange((volatile LONG *)&fdoData->UpdateDiskPropertiesWorkItemActive, 0); + } + } + } + } + + } else { + // + // The request failed. + // + + // + // ISSUE - 2000/02/04 - henrygab - non-512-byte sector sizes and failed geometry update + // what happens when the disk's sector size is bigger than + // 512 bytes and we hit this code path? this is untested. + // + // If the read capacity fails, set the geometry to reasonable parameter + // so things don't fail at unexpected places. Zero the geometry + // except for the bytes per sector and sector shift. + // + + // + // This request can sometimes fail legitimately + // (e.g. when a SCSI device is attached but turned off) + // so this is not necessarily a device/driver bug. + // + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, "ClassReadDriveCapacity on Fdo %p failed with status %ul.", Fdo, status)); + + // + // Write in a default disk geometry which we HOPE is right (??). + // + + RtlZeroMemory(&fdoExt->DiskGeometry, sizeof(DISK_GEOMETRY)); + fdoExt->DiskGeometry.BytesPerSector = 512; + fdoExt->SectorShift = 9; + fdoExt->CommonExtension.PartitionLength.QuadPart = (LONGLONG) 0; + + // + // Is this removable or fixed media + // + + if (TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA)){ + fdoExt->DiskGeometry.MediaType = RemovableMedia; + } else { + fdoExt->DiskGeometry.MediaType = FixedMedia; + } + } + +SafeExit: + + + // + // In case DEV_USE_16BYTE_CDB flag is not set, classpnp translates R/W request into READ/WRITE 10 SCSI command. + // These SCSI commands have 2 bytes in "Transfer Blocks" field. + // Make sure this max length (0xFFFF * sector size) is respected during request split. + // + if (!TEST_FLAG(fdoExt->DeviceFlags, DEV_USE_16BYTE_CDB)) { + ULONG cdb10MaxBlocks = ((ULONG)USHORT_MAX) << fdoExt->SectorShift; + + fdoData->HwMaxXferLen = min(cdb10MaxBlocks, fdoData->HwMaxXferLen); + } + + if (driveCapMdl != NULL) { + FreeDeviceInputMdl(driveCapMdl); + } + + // + // If the request failed for some reason then invalidate the cached + // capacity data for removable devices. So that we won't return + // wrong capacity in IOCTL_STORAGE_READ_CAPACITY + // + + if (!NT_SUCCESS(status) && (fdoExt->DiskGeometry.MediaType == RemovableMedia)) { + fdoData->IsCachedDriveCapDataValid = FALSE; + } + + // + // Don't let memory failures (either here or in the port driver) in the ReadDriveCapacity call + // put the paging disk in an error state such that paging fails. + // Return the last known drive capacity (which may possibly be slightly out of date, even on + // fixed media, e.g. for storage cabinets that can grow a logical disk). + // + if ((status == STATUS_INSUFFICIENT_RESOURCES) && + (fdoData->IsCachedDriveCapDataValid) && + (fdoExt->DiskGeometry.MediaType == FixedMedia)) { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, "ClassReadDriveCapacity: defaulting to cached DriveCapacity data")); + InterpretCapacityData(Fdo, &fdoData->LastKnownDriveCapacityData); + status = STATUS_SUCCESS; + } + + return status; +} + + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassSendStartUnit() + +Routine Description: + + Send command to SCSI unit to start or power up. + Because this command is issued asynchronounsly, that is, without + waiting on it to complete, the IMMEDIATE flag is not set. This + means that the CDB will not return until the drive has powered up. + This should keep subsequent requests from being submitted to the + device before it has completely spun up. + + This routine is called from the InterpretSense routine, when a + request sense returns data indicating that a drive must be + powered up. + + This routine may also be called from a class driver's error handler, + or anytime a non-critical start device should be sent to the device. + +Arguments: + + Fdo - The functional device object for the stopped device. + +Return Value: + + None. + +--*/ +VOID +ClassSendStartUnit( + _In_ PDEVICE_OBJECT Fdo + ) +{ + PIO_STACK_LOCATION irpStack; + PIRP irp; + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension; + PSCSI_REQUEST_BLOCK srb; + PCOMPLETION_CONTEXT context; + PCDB cdb; + NTSTATUS status; + PSTORAGE_REQUEST_BLOCK srbEx; + + // + // Allocate Srb from nonpaged pool. + // + + context = ExAllocatePoolWithTag(NonPagedPoolNx, + sizeof(COMPLETION_CONTEXT), + '6CcS'); + + if (context == NULL) { + + // + // ISSUE-2000/02/03-peterwie + // This code path was inheritted from the NT 4.0 class2.sys driver. + // It needs to be changed to survive low-memory conditions. + // + + KeBugCheck(SCSI_DISK_DRIVER_INTERNAL); + } + + // + // Save the device object in the context for use by the completion + // routine. + // + + context->DeviceObject = Fdo; + + srb = &context->Srb.Srb; + if (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) { + srbEx = &context->Srb.SrbEx; + status = InitializeStorageRequestBlock(srbEx, + STORAGE_ADDRESS_TYPE_BTL8, + sizeof(context->Srb.SrbExBuffer), + 1, + SrbExDataTypeScsiCdb16); + if (!NT_SUCCESS(status)) { + FREE_POOL(context); + NT_ASSERT(FALSE); + return; + } + + srbEx->SrbFunction = SRB_FUNCTION_EXECUTE_SCSI; + + } else { + + // + // Zero out srb. + // + + RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK)); + + // + // Write length to SRB. + // + + srb->Length = sizeof(SCSI_REQUEST_BLOCK); + + srb->Function = SRB_FUNCTION_EXECUTE_SCSI; + } + + // + // Set timeout value large enough for drive to spin up. + // + + SrbSetTimeOutValue(srb, START_UNIT_TIMEOUT); + + // + // Set the transfer length. + // + + SrbAssignSrbFlags(srb, + (SRB_FLAGS_NO_DATA_TRANSFER | + SRB_FLAGS_DISABLE_AUTOSENSE | + SRB_FLAGS_DISABLE_SYNCH_TRANSFER)); + + // + // Build the start unit CDB. + // + + SrbSetCdbLength(srb, 6); + cdb = SrbGetCdb(srb); + + cdb->START_STOP.OperationCode = SCSIOP_START_STOP_UNIT; + cdb->START_STOP.Start = 1; + cdb->START_STOP.Immediate = 0; + cdb->START_STOP.LogicalUnitNumber = srb->Lun; + + // + // Build the asynchronous request to be sent to the port driver. + // Since this routine is called from a DPC the IRP should always be + // available. + // + + irp = IoAllocateIrp(Fdo->StackSize, FALSE); + + if (irp == NULL) { + + // + // ISSUE-2000/02/03-peterwie + // This code path was inheritted from the NT 4.0 class2.sys driver. + // It needs to be changed to survive low-memory conditions. + // + + KeBugCheck(SCSI_DISK_DRIVER_INTERNAL); + + } + + ClassAcquireRemoveLock(Fdo, irp); + + IoSetCompletionRoutine(irp, + (PIO_COMPLETION_ROUTINE)ClassAsynchronousCompletion, + context, + TRUE, + TRUE, + TRUE); + + irpStack = IoGetNextIrpStackLocation(irp); + irpStack->MajorFunction = IRP_MJ_SCSI; + SrbSetOriginalRequest(srb, irp); + + // + // Store the SRB address in next stack for port driver. + // + + irpStack->Parameters.Scsi.Srb = srb; + + // + // Call the port driver with the IRP. + // + + IoCallDriver(fdoExtension->CommonExtension.LowerDeviceObject, irp); + + return; + +} // end StartUnit() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassAsynchronousCompletion() ISSUE-2000/02/18-henrygab - why public?! + +Routine Description: + + This routine is called when an asynchronous I/O request + which was issused by the class driver completes. Examples of such requests + are release queue or START UNIT. This routine releases the queue if + necessary. It then frees the context and the IRP. + +Arguments: + + DeviceObject - The device object for the logical unit; however since this + is the top stack location the value is NULL. + + Irp - Supplies a pointer to the Irp to be processed. + + Context - Supplies the context to be used to process this request. + +Return Value: + + None. + +--*/ +NTSTATUS +ClassAsynchronousCompletion( + PDEVICE_OBJECT DeviceObject, + PIRP Irp, + PVOID Context + ) +{ + PCOMPLETION_CONTEXT context = Context; + PSCSI_REQUEST_BLOCK srb; + ULONG srbFunction; + ULONG srbFlags; + + if (context == NULL) { + return STATUS_INVALID_PARAMETER; + } + + if (DeviceObject == NULL) { + + DeviceObject = context->DeviceObject; + } + + srb = &context->Srb.Srb; + + if (srb->Function == SRB_FUNCTION_STORAGE_REQUEST_BLOCK) { + srbFunction = ((PSTORAGE_REQUEST_BLOCK)srb)->SrbFunction; + srbFlags = ((PSTORAGE_REQUEST_BLOCK)srb)->SrbFlags; + } else { + srbFunction = srb->Function; + srbFlags = srb->SrbFlags; + } + + // + // If this is an execute srb, then check the return status and make sure. + // the queue is not frozen. + // + + if (srbFunction == SRB_FUNCTION_EXECUTE_SCSI) { + + // + // Check for a frozen queue. + // + + if (srb->SrbStatus & SRB_STATUS_QUEUE_FROZEN) { + + // + // Unfreeze the queue getting the device object from the context. + // + + ClassReleaseQueue(context->DeviceObject); + } + } + + { // free port-allocated sense buffer if we can detect + + if (((PCOMMON_DEVICE_EXTENSION)(DeviceObject->DeviceExtension))->IsFdo) { + + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension; + if (PORT_ALLOCATED_SENSE(fdoExtension, srb)) { + FREE_PORT_ALLOCATED_SENSE_BUFFER(fdoExtension, srb); + } + + } else { + + NT_ASSERT(!TEST_FLAG(srbFlags, SRB_FLAGS_FREE_SENSE_BUFFER)); + + } + } + + + // + // Free the context and the Irp. + // + + if (Irp->MdlAddress != NULL) { + MmUnlockPages(Irp->MdlAddress); + IoFreeMdl(Irp->MdlAddress); + + Irp->MdlAddress = NULL; + } + + ClassReleaseRemoveLock(DeviceObject, Irp); + + FREE_POOL(context); + + IoFreeIrp(Irp); + + // + // Indicate the I/O system should stop processing the Irp completion. + // + + return STATUS_MORE_PROCESSING_REQUIRED; + +} // end ClassAsynchronousCompletion() + + +NTSTATUS +ServiceTransferRequest( + PDEVICE_OBJECT Fdo, + PIRP Irp, + BOOLEAN PostToDpc + ) + +/*++ + +Routine description: + + This routine processes Io requests, splitting them if they + are larger than what the hardware can handle at a time. If + there isn't enough memory available, the request is placed + in a queue, to be processed at a later time + + If this is a high priority paging request, all regular Io + are throttled to provide Mm with better thoroughput + +Arguments: + + Fdo - The functional device object processing the request + Irp - The Io request to be processed + PostToDpc - Flag that indicates that this IRP must be posted to a DPC + +Return Value: + + STATUS_SUCCESS if successful, an error code otherwise + +--*/ + +{ + PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Fdo->DeviceExtension; + PCOMMON_DEVICE_EXTENSION commonExtension = Fdo->DeviceExtension; + PSTORAGE_ADAPTER_DESCRIPTOR adapterDesc = commonExtension->PartitionZeroExtension->AdapterDescriptor; + PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData; + IO_PAGING_PRIORITY priority = (TEST_FLAG(Irp->Flags, IRP_PAGING_IO)) ? IoGetPagingIoPriority(Irp) : IoPagingPriorityInvalid; + BOOLEAN deferClientIrp = FALSE; + BOOLEAN driverUsesStartIO = (commonExtension->DriverExtension->InitData.ClassStartIo != NULL); + KIRQL oldIrql; + NTSTATUS status = STATUS_UNSUCCESSFUL; + PIO_STACK_LOCATION currentIrpStack = IoGetCurrentIrpStackLocation(Irp); + + /* + * Initialize IRP status for the master IRP to + * - STATUS_FT_READ_FROM_COPY if it's a copy-specific read + * - STATUS_SUCCESS otherwise. + * + * This is required. When Classpnp determines the status for the master IRP + * when completing child IRPs, the call to IoSetMasterIrpStatus + * will be functioning properly (See TransferPktComplete function) + * + * Note: + * If the IRP is a copy-specific read, File System already initialized the IRP status + * to be STATUS_FT_READ_FROM_COPY. However, this can be changed when the IRP arrives + * at Classpnp. It's possible that other drivers in the stack may initialize the + * IRP status field to other values before forwarding the IRP down the stack. + * To be defensive, we initialize the IRP status to either STATUS_FT_READ_FROM_COPY + * if it's a copy-specific read, or STATUS_SUCCESS otherwise. + */ + if (currentIrpStack->MajorFunction == IRP_MJ_READ && + TEST_FLAG(currentIrpStack->Flags, SL_KEY_SPECIFIED) && + IsKeyReadCopyNumber(currentIrpStack->Parameters.Read.Key)) { + Irp->IoStatus.Status = STATUS_FT_READ_FROM_COPY; + } else { + Irp->IoStatus.Status = STATUS_SUCCESS; + } + + // + // If this is a high priority request, hold off all other Io requests + // + + if (priority == IoPagingPriorityHigh) + { + KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql); + + if (fdoData->NumHighPriorityPagingIo == 0) + { + // + // Entering throttle mode + // + + KeQuerySystemTime(&fdoData->ThrottleStartTime); + } + + fdoData->NumHighPriorityPagingIo++; + fdoData->MaxInterleavedNormalIo += ClassMaxInterleavePerCriticalIo; + + KeReleaseSpinLock(&fdoData->SpinLock, oldIrql); + } + else + { + if (fdoData->NumHighPriorityPagingIo != 0) + { + // + // This request wasn't flagged as critical and atleast one critical request + // is currently outstanding. Queue this request until all of those are done + // but only if the interleave threshold has been reached + // + + KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql); + + if (fdoData->NumHighPriorityPagingIo != 0) + { + if (fdoData->MaxInterleavedNormalIo == 0) + { + deferClientIrp = TRUE; + } + else + { + fdoData->MaxInterleavedNormalIo--; + } + } + + KeReleaseSpinLock(&fdoData->SpinLock, oldIrql); + } + } + + if (!deferClientIrp) + { + PIO_STACK_LOCATION currentSp = IoGetCurrentIrpStackLocation(Irp); + ULONG entireXferLen = currentSp->Parameters.Read.Length; + PUCHAR bufPtr = MmGetMdlVirtualAddress(Irp->MdlAddress); + LARGE_INTEGER targetLocation = currentSp->Parameters.Read.ByteOffset; + PTRANSFER_PACKET pkt; + SINGLE_LIST_ENTRY pktList; + PSINGLE_LIST_ENTRY slistEntry; + ULONG hwMaxXferLen; + ULONG numPackets; + ULONG i; + + + /* + * We precomputed fdoData->HwMaxXferLen using (MaximumPhysicalPages-1). + * If the buffer is page-aligned, that's one less page crossing so we can add the page back in. + * Note: adapters that return MaximumPhysicalPages=0x10 depend on this to + * transfer aligned 64K requests in one piece. + * Also note: make sure adding PAGE_SIZE back in doesn't wrap to zero. + */ + if (((ULONG_PTR)bufPtr & (PAGE_SIZE-1)) || (fdoData->HwMaxXferLen > 0xffffffff-PAGE_SIZE)){ + hwMaxXferLen = fdoData->HwMaxXferLen; + } + else { + NT_ASSERT((PAGE_SIZE%fdoExt->DiskGeometry.BytesPerSector) == 0); + hwMaxXferLen = min(fdoData->HwMaxXferLen+PAGE_SIZE, adapterDesc->MaximumTransferLength); + } + + /* + * Compute the number of hw xfers we'll have to do. + * Calculate this without allowing for an overflow condition. + */ + NT_ASSERT(hwMaxXferLen >= PAGE_SIZE); + numPackets = entireXferLen/hwMaxXferLen; + if (entireXferLen % hwMaxXferLen){ + numPackets++; + } + + /* + * Use our 'simple' slist functions since we don't need interlocked. + */ + SimpleInitSlistHdr(&pktList); + + if (driverUsesStartIO) { + /* + * special case: StartIO-based writing must stay serialized, so just + * re-use one packet. + */ + pkt = DequeueFreeTransferPacket(Fdo, TRUE); + if (pkt) { + SimplePushSlist(&pktList, (PSINGLE_LIST_ENTRY)&pkt->SlistEntry); + i = 1; + } else { + i = 0; + } + } else { + /* + * First get all the TRANSFER_PACKETs that we'll need at once. + */ + for (i = 0; i < numPackets; i++){ + pkt = DequeueFreeTransferPacket(Fdo, TRUE); + if (pkt){ + SimplePushSlist(&pktList, (PSINGLE_LIST_ENTRY)&pkt->SlistEntry); + } + else { + break; + } + } + } + + + if ((i == numPackets) && + (!driverUsesStartIO)) { + NTSTATUS pktStat; + + /* + * The IoStatus.Information field will be incremented to the + * transfer length as the pieces complete. + */ + Irp->IoStatus.Information = 0; + + /* + * Store the number of transfer pieces inside the original IRP. + * It will be used to count down the pieces as they complete. + */ + Irp->Tail.Overlay.DriverContext[0] = LongToPtr(numPackets); + + /* + * For the common 1-packet case, we want to allow for an optimization by BlkCache + * (and also potentially synchronous storage drivers) which may complete the + * downward request synchronously. + * In that synchronous completion case, we want to _not_ mark the original irp pending + * and thereby save on the top-level APC. + * It's critical to coordinate this with the completion routine so that we mark the original irp + * pending if-and-only-if we return STATUS_PENDING for it. + */ + if (numPackets > 1){ + IoMarkIrpPending(Irp); + status = STATUS_PENDING; + } + else { + status = STATUS_SUCCESS; + } + + /* + * Transmit the pieces of the transfer. + */ + while (entireXferLen > 0){ + ULONG thisPieceLen = MIN(hwMaxXferLen, entireXferLen); + + /* + * Set up a TRANSFER_PACKET for this piece and send it. + */ + slistEntry = SimplePopSlist(&pktList); + NT_ASSERT(slistEntry); + pkt = CONTAINING_RECORD(slistEntry, TRANSFER_PACKET, SlistEntry); + SetupReadWriteTransferPacket( pkt, + bufPtr, + thisPieceLen, + targetLocation, + Irp); + + // + // If the IRP needs to be split, then we need to use a partial MDL. + // This prevents problems if the same MDL is mapped multiple times. + // + if (numPackets > 1) { + pkt->UsePartialMdl = TRUE; + } + + /* + * When an IRP is completed, the completion routine checks to see if there + * is a deferred IRP ready to sent down (assuming that there are no non-idle + * requests waiting to be serviced). If such a deferred IRP is available, it + * is sent down using this routine. However, if the lower driver completes + * the request inline, there is a potential for multiple deferred IRPs being + * sent down in the context of the same completion thread, thus exhausting + * the call stack. + * In order to prevent this from happening, we need to ensure that deferred + * IRPs that are dequeued in the context of a request's completion routine + * get posted to a DPC. + */ + if (PostToDpc) { + + pkt->RetryIn100nsUnits = 0; + TransferPacketQueueRetryDpc(pkt); + status = STATUS_PENDING; + + } else { + + pktStat = SubmitTransferPacket(pkt); + + /* + * If any of the packets completes with pending, we MUST return pending. + * Also, if a packet completes with an error, return pending; this is because + * in the completion routine we mark the original irp pending if the packet failed + * (since we may retry, thereby switching threads). + */ + if (pktStat != STATUS_SUCCESS){ + status = STATUS_PENDING; + } + } + + entireXferLen -= thisPieceLen; + bufPtr += thisPieceLen; + targetLocation.QuadPart += thisPieceLen; + } + NT_ASSERT(SimpleIsSlistEmpty(&pktList)); + } + else if (i >= 1){ + /* + * We were unable to get all the TRANSFER_PACKETs we need, + * but we did get at least one. + * That means that we are in extreme low-memory stress. + * We'll try doing this transfer using a single packet. + * The port driver is certainly also in stress, so use one-page + * transfers. + */ + + /* + * Free all but one of the TRANSFER_PACKETs. + */ + while (i-- > 1){ + slistEntry = SimplePopSlist(&pktList); + NT_ASSERT(slistEntry); + pkt = CONTAINING_RECORD(slistEntry, TRANSFER_PACKET, SlistEntry); + EnqueueFreeTransferPacket(Fdo, pkt); + } + + /* + * Get the single TRANSFER_PACKET that we'll be using. + */ + slistEntry = SimplePopSlist(&pktList); + NT_ASSERT(slistEntry); + NT_ASSERT(SimpleIsSlistEmpty(&pktList)); + pkt = CONTAINING_RECORD(slistEntry, TRANSFER_PACKET, SlistEntry); + + if (!driverUsesStartIO) { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_RW, "Insufficient packets available in ServiceTransferRequest - entering lowMemRetry with pkt=%p.", pkt)); + } + + /* + * Set the number of transfer packets (one) + * inside the original irp. + */ + Irp->IoStatus.Information = 0; + Irp->Tail.Overlay.DriverContext[0] = LongToPtr(1); + IoMarkIrpPending(Irp); + + /* + * Set up the TRANSFER_PACKET for a lowMem transfer and launch. + */ + SetupReadWriteTransferPacket( pkt, + bufPtr, + entireXferLen, + targetLocation, + Irp); + + InitLowMemRetry(pkt, bufPtr, entireXferLen, targetLocation); + StepLowMemRetry(pkt); + status = STATUS_PENDING; + } + else { + /* + * We were unable to get ANY TRANSFER_PACKETs. + * Defer this client irp until some TRANSFER_PACKETs free up. + */ + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_RW, "No packets available in ServiceTransferRequest - deferring transfer (Irp=%p)...", Irp)); + + if (priority == IoPagingPriorityHigh) + { + KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql); + + if (fdoData->MaxInterleavedNormalIo < ClassMaxInterleavePerCriticalIo) + { + fdoData->MaxInterleavedNormalIo = 0; + } + else + { + fdoData->MaxInterleavedNormalIo -= ClassMaxInterleavePerCriticalIo; + } + + fdoData->NumHighPriorityPagingIo--; + + if (fdoData->NumHighPriorityPagingIo == 0) + { + LARGE_INTEGER period; + + // + // Exiting throttle mode + // + + KeQuerySystemTime(&fdoData->ThrottleStopTime); + + period.QuadPart = fdoData->ThrottleStopTime.QuadPart - fdoData->ThrottleStartTime.QuadPart; + fdoData->LongestThrottlePeriod.QuadPart = max(fdoData->LongestThrottlePeriod.QuadPart, period.QuadPart); + } + + KeReleaseSpinLock(&fdoData->SpinLock, oldIrql); + } + + deferClientIrp = TRUE; + } + } + _Analysis_assume_(deferClientIrp); + if (deferClientIrp) + { + IoMarkIrpPending(Irp); + EnqueueDeferredClientIrp(Fdo, Irp); + status = STATUS_PENDING; + } + + NT_ASSERT(status != STATUS_UNSUCCESSFUL); + + return status; +} + + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassIoComplete() + +Routine Description: + + This routine executes when the port driver has completed a request. + It looks at the SRB status in the completing SRB and if not success + it checks for valid request sense buffer information. If valid, the + info is used to update status with more precise message of type of + error. This routine deallocates the SRB. + + This routine should only be placed on the stack location for a class + driver FDO. + +Arguments: + + Fdo - Supplies the device object which represents the logical + unit. + + Irp - Supplies the Irp which has completed. + + Context - Supplies a pointer to the SRB. + +Return Value: + + NT status + +--*/ +NTSTATUS +ClassIoComplete( + IN PDEVICE_OBJECT Fdo, + IN PIRP Irp, + IN PVOID Context + ) +{ + PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp); + PSCSI_REQUEST_BLOCK srb = Context; + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension; + PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData; + NTSTATUS status; + BOOLEAN retry; + BOOLEAN callStartNextPacket; + ULONG srbFlags; + ULONG srbFunction; + + NT_ASSERT(fdoExtension->CommonExtension.IsFdo); + + if (srb->Function == SRB_FUNCTION_STORAGE_REQUEST_BLOCK) { + srbFlags = ((PSTORAGE_REQUEST_BLOCK)srb)->SrbFlags; + srbFunction = ((PSTORAGE_REQUEST_BLOCK)srb)->SrbFunction; + } else { + srbFlags = srb->SrbFlags; + srbFunction = srb->Function; + } + + #if DBG + if (srbFunction == SRB_FUNCTION_FLUSH) { + DBGLOGFLUSHINFO(fdoData, FALSE, FALSE, TRUE); + } + #endif + + // + // Check SRB status for success of completing request. + // + + if (SRB_STATUS(srb->SrbStatus) != SRB_STATUS_SUCCESS) { + LONGLONG retryInterval; + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassIoComplete: IRP %p, SRB %p\n", Irp, srb)); + + // + // Release the queue if it is frozen. + // + + if (srb->SrbStatus & SRB_STATUS_QUEUE_FROZEN) { + ClassReleaseQueue(Fdo); + } + retry = InterpretSenseInfoWithoutHistory( + Fdo, + Irp, + srb, + irpStack->MajorFunction, + ((irpStack->MajorFunction == IRP_MJ_DEVICE_CONTROL) ? + irpStack->Parameters.DeviceIoControl.IoControlCode : + 0), + MAXIMUM_RETRIES - + ((ULONG)(ULONG_PTR)irpStack->Parameters.Others.Argument4), + &status, + &retryInterval); + + // + // For Persistent Reserve requests, make sure user gets back partial data. + // + + if (irpStack->MajorFunction == IRP_MJ_DEVICE_CONTROL && + (irpStack->Parameters.DeviceIoControl.IoControlCode == IOCTL_STORAGE_PERSISTENT_RESERVE_IN || + irpStack->Parameters.DeviceIoControl.IoControlCode == IOCTL_STORAGE_PERSISTENT_RESERVE_OUT) && + status == STATUS_DATA_OVERRUN) { + + status = STATUS_SUCCESS; + retry = FALSE; + } + + // + // If the status is verified required and the this request + // should bypass verify required then retry the request. + // + + if (TEST_FLAG(irpStack->Flags, SL_OVERRIDE_VERIFY_VOLUME) && + status == STATUS_VERIFY_REQUIRED) { + + status = STATUS_IO_DEVICE_ERROR; + retry = TRUE; + } + +#pragma warning(suppress:4213) // okay to cast Arg4 as a ulong for this use case + if (retry && ((ULONG)(ULONG_PTR)irpStack->Parameters.Others.Argument4)--) { + + // + // Retry request. + // + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "Retry request %p\n", Irp)); + + if (PORT_ALLOCATED_SENSE(fdoExtension, srb)) { + FREE_PORT_ALLOCATED_SENSE_BUFFER(fdoExtension, srb); + } + + RetryRequest(Fdo, Irp, srb, FALSE, retryInterval); + return STATUS_MORE_PROCESSING_REQUIRED; + } + + } else { + + // + // Set status for successful request + // + fdoData->LoggedTURFailureSinceLastIO = FALSE; + ClasspPerfIncrementSuccessfulIo(fdoExtension); + status = STATUS_SUCCESS; + } // end if (SRB_STATUS(srb->SrbStatus) == SRB_STATUS_SUCCESS) + + + // + // ensure we have returned some info, and it matches what the + // original request wanted for PAGING operations only + // + + if ((NT_SUCCESS(status)) && TEST_FLAG(Irp->Flags, IRP_PAGING_IO)) { + NT_ASSERT(Irp->IoStatus.Information != 0); + NT_ASSERT(irpStack->Parameters.Read.Length == Irp->IoStatus.Information); + } + + // + // remember if the caller wanted to skip calling IoStartNextPacket. + // for legacy reasons, we cannot call IoStartNextPacket for IoDeviceControl + // calls. this setting only affects device objects with StartIo routines. + // + + callStartNextPacket = !TEST_FLAG(srbFlags, SRB_FLAGS_DONT_START_NEXT_PACKET); + if (irpStack->MajorFunction == IRP_MJ_DEVICE_CONTROL) { + callStartNextPacket = FALSE; + } + + // + // Free MDL if allocated. + // + + if (TEST_FLAG(srbFlags, SRB_CLASS_FLAGS_FREE_MDL)) { + SrbClearSrbFlags(srb, SRB_CLASS_FLAGS_FREE_MDL); + IoFreeMdl(Irp->MdlAddress); + Irp->MdlAddress = NULL; + } + + + // + // Free the srb + // + + if (!TEST_FLAG(srbFlags, SRB_CLASS_FLAGS_PERSISTANT)) { + + if (PORT_ALLOCATED_SENSE(fdoExtension, srb)) { + FREE_PORT_ALLOCATED_SENSE_BUFFER(fdoExtension, srb); + } + + if (fdoExtension->CommonExtension.IsSrbLookasideListInitialized){ + ClassFreeOrReuseSrb(fdoExtension, srb); + } + else { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassIoComplete is freeing an SRB (possibly) on behalf of another driver.")); + FREE_POOL(srb); + } + + } else { + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassIoComplete: Not Freeing srb @ %p because " + "SRB_CLASS_FLAGS_PERSISTANT set\n", srb)); + if (PORT_ALLOCATED_SENSE(fdoExtension, srb)) { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassIoComplete: Not Freeing sensebuffer @ %p " + " because SRB_CLASS_FLAGS_PERSISTANT set\n", + srb->SenseInfoBuffer)); + } + + } + + // + // Set status in completing IRP. + // + + Irp->IoStatus.Status = status; + + // + // Set the hard error if necessary. + // + + if (!NT_SUCCESS(status) && + IoIsErrorUserInduced(status) && + (Irp->Tail.Overlay.Thread != NULL) + ) { + + // + // Store DeviceObject for filesystem, and clear + // in IoStatus.Information field. + // + + IoSetHardErrorOrVerifyDevice(Irp, Fdo); + Irp->IoStatus.Information = 0; + } + + // + // If pending has be returned for this irp then mark the current stack as + // pending. + // + + if (Irp->PendingReturned) { + IoMarkIrpPending(Irp); + } + + if (fdoExtension->CommonExtension.DriverExtension->InitData.ClassStartIo) { + if (callStartNextPacket) { + KIRQL oldIrql; + KeRaiseIrql(DISPATCH_LEVEL, &oldIrql); + IoStartNextPacket(Fdo, TRUE); // Yes, some IO must now be cancellable. + KeLowerIrql(oldIrql); + } + } + + ClassReleaseRemoveLock(Fdo, Irp); + + return status; + +} // end ClassIoComplete() + + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassSendSrbSynchronous() + +Routine Description: + + This routine is called by SCSI device controls to complete an + SRB and send it to the port driver synchronously (ie wait for + completion). The CDB is already completed along with the SRB CDB + size and request timeout value. + +Arguments: + + Fdo - Supplies the functional device object which represents the target. + + Srb - Supplies a partially initialized SRB. The SRB cannot come from zone. + + BufferAddress - Supplies the address of the buffer. + + BufferLength - Supplies the length in bytes of the buffer. + + WriteToDevice - Indicates the data should be transfer to the device. + +Return Value: + + NTSTATUS indicating the final results of the operation. + + If NT_SUCCESS(), then the amount of usable data is contained in the field + Srb->DataTransferLength + +--*/ +NTSTATUS +ClassSendSrbSynchronous( + _In_ PDEVICE_OBJECT Fdo, + _Inout_ PSCSI_REQUEST_BLOCK _Srb, + _In_reads_bytes_opt_(BufferLength) PVOID BufferAddress, + _In_ ULONG BufferLength, + _In_ BOOLEAN WriteToDevice + ) +{ + + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension; + PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData; + IO_STATUS_BLOCK ioStatus = {0}; + PIRP irp; + PIO_STACK_LOCATION irpStack; + KEVENT event; + PVOID senseInfoBuffer = NULL; + ULONG senseInfoBufferLength = SENSE_BUFFER_SIZE_EX; + ULONG retryCount = MAXIMUM_RETRIES; + NTSTATUS status; + BOOLEAN retry; + PSTORAGE_REQUEST_BLOCK_HEADER Srb = (PSTORAGE_REQUEST_BLOCK_HEADER)_Srb; + + // + // NOTE: This code is only pagable because we are not freezing + // the queue. Allowing the queue to be frozen from a pagable + // routine could leave the queue frozen as we try to page in + // the code to unfreeze the queue. The result would be a nice + // case of deadlock. Therefore, since we are unfreezing the + // queue regardless of the result, just set the NO_FREEZE_QUEUE + // flag in the SRB. + // + + NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL); + NT_ASSERT(fdoExtension->CommonExtension.IsFdo); + + if (Srb->Function != SRB_FUNCTION_STORAGE_REQUEST_BLOCK) { + // + // Write length to SRB. + // + + Srb->Length = sizeof(SCSI_REQUEST_BLOCK); + + // + // Set SCSI bus address. + // + + Srb->Function = SRB_FUNCTION_EXECUTE_SCSI; + } + + // + // The Srb->Function should have been set corresponding to SrbType. + // + + NT_ASSERT( ((fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_SCSI_REQUEST_BLOCK) && (Srb->Function == SRB_FUNCTION_EXECUTE_SCSI)) || + ((fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) && (Srb->Function == SRB_FUNCTION_STORAGE_REQUEST_BLOCK)) ); + + // + // Sense buffer is in aligned nonpaged pool. + // + +#if defined(_ARM_) || defined(_ARM64_) + + // + // ARM has specific alignment requirements, although this will not have a functional impact on x86 or amd64 + // based platforms. We are taking the conservative approach here. + // + senseInfoBufferLength = ALIGN_UP_BY(senseInfoBufferLength,KeGetRecommendedSharedDataAlignment()); + +#endif + + senseInfoBuffer = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned, + senseInfoBufferLength, + '7CcS'); + + if (senseInfoBuffer == NULL) { + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassSendSrbSynchronous: Can't allocate request sense " + "buffer\n")); + return(STATUS_INSUFFICIENT_RESOURCES); + } + + + // + // Enable auto request sense. + // + SrbSetSenseInfoBufferLength(Srb, SENSE_BUFFER_SIZE_EX); + SrbSetSenseInfoBuffer(Srb, senseInfoBuffer); + + SrbSetDataBuffer(Srb, BufferAddress); + + + // + // Start retries here. + // + +retry: + + // + // use fdoextension's flags by default. + // do not move out of loop, as the flag may change due to errors + // sending this command. + // + + SrbAssignSrbFlags(Srb, fdoExtension->SrbFlags); + + if (BufferAddress != NULL) { + if (WriteToDevice) { + SrbSetSrbFlags(Srb, SRB_FLAGS_DATA_OUT); + } else { + SrbSetSrbFlags(Srb, SRB_FLAGS_DATA_IN); + } + } + + + // + // Initialize the QueueAction field. + // + + SrbSetRequestAttribute(Srb, SRB_SIMPLE_TAG_REQUEST); + + // + // Disable synchronous transfer for these requests. + // + SrbSetSrbFlags(Srb, SRB_FLAGS_DISABLE_SYNCH_TRANSFER | SRB_FLAGS_NO_QUEUE_FREEZE); + + // + // Set the event object to the unsignaled state. + // It will be used to signal request completion. + // + + KeInitializeEvent(&event, NotificationEvent, FALSE); + + // + // Build device I/O control request with METHOD_NEITHER data transfer. + // We'll queue a completion routine to cleanup the MDL's and such ourself. + // + + irp = IoAllocateIrp( + (CCHAR) (fdoExtension->CommonExtension.LowerDeviceObject->StackSize + 1), + FALSE); + + if (irp == NULL) { + FREE_POOL(senseInfoBuffer); + SrbSetSenseInfoBuffer(Srb, NULL); + SrbSetSenseInfoBufferLength(Srb, 0); + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassSendSrbSynchronous: Can't allocate Irp\n")); + return(STATUS_INSUFFICIENT_RESOURCES); + } + + // + // Get next stack location. + // + + irpStack = IoGetNextIrpStackLocation(irp); + + // + // Set up SRB for execute scsi request. Save SRB address in next stack + // for the port driver. + // + + irpStack->MajorFunction = IRP_MJ_SCSI; + irpStack->Parameters.Scsi.Srb = (PSCSI_REQUEST_BLOCK)Srb; + + IoSetCompletionRoutine(irp, + ClasspSendSynchronousCompletion, + Srb, + TRUE, + TRUE, + TRUE); + + irp->UserIosb = &ioStatus; + irp->UserEvent = &event; + + if (BufferAddress) { + // + // Build an MDL for the data buffer and stick it into the irp. The + // completion routine will unlock the pages and free the MDL. + // + + irp->MdlAddress = IoAllocateMdl( BufferAddress, + BufferLength, + FALSE, + FALSE, + irp ); + if (irp->MdlAddress == NULL) { + FREE_POOL(senseInfoBuffer); + SrbSetSenseInfoBuffer(Srb, NULL); + SrbSetSenseInfoBufferLength(Srb, 0); + IoFreeIrp( irp ); + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassSendSrbSynchronous: Can't allocate MDL\n")); + return STATUS_INSUFFICIENT_RESOURCES; + } + + try { + + // + // the io manager unlocks these pages upon completion + // + + MmProbeAndLockPages( irp->MdlAddress, + KernelMode, + (WriteToDevice ? IoReadAccess : + IoWriteAccess)); + + #pragma warning(suppress: 6320) // We want to handle any exception that MmProbeAndLockPages might throw + } except(EXCEPTION_EXECUTE_HANDLER) { + status = GetExceptionCode(); + + FREE_POOL(senseInfoBuffer); + SrbSetSenseInfoBuffer(Srb, NULL); + SrbSetSenseInfoBufferLength(Srb, 0); + IoFreeMdl(irp->MdlAddress); + IoFreeIrp(irp); + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassSendSrbSynchronous: Exception %lx " + "locking buffer\n", status)); + return status; + } + } + + // + // Set the transfer length. + // + + SrbSetDataTransferLength(Srb, BufferLength); + + // + // Zero out status. + // + + SrbSetScsiStatus(Srb, 0); + Srb->SrbStatus = 0; + SrbSetNextSrb(Srb, NULL); + + // + // Set up IRP Address. + // + + SrbSetOriginalRequest(Srb, irp); + + // + // Call the port driver with the request and wait for it to complete. + // + + status = IoCallDriver(fdoExtension->CommonExtension.LowerDeviceObject, irp); + + if (status == STATUS_PENDING) { + (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); + status = ioStatus.Status; + } + +// NT_ASSERT(SRB_STATUS(Srb->SrbStatus) != SRB_STATUS_PENDING); + NT_ASSERT(status != STATUS_PENDING); + NT_ASSERT(!(Srb->SrbStatus & SRB_STATUS_QUEUE_FROZEN)); + + // + // Clear the IRP address in SRB as IRP has been freed at this time + // and don't want to leave any references that may be accessed. + // + + SrbSetOriginalRequest(Srb, NULL); + + // + // Check that request completed without error. + // + + if (SRB_STATUS(Srb->SrbStatus) != SRB_STATUS_SUCCESS) { + + LONGLONG retryInterval; + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassSendSrbSynchronous - srb %ph failed (op=%s srbstat=%s(%xh), irpstat=%xh, sense=%s/%s/%s)", Srb, + DBGGETSCSIOPSTR(Srb), + DBGGETSRBSTATUSSTR(Srb), (ULONG)Srb->SrbStatus, status, + DBGGETSENSECODESTR(Srb), + DBGGETADSENSECODESTR(Srb), + DBGGETADSENSEQUALIFIERSTR(Srb))); + + // + // assert that the queue is not frozen + // + + NT_ASSERT(!TEST_FLAG(Srb->SrbStatus, SRB_STATUS_QUEUE_FROZEN)); + + // + // Update status and determine if request should be retried. + // + + retry = InterpretSenseInfoWithoutHistory(Fdo, + NULL, // no valid irp exists + (PSCSI_REQUEST_BLOCK)Srb, + IRP_MJ_SCSI, + 0, + MAXIMUM_RETRIES - retryCount, + &status, + &retryInterval); + + if (retry) { + + BOOLEAN validSense = FALSE; + UCHAR additionalSenseCode = 0; + + if (status == STATUS_DEVICE_NOT_READY) { + + validSense = ScsiGetSenseKeyAndCodes(senseInfoBuffer, + SrbGetSenseInfoBufferLength(Srb), + SCSI_SENSE_OPTIONS_FIXED_FORMAT_IF_UNKNOWN_FORMAT_INDICATED, + NULL, + &additionalSenseCode, + NULL); + } + + if ((validSense && additionalSenseCode == SCSI_ADSENSE_LUN_NOT_READY) || + (SRB_STATUS(Srb->SrbStatus) == SRB_STATUS_SELECTION_TIMEOUT)) { + + LARGE_INTEGER delay; + + // + // Delay for at least 2 seconds. + // + + if (retryInterval < 2*1000*1000*10) { + retryInterval = 2*1000*1000*10; + } + + delay.QuadPart = -retryInterval; + + // + // Stall for a while to let the device become ready + // + + KeDelayExecutionThread(KernelMode, FALSE, &delay); + + } + + // + // If retries are not exhausted then retry this operation. + // + + if (retryCount--) { + + if (PORT_ALLOCATED_SENSE_EX(fdoExtension, Srb)) { + FREE_PORT_ALLOCATED_SENSE_BUFFER_EX(fdoExtension, Srb); + } + + goto retry; + } + } + + + } else { + fdoData->LoggedTURFailureSinceLastIO = FALSE; + status = STATUS_SUCCESS; + } + + // + // required even though we allocated our own, since the port driver may + // have allocated one also + // + + if (PORT_ALLOCATED_SENSE_EX(fdoExtension, Srb)) { + FREE_PORT_ALLOCATED_SENSE_BUFFER_EX(fdoExtension, Srb); + } + + FREE_POOL(senseInfoBuffer); + SrbSetSenseInfoBuffer(Srb, NULL); + SrbSetSenseInfoBufferLength(Srb, 0); + + return status; +} + + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassInterpretSenseInfo() + +Routine Description: + + This routine interprets the data returned from the SCSI + request sense. It determines the status to return in the + IRP and whether this request can be retried. + +Arguments: + + Fdo - Supplies the device object associated with this request. + + Srb - Supplies the scsi request block which failed. + + MajorFunctionCode - Supplies the function code to be used for logging. + + IoDeviceCode - Supplies the device code to be used for logging. + + RetryCount - Number of times that the request has been retried. + + Status - Returns the status for the request. + + RetryInterval - Number of seconds before the request should be retried. + Zero indicates the request should be immediately retried. + +Return Value: + + BOOLEAN TRUE: Drivers should retry this request. + FALSE: Drivers should not retry this request. + +--*/ +BOOLEAN +ClassInterpretSenseInfo( + _In_ PDEVICE_OBJECT Fdo, + _In_ PSCSI_REQUEST_BLOCK _Srb, + _In_ UCHAR MajorFunctionCode, + _In_ ULONG IoDeviceCode, + _In_ ULONG RetryCount, + _Out_ NTSTATUS *Status, + _Out_opt_ _Deref_out_range_(0,100) ULONG *RetryInterval + ) +{ + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension; + PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData; + PSTORAGE_REQUEST_BLOCK_HEADER Srb = (PSTORAGE_REQUEST_BLOCK_HEADER)_Srb; + PVOID senseBuffer = SrbGetSenseInfoBuffer(Srb); + BOOLEAN retry = TRUE; + BOOLEAN logError = FALSE; + BOOLEAN unhandledError = FALSE; + BOOLEAN incrementErrorCount = FALSE; + + // + // NOTE: This flag must be used only for read/write requests that + // fail with a unexpected retryable error. + // + BOOLEAN logRetryableError = TRUE; + + // + // Indicates if we should log this error in our internal log. + // + BOOLEAN logErrorInternal = TRUE; + + ULONGLONG badSector = 0; + ULONG uniqueId = 0; + + NTSTATUS logStatus; + + ULONGLONG readSector; + ULONG index; + + ULONG retryInterval = 0; + KIRQL oldIrql; + PCDB cdb = SrbGetCdb(Srb); + UCHAR cdbOpcode = 0; + ULONG cdbLength = SrbGetCdbLength(Srb); + +#if DBG + BOOLEAN isReservationConflict = FALSE; +#endif + + if (cdb) { + cdbOpcode = cdb->CDB6GENERIC.OperationCode; + } + + *Status = STATUS_IO_DEVICE_ERROR; + logStatus = -1; + + if (TEST_FLAG(SrbGetSrbFlags(Srb), SRB_CLASS_FLAGS_PAGING)) { + + // + // Log anything remotely incorrect about paging i/o + // + + logError = TRUE; + uniqueId = 301; + logStatus = IO_WARNING_PAGING_FAILURE; + } + + // + // Check that request sense buffer is valid. + // + + NT_ASSERT(fdoExtension->CommonExtension.IsFdo); + + + // + // must handle the SRB_STATUS_INTERNAL_ERROR case first, + // as it has all the flags set. + // + + if (SRB_STATUS(Srb->SrbStatus) == SRB_STATUS_INTERNAL_ERROR) { + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, + "ClassInterpretSenseInfo: Internal Error code is %x\n", + SrbGetSystemStatus(Srb))); + + retry = FALSE; + *Status = SrbGetSystemStatus(Srb); + + } else if (SrbGetScsiStatus(Srb) == SCSISTAT_RESERVATION_CONFLICT) { + + // + // Need to reserve STATUS_DEVICE_BUSY to convey reservation conflict + // for read/write requests as there are upper level components that + // have built-in assumptions that STATUS_DEVICE_BUSY implies reservation + // conflict. + // + *Status = STATUS_DEVICE_BUSY; + retry = FALSE; + logError = FALSE; +#if DBG + isReservationConflict = TRUE; +#endif + + } else { + + BOOLEAN validSense = FALSE; + + if ((Srb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID) && senseBuffer) { + + UCHAR errorCode = 0; + UCHAR senseKey = 0; + UCHAR addlSenseCode = 0; + UCHAR addlSenseCodeQual = 0; + BOOLEAN isIncorrectLengthValid = FALSE; + BOOLEAN incorrectLength = FALSE; + BOOLEAN isInformationValid = FALSE; + ULONGLONG information = 0; + + + validSense = ScsiGetSenseKeyAndCodes(senseBuffer, + SrbGetSenseInfoBufferLength(Srb), + SCSI_SENSE_OPTIONS_NONE, + &senseKey, + &addlSenseCode, + &addlSenseCodeQual); + + if (!validSense && !IsSenseDataFormatValueValid(senseBuffer)) { + + NT_ASSERT(FALSE); + + validSense = ScsiGetFixedSenseKeyAndCodes(senseBuffer, + SrbGetSenseInfoBufferLength(Srb), + &senseKey, + &addlSenseCode, + &addlSenseCodeQual); + } + + if (!validSense) { + goto __ClassInterpretSenseInfo_ProcessingInvalidSenseBuffer; + } + + errorCode = ScsiGetSenseErrorCode(senseBuffer); + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: Error code is %x\n", errorCode)); + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: Sense key is %x\n", senseKey)); + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: Additional sense code is %x\n", addlSenseCode)); + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: Additional sense code qualifier is %x\n", addlSenseCodeQual)); + + if (IsDescriptorSenseDataFormat(senseBuffer)) { + + // + // Sense data in Descriptor format + // + + PVOID startBuffer = NULL; + UCHAR startBufferLength = 0; + + + if (ScsiGetSenseDescriptor(senseBuffer, + SrbGetSenseInfoBufferLength(Srb), + &startBuffer, + &startBufferLength)) { + UCHAR outType; + PVOID outBuffer = NULL; + UCHAR outBufferLength = 0; + BOOLEAN foundBlockCommandType = FALSE; + BOOLEAN foundInformationType = FALSE; + UCHAR descriptorLength = 0; + + UCHAR typeList[2] = {SCSI_SENSE_DESCRIPTOR_TYPE_INFORMATION, + SCSI_SENSE_DESCRIPTOR_TYPE_BLOCK_COMMAND}; + + while ((!foundBlockCommandType || !foundInformationType) && + ScsiGetNextSenseDescriptorByType(startBuffer, + startBufferLength, + typeList, + ARRAYSIZE(typeList), + &outType, + &outBuffer, + &outBufferLength)) { + + descriptorLength = ScsiGetSenseDescriptorLength(outBuffer); + + if (outBufferLength < descriptorLength) { + + // Descriptor data is truncated. + // Complete searching descriptors. Exit the loop now. + break; + } + + if (outType == SCSI_SENSE_DESCRIPTOR_TYPE_BLOCK_COMMAND) { + + // + // Block Command type + // + + if (!foundBlockCommandType) { + + foundBlockCommandType = TRUE; + + if (ScsiValidateBlockCommandSenseDescriptor(outBuffer, outBufferLength)) { + incorrectLength = ((PSCSI_SENSE_DESCRIPTOR_BLOCK_COMMAND)outBuffer)->IncorrectLength; + isIncorrectLengthValid = TRUE; + } + } else { + + // + // A Block Command descriptor is already found earlier. + // + // T10 SPC specification only allows one descriptor for Block Command Descriptor type. + // Assert here to catch devices that violate this rule. Ignore this descriptor. + // + NT_ASSERT(FALSE); + } + + } else if (outType == SCSI_SENSE_DESCRIPTOR_TYPE_INFORMATION) { + + // + // Information type + // + + if (!foundInformationType) { + + foundInformationType = TRUE; + + if (ScsiValidateInformationSenseDescriptor(outBuffer, outBufferLength)) { + REVERSE_BYTES_QUAD(&information, &(((PSCSI_SENSE_DESCRIPTOR_INFORMATION)outBuffer)->Information)); + isInformationValid = TRUE; + } + } else { + + // + // A Information descriptor is already found earlier. + // + // T10 SPC specification only allows one descriptor for Information Descriptor type. + // Assert here to catch devices that violate this rule. Ignore this descriptor. + // + NT_ASSERT(FALSE); + } + + } else { + + // + // ScsiGetNextDescriptorByType should only return a type that is specified by us. + // + NT_ASSERT(FALSE); + break; + } + + // + // Advance to start address of next descriptor + // + startBuffer = (PUCHAR)outBuffer + descriptorLength; + startBufferLength = outBufferLength - descriptorLength; + } + } + } else { + + // + // Sense data in Fixed format + // + + incorrectLength = ((PFIXED_SENSE_DATA)(senseBuffer))->IncorrectLength; + REVERSE_BYTES(&information, &(((PFIXED_SENSE_DATA)senseBuffer)->Information)); + isInformationValid = TRUE; + isIncorrectLengthValid = TRUE; + } + + + switch (senseKey) { + + case SCSI_SENSE_NO_SENSE: { + + // + // Check other indicators. + // + + if (isIncorrectLengthValid && incorrectLength) { + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Incorrect length detected.\n")); + *Status = STATUS_INVALID_BLOCK_LENGTH ; + retry = FALSE; + + } else { + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "No specific sense key\n")); + *Status = STATUS_IO_DEVICE_ERROR; + retry = TRUE; + } + + break; + } // end SCSI_SENSE_NO_SENSE + + case SCSI_SENSE_RECOVERED_ERROR: { + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Recovered error\n")); + *Status = STATUS_SUCCESS; + retry = FALSE; + logError = TRUE; + uniqueId = 258; + + switch(addlSenseCode) { + case SCSI_ADSENSE_TRACK_ERROR: + case SCSI_ADSENSE_SEEK_ERROR: { + logStatus = IO_ERR_SEEK_ERROR; + break; + } + + case SCSI_ADSENSE_REC_DATA_NOECC: + case SCSI_ADSENSE_REC_DATA_ECC: { + logStatus = IO_RECOVERED_VIA_ECC; + break; + } + + case SCSI_ADSENSE_FAILURE_PREDICTION_THRESHOLD_EXCEEDED: { + + UCHAR wmiEventData[sizeof(ULONG)+sizeof(UCHAR)] = {0}; + + *((PULONG)wmiEventData) = sizeof(UCHAR); + wmiEventData[sizeof(ULONG)] = addlSenseCodeQual; + + // + // Don't log another eventlog if we have already logged once + // NOTE: this should have been interlocked, but the structure + // was publicly defined to use a BOOLEAN (char). Since + // media only reports these errors once per X minutes, + // the potential race condition is nearly non-existant. + // the worst case is duplicate log entries, so ignore. + // + + logError = FALSE; + if (fdoExtension->FailurePredicted == 0) { + logError = TRUE; + } + fdoExtension->FailureReason = addlSenseCodeQual; + logStatus = IO_WRN_FAILURE_PREDICTED; + + ClassNotifyFailurePredicted(fdoExtension, + (PUCHAR)wmiEventData, + sizeof(wmiEventData), + FALSE, // do not log error + 4, // unique error value + SrbGetPathId(Srb), + SrbGetTargetId(Srb), + SrbGetLun(Srb)); + + fdoExtension->FailurePredicted = TRUE; + break; + } + + default: { + logStatus = IO_ERR_CONTROLLER_ERROR; + break; + } + + } // end switch(addlSenseCode) + + if (isIncorrectLengthValid && incorrectLength) { + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Incorrect length detected.\n")); + *Status = STATUS_INVALID_BLOCK_LENGTH ; + } + + + break; + } // end SCSI_SENSE_RECOVERED_ERROR + + case SCSI_SENSE_NOT_READY: { + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Device not ready\n")); + *Status = STATUS_DEVICE_NOT_READY; + + switch (addlSenseCode) { + + case SCSI_ADSENSE_LUN_NOT_READY: { + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Lun not ready\n")); + + retryInterval = NOT_READY_RETRY_INTERVAL; + + switch (addlSenseCodeQual) { + + case SCSI_SENSEQ_BECOMING_READY: { + DEVICE_EVENT_BECOMING_READY notReady = {0}; + + logRetryableError = FALSE; + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "In process of becoming ready\n")); + + notReady.Version = 1; + notReady.Reason = 1; + notReady.Estimated100msToReady = retryInterval * 10; + ClassSendNotification(fdoExtension, + &GUID_IO_DEVICE_BECOMING_READY, + sizeof(DEVICE_EVENT_BECOMING_READY), + ¬Ready); + break; + } + + case SCSI_SENSEQ_MANUAL_INTERVENTION_REQUIRED: { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Manual intervention required\n")); + *Status = STATUS_NO_MEDIA_IN_DEVICE; + retry = FALSE; + break; + } + + case SCSI_SENSEQ_FORMAT_IN_PROGRESS: { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Format in progress\n")); + retry = FALSE; + break; + } + + case SCSI_SENSEQ_OPERATION_IN_PROGRESS: { + DEVICE_EVENT_BECOMING_READY notReady = {0}; + + logRetryableError = FALSE; + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Operation In Progress\n")); + + notReady.Version = 1; + notReady.Reason = 2; + notReady.Estimated100msToReady = retryInterval * 10; + ClassSendNotification(fdoExtension, + &GUID_IO_DEVICE_BECOMING_READY, + sizeof(DEVICE_EVENT_BECOMING_READY), + ¬Ready); + + break; + } + + case SCSI_SENSEQ_LONG_WRITE_IN_PROGRESS: { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Long write in progress\n")); + // + // This has been seen as a transcient failure on some cdrom + // drives. The cdrom class driver is going to override this + // setting but has no way of dropping the retry interval + // + retry = FALSE; + retryInterval = 1; + break; + } + + case SCSI_SENSEQ_SPACE_ALLOC_IN_PROGRESS: { + logRetryableError = FALSE; + TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "The device (%p) is busy allocating space.\n", + Fdo)); + + // + // This indicates that a thinly-provisioned device has hit + // a temporary resource exhaustion and is busy allocating + // more space. We need to retry the request as the device + // will eventually be able to service it. + // + *Status = STATUS_RETRY; + retry = TRUE; + + break; + } + + case SCSI_SENSEQ_CAUSE_NOT_REPORTABLE: { + + if (!TEST_FLAG(fdoExtension->ScanForSpecialFlags, + CLASS_SPECIAL_CAUSE_NOT_REPORTABLE_HACK)) { + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, + "ClassInterpretSenseInfo: " + "not ready, cause unknown\n")); + /* + Many non-WHQL certified drives (mostly CD-RW) return + this when they have no media instead of the obvious + choice of: + + SCSI_SENSE_NOT_READY/SCSI_ADSENSE_NO_MEDIA_IN_DEVICE + + These drives should not pass WHQL certification due + to this discrepency. + + */ + retry = FALSE; + break; + + } else { + + // + // Treat this as init command required and fall through. + // + } + } + + case SCSI_SENSEQ_INIT_COMMAND_REQUIRED: + default: { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Initializing command required\n")); + retryInterval = 0; // go back to default + logRetryableError = FALSE; + + // + // This sense code/additional sense code + // combination may indicate that the device + // needs to be started. Send an start unit if this + // is a disk device. + // + if (TEST_FLAG(fdoExtension->DeviceFlags, DEV_SAFE_START_UNIT) && + !TEST_FLAG(SrbGetSrbFlags(Srb), SRB_CLASS_FLAGS_LOW_PRIORITY)){ + + ClassSendStartUnit(Fdo); + } + break; + } + + } // end switch (addlSenseCodeQual) + break; + } + + case SCSI_ADSENSE_NO_MEDIA_IN_DEVICE: { + TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "No Media in device.\n")); + *Status = STATUS_NO_MEDIA_IN_DEVICE; + retry = FALSE; + + // + // signal MCN that there isn't any media in the device + // + if (!TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA)) { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "No Media in a non-removable device %p\n", + Fdo)); + } + + if (addlSenseCodeQual == 0xCC){ + /* + * The IMAPI filter returns this ASCQ value when it is burning CD-R media. + * We want to indicate that the media is not present to most applications; + * but RSM has to know that the media is still in the drive (i.e. the drive is not free). + */ + ClassSetMediaChangeState(fdoExtension, MediaUnavailable, FALSE); + } + else { + ClassSetMediaChangeState(fdoExtension, MediaNotPresent, FALSE); + } + + break; + } + } // end switch (addlSenseCode) + + break; + } // end SCSI_SENSE_NOT_READY + + case SCSI_SENSE_MEDIUM_ERROR: { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Medium Error (bad block)\n")); + *Status = STATUS_DEVICE_DATA_ERROR; + + retry = FALSE; + logError = TRUE; + uniqueId = 256; + logStatus = IO_ERR_BAD_BLOCK; + + // + // Check if this error is due to unknown format + // + if (addlSenseCode == SCSI_ADSENSE_INVALID_MEDIA) { + + switch (addlSenseCodeQual) { + + case SCSI_SENSEQ_UNKNOWN_FORMAT: { + + *Status = STATUS_UNRECOGNIZED_MEDIA; + + // + // Log error only if this is a paging request + // + if (!TEST_FLAG(SrbGetSrbFlags(Srb), SRB_CLASS_FLAGS_PAGING)) { + logError = FALSE; + } + break; + } + + case SCSI_SENSEQ_CLEANING_CARTRIDGE_INSTALLED: { + + *Status = STATUS_CLEANER_CARTRIDGE_INSTALLED; + logError = FALSE; + break; + + } + default: { + break; + } + } // end switch addlSenseCodeQual + + } // end SCSI_ADSENSE_INVALID_MEDIA + + break; + + } // end SCSI_SENSE_MEDIUM_ERROR + + case SCSI_SENSE_HARDWARE_ERROR: { + BOOLEAN logHardwareError = TRUE; + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Hardware error\n")); + + if (fdoData->LegacyErrorHandling == FALSE) { + // + // Hardware errors indicate something has seriously gone + // wrong with the device and retries are very unlikely to + // succeed so fail this request back immediately. + // + retry = FALSE; + *Status = STATUS_DEVICE_HARDWARE_ERROR; + logError = FALSE; + + } else { + // + // Revert to legacy behavior. That is, retry everything by default. + // + retry = TRUE; + *Status = STATUS_IO_DEVICE_ERROR; + logError = TRUE; + uniqueId = 257; + logStatus = IO_ERR_CONTROLLER_ERROR; + logHardwareError = FALSE; + + // + // This indicates the possibility of a dropped FC packet. + // + if ((addlSenseCode == SCSI_ADSENSE_LOGICAL_UNIT_ERROR && addlSenseCodeQual == SCSI_SENSEQ_TIMEOUT_ON_LOGICAL_UNIT) || + (addlSenseCode == SCSI_ADSENSE_DATA_TRANSFER_ERROR && addlSenseCodeQual == SCSI_SENSEQ_INITIATOR_RESPONSE_TIMEOUT)) { + // + // Fail requests that report this error back to the application. + // + retry = FALSE; + + // + // Log a more descriptive error and avoid a second + // error message (IO_ERR_CONTROLLER_ERROR) being logged. + // + logHardwareError = TRUE; + logError = FALSE; + } + } + + // + // If CRC error was returned, retry after a slight delay. + // + if (addlSenseCode == SCSI_ADSENSE_LUN_COMMUNICATION && + addlSenseCodeQual == SCSI_SESNEQ_COMM_CRC_ERROR) { + retry = TRUE; + retryInterval = 1; + logHardwareError = FALSE; + logError = FALSE; + } + + // + // Hardware errors warrant a more descriptive error. + // Specifically, we need to ensure this disk is easily + // identifiable. + // + if (logHardwareError) { + UCHAR senseInfoBufferLength = SrbGetSenseInfoBufferLength(Srb); + UCHAR senseBufferSize = 0; + + if (ScsiGetTotalSenseByteCountIndicated(senseBuffer, + senseInfoBufferLength, + &senseBufferSize)) { + + senseBufferSize = min(senseBufferSize, senseInfoBufferLength); + + } else { + // + // it's smaller than required to read the total number of + // valid bytes, so just use the SenseInfoBufferLength field. + // + senseBufferSize = senseInfoBufferLength; + } + + ClasspQueueLogIOEventWithContextWorker(Fdo, + senseBufferSize, + senseBuffer, + SRB_STATUS(Srb->SrbStatus), + SrbGetScsiStatus(Srb), + (ULONG)IO_ERROR_IO_HARDWARE_ERROR, + cdbLength, + cdb, + NULL); + } + + break; + } // end SCSI_SENSE_HARDWARE_ERROR + + case SCSI_SENSE_ILLEGAL_REQUEST: { + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Illegal SCSI request\n")); + *Status = STATUS_INVALID_DEVICE_REQUEST; + retry = FALSE; + + switch (addlSenseCode) { + + case SCSI_ADSENSE_NO_SENSE: { + + switch (addlSenseCodeQual) { + + // + // 1. Duplicate List Identifier + // + case SCSI_SENSEQ_OPERATION_IS_IN_PROGRESS: { + + // + // XCOPY, READ BUFFER and CHANGE ALIASES return this sense combination under + // certain conditions. Since these commands aren't sent down natively by the + // Windows OS, return the default error for them and only handle this sense + // combination for offload data transfer commands. + // + if (ClasspIsOffloadDataTransferCommand(cdb)) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassInterpretSenseInfo (%p): Duplicate List Identifier (command %x, parameter field offset 0x%016llx)\n", + Fdo, + cdbOpcode, + information)); + + NT_ASSERTMSG("Duplicate list identifier specified", FALSE); + + // + // The host should ensure that it uses unique list id for each TokenOperation request. + // + *Status = STATUS_OPERATION_IN_PROGRESS; + } + break; + } + } + break; + } + + case SCSI_ADSENSE_LUN_COMMUNICATION: { + + switch (addlSenseCodeQual) { + + // + // 1. Source/Destination pairing can't communicate with each other or the copy manager. + // + case SCSI_SENSEQ_UNREACHABLE_TARGET: { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassInterpretSenseInfo (%p): Source-Destination LUNs can't communicate (command %x)\n", + Fdo, + cdbOpcode)); + + *Status = STATUS_DEVICE_UNREACHABLE; + break; + } + } + break; + } + + case SCSI_ADSENSE_COPY_TARGET_DEVICE_ERROR: { + + switch (addlSenseCodeQual) { + + // + // 1. Sum of logical block fields in all block device range descriptors is greater than number + // of logical blocks in the ROD minus block offset into ROD + // + case SCSI_SENSEQ_DATA_UNDERRUN: { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassInterpretSenseInfo (%p): Host specified a transfer length greater than what is represented by the token (considering the offset) [command %x]\n", + Fdo, + cdbOpcode)); + + NT_ASSERTMSG("Host specified blocks to write beyond what is represented by the token", FALSE); + + *Status = STATUS_DATA_OVERRUN; + break; + } + } + break; + } + + // + // 1. Parameter data truncation (e.g. last descriptor was not fully specified) + // + case SCSI_ADSENSE_PARAMETER_LIST_LENGTH: { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassInterpretSenseInfo (%p): Target truncated the block device range descriptors in the parameter list (command %x)\n", + Fdo, + cdbOpcode)); + + NT_ASSERTMSG("Parameter data truncation", FALSE); + + *Status = STATUS_DATA_OVERRUN; + break; + } + + case SCSI_ADSENSE_ILLEGAL_COMMAND: { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Illegal command\n")); + break; + } + + case SCSI_ADSENSE_ILLEGAL_BLOCK: { + + LARGE_INTEGER logicalBlockAddr; + LARGE_INTEGER lastLBA; + ULONG numTransferBlocks = 0; + + logicalBlockAddr.QuadPart = 0; + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: Illegal block address\n")); + + *Status = STATUS_NONEXISTENT_SECTOR; + + if (Fdo->DeviceType == FILE_DEVICE_DISK) { + + if (IS_SCSIOP_READWRITE(cdbOpcode) && cdb) { + + if (TEST_FLAG(fdoExtension->DeviceFlags, DEV_USE_16BYTE_CDB)) { + REVERSE_BYTES_QUAD(&logicalBlockAddr, &cdb->CDB16.LogicalBlock); + REVERSE_BYTES(&numTransferBlocks, &cdb->CDB16.TransferLength); + } else { + REVERSE_BYTES(&logicalBlockAddr.LowPart, &cdb->CDB10.LogicalBlockByte0); + REVERSE_BYTES_SHORT((PUSHORT)&numTransferBlocks, &cdb->CDB10.TransferBlocksMsb); + } + + REVERSE_BYTES_QUAD(&lastLBA, &fdoData->LastKnownDriveCapacityData.LogicalBlockAddress); + + if ((logicalBlockAddr.QuadPart > lastLBA.QuadPart) || + ((logicalBlockAddr.QuadPart + numTransferBlocks - 1) > lastLBA.QuadPart)) { + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Request beyond boundary. Last LBA: 0x%I64X Read LBA: 0x%I64X Length: 0x%X\n", + (__int64) lastLBA.QuadPart, (__int64) logicalBlockAddr.QuadPart, numTransferBlocks)); + } else { + // + // Should only retry these if the request was + // truly within our expected size. + // + // Fujitsu IDE drives have been observed to + // return this error transiently for a legal LBA; + // manual retry in the debugger then works, so + // there is a good chance that a programmed retry + // will also work. + // + + retry = TRUE; + retryInterval = 5; + } + } else if (ClasspIsOffloadDataTransferCommand(cdb)) { + + // + // 1. Number of logical blocks of block device range descriptor exceeds capacity of the medium + // + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassInterpretSenseInfo (%p): LBA out of range (command %x, parameter field offset 0x%016llx)\n", + Fdo, + cdbOpcode, + information)); + + NT_ASSERTMSG("Number of blocks specified exceeds LUN capacity", FALSE); + } + } + break; + } + + // + // 1. Generic error - cause not reportable + // 2. Insufficient resources to create ROD + // 3. Insufficient resources to create Token + // 4. Max number of tokens exceeded + // 5. Remote Token creation not supported + // 6. Token expired + // 7. Token unknown + // 8. Unsupported Token type + // 9. Token corrupt + // 10. Token revoked + // 11. Token cancelled + // 12. Remote Token usage not supported + // + case SCSI_ADSENSE_INVALID_TOKEN: { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassInterpretSenseInfo (%p): Invalid/Expired/Modified token specified (command %x, parameter field offset 0x%016llx)\n", + Fdo, + cdbOpcode, + information)); + + *Status = STATUS_INVALID_TOKEN; + break; + } + + case SCSI_ADSENSE_INVALID_CDB: { + if (ClasspIsOffloadDataTransferCommand(cdb)) { + + // + // 1. Mismatched I_T nexus and list identifier + // + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassInterpretSenseInfo (%p): Incorrect I_T nexus likely used (command %x)\n", + Fdo, + cdbOpcode)); + + // + // The host should ensure that it sends TokenOperation and ReceiveTokenInformation for the same + // list Id using the same I_T nexus. + // + *Status = STATUS_INVALID_INITIATOR_TARGET_PATH; + + } else { + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Invalid CDB\n")); + + // + // Note: the retry interval is not typically used. + // it is set here only because a ClassErrorHandler + // cannot set the retryInterval, and the error may + // require a few commands to be sent to clear whatever + // caused this condition (i.e. disk clears the write + // cache, requiring at least two commands) + // + // hopefully, this shortcoming can be changed for + // blackcomb. + // + + retryInterval = 3; + } + break; + } + + case SCSI_ADSENSE_INVALID_LUN: { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Invalid LUN\n")); + *Status = STATUS_NO_SUCH_DEVICE; + break; + } + + case SCSI_ADSENSE_INVALID_FIELD_PARAMETER_LIST: { + + switch (addlSenseCodeQual) { + + // + // 1. Alignment violation (e.g. copy manager is unable to copy because destination offset is NOT aligned to LUN's granularity/alignment) + // + case SCSI_SENSEQ_INVALID_RELEASE_OF_PERSISTENT_RESERVATION: { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassInterpretSenseInfo (%p): Alignment violation for command %x.\n", + Fdo, + cdbOpcode)); + + NT_ASSERTMSG("Specified offset is not aligned to LUN's granularity", FALSE); + + *Status = STATUS_INVALID_OFFSET_ALIGNMENT; + break; + } + + // + // 1. Number of block device range descriptors is greater than maximum range descriptors + // + case SCSI_SENSEQ_TOO_MANY_SEGMENT_DESCRIPTORS: { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassInterpretSenseInfo (%p): Too many descriptors in parameter list for command %x (parameter field offset 0x%016llx)\n", + Fdo, + cdbOpcode, + information)); + + NT_ASSERTMSG("Too many descriptors specified", FALSE); + + *Status = STATUS_TOO_MANY_SEGMENT_DESCRIPTORS; + break; + } + + default: { + + if (ClasspIsOffloadDataTransferCommand(cdb)) { + + // + // 1. (Various) Invalid parameter length + // 2. Requested inactivity timeout is greater than maximum inactivity timeout + // 3. Same LBA is included in more than one block device range descriptor (overlapping LBAs) + // 4. Total number of logical blocks of all block range descriptors is greater than the maximum transfer size + // 5. Total number of logical blocks of all block range descriptors is greater than maximum token transfer size + // (e.g. WriteUsingToken descriptors specify a cumulative total block count that exceeds the PopulateToken that created the token) + // 6. Block offset into ROD specified an offset that is greater than or equal to the number of logical blocks in the ROD + // 7. Number of logical blocks in a block device range descriptor is greater than maximum transfer length in blocks + // + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassInterpretSenseInfo (%p): Illegal field in parameter list for command %x (parameter field offset 0x%016llx) [AddSense %x, AddSenseQ %x]\n", + Fdo, + cdbOpcode, + information, + addlSenseCode, + addlSenseCodeQual)); + + NT_ASSERTMSG("Invalid field in parameter list", FALSE); + + *Status = STATUS_INVALID_FIELD_IN_PARAMETER_LIST; + } + + break; + } + } + break; + } + + case SCSI_ADSENSE_COPY_PROTECTION_FAILURE: { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Copy protection failure\n")); + + *Status = STATUS_COPY_PROTECTION_FAILURE; + + switch (addlSenseCodeQual) { + case SCSI_SENSEQ_AUTHENTICATION_FAILURE: + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, + "ClassInterpretSenseInfo: " + "Authentication failure\n")); + *Status = STATUS_CSS_AUTHENTICATION_FAILURE; + break; + case SCSI_SENSEQ_KEY_NOT_PRESENT: + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, + "ClassInterpretSenseInfo: " + "Key not present\n")); + *Status = STATUS_CSS_KEY_NOT_PRESENT; + break; + case SCSI_SENSEQ_KEY_NOT_ESTABLISHED: + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, + "ClassInterpretSenseInfo: " + "Key not established\n")); + *Status = STATUS_CSS_KEY_NOT_ESTABLISHED; + break; + case SCSI_SENSEQ_READ_OF_SCRAMBLED_SECTOR_WITHOUT_AUTHENTICATION: + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, + "ClassInterpretSenseInfo: " + "Read of scrambled sector w/o " + "authentication\n")); + *Status = STATUS_CSS_SCRAMBLED_SECTOR; + break; + case SCSI_SENSEQ_MEDIA_CODE_MISMATCHED_TO_LOGICAL_UNIT: + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, + "ClassInterpretSenseInfo: " + "Media region does not logical unit " + "region\n")); + *Status = STATUS_CSS_REGION_MISMATCH; + break; + case SCSI_SENSEQ_LOGICAL_UNIT_RESET_COUNT_ERROR: + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, + "ClassInterpretSenseInfo: " + "Region set error -- region may " + "be permanent\n")); + *Status = STATUS_CSS_RESETS_EXHAUSTED; + break; + } // end switch of ASCQ for COPY_PROTECTION_FAILURE + + break; + } + + case SCSI_ADSENSE_MUSIC_AREA: { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Music area\n")); + break; + } + + case SCSI_ADSENSE_DATA_AREA: { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Data area\n")); + break; + } + + case SCSI_ADSENSE_VOLUME_OVERFLOW: { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Volume overflow\n")); + break; + } + + } // end switch (addlSenseCode) + + break; + } // end SCSI_SENSE_ILLEGAL_REQUEST + + case SCSI_SENSE_UNIT_ATTENTION: { + + ULONG count; + + // + // A media change may have occured so increment the change + // count for the physical device + // + + count = InterlockedIncrement((volatile LONG *)&fdoExtension->MediaChangeCount); + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClassInterpretSenseInfo: " + "Media change count for device %d incremented to %#lx\n", + fdoExtension->DeviceNumber, count)); + + + switch (addlSenseCode) { + case SCSI_ADSENSE_MEDIUM_CHANGED: { + logRetryableError = FALSE; + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClassInterpretSenseInfo: " + "Media changed\n")); + + if (!TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA)) { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_MCN, "ClassInterpretSenseInfo: " + "Media Changed on non-removable device %p\n", + Fdo)); + } + ClassSetMediaChangeState(fdoExtension, MediaPresent, FALSE); + break; + } + + case SCSI_ADSENSE_BUS_RESET: { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Bus reset\n")); + break; + } + + case SCSI_ADSENSE_PARAMETERS_CHANGED: { + logRetryableError = FALSE; + if (addlSenseCodeQual == SCSI_SENSEQ_CAPACITY_DATA_CHANGED) { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Device capacity changed (e.g. thinly provisioned LUN). Retry the request.\n")); + + ClassQueueCapacityChangedEventWorker(Fdo); + + // + // Retry with 1 second delay as ClassQueueCapacityChangedEventWorker may trigger a couple of commands sent to disk. + // + retryInterval = 1; + retry = TRUE; + } + break; + } + + case SCSI_ADSENSE_LB_PROVISIONING: { + + switch (addlSenseCodeQual) { + + case SCSI_SENSEQ_SOFT_THRESHOLD_REACHED: { + + logRetryableError = FALSE; + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Device (%p) has hit a soft threshold.\n", + Fdo)); + + // + // This indicates that a resource provisioned or thinly + // provisioned device has hit a soft threshold. Queue a + // worker thread to log a system event and then retry the + // original request. + // + ClassQueueThresholdEventWorker(Fdo); + break; + } + default: { + retry = FALSE; + break; + } + + } // end switch (addlSenseCodeQual) + break; + } + + case SCSI_ADSENSE_OPERATING_CONDITIONS_CHANGED: { + + if (addlSenseCodeQual == SCSI_SENSEQ_MICROCODE_CHANGED) { + // + // Device firmware has been changed. Retry the request. + // + logRetryableError = TRUE; + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Device firmware has been changed.\n")); + + retryInterval = 1; + retry = TRUE; + } else { + // + // Device information has changed, we need to rescan the + // bus for changed information such as the capacity. + // + logRetryableError = FALSE; + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Device information changed. Invalidate the bus\n")); + + if (addlSenseCodeQual == SCSI_SENSEQ_INQUIRY_DATA_CHANGED) { + + ClassQueueProvisioningTypeChangedEventWorker(Fdo); + } + + if (addlSenseCodeQual == SCSI_SENSEQ_INQUIRY_DATA_CHANGED || + addlSenseCodeQual == SCSI_SENSEQ_OPERATING_DEFINITION_CHANGED) { + + // + // Since either the LB provisioning type changed, or the block/slab size + // changed, next time anyone trying to query the FunctionSupportInfo, we + // will requery the device. + // + InterlockedIncrement((volatile LONG *)&fdoExtension->FunctionSupportInfo->ChangeRequestCount); + } + + IoInvalidateDeviceRelations(fdoExtension->LowerPdo, BusRelations); + retryInterval = 5; + } + break; + } + + case SCSI_ADSENSE_OPERATOR_REQUEST: { + switch (addlSenseCodeQual) { + + case SCSI_SENSEQ_MEDIUM_REMOVAL: { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Ejection request received!\n")); + ClassSendEjectionNotification(fdoExtension); + break; + } + + case SCSI_SENSEQ_WRITE_PROTECT_ENABLE: { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Operator selected write permit?! " + "(unsupported!)\n")); + break; + } + + case SCSI_SENSEQ_WRITE_PROTECT_DISABLE: { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Operator selected write protect?! " + "(unsupported!)\n")); + break; + } + } + } + + case SCSI_ADSENSE_FAILURE_PREDICTION_THRESHOLD_EXCEEDED: { + + UCHAR wmiEventData[sizeof(ULONG)+sizeof(UCHAR)] = {0}; + + *((PULONG)wmiEventData) = sizeof(UCHAR); + wmiEventData[sizeof(ULONG)] = addlSenseCodeQual; + + // + // Don't log another eventlog if we have already logged once + // NOTE: this should have been interlocked, but the structure + // was publicly defined to use a BOOLEAN (char). Since + // media only reports these errors once per X minutes, + // the potential race condition is nearly non-existant. + // the worst case is duplicate log entries, so ignore. + // + + logError = FALSE; + if (fdoExtension->FailurePredicted == 0) { + logError = TRUE; + } + fdoExtension->FailureReason = addlSenseCodeQual; + logStatus = IO_WRN_FAILURE_PREDICTED; + + ClassNotifyFailurePredicted(fdoExtension, + (PUCHAR)wmiEventData, + sizeof(wmiEventData), + FALSE, // do not log error + 4, // unique error value + SrbGetPathId(Srb), + SrbGetTargetId(Srb), + SrbGetLun(Srb)); + + fdoExtension->FailurePredicted = TRUE; + + // + // Since this is a Unit Attention we need to make + // sure we retry this request. + // + retry = TRUE; + + break; + } + + default: { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Unit attention\n")); + break; + } + + } // end switch (addlSenseCode) + + if (TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA)) + { + + if ((ClassGetVpb(Fdo) != NULL) && (ClassGetVpb(Fdo)->Flags & VPB_MOUNTED)) + { + // + // Set bit to indicate that media may have changed + // and volume needs verification. + // + + SET_FLAG(Fdo->Flags, DO_VERIFY_VOLUME); + + *Status = STATUS_VERIFY_REQUIRED; + retry = FALSE; + } + else { + *Status = STATUS_IO_DEVICE_ERROR; + } + } + else + { + *Status = STATUS_IO_DEVICE_ERROR; + } + + break; + + } // end SCSI_SENSE_UNIT_ATTENTION + + case SCSI_SENSE_DATA_PROTECT: { + + retry = FALSE; + + if (addlSenseCode == SCSI_ADSENSE_WRITE_PROTECT) + { + switch (addlSenseCodeQual) { + + case SCSI_SENSEQ_SPACE_ALLOC_FAILED_WRITE_PROTECT: { + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Device's (%p) resources are exhausted.\n", + Fdo)); + + ClassQueueResourceExhaustionEventWorker(Fdo); + + // + // This indicates that a thinly-provisioned device has + // hit a permanent resource exhaustion. We need to + // return this status code so that patmgr can take the + // disk offline. + // + *Status = STATUS_DISK_RESOURCES_EXHAUSTED; + break; + } + default: + { + break; + } + + } // end switch addlSenseCodeQual + } + else + { + if (IS_SCSIOP_WRITE(cdbOpcode)) { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Media write protected\n")); + *Status = STATUS_MEDIA_WRITE_PROTECTED; + } else { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Access denied\n")); + *Status = STATUS_ACCESS_DENIED; + } + } + break; + } // end SCSI_SENSE_DATA_PROTECT + + case SCSI_SENSE_BLANK_CHECK: { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Media blank check\n")); + retry = FALSE; + *Status = STATUS_NO_DATA_DETECTED; + break; + } // end SCSI_SENSE_BLANK_CHECK + + case SCSI_SENSE_COPY_ABORTED: { + + switch (addlSenseCode) { + + case SCSI_ADSENSE_COPY_TARGET_DEVICE_ERROR: { + + switch (addlSenseCodeQual) { + + // + // 1. Target truncated the data transfer. + // + case SCSI_SENSEQ_DATA_UNDERRUN: { + + TracePrint((TRACE_LEVEL_WARNING, + TRACE_FLAG_IOCTL, + "ClassInterpretSenseInfo (%p): Data transfer was truncated (command %x)\n", + Fdo, + cdbOpcode)); + + *Status = STATUS_SUCCESS; + retry = FALSE; + break; + } + } + break; + } + } + break; + } + + case SCSI_SENSE_ABORTED_COMMAND: { + if (ClasspIsOffloadDataTransferCommand(cdb)) { + + switch (addlSenseCode) { + + case SCSI_ADSENSE_COPY_TARGET_DEVICE_ERROR: { + + switch (addlSenseCodeQual) { + + // + // 1. Target truncated the data transfer. + // + case SCSI_SENSEQ_DATA_UNDERRUN: { + + TracePrint((TRACE_LEVEL_WARNING, + TRACE_FLAG_IOCTL, + "ClassInterpretSenseInfo (%p): Target has truncated the data transfer (command %x)\n", + Fdo, + cdbOpcode)); + + *Status = STATUS_SUCCESS; + retry = FALSE; + break; + } + } + break; + } + + case SCSI_ADSENSE_RESOURCE_FAILURE: { + + switch (addlSenseCodeQual) { + + // + // 1. Copy manager wasn't able to finish the operation because of insuffient resources + // (e.g. microsnapshot failure on read, no space on write, etc.) + // + case SCSI_SENSEQ_INSUFFICIENT_RESOURCES: { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassInterpretSenseInfo (%p): Target has insufficient resources (command %x)\n", + Fdo, + cdb->CDB6GENERIC.OperationCode)); + + *Status = STATUS_INSUFFICIENT_RESOURCES; + retry = FALSE; + break; + } + } + break; + } + } + } else { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Command aborted\n")); + *Status = STATUS_IO_DEVICE_ERROR; + retryInterval = 1; + } + break; + } // end SCSI_SENSE_ABORTED_COMMAND + + default: { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Unrecognized sense code\n")); + *Status = STATUS_IO_DEVICE_ERROR; + break; + } + + } // end switch (senseKey) + + + + // + // Try to determine bad sector information from sense data + // + + if (((IS_SCSIOP_READWRITE(cdbOpcode)) || + (cdbOpcode == SCSIOP_VERIFY) || + (cdbOpcode == SCSIOP_VERIFY16)) && cdb) { + + if (isInformationValid) + { + readSector = 0; + badSector = information; + + if (cdbOpcode == SCSIOP_READ16 || cdbOpcode == SCSIOP_WRITE16 || cdbOpcode == SCSIOP_VERIFY16) { + REVERSE_BYTES_QUAD(&readSector, &(cdb->AsByte[2])); + } else { + REVERSE_BYTES(&readSector, &(cdb->AsByte[2])); + } + + if (cdbOpcode == SCSIOP_READ || cdbOpcode == SCSIOP_WRITE || cdbOpcode == SCSIOP_VERIFY) { + REVERSE_BYTES_SHORT(&index, &(cdb->CDB10.TransferBlocksMsb)); + } else if (cdbOpcode == SCSIOP_READ6 || cdbOpcode == SCSIOP_WRITE6) { + index = cdb->CDB6READWRITE.TransferBlocks; + } else if(cdbOpcode == SCSIOP_READ12 || cdbOpcode == SCSIOP_WRITE12) { + REVERSE_BYTES(&index, &(cdb->CDB12.TransferLength)); + } else { + REVERSE_BYTES(&index, &(cdb->CDB16.TransferLength)); + } + + // + // Make sure the bad sector is within the read sectors. + // + + if (!(badSector >= readSector && badSector < readSector + index)) { + badSector = readSector; + } + } + } + } + +__ClassInterpretSenseInfo_ProcessingInvalidSenseBuffer: + + if (!validSense) { + + // + // Request sense buffer not valid. No sense information + // to pinpoint the error. Return general request fail. + // + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Request sense info not valid. SrbStatus %2x\n", + SRB_STATUS(Srb->SrbStatus))); + retry = TRUE; + + + switch (SRB_STATUS(Srb->SrbStatus)) { + case SRB_STATUS_ABORTED: { + + // + // Update the error count for the device. + // + + incrementErrorCount = TRUE; + *Status = STATUS_IO_TIMEOUT; + retryInterval = 1; + retry = TRUE; + break; + } + + case SRB_STATUS_ERROR: { + + *Status = STATUS_IO_DEVICE_ERROR; + if (SrbGetScsiStatus(Srb) == SCSISTAT_GOOD) { + + // + // This is some strange return code. Update the error + // count for the device. + // + + incrementErrorCount = TRUE; + + } else if (SrbGetScsiStatus(Srb) == SCSISTAT_BUSY) { + + *Status = STATUS_DEVICE_NOT_READY; + logRetryableError = FALSE; + } + + break; + } + + case SRB_STATUS_INVALID_REQUEST: { + *Status = STATUS_INVALID_DEVICE_REQUEST; + retry = FALSE; + break; + } + + case SRB_STATUS_INVALID_PATH_ID: + case SRB_STATUS_NO_DEVICE: + case SRB_STATUS_NO_HBA: + case SRB_STATUS_INVALID_LUN: + case SRB_STATUS_INVALID_TARGET_ID: { + *Status = STATUS_NO_SUCH_DEVICE; + retry = FALSE; + break; + } + + case SRB_STATUS_SELECTION_TIMEOUT: { + logError = TRUE; + logStatus = IO_ERR_NOT_READY; + uniqueId = 260; + *Status = STATUS_DEVICE_NOT_CONNECTED; + retry = FALSE; + break; + } + + case SRB_STATUS_TIMEOUT: + case SRB_STATUS_COMMAND_TIMEOUT: { + + // + // Update the error count for the device. + // + incrementErrorCount = TRUE; + *Status = STATUS_IO_TIMEOUT; + break; + } + + case SRB_STATUS_PARITY_ERROR: + case SRB_STATUS_UNEXPECTED_BUS_FREE: + + // + // Update the error count for the device + // and fall through to below + // + incrementErrorCount = TRUE; + + case SRB_STATUS_BUS_RESET: { + + *Status = STATUS_IO_DEVICE_ERROR; + logRetryableError = FALSE; + break; + } + + case SRB_STATUS_DATA_OVERRUN: { + + *Status = STATUS_DATA_OVERRUN; + retry = FALSE; + + // + // For some commands, we allocate a buffer that may be + // larger than necessary. In these cases, the SRB may be + // returned with SRB_STATUS_DATA_OVERRUN to indicate a + // buffer *underrun*. However, the command was still + // successful so we ensure STATUS_SUCCESS is returned. + // We will also prevent these errors from causing noise in + // the error logs. + // + if ((cdbOpcode == SCSIOP_MODE_SENSE && SrbGetDataTransferLength(Srb) <= cdb->MODE_SENSE.AllocationLength) || + (cdbOpcode == SCSIOP_INQUIRY && SrbGetDataTransferLength(Srb) <= cdb->CDB6INQUIRY.AllocationLength)) { + *Status = STATUS_SUCCESS; + logErrorInternal = FALSE; + logError = FALSE; + } else if (cdbOpcode == SCSIOP_MODE_SENSE10) { + USHORT allocationLength; + REVERSE_BYTES_SHORT(&(cdb->MODE_SENSE10.AllocationLength), &allocationLength); + if (SrbGetDataTransferLength(Srb) <= allocationLength) { + *Status = STATUS_SUCCESS; + logErrorInternal = FALSE; + logError = FALSE; + } + } else if (ClasspIsReceiveTokenInformation(cdb)) { + ULONG allocationLength; + REVERSE_BYTES(&(cdb->RECEIVE_TOKEN_INFORMATION.AllocationLength), &allocationLength); + if (SrbGetDataTransferLength(Srb) <= allocationLength) { + *Status = STATUS_SUCCESS; + logErrorInternal = FALSE; + logError = FALSE; + } + } + + break; + } + + case SRB_STATUS_PHASE_SEQUENCE_FAILURE: { + + // + // Update the error count for the device. + // + + incrementErrorCount = TRUE; + *Status = STATUS_IO_DEVICE_ERROR; + + // + // If there was phase sequence error then limit the number of + // retries. + // + + if (RetryCount > 1 ) { + retry = FALSE; + } + + break; + } + + case SRB_STATUS_REQUEST_FLUSHED: { + + // + // If the status needs verification bit is set. Then set + // the status to need verification and no retry; otherwise, + // just retry the request. + // + + if (TEST_FLAG(Fdo->Flags, DO_VERIFY_VOLUME)) { + + *Status = STATUS_VERIFY_REQUIRED; + retry = FALSE; + + } else { + *Status = STATUS_IO_DEVICE_ERROR; + logRetryableError = FALSE; + } + + break; + } + + default: { + logError = TRUE; + logStatus = IO_ERR_CONTROLLER_ERROR; + uniqueId = 259; + *Status = STATUS_IO_DEVICE_ERROR; + unhandledError = TRUE; + logRetryableError = FALSE; + break; + } + } + + + // + // NTRAID #183546 - if we support GESN subtype NOT_READY events, and + // we know from a previous poll when the device will be ready (ETA) + // we should delay the retry more appropriately than just guessing. + // + /* + if (fdoExtension->MediaChangeDetectionInfo && + fdoExtension->MediaChangeDetectionInfo->Gesn.Supported && + TEST_FLAG(fdoExtension->MediaChangeDetectionInfo->Gesn.EventMask, + NOTIFICATION_DEVICE_BUSY_CLASS_MASK) + ) { + // check if Gesn.ReadyTime if greater than current tick count + // if so, delay that long (from 1 to 30 seconds max?) + // else, leave the guess of time alone. + } + */ + + } + + } + + if (incrementErrorCount) { + + // + // if any error count occurred, delay the retry of this io by + // at least one second, if caller supports it. + // + + if (retryInterval == 0) { + retryInterval = 1; + } + ClasspPerfIncrementErrorCount(fdoExtension); + } + + // + // If there is a class specific error handler call it. + // + + if (fdoExtension->CommonExtension.DevInfo->ClassError != NULL) { + + SCSI_REQUEST_BLOCK tempSrb = {0}; + PSCSI_REQUEST_BLOCK srbPtr = (PSCSI_REQUEST_BLOCK)Srb; + + // + // If class driver does not support extended SRB and this is + // an extended SRB, convert to legacy SRB and pass to class + // driver. + // + if ((Srb->Function == SRB_FUNCTION_STORAGE_REQUEST_BLOCK) && + ((fdoExtension->CommonExtension.DriverExtension->SrbSupport & + CLASS_SRB_STORAGE_REQUEST_BLOCK) == 0)) { + ClasspConvertToScsiRequestBlock(&tempSrb, (PSTORAGE_REQUEST_BLOCK)Srb); + srbPtr = &tempSrb; + } + + fdoExtension->CommonExtension.DevInfo->ClassError(Fdo, + srbPtr, + Status, + &retry); + } + + // + // If the caller wants to know the suggested retry interval tell them. + // + + if (ARGUMENT_PRESENT(RetryInterval)) { + *RetryInterval = retryInterval; + } + + // + // The RESERVE(6) / RELEASE(6) commands are optional. So + // if they aren't supported, try the 10-byte equivalents + // + + cdb = SrbGetCdb(Srb); + if (cdb) { + cdbOpcode = cdb->CDB6GENERIC.OperationCode; + } + + if ((cdbOpcode == SCSIOP_RESERVE_UNIT || + cdbOpcode == SCSIOP_RELEASE_UNIT) && cdb) + { + if (*Status == STATUS_INVALID_DEVICE_REQUEST) + { + SrbSetCdbLength(Srb, 10); + cdb->CDB10.OperationCode = (cdb->CDB6GENERIC.OperationCode == SCSIOP_RESERVE_UNIT) ? SCSIOP_RESERVE_UNIT10 : SCSIOP_RELEASE_UNIT10; + + SET_FLAG(fdoExtension->PrivateFdoData->HackFlags, FDO_HACK_NO_RESERVE6); + retry = TRUE; + } + } + +#if DBG + + // + // Ensure that for read/write requests, only return STATUS_DEVICE_BUSY if + // reservation conflict. + // + if (IS_SCSIOP_READWRITE(cdbOpcode) && (*Status == STATUS_DEVICE_BUSY)) { + NT_ASSERT(isReservationConflict == TRUE); + } + +#endif + + /* + * LOG the error: + * If logErrorInternal is set, log the error in our internal log. + * If logError is set, also log the error in the system log. + */ + if (logErrorInternal || logError) { + ULONG totalSize; + ULONG senseBufferSize = 0; + IO_ERROR_LOG_PACKET staticErrLogEntry = {0}; + CLASS_ERROR_LOG_DATA staticErrLogData = {0}; + SENSE_DATA convertedSenseBuffer = {0}; + UCHAR convertedSenseBufferLength = 0; + BOOLEAN senseDataConverted = FALSE; + + // + // Logic below assumes that IO_ERROR_LOG_PACKET + CLASS_ERROR_LOG_DATA + // is less than ERROR_LOG_MAXIMUM_SIZE which is not true for extended SRB. + // Given that classpnp currently does not use >16 byte CDB, we'll convert + // an extended SRB to SCSI_REQUEST_BLOCK instead of changing this code. + // More changes will need to be made when classpnp starts using >16 byte + // CDBs. + // + + // + // Calculate the total size of the error log entry. + // add to totalSize in the order that they are used. + // the advantage to calculating all the sizes here is + // that we don't have to do a bunch of extraneous checks + // later on in this code path. + // + totalSize = sizeof(IO_ERROR_LOG_PACKET) // required + + sizeof(CLASS_ERROR_LOG_DATA);// struct for ease + + // + // also save any available extra sense data, up to the maximum errlog + // packet size . WMI should be used for real-time analysis. + // the event log should only be used for post-mortem debugging. + // + if ((TEST_FLAG(Srb->SrbStatus, SRB_STATUS_AUTOSENSE_VALID)) && senseBuffer) { + + UCHAR validSenseBytes = 0; + UCHAR senseInfoBufferLength = 0; + + senseInfoBufferLength = SrbGetSenseInfoBufferLength(Srb); + + // + // If sense data is in Descriptor format, convert it to Fixed format + // for the private log. + // + + if (IsDescriptorSenseDataFormat(senseBuffer)) { + + convertedSenseBufferLength = sizeof(convertedSenseBuffer); + + senseDataConverted = ScsiConvertToFixedSenseFormat(senseBuffer, + senseInfoBufferLength, + (PVOID)&convertedSenseBuffer, + convertedSenseBufferLength); + } + + // + // For System Log, copy the maximum amount of available sense data + // + + if (ScsiGetTotalSenseByteCountIndicated(senseBuffer, + senseInfoBufferLength, + &validSenseBytes)) { + + // + // If it is able to determine number of valid bytes, + // copy the maximum amount of available + // sense data that can be saved into the the errlog. + // + + // + // set to save the most sense buffer possible + // + + senseBufferSize = max(validSenseBytes, sizeof(staticErrLogData.SenseData)); + senseBufferSize = min(senseBufferSize, senseInfoBufferLength); + + } else { + // + // it's smaller than required to read the total number of + // valid bytes, so just use the SenseInfoBufferLength field. + // + senseBufferSize = senseInfoBufferLength; + } + + /* + * Bump totalSize by the number of extra senseBuffer bytes + * (beyond the default sense buffer within CLASS_ERROR_LOG_DATA). + * Make sure to never allocate more than ERROR_LOG_MAXIMUM_SIZE. + */ + if (senseBufferSize > sizeof(staticErrLogData.SenseData)){ + totalSize += senseBufferSize-sizeof(staticErrLogData.SenseData); + if (totalSize > ERROR_LOG_MAXIMUM_SIZE){ + senseBufferSize -= totalSize-ERROR_LOG_MAXIMUM_SIZE; + totalSize = ERROR_LOG_MAXIMUM_SIZE; + } + } + } + + // + // If we've used up all of our retry attempts, set the final status to + // reflect the appropriate result. + // + // ISSUE: the test below should also check RetryCount to determine if we will actually retry, + // but there is no easy test because we'd have to consider the original retry count + // for the op; besides, InterpretTransferPacketError sometimes ignores the retry + // decision returned by this function. So just ErrorRetried to be true in the majority case. + // + if (retry){ + staticErrLogEntry.FinalStatus = STATUS_SUCCESS; + staticErrLogData.ErrorRetried = TRUE; + } else { + staticErrLogEntry.FinalStatus = *Status; + } + + // + // Don't log generic IO_WARNING_PAGING_FAILURE message if either the + // I/O is retried, or it completed successfully. + // + if (logStatus == IO_WARNING_PAGING_FAILURE && + (retry || NT_SUCCESS(*Status)) ) { + logError = FALSE; + } + + if (TEST_FLAG(SrbGetSrbFlags(Srb), SRB_CLASS_FLAGS_PAGING)) { + staticErrLogData.ErrorPaging = TRUE; + } + if (unhandledError) { + staticErrLogData.ErrorUnhandled = TRUE; + } + + // + // Calculate the device offset if there is a geometry. + // + staticErrLogEntry.DeviceOffset.QuadPart = (LONGLONG)badSector; + staticErrLogEntry.DeviceOffset.QuadPart *= (LONGLONG)fdoExtension->DiskGeometry.BytesPerSector; + if (logStatus == -1){ + staticErrLogEntry.ErrorCode = STATUS_IO_DEVICE_ERROR; + } else { + staticErrLogEntry.ErrorCode = logStatus; + } + + /* + * The dump data follows the IO_ERROR_LOG_PACKET + */ + staticErrLogEntry.DumpDataSize = (USHORT)totalSize - sizeof(IO_ERROR_LOG_PACKET); + + staticErrLogEntry.SequenceNumber = 0; + staticErrLogEntry.MajorFunctionCode = MajorFunctionCode; + staticErrLogEntry.IoControlCode = IoDeviceCode; + staticErrLogEntry.RetryCount = (UCHAR) RetryCount; + staticErrLogEntry.UniqueErrorValue = uniqueId; + + KeQueryTickCount(&staticErrLogData.TickCount); + staticErrLogData.PortNumber = (ULONG)-1; + + /* + * Save the entire contents of the SRB. + */ + if (Srb->Function == SRB_FUNCTION_STORAGE_REQUEST_BLOCK) { + ClasspConvertToScsiRequestBlock(&staticErrLogData.Srb, (PSTORAGE_REQUEST_BLOCK)Srb); + } else { + staticErrLogData.Srb = *(PSCSI_REQUEST_BLOCK)Srb; + } + + /* + * For our private log, save just the default length of the SENSE_DATA. + */ + + if ((senseBufferSize != 0) && senseBuffer) { + + // + // If sense buffer is in Fixed format, put it in the private log + // + // If sense buffer is in Descriptor format, put it in the private log if conversion to Fixed format + // succeeded. Otherwise, do not put it in the private log. + // + // If sense buffer is in unknown format, the device or the driver probably does not populate + // the first byte of sense data, we probably still want to log error in this case assuming + // it's fixed format, so that its sense key, its additional sense code, and its additional sense code + // qualifier would be shown in the debugger extension output. By doing so, it minimizes any potential + // negative impacts to our ability to diagnose issue. + // + if (IsDescriptorSenseDataFormat(senseBuffer)) { + if (senseDataConverted) { + RtlCopyMemory(&staticErrLogData.SenseData, &convertedSenseBuffer, min(convertedSenseBufferLength, sizeof(staticErrLogData.SenseData))); + } + } else { + RtlCopyMemory(&staticErrLogData.SenseData, senseBuffer, min(senseBufferSize, sizeof(staticErrLogData.SenseData))); + } + } + + /* + * Save the error log in our context. + * We only save the default sense buffer length. + */ + if (logErrorInternal) { + KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql); + fdoData->ErrorLogs[fdoData->ErrorLogNextIndex] = staticErrLogData; + fdoData->ErrorLogNextIndex++; + fdoData->ErrorLogNextIndex %= NUM_ERROR_LOG_ENTRIES; + KeReleaseSpinLock(&fdoData->SpinLock, oldIrql); + } + + /* + * Log an event if an IO is being retried for reasons that may indicate + * a transient/permanent problem with the I_T_L nexus. But log the event + * only once per retried IO. + */ + if (!IS_SCSIOP_READWRITE(cdbOpcode) || + !retry || + (RetryCount != 0)) { + + logRetryableError = FALSE; + } + + if (logRetryableError) { + + logError = TRUE; + } + + /* + * If logError is set, also save this log in the system's error log. + * But make sure we don't log TUR failures over and over + * (e.g. if an external drive was switched off and we're still sending TUR's to it every second). + */ + + if (logError) + { + // + // We do not want to log certain system events repetitively + // + + cdb = SrbGetCdb(Srb); + if (cdb) { + switch (cdb->CDB10.OperationCode) + { + case SCSIOP_TEST_UNIT_READY: + { + if (fdoData->LoggedTURFailureSinceLastIO) + { + logError = FALSE; + } + else + { + fdoData->LoggedTURFailureSinceLastIO = TRUE; + } + + break; + } + + case SCSIOP_SYNCHRONIZE_CACHE: + { + if (fdoData->LoggedSYNCFailure) + { + logError = FALSE; + } + else + { + fdoData->LoggedSYNCFailure = TRUE; + } + + break; + } + } + } + } + + if (logError){ + + if (logRetryableError) { + + NT_ASSERT(IS_SCSIOP_READWRITE(cdbOpcode)); + + // + // A large Disk TimeOutValue (like 60 seconds) results in giving a command a + // large window to complete in. However, if the target returns a retryable error + // just prior to the command timing out, and if multiple retries kick in, it may + // take a significantly long time for the request to complete back to the + // application, leading to a user perception of a hung system. So log an event + // for retried IO so that an admin can help explain the reason for this behavior. + // + ClasspQueueLogIOEventWithContextWorker(Fdo, + senseBufferSize, + senseBuffer, + SRB_STATUS(Srb->SrbStatus), + SrbGetScsiStatus(Srb), + (ULONG)IO_WARNING_IO_OPERATION_RETRIED, + cdbLength, + cdb, + NULL); + + } else { + + PIO_ERROR_LOG_PACKET errorLogEntry; + PCLASS_ERROR_LOG_DATA errlogData; + + errorLogEntry = (PIO_ERROR_LOG_PACKET)IoAllocateErrorLogEntry(Fdo, (UCHAR)totalSize); + if (errorLogEntry){ + errlogData = (PCLASS_ERROR_LOG_DATA)errorLogEntry->DumpData; + + *errorLogEntry = staticErrLogEntry; + *errlogData = staticErrLogData; + + /* + * For the system log, copy as much of the sense buffer as possible. + */ + if ((senseBufferSize != 0) && senseBuffer) { + RtlCopyMemory(&errlogData->SenseData, senseBuffer, senseBufferSize); + } + + /* + * Write the error log packet to the system error logging thread. + * It will be freed by the kernel. + */ + IoWriteErrorLogEntry(errorLogEntry); + } + } + } + } + + return retry; + +} // end ClassInterpretSenseInfo() + + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassModeSense() + +Routine Description: + + This routine sends a mode sense command to a target ID and returns + when it is complete. + +Arguments: + + Fdo - Supplies the functional device object associated with this request. + + ModeSenseBuffer - Supplies a buffer to store the sense data. + + Length - Supplies the length in bytes of the mode sense buffer. + + PageMode - Supplies the page or pages of mode sense data to be retrived. + +Return Value: + + Length of the transferred data is returned. + +--*/ +ULONG ClassModeSense( + _In_ PDEVICE_OBJECT Fdo, + _In_reads_bytes_(Length) PCHAR ModeSenseBuffer, + _In_ ULONG Length, + _In_ UCHAR PageMode + ) +{ + PAGED_CODE(); + + return ClasspModeSense(Fdo, + ModeSenseBuffer, + Length, + PageMode, + MODE_SENSE_CURRENT_VALUES); +} + + +ULONG +ClassModeSenseEx( + _In_ PDEVICE_OBJECT Fdo, + _In_reads_bytes_(Length) PCHAR ModeSenseBuffer, + _In_ ULONG Length, + _In_ UCHAR PageMode, + _In_ UCHAR PageControl + ) +{ + PAGED_CODE(); +#if (NTDDI_VERSION >= NTDDI_WINBLUE) + return ClasspModeSense(Fdo, + ModeSenseBuffer, + Length, + PageMode, + PageControl); +#else + UNREFERENCED_PARAMETER(Fdo); + UNREFERENCED_PARAMETER(ModeSenseBuffer); + UNREFERENCED_PARAMETER(Length); + UNREFERENCED_PARAMETER(PageMode); + UNREFERENCED_PARAMETER(PageControl); + return 0; +#endif +} + +ULONG ClasspModeSense( + _In_ PDEVICE_OBJECT Fdo, + _In_reads_bytes_(Length) PCHAR ModeSenseBuffer, + _In_ ULONG Length, + _In_ UCHAR PageMode, + _In_ UCHAR PageControl + ) +/* +Routine Description: + + This routine sends a mode sense command to a target ID and returns + when it is complete. + +Arguments: + + Fdo - Supplies the functional device object associated with this request. + + ModeSenseBuffer - Supplies a buffer to store the sense data. + + Length - Supplies the length in bytes of the mode sense buffer. + + PageMode - Supplies the page or pages of mode sense data to be retrived. + + PageControl - Supplies the page control value of the request, which is + one of the following: + MODE_SENSE_CURRENT_VALUES + MODE_SENSE_CHANGEABLE_VALUES + MODE_SENSE_DEFAULT_VAULES + MODE_SENSE_SAVED_VALUES + +Return Value: + + Length of the transferred data is returned. + +--*/ +{ + ULONG lengthTransferred = 0; + PMDL senseBufferMdl; + + PAGED_CODE(); + + senseBufferMdl = BuildDeviceInputMdl(ModeSenseBuffer, Length); + if (senseBufferMdl){ + + TRANSFER_PACKET *pkt = DequeueFreeTransferPacket(Fdo, TRUE); + if (pkt){ + KEVENT event; + IRP pseudoIrp = {0}; + + /* + * Store the number of packets servicing the irp (one) + * inside the original IRP. It will be used to counted down + * to zero when the packet completes. + * Initialize the original IRP's status to success. + * If the packet fails, we will set it to the error status. + */ + pseudoIrp.Tail.Overlay.DriverContext[0] = LongToPtr(1); + pseudoIrp.IoStatus.Status = STATUS_SUCCESS; + pseudoIrp.IoStatus.Information = 0; + pseudoIrp.MdlAddress = senseBufferMdl; + + /* + * Set this up as a SYNCHRONOUS transfer, submit it, + * and wait for the packet to complete. The result + * status will be written to the original irp. + */ + NT_ASSERT(Length <= 0x0ff); + KeInitializeEvent(&event, SynchronizationEvent, FALSE); + SetupModeSenseTransferPacket(pkt, &event, ModeSenseBuffer, (UCHAR)Length, PageMode, 0, &pseudoIrp, PageControl); + SubmitTransferPacket(pkt); + (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); + + if (NT_SUCCESS(pseudoIrp.IoStatus.Status)){ + lengthTransferred = (ULONG)pseudoIrp.IoStatus.Information; + } + else { + /* + * This request can sometimes fail legitimately + * (e.g. when a SCSI device is attached but turned off) + * so this is not necessarily a device/driver bug. + */ + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClasspModeSense on Fdo %ph failed with status %xh.", Fdo, pseudoIrp.IoStatus.Status)); + } + } + + FreeDeviceInputMdl(senseBufferMdl); + } + + return lengthTransferred; +} + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassFindModePage() + +Routine Description: + + This routine scans through the mode sense data and finds the requested + mode sense page code. + +Arguments: + ModeSenseBuffer - Supplies a pointer to the mode sense data. + + Length - Indicates the length of valid data. + + PageMode - Supplies the page mode to be searched for. + + Use6Byte - Indicates whether 6 or 10 byte mode sense was used. + +Return Value: + + A pointer to the the requested mode page. If the mode page was not found + then NULL is return. + +--*/ +PVOID +ClassFindModePage( + _In_reads_bytes_(Length) PCHAR ModeSenseBuffer, + _In_ ULONG Length, + _In_ UCHAR PageMode, + _In_ BOOLEAN Use6Byte + ) +{ + PUCHAR limit; + ULONG parameterHeaderLength; + PVOID result = NULL; + + limit = (PUCHAR)ModeSenseBuffer + Length; + parameterHeaderLength = (Use6Byte) ? sizeof(MODE_PARAMETER_HEADER) : sizeof(MODE_PARAMETER_HEADER10); + + if (Length >= parameterHeaderLength) { + + PMODE_PARAMETER_HEADER10 modeParam10; + ULONG blockDescriptorLength; + + /* + * Skip the mode select header and block descriptors. + */ + if (Use6Byte){ + blockDescriptorLength = ((PMODE_PARAMETER_HEADER) ModeSenseBuffer)->BlockDescriptorLength; + } + else { + modeParam10 = (PMODE_PARAMETER_HEADER10) ModeSenseBuffer; + blockDescriptorLength = modeParam10->BlockDescriptorLength[1]; + } + + ModeSenseBuffer += parameterHeaderLength + blockDescriptorLength; + + // + // ModeSenseBuffer now points at pages. Walk the pages looking for the + // requested page until the limit is reached. + // + + while (ModeSenseBuffer + + RTL_SIZEOF_THROUGH_FIELD(MODE_DISCONNECT_PAGE, PageLength) < (PCHAR)limit) { + + if (((PMODE_DISCONNECT_PAGE) ModeSenseBuffer)->PageCode == PageMode) { + + /* + * found the mode page. make sure it's safe to touch it all + * before returning the pointer to caller + */ + + if (ModeSenseBuffer + ((PMODE_DISCONNECT_PAGE)ModeSenseBuffer)->PageLength > (PCHAR)limit) { + /* + * Return NULL since the page is not safe to access in full + */ + result = NULL; + } + else { + result = ModeSenseBuffer; + } + break; + } + + // + // Advance to the next page which is 4-byte-aligned offset after this page. + // + ModeSenseBuffer += + ((PMODE_DISCONNECT_PAGE) ModeSenseBuffer)->PageLength + + RTL_SIZEOF_THROUGH_FIELD(MODE_DISCONNECT_PAGE, PageLength); + + } + } + + return result; +} // end ClassFindModePage() + + +NTSTATUS +ClassModeSelect( + _In_ PDEVICE_OBJECT Fdo, + _In_reads_bytes_(Length) PCHAR ModeSelectBuffer, + _In_ ULONG Length, + _In_ BOOLEAN SavePages + ) +{ +#if (NTDDI_VERSION >= NTDDI_WINBLUE) + return ClasspModeSelect(Fdo, + ModeSelectBuffer, + Length, + SavePages); +#else + UNREFERENCED_PARAMETER(Fdo); + UNREFERENCED_PARAMETER(ModeSelectBuffer); + UNREFERENCED_PARAMETER(Length); + UNREFERENCED_PARAMETER(SavePages); + return STATUS_NOT_SUPPORTED; +#endif +} + +/*++ +ClasspModeSelect() + +Routine Description: + + This routine sends a mode select command to a target ID and returns + when it is complete. + +Arguments: + + Fdo - Supplies the functional device object associated with this request. + + ModeSelectBuffer - Supplies a buffer to the select data. + + Length - Supplies the length in bytes of the mode select buffer. + + SavePages - Specifies the value of the save pages (SP) bit in the mode + select command. + +Return Value: + + NTSTATUS code of the request. + +--*/ +NTSTATUS +ClasspModeSelect( + _In_ PDEVICE_OBJECT Fdo, + _In_reads_bytes_(Length) PCHAR ModeSelectBuffer, + _In_ ULONG Length, + _In_ BOOLEAN SavePages + ) +{ + + PMDL senseBufferMdl; + NTSTATUS status = STATUS_UNSUCCESSFUL; + + senseBufferMdl = BuildDeviceInputMdl(ModeSelectBuffer, Length); + if (senseBufferMdl) { + + TRANSFER_PACKET *pkt = DequeueFreeTransferPacket(Fdo, TRUE); + if (pkt){ + KEVENT event; + IRP pseudoIrp = {0}; + + /* + * Store the number of packets servicing the irp (one) + * inside the original IRP. It will be used to counted down + * to zero when the packet completes. + * Initialize the original IRP's status to success. + * If the packet fails, we will set it to the error status. + */ + pseudoIrp.Tail.Overlay.DriverContext[0] = LongToPtr(1); + pseudoIrp.IoStatus.Status = STATUS_SUCCESS; + pseudoIrp.IoStatus.Information = 0; + pseudoIrp.MdlAddress = senseBufferMdl; + + /* + * Set this up as a SYNCHRONOUS transfer, submit it, + * and wait for the packet to complete. The result + * status will be written to the original irp. + */ + NT_ASSERT(Length <= 0x0ff); + KeInitializeEvent(&event, SynchronizationEvent, FALSE); + SetupModeSelectTransferPacket(pkt, &event, ModeSelectBuffer, (UCHAR)Length, SavePages, &pseudoIrp); + SubmitTransferPacket(pkt); + (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); + + if (!NT_SUCCESS(pseudoIrp.IoStatus.Status)){ + /* + * This request can sometimes fail legitimately + * (e.g. when a SCSI device is attached but turned off) + * so this is not necessarily a device/driver bug. + */ + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassModeSelect on Fdo %ph failed with status %xh.", Fdo, pseudoIrp.IoStatus.Status)); + } + + status = pseudoIrp.IoStatus.Status; + } + + FreeDeviceInputMdl(senseBufferMdl); + } else { + status = STATUS_INSUFFICIENT_RESOURCES; + } + + return status; +} + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassSendSrbAsynchronous() + +Routine Description: + + This routine takes a partially built Srb and an Irp and sends it down to + the port driver. + + This routine must be called with the remove lock held for the specified + Irp. + +Arguments: + + Fdo - Supplies the functional device object for the orginal request. + + Srb - Supplies a paritally build ScsiRequestBlock. In particular, the + CDB and the SRB timeout value must be filled in. The SRB must not be + allocated from zone. + + Irp - Supplies the requesting Irp. + + BufferAddress - Supplies a pointer to the buffer to be transfered. + + BufferLength - Supplies the length of data transfer. + + WriteToDevice - Indicates the data transfer will be from system memory to + device. + +Return Value: + + Returns STATUS_PENDING if the request is dispatched (since the + completion routine may change the irp's status value we cannot simply + return the value of the dispatch) + + or returns a status value to indicate why it failed. + +--*/ +_Success_(return == STATUS_PENDING) +NTSTATUS +ClassSendSrbAsynchronous( + _In_ PDEVICE_OBJECT Fdo, + _Inout_ __on_failure(__drv_freesMem(Mem)) __drv_aliasesMem PSCSI_REQUEST_BLOCK _Srb, + _In_ PIRP Irp, + _In_reads_bytes_opt_(BufferLength) __drv_aliasesMem PVOID BufferAddress, + _In_ ULONG BufferLength, + _In_ BOOLEAN WriteToDevice + ) +{ + + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension; + PIO_STACK_LOCATION irpStack; + PSTORAGE_REQUEST_BLOCK_HEADER Srb = (PSTORAGE_REQUEST_BLOCK_HEADER)_Srb; + + ULONG savedFlags; + + if (Srb->Function != SRB_FUNCTION_STORAGE_REQUEST_BLOCK) { + // + // Write length to SRB. + // + + Srb->Length = sizeof(SCSI_REQUEST_BLOCK); + + // + // Set SCSI bus address. + // + + Srb->Function = SRB_FUNCTION_EXECUTE_SCSI; + } + + // + // This is a violation of the SCSI spec but it is required for + // some targets. + // + + // Srb->Cdb[1] |= deviceExtension->Lun << 5; + + // + // Indicate auto request sense by specifying buffer and size. + // + + SrbSetSenseInfoBuffer(Srb, fdoExtension->SenseData); + SrbSetSenseInfoBufferLength(Srb, GET_FDO_EXTENSON_SENSE_DATA_LENGTH(fdoExtension)); + + SrbSetDataBuffer(Srb, BufferAddress); + + // + // Set the transfer length. + // + SrbSetDataTransferLength(Srb, BufferLength); + + // + // Save the class driver specific flags away. + // + + savedFlags = SrbGetSrbFlags(Srb) & SRB_FLAGS_CLASS_DRIVER_RESERVED; + + // + // Allow the caller to specify that they do not wish + // IoStartNextPacket() to be called in the completion routine. + // + + SET_FLAG(savedFlags, (SrbGetSrbFlags(Srb) & SRB_FLAGS_DONT_START_NEXT_PACKET)); + + // + // If caller wants to this request to be tagged, save this fact. + // + + if ( TEST_FLAG(SrbGetSrbFlags(Srb), SRB_FLAGS_QUEUE_ACTION_ENABLE) && + ( SRB_SIMPLE_TAG_REQUEST == SrbGetRequestAttribute(Srb) || + SRB_HEAD_OF_QUEUE_TAG_REQUEST == SrbGetRequestAttribute(Srb) || + SRB_ORDERED_QUEUE_TAG_REQUEST == SrbGetRequestAttribute(Srb) ) ) { + + SET_FLAG(savedFlags, SRB_FLAGS_QUEUE_ACTION_ENABLE); + if (TEST_FLAG(SrbGetSrbFlags(Srb), SRB_FLAGS_NO_QUEUE_FREEZE)) { + SET_FLAG(savedFlags, SRB_FLAGS_NO_QUEUE_FREEZE); + } + } + + if (BufferAddress != NULL) { + + // + // Build Mdl if necessary. + // + + if (Irp->MdlAddress == NULL) { + + PMDL mdl; + + mdl = IoAllocateMdl(BufferAddress, + BufferLength, + FALSE, + FALSE, + Irp); + + if ((mdl == NULL) || (Irp->MdlAddress == NULL)) { + + Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES; + + // + // ClassIoComplete() would have free'd the srb + // + + if (PORT_ALLOCATED_SENSE_EX(fdoExtension, Srb)) { + FREE_PORT_ALLOCATED_SENSE_BUFFER_EX(fdoExtension, Srb); + } + ClassFreeOrReuseSrb(fdoExtension, (PSCSI_REQUEST_BLOCK)Srb); + ClassReleaseRemoveLock(Fdo, Irp); + ClassCompleteRequest(Fdo, Irp, IO_NO_INCREMENT); + + return STATUS_INSUFFICIENT_RESOURCES; + } + + SET_FLAG(savedFlags, SRB_CLASS_FLAGS_FREE_MDL); + + MmBuildMdlForNonPagedPool(Irp->MdlAddress); + + } else { + + // + // Make sure the buffer requested matches the MDL. + // + + NT_ASSERT(BufferAddress == MmGetMdlVirtualAddress(Irp->MdlAddress)); + } + + // + // Set read flag. + // + + SrbAssignSrbFlags(Srb, WriteToDevice ? SRB_FLAGS_DATA_OUT : SRB_FLAGS_DATA_IN); + + } else { + + // + // Clear flags. + // + + SrbAssignSrbFlags(Srb, SRB_FLAGS_NO_DATA_TRANSFER); + } + + // + // Restore saved flags. + // + + SrbSetSrbFlags(Srb, savedFlags); + + // + // Disable synchronous transfer for these requests. + // + + SrbSetSrbFlags(Srb, SRB_FLAGS_DISABLE_SYNCH_TRANSFER); + + // + // Zero out status. + // + + SrbSetScsiStatus(Srb, 0); + Srb->SrbStatus = 0; + + SrbSetNextSrb(Srb, NULL); + + // + // Save a few parameters in the current stack location. + // + + irpStack = IoGetCurrentIrpStackLocation(Irp); + + // + // Save retry count in current Irp stack. + // + + irpStack->Parameters.Others.Argument4 = (PVOID)MAXIMUM_RETRIES; + + // + // Set up IoCompletion routine address. + // + + IoSetCompletionRoutine(Irp, ClassIoComplete, Srb, TRUE, TRUE, TRUE); + + // + // Get next stack location and + // set major function code. + // + + irpStack = IoGetNextIrpStackLocation(Irp); + + irpStack->MajorFunction = IRP_MJ_SCSI; + + // + // Save SRB address in next stack for port driver. + // + + irpStack->Parameters.Scsi.Srb = (PSCSI_REQUEST_BLOCK)Srb; + + // + // Set up Irp Address. + // + + SrbSetOriginalRequest(Srb, Irp); + + // + // Call the port driver to process the request. + // + + IoMarkIrpPending(Irp); + + IoCallDriver(fdoExtension->CommonExtension.LowerDeviceObject, Irp); + + return STATUS_PENDING; + +} // end ClassSendSrbAsynchronous() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassDeviceControlDispatch() + +Routine Description: + + The routine is the common class driver device control dispatch entry point. + This routine is invokes the device-specific drivers DeviceControl routine, + (which may call the Class driver's common DeviceControl routine). + +Arguments: + + DeviceObject - Supplies a pointer to the device object for this request. + + Irp - Supplies the Irp making the request. + +Return Value: + + Returns the status returned from the device-specific driver. + +--*/ +NTSTATUS +ClassDeviceControlDispatch( + PDEVICE_OBJECT DeviceObject, + PIRP Irp + ) +{ + + PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; + ULONG isRemoved; + + isRemoved = ClassAcquireRemoveLock(DeviceObject, Irp); + _Analysis_assume_(isRemoved); + if(isRemoved) { + + ClassReleaseRemoveLock(DeviceObject, Irp); + + Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST; + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + return STATUS_DEVICE_DOES_NOT_EXIST; + } + + // + // Call the class specific driver DeviceControl routine. + // If it doesn't handle it, it will call back into ClassDeviceControl. + // + + NT_ASSERT(commonExtension->DevInfo->ClassDeviceControl); + + return commonExtension->DevInfo->ClassDeviceControl(DeviceObject,Irp); +} // end ClassDeviceControlDispatch() + + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassDeviceControl() + +Routine Description: + + The routine is the common class driver device control dispatch function. + This routine is called by a class driver when it get an unrecognized + device control request. This routine will perform the correct action for + common requests such as lock media. If the device request is unknown it + passed down to the next level. + + This routine must be called with the remove lock held for the specified + irp. + +Arguments: + + DeviceObject - Supplies a pointer to the device object for this request. + + Irp - Supplies the Irp making the request. + +Return Value: + + Returns back a STATUS_PENDING or a completion status. + +--*/ +NTSTATUS +ClassDeviceControl( + _In_ PDEVICE_OBJECT DeviceObject, + _Inout_ PIRP Irp + ) +{ + PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; + + PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp); + PIO_STACK_LOCATION nextStack = NULL; + + ULONG controlCode = irpStack->Parameters.DeviceIoControl.IoControlCode; + + PSCSI_REQUEST_BLOCK srb = NULL; + PCDB cdb = NULL; + + NTSTATUS status; + ULONG modifiedIoControlCode = 0; + + + // + // If this is a pass through I/O control, set the minor function code + // and device address and pass it to the port driver. + // + + if ( (controlCode == IOCTL_SCSI_PASS_THROUGH) || + (controlCode == IOCTL_SCSI_PASS_THROUGH_DIRECT) || + (controlCode == IOCTL_SCSI_PASS_THROUGH_EX) || + (controlCode == IOCTL_SCSI_PASS_THROUGH_DIRECT_EX) ) { + + + + // + // Validiate the user buffer for SCSI pass through. + // For pass through EX: as the handler will validate the size anyway, + // do not apply the similar check and leave the work to the handler. + // + if ( (controlCode == IOCTL_SCSI_PASS_THROUGH) || + (controlCode == IOCTL_SCSI_PASS_THROUGH_DIRECT) ) { + + #if BUILD_WOW64_ENABLED && defined(_WIN64) + + if (IoIs32bitProcess(Irp)) { + + if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(SCSI_PASS_THROUGH32)){ + + Irp->IoStatus.Status = STATUS_INVALID_PARAMETER; + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + + status = STATUS_INVALID_PARAMETER; + goto SetStatusAndReturn; + } + } + else + + #endif + + { + if (irpStack->Parameters.DeviceIoControl.InputBufferLength < + sizeof(SCSI_PASS_THROUGH)) { + + Irp->IoStatus.Status = STATUS_INVALID_PARAMETER; + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + + status = STATUS_INVALID_PARAMETER; + goto SetStatusAndReturn; + } + } + } + + + IoCopyCurrentIrpStackLocationToNext(Irp); + + nextStack = IoGetNextIrpStackLocation(Irp); + nextStack->MinorFunction = 1; + + ClassReleaseRemoveLock(DeviceObject, Irp); + + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + goto SetStatusAndReturn; + + } + + Irp->IoStatus.Information = 0; + + + switch (controlCode) { + + case IOCTL_MOUNTDEV_QUERY_UNIQUE_ID: { + + PMOUNTDEV_UNIQUE_ID uniqueId; + + if (!commonExtension->MountedDeviceInterfaceName.Buffer) { + status = STATUS_INVALID_PARAMETER; + break; + } + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < + sizeof(MOUNTDEV_UNIQUE_ID)) { + + status = STATUS_BUFFER_TOO_SMALL; + Irp->IoStatus.Information = sizeof(MOUNTDEV_UNIQUE_ID); + break; + } + + uniqueId = Irp->AssociatedIrp.SystemBuffer; + RtlZeroMemory(uniqueId, sizeof(MOUNTDEV_UNIQUE_ID)); + uniqueId->UniqueIdLength = + commonExtension->MountedDeviceInterfaceName.Length; + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < + sizeof(USHORT) + uniqueId->UniqueIdLength) { + + status = STATUS_BUFFER_OVERFLOW; + Irp->IoStatus.Information = sizeof(MOUNTDEV_UNIQUE_ID); + break; + } + + RtlCopyMemory(uniqueId->UniqueId, + commonExtension->MountedDeviceInterfaceName.Buffer, + uniqueId->UniqueIdLength); + + status = STATUS_SUCCESS; + Irp->IoStatus.Information = sizeof(USHORT) + + uniqueId->UniqueIdLength; + break; + } + + case IOCTL_MOUNTDEV_QUERY_DEVICE_NAME: { + + PMOUNTDEV_NAME name; + + NT_ASSERT(commonExtension->DeviceName.Buffer); + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < + sizeof(MOUNTDEV_NAME)) { + + status = STATUS_BUFFER_TOO_SMALL; + Irp->IoStatus.Information = sizeof(MOUNTDEV_NAME); + break; + } + + name = Irp->AssociatedIrp.SystemBuffer; + RtlZeroMemory(name, sizeof(MOUNTDEV_NAME)); + name->NameLength = commonExtension->DeviceName.Length; + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < + sizeof(USHORT) + name->NameLength) { + + status = STATUS_BUFFER_OVERFLOW; + Irp->IoStatus.Information = sizeof(MOUNTDEV_NAME); + break; + } + + RtlCopyMemory(name->Name, commonExtension->DeviceName.Buffer, + name->NameLength); + + status = STATUS_SUCCESS; + Irp->IoStatus.Information = sizeof(USHORT) + name->NameLength; + break; + } + + case IOCTL_MOUNTDEV_QUERY_SUGGESTED_LINK_NAME: { + + PMOUNTDEV_SUGGESTED_LINK_NAME suggestedName; + WCHAR driveLetterNameBuffer[10] = {0}; + RTL_QUERY_REGISTRY_TABLE queryTable[2] = {0}; + PWSTR valueName; + UNICODE_STRING driveLetterName; + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < + sizeof(MOUNTDEV_SUGGESTED_LINK_NAME)) { + + status = STATUS_BUFFER_TOO_SMALL; + Irp->IoStatus.Information = sizeof(MOUNTDEV_SUGGESTED_LINK_NAME); + break; + } + + valueName = ExAllocatePoolWithTag( + PagedPool, + commonExtension->DeviceName.Length + sizeof(WCHAR), + '8CcS'); + + if (!valueName) { + status = STATUS_INSUFFICIENT_RESOURCES; + break; + } + + RtlCopyMemory(valueName, commonExtension->DeviceName.Buffer, + commonExtension->DeviceName.Length); + valueName[commonExtension->DeviceName.Length/sizeof(WCHAR)] = 0; + + driveLetterName.Buffer = driveLetterNameBuffer; + driveLetterName.MaximumLength = sizeof(driveLetterNameBuffer); + driveLetterName.Length = 0; + + queryTable[0].Flags = RTL_QUERY_REGISTRY_REQUIRED | + RTL_QUERY_REGISTRY_DIRECT; + queryTable[0].Name = valueName; + queryTable[0].EntryContext = &driveLetterName; + + status = RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE, + L"\\Registry\\Machine\\System\\DISK", + queryTable, NULL, NULL); + + if (!NT_SUCCESS(status)) { + FREE_POOL(valueName); + break; + } + + if (driveLetterName.Length == 4 && + driveLetterName.Buffer[0] == '%' && + driveLetterName.Buffer[1] == ':') { + + driveLetterName.Buffer[0] = 0xFF; + + } else if (driveLetterName.Length != 4 || + driveLetterName.Buffer[0] < FirstDriveLetter || + driveLetterName.Buffer[0] > LastDriveLetter || + driveLetterName.Buffer[1] != ':') { + + status = STATUS_NOT_FOUND; + FREE_POOL(valueName); + break; + } + + suggestedName = Irp->AssociatedIrp.SystemBuffer; + RtlZeroMemory(suggestedName, sizeof(MOUNTDEV_SUGGESTED_LINK_NAME)); + suggestedName->UseOnlyIfThereAreNoOtherLinks = TRUE; + suggestedName->NameLength = 28; + + Irp->IoStatus.Information = + FIELD_OFFSET(MOUNTDEV_SUGGESTED_LINK_NAME, Name) + 28; + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < + Irp->IoStatus.Information) { + + Irp->IoStatus.Information = + sizeof(MOUNTDEV_SUGGESTED_LINK_NAME); + status = STATUS_BUFFER_OVERFLOW; + FREE_POOL(valueName); + break; + } + + RtlDeleteRegistryValue(RTL_REGISTRY_ABSOLUTE, + L"\\Registry\\Machine\\System\\DISK", + valueName); + + FREE_POOL(valueName); + + RtlCopyMemory(suggestedName->Name, L"\\DosDevices\\", 24); + suggestedName->Name[12] = driveLetterName.Buffer[0]; + suggestedName->Name[13] = ':'; + + // + // NT_SUCCESS(status) based on RtlQueryRegistryValues + // + status = STATUS_SUCCESS; + + break; + } + + default: + status = STATUS_PENDING; + break; + } + + if (status != STATUS_PENDING) { + ClassReleaseRemoveLock(DeviceObject, Irp); + Irp->IoStatus.Status = status; + + + IoCompleteRequest(Irp, IO_NO_INCREMENT); + return status; + } + + if (commonExtension->IsFdo){ + + PULONG_PTR function; + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)commonExtension; + size_t sizeNeeded; + + // + // Allocate a SCSI SRB for handling various IOCTLs. + // NOTE - there is a case where an IOCTL is sent to classpnp before AdapterDescriptor + // is initialized. In this case, default to legacy SRB. + // + if ((fdoExtension->AdapterDescriptor != NULL) && + (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK)) { + sizeNeeded = CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE; + } else { + sizeNeeded = sizeof(SCSI_REQUEST_BLOCK); + } + + srb = ExAllocatePoolWithTag(NonPagedPoolNx, + sizeNeeded + + (sizeof(ULONG_PTR) * 2), + '9CcS'); + + if (srb == NULL) { + + Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES; + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + status = STATUS_INSUFFICIENT_RESOURCES; + goto SetStatusAndReturn; + } + + if ((fdoExtension->AdapterDescriptor != NULL) && + (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK)) { + status = InitializeStorageRequestBlock((PSTORAGE_REQUEST_BLOCK)srb, + STORAGE_ADDRESS_TYPE_BTL8, + CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE, + 1, + SrbExDataTypeScsiCdb16); + if (NT_SUCCESS(status)) { + ((PSTORAGE_REQUEST_BLOCK)srb)->SrbFunction = SRB_FUNCTION_EXECUTE_SCSI; + function = (PULONG_PTR)((PCHAR)srb + sizeNeeded); + } else { + // + // Should not occur. + // + NT_ASSERT(FALSE); + goto SetStatusAndReturn; + } + } else { + RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK)); + srb->Length = sizeof(SCSI_REQUEST_BLOCK); + srb->Function = SRB_FUNCTION_EXECUTE_SCSI; + function = (PULONG_PTR) ((PSCSI_REQUEST_BLOCK) (srb + 1)); + } + + // + // Save the function code and the device object in the memory after + // the SRB. + // + + *function = (ULONG_PTR) DeviceObject; + function++; + *function = (ULONG_PTR) controlCode; + + } else { + srb = NULL; + } + + // + // Change the device type to storage for the switch statement, but only + // if from a legacy device type + // + + if (((controlCode & 0xffff0000) == (IOCTL_DISK_BASE << 16)) || + ((controlCode & 0xffff0000) == (IOCTL_TAPE_BASE << 16)) || + ((controlCode & 0xffff0000) == (IOCTL_CDROM_BASE << 16)) + ) { + + modifiedIoControlCode = (controlCode & ~0xffff0000); + modifiedIoControlCode |= (IOCTL_STORAGE_BASE << 16); + + } else { + + modifiedIoControlCode = controlCode; + + } + + TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_GENERAL, "> ioctl %xh (%s)", modifiedIoControlCode, DBGGETIOCTLSTR(modifiedIoControlCode))); + + + switch (modifiedIoControlCode) { + + case IOCTL_STORAGE_GET_HOTPLUG_INFO: { + + FREE_POOL(srb); + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < + sizeof(STORAGE_HOTPLUG_INFO)) { + + // + // Indicate unsuccessful status and no data transferred. + // + + Irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL; + Irp->IoStatus.Information = sizeof(STORAGE_HOTPLUG_INFO); + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + status = STATUS_BUFFER_TOO_SMALL; + + } else if (!commonExtension->IsFdo) { + + + // + // Just forward this down and return + // + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + + } else { + + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension; + PSTORAGE_HOTPLUG_INFO info; + + fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)commonExtension; + info = Irp->AssociatedIrp.SystemBuffer; + + *info = fdoExtension->PrivateFdoData->HotplugInfo; + Irp->IoStatus.Status = STATUS_SUCCESS; + Irp->IoStatus.Information = sizeof(STORAGE_HOTPLUG_INFO); + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + status = STATUS_SUCCESS; + } + break; + } + + case IOCTL_STORAGE_SET_HOTPLUG_INFO: { + + FREE_POOL(srb); + + if (irpStack->Parameters.DeviceIoControl.InputBufferLength < + sizeof(STORAGE_HOTPLUG_INFO)) { + + // + // Indicate unsuccessful status and no data transferred. + // + + Irp->IoStatus.Status = STATUS_INFO_LENGTH_MISMATCH; + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + status = STATUS_INFO_LENGTH_MISMATCH; + goto SetStatusAndReturn; + + } + + if (!commonExtension->IsFdo) { + + + // + // Just forward this down and return + // + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + + } else { + + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)commonExtension; + PSTORAGE_HOTPLUG_INFO info = Irp->AssociatedIrp.SystemBuffer; + + status = STATUS_SUCCESS; + + if (info->Size != fdoExtension->PrivateFdoData->HotplugInfo.Size) + { + status = STATUS_INVALID_PARAMETER_1; + } + + if (info->MediaRemovable != fdoExtension->PrivateFdoData->HotplugInfo.MediaRemovable) + { + status = STATUS_INVALID_PARAMETER_2; + } + + if (info->MediaHotplug != fdoExtension->PrivateFdoData->HotplugInfo.MediaHotplug) + { + status = STATUS_INVALID_PARAMETER_3; + } + + if (NT_SUCCESS(status)) + { + if (info->WriteCacheEnableOverride != fdoExtension->PrivateFdoData->HotplugInfo.WriteCacheEnableOverride) + { + fdoExtension->PrivateFdoData->HotplugInfo.WriteCacheEnableOverride = info->WriteCacheEnableOverride; + + // + // Store the user-defined override in the registry + // + + ClassSetDeviceParameter(fdoExtension, + CLASSP_REG_SUBKEY_NAME, + CLASSP_REG_WRITE_CACHE_VALUE_NAME, + info->WriteCacheEnableOverride); + } + + fdoExtension->PrivateFdoData->HotplugInfo.DeviceHotplug = info->DeviceHotplug; + + // + // Store the user-defined override in the registry + // + + ClassSetDeviceParameter(fdoExtension, + CLASSP_REG_SUBKEY_NAME, + CLASSP_REG_REMOVAL_POLICY_VALUE_NAME, + (info->DeviceHotplug) ? RemovalPolicyExpectSurpriseRemoval : RemovalPolicyExpectOrderlyRemoval); + } + + Irp->IoStatus.Status = status; + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + } + + break; + } + + case IOCTL_STORAGE_CHECK_VERIFY: + case IOCTL_STORAGE_CHECK_VERIFY2: { + + PIRP irp2 = NULL; + PIO_STACK_LOCATION newStack; + + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = NULL; + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "DeviceIoControl: Check verify\n")); + + // + // If a buffer for a media change count was provided, make sure it's + // big enough to hold the result + // + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength) { + + // + // If the buffer is too small to hold the media change count + // then return an error to the caller + // + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < + sizeof(ULONG)) { + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, "DeviceIoControl: media count " + "buffer too small\n")); + + Irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL; + Irp->IoStatus.Information = sizeof(ULONG); + + FREE_POOL(srb); + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + + status = STATUS_BUFFER_TOO_SMALL; + goto SetStatusAndReturn; + } + } + + if (!commonExtension->IsFdo) { + + + // + // If this is a PDO then we should just forward the request down + // + NT_ASSERT(!srb); + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + + goto SetStatusAndReturn; + + } else { + + fdoExtension = DeviceObject->DeviceExtension; + + } + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength) { + + // + // The caller has provided a valid buffer. Allocate an additional + // irp and stick the CheckVerify completion routine on it. We will + // then send this down to the port driver instead of the irp the + // caller sent in + // + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "DeviceIoControl: Check verify wants " + "media count\n")); + + // + // Allocate a new irp to send the TestUnitReady to the port driver + // + + irp2 = IoAllocateIrp((CCHAR) (DeviceObject->StackSize + 3), FALSE); + + if (irp2 == NULL) { + Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES; + Irp->IoStatus.Information = 0; + FREE_POOL(srb); + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + status = STATUS_INSUFFICIENT_RESOURCES; + goto SetStatusAndReturn; + + break; + } + + // + // Make sure to acquire the lock for the new irp. + // + + ClassAcquireRemoveLock(DeviceObject, irp2); + + irp2->Tail.Overlay.Thread = Irp->Tail.Overlay.Thread; + IoSetNextIrpStackLocation(irp2); + + // + // Set the top stack location and shove the master Irp into the + // top location + // + + newStack = IoGetCurrentIrpStackLocation(irp2); + newStack->Parameters.Others.Argument1 = Irp; + newStack->DeviceObject = DeviceObject; + + // + // Stick the check verify completion routine onto the stack + // and prepare the irp for the port driver + // + + IoSetCompletionRoutine(irp2, + ClassCheckVerifyComplete, + NULL, + TRUE, + TRUE, + TRUE); + + IoSetNextIrpStackLocation(irp2); + newStack = IoGetCurrentIrpStackLocation(irp2); + newStack->DeviceObject = DeviceObject; + newStack->MajorFunction = irpStack->MajorFunction; + newStack->MinorFunction = irpStack->MinorFunction; + newStack->Flags = irpStack->Flags; + + + // + // Mark the master irp as pending - whether the lower level + // driver completes it immediately or not this should allow it + // to go all the way back up. + // + + IoMarkIrpPending(Irp); + + Irp = irp2; + + } + + // + // Test Unit Ready + // + + SrbSetCdbLength(srb, 6); + cdb = SrbGetCdb(srb); + cdb->CDB6GENERIC.OperationCode = SCSIOP_TEST_UNIT_READY; + + // + // Set timeout value. + // + + SrbSetTimeOutValue(srb, fdoExtension->TimeOutValue); + + // + // If this was a CV2 then mark the request as low-priority so we don't + // spin up the drive just to satisfy it. + // + + if (controlCode == IOCTL_STORAGE_CHECK_VERIFY2) { + SrbSetSrbFlags(srb, SRB_CLASS_FLAGS_LOW_PRIORITY); + } + + // + // Since this routine will always hand the request to the + // port driver if there isn't a data transfer to be done + // we don't have to worry about completing the request here + // on an error + // + + // + // This routine uses a completion routine so we don't want to release + // the remove lock until then. + // + + status = ClassSendSrbAsynchronous(DeviceObject, + srb, + Irp, + NULL, + 0, + FALSE); + + break; + } + + case IOCTL_STORAGE_MEDIA_REMOVAL: + case IOCTL_STORAGE_EJECTION_CONTROL: { + + PPREVENT_MEDIA_REMOVAL mediaRemoval = Irp->AssociatedIrp.SystemBuffer; + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "DiskIoControl: ejection control\n")); + + FREE_POOL(srb); + + if (irpStack->Parameters.DeviceIoControl.InputBufferLength < + sizeof(PREVENT_MEDIA_REMOVAL)) { + + // + // Indicate unsuccessful status and no data transferred. + // + + Irp->IoStatus.Status = STATUS_INFO_LENGTH_MISMATCH; + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + status = STATUS_INFO_LENGTH_MISMATCH; + goto SetStatusAndReturn; + } + + if (!commonExtension->IsFdo) { + + + // + // Just forward this down and return + // + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + } + else { + + // i don't believe this assertion is valid. this is a request + // from user-mode, so they could request this for any device + // they want? also, we handle it properly. + // NT_ASSERT(TEST_FLAG(DeviceObject->Characteristics, FILE_REMOVABLE_MEDIA)); + status = ClasspEjectionControl( + DeviceObject, + Irp, + ((modifiedIoControlCode == + IOCTL_STORAGE_EJECTION_CONTROL) ? SecureMediaLock : + SimpleMediaLock), + mediaRemoval->PreventMediaRemoval); + + Irp->IoStatus.Status = status; + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + } + + break; + } + + case IOCTL_STORAGE_MCN_CONTROL: { + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "DiskIoControl: MCN control\n")); + + if (irpStack->Parameters.DeviceIoControl.InputBufferLength < + sizeof(PREVENT_MEDIA_REMOVAL)) { + + // + // Indicate unsuccessful status and no data transferred. + // + + Irp->IoStatus.Status = STATUS_INFO_LENGTH_MISMATCH; + Irp->IoStatus.Information = 0; + + FREE_POOL(srb); + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + status = STATUS_INFO_LENGTH_MISMATCH; + goto SetStatusAndReturn; + } + + if (!commonExtension->IsFdo) { + + + // + // Just forward this down and return + // + + FREE_POOL(srb); + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + + } else { + + // + // Call to the FDO - handle the ejection control. + // + + status = ClasspMcnControl(DeviceObject->DeviceExtension, + Irp, + srb); + } + goto SetStatusAndReturn; + } + + case IOCTL_STORAGE_RESERVE: + case IOCTL_STORAGE_RELEASE: { + + // + // Reserve logical unit. + // + + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = NULL; + + if (!commonExtension->IsFdo) { + + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + goto SetStatusAndReturn; + + } else { + fdoExtension = DeviceObject->DeviceExtension; + } + + if (TEST_FLAG(fdoExtension->PrivateFdoData->HackFlags, FDO_HACK_NO_RESERVE6)) + { + SrbSetCdbLength(srb, 10); + cdb = SrbGetCdb(srb); + cdb->CDB10.OperationCode = (modifiedIoControlCode == IOCTL_STORAGE_RESERVE) ? SCSIOP_RESERVE_UNIT10 : SCSIOP_RELEASE_UNIT10; + } + else + { + SrbSetCdbLength(srb, 6); + cdb = SrbGetCdb(srb); + cdb->CDB6GENERIC.OperationCode = (modifiedIoControlCode == IOCTL_STORAGE_RESERVE) ? SCSIOP_RESERVE_UNIT : SCSIOP_RELEASE_UNIT; + } + + // + // Set timeout value. + // + + SrbSetTimeOutValue(srb, fdoExtension->TimeOutValue); + + // + // Send reserves as tagged requests. + // + + if ( IOCTL_STORAGE_RESERVE == modifiedIoControlCode ) { + SrbSetSrbFlags(srb, SRB_FLAGS_QUEUE_ACTION_ENABLE); + SrbSetRequestAttribute(srb, SRB_SIMPLE_TAG_REQUEST); + } + + status = ClassSendSrbAsynchronous(DeviceObject, + srb, + Irp, + NULL, + 0, + FALSE); + + break; + } + + case IOCTL_STORAGE_PERSISTENT_RESERVE_IN: + case IOCTL_STORAGE_PERSISTENT_RESERVE_OUT: { + + if (!commonExtension->IsFdo) { + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + goto SetStatusAndReturn; + } + + // + // Process Persistent Reserve + // + + status = ClasspPersistentReserve(DeviceObject, Irp, srb); + + break; + + } + + case IOCTL_STORAGE_EJECT_MEDIA: + case IOCTL_STORAGE_LOAD_MEDIA: + case IOCTL_STORAGE_LOAD_MEDIA2:{ + + // + // Eject media. + // + + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = NULL; + + if (!commonExtension->IsFdo) { + + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + goto SetStatusAndReturn; + } else { + fdoExtension = DeviceObject->DeviceExtension; + } + + if (commonExtension->PagingPathCount != 0) { + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, "ClassDeviceControl: call to eject paging device - " + "failure\n")); + + status = STATUS_FILES_OPEN; + Irp->IoStatus.Status = status; + + Irp->IoStatus.Information = 0; + + FREE_POOL(srb); + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + goto SetStatusAndReturn; + } + + // + // Synchronize with ejection control and ejection cleanup code as + // well as other eject/load requests. + // + + KeEnterCriticalRegion(); + (VOID)KeWaitForSingleObject(&(fdoExtension->EjectSynchronizationEvent), + UserRequest, + KernelMode, + FALSE, + NULL); + + if (fdoExtension->ProtectedLockCount != 0) { + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, "ClassDeviceControl: call to eject protected locked " + "device - failure\n")); + + status = STATUS_DEVICE_BUSY; + Irp->IoStatus.Status = status; + Irp->IoStatus.Information = 0; + + FREE_POOL(srb); + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + + KeSetEvent(&fdoExtension->EjectSynchronizationEvent, + IO_NO_INCREMENT, + FALSE); + KeLeaveCriticalRegion(); + + goto SetStatusAndReturn; + } + + SrbSetCdbLength(srb, 6); + cdb = SrbGetCdb(srb); + + cdb->START_STOP.OperationCode = SCSIOP_START_STOP_UNIT; + cdb->START_STOP.LoadEject = 1; + + if (modifiedIoControlCode == IOCTL_STORAGE_EJECT_MEDIA) { + cdb->START_STOP.Start = 0; + } else { + cdb->START_STOP.Start = 1; + } + + // + // Set timeout value. + // + + SrbSetTimeOutValue(srb, fdoExtension->TimeOutValue); + status = ClassSendSrbAsynchronous(DeviceObject, + srb, + Irp, + NULL, + 0, + FALSE); + + KeSetEvent(&fdoExtension->EjectSynchronizationEvent, IO_NO_INCREMENT, FALSE); + KeLeaveCriticalRegion(); + + break; + } + + case IOCTL_STORAGE_FIND_NEW_DEVICES: { + + FREE_POOL(srb); + + if (commonExtension->IsFdo) { + + IoInvalidateDeviceRelations( + ((PFUNCTIONAL_DEVICE_EXTENSION) commonExtension)->LowerPdo, + BusRelations); + + status = STATUS_SUCCESS; + Irp->IoStatus.Status = status; + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + } + else { + + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + } + break; + } + + case IOCTL_STORAGE_GET_DEVICE_NUMBER: { + + FREE_POOL(srb); + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength >= + sizeof(STORAGE_DEVICE_NUMBER)) { + + PSTORAGE_DEVICE_NUMBER deviceNumber = + Irp->AssociatedIrp.SystemBuffer; + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = + commonExtension->PartitionZeroExtension; + + deviceNumber->DeviceType = fdoExtension->CommonExtension.DeviceObject->DeviceType; + deviceNumber->DeviceNumber = fdoExtension->DeviceNumber; + deviceNumber->PartitionNumber = commonExtension->PartitionNumber; + + status = STATUS_SUCCESS; + Irp->IoStatus.Information = sizeof(STORAGE_DEVICE_NUMBER); + + } else { + status = STATUS_BUFFER_TOO_SMALL; + Irp->IoStatus.Information = sizeof(STORAGE_DEVICE_NUMBER); + } + + Irp->IoStatus.Status = status; + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + + break; + } + + + case IOCTL_STORAGE_READ_CAPACITY: { + + FREE_POOL(srb); + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < + sizeof(STORAGE_READ_CAPACITY)) { + + // + // Indicate unsuccessful status and no data transferred. + // + + Irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL; + Irp->IoStatus.Information = sizeof(STORAGE_READ_CAPACITY); + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + status = STATUS_BUFFER_TOO_SMALL; + break; + } + + if (!commonExtension->IsFdo) { + + + // + // Just forward this down and return + // + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + } + else { + + PFUNCTIONAL_DEVICE_EXTENSION fdoExt = DeviceObject->DeviceExtension; + PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData; + PSTORAGE_READ_CAPACITY readCapacity = Irp->AssociatedIrp.SystemBuffer; + LARGE_INTEGER diskLength; + + status = ClassReadDriveCapacity(DeviceObject); + if (NT_SUCCESS(status) && fdoData->IsCachedDriveCapDataValid) { + + readCapacity->Version = sizeof(STORAGE_READ_CAPACITY); + readCapacity->Size = sizeof(STORAGE_READ_CAPACITY); + + REVERSE_BYTES(&readCapacity->BlockLength, + &fdoData->LastKnownDriveCapacityData.BytesPerBlock); + REVERSE_BYTES_QUAD(&readCapacity->NumberOfBlocks, + &fdoData->LastKnownDriveCapacityData.LogicalBlockAddress); + readCapacity->NumberOfBlocks.QuadPart++; + + readCapacity->DiskLength = fdoExt->CommonExtension.PartitionLength; + + // + // Make sure the lengths are equal. + // Remove this after testing. + // + diskLength.QuadPart = readCapacity->NumberOfBlocks.QuadPart * + readCapacity->BlockLength; + + Irp->IoStatus.Status = STATUS_SUCCESS; + Irp->IoStatus.Information = sizeof(STORAGE_READ_CAPACITY); + + } else { + // + // Read capacity request failed. + // + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, "ClassDeviceControl: ClassReadDriveCapacity failed: 0x%X IsCachedDriveCapDataValid: %d\n", + status, fdoData->IsCachedDriveCapDataValid)); + Irp->IoStatus.Status = status; + Irp->IoStatus.Information = 0; + } + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + } + + break; + } + + case IOCTL_STORAGE_QUERY_PROPERTY: { + + PSTORAGE_PROPERTY_QUERY query = Irp->AssociatedIrp.SystemBuffer; + + if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(STORAGE_PROPERTY_QUERY)) { + + status = STATUS_INFO_LENGTH_MISMATCH; + Irp->IoStatus.Status = status; + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + FREE_POOL(srb); + break; + } + + if (!commonExtension->IsFdo) { + + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + FREE_POOL(srb); + break; + } + + // + // Determine PropertyId type and either call appropriate routine + // or pass request to lower drivers. + // + + switch ( query->PropertyId ) { + + case StorageDeviceUniqueIdProperty: { + + status = ClasspDuidQueryProperty(DeviceObject, Irp); + break; + } + + case StorageDeviceWriteCacheProperty: { + + status = ClasspWriteCacheProperty(DeviceObject, Irp, srb); + break; + } + + // these propertyId has been implemented in some port driver and filter drivers. + // to keep the backwards compatibility, classpnp will send the request down if it's supported by lower layer. + // otherwise, classpnp sends SCSI command and then interprets the result. + case StorageAccessAlignmentProperty: { + + status = ClasspAccessAlignmentProperty(DeviceObject, Irp, srb); + break; + } + + case StorageDeviceSeekPenaltyProperty: { + + status = ClasspDeviceSeekPenaltyProperty(DeviceObject, Irp, srb); + break; + } + + case StorageDeviceTrimProperty: { + + status = ClasspDeviceTrimProperty(DeviceObject, Irp, srb); + break; + } + + case StorageDeviceLBProvisioningProperty: { + + status = ClasspDeviceLBProvisioningProperty(DeviceObject, Irp, srb); + break; + } + + case StorageDeviceCopyOffloadProperty: { + + status = ClasspDeviceCopyOffloadProperty(DeviceObject, Irp, srb); + break; + } + + case StorageDeviceMediumProductType: { + + status = ClasspDeviceMediaTypeProperty(DeviceObject, Irp, srb); + break; + } + + default: { + + // + // Copy the Irp stack parameters to the next stack location. + // + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + break; + } + } // end switch + + FREE_POOL(srb); + break; + } + + case IOCTL_STORAGE_CHECK_PRIORITY_HINT_SUPPORT: { + + FREE_POOL(srb); + + if (!commonExtension->IsFdo) { + + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + break; + } + + // + // Process priority hit request + // + + status = ClasspPriorityHint(DeviceObject, Irp); + break; + } + + case IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES: { + + PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes = Irp->AssociatedIrp.SystemBuffer; + + if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES)) { + + status = STATUS_INVALID_PARAMETER; + Irp->IoStatus.Status = status; + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + FREE_POOL(srb); + break; + } + + if (irpStack->Parameters.DeviceIoControl.InputBufferLength < + (sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES) + dsmAttributes->ParameterBlockLength + dsmAttributes->DataSetRangesLength)) { + + status = STATUS_INVALID_PARAMETER; + Irp->IoStatus.Status = status; + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + FREE_POOL(srb); + break; + } + + if (!commonExtension->IsFdo) { + + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + FREE_POOL(srb); + break; + } + + switch(dsmAttributes->Action) { + + // only process Trim action in class layer if possible. + case DeviceDsmAction_Trim: { + status = ClasspDeviceTrimProcess(DeviceObject, Irp, srb); + break; + } + + case DeviceDsmAction_OffloadRead: { + status = ClassDeviceProcessOffloadRead(DeviceObject, Irp, srb); + break; + } + + case DeviceDsmAction_OffloadWrite: { + status = ClassDeviceProcessOffloadWrite(DeviceObject, Irp, srb); + break; + } + + case DeviceDsmAction_Allocation: { + status = ClasspDeviceGetLBAStatus(DeviceObject, Irp, srb); + break; + } + + + default: { + + + // + // Copy the Irp stack parameters to the next stack location. + // + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + break; + } + } // end switch + + FREE_POOL(srb); + break; + } + + case IOCTL_STORAGE_GET_LB_PROVISIONING_MAP_RESOURCES: { + + if (commonExtension->IsFdo) { + + status = ClassDeviceGetLBProvisioningResources(DeviceObject, Irp, srb); + + } else { + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + } + + FREE_POOL(srb); + + break; + } + + case IOCTL_STORAGE_EVENT_NOTIFICATION: { + + FREE_POOL(srb); + + status = ClasspStorageEventNotification(DeviceObject, Irp); + break; + } + +#if (NTDDI_VERSION >= NTDDI_WINTRHESHOLD) + case IOCTL_STORAGE_FIRMWARE_GET_INFO: { + FREE_POOL(srb); + + status = ClassDeviceHwFirmwareGetInfoProcess(DeviceObject, Irp); + break; + } + + case IOCTL_STORAGE_FIRMWARE_DOWNLOAD: { + if (!commonExtension->IsFdo) { + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + goto SetStatusAndReturn; + } + + status = ClassDeviceHwFirmwareDownloadProcess(DeviceObject, Irp, srb); + break; + } + + case IOCTL_STORAGE_FIRMWARE_ACTIVATE: { + if (!commonExtension->IsFdo) { + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + goto SetStatusAndReturn; + } + + status = ClassDeviceHwFirmwareActivateProcess(DeviceObject, Irp, srb); + break; + } +#endif + + + default: { + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_IOCTL, "IoDeviceControl: Unsupported device IOCTL %x for %p\n", + controlCode, DeviceObject)); + + + // + // Pass the device control to the next driver. + // + + FREE_POOL(srb); + + // + // Copy the Irp stack parameters to the next stack location. + // + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + break; + } + + } // end switch( ... + +SetStatusAndReturn: + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "< ioctl %xh (%s): status %xh.", modifiedIoControlCode, DBGGETIOCTLSTR(modifiedIoControlCode), status)); + + return status; +} // end ClassDeviceControl() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassShutdownFlush() + +Routine Description: + + This routine is called for a shutdown and flush IRPs. These are sent by the + system before it actually shuts down or when the file system does a flush. + If it exists, the device-specific driver's routine will be invoked. If there + wasn't one specified, the Irp will be completed with an Invalid device request. + +Arguments: + + DriverObject - Pointer to device object to being shutdown by system. + + Irp - IRP involved. + +Return Value: + + NT Status + +--*/ +NTSTATUS +ClassShutdownFlush( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP Irp + ) +{ + PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; + + ULONG isRemoved; + + isRemoved = ClassAcquireRemoveLock(DeviceObject, Irp); + _Analysis_assume_(isRemoved); + if(isRemoved) { + + ClassReleaseRemoveLock(DeviceObject, Irp); + + Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST; + + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + + return STATUS_DEVICE_DOES_NOT_EXIST; + } + + if (commonExtension->DevInfo->ClassShutdownFlush) { + + // + // Call the device-specific driver's routine. + // + + return commonExtension->DevInfo->ClassShutdownFlush(DeviceObject, Irp); + } + + // + // Device-specific driver doesn't support this. + // + + Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST; + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + + return STATUS_INVALID_DEVICE_REQUEST; +} // end ClassShutdownFlush() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClasspIsPortable() + +Routine Description: + + This routine is called during start device to determine whether the PDO + for a stack reports itself as portable. + +Arguments: + + FdoExtension - Pointer to FDO whose PDO we check for portability. + + IsPortable - Boolean pointer in which to store result. + +Return Value: + + NT Status + +--*/ +NTSTATUS +ClasspIsPortable( + _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension, + _Out_ PBOOLEAN IsPortable + ) +{ + DEVPROP_BOOLEAN isInternal = DEVPROP_FALSE; + BOOLEAN isPortable = FALSE; + ULONG size = 0; + NTSTATUS status = STATUS_SUCCESS; + DEVPROPTYPE type = DEVPROP_TYPE_EMPTY; + + PAGED_CODE(); + + *IsPortable = FALSE; + + // + // Check to see if the underlying device + // object is in local machine container + // + + status = IoGetDevicePropertyData(FdoExtension->LowerPdo, + &DEVPKEY_Device_InLocalMachineContainer, + 0, + 0, + sizeof(isInternal), + &isInternal, + &size, + &type); + + if (!NT_SUCCESS(status)) { + goto cleanup; + } + + NT_ASSERT(size == sizeof(isInternal)); + NT_ASSERT(type == DEVPROP_TYPE_BOOLEAN); + + // + // Volume is hot-pluggable if the disk pdo + // container id differs from that of root device + // + + if (isInternal == DEVPROP_TRUE) { + goto cleanup; + } + + isPortable = TRUE; + + // + // Examine the bus type to ensure + // that this really is a fixed disk + // + + if (FdoExtension->DeviceDescriptor->BusType == BusTypeFibre || + FdoExtension->DeviceDescriptor->BusType == BusTypeiScsi || + FdoExtension->DeviceDescriptor->BusType == BusTypeRAID) { + + isPortable = FALSE; + } + + *IsPortable = isPortable; + +cleanup: + + return status; +} + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassCreateDeviceObject() + +Routine Description: + + This routine creates an object for the physical device specified and + sets up the deviceExtension's function pointers for each entry point + in the device-specific driver. + +Arguments: + + DriverObject - Pointer to driver object created by system. + + ObjectNameBuffer - Dir. name of the object to create. + + LowerDeviceObject - Pointer to the lower device object + + IsFdo - should this be an fdo or a pdo + + DeviceObject - Pointer to the device object pointer we will return. + +Return Value: + + NTSTATUS + +--*/ + +_IRQL_requires_max_(PASSIVE_LEVEL) +_Must_inspect_result_ +_Post_satisfies_(return <= 0) +SCSIPORT_API +NTSTATUS +ClassCreateDeviceObject( + _In_ PDRIVER_OBJECT DriverObject, + _In_z_ PCCHAR ObjectNameBuffer, + _In_ PDEVICE_OBJECT LowerDevice, + _In_ BOOLEAN IsFdo, + _Outptr_result_nullonfailure_ + _At_(*DeviceObject, __drv_allocatesMem(Mem) __drv_aliasesMem) + PDEVICE_OBJECT *DeviceObject + ) +{ + BOOLEAN isPartitionable; + STRING ntNameString; + UNICODE_STRING ntUnicodeString; + NTSTATUS status; + PDEVICE_OBJECT deviceObject = NULL; + + ULONG characteristics; + SIZE_T rundownSize = ExSizeOfRundownProtectionCacheAware(); + PCHAR rundownAddr = NULL; + ULONG devExtSize; + +#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case + PCLASS_DRIVER_EXTENSION driverExtension = IoGetDriverObjectExtension(DriverObject, CLASS_DRIVER_EXTENSION_KEY); + + PCLASS_DEV_INFO devInfo; + + PAGED_CODE(); + + _Analysis_assume_(driverExtension != NULL); + + *DeviceObject = NULL; + RtlInitUnicodeString(&ntUnicodeString, NULL); + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "ClassCreateFdo: Create device object\n")); + + NT_ASSERT(LowerDevice); + + // + // Make sure that if we're making PDO's we have an enumeration routine + // + + isPartitionable = (driverExtension->InitData.ClassEnumerateDevice != NULL); + + NT_ASSERT(IsFdo || isPartitionable); + + // + // Grab the correct dev-info structure out of the init data + // + + if (IsFdo) { + devInfo = &(driverExtension->InitData.FdoData); + } else { + devInfo = &(driverExtension->InitData.PdoData); + } + + characteristics = devInfo->DeviceCharacteristics; + + if (ARGUMENT_PRESENT(ObjectNameBuffer)) { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "ClassCreateFdo: Name is %s\n", ObjectNameBuffer)); + + RtlInitString(&ntNameString, ObjectNameBuffer); + + status = RtlAnsiStringToUnicodeString(&ntUnicodeString, &ntNameString, TRUE); + + if (!NT_SUCCESS(status)) { + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, + "ClassCreateFdo: Cannot convert string %s\n", + ObjectNameBuffer)); + + ntUnicodeString.Buffer = NULL; + return status; + } + } else { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "ClassCreateFdo: Object will be unnamed\n")); + + if (IsFdo == FALSE) { + + // + // PDO's have to have some sort of name. + // + + SET_FLAG(characteristics, FILE_AUTOGENERATED_DEVICE_NAME); + } + + RtlInitUnicodeString(&ntUnicodeString, NULL); + } + + devExtSize = devInfo->DeviceExtensionSize + + (ULONG)sizeof(CLASS_PRIVATE_COMMON_DATA) + (ULONG)rundownSize; + status = IoCreateDevice(DriverObject, + devExtSize, + &ntUnicodeString, + devInfo->DeviceType, + characteristics, + FALSE, + &deviceObject); + + if (!NT_SUCCESS(status)) { + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClassCreateFdo: Can not create device object %lx\n", + status)); + NT_ASSERT(deviceObject == NULL); + + // + // buffer is not used any longer here. + // + + if (ntUnicodeString.Buffer != NULL) { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, "ClassCreateFdo: Freeing unicode name buffer\n")); + FREE_POOL(ntUnicodeString.Buffer); + RtlInitUnicodeString(&ntUnicodeString, NULL); + } + + } else { + + PCOMMON_DEVICE_EXTENSION commonExtension = deviceObject->DeviceExtension; + + RtlZeroMemory( + deviceObject->DeviceExtension, + devExtSize); + + // + // Setup version code + // + + commonExtension->Version = 0x03; + + // + // Setup the remove lock and event + // + + commonExtension->IsRemoved = NO_REMOVE; + +#if DBG + + commonExtension->RemoveLock = 0; + +#endif + + KeInitializeEvent(&commonExtension->RemoveEvent, + SynchronizationEvent, + FALSE); + + + ClasspInitializeRemoveTracking(deviceObject); + + // + // Initialize the PrivateCommonData + // + + commonExtension->PrivateCommonData = (PCLASS_PRIVATE_COMMON_DATA) + ((PCHAR)deviceObject->DeviceExtension + devInfo->DeviceExtensionSize); + rundownAddr = (PCHAR)commonExtension->PrivateCommonData + sizeof(CLASS_PRIVATE_COMMON_DATA); + ExInitializeRundownProtectionCacheAware((PEX_RUNDOWN_REF_CACHE_AWARE)rundownAddr, rundownSize); + commonExtension->PrivateCommonData->RemoveLockFailAcquire = 0; + + // + // Acquire the lock once. This reference will be released when the + // remove IRP has been received. + // + + ClassAcquireRemoveLock(deviceObject, (PIRP) deviceObject); + + // + // Store a pointer to the driver extension so we don't have to do + // lookups to get it. + // + + commonExtension->DriverExtension = driverExtension; + + // + // Fill in entry points + // + + commonExtension->DevInfo = devInfo; + + // + // Initialize some of the common values in the structure + // + + commonExtension->DeviceObject = deviceObject; + + commonExtension->LowerDeviceObject = NULL; + + commonExtension->DispatchTable = driverExtension->DeviceMajorFunctionTable; + + if(IsFdo) { + + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PVOID) commonExtension; + + commonExtension->PartitionZeroExtension = deviceObject->DeviceExtension; + + // + // Set the initial device object flags. + // + + SET_FLAG(deviceObject->Flags, DO_POWER_PAGABLE); + // + // Clear the PDO list + // + + commonExtension->ChildList = NULL; + + commonExtension->DriverData = + ((PFUNCTIONAL_DEVICE_EXTENSION) deviceObject->DeviceExtension + 1); + + // + // The disk class driver creates only FDO. The partition number + // for the FDO must be 0. + // + + if ((isPartitionable == TRUE) || + (devInfo->DeviceType == FILE_DEVICE_DISK)) { + + commonExtension->PartitionNumber = 0; + } else { + commonExtension->PartitionNumber = (ULONG) (-1L); + } + + fdoExtension->DevicePowerState = PowerDeviceD0; + + KeInitializeEvent(&fdoExtension->EjectSynchronizationEvent, + SynchronizationEvent, + TRUE); + + KeInitializeEvent(&fdoExtension->ChildLock, + SynchronizationEvent, + TRUE); + + status = ClasspAllocateReleaseRequest(deviceObject); + + if(!NT_SUCCESS(status)) { + IoDeleteDevice(deviceObject); + *DeviceObject = NULL; + + if (ntUnicodeString.Buffer != NULL) { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "ClassCreateFdo: Freeing unicode name buffer\n")); + FREE_POOL(ntUnicodeString.Buffer); + RtlInitUnicodeString(&ntUnicodeString, NULL); + } + + return status; + } + + } else { + + PPHYSICAL_DEVICE_EXTENSION pdoExtension = + deviceObject->DeviceExtension; + + PFUNCTIONAL_DEVICE_EXTENSION p0Extension = + LowerDevice->DeviceExtension; + + SET_FLAG(deviceObject->Flags, DO_POWER_PAGABLE); + + commonExtension->PartitionZeroExtension = p0Extension; + + // + // Stick this onto the PDO list + // + + ClassAddChild(p0Extension, pdoExtension, TRUE); + + commonExtension->DriverData = (PVOID) (pdoExtension + 1); + + // + // Get the top of stack for the lower device - this allows + // filters to get stuck in between the partitions and the + // physical disk. + // + + commonExtension->LowerDeviceObject = + IoGetAttachedDeviceReference(LowerDevice); + + // + // Pnp will keep a reference to the lower device object long + // after this partition has been deleted. Dereference now so + // we don't have to deal with it later. + // + + ObDereferenceObject(commonExtension->LowerDeviceObject); + } + + KeInitializeEvent(&commonExtension->PathCountEvent, SynchronizationEvent, TRUE); + + commonExtension->IsFdo = IsFdo; + + commonExtension->DeviceName = ntUnicodeString; + + commonExtension->PreviousState = 0xff; + + InitializeDictionary(&(commonExtension->FileObjectDictionary)); + + commonExtension->CurrentState = IRP_MN_STOP_DEVICE; + + if (commonExtension->DriverExtension->InitData.ClassStartIo) { + IoSetStartIoAttributes(deviceObject, TRUE, TRUE); + } + } + + *DeviceObject = deviceObject; + + return status; +} // end ClassCreateDeviceObject() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassClaimDevice() + +Routine Description: + + This function claims a device in the port driver. The port driver object + is updated with the correct driver object if the device is successfully + claimed. + +Arguments: + + LowerDeviceObject - Supplies the base port device object. + + Release - Indicates the logical unit should be released rather than claimed. + +Return Value: + + Returns a status indicating success or failure of the operation. + +--*/ +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS +ClassClaimDevice( + _In_ PDEVICE_OBJECT LowerDeviceObject, + _In_ BOOLEAN Release + ) +{ + IO_STATUS_BLOCK ioStatus; + PIRP irp; + PIO_STACK_LOCATION irpStack; + KEVENT event; + NTSTATUS status; + SCSI_REQUEST_BLOCK srb = {0}; + + PAGED_CODE(); + + // + // WORK ITEM - MPIO related. Need to think about how to handle. + // + + srb.Length = sizeof(SCSI_REQUEST_BLOCK); + + srb.Function = Release ? SRB_FUNCTION_RELEASE_DEVICE : + SRB_FUNCTION_CLAIM_DEVICE; + + // + // Set the event object to the unsignaled state. + // It will be used to signal request completion + // + + KeInitializeEvent(&event, SynchronizationEvent, FALSE); + + // + // Build synchronous request with no transfer. + // + + irp = IoBuildDeviceIoControlRequest(IOCTL_SCSI_EXECUTE_NONE, + LowerDeviceObject, + NULL, + 0, + NULL, + 0, + TRUE, + &event, + &ioStatus); + + if (irp == NULL) { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, "ClassClaimDevice: Can't allocate Irp\n")); + return STATUS_INSUFFICIENT_RESOURCES; + } + + irpStack = IoGetNextIrpStackLocation(irp); + + // + // Save SRB address in next stack for port driver. + // + + irpStack->Parameters.Scsi.Srb = &srb; + + // + // Set up IRP Address. + // + + srb.OriginalRequest = irp; + + // + // Call the port driver with the request and wait for it to complete. + // + + status = IoCallDriver(LowerDeviceObject, irp); + if (status == STATUS_PENDING) { + + (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); + status = ioStatus.Status; + } + + // + // If this is a release request, then just decrement the reference count + // and return. The status does not matter. + // + + if (Release) { + + // ObDereferenceObject(LowerDeviceObject); + return STATUS_SUCCESS; + } + + if (!NT_SUCCESS(status)) { + return status; + } + + NT_ASSERT(srb.DataBuffer != NULL); + NT_ASSERT(!TEST_FLAG(srb.SrbFlags, SRB_FLAGS_FREE_SENSE_BUFFER)); + + return status; +} // end ClassClaimDevice() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassInternalIoControl() + +Routine Description: + + This routine passes internal device controls to the port driver. + Internal device controls are used by higher level drivers both for ioctls + and to pass through scsi requests. + + If the IoControlCode does not match any of the handled ioctls and is + a valid system address then the request will be treated as an SRB and + passed down to the lower driver. If the IoControlCode is not a valid + system address the ioctl will be failed. + + Callers must therefore be extremely cautious to pass correct, initialized + values to this function. + +Arguments: + + DeviceObject - Supplies a pointer to the device object for this request. + + Irp - Supplies the Irp making the request. + +Return Value: + + Returns back a STATUS_PENDING or a completion status. + +--*/ +NTSTATUS +ClassInternalIoControl( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP Irp + ) +{ + PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; + + PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp); + PIO_STACK_LOCATION nextStack = IoGetNextIrpStackLocation(Irp); + + ULONG isRemoved; + + PSCSI_REQUEST_BLOCK srb; + + isRemoved = ClassAcquireRemoveLock(DeviceObject, Irp); + _Analysis_assume_(isRemoved); + if(isRemoved) { + + Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST; + + ClassReleaseRemoveLock(DeviceObject, Irp); + + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + + return STATUS_DEVICE_DOES_NOT_EXIST; + } + + // + // Get a pointer to the SRB. + // + + srb = irpStack->Parameters.Scsi.Srb; + + // + // Set the parameters in the next stack location. + // + + if(commonExtension->IsFdo) { + nextStack->Parameters.Scsi.Srb = srb; + nextStack->MajorFunction = IRP_MJ_SCSI; + nextStack->MinorFunction = IRP_MN_SCSI_CLASS; + + } else { + + IoCopyCurrentIrpStackLocationToNext(Irp); + } + + ClassReleaseRemoveLock(DeviceObject, Irp); + + return IoCallDriver(commonExtension->LowerDeviceObject, Irp); +} // end ClassInternalIoControl() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassQueryTimeOutRegistryValue() + +Routine Description: + + This routine determines whether a reg key for a user-specified timeout + value exists. This should be called at initialization time. + +Arguments: + + DeviceObject - Pointer to the device object we are retrieving the timeout + value for + +Return Value: + + None, but it sets a new default timeout for a class of devices. + +--*/ +_IRQL_requires_max_(PASSIVE_LEVEL) +ULONG +ClassQueryTimeOutRegistryValue( + _In_ PDEVICE_OBJECT DeviceObject + ) +{ + // + // Find the appropriate reg. key + // + +#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case + PCLASS_DRIVER_EXTENSION driverExtension = IoGetDriverObjectExtension(DeviceObject->DriverObject, CLASS_DRIVER_EXTENSION_KEY); + + PUNICODE_STRING registryPath = &(driverExtension->RegistryPath); + + PRTL_QUERY_REGISTRY_TABLE parameters = NULL; + PWSTR path; + NTSTATUS status; + LONG timeOut = 0; + ULONG zero = 0; + ULONG size; + + PAGED_CODE(); + + if (!registryPath) { + return 0; + } + + parameters = ExAllocatePoolWithTag(NonPagedPoolNx, + sizeof(RTL_QUERY_REGISTRY_TABLE)*2, + '1BcS'); + + if (!parameters) { + return 0; + } + + size = registryPath->MaximumLength + sizeof(WCHAR); + path = ExAllocatePoolWithTag(NonPagedPoolNx, size, '2BcS'); + + if (!path) { + FREE_POOL(parameters); + return 0; + } + + RtlZeroMemory(path,size); + RtlCopyMemory(path, registryPath->Buffer, size - sizeof(WCHAR)); + + + // + // Check for the Timeout value. + // + + RtlZeroMemory(parameters, + (sizeof(RTL_QUERY_REGISTRY_TABLE)*2)); + + parameters[0].Flags = RTL_QUERY_REGISTRY_DIRECT; + parameters[0].Name = L"TimeOutValue"; + parameters[0].EntryContext = &timeOut; + parameters[0].DefaultType = REG_DWORD; + parameters[0].DefaultData = &zero; + parameters[0].DefaultLength = sizeof(ULONG); + + status = RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE | RTL_REGISTRY_OPTIONAL, + path, + parameters, + NULL, + NULL); + + if (!(NT_SUCCESS(status))) { + timeOut = 0; + } + + FREE_POOL(parameters); + FREE_POOL(path); + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, + "ClassQueryTimeOutRegistryValue: Timeout value %d\n", + timeOut)); + + + return timeOut; + +} // end ClassQueryTimeOutRegistryValue() + + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassCheckVerifyComplete() ISSUE-2000/02/18-henrygab - why public?! + +Routine Description: + + This routine executes when the port driver has completed a check verify + ioctl. It will set the status of the master Irp, copy the media change + count and complete the request. + +Arguments: + + Fdo - Supplies the functional device object which represents the logical unit. + + Irp - Supplies the Irp which has completed. + + Context - NULL + +Return Value: + + NT status + +--*/ +NTSTATUS +ClassCheckVerifyComplete( + IN PDEVICE_OBJECT Fdo, + IN PIRP Irp, + IN PVOID Context + ) +{ + PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp); + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension; + + PIRP originalIrp; + + UNREFERENCED_PARAMETER(Context); + + ASSERT_FDO(Fdo); + + originalIrp = irpStack->Parameters.Others.Argument1; + + // + // Copy the media change count and status + // + + *((PULONG) (originalIrp->AssociatedIrp.SystemBuffer)) = + fdoExtension->MediaChangeCount; + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "ClassCheckVerifyComplete - Media change count for" + "device %d is %lx - saved as %lx\n", + fdoExtension->DeviceNumber, + fdoExtension->MediaChangeCount, + *((PULONG) originalIrp->AssociatedIrp.SystemBuffer))); + + originalIrp->IoStatus.Status = Irp->IoStatus.Status; + originalIrp->IoStatus.Information = sizeof(ULONG); + + ClassReleaseRemoveLock(Fdo, originalIrp); + ClassCompleteRequest(Fdo, originalIrp, IO_DISK_INCREMENT); + + IoFreeIrp(Irp); + + return STATUS_MORE_PROCESSING_REQUIRED; + +} // end ClassCheckVerifyComplete() + + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassGetDescriptor() + +Routine Description: + + This routine will perform a query for the specified property id and will + allocate a non-paged buffer to store the data in. It is the responsibility + of the caller to ensure that this buffer is freed. + + This routine must be run at IRQL_PASSIVE_LEVEL + +Arguments: + + DeviceObject - the device to query + DeviceInfo - a location to store a pointer to the buffer we allocate + +Return Value: + + status + if status is unsuccessful *DeviceInfo will be set to NULL, else the + buffer allocated on behalf of the caller. + +--*/ +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS +ClassGetDescriptor( + _In_ PDEVICE_OBJECT DeviceObject, + _In_ PSTORAGE_PROPERTY_ID PropertyId, + _Outptr_ PVOID *Descriptor + ) +{ + STORAGE_PROPERTY_QUERY query = {0}; + IO_STATUS_BLOCK ioStatus; + + PSTORAGE_DESCRIPTOR_HEADER descriptor = NULL; + ULONG length; + + PAGED_CODE(); + + // + // Set the passed-in descriptor pointer to NULL as default + // + + *Descriptor = NULL; + + query.PropertyId = *PropertyId; + query.QueryType = PropertyStandardQuery; + + // + // On the first pass we just want to get the first few + // bytes of the descriptor so we can read it's size + // + + descriptor = (PVOID)&query; + + NT_ASSERT(sizeof(STORAGE_PROPERTY_QUERY) >= (sizeof(ULONG)*2)); + + ClassSendDeviceIoControlSynchronous( + IOCTL_STORAGE_QUERY_PROPERTY, + DeviceObject, + &query, + sizeof(STORAGE_PROPERTY_QUERY), + sizeof(ULONG) * 2, + FALSE, + &ioStatus + ); + + if(!NT_SUCCESS(ioStatus.Status)) { + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClassGetDescriptor: error %lx trying to " + "query properties #1\n", ioStatus.Status)); + return ioStatus.Status; + } + + if (descriptor->Size == 0) { + + // + // This DebugPrint is to help third-party driver writers + // + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClassGetDescriptor: size returned was zero?! (status " + "%x\n", ioStatus.Status)); + return STATUS_UNSUCCESSFUL; + + } + + // + // This time we know how much data there is so we can + // allocate a buffer of the correct size + // + + length = descriptor->Size; + NT_ASSERT(length >= sizeof(STORAGE_PROPERTY_QUERY)); + length = max(length, sizeof(STORAGE_PROPERTY_QUERY)); + + descriptor = ExAllocatePoolWithTag(NonPagedPoolNx, length, '4BcS'); + + if(descriptor == NULL) { + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClassGetDescriptor: unable to memory for descriptor " + "(%d bytes)\n", length)); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // + // setup the query again, as it was overwritten above + // + + RtlZeroMemory(&query, sizeof(STORAGE_PROPERTY_QUERY)); + query.PropertyId = *PropertyId; + query.QueryType = PropertyStandardQuery; + + // + // copy the input to the new outputbuffer + // + + RtlZeroMemory(descriptor, length); + + RtlCopyMemory(descriptor, + &query, + sizeof(STORAGE_PROPERTY_QUERY) + ); + + ClassSendDeviceIoControlSynchronous( + IOCTL_STORAGE_QUERY_PROPERTY, + DeviceObject, + descriptor, + sizeof(STORAGE_PROPERTY_QUERY), + length, + FALSE, + &ioStatus + ); + + if(!NT_SUCCESS(ioStatus.Status)) { + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClassGetDescriptor: error %lx trying to " + "query properties #1\n", ioStatus.Status)); + FREE_POOL(descriptor); + return ioStatus.Status; + } + + // + // return the memory we've allocated to the caller + // + + *Descriptor = descriptor; + return ioStatus.Status; +} // end ClassGetDescriptor() + + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassSignalCompletion() + +Routine Description: + + This completion routine will signal the event given as context and then + return STATUS_MORE_PROCESSING_REQUIRED to stop event completion. It is + the responsibility of the routine waiting on the event to complete the + request and free the event. + +Arguments: + + DeviceObject - a pointer to the device object + + Irp - a pointer to the irp + + Event - a pointer to the event to signal + +Return Value: + + STATUS_MORE_PROCESSING_REQUIRED + +--*/ +NTSTATUS +ClassSignalCompletion( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP Irp, + IN PVOID Context + ) +{ + PKEVENT Event = (PKEVENT)Context; + + UNREFERENCED_PARAMETER(DeviceObject); + UNREFERENCED_PARAMETER(Irp); + + if (Context == NULL) { + NT_ASSERT(Context != NULL); + return STATUS_INVALID_PARAMETER; + } + + KeSetEvent(Event, IO_NO_INCREMENT, FALSE); + + return STATUS_MORE_PROCESSING_REQUIRED; +} // end ClassSignalCompletion() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassPnpQueryFdoRelations() + +Routine Description: + + This routine will call the driver's enumeration routine to update the + list of PDO's. It will then build a response to the + IRP_MN_QUERY_DEVICE_RELATIONS and place it into the information field in + the irp. + +Arguments: + + Fdo - a pointer to the functional device object we are enumerating + + Irp - a pointer to the enumeration request + +Return Value: + + status + +--*/ +NTSTATUS +ClassPnpQueryFdoRelations( + IN PDEVICE_OBJECT Fdo, + IN PIRP Irp + ) +{ + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension; +#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case + PCLASS_DRIVER_EXTENSION driverExtension = IoGetDriverObjectExtension(Fdo->DriverObject, CLASS_DRIVER_EXTENSION_KEY); + + PAGED_CODE(); + + _Analysis_assume_(driverExtension != NULL); + + // + // If there's already an enumeration in progress then don't start another + // one. + // + + if(InterlockedIncrement((volatile LONG *)&(fdoExtension->EnumerationInterlock)) == 1) { + driverExtension->InitData.ClassEnumerateDevice(Fdo); + } + + Irp->IoStatus.Status = ClassRetrieveDeviceRelations( + Fdo, + BusRelations, + &((PDEVICE_RELATIONS) Irp->IoStatus.Information)); + InterlockedDecrement((volatile LONG *)&(fdoExtension->EnumerationInterlock)); + + return Irp->IoStatus.Status; +} // end ClassPnpQueryFdoRelations() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassMarkChildrenMissing() + +Routine Description: + + This routine will call ClassMarkChildMissing() for all children. + It acquires the ChildLock before calling ClassMarkChildMissing(). + +Arguments: + + Fdo - the "bus's" device object, such as the disk FDO for non-removable + disks with multiple partitions. + +Return Value: + + None + +--*/ +_IRQL_requires_max_(PASSIVE_LEVEL) +VOID +ClassMarkChildrenMissing( + _In_ PFUNCTIONAL_DEVICE_EXTENSION Fdo + ) +{ + PCOMMON_DEVICE_EXTENSION commonExtension = &(Fdo->CommonExtension); + PPHYSICAL_DEVICE_EXTENSION nextChild = commonExtension->ChildList; + + PAGED_CODE(); + + ClassAcquireChildLock(Fdo); + + while (nextChild){ + PPHYSICAL_DEVICE_EXTENSION tmpChild; + + /* + * ClassMarkChildMissing will also dequeue the child extension. + * So get the next pointer before calling ClassMarkChildMissing. + */ + tmpChild = nextChild; + nextChild = tmpChild->CommonExtension.ChildList; + ClassMarkChildMissing(tmpChild, FALSE); + } + ClassReleaseChildLock(Fdo); + return; +} // end ClassMarkChildrenMissing() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassMarkChildMissing() + +Routine Description: + + This routine will make an active child "missing." If the device has never + been enumerated then it will be deleted on the spot. If the device has + not been enumerated then it will be marked as missing so that we can + not report it in the next device enumeration. + +Arguments: + + Child - the child device to be marked as missing. + + AcquireChildLock - TRUE if the child lock should be acquired before removing + the missing child. FALSE if the child lock is already + acquired by this thread. + +Return Value: + + returns whether or not the child device object has previously been reported + to PNP. + +--*/ +_IRQL_requires_max_(PASSIVE_LEVEL) +BOOLEAN +ClassMarkChildMissing( + _In_ PPHYSICAL_DEVICE_EXTENSION Child, + _In_ BOOLEAN AcquireChildLock + ) +{ + BOOLEAN returnValue = Child->IsEnumerated; + + PAGED_CODE(); + ASSERT_PDO(Child->DeviceObject); + + Child->IsMissing = TRUE; + + // + // Make sure this child is not in the active list. + // + + ClassRemoveChild(Child->CommonExtension.PartitionZeroExtension, + Child, + AcquireChildLock); + + if(Child->IsEnumerated == FALSE) { + PCOMMON_DEVICE_EXTENSION commonExtension = Child->DeviceObject->DeviceExtension; + commonExtension->IsRemoved = REMOVE_PENDING; + ClassRemoveDevice(Child->DeviceObject, IRP_MN_REMOVE_DEVICE); + } + + return returnValue; +} // end ClassMarkChildMissing() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassRetrieveDeviceRelations() + +Routine Description: + + This routine will allocate a buffer to hold the specified list of + relations. It will then fill in the list with referenced device pointers + and will return the request. + +Arguments: + + Fdo - pointer to the FDO being queried + + RelationType - what type of relations are being queried + + DeviceRelations - a location to store a pointer to the response + +Return Value: + + status + +--*/ +NTSTATUS +ClassRetrieveDeviceRelations( + IN PDEVICE_OBJECT Fdo, + IN DEVICE_RELATION_TYPE RelationType, + OUT PDEVICE_RELATIONS *DeviceRelations + ) +{ + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension; + + ULONG count = 0; + ULONG i; + + PPHYSICAL_DEVICE_EXTENSION nextChild; + + ULONG relationsSize; + PDEVICE_RELATIONS deviceRelations = NULL; + PDEVICE_RELATIONS oldRelations = *DeviceRelations; + + NTSTATUS status; + + UNREFERENCED_PARAMETER(RelationType); + + PAGED_CODE(); + + ClassAcquireChildLock(fdoExtension); + + nextChild = fdoExtension->CommonExtension.ChildList; + + // + // Count the number of PDO's attached to this disk + // + + while (nextChild != NULL) { + PCOMMON_DEVICE_EXTENSION commonExtension; + + commonExtension = &(nextChild->CommonExtension); + + NT_ASSERTMSG("ClassPnp internal error: missing child on active list\n", + (nextChild->IsMissing == FALSE)); + + nextChild = commonExtension->ChildList; + + count++; + }; + + // + // If relations already exist in the QDR, adjust the current count + // to include the previous list. + // + + if (oldRelations) { + count += oldRelations->Count; + } + + relationsSize = (sizeof(DEVICE_RELATIONS) + + (count * sizeof(PDEVICE_OBJECT))); + + deviceRelations = ExAllocatePoolWithTag(PagedPool, relationsSize, '5BcS'); + + if (deviceRelations == NULL) { + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_ENUM, "ClassRetrieveDeviceRelations: unable to allocate " + "%d bytes for device relations\n", relationsSize)); + + ClassReleaseChildLock(fdoExtension); + + return STATUS_INSUFFICIENT_RESOURCES; + } + + RtlZeroMemory(deviceRelations, relationsSize); + + if (oldRelations) { + + // + // Copy the old relations to the new list and free the old list. + // + + for (i = 0; i < oldRelations->Count; i++) { + deviceRelations->Objects[i] = oldRelations->Objects[i]; + } + + FREE_POOL(oldRelations); + } + + nextChild = fdoExtension->CommonExtension.ChildList; + i = count; + + while (nextChild != NULL) { + PCOMMON_DEVICE_EXTENSION commonExtension; + + commonExtension = &(nextChild->CommonExtension); + + NT_ASSERTMSG("ClassPnp internal error: missing child on active list\n", + (nextChild->IsMissing == FALSE)); + + _Analysis_assume_(i >= 1); + deviceRelations->Objects[--i] = nextChild->DeviceObject; + + status = ObReferenceObjectByPointer( + nextChild->DeviceObject, + 0, + NULL, + KernelMode); + if (!NT_SUCCESS(status)) { + NT_ASSERT(!"Error referencing child device by pointer"); + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_ENUM, + "ClassRetrieveDeviceRelations: Error referencing child " + "device %p by pointer\n", nextChild->DeviceObject)); + + } + nextChild->IsEnumerated = TRUE; + nextChild = commonExtension->ChildList; + } + + NT_ASSERTMSG("Child list has changed: ", i == 0); + + deviceRelations->Count = count; + *DeviceRelations = deviceRelations; + + ClassReleaseChildLock(fdoExtension); + return STATUS_SUCCESS; +} // end ClassRetrieveDeviceRelations() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassGetPdoId() + +Routine Description: + + This routine will call into the driver to retrieve a copy of one of it's + id strings. + +Arguments: + + Pdo - a pointer to the pdo being queried + + IdType - which type of id string is being queried + + IdString - an allocated unicode string structure which the driver + can fill in. + +Return Value: + + status + +--*/ +NTSTATUS +ClassGetPdoId( + IN PDEVICE_OBJECT Pdo, + IN BUS_QUERY_ID_TYPE IdType, + IN PUNICODE_STRING IdString + ) +{ +#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case + PCLASS_DRIVER_EXTENSION driverExtension = IoGetDriverObjectExtension(Pdo->DriverObject, CLASS_DRIVER_EXTENSION_KEY); + + _Analysis_assume_(driverExtension != NULL); + + ASSERT_PDO(Pdo); + NT_ASSERT(driverExtension->InitData.ClassQueryId); + + PAGED_CODE(); + + return driverExtension->InitData.ClassQueryId( Pdo, IdType, IdString); +} // end ClassGetPdoId() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassQueryPnpCapabilities() + +Routine Description: + + This routine will call into the class driver to retrieve it's pnp + capabilities. + +Arguments: + + PhysicalDeviceObject - The physical device object to retrieve properties + for. + +Return Value: + + status + +--*/ +NTSTATUS +ClassQueryPnpCapabilities( + IN PDEVICE_OBJECT DeviceObject, + IN PDEVICE_CAPABILITIES Capabilities + ) +{ + PCLASS_DRIVER_EXTENSION driverExtension = + ClassGetDriverExtension(DeviceObject->DriverObject); + PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; + + PCLASS_QUERY_PNP_CAPABILITIES queryRoutine = NULL; + + PAGED_CODE(); + + NT_ASSERT(DeviceObject); + NT_ASSERT(Capabilities); + + if(commonExtension->IsFdo) { + queryRoutine = driverExtension->InitData.FdoData.ClassQueryPnpCapabilities; + } else { + queryRoutine = driverExtension->InitData.PdoData.ClassQueryPnpCapabilities; + } + + if(queryRoutine) { + return queryRoutine(DeviceObject, + Capabilities); + } else { + return STATUS_NOT_IMPLEMENTED; + } +} // end ClassQueryPnpCapabilities() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassInvalidateBusRelations() + +Routine Description: + + This routine re-enumerates the devices on the "bus". It will call into + the driver's ClassEnumerate routine to update the device objects + immediately. It will then schedule a bus re-enumeration for pnp by calling + IoInvalidateDeviceRelations. + +Arguments: + + Fdo - a pointer to the functional device object for this bus + +Return Value: + + none + +--*/ +_IRQL_requires_max_(PASSIVE_LEVEL) +VOID +ClassInvalidateBusRelations( + _In_ PDEVICE_OBJECT Fdo + ) +{ + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension; +#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case + PCLASS_DRIVER_EXTENSION driverExtension = IoGetDriverObjectExtension(Fdo->DriverObject, CLASS_DRIVER_EXTENSION_KEY); + + NTSTATUS status = STATUS_SUCCESS; + + PAGED_CODE(); + + _Analysis_assume_(driverExtension != NULL); + + ASSERT_FDO(Fdo); + NT_ASSERT(driverExtension->InitData.ClassEnumerateDevice != NULL); + + if(InterlockedIncrement((volatile LONG *)&(fdoExtension->EnumerationInterlock)) == 1) { + status = driverExtension->InitData.ClassEnumerateDevice(Fdo); + } + InterlockedDecrement((volatile LONG *)&(fdoExtension->EnumerationInterlock)); + + if(!NT_SUCCESS(status)) { + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_ENUM, "ClassInvalidateBusRelations: EnumerateDevice routine " + "returned %lx\n", status)); + } + + IoInvalidateDeviceRelations(fdoExtension->LowerPdo, BusRelations); + + return; +} // end ClassInvalidateBusRelations() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassRemoveDevice() ISSUE-2000/02/18-henrygab - why public?! + +Routine Description: + + This routine is called to handle the "removal" of a device. It will + forward the request downwards if necesssary, call into the driver + to release any necessary resources (memory, events, etc) and then + will delete the device object. + +Arguments: + + DeviceObject - a pointer to the device object being removed + + RemoveType - indicates what type of remove this is (regular or surprise). + +Return Value: + + status + +--*/ +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS +ClassRemoveDevice( + _In_ PDEVICE_OBJECT DeviceObject, + _In_ UCHAR RemoveType + ) +{ +#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case + PCLASS_DRIVER_EXTENSION driverExtension = IoGetDriverObjectExtension(DeviceObject->DriverObject, CLASS_DRIVER_EXTENSION_KEY); + PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; + PDEVICE_OBJECT lowerDeviceObject = commonExtension->LowerDeviceObject; + BOOLEAN proceedWithRemove = TRUE; + NTSTATUS status; + PEX_RUNDOWN_REF_CACHE_AWARE removeLockRundown = NULL; + + + PAGED_CODE(); + + _Analysis_assume_(driverExtension != NULL); + + /* + * Deregister from WMI. + */ + if (commonExtension->IsFdo || + driverExtension->InitData.PdoData.ClassWmiInfo.GuidRegInfo) { + status = IoWMIRegistrationControl(DeviceObject, WMIREG_ACTION_DEREGISTER); + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_WMI, "ClassRemoveDevice: IoWMIRegistrationControl(%p, WMI_ACTION_DEREGISTER) --> %lx", DeviceObject, status)); + } + + /* + * If we exposed a "shingle" (a named device interface openable by CreateFile) + * then delete it now. + */ + if (commonExtension->MountedDeviceInterfaceName.Buffer){ + (VOID)IoSetDeviceInterfaceState(&commonExtension->MountedDeviceInterfaceName, FALSE); + RtlFreeUnicodeString(&commonExtension->MountedDeviceInterfaceName); + RtlInitUnicodeString(&commonExtension->MountedDeviceInterfaceName, NULL); + } + + // + // If this is a surprise removal we leave the device around - which means + // we don't have to (or want to) drop the remove lock and wait for pending + // requests to complete. + // + + if (RemoveType == IRP_MN_REMOVE_DEVICE) { + + // + // Release the lock we acquired when the device object was created. + // + + ClassReleaseRemoveLock(DeviceObject, (PIRP) DeviceObject); + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP, "ClasspRemoveDevice - Reference count is now %d\n", + commonExtension->RemoveLock)); + + // + // The RemoveLockRundown allows fast protection of the device object + // structure that is torn down by a single thread. While in the rundown process (the call to + // ExWaitForRundownProtectionReleaseCacheAware returns), the rundown object becomes + // invalid and the subsequent calls to ExAcquireRundownProtectionCacheAware will return FALSE. + // ExReInitializeRundownProtectionCacheAware needs to be called to re-initialize the + // RemoveLockRundown protection. + // + + removeLockRundown = (PEX_RUNDOWN_REF_CACHE_AWARE) + ((PCHAR)commonExtension->PrivateCommonData + + sizeof(CLASS_PRIVATE_COMMON_DATA)); + ExWaitForRundownProtectionReleaseCacheAware(removeLockRundown); + + KeSetEvent(&commonExtension->RemoveEvent, + IO_NO_INCREMENT, + FALSE); + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP, "ClasspRemoveDevice - removing device %p\n", DeviceObject)); + + if (commonExtension->IsFdo) { + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP, "ClasspRemoveDevice - FDO %p has received a " + "remove request.\n", DeviceObject)); + + } else { + PPHYSICAL_DEVICE_EXTENSION pdoExtension = DeviceObject->DeviceExtension; + + if (pdoExtension->IsMissing) { + /* + * The child partition PDO is missing, so we are going to go ahead + * and delete it for the remove. + */ + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP, "ClasspRemoveDevice - PDO %p is missing and will be removed", DeviceObject)); + } else { + /* + * We got a remove for a child partition PDO which is not actually missing. + * So we will NOT actually delete it. + */ + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP, "ClasspRemoveDevice - PDO %p still exists and will be removed when it disappears", DeviceObject)); + + ExReInitializeRundownProtectionCacheAware(removeLockRundown); + + // + // Reacquire the remove lock for the next time this comes around. + // + + ClassAcquireRemoveLock(DeviceObject, (PIRP) DeviceObject); + + // + // the device wasn't missing so it's not really been removed. + // + + commonExtension->IsRemoved = NO_REMOVE; + + IoInvalidateDeviceRelations( + commonExtension->PartitionZeroExtension->LowerPdo, + BusRelations); + + proceedWithRemove = FALSE; + } + } + } + + + if (proceedWithRemove) { + + /* + * Call the class driver's remove handler. + * All this is supposed to do is clean up its data and device interfaces. + */ + NT_ASSERT(commonExtension->DevInfo->ClassRemoveDevice); + status = commonExtension->DevInfo->ClassRemoveDevice(DeviceObject, RemoveType); + NT_ASSERT(NT_SUCCESS(status)); + status = STATUS_SUCCESS; + UNREFERENCED_PARAMETER(status); // disables prefast warning; defensive coding... + + if (commonExtension->IsFdo) { + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension; + + ClasspDisableTimer(fdoExtension); + + if (RemoveType == IRP_MN_REMOVE_DEVICE) { + + PPHYSICAL_DEVICE_EXTENSION child; + + // + // If this FDO is idle power managed, remove it from the + // list of idle power managed FDOs. + // + if (fdoExtension->FunctionSupportInfo && + fdoExtension->FunctionSupportInfo->IdlePower.IdlePowerEnabled) { + PIDLE_POWER_FDO_LIST_ENTRY fdoEntry; + + KeAcquireGuardedMutex(&IdlePowerFDOListMutex); + fdoEntry = (PIDLE_POWER_FDO_LIST_ENTRY)IdlePowerFDOList.Flink; + while ((PLIST_ENTRY)fdoEntry != &IdlePowerFDOList) { + PIDLE_POWER_FDO_LIST_ENTRY nextEntry = (PIDLE_POWER_FDO_LIST_ENTRY)fdoEntry->ListEntry.Flink; + if (fdoEntry->Fdo == DeviceObject) { + RemoveEntryList(&(fdoEntry->ListEntry)); + ExFreePool(fdoEntry); + break; + } + fdoEntry = nextEntry; + } + KeReleaseGuardedMutex(&IdlePowerFDOListMutex); + } + + // + // Cleanup the media detection resources now that the class driver + // has stopped it's timer (if any) and we can be sure they won't + // call us to do detection again. + // + + ClassCleanupMediaChangeDetection(fdoExtension); + + // + // Cleanup any Failure Prediction stuff + // + FREE_POOL(fdoExtension->FailurePredictionInfo); + + /* + * Ordinarily all child PDOs will be removed by the time + * that the parent gets the REMOVE_DEVICE. + * However, if a child PDO has been created but has not + * been announced in a QueryDeviceRelations, then it is + * just a private data structure unknown to pnp, and we have + * to delete it ourselves. + */ + ClassAcquireChildLock(fdoExtension); + child = ClassRemoveChild(fdoExtension, NULL, FALSE); + while (child) { + PCOMMON_DEVICE_EXTENSION childCommonExtension = child->DeviceObject->DeviceExtension; + + // + // Yank the pdo. This routine will unlink the device from the + // pdo list so NextPdo will point to the next one when it's + // complete. + // + child->IsMissing = TRUE; + childCommonExtension->IsRemoved = REMOVE_PENDING; + ClassRemoveDevice(child->DeviceObject, IRP_MN_REMOVE_DEVICE); + child = ClassRemoveChild(fdoExtension, NULL, FALSE); + } + ClassReleaseChildLock(fdoExtension); + } + else if (RemoveType == IRP_MN_SURPRISE_REMOVAL){ + /* + * This is a surprise-remove on the parent FDO. + * We will mark the child PDOs as missing so that they + * will actually get deleted when they get a REMOVE_DEVICE. + */ + ClassMarkChildrenMissing(fdoExtension); + } + + if (RemoveType == IRP_MN_REMOVE_DEVICE) { + + ClasspFreeReleaseRequest(DeviceObject); + + // + // Free FDO-specific data structs + // + if (fdoExtension->PrivateFdoData) { + // + // Only remove the entry if the list has been initialized, or + // else we will access invalid memory. + // + PLIST_ENTRY allFdosListEntry = &fdoExtension->PrivateFdoData->AllFdosListEntry; + if (allFdosListEntry->Flink && allFdosListEntry->Blink) { + // + // Remove the FDO from the static list. + // Pnp is synchronized so this shouldn't need any synchronization. + // + RemoveEntryList(allFdosListEntry); + } + InitializeListHead(allFdosListEntry); + + DestroyAllTransferPackets(DeviceObject); + + // + // Delete the tick timer now. + // + ClasspDeleteTimer(fdoExtension); + + + FREE_POOL(fdoExtension->PrivateFdoData->PowerProcessIrp); + FREE_POOL(fdoExtension->PrivateFdoData->FreeTransferPacketsLists); + FREE_POOL(fdoExtension->PrivateFdoData); + } + +#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD) + FREE_POOL(fdoExtension->AdditionalFdoData); +#endif + + if (commonExtension->DeviceName.Buffer) { + FREE_POOL(commonExtension->DeviceName.Buffer); + RtlInitUnicodeString(&commonExtension->DeviceName, NULL); + } + +#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD) + if (fdoExtension->FunctionSupportInfo != NULL) { + FREE_POOL(fdoExtension->FunctionSupportInfo->HwFirmwareInfo); + } +#endif + FREE_POOL(fdoExtension->FunctionSupportInfo); + + FREE_POOL(fdoExtension->MiniportDescriptor); + + FREE_POOL(fdoExtension->AdapterDescriptor); + + FREE_POOL(fdoExtension->DeviceDescriptor); + + // + // Detach our device object from the stack - there's no reason + // to hold off our cleanup any longer. + // + + IoDetachDevice(lowerDeviceObject); + } + } + else { + /* + * This is a child partition PDO. + * We have already determined that it was previously marked + * as missing. So if this is a REMOVE_DEVICE, we will actually + * delete it. + */ + if (RemoveType == IRP_MN_REMOVE_DEVICE) { + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = commonExtension->PartitionZeroExtension; + PPHYSICAL_DEVICE_EXTENSION pdoExtension = (PPHYSICAL_DEVICE_EXTENSION)commonExtension; + + // + // See if this device is in the child list (if this was a suprise + // removal it might be) and remove it. + // + ClassRemoveChild(fdoExtension, pdoExtension, TRUE); + } + } + + commonExtension->PartitionLength.QuadPart = 0; + + if (RemoveType == IRP_MN_REMOVE_DEVICE) { + + ClasspUninitializeRemoveTracking(DeviceObject); + + IoDeleteDevice(DeviceObject); + } + } + + return STATUS_SUCCESS; +} // end ClassRemoveDevice() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassGetDriverExtension() + +Routine Description: + + This routine will return the classpnp's driver extension. + +Arguments: + + DriverObject - the driver object for which to get classpnp's extension + +Return Value: + + Either NULL if none, or a pointer to the driver extension + +--*/ +__drv_aliasesMem +_IRQL_requires_max_(DISPATCH_LEVEL) +PCLASS_DRIVER_EXTENSION +ClassGetDriverExtension( + _In_ PDRIVER_OBJECT DriverObject + ) +{ +#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case + return IoGetDriverObjectExtension(DriverObject, CLASS_DRIVER_EXTENSION_KEY); +} // end ClassGetDriverExtension() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClasspStartIo() + +Routine Description: + + This routine wraps the class driver's start io routine. If the device + is being removed it will complete any requests with + STATUS_DEVICE_DOES_NOT_EXIST and fire up the next packet. + +Arguments: + +Return Value: + + none + +--*/ +VOID +ClasspStartIo( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP Irp + ) +{ + PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; + + // + // We're already holding the remove lock so just check the variable and + // see what's going on. + // + + if(commonExtension->IsRemoved) { + + Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST; + +#pragma warning(suppress:4054) // okay to type cast function pointer to PIRP for this use case + ClassAcquireRemoveLock(DeviceObject, (PIRP) ClasspStartIo); + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_DISK_INCREMENT); + IoStartNextPacket(DeviceObject, TRUE); // Some IO is cancellable + +#pragma warning(suppress:4054) // okay to type cast function pointer to PIRP for this use case + ClassReleaseRemoveLock(DeviceObject, (PIRP) ClasspStartIo); + + return; + } + + commonExtension->DriverExtension->InitData.ClassStartIo( + DeviceObject, + Irp); + + return; +} // ClasspStartIo() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassUpdateInformationInRegistry() + +Routine Description: + + This routine has knowledge about the layout of the device map information + in the registry. It will update this information to include a value + entry specifying the dos device name that is assumed to get assigned + to this NT device name. For more information on this assigning of the + dos device name look in the drive support routine in the hal that assigns + all dos names. + + Since some versions of some device's firmware did not work and some + vendors did not bother to follow the specification, the entire inquiry + information must also be stored in the registry so than someone can + figure out the firmware version. + +Arguments: + + DeviceObject - A pointer to the device object for the tape device. + +Return Value: + + None + +--*/ +_IRQL_requires_max_(PASSIVE_LEVEL) +VOID +ClassUpdateInformationInRegistry( + _In_ PDEVICE_OBJECT Fdo, + _In_ PCHAR DeviceName, + _In_ ULONG DeviceNumber, + _In_reads_bytes_opt_(InquiryDataLength) PINQUIRYDATA InquiryData, + _In_ ULONG InquiryDataLength + ) +{ + NTSTATUS status; + SCSI_ADDRESS scsiAddress = {0}; + OBJECT_ATTRIBUTES objectAttributes = {0}; + STRING string; + UNICODE_STRING unicodeName = {0}; + UNICODE_STRING unicodeRegistryPath = {0}; + UNICODE_STRING unicodeData = {0}; + HANDLE targetKey; + IO_STATUS_BLOCK ioStatus; + UCHAR buffer[256] = {0}; + + PAGED_CODE(); + + NT_ASSERT(DeviceName); + targetKey = NULL; + + TRY { + + // + // Issue GET_ADDRESS Ioctl to determine path, target, and lun information. + // + + ClassSendDeviceIoControlSynchronous( + IOCTL_SCSI_GET_ADDRESS, + Fdo, + &scsiAddress, + 0, + sizeof(SCSI_ADDRESS), + FALSE, + &ioStatus + ); + + if (!NT_SUCCESS(ioStatus.Status)) { + + status = ioStatus.Status; + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, + "UpdateInformationInRegistry: Get Address failed %lx\n", + status)); + LEAVE; + + } else { + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, + "GetAddress: Port %x, Path %x, Target %x, Lun %x\n", + scsiAddress.PortNumber, + scsiAddress.PathId, + scsiAddress.TargetId, + scsiAddress.Lun)); + + } + + status = RtlStringCchPrintfA((NTSTRSAFE_PSTR)buffer, + sizeof(buffer)-1, + "\\Registry\\Machine\\Hardware\\DeviceMap\\Scsi\\Scsi Port %d\\Scsi Bus %d\\Target Id %d\\Logical Unit Id %d", + scsiAddress.PortNumber, + scsiAddress.PathId, + scsiAddress.TargetId, + scsiAddress.Lun); + + if (!NT_SUCCESS(status)) { + LEAVE; + } + + RtlInitString(&string, (PCSZ)buffer); + + status = RtlAnsiStringToUnicodeString(&unicodeRegistryPath, + &string, + TRUE); + + if (!NT_SUCCESS(status)) { + LEAVE; + } + + // + // Open the registry key for the scsi information for this + // scsibus, target, lun. + // + + InitializeObjectAttributes(&objectAttributes, + &unicodeRegistryPath, + OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, + NULL, + NULL); + + status = ZwOpenKey(&targetKey, + KEY_READ | KEY_WRITE, + &objectAttributes); + + if (!NT_SUCCESS(status)) { + LEAVE; + } + + // + // Now construct and attempt to create the registry value + // specifying the device name in the appropriate place in the + // device map. + // + + RtlInitUnicodeString(&unicodeName, L"DeviceName"); + + status = RtlStringCchPrintfA((NTSTRSAFE_PSTR)buffer, sizeof(buffer)-1, "%s%d", DeviceName, DeviceNumber); + if (!NT_SUCCESS(status)) { + LEAVE; + } + + RtlInitString(&string, (PCSZ)buffer); + status = RtlAnsiStringToUnicodeString(&unicodeData, + &string, + TRUE); + if (NT_SUCCESS(status)) { + status = ZwSetValueKey(targetKey, + &unicodeName, + 0, + REG_SZ, + unicodeData.Buffer, + unicodeData.Length); + } + + // + // if they sent in data, update the registry + // + + if (NT_SUCCESS(status) && InquiryDataLength) { + + NT_ASSERT(InquiryData); + + RtlInitUnicodeString(&unicodeName, L"InquiryData"); + status = ZwSetValueKey(targetKey, + &unicodeName, + 0, + REG_BINARY, + InquiryData, + InquiryDataLength); + } + + // that's all, except to clean up. + + } FINALLY { + + if (!NT_SUCCESS(status)) { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, + "Failure to update information in registry: %08x\n", + status + )); + } + + if (unicodeData.Buffer) { + RtlFreeUnicodeString(&unicodeData); + } + if (unicodeRegistryPath.Buffer) { + RtlFreeUnicodeString(&unicodeRegistryPath); + } + if (targetKey) { + ZwClose(targetKey); + } + + } + +} // end ClassUpdateInformationInRegistry() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClasspSendSynchronousCompletion() + +Routine Description: + + This completion routine will set the user event in the irp after + freeing the irp and the associated MDL (if any). + +Arguments: + + DeviceObject - the device object which requested the completion routine + + Irp - the irp being completed + + Context - unused + +Return Value: + + STATUS_MORE_PROCESSING_REQUIRED + +--*/ +NTSTATUS +ClasspSendSynchronousCompletion( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP Irp, + IN PVOID Context + ) +{ + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClasspSendSynchronousCompletion: %p %p %p\n", + DeviceObject, Irp, Context)); + // + // First set the status and information fields in the io status block + // provided by the caller. + // + + *(Irp->UserIosb) = Irp->IoStatus; + + // + // Unlock the pages for the data buffer. + // + + if(Irp->MdlAddress) { + MmUnlockPages(Irp->MdlAddress); + IoFreeMdl(Irp->MdlAddress); + } + + // + // Signal the caller's event. + // + + KeSetEvent(Irp->UserEvent, IO_NO_INCREMENT, FALSE); + + // + // Free the MDL and the IRP. + // + + IoFreeIrp(Irp); + + return STATUS_MORE_PROCESSING_REQUIRED; +} // end ClasspSendSynchronousCompletion() + +/*++ + + ISSUE-2000/02/20-henrygab Not documented ClasspRegisterMountedDeviceInterface + +--*/ +VOID +ClasspRegisterMountedDeviceInterface( + IN PDEVICE_OBJECT DeviceObject + ) +{ + + PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; + BOOLEAN isFdo = commonExtension->IsFdo; + PDEVICE_OBJECT pdo; + UNICODE_STRING interfaceName; + + NTSTATUS status; + + PAGED_CODE(); + + if(isFdo) { + + PFUNCTIONAL_DEVICE_EXTENSION functionalExtension; + + functionalExtension = + (PFUNCTIONAL_DEVICE_EXTENSION) commonExtension; + pdo = functionalExtension->LowerPdo; + } else { + pdo = DeviceObject; + } + +#pragma prefast(suppress:6014, "The allocated memory that interfaceName points to will be freed in ClassRemoveDevice().") + status = IoRegisterDeviceInterface( + pdo, + &MOUNTDEV_MOUNTED_DEVICE_GUID, + NULL, + &interfaceName + ); + + if(NT_SUCCESS(status)) { + + // + // Copy the interface name before setting the interface state - the + // name is needed by the components we notify. + // + + commonExtension->MountedDeviceInterfaceName = interfaceName; + status = IoSetDeviceInterfaceState(&interfaceName, TRUE); + + if(!NT_SUCCESS(status)) { + RtlFreeUnicodeString(&interfaceName); + } + } + + if(!NT_SUCCESS(status)) { + RtlInitUnicodeString(&(commonExtension->MountedDeviceInterfaceName), + NULL); + } + return; +} // end ClasspRegisterMountedDeviceInterface() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassSendDeviceIoControlSynchronous() + +Routine Description: + + This routine is based upon IoBuildDeviceIoControlRequest(). It has been + modified to reduce code and memory by not double-buffering the io, using + the same buffer for both input and output, allocating and deallocating + the mdl on behalf of the caller, and waiting for the io to complete. + + This routine also works around the rare cases in which APC's are disabled. + Since IoBuildDeviceIoControl() used APC's to signal completion, this had + led to a number of difficult-to-detect hangs, where the irp was completed, + but the event passed to IoBuild..() was still being waited upon by the + caller. + +Arguments: + + IoControlCode - the IOCTL to send + + TargetDeviceObject - the device object that should handle the ioctl + + Buffer - the input and output buffer, or NULL if no input/output + + InputBufferLength - the number of bytes prepared for the IOCTL in Buffer + + OutputBufferLength - the number of bytes to be filled in upon success + + InternalDeviceIoControl - if TRUE, uses IRP_MJ_INTERNAL_DEVICE_CONTROL + + IoStatus - the status block that contains the results of the operation + +Return Value: + +--*/ +VOID +ClassSendDeviceIoControlSynchronous( + _In_ ULONG IoControlCode, + _In_ PDEVICE_OBJECT TargetDeviceObject, + _Inout_updates_opt_(_Inexpressible_(max(InputBufferLength, OutputBufferLength))) PVOID Buffer, + _In_ ULONG InputBufferLength, + _In_ ULONG OutputBufferLength, + _In_ BOOLEAN InternalDeviceIoControl, + _Out_ PIO_STATUS_BLOCK IoStatus + ) +{ + PIRP irp; + PIO_STACK_LOCATION irpSp; + ULONG method; + + PAGED_CODE(); + + irp = NULL; + method = IoControlCode & 3; + + #if DBG // Begin Argument Checking (nop in fre version) + + NT_ASSERT(ARGUMENT_PRESENT(IoStatus)); + + if ((InputBufferLength != 0) || (OutputBufferLength != 0)) { + NT_ASSERT(ARGUMENT_PRESENT(Buffer)); + } + else { + NT_ASSERT(!ARGUMENT_PRESENT(Buffer)); + } + #endif + + // + // Begin by allocating the IRP for this request. Do not charge quota to + // the current process for this IRP. + // + + irp = IoAllocateIrp(TargetDeviceObject->StackSize, FALSE); + if (!irp) { + IoStatus->Information = 0; + IoStatus->Status = STATUS_INSUFFICIENT_RESOURCES; + return; + } + + // + // Get a pointer to the stack location of the first driver which will be + // invoked. This is where the function codes and the parameters are set. + // + + irpSp = IoGetNextIrpStackLocation(irp); + + // + // Set the major function code based on the type of device I/O control + // function the caller has specified. + // + + if (InternalDeviceIoControl) { + irpSp->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL; + } else { + irpSp->MajorFunction = IRP_MJ_DEVICE_CONTROL; + } + + // + // Copy the caller's parameters to the service-specific portion of the + // IRP for those parameters that are the same for all four methods. + // + + irpSp->Parameters.DeviceIoControl.OutputBufferLength = OutputBufferLength; + irpSp->Parameters.DeviceIoControl.InputBufferLength = InputBufferLength; + irpSp->Parameters.DeviceIoControl.IoControlCode = IoControlCode; + + // + // Get the method bits from the I/O control code to determine how the + // buffers are to be passed to the driver. + // + + switch (method) + { + // + // case 0 + // + case METHOD_BUFFERED: + { + if ((InputBufferLength != 0) || (OutputBufferLength != 0)) + { + irp->AssociatedIrp.SystemBuffer = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned, + max(InputBufferLength, OutputBufferLength), + CLASS_TAG_DEVICE_CONTROL); + if (irp->AssociatedIrp.SystemBuffer == NULL) + { + IoFreeIrp(irp); + + IoStatus->Information = 0; + IoStatus->Status = STATUS_INSUFFICIENT_RESOURCES; + return; + } + + if (InputBufferLength != 0) + { + RtlCopyMemory(irp->AssociatedIrp.SystemBuffer, Buffer, InputBufferLength); + } + } + + irp->UserBuffer = Buffer; + + break; + } + + // + // case 1, case 2 + // + case METHOD_IN_DIRECT: + case METHOD_OUT_DIRECT: + { + if (InputBufferLength != 0) + { + irp->AssociatedIrp.SystemBuffer = Buffer; + } + + if (OutputBufferLength != 0) + { + irp->MdlAddress = IoAllocateMdl(Buffer, + OutputBufferLength, + FALSE, + FALSE, + (PIRP) NULL); + if (irp->MdlAddress == NULL) + { + IoFreeIrp(irp); + + IoStatus->Information = 0; + IoStatus->Status = STATUS_INSUFFICIENT_RESOURCES; + return; + } + + try + { + MmProbeAndLockPages(irp->MdlAddress, + KernelMode, + (method == METHOD_IN_DIRECT) ? IoReadAccess : IoWriteAccess); + } + #pragma warning(suppress: 6320) // We want to handle any exception that MmProbeAndLockPages might throw + except(EXCEPTION_EXECUTE_HANDLER) + { + IoFreeMdl(irp->MdlAddress); + IoFreeIrp(irp); + + IoStatus->Information = 0; + IoStatus->Status = GetExceptionCode(); + return; + } + } + + break; + } + + // + // case 3 + // + case METHOD_NEITHER: + { + NT_ASSERT(!"ClassSendDeviceIoControlSynchronous does not support METHOD_NEITHER Ioctls"); + + IoFreeIrp(irp); + + IoStatus->Information = 0; + IoStatus->Status = STATUS_NOT_SUPPORTED; + return; + } + } + + irp->Tail.Overlay.Thread = PsGetCurrentThread(); + + // + // send the irp synchronously + // + + ClassSendIrpSynchronous(TargetDeviceObject, irp); + + // + // copy the iostatus block for the caller + // + + *IoStatus = irp->IoStatus; + + // + // free any allocated resources + // + + switch (method) { + case METHOD_BUFFERED: { + + NT_ASSERT(irp->UserBuffer == Buffer); + + // + // first copy the buffered result, if any + // Note that there are no security implications in + // not checking for success since only drivers can + // call into this routine anyways... + // + + if (OutputBufferLength != 0) { + #pragma warning(suppress: 6386) // Buffer's size is max(InputBufferLength, OutputBufferLength) + RtlCopyMemory(Buffer, // irp->UserBuffer + irp->AssociatedIrp.SystemBuffer, + OutputBufferLength + ); + } + + // + // then free the memory allocated to buffer the io + // + + if ((InputBufferLength !=0) || (OutputBufferLength != 0)) { + FREE_POOL(irp->AssociatedIrp.SystemBuffer); + } + break; + } + + case METHOD_IN_DIRECT: + case METHOD_OUT_DIRECT: { + + // + // we alloc a mdl if there is an output buffer specified + // free it here after unlocking the pages + // + + if (OutputBufferLength != 0) { + NT_ASSERT(irp->MdlAddress != NULL); + MmUnlockPages(irp->MdlAddress); + IoFreeMdl(irp->MdlAddress); + irp->MdlAddress = (PMDL) NULL; + } + break; + } + + case METHOD_NEITHER: { + NT_ASSERT(!"Code is out of date"); + break; + } + } + + // + // we always have allocated an irp. free it here. + // + + IoFreeIrp(irp); + irp = (PIRP) NULL; + + // + // return the io status block's status to the caller + // + + return; +} // end ClassSendDeviceIoControlSynchronous() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassForwardIrpSynchronous() + +Routine Description: + + Forwards a given irp to the next lower device object. + +Arguments: + + CommonExtension - the common class extension + + Irp - the request to forward down the stack + +Return Value: + +--*/ +NTSTATUS +ClassForwardIrpSynchronous( + _In_ PCOMMON_DEVICE_EXTENSION CommonExtension, + _In_ PIRP Irp + ) +{ + IoCopyCurrentIrpStackLocationToNext(Irp); + return ClassSendIrpSynchronous(CommonExtension->LowerDeviceObject, Irp); +} // end ClassForwardIrpSynchronous() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassSendIrpSynchronous() + +Routine Description: + + This routine sends the given irp to the given device object, and waits for + it to complete. On debug versions, will print out a debug message and + optionally assert for "lost" irps based upon classpnp's globals + +Arguments: + + TargetDeviceObject - the device object to handle this irp + + Irp - the request to be sent + +Return Value: + +--*/ +NTSTATUS +ClassSendIrpSynchronous( + _In_ PDEVICE_OBJECT TargetDeviceObject, + _In_ PIRP Irp + ) +{ + KEVENT event; + NTSTATUS status; + + NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL); + NT_ASSERT(TargetDeviceObject != NULL); + NT_ASSERT(Irp != NULL); + NT_ASSERT(Irp->StackCount >= TargetDeviceObject->StackSize); + + // + // ISSUE-2000/02/20-henrygab What if APCs are disabled? + // May need to enter critical section before IoCallDriver() + // until the event is hit? + // + + KeInitializeEvent(&event, SynchronizationEvent, FALSE); + IoSetCompletionRoutine(Irp, ClassSignalCompletion, &event, + TRUE, TRUE, TRUE); + + status = IoCallDriver(TargetDeviceObject, Irp); + _Analysis_assume_(status!=STATUS_PENDING); + if (status == STATUS_PENDING) { + + #if DBG + LARGE_INTEGER timeout; + + timeout.QuadPart = (LONGLONG)(-1 * 10 * 1000 * (LONGLONG)1000 * + ClasspnpGlobals.SecondsToWaitForIrps); + + do { + status = KeWaitForSingleObject(&event, + Executive, + KernelMode, + FALSE, + &timeout); + + + if (status == STATUS_TIMEOUT) { + + // + // This DebugPrint should almost always be investigated by the + // party who sent the irp and/or the current owner of the irp. + // Synchronous Irps should not take this long (currently 30 + // seconds) without good reason. This points to a potentially + // serious problem in the underlying device stack. + // + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassSendIrpSynchronous: (%p) irp %p did not " + "complete within %x seconds\n", + TargetDeviceObject, Irp, + ClasspnpGlobals.SecondsToWaitForIrps + )); + + if (ClasspnpGlobals.BreakOnLostIrps != 0) { + NT_ASSERT(!" - Irp failed to complete within 30 seconds - "); + } + } + + + } while (status==STATUS_TIMEOUT); + #else + (VOID)KeWaitForSingleObject(&event, + Executive, + KernelMode, + FALSE, + NULL); + #endif + + status = Irp->IoStatus.Status; + } + + return status; +} // end ClassSendIrpSynchronous() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassGetVpb() + +Routine Description: + + This routine returns the current VPB (Volume Parameter Block) for the + given device object. + The Vpb field is only visible in the ntddk.h (not the wdm.h) definition + of DEVICE_OBJECT; hence this exported function. + +Arguments: + + DeviceObject - the device to get the VPB for + +Return Value: + + the VPB, or NULL if none. + +--*/ +PVPB +ClassGetVpb( + _In_ PDEVICE_OBJECT DeviceObject + ) +{ +#pragma prefast(suppress:28175) + return DeviceObject->Vpb; +} // end ClassGetVpb() + +/*++ + + ISSUE-2000/02/20-henrygab Not documented ClasspAllocateReleaseRequest + +--*/ +NTSTATUS +ClasspAllocateReleaseRequest( + IN PDEVICE_OBJECT Fdo + ) +{ + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension; + + PAGED_CODE(); + + KeInitializeSpinLock(&(fdoExtension->ReleaseQueueSpinLock)); + + fdoExtension->ReleaseQueueNeeded = FALSE; + fdoExtension->ReleaseQueueInProgress = FALSE; + fdoExtension->ReleaseQueueIrpFromPool = FALSE; + + // + // The class driver is responsible for allocating a properly sized irp, + // or ClassReleaseQueue will attempt to do it on the first error. + // + + fdoExtension->ReleaseQueueIrp = NULL; + + // + // Write length to SRB. + // + + fdoExtension->ReleaseQueueSrb.Length = sizeof(SCSI_REQUEST_BLOCK); + + return STATUS_SUCCESS; +} // end ClasspAllocateReleaseRequest() + +/*++ + + ISSUE-2000/02/20-henrygab Not documented ClasspFreeReleaseRequest + +--*/ +VOID +ClasspFreeReleaseRequest( + IN PDEVICE_OBJECT Fdo + ) +{ + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension; + + PAGED_CODE(); + + //KIRQL oldIrql; + + NT_ASSERT(fdoExtension->CommonExtension.IsRemoved != NO_REMOVE); + + // + // free anything the driver allocated + // + + if (fdoExtension->ReleaseQueueIrp) { + if (fdoExtension->ReleaseQueueIrpFromPool) { + FREE_POOL(fdoExtension->ReleaseQueueIrp); + } else { + IoFreeIrp(fdoExtension->ReleaseQueueIrp); + } + fdoExtension->ReleaseQueueIrp = NULL; + } + + // + // free anything that we allocated + // + + if ((fdoExtension->PrivateFdoData) && + (fdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated)) { + + FREE_POOL(fdoExtension->PrivateFdoData->ReleaseQueueIrp); + fdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated = FALSE; + } + + return; +} // end ClasspFreeReleaseRequest() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassReleaseQueue() + +Routine Description: + + This routine issues an internal device control command + to the port driver to release a frozen queue. The call + is issued asynchronously as ClassReleaseQueue will be invoked + from the IO completion DPC (and will have no context to + wait for a synchronous call to complete). + + This routine must be called with the remove lock held. + +Arguments: + + Fdo - The functional device object for the device with the frozen queue. + +Return Value: + + None. + +--*/ +VOID +ClassReleaseQueue( + _In_ PDEVICE_OBJECT Fdo + ) +{ + ClasspReleaseQueue(Fdo, NULL); + return; +} // end ClassReleaseQueue() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClasspAllocateReleaseQueueIrp() + +Routine Description: + + This routine allocates the release queue irp held in classpnp's private + extension. This was added to allow no-memory conditions to be more + survivable. + +Return Value: + + NT_SUCCESS value. + +Notes: + + Does not grab the spinlock. Should only be called from StartDevice() + routine. May be called elsewhere for poorly-behaved drivers that cause + the queue to lockup before the device is started. This should *never* + occur, since it's illegal to send a request to a non-started PDO. This + condition is checked for in ClasspReleaseQueue(). + +--*/ +NTSTATUS +ClasspAllocateReleaseQueueIrp( + PFUNCTIONAL_DEVICE_EXTENSION FdoExtension + ) +{ + UCHAR lowerStackSize; + + // + // do an initial check w/o the spinlock + // + + if (FdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated) { + return STATUS_SUCCESS; + } + + + lowerStackSize = FdoExtension->CommonExtension.LowerDeviceObject->StackSize; + + // + // don't allocate one if one is in progress! this means whoever called + // this routine didn't check if one was in progress. + // + + NT_ASSERT(!(FdoExtension->ReleaseQueueInProgress)); + + FdoExtension->PrivateFdoData->ReleaseQueueIrp = + ExAllocatePoolWithTag(NonPagedPoolNx, + IoSizeOfIrp(lowerStackSize), + CLASS_TAG_RELEASE_QUEUE + ); + + if (FdoExtension->PrivateFdoData->ReleaseQueueIrp == NULL) { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP, "ClassPnpStartDevice: Cannot allocate for " + "release queue irp\n")); + return STATUS_INSUFFICIENT_RESOURCES; + } + IoInitializeIrp(FdoExtension->PrivateFdoData->ReleaseQueueIrp, + IoSizeOfIrp(lowerStackSize), + lowerStackSize); + FdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated = TRUE; + + return STATUS_SUCCESS; +} + +/*++//////////////////////////////////////////////////////////////////////////// + +ClasspAllocatePowerProcessIrp() + +Routine Description: + + This routine allocates the power process irp. + This routine should be called after PrivateFdoData is allocated. + +Return Value: + + NTSTATUS value. + +Notes: + + Should only be called from StartDevice() + +--*/ +NTSTATUS +ClasspAllocatePowerProcessIrp( + PFUNCTIONAL_DEVICE_EXTENSION FdoExtension + ) +{ + UCHAR stackSize; + + NT_ASSERT(FdoExtension->PrivateFdoData != NULL); + + stackSize = FdoExtension->CommonExtension.LowerDeviceObject->StackSize + 1; + + FdoExtension->PrivateFdoData->PowerProcessIrp = ExAllocatePoolWithTag(NonPagedPoolNx, + IoSizeOfIrp(stackSize), + CLASS_TAG_POWER + ); + + if (FdoExtension->PrivateFdoData->PowerProcessIrp == NULL) { + + return STATUS_INSUFFICIENT_RESOURCES; + } else { + + IoInitializeIrp(FdoExtension->PrivateFdoData->PowerProcessIrp, + IoSizeOfIrp(stackSize), + stackSize); + + return STATUS_SUCCESS; + } +} + + +/*++//////////////////////////////////////////////////////////////////////////// + +ClasspReleaseQueue() + +Routine Description: + + This routine issues an internal device control command + to the port driver to release a frozen queue. The call + is issued asynchronously as ClassReleaseQueue will be invoked + from the IO completion DPC (and will have no context to + wait for a synchronous call to complete). + + This routine must be called with the remove lock held. + +Arguments: + + Fdo - The functional device object for the device with the frozen queue. + + ReleaseQueueIrp - If this irp is supplied then the test to determine whether + a release queue request is in progress will be ignored. + The irp provided must be the IRP originally allocated + for release queue requests (so this parameter can only + really be provided by the release queue completion + routine.) + +Return Value: + + None. + +--*/ +VOID +ClasspReleaseQueue( + IN PDEVICE_OBJECT Fdo, + IN PIRP ReleaseQueueIrp OPTIONAL + ) +{ + PIO_STACK_LOCATION irpStack; + PIRP irp; + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension; + PDEVICE_OBJECT lowerDevice; + PSTORAGE_REQUEST_BLOCK_HEADER srb; + KIRQL currentIrql; + ULONG function; + + lowerDevice = fdoExtension->CommonExtension.LowerDeviceObject; + + // + // we raise irql seperately so we're not swapped out or suspended + // while holding the release queue irp in this routine. this lets + // us release the spin lock before lowering irql. + // + + KeRaiseIrql(DISPATCH_LEVEL, ¤tIrql); + + KeAcquireSpinLockAtDpcLevel(&(fdoExtension->ReleaseQueueSpinLock)); + + // + // make sure that if they passed us an irp, it matches our allocated irp. + // + + NT_ASSERT((ReleaseQueueIrp == NULL) || + (ReleaseQueueIrp == fdoExtension->PrivateFdoData->ReleaseQueueIrp)); + + // + // ASSERT that we've already allocated this. (should not occur) + // try to allocate it anyways, then finally bugcheck if + // there's still no memory... + // + + NT_ASSERT(fdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated); + if (!fdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated) { + ClasspAllocateReleaseQueueIrp(fdoExtension); + } + if (!fdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated) { + KeBugCheckEx(SCSI_DISK_DRIVER_INTERNAL, 0x12, (ULONG_PTR)Fdo, 0x0, 0x0); + } + + if ((fdoExtension->ReleaseQueueInProgress) && (ReleaseQueueIrp == NULL)) { + + // + // Someone is already using the irp - just set the flag to indicate that + // we need to release the queue again. + // + + fdoExtension->ReleaseQueueNeeded = TRUE; + KeReleaseSpinLockFromDpcLevel(&(fdoExtension->ReleaseQueueSpinLock)); + KeLowerIrql(currentIrql); + return; + + } + + // + // Mark that there is a release queue in progress and drop the spinlock. + // + + fdoExtension->ReleaseQueueInProgress = TRUE; + if (ReleaseQueueIrp) { + irp = ReleaseQueueIrp; + } else { + irp = fdoExtension->PrivateFdoData->ReleaseQueueIrp; + } + + if (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) { + srb = (PSTORAGE_REQUEST_BLOCK_HEADER)&(fdoExtension->PrivateFdoData->ReleaseQueueSrb.SrbEx); + } else { + srb = (PSTORAGE_REQUEST_BLOCK_HEADER)&(fdoExtension->ReleaseQueueSrb); + } + + KeReleaseSpinLockFromDpcLevel(&(fdoExtension->ReleaseQueueSpinLock)); + + NT_ASSERT(irp != NULL); + + irpStack = IoGetNextIrpStackLocation(irp); + + irpStack->MajorFunction = IRP_MJ_SCSI; + + SrbSetOriginalRequest(srb, irp); + + // + // Store the SRB address in next stack for port driver. + // + + irpStack->Parameters.Scsi.Srb = (PSCSI_REQUEST_BLOCK)srb; + + // + // If this device is removable then flush the queue. This will also + // release it. + // + + if (TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA)){ + function = SRB_FUNCTION_FLUSH_QUEUE; + } + else { + function = SRB_FUNCTION_RELEASE_QUEUE; + } + + if (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) { + ((PSTORAGE_REQUEST_BLOCK)srb)->SrbFunction = function; + } else { + srb->Function = (UCHAR)function; + } + + ClassAcquireRemoveLock(Fdo, irp); + + IoSetCompletionRoutine(irp, + ClassReleaseQueueCompletion, + Fdo, + TRUE, + TRUE, + TRUE); + + IoCallDriver(lowerDevice, irp); + + KeLowerIrql(currentIrql); + + return; + +} // end ClassReleaseQueue() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassReleaseQueueCompletion() + +Routine Description: + + This routine is called when an asynchronous I/O request + which was issused by the class driver completes. Examples of such requests + are release queue or START UNIT. This routine releases the queue if + necessary. It then frees the context and the IRP. + +Arguments: + + DeviceObject - The device object for the logical unit; however since this + is the top stack location the value is NULL. + + Irp - Supplies a pointer to the Irp to be processed. + + Context - Supplies the context to be used to process this request. + +Return Value: + + None. + +--*/ +NTSTATUS +ClassReleaseQueueCompletion( + PDEVICE_OBJECT DeviceObject, + PIRP Irp, + PVOID Context + ) +{ + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension; + KIRQL oldIrql; + + BOOLEAN releaseQueueNeeded; + + if (Context == NULL) { + NT_ASSERT(Context != NULL); + return STATUS_INVALID_PARAMETER; + } + + DeviceObject = Context; + + fdoExtension = DeviceObject->DeviceExtension; + + ClassReleaseRemoveLock(DeviceObject, Irp); + + // + // Grab the spinlock and clear the release queue in progress flag so others + // can run. Save (and clear) the state of the release queue needed flag + // so that we can issue a new release queue outside the spinlock. + // + + KeAcquireSpinLock(&(fdoExtension->ReleaseQueueSpinLock), &oldIrql); + + releaseQueueNeeded = fdoExtension->ReleaseQueueNeeded; + + fdoExtension->ReleaseQueueNeeded = FALSE; + fdoExtension->ReleaseQueueInProgress = FALSE; + + KeReleaseSpinLock(&(fdoExtension->ReleaseQueueSpinLock), oldIrql); + + // + // If we need a release queue then issue one now. Another processor may + // have already started one in which case we'll try to issue this one after + // it is done - but we should never recurse more than one deep. + // + + if(releaseQueueNeeded) { + ClasspReleaseQueue(DeviceObject, Irp); + } + + // + // Indicate the I/O system should stop processing the Irp completion. + // + + return STATUS_MORE_PROCESSING_REQUIRED; + +} // ClassAsynchronousCompletion() + + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassAcquireChildLock() + +Routine Description: + + This routine acquires the lock protecting children PDOs. It may be + acquired recursively by the same thread, but must be release by the + thread once for each acquisition. + +Arguments: + + FdoExtension - the device whose child list is protected. + +Return Value: + + None + +--*/ +_IRQL_requires_max_(PASSIVE_LEVEL) +VOID +ClassAcquireChildLock( + _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension + ) +{ + PAGED_CODE(); + + if(FdoExtension->ChildLockOwner != KeGetCurrentThread()) { + (VOID)KeWaitForSingleObject(&FdoExtension->ChildLock, + Executive, KernelMode, + FALSE, NULL); + + NT_ASSERT(FdoExtension->ChildLockOwner == NULL); + NT_ASSERT(FdoExtension->ChildLockAcquisitionCount == 0); + + FdoExtension->ChildLockOwner = KeGetCurrentThread(); + } else { + NT_ASSERT(FdoExtension->ChildLockAcquisitionCount != 0); + } + + FdoExtension->ChildLockAcquisitionCount++; + return; +} + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassReleaseChildLock() ISSUE-2000/02/18-henrygab - not documented + +Routine Description: + + This routine releases the lock protecting children PDOs. It must be + called once for each time ClassAcquireChildLock was called. + +Arguments: + + FdoExtension - the device whose child list is protected + +Return Value: + + None. + +--*/ +VOID +ClassReleaseChildLock( + _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension + ) +{ + NT_ASSERT(FdoExtension->ChildLockOwner == KeGetCurrentThread()); + NT_ASSERT(FdoExtension->ChildLockAcquisitionCount != 0); + + FdoExtension->ChildLockAcquisitionCount -= 1; + + if(FdoExtension->ChildLockAcquisitionCount == 0) { + FdoExtension->ChildLockOwner = NULL; + KeSetEvent(&FdoExtension->ChildLock, IO_NO_INCREMENT, FALSE); + } + + return; +} // end ClassReleaseChildLock( + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassAddChild() + +Routine Description: + + This routine will insert a new child into the head of the child list. + +Arguments: + + Parent - the child's parent (contains the head of the list) + Child - the child to be inserted. + AcquireLock - whether the child lock should be acquired (TRUE) or whether + it's already been acquired by or on behalf of the caller + (FALSE). + +Return Value: + + None. + +--*/ +VOID +ClassAddChild( + _In_ PFUNCTIONAL_DEVICE_EXTENSION Parent, + _In_ PPHYSICAL_DEVICE_EXTENSION Child, + _In_ BOOLEAN AcquireLock + ) +{ + if(AcquireLock) { + ClassAcquireChildLock(Parent); + } + + #if DBG + // + // Make sure this child's not already in the list. + // + { + PPHYSICAL_DEVICE_EXTENSION testChild; + + for (testChild = Parent->CommonExtension.ChildList; + testChild != NULL; + testChild = testChild->CommonExtension.ChildList) { + + NT_ASSERT(testChild != Child); + } + } + #endif + + Child->CommonExtension.ChildList = Parent->CommonExtension.ChildList; + Parent->CommonExtension.ChildList = Child; + + if(AcquireLock) { + ClassReleaseChildLock(Parent); + } + return; +} // end ClassAddChild() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassRemoveChild() + +Routine Description: + + This routine will remove a child from the child list. + +Arguments: + + Parent - the parent to be removed from. + + Child - the child to be removed or NULL if the first child should be + removed. + + AcquireLock - whether the child lock should be acquired (TRUE) or whether + it's already been acquired by or on behalf of the caller + (FALSE). + +Return Value: + + A pointer to the child which was removed or NULL if no such child could + be found in the list (or if Child was NULL but the list is empty). + +--*/ +PPHYSICAL_DEVICE_EXTENSION +ClassRemoveChild( + IN PFUNCTIONAL_DEVICE_EXTENSION Parent, + IN PPHYSICAL_DEVICE_EXTENSION Child, + IN BOOLEAN AcquireLock + ) +{ + if(AcquireLock) { + ClassAcquireChildLock(Parent); + } + + TRY { + PCOMMON_DEVICE_EXTENSION previousChild = &Parent->CommonExtension; + + // + // If the list is empty then bail out now. + // + + if(Parent->CommonExtension.ChildList == NULL) { + Child = NULL; + LEAVE; + } + + // + // If the caller specified a child then find the child object before + // it. If none was specified then the FDO is the child object before + // the one we want to remove. + // + + if(Child != NULL) { + + // + // Scan through the child list to find the entry which points to + // this one. + // + + do { + NT_ASSERT(previousChild != &Child->CommonExtension); + + if(previousChild->ChildList == Child) { + break; + } + + previousChild = &previousChild->ChildList->CommonExtension; + } while(previousChild != NULL); + + if(previousChild == NULL) { + Child = NULL; + LEAVE; + } + } + + // + // Save the next child away then unlink it from the list. + // + + Child = previousChild->ChildList; + previousChild->ChildList = Child->CommonExtension.ChildList; + Child->CommonExtension.ChildList = NULL; + + } FINALLY { + if(AcquireLock) { + ClassReleaseChildLock(Parent); + } + } + return Child; +} // end ClassRemoveChild() + + +/*++ + + ISSUE-2000/02/20-henrygab Not documented ClasspRetryRequestDpc + +--*/ +VOID +ClasspRetryRequestDpc( + IN PKDPC Dpc, + IN PVOID DeferredContext, + IN PVOID Arg1, + IN PVOID Arg2 + ) +{ + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension; + PCOMMON_DEVICE_EXTENSION commonExtension; + PCLASS_PRIVATE_FDO_DATA fdoData; + PCLASS_RETRY_INFO retryList; + KIRQL irql; + PDEVICE_OBJECT DeviceObject = (PDEVICE_OBJECT)DeferredContext; + + UNREFERENCED_PARAMETER(Dpc); + UNREFERENCED_PARAMETER(Arg1); + UNREFERENCED_PARAMETER(Arg2); + + if (DeferredContext == NULL) { + NT_ASSERT(DeferredContext != NULL); + return; + } + + commonExtension = DeviceObject->DeviceExtension; + NT_ASSERT(commonExtension->IsFdo); + fdoExtension = DeviceObject->DeviceExtension; + fdoData = fdoExtension->PrivateFdoData; + + + KeAcquireSpinLock(&fdoData->Retry.Lock, &irql); + { + LARGE_INTEGER now; + KeQueryTickCount(&now); + + // + // if CurrentTick is less than now + // fire another DPC + // else + // retry entire list + // endif + // + + if (now.QuadPart < fdoData->Retry.Tick.QuadPart) { + + ClasspRetryDpcTimer(fdoData); + retryList = NULL; + + } else { + + retryList = fdoData->Retry.ListHead; + fdoData->Retry.ListHead = NULL; + fdoData->Retry.Delta.QuadPart = (LONGLONG)0; + fdoData->Retry.Tick.QuadPart = (LONGLONG)0; + + } + } + KeReleaseSpinLock(&fdoData->Retry.Lock, irql); + + while (retryList != NULL) { + + PIRP irp; + + + irp = CONTAINING_RECORD(retryList, IRP, Tail.Overlay.DriverContext[0]); + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassRetry: -- %p\n", irp)); + retryList = retryList->Next; + #if DBG + irp->Tail.Overlay.DriverContext[0] = ULongToPtr(0xdddddddd); // invalidate data + irp->Tail.Overlay.DriverContext[1] = ULongToPtr(0xdddddddd); // invalidate data + irp->Tail.Overlay.DriverContext[2] = ULongToPtr(0xdddddddd); // invalidate data + irp->Tail.Overlay.DriverContext[3] = ULongToPtr(0xdddddddd); // invalidate data + #endif + + + if (NO_REMOVE == InterlockedCompareExchange((volatile LONG *)&commonExtension->IsRemoved, REMOVE_PENDING, REMOVE_PENDING)) { + + IoCallDriver(commonExtension->LowerDeviceObject, irp); + + } else { + + PIO_STACK_LOCATION irpStack; + + // + // Ensure that we don't skip a completion routine (equivalent of sending down a request + // to a device after it has received a remove and it completes the request. We need to + // mimic that behavior here). + // + IoSetNextIrpStackLocation(irp); + + irpStack = IoGetCurrentIrpStackLocation(irp); + + if (irpStack->MajorFunction == IRP_MJ_SCSI) { + + PSCSI_REQUEST_BLOCK srb = irpStack->Parameters.Scsi.Srb; + + if (srb) { + srb->SrbStatus = SRB_STATUS_NO_DEVICE; + } + } + + // + // Ensure that no retries will take place. This takes care of requests that are either + // not IRP_MJ_SCSI or for ones where the SRB was passed in to the completion routine + // as a context as opposed to an argument on the IRP stack location. + // + irpStack->Parameters.Others.Argument4 = (PVOID)0; + + irp->IoStatus.Status = STATUS_NO_SUCH_DEVICE; + irp->IoStatus.Information = 0; + IoCompleteRequest(irp, IO_NO_INCREMENT); + } + + } + return; + +} // end ClasspRetryRequestDpc() + +/*++ + + ISSUE-2000/02/20-henrygab Not documented ClassRetryRequest + +--*/ +VOID +ClassRetryRequest( + IN PDEVICE_OBJECT SelfDeviceObject, + IN PIRP Irp, + _In_ _In_range_(0,MAXIMUM_RETRY_FOR_SINGLE_IO_IN_100NS_UNITS) // 100 seconds; already an assert on this... + IN LONGLONG TimeDelta100ns // in 100ns units + ) +{ + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension; + PCLASS_PRIVATE_FDO_DATA fdoData; + PCLASS_RETRY_INFO retryInfo; + LARGE_INTEGER delta; + KIRQL irql; + + // + // this checks we aren't destroying irps + // + NT_ASSERT(sizeof(CLASS_RETRY_INFO) <= (4*sizeof(PVOID))); + + fdoExtension = SelfDeviceObject->DeviceExtension; + + if (!fdoExtension->CommonExtension.IsFdo) { + + // + // this debug print/assertion should ALWAYS be investigated. + // ClassRetryRequest can currently only be used by FDO's + // + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassRetryRequestEx: LOST IRP %p\n", Irp)); + NT_ASSERT(!"ClassRetryRequestEx Called From PDO? LOST IRP"); + return; + + } + + fdoData = fdoExtension->PrivateFdoData; + + if (TimeDelta100ns < 0) { + NT_ASSERT(!"ClassRetryRequest - must use positive delay"); + TimeDelta100ns *= -1; + } + + /* + * We are going to queue the irp and send it down in a timer DPC. + * This means that we may be causing the irp to complete on a different thread than the issuing thread. + * So mark the irp pending. + */ + IoMarkIrpPending(Irp); + + // + // prepare what we can out of the loop + // + + retryInfo = (PCLASS_RETRY_INFO)(&Irp->Tail.Overlay.DriverContext[0]); + RtlZeroMemory(retryInfo, sizeof(CLASS_RETRY_INFO)); + + delta.QuadPart = (TimeDelta100ns / fdoData->Retry.Granularity); + if (TimeDelta100ns % fdoData->Retry.Granularity) { + delta.QuadPart ++; // round up to next tick + } + if (delta.QuadPart == (LONGLONG)0) { + delta.QuadPart = MINIMUM_RETRY_UNITS; + } + + // + // now determine if we should fire another DPC or not + // + + KeAcquireSpinLock(&fdoData->Retry.Lock, &irql); + + // + // always add request to the list + // + + retryInfo->Next = fdoData->Retry.ListHead; + fdoData->Retry.ListHead = retryInfo; + + if (fdoData->Retry.Delta.QuadPart == (LONGLONG)0) { + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassRetry: +++ %p\n", Irp)); + + // + // must be exactly one item on list + // + + NT_ASSERT(fdoData->Retry.ListHead != NULL); + NT_ASSERT(fdoData->Retry.ListHead->Next == NULL); + + // + // if currentDelta is zero, always fire a DPC + // + + KeQueryTickCount(&fdoData->Retry.Tick); + fdoData->Retry.Tick.QuadPart += delta.QuadPart; + fdoData->Retry.Delta.QuadPart = delta.QuadPart; + ClasspRetryDpcTimer(fdoData); + + } else if (delta.QuadPart > fdoData->Retry.Delta.QuadPart) { + + // + // if delta is greater than the list's current delta, + // increase the DPC handling time by difference + // and update the delta to new larger value + // allow the DPC to re-fire itself if needed + // + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassRetry: ++ %p\n", Irp)); + + // + // must be at least two items on list + // + + NT_ASSERT(fdoData->Retry.ListHead != NULL); + NT_ASSERT(fdoData->Retry.ListHead->Next != NULL); + + fdoData->Retry.Tick.QuadPart -= fdoData->Retry.Delta.QuadPart; + fdoData->Retry.Tick.QuadPart += delta.QuadPart; + + fdoData->Retry.Delta.QuadPart = delta.QuadPart; + + } else { + + // + // just inserting it on the list was enough + // + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassRetry: ++ %p\n", Irp)); + + } + + + KeReleaseSpinLock(&fdoData->Retry.Lock, irql); + + +} // end ClassRetryRequest() + +/*++ + + ISSUE-2000/02/20-henrygab Not documented ClasspRetryDpcTimer + +--*/ +VOID +ClasspRetryDpcTimer( + IN PCLASS_PRIVATE_FDO_DATA FdoData + ) +{ + LARGE_INTEGER fire; + + NT_ASSERT(FdoData->Retry.Tick.QuadPart != (LONGLONG)0); + NT_ASSERT(FdoData->Retry.ListHead != NULL); // never fire an empty list + + // + // fire == (CurrentTick - now) * (100ns per tick) + // + // NOTE: Overflow is nearly impossible and is ignored here + // + + KeQueryTickCount(&fire); + fire.QuadPart = FdoData->Retry.Tick.QuadPart - fire.QuadPart; + fire.QuadPart *= FdoData->Retry.Granularity; + + // + // fire is now multiples of 100ns until should fire the timer. + // if timer should already have expired, or would fire too quickly, + // fire it in some arbitrary number of ticks to prevent infinitely + // recursing. + // + + if (fire.QuadPart < MINIMUM_RETRY_UNITS) { + fire.QuadPart = MINIMUM_RETRY_UNITS; + } + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, + "ClassRetry: ======= %I64x ticks\n", + fire.QuadPart)); + + // + // must use negative to specify relative time to fire + // + + fire.QuadPart = fire.QuadPart * ((LONGLONG)-1); + + // + // set the timer, since this is the first addition + // + + KeSetTimerEx(&FdoData->Retry.Timer, fire, 0, &FdoData->Retry.Dpc); + + return; +} // end ClasspRetryDpcTimer() + +NTSTATUS +ClasspInitializeHotplugInfo( + IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension + ) +{ + PCLASS_PRIVATE_FDO_DATA fdoData = FdoExtension->PrivateFdoData; + DEVICE_REMOVAL_POLICY deviceRemovalPolicy = 0; + NTSTATUS status; + ULONG resultLength = 0; + ULONG writeCacheOverride; + + PAGED_CODE(); + + // + // start with some default settings + // + RtlZeroMemory(&(fdoData->HotplugInfo), sizeof(STORAGE_HOTPLUG_INFO)); + + // + // set the size (aka version) + // + + fdoData->HotplugInfo.Size = sizeof(STORAGE_HOTPLUG_INFO); + + // + // set if the device has removable media + // + + if (FdoExtension->DeviceDescriptor->RemovableMedia) { + fdoData->HotplugInfo.MediaRemovable = TRUE; + } else { + fdoData->HotplugInfo.MediaRemovable = FALSE; + } + + // + // this refers to devices which, for reasons not yet understood, + // do not fail PREVENT_MEDIA_REMOVAL requests even though they + // have no way to lock the media into the drive. this allows + // the filesystems to turn off delayed-write caching for these + // devices as well. + // + + if (TEST_FLAG(FdoExtension->PrivateFdoData->HackFlags, + FDO_HACK_CANNOT_LOCK_MEDIA)) { + fdoData->HotplugInfo.MediaHotplug = TRUE; + } else { + fdoData->HotplugInfo.MediaHotplug = FALSE; + } + + // + // Query the default removal policy from the kernel + // + + status = IoGetDeviceProperty(FdoExtension->LowerPdo, + DevicePropertyRemovalPolicy, + sizeof(DEVICE_REMOVAL_POLICY), + (PVOID)&deviceRemovalPolicy, + &resultLength); + if (!NT_SUCCESS(status)) { + return status; + } + + if (resultLength != sizeof(DEVICE_REMOVAL_POLICY)) { + return STATUS_UNSUCCESSFUL; + } + + // + // Look into the registry to see if the user has chosen + // to override the default setting for the removal policy. + // User can override only if the default removal policy is + // orderly or suprise removal. + + if ((deviceRemovalPolicy == RemovalPolicyExpectOrderlyRemoval) || + (deviceRemovalPolicy == RemovalPolicyExpectSurpriseRemoval)) { + + DEVICE_REMOVAL_POLICY userRemovalPolicy = 0; + + ClassGetDeviceParameter(FdoExtension, + CLASSP_REG_SUBKEY_NAME, + CLASSP_REG_REMOVAL_POLICY_VALUE_NAME, + (PULONG)&userRemovalPolicy); + + // + // Validate the override value and use it only if it is an + // allowed value. + // + if ((userRemovalPolicy == RemovalPolicyExpectOrderlyRemoval) || + (userRemovalPolicy == RemovalPolicyExpectSurpriseRemoval)) { + deviceRemovalPolicy = userRemovalPolicy; + } + } + + // + // use this info to set the DeviceHotplug setting + // don't rely on DeviceCapabilities, since it can't properly + // determine device relations, etc. let the kernel figure this + // stuff out instead. + // + + if (deviceRemovalPolicy == RemovalPolicyExpectSurpriseRemoval) { + fdoData->HotplugInfo.DeviceHotplug = TRUE; + } else { + fdoData->HotplugInfo.DeviceHotplug = FALSE; + } + + // + // this refers to the *filesystem* caching, but has to be included + // here since it's a per-device setting. this may change to be + // stored by the system in the future. + // + + writeCacheOverride = FALSE; + ClassGetDeviceParameter(FdoExtension, + CLASSP_REG_SUBKEY_NAME, + CLASSP_REG_WRITE_CACHE_VALUE_NAME, + &writeCacheOverride); + + if (writeCacheOverride) { + fdoData->HotplugInfo.WriteCacheEnableOverride = TRUE; + } else { + fdoData->HotplugInfo.WriteCacheEnableOverride = FALSE; + } + + return STATUS_SUCCESS; +} + +VOID +ClasspScanForClassHacks( + IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension, + IN ULONG_PTR Data + ) +{ + PAGED_CODE(); + + // + // remove invalid flags and save + // + + CLEAR_FLAG(Data, FDO_HACK_INVALID_FLAGS); + SET_FLAG(FdoExtension->PrivateFdoData->HackFlags, Data); + return; +} + +VOID +ClasspScanForSpecialInRegistry( + IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension + ) +{ + HANDLE deviceParameterHandle; // device instance key + HANDLE classParameterHandle; // classpnp subkey + OBJECT_ATTRIBUTES objectAttributes = {0}; + UNICODE_STRING subkeyName; + NTSTATUS status; + + // + // seeded in the ENUM tree by ClassInstaller + // + ULONG deviceHacks; + RTL_QUERY_REGISTRY_TABLE queryTable[2] = {0}; // null terminated array + + PAGED_CODE(); + + deviceParameterHandle = NULL; + classParameterHandle = NULL; + deviceHacks = 0; + + status = IoOpenDeviceRegistryKey(FdoExtension->LowerPdo, + PLUGPLAY_REGKEY_DEVICE, + KEY_WRITE, + &deviceParameterHandle + ); + + if (!NT_SUCCESS(status)) { + goto cleanupScanForSpecial; + } + + RtlInitUnicodeString(&subkeyName, CLASSP_REG_SUBKEY_NAME); + InitializeObjectAttributes(&objectAttributes, + &subkeyName, + OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, + deviceParameterHandle, + NULL + ); + + status = ZwOpenKey( &classParameterHandle, + KEY_READ, + &objectAttributes + ); + + if (!NT_SUCCESS(status)) { + goto cleanupScanForSpecial; + } + + // + // Setup the structure to read + // + + queryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT; + queryTable[0].Name = CLASSP_REG_HACK_VALUE_NAME; + queryTable[0].EntryContext = &deviceHacks; + queryTable[0].DefaultType = REG_DWORD; + queryTable[0].DefaultData = &deviceHacks; + queryTable[0].DefaultLength = 0; + + // + // read values + // + + status = RtlQueryRegistryValues(RTL_REGISTRY_HANDLE, + (PWSTR)classParameterHandle, + &queryTable[0], + NULL, + NULL + ); + if (!NT_SUCCESS(status)) { + goto cleanupScanForSpecial; + } + + // + // remove unknown values and save... + // + + CLEAR_FLAG(deviceHacks, FDO_HACK_INVALID_FLAGS); + SET_FLAG(FdoExtension->PrivateFdoData->HackFlags, deviceHacks); + + +cleanupScanForSpecial: + + if (deviceParameterHandle) { + ZwClose(deviceParameterHandle); + } + + if (classParameterHandle) { + ZwClose(classParameterHandle); + } + + // + // we should modify the system hive to include another key for us to grab + // settings from. in this case: Classpnp\HackFlags + // + // the use of a DWORD value for the HackFlags allows 32 hacks w/o + // significant use of the registry, and also reduces OEM exposure. + // + // definition of bit flags: + // 0x00000001 -- Device succeeds PREVENT_MEDIUM_REMOVAL, but + // cannot actually prevent removal. + // 0x00000002 -- Device hard-hangs or times out for GESN requests. + // 0xfffffffc -- Currently reserved, may be used later. + // + + return; +} + +/*++//////////////////////////////////////////////////////////////////////////// + +ClasspUpdateDiskProperties() + +Routine Description: + + This routine will send IOCTL_DISK_UPDATE_PROPERTIES to top of stack - Partition Manager + to invalidate the cached geometry. + +Arguments: + + Fdo - The device object whose capacity needs to be verified. + +Return Value: + + NTSTATUS + +--*/ + +VOID +ClasspUpdateDiskProperties( + IN PDEVICE_OBJECT Fdo, + IN PVOID Context + ) +{ + PDEVICE_OBJECT topOfStack; + IO_STATUS_BLOCK ioStatus; + NTSTATUS status = STATUS_SUCCESS; + KEVENT event; + PIRP irp = NULL; + PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Fdo->DeviceExtension; + PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData; + PIO_WORKITEM WorkItem = (PIO_WORKITEM)Context; + + KeInitializeEvent(&event, SynchronizationEvent, FALSE); + + topOfStack = IoGetAttachedDeviceReference(Fdo); + + // + // Send down irp to update properties + // + + irp = IoBuildDeviceIoControlRequest( + IOCTL_DISK_UPDATE_PROPERTIES, + topOfStack, + NULL, + 0, + NULL, + 0, + FALSE, + &event, + &ioStatus); + + if (irp != NULL) { + + + status = IoCallDriver(topOfStack, irp); + if (status == STATUS_PENDING) { + (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); + status = ioStatus.Status; + } + + } else { + status = STATUS_INSUFFICIENT_RESOURCES; + } + + InterlockedExchange((volatile LONG *)&fdoData->UpdateDiskPropertiesWorkItemActive, 0); + + if (!NT_SUCCESS(status)) { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, "ClasspUpdateDiskProperties: Disk property update for fdo %p failed with status 0x%X.\n", Fdo, status)); + } else { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "ClasspUpdateDiskProperties: Drive capacity has changed for %p.\n", Fdo)); + } + ObDereferenceObject(topOfStack); + + if (WorkItem != NULL) { + IoFreeWorkItem(WorkItem); + } + + return; +} + +BOOLEAN +InterpretSenseInfoWithoutHistory( + _In_ PDEVICE_OBJECT Fdo, + _In_opt_ PIRP OriginalRequest, + _In_ PSCSI_REQUEST_BLOCK Srb, + UCHAR MajorFunctionCode, + ULONG IoDeviceCode, + ULONG PreviousRetryCount, + _Out_ NTSTATUS * Status, + _Out_opt_ _Deref_out_range_(0,MAXIMUM_RETRY_FOR_SINGLE_IO_IN_100NS_UNITS) + LONGLONG * RetryIn100nsUnits + ) +{ + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension; + PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData; + LONGLONG tmpRetry = 0; + BOOLEAN retry = FALSE; + + if (fdoData->InterpretSenseInfo != NULL) + { + SCSI_REQUEST_BLOCK tempSrb = {0}; + PSCSI_REQUEST_BLOCK srbPtr = Srb; + + // SAL annotations and ClassInitializeEx() both validate this + NT_ASSERT(fdoData->InterpretSenseInfo->Interpret != NULL); + + // + // If class driver does not support extended SRB and this is + // an extended SRB, convert to legacy SRB and pass to class + // driver. + // + if ((Srb->Function == SRB_FUNCTION_STORAGE_REQUEST_BLOCK) && + ((fdoExtension->CommonExtension.DriverExtension->SrbSupport & + CLASS_SRB_STORAGE_REQUEST_BLOCK) == 0)) { + ClasspConvertToScsiRequestBlock(&tempSrb, (PSTORAGE_REQUEST_BLOCK)Srb); + srbPtr = &tempSrb; + } + + retry = fdoData->InterpretSenseInfo->Interpret(Fdo, + OriginalRequest, + srbPtr, + MajorFunctionCode, + IoDeviceCode, + PreviousRetryCount, + NULL, + Status, + &tmpRetry); + } + else + { + ULONG seconds = 0; + + retry = ClassInterpretSenseInfo(Fdo, + Srb, + MajorFunctionCode, + IoDeviceCode, + PreviousRetryCount, + Status, + &seconds); + tmpRetry = ((LONGLONG)seconds) * 1000 * 1000 * 10; + } + + + if (RetryIn100nsUnits != NULL) + { + *RetryIn100nsUnits = tmpRetry; + } + return retry; +} + +VOID +ClasspGetInquiryVpdSupportInfo( + _Inout_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension + ) +{ + NTSTATUS status; + PCOMMON_DEVICE_EXTENSION commonExtension = (PCOMMON_DEVICE_EXTENSION)FdoExtension; + SCSI_REQUEST_BLOCK srb = {0}; + PCDB cdb; + PVPD_SUPPORTED_PAGES_PAGE supportedPages = NULL; + UCHAR bufferLength = VPD_MAX_BUFFER_SIZE; + ULONG allocationBufferLength = bufferLength; + UCHAR srbExBuffer[CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE] = {0}; + PSTORAGE_REQUEST_BLOCK_HEADER srbHeader; + +#if defined(_ARM_) || defined(_ARM64_) + // + // ARM has specific alignment requirements, although this will not have a functional impact on x86 or amd64 + // based platforms. We are taking the conservative approach here. + // + allocationBufferLength = ALIGN_UP_BY(allocationBufferLength,KeGetRecommendedSharedDataAlignment()); + supportedPages = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned, + allocationBufferLength, + '3CcS' + ); + +#else + supportedPages = ExAllocatePoolWithTag(NonPagedPoolNx, + bufferLength, + '3CcS' + ); +#endif + + if (supportedPages == NULL) { + // memory allocation failure. + return; + } + + RtlZeroMemory(supportedPages, allocationBufferLength); + + // prepare the Srb + if (FdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) { + + #pragma prefast(suppress:26015, "InitializeStorageRequestBlock ensures buffer access is bounded") + status = InitializeStorageRequestBlock((PSTORAGE_REQUEST_BLOCK)srbExBuffer, + STORAGE_ADDRESS_TYPE_BTL8, + sizeof(srbExBuffer), + 1, + SrbExDataTypeScsiCdb16); + + if (!NT_SUCCESS(status)) { + // + // Should not happen. Revert to legacy SRB. + NT_ASSERT(FALSE); + srb.Length = SCSI_REQUEST_BLOCK_SIZE; + srb.Function = SRB_FUNCTION_EXECUTE_SCSI; + srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)&srb; + } else { + ((PSTORAGE_REQUEST_BLOCK)srbExBuffer)->SrbFunction = SRB_FUNCTION_EXECUTE_SCSI; + srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)srbExBuffer; + } + + } else { + srb.Length = SCSI_REQUEST_BLOCK_SIZE; + srb.Function = SRB_FUNCTION_EXECUTE_SCSI; + srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)&srb; + } + + SrbSetTimeOutValue(srbHeader, FdoExtension->TimeOutValue); + SrbSetRequestTag(srbHeader, SP_UNTAGGED); + SrbSetRequestAttribute(srbHeader, SRB_SIMPLE_TAG_REQUEST); + SrbAssignSrbFlags(srbHeader, FdoExtension->SrbFlags); + SrbSetCdbLength(srbHeader, 6); + + cdb = SrbGetCdb(srbHeader); + cdb->CDB6INQUIRY3.OperationCode = SCSIOP_INQUIRY; + cdb->CDB6INQUIRY3.EnableVitalProductData = 1; //EVPD bit + cdb->CDB6INQUIRY3.PageCode = VPD_SUPPORTED_PAGES; + cdb->CDB6INQUIRY3.AllocationLength = bufferLength; //AllocationLength field in CDB6INQUIRY3 is only one byte. + + status = ClassSendSrbSynchronous(commonExtension->DeviceObject, + (PSCSI_REQUEST_BLOCK)srbHeader, + supportedPages, + allocationBufferLength, + FALSE); + + // + // Handle the case where we get back STATUS_DATA_OVERRUN b/c the input + // buffer was larger than necessary. + // + if (status == STATUS_DATA_OVERRUN && SrbGetDataTransferLength(srbHeader) < bufferLength) + { + status = STATUS_SUCCESS; + } + + if ( NT_SUCCESS(status) && + (supportedPages->PageLength > 0) && + (supportedPages->SupportedPageList[0] > 0) ) { + // ataport treats all INQUIRY command as standard INQUIRY command, thus fills invalid info + // If VPD INQUIRY is supported, the first page reported (additional length field for standard INQUIRY data) should be '00' + status = STATUS_NOT_SUPPORTED; + } + + if (NT_SUCCESS(status)) { + int i; + + for (i = 0; i < supportedPages->PageLength; i++) { + if ( (i > 0) && (supportedPages->SupportedPageList[i] <= supportedPages->SupportedPageList[i - 1]) ) { + // shall be in ascending order beginning with page code 00h. + FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits = FALSE; + FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockLimits = FALSE; + FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceCharacteristics = FALSE; + FdoExtension->FunctionSupportInfo->ValidInquiryPages.LBProvisioning = FALSE; + + + break; + } + switch (supportedPages->SupportedPageList[i]) { + case VPD_THIRD_PARTY_COPY: + FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits = TRUE; + break; + + case VPD_BLOCK_LIMITS: + FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockLimits = TRUE; + break; + + case VPD_BLOCK_DEVICE_CHARACTERISTICS: + FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceCharacteristics = TRUE; + break; + + case VPD_LOGICAL_BLOCK_PROVISIONING: + FdoExtension->FunctionSupportInfo->ValidInquiryPages.LBProvisioning = TRUE; + break; + + } + } + } + + ExFreePool(supportedPages); + + return; +} + +// +// ClasspGetLBProvisioningInfo +// +// Description: This function does the work to get Logical Block Provisioning +// (LBP) info for a given LUN, including UNMAP support and parameters. It +// will attempt to get both the Logical Block Provisioning (0xB2) VPD page +// and the Block Limits (0xB0) VPD page and cache the relevant values in +// the given FDO extension. +// +// After calling this function, you can use the ClasspIsThinProvisioned() +// function to determine if the device is thinly provisioned and the +// ClasspSupportsUnmap() function to determine if the device supports the +// UNMAP command. +// +// Arguments: +// - FdoExtension: The FDO extension associated with the LUN for which Thin +// Provisioning info is desired. The Thin Provisioning info is stored +// in the FunctionSupportInfo member of this FDO extension. +// +// Returns: +// - STATUS_INVALID_PARAMETER if the given FDO extension has not been +// allocated properly. +// - STATUS_INSUFFICIENT_RESOURCES if this function was unable to allocate +// an SRB used to get the necessary VPD pages. +// - STATUS_SUCCESS in all other cases. If any of the incidental functions +// don't return STATUS_SUCCESS this function will just assume Thin +// Provisioning is not enabled and return STATUS_SUCCESS. +// +NTSTATUS +ClasspGetLBProvisioningInfo( + _Inout_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension + ) +{ + PSCSI_REQUEST_BLOCK srb = NULL; + ULONG srbSize = 0; + + // + // Make sure we actually have data structures to work with. + // + if (FdoExtension == NULL || + FdoExtension->FunctionSupportInfo == NULL) { + return STATUS_INVALID_PARAMETER; + } + + // + // Allocate an SRB for querying the device for LBP-related info if either + // the Logical Block Provisioning (0xB2) or Block Limits (0xB0) VPD page + // exists. + // + if ((FdoExtension->FunctionSupportInfo->ValidInquiryPages.LBProvisioning == TRUE) || + (FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockLimits == TRUE)) { + + if ((FdoExtension->AdapterDescriptor != NULL) && + (FdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK)) { + srbSize = CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE; + } else { + srbSize = sizeof(SCSI_REQUEST_BLOCK); + } + + srb = ExAllocatePoolWithTag(NonPagedPoolNx, srbSize, '0DcS'); + + if (srb == NULL) { + return STATUS_INSUFFICIENT_RESOURCES; + } + } + + // + // Get the Logical Block Provisioning VPD page (0xB2). This function will + // set some default values if the LBP VPD page is not supported. + // + ClasspDeviceGetLBProvisioningVPDPage(FdoExtension->DeviceObject, srb); + + + // + // Get the Block Limits VPD page (0xB0), which may override the default + // UNMAP parameter values set above. + // + ClasspDeviceGetBlockLimitsVPDPage(FdoExtension, + srb, + srbSize, + &FdoExtension->FunctionSupportInfo->BlockLimitsData); + + FREE_POOL(srb); + + return STATUS_SUCCESS; +} + + + +_IRQL_requires_(PASSIVE_LEVEL) +_IRQL_requires_same_ +NTSTATUS +ClassDetermineTokenOperationCommandSupport( + _In_ PDEVICE_OBJECT DeviceObject + ) + +/*++ + +Routine Description: + + This routine determines if Token Operation and Receive ROD Token Information commands + are supported by this device and updates the internal structures to reflect this. + In addition, it also queries the registry to determine the maximum listIdentifier + to use for TokenOperation commands. + + This function must be called at IRQL == PASSIVE_LEVEL. + +Arguments: + + DeviceObject - The device object for which we want to determine command support. + +Return Value: + + Nothing + +--*/ + +{ + NTSTATUS status = STATUS_SUCCESS; + + PAGED_CODE(); + + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_PNP, + "ClassDetermineTokenOperationCommandSupport (%p): Entering function.\n", + DeviceObject)); + + // + // Send down Inquiry for VPD_THIRD_PARTY_COPY_PAGE and cache away the device parameters + // from WINDOWS_BLOCK_DEVICE_TOKEN_LIMITS_DESCRIPTOR. + // + status = ClasspGetBlockDeviceTokenLimitsInfo(DeviceObject); + + if (NT_SUCCESS(status)) { + + ULONG maxListIdentifier = MaxTokenOperationListIdentifier; + + // + // Query the maximum list identifier to use for TokenOperation commands. + // + if (NT_SUCCESS(ClasspGetMaximumTokenListIdentifier(DeviceObject, REG_DISK_CLASS_CONTROL, &maxListIdentifier))) { + if (maxListIdentifier >= MIN_TOKEN_LIST_IDENTIFIERS) { + + NT_ASSERT(maxListIdentifier <= MAX_TOKEN_LIST_IDENTIFIERS); + MaxTokenOperationListIdentifier = maxListIdentifier; + } + } + + } + + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_PNP, + "ClassDetermineTokenOperationCommandSupport (%p): Exiting function with status %x.\n", + DeviceObject, + status)); + + return status; +} + + +_IRQL_requires_same_ +NTSTATUS +ClasspGetBlockDeviceTokenLimitsInfo( + _Inout_ PDEVICE_OBJECT DeviceObject + ) + +/*++ + +Routine Description: + + This routine does the work to get the Block Device Token Limits info for a given LUN. + This is done by sending Inquiry for the Third Party Copy VPD page. + +Arguments: + + DeviceObject - The FDO associated with the LUN for which Block Device Token + limits info is desired. + +Return Value: + + STATUS_DEVICE_FEATURE_NOT_SUPPORTED if either the Inquiry fails or validations fail. + STATUS_SUCCESS otherwise. + +--*/ + +{ + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension; + NTSTATUS status; + PSCSI_REQUEST_BLOCK srb = NULL; + ULONG srbSize = 0; + USHORT pageLength = 0; + USHORT descriptorLength = 0; + PVOID dataBuffer = NULL; + UCHAR bufferLength = VPD_MAX_BUFFER_SIZE; + ULONG allocationBufferLength = bufferLength; + PCDB cdb; + ULONG dataTransferLength = 0; + PVPD_THIRD_PARTY_COPY_PAGE operatingParameters = NULL; + PWINDOWS_BLOCK_DEVICE_TOKEN_LIMITS_DESCRIPTOR blockDeviceTokenLimits = NULL; + + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_PNP, + "ClasspGetBlockDeviceTokenLimitsInfo (%p): Entering function.\n", + DeviceObject)); + + // + // Allocate an SRB for querying the device for LBP-related info. + // + if ((fdoExtension->AdapterDescriptor != NULL) && + (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK)) { + srbSize = CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE; + } else { + srbSize = sizeof(SCSI_REQUEST_BLOCK); + } + + srb = ExAllocatePoolWithTag(NonPagedPoolNx, srbSize, CLASSPNP_POOL_TAG_SRB); + + if (!srb) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_PNP, + "ClasspGetBlockDeviceTokenLimitsInfo (%p): Couldn't allocate SRB.\n", + DeviceObject)); + + status = STATUS_INSUFFICIENT_RESOURCES; + goto __ClasspGetBlockDeviceTokenLimitsInfo_Exit; + } + +#if defined(_ARM_) || defined(_ARM64_) + // + // ARM has specific alignment requirements, although this will not have a functional impact on x86 or amd64 + // based platforms. We are taking the conservative approach here. + // + allocationBufferLength = ALIGN_UP_BY(allocationBufferLength, KeGetRecommendedSharedDataAlignment()); + dataBuffer = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned, allocationBufferLength, CLASSPNP_POOL_TAG_VPD); +#else + dataBuffer = ExAllocatePoolWithTag(NonPagedPoolNx, bufferLength, CLASSPNP_POOL_TAG_VPD); +#endif + + if (!dataBuffer) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_PNP, + "ClasspGetBlockDeviceTokenLimitsInfo (%p): Couldn't allocate dataBuffer.\n", + DeviceObject)); + + status = STATUS_INSUFFICIENT_RESOURCES; + goto __ClasspGetBlockDeviceTokenLimitsInfo_Exit; + } + + operatingParameters = (PVPD_THIRD_PARTY_COPY_PAGE)dataBuffer; + + RtlZeroMemory(dataBuffer, allocationBufferLength); + + if ((fdoExtension->AdapterDescriptor != NULL) && + (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK)) { + status = InitializeStorageRequestBlock((PSTORAGE_REQUEST_BLOCK)srb, + STORAGE_ADDRESS_TYPE_BTL8, + CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE, + 1, + SrbExDataTypeScsiCdb16); + if (NT_SUCCESS(status)) { + + ((PSTORAGE_REQUEST_BLOCK)srb)->SrbFunction = SRB_FUNCTION_EXECUTE_SCSI; + + } else { + + // + // Should not occur. Revert to legacy SRB. + // + NT_ASSERT(FALSE); + + TracePrint((TRACE_LEVEL_WARNING, + TRACE_FLAG_PNP, + "ClasspGetBlockDeviceTokenLimitsInfo (%p): Falling back to using SRB (instead of SRB_EX).\n", + DeviceObject)); + + RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK)); + srb->Length = sizeof(SCSI_REQUEST_BLOCK); + srb->Function = SRB_FUNCTION_EXECUTE_SCSI; + } + } else { + + RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK)); + srb->Length = sizeof(SCSI_REQUEST_BLOCK); + srb->Function = SRB_FUNCTION_EXECUTE_SCSI; + } + + SrbSetTimeOutValue(srb, fdoExtension->TimeOutValue); + SrbSetRequestTag(srb, SP_UNTAGGED); + SrbSetRequestAttribute(srb, SRB_SIMPLE_TAG_REQUEST); + SrbAssignSrbFlags(srb, fdoExtension->SrbFlags); + + SrbSetCdbLength(srb, 6); + + cdb = SrbGetCdb(srb); + cdb->CDB6INQUIRY3.OperationCode = SCSIOP_INQUIRY; + cdb->CDB6INQUIRY3.EnableVitalProductData = 1; + cdb->CDB6INQUIRY3.PageCode = VPD_THIRD_PARTY_COPY; + cdb->CDB6INQUIRY3.AllocationLength = bufferLength; + + status = ClassSendSrbSynchronous(fdoExtension->DeviceObject, + srb, + dataBuffer, + allocationBufferLength, + FALSE); + + // + // Handle the case where we get back STATUS_DATA_OVERRUN because the input + // buffer was larger than necessary. + // + dataTransferLength = SrbGetDataTransferLength(srb); + + if (status == STATUS_DATA_OVERRUN && dataTransferLength < bufferLength) { + + status = STATUS_SUCCESS; + } + + if (NT_SUCCESS(status)) { + + REVERSE_BYTES_SHORT(&pageLength, &operatingParameters->PageLength); + + } else { + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_PNP, + "ClasspGetBlockDeviceTokenLimitsInfo (%p): Inquiry for TPC VPD failed with %x.\n", + DeviceObject, + status)); + } + + if ((NT_SUCCESS(status)) && + (pageLength >= sizeof(WINDOWS_BLOCK_DEVICE_TOKEN_LIMITS_DESCRIPTOR))) { + + USHORT descriptorType; + + blockDeviceTokenLimits = (PWINDOWS_BLOCK_DEVICE_TOKEN_LIMITS_DESCRIPTOR)operatingParameters->ThirdPartyCopyDescriptors; + REVERSE_BYTES_SHORT(&descriptorType, &blockDeviceTokenLimits->DescriptorType); + REVERSE_BYTES_SHORT(&descriptorLength, &blockDeviceTokenLimits->DescriptorLength); + + if ((descriptorType == BLOCK_DEVICE_TOKEN_LIMITS_DESCRIPTOR_TYPE_WINDOWS) && + (VPD_PAGE_HEADER_SIZE + descriptorLength == sizeof(WINDOWS_BLOCK_DEVICE_TOKEN_LIMITS_DESCRIPTOR))) { + + fdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits = TRUE; + + REVERSE_BYTES_SHORT(&fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumRangeDescriptors, &blockDeviceTokenLimits->MaximumRangeDescriptors); + REVERSE_BYTES(&fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumInactivityTimer, &blockDeviceTokenLimits->MaximumInactivityTimer); + REVERSE_BYTES(&fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.DefaultInactivityTimer, &blockDeviceTokenLimits->DefaultInactivityTimer); + REVERSE_BYTES_QUAD(&fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumTokenTransferSize, &blockDeviceTokenLimits->MaximumTokenTransferSize); + REVERSE_BYTES_QUAD(&fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount, &blockDeviceTokenLimits->OptimalTransferCount); + + TracePrint((TRACE_LEVEL_INFORMATION, + TRACE_FLAG_PNP, + "ClasspGetBlockDeviceTokenLimitsInfo (%p): %s %s (rev %s) reported following parameters: \ + \n\t\t\tMaxRangeDescriptors: %u\n\t\t\tMaxIAT: %u\n\t\t\tDefaultIAT: %u \ + \n\t\t\tMaxTokenTransferSize: %I64u\n\t\t\tOptimalTransferCount: %I64u\n\t\t\tOptimalTransferLengthGranularity: %u \ + \n\t\t\tOptimalTransferLength: %u\n\t\t\tMaxTransferLength: %u\n", + DeviceObject, + (PCSZ)(((PUCHAR)fdoExtension->DeviceDescriptor) + fdoExtension->DeviceDescriptor->VendorIdOffset), + (PCSZ)(((PUCHAR)fdoExtension->DeviceDescriptor) + fdoExtension->DeviceDescriptor->ProductIdOffset), + (PCSZ)(((PUCHAR)fdoExtension->DeviceDescriptor) + fdoExtension->DeviceDescriptor->ProductRevisionOffset), + fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumRangeDescriptors, + fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumInactivityTimer, + fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.DefaultInactivityTimer, + fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumTokenTransferSize, + fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount, + fdoExtension->FunctionSupportInfo->BlockLimitsData.OptimalTransferLengthGranularity, + fdoExtension->FunctionSupportInfo->BlockLimitsData.OptimalTransferLength, + fdoExtension->FunctionSupportInfo->BlockLimitsData.MaximumTransferLength)); + } else { + + TracePrint((TRACE_LEVEL_WARNING, + TRACE_FLAG_PNP, + "ClasspGetBlockDeviceTokenLimitsInfo (%p): ThirdPartyCopy VPD data doesn't have Windows OffloadDataTransfer descriptor.\n", + DeviceObject)); + + fdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits = FALSE; + status = STATUS_DEVICE_FEATURE_NOT_SUPPORTED; + } + } else { + + TracePrint((TRACE_LEVEL_WARNING, + TRACE_FLAG_PNP, + "ClasspGetBlockDeviceTokenLimitsInfo (%p): TPC VPD data didn't return TPC descriptors of interest.\n", + DeviceObject)); + + fdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits = FALSE; + status = STATUS_DEVICE_FEATURE_NOT_SUPPORTED; + } + +__ClasspGetBlockDeviceTokenLimitsInfo_Exit: + + FREE_POOL(dataBuffer); + FREE_POOL(srb); + fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus = status; + + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_PNP, + "ClasspGetBlockDeviceTokenLimitsInfo (%p): Exiting function with status %x.\n", + DeviceObject, + status)); + + return status; +} + + +_IRQL_requires_max_(APC_LEVEL) +_IRQL_requires_min_(PASSIVE_LEVEL) +_IRQL_requires_same_ +NTSTATUS +ClassDeviceProcessOffloadRead( + _In_ PDEVICE_OBJECT DeviceObject, + _In_ PIRP Irp, + _Inout_ PSCSI_REQUEST_BLOCK Srb + ) + +/*++ + +Routine Description: + + This routine services IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES for CopyOffload + Read. If the device supports copy offload, it performs the translation of the + IOCTL into the appropriate SCSI commands to complete the operation. + + This function must be called at IRQL < DISPATCH_LEVEL. + +Arguments: + + DeviceObject - Supplies the device object associated with this request + Irp - The IRP to be processed + Srb - An SRB that can be optinally used to process this request + +Return Value: + + NTSTATUS code + +--*/ + +{ + PIO_STACK_LOCATION irpStack; + NTSTATUS status; + PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes; + PDEVICE_DSM_OFFLOAD_READ_PARAMETERS offloadReadParameters; + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension; + + UNREFERENCED_PARAMETER(Srb); + + // + // This function must be called at less than dispatch level. + // Fail if IRQL >= DISPATCH_LEVEL. + // + PAGED_CODE(); + + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_IOCTL, + "ClassDeviceProcessOffloadRead (%p): Entering function. Irp %p\n", + DeviceObject, + Irp)); + + + irpStack = IoGetCurrentIrpStackLocation (Irp); + + // + // Validations + // + status = ClasspValidateOffloadSupported(DeviceObject, Irp); + if (!NT_SUCCESS(status)) { + goto __ClassDeviceProcessOffloadRead_CompleteAndExit; + } + + if (KeGetCurrentIrql() >= DISPATCH_LEVEL) { + + NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL); + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassDeviceProcessOffloadRead (%p): Called at raised IRQL.\n", + DeviceObject)); + + status = STATUS_INVALID_LEVEL; + goto __ClassDeviceProcessOffloadRead_CompleteAndExit; + } + + // + // Ensure that this DSM IOCTL was generated in kernel + // + if (Irp->RequestorMode != KernelMode) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassDeviceProcessOffloadRead (%p): Called from user mode.\n", + DeviceObject)); + + status = STATUS_ACCESS_DENIED; + goto __ClassDeviceProcessOffloadRead_CompleteAndExit; + } + + status = ClasspValidateOffloadInputParameters(DeviceObject, Irp); + if (!NT_SUCCESS(status)) { + goto __ClassDeviceProcessOffloadRead_CompleteAndExit; + } + + dsmAttributes = Irp->AssociatedIrp.SystemBuffer; + + // + // Validate that we were passed in correct sized parameter block. + // + if (dsmAttributes->ParameterBlockLength < sizeof(DEVICE_DSM_OFFLOAD_READ_PARAMETERS)) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassDeviceProcessOffloadRead (%p): Parameter block size (%u) too small. Required %u.\n", + DeviceObject, + dsmAttributes->ParameterBlockLength, + sizeof(DEVICE_DSM_OFFLOAD_READ_PARAMETERS))); + + status = STATUS_INVALID_PARAMETER; + goto __ClassDeviceProcessOffloadRead_CompleteAndExit; + } + + offloadReadParameters = Add2Ptr(dsmAttributes, dsmAttributes->ParameterBlockOffset); + + fdoExtension = DeviceObject->DeviceExtension; + + // + // If the request TTL is greater than the max supported by this storage, the target will + // end up failing this command, so might as well do the check up front. + // + if ((fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumInactivityTimer > 0) && + (offloadReadParameters->TimeToLive > fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumInactivityTimer)) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassDeviceProcessOffloadRead (%p): Requested TTL (%u) greater than max supported (%u).\n", + DeviceObject, + offloadReadParameters->TimeToLive, + fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumInactivityTimer)); + + status = STATUS_INVALID_PARAMETER; + goto __ClassDeviceProcessOffloadRead_CompleteAndExit; + } + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(STORAGE_OFFLOAD_READ_OUTPUT)) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassDeviceProcessOffloadRead (%p): Output buffer size (%u) too small.\n", + DeviceObject, + irpStack->Parameters.DeviceIoControl.OutputBufferLength)); + + status = STATUS_BUFFER_TOO_SMALL; + goto __ClassDeviceProcessOffloadRead_CompleteAndExit; + } + + + + status = ClasspServicePopulateTokenTransferRequest(DeviceObject, Irp); + + if (status == STATUS_PENDING) { + goto __ClassDeviceProcessOffloadRead_Exit; + } + +__ClassDeviceProcessOffloadRead_CompleteAndExit: + ClasspCompleteOffloadRequest(DeviceObject, Irp, status); +__ClassDeviceProcessOffloadRead_Exit: + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_IOCTL, + "ClassDeviceProcessOffloadRead (%p): Exiting function Irp %p with status %x.\n", + DeviceObject, + Irp, + status)); + + return status; +} + +VOID +ClasspCompleteOffloadRequest( + _In_ PDEVICE_OBJECT DeviceObject, + _In_ PIRP Irp, + _In_ NTSTATUS CompletionStatus) +{ + NTSTATUS status = CompletionStatus; + + + Irp->IoStatus.Status = status; + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + + return; +} + + +_IRQL_requires_max_(APC_LEVEL) +_IRQL_requires_min_(PASSIVE_LEVEL) +_IRQL_requires_same_ +NTSTATUS +ClassDeviceProcessOffloadWrite( + _In_ PDEVICE_OBJECT DeviceObject, + _In_ PIRP Irp, + _Inout_ PSCSI_REQUEST_BLOCK Srb + ) + +/*++ + +Routine Description: + + This routine services IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES for CopyOffload + Write. If the device supports copy offload, it performs the translation of the + IOCTL into the appropriate SCSI commands to complete the operation. + + This function must be called at IRQL < DISPATCH_LEVEL. + +Arguments: + + DeviceObject - Supplies the device object associated with this request + Irp - The IRP to be processed + Srb - An SRB that can be optinally used to process this request + +Return Value: + + NTSTATUS code + +--*/ + +{ + PIO_STACK_LOCATION irpStack; + NTSTATUS status; + PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes; + + UNREFERENCED_PARAMETER(Srb); + + // + // This function must be called at less than dispatch level. + // + PAGED_CODE(); + + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_IOCTL, + "ClassDeviceProcessOffloadWrite (%p): Entering function. Irp %p\n", + DeviceObject, + Irp)); + + + irpStack = IoGetCurrentIrpStackLocation(Irp); + + // + // Validations + // + status = ClasspValidateOffloadSupported(DeviceObject, Irp); + if (!NT_SUCCESS(status)) { + goto __ClassDeviceProcessOffloadWrite_CompleteAndExit; + } + + if (KeGetCurrentIrql() >= DISPATCH_LEVEL) { + + NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL); + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassDeviceProcessOffloadWrite (%p): Called at raised IRQL.\n", + DeviceObject)); + + status = STATUS_INVALID_LEVEL; + goto __ClassDeviceProcessOffloadWrite_CompleteAndExit; + } + + // + // Ensure that this DSM IOCTL was generated in kernel + // + if (Irp->RequestorMode != KernelMode) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassDeviceProcessOffloadWrite (%p): Called from user mode.\n", + DeviceObject)); + + status = STATUS_ACCESS_DENIED; + goto __ClassDeviceProcessOffloadWrite_CompleteAndExit; + } + + status = ClasspValidateOffloadInputParameters(DeviceObject, Irp); + if (!NT_SUCCESS(status)) { + goto __ClassDeviceProcessOffloadWrite_CompleteAndExit; + } + + dsmAttributes = Irp->AssociatedIrp.SystemBuffer; + + // + // Validate that we were passed in correct sized parameter block. + // + if (dsmAttributes->ParameterBlockLength < sizeof(DEVICE_DSM_OFFLOAD_WRITE_PARAMETERS)) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassDeviceProcessOffloadWrite (%p): Parameter block size (%u) too small. Required %u.\n", + DeviceObject, + dsmAttributes->ParameterBlockLength, + sizeof(DEVICE_DSM_OFFLOAD_WRITE_PARAMETERS))); + + status = STATUS_INVALID_PARAMETER; + goto __ClassDeviceProcessOffloadWrite_CompleteAndExit; + } + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(STORAGE_OFFLOAD_WRITE_OUTPUT)) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassDeviceProcessOffloadWrite (%p): Output buffer size (%u) too small.\n", + DeviceObject, + irpStack->Parameters.DeviceIoControl.OutputBufferLength)); + + status = STATUS_BUFFER_TOO_SMALL; + goto __ClassDeviceProcessOffloadWrite_CompleteAndExit; + } + + + + status = ClasspServiceWriteUsingTokenTransferRequest(DeviceObject, Irp); + + if (status == STATUS_PENDING) { + goto __ClassDeviceProcessOffloadWrite_Exit; + } + +__ClassDeviceProcessOffloadWrite_CompleteAndExit: + ClasspCompleteOffloadRequest(DeviceObject, Irp, status); +__ClassDeviceProcessOffloadWrite_Exit: + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_IOCTL, + "ClassDeviceProcessOffloadWrite (%p): Exiting function Irp %p with status %x\n", + DeviceObject, + Irp, + status)); + + return status; +} + + +_IRQL_requires_max_(APC_LEVEL) +_IRQL_requires_min_(PASSIVE_LEVEL) +_IRQL_requires_same_ +NTSTATUS +ClasspServicePopulateTokenTransferRequest( + _In_ PDEVICE_OBJECT Fdo, + _In_ PIRP Irp + ) + +/*++ + +Routine description: + + This routine processes offload read requests by building the SRB + for PopulateToken and its result (i.e. token). + +Arguments: + + Fdo - The functional device object processing the request + Irp - The Io request to be processed + +Return Value: + + STATUS_SUCCESS if successful, an error code otherwise + +--*/ + +{ + BOOLEAN allDataSetRangeFullyConverted; + UINT32 allocationSize; + ULONG blockDescrIndex; + PBLOCK_DEVICE_RANGE_DESCRIPTOR blockDescrPointer; + PVOID buffer; + ULONG bufferLength; + ULONG dataSetRangeIndex; + PDEVICE_DATA_SET_RANGE dataSetRanges; + ULONG dataSetRangesCount; + PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes; + ULONGLONG entireXferLen; + PFUNCTIONAL_DEVICE_EXTENSION fdoExt; + ULONG i; + ULONG lbaCount; + ULONG listIdentifier; + ULONG maxBlockDescrCount; + ULONGLONG maxLbaCount; + POFFLOAD_READ_CONTEXT offloadReadContext; + PDEVICE_DSM_OFFLOAD_READ_PARAMETERS offloadReadParameters; + PTRANSFER_PACKET pkt; + USHORT populateTokenDataLength; + USHORT populateTokenDescriptorsLength; + PMDL populateTokenMdl; + PIRP pseudoIrp; + ULONG receiveTokenInformationBufferLength; + NTSTATUS status; + DEVICE_DATA_SET_RANGE tempDataSetRange; + BOOLEAN tempDataSetRangeFullyConverted; + PUCHAR token; + ULONG tokenLength; + ULONG tokenOperationBufferLength; + ULONGLONG totalSectorCount; + ULONGLONG totalSectorsToProcess; + ULONG transferSize; + + PAGED_CODE(); + + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_IOCTL, + "ClasspServicePopulateTokenTransferRequest (%p): Entering function. Irp %p\n", + Fdo, + Irp)); + + fdoExt = Fdo->DeviceExtension; + status = STATUS_SUCCESS; + dsmAttributes = Irp->AssociatedIrp.SystemBuffer; + buffer = NULL; + pkt = NULL; + populateTokenMdl = NULL; + offloadReadParameters = Add2Ptr(dsmAttributes, dsmAttributes->ParameterBlockOffset); + dataSetRanges = Add2Ptr(dsmAttributes, dsmAttributes->DataSetRangesOffset); + dataSetRangesCount = dsmAttributes->DataSetRangesLength / sizeof(DEVICE_DATA_SET_RANGE); + totalSectorsToProcess = 0; + token = NULL; + tokenLength = 0; + bufferLength = 0; + offloadReadContext = NULL; + + NT_ASSERT(fdoExt->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits && + NT_SUCCESS(fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus)); + + + for (i = 0, entireXferLen = 0; i < dataSetRangesCount; i++) { + entireXferLen += dataSetRanges[i].LengthInBytes; + } + + // + // We need to truncate the read request based on the following hardware limitations: + // 1. The size of the data buffer containing the TokenOperation command's parameters must + // not exceed the MaximumTransferLength (and max physical pages) of the underlying + // adapter. + // 2. The number of descriptors specified in the TokenOperation command must not exceed + // the MaximumRangeDescriptors. + // 3. The cumulative total of the number of transfer blocks in all the descriptors in + // the TokenOperation command must not exceed the MaximumTokenTransferSize. + // 4. If the TTL has been set to the 0 (i.e. indicates the use of the default TLT), + // limit the cumulative total of the number of transfer blocks in all the descriptors + // in the TokenOperation command to the OptimalTransferCount. + // NOTE: only a TTL of 0 has an implicit indication that the initiator of the + // request would like the storage stack to be smart about the amount of data + // transfer. + // + // In addition to the above, we need to ensure that for each of the descriptors in the + // TokenOperation command: + // 1. The number of blocks specified is an exact multiple of the OptimalTransferLengthGranularity. + // 2. The number of blocks specified is limited to the MaximumTransferLength. (We shall + // however, limit the number of blocks specified in each descriptor to be a maximum of + // OptimalTransferLength or MaximumTransferLength, whichever is lesser). + // + // Finally, we shall always send down the PopulateToken command using IMMED = 0 for this + // release. This makes it simpler to handle multi-initiator scenarios since we won't need + // to deal with the PopulateToken (with IMMED = 1) succeeding but ReceiveRODTokenInformation + // failing due to the path/node failing, thus making it impossible to retrieve the token. + // + // The LBA ranges is in DEVICE_DATA_SET_RANGE format, it needs to be converted into + // WINDOWS_RANGE_DESCRIPTOR Block Descriptors. + // + + ClasspGetTokenOperationCommandBufferLength(Fdo, + SERVICE_ACTION_POPULATE_TOKEN, + &bufferLength, + &tokenOperationBufferLength, + &receiveTokenInformationBufferLength); + + allocationSize = sizeof(OFFLOAD_READ_CONTEXT) + bufferLength; + + offloadReadContext = ExAllocatePoolWithTag( + NonPagedPoolNx, + allocationSize, + CLASSPNP_POOL_TAG_TOKEN_OPERATION); + + if (!offloadReadContext) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspServicePopulateTokenTransferRequest (%p): Failed to allocate buffer for PopulateToken operations.\n", + Fdo)); + + status = STATUS_INSUFFICIENT_RESOURCES; + goto __ClasspServicePopulateTokenTransferRequest_ErrorExit; + } + + RtlZeroMemory(offloadReadContext, allocationSize); + + offloadReadContext->Fdo = Fdo; + offloadReadContext->OffloadReadDsmIrp = Irp; + + // + // The buffer for the commands is after the offloadReadContext. + // + buffer = (offloadReadContext + 1); + + // + // No matter how large the offload read request, we'll be sending it down in one shot. + // So we need only one transfer packet. + // + pkt = DequeueFreeTransferPacket(Fdo, TRUE); + if (!pkt){ + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspServicePopulateTokenTransferRequest (%p): Failed to retrieve transfer packet for TokenOperation (PopulateToken) operation.\n", + Fdo)); + + status = STATUS_INSUFFICIENT_RESOURCES; + goto __ClasspServicePopulateTokenTransferRequest_ErrorExit; + } + + offloadReadContext->Pkt = pkt; + + ClasspGetTokenOperationDescriptorLimits(Fdo, + SERVICE_ACTION_POPULATE_TOKEN, + tokenOperationBufferLength, + &maxBlockDescrCount, + &maxLbaCount); + + // + // We will limit the maximum data transfer in an offload read operation to: + // - OTC if TTL was specified as 0 + // - MaximumTransferTransferSize if lesser than above, or if TTL was specified was non-zero + // + if ((offloadReadParameters->TimeToLive == 0) && (fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount > 0)) { + + maxLbaCount = MIN(maxLbaCount, fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount); + } + + // + // Since we do not want very fragmented files to end up causing the PopulateToken command to take + // too long (and potentially timeout), we will limit the max number of descriptors sent down in a + // command. + // + maxBlockDescrCount = MIN(maxBlockDescrCount, MAX_NUMBER_BLOCK_DEVICE_DESCRIPTORS); + + TracePrint((TRACE_LEVEL_INFORMATION, + TRACE_FLAG_IOCTL, + "ClasspServicePopulateTokenTransferRequest (%p): Using MaxBlockDescrCount %u and MaxLbaCount %I64u.\n", + Fdo, + maxBlockDescrCount, + maxLbaCount)); + + allDataSetRangeFullyConverted = FALSE; + tempDataSetRangeFullyConverted = TRUE; + dataSetRangeIndex = (ULONG)-1; + + blockDescrPointer = (PBLOCK_DEVICE_RANGE_DESCRIPTOR) + &((PPOPULATE_TOKEN_HEADER)buffer)->BlockDeviceRangeDescriptor[0]; + + RtlZeroMemory(&tempDataSetRange, sizeof(tempDataSetRange)); + + blockDescrIndex = 0; + lbaCount = 0; + + // + // Send PopulateToken command when the buffer is full or all input entries are converted. + // + while (!((blockDescrIndex == maxBlockDescrCount) || // buffer full or block descriptor count reached + (lbaCount == maxLbaCount) || // block LBA count reached + (allDataSetRangeFullyConverted))) { // all DataSetRanges have been converted + + // + // If the previous entry conversion completed, continue the next one; + // Otherwise, still process the left part of the un-completed entry. + // + if (tempDataSetRangeFullyConverted) { + dataSetRangeIndex++; + tempDataSetRange.StartingOffset = dataSetRanges[dataSetRangeIndex].StartingOffset; + tempDataSetRange.LengthInBytes = dataSetRanges[dataSetRangeIndex].LengthInBytes; + } + + totalSectorCount = 0; + + ClasspConvertDataSetRangeToBlockDescr(Fdo, + blockDescrPointer, + &blockDescrIndex, + maxBlockDescrCount, + &lbaCount, + maxLbaCount, + &tempDataSetRange, + &totalSectorCount); + + tempDataSetRangeFullyConverted = (tempDataSetRange.LengthInBytes == 0) ? TRUE : FALSE; + + allDataSetRangeFullyConverted = tempDataSetRangeFullyConverted && ((dataSetRangeIndex + 1) == dataSetRangesCount); + + totalSectorsToProcess += totalSectorCount; + } + + // + // Calculate transfer size, including the header + // + transferSize = (blockDescrIndex * sizeof(BLOCK_DEVICE_RANGE_DESCRIPTOR)) + FIELD_OFFSET(POPULATE_TOKEN_HEADER, BlockDeviceRangeDescriptor); + + NT_ASSERT(transferSize <= MAX_TOKEN_OPERATION_PARAMETER_DATA_LENGTH); + + populateTokenDataLength = (USHORT)transferSize - RTL_SIZEOF_THROUGH_FIELD(POPULATE_TOKEN_HEADER, PopulateTokenDataLength); + REVERSE_BYTES_SHORT(((PPOPULATE_TOKEN_HEADER)buffer)->PopulateTokenDataLength, &populateTokenDataLength); + + ((PPOPULATE_TOKEN_HEADER)buffer)->Immediate = 0; + + REVERSE_BYTES(((PPOPULATE_TOKEN_HEADER)buffer)->InactivityTimeout, &offloadReadParameters->TimeToLive); + + populateTokenDescriptorsLength = (USHORT)transferSize - FIELD_OFFSET(POPULATE_TOKEN_HEADER, BlockDeviceRangeDescriptor); + REVERSE_BYTES_SHORT(((PPOPULATE_TOKEN_HEADER)buffer)->BlockDeviceRangeDescriptorListLength, &populateTokenDescriptorsLength); + + // + // Reuse a single buffer for both TokenOperation and ReceiveTokenInformation. This has the one disadvantage + // that we'll be marking the page(s) as IoWriteAccess even though we only need read access for token + // operation command. However, the advantage is that we eliminate the possibility of any potential failures + // when trying to allocate an MDL for the ReceiveTokenInformation later on. + // + bufferLength = max(transferSize, receiveTokenInformationBufferLength); + + populateTokenMdl = ClasspBuildDeviceMdl(buffer, bufferLength, FALSE); + if (!populateTokenMdl) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspServicePopulateTokenTransferRequest (%p): Failed to allocate MDL for PopulateToken operations.\n", + Fdo)); + + status = STATUS_INSUFFICIENT_RESOURCES; + goto __ClasspServicePopulateTokenTransferRequest_ErrorExit; + } + + offloadReadContext->PopulateTokenMdl = populateTokenMdl; + + pseudoIrp = &offloadReadContext->PseudoIrp; + + + pseudoIrp->IoStatus.Status = STATUS_SUCCESS; + pseudoIrp->IoStatus.Information = 0; + pseudoIrp->Tail.Overlay.DriverContext[0] = LongToPtr(1); + pseudoIrp->MdlAddress = populateTokenMdl; + + InterlockedCompareExchange((volatile LONG *)&TokenOperationListIdentifier, -1, MaxTokenOperationListIdentifier); + listIdentifier = InterlockedIncrement((volatile LONG *)&TokenOperationListIdentifier); + + ClasspSetupPopulateTokenTransferPacket( + offloadReadContext, + pkt, + transferSize, + (PUCHAR)buffer, + pseudoIrp, + listIdentifier); + + TracePrint((TRACE_LEVEL_INFORMATION, + TRACE_FLAG_IOCTL, + "ClasspServicePopulateTokenTransferRequest (%p): Generate token for %I64u bytes (versus %I64u) [via %u descriptors]. \ + \n\t\t\tDataLength: %u, DescriptorsLength: %u. Pkt %p (list id %x). Requested TTL: %u secs.\n", + Fdo, + totalSectorsToProcess * fdoExt->DiskGeometry.BytesPerSector, + entireXferLen, + blockDescrIndex, + populateTokenDataLength, + populateTokenDescriptorsLength, + pkt, + listIdentifier, + offloadReadParameters->TimeToLive)); + + // + // Save (into the offloadReadContext) any remaining things that + // ClasspPopulateTokenTransferPacketDone() will need. + // + + offloadReadContext->ListIdentifier = listIdentifier; + offloadReadContext->BufferLength = bufferLength; + offloadReadContext->ReceiveTokenInformationBufferLength = receiveTokenInformationBufferLength; + offloadReadContext->TotalSectorsToProcess = totalSectorsToProcess; // so far + offloadReadContext->EntireXferLen = entireXferLen; + + NT_ASSERT(status == STATUS_SUCCESS); // so far. + + IoMarkIrpPending(Irp); + SubmitTransferPacket(pkt); + + status = STATUS_PENDING; + goto __ClasspServicePopulateTokenTransferRequest_Exit; + + // + // Error cleanup label only - not used in success case: + // + +__ClasspServicePopulateTokenTransferRequest_ErrorExit: + + NT_ASSERT(status != STATUS_PENDING); + + if (offloadReadContext != NULL) { + ClasspCleanupOffloadReadContext(offloadReadContext); + offloadReadContext = NULL; + } + +__ClasspServicePopulateTokenTransferRequest_Exit: + + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_IOCTL, + "ClasspServicePopulateTokenTransferRequest (%p): Exiting function (Irp %p) with status %x.\n", + Fdo, + Irp, + status)); + + return status; +} + + +VOID +ClasspPopulateTokenTransferPacketDone( + _In_ PVOID Context + ) + +/*++ + +Routine description: + + This routine continues an offload read operation on completion of the + populate token transfer packet. + + This function is responsible for continuing or completing the offload read + operation. + +Arguments: + + Context - Pointer to the OFFLOAD_READ_CONTEXT for the offload read + operation. + +Return Value: + + None. + +--*/ + +{ + PDEVICE_OBJECT fdo; + ULONG listIdentifier; + POFFLOAD_READ_CONTEXT offloadReadContext; + PTRANSFER_PACKET pkt; + PIRP pseudoIrp; + NTSTATUS status; + + offloadReadContext = Context; + pseudoIrp = &offloadReadContext->PseudoIrp; + pkt = offloadReadContext->Pkt; + fdo = offloadReadContext->Fdo; + listIdentifier = offloadReadContext->ListIdentifier; + + offloadReadContext->Pkt = NULL; + + + status = pseudoIrp->IoStatus.Status; + NT_ASSERT(status != STATUS_PENDING); + + if (!NT_SUCCESS(status)) { + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspPopulateTokenTransferPacketDone (%p): Generate token for list Id %x failed with %x (Pkt %p).\n", + fdo, + listIdentifier, + status, + pkt)); + goto __ClasspPopulateTokenTransferPacketDone_ErrorExit; + } + + // + // If a token was successfully generated, it is now time to retrieve the token. + // The called function is responsible for completing the offload read DSM IRP. + // + ClasspReceivePopulateTokenInformation(offloadReadContext); + + // + // ClasspReceivePopulateTokenInformation() takes care of completing the IRP, + // so this function is done regardless of success or failure in + // ClasspReceivePopulateTokenInformation(). + // + + return; + + // + // Error cleanup label only - not used in success case: + // + +__ClasspPopulateTokenTransferPacketDone_ErrorExit: + + NT_ASSERT(!NT_SUCCESS(status)); + + // + // ClasspCompleteOffloadRead also cleans up offloadReadContext. + // + + ClasspCompleteOffloadRead(offloadReadContext, status); + + return; +} + + +VOID +ClasspCompleteOffloadRead( + _In_ POFFLOAD_READ_CONTEXT OffloadReadContext, + _In_ NTSTATUS CompletionStatus + ) + +/*++ + +Routine description: + + This routine completes an offload read operation with given status, and + cleans up the OFFLOAD_READ_CONTEXT. + +Arguments: + + OffloadReadContext - Pointer to the OFFLOAD_READ_CONTEXT for the offload + read operation. + + CompletionStatus - The completion status for the offload read operation. + +Return Value: + + None. + +--*/ + +{ + PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes; + ULONGLONG entireXferLen; + PDEVICE_OBJECT fdo; + PFUNCTIONAL_DEVICE_EXTENSION fdoExt; + PIRP irp; + NTSTATUS status; + PUCHAR token; + ULONGLONG totalSectorsProcessed; + + status = CompletionStatus; + dsmAttributes = OffloadReadContext->OffloadReadDsmIrp->AssociatedIrp.SystemBuffer; + totalSectorsProcessed = OffloadReadContext->TotalSectorsProcessed; + fdoExt = OffloadReadContext->Fdo->DeviceExtension; + entireXferLen = OffloadReadContext->EntireXferLen; + token = OffloadReadContext->Token; + irp = OffloadReadContext->OffloadReadDsmIrp; + fdo = OffloadReadContext->Fdo; + + ((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->OffloadReadFlags = 0; + ((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->Reserved = 0; + + if (NT_SUCCESS(status)) { + ULONGLONG totalBytesProcessed = totalSectorsProcessed * fdoExt->DiskGeometry.BytesPerSector; + + TracePrint((totalBytesProcessed == entireXferLen ? TRACE_LEVEL_INFORMATION : TRACE_LEVEL_WARNING, + TRACE_FLAG_IOCTL, + "ClasspCompleteOffloadRead (%p): Successfully populated token with %I64u (out of %I64u) bytes (list Id %x).\n", + fdo, + totalBytesProcessed, + entireXferLen, + OffloadReadContext->ListIdentifier)); + + if (totalBytesProcessed < entireXferLen) { + SET_FLAG(((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->OffloadReadFlags, STORAGE_OFFLOAD_READ_RANGE_TRUNCATED); + } + ((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->LengthProtected = totalBytesProcessed; + ((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->TokenLength = STORAGE_OFFLOAD_MAX_TOKEN_LENGTH; + RtlCopyMemory(&((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->Token, token, STORAGE_OFFLOAD_MAX_TOKEN_LENGTH); + } else { + ((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->LengthProtected = 0; + ((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->TokenLength = 0; + } + + irp->IoStatus.Information = sizeof(STORAGE_OFFLOAD_READ_OUTPUT); + + ClasspCompleteOffloadRequest(fdo, irp, status); + ClasspCleanupOffloadReadContext(OffloadReadContext); + OffloadReadContext = NULL; + + return; +} + + +VOID +ClasspCleanupOffloadReadContext( + _In_ __drv_freesMem(mem) POFFLOAD_READ_CONTEXT OffloadReadContext + ) + +/*++ + +Routine description: + + This routine cleans up an OFFLOAD_READ_CONTEXT. + +Arguments: + + OffloadReadContext - Pointer to the OFFLOAD_READ_CONTEXT for the offload + read operation. + +Return Value: + + None. + +--*/ + +{ + PMDL populateTokenMdl; + + populateTokenMdl = OffloadReadContext->PopulateTokenMdl; + + NT_ASSERT(OffloadReadContext != NULL); + + if (populateTokenMdl) { + ClasspFreeDeviceMdl(populateTokenMdl); + } + FREE_POOL(OffloadReadContext); + + return; +} + + +_IRQL_requires_same_ +VOID +ClasspReceivePopulateTokenInformation( + _In_ POFFLOAD_READ_CONTEXT OffloadReadContext + ) + +/*++ + +Routine description: + + This routine retrieves the token after a PopulateToken command + has been sent down. + + Can also be called repeatedly for a single offload op when a previous + RECEIVE ROD TOKEN INFORMATION for that op indicated that the operation was + not yet complete. + + This function is responsible for continuing or completing the offload read + operation. + +Arguments: + + OffloadReadContext - Pointer to the OFFLOAD_READ_CONTEXT for the offload + read operation. + +Return Value: + + None. + +--*/ + +{ + PVOID buffer; + ULONG bufferLength; + ULONG cdbLength; + PDEVICE_OBJECT fdo; + PIRP irp; + ULONG listIdentifier; + PTRANSFER_PACKET pkt; + PIRP pseudoIrp; + ULONG receiveTokenInformationBufferLength; + PSCSI_REQUEST_BLOCK srb; + NTSTATUS status; + ULONG tempSizeUlong; + PULONGLONG totalSectorsProcessed; + + totalSectorsProcessed = &OffloadReadContext->TotalSectorsProcessed; + buffer = OffloadReadContext + 1; + bufferLength = OffloadReadContext->BufferLength; + fdo = OffloadReadContext->Fdo; + irp = OffloadReadContext->OffloadReadDsmIrp; + receiveTokenInformationBufferLength = OffloadReadContext->ReceiveTokenInformationBufferLength; + listIdentifier = OffloadReadContext->ListIdentifier; + + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_IOCTL, + "ClasspReceivePopulateTokenInformation (%p): Entering function. Irp %p\n", + fdo, + irp)); + + srb = &OffloadReadContext->Srb; + *totalSectorsProcessed = 0; + + pkt = DequeueFreeTransferPacket(fdo, TRUE); + if (!pkt){ + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspReceivePopulateTokenInformation (%p): Failed to retrieve transfer packet for ReceiveTokenInformation (PopulateToken) operation.\n", + fdo)); + + status = STATUS_INSUFFICIENT_RESOURCES; + goto __ClasspReceivePopulateTokenInformation_ErrorExit; + } + + OffloadReadContext->Pkt = pkt; + + RtlZeroMemory(buffer, bufferLength); + + tempSizeUlong = receiveTokenInformationBufferLength - 4; + REVERSE_BYTES(((PRECEIVE_TOKEN_INFORMATION_HEADER)buffer)->AvailableData, &tempSizeUlong); + + pseudoIrp = &OffloadReadContext->PseudoIrp; + RtlZeroMemory(pseudoIrp, sizeof(IRP)); + + + pseudoIrp->IoStatus.Status = STATUS_SUCCESS; + pseudoIrp->IoStatus.Information = 0; + pseudoIrp->Tail.Overlay.DriverContext[0] = LongToPtr(1); + pseudoIrp->MdlAddress = OffloadReadContext->PopulateTokenMdl; + + ClasspSetupReceivePopulateTokenInformationTransferPacket( + OffloadReadContext, + pkt, + receiveTokenInformationBufferLength, + (PUCHAR)buffer, + pseudoIrp, + listIdentifier); + + // + // Cache away the CDB as it may be required for forwarded sense data + // after this command completes. + // + RtlZeroMemory(srb, sizeof(*srb)); + cdbLength = SrbGetCdbLength(pkt->Srb); + if (cdbLength <= 16) { + RtlCopyMemory(&srb->Cdb, SrbGetCdb(pkt->Srb), cdbLength); + } + + SubmitTransferPacket(pkt); + + return; + + // + // Error cleanup label only - not used in success case: + // + +__ClasspReceivePopulateTokenInformation_ErrorExit: + + NT_ASSERT(!NT_SUCCESS(status)); + + // + // ClasspCompleteOffloadRead also cleans up offloadReadContext. + // + + ClasspCompleteOffloadRead(OffloadReadContext, status); + + return; +} + + +VOID +ClasspReceivePopulateTokenInformationTransferPacketDone( + _In_ PVOID Context + ) + +/*++ + +Routine description: + + This routine continues an offload read operation on completion of the + RECEIVE ROD TOKEN INFORMATION transfer packet. + + This routine is responsible for continuing or completing the offload read + operation. + +Arguments: + + Context - Pointer to the OFFLOAD_READ_CONTEXT for the offload read + operation. + +Return Value: + + None. + +--*/ + +{ + ULONG availableData; + PVOID buffer; + UCHAR completionStatus; + ULONG estimatedRetryInterval; + PDEVICE_OBJECT fdo; + PFUNCTIONAL_DEVICE_EXTENSION fdoExt; + PIRP irp; + ULONG listIdentifier; + POFFLOAD_READ_CONTEXT offloadReadContext; + BOOLEAN operationCompleted; + UCHAR operationStatus; + PIRP pseudoIrp; + USHORT segmentsProcessed; + PSENSE_DATA senseData; + ULONG senseDataFieldLength; + UCHAR senseDataLength; + PSCSI_REQUEST_BLOCK srb; + NTSTATUS status; + PUCHAR token; + PVOID tokenAscii; + PBLOCK_DEVICE_TOKEN_DESCRIPTOR tokenDescriptor; + ULONG tokenDescriptorLength; + PRECEIVE_TOKEN_INFORMATION_HEADER tokenInformationResults; + PRECEIVE_TOKEN_INFORMATION_RESPONSE_HEADER tokenInformationResultsResponse; + ULONG tokenLength; + ULONG tokenSize; + PULONGLONG totalSectorsProcessed; + ULONGLONG totalSectorsToProcess; + ULONGLONG transferBlockCount; + + offloadReadContext = Context; + fdo = offloadReadContext->Fdo; + fdoExt = fdo->DeviceExtension; + buffer = offloadReadContext + 1; + listIdentifier = offloadReadContext->ListIdentifier; + irp = offloadReadContext->OffloadReadDsmIrp; + totalSectorsToProcess = offloadReadContext->TotalSectorsToProcess; + totalSectorsProcessed = &offloadReadContext->TotalSectorsProcessed; + srb = &offloadReadContext->Srb; + tokenAscii = NULL; + tokenSize = BLOCK_DEVICE_TOKEN_SIZE; + tokenInformationResults = (PRECEIVE_TOKEN_INFORMATION_HEADER)buffer; + senseData = (PSENSE_DATA)((PUCHAR)tokenInformationResults + FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_HEADER, SenseData)); + transferBlockCount = 0; + tokenInformationResultsResponse = NULL; + tokenDescriptor = NULL; + operationCompleted = FALSE; + tokenDescriptorLength = 0; + tokenLength = 0; + token = NULL; + pseudoIrp = &offloadReadContext->PseudoIrp; + + status = pseudoIrp->IoStatus.Status; + NT_ASSERT(status != STATUS_PENDING); + + // + // The buffer we hand allows for the max sizes for all the fields whereas the returned + // data may be lesser (e.g. sense data info will almost never be MAX_SENSE_BUFFER_SIZE, etc. + // so handle underrun "error" + // + if (status == STATUS_DATA_OVERRUN) { + + status = STATUS_SUCCESS; + } + + if (!NT_SUCCESS(status)) { + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): Token retrieval failed for list Id %x with %x.\n", + fdo, + listIdentifier, + status)); + goto __ClasspReceivePopulateTokenInformationTransferPacketDone_Exit; + } + + REVERSE_BYTES(&availableData, &tokenInformationResults->AvailableData); + + NT_ASSERT(availableData <= FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_HEADER, SenseData) + MAX_SENSE_BUFFER_SIZE + + FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_RESPONSE_HEADER, TokenDescriptor) + BLOCK_DEVICE_TOKEN_SIZE); + + NT_ASSERT(tokenInformationResults->ResponseToServiceAction == SERVICE_ACTION_POPULATE_TOKEN); + + operationStatus = tokenInformationResults->OperationStatus; + operationCompleted = ClasspIsTokenOperationComplete(operationStatus); + NT_ASSERT(operationCompleted); + + REVERSE_BYTES(&estimatedRetryInterval, &tokenInformationResults->EstimatedStatusUpdateDelay); + + completionStatus = tokenInformationResults->CompletionStatus; + + NT_ASSERT(tokenInformationResults->TransferCountUnits == TRANSFER_COUNT_UNITS_NUMBER_BLOCKS); + REVERSE_BYTES_QUAD(&transferBlockCount, &tokenInformationResults->TransferCount); + + REVERSE_BYTES_SHORT(&segmentsProcessed, &tokenInformationResults->SegmentsProcessed); + NT_ASSERT(segmentsProcessed == 0); + + if (operationCompleted) { + + if (transferBlockCount > totalSectorsToProcess) { + + // + // Buggy or hostile target. Don't let it claim more was procesed + // than was requested. Since this is likely a bug and it's unknown + // how much was actually transferred, assume no data was + // transferred. + // + + NT_ASSERT(transferBlockCount <= totalSectorsToProcess); + transferBlockCount = 0; + } + + if (operationStatus != OPERATION_COMPLETED_WITH_SUCCESS && + operationStatus != OPERATION_COMPLETED_WITH_RESIDUAL_DATA) { + + // + // Assert on buggy response from target, but in any case, make sure not + // to claim that any data was written. + // + + NT_ASSERT(transferBlockCount == 0); + transferBlockCount = 0; + } + + // + // Since the TokenOperation was sent down synchronously, the operation is complete as soon as the command returns. + // + + senseDataFieldLength = tokenInformationResults->SenseDataFieldLength; + senseDataLength = tokenInformationResults->SenseDataLength; + NT_ASSERT(senseDataFieldLength >= senseDataLength); + + tokenInformationResultsResponse = (PRECEIVE_TOKEN_INFORMATION_RESPONSE_HEADER)((PUCHAR)tokenInformationResults + + FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_HEADER, SenseData) + + tokenInformationResults->SenseDataFieldLength); + + REVERSE_BYTES(&tokenDescriptorLength, &tokenInformationResultsResponse->TokenDescriptorsLength); + + if (tokenDescriptorLength > 0) { + + NT_ASSERT(tokenDescriptorLength == sizeof(BLOCK_DEVICE_TOKEN_DESCRIPTOR)); + + if (tokenDescriptorLength != sizeof(BLOCK_DEVICE_TOKEN_DESCRIPTOR)) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): Bad firmware, token descriptor length %u.\n", + fdo, + tokenDescriptorLength)); + + NT_ASSERT((*totalSectorsProcessed) == 0); + NT_ASSERT(tokenLength == 0); + + } else { + + USHORT restrictedId; + + tokenDescriptor = (PBLOCK_DEVICE_TOKEN_DESCRIPTOR)tokenInformationResultsResponse->TokenDescriptor; + + REVERSE_BYTES_SHORT(&restrictedId, &tokenDescriptor->TokenIdentifier); + NT_ASSERT(restrictedId == 0); + + tokenLength = BLOCK_DEVICE_TOKEN_SIZE; + token = tokenDescriptor->Token; + + *totalSectorsProcessed = transferBlockCount; + + if (transferBlockCount < totalSectorsToProcess) { + + NT_ASSERT(operationStatus == OPERATION_COMPLETED_WITH_RESIDUAL_DATA || + operationStatus == OPERATION_COMPLETED_WITH_ERROR || + operationStatus == OPERATION_TERMINATED); + + if (transferBlockCount == 0) { + // + // Treat the same as not getting a token. + // + + tokenLength = 0; + } + + } else { + + NT_ASSERT(operationStatus == OPERATION_COMPLETED_WITH_SUCCESS); + NT_ASSERT(transferBlockCount == totalSectorsToProcess); + } + + // + // Need to convert to ascii. + // + tokenAscii = ClasspBinaryToAscii((PUCHAR)token, + tokenSize, + &tokenSize); + + TracePrint((transferBlockCount == totalSectorsToProcess ? TRACE_LEVEL_INFORMATION : TRACE_LEVEL_WARNING, + TRACE_FLAG_IOCTL, + "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): %wsToken %s generated successfully for list Id %x for data size %I64u bytes.\n", + fdo, + transferBlockCount == totalSectorsToProcess ? L"" : L"Target truncated read. ", + (tokenAscii == NULL) ? "" : tokenAscii, + listIdentifier, + (*totalSectorsProcessed) * fdoExt->DiskGeometry.BytesPerSector)); + + FREE_POOL(tokenAscii); + } + } else { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): Target failed to generate a token for list Id %x for data size %I64u bytes (requested %I64u bytes).\n", + fdo, + listIdentifier, + transferBlockCount * fdoExt->DiskGeometry.BytesPerSector, + totalSectorsToProcess * fdoExt->DiskGeometry.BytesPerSector)); + + *totalSectorsProcessed = 0; + + NT_ASSERT(operationStatus == OPERATION_COMPLETED_WITH_ERROR); + } + + // + // Operation that completes with success can have sense data (for target to pass on some extra info) + // but we don't care about such sense info. + // Operation that complete but not with success, may not have sense data associated, but may + // have valid CompletionStatus. + // + // The "status" may be overriden by ClassInterpretSenseInfo(). Final + // status is determined a bit later - this is just the default status + // when ClassInterpretSenseInfo() doesn't get to run here. + // + status = STATUS_SUCCESS; + if (operationStatus == OPERATION_COMPLETED_WITH_ERROR || + operationStatus == OPERATION_COMPLETED_WITH_RESIDUAL_DATA || + operationStatus == OPERATION_TERMINATED) { + + SrbSetScsiStatus((PSTORAGE_REQUEST_BLOCK_HEADER)srb, completionStatus); + + if (senseDataLength) { + + ULONG retryInterval; + + NT_ASSERT(senseDataLength <= sizeof(SENSE_DATA)); + + srb->Function = SRB_FUNCTION_EXECUTE_SCSI; + + srb->SrbStatus = SRB_STATUS_AUTOSENSE_VALID | SRB_STATUS_ERROR; + SrbSetSenseInfoBuffer((PSTORAGE_REQUEST_BLOCK_HEADER)srb, senseData); + SrbSetSenseInfoBufferLength((PSTORAGE_REQUEST_BLOCK_HEADER)srb, senseDataLength); + + ClassInterpretSenseInfo(fdo, + srb, + IRP_MJ_SCSI, + 0, + 0, + &status, + &retryInterval); + + TracePrint((TRACE_LEVEL_WARNING, + TRACE_FLAG_IOCTL, + "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): Reason for truncation/failure: %x - for list Id %x for data size %I64u bytes.\n", + fdo, + status, + listIdentifier, + transferBlockCount * fdoExt->DiskGeometry.BytesPerSector)); + } else { + + TracePrint((TRACE_LEVEL_WARNING, + TRACE_FLAG_IOCTL, + "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): No sense data available but reason for truncation/failure, possibly: %x - for list Id %x for data size %I64u bytes.\n", + fdo, + completionStatus, + listIdentifier, + transferBlockCount * fdoExt->DiskGeometry.BytesPerSector)); + } + } + + if (tokenLength > 0) { + + offloadReadContext->Token = token; + + // + // Even if target returned an error, from the OS upper layers' perspective, + // it is a success (with truncation) if any data at all was read. + // + status = STATUS_SUCCESS; + + } else { + + if (NT_SUCCESS(status)) { + // + // Make sure status is a failing status, without throwing away an + // already-failing status obtained from sense data. + // + status = STATUS_UNSUCCESSFUL; + } + } + + // + // Done with the operation. + // + + NT_ASSERT(status != STATUS_PENDING); + goto __ClasspReceivePopulateTokenInformationTransferPacketDone_Exit; + + } else { + + status = STATUS_UNSUCCESSFUL; + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): Token retrieval failed for list Id %x with %x.\n", + fdo, + listIdentifier, + status)); + + NT_ASSERT(*totalSectorsProcessed == 0); + goto __ClasspReceivePopulateTokenInformationTransferPacketDone_Exit; + } + +__ClasspReceivePopulateTokenInformationTransferPacketDone_Exit: + + if (status != STATUS_PENDING) { + + // + // The "status" value can be success or failure at this point, as + // appropriate. + // + + ClasspCompleteOffloadRead(offloadReadContext, status); + } + + // + // Due to tracing a potentially freed pointer value "Irp", this trace could + // be delayed beyond another offload op picking up the same pointer value + // for its Irp. This function exits after the operation is complete when + // status != STATUS_PENDING. + // + + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_IOCTL, + "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): Exiting function (Irp %p) with internal status %x.\n", + fdo, + irp, + status)); + + return; +} + + +_IRQL_requires_max_(APC_LEVEL) +_IRQL_requires_min_(PASSIVE_LEVEL) +_IRQL_requires_same_ +NTSTATUS +ClasspServiceWriteUsingTokenTransferRequest( + _In_ PDEVICE_OBJECT Fdo, + _In_ PIRP Irp + ) + +/*++ + +Routine description: + + This routine processes offload write requests by building the SRB + for WriteUsingToken. + +Arguments: + + Fdo - The functional device object processing the request + Irp - The Io request to be processed + +Return Value: + + STATUS_SUCCESS if successful, an error code otherwise + +--*/ + +{ + ULONG allocationSize; + PVOID buffer; + ULONG bufferLength; + PDEVICE_DATA_SET_RANGE dataSetRanges; + ULONG dataSetRangesCount; + PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes; + ULONGLONG entireXferLen; + PFUNCTIONAL_DEVICE_EXTENSION fdoExt; + ULONG i; + ULONGLONG logicalBlockOffset; + ULONG maxBlockDescrCount; + ULONGLONG maxLbaCount; + POFFLOAD_WRITE_CONTEXT offloadWriteContext; + PDEVICE_DSM_OFFLOAD_WRITE_PARAMETERS offloadWriteParameters; + ULONG receiveTokenInformationBufferLength; + NTSTATUS status; + NTSTATUS tempStatus; + BOOLEAN tokenInvalidated; + ULONG tokenOperationBufferLength; + PMDL writeUsingTokenMdl; + + PAGED_CODE(); + + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_IOCTL, + "ClasspServiceWriteUsingTokenTransferRequest (%p): Entering function. Irp %p.\n", + Fdo, + Irp)); + + fdoExt = Fdo->DeviceExtension; + status = STATUS_SUCCESS; + tempStatus = STATUS_SUCCESS; + dsmAttributes = Irp->AssociatedIrp.SystemBuffer; + buffer = NULL; + writeUsingTokenMdl = NULL; + offloadWriteParameters = Add2Ptr(dsmAttributes, dsmAttributes->ParameterBlockOffset); + dataSetRanges = Add2Ptr(dsmAttributes, dsmAttributes->DataSetRangesOffset); + dataSetRangesCount = dsmAttributes->DataSetRangesLength / sizeof(DEVICE_DATA_SET_RANGE); + logicalBlockOffset = offloadWriteParameters->TokenOffset / fdoExt->DiskGeometry.BytesPerSector; + tokenInvalidated = FALSE; + bufferLength = 0; + + + NT_ASSERT(fdoExt->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits && + NT_SUCCESS(fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus)); + + for (i = 0, entireXferLen = 0; i < dataSetRangesCount; i++) { + entireXferLen += dataSetRanges[i].LengthInBytes; + } + + // + // We need to split the write request based on the following hardware limitations: + // 1. The size of the data buffer containing the TokenOperation command's parameters must + // not exceed the MaximumTransferLength (and max physical pages) of the underlying + // adapter. + // 2. The number of descriptors specified in the TokenOperation command must not exceed + // the MaximumRangeDescriptors. + // 3. The cumulative total of the number of transfer blocks in all the descriptors in + // the TokenOperation command must not exceed the MaximumTokenTransferSize. + // + // In addition to the above, we need to ensure that for each of the descriptors in the + // TokenOperation command: + // 1. The number of blocks specified is an exact multiple of the OptimalTransferLengthGranularity. + // 2. The number of blocks specified is limited to the MaximumTransferLength. (We shall + // however, limit the number of blocks specified in each descriptor to be a maximum of + // OptimalTransferLength or MaximumTransferLength, whichever is lesser). + // + // Finally, we shall always send down the WriteUsingToken command using IMMED = 0 for this + // release. This makes it simpler to handle multi-initiator scenarios since we won't need + // to deal with the WriteUsingToken (with IMMED = 1) succeeding but ReceiveRODTokenInformation + // failing due to the path/node failing, thus making it impossible to retrieve results of + // the data transfer operation, or to cancel the data transfer via CopyOperationAbort. + // Since a write data transfer of a large amount of data may take a long time when sent + // down IMMED = 0, we shall limit the size to a maximum of 256MB even if it means truncating + // the original requested size to this capped size. The application is expected to deal with + // this truncation. + // (NOTE: the cap of 256MB is chosen to match with the Copy Engine's chunk size). + // + // The LBA ranges is in DEVICE_DATA_SET_RANGE format, it needs to be converted into + // WINDOWS_BLOCK_DEVICE_RANGE_DESCRIPTOR Block Descriptors. + // + + ClasspGetTokenOperationCommandBufferLength(Fdo, + SERVICE_ACTION_WRITE_USING_TOKEN, + &bufferLength, + &tokenOperationBufferLength, + &receiveTokenInformationBufferLength); + + allocationSize = sizeof(OFFLOAD_WRITE_CONTEXT) + bufferLength; + + offloadWriteContext = ExAllocatePoolWithTag( + NonPagedPoolNx, + allocationSize, + CLASSPNP_POOL_TAG_TOKEN_OPERATION); + + if (!offloadWriteContext) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspServiceWriteUsingTokenTransferRequest (%p): Failed to allocate buffer for WriteUsingToken operations.\n", + Fdo)); + + status = STATUS_INSUFFICIENT_RESOURCES; + goto __ClasspServiceWriteUsingTokenTransferRequest_ErrorExit; + } + + // + // Only zero the context portion here. The buffer portion is zeroed for + // each sub-request. + // + RtlZeroMemory(offloadWriteContext, sizeof(OFFLOAD_WRITE_CONTEXT)); + + offloadWriteContext->Fdo = Fdo; + offloadWriteContext->OffloadWriteDsmIrp = Irp; + offloadWriteContext->OperationStartTime = KeQueryInterruptTime(); + + // + // The buffer for the commands is after the offloadWriteContext. + // + buffer = (offloadWriteContext + 1); + + // + // Set up fields that allow iterating through whole request, by issuing sub- + // requests which each do some of the writing. Because of truncation by the + // target, it's not known exactly how many bytes will be written by the + // target in each sub-request (can be less than requested), so the + // progress through the outer request must only commit the move through the + // upper DSM ranges when a lower request is done and the number of written + // sectors is known. + // + + NT_ASSERT(offloadWriteContext->TotalSectorsProcessedSuccessfully == 0); + offloadWriteContext->TotalRequestSizeSectors = entireXferLen / fdoExt->DiskGeometry.BytesPerSector; + NT_ASSERT(offloadWriteContext->DataSetRangeIndex == 0); + NT_ASSERT(offloadWriteContext->DataSetRangeByteOffset == 0); + offloadWriteContext->DataSetRangesCount = dataSetRangesCount; + + offloadWriteContext->DsmAttributes = dsmAttributes; + offloadWriteContext->OffloadWriteParameters = offloadWriteParameters; + offloadWriteContext->DataSetRanges = dataSetRanges; + offloadWriteContext->LogicalBlockOffset = logicalBlockOffset; + + ClasspGetTokenOperationDescriptorLimits(Fdo, + SERVICE_ACTION_WRITE_USING_TOKEN, + tokenOperationBufferLength, + &maxBlockDescrCount, + &maxLbaCount); + + if (fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount && fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumTokenTransferSize) { + + NT_ASSERT(fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount <= fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumTokenTransferSize); + } + + // + // We will limit the maximum data transfer in an offload write operation to: + // - 64MB if OptimalTransferCount = 0 + // - OptimalTransferCount if < 256MB + // - 256MB if OptimalTransferCount >= 256MB + // - MaximumTokenTransferSize if lesser than above chosen size + // + if (0 == fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount) { + + maxLbaCount = MIN(maxLbaCount, (DEFAULT_MAX_NUMBER_BYTES_PER_SYNC_WRITE_USING_TOKEN / (ULONGLONG)fdoExt->DiskGeometry.BytesPerSector)); + + } else if (fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount < (MAX_NUMBER_BYTES_PER_SYNC_WRITE_USING_TOKEN / (ULONGLONG)fdoExt->DiskGeometry.BytesPerSector)) { + + maxLbaCount = MIN(maxLbaCount, fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount); + + } else { + + maxLbaCount = MIN(maxLbaCount, (MAX_NUMBER_BYTES_PER_SYNC_WRITE_USING_TOKEN / (ULONGLONG)fdoExt->DiskGeometry.BytesPerSector)); + } + + // + // Since we do not want very fragmented files to end up causing the WriteUsingToken command to take + // too long (and potentially timeout), we will limit the max number of descriptors sent down in a + // command. + // + maxBlockDescrCount = MIN(maxBlockDescrCount, MAX_NUMBER_BLOCK_DEVICE_DESCRIPTORS); + + TracePrint((TRACE_LEVEL_INFORMATION, + TRACE_FLAG_IOCTL, + "ClasspServiceWriteUsingTokenTransferRequest (%p): Using MaxBlockDescrCount %u and MaxLbaCount %I64u.\n", + Fdo, + maxBlockDescrCount, + maxLbaCount)); + + offloadWriteContext->MaxBlockDescrCount = maxBlockDescrCount; + offloadWriteContext->MaxLbaCount = maxLbaCount; + + // + // Reuse a single buffer for both TokenOperation and ReceiveTokenInformation. This has the one disadvantage + // that we'll be marking the page(s) as IoWriteAccess even though we only need read access for token + // operation command and we may not even need to send down a ReceiveTokenInformation (in case of a successful + // synchronous transfer). However, the advantage is that we eliminate the possibility of any potential + // failures when trying to allocate an MDL for the ReceiveTokenInformation later on if we need to send it. + // + writeUsingTokenMdl = ClasspBuildDeviceMdl(buffer, bufferLength, FALSE); + if (!writeUsingTokenMdl) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspServiceWriteUsingTokenTransferRequest (%p): Failed to allocate MDL for WriteUsingToken operations.\n", + Fdo)); + + status = STATUS_INSUFFICIENT_RESOURCES; + goto __ClasspServiceWriteUsingTokenTransferRequest_ErrorExit; + } + + offloadWriteContext->WriteUsingTokenMdl = writeUsingTokenMdl; + + // + // There are potentially two approaches that we can take: + // 1. Determine how many transfer packets we need (in case we need to split the request), get + // them all up-front and then send down all the split WriteUsingToken commands in parallel. + // The benefit of this approach is that the performance will be improved in the success case. + // But error case handling becomes very complex to handle, since if one of the intermediate + // write fails, there is no way to cancel the remaining writes that were sent. Waiting for + // such requests to complete in geo-distributed source and target cases can be very time + // consuming. The complexity gets worse in the case that the target succeeds only a partial + // amount of data for one the of intermediate split commands. + // [OR] + // 2. Until the entire data set range is processed, build the command for as much of the range as + // possible, send down a packet, and once it completes, repeat sequentially in a loop. + // The advantage of this approach is its simplistic nature. In the success case, it will + // be less performant as compared to the previous approach, but since the gain of offload + // copy is so significant compared to native buffered-copy, the tradeoff is acceptable. + // In the failure case the simplicity offers the following benefit - if any command fails, + // there is no further processing that is needed. And the cumulative total bytes that succeeded + // (until the failing split WriteUsingToken command) is easily tracked. + // + // Given the above, we're going with the second approach. + // + + NT_ASSERT(status == STATUS_SUCCESS); // so far + + // + // Save (into the offloadReadContext) any remaining things that + // ClasspPopulateTokenTransferPacketDone() will need. + // + + offloadWriteContext->BufferLength = bufferLength; + offloadWriteContext->ReceiveTokenInformationBufferLength = receiveTokenInformationBufferLength; + offloadWriteContext->EntireXferLen = entireXferLen; + + IoMarkIrpPending(Irp); + ClasspContinueOffloadWrite(offloadWriteContext); + + status = STATUS_PENDING; + goto __ClasspServiceWriteUsingTokenTransferRequest_Exit; + + // + // Error label only - not used in success case: + // + +__ClasspServiceWriteUsingTokenTransferRequest_ErrorExit: + + NT_ASSERT(status != STATUS_PENDING); + + if (offloadWriteContext != NULL) { + ClasspCleanupOffloadWriteContext(offloadWriteContext); + offloadWriteContext = NULL; + } + +__ClasspServiceWriteUsingTokenTransferRequest_Exit: + + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_IOCTL, + "ClasspServiceWriteUsingTokenTransferRequest (%p): Exiting function (Irp %p) with status %x.\n", + Fdo, + Irp, + status)); + + return status; +} + + +VOID +#pragma warning(suppress: 28194) // This function will either alias or free OffloadWriteContext +ClasspContinueOffloadWrite( + _In_ __drv_aliasesMem POFFLOAD_WRITE_CONTEXT OffloadWriteContext + ) + +/*++ + +Routine description: + + This routine continues an offload write operation. This routine expects the + offload write operation to be set up and ready to start a WRITE USING TOKEN, + but with no WRITE USING TOKEN currently in flight. + + This routine is responsible for continuing or completing the offload write + operation. + +Arguments: + + OffloadWriteContext - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload + write operation. + +Return Value: + + None. + +--*/ + +{ + BOOLEAN allDataSetRangeFullyConverted; + ULONG blockDescrIndex; + PBLOCK_DEVICE_RANGE_DESCRIPTOR blockDescrPointer; + PVOID buffer; + ULONG bufferLength; + ULONGLONG dataSetRangeByteOffset; + ULONG dataSetRangeIndex; + PDEVICE_DATA_SET_RANGE dataSetRanges; + ULONG dataSetRangesCount; + ULONGLONG entireXferLen; + PDEVICE_OBJECT fdo; + PFUNCTIONAL_DEVICE_EXTENSION fdoExt; + PIRP irp; + ULONG lbaCount; + ULONG listIdentifier; + ULONGLONG logicalBlockOffset; + ULONG maxBlockDescrCount; + ULONGLONG maxLbaCount; + PDEVICE_DSM_OFFLOAD_WRITE_PARAMETERS offloadWriteParameters; + PTRANSFER_PACKET pkt; + PIRP pseudoIrp; + NTSTATUS status; + DEVICE_DATA_SET_RANGE tempDataSetRange; + BOOLEAN tempDataSetRangeFullyConverted; + PVOID tokenAscii; + ULONG tokenSize; + ULONGLONG totalSectorCount; + ULONGLONG totalSectorsProcessedSuccessfully; + ULONGLONG totalSectorsToProcess; + ULONG transferSize; + USHORT writeUsingTokenDataLength; + USHORT writeUsingTokenDescriptorsLength; + PMDL writeUsingTokenMdl; + + tokenAscii = NULL; + tokenSize = BLOCK_DEVICE_TOKEN_SIZE; + tempDataSetRangeFullyConverted = FALSE; + allDataSetRangeFullyConverted = FALSE; + fdo = OffloadWriteContext->Fdo; + fdoExt = fdo->DeviceExtension; + irp = OffloadWriteContext->OffloadWriteDsmIrp; + buffer = OffloadWriteContext + 1; + bufferLength = OffloadWriteContext->BufferLength; + dataSetRanges = OffloadWriteContext->DataSetRanges; + offloadWriteParameters = OffloadWriteContext->OffloadWriteParameters; + pseudoIrp = &OffloadWriteContext->PseudoIrp; + writeUsingTokenMdl = OffloadWriteContext->WriteUsingTokenMdl; + entireXferLen = OffloadWriteContext->EntireXferLen; + + RtlZeroMemory(buffer, bufferLength); + + pkt = DequeueFreeTransferPacket(fdo, TRUE); + if (!pkt){ + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspContinueOffloadWrite (%p): Failed to retrieve transfer packet for TokenOperation (WriteUsingToken) operation.\n", + fdo)); + + status = STATUS_INSUFFICIENT_RESOURCES; + goto __ClasspContinueOffloadWrite_ErrorExit; + } + + OffloadWriteContext->Pkt = pkt; + + blockDescrPointer = (PBLOCK_DEVICE_RANGE_DESCRIPTOR) + &((PWRITE_USING_TOKEN_HEADER)buffer)->BlockDeviceRangeDescriptor[0]; + + blockDescrIndex = 0; + lbaCount = 0; + + totalSectorsToProcess = 0; + + maxBlockDescrCount = OffloadWriteContext->MaxBlockDescrCount; + maxLbaCount = OffloadWriteContext->MaxLbaCount; + + // + // The OffloadWriteContext->DataSetRangeIndex, DataSetRangeByteOffset, and + // TotalSectorsProcessedSuccessfully don't move forward until RRTI has + // reported the actual amount written. + // + // For that reason, this function only updates + // OffloadWriteContext->TotalSectorsProcessed, which tracks the number of + // sectors requested to be written by the current WRITE USING TOKEN command + // (not all will necessarily be written). + // + + dataSetRangeIndex = OffloadWriteContext->DataSetRangeIndex; + dataSetRangesCount = OffloadWriteContext->DataSetRangesCount; + dataSetRangeByteOffset = OffloadWriteContext->DataSetRangeByteOffset; + totalSectorsProcessedSuccessfully = OffloadWriteContext->TotalSectorsProcessedSuccessfully; + + // + // Send WriteUsingToken commands when the buffer is full or all input entries are converted. + // + while (!((blockDescrIndex == maxBlockDescrCount) || // buffer full or block descriptor count reached + (lbaCount == maxLbaCount) || // block LBA count reached + (allDataSetRangeFullyConverted))) { // all DataSetRanges have been converted + + NT_ASSERT(dataSetRangeIndex < dataSetRangesCount); + NT_ASSERT(dataSetRangeByteOffset < dataSetRanges[dataSetRangeIndex].LengthInBytes); + + tempDataSetRange.StartingOffset = dataSetRanges[dataSetRangeIndex].StartingOffset + dataSetRangeByteOffset; + tempDataSetRange.LengthInBytes = dataSetRanges[dataSetRangeIndex].LengthInBytes - dataSetRangeByteOffset; + + totalSectorCount = 0; + + ClasspConvertDataSetRangeToBlockDescr(fdo, + blockDescrPointer, + &blockDescrIndex, + maxBlockDescrCount, + &lbaCount, + maxLbaCount, + &tempDataSetRange, + &totalSectorCount); + + tempDataSetRangeFullyConverted = (tempDataSetRange.LengthInBytes == 0) ? TRUE : FALSE; + + allDataSetRangeFullyConverted = tempDataSetRangeFullyConverted && ((dataSetRangeIndex + 1) == dataSetRangesCount); + + if (tempDataSetRangeFullyConverted) { + dataSetRangeIndex += 1; + dataSetRangeByteOffset = 0; + NT_ASSERT(dataSetRangeIndex <= dataSetRangesCount); + } else { + dataSetRangeByteOffset += totalSectorCount * fdoExt->DiskGeometry.BytesPerSector; + NT_ASSERT(dataSetRangeByteOffset < dataSetRanges[dataSetRangeIndex].LengthInBytes); + } + + totalSectorsToProcess += totalSectorCount; + } + + // + // Save the number of sectors being attempted in this WRITE USING TOKEN + // command, so that a success return from the command will know how much + // was written, without needing to issue a RECEIVE ROD TOKEN INFORMATION + // command. + // + OffloadWriteContext->TotalSectorsToProcess = totalSectorsToProcess; + OffloadWriteContext->TotalSectorsProcessed = 0; + + // + // Calculate transfer size, including the header + // + transferSize = (blockDescrIndex * sizeof(BLOCK_DEVICE_RANGE_DESCRIPTOR)) + FIELD_OFFSET(WRITE_USING_TOKEN_HEADER, BlockDeviceRangeDescriptor); + + NT_ASSERT(transferSize <= MAX_TOKEN_OPERATION_PARAMETER_DATA_LENGTH); + + writeUsingTokenDataLength = (USHORT)transferSize - RTL_SIZEOF_THROUGH_FIELD(WRITE_USING_TOKEN_HEADER, WriteUsingTokenDataLength); + REVERSE_BYTES_SHORT(((PWRITE_USING_TOKEN_HEADER)buffer)->WriteUsingTokenDataLength, &writeUsingTokenDataLength); + + ((PWRITE_USING_TOKEN_HEADER)buffer)->Immediate = 0; + + logicalBlockOffset = OffloadWriteContext->LogicalBlockOffset + totalSectorsProcessedSuccessfully; + REVERSE_BYTES_QUAD(((PWRITE_USING_TOKEN_HEADER)buffer)->BlockOffsetIntoToken, &logicalBlockOffset); + + RtlCopyMemory(((PWRITE_USING_TOKEN_HEADER)buffer)->Token, + &offloadWriteParameters->Token, + BLOCK_DEVICE_TOKEN_SIZE); + + writeUsingTokenDescriptorsLength = (USHORT)transferSize - FIELD_OFFSET(WRITE_USING_TOKEN_HEADER, BlockDeviceRangeDescriptor); + REVERSE_BYTES_SHORT(((PWRITE_USING_TOKEN_HEADER)buffer)->BlockDeviceRangeDescriptorListLength, &writeUsingTokenDescriptorsLength); + + RtlZeroMemory(pseudoIrp, sizeof(IRP)); + + pseudoIrp->IoStatus.Status = STATUS_SUCCESS; + pseudoIrp->IoStatus.Information = 0; + pseudoIrp->Tail.Overlay.DriverContext[0] = LongToPtr(1); + pseudoIrp->MdlAddress = writeUsingTokenMdl; + + InterlockedCompareExchange((volatile LONG *)&TokenOperationListIdentifier, -1, MaxTokenOperationListIdentifier); + listIdentifier = InterlockedIncrement((volatile LONG *)&TokenOperationListIdentifier); + + ClasspSetupWriteUsingTokenTransferPacket( + OffloadWriteContext, + pkt, + transferSize, + (PUCHAR)buffer, + pseudoIrp, + listIdentifier); + + tokenAscii = ClasspBinaryToAscii((PUCHAR)(((PWRITE_USING_TOKEN_HEADER)buffer)->Token), + tokenSize, + &tokenSize); + + TracePrint((TRACE_LEVEL_INFORMATION, + TRACE_FLAG_IOCTL, + "ClasspContinueOffloadWrite (%p): Offloading write for %I64u bytes (versus %I64u) [via %u descriptors]. \ + \n\t\t\tDataLength: %u, DescriptorsLength: %u. Pkt %p (list id %x) [Token: %s]\n", + fdo, + totalSectorsToProcess * fdoExt->DiskGeometry.BytesPerSector, + entireXferLen, + blockDescrIndex, + writeUsingTokenDataLength, + writeUsingTokenDescriptorsLength, + pkt, + listIdentifier, + (tokenAscii == NULL) ? "" : tokenAscii)); + + FREE_POOL(tokenAscii); + + OffloadWriteContext->ListIdentifier = listIdentifier; + + SubmitTransferPacket(pkt); + + // + // ClasspWriteUsingTokenTransferPacketDone() takes care of completing the + // IRP, so this function is done. + // + + return; + + // + // Error cleaup label only - not used in success case: + // + +__ClasspContinueOffloadWrite_ErrorExit: + + NT_ASSERT(!NT_SUCCESS(status)); + + // + // ClasspCompleteOffloadWrite also cleans up offloadWriteContext. + // + + ClasspCompleteOffloadWrite(OffloadWriteContext, status); + + return; +} + + +VOID +ClasspAdvanceOffloadWritePosition( + _In_ POFFLOAD_WRITE_CONTEXT OffloadWriteContext, + _In_ ULONGLONG SectorsToAdvance + ) + +/*++ + +Routine description: + + After the target has responded to WRITE USING TOKEN with success, or RRTI + with a specific TRANSFER COUNT, this routine is used to update the relative + position within the overall offload write request. This position includes + the TotalSectorsProcessedSuccessfully, the DataSetRangeIndex, and the + DataSetRangeByteOffset. + + The caller is responsible for continuing or completing the offload write + operation (this routine doesn't do that). + +Arguments: + + OffloadWriteContext - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload + write operation. + + SectorsToAdvance - The number of sectors which were just written + successfully (not the total for the offload write operation overall, + just the number done by the most recent WRITE USING TOKEN). + +Return Value: + + None. + +--*/ + +{ + ULONGLONG bytesToAdvance; + ULONGLONG bytesToDo; + PULONGLONG dataSetRangeByteOffset; + PULONG dataSetRangeIndex; + PDEVICE_DATA_SET_RANGE dataSetRanges; + PDEVICE_OBJECT fdo; + PFUNCTIONAL_DEVICE_EXTENSION fdoExt; + PULONGLONG totalSectorsProcessedSuccessfully; + + fdo = OffloadWriteContext->Fdo; + fdoExt = fdo->DeviceExtension; + dataSetRanges = OffloadWriteContext->DataSetRanges; + dataSetRangeByteOffset = &OffloadWriteContext->DataSetRangeByteOffset; + dataSetRangeIndex = &OffloadWriteContext->DataSetRangeIndex; + totalSectorsProcessedSuccessfully = &OffloadWriteContext->TotalSectorsProcessedSuccessfully; + bytesToAdvance = SectorsToAdvance * fdoExt->DiskGeometry.BytesPerSector; + + (*totalSectorsProcessedSuccessfully) += SectorsToAdvance; + NT_ASSERT((*totalSectorsProcessedSuccessfully) <= OffloadWriteContext->TotalRequestSizeSectors); + + while (bytesToAdvance != 0) { + bytesToDo = dataSetRanges[*dataSetRangeIndex].LengthInBytes - *dataSetRangeByteOffset; + if (bytesToDo > bytesToAdvance) { + bytesToDo = bytesToAdvance; + } + (*dataSetRangeByteOffset) += bytesToDo; + bytesToAdvance -= bytesToDo; + if ((*dataSetRangeByteOffset) == dataSetRanges[*dataSetRangeIndex].LengthInBytes) { + (*dataSetRangeIndex) += 1; + (*dataSetRangeByteOffset) = 0; + } + } + + NT_ASSERT((*dataSetRangeIndex) <= OffloadWriteContext->DataSetRangesCount); + + return; +} + + +VOID +ClasspWriteUsingTokenTransferPacketDone( + _In_ PVOID Context + ) + +/*++ + +Routine description: + + This routine continues an offload write operation when the WRITE USING + TOKEN transfer packet completes. + + This routine may be able to determine that all requested sectors were + written if the WRITE USING TOKEN completed with success, or may need to + issue a RECEIVE ROD TOKEN INFORMATION if the WRITE USING TOKEN indicated + check condition. + + This routine is responsible for continuing or completing the offload write + operation. + +Arguments: + + Context - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload write + operation. + +Return Value: + + None. + +--*/ + +{ + ULONGLONG entireXferLen; + PDEVICE_OBJECT fdo; + PFUNCTIONAL_DEVICE_EXTENSION fdoExt; + ULONG listIdentifier; + POFFLOAD_WRITE_CONTEXT offloadWriteContext; + PTRANSFER_PACKET pkt; + PIRP pseudoIrp; + NTSTATUS status; + PBOOLEAN tokenInvalidated; + ULONGLONG totalSectorsToProcess; + + offloadWriteContext = Context; + pseudoIrp = &offloadWriteContext->PseudoIrp; + fdo = offloadWriteContext->Fdo; + fdoExt = fdo->DeviceExtension; + listIdentifier = offloadWriteContext->ListIdentifier; + totalSectorsToProcess = offloadWriteContext->TotalSectorsToProcess; + entireXferLen = offloadWriteContext->EntireXferLen; + tokenInvalidated = &offloadWriteContext->TokenInvalidated; + pkt = offloadWriteContext->Pkt; + + offloadWriteContext->Pkt = NULL; + + + status = pseudoIrp->IoStatus.Status; + NT_ASSERT(status != STATUS_PENDING); + + // + // If the request failed with any of the following errors, then it is meaningless to send + // down a ReceiveTokenInformation (regardless of whether the transfer was requested as sync + // or async), since the target has no saved information about the command: + // - STATUS_INVALID_TOKEN + // - STATUS_INVALID_PARAMETER + // + if (status == STATUS_INVALID_PARAMETER || + status == STATUS_INVALID_TOKEN) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspWriteUsingTokenTransferPacketDone (%p): Write failed with %x (list id %x).\n", + fdo, + status, + listIdentifier)); + + // + // If the token isn't valid any longer, we should let the upper layers know so that + // they don't waste time retrying the write with the same token. + // + if (status == STATUS_INVALID_TOKEN) { + + *tokenInvalidated = TRUE; + } + + NT_ASSERT(status != STATUS_PENDING && !NT_SUCCESS(status)); + goto __ClasspWriteUsingTokenTransferPacketDone_Exit; + + } else if ((NT_SUCCESS(status)) && + (pkt->Srb->SrbStatus == SRB_STATUS_SUCCESS || pkt->TransferCount != 0)) { + + // + // If the TokenOperation command was sent to the target requesting synchronous data + // transfer, a success indicates that the command is complete. + // This could either be because of a successful completion of the entire transfer + // or because of a partial transfer due to target truncation. If it is the latter, + // and the information field of the sense data has returned the TransferCount, we + // can avoid sending down an RRTI. + // + if (pkt->Srb->SrbStatus == SRB_STATUS_SUCCESS) { + + // + // The entire transfer has completed successfully. + // + offloadWriteContext->TotalSectorsProcessed = totalSectorsToProcess; + TracePrint((TRACE_LEVEL_INFORMATION, + TRACE_FLAG_IOCTL, + "ClasspWriteUsingTokenTransferPacketDone (%p): Successfully wrote using token %I64u (out of %I64u) bytes (list Id %x).\n", + fdo, + totalSectorsToProcess * fdoExt->DiskGeometry.BytesPerSector, + entireXferLen, + listIdentifier)); + } else { + + // + // The target has returned how much data it transferred in the response to the + // WUT command itself, allowing us to optimize by removing the necessaity for + // sending down an RRTI to query the TransferCount. + // + NT_ASSERT(pkt->TransferCount); + + offloadWriteContext->TotalSectorsProcessed = totalSectorsToProcess = pkt->TransferCount; + TracePrint((TRACE_LEVEL_INFORMATION, + TRACE_FLAG_IOCTL, + "ClasspWriteUsingTokenTransferPacketDone (%p): Target truncated write using token %I64u (out of %I64u) bytes (list Id %x).\n", + fdo, + totalSectorsToProcess * fdoExt->DiskGeometry.BytesPerSector, + entireXferLen, + listIdentifier)); + } + + ClasspAdvanceOffloadWritePosition(offloadWriteContext, totalSectorsToProcess); + + NT_ASSERT(status != STATUS_PENDING); + + // + // ClasspReceiveWriteUsingTokenInformationDone() takes care of + // completing the operation (eventually), so pending from point of view + // of this function. + // + + ClasspReceiveWriteUsingTokenInformationDone(offloadWriteContext, status); + status = STATUS_PENDING; + + goto __ClasspWriteUsingTokenTransferPacketDone_Exit; + + } else { + + // + // Since the TokenOperation was failed (or the target truncated the transfer but + // didn't indicate the amount), we need to send down ReceiveTokenInformation. + // + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspWriteUsingTokenTransferPacketDone (%p): Write failed with status %x, %x (list id %x).\n", + fdo, + status, + pkt->Srb->SrbStatus, + listIdentifier)); + + ClasspReceiveWriteUsingTokenInformation(offloadWriteContext); + + status = STATUS_PENDING; + goto __ClasspWriteUsingTokenTransferPacketDone_Exit; + } + +__ClasspWriteUsingTokenTransferPacketDone_Exit: + + if (status != STATUS_PENDING) { + ClasspCompleteOffloadWrite(offloadWriteContext, status); + } + + return; +} + + +VOID +ClasspReceiveWriteUsingTokenInformationDone( + _In_ POFFLOAD_WRITE_CONTEXT OffloadWriteContext, + _In_ NTSTATUS CompletionCausingStatus + ) + +/*++ + +Routine description: + + This routine continues an offload write operation when a WRITE USING TOKEN + and possible associated RECEIVE ROD TOKEN INFORMATION have both fully + completed and the RRTI has indicated completion of the WUT. + + This routine checks to see if the total sectors written is already equal to + the overall total requested sector count, and if so, completes the offload + write operation. If not, this routine continues the operation by issuing + another WUT. + + This routine is responsible for continuing or completing the offload write + operation. + +Arguments: + + OffloadWriteContext - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload + write operation. + +Return Value: + + None. + +--*/ + +{ + NT_ASSERT( + OffloadWriteContext->TotalSectorsProcessedSuccessfully <= + OffloadWriteContext->TotalRequestSizeSectors); + + // Time taken in 100 ns units + ULONGLONG durationIn100ns = (KeQueryInterruptTime() - OffloadWriteContext->OperationStartTime); + + + if (OffloadWriteContext->TotalSectorsProcessedSuccessfully == OffloadWriteContext->TotalRequestSizeSectors) { + + ClasspCompleteOffloadWrite(OffloadWriteContext, CompletionCausingStatus); + + goto __ClasspReceiveWriteUsingTokenInformationDone_Exit; + } + + // + // Since we don't want a layered timeout mechanism (e.g. guest and parent OS in Hyper-V scenarios) + // to cause a SCSI timeout for the higher layer token operations. + // + if (MAX_TARGET_DURATION <= durationIn100ns) { + + TracePrint((TRACE_LEVEL_WARNING, + TRACE_FLAG_IOCTL, + "ClasspReceiveWriteUsingTokenInformationDone (%p): Truncating write (list id %x) because of max-duration-rule.\n", + OffloadWriteContext->Fdo, + OffloadWriteContext->ListIdentifier)); + + // + // We could technically pass in STATUS_IO_OPERATION_TIMEOUT, but ClasspCompleteOffloadWrite + // won't end up doing anything (useful) with this status, since some bytes would already + // have been transferred. + // + ClasspCompleteOffloadWrite(OffloadWriteContext, STATUS_UNSUCCESSFUL); + + goto __ClasspReceiveWriteUsingTokenInformationDone_Exit; + } + + NT_ASSERT( + OffloadWriteContext->TotalSectorsProcessedSuccessfully < + OffloadWriteContext->TotalRequestSizeSectors); + + // + // Keep going with the next sub-request. + // + + ClasspContinueOffloadWrite(OffloadWriteContext); + +__ClasspReceiveWriteUsingTokenInformationDone_Exit: + + return; +} + + +VOID +ClasspCompleteOffloadWrite( + _In_ __drv_freesMem(Mem) POFFLOAD_WRITE_CONTEXT OffloadWriteContext, + _In_ NTSTATUS CompletionCausingStatus + ) + +/*++ + +Routine description: + + This routine is used to complete an offload write operation. + + The input CompletionCausingStatus doesn't necessarily drive the completion + status of the offload write operation overall, if the offload write + operation overall has previously written some sectors successfully. + + This routine completes the offload write operation. + +Arguments: + + OffloadWriteContext - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload + write operation. + + CompletionCausingStatus - Status code indicating a reason that the offload + write operation is completing. For success this will be STATUS_SUCCESS, + but for a failure, this status will indicate what failure occurred. + This status doesn't directly propagate to the completion status of the + overall offload write operation if this status is failure and the + overall offload write operation has already previously written some + sectors successfully. + +Return Value: + + None. + +--*/ + +{ + PDEVICE_OBJECT fdo; + PFUNCTIONAL_DEVICE_EXTENSION fdoExt; + PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes; + PULONGLONG totalSectorsProcessedSuccessfully; + ULONGLONG entireXferLen; + PIRP irp; + PBOOLEAN tokenInvalidated; + ULONG listIdentifier; + ULONGLONG totalSectorsProcessed; + NTSTATUS status; + ULONGLONG totalBytesProcessed; + + fdo = OffloadWriteContext->Fdo; + fdoExt = fdo->DeviceExtension; + dsmAttributes = OffloadWriteContext->DsmAttributes; + totalSectorsProcessedSuccessfully = &OffloadWriteContext->TotalSectorsProcessedSuccessfully; + entireXferLen = OffloadWriteContext->EntireXferLen; + irp = OffloadWriteContext->OffloadWriteDsmIrp; + tokenInvalidated = &OffloadWriteContext->TokenInvalidated; + listIdentifier = OffloadWriteContext->ListIdentifier; + totalSectorsProcessed = OffloadWriteContext->TotalSectorsProcessed; + status = CompletionCausingStatus; + + ((PSTORAGE_OFFLOAD_WRITE_OUTPUT)dsmAttributes)->OffloadWriteFlags = 0; + ((PSTORAGE_OFFLOAD_WRITE_OUTPUT)dsmAttributes)->Reserved = 0; + + totalBytesProcessed = (*totalSectorsProcessedSuccessfully) * fdoExt->DiskGeometry.BytesPerSector; + + TracePrint((totalBytesProcessed == entireXferLen ? TRACE_LEVEL_INFORMATION : TRACE_LEVEL_WARNING, + TRACE_FLAG_IOCTL, + "ClasspCompleteOffloadWrite (%p): %ws wrote using token %I64u (out of %I64u) bytes (Irp %p).\n", + fdo, + NT_SUCCESS(status) ? L"Successful" : L"Failed", + totalBytesProcessed, + entireXferLen, + irp)); + + if (totalBytesProcessed > 0 && totalBytesProcessed < entireXferLen) { + SET_FLAG(((PSTORAGE_OFFLOAD_WRITE_OUTPUT)dsmAttributes)->OffloadWriteFlags, STORAGE_OFFLOAD_WRITE_RANGE_TRUNCATED); + } + if (*tokenInvalidated) { + SET_FLAG(((PSTORAGE_OFFLOAD_WRITE_OUTPUT)dsmAttributes)->OffloadWriteFlags, STORAGE_OFFLOAD_TOKEN_INVALID); + } + ((PSTORAGE_OFFLOAD_WRITE_OUTPUT)dsmAttributes)->LengthCopied = totalBytesProcessed; + + + if (!NT_SUCCESS(status)) { + + TracePrint((TRACE_LEVEL_WARNING, + TRACE_FLAG_IOCTL, + "ClasspCompleteOffloadWrite (%p): TokenOperation for WriteUsingToken (list Id %u) completed with %x writing %I64u blocks (currentTotal %I64u blocks).\n", + fdo, + listIdentifier, + status, + totalSectorsProcessed, + *totalSectorsProcessedSuccessfully)); + + // + // Even if target returned an error, from the OS upper layers' perspective, + // it is a success (with truncation) if any data at all was written. + // + if (*totalSectorsProcessedSuccessfully) { + status = STATUS_SUCCESS; + } + } + + irp->IoStatus.Information = sizeof(STORAGE_OFFLOAD_WRITE_OUTPUT); + + ClasspCompleteOffloadRequest(fdo, irp, status); + ClasspCleanupOffloadWriteContext(OffloadWriteContext); + OffloadWriteContext = NULL; + + return; +} + + +VOID +ClasspCleanupOffloadWriteContext( + _In_ __drv_freesMem(mem) POFFLOAD_WRITE_CONTEXT OffloadWriteContext + ) + +/*++ + +Routine description: + + This routine cleans up an offload write context. + +Arguments: + + OffloadWriteContext - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload + write operation. + +Return Value: + + None. + +--*/ + +{ + PMDL writeUsingTokenMdl = OffloadWriteContext->WriteUsingTokenMdl; + + if (writeUsingTokenMdl) { + ClasspFreeDeviceMdl(writeUsingTokenMdl); + } + FREE_POOL(OffloadWriteContext); + + return; +} + + +_IRQL_requires_same_ +VOID +ClasspReceiveWriteUsingTokenInformation( + _In_ POFFLOAD_WRITE_CONTEXT OffloadWriteContext + ) + +/*++ + +Routine description: + + This routine retrieves the token after a WriteUsingToken command + has been sent down in case of an error or if there is a need to + poll for the result. + + This routine is responsible for continuing or completing the offload write + operation. + +Arguments: + + OffloadWriteContext - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload + write operation. + +Return Value: + + None. + +--*/ + +{ + PVOID buffer; + ULONG bufferLength; + ULONG cdbLength; + PDEVICE_OBJECT fdo; + PIRP irp; + ULONG listIdentifier; + PTRANSFER_PACKET pkt; + PIRP pseudoIrp; + ULONG receiveTokenInformationBufferLength; + PSCSI_REQUEST_BLOCK srb; + NTSTATUS status; + ULONG tempSizeUlong; + PMDL writeUsingTokenMdl; + + fdo = OffloadWriteContext->Fdo; + irp = OffloadWriteContext->OffloadWriteDsmIrp; + pseudoIrp = &OffloadWriteContext->PseudoIrp; + buffer = OffloadWriteContext + 1; + bufferLength = OffloadWriteContext->BufferLength; + receiveTokenInformationBufferLength = OffloadWriteContext->ReceiveTokenInformationBufferLength; + writeUsingTokenMdl = OffloadWriteContext->WriteUsingTokenMdl; + listIdentifier = OffloadWriteContext->ListIdentifier; + srb = &OffloadWriteContext->Srb; + status = STATUS_SUCCESS; + + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_IOCTL, + "ClasspReceiveWriteUsingTokenInformation (%p): Entering function. Irp %p\n", + fdo, + irp)); + + // + // The WRITE USING TOKEN wasn't immediately fully successful, so that means + // the only way to find out how many sectors were processed by the WRITE + // USING TOKEN is to get a successful RECEIVE ROD TOKEN INFORMATION that + // indicates the operation is complete. + // + + NT_ASSERT(OffloadWriteContext->TotalSectorsProcessed == 0); + + pkt = DequeueFreeTransferPacket(fdo, TRUE); + + if (!pkt) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspReceiveWriteUsingTokenInformation (%p): Failed to retrieve transfer packet for ReceiveTokenInformation (WriteUsingToken) operation.\n", + fdo)); + + status = STATUS_INSUFFICIENT_RESOURCES; + + goto __ClasspReceiveWriteUsingTokenInformation_ErrorExit; + } + + RtlZeroMemory(buffer, bufferLength); + + tempSizeUlong = receiveTokenInformationBufferLength - 4; + REVERSE_BYTES(((PRECEIVE_TOKEN_INFORMATION_HEADER)buffer)->AvailableData, &tempSizeUlong); + + RtlZeroMemory(pseudoIrp, sizeof(IRP)); + + pseudoIrp->IoStatus.Status = STATUS_SUCCESS; + pseudoIrp->IoStatus.Information = 0; + pseudoIrp->Tail.Overlay.DriverContext[0] = LongToPtr(1); + pseudoIrp->MdlAddress = writeUsingTokenMdl; + + ClasspSetupReceiveWriteUsingTokenInformationTransferPacket( + OffloadWriteContext, + pkt, + bufferLength, + (PUCHAR)buffer, + pseudoIrp, + listIdentifier); + + // + // Cache away the CDB as it may be required for forwarded sense data + // after this command completes. + // + RtlZeroMemory(srb, sizeof(*srb)); + cdbLength = SrbGetCdbLength(pkt->Srb); + if (cdbLength <= 16) { + RtlCopyMemory(&srb->Cdb, SrbGetCdb(pkt->Srb), cdbLength); + } + + SubmitTransferPacket(pkt); + + return; + + // + // Error label only - not used by success cases: + // + +__ClasspReceiveWriteUsingTokenInformation_ErrorExit: + + NT_ASSERT(!NT_SUCCESS(status)); + + // + // ClasspCompleteOffloadWrite also cleans up OffloadWriteContext. + // + + ClasspCompleteOffloadWrite(OffloadWriteContext, status); + + return; +} + + +VOID +ClasspReceiveWriteUsingTokenInformationTransferPacketDone( + _In_ POFFLOAD_WRITE_CONTEXT OffloadWriteContext + ) + +/*++ + +Routine description: + + This routine continues an offload write operation when a RECEIVE ROD TOKEN + INFORMATION transfer packet is done. + + This routine may need to send another RRTI, or it may be able to indicate + that this WUT is done via call to + ClasspReceiveWriteUsingTokenInformationDone(). + + This routine is responsible for continuing or completing the offload write + operation. + +Arguments: + + OffloadWriteContext - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload + write operation. + +Return Value: + + None. + +--*/ + +{ + ULONG availableData; + PVOID buffer; + UCHAR completionStatus; + ULONG estimatedRetryInterval; + PDEVICE_OBJECT fdo; + PFUNCTIONAL_DEVICE_EXTENSION fdoExt; + PIRP irp; + ULONG listIdentifier; + BOOLEAN operationCompleted; + UCHAR operationStatus; + PIRP pseudoIrp; + USHORT segmentsProcessed; + PSENSE_DATA senseData; + ULONG senseDataFieldLength; + UCHAR senseDataLength; + PSCSI_REQUEST_BLOCK srb; + NTSTATUS status; + ULONG tokenDescriptorLength; + PRECEIVE_TOKEN_INFORMATION_HEADER tokenInformationResults; + PRECEIVE_TOKEN_INFORMATION_RESPONSE_HEADER tokenInformationResponsePadding; + PBOOLEAN tokenInvalidated; + PULONGLONG totalSectorsProcessed; + ULONGLONG totalSectorsToProcess; + ULONGLONG transferBlockCount; + + fdo = OffloadWriteContext->Fdo; + fdoExt = fdo->DeviceExtension; + listIdentifier = OffloadWriteContext->ListIdentifier; + totalSectorsProcessed = &OffloadWriteContext->TotalSectorsProcessed; + totalSectorsToProcess = OffloadWriteContext->TotalSectorsToProcess; + irp = OffloadWriteContext->OffloadWriteDsmIrp; + pseudoIrp = &OffloadWriteContext->PseudoIrp; + tokenInvalidated = &OffloadWriteContext->TokenInvalidated; + srb = &OffloadWriteContext->Srb; + operationCompleted = FALSE; + buffer = OffloadWriteContext + 1; + tokenInformationResults = (PRECEIVE_TOKEN_INFORMATION_HEADER)buffer; + senseData = (PSENSE_DATA)((PUCHAR)tokenInformationResults + FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_HEADER, SenseData)); + transferBlockCount = 0; + tokenInformationResponsePadding = NULL; + tokenDescriptorLength = 0; + + NT_ASSERT((*totalSectorsProcessed) == 0); + + OffloadWriteContext->Pkt = NULL; + + + status = pseudoIrp->IoStatus.Status; + NT_ASSERT(status != STATUS_PENDING); + + // + // The buffer we hand allows for the max sizes for all the fields whereas the returned + // data may be lesser (e.g. sense data info will almost never be MAX_SENSE_BUFFER_SIZE, etc. + // so handle underrun "error" + // + if (status == STATUS_DATA_OVERRUN) { + + status = STATUS_SUCCESS; + } + + if (!NT_SUCCESS(status)) { + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspReceiveWriteUsingTokenInformationTransferPacketDone (%p): Failed with %x to retrieve write results for list Id %x for data size %I64u bytes.\n", + fdo, + status, + listIdentifier, + totalSectorsToProcess * fdoExt->DiskGeometry.BytesPerSector)); + + NT_ASSERT((*totalSectorsProcessed) == 0); + + goto __ClasspReceiveWriteUsingTokenInformationTransferPacketDone_ErrorExit; + } + + REVERSE_BYTES(&availableData, &tokenInformationResults->AvailableData); + + NT_ASSERT(availableData <= FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_HEADER, SenseData) + MAX_SENSE_BUFFER_SIZE); + + NT_ASSERT(tokenInformationResults->ResponseToServiceAction == SERVICE_ACTION_WRITE_USING_TOKEN); + + operationStatus = tokenInformationResults->OperationStatus; + operationCompleted = ClasspIsTokenOperationComplete(operationStatus); + NT_ASSERT(operationCompleted); + + REVERSE_BYTES(&estimatedRetryInterval, &tokenInformationResults->EstimatedStatusUpdateDelay); + + completionStatus = tokenInformationResults->CompletionStatus; + + senseDataFieldLength = tokenInformationResults->SenseDataFieldLength; + senseDataLength = tokenInformationResults->SenseDataLength; + NT_ASSERT(senseDataFieldLength >= senseDataLength); + + tokenInformationResponsePadding = (PRECEIVE_TOKEN_INFORMATION_RESPONSE_HEADER)((PUCHAR)tokenInformationResults + + FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_HEADER, SenseData) + + tokenInformationResults->SenseDataFieldLength); + + REVERSE_BYTES(&tokenDescriptorLength, &tokenInformationResponsePadding->TokenDescriptorsLength); + NT_ASSERT(tokenDescriptorLength == 0); + + NT_ASSERT(tokenInformationResults->TransferCountUnits == TRANSFER_COUNT_UNITS_NUMBER_BLOCKS); + REVERSE_BYTES_QUAD(&transferBlockCount, &tokenInformationResults->TransferCount); + + REVERSE_BYTES_SHORT(&segmentsProcessed, &tokenInformationResults->SegmentsProcessed); + NT_ASSERT(segmentsProcessed == 0); + + if (operationCompleted) { + + if (transferBlockCount > totalSectorsToProcess) { + + // + // Buggy or hostile target. Don't let it claim more was procesed + // than was requested. Since this is likely a bug and it's unknown + // how much was actually transferred, assume no data was + // transferred. + // + + NT_ASSERT(transferBlockCount <= totalSectorsToProcess); + transferBlockCount = 0; + } + + if (operationStatus != OPERATION_COMPLETED_WITH_SUCCESS && + operationStatus != OPERATION_COMPLETED_WITH_RESIDUAL_DATA) { + + // + // Assert on buggy response from target, but in any case, make sure not + // to claim that any data was written. + // + + NT_ASSERT(transferBlockCount == 0); + transferBlockCount = 0; + } + + // + // Since the TokenOperation was sent down synchronously but failed, the operation is complete as soon as the + // ReceiveTokenInformation command returns. + // + + NT_ASSERT((*totalSectorsProcessed) == 0); + *totalSectorsProcessed = transferBlockCount; + ClasspAdvanceOffloadWritePosition(OffloadWriteContext, transferBlockCount); + + if (transferBlockCount < totalSectorsToProcess) { + + NT_ASSERT(operationStatus == OPERATION_COMPLETED_WITH_RESIDUAL_DATA || + operationStatus == OPERATION_COMPLETED_WITH_ERROR || + operationStatus == OPERATION_TERMINATED); + + } else { + + NT_ASSERT(operationStatus == OPERATION_COMPLETED_WITH_SUCCESS); + } + + TracePrint((transferBlockCount == totalSectorsToProcess ? TRACE_LEVEL_INFORMATION : TRACE_LEVEL_WARNING, + TRACE_FLAG_IOCTL, + "ClasspReceiveWriteUsingTokenInformationTransferPacketDone (%p): %wsSuccessfully wrote (for list Id %x) for data size %I64u bytes\n", + fdo, + transferBlockCount == totalSectorsToProcess ? L"" : L"Target truncated write. ", + listIdentifier, + (*totalSectorsProcessed) * fdoExt->DiskGeometry.BytesPerSector)); + + // + // Operation that completes with success can have sense data (for target to pass on some extra info) + // but we don't care about such sense info. + // Operation that complete but not with success, may not have sense data associated, but may + // have valid CompletionStatus. + // + // The "status" may be overriden by ClassInterpretSenseInfo(). Final + // status is determined a bit later - this is just the default status + // when ClassInterpretSenseInfo() doesn't get to run here. + // + status = STATUS_SUCCESS; + if (operationStatus == OPERATION_COMPLETED_WITH_ERROR || + operationStatus == OPERATION_COMPLETED_WITH_RESIDUAL_DATA || + operationStatus == OPERATION_TERMINATED) { + + SrbSetScsiStatus((PSTORAGE_REQUEST_BLOCK_HEADER)srb, completionStatus); + + if (senseDataLength) { + + ULONG retryInterval; + + NT_ASSERT(senseDataLength <= sizeof(SENSE_DATA)); + + srb->Function = SRB_FUNCTION_EXECUTE_SCSI; + + srb->SrbStatus = SRB_STATUS_AUTOSENSE_VALID | SRB_STATUS_ERROR; + SrbSetSenseInfoBuffer((PSTORAGE_REQUEST_BLOCK_HEADER)srb, senseData); + SrbSetSenseInfoBufferLength((PSTORAGE_REQUEST_BLOCK_HEADER)srb, senseDataLength); + + ClassInterpretSenseInfo(fdo, + srb, + IRP_MJ_SCSI, + 0, + 0, + &status, + &retryInterval); + + TracePrint((TRACE_LEVEL_WARNING, + TRACE_FLAG_IOCTL, + "ClasspReceiveWriteUsingTokenInformationTransferPacketDone (%p): Reason for truncation/failure: %x - for list Id %x for data size %I64u bytes.\n", + fdo, + status, + listIdentifier, + transferBlockCount * fdoExt->DiskGeometry.BytesPerSector)); + + // + // If the token isn't valid any longer, we should let the upper layers know so that + // they don't waste time retrying the write with the same token. + // + if (status == STATUS_INVALID_TOKEN) { + + *tokenInvalidated = TRUE; + } + } else { + + TracePrint((TRACE_LEVEL_WARNING, + TRACE_FLAG_IOCTL, + "ClasspReceiveWriteUsingTokenInformationTransferPacketDone (%p): No sense data available but reason for truncation/failure, possibly: %x - for list Id %x for data size %I64u bytes.\n", + fdo, + completionStatus, + listIdentifier, + transferBlockCount * fdoExt->DiskGeometry.BytesPerSector)); + } + } + + // + // Initialize status. Upper layer needs to know if command failed because it + // timed out without doing any writing. ClasspCompleteOffloadWrite() will + // force status to success if any data was written, so it's this function's + // job to set the status appropriately based on the outcome of this + // WRITE USING TOKEN command, and then ClasspCompleteOffloadWrite() + // can override with success if previos WRITE USING TOKEN commands + // issued for the same upper request were able to write some data. + // + if (transferBlockCount != 0) { + status = STATUS_SUCCESS; + } else { + + if (NT_SUCCESS(status)) { + // + // Make sure status is a failing status, without throwing away an + // already-failing status obtained from sense data. + // + status = STATUS_UNSUCCESSFUL; + } + } + + NT_ASSERT(status != STATUS_PENDING); + + if (!NT_SUCCESS(status)) { + goto __ClasspReceiveWriteUsingTokenInformationTransferPacketDone_ErrorExit; + } + + ClasspReceiveWriteUsingTokenInformationDone(OffloadWriteContext, status); + status = STATUS_PENDING; + goto __ClasspReceiveWriteUsingTokenInformationTransferPacketDone_Exit; + + } else { + + status = STATUS_UNSUCCESSFUL; + + goto __ClasspReceiveWriteUsingTokenInformationTransferPacketDone_ErrorExit; + } + + // + // Error label only - not used in success case: + // + +__ClasspReceiveWriteUsingTokenInformationTransferPacketDone_ErrorExit: + + NT_ASSERT(!NT_SUCCESS(status)); + + ClasspCompleteOffloadWrite(OffloadWriteContext, status); + +__ClasspReceiveWriteUsingTokenInformationTransferPacketDone_Exit: + + // + // Due to tracing a potentially freed pointer value "Irp", this trace could + // be delayed beyond another offload op picking up the same pointer value + // for its Irp. + // + + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_IOCTL, + "ClasspReceiveWriteUsingTokenInformationTransferPacketDone (%p): Exiting function (Irp %p) with status %x.\n", + fdo, + irp, + status)); + + return; +} + + +NTSTATUS +ClasspRefreshFunctionSupportInfo( + _Inout_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension, + _In_ BOOLEAN ForceQuery + ) +/* +Routine Description: + + This function is to update various properties described in FDO extension's + CLASS_FUNCTION_SUPPORT_INFO structure by requerying the device's VPD pages. + Although this function is capable of updating any properties in the + CLASS_FUNCTION_SUPPORT_INFO structure, it will initially support only + a small number of proporties in the block limit data + +Arguments: + + FdoExtension : FDO extension + + ForceQuery : TRUE if the caller wants to force a query of the device's + VPD pages. Otherwise, the function may use cached data. + +Return Value: + + STATUS_SUCCESS or an error status + +--*/ +{ + NTSTATUS status; + PSCSI_REQUEST_BLOCK srb = NULL; + ULONG srbSize; + CLASS_VPD_B0_DATA blockLimitsDataNew; + PCLASS_VPD_B0_DATA blockLimitsDataOriginal; + KLOCK_QUEUE_HANDLE lockHandle; + ULONG generationCount; + ULONG changeRequestCount; + + // + // ChangeRequestCount is incremented every time we get an unit attention with + // SCSI_ADSENSE_OPERATING_CONDITIONS_CHANGED. GenerationCount will be set to + // ChangeRequestCount after CLASS_FUNCTION_SUPPORT_INFO is refreshed with the latest + // VPD data. i.e. if both values are the same, data in + // CLASS_FUNCTION_SUPPORT_INFO is current + // + + generationCount = FdoExtension->FunctionSupportInfo->GenerationCount; + changeRequestCount = FdoExtension->FunctionSupportInfo->ChangeRequestCount; + if (!ForceQuery && generationCount == changeRequestCount) { + return STATUS_SUCCESS; + } + + // + // Allocate an SRB for querying the device for LBP-related info if either + // the Logical Block Provisioning (0xB2) or Block Limits (0xB0) VPD page + // exists. + // + if ((FdoExtension->AdapterDescriptor != NULL) && + (FdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK)) { + srbSize = CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE; + } else { + srbSize = sizeof(SCSI_REQUEST_BLOCK); + } + + srb = ExAllocatePoolWithTag(NonPagedPoolNx, srbSize, '1DcS'); + if (srb == NULL) { + return STATUS_INSUFFICIENT_RESOURCES; + } + + status = ClasspDeviceGetBlockLimitsVPDPage(FdoExtension, + srb, + srbSize, + &blockLimitsDataNew); + + if (NT_SUCCESS(status)) { + + KeAcquireInStackQueuedSpinLock(&FdoExtension->FunctionSupportInfo->SyncLock, &lockHandle); + + // + // If the generationCount didn't change since we looked at it last time, it means + // no one has tried to update the CLASS_FUNCTION_SUPPORT_INFO data; otherwise, someone + // else has beat us to it. + // + if (generationCount == FdoExtension->FunctionSupportInfo->GenerationCount) { + + blockLimitsDataOriginal = &FdoExtension->FunctionSupportInfo->BlockLimitsData; + if (blockLimitsDataOriginal->CommandStatus == -1) { + // + // CommandStatus == -1 means this is the first time we have + // gotten the block limits data. + // + *blockLimitsDataOriginal = blockLimitsDataNew; + } else { + // + // We only expect the Optimal Unmap Granularity (and alignment) + // to change, so those are the only parameters we update. + // + blockLimitsDataOriginal->UGAVALID = blockLimitsDataNew.UGAVALID; + blockLimitsDataOriginal->UnmapGranularityAlignment = blockLimitsDataNew.UnmapGranularityAlignment; + blockLimitsDataOriginal->OptimalUnmapGranularity = blockLimitsDataNew.OptimalUnmapGranularity; + } + FdoExtension->FunctionSupportInfo->GenerationCount = changeRequestCount; + } + + KeReleaseInStackQueuedSpinLock(&lockHandle); + } + + FREE_POOL(srb); + return status; +} + +NTSTATUS +ClasspBlockLimitsDataSnapshot( + _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension, + _In_ BOOLEAN ForceQuery, + _Out_ PCLASS_VPD_B0_DATA BlockLimitsData, + _Out_ PULONG GenerationCount + ) +/* +Routine Description: + + This function is to get a copy of the latest block limits data. + + When this function is called multiple times, GenerationCount can change (value always goes up) + while BlockLimitsData stays the same. In this case, the caller should assume BlockLimitsData + has changed to different values and eventually changed back to the same state when the first + call to this function was made. + +Arguments: + + FdoExtension : FDO extension + + ForceQuery : TRUE if the caller wants to force a query of the device's + VPD pages. Otherwise, the function may use cached data. + + BlockLimitsData : pointer to memory that will receive the block limits data + + GenerationCount : generation count of the block limit data. + + DataIsOutdated: set to TRUE if the BlockLimitsData is old but this function fails to + query the latest data from the device due to insufficient resources + +Return Value: + + STATUS_SUCCESS or an error status + +--*/ +{ + NTSTATUS status; + KLOCK_QUEUE_HANDLE lockHandle; + + status = ClasspRefreshFunctionSupportInfo(FdoExtension, ForceQuery); + + KeAcquireInStackQueuedSpinLock(&FdoExtension->FunctionSupportInfo->SyncLock, &lockHandle); + *BlockLimitsData = FdoExtension->FunctionSupportInfo->BlockLimitsData; + *GenerationCount = FdoExtension->FunctionSupportInfo->GenerationCount; + KeReleaseInStackQueuedSpinLock(&lockHandle); + + return status; +} + |
