summaryrefslogtreecommitdiff
path: root/storage/class/classpnp/src/utils.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/utils.c
parentef1905bf1e8825bb31120dfb27e0daf3154d859a (diff)
Initial publish
Diffstat (limited to 'storage/class/classpnp/src/utils.c')
-rw-r--r--storage/class/classpnp/src/utils.c8906
1 files changed, 8906 insertions, 0 deletions
diff --git a/storage/class/classpnp/src/utils.c b/storage/class/classpnp/src/utils.c
new file mode 100644
index 00000000..f8236c68
--- /dev/null
+++ b/storage/class/classpnp/src/utils.c
@@ -0,0 +1,8906 @@
+/*++
+
+Copyright (C) Microsoft Corporation, 1991 - 2010
+
+Module Name:
+
+ utils.c
+
+Abstract:
+
+ SCSI class driver routines
+
+Environment:
+
+ kernel mode only
+
+Notes:
+
+
+Revision History:
+
+--*/
+
+
+#include "classp.h"
+#include "debug.h"
+#include <ntiologc.h>
+
+
+#ifdef DEBUG_USE_WPP
+#include "utils.tmh"
+#endif
+
+//
+// Constant value used in firmware upgrade process.
+//
+#define FIRMWARE_ACTIVATE_TIMEOUT_VALUE 30
+
+
+#ifdef ALLOC_PRAGMA
+ #pragma alloc_text(PAGE, ClassGetDeviceParameter)
+ #pragma alloc_text(PAGE, ClassScanForSpecial)
+ #pragma alloc_text(PAGE, ClassSetDeviceParameter)
+ #pragma alloc_text(PAGE, ClasspMyStringMatches)
+ #pragma alloc_text(PAGE, ClasspDeviceCopyOffloadProperty)
+ #pragma alloc_text(PAGE, ClasspValidateOffloadSupported)
+ #pragma alloc_text(PAGE, ClasspValidateOffloadInputParameters)
+#endif
+
+// custom string match -- careful!
+BOOLEAN ClasspMyStringMatches(_In_opt_z_ PCHAR StringToMatch, _In_z_ PCHAR TargetString)
+{
+ ULONG length; // strlen returns an int, not size_t (!)
+ PAGED_CODE();
+ NT_ASSERT(TargetString);
+ // if no match requested, return TRUE
+ if (StringToMatch == NULL) {
+ return TRUE;
+ }
+ // cache the string length for efficiency
+ length = (ULONG)strlen(StringToMatch);
+ // ZERO-length strings may only match zero-length strings
+ if (length == 0) {
+ return (strlen(TargetString) == 0);
+ }
+ // strncmp returns zero if the strings match
+ return (strncmp(StringToMatch, TargetString, length) == 0);
+}
+
+
+_IRQL_requires_max_(PASSIVE_LEVEL)
+VOID ClassGetDeviceParameter(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ _In_opt_ PWSTR SubkeyName,
+ _In_ PWSTR ParameterName,
+ _Inout_ PULONG ParameterValue // also default value
+ )
+{
+ NTSTATUS status;
+ RTL_QUERY_REGISTRY_TABLE queryTable[2] = {0};
+ HANDLE deviceParameterHandle = NULL;
+ HANDLE deviceSubkeyHandle = NULL;
+ ULONG defaultParameterValue;
+
+ PAGED_CODE();
+
+ //
+ // open the given parameter
+ //
+
+ status = IoOpenDeviceRegistryKey(FdoExtension->LowerPdo,
+ PLUGPLAY_REGKEY_DEVICE,
+ KEY_READ,
+ &deviceParameterHandle);
+
+ if (NT_SUCCESS(status) && (SubkeyName != NULL)) {
+
+ UNICODE_STRING subkeyName;
+ OBJECT_ATTRIBUTES objectAttributes = {0};
+
+ RtlInitUnicodeString(&subkeyName, SubkeyName);
+ InitializeObjectAttributes(&objectAttributes,
+ &subkeyName,
+ OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
+ deviceParameterHandle,
+ NULL);
+
+ status = ZwOpenKey(&deviceSubkeyHandle,
+ KEY_READ,
+ &objectAttributes);
+ if (!NT_SUCCESS(status)) {
+ ZwClose(deviceParameterHandle);
+ }
+
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ defaultParameterValue = *ParameterValue;
+
+ queryTable->Flags = RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_REQUIRED;
+ queryTable->Name = ParameterName;
+ queryTable->EntryContext = ParameterValue;
+ queryTable->DefaultType = REG_DWORD;
+ queryTable->DefaultData = NULL;
+ queryTable->DefaultLength = 0;
+
+ status = RtlQueryRegistryValues(RTL_REGISTRY_HANDLE,
+ (PWSTR)(SubkeyName ?
+ deviceSubkeyHandle :
+ deviceParameterHandle),
+ queryTable,
+ NULL,
+ NULL);
+ if (!NT_SUCCESS(status)) {
+ *ParameterValue = defaultParameterValue; // use default value
+ }
+
+ //
+ // close what we open
+ //
+
+ if (SubkeyName) {
+ ZwClose(deviceSubkeyHandle);
+ }
+
+ ZwClose(deviceParameterHandle);
+ }
+
+ if (!NT_SUCCESS(status)) {
+
+ //
+ // Windows 2000 SP3 uses the driver-specific key, so look in there
+ //
+
+ status = IoOpenDeviceRegistryKey(FdoExtension->LowerPdo,
+ PLUGPLAY_REGKEY_DRIVER,
+ KEY_READ,
+ &deviceParameterHandle);
+
+ if (NT_SUCCESS(status) && (SubkeyName != NULL)) {
+
+ UNICODE_STRING subkeyName;
+ OBJECT_ATTRIBUTES objectAttributes = {0};
+
+ RtlInitUnicodeString(&subkeyName, SubkeyName);
+ InitializeObjectAttributes(&objectAttributes,
+ &subkeyName,
+ OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
+ deviceParameterHandle,
+ NULL);
+
+ status = ZwOpenKey(&deviceSubkeyHandle, KEY_READ, &objectAttributes);
+
+ if (!NT_SUCCESS(status)) {
+ ZwClose(deviceParameterHandle);
+ }
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ defaultParameterValue = *ParameterValue;
+
+ queryTable->Flags = RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_REQUIRED;
+ queryTable->Name = ParameterName;
+ queryTable->EntryContext = ParameterValue;
+ queryTable->DefaultType = REG_DWORD;
+ queryTable->DefaultData = NULL;
+ queryTable->DefaultLength = 0;
+
+ status = RtlQueryRegistryValues(RTL_REGISTRY_HANDLE,
+ (PWSTR)(SubkeyName ?
+ deviceSubkeyHandle :
+ deviceParameterHandle),
+ queryTable,
+ NULL,
+ NULL);
+ if (NT_SUCCESS(status)) {
+
+ //
+ // Migrate the value over to the device-specific key
+ //
+
+ ClassSetDeviceParameter(FdoExtension, SubkeyName, ParameterName, *ParameterValue);
+
+ } else {
+
+ //
+ // Use the default value
+ //
+
+ *ParameterValue = defaultParameterValue;
+ }
+
+ if (SubkeyName) {
+ ZwClose(deviceSubkeyHandle);
+ }
+
+ ZwClose(deviceParameterHandle);
+ }
+ }
+
+ return;
+
+} // end ClassGetDeviceParameter()
+
+_IRQL_requires_max_(PASSIVE_LEVEL)
+NTSTATUS ClassSetDeviceParameter(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ _In_opt_ PWSTR SubkeyName,
+ _In_ PWSTR ParameterName,
+ _In_ ULONG ParameterValue)
+{
+ NTSTATUS status;
+ HANDLE deviceParameterHandle = NULL;
+ HANDLE deviceSubkeyHandle = NULL;
+
+ PAGED_CODE();
+
+ //
+ // open the given parameter
+ //
+
+ status = IoOpenDeviceRegistryKey(FdoExtension->LowerPdo,
+ PLUGPLAY_REGKEY_DEVICE,
+ KEY_READ | KEY_WRITE,
+ &deviceParameterHandle);
+
+ if (NT_SUCCESS(status) && (SubkeyName != NULL)) {
+
+ UNICODE_STRING subkeyName;
+ OBJECT_ATTRIBUTES objectAttributes;
+
+ RtlInitUnicodeString(&subkeyName, SubkeyName);
+ InitializeObjectAttributes(&objectAttributes,
+ &subkeyName,
+ OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
+ deviceParameterHandle,
+ NULL);
+
+ status = ZwCreateKey(&deviceSubkeyHandle,
+ KEY_READ | KEY_WRITE,
+ &objectAttributes,
+ 0, NULL, 0, NULL);
+ if (!NT_SUCCESS(status)) {
+ ZwClose(deviceParameterHandle);
+ }
+
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ status = RtlWriteRegistryValue(
+ RTL_REGISTRY_HANDLE,
+ (PWSTR) (SubkeyName ?
+ deviceSubkeyHandle :
+ deviceParameterHandle),
+ ParameterName,
+ REG_DWORD,
+ &ParameterValue,
+ sizeof(ULONG));
+
+ //
+ // close what we open
+ //
+
+ if (SubkeyName) {
+ ZwClose(deviceSubkeyHandle);
+ }
+
+ ZwClose(deviceParameterHandle);
+ }
+
+ return status;
+
+} // end ClassSetDeviceParameter()
+
+
+/*
+ * ClassScanForSpecial
+ *
+ * This routine was written to simplify scanning for special
+ * hardware based upon id strings. it does not check the registry.
+ */
+
+_IRQL_requires_max_(PASSIVE_LEVEL)
+VOID ClassScanForSpecial(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ _In_ CLASSPNP_SCAN_FOR_SPECIAL_INFO DeviceList[],
+ _In_ PCLASS_SCAN_FOR_SPECIAL_HANDLER Function)
+{
+ PSTORAGE_DEVICE_DESCRIPTOR deviceDescriptor;
+ PUCHAR vendorId;
+ PUCHAR productId;
+ PUCHAR productRevision;
+ UCHAR nullString[] = "";
+
+ PAGED_CODE();
+ NT_ASSERT(DeviceList);
+ NT_ASSERT(Function);
+
+ deviceDescriptor = FdoExtension->DeviceDescriptor;
+
+ if (DeviceList == NULL) {
+ return;
+ }
+ if (Function == NULL) {
+ return;
+ }
+
+ //
+ // SCSI sets offsets to -1, ATAPI sets to 0. check for both.
+ //
+
+ if (deviceDescriptor->VendorIdOffset != 0 &&
+ deviceDescriptor->VendorIdOffset != -1) {
+ vendorId = ((PUCHAR)deviceDescriptor);
+ vendorId += deviceDescriptor->VendorIdOffset;
+ } else {
+ vendorId = nullString;
+ }
+ if (deviceDescriptor->ProductIdOffset != 0 &&
+ deviceDescriptor->ProductIdOffset != -1) {
+ productId = ((PUCHAR)deviceDescriptor);
+ productId += deviceDescriptor->ProductIdOffset;
+ } else {
+ productId = nullString;
+ }
+ if (deviceDescriptor->ProductRevisionOffset != 0 &&
+ deviceDescriptor->ProductRevisionOffset != -1) {
+ productRevision = ((PUCHAR)deviceDescriptor);
+ productRevision += deviceDescriptor->ProductRevisionOffset;
+ } else {
+ productRevision = nullString;
+ }
+
+ //
+ // loop while the device list is valid (not null-filled)
+ //
+
+ for (;(DeviceList->VendorId != NULL ||
+ DeviceList->ProductId != NULL ||
+ DeviceList->ProductRevision != NULL);DeviceList++) {
+
+ if (ClasspMyStringMatches(DeviceList->VendorId, (PCHAR)vendorId) &&
+ ClasspMyStringMatches(DeviceList->ProductId, (PCHAR)productId) &&
+ ClasspMyStringMatches(DeviceList->ProductRevision, (PCHAR)productRevision)
+ ) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "ClasspScanForSpecialByInquiry: Found matching "
+ "controller Ven: %s Prod: %s Rev: %s\n",
+ (PCSZ)vendorId, (PCSZ)productId, (PCSZ)productRevision));
+
+ //
+ // pass the context to the call back routine and exit
+ //
+
+ (Function)(FdoExtension, DeviceList->Data);
+
+ //
+ // for CHK builds, try to prevent wierd stacks by having a debug
+ // print here. it's a hack, but i know of no other way to prevent
+ // the stack from being wrong.
+ //
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "ClasspScanForSpecialByInquiry: "
+ "completed callback\n"));
+ return;
+
+ } // else the strings did not match
+
+ } // none of the devices matched.
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "ClasspScanForSpecialByInquiry: no match found for %p\n",
+ FdoExtension->DeviceObject));
+ return;
+
+} // end ClasspScanForSpecialByInquiry()
+
+
+//
+// In order to provide better performance without the need to reboot,
+// we need to implement a self-adjusting method to set and clear the
+// srb flags based upon current performance.
+//
+// whenever there is an error, immediately grab the spin lock. the
+// MP perf hit here is acceptable, since we're in an error path. this
+// is also neccessary because we are guaranteed to be modifying the
+// SRB flags here, setting SuccessfulIO to zero, and incrementing the
+// actual error count (which is always done within this spinlock).
+//
+// whenever there is no error, increment a counter. if there have been
+// errors on the device, and we've enabled dynamic perf, *and* we've
+// just crossed the perf threshhold, then grab the spin lock and
+// double check that the threshhold has, indeed been hit(*). then
+// decrement the error count, and if it's dropped sufficiently, undo
+// some of the safety changes made in the SRB flags due to the errors.
+//
+// * this works in all cases. even if lots of ios occur after the
+// previous guy went in and cleared the successfulio counter, that
+// just means that we've hit the threshhold again, and so it's proper
+// to run the inner loop again.
+//
+
+VOID
+ClasspPerfIncrementErrorCount(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ )
+{
+ PCLASS_PRIVATE_FDO_DATA fdoData = FdoExtension->PrivateFdoData;
+ KIRQL oldIrql;
+ ULONG errors;
+
+ KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
+
+ fdoData->Perf.SuccessfulIO = 0; // implicit interlock
+ errors = InterlockedIncrement((volatile LONG *)&FdoExtension->ErrorCount);
+
+ if (errors >= CLASS_ERROR_LEVEL_1) {
+
+ //
+ // If the error count has exceeded the error limit, then disable
+ // any tagged queuing, multiple requests per lu queueing
+ // and sychronous data transfers.
+ //
+ // Clearing the no queue freeze flag prevents the port driver
+ // from sending multiple requests per logical unit.
+ //
+
+ CLEAR_FLAG(FdoExtension->SrbFlags, SRB_FLAGS_NO_QUEUE_FREEZE);
+ CLEAR_FLAG(FdoExtension->SrbFlags, SRB_FLAGS_QUEUE_ACTION_ENABLE);
+
+ SET_FLAG(FdoExtension->SrbFlags, SRB_FLAGS_DISABLE_SYNCH_TRANSFER);
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClasspPerfIncrementErrorCount: "
+ "Too many errors; disabling tagged queuing and "
+ "synchronous data tranfers.\n"));
+
+ }
+
+ if (errors >= CLASS_ERROR_LEVEL_2) {
+
+ //
+ // If a second threshold is reached, disable disconnects.
+ //
+
+ SET_FLAG(FdoExtension->SrbFlags, SRB_FLAGS_DISABLE_DISCONNECT);
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClasspPerfIncrementErrorCount: "
+ "Too many errors; disabling disconnects.\n"));
+ }
+
+ KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
+ return;
+}
+
+VOID
+ClasspPerfIncrementSuccessfulIo(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ )
+{
+ PCLASS_PRIVATE_FDO_DATA fdoData = FdoExtension->PrivateFdoData;
+ KIRQL oldIrql;
+ ULONG errors;
+ ULONG succeeded = 0;
+
+ //
+ // don't take a hit from the interlocked op unless we're in
+ // a degraded state and we've got a threshold to hit.
+ //
+
+ if (FdoExtension->ErrorCount == 0) {
+ return;
+ }
+
+ if (fdoData->Perf.ReEnableThreshhold == 0) {
+ return;
+ }
+
+ succeeded = InterlockedIncrement((volatile LONG *)&fdoData->Perf.SuccessfulIO);
+ if (succeeded < fdoData->Perf.ReEnableThreshhold) {
+ return;
+ }
+
+ //
+ // if we hit the threshold, grab the spinlock and verify we've
+ // actually done so. this allows us to ignore the spinlock 99%
+ // of the time.
+ //
+
+ KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
+
+ //
+ // re-read the value, so we don't run this multiple times
+ // for a single threshhold being hit. this keeps errorcount
+ // somewhat useful.
+ //
+
+ succeeded = fdoData->Perf.SuccessfulIO;
+
+ if ((FdoExtension->ErrorCount != 0) &&
+ (fdoData->Perf.ReEnableThreshhold <= succeeded)
+ ) {
+
+ fdoData->Perf.SuccessfulIO = 0; // implicit interlock
+
+ NT_ASSERT(FdoExtension->ErrorCount > 0);
+ errors = InterlockedDecrement((volatile LONG *)&FdoExtension->ErrorCount);
+
+ //
+ // note: do in reverse order of the sets "just in case"
+ //
+
+ if (errors < CLASS_ERROR_LEVEL_2) {
+ if (errors == CLASS_ERROR_LEVEL_2 - 1) {
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClasspPerfIncrementSuccessfulIo: "
+ "Error level 2 no longer required.\n"));
+ }
+ if (!TEST_FLAG(fdoData->Perf.OriginalSrbFlags,
+ SRB_FLAGS_DISABLE_DISCONNECT)) {
+ CLEAR_FLAG(FdoExtension->SrbFlags,
+ SRB_FLAGS_DISABLE_DISCONNECT);
+ }
+ }
+
+ if (errors < CLASS_ERROR_LEVEL_1) {
+ if (errors == CLASS_ERROR_LEVEL_1 - 1) {
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClasspPerfIncrementSuccessfulIo: "
+ "Error level 1 no longer required.\n"));
+ }
+ if (!TEST_FLAG(fdoData->Perf.OriginalSrbFlags,
+ SRB_FLAGS_DISABLE_SYNCH_TRANSFER)) {
+ CLEAR_FLAG(FdoExtension->SrbFlags,
+ SRB_FLAGS_DISABLE_SYNCH_TRANSFER);
+ }
+ if (TEST_FLAG(fdoData->Perf.OriginalSrbFlags,
+ SRB_FLAGS_QUEUE_ACTION_ENABLE)) {
+ SET_FLAG(FdoExtension->SrbFlags,
+ SRB_FLAGS_QUEUE_ACTION_ENABLE);
+ }
+ if (TEST_FLAG(fdoData->Perf.OriginalSrbFlags,
+ SRB_FLAGS_NO_QUEUE_FREEZE)) {
+ SET_FLAG(FdoExtension->SrbFlags,
+ SRB_FLAGS_NO_QUEUE_FREEZE);
+ }
+ }
+ } // end of threshhold definitely being hit for first time
+
+ KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
+ return;
+}
+
+
+PMDL ClasspBuildDeviceMdl(PVOID Buffer, ULONG BufferLen, BOOLEAN WriteToDevice)
+{
+ PMDL mdl;
+
+ mdl = IoAllocateMdl(Buffer, BufferLen, FALSE, FALSE, NULL);
+ if (mdl){
+ try {
+ MmProbeAndLockPages(mdl, KernelMode, WriteToDevice ? IoReadAccess : IoWriteAccess);
+ #pragma warning(suppress: 6320) // We want to handle any exception that MmProbeAndLockPages might throw
+ } except(EXCEPTION_EXECUTE_HANDLER) {
+ NTSTATUS status = GetExceptionCode();
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, "ClasspBuildDeviceMdl: MmProbeAndLockPages failed with %xh.", status));
+ IoFreeMdl(mdl);
+ mdl = NULL;
+ }
+ }
+ else {
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, "ClasspBuildDeviceMdl: IoAllocateMdl failed"));
+ }
+
+ return mdl;
+}
+
+
+PMDL BuildDeviceInputMdl(PVOID Buffer, ULONG BufferLen)
+{
+ return ClasspBuildDeviceMdl(Buffer, BufferLen, FALSE);
+}
+
+
+VOID ClasspFreeDeviceMdl(PMDL Mdl)
+{
+ MmUnlockPages(Mdl);
+ IoFreeMdl(Mdl);
+}
+
+
+VOID FreeDeviceInputMdl(PMDL Mdl)
+{
+ ClasspFreeDeviceMdl(Mdl);
+ return;
+}
+
+
+#if 0
+ VOID
+ ClasspPerfResetCounters(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ )
+ {
+ PCLASS_PRIVATE_FDO_DATA fdoData = FdoExtension->PrivateFdoData;
+ KIRQL oldIrql;
+
+ KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClasspPerfResetCounters: "
+ "Resetting all perf counters.\n"));
+ fdoData->Perf.SuccessfulIO = 0;
+ FdoExtension->ErrorCount = 0;
+
+ if (!TEST_FLAG(fdoData->Perf.OriginalSrbFlags,
+ SRB_FLAGS_DISABLE_DISCONNECT)) {
+ CLEAR_FLAG(FdoExtension->SrbFlags,
+ SRB_FLAGS_DISABLE_DISCONNECT);
+ }
+ if (!TEST_FLAG(fdoData->Perf.OriginalSrbFlags,
+ SRB_FLAGS_DISABLE_SYNCH_TRANSFER)) {
+ CLEAR_FLAG(FdoExtension->SrbFlags,
+ SRB_FLAGS_DISABLE_SYNCH_TRANSFER);
+ }
+ if (TEST_FLAG(fdoData->Perf.OriginalSrbFlags,
+ SRB_FLAGS_QUEUE_ACTION_ENABLE)) {
+ SET_FLAG(FdoExtension->SrbFlags,
+ SRB_FLAGS_QUEUE_ACTION_ENABLE);
+ }
+ if (TEST_FLAG(fdoData->Perf.OriginalSrbFlags,
+ SRB_FLAGS_NO_QUEUE_FREEZE)) {
+ SET_FLAG(FdoExtension->SrbFlags,
+ SRB_FLAGS_NO_QUEUE_FREEZE);
+ }
+ KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
+ return;
+ }
+#endif
+
+
+/*++
+
+ClasspDuidGetDeviceIdProperty
+
+Routine Description:
+
+ Add StorageDeviceIdProperty to the device unique ID structure.
+
+Arguments:
+
+ DeviceObject - a pointer to the device object
+ Irp - a pointer to the I/O request packet
+
+Return Value:
+
+ Status Code
+
+--*/
+NTSTATUS
+ClasspDuidGetDeviceIdProperty(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp
+ )
+{
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+ PSTORAGE_DEVICE_ID_DESCRIPTOR deviceIdDescriptor = NULL;
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ PSTORAGE_DESCRIPTOR_HEADER descHeader;
+ PSTORAGE_DEVICE_UNIQUE_IDENTIFIER storageDuid;
+ PUCHAR dest;
+
+ STORAGE_PROPERTY_ID propertyId = StorageDeviceIdProperty;
+
+ NTSTATUS status;
+
+ ULONG queryLength;
+ ULONG offset;
+
+ //
+ // Get the VPD page 83h data.
+ //
+
+ status = ClassGetDescriptor(commonExtension->LowerDeviceObject,
+ &propertyId,
+ &deviceIdDescriptor);
+
+ if (!NT_SUCCESS(status) || !deviceIdDescriptor) {
+ goto FnExit;
+ }
+
+ queryLength = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
+ descHeader = Irp->AssociatedIrp.SystemBuffer;
+
+ //
+ // Adjust required size and potential destination location.
+ //
+
+ offset = descHeader->Size;
+ dest = (PUCHAR)descHeader + offset;
+
+ descHeader->Size += deviceIdDescriptor->Size;
+
+ if (queryLength < descHeader->Size) {
+
+ //
+ // Output buffer is too small. Return error and make sure
+ // the caller gets info about required buffer size.
+ //
+
+ Irp->IoStatus.Information = sizeof(STORAGE_DESCRIPTOR_HEADER);
+ status = STATUS_BUFFER_OVERFLOW;
+ goto FnExit;
+ }
+
+ storageDuid = Irp->AssociatedIrp.SystemBuffer;
+ storageDuid->StorageDeviceIdOffset = offset;
+
+ RtlCopyMemory(dest,
+ deviceIdDescriptor,
+ deviceIdDescriptor->Size);
+
+ Irp->IoStatus.Information = storageDuid->Size;
+ status = STATUS_SUCCESS;
+
+FnExit:
+
+ FREE_POOL(deviceIdDescriptor);
+
+ return status;
+}
+
+
+
+/*++
+
+ClasspDuidGetDeviceProperty
+
+Routine Description:
+
+ Add StorageDeviceProperty to the device unique ID structure.
+
+Arguments:
+
+ DeviceObject - a pointer to the device object
+ Irp - a pointer to the I/O request packet
+
+Return Value:
+
+ Status Code
+
+--*/
+NTSTATUS
+ClasspDuidGetDeviceProperty(
+ PDEVICE_OBJECT DeviceObject,
+ PIRP Irp
+ )
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
+ PSTORAGE_DEVICE_DESCRIPTOR deviceDescriptor = fdoExtension->DeviceDescriptor;
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ PSTORAGE_DESCRIPTOR_HEADER descHeader;
+ PSTORAGE_DEVICE_UNIQUE_IDENTIFIER storageDuid;
+ PUCHAR dest;
+
+ NTSTATUS status = STATUS_NOT_FOUND;
+
+ ULONG queryLength;
+ ULONG offset;
+
+ //
+ // Use the StorageDeviceProperty already cached in the device extension.
+ //
+
+ if (!deviceDescriptor) {
+ goto FnExit;
+ }
+
+ queryLength = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
+ descHeader = Irp->AssociatedIrp.SystemBuffer;
+
+ //
+ // Use this info only if serial number is available.
+ //
+
+ if (deviceDescriptor->SerialNumberOffset == 0) {
+ goto FnExit;
+ }
+
+ //
+ // Adjust required size and potential destination location.
+ //
+
+ offset = descHeader->Size;
+ dest = (PUCHAR)descHeader + offset;
+
+ descHeader->Size += deviceDescriptor->Size;
+
+ if (queryLength < descHeader->Size) {
+
+ //
+ // Output buffer is too small. Return error and make sure
+ // the caller get info about required buffer size.
+ //
+
+ Irp->IoStatus.Information = sizeof(STORAGE_DESCRIPTOR_HEADER);
+ status = STATUS_BUFFER_OVERFLOW;
+ goto FnExit;
+ }
+
+ storageDuid = Irp->AssociatedIrp.SystemBuffer;
+ storageDuid->StorageDeviceOffset = offset;
+
+ RtlCopyMemory(dest,
+ deviceDescriptor,
+ deviceDescriptor->Size);
+
+ Irp->IoStatus.Information = storageDuid->Size;
+ status = STATUS_SUCCESS;
+
+FnExit:
+
+ return status;
+}
+
+
+/*++
+
+ClasspDuidGetDriveLayout
+
+Routine Description:
+
+ Add drive layout signature to the device unique ID structure.
+ Layout signature is only added for disk-type devices.
+
+Arguments:
+
+ DeviceObject - a pointer to the device object
+ Irp - a pointer to the I/O request packet
+
+Return Value:
+
+ Status Code
+
+--*/
+NTSTATUS
+ClasspDuidGetDriveLayout(
+ PDEVICE_OBJECT DeviceObject,
+ PIRP Irp
+ )
+{
+ PDRIVE_LAYOUT_INFORMATION_EX layoutEx = NULL;
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ PSTORAGE_DESCRIPTOR_HEADER descHeader;
+ PSTORAGE_DEVICE_UNIQUE_IDENTIFIER storageDuid;
+ PSTORAGE_DEVICE_LAYOUT_SIGNATURE driveLayoutSignature;
+
+ NTSTATUS status = STATUS_NOT_FOUND;
+
+ ULONG queryLength;
+ ULONG offset;
+
+ //
+ // Only process disk-type devices.
+ //
+
+ if (DeviceObject->DeviceType != FILE_DEVICE_DISK) {
+ goto FnExit;
+ }
+
+ //
+ // Get current partition table and process only if GPT
+ // or MBR layout.
+ //
+
+ status = IoReadPartitionTableEx(DeviceObject, &layoutEx);
+
+ if (!NT_SUCCESS(status)) {
+ status = STATUS_NOT_FOUND;
+ goto FnExit;
+ }
+
+ if (layoutEx->PartitionStyle != PARTITION_STYLE_GPT &&
+ layoutEx->PartitionStyle != PARTITION_STYLE_MBR) {
+ status = STATUS_NOT_FOUND;
+ goto FnExit;
+ }
+
+ queryLength = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
+ descHeader = Irp->AssociatedIrp.SystemBuffer;
+
+ //
+ // Adjust required size and potential destination location.
+ //
+
+ offset = descHeader->Size;
+ driveLayoutSignature = (PSTORAGE_DEVICE_LAYOUT_SIGNATURE)((PUCHAR)descHeader + offset);
+
+ descHeader->Size += sizeof(STORAGE_DEVICE_LAYOUT_SIGNATURE);
+
+ if (queryLength < descHeader->Size) {
+
+ //
+ // Output buffer is too small. Return error and make sure
+ // the caller get info about required buffer size.
+ //
+
+ Irp->IoStatus.Information = sizeof(STORAGE_DESCRIPTOR_HEADER);
+ status = STATUS_BUFFER_OVERFLOW;
+ goto FnExit;
+ }
+
+ storageDuid = Irp->AssociatedIrp.SystemBuffer;
+
+ driveLayoutSignature->Size = sizeof(STORAGE_DEVICE_LAYOUT_SIGNATURE);
+ driveLayoutSignature->Version = DUID_VERSION_1;
+
+ if (layoutEx->PartitionStyle == PARTITION_STYLE_MBR) {
+
+ driveLayoutSignature->Mbr = TRUE;
+
+ RtlCopyMemory(&driveLayoutSignature->DeviceSpecific.MbrSignature,
+ &layoutEx->Mbr.Signature,
+ sizeof(layoutEx->Mbr.Signature));
+
+ } else {
+
+ driveLayoutSignature->Mbr = FALSE;
+
+ RtlCopyMemory(&driveLayoutSignature->DeviceSpecific.GptDiskId,
+ &layoutEx->Gpt.DiskId,
+ sizeof(layoutEx->Gpt.DiskId));
+ }
+
+ storageDuid->DriveLayoutSignatureOffset = offset;
+
+ Irp->IoStatus.Information = storageDuid->Size;
+ status = STATUS_SUCCESS;
+
+
+FnExit:
+
+ FREE_POOL(layoutEx);
+
+ return status;
+}
+
+
+/*++
+
+ClasspDuidQueryProperty
+
+Routine Description:
+
+ Handles IOCTL_STORAGE_QUERY_PROPERTY for device unique ID requests
+ (when PropertyId is StorageDeviceUniqueIdProperty).
+
+Arguments:
+
+ DeviceObject - a pointer to the device object
+ Irp - a pointer to the I/O request packet
+
+Return Value:
+
+ Status Code
+
+--*/
+NTSTATUS
+ClasspDuidQueryProperty(
+ PDEVICE_OBJECT DeviceObject,
+ PIRP Irp
+ )
+{
+ PSTORAGE_PROPERTY_QUERY query = Irp->AssociatedIrp.SystemBuffer;
+ PSTORAGE_DESCRIPTOR_HEADER descHeader;
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+
+ NTSTATUS status;
+
+ ULONG outLength = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
+
+ BOOLEAN includeOptionalIds;
+ BOOLEAN overflow = FALSE;
+ BOOLEAN infoFound = FALSE;
+ BOOLEAN useStatus = TRUE; // Use the status directly instead of relying on overflow and infoFound flags.
+
+ //
+ // Must run at less then dispatch.
+ //
+
+ if (KeGetCurrentIrql() >= DISPATCH_LEVEL) {
+
+ NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
+ status = STATUS_INVALID_LEVEL;
+ goto FnExit;
+ }
+
+ //
+ // Check proper query type.
+ //
+
+ if (query->QueryType == PropertyExistsQuery) {
+ Irp->IoStatus.Information = 0;
+ status = STATUS_SUCCESS;
+ goto FnExit;
+ }
+
+ if (query->QueryType != PropertyStandardQuery) {
+ status = STATUS_NOT_SUPPORTED;
+ goto FnExit;
+ }
+
+ //
+ // Check AdditionalParameters validity.
+ //
+
+ if (query->AdditionalParameters[0] == DUID_INCLUDE_SOFTWARE_IDS) {
+ includeOptionalIds = TRUE;
+ } else if (query->AdditionalParameters[0] == DUID_HARDWARE_IDS_ONLY) {
+ includeOptionalIds = FALSE;
+ } else {
+ status = STATUS_INVALID_PARAMETER;
+ goto FnExit;
+ }
+
+ //
+ // Verify output parameters.
+ //
+
+ if (outLength < sizeof(STORAGE_DESCRIPTOR_HEADER)) {
+
+ status = STATUS_INFO_LENGTH_MISMATCH;
+ goto FnExit;
+ }
+
+ //
+ // From this point forward the status depends on the overflow
+ // and infoFound flags.
+ //
+
+ useStatus = FALSE;
+
+ descHeader = Irp->AssociatedIrp.SystemBuffer;
+ RtlZeroMemory(descHeader, outLength);
+
+ descHeader->Version = DUID_VERSION_1;
+ descHeader->Size = sizeof(STORAGE_DEVICE_UNIQUE_IDENTIFIER);
+
+ //
+ // Try to build device unique id from StorageDeviceIdProperty.
+ //
+
+ status = ClasspDuidGetDeviceIdProperty(DeviceObject,
+ Irp);
+
+ if (status == STATUS_BUFFER_OVERFLOW) {
+ overflow = TRUE;
+ }
+
+ if (NT_SUCCESS(status)) {
+ infoFound = TRUE;
+ }
+
+ //
+ // Try to build device unique id from StorageDeviceProperty.
+ //
+
+ status = ClasspDuidGetDeviceProperty(DeviceObject,
+ Irp);
+
+ if (status == STATUS_BUFFER_OVERFLOW) {
+ overflow = TRUE;
+ }
+
+ if (NT_SUCCESS(status)) {
+ infoFound = TRUE;
+ }
+
+ //
+ // The following portion is optional and only included if the
+ // caller requested software IDs.
+ //
+
+ if (!includeOptionalIds) {
+ goto FnExit;
+ }
+
+ //
+ // Try to build device unique id from drive layout signature (disk
+ // devices only).
+ //
+
+ status = ClasspDuidGetDriveLayout(DeviceObject,
+ Irp);
+
+ if (status == STATUS_BUFFER_OVERFLOW) {
+ overflow = TRUE;
+ }
+
+ if (NT_SUCCESS(status)) {
+ infoFound = TRUE;
+ }
+
+FnExit:
+
+ if (!useStatus) {
+
+ //
+ // Return overflow, success, or a generic error.
+ //
+
+ if (overflow) {
+
+ //
+ // If output buffer is STORAGE_DESCRIPTOR_HEADER, then return
+ // success to the user. Otherwise, send an error so the user
+ // knows a larger buffer is required.
+ //
+
+ if (outLength == sizeof(STORAGE_DESCRIPTOR_HEADER)) {
+ status = STATUS_SUCCESS;
+ Irp->IoStatus.Information = sizeof(STORAGE_DESCRIPTOR_HEADER);
+ } else {
+ status = STATUS_BUFFER_OVERFLOW;
+ }
+
+ } else if (infoFound) {
+ status = STATUS_SUCCESS;
+
+ //
+ // Exercise the compare routine. This should always succeed.
+ //
+
+ NT_ASSERT(DuidExactMatch == CompareStorageDuids(Irp->AssociatedIrp.SystemBuffer,
+ Irp->AssociatedIrp.SystemBuffer));
+
+ } else {
+ status = STATUS_NOT_FOUND;
+ }
+ }
+
+ Irp->IoStatus.Status = status;
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+
+ return status;
+}
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClasspWriteCacheProperty()
+
+Routine Description:
+
+ This routine reads the caching mode page from the device to
+ build the Write Cache property page.
+
+Arguments:
+
+ DeviceObject - The device object to handle this irp
+
+ Irp - The IRP for this request
+
+ Srb - SRB allocated by the dispatch routine
+
+Return Value:
+
+--*/
+
+NTSTATUS ClasspWriteCacheProperty(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ )
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
+ PSTORAGE_WRITE_CACHE_PROPERTY writeCache;
+ PSTORAGE_PROPERTY_QUERY query = Irp->AssociatedIrp.SystemBuffer;
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ PMODE_PARAMETER_HEADER modeData = NULL;
+ PMODE_CACHING_PAGE pageData = NULL;
+ ULONG length, information = 0;
+ NTSTATUS status;
+ PCDB cdb;
+
+ //
+ // Must run at less then dispatch.
+ //
+
+ if (KeGetCurrentIrql() >= DISPATCH_LEVEL) {
+
+ NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
+ status = STATUS_INVALID_LEVEL;
+ goto WriteCacheExit;
+ }
+
+ //
+ // Check proper query type.
+ //
+
+ if (query->QueryType == PropertyExistsQuery) {
+ status = STATUS_SUCCESS;
+ goto WriteCacheExit;
+ }
+
+ if (query->QueryType != PropertyStandardQuery) {
+ status = STATUS_NOT_SUPPORTED;
+ goto WriteCacheExit;
+ }
+
+ length = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
+
+ if (length < sizeof(STORAGE_DESCRIPTOR_HEADER)) {
+ status = STATUS_INFO_LENGTH_MISMATCH;
+ goto WriteCacheExit;
+ }
+
+ writeCache = (PSTORAGE_WRITE_CACHE_PROPERTY) Irp->AssociatedIrp.SystemBuffer;
+ RtlZeroMemory(writeCache, length);
+
+ //
+ // Set version and required size.
+ //
+
+ writeCache->Version = sizeof(STORAGE_WRITE_CACHE_PROPERTY);
+ writeCache->Size = sizeof(STORAGE_WRITE_CACHE_PROPERTY);
+
+ if (length < sizeof(STORAGE_WRITE_CACHE_PROPERTY)) {
+ information = sizeof(STORAGE_DESCRIPTOR_HEADER);
+ status = STATUS_SUCCESS;
+ goto WriteCacheExit;
+ }
+
+ //
+ // Set known values
+ //
+
+ writeCache->NVCacheEnabled = FALSE;
+ writeCache->UserDefinedPowerProtection = TEST_FLAG(fdoExtension->DeviceFlags, DEV_POWER_PROTECTED);
+
+ //
+ // Check for flush cache support by sending a sync cache command
+ // to the device.
+ //
+
+ //
+ // Set timeout value and mark the request as not being a tagged request.
+ //
+ SrbSetTimeOutValue(Srb, fdoExtension->TimeOutValue * 4);
+ SrbSetRequestTag(Srb, SP_UNTAGGED);
+ SrbSetRequestAttribute(Srb, SRB_SIMPLE_TAG_REQUEST);
+ SrbAssignSrbFlags(Srb, fdoExtension->SrbFlags);
+
+ SrbSetCdbLength(Srb, 10);
+ cdb = SrbGetCdb(Srb);
+ cdb->CDB10.OperationCode = SCSIOP_SYNCHRONIZE_CACHE;
+
+ status = ClassSendSrbSynchronous(DeviceObject,
+ Srb,
+ NULL,
+ 0,
+ TRUE);
+ if (NT_SUCCESS(status)) {
+ writeCache->FlushCacheSupported = TRUE;
+ } else {
+ //
+ // Device does not support sync cache
+ //
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "ClasspWriteCacheProperty: Synchronize cache failed with status 0x%X\n", status));
+ writeCache->FlushCacheSupported = FALSE;
+ //
+ // Reset the status if there was any failure
+ //
+ status = STATUS_SUCCESS;
+ }
+
+ modeData = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned,
+ MODE_PAGE_DATA_SIZE,
+ CLASS_TAG_MODE_DATA);
+
+ if (modeData == NULL) {
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, "ClasspWriteCacheProperty: Unable to allocate mode data buffer\n"));
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto WriteCacheExit;
+ }
+
+ RtlZeroMemory(modeData, MODE_PAGE_DATA_SIZE);
+
+ length = ClassModeSense(DeviceObject,
+ (PCHAR) modeData,
+ MODE_PAGE_DATA_SIZE,
+ MODE_PAGE_CACHING);
+
+ if (length < sizeof(MODE_PARAMETER_HEADER)) {
+
+ //
+ // Retry the request in case of a check condition.
+ //
+
+ length = ClassModeSense(DeviceObject,
+ (PCHAR) modeData,
+ MODE_PAGE_DATA_SIZE,
+ MODE_PAGE_CACHING);
+
+ if (length < sizeof(MODE_PARAMETER_HEADER)) {
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, "ClasspWriteCacheProperty: Mode Sense failed\n"));
+ status = STATUS_IO_DEVICE_ERROR;
+ goto WriteCacheExit;
+ }
+ }
+
+ //
+ // If the length is greater than length indicated by the mode data reset
+ // the data to the mode data.
+ //
+
+ if (length > (ULONG) (modeData->ModeDataLength + 1)) {
+ length = modeData->ModeDataLength + 1;
+ }
+
+ //
+ // Look for caching page in the returned mode page data.
+ //
+
+ pageData = ClassFindModePage((PCHAR) modeData,
+ length,
+ MODE_PAGE_CACHING,
+ TRUE);
+
+ //
+ // Check if valid caching page exists.
+ //
+
+ if (pageData == NULL) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "ClasspWriteCacheProperty: Unable to find caching mode page.\n"));
+ //
+ // Set write cache value as unknown.
+ //
+ writeCache->WriteCacheEnabled = WriteCacheEnableUnknown;
+ writeCache->WriteCacheType = WriteCacheTypeUnknown;
+ } else {
+ writeCache->WriteCacheEnabled = pageData->WriteCacheEnable ?
+ WriteCacheEnabled : WriteCacheDisabled;
+
+ writeCache->WriteCacheType = pageData->WriteCacheEnable ?
+ WriteCacheTypeWriteBack : WriteCacheTypeUnknown;
+ }
+
+ //
+ // Check write through support. If the device previously failed a write request
+ // with FUA bit is set, then CLASS_SPECIAL_FUA_NOT_SUPPORTED will be set,
+ // which means write through is not support by the device.
+ //
+
+ if ((modeData->DeviceSpecificParameter & MODE_DSP_FUA_SUPPORTED) &&
+ (!TEST_FLAG(fdoExtension->ScanForSpecialFlags, CLASS_SPECIAL_FUA_NOT_SUPPORTED))) {
+ writeCache->WriteThroughSupported = WriteThroughSupported;
+ } else {
+ writeCache->WriteThroughSupported = WriteThroughNotSupported;
+ }
+
+ //
+ // Get the changeable caching mode page and check write cache is changeable.
+ //
+
+ RtlZeroMemory(modeData, MODE_PAGE_DATA_SIZE);
+
+ length = ClasspModeSense(DeviceObject,
+ (PCHAR) modeData,
+ MODE_PAGE_DATA_SIZE,
+ MODE_PAGE_CACHING,
+ MODE_SENSE_CHANGEABLE_VALUES);
+
+ if (length < sizeof(MODE_PARAMETER_HEADER)) {
+
+ //
+ // Retry the request in case of a check condition.
+ //
+
+ length = ClasspModeSense(DeviceObject,
+ (PCHAR) modeData,
+ MODE_PAGE_DATA_SIZE,
+ MODE_PAGE_CACHING,
+ MODE_SENSE_CHANGEABLE_VALUES);
+
+ if (length < sizeof(MODE_PARAMETER_HEADER)) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "ClasspWriteCacheProperty: Mode Sense failed\n"));
+
+ //
+ // If the device fails to return changeable pages, then
+ // set the write cache changeable value to unknown.
+ //
+
+ writeCache->WriteCacheChangeable = WriteCacheChangeUnknown;
+ information = sizeof(STORAGE_WRITE_CACHE_PROPERTY);
+ goto WriteCacheExit;
+ }
+ }
+
+ //
+ // If the length is greater than length indicated by the mode data reset
+ // the data to the mode data.
+ //
+
+ if (length > (ULONG) (modeData->ModeDataLength + 1)) {
+ length = modeData->ModeDataLength + 1;
+ }
+
+ //
+ // Look for caching page in the returned mode page data.
+ //
+
+ pageData = ClassFindModePage((PCHAR) modeData,
+ length,
+ MODE_PAGE_CACHING,
+ TRUE);
+ //
+ // Check if valid caching page exists.
+ //
+
+ if (pageData == NULL) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "ClasspWriteCacheProperty: Unable to find caching mode page.\n"));
+ //
+ // Set write cache changeable value to unknown.
+ //
+ writeCache->WriteCacheChangeable = WriteCacheChangeUnknown;
+ } else {
+ writeCache->WriteCacheChangeable = pageData->WriteCacheEnable ?
+ WriteCacheChangeable : WriteCacheNotChangeable;
+ }
+
+ information = sizeof(STORAGE_WRITE_CACHE_PROPERTY);
+
+WriteCacheExit:
+
+ FREE_POOL(modeData);
+
+ //
+ // Set the size and status in IRP
+ //
+ Irp->IoStatus.Information = information;;
+ Irp->IoStatus.Status = status;
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+
+ return status;
+}
+
+ULONG
+ClasspCalculateLogicalSectorSize (
+ _In_ PDEVICE_OBJECT Fdo,
+ _In_ ULONG BytesPerBlockInBigEndian
+ )
+/*++
+ Convert the big-endian value.
+ if it's 0, default to the standard 512 bytes.
+ if it's not a power of 2 value, round down to power of 2.
+--*/
+{
+ ULONG logicalSectorSize;
+
+ REVERSE_BYTES(&logicalSectorSize, &BytesPerBlockInBigEndian);
+
+ if (logicalSectorSize == 0) {
+ logicalSectorSize = 512;
+ } else {
+ //
+ // Clear all but the highest set bit.
+ // That will give us a bytesPerSector value that is a power of 2.
+ //
+ if (logicalSectorSize & (logicalSectorSize-1)) {
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "FDO %ph has non-standard sector size 0x%x.", Fdo, logicalSectorSize));
+ do {
+ logicalSectorSize &= logicalSectorSize-1;
+ }
+ while (logicalSectorSize & (logicalSectorSize-1));
+ }
+ }
+
+ return logicalSectorSize;
+}
+
+NTSTATUS
+InterpretReadCapacity16Data (
+ _Inout_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ _In_ PREAD_CAPACITY16_DATA ReadCapacity16Data
+ )
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ USHORT lowestAlignedBlock;
+ USHORT logicalBlocksPerPhysicalBlock;
+ PCLASS_READ_CAPACITY16_DATA cachedData = &(FdoExtension->FunctionSupportInfo->ReadCapacity16Data);
+
+ // use Logical Sector Size from DiskGeometry to avoid duplicated calculation.
+ FdoExtension->FunctionSupportInfo->ReadCapacity16Data.BytesPerLogicalSector = ClasspCalculateLogicalSectorSize(FdoExtension->DeviceObject, ReadCapacity16Data->BytesPerBlock);
+
+ // FdoExtension->DiskGeometry.BytesPerSector might be 0 for class drivers that don't get READ CAPACITY info yet.
+ NT_ASSERT( (FdoExtension->DiskGeometry.BytesPerSector == 0) ||
+ (FdoExtension->DiskGeometry.BytesPerSector == FdoExtension->FunctionSupportInfo->ReadCapacity16Data.BytesPerLogicalSector) );
+
+ logicalBlocksPerPhysicalBlock = 1 << ReadCapacity16Data->LogicalPerPhysicalExponent;
+ lowestAlignedBlock = (ReadCapacity16Data->LowestAlignedBlock_MSB << 8) | ReadCapacity16Data->LowestAlignedBlock_LSB;
+
+ if (lowestAlignedBlock > logicalBlocksPerPhysicalBlock) {
+ // we get garbage data
+ status = STATUS_UNSUCCESSFUL;
+ } else {
+ // value of lowestAlignedBlock (from T10 spec) needs to be converted.
+ lowestAlignedBlock = (logicalBlocksPerPhysicalBlock - lowestAlignedBlock) % logicalBlocksPerPhysicalBlock;
+ }
+
+ if (NT_SUCCESS(status)) {
+ // fill output buffer
+ cachedData->BytesPerPhysicalSector = cachedData->BytesPerLogicalSector * logicalBlocksPerPhysicalBlock;
+ cachedData->BytesOffsetForSectorAlignment = cachedData->BytesPerLogicalSector * lowestAlignedBlock;
+
+ //
+ // Fill in the Logical Block Provisioning info. Note that we do not
+ // use these fields; we use the Provisioning Type and LBPRZ fields from
+ // the Logical Block Provisioning VPD page (0xB2).
+ //
+ cachedData->LBProvisioningEnabled = ReadCapacity16Data->LBPME;
+ cachedData->LBProvisioningReadZeros = ReadCapacity16Data->LBPRZ;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_INIT,
+ "InterpretReadCapacity16Data: Device\'s LBP enabled = %d\n",
+ cachedData->LBProvisioningEnabled));
+ }
+
+ return status;
+}
+
+NTSTATUS
+ClassReadCapacity16 (
+ _Inout_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ )
+/*
+ This routine may send down a READ CAPACITY 16 command to retrieve info.
+ The info will be cached in FdoExtension->LowerLayerSupport->AccessAlignment.
+
+ After info retrieving finished, this function sets following field:
+ FdoExtension->LowerLayerSupport->AccessAlignment.LowerLayerSupported = Supported;
+ to indicate that info has been cached.
+
+ NOTE: some future processes may use this funciton to send the command anyway, it will be caller's decision
+ on checking 'AccessAlignment.LowerLayerSupported' in case the cached info is good enough.
+*/
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ PREAD_CAPACITY16_DATA dataBuffer = NULL;
+ UCHAR bufferLength = sizeof(READ_CAPACITY16_DATA);
+ ULONG allocationBufferLength = bufferLength; //DMA buffer size for alignment
+ PCDB cdb;
+ ULONG dataTransferLength = 0;
+
+ //
+ // If the information retrieval has already been attempted, return the cached status.
+ //
+ if (FdoExtension->FunctionSupportInfo->ReadCapacity16Data.CommandStatus != -1) {
+ // get cached NTSTATUS from previous call.
+ return FdoExtension->FunctionSupportInfo->ReadCapacity16Data.CommandStatus;
+ }
+
+ if (ClasspIsObsoletePortDriver(FdoExtension)) {
+ FdoExtension->FunctionSupportInfo->ReadCapacity16Data.CommandStatus = STATUS_NOT_IMPLEMENTED;
+ return STATUS_NOT_IMPLEMENTED;
+ }
+
+#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 = (PREAD_CAPACITY16_DATA)ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned, allocationBufferLength, '4CcS');
+#else
+ dataBuffer = (PREAD_CAPACITY16_DATA)ExAllocatePoolWithTag(NonPagedPoolNx, bufferLength, '4CcS');
+#endif
+
+ if (dataBuffer == NULL) {
+ // return without updating FdoExtension->FunctionSupportInfo->ReadCapacity16Data.CommandStatus
+ // the field will remain value as "-1", so that the command will be attempted next time this function is called.
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ RtlZeroMemory(dataBuffer, allocationBufferLength);
+
+ //
+ // Initialize the SRB.
+ //
+ if (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.
+ //
+ NT_ASSERT(FALSE);
+ }
+ } else {
+ RtlZeroMemory(Srb, sizeof(SCSI_REQUEST_BLOCK));
+ Srb->Length = sizeof(SCSI_REQUEST_BLOCK);
+ Srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
+ }
+
+ //prepare the Srb
+ if (NT_SUCCESS(status))
+ {
+ SrbSetTimeOutValue(Srb, FdoExtension->TimeOutValue);
+ SrbSetRequestTag(Srb, SP_UNTAGGED);
+ SrbSetRequestAttribute(Srb, SRB_SIMPLE_TAG_REQUEST);
+ SrbAssignSrbFlags(Srb, FdoExtension->SrbFlags);
+
+ SrbSetCdbLength(Srb, 16);
+
+ cdb = SrbGetCdb(Srb);
+ cdb->READ_CAPACITY16.OperationCode = SCSIOP_READ_CAPACITY16;
+ cdb->READ_CAPACITY16.ServiceAction = SERVICE_ACTION_READ_CAPACITY16;
+ cdb->READ_CAPACITY16.AllocationLength[3] = bufferLength;
+
+ status = ClassSendSrbSynchronous(FdoExtension->DeviceObject,
+ Srb,
+ dataBuffer,
+ allocationBufferLength,
+ FALSE);
+
+ dataTransferLength = SrbGetDataTransferLength(Srb);
+ }
+
+ if (NT_SUCCESS(status) && (dataTransferLength < 16))
+ {
+ // the device should return at least 16 bytes of data for this command.
+ status = STATUS_INFO_LENGTH_MISMATCH;
+ }
+
+ //
+ // Handle the case where we get back STATUS_DATA_OVERRUN b/c the input
+ // buffer was larger than necessary.
+ //
+ if (status == STATUS_DATA_OVERRUN && dataTransferLength < bufferLength)
+ {
+ status = STATUS_SUCCESS;
+ }
+
+ if (NT_SUCCESS(status))
+ {
+ // cache data into FdoExtension
+ status = InterpretReadCapacity16Data(FdoExtension, dataBuffer);
+ }
+
+ // cache the status indicates that this funciton has been called.
+ FdoExtension->FunctionSupportInfo->ReadCapacity16Data.CommandStatus = status;
+
+ ExFreePool(dataBuffer);
+
+ return status;
+}
+
+NTSTATUS ClasspAccessAlignmentProperty(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ )
+/*
+ At first time of receiving the request, this function will forward it to lower stack to determine if it's supportted.
+ If it's not supported, SCSIOP_READ_CAPACITY16 will be sent down to retrieve the information.
+*/
+{
+ NTSTATUS status = STATUS_UNSUCCESSFUL;
+
+ PCOMMON_DEVICE_EXTENSION commonExtension = (PCOMMON_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
+ PSTORAGE_PROPERTY_QUERY query = (PSTORAGE_PROPERTY_QUERY)Irp->AssociatedIrp.SystemBuffer;
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ ULONG length = 0;
+ ULONG information = 0;
+
+ PSTORAGE_ACCESS_ALIGNMENT_DESCRIPTOR accessAlignment;
+
+ //
+ // check registry setting and fail the IOCTL if it's required.
+ // this registry setting can be used to work around issues which upper layer doesn't support large physical sector size.
+ //
+ if (fdoExtension->FunctionSupportInfo->RegAccessAlignmentQueryNotSupported) {
+ status = STATUS_NOT_SUPPORTED;
+ goto Exit;
+ }
+
+ if ( (DeviceObject->DeviceType != FILE_DEVICE_DISK) ||
+ (TEST_FLAG(DeviceObject->Characteristics, FILE_FLOPPY_DISKETTE)) ||
+ (fdoExtension->FunctionSupportInfo->LowerLayerSupport.AccessAlignmentProperty == Supported) ) {
+ // if it's not disk, forward the request to lower layer,
+ // if the IOCTL is supported by lower stack, forward it down.
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ return status;
+ }
+
+ //
+ // Check proper query type.
+ //
+
+ if (query->QueryType == PropertyExistsQuery) {
+ status = STATUS_SUCCESS;
+ goto Exit;
+ } else if (query->QueryType != PropertyStandardQuery) {
+ status = STATUS_NOT_SUPPORTED;
+ goto Exit;
+ }
+
+ //
+ // Request validation.
+ // Note that InputBufferLength and IsFdo have been validated beforing entering this routine.
+ //
+
+ if (KeGetCurrentIrql() >= DISPATCH_LEVEL) {
+ NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
+ status = STATUS_INVALID_LEVEL;
+ goto Exit;
+ }
+
+ // do not touch this buffer because it can still be used as input buffer for lower layer in 'SupportUnknown' case.
+ accessAlignment = (PSTORAGE_ACCESS_ALIGNMENT_DESCRIPTOR)Irp->AssociatedIrp.SystemBuffer;
+
+ length = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
+
+ if (length < sizeof(STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR)) {
+
+ if (length >= sizeof(STORAGE_DESCRIPTOR_HEADER)) {
+
+ information = sizeof(STORAGE_DESCRIPTOR_HEADER);
+ accessAlignment->Version = sizeof(STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR);
+ accessAlignment->Size = sizeof(STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR);
+ status = STATUS_SUCCESS;
+ goto Exit;
+ }
+
+ status = STATUS_BUFFER_TOO_SMALL;
+ goto Exit;
+ }
+
+ // not support Cache Line,
+ // 'BytesPerCacheLine' and 'BytesOffsetForCacheAlignment' fields are zero-ed.
+
+ //
+ // note that 'Supported' case has been handled at the beginning of this function.
+ //
+ switch (fdoExtension->FunctionSupportInfo->LowerLayerSupport.AccessAlignmentProperty) {
+ case SupportUnknown: {
+ // send down request and wait for the request to complete.
+ status = ClassForwardIrpSynchronous(commonExtension, Irp);
+
+ if (ClasspLowerLayerNotSupport(status)) {
+ // case 1: the request is not supported by lower layer, sends down command
+ // some port drivers (or filter drivers) return STATUS_INVALID_DEVICE_REQUEST if a request is not supported.
+
+ // ClassReadCapacity16() will either return status from cached data or send command to retrieve the information.
+ if (ClasspIsObsoletePortDriver(fdoExtension) == FALSE) {
+ status = ClassReadCapacity16(fdoExtension, Srb);
+ } else {
+ fdoExtension->FunctionSupportInfo->ReadCapacity16Data.CommandStatus = status;
+ }
+
+ // data is ready in fdoExtension
+ // set the support status after the SCSI command is executed to avoid racing condition between multiple same type of requests.
+ fdoExtension->FunctionSupportInfo->LowerLayerSupport.AccessAlignmentProperty = NotSupported;
+
+ if (NT_SUCCESS(status)) {
+ // fill output buffer
+ RtlZeroMemory(accessAlignment, length);
+ accessAlignment->Version = sizeof(STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR);
+ accessAlignment->Size = sizeof(STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR);
+ accessAlignment->BytesPerLogicalSector = fdoExtension->FunctionSupportInfo->ReadCapacity16Data.BytesPerLogicalSector;
+ accessAlignment->BytesPerPhysicalSector = fdoExtension->FunctionSupportInfo->ReadCapacity16Data.BytesPerPhysicalSector;
+ accessAlignment->BytesOffsetForSectorAlignment = fdoExtension->FunctionSupportInfo->ReadCapacity16Data.BytesOffsetForSectorAlignment;
+
+ // set returned data length
+ information = sizeof(STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR);
+ } else {
+ information = 0;
+ }
+ goto Exit;
+ } else {
+ // case 2: the request is supported and it completes successfully
+ // case 3: the request is supported by lower stack but other failure status is returned.
+ // from now on, the same request will be send down to lower stack directly.
+ fdoExtension->FunctionSupportInfo->LowerLayerSupport.AccessAlignmentProperty = Supported;
+ information = (ULONG)Irp->IoStatus.Information;
+
+
+ goto Exit;
+ }
+ break;
+ }
+
+ case NotSupported: {
+
+ // ClassReadCapacity16() will either return status from cached data or send command to retrieve the information.
+ status = ClassReadCapacity16(fdoExtension, Srb);
+
+ if (NT_SUCCESS(status)) {
+ RtlZeroMemory(accessAlignment, length);
+ accessAlignment->Version = sizeof(STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR);
+ accessAlignment->Size = sizeof(STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR);
+ accessAlignment->BytesPerLogicalSector = fdoExtension->FunctionSupportInfo->ReadCapacity16Data.BytesPerLogicalSector;
+ accessAlignment->BytesPerPhysicalSector = fdoExtension->FunctionSupportInfo->ReadCapacity16Data.BytesPerPhysicalSector;
+ accessAlignment->BytesOffsetForSectorAlignment = fdoExtension->FunctionSupportInfo->ReadCapacity16Data.BytesOffsetForSectorAlignment;
+
+ information = sizeof(STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR);
+ } else {
+ information = 0;
+ }
+ goto Exit;
+
+ break;
+ }
+
+ case Supported: {
+ NT_ASSERT(FALSE); // this case is handled at the begining of the function.
+ status = STATUS_INTERNAL_ERROR;
+ break;
+ }
+
+ } // end of switch (fdoExtension->FunctionSupportInfo->LowerLayerSupport.AccessAlignmentProperty)
+
+Exit:
+
+ //
+ // Set the size and status in IRP
+ //
+ Irp->IoStatus.Information = information;
+ Irp->IoStatus.Status = status;
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+
+ return status;
+}
+
+NTSTATUS
+__inline
+IncursSeekPenalty (
+ _In_ USHORT MediumRotationRate,
+ _In_ PBOOLEAN IncursSeekPenalty
+ )
+{
+ NTSTATUS status;
+
+ if (MediumRotationRate == 0x0001) {
+ // Non-rotating media (e.g., solid state device)
+ *IncursSeekPenalty = FALSE;
+ status = STATUS_SUCCESS;
+ } else if ( (MediumRotationRate >= 0x401) &&
+ (MediumRotationRate <= 0xFFFE) ) {
+ // Nominal media rotation rate in rotations per minute (rpm)
+ *IncursSeekPenalty = TRUE;
+ status = STATUS_SUCCESS;
+ } else {
+ // Unknown cases:
+ // 0 - Rate not reported
+ // 0002h-0400h - Reserved
+ // FFFFh - Reserved
+ status = STATUS_UNSUCCESSFUL;
+ }
+
+ return status;
+}
+
+
+NTSTATUS
+ClasspDeviceMediaTypeProperty(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _Inout_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ )
+/*++
+
+Routine Description:
+
+ This routine returns the medium product type reported by the device for the associated LU.
+
+ 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 - The SRB associated with the request
+
+Return Value:
+
+ NTSTATUS code
+
+--*/
+{
+ NTSTATUS status = STATUS_UNSUCCESSFUL;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
+ PSTORAGE_PROPERTY_QUERY query = (PSTORAGE_PROPERTY_QUERY)Irp->AssociatedIrp.SystemBuffer;
+ PSTORAGE_MEDIUM_PRODUCT_TYPE_DESCRIPTOR pDesc = (PSTORAGE_MEDIUM_PRODUCT_TYPE_DESCRIPTOR)Irp->AssociatedIrp.SystemBuffer;
+ PIO_STACK_LOCATION irpStack;
+ ULONG length = 0;
+ ULONG information = 0;
+
+ irpStack = IoGetCurrentIrpStackLocation(Irp);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspDeviceMediaTypeProperty (%p): Entering function.\n",
+ DeviceObject));
+
+ //
+ // Check proper query type.
+ //
+ if (query->QueryType == PropertyExistsQuery) {
+
+ //
+ // In order to maintain consistency with the how the rest of the properties
+ // are handled, always return success for PropertyExistsQuery.
+ //
+ status = STATUS_SUCCESS;
+ goto __ClasspDeviceMediaTypeProperty_Exit;
+
+ } else if (query->QueryType != PropertyStandardQuery) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspDeviceMediaTypeProperty (%p): Unsupported query type %x for media type property.\n",
+ DeviceObject,
+ query->QueryType));
+
+ status = STATUS_NOT_SUPPORTED;
+ goto __ClasspDeviceMediaTypeProperty_Exit;
+ }
+
+ //
+ // Validate the request.
+ // InputBufferLength and IsFdo have already been validated.
+ //
+
+ if (KeGetCurrentIrql() >= DISPATCH_LEVEL) {
+
+ NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspDeviceMediaTypeProperty (%p): Query property for media type at incorrect IRQL.\n",
+ DeviceObject));
+
+ status = STATUS_INVALID_LEVEL;
+ goto __ClasspDeviceMediaTypeProperty_Exit;
+ }
+
+ length = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
+
+ if (length >= sizeof(STORAGE_DESCRIPTOR_HEADER)) {
+
+ information = sizeof(STORAGE_MEDIUM_PRODUCT_TYPE_DESCRIPTOR);
+ pDesc->Version = sizeof(STORAGE_MEDIUM_PRODUCT_TYPE_DESCRIPTOR);
+ pDesc->Size = sizeof(STORAGE_MEDIUM_PRODUCT_TYPE_DESCRIPTOR);
+ } else {
+
+ status = STATUS_BUFFER_TOO_SMALL;
+ goto __ClasspDeviceMediaTypeProperty_Exit;
+ }
+
+ if (length < sizeof(STORAGE_MEDIUM_PRODUCT_TYPE_DESCRIPTOR)) {
+
+ status = STATUS_SUCCESS;
+ goto __ClasspDeviceMediaTypeProperty_Exit;
+ }
+
+ //
+ // Only query BlockDeviceCharacteristics VPD page if device support has been confirmed.
+ //
+ if (fdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceCharacteristics == TRUE) {
+ status = ClasspDeviceGetBlockDeviceCharacteristicsVPDPage(fdoExtension, Srb);
+ } else {
+ //
+ // Otherwise device was previously found lacking support for this VPD page. Fail the request.
+ //
+ status = STATUS_INVALID_DEVICE_REQUEST;
+ goto __ClasspDeviceMediaTypeProperty_Exit;
+ }
+
+ if (!NT_SUCCESS(status)) {
+
+ status = fdoExtension->FunctionSupportInfo->DeviceCharacteristicsData.CommandStatus;
+ information = 0;
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspDeviceGetBlockDeviceCharacteristicsVPDPage (%p): VPD retrieval fails with %x.\n",
+ DeviceObject,
+ status));
+
+ goto __ClasspDeviceMediaTypeProperty_Exit;
+ }
+
+ //
+ // Fill in the output buffer. All data is copied from the FDO extension, cached
+ // from device response to earlier VPD_BLOCK_DEVICE_CHARACTERISTICS query.
+ //
+ pDesc->MediumProductType = fdoExtension->FunctionSupportInfo->DeviceCharacteristicsData.MediumProductType;
+ status = STATUS_SUCCESS;
+
+__ClasspDeviceMediaTypeProperty_Exit:
+
+ //
+ // Set the size and status in IRP
+ //
+ Irp->IoStatus.Information = information;
+ Irp->IoStatus.Status = status;
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspDeviceMediaTypeProperty (%p): Exiting function with status %x.\n",
+ DeviceObject,
+ status));
+
+ return status;
+}
+
+NTSTATUS ClasspDeviceGetBlockDeviceCharacteristicsVPDPage(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension,
+ _In_ PSCSI_REQUEST_BLOCK Srb
+ )
+/*
+Routine Description:
+
+ This function sends an INQUIRY command request for VPD_BLOCK_DEVICE_CHARACTERISTICS to
+ the device. Relevant data from the response is cached in the FDO extension.
+
+Arguments:
+ FdoExtension: The FDO extension of the device to which the INQUIRY command will be sent.
+ Srb: Allocated by the caller.
+ SrbSize: The size of the Srb buffer in bytes.
+
+Return Value:
+
+ STATUS_INVALID_PARAMETER: May be returned if the LogPage buffer is NULL or
+ not large enough.
+ STATUS_SUCCESS: The log page was obtained and placed in the LogPage buffer.
+
+ This function may return other NTSTATUS codes from internal function calls.
+--*/
+{
+ NTSTATUS status = STATUS_UNSUCCESSFUL;
+ PCDB cdb;
+ UCHAR bufferLength = sizeof(VPD_BLOCK_DEVICE_CHARACTERISTICS_PAGE); // data is 64 bytes
+ ULONG allocationBufferLength = bufferLength;
+ PVPD_BLOCK_DEVICE_CHARACTERISTICS_PAGE dataBuffer = NULL;
+
+
+#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 = (PVPD_BLOCK_DEVICE_CHARACTERISTICS_PAGE)ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned,
+ allocationBufferLength,
+ '5CcS'
+ );
+#else
+
+ dataBuffer = (PVPD_BLOCK_DEVICE_CHARACTERISTICS_PAGE)ExAllocatePoolWithTag(NonPagedPoolNx,
+ bufferLength,
+ '5CcS'
+ );
+#endif
+ if (dataBuffer == NULL) {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto Exit;
+ }
+
+ RtlZeroMemory(dataBuffer, allocationBufferLength);
+
+ // prepare the Srb
+ 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; //EVPD bit
+ cdb->CDB6INQUIRY3.PageCode = VPD_BLOCK_DEVICE_CHARACTERISTICS;
+ cdb->CDB6INQUIRY3.AllocationLength = bufferLength; //AllocationLength field in CDB6INQUIRY3 is only one byte.
+
+ status = ClassSendSrbSynchronous(fdoExtension->CommonExtension.DeviceObject,
+ Srb,
+ dataBuffer,
+ allocationBufferLength,
+ FALSE);
+ if (NT_SUCCESS(status)) {
+ if (SrbGetDataTransferLength(Srb) < 0x8) {
+ // the device should return at least 8 bytes of data for use.
+ status = STATUS_UNSUCCESSFUL;
+ } else if ( (dataBuffer->PageLength != 0x3C) || (dataBuffer->PageCode != VPD_BLOCK_DEVICE_CHARACTERISTICS) ) {
+ // 'PageLength' shall be 0x3C; and 'PageCode' shall match.
+ status = STATUS_UNSUCCESSFUL;
+ } else {
+ // cache data into fdoExtension
+ fdoExtension->FunctionSupportInfo->DeviceCharacteristicsData.MediumRotationRate = (dataBuffer->MediumRotationRateMsb << 8) |
+ dataBuffer->MediumRotationRateLsb;
+ fdoExtension->FunctionSupportInfo->DeviceCharacteristicsData.MediumProductType = dataBuffer->MediumProductType;
+ fdoExtension->FunctionSupportInfo->DeviceCharacteristicsData.NominalFormFactor = dataBuffer->NominalFormFactor;
+ }
+ } else {
+ // the command failed, surface up the command error from 'status' variable. Nothing to do here.
+ }
+
+Exit:
+ if (dataBuffer != NULL) {
+ ExFreePool(dataBuffer);
+ }
+
+ return status;
+}
+
+NTSTATUS ClasspDeviceSeekPenaltyProperty(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ )
+/*
+ At first time of receiving the request, this function will forward it to lower stack to determine if it's supportted.
+ If it's not supported, INQUIRY (Block Device Characteristics VPD page) will be sent down to retrieve the information.
+*/
+{
+ NTSTATUS status = STATUS_UNSUCCESSFUL;
+
+ PCOMMON_DEVICE_EXTENSION commonExtension = (PCOMMON_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
+ PSTORAGE_PROPERTY_QUERY query = (PSTORAGE_PROPERTY_QUERY)Irp->AssociatedIrp.SystemBuffer;
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ ULONG length = 0;
+ ULONG information = 0;
+ BOOLEAN incursSeekPenalty = TRUE;
+ PDEVICE_SEEK_PENALTY_DESCRIPTOR seekPenalty;
+
+ if ( (DeviceObject->DeviceType != FILE_DEVICE_DISK) ||
+ (TEST_FLAG(DeviceObject->Characteristics, FILE_FLOPPY_DISKETTE)) ||
+ (fdoExtension->FunctionSupportInfo->LowerLayerSupport.SeekPenaltyProperty == Supported) ) {
+ // if it's not disk, forward the request to lower layer,
+ // if the IOCTL is supported by lower stack, forward it down.
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ return status;
+ }
+
+ //
+ // Check proper query type.
+ //
+
+ if (query->QueryType == PropertyExistsQuery) {
+ status = STATUS_SUCCESS;
+ goto Exit;
+ } else if (query->QueryType != PropertyStandardQuery) {
+ status = STATUS_NOT_SUPPORTED;
+ goto Exit;
+ }
+
+ //
+ // Request validation.
+ // Note that InputBufferLength and IsFdo have been validated beforing entering this routine.
+ //
+
+ if (KeGetCurrentIrql() >= DISPATCH_LEVEL) {
+ NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
+ status = STATUS_INVALID_LEVEL;
+ goto Exit;
+ }
+
+ // do not touch this buffer because it can still be used as input buffer for lower layer in 'SupportUnknown' case.
+ seekPenalty = (PDEVICE_SEEK_PENALTY_DESCRIPTOR)Irp->AssociatedIrp.SystemBuffer;
+
+ length = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
+
+ if (length < sizeof(DEVICE_SEEK_PENALTY_DESCRIPTOR)) {
+
+ if (length >= sizeof(STORAGE_DESCRIPTOR_HEADER)) {
+
+ information = sizeof(STORAGE_DESCRIPTOR_HEADER);
+ seekPenalty->Version = sizeof(DEVICE_SEEK_PENALTY_DESCRIPTOR);
+ seekPenalty->Size = sizeof(DEVICE_SEEK_PENALTY_DESCRIPTOR);
+ status = STATUS_SUCCESS;
+ goto Exit;
+ }
+
+ status = STATUS_BUFFER_TOO_SMALL;
+ goto Exit;
+ }
+
+ //
+ // note that 'Supported' case has been handled at the beginning of this function.
+ //
+ switch (fdoExtension->FunctionSupportInfo->LowerLayerSupport.SeekPenaltyProperty) {
+ case SupportUnknown: {
+ // send down request and wait for the request to complete.
+ status = ClassForwardIrpSynchronous(commonExtension, Irp);
+
+ if (ClasspLowerLayerNotSupport(status)) {
+ // case 1: the request is not supported by lower layer, sends down command
+ // some port drivers (or filter drivers) return STATUS_INVALID_DEVICE_REQUEST if a request is not supported.
+
+ // send INQUIRY command if the VPD page is supported.
+ if (fdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceCharacteristics == TRUE) {
+ status = ClasspDeviceGetBlockDeviceCharacteristicsVPDPage(fdoExtension, Srb);
+ } else {
+ // the INQUIRY - VPD page command to discover the info is not supported, fail the request.
+ status = STATUS_INVALID_DEVICE_REQUEST;
+ }
+
+ if (NT_SUCCESS(status)) {
+ status = IncursSeekPenalty(fdoExtension->FunctionSupportInfo->DeviceCharacteristicsData.MediumRotationRate, &incursSeekPenalty);
+ }
+
+ fdoExtension->FunctionSupportInfo->DeviceCharacteristicsData.CommandStatus = status;
+
+ // data is ready in fdoExtension
+ // set the support status after the SCSI command is executed to avoid racing condition between multiple same type of requests.
+ fdoExtension->FunctionSupportInfo->LowerLayerSupport.SeekPenaltyProperty = NotSupported;
+
+ // fill output buffer
+ if (NT_SUCCESS(status)) {
+ RtlZeroMemory(seekPenalty, length);
+ seekPenalty->Version = sizeof(DEVICE_SEEK_PENALTY_DESCRIPTOR);
+ seekPenalty->Size = sizeof(DEVICE_SEEK_PENALTY_DESCRIPTOR);
+ seekPenalty->IncursSeekPenalty = incursSeekPenalty;
+ information = sizeof(DEVICE_SEEK_PENALTY_DESCRIPTOR);
+
+
+ } else {
+ information = 0;
+ }
+
+ } else {
+ // case 2: the request is supported and it completes successfully
+ // case 3: the request is supported by lower stack but other failure status is returned.
+ // from now on, the same request will be send down to lower stack directly.
+ fdoExtension->FunctionSupportInfo->LowerLayerSupport.SeekPenaltyProperty = Supported;
+ information = (ULONG)Irp->IoStatus.Information;
+
+ }
+
+
+ goto Exit;
+
+ break;
+ }
+
+ case NotSupported: {
+ status = fdoExtension->FunctionSupportInfo->DeviceCharacteristicsData.CommandStatus;
+
+ if (NT_SUCCESS(status)) {
+ status = IncursSeekPenalty(fdoExtension->FunctionSupportInfo->DeviceCharacteristicsData.MediumRotationRate, &incursSeekPenalty);
+ }
+
+ if (NT_SUCCESS(status)) {
+ RtlZeroMemory(seekPenalty, length);
+ seekPenalty->Version = sizeof(DEVICE_SEEK_PENALTY_DESCRIPTOR);
+ seekPenalty->Size = sizeof(DEVICE_SEEK_PENALTY_DESCRIPTOR);
+ seekPenalty->IncursSeekPenalty = incursSeekPenalty;
+ information = sizeof(DEVICE_SEEK_PENALTY_DESCRIPTOR);
+
+ } else {
+ information = 0;
+ }
+
+ goto Exit;
+
+ break;
+ }
+
+ case Supported: {
+ NT_ASSERT(FALSE); // this case is handled at the begining of the function.
+ break;
+ }
+
+ } // end of switch (fdoExtension->FunctionSupportInfo->LowerLayerSupport.SeekPenaltyProperty)
+
+Exit:
+
+ //
+ // Set the size and status in IRP
+ //
+ Irp->IoStatus.Information = information;;
+ Irp->IoStatus.Status = status;
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+
+ return status;
+}
+
+NTSTATUS ClasspDeviceGetLBProvisioningVPDPage(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _Inout_opt_ PSCSI_REQUEST_BLOCK Srb
+ )
+{
+ NTSTATUS status = STATUS_UNSUCCESSFUL;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
+ USHORT pageLength = 0;
+
+ PVOID dataBuffer = NULL;
+ UCHAR bufferLength = VPD_MAX_BUFFER_SIZE; // use biggest buffer possible
+ ULONG allocationBufferLength = bufferLength; // Since the CDB size may differ from the actual buffer allocation
+ PCDB cdb;
+ ULONG dataTransferLength = 0;
+ PVPD_LOGICAL_BLOCK_PROVISIONING_PAGE lbProvisioning = NULL;
+
+ //
+ // if the informaiton has been attempted to retrieve, return the cached status.
+ //
+ if (fdoExtension->FunctionSupportInfo->LBProvisioningData.CommandStatus != -1) {
+ // get cached NTSTATUS from previous call.
+ return fdoExtension->FunctionSupportInfo->LBProvisioningData.CommandStatus;
+ }
+
+ //
+ // Initialize LBProvisioningData fields to 'unsupported' defaults.
+ //
+ fdoExtension->FunctionSupportInfo->LBProvisioningData.ProvisioningType = PROVISIONING_TYPE_UNKNOWN;
+ fdoExtension->FunctionSupportInfo->LBProvisioningData.LBPRZ = FALSE;
+ fdoExtension->FunctionSupportInfo->LBProvisioningData.LBPU = FALSE;
+ fdoExtension->FunctionSupportInfo->LBProvisioningData.ANC_SUP = FALSE;
+ fdoExtension->FunctionSupportInfo->LBProvisioningData.ThresholdExponent = 0;
+
+ //
+ // Try to get the Thin Provisioning VPD page (0xB2), if it is supported.
+ //
+ if (fdoExtension->FunctionSupportInfo->ValidInquiryPages.LBProvisioning == TRUE &&
+ Srb != NULL)
+ {
+#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,'0CcS');
+#else
+ dataBuffer = ExAllocatePoolWithTag(NonPagedPoolNx, bufferLength,'0CcS');
+#endif
+ if (dataBuffer == NULL) {
+ // return without updating FdoExtension->FunctionSupportInfo->LBProvisioningData.CommandStatus
+ // the field will remain value as "-1", so that the command will be attempted next time this function is called.
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto Exit;
+ }
+
+ lbProvisioning = (PVPD_LOGICAL_BLOCK_PROVISIONING_PAGE)dataBuffer;
+
+ RtlZeroMemory(dataBuffer, allocationBufferLength);
+
+ if (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.
+ //
+ NT_ASSERT(FALSE);
+ }
+ } else {
+ RtlZeroMemory(Srb, sizeof(SCSI_REQUEST_BLOCK));
+ Srb->Length = sizeof(SCSI_REQUEST_BLOCK);
+ Srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
+ status = STATUS_SUCCESS;
+ }
+
+ if (NT_SUCCESS(status)) {
+ // prepare the Srb
+ 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; //EVPD bit
+ cdb->CDB6INQUIRY3.PageCode = VPD_LOGICAL_BLOCK_PROVISIONING;
+ cdb->CDB6INQUIRY3.AllocationLength = bufferLength; //AllocationLength field in CDB6INQUIRY3 is only one byte.
+
+ status = ClassSendSrbSynchronous(fdoExtension->DeviceObject,
+ Srb,
+ dataBuffer,
+ allocationBufferLength,
+ FALSE);
+
+ dataTransferLength = SrbGetDataTransferLength(Srb);
+ }
+
+ //
+ // Handle the case where we get back STATUS_DATA_OVERRUN b/c the input
+ // buffer was larger than necessary.
+ //
+ if (status == STATUS_DATA_OVERRUN && dataTransferLength < bufferLength)
+ {
+ status = STATUS_SUCCESS;
+ }
+
+ if (NT_SUCCESS(status)) {
+ REVERSE_BYTES_SHORT(&pageLength, &(lbProvisioning->PageLength));
+ }
+
+ if ( NT_SUCCESS(status) &&
+ ((dataTransferLength < 0x08) ||
+ (pageLength < (FIELD_OFFSET(VPD_LOGICAL_BLOCK_PROVISIONING_PAGE, Reserved2) - FIELD_OFFSET(VPD_LOGICAL_BLOCK_PROVISIONING_PAGE,ThresholdExponent))) ||
+ (lbProvisioning->PageCode != VPD_LOGICAL_BLOCK_PROVISIONING)) ) {
+ // the device should return at least 8 bytes of data for use.
+ // 'PageCode' shall match and we need all the relevant data after the header.
+ status = STATUS_INFO_LENGTH_MISMATCH;
+ }
+
+ //
+ // Fill in the FDO extension with either the data from the VPD page, or
+ // use defaults if there was an error.
+ //
+ if (NT_SUCCESS(status))
+ {
+ fdoExtension->FunctionSupportInfo->LBProvisioningData.ProvisioningType = lbProvisioning->ProvisioningType;
+ fdoExtension->FunctionSupportInfo->LBProvisioningData.LBPRZ = lbProvisioning->LBPRZ;
+ fdoExtension->FunctionSupportInfo->LBProvisioningData.LBPU = lbProvisioning->LBPU;
+ fdoExtension->FunctionSupportInfo->LBProvisioningData.ANC_SUP = lbProvisioning->ANC_SUP;
+ fdoExtension->FunctionSupportInfo->LBProvisioningData.ThresholdExponent = lbProvisioning->ThresholdExponent;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "ClasspDeviceGetLBProvisioningVPDPage (%p): %s %s (rev %s) reported following parameters: \
+ \n\t\t\tProvisioningType: %u \
+ \n\t\t\tLBPRZ: %u \
+ \n\t\t\tLBPU: %u \
+ \n\t\t\tANC_SUP: %I64u \
+ \n\t\t\tThresholdExponent: %u\n",
+ DeviceObject,
+ (PCSZ)(((PUCHAR)fdoExtension->DeviceDescriptor) + fdoExtension->DeviceDescriptor->VendorIdOffset),
+ (PCSZ)(((PUCHAR)fdoExtension->DeviceDescriptor) + fdoExtension->DeviceDescriptor->ProductIdOffset),
+ (PCSZ)(((PUCHAR)fdoExtension->DeviceDescriptor) + fdoExtension->DeviceDescriptor->ProductRevisionOffset),
+ lbProvisioning->ProvisioningType,
+ lbProvisioning->LBPRZ,
+ lbProvisioning->LBPU,
+ lbProvisioning->ANC_SUP,
+ lbProvisioning->ThresholdExponent));
+ }
+ } else {
+ status = STATUS_INVALID_DEVICE_REQUEST;
+ }
+
+ fdoExtension->FunctionSupportInfo->LBProvisioningData.CommandStatus = status;
+
+Exit:
+ FREE_POOL(dataBuffer);
+
+ return status;
+}
+
+
+NTSTATUS ClasspDeviceGetBlockLimitsVPDPage(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ _Inout_bytecount_(SrbSize) PSCSI_REQUEST_BLOCK Srb,
+ _In_ ULONG SrbSize,
+ _Out_ PCLASS_VPD_B0_DATA BlockLimitsData
+ )
+{
+ NTSTATUS status = STATUS_UNSUCCESSFUL;
+ PVOID dataBuffer = NULL;
+ UCHAR bufferLength = VPD_MAX_BUFFER_SIZE; // use biggest buffer possible
+ ULONG allocationBufferLength = bufferLength;
+ PCDB cdb;
+ PVPD_BLOCK_LIMITS_PAGE blockLimits = NULL;
+ ULONG dataTransferLength = 0;
+
+ //
+ // Set default values for UNMAP parameters based upon UNMAP support or lack
+ // thereof.
+ //
+ if (FdoExtension->FunctionSupportInfo->LBProvisioningData.LBPU) {
+ //
+ // If UNMAP is supported, we default to the maximum LBA count and
+ // block descriptor count. We also default the UNMAP granularity to
+ // a single block and specify no granularity alignment.
+ //
+ BlockLimitsData->MaxUnmapLbaCount = (ULONG)-1;
+ BlockLimitsData->MaxUnmapBlockDescrCount = (ULONG)-1;
+ BlockLimitsData->OptimalUnmapGranularity = 1;
+ BlockLimitsData->UnmapGranularityAlignment = 0;
+ BlockLimitsData->UGAVALID = FALSE;
+ } else {
+ BlockLimitsData->MaxUnmapLbaCount = 0;
+ BlockLimitsData->MaxUnmapBlockDescrCount = 0;
+ BlockLimitsData->OptimalUnmapGranularity = 0;
+ BlockLimitsData->UnmapGranularityAlignment = 0;
+ BlockLimitsData->UGAVALID = FALSE;
+ }
+
+ //
+ // Try to get the Block Limits VPD page (0xB0), if it is supported.
+ //
+ if (FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockLimits == TRUE)
+ {
+#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, '0CcS');
+#else
+ dataBuffer = ExAllocatePoolWithTag(NonPagedPoolNx, bufferLength, '0CcS');
+#endif
+ if (dataBuffer == NULL)
+ {
+ // return without updating FdoExtension->FunctionSupportInfo->BlockLimitsData.CommandStatus
+ // the field will remain value as "-1", so that the command will be attempted next time this function is called.
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto Exit;
+ }
+
+ blockLimits = (PVPD_BLOCK_LIMITS_PAGE)dataBuffer;
+
+ RtlZeroMemory(dataBuffer, allocationBufferLength);
+
+ if (FdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+
+ #pragma prefast(suppress:26015, "InitializeStorageRequestBlock ensures buffer access is bounded")
+ status = InitializeStorageRequestBlock((PSTORAGE_REQUEST_BLOCK)Srb,
+ STORAGE_ADDRESS_TYPE_BTL8,
+ SrbSize,
+ 1,
+ SrbExDataTypeScsiCdb16);
+
+ if (NT_SUCCESS(status)) {
+ ((PSTORAGE_REQUEST_BLOCK)Srb)->SrbFunction = SRB_FUNCTION_EXECUTE_SCSI;
+ } else {
+ //
+ // Should not occur.
+ //
+ NT_ASSERT(FALSE);
+ }
+ } else {
+ RtlZeroMemory(Srb, sizeof(SCSI_REQUEST_BLOCK));
+ Srb->Length = sizeof(SCSI_REQUEST_BLOCK);
+ Srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
+ status = STATUS_SUCCESS;
+ }
+
+ if (NT_SUCCESS(status)) {
+ // prepare the Srb
+ 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; //EVPD bit
+ cdb->CDB6INQUIRY3.PageCode = VPD_BLOCK_LIMITS;
+ cdb->CDB6INQUIRY3.AllocationLength = bufferLength; //AllocationLength field in CDB6INQUIRY3 is only one byte.
+
+ status = ClassSendSrbSynchronous(FdoExtension->DeviceObject,
+ Srb,
+ dataBuffer,
+ allocationBufferLength,
+ FALSE);
+ dataTransferLength = SrbGetDataTransferLength(Srb);
+ }
+
+ //
+ // Handle the case where we get back STATUS_DATA_OVERRUN b/c the input
+ // buffer was larger than necessary.
+ //
+
+ if (status == STATUS_DATA_OVERRUN && dataTransferLength < bufferLength)
+ {
+ status = STATUS_SUCCESS;
+ }
+
+ if (NT_SUCCESS(status))
+ {
+ USHORT pageLength;
+ REVERSE_BYTES_SHORT(&pageLength, &(blockLimits->PageLength));
+
+ //
+ // Regardless of the device's support for unmap, cache away at least the basic block limits information
+ //
+ if (dataTransferLength >= 0x10 && blockLimits->PageCode == VPD_BLOCK_LIMITS) {
+
+ // (6:7) OPTIMAL TRANSFER LENGTH GRANULARITY
+ REVERSE_BYTES_SHORT(&BlockLimitsData->OptimalTransferLengthGranularity, &blockLimits->OptimalTransferLengthGranularity);
+ // (8:11) MAXIMUM TRANSFER LENGTH
+ REVERSE_BYTES(&BlockLimitsData->MaximumTransferLength, &blockLimits->MaximumTransferLength);
+ // (12:15) OPTIMAL TRANSFER LENGTH
+ REVERSE_BYTES(&BlockLimitsData->OptimalTransferLength, &blockLimits->OptimalTransferLength);
+ }
+
+ if ((dataTransferLength < 0x24) ||
+ (pageLength < (FIELD_OFFSET(VPD_BLOCK_LIMITS_PAGE,Reserved1) - FIELD_OFFSET(VPD_BLOCK_LIMITS_PAGE,Reserved0))) ||
+ (blockLimits->PageCode != VPD_BLOCK_LIMITS))
+ {
+ // the device should return at least 36 bytes of data for use.
+ // 'PageCode' shall match and we need all the relevant data after the header.
+ status = STATUS_INFO_LENGTH_MISMATCH;
+ }
+ }
+
+ if (NT_SUCCESS(status))
+ {
+ // cache data into FdoExtension
+ // (20:23) MAXIMUM UNMAP LBA COUNT
+ REVERSE_BYTES(&BlockLimitsData->MaxUnmapLbaCount, &blockLimits->MaximumUnmapLBACount);
+ // (24:27) MAXIMUM UNMAP BLOCK DESCRIPTOR COUNT
+ REVERSE_BYTES(&BlockLimitsData->MaxUnmapBlockDescrCount, &blockLimits->MaximumUnmapBlockDescriptorCount);
+ // (28:31) OPTIMAL UNMAP GRANULARITY
+ REVERSE_BYTES(&BlockLimitsData->OptimalUnmapGranularity, &blockLimits->OptimalUnmapGranularity);
+
+ // (32:35) UNMAP GRANULARITY ALIGNMENT; (32) bit7: UGAVALID
+ BlockLimitsData->UGAVALID = blockLimits->UGAValid;
+ if (BlockLimitsData->UGAVALID == TRUE) {
+ REVERSE_BYTES(&BlockLimitsData->UnmapGranularityAlignment, &blockLimits->UnmapGranularityAlignment);
+ BlockLimitsData->UnmapGranularityAlignment &= 0x7FFFFFFF; // remove value of UGAVALID bit.
+ } else {
+ BlockLimitsData->UnmapGranularityAlignment = 0;
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "ClasspDeviceGetBlockLimitsVPDPage (%p): %s %s (rev %s) reported following parameters: \
+ \n\t\t\tOptimalTransferLengthGranularity: %u \
+ \n\t\t\tMaximumTransferLength: %u \
+ \n\t\t\tOptimalTransferLength: %u \
+ \n\t\t\tMaximumUnmapLBACount: %u \
+ \n\t\t\tMaximumUnmapBlockDescriptorCount: %u \
+ \n\t\t\tOptimalUnmapGranularity: %u \
+ \n\t\t\tUGAValid: %u \
+ \n\t\t\tUnmapGranularityAlignment: %u\n",
+ FdoExtension->DeviceObject,
+ (PCSZ)(((PUCHAR)FdoExtension->DeviceDescriptor) + FdoExtension->DeviceDescriptor->VendorIdOffset),
+ (PCSZ)(((PUCHAR)FdoExtension->DeviceDescriptor) + FdoExtension->DeviceDescriptor->ProductIdOffset),
+ (PCSZ)(((PUCHAR)FdoExtension->DeviceDescriptor) + FdoExtension->DeviceDescriptor->ProductRevisionOffset),
+ BlockLimitsData->OptimalTransferLengthGranularity,
+ BlockLimitsData->MaximumTransferLength,
+ BlockLimitsData->OptimalTransferLength,
+ BlockLimitsData->MaxUnmapLbaCount,
+ BlockLimitsData->MaxUnmapBlockDescrCount,
+ BlockLimitsData->OptimalUnmapGranularity,
+ BlockLimitsData->UGAVALID,
+ BlockLimitsData->UnmapGranularityAlignment));
+
+ }
+ } else {
+ status = STATUS_INVALID_DEVICE_REQUEST;
+ }
+
+ BlockLimitsData->CommandStatus = status;
+
+Exit:
+ FREE_POOL(dataBuffer);
+
+ return status;
+}
+
+
+NTSTATUS ClasspDeviceTrimProperty(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ )
+/*
+ At first time of receiving the request, this function will forward it to lower stack to determine if it's supportted.
+ If it's not supported, INQUIRY (Block Limits VPD page) will be sent down to retrieve the information.
+*/
+{
+ NTSTATUS status = STATUS_SUCCESS;
+
+ PCOMMON_DEVICE_EXTENSION commonExtension = (PCOMMON_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
+ PSTORAGE_PROPERTY_QUERY query = (PSTORAGE_PROPERTY_QUERY)Irp->AssociatedIrp.SystemBuffer;
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ ULONG length = 0;
+ ULONG information = 0;
+
+ PDEVICE_TRIM_DESCRIPTOR trimDescr;
+
+ UNREFERENCED_PARAMETER(Srb);
+
+ if ( (DeviceObject->DeviceType != FILE_DEVICE_DISK) ||
+ (TEST_FLAG(DeviceObject->Characteristics, FILE_FLOPPY_DISKETTE)) ||
+ (fdoExtension->FunctionSupportInfo->LowerLayerSupport.TrimProperty == Supported) ) {
+ // if it's not disk, forward the request to lower layer,
+ // if the IOCTL is supported by lower stack, forward it down.
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ return status;
+ }
+
+ //
+ // Check proper query type.
+ //
+
+ if (query->QueryType == PropertyExistsQuery) {
+ status = STATUS_SUCCESS;
+ goto Exit;
+ } else if (query->QueryType != PropertyStandardQuery) {
+ status = STATUS_NOT_SUPPORTED;
+ goto Exit;
+ }
+
+ //
+ // Request validation.
+ // Note that InputBufferLength and IsFdo have been validated beforing entering this routine.
+ //
+
+ if (KeGetCurrentIrql() >= DISPATCH_LEVEL) {
+ NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
+ status = STATUS_INVALID_LEVEL;
+ goto Exit;
+ }
+
+ // do not touch this buffer because it can still be used as input buffer for lower layer in 'SupportUnknown' case.
+ trimDescr = (PDEVICE_TRIM_DESCRIPTOR)Irp->AssociatedIrp.SystemBuffer;
+
+ length = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
+
+ if (length < sizeof(DEVICE_TRIM_DESCRIPTOR)) {
+
+ if (length >= sizeof(STORAGE_DESCRIPTOR_HEADER)) {
+
+ information = sizeof(STORAGE_DESCRIPTOR_HEADER);
+ trimDescr->Version = sizeof(DEVICE_TRIM_DESCRIPTOR);
+ trimDescr->Size = sizeof(DEVICE_TRIM_DESCRIPTOR);
+ status = STATUS_SUCCESS;
+ goto Exit;
+ }
+
+ status = STATUS_BUFFER_TOO_SMALL;
+ goto Exit;
+ }
+
+ //
+ // note that 'Supported' case has been handled at the beginning of this function.
+ //
+ switch (fdoExtension->FunctionSupportInfo->LowerLayerSupport.TrimProperty) {
+ case SupportUnknown: {
+ // send down request and wait for the request to complete.
+ status = ClassForwardIrpSynchronous(commonExtension, Irp);
+
+ if ( (status == STATUS_NOT_SUPPORTED) ||
+ (status == STATUS_NOT_IMPLEMENTED) ||
+ (status == STATUS_INVALID_DEVICE_REQUEST) ||
+ (status == STATUS_INVALID_PARAMETER_1) ) {
+ // case 1: the request is not supported by lower layer, sends down command
+ // some port drivers (or filter drivers) return STATUS_INVALID_DEVICE_REQUEST if a request is not supported.
+ status = fdoExtension->FunctionSupportInfo->LBProvisioningData.CommandStatus;
+ NT_ASSERT(status != -1);
+
+ // data is ready in fdoExtension
+ // set the support status after the SCSI command is executed to avoid racing condition between multiple same type of requests.
+ fdoExtension->FunctionSupportInfo->LowerLayerSupport.TrimProperty = NotSupported;
+
+ if (NT_SUCCESS(status)) {
+ // fill output buffer
+ RtlZeroMemory(trimDescr, length);
+ trimDescr->Version = sizeof(DEVICE_TRIM_DESCRIPTOR);
+ trimDescr->Size = sizeof(DEVICE_TRIM_DESCRIPTOR);
+ trimDescr->TrimEnabled = ClasspSupportsUnmap(fdoExtension->FunctionSupportInfo);
+
+ // set returned data length
+ information = sizeof(DEVICE_TRIM_DESCRIPTOR);
+ } else {
+ // there was error retrieving TrimProperty. Surface the error up from 'status' variable.
+ information = 0;
+ }
+ goto Exit;
+ } else {
+ // case 2: the request is supported and it completes successfully
+ // case 3: the request is supported by lower stack but other failure status is returned.
+ // from now on, the same request will be send down to lower stack directly.
+ fdoExtension->FunctionSupportInfo->LowerLayerSupport.TrimProperty = Supported;
+ information = (ULONG)Irp->IoStatus.Information;
+ goto Exit;
+ }
+ break;
+ }
+
+ case NotSupported: {
+ status = fdoExtension->FunctionSupportInfo->LBProvisioningData.CommandStatus;
+ NT_ASSERT(status != -1);
+
+ if (NT_SUCCESS(status)) {
+ RtlZeroMemory(trimDescr, length);
+ trimDescr->Version = sizeof(DEVICE_TRIM_DESCRIPTOR);
+ trimDescr->Size = sizeof(DEVICE_TRIM_DESCRIPTOR);
+ trimDescr->TrimEnabled = ClasspSupportsUnmap(fdoExtension->FunctionSupportInfo);
+
+ information = sizeof(DEVICE_TRIM_DESCRIPTOR);
+ } else {
+ information = 0;
+ }
+ goto Exit;
+
+ break;
+ }
+
+ case Supported: {
+ NT_ASSERT(FALSE); // this case is handled at the begining of the function.
+ break;
+ }
+
+ } // end of switch (fdoExtension->FunctionSupportInfo->LowerLayerSupport.TrimProperty)
+
+Exit:
+
+ //
+ // Set the size and status in IRP
+ //
+ Irp->IoStatus.Information = information;
+ Irp->IoStatus.Status = status;
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+
+ return status;
+}
+
+NTSTATUS ClasspDeviceLBProvisioningProperty(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _Inout_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ )
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ NTSTATUS blockLimitsStatus;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
+ PSTORAGE_PROPERTY_QUERY query = (PSTORAGE_PROPERTY_QUERY)Irp->AssociatedIrp.SystemBuffer;
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ ULONG length = 0;
+ ULONG information = 0;
+ CLASS_VPD_B0_DATA blockLimitsData;
+ ULONG generationCount;
+
+ PDEVICE_LB_PROVISIONING_DESCRIPTOR lbpDescr;
+
+ UNREFERENCED_PARAMETER(Srb);
+
+ //
+ // Check proper query type.
+ //
+ if (query->QueryType == PropertyExistsQuery) {
+ status = STATUS_SUCCESS;
+ goto Exit;
+ } else if (query->QueryType != PropertyStandardQuery) {
+ status = STATUS_NOT_SUPPORTED;
+ goto Exit;
+ }
+
+ //
+ // Request validation.
+ // Note that InputBufferLength and IsFdo have been validated beforing entering this routine.
+ //
+
+ if (KeGetCurrentIrql() >= DISPATCH_LEVEL) {
+ NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
+ status = STATUS_INVALID_LEVEL;
+ goto Exit;
+ }
+
+ lbpDescr = (PDEVICE_LB_PROVISIONING_DESCRIPTOR)Irp->AssociatedIrp.SystemBuffer;
+
+ length = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
+
+ RtlZeroMemory(lbpDescr, length);
+
+ if (length < DEVICE_LB_PROVISIONING_DESCRIPTOR_V1_SIZE) {
+
+ if (length >= sizeof(STORAGE_DESCRIPTOR_HEADER)) {
+
+ information = sizeof(STORAGE_DESCRIPTOR_HEADER);
+ lbpDescr->Version = sizeof(DEVICE_LB_PROVISIONING_DESCRIPTOR);
+ lbpDescr->Size = sizeof(DEVICE_LB_PROVISIONING_DESCRIPTOR);
+ status = STATUS_SUCCESS;
+ goto Exit;
+ }
+
+ status = STATUS_BUFFER_TOO_SMALL;
+ goto Exit;
+ }
+
+ //
+ // Set the structure version/size based upon the size of the given output
+ // buffer. We may be working with an older component that was built with
+ // the V1 structure definition.
+ //
+ if (length < sizeof(DEVICE_LB_PROVISIONING_DESCRIPTOR)) {
+ lbpDescr->Version = DEVICE_LB_PROVISIONING_DESCRIPTOR_V1_SIZE;
+ lbpDescr->Size = DEVICE_LB_PROVISIONING_DESCRIPTOR_V1_SIZE;
+ information = DEVICE_LB_PROVISIONING_DESCRIPTOR_V1_SIZE;
+ } else {
+ lbpDescr->Version = sizeof(DEVICE_LB_PROVISIONING_DESCRIPTOR);
+ lbpDescr->Size = sizeof(DEVICE_LB_PROVISIONING_DESCRIPTOR);
+ information = sizeof(DEVICE_LB_PROVISIONING_DESCRIPTOR);
+ }
+
+ //
+ // Take a snapshot of the block limits data since it can change.
+ // If we failed to get the block limits data, we'll just set the Optimal
+ // Unmap Granularity (and alignment) will default to 0. We don't want to
+ // fail the request outright since there is some non-block limits data that
+ // we can return.
+ //
+ blockLimitsStatus = ClasspBlockLimitsDataSnapshot(fdoExtension,
+ TRUE,
+ &blockLimitsData,
+ &generationCount);
+
+ //
+ // Fill in the output buffer. All data is copied from the FDO extension where we
+ // cached Logical Block Provisioning info when the device was first initialized.
+ //
+
+ lbpDescr->ThinProvisioningEnabled = ClasspIsThinProvisioned(fdoExtension->FunctionSupportInfo);
+
+ //
+ // Make sure we have a non-zero value for the number of bytes per block.
+ //
+ if (fdoExtension->DiskGeometry.BytesPerSector == 0)
+ {
+ status = ClassReadDriveCapacity(fdoExtension->DeviceObject);
+ if(!NT_SUCCESS(status) || fdoExtension->DiskGeometry.BytesPerSector == 0)
+ {
+ status = STATUS_INVALID_DEVICE_REQUEST;
+ information = 0;
+ goto Exit;
+ }
+ }
+
+ lbpDescr->ThinProvisioningReadZeros = fdoExtension->FunctionSupportInfo->LBProvisioningData.LBPRZ;
+ lbpDescr->AnchorSupported = fdoExtension->FunctionSupportInfo->LBProvisioningData.ANC_SUP;
+
+ if (NT_SUCCESS(blockLimitsStatus)) {
+ lbpDescr->UnmapGranularityAlignmentValid = blockLimitsData.UGAVALID;
+
+ //
+ // Granularity and Alignment are given to us in units of blocks,
+ // but we convert and return them in bytes as it is more convenient
+ // to the caller.
+ //
+ lbpDescr->OptimalUnmapGranularity = (ULONGLONG)blockLimitsData.OptimalUnmapGranularity * fdoExtension->DiskGeometry.BytesPerSector;
+ lbpDescr->UnmapGranularityAlignment = (ULONGLONG)blockLimitsData.UnmapGranularityAlignment * fdoExtension->DiskGeometry.BytesPerSector;
+
+#if (NTDDI_VERSION >= NTDDI_WINBLUE)
+ //
+ // If the output buffer is large enough (i.e. not a V1 structure) copy
+ // over the max UNMAP LBA count and max UNMAP block descriptor count.
+ //
+ if (length >= sizeof(DEVICE_LB_PROVISIONING_DESCRIPTOR)) {
+ lbpDescr->MaxUnmapLbaCount = blockLimitsData.MaxUnmapLbaCount;
+ lbpDescr->MaxUnmapBlockDescriptorCount = blockLimitsData.MaxUnmapBlockDescrCount;
+ }
+#endif
+ }
+
+Exit:
+
+ //
+ // Set the size and status in IRP
+ //
+ Irp->IoStatus.Information = information;
+ Irp->IoStatus.Status = status;
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+
+ return status;
+}
+
+
+VOID
+ConvertDataSetRangeToUnmapBlockDescr(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ _In_ PUNMAP_BLOCK_DESCRIPTOR BlockDescr,
+ _Inout_ PULONG CurrentBlockDescrIndex,
+ _In_ ULONG MaxBlockDescrIndex,
+ _Inout_ PULONGLONG CurrentLbaCount,
+ _In_ ULONGLONG MaxLbaCount,
+ _Inout_ PDEVICE_DATA_SET_RANGE DataSetRange
+ )
+/*++
+
+Routine Description:
+
+ Convert DEVICE_DATA_SET_RANGE entry to be UNMAP_BLOCK_DESCRIPTOR entries.
+
+ As LengthInBytes field in DEVICE_DATA_SET_RANGE structure is 64 bits (bytes)
+ and LbaCount field in UNMAP_BLOCK_DESCRIPTOR structure is 32 bits (sectors),
+ it's possible that one DEVICE_DATA_SET_RANGE entry needs multiple UNMAP_BLOCK_DESCRIPTOR entries.
+ We must also take the unmap granularity into consideration and split up the
+ the given ranges so that they are aligned with the specified granularity.
+
+Arguments:
+ All arguments must be validated by the caller.
+
+ FdoExtension - The FDO extension of the device to which the unmap
+ command that will use the resulting unmap block descriptors will be
+ sent.
+ BlockDescr - Pointer to a buffer that will contain the unmap block
+ descriptors. This buffer should be allocated by the caller and the
+ caller should also ensure that it is large enough to contain all the
+ requested descriptors. Its size is implied by MaxBlockDescrIndex.
+ CurrentBlockDescrIndex - This contains the next block descriptor index to
+ be processed when this function returns. This function should be called
+ again with the same parameter to continue processing.
+ MaxBlockDescrIndex - This is the index of the last unmap block descriptor,
+ provided so that the function does not go off the end of BlockDescr.
+ CurrentLbaCount - This contains the number of LBAs left to be processed
+ when this function returns. This function should be called again with
+ the same parameter to continue processing.
+ MaxLbaCount - This is the max number of LBAs that can be sent in a single
+ unmap command.
+ DataSetRange - This range will be modified to reflect the un-converted part.
+ It must be valid (including being granularity-aligned) when it is first
+ passed to this function.
+
+Return Value:
+
+ Count of UNMAP_BLOCK_DESCRIPTOR entries converted.
+
+ NOTE: if LengthInBytes does not reach to 0, the conversion for DEVICE_DATA_SET_RANGE entry
+ is not completed. Further conversion is needed by calling this function again.
+
+--*/
+{
+
+ ULONGLONG startingSector;
+ ULONGLONG sectorCount;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "ConvertDataSetRangeToUnmapBlockDescr (%p): Generating UNMAP Block Descriptors from DataSetRange: \
+ \n\t\tStartingOffset = %I64u bytes \
+ \n\t\tLength = %I64u bytes\n",
+ FdoExtension->DeviceObject,
+ DataSetRange->StartingOffset,
+ DataSetRange->LengthInBytes));
+
+ while ( (DataSetRange->LengthInBytes > 0) &&
+ (*CurrentBlockDescrIndex < MaxBlockDescrIndex) &&
+ (*CurrentLbaCount < MaxLbaCount) ) {
+
+ //
+ // Convert the starting offset and length from bytes to blocks.
+ //
+ startingSector = (ULONGLONG)(DataSetRange->StartingOffset / FdoExtension->DiskGeometry.BytesPerSector);
+ sectorCount = (DataSetRange->LengthInBytes / FdoExtension->DiskGeometry.BytesPerSector);
+
+ //
+ // Make sure the sector count isn't more than can be specified with a
+ // single descriptor.
+ //
+ if (sectorCount > MAXULONG) {
+ sectorCount = MAXULONG;
+ }
+
+ //
+ // The max LBA count is the max number of LBAs that can be unmapped with
+ // a single UNMAP command. Make sure we don't exceed this value.
+ //
+ if ((*CurrentLbaCount + sectorCount) > MaxLbaCount) {
+ sectorCount = MaxLbaCount - *CurrentLbaCount;
+ }
+
+ REVERSE_BYTES_QUAD(BlockDescr[*CurrentBlockDescrIndex].StartingLba, &startingSector);
+ REVERSE_BYTES(BlockDescr[*CurrentBlockDescrIndex].LbaCount, (PULONG)&sectorCount);
+
+ DataSetRange->StartingOffset += sectorCount * FdoExtension->DiskGeometry.BytesPerSector;
+ DataSetRange->LengthInBytes -= sectorCount * FdoExtension->DiskGeometry.BytesPerSector;
+
+ *CurrentBlockDescrIndex += 1;
+ *CurrentLbaCount += (ULONG)sectorCount;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "ConvertDataSetRangeToUnmapBlockDescr (%p): Generated UNMAP Block Descriptor: \
+ \n\t\t\tStartingLBA = %I64u \
+ \n\t\t\tLBACount = %I64u\n",
+ FdoExtension->DeviceObject,
+ startingSector,
+ sectorCount));
+ }
+
+ return;
+}
+
+
+NTSTATUS
+DeviceProcessDsmTrimRequest(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ _In_ PDEVICE_DATA_SET_RANGE DataSetRanges,
+ _In_ ULONG DataSetRangesCount,
+ _In_ ULONG UnmapGranularity,
+ _In_ ULONG SrbFlags,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+)
+/*++
+
+Routine Description:
+
+ Process TRIM request that received from upper layer.
+
+Arguments:
+
+ FdoExtension
+ DataSetRanges - this parameter must be already validated in caller.
+ DataSetRangesCount - this parameter must be already validated in caller.
+ UnmapGranularity - The unmap granularity in blocks. This is used to split
+ up the unmap command into chunks that are granularity-aligned.
+ Srb - The SRB to use for the unmap command. The caller must allocate it,
+ but this function will take care of initialzing it.
+
+Return Value:
+
+ status of the operation
+
+--*/
+{
+ NTSTATUS status = STATUS_SUCCESS;
+
+ PUNMAP_LIST_HEADER buffer = NULL;
+ PUNMAP_BLOCK_DESCRIPTOR blockDescrPointer;
+ ULONG bufferLength;
+ ULONG maxBlockDescrCount;
+ ULONG neededBlockDescrCount;
+ ULONG i;
+
+ BOOLEAN allDataSetRangeFullyConverted;
+ BOOLEAN needToSendCommand;
+ BOOLEAN tempDataSetRangeFullyConverted;
+
+ ULONG dataSetRangeIndex;
+ DEVICE_DATA_SET_RANGE tempDataSetRange;
+
+ ULONG blockDescrIndex;
+ ULONGLONG lbaCount;
+ ULONGLONG maxLbaCount;
+ ULONGLONG maxParameterListLength;
+
+ UNREFERENCED_PARAMETER(UnmapGranularity);
+
+ //
+ // The given LBA ranges are in DEVICE_DATA_SET_RANGE format and need to be converted into UNMAP Block Descriptors.
+ // The UNMAP command is able to carry 0xFFFF bytes (0xFFF8 in reality as there are 8 bytes of header plus n*16 bytes of Block Descriptors) of data.
+ // The actual size will also be constrained by the Maximum LBA Count and Maximum Transfer Length.
+ //
+
+ //
+ // 1.1 Calculate how many Block Descriptors are needed to complete this request.
+ //
+ neededBlockDescrCount = 0;
+ for (i = 0; i < DataSetRangesCount; i++) {
+ lbaCount = DataSetRanges[i].LengthInBytes / FdoExtension->DiskGeometry.BytesPerSector;
+
+ //
+ // 1.1.1 the UNMAP_BLOCK_DESCRIPTOR LbaCount is 32 bits, the max value is 0xFFFFFFFF
+ //
+ if (lbaCount > 0) {
+ neededBlockDescrCount += (ULONG)((lbaCount - 1) / MAXULONG + 1);
+ }
+ }
+
+ //
+ // Honor Max Unmap Block Descriptor Count if it has been specified. Otherwise,
+ // use the maximum value that the Parameter List Length field will allow (0xFFFF).
+ // If the count is 0xFFFFFFFF, then no maximum is specified.
+ //
+ if (FdoExtension->FunctionSupportInfo->BlockLimitsData.MaxUnmapBlockDescrCount != 0 &&
+ FdoExtension->FunctionSupportInfo->BlockLimitsData.MaxUnmapBlockDescrCount != MAXULONG)
+ {
+ maxParameterListLength = (ULONGLONG)(FdoExtension->FunctionSupportInfo->BlockLimitsData.MaxUnmapBlockDescrCount * sizeof(UNMAP_BLOCK_DESCRIPTOR))
+ + sizeof(UNMAP_LIST_HEADER);
+
+ //
+ // In the SBC-3, the Max Unmap Block Descriptor Count field in the 0xB0
+ // page is 4 bytes and the Parameter List Length in the UNMAP command is
+ // 2 bytes, therefore it is possible that the Max Unmap Block Descriptor
+ // Count could imply more bytes than can be specified in the Parameter
+ // List Length field. Adjust for that here.
+ //
+ maxParameterListLength = min(maxParameterListLength, MAXUSHORT);
+ }
+ else
+ {
+ maxParameterListLength = MAXUSHORT;
+ }
+
+ //
+ // 1.2 Calculate the buffer size needed, capped by the device's limitations.
+ //
+ bufferLength = min(FdoExtension->PrivateFdoData->HwMaxXferLen, (ULONG)maxParameterListLength);
+ bufferLength = min(bufferLength, (neededBlockDescrCount * sizeof(UNMAP_BLOCK_DESCRIPTOR) + sizeof(UNMAP_LIST_HEADER)));
+
+ maxBlockDescrCount = (bufferLength - sizeof(UNMAP_LIST_HEADER)) / sizeof(UNMAP_BLOCK_DESCRIPTOR);
+
+ if (maxBlockDescrCount == 0) {
+ //
+ // This shouldn't happen since we've already done validation.
+ //
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "DeviceProcessDsmTrimRequest (%p): Max Block Descriptor count is Zero\n",
+ FdoExtension->DeviceObject));
+
+ NT_ASSERT(maxBlockDescrCount != 0);
+ status = STATUS_DATA_ERROR;
+ goto Exit;
+ }
+
+ //
+ // The Maximum LBA Count is set during device initialization.
+ //
+ maxLbaCount = (ULONGLONG)FdoExtension->FunctionSupportInfo->BlockLimitsData.MaxUnmapLbaCount;
+ if (maxLbaCount == 0) {
+ //
+ // This shouldn't happen since we've already done validation.
+ //
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "DeviceProcessDsmTrimRequest (%p): Max LBA count is Zero\n",
+ FdoExtension->DeviceObject));
+
+ NT_ASSERT(maxLbaCount != 0);
+ status = STATUS_DATA_ERROR;
+ goto Exit;
+ }
+
+ //
+ // Finally, allocate the buffer we'll use to send the UNMAP command.
+ //
+
+#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.
+ //
+ bufferLength = ALIGN_UP_BY(bufferLength,KeGetRecommendedSharedDataAlignment());
+ buffer = (PUNMAP_LIST_HEADER)ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned, bufferLength, CLASS_TAG_LB_PROVISIONING);
+#else
+ buffer = (PUNMAP_LIST_HEADER)ExAllocatePoolWithTag(NonPagedPoolNx, bufferLength, CLASS_TAG_LB_PROVISIONING);
+#endif
+
+ if (buffer == NULL) {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto Exit;
+ }
+
+ RtlZeroMemory(buffer, bufferLength);
+
+ blockDescrPointer = &buffer->Descriptors[0];
+
+ allDataSetRangeFullyConverted = FALSE;
+ needToSendCommand = FALSE;
+ tempDataSetRangeFullyConverted = TRUE;
+ dataSetRangeIndex = 0;
+ RtlZeroMemory(&tempDataSetRange, sizeof(tempDataSetRange));
+
+ blockDescrIndex = 0;
+ lbaCount = 0;
+
+ while (!allDataSetRangeFullyConverted) {
+
+ //
+ // If the previous entry conversion completed, go on to the next one;
+ // otherwise, continue processing the current entry.
+ //
+ if (tempDataSetRangeFullyConverted) {
+ tempDataSetRange.StartingOffset = DataSetRanges[dataSetRangeIndex].StartingOffset;
+ tempDataSetRange.LengthInBytes = DataSetRanges[dataSetRangeIndex].LengthInBytes;
+ dataSetRangeIndex++;
+ }
+
+ ConvertDataSetRangeToUnmapBlockDescr(FdoExtension,
+ blockDescrPointer,
+ &blockDescrIndex,
+ maxBlockDescrCount,
+ &lbaCount,
+ maxLbaCount,
+ &tempDataSetRange
+ );
+
+ tempDataSetRangeFullyConverted = (tempDataSetRange.LengthInBytes == 0) ? TRUE : FALSE;
+
+ allDataSetRangeFullyConverted = tempDataSetRangeFullyConverted && (dataSetRangeIndex == DataSetRangesCount);
+
+ //
+ // Send the UNMAP command when the buffer is full or when all input entries are converted.
+ //
+ if ( (blockDescrIndex == maxBlockDescrCount) || // Buffer full or block descriptor count reached
+ (lbaCount == maxLbaCount) || // Block LBA count reached
+ allDataSetRangeFullyConverted) { // All DataSetRanges have been converted
+
+ USHORT transferSize;
+ USHORT tempSize;
+ PCDB cdb;
+
+ //
+ // Get the transfer size, including the header.
+ //
+ transferSize = (USHORT)(blockDescrIndex * sizeof(UNMAP_BLOCK_DESCRIPTOR) + sizeof(UNMAP_LIST_HEADER));
+ if (transferSize > bufferLength)
+ {
+ //
+ // This should never happen.
+ //
+ NT_ASSERT(transferSize <= bufferLength);
+ status = STATUS_BUFFER_TOO_SMALL;
+ break;
+ }
+
+ tempSize = transferSize - (USHORT)FIELD_OFFSET(UNMAP_LIST_HEADER, BlockDescrDataLength);
+ REVERSE_BYTES_SHORT(buffer->DataLength, &tempSize);
+ tempSize = transferSize - (USHORT)FIELD_OFFSET(UNMAP_LIST_HEADER, Descriptors[0]);
+ REVERSE_BYTES_SHORT(buffer->BlockDescrDataLength, &tempSize);
+
+ //
+ // Initialize the SRB.
+ //
+ if (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.
+ //
+ NT_ASSERT(FALSE);
+ break;
+ }
+
+ } else {
+ RtlZeroMemory(Srb, sizeof(SCSI_REQUEST_BLOCK));
+ Srb->Length = sizeof(SCSI_REQUEST_BLOCK);
+ Srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
+ }
+
+ //
+ // Prepare the Srb
+ //
+ SrbSetTimeOutValue(Srb, FdoExtension->TimeOutValue);
+ SrbSetRequestTag(Srb, SP_UNTAGGED);
+ SrbSetRequestAttribute(Srb, SRB_SIMPLE_TAG_REQUEST);
+
+ //
+ // Set the SrbFlags to indicate that it's a data-out operation.
+ // Also set any passed-in SrbFlags.
+ //
+ SrbAssignSrbFlags(Srb, FdoExtension->SrbFlags);
+ SrbClearSrbFlags(Srb, SRB_FLAGS_DATA_IN);
+ SrbSetSrbFlags(Srb, SRB_FLAGS_DATA_OUT);
+ SrbSetSrbFlags(Srb, SrbFlags);
+
+ SrbSetCdbLength(Srb, 10);
+
+ cdb = SrbGetCdb(Srb);
+ cdb->UNMAP.OperationCode = SCSIOP_UNMAP;
+ cdb->UNMAP.Anchor = 0;
+ cdb->UNMAP.GroupNumber = 0;
+ cdb->UNMAP.AllocationLength[0] = (UCHAR)(transferSize >> 8);
+ cdb->UNMAP.AllocationLength[1] = (UCHAR)transferSize;
+
+ status = ClassSendSrbSynchronous(FdoExtension->DeviceObject,
+ Srb,
+ buffer,
+ transferSize,
+ TRUE);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "DeviceProcessDsmTrimRequest (%p): UNMAP command issued. Returned NTSTATUS: %!STATUS!.\n",
+ FdoExtension->DeviceObject,
+ status
+ ));
+
+ //
+ // Clear the buffer so we can re-use it.
+ //
+ blockDescrIndex = 0;
+ lbaCount = 0;
+ RtlZeroMemory(buffer, bufferLength);
+ }
+ }
+
+Exit:
+
+ FREE_POOL(buffer);
+
+ return status;
+}
+
+NTSTATUS ClasspDeviceTrimProcess(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ )
+/*
+ This function is to process IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES with DeviceDsmAction_Trim.
+ At first time of receiving the request, this function will forward it to lower stack to determine if it's supportted.
+ If it's not supported, UNMAP (with anchor attribute set) will be sent down to process the request.
+*/
+{
+ NTSTATUS status = STATUS_SUCCESS;
+
+ PCOMMON_DEVICE_EXTENSION commonExtension = (PCOMMON_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
+
+ PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes = Irp->AssociatedIrp.SystemBuffer;
+
+ PDEVICE_DATA_SET_RANGE dataSetRanges;
+ ULONG dataSetRangesCount;
+ DEVICE_DATA_SET_RANGE entireDataSetRange = {0};
+ ULONG i;
+ ULONGLONG granularityAlignmentInBytes;
+ ULONG granularityInBlocks;
+ ULONG srbFlags = 0;
+
+ CLASS_VPD_B0_DATA blockLimitsData;
+ ULONG generationCount;
+
+ if ( (DeviceObject->DeviceType != FILE_DEVICE_DISK) ||
+ (TEST_FLAG(DeviceObject->Characteristics, FILE_FLOPPY_DISKETTE)) ||
+ (fdoExtension->FunctionSupportInfo->LowerLayerSupport.TrimProcess == Supported) ) {
+ // if it's not disk, forward the request to lower layer,
+ // if the IOCTL is supported by lower stack, forward it down.
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "ClasspDeviceTrimProcess (%p): Lower layer supports Trim DSM IOCTL, forwarding IOCTL.\n",
+ DeviceObject));
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ return status;
+ }
+
+ //
+ // Request validation.
+ // Note that InputBufferLength and IsFdo have been validated beforing entering this routine.
+ //
+
+ if (KeGetCurrentIrql() >= DISPATCH_LEVEL) {
+ NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
+ status = STATUS_INVALID_LEVEL;
+ goto Exit;
+ }
+
+ //
+ // If the caller has not set the "entire dataset range" flag then at least
+ // one dataset range should be specified. However, if the caller *has* set
+ // the flag, then there should not be any dataset ranges specified.
+ //
+ if ((!TEST_FLAG(dsmAttributes->Flags, DEVICE_DSM_FLAG_ENTIRE_DATA_SET_RANGE) &&
+ (dsmAttributes->DataSetRangesOffset == 0 ||
+ dsmAttributes->DataSetRangesLength == 0)) ||
+ (TEST_FLAG(dsmAttributes->Flags, DEVICE_DSM_FLAG_ENTIRE_DATA_SET_RANGE) &&
+ (dsmAttributes->DataSetRangesOffset != 0 ||
+ dsmAttributes->DataSetRangesLength != 0))) {
+
+ status = STATUS_INVALID_PARAMETER;
+ goto Exit;
+ }
+
+ //
+ // note that 'Supported' case has been handled at the beginning of this function.
+ //
+ switch (fdoExtension->FunctionSupportInfo->LowerLayerSupport.TrimProcess) {
+ case SupportUnknown: {
+ // send down request and wait for the request to complete.
+ status = ClassForwardIrpSynchronous(commonExtension, Irp);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "ClasspDeviceTrimProcess (%p): Trim DSM IOCTL support unknown. Forwarded IOCTL and received NTSTATUS %!STATUS!.\n",
+ DeviceObject,
+ status));
+
+ if (ClasspLowerLayerNotSupport(status)) {
+ // case 1: the request is not supported by lower layer, sends down command
+ // some port drivers (or filter drivers) return STATUS_INVALID_DEVICE_REQUEST if a request is not supported.
+ // In this case we'll just fall through to the NotSupported case so that we can handle it ourselves.
+
+ //
+ // VPD pages 0xB2 and 0xB0 should have been cached in Start Device phase - ClassPnpStartDevice.
+ // 0xB2 page: fdoExtension->FunctionSupportInfo->LBProvisioningData;
+ // 0xB0 page: fdoExtension->FunctionSupportInfo->BlockLimitsData
+ //
+ if (fdoExtension->FunctionSupportInfo->ValidInquiryPages.LBProvisioning == TRUE) {
+ NT_ASSERT(fdoExtension->FunctionSupportInfo->LBProvisioningData.CommandStatus != -1);
+ }
+
+ if (fdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockLimits == TRUE) {
+ NT_ASSERT(fdoExtension->FunctionSupportInfo->BlockLimitsData.CommandStatus != -1);
+ }
+
+ } else {
+
+ // case 2: the request is supported and it completes successfully
+ // case 3: the request is supported by lower stack but other failure status is returned.
+ // from now on, the same request will be send down to lower stack directly.
+ fdoExtension->FunctionSupportInfo->LowerLayerSupport.TrimProcess = Supported;
+ goto Exit;
+ }
+ }
+
+ case NotSupported: {
+
+ // send UNMAP command if it is supported. don't need to check 'status' value.
+ if (ClasspSupportsUnmap(fdoExtension->FunctionSupportInfo))
+ {
+ //
+ // Make sure that we know the bytes per sector (logical block) as it's
+ // necessary for calculations involving granularity and alignment.
+ //
+ if (fdoExtension->DiskGeometry.BytesPerSector == 0) {
+ status = ClassReadDriveCapacity(fdoExtension->DeviceObject);
+ if(!NT_SUCCESS(status) || fdoExtension->DiskGeometry.BytesPerSector == 0) {
+ status = STATUS_INVALID_DEVICE_REQUEST;
+ goto Exit;
+ }
+ }
+
+ //
+ // Take a snapshot of the block limits data since it can change.
+ // It's acceptable if the block limits data is outdated since
+ // there isn't a hard requirement on the unmap granularity.
+ //
+ ClasspBlockLimitsDataSnapshot(fdoExtension,
+ FALSE,
+ &blockLimitsData,
+ &generationCount);
+
+ //
+ // Check to see if the Optimal Unmap Granularity and Unmap Granularity
+ // Alignment have been specified. If not, default the granularity to
+ // one block and the alignment to zero.
+ //
+ if (blockLimitsData.OptimalUnmapGranularity != 0)
+ {
+ granularityInBlocks = blockLimitsData.OptimalUnmapGranularity;
+ }
+ else
+ {
+ granularityInBlocks = 1;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "ClasspDeviceTrimProcess (%p): Optimal Unmap Granularity not provided, defaulted to 1.\n",
+ DeviceObject));
+ }
+
+ if (blockLimitsData.UGAVALID == TRUE)
+ {
+ granularityAlignmentInBytes = (ULONGLONG)blockLimitsData.UnmapGranularityAlignment * fdoExtension->DiskGeometry.BytesPerSector;
+ }
+ else
+ {
+ granularityAlignmentInBytes = 0;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "ClasspDeviceTrimProcess (%p): Unmap Granularity Alignment not provided, defaulted to 0.\n",
+ DeviceObject));
+ }
+
+ if (TEST_FLAG(dsmAttributes->Flags, DEVICE_DSM_FLAG_ENTIRE_DATA_SET_RANGE))
+ {
+ //
+ // The caller wants to UNMAP the entire disk so we need to build a single
+ // dataset range that represents the entire disk.
+ //
+ entireDataSetRange.StartingOffset = granularityAlignmentInBytes;
+ entireDataSetRange.LengthInBytes = (ULONGLONG)fdoExtension->CommonExtension.PartitionLength.QuadPart - (ULONGLONG)entireDataSetRange.StartingOffset;
+
+ dataSetRanges = &entireDataSetRange;
+ dataSetRangesCount = 1;
+ }
+ else
+ {
+
+ dataSetRanges = (PDEVICE_DATA_SET_RANGE)((PUCHAR)dsmAttributes + dsmAttributes->DataSetRangesOffset);
+ dataSetRangesCount = dsmAttributes->DataSetRangesLength / sizeof(DEVICE_DATA_SET_RANGE);
+
+ //
+ // Validate the data ranges. Make sure the range is block-aligned,
+ // falls in a valid portion of the disk, and is non-zero.
+ //
+ for (i = 0; i < dataSetRangesCount; i++)
+ {
+ if ((dataSetRanges[i].StartingOffset % fdoExtension->DiskGeometry.BytesPerSector != 0) ||
+ (dataSetRanges[i].LengthInBytes % fdoExtension->DiskGeometry.BytesPerSector != 0) ||
+ (dataSetRanges[i].StartingOffset < (LONGLONG)granularityAlignmentInBytes) ||
+ (dataSetRanges[i].LengthInBytes == 0) ||
+ ((ULONGLONG)dataSetRanges[i].StartingOffset + dataSetRanges[i].LengthInBytes > (ULONGLONG)fdoExtension->CommonExtension.PartitionLength.QuadPart))
+ {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspDeviceTrimProcess (%p): Invalid dataset range. StartingOffset = %I64x, LengthInBytes = %I64x\n",
+ DeviceObject,
+ dataSetRanges[i].StartingOffset,
+ dataSetRanges[i].LengthInBytes));
+
+ status = STATUS_INVALID_PARAMETER;
+ goto Exit;
+ }
+ }
+ }
+
+
+ if (!TEST_FLAG(dsmAttributes->Flags, DEVICE_DSM_FLAG_TRIM_NOT_FS_ALLOCATED))
+ {
+ {
+ //
+ // For security reasons, file-level TRIM must be forwarded on only
+ // if reading the unmapped blocks' contents will return back zeros.
+ // This is because if LBPRZ bit is not set, it indicates that a read
+ // of unmapped blocks may return "any" data thus potentially leaking
+ // in data (into the read buffer) from other blocks.
+ //
+ if (fdoExtension->FunctionSupportInfo->ValidInquiryPages.LBProvisioning &&
+ !fdoExtension->FunctionSupportInfo->LBProvisioningData.LBPRZ) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspDeviceTrimProcess (%p): Device does not support file level TRIM.\n",
+ DeviceObject));
+
+ status = STATUS_TRIM_READ_ZERO_NOT_SUPPORTED;
+ goto Exit;
+ }
+ }
+ }
+
+ // process DSM IOCTL
+ status = DeviceProcessDsmTrimRequest(fdoExtension,
+ dataSetRanges,
+ dataSetRangesCount,
+ granularityInBlocks,
+ srbFlags,
+ Srb);
+ } else {
+ // DSM IOCTL should be completed as not supported
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspDeviceTrimProcess (%p): Device does not support UNMAP.\n",
+ DeviceObject));
+
+ status = STATUS_NOT_SUPPORTED;
+ }
+
+ // set the support status after the SCSI command is executed to avoid racing condition between multiple same type of requests.
+ fdoExtension->FunctionSupportInfo->LowerLayerSupport.TrimProcess = NotSupported;
+
+ break;
+ }
+
+ case Supported: {
+ NT_ASSERT(FALSE); // this case is handled at the begining of the function.
+ break;
+ }
+
+ } // end of switch (fdoExtension->FunctionSupportInfo->LowerLayerSupport.TrimProcess)
+
+Exit:
+
+ //
+ // Set the size and status in IRP
+ //
+ Irp->IoStatus.Information = 0;
+ Irp->IoStatus.Status = status;
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+
+ return status;
+}
+
+NTSTATUS
+GetLBAStatus(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ _In_ PSCSI_REQUEST_BLOCK Srb,
+ _In_ ULONGLONG StartingLBA,
+ _Inout_ PLBA_STATUS_LIST_HEADER LBAStatusHeader,
+ _In_ ULONG LBAStatusSize,
+ _In_ BOOLEAN ConsolidateableBlocksOnly
+ )
+/*++
+
+Routine Description:
+
+ Send down a Get LBA Status command for the given range.
+
+Arguments:
+ FdoExtension: The FDO extension of the device to which Get LBA Status will
+ be sent.
+ Srb: This should be allocated and initialized before it's passed in. It
+ will be used for the Get LBA Status command.
+ StartingLBA: The LBA that is at the beginning of the requested range.
+ LBAStatusHeader: Caller-allocated output buffer.
+ LBASTatusSize: Size of the caller-allocated output buffer.
+
+Return Value:
+
+ Status of the operation.
+
+--*/
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ PCDB cdb;
+
+ if (LBAStatusHeader == NULL || LBAStatusSize == 0)
+ {
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Build and send down the Get LBA Status command.
+ //
+ SrbSetTimeOutValue(Srb, FdoExtension->TimeOutValue);
+ SrbSetRequestTag(Srb, SP_UNTAGGED);
+ SrbSetRequestAttribute(Srb, SRB_SIMPLE_TAG_REQUEST);
+ SrbAssignSrbFlags(Srb, FdoExtension->SrbFlags);
+ SrbSetCdbLength(Srb, sizeof(cdb->GET_LBA_STATUS));
+
+
+ cdb = SrbGetCdb(Srb);
+ cdb->GET_LBA_STATUS.OperationCode = SCSIOP_GET_LBA_STATUS;
+ cdb->GET_LBA_STATUS.ServiceAction = SERVICE_ACTION_GET_LBA_STATUS;
+ REVERSE_BYTES_QUAD(&(cdb->GET_LBA_STATUS.StartingLBA), &StartingLBA);
+ REVERSE_BYTES(&(cdb->GET_LBA_STATUS.AllocationLength), &LBAStatusSize);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "GetLBAStatus (%p): sending command with StartingLBA = 0x%I64x, AllocationLength = 0x%I64x, ConsolidateableBlocksOnly = %u\n",
+ FdoExtension->DeviceObject,
+ StartingLBA,
+ LBAStatusSize,
+ ConsolidateableBlocksOnly));
+
+ status = ClassSendSrbSynchronous(FdoExtension->DeviceObject,
+ Srb,
+ LBAStatusHeader,
+ LBAStatusSize,
+ 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(Srb) < LBAStatusSize)
+ {
+ status = STATUS_SUCCESS;
+ }
+
+ // log command.
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "GetLBAStatus (%p): command returned NT Status: %!STATUS!\n",
+ FdoExtension->DeviceObject,
+ status
+ ));
+
+ return status;
+}
+
+
+NTSTATUS ClasspDeviceGetLBAStatus(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _Inout_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ )
+/*
+Routine Description:
+
+ This function is to process IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES with DeviceDsmAction_Allocation.
+
+ 1. This function will only handle the first dataset range.
+ 2. This function will not handle dataset ranges whose LengthInBytes is greater than:
+ ((MAXULONG - sizeof(LBA_STATUS_LIST_HEADER)) / sizeof(LBA_STATUS_DESCRIPTOR)) * BytesPerSlab
+
+ The input buffer should consist of a DEVICE_MANAGE_DATA_SET_ATTRIBUTES followed
+ in memory by a single DEVICE_DATA_SET_RANGE that specifies the requested range
+ of slabs for which mapping status is desired.
+
+ The output buffer will consist of a DEVICE_MANAGE_DATA_SET_ATTRIBUTES_OUTPUT
+ followed in memory by a single DEVICE_DATA_SET_LB_PROVISIONING_STATE that
+ contains a bitmap that represents the mapped status of the slabs in the requested
+ range. Note that the number of slabs returned may be less than the number
+ requested.
+
+ Thus function will automatically re-align the given range offset if it was
+ not slab-aligned. The delta between the given range offset and the properly
+ aligned offset will be given in returned DEVICE_DATA_SET_LB_PROVISIONING_STATE.
+
+Arguments:
+ DeviceObject: The FDO of the device to which Get LBA Status will be sent.
+ Irp: The IRP for the request. This function will read the input buffer and
+ write to the output buffer at the current IRP stack location.
+ Srb: This should be allocated and initialized before it's passed in. It
+ will be used for the Get LBA Status command.
+
+Return Value:
+
+ STATUS_INVALID_PARAMETER: May be returned under the following conditions:
+ - If the requested range was too large. The caller should try again with a
+ smaller range. See above for how to calculate the maximum range.
+ - If the given starting offset was not within the valid range of the device.
+ STATUS_NOT_SUPPORTED: The storage did not report some information critical to
+ the execution of this function (e.g. Optimal Unmap Granularity).
+ STATUS_BUFFER_TOO_SMALL: The output buffer is not large enough to hold the max
+ data that could be returned from this function. If the output buffer is
+ at least the size of a ULONG, we will write the required output buffer size
+ to the first ULONG bytes of the output buffer.
+ STATUS_UNSUCCESSFUL: The Get LBA Status command succeeded but did not
+ return data as expected.
+--*/
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes = (PDEVICE_MANAGE_DATA_SET_ATTRIBUTES)Irp->AssociatedIrp.SystemBuffer;
+ PDEVICE_DATA_SET_RANGE dataSetRanges = NULL;
+ PDEVICE_MANAGE_DATA_SET_ATTRIBUTES_OUTPUT dsmOutput = (PDEVICE_MANAGE_DATA_SET_ATTRIBUTES_OUTPUT)Irp->AssociatedIrp.SystemBuffer;
+ ULONG dsmOutputLength;
+ NTSTATUS finalStatus;
+ NTSTATUS getLBAWorkerStatus;
+ ULONG retryCount;
+ ULONG retryCountMax;
+ CLASS_VPD_B0_DATA blockLimitsData;
+ ULONG generationCount1;
+ ULONG generationCount2;
+ BOOLEAN blockLimitsDataMayHaveChanged;
+ ULONG_PTR information = 0;
+ LONGLONG startingOffset;
+ ULONGLONG lengthInBytes;
+ BOOLEAN consolidateableBlocksOnly = FALSE;
+ ULONG outputVersion;
+
+ //
+ // Basic parameter validation.
+ // Note that InputBufferLength and IsFdo have been validated beforing entering this routine.
+ //
+ if (dsmOutput == NULL ||
+ dsmAttributes == NULL)
+ {
+ finalStatus = STATUS_INVALID_PARAMETER;
+ goto Exit;
+ }
+
+ if (TEST_FLAG(dsmAttributes->Flags, DEVICE_DSM_FLAG_ENTIRE_DATA_SET_RANGE)) {
+ //
+ // The caller wants the mapping status of the entire disk.
+ //
+ ULONG unmapGranularityAlignment = 0;
+ if (fdoExtension->FunctionSupportInfo->BlockLimitsData.UGAVALID) {
+ unmapGranularityAlignment = fdoExtension->FunctionSupportInfo->BlockLimitsData.UnmapGranularityAlignment;
+ }
+ startingOffset = unmapGranularityAlignment;
+ lengthInBytes = (ULONGLONG)fdoExtension->CommonExtension.PartitionLength.QuadPart - (ULONGLONG)startingOffset;
+ } else {
+ if (dsmAttributes->DataSetRangesOffset == 0 ||
+ dsmAttributes->DataSetRangesLength == 0) {
+ finalStatus = STATUS_INVALID_PARAMETER;
+ goto Exit;
+ }
+
+ //
+ // We only service the first dataset range specified.
+ //
+ dataSetRanges = (PDEVICE_DATA_SET_RANGE)((PUCHAR)dsmAttributes + dsmAttributes->DataSetRangesOffset);
+ startingOffset = dataSetRanges[0].StartingOffset;
+ lengthInBytes = dataSetRanges[0].LengthInBytes;
+ }
+
+
+ //
+ // See if the sender is requesting a specific version of the output data
+ // structure. Othwerwise, default to V1.
+ //
+ outputVersion = DEVICE_DATA_SET_LB_PROVISIONING_STATE_VERSION_V1;
+#if (NTDDI_VERSION >= NTDDI_WINBLUE)
+ if ((dsmAttributes->ParameterBlockOffset >= sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES)) &&
+ (dsmAttributes->ParameterBlockLength >= sizeof(DEVICE_DATA_SET_LBP_STATE_PARAMETERS))) {
+ PDEVICE_DATA_SET_LBP_STATE_PARAMETERS parameters = Add2Ptr(dsmAttributes, dsmAttributes->ParameterBlockOffset);
+ if ((parameters->Version == DEVICE_DATA_SET_LBP_STATE_PARAMETERS_VERSION_V1) &&
+ (parameters->Size >= sizeof(DEVICE_DATA_SET_LBP_STATE_PARAMETERS))) {
+
+ outputVersion = parameters->OutputVersion;
+
+ if ((outputVersion != DEVICE_DATA_SET_LB_PROVISIONING_STATE_VERSION_V1) &&
+ (outputVersion != DEVICE_DATA_SET_LB_PROVISIONING_STATE_VERSION_V2)) {
+ finalStatus = STATUS_INVALID_PARAMETER;
+ goto Exit;
+ }
+ }
+ }
+#endif
+
+ //
+ // Take a snapshot of the block limits data for the worker function to use.
+ // We need to fail the request if we fail to get updated block limits data
+ // since we need an accurate Optimal Unmap Granularity value to properly
+ // convert the returned mapping descriptors into a bitmap.
+ //
+ finalStatus = ClasspBlockLimitsDataSnapshot(fdoExtension,
+ TRUE,
+ &blockLimitsData,
+ &generationCount1);
+
+ if (!NT_SUCCESS(finalStatus)) {
+ information = 0;
+ goto Exit;
+ }
+
+ if (dsmAttributes->Flags & DEVICE_DSM_FLAG_ALLOCATION_CONSOLIDATEABLE_ONLY) {
+ consolidateableBlocksOnly = TRUE;
+ }
+
+ //
+ // The retry logic is to handle the case when block limits data changes during rare occasions
+ // (e.g. diff-VHD fork or merge).
+ //
+ retryCountMax = GET_LBA_STATUS_RETRY_COUNT_MAX;
+ for (retryCount = 0; retryCount < retryCountMax; retryCount++) {
+
+ dsmOutputLength = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
+ getLBAWorkerStatus = ClasspDeviceGetLBAStatusWorker(DeviceObject,
+ &blockLimitsData,
+ startingOffset,
+ lengthInBytes,
+ dsmOutput,
+ &dsmOutputLength,
+ Srb,
+ consolidateableBlocksOnly,
+ outputVersion,
+ &blockLimitsDataMayHaveChanged);
+
+ if (!NT_SUCCESS(getLBAWorkerStatus) && !blockLimitsDataMayHaveChanged) {
+ information = 0;
+ finalStatus = getLBAWorkerStatus;
+ break;
+ }
+
+ //
+ // Again, we need to fail the request if we fail to get updated block
+ // limits data since we need an accurate Optimal Unmap Granularity value.
+ //
+ finalStatus = ClasspBlockLimitsDataSnapshot(fdoExtension,
+ TRUE,
+ &blockLimitsData,
+ &generationCount2);
+ if (!NT_SUCCESS(finalStatus)) {
+ information = 0;
+ goto Exit;
+ }
+
+ if (generationCount1 == generationCount2) {
+ //
+ // Block limits data stays the same during the call to ClasspDeviceGetLBAStatusWorker()
+ // The result from ClasspDeviceGetLBAStatusWorker() is valid.
+ //
+ finalStatus = getLBAWorkerStatus;
+ if (NT_SUCCESS(finalStatus)) {
+ information = dsmOutputLength;
+ }
+ break;
+ }
+
+ //
+ // Try again with the latest block limits data
+ //
+ generationCount1 = generationCount2;
+ information = 0;
+ finalStatus = STATUS_DEVICE_DATA_ERROR;
+ }
+
+Exit:
+ Irp->IoStatus.Information = information;
+ Irp->IoStatus.Status = finalStatus;
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ return finalStatus;
+}
+
+NTSTATUS
+ClasspDeviceGetLBAStatusWorker(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PCLASS_VPD_B0_DATA BlockLimitsData,
+ _In_ ULONGLONG StartingOffset,
+ _In_ ULONGLONG LengthInBytes,
+ _Out_ PDEVICE_MANAGE_DATA_SET_ATTRIBUTES_OUTPUT DsmOutput,
+ _Inout_ PULONG DsmOutputLength,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb,
+ _In_ BOOLEAN ConsolidateableBlocksOnly,
+ _In_ ULONG OutputVersion,
+ _Out_ PBOOLEAN BlockLimitsDataMayHaveChanged
+ )
+/*
+Routine Description:
+
+ This function is to process IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES with DeviceDsmAction_Allocation.
+
+ 1. This function will only handle the first dataset range.
+ 2. This function will not handle dataset ranges whose LengthInBytes is greater than:
+ ((MAXULONG - sizeof(LBA_STATUS_LIST_HEADER)) / sizeof(LBA_STATUS_DESCRIPTOR)) * BytesPerSlab
+
+ The input buffer should consist of a DEVICE_MANAGE_DATA_SET_ATTRIBUTES followed
+ in memory by a single DEVICE_DATA_SET_RANGE that specifies the requested range
+ of slabs for which mapping status is desired.
+
+ The output buffer will consist of a DEVICE_MANAGE_DATA_SET_ATTRIBUTES_OUTPUT
+ followed in memory by a single DEVICE_DATA_SET_LB_PROVISIONING_STATE that
+ contains a bitmap that represents the mapped status of the slabs in the requested
+ range. Note that the number of slabs returned may be less than the number
+ requested.
+
+ Thus function will automatically re-align the given range offset if it was
+ not slab-aligned. The delta between the given range offset and the properly
+ aligned offset will be given in returned DEVICE_DATA_SET_LB_PROVISIONING_STATE.
+
+Arguments:
+ DeviceObject: The FDO of the device to which Get LBA Status will be sent.
+ BlockLimitsData: Block limits data of the device
+ StartingOffset: Starting byte offset of byte range to query LBA status (must be sector aligned)
+ LengthInBytes: Length of byte range to query LBA status (multiple of sector size)
+ DsmOutput: Output data buffer
+ DsmOutputLength: output data buffer size. It will be updated with actual bytes used.
+ Srb: This should be allocated and initialized before it's passed in. It
+ will be used for the Get LBA Status command.
+ ConsolidateableBlocksOnly: Only blocks that are eligible for consolidation
+ should be returned.
+ OutputVersion: The version of the DEVICE_DATA_SET_LB_PROVISIONING_STATE
+ structure to return. This should be one of:
+ - DEVICE_DATA_SET_LB_PROVISIONING_STATE_VERSION_V1
+ - DEVICE_DATA_SET_LB_PROVISIONING_STATE_VERSION_V2
+ BlockLimitsDataMayHaveChanged: if this function fails, this flag indicates
+ if the failure can be caused by changes in device's block limit data.
+
+Return Value:
+
+ STATUS_INVALID_PARAMETER: May be returned under the following conditions:
+ - If the requested range was too large. The caller should try again with a
+ smaller range. See above for how to calculate the maximum range.
+ - If the given starting offset was not within the valid range of the device.
+ STATUS_NOT_SUPPORTED: The storage did not report some information critical to
+ the execution of this function (e.g. Optimal Unmap Granularity).
+ STATUS_BUFFER_TOO_SMALL: The output buffer is not large enough to hold the max
+ data that could be returned from this function. If the output buffer is
+ at least the size of a ULONG, we will write the required output buffer size
+ to the first ULONG bytes of the output buffer.
+ STATUS_DEVICE_DATA_ERROR: The Get LBA Status command succeeded but did not
+ return data as expected.
+--*/
+{
+ NTSTATUS status = STATUS_SUCCESS;
+
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
+
+ PDEVICE_DATA_SET_LB_PROVISIONING_STATE lbpState;
+ ULONG bitMapGranularityInBits = FIELD_SIZE(DEVICE_DATA_SET_LB_PROVISIONING_STATE,SlabAllocationBitMap[0]) * 8;
+ ULONG requiredOutputLength;
+ ULONG outputLength = *DsmOutputLength;
+
+ ULONG blocksPerSlab;
+ ULONGLONG bytesPerSlab;
+ ULONGLONG alignmentInBytes = 0;
+ ULONG alignmentInBlocks = 0;
+ ULONG maxBufferSize;
+ ULONG maxSlabs;
+ ULONGLONG requestedSlabs; // Total number of slabs requested by the caller.
+ ULONGLONG startingLBA;
+ ULONGLONG startingOffsetDelta;
+ ULONG totalProcessedSlabs = 0; // Total number of slabs we processed.
+ ULONGLONG slabsPerCommand; // Number of slabs we can ask for in one Get LBA Status command.
+ BOOLEAN doneProcessing = FALSE; // Indicates we should break out of the Get LBA Status loop.
+
+ ULONG lbaStatusSize;
+ PLBA_STATUS_LIST_HEADER lbaStatusListHeader = NULL;
+
+ //
+ // This function can fail if the block limits data on the device changes.
+ // This flag tells the caller if it should retry with a newer block limits data
+ //
+ *BlockLimitsDataMayHaveChanged = FALSE;
+
+ //
+ // Make sure we're running at PASSIVE_LEVEL
+ //
+ if (KeGetCurrentIrql() >= DISPATCH_LEVEL)
+ {
+ NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
+ status = STATUS_INVALID_LEVEL;
+ goto Exit;
+ }
+
+ //
+ // Don't send down a Get LBA Status command if UNMAP isn't supported.
+ //
+ if (!fdoExtension->FunctionSupportInfo->LBProvisioningData.LBPU)
+ {
+ return STATUS_NOT_SUPPORTED;
+ goto Exit;
+ }
+
+ //
+ // Make sure we have a non-zero value for the number of bytes per block.
+ // Otherwise we will end up dividing by zero later on.
+ //
+ if (fdoExtension->DiskGeometry.BytesPerSector == 0)
+ {
+ status = ClassReadDriveCapacity(fdoExtension->DeviceObject);
+ if(!NT_SUCCESS(status) || fdoExtension->DiskGeometry.BytesPerSector == 0)
+ {
+ status = STATUS_INVALID_DEVICE_REQUEST;
+ goto Exit;
+ }
+ }
+
+ //
+ // We only service the first dataset range specified.
+ //
+ if (BlockLimitsData->UGAVALID == TRUE) {
+ alignmentInBlocks = BlockLimitsData->UnmapGranularityAlignment;
+ alignmentInBytes = (ULONGLONG)alignmentInBlocks * (ULONGLONG)fdoExtension->DiskGeometry.BytesPerSector;
+ }
+
+ //
+ // Make sure the specified range is valid. The Unmap Granularity Alignment
+ // defines a region at the beginning of the disk that cannot be
+ // mapped/unmapped so the specified range should not include any part of that
+ // region.
+ //
+ if (LengthInBytes == 0 ||
+ StartingOffset < alignmentInBytes ||
+ StartingOffset + LengthInBytes > (ULONGLONG)fdoExtension->CommonExtension.PartitionLength.QuadPart)
+ {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspDeviceGetLBAStatusWorker (%p): Invalid range, length is %I64u bytes, starting offset is %I64u bytes, Unmap alignment is %I64u bytes, and disk size is %I64u bytes\n",
+ DeviceObject,
+ LengthInBytes,
+ StartingOffset,
+ alignmentInBytes,
+ (ULONGLONG)fdoExtension->CommonExtension.PartitionLength.QuadPart));
+
+ status = STATUS_INVALID_PARAMETER;
+ goto Exit;
+ }
+
+ //
+ // Calculate the number of bytes per slab so that we can convert (and
+ // possibly align) the given offset (given in bytes) to slabs.
+ //
+ blocksPerSlab = BlockLimitsData->OptimalUnmapGranularity;
+ bytesPerSlab = (ULONGLONG)blocksPerSlab * (ULONGLONG)fdoExtension->DiskGeometry.BytesPerSector;
+
+ //
+ // If the starting offset is not slab-aligned, we need to adjust it to
+ // be aligned with the next highest slab. We also need to save the delta
+ // to return to the user later.
+ //
+ if (((StartingOffset - alignmentInBytes) % bytesPerSlab) != 0)
+ {
+ startingLBA = (((StartingOffset - alignmentInBytes) / bytesPerSlab) + 1) * (ULONGLONG)blocksPerSlab + alignmentInBlocks;
+ startingOffsetDelta = (startingLBA * fdoExtension->DiskGeometry.BytesPerSector) - StartingOffset;
+ }
+ else
+ {
+ startingLBA = ((StartingOffset - alignmentInBytes) / bytesPerSlab) * (ULONGLONG)blocksPerSlab + alignmentInBlocks;
+ startingOffsetDelta = 0;
+ }
+
+ //
+ // Caclulate the number of slabs the caller requested.
+ //
+ if ((LengthInBytes % bytesPerSlab) == 0) {
+ requestedSlabs = (LengthInBytes / bytesPerSlab);
+ } else {
+ //
+ // Round up the number of requested slabs if the length indicates a
+ // partial slab. This should cover the case where the user specifies
+ // a dataset range for the whole disk, but the size of the disk is not
+ // a slab-multiple. Rounding up allows us to return the status of the
+ // partial slab
+ //
+ requestedSlabs = (LengthInBytes / bytesPerSlab) + 1;
+ }
+
+ //
+ // If the caller asked for no slabs then return STATUS_INVALID_PARAMETER.
+ //
+ if (requestedSlabs == 0)
+ {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspDeviceGetLBAStatusWorker (%p): Invalid number (%I64u) of slabs requested\n",
+ DeviceObject,
+ requestedSlabs));
+
+ status = STATUS_INVALID_PARAMETER;
+ goto Exit;
+ }
+
+ //
+ // Cap requested slabs at MAXULONG since SlabAllocationBitMapBitCount
+ // is a 4-byte field. We may return less data than requested, but the
+ // caller can simply re-query for the omitted portion(s).
+ //
+ requestedSlabs = min(requestedSlabs, MAXULONG);
+
+ //
+ // Calculate the required size of the output buffer based upon the desired
+ // version of the output structure.
+ // In the worst case, Get LBA Status returns a descriptor for each slab
+ // requested, thus the required output buffer length is equal to:
+ // 1. The size of DEVICE_MANAGE_DATA_SET_ATTRIBUTES_OUTPUT; plus
+ // 2. The size of DEVICE_DATA_SET_LB_PROVISIONING_STATE(_V2); plus
+ // 3. The size of a ULONG array large enough to hold a bit for each slab requested.
+ // (The first element is already allocated in DEVICE_DATA_SET_LB_PROVISIONING_STATE(_V2).)
+ //
+#if (NTDDI_VERSION >= NTDDI_WINBLUE)
+ if (OutputVersion == DEVICE_DATA_SET_LB_PROVISIONING_STATE_VERSION_V2) {
+
+ requiredOutputLength = (ULONG)(sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES_OUTPUT)
+ + sizeof(DEVICE_DATA_SET_LB_PROVISIONING_STATE_V2)
+ + (((requestedSlabs - 1) / bitMapGranularityInBits))
+ * FIELD_SIZE(DEVICE_DATA_SET_LB_PROVISIONING_STATE_V2, SlabAllocationBitMap[0]));
+
+ } else
+#else
+ UNREFERENCED_PARAMETER(OutputVersion);
+#endif
+ {
+
+ requiredOutputLength = (ULONG)(sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES_OUTPUT)
+ + sizeof(DEVICE_DATA_SET_LB_PROVISIONING_STATE)
+ + (((requestedSlabs - 1) / bitMapGranularityInBits))
+ * FIELD_SIZE(DEVICE_DATA_SET_LB_PROVISIONING_STATE, SlabAllocationBitMap[0]));
+ }
+
+ //
+ // The output buffer is not big enough to hold the requested data.
+ // Inform the caller of the correct buffer size.
+ //
+ if (outputLength < requiredOutputLength)
+ {
+ status = STATUS_BUFFER_TOO_SMALL;
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspDeviceGetLBAStatusWorker (%p): Given output buffer is %u bytes, needs to be %u bytes\n",
+ DeviceObject,
+ outputLength,
+ requiredOutputLength));
+
+ //
+ // If the output buffer is big enough, write the required buffer
+ // length to the first ULONG bytes of the output buffer.
+ //
+ if (outputLength >= sizeof(ULONG))
+ {
+ *((PULONG)DsmOutput) = requiredOutputLength;
+ }
+
+ goto Exit;
+ }
+
+ //
+ // Calculate the maximum number of slabs that could be returned by a single
+ // Get LBA Status command. The max buffer size could either be capped by
+ // the Parameter Data Length field or the Max Transfer Length of the
+ // adapter.
+ // The number of slabs we actually ask for in a single command is the
+ // smaller of the number of slabs requested by the user or the max number
+ // of slabs we can theoretically ask for in a single command.
+ //
+ maxBufferSize = MIN(MAXULONG, fdoExtension->PrivateFdoData->HwMaxXferLen);
+ maxSlabs = (maxBufferSize - sizeof(LBA_STATUS_LIST_HEADER)) / sizeof(LBA_STATUS_DESCRIPTOR);
+ slabsPerCommand = min(requestedSlabs, maxSlabs);
+
+ //
+ // Allocate the buffer that will contain the returned LBA Status Descriptors.
+ // Assume that in the worst case every other slab has a different mapping
+ // status. That means that there may be a descriptor for every slab requested.
+ //
+ lbaStatusSize = (ULONG)(sizeof(LBA_STATUS_LIST_HEADER) + (slabsPerCommand * sizeof(LBA_STATUS_DESCRIPTOR)));
+#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.
+ //
+ lbaStatusSize = ALIGN_UP_BY(lbaStatusSize,KeGetRecommendedSharedDataAlignment());
+ lbaStatusListHeader = (PLBA_STATUS_LIST_HEADER)ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned, lbaStatusSize, CLASS_TAG_LB_PROVISIONING);
+#else
+ lbaStatusListHeader = (PLBA_STATUS_LIST_HEADER)ExAllocatePoolWithTag(NonPagedPoolNx, lbaStatusSize, CLASS_TAG_LB_PROVISIONING);
+#endif
+
+ if (lbaStatusListHeader == NULL)
+ {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspDeviceGetLBAStatusWorker (%p): Failed to allocate %u bytes for descriptors\n",
+ DeviceObject,
+ lbaStatusSize));
+
+ NT_ASSERT(lbaStatusListHeader != NULL);
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto Exit;
+ }
+
+ //
+ // Set default values for the output buffer.
+ // If we process at least one slab from the device we will update the
+ // offset and lengths accordingly.
+ //
+ DsmOutput->Action = DeviceDsmAction_Allocation;
+ DsmOutput->Size = sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES_OUTPUT);
+ DsmOutput->OutputBlockOffset = 0;
+ DsmOutput->OutputBlockLength = 0;
+ *DsmOutputLength = DsmOutput->Size;
+
+ //
+ // The returned DEVICE_DATA_SET_LB_PROVISIONING_STATE is at the end of the
+ // DSM output structure. Zero it out before we start to fill it in.
+ //
+ lbpState = Add2Ptr(DsmOutput, sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES_OUTPUT));
+ RtlZeroMemory(lbpState, requiredOutputLength - sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES_OUTPUT));
+
+ do {
+ //
+ // Send down GetLBAStatus for the current range.
+ //
+ status = GetLBAStatus(fdoExtension,
+ Srb,
+ startingLBA,
+ lbaStatusListHeader,
+ lbaStatusSize,
+ ConsolidateableBlocksOnly);
+
+ if (NT_SUCCESS(status))
+ {
+ ULONG descrIndex = 0;
+ ULONG descrSize = 0;
+ ULONG descrSizeOverhead;
+ ULONG descrCount = 0;
+ ULONGLONG expectedStartingLBA;
+ BOOLEAN processCurrentDescriptor = TRUE;
+ ULONG commandProcessedSlabs = 0; // Number of slabs processed for this command.
+
+ descrSizeOverhead = FIELD_OFFSET(LBA_STATUS_LIST_HEADER, Descriptors[0]) -
+ RTL_SIZEOF_THROUGH_FIELD(LBA_STATUS_LIST_HEADER, ParameterLength);
+ REVERSE_BYTES(&descrSize, &(lbaStatusListHeader->ParameterLength));
+
+ //
+ // If the returned Parameter Data Length field describes more
+ // descriptors than we allocated space for then make sure we don't
+ // try to process more descriptors than are actually present.
+ //
+ if (descrSize > (lbaStatusSize - RTL_SIZEOF_THROUGH_FIELD(LBA_STATUS_LIST_HEADER, ParameterLength))) {
+ descrSize = (lbaStatusSize - RTL_SIZEOF_THROUGH_FIELD(LBA_STATUS_LIST_HEADER, ParameterLength));
+ }
+
+ if (descrSize >= descrSizeOverhead) {
+ descrSize -= descrSizeOverhead;
+ descrCount = descrSize / sizeof(LBA_STATUS_DESCRIPTOR);
+
+ //
+ // Make sure at least one descriptor was returned.
+ //
+ if (descrCount > 0) {
+ //
+ // We expect the first starting LBA returned by the device to be the
+ // same starting LBA we specified in the command.
+ //
+ expectedStartingLBA = startingLBA;
+
+ //
+ // Translate the returned LBA status descriptors into a bitmap where each bit represents
+ // a slab. The slab size is represented by the Optimal Unmap Granularity.
+ // 1 = The slab is mapped.
+ // 0 = The slab is unmapped (deallocated or anchored).
+ //
+ for (descrIndex = 0; descrIndex < descrCount && totalProcessedSlabs < requestedSlabs && !doneProcessing; descrIndex++)
+ {
+ PLBA_STATUS_DESCRIPTOR lbaStatusDescr = &(lbaStatusListHeader->Descriptors[descrIndex]);
+ ULONGLONG returnedStartingLBA;
+ ULONG mapped = (lbaStatusDescr->ProvisioningStatus != LBA_STATUS_MAPPED) ? 0x0 : 0x1;
+ ULONG lbaCount = 0;
+
+ REVERSE_BYTES_QUAD(&returnedStartingLBA, &(lbaStatusDescr->StartingLBA));
+ REVERSE_BYTES(&lbaCount, &(lbaStatusDescr->LogicalBlockCount));
+
+ if (returnedStartingLBA != expectedStartingLBA)
+ {
+ //
+ // We expect the descriptors will express a contiguous range of LBAs.
+ // If the starting LBA is not contiguous with the LBA range from the
+ // previous descriptor then we should not process any more descriptors,
+ // including the current one.
+ //
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspDeviceGetLBAStatusWorker (%p): Device returned starting LBA = %I64x when %I64x was expected.\n",
+ DeviceObject,
+ returnedStartingLBA,
+ startingLBA));
+
+ doneProcessing = TRUE;
+ processCurrentDescriptor = FALSE;
+ *BlockLimitsDataMayHaveChanged = TRUE;
+ }
+ else if (lbaCount > 0 && lbaCount % blocksPerSlab != 0)
+ {
+ //
+ // If the device returned an LBA count with a partial slab, round
+ // the LBA count up to the nearest slab and set a flag to stop
+ // processing further descriptors. This is mainly to handle the
+ // case where disk size may not be slab-aligned and thus the last
+ // "slab" is actually a partial slab.
+ //
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_IOCTL,
+ "ClasspDeviceGetLBAStatusWorker (%p): Device returned an LBA count (%u) that is not a multiple of the slab size (%u)\n",
+ DeviceObject,
+ lbaCount,
+ blocksPerSlab));
+
+ lbaCount = ((lbaCount / blocksPerSlab) + 1) * blocksPerSlab;
+
+ doneProcessing = TRUE;
+ processCurrentDescriptor = TRUE;
+ }
+ else if (lbaCount == 0)
+ {
+ //
+ // If the LBA count is 0, just skip this descriptor.
+ //
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_IOCTL,
+ "ClasspDeviceGetLBAStatusWorker (%p): Device returned a zero LBA count\n",
+ DeviceObject));
+
+ processCurrentDescriptor = FALSE;
+ }
+
+ //
+ // Generate bits for the slabs described in the current descriptor.
+ // It's possible the device may have returned more slabs than requested
+ // so we make sure to stop once we've processed all we need.
+ //
+ if (processCurrentDescriptor)
+ {
+ ULONG descrSlabs = lbaCount / blocksPerSlab; // Number of slabs in this descriptor.
+
+ for(; 0 < descrSlabs && totalProcessedSlabs < requestedSlabs; descrSlabs--, commandProcessedSlabs++, totalProcessedSlabs++)
+ {
+ ULONG bitMapIndex = totalProcessedSlabs / bitMapGranularityInBits;
+ ULONG bitPos = totalProcessedSlabs % bitMapGranularityInBits;
+
+ #if (NTDDI_VERSION >= NTDDI_WINBLUE)
+ if (OutputVersion == DEVICE_DATA_SET_LB_PROVISIONING_STATE_VERSION_V2) {
+ ((PDEVICE_DATA_SET_LB_PROVISIONING_STATE_V2)lbpState)->SlabAllocationBitMap[bitMapIndex] |= (mapped << bitPos);
+ } else
+ #endif
+ {
+ lbpState->SlabAllocationBitMap[bitMapIndex] |= (mapped << bitPos);
+ }
+ }
+ }
+
+ //
+ // Calculate the next expected starting LBA.
+ //
+ expectedStartingLBA = returnedStartingLBA + lbaCount;
+ }
+
+ if (commandProcessedSlabs > 0) {
+
+ //
+ // Calculate the starting LBA we'll use for the next command.
+ //
+ startingLBA += ((ULONGLONG)commandProcessedSlabs * (ULONGLONG)blocksPerSlab);
+
+ } else {
+ //
+ // This should never happen, but we should handle it gracefully anyway.
+ //
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspDeviceGetLBAStatusWorker (%p): The slab allocation bitmap has zero length.\n",
+ DeviceObject));
+
+ NT_ASSERT(commandProcessedSlabs != 0);
+ doneProcessing = TRUE;
+ status = STATUS_UNSUCCESSFUL;
+ }
+ } else {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspDeviceGetLBAStatusWorker (%p): Device returned no LBA Status Descriptors.\n",
+ DeviceObject));
+
+ doneProcessing = TRUE;
+ status = STATUS_UNSUCCESSFUL;
+ }
+ } else {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspDeviceGetLBAStatusWorker (%p): not enough bytes returned\n",
+ DeviceObject));
+
+ doneProcessing = TRUE;
+ status = STATUS_DEVICE_DATA_ERROR;
+ }
+ }
+
+ //
+ // Loop until we encounter some error or we've processed all the requested slabs.
+ //
+ } while (NT_SUCCESS(status) &&
+ !doneProcessing &&
+ (totalProcessedSlabs < requestedSlabs));
+
+ //
+ // At least one slab was returned by the device and processed, which we
+ // consider success. It's up to the caller to detect truncation.
+ // Update the output buffer sizes, offsets, etc. accordingly.
+ //
+ if (totalProcessedSlabs > 0) {
+
+ #if (NTDDI_VERSION >= NTDDI_WINBLUE)
+ if (OutputVersion == DEVICE_DATA_SET_LB_PROVISIONING_STATE_VERSION_V2) {
+ PDEVICE_DATA_SET_LB_PROVISIONING_STATE_V2 lbpStateV2 = (PDEVICE_DATA_SET_LB_PROVISIONING_STATE_V2)lbpState;
+
+ lbpStateV2->SlabSizeInBytes = bytesPerSlab;
+ lbpStateV2->SlabOffsetDeltaInBytes = startingOffsetDelta;
+ lbpStateV2->SlabAllocationBitMapBitCount = totalProcessedSlabs;
+ lbpStateV2->SlabAllocationBitMapLength = ((totalProcessedSlabs - 1) / (ULONGLONG)bitMapGranularityInBits) + 1;
+ lbpStateV2->Version = DEVICE_DATA_SET_LB_PROVISIONING_STATE_VERSION_V2;
+
+ //
+ // Note that there is already one element of the bitmap array allocated
+ // in the DEVICE_DATA_SET_LB_PROVISIONING_STATE_V2 structure itself, which
+ // is why we subtract 1 from SlabAllocationBitMapLength.
+ //
+ lbpStateV2->Size = sizeof(DEVICE_DATA_SET_LB_PROVISIONING_STATE_V2)
+ + ((lbpStateV2->SlabAllocationBitMapLength - 1) * sizeof(lbpStateV2->SlabAllocationBitMap[0]));
+
+ } else
+ #endif
+ {
+
+ lbpState->SlabSizeInBytes = bytesPerSlab;
+ lbpState->SlabOffsetDeltaInBytes = (ULONG)startingOffsetDelta;
+ lbpState->SlabAllocationBitMapBitCount = totalProcessedSlabs;
+ lbpState->SlabAllocationBitMapLength = ((totalProcessedSlabs - 1) / bitMapGranularityInBits) + 1;
+ lbpState->Version = DEVICE_DATA_SET_LB_PROVISIONING_STATE_VERSION_V1;
+
+ //
+ // Note that there is already one element of the bitmap array allocated
+ // in the DEVICE_DATA_SET_LB_PROVISIONING_STATE structure itself, which
+ // is why we subtract 1 from SlabAllocationBitMapLength.
+ //
+ lbpState->Size = sizeof(DEVICE_DATA_SET_LB_PROVISIONING_STATE)
+ + ((lbpState->SlabAllocationBitMapLength - 1) * sizeof(lbpState->SlabAllocationBitMap[0]));
+ }
+
+ DsmOutput->OutputBlockLength = lbpState->Size; // Size is at the same offset in all versions of the structure.
+ DsmOutput->OutputBlockOffset = sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES_OUTPUT);
+ *DsmOutputLength = DsmOutput->Size + DsmOutput->OutputBlockLength;
+
+ status = STATUS_SUCCESS;
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "ClasspDeviceGetLBAStatusWorker (%p): Processed a total of %u slabs\n",
+ DeviceObject,
+ totalProcessedSlabs));
+Exit:
+
+ FREE_POOL(lbaStatusListHeader);
+ return status;
+}
+
+NTSTATUS ClassGetLBProvisioningLogPage(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PSCSI_REQUEST_BLOCK Srb,
+ _In_ ULONG LogPageSize,
+ _Inout_ PLOG_PAGE_LOGICAL_BLOCK_PROVISIONING LogPage
+ )
+/*
+Routine Description:
+
+ This function sends a LOG SENSE command to the given device and returns the
+ Logical Block Provisioning Log Page, if available.
+
+Arguments:
+ DeviceObject: The FDO of the device to which the Log Sense command will be sent.
+ Srb: This should be allocated before it is passed in, but it does not have
+ to be initialized. This function will initialize it.
+ LogPageSize: The size of the LogPage buffer in bytes.
+ LogPage: A pointer to an already allocated output buffer that may contain
+ the LBP log page when this function returns.
+
+Return Value:
+
+ STATUS_INVALID_PARAMETER: May be returned if the LogPage buffer is NULL or
+ not large enough.
+ STATUS_SUCCESS: The log page was obtained and placed in the LogPage buffer.
+
+ This function may return other NTSTATUS codes from internal function calls.
+--*/
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
+ NTSTATUS status = STATUS_SUCCESS;
+ PCDB cdb = NULL;
+
+ //
+ // Make sure the caller passed in an adequate output buffer. The Allocation
+ // Length field in the Log Sense command is only 2 bytes so we need to also
+ // make sure that the given log page size isn't larger than MAXUSHORT.
+ //
+ if (LogPage == NULL ||
+ LogPageSize < sizeof(LOG_PAGE_LOGICAL_BLOCK_PROVISIONING) ||
+ LogPageSize > MAXUSHORT)
+ {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "ClassGetLBProvisioningLogPage: DO (%p), Invalid parameter, LogPage = %p, LogPageSize = %u.\n",
+ DeviceObject,
+ LogPage,
+ LogPageSize));
+
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Initialize the SRB.
+ //
+ if (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.
+ //
+ NT_ASSERT(FALSE);
+ }
+ } else {
+ RtlZeroMemory(Srb, sizeof(SCSI_REQUEST_BLOCK));
+ Srb->Length = sizeof(SCSI_REQUEST_BLOCK);
+ Srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
+ }
+
+ //
+ // Build and send down the Log Sense command.
+ //
+ SrbSetTimeOutValue(Srb, fdoExtension->TimeOutValue);
+ SrbSetRequestTag(Srb, SP_UNTAGGED);
+ SrbSetRequestAttribute(Srb, SRB_SIMPLE_TAG_REQUEST);
+ SrbAssignSrbFlags(Srb, fdoExtension->SrbFlags);
+ SrbSetCdbLength(Srb, sizeof(cdb->LOGSENSE));
+
+ cdb = SrbGetCdb(Srb);
+ cdb->LOGSENSE.OperationCode = SCSIOP_LOG_SENSE;
+ cdb->LOGSENSE.PageCode = LOG_PAGE_CODE_LOGICAL_BLOCK_PROVISIONING;
+ cdb->LOGSENSE.PCBit = 0;
+ cdb->LOGSENSE.ParameterPointer[0] = 0;
+ cdb->LOGSENSE.ParameterPointer[1] = 0;
+ REVERSE_BYTES_SHORT(&(cdb->LOGSENSE.AllocationLength), &LogPageSize);
+
+ status = ClassSendSrbSynchronous(fdoExtension->DeviceObject,
+ Srb,
+ LogPage,
+ LogPageSize,
+ 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(Srb) < LogPageSize)
+ {
+ status = STATUS_SUCCESS;
+ }
+
+ //
+ // Log the command.
+ //
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "ClassGetLBProvisioningLogPage: DO (%p), LogSense command issued for LBP log page. NT Status: %!STATUS!.\n",
+ DeviceObject,
+ status
+ ));
+
+ return status;
+}
+
+NTSTATUS ClassInterpretLBProvisioningLogPage(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ ULONG LogPageSize,
+ _In_ PLOG_PAGE_LOGICAL_BLOCK_PROVISIONING LogPage,
+ _In_ ULONG ResourcesSize,
+ _Out_ PSTORAGE_LB_PROVISIONING_MAP_RESOURCES Resources
+ )
+/*
+Routine Description:
+
+ This function takes a Logical Block Provisioning log page (returned by
+ ClassGetLBProvisioningLogPage(), for example), interprets its contents,
+ and returns the interpreted data in a STORAGE_LB_PROVISIONING_MAP_RESOURCES
+ structure.
+
+ None, some, or all of the data in the output buffer may be valid. The
+ caller must look at the individual "Valid" fields to see which fields have
+ valid data.
+
+Arguments:
+ DeviceObject: The FDO of the device from which the log page was obtained.
+ LogPageSize: The size of the LogPage buffer in bytes.
+ LogPage: A pointer to a valid LBP log page structure.
+ ResourcesSize: The size of the Resources buffer in bytes.
+ Resources: A pointer to an already allocated output buffer that may contain
+ the interpreted log page data when this function returns.
+
+Return Value:
+
+ STATUS_NOT_SUPPORTED: May be returned if the threshold exponent from the
+ 0xB2 page is invalid.
+ STATUS_INVALID_PARAMETER: May be returned if either the LogPage or Resources
+ buffers are NULL or too small.
+ STATUS_SUCCESS: The log page data was interpreted and the Resources output
+ buffer has data in it.
+
+ This function may return other NTSTATUS codes from internal function calls.
+--*/
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
+ USHORT pageLength;
+ PLOG_PARAMETER_HEADER parameter;
+ PVOID endOfPage;
+ USHORT parameterCode;
+ ULONG resourceCount;
+ UCHAR thresholdExponent = fdoExtension->FunctionSupportInfo->LBProvisioningData.ThresholdExponent;
+ ULONGLONG thresholdSetSize;
+
+ //
+ // SBC-3 states that the threshold exponent (from the 0xB2 VPD page), must
+ // be non-zero and less than or equal to 32.
+ //
+ if (thresholdExponent < 0 || thresholdExponent > 32)
+ {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "ClassInterpretLBProvisioningLogPage: DO (%p), Threshold Exponent (%u) is invalid.\n",
+ DeviceObject,
+ thresholdExponent));
+
+ return STATUS_NOT_SUPPORTED;
+ }
+
+ if (Resources == NULL ||
+ ResourcesSize < sizeof(STORAGE_LB_PROVISIONING_MAP_RESOURCES) ||
+ LogPage == NULL ||
+ LogPageSize < sizeof(LOG_PAGE_LOGICAL_BLOCK_PROVISIONING))
+ {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "ClassInterpretLBProvisioningLogPage: DO (%p), Invalid parameter, Resources = %p, ResourcesSize = %u, LogPage = %p, LogPageSize = %u.\n",
+ DeviceObject,
+ Resources,
+ ResourcesSize,
+ LogPage,
+ LogPageSize));
+
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Calculate the threshold set size (in LBAs).
+ //
+ thresholdSetSize = 1I64 << thresholdExponent;
+
+ REVERSE_BYTES_SHORT(&pageLength, &(LogPage->PageLength));
+
+ //
+ // Initialize the output buffer.
+ //
+ RtlZeroMemory(Resources, sizeof(STORAGE_LB_PROVISIONING_MAP_RESOURCES));
+ Resources->Size = sizeof(STORAGE_LB_PROVISIONING_MAP_RESOURCES);
+ Resources->Version = sizeof(STORAGE_LB_PROVISIONING_MAP_RESOURCES);
+
+ //
+ // Make sure we don't walk off the end of the log page buffer
+ // if pageLength is somehow longer than the buffer itself.
+ //
+ pageLength = (USHORT)min(pageLength, (LogPageSize - FIELD_OFFSET(LOG_PAGE_LOGICAL_BLOCK_PROVISIONING, Parameters)));
+
+ parameter = (PLOG_PARAMETER_HEADER)((PUCHAR)LogPage + FIELD_OFFSET(LOG_PAGE_LOGICAL_BLOCK_PROVISIONING, Parameters));
+ endOfPage = (PVOID)((PUCHAR)parameter + pageLength);
+
+ //
+ // Walk the parameters.
+ //
+ while ((PVOID)parameter < endOfPage)
+ {
+ if (parameter->ParameterLength > 0)
+ {
+ REVERSE_BYTES_SHORT(&parameterCode, &(parameter->ParameterCode));
+ switch(parameterCode)
+ {
+ case LOG_PAGE_LBP_PARAMETER_CODE_AVAILABLE:
+ {
+ REVERSE_BYTES(&resourceCount, &(((PLOG_PARAMETER_THRESHOLD_RESOURCE_COUNT)parameter)->ResourceCount));
+ Resources->AvailableMappingResources = (ULONGLONG)resourceCount * thresholdSetSize * (ULONGLONG)fdoExtension->DiskGeometry.BytesPerSector;
+ Resources->AvailableMappingResourcesValid = TRUE;
+
+ //
+ // Devices that implement SBC-3 revisions older than r27 will not specify
+ // an LBP log page parameter that has fields beyond ResourceCount.
+ //
+ if (parameter->ParameterLength > FIELD_OFFSET(LOG_PARAMETER_THRESHOLD_RESOURCE_COUNT, ResourceCount[3])) {
+ Resources->AvailableMappingResourcesScope = ((PLOG_PARAMETER_THRESHOLD_RESOURCE_COUNT)parameter)->Scope;
+ }
+
+ break;
+ }
+
+ case LOG_PAGE_LBP_PARAMETER_CODE_USED:
+ {
+ REVERSE_BYTES(&resourceCount, &(((PLOG_PARAMETER_THRESHOLD_RESOURCE_COUNT)parameter)->ResourceCount));
+ Resources->UsedMappingResources = (ULONGLONG)resourceCount * thresholdSetSize * (ULONGLONG)fdoExtension->DiskGeometry.BytesPerSector;
+ Resources->UsedMappingResourcesValid = TRUE;
+
+ //
+ // Devices that implement SBC-3 revisions older than r27 will not specify
+ // an LBP log page parameter that has fields beyond ResourceCount.
+ //
+ if (parameter->ParameterLength > FIELD_OFFSET(LOG_PARAMETER_THRESHOLD_RESOURCE_COUNT, ResourceCount[3])) {
+ Resources->UsedMappingResourcesScope = ((PLOG_PARAMETER_THRESHOLD_RESOURCE_COUNT)parameter)->Scope;
+ }
+
+ break;
+ }
+ }
+ }
+
+ //
+ // Move to the next parameter.
+ //
+ parameter = (PLOG_PARAMETER_HEADER)((PUCHAR)parameter + sizeof(LOG_PARAMETER_HEADER) + parameter->ParameterLength);
+ }
+
+ return STATUS_SUCCESS;
+}
+
+NTSTATUS ClassGetLBProvisioningResources(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb,
+ _In_ ULONG ResourcesSize,
+ _Inout_ PSTORAGE_LB_PROVISIONING_MAP_RESOURCES Resources
+ )
+/*
+Routine Description:
+
+ This function obtains the Logical Block Provisioning log page, interprets
+ its contents, and returns the interpreted data in a
+ STORAGE_LB_PROVISIONING_MAP_RESOURCES structure.
+
+ None, some, or all of the data in the output buffer may be valid. The
+ caller must look at the individual "Valid" fields to see which fields have
+ valid data.
+
+Arguments:
+ DeviceObject: The target FDO.
+ Srb: This should be allocated before it is passed in, but it does not have
+ to be initialized.
+ ResourcesSize: The size of the Resources buffer in bytes.
+ Resources: A pointer to an already allocated output buffer that may contain
+ the interpreted log page data when this function returns.
+
+Return Value:
+
+ STATUS_NOT_SUPPORTED: May be returned if the device does not have LBP enabled.
+ STATUS_INVALID_PARAMETER: May be returned if either the Resources buffer is
+ NULL or too small.
+ STATUS_INSUFFICIENT_RESOURCES: May be returned if a log page buffer could not
+ be allocated.
+ STATUS_SUCCESS: The log page data was obtained and the Resources output
+ buffer has data in it.
+
+ This function may return other NTSTATUS codes from internal function calls.
+--*/
+{
+ NTSTATUS status;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
+ ULONG logPageSize;
+ PLOG_PAGE_LOGICAL_BLOCK_PROVISIONING logPage = NULL;
+
+ //
+ // This functionality is only supported for devices that support logical
+ // block provisioning.
+ //
+ if (fdoExtension->FunctionSupportInfo->ValidInquiryPages.LBProvisioning == FALSE)
+ {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "ClassGetLBProvisioningResources: DO (%p), Device does not support logical block provisioning.\n",
+ DeviceObject));
+
+ return STATUS_NOT_SUPPORTED;
+ }
+
+ //
+ // Validate the output buffer.
+ //
+ if (Resources == NULL ||
+ ResourcesSize < sizeof(STORAGE_LB_PROVISIONING_MAP_RESOURCES))
+ {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "ClassGetLBProvisioningResources: DO (%p), Invalid parameter, Resources = %p, ResourcesSize = %u.\n",
+ DeviceObject,
+ Resources,
+ ResourcesSize));
+
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Allocate a buffer for the log page. Currently the log page contains:
+ // 1. Log page header
+ // 2. Log page parameter for used resources
+ // 3. Log page parameter for available resources
+ //
+ logPageSize = sizeof(LOG_PAGE_LOGICAL_BLOCK_PROVISIONING) + (2 * sizeof(LOG_PARAMETER_THRESHOLD_RESOURCE_COUNT));
+
+#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.
+ //
+ logPageSize = ALIGN_UP_BY(logPageSize, KeGetRecommendedSharedDataAlignment());
+ logPage = (PLOG_PAGE_LOGICAL_BLOCK_PROVISIONING)ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned, logPageSize, CLASS_TAG_LB_PROVISIONING);
+#else
+ logPage = (PLOG_PAGE_LOGICAL_BLOCK_PROVISIONING)ExAllocatePoolWithTag(NonPagedPoolNx, logPageSize, CLASS_TAG_LB_PROVISIONING);
+#endif
+ if (logPage != NULL)
+ {
+ //
+ // Get the LBP log page from the device.
+ //
+ status = ClassGetLBProvisioningLogPage(DeviceObject,
+ Srb,
+ logPageSize,
+ logPage);
+
+ if (NT_SUCCESS(status))
+ {
+ //
+ // Interpret the log page and fill in the output buffer.
+ //
+ status = ClassInterpretLBProvisioningLogPage(DeviceObject,
+ logPageSize,
+ logPage,
+ ResourcesSize,
+ Resources);
+ }
+
+ ExFreePool(logPage);
+ }
+ else
+ {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "ClassGetLBProvisioningResources: DO (%p), Failed to allocate memory for LBP log page.\n",
+ DeviceObject));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ return status;
+}
+
+NTSTATUS
+ClassDeviceGetLBProvisioningResources(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _Inout_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ )
+/*
+Routine Description:
+
+ This function returns the LBP resource counts in a
+ STORAGE_LB_PROVISIONING_MAP_RESOURCES structure in the IRP.
+
+ None, some, or all of the data in the output buffer may be valid. The
+ caller must look at the individual "Valid" fields to see which fields have
+ valid data.
+
+Arguments:
+ DeviceObject: The target FDO.
+ Irp: The IRP which will contain the output buffer upon completion.
+ Srb: This should be allocated before it is passed in, but it does not have
+ to be initialized.
+
+Return Value:
+
+ Some NTSTATUS code.
+
+--*/
+{
+ NTSTATUS status;
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ PSTORAGE_LB_PROVISIONING_MAP_RESOURCES mapResources = (PSTORAGE_LB_PROVISIONING_MAP_RESOURCES)Irp->AssociatedIrp.SystemBuffer;
+
+ status = ClassGetLBProvisioningResources(DeviceObject,
+ Srb,
+ irpStack->Parameters.DeviceIoControl.OutputBufferLength,
+ mapResources);
+
+ if (NT_SUCCESS(status)) {
+ Irp->IoStatus.Information = mapResources->Size;
+ } else {
+ Irp->IoStatus.Information = 0;
+ }
+
+ Irp->IoStatus.Status = status;
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+
+ return status;
+}
+
+_Function_class_(IO_WORKITEM_ROUTINE)
+_IRQL_requires_(PASSIVE_LEVEL)
+_IRQL_requires_same_
+VOID
+ClassLogThresholdEvent(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_opt_ PVOID Context
+ )
+/*
+ Routine Description:
+
+ This function logs a logical block provisioning soft threshold event to the
+ system event log.
+
+Arguments:
+ DeviceObject: The FDO that represents the device that reported the soft
+ threshold.
+ Context: A pointer to the IO_WORKITEM in which this function is running.
+
+--*/
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ PIO_WORKITEM workItem = (PIO_WORKITEM)Context;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
+ PSCSI_REQUEST_BLOCK srb = NULL;
+ STORAGE_LB_PROVISIONING_MAP_RESOURCES resources = {0};
+ ULONG resourcesSize = sizeof(STORAGE_LB_PROVISIONING_MAP_RESOURCES);
+ PIO_ERROR_LOG_PACKET errorLogEntry = NULL;
+ ULONG logEntrySize = sizeof(IO_ERROR_LOG_PACKET);
+ PWCHAR stringIndex = NULL;
+ LONG stringSize = 0;
+ ULONG srbSize;
+
+ //
+ // Allocate an SRB for getting the LBP log page.
+ //
+ 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,
+ 'ACcS');
+ if (srb != NULL) {
+
+ //
+ // Try to get the LBP resources from the device so we can report them in
+ // the system event log.
+ //
+ ClassGetLBProvisioningResources(DeviceObject,
+ srb,
+ resourcesSize,
+ &resources);
+
+ //
+ // We need to allocate enough space for 3 insertion strings:
+ // The first is a ULONG representing the disk number in decimal, which means
+ // a max of 10 digits, plus one for the NULL character.
+ // The second and third are ULONGLONGs representing the used and available
+ // bytes, which means a max of 20 digits, plus one for the NULL character.
+ // Make sure we do not exceed the max error log size or the max size of a
+ // UCHAR since the size gets truncated to a UCHAR when we pass it to
+ // IoAllocateErrorLogEntry().
+ //
+ logEntrySize = sizeof(IO_ERROR_LOG_PACKET) + (11 * sizeof(WCHAR)) + (2 * (21 * sizeof(WCHAR)));
+ logEntrySize = min(logEntrySize, ERROR_LOG_MAXIMUM_SIZE);
+ logEntrySize = min(logEntrySize, MAXUCHAR);
+
+ errorLogEntry = (PIO_ERROR_LOG_PACKET)IoAllocateErrorLogEntry(DeviceObject, (UCHAR)logEntrySize);
+ if (errorLogEntry != NULL)
+ {
+ //
+ // There are two event IDs we can use here. Both use the disk number,
+ // but one reports the available and used bytes while the other does not.
+ // We fall back on the latter if we failed to obtain the available and
+ // used byte counts from the LBP log page.
+ //
+ // The event insertion strings need to be in this order:
+ // 1. The disk number. (Both event IDs use this.)
+ // 2. Bytes used.
+ // 3. Bytes available.
+ //
+
+ RtlZeroMemory(errorLogEntry, logEntrySize);
+ errorLogEntry->StringOffset = sizeof(IO_ERROR_LOG_PACKET);
+
+ stringIndex = (PWCHAR)((ULONG_PTR)errorLogEntry + sizeof(IO_ERROR_LOG_PACKET));
+ stringSize = logEntrySize - sizeof(IO_ERROR_LOG_PACKET);
+
+ //
+ // Add the disk number to the insertion strings.
+ //
+ status = RtlStringCbPrintfW(stringIndex, stringSize, L"%d", fdoExtension->DeviceNumber);
+
+ if (NT_SUCCESS(status) )
+ {
+ errorLogEntry->NumberOfStrings++;
+
+ if (resources.UsedMappingResourcesValid &&
+ resources.AvailableMappingResourcesValid)
+ {
+ //
+ // Add the used mapping resources to the insertion strings.
+ //
+ stringIndex += (wcslen(stringIndex) + 1);
+ stringSize -= (LONG)(wcslen(stringIndex) + 1) * sizeof(WCHAR);
+
+ status = RtlStringCbPrintfW(stringIndex, stringSize, L"%I64u", resources.UsedMappingResources);
+
+ if (NT_SUCCESS(status))
+ {
+ errorLogEntry->NumberOfStrings++;
+
+ //
+ // Add the available mapping resources to the insertion strings.
+ //
+ stringIndex += (wcslen(stringIndex) + 1);
+ stringSize -= (LONG)(wcslen(stringIndex) + 1) * sizeof(WCHAR);
+
+ status = RtlStringCbPrintfW(stringIndex, stringSize, L"%I64u", resources.AvailableMappingResources);
+
+ if (NT_SUCCESS(status))
+ {
+ errorLogEntry->NumberOfStrings++;
+ }
+ }
+ }
+ else
+ {
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_GENERAL,
+ "ClassLogThresholdEvent: DO (%p), Used and available mapping resources were unavailable.\n",
+ DeviceObject));
+ }
+ }
+
+ //
+ // If we were able to successfully assemble all 3 insertion strings,
+ // then we can use one of the "extended" event IDs. Otherwise, use the basic
+ // event ID, which only requires the disk number.
+ //
+ if (errorLogEntry->NumberOfStrings == 3)
+ {
+ if (resources.UsedMappingResourcesScope == LOG_PAGE_LBP_RESOURCE_SCOPE_DEDICATED_TO_LUN &&
+ resources.AvailableMappingResourcesScope == LOG_PAGE_LBP_RESOURCE_SCOPE_DEDICATED_TO_LUN) {
+
+ errorLogEntry->ErrorCode = IO_WARNING_SOFT_THRESHOLD_REACHED_EX_LUN_LUN;
+
+ } else if (resources.UsedMappingResourcesScope == LOG_PAGE_LBP_RESOURCE_SCOPE_DEDICATED_TO_LUN &&
+ resources.AvailableMappingResourcesScope == LOG_PAGE_LBP_RESOURCE_SCOPE_NOT_DEDICATED_TO_LUN) {
+
+ errorLogEntry->ErrorCode = IO_WARNING_SOFT_THRESHOLD_REACHED_EX_LUN_POOL;
+
+ } else if (resources.UsedMappingResourcesScope == LOG_PAGE_LBP_RESOURCE_SCOPE_NOT_DEDICATED_TO_LUN &&
+ resources.AvailableMappingResourcesScope == LOG_PAGE_LBP_RESOURCE_SCOPE_DEDICATED_TO_LUN) {
+
+ errorLogEntry->ErrorCode = IO_WARNING_SOFT_THRESHOLD_REACHED_EX_POOL_LUN;
+
+ } else if (resources.UsedMappingResourcesScope == LOG_PAGE_LBP_RESOURCE_SCOPE_NOT_DEDICATED_TO_LUN &&
+ resources.AvailableMappingResourcesScope == LOG_PAGE_LBP_RESOURCE_SCOPE_NOT_DEDICATED_TO_LUN) {
+
+ errorLogEntry->ErrorCode = IO_WARNING_SOFT_THRESHOLD_REACHED_EX_POOL_POOL;
+
+ } else {
+
+ errorLogEntry->ErrorCode = IO_WARNING_SOFT_THRESHOLD_REACHED_EX;
+ }
+ }
+ else
+ {
+ errorLogEntry->ErrorCode = IO_WARNING_SOFT_THRESHOLD_REACHED;
+ }
+
+ //
+ // Write the error log packet to the system error logging thread.
+ // It will be freed automatically.
+ //
+ IoWriteErrorLogEntry(errorLogEntry);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "ClassLogThresholdEvent: DO (%p), Soft threshold notification logged.\n",
+ DeviceObject));
+ }
+ else
+ {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "ClassLogThresholdEvent: DO (%p), Failed to allocate memory for error log entry.\n",
+ DeviceObject));
+ }
+ } else {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "ClassLogThresholdEvent: DO (%p), Failed to allocate memory for SRB.\n",
+ DeviceObject));
+ }
+
+
+ //
+ // Clear the soft threshold event pending flag so that another can be queued.
+ //
+ InterlockedExchange((PLONG)&(fdoExtension->FunctionSupportInfo->LBProvisioningData.SoftThresholdEventPending), 0);
+
+ ClassReleaseRemoveLock(DeviceObject, (PIRP)workItem);
+
+ FREE_POOL(srb);
+
+ if (workItem != NULL) {
+ IoFreeWorkItem(workItem);
+ }
+}
+
+NTSTATUS
+ClasspLogSystemEventWithDeviceNumber(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ NTSTATUS IoErrorCode
+ )
+/*
+ Routine Description:
+
+ This function is a helper routine to log any system events that require
+ the DeviceNumber (e.g. disk number). It is basically a wrapper for the
+ IoWriteErrorLogEntry call.
+
+Arguments:
+ DeviceObject: The FDO that represents the device for which the event needs to be logged.
+ IoErrorCode: The IO error code for the event.
+
+Return Value:
+ STATUS_SUCCESS - if the event was logged
+ STATUS_INSUFFICIENT_RESOURCES - otherwise
+
+--*/
+{
+ NTSTATUS status = STATUS_INSUFFICIENT_RESOURCES;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
+ PIO_ERROR_LOG_PACKET errorLogEntry = NULL;
+ ULONG logEntrySize = sizeof(IO_ERROR_LOG_PACKET);
+ PWCHAR stringIndex = NULL;
+ LONG stringSize = 0;
+
+ //
+ // We need to allocate enough space for one insertion string: a ULONG
+ // representing the disk number in decimal, which means a max of 10 digits,
+ // plus one for the NULL character.
+ // Make sure we do not exceed the max error log size or the max size of a
+ // UCHAR since the size gets truncated to a UCHAR when we pass it to
+ // IoAllocateErrorLogEntry().
+ //
+ logEntrySize = sizeof(IO_ERROR_LOG_PACKET) + (11 * sizeof(WCHAR));
+ logEntrySize = min(logEntrySize, ERROR_LOG_MAXIMUM_SIZE);
+ logEntrySize = min(logEntrySize, MAXUCHAR);
+
+ errorLogEntry = (PIO_ERROR_LOG_PACKET)IoAllocateErrorLogEntry(DeviceObject, (UCHAR)logEntrySize);
+ if (errorLogEntry) {
+
+ RtlZeroMemory(errorLogEntry, logEntrySize);
+ errorLogEntry->StringOffset = sizeof(IO_ERROR_LOG_PACKET);
+ errorLogEntry->ErrorCode = IoErrorCode;
+
+ stringIndex = (PWCHAR)((ULONG_PTR)errorLogEntry + sizeof(IO_ERROR_LOG_PACKET));
+ stringSize = logEntrySize - sizeof(IO_ERROR_LOG_PACKET);
+
+ //
+ // Add the disk number to the insertion strings.
+ //
+ status = RtlStringCbPrintfW(stringIndex, stringSize, L"%d", fdoExtension->DeviceNumber);
+
+ if (NT_SUCCESS(status)) {
+ errorLogEntry->NumberOfStrings++;
+ }
+
+ //
+ // Write the error log packet to the system error logging thread.
+ // It will be freed automatically.
+ //
+ IoWriteErrorLogEntry(errorLogEntry);
+
+ status = STATUS_SUCCESS;
+ }
+
+ return status;
+}
+
+_Function_class_(IO_WORKITEM_ROUTINE)
+_IRQL_requires_(PASSIVE_LEVEL)
+_IRQL_requires_same_
+VOID
+ClassLogResourceExhaustionEvent(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_opt_ PVOID Context
+ )
+/*
+ Routine Description:
+
+ This function logs a logical block provisioning permanent resource exhaustion
+ event to the system event log.
+
+Arguments:
+ DeviceObject: The FDO that represents the device that reported the permanent
+ resource exhaustion.
+ Context: A pointer to the IO_WORKITEM in which this function is running.
+
+--*/
+{
+ PIO_WORKITEM workItem = (PIO_WORKITEM)Context;
+
+ if (NT_SUCCESS(ClasspLogSystemEventWithDeviceNumber(DeviceObject, IO_ERROR_DISK_RESOURCES_EXHAUSTED))) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "ClassLogResourceExhaustionEvent: DO (%p), Permanent resource exhaustion logged.\n",
+ DeviceObject));
+ } else {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "ClassLogResourceExhaustionEvent: DO (%p), Failed to allocate memory for error log entry.\n",
+ DeviceObject));
+ }
+
+
+ ClassReleaseRemoveLock(DeviceObject, (PIRP)workItem);
+
+ if (workItem != NULL) {
+ IoFreeWorkItem(workItem);
+ }
+}
+
+
+VOID ClassQueueThresholdEventWorker(
+ _In_ PDEVICE_OBJECT DeviceObject
+ )
+/*
+Routine Description:
+
+ This function queues a delayed work item that will eventually log a
+ logical block provisioning soft threshold event to the system event log.
+
+Arguments:
+ DeviceObject: The FDO that represents the device that reported the soft
+ threshold.
+
+--*/
+{
+ PCOMMON_DEVICE_EXTENSION commonExtension = (PCOMMON_DEVICE_EXTENSION)(DeviceObject->DeviceExtension);
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)(DeviceObject->DeviceExtension);
+ PIO_WORKITEM workItem = NULL;
+
+ if (commonExtension->IsFdo &&
+ InterlockedCompareExchange((PLONG)&(fdoExtension->FunctionSupportInfo->LBProvisioningData.SoftThresholdEventPending), 1, 0) == 0)
+ {
+ workItem = IoAllocateWorkItem(DeviceObject);
+
+ if (workItem)
+ {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "ClassQueueThresholdEventWorker: DO (%p), Queueing soft threshold notification work item.\n",
+ DeviceObject));
+
+
+ ClassAcquireRemoveLock(DeviceObject, (PIRP)(workItem));
+
+ //
+ // Queue a work item to write the threshold notification to the
+ // system event log.
+ //
+ IoQueueWorkItem(workItem, ClassLogThresholdEvent, DelayedWorkQueue, workItem);
+ }
+ else
+ {
+ //
+ // Clear the soft threshold event pending flag since this is normally
+ // done when the work item completes.
+ //
+ InterlockedExchange((PLONG)&(fdoExtension->FunctionSupportInfo->LBProvisioningData.SoftThresholdEventPending), 0);
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "ClassQueueThresholdEventWorker: DO (%p), Failed to allocate memory for the work item.\n",
+ DeviceObject));
+ }
+ }
+}
+
+VOID ClassQueueResourceExhaustionEventWorker(
+ _In_ PDEVICE_OBJECT DeviceObject
+ )
+/*
+Routine Description:
+
+ This function queues a delayed work item that will eventually log a
+ logical block provisioning permanent resource exhaustion event to the
+ system event log.
+
+Arguments:
+ DeviceObject: The FDO that represents the device that reported the resource
+ exhaustion.
+
+--*/
+{
+ PCOMMON_DEVICE_EXTENSION commonExtension = (PCOMMON_DEVICE_EXTENSION)(DeviceObject->DeviceExtension);
+ PIO_WORKITEM workItem = NULL;
+
+ if (commonExtension->IsFdo)
+ {
+ workItem = IoAllocateWorkItem(DeviceObject);
+
+ if (workItem)
+ {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "ClassQueueResourceExhaustionEventWorker: DO (%p), Queueing permanent resource exhaustion event work item.\n",
+ DeviceObject));
+
+ ClassAcquireRemoveLock(DeviceObject, (PIRP)(workItem));
+
+ //
+ // Queue a work item to write the threshold notification to the
+ // system event log.
+ //
+ IoQueueWorkItem(workItem, ClassLogResourceExhaustionEvent, DelayedWorkQueue, workItem);
+ }
+ else
+ {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "ClassQueueResourceExhaustionEventWorker: DO (%p), Failed to allocate memory for the work item.\n",
+ DeviceObject));
+ }
+ }
+}
+
+_Function_class_(IO_WORKITEM_ROUTINE)
+_IRQL_requires_(PASSIVE_LEVEL)
+_IRQL_requires_same_
+VOID
+ClassLogCapacityChangedProcess(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_opt_ PVOID Context
+ )
+/*
+ Routine Description:
+
+ This function logs a capacity changed event to the system event log.
+
+Arguments:
+ DeviceObject: The FDO that represents the device that reported the capacity change.
+ Context: A pointer to the IO_WORKITEM in which this function is running.
+
+--*/
+{
+ NTSTATUS status;
+ PIO_WORKITEM workItem = (PIO_WORKITEM)Context;
+
+ status = ClasspLogSystemEventWithDeviceNumber(DeviceObject, IO_WARNING_DISK_CAPACITY_CHANGED);
+
+ if (NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "ClassLogCapacityChangedEvent: DO (%p), Capacity changed logged.\n",
+ DeviceObject));
+ } else {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "ClassLogCapacityChangedEvent: DO (%p), Failed to allocate memory for error log entry.\n",
+ DeviceObject));
+ }
+
+ //
+ // Get disk capacity and notify upper layer if capacity is changed.
+ //
+ status = ClassReadDriveCapacity(DeviceObject);
+
+ if (!NT_SUCCESS(status)) {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "ClassLogCapacityChangedEvent: DO (%p), ClassReadDriveCapacity returned %!STATUS!.\n",
+ DeviceObject,
+ status));
+ }
+
+ ClassReleaseRemoveLock(DeviceObject, (PIRP)workItem);
+
+ if (workItem != NULL) {
+ IoFreeWorkItem(workItem);
+ }
+}
+
+
+VOID
+ClassQueueCapacityChangedEventWorker(
+ _In_ PDEVICE_OBJECT DeviceObject
+ )
+/*
+Routine Description:
+
+ This function queues a delayed work item that will eventually log a
+ disk capacity changed event to the system event log.
+
+Arguments:
+ DeviceObject: The FDO that represents the device that reported the capacity change.
+
+--*/
+{
+ PCOMMON_DEVICE_EXTENSION commonExtension = (PCOMMON_DEVICE_EXTENSION)(DeviceObject->DeviceExtension);
+ PIO_WORKITEM workItem = NULL;
+
+ if (commonExtension->IsFdo)
+ {
+ workItem = IoAllocateWorkItem(DeviceObject);
+
+ if (workItem)
+ {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "ClassQueueCapacityChangedEventWorker: DO (%p), Queueing capacity changed event work item.\n",
+ DeviceObject));
+
+ ClassAcquireRemoveLock(DeviceObject, (PIRP)(workItem));
+
+ //
+ // Queue a work item to write the threshold notification to the
+ // system event log.
+ //
+ IoQueueWorkItem(workItem, ClassLogCapacityChangedProcess, DelayedWorkQueue, workItem);
+ }
+ else
+ {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "ClassQueueCapacityChangedEventWorker: DO (%p), Failed to allocate memory for the work item.\n",
+ DeviceObject));
+ }
+ }
+}
+
+_Function_class_(IO_WORKITEM_ROUTINE)
+_IRQL_requires_(PASSIVE_LEVEL)
+_IRQL_requires_same_
+VOID
+ClassLogProvisioningTypeChangedEvent(
+ PDEVICE_OBJECT DeviceObject,
+ PVOID Context
+ )
+/*
+ Routine Description:
+
+ This function logs a provisioning type changed event to the system event log.
+
+Arguments:
+ DeviceObject: The FDO that represents the device that reported the provisioning type change.
+ Context: A pointer to the IO_WORKITEM in which this function is running.
+
+--*/
+{
+ PIO_WORKITEM workItem = (PIO_WORKITEM)Context;
+
+ if (NT_SUCCESS(ClasspLogSystemEventWithDeviceNumber(DeviceObject, IO_WARNING_DISK_PROVISIONING_TYPE_CHANGED))) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "ClassLogProvisioningTypeChangedEvent: DO (%p), LB Provisioning Type changed logged.\n",
+ DeviceObject));
+ } else {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "ClassLogProvisioningTypeChangedEvent: DO (%p), Failed to allocate memory for error log entry.\n",
+ DeviceObject));
+ }
+
+ ClassReleaseRemoveLock(DeviceObject, (PIRP)workItem);
+
+ IoFreeWorkItem(workItem);
+}
+
+
+VOID
+ClassQueueProvisioningTypeChangedEventWorker(
+ _In_ PDEVICE_OBJECT DeviceObject
+ )
+/*
+Routine Description:
+
+ This function queues a delayed work item that will eventually log a
+ provisioning type changed event to the system event log.
+
+Arguments:
+ DeviceObject: The FDO that represents the device that reported the provisioning type change.
+
+--*/
+{
+ PCOMMON_DEVICE_EXTENSION commonExtension = (PCOMMON_DEVICE_EXTENSION)(DeviceObject->DeviceExtension);
+ PIO_WORKITEM workItem = NULL;
+
+ if (commonExtension->IsFdo)
+ {
+ workItem = IoAllocateWorkItem(DeviceObject);
+
+ if (workItem)
+ {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "ClassQueueProvisioningTypeChangedEventWorker: DO (%p), Queueing LB provisioning type changed event work item.\n",
+ DeviceObject));
+
+ ClassAcquireRemoveLock(DeviceObject, (PIRP)(workItem));
+
+ //
+ // Queue a work item to write the threshold notification to the
+ // system event log.
+ //
+ IoQueueWorkItem(workItem, ClassLogProvisioningTypeChangedEvent, DelayedWorkQueue, workItem);
+ }
+ else
+ {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "ClassQueueProvisioningTypeChangedEventWorker: DO (%p), Failed to allocate memory for the work item.\n",
+ DeviceObject));
+ }
+ }
+}
+
+_Function_class_(IO_WORKITEM_ROUTINE)
+_IRQL_requires_(PASSIVE_LEVEL)
+_IRQL_requires_same_
+VOID
+ClasspLogIOEventWithContext(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_opt_ PVOID Context
+ )
+/*
+ Routine Description:
+
+ This function logs an event to the system event log with dumpdata containing opcode and
+ sense data.
+
+Arguments:
+ DeviceObject: The FDO that represents the device that retried the IO.
+ Context: A pointer to the OPCODE_SENSE_DATA_IO_LOG_MESSAGE_CONTEXT that has data to be logged as part of the message.
+
+--*/
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
+ POPCODE_SENSE_DATA_IO_LOG_MESSAGE_CONTEXT_HEADER ioLogMessageContextHeader = (POPCODE_SENSE_DATA_IO_LOG_MESSAGE_CONTEXT_HEADER)Context;
+ PIO_WORKITEM workItem;
+ PIO_ERROR_LOG_PACKET errorLogEntry = NULL;
+ ULONG logEntrySize;
+ PWCHAR stringIndex = NULL;
+ LONG stringSize = 0;
+ ULONG senseBufferSize;
+ ULONG stringsBufferLength = 0;
+ ULONG pdoNameLength = 0;
+
+ NT_ASSERT(ioLogMessageContextHeader != NULL);
+ _Analysis_assume_(ioLogMessageContextHeader != NULL);
+
+ switch (ioLogMessageContextHeader->ErrorCode) {
+
+ case IO_ERROR_IO_HARDWARE_ERROR:
+ case IO_WARNING_IO_OPERATION_RETRIED: {
+
+ //
+ // We need to allocate enough space for 3 insertion strings:
+ // 1. A ULONGLONG in Hex representing the LBA which means a max of 16 digits,
+ // plus two for "0x" plus one for the NULL character.
+ // 2. A ULONG representing the disk number in decimal, which means
+ // a max of 10 digits, plus one for the NULL character.
+ // 3. The PDO name, so that if the disk number is hidden from the
+ // user for some reason, there is still a way to associate the
+ // event with the correct device.
+ //
+ stringsBufferLength = (19 + 11) * sizeof(WCHAR);
+
+ //
+ // Query for the size of the PDO name.
+ //
+ status = IoGetDeviceProperty(fdoExtension->LowerPdo,
+ DevicePropertyPhysicalDeviceObjectName,
+ 0,
+ NULL,
+ &pdoNameLength);
+
+ if (status == STATUS_BUFFER_TOO_SMALL && pdoNameLength > 0) {
+ stringsBufferLength += pdoNameLength;
+ } else {
+ pdoNameLength = 0;
+ }
+
+ break;
+ }
+
+ }
+
+ workItem = ioLogMessageContextHeader->WorkItem;
+
+ //
+ // DumpData[0] which is of ULONG size and will contain opcode|srbstatus|scsistatus.
+ // Then we will have sensebuffer, hence
+ // DumpDataSize = senseBufferSize + sizeof(ULONG)
+ // and DumpDataSize must be multiple of sizeof(ULONG)
+ // which means senseBufferSize needs to ULONG aligned
+ // Please note we will use original buffersize for padding later
+ //
+ senseBufferSize = ALIGN_UP_BY(ioLogMessageContextHeader->SenseDataSize, sizeof(ULONG));
+
+ logEntrySize = FIELD_OFFSET( IO_ERROR_LOG_PACKET, DumpData ) + sizeof(ULONG) + senseBufferSize;
+
+ //
+ // We need to make sure the string offset is WCHAR-aligned (the insertion strings
+ // come after the sense buffer in the dump data, if any).
+ // But we don't need to do anything special for it,
+ // since FIELD_OFFSET( IO_ERROR_LOG_PACKET, DumpData) is currently ULONG aligned
+ // and SenseBufferSize is also ULONG aligned. This means buffer that precedes the insertion string is ULONG aligned
+ // note stringoffset = FIELD_OFFSET( IO_ERROR_LOG_PACKET, DumpData ) + DumpDataSize
+ // This leads us to fact that stringoffset will always be ULONG aligned and effectively WCHAR aligned
+ //
+
+ //
+ // We need to allocate enough space for the insertion strings provided in the passed in Context
+ // as well as the opcode and the sense data, while making sure we cap at max error log size.
+ // The log packet is followed by the opcode, then the sense data, and then the
+ // insertion strings.
+ //
+ logEntrySize = logEntrySize + stringsBufferLength;
+
+ if (logEntrySize > ERROR_LOG_MAXIMUM_SIZE) {
+ if (senseBufferSize) {
+ if (logEntrySize - ERROR_LOG_MAXIMUM_SIZE < senseBufferSize) {
+ //
+ // In below steps, senseBufferSize will become same or less than as ioLogMessageContextHeader->SenseDataSize
+ // it can't be more than that.
+ //
+ senseBufferSize -= logEntrySize - ERROR_LOG_MAXIMUM_SIZE;
+
+ //
+ // Decrease the sensebuffersize further, if needed, to keep senseBufferSize ULONG aligned
+ //
+ senseBufferSize = ALIGN_DOWN_BY(senseBufferSize, sizeof(ULONG));
+
+ } else {
+ senseBufferSize = 0;
+ }
+ }
+ logEntrySize = ERROR_LOG_MAXIMUM_SIZE;
+ }
+
+ errorLogEntry = (PIO_ERROR_LOG_PACKET)IoAllocateErrorLogEntry(DeviceObject, (UCHAR)logEntrySize);
+
+ if (errorLogEntry) {
+
+ RtlZeroMemory(errorLogEntry, logEntrySize);
+ errorLogEntry->MajorFunctionCode = IRP_MJ_SCSI;
+ errorLogEntry->RetryCount = 1;
+ errorLogEntry->DumpDataSize = (USHORT)(sizeof(ULONG) + senseBufferSize);
+ errorLogEntry->StringOffset = (USHORT)(FIELD_OFFSET( IO_ERROR_LOG_PACKET, DumpData ) + errorLogEntry->DumpDataSize);
+ errorLogEntry->ErrorCode = ioLogMessageContextHeader->ErrorCode;
+ errorLogEntry->DumpData[0] = (((ULONG)(ioLogMessageContextHeader->OpCode)) << 24) |
+ (((ULONG)(ioLogMessageContextHeader->SrbStatus)) << 16) |
+ (((ULONG)(ioLogMessageContextHeader->ScsiStatus)) << 8);
+
+ //
+ // Copy sense data and do padding for sense data if needed, with '-'
+ //
+ if (senseBufferSize > ioLogMessageContextHeader->SenseDataSize) {
+ RtlCopyMemory(&errorLogEntry->DumpData[1], ioLogMessageContextHeader->SenseData, ioLogMessageContextHeader->SenseDataSize);
+ RtlFillMemory( (PCHAR)&errorLogEntry->DumpData[1] + ioLogMessageContextHeader->SenseDataSize , (senseBufferSize - ioLogMessageContextHeader->SenseDataSize) , '-' );
+ } else {
+ RtlCopyMemory(&errorLogEntry->DumpData[1], ioLogMessageContextHeader->SenseData, senseBufferSize);
+ }
+
+ stringIndex = (PWCHAR)((PCHAR)errorLogEntry->DumpData + errorLogEntry->DumpDataSize);
+ stringSize = logEntrySize - errorLogEntry->StringOffset;
+
+ //
+ // Add the strings
+ //
+ switch (ioLogMessageContextHeader->ErrorCode) {
+ case IO_ERROR_IO_HARDWARE_ERROR:
+ case IO_WARNING_IO_OPERATION_RETRIED: {
+
+ PIO_RETRIED_LOG_MESSAGE_CONTEXT ioLogMessageContext = (PIO_RETRIED_LOG_MESSAGE_CONTEXT)Context;
+
+ //
+ // The first is a "0x" plus ULONGLONG in hex representing the LBA plus the NULL character.
+ // The second is a ULONG representing the disk number plus the NULL character.
+ //
+ status = RtlStringCbPrintfW(stringIndex, stringSize, L"0x%I64x", ioLogMessageContext->Lba.QuadPart);
+ if (NT_SUCCESS(status)) {
+ errorLogEntry->NumberOfStrings++;
+
+ //
+ // Add the disk number to the insertion strings.
+ //
+ stringSize -= (ULONG)(wcslen(stringIndex) + 1) * sizeof(WCHAR);
+ stringIndex += (wcslen(stringIndex) + 1);
+
+ if (stringSize > 0) {
+
+ status = RtlStringCbPrintfW(stringIndex, stringSize, L"%d", ioLogMessageContext->DeviceNumber);
+
+ if (NT_SUCCESS(status)) {
+
+ errorLogEntry->NumberOfStrings++;
+
+ stringSize -= (ULONG)(wcslen(stringIndex) + 1) * sizeof(WCHAR);
+ stringIndex += (wcslen(stringIndex) + 1);
+
+ if (stringSize >= (LONG)pdoNameLength && pdoNameLength > 0) {
+ ULONG resultLength;
+
+ //
+ // Get the PDO name and place it in the insertion string buffer.
+ //
+ status = IoGetDeviceProperty(fdoExtension->LowerPdo,
+ DevicePropertyPhysicalDeviceObjectName,
+ pdoNameLength,
+ stringIndex,
+ &resultLength);
+
+ if (NT_SUCCESS(status) && resultLength > 0) {
+ errorLogEntry->NumberOfStrings++;
+ }
+ }
+ }
+ }
+ }
+
+ break;
+ }
+
+ }
+
+ //
+ // Write the error log packet to the system error logging thread.
+ // It will be freed automatically.
+ //
+ IoWriteErrorLogEntry(errorLogEntry);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "ClasspLogIORetriedEvent: DO (%p), Soft threshold notification logged.\n",
+ DeviceObject));
+ }
+
+ ClassReleaseRemoveLock(DeviceObject, (PIRP)workItem);
+
+ if (ioLogMessageContextHeader->SenseData) {
+ ExFreePool(ioLogMessageContextHeader->SenseData);
+ }
+ if (workItem) {
+ IoFreeWorkItem(workItem);
+ }
+ ExFreePool(ioLogMessageContextHeader);
+}
+
+
+VOID
+ClasspQueueLogIOEventWithContextWorker(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ ULONG SenseBufferSize,
+ _In_ PVOID SenseData,
+ _In_ UCHAR SrbStatus,
+ _In_ UCHAR ScsiStatus,
+ _In_ ULONG ErrorCode,
+ _In_ ULONG CdbLength,
+ _In_opt_ PCDB Cdb,
+ _In_opt_ PTRANSFER_PACKET Pkt
+ )
+/*
+Routine Description:
+
+ Helper function that queues a delayed work item that will eventually
+ log an event to the system event log corresponding to passed in ErrorCode.
+ The dumpdata is fixed to include the opcode and the sense information.
+ But the number of insertion strings varies based on the passed in ErrorCode.
+
+Arguments:
+ DeviceObject: The FDO that represents the device that was the target of the IO.
+ SesneBufferSize: Size of the SenseData buffer.
+ SenseData: Error information from the target (to be included in the dump data).
+ SrbStatus: Srb status returned by the miniport.
+ ScsiStatus: SCSI status associated with the request upon completion from lower layers.
+ ErrorCode: Numerical value of the error code.
+ CdbLength: Number of bytes of Cdb.
+ Cdb: Pointer to the CDB.
+ Pkt: The tranfer packet representing the IO of interest. This may be NULL.
+
+--*/
+{
+ PCOMMON_DEVICE_EXTENSION commonExtension = (PCOMMON_DEVICE_EXTENSION)(DeviceObject->DeviceExtension);
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
+ POPCODE_SENSE_DATA_IO_LOG_MESSAGE_CONTEXT_HEADER ioLogMessageContextHeader = NULL;
+ PVOID senseData = NULL;
+ PIO_WORKITEM workItem = NULL;
+ ULONG senseBufferSize = 0;
+ LARGE_INTEGER lba = {0};
+
+ if (!commonExtension->IsFdo) {
+ return;
+ }
+
+ if (!Cdb) {
+ return;
+ }
+
+ workItem = IoAllocateWorkItem(DeviceObject);
+ if (!workItem) {
+ goto __ClasspQueueLogIOEventWithContextWorker_ExitWithMessage;
+ }
+
+ if (SenseBufferSize) {
+ senseData = ExAllocatePoolWithTag(NonPagedPoolNx, SenseBufferSize, CLASSPNP_POOL_TAG_LOG_MESSAGE);
+ if (senseData) {
+ senseBufferSize = SenseBufferSize;
+ }
+ }
+
+ if (CdbLength == 16) {
+ REVERSE_BYTES_QUAD(&lba, Cdb->CDB16.LogicalBlock);
+ } else {
+ ((PFOUR_BYTE)&lba.LowPart)->Byte3 = Cdb->CDB10.LogicalBlockByte0;
+ ((PFOUR_BYTE)&lba.LowPart)->Byte2 = Cdb->CDB10.LogicalBlockByte1;
+ ((PFOUR_BYTE)&lba.LowPart)->Byte1 = Cdb->CDB10.LogicalBlockByte2;
+ ((PFOUR_BYTE)&lba.LowPart)->Byte0 = Cdb->CDB10.LogicalBlockByte3;
+ }
+
+ //
+ // Calculate the amount of buffer required for the insertion strings.
+ //
+ switch (ErrorCode) {
+ case IO_ERROR_IO_HARDWARE_ERROR:
+ case IO_WARNING_IO_OPERATION_RETRIED: {
+
+ PIO_RETRIED_LOG_MESSAGE_CONTEXT ioLogMessageContext = NULL;
+
+ ioLogMessageContext = ExAllocatePoolWithTag(NonPagedPoolNx, sizeof(IO_RETRIED_LOG_MESSAGE_CONTEXT), CLASSPNP_POOL_TAG_LOG_MESSAGE);
+ if (!ioLogMessageContext) {
+ goto __ClasspQueueLogIOEventWithContextWorker_ExitWithMessage;
+ }
+
+ ioLogMessageContext->Lba.QuadPart = lba.QuadPart;
+ ioLogMessageContext->DeviceNumber = fdoExtension->DeviceNumber;
+
+ ioLogMessageContextHeader = (POPCODE_SENSE_DATA_IO_LOG_MESSAGE_CONTEXT_HEADER)ioLogMessageContext;
+
+ break;
+ }
+
+ default: goto __ClasspQueueLogIOEventWithContextWorker_Exit;
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "ClasspQueueLogIOEventWithContextWorker: DO (%p), Pkt (%p), Queueing IO retried event log message work item.\n",
+ DeviceObject,
+ Pkt));
+
+ ioLogMessageContextHeader->WorkItem = workItem;
+ if (senseData) {
+ RtlCopyMemory(senseData, SenseData, SenseBufferSize);
+ }
+ ioLogMessageContextHeader->SenseData = senseData;
+ ioLogMessageContextHeader->SenseDataSize = senseBufferSize;
+ ioLogMessageContextHeader->SrbStatus = SrbStatus;
+ ioLogMessageContextHeader->ScsiStatus = ScsiStatus;
+ ioLogMessageContextHeader->OpCode = Cdb->CDB6GENERIC.OperationCode;
+ ioLogMessageContextHeader->Reserved = 0;
+ ioLogMessageContextHeader->ErrorCode = ErrorCode;
+
+ ClassAcquireRemoveLock(DeviceObject, (PIRP)(workItem));
+
+ //
+ // Queue a work item to write the system event log.
+ //
+ IoQueueWorkItem(workItem, ClasspLogIOEventWithContext, DelayedWorkQueue, ioLogMessageContextHeader);
+
+ return;
+
+__ClasspQueueLogIOEventWithContextWorker_ExitWithMessage:
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "ClasspQueueLogIOEventWithContextWorker: DO (%p), Failed to allocate memory for the log message.\n",
+ DeviceObject));
+
+__ClasspQueueLogIOEventWithContextWorker_Exit:
+ if (senseData) {
+ ExFreePool(senseData);
+ }
+ if (workItem) {
+ IoFreeWorkItem(workItem);
+ }
+ if (ioLogMessageContextHeader) {
+ ExFreePool(ioLogMessageContextHeader);
+ }
+}
+
+__inline
+BOOLEAN
+ValidPersistentReserveScope(
+ UCHAR Scope)
+{
+ switch (Scope) {
+ case RESERVATION_SCOPE_LU:
+ case RESERVATION_SCOPE_ELEMENT:
+
+ return TRUE;
+
+ default:
+
+ break;
+ }
+
+ return FALSE;
+}
+
+__inline
+ValidPersistentReserveType(
+ UCHAR Type)
+{
+ switch (Type) {
+ case RESERVATION_TYPE_WRITE_EXCLUSIVE:
+ case RESERVATION_TYPE_EXCLUSIVE:
+ case RESERVATION_TYPE_WRITE_EXCLUSIVE_REGISTRANTS:
+ case RESERVATION_TYPE_EXCLUSIVE_REGISTRANTS:
+
+ return TRUE;
+
+ default:
+
+ break;
+ }
+
+ return FALSE;
+}
+
+
+/*++
+
+ClasspPersistentReserve
+
+Routine Description:
+
+ Handles IOCTL_STORAGE_PERSISTENT_RESERVE_IN and IOCTL_STORAGE_PERSISTENT_RESERVE_OUT.
+
+Arguments:
+
+ DeviceObject - a pointer to the device object
+ Irp - a pointer to the I/O request packet
+ Srb - pointer to preallocated SCSI_REQUEST_BLOCK.
+
+Return Value:
+
+ Status Code
+
+--*/
+NTSTATUS
+ClasspPersistentReserve(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ )
+{
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ PCDB cdb = NULL;
+ PPERSISTENT_RESERVE_COMMAND prCommand = Irp->AssociatedIrp.SystemBuffer;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
+
+ NTSTATUS status;
+
+ ULONG dataBufLen;
+ ULONG controlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;
+
+ BOOLEAN writeToDevice;
+
+ //
+ // Check common input buffer parameters.
+ //
+
+ if (irpStack->Parameters.DeviceIoControl.InputBufferLength <
+ sizeof(PERSISTENT_RESERVE_COMMAND) ||
+ prCommand->Size < sizeof(PERSISTENT_RESERVE_COMMAND)) {
+
+ status = STATUS_INFO_LENGTH_MISMATCH;
+ Irp->IoStatus.Status = status;
+ Irp->IoStatus.Information = 0;
+
+ FREE_POOL(Srb);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ goto ClasspPersistentReserve_Exit;
+ }
+
+ //
+ // Check buffer alignment. Only an issue if another kernel mode component
+ // (not the I/O manager) allocates the buffer.
+ //
+
+ if ((ULONG_PTR)prCommand & fdoExtension->AdapterDescriptor->AlignmentMask) {
+
+ status = STATUS_INVALID_USER_BUFFER;
+ Irp->IoStatus.Status = status;
+ Irp->IoStatus.Information = 0;
+
+ FREE_POOL(Srb);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ goto ClasspPersistentReserve_Exit;
+ }
+
+ //
+ // Check additional parameters.
+ //
+
+ status = STATUS_SUCCESS;
+
+ SrbSetCdbLength(Srb, 10);
+ cdb = SrbGetCdb(Srb);
+
+ if (controlCode == IOCTL_STORAGE_PERSISTENT_RESERVE_IN) {
+
+ //
+ // Check output buffer for PR In.
+ //
+
+ if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
+ prCommand->PR_IN.AllocationLength) {
+
+ status = STATUS_INVALID_PARAMETER;
+ }
+
+ switch (prCommand->PR_IN.ServiceAction) {
+
+ case RESERVATION_ACTION_READ_KEYS:
+
+ if (prCommand->PR_IN.AllocationLength < sizeof(PRI_REGISTRATION_LIST)) {
+
+ status = STATUS_INVALID_PARAMETER;
+ }
+
+ break;
+
+ case RESERVATION_ACTION_READ_RESERVATIONS:
+
+ if (prCommand->PR_IN.AllocationLength < sizeof(PRI_RESERVATION_LIST)) {
+
+ status = STATUS_INVALID_PARAMETER;
+ }
+
+ break;
+
+ default:
+
+ status = STATUS_INVALID_PARAMETER;
+ break;
+ }
+
+ if (!NT_SUCCESS(status)) {
+
+ Irp->IoStatus.Status = status;
+ Irp->IoStatus.Information = 0;
+
+ FREE_POOL(Srb);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ goto ClasspPersistentReserve_Exit;
+ }
+
+ //
+ // Fill in the CDB.
+ //
+
+ cdb->PERSISTENT_RESERVE_IN.OperationCode = SCSIOP_PERSISTENT_RESERVE_IN;
+ cdb->PERSISTENT_RESERVE_IN.ServiceAction = prCommand->PR_IN.ServiceAction;
+
+ REVERSE_BYTES_SHORT(&(cdb->PERSISTENT_RESERVE_IN.AllocationLength),
+ &(prCommand->PR_IN.AllocationLength));
+
+ dataBufLen = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
+ writeToDevice = FALSE;
+
+
+ } else {
+
+ //
+ // Verify ServiceAction, Scope, and Type
+ //
+
+ switch (prCommand->PR_OUT.ServiceAction) {
+
+ case RESERVATION_ACTION_REGISTER:
+ case RESERVATION_ACTION_REGISTER_IGNORE_EXISTING:
+ case RESERVATION_ACTION_CLEAR:
+
+ // Scope and type ignored.
+
+ break;
+
+ case RESERVATION_ACTION_RESERVE:
+ case RESERVATION_ACTION_RELEASE:
+ case RESERVATION_ACTION_PREEMPT:
+ case RESERVATION_ACTION_PREEMPT_ABORT:
+
+ if (!ValidPersistentReserveScope(prCommand->PR_OUT.Scope) ||
+ !ValidPersistentReserveType(prCommand->PR_OUT.Type)) {
+
+ status = STATUS_INVALID_PARAMETER;
+
+ }
+
+ break;
+
+ default:
+
+ status = STATUS_INVALID_PARAMETER;
+
+ break;
+ }
+
+ //
+ // Check input buffer for PR Out.
+ // Caller must include the PR parameter list.
+ //
+
+ if (NT_SUCCESS(status)) {
+
+ if (irpStack->Parameters.DeviceIoControl.InputBufferLength <
+ (sizeof(PERSISTENT_RESERVE_COMMAND) +
+ sizeof(PRO_PARAMETER_LIST)) ||
+ prCommand->Size <
+ irpStack->Parameters.DeviceIoControl.InputBufferLength) {
+
+ status = STATUS_INVALID_PARAMETER;
+
+ }
+ }
+
+
+ if (!NT_SUCCESS(status)) {
+
+ Irp->IoStatus.Status = status;
+ Irp->IoStatus.Information = 0;
+
+ FREE_POOL(Srb);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ goto ClasspPersistentReserve_Exit;
+ }
+
+ //
+ // Fill in the CDB.
+ //
+
+ cdb->PERSISTENT_RESERVE_OUT.OperationCode = SCSIOP_PERSISTENT_RESERVE_OUT;
+ cdb->PERSISTENT_RESERVE_OUT.ServiceAction = prCommand->PR_OUT.ServiceAction;
+ cdb->PERSISTENT_RESERVE_OUT.Scope = prCommand->PR_OUT.Scope;
+ cdb->PERSISTENT_RESERVE_OUT.Type = prCommand->PR_OUT.Type;
+
+ cdb->PERSISTENT_RESERVE_OUT.ParameterListLength[1] = (UCHAR)sizeof(PRO_PARAMETER_LIST);
+
+ //
+ // Move the parameter list to the beginning of the data buffer (so it is aligned
+ // correctly and that the MDL describes it correctly).
+ //
+
+ RtlMoveMemory(prCommand,
+ prCommand->PR_OUT.ParameterList,
+ sizeof(PRO_PARAMETER_LIST));
+
+ dataBufLen = sizeof(PRO_PARAMETER_LIST);
+ writeToDevice = TRUE;
+ }
+
+ //
+ // Fill in the SRB
+ //
+
+ //
+ // Set timeout value.
+ //
+
+ SrbSetTimeOutValue(Srb, fdoExtension->TimeOutValue);
+
+ //
+ // Send as a tagged request.
+ //
+
+ SrbSetRequestAttribute(Srb, SRB_HEAD_OF_QUEUE_TAG_REQUEST);
+ SrbSetSrbFlags(Srb, SRB_FLAGS_NO_QUEUE_FREEZE | SRB_FLAGS_QUEUE_ACTION_ENABLE);
+
+ status = ClassSendSrbAsynchronous(DeviceObject,
+ Srb,
+ Irp,
+ prCommand,
+ dataBufLen,
+ writeToDevice);
+
+ClasspPersistentReserve_Exit:
+
+ return status;
+
+}
+
+/*++
+
+ClasspPriorityHint
+
+Routine Description:
+
+ Handles IOCTL_STORAGE_CHECK_PRIORITY_HINT_SUPPORT.
+
+Arguments:
+
+ DeviceObject - a pointer to the device object
+ Irp - a pointer to the I/O request packet
+
+Return Value:
+
+ Status Code
+
+--*/
+NTSTATUS
+ClasspPriorityHint(
+ PDEVICE_OBJECT DeviceObject,
+ PIRP Irp
+ )
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
+ PSTORAGE_PRIORITY_HINT_SUPPORT priSupport = Irp->AssociatedIrp.SystemBuffer;
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ NTSTATUS status = STATUS_SUCCESS;
+
+ Irp->IoStatus.Information = 0;
+
+ //
+ // Check whether this device supports idle priority.
+ //
+ if (!fdoData->IdlePrioritySupported) {
+ status = STATUS_NOT_SUPPORTED;
+ goto PriorityHintExit;
+ }
+
+ if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
+ sizeof(STORAGE_PRIORITY_HINT_SUPPORT)) {
+
+ status = STATUS_BUFFER_TOO_SMALL;
+ goto PriorityHintExit;
+ }
+
+ RtlZeroMemory(priSupport, sizeof(STORAGE_PRIORITY_HINT_SUPPORT));
+
+ status = ClassForwardIrpSynchronous(commonExtension, Irp);
+ if (!NT_SUCCESS(status)) {
+ //
+ // If I/O priority is not supported by lower drivers, just set the
+ // priorities supported by class driver.
+ //
+ TracePrint((TRACE_LEVEL_FATAL, TRACE_FLAG_IOCTL, "ClasspPriorityHint: I/O priority not supported by port driver.\n"));
+ priSupport->SupportFlags = 0;
+ status = STATUS_SUCCESS;
+ }
+
+ TracePrint((TRACE_LEVEL_FATAL, TRACE_FLAG_IOCTL, "ClasspPriorityHint: I/O priorities supported by port driver: %X\n", priSupport->SupportFlags));
+
+ priSupport->SupportFlags |= (1 << IoPriorityVeryLow) |
+ (1 << IoPriorityLow) |
+ (1 << IoPriorityNormal) ;
+
+ TracePrint((TRACE_LEVEL_FATAL, TRACE_FLAG_IOCTL, "ClasspPriorityHint: I/O priorities supported: %X\n", priSupport->SupportFlags));
+ Irp->IoStatus.Information = sizeof(STORAGE_PRIORITY_HINT_SUPPORT);
+
+PriorityHintExit:
+
+ Irp->IoStatus.Status = status;
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ return status;
+}
+
+/*++
+
+ClasspConvertToScsiRequestBlock
+
+Routine Description:
+
+ Convert an extended SRB to a SCSI_REQUEST_BLOCK. This function handles only
+ a single SRB and will not converted SRBs that are linked.
+
+Arguments:
+
+ Srb - a pointer to a SCSI_REQUEST_BLOCK
+ SrbEx - a pointer to an extended SRB
+
+Return Value:
+
+ None
+
+--*/
+VOID
+ClasspConvertToScsiRequestBlock(
+ _Out_ PSCSI_REQUEST_BLOCK Srb,
+ _In_ PSTORAGE_REQUEST_BLOCK SrbEx
+ )
+{
+ PSTOR_ADDR_BTL8 storAddrBtl8;
+ ULONG i;
+ BOOLEAN foundEntry = FALSE;
+ PSRBEX_DATA srbExData;
+
+ if ((Srb == NULL) || (SrbEx == NULL)) {
+ return;
+ }
+
+ RtlZeroMemory(Srb, sizeof(SCSI_REQUEST_BLOCK));
+
+ Srb->Length = sizeof(SCSI_REQUEST_BLOCK);
+ Srb->Function = (UCHAR)SrbEx->SrbFunction;
+ Srb->SrbStatus = SrbEx->SrbStatus;
+ Srb->QueueTag = (UCHAR)SrbEx->RequestTag;
+ Srb->QueueAction = (UCHAR)SrbEx->RequestAttribute;
+ Srb->SrbFlags = SrbEx->SrbFlags;
+ Srb->DataTransferLength = SrbEx->DataTransferLength;
+ Srb->TimeOutValue = SrbEx->TimeOutValue;
+ Srb->DataBuffer = SrbEx->DataBuffer;
+ Srb->OriginalRequest = SrbEx->OriginalRequest;
+ Srb->SrbExtension = SrbEx->MiniportContext;
+ Srb->InternalStatus = SrbEx->SystemStatus;
+
+ //
+ // Handle address fields
+ //
+ if (SrbEx->AddressOffset >= sizeof(STORAGE_REQUEST_BLOCK)) {
+ storAddrBtl8 = (PSTOR_ADDR_BTL8)((PCHAR)SrbEx + SrbEx->AddressOffset);
+
+ if (storAddrBtl8->Type == STOR_ADDRESS_TYPE_BTL8) {
+ Srb->PathId = storAddrBtl8->Path;
+ Srb->TargetId = storAddrBtl8->Target;
+ Srb->Lun = storAddrBtl8->Lun;
+ } else {
+ // Catch unsupported address types
+ NT_ASSERT(FALSE);
+ }
+ }
+
+ //
+ // Handle SRB function specific fields
+ //
+ if (SrbEx->NumSrbExData > 0) {
+
+ for (i = 0; i < SrbEx->NumSrbExData; i++) {
+
+ if ((SrbEx->SrbExDataOffset[i] == 0) ||
+ (SrbEx->SrbExDataOffset[i] < sizeof(STORAGE_REQUEST_BLOCK))) {
+ // Catch invalid offsets
+ NT_ASSERT(FALSE);
+ continue;
+ }
+
+ srbExData = (PSRBEX_DATA)((PCHAR)SrbEx + SrbEx->SrbExDataOffset[i]);
+
+ switch (SrbEx->SrbFunction) {
+
+ case SRB_FUNCTION_EXECUTE_SCSI:
+
+ switch (srbExData->Type) {
+
+ case SrbExDataTypeScsiCdb16:
+ Srb->ScsiStatus = ((PSRBEX_DATA_SCSI_CDB16)srbExData)->ScsiStatus;
+ Srb->CdbLength = ((PSRBEX_DATA_SCSI_CDB16)srbExData)->CdbLength;
+ Srb->SenseInfoBufferLength = ((PSRBEX_DATA_SCSI_CDB16)srbExData)->SenseInfoBufferLength;
+ Srb->SenseInfoBuffer = ((PSRBEX_DATA_SCSI_CDB16)srbExData)->SenseInfoBuffer;
+ RtlCopyMemory(Srb->Cdb, ((PSRBEX_DATA_SCSI_CDB16)srbExData)->Cdb, sizeof(Srb->Cdb));
+ foundEntry = TRUE;
+ break;
+
+ case SrbExDataTypeScsiCdb32:
+ Srb->ScsiStatus = ((PSRBEX_DATA_SCSI_CDB32)srbExData)->ScsiStatus;
+ Srb->CdbLength = ((PSRBEX_DATA_SCSI_CDB32)srbExData)->CdbLength;
+ Srb->SenseInfoBufferLength = ((PSRBEX_DATA_SCSI_CDB32)srbExData)->SenseInfoBufferLength;
+ Srb->SenseInfoBuffer = ((PSRBEX_DATA_SCSI_CDB32)srbExData)->SenseInfoBuffer;
+
+ // Copy only the first 16 bytes
+ RtlCopyMemory(Srb->Cdb, ((PSRBEX_DATA_SCSI_CDB32)srbExData)->Cdb, sizeof(Srb->Cdb));
+ foundEntry = TRUE;
+ break;
+
+ case SrbExDataTypeScsiCdbVar:
+ Srb->ScsiStatus = ((PSRBEX_DATA_SCSI_CDB_VAR)srbExData)->ScsiStatus;
+ Srb->CdbLength = (UCHAR)((PSRBEX_DATA_SCSI_CDB_VAR)srbExData)->CdbLength;
+ Srb->SenseInfoBufferLength = ((PSRBEX_DATA_SCSI_CDB_VAR)srbExData)->SenseInfoBufferLength;
+ Srb->SenseInfoBuffer = ((PSRBEX_DATA_SCSI_CDB_VAR)srbExData)->SenseInfoBuffer;
+
+ // Copy only the first 16 bytes
+ RtlCopyMemory(Srb->Cdb, ((PSRBEX_DATA_SCSI_CDB_VAR)srbExData)->Cdb, sizeof(Srb->Cdb));
+ foundEntry = TRUE;
+ break;
+
+ default:
+ break;
+
+ }
+ break;
+
+ case SRB_FUNCTION_WMI:
+
+ if (srbExData->Type == SrbExDataTypeWmi) {
+ ((PSCSI_WMI_REQUEST_BLOCK)Srb)->WMISubFunction = ((PSRBEX_DATA_WMI)srbExData)->WMISubFunction;
+ ((PSCSI_WMI_REQUEST_BLOCK)Srb)->WMIFlags = ((PSRBEX_DATA_WMI)srbExData)->WMIFlags;
+ ((PSCSI_WMI_REQUEST_BLOCK)Srb)->DataPath = ((PSRBEX_DATA_WMI)srbExData)->DataPath;
+ foundEntry = TRUE;
+ }
+ break;
+
+ case SRB_FUNCTION_PNP:
+
+ if (srbExData->Type == SrbExDataTypePnP) {
+ ((PSCSI_PNP_REQUEST_BLOCK)Srb)->PnPAction = ((PSRBEX_DATA_PNP)srbExData)->PnPAction;
+ ((PSCSI_PNP_REQUEST_BLOCK)Srb)->PnPSubFunction = ((PSRBEX_DATA_PNP)srbExData)->PnPSubFunction;
+ ((PSCSI_PNP_REQUEST_BLOCK)Srb)->SrbPnPFlags = ((PSRBEX_DATA_PNP)srbExData)->SrbPnPFlags;
+ foundEntry = TRUE;
+ }
+ break;
+
+ case SRB_FUNCTION_POWER:
+
+ if (srbExData->Type == SrbExDataTypePower) {
+ ((PSCSI_POWER_REQUEST_BLOCK)Srb)->DevicePowerState = ((PSRBEX_DATA_POWER)srbExData)->DevicePowerState;
+ ((PSCSI_POWER_REQUEST_BLOCK)Srb)->PowerAction = ((PSRBEX_DATA_POWER)srbExData)->PowerAction;
+ ((PSCSI_POWER_REQUEST_BLOCK)Srb)->SrbPowerFlags = ((PSRBEX_DATA_POWER)srbExData)->SrbPowerFlags;
+ foundEntry = TRUE;
+ }
+ break;
+
+ default:
+ break;
+
+ }
+
+ //
+ // Quit on first match
+ //
+ if (foundEntry) {
+ break;
+ }
+ }
+ }
+
+ return;
+}
+
+
+
+_IRQL_requires_max_(PASSIVE_LEVEL)
+NTSTATUS
+ClasspGetMaximumTokenListIdentifier(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_z_ PWSTR RegistryPath,
+ _Out_ PULONG MaximumListIdentifier
+ )
+
+/*++
+
+Routine Description:
+
+ This routine returns the maximum ListIdentifier (to be used when building TokenOperation
+ requests) by querying the value MaximumListIdentifier under the key 'RegistryPath'.
+
+Arguments:
+
+ DeviceObject - The device handling the request.
+ RegistryPath - The absolute registry path under which MaximumListIdentifier resides.
+ MaximumListIdentifier - Returns the value being queried.
+
+Return Value:
+
+ STATUS_SUCCESS or appropriate error status returned by Registry API.
+
+--*/
+
+{
+ RTL_QUERY_REGISTRY_TABLE queryTable[2];
+ ULONG value = 0;
+ NTSTATUS status;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "ClasspGetMaximumTokenListIdentifier (%p): Entering function.\n",
+ DeviceObject));
+
+ //
+ // Zero the table entries.
+ //
+ RtlZeroMemory(queryTable, sizeof(queryTable));
+
+ //
+ // The query table has two entries. One for the MaximumListIdentifier and
+ // the second which is the 'NULL' terminator.
+ //
+ // Indicate that there is NO call-back routine.
+ //
+ queryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT;
+
+ //
+ // The value to query.
+ //
+ queryTable[0].Name = REG_MAX_LIST_IDENTIFIER_VALUE;
+
+ //
+ // Where to put the value, the type of the value, default value and length.
+ //
+ queryTable[0].EntryContext = &value;
+ queryTable[0].DefaultType = REG_DWORD;
+ queryTable[0].DefaultData = &value;
+ queryTable[0].DefaultLength = sizeof(value);
+
+ //
+ // Try to get the maximum listIdentifier.
+ //
+ status = RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE,
+ RegistryPath,
+ queryTable,
+ NULL,
+ NULL);
+
+ if (NT_SUCCESS(status)) {
+ *MaximumListIdentifier = value;
+ } else {
+ *MaximumListIdentifier = 0;
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "ClasspGetMaximumTokenListIdentifier (%p): Exiting function with status %x (maxListId %u).\n",
+ DeviceObject,
+ status,
+ *MaximumListIdentifier));
+
+ return status;
+}
+
+
+_IRQL_requires_max_(APC_LEVEL)
+_IRQL_requires_min_(PASSIVE_LEVEL)
+_IRQL_requires_same_
+NTSTATUS
+ClasspDeviceCopyOffloadProperty(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _Inout_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine returns the copy offload parameters associated with the device.
+
+ 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 - The SRB associated with the request
+
+Return Value:
+
+ NTSTATUS code
+
+--*/
+
+{
+ NTSTATUS status;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
+ PSTORAGE_PROPERTY_QUERY query;
+ PIO_STACK_LOCATION irpStack;
+ ULONG length;
+ ULONG information;
+ PDEVICE_COPY_OFFLOAD_DESCRIPTOR copyOffloadDescr = (PDEVICE_COPY_OFFLOAD_DESCRIPTOR)Irp->AssociatedIrp.SystemBuffer;
+
+ UNREFERENCED_PARAMETER(Srb);
+
+ PAGED_CODE();
+
+ fdoExtension = DeviceObject->DeviceExtension;
+ query = (PSTORAGE_PROPERTY_QUERY)Irp->AssociatedIrp.SystemBuffer;
+ irpStack = IoGetCurrentIrpStackLocation(Irp);
+ length = 0;
+ information = 0;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspDeviceCopyOffloadProperty (%p): Entering function.\n",
+ DeviceObject));
+
+ //
+ // Check proper query type.
+ //
+ if (query->QueryType == PropertyExistsQuery) {
+
+ //
+ // In order to maintain consistency with the how the rest of the properties
+ // are handled, we shall always return success for PropertyExistsQuery.
+ //
+ status = STATUS_SUCCESS;
+ goto __ClasspDeviceCopyOffloadProperty_Exit;
+
+ } else if (query->QueryType != PropertyStandardQuery) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspDeviceCopyOffloadProperty (%p): Unsupported query type %x for Copy Offload property.\n",
+ DeviceObject,
+ query->QueryType));
+
+ status = STATUS_NOT_SUPPORTED;
+ goto __ClasspDeviceCopyOffloadProperty_Exit;
+ }
+
+ //
+ // Request validation.
+ // Note that InputBufferLength and IsFdo have been validated beforing entering this routine.
+ //
+
+ if (KeGetCurrentIrql() >= DISPATCH_LEVEL) {
+
+ NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspDeviceCopyOffloadProperty (%p): Query property for Copy Offload called at incorrect IRQL.\n",
+ DeviceObject));
+
+ status = STATUS_INVALID_LEVEL;
+ goto __ClasspDeviceCopyOffloadProperty_Exit;
+ }
+
+ length = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
+
+ if (length < sizeof(DEVICE_COPY_OFFLOAD_DESCRIPTOR)) {
+
+ if (length >= sizeof(STORAGE_DESCRIPTOR_HEADER)) {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_IOCTL,
+ "ClasspDeviceCopyOffloadProperty (%p): Length %u specified for Copy Offload property enough only for header.\n",
+ DeviceObject,
+ length));
+
+ information = sizeof(STORAGE_DESCRIPTOR_HEADER);
+ copyOffloadDescr->Version = sizeof(DEVICE_COPY_OFFLOAD_DESCRIPTOR);
+ copyOffloadDescr->Size = sizeof(DEVICE_COPY_OFFLOAD_DESCRIPTOR);
+
+ status = STATUS_SUCCESS;
+ goto __ClasspDeviceCopyOffloadProperty_Exit;
+ }
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspDeviceCopyOffloadProperty (%p): Incorrect length %u specified for Copy Offload property.\n",
+ DeviceObject,
+ length));
+
+ status = STATUS_BUFFER_TOO_SMALL;
+ goto __ClasspDeviceCopyOffloadProperty_Exit;
+ }
+
+ if (!fdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspDeviceCopyOffloadProperty (%p): Command not supported on this device.\n",
+ DeviceObject));
+
+ status = STATUS_DEVICE_FEATURE_NOT_SUPPORTED;
+ goto __ClasspDeviceCopyOffloadProperty_Exit;
+ }
+
+ if (!NT_SUCCESS(fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus)) {
+
+ status = fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus;
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspDeviceCopyOffloadProperty (%p): VPD retrieval had failed with %x.\n",
+ DeviceObject,
+ status));
+
+ goto __ClasspDeviceCopyOffloadProperty_Exit;
+ }
+
+ //
+ // Fill in the output buffer. All data is copied from the FDO extension where we
+ // cached Block Limits and Block Device Token Limits info when the device was first initialized.
+ //
+ RtlZeroMemory(copyOffloadDescr, length);
+ copyOffloadDescr->Version = 1;
+ copyOffloadDescr->Size = sizeof(DEVICE_COPY_OFFLOAD_DESCRIPTOR);
+ copyOffloadDescr->MaximumTokenLifetime = fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumInactivityTimer;
+ copyOffloadDescr->DefaultTokenLifetime = fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.DefaultInactivityTimer;
+ copyOffloadDescr->MaximumTransferSize = fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumTokenTransferSize;
+ copyOffloadDescr->OptimalTransferCount = fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount;
+ copyOffloadDescr->MaximumDataDescriptors = fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumRangeDescriptors;
+
+ if (NT_SUCCESS(fdoExtension->FunctionSupportInfo->BlockLimitsData.CommandStatus)) {
+
+ copyOffloadDescr->MaximumTransferLengthPerDescriptor = fdoExtension->FunctionSupportInfo->BlockLimitsData.MaximumTransferLength;
+ copyOffloadDescr->OptimalTransferLengthPerDescriptor = fdoExtension->FunctionSupportInfo->BlockLimitsData.OptimalTransferLength;
+ copyOffloadDescr->OptimalTransferLengthGranularity = fdoExtension->FunctionSupportInfo->BlockLimitsData.OptimalTransferLengthGranularity;
+ }
+
+ information = sizeof(DEVICE_COPY_OFFLOAD_DESCRIPTOR);
+ status = STATUS_SUCCESS;
+
+__ClasspDeviceCopyOffloadProperty_Exit:
+
+ //
+ // Set the size and status in IRP
+ //
+ Irp->IoStatus.Information = information;
+ Irp->IoStatus.Status = status;
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspDeviceCopyOffloadProperty (%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
+ClasspValidateOffloadSupported(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine validates if this device supports offload requests.
+
+ 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
+
+Return Value:
+
+ NTSTATUS code
+
+--*/
+
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt;
+ NTSTATUS status;
+
+ PAGED_CODE();
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspValidateOffloadSupported (%p): Entering function. Irp %p\n",
+ DeviceObject,
+ Irp));
+
+ fdoExt = DeviceObject->DeviceExtension;
+ status = STATUS_SUCCESS;
+
+ //
+ // For now this command is only supported by disk devices
+ //
+ if ((DeviceObject->DeviceType == FILE_DEVICE_DISK) &&
+ (!TEST_FLAG(DeviceObject->Characteristics, FILE_FLOPPY_DISKETTE))) {
+
+ if (!fdoExt->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspValidateOffloadSupported (%p): Command not supported on this disk device.\n",
+ DeviceObject));
+
+ status = STATUS_DEVICE_FEATURE_NOT_SUPPORTED;
+ goto __ClasspValidateOffloadSupported_Exit;
+ }
+
+ if (!NT_SUCCESS(fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus)) {
+
+ status = fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus;
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspValidateOffloadSupported (%p): VPD retrieval failed with %x.\n",
+ DeviceObject,
+ status));
+
+ goto __ClasspValidateOffloadSupported_Exit;
+ }
+ } else {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_IOCTL,
+ "ClasspValidateOffloadSupported (%p): Suported only on Disk devices.\n",
+ DeviceObject));
+
+ status = STATUS_DEVICE_FEATURE_NOT_SUPPORTED;
+ goto __ClasspValidateOffloadSupported_Exit;
+ }
+
+__ClasspValidateOffloadSupported_Exit:
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspValidateOffloadSupported (%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
+ClasspValidateOffloadInputParameters(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine does some basic validation of the input parameters of the offload request.
+
+ 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
+
+Return Value:
+
+ NTSTATUS code
+
+--*/
+
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
+ PIO_STACK_LOCATION irpStack;
+ PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes;
+ PDEVICE_DATA_SET_RANGE dataSetRanges;
+ ULONG dataSetRangesCount;
+ ULONG i;
+ NTSTATUS status;
+
+ PAGED_CODE();
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspValidateOffloadInputParameters (%p): Entering function Irp %p.\n",
+ DeviceObject,
+ Irp));
+
+ fdoExtension = DeviceObject->DeviceExtension;
+ irpStack = IoGetCurrentIrpStackLocation (Irp);
+ dsmAttributes = Irp->AssociatedIrp.SystemBuffer;
+ status = STATUS_SUCCESS;
+
+ if (!dsmAttributes) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspValidateOffloadInputParameters (%p): NULL DsmAttributes passed in.\n",
+ DeviceObject));
+
+ status = STATUS_INVALID_PARAMETER;
+ goto __ClasspValidateOffloadInputParameters_Exit;
+ }
+
+ if ((irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES)) ||
+ (irpStack->Parameters.DeviceIoControl.InputBufferLength <
+ (sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES) + dsmAttributes->ParameterBlockLength + dsmAttributes->DataSetRangesLength))) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspValidateOffloadInputParameters (%p): Input buffer size (%u) too small.\n",
+ DeviceObject,
+ irpStack->Parameters.DeviceIoControl.InputBufferLength));
+
+ status = STATUS_INVALID_PARAMETER;
+ goto __ClasspValidateOffloadInputParameters_Exit;
+ }
+
+ if ((dsmAttributes->DataSetRangesOffset == 0) ||
+ (dsmAttributes->DataSetRangesLength == 0)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspValidateOffloadInputParameters (%p): Incorrect DataSetRanges [offset %u, length %u].\n",
+ DeviceObject,
+ dsmAttributes->DataSetRangesOffset,
+ dsmAttributes->DataSetRangesLength));
+
+ status = STATUS_INVALID_PARAMETER;
+ goto __ClasspValidateOffloadInputParameters_Exit;
+ }
+
+ dataSetRanges = Add2Ptr(dsmAttributes, dsmAttributes->DataSetRangesOffset);
+ dataSetRangesCount = dsmAttributes->DataSetRangesLength / sizeof(DEVICE_DATA_SET_RANGE);
+
+ if (dataSetRangesCount == 0) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspValidateOffloadInputParameters (%p): DataSetRanges specifies no extents.\n",
+ DeviceObject));
+
+ status = STATUS_INVALID_PARAMETER;
+ goto __ClasspValidateOffloadInputParameters_Exit;
+ }
+
+ //
+ // Some third party disk class drivers do not query the geometry at initialization time,
+ // so this information may not be available at this time. If that is the case, we'll
+ // first query that information before proceeding with the rest of our validations.
+ //
+ if (fdoExtension->DiskGeometry.BytesPerSector == 0) {
+ status = ClassReadDriveCapacity(fdoExtension->DeviceObject);
+ if ((!NT_SUCCESS(status)) || (fdoExtension->DiskGeometry.BytesPerSector == 0)) {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspValidateOffloadInputParameters (%p): Couldn't retrieve disk geometry, status: %x, bytes/sector: %u.\n",
+ DeviceObject,
+ status,
+ fdoExtension->DiskGeometry.BytesPerSector));
+
+ status = STATUS_INVALID_PARAMETER;
+ goto __ClasspValidateOffloadInputParameters_Exit;
+ }
+ }
+
+ //
+ // Data must be aligned to sector boundary and
+ // LengthInBytes must be > 0 for it to be a valid LBA entry
+ //
+ for (i = 0; i < dataSetRangesCount; i++) {
+ if ((dataSetRanges[i].StartingOffset % fdoExtension->DiskGeometry.BytesPerSector != 0) ||
+ (dataSetRanges[i].LengthInBytes % fdoExtension->DiskGeometry.BytesPerSector != 0) ||
+ (dataSetRanges[i].LengthInBytes == 0) ) {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspValidateOffloadInputParameters (%p): Incorrect DataSetRanges entry %u [offset %I64x, length %I64x].\n",
+ DeviceObject,
+ i,
+ dataSetRanges[i].StartingOffset,
+ dataSetRanges[i].LengthInBytes));
+
+ status = STATUS_INVALID_PARAMETER;
+ goto __ClasspValidateOffloadInputParameters_Exit;
+ }
+
+ if ((ULONGLONG)dataSetRanges[i].StartingOffset + dataSetRanges[i].LengthInBytes > (ULONGLONG)fdoExtension->CommonExtension.PartitionLength.QuadPart) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspValidateOffloadInputParameters (%p): Error! DataSetRange %u (starting LBA %I64x) specified length %I64x exceeds the medium's capacity (%I64x).\n",
+ DeviceObject,
+ i,
+ dataSetRanges[i].StartingOffset,
+ dataSetRanges[i].LengthInBytes,
+ fdoExtension->CommonExtension.PartitionLength.QuadPart));
+
+ status = STATUS_NONEXISTENT_SECTOR;
+ goto __ClasspValidateOffloadInputParameters_Exit;
+ }
+ }
+
+__ClasspValidateOffloadInputParameters_Exit:
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspValidateOffloadInputParameters (%p): Exiting function Irp %p with status %x.\n",
+ DeviceObject,
+ Irp,
+ status));
+
+ return status;
+}
+
+
+_IRQL_requires_same_
+NTSTATUS
+ClasspGetTokenOperationCommandBufferLength(
+ _In_ PDEVICE_OBJECT Fdo,
+ _In_ ULONG ServiceAction,
+ _Inout_ PULONG CommandBufferLength,
+ _Out_opt_ PULONG TokenOperationBufferLength,
+ _Out_opt_ PULONG ReceiveTokenInformationBufferLength
+ )
+
+/*++
+
+Routine description:
+
+ This routine calculates the buffer length required to service a TokenOperation and its
+ corresponding ReceiveTokenInformation command.
+
+Arguments:
+
+ Fdo - The functional device object processing the PopulateToken/WriteUsingToken request
+ ServiceAction - Used to distinguish between a PopulateToken and a WriteUsingToken operation
+ CommandBufferLength - Returns the length of the buffer needed to service the token request (i.e. TokenOperation and its corresponding ReceiveTokenInformation command)
+ TokenOperationBufferLength - Optional parameter, which returns the length of the buffer needed to service just the TokenOperation command.
+ ReceiveTokenInformationBufferLength - Optional parameter, which returns the length of the buffer needed to service just the ReceiveTokenInformation command.
+
+Return Value:
+
+ STATUS_SUCCESS
+
+--*/
+
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
+ ULONG tokenOperationBufferLength;
+ ULONG receiveTokenInformationBufferLength;
+ PCOMMON_DEVICE_EXTENSION commonExtension = Fdo->DeviceExtension;
+ PSTORAGE_ADAPTER_DESCRIPTOR adapterDesc = commonExtension->PartitionZeroExtension->AdapterDescriptor;
+ ULONG hwMaxXferLen;
+ ULONG bufferLength = 0;
+ ULONG tokenOperationHeaderSize;
+ ULONG responseSize;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspGetTokenOperationCommandBufferLengths (%p): Entering function.\n",
+ Fdo));
+
+ NT_ASSERT(fdoExt->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits &&
+ NT_SUCCESS(fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus));
+
+ if (ServiceAction == SERVICE_ACTION_POPULATE_TOKEN) {
+ tokenOperationHeaderSize = FIELD_OFFSET(POPULATE_TOKEN_HEADER, BlockDeviceRangeDescriptor);
+ responseSize = FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_RESPONSE_HEADER, TokenDescriptor) + sizeof(BLOCK_DEVICE_TOKEN_DESCRIPTOR);
+ } else {
+ tokenOperationHeaderSize = FIELD_OFFSET(WRITE_USING_TOKEN_HEADER, BlockDeviceRangeDescriptor);
+ responseSize = 0;
+ }
+
+ //
+ // The TokenOperation command can specify a parameter length of max 2^16.
+ // If the device has a max limit on the number of range descriptors that can be specified in
+ // the TokenOperation command, we are limited to the lesser of these two values.
+ //
+ if (fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumRangeDescriptors == 0) {
+
+ tokenOperationBufferLength = MAX_TOKEN_OPERATION_PARAMETER_DATA_LENGTH;
+
+ } else {
+
+ tokenOperationBufferLength = MIN(tokenOperationHeaderSize + fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumRangeDescriptors * sizeof(BLOCK_DEVICE_RANGE_DESCRIPTOR),
+ MAX_TOKEN_OPERATION_PARAMETER_DATA_LENGTH);
+ }
+
+
+ //
+ // The ReceiveTokenInformation command can specify a parameter length of max 2 ^ 32
+ // Also, since the sense data can be of variable size, we'll use MAX_SENSE_BUFFER_SIZE.
+ //
+ receiveTokenInformationBufferLength = MIN(FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_HEADER, SenseData) + MAX_SENSE_BUFFER_SIZE + responseSize,
+ MAX_RECEIVE_TOKEN_INFORMATION_PARAMETER_DATA_LENGTH);
+
+ //
+ // Since we're going to reuse the buffer for both the TokenOperation and the ReceiveTokenInformation
+ // commands, the buffer length needs to handle both operations.
+ //
+ bufferLength = MAX(tokenOperationBufferLength, receiveTokenInformationBufferLength);
+
+ //
+ // The buffer length needs to be further limited to the adapter's capability though.
+ //
+ hwMaxXferLen = MIN(fdoData->HwMaxXferLen, adapterDesc->MaximumTransferLength);
+ bufferLength = MIN(bufferLength, hwMaxXferLen);
+
+ *CommandBufferLength = bufferLength;
+
+ if (TokenOperationBufferLength) {
+ *TokenOperationBufferLength = tokenOperationBufferLength;
+ }
+
+ if (ReceiveTokenInformationBufferLength) {
+ *ReceiveTokenInformationBufferLength = receiveTokenInformationBufferLength;
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspGetTokenOperationCommandBufferLengths (%p): Exiting function with bufferLength %u (tokenOpBufLen %u, recTokenInfoBufLen %u).\n",
+ Fdo,
+ bufferLength,
+ tokenOperationBufferLength,
+ receiveTokenInformationBufferLength));
+
+ return STATUS_SUCCESS;
+}
+
+
+_IRQL_requires_same_
+NTSTATUS
+ClasspGetTokenOperationDescriptorLimits(
+ _In_ PDEVICE_OBJECT Fdo,
+ _In_ ULONG ServiceAction,
+ _In_ ULONG MaxParameterBufferLength,
+ _Out_ PULONG MaxBlockDescriptorsCount,
+ _Out_ PULONGLONG MaxBlockDescriptorsLength
+ )
+
+/*++
+
+Routine description:
+
+ This routine calculates the maximum block descriptors and the maximum token transfer size
+ that can be accomodated in a single TokenOperation command.
+
+Arguments:
+
+ Fdo - The functional device object processing the PopulateToken/WriteUsingToken request
+ ServiceAction - Used to distinguish between a PopulateToken and a WriteUsingToken operation
+ MaxParameterBufferLength - The length constraint of the entire buffer for the parameter list based on other limitations (e.g. adapter max transfer length)
+ MaxBlockDescriptorsCount - Returns the maximum number of the block range descriptors that can be passed in a single TokenOperation command.
+ MaxBlockDescriptorsLength - Returns the maximum cumulative number of blocks across all the descriptors that must not be exceeded in a single TokenOperation command.
+
+Return Value:
+
+ STATUS_SUCCESS
+
+--*/
+
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Fdo->DeviceExtension;
+ ULONG tokenOperationHeaderSize = (ServiceAction == SERVICE_ACTION_POPULATE_TOKEN) ?
+ FIELD_OFFSET(POPULATE_TOKEN_HEADER, BlockDeviceRangeDescriptor) :
+ FIELD_OFFSET(WRITE_USING_TOKEN_HEADER, BlockDeviceRangeDescriptor);
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspGetTokenOperationDescriptorLimits (%p): Entering function.\n",
+ Fdo));
+
+ NT_ASSERT(fdoExt->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits &&
+ NT_SUCCESS(fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus));
+
+ *MaxBlockDescriptorsCount = (MaxParameterBufferLength - tokenOperationHeaderSize) / sizeof(BLOCK_DEVICE_RANGE_DESCRIPTOR);
+ *MaxBlockDescriptorsLength = (fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumTokenTransferSize == 0) ?
+ MAX_TOKEN_TRANSFER_SIZE : fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumTokenTransferSize;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspGetTokenOperationDescriptorLimits (%p): Exiting function with MaxDescr %u, MaxXferBlocks %I64u.\n",
+ Fdo,
+ *MaxBlockDescriptorsCount,
+ *MaxBlockDescriptorsLength));
+
+ return STATUS_SUCCESS;
+}
+
+
+
+_IRQL_requires_max_(APC_LEVEL)
+_IRQL_requires_min_(PASSIVE_LEVEL)
+_IRQL_requires_same_
+VOID
+ClasspConvertDataSetRangeToBlockDescr(
+ _In_ PDEVICE_OBJECT Fdo,
+ _In_ PVOID BlockDescr,
+ _Inout_ PULONG CurrentBlockDescrIndex,
+ _In_ ULONG MaxBlockDescrCount,
+ _Inout_ PULONG CurrentLbaCount,
+ _In_ ULONGLONG MaxLbaCount,
+ _Inout_ PDEVICE_DATA_SET_RANGE DataSetRange,
+ _Inout_ PULONGLONG TotalSectorsProcessed
+ )
+
+/*++
+
+Routine Description:
+
+ Convert DEVICE_DATA_SET_RANGE entry to be WINDOWS_BLOCK_DEVICE_RANGE_DESCRIPTOR entries.
+
+ As LengthInBytes field in DEVICE_DATA_SET_RANGE structure is 64 bits (bytes)
+ and LbaCount field in WINDOWS_BLOCK_DEVICE_RANGE_DESCRIPTOR structure is 32 bits (sectors),
+ it's possible that one DEVICE_DATA_SET_RANGE entry needs multiple
+ WINDOWS_BLOCK_DEVICE_RANGE_DESCRIPTOR entries. This routine handles the need for that
+ potential split.
+
+Arguments:
+
+ Fdo - The functional device object
+ BlockDescr - Pointer to the start of the Token Operation command's block descriptor
+ CurrentBlockDescrIndex - Index into the block descriptor at which to update the DataSetRange info
+ It also gets updated to return the index to the next empty one.
+ MaxBlockDescrCount - Maximum number of block descriptors that the device can handle in a single TokenOperation command
+ CurrentLbaCount - Returns the LBA of the last successfully processed DataSetRange
+ MaxLbaCount - Maximum transfer size that the device is capable of handling in a single TokenOperation command
+ DataSetRange - Contains information about one range extent that needs to be converted into a block descriptor
+ TotalSectorsProcessed - Returns the number of sectors corresponding to the DataSetRange that were succesfully mapped into block descriptors
+
+Return Value:
+
+ Nothing.
+
+ NOTE: if LengthInBytes does not reach to 0, the conversion for DEVICE_DATA_SET_RANGE entry
+ is not completed. Further conversion is needed by calling this function again.
+
+--*/
+
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
+ PBLOCK_DEVICE_RANGE_DESCRIPTOR blockDescr;
+ ULONGLONG startingSector;
+ ULONGLONG sectorCount;
+ ULONGLONG totalSectorCount;
+ ULONGLONG numberOfOptimalChunks;
+ USHORT optimalLbaPerDescrGranularity;
+ ULONG optimalLbaPerDescr;
+ ULONG maxLbaPerDescr;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspConvertDataSetRangeToBlockDescr (%p): Entering function. Starting offset %I64x.\n",
+ Fdo,
+ DataSetRange->StartingOffset));
+
+ fdoExtension = Fdo->DeviceExtension;
+ blockDescr = (PBLOCK_DEVICE_RANGE_DESCRIPTOR)BlockDescr;
+ totalSectorCount = 0;
+
+
+ //
+ // Since the OptimalTransferLength and the MaximumTransferLength are overloaded parameters for
+ // offloaded data transfers and regular read/write requests, it is not safe to use these values
+ // as they may report back what is used by regular read/write, which will cause a perf degradation
+ // in the offloaded case, since we may end up limiting the per block range descriptor length
+ // specified as opposed to what the target can actually handle in a single request.
+ // So until the SPC spec introduces these values specific to offloaded data transfers, we shall
+ // ignore them completely. The expectation we have from the target is as follows:
+ // 1. If the length specified in any of the block range descriptors is greater than the OTL that
+ // applies to ODX, the target will internally split into additional descriptors.
+ // 2. If the above causes it to run out of descriptors, or if the length specified in any of the
+ // descriptors is greater than the MTL that applies to ODX, the target will operate on as much
+ // data as possible and truncate the request to that point.
+ //
+ optimalLbaPerDescrGranularity = 0;
+ optimalLbaPerDescr = 0;
+ maxLbaPerDescr = 0;
+
+ if (optimalLbaPerDescr && maxLbaPerDescr) {
+
+ NT_ASSERT(optimalLbaPerDescr <= maxLbaPerDescr);
+ }
+
+ while ((DataSetRange->LengthInBytes > 0) &&
+ (*CurrentBlockDescrIndex < MaxBlockDescrCount) &&
+ (*CurrentLbaCount < MaxLbaCount)) {
+
+ startingSector = (ULONGLONG)(DataSetRange->StartingOffset / fdoExtension->DiskGeometry.BytesPerSector);
+
+ //
+ // Since the block descriptor has only 4 bytes for the number of logical blocks, we are
+ // constrained by that theoretical maximum.
+ //
+ sectorCount = MIN(DataSetRange->LengthInBytes / fdoExtension->DiskGeometry.BytesPerSector,
+ MAX_NUMBER_BLOCKS_PER_BLOCK_DEVICE_RANGE_DESCRIPTOR);
+
+ //
+ // We are constrained by MaxLbaCount.
+ //
+ if (((ULONGLONG)*CurrentLbaCount + sectorCount) >= MaxLbaCount) {
+
+ sectorCount = MaxLbaCount - *CurrentLbaCount;
+ }
+
+ //
+ // For each descriptor, the block count should be lesser than the MaximumTransferSize
+ //
+ if (maxLbaPerDescr > 0) {
+
+ //
+ // Each block device range descriptor can specify a max number of LBAs
+ //
+ sectorCount = MIN(sectorCount, maxLbaPerDescr);
+ }
+
+ //
+ // If the number of LBAs specified in the descriptor is greater than the OptimalTransferLength,
+ // processing of this descriptor by the target may incur a significant delay.
+ // So in order to allow the target to perform optimally, we'll further limit the number
+ // of blocks specified in any descriptor to be maximum OptimalTranferLength.
+ //
+ if (optimalLbaPerDescr > 0) {
+
+ sectorCount = MIN(sectorCount, optimalLbaPerDescr);
+ }
+
+ //
+ // In addition, it should either be an exact multiple of the OptimalTransferLengthGranularity,
+ // or be lesser than the OptimalTransferLengthGranularity (taken care of here).
+ //
+ if (optimalLbaPerDescrGranularity > 0) {
+
+ numberOfOptimalChunks = sectorCount / optimalLbaPerDescrGranularity;
+
+ if (numberOfOptimalChunks > 0) {
+ sectorCount = numberOfOptimalChunks * optimalLbaPerDescrGranularity;
+ }
+ }
+
+ NT_ASSERT(sectorCount <= MAX_NUMBER_BLOCKS_PER_BLOCK_DEVICE_RANGE_DESCRIPTOR);
+
+ REVERSE_BYTES_QUAD(blockDescr[*CurrentBlockDescrIndex].LogicalBlockAddress, &startingSector);
+ REVERSE_BYTES(blockDescr[*CurrentBlockDescrIndex].TransferLength, &sectorCount);
+
+ totalSectorCount += sectorCount;
+
+ DataSetRange->StartingOffset += sectorCount * fdoExtension->DiskGeometry.BytesPerSector;
+ DataSetRange->LengthInBytes -= sectorCount * fdoExtension->DiskGeometry.BytesPerSector;
+
+ *CurrentBlockDescrIndex += 1;
+ *CurrentLbaCount += (ULONG)sectorCount;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "ClasspConvertDataSetRangeToBlockDescr (%p): Descriptor: %u, starting LBA: %I64x, length: %I64x bytes, media size: %I64x.\n",
+ Fdo,
+ *CurrentBlockDescrIndex - 1,
+ startingSector,
+ sectorCount * fdoExtension->DiskGeometry.BytesPerSector,
+ (ULONGLONG)fdoExtension->CommonExtension.PartitionLength.QuadPart));
+ }
+
+ *TotalSectorsProcessed = totalSectorCount;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspConvertDataSetRangeToBlockDescr (%p): Exiting function (starting offset %I64x). Total sectors processed %I64u.\n",
+ Fdo,
+ DataSetRange->StartingOffset,
+ totalSectorCount));
+
+ return;
+}
+
+_IRQL_requires_same_
+PUCHAR
+ClasspBinaryToAscii(
+ _In_reads_(Length) PUCHAR HexBuffer,
+ _In_ ULONG Length,
+ _Inout_ PULONG UpdateLength
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will convert HexBuffer into an ascii NULL-terminated string.
+
+ Note: This routine will allocate memory for storing the ascii string. It is
+ the responsibility of the caller to free this buffer.
+
+Arguments:
+
+ HexBuffer - Pointer to the binary data.
+ Length - Length, in bytes, of HexBuffer.
+ UpdateLength - Storage to place the actual length of the returned string.
+
+Return Value:
+
+ ASCII string equivalent of the hex buffer, or NULL if an error occurred.
+
+--*/
+
+{
+ static const UCHAR integerTable[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
+ ULONG i;
+ ULONG j;
+ ULONG actualLength;
+ PUCHAR buffer = NULL;
+ UCHAR highWord;
+ UCHAR lowWord;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspBinaryToAscii (HexBuff %p): Entering function.\n",
+ HexBuffer));
+
+ if (!HexBuffer || Length == 0) {
+ *UpdateLength = 0;
+ goto __ClasspBinaryToAscii_Exit;
+ }
+
+ //
+ // Each byte converts into 2 chars:
+ // e.g. 0x05 => '0' '5'
+ // 0x0C => '0' 'C'
+ // 0x12 => '1' '2'
+ // And we need a terminating NULL for the string.
+ //
+ actualLength = (Length * 2) + 1;
+
+ //
+ // Allocate the buffer.
+ //
+ buffer = ExAllocatePoolWithTag(NonPagedPoolNx, actualLength, CLASSPNP_POOL_TAG_TOKEN_OPERATION);
+ if (!buffer) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspBinaryToAscii (HexBuff %p): Failed to allocate buffer for ASCII equivalent.\n",
+ HexBuffer));
+
+ *UpdateLength = 0;
+ goto __ClasspBinaryToAscii_Exit;
+ }
+
+ RtlZeroMemory(buffer, actualLength);
+
+ for (i = 0, j = 0; i < Length; i++) {
+
+ //
+ // Split out each nibble from the binary byte.
+ //
+ highWord = HexBuffer[i] >> 4;
+ lowWord = HexBuffer[i] & 0x0F;
+
+ //
+ // Using the lookup table, convert and stuff into
+ // the ascii buffer.
+ //
+ buffer[j++] = integerTable[highWord];
+#pragma warning(suppress: 6386) // PREFast bug means it doesn't see that Length < actualLength
+ buffer[j++] = integerTable[lowWord];
+ }
+
+ //
+ // Update the caller's length field.
+ //
+ *UpdateLength = actualLength;
+
+__ClasspBinaryToAscii_Exit:
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspBinaryToAscii (HexBuff %p): Exiting function with buffer %s.\n",
+ HexBuffer,
+ (buffer == NULL) ? "" : (const char*)buffer));
+
+ return buffer;
+}
+
+_IRQL_requires_same_
+NTSTATUS
+ClasspStorageEventNotification(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine handles an asynchronous event notification (most likely from
+ port drivers). Currently, we only care about media status change events.
+
+Arguments:
+
+ DeviceObject - Supplies the device object associated with this request
+ Irp - The IRP to be processed
+
+Return Value:
+
+ NTSTATUS code
+
+--*/
+
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
+ PIO_STACK_LOCATION irpStack;
+ PSTORAGE_EVENT_NOTIFICATION storageEvents;
+ NTSTATUS status;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspStorageEventNotification (%p): Entering function Irp %p.\n",
+ DeviceObject,
+ Irp));
+
+ fdoExtension = DeviceObject->DeviceExtension;
+ irpStack = IoGetCurrentIrpStackLocation (Irp);
+ storageEvents = Irp->AssociatedIrp.SystemBuffer;
+ status = STATUS_SUCCESS;
+
+ if (!storageEvents) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspStorageEventNotification (%p): NULL storage events passed in.\n",
+ DeviceObject));
+
+ status = STATUS_INVALID_PARAMETER;
+ goto __ClasspStorageEventNotification_Exit;
+ }
+
+ if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(STORAGE_EVENT_NOTIFICATION)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspStorageEventNotification (%p): Input buffer size (%u) too small.\n",
+ DeviceObject,
+ irpStack->Parameters.DeviceIoControl.InputBufferLength));
+
+ status = STATUS_INFO_LENGTH_MISMATCH;
+ goto __ClasspStorageEventNotification_Exit;
+ }
+
+ if ((storageEvents->Version != STORAGE_EVENT_NOTIFICATION_VERSION_V1) ||
+ (storageEvents->Size != sizeof(STORAGE_EVENT_NOTIFICATION))) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspStorageEventNotification (%p): Invalid version/size [version %u, size %u].\n",
+ DeviceObject,
+ storageEvents->Version,
+ storageEvents->Size));
+
+ status = STATUS_INVALID_PARAMETER;
+ goto __ClasspStorageEventNotification_Exit;
+ }
+
+ //
+ // Handle a media status event.
+ //
+ if (storageEvents->Events & STORAGE_EVENT_MEDIA_STATUS) {
+
+ //
+ // Only initiate operation if underlying port driver supports asynchronous notification
+ // and this is the FDO.
+ //
+ if ((fdoExtension->CommonExtension.IsFdo == TRUE) &&
+ (fdoExtension->FunctionSupportInfo->AsynchronousNotificationSupported)) {
+ ClassCheckMediaState(fdoExtension);
+ } else {
+ status = STATUS_NOT_SUPPORTED;
+ }
+
+ }
+
+__ClasspStorageEventNotification_Exit:
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspStorageEventNotification (%p): Exiting function Irp %p with status %x.\n",
+ DeviceObject,
+ Irp,
+ status));
+
+ Irp->IoStatus.Information = 0;
+ Irp->IoStatus.Status = status;
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+
+ return status;
+}
+
+VOID
+ClasspZeroQERR(
+ _In_ PDEVICE_OBJECT DeviceObject
+ )
+/*++
+
+Routine Description:
+
+ This routine will attempt to set the QERR bit of the mode Control page to
+ zero.
+
+Arguments:
+
+ DeviceObject - Supplies the device object associated with this request
+
+Return Value:
+
+ None
+
+--*/
+{
+ PMODE_PARAMETER_HEADER modeData = NULL;
+ PMODE_CONTROL_PAGE pageData = NULL;
+ ULONG size = 0;
+
+ modeData = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned,
+ MODE_PAGE_DATA_SIZE,
+ CLASS_TAG_MODE_DATA);
+
+ if (modeData == NULL) {
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_SCSI, "ClasspZeroQERR: Unable to allocate mode data buffer\n"));
+ goto ClasspZeroQERR_Exit;
+ }
+
+ RtlZeroMemory(modeData, MODE_PAGE_DATA_SIZE);
+
+ size = ClassModeSense(DeviceObject,
+ (PCHAR) modeData,
+ MODE_PAGE_DATA_SIZE,
+ MODE_PAGE_CONTROL);
+
+ if (size < sizeof(MODE_PARAMETER_HEADER)) {
+
+ //
+ // Retry the request in case of a check condition.
+ //
+
+ size = ClassModeSense(DeviceObject,
+ (PCHAR) modeData,
+ MODE_PAGE_DATA_SIZE,
+ MODE_PAGE_CONTROL);
+
+ if (size < sizeof(MODE_PARAMETER_HEADER)) {
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_SCSI, "ClasspZeroQERR: Mode Sense failed\n"));
+ goto ClasspZeroQERR_Exit;
+ }
+ }
+
+ //
+ // If the size is greater than size indicated by the mode data reset
+ // the data to the mode data.
+ //
+
+ if (size > (ULONG) (modeData->ModeDataLength + 1)) {
+ size = modeData->ModeDataLength + 1;
+ }
+
+ //
+ // Look for control page in the returned mode page data.
+ //
+
+ pageData = ClassFindModePage((PCHAR) modeData,
+ size,
+ MODE_PAGE_CONTROL,
+ TRUE);
+
+ if (pageData) {
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_SCSI,
+ "ClasspZeroQERR (%p): Current settings: QERR = %u, TST = %u, TAS = %u.\n",
+ DeviceObject,
+ pageData->QERR,
+ pageData->TST,
+ pageData->TAS));
+
+ if (pageData->QERR != 0) {
+ NTSTATUS status;
+ UCHAR pageSavable = 0;
+
+ //
+ // Set QERR to 0 with a Mode Select command. Re-use the modeData
+ // and pageData structures.
+ //
+ pageData->QERR = 0;
+
+ //
+ // We use the original Page Savable (PS) value for the Save Pages
+ // (SP) bit due to behavior described under the MODE SELECT(6)
+ // section of SPC-4.
+ //
+ pageSavable = pageData->PageSavable;
+
+ status = ClasspModeSelect(DeviceObject,
+ (PCHAR)modeData,
+ size,
+ pageSavable);
+
+ if (!NT_SUCCESS(status)) {
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_SCSI,
+ "ClasspZeroQERR (%p): Failed to set QERR = 0 with status %x\n",
+ DeviceObject,
+ status));
+ }
+ }
+ }
+
+ClasspZeroQERR_Exit:
+
+ if (modeData != NULL) {
+ ExFreePool(modeData);
+ }
+}
+
+_IRQL_requires_max_(PASSIVE_LEVEL)
+NTSTATUS
+ClasspPowerActivateDevice(
+ _In_ PDEVICE_OBJECT DeviceObject
+ )
+/*++
+
+Routine Description:
+
+ This routine synchronously sends an IOCTL_STORAGE_POWER_ACTIVE to the port
+ PDO in order to take an active reference on the given device. The device
+ will remain powered up and active for as long as this active reference is
+ taken.
+
+ The caller should ensure idle power management is enabled for the device
+ before calling this function.
+
+ Call ClasspPowerIdleDevice to release the active reference.
+
+Arguments:
+
+ DeviceObject - Supplies the FDO associated with this request.
+
+Return Value:
+
+ STATUS_SUCCESS if the active reference was successfully taken.
+
+--*/
+{
+ NTSTATUS status = STATUS_UNSUCCESSFUL;
+ PIRP irp;
+ KEVENT event;
+ IO_STATUS_BLOCK ioStatus;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
+
+ NT_ASSERT(fdoExtension->CommonExtension.IsFdo);
+ NT_ASSERT(fdoExtension->FunctionSupportInfo->IdlePower.IdlePowerEnabled);
+
+ KeInitializeEvent(&event, SynchronizationEvent, FALSE);
+
+ irp = IoBuildDeviceIoControlRequest(IOCTL_STORAGE_POWER_ACTIVE,
+ fdoExtension->LowerPdo,
+ NULL,
+ 0,
+ NULL,
+ 0,
+ FALSE,
+ &event,
+ &ioStatus);
+
+ if (irp != NULL) {
+ status = IoCallDriver(fdoExtension->LowerPdo, irp);
+ if (status == STATUS_PENDING) {
+ (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
+ status = ioStatus.Status;
+ }
+ } else {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ return status;
+}
+
+_IRQL_requires_max_(PASSIVE_LEVEL)
+NTSTATUS
+ClasspPowerIdleDevice(
+ _In_ PDEVICE_OBJECT DeviceObject
+ )
+/*++
+
+Routine Description:
+
+ This routine synchronously sends an IOCTL_STORAGE_POWER_IDLE to the port
+ PDO in order to release an active reference on the given device.
+
+ A call to ClasspPowerActivateDevice *must* have preceded a call to this
+ function.
+
+ The caller should ensure idle power management is enabled for the device
+ before calling this function.
+
+Arguments:
+
+ DeviceObject - Supplies the FDO associated with this request.
+
+Return Value:
+
+ STATUS_SUCCESS if the active reference was successfully released.
+
+--*/
+{
+ NTSTATUS status = STATUS_UNSUCCESSFUL;
+ PIRP irp;
+ KEVENT event;
+ IO_STATUS_BLOCK ioStatus;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
+
+ NT_ASSERT(fdoExtension->CommonExtension.IsFdo);
+ NT_ASSERT(fdoExtension->FunctionSupportInfo->IdlePower.IdlePowerEnabled);
+
+ KeInitializeEvent(&event, SynchronizationEvent, FALSE);
+
+ irp = IoBuildDeviceIoControlRequest(IOCTL_STORAGE_POWER_IDLE,
+ fdoExtension->LowerPdo,
+ NULL,
+ 0,
+ NULL,
+ 0,
+ FALSE,
+ &event,
+ &ioStatus);
+
+ if (irp != NULL) {
+ status = IoCallDriver(fdoExtension->LowerPdo, irp);
+ if (status == STATUS_PENDING) {
+ (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
+ status = ioStatus.Status;
+ }
+ } else {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ return status;
+}
+
+
+#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
+
+NTSTATUS
+ClasspGetHwFirmwareInfo(
+ _In_ PDEVICE_OBJECT DeviceObject
+ )
+{
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
+
+ PSTORAGE_HW_FIRMWARE_INFO firmwareInfo = NULL;
+ PSTORAGE_HW_FIRMWARE_INFO_QUERY query = NULL;
+
+ IO_STATUS_BLOCK ioStatus = {0};
+ ULONG dataLength = sizeof(STORAGE_HW_FIRMWARE_INFO);
+ ULONG iteration = 1;
+
+ CLASS_FUNCTION_SUPPORT oldState;
+ KLOCK_QUEUE_HANDLE lockHandle;
+
+ //
+ // Try to get firmware information that contains only one slot.
+ // We will retry the query if the required buffer size is bigger than that.
+ //
+retry:
+
+ firmwareInfo = ExAllocatePoolWithTag(NonPagedPoolNx, dataLength, CLASSPNP_POOL_TAG_FIRMWARE);
+
+ if (firmwareInfo == NULL) {
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClasspGetHwFirmwareInfo: cannot allocate memory to hold data. \n"));
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ RtlZeroMemory(firmwareInfo, dataLength);
+
+ //
+ // Set up query data, making sure the "Flags" field indicating the request is for device itself.
+ //
+ query = (PSTORAGE_HW_FIRMWARE_INFO_QUERY)firmwareInfo;
+
+ query->Version = sizeof(STORAGE_HW_FIRMWARE_INFO_QUERY);
+ query->Size = sizeof(STORAGE_HW_FIRMWARE_INFO_QUERY);
+ query->Flags = 0;
+
+ //
+ // On the first pass we just want to get the first few
+ // bytes of the descriptor so we can read it's size
+ //
+ ClassSendDeviceIoControlSynchronous(IOCTL_STORAGE_FIRMWARE_GET_INFO,
+ commonExtension->LowerDeviceObject,
+ query,
+ sizeof(STORAGE_HW_FIRMWARE_INFO_QUERY),
+ dataLength,
+ FALSE,
+ &ioStatus
+ );
+
+ if (!NT_SUCCESS(ioStatus.Status) &&
+ (ioStatus.Status != STATUS_BUFFER_OVERFLOW)) {
+ if (ClasspLowerLayerNotSupport(ioStatus.Status)) {
+ oldState = InterlockedCompareExchange((PLONG)(&fdoExtension->FunctionSupportInfo->HwFirmwareGetInfoSupport), (LONG)NotSupported, (ULONG)SupportUnknown);
+ }
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClasspGetHwFirmwareInfo: error %lx trying to "
+ "query hardware firmware information #%d \n", ioStatus.Status, iteration));
+ FREE_POOL(firmwareInfo);
+ return ioStatus.Status;
+ }
+
+ //
+ // Catch implementation issues from lower level driver.
+ //
+ if ((firmwareInfo->Version < sizeof(STORAGE_HW_FIRMWARE_INFO)) ||
+ (firmwareInfo->Size < sizeof(STORAGE_HW_FIRMWARE_INFO)) ||
+ (firmwareInfo->SlotCount == 0) ||
+ (firmwareInfo->ActiveSlot >= firmwareInfo->SlotCount) ||
+ ((firmwareInfo->PendingActivateSlot >= firmwareInfo->SlotCount) && (firmwareInfo->PendingActivateSlot != STORAGE_HW_FIRMWARE_INVALID_SLOT)) ||
+ (firmwareInfo->ImagePayloadMaxSize > fdoExtension->AdapterDescriptor->MaximumTransferLength)) {
+
+ oldState = InterlockedCompareExchange((PLONG)(&fdoExtension->FunctionSupportInfo->HwFirmwareGetInfoSupport), (LONG)NotSupported, (ULONG)SupportUnknown);
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClasspGetHwFirmwareInfo: error in returned data! "
+ "Version: 0x%X, Size: 0x%X, SlotCount: 0x%X, ActiveSlot: 0x%X, PendingActiveSlot: 0x%X, ImagePayloadMaxSize: 0x%X \n",
+ firmwareInfo->Version,
+ firmwareInfo->Size,
+ firmwareInfo->SlotCount,
+ firmwareInfo->ActiveSlot,
+ firmwareInfo->PendingActivateSlot,
+ firmwareInfo->ImagePayloadMaxSize));
+
+ FREE_POOL(firmwareInfo);
+ return STATUS_UNSUCCESSFUL;
+ }
+
+ //
+ // If the data size is bigger than sizeof(STORAGE_HW_FIRMWARE_INFO), e.g. device has more than one firmware slot,
+ // allocate a buffer to get all the data.
+ //
+ if ((firmwareInfo->Size > sizeof(STORAGE_HW_FIRMWARE_INFO)) &&
+ (iteration < 2)) {
+
+ dataLength = max(firmwareInfo->Size, sizeof(STORAGE_HW_FIRMWARE_INFO) + sizeof(STORAGE_HW_FIRMWARE_SLOT_INFO) * (firmwareInfo->SlotCount - 1));
+
+ //
+ // Retry the query with required buffer length.
+ //
+ FREE_POOL(firmwareInfo);
+ iteration++;
+ goto retry;
+ }
+
+
+ //
+ // Set the support status and use the memory we've allocated as caching buffer.
+ // In case of a competing thread already set the state, it will assign the caching buffer so release the current allocated one.
+ //
+ KeAcquireInStackQueuedSpinLock(&fdoExtension->FunctionSupportInfo->SyncLock, &lockHandle);
+
+ oldState = InterlockedCompareExchange((PLONG)(&fdoExtension->FunctionSupportInfo->HwFirmwareGetInfoSupport), (LONG)Supported, (ULONG)SupportUnknown);
+
+ if (oldState == SupportUnknown) {
+ fdoExtension->FunctionSupportInfo->HwFirmwareInfo = firmwareInfo;
+ } else if (oldState == Supported) {
+ //
+ // swap the buffers to keep the latest version.
+ //
+ PSTORAGE_HW_FIRMWARE_INFO cachedInfo = fdoExtension->FunctionSupportInfo->HwFirmwareInfo;
+
+ fdoExtension->FunctionSupportInfo->HwFirmwareInfo = firmwareInfo;
+
+ FREE_POOL(cachedInfo);
+ } else {
+ FREE_POOL(firmwareInfo);
+ }
+
+ KeReleaseInStackQueuedSpinLock(&lockHandle);
+
+ return ioStatus.Status;
+} // end ClasspGetHwFirmwareInfo()
+
+#endif // #if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
+
+NTSTATUS
+ClassDeviceHwFirmwareGetInfoProcess(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _Inout_ PIRP Irp
+ )
+/*
+Routine Description:
+
+ This function processes the Storage Hardware Firmware Get Information request.
+ If the information is not cached yet, it gets from lower level driver.
+
+Arguments:
+ DeviceObject: The target FDO.
+ Irp: The IRP which will contain the output buffer upon completion.
+
+Return Value:
+
+ NTSTATUS code.
+
+--*/
+{
+ NTSTATUS status = STATUS_SUCCESS;
+
+#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
+
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ PSTORAGE_HW_FIRMWARE_INFO_QUERY query = (PSTORAGE_HW_FIRMWARE_INFO_QUERY)Irp->AssociatedIrp.SystemBuffer;
+ BOOLEAN passDown = FALSE;
+ BOOLEAN copyData = FALSE;
+
+
+ //
+ // Input buffer is not big enough to contain required input information.
+ //
+ if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(STORAGE_HW_FIRMWARE_INFO_QUERY)) {
+
+ status = STATUS_INFO_LENGTH_MISMATCH;
+ goto Exit_Firmware_Get_Info;
+ }
+
+ //
+ // Output buffer is too small to contain return data.
+ //
+ if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(STORAGE_HW_FIRMWARE_INFO)) {
+
+ status = STATUS_BUFFER_TOO_SMALL;
+ goto Exit_Firmware_Get_Info;
+ }
+
+ //
+ // If the request is for a FDO, process the request for Storport, SDstor and Spaceport only.
+ //
+ if (commonExtension->IsFdo &&
+ (fdoExtension->MiniportDescriptor->Portdriver != StoragePortCodeSetStorport) &&
+ (fdoExtension->MiniportDescriptor->Portdriver != StoragePortCodeSetSpaceport) &&
+ (fdoExtension->MiniportDescriptor->Portdriver != StoragePortCodeSetSDport)) {
+
+ status = STATUS_NOT_IMPLEMENTED;
+ goto Exit_Firmware_Get_Info;
+ }
+
+ //
+ // Buffer "FunctionSupportInfo" is allocated during start device process. Following check defenses the situation
+ // of receiving this IOCTL when the device is created but not started, or device start failed but not get removed yet.
+ //
+ if (commonExtension->IsFdo && (fdoExtension->FunctionSupportInfo == NULL)) {
+
+ status = STATUS_UNSUCCESSFUL;
+ goto Exit_Firmware_Get_Info;
+ }
+
+ //
+ // Process the situation that request should be forwarded to lower level.
+ //
+ if (!commonExtension->IsFdo) {
+ passDown = TRUE;
+ }
+
+ if ((query->Flags & STORAGE_HW_FIRMWARE_REQUEST_FLAG_CONTROLLER) != 0) {
+ passDown = TRUE;
+ }
+
+ if (passDown) {
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ return status;
+ }
+
+ //
+ // The request is for a FDO. Process the request.
+ //
+ if (fdoExtension->FunctionSupportInfo->HwFirmwareGetInfoSupport == NotSupported) {
+ status = STATUS_NOT_IMPLEMENTED;
+ goto Exit_Firmware_Get_Info;
+ } else {
+ //
+ // Retrieve information from lower layer for the request. The cached information is not used
+ // in case device firmware information changed.
+ //
+ status = ClasspGetHwFirmwareInfo(DeviceObject);
+ copyData = NT_SUCCESS(status);
+ }
+
+Exit_Firmware_Get_Info:
+
+ if (copyData) {
+ //
+ // Firmware information is already cached in classpnp. Return a copy.
+ //
+ ULONG dataLength = min(irpStack->Parameters.DeviceIoControl.OutputBufferLength, fdoExtension->FunctionSupportInfo->HwFirmwareInfo->Size);
+
+ memcpy(Irp->AssociatedIrp.SystemBuffer, fdoExtension->FunctionSupportInfo->HwFirmwareInfo, dataLength);
+
+ Irp->IoStatus.Information = dataLength;
+ }
+
+ Irp->IoStatus.Status = status;
+
+#else
+ status = STATUS_NOT_IMPLEMENTED;
+ Irp->IoStatus.Status = status;
+#endif // #if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+
+ return status;
+}
+
+
+#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
+_IRQL_requires_same_
+_IRQL_requires_max_(DISPATCH_LEVEL)
+NTSTATUS
+ClassHwFirmwareDownloadComplete (
+ _In_ PDEVICE_OBJECT Fdo,
+ _In_ PIRP Irp,
+ _In_reads_opt_(_Inexpressible_("varies")) PVOID Context
+ )
+{
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+
+ PIRP originalIrp;
+
+ //
+ // Free the allocated buffer for firmware image.
+ //
+ if (Context != NULL) {
+ FREE_POOL(Context);
+ }
+
+ originalIrp = irpStack->Parameters.Others.Argument1;
+
+ NT_ASSERT(originalIrp != NULL);
+
+ originalIrp->IoStatus.Status = Irp->IoStatus.Status;
+ originalIrp->IoStatus.Information = Irp->IoStatus.Information;
+
+ ClassReleaseRemoveLock(Fdo, originalIrp);
+ ClassCompleteRequest(Fdo, originalIrp, IO_DISK_INCREMENT);
+
+ IoFreeIrp(Irp);
+
+ return STATUS_MORE_PROCESSING_REQUIRED;
+
+} // end ClassHwFirmwareDownloadComplete()
+#endif // #if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
+
+
+NTSTATUS
+ClassDeviceHwFirmwareDownloadProcess(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _Inout_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ )
+{
+ NTSTATUS status = STATUS_SUCCESS;
+
+#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
+
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
+
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ PSTORAGE_HW_FIRMWARE_DOWNLOAD firmwareDownload = (PSTORAGE_HW_FIRMWARE_DOWNLOAD)Irp->AssociatedIrp.SystemBuffer;
+ BOOLEAN passDown = FALSE;
+ ULONG i;
+ ULONG bufferSize = 0;
+ PUCHAR firmwareImageBuffer = NULL;
+ PIRP irp2 = NULL;
+ PIO_STACK_LOCATION newStack = NULL;
+ PCDB cdb = NULL;
+
+
+ //
+ // Input buffer is not big enough to contain required input information.
+ //
+ if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(STORAGE_HW_FIRMWARE_DOWNLOAD)) {
+
+ status = STATUS_INFO_LENGTH_MISMATCH;
+ goto Exit_Firmware_Download;
+ }
+
+ //
+ // Input buffer basic validation.
+ //
+ if ((firmwareDownload->Version < sizeof(STORAGE_HW_FIRMWARE_DOWNLOAD)) ||
+ (firmwareDownload->Size > irpStack->Parameters.DeviceIoControl.InputBufferLength) ||
+ ((firmwareDownload->BufferSize + FIELD_OFFSET(STORAGE_HW_FIRMWARE_DOWNLOAD, ImageBuffer)) > firmwareDownload->Size)) {
+
+ status = STATUS_INVALID_PARAMETER;
+ goto Exit_Firmware_Download;
+ }
+
+ //
+ // If the request is for a FDO, process the request for Storport, SDstor and Spaceport only.
+ //
+ if (commonExtension->IsFdo &&
+ (fdoExtension->MiniportDescriptor->Portdriver != StoragePortCodeSetStorport) &&
+ (fdoExtension->MiniportDescriptor->Portdriver != StoragePortCodeSetSpaceport) &&
+ (fdoExtension->MiniportDescriptor->Portdriver != StoragePortCodeSetSDport)) {
+
+ status = STATUS_NOT_IMPLEMENTED;
+ goto Exit_Firmware_Download;
+ }
+
+ //
+ // Buffer "FunctionSupportInfo" is allocated during start device process. Following check defenses the situation
+ // of receiving this IOCTL when the device is created but not started, or device start failed but not get removed yet.
+ //
+ if (commonExtension->IsFdo && (fdoExtension->FunctionSupportInfo == NULL)) {
+
+ status = STATUS_UNSUCCESSFUL;
+ goto Exit_Firmware_Download;
+ }
+
+ //
+ // Process the situation that request should be forwarded to lower level.
+ //
+ if (!commonExtension->IsFdo) {
+ passDown = TRUE;
+ }
+
+ if ((firmwareDownload->Flags & STORAGE_HW_FIRMWARE_REQUEST_FLAG_CONTROLLER) != 0) {
+ passDown = TRUE;
+ }
+
+ if (passDown) {
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ FREE_POOL(Srb);
+ return status;
+ }
+
+ //
+ // If firmware information hasn't been cached in classpnp, retrieve it.
+ //
+ if (fdoExtension->FunctionSupportInfo->HwFirmwareInfo == NULL) {
+ if (fdoExtension->FunctionSupportInfo->HwFirmwareGetInfoSupport == NotSupported) {
+ status = STATUS_NOT_IMPLEMENTED;
+ goto Exit_Firmware_Download;
+ } else {
+ //
+ // If this is the first time of retrieving firmware information,
+ // send request to lower level to get it.
+ //
+ status = ClasspGetHwFirmwareInfo(DeviceObject);
+
+ if (!NT_SUCCESS(status)) {
+ goto Exit_Firmware_Download;
+ }
+ }
+ }
+
+ //
+ // Fail the request if the firmware information cannot be retrieved.
+ //
+ if (fdoExtension->FunctionSupportInfo->HwFirmwareInfo == NULL) {
+ if (fdoExtension->FunctionSupportInfo->HwFirmwareGetInfoSupport == NotSupported) {
+ status = STATUS_NOT_IMPLEMENTED;
+ } else {
+ status = STATUS_UNSUCCESSFUL;
+ }
+
+ goto Exit_Firmware_Download;
+ }
+
+ //
+ // Validate the device support
+ //
+ if (fdoExtension->FunctionSupportInfo->HwFirmwareInfo->SupportUpgrade == FALSE) {
+ status = STATUS_NOT_SUPPORTED;
+ goto Exit_Firmware_Download;
+ }
+
+ //
+ // Check if the slot can be used to hold firmware image.
+ //
+ for (i = 0; i < fdoExtension->FunctionSupportInfo->HwFirmwareInfo->SlotCount; i++) {
+ if (fdoExtension->FunctionSupportInfo->HwFirmwareInfo->Slot[i].SlotNumber == firmwareDownload->Slot) {
+ break;
+ }
+ }
+
+ if ((i >= fdoExtension->FunctionSupportInfo->HwFirmwareInfo->SlotCount) ||
+ (fdoExtension->FunctionSupportInfo->HwFirmwareInfo->Slot[i].ReadOnly == TRUE)) {
+ //
+ // Either the slot number is out of scope or the slot is read-only.
+ //
+ status = STATUS_INVALID_PARAMETER;
+ goto Exit_Firmware_Download;
+ }
+
+ //
+ // Buffer size and alignment validation.
+ // Max Offset and Buffer Size can be represented by SCSI command is max value for 3 bytes.
+ //
+ if ((firmwareDownload->BufferSize == 0) ||
+ ((firmwareDownload->BufferSize % fdoExtension->FunctionSupportInfo->HwFirmwareInfo->ImagePayloadAlignment) != 0) ||
+ (firmwareDownload->BufferSize > fdoExtension->FunctionSupportInfo->HwFirmwareInfo->ImagePayloadMaxSize) ||
+ (firmwareDownload->BufferSize > fdoExtension->AdapterDescriptor->MaximumTransferLength) ||
+ ((firmwareDownload->Offset % fdoExtension->FunctionSupportInfo->HwFirmwareInfo->ImagePayloadAlignment) != 0) ||
+ (firmwareDownload->Offset > 0xFFFFFF) ||
+ (firmwareDownload->BufferSize > 0xFFFFFF)) {
+
+ status = STATUS_INVALID_PARAMETER;
+ goto Exit_Firmware_Download;
+ }
+
+
+ //
+ // Process the request by translating it into WRITE BUFFER command.
+ //
+ if (((ULONG_PTR)firmwareDownload->ImageBuffer % fdoExtension->FunctionSupportInfo->HwFirmwareInfo->ImagePayloadAlignment) != 0) {
+ //
+ // Allocate buffer aligns to PAGE_SIZE to accommodate any alignment requirement.
+ //
+ bufferSize = ALIGN_UP_BY(firmwareDownload->BufferSize, PAGE_SIZE);
+
+#pragma prefast(suppress:6014, "The allocated memory that firmwareImageBuffer points to will be freed in ClassHwFirmwareDownloadComplete().")
+ firmwareImageBuffer = ExAllocatePoolWithTag(NonPagedPoolNx, bufferSize, CLASSPNP_POOL_TAG_FIRMWARE);
+
+ if (firmwareImageBuffer == NULL) {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto Exit_Firmware_Download;
+ }
+
+ RtlZeroMemory(firmwareImageBuffer, bufferSize);
+
+ RtlCopyMemory(firmwareImageBuffer, firmwareDownload->ImageBuffer, (ULONG)firmwareDownload->BufferSize);
+
+ } else {
+ firmwareImageBuffer = firmwareDownload->ImageBuffer;
+ bufferSize = (ULONG)firmwareDownload->BufferSize;
+ }
+
+ //
+ // Allocate a new irp to send the WRITE BUFFER command down.
+ // Similar process as IOCTL_STORAGE_CHECK_VERIFY.
+ //
+ irp2 = IoAllocateIrp((CCHAR)(DeviceObject->StackSize + 3), FALSE);
+
+ if (irp2 == NULL) {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+
+ if (firmwareImageBuffer != firmwareDownload->ImageBuffer) {
+ FREE_POOL(firmwareImageBuffer);
+ }
+
+ goto Exit_Firmware_Download;
+ }
+
+ //
+ // 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 firmware download completion routine onto the stack
+ // and prepare the irp for the port driver
+ //
+ IoSetCompletionRoutine(irp2,
+ ClassHwFirmwareDownloadComplete,
+ (firmwareImageBuffer != firmwareDownload->ImageBuffer) ? firmwareImageBuffer : 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);
+
+ //
+ // Setup the CDB.
+ //
+ SrbSetCdbLength(Srb, CDB10GENERIC_LENGTH);
+ cdb = SrbGetCdb(Srb);
+ cdb->WRITE_BUFFER.OperationCode = SCSIOP_WRITE_DATA_BUFF;
+ cdb->WRITE_BUFFER.Mode = 0x0E;
+ cdb->WRITE_BUFFER.ModeSpecific = 0; //Reserved for Mode 0x0E
+ cdb->WRITE_BUFFER.BufferID = firmwareDownload->Slot;
+
+ cdb->WRITE_BUFFER.BufferOffset[0] = *((PCHAR)&firmwareDownload->Offset + 2);
+ cdb->WRITE_BUFFER.BufferOffset[1] = *((PCHAR)&firmwareDownload->Offset + 1);
+ cdb->WRITE_BUFFER.BufferOffset[2] = *((PCHAR)&firmwareDownload->Offset);
+
+ cdb->WRITE_BUFFER.ParameterListLength[0] = *((PCHAR)&firmwareDownload->BufferSize + 2);
+ cdb->WRITE_BUFFER.ParameterListLength[1] = *((PCHAR)&firmwareDownload->BufferSize + 1);
+ cdb->WRITE_BUFFER.ParameterListLength[2] = *((PCHAR)&firmwareDownload->BufferSize);
+
+ //
+ // Send as a tagged command.
+ //
+ SrbSetRequestAttribute(Srb, SRB_HEAD_OF_QUEUE_TAG_REQUEST);
+ SrbSetSrbFlags(Srb, SRB_FLAGS_NO_QUEUE_FREEZE | SRB_FLAGS_QUEUE_ACTION_ENABLE);
+
+ //
+ // Set timeout value.
+ //
+ SrbSetTimeOutValue(Srb, fdoExtension->TimeOutValue);
+
+ //
+ // This routine uses a completion routine so we don't want to release
+ // the remove lock until then.
+ //
+ status = ClassSendSrbAsynchronous(DeviceObject,
+ Srb,
+ irp2,
+ firmwareImageBuffer,
+ bufferSize,
+ TRUE);
+
+ if (status != STATUS_PENDING) {
+ //
+ // If the new irp cannot be sent down, free allocated memory and bail out.
+ //
+ if (firmwareImageBuffer != firmwareDownload->ImageBuffer) {
+ FREE_POOL(firmwareImageBuffer);
+ }
+
+ //
+ // If the irp cannot be sent down, the Srb has been freed. NULL it to prevent from freeing it again.
+ //
+ Srb = NULL;
+
+ ClassReleaseRemoveLock(DeviceObject, irp2);
+
+ IoFreeIrp(irp2);
+
+ goto Exit_Firmware_Download;
+ }
+
+ return status;
+
+Exit_Firmware_Download:
+
+ //
+ // Firmware Download request will be failed.
+ //
+ NT_ASSERT(!NT_SUCCESS(status));
+
+ Irp->IoStatus.Status = status;
+
+#else
+ status = STATUS_NOT_IMPLEMENTED;
+ Irp->IoStatus.Status = status;
+#endif // #if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+
+ FREE_POOL(Srb);
+
+ return status;
+}
+
+NTSTATUS
+ClassDeviceHwFirmwareActivateProcess(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _Inout_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ )
+{
+ NTSTATUS status = STATUS_SUCCESS;
+
+#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
+
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
+
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ PSTORAGE_HW_FIRMWARE_ACTIVATE firmwareActivate = (PSTORAGE_HW_FIRMWARE_ACTIVATE)Irp->AssociatedIrp.SystemBuffer;
+ BOOLEAN passDown = FALSE;
+ PCDB cdb = NULL;
+ ULONG i;
+
+
+ //
+ // Input buffer is not big enough to contain required input information.
+ //
+ if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(STORAGE_HW_FIRMWARE_ACTIVATE)) {
+
+ status = STATUS_INFO_LENGTH_MISMATCH;
+ goto Exit_Firmware_Activate;
+ }
+
+ //
+ // Input buffer basic validation.
+ //
+ if ((firmwareActivate->Version < sizeof(STORAGE_HW_FIRMWARE_ACTIVATE)) ||
+ (firmwareActivate->Size > irpStack->Parameters.DeviceIoControl.InputBufferLength)) {
+
+ status = STATUS_INVALID_PARAMETER;
+ goto Exit_Firmware_Activate;
+ }
+
+ //
+ // If the request is for a FDO, process the request for Storport, SDstor and Spaceport only.
+ //
+ if (commonExtension->IsFdo &&
+ (fdoExtension->MiniportDescriptor->Portdriver != StoragePortCodeSetStorport) &&
+ (fdoExtension->MiniportDescriptor->Portdriver != StoragePortCodeSetSpaceport) &&
+ (fdoExtension->MiniportDescriptor->Portdriver != StoragePortCodeSetSDport)) {
+
+ status = STATUS_NOT_IMPLEMENTED;
+ goto Exit_Firmware_Activate;
+ }
+
+ //
+ // Buffer "FunctionSupportInfo" is allocated during start device process. Following check defenses the situation
+ // of receiving this IOCTL when the device is created but not started, or device start failed but not get removed yet.
+ //
+ if (commonExtension->IsFdo && (fdoExtension->FunctionSupportInfo == NULL)) {
+
+ status = STATUS_UNSUCCESSFUL;
+ goto Exit_Firmware_Activate;
+ }
+
+ //
+ // Process the situation that request should be forwarded to lower level.
+ //
+ if (!commonExtension->IsFdo) {
+ passDown = TRUE;
+ }
+
+ if ((firmwareActivate->Flags & STORAGE_HW_FIRMWARE_REQUEST_FLAG_CONTROLLER) != 0) {
+ passDown = TRUE;
+ }
+
+ if (passDown) {
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ FREE_POOL(Srb);
+ return status;
+ }
+
+ //
+ // If firmware information hasn't been cached in classpnp, retrieve it.
+ //
+ if (fdoExtension->FunctionSupportInfo->HwFirmwareInfo == NULL) {
+ if (fdoExtension->FunctionSupportInfo->HwFirmwareGetInfoSupport == NotSupported) {
+ status = STATUS_NOT_IMPLEMENTED;
+ goto Exit_Firmware_Activate;
+ } else {
+ //
+ // If this is the first time of retrieving firmware information,
+ // send request to lower level to get it.
+ //
+ status = ClasspGetHwFirmwareInfo(DeviceObject);
+
+ if (!NT_SUCCESS(status)) {
+ goto Exit_Firmware_Activate;
+ }
+ }
+ }
+
+ //
+ // Fail the request if the firmware information cannot be retrieved.
+ //
+ if (fdoExtension->FunctionSupportInfo->HwFirmwareInfo == NULL) {
+ if (fdoExtension->FunctionSupportInfo->HwFirmwareGetInfoSupport == NotSupported) {
+ status = STATUS_NOT_IMPLEMENTED;
+ } else {
+ status = STATUS_UNSUCCESSFUL;
+ }
+
+ goto Exit_Firmware_Activate;
+ }
+
+ //
+ // Validate the device support
+ //
+ if (fdoExtension->FunctionSupportInfo->HwFirmwareInfo->SupportUpgrade == FALSE) {
+ status = STATUS_NOT_SUPPORTED;
+ goto Exit_Firmware_Activate;
+ }
+
+ //
+ // Check if the slot number is valid.
+ //
+ for (i = 0; i < fdoExtension->FunctionSupportInfo->HwFirmwareInfo->SlotCount; i++) {
+ if (fdoExtension->FunctionSupportInfo->HwFirmwareInfo->Slot[i].SlotNumber == firmwareActivate->Slot) {
+ break;
+ }
+ }
+
+ if (i >= fdoExtension->FunctionSupportInfo->HwFirmwareInfo->SlotCount) {
+ //
+ // Either the slot number is out of scope or the slot is read-only.
+ //
+ status = STATUS_INVALID_PARAMETER;
+ goto Exit_Firmware_Activate;
+ }
+
+ //
+ // Process the request by translating it into WRITE BUFFER command.
+ //
+ //
+ // Setup the CDB. This should be an untagged request.
+ //
+ SrbSetCdbLength(Srb, CDB10GENERIC_LENGTH);
+ cdb = SrbGetCdb(Srb);
+ cdb->WRITE_BUFFER.OperationCode = SCSIOP_WRITE_DATA_BUFF;
+ cdb->WRITE_BUFFER.Mode = 0x0F;
+ cdb->WRITE_BUFFER.ModeSpecific = 0; //Reserved for Mode 0x0F
+ cdb->WRITE_BUFFER.BufferID = firmwareActivate->Slot; //NOTE: this field will be ignored by SCSI device.
+
+ //
+ // Set timeout value.
+ //
+ SrbSetTimeOutValue(Srb, FIRMWARE_ACTIVATE_TIMEOUT_VALUE);
+
+ //
+ // This routine uses a completion routine - ClassIoComplete() so we don't want to release
+ // the remove lock until then.
+ //
+ status = ClassSendSrbAsynchronous(DeviceObject,
+ Srb,
+ Irp,
+ NULL,
+ 0,
+ FALSE);
+
+ if (status != STATUS_PENDING) {
+ //
+ // If the irp cannot be sent down, the Srb has been freed. NULL it to prevent from freeing it again.
+ //
+ Srb = NULL;
+
+ goto Exit_Firmware_Activate;
+ }
+
+ return status;
+
+Exit_Firmware_Activate:
+
+ //
+ // Firmware Activate request will be failed.
+ //
+ NT_ASSERT(!NT_SUCCESS(status));
+
+ Irp->IoStatus.Status = status;
+
+#else
+ status = STATUS_NOT_IMPLEMENTED;
+ Irp->IoStatus.Status = status;
+#endif // #if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+
+ FREE_POOL(Srb);
+ return status;
+}
+
+