summaryrefslogtreecommitdiff
path: root/storage/class/classpnp/src/class.c
diff options
context:
space:
mode:
authorDave Wilson <[email protected]>2015-03-17 19:50:07 -0700
committerDave Wilson <[email protected]>2015-03-17 19:50:07 -0700
commit97cf5197cf5b882b2c689d8dc2b555f2edf8f418 (patch)
tree46f3701832d70b420eb0fc0eb93261f9da45db3f /storage/class/classpnp/src/class.c
parentef1905bf1e8825bb31120dfb27e0daf3154d859a (diff)
Initial publish
Diffstat (limited to 'storage/class/classpnp/src/class.c')
-rw-r--r--storage/class/classpnp/src/class.c16415
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),
+ &notReady);
+ 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),
+ &notReady);
+
+ 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, &currentIrql);
+
+ 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;
+}
+