summaryrefslogtreecommitdiff
path: root/storage/class/classpnp/src
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
parentef1905bf1e8825bb31120dfb27e0daf3154d859a (diff)
Initial publish
Diffstat (limited to 'storage/class/classpnp/src')
-rw-r--r--storage/class/classpnp/src/autorun.c4388
-rw-r--r--storage/class/classpnp/src/class.c16415
-rw-r--r--storage/class/classpnp/src/class.def119
-rw-r--r--storage/class/classpnp/src/class.rc22
-rw-r--r--storage/class/classpnp/src/classlog.mof175
-rw-r--r--storage/class/classpnp/src/classp.h2619
-rw-r--r--storage/class/classpnp/src/classpnp.htm273
-rw-r--r--storage/class/classpnp/src/classpnp.vcxproj322
-rw-r--r--storage/class/classpnp/src/classpnp.vcxproj.Filters87
-rw-r--r--storage/class/classpnp/src/classwmi.c1224
-rw-r--r--storage/class/classpnp/src/clntirp.c783
-rw-r--r--storage/class/classpnp/src/create.c1018
-rw-r--r--storage/class/classpnp/src/data.c222
-rw-r--r--storage/class/classpnp/src/debug.c966
-rw-r--r--storage/class/classpnp/src/debug.h132
-rw-r--r--storage/class/classpnp/src/dictlib.c218
-rw-r--r--storage/class/classpnp/src/dispatch.c131
-rw-r--r--storage/class/classpnp/src/history.c146
-rw-r--r--storage/class/classpnp/src/lock.c550
-rw-r--r--storage/class/classpnp/src/obsolete.c1125
-rw-r--r--storage/class/classpnp/src/power.c2650
-rw-r--r--storage/class/classpnp/src/retry.c758
-rw-r--r--storage/class/classpnp/src/srblib.c374
-rw-r--r--storage/class/classpnp/src/utils.c8906
-rw-r--r--storage/class/classpnp/src/xferpkt.c2047
25 files changed, 45670 insertions, 0 deletions
diff --git a/storage/class/classpnp/src/autorun.c b/storage/class/classpnp/src/autorun.c
new file mode 100644
index 00000000..4dba537b
--- /dev/null
+++ b/storage/class/classpnp/src/autorun.c
@@ -0,0 +1,4388 @@
+/*++
+
+Copyright (C) Microsoft Corporation, 1991 - 2010
+
+Module Name:
+
+ autorun.c
+
+Abstract:
+
+ Code for support of media change detection in the class driver
+
+Environment:
+
+ kernel mode only
+
+Notes:
+
+
+Revision History:
+
+--*/
+
+#include "classp.h"
+#include "debug.h"
+
+#ifdef DEBUG_USE_WPP
+#include "autorun.tmh"
+#endif
+
+#define GESN_TIMEOUT_VALUE (0x4)
+#define GESN_BUFFER_SIZE (0x8)
+#define GESN_DEVICE_BUSY_LOWER_THRESHOLD_100_MS (2)
+
+#define MAXIMUM_IMMEDIATE_MCN_RETRIES (0x20)
+#define MCN_REG_SUBKEY_NAME (L"MediaChangeNotification")
+#define MCN_REG_AUTORUN_DISABLE_INSTANCE_NAME (L"AlwaysDisableMCN")
+#define MCN_REG_AUTORUN_ENABLE_INSTANCE_NAME (L"AlwaysEnableMCN")
+
+const GUID StoragePredictFailureEventGuid = WMI_STORAGE_PREDICT_FAILURE_EVENT_GUID;
+
+//
+// Only send polling irp when device is fully powered up, a
+// power down irp is not in progress, and the screen is on.
+//
+// NOTE: This helps close a window in time where a polling irp could cause
+// a drive to spin up right after it has powered down. The problem is
+// that SCSIPORT, ATAPI and SBP2 will be in the process of powering
+// down (which may take a few seconds), but won't know that. It would
+// then get a polling irp which will be put into its queue since it
+// the disk isn't powered down yet. Once the disk is powered down it
+// will find the polling irp in the queue and then power up the
+// device to do the poll. They do not want to check if the polling
+// irp has the SRB_NO_KEEP_AWAKE flag here since it is in a critical
+// path and would slow down all I/Os. A better way to fix this
+// would be to serialize the polling and power down irps so that
+// only one of them is sent to the device at a time.
+//
+__inline
+BOOLEAN
+ClasspCanSendPollingIrp(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension
+ )
+{
+ return ((fdoExtension->DevicePowerState == PowerDeviceD0) &&
+ (fdoExtension->PowerDownInProgress == FALSE) &&
+ (ClasspScreenOff == FALSE));
+}
+
+BOOLEAN
+ClasspIsMediaChangeDisabledDueToHardwareLimitation(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ IN PUNICODE_STRING RegistryPath
+ );
+
+NTSTATUS
+ClasspMediaChangeDeviceInstanceOverride(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ OUT PBOOLEAN Enabled
+ );
+
+BOOLEAN
+ClasspIsMediaChangeDisabledForClass(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ IN PUNICODE_STRING RegistryPath
+ );
+
+VOID
+ClasspSetMediaChangeStateEx(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ IN MEDIA_CHANGE_DETECTION_STATE NewState,
+ IN BOOLEAN Wait,
+ IN BOOLEAN KnownStateChange // can ignore oldstate == unknown
+ );
+
+RTL_QUERY_REGISTRY_ROUTINE ClasspMediaChangeRegistryCallBack;
+
+VOID
+ClasspSendMediaStateIrp(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ IN PMEDIA_CHANGE_DETECTION_INFO Info,
+ IN ULONG CountDown
+ );
+
+IO_WORKITEM_ROUTINE ClasspFailurePredict;
+
+NTSTATUS
+ClasspInitializePolling(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ IN BOOLEAN AllowDriveToSleep
+ );
+
+
+IO_WORKITEM_ROUTINE ClasspDisableGesn;
+
+IO_COMPLETION_ROUTINE ClasspMediaChangeDetectionCompletion;
+
+KDEFERRED_ROUTINE ClasspTimerTick;
+
+#if (NTDDI_VERSION >= NTDDI_WINBLUE)
+EXT_CALLBACK ClasspTimerTickEx;
+#endif
+
+BOOLEAN ClasspScreenOff = FALSE;
+
+//
+// Tick timer related defines.
+//
+#define TICK_TIMER_PERIOD_IN_MSEC 1000
+#define TICK_TIMER_DELAY_IN_MSEC 1000
+
+#if ALLOC_PRAGMA
+
+#pragma alloc_text(PAGE, ClassInitializeMediaChangeDetection)
+#pragma alloc_text(PAGE, ClassEnableMediaChangeDetection)
+#pragma alloc_text(PAGE, ClassDisableMediaChangeDetection)
+#pragma alloc_text(PAGE, ClassCleanupMediaChangeDetection)
+#pragma alloc_text(PAGE, ClasspMediaChangeRegistryCallBack)
+#pragma alloc_text(PAGE, ClasspInitializePolling)
+#pragma alloc_text(PAGE, ClasspDisableGesn)
+
+#pragma alloc_text(PAGE, ClasspIsMediaChangeDisabledDueToHardwareLimitation)
+#pragma alloc_text(PAGE, ClasspMediaChangeDeviceInstanceOverride)
+#pragma alloc_text(PAGE, ClasspIsMediaChangeDisabledForClass)
+
+#pragma alloc_text(PAGE, ClassSetFailurePredictionPoll)
+
+#pragma alloc_text(PAGE, ClasspInitializeGesn)
+#pragma alloc_text(PAGE, ClasspMcnControl)
+
+#endif
+
+// ISSUE -- make this public?
+VOID
+ClassSendEjectionNotification(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ )
+{
+ //
+ // For post-NT5.1 work, need to move EjectSynchronizationEvent
+ // to be a MUTEX so we can attempt to grab it here and benefit
+ // from deadlock detection. This will allow checking if the media
+ // has been locked by programs before broadcasting these events.
+ // (what's the point of broadcasting if the media is not locked?)
+ //
+ // This would currently only be a slight optimization. For post-NT5.1,
+ // it would allow us to send a single PERSISTENT_PREVENT to MMC devices,
+ // thereby cleaning up a lot of the ejection code. Then, when the
+ // ejection request occured, we could see if any locks for the media
+ // existed. if locked, broadcast. if not, we send the eject irp.
+ //
+
+ //
+ // for now, just always broadcast. make this a public routine,
+ // so class drivers can add special hacks to broadcast this for their
+ // non-MMC-compliant devices also from sense codes.
+ //
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClassSendEjectionNotification: media EJECT_REQUEST"));
+ ClassSendNotification(FdoExtension,
+ &GUID_IO_MEDIA_EJECT_REQUEST,
+ 0,
+ NULL);
+ return;
+}
+
+
+_IRQL_requires_max_(DISPATCH_LEVEL)
+VOID
+ClassSendNotification(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ _In_ const GUID * Guid,
+ _In_ ULONG ExtraDataSize,
+ _In_reads_bytes_opt_(ExtraDataSize) PVOID ExtraData
+ )
+{
+ PTARGET_DEVICE_CUSTOM_NOTIFICATION notification;
+ ULONG requiredSize;
+ NTSTATUS status;
+
+ status = RtlULongAdd((sizeof(TARGET_DEVICE_CUSTOM_NOTIFICATION) - sizeof(UCHAR)),
+ ExtraDataSize,
+ &requiredSize);
+
+ if (!(NT_SUCCESS(status)) || (requiredSize > 0x0000ffff)) {
+ // MAX_USHORT, max total size for these events!
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN,
+ "Error sending event: size too large! (%x)\n",
+ requiredSize));
+ return;
+ }
+
+ notification = ExAllocatePoolWithTag(NonPagedPoolNx,
+ requiredSize,
+ 'oNcS');
+
+ //
+ // if none allocated, exit
+ //
+
+ if (notification == NULL) {
+ return;
+ }
+
+ //
+ // Prepare and send the request!
+ //
+
+ RtlZeroMemory(notification, requiredSize);
+ notification->Version = 1;
+ notification->Size = (USHORT)(requiredSize);
+ notification->FileObject = NULL;
+ notification->NameBufferOffset = -1;
+ notification->Event = *Guid;
+
+ if (ExtraData != NULL && ExtraDataSize != 0) {
+ RtlCopyMemory(notification->CustomDataBuffer, ExtraData, ExtraDataSize);
+ }
+
+ IoReportTargetDeviceChangeAsynchronous(FdoExtension->LowerPdo,
+ notification,
+ NULL, NULL);
+
+ FREE_POOL(notification);
+ return;
+}
+
+
+NTSTATUS
+ClasspInterpretGesnData(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ IN PNOTIFICATION_EVENT_STATUS_HEADER Header,
+ OUT PBOOLEAN ResendImmediately
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will interpret the data returned for a GESN command, and
+ (if appropriate) set the media change event, and broadcast the
+ appropriate events to user mode for applications who care.
+
+Arguments:
+
+ FdoExtension - the device
+
+ DataBuffer - the resulting data from a GESN event.
+ requires at least EIGHT valid bytes (header == 4, data == 4)
+
+ ResendImmediately - whether or not to immediately resend the request.
+ this should be FALSE if there was no event, FALSE if the reported
+ event was of the DEVICE BUSY class, else true.
+
+Return Value:
+
+ STATUS_SUCCESS if successful, an error code otherwise
+
+Notes:
+
+ DataBuffer must be at least four bytes of valid data (header == 4 bytes),
+ and have at least eight bytes of allocated memory (all events == 4 bytes).
+
+ The call to StartNextPacket may occur before this routine is completed.
+ the operational change notifications are informational in nature, and
+ while useful, are not neccessary to ensure proper operation. For example,
+ if the device morphs to no longer supporting WRITE commands, all further
+ write commands will fail. There exists a small timing window wherein
+ IOCTL_IS_DISK_WRITABLE may be called and get an incorrect response. If
+ a device supports software write protect, it is expected that the
+ application can handle such a case.
+
+ NOTE: perhaps setting the updaterequired byte to one should be done here.
+ if so, it relies upon the setting of a 32-byte value to be an atomic
+ operation. unfortunately, there is no simple way to notify a class driver
+ which wants to know that the device behavior requires updating.
+
+ Not ready events may be sent every second. For example, if we were
+ to minimize the number of asynchronous notifications, an application may
+ register just after a large busy time was reported. This would then
+ prevent the application from knowing the device was busy until some
+ arbitrarily chosen timeout has occurred. Also, the GESN request would
+ have to still occur, since it checks for non-busy events (such as user
+ keybutton presses and media change events) as well. The specification
+ states that the lower-numered events get reported first, so busy events,
+ while repeating, will only be reported when all other events have been
+ cleared from the device.
+
+--*/
+
+{
+ PMEDIA_CHANGE_DETECTION_INFO info;
+ LONG dataLength;
+ LONG requiredLength;
+ NTSTATUS status = STATUS_SUCCESS;
+
+ info = FdoExtension->MediaChangeDetectionInfo;
+
+ //
+ // note: don't allocate anything in this routine so that we can
+ // always just 'return'.
+ //
+
+ *ResendImmediately = FALSE;
+ if (Header->NEA) {
+ return status;
+ }
+ if (Header->NotificationClass == NOTIFICATION_NO_CLASS_EVENTS) {
+ return status;
+ }
+
+ //
+ // HACKHACK - REF #0001
+ // This loop is only taken initially, due to the inability to reliably
+ // auto-detect drives that report events correctly at boot. When we
+ // detect this behavior during the normal course of running, we will
+ // disable the hack, allowing more efficient use of the system. This
+ // should occur "nearly" instantly, as the drive should have multiple
+ // events queue'd (ie. power, morphing, media).
+ //
+
+ if (info->Gesn.HackEventMask) {
+
+ //
+ // all events use the low four bytes of zero to indicate
+ // that there was no change in status.
+ //
+
+ UCHAR thisEvent = Header->ClassEventData[0] & 0xf;
+ UCHAR lowestSetBit;
+ UCHAR thisEventBit = (1 << Header->NotificationClass);
+
+ if (!TEST_FLAG(info->Gesn.EventMask, thisEventBit)) {
+
+ //
+ // The drive is reporting an event that wasn't requested
+ //
+
+ return STATUS_DEVICE_PROTOCOL_ERROR;
+ }
+
+ //
+ // some bit magic here... this results in the lowest set bit only
+ //
+
+ lowestSetBit = info->Gesn.EventMask;
+ lowestSetBit &= (info->Gesn.EventMask - 1);
+ lowestSetBit ^= (info->Gesn.EventMask);
+
+ if (thisEventBit != lowestSetBit) {
+
+ //
+ // HACKHACK - REF #0001
+ // the first time we ever see an event set that is not the lowest
+ // set bit in the request (iow, highest priority), we know that the
+ // hack is no longer required, as the device is ignoring "no change"
+ // events when a real event is waiting in the other requested queues.
+ //
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "Classpnp => GESN::NONE: Compliant drive found, "
+ "removing GESN hack (%x, %x)\n",
+ thisEventBit, info->Gesn.EventMask));
+
+ info->Gesn.HackEventMask = FALSE;
+
+ } else if (thisEvent == 0) { // NOTIFICATION_*_EVENT_NO_CHANGE
+
+ //
+ // HACKHACK - REF #0001
+ // note: this hack prevents poorly implemented firmware from constantly
+ // returning "No Event". we do this by cycling through the
+ // supported list of events here.
+ //
+
+ SET_FLAG(info->Gesn.NoChangeEventMask, thisEventBit);
+ CLEAR_FLAG(info->Gesn.EventMask, thisEventBit);
+
+ //
+ // if we have cycled through all supported event types, then
+ // we need to reset the events we are asking about. else we
+ // want to resend this request immediately in case there was
+ // another event pending.
+ //
+
+ if (info->Gesn.EventMask == 0) {
+ info->Gesn.EventMask = info->Gesn.NoChangeEventMask;
+ info->Gesn.NoChangeEventMask = 0;
+ } else {
+ *ResendImmediately = TRUE;
+ }
+ return status;
+ }
+
+ } // end if (info->Gesn.HackEventMask)
+
+ dataLength =
+ (Header->EventDataLength[0] << 8) |
+ (Header->EventDataLength[1] & 0xff);
+ dataLength -= 2;
+ requiredLength = 4; // all events are four bytes
+
+ if (dataLength < requiredLength) {
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN,
+ "Classpnp => GESN returned only %x bytes data for fdo %p\n",
+ dataLength, FdoExtension->DeviceObject));
+
+ return STATUS_DEVICE_PROTOCOL_ERROR;
+ }
+ if (dataLength != requiredLength) {
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN,
+ "Classpnp => GESN returned too many (%x) bytes data for fdo %p\n",
+ dataLength, FdoExtension->DeviceObject));
+ // dataLength = 4;
+ }
+
+ NT_ASSERT(dataLength == 4);
+
+ if ((Header->ClassEventData[0] & 0xf) == 0)
+ {
+ // a zero event is a "no change event, so do not retry
+ return status;
+ }
+
+ // because a event other than "no change" occurred,
+ // we should immediately resend this request.
+ *ResendImmediately = TRUE;
+
+
+/*
+ ClassSendNotification(FdoExtension,
+ &GUID_IO_GENERIC_GESN_EVENT,
+ sizeof(NOTIFICATION_EVENT_STATUS_HEADER) + dataLength,
+ Header)
+*/
+
+
+
+ switch (Header->NotificationClass) {
+
+ case NOTIFICATION_OPERATIONAL_CHANGE_CLASS_EVENTS: { // 0x01
+
+ PNOTIFICATION_OPERATIONAL_STATUS opChangeInfo =
+ (PNOTIFICATION_OPERATIONAL_STATUS)(Header->ClassEventData);
+ ULONG event;
+
+ if (opChangeInfo->OperationalEvent == NOTIFICATION_OPERATIONAL_EVENT_CHANGE_REQUESTED) {
+ break;
+ }
+
+ event = (opChangeInfo->Operation[0] << 8) |
+ (opChangeInfo->Operation[1] ) ;
+
+ // Workaround some hardware that is buggy but prevalent in the market
+ // This hardware has the property that it will report OpChange events repeatedly,
+ // causing us to retry immediately so quickly that we will eventually disable
+ // GESN to prevent an infinite loop.
+ // (only one valid OpChange event type now, only two ever defined)
+ if (info->MediaChangeRetryCount >= 4) {
+
+ //
+ // HACKHACK - REF #0002
+ // Some drives incorrectly report OpChange/Change (001b/0001h) events
+ // continuously when the tray has been ejected. This causes this routine
+ // to set ResendImmediately to "TRUE", and that results in our cycling
+ // 32 times immediately resending. At that point, we give up detecting
+ // the infinite retry loop, and disable GESN on these drives. This
+ // prevents Media Eject Request (from eject button) from being reported.
+ // Thus, instead we should attempt to workaround this issue by detecting
+ // this behavior.
+ //
+
+ static UCHAR const OpChangeMask = 0x02;
+
+ // At least one device reports "temporarily busy" (which is useless) on eject
+ // At least one device reports "OpChange" repeatedly when re-inserting media
+ // All seem to work well using this workaround
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_MCN,
+ "Classpnp => GESN OpChange events are broken. Working around this "
+ "problem in software (for fdo %p)\n",
+ FdoExtension->DeviceObject));
+
+
+ // OpChange is not the only bit set -- Media class is required....
+ NT_ASSERT(CountOfSetBitsUChar(info->Gesn.EventMask) != 1);
+
+ //
+ // Force the use of the hackhack (ref #0001) to workaround the
+ // issue noted this hackhack (ref #0002).
+ //
+ SET_FLAG(info->Gesn.NoChangeEventMask, OpChangeMask);
+ CLEAR_FLAG(info->Gesn.EventMask, OpChangeMask);
+ info->Gesn.HackEventMask = TRUE;
+
+ //
+ // don't request the opChange event again. use the method
+ // defined by hackhack (ref #0001) as the workaround.
+ //
+
+ if (info->Gesn.EventMask == 0) {
+ info->Gesn.EventMask = info->Gesn.NoChangeEventMask;
+ info->Gesn.NoChangeEventMask = 0;
+ *ResendImmediately = FALSE;
+ } else {
+ *ResendImmediately = TRUE;
+ }
+
+ break;
+ }
+
+
+ if ((event == NOTIFICATION_OPERATIONAL_OPCODE_FEATURE_ADDED) |
+ (event == NOTIFICATION_OPERATIONAL_OPCODE_FEATURE_CHANGE)) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "Classpnp => GESN says features added/changedfor fdo %p\n",
+ FdoExtension->DeviceObject));
+
+ // don't notify that new media arrived, just set the
+ // DO_VERIFY to force a FS reload.
+
+ if (TEST_FLAG(FdoExtension->DeviceObject->Characteristics,
+ FILE_REMOVABLE_MEDIA) &&
+ (ClassGetVpb(FdoExtension->DeviceObject) != NULL) &&
+ (ClassGetVpb(FdoExtension->DeviceObject)->Flags & VPB_MOUNTED)
+ ) {
+
+ SET_FLAG(FdoExtension->DeviceObject->Flags, DO_VERIFY_VOLUME);
+ }
+
+ //
+ // If there is a class specific error handler, call it with
+ // a "fake" media change error in case it needs to update
+ // internal structures as though a media change occurred.
+ //
+
+ if (FdoExtension->CommonExtension.DevInfo->ClassError != NULL) {
+
+ SCSI_REQUEST_BLOCK srb = {0};
+ UCHAR srbExBuffer[CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE] = {0};
+ PSTORAGE_REQUEST_BLOCK srbEx = (PSTORAGE_REQUEST_BLOCK)srbExBuffer;
+ PSCSI_REQUEST_BLOCK srbPtr;
+
+ SENSE_DATA sense = {0};
+ NTSTATUS tempStatus;
+ BOOLEAN retry;
+
+ tempStatus = STATUS_MEDIA_CHANGED;
+ retry = FALSE;
+
+ sense.ErrorCode = SCSI_SENSE_ERRORCODE_FIXED_CURRENT;
+
+ sense.AdditionalSenseLength = sizeof(SENSE_DATA) -
+ RTL_SIZEOF_THROUGH_FIELD(SENSE_DATA, AdditionalSenseLength);
+
+ sense.SenseKey = SCSI_SENSE_UNIT_ATTENTION;
+ sense.AdditionalSenseCode = SCSI_ADSENSE_MEDIUM_CHANGED;
+
+ //
+ // Send the right type of SRB to the class driver
+ //
+ if ((FdoExtension->CommonExtension.DriverExtension->SrbSupport &
+ CLASS_SRB_STORAGE_REQUEST_BLOCK) != 0) {
+ #pragma prefast(suppress:26015, "InitializeStorageRequestBlock ensures buffer access is bounded")
+ status = InitializeStorageRequestBlock(srbEx,
+ STORAGE_ADDRESS_TYPE_BTL8,
+ CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE,
+ 1,
+ SrbExDataTypeScsiCdb16);
+ if (NT_SUCCESS(status)) {
+ SrbSetCdbLength(srbEx, 6);
+ srbEx->SrbStatus = SRB_STATUS_AUTOSENSE_VALID | SRB_STATUS_ERROR;
+ SrbSetSenseInfoBuffer(srbEx, &sense);
+ SrbSetSenseInfoBufferLength(srbEx, sizeof(sense));
+ srbPtr = (PSCSI_REQUEST_BLOCK)srbEx;
+ } else {
+ // should not happen. Revert to legacy SRB.
+ NT_ASSERT(FALSE);
+ srb.CdbLength = 6;
+ srb.Length = sizeof(SCSI_REQUEST_BLOCK);
+ srb.SrbStatus = SRB_STATUS_AUTOSENSE_VALID | SRB_STATUS_ERROR;
+ srb.SenseInfoBuffer = &sense;
+ srb.SenseInfoBufferLength = sizeof(SENSE_DATA);
+ srbPtr = &srb;
+ }
+ } else {
+ srb.CdbLength = 6;
+ srb.Length = sizeof(SCSI_REQUEST_BLOCK);
+ srb.SrbStatus = SRB_STATUS_AUTOSENSE_VALID | SRB_STATUS_ERROR;
+ srb.SenseInfoBuffer = &sense;
+ srb.SenseInfoBufferLength = sizeof(SENSE_DATA);
+ srbPtr = &srb;
+ }
+
+ FdoExtension->CommonExtension.DevInfo->ClassError(FdoExtension->DeviceObject,
+ srbPtr,
+ &tempStatus,
+ &retry);
+
+ } // end class error handler
+
+ }
+ break;
+ }
+
+ case NOTIFICATION_EXTERNAL_REQUEST_CLASS_EVENTS: { // 0x3
+
+ PNOTIFICATION_EXTERNAL_STATUS externalInfo =
+ (PNOTIFICATION_EXTERNAL_STATUS)(Header->ClassEventData);
+ DEVICE_EVENT_EXTERNAL_REQUEST externalData = {0};
+
+ //
+ // unfortunately, due to time constraints, we will only notify
+ // about keys being pressed, and not released. this makes keys
+ // single-function, but simplifies the code significantly.
+ //
+
+ if (externalInfo->ExternalEvent != NOTIFICATION_EXTERNAL_EVENT_BUTTON_DOWN) {
+ break;
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "Classpnp => GESN::EXTERNAL: Event: %x Status %x Req %x\n",
+ externalInfo->ExternalEvent, externalInfo->ExternalStatus,
+ (externalInfo->Request[0] << 8) | externalInfo->Request[1]
+ ));
+
+ externalData.Version = 1;
+ externalData.DeviceClass = 0;
+ externalData.ButtonStatus = externalInfo->ExternalEvent;
+ externalData.Request =
+ (externalInfo->Request[0] << 8) |
+ (externalInfo->Request[1] & 0xff);
+ KeQuerySystemTime(&(externalData.SystemTime));
+ externalData.SystemTime.QuadPart *= (LONGLONG)KeQueryTimeIncrement();
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClasspInterpretGesnData: media DEVICE_EXTERNAL_REQUEST"));
+ ClassSendNotification(FdoExtension,
+ &GUID_IO_DEVICE_EXTERNAL_REQUEST,
+ sizeof(DEVICE_EVENT_EXTERNAL_REQUEST),
+ &externalData);
+ return status;
+ }
+
+ case NOTIFICATION_MEDIA_STATUS_CLASS_EVENTS: { // 0x4
+
+ PNOTIFICATION_MEDIA_STATUS mediaInfo =
+ (PNOTIFICATION_MEDIA_STATUS)(Header->ClassEventData);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "Classpnp => GESN::MEDIA: Event: %x Status %x\n",
+ mediaInfo->MediaEvent, mediaInfo->MediaStatus));
+
+ if ((mediaInfo->MediaEvent == NOTIFICATION_MEDIA_EVENT_NEW_MEDIA) ||
+ (mediaInfo->MediaEvent == NOTIFICATION_MEDIA_EVENT_MEDIA_CHANGE)) {
+
+
+ if (TEST_FLAG(FdoExtension->DeviceObject->Characteristics,
+ FILE_REMOVABLE_MEDIA) &&
+ (ClassGetVpb(FdoExtension->DeviceObject) != NULL) &&
+ (ClassGetVpb(FdoExtension->DeviceObject)->Flags & VPB_MOUNTED)
+ ) {
+
+ SET_FLAG(FdoExtension->DeviceObject->Flags, DO_VERIFY_VOLUME);
+
+ }
+ InterlockedIncrement((volatile LONG *)&FdoExtension->MediaChangeCount);
+ ClasspSetMediaChangeStateEx(FdoExtension,
+ MediaPresent,
+ FALSE,
+ TRUE);
+
+ } else if (mediaInfo->MediaEvent == NOTIFICATION_MEDIA_EVENT_MEDIA_REMOVAL) {
+
+ ClasspSetMediaChangeStateEx(FdoExtension,
+ MediaNotPresent,
+ FALSE,
+ TRUE);
+
+ } else if (mediaInfo->MediaEvent == NOTIFICATION_MEDIA_EVENT_EJECT_REQUEST) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "Classpnp => GESN Ejection request received!\n"));
+ ClassSendEjectionNotification(FdoExtension);
+
+ }
+ break;
+
+ }
+
+ case NOTIFICATION_DEVICE_BUSY_CLASS_EVENTS: { // lowest priority events...
+
+ PNOTIFICATION_BUSY_STATUS busyInfo =
+ (PNOTIFICATION_BUSY_STATUS)(Header->ClassEventData);
+ DEVICE_EVENT_BECOMING_READY busyData = {0};
+
+ //
+ // NOTE: we never actually need to immediately retry for these
+ // events: if one exists, the device is busy, and if not,
+ // we still don't want to retry.
+ //
+
+ *ResendImmediately = FALSE;
+
+ //
+ // else we want to report the approximated time till it's ready.
+ //
+
+ busyData.Version = 1;
+ busyData.Reason = busyInfo->DeviceBusyStatus;
+ busyData.Estimated100msToReady = (busyInfo->Time[0] << 8) |
+ (busyInfo->Time[1] & 0xff);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "Classpnp => GESN::BUSY: Event: %x Status %x Time %x\n",
+ busyInfo->DeviceBusyEvent, busyInfo->DeviceBusyStatus,
+ busyData.Estimated100msToReady
+ ));
+
+ //
+ // Ignore the notification if the time is small
+ //
+ if (busyData.Estimated100msToReady < GESN_DEVICE_BUSY_LOWER_THRESHOLD_100_MS) {
+ break;
+ }
+
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClasspInterpretGesnData: media BECOMING_READY"));
+ ClassSendNotification(FdoExtension,
+ &GUID_IO_DEVICE_BECOMING_READY,
+ sizeof(DEVICE_EVENT_BECOMING_READY),
+ &busyData);
+ break;
+ }
+
+ default: {
+
+ break;
+
+ }
+
+ } // end switch on notification class
+ return status;
+}
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClasspInternalSetMediaChangeState()
+
+Routine Description:
+
+ This routine will (if appropriate) set the media change event for the
+ device. The event will be set if the media state is changed and
+ media change events are enabled. Otherwise the media state will be
+ tracked but the event will not be set.
+
+ This routine will lock out the other media change routines if possible
+ but if not a media change notification may be lost after the enable has
+ been completed.
+
+Arguments:
+
+ FdoExtension - the device
+
+ MediaPresent - indicates whether the device has media inserted into it
+ (TRUE) or not (FALSE).
+
+Return Value:
+
+ none
+
+--*/
+VOID
+ClasspInternalSetMediaChangeState(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ IN MEDIA_CHANGE_DETECTION_STATE NewState,
+ IN BOOLEAN KnownStateChange // can ignore oldstate == unknown
+ )
+{
+#if DBG
+ PCSZ states[] = {"Unknown", "Present", "Not Present", "Unavailable"};
+#endif
+ MEDIA_CHANGE_DETECTION_STATE oldMediaState;
+ PMEDIA_CHANGE_DETECTION_INFO info = FdoExtension->MediaChangeDetectionInfo;
+ CLASS_MEDIA_CHANGE_CONTEXT mcnContext;
+
+ if (!((NewState >= MediaUnknown) && (NewState <= MediaUnavailable))) {
+ return;
+ }
+
+ if(info == NULL) {
+ return;
+ }
+
+ oldMediaState = InterlockedExchange(
+ (PLONG)(&info->MediaChangeDetectionState),
+ (LONG)NewState);
+
+ if((oldMediaState == MediaUnknown) && (!KnownStateChange)) {
+
+ //
+ // The media was in an indeterminate state before - don't notify for
+ // this change.
+ //
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "ClassSetMediaChangeState: State was unknown - this may "
+ "not be a change\n"));
+ return;
+
+ } else if(oldMediaState == NewState) {
+
+ //
+ // Media is in the same state it was before.
+ //
+
+ return;
+ }
+
+ if(info->MediaChangeDetectionDisableCount != 0) {
+#if DBG
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "ClassSetMediaChangeState: MCN not enabled, state "
+ "changed from %s to %s\n",
+ states[oldMediaState], states[NewState]));
+#endif
+ return;
+
+ }
+#if DBG
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "ClassSetMediaChangeState: State change from %s to %s\n",
+ states[oldMediaState], states[NewState]));
+#endif
+
+ //
+ // make the data useful -- it used to always be zero.
+ //
+ mcnContext.MediaChangeCount = FdoExtension->MediaChangeCount;
+ mcnContext.NewState = NewState;
+
+ if (NewState == MediaPresent) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClasspInternalSetMediaChangeState: media ARRIVAL"));
+ ClassSendNotification(FdoExtension,
+ &GUID_IO_MEDIA_ARRIVAL,
+ sizeof(CLASS_MEDIA_CHANGE_CONTEXT),
+ &mcnContext);
+
+ }
+ else if ((NewState == MediaNotPresent) || (NewState == MediaUnavailable)) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClasspInternalSetMediaChangeState: media REMOVAL"));
+ ClassSendNotification(FdoExtension,
+ &GUID_IO_MEDIA_REMOVAL,
+ sizeof(CLASS_MEDIA_CHANGE_CONTEXT),
+ &mcnContext);
+
+ } else {
+
+ //
+ // Don't notify of changed going to unknown.
+ //
+
+ return;
+ }
+
+ return;
+} // end ClasspInternalSetMediaChangeState()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassSetMediaChangeState()
+
+Routine Description:
+
+ This routine will (if appropriate) set the media change event for the
+ device. The event will be set if the media state is changed and
+ media change events are enabled. Otherwise the media state will be
+ tracked but the event will not be set.
+
+ This routine will lock out the other media change routines if possible
+ but if not a media change notification may be lost after the enable has
+ been completed.
+
+Arguments:
+
+ FdoExtension - the device
+
+ MediaPresent - indicates whether the device has media inserted into it
+ (TRUE) or not (FALSE).
+
+ Wait - indicates whether the function should wait until it can acquire
+ the synchronization lock or not.
+
+Return Value:
+
+ none
+
+--*/
+
+VOID
+#pragma prefast(suppress:26165, "The mutex won't be acquired in the case of a timeout.")
+ClasspSetMediaChangeStateEx(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ IN MEDIA_CHANGE_DETECTION_STATE NewState,
+ IN BOOLEAN Wait,
+ IN BOOLEAN KnownStateChange // can ignore oldstate == unknown
+ )
+{
+ PMEDIA_CHANGE_DETECTION_INFO info = FdoExtension->MediaChangeDetectionInfo;
+ LARGE_INTEGER zero;
+ NTSTATUS status;
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "> ClasspSetMediaChangeStateEx"));
+
+ //
+ // Reset SMART status on media removal as the old status may not be
+ // valid when there is no media in the device or when new media is
+ // inserted.
+ //
+
+ if (NewState == MediaNotPresent) {
+
+ FdoExtension->FailurePredicted = FALSE;
+ FdoExtension->FailureReason = 0;
+
+ }
+
+
+ zero.QuadPart = 0;
+
+ if(info == NULL) {
+ return;
+ }
+
+ status = KeWaitForMutexObject(&info->MediaChangeMutex,
+ Executive,
+ KernelMode,
+ FALSE,
+ ((Wait == TRUE) ? NULL : &zero));
+
+ if(status == STATUS_TIMEOUT) {
+
+ //
+ // Someone else is in the process of setting the media state
+ //
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN, "ClasspSetMediaChangeStateEx - timed out waiting for mutex"));
+ return;
+ }
+
+ //
+ // Change the media present state and signal an event, if applicable
+ //
+
+ ClasspInternalSetMediaChangeState(FdoExtension, NewState, KnownStateChange);
+
+ KeReleaseMutex(&info->MediaChangeMutex, FALSE);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "< ClasspSetMediaChangeStateEx"));
+
+ return;
+} // end ClassSetMediaChangeStateEx()
+
+_IRQL_requires_max_(PASSIVE_LEVEL)
+VOID
+ClassSetMediaChangeState(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ _In_ MEDIA_CHANGE_DETECTION_STATE NewState,
+ _In_ BOOLEAN Wait
+ )
+{
+ ClasspSetMediaChangeStateEx(FdoExtension, NewState, Wait, FALSE);
+ return;
+}
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClasspMediaChangeDetectionCompletion()
+
+Routine Description:
+
+ This routine handles the completion of the test unit ready irps used to
+ determine if the media has changed. If the media has changed, this code
+ signals the named event to wake up other system services that react to
+ media change (aka AutoPlay).
+
+Arguments:
+
+ DeviceObject - the object for the completion
+ Irp - the IRP being completed
+ Context - the SRB from the IRP
+
+Return Value:
+
+ NTSTATUS
+
+--*/
+NTSTATUS
+ClasspMediaChangeDetectionCompletion(
+ PDEVICE_OBJECT DeviceObject,
+ PIRP Irp,
+ PVOID Context
+ )
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData;
+ PMEDIA_CHANGE_DETECTION_INFO info;
+ NTSTATUS status;
+ BOOLEAN retryImmediately = FALSE;
+ PSTORAGE_REQUEST_BLOCK_HEADER Srb = (PSTORAGE_REQUEST_BLOCK_HEADER) Context;
+
+ _Analysis_assume_(Srb != NULL);
+
+ //
+ // Since the class driver created this request, it's completion routine
+ // will not get a valid device object handed in. Use the one in the
+ // irp stack instead
+ //
+
+ DeviceObject = IoGetCurrentIrpStackLocation(Irp)->DeviceObject;
+ fdoExtension = DeviceObject->DeviceExtension;
+ fdoData = fdoExtension->PrivateFdoData;
+ info = fdoExtension->MediaChangeDetectionInfo;
+
+ NT_ASSERT(info->MediaChangeIrp != NULL);
+ NT_ASSERT(!TEST_FLAG(Srb->SrbStatus, SRB_STATUS_QUEUE_FROZEN));
+ TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN, "> ClasspMediaChangeDetectionCompletion: Device %p completed MCN irp %p.", DeviceObject, Irp));
+
+ /*
+ * HACK for IoMega 2GB Jaz drive:
+ * This drive spins down on its own to preserve the media.
+ * When spun down, TUR fails with 2/4/0 (SCSI_SENSE_NOT_READY/SCSI_ADSENSE_LUN_NOT_READY/?).
+ * InterpretSenseInfo routine would then call ClassSendStartUnit to spin the media up, which defeats the
+ * purpose of the spindown.
+ * So in this case, make this into a successful TUR.
+ * This allows the drive to stay spun down until it is actually accessed again.
+ * (If the media were actually removed, TUR would fail with 2/3a/0 ).
+ * This hack only applies to drives with the CAUSE_NOT_REPORTABLE_HACK bit set; this
+ * is set by disk.sys when HackCauseNotReportableHack is set for the drive in its BadControllers list.
+ */
+
+ if ((SRB_STATUS(Srb->SrbStatus) != SRB_STATUS_SUCCESS) &&
+ TEST_FLAG(fdoExtension->ScanForSpecialFlags, CLASS_SPECIAL_CAUSE_NOT_REPORTABLE_HACK)) {
+
+ PVOID senseData = SrbGetSenseInfoBuffer(Srb);
+
+ if (senseData) {
+
+ BOOLEAN validSense = TRUE;
+ UCHAR senseInfoBufferLength = SrbGetSenseInfoBufferLength(Srb);
+ UCHAR senseKey = 0;
+ UCHAR additionalSenseCode = 0;
+
+ validSense = ScsiGetSenseKeyAndCodes(senseData,
+ senseInfoBufferLength,
+ SCSI_SENSE_OPTIONS_FIXED_FORMAT_IF_UNKNOWN_FORMAT_INDICATED,
+ &senseKey,
+ &additionalSenseCode,
+ NULL);
+
+ if (validSense &&
+ senseKey == SCSI_SENSE_NOT_READY &&
+ additionalSenseCode == SCSI_ADSENSE_LUN_NOT_READY) {
+ Srb->SrbStatus = SRB_STATUS_SUCCESS;
+ }
+ }
+ }
+
+ //
+ // use InterpretSenseInfo routine to check for media state, and also
+ // to call ClassError() with correct parameters.
+ //
+ status = STATUS_SUCCESS;
+ if (SRB_STATUS(Srb->SrbStatus) != SRB_STATUS_SUCCESS) {
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN, "ClasspMediaChangeDetectionCompletion - failed - srb status=%s, sense=%s/%s/%s.",
+ DBGGETSRBSTATUSSTR(Srb), DBGGETSENSECODESTR(Srb), DBGGETADSENSECODESTR(Srb), DBGGETADSENSEQUALIFIERSTR(Srb)));
+
+ InterpretSenseInfoWithoutHistory(DeviceObject,
+ Irp,
+ (PSCSI_REQUEST_BLOCK)Srb,
+ IRP_MJ_SCSI,
+ 0,
+ 0,
+ &status,
+ NULL);
+ }
+ else {
+
+ fdoData->LoggedTURFailureSinceLastIO = FALSE;
+
+ if (!info->Gesn.Supported) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClasspMediaChangeDetectionCompletion - succeeded and GESN NOT supported, setting MediaPresent."));
+
+ //
+ // success != media for GESN case
+ //
+
+ ClassSetMediaChangeState(fdoExtension, MediaPresent, FALSE);
+
+ }
+ else {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClasspMediaChangeDetectionCompletion - succeeded (GESN supported)."));
+ }
+ }
+
+ if (info->Gesn.Supported) {
+
+ if (status == STATUS_DATA_OVERRUN) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClasspMediaChangeDetectionCompletion - Overrun"));
+ status = STATUS_SUCCESS;
+ }
+
+ if (!NT_SUCCESS(status)) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClasspMediaChangeDetectionCompletion: GESN failed with status %x", status));
+ } else {
+
+ //
+ // for GESN, need to interpret the results of the data.
+ // this may also require an immediate retry
+ //
+
+ if (Irp->IoStatus.Information == 8 ) {
+ ClasspInterpretGesnData(fdoExtension,
+ (PVOID)info->Gesn.Buffer,
+ &retryImmediately);
+ }
+
+ } // end of NT_SUCCESS(status)
+
+ } // end of Info->Gesn.Supported
+
+ //
+ // free port-allocated sense buffer, if any.
+ //
+
+ if (PORT_ALLOCATED_SENSE_EX(fdoExtension, Srb)) {
+ FREE_PORT_ALLOCATED_SENSE_BUFFER_EX(fdoExtension, Srb);
+ }
+
+ //
+ // Remember the IRP and SRB for use the next time.
+ //
+
+ NT_ASSERT(IoGetNextIrpStackLocation(Irp));
+ IoGetNextIrpStackLocation(Irp)->Parameters.Scsi.Srb = (PSCSI_REQUEST_BLOCK)Srb;
+
+ //
+ // Reset the MCN timer.
+ //
+
+ ClassResetMediaChangeTimer(fdoExtension);
+
+ //
+ // run a sanity check to make sure we're not recursing continuously
+ //
+
+ if (retryImmediately) {
+
+ info->MediaChangeRetryCount++;
+
+ if (info->MediaChangeRetryCount > MAXIMUM_IMMEDIATE_MCN_RETRIES) {
+
+ //
+ // Disable GESN on this device.
+ // Create a work item to set the value in the registry
+ //
+
+ PIO_WORKITEM workItem;
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClasspMediaChangeDetectionCompletion: Disabling GESN for device %p", DeviceObject));
+
+ workItem = IoAllocateWorkItem(DeviceObject);
+
+ if (workItem) {
+ IoQueueWorkItem(workItem, ClasspDisableGesn, DelayedWorkQueue, workItem);
+ }
+
+ info->Gesn.Supported = 0;
+ info->Gesn.EventMask = 0;
+ info->Gesn.BufferSize = 0;
+ info->MediaChangeRetryCount = 0;
+ retryImmediately = FALSE;
+ }
+
+ } else {
+
+ info->MediaChangeRetryCount = 0;
+
+ }
+
+
+ //
+ // release the remove lock....
+ //
+
+ {
+ UCHAR uniqueValue = 0;
+ ClassAcquireRemoveLock(DeviceObject, (PVOID)(&uniqueValue));
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+
+
+ //
+ // set the irp as not in use
+ //
+ {
+#if DBG
+ volatile LONG irpWasInUse;
+ irpWasInUse = InterlockedCompareExchange(&info->MediaChangeIrpInUse, 0, 1);
+ #if _MSC_FULL_VER != 13009111 // This compiler always takes the wrong path here.
+ NT_ASSERT(irpWasInUse);
+ #endif
+#else
+ InterlockedCompareExchange(&info->MediaChangeIrpInUse, 0, 1);
+#endif
+ }
+
+ //
+ // now send it again before we release our last remove lock
+ //
+
+ if (retryImmediately) {
+ ClasspSendMediaStateIrp(fdoExtension, info, 0);
+ }
+ else {
+ TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN, "ClasspMediaChangeDetectionCompletion - not retrying immediately"));
+ }
+
+ //
+ // release the temporary remove lock
+ //
+
+ ClassReleaseRemoveLock(DeviceObject, (PVOID)(&uniqueValue));
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN, "< ClasspMediaChangeDetectionCompletion"));
+
+ return STATUS_MORE_PROCESSING_REQUIRED;
+}
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClasspSendTestUnitIrp() - ISSUE-2000/02/20-henrygab - not documented
+
+Routine Description:
+
+ This routine
+
+Arguments:
+
+ DeviceObject -
+ Irp -
+
+Return Value:
+
+
+--*/
+PIRP
+ClasspPrepareMcnIrp(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ IN PMEDIA_CHANGE_DETECTION_INFO Info,
+ IN BOOLEAN UseGesn
+)
+{
+ PSCSI_REQUEST_BLOCK srb;
+ PSTORAGE_REQUEST_BLOCK srbEx;
+ PIO_STACK_LOCATION irpStack;
+ PIO_STACK_LOCATION nextIrpStack;
+ NTSTATUS status;
+ PCDB cdb;
+ PIRP irp;
+ PVOID buffer;
+ UCHAR bufferLength;
+ ULONG srbFlags;
+ ULONG timeOutValue;
+ UCHAR cdbLength;
+ PVOID dataBuffer;
+ ULONG dataTransferLength;
+
+ //
+ // Setup the IRP to perform a test unit ready.
+ //
+
+ irp = Info->MediaChangeIrp;
+
+ if (irp == NULL) {
+ NT_ASSERT(irp);
+ return NULL;
+ }
+
+ //
+ // don't keep sending this if the device is being removed.
+ //
+
+ status = ClassAcquireRemoveLock(FdoExtension->DeviceObject, irp);
+ if (status == REMOVE_COMPLETE) {
+ NT_ASSERT(status != REMOVE_COMPLETE);
+ return NULL;
+ }
+ else if (status == REMOVE_PENDING) {
+ ClassReleaseRemoveLock(FdoExtension->DeviceObject, irp);
+ return NULL;
+ }
+ else {
+ NT_ASSERT(status == NO_REMOVE);
+ }
+
+ IoReuseIrp(irp, STATUS_NOT_SUPPORTED);
+
+ /*
+ * For the driver that creates an IRP, there is no 'current' stack location.
+ * Step down one IRP stack location so that the extra top one
+ * becomes our 'current' one.
+ */
+ IoSetNextIrpStackLocation(irp);
+
+ /*
+ * Cache our device object in the extra top IRP stack location
+ * so we have it in our completion routine.
+ */
+ irpStack = IoGetCurrentIrpStackLocation(irp);
+ irpStack->DeviceObject = FdoExtension->DeviceObject;
+
+ //
+ // If the irp is sent down when the volume needs to be
+ // verified, CdRomUpdateGeometryCompletion won't complete
+ // it since it's not associated with a thread. Marking
+ // it to override the verify causes it always be sent
+ // to the port driver
+ //
+
+ irpStack->Flags |= SL_OVERRIDE_VERIFY_VOLUME;
+
+ nextIrpStack = IoGetNextIrpStackLocation(irp);
+ nextIrpStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
+ nextIrpStack->Parameters.Scsi.Srb = &(Info->MediaChangeSrb.Srb);
+
+ //
+ // Prepare the SRB for execution.
+ //
+
+ buffer = Info->SenseBuffer;
+ bufferLength = Info->SenseBufferLength;
+
+ NT_ASSERT(bufferLength > 0);
+ RtlZeroMemory(buffer, bufferLength);
+
+ srbFlags = FdoExtension->SrbFlags;
+ SET_FLAG(srbFlags, Info->SrbFlags);
+
+ timeOutValue = FdoExtension->TimeOutValue * 2;
+ if (timeOutValue == 0) {
+
+ if (FdoExtension->TimeOutValue == 0) {
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN,
+ "ClassSendTestUnitIrp: FdoExtension->TimeOutValue "
+ "is set to zero?! -- resetting to 10\n"));
+ timeOutValue = 10 * 2; // reasonable default
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN,
+ "ClassSendTestUnitIrp: Someone set "
+ "srb->TimeOutValue to zero?! -- resetting to %x\n",
+ FdoExtension->TimeOutValue * 2));
+ timeOutValue = FdoExtension->TimeOutValue * 2;
+
+ }
+
+ }
+
+ if (!UseGesn) {
+ nextIrpStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_SCSI_EXECUTE_NONE;
+ irp->MdlAddress = NULL;
+
+ SET_FLAG(srbFlags, SRB_FLAGS_NO_DATA_TRANSFER);
+
+ //
+ // Set SRB_FLAGS_NO_KEEP_AWAKE for non-cdrom devices if these requests should
+ // not prevent devices from going to sleep.
+ //
+ if ((FdoExtension->DeviceObject->DeviceType != FILE_DEVICE_CD_ROM) &&
+ (ClasspScreenOff == TRUE)) {
+ SET_FLAG(srbFlags, SRB_FLAGS_NO_KEEP_AWAKE);
+ }
+
+ cdbLength = 6;
+ dataBuffer = NULL;
+ dataTransferLength = 0;
+
+ } else {
+ NT_ASSERT(Info->Gesn.Buffer);
+
+ nextIrpStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_SCSI_EXECUTE_IN;
+ irp->MdlAddress = Info->Gesn.Mdl;
+
+ SET_FLAG(srbFlags, SRB_FLAGS_DATA_IN);
+ cdbLength = 10;
+ dataBuffer = Info->Gesn.Buffer;
+ dataTransferLength = Info->Gesn.BufferSize;
+ timeOutValue = GESN_TIMEOUT_VALUE; // much shorter timeout for GESN
+
+ }
+
+ //
+ // SRB used here is the MediaChangeSrb in _MEDIA_CHANGE_DETECTION_INFO.
+ //
+ srb = nextIrpStack->Parameters.Scsi.Srb;
+ if (FdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+ srbEx = (PSTORAGE_REQUEST_BLOCK)nextIrpStack->Parameters.Scsi.Srb;
+
+ status = InitializeStorageRequestBlock(srbEx,
+ STORAGE_ADDRESS_TYPE_BTL8,
+ CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE,
+ 1,
+ SrbExDataTypeScsiCdb16);
+ if (!NT_SUCCESS(status)) {
+ // should not happen
+ NT_ASSERT(FALSE);
+ return NULL;
+ }
+
+ srbEx->RequestTag = SP_UNTAGGED;
+ srbEx->RequestAttribute = SRB_SIMPLE_TAG_REQUEST;
+ srbEx->SrbFunction = SRB_FUNCTION_EXECUTE_SCSI;
+ srbEx->SrbStatus = 0;
+ srbEx->OriginalRequest = irp;
+ srbEx->SrbFlags = srbFlags;
+ srbEx->TimeOutValue = timeOutValue;
+ srbEx->DataBuffer = dataBuffer;
+ srbEx->DataTransferLength = dataTransferLength;
+
+ SrbSetScsiStatus(srbEx, 0);
+ SrbSetSenseInfoBuffer(srbEx, buffer);
+ SrbSetSenseInfoBufferLength(srbEx, bufferLength);
+ SrbSetCdbLength(srbEx, cdbLength);
+
+ cdb = SrbGetCdb(srbEx);
+
+ } else {
+ RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK));
+
+ srb->QueueTag = SP_UNTAGGED;
+ srb->QueueAction = SRB_SIMPLE_TAG_REQUEST;
+ srb->Length = sizeof(SCSI_REQUEST_BLOCK);
+ srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
+ srb->SenseInfoBuffer = buffer;
+ srb->SenseInfoBufferLength = bufferLength;
+ srb->SrbStatus = 0;
+ srb->ScsiStatus = 0;
+ srb->OriginalRequest = irp;
+
+ srb->SrbFlags = srbFlags;
+ srb->TimeOutValue = timeOutValue;
+ srb->CdbLength = cdbLength;
+ srb->DataBuffer = dataBuffer;
+ srb->DataTransferLength = dataTransferLength;
+
+ cdb = (PCDB) &srb->Cdb[0];
+
+ }
+
+ if (cdb) {
+ if (!UseGesn) {
+ cdb->CDB6GENERIC.OperationCode = SCSIOP_TEST_UNIT_READY;
+ } else {
+ cdb->GET_EVENT_STATUS_NOTIFICATION.OperationCode =
+ SCSIOP_GET_EVENT_STATUS;
+ cdb->GET_EVENT_STATUS_NOTIFICATION.Immediate = 1;
+ cdb->GET_EVENT_STATUS_NOTIFICATION.EventListLength[0] =
+ (UCHAR)((Info->Gesn.BufferSize) >> 8);
+ cdb->GET_EVENT_STATUS_NOTIFICATION.EventListLength[1] =
+ (UCHAR)((Info->Gesn.BufferSize) & 0xff);
+ cdb->GET_EVENT_STATUS_NOTIFICATION.NotificationClassRequest =
+ Info->Gesn.EventMask;
+ }
+ }
+
+ IoSetCompletionRoutine(irp,
+ ClasspMediaChangeDetectionCompletion,
+ srb,
+ TRUE,
+ TRUE,
+ TRUE);
+
+ return irp;
+
+}
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClasspSendMediaStateIrp() - ISSUE-2000/02/20-henrygab - not documented
+
+Routine Description:
+
+ This routine
+
+Arguments:
+
+ DeviceObject -
+ Irp -
+
+Return Value:
+
+--*/
+VOID
+ClasspSendMediaStateIrp(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ IN PMEDIA_CHANGE_DETECTION_INFO Info,
+ IN ULONG CountDown
+ )
+{
+ BOOLEAN requestPending = FALSE;
+ LONG irpInUse;
+
+ TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN, "> ClasspSendMediaStateIrp"));
+
+ if (((FdoExtension->CommonExtension.CurrentState != IRP_MN_START_DEVICE) ||
+ (FdoExtension->DevicePowerState != PowerDeviceD0)
+ ) &&
+ (!Info->MediaChangeIrpLost)) {
+
+ //
+ // the device may be stopped, powered down, or otherwise queueing io,
+ // so should not timeout the autorun irp (yet) -- set to zero ticks.
+ // scattered code relies upon this to not prematurely "lose" an
+ // autoplay irp that was queued.
+ //
+
+ Info->MediaChangeIrpTimeInUse = 0;
+ }
+
+ //
+ // if the irp is not in use, mark it as such.
+ //
+
+ irpInUse = InterlockedCompareExchange(&Info->MediaChangeIrpInUse, 1, 0);
+
+ if (irpInUse) {
+
+ LONG timeInUse;
+
+ timeInUse = InterlockedIncrement(&Info->MediaChangeIrpTimeInUse);
+
+ TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN, "ClasspSendMediaStateIrp: irp in use for "
+ "%x seconds when synchronizing for MCD\n", timeInUse));
+
+ if (Info->MediaChangeIrpLost == FALSE) {
+
+ if (timeInUse > MEDIA_CHANGE_TIMEOUT_TIME) {
+
+ //
+ // currently set to five minutes. hard to imagine a drive
+ // taking that long to spin up.
+ //
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN,
+ "CdRom%d: Media Change Notification has lost "
+ "it's irp and doesn't know where to find it. "
+ "Leave it alone and it'll come home dragging "
+ "it's stack behind it.\n",
+ FdoExtension->DeviceNumber));
+ Info->MediaChangeIrpLost = TRUE;
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN, "< ClasspSendMediaStateIrp - irpInUse"));
+ return;
+
+ }
+
+ TRY {
+
+ if (Info->MediaChangeDetectionDisableCount != 0) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClassCheckMediaState: device %p has "
+ " detection disabled \n", FdoExtension->DeviceObject));
+ LEAVE;
+ }
+
+ if (FdoExtension->DevicePowerState != PowerDeviceD0) {
+
+ //
+ // It's possible that the device went to D3 while the screen was
+ // off so we need to make sure that we send the IRP regardless
+ // of the device's power state in order to wake the device back
+ // up when the screen comes back on.
+ // When the screen is off we set the SRB_FLAG_NO_KEEP_AWAKE flag
+ // so that the lower driver does not power-up the device for this
+ // request. When the screen comes back on, however, we want to
+ // resume checking for media presence so we no longer set the flag.
+ // When the device is in D3 we also stop the polling timer as well.
+ //
+
+ //
+ // NOTE: we don't increment the time in use until our power state
+ // changes above. this way, we won't "lose" the autoplay irp.
+ // it's up to the lower driver to determine if powering up is a
+ // good idea.
+ //
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "ClassCheckMediaState: device %p needs to powerup "
+ "to handle this io (may take a few extra seconds).\n",
+ FdoExtension->DeviceObject));
+ }
+
+ Info->MediaChangeIrpTimeInUse = 0;
+ Info->MediaChangeIrpLost = FALSE;
+
+ if (CountDown == 0) {
+
+ PIRP irp;
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "ClassCheckMediaState: timer expired\n"));
+
+ if (Info->MediaChangeDetectionDisableCount != 0) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "ClassCheckMediaState: detection disabled\n"));
+ LEAVE;
+ }
+
+ //
+ // Prepare the IRP for the test unit ready
+ //
+
+ irp = ClasspPrepareMcnIrp(FdoExtension,
+ Info,
+ Info->Gesn.Supported);
+
+ //
+ // Issue the request.
+ //
+
+ TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN,
+ "ClasspSendMediaStateIrp: Device %p getting TUR "
+ " irp %p\n", FdoExtension->DeviceObject, irp));
+
+ if (irp == NULL) {
+ LEAVE;
+ }
+
+
+ //
+ // note: if we send it to the class dispatch routines, there is
+ // a timing window here (since they grab the remove lock)
+ // where we'd be removed. ELIMINATE the window by grabbing
+ // the lock ourselves above and sending it to the lower
+ // device object directly or to the device's StartIo
+ // routine (which doesn't acquire the lock).
+ //
+
+ requestPending = TRUE;
+
+ TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN, " ClasspSendMediaStateIrp - calling IoCallDriver."));
+ IoCallDriver(FdoExtension->CommonExtension.LowerDeviceObject, irp);
+ }
+
+ } FINALLY {
+
+ if(requestPending == FALSE) {
+#if DBG
+ irpInUse = InterlockedCompareExchange(&Info->MediaChangeIrpInUse, 0, 1);
+ #if _MSC_FULL_VER != 13009111 // This compiler always takes the wrong path here.
+ NT_ASSERT(irpInUse);
+ #endif
+#else
+ InterlockedCompareExchange(&Info->MediaChangeIrpInUse, 0, 1);
+#endif
+ }
+
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN, "< ClasspSendMediaStateIrp"));
+
+ return;
+} // end ClasspSendMediaStateIrp()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassCheckMediaState()
+
+Routine Description:
+
+ This routine is called by the class driver to test for a media change
+ condition and/or poll for disk failure prediction. It should be called
+ from the class driver's IO timer routine once per second.
+
+Arguments:
+
+ FdoExtension - the device extension
+
+Return Value:
+
+ none
+
+--*/
+VOID
+ClassCheckMediaState(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ )
+{
+ PMEDIA_CHANGE_DETECTION_INFO info = FdoExtension->MediaChangeDetectionInfo;
+ LONG countDown;
+
+ if(info == NULL) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "ClassCheckMediaState: detection not enabled\n"));
+ return;
+ }
+
+ //
+ // Media change support is active and the IRP is waiting. Decrement the
+ // timer. There is no MP protection on the timer counter. This code
+ // is the only code that will manipulate the timer counter and only one
+ // instance of it should be running at any given time.
+ //
+
+ countDown = InterlockedDecrement(&(info->MediaChangeCountDown));
+
+ //
+ // Try to acquire the media change event. If we can't do it immediately
+ // then bail out and assume the caller will try again later.
+ //
+ ClasspSendMediaStateIrp(FdoExtension,
+ info,
+ countDown);
+
+ return;
+} // end ClassCheckMediaState()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassResetMediaChangeTimer()
+
+Routine Description:
+
+ Resets the media change count down timer to the default number of seconds.
+
+Arguments:
+
+ FdoExtension - the device to reset the timer for
+
+Return Value:
+
+ None
+
+--*/
+VOID
+ClassResetMediaChangeTimer(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ )
+{
+ PMEDIA_CHANGE_DETECTION_INFO info = FdoExtension->MediaChangeDetectionInfo;
+
+ if(info != NULL) {
+ InterlockedExchange(&(info->MediaChangeCountDown),
+ MEDIA_CHANGE_DEFAULT_TIME);
+ }
+ return;
+} // end ClassResetMediaChangeTimer()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClasspInitializePolling() - ISSUE-2000/02/20-henrygab - not documented
+
+Routine Description:
+
+ This routine
+
+Arguments:
+
+ DeviceObject -
+ Irp -
+
+Return Value:
+
+--*/
+NTSTATUS
+ClasspInitializePolling(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ IN BOOLEAN AllowDriveToSleep
+ )
+{
+ PDEVICE_OBJECT fdo = FdoExtension->DeviceObject;
+
+ PMEDIA_CHANGE_DETECTION_INFO info;
+ PIRP irp;
+
+ PAGED_CODE();
+
+ if (FdoExtension->MediaChangeDetectionInfo != NULL) {
+ return STATUS_SUCCESS;
+ }
+
+ info = ExAllocatePoolWithTag(NonPagedPoolNx,
+ sizeof(MEDIA_CHANGE_DETECTION_INFO),
+ CLASS_TAG_MEDIA_CHANGE_DETECTION);
+
+ if (info != NULL) {
+ RtlZeroMemory(info, sizeof(MEDIA_CHANGE_DETECTION_INFO));
+
+ FdoExtension->KernelModeMcnContext.FileObject = (PVOID)-1;
+ FdoExtension->KernelModeMcnContext.DeviceObject = (PVOID)-1;
+ FdoExtension->KernelModeMcnContext.LockCount = 0;
+ FdoExtension->KernelModeMcnContext.McnDisableCount = 0;
+
+ /*
+ * Allocate an IRP to carry the Test-Unit-Ready.
+ * Allocate an extra IRP stack location
+ * so we can cache our device object in the top location.
+ */
+ irp = IoAllocateIrp((CCHAR)(fdo->StackSize+1), FALSE);
+
+ if (irp != NULL) {
+
+ PVOID buffer;
+ BOOLEAN GesnSupported = FALSE;
+
+ buffer = ExAllocatePoolWithTag(
+ NonPagedPoolNxCacheAligned,
+ SENSE_BUFFER_SIZE_EX,
+ CLASS_TAG_MEDIA_CHANGE_DETECTION);
+
+ if (buffer != NULL) {
+
+ info->MediaChangeIrp = irp;
+ info->SenseBuffer = buffer;
+ info->SenseBufferLength = SENSE_BUFFER_SIZE_EX;
+
+ //
+ // Set default values for the media change notification
+ // configuration.
+ //
+
+ info->MediaChangeCountDown = MEDIA_CHANGE_DEFAULT_TIME;
+ info->MediaChangeDetectionDisableCount = 0;
+
+ //
+ // Assume that there is initially no media in the device
+ // only notify upper layers if there is something there
+ //
+
+ info->MediaChangeDetectionState = MediaUnknown;
+
+ info->MediaChangeIrpTimeInUse = 0;
+ info->MediaChangeIrpLost = FALSE;
+
+ //
+ // setup all extra flags we'll be setting for this irp
+ //
+ info->SrbFlags = 0;
+ if (AllowDriveToSleep) {
+ SET_FLAG(info->SrbFlags, SRB_FLAGS_NO_KEEP_AWAKE);
+ }
+ SET_FLAG(info->SrbFlags, SRB_CLASS_FLAGS_LOW_PRIORITY);
+ SET_FLAG(info->SrbFlags, SRB_FLAGS_NO_QUEUE_FREEZE);
+ SET_FLAG(info->SrbFlags, SRB_FLAGS_DISABLE_SYNCH_TRANSFER);
+
+ KeInitializeMutex(&info->MediaChangeMutex, 0x100);
+
+ //
+ // It is ok to support media change events on this
+ // device.
+ //
+
+ FdoExtension->MediaChangeDetectionInfo = info;
+
+ //
+ // NOTE: the DeviceType is FILE_DEVICE_CD_ROM even
+ // when the device supports DVD (no need to
+ // check for FILE_DEVICE_DVD, as it's not a
+ // valid check).
+ //
+
+ if (FdoExtension->DeviceObject->DeviceType == FILE_DEVICE_CD_ROM) {
+
+ NTSTATUS status;
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "ClasspInitializePolling: Testing for GESN\n"));
+ status = ClasspInitializeGesn(FdoExtension, info);
+ if (NT_SUCCESS(status)) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "ClasspInitializePolling: GESN available "
+ "for %p\n", FdoExtension->DeviceObject));
+ NT_ASSERT(info->Gesn.Supported );
+ NT_ASSERT(info->Gesn.Buffer != NULL);
+ NT_ASSERT(info->Gesn.BufferSize != 0);
+ NT_ASSERT(info->Gesn.EventMask != 0);
+ GesnSupported = TRUE;
+ } else {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "ClasspInitializePolling: GESN *NOT* available "
+ "for %p\n", FdoExtension->DeviceObject));
+ }
+ }
+
+ if (GesnSupported == FALSE) {
+ NT_ASSERT(info->Gesn.Supported == 0);
+ NT_ASSERT(info->Gesn.Buffer == NULL);
+ NT_ASSERT(info->Gesn.BufferSize == 0);
+ NT_ASSERT(info->Gesn.EventMask == 0);
+ info->Gesn.Supported = 0; // just in case....
+ }
+
+ //
+ // Register for screen state notification. Will use this to
+ // determine user presence.
+ //
+ if (ScreenStateNotificationHandle == NULL) {
+ PoRegisterPowerSettingCallback(fdo,
+ &GUID_CONSOLE_DISPLAY_STATE,
+ &ClasspPowerSettingCallback,
+ NULL,
+ &ScreenStateNotificationHandle);
+ }
+
+ return STATUS_SUCCESS;
+ }
+
+ IoFreeIrp(irp);
+ }
+
+ FREE_POOL(info);
+ }
+
+ //
+ // nothing to free here
+ //
+ return STATUS_INSUFFICIENT_RESOURCES;
+
+} // end ClasspInitializePolling()
+
+NTSTATUS
+ClasspInitializeGesn(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ IN PMEDIA_CHANGE_DETECTION_INFO Info
+ )
+{
+ PNOTIFICATION_EVENT_STATUS_HEADER header;
+ CLASS_DETECTION_STATE detectionState = ClassDetectionUnknown;
+ PSTORAGE_DEVICE_DESCRIPTOR deviceDescriptor;
+ NTSTATUS status = STATUS_NOT_SUPPORTED;
+ PIRP irp;
+ KEVENT event;
+ BOOLEAN retryImmediately;
+ ULONG i;
+ ULONG atapiResets;
+ ULONG srbFlags;
+
+ PAGED_CODE();
+ NT_ASSERT(Info == FdoExtension->MediaChangeDetectionInfo);
+
+ //
+ // read if we already know the abilities of the device
+ //
+
+ ClassGetDeviceParameter(FdoExtension,
+ CLASSP_REG_SUBKEY_NAME,
+ CLASSP_REG_MMC_DETECTION_VALUE_NAME,
+ (PULONG)&detectionState);
+
+ if (detectionState == ClassDetectionUnsupported) {
+ goto ExitWithError;
+ }
+
+ //
+ // check if the device has a hack flag saying never to try this.
+ //
+
+ if (TEST_FLAG(FdoExtension->PrivateFdoData->HackFlags,
+ FDO_HACK_GESN_IS_BAD)) {
+
+ ClassSetDeviceParameter(FdoExtension,
+ CLASSP_REG_SUBKEY_NAME,
+ CLASSP_REG_MMC_DETECTION_VALUE_NAME,
+ ClassDetectionUnsupported);
+ goto ExitWithError;
+
+ }
+
+
+ //
+ // else go through the process since we allocate buffers and
+ // get all sorts of device settings.
+ //
+
+ if (Info->Gesn.Buffer == NULL) {
+ Info->Gesn.Buffer = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned,
+ GESN_BUFFER_SIZE,
+ '??cS');
+ }
+ if (Info->Gesn.Buffer == NULL) {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto ExitWithError;
+ }
+ if (Info->Gesn.Mdl != NULL) {
+ IoFreeMdl(Info->Gesn.Mdl);
+ }
+ Info->Gesn.Mdl = IoAllocateMdl(Info->Gesn.Buffer,
+ GESN_BUFFER_SIZE,
+ FALSE, FALSE, NULL);
+ if (Info->Gesn.Mdl == NULL) {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto ExitWithError;
+ }
+
+ MmBuildMdlForNonPagedPool(Info->Gesn.Mdl);
+ Info->Gesn.BufferSize = GESN_BUFFER_SIZE;
+ Info->Gesn.EventMask = 0;
+
+ //
+ // all items are prepared to use GESN (except the event mask, so don't
+ // optimize this part out!).
+ //
+ // now see if it really works. we have to loop through this because
+ // many SAMSUNG (and one COMPAQ) drives timeout when requesting
+ // NOT_READY events, even when the IMMEDIATE bit is set. :(
+ //
+ // using a drive list is cumbersome, so this might fix the problem.
+ //
+
+ deviceDescriptor = FdoExtension->DeviceDescriptor;
+ atapiResets = 0;
+ retryImmediately = TRUE;
+ for (i = 0; i < 16 && retryImmediately == TRUE; i++) {
+
+ irp = ClasspPrepareMcnIrp(FdoExtension, Info, TRUE);
+ if (irp == NULL) {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto ExitWithError;
+ }
+
+ if (Info->MediaChangeSrb.Srb.Function == SRB_FUNCTION_STORAGE_REQUEST_BLOCK) {
+ srbFlags = Info->MediaChangeSrb.SrbEx.SrbFlags;
+ } else {
+ srbFlags = Info->MediaChangeSrb.Srb.SrbFlags;
+ }
+ NT_ASSERT(TEST_FLAG(srbFlags, SRB_FLAGS_NO_QUEUE_FREEZE));
+
+ //
+ // replace the completion routine with a different one this time...
+ //
+
+ KeInitializeEvent(&event, SynchronizationEvent, FALSE);
+ IoSetCompletionRoutine(irp,
+ ClassSignalCompletion,
+ &event,
+ TRUE, TRUE, TRUE);
+
+ status = IoCallDriver(FdoExtension->CommonExtension.LowerDeviceObject, irp);
+
+ if (status == STATUS_PENDING) {
+ status = KeWaitForSingleObject(&event,
+ Executive,
+ KernelMode,
+ FALSE,
+ NULL);
+ NT_ASSERT(NT_SUCCESS(status));
+ }
+ ClassReleaseRemoveLock(FdoExtension->DeviceObject, irp);
+
+ if (SRB_STATUS(Info->MediaChangeSrb.Srb.SrbStatus) != SRB_STATUS_SUCCESS) {
+
+ InterpretSenseInfoWithoutHistory(FdoExtension->DeviceObject,
+ irp,
+ &(Info->MediaChangeSrb.Srb),
+ IRP_MJ_SCSI,
+ 0,
+ 0,
+ &status,
+ NULL);
+ }
+
+ if ((deviceDescriptor->BusType == BusTypeAtapi) &&
+ (Info->MediaChangeSrb.Srb.SrbStatus == SRB_STATUS_BUS_RESET)
+ ) {
+
+ //
+ // ATAPI unfortunately returns SRB_STATUS_BUS_RESET instead
+ // of SRB_STATUS_TIMEOUT, so we cannot differentiate between
+ // the two. if we get this status four time consecutively,
+ // stop trying this command. it is too late to change ATAPI
+ // at this point, so special-case this here. (07/10/2001)
+ // NOTE: any value more than 4 may cause the device to be
+ // marked missing.
+ //
+
+ atapiResets++;
+ if (atapiResets >= 4) {
+ status = STATUS_IO_DEVICE_ERROR;
+ goto ExitWithError;
+ }
+ }
+
+ if (status == STATUS_DATA_OVERRUN) {
+ status = STATUS_SUCCESS;
+ }
+
+ if ((status == STATUS_INVALID_DEVICE_REQUEST) ||
+ (status == STATUS_TIMEOUT) ||
+ (status == STATUS_IO_DEVICE_ERROR) ||
+ (status == STATUS_IO_TIMEOUT)
+ ) {
+
+ //
+ // with these error codes, we don't ever want to try this command
+ // again on this device, since it reacts poorly.
+ //
+
+ ClassSetDeviceParameter(FdoExtension,
+ CLASSP_REG_SUBKEY_NAME,
+ CLASSP_REG_MMC_DETECTION_VALUE_NAME,
+ ClassDetectionUnsupported);
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN,
+ "Classpnp => GESN test failed %x for fdo %p\n",
+ status, FdoExtension->DeviceObject));
+ goto ExitWithError;
+
+
+ }
+
+ if (!NT_SUCCESS(status)) {
+
+ //
+ // this may be other errors that should not disable GESN
+ // for all future start_device calls.
+ //
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN,
+ "Classpnp => GESN test failed %x for fdo %p\n",
+ status, FdoExtension->DeviceObject));
+ goto ExitWithError;
+ }
+
+ if (i == 0) {
+
+ //
+ // the first time, the request was just retrieving a mask of
+ // available bits. use this to mask future requests.
+ //
+
+ header = (PNOTIFICATION_EVENT_STATUS_HEADER)(Info->Gesn.Buffer);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "Classpnp => Fdo %p supports event mask %x\n",
+ FdoExtension->DeviceObject, header->SupportedEventClasses));
+
+
+ if (TEST_FLAG(header->SupportedEventClasses,
+ NOTIFICATION_MEDIA_STATUS_CLASS_MASK)) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "Classpnp => GESN supports MCN\n"));
+ }
+ if (TEST_FLAG(header->SupportedEventClasses,
+ NOTIFICATION_DEVICE_BUSY_CLASS_MASK)) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "Classpnp => GESN supports DeviceBusy\n"));
+ }
+ if (TEST_FLAG(header->SupportedEventClasses,
+ NOTIFICATION_OPERATIONAL_CHANGE_CLASS_MASK)) {
+
+ if (TEST_FLAG(FdoExtension->PrivateFdoData->HackFlags,
+ FDO_HACK_GESN_IGNORE_OPCHANGE)) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "Classpnp => GESN supports OpChange, but "
+ "must ignore these events for compatibility\n"));
+ CLEAR_FLAG(header->SupportedEventClasses,
+ NOTIFICATION_OPERATIONAL_CHANGE_CLASS_MASK);
+ } else {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "Classpnp => GESN supports OpChange\n"));
+ }
+ }
+ Info->Gesn.EventMask = header->SupportedEventClasses;
+
+ //
+ // realistically, we are only considering the following events:
+ // EXTERNAL REQUEST - this is being tested for play/stop/etc.
+ // MEDIA STATUS - autorun and ejection requests.
+ // DEVICE BUSY - to allow us to predict when media will be ready.
+ // therefore, we should not bother querying for the other,
+ // unknown events. clear all but the above flags.
+ //
+
+ Info->Gesn.EventMask &=
+ NOTIFICATION_OPERATIONAL_CHANGE_CLASS_MASK |
+ NOTIFICATION_EXTERNAL_REQUEST_CLASS_MASK |
+ NOTIFICATION_MEDIA_STATUS_CLASS_MASK |
+ NOTIFICATION_DEVICE_BUSY_CLASS_MASK ;
+
+
+ //
+ // HACKHACK - REF #0001
+ // Some devices will *never* report an event if we've also requested
+ // that it report lower-priority events. this is due to a
+ // misunderstanding in the specification wherein a "No Change" is
+ // interpreted to be a real event. what should occur is that the
+ // device should ignore "No Change" events when multiple event types
+ // are requested unless there are no other events waiting. this
+ // greatly reduces the number of requests that the host must send
+ // to determine if an event has occurred. Since we must work on all
+ // drives, default to enabling the hack until we find evidence of
+ // proper firmware.
+ //
+ if (Info->Gesn.EventMask == 0) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "Classpnp => GESN supported, but not mask we care "
+ "about (%x) for FDO %p\n",
+ header->SupportedEventClasses,
+ FdoExtension->DeviceObject));
+ goto ExitWithError;
+
+ } else if (CountOfSetBitsUChar(Info->Gesn.EventMask) == 1) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "Classpnp => GESN hack not required for FDO %p\n",
+ FdoExtension->DeviceObject));
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "Classpnp => GESN hack enabled for FDO %p\n",
+ FdoExtension->DeviceObject));
+ Info->Gesn.HackEventMask = 1;
+
+ }
+
+ } else {
+
+ //
+ // not the first time looping through, so interpret the results.
+ //
+
+ status = ClasspInterpretGesnData(FdoExtension,
+ (PVOID)Info->Gesn.Buffer,
+ &retryImmediately);
+
+ if (!NT_SUCCESS(status)) {
+
+ //
+ // This drive does not support GESN correctly
+ //
+
+ ClassSetDeviceParameter(FdoExtension,
+ CLASSP_REG_SUBKEY_NAME,
+ CLASSP_REG_MMC_DETECTION_VALUE_NAME,
+ ClassDetectionUnsupported);
+ goto ExitWithError;
+ }
+ }
+
+ } // end loop of GESN requests....
+
+ //
+ // we can only use this if it can be relied upon for media changes,
+ // since we are (by definition) no longer going to be polling via
+ // a TEST_UNIT_READY irp, and drives will not report UNIT ATTENTION
+ // for this command (although a filter driver, such as one for burning
+ // cd's, might still fake those errors).
+ //
+ // since we also rely upon NOT_READY events to change the cursor
+ // into a "wait" cursor; GESN is still more reliable than other
+ // methods, and includes eject button requests, so we'll use it
+ // without DEVICE_BUSY in Windows Vista.
+ //
+
+ if (TEST_FLAG(Info->Gesn.EventMask, NOTIFICATION_MEDIA_STATUS_CLASS_MASK)) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "Classpnp => Enabling GESN support for fdo %p\n",
+ FdoExtension->DeviceObject));
+ Info->Gesn.Supported = TRUE;
+
+ ClassSetDeviceParameter(FdoExtension,
+ CLASSP_REG_SUBKEY_NAME,
+ CLASSP_REG_MMC_DETECTION_VALUE_NAME,
+ ClassDetectionSupported);
+
+ return STATUS_SUCCESS;
+
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "Classpnp => GESN available but not enabled for fdo %p\n",
+ FdoExtension->DeviceObject));
+ goto ExitWithError;
+
+ // fall through...
+
+ExitWithError:
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN,
+ "Classpnp => GESN support detection failed for fdo %p with status %08x\n",
+ FdoExtension->DeviceObject, status));
+
+
+ if (Info->Gesn.Mdl) {
+ IoFreeMdl(Info->Gesn.Mdl);
+ Info->Gesn.Mdl = NULL;
+ }
+ FREE_POOL(Info->Gesn.Buffer);
+ Info->Gesn.Supported = 0;
+ Info->Gesn.EventMask = 0;
+ Info->Gesn.BufferSize = 0;
+ return STATUS_NOT_SUPPORTED;
+
+}
+
+
+//
+// Work item to set the hack flag in the registry to disable GESN
+// on devices that sends too many events
+//
+
+VOID
+ClasspDisableGesn(
+ IN PDEVICE_OBJECT Fdo,
+ IN PVOID Context
+ )
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
+ PIO_WORKITEM WorkItem = (PIO_WORKITEM)Context;
+
+ PAGED_CODE();
+
+ //
+ // Set the hack flag in the registry
+ //
+ ClassSetDeviceParameter(fdoExtension,
+ CLASSP_REG_SUBKEY_NAME,
+ CLASSP_REG_MMC_DETECTION_VALUE_NAME,
+ ClassDetectionUnsupported);
+ _Analysis_assume_(WorkItem != NULL);
+ IoFreeWorkItem(WorkItem);
+}
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassInitializeTestUnitPolling()
+
+Routine Description:
+
+ This routine will initialize MCN regardless of the settings stored
+ in the registry. This should be used with caution, as some devices
+ react badly to constant io. (i.e. never spin down, continuously cycling
+ media in changers, ejection of media, etc.) It is highly suggested to
+ use ClassInitializeMediaChangeDetection() instead.
+
+Arguments:
+
+ FdoExtension is the device to poll
+
+ AllowDriveToSleep says whether to attempt to allow the drive to sleep
+ or not. This only affects system-known spin down states, so if a
+ drive spins itself down, this has no effect until the system spins
+ it down.
+
+Return Value:
+
+--*/
+_IRQL_requires_max_(PASSIVE_LEVEL)
+NTSTATUS
+ClassInitializeTestUnitPolling(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ _In_ BOOLEAN AllowDriveToSleep
+ )
+{
+ return ClasspInitializePolling(FdoExtension, AllowDriveToSleep);
+} // end ClassInitializeTestUnitPolling()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassInitializeMediaChangeDetection()
+
+Routine Description:
+
+ This routine checks to see if it is safe to initialize MCN (the back end
+ to autorun) for a given device. It will then check the device-type wide
+ key "Autorun" in the service key (for legacy reasons), and then look in
+ the device-specific key to potentially override that setting.
+
+ If MCN is to be enabled, all neccessary structures and memory are
+ allocated and initialized.
+
+ This routine MUST be called only from the ClassInit() callback.
+
+Arguments:
+
+ FdoExtension - the device to initialize MCN for, if appropriate
+
+ EventPrefix - unused, legacy argument. Set to zero.
+
+Return Value:
+
+--*/
+_IRQL_requires_max_(PASSIVE_LEVEL)
+VOID
+ClassInitializeMediaChangeDetection(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ _In_ PUCHAR EventPrefix
+ )
+{
+ PDEVICE_OBJECT fdo = FdoExtension->DeviceObject;
+ NTSTATUS status;
+
+ PCLASS_DRIVER_EXTENSION driverExtension = ClassGetDriverExtension(
+ fdo->DriverObject);
+
+ BOOLEAN disabledForBadHardware;
+ BOOLEAN disabled;
+ BOOLEAN instanceOverride;
+
+ UNREFERENCED_PARAMETER(EventPrefix);
+
+ PAGED_CODE();
+
+ //
+ // NOTE: This assumes that ClassInitializeMediaChangeDetection is always
+ // called in the context of the ClassInitDevice callback. If called
+ // after then this check will have already been made and the
+ // once a second timer will not have been enabled.
+ //
+
+ disabledForBadHardware = ClasspIsMediaChangeDisabledDueToHardwareLimitation(
+ FdoExtension,
+ &(driverExtension->RegistryPath)
+ );
+
+ if (disabledForBadHardware) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "ClassInitializeMCN: Disabled due to hardware"
+ "limitations for this device"));
+ return;
+ }
+
+ //
+ // autorun should now be enabled by default for all media types.
+ //
+
+ disabled = ClasspIsMediaChangeDisabledForClass(
+ FdoExtension,
+ &(driverExtension->RegistryPath)
+ );
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "ClassInitializeMCN: Class MCN is %s\n",
+ (disabled ? "disabled" : "enabled")));
+
+ status = ClasspMediaChangeDeviceInstanceOverride(
+ FdoExtension,
+ &instanceOverride); // default value
+
+ if (!NT_SUCCESS(status)) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "ClassInitializeMCN: Instance using default\n"));
+ } else {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "ClassInitializeMCN: Instance override: %s MCN\n",
+ (instanceOverride ? "Enabling" : "Disabling")));
+ disabled = !instanceOverride;
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "ClassInitializeMCN: Instance MCN is %s\n",
+ (disabled ? "disabled" : "enabled")));
+
+ if (disabled) {
+ return;
+ }
+
+ //
+ // Do not allow drive to sleep for all types of devices initially.
+ // For non-cdrom devices, allow devices to go to sleep if it's
+ // unlikely a media change will occur (e.g. user not present).
+ //
+ ClasspInitializePolling(FdoExtension, FALSE);
+
+ return;
+} // end ClassInitializeMediaChangeDetection()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClasspMediaChangeDeviceInstanceOverride()
+
+Routine Description:
+
+ The user can override the global setting to enable or disable Autorun on a
+ specific cdrom device via the control panel. This routine checks and/or
+ sets this value.
+
+Arguments:
+
+ FdoExtension - the device to set/get the value for
+ Value - the value to use in a set
+ SetValue - whether to set the value
+
+Return Value:
+
+ TRUE - Autorun is disabled
+ FALSE - Autorun is not disabled (Default)
+
+--*/
+NTSTATUS
+ClasspMediaChangeDeviceInstanceOverride(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ OUT PBOOLEAN Enabled
+ )
+{
+ HANDLE deviceParameterHandle = NULL; // cdrom instance key
+ HANDLE driverParameterHandle = NULL; // cdrom specific key
+ RTL_QUERY_REGISTRY_TABLE queryTable[3];
+ OBJECT_ATTRIBUTES objectAttributes;
+ UNICODE_STRING subkeyName;
+ NTSTATUS status = STATUS_UNSUCCESSFUL;
+ ULONG alwaysEnable = FALSE;
+ ULONG alwaysDisable = FALSE;
+ ULONG i;
+
+ PAGED_CODE();
+
+ TRY {
+
+ status = IoOpenDeviceRegistryKey( FdoExtension->LowerPdo,
+ PLUGPLAY_REGKEY_DEVICE,
+ KEY_ALL_ACCESS,
+ &deviceParameterHandle
+ );
+ if (!NT_SUCCESS(status)) {
+
+ //
+ // this can occur when a new device is added to the system
+ // this is due to cdrom.sys being an 'essential' driver
+ //
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "ClassMediaChangeDeviceInstanceDisabled: "
+ "Could not open device registry key [%lx]\n", status));
+ LEAVE;
+ }
+
+ RtlInitUnicodeString(&subkeyName, MCN_REG_SUBKEY_NAME);
+ InitializeObjectAttributes(&objectAttributes,
+ &subkeyName,
+ OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
+ deviceParameterHandle,
+ (PSECURITY_DESCRIPTOR) NULL);
+
+ status = ZwCreateKey(&driverParameterHandle,
+ KEY_READ,
+ &objectAttributes,
+ 0,
+ (PUNICODE_STRING) NULL,
+ REG_OPTION_NON_VOLATILE,
+ NULL);
+
+ if (!NT_SUCCESS(status)) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "ClassMediaChangeDeviceInstanceDisabled: "
+ "subkey could not be created. %lx\n", status));
+ LEAVE;
+ }
+
+ //
+ // Default to not changing autorun behavior, based upon setting
+ // registryValue to zero.
+ //
+
+ for (i=0;i<2;i++) {
+
+ RtlZeroMemory(&queryTable[0], sizeof(queryTable));
+
+ queryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT;
+ queryTable[0].DefaultType = REG_DWORD;
+ queryTable[0].DefaultLength = 0;
+
+ if (i==0) {
+ queryTable[0].Name = MCN_REG_AUTORUN_DISABLE_INSTANCE_NAME;
+ queryTable[0].EntryContext = &alwaysDisable;
+ queryTable[0].DefaultData = &alwaysDisable;
+ } else {
+ queryTable[0].Name = MCN_REG_AUTORUN_ENABLE_INSTANCE_NAME;
+ queryTable[0].EntryContext = &alwaysEnable;
+ queryTable[0].DefaultData = &alwaysEnable;
+ }
+
+ //
+ // don't care if it succeeds, since we set defaults above
+ //
+
+ RtlQueryRegistryValues(RTL_REGISTRY_HANDLE,
+ (PWSTR)driverParameterHandle,
+ queryTable,
+ NULL,
+ NULL);
+ }
+
+ } FINALLY {
+
+ if (driverParameterHandle) ZwClose(driverParameterHandle);
+ if (deviceParameterHandle) ZwClose(deviceParameterHandle);
+
+ }
+
+ if (alwaysEnable && alwaysDisable) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "ClassMediaChangeDeviceInstanceDisabled: %s selected\n",
+ "Both Enable and Disable set -- DISABLE"));
+ NT_ASSERT(NT_SUCCESS(status));
+ status = STATUS_SUCCESS;
+ *Enabled = FALSE;
+
+ } else if (alwaysDisable) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "ClassMediaChangeDeviceInstanceDisabled: %s selected\n",
+ "DISABLE"));
+ NT_ASSERT(NT_SUCCESS(status));
+ status = STATUS_SUCCESS;
+ *Enabled = FALSE;
+
+ } else if (alwaysEnable) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "ClassMediaChangeDeviceInstanceDisabled: %s selected\n",
+ "ENABLE"));
+ NT_ASSERT(NT_SUCCESS(status));
+ status = STATUS_SUCCESS;
+ *Enabled = TRUE;
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "ClassMediaChangeDeviceInstanceDisabled: %s selected\n",
+ "DEFAULT"));
+ status = STATUS_UNSUCCESSFUL;
+
+ }
+
+ return status;
+
+} // end ClasspMediaChangeDeviceInstanceOverride()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClasspIsMediaChangeDisabledDueToHardwareLimitation()
+
+Routine Description:
+
+ The key AutoRunAlwaysDisable contains a MULTI_SZ of hardware IDs for
+ which to never enable MediaChangeNotification.
+
+ The user can override the global setting to enable or disable Autorun on a
+ specific cdrom device via the control panel.
+
+Arguments:
+
+ FdoExtension -
+ RegistryPath - pointer to the unicode string inside
+ ...\CurrentControlSet\Services\Cdrom
+
+Return Value:
+
+ TRUE - no autorun.
+ FALSE - Autorun may be enabled
+
+--*/
+BOOLEAN
+ClasspIsMediaChangeDisabledDueToHardwareLimitation(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ IN PUNICODE_STRING RegistryPath
+ )
+{
+ PSTORAGE_DEVICE_DESCRIPTOR deviceDescriptor = FdoExtension->DeviceDescriptor;
+ OBJECT_ATTRIBUTES objectAttributes = {0};
+ HANDLE serviceKey = NULL;
+ RTL_QUERY_REGISTRY_TABLE parameters[2] = {0};
+
+ UNICODE_STRING deviceUnicodeString;
+ ANSI_STRING deviceString;
+ ULONG mediaChangeNotificationDisabled = FALSE;
+
+ NTSTATUS status;
+
+
+ PAGED_CODE();
+
+ //
+ // open the service key.
+ //
+
+ InitializeObjectAttributes(&objectAttributes,
+ RegistryPath,
+ OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
+ NULL,
+ NULL);
+
+ status = ZwOpenKey(&serviceKey,
+ KEY_READ,
+ &objectAttributes);
+
+ NT_ASSERT(NT_SUCCESS(status));
+
+
+ if(!NT_SUCCESS(status)) {
+
+ //
+ // always take the safe path. if we can't open the service key,
+ // disable autorun
+ //
+
+ return TRUE;
+
+ }
+
+ TRY {
+ //
+ // Determine if drive is in a list of those requiring
+ // autorun to be disabled. this is stored in a REG_MULTI_SZ
+ // named AutoRunAlwaysDisable. this is required as some autochangers
+ // must load the disc to reply to ChkVerify request, causing them
+ // to cycle discs continuously.
+ //
+
+ PWSTR nullMultiSz;
+ PUCHAR vendorId;
+ PUCHAR productId;
+ PUCHAR revisionId;
+ ULONG length;
+ ULONG offset;
+
+ deviceString.Buffer = NULL;
+ deviceUnicodeString.Buffer = NULL;
+
+ //
+ // there may be nothing to check against
+ //
+
+ if ((deviceDescriptor->VendorIdOffset == 0) &&
+ (deviceDescriptor->ProductIdOffset == 0)) {
+ LEAVE;
+ }
+
+ length = 0;
+
+ if (deviceDescriptor->VendorIdOffset == 0) {
+ vendorId = NULL;
+ } else {
+ vendorId = (PUCHAR) deviceDescriptor + deviceDescriptor->VendorIdOffset;
+ length = (ULONG)strlen((PCSZ)vendorId);
+ }
+
+ if ( deviceDescriptor->ProductIdOffset == 0 ) {
+ productId = NULL;
+ } else {
+ productId = (PUCHAR)deviceDescriptor + deviceDescriptor->ProductIdOffset;
+ length += (ULONG)strlen((PCSZ)productId);
+ }
+
+ if ( deviceDescriptor->ProductRevisionOffset == 0 ) {
+ revisionId = NULL;
+ } else {
+ revisionId = (PUCHAR) deviceDescriptor + deviceDescriptor->ProductRevisionOffset;
+ length += (ULONG)strlen((PCSZ)revisionId);
+ }
+
+ //
+ // allocate a buffer for the string
+ //
+
+ deviceString.Length = (USHORT)( length );
+ deviceString.MaximumLength = deviceString.Length + 1;
+ deviceString.Buffer = (PCHAR)ExAllocatePoolWithTag( NonPagedPoolNx,
+ deviceString.MaximumLength,
+ CLASS_TAG_AUTORUN_DISABLE
+ );
+ if (deviceString.Buffer == NULL) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "ClassMediaChangeDisabledForHardware: Unable to alloc "
+ "string buffer\n" ));
+ LEAVE;
+ }
+
+ //
+ // copy strings to the buffer
+ //
+ offset = 0;
+
+ if (vendorId != NULL) {
+ RtlCopyMemory(deviceString.Buffer + offset,
+ vendorId,
+ strlen((PCSZ)vendorId));
+ offset += (ULONG)strlen((PCSZ)vendorId);
+ }
+
+ if ( productId != NULL ) {
+ RtlCopyMemory(deviceString.Buffer + offset,
+ productId,
+ strlen((PCSZ)productId));
+ offset += (ULONG)strlen((PCSZ)productId);
+ }
+ if ( revisionId != NULL ) {
+ RtlCopyMemory(deviceString.Buffer + offset,
+ revisionId,
+ strlen((PCSZ)revisionId));
+ offset += (ULONG)strlen((PCSZ)revisionId);
+ }
+
+ NT_ASSERT(offset == deviceString.Length);
+
+ #pragma warning(suppress:6386) // Not an issue as deviceString.Buffer is of size deviceString.MaximumLength, which is equal to (deviceString.Length + 1)
+ deviceString.Buffer[deviceString.Length] = '\0'; // Null-terminated
+
+ //
+ // convert to unicode as registry deals with unicode strings
+ //
+
+ status = RtlAnsiStringToUnicodeString( &deviceUnicodeString,
+ &deviceString,
+ TRUE
+ );
+ if (!NT_SUCCESS(status)) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "ClassMediaChangeDisabledForHardware: cannot convert "
+ "to unicode %lx\n", status));
+ LEAVE;
+ }
+
+ //
+ // query the value, setting valueFound to true if found
+ //
+ nullMultiSz = L"\0";
+ parameters[0].QueryRoutine = ClasspMediaChangeRegistryCallBack;
+ parameters[0].Flags = RTL_QUERY_REGISTRY_REQUIRED;
+ parameters[0].Name = L"AutoRunAlwaysDisable";
+ parameters[0].EntryContext = &mediaChangeNotificationDisabled;
+ parameters[0].DefaultType = REG_MULTI_SZ;
+ parameters[0].DefaultData = nullMultiSz;
+ parameters[0].DefaultLength = 0;
+
+ status = RtlQueryRegistryValues(RTL_REGISTRY_HANDLE,
+ serviceKey,
+ parameters,
+ &deviceUnicodeString,
+ NULL);
+
+ if ( !NT_SUCCESS(status) ) {
+ LEAVE;
+ }
+
+ } FINALLY {
+
+ FREE_POOL( deviceString.Buffer );
+ if (deviceUnicodeString.Buffer != NULL) {
+ RtlFreeUnicodeString( &deviceUnicodeString );
+ }
+
+ ZwClose(serviceKey);
+ }
+
+ if (mediaChangeNotificationDisabled) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClassMediaChangeDisabledForHardware: "
+ "Device is on disable list\n"));
+ return TRUE;
+ }
+ return FALSE;
+
+} // end ClasspIsMediaChangeDisabledDueToHardwareLimitation()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClasspIsMediaChangeDisabledForClass()
+
+Routine Description:
+
+ The user must specify that AutoPlay is to run on the platform
+ by setting the registry value HKEY_LOCAL_MACHINE\System\CurrentControlSet\
+ Services\<SERVICE>\Autorun:REG_DWORD:1.
+
+ The user can override the global setting to enable or disable Autorun on a
+ specific cdrom device via the control panel.
+
+Arguments:
+
+ FdoExtension -
+ RegistryPath - pointer to the unicode string inside
+ ...\CurrentControlSet\Services\Cdrom
+
+Return Value:
+
+ TRUE - Autorun is disabled for this class
+ FALSE - Autorun is enabled for this class
+
+--*/
+BOOLEAN
+ClasspIsMediaChangeDisabledForClass(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ IN PUNICODE_STRING RegistryPath
+ )
+{
+ OBJECT_ATTRIBUTES objectAttributes = {0};
+ HANDLE serviceKey = NULL;
+ HANDLE parametersKey = NULL;
+ RTL_QUERY_REGISTRY_TABLE parameters[3] = {0};
+
+ UNICODE_STRING paramStr;
+
+ //
+ // Default to ENABLING MediaChangeNotification (!)
+ //
+
+ ULONG mcnRegistryValue = 1;
+
+ NTSTATUS status;
+
+
+ PAGED_CODE();
+
+ //
+ // open the service key.
+ //
+
+ InitializeObjectAttributes(&objectAttributes,
+ RegistryPath,
+ OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
+ NULL,
+ NULL);
+
+ status = ZwOpenKey(&serviceKey,
+ KEY_READ,
+ &objectAttributes);
+
+ NT_ASSERT(NT_SUCCESS(status));
+
+ if(!NT_SUCCESS(status)) {
+
+ //
+ // return the default value, which is the
+ // inverse of the registry setting default
+ // since this routine asks if it's disabled
+ //
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClassCheckServiceMCN: Defaulting to %s\n",
+ (mcnRegistryValue ? "Enabled" : "Disabled")));
+ return (BOOLEAN)(!mcnRegistryValue);
+
+ }
+
+ //
+ // Open the parameters key (if any) beneath the services key.
+ //
+
+ RtlInitUnicodeString(&paramStr, L"Parameters");
+
+ InitializeObjectAttributes(&objectAttributes,
+ &paramStr,
+ OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
+ serviceKey,
+ NULL);
+
+ status = ZwOpenKey(&parametersKey,
+ KEY_READ,
+ &objectAttributes);
+
+ if (!NT_SUCCESS(status)) {
+ parametersKey = NULL;
+ }
+
+
+
+ //
+ // Check for the Autorun value.
+ //
+
+ parameters[0].Flags = RTL_QUERY_REGISTRY_DIRECT;
+ parameters[0].Name = L"Autorun";
+ parameters[0].EntryContext = &mcnRegistryValue;
+ parameters[0].DefaultType = REG_DWORD;
+ parameters[0].DefaultData = &mcnRegistryValue;
+ parameters[0].DefaultLength = sizeof(ULONG);
+
+ // ignore failures
+ RtlQueryRegistryValues(RTL_REGISTRY_HANDLE | RTL_REGISTRY_OPTIONAL,
+ serviceKey,
+ parameters,
+ NULL,
+ NULL);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClassCheckServiceMCN: "
+ "<Service>/Autorun flag = %d\n", mcnRegistryValue));
+
+ if(parametersKey != NULL) {
+
+ // ignore failures
+ RtlQueryRegistryValues(RTL_REGISTRY_HANDLE | RTL_REGISTRY_OPTIONAL,
+ parametersKey,
+ parameters,
+ NULL,
+ NULL);
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClassCheckServiceMCN: "
+ "<Service>/Parameters/Autorun flag = %d\n",
+ mcnRegistryValue));
+ ZwClose(parametersKey);
+
+ }
+ ZwClose(serviceKey);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClassCheckServiceMCN: "
+ "Autoplay for device %p is %s\n",
+ FdoExtension->DeviceObject,
+ (mcnRegistryValue ? "on" : "off")
+ ));
+
+ //
+ // return if it is _disabled_, which is the
+ // inverse of the registry setting
+ //
+
+ return (BOOLEAN)(!mcnRegistryValue);
+} // end ClasspIsMediaChangeDisabledForClass()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassEnableMediaChangeDetection() ISSUE-2000/02/20-henrygab - why public?
+ClassEnableMediaChangeDetection() ISSUE-2000/02/20-henrygab - not documented
+
+Routine Description:
+
+ This routine
+
+Arguments:
+
+ DeviceObject -
+ Irp -
+
+Return Value:
+
+--*/
+_IRQL_requires_max_(PASSIVE_LEVEL)
+VOID
+ClassEnableMediaChangeDetection(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ )
+{
+ PMEDIA_CHANGE_DETECTION_INFO info = FdoExtension->MediaChangeDetectionInfo;
+ LONG oldCount;
+
+ PAGED_CODE();
+
+ if(info == NULL) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "ClassEnableMediaChangeDetection: not initialized\n"));
+ return;
+ }
+
+ (VOID)KeWaitForMutexObject(&info->MediaChangeMutex,
+ UserRequest,
+ KernelMode,
+ FALSE,
+ NULL);
+
+ oldCount = --info->MediaChangeDetectionDisableCount;
+
+ NT_ASSERT(oldCount >= 0);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClassEnableMediaChangeDetection: Disable count "
+ "reduced to %d - ",
+ info->MediaChangeDetectionDisableCount));
+
+ if(oldCount == 0) {
+
+ //
+ // We don't know what state the media is in anymore.
+ //
+
+ ClasspInternalSetMediaChangeState(FdoExtension,
+ MediaUnknown,
+ FALSE
+ );
+
+ //
+ // Reset the MCN timer.
+ //
+
+ ClassResetMediaChangeTimer(FdoExtension);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "MCD is enabled\n"));
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "MCD still disabled\n"));
+
+ }
+
+
+ //
+ // Let something else run.
+ //
+
+ KeReleaseMutex(&info->MediaChangeMutex, FALSE);
+
+ return;
+} // end ClassEnableMediaChangeDetection()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassDisableMediaChangeDetection() ISSUE-2000/02/20-henrygab - why public?
+ClassDisableMediaChangeDetection() ISSUE-2000/02/20-henrygab - not documented
+
+Routine Description:
+
+ This routine
+
+Arguments:
+
+ DeviceObject -
+ Irp -
+
+Return Value:
+
+--*/
+ULONG BreakOnMcnDisable = FALSE;
+
+_IRQL_requires_max_(PASSIVE_LEVEL)
+VOID
+ClassDisableMediaChangeDetection(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ )
+{
+ PMEDIA_CHANGE_DETECTION_INFO info = FdoExtension->MediaChangeDetectionInfo;
+
+ PAGED_CODE();
+
+ if(info == NULL) {
+ return;
+ }
+
+ (VOID)KeWaitForMutexObject(&info->MediaChangeMutex,
+ UserRequest,
+ KernelMode,
+ FALSE,
+ NULL);
+
+ info->MediaChangeDetectionDisableCount++;
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClassDisableMediaChangeDetection: "
+ "disable count is %d\n",
+ info->MediaChangeDetectionDisableCount));
+
+ KeReleaseMutex(&info->MediaChangeMutex, FALSE);
+
+ return;
+} // end ClassDisableMediaChangeDetection()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassCleanupMediaChangeDetection() ISSUE-2000/02/20-henrygab - why public?!
+
+Routine Description:
+
+ This routine will cleanup any resources allocated for MCN. It is called
+ by classpnp during remove device, and therefore is not typically required
+ by external drivers.
+
+Arguments:
+
+Return Value:
+
+--*/
+_IRQL_requires_max_(PASSIVE_LEVEL)
+VOID
+ClassCleanupMediaChangeDetection(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ )
+{
+ PMEDIA_CHANGE_DETECTION_INFO info = FdoExtension->MediaChangeDetectionInfo;
+
+ PAGED_CODE()
+
+ if(info == NULL) {
+ return;
+ }
+
+ FdoExtension->MediaChangeDetectionInfo = NULL;
+
+ if (info->Gesn.Mdl) {
+ IoFreeMdl(info->Gesn.Mdl);
+ }
+ FREE_POOL(info->Gesn.Buffer);
+ IoFreeIrp(info->MediaChangeIrp);
+ FREE_POOL(info->SenseBuffer);
+ FREE_POOL(info);
+ return;
+} // end ClassCleanupMediaChangeDetection()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClasspMcnControl() - ISSUE-2000/02/20-henrygab - not documented
+
+Routine Description:
+
+ This routine
+
+Arguments:
+
+ DeviceObject -
+ Irp -
+
+Return Value:
+
+--*/
+NTSTATUS
+ClasspMcnControl(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ IN PIRP Irp,
+ IN PSCSI_REQUEST_BLOCK Srb
+ )
+{
+ PCOMMON_DEVICE_EXTENSION commonExtension =
+ (PCOMMON_DEVICE_EXTENSION) FdoExtension;
+
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ PPREVENT_MEDIA_REMOVAL request = Irp->AssociatedIrp.SystemBuffer;
+
+ PFILE_OBJECT fileObject = irpStack->FileObject;
+ PFILE_OBJECT_EXTENSION fsContext = NULL;
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ PAGED_CODE();
+
+ //
+ // Check to make sure we have a file object extension to keep track of this
+ // request. If not we'll fail it before synchronizing.
+ //
+
+ TRY {
+
+ if(fileObject != NULL) {
+ fsContext = ClassGetFsContext(commonExtension, fileObject);
+ }else if(Irp->RequestorMode == KernelMode) { // && fileObject == NULL
+ fsContext = &FdoExtension->KernelModeMcnContext;
+ }
+
+ if (fsContext == NULL) {
+
+ //
+ // This handle isn't setup correctly. We can't let the
+ // operation go.
+ //
+
+ status = STATUS_INVALID_PARAMETER;
+ LEAVE;
+ }
+
+ if(request->PreventMediaRemoval) {
+
+ //
+ // This is a lock command. Reissue the command in case bus or
+ // device was reset and the lock was cleared.
+ //
+
+ ClassDisableMediaChangeDetection(FdoExtension);
+ InterlockedIncrement((volatile LONG *)&(fsContext->McnDisableCount));
+
+ } else {
+
+ if(fsContext->McnDisableCount == 0) {
+ status = STATUS_INVALID_DEVICE_STATE;
+ LEAVE;
+ }
+
+ InterlockedDecrement((volatile LONG *)&(fsContext->McnDisableCount));
+ ClassEnableMediaChangeDetection(FdoExtension);
+ }
+
+ } FINALLY {
+
+ Irp->IoStatus.Status = status;
+
+ FREE_POOL(Srb);
+
+ ClassReleaseRemoveLock(FdoExtension->DeviceObject, Irp);
+ ClassCompleteRequest(FdoExtension->DeviceObject,
+ Irp,
+ IO_NO_INCREMENT);
+ }
+ return status;
+} // end ClasspMcnControl(
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClasspMediaChangeRegistryCallBack()
+
+Routine Description:
+
+ This callback for a registry SZ or MULTI_SZ is called once for each
+ SZ in the value. It will attempt to match the data with the
+ UNICODE_STRING passed in as Context, and modify EntryContext if a
+ match is found. Written for ClasspCheckRegistryForMediaChangeCompletion
+
+Arguments:
+
+ ValueName - name of the key that was opened
+ ValueType - type of data stored in the value (REG_SZ for this routine)
+ ValueData - data in the registry, in this case a wide string
+ ValueLength - length of the data including the terminating null
+ Context - unicode string to compare against ValueData
+ EntryContext - should be initialized to 0, will be set to 1 if match found
+
+Return Value:
+
+ STATUS_SUCCESS
+ EntryContext will be 1 if found
+
+--*/
+_Function_class_(RTL_QUERY_REGISTRY_ROUTINE)
+_IRQL_requires_max_(PASSIVE_LEVEL)
+_IRQL_requires_same_
+NTSTATUS
+ClasspMediaChangeRegistryCallBack(
+ _In_z_ PWSTR ValueName,
+ _In_ ULONG ValueType,
+ _In_reads_bytes_opt_(ValueLength) PVOID ValueData,
+ _In_ ULONG ValueLength,
+ _In_opt_ PVOID Context,
+ _In_opt_ PVOID EntryContext
+ )
+{
+ PULONG valueFound;
+ PUNICODE_STRING deviceString;
+ PWSTR keyValue;
+
+ PAGED_CODE();
+ UNREFERENCED_PARAMETER(ValueName);
+
+ if (ValueData == NULL ||
+ Context == NULL ||
+ EntryContext == NULL) {
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // if we have already set the value to true, exit
+ //
+
+ valueFound = EntryContext;
+ if ((*valueFound) != 0) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClasspMcnRegCB: already set to true\n"));
+ return STATUS_SUCCESS;
+ }
+
+ if (ValueLength == sizeof(WCHAR)) {
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN, "ClasspMcnRegCB: NULL string should "
+ "never be passed to registry call-back!\n"));
+ return STATUS_SUCCESS;
+ }
+
+
+ //
+ // if the data is not a terminated string, exit
+ //
+
+ if (ValueType != REG_SZ) {
+ return STATUS_SUCCESS;
+ }
+
+ deviceString = Context;
+ keyValue = ValueData;
+ ValueLength -= sizeof(WCHAR); // ignore the null character
+
+ //
+ // do not compare more memory than is in deviceString
+ //
+
+ if (ValueLength > deviceString->Length) {
+ ValueLength = deviceString->Length;
+ }
+
+ //
+ // if the strings match, disable autorun
+ //
+
+ if (RtlCompareMemory(deviceString->Buffer, keyValue, ValueLength) == ValueLength) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClasspRegMcnCB: Match found\n"));
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClasspRegMcnCB: DeviceString at %p\n",
+ deviceString->Buffer));
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClasspRegMcnCB: KeyValue at %p\n",
+ keyValue));
+ (*valueFound) = TRUE;
+ }
+
+ return STATUS_SUCCESS;
+} // end ClasspMediaChangeRegistryCallBack()
+
+#if (NTDDI_VERSION >= NTDDI_WINBLUE)
+VOID
+ClasspTimerTickEx(
+ _In_ PEX_TIMER Timer,
+ _In_opt_ PVOID Context
+)
+{
+ KDPC dummyDpc = { 0 };
+
+ UNREFERENCED_PARAMETER(Timer);
+ //
+ // This is just a wrapper around ClasspTimerTick that allows us to make
+ // the TickTimer a no-wake EX_TIMER.
+ // We pass in a dummy DPC b/c ClasspTimerTick expects a non-NULL parameter
+ // for the DPC. However, ClasspTimerTick does not actually reference it.
+ //
+ ClasspTimerTick(&dummyDpc, Context, NULL, NULL);
+}
+#endif
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClasspTimerTick() - ISSUE-2000/02/20-henrygab - not documented
+
+Routine Description:
+
+ This routine
+
+Arguments:
+
+ DeviceObject -
+ Irp -
+
+Return Value:
+
+--*/
+_Function_class_(KDEFERRED_ROUTINE)
+_IRQL_requires_max_(DISPATCH_LEVEL)
+_IRQL_requires_min_(DISPATCH_LEVEL)
+_IRQL_requires_(DISPATCH_LEVEL)
+_IRQL_requires_same_
+VOID
+ClasspTimerTick(
+ _In_ PKDPC Dpc,
+ _In_opt_ PVOID DeferredContext,
+ _In_opt_ PVOID SystemArgument1,
+ _In_opt_ PVOID SystemArgument2
+ )
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeferredContext;
+ PCOMMON_DEVICE_EXTENSION commonExtension;
+ PDEVICE_OBJECT DeviceObject;
+ ULONG isRemoved;
+
+ UNREFERENCED_PARAMETER(Dpc);
+ UNREFERENCED_PARAMETER(SystemArgument1);
+ UNREFERENCED_PARAMETER(SystemArgument2);
+
+ NT_ASSERT(fdoExtension != NULL);
+ _Analysis_assume_(fdoExtension != NULL);
+
+ commonExtension = &fdoExtension->CommonExtension;
+ DeviceObject = fdoExtension->DeviceObject;
+ NT_ASSERT(commonExtension->IsFdo);
+
+ //
+ // Do any media change work
+ //
+#pragma warning(suppress:4054) // okay to type cast function pointer to PIRP for this use case
+ isRemoved = ClassAcquireRemoveLock(DeviceObject, (PIRP)ClasspTimerTick);
+
+ //
+ // We stop the timer before deleting the device. It's safe to keep going
+ // if the flag value is REMOVE_PENDING because the removal thread will be
+ // blocked trying to stop the timer.
+ //
+
+ NT_ASSERT(isRemoved != REMOVE_COMPLETE);
+
+ //
+ // This routine is reasonably safe even if the device object has a pending
+ // remove
+
+ if (!isRemoved) {
+
+ PFAILURE_PREDICTION_INFO info = fdoExtension->FailurePredictionInfo;
+
+ //
+ // Do any media change detection work
+ //
+
+ if ((fdoExtension->MediaChangeDetectionInfo != NULL) &&
+ (fdoExtension->FunctionSupportInfo->AsynchronousNotificationSupported == FALSE)) {
+
+ ClassCheckMediaState(fdoExtension);
+
+ }
+
+ //
+ // Do any failure prediction work
+ //
+ if ((info != NULL) && (info->Method != FailurePredictionNone)) {
+
+ ULONG countDown;
+
+ if (ClasspCanSendPollingIrp(fdoExtension)) {
+
+ //
+ // Synchronization is not required here since the Interlocked
+ // locked instruction guarantees atomicity. Other code that
+ // resets CountDown uses InterlockedExchange which is also
+ // atomic.
+ //
+ countDown = InterlockedDecrement((volatile LONG *)&info->CountDown);
+ if (countDown == 0) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClasspTimerTick: Send FP irp for %p\n",
+ DeviceObject));
+
+ if(info->WorkQueueItem == NULL) {
+
+ info->WorkQueueItem =
+ IoAllocateWorkItem(fdoExtension->DeviceObject);
+
+ if(info->WorkQueueItem == NULL) {
+
+ //
+ // Set the countdown to one minute in the future.
+ // we'll try again then in the hopes there's more
+ // free memory.
+ //
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN, "ClassTimerTick: Couldn't allocate "
+ "item - try again in one minute\n"));
+ InterlockedExchange((volatile LONG *)&info->CountDown, 60);
+
+ } else {
+
+ //
+ // Grab the remove lock so that removal will block
+ // until the work item is done.
+ //
+
+ ClassAcquireRemoveLock(fdoExtension->DeviceObject,
+ info->WorkQueueItem);
+
+ IoQueueWorkItem(info->WorkQueueItem,
+ ClasspFailurePredict,
+ DelayedWorkQueue,
+ info);
+ }
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClasspTimerTick: Failure "
+ "Prediction work item is "
+ "already active for device %p\n",
+ DeviceObject));
+
+ }
+ } // end (countdown == 0)
+
+ } else {
+ //
+ // If device is sleeping then just rearm polling timer
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClassTimerTick, SHHHH!!! device is %p is sleeping\n",
+ DeviceObject));
+ }
+
+ } // end failure prediction polling
+
+ //
+ // Give driver a chance to do its own specific work
+ //
+
+ if (commonExtension->DriverExtension->InitData.ClassTick != NULL) {
+
+ commonExtension->DriverExtension->InitData.ClassTick(DeviceObject);
+
+ } // end device specific tick handler
+ } // end check for removed
+
+#pragma warning(suppress:4054) // okay to type cast function pointer to PIRP for this use case
+ ClassReleaseRemoveLock(DeviceObject, (PIRP)ClasspTimerTick);
+} // end ClasspTimerTick()
+
+#if (NTDDI_VERSION >= NTDDI_WINBLUE)
+BOOLEAN
+ClasspUpdateTimerNoWakeTolerance(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+)
+/*
+Routine Description:
+
+ Updates the no-wake timer's tolerance based on system state.
+
+ If the timer is not allocated, initialized, or enabled then this function
+ does nothing.
+
+ If the timer is enabled but the no-wake tolerance has *not* changed from
+ its previous value then this function does nothing.
+
+ If the timer is enabled and the no-wake tolerance has changed from its
+ previous value then this function *will* set/reset the tick timer.
+
+Arguments:
+
+ FdoExtension for the device that has the timer whose tolerance needs updating.
+
+Returns:
+
+ TRUE if the timer was set/reset.
+ FALSE if the timer was not set/reset.
+
+*/
+{
+ PCLASS_PRIVATE_FDO_DATA fdoData = NULL;
+
+ if (FdoExtension->CommonExtension.IsFdo) {
+ fdoData = FdoExtension->PrivateFdoData;
+ }
+
+ if (fdoData != NULL &&
+ fdoData->TickTimer != NULL &&
+ fdoData->TimerInitialized &&
+ fdoData->TickTimerEnabled) {
+
+ LONGLONG noWakeTolerance = TICK_TIMER_DELAY_IN_MSEC * (10 * 1000);
+
+ //
+ // Set the no-wake tolerance to "unlimited" if the conditions below
+ // are met. An "unlimited" no-wake tolerance means that the timer
+ // will *never* wake the processor if the processor is in a
+ // low-power state.
+ // 1. The screen is off.
+ // 2. The class driver is *not* a consumer of the tick timer (ClassTick is NULL).
+ // 3. This is a disk device.
+ // Otherwise the tolerance is set to the normal, default tolerable delay.
+ //
+ if (ClasspScreenOff &&
+ FdoExtension->CommonExtension.DriverExtension->InitData.ClassTick == NULL &&
+ FdoExtension->DeviceObject->DeviceType == FILE_DEVICE_DISK) {
+ noWakeTolerance = EX_TIMER_UNLIMITED_TOLERANCE;
+ }
+
+ //
+ // The new tolerance is different from the current tolerance so we need
+ // to set/reset the timer with the new tolerance value.
+ //
+ if (fdoData->CurrentNoWakeTolerance != noWakeTolerance) {
+ EXT_SET_PARAMETERS parameters;
+ LONGLONG period = TICK_TIMER_PERIOD_IN_MSEC * (10 * 1000); // Convert to units of 100ns.
+ LONGLONG dueTime = period * (-1); // Negative sign indicates dueTime is relative.
+
+ ExInitializeSetTimerParameters(&parameters);
+ parameters.NoWakeTolerance = noWakeTolerance;
+ fdoData->CurrentNoWakeTolerance = noWakeTolerance;
+
+ ExSetTimer(fdoData->TickTimer,
+ dueTime,
+ period,
+ &parameters);
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+#endif
+
+NTSTATUS
+ClasspInitializeTimer(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+)
+/*
+Routine Description:
+
+ This routine will attempt to initialize the tick timer.
+ The caller should call ClasspEnableTmer() to actually start the timer.
+
+ If the caller just needs to check if the timer is initialized, the caller
+ should simply check FdoExtension->PrivateFdoData->TimerInitialized rather
+ than call this function.
+
+ The caller should subsequently call ClasspDeleteTimer() when they are done
+ with the timer.
+
+Arguments:
+
+ FdoExtension
+
+Return Value:
+
+ STATUS_SUCCESS if the timer is initialized (the timer may already have been
+ initialized by a previous call).
+ A non-success status if the timer is not initialized.
+
+*/
+{
+ PCLASS_PRIVATE_FDO_DATA fdoData = NULL;
+
+ if (FdoExtension->CommonExtension.IsFdo) {
+ fdoData = FdoExtension->PrivateFdoData;
+ }
+
+ if (fdoData == NULL) {
+ return STATUS_UNSUCCESSFUL;
+ }
+
+ if (fdoData->TimerInitialized == FALSE) {
+#if (NTDDI_VERSION >= NTDDI_WINBLUE)
+ NT_ASSERT(fdoData->TickTimer == NULL);
+ //
+ // The tick timer is a no-wake timer, which means it will not wake
+ // the processor while the processor is in a low power state until
+ // the timer's no-wake tolerance is reached.
+ //
+ fdoData->TickTimer = ExAllocateTimer(ClasspTimerTickEx, FdoExtension, EX_TIMER_NO_WAKE);
+ if (fdoData->TickTimer == NULL) {
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+#else
+ KeInitializeDpc(&fdoData->TickTimerDpc, ClasspTimerTick, FdoExtension);
+ KeInitializeTimer(&fdoData->TickTimer);
+#endif
+ fdoData->TimerInitialized = TRUE;
+ }
+
+ return STATUS_SUCCESS;
+}
+
+VOID
+ClasspDeleteTimer(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+)
+/*
+Routine Description:
+
+ This routine will attempt to de-initialize and free the tick timer.
+ This routine should only be called after a successful call to
+ ClasspInitializeTimer().
+
+Arguments:
+
+ FdoExtension
+
+Return Value:
+
+ None.
+
+*/
+{
+ PCLASS_PRIVATE_FDO_DATA fdoData = NULL;
+
+ if (FdoExtension->CommonExtension.IsFdo) {
+ fdoData = FdoExtension->PrivateFdoData;
+ if (fdoData != NULL) {
+#if (NTDDI_VERSION >= NTDDI_WINBLUE)
+ if (fdoData->TickTimer != NULL) {
+ EXT_DELETE_PARAMETERS parameters;
+ ExInitializeDeleteTimerParameters(&parameters);
+ ExDeleteTimer(fdoData->TickTimer, TRUE, FALSE, &parameters);
+ fdoData->TickTimer = NULL;
+ }
+#endif
+ fdoData->TimerInitialized = FALSE;
+ fdoData->TickTimerEnabled = FALSE;
+ }
+ }
+}
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClasspEnableTimer() - ISSUE-2000/02/20-henrygab - not documented
+
+Routine Description:
+
+ This routine will enable the tick timer. ClasspInitializeTimer() should
+ first be called to initialize the timer. Use ClasspDisableTimer() to
+ disable the timer and then call this function to re-enable it.
+
+Arguments:
+
+ FdoExtension
+
+Return Value:
+
+ None.
+
+--*/
+VOID
+ClasspEnableTimer(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ )
+{
+ PCLASS_PRIVATE_FDO_DATA fdoData = NULL;
+
+ if (FdoExtension->CommonExtension.IsFdo) {
+ fdoData = FdoExtension->PrivateFdoData;
+ }
+
+ if (fdoData != NULL) {
+ //
+ // The timer should have already been initialized, but if that's not
+ // the case it's not the end of the world. We can attempt to
+ // initialize it now.
+ //
+ NT_ASSERT(fdoData->TimerInitialized);
+ if (fdoData->TimerInitialized == FALSE) {
+ NTSTATUS status;
+ status = ClasspInitializeTimer(FdoExtension);
+ if (NT_SUCCESS(status) == FALSE) {
+ return;
+ }
+ }
+
+#if (NTDDI_VERSION >= NTDDI_WINBLUE)
+ if (fdoData->TickTimer != NULL) {
+ EXT_SET_PARAMETERS parameters;
+ LONGLONG period = TICK_TIMER_PERIOD_IN_MSEC * (10 * 1000); // Convert to units of 100ns.
+ LONGLONG dueTime = period * (-1); // Negative sign indicates dueTime is relative.
+
+ ExInitializeSetTimerParameters(&parameters);
+
+ //
+ // Set the no-wake tolerance to "unlimited" if the conditions below
+ // are met. An "unlimited" no-wake tolerance means that the timer
+ // will *never* wake the processor if the processor is in a
+ // low-power state.
+ // 1. The screen is off.
+ // 2. The class driver is *not* a consumer of the tick timer (ClassTick is NULL).
+ // 3. This is a disk device.
+ // Otherwise the tolerance is set to the normal tolerable delay.
+ //
+ if (ClasspScreenOff &&
+ FdoExtension->CommonExtension.DriverExtension->InitData.ClassTick == NULL &&
+ FdoExtension->DeviceObject->DeviceType == FILE_DEVICE_DISK) {
+ parameters.NoWakeTolerance = EX_TIMER_UNLIMITED_TOLERANCE;
+ } else {
+ parameters.NoWakeTolerance = TICK_TIMER_DELAY_IN_MSEC * (10 * 1000);
+ }
+
+ fdoData->CurrentNoWakeTolerance = parameters.NoWakeTolerance;
+
+ ExSetTimer(fdoData->TickTimer,
+ dueTime,
+ period,
+ &parameters);
+
+ fdoData->TickTimerEnabled = TRUE;
+ } else {
+ NT_ASSERT(fdoData->TickTimer != NULL);
+ }
+#else
+ //
+ // Start the periodic tick timer using a coalescable timer with some delay
+ //
+ {
+ LARGE_INTEGER timeout;
+ timeout.QuadPart = TICK_TIMER_PERIOD_IN_MSEC * (10 * 1000) * (-1);
+ KeSetCoalescableTimer(&fdoData->TickTimer,
+ timeout, TICK_TIMER_PERIOD_IN_MSEC, TICK_TIMER_DELAY_IN_MSEC,
+ &fdoData->TickTimerDpc);
+ fdoData->TickTimerEnabled = TRUE;
+ }
+#endif
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClasspEnableTimer: Periodic tick timer enabled "
+ "for device %p\n", FdoExtension->DeviceObject));
+
+ }
+
+} // end ClasspEnableTimer()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClasspDisableTimer() - ISSUE-2000/02/20-henrygab - not documented
+
+Routine Description:
+
+ This routine
+
+Arguments:
+
+ FdoExtension
+
+Return Value:
+
+--*/
+VOID
+ClasspDisableTimer(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ )
+{
+ PCLASS_PRIVATE_FDO_DATA fdoData = NULL;
+
+ if (FdoExtension->CommonExtension.IsFdo) {
+ fdoData = FdoExtension->PrivateFdoData;
+ }
+
+ if (fdoData && fdoData->TimerInitialized == TRUE) {
+
+ //
+ // we are only going to stop the actual timer in remove device routine
+ // or when done transitioning to D3 (timer will be started again when
+ // done transitioning to D0).
+ //
+ // it is the responsibility of the code within the timer routine to
+ // check if the device is removed and not processing io for the final
+ // call.
+ // this keeps the code clean and prevents lots of bugs.
+ //
+#if (NTDDI_VERSION >= NTDDI_WINBLUE)
+ NT_ASSERT(fdoData->TickTimer != NULL);
+ ExCancelTimer(fdoData->TickTimer, NULL);
+#else
+ KeCancelTimer(&fdoData->TickTimer);
+#endif
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClasspDisableTimer: Periodic tick timer disabled "
+ "for device %p\n", FdoExtension->DeviceObject));
+ fdoData->TickTimerEnabled = FALSE;
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN, "ClasspDisableTimer: Timer never initialized\n"));
+
+ }
+
+ return;
+} // end ClasspDisableTimer()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClasspFailurePredict() - ISSUE-2000/02/20-henrygab - not documented
+
+Routine Description:
+
+ This routine
+
+Arguments:
+
+ DeviceObject - Device object
+ Context - Context (PFAILURE_PREDICTION_INFO)
+
+Return Value:
+
+Note: this function can be called (via the workitem callback) after the paging device is shut down,
+ so it must be PAGE LOCKED.
+--*/
+VOID
+ClasspFailurePredict(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PVOID Context
+ )
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
+ PIO_WORKITEM workItem;
+ STORAGE_PREDICT_FAILURE checkFailure = {0};
+ SCSI_ADDRESS scsiAddress = {0};
+ PFAILURE_PREDICTION_INFO Info = (PFAILURE_PREDICTION_INFO)Context;
+
+ NTSTATUS status;
+
+ if (Info == NULL) {
+ NT_ASSERT(Info != NULL);
+ return;
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_WMI, "ClasspFailurePredict: Polling for failure\n"));
+
+ //
+ // Mark the work item as inactive and reset the countdown timer. we
+ // can't risk freeing the work item until we've released the remove-lock
+ // though - if we do it might get reused as a tag before we can release
+ // the lock.
+ //
+
+ InterlockedExchange((volatile LONG *)&Info->CountDown, Info->Period);
+ workItem = InterlockedExchangePointer(&(Info->WorkQueueItem), NULL);
+
+ if (ClasspCanSendPollingIrp(fdoExtension)) {
+
+ KEVENT event;
+ PDEVICE_OBJECT topOfStack;
+ PIRP irp = NULL;
+ IO_STATUS_BLOCK ioStatus;
+ NTSTATUS activateStatus = STATUS_UNSUCCESSFUL;
+
+ //
+ // Take an active reference on the device to ensure it is powered up
+ // while we do the failure prediction query.
+ //
+ if (fdoExtension->FunctionSupportInfo->IdlePower.IdlePowerEnabled) {
+ activateStatus = ClasspPowerActivateDevice(DeviceObject);
+ }
+
+ KeInitializeEvent(&event, SynchronizationEvent, FALSE);
+
+ topOfStack = IoGetAttachedDeviceReference(DeviceObject);
+
+ //
+ // Send down irp to see if drive is predicting failure
+ //
+
+ irp = IoBuildDeviceIoControlRequest(
+ IOCTL_STORAGE_PREDICT_FAILURE,
+ topOfStack,
+ NULL,
+ 0,
+ &checkFailure,
+ sizeof(STORAGE_PREDICT_FAILURE),
+ FALSE,
+ &event,
+ &ioStatus);
+
+
+ if (irp != NULL) {
+
+
+ status = IoCallDriver(topOfStack, irp);
+ if (status == STATUS_PENDING) {
+ (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
+ status = ioStatus.Status;
+ }
+
+
+ } else {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ if (NT_SUCCESS(status) && (checkFailure.PredictFailure)) {
+
+ checkFailure.PredictFailure = 512;
+
+ //
+ // Send down irp to get scsi address
+ //
+ KeInitializeEvent(&event, SynchronizationEvent, FALSE);
+
+ RtlZeroMemory(&scsiAddress, sizeof(SCSI_ADDRESS));
+ irp = IoBuildDeviceIoControlRequest(
+ IOCTL_SCSI_GET_ADDRESS,
+ topOfStack,
+ NULL,
+ 0,
+ &scsiAddress,
+ sizeof(SCSI_ADDRESS),
+ FALSE,
+ &event,
+ &ioStatus);
+
+ if (irp != NULL) {
+
+
+ status = IoCallDriver(topOfStack, irp);
+ if (status == STATUS_PENDING) {
+ (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
+ }
+
+ }
+
+ ClassNotifyFailurePredicted(fdoExtension,
+ (PUCHAR)&checkFailure,
+ sizeof(checkFailure),
+ (BOOLEAN)(fdoExtension->FailurePredicted == FALSE),
+ 2,
+ scsiAddress.PathId,
+ scsiAddress.TargetId,
+ scsiAddress.Lun);
+
+ fdoExtension->FailurePredicted = TRUE;
+
+ }
+
+ ObDereferenceObject(topOfStack);
+
+ //
+ // Update the failure prediction query time and release the active
+ // reference.
+ //
+
+ KeQuerySystemTime(&(Info->LastFailurePredictionQueryTime));
+
+ if (NT_SUCCESS(activateStatus)) {
+ ClasspPowerIdleDevice(DeviceObject);
+ }
+ }
+
+ ClassReleaseRemoveLock(DeviceObject, (PIRP) workItem);
+ IoFreeWorkItem(workItem);
+ return;
+} // end ClasspFailurePredict()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassNotifyFailurePredicted() ISSUE-alanwar-2000/02/20 - not documented
+
+Routine Description:
+
+Arguments:
+
+Return Value:
+
+--*/
+_IRQL_requires_max_(DISPATCH_LEVEL)
+VOID
+ClassNotifyFailurePredicted(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ _In_reads_bytes_(BufferSize) PUCHAR Buffer,
+ _In_ ULONG BufferSize,
+ _In_ BOOLEAN LogError,
+ _In_ ULONG UniqueErrorValue,
+ _In_ UCHAR PathId,
+ _In_ UCHAR TargetId,
+ _In_ UCHAR Lun
+ )
+{
+ PIO_ERROR_LOG_PACKET logEntry;
+ EVENT_DESCRIPTOR eventDescriptor;
+ PCLASS_DRIVER_EXTENSION driverExtension;
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_WMI, "ClasspFailurePredictPollCompletion: Failure predicted for device %p\n", FdoExtension->DeviceObject));
+
+ //
+ // Fire off a WMI event
+ //
+ ClassWmiFireEvent(FdoExtension->DeviceObject,
+ (LPGUID)&StoragePredictFailureEventGuid,
+ 0,
+ BufferSize,
+ Buffer);
+ //
+ // Log an error into the eventlog
+ //
+
+ if (LogError)
+ {
+ logEntry = IoAllocateErrorLogEntry(
+ FdoExtension->DeviceObject,
+ sizeof(IO_ERROR_LOG_PACKET) + (3 * sizeof(ULONG)));
+
+ if (logEntry != NULL)
+ {
+
+ logEntry->FinalStatus = STATUS_SUCCESS;
+ logEntry->ErrorCode = IO_WRN_FAILURE_PREDICTED;
+ logEntry->SequenceNumber = 0;
+ logEntry->MajorFunctionCode = IRP_MJ_DEVICE_CONTROL;
+ logEntry->IoControlCode = IOCTL_STORAGE_PREDICT_FAILURE;
+ logEntry->RetryCount = 0;
+ logEntry->UniqueErrorValue = UniqueErrorValue;
+ logEntry->DumpDataSize = 3;
+
+ logEntry->DumpData[0] = PathId;
+ logEntry->DumpData[1] = TargetId;
+ logEntry->DumpData[2] = Lun;
+
+ //
+ // Write the error log packet.
+ //
+
+ IoWriteErrorLogEntry(logEntry);
+ }
+ }
+
+ //
+ // Send ETW event if LogError is TRUE. ClassInterpretSenseInfo sets this
+ // to FALSE. So if failure is predicted for the first time and UniqueErrorValue
+ // is 4 (used by ClassInterpretSenseInfo) then send ETW event.
+ //
+
+ if ((LogError == TRUE) ||
+ ((FdoExtension->FailurePredicted == FALSE) && (UniqueErrorValue == 4))) {
+
+#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
+ driverExtension = IoGetDriverObjectExtension(FdoExtension->DeviceObject->DriverObject, CLASS_DRIVER_EXTENSION_KEY);
+
+ if ((driverExtension != NULL) && (driverExtension->EtwHandle != 0)) {
+ EventDescCreate(&eventDescriptor,
+ 1, // Id
+ 0, // Version
+ 0, // Channel
+ 0, // Level
+ 0, // Task
+ 0, // OpCode
+ 0); // Keyword
+
+ EtwWrite(driverExtension->EtwHandle,
+ &eventDescriptor,
+ NULL,
+ 0,
+ NULL);
+ }
+ }
+
+} // end ClassNotifyFailurePredicted()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassSetFailurePredictionPoll()
+
+Routine Description:
+
+ This routine enables polling for failure prediction, setting the timer
+ to fire every N seconds as specified by the PollingPeriod.
+
+Arguments:
+
+ FdoExtension - the device to setup failure prediction for.
+
+ FailurePredictionMethod - specific failure prediction method to use
+ if set to FailurePredictionNone, will disable failure detection
+
+ PollingPeriod - if 0 then no change to current polling timer
+
+Return Value:
+
+ NT Status
+
+--*/
+_IRQL_requires_max_(PASSIVE_LEVEL)
+NTSTATUS
+ClassSetFailurePredictionPoll(
+ _Inout_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ _In_ FAILURE_PREDICTION_METHOD FailurePredictionMethod,
+ _In_ ULONG PollingPeriod
+ )
+{
+ PFAILURE_PREDICTION_INFO info;
+ NTSTATUS status;
+
+ PAGED_CODE();
+
+ if (FdoExtension->FailurePredictionInfo == NULL) {
+
+ if (FailurePredictionMethod != FailurePredictionNone) {
+
+ info = ExAllocatePoolWithTag(NonPagedPoolNx,
+ sizeof(FAILURE_PREDICTION_INFO),
+ CLASS_TAG_FAILURE_PREDICT);
+
+ if (info == NULL) {
+
+ return STATUS_INSUFFICIENT_RESOURCES;
+
+ }
+
+ KeInitializeEvent(&info->Event, SynchronizationEvent, TRUE);
+
+ info->WorkQueueItem = NULL;
+ info->Period = DEFAULT_FAILURE_PREDICTION_PERIOD;
+
+ KeQuerySystemTime(&(info->LastFailurePredictionQueryTime));
+
+ } else {
+
+ //
+ // FaultPrediction has not been previously initialized, nor
+ // is it being initialized now. No need to do anything.
+ //
+ return STATUS_SUCCESS;
+
+ }
+
+ FdoExtension->FailurePredictionInfo = info;
+
+ } else {
+
+ info = FdoExtension->FailurePredictionInfo;
+
+ }
+
+ /*
+ * Make sure the user-mode thread is not suspended while we hold the synchronization event.
+ */
+ KeEnterCriticalRegion();
+
+ (VOID)KeWaitForSingleObject(&info->Event,
+ UserRequest,
+ KernelMode,
+ FALSE,
+ NULL);
+
+
+ //
+ // Reset polling period and counter. Setup failure detection type
+ //
+
+ if (PollingPeriod != 0) {
+
+ InterlockedExchange((volatile LONG *)&info->Period, PollingPeriod);
+ }
+
+ InterlockedExchange((volatile LONG *)&info->CountDown, info->Period);
+
+ info->Method = FailurePredictionMethod;
+ if (FailurePredictionMethod != FailurePredictionNone) {
+
+ ClasspEnableTimer(FdoExtension);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_WMI, "ClassEnableFailurePredictPoll: Enabled for "
+ "device %p\n", FdoExtension->DeviceObject));
+
+ } else {
+
+ ClasspDisableTimer(FdoExtension);
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_WMI, "ClassEnableFailurePredictPoll: Disabled for "
+ "device %p\n", FdoExtension->DeviceObject));
+ }
+ status = STATUS_SUCCESS;
+
+
+ KeSetEvent(&info->Event, IO_NO_INCREMENT, FALSE);
+
+ KeLeaveCriticalRegion();
+
+ return status;
+} // end ClassSetFailurePredictionPoll()
+
+BOOLEAN
+ClasspFailurePredictionPeriodMissed(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ )
+/*
+Routine Description:
+ This routine can be used to determine if a failure prediction polling
+ period has been missed. That is, the time since the last failure
+ prediction IOCTL has been sent is greater than the failure prediction
+ polling period. This can happen if failure prediction polling was
+ disabled, such as when the device is in D3 or when the screen is off.
+
+Parameters:
+ FdoExtension - FDO extension. The caller should make sure the FDO
+ extension is valid. The FailurePredictionInfo structure should also
+ be valid and the failure prediction method should not be "none".
+
+Returns:
+ TRUE if there was one or more failure prediction polling periods that was
+ missed.
+ FALSE otherwise.
+*/
+{
+ LARGE_INTEGER currentTime;
+ LARGE_INTEGER timeDifference;
+ BOOLEAN missedPeriod = FALSE;
+
+ NT_ASSERT(FdoExtension);
+ NT_ASSERT(FdoExtension->FailurePredictionInfo);
+ NT_ASSERT(FdoExtension->FailurePredictionInfo->Method != FailurePredictionNone);
+
+ //
+ // Find the difference between the last failure prediction
+ // query and the current time and convert it to seconds.
+ //
+ KeQuerySystemTime(&currentTime);
+ timeDifference.QuadPart = currentTime.QuadPart - FdoExtension->FailurePredictionInfo->LastFailurePredictionQueryTime.QuadPart;
+ timeDifference.QuadPart /= (10LL * 1000LL * 1000LL);
+
+ if (timeDifference.QuadPart >= FdoExtension->FailurePredictionInfo->Period) {
+ missedPeriod = TRUE;
+ }
+
+ return missedPeriod;
+}
+
+
diff --git a/storage/class/classpnp/src/class.c b/storage/class/classpnp/src/class.c
new file mode 100644
index 00000000..2f92e453
--- /dev/null
+++ b/storage/class/classpnp/src/class.c
@@ -0,0 +1,16415 @@
+/*++
+
+Copyright (C) Microsoft Corporation, 1991 - 2010
+
+Module Name:
+
+ class.c
+
+Abstract:
+
+ SCSI class driver routines
+
+Environment:
+
+ kernel mode only
+
+Notes:
+
+
+Revision History:
+
+--*/
+
+#define CLASS_INIT_GUID 1
+#define DEBUG_MAIN_SOURCE 1
+
+#include "classp.h"
+#include "debug.h"
+#include <process.h>
+#include <devpkey.h>
+#include <ntiologc.h>
+
+
+#ifdef DEBUG_USE_WPP
+#include "class.tmh"
+#endif
+
+#ifdef ALLOC_PRAGMA
+ #pragma alloc_text(INIT, DriverEntry)
+ #pragma alloc_text(PAGE, ClassAddDevice)
+ #pragma alloc_text(PAGE, ClassClaimDevice)
+ #pragma alloc_text(PAGE, ClassCreateDeviceObject)
+ #pragma alloc_text(PAGE, ClassDispatchPnp)
+ #pragma alloc_text(PAGE, ClassGetDescriptor)
+ #pragma alloc_text(PAGE, ClassGetPdoId)
+ #pragma alloc_text(PAGE, ClassInitialize)
+ #pragma alloc_text(PAGE, ClassInitializeEx)
+ #pragma alloc_text(PAGE, ClassInvalidateBusRelations)
+ #pragma alloc_text(PAGE, ClassMarkChildMissing)
+ #pragma alloc_text(PAGE, ClassMarkChildrenMissing)
+ #pragma alloc_text(PAGE, ClassModeSense)
+ #pragma alloc_text(PAGE, ClassPnpQueryFdoRelations)
+ #pragma alloc_text(PAGE, ClassPnpStartDevice)
+ #pragma alloc_text(PAGE, ClassQueryPnpCapabilities)
+ #pragma alloc_text(PAGE, ClassQueryTimeOutRegistryValue)
+ #pragma alloc_text(PAGE, ClassRemoveDevice)
+ #pragma alloc_text(PAGE, ClassRetrieveDeviceRelations)
+ #pragma alloc_text(PAGE, ClassUpdateInformationInRegistry)
+ #pragma alloc_text(PAGE, ClassSendDeviceIoControlSynchronous)
+ #pragma alloc_text(PAGE, ClassUnload)
+ #pragma alloc_text(PAGE, ClasspAllocateReleaseRequest)
+ #pragma alloc_text(PAGE, ClasspFreeReleaseRequest)
+ #pragma alloc_text(PAGE, ClasspInitializeHotplugInfo)
+ #pragma alloc_text(PAGE, ClasspRegisterMountedDeviceInterface)
+ #pragma alloc_text(PAGE, ClasspScanForClassHacks)
+ #pragma alloc_text(PAGE, ClasspScanForSpecialInRegistry)
+ #pragma alloc_text(PAGE, ClasspModeSense)
+ #pragma alloc_text(PAGE, ClasspIsPortable)
+ #pragma alloc_text(PAGE, ClassAcquireChildLock)
+ #pragma alloc_text(PAGE, ClassDetermineTokenOperationCommandSupport)
+ #pragma alloc_text(PAGE, ClassDeviceProcessOffloadRead)
+ #pragma alloc_text(PAGE, ClassDeviceProcessOffloadWrite)
+ #pragma alloc_text(PAGE, ClasspServicePopulateTokenTransferRequest)
+ #pragma alloc_text(PAGE, ClasspServiceWriteUsingTokenTransferRequest)
+#if (NTDDI_VERSION >= NTDDI_WINBLUE)
+ #pragma alloc_text(PAGE, ClassModeSenseEx)
+#endif
+#endif
+
+#pragma prefast(disable:28159, "There are certain cases when we have to bugcheck...")
+
+IO_COMPLETION_ROUTINE ClassCheckVerifyComplete;
+
+
+ULONG ClassPnpAllowUnload = TRUE;
+ULONG ClassMaxInterleavePerCriticalIo = CLASS_MAX_INTERLEAVE_PER_CRITICAL_IO;
+CONST LARGE_INTEGER Magic10000 = {0xe219652c, 0xd1b71758};
+GUID StoragePredictFailureDPSGuid = WDI_STORAGE_PREDICT_FAILURE_DPS_GUID;
+
+#define FirstDriveLetter 'C'
+#define LastDriveLetter 'Z'
+
+BOOLEAN UseQPCTime = FALSE;
+
+//
+// Keep track of whether security cookie is initialized or not. This is
+// required by SDL.
+//
+
+BOOLEAN InitSecurityCookie = FALSE;
+
+//
+// List Identifier for offload data transfer operations
+//
+ULONG MaxTokenOperationListIdentifier = MAX_TOKEN_LIST_IDENTIFIERS;
+volatile ULONG TokenOperationListIdentifier = (ULONG)-1;
+
+//
+// List of FDOs that have enabled idle power management.
+//
+LIST_ENTRY IdlePowerFDOList = {0};
+KGUARDED_MUTEX IdlePowerFDOListMutex;
+
+//
+// Handle used to register for power setting notifications.
+//
+PVOID PowerSettingNotificationHandle;
+
+//
+// Handle used to register for screen state setting notifications.
+//
+PVOID ScreenStateNotificationHandle;
+
+//
+// Disk idle timeout in milliseconds.
+// We default this to 0xFFFFFFFF as this is what the power manager considers
+// "never" and ensures we do not set a disk idle timeout until the power
+// manager calls us back with a different value.
+//
+ULONG DiskIdleTimeoutInMS = 0xFFFFFFFF;
+
+
+NTSTATUS DllUnload(VOID)
+{
+ DbgPrintEx(DPFLTR_CLASSPNP_ID, DPFLTR_INFO_LEVEL, "classpnp.sys is now unloading\n");
+
+ if (PowerSettingNotificationHandle) {
+ PoUnregisterPowerSettingCallback(PowerSettingNotificationHandle);
+ PowerSettingNotificationHandle = NULL;
+ }
+
+ if (ScreenStateNotificationHandle) {
+ PoUnregisterPowerSettingCallback(ScreenStateNotificationHandle);
+ ScreenStateNotificationHandle = NULL;
+ }
+
+
+ return STATUS_SUCCESS;
+}
+
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+DriverEntry()
+
+Routine Description:
+
+ Temporary entry point needed to initialize the class system dll.
+ It doesn't do anything.
+
+Arguments:
+
+ DriverObject - Pointer to the driver object created by the system.
+
+Return Value:
+
+ STATUS_SUCCESS
+
+--*/
+NTSTATUS
+DriverEntry(
+ IN PDRIVER_OBJECT DriverObject,
+ IN PUNICODE_STRING RegistryPath
+ )
+{
+ UNREFERENCED_PARAMETER(DriverObject);
+ UNREFERENCED_PARAMETER(RegistryPath);
+
+ return STATUS_SUCCESS;
+}
+
+
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassInitialize()
+
+Routine Description:
+
+ This routine is called by a class driver during its
+ DriverEntry routine to initialize the driver.
+
+Arguments:
+
+ Argument1 - Driver Object.
+ Argument2 - Registry Path.
+ InitializationData - Device-specific driver's initialization data.
+
+Return Value:
+
+ A valid return code for a DriverEntry routine.
+
+--*/
+_IRQL_requires_max_(PASSIVE_LEVEL)
+_Must_inspect_result_
+ULONG
+ClassInitialize(
+ _In_ PVOID Argument1,
+ _In_ PVOID Argument2,
+ _In_ PCLASS_INIT_DATA InitializationData
+ )
+{
+ PDRIVER_OBJECT DriverObject = Argument1;
+ PUNICODE_STRING RegistryPath = Argument2;
+
+ PCLASS_DRIVER_EXTENSION driverExtension;
+
+ NTSTATUS status;
+
+
+
+ PAGED_CODE();
+
+ //
+ // Initialize the security cookie if needed.
+ //
+ if (InitSecurityCookie == FALSE) {
+ __security_init_cookie();
+ InitSecurityCookie = TRUE;
+ }
+
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "\n\nSCSI Class Driver\n"));
+
+ ClasspInitializeDebugGlobals();
+
+ //
+ // Validate the length of this structure. This is effectively a
+ // version check.
+ //
+
+ if (InitializationData->InitializationDataSize != sizeof(CLASS_INIT_DATA)) {
+
+ //
+ // This DebugPrint is to help third-party driver writers
+ //
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClassInitialize: Class driver wrong version\n"));
+ return (ULONG) STATUS_REVISION_MISMATCH;
+ }
+
+ //
+ // Check that each required entry is not NULL. Note that Shutdown, Flush and Error
+ // are not required entry points.
+ //
+
+ if ((!InitializationData->FdoData.ClassDeviceControl) ||
+ (!((InitializationData->FdoData.ClassReadWriteVerification) ||
+ (InitializationData->ClassStartIo))) ||
+ (!InitializationData->ClassAddDevice) ||
+ (!InitializationData->FdoData.ClassStartDevice)) {
+
+ //
+ // This DebugPrint is to help third-party driver writers
+ //
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT,
+ "ClassInitialize: Class device-specific driver missing required "
+ "FDO entry\n"));
+
+ return (ULONG) STATUS_REVISION_MISMATCH;
+ }
+
+ if ((InitializationData->ClassEnumerateDevice) &&
+ ((!InitializationData->PdoData.ClassDeviceControl) ||
+ (!InitializationData->PdoData.ClassStartDevice) ||
+ (!((InitializationData->PdoData.ClassReadWriteVerification) ||
+ (InitializationData->ClassStartIo))))) {
+
+ //
+ // This DebugPrint is to help third-party driver writers
+ //
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClassInitialize: Class device-specific missing "
+ "required PDO entry\n"));
+
+ return (ULONG) STATUS_REVISION_MISMATCH;
+ }
+
+ if((InitializationData->FdoData.ClassStopDevice == NULL) ||
+ ((InitializationData->ClassEnumerateDevice != NULL) &&
+ (InitializationData->PdoData.ClassStopDevice == NULL))) {
+
+ //
+ // This DebugPrint is to help third-party driver writers
+ //
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClassInitialize: Class device-specific missing "
+ "required PDO entry\n"));
+ NT_ASSERT(FALSE);
+ return (ULONG) STATUS_REVISION_MISMATCH;
+ }
+
+ //
+ // Setup the default power handlers if the class driver didn't provide
+ // any.
+ //
+
+ if(InitializationData->FdoData.ClassPowerDevice == NULL) {
+ InitializationData->FdoData.ClassPowerDevice = ClassMinimalPowerHandler;
+ }
+
+ if((InitializationData->ClassEnumerateDevice != NULL) &&
+ (InitializationData->PdoData.ClassPowerDevice == NULL)) {
+ InitializationData->PdoData.ClassPowerDevice = ClassMinimalPowerHandler;
+ }
+
+ //
+ // warn that unload is not supported
+ //
+ // ISSUE-2000/02/03-peterwie
+ // We should think about making this a fatal error.
+ //
+
+ if(InitializationData->ClassUnload == NULL) {
+
+ //
+ // This DebugPrint is to help third-party driver writers
+ //
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, "ClassInitialize: driver does not support unload %wZ\n",
+ RegistryPath));
+ }
+
+ //
+ // Create an extension for the driver object
+ //
+#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
+ status = IoAllocateDriverObjectExtension(DriverObject, CLASS_DRIVER_EXTENSION_KEY, sizeof(CLASS_DRIVER_EXTENSION), &driverExtension);
+
+ if(NT_SUCCESS(status)) {
+
+ //
+ // Copy the registry path into the driver extension so we can use it later
+ //
+
+ driverExtension->RegistryPath.Length = RegistryPath->Length;
+ driverExtension->RegistryPath.MaximumLength = RegistryPath->MaximumLength;
+
+ driverExtension->RegistryPath.Buffer =
+ ExAllocatePoolWithTag(PagedPool,
+ RegistryPath->MaximumLength,
+ '1CcS');
+
+ if(driverExtension->RegistryPath.Buffer == NULL) {
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ return status;
+ }
+
+ RtlCopyUnicodeString(
+ &(driverExtension->RegistryPath),
+ RegistryPath);
+
+ //
+ // Copy the initialization data into the driver extension so we can reuse
+ // it during our add device routine
+ //
+
+ RtlCopyMemory(
+ &(driverExtension->InitData),
+ InitializationData,
+ sizeof(CLASS_INIT_DATA));
+
+ driverExtension->DeviceCount = 0;
+
+ ClassInitializeDispatchTables(driverExtension);
+
+ } else if (status == STATUS_OBJECT_NAME_COLLISION) {
+
+ //
+ // The extension already exists - get a pointer to it
+ //
+
+#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
+ driverExtension = IoGetDriverObjectExtension(DriverObject, CLASS_DRIVER_EXTENSION_KEY);
+
+ NT_ASSERT(driverExtension != NULL);
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClassInitialize: Class driver extension could not be "
+ "allocated %lx\n", status));
+ return status;
+ }
+
+
+ //
+ // Update driver object with entry points.
+ //
+
+#pragma prefast(push)
+#pragma prefast(disable:28175, "Accessing DRIVER_OBJECT fileds is OK here since this function " \
+ "is supposed to be invoked from DriverEntry only")
+ DriverObject->MajorFunction[IRP_MJ_CREATE] = ClassGlobalDispatch;
+ DriverObject->MajorFunction[IRP_MJ_CLOSE] = ClassGlobalDispatch;
+ DriverObject->MajorFunction[IRP_MJ_READ] = ClassGlobalDispatch;
+ DriverObject->MajorFunction[IRP_MJ_WRITE] = ClassGlobalDispatch;
+ DriverObject->MajorFunction[IRP_MJ_SCSI] = ClassGlobalDispatch;
+ DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = ClassGlobalDispatch;
+ DriverObject->MajorFunction[IRP_MJ_SHUTDOWN] = ClassGlobalDispatch;
+ DriverObject->MajorFunction[IRP_MJ_FLUSH_BUFFERS] = ClassGlobalDispatch;
+ DriverObject->MajorFunction[IRP_MJ_PNP] = ClassGlobalDispatch;
+ DriverObject->MajorFunction[IRP_MJ_POWER] = ClassGlobalDispatch;
+ DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = ClassGlobalDispatch;
+
+ if (InitializationData->ClassStartIo) {
+ DriverObject->DriverStartIo = ClasspStartIo;
+ }
+
+ if ((InitializationData->ClassUnload) && (ClassPnpAllowUnload == TRUE)) {
+ DriverObject->DriverUnload = ClassUnload;
+ } else {
+ DriverObject->DriverUnload = NULL;
+ }
+
+ DriverObject->DriverExtension->AddDevice = ClassAddDevice;
+#pragma prefast(pop)
+
+
+ //
+ // Register for event tracing
+ //
+ if (driverExtension->EtwHandle == 0) {
+ status = EtwRegister(&StoragePredictFailureDPSGuid,
+ NULL,
+ NULL,
+ &driverExtension->EtwHandle);
+ if (!NT_SUCCESS(status)) {
+ driverExtension->EtwHandle = 0;
+ }
+ WPP_INIT_TRACING(DriverObject, RegistryPath);
+ }
+
+
+ //
+ // Ensure these are only initialized once.
+ //
+ if (IdlePowerFDOList.Flink == NULL) {
+ InitializeListHead(&IdlePowerFDOList);
+ KeInitializeGuardedMutex(&IdlePowerFDOListMutex);
+ }
+
+ status = STATUS_SUCCESS;
+ return status;
+} // end ClassInitialize()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassInitializeEx()
+
+Routine Description:
+
+ This routine is allows the caller to do any extra initialization or
+ setup that is not done in ClassInitialize. The operation is
+ controlled by the GUID that is passed and the contents of the Data
+ parameter is dependent upon the GUID.
+
+ This is the list of supported operations:
+
+ GUID_CLASSPNP_QUERY_REGINFOEX == CLASS_QUERY_WMI_REGINFO_EX_LIST
+
+ Initialized classpnp to callback a PCLASS_QUERY_WMI_REGINFO_EX
+ callback instead of a PCLASS_QUERY_WMI_REGINFO callback. The
+ former callback allows the driver to specify the name of the
+ mof resource.
+
+ GUID_CLASSPNP_SENSEINFO2 == CLASS_INTERPRET_SENSE_INFO2
+
+ Initialize classpnp to callback into class drive for interpretation
+ of all sense info, and to indicate the count of "history" to keep
+ for each packet.
+
+ GUID_CLASSPNP_WORKING_SET == CLASS_WORKING_SET
+
+ Allow class driver to override the min and max working set transfer
+ packet value used in classpnp.
+
+ GUID_CLASSPNP_SRB_SUPPORT == ULONG
+
+ Allow class driver to provide supported SRB types.
+
+Arguments:
+
+ DriverObject
+ Guid
+ Data
+
+Return Value:
+
+ Status Code
+
+--*/
+_IRQL_requires_max_(PASSIVE_LEVEL)
+_Must_inspect_result_
+ULONG
+ClassInitializeEx(
+ _In_ PDRIVER_OBJECT DriverObject,
+ _In_ LPGUID Guid,
+ _In_ PVOID Data
+ )
+{
+ PCLASS_DRIVER_EXTENSION driverExtension;
+
+ NTSTATUS status;
+
+ PAGED_CODE();
+
+#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
+ driverExtension = IoGetDriverObjectExtension( DriverObject, CLASS_DRIVER_EXTENSION_KEY );
+
+ if (driverExtension == NULL)
+ {
+ NT_ASSERT(FALSE);
+ return (ULONG)STATUS_UNSUCCESSFUL;
+ }
+
+ if (IsEqualGUID(Guid, &ClassGuidQueryRegInfoEx))
+ {
+ PCLASS_QUERY_WMI_REGINFO_EX_LIST List;
+
+ //
+ // Indicate the device supports PCLASS_QUERY_REGINFO_EX
+ // callback instead of PCLASS_QUERY_REGINFO callback.
+ //
+ List = (PCLASS_QUERY_WMI_REGINFO_EX_LIST)Data;
+
+ if (List->Size == sizeof(CLASS_QUERY_WMI_REGINFO_EX_LIST))
+ {
+ driverExtension->ClassFdoQueryWmiRegInfoEx = List->ClassFdoQueryWmiRegInfoEx;
+ driverExtension->ClassPdoQueryWmiRegInfoEx = List->ClassPdoQueryWmiRegInfoEx;
+ status = STATUS_SUCCESS;
+ } else {
+ status = STATUS_INVALID_PARAMETER;
+ }
+ }
+ else if (IsEqualGUID(Guid, &ClassGuidWorkingSet))
+ {
+ PCLASS_WORKING_SET infoOriginal = (PCLASS_WORKING_SET)Data;
+ PCLASS_WORKING_SET info = NULL;
+
+ // only try to allocate memory for cached copy if size is correct
+ if (infoOriginal->Size != sizeof(CLASS_WORKING_SET))
+ {
+ // incorrect size -- client programming error
+ status = STATUS_INVALID_PARAMETER;
+ }
+ else
+ {
+ info = ExAllocatePoolWithTag(NonPagedPoolNx,
+ sizeof(CLASS_WORKING_SET),
+ CLASS_TAG_WORKING_SET
+ );
+ if (info == NULL)
+ {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ else
+ {
+ // cache the structure internally
+ RtlCopyMemory(info, infoOriginal, sizeof(CLASS_WORKING_SET));
+ status = STATUS_SUCCESS;
+ }
+ }
+ // if we successfully cached a copy, validate all the data within
+ if (NT_SUCCESS(status))
+ {
+ if (info->Size != sizeof(CLASS_WORKING_SET))
+ {
+ // incorrect size -- client programming error
+ status = STATUS_INVALID_PARAMETER;
+ }
+ else if (info->XferPacketsWorkingSetMaximum > CLASS_WORKING_SET_MAXIMUM)
+ {
+ // too many requested in the working set
+ status = STATUS_INVALID_PARAMETER;
+ }
+ else if (info->XferPacketsWorkingSetMinimum > CLASS_WORKING_SET_MAXIMUM)
+ {
+ // too many requested in the working set
+ status = STATUS_INVALID_PARAMETER;
+ }
+ else if (driverExtension->InitData.FdoData.DeviceType != FILE_DEVICE_CD_ROM)
+ {
+ // classpnp developer wants to restrict this code path
+ // for now to CDROM devices only.
+ status = STATUS_INVALID_DEVICE_REQUEST;
+ }
+ else if (driverExtension->WorkingSet != NULL)
+ {
+ // not allowed to change it once it is set for a driver
+ status = STATUS_INVALID_PARAMETER;
+ NT_ASSERT(FALSE);
+ }
+ }
+ // save results or cleanup
+ if (NT_SUCCESS(status))
+ {
+ driverExtension->WorkingSet = info; info = NULL;
+ }
+ else
+ {
+ FREE_POOL( info );
+ }
+ }
+ else if (IsEqualGUID(Guid, &ClassGuidSenseInfo2))
+ {
+ PCLASS_INTERPRET_SENSE_INFO2 infoOriginal = (PCLASS_INTERPRET_SENSE_INFO2)Data;
+ PCLASS_INTERPRET_SENSE_INFO2 info = NULL;
+
+ // only try to allocate memory for cached copy if size is correct
+ if (infoOriginal->Size != sizeof(CLASS_INTERPRET_SENSE_INFO2))
+ {
+ // incorrect size -- client programming error
+ status = STATUS_INVALID_PARAMETER;
+ }
+ else
+ {
+ info = ExAllocatePoolWithTag(NonPagedPoolNx,
+ sizeof(CLASS_INTERPRET_SENSE_INFO2),
+ CLASS_TAG_SENSE2
+ );
+ if (info == NULL)
+ {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ else
+ {
+ // cache the structure internally
+ RtlCopyMemory(info, infoOriginal, sizeof(CLASS_INTERPRET_SENSE_INFO2));
+ status = STATUS_SUCCESS;
+ }
+ }
+
+ // if we successfully cached a copy, validate all the data within
+ if (NT_SUCCESS(status))
+ {
+ if (info->Size != sizeof(CLASS_INTERPRET_SENSE_INFO2))
+ {
+ // incorrect size -- client programming error
+ status = STATUS_INVALID_PARAMETER;
+ }
+ else if (info->HistoryCount > CLASS_INTERPRET_SENSE_INFO2_MAXIMUM_HISTORY_COUNT)
+ {
+ // incorrect count -- client programming error
+ status = STATUS_INVALID_PARAMETER;
+ }
+ else if (info->Compress == NULL)
+ {
+ // Compression of the history is required to be supported
+ status = STATUS_INVALID_PARAMETER;
+ }
+ else if (info->HistoryCount == 0)
+ {
+ // History count cannot be zero
+ status = STATUS_INVALID_PARAMETER;
+ }
+ else if (info->Interpret == NULL)
+ {
+ // Updated interpret sense info function is required
+ status = STATUS_INVALID_PARAMETER;
+ }
+ else if (driverExtension->InitData.FdoData.DeviceType != FILE_DEVICE_CD_ROM)
+ {
+ // classpnp developer wants to restrict this code path
+ // for now to CDROM devices only.
+ status = STATUS_INVALID_DEVICE_REQUEST;
+ }
+ else if (driverExtension->InterpretSenseInfo != NULL)
+ {
+ // not allowed to change it once it is set for a driver
+ status = STATUS_INVALID_PARAMETER;
+ NT_ASSERT(FALSE);
+ }
+ }
+
+ // save results or cleanup
+ if (NT_SUCCESS(status))
+ {
+ driverExtension->InterpretSenseInfo = info; info = NULL;
+ }
+ else
+ {
+ FREE_POOL( info );
+ }
+ }
+ else if (IsEqualGUID(Guid, &ClassGuidSrbSupport))
+ {
+ ULONG srbSupport = *((PULONG)Data);
+
+ //
+ // Validate that at least one of the supported bit flags is set. Assume
+ // all class drivers support SCSI_REQUEST_BLOCK as a class driver that
+ // supports only extended SRB is not feasible.
+ //
+ if ((srbSupport &
+ (CLASS_SRB_SCSI_REQUEST_BLOCK | CLASS_SRB_STORAGE_REQUEST_BLOCK)) != 0) {
+ driverExtension->SrbSupport = srbSupport;
+ status = STATUS_SUCCESS;
+
+ //
+ // Catch cases of a class driver reporting only extended SRB support
+ //
+ if ((driverExtension->SrbSupport & CLASS_SRB_SCSI_REQUEST_BLOCK) == 0) {
+ NT_ASSERT(FALSE);
+ }
+ } else {
+ status = STATUS_INVALID_PARAMETER;
+ }
+ }
+ else
+ {
+ status = STATUS_NOT_SUPPORTED;
+ }
+
+ return status;
+
+} // end ClassInitializeEx()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassUnload()
+
+Routine Description:
+
+ called when there are no more references to the driver. this allows
+ drivers to be updated without rebooting.
+
+Arguments:
+
+ DriverObject - a pointer to the driver object that is being unloaded
+
+Status:
+
+--*/
+VOID
+ClassUnload(
+ IN PDRIVER_OBJECT DriverObject
+ )
+{
+ PCLASS_DRIVER_EXTENSION driverExtension;
+
+ PAGED_CODE();
+
+ NT_ASSERT( DriverObject->DeviceObject == NULL );
+
+#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
+ driverExtension = IoGetDriverObjectExtension( DriverObject, CLASS_DRIVER_EXTENSION_KEY );
+
+
+ if (driverExtension == NULL)
+ {
+ NT_ASSERT(FALSE);
+ return;
+ }
+
+ NT_ASSERT(driverExtension->RegistryPath.Buffer != NULL);
+ NT_ASSERT(driverExtension->InitData.ClassUnload != NULL);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "ClassUnload: driver unloading %wZ\n",
+ &driverExtension->RegistryPath));
+
+ //
+ // attempt to process the driver's unload routine first.
+ //
+
+ driverExtension->InitData.ClassUnload(DriverObject);
+
+ //
+ // free own allocated resources and return
+ //
+
+ FREE_POOL( driverExtension->WorkingSet );
+ FREE_POOL( driverExtension->InterpretSenseInfo );
+ FREE_POOL( driverExtension->RegistryPath.Buffer );
+ driverExtension->RegistryPath.Length = 0;
+ driverExtension->RegistryPath.MaximumLength = 0;
+
+
+ //
+ // Unregister ETW
+ //
+ if (driverExtension->EtwHandle != 0) {
+ EtwUnregister(driverExtension->EtwHandle);
+ driverExtension->EtwHandle = 0;
+
+ WPP_CLEANUP(DriverObject);
+ }
+
+
+ return;
+} // end ClassUnload()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassAddDevice()
+
+Routine Description:
+
+ SCSI class driver add device routine. This is called by pnp when a new
+ physical device come into being.
+
+ This routine will call out to the class driver to verify that it should
+ own this device then will create and attach a device object and then hand
+ it to the driver to initialize and create symbolic links
+
+Arguments:
+
+ DriverObject - a pointer to the driver object that this is being created for
+ PhysicalDeviceObject - a pointer to the physical device object
+
+Status: STATUS_NO_SUCH_DEVICE if the class driver did not want this device
+ STATUS_SUCCESS if the creation and attachment was successful
+ status of device creation and initialization
+
+--*/
+NTSTATUS
+#pragma prefast(suppress:28152, "We expect the class driver to clear the DO_DEVICE_INITIALIZING flag in its AddDevice routine.")
+ClassAddDevice(
+ IN PDRIVER_OBJECT DriverObject,
+ IN PDEVICE_OBJECT PhysicalDeviceObject
+ )
+{
+#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
+ PCLASS_DRIVER_EXTENSION driverExtension = IoGetDriverObjectExtension(DriverObject, CLASS_DRIVER_EXTENSION_KEY);
+
+ NTSTATUS status;
+
+ PAGED_CODE();
+
+
+ status = driverExtension->InitData.ClassAddDevice(DriverObject,
+ PhysicalDeviceObject);
+
+ return status;
+} // end ClassAddDevice()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassDispatchPnp()
+
+Routine Description:
+
+ Storage class driver pnp routine. This is called by the io system when
+ a PNP request is sent to the device.
+
+Arguments:
+
+ DeviceObject - pointer to the device object
+
+ Irp - pointer to the io request packet
+
+Return Value:
+
+ status
+
+--*/
+NTSTATUS
+ClassDispatchPnp(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp
+ )
+{
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+ BOOLEAN isFdo = commonExtension->IsFdo;
+
+ PCLASS_DRIVER_EXTENSION driverExtension;
+ PCLASS_INIT_DATA initData;
+ PCLASS_DEV_INFO devInfo;
+
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+
+ NTSTATUS status = Irp->IoStatus.Status;
+ BOOLEAN completeRequest = TRUE;
+ BOOLEAN lockReleased = FALSE;
+
+
+ PAGED_CODE();
+
+ //
+ // Extract all the useful information out of the driver object
+ // extension
+ //
+
+#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
+ driverExtension = IoGetDriverObjectExtension(DeviceObject->DriverObject, CLASS_DRIVER_EXTENSION_KEY);
+
+ if (driverExtension){
+
+ initData = &(driverExtension->InitData);
+
+ if(isFdo) {
+ devInfo = &(initData->FdoData);
+ } else {
+ devInfo = &(initData->PdoData);
+ }
+
+ ClassAcquireRemoveLock(DeviceObject, Irp);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): minor code %#x for %s %p\n",
+ DeviceObject, Irp,
+ irpStack->MinorFunction,
+ isFdo ? "fdo" : "pdo",
+ DeviceObject));
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): previous %#x, current %#x\n",
+ DeviceObject, Irp,
+ commonExtension->PreviousState,
+ commonExtension->CurrentState));
+
+
+ switch(irpStack->MinorFunction) {
+
+ case IRP_MN_START_DEVICE: {
+
+ //
+ // if this is sent to the FDO we should forward it down the
+ // attachment chain before we start the FDO.
+ //
+
+ if (isFdo) {
+ status = ClassForwardIrpSynchronous(commonExtension, Irp);
+ }
+ else {
+ status = STATUS_SUCCESS;
+ }
+
+ if (NT_SUCCESS(status)){
+ status = Irp->IoStatus.Status = ClassPnpStartDevice(DeviceObject);
+ }
+
+ break;
+ }
+
+
+ case IRP_MN_QUERY_DEVICE_RELATIONS: {
+
+ DEVICE_RELATION_TYPE type =
+ irpStack->Parameters.QueryDeviceRelations.Type;
+
+ PDEVICE_RELATIONS deviceRelations = NULL;
+
+
+ if(!isFdo) {
+
+ if(type == TargetDeviceRelation) {
+
+ //
+ // Device relations has one entry built in to it's size.
+ //
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+
+ deviceRelations = ExAllocatePoolWithTag(PagedPool,
+ sizeof(DEVICE_RELATIONS),
+ '2CcS');
+
+ if(deviceRelations != NULL) {
+
+ RtlZeroMemory(deviceRelations,
+ sizeof(DEVICE_RELATIONS));
+
+ Irp->IoStatus.Information = (ULONG_PTR) deviceRelations;
+
+ deviceRelations->Count = 1;
+ deviceRelations->Objects[0] = DeviceObject;
+ ObReferenceObject(deviceRelations->Objects[0]);
+
+ status = STATUS_SUCCESS;
+ }
+
+ } else {
+ //
+ // PDO's just complete enumeration requests without altering
+ // the status.
+ //
+
+ status = Irp->IoStatus.Status;
+ }
+
+ break;
+
+ } else if (type == BusRelations) {
+
+ NT_ASSERT(commonExtension->IsInitialized);
+
+ //
+ // Make sure we support enumeration
+ //
+
+ if(initData->ClassEnumerateDevice == NULL) {
+
+ //
+ // Just send the request down to the lower driver. Perhaps
+ // It can enumerate children.
+ //
+
+ } else {
+
+ //
+ // Re-enumerate the device
+ //
+
+ status = ClassPnpQueryFdoRelations(DeviceObject, Irp);
+
+ if(!NT_SUCCESS(status)) {
+ completeRequest = TRUE;
+ break;
+ }
+ }
+ }
+
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ completeRequest = FALSE;
+
+ break;
+ }
+
+ case IRP_MN_QUERY_ID: {
+
+ BUS_QUERY_ID_TYPE idType = irpStack->Parameters.QueryId.IdType;
+ UNICODE_STRING unicodeString;
+
+
+ if(isFdo) {
+
+
+ //
+ // FDO's should just forward the query down to the lower
+ // device objects
+ //
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ completeRequest = FALSE;
+ break;
+ }
+
+ //
+ // PDO's need to give an answer - this is easy for now
+ //
+
+ RtlInitUnicodeString(&unicodeString, NULL);
+
+ status = ClassGetPdoId(DeviceObject,
+ idType,
+ &unicodeString);
+
+ if(status == STATUS_NOT_IMPLEMENTED) {
+ //
+ // The driver doesn't implement this ID (whatever it is).
+ // Use the status out of the IRP so that we don't mangle a
+ // response from someone else.
+ //
+
+ status = Irp->IoStatus.Status;
+ } else if(NT_SUCCESS(status)) {
+ Irp->IoStatus.Information = (ULONG_PTR) unicodeString.Buffer;
+ } else {
+ Irp->IoStatus.Information = (ULONG_PTR) NULL;
+ }
+
+ break;
+ }
+
+ case IRP_MN_QUERY_STOP_DEVICE:
+ case IRP_MN_QUERY_REMOVE_DEVICE: {
+
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): Processing QUERY_%s irp\n",
+ DeviceObject, Irp,
+ ((irpStack->MinorFunction == IRP_MN_QUERY_STOP_DEVICE) ?
+ "STOP" : "REMOVE")));
+
+ //
+ // If this device is in use for some reason (paging, etc...)
+ // then we need to fail the request.
+ //
+
+ if(commonExtension->PagingPathCount != 0) {
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): device is in paging "
+ "path and cannot be removed\n",
+ DeviceObject, Irp));
+ status = STATUS_DEVICE_BUSY;
+ break;
+ }
+
+
+ //
+ // Check with the class driver to see if the query operation
+ // can succeed.
+ //
+
+ if(irpStack->MinorFunction == IRP_MN_QUERY_STOP_DEVICE) {
+ status = devInfo->ClassStopDevice(DeviceObject,
+ irpStack->MinorFunction);
+ } else {
+ status = devInfo->ClassRemoveDevice(DeviceObject,
+ irpStack->MinorFunction);
+ }
+
+ if(NT_SUCCESS(status)) {
+
+ //
+ // ASSERT that we never get two queries in a row, as
+ // this will severly mess up the state machine
+ //
+ NT_ASSERT(commonExtension->CurrentState != irpStack->MinorFunction);
+ commonExtension->PreviousState = commonExtension->CurrentState;
+ commonExtension->CurrentState = irpStack->MinorFunction;
+
+ if(isFdo) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): Forwarding QUERY_"
+ "%s irp\n", DeviceObject, Irp,
+ ((irpStack->MinorFunction == IRP_MN_QUERY_STOP_DEVICE) ?
+ "STOP" : "REMOVE")));
+ status = ClassForwardIrpSynchronous(commonExtension, Irp);
+ }
+ }
+
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): Final status == %x\n",
+ DeviceObject, Irp, status));
+
+ break;
+ }
+
+ case IRP_MN_CANCEL_STOP_DEVICE:
+ case IRP_MN_CANCEL_REMOVE_DEVICE: {
+
+
+ //
+ // Check with the class driver to see if the query or cancel
+ // operation can succeed.
+ //
+
+ if(irpStack->MinorFunction == IRP_MN_CANCEL_STOP_DEVICE) {
+ status = devInfo->ClassStopDevice(DeviceObject,
+ irpStack->MinorFunction);
+ NT_ASSERTMSG("ClassDispatchPnp !! CANCEL_STOP_DEVICE should never be failed\n", NT_SUCCESS(status));
+ } else {
+ status = devInfo->ClassRemoveDevice(DeviceObject,
+ irpStack->MinorFunction);
+ NT_ASSERTMSG("ClassDispatchPnp !! CANCEL_REMOVE_DEVICE should never be failed\n", NT_SUCCESS(status));
+ }
+
+ Irp->IoStatus.Status = status;
+
+ //
+ // We got a CANCEL - roll back to the previous state only
+ // if the current state is the respective QUERY state.
+ //
+
+ if(((irpStack->MinorFunction == IRP_MN_CANCEL_STOP_DEVICE) &&
+ (commonExtension->CurrentState == IRP_MN_QUERY_STOP_DEVICE)
+ ) ||
+ ((irpStack->MinorFunction == IRP_MN_CANCEL_REMOVE_DEVICE) &&
+ (commonExtension->CurrentState == IRP_MN_QUERY_REMOVE_DEVICE)
+ )
+ ) {
+
+ commonExtension->CurrentState =
+ commonExtension->PreviousState;
+ commonExtension->PreviousState = 0xff;
+
+ }
+
+
+ if(isFdo) {
+
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ completeRequest = FALSE;
+ } else {
+ status = STATUS_SUCCESS;
+ }
+
+ break;
+ }
+
+ case IRP_MN_STOP_DEVICE: {
+
+
+ //
+ // These all mean nothing to the class driver currently. The
+ // port driver will handle all queueing when necessary.
+ //
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): got stop request for %s\n",
+ DeviceObject, Irp,
+ (isFdo ? "fdo" : "pdo")
+ ));
+
+ NT_ASSERT(commonExtension->PagingPathCount == 0);
+
+ //
+ // ISSUE-2000/02/03-peterwie
+ // if we stop the timer here then it means no class driver can
+ // do i/o in its ClassStopDevice routine. This is because the
+ // retry (among other things) is tied into the tick handler
+ // and disabling retries could cause the class driver to deadlock.
+ // Currently no class driver we're aware of issues i/o in its
+ // Stop routine but this is a case we may want to defend ourself
+ // against.
+ //
+
+ ClasspDisableTimer((PFUNCTIONAL_DEVICE_EXTENSION)commonExtension);
+
+
+ status = devInfo->ClassStopDevice(DeviceObject, IRP_MN_STOP_DEVICE);
+
+ NT_ASSERTMSG("ClassDispatchPnp !! STOP_DEVICE should never be failed\n", NT_SUCCESS(status));
+
+ if(isFdo) {
+ status = ClassForwardIrpSynchronous(commonExtension, Irp);
+ }
+
+ if(NT_SUCCESS(status)) {
+ commonExtension->CurrentState = irpStack->MinorFunction;
+ commonExtension->PreviousState = 0xff;
+ }
+
+
+ break;
+ }
+
+ case IRP_MN_REMOVE_DEVICE:
+ case IRP_MN_SURPRISE_REMOVAL: {
+ UCHAR removeType = irpStack->MinorFunction;
+
+ //
+ // Log a sytem event when non-removable disks are surprise-removed.
+ //
+ if (isFdo &&
+ (removeType == IRP_MN_SURPRISE_REMOVAL)) {
+
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
+ BOOLEAN logSurpriseRemove = TRUE;
+ STORAGE_BUS_TYPE busType = fdoExtension->DeviceDescriptor->BusType;
+
+ //
+ // Don't log an event for VHDs
+ //
+ if (busType == BusTypeFileBackedVirtual) {
+ logSurpriseRemove = FALSE;
+
+ } else if (fdoData->HotplugInfo.MediaRemovable) {
+ logSurpriseRemove = FALSE;
+
+ } else if (fdoData->HotplugInfo.DeviceHotplug && ( busType == BusTypeUsb || busType == BusType1394)) {
+
+ /*
+ This device is reported as DeviceHotplug but since the busType is Usb or 1394, don't log an event
+ Note that some storage arrays may report DeviceHotplug and we would like to log an event in those cases
+ */
+
+ logSurpriseRemove = FALSE;
+ }
+
+ if (logSurpriseRemove) {
+
+ ClasspLogSystemEventWithDeviceNumber(DeviceObject, IO_WARNING_DISK_SURPRISE_REMOVED);
+ }
+
+ }
+
+ if (commonExtension->PagingPathCount != 0) {
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): paging device is getting removed!", DeviceObject, Irp));
+ }
+
+ //
+ // Release the lock for this IRP before calling in.
+ //
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ lockReleased = TRUE;
+
+ /*
+ * Set IsRemoved before propagating the REMOVE down the stack.
+ * This keeps class-initiated I/O (e.g. the MCN irp) from getting sent
+ * after we propagate the remove.
+ */
+ commonExtension->IsRemoved = REMOVE_PENDING;
+
+ /*
+ * If a timer was started on the device, stop it.
+ */
+ ClasspDisableTimer((PFUNCTIONAL_DEVICE_EXTENSION)commonExtension);
+
+ /*
+ * "Fire-and-forget" the remove irp to the lower stack.
+ * Don't touch the irp (or the irp stack!) after this.
+ */
+ if (isFdo) {
+
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ NT_ASSERT(NT_SUCCESS(status));
+ completeRequest = FALSE;
+ }
+ else {
+ status = STATUS_SUCCESS;
+ }
+
+ /*
+ * Do our own cleanup and call the class driver's remove
+ * cleanup routine.
+ * For IRP_MN_REMOVE_DEVICE, this also deletes our device object,
+ * so don't touch the extension after this.
+ */
+ commonExtension->PreviousState = commonExtension->CurrentState;
+ commonExtension->CurrentState = removeType;
+ ClassRemoveDevice(DeviceObject, removeType);
+
+ break;
+ }
+
+ case IRP_MN_DEVICE_USAGE_NOTIFICATION: {
+
+ DEVICE_USAGE_NOTIFICATION_TYPE type = irpStack->Parameters.UsageNotification.Type;
+ BOOLEAN setPagable;
+
+
+ switch(type) {
+
+ case DeviceUsageTypePaging: {
+
+ if ((irpStack->Parameters.UsageNotification.InPath) &&
+ (commonExtension->CurrentState != IRP_MN_START_DEVICE)) {
+
+ //
+ // Device isn't started. Don't allow adding a
+ // paging file, but allow a removal of one.
+ //
+
+ status = STATUS_DEVICE_NOT_READY;
+ break;
+ }
+
+ NT_ASSERT(commonExtension->IsInitialized);
+
+ /*
+ * Ensure that this user thread is not suspended while we are holding the PathCountEvent.
+ */
+ KeEnterCriticalRegion();
+
+ (VOID)KeWaitForSingleObject(&commonExtension->PathCountEvent,
+ Executive, KernelMode,
+ FALSE, NULL);
+ status = STATUS_SUCCESS;
+
+ //
+ // If the volume is removable we should try to lock it in
+ // place or unlock it once per paging path count
+ //
+
+ if (commonExtension->IsFdo){
+ status = ClasspEjectionControl(
+ DeviceObject,
+ Irp,
+ InternalMediaLock,
+ (BOOLEAN)irpStack->Parameters.UsageNotification.InPath);
+ }
+
+ if (!NT_SUCCESS(status)){
+ KeSetEvent(&commonExtension->PathCountEvent, IO_NO_INCREMENT, FALSE);
+ KeLeaveCriticalRegion();
+ break;
+ }
+
+ //
+ // if removing last paging device, need to set DO_POWER_PAGABLE
+ // bit here, and possible re-set it below on failure.
+ //
+
+ setPagable = FALSE;
+
+ if ((!irpStack->Parameters.UsageNotification.InPath) &&
+ (commonExtension->PagingPathCount == 1)) {
+
+ //
+ // removing last paging file
+ // must have DO_POWER_PAGABLE bits set, but only
+ // if none set the DO_POWER_INRUSH bit and no other special files
+ //
+
+ if (TEST_FLAG(DeviceObject->Flags, DO_POWER_INRUSH)) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): Last "
+ "paging file removed, but "
+ "DO_POWER_INRUSH was set, so NOT "
+ "setting DO_POWER_PAGABLE\n",
+ DeviceObject, Irp));
+ } else if ((commonExtension->HibernationPathCount == 0) &&
+ (commonExtension->DumpPathCount == 0)) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): Last "
+ "paging file removed, "
+ "setting DO_POWER_PAGABLE\n",
+ DeviceObject, Irp));
+ SET_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE);
+ setPagable = TRUE;
+ }
+
+ }
+
+ //
+ // forward the irp before finishing handling the
+ // special cases
+ //
+
+ status = ClassForwardIrpSynchronous(commonExtension, Irp);
+
+ //
+ // now deal with the failure and success cases.
+ // note that we are not allowed to fail the irp
+ // once it is sent to the lower drivers.
+ //
+
+ if (NT_SUCCESS(status)) {
+
+ IoAdjustPagingPathCount(
+ (volatile LONG *)&commonExtension->PagingPathCount,
+ irpStack->Parameters.UsageNotification.InPath);
+
+ if (irpStack->Parameters.UsageNotification.InPath) {
+ if (commonExtension->PagingPathCount == 1) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): "
+ "Clearing PAGABLE bit\n",
+ DeviceObject, Irp));
+ CLEAR_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE);
+
+
+ }
+
+ }
+
+ } else {
+
+ //
+ // cleanup the changes done above
+ //
+
+ if (setPagable == TRUE) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): Unsetting "
+ "PAGABLE bit due to irp failure\n",
+ DeviceObject, Irp));
+ CLEAR_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE);
+ setPagable = FALSE;
+ }
+
+ //
+ // relock or unlock the media if needed.
+ //
+
+ if (commonExtension->IsFdo) {
+
+ ClasspEjectionControl(
+ DeviceObject,
+ Irp,
+ InternalMediaLock,
+ (BOOLEAN)!irpStack->Parameters.UsageNotification.InPath);
+ }
+ }
+
+ //
+ // set the event so the next one can occur.
+ //
+
+ KeSetEvent(&commonExtension->PathCountEvent,
+ IO_NO_INCREMENT, FALSE);
+ KeLeaveCriticalRegion();
+ break;
+ }
+
+ case DeviceUsageTypeHibernation: {
+
+ //
+ // if removing last hiber device, need to set DO_POWER_PAGABLE
+ // bit here, and possible re-set it below on failure.
+ //
+
+ setPagable = FALSE;
+
+ if ((!irpStack->Parameters.UsageNotification.InPath) &&
+ (commonExtension->HibernationPathCount == 1)) {
+
+ //
+ // removing last hiber file
+ // must have DO_POWER_PAGABLE bits set, but only
+ // if none set the DO_POWER_INRUSH bit and no other special files
+ //
+
+ if (TEST_FLAG(DeviceObject->Flags, DO_POWER_INRUSH)) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): Last "
+ "hiber file removed, but "
+ "DO_POWER_INRUSH was set, so NOT "
+ "setting DO_POWER_PAGABLE\n",
+ DeviceObject, Irp));
+ } else if ((commonExtension->PagingPathCount == 0) &&
+ (commonExtension->DumpPathCount == 0)) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): Last "
+ "hiber file removed, "
+ "setting DO_POWER_PAGABLE\n",
+ DeviceObject, Irp));
+ SET_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE);
+ setPagable = TRUE;
+ }
+
+ }
+
+ status = ClassForwardIrpSynchronous(commonExtension, Irp);
+ if (!NT_SUCCESS(status)) {
+
+ if (setPagable == TRUE) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): Unsetting "
+ "PAGABLE bit due to irp failure\n",
+ DeviceObject, Irp));
+ CLEAR_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE);
+ setPagable = FALSE;
+ }
+
+ } else {
+
+ IoAdjustPagingPathCount(
+ (volatile LONG *)&commonExtension->HibernationPathCount,
+ irpStack->Parameters.UsageNotification.InPath
+ );
+
+ if ((irpStack->Parameters.UsageNotification.InPath) &&
+ (commonExtension->HibernationPathCount == 1)) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): "
+ "Clearing PAGABLE bit\n",
+ DeviceObject, Irp));
+ CLEAR_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE);
+ }
+ }
+
+ break;
+ }
+
+ case DeviceUsageTypeDumpFile: {
+
+ //
+ // if removing last dump device, need to set DO_POWER_PAGABLE
+ // bit here, and possible re-set it below on failure.
+ //
+
+ setPagable = FALSE;
+
+ if ((!irpStack->Parameters.UsageNotification.InPath) &&
+ (commonExtension->DumpPathCount == 1)) {
+
+ //
+ // removing last dump file
+ // must have DO_POWER_PAGABLE bits set, but only
+ // if none set the DO_POWER_INRUSH bit and no other special files
+ //
+
+ if (TEST_FLAG(DeviceObject->Flags, DO_POWER_INRUSH)) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): Last "
+ "dump file removed, but "
+ "DO_POWER_INRUSH was set, so NOT "
+ "setting DO_POWER_PAGABLE\n",
+ DeviceObject, Irp));
+ } else if ((commonExtension->PagingPathCount == 0) &&
+ (commonExtension->HibernationPathCount == 0)) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): Last "
+ "dump file removed, "
+ "setting DO_POWER_PAGABLE\n",
+ DeviceObject, Irp));
+ SET_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE);
+ setPagable = TRUE;
+ }
+
+ }
+
+ status = ClassForwardIrpSynchronous(commonExtension, Irp);
+ if (!NT_SUCCESS(status)) {
+
+ if (setPagable == TRUE) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): Unsetting "
+ "PAGABLE bit due to irp failure\n",
+ DeviceObject, Irp));
+ CLEAR_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE);
+ setPagable = FALSE;
+ }
+
+ } else {
+
+ IoAdjustPagingPathCount(
+ (volatile LONG *)&commonExtension->DumpPathCount,
+ irpStack->Parameters.UsageNotification.InPath
+ );
+
+ if ((irpStack->Parameters.UsageNotification.InPath) &&
+ (commonExtension->DumpPathCount == 1)) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): "
+ "Clearing PAGABLE bit\n",
+ DeviceObject, Irp));
+ CLEAR_FLAG(DeviceObject->Flags, DO_POWER_PAGABLE);
+ }
+ }
+
+ break;
+ }
+
+ case DeviceUsageTypeBoot: {
+
+ if (isFdo) {
+ PCLASS_PRIVATE_FDO_DATA fdoData;
+
+ fdoData = ((PFUNCTIONAL_DEVICE_EXTENSION)(DeviceObject->DeviceExtension))->PrivateFdoData;
+
+
+ //
+ // If boot disk has removal policy as RemovalPolicyExpectSurpriseRemoval (e.g. disk is hotplug-able),
+ // change the removal policy to RemovalPolicyExpectOrderlyRemoval.
+ // This will cause the write cache of disk to be enabled on subsequent start Fdo (next boot).
+ //
+ if ((fdoData != NULL) &&
+ fdoData->HotplugInfo.DeviceHotplug) {
+
+ fdoData->HotplugInfo.DeviceHotplug = FALSE;
+ fdoData->HotplugInfo.MediaRemovable = FALSE;
+
+ ClassSetDeviceParameter((PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension,
+ CLASSP_REG_SUBKEY_NAME,
+ CLASSP_REG_REMOVAL_POLICY_VALUE_NAME,
+ RemovalPolicyExpectOrderlyRemoval);
+ }
+ }
+
+ status = ClassForwardIrpSynchronous(commonExtension, Irp);
+ break;
+ }
+
+ default: {
+ status = STATUS_INVALID_PARAMETER;
+ break;
+ }
+ }
+ break;
+ }
+
+ case IRP_MN_QUERY_CAPABILITIES: {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): QueryCapabilities\n",
+ DeviceObject, Irp));
+
+ if(!isFdo) {
+
+ status = ClassQueryPnpCapabilities(
+ DeviceObject,
+ irpStack->Parameters.DeviceCapabilities.Capabilities
+ );
+
+ break;
+
+ } else {
+
+ PDEVICE_CAPABILITIES deviceCapabilities;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData;
+
+ fdoExtension = DeviceObject->DeviceExtension;
+ fdoData = fdoExtension->PrivateFdoData;
+ deviceCapabilities =
+ irpStack->Parameters.DeviceCapabilities.Capabilities;
+
+ //
+ // forward the irp before handling the special cases
+ //
+
+ status = ClassForwardIrpSynchronous(commonExtension, Irp);
+ if (!NT_SUCCESS(status)) {
+ break;
+ }
+
+ //
+ // we generally want to remove the device from the hotplug
+ // applet, which requires the SR-OK bit to be set.
+ // only when the user specifies that they are capable of
+ // safely removing things do we want to clear this bit
+ // (saved in WriteCacheEnableOverride)
+ //
+ // setting of this bit is done either above, or by the
+ // lower driver.
+ //
+ // note: may not be started, so check we have FDO data first.
+ //
+
+ if (fdoData &&
+ fdoData->HotplugInfo.WriteCacheEnableOverride) {
+ if (deviceCapabilities->SurpriseRemovalOK) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "Classpnp: Clearing SR-OK bit in "
+ "device capabilities due to hotplug "
+ "device or media\n"));
+ }
+ deviceCapabilities->SurpriseRemovalOK = FALSE;
+ }
+ break;
+
+ } // end QUERY_CAPABILITIES for FDOs
+
+ break;
+
+
+ } // end QUERY_CAPABILITIES
+
+ default: {
+
+ if (isFdo){
+
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+
+ completeRequest = FALSE;
+ }
+
+ break;
+ }
+ }
+ }
+ else {
+ NT_ASSERT(driverExtension);
+ status = STATUS_INTERNAL_ERROR;
+ }
+
+ if (completeRequest){
+ Irp->IoStatus.Status = status;
+
+ if (!lockReleased){
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ }
+
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): leaving with previous %#x, current %#x.", DeviceObject, Irp, commonExtension->PreviousState, commonExtension->CurrentState));
+ }
+ else {
+ /*
+ * The irp is already completed so don't touch it.
+ * This may be a remove so don't touch the device extension.
+ */
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, "ClassDispatchPnp (%p,%p): leaving.", DeviceObject, Irp));
+ }
+
+ return status;
+} // end ClassDispatchPnp()
+
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassPnpStartDevice()
+
+Routine Description:
+
+ Storage class driver routine for IRP_MN_START_DEVICE requests.
+ This routine kicks off any device specific initialization
+
+Arguments:
+
+ DeviceObject - a pointer to the device object
+
+ Irp - a pointer to the io request packet
+
+Return Value:
+
+ none
+
+--*/
+NTSTATUS ClassPnpStartDevice(IN PDEVICE_OBJECT DeviceObject)
+{
+ PCLASS_DRIVER_EXTENSION driverExtension;
+ PCLASS_INIT_DATA initData;
+
+ PCLASS_DEV_INFO devInfo;
+
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
+ BOOLEAN isFdo = commonExtension->IsFdo;
+
+ BOOLEAN isMountedDevice = TRUE;
+ BOOLEAN isPortable = FALSE;
+
+ NTSTATUS status = STATUS_SUCCESS;
+ PDEVICE_POWER_DESCRIPTOR powerDescriptor = NULL;
+
+
+ PAGED_CODE();
+
+#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
+ driverExtension = IoGetDriverObjectExtension(DeviceObject->DriverObject, CLASS_DRIVER_EXTENSION_KEY);
+
+ initData = &(driverExtension->InitData);
+ if(isFdo) {
+ devInfo = &(initData->FdoData);
+ } else {
+ devInfo = &(initData->PdoData);
+ }
+
+ NT_ASSERT(devInfo->ClassInitDevice != NULL);
+ NT_ASSERT(devInfo->ClassStartDevice != NULL);
+
+ if (!commonExtension->IsInitialized){
+
+ //
+ // perform FDO/PDO specific initialization
+ //
+
+ if (isFdo){
+ STORAGE_PROPERTY_ID propertyId;
+
+ //
+ // allocate a private extension for class data
+ //
+
+ if (fdoExtension->PrivateFdoData == NULL) {
+ fdoExtension->PrivateFdoData = ExAllocatePoolWithTag(NonPagedPoolNx,
+ sizeof(CLASS_PRIVATE_FDO_DATA),
+ CLASS_TAG_PRIVATE_DATA
+ );
+ }
+
+ if (fdoExtension->PrivateFdoData == NULL) {
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP, "ClassPnpStartDevice: Cannot allocate for private fdo data\n"));
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ RtlZeroMemory(fdoExtension->PrivateFdoData, sizeof(CLASS_PRIVATE_FDO_DATA));
+
+
+#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
+ //
+ // Allocate a structure to hold more data than what we can put in FUNCTIONAL_DEVICE_EXTENSION.
+ // This structure's memory is managed by classpnp, so it is more extensible.
+ //
+ if (fdoExtension->AdditionalFdoData == NULL) {
+ fdoExtension->AdditionalFdoData = ExAllocatePoolWithTag(NonPagedPoolNx,
+ sizeof(ADDITIONAL_FDO_DATA),
+ CLASSPNP_POOL_TAG_ADDITIONAL_DATA
+ );
+ }
+
+ if (fdoExtension->AdditionalFdoData == NULL) {
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP, "ClassPnpStartDevice: Cannot allocate memory for the additional data structure.\n"));
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ RtlZeroMemory(fdoExtension->AdditionalFdoData, sizeof(ADDITIONAL_FDO_DATA));
+#endif
+
+ status = ClasspInitializeTimer(fdoExtension);
+ if (NT_SUCCESS(status) == FALSE) {
+ FREE_POOL(fdoExtension->PrivateFdoData);
+#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
+ FREE_POOL(fdoExtension->AdditionalFdoData);
+#endif
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP, "ClassPnpStartDevice: Failed to initialize tick timer\n"));
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ //
+ // allocate LowerLayerSupport for class data
+ //
+
+ if (fdoExtension->FunctionSupportInfo == NULL) {
+ fdoExtension->FunctionSupportInfo = (PCLASS_FUNCTION_SUPPORT_INFO)ExAllocatePoolWithTag(NonPagedPoolNx,
+ sizeof(CLASS_FUNCTION_SUPPORT_INFO),
+ '3BcS'
+ );
+ }
+
+ if (fdoExtension->FunctionSupportInfo == NULL) {
+ ClasspDeleteTimer(fdoExtension);
+ FREE_POOL(fdoExtension->PrivateFdoData);
+#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
+ FREE_POOL(fdoExtension->AdditionalFdoData);
+#endif
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP, "ClassPnpStartDevice: Cannot allocate for FunctionSupportInfo\n"));
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ //
+ // initialize the struct's various fields.
+ //
+ RtlZeroMemory(fdoExtension->FunctionSupportInfo, sizeof(CLASS_FUNCTION_SUPPORT_INFO));
+ KeInitializeSpinLock(&fdoExtension->FunctionSupportInfo->SyncLock);
+
+ //
+ // intialize the CommandStatus to -1 indicates that no effort made yet to retrieve the info.
+ // Possible values of CommandStatus (data type: NTSTATUS):
+ // -1: It's not attempted yet to retrieve the information.
+ // success: Command sent and succeeded, information cached in FdoExtension.
+ // failed/warning: Command is either not supported or failed by device or lower level driver.
+ // The command should not be attempted again.
+ //
+ fdoExtension->FunctionSupportInfo->BlockLimitsData.CommandStatus = -1;
+ fdoExtension->FunctionSupportInfo->DeviceCharacteristicsData.CommandStatus = -1;
+ fdoExtension->FunctionSupportInfo->LBProvisioningData.CommandStatus = -1;
+ fdoExtension->FunctionSupportInfo->ReadCapacity16Data.CommandStatus = -1;
+ fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus = -1;
+
+
+ KeInitializeTimer(&fdoExtension->PrivateFdoData->Retry.Timer);
+ KeInitializeDpc(&fdoExtension->PrivateFdoData->Retry.Dpc,
+ ClasspRetryRequestDpc,
+ DeviceObject);
+ KeInitializeSpinLock(&fdoExtension->PrivateFdoData->Retry.Lock);
+ fdoExtension->PrivateFdoData->Retry.Granularity = KeQueryTimeIncrement();
+ commonExtension->Reserved4 = (ULONG_PTR)(' GPH'); // debug aid
+ InitializeListHead(&fdoExtension->PrivateFdoData->DeferredClientIrpList);
+
+ KeInitializeSpinLock(&fdoExtension->PrivateFdoData->SpinLock);
+
+ //
+ // keep a pointer to the senseinfo2 stuff locally also (used in every read/write).
+ //
+ fdoExtension->PrivateFdoData->InterpretSenseInfo = driverExtension->InterpretSenseInfo;
+
+ fdoExtension->PrivateFdoData->MaxNumberOfIoRetries = NUM_IO_RETRIES;
+
+ //
+ // Initialize release queue extended SRB
+ //
+ status = InitializeStorageRequestBlock(&(fdoExtension->PrivateFdoData->ReleaseQueueSrb.SrbEx),
+ STORAGE_ADDRESS_TYPE_BTL8,
+ sizeof(fdoExtension->PrivateFdoData->ReleaseQueueSrb.ReleaseQueueSrbBuffer),
+ 0);
+ if (!NT_SUCCESS(status)) {
+ NT_ASSERT(FALSE);
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP,
+ "ClassPnpStartDevice: fail to initialize release queue extended SRB 0x%x\n", status));
+ return status;
+ }
+
+
+ // Initialize performance counter frequency
+ KeQueryPerformanceCounter(&(fdoExtension->PrivateFdoData->PerfCounterFrequency));
+
+ if (fdoExtension->PrivateFdoData->PerfCounterFrequency.QuadPart == 0) {
+ fdoExtension->PrivateFdoData->PerfCounterFrequency.QuadPart = 1;
+ }
+
+ /*
+ * Anchor the FDO in our static list.
+ * Pnp is synchronized, so we shouldn't need any synchronization here.
+ */
+ InsertTailList(&AllFdosList, &fdoExtension->PrivateFdoData->AllFdosListEntry);
+
+ //
+ // NOTE: the old interface allowed the class driver to allocate
+ // this. this was unsafe for low-memory conditions. allocate one
+ // unconditionally now, and modify our internal functions to use
+ // our own exclusively as it is the only safe way to do this.
+ //
+
+ status = ClasspAllocateReleaseQueueIrp(fdoExtension);
+ if (!NT_SUCCESS(status)) {
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP, "ClassPnpStartDevice: Cannot allocate the private release queue irp\n"));
+ return status;
+ }
+
+ status = ClasspAllocatePowerProcessIrp(fdoExtension);
+ if (!NT_SUCCESS(status)) {
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP, "ClassPnpStartDevice: Cannot allocate the power process irp\n"));
+ return status;
+ }
+
+ //
+ // Call port driver to get miniport properties for disk devices
+ // It's ok for this call to fail
+ //
+
+ if ((DeviceObject->DeviceType == FILE_DEVICE_DISK) &&
+ (!TEST_FLAG(DeviceObject->Characteristics, FILE_FLOPPY_DISKETTE))) {
+
+ propertyId = StorageMiniportProperty;
+
+ status = ClassGetDescriptor(fdoExtension->CommonExtension.LowerDeviceObject,
+ &propertyId,
+ &fdoExtension->MiniportDescriptor);
+
+ //
+ // function ClassGetDescriptor returns succeed with buffer "fdoExtension->MiniportDescriptor" allocated.
+ //
+ if ( NT_SUCCESS(status) &&
+ (fdoExtension->MiniportDescriptor->Portdriver != StoragePortCodeSetStorport &&
+ fdoExtension->MiniportDescriptor->Portdriver != StoragePortCodeSetUSBport) ) {
+ //
+ // field "IoTimeoutValue" supported for either Storport or USBStor
+ //
+ fdoExtension->MiniportDescriptor->IoTimeoutValue = 0;
+ }
+
+
+
+ }
+
+ //
+ // Call port driver to get adapter capabilities.
+ //
+
+ propertyId = StorageAdapterProperty;
+
+ status = ClassGetDescriptor(
+ commonExtension->LowerDeviceObject,
+ &propertyId,
+ &fdoExtension->AdapterDescriptor);
+ if (!NT_SUCCESS(status)) {
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP, "ClassPnpStartDevice: ClassGetDescriptor [ADAPTER] failed %lx\n", status));
+ return status;
+ }
+
+ //
+ // Call port driver to get device descriptor.
+ //
+
+ propertyId = StorageDeviceProperty;
+
+ status = ClassGetDescriptor(
+ commonExtension->LowerDeviceObject,
+ &propertyId,
+ &fdoExtension->DeviceDescriptor);
+ if (NT_SUCCESS(status)){
+
+ ClasspScanForSpecialInRegistry(fdoExtension);
+ ClassScanForSpecial(fdoExtension, ClassBadItems, ClasspScanForClassHacks);
+
+ //
+ // allow perf to be re-enabled after a given number of failed IOs
+ // require this number to be at least CLASS_PERF_RESTORE_MINIMUM
+ //
+
+ {
+ ULONG t = CLASS_PERF_RESTORE_MINIMUM;
+
+ ClassGetDeviceParameter(fdoExtension,
+ CLASSP_REG_SUBKEY_NAME,
+ CLASSP_REG_PERF_RESTORE_VALUE_NAME,
+ &t);
+ if (t >= CLASS_PERF_RESTORE_MINIMUM) {
+ fdoExtension->PrivateFdoData->Perf.ReEnableThreshhold = t;
+ }
+ }
+
+ //
+ // compatibility comes first. writable cd media will not
+ // get a SYNCH_CACHE on power down.
+ //
+ if (fdoExtension->DeviceObject->DeviceType != FILE_DEVICE_DISK) {
+ SET_FLAG(fdoExtension->PrivateFdoData->HackFlags, FDO_HACK_NO_SYNC_CACHE);
+ }
+
+
+ //
+ // Test if the device is portable and updated the characteristics if so
+ //
+ status = ClasspIsPortable(fdoExtension,
+ &isPortable);
+
+ if (NT_SUCCESS(status) && (isPortable == TRUE)) {
+ DeviceObject->Characteristics |= FILE_PORTABLE_DEVICE;
+ }
+
+ //
+ // initialize the hotplug information only after the ScanForSpecial
+ // routines, as it relies upon the hack flags.
+ //
+ status = ClasspInitializeHotplugInfo(fdoExtension);
+ if (NT_SUCCESS(status)){
+ /*
+ * Allocate/initialize TRANSFER_PACKETs and related resources.
+ */
+ status = InitializeTransferPackets(DeviceObject);
+ }
+ else {
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP, "ClassPnpStartDevice: Could not initialize hotplug information %lx\n", status));
+ }
+ }
+ else {
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP, "ClassPnpStartDevice: ClassGetDescriptor [DEVICE] failed %lx\n", status));
+ return status;
+ }
+
+
+ if (NT_SUCCESS(status)) {
+
+ //
+ // Retrieve info on whether async notification is supported by port drivers
+ //
+ propertyId = StorageDevicePowerProperty;
+
+ status = ClassGetDescriptor(fdoExtension->CommonExtension.LowerDeviceObject,
+ &propertyId,
+ &powerDescriptor);
+ if (NT_SUCCESS(status) && (powerDescriptor != NULL)) {
+ fdoExtension->FunctionSupportInfo->AsynchronousNotificationSupported = powerDescriptor->AsynchronousNotificationSupported;
+ fdoExtension->FunctionSupportInfo->IdlePower.D3ColdSupported = powerDescriptor->D3ColdSupported;
+ fdoExtension->FunctionSupportInfo->IdlePower.NoVerifyDuringIdlePower = powerDescriptor->NoVerifyDuringIdlePower;
+ FREE_POOL(powerDescriptor);
+ } else {
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP, "ClassPnpStartDevice: ClassGetDescriptor [DevicePower] failed %lx\n", status));
+
+ //
+ // Ignore error as device power property is optional
+ //
+ status = STATUS_SUCCESS;
+ }
+ }
+ }
+
+ //
+ // ISSUE - drivers need to disable write caching on the media
+ // if hotplug and !useroverride. perhaps we should
+ // allow registration of a callback to enable/disable
+ // write cache instead.
+ //
+
+ if (NT_SUCCESS(status)){
+ status = devInfo->ClassInitDevice(DeviceObject);
+ }
+
+ if (commonExtension->IsFdo) {
+ fdoExtension->PrivateFdoData->Perf.OriginalSrbFlags = fdoExtension->SrbFlags;
+
+ //
+ // initialization for disk device
+ //
+ if ((DeviceObject->DeviceType == FILE_DEVICE_DISK) &&
+ (!TEST_FLAG(DeviceObject->Characteristics, FILE_FLOPPY_DISKETTE))) {
+
+ ULONG accessAlignmentNotSupported = 0;
+ ULONG qerrOverrideMode = QERR_SET_ZERO_ODX_OR_TP_ONLY;
+ ULONG legacyErrorHandling = FALSE;
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP,
+ "ClassPnpStartDevice: Enabling idle timer for %p\n", DeviceObject));
+ // Initialize idle timer for disk devices
+ ClasspInitializeIdleTimer(fdoExtension);
+
+ if (ClasspIsObsoletePortDriver(fdoExtension) == FALSE) {
+ // get INQUIRY VPD support information. It's safe to send command as everything is ready in ClassInitDevice().
+ ClasspGetInquiryVpdSupportInfo(fdoExtension);
+
+ // Query and cache away Logical Block Provisioning info in the FDO extension.
+ // The cached information will be used in responding to some IOCTLs
+ ClasspGetLBProvisioningInfo(fdoExtension);
+
+ //
+ // Query and cache away Block Device ROD Limits info in the FDO extension.
+ //
+ if (fdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits) {
+ ClassDetermineTokenOperationCommandSupport(DeviceObject);
+ }
+
+ //
+ // See if the user has specified a particular QERR override
+ // mode. "Override" meaning setting QERR = 0 via Mode Select.
+ // 0 = Only when ODX or Thin Provisioning are supported (default)
+ // 1 = Always
+ // 2 = Never (or any value >= 2)
+ //
+ ClassGetDeviceParameter(fdoExtension,
+ CLASSP_REG_SUBKEY_NAME,
+ CLASSP_REG_QERR_OVERRIDE_MODE,
+ &qerrOverrideMode);
+
+ //
+ // If this device is thinly provisioned or supports ODX, we
+ // may need to force QERR to zero. The user may have also
+ // specified that we should always or never do this.
+ //
+ if (qerrOverrideMode == QERR_SET_ZERO_ALWAYS ||
+ (qerrOverrideMode == QERR_SET_ZERO_ODX_OR_TP_ONLY &&
+ (ClasspIsThinProvisioned(fdoExtension->FunctionSupportInfo) ||
+ NT_SUCCESS(ClasspValidateOffloadSupported(DeviceObject, NULL))))) {
+
+ ClasspZeroQERR(DeviceObject);
+ }
+
+ } else {
+
+ //
+ // Since this device has been exposed by a legacy miniport (e.g. SCSIPort miniport)
+ // set its LB Provisioning command status to an error status that will be surfaced
+ // up to the caller of a TRIM/Unmap command.
+ //
+ fdoExtension->FunctionSupportInfo->LBProvisioningData.CommandStatus = STATUS_UNSUCCESSFUL;
+ fdoExtension->FunctionSupportInfo->BlockLimitsData.CommandStatus = STATUS_UNSUCCESSFUL;
+ fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus = STATUS_UNSUCCESSFUL;
+ }
+
+ // Get registry setting of failing the IOCTL for AccessAlignment Property.
+ ClassGetDeviceParameter(fdoExtension,
+ CLASSP_REG_SUBKEY_NAME,
+ CLASSP_REG_ACCESS_ALIGNMENT_NOT_SUPPORTED,
+ &accessAlignmentNotSupported);
+
+ if (accessAlignmentNotSupported > 0) {
+ fdoExtension->FunctionSupportInfo->RegAccessAlignmentQueryNotSupported = TRUE;
+ }
+
+#if (NTDDI_VERSION >= NTDDI_WINBLUE)
+
+
+ //
+ // See if the user has specified legacy error handling.
+ //
+ ClassGetDeviceParameter(fdoExtension,
+ CLASSP_REG_SUBKEY_NAME,
+ CLASSP_REG_LEGACY_ERROR_HANDLING,
+ &legacyErrorHandling);
+
+ if (legacyErrorHandling) {
+ //
+ // Legacy error handling means that the maximum number of
+ // retries allowd for an IO request is 8 instead of 4.
+ //
+ fdoExtension->PrivateFdoData->MaxNumberOfIoRetries = LEGACY_NUM_IO_RETRIES;
+ fdoExtension->PrivateFdoData->LegacyErrorHandling = TRUE;
+ }
+#else
+ UNREFERENCED_PARAMETER(legacyErrorHandling);
+#endif
+
+ }
+
+ }
+ }
+
+ if (!NT_SUCCESS(status)){
+
+ //
+ // Just bail out - the remove that comes down will clean up the
+ // initialized scraps.
+ //
+
+ return status;
+ } else {
+ commonExtension->IsInitialized = TRUE;
+ }
+
+ //
+ // If device requests autorun functionality or a once a second callback
+ // then enable the once per second timer. Exception is if media change
+ // detection is desired but device supports async notification.
+ //
+ // NOTE: This assumes that ClassInitializeMediaChangeDetection is always
+ // called in the context of the ClassInitDevice callback. If called
+ // after then this check will have already been made and the
+ // once a second timer will not have been enabled.
+ //
+ if ((isFdo) &&
+ ((initData->ClassTick != NULL) ||
+ ((fdoExtension->MediaChangeDetectionInfo != NULL) &&
+ (fdoExtension->FunctionSupportInfo != NULL) &&
+ (fdoExtension->FunctionSupportInfo->AsynchronousNotificationSupported == FALSE)) ||
+ ((fdoExtension->FailurePredictionInfo != NULL) &&
+ (fdoExtension->FailurePredictionInfo->Method != FailurePredictionNone))))
+ {
+ ClasspEnableTimer(fdoExtension);
+
+ //
+ // In addition, we may change our polling behavior when the screen is
+ // off so register for screen state notification if we haven't already
+ // done so.
+ //
+ if (ScreenStateNotificationHandle == NULL) {
+ PoRegisterPowerSettingCallback(DeviceObject,
+ &GUID_CONSOLE_DISPLAY_STATE,
+ &ClasspPowerSettingCallback,
+ NULL,
+ &ScreenStateNotificationHandle);
+ }
+ }
+
+ //
+ // NOTE: the timer looks at commonExtension->CurrentState now
+ // to prevent Media Change Notification code from running
+ // until the device is started, but allows the device
+ // specific tick handler to run. therefore it is imperative
+ // that commonExtension->CurrentState not be updated until
+ // the device specific startdevice handler has finished.
+ //
+
+ status = devInfo->ClassStartDevice(DeviceObject);
+
+ if (NT_SUCCESS(status)){
+ commonExtension->CurrentState = IRP_MN_START_DEVICE;
+
+ if((isFdo) && (initData->ClassEnumerateDevice != NULL)) {
+ isMountedDevice = FALSE;
+ }
+
+ if (DeviceObject->DeviceType != FILE_DEVICE_CD_ROM) {
+
+ isMountedDevice = FALSE;
+ }
+
+ //
+ // Register for mounted device interface if this is a
+ // sfloppy device.
+ //
+ if ((DeviceObject->DeviceType == FILE_DEVICE_DISK) &&
+ (TEST_FLAG(DeviceObject->Characteristics, FILE_FLOPPY_DISKETTE))) {
+
+ isMountedDevice = TRUE;
+ }
+
+ if(isMountedDevice) {
+ ClasspRegisterMountedDeviceInterface(DeviceObject);
+ }
+
+ if(commonExtension->IsFdo) {
+ IoWMIRegistrationControl(DeviceObject, WMIREG_ACTION_REGISTER);
+
+ //
+ // Tell Storport (Usbstor or SD) to enable idle power management for this
+ // device, assuming the user hasn't turned it off in the registry.
+ //
+ if (fdoExtension->FunctionSupportInfo != NULL &&
+ fdoExtension->FunctionSupportInfo->IdlePower.IdlePowerEnabled == FALSE &&
+ fdoExtension->MiniportDescriptor != NULL &&
+ (fdoExtension->MiniportDescriptor->Portdriver == StoragePortCodeSetStorport ||
+ fdoExtension->MiniportDescriptor->Portdriver == StoragePortCodeSetSDport ||
+ fdoExtension->MiniportDescriptor->Portdriver == StoragePortCodeSetUSBport)) {
+ ULONG disableIdlePower= 0;
+ ClassGetDeviceParameter(fdoExtension,
+ CLASSP_REG_SUBKEY_NAME,
+ CLASSP_REG_DISBALE_IDLE_POWER_NAME,
+ &disableIdlePower);
+
+ if (!disableIdlePower) {
+ ClasspEnableIdlePower(DeviceObject);
+ }
+ }
+ }
+ }
+ else {
+ ClasspDisableTimer(fdoExtension);
+ }
+
+
+ return status;
+}
+
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassReadWrite()
+
+Routine Description:
+
+ This is the system entry point for read and write requests. The
+ device-specific handler is invoked to perform any validation necessary.
+
+ If the device object is a PDO (partition object) then the request will
+ simply be adjusted for Partition0 and issued to the lower device driver.
+
+ IF the device object is an FDO (paritition 0 object), the number of bytes
+ in the request are checked against the maximum byte counts that the adapter
+ supports and requests are broken up into
+ smaller sizes if necessary.
+
+Arguments:
+
+ DeviceObject - a pointer to the device object for this request
+
+ Irp - IO request
+
+Return Value:
+
+ NT Status
+
+--*/
+NTSTATUS ClassReadWrite(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
+{
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+ PDEVICE_OBJECT lowerDeviceObject = commonExtension->LowerDeviceObject;
+ PIO_STACK_LOCATION currentIrpStack = IoGetCurrentIrpStackLocation(Irp);
+ ULONG transferByteCount = currentIrpStack->Parameters.Read.Length;
+ ULONG isRemoved;
+ NTSTATUS status;
+
+ /*
+ * Grab the remove lock. If we can't acquire it, bail out.
+ */
+ isRemoved = ClassAcquireRemoveLock(DeviceObject, Irp);
+ if (isRemoved) {
+ Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST;
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ status = STATUS_DEVICE_DOES_NOT_EXIST;
+ }
+ else if (TEST_FLAG(DeviceObject->Flags, DO_VERIFY_VOLUME) &&
+ (currentIrpStack->MinorFunction != CLASSP_VOLUME_VERIFY_CHECKED) &&
+ !TEST_FLAG(currentIrpStack->Flags, SL_OVERRIDE_VERIFY_VOLUME)){
+
+ /*
+ * DO_VERIFY_VOLUME is set for the device object,
+ * but this request is not itself a verify request.
+ * So fail this request.
+ */
+ if (Irp->Tail.Overlay.Thread != NULL) {
+ IoSetHardErrorOrVerifyDevice(Irp, DeviceObject);
+ }
+ Irp->IoStatus.Status = STATUS_VERIFY_REQUIRED;
+ Irp->IoStatus.Information = 0;
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, 0);
+ status = STATUS_VERIFY_REQUIRED;
+ }
+ else {
+
+ /*
+ * Since we've bypassed the verify-required tests we don't need to repeat
+ * them with this IRP - in particular we don't want to worry about
+ * hitting them at the partition 0 level if the request has come through
+ * a non-zero partition.
+ */
+ currentIrpStack->MinorFunction = CLASSP_VOLUME_VERIFY_CHECKED;
+
+ /*
+ * Call the miniport driver's pre-pass filter to check if we
+ * should continue with this transfer.
+ */
+ NT_ASSERT(commonExtension->DevInfo->ClassReadWriteVerification);
+ status = commonExtension->DevInfo->ClassReadWriteVerification(DeviceObject, Irp);
+ // Code Analysis cannot analyze the code paths specific to clients.
+ _Analysis_assume_(status != STATUS_PENDING);
+ if (!NT_SUCCESS(status)){
+ NT_ASSERT(Irp->IoStatus.Status == status);
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest (DeviceObject, Irp, IO_NO_INCREMENT);
+ }
+ else if (status == STATUS_PENDING){
+ /*
+ * ClassReadWriteVerification queued this request.
+ * So don't touch the irp anymore.
+ */
+ }
+ else {
+
+ if (transferByteCount == 0) {
+ /*
+ * Several parts of the code turn 0 into 0xffffffff,
+ * so don't process a zero-length request any further.
+ */
+ Irp->IoStatus.Status = STATUS_SUCCESS;
+ Irp->IoStatus.Information = 0;
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ status = STATUS_SUCCESS;
+ }
+ else {
+ /*
+ * If the driver has its own StartIo routine, call it.
+ */
+ if (commonExtension->DriverExtension->InitData.ClassStartIo) {
+ IoMarkIrpPending(Irp);
+ IoStartPacket(DeviceObject, Irp, NULL, NULL);
+ status = STATUS_PENDING;
+ }
+ else {
+ /*
+ * The driver does not have its own StartIo routine.
+ * So process this request ourselves.
+ */
+
+ /*
+ * Add partition byte offset to make starting byte relative to
+ * beginning of disk.
+ */
+ currentIrpStack->Parameters.Read.ByteOffset.QuadPart +=
+ commonExtension->StartingOffset.QuadPart;
+
+ if (commonExtension->IsFdo){
+
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
+
+ /*
+ * Add in any skew for the disk manager software.
+ */
+ currentIrpStack->Parameters.Read.ByteOffset.QuadPart +=
+ commonExtension->PartitionZeroExtension->DMByteSkew;
+
+ //
+ // In case DEV_USE_16BYTE_CDB flag is not set, R/W request will be translated into READ/WRITE 10 SCSI command.
+ // These SCSI commands have 4 bytes in "Starting LBA" field.
+ // Requests cannot be represented in these SCSI commands should be failed.
+ //
+ if (!TEST_FLAG(fdoExtension->DeviceFlags, DEV_USE_16BYTE_CDB)) {
+ LARGE_INTEGER startingLba;
+
+ startingLba.QuadPart = currentIrpStack->Parameters.Read.ByteOffset.QuadPart >> fdoExtension->SectorShift;
+
+ if (startingLba.QuadPart > MAXULONG) {
+ Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
+ Irp->IoStatus.Information = 0;
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ status = STATUS_INVALID_PARAMETER;
+ goto Exit;
+ }
+ }
+
+#if DBG
+ //
+ // Record the caller if:
+ // 1. the disk is currently off
+ // 2. the operation is a read, (likely resulting in disk spinnage)
+ // 3. the operation is marked WT (likely resulting in disk spinnage)
+ //
+ if((fdoExtension->DevicePowerState == PowerDeviceD3) && // disk is off
+ ((currentIrpStack->MajorFunction == IRP_MJ_READ) || // It's a read.
+ (TEST_FLAG(currentIrpStack->Flags, SL_WRITE_THROUGH))) ) { // they *really* want it to go to disk.
+
+ SnapDiskStartup();
+ }
+#endif
+
+ /*
+ * Perform the actual transfer(s) on the hardware
+ * to service this request.
+ */
+ if (ClasspIsIdleRequestSupported(fdoData, Irp)) {
+ ClasspMarkIrpAsIdle(Irp, TRUE);
+ status = ClasspEnqueueIdleRequest(DeviceObject, Irp);
+ } else {
+ UCHAR uniqueAddr = 0;
+
+ //
+ // Since we're touching fdoData after servicing the transfer packet, this opens us up to
+ // a potential window, where the device may be removed between the time that
+ // ServiceTransferPacket completes and we've had a chance to access fdoData. In order
+ // to guard against this, we acquire the removelock an additional time here. This
+ // acquire is guaranteed to succeed otherwise we wouldn't be here (because of the
+ // outer acquire).
+ // The sequence of events we're guarding against with this remLock acquire is:
+ // 1. This UL IRP acquired the lock.
+ // 2. Device gets surprised removed, then gets IRP_MN_REMOVE_DEVICE; ClassRemoveDevice
+ // waits for the above RemoveLock.
+ // 3. ServiceTransferRequest breaks the UL IRP into DL IRPs.
+ // 4. DL IRPs complete with STATUS_NO_SUCH_DEVICE and TransferPktComplete completes the UL
+ // IRP with STATUS_NO_SUCH_DEVICE; releases the RemoveLock.
+ // 5. ClassRemoveDevice is now unblocked, continues running and frees resources (including
+ // fdoData).
+ // 6. Finally ClassReadWrite gets to run again and accesses a freed fdoData when trying to
+ // check/update idle-related fields.
+ //
+ ClassAcquireRemoveLock(DeviceObject, (PVOID)&uniqueAddr);
+
+ ClasspMarkIrpAsIdle(Irp, FALSE);
+ status = ServiceTransferRequest(DeviceObject, Irp, FALSE);
+ if (fdoData->IdlePrioritySupported == TRUE) {
+ fdoData->LastIoTime = ClasspGetCurrentTime(NULL);
+ fdoData->IdleTicks = 0;
+ }
+
+ ClassReleaseRemoveLock(DeviceObject, (PVOID)&uniqueAddr);
+ }
+ }
+ else {
+ /*
+ * This is a child PDO enumerated for our FDO by e.g. disk.sys
+ * and owned by e.g. partmgr. Send it down to the next device
+ * and the same irp will come back to us for the FDO.
+ */
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ status = IoCallDriver(lowerDeviceObject, Irp);
+ }
+ }
+ }
+ }
+ }
+
+Exit:
+ return status;
+}
+
+
+VOID InterpretCapacityData(PDEVICE_OBJECT Fdo, PREAD_CAPACITY_DATA_EX ReadCapacityData)
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Fdo->DeviceExtension;
+ ULONG cylinderSize;
+ ULONG bytesPerSector;
+ LARGE_INTEGER lastSector;
+ LARGE_INTEGER largeInt;
+
+ bytesPerSector = ClasspCalculateLogicalSectorSize(Fdo, ReadCapacityData->BytesPerBlock);
+
+ fdoExt->DiskGeometry.BytesPerSector = bytesPerSector;
+ WHICH_BIT(fdoExt->DiskGeometry.BytesPerSector, fdoExt->SectorShift);
+
+ /*
+ * LogicalBlockAddress is the last sector of the logical drive, in big-endian.
+ * It tells us the size of the drive (#sectors is lastSector+1).
+ */
+
+ largeInt = ReadCapacityData->LogicalBlockAddress;
+ REVERSE_BYTES_QUAD(&lastSector, &largeInt);
+
+ if (fdoExt->DMActive){
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, "ClassReadDriveCapacity: reducing number of sectors by %d\n", fdoExt->DMSkew));
+ lastSector.QuadPart -= fdoExt->DMSkew;
+ }
+
+ /*
+ * Check to see if we have a geometry we should be using already.
+ * If not, we set part of the disk geometry to garbage values that will be filled in by the caller (e.g. disk.sys).
+ *
+ * So the first call to ClassReadDriveCapacity always sets a meaningless geometry.
+ * TracksPerCylinder and SectorsPerTrack are kind of meaningless anyway wrt I/O,
+ * because I/O is always targeted to a logical sector number.
+ * All that really matters is BytesPerSector and the number of sectors.
+ */
+ cylinderSize = fdoExt->DiskGeometry.TracksPerCylinder * fdoExt->DiskGeometry.SectorsPerTrack;
+ if (cylinderSize == 0){
+ fdoExt->DiskGeometry.TracksPerCylinder = 0xff;
+ fdoExt->DiskGeometry.SectorsPerTrack = 0x3f;
+ cylinderSize = fdoExt->DiskGeometry.TracksPerCylinder * fdoExt->DiskGeometry.SectorsPerTrack;
+ }
+
+ /*
+ * Calculate number of cylinders.
+ * If there are zero cylinders, then the device lied AND it's
+ * smaller than 0xff*0x3f (about 16k sectors, usually 8 meg)
+ * this can fit into a single LONGLONG, so create another usable
+ * geometry, even if it's unusual looking.
+ * This allows small, non-standard devices, such as Sony's Memory Stick, to show up as having a partition.
+ */
+ fdoExt->DiskGeometry.Cylinders.QuadPart = (LONGLONG)((lastSector.QuadPart + 1)/cylinderSize);
+ if (fdoExt->DiskGeometry.Cylinders.QuadPart == (LONGLONG)0) {
+ fdoExt->DiskGeometry.SectorsPerTrack = 1;
+ fdoExt->DiskGeometry.TracksPerCylinder = 1;
+ fdoExt->DiskGeometry.Cylinders.QuadPart = lastSector.QuadPart + 1;
+ }
+
+ /*
+ * Calculate media capacity in bytes.
+ * For this purpose we treat the entire LUN as is if it is one partition. Disk will deal with actual partitioning.
+ */
+ fdoExt->CommonExtension.PartitionLength.QuadPart =
+ ((LONGLONG)(lastSector.QuadPart + 1)) << fdoExt->SectorShift;
+
+ /*
+ * Is this removable or fixed media
+ */
+ if (TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA)){
+ fdoExt->DiskGeometry.MediaType = RemovableMedia;
+ }
+ else {
+ fdoExt->DiskGeometry.MediaType = FixedMedia;
+ }
+
+}
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassReadDriveCapacity()
+
+Routine Description:
+
+ This routine sends a READ CAPACITY to the requested device, updates
+ the geometry information in the device object and returns
+ when it is complete. This routine is synchronous.
+
+ This routine must be called with the remove lock held or some other
+ assurance that the Fdo will not be removed while processing.
+
+Arguments:
+
+ DeviceObject - Supplies a pointer to the device object that represents
+ the device whose capacity is to be read.
+
+Return Value:
+
+ Status is returned.
+
+--*/
+_Must_inspect_result_
+NTSTATUS ClassReadDriveCapacity(_In_ PDEVICE_OBJECT Fdo)
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Fdo->DeviceExtension;
+ PCOMMON_DEVICE_EXTENSION commonExtension = Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
+ READ_CAPACITY_DATA_EX PTRALIGN readCapacityData = {0};
+ PTRANSFER_PACKET pkt;
+ NTSTATUS status;
+ PMDL driveCapMdl = NULL;
+ KEVENT event;
+ IRP pseudoIrp = {0};
+ ULONG readCapacityDataSize;
+ BOOLEAN use16ByteCdb;
+ BOOLEAN match = TRUE;
+
+ use16ByteCdb = TEST_FLAG(fdoExt->DeviceFlags, DEV_USE_16BYTE_CDB);
+
+RetryRequest:
+
+ if (use16ByteCdb) {
+ readCapacityDataSize = sizeof(READ_CAPACITY_DATA_EX);
+ } else {
+ readCapacityDataSize = sizeof(READ_CAPACITY_DATA);
+ }
+
+ if (driveCapMdl != NULL) {
+ FreeDeviceInputMdl(driveCapMdl);
+ driveCapMdl = NULL;
+ }
+
+ //
+ // Allocate the MDL based on the Read Capacity command.
+ //
+ driveCapMdl = BuildDeviceInputMdl(&readCapacityData, readCapacityDataSize);
+ if (driveCapMdl == NULL) {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto SafeExit;
+ }
+
+ pkt = DequeueFreeTransferPacket(Fdo, TRUE);
+ if (pkt == NULL) {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto SafeExit;
+ }
+
+ //
+ // Our engine needs an "original irp" to write the status back to
+ // and to count down packets (one in this case).
+ // Just use a pretend irp for this.
+ //
+
+ pseudoIrp.Tail.Overlay.DriverContext[0] = LongToPtr(1);
+ pseudoIrp.IoStatus.Status = STATUS_SUCCESS;
+ pseudoIrp.IoStatus.Information = 0;
+ pseudoIrp.MdlAddress = driveCapMdl;
+
+ //
+ // Set this up as a SYNCHRONOUS transfer, submit it,
+ // and wait for the packet to complete. The result
+ // status will be written to the original irp.
+ //
+
+ KeInitializeEvent(&event, SynchronizationEvent, FALSE);
+ SetupDriveCapacityTransferPacket(pkt,
+ &readCapacityData,
+ readCapacityDataSize,
+ &event,
+ &pseudoIrp,
+ use16ByteCdb);
+ SubmitTransferPacket(pkt);
+ (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
+
+ status = pseudoIrp.IoStatus.Status;
+
+ //
+ // If we got an UNDERRUN, retry exactly once.
+ // (The transfer_packet engine didn't retry because the result
+ // status was success).
+ //
+
+ if (NT_SUCCESS(status) &&
+ (pseudoIrp.IoStatus.Information < readCapacityDataSize)) {
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClassReadDriveCapacity: read len (%xh) < %xh, retrying ...",
+ (ULONG)pseudoIrp.IoStatus.Information, readCapacityDataSize));
+
+ pkt = DequeueFreeTransferPacket(Fdo, TRUE);
+ if (pkt) {
+ pseudoIrp.Tail.Overlay.DriverContext[0] = LongToPtr(1);
+ pseudoIrp.IoStatus.Status = STATUS_SUCCESS;
+ pseudoIrp.IoStatus.Information = 0;
+ KeInitializeEvent(&event, SynchronizationEvent, FALSE);
+ SetupDriveCapacityTransferPacket(pkt,
+ &readCapacityData,
+ readCapacityDataSize,
+ &event,
+ &pseudoIrp,
+ use16ByteCdb);
+ SubmitTransferPacket(pkt);
+ (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
+ status = pseudoIrp.IoStatus.Status;
+ if (pseudoIrp.IoStatus.Information < readCapacityDataSize){
+ status = STATUS_DEVICE_BUSY;
+ }
+ } else {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ }
+
+ if (NT_SUCCESS(status)) {
+ //
+ // The request succeeded. Check for 8 byte LBA support.
+ //
+
+ if (use16ByteCdb == FALSE) {
+
+ PREAD_CAPACITY_DATA readCapacity;
+
+ //
+ // Check whether the device supports 8 byte LBA. If the device supports
+ // it then retry the request using 16 byte CDB.
+ //
+
+ readCapacity = (PREAD_CAPACITY_DATA) &readCapacityData;
+
+ if (readCapacity->LogicalBlockAddress == 0xFFFFFFFF) {
+ //
+ // Device returned max size for last LBA. Need to send
+ // 16 byte request to get the size.
+ //
+ use16ByteCdb = TRUE;
+ goto RetryRequest;
+
+ } else {
+ //
+ // Convert the 4 byte LBA (READ_CAPACITY_DATA) to 8 byte LBA (READ_CAPACITY_DATA_EX)
+ // format for ease of use. This is the only format stored in the device extension.
+ //
+
+ RtlMoveMemory((PUCHAR)(&readCapacityData) + sizeof(ULONG), readCapacity, sizeof(READ_CAPACITY_DATA));
+ RtlZeroMemory((PUCHAR)(&readCapacityData), sizeof(ULONG));
+
+ }
+ } else {
+ //
+ // Device completed 16 byte command successfully, it supports 8-byte LBA.
+ //
+
+ SET_FLAG(fdoExt->DeviceFlags, DEV_USE_16BYTE_CDB);
+ }
+
+ //
+ // Read out and store the drive information.
+ //
+
+ InterpretCapacityData(Fdo, &readCapacityData);
+
+ //
+ // Before caching the new drive capacity, compare it with the
+ // cached capacity for any change.
+ //
+
+ if (fdoData->IsCachedDriveCapDataValid == TRUE) {
+
+ match = (BOOLEAN) RtlEqualMemory(&fdoData->LastKnownDriveCapacityData,
+ &readCapacityData, sizeof(READ_CAPACITY_DATA_EX));
+ }
+
+ //
+ // Store the readCapacityData in private FDO data.
+ // This is so that runtime memory failures don't cause disk.sys to put
+ // the paging disk in an error state. Also this is used in
+ // IOCTL_STORAGE_READ_CAPACITY.
+ //
+ fdoData->LastKnownDriveCapacityData = readCapacityData;
+ fdoData->IsCachedDriveCapDataValid = TRUE;
+
+ if (match == FALSE) {
+ if (commonExtension->CurrentState != IRP_MN_START_DEVICE)
+ {
+ //
+ // This can happen if a disk reports Parameters Changed / Capacity Data Changed sense data.
+ // NT_ASSERT(!"Drive capacity has changed while the device wasn't started!");
+ //
+ } else {
+ //
+ // state of (commonExtension->CurrentState == IRP_MN_START_DEVICE) indicates that the device has been started.
+ // UpdateDiskPropertiesWorkItemActive is used as a flag to ensure we only have one work item updating the disk
+ // properties at a time.
+ //
+ if (InterlockedCompareExchange((volatile LONG *)&fdoData->UpdateDiskPropertiesWorkItemActive, 1, 0) == 0)
+ {
+ PIO_WORKITEM workItem;
+
+ workItem = IoAllocateWorkItem(Fdo);
+
+ if (workItem) {
+ //
+ // The disk capacity has changed, send notification to the disk driver.
+ // Start a work item to notify the disk class driver asynchronously.
+ //
+ IoQueueWorkItem(workItem, ClasspUpdateDiskProperties, DelayedWorkQueue, workItem);
+ } else {
+ InterlockedExchange((volatile LONG *)&fdoData->UpdateDiskPropertiesWorkItemActive, 0);
+ }
+ }
+ }
+ }
+
+ } else {
+ //
+ // The request failed.
+ //
+
+ //
+ // ISSUE - 2000/02/04 - henrygab - non-512-byte sector sizes and failed geometry update
+ // what happens when the disk's sector size is bigger than
+ // 512 bytes and we hit this code path? this is untested.
+ //
+ // If the read capacity fails, set the geometry to reasonable parameter
+ // so things don't fail at unexpected places. Zero the geometry
+ // except for the bytes per sector and sector shift.
+ //
+
+ //
+ // This request can sometimes fail legitimately
+ // (e.g. when a SCSI device is attached but turned off)
+ // so this is not necessarily a device/driver bug.
+ //
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, "ClassReadDriveCapacity on Fdo %p failed with status %ul.", Fdo, status));
+
+ //
+ // Write in a default disk geometry which we HOPE is right (??).
+ //
+
+ RtlZeroMemory(&fdoExt->DiskGeometry, sizeof(DISK_GEOMETRY));
+ fdoExt->DiskGeometry.BytesPerSector = 512;
+ fdoExt->SectorShift = 9;
+ fdoExt->CommonExtension.PartitionLength.QuadPart = (LONGLONG) 0;
+
+ //
+ // Is this removable or fixed media
+ //
+
+ if (TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA)){
+ fdoExt->DiskGeometry.MediaType = RemovableMedia;
+ } else {
+ fdoExt->DiskGeometry.MediaType = FixedMedia;
+ }
+ }
+
+SafeExit:
+
+
+ //
+ // In case DEV_USE_16BYTE_CDB flag is not set, classpnp translates R/W request into READ/WRITE 10 SCSI command.
+ // These SCSI commands have 2 bytes in "Transfer Blocks" field.
+ // Make sure this max length (0xFFFF * sector size) is respected during request split.
+ //
+ if (!TEST_FLAG(fdoExt->DeviceFlags, DEV_USE_16BYTE_CDB)) {
+ ULONG cdb10MaxBlocks = ((ULONG)USHORT_MAX) << fdoExt->SectorShift;
+
+ fdoData->HwMaxXferLen = min(cdb10MaxBlocks, fdoData->HwMaxXferLen);
+ }
+
+ if (driveCapMdl != NULL) {
+ FreeDeviceInputMdl(driveCapMdl);
+ }
+
+ //
+ // If the request failed for some reason then invalidate the cached
+ // capacity data for removable devices. So that we won't return
+ // wrong capacity in IOCTL_STORAGE_READ_CAPACITY
+ //
+
+ if (!NT_SUCCESS(status) && (fdoExt->DiskGeometry.MediaType == RemovableMedia)) {
+ fdoData->IsCachedDriveCapDataValid = FALSE;
+ }
+
+ //
+ // Don't let memory failures (either here or in the port driver) in the ReadDriveCapacity call
+ // put the paging disk in an error state such that paging fails.
+ // Return the last known drive capacity (which may possibly be slightly out of date, even on
+ // fixed media, e.g. for storage cabinets that can grow a logical disk).
+ //
+ if ((status == STATUS_INSUFFICIENT_RESOURCES) &&
+ (fdoData->IsCachedDriveCapDataValid) &&
+ (fdoExt->DiskGeometry.MediaType == FixedMedia)) {
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, "ClassReadDriveCapacity: defaulting to cached DriveCapacity data"));
+ InterpretCapacityData(Fdo, &fdoData->LastKnownDriveCapacityData);
+ status = STATUS_SUCCESS;
+ }
+
+ return status;
+}
+
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassSendStartUnit()
+
+Routine Description:
+
+ Send command to SCSI unit to start or power up.
+ Because this command is issued asynchronounsly, that is, without
+ waiting on it to complete, the IMMEDIATE flag is not set. This
+ means that the CDB will not return until the drive has powered up.
+ This should keep subsequent requests from being submitted to the
+ device before it has completely spun up.
+
+ This routine is called from the InterpretSense routine, when a
+ request sense returns data indicating that a drive must be
+ powered up.
+
+ This routine may also be called from a class driver's error handler,
+ or anytime a non-critical start device should be sent to the device.
+
+Arguments:
+
+ Fdo - The functional device object for the stopped device.
+
+Return Value:
+
+ None.
+
+--*/
+VOID
+ClassSendStartUnit(
+ _In_ PDEVICE_OBJECT Fdo
+ )
+{
+ PIO_STACK_LOCATION irpStack;
+ PIRP irp;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
+ PSCSI_REQUEST_BLOCK srb;
+ PCOMPLETION_CONTEXT context;
+ PCDB cdb;
+ NTSTATUS status;
+ PSTORAGE_REQUEST_BLOCK srbEx;
+
+ //
+ // Allocate Srb from nonpaged pool.
+ //
+
+ context = ExAllocatePoolWithTag(NonPagedPoolNx,
+ sizeof(COMPLETION_CONTEXT),
+ '6CcS');
+
+ if (context == NULL) {
+
+ //
+ // ISSUE-2000/02/03-peterwie
+ // This code path was inheritted from the NT 4.0 class2.sys driver.
+ // It needs to be changed to survive low-memory conditions.
+ //
+
+ KeBugCheck(SCSI_DISK_DRIVER_INTERNAL);
+ }
+
+ //
+ // Save the device object in the context for use by the completion
+ // routine.
+ //
+
+ context->DeviceObject = Fdo;
+
+ srb = &context->Srb.Srb;
+ if (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+ srbEx = &context->Srb.SrbEx;
+ status = InitializeStorageRequestBlock(srbEx,
+ STORAGE_ADDRESS_TYPE_BTL8,
+ sizeof(context->Srb.SrbExBuffer),
+ 1,
+ SrbExDataTypeScsiCdb16);
+ if (!NT_SUCCESS(status)) {
+ FREE_POOL(context);
+ NT_ASSERT(FALSE);
+ return;
+ }
+
+ srbEx->SrbFunction = SRB_FUNCTION_EXECUTE_SCSI;
+
+ } else {
+
+ //
+ // Zero out srb.
+ //
+
+ RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK));
+
+ //
+ // Write length to SRB.
+ //
+
+ srb->Length = sizeof(SCSI_REQUEST_BLOCK);
+
+ srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
+ }
+
+ //
+ // Set timeout value large enough for drive to spin up.
+ //
+
+ SrbSetTimeOutValue(srb, START_UNIT_TIMEOUT);
+
+ //
+ // Set the transfer length.
+ //
+
+ SrbAssignSrbFlags(srb,
+ (SRB_FLAGS_NO_DATA_TRANSFER |
+ SRB_FLAGS_DISABLE_AUTOSENSE |
+ SRB_FLAGS_DISABLE_SYNCH_TRANSFER));
+
+ //
+ // Build the start unit CDB.
+ //
+
+ SrbSetCdbLength(srb, 6);
+ cdb = SrbGetCdb(srb);
+
+ cdb->START_STOP.OperationCode = SCSIOP_START_STOP_UNIT;
+ cdb->START_STOP.Start = 1;
+ cdb->START_STOP.Immediate = 0;
+ cdb->START_STOP.LogicalUnitNumber = srb->Lun;
+
+ //
+ // Build the asynchronous request to be sent to the port driver.
+ // Since this routine is called from a DPC the IRP should always be
+ // available.
+ //
+
+ irp = IoAllocateIrp(Fdo->StackSize, FALSE);
+
+ if (irp == NULL) {
+
+ //
+ // ISSUE-2000/02/03-peterwie
+ // This code path was inheritted from the NT 4.0 class2.sys driver.
+ // It needs to be changed to survive low-memory conditions.
+ //
+
+ KeBugCheck(SCSI_DISK_DRIVER_INTERNAL);
+
+ }
+
+ ClassAcquireRemoveLock(Fdo, irp);
+
+ IoSetCompletionRoutine(irp,
+ (PIO_COMPLETION_ROUTINE)ClassAsynchronousCompletion,
+ context,
+ TRUE,
+ TRUE,
+ TRUE);
+
+ irpStack = IoGetNextIrpStackLocation(irp);
+ irpStack->MajorFunction = IRP_MJ_SCSI;
+ SrbSetOriginalRequest(srb, irp);
+
+ //
+ // Store the SRB address in next stack for port driver.
+ //
+
+ irpStack->Parameters.Scsi.Srb = srb;
+
+ //
+ // Call the port driver with the IRP.
+ //
+
+ IoCallDriver(fdoExtension->CommonExtension.LowerDeviceObject, irp);
+
+ return;
+
+} // end StartUnit()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassAsynchronousCompletion() ISSUE-2000/02/18-henrygab - why public?!
+
+Routine Description:
+
+ This routine is called when an asynchronous I/O request
+ which was issused by the class driver completes. Examples of such requests
+ are release queue or START UNIT. This routine releases the queue if
+ necessary. It then frees the context and the IRP.
+
+Arguments:
+
+ DeviceObject - The device object for the logical unit; however since this
+ is the top stack location the value is NULL.
+
+ Irp - Supplies a pointer to the Irp to be processed.
+
+ Context - Supplies the context to be used to process this request.
+
+Return Value:
+
+ None.
+
+--*/
+NTSTATUS
+ClassAsynchronousCompletion(
+ PDEVICE_OBJECT DeviceObject,
+ PIRP Irp,
+ PVOID Context
+ )
+{
+ PCOMPLETION_CONTEXT context = Context;
+ PSCSI_REQUEST_BLOCK srb;
+ ULONG srbFunction;
+ ULONG srbFlags;
+
+ if (context == NULL) {
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ if (DeviceObject == NULL) {
+
+ DeviceObject = context->DeviceObject;
+ }
+
+ srb = &context->Srb.Srb;
+
+ if (srb->Function == SRB_FUNCTION_STORAGE_REQUEST_BLOCK) {
+ srbFunction = ((PSTORAGE_REQUEST_BLOCK)srb)->SrbFunction;
+ srbFlags = ((PSTORAGE_REQUEST_BLOCK)srb)->SrbFlags;
+ } else {
+ srbFunction = srb->Function;
+ srbFlags = srb->SrbFlags;
+ }
+
+ //
+ // If this is an execute srb, then check the return status and make sure.
+ // the queue is not frozen.
+ //
+
+ if (srbFunction == SRB_FUNCTION_EXECUTE_SCSI) {
+
+ //
+ // Check for a frozen queue.
+ //
+
+ if (srb->SrbStatus & SRB_STATUS_QUEUE_FROZEN) {
+
+ //
+ // Unfreeze the queue getting the device object from the context.
+ //
+
+ ClassReleaseQueue(context->DeviceObject);
+ }
+ }
+
+ { // free port-allocated sense buffer if we can detect
+
+ if (((PCOMMON_DEVICE_EXTENSION)(DeviceObject->DeviceExtension))->IsFdo) {
+
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
+ if (PORT_ALLOCATED_SENSE(fdoExtension, srb)) {
+ FREE_PORT_ALLOCATED_SENSE_BUFFER(fdoExtension, srb);
+ }
+
+ } else {
+
+ NT_ASSERT(!TEST_FLAG(srbFlags, SRB_FLAGS_FREE_SENSE_BUFFER));
+
+ }
+ }
+
+
+ //
+ // Free the context and the Irp.
+ //
+
+ if (Irp->MdlAddress != NULL) {
+ MmUnlockPages(Irp->MdlAddress);
+ IoFreeMdl(Irp->MdlAddress);
+
+ Irp->MdlAddress = NULL;
+ }
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+
+ FREE_POOL(context);
+
+ IoFreeIrp(Irp);
+
+ //
+ // Indicate the I/O system should stop processing the Irp completion.
+ //
+
+ return STATUS_MORE_PROCESSING_REQUIRED;
+
+} // end ClassAsynchronousCompletion()
+
+
+NTSTATUS
+ServiceTransferRequest(
+ PDEVICE_OBJECT Fdo,
+ PIRP Irp,
+ BOOLEAN PostToDpc
+ )
+
+/*++
+
+Routine description:
+
+ This routine processes Io requests, splitting them if they
+ are larger than what the hardware can handle at a time. If
+ there isn't enough memory available, the request is placed
+ in a queue, to be processed at a later time
+
+ If this is a high priority paging request, all regular Io
+ are throttled to provide Mm with better thoroughput
+
+Arguments:
+
+ Fdo - The functional device object processing the request
+ Irp - The Io request to be processed
+ PostToDpc - Flag that indicates that this IRP must be posted to a DPC
+
+Return Value:
+
+ STATUS_SUCCESS if successful, an error code otherwise
+
+--*/
+
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Fdo->DeviceExtension;
+ PCOMMON_DEVICE_EXTENSION commonExtension = Fdo->DeviceExtension;
+ PSTORAGE_ADAPTER_DESCRIPTOR adapterDesc = commonExtension->PartitionZeroExtension->AdapterDescriptor;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
+ IO_PAGING_PRIORITY priority = (TEST_FLAG(Irp->Flags, IRP_PAGING_IO)) ? IoGetPagingIoPriority(Irp) : IoPagingPriorityInvalid;
+ BOOLEAN deferClientIrp = FALSE;
+ BOOLEAN driverUsesStartIO = (commonExtension->DriverExtension->InitData.ClassStartIo != NULL);
+ KIRQL oldIrql;
+ NTSTATUS status = STATUS_UNSUCCESSFUL;
+ PIO_STACK_LOCATION currentIrpStack = IoGetCurrentIrpStackLocation(Irp);
+
+ /*
+ * Initialize IRP status for the master IRP to
+ * - STATUS_FT_READ_FROM_COPY if it's a copy-specific read
+ * - STATUS_SUCCESS otherwise.
+ *
+ * This is required. When Classpnp determines the status for the master IRP
+ * when completing child IRPs, the call to IoSetMasterIrpStatus
+ * will be functioning properly (See TransferPktComplete function)
+ *
+ * Note:
+ * If the IRP is a copy-specific read, File System already initialized the IRP status
+ * to be STATUS_FT_READ_FROM_COPY. However, this can be changed when the IRP arrives
+ * at Classpnp. It's possible that other drivers in the stack may initialize the
+ * IRP status field to other values before forwarding the IRP down the stack.
+ * To be defensive, we initialize the IRP status to either STATUS_FT_READ_FROM_COPY
+ * if it's a copy-specific read, or STATUS_SUCCESS otherwise.
+ */
+ if (currentIrpStack->MajorFunction == IRP_MJ_READ &&
+ TEST_FLAG(currentIrpStack->Flags, SL_KEY_SPECIFIED) &&
+ IsKeyReadCopyNumber(currentIrpStack->Parameters.Read.Key)) {
+ Irp->IoStatus.Status = STATUS_FT_READ_FROM_COPY;
+ } else {
+ Irp->IoStatus.Status = STATUS_SUCCESS;
+ }
+
+ //
+ // If this is a high priority request, hold off all other Io requests
+ //
+
+ if (priority == IoPagingPriorityHigh)
+ {
+ KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
+
+ if (fdoData->NumHighPriorityPagingIo == 0)
+ {
+ //
+ // Entering throttle mode
+ //
+
+ KeQuerySystemTime(&fdoData->ThrottleStartTime);
+ }
+
+ fdoData->NumHighPriorityPagingIo++;
+ fdoData->MaxInterleavedNormalIo += ClassMaxInterleavePerCriticalIo;
+
+ KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
+ }
+ else
+ {
+ if (fdoData->NumHighPriorityPagingIo != 0)
+ {
+ //
+ // This request wasn't flagged as critical and atleast one critical request
+ // is currently outstanding. Queue this request until all of those are done
+ // but only if the interleave threshold has been reached
+ //
+
+ KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
+
+ if (fdoData->NumHighPriorityPagingIo != 0)
+ {
+ if (fdoData->MaxInterleavedNormalIo == 0)
+ {
+ deferClientIrp = TRUE;
+ }
+ else
+ {
+ fdoData->MaxInterleavedNormalIo--;
+ }
+ }
+
+ KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
+ }
+ }
+
+ if (!deferClientIrp)
+ {
+ PIO_STACK_LOCATION currentSp = IoGetCurrentIrpStackLocation(Irp);
+ ULONG entireXferLen = currentSp->Parameters.Read.Length;
+ PUCHAR bufPtr = MmGetMdlVirtualAddress(Irp->MdlAddress);
+ LARGE_INTEGER targetLocation = currentSp->Parameters.Read.ByteOffset;
+ PTRANSFER_PACKET pkt;
+ SINGLE_LIST_ENTRY pktList;
+ PSINGLE_LIST_ENTRY slistEntry;
+ ULONG hwMaxXferLen;
+ ULONG numPackets;
+ ULONG i;
+
+
+ /*
+ * We precomputed fdoData->HwMaxXferLen using (MaximumPhysicalPages-1).
+ * If the buffer is page-aligned, that's one less page crossing so we can add the page back in.
+ * Note: adapters that return MaximumPhysicalPages=0x10 depend on this to
+ * transfer aligned 64K requests in one piece.
+ * Also note: make sure adding PAGE_SIZE back in doesn't wrap to zero.
+ */
+ if (((ULONG_PTR)bufPtr & (PAGE_SIZE-1)) || (fdoData->HwMaxXferLen > 0xffffffff-PAGE_SIZE)){
+ hwMaxXferLen = fdoData->HwMaxXferLen;
+ }
+ else {
+ NT_ASSERT((PAGE_SIZE%fdoExt->DiskGeometry.BytesPerSector) == 0);
+ hwMaxXferLen = min(fdoData->HwMaxXferLen+PAGE_SIZE, adapterDesc->MaximumTransferLength);
+ }
+
+ /*
+ * Compute the number of hw xfers we'll have to do.
+ * Calculate this without allowing for an overflow condition.
+ */
+ NT_ASSERT(hwMaxXferLen >= PAGE_SIZE);
+ numPackets = entireXferLen/hwMaxXferLen;
+ if (entireXferLen % hwMaxXferLen){
+ numPackets++;
+ }
+
+ /*
+ * Use our 'simple' slist functions since we don't need interlocked.
+ */
+ SimpleInitSlistHdr(&pktList);
+
+ if (driverUsesStartIO) {
+ /*
+ * special case: StartIO-based writing must stay serialized, so just
+ * re-use one packet.
+ */
+ pkt = DequeueFreeTransferPacket(Fdo, TRUE);
+ if (pkt) {
+ SimplePushSlist(&pktList, (PSINGLE_LIST_ENTRY)&pkt->SlistEntry);
+ i = 1;
+ } else {
+ i = 0;
+ }
+ } else {
+ /*
+ * First get all the TRANSFER_PACKETs that we'll need at once.
+ */
+ for (i = 0; i < numPackets; i++){
+ pkt = DequeueFreeTransferPacket(Fdo, TRUE);
+ if (pkt){
+ SimplePushSlist(&pktList, (PSINGLE_LIST_ENTRY)&pkt->SlistEntry);
+ }
+ else {
+ break;
+ }
+ }
+ }
+
+
+ if ((i == numPackets) &&
+ (!driverUsesStartIO)) {
+ NTSTATUS pktStat;
+
+ /*
+ * The IoStatus.Information field will be incremented to the
+ * transfer length as the pieces complete.
+ */
+ Irp->IoStatus.Information = 0;
+
+ /*
+ * Store the number of transfer pieces inside the original IRP.
+ * It will be used to count down the pieces as they complete.
+ */
+ Irp->Tail.Overlay.DriverContext[0] = LongToPtr(numPackets);
+
+ /*
+ * For the common 1-packet case, we want to allow for an optimization by BlkCache
+ * (and also potentially synchronous storage drivers) which may complete the
+ * downward request synchronously.
+ * In that synchronous completion case, we want to _not_ mark the original irp pending
+ * and thereby save on the top-level APC.
+ * It's critical to coordinate this with the completion routine so that we mark the original irp
+ * pending if-and-only-if we return STATUS_PENDING for it.
+ */
+ if (numPackets > 1){
+ IoMarkIrpPending(Irp);
+ status = STATUS_PENDING;
+ }
+ else {
+ status = STATUS_SUCCESS;
+ }
+
+ /*
+ * Transmit the pieces of the transfer.
+ */
+ while (entireXferLen > 0){
+ ULONG thisPieceLen = MIN(hwMaxXferLen, entireXferLen);
+
+ /*
+ * Set up a TRANSFER_PACKET for this piece and send it.
+ */
+ slistEntry = SimplePopSlist(&pktList);
+ NT_ASSERT(slistEntry);
+ pkt = CONTAINING_RECORD(slistEntry, TRANSFER_PACKET, SlistEntry);
+ SetupReadWriteTransferPacket( pkt,
+ bufPtr,
+ thisPieceLen,
+ targetLocation,
+ Irp);
+
+ //
+ // If the IRP needs to be split, then we need to use a partial MDL.
+ // This prevents problems if the same MDL is mapped multiple times.
+ //
+ if (numPackets > 1) {
+ pkt->UsePartialMdl = TRUE;
+ }
+
+ /*
+ * When an IRP is completed, the completion routine checks to see if there
+ * is a deferred IRP ready to sent down (assuming that there are no non-idle
+ * requests waiting to be serviced). If such a deferred IRP is available, it
+ * is sent down using this routine. However, if the lower driver completes
+ * the request inline, there is a potential for multiple deferred IRPs being
+ * sent down in the context of the same completion thread, thus exhausting
+ * the call stack.
+ * In order to prevent this from happening, we need to ensure that deferred
+ * IRPs that are dequeued in the context of a request's completion routine
+ * get posted to a DPC.
+ */
+ if (PostToDpc) {
+
+ pkt->RetryIn100nsUnits = 0;
+ TransferPacketQueueRetryDpc(pkt);
+ status = STATUS_PENDING;
+
+ } else {
+
+ pktStat = SubmitTransferPacket(pkt);
+
+ /*
+ * If any of the packets completes with pending, we MUST return pending.
+ * Also, if a packet completes with an error, return pending; this is because
+ * in the completion routine we mark the original irp pending if the packet failed
+ * (since we may retry, thereby switching threads).
+ */
+ if (pktStat != STATUS_SUCCESS){
+ status = STATUS_PENDING;
+ }
+ }
+
+ entireXferLen -= thisPieceLen;
+ bufPtr += thisPieceLen;
+ targetLocation.QuadPart += thisPieceLen;
+ }
+ NT_ASSERT(SimpleIsSlistEmpty(&pktList));
+ }
+ else if (i >= 1){
+ /*
+ * We were unable to get all the TRANSFER_PACKETs we need,
+ * but we did get at least one.
+ * That means that we are in extreme low-memory stress.
+ * We'll try doing this transfer using a single packet.
+ * The port driver is certainly also in stress, so use one-page
+ * transfers.
+ */
+
+ /*
+ * Free all but one of the TRANSFER_PACKETs.
+ */
+ while (i-- > 1){
+ slistEntry = SimplePopSlist(&pktList);
+ NT_ASSERT(slistEntry);
+ pkt = CONTAINING_RECORD(slistEntry, TRANSFER_PACKET, SlistEntry);
+ EnqueueFreeTransferPacket(Fdo, pkt);
+ }
+
+ /*
+ * Get the single TRANSFER_PACKET that we'll be using.
+ */
+ slistEntry = SimplePopSlist(&pktList);
+ NT_ASSERT(slistEntry);
+ NT_ASSERT(SimpleIsSlistEmpty(&pktList));
+ pkt = CONTAINING_RECORD(slistEntry, TRANSFER_PACKET, SlistEntry);
+
+ if (!driverUsesStartIO) {
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_RW, "Insufficient packets available in ServiceTransferRequest - entering lowMemRetry with pkt=%p.", pkt));
+ }
+
+ /*
+ * Set the number of transfer packets (one)
+ * inside the original irp.
+ */
+ Irp->IoStatus.Information = 0;
+ Irp->Tail.Overlay.DriverContext[0] = LongToPtr(1);
+ IoMarkIrpPending(Irp);
+
+ /*
+ * Set up the TRANSFER_PACKET for a lowMem transfer and launch.
+ */
+ SetupReadWriteTransferPacket( pkt,
+ bufPtr,
+ entireXferLen,
+ targetLocation,
+ Irp);
+
+ InitLowMemRetry(pkt, bufPtr, entireXferLen, targetLocation);
+ StepLowMemRetry(pkt);
+ status = STATUS_PENDING;
+ }
+ else {
+ /*
+ * We were unable to get ANY TRANSFER_PACKETs.
+ * Defer this client irp until some TRANSFER_PACKETs free up.
+ */
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_RW, "No packets available in ServiceTransferRequest - deferring transfer (Irp=%p)...", Irp));
+
+ if (priority == IoPagingPriorityHigh)
+ {
+ KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
+
+ if (fdoData->MaxInterleavedNormalIo < ClassMaxInterleavePerCriticalIo)
+ {
+ fdoData->MaxInterleavedNormalIo = 0;
+ }
+ else
+ {
+ fdoData->MaxInterleavedNormalIo -= ClassMaxInterleavePerCriticalIo;
+ }
+
+ fdoData->NumHighPriorityPagingIo--;
+
+ if (fdoData->NumHighPriorityPagingIo == 0)
+ {
+ LARGE_INTEGER period;
+
+ //
+ // Exiting throttle mode
+ //
+
+ KeQuerySystemTime(&fdoData->ThrottleStopTime);
+
+ period.QuadPart = fdoData->ThrottleStopTime.QuadPart - fdoData->ThrottleStartTime.QuadPart;
+ fdoData->LongestThrottlePeriod.QuadPart = max(fdoData->LongestThrottlePeriod.QuadPart, period.QuadPart);
+ }
+
+ KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
+ }
+
+ deferClientIrp = TRUE;
+ }
+ }
+ _Analysis_assume_(deferClientIrp);
+ if (deferClientIrp)
+ {
+ IoMarkIrpPending(Irp);
+ EnqueueDeferredClientIrp(Fdo, Irp);
+ status = STATUS_PENDING;
+ }
+
+ NT_ASSERT(status != STATUS_UNSUCCESSFUL);
+
+ return status;
+}
+
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassIoComplete()
+
+Routine Description:
+
+ This routine executes when the port driver has completed a request.
+ It looks at the SRB status in the completing SRB and if not success
+ it checks for valid request sense buffer information. If valid, the
+ info is used to update status with more precise message of type of
+ error. This routine deallocates the SRB.
+
+ This routine should only be placed on the stack location for a class
+ driver FDO.
+
+Arguments:
+
+ Fdo - Supplies the device object which represents the logical
+ unit.
+
+ Irp - Supplies the Irp which has completed.
+
+ Context - Supplies a pointer to the SRB.
+
+Return Value:
+
+ NT status
+
+--*/
+NTSTATUS
+ClassIoComplete(
+ IN PDEVICE_OBJECT Fdo,
+ IN PIRP Irp,
+ IN PVOID Context
+ )
+{
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ PSCSI_REQUEST_BLOCK srb = Context;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
+ NTSTATUS status;
+ BOOLEAN retry;
+ BOOLEAN callStartNextPacket;
+ ULONG srbFlags;
+ ULONG srbFunction;
+
+ NT_ASSERT(fdoExtension->CommonExtension.IsFdo);
+
+ if (srb->Function == SRB_FUNCTION_STORAGE_REQUEST_BLOCK) {
+ srbFlags = ((PSTORAGE_REQUEST_BLOCK)srb)->SrbFlags;
+ srbFunction = ((PSTORAGE_REQUEST_BLOCK)srb)->SrbFunction;
+ } else {
+ srbFlags = srb->SrbFlags;
+ srbFunction = srb->Function;
+ }
+
+ #if DBG
+ if (srbFunction == SRB_FUNCTION_FLUSH) {
+ DBGLOGFLUSHINFO(fdoData, FALSE, FALSE, TRUE);
+ }
+ #endif
+
+ //
+ // Check SRB status for success of completing request.
+ //
+
+ if (SRB_STATUS(srb->SrbStatus) != SRB_STATUS_SUCCESS) {
+ LONGLONG retryInterval;
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassIoComplete: IRP %p, SRB %p\n", Irp, srb));
+
+ //
+ // Release the queue if it is frozen.
+ //
+
+ if (srb->SrbStatus & SRB_STATUS_QUEUE_FROZEN) {
+ ClassReleaseQueue(Fdo);
+ }
+ retry = InterpretSenseInfoWithoutHistory(
+ Fdo,
+ Irp,
+ srb,
+ irpStack->MajorFunction,
+ ((irpStack->MajorFunction == IRP_MJ_DEVICE_CONTROL) ?
+ irpStack->Parameters.DeviceIoControl.IoControlCode :
+ 0),
+ MAXIMUM_RETRIES -
+ ((ULONG)(ULONG_PTR)irpStack->Parameters.Others.Argument4),
+ &status,
+ &retryInterval);
+
+ //
+ // For Persistent Reserve requests, make sure user gets back partial data.
+ //
+
+ if (irpStack->MajorFunction == IRP_MJ_DEVICE_CONTROL &&
+ (irpStack->Parameters.DeviceIoControl.IoControlCode == IOCTL_STORAGE_PERSISTENT_RESERVE_IN ||
+ irpStack->Parameters.DeviceIoControl.IoControlCode == IOCTL_STORAGE_PERSISTENT_RESERVE_OUT) &&
+ status == STATUS_DATA_OVERRUN) {
+
+ status = STATUS_SUCCESS;
+ retry = FALSE;
+ }
+
+ //
+ // If the status is verified required and the this request
+ // should bypass verify required then retry the request.
+ //
+
+ if (TEST_FLAG(irpStack->Flags, SL_OVERRIDE_VERIFY_VOLUME) &&
+ status == STATUS_VERIFY_REQUIRED) {
+
+ status = STATUS_IO_DEVICE_ERROR;
+ retry = TRUE;
+ }
+
+#pragma warning(suppress:4213) // okay to cast Arg4 as a ulong for this use case
+ if (retry && ((ULONG)(ULONG_PTR)irpStack->Parameters.Others.Argument4)--) {
+
+ //
+ // Retry request.
+ //
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "Retry request %p\n", Irp));
+
+ if (PORT_ALLOCATED_SENSE(fdoExtension, srb)) {
+ FREE_PORT_ALLOCATED_SENSE_BUFFER(fdoExtension, srb);
+ }
+
+ RetryRequest(Fdo, Irp, srb, FALSE, retryInterval);
+ return STATUS_MORE_PROCESSING_REQUIRED;
+ }
+
+ } else {
+
+ //
+ // Set status for successful request
+ //
+ fdoData->LoggedTURFailureSinceLastIO = FALSE;
+ ClasspPerfIncrementSuccessfulIo(fdoExtension);
+ status = STATUS_SUCCESS;
+ } // end if (SRB_STATUS(srb->SrbStatus) == SRB_STATUS_SUCCESS)
+
+
+ //
+ // ensure we have returned some info, and it matches what the
+ // original request wanted for PAGING operations only
+ //
+
+ if ((NT_SUCCESS(status)) && TEST_FLAG(Irp->Flags, IRP_PAGING_IO)) {
+ NT_ASSERT(Irp->IoStatus.Information != 0);
+ NT_ASSERT(irpStack->Parameters.Read.Length == Irp->IoStatus.Information);
+ }
+
+ //
+ // remember if the caller wanted to skip calling IoStartNextPacket.
+ // for legacy reasons, we cannot call IoStartNextPacket for IoDeviceControl
+ // calls. this setting only affects device objects with StartIo routines.
+ //
+
+ callStartNextPacket = !TEST_FLAG(srbFlags, SRB_FLAGS_DONT_START_NEXT_PACKET);
+ if (irpStack->MajorFunction == IRP_MJ_DEVICE_CONTROL) {
+ callStartNextPacket = FALSE;
+ }
+
+ //
+ // Free MDL if allocated.
+ //
+
+ if (TEST_FLAG(srbFlags, SRB_CLASS_FLAGS_FREE_MDL)) {
+ SrbClearSrbFlags(srb, SRB_CLASS_FLAGS_FREE_MDL);
+ IoFreeMdl(Irp->MdlAddress);
+ Irp->MdlAddress = NULL;
+ }
+
+
+ //
+ // Free the srb
+ //
+
+ if (!TEST_FLAG(srbFlags, SRB_CLASS_FLAGS_PERSISTANT)) {
+
+ if (PORT_ALLOCATED_SENSE(fdoExtension, srb)) {
+ FREE_PORT_ALLOCATED_SENSE_BUFFER(fdoExtension, srb);
+ }
+
+ if (fdoExtension->CommonExtension.IsSrbLookasideListInitialized){
+ ClassFreeOrReuseSrb(fdoExtension, srb);
+ }
+ else {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassIoComplete is freeing an SRB (possibly) on behalf of another driver."));
+ FREE_POOL(srb);
+ }
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassIoComplete: Not Freeing srb @ %p because "
+ "SRB_CLASS_FLAGS_PERSISTANT set\n", srb));
+ if (PORT_ALLOCATED_SENSE(fdoExtension, srb)) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassIoComplete: Not Freeing sensebuffer @ %p "
+ " because SRB_CLASS_FLAGS_PERSISTANT set\n",
+ srb->SenseInfoBuffer));
+ }
+
+ }
+
+ //
+ // Set status in completing IRP.
+ //
+
+ Irp->IoStatus.Status = status;
+
+ //
+ // Set the hard error if necessary.
+ //
+
+ if (!NT_SUCCESS(status) &&
+ IoIsErrorUserInduced(status) &&
+ (Irp->Tail.Overlay.Thread != NULL)
+ ) {
+
+ //
+ // Store DeviceObject for filesystem, and clear
+ // in IoStatus.Information field.
+ //
+
+ IoSetHardErrorOrVerifyDevice(Irp, Fdo);
+ Irp->IoStatus.Information = 0;
+ }
+
+ //
+ // If pending has be returned for this irp then mark the current stack as
+ // pending.
+ //
+
+ if (Irp->PendingReturned) {
+ IoMarkIrpPending(Irp);
+ }
+
+ if (fdoExtension->CommonExtension.DriverExtension->InitData.ClassStartIo) {
+ if (callStartNextPacket) {
+ KIRQL oldIrql;
+ KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
+ IoStartNextPacket(Fdo, TRUE); // Yes, some IO must now be cancellable.
+ KeLowerIrql(oldIrql);
+ }
+ }
+
+ ClassReleaseRemoveLock(Fdo, Irp);
+
+ return status;
+
+} // end ClassIoComplete()
+
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassSendSrbSynchronous()
+
+Routine Description:
+
+ This routine is called by SCSI device controls to complete an
+ SRB and send it to the port driver synchronously (ie wait for
+ completion). The CDB is already completed along with the SRB CDB
+ size and request timeout value.
+
+Arguments:
+
+ Fdo - Supplies the functional device object which represents the target.
+
+ Srb - Supplies a partially initialized SRB. The SRB cannot come from zone.
+
+ BufferAddress - Supplies the address of the buffer.
+
+ BufferLength - Supplies the length in bytes of the buffer.
+
+ WriteToDevice - Indicates the data should be transfer to the device.
+
+Return Value:
+
+ NTSTATUS indicating the final results of the operation.
+
+ If NT_SUCCESS(), then the amount of usable data is contained in the field
+ Srb->DataTransferLength
+
+--*/
+NTSTATUS
+ClassSendSrbSynchronous(
+ _In_ PDEVICE_OBJECT Fdo,
+ _Inout_ PSCSI_REQUEST_BLOCK _Srb,
+ _In_reads_bytes_opt_(BufferLength) PVOID BufferAddress,
+ _In_ ULONG BufferLength,
+ _In_ BOOLEAN WriteToDevice
+ )
+{
+
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
+ IO_STATUS_BLOCK ioStatus = {0};
+ PIRP irp;
+ PIO_STACK_LOCATION irpStack;
+ KEVENT event;
+ PVOID senseInfoBuffer = NULL;
+ ULONG senseInfoBufferLength = SENSE_BUFFER_SIZE_EX;
+ ULONG retryCount = MAXIMUM_RETRIES;
+ NTSTATUS status;
+ BOOLEAN retry;
+ PSTORAGE_REQUEST_BLOCK_HEADER Srb = (PSTORAGE_REQUEST_BLOCK_HEADER)_Srb;
+
+ //
+ // NOTE: This code is only pagable because we are not freezing
+ // the queue. Allowing the queue to be frozen from a pagable
+ // routine could leave the queue frozen as we try to page in
+ // the code to unfreeze the queue. The result would be a nice
+ // case of deadlock. Therefore, since we are unfreezing the
+ // queue regardless of the result, just set the NO_FREEZE_QUEUE
+ // flag in the SRB.
+ //
+
+ NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
+ NT_ASSERT(fdoExtension->CommonExtension.IsFdo);
+
+ if (Srb->Function != SRB_FUNCTION_STORAGE_REQUEST_BLOCK) {
+ //
+ // Write length to SRB.
+ //
+
+ Srb->Length = sizeof(SCSI_REQUEST_BLOCK);
+
+ //
+ // Set SCSI bus address.
+ //
+
+ Srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
+ }
+
+ //
+ // The Srb->Function should have been set corresponding to SrbType.
+ //
+
+ NT_ASSERT( ((fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_SCSI_REQUEST_BLOCK) && (Srb->Function == SRB_FUNCTION_EXECUTE_SCSI)) ||
+ ((fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) && (Srb->Function == SRB_FUNCTION_STORAGE_REQUEST_BLOCK)) );
+
+ //
+ // Sense buffer is in aligned nonpaged pool.
+ //
+
+#if defined(_ARM_) || defined(_ARM64_)
+
+ //
+ // ARM has specific alignment requirements, although this will not have a functional impact on x86 or amd64
+ // based platforms. We are taking the conservative approach here.
+ //
+ senseInfoBufferLength = ALIGN_UP_BY(senseInfoBufferLength,KeGetRecommendedSharedDataAlignment());
+
+#endif
+
+ senseInfoBuffer = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned,
+ senseInfoBufferLength,
+ '7CcS');
+
+ if (senseInfoBuffer == NULL) {
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassSendSrbSynchronous: Can't allocate request sense "
+ "buffer\n"));
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+
+
+ //
+ // Enable auto request sense.
+ //
+ SrbSetSenseInfoBufferLength(Srb, SENSE_BUFFER_SIZE_EX);
+ SrbSetSenseInfoBuffer(Srb, senseInfoBuffer);
+
+ SrbSetDataBuffer(Srb, BufferAddress);
+
+
+ //
+ // Start retries here.
+ //
+
+retry:
+
+ //
+ // use fdoextension's flags by default.
+ // do not move out of loop, as the flag may change due to errors
+ // sending this command.
+ //
+
+ SrbAssignSrbFlags(Srb, fdoExtension->SrbFlags);
+
+ if (BufferAddress != NULL) {
+ if (WriteToDevice) {
+ SrbSetSrbFlags(Srb, SRB_FLAGS_DATA_OUT);
+ } else {
+ SrbSetSrbFlags(Srb, SRB_FLAGS_DATA_IN);
+ }
+ }
+
+
+ //
+ // Initialize the QueueAction field.
+ //
+
+ SrbSetRequestAttribute(Srb, SRB_SIMPLE_TAG_REQUEST);
+
+ //
+ // Disable synchronous transfer for these requests.
+ //
+ SrbSetSrbFlags(Srb, SRB_FLAGS_DISABLE_SYNCH_TRANSFER | SRB_FLAGS_NO_QUEUE_FREEZE);
+
+ //
+ // Set the event object to the unsignaled state.
+ // It will be used to signal request completion.
+ //
+
+ KeInitializeEvent(&event, NotificationEvent, FALSE);
+
+ //
+ // Build device I/O control request with METHOD_NEITHER data transfer.
+ // We'll queue a completion routine to cleanup the MDL's and such ourself.
+ //
+
+ irp = IoAllocateIrp(
+ (CCHAR) (fdoExtension->CommonExtension.LowerDeviceObject->StackSize + 1),
+ FALSE);
+
+ if (irp == NULL) {
+ FREE_POOL(senseInfoBuffer);
+ SrbSetSenseInfoBuffer(Srb, NULL);
+ SrbSetSenseInfoBufferLength(Srb, 0);
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassSendSrbSynchronous: Can't allocate Irp\n"));
+ return(STATUS_INSUFFICIENT_RESOURCES);
+ }
+
+ //
+ // Get next stack location.
+ //
+
+ irpStack = IoGetNextIrpStackLocation(irp);
+
+ //
+ // Set up SRB for execute scsi request. Save SRB address in next stack
+ // for the port driver.
+ //
+
+ irpStack->MajorFunction = IRP_MJ_SCSI;
+ irpStack->Parameters.Scsi.Srb = (PSCSI_REQUEST_BLOCK)Srb;
+
+ IoSetCompletionRoutine(irp,
+ ClasspSendSynchronousCompletion,
+ Srb,
+ TRUE,
+ TRUE,
+ TRUE);
+
+ irp->UserIosb = &ioStatus;
+ irp->UserEvent = &event;
+
+ if (BufferAddress) {
+ //
+ // Build an MDL for the data buffer and stick it into the irp. The
+ // completion routine will unlock the pages and free the MDL.
+ //
+
+ irp->MdlAddress = IoAllocateMdl( BufferAddress,
+ BufferLength,
+ FALSE,
+ FALSE,
+ irp );
+ if (irp->MdlAddress == NULL) {
+ FREE_POOL(senseInfoBuffer);
+ SrbSetSenseInfoBuffer(Srb, NULL);
+ SrbSetSenseInfoBufferLength(Srb, 0);
+ IoFreeIrp( irp );
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassSendSrbSynchronous: Can't allocate MDL\n"));
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ try {
+
+ //
+ // the io manager unlocks these pages upon completion
+ //
+
+ MmProbeAndLockPages( irp->MdlAddress,
+ KernelMode,
+ (WriteToDevice ? IoReadAccess :
+ IoWriteAccess));
+
+ #pragma warning(suppress: 6320) // We want to handle any exception that MmProbeAndLockPages might throw
+ } except(EXCEPTION_EXECUTE_HANDLER) {
+ status = GetExceptionCode();
+
+ FREE_POOL(senseInfoBuffer);
+ SrbSetSenseInfoBuffer(Srb, NULL);
+ SrbSetSenseInfoBufferLength(Srb, 0);
+ IoFreeMdl(irp->MdlAddress);
+ IoFreeIrp(irp);
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassSendSrbSynchronous: Exception %lx "
+ "locking buffer\n", status));
+ return status;
+ }
+ }
+
+ //
+ // Set the transfer length.
+ //
+
+ SrbSetDataTransferLength(Srb, BufferLength);
+
+ //
+ // Zero out status.
+ //
+
+ SrbSetScsiStatus(Srb, 0);
+ Srb->SrbStatus = 0;
+ SrbSetNextSrb(Srb, NULL);
+
+ //
+ // Set up IRP Address.
+ //
+
+ SrbSetOriginalRequest(Srb, irp);
+
+ //
+ // Call the port driver with the request and wait for it to complete.
+ //
+
+ status = IoCallDriver(fdoExtension->CommonExtension.LowerDeviceObject, irp);
+
+ if (status == STATUS_PENDING) {
+ (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
+ status = ioStatus.Status;
+ }
+
+// NT_ASSERT(SRB_STATUS(Srb->SrbStatus) != SRB_STATUS_PENDING);
+ NT_ASSERT(status != STATUS_PENDING);
+ NT_ASSERT(!(Srb->SrbStatus & SRB_STATUS_QUEUE_FROZEN));
+
+ //
+ // Clear the IRP address in SRB as IRP has been freed at this time
+ // and don't want to leave any references that may be accessed.
+ //
+
+ SrbSetOriginalRequest(Srb, NULL);
+
+ //
+ // Check that request completed without error.
+ //
+
+ if (SRB_STATUS(Srb->SrbStatus) != SRB_STATUS_SUCCESS) {
+
+ LONGLONG retryInterval;
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassSendSrbSynchronous - srb %ph failed (op=%s srbstat=%s(%xh), irpstat=%xh, sense=%s/%s/%s)", Srb,
+ DBGGETSCSIOPSTR(Srb),
+ DBGGETSRBSTATUSSTR(Srb), (ULONG)Srb->SrbStatus, status,
+ DBGGETSENSECODESTR(Srb),
+ DBGGETADSENSECODESTR(Srb),
+ DBGGETADSENSEQUALIFIERSTR(Srb)));
+
+ //
+ // assert that the queue is not frozen
+ //
+
+ NT_ASSERT(!TEST_FLAG(Srb->SrbStatus, SRB_STATUS_QUEUE_FROZEN));
+
+ //
+ // Update status and determine if request should be retried.
+ //
+
+ retry = InterpretSenseInfoWithoutHistory(Fdo,
+ NULL, // no valid irp exists
+ (PSCSI_REQUEST_BLOCK)Srb,
+ IRP_MJ_SCSI,
+ 0,
+ MAXIMUM_RETRIES - retryCount,
+ &status,
+ &retryInterval);
+
+ if (retry) {
+
+ BOOLEAN validSense = FALSE;
+ UCHAR additionalSenseCode = 0;
+
+ if (status == STATUS_DEVICE_NOT_READY) {
+
+ validSense = ScsiGetSenseKeyAndCodes(senseInfoBuffer,
+ SrbGetSenseInfoBufferLength(Srb),
+ SCSI_SENSE_OPTIONS_FIXED_FORMAT_IF_UNKNOWN_FORMAT_INDICATED,
+ NULL,
+ &additionalSenseCode,
+ NULL);
+ }
+
+ if ((validSense && additionalSenseCode == SCSI_ADSENSE_LUN_NOT_READY) ||
+ (SRB_STATUS(Srb->SrbStatus) == SRB_STATUS_SELECTION_TIMEOUT)) {
+
+ LARGE_INTEGER delay;
+
+ //
+ // Delay for at least 2 seconds.
+ //
+
+ if (retryInterval < 2*1000*1000*10) {
+ retryInterval = 2*1000*1000*10;
+ }
+
+ delay.QuadPart = -retryInterval;
+
+ //
+ // Stall for a while to let the device become ready
+ //
+
+ KeDelayExecutionThread(KernelMode, FALSE, &delay);
+
+ }
+
+ //
+ // If retries are not exhausted then retry this operation.
+ //
+
+ if (retryCount--) {
+
+ if (PORT_ALLOCATED_SENSE_EX(fdoExtension, Srb)) {
+ FREE_PORT_ALLOCATED_SENSE_BUFFER_EX(fdoExtension, Srb);
+ }
+
+ goto retry;
+ }
+ }
+
+
+ } else {
+ fdoData->LoggedTURFailureSinceLastIO = FALSE;
+ status = STATUS_SUCCESS;
+ }
+
+ //
+ // required even though we allocated our own, since the port driver may
+ // have allocated one also
+ //
+
+ if (PORT_ALLOCATED_SENSE_EX(fdoExtension, Srb)) {
+ FREE_PORT_ALLOCATED_SENSE_BUFFER_EX(fdoExtension, Srb);
+ }
+
+ FREE_POOL(senseInfoBuffer);
+ SrbSetSenseInfoBuffer(Srb, NULL);
+ SrbSetSenseInfoBufferLength(Srb, 0);
+
+ return status;
+}
+
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassInterpretSenseInfo()
+
+Routine Description:
+
+ This routine interprets the data returned from the SCSI
+ request sense. It determines the status to return in the
+ IRP and whether this request can be retried.
+
+Arguments:
+
+ Fdo - Supplies the device object associated with this request.
+
+ Srb - Supplies the scsi request block which failed.
+
+ MajorFunctionCode - Supplies the function code to be used for logging.
+
+ IoDeviceCode - Supplies the device code to be used for logging.
+
+ RetryCount - Number of times that the request has been retried.
+
+ Status - Returns the status for the request.
+
+ RetryInterval - Number of seconds before the request should be retried.
+ Zero indicates the request should be immediately retried.
+
+Return Value:
+
+ BOOLEAN TRUE: Drivers should retry this request.
+ FALSE: Drivers should not retry this request.
+
+--*/
+BOOLEAN
+ClassInterpretSenseInfo(
+ _In_ PDEVICE_OBJECT Fdo,
+ _In_ PSCSI_REQUEST_BLOCK _Srb,
+ _In_ UCHAR MajorFunctionCode,
+ _In_ ULONG IoDeviceCode,
+ _In_ ULONG RetryCount,
+ _Out_ NTSTATUS *Status,
+ _Out_opt_ _Deref_out_range_(0,100) ULONG *RetryInterval
+ )
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
+ PSTORAGE_REQUEST_BLOCK_HEADER Srb = (PSTORAGE_REQUEST_BLOCK_HEADER)_Srb;
+ PVOID senseBuffer = SrbGetSenseInfoBuffer(Srb);
+ BOOLEAN retry = TRUE;
+ BOOLEAN logError = FALSE;
+ BOOLEAN unhandledError = FALSE;
+ BOOLEAN incrementErrorCount = FALSE;
+
+ //
+ // NOTE: This flag must be used only for read/write requests that
+ // fail with a unexpected retryable error.
+ //
+ BOOLEAN logRetryableError = TRUE;
+
+ //
+ // Indicates if we should log this error in our internal log.
+ //
+ BOOLEAN logErrorInternal = TRUE;
+
+ ULONGLONG badSector = 0;
+ ULONG uniqueId = 0;
+
+ NTSTATUS logStatus;
+
+ ULONGLONG readSector;
+ ULONG index;
+
+ ULONG retryInterval = 0;
+ KIRQL oldIrql;
+ PCDB cdb = SrbGetCdb(Srb);
+ UCHAR cdbOpcode = 0;
+ ULONG cdbLength = SrbGetCdbLength(Srb);
+
+#if DBG
+ BOOLEAN isReservationConflict = FALSE;
+#endif
+
+ if (cdb) {
+ cdbOpcode = cdb->CDB6GENERIC.OperationCode;
+ }
+
+ *Status = STATUS_IO_DEVICE_ERROR;
+ logStatus = -1;
+
+ if (TEST_FLAG(SrbGetSrbFlags(Srb), SRB_CLASS_FLAGS_PAGING)) {
+
+ //
+ // Log anything remotely incorrect about paging i/o
+ //
+
+ logError = TRUE;
+ uniqueId = 301;
+ logStatus = IO_WARNING_PAGING_FAILURE;
+ }
+
+ //
+ // Check that request sense buffer is valid.
+ //
+
+ NT_ASSERT(fdoExtension->CommonExtension.IsFdo);
+
+
+ //
+ // must handle the SRB_STATUS_INTERNAL_ERROR case first,
+ // as it has all the flags set.
+ //
+
+ if (SRB_STATUS(Srb->SrbStatus) == SRB_STATUS_INTERNAL_ERROR) {
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
+ "ClassInterpretSenseInfo: Internal Error code is %x\n",
+ SrbGetSystemStatus(Srb)));
+
+ retry = FALSE;
+ *Status = SrbGetSystemStatus(Srb);
+
+ } else if (SrbGetScsiStatus(Srb) == SCSISTAT_RESERVATION_CONFLICT) {
+
+ //
+ // Need to reserve STATUS_DEVICE_BUSY to convey reservation conflict
+ // for read/write requests as there are upper level components that
+ // have built-in assumptions that STATUS_DEVICE_BUSY implies reservation
+ // conflict.
+ //
+ *Status = STATUS_DEVICE_BUSY;
+ retry = FALSE;
+ logError = FALSE;
+#if DBG
+ isReservationConflict = TRUE;
+#endif
+
+ } else {
+
+ BOOLEAN validSense = FALSE;
+
+ if ((Srb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID) && senseBuffer) {
+
+ UCHAR errorCode = 0;
+ UCHAR senseKey = 0;
+ UCHAR addlSenseCode = 0;
+ UCHAR addlSenseCodeQual = 0;
+ BOOLEAN isIncorrectLengthValid = FALSE;
+ BOOLEAN incorrectLength = FALSE;
+ BOOLEAN isInformationValid = FALSE;
+ ULONGLONG information = 0;
+
+
+ validSense = ScsiGetSenseKeyAndCodes(senseBuffer,
+ SrbGetSenseInfoBufferLength(Srb),
+ SCSI_SENSE_OPTIONS_NONE,
+ &senseKey,
+ &addlSenseCode,
+ &addlSenseCodeQual);
+
+ if (!validSense && !IsSenseDataFormatValueValid(senseBuffer)) {
+
+ NT_ASSERT(FALSE);
+
+ validSense = ScsiGetFixedSenseKeyAndCodes(senseBuffer,
+ SrbGetSenseInfoBufferLength(Srb),
+ &senseKey,
+ &addlSenseCode,
+ &addlSenseCodeQual);
+ }
+
+ if (!validSense) {
+ goto __ClassInterpretSenseInfo_ProcessingInvalidSenseBuffer;
+ }
+
+ errorCode = ScsiGetSenseErrorCode(senseBuffer);
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: Error code is %x\n", errorCode));
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: Sense key is %x\n", senseKey));
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: Additional sense code is %x\n", addlSenseCode));
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: Additional sense code qualifier is %x\n", addlSenseCodeQual));
+
+ if (IsDescriptorSenseDataFormat(senseBuffer)) {
+
+ //
+ // Sense data in Descriptor format
+ //
+
+ PVOID startBuffer = NULL;
+ UCHAR startBufferLength = 0;
+
+
+ if (ScsiGetSenseDescriptor(senseBuffer,
+ SrbGetSenseInfoBufferLength(Srb),
+ &startBuffer,
+ &startBufferLength)) {
+ UCHAR outType;
+ PVOID outBuffer = NULL;
+ UCHAR outBufferLength = 0;
+ BOOLEAN foundBlockCommandType = FALSE;
+ BOOLEAN foundInformationType = FALSE;
+ UCHAR descriptorLength = 0;
+
+ UCHAR typeList[2] = {SCSI_SENSE_DESCRIPTOR_TYPE_INFORMATION,
+ SCSI_SENSE_DESCRIPTOR_TYPE_BLOCK_COMMAND};
+
+ while ((!foundBlockCommandType || !foundInformationType) &&
+ ScsiGetNextSenseDescriptorByType(startBuffer,
+ startBufferLength,
+ typeList,
+ ARRAYSIZE(typeList),
+ &outType,
+ &outBuffer,
+ &outBufferLength)) {
+
+ descriptorLength = ScsiGetSenseDescriptorLength(outBuffer);
+
+ if (outBufferLength < descriptorLength) {
+
+ // Descriptor data is truncated.
+ // Complete searching descriptors. Exit the loop now.
+ break;
+ }
+
+ if (outType == SCSI_SENSE_DESCRIPTOR_TYPE_BLOCK_COMMAND) {
+
+ //
+ // Block Command type
+ //
+
+ if (!foundBlockCommandType) {
+
+ foundBlockCommandType = TRUE;
+
+ if (ScsiValidateBlockCommandSenseDescriptor(outBuffer, outBufferLength)) {
+ incorrectLength = ((PSCSI_SENSE_DESCRIPTOR_BLOCK_COMMAND)outBuffer)->IncorrectLength;
+ isIncorrectLengthValid = TRUE;
+ }
+ } else {
+
+ //
+ // A Block Command descriptor is already found earlier.
+ //
+ // T10 SPC specification only allows one descriptor for Block Command Descriptor type.
+ // Assert here to catch devices that violate this rule. Ignore this descriptor.
+ //
+ NT_ASSERT(FALSE);
+ }
+
+ } else if (outType == SCSI_SENSE_DESCRIPTOR_TYPE_INFORMATION) {
+
+ //
+ // Information type
+ //
+
+ if (!foundInformationType) {
+
+ foundInformationType = TRUE;
+
+ if (ScsiValidateInformationSenseDescriptor(outBuffer, outBufferLength)) {
+ REVERSE_BYTES_QUAD(&information, &(((PSCSI_SENSE_DESCRIPTOR_INFORMATION)outBuffer)->Information));
+ isInformationValid = TRUE;
+ }
+ } else {
+
+ //
+ // A Information descriptor is already found earlier.
+ //
+ // T10 SPC specification only allows one descriptor for Information Descriptor type.
+ // Assert here to catch devices that violate this rule. Ignore this descriptor.
+ //
+ NT_ASSERT(FALSE);
+ }
+
+ } else {
+
+ //
+ // ScsiGetNextDescriptorByType should only return a type that is specified by us.
+ //
+ NT_ASSERT(FALSE);
+ break;
+ }
+
+ //
+ // Advance to start address of next descriptor
+ //
+ startBuffer = (PUCHAR)outBuffer + descriptorLength;
+ startBufferLength = outBufferLength - descriptorLength;
+ }
+ }
+ } else {
+
+ //
+ // Sense data in Fixed format
+ //
+
+ incorrectLength = ((PFIXED_SENSE_DATA)(senseBuffer))->IncorrectLength;
+ REVERSE_BYTES(&information, &(((PFIXED_SENSE_DATA)senseBuffer)->Information));
+ isInformationValid = TRUE;
+ isIncorrectLengthValid = TRUE;
+ }
+
+
+ switch (senseKey) {
+
+ case SCSI_SENSE_NO_SENSE: {
+
+ //
+ // Check other indicators.
+ //
+
+ if (isIncorrectLengthValid && incorrectLength) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Incorrect length detected.\n"));
+ *Status = STATUS_INVALID_BLOCK_LENGTH ;
+ retry = FALSE;
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "No specific sense key\n"));
+ *Status = STATUS_IO_DEVICE_ERROR;
+ retry = TRUE;
+ }
+
+ break;
+ } // end SCSI_SENSE_NO_SENSE
+
+ case SCSI_SENSE_RECOVERED_ERROR: {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Recovered error\n"));
+ *Status = STATUS_SUCCESS;
+ retry = FALSE;
+ logError = TRUE;
+ uniqueId = 258;
+
+ switch(addlSenseCode) {
+ case SCSI_ADSENSE_TRACK_ERROR:
+ case SCSI_ADSENSE_SEEK_ERROR: {
+ logStatus = IO_ERR_SEEK_ERROR;
+ break;
+ }
+
+ case SCSI_ADSENSE_REC_DATA_NOECC:
+ case SCSI_ADSENSE_REC_DATA_ECC: {
+ logStatus = IO_RECOVERED_VIA_ECC;
+ break;
+ }
+
+ case SCSI_ADSENSE_FAILURE_PREDICTION_THRESHOLD_EXCEEDED: {
+
+ UCHAR wmiEventData[sizeof(ULONG)+sizeof(UCHAR)] = {0};
+
+ *((PULONG)wmiEventData) = sizeof(UCHAR);
+ wmiEventData[sizeof(ULONG)] = addlSenseCodeQual;
+
+ //
+ // Don't log another eventlog if we have already logged once
+ // NOTE: this should have been interlocked, but the structure
+ // was publicly defined to use a BOOLEAN (char). Since
+ // media only reports these errors once per X minutes,
+ // the potential race condition is nearly non-existant.
+ // the worst case is duplicate log entries, so ignore.
+ //
+
+ logError = FALSE;
+ if (fdoExtension->FailurePredicted == 0) {
+ logError = TRUE;
+ }
+ fdoExtension->FailureReason = addlSenseCodeQual;
+ logStatus = IO_WRN_FAILURE_PREDICTED;
+
+ ClassNotifyFailurePredicted(fdoExtension,
+ (PUCHAR)wmiEventData,
+ sizeof(wmiEventData),
+ FALSE, // do not log error
+ 4, // unique error value
+ SrbGetPathId(Srb),
+ SrbGetTargetId(Srb),
+ SrbGetLun(Srb));
+
+ fdoExtension->FailurePredicted = TRUE;
+ break;
+ }
+
+ default: {
+ logStatus = IO_ERR_CONTROLLER_ERROR;
+ break;
+ }
+
+ } // end switch(addlSenseCode)
+
+ if (isIncorrectLengthValid && incorrectLength) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Incorrect length detected.\n"));
+ *Status = STATUS_INVALID_BLOCK_LENGTH ;
+ }
+
+
+ break;
+ } // end SCSI_SENSE_RECOVERED_ERROR
+
+ case SCSI_SENSE_NOT_READY: {
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Device not ready\n"));
+ *Status = STATUS_DEVICE_NOT_READY;
+
+ switch (addlSenseCode) {
+
+ case SCSI_ADSENSE_LUN_NOT_READY: {
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Lun not ready\n"));
+
+ retryInterval = NOT_READY_RETRY_INTERVAL;
+
+ switch (addlSenseCodeQual) {
+
+ case SCSI_SENSEQ_BECOMING_READY: {
+ DEVICE_EVENT_BECOMING_READY notReady = {0};
+
+ logRetryableError = FALSE;
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "In process of becoming ready\n"));
+
+ notReady.Version = 1;
+ notReady.Reason = 1;
+ notReady.Estimated100msToReady = retryInterval * 10;
+ ClassSendNotification(fdoExtension,
+ &GUID_IO_DEVICE_BECOMING_READY,
+ sizeof(DEVICE_EVENT_BECOMING_READY),
+ &notReady);
+ break;
+ }
+
+ case SCSI_SENSEQ_MANUAL_INTERVENTION_REQUIRED: {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Manual intervention required\n"));
+ *Status = STATUS_NO_MEDIA_IN_DEVICE;
+ retry = FALSE;
+ break;
+ }
+
+ case SCSI_SENSEQ_FORMAT_IN_PROGRESS: {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Format in progress\n"));
+ retry = FALSE;
+ break;
+ }
+
+ case SCSI_SENSEQ_OPERATION_IN_PROGRESS: {
+ DEVICE_EVENT_BECOMING_READY notReady = {0};
+
+ logRetryableError = FALSE;
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Operation In Progress\n"));
+
+ notReady.Version = 1;
+ notReady.Reason = 2;
+ notReady.Estimated100msToReady = retryInterval * 10;
+ ClassSendNotification(fdoExtension,
+ &GUID_IO_DEVICE_BECOMING_READY,
+ sizeof(DEVICE_EVENT_BECOMING_READY),
+ &notReady);
+
+ break;
+ }
+
+ case SCSI_SENSEQ_LONG_WRITE_IN_PROGRESS: {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Long write in progress\n"));
+ //
+ // This has been seen as a transcient failure on some cdrom
+ // drives. The cdrom class driver is going to override this
+ // setting but has no way of dropping the retry interval
+ //
+ retry = FALSE;
+ retryInterval = 1;
+ break;
+ }
+
+ case SCSI_SENSEQ_SPACE_ALLOC_IN_PROGRESS: {
+ logRetryableError = FALSE;
+ TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "The device (%p) is busy allocating space.\n",
+ Fdo));
+
+ //
+ // This indicates that a thinly-provisioned device has hit
+ // a temporary resource exhaustion and is busy allocating
+ // more space. We need to retry the request as the device
+ // will eventually be able to service it.
+ //
+ *Status = STATUS_RETRY;
+ retry = TRUE;
+
+ break;
+ }
+
+ case SCSI_SENSEQ_CAUSE_NOT_REPORTABLE: {
+
+ if (!TEST_FLAG(fdoExtension->ScanForSpecialFlags,
+ CLASS_SPECIAL_CAUSE_NOT_REPORTABLE_HACK)) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,
+ "ClassInterpretSenseInfo: "
+ "not ready, cause unknown\n"));
+ /*
+ Many non-WHQL certified drives (mostly CD-RW) return
+ this when they have no media instead of the obvious
+ choice of:
+
+ SCSI_SENSE_NOT_READY/SCSI_ADSENSE_NO_MEDIA_IN_DEVICE
+
+ These drives should not pass WHQL certification due
+ to this discrepency.
+
+ */
+ retry = FALSE;
+ break;
+
+ } else {
+
+ //
+ // Treat this as init command required and fall through.
+ //
+ }
+ }
+
+ case SCSI_SENSEQ_INIT_COMMAND_REQUIRED:
+ default: {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Initializing command required\n"));
+ retryInterval = 0; // go back to default
+ logRetryableError = FALSE;
+
+ //
+ // This sense code/additional sense code
+ // combination may indicate that the device
+ // needs to be started. Send an start unit if this
+ // is a disk device.
+ //
+ if (TEST_FLAG(fdoExtension->DeviceFlags, DEV_SAFE_START_UNIT) &&
+ !TEST_FLAG(SrbGetSrbFlags(Srb), SRB_CLASS_FLAGS_LOW_PRIORITY)){
+
+ ClassSendStartUnit(Fdo);
+ }
+ break;
+ }
+
+ } // end switch (addlSenseCodeQual)
+ break;
+ }
+
+ case SCSI_ADSENSE_NO_MEDIA_IN_DEVICE: {
+ TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "No Media in device.\n"));
+ *Status = STATUS_NO_MEDIA_IN_DEVICE;
+ retry = FALSE;
+
+ //
+ // signal MCN that there isn't any media in the device
+ //
+ if (!TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA)) {
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "No Media in a non-removable device %p\n",
+ Fdo));
+ }
+
+ if (addlSenseCodeQual == 0xCC){
+ /*
+ * The IMAPI filter returns this ASCQ value when it is burning CD-R media.
+ * We want to indicate that the media is not present to most applications;
+ * but RSM has to know that the media is still in the drive (i.e. the drive is not free).
+ */
+ ClassSetMediaChangeState(fdoExtension, MediaUnavailable, FALSE);
+ }
+ else {
+ ClassSetMediaChangeState(fdoExtension, MediaNotPresent, FALSE);
+ }
+
+ break;
+ }
+ } // end switch (addlSenseCode)
+
+ break;
+ } // end SCSI_SENSE_NOT_READY
+
+ case SCSI_SENSE_MEDIUM_ERROR: {
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Medium Error (bad block)\n"));
+ *Status = STATUS_DEVICE_DATA_ERROR;
+
+ retry = FALSE;
+ logError = TRUE;
+ uniqueId = 256;
+ logStatus = IO_ERR_BAD_BLOCK;
+
+ //
+ // Check if this error is due to unknown format
+ //
+ if (addlSenseCode == SCSI_ADSENSE_INVALID_MEDIA) {
+
+ switch (addlSenseCodeQual) {
+
+ case SCSI_SENSEQ_UNKNOWN_FORMAT: {
+
+ *Status = STATUS_UNRECOGNIZED_MEDIA;
+
+ //
+ // Log error only if this is a paging request
+ //
+ if (!TEST_FLAG(SrbGetSrbFlags(Srb), SRB_CLASS_FLAGS_PAGING)) {
+ logError = FALSE;
+ }
+ break;
+ }
+
+ case SCSI_SENSEQ_CLEANING_CARTRIDGE_INSTALLED: {
+
+ *Status = STATUS_CLEANER_CARTRIDGE_INSTALLED;
+ logError = FALSE;
+ break;
+
+ }
+ default: {
+ break;
+ }
+ } // end switch addlSenseCodeQual
+
+ } // end SCSI_ADSENSE_INVALID_MEDIA
+
+ break;
+
+ } // end SCSI_SENSE_MEDIUM_ERROR
+
+ case SCSI_SENSE_HARDWARE_ERROR: {
+ BOOLEAN logHardwareError = TRUE;
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Hardware error\n"));
+
+ if (fdoData->LegacyErrorHandling == FALSE) {
+ //
+ // Hardware errors indicate something has seriously gone
+ // wrong with the device and retries are very unlikely to
+ // succeed so fail this request back immediately.
+ //
+ retry = FALSE;
+ *Status = STATUS_DEVICE_HARDWARE_ERROR;
+ logError = FALSE;
+
+ } else {
+ //
+ // Revert to legacy behavior. That is, retry everything by default.
+ //
+ retry = TRUE;
+ *Status = STATUS_IO_DEVICE_ERROR;
+ logError = TRUE;
+ uniqueId = 257;
+ logStatus = IO_ERR_CONTROLLER_ERROR;
+ logHardwareError = FALSE;
+
+ //
+ // This indicates the possibility of a dropped FC packet.
+ //
+ if ((addlSenseCode == SCSI_ADSENSE_LOGICAL_UNIT_ERROR && addlSenseCodeQual == SCSI_SENSEQ_TIMEOUT_ON_LOGICAL_UNIT) ||
+ (addlSenseCode == SCSI_ADSENSE_DATA_TRANSFER_ERROR && addlSenseCodeQual == SCSI_SENSEQ_INITIATOR_RESPONSE_TIMEOUT)) {
+ //
+ // Fail requests that report this error back to the application.
+ //
+ retry = FALSE;
+
+ //
+ // Log a more descriptive error and avoid a second
+ // error message (IO_ERR_CONTROLLER_ERROR) being logged.
+ //
+ logHardwareError = TRUE;
+ logError = FALSE;
+ }
+ }
+
+ //
+ // If CRC error was returned, retry after a slight delay.
+ //
+ if (addlSenseCode == SCSI_ADSENSE_LUN_COMMUNICATION &&
+ addlSenseCodeQual == SCSI_SESNEQ_COMM_CRC_ERROR) {
+ retry = TRUE;
+ retryInterval = 1;
+ logHardwareError = FALSE;
+ logError = FALSE;
+ }
+
+ //
+ // Hardware errors warrant a more descriptive error.
+ // Specifically, we need to ensure this disk is easily
+ // identifiable.
+ //
+ if (logHardwareError) {
+ UCHAR senseInfoBufferLength = SrbGetSenseInfoBufferLength(Srb);
+ UCHAR senseBufferSize = 0;
+
+ if (ScsiGetTotalSenseByteCountIndicated(senseBuffer,
+ senseInfoBufferLength,
+ &senseBufferSize)) {
+
+ senseBufferSize = min(senseBufferSize, senseInfoBufferLength);
+
+ } else {
+ //
+ // it's smaller than required to read the total number of
+ // valid bytes, so just use the SenseInfoBufferLength field.
+ //
+ senseBufferSize = senseInfoBufferLength;
+ }
+
+ ClasspQueueLogIOEventWithContextWorker(Fdo,
+ senseBufferSize,
+ senseBuffer,
+ SRB_STATUS(Srb->SrbStatus),
+ SrbGetScsiStatus(Srb),
+ (ULONG)IO_ERROR_IO_HARDWARE_ERROR,
+ cdbLength,
+ cdb,
+ NULL);
+ }
+
+ break;
+ } // end SCSI_SENSE_HARDWARE_ERROR
+
+ case SCSI_SENSE_ILLEGAL_REQUEST: {
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Illegal SCSI request\n"));
+ *Status = STATUS_INVALID_DEVICE_REQUEST;
+ retry = FALSE;
+
+ switch (addlSenseCode) {
+
+ case SCSI_ADSENSE_NO_SENSE: {
+
+ switch (addlSenseCodeQual) {
+
+ //
+ // 1. Duplicate List Identifier
+ //
+ case SCSI_SENSEQ_OPERATION_IS_IN_PROGRESS: {
+
+ //
+ // XCOPY, READ BUFFER and CHANGE ALIASES return this sense combination under
+ // certain conditions. Since these commands aren't sent down natively by the
+ // Windows OS, return the default error for them and only handle this sense
+ // combination for offload data transfer commands.
+ //
+ if (ClasspIsOffloadDataTransferCommand(cdb)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClassInterpretSenseInfo (%p): Duplicate List Identifier (command %x, parameter field offset 0x%016llx)\n",
+ Fdo,
+ cdbOpcode,
+ information));
+
+ NT_ASSERTMSG("Duplicate list identifier specified", FALSE);
+
+ //
+ // The host should ensure that it uses unique list id for each TokenOperation request.
+ //
+ *Status = STATUS_OPERATION_IN_PROGRESS;
+ }
+ break;
+ }
+ }
+ break;
+ }
+
+ case SCSI_ADSENSE_LUN_COMMUNICATION: {
+
+ switch (addlSenseCodeQual) {
+
+ //
+ // 1. Source/Destination pairing can't communicate with each other or the copy manager.
+ //
+ case SCSI_SENSEQ_UNREACHABLE_TARGET: {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClassInterpretSenseInfo (%p): Source-Destination LUNs can't communicate (command %x)\n",
+ Fdo,
+ cdbOpcode));
+
+ *Status = STATUS_DEVICE_UNREACHABLE;
+ break;
+ }
+ }
+ break;
+ }
+
+ case SCSI_ADSENSE_COPY_TARGET_DEVICE_ERROR: {
+
+ switch (addlSenseCodeQual) {
+
+ //
+ // 1. Sum of logical block fields in all block device range descriptors is greater than number
+ // of logical blocks in the ROD minus block offset into ROD
+ //
+ case SCSI_SENSEQ_DATA_UNDERRUN: {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClassInterpretSenseInfo (%p): Host specified a transfer length greater than what is represented by the token (considering the offset) [command %x]\n",
+ Fdo,
+ cdbOpcode));
+
+ NT_ASSERTMSG("Host specified blocks to write beyond what is represented by the token", FALSE);
+
+ *Status = STATUS_DATA_OVERRUN;
+ break;
+ }
+ }
+ break;
+ }
+
+ //
+ // 1. Parameter data truncation (e.g. last descriptor was not fully specified)
+ //
+ case SCSI_ADSENSE_PARAMETER_LIST_LENGTH: {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClassInterpretSenseInfo (%p): Target truncated the block device range descriptors in the parameter list (command %x)\n",
+ Fdo,
+ cdbOpcode));
+
+ NT_ASSERTMSG("Parameter data truncation", FALSE);
+
+ *Status = STATUS_DATA_OVERRUN;
+ break;
+ }
+
+ case SCSI_ADSENSE_ILLEGAL_COMMAND: {
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Illegal command\n"));
+ break;
+ }
+
+ case SCSI_ADSENSE_ILLEGAL_BLOCK: {
+
+ LARGE_INTEGER logicalBlockAddr;
+ LARGE_INTEGER lastLBA;
+ ULONG numTransferBlocks = 0;
+
+ logicalBlockAddr.QuadPart = 0;
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: Illegal block address\n"));
+
+ *Status = STATUS_NONEXISTENT_SECTOR;
+
+ if (Fdo->DeviceType == FILE_DEVICE_DISK) {
+
+ if (IS_SCSIOP_READWRITE(cdbOpcode) && cdb) {
+
+ if (TEST_FLAG(fdoExtension->DeviceFlags, DEV_USE_16BYTE_CDB)) {
+ REVERSE_BYTES_QUAD(&logicalBlockAddr, &cdb->CDB16.LogicalBlock);
+ REVERSE_BYTES(&numTransferBlocks, &cdb->CDB16.TransferLength);
+ } else {
+ REVERSE_BYTES(&logicalBlockAddr.LowPart, &cdb->CDB10.LogicalBlockByte0);
+ REVERSE_BYTES_SHORT((PUSHORT)&numTransferBlocks, &cdb->CDB10.TransferBlocksMsb);
+ }
+
+ REVERSE_BYTES_QUAD(&lastLBA, &fdoData->LastKnownDriveCapacityData.LogicalBlockAddress);
+
+ if ((logicalBlockAddr.QuadPart > lastLBA.QuadPart) ||
+ ((logicalBlockAddr.QuadPart + numTransferBlocks - 1) > lastLBA.QuadPart)) {
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Request beyond boundary. Last LBA: 0x%I64X Read LBA: 0x%I64X Length: 0x%X\n",
+ (__int64) lastLBA.QuadPart, (__int64) logicalBlockAddr.QuadPart, numTransferBlocks));
+ } else {
+ //
+ // Should only retry these if the request was
+ // truly within our expected size.
+ //
+ // Fujitsu IDE drives have been observed to
+ // return this error transiently for a legal LBA;
+ // manual retry in the debugger then works, so
+ // there is a good chance that a programmed retry
+ // will also work.
+ //
+
+ retry = TRUE;
+ retryInterval = 5;
+ }
+ } else if (ClasspIsOffloadDataTransferCommand(cdb)) {
+
+ //
+ // 1. Number of logical blocks of block device range descriptor exceeds capacity of the medium
+ //
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClassInterpretSenseInfo (%p): LBA out of range (command %x, parameter field offset 0x%016llx)\n",
+ Fdo,
+ cdbOpcode,
+ information));
+
+ NT_ASSERTMSG("Number of blocks specified exceeds LUN capacity", FALSE);
+ }
+ }
+ break;
+ }
+
+ //
+ // 1. Generic error - cause not reportable
+ // 2. Insufficient resources to create ROD
+ // 3. Insufficient resources to create Token
+ // 4. Max number of tokens exceeded
+ // 5. Remote Token creation not supported
+ // 6. Token expired
+ // 7. Token unknown
+ // 8. Unsupported Token type
+ // 9. Token corrupt
+ // 10. Token revoked
+ // 11. Token cancelled
+ // 12. Remote Token usage not supported
+ //
+ case SCSI_ADSENSE_INVALID_TOKEN: {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClassInterpretSenseInfo (%p): Invalid/Expired/Modified token specified (command %x, parameter field offset 0x%016llx)\n",
+ Fdo,
+ cdbOpcode,
+ information));
+
+ *Status = STATUS_INVALID_TOKEN;
+ break;
+ }
+
+ case SCSI_ADSENSE_INVALID_CDB: {
+ if (ClasspIsOffloadDataTransferCommand(cdb)) {
+
+ //
+ // 1. Mismatched I_T nexus and list identifier
+ //
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClassInterpretSenseInfo (%p): Incorrect I_T nexus likely used (command %x)\n",
+ Fdo,
+ cdbOpcode));
+
+ //
+ // The host should ensure that it sends TokenOperation and ReceiveTokenInformation for the same
+ // list Id using the same I_T nexus.
+ //
+ *Status = STATUS_INVALID_INITIATOR_TARGET_PATH;
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Invalid CDB\n"));
+
+ //
+ // Note: the retry interval is not typically used.
+ // it is set here only because a ClassErrorHandler
+ // cannot set the retryInterval, and the error may
+ // require a few commands to be sent to clear whatever
+ // caused this condition (i.e. disk clears the write
+ // cache, requiring at least two commands)
+ //
+ // hopefully, this shortcoming can be changed for
+ // blackcomb.
+ //
+
+ retryInterval = 3;
+ }
+ break;
+ }
+
+ case SCSI_ADSENSE_INVALID_LUN: {
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Invalid LUN\n"));
+ *Status = STATUS_NO_SUCH_DEVICE;
+ break;
+ }
+
+ case SCSI_ADSENSE_INVALID_FIELD_PARAMETER_LIST: {
+
+ switch (addlSenseCodeQual) {
+
+ //
+ // 1. Alignment violation (e.g. copy manager is unable to copy because destination offset is NOT aligned to LUN's granularity/alignment)
+ //
+ case SCSI_SENSEQ_INVALID_RELEASE_OF_PERSISTENT_RESERVATION: {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClassInterpretSenseInfo (%p): Alignment violation for command %x.\n",
+ Fdo,
+ cdbOpcode));
+
+ NT_ASSERTMSG("Specified offset is not aligned to LUN's granularity", FALSE);
+
+ *Status = STATUS_INVALID_OFFSET_ALIGNMENT;
+ break;
+ }
+
+ //
+ // 1. Number of block device range descriptors is greater than maximum range descriptors
+ //
+ case SCSI_SENSEQ_TOO_MANY_SEGMENT_DESCRIPTORS: {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClassInterpretSenseInfo (%p): Too many descriptors in parameter list for command %x (parameter field offset 0x%016llx)\n",
+ Fdo,
+ cdbOpcode,
+ information));
+
+ NT_ASSERTMSG("Too many descriptors specified", FALSE);
+
+ *Status = STATUS_TOO_MANY_SEGMENT_DESCRIPTORS;
+ break;
+ }
+
+ default: {
+
+ if (ClasspIsOffloadDataTransferCommand(cdb)) {
+
+ //
+ // 1. (Various) Invalid parameter length
+ // 2. Requested inactivity timeout is greater than maximum inactivity timeout
+ // 3. Same LBA is included in more than one block device range descriptor (overlapping LBAs)
+ // 4. Total number of logical blocks of all block range descriptors is greater than the maximum transfer size
+ // 5. Total number of logical blocks of all block range descriptors is greater than maximum token transfer size
+ // (e.g. WriteUsingToken descriptors specify a cumulative total block count that exceeds the PopulateToken that created the token)
+ // 6. Block offset into ROD specified an offset that is greater than or equal to the number of logical blocks in the ROD
+ // 7. Number of logical blocks in a block device range descriptor is greater than maximum transfer length in blocks
+ //
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClassInterpretSenseInfo (%p): Illegal field in parameter list for command %x (parameter field offset 0x%016llx) [AddSense %x, AddSenseQ %x]\n",
+ Fdo,
+ cdbOpcode,
+ information,
+ addlSenseCode,
+ addlSenseCodeQual));
+
+ NT_ASSERTMSG("Invalid field in parameter list", FALSE);
+
+ *Status = STATUS_INVALID_FIELD_IN_PARAMETER_LIST;
+ }
+
+ break;
+ }
+ }
+ break;
+ }
+
+ case SCSI_ADSENSE_COPY_PROTECTION_FAILURE: {
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Copy protection failure\n"));
+
+ *Status = STATUS_COPY_PROTECTION_FAILURE;
+
+ switch (addlSenseCodeQual) {
+ case SCSI_SENSEQ_AUTHENTICATION_FAILURE:
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
+ "ClassInterpretSenseInfo: "
+ "Authentication failure\n"));
+ *Status = STATUS_CSS_AUTHENTICATION_FAILURE;
+ break;
+ case SCSI_SENSEQ_KEY_NOT_PRESENT:
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
+ "ClassInterpretSenseInfo: "
+ "Key not present\n"));
+ *Status = STATUS_CSS_KEY_NOT_PRESENT;
+ break;
+ case SCSI_SENSEQ_KEY_NOT_ESTABLISHED:
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
+ "ClassInterpretSenseInfo: "
+ "Key not established\n"));
+ *Status = STATUS_CSS_KEY_NOT_ESTABLISHED;
+ break;
+ case SCSI_SENSEQ_READ_OF_SCRAMBLED_SECTOR_WITHOUT_AUTHENTICATION:
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
+ "ClassInterpretSenseInfo: "
+ "Read of scrambled sector w/o "
+ "authentication\n"));
+ *Status = STATUS_CSS_SCRAMBLED_SECTOR;
+ break;
+ case SCSI_SENSEQ_MEDIA_CODE_MISMATCHED_TO_LOGICAL_UNIT:
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
+ "ClassInterpretSenseInfo: "
+ "Media region does not logical unit "
+ "region\n"));
+ *Status = STATUS_CSS_REGION_MISMATCH;
+ break;
+ case SCSI_SENSEQ_LOGICAL_UNIT_RESET_COUNT_ERROR:
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
+ "ClassInterpretSenseInfo: "
+ "Region set error -- region may "
+ "be permanent\n"));
+ *Status = STATUS_CSS_RESETS_EXHAUSTED;
+ break;
+ } // end switch of ASCQ for COPY_PROTECTION_FAILURE
+
+ break;
+ }
+
+ case SCSI_ADSENSE_MUSIC_AREA: {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Music area\n"));
+ break;
+ }
+
+ case SCSI_ADSENSE_DATA_AREA: {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Data area\n"));
+ break;
+ }
+
+ case SCSI_ADSENSE_VOLUME_OVERFLOW: {
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Volume overflow\n"));
+ break;
+ }
+
+ } // end switch (addlSenseCode)
+
+ break;
+ } // end SCSI_SENSE_ILLEGAL_REQUEST
+
+ case SCSI_SENSE_UNIT_ATTENTION: {
+
+ ULONG count;
+
+ //
+ // A media change may have occured so increment the change
+ // count for the physical device
+ //
+
+ count = InterlockedIncrement((volatile LONG *)&fdoExtension->MediaChangeCount);
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClassInterpretSenseInfo: "
+ "Media change count for device %d incremented to %#lx\n",
+ fdoExtension->DeviceNumber, count));
+
+
+ switch (addlSenseCode) {
+ case SCSI_ADSENSE_MEDIUM_CHANGED: {
+ logRetryableError = FALSE;
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClassInterpretSenseInfo: "
+ "Media changed\n"));
+
+ if (!TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA)) {
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_MCN, "ClassInterpretSenseInfo: "
+ "Media Changed on non-removable device %p\n",
+ Fdo));
+ }
+ ClassSetMediaChangeState(fdoExtension, MediaPresent, FALSE);
+ break;
+ }
+
+ case SCSI_ADSENSE_BUS_RESET: {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Bus reset\n"));
+ break;
+ }
+
+ case SCSI_ADSENSE_PARAMETERS_CHANGED: {
+ logRetryableError = FALSE;
+ if (addlSenseCodeQual == SCSI_SENSEQ_CAPACITY_DATA_CHANGED) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Device capacity changed (e.g. thinly provisioned LUN). Retry the request.\n"));
+
+ ClassQueueCapacityChangedEventWorker(Fdo);
+
+ //
+ // Retry with 1 second delay as ClassQueueCapacityChangedEventWorker may trigger a couple of commands sent to disk.
+ //
+ retryInterval = 1;
+ retry = TRUE;
+ }
+ break;
+ }
+
+ case SCSI_ADSENSE_LB_PROVISIONING: {
+
+ switch (addlSenseCodeQual) {
+
+ case SCSI_SENSEQ_SOFT_THRESHOLD_REACHED: {
+
+ logRetryableError = FALSE;
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Device (%p) has hit a soft threshold.\n",
+ Fdo));
+
+ //
+ // This indicates that a resource provisioned or thinly
+ // provisioned device has hit a soft threshold. Queue a
+ // worker thread to log a system event and then retry the
+ // original request.
+ //
+ ClassQueueThresholdEventWorker(Fdo);
+ break;
+ }
+ default: {
+ retry = FALSE;
+ break;
+ }
+
+ } // end switch (addlSenseCodeQual)
+ break;
+ }
+
+ case SCSI_ADSENSE_OPERATING_CONDITIONS_CHANGED: {
+
+ if (addlSenseCodeQual == SCSI_SENSEQ_MICROCODE_CHANGED) {
+ //
+ // Device firmware has been changed. Retry the request.
+ //
+ logRetryableError = TRUE;
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Device firmware has been changed.\n"));
+
+ retryInterval = 1;
+ retry = TRUE;
+ } else {
+ //
+ // Device information has changed, we need to rescan the
+ // bus for changed information such as the capacity.
+ //
+ logRetryableError = FALSE;
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Device information changed. Invalidate the bus\n"));
+
+ if (addlSenseCodeQual == SCSI_SENSEQ_INQUIRY_DATA_CHANGED) {
+
+ ClassQueueProvisioningTypeChangedEventWorker(Fdo);
+ }
+
+ if (addlSenseCodeQual == SCSI_SENSEQ_INQUIRY_DATA_CHANGED ||
+ addlSenseCodeQual == SCSI_SENSEQ_OPERATING_DEFINITION_CHANGED) {
+
+ //
+ // Since either the LB provisioning type changed, or the block/slab size
+ // changed, next time anyone trying to query the FunctionSupportInfo, we
+ // will requery the device.
+ //
+ InterlockedIncrement((volatile LONG *)&fdoExtension->FunctionSupportInfo->ChangeRequestCount);
+ }
+
+ IoInvalidateDeviceRelations(fdoExtension->LowerPdo, BusRelations);
+ retryInterval = 5;
+ }
+ break;
+ }
+
+ case SCSI_ADSENSE_OPERATOR_REQUEST: {
+ switch (addlSenseCodeQual) {
+
+ case SCSI_SENSEQ_MEDIUM_REMOVAL: {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Ejection request received!\n"));
+ ClassSendEjectionNotification(fdoExtension);
+ break;
+ }
+
+ case SCSI_SENSEQ_WRITE_PROTECT_ENABLE: {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Operator selected write permit?! "
+ "(unsupported!)\n"));
+ break;
+ }
+
+ case SCSI_SENSEQ_WRITE_PROTECT_DISABLE: {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Operator selected write protect?! "
+ "(unsupported!)\n"));
+ break;
+ }
+ }
+ }
+
+ case SCSI_ADSENSE_FAILURE_PREDICTION_THRESHOLD_EXCEEDED: {
+
+ UCHAR wmiEventData[sizeof(ULONG)+sizeof(UCHAR)] = {0};
+
+ *((PULONG)wmiEventData) = sizeof(UCHAR);
+ wmiEventData[sizeof(ULONG)] = addlSenseCodeQual;
+
+ //
+ // Don't log another eventlog if we have already logged once
+ // NOTE: this should have been interlocked, but the structure
+ // was publicly defined to use a BOOLEAN (char). Since
+ // media only reports these errors once per X minutes,
+ // the potential race condition is nearly non-existant.
+ // the worst case is duplicate log entries, so ignore.
+ //
+
+ logError = FALSE;
+ if (fdoExtension->FailurePredicted == 0) {
+ logError = TRUE;
+ }
+ fdoExtension->FailureReason = addlSenseCodeQual;
+ logStatus = IO_WRN_FAILURE_PREDICTED;
+
+ ClassNotifyFailurePredicted(fdoExtension,
+ (PUCHAR)wmiEventData,
+ sizeof(wmiEventData),
+ FALSE, // do not log error
+ 4, // unique error value
+ SrbGetPathId(Srb),
+ SrbGetTargetId(Srb),
+ SrbGetLun(Srb));
+
+ fdoExtension->FailurePredicted = TRUE;
+
+ //
+ // Since this is a Unit Attention we need to make
+ // sure we retry this request.
+ //
+ retry = TRUE;
+
+ break;
+ }
+
+ default: {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Unit attention\n"));
+ break;
+ }
+
+ } // end switch (addlSenseCode)
+
+ if (TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA))
+ {
+
+ if ((ClassGetVpb(Fdo) != NULL) && (ClassGetVpb(Fdo)->Flags & VPB_MOUNTED))
+ {
+ //
+ // Set bit to indicate that media may have changed
+ // and volume needs verification.
+ //
+
+ SET_FLAG(Fdo->Flags, DO_VERIFY_VOLUME);
+
+ *Status = STATUS_VERIFY_REQUIRED;
+ retry = FALSE;
+ }
+ else {
+ *Status = STATUS_IO_DEVICE_ERROR;
+ }
+ }
+ else
+ {
+ *Status = STATUS_IO_DEVICE_ERROR;
+ }
+
+ break;
+
+ } // end SCSI_SENSE_UNIT_ATTENTION
+
+ case SCSI_SENSE_DATA_PROTECT: {
+
+ retry = FALSE;
+
+ if (addlSenseCode == SCSI_ADSENSE_WRITE_PROTECT)
+ {
+ switch (addlSenseCodeQual) {
+
+ case SCSI_SENSEQ_SPACE_ALLOC_FAILED_WRITE_PROTECT: {
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Device's (%p) resources are exhausted.\n",
+ Fdo));
+
+ ClassQueueResourceExhaustionEventWorker(Fdo);
+
+ //
+ // This indicates that a thinly-provisioned device has
+ // hit a permanent resource exhaustion. We need to
+ // return this status code so that patmgr can take the
+ // disk offline.
+ //
+ *Status = STATUS_DISK_RESOURCES_EXHAUSTED;
+ break;
+ }
+ default:
+ {
+ break;
+ }
+
+ } // end switch addlSenseCodeQual
+ }
+ else
+ {
+ if (IS_SCSIOP_WRITE(cdbOpcode)) {
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Media write protected\n"));
+ *Status = STATUS_MEDIA_WRITE_PROTECTED;
+ } else {
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Access denied\n"));
+ *Status = STATUS_ACCESS_DENIED;
+ }
+ }
+ break;
+ } // end SCSI_SENSE_DATA_PROTECT
+
+ case SCSI_SENSE_BLANK_CHECK: {
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Media blank check\n"));
+ retry = FALSE;
+ *Status = STATUS_NO_DATA_DETECTED;
+ break;
+ } // end SCSI_SENSE_BLANK_CHECK
+
+ case SCSI_SENSE_COPY_ABORTED: {
+
+ switch (addlSenseCode) {
+
+ case SCSI_ADSENSE_COPY_TARGET_DEVICE_ERROR: {
+
+ switch (addlSenseCodeQual) {
+
+ //
+ // 1. Target truncated the data transfer.
+ //
+ case SCSI_SENSEQ_DATA_UNDERRUN: {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_IOCTL,
+ "ClassInterpretSenseInfo (%p): Data transfer was truncated (command %x)\n",
+ Fdo,
+ cdbOpcode));
+
+ *Status = STATUS_SUCCESS;
+ retry = FALSE;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ break;
+ }
+
+ case SCSI_SENSE_ABORTED_COMMAND: {
+ if (ClasspIsOffloadDataTransferCommand(cdb)) {
+
+ switch (addlSenseCode) {
+
+ case SCSI_ADSENSE_COPY_TARGET_DEVICE_ERROR: {
+
+ switch (addlSenseCodeQual) {
+
+ //
+ // 1. Target truncated the data transfer.
+ //
+ case SCSI_SENSEQ_DATA_UNDERRUN: {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_IOCTL,
+ "ClassInterpretSenseInfo (%p): Target has truncated the data transfer (command %x)\n",
+ Fdo,
+ cdbOpcode));
+
+ *Status = STATUS_SUCCESS;
+ retry = FALSE;
+ break;
+ }
+ }
+ break;
+ }
+
+ case SCSI_ADSENSE_RESOURCE_FAILURE: {
+
+ switch (addlSenseCodeQual) {
+
+ //
+ // 1. Copy manager wasn't able to finish the operation because of insuffient resources
+ // (e.g. microsnapshot failure on read, no space on write, etc.)
+ //
+ case SCSI_SENSEQ_INSUFFICIENT_RESOURCES: {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClassInterpretSenseInfo (%p): Target has insufficient resources (command %x)\n",
+ Fdo,
+ cdb->CDB6GENERIC.OperationCode));
+
+ *Status = STATUS_INSUFFICIENT_RESOURCES;
+ retry = FALSE;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ } else {
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Command aborted\n"));
+ *Status = STATUS_IO_DEVICE_ERROR;
+ retryInterval = 1;
+ }
+ break;
+ } // end SCSI_SENSE_ABORTED_COMMAND
+
+ default: {
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Unrecognized sense code\n"));
+ *Status = STATUS_IO_DEVICE_ERROR;
+ break;
+ }
+
+ } // end switch (senseKey)
+
+
+
+ //
+ // Try to determine bad sector information from sense data
+ //
+
+ if (((IS_SCSIOP_READWRITE(cdbOpcode)) ||
+ (cdbOpcode == SCSIOP_VERIFY) ||
+ (cdbOpcode == SCSIOP_VERIFY16)) && cdb) {
+
+ if (isInformationValid)
+ {
+ readSector = 0;
+ badSector = information;
+
+ if (cdbOpcode == SCSIOP_READ16 || cdbOpcode == SCSIOP_WRITE16 || cdbOpcode == SCSIOP_VERIFY16) {
+ REVERSE_BYTES_QUAD(&readSector, &(cdb->AsByte[2]));
+ } else {
+ REVERSE_BYTES(&readSector, &(cdb->AsByte[2]));
+ }
+
+ if (cdbOpcode == SCSIOP_READ || cdbOpcode == SCSIOP_WRITE || cdbOpcode == SCSIOP_VERIFY) {
+ REVERSE_BYTES_SHORT(&index, &(cdb->CDB10.TransferBlocksMsb));
+ } else if (cdbOpcode == SCSIOP_READ6 || cdbOpcode == SCSIOP_WRITE6) {
+ index = cdb->CDB6READWRITE.TransferBlocks;
+ } else if(cdbOpcode == SCSIOP_READ12 || cdbOpcode == SCSIOP_WRITE12) {
+ REVERSE_BYTES(&index, &(cdb->CDB12.TransferLength));
+ } else {
+ REVERSE_BYTES(&index, &(cdb->CDB16.TransferLength));
+ }
+
+ //
+ // Make sure the bad sector is within the read sectors.
+ //
+
+ if (!(badSector >= readSector && badSector < readSector + index)) {
+ badSector = readSector;
+ }
+ }
+ }
+ }
+
+__ClassInterpretSenseInfo_ProcessingInvalidSenseBuffer:
+
+ if (!validSense) {
+
+ //
+ // Request sense buffer not valid. No sense information
+ // to pinpoint the error. Return general request fail.
+ //
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: "
+ "Request sense info not valid. SrbStatus %2x\n",
+ SRB_STATUS(Srb->SrbStatus)));
+ retry = TRUE;
+
+
+ switch (SRB_STATUS(Srb->SrbStatus)) {
+ case SRB_STATUS_ABORTED: {
+
+ //
+ // Update the error count for the device.
+ //
+
+ incrementErrorCount = TRUE;
+ *Status = STATUS_IO_TIMEOUT;
+ retryInterval = 1;
+ retry = TRUE;
+ break;
+ }
+
+ case SRB_STATUS_ERROR: {
+
+ *Status = STATUS_IO_DEVICE_ERROR;
+ if (SrbGetScsiStatus(Srb) == SCSISTAT_GOOD) {
+
+ //
+ // This is some strange return code. Update the error
+ // count for the device.
+ //
+
+ incrementErrorCount = TRUE;
+
+ } else if (SrbGetScsiStatus(Srb) == SCSISTAT_BUSY) {
+
+ *Status = STATUS_DEVICE_NOT_READY;
+ logRetryableError = FALSE;
+ }
+
+ break;
+ }
+
+ case SRB_STATUS_INVALID_REQUEST: {
+ *Status = STATUS_INVALID_DEVICE_REQUEST;
+ retry = FALSE;
+ break;
+ }
+
+ case SRB_STATUS_INVALID_PATH_ID:
+ case SRB_STATUS_NO_DEVICE:
+ case SRB_STATUS_NO_HBA:
+ case SRB_STATUS_INVALID_LUN:
+ case SRB_STATUS_INVALID_TARGET_ID: {
+ *Status = STATUS_NO_SUCH_DEVICE;
+ retry = FALSE;
+ break;
+ }
+
+ case SRB_STATUS_SELECTION_TIMEOUT: {
+ logError = TRUE;
+ logStatus = IO_ERR_NOT_READY;
+ uniqueId = 260;
+ *Status = STATUS_DEVICE_NOT_CONNECTED;
+ retry = FALSE;
+ break;
+ }
+
+ case SRB_STATUS_TIMEOUT:
+ case SRB_STATUS_COMMAND_TIMEOUT: {
+
+ //
+ // Update the error count for the device.
+ //
+ incrementErrorCount = TRUE;
+ *Status = STATUS_IO_TIMEOUT;
+ break;
+ }
+
+ case SRB_STATUS_PARITY_ERROR:
+ case SRB_STATUS_UNEXPECTED_BUS_FREE:
+
+ //
+ // Update the error count for the device
+ // and fall through to below
+ //
+ incrementErrorCount = TRUE;
+
+ case SRB_STATUS_BUS_RESET: {
+
+ *Status = STATUS_IO_DEVICE_ERROR;
+ logRetryableError = FALSE;
+ break;
+ }
+
+ case SRB_STATUS_DATA_OVERRUN: {
+
+ *Status = STATUS_DATA_OVERRUN;
+ retry = FALSE;
+
+ //
+ // For some commands, we allocate a buffer that may be
+ // larger than necessary. In these cases, the SRB may be
+ // returned with SRB_STATUS_DATA_OVERRUN to indicate a
+ // buffer *underrun*. However, the command was still
+ // successful so we ensure STATUS_SUCCESS is returned.
+ // We will also prevent these errors from causing noise in
+ // the error logs.
+ //
+ if ((cdbOpcode == SCSIOP_MODE_SENSE && SrbGetDataTransferLength(Srb) <= cdb->MODE_SENSE.AllocationLength) ||
+ (cdbOpcode == SCSIOP_INQUIRY && SrbGetDataTransferLength(Srb) <= cdb->CDB6INQUIRY.AllocationLength)) {
+ *Status = STATUS_SUCCESS;
+ logErrorInternal = FALSE;
+ logError = FALSE;
+ } else if (cdbOpcode == SCSIOP_MODE_SENSE10) {
+ USHORT allocationLength;
+ REVERSE_BYTES_SHORT(&(cdb->MODE_SENSE10.AllocationLength), &allocationLength);
+ if (SrbGetDataTransferLength(Srb) <= allocationLength) {
+ *Status = STATUS_SUCCESS;
+ logErrorInternal = FALSE;
+ logError = FALSE;
+ }
+ } else if (ClasspIsReceiveTokenInformation(cdb)) {
+ ULONG allocationLength;
+ REVERSE_BYTES(&(cdb->RECEIVE_TOKEN_INFORMATION.AllocationLength), &allocationLength);
+ if (SrbGetDataTransferLength(Srb) <= allocationLength) {
+ *Status = STATUS_SUCCESS;
+ logErrorInternal = FALSE;
+ logError = FALSE;
+ }
+ }
+
+ break;
+ }
+
+ case SRB_STATUS_PHASE_SEQUENCE_FAILURE: {
+
+ //
+ // Update the error count for the device.
+ //
+
+ incrementErrorCount = TRUE;
+ *Status = STATUS_IO_DEVICE_ERROR;
+
+ //
+ // If there was phase sequence error then limit the number of
+ // retries.
+ //
+
+ if (RetryCount > 1 ) {
+ retry = FALSE;
+ }
+
+ break;
+ }
+
+ case SRB_STATUS_REQUEST_FLUSHED: {
+
+ //
+ // If the status needs verification bit is set. Then set
+ // the status to need verification and no retry; otherwise,
+ // just retry the request.
+ //
+
+ if (TEST_FLAG(Fdo->Flags, DO_VERIFY_VOLUME)) {
+
+ *Status = STATUS_VERIFY_REQUIRED;
+ retry = FALSE;
+
+ } else {
+ *Status = STATUS_IO_DEVICE_ERROR;
+ logRetryableError = FALSE;
+ }
+
+ break;
+ }
+
+ default: {
+ logError = TRUE;
+ logStatus = IO_ERR_CONTROLLER_ERROR;
+ uniqueId = 259;
+ *Status = STATUS_IO_DEVICE_ERROR;
+ unhandledError = TRUE;
+ logRetryableError = FALSE;
+ break;
+ }
+ }
+
+
+ //
+ // NTRAID #183546 - if we support GESN subtype NOT_READY events, and
+ // we know from a previous poll when the device will be ready (ETA)
+ // we should delay the retry more appropriately than just guessing.
+ //
+ /*
+ if (fdoExtension->MediaChangeDetectionInfo &&
+ fdoExtension->MediaChangeDetectionInfo->Gesn.Supported &&
+ TEST_FLAG(fdoExtension->MediaChangeDetectionInfo->Gesn.EventMask,
+ NOTIFICATION_DEVICE_BUSY_CLASS_MASK)
+ ) {
+ // check if Gesn.ReadyTime if greater than current tick count
+ // if so, delay that long (from 1 to 30 seconds max?)
+ // else, leave the guess of time alone.
+ }
+ */
+
+ }
+
+ }
+
+ if (incrementErrorCount) {
+
+ //
+ // if any error count occurred, delay the retry of this io by
+ // at least one second, if caller supports it.
+ //
+
+ if (retryInterval == 0) {
+ retryInterval = 1;
+ }
+ ClasspPerfIncrementErrorCount(fdoExtension);
+ }
+
+ //
+ // If there is a class specific error handler call it.
+ //
+
+ if (fdoExtension->CommonExtension.DevInfo->ClassError != NULL) {
+
+ SCSI_REQUEST_BLOCK tempSrb = {0};
+ PSCSI_REQUEST_BLOCK srbPtr = (PSCSI_REQUEST_BLOCK)Srb;
+
+ //
+ // If class driver does not support extended SRB and this is
+ // an extended SRB, convert to legacy SRB and pass to class
+ // driver.
+ //
+ if ((Srb->Function == SRB_FUNCTION_STORAGE_REQUEST_BLOCK) &&
+ ((fdoExtension->CommonExtension.DriverExtension->SrbSupport &
+ CLASS_SRB_STORAGE_REQUEST_BLOCK) == 0)) {
+ ClasspConvertToScsiRequestBlock(&tempSrb, (PSTORAGE_REQUEST_BLOCK)Srb);
+ srbPtr = &tempSrb;
+ }
+
+ fdoExtension->CommonExtension.DevInfo->ClassError(Fdo,
+ srbPtr,
+ Status,
+ &retry);
+ }
+
+ //
+ // If the caller wants to know the suggested retry interval tell them.
+ //
+
+ if (ARGUMENT_PRESENT(RetryInterval)) {
+ *RetryInterval = retryInterval;
+ }
+
+ //
+ // The RESERVE(6) / RELEASE(6) commands are optional. So
+ // if they aren't supported, try the 10-byte equivalents
+ //
+
+ cdb = SrbGetCdb(Srb);
+ if (cdb) {
+ cdbOpcode = cdb->CDB6GENERIC.OperationCode;
+ }
+
+ if ((cdbOpcode == SCSIOP_RESERVE_UNIT ||
+ cdbOpcode == SCSIOP_RELEASE_UNIT) && cdb)
+ {
+ if (*Status == STATUS_INVALID_DEVICE_REQUEST)
+ {
+ SrbSetCdbLength(Srb, 10);
+ cdb->CDB10.OperationCode = (cdb->CDB6GENERIC.OperationCode == SCSIOP_RESERVE_UNIT) ? SCSIOP_RESERVE_UNIT10 : SCSIOP_RELEASE_UNIT10;
+
+ SET_FLAG(fdoExtension->PrivateFdoData->HackFlags, FDO_HACK_NO_RESERVE6);
+ retry = TRUE;
+ }
+ }
+
+#if DBG
+
+ //
+ // Ensure that for read/write requests, only return STATUS_DEVICE_BUSY if
+ // reservation conflict.
+ //
+ if (IS_SCSIOP_READWRITE(cdbOpcode) && (*Status == STATUS_DEVICE_BUSY)) {
+ NT_ASSERT(isReservationConflict == TRUE);
+ }
+
+#endif
+
+ /*
+ * LOG the error:
+ * If logErrorInternal is set, log the error in our internal log.
+ * If logError is set, also log the error in the system log.
+ */
+ if (logErrorInternal || logError) {
+ ULONG totalSize;
+ ULONG senseBufferSize = 0;
+ IO_ERROR_LOG_PACKET staticErrLogEntry = {0};
+ CLASS_ERROR_LOG_DATA staticErrLogData = {0};
+ SENSE_DATA convertedSenseBuffer = {0};
+ UCHAR convertedSenseBufferLength = 0;
+ BOOLEAN senseDataConverted = FALSE;
+
+ //
+ // Logic below assumes that IO_ERROR_LOG_PACKET + CLASS_ERROR_LOG_DATA
+ // is less than ERROR_LOG_MAXIMUM_SIZE which is not true for extended SRB.
+ // Given that classpnp currently does not use >16 byte CDB, we'll convert
+ // an extended SRB to SCSI_REQUEST_BLOCK instead of changing this code.
+ // More changes will need to be made when classpnp starts using >16 byte
+ // CDBs.
+ //
+
+ //
+ // Calculate the total size of the error log entry.
+ // add to totalSize in the order that they are used.
+ // the advantage to calculating all the sizes here is
+ // that we don't have to do a bunch of extraneous checks
+ // later on in this code path.
+ //
+ totalSize = sizeof(IO_ERROR_LOG_PACKET) // required
+ + sizeof(CLASS_ERROR_LOG_DATA);// struct for ease
+
+ //
+ // also save any available extra sense data, up to the maximum errlog
+ // packet size . WMI should be used for real-time analysis.
+ // the event log should only be used for post-mortem debugging.
+ //
+ if ((TEST_FLAG(Srb->SrbStatus, SRB_STATUS_AUTOSENSE_VALID)) && senseBuffer) {
+
+ UCHAR validSenseBytes = 0;
+ UCHAR senseInfoBufferLength = 0;
+
+ senseInfoBufferLength = SrbGetSenseInfoBufferLength(Srb);
+
+ //
+ // If sense data is in Descriptor format, convert it to Fixed format
+ // for the private log.
+ //
+
+ if (IsDescriptorSenseDataFormat(senseBuffer)) {
+
+ convertedSenseBufferLength = sizeof(convertedSenseBuffer);
+
+ senseDataConverted = ScsiConvertToFixedSenseFormat(senseBuffer,
+ senseInfoBufferLength,
+ (PVOID)&convertedSenseBuffer,
+ convertedSenseBufferLength);
+ }
+
+ //
+ // For System Log, copy the maximum amount of available sense data
+ //
+
+ if (ScsiGetTotalSenseByteCountIndicated(senseBuffer,
+ senseInfoBufferLength,
+ &validSenseBytes)) {
+
+ //
+ // If it is able to determine number of valid bytes,
+ // copy the maximum amount of available
+ // sense data that can be saved into the the errlog.
+ //
+
+ //
+ // set to save the most sense buffer possible
+ //
+
+ senseBufferSize = max(validSenseBytes, sizeof(staticErrLogData.SenseData));
+ senseBufferSize = min(senseBufferSize, senseInfoBufferLength);
+
+ } else {
+ //
+ // it's smaller than required to read the total number of
+ // valid bytes, so just use the SenseInfoBufferLength field.
+ //
+ senseBufferSize = senseInfoBufferLength;
+ }
+
+ /*
+ * Bump totalSize by the number of extra senseBuffer bytes
+ * (beyond the default sense buffer within CLASS_ERROR_LOG_DATA).
+ * Make sure to never allocate more than ERROR_LOG_MAXIMUM_SIZE.
+ */
+ if (senseBufferSize > sizeof(staticErrLogData.SenseData)){
+ totalSize += senseBufferSize-sizeof(staticErrLogData.SenseData);
+ if (totalSize > ERROR_LOG_MAXIMUM_SIZE){
+ senseBufferSize -= totalSize-ERROR_LOG_MAXIMUM_SIZE;
+ totalSize = ERROR_LOG_MAXIMUM_SIZE;
+ }
+ }
+ }
+
+ //
+ // If we've used up all of our retry attempts, set the final status to
+ // reflect the appropriate result.
+ //
+ // ISSUE: the test below should also check RetryCount to determine if we will actually retry,
+ // but there is no easy test because we'd have to consider the original retry count
+ // for the op; besides, InterpretTransferPacketError sometimes ignores the retry
+ // decision returned by this function. So just ErrorRetried to be true in the majority case.
+ //
+ if (retry){
+ staticErrLogEntry.FinalStatus = STATUS_SUCCESS;
+ staticErrLogData.ErrorRetried = TRUE;
+ } else {
+ staticErrLogEntry.FinalStatus = *Status;
+ }
+
+ //
+ // Don't log generic IO_WARNING_PAGING_FAILURE message if either the
+ // I/O is retried, or it completed successfully.
+ //
+ if (logStatus == IO_WARNING_PAGING_FAILURE &&
+ (retry || NT_SUCCESS(*Status)) ) {
+ logError = FALSE;
+ }
+
+ if (TEST_FLAG(SrbGetSrbFlags(Srb), SRB_CLASS_FLAGS_PAGING)) {
+ staticErrLogData.ErrorPaging = TRUE;
+ }
+ if (unhandledError) {
+ staticErrLogData.ErrorUnhandled = TRUE;
+ }
+
+ //
+ // Calculate the device offset if there is a geometry.
+ //
+ staticErrLogEntry.DeviceOffset.QuadPart = (LONGLONG)badSector;
+ staticErrLogEntry.DeviceOffset.QuadPart *= (LONGLONG)fdoExtension->DiskGeometry.BytesPerSector;
+ if (logStatus == -1){
+ staticErrLogEntry.ErrorCode = STATUS_IO_DEVICE_ERROR;
+ } else {
+ staticErrLogEntry.ErrorCode = logStatus;
+ }
+
+ /*
+ * The dump data follows the IO_ERROR_LOG_PACKET
+ */
+ staticErrLogEntry.DumpDataSize = (USHORT)totalSize - sizeof(IO_ERROR_LOG_PACKET);
+
+ staticErrLogEntry.SequenceNumber = 0;
+ staticErrLogEntry.MajorFunctionCode = MajorFunctionCode;
+ staticErrLogEntry.IoControlCode = IoDeviceCode;
+ staticErrLogEntry.RetryCount = (UCHAR) RetryCount;
+ staticErrLogEntry.UniqueErrorValue = uniqueId;
+
+ KeQueryTickCount(&staticErrLogData.TickCount);
+ staticErrLogData.PortNumber = (ULONG)-1;
+
+ /*
+ * Save the entire contents of the SRB.
+ */
+ if (Srb->Function == SRB_FUNCTION_STORAGE_REQUEST_BLOCK) {
+ ClasspConvertToScsiRequestBlock(&staticErrLogData.Srb, (PSTORAGE_REQUEST_BLOCK)Srb);
+ } else {
+ staticErrLogData.Srb = *(PSCSI_REQUEST_BLOCK)Srb;
+ }
+
+ /*
+ * For our private log, save just the default length of the SENSE_DATA.
+ */
+
+ if ((senseBufferSize != 0) && senseBuffer) {
+
+ //
+ // If sense buffer is in Fixed format, put it in the private log
+ //
+ // If sense buffer is in Descriptor format, put it in the private log if conversion to Fixed format
+ // succeeded. Otherwise, do not put it in the private log.
+ //
+ // If sense buffer is in unknown format, the device or the driver probably does not populate
+ // the first byte of sense data, we probably still want to log error in this case assuming
+ // it's fixed format, so that its sense key, its additional sense code, and its additional sense code
+ // qualifier would be shown in the debugger extension output. By doing so, it minimizes any potential
+ // negative impacts to our ability to diagnose issue.
+ //
+ if (IsDescriptorSenseDataFormat(senseBuffer)) {
+ if (senseDataConverted) {
+ RtlCopyMemory(&staticErrLogData.SenseData, &convertedSenseBuffer, min(convertedSenseBufferLength, sizeof(staticErrLogData.SenseData)));
+ }
+ } else {
+ RtlCopyMemory(&staticErrLogData.SenseData, senseBuffer, min(senseBufferSize, sizeof(staticErrLogData.SenseData)));
+ }
+ }
+
+ /*
+ * Save the error log in our context.
+ * We only save the default sense buffer length.
+ */
+ if (logErrorInternal) {
+ KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
+ fdoData->ErrorLogs[fdoData->ErrorLogNextIndex] = staticErrLogData;
+ fdoData->ErrorLogNextIndex++;
+ fdoData->ErrorLogNextIndex %= NUM_ERROR_LOG_ENTRIES;
+ KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
+ }
+
+ /*
+ * Log an event if an IO is being retried for reasons that may indicate
+ * a transient/permanent problem with the I_T_L nexus. But log the event
+ * only once per retried IO.
+ */
+ if (!IS_SCSIOP_READWRITE(cdbOpcode) ||
+ !retry ||
+ (RetryCount != 0)) {
+
+ logRetryableError = FALSE;
+ }
+
+ if (logRetryableError) {
+
+ logError = TRUE;
+ }
+
+ /*
+ * If logError is set, also save this log in the system's error log.
+ * But make sure we don't log TUR failures over and over
+ * (e.g. if an external drive was switched off and we're still sending TUR's to it every second).
+ */
+
+ if (logError)
+ {
+ //
+ // We do not want to log certain system events repetitively
+ //
+
+ cdb = SrbGetCdb(Srb);
+ if (cdb) {
+ switch (cdb->CDB10.OperationCode)
+ {
+ case SCSIOP_TEST_UNIT_READY:
+ {
+ if (fdoData->LoggedTURFailureSinceLastIO)
+ {
+ logError = FALSE;
+ }
+ else
+ {
+ fdoData->LoggedTURFailureSinceLastIO = TRUE;
+ }
+
+ break;
+ }
+
+ case SCSIOP_SYNCHRONIZE_CACHE:
+ {
+ if (fdoData->LoggedSYNCFailure)
+ {
+ logError = FALSE;
+ }
+ else
+ {
+ fdoData->LoggedSYNCFailure = TRUE;
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ if (logError){
+
+ if (logRetryableError) {
+
+ NT_ASSERT(IS_SCSIOP_READWRITE(cdbOpcode));
+
+ //
+ // A large Disk TimeOutValue (like 60 seconds) results in giving a command a
+ // large window to complete in. However, if the target returns a retryable error
+ // just prior to the command timing out, and if multiple retries kick in, it may
+ // take a significantly long time for the request to complete back to the
+ // application, leading to a user perception of a hung system. So log an event
+ // for retried IO so that an admin can help explain the reason for this behavior.
+ //
+ ClasspQueueLogIOEventWithContextWorker(Fdo,
+ senseBufferSize,
+ senseBuffer,
+ SRB_STATUS(Srb->SrbStatus),
+ SrbGetScsiStatus(Srb),
+ (ULONG)IO_WARNING_IO_OPERATION_RETRIED,
+ cdbLength,
+ cdb,
+ NULL);
+
+ } else {
+
+ PIO_ERROR_LOG_PACKET errorLogEntry;
+ PCLASS_ERROR_LOG_DATA errlogData;
+
+ errorLogEntry = (PIO_ERROR_LOG_PACKET)IoAllocateErrorLogEntry(Fdo, (UCHAR)totalSize);
+ if (errorLogEntry){
+ errlogData = (PCLASS_ERROR_LOG_DATA)errorLogEntry->DumpData;
+
+ *errorLogEntry = staticErrLogEntry;
+ *errlogData = staticErrLogData;
+
+ /*
+ * For the system log, copy as much of the sense buffer as possible.
+ */
+ if ((senseBufferSize != 0) && senseBuffer) {
+ RtlCopyMemory(&errlogData->SenseData, senseBuffer, senseBufferSize);
+ }
+
+ /*
+ * Write the error log packet to the system error logging thread.
+ * It will be freed by the kernel.
+ */
+ IoWriteErrorLogEntry(errorLogEntry);
+ }
+ }
+ }
+ }
+
+ return retry;
+
+} // end ClassInterpretSenseInfo()
+
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassModeSense()
+
+Routine Description:
+
+ This routine sends a mode sense command to a target ID and returns
+ when it is complete.
+
+Arguments:
+
+ Fdo - Supplies the functional device object associated with this request.
+
+ ModeSenseBuffer - Supplies a buffer to store the sense data.
+
+ Length - Supplies the length in bytes of the mode sense buffer.
+
+ PageMode - Supplies the page or pages of mode sense data to be retrived.
+
+Return Value:
+
+ Length of the transferred data is returned.
+
+--*/
+ULONG ClassModeSense(
+ _In_ PDEVICE_OBJECT Fdo,
+ _In_reads_bytes_(Length) PCHAR ModeSenseBuffer,
+ _In_ ULONG Length,
+ _In_ UCHAR PageMode
+ )
+{
+ PAGED_CODE();
+
+ return ClasspModeSense(Fdo,
+ ModeSenseBuffer,
+ Length,
+ PageMode,
+ MODE_SENSE_CURRENT_VALUES);
+}
+
+
+ULONG
+ClassModeSenseEx(
+ _In_ PDEVICE_OBJECT Fdo,
+ _In_reads_bytes_(Length) PCHAR ModeSenseBuffer,
+ _In_ ULONG Length,
+ _In_ UCHAR PageMode,
+ _In_ UCHAR PageControl
+ )
+{
+ PAGED_CODE();
+#if (NTDDI_VERSION >= NTDDI_WINBLUE)
+ return ClasspModeSense(Fdo,
+ ModeSenseBuffer,
+ Length,
+ PageMode,
+ PageControl);
+#else
+ UNREFERENCED_PARAMETER(Fdo);
+ UNREFERENCED_PARAMETER(ModeSenseBuffer);
+ UNREFERENCED_PARAMETER(Length);
+ UNREFERENCED_PARAMETER(PageMode);
+ UNREFERENCED_PARAMETER(PageControl);
+ return 0;
+#endif
+}
+
+ULONG ClasspModeSense(
+ _In_ PDEVICE_OBJECT Fdo,
+ _In_reads_bytes_(Length) PCHAR ModeSenseBuffer,
+ _In_ ULONG Length,
+ _In_ UCHAR PageMode,
+ _In_ UCHAR PageControl
+ )
+/*
+Routine Description:
+
+ This routine sends a mode sense command to a target ID and returns
+ when it is complete.
+
+Arguments:
+
+ Fdo - Supplies the functional device object associated with this request.
+
+ ModeSenseBuffer - Supplies a buffer to store the sense data.
+
+ Length - Supplies the length in bytes of the mode sense buffer.
+
+ PageMode - Supplies the page or pages of mode sense data to be retrived.
+
+ PageControl - Supplies the page control value of the request, which is
+ one of the following:
+ MODE_SENSE_CURRENT_VALUES
+ MODE_SENSE_CHANGEABLE_VALUES
+ MODE_SENSE_DEFAULT_VAULES
+ MODE_SENSE_SAVED_VALUES
+
+Return Value:
+
+ Length of the transferred data is returned.
+
+--*/
+{
+ ULONG lengthTransferred = 0;
+ PMDL senseBufferMdl;
+
+ PAGED_CODE();
+
+ senseBufferMdl = BuildDeviceInputMdl(ModeSenseBuffer, Length);
+ if (senseBufferMdl){
+
+ TRANSFER_PACKET *pkt = DequeueFreeTransferPacket(Fdo, TRUE);
+ if (pkt){
+ KEVENT event;
+ IRP pseudoIrp = {0};
+
+ /*
+ * Store the number of packets servicing the irp (one)
+ * inside the original IRP. It will be used to counted down
+ * to zero when the packet completes.
+ * Initialize the original IRP's status to success.
+ * If the packet fails, we will set it to the error status.
+ */
+ pseudoIrp.Tail.Overlay.DriverContext[0] = LongToPtr(1);
+ pseudoIrp.IoStatus.Status = STATUS_SUCCESS;
+ pseudoIrp.IoStatus.Information = 0;
+ pseudoIrp.MdlAddress = senseBufferMdl;
+
+ /*
+ * Set this up as a SYNCHRONOUS transfer, submit it,
+ * and wait for the packet to complete. The result
+ * status will be written to the original irp.
+ */
+ NT_ASSERT(Length <= 0x0ff);
+ KeInitializeEvent(&event, SynchronizationEvent, FALSE);
+ SetupModeSenseTransferPacket(pkt, &event, ModeSenseBuffer, (UCHAR)Length, PageMode, 0, &pseudoIrp, PageControl);
+ SubmitTransferPacket(pkt);
+ (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
+
+ if (NT_SUCCESS(pseudoIrp.IoStatus.Status)){
+ lengthTransferred = (ULONG)pseudoIrp.IoStatus.Information;
+ }
+ else {
+ /*
+ * This request can sometimes fail legitimately
+ * (e.g. when a SCSI device is attached but turned off)
+ * so this is not necessarily a device/driver bug.
+ */
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClasspModeSense on Fdo %ph failed with status %xh.", Fdo, pseudoIrp.IoStatus.Status));
+ }
+ }
+
+ FreeDeviceInputMdl(senseBufferMdl);
+ }
+
+ return lengthTransferred;
+}
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassFindModePage()
+
+Routine Description:
+
+ This routine scans through the mode sense data and finds the requested
+ mode sense page code.
+
+Arguments:
+ ModeSenseBuffer - Supplies a pointer to the mode sense data.
+
+ Length - Indicates the length of valid data.
+
+ PageMode - Supplies the page mode to be searched for.
+
+ Use6Byte - Indicates whether 6 or 10 byte mode sense was used.
+
+Return Value:
+
+ A pointer to the the requested mode page. If the mode page was not found
+ then NULL is return.
+
+--*/
+PVOID
+ClassFindModePage(
+ _In_reads_bytes_(Length) PCHAR ModeSenseBuffer,
+ _In_ ULONG Length,
+ _In_ UCHAR PageMode,
+ _In_ BOOLEAN Use6Byte
+ )
+{
+ PUCHAR limit;
+ ULONG parameterHeaderLength;
+ PVOID result = NULL;
+
+ limit = (PUCHAR)ModeSenseBuffer + Length;
+ parameterHeaderLength = (Use6Byte) ? sizeof(MODE_PARAMETER_HEADER) : sizeof(MODE_PARAMETER_HEADER10);
+
+ if (Length >= parameterHeaderLength) {
+
+ PMODE_PARAMETER_HEADER10 modeParam10;
+ ULONG blockDescriptorLength;
+
+ /*
+ * Skip the mode select header and block descriptors.
+ */
+ if (Use6Byte){
+ blockDescriptorLength = ((PMODE_PARAMETER_HEADER) ModeSenseBuffer)->BlockDescriptorLength;
+ }
+ else {
+ modeParam10 = (PMODE_PARAMETER_HEADER10) ModeSenseBuffer;
+ blockDescriptorLength = modeParam10->BlockDescriptorLength[1];
+ }
+
+ ModeSenseBuffer += parameterHeaderLength + blockDescriptorLength;
+
+ //
+ // ModeSenseBuffer now points at pages. Walk the pages looking for the
+ // requested page until the limit is reached.
+ //
+
+ while (ModeSenseBuffer +
+ RTL_SIZEOF_THROUGH_FIELD(MODE_DISCONNECT_PAGE, PageLength) < (PCHAR)limit) {
+
+ if (((PMODE_DISCONNECT_PAGE) ModeSenseBuffer)->PageCode == PageMode) {
+
+ /*
+ * found the mode page. make sure it's safe to touch it all
+ * before returning the pointer to caller
+ */
+
+ if (ModeSenseBuffer + ((PMODE_DISCONNECT_PAGE)ModeSenseBuffer)->PageLength > (PCHAR)limit) {
+ /*
+ * Return NULL since the page is not safe to access in full
+ */
+ result = NULL;
+ }
+ else {
+ result = ModeSenseBuffer;
+ }
+ break;
+ }
+
+ //
+ // Advance to the next page which is 4-byte-aligned offset after this page.
+ //
+ ModeSenseBuffer +=
+ ((PMODE_DISCONNECT_PAGE) ModeSenseBuffer)->PageLength +
+ RTL_SIZEOF_THROUGH_FIELD(MODE_DISCONNECT_PAGE, PageLength);
+
+ }
+ }
+
+ return result;
+} // end ClassFindModePage()
+
+
+NTSTATUS
+ClassModeSelect(
+ _In_ PDEVICE_OBJECT Fdo,
+ _In_reads_bytes_(Length) PCHAR ModeSelectBuffer,
+ _In_ ULONG Length,
+ _In_ BOOLEAN SavePages
+ )
+{
+#if (NTDDI_VERSION >= NTDDI_WINBLUE)
+ return ClasspModeSelect(Fdo,
+ ModeSelectBuffer,
+ Length,
+ SavePages);
+#else
+ UNREFERENCED_PARAMETER(Fdo);
+ UNREFERENCED_PARAMETER(ModeSelectBuffer);
+ UNREFERENCED_PARAMETER(Length);
+ UNREFERENCED_PARAMETER(SavePages);
+ return STATUS_NOT_SUPPORTED;
+#endif
+}
+
+/*++
+ClasspModeSelect()
+
+Routine Description:
+
+ This routine sends a mode select command to a target ID and returns
+ when it is complete.
+
+Arguments:
+
+ Fdo - Supplies the functional device object associated with this request.
+
+ ModeSelectBuffer - Supplies a buffer to the select data.
+
+ Length - Supplies the length in bytes of the mode select buffer.
+
+ SavePages - Specifies the value of the save pages (SP) bit in the mode
+ select command.
+
+Return Value:
+
+ NTSTATUS code of the request.
+
+--*/
+NTSTATUS
+ClasspModeSelect(
+ _In_ PDEVICE_OBJECT Fdo,
+ _In_reads_bytes_(Length) PCHAR ModeSelectBuffer,
+ _In_ ULONG Length,
+ _In_ BOOLEAN SavePages
+ )
+{
+
+ PMDL senseBufferMdl;
+ NTSTATUS status = STATUS_UNSUCCESSFUL;
+
+ senseBufferMdl = BuildDeviceInputMdl(ModeSelectBuffer, Length);
+ if (senseBufferMdl) {
+
+ TRANSFER_PACKET *pkt = DequeueFreeTransferPacket(Fdo, TRUE);
+ if (pkt){
+ KEVENT event;
+ IRP pseudoIrp = {0};
+
+ /*
+ * Store the number of packets servicing the irp (one)
+ * inside the original IRP. It will be used to counted down
+ * to zero when the packet completes.
+ * Initialize the original IRP's status to success.
+ * If the packet fails, we will set it to the error status.
+ */
+ pseudoIrp.Tail.Overlay.DriverContext[0] = LongToPtr(1);
+ pseudoIrp.IoStatus.Status = STATUS_SUCCESS;
+ pseudoIrp.IoStatus.Information = 0;
+ pseudoIrp.MdlAddress = senseBufferMdl;
+
+ /*
+ * Set this up as a SYNCHRONOUS transfer, submit it,
+ * and wait for the packet to complete. The result
+ * status will be written to the original irp.
+ */
+ NT_ASSERT(Length <= 0x0ff);
+ KeInitializeEvent(&event, SynchronizationEvent, FALSE);
+ SetupModeSelectTransferPacket(pkt, &event, ModeSelectBuffer, (UCHAR)Length, SavePages, &pseudoIrp);
+ SubmitTransferPacket(pkt);
+ (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
+
+ if (!NT_SUCCESS(pseudoIrp.IoStatus.Status)){
+ /*
+ * This request can sometimes fail legitimately
+ * (e.g. when a SCSI device is attached but turned off)
+ * so this is not necessarily a device/driver bug.
+ */
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassModeSelect on Fdo %ph failed with status %xh.", Fdo, pseudoIrp.IoStatus.Status));
+ }
+
+ status = pseudoIrp.IoStatus.Status;
+ }
+
+ FreeDeviceInputMdl(senseBufferMdl);
+ } else {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ return status;
+}
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassSendSrbAsynchronous()
+
+Routine Description:
+
+ This routine takes a partially built Srb and an Irp and sends it down to
+ the port driver.
+
+ This routine must be called with the remove lock held for the specified
+ Irp.
+
+Arguments:
+
+ Fdo - Supplies the functional device object for the orginal request.
+
+ Srb - Supplies a paritally build ScsiRequestBlock. In particular, the
+ CDB and the SRB timeout value must be filled in. The SRB must not be
+ allocated from zone.
+
+ Irp - Supplies the requesting Irp.
+
+ BufferAddress - Supplies a pointer to the buffer to be transfered.
+
+ BufferLength - Supplies the length of data transfer.
+
+ WriteToDevice - Indicates the data transfer will be from system memory to
+ device.
+
+Return Value:
+
+ Returns STATUS_PENDING if the request is dispatched (since the
+ completion routine may change the irp's status value we cannot simply
+ return the value of the dispatch)
+
+ or returns a status value to indicate why it failed.
+
+--*/
+_Success_(return == STATUS_PENDING)
+NTSTATUS
+ClassSendSrbAsynchronous(
+ _In_ PDEVICE_OBJECT Fdo,
+ _Inout_ __on_failure(__drv_freesMem(Mem)) __drv_aliasesMem PSCSI_REQUEST_BLOCK _Srb,
+ _In_ PIRP Irp,
+ _In_reads_bytes_opt_(BufferLength) __drv_aliasesMem PVOID BufferAddress,
+ _In_ ULONG BufferLength,
+ _In_ BOOLEAN WriteToDevice
+ )
+{
+
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
+ PIO_STACK_LOCATION irpStack;
+ PSTORAGE_REQUEST_BLOCK_HEADER Srb = (PSTORAGE_REQUEST_BLOCK_HEADER)_Srb;
+
+ ULONG savedFlags;
+
+ if (Srb->Function != SRB_FUNCTION_STORAGE_REQUEST_BLOCK) {
+ //
+ // Write length to SRB.
+ //
+
+ Srb->Length = sizeof(SCSI_REQUEST_BLOCK);
+
+ //
+ // Set SCSI bus address.
+ //
+
+ Srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
+ }
+
+ //
+ // This is a violation of the SCSI spec but it is required for
+ // some targets.
+ //
+
+ // Srb->Cdb[1] |= deviceExtension->Lun << 5;
+
+ //
+ // Indicate auto request sense by specifying buffer and size.
+ //
+
+ SrbSetSenseInfoBuffer(Srb, fdoExtension->SenseData);
+ SrbSetSenseInfoBufferLength(Srb, GET_FDO_EXTENSON_SENSE_DATA_LENGTH(fdoExtension));
+
+ SrbSetDataBuffer(Srb, BufferAddress);
+
+ //
+ // Set the transfer length.
+ //
+ SrbSetDataTransferLength(Srb, BufferLength);
+
+ //
+ // Save the class driver specific flags away.
+ //
+
+ savedFlags = SrbGetSrbFlags(Srb) & SRB_FLAGS_CLASS_DRIVER_RESERVED;
+
+ //
+ // Allow the caller to specify that they do not wish
+ // IoStartNextPacket() to be called in the completion routine.
+ //
+
+ SET_FLAG(savedFlags, (SrbGetSrbFlags(Srb) & SRB_FLAGS_DONT_START_NEXT_PACKET));
+
+ //
+ // If caller wants to this request to be tagged, save this fact.
+ //
+
+ if ( TEST_FLAG(SrbGetSrbFlags(Srb), SRB_FLAGS_QUEUE_ACTION_ENABLE) &&
+ ( SRB_SIMPLE_TAG_REQUEST == SrbGetRequestAttribute(Srb) ||
+ SRB_HEAD_OF_QUEUE_TAG_REQUEST == SrbGetRequestAttribute(Srb) ||
+ SRB_ORDERED_QUEUE_TAG_REQUEST == SrbGetRequestAttribute(Srb) ) ) {
+
+ SET_FLAG(savedFlags, SRB_FLAGS_QUEUE_ACTION_ENABLE);
+ if (TEST_FLAG(SrbGetSrbFlags(Srb), SRB_FLAGS_NO_QUEUE_FREEZE)) {
+ SET_FLAG(savedFlags, SRB_FLAGS_NO_QUEUE_FREEZE);
+ }
+ }
+
+ if (BufferAddress != NULL) {
+
+ //
+ // Build Mdl if necessary.
+ //
+
+ if (Irp->MdlAddress == NULL) {
+
+ PMDL mdl;
+
+ mdl = IoAllocateMdl(BufferAddress,
+ BufferLength,
+ FALSE,
+ FALSE,
+ Irp);
+
+ if ((mdl == NULL) || (Irp->MdlAddress == NULL)) {
+
+ Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
+
+ //
+ // ClassIoComplete() would have free'd the srb
+ //
+
+ if (PORT_ALLOCATED_SENSE_EX(fdoExtension, Srb)) {
+ FREE_PORT_ALLOCATED_SENSE_BUFFER_EX(fdoExtension, Srb);
+ }
+ ClassFreeOrReuseSrb(fdoExtension, (PSCSI_REQUEST_BLOCK)Srb);
+ ClassReleaseRemoveLock(Fdo, Irp);
+ ClassCompleteRequest(Fdo, Irp, IO_NO_INCREMENT);
+
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ SET_FLAG(savedFlags, SRB_CLASS_FLAGS_FREE_MDL);
+
+ MmBuildMdlForNonPagedPool(Irp->MdlAddress);
+
+ } else {
+
+ //
+ // Make sure the buffer requested matches the MDL.
+ //
+
+ NT_ASSERT(BufferAddress == MmGetMdlVirtualAddress(Irp->MdlAddress));
+ }
+
+ //
+ // Set read flag.
+ //
+
+ SrbAssignSrbFlags(Srb, WriteToDevice ? SRB_FLAGS_DATA_OUT : SRB_FLAGS_DATA_IN);
+
+ } else {
+
+ //
+ // Clear flags.
+ //
+
+ SrbAssignSrbFlags(Srb, SRB_FLAGS_NO_DATA_TRANSFER);
+ }
+
+ //
+ // Restore saved flags.
+ //
+
+ SrbSetSrbFlags(Srb, savedFlags);
+
+ //
+ // Disable synchronous transfer for these requests.
+ //
+
+ SrbSetSrbFlags(Srb, SRB_FLAGS_DISABLE_SYNCH_TRANSFER);
+
+ //
+ // Zero out status.
+ //
+
+ SrbSetScsiStatus(Srb, 0);
+ Srb->SrbStatus = 0;
+
+ SrbSetNextSrb(Srb, NULL);
+
+ //
+ // Save a few parameters in the current stack location.
+ //
+
+ irpStack = IoGetCurrentIrpStackLocation(Irp);
+
+ //
+ // Save retry count in current Irp stack.
+ //
+
+ irpStack->Parameters.Others.Argument4 = (PVOID)MAXIMUM_RETRIES;
+
+ //
+ // Set up IoCompletion routine address.
+ //
+
+ IoSetCompletionRoutine(Irp, ClassIoComplete, Srb, TRUE, TRUE, TRUE);
+
+ //
+ // Get next stack location and
+ // set major function code.
+ //
+
+ irpStack = IoGetNextIrpStackLocation(Irp);
+
+ irpStack->MajorFunction = IRP_MJ_SCSI;
+
+ //
+ // Save SRB address in next stack for port driver.
+ //
+
+ irpStack->Parameters.Scsi.Srb = (PSCSI_REQUEST_BLOCK)Srb;
+
+ //
+ // Set up Irp Address.
+ //
+
+ SrbSetOriginalRequest(Srb, Irp);
+
+ //
+ // Call the port driver to process the request.
+ //
+
+ IoMarkIrpPending(Irp);
+
+ IoCallDriver(fdoExtension->CommonExtension.LowerDeviceObject, Irp);
+
+ return STATUS_PENDING;
+
+} // end ClassSendSrbAsynchronous()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassDeviceControlDispatch()
+
+Routine Description:
+
+ The routine is the common class driver device control dispatch entry point.
+ This routine is invokes the device-specific drivers DeviceControl routine,
+ (which may call the Class driver's common DeviceControl routine).
+
+Arguments:
+
+ DeviceObject - Supplies a pointer to the device object for this request.
+
+ Irp - Supplies the Irp making the request.
+
+Return Value:
+
+ Returns the status returned from the device-specific driver.
+
+--*/
+NTSTATUS
+ClassDeviceControlDispatch(
+ PDEVICE_OBJECT DeviceObject,
+ PIRP Irp
+ )
+{
+
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+ ULONG isRemoved;
+
+ isRemoved = ClassAcquireRemoveLock(DeviceObject, Irp);
+ _Analysis_assume_(isRemoved);
+ if(isRemoved) {
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+
+ Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST;
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ return STATUS_DEVICE_DOES_NOT_EXIST;
+ }
+
+ //
+ // Call the class specific driver DeviceControl routine.
+ // If it doesn't handle it, it will call back into ClassDeviceControl.
+ //
+
+ NT_ASSERT(commonExtension->DevInfo->ClassDeviceControl);
+
+ return commonExtension->DevInfo->ClassDeviceControl(DeviceObject,Irp);
+} // end ClassDeviceControlDispatch()
+
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassDeviceControl()
+
+Routine Description:
+
+ The routine is the common class driver device control dispatch function.
+ This routine is called by a class driver when it get an unrecognized
+ device control request. This routine will perform the correct action for
+ common requests such as lock media. If the device request is unknown it
+ passed down to the next level.
+
+ This routine must be called with the remove lock held for the specified
+ irp.
+
+Arguments:
+
+ DeviceObject - Supplies a pointer to the device object for this request.
+
+ Irp - Supplies the Irp making the request.
+
+Return Value:
+
+ Returns back a STATUS_PENDING or a completion status.
+
+--*/
+NTSTATUS
+ClassDeviceControl(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _Inout_ PIRP Irp
+ )
+{
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ PIO_STACK_LOCATION nextStack = NULL;
+
+ ULONG controlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;
+
+ PSCSI_REQUEST_BLOCK srb = NULL;
+ PCDB cdb = NULL;
+
+ NTSTATUS status;
+ ULONG modifiedIoControlCode = 0;
+
+
+ //
+ // If this is a pass through I/O control, set the minor function code
+ // and device address and pass it to the port driver.
+ //
+
+ if ( (controlCode == IOCTL_SCSI_PASS_THROUGH) ||
+ (controlCode == IOCTL_SCSI_PASS_THROUGH_DIRECT) ||
+ (controlCode == IOCTL_SCSI_PASS_THROUGH_EX) ||
+ (controlCode == IOCTL_SCSI_PASS_THROUGH_DIRECT_EX) ) {
+
+
+
+ //
+ // Validiate the user buffer for SCSI pass through.
+ // For pass through EX: as the handler will validate the size anyway,
+ // do not apply the similar check and leave the work to the handler.
+ //
+ if ( (controlCode == IOCTL_SCSI_PASS_THROUGH) ||
+ (controlCode == IOCTL_SCSI_PASS_THROUGH_DIRECT) ) {
+
+ #if BUILD_WOW64_ENABLED && defined(_WIN64)
+
+ if (IoIs32bitProcess(Irp)) {
+
+ if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(SCSI_PASS_THROUGH32)){
+
+ Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+
+ status = STATUS_INVALID_PARAMETER;
+ goto SetStatusAndReturn;
+ }
+ }
+ else
+
+ #endif
+
+ {
+ if (irpStack->Parameters.DeviceIoControl.InputBufferLength <
+ sizeof(SCSI_PASS_THROUGH)) {
+
+ Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+
+ status = STATUS_INVALID_PARAMETER;
+ goto SetStatusAndReturn;
+ }
+ }
+ }
+
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+
+ nextStack = IoGetNextIrpStackLocation(Irp);
+ nextStack->MinorFunction = 1;
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ goto SetStatusAndReturn;
+
+ }
+
+ Irp->IoStatus.Information = 0;
+
+
+ switch (controlCode) {
+
+ case IOCTL_MOUNTDEV_QUERY_UNIQUE_ID: {
+
+ PMOUNTDEV_UNIQUE_ID uniqueId;
+
+ if (!commonExtension->MountedDeviceInterfaceName.Buffer) {
+ status = STATUS_INVALID_PARAMETER;
+ break;
+ }
+
+ if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
+ sizeof(MOUNTDEV_UNIQUE_ID)) {
+
+ status = STATUS_BUFFER_TOO_SMALL;
+ Irp->IoStatus.Information = sizeof(MOUNTDEV_UNIQUE_ID);
+ break;
+ }
+
+ uniqueId = Irp->AssociatedIrp.SystemBuffer;
+ RtlZeroMemory(uniqueId, sizeof(MOUNTDEV_UNIQUE_ID));
+ uniqueId->UniqueIdLength =
+ commonExtension->MountedDeviceInterfaceName.Length;
+
+ if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
+ sizeof(USHORT) + uniqueId->UniqueIdLength) {
+
+ status = STATUS_BUFFER_OVERFLOW;
+ Irp->IoStatus.Information = sizeof(MOUNTDEV_UNIQUE_ID);
+ break;
+ }
+
+ RtlCopyMemory(uniqueId->UniqueId,
+ commonExtension->MountedDeviceInterfaceName.Buffer,
+ uniqueId->UniqueIdLength);
+
+ status = STATUS_SUCCESS;
+ Irp->IoStatus.Information = sizeof(USHORT) +
+ uniqueId->UniqueIdLength;
+ break;
+ }
+
+ case IOCTL_MOUNTDEV_QUERY_DEVICE_NAME: {
+
+ PMOUNTDEV_NAME name;
+
+ NT_ASSERT(commonExtension->DeviceName.Buffer);
+
+ if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
+ sizeof(MOUNTDEV_NAME)) {
+
+ status = STATUS_BUFFER_TOO_SMALL;
+ Irp->IoStatus.Information = sizeof(MOUNTDEV_NAME);
+ break;
+ }
+
+ name = Irp->AssociatedIrp.SystemBuffer;
+ RtlZeroMemory(name, sizeof(MOUNTDEV_NAME));
+ name->NameLength = commonExtension->DeviceName.Length;
+
+ if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
+ sizeof(USHORT) + name->NameLength) {
+
+ status = STATUS_BUFFER_OVERFLOW;
+ Irp->IoStatus.Information = sizeof(MOUNTDEV_NAME);
+ break;
+ }
+
+ RtlCopyMemory(name->Name, commonExtension->DeviceName.Buffer,
+ name->NameLength);
+
+ status = STATUS_SUCCESS;
+ Irp->IoStatus.Information = sizeof(USHORT) + name->NameLength;
+ break;
+ }
+
+ case IOCTL_MOUNTDEV_QUERY_SUGGESTED_LINK_NAME: {
+
+ PMOUNTDEV_SUGGESTED_LINK_NAME suggestedName;
+ WCHAR driveLetterNameBuffer[10] = {0};
+ RTL_QUERY_REGISTRY_TABLE queryTable[2] = {0};
+ PWSTR valueName;
+ UNICODE_STRING driveLetterName;
+
+ if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
+ sizeof(MOUNTDEV_SUGGESTED_LINK_NAME)) {
+
+ status = STATUS_BUFFER_TOO_SMALL;
+ Irp->IoStatus.Information = sizeof(MOUNTDEV_SUGGESTED_LINK_NAME);
+ break;
+ }
+
+ valueName = ExAllocatePoolWithTag(
+ PagedPool,
+ commonExtension->DeviceName.Length + sizeof(WCHAR),
+ '8CcS');
+
+ if (!valueName) {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ break;
+ }
+
+ RtlCopyMemory(valueName, commonExtension->DeviceName.Buffer,
+ commonExtension->DeviceName.Length);
+ valueName[commonExtension->DeviceName.Length/sizeof(WCHAR)] = 0;
+
+ driveLetterName.Buffer = driveLetterNameBuffer;
+ driveLetterName.MaximumLength = sizeof(driveLetterNameBuffer);
+ driveLetterName.Length = 0;
+
+ queryTable[0].Flags = RTL_QUERY_REGISTRY_REQUIRED |
+ RTL_QUERY_REGISTRY_DIRECT;
+ queryTable[0].Name = valueName;
+ queryTable[0].EntryContext = &driveLetterName;
+
+ status = RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE,
+ L"\\Registry\\Machine\\System\\DISK",
+ queryTable, NULL, NULL);
+
+ if (!NT_SUCCESS(status)) {
+ FREE_POOL(valueName);
+ break;
+ }
+
+ if (driveLetterName.Length == 4 &&
+ driveLetterName.Buffer[0] == '%' &&
+ driveLetterName.Buffer[1] == ':') {
+
+ driveLetterName.Buffer[0] = 0xFF;
+
+ } else if (driveLetterName.Length != 4 ||
+ driveLetterName.Buffer[0] < FirstDriveLetter ||
+ driveLetterName.Buffer[0] > LastDriveLetter ||
+ driveLetterName.Buffer[1] != ':') {
+
+ status = STATUS_NOT_FOUND;
+ FREE_POOL(valueName);
+ break;
+ }
+
+ suggestedName = Irp->AssociatedIrp.SystemBuffer;
+ RtlZeroMemory(suggestedName, sizeof(MOUNTDEV_SUGGESTED_LINK_NAME));
+ suggestedName->UseOnlyIfThereAreNoOtherLinks = TRUE;
+ suggestedName->NameLength = 28;
+
+ Irp->IoStatus.Information =
+ FIELD_OFFSET(MOUNTDEV_SUGGESTED_LINK_NAME, Name) + 28;
+
+ if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
+ Irp->IoStatus.Information) {
+
+ Irp->IoStatus.Information =
+ sizeof(MOUNTDEV_SUGGESTED_LINK_NAME);
+ status = STATUS_BUFFER_OVERFLOW;
+ FREE_POOL(valueName);
+ break;
+ }
+
+ RtlDeleteRegistryValue(RTL_REGISTRY_ABSOLUTE,
+ L"\\Registry\\Machine\\System\\DISK",
+ valueName);
+
+ FREE_POOL(valueName);
+
+ RtlCopyMemory(suggestedName->Name, L"\\DosDevices\\", 24);
+ suggestedName->Name[12] = driveLetterName.Buffer[0];
+ suggestedName->Name[13] = ':';
+
+ //
+ // NT_SUCCESS(status) based on RtlQueryRegistryValues
+ //
+ status = STATUS_SUCCESS;
+
+ break;
+ }
+
+ default:
+ status = STATUS_PENDING;
+ break;
+ }
+
+ if (status != STATUS_PENDING) {
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ Irp->IoStatus.Status = status;
+
+
+ IoCompleteRequest(Irp, IO_NO_INCREMENT);
+ return status;
+ }
+
+ if (commonExtension->IsFdo){
+
+ PULONG_PTR function;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)commonExtension;
+ size_t sizeNeeded;
+
+ //
+ // Allocate a SCSI SRB for handling various IOCTLs.
+ // NOTE - there is a case where an IOCTL is sent to classpnp before AdapterDescriptor
+ // is initialized. In this case, default to legacy SRB.
+ //
+ if ((fdoExtension->AdapterDescriptor != NULL) &&
+ (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK)) {
+ sizeNeeded = CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE;
+ } else {
+ sizeNeeded = sizeof(SCSI_REQUEST_BLOCK);
+ }
+
+ srb = ExAllocatePoolWithTag(NonPagedPoolNx,
+ sizeNeeded +
+ (sizeof(ULONG_PTR) * 2),
+ '9CcS');
+
+ if (srb == NULL) {
+
+ Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto SetStatusAndReturn;
+ }
+
+ if ((fdoExtension->AdapterDescriptor != NULL) &&
+ (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK)) {
+ status = InitializeStorageRequestBlock((PSTORAGE_REQUEST_BLOCK)srb,
+ STORAGE_ADDRESS_TYPE_BTL8,
+ CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE,
+ 1,
+ SrbExDataTypeScsiCdb16);
+ if (NT_SUCCESS(status)) {
+ ((PSTORAGE_REQUEST_BLOCK)srb)->SrbFunction = SRB_FUNCTION_EXECUTE_SCSI;
+ function = (PULONG_PTR)((PCHAR)srb + sizeNeeded);
+ } else {
+ //
+ // Should not occur.
+ //
+ NT_ASSERT(FALSE);
+ goto SetStatusAndReturn;
+ }
+ } else {
+ RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK));
+ srb->Length = sizeof(SCSI_REQUEST_BLOCK);
+ srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
+ function = (PULONG_PTR) ((PSCSI_REQUEST_BLOCK) (srb + 1));
+ }
+
+ //
+ // Save the function code and the device object in the memory after
+ // the SRB.
+ //
+
+ *function = (ULONG_PTR) DeviceObject;
+ function++;
+ *function = (ULONG_PTR) controlCode;
+
+ } else {
+ srb = NULL;
+ }
+
+ //
+ // Change the device type to storage for the switch statement, but only
+ // if from a legacy device type
+ //
+
+ if (((controlCode & 0xffff0000) == (IOCTL_DISK_BASE << 16)) ||
+ ((controlCode & 0xffff0000) == (IOCTL_TAPE_BASE << 16)) ||
+ ((controlCode & 0xffff0000) == (IOCTL_CDROM_BASE << 16))
+ ) {
+
+ modifiedIoControlCode = (controlCode & ~0xffff0000);
+ modifiedIoControlCode |= (IOCTL_STORAGE_BASE << 16);
+
+ } else {
+
+ modifiedIoControlCode = controlCode;
+
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_GENERAL, "> ioctl %xh (%s)", modifiedIoControlCode, DBGGETIOCTLSTR(modifiedIoControlCode)));
+
+
+ switch (modifiedIoControlCode) {
+
+ case IOCTL_STORAGE_GET_HOTPLUG_INFO: {
+
+ FREE_POOL(srb);
+
+ if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
+ sizeof(STORAGE_HOTPLUG_INFO)) {
+
+ //
+ // Indicate unsuccessful status and no data transferred.
+ //
+
+ Irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL;
+ Irp->IoStatus.Information = sizeof(STORAGE_HOTPLUG_INFO);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ status = STATUS_BUFFER_TOO_SMALL;
+
+ } else if (!commonExtension->IsFdo) {
+
+
+ //
+ // Just forward this down and return
+ //
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+
+ } else {
+
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
+ PSTORAGE_HOTPLUG_INFO info;
+
+ fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)commonExtension;
+ info = Irp->AssociatedIrp.SystemBuffer;
+
+ *info = fdoExtension->PrivateFdoData->HotplugInfo;
+ Irp->IoStatus.Status = STATUS_SUCCESS;
+ Irp->IoStatus.Information = sizeof(STORAGE_HOTPLUG_INFO);
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ status = STATUS_SUCCESS;
+ }
+ break;
+ }
+
+ case IOCTL_STORAGE_SET_HOTPLUG_INFO: {
+
+ FREE_POOL(srb);
+
+ if (irpStack->Parameters.DeviceIoControl.InputBufferLength <
+ sizeof(STORAGE_HOTPLUG_INFO)) {
+
+ //
+ // Indicate unsuccessful status and no data transferred.
+ //
+
+ Irp->IoStatus.Status = STATUS_INFO_LENGTH_MISMATCH;
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ status = STATUS_INFO_LENGTH_MISMATCH;
+ goto SetStatusAndReturn;
+
+ }
+
+ if (!commonExtension->IsFdo) {
+
+
+ //
+ // Just forward this down and return
+ //
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+
+ } else {
+
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)commonExtension;
+ PSTORAGE_HOTPLUG_INFO info = Irp->AssociatedIrp.SystemBuffer;
+
+ status = STATUS_SUCCESS;
+
+ if (info->Size != fdoExtension->PrivateFdoData->HotplugInfo.Size)
+ {
+ status = STATUS_INVALID_PARAMETER_1;
+ }
+
+ if (info->MediaRemovable != fdoExtension->PrivateFdoData->HotplugInfo.MediaRemovable)
+ {
+ status = STATUS_INVALID_PARAMETER_2;
+ }
+
+ if (info->MediaHotplug != fdoExtension->PrivateFdoData->HotplugInfo.MediaHotplug)
+ {
+ status = STATUS_INVALID_PARAMETER_3;
+ }
+
+ if (NT_SUCCESS(status))
+ {
+ if (info->WriteCacheEnableOverride != fdoExtension->PrivateFdoData->HotplugInfo.WriteCacheEnableOverride)
+ {
+ fdoExtension->PrivateFdoData->HotplugInfo.WriteCacheEnableOverride = info->WriteCacheEnableOverride;
+
+ //
+ // Store the user-defined override in the registry
+ //
+
+ ClassSetDeviceParameter(fdoExtension,
+ CLASSP_REG_SUBKEY_NAME,
+ CLASSP_REG_WRITE_CACHE_VALUE_NAME,
+ info->WriteCacheEnableOverride);
+ }
+
+ fdoExtension->PrivateFdoData->HotplugInfo.DeviceHotplug = info->DeviceHotplug;
+
+ //
+ // Store the user-defined override in the registry
+ //
+
+ ClassSetDeviceParameter(fdoExtension,
+ CLASSP_REG_SUBKEY_NAME,
+ CLASSP_REG_REMOVAL_POLICY_VALUE_NAME,
+ (info->DeviceHotplug) ? RemovalPolicyExpectSurpriseRemoval : RemovalPolicyExpectOrderlyRemoval);
+ }
+
+ Irp->IoStatus.Status = status;
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ }
+
+ break;
+ }
+
+ case IOCTL_STORAGE_CHECK_VERIFY:
+ case IOCTL_STORAGE_CHECK_VERIFY2: {
+
+ PIRP irp2 = NULL;
+ PIO_STACK_LOCATION newStack;
+
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = NULL;
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "DeviceIoControl: Check verify\n"));
+
+ //
+ // If a buffer for a media change count was provided, make sure it's
+ // big enough to hold the result
+ //
+
+ if (irpStack->Parameters.DeviceIoControl.OutputBufferLength) {
+
+ //
+ // If the buffer is too small to hold the media change count
+ // then return an error to the caller
+ //
+
+ if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
+ sizeof(ULONG)) {
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, "DeviceIoControl: media count "
+ "buffer too small\n"));
+
+ Irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL;
+ Irp->IoStatus.Information = sizeof(ULONG);
+
+ FREE_POOL(srb);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+
+ status = STATUS_BUFFER_TOO_SMALL;
+ goto SetStatusAndReturn;
+ }
+ }
+
+ if (!commonExtension->IsFdo) {
+
+
+ //
+ // If this is a PDO then we should just forward the request down
+ //
+ NT_ASSERT(!srb);
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+
+ goto SetStatusAndReturn;
+
+ } else {
+
+ fdoExtension = DeviceObject->DeviceExtension;
+
+ }
+
+ if (irpStack->Parameters.DeviceIoControl.OutputBufferLength) {
+
+ //
+ // The caller has provided a valid buffer. Allocate an additional
+ // irp and stick the CheckVerify completion routine on it. We will
+ // then send this down to the port driver instead of the irp the
+ // caller sent in
+ //
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "DeviceIoControl: Check verify wants "
+ "media count\n"));
+
+ //
+ // Allocate a new irp to send the TestUnitReady to the port driver
+ //
+
+ irp2 = IoAllocateIrp((CCHAR) (DeviceObject->StackSize + 3), FALSE);
+
+ if (irp2 == NULL) {
+ Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
+ Irp->IoStatus.Information = 0;
+ FREE_POOL(srb);
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto SetStatusAndReturn;
+
+ break;
+ }
+
+ //
+ // Make sure to acquire the lock for the new irp.
+ //
+
+ ClassAcquireRemoveLock(DeviceObject, irp2);
+
+ irp2->Tail.Overlay.Thread = Irp->Tail.Overlay.Thread;
+ IoSetNextIrpStackLocation(irp2);
+
+ //
+ // Set the top stack location and shove the master Irp into the
+ // top location
+ //
+
+ newStack = IoGetCurrentIrpStackLocation(irp2);
+ newStack->Parameters.Others.Argument1 = Irp;
+ newStack->DeviceObject = DeviceObject;
+
+ //
+ // Stick the check verify completion routine onto the stack
+ // and prepare the irp for the port driver
+ //
+
+ IoSetCompletionRoutine(irp2,
+ ClassCheckVerifyComplete,
+ NULL,
+ TRUE,
+ TRUE,
+ TRUE);
+
+ IoSetNextIrpStackLocation(irp2);
+ newStack = IoGetCurrentIrpStackLocation(irp2);
+ newStack->DeviceObject = DeviceObject;
+ newStack->MajorFunction = irpStack->MajorFunction;
+ newStack->MinorFunction = irpStack->MinorFunction;
+ newStack->Flags = irpStack->Flags;
+
+
+ //
+ // Mark the master irp as pending - whether the lower level
+ // driver completes it immediately or not this should allow it
+ // to go all the way back up.
+ //
+
+ IoMarkIrpPending(Irp);
+
+ Irp = irp2;
+
+ }
+
+ //
+ // Test Unit Ready
+ //
+
+ SrbSetCdbLength(srb, 6);
+ cdb = SrbGetCdb(srb);
+ cdb->CDB6GENERIC.OperationCode = SCSIOP_TEST_UNIT_READY;
+
+ //
+ // Set timeout value.
+ //
+
+ SrbSetTimeOutValue(srb, fdoExtension->TimeOutValue);
+
+ //
+ // If this was a CV2 then mark the request as low-priority so we don't
+ // spin up the drive just to satisfy it.
+ //
+
+ if (controlCode == IOCTL_STORAGE_CHECK_VERIFY2) {
+ SrbSetSrbFlags(srb, SRB_CLASS_FLAGS_LOW_PRIORITY);
+ }
+
+ //
+ // Since this routine will always hand the request to the
+ // port driver if there isn't a data transfer to be done
+ // we don't have to worry about completing the request here
+ // on an error
+ //
+
+ //
+ // This routine uses a completion routine so we don't want to release
+ // the remove lock until then.
+ //
+
+ status = ClassSendSrbAsynchronous(DeviceObject,
+ srb,
+ Irp,
+ NULL,
+ 0,
+ FALSE);
+
+ break;
+ }
+
+ case IOCTL_STORAGE_MEDIA_REMOVAL:
+ case IOCTL_STORAGE_EJECTION_CONTROL: {
+
+ PPREVENT_MEDIA_REMOVAL mediaRemoval = Irp->AssociatedIrp.SystemBuffer;
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "DiskIoControl: ejection control\n"));
+
+ FREE_POOL(srb);
+
+ if (irpStack->Parameters.DeviceIoControl.InputBufferLength <
+ sizeof(PREVENT_MEDIA_REMOVAL)) {
+
+ //
+ // Indicate unsuccessful status and no data transferred.
+ //
+
+ Irp->IoStatus.Status = STATUS_INFO_LENGTH_MISMATCH;
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ status = STATUS_INFO_LENGTH_MISMATCH;
+ goto SetStatusAndReturn;
+ }
+
+ if (!commonExtension->IsFdo) {
+
+
+ //
+ // Just forward this down and return
+ //
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ }
+ else {
+
+ // i don't believe this assertion is valid. this is a request
+ // from user-mode, so they could request this for any device
+ // they want? also, we handle it properly.
+ // NT_ASSERT(TEST_FLAG(DeviceObject->Characteristics, FILE_REMOVABLE_MEDIA));
+ status = ClasspEjectionControl(
+ DeviceObject,
+ Irp,
+ ((modifiedIoControlCode ==
+ IOCTL_STORAGE_EJECTION_CONTROL) ? SecureMediaLock :
+ SimpleMediaLock),
+ mediaRemoval->PreventMediaRemoval);
+
+ Irp->IoStatus.Status = status;
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ }
+
+ break;
+ }
+
+ case IOCTL_STORAGE_MCN_CONTROL: {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "DiskIoControl: MCN control\n"));
+
+ if (irpStack->Parameters.DeviceIoControl.InputBufferLength <
+ sizeof(PREVENT_MEDIA_REMOVAL)) {
+
+ //
+ // Indicate unsuccessful status and no data transferred.
+ //
+
+ Irp->IoStatus.Status = STATUS_INFO_LENGTH_MISMATCH;
+ Irp->IoStatus.Information = 0;
+
+ FREE_POOL(srb);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ status = STATUS_INFO_LENGTH_MISMATCH;
+ goto SetStatusAndReturn;
+ }
+
+ if (!commonExtension->IsFdo) {
+
+
+ //
+ // Just forward this down and return
+ //
+
+ FREE_POOL(srb);
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+
+ } else {
+
+ //
+ // Call to the FDO - handle the ejection control.
+ //
+
+ status = ClasspMcnControl(DeviceObject->DeviceExtension,
+ Irp,
+ srb);
+ }
+ goto SetStatusAndReturn;
+ }
+
+ case IOCTL_STORAGE_RESERVE:
+ case IOCTL_STORAGE_RELEASE: {
+
+ //
+ // Reserve logical unit.
+ //
+
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = NULL;
+
+ if (!commonExtension->IsFdo) {
+
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ goto SetStatusAndReturn;
+
+ } else {
+ fdoExtension = DeviceObject->DeviceExtension;
+ }
+
+ if (TEST_FLAG(fdoExtension->PrivateFdoData->HackFlags, FDO_HACK_NO_RESERVE6))
+ {
+ SrbSetCdbLength(srb, 10);
+ cdb = SrbGetCdb(srb);
+ cdb->CDB10.OperationCode = (modifiedIoControlCode == IOCTL_STORAGE_RESERVE) ? SCSIOP_RESERVE_UNIT10 : SCSIOP_RELEASE_UNIT10;
+ }
+ else
+ {
+ SrbSetCdbLength(srb, 6);
+ cdb = SrbGetCdb(srb);
+ cdb->CDB6GENERIC.OperationCode = (modifiedIoControlCode == IOCTL_STORAGE_RESERVE) ? SCSIOP_RESERVE_UNIT : SCSIOP_RELEASE_UNIT;
+ }
+
+ //
+ // Set timeout value.
+ //
+
+ SrbSetTimeOutValue(srb, fdoExtension->TimeOutValue);
+
+ //
+ // Send reserves as tagged requests.
+ //
+
+ if ( IOCTL_STORAGE_RESERVE == modifiedIoControlCode ) {
+ SrbSetSrbFlags(srb, SRB_FLAGS_QUEUE_ACTION_ENABLE);
+ SrbSetRequestAttribute(srb, SRB_SIMPLE_TAG_REQUEST);
+ }
+
+ status = ClassSendSrbAsynchronous(DeviceObject,
+ srb,
+ Irp,
+ NULL,
+ 0,
+ FALSE);
+
+ break;
+ }
+
+ case IOCTL_STORAGE_PERSISTENT_RESERVE_IN:
+ case IOCTL_STORAGE_PERSISTENT_RESERVE_OUT: {
+
+ if (!commonExtension->IsFdo) {
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ goto SetStatusAndReturn;
+ }
+
+ //
+ // Process Persistent Reserve
+ //
+
+ status = ClasspPersistentReserve(DeviceObject, Irp, srb);
+
+ break;
+
+ }
+
+ case IOCTL_STORAGE_EJECT_MEDIA:
+ case IOCTL_STORAGE_LOAD_MEDIA:
+ case IOCTL_STORAGE_LOAD_MEDIA2:{
+
+ //
+ // Eject media.
+ //
+
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = NULL;
+
+ if (!commonExtension->IsFdo) {
+
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ goto SetStatusAndReturn;
+ } else {
+ fdoExtension = DeviceObject->DeviceExtension;
+ }
+
+ if (commonExtension->PagingPathCount != 0) {
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, "ClassDeviceControl: call to eject paging device - "
+ "failure\n"));
+
+ status = STATUS_FILES_OPEN;
+ Irp->IoStatus.Status = status;
+
+ Irp->IoStatus.Information = 0;
+
+ FREE_POOL(srb);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ goto SetStatusAndReturn;
+ }
+
+ //
+ // Synchronize with ejection control and ejection cleanup code as
+ // well as other eject/load requests.
+ //
+
+ KeEnterCriticalRegion();
+ (VOID)KeWaitForSingleObject(&(fdoExtension->EjectSynchronizationEvent),
+ UserRequest,
+ KernelMode,
+ FALSE,
+ NULL);
+
+ if (fdoExtension->ProtectedLockCount != 0) {
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, "ClassDeviceControl: call to eject protected locked "
+ "device - failure\n"));
+
+ status = STATUS_DEVICE_BUSY;
+ Irp->IoStatus.Status = status;
+ Irp->IoStatus.Information = 0;
+
+ FREE_POOL(srb);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+
+ KeSetEvent(&fdoExtension->EjectSynchronizationEvent,
+ IO_NO_INCREMENT,
+ FALSE);
+ KeLeaveCriticalRegion();
+
+ goto SetStatusAndReturn;
+ }
+
+ SrbSetCdbLength(srb, 6);
+ cdb = SrbGetCdb(srb);
+
+ cdb->START_STOP.OperationCode = SCSIOP_START_STOP_UNIT;
+ cdb->START_STOP.LoadEject = 1;
+
+ if (modifiedIoControlCode == IOCTL_STORAGE_EJECT_MEDIA) {
+ cdb->START_STOP.Start = 0;
+ } else {
+ cdb->START_STOP.Start = 1;
+ }
+
+ //
+ // Set timeout value.
+ //
+
+ SrbSetTimeOutValue(srb, fdoExtension->TimeOutValue);
+ status = ClassSendSrbAsynchronous(DeviceObject,
+ srb,
+ Irp,
+ NULL,
+ 0,
+ FALSE);
+
+ KeSetEvent(&fdoExtension->EjectSynchronizationEvent, IO_NO_INCREMENT, FALSE);
+ KeLeaveCriticalRegion();
+
+ break;
+ }
+
+ case IOCTL_STORAGE_FIND_NEW_DEVICES: {
+
+ FREE_POOL(srb);
+
+ if (commonExtension->IsFdo) {
+
+ IoInvalidateDeviceRelations(
+ ((PFUNCTIONAL_DEVICE_EXTENSION) commonExtension)->LowerPdo,
+ BusRelations);
+
+ status = STATUS_SUCCESS;
+ Irp->IoStatus.Status = status;
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ }
+ else {
+
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ }
+ break;
+ }
+
+ case IOCTL_STORAGE_GET_DEVICE_NUMBER: {
+
+ FREE_POOL(srb);
+
+ if (irpStack->Parameters.DeviceIoControl.OutputBufferLength >=
+ sizeof(STORAGE_DEVICE_NUMBER)) {
+
+ PSTORAGE_DEVICE_NUMBER deviceNumber =
+ Irp->AssociatedIrp.SystemBuffer;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension =
+ commonExtension->PartitionZeroExtension;
+
+ deviceNumber->DeviceType = fdoExtension->CommonExtension.DeviceObject->DeviceType;
+ deviceNumber->DeviceNumber = fdoExtension->DeviceNumber;
+ deviceNumber->PartitionNumber = commonExtension->PartitionNumber;
+
+ status = STATUS_SUCCESS;
+ Irp->IoStatus.Information = sizeof(STORAGE_DEVICE_NUMBER);
+
+ } else {
+ status = STATUS_BUFFER_TOO_SMALL;
+ Irp->IoStatus.Information = sizeof(STORAGE_DEVICE_NUMBER);
+ }
+
+ Irp->IoStatus.Status = status;
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+
+ break;
+ }
+
+
+ case IOCTL_STORAGE_READ_CAPACITY: {
+
+ FREE_POOL(srb);
+
+ if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
+ sizeof(STORAGE_READ_CAPACITY)) {
+
+ //
+ // Indicate unsuccessful status and no data transferred.
+ //
+
+ Irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL;
+ Irp->IoStatus.Information = sizeof(STORAGE_READ_CAPACITY);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ status = STATUS_BUFFER_TOO_SMALL;
+ break;
+ }
+
+ if (!commonExtension->IsFdo) {
+
+
+ //
+ // Just forward this down and return
+ //
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ }
+ else {
+
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt = DeviceObject->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
+ PSTORAGE_READ_CAPACITY readCapacity = Irp->AssociatedIrp.SystemBuffer;
+ LARGE_INTEGER diskLength;
+
+ status = ClassReadDriveCapacity(DeviceObject);
+ if (NT_SUCCESS(status) && fdoData->IsCachedDriveCapDataValid) {
+
+ readCapacity->Version = sizeof(STORAGE_READ_CAPACITY);
+ readCapacity->Size = sizeof(STORAGE_READ_CAPACITY);
+
+ REVERSE_BYTES(&readCapacity->BlockLength,
+ &fdoData->LastKnownDriveCapacityData.BytesPerBlock);
+ REVERSE_BYTES_QUAD(&readCapacity->NumberOfBlocks,
+ &fdoData->LastKnownDriveCapacityData.LogicalBlockAddress);
+ readCapacity->NumberOfBlocks.QuadPart++;
+
+ readCapacity->DiskLength = fdoExt->CommonExtension.PartitionLength;
+
+ //
+ // Make sure the lengths are equal.
+ // Remove this after testing.
+ //
+ diskLength.QuadPart = readCapacity->NumberOfBlocks.QuadPart *
+ readCapacity->BlockLength;
+
+ Irp->IoStatus.Status = STATUS_SUCCESS;
+ Irp->IoStatus.Information = sizeof(STORAGE_READ_CAPACITY);
+
+ } else {
+ //
+ // Read capacity request failed.
+ //
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, "ClassDeviceControl: ClassReadDriveCapacity failed: 0x%X IsCachedDriveCapDataValid: %d\n",
+ status, fdoData->IsCachedDriveCapDataValid));
+ Irp->IoStatus.Status = status;
+ Irp->IoStatus.Information = 0;
+ }
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ }
+
+ break;
+ }
+
+ case IOCTL_STORAGE_QUERY_PROPERTY: {
+
+ PSTORAGE_PROPERTY_QUERY query = Irp->AssociatedIrp.SystemBuffer;
+
+ if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(STORAGE_PROPERTY_QUERY)) {
+
+ status = STATUS_INFO_LENGTH_MISMATCH;
+ Irp->IoStatus.Status = status;
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ FREE_POOL(srb);
+ break;
+ }
+
+ if (!commonExtension->IsFdo) {
+
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ FREE_POOL(srb);
+ break;
+ }
+
+ //
+ // Determine PropertyId type and either call appropriate routine
+ // or pass request to lower drivers.
+ //
+
+ switch ( query->PropertyId ) {
+
+ case StorageDeviceUniqueIdProperty: {
+
+ status = ClasspDuidQueryProperty(DeviceObject, Irp);
+ break;
+ }
+
+ case StorageDeviceWriteCacheProperty: {
+
+ status = ClasspWriteCacheProperty(DeviceObject, Irp, srb);
+ break;
+ }
+
+ // these propertyId has been implemented in some port driver and filter drivers.
+ // to keep the backwards compatibility, classpnp will send the request down if it's supported by lower layer.
+ // otherwise, classpnp sends SCSI command and then interprets the result.
+ case StorageAccessAlignmentProperty: {
+
+ status = ClasspAccessAlignmentProperty(DeviceObject, Irp, srb);
+ break;
+ }
+
+ case StorageDeviceSeekPenaltyProperty: {
+
+ status = ClasspDeviceSeekPenaltyProperty(DeviceObject, Irp, srb);
+ break;
+ }
+
+ case StorageDeviceTrimProperty: {
+
+ status = ClasspDeviceTrimProperty(DeviceObject, Irp, srb);
+ break;
+ }
+
+ case StorageDeviceLBProvisioningProperty: {
+
+ status = ClasspDeviceLBProvisioningProperty(DeviceObject, Irp, srb);
+ break;
+ }
+
+ case StorageDeviceCopyOffloadProperty: {
+
+ status = ClasspDeviceCopyOffloadProperty(DeviceObject, Irp, srb);
+ break;
+ }
+
+ case StorageDeviceMediumProductType: {
+
+ status = ClasspDeviceMediaTypeProperty(DeviceObject, Irp, srb);
+ break;
+ }
+
+ default: {
+
+ //
+ // Copy the Irp stack parameters to the next stack location.
+ //
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ break;
+ }
+ } // end switch
+
+ FREE_POOL(srb);
+ break;
+ }
+
+ case IOCTL_STORAGE_CHECK_PRIORITY_HINT_SUPPORT: {
+
+ FREE_POOL(srb);
+
+ if (!commonExtension->IsFdo) {
+
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ break;
+ }
+
+ //
+ // Process priority hit request
+ //
+
+ status = ClasspPriorityHint(DeviceObject, Irp);
+ break;
+ }
+
+ case IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES: {
+
+ PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes = Irp->AssociatedIrp.SystemBuffer;
+
+ if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES)) {
+
+ status = STATUS_INVALID_PARAMETER;
+ Irp->IoStatus.Status = status;
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ FREE_POOL(srb);
+ break;
+ }
+
+ if (irpStack->Parameters.DeviceIoControl.InputBufferLength <
+ (sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES) + dsmAttributes->ParameterBlockLength + dsmAttributes->DataSetRangesLength)) {
+
+ status = STATUS_INVALID_PARAMETER;
+ Irp->IoStatus.Status = status;
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ FREE_POOL(srb);
+ break;
+ }
+
+ if (!commonExtension->IsFdo) {
+
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ FREE_POOL(srb);
+ break;
+ }
+
+ switch(dsmAttributes->Action) {
+
+ // only process Trim action in class layer if possible.
+ case DeviceDsmAction_Trim: {
+ status = ClasspDeviceTrimProcess(DeviceObject, Irp, srb);
+ break;
+ }
+
+ case DeviceDsmAction_OffloadRead: {
+ status = ClassDeviceProcessOffloadRead(DeviceObject, Irp, srb);
+ break;
+ }
+
+ case DeviceDsmAction_OffloadWrite: {
+ status = ClassDeviceProcessOffloadWrite(DeviceObject, Irp, srb);
+ break;
+ }
+
+ case DeviceDsmAction_Allocation: {
+ status = ClasspDeviceGetLBAStatus(DeviceObject, Irp, srb);
+ break;
+ }
+
+
+ default: {
+
+
+ //
+ // Copy the Irp stack parameters to the next stack location.
+ //
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ break;
+ }
+ } // end switch
+
+ FREE_POOL(srb);
+ break;
+ }
+
+ case IOCTL_STORAGE_GET_LB_PROVISIONING_MAP_RESOURCES: {
+
+ if (commonExtension->IsFdo) {
+
+ status = ClassDeviceGetLBProvisioningResources(DeviceObject, Irp, srb);
+
+ } else {
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ }
+
+ FREE_POOL(srb);
+
+ break;
+ }
+
+ case IOCTL_STORAGE_EVENT_NOTIFICATION: {
+
+ FREE_POOL(srb);
+
+ status = ClasspStorageEventNotification(DeviceObject, Irp);
+ break;
+ }
+
+#if (NTDDI_VERSION >= NTDDI_WINTRHESHOLD)
+ case IOCTL_STORAGE_FIRMWARE_GET_INFO: {
+ FREE_POOL(srb);
+
+ status = ClassDeviceHwFirmwareGetInfoProcess(DeviceObject, Irp);
+ break;
+ }
+
+ case IOCTL_STORAGE_FIRMWARE_DOWNLOAD: {
+ if (!commonExtension->IsFdo) {
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ goto SetStatusAndReturn;
+ }
+
+ status = ClassDeviceHwFirmwareDownloadProcess(DeviceObject, Irp, srb);
+ break;
+ }
+
+ case IOCTL_STORAGE_FIRMWARE_ACTIVATE: {
+ if (!commonExtension->IsFdo) {
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ goto SetStatusAndReturn;
+ }
+
+ status = ClassDeviceHwFirmwareActivateProcess(DeviceObject, Irp, srb);
+ break;
+ }
+#endif
+
+
+ default: {
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_IOCTL, "IoDeviceControl: Unsupported device IOCTL %x for %p\n",
+ controlCode, DeviceObject));
+
+
+ //
+ // Pass the device control to the next driver.
+ //
+
+ FREE_POOL(srb);
+
+ //
+ // Copy the Irp stack parameters to the next stack location.
+ //
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ break;
+ }
+
+ } // end switch( ...
+
+SetStatusAndReturn:
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "< ioctl %xh (%s): status %xh.", modifiedIoControlCode, DBGGETIOCTLSTR(modifiedIoControlCode), status));
+
+ return status;
+} // end ClassDeviceControl()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassShutdownFlush()
+
+Routine Description:
+
+ This routine is called for a shutdown and flush IRPs. These are sent by the
+ system before it actually shuts down or when the file system does a flush.
+ If it exists, the device-specific driver's routine will be invoked. If there
+ wasn't one specified, the Irp will be completed with an Invalid device request.
+
+Arguments:
+
+ DriverObject - Pointer to device object to being shutdown by system.
+
+ Irp - IRP involved.
+
+Return Value:
+
+ NT Status
+
+--*/
+NTSTATUS
+ClassShutdownFlush(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp
+ )
+{
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+
+ ULONG isRemoved;
+
+ isRemoved = ClassAcquireRemoveLock(DeviceObject, Irp);
+ _Analysis_assume_(isRemoved);
+ if(isRemoved) {
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+
+ Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST;
+
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+
+ return STATUS_DEVICE_DOES_NOT_EXIST;
+ }
+
+ if (commonExtension->DevInfo->ClassShutdownFlush) {
+
+ //
+ // Call the device-specific driver's routine.
+ //
+
+ return commonExtension->DevInfo->ClassShutdownFlush(DeviceObject, Irp);
+ }
+
+ //
+ // Device-specific driver doesn't support this.
+ //
+
+ Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+
+ return STATUS_INVALID_DEVICE_REQUEST;
+} // end ClassShutdownFlush()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClasspIsPortable()
+
+Routine Description:
+
+ This routine is called during start device to determine whether the PDO
+ for a stack reports itself as portable.
+
+Arguments:
+
+ FdoExtension - Pointer to FDO whose PDO we check for portability.
+
+ IsPortable - Boolean pointer in which to store result.
+
+Return Value:
+
+ NT Status
+
+--*/
+NTSTATUS
+ClasspIsPortable(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ _Out_ PBOOLEAN IsPortable
+ )
+{
+ DEVPROP_BOOLEAN isInternal = DEVPROP_FALSE;
+ BOOLEAN isPortable = FALSE;
+ ULONG size = 0;
+ NTSTATUS status = STATUS_SUCCESS;
+ DEVPROPTYPE type = DEVPROP_TYPE_EMPTY;
+
+ PAGED_CODE();
+
+ *IsPortable = FALSE;
+
+ //
+ // Check to see if the underlying device
+ // object is in local machine container
+ //
+
+ status = IoGetDevicePropertyData(FdoExtension->LowerPdo,
+ &DEVPKEY_Device_InLocalMachineContainer,
+ 0,
+ 0,
+ sizeof(isInternal),
+ &isInternal,
+ &size,
+ &type);
+
+ if (!NT_SUCCESS(status)) {
+ goto cleanup;
+ }
+
+ NT_ASSERT(size == sizeof(isInternal));
+ NT_ASSERT(type == DEVPROP_TYPE_BOOLEAN);
+
+ //
+ // Volume is hot-pluggable if the disk pdo
+ // container id differs from that of root device
+ //
+
+ if (isInternal == DEVPROP_TRUE) {
+ goto cleanup;
+ }
+
+ isPortable = TRUE;
+
+ //
+ // Examine the bus type to ensure
+ // that this really is a fixed disk
+ //
+
+ if (FdoExtension->DeviceDescriptor->BusType == BusTypeFibre ||
+ FdoExtension->DeviceDescriptor->BusType == BusTypeiScsi ||
+ FdoExtension->DeviceDescriptor->BusType == BusTypeRAID) {
+
+ isPortable = FALSE;
+ }
+
+ *IsPortable = isPortable;
+
+cleanup:
+
+ return status;
+}
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassCreateDeviceObject()
+
+Routine Description:
+
+ This routine creates an object for the physical device specified and
+ sets up the deviceExtension's function pointers for each entry point
+ in the device-specific driver.
+
+Arguments:
+
+ DriverObject - Pointer to driver object created by system.
+
+ ObjectNameBuffer - Dir. name of the object to create.
+
+ LowerDeviceObject - Pointer to the lower device object
+
+ IsFdo - should this be an fdo or a pdo
+
+ DeviceObject - Pointer to the device object pointer we will return.
+
+Return Value:
+
+ NTSTATUS
+
+--*/
+
+_IRQL_requires_max_(PASSIVE_LEVEL)
+_Must_inspect_result_
+_Post_satisfies_(return <= 0)
+SCSIPORT_API
+NTSTATUS
+ClassCreateDeviceObject(
+ _In_ PDRIVER_OBJECT DriverObject,
+ _In_z_ PCCHAR ObjectNameBuffer,
+ _In_ PDEVICE_OBJECT LowerDevice,
+ _In_ BOOLEAN IsFdo,
+ _Outptr_result_nullonfailure_
+ _At_(*DeviceObject, __drv_allocatesMem(Mem) __drv_aliasesMem)
+ PDEVICE_OBJECT *DeviceObject
+ )
+{
+ BOOLEAN isPartitionable;
+ STRING ntNameString;
+ UNICODE_STRING ntUnicodeString;
+ NTSTATUS status;
+ PDEVICE_OBJECT deviceObject = NULL;
+
+ ULONG characteristics;
+ SIZE_T rundownSize = ExSizeOfRundownProtectionCacheAware();
+ PCHAR rundownAddr = NULL;
+ ULONG devExtSize;
+
+#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
+ PCLASS_DRIVER_EXTENSION driverExtension = IoGetDriverObjectExtension(DriverObject, CLASS_DRIVER_EXTENSION_KEY);
+
+ PCLASS_DEV_INFO devInfo;
+
+ PAGED_CODE();
+
+ _Analysis_assume_(driverExtension != NULL);
+
+ *DeviceObject = NULL;
+ RtlInitUnicodeString(&ntUnicodeString, NULL);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "ClassCreateFdo: Create device object\n"));
+
+ NT_ASSERT(LowerDevice);
+
+ //
+ // Make sure that if we're making PDO's we have an enumeration routine
+ //
+
+ isPartitionable = (driverExtension->InitData.ClassEnumerateDevice != NULL);
+
+ NT_ASSERT(IsFdo || isPartitionable);
+
+ //
+ // Grab the correct dev-info structure out of the init data
+ //
+
+ if (IsFdo) {
+ devInfo = &(driverExtension->InitData.FdoData);
+ } else {
+ devInfo = &(driverExtension->InitData.PdoData);
+ }
+
+ characteristics = devInfo->DeviceCharacteristics;
+
+ if (ARGUMENT_PRESENT(ObjectNameBuffer)) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "ClassCreateFdo: Name is %s\n", ObjectNameBuffer));
+
+ RtlInitString(&ntNameString, ObjectNameBuffer);
+
+ status = RtlAnsiStringToUnicodeString(&ntUnicodeString, &ntNameString, TRUE);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT,
+ "ClassCreateFdo: Cannot convert string %s\n",
+ ObjectNameBuffer));
+
+ ntUnicodeString.Buffer = NULL;
+ return status;
+ }
+ } else {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "ClassCreateFdo: Object will be unnamed\n"));
+
+ if (IsFdo == FALSE) {
+
+ //
+ // PDO's have to have some sort of name.
+ //
+
+ SET_FLAG(characteristics, FILE_AUTOGENERATED_DEVICE_NAME);
+ }
+
+ RtlInitUnicodeString(&ntUnicodeString, NULL);
+ }
+
+ devExtSize = devInfo->DeviceExtensionSize +
+ (ULONG)sizeof(CLASS_PRIVATE_COMMON_DATA) + (ULONG)rundownSize;
+ status = IoCreateDevice(DriverObject,
+ devExtSize,
+ &ntUnicodeString,
+ devInfo->DeviceType,
+ characteristics,
+ FALSE,
+ &deviceObject);
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClassCreateFdo: Can not create device object %lx\n",
+ status));
+ NT_ASSERT(deviceObject == NULL);
+
+ //
+ // buffer is not used any longer here.
+ //
+
+ if (ntUnicodeString.Buffer != NULL) {
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, "ClassCreateFdo: Freeing unicode name buffer\n"));
+ FREE_POOL(ntUnicodeString.Buffer);
+ RtlInitUnicodeString(&ntUnicodeString, NULL);
+ }
+
+ } else {
+
+ PCOMMON_DEVICE_EXTENSION commonExtension = deviceObject->DeviceExtension;
+
+ RtlZeroMemory(
+ deviceObject->DeviceExtension,
+ devExtSize);
+
+ //
+ // Setup version code
+ //
+
+ commonExtension->Version = 0x03;
+
+ //
+ // Setup the remove lock and event
+ //
+
+ commonExtension->IsRemoved = NO_REMOVE;
+
+#if DBG
+
+ commonExtension->RemoveLock = 0;
+
+#endif
+
+ KeInitializeEvent(&commonExtension->RemoveEvent,
+ SynchronizationEvent,
+ FALSE);
+
+
+ ClasspInitializeRemoveTracking(deviceObject);
+
+ //
+ // Initialize the PrivateCommonData
+ //
+
+ commonExtension->PrivateCommonData = (PCLASS_PRIVATE_COMMON_DATA)
+ ((PCHAR)deviceObject->DeviceExtension + devInfo->DeviceExtensionSize);
+ rundownAddr = (PCHAR)commonExtension->PrivateCommonData + sizeof(CLASS_PRIVATE_COMMON_DATA);
+ ExInitializeRundownProtectionCacheAware((PEX_RUNDOWN_REF_CACHE_AWARE)rundownAddr, rundownSize);
+ commonExtension->PrivateCommonData->RemoveLockFailAcquire = 0;
+
+ //
+ // Acquire the lock once. This reference will be released when the
+ // remove IRP has been received.
+ //
+
+ ClassAcquireRemoveLock(deviceObject, (PIRP) deviceObject);
+
+ //
+ // Store a pointer to the driver extension so we don't have to do
+ // lookups to get it.
+ //
+
+ commonExtension->DriverExtension = driverExtension;
+
+ //
+ // Fill in entry points
+ //
+
+ commonExtension->DevInfo = devInfo;
+
+ //
+ // Initialize some of the common values in the structure
+ //
+
+ commonExtension->DeviceObject = deviceObject;
+
+ commonExtension->LowerDeviceObject = NULL;
+
+ commonExtension->DispatchTable = driverExtension->DeviceMajorFunctionTable;
+
+ if(IsFdo) {
+
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PVOID) commonExtension;
+
+ commonExtension->PartitionZeroExtension = deviceObject->DeviceExtension;
+
+ //
+ // Set the initial device object flags.
+ //
+
+ SET_FLAG(deviceObject->Flags, DO_POWER_PAGABLE);
+ //
+ // Clear the PDO list
+ //
+
+ commonExtension->ChildList = NULL;
+
+ commonExtension->DriverData =
+ ((PFUNCTIONAL_DEVICE_EXTENSION) deviceObject->DeviceExtension + 1);
+
+ //
+ // The disk class driver creates only FDO. The partition number
+ // for the FDO must be 0.
+ //
+
+ if ((isPartitionable == TRUE) ||
+ (devInfo->DeviceType == FILE_DEVICE_DISK)) {
+
+ commonExtension->PartitionNumber = 0;
+ } else {
+ commonExtension->PartitionNumber = (ULONG) (-1L);
+ }
+
+ fdoExtension->DevicePowerState = PowerDeviceD0;
+
+ KeInitializeEvent(&fdoExtension->EjectSynchronizationEvent,
+ SynchronizationEvent,
+ TRUE);
+
+ KeInitializeEvent(&fdoExtension->ChildLock,
+ SynchronizationEvent,
+ TRUE);
+
+ status = ClasspAllocateReleaseRequest(deviceObject);
+
+ if(!NT_SUCCESS(status)) {
+ IoDeleteDevice(deviceObject);
+ *DeviceObject = NULL;
+
+ if (ntUnicodeString.Buffer != NULL) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "ClassCreateFdo: Freeing unicode name buffer\n"));
+ FREE_POOL(ntUnicodeString.Buffer);
+ RtlInitUnicodeString(&ntUnicodeString, NULL);
+ }
+
+ return status;
+ }
+
+ } else {
+
+ PPHYSICAL_DEVICE_EXTENSION pdoExtension =
+ deviceObject->DeviceExtension;
+
+ PFUNCTIONAL_DEVICE_EXTENSION p0Extension =
+ LowerDevice->DeviceExtension;
+
+ SET_FLAG(deviceObject->Flags, DO_POWER_PAGABLE);
+
+ commonExtension->PartitionZeroExtension = p0Extension;
+
+ //
+ // Stick this onto the PDO list
+ //
+
+ ClassAddChild(p0Extension, pdoExtension, TRUE);
+
+ commonExtension->DriverData = (PVOID) (pdoExtension + 1);
+
+ //
+ // Get the top of stack for the lower device - this allows
+ // filters to get stuck in between the partitions and the
+ // physical disk.
+ //
+
+ commonExtension->LowerDeviceObject =
+ IoGetAttachedDeviceReference(LowerDevice);
+
+ //
+ // Pnp will keep a reference to the lower device object long
+ // after this partition has been deleted. Dereference now so
+ // we don't have to deal with it later.
+ //
+
+ ObDereferenceObject(commonExtension->LowerDeviceObject);
+ }
+
+ KeInitializeEvent(&commonExtension->PathCountEvent, SynchronizationEvent, TRUE);
+
+ commonExtension->IsFdo = IsFdo;
+
+ commonExtension->DeviceName = ntUnicodeString;
+
+ commonExtension->PreviousState = 0xff;
+
+ InitializeDictionary(&(commonExtension->FileObjectDictionary));
+
+ commonExtension->CurrentState = IRP_MN_STOP_DEVICE;
+
+ if (commonExtension->DriverExtension->InitData.ClassStartIo) {
+ IoSetStartIoAttributes(deviceObject, TRUE, TRUE);
+ }
+ }
+
+ *DeviceObject = deviceObject;
+
+ return status;
+} // end ClassCreateDeviceObject()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassClaimDevice()
+
+Routine Description:
+
+ This function claims a device in the port driver. The port driver object
+ is updated with the correct driver object if the device is successfully
+ claimed.
+
+Arguments:
+
+ LowerDeviceObject - Supplies the base port device object.
+
+ Release - Indicates the logical unit should be released rather than claimed.
+
+Return Value:
+
+ Returns a status indicating success or failure of the operation.
+
+--*/
+_IRQL_requires_max_(PASSIVE_LEVEL)
+NTSTATUS
+ClassClaimDevice(
+ _In_ PDEVICE_OBJECT LowerDeviceObject,
+ _In_ BOOLEAN Release
+ )
+{
+ IO_STATUS_BLOCK ioStatus;
+ PIRP irp;
+ PIO_STACK_LOCATION irpStack;
+ KEVENT event;
+ NTSTATUS status;
+ SCSI_REQUEST_BLOCK srb = {0};
+
+ PAGED_CODE();
+
+ //
+ // WORK ITEM - MPIO related. Need to think about how to handle.
+ //
+
+ srb.Length = sizeof(SCSI_REQUEST_BLOCK);
+
+ srb.Function = Release ? SRB_FUNCTION_RELEASE_DEVICE :
+ SRB_FUNCTION_CLAIM_DEVICE;
+
+ //
+ // Set the event object to the unsignaled state.
+ // It will be used to signal request completion
+ //
+
+ KeInitializeEvent(&event, SynchronizationEvent, FALSE);
+
+ //
+ // Build synchronous request with no transfer.
+ //
+
+ irp = IoBuildDeviceIoControlRequest(IOCTL_SCSI_EXECUTE_NONE,
+ LowerDeviceObject,
+ NULL,
+ 0,
+ NULL,
+ 0,
+ TRUE,
+ &event,
+ &ioStatus);
+
+ if (irp == NULL) {
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, "ClassClaimDevice: Can't allocate Irp\n"));
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ irpStack = IoGetNextIrpStackLocation(irp);
+
+ //
+ // Save SRB address in next stack for port driver.
+ //
+
+ irpStack->Parameters.Scsi.Srb = &srb;
+
+ //
+ // Set up IRP Address.
+ //
+
+ srb.OriginalRequest = irp;
+
+ //
+ // Call the port driver with the request and wait for it to complete.
+ //
+
+ status = IoCallDriver(LowerDeviceObject, irp);
+ if (status == STATUS_PENDING) {
+
+ (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
+ status = ioStatus.Status;
+ }
+
+ //
+ // If this is a release request, then just decrement the reference count
+ // and return. The status does not matter.
+ //
+
+ if (Release) {
+
+ // ObDereferenceObject(LowerDeviceObject);
+ return STATUS_SUCCESS;
+ }
+
+ if (!NT_SUCCESS(status)) {
+ return status;
+ }
+
+ NT_ASSERT(srb.DataBuffer != NULL);
+ NT_ASSERT(!TEST_FLAG(srb.SrbFlags, SRB_FLAGS_FREE_SENSE_BUFFER));
+
+ return status;
+} // end ClassClaimDevice()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassInternalIoControl()
+
+Routine Description:
+
+ This routine passes internal device controls to the port driver.
+ Internal device controls are used by higher level drivers both for ioctls
+ and to pass through scsi requests.
+
+ If the IoControlCode does not match any of the handled ioctls and is
+ a valid system address then the request will be treated as an SRB and
+ passed down to the lower driver. If the IoControlCode is not a valid
+ system address the ioctl will be failed.
+
+ Callers must therefore be extremely cautious to pass correct, initialized
+ values to this function.
+
+Arguments:
+
+ DeviceObject - Supplies a pointer to the device object for this request.
+
+ Irp - Supplies the Irp making the request.
+
+Return Value:
+
+ Returns back a STATUS_PENDING or a completion status.
+
+--*/
+NTSTATUS
+ClassInternalIoControl(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp
+ )
+{
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ PIO_STACK_LOCATION nextStack = IoGetNextIrpStackLocation(Irp);
+
+ ULONG isRemoved;
+
+ PSCSI_REQUEST_BLOCK srb;
+
+ isRemoved = ClassAcquireRemoveLock(DeviceObject, Irp);
+ _Analysis_assume_(isRemoved);
+ if(isRemoved) {
+
+ Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST;
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+
+ return STATUS_DEVICE_DOES_NOT_EXIST;
+ }
+
+ //
+ // Get a pointer to the SRB.
+ //
+
+ srb = irpStack->Parameters.Scsi.Srb;
+
+ //
+ // Set the parameters in the next stack location.
+ //
+
+ if(commonExtension->IsFdo) {
+ nextStack->Parameters.Scsi.Srb = srb;
+ nextStack->MajorFunction = IRP_MJ_SCSI;
+ nextStack->MinorFunction = IRP_MN_SCSI_CLASS;
+
+ } else {
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+ }
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+
+ return IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+} // end ClassInternalIoControl()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassQueryTimeOutRegistryValue()
+
+Routine Description:
+
+ This routine determines whether a reg key for a user-specified timeout
+ value exists. This should be called at initialization time.
+
+Arguments:
+
+ DeviceObject - Pointer to the device object we are retrieving the timeout
+ value for
+
+Return Value:
+
+ None, but it sets a new default timeout for a class of devices.
+
+--*/
+_IRQL_requires_max_(PASSIVE_LEVEL)
+ULONG
+ClassQueryTimeOutRegistryValue(
+ _In_ PDEVICE_OBJECT DeviceObject
+ )
+{
+ //
+ // Find the appropriate reg. key
+ //
+
+#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
+ PCLASS_DRIVER_EXTENSION driverExtension = IoGetDriverObjectExtension(DeviceObject->DriverObject, CLASS_DRIVER_EXTENSION_KEY);
+
+ PUNICODE_STRING registryPath = &(driverExtension->RegistryPath);
+
+ PRTL_QUERY_REGISTRY_TABLE parameters = NULL;
+ PWSTR path;
+ NTSTATUS status;
+ LONG timeOut = 0;
+ ULONG zero = 0;
+ ULONG size;
+
+ PAGED_CODE();
+
+ if (!registryPath) {
+ return 0;
+ }
+
+ parameters = ExAllocatePoolWithTag(NonPagedPoolNx,
+ sizeof(RTL_QUERY_REGISTRY_TABLE)*2,
+ '1BcS');
+
+ if (!parameters) {
+ return 0;
+ }
+
+ size = registryPath->MaximumLength + sizeof(WCHAR);
+ path = ExAllocatePoolWithTag(NonPagedPoolNx, size, '2BcS');
+
+ if (!path) {
+ FREE_POOL(parameters);
+ return 0;
+ }
+
+ RtlZeroMemory(path,size);
+ RtlCopyMemory(path, registryPath->Buffer, size - sizeof(WCHAR));
+
+
+ //
+ // Check for the Timeout value.
+ //
+
+ RtlZeroMemory(parameters,
+ (sizeof(RTL_QUERY_REGISTRY_TABLE)*2));
+
+ parameters[0].Flags = RTL_QUERY_REGISTRY_DIRECT;
+ parameters[0].Name = L"TimeOutValue";
+ parameters[0].EntryContext = &timeOut;
+ parameters[0].DefaultType = REG_DWORD;
+ parameters[0].DefaultData = &zero;
+ parameters[0].DefaultLength = sizeof(ULONG);
+
+ status = RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE | RTL_REGISTRY_OPTIONAL,
+ path,
+ parameters,
+ NULL,
+ NULL);
+
+ if (!(NT_SUCCESS(status))) {
+ timeOut = 0;
+ }
+
+ FREE_POOL(parameters);
+ FREE_POOL(path);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,
+ "ClassQueryTimeOutRegistryValue: Timeout value %d\n",
+ timeOut));
+
+
+ return timeOut;
+
+} // end ClassQueryTimeOutRegistryValue()
+
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassCheckVerifyComplete() ISSUE-2000/02/18-henrygab - why public?!
+
+Routine Description:
+
+ This routine executes when the port driver has completed a check verify
+ ioctl. It will set the status of the master Irp, copy the media change
+ count and complete the request.
+
+Arguments:
+
+ Fdo - Supplies the functional device object which represents the logical unit.
+
+ Irp - Supplies the Irp which has completed.
+
+ Context - NULL
+
+Return Value:
+
+ NT status
+
+--*/
+NTSTATUS
+ClassCheckVerifyComplete(
+ IN PDEVICE_OBJECT Fdo,
+ IN PIRP Irp,
+ IN PVOID Context
+ )
+{
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
+
+ PIRP originalIrp;
+
+ UNREFERENCED_PARAMETER(Context);
+
+ ASSERT_FDO(Fdo);
+
+ originalIrp = irpStack->Parameters.Others.Argument1;
+
+ //
+ // Copy the media change count and status
+ //
+
+ *((PULONG) (originalIrp->AssociatedIrp.SystemBuffer)) =
+ fdoExtension->MediaChangeCount;
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "ClassCheckVerifyComplete - Media change count for"
+ "device %d is %lx - saved as %lx\n",
+ fdoExtension->DeviceNumber,
+ fdoExtension->MediaChangeCount,
+ *((PULONG) originalIrp->AssociatedIrp.SystemBuffer)));
+
+ originalIrp->IoStatus.Status = Irp->IoStatus.Status;
+ originalIrp->IoStatus.Information = sizeof(ULONG);
+
+ ClassReleaseRemoveLock(Fdo, originalIrp);
+ ClassCompleteRequest(Fdo, originalIrp, IO_DISK_INCREMENT);
+
+ IoFreeIrp(Irp);
+
+ return STATUS_MORE_PROCESSING_REQUIRED;
+
+} // end ClassCheckVerifyComplete()
+
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassGetDescriptor()
+
+Routine Description:
+
+ This routine will perform a query for the specified property id and will
+ allocate a non-paged buffer to store the data in. It is the responsibility
+ of the caller to ensure that this buffer is freed.
+
+ This routine must be run at IRQL_PASSIVE_LEVEL
+
+Arguments:
+
+ DeviceObject - the device to query
+ DeviceInfo - a location to store a pointer to the buffer we allocate
+
+Return Value:
+
+ status
+ if status is unsuccessful *DeviceInfo will be set to NULL, else the
+ buffer allocated on behalf of the caller.
+
+--*/
+_IRQL_requires_max_(PASSIVE_LEVEL)
+NTSTATUS
+ClassGetDescriptor(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PSTORAGE_PROPERTY_ID PropertyId,
+ _Outptr_ PVOID *Descriptor
+ )
+{
+ STORAGE_PROPERTY_QUERY query = {0};
+ IO_STATUS_BLOCK ioStatus;
+
+ PSTORAGE_DESCRIPTOR_HEADER descriptor = NULL;
+ ULONG length;
+
+ PAGED_CODE();
+
+ //
+ // Set the passed-in descriptor pointer to NULL as default
+ //
+
+ *Descriptor = NULL;
+
+ query.PropertyId = *PropertyId;
+ query.QueryType = PropertyStandardQuery;
+
+ //
+ // On the first pass we just want to get the first few
+ // bytes of the descriptor so we can read it's size
+ //
+
+ descriptor = (PVOID)&query;
+
+ NT_ASSERT(sizeof(STORAGE_PROPERTY_QUERY) >= (sizeof(ULONG)*2));
+
+ ClassSendDeviceIoControlSynchronous(
+ IOCTL_STORAGE_QUERY_PROPERTY,
+ DeviceObject,
+ &query,
+ sizeof(STORAGE_PROPERTY_QUERY),
+ sizeof(ULONG) * 2,
+ FALSE,
+ &ioStatus
+ );
+
+ if(!NT_SUCCESS(ioStatus.Status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClassGetDescriptor: error %lx trying to "
+ "query properties #1\n", ioStatus.Status));
+ return ioStatus.Status;
+ }
+
+ if (descriptor->Size == 0) {
+
+ //
+ // This DebugPrint is to help third-party driver writers
+ //
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClassGetDescriptor: size returned was zero?! (status "
+ "%x\n", ioStatus.Status));
+ return STATUS_UNSUCCESSFUL;
+
+ }
+
+ //
+ // This time we know how much data there is so we can
+ // allocate a buffer of the correct size
+ //
+
+ length = descriptor->Size;
+ NT_ASSERT(length >= sizeof(STORAGE_PROPERTY_QUERY));
+ length = max(length, sizeof(STORAGE_PROPERTY_QUERY));
+
+ descriptor = ExAllocatePoolWithTag(NonPagedPoolNx, length, '4BcS');
+
+ if(descriptor == NULL) {
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClassGetDescriptor: unable to memory for descriptor "
+ "(%d bytes)\n", length));
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ //
+ // setup the query again, as it was overwritten above
+ //
+
+ RtlZeroMemory(&query, sizeof(STORAGE_PROPERTY_QUERY));
+ query.PropertyId = *PropertyId;
+ query.QueryType = PropertyStandardQuery;
+
+ //
+ // copy the input to the new outputbuffer
+ //
+
+ RtlZeroMemory(descriptor, length);
+
+ RtlCopyMemory(descriptor,
+ &query,
+ sizeof(STORAGE_PROPERTY_QUERY)
+ );
+
+ ClassSendDeviceIoControlSynchronous(
+ IOCTL_STORAGE_QUERY_PROPERTY,
+ DeviceObject,
+ descriptor,
+ sizeof(STORAGE_PROPERTY_QUERY),
+ length,
+ FALSE,
+ &ioStatus
+ );
+
+ if(!NT_SUCCESS(ioStatus.Status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClassGetDescriptor: error %lx trying to "
+ "query properties #1\n", ioStatus.Status));
+ FREE_POOL(descriptor);
+ return ioStatus.Status;
+ }
+
+ //
+ // return the memory we've allocated to the caller
+ //
+
+ *Descriptor = descriptor;
+ return ioStatus.Status;
+} // end ClassGetDescriptor()
+
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassSignalCompletion()
+
+Routine Description:
+
+ This completion routine will signal the event given as context and then
+ return STATUS_MORE_PROCESSING_REQUIRED to stop event completion. It is
+ the responsibility of the routine waiting on the event to complete the
+ request and free the event.
+
+Arguments:
+
+ DeviceObject - a pointer to the device object
+
+ Irp - a pointer to the irp
+
+ Event - a pointer to the event to signal
+
+Return Value:
+
+ STATUS_MORE_PROCESSING_REQUIRED
+
+--*/
+NTSTATUS
+ClassSignalCompletion(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN PVOID Context
+ )
+{
+ PKEVENT Event = (PKEVENT)Context;
+
+ UNREFERENCED_PARAMETER(DeviceObject);
+ UNREFERENCED_PARAMETER(Irp);
+
+ if (Context == NULL) {
+ NT_ASSERT(Context != NULL);
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ KeSetEvent(Event, IO_NO_INCREMENT, FALSE);
+
+ return STATUS_MORE_PROCESSING_REQUIRED;
+} // end ClassSignalCompletion()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassPnpQueryFdoRelations()
+
+Routine Description:
+
+ This routine will call the driver's enumeration routine to update the
+ list of PDO's. It will then build a response to the
+ IRP_MN_QUERY_DEVICE_RELATIONS and place it into the information field in
+ the irp.
+
+Arguments:
+
+ Fdo - a pointer to the functional device object we are enumerating
+
+ Irp - a pointer to the enumeration request
+
+Return Value:
+
+ status
+
+--*/
+NTSTATUS
+ClassPnpQueryFdoRelations(
+ IN PDEVICE_OBJECT Fdo,
+ IN PIRP Irp
+ )
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
+#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
+ PCLASS_DRIVER_EXTENSION driverExtension = IoGetDriverObjectExtension(Fdo->DriverObject, CLASS_DRIVER_EXTENSION_KEY);
+
+ PAGED_CODE();
+
+ _Analysis_assume_(driverExtension != NULL);
+
+ //
+ // If there's already an enumeration in progress then don't start another
+ // one.
+ //
+
+ if(InterlockedIncrement((volatile LONG *)&(fdoExtension->EnumerationInterlock)) == 1) {
+ driverExtension->InitData.ClassEnumerateDevice(Fdo);
+ }
+
+ Irp->IoStatus.Status = ClassRetrieveDeviceRelations(
+ Fdo,
+ BusRelations,
+ &((PDEVICE_RELATIONS) Irp->IoStatus.Information));
+ InterlockedDecrement((volatile LONG *)&(fdoExtension->EnumerationInterlock));
+
+ return Irp->IoStatus.Status;
+} // end ClassPnpQueryFdoRelations()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassMarkChildrenMissing()
+
+Routine Description:
+
+ This routine will call ClassMarkChildMissing() for all children.
+ It acquires the ChildLock before calling ClassMarkChildMissing().
+
+Arguments:
+
+ Fdo - the "bus's" device object, such as the disk FDO for non-removable
+ disks with multiple partitions.
+
+Return Value:
+
+ None
+
+--*/
+_IRQL_requires_max_(PASSIVE_LEVEL)
+VOID
+ClassMarkChildrenMissing(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION Fdo
+ )
+{
+ PCOMMON_DEVICE_EXTENSION commonExtension = &(Fdo->CommonExtension);
+ PPHYSICAL_DEVICE_EXTENSION nextChild = commonExtension->ChildList;
+
+ PAGED_CODE();
+
+ ClassAcquireChildLock(Fdo);
+
+ while (nextChild){
+ PPHYSICAL_DEVICE_EXTENSION tmpChild;
+
+ /*
+ * ClassMarkChildMissing will also dequeue the child extension.
+ * So get the next pointer before calling ClassMarkChildMissing.
+ */
+ tmpChild = nextChild;
+ nextChild = tmpChild->CommonExtension.ChildList;
+ ClassMarkChildMissing(tmpChild, FALSE);
+ }
+ ClassReleaseChildLock(Fdo);
+ return;
+} // end ClassMarkChildrenMissing()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassMarkChildMissing()
+
+Routine Description:
+
+ This routine will make an active child "missing." If the device has never
+ been enumerated then it will be deleted on the spot. If the device has
+ not been enumerated then it will be marked as missing so that we can
+ not report it in the next device enumeration.
+
+Arguments:
+
+ Child - the child device to be marked as missing.
+
+ AcquireChildLock - TRUE if the child lock should be acquired before removing
+ the missing child. FALSE if the child lock is already
+ acquired by this thread.
+
+Return Value:
+
+ returns whether or not the child device object has previously been reported
+ to PNP.
+
+--*/
+_IRQL_requires_max_(PASSIVE_LEVEL)
+BOOLEAN
+ClassMarkChildMissing(
+ _In_ PPHYSICAL_DEVICE_EXTENSION Child,
+ _In_ BOOLEAN AcquireChildLock
+ )
+{
+ BOOLEAN returnValue = Child->IsEnumerated;
+
+ PAGED_CODE();
+ ASSERT_PDO(Child->DeviceObject);
+
+ Child->IsMissing = TRUE;
+
+ //
+ // Make sure this child is not in the active list.
+ //
+
+ ClassRemoveChild(Child->CommonExtension.PartitionZeroExtension,
+ Child,
+ AcquireChildLock);
+
+ if(Child->IsEnumerated == FALSE) {
+ PCOMMON_DEVICE_EXTENSION commonExtension = Child->DeviceObject->DeviceExtension;
+ commonExtension->IsRemoved = REMOVE_PENDING;
+ ClassRemoveDevice(Child->DeviceObject, IRP_MN_REMOVE_DEVICE);
+ }
+
+ return returnValue;
+} // end ClassMarkChildMissing()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassRetrieveDeviceRelations()
+
+Routine Description:
+
+ This routine will allocate a buffer to hold the specified list of
+ relations. It will then fill in the list with referenced device pointers
+ and will return the request.
+
+Arguments:
+
+ Fdo - pointer to the FDO being queried
+
+ RelationType - what type of relations are being queried
+
+ DeviceRelations - a location to store a pointer to the response
+
+Return Value:
+
+ status
+
+--*/
+NTSTATUS
+ClassRetrieveDeviceRelations(
+ IN PDEVICE_OBJECT Fdo,
+ IN DEVICE_RELATION_TYPE RelationType,
+ OUT PDEVICE_RELATIONS *DeviceRelations
+ )
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
+
+ ULONG count = 0;
+ ULONG i;
+
+ PPHYSICAL_DEVICE_EXTENSION nextChild;
+
+ ULONG relationsSize;
+ PDEVICE_RELATIONS deviceRelations = NULL;
+ PDEVICE_RELATIONS oldRelations = *DeviceRelations;
+
+ NTSTATUS status;
+
+ UNREFERENCED_PARAMETER(RelationType);
+
+ PAGED_CODE();
+
+ ClassAcquireChildLock(fdoExtension);
+
+ nextChild = fdoExtension->CommonExtension.ChildList;
+
+ //
+ // Count the number of PDO's attached to this disk
+ //
+
+ while (nextChild != NULL) {
+ PCOMMON_DEVICE_EXTENSION commonExtension;
+
+ commonExtension = &(nextChild->CommonExtension);
+
+ NT_ASSERTMSG("ClassPnp internal error: missing child on active list\n",
+ (nextChild->IsMissing == FALSE));
+
+ nextChild = commonExtension->ChildList;
+
+ count++;
+ };
+
+ //
+ // If relations already exist in the QDR, adjust the current count
+ // to include the previous list.
+ //
+
+ if (oldRelations) {
+ count += oldRelations->Count;
+ }
+
+ relationsSize = (sizeof(DEVICE_RELATIONS) +
+ (count * sizeof(PDEVICE_OBJECT)));
+
+ deviceRelations = ExAllocatePoolWithTag(PagedPool, relationsSize, '5BcS');
+
+ if (deviceRelations == NULL) {
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_ENUM, "ClassRetrieveDeviceRelations: unable to allocate "
+ "%d bytes for device relations\n", relationsSize));
+
+ ClassReleaseChildLock(fdoExtension);
+
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ RtlZeroMemory(deviceRelations, relationsSize);
+
+ if (oldRelations) {
+
+ //
+ // Copy the old relations to the new list and free the old list.
+ //
+
+ for (i = 0; i < oldRelations->Count; i++) {
+ deviceRelations->Objects[i] = oldRelations->Objects[i];
+ }
+
+ FREE_POOL(oldRelations);
+ }
+
+ nextChild = fdoExtension->CommonExtension.ChildList;
+ i = count;
+
+ while (nextChild != NULL) {
+ PCOMMON_DEVICE_EXTENSION commonExtension;
+
+ commonExtension = &(nextChild->CommonExtension);
+
+ NT_ASSERTMSG("ClassPnp internal error: missing child on active list\n",
+ (nextChild->IsMissing == FALSE));
+
+ _Analysis_assume_(i >= 1);
+ deviceRelations->Objects[--i] = nextChild->DeviceObject;
+
+ status = ObReferenceObjectByPointer(
+ nextChild->DeviceObject,
+ 0,
+ NULL,
+ KernelMode);
+ if (!NT_SUCCESS(status)) {
+ NT_ASSERT(!"Error referencing child device by pointer");
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_ENUM,
+ "ClassRetrieveDeviceRelations: Error referencing child "
+ "device %p by pointer\n", nextChild->DeviceObject));
+
+ }
+ nextChild->IsEnumerated = TRUE;
+ nextChild = commonExtension->ChildList;
+ }
+
+ NT_ASSERTMSG("Child list has changed: ", i == 0);
+
+ deviceRelations->Count = count;
+ *DeviceRelations = deviceRelations;
+
+ ClassReleaseChildLock(fdoExtension);
+ return STATUS_SUCCESS;
+} // end ClassRetrieveDeviceRelations()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassGetPdoId()
+
+Routine Description:
+
+ This routine will call into the driver to retrieve a copy of one of it's
+ id strings.
+
+Arguments:
+
+ Pdo - a pointer to the pdo being queried
+
+ IdType - which type of id string is being queried
+
+ IdString - an allocated unicode string structure which the driver
+ can fill in.
+
+Return Value:
+
+ status
+
+--*/
+NTSTATUS
+ClassGetPdoId(
+ IN PDEVICE_OBJECT Pdo,
+ IN BUS_QUERY_ID_TYPE IdType,
+ IN PUNICODE_STRING IdString
+ )
+{
+#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
+ PCLASS_DRIVER_EXTENSION driverExtension = IoGetDriverObjectExtension(Pdo->DriverObject, CLASS_DRIVER_EXTENSION_KEY);
+
+ _Analysis_assume_(driverExtension != NULL);
+
+ ASSERT_PDO(Pdo);
+ NT_ASSERT(driverExtension->InitData.ClassQueryId);
+
+ PAGED_CODE();
+
+ return driverExtension->InitData.ClassQueryId( Pdo, IdType, IdString);
+} // end ClassGetPdoId()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassQueryPnpCapabilities()
+
+Routine Description:
+
+ This routine will call into the class driver to retrieve it's pnp
+ capabilities.
+
+Arguments:
+
+ PhysicalDeviceObject - The physical device object to retrieve properties
+ for.
+
+Return Value:
+
+ status
+
+--*/
+NTSTATUS
+ClassQueryPnpCapabilities(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PDEVICE_CAPABILITIES Capabilities
+ )
+{
+ PCLASS_DRIVER_EXTENSION driverExtension =
+ ClassGetDriverExtension(DeviceObject->DriverObject);
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+
+ PCLASS_QUERY_PNP_CAPABILITIES queryRoutine = NULL;
+
+ PAGED_CODE();
+
+ NT_ASSERT(DeviceObject);
+ NT_ASSERT(Capabilities);
+
+ if(commonExtension->IsFdo) {
+ queryRoutine = driverExtension->InitData.FdoData.ClassQueryPnpCapabilities;
+ } else {
+ queryRoutine = driverExtension->InitData.PdoData.ClassQueryPnpCapabilities;
+ }
+
+ if(queryRoutine) {
+ return queryRoutine(DeviceObject,
+ Capabilities);
+ } else {
+ return STATUS_NOT_IMPLEMENTED;
+ }
+} // end ClassQueryPnpCapabilities()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassInvalidateBusRelations()
+
+Routine Description:
+
+ This routine re-enumerates the devices on the "bus". It will call into
+ the driver's ClassEnumerate routine to update the device objects
+ immediately. It will then schedule a bus re-enumeration for pnp by calling
+ IoInvalidateDeviceRelations.
+
+Arguments:
+
+ Fdo - a pointer to the functional device object for this bus
+
+Return Value:
+
+ none
+
+--*/
+_IRQL_requires_max_(PASSIVE_LEVEL)
+VOID
+ClassInvalidateBusRelations(
+ _In_ PDEVICE_OBJECT Fdo
+ )
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
+#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
+ PCLASS_DRIVER_EXTENSION driverExtension = IoGetDriverObjectExtension(Fdo->DriverObject, CLASS_DRIVER_EXTENSION_KEY);
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ PAGED_CODE();
+
+ _Analysis_assume_(driverExtension != NULL);
+
+ ASSERT_FDO(Fdo);
+ NT_ASSERT(driverExtension->InitData.ClassEnumerateDevice != NULL);
+
+ if(InterlockedIncrement((volatile LONG *)&(fdoExtension->EnumerationInterlock)) == 1) {
+ status = driverExtension->InitData.ClassEnumerateDevice(Fdo);
+ }
+ InterlockedDecrement((volatile LONG *)&(fdoExtension->EnumerationInterlock));
+
+ if(!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_ENUM, "ClassInvalidateBusRelations: EnumerateDevice routine "
+ "returned %lx\n", status));
+ }
+
+ IoInvalidateDeviceRelations(fdoExtension->LowerPdo, BusRelations);
+
+ return;
+} // end ClassInvalidateBusRelations()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassRemoveDevice() ISSUE-2000/02/18-henrygab - why public?!
+
+Routine Description:
+
+ This routine is called to handle the "removal" of a device. It will
+ forward the request downwards if necesssary, call into the driver
+ to release any necessary resources (memory, events, etc) and then
+ will delete the device object.
+
+Arguments:
+
+ DeviceObject - a pointer to the device object being removed
+
+ RemoveType - indicates what type of remove this is (regular or surprise).
+
+Return Value:
+
+ status
+
+--*/
+_IRQL_requires_max_(PASSIVE_LEVEL)
+NTSTATUS
+ClassRemoveDevice(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ UCHAR RemoveType
+ )
+{
+#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
+ PCLASS_DRIVER_EXTENSION driverExtension = IoGetDriverObjectExtension(DeviceObject->DriverObject, CLASS_DRIVER_EXTENSION_KEY);
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+ PDEVICE_OBJECT lowerDeviceObject = commonExtension->LowerDeviceObject;
+ BOOLEAN proceedWithRemove = TRUE;
+ NTSTATUS status;
+ PEX_RUNDOWN_REF_CACHE_AWARE removeLockRundown = NULL;
+
+
+ PAGED_CODE();
+
+ _Analysis_assume_(driverExtension != NULL);
+
+ /*
+ * Deregister from WMI.
+ */
+ if (commonExtension->IsFdo ||
+ driverExtension->InitData.PdoData.ClassWmiInfo.GuidRegInfo) {
+ status = IoWMIRegistrationControl(DeviceObject, WMIREG_ACTION_DEREGISTER);
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_WMI, "ClassRemoveDevice: IoWMIRegistrationControl(%p, WMI_ACTION_DEREGISTER) --> %lx", DeviceObject, status));
+ }
+
+ /*
+ * If we exposed a "shingle" (a named device interface openable by CreateFile)
+ * then delete it now.
+ */
+ if (commonExtension->MountedDeviceInterfaceName.Buffer){
+ (VOID)IoSetDeviceInterfaceState(&commonExtension->MountedDeviceInterfaceName, FALSE);
+ RtlFreeUnicodeString(&commonExtension->MountedDeviceInterfaceName);
+ RtlInitUnicodeString(&commonExtension->MountedDeviceInterfaceName, NULL);
+ }
+
+ //
+ // If this is a surprise removal we leave the device around - which means
+ // we don't have to (or want to) drop the remove lock and wait for pending
+ // requests to complete.
+ //
+
+ if (RemoveType == IRP_MN_REMOVE_DEVICE) {
+
+ //
+ // Release the lock we acquired when the device object was created.
+ //
+
+ ClassReleaseRemoveLock(DeviceObject, (PIRP) DeviceObject);
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP, "ClasspRemoveDevice - Reference count is now %d\n",
+ commonExtension->RemoveLock));
+
+ //
+ // The RemoveLockRundown allows fast protection of the device object
+ // structure that is torn down by a single thread. While in the rundown process (the call to
+ // ExWaitForRundownProtectionReleaseCacheAware returns), the rundown object becomes
+ // invalid and the subsequent calls to ExAcquireRundownProtectionCacheAware will return FALSE.
+ // ExReInitializeRundownProtectionCacheAware needs to be called to re-initialize the
+ // RemoveLockRundown protection.
+ //
+
+ removeLockRundown = (PEX_RUNDOWN_REF_CACHE_AWARE)
+ ((PCHAR)commonExtension->PrivateCommonData +
+ sizeof(CLASS_PRIVATE_COMMON_DATA));
+ ExWaitForRundownProtectionReleaseCacheAware(removeLockRundown);
+
+ KeSetEvent(&commonExtension->RemoveEvent,
+ IO_NO_INCREMENT,
+ FALSE);
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP, "ClasspRemoveDevice - removing device %p\n", DeviceObject));
+
+ if (commonExtension->IsFdo) {
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP, "ClasspRemoveDevice - FDO %p has received a "
+ "remove request.\n", DeviceObject));
+
+ } else {
+ PPHYSICAL_DEVICE_EXTENSION pdoExtension = DeviceObject->DeviceExtension;
+
+ if (pdoExtension->IsMissing) {
+ /*
+ * The child partition PDO is missing, so we are going to go ahead
+ * and delete it for the remove.
+ */
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP, "ClasspRemoveDevice - PDO %p is missing and will be removed", DeviceObject));
+ } else {
+ /*
+ * We got a remove for a child partition PDO which is not actually missing.
+ * So we will NOT actually delete it.
+ */
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP, "ClasspRemoveDevice - PDO %p still exists and will be removed when it disappears", DeviceObject));
+
+ ExReInitializeRundownProtectionCacheAware(removeLockRundown);
+
+ //
+ // Reacquire the remove lock for the next time this comes around.
+ //
+
+ ClassAcquireRemoveLock(DeviceObject, (PIRP) DeviceObject);
+
+ //
+ // the device wasn't missing so it's not really been removed.
+ //
+
+ commonExtension->IsRemoved = NO_REMOVE;
+
+ IoInvalidateDeviceRelations(
+ commonExtension->PartitionZeroExtension->LowerPdo,
+ BusRelations);
+
+ proceedWithRemove = FALSE;
+ }
+ }
+ }
+
+
+ if (proceedWithRemove) {
+
+ /*
+ * Call the class driver's remove handler.
+ * All this is supposed to do is clean up its data and device interfaces.
+ */
+ NT_ASSERT(commonExtension->DevInfo->ClassRemoveDevice);
+ status = commonExtension->DevInfo->ClassRemoveDevice(DeviceObject, RemoveType);
+ NT_ASSERT(NT_SUCCESS(status));
+ status = STATUS_SUCCESS;
+ UNREFERENCED_PARAMETER(status); // disables prefast warning; defensive coding...
+
+ if (commonExtension->IsFdo) {
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
+
+ ClasspDisableTimer(fdoExtension);
+
+ if (RemoveType == IRP_MN_REMOVE_DEVICE) {
+
+ PPHYSICAL_DEVICE_EXTENSION child;
+
+ //
+ // If this FDO is idle power managed, remove it from the
+ // list of idle power managed FDOs.
+ //
+ if (fdoExtension->FunctionSupportInfo &&
+ fdoExtension->FunctionSupportInfo->IdlePower.IdlePowerEnabled) {
+ PIDLE_POWER_FDO_LIST_ENTRY fdoEntry;
+
+ KeAcquireGuardedMutex(&IdlePowerFDOListMutex);
+ fdoEntry = (PIDLE_POWER_FDO_LIST_ENTRY)IdlePowerFDOList.Flink;
+ while ((PLIST_ENTRY)fdoEntry != &IdlePowerFDOList) {
+ PIDLE_POWER_FDO_LIST_ENTRY nextEntry = (PIDLE_POWER_FDO_LIST_ENTRY)fdoEntry->ListEntry.Flink;
+ if (fdoEntry->Fdo == DeviceObject) {
+ RemoveEntryList(&(fdoEntry->ListEntry));
+ ExFreePool(fdoEntry);
+ break;
+ }
+ fdoEntry = nextEntry;
+ }
+ KeReleaseGuardedMutex(&IdlePowerFDOListMutex);
+ }
+
+ //
+ // Cleanup the media detection resources now that the class driver
+ // has stopped it's timer (if any) and we can be sure they won't
+ // call us to do detection again.
+ //
+
+ ClassCleanupMediaChangeDetection(fdoExtension);
+
+ //
+ // Cleanup any Failure Prediction stuff
+ //
+ FREE_POOL(fdoExtension->FailurePredictionInfo);
+
+ /*
+ * Ordinarily all child PDOs will be removed by the time
+ * that the parent gets the REMOVE_DEVICE.
+ * However, if a child PDO has been created but has not
+ * been announced in a QueryDeviceRelations, then it is
+ * just a private data structure unknown to pnp, and we have
+ * to delete it ourselves.
+ */
+ ClassAcquireChildLock(fdoExtension);
+ child = ClassRemoveChild(fdoExtension, NULL, FALSE);
+ while (child) {
+ PCOMMON_DEVICE_EXTENSION childCommonExtension = child->DeviceObject->DeviceExtension;
+
+ //
+ // Yank the pdo. This routine will unlink the device from the
+ // pdo list so NextPdo will point to the next one when it's
+ // complete.
+ //
+ child->IsMissing = TRUE;
+ childCommonExtension->IsRemoved = REMOVE_PENDING;
+ ClassRemoveDevice(child->DeviceObject, IRP_MN_REMOVE_DEVICE);
+ child = ClassRemoveChild(fdoExtension, NULL, FALSE);
+ }
+ ClassReleaseChildLock(fdoExtension);
+ }
+ else if (RemoveType == IRP_MN_SURPRISE_REMOVAL){
+ /*
+ * This is a surprise-remove on the parent FDO.
+ * We will mark the child PDOs as missing so that they
+ * will actually get deleted when they get a REMOVE_DEVICE.
+ */
+ ClassMarkChildrenMissing(fdoExtension);
+ }
+
+ if (RemoveType == IRP_MN_REMOVE_DEVICE) {
+
+ ClasspFreeReleaseRequest(DeviceObject);
+
+ //
+ // Free FDO-specific data structs
+ //
+ if (fdoExtension->PrivateFdoData) {
+ //
+ // Only remove the entry if the list has been initialized, or
+ // else we will access invalid memory.
+ //
+ PLIST_ENTRY allFdosListEntry = &fdoExtension->PrivateFdoData->AllFdosListEntry;
+ if (allFdosListEntry->Flink && allFdosListEntry->Blink) {
+ //
+ // Remove the FDO from the static list.
+ // Pnp is synchronized so this shouldn't need any synchronization.
+ //
+ RemoveEntryList(allFdosListEntry);
+ }
+ InitializeListHead(allFdosListEntry);
+
+ DestroyAllTransferPackets(DeviceObject);
+
+ //
+ // Delete the tick timer now.
+ //
+ ClasspDeleteTimer(fdoExtension);
+
+
+ FREE_POOL(fdoExtension->PrivateFdoData->PowerProcessIrp);
+ FREE_POOL(fdoExtension->PrivateFdoData->FreeTransferPacketsLists);
+ FREE_POOL(fdoExtension->PrivateFdoData);
+ }
+
+#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
+ FREE_POOL(fdoExtension->AdditionalFdoData);
+#endif
+
+ if (commonExtension->DeviceName.Buffer) {
+ FREE_POOL(commonExtension->DeviceName.Buffer);
+ RtlInitUnicodeString(&commonExtension->DeviceName, NULL);
+ }
+
+#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
+ if (fdoExtension->FunctionSupportInfo != NULL) {
+ FREE_POOL(fdoExtension->FunctionSupportInfo->HwFirmwareInfo);
+ }
+#endif
+ FREE_POOL(fdoExtension->FunctionSupportInfo);
+
+ FREE_POOL(fdoExtension->MiniportDescriptor);
+
+ FREE_POOL(fdoExtension->AdapterDescriptor);
+
+ FREE_POOL(fdoExtension->DeviceDescriptor);
+
+ //
+ // Detach our device object from the stack - there's no reason
+ // to hold off our cleanup any longer.
+ //
+
+ IoDetachDevice(lowerDeviceObject);
+ }
+ }
+ else {
+ /*
+ * This is a child partition PDO.
+ * We have already determined that it was previously marked
+ * as missing. So if this is a REMOVE_DEVICE, we will actually
+ * delete it.
+ */
+ if (RemoveType == IRP_MN_REMOVE_DEVICE) {
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = commonExtension->PartitionZeroExtension;
+ PPHYSICAL_DEVICE_EXTENSION pdoExtension = (PPHYSICAL_DEVICE_EXTENSION)commonExtension;
+
+ //
+ // See if this device is in the child list (if this was a suprise
+ // removal it might be) and remove it.
+ //
+ ClassRemoveChild(fdoExtension, pdoExtension, TRUE);
+ }
+ }
+
+ commonExtension->PartitionLength.QuadPart = 0;
+
+ if (RemoveType == IRP_MN_REMOVE_DEVICE) {
+
+ ClasspUninitializeRemoveTracking(DeviceObject);
+
+ IoDeleteDevice(DeviceObject);
+ }
+ }
+
+ return STATUS_SUCCESS;
+} // end ClassRemoveDevice()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassGetDriverExtension()
+
+Routine Description:
+
+ This routine will return the classpnp's driver extension.
+
+Arguments:
+
+ DriverObject - the driver object for which to get classpnp's extension
+
+Return Value:
+
+ Either NULL if none, or a pointer to the driver extension
+
+--*/
+__drv_aliasesMem
+_IRQL_requires_max_(DISPATCH_LEVEL)
+PCLASS_DRIVER_EXTENSION
+ClassGetDriverExtension(
+ _In_ PDRIVER_OBJECT DriverObject
+ )
+{
+#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
+ return IoGetDriverObjectExtension(DriverObject, CLASS_DRIVER_EXTENSION_KEY);
+} // end ClassGetDriverExtension()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClasspStartIo()
+
+Routine Description:
+
+ This routine wraps the class driver's start io routine. If the device
+ is being removed it will complete any requests with
+ STATUS_DEVICE_DOES_NOT_EXIST and fire up the next packet.
+
+Arguments:
+
+Return Value:
+
+ none
+
+--*/
+VOID
+ClasspStartIo(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp
+ )
+{
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+
+ //
+ // We're already holding the remove lock so just check the variable and
+ // see what's going on.
+ //
+
+ if(commonExtension->IsRemoved) {
+
+ Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST;
+
+#pragma warning(suppress:4054) // okay to type cast function pointer to PIRP for this use case
+ ClassAcquireRemoveLock(DeviceObject, (PIRP) ClasspStartIo);
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_DISK_INCREMENT);
+ IoStartNextPacket(DeviceObject, TRUE); // Some IO is cancellable
+
+#pragma warning(suppress:4054) // okay to type cast function pointer to PIRP for this use case
+ ClassReleaseRemoveLock(DeviceObject, (PIRP) ClasspStartIo);
+
+ return;
+ }
+
+ commonExtension->DriverExtension->InitData.ClassStartIo(
+ DeviceObject,
+ Irp);
+
+ return;
+} // ClasspStartIo()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassUpdateInformationInRegistry()
+
+Routine Description:
+
+ This routine has knowledge about the layout of the device map information
+ in the registry. It will update this information to include a value
+ entry specifying the dos device name that is assumed to get assigned
+ to this NT device name. For more information on this assigning of the
+ dos device name look in the drive support routine in the hal that assigns
+ all dos names.
+
+ Since some versions of some device's firmware did not work and some
+ vendors did not bother to follow the specification, the entire inquiry
+ information must also be stored in the registry so than someone can
+ figure out the firmware version.
+
+Arguments:
+
+ DeviceObject - A pointer to the device object for the tape device.
+
+Return Value:
+
+ None
+
+--*/
+_IRQL_requires_max_(PASSIVE_LEVEL)
+VOID
+ClassUpdateInformationInRegistry(
+ _In_ PDEVICE_OBJECT Fdo,
+ _In_ PCHAR DeviceName,
+ _In_ ULONG DeviceNumber,
+ _In_reads_bytes_opt_(InquiryDataLength) PINQUIRYDATA InquiryData,
+ _In_ ULONG InquiryDataLength
+ )
+{
+ NTSTATUS status;
+ SCSI_ADDRESS scsiAddress = {0};
+ OBJECT_ATTRIBUTES objectAttributes = {0};
+ STRING string;
+ UNICODE_STRING unicodeName = {0};
+ UNICODE_STRING unicodeRegistryPath = {0};
+ UNICODE_STRING unicodeData = {0};
+ HANDLE targetKey;
+ IO_STATUS_BLOCK ioStatus;
+ UCHAR buffer[256] = {0};
+
+ PAGED_CODE();
+
+ NT_ASSERT(DeviceName);
+ targetKey = NULL;
+
+ TRY {
+
+ //
+ // Issue GET_ADDRESS Ioctl to determine path, target, and lun information.
+ //
+
+ ClassSendDeviceIoControlSynchronous(
+ IOCTL_SCSI_GET_ADDRESS,
+ Fdo,
+ &scsiAddress,
+ 0,
+ sizeof(SCSI_ADDRESS),
+ FALSE,
+ &ioStatus
+ );
+
+ if (!NT_SUCCESS(ioStatus.Status)) {
+
+ status = ioStatus.Status;
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT,
+ "UpdateInformationInRegistry: Get Address failed %lx\n",
+ status));
+ LEAVE;
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT,
+ "GetAddress: Port %x, Path %x, Target %x, Lun %x\n",
+ scsiAddress.PortNumber,
+ scsiAddress.PathId,
+ scsiAddress.TargetId,
+ scsiAddress.Lun));
+
+ }
+
+ status = RtlStringCchPrintfA((NTSTRSAFE_PSTR)buffer,
+ sizeof(buffer)-1,
+ "\\Registry\\Machine\\Hardware\\DeviceMap\\Scsi\\Scsi Port %d\\Scsi Bus %d\\Target Id %d\\Logical Unit Id %d",
+ scsiAddress.PortNumber,
+ scsiAddress.PathId,
+ scsiAddress.TargetId,
+ scsiAddress.Lun);
+
+ if (!NT_SUCCESS(status)) {
+ LEAVE;
+ }
+
+ RtlInitString(&string, (PCSZ)buffer);
+
+ status = RtlAnsiStringToUnicodeString(&unicodeRegistryPath,
+ &string,
+ TRUE);
+
+ if (!NT_SUCCESS(status)) {
+ LEAVE;
+ }
+
+ //
+ // Open the registry key for the scsi information for this
+ // scsibus, target, lun.
+ //
+
+ InitializeObjectAttributes(&objectAttributes,
+ &unicodeRegistryPath,
+ OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
+ NULL,
+ NULL);
+
+ status = ZwOpenKey(&targetKey,
+ KEY_READ | KEY_WRITE,
+ &objectAttributes);
+
+ if (!NT_SUCCESS(status)) {
+ LEAVE;
+ }
+
+ //
+ // Now construct and attempt to create the registry value
+ // specifying the device name in the appropriate place in the
+ // device map.
+ //
+
+ RtlInitUnicodeString(&unicodeName, L"DeviceName");
+
+ status = RtlStringCchPrintfA((NTSTRSAFE_PSTR)buffer, sizeof(buffer)-1, "%s%d", DeviceName, DeviceNumber);
+ if (!NT_SUCCESS(status)) {
+ LEAVE;
+ }
+
+ RtlInitString(&string, (PCSZ)buffer);
+ status = RtlAnsiStringToUnicodeString(&unicodeData,
+ &string,
+ TRUE);
+ if (NT_SUCCESS(status)) {
+ status = ZwSetValueKey(targetKey,
+ &unicodeName,
+ 0,
+ REG_SZ,
+ unicodeData.Buffer,
+ unicodeData.Length);
+ }
+
+ //
+ // if they sent in data, update the registry
+ //
+
+ if (NT_SUCCESS(status) && InquiryDataLength) {
+
+ NT_ASSERT(InquiryData);
+
+ RtlInitUnicodeString(&unicodeName, L"InquiryData");
+ status = ZwSetValueKey(targetKey,
+ &unicodeName,
+ 0,
+ REG_BINARY,
+ InquiryData,
+ InquiryDataLength);
+ }
+
+ // that's all, except to clean up.
+
+ } FINALLY {
+
+ if (!NT_SUCCESS(status)) {
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT,
+ "Failure to update information in registry: %08x\n",
+ status
+ ));
+ }
+
+ if (unicodeData.Buffer) {
+ RtlFreeUnicodeString(&unicodeData);
+ }
+ if (unicodeRegistryPath.Buffer) {
+ RtlFreeUnicodeString(&unicodeRegistryPath);
+ }
+ if (targetKey) {
+ ZwClose(targetKey);
+ }
+
+ }
+
+} // end ClassUpdateInformationInRegistry()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClasspSendSynchronousCompletion()
+
+Routine Description:
+
+ This completion routine will set the user event in the irp after
+ freeing the irp and the associated MDL (if any).
+
+Arguments:
+
+ DeviceObject - the device object which requested the completion routine
+
+ Irp - the irp being completed
+
+ Context - unused
+
+Return Value:
+
+ STATUS_MORE_PROCESSING_REQUIRED
+
+--*/
+NTSTATUS
+ClasspSendSynchronousCompletion(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN PVOID Context
+ )
+{
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClasspSendSynchronousCompletion: %p %p %p\n",
+ DeviceObject, Irp, Context));
+ //
+ // First set the status and information fields in the io status block
+ // provided by the caller.
+ //
+
+ *(Irp->UserIosb) = Irp->IoStatus;
+
+ //
+ // Unlock the pages for the data buffer.
+ //
+
+ if(Irp->MdlAddress) {
+ MmUnlockPages(Irp->MdlAddress);
+ IoFreeMdl(Irp->MdlAddress);
+ }
+
+ //
+ // Signal the caller's event.
+ //
+
+ KeSetEvent(Irp->UserEvent, IO_NO_INCREMENT, FALSE);
+
+ //
+ // Free the MDL and the IRP.
+ //
+
+ IoFreeIrp(Irp);
+
+ return STATUS_MORE_PROCESSING_REQUIRED;
+} // end ClasspSendSynchronousCompletion()
+
+/*++
+
+ ISSUE-2000/02/20-henrygab Not documented ClasspRegisterMountedDeviceInterface
+
+--*/
+VOID
+ClasspRegisterMountedDeviceInterface(
+ IN PDEVICE_OBJECT DeviceObject
+ )
+{
+
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+ BOOLEAN isFdo = commonExtension->IsFdo;
+ PDEVICE_OBJECT pdo;
+ UNICODE_STRING interfaceName;
+
+ NTSTATUS status;
+
+ PAGED_CODE();
+
+ if(isFdo) {
+
+ PFUNCTIONAL_DEVICE_EXTENSION functionalExtension;
+
+ functionalExtension =
+ (PFUNCTIONAL_DEVICE_EXTENSION) commonExtension;
+ pdo = functionalExtension->LowerPdo;
+ } else {
+ pdo = DeviceObject;
+ }
+
+#pragma prefast(suppress:6014, "The allocated memory that interfaceName points to will be freed in ClassRemoveDevice().")
+ status = IoRegisterDeviceInterface(
+ pdo,
+ &MOUNTDEV_MOUNTED_DEVICE_GUID,
+ NULL,
+ &interfaceName
+ );
+
+ if(NT_SUCCESS(status)) {
+
+ //
+ // Copy the interface name before setting the interface state - the
+ // name is needed by the components we notify.
+ //
+
+ commonExtension->MountedDeviceInterfaceName = interfaceName;
+ status = IoSetDeviceInterfaceState(&interfaceName, TRUE);
+
+ if(!NT_SUCCESS(status)) {
+ RtlFreeUnicodeString(&interfaceName);
+ }
+ }
+
+ if(!NT_SUCCESS(status)) {
+ RtlInitUnicodeString(&(commonExtension->MountedDeviceInterfaceName),
+ NULL);
+ }
+ return;
+} // end ClasspRegisterMountedDeviceInterface()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassSendDeviceIoControlSynchronous()
+
+Routine Description:
+
+ This routine is based upon IoBuildDeviceIoControlRequest(). It has been
+ modified to reduce code and memory by not double-buffering the io, using
+ the same buffer for both input and output, allocating and deallocating
+ the mdl on behalf of the caller, and waiting for the io to complete.
+
+ This routine also works around the rare cases in which APC's are disabled.
+ Since IoBuildDeviceIoControl() used APC's to signal completion, this had
+ led to a number of difficult-to-detect hangs, where the irp was completed,
+ but the event passed to IoBuild..() was still being waited upon by the
+ caller.
+
+Arguments:
+
+ IoControlCode - the IOCTL to send
+
+ TargetDeviceObject - the device object that should handle the ioctl
+
+ Buffer - the input and output buffer, or NULL if no input/output
+
+ InputBufferLength - the number of bytes prepared for the IOCTL in Buffer
+
+ OutputBufferLength - the number of bytes to be filled in upon success
+
+ InternalDeviceIoControl - if TRUE, uses IRP_MJ_INTERNAL_DEVICE_CONTROL
+
+ IoStatus - the status block that contains the results of the operation
+
+Return Value:
+
+--*/
+VOID
+ClassSendDeviceIoControlSynchronous(
+ _In_ ULONG IoControlCode,
+ _In_ PDEVICE_OBJECT TargetDeviceObject,
+ _Inout_updates_opt_(_Inexpressible_(max(InputBufferLength, OutputBufferLength))) PVOID Buffer,
+ _In_ ULONG InputBufferLength,
+ _In_ ULONG OutputBufferLength,
+ _In_ BOOLEAN InternalDeviceIoControl,
+ _Out_ PIO_STATUS_BLOCK IoStatus
+ )
+{
+ PIRP irp;
+ PIO_STACK_LOCATION irpSp;
+ ULONG method;
+
+ PAGED_CODE();
+
+ irp = NULL;
+ method = IoControlCode & 3;
+
+ #if DBG // Begin Argument Checking (nop in fre version)
+
+ NT_ASSERT(ARGUMENT_PRESENT(IoStatus));
+
+ if ((InputBufferLength != 0) || (OutputBufferLength != 0)) {
+ NT_ASSERT(ARGUMENT_PRESENT(Buffer));
+ }
+ else {
+ NT_ASSERT(!ARGUMENT_PRESENT(Buffer));
+ }
+ #endif
+
+ //
+ // Begin by allocating the IRP for this request. Do not charge quota to
+ // the current process for this IRP.
+ //
+
+ irp = IoAllocateIrp(TargetDeviceObject->StackSize, FALSE);
+ if (!irp) {
+ IoStatus->Information = 0;
+ IoStatus->Status = STATUS_INSUFFICIENT_RESOURCES;
+ return;
+ }
+
+ //
+ // Get a pointer to the stack location of the first driver which will be
+ // invoked. This is where the function codes and the parameters are set.
+ //
+
+ irpSp = IoGetNextIrpStackLocation(irp);
+
+ //
+ // Set the major function code based on the type of device I/O control
+ // function the caller has specified.
+ //
+
+ if (InternalDeviceIoControl) {
+ irpSp->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
+ } else {
+ irpSp->MajorFunction = IRP_MJ_DEVICE_CONTROL;
+ }
+
+ //
+ // Copy the caller's parameters to the service-specific portion of the
+ // IRP for those parameters that are the same for all four methods.
+ //
+
+ irpSp->Parameters.DeviceIoControl.OutputBufferLength = OutputBufferLength;
+ irpSp->Parameters.DeviceIoControl.InputBufferLength = InputBufferLength;
+ irpSp->Parameters.DeviceIoControl.IoControlCode = IoControlCode;
+
+ //
+ // Get the method bits from the I/O control code to determine how the
+ // buffers are to be passed to the driver.
+ //
+
+ switch (method)
+ {
+ //
+ // case 0
+ //
+ case METHOD_BUFFERED:
+ {
+ if ((InputBufferLength != 0) || (OutputBufferLength != 0))
+ {
+ irp->AssociatedIrp.SystemBuffer = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned,
+ max(InputBufferLength, OutputBufferLength),
+ CLASS_TAG_DEVICE_CONTROL);
+ if (irp->AssociatedIrp.SystemBuffer == NULL)
+ {
+ IoFreeIrp(irp);
+
+ IoStatus->Information = 0;
+ IoStatus->Status = STATUS_INSUFFICIENT_RESOURCES;
+ return;
+ }
+
+ if (InputBufferLength != 0)
+ {
+ RtlCopyMemory(irp->AssociatedIrp.SystemBuffer, Buffer, InputBufferLength);
+ }
+ }
+
+ irp->UserBuffer = Buffer;
+
+ break;
+ }
+
+ //
+ // case 1, case 2
+ //
+ case METHOD_IN_DIRECT:
+ case METHOD_OUT_DIRECT:
+ {
+ if (InputBufferLength != 0)
+ {
+ irp->AssociatedIrp.SystemBuffer = Buffer;
+ }
+
+ if (OutputBufferLength != 0)
+ {
+ irp->MdlAddress = IoAllocateMdl(Buffer,
+ OutputBufferLength,
+ FALSE,
+ FALSE,
+ (PIRP) NULL);
+ if (irp->MdlAddress == NULL)
+ {
+ IoFreeIrp(irp);
+
+ IoStatus->Information = 0;
+ IoStatus->Status = STATUS_INSUFFICIENT_RESOURCES;
+ return;
+ }
+
+ try
+ {
+ MmProbeAndLockPages(irp->MdlAddress,
+ KernelMode,
+ (method == METHOD_IN_DIRECT) ? IoReadAccess : IoWriteAccess);
+ }
+ #pragma warning(suppress: 6320) // We want to handle any exception that MmProbeAndLockPages might throw
+ except(EXCEPTION_EXECUTE_HANDLER)
+ {
+ IoFreeMdl(irp->MdlAddress);
+ IoFreeIrp(irp);
+
+ IoStatus->Information = 0;
+ IoStatus->Status = GetExceptionCode();
+ return;
+ }
+ }
+
+ break;
+ }
+
+ //
+ // case 3
+ //
+ case METHOD_NEITHER:
+ {
+ NT_ASSERT(!"ClassSendDeviceIoControlSynchronous does not support METHOD_NEITHER Ioctls");
+
+ IoFreeIrp(irp);
+
+ IoStatus->Information = 0;
+ IoStatus->Status = STATUS_NOT_SUPPORTED;
+ return;
+ }
+ }
+
+ irp->Tail.Overlay.Thread = PsGetCurrentThread();
+
+ //
+ // send the irp synchronously
+ //
+
+ ClassSendIrpSynchronous(TargetDeviceObject, irp);
+
+ //
+ // copy the iostatus block for the caller
+ //
+
+ *IoStatus = irp->IoStatus;
+
+ //
+ // free any allocated resources
+ //
+
+ switch (method) {
+ case METHOD_BUFFERED: {
+
+ NT_ASSERT(irp->UserBuffer == Buffer);
+
+ //
+ // first copy the buffered result, if any
+ // Note that there are no security implications in
+ // not checking for success since only drivers can
+ // call into this routine anyways...
+ //
+
+ if (OutputBufferLength != 0) {
+ #pragma warning(suppress: 6386) // Buffer's size is max(InputBufferLength, OutputBufferLength)
+ RtlCopyMemory(Buffer, // irp->UserBuffer
+ irp->AssociatedIrp.SystemBuffer,
+ OutputBufferLength
+ );
+ }
+
+ //
+ // then free the memory allocated to buffer the io
+ //
+
+ if ((InputBufferLength !=0) || (OutputBufferLength != 0)) {
+ FREE_POOL(irp->AssociatedIrp.SystemBuffer);
+ }
+ break;
+ }
+
+ case METHOD_IN_DIRECT:
+ case METHOD_OUT_DIRECT: {
+
+ //
+ // we alloc a mdl if there is an output buffer specified
+ // free it here after unlocking the pages
+ //
+
+ if (OutputBufferLength != 0) {
+ NT_ASSERT(irp->MdlAddress != NULL);
+ MmUnlockPages(irp->MdlAddress);
+ IoFreeMdl(irp->MdlAddress);
+ irp->MdlAddress = (PMDL) NULL;
+ }
+ break;
+ }
+
+ case METHOD_NEITHER: {
+ NT_ASSERT(!"Code is out of date");
+ break;
+ }
+ }
+
+ //
+ // we always have allocated an irp. free it here.
+ //
+
+ IoFreeIrp(irp);
+ irp = (PIRP) NULL;
+
+ //
+ // return the io status block's status to the caller
+ //
+
+ return;
+} // end ClassSendDeviceIoControlSynchronous()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassForwardIrpSynchronous()
+
+Routine Description:
+
+ Forwards a given irp to the next lower device object.
+
+Arguments:
+
+ CommonExtension - the common class extension
+
+ Irp - the request to forward down the stack
+
+Return Value:
+
+--*/
+NTSTATUS
+ClassForwardIrpSynchronous(
+ _In_ PCOMMON_DEVICE_EXTENSION CommonExtension,
+ _In_ PIRP Irp
+ )
+{
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+ return ClassSendIrpSynchronous(CommonExtension->LowerDeviceObject, Irp);
+} // end ClassForwardIrpSynchronous()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassSendIrpSynchronous()
+
+Routine Description:
+
+ This routine sends the given irp to the given device object, and waits for
+ it to complete. On debug versions, will print out a debug message and
+ optionally assert for "lost" irps based upon classpnp's globals
+
+Arguments:
+
+ TargetDeviceObject - the device object to handle this irp
+
+ Irp - the request to be sent
+
+Return Value:
+
+--*/
+NTSTATUS
+ClassSendIrpSynchronous(
+ _In_ PDEVICE_OBJECT TargetDeviceObject,
+ _In_ PIRP Irp
+ )
+{
+ KEVENT event;
+ NTSTATUS status;
+
+ NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
+ NT_ASSERT(TargetDeviceObject != NULL);
+ NT_ASSERT(Irp != NULL);
+ NT_ASSERT(Irp->StackCount >= TargetDeviceObject->StackSize);
+
+ //
+ // ISSUE-2000/02/20-henrygab What if APCs are disabled?
+ // May need to enter critical section before IoCallDriver()
+ // until the event is hit?
+ //
+
+ KeInitializeEvent(&event, SynchronizationEvent, FALSE);
+ IoSetCompletionRoutine(Irp, ClassSignalCompletion, &event,
+ TRUE, TRUE, TRUE);
+
+ status = IoCallDriver(TargetDeviceObject, Irp);
+ _Analysis_assume_(status!=STATUS_PENDING);
+ if (status == STATUS_PENDING) {
+
+ #if DBG
+ LARGE_INTEGER timeout;
+
+ timeout.QuadPart = (LONGLONG)(-1 * 10 * 1000 * (LONGLONG)1000 *
+ ClasspnpGlobals.SecondsToWaitForIrps);
+
+ do {
+ status = KeWaitForSingleObject(&event,
+ Executive,
+ KernelMode,
+ FALSE,
+ &timeout);
+
+
+ if (status == STATUS_TIMEOUT) {
+
+ //
+ // This DebugPrint should almost always be investigated by the
+ // party who sent the irp and/or the current owner of the irp.
+ // Synchronous Irps should not take this long (currently 30
+ // seconds) without good reason. This points to a potentially
+ // serious problem in the underlying device stack.
+ //
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassSendIrpSynchronous: (%p) irp %p did not "
+ "complete within %x seconds\n",
+ TargetDeviceObject, Irp,
+ ClasspnpGlobals.SecondsToWaitForIrps
+ ));
+
+ if (ClasspnpGlobals.BreakOnLostIrps != 0) {
+ NT_ASSERT(!" - Irp failed to complete within 30 seconds - ");
+ }
+ }
+
+
+ } while (status==STATUS_TIMEOUT);
+ #else
+ (VOID)KeWaitForSingleObject(&event,
+ Executive,
+ KernelMode,
+ FALSE,
+ NULL);
+ #endif
+
+ status = Irp->IoStatus.Status;
+ }
+
+ return status;
+} // end ClassSendIrpSynchronous()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassGetVpb()
+
+Routine Description:
+
+ This routine returns the current VPB (Volume Parameter Block) for the
+ given device object.
+ The Vpb field is only visible in the ntddk.h (not the wdm.h) definition
+ of DEVICE_OBJECT; hence this exported function.
+
+Arguments:
+
+ DeviceObject - the device to get the VPB for
+
+Return Value:
+
+ the VPB, or NULL if none.
+
+--*/
+PVPB
+ClassGetVpb(
+ _In_ PDEVICE_OBJECT DeviceObject
+ )
+{
+#pragma prefast(suppress:28175)
+ return DeviceObject->Vpb;
+} // end ClassGetVpb()
+
+/*++
+
+ ISSUE-2000/02/20-henrygab Not documented ClasspAllocateReleaseRequest
+
+--*/
+NTSTATUS
+ClasspAllocateReleaseRequest(
+ IN PDEVICE_OBJECT Fdo
+ )
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
+
+ PAGED_CODE();
+
+ KeInitializeSpinLock(&(fdoExtension->ReleaseQueueSpinLock));
+
+ fdoExtension->ReleaseQueueNeeded = FALSE;
+ fdoExtension->ReleaseQueueInProgress = FALSE;
+ fdoExtension->ReleaseQueueIrpFromPool = FALSE;
+
+ //
+ // The class driver is responsible for allocating a properly sized irp,
+ // or ClassReleaseQueue will attempt to do it on the first error.
+ //
+
+ fdoExtension->ReleaseQueueIrp = NULL;
+
+ //
+ // Write length to SRB.
+ //
+
+ fdoExtension->ReleaseQueueSrb.Length = sizeof(SCSI_REQUEST_BLOCK);
+
+ return STATUS_SUCCESS;
+} // end ClasspAllocateReleaseRequest()
+
+/*++
+
+ ISSUE-2000/02/20-henrygab Not documented ClasspFreeReleaseRequest
+
+--*/
+VOID
+ClasspFreeReleaseRequest(
+ IN PDEVICE_OBJECT Fdo
+ )
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
+
+ PAGED_CODE();
+
+ //KIRQL oldIrql;
+
+ NT_ASSERT(fdoExtension->CommonExtension.IsRemoved != NO_REMOVE);
+
+ //
+ // free anything the driver allocated
+ //
+
+ if (fdoExtension->ReleaseQueueIrp) {
+ if (fdoExtension->ReleaseQueueIrpFromPool) {
+ FREE_POOL(fdoExtension->ReleaseQueueIrp);
+ } else {
+ IoFreeIrp(fdoExtension->ReleaseQueueIrp);
+ }
+ fdoExtension->ReleaseQueueIrp = NULL;
+ }
+
+ //
+ // free anything that we allocated
+ //
+
+ if ((fdoExtension->PrivateFdoData) &&
+ (fdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated)) {
+
+ FREE_POOL(fdoExtension->PrivateFdoData->ReleaseQueueIrp);
+ fdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated = FALSE;
+ }
+
+ return;
+} // end ClasspFreeReleaseRequest()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassReleaseQueue()
+
+Routine Description:
+
+ This routine issues an internal device control command
+ to the port driver to release a frozen queue. The call
+ is issued asynchronously as ClassReleaseQueue will be invoked
+ from the IO completion DPC (and will have no context to
+ wait for a synchronous call to complete).
+
+ This routine must be called with the remove lock held.
+
+Arguments:
+
+ Fdo - The functional device object for the device with the frozen queue.
+
+Return Value:
+
+ None.
+
+--*/
+VOID
+ClassReleaseQueue(
+ _In_ PDEVICE_OBJECT Fdo
+ )
+{
+ ClasspReleaseQueue(Fdo, NULL);
+ return;
+} // end ClassReleaseQueue()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClasspAllocateReleaseQueueIrp()
+
+Routine Description:
+
+ This routine allocates the release queue irp held in classpnp's private
+ extension. This was added to allow no-memory conditions to be more
+ survivable.
+
+Return Value:
+
+ NT_SUCCESS value.
+
+Notes:
+
+ Does not grab the spinlock. Should only be called from StartDevice()
+ routine. May be called elsewhere for poorly-behaved drivers that cause
+ the queue to lockup before the device is started. This should *never*
+ occur, since it's illegal to send a request to a non-started PDO. This
+ condition is checked for in ClasspReleaseQueue().
+
+--*/
+NTSTATUS
+ClasspAllocateReleaseQueueIrp(
+ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ )
+{
+ UCHAR lowerStackSize;
+
+ //
+ // do an initial check w/o the spinlock
+ //
+
+ if (FdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated) {
+ return STATUS_SUCCESS;
+ }
+
+
+ lowerStackSize = FdoExtension->CommonExtension.LowerDeviceObject->StackSize;
+
+ //
+ // don't allocate one if one is in progress! this means whoever called
+ // this routine didn't check if one was in progress.
+ //
+
+ NT_ASSERT(!(FdoExtension->ReleaseQueueInProgress));
+
+ FdoExtension->PrivateFdoData->ReleaseQueueIrp =
+ ExAllocatePoolWithTag(NonPagedPoolNx,
+ IoSizeOfIrp(lowerStackSize),
+ CLASS_TAG_RELEASE_QUEUE
+ );
+
+ if (FdoExtension->PrivateFdoData->ReleaseQueueIrp == NULL) {
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP, "ClassPnpStartDevice: Cannot allocate for "
+ "release queue irp\n"));
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+ IoInitializeIrp(FdoExtension->PrivateFdoData->ReleaseQueueIrp,
+ IoSizeOfIrp(lowerStackSize),
+ lowerStackSize);
+ FdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated = TRUE;
+
+ return STATUS_SUCCESS;
+}
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClasspAllocatePowerProcessIrp()
+
+Routine Description:
+
+ This routine allocates the power process irp.
+ This routine should be called after PrivateFdoData is allocated.
+
+Return Value:
+
+ NTSTATUS value.
+
+Notes:
+
+ Should only be called from StartDevice()
+
+--*/
+NTSTATUS
+ClasspAllocatePowerProcessIrp(
+ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ )
+{
+ UCHAR stackSize;
+
+ NT_ASSERT(FdoExtension->PrivateFdoData != NULL);
+
+ stackSize = FdoExtension->CommonExtension.LowerDeviceObject->StackSize + 1;
+
+ FdoExtension->PrivateFdoData->PowerProcessIrp = ExAllocatePoolWithTag(NonPagedPoolNx,
+ IoSizeOfIrp(stackSize),
+ CLASS_TAG_POWER
+ );
+
+ if (FdoExtension->PrivateFdoData->PowerProcessIrp == NULL) {
+
+ return STATUS_INSUFFICIENT_RESOURCES;
+ } else {
+
+ IoInitializeIrp(FdoExtension->PrivateFdoData->PowerProcessIrp,
+ IoSizeOfIrp(stackSize),
+ stackSize);
+
+ return STATUS_SUCCESS;
+ }
+}
+
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClasspReleaseQueue()
+
+Routine Description:
+
+ This routine issues an internal device control command
+ to the port driver to release a frozen queue. The call
+ is issued asynchronously as ClassReleaseQueue will be invoked
+ from the IO completion DPC (and will have no context to
+ wait for a synchronous call to complete).
+
+ This routine must be called with the remove lock held.
+
+Arguments:
+
+ Fdo - The functional device object for the device with the frozen queue.
+
+ ReleaseQueueIrp - If this irp is supplied then the test to determine whether
+ a release queue request is in progress will be ignored.
+ The irp provided must be the IRP originally allocated
+ for release queue requests (so this parameter can only
+ really be provided by the release queue completion
+ routine.)
+
+Return Value:
+
+ None.
+
+--*/
+VOID
+ClasspReleaseQueue(
+ IN PDEVICE_OBJECT Fdo,
+ IN PIRP ReleaseQueueIrp OPTIONAL
+ )
+{
+ PIO_STACK_LOCATION irpStack;
+ PIRP irp;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
+ PDEVICE_OBJECT lowerDevice;
+ PSTORAGE_REQUEST_BLOCK_HEADER srb;
+ KIRQL currentIrql;
+ ULONG function;
+
+ lowerDevice = fdoExtension->CommonExtension.LowerDeviceObject;
+
+ //
+ // we raise irql seperately so we're not swapped out or suspended
+ // while holding the release queue irp in this routine. this lets
+ // us release the spin lock before lowering irql.
+ //
+
+ KeRaiseIrql(DISPATCH_LEVEL, &currentIrql);
+
+ KeAcquireSpinLockAtDpcLevel(&(fdoExtension->ReleaseQueueSpinLock));
+
+ //
+ // make sure that if they passed us an irp, it matches our allocated irp.
+ //
+
+ NT_ASSERT((ReleaseQueueIrp == NULL) ||
+ (ReleaseQueueIrp == fdoExtension->PrivateFdoData->ReleaseQueueIrp));
+
+ //
+ // ASSERT that we've already allocated this. (should not occur)
+ // try to allocate it anyways, then finally bugcheck if
+ // there's still no memory...
+ //
+
+ NT_ASSERT(fdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated);
+ if (!fdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated) {
+ ClasspAllocateReleaseQueueIrp(fdoExtension);
+ }
+ if (!fdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated) {
+ KeBugCheckEx(SCSI_DISK_DRIVER_INTERNAL, 0x12, (ULONG_PTR)Fdo, 0x0, 0x0);
+ }
+
+ if ((fdoExtension->ReleaseQueueInProgress) && (ReleaseQueueIrp == NULL)) {
+
+ //
+ // Someone is already using the irp - just set the flag to indicate that
+ // we need to release the queue again.
+ //
+
+ fdoExtension->ReleaseQueueNeeded = TRUE;
+ KeReleaseSpinLockFromDpcLevel(&(fdoExtension->ReleaseQueueSpinLock));
+ KeLowerIrql(currentIrql);
+ return;
+
+ }
+
+ //
+ // Mark that there is a release queue in progress and drop the spinlock.
+ //
+
+ fdoExtension->ReleaseQueueInProgress = TRUE;
+ if (ReleaseQueueIrp) {
+ irp = ReleaseQueueIrp;
+ } else {
+ irp = fdoExtension->PrivateFdoData->ReleaseQueueIrp;
+ }
+
+ if (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+ srb = (PSTORAGE_REQUEST_BLOCK_HEADER)&(fdoExtension->PrivateFdoData->ReleaseQueueSrb.SrbEx);
+ } else {
+ srb = (PSTORAGE_REQUEST_BLOCK_HEADER)&(fdoExtension->ReleaseQueueSrb);
+ }
+
+ KeReleaseSpinLockFromDpcLevel(&(fdoExtension->ReleaseQueueSpinLock));
+
+ NT_ASSERT(irp != NULL);
+
+ irpStack = IoGetNextIrpStackLocation(irp);
+
+ irpStack->MajorFunction = IRP_MJ_SCSI;
+
+ SrbSetOriginalRequest(srb, irp);
+
+ //
+ // Store the SRB address in next stack for port driver.
+ //
+
+ irpStack->Parameters.Scsi.Srb = (PSCSI_REQUEST_BLOCK)srb;
+
+ //
+ // If this device is removable then flush the queue. This will also
+ // release it.
+ //
+
+ if (TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA)){
+ function = SRB_FUNCTION_FLUSH_QUEUE;
+ }
+ else {
+ function = SRB_FUNCTION_RELEASE_QUEUE;
+ }
+
+ if (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+ ((PSTORAGE_REQUEST_BLOCK)srb)->SrbFunction = function;
+ } else {
+ srb->Function = (UCHAR)function;
+ }
+
+ ClassAcquireRemoveLock(Fdo, irp);
+
+ IoSetCompletionRoutine(irp,
+ ClassReleaseQueueCompletion,
+ Fdo,
+ TRUE,
+ TRUE,
+ TRUE);
+
+ IoCallDriver(lowerDevice, irp);
+
+ KeLowerIrql(currentIrql);
+
+ return;
+
+} // end ClassReleaseQueue()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassReleaseQueueCompletion()
+
+Routine Description:
+
+ This routine is called when an asynchronous I/O request
+ which was issused by the class driver completes. Examples of such requests
+ are release queue or START UNIT. This routine releases the queue if
+ necessary. It then frees the context and the IRP.
+
+Arguments:
+
+ DeviceObject - The device object for the logical unit; however since this
+ is the top stack location the value is NULL.
+
+ Irp - Supplies a pointer to the Irp to be processed.
+
+ Context - Supplies the context to be used to process this request.
+
+Return Value:
+
+ None.
+
+--*/
+NTSTATUS
+ClassReleaseQueueCompletion(
+ PDEVICE_OBJECT DeviceObject,
+ PIRP Irp,
+ PVOID Context
+ )
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
+ KIRQL oldIrql;
+
+ BOOLEAN releaseQueueNeeded;
+
+ if (Context == NULL) {
+ NT_ASSERT(Context != NULL);
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ DeviceObject = Context;
+
+ fdoExtension = DeviceObject->DeviceExtension;
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+
+ //
+ // Grab the spinlock and clear the release queue in progress flag so others
+ // can run. Save (and clear) the state of the release queue needed flag
+ // so that we can issue a new release queue outside the spinlock.
+ //
+
+ KeAcquireSpinLock(&(fdoExtension->ReleaseQueueSpinLock), &oldIrql);
+
+ releaseQueueNeeded = fdoExtension->ReleaseQueueNeeded;
+
+ fdoExtension->ReleaseQueueNeeded = FALSE;
+ fdoExtension->ReleaseQueueInProgress = FALSE;
+
+ KeReleaseSpinLock(&(fdoExtension->ReleaseQueueSpinLock), oldIrql);
+
+ //
+ // If we need a release queue then issue one now. Another processor may
+ // have already started one in which case we'll try to issue this one after
+ // it is done - but we should never recurse more than one deep.
+ //
+
+ if(releaseQueueNeeded) {
+ ClasspReleaseQueue(DeviceObject, Irp);
+ }
+
+ //
+ // Indicate the I/O system should stop processing the Irp completion.
+ //
+
+ return STATUS_MORE_PROCESSING_REQUIRED;
+
+} // ClassAsynchronousCompletion()
+
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassAcquireChildLock()
+
+Routine Description:
+
+ This routine acquires the lock protecting children PDOs. It may be
+ acquired recursively by the same thread, but must be release by the
+ thread once for each acquisition.
+
+Arguments:
+
+ FdoExtension - the device whose child list is protected.
+
+Return Value:
+
+ None
+
+--*/
+_IRQL_requires_max_(PASSIVE_LEVEL)
+VOID
+ClassAcquireChildLock(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ )
+{
+ PAGED_CODE();
+
+ if(FdoExtension->ChildLockOwner != KeGetCurrentThread()) {
+ (VOID)KeWaitForSingleObject(&FdoExtension->ChildLock,
+ Executive, KernelMode,
+ FALSE, NULL);
+
+ NT_ASSERT(FdoExtension->ChildLockOwner == NULL);
+ NT_ASSERT(FdoExtension->ChildLockAcquisitionCount == 0);
+
+ FdoExtension->ChildLockOwner = KeGetCurrentThread();
+ } else {
+ NT_ASSERT(FdoExtension->ChildLockAcquisitionCount != 0);
+ }
+
+ FdoExtension->ChildLockAcquisitionCount++;
+ return;
+}
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassReleaseChildLock() ISSUE-2000/02/18-henrygab - not documented
+
+Routine Description:
+
+ This routine releases the lock protecting children PDOs. It must be
+ called once for each time ClassAcquireChildLock was called.
+
+Arguments:
+
+ FdoExtension - the device whose child list is protected
+
+Return Value:
+
+ None.
+
+--*/
+VOID
+ClassReleaseChildLock(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ )
+{
+ NT_ASSERT(FdoExtension->ChildLockOwner == KeGetCurrentThread());
+ NT_ASSERT(FdoExtension->ChildLockAcquisitionCount != 0);
+
+ FdoExtension->ChildLockAcquisitionCount -= 1;
+
+ if(FdoExtension->ChildLockAcquisitionCount == 0) {
+ FdoExtension->ChildLockOwner = NULL;
+ KeSetEvent(&FdoExtension->ChildLock, IO_NO_INCREMENT, FALSE);
+ }
+
+ return;
+} // end ClassReleaseChildLock(
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassAddChild()
+
+Routine Description:
+
+ This routine will insert a new child into the head of the child list.
+
+Arguments:
+
+ Parent - the child's parent (contains the head of the list)
+ Child - the child to be inserted.
+ AcquireLock - whether the child lock should be acquired (TRUE) or whether
+ it's already been acquired by or on behalf of the caller
+ (FALSE).
+
+Return Value:
+
+ None.
+
+--*/
+VOID
+ClassAddChild(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION Parent,
+ _In_ PPHYSICAL_DEVICE_EXTENSION Child,
+ _In_ BOOLEAN AcquireLock
+ )
+{
+ if(AcquireLock) {
+ ClassAcquireChildLock(Parent);
+ }
+
+ #if DBG
+ //
+ // Make sure this child's not already in the list.
+ //
+ {
+ PPHYSICAL_DEVICE_EXTENSION testChild;
+
+ for (testChild = Parent->CommonExtension.ChildList;
+ testChild != NULL;
+ testChild = testChild->CommonExtension.ChildList) {
+
+ NT_ASSERT(testChild != Child);
+ }
+ }
+ #endif
+
+ Child->CommonExtension.ChildList = Parent->CommonExtension.ChildList;
+ Parent->CommonExtension.ChildList = Child;
+
+ if(AcquireLock) {
+ ClassReleaseChildLock(Parent);
+ }
+ return;
+} // end ClassAddChild()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassRemoveChild()
+
+Routine Description:
+
+ This routine will remove a child from the child list.
+
+Arguments:
+
+ Parent - the parent to be removed from.
+
+ Child - the child to be removed or NULL if the first child should be
+ removed.
+
+ AcquireLock - whether the child lock should be acquired (TRUE) or whether
+ it's already been acquired by or on behalf of the caller
+ (FALSE).
+
+Return Value:
+
+ A pointer to the child which was removed or NULL if no such child could
+ be found in the list (or if Child was NULL but the list is empty).
+
+--*/
+PPHYSICAL_DEVICE_EXTENSION
+ClassRemoveChild(
+ IN PFUNCTIONAL_DEVICE_EXTENSION Parent,
+ IN PPHYSICAL_DEVICE_EXTENSION Child,
+ IN BOOLEAN AcquireLock
+ )
+{
+ if(AcquireLock) {
+ ClassAcquireChildLock(Parent);
+ }
+
+ TRY {
+ PCOMMON_DEVICE_EXTENSION previousChild = &Parent->CommonExtension;
+
+ //
+ // If the list is empty then bail out now.
+ //
+
+ if(Parent->CommonExtension.ChildList == NULL) {
+ Child = NULL;
+ LEAVE;
+ }
+
+ //
+ // If the caller specified a child then find the child object before
+ // it. If none was specified then the FDO is the child object before
+ // the one we want to remove.
+ //
+
+ if(Child != NULL) {
+
+ //
+ // Scan through the child list to find the entry which points to
+ // this one.
+ //
+
+ do {
+ NT_ASSERT(previousChild != &Child->CommonExtension);
+
+ if(previousChild->ChildList == Child) {
+ break;
+ }
+
+ previousChild = &previousChild->ChildList->CommonExtension;
+ } while(previousChild != NULL);
+
+ if(previousChild == NULL) {
+ Child = NULL;
+ LEAVE;
+ }
+ }
+
+ //
+ // Save the next child away then unlink it from the list.
+ //
+
+ Child = previousChild->ChildList;
+ previousChild->ChildList = Child->CommonExtension.ChildList;
+ Child->CommonExtension.ChildList = NULL;
+
+ } FINALLY {
+ if(AcquireLock) {
+ ClassReleaseChildLock(Parent);
+ }
+ }
+ return Child;
+} // end ClassRemoveChild()
+
+
+/*++
+
+ ISSUE-2000/02/20-henrygab Not documented ClasspRetryRequestDpc
+
+--*/
+VOID
+ClasspRetryRequestDpc(
+ IN PKDPC Dpc,
+ IN PVOID DeferredContext,
+ IN PVOID Arg1,
+ IN PVOID Arg2
+ )
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
+ PCOMMON_DEVICE_EXTENSION commonExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData;
+ PCLASS_RETRY_INFO retryList;
+ KIRQL irql;
+ PDEVICE_OBJECT DeviceObject = (PDEVICE_OBJECT)DeferredContext;
+
+ UNREFERENCED_PARAMETER(Dpc);
+ UNREFERENCED_PARAMETER(Arg1);
+ UNREFERENCED_PARAMETER(Arg2);
+
+ if (DeferredContext == NULL) {
+ NT_ASSERT(DeferredContext != NULL);
+ return;
+ }
+
+ commonExtension = DeviceObject->DeviceExtension;
+ NT_ASSERT(commonExtension->IsFdo);
+ fdoExtension = DeviceObject->DeviceExtension;
+ fdoData = fdoExtension->PrivateFdoData;
+
+
+ KeAcquireSpinLock(&fdoData->Retry.Lock, &irql);
+ {
+ LARGE_INTEGER now;
+ KeQueryTickCount(&now);
+
+ //
+ // if CurrentTick is less than now
+ // fire another DPC
+ // else
+ // retry entire list
+ // endif
+ //
+
+ if (now.QuadPart < fdoData->Retry.Tick.QuadPart) {
+
+ ClasspRetryDpcTimer(fdoData);
+ retryList = NULL;
+
+ } else {
+
+ retryList = fdoData->Retry.ListHead;
+ fdoData->Retry.ListHead = NULL;
+ fdoData->Retry.Delta.QuadPart = (LONGLONG)0;
+ fdoData->Retry.Tick.QuadPart = (LONGLONG)0;
+
+ }
+ }
+ KeReleaseSpinLock(&fdoData->Retry.Lock, irql);
+
+ while (retryList != NULL) {
+
+ PIRP irp;
+
+
+ irp = CONTAINING_RECORD(retryList, IRP, Tail.Overlay.DriverContext[0]);
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassRetry: -- %p\n", irp));
+ retryList = retryList->Next;
+ #if DBG
+ irp->Tail.Overlay.DriverContext[0] = ULongToPtr(0xdddddddd); // invalidate data
+ irp->Tail.Overlay.DriverContext[1] = ULongToPtr(0xdddddddd); // invalidate data
+ irp->Tail.Overlay.DriverContext[2] = ULongToPtr(0xdddddddd); // invalidate data
+ irp->Tail.Overlay.DriverContext[3] = ULongToPtr(0xdddddddd); // invalidate data
+ #endif
+
+
+ if (NO_REMOVE == InterlockedCompareExchange((volatile LONG *)&commonExtension->IsRemoved, REMOVE_PENDING, REMOVE_PENDING)) {
+
+ IoCallDriver(commonExtension->LowerDeviceObject, irp);
+
+ } else {
+
+ PIO_STACK_LOCATION irpStack;
+
+ //
+ // Ensure that we don't skip a completion routine (equivalent of sending down a request
+ // to a device after it has received a remove and it completes the request. We need to
+ // mimic that behavior here).
+ //
+ IoSetNextIrpStackLocation(irp);
+
+ irpStack = IoGetCurrentIrpStackLocation(irp);
+
+ if (irpStack->MajorFunction == IRP_MJ_SCSI) {
+
+ PSCSI_REQUEST_BLOCK srb = irpStack->Parameters.Scsi.Srb;
+
+ if (srb) {
+ srb->SrbStatus = SRB_STATUS_NO_DEVICE;
+ }
+ }
+
+ //
+ // Ensure that no retries will take place. This takes care of requests that are either
+ // not IRP_MJ_SCSI or for ones where the SRB was passed in to the completion routine
+ // as a context as opposed to an argument on the IRP stack location.
+ //
+ irpStack->Parameters.Others.Argument4 = (PVOID)0;
+
+ irp->IoStatus.Status = STATUS_NO_SUCH_DEVICE;
+ irp->IoStatus.Information = 0;
+ IoCompleteRequest(irp, IO_NO_INCREMENT);
+ }
+
+ }
+ return;
+
+} // end ClasspRetryRequestDpc()
+
+/*++
+
+ ISSUE-2000/02/20-henrygab Not documented ClassRetryRequest
+
+--*/
+VOID
+ClassRetryRequest(
+ IN PDEVICE_OBJECT SelfDeviceObject,
+ IN PIRP Irp,
+ _In_ _In_range_(0,MAXIMUM_RETRY_FOR_SINGLE_IO_IN_100NS_UNITS) // 100 seconds; already an assert on this...
+ IN LONGLONG TimeDelta100ns // in 100ns units
+ )
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData;
+ PCLASS_RETRY_INFO retryInfo;
+ LARGE_INTEGER delta;
+ KIRQL irql;
+
+ //
+ // this checks we aren't destroying irps
+ //
+ NT_ASSERT(sizeof(CLASS_RETRY_INFO) <= (4*sizeof(PVOID)));
+
+ fdoExtension = SelfDeviceObject->DeviceExtension;
+
+ if (!fdoExtension->CommonExtension.IsFdo) {
+
+ //
+ // this debug print/assertion should ALWAYS be investigated.
+ // ClassRetryRequest can currently only be used by FDO's
+ //
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassRetryRequestEx: LOST IRP %p\n", Irp));
+ NT_ASSERT(!"ClassRetryRequestEx Called From PDO? LOST IRP");
+ return;
+
+ }
+
+ fdoData = fdoExtension->PrivateFdoData;
+
+ if (TimeDelta100ns < 0) {
+ NT_ASSERT(!"ClassRetryRequest - must use positive delay");
+ TimeDelta100ns *= -1;
+ }
+
+ /*
+ * We are going to queue the irp and send it down in a timer DPC.
+ * This means that we may be causing the irp to complete on a different thread than the issuing thread.
+ * So mark the irp pending.
+ */
+ IoMarkIrpPending(Irp);
+
+ //
+ // prepare what we can out of the loop
+ //
+
+ retryInfo = (PCLASS_RETRY_INFO)(&Irp->Tail.Overlay.DriverContext[0]);
+ RtlZeroMemory(retryInfo, sizeof(CLASS_RETRY_INFO));
+
+ delta.QuadPart = (TimeDelta100ns / fdoData->Retry.Granularity);
+ if (TimeDelta100ns % fdoData->Retry.Granularity) {
+ delta.QuadPart ++; // round up to next tick
+ }
+ if (delta.QuadPart == (LONGLONG)0) {
+ delta.QuadPart = MINIMUM_RETRY_UNITS;
+ }
+
+ //
+ // now determine if we should fire another DPC or not
+ //
+
+ KeAcquireSpinLock(&fdoData->Retry.Lock, &irql);
+
+ //
+ // always add request to the list
+ //
+
+ retryInfo->Next = fdoData->Retry.ListHead;
+ fdoData->Retry.ListHead = retryInfo;
+
+ if (fdoData->Retry.Delta.QuadPart == (LONGLONG)0) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassRetry: +++ %p\n", Irp));
+
+ //
+ // must be exactly one item on list
+ //
+
+ NT_ASSERT(fdoData->Retry.ListHead != NULL);
+ NT_ASSERT(fdoData->Retry.ListHead->Next == NULL);
+
+ //
+ // if currentDelta is zero, always fire a DPC
+ //
+
+ KeQueryTickCount(&fdoData->Retry.Tick);
+ fdoData->Retry.Tick.QuadPart += delta.QuadPart;
+ fdoData->Retry.Delta.QuadPart = delta.QuadPart;
+ ClasspRetryDpcTimer(fdoData);
+
+ } else if (delta.QuadPart > fdoData->Retry.Delta.QuadPart) {
+
+ //
+ // if delta is greater than the list's current delta,
+ // increase the DPC handling time by difference
+ // and update the delta to new larger value
+ // allow the DPC to re-fire itself if needed
+ //
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassRetry: ++ %p\n", Irp));
+
+ //
+ // must be at least two items on list
+ //
+
+ NT_ASSERT(fdoData->Retry.ListHead != NULL);
+ NT_ASSERT(fdoData->Retry.ListHead->Next != NULL);
+
+ fdoData->Retry.Tick.QuadPart -= fdoData->Retry.Delta.QuadPart;
+ fdoData->Retry.Tick.QuadPart += delta.QuadPart;
+
+ fdoData->Retry.Delta.QuadPart = delta.QuadPart;
+
+ } else {
+
+ //
+ // just inserting it on the list was enough
+ //
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassRetry: ++ %p\n", Irp));
+
+ }
+
+
+ KeReleaseSpinLock(&fdoData->Retry.Lock, irql);
+
+
+} // end ClassRetryRequest()
+
+/*++
+
+ ISSUE-2000/02/20-henrygab Not documented ClasspRetryDpcTimer
+
+--*/
+VOID
+ClasspRetryDpcTimer(
+ IN PCLASS_PRIVATE_FDO_DATA FdoData
+ )
+{
+ LARGE_INTEGER fire;
+
+ NT_ASSERT(FdoData->Retry.Tick.QuadPart != (LONGLONG)0);
+ NT_ASSERT(FdoData->Retry.ListHead != NULL); // never fire an empty list
+
+ //
+ // fire == (CurrentTick - now) * (100ns per tick)
+ //
+ // NOTE: Overflow is nearly impossible and is ignored here
+ //
+
+ KeQueryTickCount(&fire);
+ fire.QuadPart = FdoData->Retry.Tick.QuadPart - fire.QuadPart;
+ fire.QuadPart *= FdoData->Retry.Granularity;
+
+ //
+ // fire is now multiples of 100ns until should fire the timer.
+ // if timer should already have expired, or would fire too quickly,
+ // fire it in some arbitrary number of ticks to prevent infinitely
+ // recursing.
+ //
+
+ if (fire.QuadPart < MINIMUM_RETRY_UNITS) {
+ fire.QuadPart = MINIMUM_RETRY_UNITS;
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,
+ "ClassRetry: ======= %I64x ticks\n",
+ fire.QuadPart));
+
+ //
+ // must use negative to specify relative time to fire
+ //
+
+ fire.QuadPart = fire.QuadPart * ((LONGLONG)-1);
+
+ //
+ // set the timer, since this is the first addition
+ //
+
+ KeSetTimerEx(&FdoData->Retry.Timer, fire, 0, &FdoData->Retry.Dpc);
+
+ return;
+} // end ClasspRetryDpcTimer()
+
+NTSTATUS
+ClasspInitializeHotplugInfo(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ )
+{
+ PCLASS_PRIVATE_FDO_DATA fdoData = FdoExtension->PrivateFdoData;
+ DEVICE_REMOVAL_POLICY deviceRemovalPolicy = 0;
+ NTSTATUS status;
+ ULONG resultLength = 0;
+ ULONG writeCacheOverride;
+
+ PAGED_CODE();
+
+ //
+ // start with some default settings
+ //
+ RtlZeroMemory(&(fdoData->HotplugInfo), sizeof(STORAGE_HOTPLUG_INFO));
+
+ //
+ // set the size (aka version)
+ //
+
+ fdoData->HotplugInfo.Size = sizeof(STORAGE_HOTPLUG_INFO);
+
+ //
+ // set if the device has removable media
+ //
+
+ if (FdoExtension->DeviceDescriptor->RemovableMedia) {
+ fdoData->HotplugInfo.MediaRemovable = TRUE;
+ } else {
+ fdoData->HotplugInfo.MediaRemovable = FALSE;
+ }
+
+ //
+ // this refers to devices which, for reasons not yet understood,
+ // do not fail PREVENT_MEDIA_REMOVAL requests even though they
+ // have no way to lock the media into the drive. this allows
+ // the filesystems to turn off delayed-write caching for these
+ // devices as well.
+ //
+
+ if (TEST_FLAG(FdoExtension->PrivateFdoData->HackFlags,
+ FDO_HACK_CANNOT_LOCK_MEDIA)) {
+ fdoData->HotplugInfo.MediaHotplug = TRUE;
+ } else {
+ fdoData->HotplugInfo.MediaHotplug = FALSE;
+ }
+
+ //
+ // Query the default removal policy from the kernel
+ //
+
+ status = IoGetDeviceProperty(FdoExtension->LowerPdo,
+ DevicePropertyRemovalPolicy,
+ sizeof(DEVICE_REMOVAL_POLICY),
+ (PVOID)&deviceRemovalPolicy,
+ &resultLength);
+ if (!NT_SUCCESS(status)) {
+ return status;
+ }
+
+ if (resultLength != sizeof(DEVICE_REMOVAL_POLICY)) {
+ return STATUS_UNSUCCESSFUL;
+ }
+
+ //
+ // Look into the registry to see if the user has chosen
+ // to override the default setting for the removal policy.
+ // User can override only if the default removal policy is
+ // orderly or suprise removal.
+
+ if ((deviceRemovalPolicy == RemovalPolicyExpectOrderlyRemoval) ||
+ (deviceRemovalPolicy == RemovalPolicyExpectSurpriseRemoval)) {
+
+ DEVICE_REMOVAL_POLICY userRemovalPolicy = 0;
+
+ ClassGetDeviceParameter(FdoExtension,
+ CLASSP_REG_SUBKEY_NAME,
+ CLASSP_REG_REMOVAL_POLICY_VALUE_NAME,
+ (PULONG)&userRemovalPolicy);
+
+ //
+ // Validate the override value and use it only if it is an
+ // allowed value.
+ //
+ if ((userRemovalPolicy == RemovalPolicyExpectOrderlyRemoval) ||
+ (userRemovalPolicy == RemovalPolicyExpectSurpriseRemoval)) {
+ deviceRemovalPolicy = userRemovalPolicy;
+ }
+ }
+
+ //
+ // use this info to set the DeviceHotplug setting
+ // don't rely on DeviceCapabilities, since it can't properly
+ // determine device relations, etc. let the kernel figure this
+ // stuff out instead.
+ //
+
+ if (deviceRemovalPolicy == RemovalPolicyExpectSurpriseRemoval) {
+ fdoData->HotplugInfo.DeviceHotplug = TRUE;
+ } else {
+ fdoData->HotplugInfo.DeviceHotplug = FALSE;
+ }
+
+ //
+ // this refers to the *filesystem* caching, but has to be included
+ // here since it's a per-device setting. this may change to be
+ // stored by the system in the future.
+ //
+
+ writeCacheOverride = FALSE;
+ ClassGetDeviceParameter(FdoExtension,
+ CLASSP_REG_SUBKEY_NAME,
+ CLASSP_REG_WRITE_CACHE_VALUE_NAME,
+ &writeCacheOverride);
+
+ if (writeCacheOverride) {
+ fdoData->HotplugInfo.WriteCacheEnableOverride = TRUE;
+ } else {
+ fdoData->HotplugInfo.WriteCacheEnableOverride = FALSE;
+ }
+
+ return STATUS_SUCCESS;
+}
+
+VOID
+ClasspScanForClassHacks(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ IN ULONG_PTR Data
+ )
+{
+ PAGED_CODE();
+
+ //
+ // remove invalid flags and save
+ //
+
+ CLEAR_FLAG(Data, FDO_HACK_INVALID_FLAGS);
+ SET_FLAG(FdoExtension->PrivateFdoData->HackFlags, Data);
+ return;
+}
+
+VOID
+ClasspScanForSpecialInRegistry(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ )
+{
+ HANDLE deviceParameterHandle; // device instance key
+ HANDLE classParameterHandle; // classpnp subkey
+ OBJECT_ATTRIBUTES objectAttributes = {0};
+ UNICODE_STRING subkeyName;
+ NTSTATUS status;
+
+ //
+ // seeded in the ENUM tree by ClassInstaller
+ //
+ ULONG deviceHacks;
+ RTL_QUERY_REGISTRY_TABLE queryTable[2] = {0}; // null terminated array
+
+ PAGED_CODE();
+
+ deviceParameterHandle = NULL;
+ classParameterHandle = NULL;
+ deviceHacks = 0;
+
+ status = IoOpenDeviceRegistryKey(FdoExtension->LowerPdo,
+ PLUGPLAY_REGKEY_DEVICE,
+ KEY_WRITE,
+ &deviceParameterHandle
+ );
+
+ if (!NT_SUCCESS(status)) {
+ goto cleanupScanForSpecial;
+ }
+
+ RtlInitUnicodeString(&subkeyName, CLASSP_REG_SUBKEY_NAME);
+ InitializeObjectAttributes(&objectAttributes,
+ &subkeyName,
+ OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
+ deviceParameterHandle,
+ NULL
+ );
+
+ status = ZwOpenKey( &classParameterHandle,
+ KEY_READ,
+ &objectAttributes
+ );
+
+ if (!NT_SUCCESS(status)) {
+ goto cleanupScanForSpecial;
+ }
+
+ //
+ // Setup the structure to read
+ //
+
+ queryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT;
+ queryTable[0].Name = CLASSP_REG_HACK_VALUE_NAME;
+ queryTable[0].EntryContext = &deviceHacks;
+ queryTable[0].DefaultType = REG_DWORD;
+ queryTable[0].DefaultData = &deviceHacks;
+ queryTable[0].DefaultLength = 0;
+
+ //
+ // read values
+ //
+
+ status = RtlQueryRegistryValues(RTL_REGISTRY_HANDLE,
+ (PWSTR)classParameterHandle,
+ &queryTable[0],
+ NULL,
+ NULL
+ );
+ if (!NT_SUCCESS(status)) {
+ goto cleanupScanForSpecial;
+ }
+
+ //
+ // remove unknown values and save...
+ //
+
+ CLEAR_FLAG(deviceHacks, FDO_HACK_INVALID_FLAGS);
+ SET_FLAG(FdoExtension->PrivateFdoData->HackFlags, deviceHacks);
+
+
+cleanupScanForSpecial:
+
+ if (deviceParameterHandle) {
+ ZwClose(deviceParameterHandle);
+ }
+
+ if (classParameterHandle) {
+ ZwClose(classParameterHandle);
+ }
+
+ //
+ // we should modify the system hive to include another key for us to grab
+ // settings from. in this case: Classpnp\HackFlags
+ //
+ // the use of a DWORD value for the HackFlags allows 32 hacks w/o
+ // significant use of the registry, and also reduces OEM exposure.
+ //
+ // definition of bit flags:
+ // 0x00000001 -- Device succeeds PREVENT_MEDIUM_REMOVAL, but
+ // cannot actually prevent removal.
+ // 0x00000002 -- Device hard-hangs or times out for GESN requests.
+ // 0xfffffffc -- Currently reserved, may be used later.
+ //
+
+ return;
+}
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClasspUpdateDiskProperties()
+
+Routine Description:
+
+ This routine will send IOCTL_DISK_UPDATE_PROPERTIES to top of stack - Partition Manager
+ to invalidate the cached geometry.
+
+Arguments:
+
+ Fdo - The device object whose capacity needs to be verified.
+
+Return Value:
+
+ NTSTATUS
+
+--*/
+
+VOID
+ClasspUpdateDiskProperties(
+ IN PDEVICE_OBJECT Fdo,
+ IN PVOID Context
+ )
+{
+ PDEVICE_OBJECT topOfStack;
+ IO_STATUS_BLOCK ioStatus;
+ NTSTATUS status = STATUS_SUCCESS;
+ KEVENT event;
+ PIRP irp = NULL;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
+ PIO_WORKITEM WorkItem = (PIO_WORKITEM)Context;
+
+ KeInitializeEvent(&event, SynchronizationEvent, FALSE);
+
+ topOfStack = IoGetAttachedDeviceReference(Fdo);
+
+ //
+ // Send down irp to update properties
+ //
+
+ irp = IoBuildDeviceIoControlRequest(
+ IOCTL_DISK_UPDATE_PROPERTIES,
+ topOfStack,
+ NULL,
+ 0,
+ NULL,
+ 0,
+ FALSE,
+ &event,
+ &ioStatus);
+
+ if (irp != NULL) {
+
+
+ status = IoCallDriver(topOfStack, irp);
+ if (status == STATUS_PENDING) {
+ (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
+ status = ioStatus.Status;
+ }
+
+ } else {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ InterlockedExchange((volatile LONG *)&fdoData->UpdateDiskPropertiesWorkItemActive, 0);
+
+ if (!NT_SUCCESS(status)) {
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, "ClasspUpdateDiskProperties: Disk property update for fdo %p failed with status 0x%X.\n", Fdo, status));
+ } else {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "ClasspUpdateDiskProperties: Drive capacity has changed for %p.\n", Fdo));
+ }
+ ObDereferenceObject(topOfStack);
+
+ if (WorkItem != NULL) {
+ IoFreeWorkItem(WorkItem);
+ }
+
+ return;
+}
+
+BOOLEAN
+InterpretSenseInfoWithoutHistory(
+ _In_ PDEVICE_OBJECT Fdo,
+ _In_opt_ PIRP OriginalRequest,
+ _In_ PSCSI_REQUEST_BLOCK Srb,
+ UCHAR MajorFunctionCode,
+ ULONG IoDeviceCode,
+ ULONG PreviousRetryCount,
+ _Out_ NTSTATUS * Status,
+ _Out_opt_ _Deref_out_range_(0,MAXIMUM_RETRY_FOR_SINGLE_IO_IN_100NS_UNITS)
+ LONGLONG * RetryIn100nsUnits
+ )
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
+ LONGLONG tmpRetry = 0;
+ BOOLEAN retry = FALSE;
+
+ if (fdoData->InterpretSenseInfo != NULL)
+ {
+ SCSI_REQUEST_BLOCK tempSrb = {0};
+ PSCSI_REQUEST_BLOCK srbPtr = Srb;
+
+ // SAL annotations and ClassInitializeEx() both validate this
+ NT_ASSERT(fdoData->InterpretSenseInfo->Interpret != NULL);
+
+ //
+ // If class driver does not support extended SRB and this is
+ // an extended SRB, convert to legacy SRB and pass to class
+ // driver.
+ //
+ if ((Srb->Function == SRB_FUNCTION_STORAGE_REQUEST_BLOCK) &&
+ ((fdoExtension->CommonExtension.DriverExtension->SrbSupport &
+ CLASS_SRB_STORAGE_REQUEST_BLOCK) == 0)) {
+ ClasspConvertToScsiRequestBlock(&tempSrb, (PSTORAGE_REQUEST_BLOCK)Srb);
+ srbPtr = &tempSrb;
+ }
+
+ retry = fdoData->InterpretSenseInfo->Interpret(Fdo,
+ OriginalRequest,
+ srbPtr,
+ MajorFunctionCode,
+ IoDeviceCode,
+ PreviousRetryCount,
+ NULL,
+ Status,
+ &tmpRetry);
+ }
+ else
+ {
+ ULONG seconds = 0;
+
+ retry = ClassInterpretSenseInfo(Fdo,
+ Srb,
+ MajorFunctionCode,
+ IoDeviceCode,
+ PreviousRetryCount,
+ Status,
+ &seconds);
+ tmpRetry = ((LONGLONG)seconds) * 1000 * 1000 * 10;
+ }
+
+
+ if (RetryIn100nsUnits != NULL)
+ {
+ *RetryIn100nsUnits = tmpRetry;
+ }
+ return retry;
+}
+
+VOID
+ClasspGetInquiryVpdSupportInfo(
+ _Inout_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ )
+{
+ NTSTATUS status;
+ PCOMMON_DEVICE_EXTENSION commonExtension = (PCOMMON_DEVICE_EXTENSION)FdoExtension;
+ SCSI_REQUEST_BLOCK srb = {0};
+ PCDB cdb;
+ PVPD_SUPPORTED_PAGES_PAGE supportedPages = NULL;
+ UCHAR bufferLength = VPD_MAX_BUFFER_SIZE;
+ ULONG allocationBufferLength = bufferLength;
+ UCHAR srbExBuffer[CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE] = {0};
+ PSTORAGE_REQUEST_BLOCK_HEADER srbHeader;
+
+#if defined(_ARM_) || defined(_ARM64_)
+ //
+ // ARM has specific alignment requirements, although this will not have a functional impact on x86 or amd64
+ // based platforms. We are taking the conservative approach here.
+ //
+ allocationBufferLength = ALIGN_UP_BY(allocationBufferLength,KeGetRecommendedSharedDataAlignment());
+ supportedPages = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned,
+ allocationBufferLength,
+ '3CcS'
+ );
+
+#else
+ supportedPages = ExAllocatePoolWithTag(NonPagedPoolNx,
+ bufferLength,
+ '3CcS'
+ );
+#endif
+
+ if (supportedPages == NULL) {
+ // memory allocation failure.
+ return;
+ }
+
+ RtlZeroMemory(supportedPages, allocationBufferLength);
+
+ // prepare the Srb
+ if (FdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+
+ #pragma prefast(suppress:26015, "InitializeStorageRequestBlock ensures buffer access is bounded")
+ status = InitializeStorageRequestBlock((PSTORAGE_REQUEST_BLOCK)srbExBuffer,
+ STORAGE_ADDRESS_TYPE_BTL8,
+ sizeof(srbExBuffer),
+ 1,
+ SrbExDataTypeScsiCdb16);
+
+ if (!NT_SUCCESS(status)) {
+ //
+ // Should not happen. Revert to legacy SRB.
+ NT_ASSERT(FALSE);
+ srb.Length = SCSI_REQUEST_BLOCK_SIZE;
+ srb.Function = SRB_FUNCTION_EXECUTE_SCSI;
+ srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)&srb;
+ } else {
+ ((PSTORAGE_REQUEST_BLOCK)srbExBuffer)->SrbFunction = SRB_FUNCTION_EXECUTE_SCSI;
+ srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)srbExBuffer;
+ }
+
+ } else {
+ srb.Length = SCSI_REQUEST_BLOCK_SIZE;
+ srb.Function = SRB_FUNCTION_EXECUTE_SCSI;
+ srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)&srb;
+ }
+
+ SrbSetTimeOutValue(srbHeader, FdoExtension->TimeOutValue);
+ SrbSetRequestTag(srbHeader, SP_UNTAGGED);
+ SrbSetRequestAttribute(srbHeader, SRB_SIMPLE_TAG_REQUEST);
+ SrbAssignSrbFlags(srbHeader, FdoExtension->SrbFlags);
+ SrbSetCdbLength(srbHeader, 6);
+
+ cdb = SrbGetCdb(srbHeader);
+ cdb->CDB6INQUIRY3.OperationCode = SCSIOP_INQUIRY;
+ cdb->CDB6INQUIRY3.EnableVitalProductData = 1; //EVPD bit
+ cdb->CDB6INQUIRY3.PageCode = VPD_SUPPORTED_PAGES;
+ cdb->CDB6INQUIRY3.AllocationLength = bufferLength; //AllocationLength field in CDB6INQUIRY3 is only one byte.
+
+ status = ClassSendSrbSynchronous(commonExtension->DeviceObject,
+ (PSCSI_REQUEST_BLOCK)srbHeader,
+ supportedPages,
+ allocationBufferLength,
+ FALSE);
+
+ //
+ // Handle the case where we get back STATUS_DATA_OVERRUN b/c the input
+ // buffer was larger than necessary.
+ //
+ if (status == STATUS_DATA_OVERRUN && SrbGetDataTransferLength(srbHeader) < bufferLength)
+ {
+ status = STATUS_SUCCESS;
+ }
+
+ if ( NT_SUCCESS(status) &&
+ (supportedPages->PageLength > 0) &&
+ (supportedPages->SupportedPageList[0] > 0) ) {
+ // ataport treats all INQUIRY command as standard INQUIRY command, thus fills invalid info
+ // If VPD INQUIRY is supported, the first page reported (additional length field for standard INQUIRY data) should be '00'
+ status = STATUS_NOT_SUPPORTED;
+ }
+
+ if (NT_SUCCESS(status)) {
+ int i;
+
+ for (i = 0; i < supportedPages->PageLength; i++) {
+ if ( (i > 0) && (supportedPages->SupportedPageList[i] <= supportedPages->SupportedPageList[i - 1]) ) {
+ // shall be in ascending order beginning with page code 00h.
+ FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits = FALSE;
+ FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockLimits = FALSE;
+ FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceCharacteristics = FALSE;
+ FdoExtension->FunctionSupportInfo->ValidInquiryPages.LBProvisioning = FALSE;
+
+
+ break;
+ }
+ switch (supportedPages->SupportedPageList[i]) {
+ case VPD_THIRD_PARTY_COPY:
+ FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits = TRUE;
+ break;
+
+ case VPD_BLOCK_LIMITS:
+ FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockLimits = TRUE;
+ break;
+
+ case VPD_BLOCK_DEVICE_CHARACTERISTICS:
+ FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceCharacteristics = TRUE;
+ break;
+
+ case VPD_LOGICAL_BLOCK_PROVISIONING:
+ FdoExtension->FunctionSupportInfo->ValidInquiryPages.LBProvisioning = TRUE;
+ break;
+
+ }
+ }
+ }
+
+ ExFreePool(supportedPages);
+
+ return;
+}
+
+//
+// ClasspGetLBProvisioningInfo
+//
+// Description: This function does the work to get Logical Block Provisioning
+// (LBP) info for a given LUN, including UNMAP support and parameters. It
+// will attempt to get both the Logical Block Provisioning (0xB2) VPD page
+// and the Block Limits (0xB0) VPD page and cache the relevant values in
+// the given FDO extension.
+//
+// After calling this function, you can use the ClasspIsThinProvisioned()
+// function to determine if the device is thinly provisioned and the
+// ClasspSupportsUnmap() function to determine if the device supports the
+// UNMAP command.
+//
+// Arguments:
+// - FdoExtension: The FDO extension associated with the LUN for which Thin
+// Provisioning info is desired. The Thin Provisioning info is stored
+// in the FunctionSupportInfo member of this FDO extension.
+//
+// Returns:
+// - STATUS_INVALID_PARAMETER if the given FDO extension has not been
+// allocated properly.
+// - STATUS_INSUFFICIENT_RESOURCES if this function was unable to allocate
+// an SRB used to get the necessary VPD pages.
+// - STATUS_SUCCESS in all other cases. If any of the incidental functions
+// don't return STATUS_SUCCESS this function will just assume Thin
+// Provisioning is not enabled and return STATUS_SUCCESS.
+//
+NTSTATUS
+ClasspGetLBProvisioningInfo(
+ _Inout_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ )
+{
+ PSCSI_REQUEST_BLOCK srb = NULL;
+ ULONG srbSize = 0;
+
+ //
+ // Make sure we actually have data structures to work with.
+ //
+ if (FdoExtension == NULL ||
+ FdoExtension->FunctionSupportInfo == NULL) {
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Allocate an SRB for querying the device for LBP-related info if either
+ // the Logical Block Provisioning (0xB2) or Block Limits (0xB0) VPD page
+ // exists.
+ //
+ if ((FdoExtension->FunctionSupportInfo->ValidInquiryPages.LBProvisioning == TRUE) ||
+ (FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockLimits == TRUE)) {
+
+ if ((FdoExtension->AdapterDescriptor != NULL) &&
+ (FdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK)) {
+ srbSize = CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE;
+ } else {
+ srbSize = sizeof(SCSI_REQUEST_BLOCK);
+ }
+
+ srb = ExAllocatePoolWithTag(NonPagedPoolNx, srbSize, '0DcS');
+
+ if (srb == NULL) {
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+ }
+
+ //
+ // Get the Logical Block Provisioning VPD page (0xB2). This function will
+ // set some default values if the LBP VPD page is not supported.
+ //
+ ClasspDeviceGetLBProvisioningVPDPage(FdoExtension->DeviceObject, srb);
+
+
+ //
+ // Get the Block Limits VPD page (0xB0), which may override the default
+ // UNMAP parameter values set above.
+ //
+ ClasspDeviceGetBlockLimitsVPDPage(FdoExtension,
+ srb,
+ srbSize,
+ &FdoExtension->FunctionSupportInfo->BlockLimitsData);
+
+ FREE_POOL(srb);
+
+ return STATUS_SUCCESS;
+}
+
+
+
+_IRQL_requires_(PASSIVE_LEVEL)
+_IRQL_requires_same_
+NTSTATUS
+ClassDetermineTokenOperationCommandSupport(
+ _In_ PDEVICE_OBJECT DeviceObject
+ )
+
+/*++
+
+Routine Description:
+
+ This routine determines if Token Operation and Receive ROD Token Information commands
+ are supported by this device and updates the internal structures to reflect this.
+ In addition, it also queries the registry to determine the maximum listIdentifier
+ to use for TokenOperation commands.
+
+ This function must be called at IRQL == PASSIVE_LEVEL.
+
+Arguments:
+
+ DeviceObject - The device object for which we want to determine command support.
+
+Return Value:
+
+ Nothing
+
+--*/
+
+{
+ NTSTATUS status = STATUS_SUCCESS;
+
+ PAGED_CODE();
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "ClassDetermineTokenOperationCommandSupport (%p): Entering function.\n",
+ DeviceObject));
+
+ //
+ // Send down Inquiry for VPD_THIRD_PARTY_COPY_PAGE and cache away the device parameters
+ // from WINDOWS_BLOCK_DEVICE_TOKEN_LIMITS_DESCRIPTOR.
+ //
+ status = ClasspGetBlockDeviceTokenLimitsInfo(DeviceObject);
+
+ if (NT_SUCCESS(status)) {
+
+ ULONG maxListIdentifier = MaxTokenOperationListIdentifier;
+
+ //
+ // Query the maximum list identifier to use for TokenOperation commands.
+ //
+ if (NT_SUCCESS(ClasspGetMaximumTokenListIdentifier(DeviceObject, REG_DISK_CLASS_CONTROL, &maxListIdentifier))) {
+ if (maxListIdentifier >= MIN_TOKEN_LIST_IDENTIFIERS) {
+
+ NT_ASSERT(maxListIdentifier <= MAX_TOKEN_LIST_IDENTIFIERS);
+ MaxTokenOperationListIdentifier = maxListIdentifier;
+ }
+ }
+
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "ClassDetermineTokenOperationCommandSupport (%p): Exiting function with status %x.\n",
+ DeviceObject,
+ status));
+
+ return status;
+}
+
+
+_IRQL_requires_same_
+NTSTATUS
+ClasspGetBlockDeviceTokenLimitsInfo(
+ _Inout_ PDEVICE_OBJECT DeviceObject
+ )
+
+/*++
+
+Routine Description:
+
+ This routine does the work to get the Block Device Token Limits info for a given LUN.
+ This is done by sending Inquiry for the Third Party Copy VPD page.
+
+Arguments:
+
+ DeviceObject - The FDO associated with the LUN for which Block Device Token
+ limits info is desired.
+
+Return Value:
+
+ STATUS_DEVICE_FEATURE_NOT_SUPPORTED if either the Inquiry fails or validations fail.
+ STATUS_SUCCESS otherwise.
+
+--*/
+
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
+ NTSTATUS status;
+ PSCSI_REQUEST_BLOCK srb = NULL;
+ ULONG srbSize = 0;
+ USHORT pageLength = 0;
+ USHORT descriptorLength = 0;
+ PVOID dataBuffer = NULL;
+ UCHAR bufferLength = VPD_MAX_BUFFER_SIZE;
+ ULONG allocationBufferLength = bufferLength;
+ PCDB cdb;
+ ULONG dataTransferLength = 0;
+ PVPD_THIRD_PARTY_COPY_PAGE operatingParameters = NULL;
+ PWINDOWS_BLOCK_DEVICE_TOKEN_LIMITS_DESCRIPTOR blockDeviceTokenLimits = NULL;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "ClasspGetBlockDeviceTokenLimitsInfo (%p): Entering function.\n",
+ DeviceObject));
+
+ //
+ // Allocate an SRB for querying the device for LBP-related info.
+ //
+ if ((fdoExtension->AdapterDescriptor != NULL) &&
+ (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK)) {
+ srbSize = CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE;
+ } else {
+ srbSize = sizeof(SCSI_REQUEST_BLOCK);
+ }
+
+ srb = ExAllocatePoolWithTag(NonPagedPoolNx, srbSize, CLASSPNP_POOL_TAG_SRB);
+
+ if (!srb) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "ClasspGetBlockDeviceTokenLimitsInfo (%p): Couldn't allocate SRB.\n",
+ DeviceObject));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __ClasspGetBlockDeviceTokenLimitsInfo_Exit;
+ }
+
+#if defined(_ARM_) || defined(_ARM64_)
+ //
+ // ARM has specific alignment requirements, although this will not have a functional impact on x86 or amd64
+ // based platforms. We are taking the conservative approach here.
+ //
+ allocationBufferLength = ALIGN_UP_BY(allocationBufferLength, KeGetRecommendedSharedDataAlignment());
+ dataBuffer = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned, allocationBufferLength, CLASSPNP_POOL_TAG_VPD);
+#else
+ dataBuffer = ExAllocatePoolWithTag(NonPagedPoolNx, bufferLength, CLASSPNP_POOL_TAG_VPD);
+#endif
+
+ if (!dataBuffer) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "ClasspGetBlockDeviceTokenLimitsInfo (%p): Couldn't allocate dataBuffer.\n",
+ DeviceObject));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __ClasspGetBlockDeviceTokenLimitsInfo_Exit;
+ }
+
+ operatingParameters = (PVPD_THIRD_PARTY_COPY_PAGE)dataBuffer;
+
+ RtlZeroMemory(dataBuffer, allocationBufferLength);
+
+ if ((fdoExtension->AdapterDescriptor != NULL) &&
+ (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK)) {
+ status = InitializeStorageRequestBlock((PSTORAGE_REQUEST_BLOCK)srb,
+ STORAGE_ADDRESS_TYPE_BTL8,
+ CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE,
+ 1,
+ SrbExDataTypeScsiCdb16);
+ if (NT_SUCCESS(status)) {
+
+ ((PSTORAGE_REQUEST_BLOCK)srb)->SrbFunction = SRB_FUNCTION_EXECUTE_SCSI;
+
+ } else {
+
+ //
+ // Should not occur. Revert to legacy SRB.
+ //
+ NT_ASSERT(FALSE);
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "ClasspGetBlockDeviceTokenLimitsInfo (%p): Falling back to using SRB (instead of SRB_EX).\n",
+ DeviceObject));
+
+ RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK));
+ srb->Length = sizeof(SCSI_REQUEST_BLOCK);
+ srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
+ }
+ } else {
+
+ RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK));
+ srb->Length = sizeof(SCSI_REQUEST_BLOCK);
+ srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
+ }
+
+ SrbSetTimeOutValue(srb, fdoExtension->TimeOutValue);
+ SrbSetRequestTag(srb, SP_UNTAGGED);
+ SrbSetRequestAttribute(srb, SRB_SIMPLE_TAG_REQUEST);
+ SrbAssignSrbFlags(srb, fdoExtension->SrbFlags);
+
+ SrbSetCdbLength(srb, 6);
+
+ cdb = SrbGetCdb(srb);
+ cdb->CDB6INQUIRY3.OperationCode = SCSIOP_INQUIRY;
+ cdb->CDB6INQUIRY3.EnableVitalProductData = 1;
+ cdb->CDB6INQUIRY3.PageCode = VPD_THIRD_PARTY_COPY;
+ cdb->CDB6INQUIRY3.AllocationLength = bufferLength;
+
+ status = ClassSendSrbSynchronous(fdoExtension->DeviceObject,
+ srb,
+ dataBuffer,
+ allocationBufferLength,
+ FALSE);
+
+ //
+ // Handle the case where we get back STATUS_DATA_OVERRUN because the input
+ // buffer was larger than necessary.
+ //
+ dataTransferLength = SrbGetDataTransferLength(srb);
+
+ if (status == STATUS_DATA_OVERRUN && dataTransferLength < bufferLength) {
+
+ status = STATUS_SUCCESS;
+ }
+
+ if (NT_SUCCESS(status)) {
+
+ REVERSE_BYTES_SHORT(&pageLength, &operatingParameters->PageLength);
+
+ } else {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_PNP,
+ "ClasspGetBlockDeviceTokenLimitsInfo (%p): Inquiry for TPC VPD failed with %x.\n",
+ DeviceObject,
+ status));
+ }
+
+ if ((NT_SUCCESS(status)) &&
+ (pageLength >= sizeof(WINDOWS_BLOCK_DEVICE_TOKEN_LIMITS_DESCRIPTOR))) {
+
+ USHORT descriptorType;
+
+ blockDeviceTokenLimits = (PWINDOWS_BLOCK_DEVICE_TOKEN_LIMITS_DESCRIPTOR)operatingParameters->ThirdPartyCopyDescriptors;
+ REVERSE_BYTES_SHORT(&descriptorType, &blockDeviceTokenLimits->DescriptorType);
+ REVERSE_BYTES_SHORT(&descriptorLength, &blockDeviceTokenLimits->DescriptorLength);
+
+ if ((descriptorType == BLOCK_DEVICE_TOKEN_LIMITS_DESCRIPTOR_TYPE_WINDOWS) &&
+ (VPD_PAGE_HEADER_SIZE + descriptorLength == sizeof(WINDOWS_BLOCK_DEVICE_TOKEN_LIMITS_DESCRIPTOR))) {
+
+ fdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits = TRUE;
+
+ REVERSE_BYTES_SHORT(&fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumRangeDescriptors, &blockDeviceTokenLimits->MaximumRangeDescriptors);
+ REVERSE_BYTES(&fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumInactivityTimer, &blockDeviceTokenLimits->MaximumInactivityTimer);
+ REVERSE_BYTES(&fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.DefaultInactivityTimer, &blockDeviceTokenLimits->DefaultInactivityTimer);
+ REVERSE_BYTES_QUAD(&fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumTokenTransferSize, &blockDeviceTokenLimits->MaximumTokenTransferSize);
+ REVERSE_BYTES_QUAD(&fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount, &blockDeviceTokenLimits->OptimalTransferCount);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_PNP,
+ "ClasspGetBlockDeviceTokenLimitsInfo (%p): %s %s (rev %s) reported following parameters: \
+ \n\t\t\tMaxRangeDescriptors: %u\n\t\t\tMaxIAT: %u\n\t\t\tDefaultIAT: %u \
+ \n\t\t\tMaxTokenTransferSize: %I64u\n\t\t\tOptimalTransferCount: %I64u\n\t\t\tOptimalTransferLengthGranularity: %u \
+ \n\t\t\tOptimalTransferLength: %u\n\t\t\tMaxTransferLength: %u\n",
+ DeviceObject,
+ (PCSZ)(((PUCHAR)fdoExtension->DeviceDescriptor) + fdoExtension->DeviceDescriptor->VendorIdOffset),
+ (PCSZ)(((PUCHAR)fdoExtension->DeviceDescriptor) + fdoExtension->DeviceDescriptor->ProductIdOffset),
+ (PCSZ)(((PUCHAR)fdoExtension->DeviceDescriptor) + fdoExtension->DeviceDescriptor->ProductRevisionOffset),
+ fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumRangeDescriptors,
+ fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumInactivityTimer,
+ fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.DefaultInactivityTimer,
+ fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumTokenTransferSize,
+ fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount,
+ fdoExtension->FunctionSupportInfo->BlockLimitsData.OptimalTransferLengthGranularity,
+ fdoExtension->FunctionSupportInfo->BlockLimitsData.OptimalTransferLength,
+ fdoExtension->FunctionSupportInfo->BlockLimitsData.MaximumTransferLength));
+ } else {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "ClasspGetBlockDeviceTokenLimitsInfo (%p): ThirdPartyCopy VPD data doesn't have Windows OffloadDataTransfer descriptor.\n",
+ DeviceObject));
+
+ fdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits = FALSE;
+ status = STATUS_DEVICE_FEATURE_NOT_SUPPORTED;
+ }
+ } else {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_PNP,
+ "ClasspGetBlockDeviceTokenLimitsInfo (%p): TPC VPD data didn't return TPC descriptors of interest.\n",
+ DeviceObject));
+
+ fdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits = FALSE;
+ status = STATUS_DEVICE_FEATURE_NOT_SUPPORTED;
+ }
+
+__ClasspGetBlockDeviceTokenLimitsInfo_Exit:
+
+ FREE_POOL(dataBuffer);
+ FREE_POOL(srb);
+ fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus = status;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_PNP,
+ "ClasspGetBlockDeviceTokenLimitsInfo (%p): Exiting function with status %x.\n",
+ DeviceObject,
+ status));
+
+ return status;
+}
+
+
+_IRQL_requires_max_(APC_LEVEL)
+_IRQL_requires_min_(PASSIVE_LEVEL)
+_IRQL_requires_same_
+NTSTATUS
+ClassDeviceProcessOffloadRead(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine services IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES for CopyOffload
+ Read. If the device supports copy offload, it performs the translation of the
+ IOCTL into the appropriate SCSI commands to complete the operation.
+
+ This function must be called at IRQL < DISPATCH_LEVEL.
+
+Arguments:
+
+ DeviceObject - Supplies the device object associated with this request
+ Irp - The IRP to be processed
+ Srb - An SRB that can be optinally used to process this request
+
+Return Value:
+
+ NTSTATUS code
+
+--*/
+
+{
+ PIO_STACK_LOCATION irpStack;
+ NTSTATUS status;
+ PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes;
+ PDEVICE_DSM_OFFLOAD_READ_PARAMETERS offloadReadParameters;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
+
+ UNREFERENCED_PARAMETER(Srb);
+
+ //
+ // This function must be called at less than dispatch level.
+ // Fail if IRQL >= DISPATCH_LEVEL.
+ //
+ PAGED_CODE();
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClassDeviceProcessOffloadRead (%p): Entering function. Irp %p\n",
+ DeviceObject,
+ Irp));
+
+
+ irpStack = IoGetCurrentIrpStackLocation (Irp);
+
+ //
+ // Validations
+ //
+ status = ClasspValidateOffloadSupported(DeviceObject, Irp);
+ if (!NT_SUCCESS(status)) {
+ goto __ClassDeviceProcessOffloadRead_CompleteAndExit;
+ }
+
+ if (KeGetCurrentIrql() >= DISPATCH_LEVEL) {
+
+ NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClassDeviceProcessOffloadRead (%p): Called at raised IRQL.\n",
+ DeviceObject));
+
+ status = STATUS_INVALID_LEVEL;
+ goto __ClassDeviceProcessOffloadRead_CompleteAndExit;
+ }
+
+ //
+ // Ensure that this DSM IOCTL was generated in kernel
+ //
+ if (Irp->RequestorMode != KernelMode) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClassDeviceProcessOffloadRead (%p): Called from user mode.\n",
+ DeviceObject));
+
+ status = STATUS_ACCESS_DENIED;
+ goto __ClassDeviceProcessOffloadRead_CompleteAndExit;
+ }
+
+ status = ClasspValidateOffloadInputParameters(DeviceObject, Irp);
+ if (!NT_SUCCESS(status)) {
+ goto __ClassDeviceProcessOffloadRead_CompleteAndExit;
+ }
+
+ dsmAttributes = Irp->AssociatedIrp.SystemBuffer;
+
+ //
+ // Validate that we were passed in correct sized parameter block.
+ //
+ if (dsmAttributes->ParameterBlockLength < sizeof(DEVICE_DSM_OFFLOAD_READ_PARAMETERS)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClassDeviceProcessOffloadRead (%p): Parameter block size (%u) too small. Required %u.\n",
+ DeviceObject,
+ dsmAttributes->ParameterBlockLength,
+ sizeof(DEVICE_DSM_OFFLOAD_READ_PARAMETERS)));
+
+ status = STATUS_INVALID_PARAMETER;
+ goto __ClassDeviceProcessOffloadRead_CompleteAndExit;
+ }
+
+ offloadReadParameters = Add2Ptr(dsmAttributes, dsmAttributes->ParameterBlockOffset);
+
+ fdoExtension = DeviceObject->DeviceExtension;
+
+ //
+ // If the request TTL is greater than the max supported by this storage, the target will
+ // end up failing this command, so might as well do the check up front.
+ //
+ if ((fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumInactivityTimer > 0) &&
+ (offloadReadParameters->TimeToLive > fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumInactivityTimer)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClassDeviceProcessOffloadRead (%p): Requested TTL (%u) greater than max supported (%u).\n",
+ DeviceObject,
+ offloadReadParameters->TimeToLive,
+ fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumInactivityTimer));
+
+ status = STATUS_INVALID_PARAMETER;
+ goto __ClassDeviceProcessOffloadRead_CompleteAndExit;
+ }
+
+ if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(STORAGE_OFFLOAD_READ_OUTPUT)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClassDeviceProcessOffloadRead (%p): Output buffer size (%u) too small.\n",
+ DeviceObject,
+ irpStack->Parameters.DeviceIoControl.OutputBufferLength));
+
+ status = STATUS_BUFFER_TOO_SMALL;
+ goto __ClassDeviceProcessOffloadRead_CompleteAndExit;
+ }
+
+
+
+ status = ClasspServicePopulateTokenTransferRequest(DeviceObject, Irp);
+
+ if (status == STATUS_PENDING) {
+ goto __ClassDeviceProcessOffloadRead_Exit;
+ }
+
+__ClassDeviceProcessOffloadRead_CompleteAndExit:
+ ClasspCompleteOffloadRequest(DeviceObject, Irp, status);
+__ClassDeviceProcessOffloadRead_Exit:
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClassDeviceProcessOffloadRead (%p): Exiting function Irp %p with status %x.\n",
+ DeviceObject,
+ Irp,
+ status));
+
+ return status;
+}
+
+VOID
+ClasspCompleteOffloadRequest(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PIRP Irp,
+ _In_ NTSTATUS CompletionStatus)
+{
+ NTSTATUS status = CompletionStatus;
+
+
+ Irp->IoStatus.Status = status;
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+
+ return;
+}
+
+
+_IRQL_requires_max_(APC_LEVEL)
+_IRQL_requires_min_(PASSIVE_LEVEL)
+_IRQL_requires_same_
+NTSTATUS
+ClassDeviceProcessOffloadWrite(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine services IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES for CopyOffload
+ Write. If the device supports copy offload, it performs the translation of the
+ IOCTL into the appropriate SCSI commands to complete the operation.
+
+ This function must be called at IRQL < DISPATCH_LEVEL.
+
+Arguments:
+
+ DeviceObject - Supplies the device object associated with this request
+ Irp - The IRP to be processed
+ Srb - An SRB that can be optinally used to process this request
+
+Return Value:
+
+ NTSTATUS code
+
+--*/
+
+{
+ PIO_STACK_LOCATION irpStack;
+ NTSTATUS status;
+ PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes;
+
+ UNREFERENCED_PARAMETER(Srb);
+
+ //
+ // This function must be called at less than dispatch level.
+ //
+ PAGED_CODE();
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClassDeviceProcessOffloadWrite (%p): Entering function. Irp %p\n",
+ DeviceObject,
+ Irp));
+
+
+ irpStack = IoGetCurrentIrpStackLocation(Irp);
+
+ //
+ // Validations
+ //
+ status = ClasspValidateOffloadSupported(DeviceObject, Irp);
+ if (!NT_SUCCESS(status)) {
+ goto __ClassDeviceProcessOffloadWrite_CompleteAndExit;
+ }
+
+ if (KeGetCurrentIrql() >= DISPATCH_LEVEL) {
+
+ NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClassDeviceProcessOffloadWrite (%p): Called at raised IRQL.\n",
+ DeviceObject));
+
+ status = STATUS_INVALID_LEVEL;
+ goto __ClassDeviceProcessOffloadWrite_CompleteAndExit;
+ }
+
+ //
+ // Ensure that this DSM IOCTL was generated in kernel
+ //
+ if (Irp->RequestorMode != KernelMode) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClassDeviceProcessOffloadWrite (%p): Called from user mode.\n",
+ DeviceObject));
+
+ status = STATUS_ACCESS_DENIED;
+ goto __ClassDeviceProcessOffloadWrite_CompleteAndExit;
+ }
+
+ status = ClasspValidateOffloadInputParameters(DeviceObject, Irp);
+ if (!NT_SUCCESS(status)) {
+ goto __ClassDeviceProcessOffloadWrite_CompleteAndExit;
+ }
+
+ dsmAttributes = Irp->AssociatedIrp.SystemBuffer;
+
+ //
+ // Validate that we were passed in correct sized parameter block.
+ //
+ if (dsmAttributes->ParameterBlockLength < sizeof(DEVICE_DSM_OFFLOAD_WRITE_PARAMETERS)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClassDeviceProcessOffloadWrite (%p): Parameter block size (%u) too small. Required %u.\n",
+ DeviceObject,
+ dsmAttributes->ParameterBlockLength,
+ sizeof(DEVICE_DSM_OFFLOAD_WRITE_PARAMETERS)));
+
+ status = STATUS_INVALID_PARAMETER;
+ goto __ClassDeviceProcessOffloadWrite_CompleteAndExit;
+ }
+
+ if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(STORAGE_OFFLOAD_WRITE_OUTPUT)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClassDeviceProcessOffloadWrite (%p): Output buffer size (%u) too small.\n",
+ DeviceObject,
+ irpStack->Parameters.DeviceIoControl.OutputBufferLength));
+
+ status = STATUS_BUFFER_TOO_SMALL;
+ goto __ClassDeviceProcessOffloadWrite_CompleteAndExit;
+ }
+
+
+
+ status = ClasspServiceWriteUsingTokenTransferRequest(DeviceObject, Irp);
+
+ if (status == STATUS_PENDING) {
+ goto __ClassDeviceProcessOffloadWrite_Exit;
+ }
+
+__ClassDeviceProcessOffloadWrite_CompleteAndExit:
+ ClasspCompleteOffloadRequest(DeviceObject, Irp, status);
+__ClassDeviceProcessOffloadWrite_Exit:
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClassDeviceProcessOffloadWrite (%p): Exiting function Irp %p with status %x\n",
+ DeviceObject,
+ Irp,
+ status));
+
+ return status;
+}
+
+
+_IRQL_requires_max_(APC_LEVEL)
+_IRQL_requires_min_(PASSIVE_LEVEL)
+_IRQL_requires_same_
+NTSTATUS
+ClasspServicePopulateTokenTransferRequest(
+ _In_ PDEVICE_OBJECT Fdo,
+ _In_ PIRP Irp
+ )
+
+/*++
+
+Routine description:
+
+ This routine processes offload read requests by building the SRB
+ for PopulateToken and its result (i.e. token).
+
+Arguments:
+
+ Fdo - The functional device object processing the request
+ Irp - The Io request to be processed
+
+Return Value:
+
+ STATUS_SUCCESS if successful, an error code otherwise
+
+--*/
+
+{
+ BOOLEAN allDataSetRangeFullyConverted;
+ UINT32 allocationSize;
+ ULONG blockDescrIndex;
+ PBLOCK_DEVICE_RANGE_DESCRIPTOR blockDescrPointer;
+ PVOID buffer;
+ ULONG bufferLength;
+ ULONG dataSetRangeIndex;
+ PDEVICE_DATA_SET_RANGE dataSetRanges;
+ ULONG dataSetRangesCount;
+ PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes;
+ ULONGLONG entireXferLen;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt;
+ ULONG i;
+ ULONG lbaCount;
+ ULONG listIdentifier;
+ ULONG maxBlockDescrCount;
+ ULONGLONG maxLbaCount;
+ POFFLOAD_READ_CONTEXT offloadReadContext;
+ PDEVICE_DSM_OFFLOAD_READ_PARAMETERS offloadReadParameters;
+ PTRANSFER_PACKET pkt;
+ USHORT populateTokenDataLength;
+ USHORT populateTokenDescriptorsLength;
+ PMDL populateTokenMdl;
+ PIRP pseudoIrp;
+ ULONG receiveTokenInformationBufferLength;
+ NTSTATUS status;
+ DEVICE_DATA_SET_RANGE tempDataSetRange;
+ BOOLEAN tempDataSetRangeFullyConverted;
+ PUCHAR token;
+ ULONG tokenLength;
+ ULONG tokenOperationBufferLength;
+ ULONGLONG totalSectorCount;
+ ULONGLONG totalSectorsToProcess;
+ ULONG transferSize;
+
+ PAGED_CODE();
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspServicePopulateTokenTransferRequest (%p): Entering function. Irp %p\n",
+ Fdo,
+ Irp));
+
+ fdoExt = Fdo->DeviceExtension;
+ status = STATUS_SUCCESS;
+ dsmAttributes = Irp->AssociatedIrp.SystemBuffer;
+ buffer = NULL;
+ pkt = NULL;
+ populateTokenMdl = NULL;
+ offloadReadParameters = Add2Ptr(dsmAttributes, dsmAttributes->ParameterBlockOffset);
+ dataSetRanges = Add2Ptr(dsmAttributes, dsmAttributes->DataSetRangesOffset);
+ dataSetRangesCount = dsmAttributes->DataSetRangesLength / sizeof(DEVICE_DATA_SET_RANGE);
+ totalSectorsToProcess = 0;
+ token = NULL;
+ tokenLength = 0;
+ bufferLength = 0;
+ offloadReadContext = NULL;
+
+ NT_ASSERT(fdoExt->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits &&
+ NT_SUCCESS(fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus));
+
+
+ for (i = 0, entireXferLen = 0; i < dataSetRangesCount; i++) {
+ entireXferLen += dataSetRanges[i].LengthInBytes;
+ }
+
+ //
+ // We need to truncate the read request based on the following hardware limitations:
+ // 1. The size of the data buffer containing the TokenOperation command's parameters must
+ // not exceed the MaximumTransferLength (and max physical pages) of the underlying
+ // adapter.
+ // 2. The number of descriptors specified in the TokenOperation command must not exceed
+ // the MaximumRangeDescriptors.
+ // 3. The cumulative total of the number of transfer blocks in all the descriptors in
+ // the TokenOperation command must not exceed the MaximumTokenTransferSize.
+ // 4. If the TTL has been set to the 0 (i.e. indicates the use of the default TLT),
+ // limit the cumulative total of the number of transfer blocks in all the descriptors
+ // in the TokenOperation command to the OptimalTransferCount.
+ // NOTE: only a TTL of 0 has an implicit indication that the initiator of the
+ // request would like the storage stack to be smart about the amount of data
+ // transfer.
+ //
+ // In addition to the above, we need to ensure that for each of the descriptors in the
+ // TokenOperation command:
+ // 1. The number of blocks specified is an exact multiple of the OptimalTransferLengthGranularity.
+ // 2. The number of blocks specified is limited to the MaximumTransferLength. (We shall
+ // however, limit the number of blocks specified in each descriptor to be a maximum of
+ // OptimalTransferLength or MaximumTransferLength, whichever is lesser).
+ //
+ // Finally, we shall always send down the PopulateToken command using IMMED = 0 for this
+ // release. This makes it simpler to handle multi-initiator scenarios since we won't need
+ // to deal with the PopulateToken (with IMMED = 1) succeeding but ReceiveRODTokenInformation
+ // failing due to the path/node failing, thus making it impossible to retrieve the token.
+ //
+ // The LBA ranges is in DEVICE_DATA_SET_RANGE format, it needs to be converted into
+ // WINDOWS_RANGE_DESCRIPTOR Block Descriptors.
+ //
+
+ ClasspGetTokenOperationCommandBufferLength(Fdo,
+ SERVICE_ACTION_POPULATE_TOKEN,
+ &bufferLength,
+ &tokenOperationBufferLength,
+ &receiveTokenInformationBufferLength);
+
+ allocationSize = sizeof(OFFLOAD_READ_CONTEXT) + bufferLength;
+
+ offloadReadContext = ExAllocatePoolWithTag(
+ NonPagedPoolNx,
+ allocationSize,
+ CLASSPNP_POOL_TAG_TOKEN_OPERATION);
+
+ if (!offloadReadContext) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspServicePopulateTokenTransferRequest (%p): Failed to allocate buffer for PopulateToken operations.\n",
+ Fdo));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __ClasspServicePopulateTokenTransferRequest_ErrorExit;
+ }
+
+ RtlZeroMemory(offloadReadContext, allocationSize);
+
+ offloadReadContext->Fdo = Fdo;
+ offloadReadContext->OffloadReadDsmIrp = Irp;
+
+ //
+ // The buffer for the commands is after the offloadReadContext.
+ //
+ buffer = (offloadReadContext + 1);
+
+ //
+ // No matter how large the offload read request, we'll be sending it down in one shot.
+ // So we need only one transfer packet.
+ //
+ pkt = DequeueFreeTransferPacket(Fdo, TRUE);
+ if (!pkt){
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspServicePopulateTokenTransferRequest (%p): Failed to retrieve transfer packet for TokenOperation (PopulateToken) operation.\n",
+ Fdo));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __ClasspServicePopulateTokenTransferRequest_ErrorExit;
+ }
+
+ offloadReadContext->Pkt = pkt;
+
+ ClasspGetTokenOperationDescriptorLimits(Fdo,
+ SERVICE_ACTION_POPULATE_TOKEN,
+ tokenOperationBufferLength,
+ &maxBlockDescrCount,
+ &maxLbaCount);
+
+ //
+ // We will limit the maximum data transfer in an offload read operation to:
+ // - OTC if TTL was specified as 0
+ // - MaximumTransferTransferSize if lesser than above, or if TTL was specified was non-zero
+ //
+ if ((offloadReadParameters->TimeToLive == 0) && (fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount > 0)) {
+
+ maxLbaCount = MIN(maxLbaCount, fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount);
+ }
+
+ //
+ // Since we do not want very fragmented files to end up causing the PopulateToken command to take
+ // too long (and potentially timeout), we will limit the max number of descriptors sent down in a
+ // command.
+ //
+ maxBlockDescrCount = MIN(maxBlockDescrCount, MAX_NUMBER_BLOCK_DEVICE_DESCRIPTORS);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "ClasspServicePopulateTokenTransferRequest (%p): Using MaxBlockDescrCount %u and MaxLbaCount %I64u.\n",
+ Fdo,
+ maxBlockDescrCount,
+ maxLbaCount));
+
+ allDataSetRangeFullyConverted = FALSE;
+ tempDataSetRangeFullyConverted = TRUE;
+ dataSetRangeIndex = (ULONG)-1;
+
+ blockDescrPointer = (PBLOCK_DEVICE_RANGE_DESCRIPTOR)
+ &((PPOPULATE_TOKEN_HEADER)buffer)->BlockDeviceRangeDescriptor[0];
+
+ RtlZeroMemory(&tempDataSetRange, sizeof(tempDataSetRange));
+
+ blockDescrIndex = 0;
+ lbaCount = 0;
+
+ //
+ // Send PopulateToken command when the buffer is full or all input entries are converted.
+ //
+ while (!((blockDescrIndex == maxBlockDescrCount) || // buffer full or block descriptor count reached
+ (lbaCount == maxLbaCount) || // block LBA count reached
+ (allDataSetRangeFullyConverted))) { // all DataSetRanges have been converted
+
+ //
+ // If the previous entry conversion completed, continue the next one;
+ // Otherwise, still process the left part of the un-completed entry.
+ //
+ if (tempDataSetRangeFullyConverted) {
+ dataSetRangeIndex++;
+ tempDataSetRange.StartingOffset = dataSetRanges[dataSetRangeIndex].StartingOffset;
+ tempDataSetRange.LengthInBytes = dataSetRanges[dataSetRangeIndex].LengthInBytes;
+ }
+
+ totalSectorCount = 0;
+
+ ClasspConvertDataSetRangeToBlockDescr(Fdo,
+ blockDescrPointer,
+ &blockDescrIndex,
+ maxBlockDescrCount,
+ &lbaCount,
+ maxLbaCount,
+ &tempDataSetRange,
+ &totalSectorCount);
+
+ tempDataSetRangeFullyConverted = (tempDataSetRange.LengthInBytes == 0) ? TRUE : FALSE;
+
+ allDataSetRangeFullyConverted = tempDataSetRangeFullyConverted && ((dataSetRangeIndex + 1) == dataSetRangesCount);
+
+ totalSectorsToProcess += totalSectorCount;
+ }
+
+ //
+ // Calculate transfer size, including the header
+ //
+ transferSize = (blockDescrIndex * sizeof(BLOCK_DEVICE_RANGE_DESCRIPTOR)) + FIELD_OFFSET(POPULATE_TOKEN_HEADER, BlockDeviceRangeDescriptor);
+
+ NT_ASSERT(transferSize <= MAX_TOKEN_OPERATION_PARAMETER_DATA_LENGTH);
+
+ populateTokenDataLength = (USHORT)transferSize - RTL_SIZEOF_THROUGH_FIELD(POPULATE_TOKEN_HEADER, PopulateTokenDataLength);
+ REVERSE_BYTES_SHORT(((PPOPULATE_TOKEN_HEADER)buffer)->PopulateTokenDataLength, &populateTokenDataLength);
+
+ ((PPOPULATE_TOKEN_HEADER)buffer)->Immediate = 0;
+
+ REVERSE_BYTES(((PPOPULATE_TOKEN_HEADER)buffer)->InactivityTimeout, &offloadReadParameters->TimeToLive);
+
+ populateTokenDescriptorsLength = (USHORT)transferSize - FIELD_OFFSET(POPULATE_TOKEN_HEADER, BlockDeviceRangeDescriptor);
+ REVERSE_BYTES_SHORT(((PPOPULATE_TOKEN_HEADER)buffer)->BlockDeviceRangeDescriptorListLength, &populateTokenDescriptorsLength);
+
+ //
+ // Reuse a single buffer for both TokenOperation and ReceiveTokenInformation. This has the one disadvantage
+ // that we'll be marking the page(s) as IoWriteAccess even though we only need read access for token
+ // operation command. However, the advantage is that we eliminate the possibility of any potential failures
+ // when trying to allocate an MDL for the ReceiveTokenInformation later on.
+ //
+ bufferLength = max(transferSize, receiveTokenInformationBufferLength);
+
+ populateTokenMdl = ClasspBuildDeviceMdl(buffer, bufferLength, FALSE);
+ if (!populateTokenMdl) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspServicePopulateTokenTransferRequest (%p): Failed to allocate MDL for PopulateToken operations.\n",
+ Fdo));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __ClasspServicePopulateTokenTransferRequest_ErrorExit;
+ }
+
+ offloadReadContext->PopulateTokenMdl = populateTokenMdl;
+
+ pseudoIrp = &offloadReadContext->PseudoIrp;
+
+
+ pseudoIrp->IoStatus.Status = STATUS_SUCCESS;
+ pseudoIrp->IoStatus.Information = 0;
+ pseudoIrp->Tail.Overlay.DriverContext[0] = LongToPtr(1);
+ pseudoIrp->MdlAddress = populateTokenMdl;
+
+ InterlockedCompareExchange((volatile LONG *)&TokenOperationListIdentifier, -1, MaxTokenOperationListIdentifier);
+ listIdentifier = InterlockedIncrement((volatile LONG *)&TokenOperationListIdentifier);
+
+ ClasspSetupPopulateTokenTransferPacket(
+ offloadReadContext,
+ pkt,
+ transferSize,
+ (PUCHAR)buffer,
+ pseudoIrp,
+ listIdentifier);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "ClasspServicePopulateTokenTransferRequest (%p): Generate token for %I64u bytes (versus %I64u) [via %u descriptors]. \
+ \n\t\t\tDataLength: %u, DescriptorsLength: %u. Pkt %p (list id %x). Requested TTL: %u secs.\n",
+ Fdo,
+ totalSectorsToProcess * fdoExt->DiskGeometry.BytesPerSector,
+ entireXferLen,
+ blockDescrIndex,
+ populateTokenDataLength,
+ populateTokenDescriptorsLength,
+ pkt,
+ listIdentifier,
+ offloadReadParameters->TimeToLive));
+
+ //
+ // Save (into the offloadReadContext) any remaining things that
+ // ClasspPopulateTokenTransferPacketDone() will need.
+ //
+
+ offloadReadContext->ListIdentifier = listIdentifier;
+ offloadReadContext->BufferLength = bufferLength;
+ offloadReadContext->ReceiveTokenInformationBufferLength = receiveTokenInformationBufferLength;
+ offloadReadContext->TotalSectorsToProcess = totalSectorsToProcess; // so far
+ offloadReadContext->EntireXferLen = entireXferLen;
+
+ NT_ASSERT(status == STATUS_SUCCESS); // so far.
+
+ IoMarkIrpPending(Irp);
+ SubmitTransferPacket(pkt);
+
+ status = STATUS_PENDING;
+ goto __ClasspServicePopulateTokenTransferRequest_Exit;
+
+ //
+ // Error cleanup label only - not used in success case:
+ //
+
+__ClasspServicePopulateTokenTransferRequest_ErrorExit:
+
+ NT_ASSERT(status != STATUS_PENDING);
+
+ if (offloadReadContext != NULL) {
+ ClasspCleanupOffloadReadContext(offloadReadContext);
+ offloadReadContext = NULL;
+ }
+
+__ClasspServicePopulateTokenTransferRequest_Exit:
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspServicePopulateTokenTransferRequest (%p): Exiting function (Irp %p) with status %x.\n",
+ Fdo,
+ Irp,
+ status));
+
+ return status;
+}
+
+
+VOID
+ClasspPopulateTokenTransferPacketDone(
+ _In_ PVOID Context
+ )
+
+/*++
+
+Routine description:
+
+ This routine continues an offload read operation on completion of the
+ populate token transfer packet.
+
+ This function is responsible for continuing or completing the offload read
+ operation.
+
+Arguments:
+
+ Context - Pointer to the OFFLOAD_READ_CONTEXT for the offload read
+ operation.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PDEVICE_OBJECT fdo;
+ ULONG listIdentifier;
+ POFFLOAD_READ_CONTEXT offloadReadContext;
+ PTRANSFER_PACKET pkt;
+ PIRP pseudoIrp;
+ NTSTATUS status;
+
+ offloadReadContext = Context;
+ pseudoIrp = &offloadReadContext->PseudoIrp;
+ pkt = offloadReadContext->Pkt;
+ fdo = offloadReadContext->Fdo;
+ listIdentifier = offloadReadContext->ListIdentifier;
+
+ offloadReadContext->Pkt = NULL;
+
+
+ status = pseudoIrp->IoStatus.Status;
+ NT_ASSERT(status != STATUS_PENDING);
+
+ if (!NT_SUCCESS(status)) {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspPopulateTokenTransferPacketDone (%p): Generate token for list Id %x failed with %x (Pkt %p).\n",
+ fdo,
+ listIdentifier,
+ status,
+ pkt));
+ goto __ClasspPopulateTokenTransferPacketDone_ErrorExit;
+ }
+
+ //
+ // If a token was successfully generated, it is now time to retrieve the token.
+ // The called function is responsible for completing the offload read DSM IRP.
+ //
+ ClasspReceivePopulateTokenInformation(offloadReadContext);
+
+ //
+ // ClasspReceivePopulateTokenInformation() takes care of completing the IRP,
+ // so this function is done regardless of success or failure in
+ // ClasspReceivePopulateTokenInformation().
+ //
+
+ return;
+
+ //
+ // Error cleanup label only - not used in success case:
+ //
+
+__ClasspPopulateTokenTransferPacketDone_ErrorExit:
+
+ NT_ASSERT(!NT_SUCCESS(status));
+
+ //
+ // ClasspCompleteOffloadRead also cleans up offloadReadContext.
+ //
+
+ ClasspCompleteOffloadRead(offloadReadContext, status);
+
+ return;
+}
+
+
+VOID
+ClasspCompleteOffloadRead(
+ _In_ POFFLOAD_READ_CONTEXT OffloadReadContext,
+ _In_ NTSTATUS CompletionStatus
+ )
+
+/*++
+
+Routine description:
+
+ This routine completes an offload read operation with given status, and
+ cleans up the OFFLOAD_READ_CONTEXT.
+
+Arguments:
+
+ OffloadReadContext - Pointer to the OFFLOAD_READ_CONTEXT for the offload
+ read operation.
+
+ CompletionStatus - The completion status for the offload read operation.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes;
+ ULONGLONG entireXferLen;
+ PDEVICE_OBJECT fdo;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt;
+ PIRP irp;
+ NTSTATUS status;
+ PUCHAR token;
+ ULONGLONG totalSectorsProcessed;
+
+ status = CompletionStatus;
+ dsmAttributes = OffloadReadContext->OffloadReadDsmIrp->AssociatedIrp.SystemBuffer;
+ totalSectorsProcessed = OffloadReadContext->TotalSectorsProcessed;
+ fdoExt = OffloadReadContext->Fdo->DeviceExtension;
+ entireXferLen = OffloadReadContext->EntireXferLen;
+ token = OffloadReadContext->Token;
+ irp = OffloadReadContext->OffloadReadDsmIrp;
+ fdo = OffloadReadContext->Fdo;
+
+ ((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->OffloadReadFlags = 0;
+ ((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->Reserved = 0;
+
+ if (NT_SUCCESS(status)) {
+ ULONGLONG totalBytesProcessed = totalSectorsProcessed * fdoExt->DiskGeometry.BytesPerSector;
+
+ TracePrint((totalBytesProcessed == entireXferLen ? TRACE_LEVEL_INFORMATION : TRACE_LEVEL_WARNING,
+ TRACE_FLAG_IOCTL,
+ "ClasspCompleteOffloadRead (%p): Successfully populated token with %I64u (out of %I64u) bytes (list Id %x).\n",
+ fdo,
+ totalBytesProcessed,
+ entireXferLen,
+ OffloadReadContext->ListIdentifier));
+
+ if (totalBytesProcessed < entireXferLen) {
+ SET_FLAG(((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->OffloadReadFlags, STORAGE_OFFLOAD_READ_RANGE_TRUNCATED);
+ }
+ ((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->LengthProtected = totalBytesProcessed;
+ ((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->TokenLength = STORAGE_OFFLOAD_MAX_TOKEN_LENGTH;
+ RtlCopyMemory(&((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->Token, token, STORAGE_OFFLOAD_MAX_TOKEN_LENGTH);
+ } else {
+ ((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->LengthProtected = 0;
+ ((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->TokenLength = 0;
+ }
+
+ irp->IoStatus.Information = sizeof(STORAGE_OFFLOAD_READ_OUTPUT);
+
+ ClasspCompleteOffloadRequest(fdo, irp, status);
+ ClasspCleanupOffloadReadContext(OffloadReadContext);
+ OffloadReadContext = NULL;
+
+ return;
+}
+
+
+VOID
+ClasspCleanupOffloadReadContext(
+ _In_ __drv_freesMem(mem) POFFLOAD_READ_CONTEXT OffloadReadContext
+ )
+
+/*++
+
+Routine description:
+
+ This routine cleans up an OFFLOAD_READ_CONTEXT.
+
+Arguments:
+
+ OffloadReadContext - Pointer to the OFFLOAD_READ_CONTEXT for the offload
+ read operation.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PMDL populateTokenMdl;
+
+ populateTokenMdl = OffloadReadContext->PopulateTokenMdl;
+
+ NT_ASSERT(OffloadReadContext != NULL);
+
+ if (populateTokenMdl) {
+ ClasspFreeDeviceMdl(populateTokenMdl);
+ }
+ FREE_POOL(OffloadReadContext);
+
+ return;
+}
+
+
+_IRQL_requires_same_
+VOID
+ClasspReceivePopulateTokenInformation(
+ _In_ POFFLOAD_READ_CONTEXT OffloadReadContext
+ )
+
+/*++
+
+Routine description:
+
+ This routine retrieves the token after a PopulateToken command
+ has been sent down.
+
+ Can also be called repeatedly for a single offload op when a previous
+ RECEIVE ROD TOKEN INFORMATION for that op indicated that the operation was
+ not yet complete.
+
+ This function is responsible for continuing or completing the offload read
+ operation.
+
+Arguments:
+
+ OffloadReadContext - Pointer to the OFFLOAD_READ_CONTEXT for the offload
+ read operation.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PVOID buffer;
+ ULONG bufferLength;
+ ULONG cdbLength;
+ PDEVICE_OBJECT fdo;
+ PIRP irp;
+ ULONG listIdentifier;
+ PTRANSFER_PACKET pkt;
+ PIRP pseudoIrp;
+ ULONG receiveTokenInformationBufferLength;
+ PSCSI_REQUEST_BLOCK srb;
+ NTSTATUS status;
+ ULONG tempSizeUlong;
+ PULONGLONG totalSectorsProcessed;
+
+ totalSectorsProcessed = &OffloadReadContext->TotalSectorsProcessed;
+ buffer = OffloadReadContext + 1;
+ bufferLength = OffloadReadContext->BufferLength;
+ fdo = OffloadReadContext->Fdo;
+ irp = OffloadReadContext->OffloadReadDsmIrp;
+ receiveTokenInformationBufferLength = OffloadReadContext->ReceiveTokenInformationBufferLength;
+ listIdentifier = OffloadReadContext->ListIdentifier;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspReceivePopulateTokenInformation (%p): Entering function. Irp %p\n",
+ fdo,
+ irp));
+
+ srb = &OffloadReadContext->Srb;
+ *totalSectorsProcessed = 0;
+
+ pkt = DequeueFreeTransferPacket(fdo, TRUE);
+ if (!pkt){
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspReceivePopulateTokenInformation (%p): Failed to retrieve transfer packet for ReceiveTokenInformation (PopulateToken) operation.\n",
+ fdo));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __ClasspReceivePopulateTokenInformation_ErrorExit;
+ }
+
+ OffloadReadContext->Pkt = pkt;
+
+ RtlZeroMemory(buffer, bufferLength);
+
+ tempSizeUlong = receiveTokenInformationBufferLength - 4;
+ REVERSE_BYTES(((PRECEIVE_TOKEN_INFORMATION_HEADER)buffer)->AvailableData, &tempSizeUlong);
+
+ pseudoIrp = &OffloadReadContext->PseudoIrp;
+ RtlZeroMemory(pseudoIrp, sizeof(IRP));
+
+
+ pseudoIrp->IoStatus.Status = STATUS_SUCCESS;
+ pseudoIrp->IoStatus.Information = 0;
+ pseudoIrp->Tail.Overlay.DriverContext[0] = LongToPtr(1);
+ pseudoIrp->MdlAddress = OffloadReadContext->PopulateTokenMdl;
+
+ ClasspSetupReceivePopulateTokenInformationTransferPacket(
+ OffloadReadContext,
+ pkt,
+ receiveTokenInformationBufferLength,
+ (PUCHAR)buffer,
+ pseudoIrp,
+ listIdentifier);
+
+ //
+ // Cache away the CDB as it may be required for forwarded sense data
+ // after this command completes.
+ //
+ RtlZeroMemory(srb, sizeof(*srb));
+ cdbLength = SrbGetCdbLength(pkt->Srb);
+ if (cdbLength <= 16) {
+ RtlCopyMemory(&srb->Cdb, SrbGetCdb(pkt->Srb), cdbLength);
+ }
+
+ SubmitTransferPacket(pkt);
+
+ return;
+
+ //
+ // Error cleanup label only - not used in success case:
+ //
+
+__ClasspReceivePopulateTokenInformation_ErrorExit:
+
+ NT_ASSERT(!NT_SUCCESS(status));
+
+ //
+ // ClasspCompleteOffloadRead also cleans up offloadReadContext.
+ //
+
+ ClasspCompleteOffloadRead(OffloadReadContext, status);
+
+ return;
+}
+
+
+VOID
+ClasspReceivePopulateTokenInformationTransferPacketDone(
+ _In_ PVOID Context
+ )
+
+/*++
+
+Routine description:
+
+ This routine continues an offload read operation on completion of the
+ RECEIVE ROD TOKEN INFORMATION transfer packet.
+
+ This routine is responsible for continuing or completing the offload read
+ operation.
+
+Arguments:
+
+ Context - Pointer to the OFFLOAD_READ_CONTEXT for the offload read
+ operation.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ULONG availableData;
+ PVOID buffer;
+ UCHAR completionStatus;
+ ULONG estimatedRetryInterval;
+ PDEVICE_OBJECT fdo;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt;
+ PIRP irp;
+ ULONG listIdentifier;
+ POFFLOAD_READ_CONTEXT offloadReadContext;
+ BOOLEAN operationCompleted;
+ UCHAR operationStatus;
+ PIRP pseudoIrp;
+ USHORT segmentsProcessed;
+ PSENSE_DATA senseData;
+ ULONG senseDataFieldLength;
+ UCHAR senseDataLength;
+ PSCSI_REQUEST_BLOCK srb;
+ NTSTATUS status;
+ PUCHAR token;
+ PVOID tokenAscii;
+ PBLOCK_DEVICE_TOKEN_DESCRIPTOR tokenDescriptor;
+ ULONG tokenDescriptorLength;
+ PRECEIVE_TOKEN_INFORMATION_HEADER tokenInformationResults;
+ PRECEIVE_TOKEN_INFORMATION_RESPONSE_HEADER tokenInformationResultsResponse;
+ ULONG tokenLength;
+ ULONG tokenSize;
+ PULONGLONG totalSectorsProcessed;
+ ULONGLONG totalSectorsToProcess;
+ ULONGLONG transferBlockCount;
+
+ offloadReadContext = Context;
+ fdo = offloadReadContext->Fdo;
+ fdoExt = fdo->DeviceExtension;
+ buffer = offloadReadContext + 1;
+ listIdentifier = offloadReadContext->ListIdentifier;
+ irp = offloadReadContext->OffloadReadDsmIrp;
+ totalSectorsToProcess = offloadReadContext->TotalSectorsToProcess;
+ totalSectorsProcessed = &offloadReadContext->TotalSectorsProcessed;
+ srb = &offloadReadContext->Srb;
+ tokenAscii = NULL;
+ tokenSize = BLOCK_DEVICE_TOKEN_SIZE;
+ tokenInformationResults = (PRECEIVE_TOKEN_INFORMATION_HEADER)buffer;
+ senseData = (PSENSE_DATA)((PUCHAR)tokenInformationResults + FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_HEADER, SenseData));
+ transferBlockCount = 0;
+ tokenInformationResultsResponse = NULL;
+ tokenDescriptor = NULL;
+ operationCompleted = FALSE;
+ tokenDescriptorLength = 0;
+ tokenLength = 0;
+ token = NULL;
+ pseudoIrp = &offloadReadContext->PseudoIrp;
+
+ status = pseudoIrp->IoStatus.Status;
+ NT_ASSERT(status != STATUS_PENDING);
+
+ //
+ // The buffer we hand allows for the max sizes for all the fields whereas the returned
+ // data may be lesser (e.g. sense data info will almost never be MAX_SENSE_BUFFER_SIZE, etc.
+ // so handle underrun "error"
+ //
+ if (status == STATUS_DATA_OVERRUN) {
+
+ status = STATUS_SUCCESS;
+ }
+
+ if (!NT_SUCCESS(status)) {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): Token retrieval failed for list Id %x with %x.\n",
+ fdo,
+ listIdentifier,
+ status));
+ goto __ClasspReceivePopulateTokenInformationTransferPacketDone_Exit;
+ }
+
+ REVERSE_BYTES(&availableData, &tokenInformationResults->AvailableData);
+
+ NT_ASSERT(availableData <= FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_HEADER, SenseData) + MAX_SENSE_BUFFER_SIZE +
+ FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_RESPONSE_HEADER, TokenDescriptor) + BLOCK_DEVICE_TOKEN_SIZE);
+
+ NT_ASSERT(tokenInformationResults->ResponseToServiceAction == SERVICE_ACTION_POPULATE_TOKEN);
+
+ operationStatus = tokenInformationResults->OperationStatus;
+ operationCompleted = ClasspIsTokenOperationComplete(operationStatus);
+ NT_ASSERT(operationCompleted);
+
+ REVERSE_BYTES(&estimatedRetryInterval, &tokenInformationResults->EstimatedStatusUpdateDelay);
+
+ completionStatus = tokenInformationResults->CompletionStatus;
+
+ NT_ASSERT(tokenInformationResults->TransferCountUnits == TRANSFER_COUNT_UNITS_NUMBER_BLOCKS);
+ REVERSE_BYTES_QUAD(&transferBlockCount, &tokenInformationResults->TransferCount);
+
+ REVERSE_BYTES_SHORT(&segmentsProcessed, &tokenInformationResults->SegmentsProcessed);
+ NT_ASSERT(segmentsProcessed == 0);
+
+ if (operationCompleted) {
+
+ if (transferBlockCount > totalSectorsToProcess) {
+
+ //
+ // Buggy or hostile target. Don't let it claim more was procesed
+ // than was requested. Since this is likely a bug and it's unknown
+ // how much was actually transferred, assume no data was
+ // transferred.
+ //
+
+ NT_ASSERT(transferBlockCount <= totalSectorsToProcess);
+ transferBlockCount = 0;
+ }
+
+ if (operationStatus != OPERATION_COMPLETED_WITH_SUCCESS &&
+ operationStatus != OPERATION_COMPLETED_WITH_RESIDUAL_DATA) {
+
+ //
+ // Assert on buggy response from target, but in any case, make sure not
+ // to claim that any data was written.
+ //
+
+ NT_ASSERT(transferBlockCount == 0);
+ transferBlockCount = 0;
+ }
+
+ //
+ // Since the TokenOperation was sent down synchronously, the operation is complete as soon as the command returns.
+ //
+
+ senseDataFieldLength = tokenInformationResults->SenseDataFieldLength;
+ senseDataLength = tokenInformationResults->SenseDataLength;
+ NT_ASSERT(senseDataFieldLength >= senseDataLength);
+
+ tokenInformationResultsResponse = (PRECEIVE_TOKEN_INFORMATION_RESPONSE_HEADER)((PUCHAR)tokenInformationResults +
+ FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_HEADER, SenseData) +
+ tokenInformationResults->SenseDataFieldLength);
+
+ REVERSE_BYTES(&tokenDescriptorLength, &tokenInformationResultsResponse->TokenDescriptorsLength);
+
+ if (tokenDescriptorLength > 0) {
+
+ NT_ASSERT(tokenDescriptorLength == sizeof(BLOCK_DEVICE_TOKEN_DESCRIPTOR));
+
+ if (tokenDescriptorLength != sizeof(BLOCK_DEVICE_TOKEN_DESCRIPTOR)) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): Bad firmware, token descriptor length %u.\n",
+ fdo,
+ tokenDescriptorLength));
+
+ NT_ASSERT((*totalSectorsProcessed) == 0);
+ NT_ASSERT(tokenLength == 0);
+
+ } else {
+
+ USHORT restrictedId;
+
+ tokenDescriptor = (PBLOCK_DEVICE_TOKEN_DESCRIPTOR)tokenInformationResultsResponse->TokenDescriptor;
+
+ REVERSE_BYTES_SHORT(&restrictedId, &tokenDescriptor->TokenIdentifier);
+ NT_ASSERT(restrictedId == 0);
+
+ tokenLength = BLOCK_DEVICE_TOKEN_SIZE;
+ token = tokenDescriptor->Token;
+
+ *totalSectorsProcessed = transferBlockCount;
+
+ if (transferBlockCount < totalSectorsToProcess) {
+
+ NT_ASSERT(operationStatus == OPERATION_COMPLETED_WITH_RESIDUAL_DATA ||
+ operationStatus == OPERATION_COMPLETED_WITH_ERROR ||
+ operationStatus == OPERATION_TERMINATED);
+
+ if (transferBlockCount == 0) {
+ //
+ // Treat the same as not getting a token.
+ //
+
+ tokenLength = 0;
+ }
+
+ } else {
+
+ NT_ASSERT(operationStatus == OPERATION_COMPLETED_WITH_SUCCESS);
+ NT_ASSERT(transferBlockCount == totalSectorsToProcess);
+ }
+
+ //
+ // Need to convert to ascii.
+ //
+ tokenAscii = ClasspBinaryToAscii((PUCHAR)token,
+ tokenSize,
+ &tokenSize);
+
+ TracePrint((transferBlockCount == totalSectorsToProcess ? TRACE_LEVEL_INFORMATION : TRACE_LEVEL_WARNING,
+ TRACE_FLAG_IOCTL,
+ "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): %wsToken %s generated successfully for list Id %x for data size %I64u bytes.\n",
+ fdo,
+ transferBlockCount == totalSectorsToProcess ? L"" : L"Target truncated read. ",
+ (tokenAscii == NULL) ? "" : tokenAscii,
+ listIdentifier,
+ (*totalSectorsProcessed) * fdoExt->DiskGeometry.BytesPerSector));
+
+ FREE_POOL(tokenAscii);
+ }
+ } else {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): Target failed to generate a token for list Id %x for data size %I64u bytes (requested %I64u bytes).\n",
+ fdo,
+ listIdentifier,
+ transferBlockCount * fdoExt->DiskGeometry.BytesPerSector,
+ totalSectorsToProcess * fdoExt->DiskGeometry.BytesPerSector));
+
+ *totalSectorsProcessed = 0;
+
+ NT_ASSERT(operationStatus == OPERATION_COMPLETED_WITH_ERROR);
+ }
+
+ //
+ // Operation that completes with success can have sense data (for target to pass on some extra info)
+ // but we don't care about such sense info.
+ // Operation that complete but not with success, may not have sense data associated, but may
+ // have valid CompletionStatus.
+ //
+ // The "status" may be overriden by ClassInterpretSenseInfo(). Final
+ // status is determined a bit later - this is just the default status
+ // when ClassInterpretSenseInfo() doesn't get to run here.
+ //
+ status = STATUS_SUCCESS;
+ if (operationStatus == OPERATION_COMPLETED_WITH_ERROR ||
+ operationStatus == OPERATION_COMPLETED_WITH_RESIDUAL_DATA ||
+ operationStatus == OPERATION_TERMINATED) {
+
+ SrbSetScsiStatus((PSTORAGE_REQUEST_BLOCK_HEADER)srb, completionStatus);
+
+ if (senseDataLength) {
+
+ ULONG retryInterval;
+
+ NT_ASSERT(senseDataLength <= sizeof(SENSE_DATA));
+
+ srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
+
+ srb->SrbStatus = SRB_STATUS_AUTOSENSE_VALID | SRB_STATUS_ERROR;
+ SrbSetSenseInfoBuffer((PSTORAGE_REQUEST_BLOCK_HEADER)srb, senseData);
+ SrbSetSenseInfoBufferLength((PSTORAGE_REQUEST_BLOCK_HEADER)srb, senseDataLength);
+
+ ClassInterpretSenseInfo(fdo,
+ srb,
+ IRP_MJ_SCSI,
+ 0,
+ 0,
+ &status,
+ &retryInterval);
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_IOCTL,
+ "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): Reason for truncation/failure: %x - for list Id %x for data size %I64u bytes.\n",
+ fdo,
+ status,
+ listIdentifier,
+ transferBlockCount * fdoExt->DiskGeometry.BytesPerSector));
+ } else {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_IOCTL,
+ "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): No sense data available but reason for truncation/failure, possibly: %x - for list Id %x for data size %I64u bytes.\n",
+ fdo,
+ completionStatus,
+ listIdentifier,
+ transferBlockCount * fdoExt->DiskGeometry.BytesPerSector));
+ }
+ }
+
+ if (tokenLength > 0) {
+
+ offloadReadContext->Token = token;
+
+ //
+ // Even if target returned an error, from the OS upper layers' perspective,
+ // it is a success (with truncation) if any data at all was read.
+ //
+ status = STATUS_SUCCESS;
+
+ } else {
+
+ if (NT_SUCCESS(status)) {
+ //
+ // Make sure status is a failing status, without throwing away an
+ // already-failing status obtained from sense data.
+ //
+ status = STATUS_UNSUCCESSFUL;
+ }
+ }
+
+ //
+ // Done with the operation.
+ //
+
+ NT_ASSERT(status != STATUS_PENDING);
+ goto __ClasspReceivePopulateTokenInformationTransferPacketDone_Exit;
+
+ } else {
+
+ status = STATUS_UNSUCCESSFUL;
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): Token retrieval failed for list Id %x with %x.\n",
+ fdo,
+ listIdentifier,
+ status));
+
+ NT_ASSERT(*totalSectorsProcessed == 0);
+ goto __ClasspReceivePopulateTokenInformationTransferPacketDone_Exit;
+ }
+
+__ClasspReceivePopulateTokenInformationTransferPacketDone_Exit:
+
+ if (status != STATUS_PENDING) {
+
+ //
+ // The "status" value can be success or failure at this point, as
+ // appropriate.
+ //
+
+ ClasspCompleteOffloadRead(offloadReadContext, status);
+ }
+
+ //
+ // Due to tracing a potentially freed pointer value "Irp", this trace could
+ // be delayed beyond another offload op picking up the same pointer value
+ // for its Irp. This function exits after the operation is complete when
+ // status != STATUS_PENDING.
+ //
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): Exiting function (Irp %p) with internal status %x.\n",
+ fdo,
+ irp,
+ status));
+
+ return;
+}
+
+
+_IRQL_requires_max_(APC_LEVEL)
+_IRQL_requires_min_(PASSIVE_LEVEL)
+_IRQL_requires_same_
+NTSTATUS
+ClasspServiceWriteUsingTokenTransferRequest(
+ _In_ PDEVICE_OBJECT Fdo,
+ _In_ PIRP Irp
+ )
+
+/*++
+
+Routine description:
+
+ This routine processes offload write requests by building the SRB
+ for WriteUsingToken.
+
+Arguments:
+
+ Fdo - The functional device object processing the request
+ Irp - The Io request to be processed
+
+Return Value:
+
+ STATUS_SUCCESS if successful, an error code otherwise
+
+--*/
+
+{
+ ULONG allocationSize;
+ PVOID buffer;
+ ULONG bufferLength;
+ PDEVICE_DATA_SET_RANGE dataSetRanges;
+ ULONG dataSetRangesCount;
+ PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes;
+ ULONGLONG entireXferLen;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt;
+ ULONG i;
+ ULONGLONG logicalBlockOffset;
+ ULONG maxBlockDescrCount;
+ ULONGLONG maxLbaCount;
+ POFFLOAD_WRITE_CONTEXT offloadWriteContext;
+ PDEVICE_DSM_OFFLOAD_WRITE_PARAMETERS offloadWriteParameters;
+ ULONG receiveTokenInformationBufferLength;
+ NTSTATUS status;
+ NTSTATUS tempStatus;
+ BOOLEAN tokenInvalidated;
+ ULONG tokenOperationBufferLength;
+ PMDL writeUsingTokenMdl;
+
+ PAGED_CODE();
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspServiceWriteUsingTokenTransferRequest (%p): Entering function. Irp %p.\n",
+ Fdo,
+ Irp));
+
+ fdoExt = Fdo->DeviceExtension;
+ status = STATUS_SUCCESS;
+ tempStatus = STATUS_SUCCESS;
+ dsmAttributes = Irp->AssociatedIrp.SystemBuffer;
+ buffer = NULL;
+ writeUsingTokenMdl = NULL;
+ offloadWriteParameters = Add2Ptr(dsmAttributes, dsmAttributes->ParameterBlockOffset);
+ dataSetRanges = Add2Ptr(dsmAttributes, dsmAttributes->DataSetRangesOffset);
+ dataSetRangesCount = dsmAttributes->DataSetRangesLength / sizeof(DEVICE_DATA_SET_RANGE);
+ logicalBlockOffset = offloadWriteParameters->TokenOffset / fdoExt->DiskGeometry.BytesPerSector;
+ tokenInvalidated = FALSE;
+ bufferLength = 0;
+
+
+ NT_ASSERT(fdoExt->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits &&
+ NT_SUCCESS(fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus));
+
+ for (i = 0, entireXferLen = 0; i < dataSetRangesCount; i++) {
+ entireXferLen += dataSetRanges[i].LengthInBytes;
+ }
+
+ //
+ // We need to split the write request based on the following hardware limitations:
+ // 1. The size of the data buffer containing the TokenOperation command's parameters must
+ // not exceed the MaximumTransferLength (and max physical pages) of the underlying
+ // adapter.
+ // 2. The number of descriptors specified in the TokenOperation command must not exceed
+ // the MaximumRangeDescriptors.
+ // 3. The cumulative total of the number of transfer blocks in all the descriptors in
+ // the TokenOperation command must not exceed the MaximumTokenTransferSize.
+ //
+ // In addition to the above, we need to ensure that for each of the descriptors in the
+ // TokenOperation command:
+ // 1. The number of blocks specified is an exact multiple of the OptimalTransferLengthGranularity.
+ // 2. The number of blocks specified is limited to the MaximumTransferLength. (We shall
+ // however, limit the number of blocks specified in each descriptor to be a maximum of
+ // OptimalTransferLength or MaximumTransferLength, whichever is lesser).
+ //
+ // Finally, we shall always send down the WriteUsingToken command using IMMED = 0 for this
+ // release. This makes it simpler to handle multi-initiator scenarios since we won't need
+ // to deal with the WriteUsingToken (with IMMED = 1) succeeding but ReceiveRODTokenInformation
+ // failing due to the path/node failing, thus making it impossible to retrieve results of
+ // the data transfer operation, or to cancel the data transfer via CopyOperationAbort.
+ // Since a write data transfer of a large amount of data may take a long time when sent
+ // down IMMED = 0, we shall limit the size to a maximum of 256MB even if it means truncating
+ // the original requested size to this capped size. The application is expected to deal with
+ // this truncation.
+ // (NOTE: the cap of 256MB is chosen to match with the Copy Engine's chunk size).
+ //
+ // The LBA ranges is in DEVICE_DATA_SET_RANGE format, it needs to be converted into
+ // WINDOWS_BLOCK_DEVICE_RANGE_DESCRIPTOR Block Descriptors.
+ //
+
+ ClasspGetTokenOperationCommandBufferLength(Fdo,
+ SERVICE_ACTION_WRITE_USING_TOKEN,
+ &bufferLength,
+ &tokenOperationBufferLength,
+ &receiveTokenInformationBufferLength);
+
+ allocationSize = sizeof(OFFLOAD_WRITE_CONTEXT) + bufferLength;
+
+ offloadWriteContext = ExAllocatePoolWithTag(
+ NonPagedPoolNx,
+ allocationSize,
+ CLASSPNP_POOL_TAG_TOKEN_OPERATION);
+
+ if (!offloadWriteContext) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspServiceWriteUsingTokenTransferRequest (%p): Failed to allocate buffer for WriteUsingToken operations.\n",
+ Fdo));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __ClasspServiceWriteUsingTokenTransferRequest_ErrorExit;
+ }
+
+ //
+ // Only zero the context portion here. The buffer portion is zeroed for
+ // each sub-request.
+ //
+ RtlZeroMemory(offloadWriteContext, sizeof(OFFLOAD_WRITE_CONTEXT));
+
+ offloadWriteContext->Fdo = Fdo;
+ offloadWriteContext->OffloadWriteDsmIrp = Irp;
+ offloadWriteContext->OperationStartTime = KeQueryInterruptTime();
+
+ //
+ // The buffer for the commands is after the offloadWriteContext.
+ //
+ buffer = (offloadWriteContext + 1);
+
+ //
+ // Set up fields that allow iterating through whole request, by issuing sub-
+ // requests which each do some of the writing. Because of truncation by the
+ // target, it's not known exactly how many bytes will be written by the
+ // target in each sub-request (can be less than requested), so the
+ // progress through the outer request must only commit the move through the
+ // upper DSM ranges when a lower request is done and the number of written
+ // sectors is known.
+ //
+
+ NT_ASSERT(offloadWriteContext->TotalSectorsProcessedSuccessfully == 0);
+ offloadWriteContext->TotalRequestSizeSectors = entireXferLen / fdoExt->DiskGeometry.BytesPerSector;
+ NT_ASSERT(offloadWriteContext->DataSetRangeIndex == 0);
+ NT_ASSERT(offloadWriteContext->DataSetRangeByteOffset == 0);
+ offloadWriteContext->DataSetRangesCount = dataSetRangesCount;
+
+ offloadWriteContext->DsmAttributes = dsmAttributes;
+ offloadWriteContext->OffloadWriteParameters = offloadWriteParameters;
+ offloadWriteContext->DataSetRanges = dataSetRanges;
+ offloadWriteContext->LogicalBlockOffset = logicalBlockOffset;
+
+ ClasspGetTokenOperationDescriptorLimits(Fdo,
+ SERVICE_ACTION_WRITE_USING_TOKEN,
+ tokenOperationBufferLength,
+ &maxBlockDescrCount,
+ &maxLbaCount);
+
+ if (fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount && fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumTokenTransferSize) {
+
+ NT_ASSERT(fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount <= fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumTokenTransferSize);
+ }
+
+ //
+ // We will limit the maximum data transfer in an offload write operation to:
+ // - 64MB if OptimalTransferCount = 0
+ // - OptimalTransferCount if < 256MB
+ // - 256MB if OptimalTransferCount >= 256MB
+ // - MaximumTokenTransferSize if lesser than above chosen size
+ //
+ if (0 == fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount) {
+
+ maxLbaCount = MIN(maxLbaCount, (DEFAULT_MAX_NUMBER_BYTES_PER_SYNC_WRITE_USING_TOKEN / (ULONGLONG)fdoExt->DiskGeometry.BytesPerSector));
+
+ } else if (fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount < (MAX_NUMBER_BYTES_PER_SYNC_WRITE_USING_TOKEN / (ULONGLONG)fdoExt->DiskGeometry.BytesPerSector)) {
+
+ maxLbaCount = MIN(maxLbaCount, fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount);
+
+ } else {
+
+ maxLbaCount = MIN(maxLbaCount, (MAX_NUMBER_BYTES_PER_SYNC_WRITE_USING_TOKEN / (ULONGLONG)fdoExt->DiskGeometry.BytesPerSector));
+ }
+
+ //
+ // Since we do not want very fragmented files to end up causing the WriteUsingToken command to take
+ // too long (and potentially timeout), we will limit the max number of descriptors sent down in a
+ // command.
+ //
+ maxBlockDescrCount = MIN(maxBlockDescrCount, MAX_NUMBER_BLOCK_DEVICE_DESCRIPTORS);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "ClasspServiceWriteUsingTokenTransferRequest (%p): Using MaxBlockDescrCount %u and MaxLbaCount %I64u.\n",
+ Fdo,
+ maxBlockDescrCount,
+ maxLbaCount));
+
+ offloadWriteContext->MaxBlockDescrCount = maxBlockDescrCount;
+ offloadWriteContext->MaxLbaCount = maxLbaCount;
+
+ //
+ // Reuse a single buffer for both TokenOperation and ReceiveTokenInformation. This has the one disadvantage
+ // that we'll be marking the page(s) as IoWriteAccess even though we only need read access for token
+ // operation command and we may not even need to send down a ReceiveTokenInformation (in case of a successful
+ // synchronous transfer). However, the advantage is that we eliminate the possibility of any potential
+ // failures when trying to allocate an MDL for the ReceiveTokenInformation later on if we need to send it.
+ //
+ writeUsingTokenMdl = ClasspBuildDeviceMdl(buffer, bufferLength, FALSE);
+ if (!writeUsingTokenMdl) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspServiceWriteUsingTokenTransferRequest (%p): Failed to allocate MDL for WriteUsingToken operations.\n",
+ Fdo));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __ClasspServiceWriteUsingTokenTransferRequest_ErrorExit;
+ }
+
+ offloadWriteContext->WriteUsingTokenMdl = writeUsingTokenMdl;
+
+ //
+ // There are potentially two approaches that we can take:
+ // 1. Determine how many transfer packets we need (in case we need to split the request), get
+ // them all up-front and then send down all the split WriteUsingToken commands in parallel.
+ // The benefit of this approach is that the performance will be improved in the success case.
+ // But error case handling becomes very complex to handle, since if one of the intermediate
+ // write fails, there is no way to cancel the remaining writes that were sent. Waiting for
+ // such requests to complete in geo-distributed source and target cases can be very time
+ // consuming. The complexity gets worse in the case that the target succeeds only a partial
+ // amount of data for one the of intermediate split commands.
+ // [OR]
+ // 2. Until the entire data set range is processed, build the command for as much of the range as
+ // possible, send down a packet, and once it completes, repeat sequentially in a loop.
+ // The advantage of this approach is its simplistic nature. In the success case, it will
+ // be less performant as compared to the previous approach, but since the gain of offload
+ // copy is so significant compared to native buffered-copy, the tradeoff is acceptable.
+ // In the failure case the simplicity offers the following benefit - if any command fails,
+ // there is no further processing that is needed. And the cumulative total bytes that succeeded
+ // (until the failing split WriteUsingToken command) is easily tracked.
+ //
+ // Given the above, we're going with the second approach.
+ //
+
+ NT_ASSERT(status == STATUS_SUCCESS); // so far
+
+ //
+ // Save (into the offloadReadContext) any remaining things that
+ // ClasspPopulateTokenTransferPacketDone() will need.
+ //
+
+ offloadWriteContext->BufferLength = bufferLength;
+ offloadWriteContext->ReceiveTokenInformationBufferLength = receiveTokenInformationBufferLength;
+ offloadWriteContext->EntireXferLen = entireXferLen;
+
+ IoMarkIrpPending(Irp);
+ ClasspContinueOffloadWrite(offloadWriteContext);
+
+ status = STATUS_PENDING;
+ goto __ClasspServiceWriteUsingTokenTransferRequest_Exit;
+
+ //
+ // Error label only - not used in success case:
+ //
+
+__ClasspServiceWriteUsingTokenTransferRequest_ErrorExit:
+
+ NT_ASSERT(status != STATUS_PENDING);
+
+ if (offloadWriteContext != NULL) {
+ ClasspCleanupOffloadWriteContext(offloadWriteContext);
+ offloadWriteContext = NULL;
+ }
+
+__ClasspServiceWriteUsingTokenTransferRequest_Exit:
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspServiceWriteUsingTokenTransferRequest (%p): Exiting function (Irp %p) with status %x.\n",
+ Fdo,
+ Irp,
+ status));
+
+ return status;
+}
+
+
+VOID
+#pragma warning(suppress: 28194) // This function will either alias or free OffloadWriteContext
+ClasspContinueOffloadWrite(
+ _In_ __drv_aliasesMem POFFLOAD_WRITE_CONTEXT OffloadWriteContext
+ )
+
+/*++
+
+Routine description:
+
+ This routine continues an offload write operation. This routine expects the
+ offload write operation to be set up and ready to start a WRITE USING TOKEN,
+ but with no WRITE USING TOKEN currently in flight.
+
+ This routine is responsible for continuing or completing the offload write
+ operation.
+
+Arguments:
+
+ OffloadWriteContext - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload
+ write operation.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ BOOLEAN allDataSetRangeFullyConverted;
+ ULONG blockDescrIndex;
+ PBLOCK_DEVICE_RANGE_DESCRIPTOR blockDescrPointer;
+ PVOID buffer;
+ ULONG bufferLength;
+ ULONGLONG dataSetRangeByteOffset;
+ ULONG dataSetRangeIndex;
+ PDEVICE_DATA_SET_RANGE dataSetRanges;
+ ULONG dataSetRangesCount;
+ ULONGLONG entireXferLen;
+ PDEVICE_OBJECT fdo;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt;
+ PIRP irp;
+ ULONG lbaCount;
+ ULONG listIdentifier;
+ ULONGLONG logicalBlockOffset;
+ ULONG maxBlockDescrCount;
+ ULONGLONG maxLbaCount;
+ PDEVICE_DSM_OFFLOAD_WRITE_PARAMETERS offloadWriteParameters;
+ PTRANSFER_PACKET pkt;
+ PIRP pseudoIrp;
+ NTSTATUS status;
+ DEVICE_DATA_SET_RANGE tempDataSetRange;
+ BOOLEAN tempDataSetRangeFullyConverted;
+ PVOID tokenAscii;
+ ULONG tokenSize;
+ ULONGLONG totalSectorCount;
+ ULONGLONG totalSectorsProcessedSuccessfully;
+ ULONGLONG totalSectorsToProcess;
+ ULONG transferSize;
+ USHORT writeUsingTokenDataLength;
+ USHORT writeUsingTokenDescriptorsLength;
+ PMDL writeUsingTokenMdl;
+
+ tokenAscii = NULL;
+ tokenSize = BLOCK_DEVICE_TOKEN_SIZE;
+ tempDataSetRangeFullyConverted = FALSE;
+ allDataSetRangeFullyConverted = FALSE;
+ fdo = OffloadWriteContext->Fdo;
+ fdoExt = fdo->DeviceExtension;
+ irp = OffloadWriteContext->OffloadWriteDsmIrp;
+ buffer = OffloadWriteContext + 1;
+ bufferLength = OffloadWriteContext->BufferLength;
+ dataSetRanges = OffloadWriteContext->DataSetRanges;
+ offloadWriteParameters = OffloadWriteContext->OffloadWriteParameters;
+ pseudoIrp = &OffloadWriteContext->PseudoIrp;
+ writeUsingTokenMdl = OffloadWriteContext->WriteUsingTokenMdl;
+ entireXferLen = OffloadWriteContext->EntireXferLen;
+
+ RtlZeroMemory(buffer, bufferLength);
+
+ pkt = DequeueFreeTransferPacket(fdo, TRUE);
+ if (!pkt){
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspContinueOffloadWrite (%p): Failed to retrieve transfer packet for TokenOperation (WriteUsingToken) operation.\n",
+ fdo));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto __ClasspContinueOffloadWrite_ErrorExit;
+ }
+
+ OffloadWriteContext->Pkt = pkt;
+
+ blockDescrPointer = (PBLOCK_DEVICE_RANGE_DESCRIPTOR)
+ &((PWRITE_USING_TOKEN_HEADER)buffer)->BlockDeviceRangeDescriptor[0];
+
+ blockDescrIndex = 0;
+ lbaCount = 0;
+
+ totalSectorsToProcess = 0;
+
+ maxBlockDescrCount = OffloadWriteContext->MaxBlockDescrCount;
+ maxLbaCount = OffloadWriteContext->MaxLbaCount;
+
+ //
+ // The OffloadWriteContext->DataSetRangeIndex, DataSetRangeByteOffset, and
+ // TotalSectorsProcessedSuccessfully don't move forward until RRTI has
+ // reported the actual amount written.
+ //
+ // For that reason, this function only updates
+ // OffloadWriteContext->TotalSectorsProcessed, which tracks the number of
+ // sectors requested to be written by the current WRITE USING TOKEN command
+ // (not all will necessarily be written).
+ //
+
+ dataSetRangeIndex = OffloadWriteContext->DataSetRangeIndex;
+ dataSetRangesCount = OffloadWriteContext->DataSetRangesCount;
+ dataSetRangeByteOffset = OffloadWriteContext->DataSetRangeByteOffset;
+ totalSectorsProcessedSuccessfully = OffloadWriteContext->TotalSectorsProcessedSuccessfully;
+
+ //
+ // Send WriteUsingToken commands when the buffer is full or all input entries are converted.
+ //
+ while (!((blockDescrIndex == maxBlockDescrCount) || // buffer full or block descriptor count reached
+ (lbaCount == maxLbaCount) || // block LBA count reached
+ (allDataSetRangeFullyConverted))) { // all DataSetRanges have been converted
+
+ NT_ASSERT(dataSetRangeIndex < dataSetRangesCount);
+ NT_ASSERT(dataSetRangeByteOffset < dataSetRanges[dataSetRangeIndex].LengthInBytes);
+
+ tempDataSetRange.StartingOffset = dataSetRanges[dataSetRangeIndex].StartingOffset + dataSetRangeByteOffset;
+ tempDataSetRange.LengthInBytes = dataSetRanges[dataSetRangeIndex].LengthInBytes - dataSetRangeByteOffset;
+
+ totalSectorCount = 0;
+
+ ClasspConvertDataSetRangeToBlockDescr(fdo,
+ blockDescrPointer,
+ &blockDescrIndex,
+ maxBlockDescrCount,
+ &lbaCount,
+ maxLbaCount,
+ &tempDataSetRange,
+ &totalSectorCount);
+
+ tempDataSetRangeFullyConverted = (tempDataSetRange.LengthInBytes == 0) ? TRUE : FALSE;
+
+ allDataSetRangeFullyConverted = tempDataSetRangeFullyConverted && ((dataSetRangeIndex + 1) == dataSetRangesCount);
+
+ if (tempDataSetRangeFullyConverted) {
+ dataSetRangeIndex += 1;
+ dataSetRangeByteOffset = 0;
+ NT_ASSERT(dataSetRangeIndex <= dataSetRangesCount);
+ } else {
+ dataSetRangeByteOffset += totalSectorCount * fdoExt->DiskGeometry.BytesPerSector;
+ NT_ASSERT(dataSetRangeByteOffset < dataSetRanges[dataSetRangeIndex].LengthInBytes);
+ }
+
+ totalSectorsToProcess += totalSectorCount;
+ }
+
+ //
+ // Save the number of sectors being attempted in this WRITE USING TOKEN
+ // command, so that a success return from the command will know how much
+ // was written, without needing to issue a RECEIVE ROD TOKEN INFORMATION
+ // command.
+ //
+ OffloadWriteContext->TotalSectorsToProcess = totalSectorsToProcess;
+ OffloadWriteContext->TotalSectorsProcessed = 0;
+
+ //
+ // Calculate transfer size, including the header
+ //
+ transferSize = (blockDescrIndex * sizeof(BLOCK_DEVICE_RANGE_DESCRIPTOR)) + FIELD_OFFSET(WRITE_USING_TOKEN_HEADER, BlockDeviceRangeDescriptor);
+
+ NT_ASSERT(transferSize <= MAX_TOKEN_OPERATION_PARAMETER_DATA_LENGTH);
+
+ writeUsingTokenDataLength = (USHORT)transferSize - RTL_SIZEOF_THROUGH_FIELD(WRITE_USING_TOKEN_HEADER, WriteUsingTokenDataLength);
+ REVERSE_BYTES_SHORT(((PWRITE_USING_TOKEN_HEADER)buffer)->WriteUsingTokenDataLength, &writeUsingTokenDataLength);
+
+ ((PWRITE_USING_TOKEN_HEADER)buffer)->Immediate = 0;
+
+ logicalBlockOffset = OffloadWriteContext->LogicalBlockOffset + totalSectorsProcessedSuccessfully;
+ REVERSE_BYTES_QUAD(((PWRITE_USING_TOKEN_HEADER)buffer)->BlockOffsetIntoToken, &logicalBlockOffset);
+
+ RtlCopyMemory(((PWRITE_USING_TOKEN_HEADER)buffer)->Token,
+ &offloadWriteParameters->Token,
+ BLOCK_DEVICE_TOKEN_SIZE);
+
+ writeUsingTokenDescriptorsLength = (USHORT)transferSize - FIELD_OFFSET(WRITE_USING_TOKEN_HEADER, BlockDeviceRangeDescriptor);
+ REVERSE_BYTES_SHORT(((PWRITE_USING_TOKEN_HEADER)buffer)->BlockDeviceRangeDescriptorListLength, &writeUsingTokenDescriptorsLength);
+
+ RtlZeroMemory(pseudoIrp, sizeof(IRP));
+
+ pseudoIrp->IoStatus.Status = STATUS_SUCCESS;
+ pseudoIrp->IoStatus.Information = 0;
+ pseudoIrp->Tail.Overlay.DriverContext[0] = LongToPtr(1);
+ pseudoIrp->MdlAddress = writeUsingTokenMdl;
+
+ InterlockedCompareExchange((volatile LONG *)&TokenOperationListIdentifier, -1, MaxTokenOperationListIdentifier);
+ listIdentifier = InterlockedIncrement((volatile LONG *)&TokenOperationListIdentifier);
+
+ ClasspSetupWriteUsingTokenTransferPacket(
+ OffloadWriteContext,
+ pkt,
+ transferSize,
+ (PUCHAR)buffer,
+ pseudoIrp,
+ listIdentifier);
+
+ tokenAscii = ClasspBinaryToAscii((PUCHAR)(((PWRITE_USING_TOKEN_HEADER)buffer)->Token),
+ tokenSize,
+ &tokenSize);
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "ClasspContinueOffloadWrite (%p): Offloading write for %I64u bytes (versus %I64u) [via %u descriptors]. \
+ \n\t\t\tDataLength: %u, DescriptorsLength: %u. Pkt %p (list id %x) [Token: %s]\n",
+ fdo,
+ totalSectorsToProcess * fdoExt->DiskGeometry.BytesPerSector,
+ entireXferLen,
+ blockDescrIndex,
+ writeUsingTokenDataLength,
+ writeUsingTokenDescriptorsLength,
+ pkt,
+ listIdentifier,
+ (tokenAscii == NULL) ? "" : tokenAscii));
+
+ FREE_POOL(tokenAscii);
+
+ OffloadWriteContext->ListIdentifier = listIdentifier;
+
+ SubmitTransferPacket(pkt);
+
+ //
+ // ClasspWriteUsingTokenTransferPacketDone() takes care of completing the
+ // IRP, so this function is done.
+ //
+
+ return;
+
+ //
+ // Error cleaup label only - not used in success case:
+ //
+
+__ClasspContinueOffloadWrite_ErrorExit:
+
+ NT_ASSERT(!NT_SUCCESS(status));
+
+ //
+ // ClasspCompleteOffloadWrite also cleans up offloadWriteContext.
+ //
+
+ ClasspCompleteOffloadWrite(OffloadWriteContext, status);
+
+ return;
+}
+
+
+VOID
+ClasspAdvanceOffloadWritePosition(
+ _In_ POFFLOAD_WRITE_CONTEXT OffloadWriteContext,
+ _In_ ULONGLONG SectorsToAdvance
+ )
+
+/*++
+
+Routine description:
+
+ After the target has responded to WRITE USING TOKEN with success, or RRTI
+ with a specific TRANSFER COUNT, this routine is used to update the relative
+ position within the overall offload write request. This position includes
+ the TotalSectorsProcessedSuccessfully, the DataSetRangeIndex, and the
+ DataSetRangeByteOffset.
+
+ The caller is responsible for continuing or completing the offload write
+ operation (this routine doesn't do that).
+
+Arguments:
+
+ OffloadWriteContext - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload
+ write operation.
+
+ SectorsToAdvance - The number of sectors which were just written
+ successfully (not the total for the offload write operation overall,
+ just the number done by the most recent WRITE USING TOKEN).
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ULONGLONG bytesToAdvance;
+ ULONGLONG bytesToDo;
+ PULONGLONG dataSetRangeByteOffset;
+ PULONG dataSetRangeIndex;
+ PDEVICE_DATA_SET_RANGE dataSetRanges;
+ PDEVICE_OBJECT fdo;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt;
+ PULONGLONG totalSectorsProcessedSuccessfully;
+
+ fdo = OffloadWriteContext->Fdo;
+ fdoExt = fdo->DeviceExtension;
+ dataSetRanges = OffloadWriteContext->DataSetRanges;
+ dataSetRangeByteOffset = &OffloadWriteContext->DataSetRangeByteOffset;
+ dataSetRangeIndex = &OffloadWriteContext->DataSetRangeIndex;
+ totalSectorsProcessedSuccessfully = &OffloadWriteContext->TotalSectorsProcessedSuccessfully;
+ bytesToAdvance = SectorsToAdvance * fdoExt->DiskGeometry.BytesPerSector;
+
+ (*totalSectorsProcessedSuccessfully) += SectorsToAdvance;
+ NT_ASSERT((*totalSectorsProcessedSuccessfully) <= OffloadWriteContext->TotalRequestSizeSectors);
+
+ while (bytesToAdvance != 0) {
+ bytesToDo = dataSetRanges[*dataSetRangeIndex].LengthInBytes - *dataSetRangeByteOffset;
+ if (bytesToDo > bytesToAdvance) {
+ bytesToDo = bytesToAdvance;
+ }
+ (*dataSetRangeByteOffset) += bytesToDo;
+ bytesToAdvance -= bytesToDo;
+ if ((*dataSetRangeByteOffset) == dataSetRanges[*dataSetRangeIndex].LengthInBytes) {
+ (*dataSetRangeIndex) += 1;
+ (*dataSetRangeByteOffset) = 0;
+ }
+ }
+
+ NT_ASSERT((*dataSetRangeIndex) <= OffloadWriteContext->DataSetRangesCount);
+
+ return;
+}
+
+
+VOID
+ClasspWriteUsingTokenTransferPacketDone(
+ _In_ PVOID Context
+ )
+
+/*++
+
+Routine description:
+
+ This routine continues an offload write operation when the WRITE USING
+ TOKEN transfer packet completes.
+
+ This routine may be able to determine that all requested sectors were
+ written if the WRITE USING TOKEN completed with success, or may need to
+ issue a RECEIVE ROD TOKEN INFORMATION if the WRITE USING TOKEN indicated
+ check condition.
+
+ This routine is responsible for continuing or completing the offload write
+ operation.
+
+Arguments:
+
+ Context - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload write
+ operation.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ULONGLONG entireXferLen;
+ PDEVICE_OBJECT fdo;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt;
+ ULONG listIdentifier;
+ POFFLOAD_WRITE_CONTEXT offloadWriteContext;
+ PTRANSFER_PACKET pkt;
+ PIRP pseudoIrp;
+ NTSTATUS status;
+ PBOOLEAN tokenInvalidated;
+ ULONGLONG totalSectorsToProcess;
+
+ offloadWriteContext = Context;
+ pseudoIrp = &offloadWriteContext->PseudoIrp;
+ fdo = offloadWriteContext->Fdo;
+ fdoExt = fdo->DeviceExtension;
+ listIdentifier = offloadWriteContext->ListIdentifier;
+ totalSectorsToProcess = offloadWriteContext->TotalSectorsToProcess;
+ entireXferLen = offloadWriteContext->EntireXferLen;
+ tokenInvalidated = &offloadWriteContext->TokenInvalidated;
+ pkt = offloadWriteContext->Pkt;
+
+ offloadWriteContext->Pkt = NULL;
+
+
+ status = pseudoIrp->IoStatus.Status;
+ NT_ASSERT(status != STATUS_PENDING);
+
+ //
+ // If the request failed with any of the following errors, then it is meaningless to send
+ // down a ReceiveTokenInformation (regardless of whether the transfer was requested as sync
+ // or async), since the target has no saved information about the command:
+ // - STATUS_INVALID_TOKEN
+ // - STATUS_INVALID_PARAMETER
+ //
+ if (status == STATUS_INVALID_PARAMETER ||
+ status == STATUS_INVALID_TOKEN) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspWriteUsingTokenTransferPacketDone (%p): Write failed with %x (list id %x).\n",
+ fdo,
+ status,
+ listIdentifier));
+
+ //
+ // If the token isn't valid any longer, we should let the upper layers know so that
+ // they don't waste time retrying the write with the same token.
+ //
+ if (status == STATUS_INVALID_TOKEN) {
+
+ *tokenInvalidated = TRUE;
+ }
+
+ NT_ASSERT(status != STATUS_PENDING && !NT_SUCCESS(status));
+ goto __ClasspWriteUsingTokenTransferPacketDone_Exit;
+
+ } else if ((NT_SUCCESS(status)) &&
+ (pkt->Srb->SrbStatus == SRB_STATUS_SUCCESS || pkt->TransferCount != 0)) {
+
+ //
+ // If the TokenOperation command was sent to the target requesting synchronous data
+ // transfer, a success indicates that the command is complete.
+ // This could either be because of a successful completion of the entire transfer
+ // or because of a partial transfer due to target truncation. If it is the latter,
+ // and the information field of the sense data has returned the TransferCount, we
+ // can avoid sending down an RRTI.
+ //
+ if (pkt->Srb->SrbStatus == SRB_STATUS_SUCCESS) {
+
+ //
+ // The entire transfer has completed successfully.
+ //
+ offloadWriteContext->TotalSectorsProcessed = totalSectorsToProcess;
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "ClasspWriteUsingTokenTransferPacketDone (%p): Successfully wrote using token %I64u (out of %I64u) bytes (list Id %x).\n",
+ fdo,
+ totalSectorsToProcess * fdoExt->DiskGeometry.BytesPerSector,
+ entireXferLen,
+ listIdentifier));
+ } else {
+
+ //
+ // The target has returned how much data it transferred in the response to the
+ // WUT command itself, allowing us to optimize by removing the necessaity for
+ // sending down an RRTI to query the TransferCount.
+ //
+ NT_ASSERT(pkt->TransferCount);
+
+ offloadWriteContext->TotalSectorsProcessed = totalSectorsToProcess = pkt->TransferCount;
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_IOCTL,
+ "ClasspWriteUsingTokenTransferPacketDone (%p): Target truncated write using token %I64u (out of %I64u) bytes (list Id %x).\n",
+ fdo,
+ totalSectorsToProcess * fdoExt->DiskGeometry.BytesPerSector,
+ entireXferLen,
+ listIdentifier));
+ }
+
+ ClasspAdvanceOffloadWritePosition(offloadWriteContext, totalSectorsToProcess);
+
+ NT_ASSERT(status != STATUS_PENDING);
+
+ //
+ // ClasspReceiveWriteUsingTokenInformationDone() takes care of
+ // completing the operation (eventually), so pending from point of view
+ // of this function.
+ //
+
+ ClasspReceiveWriteUsingTokenInformationDone(offloadWriteContext, status);
+ status = STATUS_PENDING;
+
+ goto __ClasspWriteUsingTokenTransferPacketDone_Exit;
+
+ } else {
+
+ //
+ // Since the TokenOperation was failed (or the target truncated the transfer but
+ // didn't indicate the amount), we need to send down ReceiveTokenInformation.
+ //
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspWriteUsingTokenTransferPacketDone (%p): Write failed with status %x, %x (list id %x).\n",
+ fdo,
+ status,
+ pkt->Srb->SrbStatus,
+ listIdentifier));
+
+ ClasspReceiveWriteUsingTokenInformation(offloadWriteContext);
+
+ status = STATUS_PENDING;
+ goto __ClasspWriteUsingTokenTransferPacketDone_Exit;
+ }
+
+__ClasspWriteUsingTokenTransferPacketDone_Exit:
+
+ if (status != STATUS_PENDING) {
+ ClasspCompleteOffloadWrite(offloadWriteContext, status);
+ }
+
+ return;
+}
+
+
+VOID
+ClasspReceiveWriteUsingTokenInformationDone(
+ _In_ POFFLOAD_WRITE_CONTEXT OffloadWriteContext,
+ _In_ NTSTATUS CompletionCausingStatus
+ )
+
+/*++
+
+Routine description:
+
+ This routine continues an offload write operation when a WRITE USING TOKEN
+ and possible associated RECEIVE ROD TOKEN INFORMATION have both fully
+ completed and the RRTI has indicated completion of the WUT.
+
+ This routine checks to see if the total sectors written is already equal to
+ the overall total requested sector count, and if so, completes the offload
+ write operation. If not, this routine continues the operation by issuing
+ another WUT.
+
+ This routine is responsible for continuing or completing the offload write
+ operation.
+
+Arguments:
+
+ OffloadWriteContext - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload
+ write operation.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ NT_ASSERT(
+ OffloadWriteContext->TotalSectorsProcessedSuccessfully <=
+ OffloadWriteContext->TotalRequestSizeSectors);
+
+ // Time taken in 100 ns units
+ ULONGLONG durationIn100ns = (KeQueryInterruptTime() - OffloadWriteContext->OperationStartTime);
+
+
+ if (OffloadWriteContext->TotalSectorsProcessedSuccessfully == OffloadWriteContext->TotalRequestSizeSectors) {
+
+ ClasspCompleteOffloadWrite(OffloadWriteContext, CompletionCausingStatus);
+
+ goto __ClasspReceiveWriteUsingTokenInformationDone_Exit;
+ }
+
+ //
+ // Since we don't want a layered timeout mechanism (e.g. guest and parent OS in Hyper-V scenarios)
+ // to cause a SCSI timeout for the higher layer token operations.
+ //
+ if (MAX_TARGET_DURATION <= durationIn100ns) {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_IOCTL,
+ "ClasspReceiveWriteUsingTokenInformationDone (%p): Truncating write (list id %x) because of max-duration-rule.\n",
+ OffloadWriteContext->Fdo,
+ OffloadWriteContext->ListIdentifier));
+
+ //
+ // We could technically pass in STATUS_IO_OPERATION_TIMEOUT, but ClasspCompleteOffloadWrite
+ // won't end up doing anything (useful) with this status, since some bytes would already
+ // have been transferred.
+ //
+ ClasspCompleteOffloadWrite(OffloadWriteContext, STATUS_UNSUCCESSFUL);
+
+ goto __ClasspReceiveWriteUsingTokenInformationDone_Exit;
+ }
+
+ NT_ASSERT(
+ OffloadWriteContext->TotalSectorsProcessedSuccessfully <
+ OffloadWriteContext->TotalRequestSizeSectors);
+
+ //
+ // Keep going with the next sub-request.
+ //
+
+ ClasspContinueOffloadWrite(OffloadWriteContext);
+
+__ClasspReceiveWriteUsingTokenInformationDone_Exit:
+
+ return;
+}
+
+
+VOID
+ClasspCompleteOffloadWrite(
+ _In_ __drv_freesMem(Mem) POFFLOAD_WRITE_CONTEXT OffloadWriteContext,
+ _In_ NTSTATUS CompletionCausingStatus
+ )
+
+/*++
+
+Routine description:
+
+ This routine is used to complete an offload write operation.
+
+ The input CompletionCausingStatus doesn't necessarily drive the completion
+ status of the offload write operation overall, if the offload write
+ operation overall has previously written some sectors successfully.
+
+ This routine completes the offload write operation.
+
+Arguments:
+
+ OffloadWriteContext - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload
+ write operation.
+
+ CompletionCausingStatus - Status code indicating a reason that the offload
+ write operation is completing. For success this will be STATUS_SUCCESS,
+ but for a failure, this status will indicate what failure occurred.
+ This status doesn't directly propagate to the completion status of the
+ overall offload write operation if this status is failure and the
+ overall offload write operation has already previously written some
+ sectors successfully.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PDEVICE_OBJECT fdo;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt;
+ PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes;
+ PULONGLONG totalSectorsProcessedSuccessfully;
+ ULONGLONG entireXferLen;
+ PIRP irp;
+ PBOOLEAN tokenInvalidated;
+ ULONG listIdentifier;
+ ULONGLONG totalSectorsProcessed;
+ NTSTATUS status;
+ ULONGLONG totalBytesProcessed;
+
+ fdo = OffloadWriteContext->Fdo;
+ fdoExt = fdo->DeviceExtension;
+ dsmAttributes = OffloadWriteContext->DsmAttributes;
+ totalSectorsProcessedSuccessfully = &OffloadWriteContext->TotalSectorsProcessedSuccessfully;
+ entireXferLen = OffloadWriteContext->EntireXferLen;
+ irp = OffloadWriteContext->OffloadWriteDsmIrp;
+ tokenInvalidated = &OffloadWriteContext->TokenInvalidated;
+ listIdentifier = OffloadWriteContext->ListIdentifier;
+ totalSectorsProcessed = OffloadWriteContext->TotalSectorsProcessed;
+ status = CompletionCausingStatus;
+
+ ((PSTORAGE_OFFLOAD_WRITE_OUTPUT)dsmAttributes)->OffloadWriteFlags = 0;
+ ((PSTORAGE_OFFLOAD_WRITE_OUTPUT)dsmAttributes)->Reserved = 0;
+
+ totalBytesProcessed = (*totalSectorsProcessedSuccessfully) * fdoExt->DiskGeometry.BytesPerSector;
+
+ TracePrint((totalBytesProcessed == entireXferLen ? TRACE_LEVEL_INFORMATION : TRACE_LEVEL_WARNING,
+ TRACE_FLAG_IOCTL,
+ "ClasspCompleteOffloadWrite (%p): %ws wrote using token %I64u (out of %I64u) bytes (Irp %p).\n",
+ fdo,
+ NT_SUCCESS(status) ? L"Successful" : L"Failed",
+ totalBytesProcessed,
+ entireXferLen,
+ irp));
+
+ if (totalBytesProcessed > 0 && totalBytesProcessed < entireXferLen) {
+ SET_FLAG(((PSTORAGE_OFFLOAD_WRITE_OUTPUT)dsmAttributes)->OffloadWriteFlags, STORAGE_OFFLOAD_WRITE_RANGE_TRUNCATED);
+ }
+ if (*tokenInvalidated) {
+ SET_FLAG(((PSTORAGE_OFFLOAD_WRITE_OUTPUT)dsmAttributes)->OffloadWriteFlags, STORAGE_OFFLOAD_TOKEN_INVALID);
+ }
+ ((PSTORAGE_OFFLOAD_WRITE_OUTPUT)dsmAttributes)->LengthCopied = totalBytesProcessed;
+
+
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_IOCTL,
+ "ClasspCompleteOffloadWrite (%p): TokenOperation for WriteUsingToken (list Id %u) completed with %x writing %I64u blocks (currentTotal %I64u blocks).\n",
+ fdo,
+ listIdentifier,
+ status,
+ totalSectorsProcessed,
+ *totalSectorsProcessedSuccessfully));
+
+ //
+ // Even if target returned an error, from the OS upper layers' perspective,
+ // it is a success (with truncation) if any data at all was written.
+ //
+ if (*totalSectorsProcessedSuccessfully) {
+ status = STATUS_SUCCESS;
+ }
+ }
+
+ irp->IoStatus.Information = sizeof(STORAGE_OFFLOAD_WRITE_OUTPUT);
+
+ ClasspCompleteOffloadRequest(fdo, irp, status);
+ ClasspCleanupOffloadWriteContext(OffloadWriteContext);
+ OffloadWriteContext = NULL;
+
+ return;
+}
+
+
+VOID
+ClasspCleanupOffloadWriteContext(
+ _In_ __drv_freesMem(mem) POFFLOAD_WRITE_CONTEXT OffloadWriteContext
+ )
+
+/*++
+
+Routine description:
+
+ This routine cleans up an offload write context.
+
+Arguments:
+
+ OffloadWriteContext - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload
+ write operation.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PMDL writeUsingTokenMdl = OffloadWriteContext->WriteUsingTokenMdl;
+
+ if (writeUsingTokenMdl) {
+ ClasspFreeDeviceMdl(writeUsingTokenMdl);
+ }
+ FREE_POOL(OffloadWriteContext);
+
+ return;
+}
+
+
+_IRQL_requires_same_
+VOID
+ClasspReceiveWriteUsingTokenInformation(
+ _In_ POFFLOAD_WRITE_CONTEXT OffloadWriteContext
+ )
+
+/*++
+
+Routine description:
+
+ This routine retrieves the token after a WriteUsingToken command
+ has been sent down in case of an error or if there is a need to
+ poll for the result.
+
+ This routine is responsible for continuing or completing the offload write
+ operation.
+
+Arguments:
+
+ OffloadWriteContext - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload
+ write operation.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PVOID buffer;
+ ULONG bufferLength;
+ ULONG cdbLength;
+ PDEVICE_OBJECT fdo;
+ PIRP irp;
+ ULONG listIdentifier;
+ PTRANSFER_PACKET pkt;
+ PIRP pseudoIrp;
+ ULONG receiveTokenInformationBufferLength;
+ PSCSI_REQUEST_BLOCK srb;
+ NTSTATUS status;
+ ULONG tempSizeUlong;
+ PMDL writeUsingTokenMdl;
+
+ fdo = OffloadWriteContext->Fdo;
+ irp = OffloadWriteContext->OffloadWriteDsmIrp;
+ pseudoIrp = &OffloadWriteContext->PseudoIrp;
+ buffer = OffloadWriteContext + 1;
+ bufferLength = OffloadWriteContext->BufferLength;
+ receiveTokenInformationBufferLength = OffloadWriteContext->ReceiveTokenInformationBufferLength;
+ writeUsingTokenMdl = OffloadWriteContext->WriteUsingTokenMdl;
+ listIdentifier = OffloadWriteContext->ListIdentifier;
+ srb = &OffloadWriteContext->Srb;
+ status = STATUS_SUCCESS;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspReceiveWriteUsingTokenInformation (%p): Entering function. Irp %p\n",
+ fdo,
+ irp));
+
+ //
+ // The WRITE USING TOKEN wasn't immediately fully successful, so that means
+ // the only way to find out how many sectors were processed by the WRITE
+ // USING TOKEN is to get a successful RECEIVE ROD TOKEN INFORMATION that
+ // indicates the operation is complete.
+ //
+
+ NT_ASSERT(OffloadWriteContext->TotalSectorsProcessed == 0);
+
+ pkt = DequeueFreeTransferPacket(fdo, TRUE);
+
+ if (!pkt) {
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspReceiveWriteUsingTokenInformation (%p): Failed to retrieve transfer packet for ReceiveTokenInformation (WriteUsingToken) operation.\n",
+ fdo));
+
+ status = STATUS_INSUFFICIENT_RESOURCES;
+
+ goto __ClasspReceiveWriteUsingTokenInformation_ErrorExit;
+ }
+
+ RtlZeroMemory(buffer, bufferLength);
+
+ tempSizeUlong = receiveTokenInformationBufferLength - 4;
+ REVERSE_BYTES(((PRECEIVE_TOKEN_INFORMATION_HEADER)buffer)->AvailableData, &tempSizeUlong);
+
+ RtlZeroMemory(pseudoIrp, sizeof(IRP));
+
+ pseudoIrp->IoStatus.Status = STATUS_SUCCESS;
+ pseudoIrp->IoStatus.Information = 0;
+ pseudoIrp->Tail.Overlay.DriverContext[0] = LongToPtr(1);
+ pseudoIrp->MdlAddress = writeUsingTokenMdl;
+
+ ClasspSetupReceiveWriteUsingTokenInformationTransferPacket(
+ OffloadWriteContext,
+ pkt,
+ bufferLength,
+ (PUCHAR)buffer,
+ pseudoIrp,
+ listIdentifier);
+
+ //
+ // Cache away the CDB as it may be required for forwarded sense data
+ // after this command completes.
+ //
+ RtlZeroMemory(srb, sizeof(*srb));
+ cdbLength = SrbGetCdbLength(pkt->Srb);
+ if (cdbLength <= 16) {
+ RtlCopyMemory(&srb->Cdb, SrbGetCdb(pkt->Srb), cdbLength);
+ }
+
+ SubmitTransferPacket(pkt);
+
+ return;
+
+ //
+ // Error label only - not used by success cases:
+ //
+
+__ClasspReceiveWriteUsingTokenInformation_ErrorExit:
+
+ NT_ASSERT(!NT_SUCCESS(status));
+
+ //
+ // ClasspCompleteOffloadWrite also cleans up OffloadWriteContext.
+ //
+
+ ClasspCompleteOffloadWrite(OffloadWriteContext, status);
+
+ return;
+}
+
+
+VOID
+ClasspReceiveWriteUsingTokenInformationTransferPacketDone(
+ _In_ POFFLOAD_WRITE_CONTEXT OffloadWriteContext
+ )
+
+/*++
+
+Routine description:
+
+ This routine continues an offload write operation when a RECEIVE ROD TOKEN
+ INFORMATION transfer packet is done.
+
+ This routine may need to send another RRTI, or it may be able to indicate
+ that this WUT is done via call to
+ ClasspReceiveWriteUsingTokenInformationDone().
+
+ This routine is responsible for continuing or completing the offload write
+ operation.
+
+Arguments:
+
+ OffloadWriteContext - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload
+ write operation.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ULONG availableData;
+ PVOID buffer;
+ UCHAR completionStatus;
+ ULONG estimatedRetryInterval;
+ PDEVICE_OBJECT fdo;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt;
+ PIRP irp;
+ ULONG listIdentifier;
+ BOOLEAN operationCompleted;
+ UCHAR operationStatus;
+ PIRP pseudoIrp;
+ USHORT segmentsProcessed;
+ PSENSE_DATA senseData;
+ ULONG senseDataFieldLength;
+ UCHAR senseDataLength;
+ PSCSI_REQUEST_BLOCK srb;
+ NTSTATUS status;
+ ULONG tokenDescriptorLength;
+ PRECEIVE_TOKEN_INFORMATION_HEADER tokenInformationResults;
+ PRECEIVE_TOKEN_INFORMATION_RESPONSE_HEADER tokenInformationResponsePadding;
+ PBOOLEAN tokenInvalidated;
+ PULONGLONG totalSectorsProcessed;
+ ULONGLONG totalSectorsToProcess;
+ ULONGLONG transferBlockCount;
+
+ fdo = OffloadWriteContext->Fdo;
+ fdoExt = fdo->DeviceExtension;
+ listIdentifier = OffloadWriteContext->ListIdentifier;
+ totalSectorsProcessed = &OffloadWriteContext->TotalSectorsProcessed;
+ totalSectorsToProcess = OffloadWriteContext->TotalSectorsToProcess;
+ irp = OffloadWriteContext->OffloadWriteDsmIrp;
+ pseudoIrp = &OffloadWriteContext->PseudoIrp;
+ tokenInvalidated = &OffloadWriteContext->TokenInvalidated;
+ srb = &OffloadWriteContext->Srb;
+ operationCompleted = FALSE;
+ buffer = OffloadWriteContext + 1;
+ tokenInformationResults = (PRECEIVE_TOKEN_INFORMATION_HEADER)buffer;
+ senseData = (PSENSE_DATA)((PUCHAR)tokenInformationResults + FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_HEADER, SenseData));
+ transferBlockCount = 0;
+ tokenInformationResponsePadding = NULL;
+ tokenDescriptorLength = 0;
+
+ NT_ASSERT((*totalSectorsProcessed) == 0);
+
+ OffloadWriteContext->Pkt = NULL;
+
+
+ status = pseudoIrp->IoStatus.Status;
+ NT_ASSERT(status != STATUS_PENDING);
+
+ //
+ // The buffer we hand allows for the max sizes for all the fields whereas the returned
+ // data may be lesser (e.g. sense data info will almost never be MAX_SENSE_BUFFER_SIZE, etc.
+ // so handle underrun "error"
+ //
+ if (status == STATUS_DATA_OVERRUN) {
+
+ status = STATUS_SUCCESS;
+ }
+
+ if (!NT_SUCCESS(status)) {
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_IOCTL,
+ "ClasspReceiveWriteUsingTokenInformationTransferPacketDone (%p): Failed with %x to retrieve write results for list Id %x for data size %I64u bytes.\n",
+ fdo,
+ status,
+ listIdentifier,
+ totalSectorsToProcess * fdoExt->DiskGeometry.BytesPerSector));
+
+ NT_ASSERT((*totalSectorsProcessed) == 0);
+
+ goto __ClasspReceiveWriteUsingTokenInformationTransferPacketDone_ErrorExit;
+ }
+
+ REVERSE_BYTES(&availableData, &tokenInformationResults->AvailableData);
+
+ NT_ASSERT(availableData <= FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_HEADER, SenseData) + MAX_SENSE_BUFFER_SIZE);
+
+ NT_ASSERT(tokenInformationResults->ResponseToServiceAction == SERVICE_ACTION_WRITE_USING_TOKEN);
+
+ operationStatus = tokenInformationResults->OperationStatus;
+ operationCompleted = ClasspIsTokenOperationComplete(operationStatus);
+ NT_ASSERT(operationCompleted);
+
+ REVERSE_BYTES(&estimatedRetryInterval, &tokenInformationResults->EstimatedStatusUpdateDelay);
+
+ completionStatus = tokenInformationResults->CompletionStatus;
+
+ senseDataFieldLength = tokenInformationResults->SenseDataFieldLength;
+ senseDataLength = tokenInformationResults->SenseDataLength;
+ NT_ASSERT(senseDataFieldLength >= senseDataLength);
+
+ tokenInformationResponsePadding = (PRECEIVE_TOKEN_INFORMATION_RESPONSE_HEADER)((PUCHAR)tokenInformationResults +
+ FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_HEADER, SenseData) +
+ tokenInformationResults->SenseDataFieldLength);
+
+ REVERSE_BYTES(&tokenDescriptorLength, &tokenInformationResponsePadding->TokenDescriptorsLength);
+ NT_ASSERT(tokenDescriptorLength == 0);
+
+ NT_ASSERT(tokenInformationResults->TransferCountUnits == TRANSFER_COUNT_UNITS_NUMBER_BLOCKS);
+ REVERSE_BYTES_QUAD(&transferBlockCount, &tokenInformationResults->TransferCount);
+
+ REVERSE_BYTES_SHORT(&segmentsProcessed, &tokenInformationResults->SegmentsProcessed);
+ NT_ASSERT(segmentsProcessed == 0);
+
+ if (operationCompleted) {
+
+ if (transferBlockCount > totalSectorsToProcess) {
+
+ //
+ // Buggy or hostile target. Don't let it claim more was procesed
+ // than was requested. Since this is likely a bug and it's unknown
+ // how much was actually transferred, assume no data was
+ // transferred.
+ //
+
+ NT_ASSERT(transferBlockCount <= totalSectorsToProcess);
+ transferBlockCount = 0;
+ }
+
+ if (operationStatus != OPERATION_COMPLETED_WITH_SUCCESS &&
+ operationStatus != OPERATION_COMPLETED_WITH_RESIDUAL_DATA) {
+
+ //
+ // Assert on buggy response from target, but in any case, make sure not
+ // to claim that any data was written.
+ //
+
+ NT_ASSERT(transferBlockCount == 0);
+ transferBlockCount = 0;
+ }
+
+ //
+ // Since the TokenOperation was sent down synchronously but failed, the operation is complete as soon as the
+ // ReceiveTokenInformation command returns.
+ //
+
+ NT_ASSERT((*totalSectorsProcessed) == 0);
+ *totalSectorsProcessed = transferBlockCount;
+ ClasspAdvanceOffloadWritePosition(OffloadWriteContext, transferBlockCount);
+
+ if (transferBlockCount < totalSectorsToProcess) {
+
+ NT_ASSERT(operationStatus == OPERATION_COMPLETED_WITH_RESIDUAL_DATA ||
+ operationStatus == OPERATION_COMPLETED_WITH_ERROR ||
+ operationStatus == OPERATION_TERMINATED);
+
+ } else {
+
+ NT_ASSERT(operationStatus == OPERATION_COMPLETED_WITH_SUCCESS);
+ }
+
+ TracePrint((transferBlockCount == totalSectorsToProcess ? TRACE_LEVEL_INFORMATION : TRACE_LEVEL_WARNING,
+ TRACE_FLAG_IOCTL,
+ "ClasspReceiveWriteUsingTokenInformationTransferPacketDone (%p): %wsSuccessfully wrote (for list Id %x) for data size %I64u bytes\n",
+ fdo,
+ transferBlockCount == totalSectorsToProcess ? L"" : L"Target truncated write. ",
+ listIdentifier,
+ (*totalSectorsProcessed) * fdoExt->DiskGeometry.BytesPerSector));
+
+ //
+ // Operation that completes with success can have sense data (for target to pass on some extra info)
+ // but we don't care about such sense info.
+ // Operation that complete but not with success, may not have sense data associated, but may
+ // have valid CompletionStatus.
+ //
+ // The "status" may be overriden by ClassInterpretSenseInfo(). Final
+ // status is determined a bit later - this is just the default status
+ // when ClassInterpretSenseInfo() doesn't get to run here.
+ //
+ status = STATUS_SUCCESS;
+ if (operationStatus == OPERATION_COMPLETED_WITH_ERROR ||
+ operationStatus == OPERATION_COMPLETED_WITH_RESIDUAL_DATA ||
+ operationStatus == OPERATION_TERMINATED) {
+
+ SrbSetScsiStatus((PSTORAGE_REQUEST_BLOCK_HEADER)srb, completionStatus);
+
+ if (senseDataLength) {
+
+ ULONG retryInterval;
+
+ NT_ASSERT(senseDataLength <= sizeof(SENSE_DATA));
+
+ srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
+
+ srb->SrbStatus = SRB_STATUS_AUTOSENSE_VALID | SRB_STATUS_ERROR;
+ SrbSetSenseInfoBuffer((PSTORAGE_REQUEST_BLOCK_HEADER)srb, senseData);
+ SrbSetSenseInfoBufferLength((PSTORAGE_REQUEST_BLOCK_HEADER)srb, senseDataLength);
+
+ ClassInterpretSenseInfo(fdo,
+ srb,
+ IRP_MJ_SCSI,
+ 0,
+ 0,
+ &status,
+ &retryInterval);
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_IOCTL,
+ "ClasspReceiveWriteUsingTokenInformationTransferPacketDone (%p): Reason for truncation/failure: %x - for list Id %x for data size %I64u bytes.\n",
+ fdo,
+ status,
+ listIdentifier,
+ transferBlockCount * fdoExt->DiskGeometry.BytesPerSector));
+
+ //
+ // If the token isn't valid any longer, we should let the upper layers know so that
+ // they don't waste time retrying the write with the same token.
+ //
+ if (status == STATUS_INVALID_TOKEN) {
+
+ *tokenInvalidated = TRUE;
+ }
+ } else {
+
+ TracePrint((TRACE_LEVEL_WARNING,
+ TRACE_FLAG_IOCTL,
+ "ClasspReceiveWriteUsingTokenInformationTransferPacketDone (%p): No sense data available but reason for truncation/failure, possibly: %x - for list Id %x for data size %I64u bytes.\n",
+ fdo,
+ completionStatus,
+ listIdentifier,
+ transferBlockCount * fdoExt->DiskGeometry.BytesPerSector));
+ }
+ }
+
+ //
+ // Initialize status. Upper layer needs to know if command failed because it
+ // timed out without doing any writing. ClasspCompleteOffloadWrite() will
+ // force status to success if any data was written, so it's this function's
+ // job to set the status appropriately based on the outcome of this
+ // WRITE USING TOKEN command, and then ClasspCompleteOffloadWrite()
+ // can override with success if previos WRITE USING TOKEN commands
+ // issued for the same upper request were able to write some data.
+ //
+ if (transferBlockCount != 0) {
+ status = STATUS_SUCCESS;
+ } else {
+
+ if (NT_SUCCESS(status)) {
+ //
+ // Make sure status is a failing status, without throwing away an
+ // already-failing status obtained from sense data.
+ //
+ status = STATUS_UNSUCCESSFUL;
+ }
+ }
+
+ NT_ASSERT(status != STATUS_PENDING);
+
+ if (!NT_SUCCESS(status)) {
+ goto __ClasspReceiveWriteUsingTokenInformationTransferPacketDone_ErrorExit;
+ }
+
+ ClasspReceiveWriteUsingTokenInformationDone(OffloadWriteContext, status);
+ status = STATUS_PENDING;
+ goto __ClasspReceiveWriteUsingTokenInformationTransferPacketDone_Exit;
+
+ } else {
+
+ status = STATUS_UNSUCCESSFUL;
+
+ goto __ClasspReceiveWriteUsingTokenInformationTransferPacketDone_ErrorExit;
+ }
+
+ //
+ // Error label only - not used in success case:
+ //
+
+__ClasspReceiveWriteUsingTokenInformationTransferPacketDone_ErrorExit:
+
+ NT_ASSERT(!NT_SUCCESS(status));
+
+ ClasspCompleteOffloadWrite(OffloadWriteContext, status);
+
+__ClasspReceiveWriteUsingTokenInformationTransferPacketDone_Exit:
+
+ //
+ // Due to tracing a potentially freed pointer value "Irp", this trace could
+ // be delayed beyond another offload op picking up the same pointer value
+ // for its Irp.
+ //
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspReceiveWriteUsingTokenInformationTransferPacketDone (%p): Exiting function (Irp %p) with status %x.\n",
+ fdo,
+ irp,
+ status));
+
+ return;
+}
+
+
+NTSTATUS
+ClasspRefreshFunctionSupportInfo(
+ _Inout_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ _In_ BOOLEAN ForceQuery
+ )
+/*
+Routine Description:
+
+ This function is to update various properties described in FDO extension's
+ CLASS_FUNCTION_SUPPORT_INFO structure by requerying the device's VPD pages.
+ Although this function is capable of updating any properties in the
+ CLASS_FUNCTION_SUPPORT_INFO structure, it will initially support only
+ a small number of proporties in the block limit data
+
+Arguments:
+
+ FdoExtension : FDO extension
+
+ ForceQuery : TRUE if the caller wants to force a query of the device's
+ VPD pages. Otherwise, the function may use cached data.
+
+Return Value:
+
+ STATUS_SUCCESS or an error status
+
+--*/
+{
+ NTSTATUS status;
+ PSCSI_REQUEST_BLOCK srb = NULL;
+ ULONG srbSize;
+ CLASS_VPD_B0_DATA blockLimitsDataNew;
+ PCLASS_VPD_B0_DATA blockLimitsDataOriginal;
+ KLOCK_QUEUE_HANDLE lockHandle;
+ ULONG generationCount;
+ ULONG changeRequestCount;
+
+ //
+ // ChangeRequestCount is incremented every time we get an unit attention with
+ // SCSI_ADSENSE_OPERATING_CONDITIONS_CHANGED. GenerationCount will be set to
+ // ChangeRequestCount after CLASS_FUNCTION_SUPPORT_INFO is refreshed with the latest
+ // VPD data. i.e. if both values are the same, data in
+ // CLASS_FUNCTION_SUPPORT_INFO is current
+ //
+
+ generationCount = FdoExtension->FunctionSupportInfo->GenerationCount;
+ changeRequestCount = FdoExtension->FunctionSupportInfo->ChangeRequestCount;
+ if (!ForceQuery && generationCount == changeRequestCount) {
+ return STATUS_SUCCESS;
+ }
+
+ //
+ // Allocate an SRB for querying the device for LBP-related info if either
+ // the Logical Block Provisioning (0xB2) or Block Limits (0xB0) VPD page
+ // exists.
+ //
+ if ((FdoExtension->AdapterDescriptor != NULL) &&
+ (FdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK)) {
+ srbSize = CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE;
+ } else {
+ srbSize = sizeof(SCSI_REQUEST_BLOCK);
+ }
+
+ srb = ExAllocatePoolWithTag(NonPagedPoolNx, srbSize, '1DcS');
+ if (srb == NULL) {
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ status = ClasspDeviceGetBlockLimitsVPDPage(FdoExtension,
+ srb,
+ srbSize,
+ &blockLimitsDataNew);
+
+ if (NT_SUCCESS(status)) {
+
+ KeAcquireInStackQueuedSpinLock(&FdoExtension->FunctionSupportInfo->SyncLock, &lockHandle);
+
+ //
+ // If the generationCount didn't change since we looked at it last time, it means
+ // no one has tried to update the CLASS_FUNCTION_SUPPORT_INFO data; otherwise, someone
+ // else has beat us to it.
+ //
+ if (generationCount == FdoExtension->FunctionSupportInfo->GenerationCount) {
+
+ blockLimitsDataOriginal = &FdoExtension->FunctionSupportInfo->BlockLimitsData;
+ if (blockLimitsDataOriginal->CommandStatus == -1) {
+ //
+ // CommandStatus == -1 means this is the first time we have
+ // gotten the block limits data.
+ //
+ *blockLimitsDataOriginal = blockLimitsDataNew;
+ } else {
+ //
+ // We only expect the Optimal Unmap Granularity (and alignment)
+ // to change, so those are the only parameters we update.
+ //
+ blockLimitsDataOriginal->UGAVALID = blockLimitsDataNew.UGAVALID;
+ blockLimitsDataOriginal->UnmapGranularityAlignment = blockLimitsDataNew.UnmapGranularityAlignment;
+ blockLimitsDataOriginal->OptimalUnmapGranularity = blockLimitsDataNew.OptimalUnmapGranularity;
+ }
+ FdoExtension->FunctionSupportInfo->GenerationCount = changeRequestCount;
+ }
+
+ KeReleaseInStackQueuedSpinLock(&lockHandle);
+ }
+
+ FREE_POOL(srb);
+ return status;
+}
+
+NTSTATUS
+ClasspBlockLimitsDataSnapshot(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ _In_ BOOLEAN ForceQuery,
+ _Out_ PCLASS_VPD_B0_DATA BlockLimitsData,
+ _Out_ PULONG GenerationCount
+ )
+/*
+Routine Description:
+
+ This function is to get a copy of the latest block limits data.
+
+ When this function is called multiple times, GenerationCount can change (value always goes up)
+ while BlockLimitsData stays the same. In this case, the caller should assume BlockLimitsData
+ has changed to different values and eventually changed back to the same state when the first
+ call to this function was made.
+
+Arguments:
+
+ FdoExtension : FDO extension
+
+ ForceQuery : TRUE if the caller wants to force a query of the device's
+ VPD pages. Otherwise, the function may use cached data.
+
+ BlockLimitsData : pointer to memory that will receive the block limits data
+
+ GenerationCount : generation count of the block limit data.
+
+ DataIsOutdated: set to TRUE if the BlockLimitsData is old but this function fails to
+ query the latest data from the device due to insufficient resources
+
+Return Value:
+
+ STATUS_SUCCESS or an error status
+
+--*/
+{
+ NTSTATUS status;
+ KLOCK_QUEUE_HANDLE lockHandle;
+
+ status = ClasspRefreshFunctionSupportInfo(FdoExtension, ForceQuery);
+
+ KeAcquireInStackQueuedSpinLock(&FdoExtension->FunctionSupportInfo->SyncLock, &lockHandle);
+ *BlockLimitsData = FdoExtension->FunctionSupportInfo->BlockLimitsData;
+ *GenerationCount = FdoExtension->FunctionSupportInfo->GenerationCount;
+ KeReleaseInStackQueuedSpinLock(&lockHandle);
+
+ return status;
+}
+
diff --git a/storage/class/classpnp/src/class.def b/storage/class/classpnp/src/class.def
new file mode 100644
index 00000000..0cba071f
--- /dev/null
+++ b/storage/class/classpnp/src/class.def
@@ -0,0 +1,119 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+NAME CLASSPNP.SYS
+
+EXPORTS
+ ClassInitialize
+ ClassInitializeEx
+ ClassGetDescriptor
+ ClassReadDriveCapacity
+ ClassReleaseQueue
+ ClassAsynchronousCompletion
+ ClassSplitRequest
+ ClassDeviceControl
+ ClassIoComplete
+ ClassIoCompleteAssociated
+ ClassInterpretSenseInfo
+ ClassSendDeviceIoControlSynchronous
+ ClassSendIrpSynchronous
+ ClassForwardIrpSynchronous
+ ClassSendSrbSynchronous
+ ClassSendSrbAsynchronous
+ ClassBuildRequest
+ ClassModeSense
+ ClassModeSenseEx
+ ClassModeSelect
+ ClassFindModePage
+ ClassClaimDevice
+ ClassInternalIoControl
+ ClassCreateDeviceObject
+ ClassRemoveDevice
+ ClassInitializeSrbLookasideList
+ ClassDeleteSrbLookasideList
+ ClassQueryTimeOutRegistryValue
+ ClassInvalidateBusRelations
+ ClassMarkChildrenMissing
+ ClassMarkChildMissing
+ ClassDebugPrint
+ ClassGetDriverExtension
+ ClassCompleteRequest
+ ClassReleaseRemoveLock
+ ClassAcquireRemoveLockEx
+ ClassUpdateInformationInRegistry
+ ClassWmiCompleteRequest
+ ClassWmiFireEvent
+ ClassGetVpb
+ ClassSetFailurePredictionPoll
+ ClassNotifyFailurePredicted
+ ClassInitializeTestUnitPolling
+ ClassSignalCompletion
+ ClassSendStartUnit
+
+
+
+
+ ClassSetMediaChangeState
+ ClassResetMediaChangeTimer
+ ClassCheckMediaState
+ ClassInitializeMediaChangeDetection
+ ClassCleanupMediaChangeDetection
+ ClassEnableMediaChangeDetection
+ ClassDisableMediaChangeDetection
+
+
+
+
+
+ ClassSpinDownPowerHandler
+ ClassStopUnitPowerHandler
+
+
+
+
+
+ ClassAcquireChildLock
+ ClassReleaseChildLock
+
+
+
+
+
+
+
+ ClassScanForSpecial
+
+
+
+
+
+ ClassSetDeviceParameter
+ ClassGetDeviceParameter
+
+
+
+
+
+
+ ClassGetFsContext
+
+
+
+
+ ClassSendNotification
+
+
+
+
+ DllUnload PRIVATE
+
diff --git a/storage/class/classpnp/src/class.rc b/storage/class/classpnp/src/class.rc
new file mode 100644
index 00000000..fb40e033
--- /dev/null
+++ b/storage/class/classpnp/src/class.rc
@@ -0,0 +1,22 @@
+//+-------------------------------------------------------------------------
+//
+// Microsoft Windows
+//
+// Copyright (C) Microsoft Corporation, 1996 - 1999
+//
+// File: class.rc
+//
+//--------------------------------------------------------------------------
+
+#include <verrsrc.h>
+
+#include <ntverp.h>
+
+#define VER_FILETYPE VFT_DRV
+#define VER_FILESUBTYPE VFT2_DRV_SYSTEM
+#define VER_FILEDESCRIPTION_STR "SCSI Class System Dll"
+#define VER_INTERNALNAME_STR "Classpnp.sys"
+#define VER_ORIGINALFILENAME_STR "Classpnp.sys"
+#define VER_LANGNEUTRAL
+
+#include "common.ver"
diff --git a/storage/class/classpnp/src/classlog.mof b/storage/class/classpnp/src/classlog.mof
new file mode 100644
index 00000000..b82d2e81
--- /dev/null
+++ b/storage/class/classpnp/src/classlog.mof
@@ -0,0 +1,175 @@
+[WMI, guid("F903D6EC-8647-438f-9E42-891F4733EDAF")]
+
+class MSStorageDriver_ScsiRequestBlock {
+ [read, WmiDataId(1), Description("Length")]
+ uint16 length;
+
+ [read, WmiDataId(2), DisplayInHex, Description("Function")]
+ uint8 function;
+
+ [read, WmiDataId(3), DisplayInHex, Description("SRB Status")]
+ uint8 srbStatus;
+
+ [read, WmiDataId(4), DisplayInHex, Description("SCSI Status")]
+ uint8 scsiStatus;
+
+ [read, WmiDataId(5), DisplayInHex, Description("Path ID")]
+ uint8 pathID;
+
+ [read, WmiDataId(6), DisplayInHex, Description("Target ID")]
+ uint8 targetID;
+
+ [read, WmiDataId(7), DisplayInHex, Description("LUN")]
+ uint8 lun;
+
+ [read, WmiDataId(8), DisplayInHex, Description("Queue Tag")]
+ uint8 queueTag;
+
+ [read, WmiDataId(9), DisplayInHex, Description("Queue Action")]
+ uint8 queueAction;
+
+ [read, WmiDataId(10), DisplayInHex, Description("CDB Length")]
+ uint8 cdbLength;
+
+ [read, WmiDataId(11), DisplayInHex, Description("Sense Info Buffer Length")]
+ uint8 senseInfoBufferLength;
+
+ [read, WmiDataId(12), DisplayInHex, Description("SRB Flags")]
+ uint32 srbFlags;
+
+ [read, WmiDataId(13), DisplayInHex, Description("Data Transfer Length")]
+ uint32 dataTransferLength;
+
+ [read, WmiDataId(14), DisplayInHex, Description("Time Out Value")]
+ uint32 timeOutValue;
+
+ [read, WmiDataId(15), DisplayInHex, Description("Data Buffer Pointer")]
+ uint64 dataBuffer;
+
+ [read, WmiDataId(16), DisplayInHex, Description("Sense Info Buffer Pointer")]
+ uint64 senseInfoBuffer;
+
+ [read, WmiDataId(17), DisplayInHex, Description("Next SRB Pointer")]
+ uint64 nextSRB;
+
+ [read, WmiDataId(18), DisplayInHex, Description("Original Request Pointer")]
+ uint64 originalRequest;
+
+ [read, WmiDataId(19), DisplayInHex, Description("SRB Extension Pointer")]
+ uint64 srbExtension;
+
+ [read, WmiDataId(20), DisplayInHex, Description("Internal Status")]
+ uint32 internalStatus;
+
+ [read, WmiDataId(21), DisplayInHex, Description("Reserved (only available in Win64)")]
+ uint32 reserved;
+
+ [read, WmiDataId(22), DisplayInHex, Description("CDB")]
+ uint8 cdb[16];
+};
+
+[WMI, guid("9065566F-5FD6-4b40-9961-98E3A3DD174E")]
+
+class MSStorageDriver_SenseData {
+ [read, WmiDataId(1), Description("Error Code")]
+ uint8 errorCode;
+
+ [read, WmiDataId(2), Description("Valid")]
+ boolean valid;
+
+ [read, WmiDataId(3), Description("Segment Number")]
+ uint8 segmentNumber;
+
+ [read, WmiDataId(4), Description("Sense Key")]
+ uint8 senseKey;
+
+ [read, WmiDataId(5), Description("Reserved")]
+ boolean reserved;
+
+ [read, WmiDataId(6), Description("Incorrect Length")]
+ boolean incorrectLength;
+
+ [read, WmiDataId(7), Description("End Of Media")]
+ boolean endOfMedia;
+
+ [read, WmiDataId(8), Description("File Mark")]
+ boolean fileMark;
+
+ [read, WmiDataId(9), Description("Information")]
+ uint8 information[4];
+
+ [read, WmiDataId(10), Description("Additional Sense Length")]
+ uint8 additionalSenseLength;
+
+ [read, WmiDataId(11), Description("Command Specific Information")]
+ uint8 commandSpecificInformation[4];
+
+ [read, WmiDataId(12), Description("Additional Sense Code")]
+ uint8 additionalSenseCode;
+
+ [read, WmiDataId(13), Description("Additional Sense Code Qualifier")]
+ uint8 additionalSenseCodeQualifier;
+
+ [read, WmiDataId(14), Description("Field Replaceable Unit Code")]
+ uint8 fieldReplaceableUnitCode;
+
+ [read, WmiDataId(15), Description("Sense Key Specific")]
+ uint8 senseKeySpecific[3];
+};
+
+[WMI, guid("0C9BF007-50E9-407e-A9DA-0F33800E4B45")]
+
+class MSStorageDriver_ClassErrorLogEntry {
+ [read, WmiDataId(1), Description("Tick Count")]
+ uint64 tickCount;
+
+ [read, WmiDataId(2), Description("Port Number")]
+ uint32 portNumber;
+
+ [read, WmiDataId(3), Description("Error Paging")]
+ boolean errorPaging;
+
+ [read, WmiDataId(4), Description("Error Retried")]
+ boolean errorRetried;
+
+ [read, WmiDataId(5), Description("Error Unhandled")]
+ boolean errorUnhandled;
+
+ [read, WmiDataId(6), DisplayInHex, Description("Error Reserved")]
+ uint8 errorReserved;
+
+ [read, WmiDataId(7), DisplayInHex, Description("Reserved")]
+ uint8 reserved[3];
+
+ [read, WmiDataId(8), Description("SCSI Request Block")]
+ MSStorageDriver_ScsiRequestBlock srb;
+
+ [read, WmiDataId(9), Description("Sense Data")]
+ MSStorageDriver_SenseData senseData;
+
+ [read, WmiDataId(10), Description("Event Time")]
+ datetime eventTime;
+};
+
+[Dynamic, Provider("WMIProv"),
+WMI, Description("MS Storage Class Driver Error Log"),
+guid("D5A9A51E-03F9-404d-9722-15F90EB07038"),
+locale("MS\\0x409")]
+
+class MSStorageDriver_ClassErrorLog {
+ [key, read]
+ string InstanceName;
+
+ [read]
+ boolean Active;
+
+ [read,
+ WmiDataId(1),
+ Description("Number of Error Log Entries")]
+ uint32 numEntries;
+
+ [read,
+ WmiDataId(2),
+ Description("Error Log Array")]
+ MSStorageDriver_ClassErrorLogEntry logEntries[16];
+}; \ No newline at end of file
diff --git a/storage/class/classpnp/src/classp.h b/storage/class/classpnp/src/classp.h
new file mode 100644
index 00000000..3c32c25d
--- /dev/null
+++ b/storage/class/classpnp/src/classp.h
@@ -0,0 +1,2619 @@
+/*++
+
+Copyright (C) Microsoft Corporation, 1991 - 2010
+
+Module Name:
+
+ classp.h
+
+Abstract:
+
+ Private header file for classpnp.sys modules. This contains private
+ structure and function declarations as well as constant values which do
+ not need to be exported.
+
+Author:
+
+Environment:
+
+ kernel mode only
+
+Notes:
+
+
+Revision History:
+
+--*/
+
+#define RTL_USE_AVL_TABLES 0
+
+#include <stddef.h>
+#include <stdarg.h>
+#include <stdlib.h>
+
+#include <ntddk.h>
+
+#include <scsi.h>
+
+#include <wmidata.h>
+#include <classpnp.h>
+#include <storduid.h>
+
+#if CLASS_INIT_GUID
+#include <initguid.h>
+#endif
+
+#include <mountdev.h>
+#include <ioevent.h>
+#include <ntstrsafe.h>
+#include <ntintsafe.h>
+
+#include <wdmguid.h>
+
+#if (NTDDI_VERSION >= NTDDI_WIN8)
+
+#include <ntpoapi.h>
+
+#include <srbhelper.h>
+
+#endif
+
+//
+// Set component ID for DbgPrintEx calls
+//
+#ifndef DEBUG_COMP_ID
+#define DEBUG_COMP_ID DPFLTR_CLASSPNP_ID
+#endif
+
+//
+// Include header file and setup GUID for tracing
+//
+#include <storswtr.h>
+#define WPP_GUID_CLASSPNP (FA8DE7C4, ACDE, 4443, 9994, C4E2359A9EDB)
+#ifndef WPP_CONTROL_GUIDS
+#define WPP_CONTROL_GUIDS WPP_CONTROL_GUIDS_NORMAL_FLAGS(WPP_GUID_CLASSPNP)
+#endif
+
+
+/*
+ * IA64 requires 8-byte alignment for pointers, but the IA64 NT kernel expects 16-byte alignment
+ */
+#ifdef _WIN64
+ #define PTRALIGN DECLSPEC_ALIGN(16)
+#else
+ #define PTRALIGN
+#endif
+
+
+extern CLASSPNP_SCAN_FOR_SPECIAL_INFO ClassBadItems[];
+
+extern GUID ClassGuidQueryRegInfoEx;
+extern GUID ClassGuidSenseInfo2;
+extern GUID ClassGuidWorkingSet;
+extern GUID ClassGuidSrbSupport;
+
+extern ULONG ClassMaxInterleavePerCriticalIo;
+
+
+#define Add2Ptr(P,I) ((PVOID)((PUCHAR)(P) + (I)))
+
+#define CLASSP_REG_SUBKEY_NAME (L"Classpnp")
+
+#define CLASSP_REG_HACK_VALUE_NAME (L"HackMask")
+#define CLASSP_REG_MMC_DETECTION_VALUE_NAME (L"MMCDetectionState")
+#define CLASSP_REG_WRITE_CACHE_VALUE_NAME (L"WriteCacheEnableOverride")
+#define CLASSP_REG_PERF_RESTORE_VALUE_NAME (L"RestorePerfAtCount")
+#define CLASSP_REG_REMOVAL_POLICY_VALUE_NAME (L"UserRemovalPolicy")
+#define CLASSP_REG_IDLE_INTERVAL_NAME (L"IdleInterval")
+#define CLASSP_REG_IDLE_ACTIVE_MAX (L"IdleOutstandingIoMax")
+#define CLASSP_REG_IDLE_PRIORITY_SUPPORTED (L"IdlePrioritySupported")
+#define CLASSP_REG_ACCESS_ALIGNMENT_NOT_SUPPORTED (L"AccessAlignmentQueryNotSupported")
+#define CLASSP_REG_DISBALE_IDLE_POWER_NAME (L"DisableIdlePowerManagement")
+#define CLASSP_REG_IDLE_TIMEOUT_IN_SECONDS (L"IdleTimeoutInSeconds")
+#define CLASSP_REG_DISABLE_D3COLD (L"DisableD3Cold")
+#define CLASSP_REG_QERR_OVERRIDE_MODE (L"QERROverrideMode")
+#define CLASSP_REG_LEGACY_ERROR_HANDLING (L"LegacyErrorHandling")
+#define CLASSP_REG_IO_TIMEOUT_RETRY_COUNT (L"MaxIoTimeoutRetryCount")
+
+#define CLASS_PERF_RESTORE_MINIMUM (0x10)
+#define CLASS_ERROR_LEVEL_1 (0x4)
+#define CLASS_ERROR_LEVEL_2 (0x8)
+#define CLASS_MAX_INTERLEAVE_PER_CRITICAL_IO (0x4)
+
+#define FDO_HACK_CANNOT_LOCK_MEDIA (0x00000001)
+#define FDO_HACK_GESN_IS_BAD (0x00000002)
+#define FDO_HACK_NO_SYNC_CACHE (0x00000004)
+#define FDO_HACK_NO_RESERVE6 (0x00000008)
+#define FDO_HACK_GESN_IGNORE_OPCHANGE (0x00000010)
+
+#define FDO_HACK_VALID_FLAGS (0x0000001F)
+#define FDO_HACK_INVALID_FLAGS (~FDO_HACK_VALID_FLAGS)
+
+/*
+ * Lots of retries of synchronized SCSI commands that devices may not
+ * even support really slows down the system (especially while booting).
+ * (Even GetDriveCapacity may be failed on purpose if an external disk is powered off).
+ * If a disk cannot return a small initialization buffer at startup
+ * in two attempts (with delay interval) then we cannot expect it to return
+ * data consistently with four retries.
+ * So don't set the retry counts as high here as for data SRBs.
+ *
+ * If we find that these requests are failing consecutively,
+ * despite the retry interval, on otherwise reliable media,
+ * then we should either increase the retry interval for
+ * that failure or (by all means) increase these retry counts as appropriate.
+ */
+#define NUM_LOCKMEDIAREMOVAL_RETRIES 1
+#define NUM_MODESENSE_RETRIES 1
+#define NUM_MODESELECT_RETRIES 1
+#define NUM_DRIVECAPACITY_RETRIES 1
+
+#if (NTDDI_VERSION >= NTDDI_WINBLUE)
+
+//
+// New code should use the MAXIMUM_RETRIES value.
+//
+#define NUM_IO_RETRIES MAXIMUM_RETRIES
+#define LEGACY_NUM_IO_RETRIES 8
+
+#else
+
+/*
+ * We retry failed I/O requests at 1-second intervals.
+ * In the case of a failure due to bus reset, we want to make sure that we retry after the allowable
+ * reset time. For SCSI, the allowable reset time is 5 seconds. ScsiPort queues requests during
+ * a bus reset, which should cause us to retry after the reset is over; but the requests queued in
+ * the miniport are failed all the way back to us immediately. In any event, in order to make
+ * extra sure that our retries span the allowable reset time, we should retry more than 5 times.
+ */
+#define NUM_IO_RETRIES 8
+
+#endif // NTDDI_VERSION >= NTDDI_WINBLUE
+
+#define CLASS_FILE_OBJECT_EXTENSION_KEY 'eteP'
+#define CLASSP_VOLUME_VERIFY_CHECKED 0x34
+
+#define CLASS_TAG_PRIVATE_DATA 'CPcS'
+#define CLASS_TAG_SENSE2 '2ScS'
+#define CLASS_TAG_WORKING_SET 'sWcS'
+#define CLASSPNP_POOL_TAG_GENERIC 'pCcS'
+#define CLASSPNP_POOL_TAG_TOKEN_OPERATION 'oTcS'
+#define CLASSPNP_POOL_TAG_SRB 'rScS'
+#define CLASSPNP_POOL_TAG_VPD 'pVcS'
+#define CLASSPNP_POOL_TAG_LOG_MESSAGE 'mlcS'
+#define CLASSPNP_POOL_TAG_ADDITIONAL_DATA 'DAcS'
+#define CLASSPNP_POOL_TAG_FIRMWARE 'wFcS'
+
+//
+// Macros related to Token Operation commands
+//
+#define MAX_LIST_IDENTIFIER MAXULONG
+#define NUM_POPULATE_TOKEN_RETRIES 1
+#define NUM_WRITE_USING_TOKEN_RETRIES 2
+#define NUM_RECEIVE_TOKEN_INFORMATION_RETRIES 2
+#define MAX_TOKEN_OPERATION_PARAMETER_DATA_LENGTH MAXUSHORT
+#define MAX_RECEIVE_TOKEN_INFORMATION_PARAMETER_DATA_LENGTH MAXULONG
+#define MAX_TOKEN_TRANSFER_SIZE MAXULONGLONG
+#define MAX_NUMBER_BLOCKS_PER_BLOCK_DEVICE_RANGE_DESCRIPTOR MAXULONG
+#define MAX_TARGET_DURATION (4ULL * 10 * 1000 * 1000) // 4sec in 100ns units
+#define DEFAULT_MAX_NUMBER_BYTES_PER_SYNC_WRITE_USING_TOKEN (64ULL * 1024 * 1024) // 64MB
+#define MAX_NUMBER_BYTES_PER_SYNC_WRITE_USING_TOKEN (256ULL * 1024 * 1024) // 256MB
+#define MIN_TOKEN_LIST_IDENTIFIERS 256
+#define MAX_TOKEN_LIST_IDENTIFIERS MAXULONG
+#define MAX_NUMBER_BLOCK_DEVICE_DESCRIPTORS 64
+
+#define REG_DISK_CLASS_CONTROL L"\\REGISTRY\\MACHINE\\SYSTEM\\CurrentControlSet\\Control\\DISK"
+#define REG_MAX_LIST_IDENTIFIER_VALUE L"MaximumListIdentifier"
+
+#define VPD_PAGE_HEADER_SIZE 0x04
+
+
+//
+// Number of times to retry get LBA status in case of an error
+// that can be caused by VPD data change
+//
+
+#define GET_LBA_STATUS_RETRY_COUNT_MAX (2)
+
+extern ULONG MaxTokenOperationListIdentifier;
+extern volatile ULONG TokenOperationListIdentifier;
+
+extern LIST_ENTRY IdlePowerFDOList;
+extern PVOID PowerSettingNotificationHandle;
+extern PVOID ScreenStateNotificationHandle;
+extern BOOLEAN ClasspScreenOff;
+extern KGUARDED_MUTEX IdlePowerFDOListMutex;
+extern ULONG DiskIdleTimeoutInMS;
+
+//
+// Definitions from ntos\rtl\time.c
+//
+
+extern CONST LARGE_INTEGER Magic10000;
+#define SHIFT10000 13
+
+#define Convert100nsToMilliseconds(LARGE_INTEGER) \
+ ( \
+ RtlExtendedMagicDivide((LARGE_INTEGER), Magic10000, SHIFT10000) \
+ )
+
+#define ConvertMillisecondsTo100ns(MILLISECONDS) ( \
+ RtlExtendedIntegerMultiply ((MILLISECONDS), 10000) \
+ )
+
+typedef struct _MEDIA_CHANGE_DETECTION_INFO {
+
+ //
+ // Mutex to synchronize enable/disable requests and media state changes
+ //
+
+ KMUTEX MediaChangeMutex;
+
+ //
+ // The current state of the media (present, not present, unknown)
+ // protected by MediaChangeSynchronizationEvent
+ //
+
+ MEDIA_CHANGE_DETECTION_STATE MediaChangeDetectionState;
+
+ //
+ // This is a count of how many time MCD has been disabled. if it is
+ // set to zero, then we'll poll the device for MCN events with the
+ // then-current method (ie. TEST UNIT READY or GESN). this is
+ // protected by MediaChangeMutex
+ //
+
+ LONG MediaChangeDetectionDisableCount;
+
+
+ //
+ // The timer value to support media change events. This is a countdown
+ // value used to determine when to poll the device for a media change.
+ // The max value for the timer is 255 seconds. This is not protected
+ // by an event -- simply InterlockedExchanged() as needed.
+ //
+
+ LONG MediaChangeCountDown;
+
+ //
+ // recent changes allowed instant retries of the MCN irp. Since this
+ // could cause an infinite loop, keep a count of how many times we've
+ // retried immediately so that we can catch if the count exceeds an
+ // arbitrary limit.
+ //
+
+ LONG MediaChangeRetryCount;
+
+ //
+ // use GESN if it's available
+ //
+
+ struct {
+ BOOLEAN Supported;
+ BOOLEAN HackEventMask;
+ UCHAR EventMask;
+ UCHAR NoChangeEventMask;
+ PUCHAR Buffer;
+ PMDL Mdl;
+ ULONG BufferSize;
+ } Gesn;
+
+ //
+ // If this value is one, then the irp is currently in use.
+ // If this value is zero, then the irp is available.
+ // Use InterlockedCompareExchange() to set from "available" to "in use".
+ // ASSERT that InterlockedCompareExchange() showed previous value of
+ // "in use" when changing back to "available" state.
+ // This also implicitly protects the MediaChangeSrb and SenseBuffer
+ //
+
+ LONG MediaChangeIrpInUse;
+
+ //
+ // Pointer to the irp to be used for media change detection.
+ // protected by Interlocked MediaChangeIrpInUse
+ //
+
+ PIRP MediaChangeIrp;
+
+ //
+ // The srb for the media change detection.
+ // protected by Interlocked MediaChangeIrpInUse
+ //
+
+#if (NTDDI_VERSION >= NTDDI_WIN8)
+ union {
+ SCSI_REQUEST_BLOCK Srb;
+ STORAGE_REQUEST_BLOCK SrbEx;
+ UCHAR SrbExBuffer[CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE];
+ } MediaChangeSrb;
+#else
+ SCSI_REQUEST_BLOCK MediaChangeSrb;
+#endif
+ PUCHAR SenseBuffer;
+ ULONG SrbFlags;
+
+ //
+ // Second timer to keep track of how long the media change IRP has been
+ // in use. If this value exceeds the timeout (#defined) then we should
+ // print out a message to the user and set the MediaChangeIrpLost flag
+ // protected by using Interlocked() operations in ClasspSendMediaStateIrp,
+ // the only routine which should modify this value.
+ //
+
+ LONG MediaChangeIrpTimeInUse;
+
+ //
+ // Set by CdRomTickHandler when we determine that the media change irp has
+ // been lost
+ //
+
+ BOOLEAN MediaChangeIrpLost;
+
+ //
+ // Buffer size of SenseBuffer
+ //
+ UCHAR SenseBufferLength;
+
+};
+
+typedef enum {
+ SimpleMediaLock,
+ SecureMediaLock,
+ InternalMediaLock
+} MEDIA_LOCK_TYPE, *PMEDIA_LOCK_TYPE;
+
+typedef struct _FAILURE_PREDICTION_INFO {
+ FAILURE_PREDICTION_METHOD Method;
+ ULONG CountDown; // Countdown timer
+ ULONG Period; // Countdown period
+
+ PIO_WORKITEM WorkQueueItem;
+
+ KEVENT Event;
+
+ //
+ // Timestamp of last time the failure prediction info was queried.
+ //
+ LARGE_INTEGER LastFailurePredictionQueryTime;
+
+} FAILURE_PREDICTION_INFO, *PFAILURE_PREDICTION_INFO;
+
+
+
+//
+// This struct must always fit within four PVOIDs of info,
+// as it uses the irp's "PVOID DriverContext[4]" to store
+// this info
+//
+typedef struct _CLASS_RETRY_INFO {
+ struct _CLASS_RETRY_INFO *Next;
+} CLASS_RETRY_INFO, *PCLASS_RETRY_INFO;
+
+typedef struct _CSCAN_LIST {
+
+ //
+ // The current block which has an outstanding request.
+ //
+
+ ULONGLONG BlockNumber;
+
+ //
+ // The list of blocks past the CurrentBlock to which we're going to do
+ // i/o. This list is maintained in sorted order.
+ //
+
+ LIST_ENTRY CurrentSweep;
+
+ //
+ // The list of blocks behind the current block for which we'll have to
+ // wait until the next scan across the disk. This is kept as a stack,
+ // the cost of sorting it is taken when it's moved over to be the
+ // running list.
+ //
+
+ LIST_ENTRY NextSweep;
+
+} CSCAN_LIST, *PCSCAN_LIST;
+
+//
+// add to the front of this structure to help prevent illegal
+// snooping by other utilities.
+//
+
+
+
+typedef enum _CLASS_DETECTION_STATE {
+ ClassDetectionUnknown = 0,
+ ClassDetectionUnsupported = 1,
+ ClassDetectionSupported = 2
+} CLASS_DETECTION_STATE, *PCLASS_DETECTION_STATE;
+
+#if _MSC_VER >= 1600
+#pragma warning(push)
+#endif
+#pragma warning(disable:4214) // bit field types other than int
+//
+// CLASS_ERROR_LOG_DATA will still use SCSI_REQUEST_BLOCK even
+// when using extended SRB as an extended SRB is too large to
+// fit into. Should revisit this code once classpnp starts to
+// use greater than 16 byte CDB.
+//
+typedef struct _CLASS_ERROR_LOG_DATA {
+ LARGE_INTEGER TickCount; // Offset 0x00
+ ULONG PortNumber; // Offset 0x08
+
+ UCHAR ErrorPaging : 1; // Offset 0x0c
+ UCHAR ErrorRetried : 1;
+ UCHAR ErrorUnhandled : 1;
+ UCHAR ErrorReserved : 5;
+
+ UCHAR Reserved[3];
+
+ SCSI_REQUEST_BLOCK Srb; // Offset 0x10
+
+ /*
+ * We define the SenseData as the default length.
+ * Since the sense data returned by the port driver may be longer,
+ * SenseData must be at the end of this structure.
+ * For our internal error log, we only log the default length.
+ */
+ SENSE_DATA SenseData; // Offset 0x50 for x86 (or 0x68 for ia64) (ULONG32 Alignment required!)
+
+} CLASS_ERROR_LOG_DATA, *PCLASS_ERROR_LOG_DATA;
+#if _MSC_VER >= 1600
+#pragma warning(pop)
+#endif
+
+#define NUM_ERROR_LOG_ENTRIES 16
+#define DBG_NUM_PACKET_LOG_ENTRIES (64*2) // 64 send&receive's
+
+#if (NTDDI_VERSION >= NTDDI_WIN8)
+typedef
+VOID
+(*PCONTINUATION_ROUTINE)(
+ _In_ PVOID Context
+ );
+#endif
+
+typedef struct _TRANSFER_PACKET {
+
+ LIST_ENTRY AllPktsListEntry; // entry in fdoData's static AllTransferPacketsList
+ SLIST_ENTRY SlistEntry; // for when in free list (use fast slist)
+
+ PIRP Irp;
+ PDEVICE_OBJECT Fdo;
+
+ /*
+ * This is the client IRP that this TRANSFER_PACKET is currently
+ * servicing.
+ */
+ PIRP OriginalIrp;
+ BOOLEAN CompleteOriginalIrpWhenLastPacketCompletes;
+
+ /*
+ * Stuff for retrying the transfer.
+ */
+#if (NTDDI_VERSION >= NTDDI_WINBLUE)
+ USHORT NumRetries; // Total number of retries remaining.
+ UCHAR NumIoTimeoutRetries; // Number of retries remaining for a timed-out request.
+ UCHAR TimedOut; // Indicates if this packet has timed-out.
+#else
+ ULONG NumRetries;
+#endif
+ KTIMER RetryTimer;
+ KDPC RetryTimerDPC;
+
+ _Field_range_(0,MAXIMUM_RETRY_FOR_SINGLE_IO_IN_100NS_UNITS)
+ LONGLONG RetryIn100nsUnits;
+
+ /*
+ * Event for synchronizing the transfer (optional).
+ * (Note that we can't have the event in the packet itself because
+ * by the time a thread waits on an event the packet may have
+ * been completed and re-issued.
+ */
+ PKEVENT SyncEventPtr;
+
+ /*
+ * Stuff for retrying during extreme low-memory stress
+ * (when we retry 1 page at a time).
+ * NOTE: These fields are also used for StartIO-based
+ * class drivers, even when not in low memory conditions.
+ */
+ BOOLEAN DriverUsesStartIO; // if this is set, then the below low-mem flags are always used
+ BOOLEAN InLowMemRetry;
+ PUCHAR LowMemRetry_remainingBufPtr;
+ ULONG LowMemRetry_remainingBufLen;
+ LARGE_INTEGER LowMemRetry_nextChunkTargetLocation;
+
+ /*
+ * Fields used for cancelling the packet.
+ */
+ // BOOLEAN Cancelled;
+ // KEVENT CancelledEvent;
+
+ /*
+ * We keep the buffer and length values here as well
+ * as in the SRB because some miniports return
+ * the transferred length in SRB.DataTransferLength,
+ * and if the SRB failed we need that value again for the retry.
+ * We don't trust the lower stack to preserve any of these values in the SRB.
+ */
+ PUCHAR BufPtrCopy;
+ ULONG BufLenCopy;
+ LARGE_INTEGER TargetLocationCopy;
+
+ /*
+ * This is a standard SCSI structure that receives a detailed
+ * report about a SCSI error on the hardware.
+ */
+ SENSE_DATA_EX SrbErrorSenseData;
+
+ /*
+ * This is the SRB block for this TRANSFER_PACKET.
+ * For IOCTLs, the SRB block includes two DWORDs for
+ * device object and ioctl code; so these must
+ * immediately follow the SRB block.
+ */
+
+#if (NTDDI_VERSION >= NTDDI_WIN8)
+ PSTORAGE_REQUEST_BLOCK_HEADER Srb;
+#else
+ SCSI_REQUEST_BLOCK Srb;
+#endif
+ // ULONG SrbIoctlDevObj; // not handling ioctls yet
+ // ULONG SrbIoctlCode;
+
+ #if DBG
+ LARGE_INTEGER DbgTimeSent;
+ LARGE_INTEGER DbgTimeReturned;
+ ULONG DbgPktId;
+ IRP DbgOriginalIrpCopy;
+ MDL DbgMdlCopy;
+ #endif
+
+ BOOLEAN UsePartialMdl;
+ PMDL PartialMdl;
+
+ PSRB_HISTORY RetryHistory;
+
+ // The time at which this request was sent to port driver.
+ ULONGLONG RequestStartTime;
+
+#if (NTDDI_VERSION >= NTDDI_WIN8)
+ // ActivityId that is associated with the IRP that this transfer packet services.
+ GUID ActivityId;
+
+ // If non-NULL, called at packet completion with this context.
+ PCONTINUATION_ROUTINE ContinuationRoutine;
+ PVOID ContinuationContext;
+ ULONGLONG TransferCount;
+ ULONG AllocateNode;
+#endif
+} TRANSFER_PACKET, *PTRANSFER_PACKET;
+
+/*
+ * MIN_INITIAL_TRANSFER_PACKETS is the minimum number of packets that
+ * we preallocate at startup for each device (we need at least one packet
+ * to guarantee forward progress during memory stress).
+ * MIN_WORKINGSET_TRANSFER_PACKETS is the number of TRANSFER_PACKETs
+ * we allow to build up and remain for each device;
+ * we _lazily_ work down to this number when they're not needed.
+ * MAX_WORKINGSET_TRANSFER_PACKETS is the number of TRANSFER_PACKETs
+ * that we _immediately_ reduce to when they are not needed.
+ *
+ * The absolute maximum number of packets that we will allocate is
+ * whatever is required by the current activity, up to the memory limit;
+ * as soon as stress ends, we snap down to MAX_WORKINGSET_TRANSFER_PACKETS;
+ * we then lazily work down to MIN_WORKINGSET_TRANSFER_PACKETS.
+ */
+#define MIN_INITIAL_TRANSFER_PACKETS 1
+#define MIN_WORKINGSET_TRANSFER_PACKETS_Client 16
+#define MAX_WORKINGSET_TRANSFER_PACKETS_Client 32
+#define MIN_WORKINGSET_TRANSFER_PACKETS_Server_UpperBound 256
+#define MIN_WORKINGSET_TRANSFER_PACKETS_Server_LowerBound 32
+#define MAX_WORKINGSET_TRANSFER_PACKETS_Server 1024
+#define MIN_WORKINGSET_TRANSFER_PACKETS_SPACES 512
+#define MAX_WORKINGSET_TRANSFER_PACKETS_SPACES 2048
+#define MAX_OUTSTANDING_IO_PER_LUN_DEFAULT 16
+#define MAX_CLEANUP_TRANSFER_PACKETS_AT_ONCE 8192
+
+
+
+typedef struct _PNL_SLIST_HEADER {
+ DECLSPEC_CACHEALIGN SLIST_HEADER SListHeader;
+ DECLSPEC_CACHEALIGN ULONG NumFreeTransferPackets;
+ ULONG NumTotalTransferPackets;
+ ULONG DbgPeakNumTransferPackets;
+} PNL_SLIST_HEADER, *PPNL_SLIST_HEADER;
+
+//
+// !!! WARNING !!!
+// DO NOT use the following structure in code outside of classpnp
+// as structure will not be guaranteed between OS versions.
+//
+// add to the front of this structure to help prevent illegal
+// snooping by other utilities.
+//
+struct _CLASS_PRIVATE_FDO_DATA {
+
+
+#if (NTDDI_VERSION >= NTDDI_WIN8)
+
+ //
+ // Periodic timer for polling for media change detection, failure prediction
+ // and class tick function.
+ //
+
+#if (NTDDI_VERSION >= NTDDI_WINBLUE)
+ PEX_TIMER TickTimer;
+ LONGLONG CurrentNoWakeTolerance;
+#else
+ KTIMER TickTimer;
+ KDPC TickTimerDpc;
+#endif // (NTDDI_VERSION >= NTDDI_WINBLUE)
+
+ //
+ // Power related and release queue SRBs
+ //
+ union {
+ STORAGE_REQUEST_BLOCK SrbEx;
+ UCHAR PowerSrbBuffer[CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE];
+ } PowerSrb;
+
+ union {
+ STORAGE_REQUEST_BLOCK SrbEx;
+ UCHAR ReleaseQueueSrbBuffer[CLASS_SRBEX_NO_SRBEX_DATA_BUFFER_SIZE];
+ } ReleaseQueueSrb;
+
+#endif
+
+ ULONG TrackingFlags;
+
+ /*
+ * Flag to detect recursion caused by devices
+ * reporting different capacity per each request
+ */
+ ULONG UpdateDiskPropertiesWorkItemActive;
+
+ //
+ // Local equivalents of MinWorkingSetTransferPackets and MaxWorkingSetTransferPackets.
+ // These values are initialized by the global equivalents but are then adjusted as
+ // requested by the class driver.
+ //
+ ULONG LocalMinWorkingSetTransferPackets;
+ ULONG LocalMaxWorkingSetTransferPackets;
+
+#if DBG
+
+ ULONG MaxOutstandingIOPerLUN;
+
+#endif
+
+ /*
+ * Entry in static list used by debug extension to quickly find all class FDOs.
+ */
+ LIST_ENTRY AllFdosListEntry;
+
+ //
+ // this private structure allows us to
+ // dynamically re-enable the perf benefits
+ // lost due to transient error conditions.
+ // in w2k, a reboot was required. :(
+ //
+ struct {
+ ULONG OriginalSrbFlags;
+ ULONG SuccessfulIO;
+ ULONG ReEnableThreshhold; // 0 means never
+ } Perf;
+
+ ULONG_PTR HackFlags;
+
+ STORAGE_HOTPLUG_INFO HotplugInfo;
+
+ // Legacy. Still used by obsolete legacy code.
+ struct {
+ LARGE_INTEGER Delta; // in ticks
+ LARGE_INTEGER Tick; // when it should fire
+ PCLASS_RETRY_INFO ListHead; // singly-linked list
+ ULONG Granularity; // static
+ KSPIN_LOCK Lock; // protective spin lock
+ KDPC Dpc; // DPC routine object
+ KTIMER Timer; // timer to fire DPC
+ } Retry;
+
+ BOOLEAN TimerInitialized;
+ BOOLEAN LoggedTURFailureSinceLastIO;
+ BOOLEAN LoggedSYNCFailure;
+
+ //
+ // privately allocated release queue irp
+ // protected by fdoExtension->ReleaseQueueSpinLock
+ //
+ BOOLEAN ReleaseQueueIrpAllocated;
+ PIRP ReleaseQueueIrp;
+
+ /*
+ * Queues for TRANSFER_PACKETs that contextualize the IRPs and SRBs
+ * that we send down to the port driver.
+ * (The free list is an slist so that we can use fast
+ * interlocked operations on it; but the relatively-static
+ * AllTransferPacketsList list has to be
+ * a doubly-linked list since we have to dequeue from the middle).
+ */
+ LIST_ENTRY AllTransferPacketsList;
+ PPNL_SLIST_HEADER FreeTransferPacketsLists;
+
+ /*
+ * Queue for deferred client irps
+ */
+ LIST_ENTRY DeferredClientIrpList;
+
+ /*
+ * Precomputed maximum transfer length for the hardware.
+ */
+ ULONG HwMaxXferLen;
+
+ /*
+ * SCSI_REQUEST_BLOCK template preconfigured with the constant values.
+ * This is slapped into the SRB in the TRANSFER_PACKET for each transfer.
+ */
+
+#if (NTDDI_VERSION >= NTDDI_WIN8)
+ PSTORAGE_REQUEST_BLOCK_HEADER SrbTemplate;
+#else
+ SCSI_REQUEST_BLOCK SrbTemplate;
+#endif
+
+ KSPIN_LOCK SpinLock;
+
+ /*
+ * For non-removable media, we read the drive capacity at start time and cache it.
+ * This is so that ReadDriveCapacity failures at runtime (e.g. due to memory stress)
+ * don't cause I/O on the paging disk to start failing.
+ */
+ READ_CAPACITY_DATA_EX LastKnownDriveCapacityData;
+ BOOLEAN IsCachedDriveCapDataValid;
+
+ //
+ // Idle priority support flag
+ //
+ BOOLEAN IdlePrioritySupported;
+
+ //
+ // Tick timer enabled
+ //
+ BOOLEAN TickTimerEnabled;
+
+ BOOLEAN ReservedBoolean;
+
+ /*
+ * Circular array of timestamped logs of errors that occurred on this device.
+ */
+ ULONG ErrorLogNextIndex;
+ CLASS_ERROR_LOG_DATA ErrorLogs[NUM_ERROR_LOG_ENTRIES];
+
+ //
+ // Number of outstanding critical Io requests from Mm
+ //
+ ULONG NumHighPriorityPagingIo;
+
+ //
+ // Maximum number of normal Io requests that can be interleaved with the critical ones
+ //
+ ULONG MaxInterleavedNormalIo;
+
+ //
+ // The timestamp when entering throttle mode
+ //
+ LARGE_INTEGER ThrottleStartTime;
+
+ //
+ // The timestamp when exiting throttle mode
+ //
+ LARGE_INTEGER ThrottleStopTime;
+
+ //
+ // The longest time ever spent in throttle mode
+ //
+ LARGE_INTEGER LongestThrottlePeriod;
+
+ #if DBG
+ ULONG DbgMaxPktId;
+
+ /*
+ * Logging fields for ForceUnitAccess and Flush
+ */
+ BOOLEAN DbgInitFlushLogging; // must reset this to 1 for each logging session
+ ULONG DbgNumIORequests;
+ ULONG DbgNumFUAs; // num I/O requests with ForceUnitAccess bit set
+ ULONG DbgNumFlushes; // num SRB_FUNCTION_FLUSH_QUEUE
+ ULONG DbgIOsSinceFUA;
+ ULONG DbgIOsSinceFlush;
+ ULONG DbgAveIOsToFUA; // average number of I/O requests between FUAs
+ ULONG DbgAveIOsToFlush; // ...
+ ULONG DbgMaxIOsToFUA;
+ ULONG DbgMaxIOsToFlush;
+ ULONG DbgMinIOsToFUA;
+ ULONG DbgMinIOsToFlush;
+
+ /*
+ * Debug log of previously sent packets (including retries).
+ */
+ ULONG DbgPacketLogNextIndex;
+ TRANSFER_PACKET DbgPacketLogs[DBG_NUM_PACKET_LOG_ENTRIES];
+ #endif
+
+ //
+ // Spin lock for low priority I/O list
+ //
+ KSPIN_LOCK IdleListLock;
+
+ //
+ // Queue for low priority I/O
+ //
+ LIST_ENTRY IdleIrpList;
+
+ //
+ // Timer for low priority I/O
+ //
+ KTIMER IdleTimer;
+
+ //
+ // DPC for low priority I/O
+ //
+ KDPC IdleDpc;
+
+#if (NTDDI_VERSION >= NTDDI_WIN8)
+
+ //
+ // Time (ms) since the completion of the last non-idle request before the
+ // first idle request should be issued. Due to the coarseness of the idle
+ // timer frequency, some variability in the idle interval will be tolerated
+ // such that it is the desired idle interval on average.
+ //
+ USHORT IdleInterval;
+
+ //
+ // Max number of active idle requests.
+ //
+ USHORT IdleActiveIoMax;
+
+#endif
+
+ //
+ // Timer interval for sending low priority I/O
+ //
+ USHORT IdleTimerInterval;
+
+ //
+ // Idle counts required to process idle request
+ // to avoid starvation
+ //
+ USHORT StarvationCount;
+
+ //
+ // Idle timer tick count
+ //
+ ULONG IdleTimerTicks;
+
+ //
+ // Idle timer tick count
+ //
+ ULONG IdleTicks;
+
+ //
+ // Idle I/O count
+ //
+ ULONG IdleIoCount;
+
+ //
+ // Flag to indicate timer status
+ //
+ LONG IdleTimerStarted;
+
+ //
+ // Normal priority I/O time
+ //
+ LARGE_INTEGER LastIoTime;
+
+ //
+ // Count of active normal priority I/O
+ //
+ LONG ActiveIoCount;
+
+ //
+ // Count of active idle priority I/O
+ //
+ LONG ActiveIdleIoCount;
+
+ //
+ // Support for class drivers to extend
+ // the interpret sense information routine
+ // and retry history per-packet. Copy of
+ // values in driver extension.
+ //
+ PCLASS_INTERPRET_SENSE_INFO2 InterpretSenseInfo;
+
+ //
+ // power process parameters. they work closely with CLASS_POWER_CONTEXT structure.
+ //
+ ULONG MaxPowerOperationRetryCount;
+ PIRP PowerProcessIrp;
+
+ // Counter frequency : Currently used for KeQueryPerferformanceCounter
+ LARGE_INTEGER PerfCounterFrequency;
+
+ //
+ // Indicates legacy error handling should be used.
+ // This means:
+ // - Max number of retries for an IO request is 8 (instead of 4).
+ //
+ BOOLEAN LegacyErrorHandling;
+
+ //
+ // Maximum number of retries allowed for IO requests for this device.
+ //
+ UCHAR MaxNumberOfIoRetries;
+};
+
+//
+// !!! WARNING !!!
+// DO NOT use the following structure in code outside of classpnp
+// as structure will not be guaranteed between OS versions.
+//
+// EX_RUNDOWN_REF_CACHE_AWARE is variable size and follows
+// RemoveLockFailAcquire. EX_RUNDOWN_REF_CACHE_AWARE must be part
+// of the device extension allocation to avoid issues with a device
+// that has been PNP remove but still has outstanding references.
+// In this case, the removed object may still receive incoming requests.
+//
+// There are code dependencies on the structure layout. To minimize
+// code changes, new fields to _CLASS_PRIVATE_COMMON_DATA should be
+// added based on the following guidance.
+// - Fixed size: beginning of _CLASS_PRIVATE_COMMON_DATA
+// - Variable size: at the end of _CLASS_PRIVATE_COMMON_DATA after the
+// last variable size field.
+//
+
+struct _CLASS_PRIVATE_COMMON_DATA {
+
+ //
+ // Cacheaware rundown lock reference
+ //
+
+ LONG RemoveLockFailAcquire;
+
+ //
+ // N.B. EX_RUNDOWN_REF_CACHE_AWARE begins with a pointer-sized item that is
+ // accessed interlocked, and must be aligned on ARM platforms. In order
+ // for this to work on ARM64, an additional 32-bit slot must be allocated.
+ //
+
+#if defined(_WIN64)
+ LONG Align;
+#endif
+
+ // EX_RUNDOWN_REF_CACHE_AWARE (variable size) follows
+
+};
+
+//
+// Verify that the size of _CLASS_PRIVATE_COMMON_DATA is pointer size aligned
+// to ensure the EX_RUNDOWN_REF_CACHE_AWARE following it is properly aligned.
+//
+
+C_ASSERT((sizeof(struct _CLASS_PRIVATE_COMMON_DATA) % sizeof(PVOID)) == 0);
+
+typedef struct _IDLE_POWER_FDO_LIST_ENTRY {
+ LIST_ENTRY ListEntry;
+ PDEVICE_OBJECT Fdo;
+} IDLE_POWER_FDO_LIST_ENTRY, *PIDLE_POWER_FDO_LIST_ENTRY;
+
+typedef struct _OFFLOAD_READ_CONTEXT {
+
+ PDEVICE_OBJECT Fdo;
+
+ //
+ // Upper offload read DSM irp.
+ //
+
+ PIRP OffloadReadDsmIrp;
+
+ //
+ // A pseudo-irp is used despite the operation being async. This is in
+ // contrast to normal read and write, which let TransferPktComplete()
+ // complete the upper IRP directly. Offload requests are enough different
+ // that it makes more sense to let them manage their own async steps with
+ // minimal help from TransferPktComplete() (just a continuation function
+ // call during TransferPktComplete()).
+ //
+
+ IRP PseudoIrp;
+
+ //
+ // The offload read context tracks one packet in flight at a time - it'll be
+ // the POPULATE TOKEN packet first, then RECEIVE ROD TOKEN INFORMATION.
+ //
+ // This field exists only for debug purposes.
+ //
+
+ PTRANSFER_PACKET Pkt;
+
+ PMDL PopulateTokenMdl;
+
+ ULONG BufferLength;
+
+ ULONG ListIdentifier;
+
+ ULONG ReceiveTokenInformationBufferLength;
+
+ //
+ // Total sectors that the operation is attempting to process.
+ //
+
+ ULONGLONG TotalSectorsToProcess;
+
+ //
+ // Total sectors actually processed.
+ //
+
+ ULONGLONG TotalSectorsProcessed;
+
+ //
+ // Total upper request size in bytes.
+ //
+
+ ULONGLONG EntireXferLen;
+
+ //
+ // Just a cached copy of what was in the transfer packet.
+ //
+
+ SCSI_REQUEST_BLOCK Srb;
+
+ //
+ // Pointer into the token part of the SCSI buffer (the buffer immediately
+ // after this struct), for easy reference.
+ //
+
+ PUCHAR Token;
+
+ // The SCSI buffer (in/out buffer, not CDB) for the commands immediately
+ // follows this struct, so no need to have a field redundantly pointing to
+ // the buffer.
+} OFFLOAD_READ_CONTEXT, *POFFLOAD_READ_CONTEXT;
+
+
+typedef struct _OFFLOAD_WRITE_CONTEXT {
+
+ PDEVICE_OBJECT Fdo;
+
+ PIRP OffloadWriteDsmIrp;
+
+ ULONGLONG EntireXferLen;
+ ULONGLONG TotalRequestSizeSectors;
+
+ ULONG DataSetRangesCount;
+
+ PDEVICE_MANAGE_DATA_SET_ATTRIBUTES DsmAttributes;
+ PDEVICE_DATA_SET_RANGE DataSetRanges;
+ PDEVICE_DSM_OFFLOAD_WRITE_PARAMETERS OffloadWriteParameters;
+ ULONGLONG LogicalBlockOffset;
+
+ ULONG MaxBlockDescrCount;
+ ULONGLONG MaxLbaCount;
+
+ ULONG BufferLength;
+ ULONG ReceiveTokenInformationBufferLength;
+
+ IRP PseudoIrp;
+
+ PMDL WriteUsingTokenMdl;
+
+ ULONGLONG TotalSectorsProcessedSuccessfully;
+ ULONG DataSetRangeIndex;
+ ULONGLONG DataSetRangeByteOffset;
+
+ PTRANSFER_PACKET Pkt;
+
+ //
+ // Per-WUT (WRITE USING TOKEN), not overall.
+ //
+
+ ULONGLONG TotalSectorsToProcess;
+ ULONGLONG TotalSectorsProcessed;
+
+ ULONG ListIdentifier;
+
+ BOOLEAN TokenInvalidated;
+
+ //
+ // Just a cached copy of what was in the transfer packet.
+ //
+
+ SCSI_REQUEST_BLOCK Srb;
+
+ ULONGLONG OperationStartTime;
+
+} OFFLOAD_WRITE_CONTEXT, *POFFLOAD_WRITE_CONTEXT;
+
+
+typedef struct _OPCODE_SENSE_DATA_IO_LOG_MESSAGE_CONTEXT_HEADER {
+ PIO_WORKITEM WorkItem;
+ PVOID SenseData;
+ ULONG SenseDataSize;
+ UCHAR SrbStatus;
+ UCHAR ScsiStatus;
+ UCHAR OpCode;
+ UCHAR Reserved;
+ ULONG ErrorCode;
+} OPCODE_SENSE_DATA_IO_LOG_MESSAGE_CONTEXT_HEADER, *POPCODE_SENSE_DATA_IO_LOG_MESSAGE_CONTEXT_HEADER;
+
+typedef struct _IO_RETRIED_LOG_MESSAGE_CONTEXT {
+ OPCODE_SENSE_DATA_IO_LOG_MESSAGE_CONTEXT_HEADER ContextHeader;
+ LARGE_INTEGER Lba;
+ ULONG DeviceNumber;
+} IO_RETRIED_LOG_MESSAGE_CONTEXT, *PIO_RETRIED_LOG_MESSAGE_CONTEXT;
+
+
+#define QERR_SET_ZERO_ODX_OR_TP_ONLY 0
+#define QERR_SET_ZERO_ALWAYS 1
+#define QERR_SET_ZERO_NEVER 2
+
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
+
+#define NOT_READY_RETRY_INTERVAL 10
+#define MINIMUM_RETRY_UNITS ((LONGLONG)32)
+#define MODE_PAGE_DATA_SIZE 192
+
+#define CLASS_IDLE_INTERVAL 50 // 50 milliseconds
+#define CLASS_STARVATION_INTERVAL 500 // 500 milliseconds
+#define CLASS_IDLE_TIMER_TICKS 4
+
+
+/*
+ * Simple singly-linked-list queuing macros, with no synchronization.
+ */
+__inline VOID SimpleInitSlistHdr(SINGLE_LIST_ENTRY *SListHdr)
+{
+ SListHdr->Next = NULL;
+}
+__inline VOID SimplePushSlist(SINGLE_LIST_ENTRY *SListHdr, SINGLE_LIST_ENTRY *SListEntry)
+{
+ SListEntry->Next = SListHdr->Next;
+ SListHdr->Next = SListEntry;
+}
+__inline SINGLE_LIST_ENTRY *SimplePopSlist(SINGLE_LIST_ENTRY *SListHdr)
+{
+ SINGLE_LIST_ENTRY *sListEntry = SListHdr->Next;
+ if (sListEntry){
+ SListHdr->Next = sListEntry->Next;
+ sListEntry->Next = NULL;
+ }
+ return sListEntry;
+}
+__inline BOOLEAN SimpleIsSlistEmpty(SINGLE_LIST_ENTRY *SListHdr)
+{
+ return (SListHdr->Next == NULL);
+}
+
+__inline
+BOOLEAN
+ClasspIsIdleRequestSupported(
+ PCLASS_PRIVATE_FDO_DATA FdoData,
+ PIRP Irp
+ )
+{
+ IO_PRIORITY_HINT ioPriority = IoGetIoPriorityHint(Irp);
+ return ((ioPriority <= IoPriorityLow) && (FdoData->IdlePrioritySupported == TRUE));
+}
+
+__inline
+VOID
+ClasspMarkIrpAsIdle(
+ PIRP Irp,
+ BOOLEAN Idle
+ )
+{
+// truncation is not an issue for this use case
+// nonstandard extension used is not an issue for this use case
+#pragma warning(suppress:4305; suppress:4213)
+ ((BOOLEAN)Irp->Tail.Overlay.DriverContext[1]) = Idle;
+}
+
+__inline
+BOOLEAN
+ClasspIsIdleRequest(
+ PIRP Irp
+ )
+{
+#pragma warning(suppress:4305) // truncation is not an issue for this use case
+ return ((BOOLEAN)Irp->Tail.Overlay.DriverContext[1]);
+}
+
+extern BOOLEAN UseQPCTime;
+
+__inline
+LARGE_INTEGER
+ClasspGetCurrentTime(
+ PLARGE_INTEGER Frequency
+ )
+{
+ LARGE_INTEGER currentTime;
+
+ if (UseQPCTime) {
+ currentTime = KeQueryPerformanceCounter(Frequency);
+ } else {
+ currentTime.QuadPart = (LONGLONG)KeQueryUnbiasedInterruptTime();
+ }
+
+ return currentTime;
+}
+
+__inline
+ULONGLONG
+ClasspTimeDiffToMs(
+ PCLASS_PRIVATE_FDO_DATA FdoData,
+ ULONGLONG TimeDiff
+ )
+{
+ if (UseQPCTime) {
+ TimeDiff *= 1000;
+ TimeDiff /= FdoData->PerfCounterFrequency.QuadPart;
+ } else {
+ TimeDiff /= (10 * 1000);
+ }
+
+ return TimeDiff;
+}
+
+__inline
+BOOLEAN
+ClasspSupportsUnmap(
+ _In_ PCLASS_FUNCTION_SUPPORT_INFO SupportInfo
+ )
+{
+ return SupportInfo->LBProvisioningData.LBPU;
+}
+
+__inline
+BOOLEAN
+ClasspIsThinProvisioned(
+ _In_ PCLASS_FUNCTION_SUPPORT_INFO SupportInfo
+ )
+{
+ //
+ // We only support thinly provisioned devices that also support UNMAP.
+ //
+ if (SupportInfo->LBProvisioningData.ProvisioningType == PROVISIONING_TYPE_THIN &&
+ SupportInfo->LBProvisioningData.LBPU == TRUE)
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+__inline
+BOOLEAN
+ClasspIsObsoletePortDriver(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ )
+{
+ if ( (FdoExtension->MiniportDescriptor != NULL) &&
+ (FdoExtension->MiniportDescriptor->Portdriver == StoragePortCodeSetSCSIport) ) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+ULONG
+ClasspCalculateLogicalSectorSize (
+ _In_ PDEVICE_OBJECT Fdo,
+ _In_ ULONG BytesPerBlockInBigEndian
+ );
+
+DRIVER_INITIALIZE DriverEntry;
+
+DRIVER_UNLOAD ClassUnload;
+
+_Dispatch_type_(IRP_MJ_CREATE)
+_Dispatch_type_(IRP_MJ_CLOSE)
+DRIVER_DISPATCH ClassCreateClose;
+
+NTSTATUS
+ClasspCreateClose(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp
+ );
+
+VOID
+ClasspCleanupProtectedLocks(
+ IN PFILE_OBJECT_EXTENSION FsContext
+ );
+
+NTSTATUS
+ClasspEjectionControl(
+ IN PDEVICE_OBJECT Fdo,
+ IN PIRP Irp,
+ IN MEDIA_LOCK_TYPE LockType,
+ IN BOOLEAN Lock
+ );
+
+_Dispatch_type_(IRP_MJ_READ)
+_Dispatch_type_(IRP_MJ_WRITE)
+DRIVER_DISPATCH ClassReadWrite;
+
+_Dispatch_type_(IRP_MJ_DEVICE_CONTROL)
+DRIVER_DISPATCH ClassDeviceControlDispatch;
+
+_Dispatch_type_(IRP_MJ_PNP)
+DRIVER_DISPATCH ClassDispatchPnp;
+
+NTSTATUS
+ClassPnpStartDevice(
+ IN PDEVICE_OBJECT DeviceObject
+ );
+
+_Dispatch_type_(IRP_MJ_SHUTDOWN)
+_Dispatch_type_(IRP_MJ_FLUSH_BUFFERS)
+DRIVER_DISPATCH ClassShutdownFlush;
+
+_Dispatch_type_(IRP_MJ_SYSTEM_CONTROL)
+DRIVER_DISPATCH ClassSystemControl;
+
+
+//
+// Class internal routines
+//
+
+DRIVER_ADD_DEVICE ClassAddDevice;
+
+IO_COMPLETION_ROUTINE ClasspSendSynchronousCompletion;
+
+VOID
+RetryRequest(
+ PDEVICE_OBJECT DeviceObject,
+ PIRP Irp,
+ PSCSI_REQUEST_BLOCK Srb,
+ BOOLEAN Associated,
+ LONGLONG TimeDelta100ns
+ );
+
+NTSTATUS
+ClassIoCompletion(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN PVOID Context
+ );
+
+NTSTATUS
+ClassPnpQueryFdoRelations(
+ IN PDEVICE_OBJECT Fdo,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+ClassRetrieveDeviceRelations(
+ IN PDEVICE_OBJECT Fdo,
+ IN DEVICE_RELATION_TYPE RelationType,
+ OUT PDEVICE_RELATIONS *DeviceRelations
+ );
+
+NTSTATUS
+ClassGetPdoId(
+ IN PDEVICE_OBJECT Pdo,
+ IN BUS_QUERY_ID_TYPE IdType,
+ IN PUNICODE_STRING IdString
+ );
+
+NTSTATUS
+ClassQueryPnpCapabilities(
+ IN PDEVICE_OBJECT PhysicalDeviceObject,
+ IN PDEVICE_CAPABILITIES Capabilities
+ );
+
+DRIVER_STARTIO ClasspStartIo;
+
+NTSTATUS
+ClasspPagingNotificationCompletion(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN PDEVICE_OBJECT RealDeviceObject
+ );
+
+NTSTATUS
+ClasspMediaChangeCompletion(
+ PDEVICE_OBJECT DeviceObject,
+ PIRP Irp,
+ PVOID Context
+ );
+
+NTSTATUS
+ClasspMcnControl(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ IN PIRP Irp,
+ IN PSCSI_REQUEST_BLOCK Srb
+ );
+
+VOID
+ClasspRegisterMountedDeviceInterface(
+ IN PDEVICE_OBJECT DeviceObject
+ );
+
+VOID
+ClasspDisableTimer(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ );
+
+VOID
+ClasspEnableTimer(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ );
+
+NTSTATUS
+ClasspInitializeTimer(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+);
+
+VOID
+ClasspDeleteTimer(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+);
+
+#if (NTDDI_VERSION >= NTDDI_WINBLUE)
+BOOLEAN
+ClasspUpdateTimerNoWakeTolerance(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+);
+#endif
+
+NTSTATUS
+ClasspDuidQueryProperty(
+ PDEVICE_OBJECT DeviceObject,
+ PIRP Irp
+ );
+
+_Dispatch_type_(IRP_MJ_CREATE)
+_Dispatch_type_(IRP_MJ_CLOSE)
+_Dispatch_type_(IRP_MJ_READ)
+_Dispatch_type_(IRP_MJ_WRITE)
+_Dispatch_type_(IRP_MJ_SCSI)
+_Dispatch_type_(IRP_MJ_DEVICE_CONTROL)
+_Dispatch_type_(IRP_MJ_SHUTDOWN)
+_Dispatch_type_(IRP_MJ_FLUSH_BUFFERS)
+_Dispatch_type_(IRP_MJ_PNP)
+_Dispatch_type_(IRP_MJ_POWER)
+_Dispatch_type_(IRP_MJ_SYSTEM_CONTROL)
+DRIVER_DISPATCH ClassGlobalDispatch;
+
+VOID
+ClassInitializeDispatchTables(
+ PCLASS_DRIVER_EXTENSION DriverExtension
+ );
+
+NTSTATUS
+ClasspPersistentReserve(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ );
+
+//
+// routines for dictionary list support
+//
+
+VOID
+InitializeDictionary(
+ IN PDICTIONARY Dictionary
+ );
+
+BOOLEAN
+TestDictionarySignature(
+ IN PDICTIONARY Dictionary
+ );
+
+NTSTATUS
+AllocateDictionaryEntry(
+ IN PDICTIONARY Dictionary,
+ IN ULONGLONG Key,
+ IN ULONG Size,
+ IN ULONG Tag,
+ OUT PVOID *Entry
+ );
+
+PVOID
+GetDictionaryEntry(
+ IN PDICTIONARY Dictionary,
+ IN ULONGLONG Key
+ );
+
+VOID
+FreeDictionaryEntry(
+ IN PDICTIONARY Dictionary,
+ IN PVOID Entry
+ );
+
+
+NTSTATUS
+ClasspAllocateReleaseRequest(
+ IN PDEVICE_OBJECT Fdo
+ );
+
+VOID
+ClasspFreeReleaseRequest(
+ IN PDEVICE_OBJECT Fdo
+ );
+
+IO_COMPLETION_ROUTINE ClassReleaseQueueCompletion;
+
+VOID
+ClasspReleaseQueue(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP ReleaseQueueIrp
+ );
+
+VOID
+ClasspDisablePowerNotification(
+ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+);
+
+//
+// class power routines
+//
+
+_Dispatch_type_(IRP_MJ_POWER)
+DRIVER_DISPATCH ClassDispatchPower;
+
+NTSTATUS
+ClassMinimalPowerHandler(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp
+ );
+
+_IRQL_requires_same_
+NTSTATUS
+ClasspEnableIdlePower(
+ _In_ PDEVICE_OBJECT DeviceObject
+ );
+
+POWER_SETTING_CALLBACK ClasspPowerSettingCallback;
+
+//
+// Child list routines
+//
+
+VOID
+ClassAddChild(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION Parent,
+ _In_ PPHYSICAL_DEVICE_EXTENSION Child,
+ _In_ BOOLEAN AcquireLock
+ );
+
+PPHYSICAL_DEVICE_EXTENSION
+ClassRemoveChild(
+ IN PFUNCTIONAL_DEVICE_EXTENSION Parent,
+ IN PPHYSICAL_DEVICE_EXTENSION Child,
+ IN BOOLEAN AcquireLock
+ );
+
+VOID
+ClasspRetryDpcTimer(
+ IN PCLASS_PRIVATE_FDO_DATA FdoData
+ );
+
+KDEFERRED_ROUTINE ClasspRetryRequestDpc;
+
+VOID
+ClassFreeOrReuseSrb(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ IN __drv_freesMem(mem) PSCSI_REQUEST_BLOCK Srb
+ );
+
+VOID
+ClassRetryRequest(
+ IN PDEVICE_OBJECT SelfDeviceObject,
+ IN PIRP Irp,
+ _In_ _In_range_(0,MAXIMUM_RETRY_FOR_SINGLE_IO_IN_100NS_UNITS) // this is 100 seconds; already an assert in classpnp based on this
+ IN LONGLONG TimeDelta100ns // in 100ns units
+ );
+
+VOID
+ClasspBuildRequestEx(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ _In_ PIRP Irp,
+ _In_ __drv_aliasesMem PSCSI_REQUEST_BLOCK Srb
+ );
+
+NTSTATUS
+ClasspAllocateReleaseQueueIrp(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ );
+
+NTSTATUS
+ClasspAllocatePowerProcessIrp(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ );
+
+NTSTATUS
+ClasspInitializeGesn(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ IN PMEDIA_CHANGE_DETECTION_INFO Info
+ );
+
+VOID
+ClassSendEjectionNotification(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ );
+
+VOID
+ClasspScanForSpecialInRegistry(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ );
+
+VOID
+ClasspScanForClassHacks(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ IN ULONG_PTR Data
+ );
+
+NTSTATUS
+ClasspInitializeHotplugInfo(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ );
+
+VOID
+ClasspPerfIncrementErrorCount(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ );
+VOID
+ClasspPerfIncrementSuccessfulIo(
+ IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ );
+
+IO_WORKITEM_ROUTINE ClasspUpdateDiskProperties;
+
+__drv_allocatesMem(Mem)
+PTRANSFER_PACKET NewTransferPacket(PDEVICE_OBJECT Fdo);
+VOID DestroyTransferPacket(_In_ __drv_freesMem(mem) PTRANSFER_PACKET Pkt);
+VOID EnqueueFreeTransferPacket(PDEVICE_OBJECT Fdo, __drv_aliasesMem PTRANSFER_PACKET Pkt);
+PTRANSFER_PACKET DequeueFreeTransferPacket(PDEVICE_OBJECT Fdo, BOOLEAN AllocIfNeeded);
+PTRANSFER_PACKET DequeueFreeTransferPacketEx(_In_ PDEVICE_OBJECT Fdo, _In_ BOOLEAN AllocIfNeeded, _In_ ULONG Node);
+VOID SetupReadWriteTransferPacket(PTRANSFER_PACKET pkt, PVOID Buf, ULONG Len, LARGE_INTEGER DiskLocation, PIRP OriginalIrp);
+NTSTATUS SubmitTransferPacket(PTRANSFER_PACKET Pkt);
+IO_COMPLETION_ROUTINE TransferPktComplete;
+NTSTATUS ServiceTransferRequest(PDEVICE_OBJECT Fdo, PIRP Irp, BOOLEAN PostToDpc);
+VOID TransferPacketQueueRetryDpc(PTRANSFER_PACKET Pkt);
+KDEFERRED_ROUTINE TransferPacketRetryTimerDpc;
+BOOLEAN InterpretTransferPacketError(PTRANSFER_PACKET Pkt);
+BOOLEAN RetryTransferPacket(PTRANSFER_PACKET Pkt);
+VOID EnqueueDeferredClientIrp(PDEVICE_OBJECT Fdo, PIRP Irp);
+PIRP DequeueDeferredClientIrp(PDEVICE_OBJECT Fdo);
+VOID InitLowMemRetry(PTRANSFER_PACKET Pkt, PVOID BufPtr, ULONG Len, LARGE_INTEGER TargetLocation);
+BOOLEAN StepLowMemRetry(PTRANSFER_PACKET Pkt);
+VOID SetupEjectionTransferPacket(TRANSFER_PACKET *Pkt, BOOLEAN PreventMediaRemoval, PKEVENT SyncEventPtr, PIRP OriginalIrp);
+VOID SetupModeSenseTransferPacket(TRANSFER_PACKET *Pkt, PKEVENT SyncEventPtr, PVOID ModeSenseBuffer, UCHAR ModeSenseBufferLen, UCHAR PageMode, UCHAR SubPage, PIRP OriginalIrp, UCHAR PageControl);
+VOID SetupModeSelectTransferPacket(TRANSFER_PACKET *Pkt, PKEVENT SyncEventPtr, PVOID ModeSelectBuffer, UCHAR ModeSelectBufferLen, BOOLEAN SavePages, PIRP OriginalIrp);
+VOID SetupDriveCapacityTransferPacket(TRANSFER_PACKET *Pkt, PVOID ReadCapacityBuffer, ULONG ReadCapacityBufferLen, PKEVENT SyncEventPtr, PIRP OriginalIrp, BOOLEAN Use16ByteCdb);
+PMDL BuildDeviceInputMdl(PVOID Buffer, ULONG BufferLen);
+PMDL ClasspBuildDeviceMdl(PVOID Buffer, ULONG BufferLen, BOOLEAN WriteToDevice);
+VOID FreeDeviceInputMdl(PMDL Mdl);
+VOID ClasspFreeDeviceMdl(PMDL Mdl);
+NTSTATUS InitializeTransferPackets(PDEVICE_OBJECT Fdo);
+VOID DestroyAllTransferPackets(PDEVICE_OBJECT Fdo);
+VOID InterpretCapacityData(PDEVICE_OBJECT Fdo, PREAD_CAPACITY_DATA_EX ReadCapacityData);
+IO_WORKITEM_ROUTINE_EX CleanupTransferPacketToWorkingSetSizeWorker;
+VOID CleanupTransferPacketToWorkingSetSize(_In_ PDEVICE_OBJECT Fdo, _In_ BOOLEAN LimitNumPktToDelete, _In_ ULONG Node);
+
+_IRQL_requires_max_(APC_LEVEL)
+_IRQL_requires_min_(PASSIVE_LEVEL)
+_IRQL_requires_same_
+VOID
+ClasspSetupPopulateTokenTransferPacket(
+ _In_ __drv_aliasesMem POFFLOAD_READ_CONTEXT OffloadReadContext,
+ _In_ PTRANSFER_PACKET Pkt,
+ _In_ ULONG Length,
+ _In_reads_bytes_(Length) PUCHAR PopulateTokenBuffer,
+ _In_ PIRP OriginalIrp,
+ _In_ ULONG ListIdentifier
+ );
+
+_IRQL_requires_max_(APC_LEVEL)
+_IRQL_requires_min_(PASSIVE_LEVEL)
+_IRQL_requires_same_
+VOID
+ClasspSetupReceivePopulateTokenInformationTransferPacket(
+ _In_ POFFLOAD_READ_CONTEXT OffloadReadContext,
+ _In_ PTRANSFER_PACKET Pkt,
+ _In_ ULONG Length,
+ _In_reads_bytes_(Length) PUCHAR ReceivePopulateTokenInformationBuffer,
+ _In_ PIRP OriginalIrp,
+ _In_ ULONG ListIdentifier
+ );
+
+_IRQL_requires_max_(APC_LEVEL)
+_IRQL_requires_min_(PASSIVE_LEVEL)
+_IRQL_requires_same_
+VOID
+ClasspSetupWriteUsingTokenTransferPacket(
+ _In_ __drv_aliasesMem POFFLOAD_WRITE_CONTEXT OffloadWriteContext,
+ _In_ PTRANSFER_PACKET Pkt,
+ _In_ ULONG Length,
+ _In_reads_bytes_(Length) PUCHAR WriteUsingTokenBuffer,
+ _In_ PIRP OriginalIrp,
+ _In_ ULONG ListIdentifier
+ );
+
+_IRQL_requires_max_(APC_LEVEL)
+_IRQL_requires_min_(PASSIVE_LEVEL)
+_IRQL_requires_same_
+VOID
+ClasspSetupReceiveWriteUsingTokenInformationTransferPacket(
+ _In_ POFFLOAD_WRITE_CONTEXT OffloadWriteContext,
+ _In_ PTRANSFER_PACKET Pkt,
+ _In_ ULONG Length,
+ _In_reads_bytes_(Length) PUCHAR ReceiveWriteUsingTokenInformationBuffer,
+ _In_ PIRP OriginalIrp,
+ _In_ ULONG ListIdentifier
+ );
+
+ULONG ClasspModeSense(
+ _In_ PDEVICE_OBJECT Fdo,
+ _In_reads_bytes_(Length) PCHAR ModeSenseBuffer,
+ _In_ ULONG Length,
+ _In_ UCHAR PageMode,
+ _In_ UCHAR PageControl
+ );
+
+NTSTATUS
+ClasspModeSelect(
+ _In_ PDEVICE_OBJECT Fdo,
+ _In_reads_bytes_(Length) PCHAR ModeSelectBuffer,
+ _In_ ULONG Length,
+ _In_ BOOLEAN SavePages
+ );
+
+NTSTATUS ClasspWriteCacheProperty(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ );
+
+NTSTATUS ClasspAccessAlignmentProperty(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ );
+
+NTSTATUS ClasspDeviceSeekPenaltyProperty(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ );
+
+NTSTATUS ClasspDeviceGetLBProvisioningVPDPage(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _Inout_opt_ PSCSI_REQUEST_BLOCK Srb
+ );
+
+NTSTATUS ClasspDeviceGetBlockDeviceCharacteristicsVPDPage(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension,
+ _In_ PSCSI_REQUEST_BLOCK Srb
+ );
+
+NTSTATUS ClasspDeviceGetBlockLimitsVPDPage(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ _Inout_bytecount_(SrbSize) PSCSI_REQUEST_BLOCK Srb,
+ _In_ ULONG SrbSize,
+ _Out_ PCLASS_VPD_B0_DATA BlockLimitsData
+ );
+
+NTSTATUS ClasspDeviceTrimProperty(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ );
+
+NTSTATUS ClasspDeviceLBProvisioningProperty(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _Inout_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ );
+
+NTSTATUS ClasspDeviceTrimProcess(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ );
+
+NTSTATUS ClasspDeviceGetLBAStatus(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _Inout_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ );
+
+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
+ );
+
+VOID ClassQueueThresholdEventWorker(
+ _In_ PDEVICE_OBJECT DeviceObject
+ );
+
+VOID ClassQueueResourceExhaustionEventWorker(
+ _In_ PDEVICE_OBJECT DeviceObject
+ );
+
+VOID ClassQueueCapacityChangedEventWorker(
+ _In_ PDEVICE_OBJECT DeviceObject
+ );
+
+VOID ClassQueueProvisioningTypeChangedEventWorker(
+ _In_ PDEVICE_OBJECT DeviceObject
+ );
+
+IO_WORKITEM_ROUTINE ClasspLogIOEventWithContext;
+
+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
+ );
+
+VOID
+ClasspZeroQERR(
+ _In_ PDEVICE_OBJECT DeviceObject
+ );
+
+
+_IRQL_requires_max_(PASSIVE_LEVEL)
+NTSTATUS
+ClasspGetMaximumTokenListIdentifier(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_z_ PWSTR RegistryPath,
+ _Out_ PULONG MaximumListIdentifier
+ );
+
+_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
+ );
+
+_IRQL_requires_max_(APC_LEVEL)
+_IRQL_requires_min_(PASSIVE_LEVEL)
+_IRQL_requires_same_
+NTSTATUS
+ClasspValidateOffloadSupported(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PIRP Irp
+ );
+
+_IRQL_requires_max_(APC_LEVEL)
+_IRQL_requires_min_(PASSIVE_LEVEL)
+_IRQL_requires_same_
+NTSTATUS
+ClasspValidateOffloadInputParameters(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PIRP Irp
+ );
+
+_IRQL_requires_same_
+NTSTATUS
+ClasspGetTokenOperationCommandBufferLength(
+ _In_ PDEVICE_OBJECT Fdo,
+ _In_ ULONG ServiceAction,
+ _Inout_ PULONG CommandBufferLength,
+ _Out_opt_ PULONG TokenOperationBufferLength,
+ _Out_opt_ PULONG ReceiveTokenInformationBufferLength
+ );
+
+_IRQL_requires_same_
+NTSTATUS
+ClasspGetTokenOperationDescriptorLimits(
+ _In_ PDEVICE_OBJECT Fdo,
+ _In_ ULONG ServiceAction,
+ _In_ ULONG MaxParameterBufferLength,
+ _Out_ PULONG MaxBlockDescriptorsCount,
+ _Out_ PULONGLONG MaxBlockDescriptorsLength
+ );
+
+_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
+ );
+
+NTSTATUS
+ClasspDeviceMediaTypeProperty(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _Inout_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ );
+
+_IRQL_requires_same_
+PUCHAR
+ClasspBinaryToAscii(
+ _In_reads_(Length) PUCHAR HexBuffer,
+ _In_ ULONG Length,
+ _Inout_ PULONG UpdateLength
+ );
+
+__inline
+BOOLEAN
+ClasspIsTokenOperationComplete(
+ _In_ ULONG CurrentStatus
+ )
+{
+ BOOLEAN operationCompleted = FALSE;
+
+ switch (CurrentStatus) {
+ case OPERATION_COMPLETED_WITH_SUCCESS:
+ case OPERATION_COMPLETED_WITH_ERROR:
+ case OPERATION_COMPLETED_WITH_RESIDUAL_DATA:
+ case OPERATION_TERMINATED: {
+
+ operationCompleted = TRUE;
+ }
+ }
+
+ return operationCompleted;
+}
+
+__inline
+BOOLEAN
+ClasspIsTokenOperation(
+ _In_ PCDB Cdb
+ )
+{
+ BOOLEAN tokenOperation = FALSE;
+
+ if (Cdb) {
+ ULONG opCode = Cdb->AsByte[0];
+ ULONG serviceAction = Cdb->AsByte[1];
+
+ if ((opCode == SCSIOP_POPULATE_TOKEN && serviceAction == SERVICE_ACTION_POPULATE_TOKEN) ||
+ (opCode == SCSIOP_WRITE_USING_TOKEN && serviceAction == SERVICE_ACTION_WRITE_USING_TOKEN)) {
+
+ tokenOperation = TRUE;
+ }
+ }
+
+ return tokenOperation;
+}
+
+__inline
+BOOLEAN
+ClasspIsReceiveTokenInformation(
+ _In_ PCDB Cdb
+ )
+{
+ BOOLEAN receiveTokenInformation = FALSE;
+
+ if (Cdb) {
+ ULONG opCode = Cdb->AsByte[0];
+ ULONG serviceAction = Cdb->AsByte[1];
+
+ if (opCode == SCSIOP_RECEIVE_ROD_TOKEN_INFORMATION && serviceAction == SERVICE_ACTION_RECEIVE_TOKEN_INFORMATION) {
+
+ receiveTokenInformation = TRUE;
+ }
+ }
+
+ return receiveTokenInformation;
+}
+
+__inline
+BOOLEAN
+ClasspIsOffloadDataTransferCommand(
+ _In_ PCDB Cdb
+ )
+{
+ BOOLEAN offloadCommand = (ClasspIsTokenOperation(Cdb) || ClasspIsReceiveTokenInformation(Cdb)) ? TRUE : FALSE;
+
+ return offloadCommand;
+}
+
+extern LIST_ENTRY AllFdosList;
+
+
+VOID
+ClasspInitializeIdleTimer(
+ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ );
+
+NTSTATUS
+ClasspIsPortable(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ _Out_ PBOOLEAN IsPortable
+ );
+
+VOID
+ClasspGetInquiryVpdSupportInfo(
+ _Inout_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ );
+
+NTSTATUS
+ClasspGetLBProvisioningInfo(
+ _Inout_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ );
+
+
+_IRQL_requires_(PASSIVE_LEVEL)
+_IRQL_requires_same_
+NTSTATUS
+ClassDetermineTokenOperationCommandSupport(
+ _In_ PDEVICE_OBJECT DeviceObject
+ );
+
+_IRQL_requires_same_
+NTSTATUS
+ClasspGetBlockDeviceTokenLimitsInfo(
+ _Inout_ PDEVICE_OBJECT DeviceObject
+ );
+
+_IRQL_requires_max_(APC_LEVEL)
+_IRQL_requires_min_(PASSIVE_LEVEL)
+_IRQL_requires_same_
+NTSTATUS
+ClassDeviceProcessOffloadRead(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ );
+
+_IRQL_requires_max_(APC_LEVEL)
+_IRQL_requires_min_(PASSIVE_LEVEL)
+_IRQL_requires_same_
+NTSTATUS
+ClassDeviceProcessOffloadWrite(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ );
+
+_IRQL_requires_max_(APC_LEVEL)
+_IRQL_requires_min_(PASSIVE_LEVEL)
+_IRQL_requires_same_
+NTSTATUS
+ClasspServicePopulateTokenTransferRequest(
+ _In_ PDEVICE_OBJECT Fdo,
+ _In_ PIRP Irp
+ );
+
+_IRQL_requires_same_
+VOID
+ClasspReceivePopulateTokenInformation(
+ _In_ POFFLOAD_READ_CONTEXT OffloadReadContext
+ );
+
+_IRQL_requires_max_(APC_LEVEL)
+_IRQL_requires_min_(PASSIVE_LEVEL)
+_IRQL_requires_same_
+NTSTATUS
+ClasspServiceWriteUsingTokenTransferRequest(
+ _In_ PDEVICE_OBJECT Fdo,
+ _In_ PIRP Irp
+ );
+
+_IRQL_requires_same_
+VOID
+ClasspReceiveWriteUsingTokenInformation(
+ _In_ POFFLOAD_WRITE_CONTEXT OffloadWriteContext
+ );
+
+VOID
+ClasspCompleteOffloadRequest(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PIRP Irp,
+ _In_ NTSTATUS CompletionStatus
+ );
+
+VOID
+ClasspCleanupOffloadReadContext(
+ _In_ __drv_freesMem(mem) POFFLOAD_READ_CONTEXT OffloadReadContext
+ );
+
+VOID
+ClasspCompleteOffloadRead(
+ _In_ POFFLOAD_READ_CONTEXT OffloadReadContext,
+ _In_ NTSTATUS CompletionStatus
+ );
+
+// PCONTINUATION_ROUTINE
+VOID
+ClasspPopulateTokenTransferPacketDone(
+ _In_ PVOID Context
+ );
+
+// PCONTINUATION_ROUTINE
+VOID
+ClasspReceivePopulateTokenInformationTransferPacketDone(
+ _In_ PVOID Context
+ );
+
+VOID
+ClasspContinueOffloadWrite(
+ _In_ __drv_aliasesMem POFFLOAD_WRITE_CONTEXT OffloadWriteContext
+ );
+
+VOID
+ClasspCleanupOffloadWriteContext(
+ _In_ __drv_freesMem(mem) POFFLOAD_WRITE_CONTEXT OffloadWriteContext
+ );
+
+VOID
+ClasspCompleteOffloadWrite(
+ _In_ __drv_freesMem(Mem) POFFLOAD_WRITE_CONTEXT OffloadWriteContext,
+ _In_ NTSTATUS CompletionCausingStatus
+ );
+
+VOID
+ClasspReceiveWriteUsingTokenInformationDone(
+ _In_ POFFLOAD_WRITE_CONTEXT OffloadWriteContext,
+ _In_ NTSTATUS CompletionCausingStatus
+ );
+
+VOID
+ClasspWriteUsingTokenTransferPacketDone(
+ _In_ PVOID Context
+ );
+
+VOID
+ClasspReceiveWriteUsingTokenInformationTransferPacketDone(
+ _In_ POFFLOAD_WRITE_CONTEXT OffloadWriteContext
+ );
+
+NTSTATUS
+ClasspRefreshFunctionSupportInfo(
+ _Inout_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ _In_ BOOLEAN ForceQuery
+ );
+
+NTSTATUS
+ClasspBlockLimitsDataSnapshot(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ _In_ BOOLEAN ForceQuery,
+ _Out_ PCLASS_VPD_B0_DATA BlockLimitsData,
+ _Out_ PULONG GenerationCount
+ );
+
+NTSTATUS
+InterpretReadCapacity16Data (
+ _Inout_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ _In_ PREAD_CAPACITY16_DATA ReadCapacity16Data
+ );
+
+NTSTATUS
+ClassReadCapacity16 (
+ _Inout_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ );
+
+NTSTATUS
+ClassDeviceGetLBProvisioningResources(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _Inout_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ );
+
+_IRQL_requires_same_
+NTSTATUS
+ClasspStorageEventNotification(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PIRP Irp
+ );
+
+_IRQL_requires_max_(PASSIVE_LEVEL)
+NTSTATUS
+ClasspPowerActivateDevice(
+ _In_ PDEVICE_OBJECT DeviceObject
+ );
+
+_IRQL_requires_max_(PASSIVE_LEVEL)
+NTSTATUS
+ClasspPowerIdleDevice(
+ _In_ PDEVICE_OBJECT DeviceObject
+ );
+
+IO_WORKITEM_ROUTINE ClassLogThresholdEvent;
+
+NTSTATUS
+ClasspLogSystemEventWithDeviceNumber(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ NTSTATUS IoErrorCode
+ );
+
+IO_WORKITEM_ROUTINE ClassLogResourceExhaustionEvent;
+
+NTSTATUS
+ClasspEnqueueIdleRequest(
+ PDEVICE_OBJECT DeviceObject,
+ PIRP Irp
+ );
+
+VOID
+ClasspCompleteIdleRequest(
+ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ );
+
+NTSTATUS
+ClasspPriorityHint(
+ PDEVICE_OBJECT DeviceObject,
+ PIRP Irp
+ );
+
+VOID
+HistoryInitializeRetryLogs(
+ _Out_ PSRB_HISTORY History,
+ ULONG HistoryCount
+ );
+#define HISTORYINITIALIZERETRYLOGS(_packet) \
+ { \
+ if (_packet->RetryHistory != NULL) \
+ { \
+ HistoryInitializeRetryLogs( \
+ _packet->RetryHistory, \
+ _packet->RetryHistory->TotalHistoryCount \
+ ); \
+ } \
+ }
+
+VOID
+HistoryLogSendPacket(
+ TRANSFER_PACKET *Pkt
+ );
+#define HISTORYLOGSENDPACKET(_packet) \
+ { \
+ if (_packet->RetryHistory != NULL) { \
+ HistoryLogSendPacket(_packet); \
+ } \
+ }
+
+VOID
+HistoryLogReturnedPacket(
+ TRANSFER_PACKET *Pkt
+ );
+
+#define HISTORYLOGRETURNEDPACKET(_packet) \
+ { \
+ if (_packet->RetryHistory != NULL) { \
+ HistoryLogReturnedPacket(_packet); \
+ } \
+ }
+
+BOOLEAN
+InterpretSenseInfoWithoutHistory(
+ _In_ PDEVICE_OBJECT Fdo,
+ _In_opt_ PIRP OriginalRequest,
+ _In_ PSCSI_REQUEST_BLOCK Srb,
+ UCHAR MajorFunctionCode,
+ ULONG IoDeviceCode,
+ ULONG PreviousRetryCount,
+ _Out_ NTSTATUS * Status,
+ _Out_opt_ _Deref_out_range_(0,MAXIMUM_RETRY_FOR_SINGLE_IO_IN_100NS_UNITS)
+ LONGLONG * RetryIn100nsUnits
+ );
+
+BOOLEAN
+ClasspMyStringMatches(
+ _In_opt_z_ PCHAR StringToMatch,
+ _In_z_ PCHAR TargetString
+ );
+
+
+
+#define TRACKING_FORWARD_PROGRESS_PATH1 (0x00000001)
+#define TRACKING_FORWARD_PROGRESS_PATH2 (0x00000002)
+#define TRACKING_FORWARD_PROGRESS_PATH3 (0x00000004)
+
+
+VOID
+ClasspInitializeRemoveTracking(
+ _In_ PDEVICE_OBJECT DeviceObject
+ );
+
+VOID
+ClasspUninitializeRemoveTracking(
+ _In_ PDEVICE_OBJECT DeviceObject
+ );
+
+RTL_GENERIC_COMPARE_ROUTINE RemoveTrackingCompareRoutine;
+
+RTL_GENERIC_ALLOCATE_ROUTINE RemoveTrackingAllocateRoutine;
+
+RTL_GENERIC_FREE_ROUTINE RemoveTrackingFreeRoutine;
+
+#if (NTDDI_VERSION >= NTDDI_WIN8)
+
+typedef PVOID
+(*PSRB_ALLOCATE_ROUTINE) (
+ _In_ CLONG ByteSize
+ );
+
+PVOID
+DefaultStorageRequestBlockAllocateRoutine(
+ _In_ CLONG ByteSize
+ );
+
+
+NTSTATUS
+CreateStorageRequestBlock(
+ _Inout_ PSTORAGE_REQUEST_BLOCK *Srb,
+ _In_ USHORT AddressType,
+ _In_opt_ PSRB_ALLOCATE_ROUTINE AllocateRoutine,
+ _Inout_opt_ ULONG *ByteSize,
+ _In_ ULONG NumSrbExData,
+ ...
+ );
+
+NTSTATUS
+InitializeStorageRequestBlock(
+ _Inout_bytecount_(ByteSize) PSTORAGE_REQUEST_BLOCK Srb,
+ _In_ USHORT AddressType,
+ _In_ ULONG ByteSize,
+ _In_ ULONG NumSrbExData,
+ ...
+ );
+
+VOID
+ClasspConvertToScsiRequestBlock(
+ _Out_ PSCSI_REQUEST_BLOCK Srb,
+ _In_ PSTORAGE_REQUEST_BLOCK SrbEx
+ );
+
+__inline PCDB
+ClasspTransferPacketGetCdb(
+ _In_ PTRANSFER_PACKET Pkt
+ )
+{
+ return SrbGetCdb(Pkt->Srb);
+}
+
+//
+// This inline function calculates number of retries already happened till now for known operation codes
+// and set the out parameter - TimesAlreadyRetried with the value, returns True
+//
+// For unknown operation codes this function will return false and will set TimesAlreadyRetried with zero
+//
+__inline BOOLEAN
+ClasspTransferPacketGetNumberOfRetriesDone(
+ _In_ PTRANSFER_PACKET Pkt,
+ _In_ PCDB Cdb,
+ _Out_ PULONG TimesAlreadyRetried
+ )
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Pkt->Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
+
+ if (Cdb->MEDIA_REMOVAL.OperationCode == SCSIOP_MEDIUM_REMOVAL)
+ {
+ *TimesAlreadyRetried = NUM_LOCKMEDIAREMOVAL_RETRIES - Pkt->NumRetries;
+ }
+ else if ((Cdb->MODE_SENSE.OperationCode == SCSIOP_MODE_SENSE) ||
+ (Cdb->MODE_SENSE.OperationCode == SCSIOP_MODE_SENSE10))
+ {
+ *TimesAlreadyRetried = NUM_MODESENSE_RETRIES - Pkt->NumRetries;
+ }
+ else if ((Cdb->CDB10.OperationCode == SCSIOP_READ_CAPACITY) ||
+ (Cdb->CDB16.OperationCode == SCSIOP_READ_CAPACITY16))
+ {
+ *TimesAlreadyRetried = NUM_DRIVECAPACITY_RETRIES - Pkt->NumRetries;
+ }
+ else if (IS_SCSIOP_READWRITE(Cdb->CDB10.OperationCode))
+ {
+ *TimesAlreadyRetried = fdoData->MaxNumberOfIoRetries - Pkt->NumRetries;
+ }
+ else if (Cdb->TOKEN_OPERATION.OperationCode == SCSIOP_POPULATE_TOKEN &&
+ Cdb->TOKEN_OPERATION.ServiceAction == SERVICE_ACTION_POPULATE_TOKEN)
+ {
+ *TimesAlreadyRetried = NUM_POPULATE_TOKEN_RETRIES - Pkt->NumRetries;
+ }
+ else if (Cdb->TOKEN_OPERATION.OperationCode == SCSIOP_WRITE_USING_TOKEN &&
+ Cdb->TOKEN_OPERATION.ServiceAction == SERVICE_ACTION_WRITE_USING_TOKEN)
+ {
+ *TimesAlreadyRetried = NUM_WRITE_USING_TOKEN_RETRIES - Pkt->NumRetries;
+ }
+ else if (ClasspIsReceiveTokenInformation(Cdb))
+ {
+ *TimesAlreadyRetried = NUM_RECEIVE_TOKEN_INFORMATION_RETRIES - Pkt->NumRetries;
+ }
+
+ else
+ {
+ *TimesAlreadyRetried = 0;
+ return FALSE;
+ }
+
+
+ return TRUE;
+}
+
+
+__inline PVOID
+ClasspTransferPacketGetSenseInfoBuffer(
+ _In_ PTRANSFER_PACKET Pkt
+ )
+{
+ return SrbGetSenseInfoBuffer(Pkt->Srb);
+}
+
+__inline UCHAR
+ClasspTransferPacketGetSenseInfoBufferLength(
+ _In_ PTRANSFER_PACKET Pkt
+ )
+{
+ return SrbGetSenseInfoBufferLength(Pkt->Srb);
+}
+
+
+__inline VOID
+ClasspSrbSetOriginalIrp(
+ _In_ PSTORAGE_REQUEST_BLOCK_HEADER Srb,
+ _In_ PIRP Irp
+ )
+{
+ if (Srb->Function == SRB_FUNCTION_STORAGE_REQUEST_BLOCK)
+ {
+ ((PSTORAGE_REQUEST_BLOCK)Srb)->MiniportContext = (PVOID)Irp;
+ }
+ else
+ {
+ ((PSCSI_REQUEST_BLOCK)Srb)->SrbExtension = (PVOID)Irp;
+ }
+}
+
+__inline
+BOOLEAN
+PORT_ALLOCATED_SENSE_EX(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ _In_ PSTORAGE_REQUEST_BLOCK_HEADER Srb
+ )
+{
+ return ((BOOLEAN)((TEST_FLAG(SrbGetSrbFlags(Srb), SRB_FLAGS_PORT_DRIVER_ALLOCSENSE) &&
+ TEST_FLAG(SrbGetSrbFlags(Srb), SRB_FLAGS_FREE_SENSE_BUFFER)) &&
+ (SrbGetSenseInfoBuffer(Srb) != FdoExtension->SenseData))
+ );
+}
+
+__inline
+VOID
+FREE_PORT_ALLOCATED_SENSE_BUFFER_EX(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ _In_ PSTORAGE_REQUEST_BLOCK_HEADER Srb
+ )
+{
+ NT_ASSERT(TEST_FLAG(SrbGetSrbFlags(Srb), SRB_FLAGS_PORT_DRIVER_ALLOCSENSE));
+ NT_ASSERT(TEST_FLAG(SrbGetSrbFlags(Srb), SRB_FLAGS_FREE_SENSE_BUFFER));
+ NT_ASSERT(SrbGetSenseInfoBuffer(Srb) != FdoExtension->SenseData);
+
+ ExFreePool(SrbGetSenseInfoBuffer(Srb));
+ SrbSetSenseInfoBuffer(Srb, FdoExtension->SenseData);
+ SrbSetSenseInfoBufferLength(Srb, GET_FDO_EXTENSON_SENSE_DATA_LENGTH(FdoExtension));
+ SrbClearSrbFlags(Srb, SRB_FLAGS_FREE_SENSE_BUFFER);
+ return;
+}
+
+#endif //NTDDI_WIN8
+
+BOOLEAN
+ClasspFailurePredictionPeriodMissed(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ );
+
+__inline
+ULONG
+ClasspGetMaxUsableBufferLengthFromOffset(
+ _In_ PVOID BaseAddress,
+ _In_ ULONG OffsetInBytes,
+ _In_ ULONG BaseStructureSizeInBytes
+ )
+/*++
+
+Routine Description:
+
+ This routine returns the maximum size of a buffer that starts at a given offset,
+ based on the size of the containing structure.
+
+Arguments:
+
+ BaseAddress - The base address of the structure. The offset is computed relative to this.
+
+ OffsetInBytes - (BaseAddress + OffsetInBytes) points to the beginning of the buffer.
+
+ BaseStructureSizeInBytes - The size of the structure which contains the buffer.
+
+Return Value:
+
+ max(BaseStructureSizeInBytes - OffsetInBytes, 0). If any operations wrap around,
+ the return value is 0.
+
+--*/
+
+{
+
+ ULONG_PTR offsetAddress = ((ULONG_PTR)BaseAddress + OffsetInBytes);
+
+ if (offsetAddress < (ULONG_PTR)BaseAddress) {
+ //
+ // This means BaseAddress + OffsetInBytes > ULONG_PTR_MAX.
+ //
+ return 0;
+ }
+
+ if (OffsetInBytes > BaseStructureSizeInBytes) {
+ return 0;
+ }
+
+ return BaseStructureSizeInBytes - OffsetInBytes;
+}
+
+
+__inline
+BOOLEAN
+ClasspLowerLayerNotSupport (
+ _In_ NTSTATUS Status
+ )
+{
+ return ((Status == STATUS_NOT_SUPPORTED) ||
+ (Status == STATUS_NOT_IMPLEMENTED) ||
+ (Status == STATUS_INVALID_DEVICE_REQUEST) ||
+ (Status == STATUS_INVALID_PARAMETER_1));
+}
+
+NTSTATUS
+ClassDeviceHwFirmwareGetInfoProcess(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _Inout_ PIRP Irp
+ );
+
+NTSTATUS
+ClassDeviceHwFirmwareDownloadProcess(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _Inout_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ );
+
+NTSTATUS
+ClassDeviceHwFirmwareActivateProcess(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _Inout_ PIRP Irp,
+ _Inout_ PSCSI_REQUEST_BLOCK Srb
+ );
+
diff --git a/storage/class/classpnp/src/classpnp.htm b/storage/class/classpnp/src/classpnp.htm
new file mode 100644
index 00000000..4c2b159e
--- /dev/null
+++ b/storage/class/classpnp/src/classpnp.htm
@@ -0,0 +1,273 @@
+<html xmlns:v="urn:schemas-microsoft-com:vml"
+xmlns:o="urn:schemas-microsoft-com:office:office"
+xmlns:w="urn:schemas-microsoft-com:office:word"
+xmlns="http://www.w3.org/TR/REC-html40">
+
+<head>
+<meta http-equiv=Content-Type content="text/html; charset=windows-1252">
+<meta name=ProgId content=Word.Document>
+<meta name=Generator content="Microsoft Word 11">
+<meta name=Originator content="Microsoft Word 11">
+<link rel=File-List href="classpnp_files/filelist.xml">
+<title>ClassPnP</title>
+<!--[if gte mso 9]><xml>
+ <w:WordDocument>
+ <w:Zoom>BestFit</w:Zoom>
+ <w:SpellingState>Clean</w:SpellingState>
+ <w:GrammarState>Clean</w:GrammarState>
+ <w:ValidateAgainstSchemas/>
+ <w:SaveIfXMLInvalid>false</w:SaveIfXMLInvalid>
+ <w:IgnoreMixedContent>false</w:IgnoreMixedContent>
+ <w:AlwaysShowPlaceholderText>false</w:AlwaysShowPlaceholderText>
+ <w:BrowserLevel>MicrosoftInternetExplorer4</w:BrowserLevel>
+ </w:WordDocument>
+</xml><![endif]--><!--[if gte mso 9]><xml>
+ <w:LatentStyles DefLockedState="false" LatentStyleCount="156">
+ </w:LatentStyles>
+</xml><![endif]-->
+<style>
+<!--
+ /* Font Definitions */
+ @font-face
+ {font-family:"MS Mincho";
+ panose-1:2 2 6 9 4 2 5 8 3 4;
+ mso-font-alt:"\FF2D\FF33 \660E\671D";
+ mso-font-charset:128;
+ mso-generic-font-family:roman;
+ mso-font-format:other;
+ mso-font-pitch:fixed;
+ mso-font-signature:1 134676480 16 0 131072 0;}
+@font-face
+ {font-family:Verdana;
+ panose-1:2 11 6 4 3 5 4 4 2 4;
+ mso-font-alt:Tahoma;
+ mso-font-charset:0;
+ mso-generic-font-family:swiss;
+ mso-font-pitch:variable;
+ mso-font-signature:536871559 0 0 0 415 0;}
+@font-face
+ {font-family:"MS Sans Serif";
+ panose-1:0 0 0 0 0 0 0 0 0 0;
+ mso-font-alt:"Times New Roman";
+ mso-font-charset:0;
+ mso-generic-font-family:roman;
+ mso-font-format:other;
+ mso-font-pitch:auto;
+ mso-font-signature:0 0 0 0 0 0;}
+@font-face
+ {font-family:"\@MS Mincho";
+ mso-font-charset:128;
+ mso-generic-font-family:modern;
+ mso-font-pitch:fixed;
+ mso-font-signature:-1610612033 1757936891 16 0 131231 0;}
+ /* Style Definitions */
+ p.MsoNormal, li.MsoNormal, div.MsoNormal
+ {mso-style-parent:"";
+ margin:0in;
+ margin-bottom:.0001pt;
+ mso-pagination:widow-orphan;
+ font-size:12.0pt;
+ font-family:"Times New Roman";
+ mso-fareast-font-family:"MS Mincho";
+ color:black;}
+h2
+ {mso-margin-top-alt:auto;
+ margin-right:0in;
+ mso-margin-bottom-alt:auto;
+ margin-left:0in;
+ mso-pagination:widow-orphan;
+ mso-outline-level:2;
+ font-size:18.0pt;
+ font-family:"Times New Roman";
+ mso-fareast-font-family:"Times New Roman";
+ color:black;
+ font-weight:bold;}
+h3
+ {mso-margin-top-alt:auto;
+ margin-right:0in;
+ mso-margin-bottom-alt:auto;
+ margin-left:0in;
+ mso-pagination:widow-orphan;
+ mso-outline-level:3;
+ font-size:13.5pt;
+ font-family:"Times New Roman";
+ mso-fareast-font-family:"Times New Roman";
+ color:black;
+ font-weight:bold;}
+h4
+ {mso-margin-top-alt:auto;
+ margin-right:0in;
+ mso-margin-bottom-alt:auto;
+ margin-left:0in;
+ mso-pagination:widow-orphan;
+ mso-outline-level:4;
+ font-size:12.0pt;
+ font-family:"Times New Roman";
+ mso-fareast-font-family:"Times New Roman";
+ color:black;
+ font-weight:bold;}
+a:link, span.MsoHyperlink
+ {color:blue;
+ text-decoration:underline;
+ text-underline:single;}
+a:visited, span.MsoHyperlinkFollowed
+ {color:purple;
+ text-decoration:underline;
+ text-underline:single;}
+p
+ {mso-margin-top-alt:auto;
+ margin-right:0in;
+ mso-margin-bottom-alt:auto;
+ margin-left:0in;
+ mso-pagination:widow-orphan;
+ font-size:12.0pt;
+ font-family:"Times New Roman";
+ mso-fareast-font-family:"MS Mincho";
+ color:black;}
+pre
+ {margin:0in;
+ margin-bottom:.0001pt;
+ mso-pagination:widow-orphan;
+ tab-stops:45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;
+ font-size:10.0pt;
+ font-family:"Courier New";
+ mso-fareast-font-family:"MS Mincho";
+ color:black;}
+span.SpellE
+ {mso-style-name:"";
+ mso-spl-e:yes;}
+span.GramE
+ {mso-style-name:"";
+ mso-gram-e:yes;}
+@page Section1
+ {size:8.5in 11.0in;
+ margin:1.0in 1.25in 1.0in 1.25in;
+ mso-header-margin:.5in;
+ mso-footer-margin:.5in;
+ mso-paper-source:0;}
+div.Section1
+ {page:Section1;}
+-->
+</style>
+<!--[if gte mso 10]>
+<style>
+ /* Style Definitions */
+ table.MsoNormalTable
+ {mso-style-name:"Table Normal";
+ mso-tstyle-rowband-size:0;
+ mso-tstyle-colband-size:0;
+ mso-style-noshow:yes;
+ mso-style-parent:"";
+ mso-padding-alt:0in 5.4pt 0in 5.4pt;
+ mso-para-margin:0in;
+ mso-para-margin-bottom:.0001pt;
+ mso-pagination:widow-orphan;
+ font-size:10.0pt;
+ font-family:"Times New Roman";
+ mso-ansi-language:#0400;
+ mso-fareast-language:#0400;
+ mso-bidi-language:#0400;}
+</style>
+<![endif]-->
+<meta name=Template content="C:\PROGRAM FILES\MICROSOFT OFFICE\OFFICE\html.dot">
+<!--[if gte mso 9]><xml>
+ <o:shapedefaults v:ext="edit" spidmax="3074"/>
+</xml><![endif]--><!--[if gte mso 9]><xml>
+ <o:shapelayout v:ext="edit">
+ <o:idmap v:ext="edit" data="1"/>
+ </o:shapelayout></xml><![endif]-->
+</head>
+
+<body bgcolor=white lang=EN-US link=blue vlink=purple style='tab-interval:.5in'
+leftmargin=8>
+
+<div class=Section1>
+
+<h2><a name=classpnp></a><span class=SpellE><span style='mso-bookmark:classpnp'><span
+style='font-family:Verdana'>ClassPnP</span></span></span><span
+style='mso-bookmark:classpnp'></span><span style='font-family:Verdana'> <o:p></o:p></span></h2>
+
+<h3><span style='font-family:Verdana'>Summary<o:p></o:p></span></h3>
+
+<p><span style='font-size:10.0pt;font-family:Verdana'>This is the library for
+all storage drivers. It simplifies writing a storage driver by implementing 90 percent
+of the code required to support Plug and Play, Power Management, et cetera.
+This library is used by <span class=SpellE>disk.sys</span>, <span class=SpellE>cdrom.sys</span>
+and the tape class drivers.<o:p></o:p></span></p>
+
+<p><span style='font-size:10.0pt;font-family:Verdana'>No INF file is needed to
+install this library. The library is 64-bit compliant.<o:p></o:p></span></p>
+
+<h3><span style='font-family:Verdana'>Building the Sample<o:p></o:p></span></h3>
+
+<p><span style='font-size:10.0pt;font-family:Verdana'>To build the sample, run <b>build</b>.
+Once built, one binary will be created: <span class=SpellE>classpnp.sys</span>.
+This sample is based on live source code, and only builds in the current OS
+build environment.<span style='mso-spacerun:yes'>� </span><o:p></o:p></span></p>
+
+<h3><span style='font-family:Verdana'>CODE TOUR<o:p></o:p></span></h3>
+
+<h4><span style='font-family:Verdana'>File Manifest<o:p></o:p></span></h4>
+
+<pre><u>File<span style='mso-tab-count:3'>������������������ </span>Description<o:p></o:p></u></pre><pre><o:p>&nbsp;</o:p></pre><pre><span
+class=SpellE>Autorun.c</span><span style='mso-tab-count:2'>������������� </span>Media change notification (MCN) code</pre><pre><span
+class=SpellE>Class.c</span><span style='mso-tab-count:2'>������� </span><span
+style='mso-tab-count:1'>������� </span>Main code base</pre><pre><span
+class=SpellE>Class.rc</span><span style='mso-tab-count:2'>�������������� </span>Resource file</pre><pre><span
+class=SpellE>Class.src</span><span style='mso-tab-count:2'>������������� </span>Exports</pre><pre><span
+class=SpellE>Classp.h</span><span style='mso-tab-count:2'>�������������� </span>Private <span
+class=GramE>header</span></pre><pre><span class=SpellE>Classwmi.c</span><span
+style='mso-tab-count:2'>������������ </span>WMI functionality</pre><pre><span
+class=SpellE>Clntirp.c</span><span style='mso-tab-count:2'>������������� </span>Client IRP queuing code</pre><pre><span
+class=SpellE>Create.c</span><span style='mso-tab-count:2'>�������������� </span>Create IRP code</pre><pre><span
+class=SpellE>Data.c</span><span style='mso-tab-count:2'>�������� </span><span
+style='mso-tab-count:1'>������� </span>Static driver data</pre><pre><span
+class=SpellE>Debug.c</span><span style='mso-tab-count:2'>������� </span><span
+style='mso-tab-count:1'>������� </span>Debug code and data</pre><pre><span
+class=SpellE>Debug.h</span><span style='mso-tab-count:2'>������� </span><span
+style='mso-tab-count:1'>������� </span>Debug header file</pre><pre><span
+class=SpellE>Dictlib.c</span><span style='mso-tab-count:2'>������������� </span>File system dictionary code</pre><pre><span
+class=SpellE>Lock.c</span><span style='mso-tab-count:2'>�������� </span><span
+style='mso-tab-count:1'>������� </span>Storage <span class=GramE>remove</span> lock implementation</pre><pre><span
+class=SpellE>Makefile</span><span style='mso-tab-count:2'>�������������� </span><span
+class=SpellE>Makefile</span></pre><pre><span class=SpellE>Obsolete.c</span><span
+style='mso-tab-count:2'>������������ </span>Obsolete code used by legacy drivers</pre><pre><span
+class=SpellE>Power.c</span><span style='mso-tab-count:2'>������� </span><span
+style='mso-tab-count:1'>������� </span>Power code</pre><pre><span class=SpellE>Retry.c</span><span
+style='mso-tab-count:2'>������� </span><span style='mso-tab-count:1'>������� </span>Transfer <span
+class=GramE>packet retry</span> code</pre><pre>Sources<span style='mso-tab-count:
+2'>������� </span><span style='mso-tab-count:1'>������� </span>Sources file</pre><pre><span
+class=SpellE>Utils.c</span><span style='mso-tab-count:2'>������� </span><span
+style='mso-tab-count:1'>������� </span>Utility code</pre><pre><span
+class=SpellE>Xferpkt.c</span><span style='mso-tab-count:2'>������������� </span>Transfer packet processing code</pre><pre><o:p>&nbsp;</o:p></pre><pre><o:p>&nbsp;</o:p></pre>
+
+<p align=center style='text-align:center;tab-stops:45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt'><span
+style='font-size:10.0pt;font-family:"Courier New"'><a href="#top"><span
+style='font-family:Verdana'>Top of page</span></a></span><span
+style='font-size:10.0pt;font-family:Verdana;mso-bidi-font-family:"Courier New"'>
+<o:p></o:p></span></p>
+
+<pre><o:p>&nbsp;</o:p></pre>
+
+<table class=MsoNormalTable border=0 cellspacing=0 cellpadding=0 width=624
+ style='width:6.5in;mso-cellspacing:0in;mso-padding-alt:0in 5.4pt 0in 5.4pt'>
+ <tr style='mso-yfti-irow:0;mso-yfti-firstrow:yes;mso-yfti-lastrow:yes;
+ height:1.5pt'>
+ <td style='background:aqua;padding:.75pt .75pt .75pt .75pt;height:1.5pt'>
+ <p class=MsoNormal><o:p>&nbsp;</o:p></p>
+ </td>
+ </tr>
+</table>
+
+<pre><o:p>&nbsp;</o:p></pre><pre><o:p>&nbsp;</o:p></pre>
+
+<p style='tab-stops:45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt'><span
+style='font-size:7.5pt;font-family:"MS Sans Serif";mso-bidi-font-family:"Courier New"'>�
+2004 Microsoft Corporation</span><span style='font-size:10.0pt;font-family:
+Verdana;mso-bidi-font-family:"Courier New"'> <o:p></o:p></span></p>
+
+</div>
+
+</body>
+
+</html>
diff --git a/storage/class/classpnp/src/classpnp.vcxproj b/storage/class/classpnp/src/classpnp.vcxproj
new file mode 100644
index 00000000..1a18fbc8
--- /dev/null
+++ b/storage/class/classpnp/src/classpnp.vcxproj
@@ -0,0 +1,322 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{6AEA931E-5110-494C-A1DB-9FDE40CE7133}</ProjectGuid>
+ <RootNamespace>$(MSBuildProjectName)</RootNamespace>
+ <SupportsPackaging>false</SupportsPackaging>
+ <Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>
+ <Platform Condition="'$(Platform)' == ''">Win32</Platform>
+ <SampleGuid>{FDD23D45-9A28-4A9B-8C8A-3B388DEAC852}</SampleGuid>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <TargetVersion>Windows10</TargetVersion>
+ <UseDebugLibraries>False</UseDebugLibraries>
+ <DriverTargetPlatform>Universal</DriverTargetPlatform>
+ <DriverType>ExportDriver</DriverType>
+ <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
+ <ConfigurationType>Driver</ConfigurationType>
+ </PropertyGroup>
+ <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <TargetVersion>Windows10</TargetVersion>
+ <UseDebugLibraries>True</UseDebugLibraries>
+ <DriverTargetPlatform>Universal</DriverTargetPlatform>
+ <DriverType>ExportDriver</DriverType>
+ <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
+ <ConfigurationType>Driver</ConfigurationType>
+ </PropertyGroup>
+ <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <TargetVersion>Windows10</TargetVersion>
+ <UseDebugLibraries>False</UseDebugLibraries>
+ <DriverTargetPlatform>Universal</DriverTargetPlatform>
+ <DriverType>ExportDriver</DriverType>
+ <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
+ <ConfigurationType>Driver</ConfigurationType>
+ </PropertyGroup>
+ <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <TargetVersion>Windows10</TargetVersion>
+ <UseDebugLibraries>True</UseDebugLibraries>
+ <DriverTargetPlatform>Universal</DriverTargetPlatform>
+ <DriverType>ExportDriver</DriverType>
+ <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
+ <ConfigurationType>Driver</ConfigurationType>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <PropertyGroup>
+ <OutDir>$(IntDir)</OutDir>
+ </PropertyGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
+ </ImportGroup>
+ <ItemGroup Label="WrappedTaskItems">
+ <ClCompile Include="autorun.c; class.c; classwmi.c; create.c; data.c; dictlib.c; dispatch.c; history.c; lock.c; power.c; xferpkt.c; clntirp.c; retry.c; utils.c; obsolete.c; debug.c; srblib.c">
+ <WppEnabled Condition="'$(UseDebugLibraries)'=='false'">true</WppEnabled>
+ <WppKernelMode Condition="'$(UseDebugLibraries)'=='false'">true</WppKernelMode>
+ <WppTraceFunction Condition="'$(UseDebugLibraries)'=='false'">TracePrint((LEVEL,FLAGS,MSG,...))</WppTraceFunction>
+ </ClCompile>
+ <OtherWpp Include="class.rc; classlog.mof">
+ <WppEnabled Condition="'$(UseDebugLibraries)'=='false'">true</WppEnabled>
+ <WppKernelMode Condition="'$(UseDebugLibraries)'=='false'">true</WppKernelMode>
+ <WppTraceFunction Condition="'$(UseDebugLibraries)'=='false'">TracePrint((LEVEL,FLAGS,MSG,...))</WppTraceFunction>
+ </OtherWpp>
+ <Wmimofck Include=".\$(IntDir)\classlog.bmf">
+ <HeaderOutputFile>.\$(IntDir)\classlog.h</HeaderOutputFile>
+ <HexdumpOutputFile>.\$(IntDir)\classlog.x</HexdumpOutputFile>
+ <GenerateStructureDefinitionsForDatablocks>true</GenerateStructureDefinitionsForDatablocks>
+ </Wmimofck>
+ </ItemGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <TargetName>classpnp</TargetName>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <TargetName>classpnp</TargetName>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <TargetName>classpnp</TargetName>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <TargetName>classpnp</TargetName>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <TreatWarningAsError>true</TreatWarningAsError>
+ <WarningLevel>Level4</WarningLevel>
+ <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);..\inc;..\..\inc</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BREAK_ON_LOST_IRPS=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_SECONDS_TO_WAIT_FOR_SYNCHRONOUS_SRB=100</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_USE_DELAYED_RETRY=1</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFER_SIZE=512</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFERS=512</PreprocessorDefinitions>
+ </ClCompile>
+ <ResourceCompile>
+ <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);..\inc;..\..\inc</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BREAK_ON_LOST_IRPS=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_SECONDS_TO_WAIT_FOR_SYNCHRONOUS_SRB=100</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_USE_DELAYED_RETRY=1</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFER_SIZE=512</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFERS=512</PreprocessorDefinitions>
+ </ResourceCompile>
+ <Midl>
+ <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);..\inc;..\..\inc</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BREAK_ON_LOST_IRPS=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_SECONDS_TO_WAIT_FOR_SYNCHRONOUS_SRB=100</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_USE_DELAYED_RETRY=1</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFER_SIZE=512</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFERS=512</PreprocessorDefinitions>
+ </Midl>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <TreatWarningAsError>true</TreatWarningAsError>
+ <WarningLevel>Level4</WarningLevel>
+ <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);..\inc;..\..\inc</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BREAK_ON_LOST_IRPS=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_SECONDS_TO_WAIT_FOR_SYNCHRONOUS_SRB=100</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_USE_DELAYED_RETRY=1</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFER_SIZE=512</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFERS=512</PreprocessorDefinitions>
+ </ClCompile>
+ <ResourceCompile>
+ <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);..\inc;..\..\inc</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BREAK_ON_LOST_IRPS=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_SECONDS_TO_WAIT_FOR_SYNCHRONOUS_SRB=100</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_USE_DELAYED_RETRY=1</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFER_SIZE=512</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFERS=512</PreprocessorDefinitions>
+ </ResourceCompile>
+ <Midl>
+ <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);..\inc;..\..\inc</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BREAK_ON_LOST_IRPS=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_SECONDS_TO_WAIT_FOR_SYNCHRONOUS_SRB=100</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_USE_DELAYED_RETRY=1</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFER_SIZE=512</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFERS=512</PreprocessorDefinitions>
+ </Midl>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <TreatWarningAsError>true</TreatWarningAsError>
+ <WarningLevel>Level4</WarningLevel>
+ <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);..\inc;..\..\inc</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BREAK_ON_LOST_IRPS=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_SECONDS_TO_WAIT_FOR_SYNCHRONOUS_SRB=100</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_USE_DELAYED_RETRY=1</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFER_SIZE=512</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFERS=512</PreprocessorDefinitions>
+ </ClCompile>
+ <ResourceCompile>
+ <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);..\inc;..\..\inc</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BREAK_ON_LOST_IRPS=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_SECONDS_TO_WAIT_FOR_SYNCHRONOUS_SRB=100</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_USE_DELAYED_RETRY=1</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFER_SIZE=512</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFERS=512</PreprocessorDefinitions>
+ </ResourceCompile>
+ <Midl>
+ <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);..\inc;..\..\inc</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BREAK_ON_LOST_IRPS=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_SECONDS_TO_WAIT_FOR_SYNCHRONOUS_SRB=100</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_USE_DELAYED_RETRY=1</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFER_SIZE=512</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFERS=512</PreprocessorDefinitions>
+ </Midl>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <TreatWarningAsError>true</TreatWarningAsError>
+ <WarningLevel>Level4</WarningLevel>
+ <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);..\inc;..\..\inc</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BREAK_ON_LOST_IRPS=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_SECONDS_TO_WAIT_FOR_SYNCHRONOUS_SRB=100</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_USE_DELAYED_RETRY=1</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFER_SIZE=512</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFERS=512</PreprocessorDefinitions>
+ </ClCompile>
+ <ResourceCompile>
+ <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);..\inc;..\..\inc</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BREAK_ON_LOST_IRPS=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_SECONDS_TO_WAIT_FOR_SYNCHRONOUS_SRB=100</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_USE_DELAYED_RETRY=1</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFER_SIZE=512</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFERS=512</PreprocessorDefinitions>
+ </ResourceCompile>
+ <Midl>
+ <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);..\inc;..\..\inc</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BREAK_ON_LOST_IRPS=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_SECONDS_TO_WAIT_FOR_SYNCHRONOUS_SRB=100</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_USE_DELAYED_RETRY=1</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT=0</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFER_SIZE=512</PreprocessorDefinitions>
+ <PreprocessorDefinitions>%(PreprocessorDefinitions);CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFERS=512</PreprocessorDefinitions>
+ </Midl>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <PreprocessorDefinitions Condition="!('$(UseDebugLibraries)'=='false')">%(PreprocessorDefinitions);DEBUG_USE_KDPRINT</PreprocessorDefinitions>
+ <ExceptionHandling>
+ </ExceptionHandling>
+ </ClCompile>
+ <Midl>
+ <PreprocessorDefinitions Condition="!('$(UseDebugLibraries)'=='false')">%(PreprocessorDefinitions);DEBUG_USE_KDPRINT</PreprocessorDefinitions>
+ </Midl>
+ <ResourceCompile>
+ <PreprocessorDefinitions Condition="!('$(UseDebugLibraries)'=='false')">%(PreprocessorDefinitions);DEBUG_USE_KDPRINT</PreprocessorDefinitions>
+ </ResourceCompile>
+ <Link>
+ <ModuleDefinitionFile>class.def</ModuleDefinitionFile>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <PreprocessorDefinitions Condition="!('$(UseDebugLibraries)'=='false')">%(PreprocessorDefinitions);DEBUG_USE_KDPRINT</PreprocessorDefinitions>
+ <ExceptionHandling>
+ </ExceptionHandling>
+ </ClCompile>
+ <Midl>
+ <PreprocessorDefinitions Condition="!('$(UseDebugLibraries)'=='false')">%(PreprocessorDefinitions);DEBUG_USE_KDPRINT</PreprocessorDefinitions>
+ </Midl>
+ <ResourceCompile>
+ <PreprocessorDefinitions Condition="!('$(UseDebugLibraries)'=='false')">%(PreprocessorDefinitions);DEBUG_USE_KDPRINT</PreprocessorDefinitions>
+ </ResourceCompile>
+ <Link>
+ <ModuleDefinitionFile>class.def</ModuleDefinitionFile>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <PreprocessorDefinitions Condition="!('$(UseDebugLibraries)'=='false')">%(PreprocessorDefinitions);DEBUG_USE_KDPRINT</PreprocessorDefinitions>
+ <ExceptionHandling>
+ </ExceptionHandling>
+ </ClCompile>
+ <Midl>
+ <PreprocessorDefinitions Condition="!('$(UseDebugLibraries)'=='false')">%(PreprocessorDefinitions);DEBUG_USE_KDPRINT</PreprocessorDefinitions>
+ </Midl>
+ <ResourceCompile>
+ <PreprocessorDefinitions Condition="!('$(UseDebugLibraries)'=='false')">%(PreprocessorDefinitions);DEBUG_USE_KDPRINT</PreprocessorDefinitions>
+ </ResourceCompile>
+ <Link>
+ <ModuleDefinitionFile>class.def</ModuleDefinitionFile>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <PreprocessorDefinitions Condition="!('$(UseDebugLibraries)'=='false')">%(PreprocessorDefinitions);DEBUG_USE_KDPRINT</PreprocessorDefinitions>
+ <ExceptionHandling>
+ </ExceptionHandling>
+ </ClCompile>
+ <Midl>
+ <PreprocessorDefinitions Condition="!('$(UseDebugLibraries)'=='false')">%(PreprocessorDefinitions);DEBUG_USE_KDPRINT</PreprocessorDefinitions>
+ </Midl>
+ <ResourceCompile>
+ <PreprocessorDefinitions Condition="!('$(UseDebugLibraries)'=='false')">%(PreprocessorDefinitions);DEBUG_USE_KDPRINT</PreprocessorDefinitions>
+ </ResourceCompile>
+ <Link>
+ <ModuleDefinitionFile>class.def</ModuleDefinitionFile>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <MofComp Include="classlog.mof" />
+ <ResourceCompile Include="class.rc" />
+ </ItemGroup>
+ <Target Name="Custom Build Target 0" BeforeTargets="BeforeClCompile">
+ <ItemGroup>
+ <CustomBuildTarget0Input Include=".\$(IntDir)\classlog.mof" />
+ </ItemGroup>
+ <Exec Command="if not exist &quot;%(CustomBuildTarget0Input.Identity)&quot; copy &quot;.\%(CustomBuildTarget0Input.Filename)%(CustomBuildTarget0Input.Extension)&quot; &quot;%(CustomBuildTarget0Input.Identity)&quot;" WorkingDirectory="$(MSBuildProjectDirectory)" />
+ <Exec Command="mofcomp -Amendment:ms_409 -MFL:$(IntDir)\MFL.MFL -MOF:$(IntDir)\MOF.MOF &quot;%(CustomBuildTarget0Input.Identity)&quot;" WorkingDirectory="$(MSBuildProjectDirectory)" />
+ <Exec Command="wmimofck -y$(IntDir)\MOF.MOF -z$(IntDir)\MFL.MFL $(IntDir)\MOFMFL.MOF" WorkingDirectory="$(MSBuildProjectDirectory)" />
+ <Exec Command="mofcomp -B:&quot;.\$(IntDir)\classlog.bmf&quot; $(IntDir)\MOFMFL.MOF" WorkingDirectory="$(MSBuildProjectDirectory)" />
+ </Target>
+ <ItemGroup>
+ <Inf Exclude="@(Inf)" Include="*.inf" />
+ <FilesToPackage Include="$(TargetPath)" Condition="'$(ConfigurationType)'=='Driver' or '$(ConfigurationType)'=='DynamicLibrary'" />
+ <FilesToPackage Include="@(Inf->'%(CopyOutput)')" Condition="'@(Inf)'!=''" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Exclude="@(None)" Include="*.txt;*.htm;*.html" />
+ <None Exclude="@(None)" Include="*.ico;*.cur;*.bmp;*.dlg;*.rct;*.gif;*.jpg;*.jpeg;*.wav;*.jpe;*.tiff;*.tif;*.png;*.rc2" />
+ <None Exclude="@(None)" Include="*.def;*.bat;*.hpj;*.asmx" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Exclude="@(ClInclude)" Include="*.h;*.hpp;*.hxx;*.hm;*.inl;*.xsd" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+</Project> \ No newline at end of file
diff --git a/storage/class/classpnp/src/classpnp.vcxproj.Filters b/storage/class/classpnp/src/classpnp.vcxproj.Filters
new file mode 100644
index 00000000..4ee10b9f
--- /dev/null
+++ b/storage/class/classpnp/src/classpnp.vcxproj.Filters
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx;*</Extensions>
+ <UniqueIdentifier>{30C77614-BC54-48A1-B49D-DA6B50452C12}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Header Files">
+ <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
+ <UniqueIdentifier>{289E064C-1506-47AC-86D9-854F5A784947}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Resource Files">
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms;man;xml</Extensions>
+ <UniqueIdentifier>{7085ABD8-FCBB-43B4-993A-2B3187FFA33E}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Driver Files">
+ <Extensions>inf;inv;inx;mof;mc;</Extensions>
+ <UniqueIdentifier>{26B13FCD-BE1B-4A53-B538-39878886CB7C}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="autorun.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="class.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="classwmi.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="clntirp.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="create.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="data.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="debug.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="dictlib.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="dispatch.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="history.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="lock.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="obsolete.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="power.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="retry.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="srblib.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="utils.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="xferpkt.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <None Include="class.def">
+ <Filter>Source Files</Filter>
+ </None>
+ </ItemGroup>
+ <ItemGroup>
+ <MofComp Include="classlog.mof">
+ <Filter>Driver Files</Filter>
+ </MofComp>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="class.rc">
+ <Filter>Resource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/storage/class/classpnp/src/classwmi.c b/storage/class/classpnp/src/classwmi.c
new file mode 100644
index 00000000..1df85512
--- /dev/null
+++ b/storage/class/classpnp/src/classwmi.c
@@ -0,0 +1,1224 @@
+/*++
+
+Copyright (C) Microsoft Corporation, 1991 - 1999
+
+Module Name:
+
+ classwmi.c
+
+Abstract:
+
+ SCSI class driver routines
+
+Environment:
+
+ kernel mode only
+
+Notes:
+
+
+Revision History:
+
+--*/
+
+#include "stddef.h"
+#include "ntddk.h"
+#include "scsi.h"
+
+#include "classpnp.h"
+
+#include "mountdev.h"
+
+#include <stdarg.h>
+
+#include "classp.h"
+#include <wmistr.h>
+#include <wmidata.h>
+#include <classlog.h>
+
+#ifdef DEBUG_USE_WPP
+#include "classwmi.tmh"
+#endif
+
+const UCHAR wmiInternalMOF[] = {
+#include "classlog.x"
+};
+
+#define TIME_STRING_LENGTH 25
+
+BOOLEAN
+ClassFindGuid(
+ PGUIDREGINFO GuidList,
+ ULONG GuidCount,
+ LPGUID Guid,
+ PULONG GuidIndex
+ );
+
+NTSTATUS
+ClassQueryInternalDataBlock(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN ULONG GuidIndex,
+ IN ULONG BufferAvail,
+ OUT PUCHAR Buffer
+ );
+
+PWCHAR
+ConvertTickToDateTime(
+ IN LARGE_INTEGER Tick,
+ _Out_writes_(TIME_STRING_LENGTH) PWCHAR String
+ );
+
+BOOLEAN
+ClassFindInternalGuid(
+ LPGUID Guid,
+ PULONG GuidIndex
+ );
+
+
+//
+// This is the name for the MOF resource that must be part of all drivers that
+// register via this interface.
+#define MOFRESOURCENAME L"MofResourceName"
+
+//
+// What can be paged ???
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, ClassSystemControl)
+#pragma alloc_text(PAGE, ClassFindGuid)
+#pragma alloc_text(PAGE, ClassFindInternalGuid)
+#endif
+
+//
+// Define WMI interface to all class drivers
+//
+GUIDREGINFO wmiClassGuids[] =
+{
+ {
+ MSWmi_MofDataGuid, 1, 0
+ },
+ {
+ MSStorageDriver_ClassErrorLogGuid, 1, 0
+ }
+};
+
+#define MSWmi_MofData_GUID_Index 0
+#define MSStorageDriver_ClassErrorLogGuid_Index 1
+#define NUM_CLASS_WMI_GUIDS (sizeof(wmiClassGuids) / sizeof(GUIDREGINFO))
+
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassFindGuid()
+
+Routine Description:
+
+ This routine will search the list of guids registered and return
+ the index for the one that was registered.
+
+Arguments:
+
+ GuidList is the list of guids to search
+
+ GuidCount is the count of guids in the list
+
+ Guid is the guid being searched for
+
+ *GuidIndex returns the index to the guid
+
+Return Value:
+
+ TRUE if guid is found else FALSE
+
+--*/
+BOOLEAN
+ClassFindGuid(
+ PGUIDREGINFO GuidList,
+ ULONG GuidCount,
+ LPGUID Guid,
+ PULONG GuidIndex
+ )
+{
+ ULONG i;
+
+ PAGED_CODE();
+
+ for (i = 0; i < GuidCount; i++)
+ {
+ if (IsEqualGUID(Guid, &GuidList[i].Guid))
+ {
+ *GuidIndex = i;
+ return(TRUE);
+ }
+ }
+ return(FALSE);
+} // end ClassFindGuid()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassFindInternalGuid()
+
+Routine Description:
+
+ This routine will search the list of internal guids registered and return
+ the index for the one that was registered.
+
+Arguments:
+
+ Guid is the guid being searched for
+
+ *GuidIndex returns the index to the guid
+
+Return Value:
+
+ TRUE if guid is found else FALSE
+
+--*/
+BOOLEAN
+ClassFindInternalGuid(
+ LPGUID Guid,
+ PULONG GuidIndex
+ )
+{
+ ULONG i;
+
+ PAGED_CODE();
+
+ for (i = 0; i < NUM_CLASS_WMI_GUIDS; i++)
+ {
+ if (IsEqualGUID(Guid, &wmiClassGuids[i].Guid))
+ {
+ *GuidIndex = i;
+ return(TRUE);
+ }
+ }
+
+ return(FALSE);
+} // end ClassFindGuid()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassSystemControl()
+
+Routine Description:
+
+ Dispatch routine for IRP_MJ_SYSTEM_CONTROL. This routine will process
+ all wmi requests received, forwarding them if they are not for this
+ driver or determining if the guid is valid and if so passing it to
+ the driver specific function for handing wmi requests.
+
+Arguments:
+
+ DeviceObject - Supplies a pointer to the device object for this request.
+
+ Irp - Supplies the Irp making the request.
+
+Return Value:
+
+ status
+
+--*/
+NTSTATUS
+ClassSystemControl(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp
+ )
+{
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+ PCLASS_DRIVER_EXTENSION driverExtension;
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ ULONG isRemoved;
+ ULONG bufferSize;
+ PUCHAR buffer;
+ NTSTATUS status;
+ UCHAR minorFunction;
+ ULONG guidIndex = (ULONG)-1;
+ PCLASS_WMI_INFO classWmiInfo;
+ BOOLEAN isInternalGuid = FALSE;
+
+ PAGED_CODE();
+
+ //
+ // Make sure device has not been removed
+ isRemoved = ClassAcquireRemoveLock(DeviceObject, Irp);
+ if(isRemoved)
+ {
+ Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST;
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ return STATUS_DEVICE_DOES_NOT_EXIST;
+ }
+
+ //
+ // If the irp is not a WMI irp or it is not targetted at this device
+ // or this device has not regstered with WMI then just forward it on.
+ minorFunction = irpStack->MinorFunction;
+ if ((minorFunction > IRP_MN_EXECUTE_METHOD) ||
+ (irpStack->Parameters.WMI.ProviderId != (ULONG_PTR)DeviceObject) ||
+ ((minorFunction != IRP_MN_REGINFO) &&
+ (commonExtension->GuidCount == 0)))
+ {
+ //
+ // CONSIDER: Do I need to hang onto lock until IoCallDriver returns ?
+ IoSkipCurrentIrpStackLocation(Irp);
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ return(IoCallDriver(commonExtension->LowerDeviceObject, Irp));
+ }
+
+ buffer = (PUCHAR)irpStack->Parameters.WMI.Buffer;
+ bufferSize = irpStack->Parameters.WMI.BufferSize;
+
+ if (minorFunction != IRP_MN_REGINFO)
+ {
+ //
+ // For all requests other than query registration info we are passed
+ // a guid. Determine if the guid is one that is supported by the
+ // device.
+ if (commonExtension->GuidRegInfo != NULL &&
+ ClassFindGuid(commonExtension->GuidRegInfo,
+ commonExtension->GuidCount,
+ (LPGUID)irpStack->Parameters.WMI.DataPath,
+ &guidIndex))
+ {
+ isInternalGuid = FALSE;
+ status = STATUS_SUCCESS;
+ } else if (ClassFindInternalGuid((LPGUID)irpStack->Parameters.WMI.DataPath,
+ &guidIndex)) {
+ isInternalGuid = TRUE;
+ status = STATUS_SUCCESS;
+ } else {
+ status = STATUS_WMI_GUID_NOT_FOUND;
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_WMI, "WMI GUID not found!"));
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_WMI, "WMI Find Guid = %x, isInternalGuid = %x", status, isInternalGuid));
+ if (NT_SUCCESS(status) &&
+ ((minorFunction == IRP_MN_QUERY_SINGLE_INSTANCE) ||
+ (minorFunction == IRP_MN_CHANGE_SINGLE_INSTANCE) ||
+ (minorFunction == IRP_MN_CHANGE_SINGLE_ITEM) ||
+ (minorFunction == IRP_MN_EXECUTE_METHOD)))
+ {
+ if ( (((PWNODE_HEADER)buffer)->Flags) &
+ WNODE_FLAG_STATIC_INSTANCE_NAMES)
+ {
+ if ( ((PWNODE_SINGLE_INSTANCE)buffer)->InstanceIndex != 0 )
+ {
+ status = STATUS_WMI_INSTANCE_NOT_FOUND;
+ }
+ } else {
+ status = STATUS_WMI_INSTANCE_NOT_FOUND;
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_WMI, "WMI Instance not found!"));
+ }
+ }
+
+ if (! NT_SUCCESS(status))
+ {
+ Irp->IoStatus.Status = status;
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ return(status);
+ }
+ }
+
+ driverExtension = commonExtension->DriverExtension;
+
+ classWmiInfo = commonExtension->IsFdo ?
+ &driverExtension->InitData.FdoData.ClassWmiInfo :
+ &driverExtension->InitData.PdoData.ClassWmiInfo;
+ switch(minorFunction)
+ {
+ case IRP_MN_REGINFO:
+ {
+ ULONG guidCount;
+ PGUIDREGINFO guidList;
+ PWMIREGINFOW wmiRegInfo;
+ PWMIREGGUIDW wmiRegGuid;
+ PUNICODE_STRING regPath;
+ PWCHAR stringPtr;
+ ULONG retSize;
+ ULONG registryPathOffset;
+ ULONG mofResourceOffset;
+ ULONG bufferNeeded;
+ ULONG i;
+ ULONG_PTR nameInfo;
+ ULONG nameSize, nameOffset, nameFlags;
+ UNICODE_STRING name, mofName;
+ PCLASS_QUERY_WMI_REGINFO_EX ClassQueryWmiRegInfoEx;
+
+ name.Buffer = NULL;
+ name.Length = 0;
+ name.MaximumLength = 0;
+ nameFlags = 0;
+
+ ClassQueryWmiRegInfoEx = commonExtension->IsFdo ?
+ driverExtension->ClassFdoQueryWmiRegInfoEx :
+ driverExtension->ClassPdoQueryWmiRegInfoEx;
+
+ if ((classWmiInfo->GuidRegInfo != NULL) &&
+ (classWmiInfo->ClassQueryWmiRegInfo != NULL) &&
+ (ClassQueryWmiRegInfoEx == NULL))
+ {
+ status = classWmiInfo->ClassQueryWmiRegInfo(
+ DeviceObject,
+ &nameFlags,
+ &name);
+
+ RtlInitUnicodeString(&mofName, MOFRESOURCENAME);
+
+ } else if ((classWmiInfo->GuidRegInfo != NULL) && (ClassQueryWmiRegInfoEx != NULL)) {
+ RtlInitUnicodeString(&mofName, L"");
+
+ status = (*ClassQueryWmiRegInfoEx)(
+ DeviceObject,
+ &nameFlags,
+ &name,
+ &mofName);
+ } else {
+ RtlInitUnicodeString(&mofName, L"");
+ nameFlags = WMIREG_FLAG_INSTANCE_PDO;
+ status = STATUS_SUCCESS;
+ }
+
+ if (NT_SUCCESS(status) &&
+ (! (nameFlags & WMIREG_FLAG_INSTANCE_PDO) &&
+ (name.Buffer == NULL)))
+ {
+ //
+ // if PDO flag not specified then an instance name must be
+ status = STATUS_INVALID_DEVICE_REQUEST;
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_WMI, "Invalid Device Request!"));
+ }
+
+ if (NT_SUCCESS(status))
+ {
+ guidList = classWmiInfo->GuidRegInfo;
+ guidCount = (classWmiInfo->GuidRegInfo == NULL ? 0 : classWmiInfo->GuidCount) + NUM_CLASS_WMI_GUIDS;
+
+ nameOffset = sizeof(WMIREGINFO) +
+ guidCount * sizeof(WMIREGGUIDW);
+
+ if (nameFlags & WMIREG_FLAG_INSTANCE_PDO)
+ {
+ nameSize = 0;
+ nameInfo = commonExtension->IsFdo ?
+ (ULONG_PTR)((PFUNCTIONAL_DEVICE_EXTENSION)commonExtension)->LowerPdo :
+ (ULONG_PTR)DeviceObject;
+ } else {
+ nameFlags |= WMIREG_FLAG_INSTANCE_LIST;
+ nameSize = name.Length + sizeof(USHORT);
+ nameInfo = nameOffset;
+ }
+
+ mofResourceOffset = nameOffset + nameSize;
+
+ registryPathOffset = mofResourceOffset +
+ mofName.Length + sizeof(USHORT);
+
+ regPath = &driverExtension->RegistryPath;
+
+ bufferNeeded = registryPathOffset + regPath->Length;
+ bufferNeeded += sizeof(USHORT);
+
+ if (bufferNeeded <= bufferSize)
+ {
+ retSize = bufferNeeded;
+
+ commonExtension->GuidCount = guidCount;
+ commonExtension->GuidRegInfo = guidList;
+
+ wmiRegInfo = (PWMIREGINFO)buffer;
+ wmiRegInfo->BufferSize = bufferNeeded;
+ wmiRegInfo->NextWmiRegInfo = 0;
+ wmiRegInfo->MofResourceName = mofResourceOffset;
+ wmiRegInfo->RegistryPath = registryPathOffset;
+ wmiRegInfo->GuidCount = guidCount;
+
+ for (i = 0; i < classWmiInfo->GuidCount; i++)
+ {
+ wmiRegGuid = &wmiRegInfo->WmiRegGuid[i];
+ wmiRegGuid->Guid = guidList[i].Guid;
+ wmiRegGuid->Flags = guidList[i].Flags | nameFlags;
+ wmiRegGuid->InstanceInfo = nameInfo;
+ wmiRegGuid->InstanceCount = 1;
+ }
+ for (i = 0; i < NUM_CLASS_WMI_GUIDS; i++)
+ {
+ wmiRegGuid = &wmiRegInfo->WmiRegGuid[i + classWmiInfo->GuidCount];
+ wmiRegGuid->Guid = wmiClassGuids[i].Guid;
+ wmiRegGuid->Flags = wmiClassGuids[i].Flags | nameFlags;
+ wmiRegGuid->InstanceInfo = nameInfo;
+ wmiRegGuid->InstanceCount = 1;
+ }
+
+ if ( nameFlags & WMIREG_FLAG_INSTANCE_LIST)
+ {
+ bufferNeeded = nameOffset + sizeof(WCHAR);
+ bufferNeeded += name.Length;
+
+ if (bufferSize >= bufferNeeded){
+ stringPtr = (PWCHAR)((PUCHAR)buffer + nameOffset);
+ *stringPtr++ = name.Length;
+ RtlCopyMemory(stringPtr, name.Buffer, name.Length);
+ }
+ else {
+ NT_ASSERT(bufferSize >= bufferNeeded);
+ status = STATUS_INVALID_BUFFER_SIZE;
+ }
+ }
+
+ bufferNeeded = mofResourceOffset + sizeof(WCHAR);
+ bufferNeeded += mofName.Length;
+
+ if (bufferSize >= bufferNeeded){
+ stringPtr = (PWCHAR)((PUCHAR)buffer + mofResourceOffset);
+ *stringPtr++ = mofName.Length;
+ RtlCopyMemory(stringPtr, mofName.Buffer, mofName.Length);
+ }
+ else {
+ NT_ASSERT(bufferSize >= bufferNeeded);
+ status = STATUS_INVALID_BUFFER_SIZE;
+ }
+
+ bufferNeeded = registryPathOffset + sizeof(WCHAR);
+ bufferNeeded += regPath->Length;
+
+ if (bufferSize >= bufferNeeded){
+ stringPtr = (PWCHAR)((PUCHAR)buffer + registryPathOffset);
+ *stringPtr++ = regPath->Length;
+ RtlCopyMemory(stringPtr,
+ regPath->Buffer,
+ regPath->Length);
+ }
+ else {
+
+ NT_ASSERT(bufferSize >= bufferNeeded);
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_WMI, "Invalid Buffer Size!"));
+ status = STATUS_INVALID_BUFFER_SIZE;
+ }
+
+ } else {
+ *((PULONG)buffer) = bufferNeeded;
+ retSize = sizeof(ULONG);
+ }
+ } else {
+ retSize = 0;
+ }
+
+ FREE_POOL(name.Buffer);
+
+ Irp->IoStatus.Status = status;
+ Irp->IoStatus.Information = retSize;
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ return(status);
+ }
+
+ case IRP_MN_QUERY_ALL_DATA:
+ {
+ PWNODE_ALL_DATA wnode;
+ ULONG bufferAvail;
+
+ wnode = (PWNODE_ALL_DATA)buffer;
+
+ if (bufferSize < sizeof(WNODE_ALL_DATA))
+ {
+ bufferAvail = 0;
+ } else {
+ bufferAvail = bufferSize - sizeof(WNODE_ALL_DATA);
+ }
+
+ wnode->DataBlockOffset = sizeof(WNODE_ALL_DATA);
+
+ NT_ASSERT(guidIndex != (ULONG)-1);
+ _Analysis_assume_(isInternalGuid);
+ if (isInternalGuid)
+ {
+ status = ClassQueryInternalDataBlock(
+ DeviceObject,
+ Irp,
+ guidIndex,
+ bufferAvail,
+ buffer + sizeof(WNODE_ALL_DATA));
+ } else {
+ status = classWmiInfo->ClassQueryWmiDataBlock(
+ DeviceObject,
+ Irp,
+ guidIndex,
+ bufferAvail,
+ buffer + sizeof(WNODE_ALL_DATA));
+ }
+ break;
+ }
+
+ case IRP_MN_QUERY_SINGLE_INSTANCE:
+ {
+ PWNODE_SINGLE_INSTANCE wnode;
+ ULONG dataBlockOffset;
+
+ wnode = (PWNODE_SINGLE_INSTANCE)buffer;
+
+ dataBlockOffset = wnode->DataBlockOffset;
+
+ NT_ASSERT(guidIndex != (ULONG)-1);
+ _Analysis_assume_(isInternalGuid);
+ if (isInternalGuid)
+ {
+ status = ClassQueryInternalDataBlock(
+ DeviceObject,
+ Irp,
+ guidIndex,
+ bufferSize - dataBlockOffset,
+ (PUCHAR)wnode + dataBlockOffset);
+ } else {
+ status = classWmiInfo->ClassQueryWmiDataBlock(
+ DeviceObject,
+ Irp,
+ guidIndex,
+ bufferSize - dataBlockOffset,
+ (PUCHAR)wnode + dataBlockOffset);
+ }
+ break;
+ }
+
+ case IRP_MN_CHANGE_SINGLE_INSTANCE:
+ {
+ PWNODE_SINGLE_INSTANCE wnode;
+
+ wnode = (PWNODE_SINGLE_INSTANCE)buffer;
+ _Analysis_assume_(isInternalGuid);
+ if (isInternalGuid)
+ {
+ status = ClassWmiCompleteRequest(DeviceObject,
+ Irp,
+ STATUS_WMI_GUID_NOT_FOUND,
+ 0,
+ IO_NO_INCREMENT);
+ } else {
+
+ NT_ASSERT(guidIndex != (ULONG)-1);
+
+ status = classWmiInfo->ClassSetWmiDataBlock(
+ DeviceObject,
+ Irp,
+ guidIndex,
+ wnode->SizeDataBlock,
+ (PUCHAR)wnode + wnode->DataBlockOffset);
+ }
+
+ break;
+ }
+
+ case IRP_MN_CHANGE_SINGLE_ITEM:
+ {
+ PWNODE_SINGLE_ITEM wnode;
+
+ wnode = (PWNODE_SINGLE_ITEM)buffer;
+
+ NT_ASSERT(guidIndex != (ULONG)-1);
+ _Analysis_assume_(isInternalGuid);
+ if (isInternalGuid)
+ {
+ status = ClassWmiCompleteRequest(DeviceObject,
+ Irp,
+ STATUS_WMI_GUID_NOT_FOUND,
+ 0,
+ IO_NO_INCREMENT);
+ } else {
+
+ NT_ASSERT(guidIndex != (ULONG)-1);
+
+ status = classWmiInfo->ClassSetWmiDataItem(
+ DeviceObject,
+ Irp,
+ guidIndex,
+ wnode->ItemId,
+ wnode->SizeDataItem,
+ (PUCHAR)wnode + wnode->DataBlockOffset);
+
+ }
+
+ break;
+ }
+
+ case IRP_MN_EXECUTE_METHOD:
+ {
+ PWNODE_METHOD_ITEM wnode;
+
+ wnode = (PWNODE_METHOD_ITEM)buffer;
+ _Analysis_assume_(isInternalGuid);
+ if (isInternalGuid)
+ {
+ status = ClassWmiCompleteRequest(DeviceObject,
+ Irp,
+ STATUS_WMI_GUID_NOT_FOUND,
+ 0,
+ IO_NO_INCREMENT);
+ } else {
+
+ NT_ASSERT(guidIndex != (ULONG)-1);
+
+ status = classWmiInfo->ClassExecuteWmiMethod(
+ DeviceObject,
+ Irp,
+ guidIndex,
+ wnode->MethodId,
+ wnode->SizeDataBlock,
+ bufferSize - wnode->DataBlockOffset,
+ buffer + wnode->DataBlockOffset);
+ }
+
+ break;
+ }
+
+ case IRP_MN_ENABLE_EVENTS:
+ {
+ _Analysis_assume_(isInternalGuid);
+ if (isInternalGuid)
+ {
+ status = ClassWmiCompleteRequest(DeviceObject,
+ Irp,
+ STATUS_WMI_GUID_NOT_FOUND,
+ 0,
+ IO_NO_INCREMENT);
+ } else {
+
+ NT_ASSERT(guidIndex != (ULONG)-1);
+
+ status = classWmiInfo->ClassWmiFunctionControl(
+ DeviceObject,
+ Irp,
+ guidIndex,
+ EventGeneration,
+ TRUE);
+ }
+ break;
+ }
+
+ case IRP_MN_DISABLE_EVENTS:
+ {
+ _Analysis_assume_(isInternalGuid);
+ if (isInternalGuid)
+ {
+ status = ClassWmiCompleteRequest(DeviceObject,
+ Irp,
+ STATUS_WMI_GUID_NOT_FOUND,
+ 0,
+ IO_NO_INCREMENT);
+ } else {
+
+ NT_ASSERT(guidIndex != (ULONG)-1);
+
+ status = classWmiInfo->ClassWmiFunctionControl(
+ DeviceObject,
+ Irp,
+ guidIndex,
+ EventGeneration,
+ FALSE);
+ }
+ break;
+ }
+
+ case IRP_MN_ENABLE_COLLECTION:
+ {
+ _Analysis_assume_(isInternalGuid);
+ if (isInternalGuid)
+ {
+ status = ClassWmiCompleteRequest(DeviceObject,
+ Irp,
+ STATUS_WMI_GUID_NOT_FOUND,
+ 0,
+ IO_NO_INCREMENT);
+ } else {
+
+ NT_ASSERT(guidIndex != (ULONG)-1);
+
+ status = classWmiInfo->ClassWmiFunctionControl(
+ DeviceObject,
+ Irp,
+ guidIndex,
+ DataBlockCollection,
+ TRUE);
+ }
+ break;
+ }
+
+ case IRP_MN_DISABLE_COLLECTION:
+ {
+ _Analysis_assume_(isInternalGuid);
+ if (isInternalGuid)
+ {
+ status = ClassWmiCompleteRequest(DeviceObject,
+ Irp,
+ STATUS_WMI_GUID_NOT_FOUND,
+ 0,
+ IO_NO_INCREMENT);
+ } else {
+
+ NT_ASSERT(guidIndex != (ULONG)-1);
+
+ status = classWmiInfo->ClassWmiFunctionControl(
+ DeviceObject,
+ Irp,
+ guidIndex,
+ DataBlockCollection,
+ FALSE);
+ }
+
+ break;
+ }
+
+ default:
+ {
+ status = STATUS_INVALID_DEVICE_REQUEST;
+ break;
+ }
+
+ }
+
+ return(status);
+} // end ClassSystemControl()
+
+
+NTSTATUS
+ClassQueryInternalDataBlock(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN ULONG GuidIndex,
+ IN ULONG BufferAvail,
+ OUT PUCHAR Buffer
+ )
+/*++
+
+Routine Description:
+
+ This routine allows querying for the contents of an internal WMI
+ data block. When the driver has finished filling the data block it
+ must call ClassWmiCompleteRequest to complete the irp.
+
+Arguments:
+
+ DeviceObject is the device whose data block is being queried
+
+ Irp is the Irp that makes this request
+
+ GuidIndex is the index into the list of guids provided when the
+ device registered
+
+ BufferAvail on has the maximum size available to write the data
+ block.
+
+ Buffer on return is filled with the returned data block
+
+
+Return Value:
+
+ status
+
+--*/
+{
+ NTSTATUS status;
+ ULONG sizeNeeded = 0, i;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt = DeviceObject->DeviceExtension;
+
+ if (GuidIndex == MSWmi_MofData_GUID_Index) {
+ sizeNeeded = sizeof(wmiInternalMOF);
+ if (BufferAvail >= sizeNeeded) {
+ RtlMoveMemory(Buffer, wmiInternalMOF, sizeof(wmiInternalMOF));
+ status = STATUS_SUCCESS;
+ } else {
+ status = STATUS_BUFFER_TOO_SMALL;
+ }
+ } else if (GuidIndex == MSStorageDriver_ClassErrorLogGuid_Index) {
+
+ //
+ // NOTE - ClassErrorLog is still using SCSI_REQUEST_BLOCK and will not be
+ // updated to support extended SRB until classpnp is updated to send >16
+ // byte CDBs. Extended SRBs will be translated to SCSI_REQUEST_BLOCK.
+ //
+ sizeNeeded = MSStorageDriver_ClassErrorLog_SIZE;
+ if (BufferAvail >= sizeNeeded) {
+ PMSStorageDriver_ClassErrorLog errorLog = (PMSStorageDriver_ClassErrorLog) Buffer;
+ PMSStorageDriver_ClassErrorLogEntry logEntry;
+ PMSStorageDriver_ScsiRequestBlock srbBlock;
+ PMSStorageDriver_SenseData senseData;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
+ PCLASS_ERROR_LOG_DATA fdoLogEntry;
+ PSCSI_REQUEST_BLOCK fdoSRBBlock;
+ PSENSE_DATA fdoSenseData;
+ errorLog->numEntries = NUM_ERROR_LOG_ENTRIES;
+ for (i = 0; i < NUM_ERROR_LOG_ENTRIES; i++) {
+ fdoLogEntry = &fdoData->ErrorLogs[i];
+ fdoSRBBlock = &fdoLogEntry->Srb;
+ fdoSenseData = &fdoLogEntry->SenseData;
+ logEntry = &errorLog->logEntries[i];
+ srbBlock = &logEntry->srb;
+ senseData = &logEntry->senseData;
+ logEntry->tickCount = fdoLogEntry->TickCount.QuadPart;
+ logEntry->portNumber = fdoLogEntry->PortNumber;
+ logEntry->errorPaging = (fdoLogEntry->ErrorPaging == 0 ? FALSE : TRUE);
+ logEntry->errorRetried = (fdoLogEntry->ErrorRetried == 0 ? FALSE : TRUE);
+ logEntry->errorUnhandled = (fdoLogEntry->ErrorUnhandled == 0 ? FALSE : TRUE);
+ logEntry->errorReserved = fdoLogEntry->ErrorReserved;
+ RtlMoveMemory(logEntry->reserved, fdoLogEntry->Reserved, sizeof(logEntry->reserved));
+ ConvertTickToDateTime(fdoLogEntry->TickCount, logEntry->eventTime);
+
+ srbBlock->length = fdoSRBBlock->Length;
+ srbBlock->function = fdoSRBBlock->Function;
+ srbBlock->srbStatus = fdoSRBBlock->SrbStatus;
+ srbBlock->scsiStatus = fdoSRBBlock->ScsiStatus;
+ srbBlock->pathID = fdoSRBBlock->PathId;
+ srbBlock->targetID = fdoSRBBlock->TargetId;
+ srbBlock->lun = fdoSRBBlock->Lun;
+ srbBlock->queueTag = fdoSRBBlock->QueueTag;
+ srbBlock->queueAction = fdoSRBBlock->QueueAction;
+ srbBlock->cdbLength = fdoSRBBlock->CdbLength;
+ srbBlock->senseInfoBufferLength = fdoSRBBlock->SenseInfoBufferLength;
+ srbBlock->srbFlags = fdoSRBBlock->SrbFlags;
+ srbBlock->dataTransferLength = fdoSRBBlock->DataTransferLength;
+ srbBlock->timeOutValue = fdoSRBBlock->TimeOutValue;
+ srbBlock->dataBuffer = (ULONGLONG) fdoSRBBlock->DataBuffer;
+ srbBlock->senseInfoBuffer = (ULONGLONG) fdoSRBBlock->SenseInfoBuffer;
+ srbBlock->nextSRB = (ULONGLONG) fdoSRBBlock->NextSrb;
+ srbBlock->originalRequest = (ULONGLONG) fdoSRBBlock->OriginalRequest;
+ srbBlock->srbExtension = (ULONGLONG) fdoSRBBlock->SrbExtension;
+ srbBlock->internalStatus = fdoSRBBlock->InternalStatus;
+#if defined(_WIN64)
+ srbBlock->reserved = fdoSRBBlock->Reserved;
+#else
+ srbBlock->reserved = 0;
+#endif
+ RtlMoveMemory(srbBlock->cdb, fdoSRBBlock->Cdb, sizeof(srbBlock->cdb));
+
+ //
+ // Note: Sense data has been converted into Fixed format before it was
+ // put in the log. Therefore, no conversion is needed here.
+ //
+ senseData->errorCode = fdoSenseData->ErrorCode;
+ senseData->valid = (fdoSenseData->Valid == 0 ? FALSE : TRUE);
+ senseData->segmentNumber = fdoSenseData->SegmentNumber;
+ senseData->senseKey = fdoSenseData->SenseKey;
+ senseData->reserved = (fdoSenseData->Reserved == 0 ? FALSE : TRUE);
+ senseData->incorrectLength = (fdoSenseData->IncorrectLength == 0 ? FALSE : TRUE);
+ senseData->endOfMedia = (fdoSenseData->EndOfMedia == 0 ? FALSE : TRUE);
+ senseData->fileMark = (fdoSenseData->FileMark == 0 ? FALSE : TRUE);
+ RtlMoveMemory(senseData->information, fdoSenseData->Information, sizeof(senseData->information));
+ senseData->additionalSenseLength = fdoSenseData->AdditionalSenseLength;
+ RtlMoveMemory(senseData->commandSpecificInformation, fdoSenseData->CommandSpecificInformation, sizeof(senseData->commandSpecificInformation));
+ senseData->additionalSenseCode = fdoSenseData->AdditionalSenseCode;
+ senseData->additionalSenseCodeQualifier = fdoSenseData->AdditionalSenseCodeQualifier;
+ senseData->fieldReplaceableUnitCode = fdoSenseData->FieldReplaceableUnitCode;
+ RtlMoveMemory(senseData->senseKeySpecific, fdoSenseData->SenseKeySpecific, sizeof(senseData->senseKeySpecific));
+ }
+ status = STATUS_SUCCESS;
+ } else {
+ status = STATUS_BUFFER_TOO_SMALL;
+ }
+ } else if (GuidIndex > 0 && GuidIndex < NUM_CLASS_WMI_GUIDS) {
+ status = STATUS_WMI_INSTANCE_NOT_FOUND;
+ } else {
+ status = STATUS_WMI_GUID_NOT_FOUND;
+ }
+ status = ClassWmiCompleteRequest(DeviceObject,
+ Irp,
+ status,
+ sizeNeeded,
+ IO_NO_INCREMENT);
+ return status;
+}
+
+PWCHAR
+ConvertTickToDateTime(
+ IN LARGE_INTEGER Tick,
+ _Out_writes_(TIME_STRING_LENGTH) PWCHAR String
+ )
+
+/*++
+
+Routine Description:
+
+ This routine converts a tick count to a datetime (MOF) data type
+
+Arguments:
+
+ Tick - The tick count that needs to be converted
+ String - The buffer to hold the time string, must be able to hold WCHAR[25]
+
+Return Value:
+
+ The time string
+
+--*/
+
+{
+ LARGE_INTEGER nowTick, nowTime, time;
+ ULONG maxInc = 0;
+ TIME_FIELDS timeFields = {0};
+ WCHAR outDateTime[TIME_STRING_LENGTH + 1];
+
+ nowTick.QuadPart = 0;
+ nowTime.QuadPart = 0;
+ //
+ // Translate the tick count to a system time
+ //
+ KeQueryTickCount(&nowTick);
+ maxInc = KeQueryTimeIncrement();
+ KeQuerySystemTime(&nowTime);
+ time.QuadPart = nowTime.QuadPart - ((nowTick.QuadPart - Tick.QuadPart) * maxInc);
+
+ RtlTimeToTimeFields(&time, &timeFields);
+
+ //
+ // The buffer String is of size MAX_PATH. Use that to specify the buffer size.
+ //
+ //yyyymmddhhmmss.mmmmmmsutc
+ RtlStringCbPrintfW(outDateTime, sizeof(outDateTime), L"%04d%02d%02d%02d%02d%02d.%03d***+000", timeFields.Year, timeFields.Month, timeFields.Day, timeFields.Hour, timeFields.Minute, timeFields.Second, timeFields.Milliseconds);
+ RtlMoveMemory(String, outDateTime, sizeof(WCHAR) * TIME_STRING_LENGTH);
+ return String;
+}
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassWmiCompleteRequest()
+
+Routine Description:
+
+
+ This routine will do the work of completing a WMI irp. Depending upon the
+ the WMI request this routine will fixup the returned WNODE appropriately.
+
+ NOTE: This routine assumes that the ClassRemoveLock is held and it will
+ release it.
+
+Arguments:
+
+ DeviceObject - Supplies a pointer to the device object for this request.
+
+ Irp - Supplies the Irp making the request.
+
+ Status - Status to complete the irp with. STATUS_BUFFER_TOO_SMALL is used
+ to indicate that more buffer is required for the data requested.
+
+ BufferUsed - number of bytes of actual data to return (not including WMI
+ specific structures)
+
+ PriorityBoost - priority boost to pass to ClassCompleteRequest
+
+Return Value:
+
+ status
+
+--*/
+SCSIPORT_API
+NTSTATUS
+ClassWmiCompleteRequest(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _Inout_ PIRP Irp,
+ _In_ NTSTATUS Status,
+ _In_ ULONG BufferUsed,
+ _In_ CCHAR PriorityBoost
+ )
+{
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ PUCHAR buffer;
+ ULONG retSize;
+ UCHAR minorFunction;
+
+ minorFunction = irpStack->MinorFunction;
+ buffer = (PUCHAR)irpStack->Parameters.WMI.Buffer;
+
+ switch(minorFunction)
+ {
+ case IRP_MN_QUERY_ALL_DATA:
+ {
+ PWNODE_ALL_DATA wnode;
+ PWNODE_TOO_SMALL wnodeTooSmall;
+ ULONG bufferNeeded;
+
+ wnode = (PWNODE_ALL_DATA)buffer;
+
+ bufferNeeded = sizeof(WNODE_ALL_DATA) + BufferUsed;
+
+ if (NT_SUCCESS(Status))
+ {
+ retSize = bufferNeeded;
+ wnode->WnodeHeader.BufferSize = bufferNeeded;
+ KeQuerySystemTime(&wnode->WnodeHeader.TimeStamp);
+ wnode->WnodeHeader.Flags |= WNODE_FLAG_FIXED_INSTANCE_SIZE;
+ wnode->FixedInstanceSize = BufferUsed;
+ wnode->InstanceCount = 1;
+
+ } else if (Status == STATUS_BUFFER_TOO_SMALL) {
+ wnodeTooSmall = (PWNODE_TOO_SMALL)wnode;
+
+ wnodeTooSmall->WnodeHeader.BufferSize = sizeof(WNODE_TOO_SMALL);
+ wnodeTooSmall->WnodeHeader.Flags = WNODE_FLAG_TOO_SMALL;
+ wnodeTooSmall->SizeNeeded = sizeof(WNODE_ALL_DATA) + BufferUsed;
+ retSize = sizeof(WNODE_TOO_SMALL);
+ Status = STATUS_SUCCESS;
+ } else {
+ retSize = 0;
+ }
+ break;
+ }
+
+ case IRP_MN_QUERY_SINGLE_INSTANCE:
+ {
+ PWNODE_SINGLE_INSTANCE wnode;
+ PWNODE_TOO_SMALL wnodeTooSmall;
+ ULONG bufferNeeded;
+
+ wnode = (PWNODE_SINGLE_INSTANCE)buffer;
+
+ bufferNeeded = wnode->DataBlockOffset + BufferUsed;
+
+ if (NT_SUCCESS(Status))
+ {
+ retSize = bufferNeeded;
+ wnode->WnodeHeader.BufferSize = bufferNeeded;
+ KeQuerySystemTime(&wnode->WnodeHeader.TimeStamp);
+ wnode->SizeDataBlock = BufferUsed;
+
+ } else if (Status == STATUS_BUFFER_TOO_SMALL) {
+ wnodeTooSmall = (PWNODE_TOO_SMALL)wnode;
+
+ wnodeTooSmall->WnodeHeader.BufferSize = sizeof(WNODE_TOO_SMALL);
+ wnodeTooSmall->WnodeHeader.Flags = WNODE_FLAG_TOO_SMALL;
+ wnodeTooSmall->SizeNeeded = bufferNeeded;
+ retSize = sizeof(WNODE_TOO_SMALL);
+ Status = STATUS_SUCCESS;
+ } else {
+ retSize = 0;
+ }
+ break;
+ }
+
+ case IRP_MN_EXECUTE_METHOD:
+ {
+ PWNODE_METHOD_ITEM wnode;
+ PWNODE_TOO_SMALL wnodeTooSmall;
+ ULONG bufferNeeded;
+
+ wnode = (PWNODE_METHOD_ITEM)buffer;
+
+ bufferNeeded = wnode->DataBlockOffset + BufferUsed;
+
+ if (NT_SUCCESS(Status))
+ {
+ retSize = bufferNeeded;
+ wnode->WnodeHeader.BufferSize = bufferNeeded;
+ KeQuerySystemTime(&wnode->WnodeHeader.TimeStamp);
+ wnode->SizeDataBlock = BufferUsed;
+
+ } else if (Status == STATUS_BUFFER_TOO_SMALL) {
+ wnodeTooSmall = (PWNODE_TOO_SMALL)wnode;
+
+ wnodeTooSmall->WnodeHeader.BufferSize = sizeof(WNODE_TOO_SMALL);
+ wnodeTooSmall->WnodeHeader.Flags = WNODE_FLAG_TOO_SMALL;
+ wnodeTooSmall->SizeNeeded = bufferNeeded;
+ retSize = sizeof(WNODE_TOO_SMALL);
+ Status = STATUS_SUCCESS;
+ } else {
+ retSize = 0;
+ }
+ break;
+ }
+
+ default:
+ {
+ //
+ // All other requests don't return any data
+ retSize = 0;
+ break;
+ }
+
+ }
+
+ Irp->IoStatus.Status = Status;
+ Irp->IoStatus.Information = retSize;
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, PriorityBoost);
+ return(Status);
+} // end ClassWmiCompleteRequest()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassWmiFireEvent()
+
+Routine Description:
+
+ This routine will fire a WMI event using the data buffer passed. This
+ routine may be called at or below DPC level
+
+Arguments:
+
+ DeviceObject - Supplies a pointer to the device object for this event
+
+ Guid is pointer to the GUID that represents the event
+
+ InstanceIndex is the index of the instance of the event
+
+ EventDataSize is the number of bytes of data that is being fired with
+ with the event
+
+ EventData is the data that is fired with the events. This may be NULL
+ if there is no data associated with the event
+
+
+Return Value:
+
+ status
+
+--*/
+_IRQL_requires_max_(DISPATCH_LEVEL)
+NTSTATUS
+ClassWmiFireEvent(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ LPGUID Guid,
+ _In_ ULONG InstanceIndex,
+ _In_ ULONG EventDataSize,
+ _In_reads_bytes_(EventDataSize) PVOID EventData
+ )
+{
+
+ ULONG sizeNeeded;
+ PWNODE_SINGLE_INSTANCE event;
+ NTSTATUS status;
+
+ if (EventData == NULL)
+ {
+ EventDataSize = 0;
+ }
+
+ sizeNeeded = sizeof(WNODE_SINGLE_INSTANCE) + EventDataSize;
+
+ event = ExAllocatePoolWithTag(NonPagedPoolNx, sizeNeeded, CLASS_TAG_WMI);
+ if (event != NULL)
+ {
+ RtlZeroMemory(event, sizeNeeded);
+ event->WnodeHeader.Guid = *Guid;
+ event->WnodeHeader.ProviderId = IoWMIDeviceObjectToProviderId(DeviceObject);
+ event->WnodeHeader.BufferSize = sizeNeeded;
+ event->WnodeHeader.Flags = WNODE_FLAG_SINGLE_INSTANCE |
+ WNODE_FLAG_EVENT_ITEM |
+ WNODE_FLAG_STATIC_INSTANCE_NAMES;
+ KeQuerySystemTime(&event->WnodeHeader.TimeStamp);
+
+ event->InstanceIndex = InstanceIndex;
+ event->SizeDataBlock = EventDataSize;
+ event->DataBlockOffset = sizeof(WNODE_SINGLE_INSTANCE);
+ if (EventData != NULL)
+ {
+ RtlCopyMemory( &event->VariableData, EventData, EventDataSize);
+ }
+
+ status = IoWMIWriteEvent(event);
+ if (! NT_SUCCESS(status))
+ {
+ FREE_POOL(event);
+ }
+ } else {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ return(status);
+} // end ClassWmiFireEvent()
diff --git a/storage/class/classpnp/src/clntirp.c b/storage/class/classpnp/src/clntirp.c
new file mode 100644
index 00000000..d615dce1
--- /dev/null
+++ b/storage/class/classpnp/src/clntirp.c
@@ -0,0 +1,783 @@
+/*++
+
+Copyright (C) Microsoft Corporation, 1991 - 1999
+
+Module Name:
+
+ clntirp.c
+
+Abstract:
+
+ Client IRP queuing routines for CLASSPNP
+
+Environment:
+
+ kernel mode only
+
+Notes:
+
+
+Revision History:
+
+--*/
+
+#include "classp.h"
+#include "debug.h"
+
+
+#ifdef DEBUG_USE_WPP
+#include "clntirp.tmh"
+#endif
+
+VOID
+ClasspStartIdleTimer(
+ IN PCLASS_PRIVATE_FDO_DATA FdoData,
+ IN ULONGLONG IdleInterval
+ );
+
+VOID
+ClasspStopIdleTimer(
+ PCLASS_PRIVATE_FDO_DATA FdoData
+ );
+
+KDEFERRED_ROUTINE ClasspIdleTimerDpc;
+
+VOID
+ClasspServiceIdleRequest(
+ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ BOOLEAN PostToDpc
+ );
+
+PIRP
+ClasspDequeueIdleRequest(
+ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ );
+
+
+/*++
+
+EnqueueDeferredClientIrp
+
+Routine Description:
+
+ Insert the deferred irp into the list.
+
+ Note: we currently do not support Cancel for storage irps.
+
+Arguments:
+
+ Fdo - Pointer to the device object
+ Irp - Pointer to the I/O request packet
+
+Return Value:
+
+ None
+
+--*/
+VOID
+EnqueueDeferredClientIrp(
+ PDEVICE_OBJECT Fdo,
+ PIRP Irp
+ )
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
+ KIRQL oldIrql;
+
+
+ KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
+ InsertTailList(&fdoData->DeferredClientIrpList, &Irp->Tail.Overlay.ListEntry);
+
+
+ KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
+}
+
+/*++
+
+DequeueDeferredClientIrp
+
+Routine Description:
+
+ Remove the deferred irp from the list.
+
+Arguments:
+
+ Fdo - Pointer to the device object
+
+Return Value:
+
+ Pointer to removed IRP
+
+--*/
+PIRP
+DequeueDeferredClientIrp(
+ PDEVICE_OBJECT Fdo
+ )
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
+ PIRP irp;
+
+ //
+ // The DeferredClientIrpList is almost always empty.
+ // We don't want to grab the spinlock every time we check it (which is on every xfer completion)
+ // so check once first before we grab the spinlock.
+ //
+ if (IsListEmpty(&fdoData->DeferredClientIrpList)){
+ irp = NULL;
+ }
+ else {
+ PLIST_ENTRY listEntry;
+ KIRQL oldIrql;
+
+ KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
+ if (IsListEmpty(&fdoData->DeferredClientIrpList)){
+ listEntry = NULL;
+ }
+ else {
+ listEntry = RemoveHeadList(&fdoData->DeferredClientIrpList);
+ }
+ KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
+
+ if (listEntry == NULL) {
+ irp = NULL;
+ }
+ else {
+ irp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry);
+ NT_ASSERT(irp->Type == IO_TYPE_IRP);
+
+
+ InitializeListHead(&irp->Tail.Overlay.ListEntry);
+ }
+ }
+
+ return irp;
+}
+
+/*++
+
+ClasspInitializeIdleTimer
+
+Routine Description:
+
+ Initialize the idle timer for the given device.
+
+Arguments:
+
+ FdoExtension - Pointer to the device extension
+ IdleInterval - Timer interval
+
+Return Value:
+
+ None
+
+--*/
+VOID
+ClasspInitializeIdleTimer(
+ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ )
+{
+ PCLASS_PRIVATE_FDO_DATA fdoData = FdoExtension->PrivateFdoData;
+ ULONG idleInterval = CLASS_IDLE_INTERVAL;
+ ULONG idlePrioritySupported = TRUE;
+ ULONG activeIdleIoMax = 1;
+
+ ClassGetDeviceParameter(FdoExtension,
+ CLASSP_REG_SUBKEY_NAME,
+ CLASSP_REG_IDLE_PRIORITY_SUPPORTED,
+ &idlePrioritySupported);
+
+
+ if (idlePrioritySupported == FALSE) {
+ //
+ // User has set the registry to disable idle priority for this disk.
+ // No need to initialize any further.
+ // Always ensure that none of the other fields used for idle priority
+ // io are ever used without checking if it is supported.
+ //
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_TIMER, "ClasspInitializeIdleTimer: Idle priority not supported for disk %p\n", FdoExtension));
+ fdoData->IdlePrioritySupported = FALSE;
+ fdoData->IdleIoCount = 0;
+ fdoData->ActiveIoCount = 0;
+ return;
+ }
+
+ ClassGetDeviceParameter(FdoExtension,
+ CLASSP_REG_SUBKEY_NAME,
+ CLASSP_REG_IDLE_INTERVAL_NAME,
+ &idleInterval);
+
+ if ((idleInterval < CLASS_IDLE_TIMER_TICKS) || (idleInterval > USHORT_MAX)) {
+ //
+ // If the interval is too low or too high, reset it to the default value.
+ //
+ idleInterval = CLASS_IDLE_INTERVAL;
+ }
+
+ fdoData->IdlePrioritySupported = TRUE;
+ KeInitializeSpinLock(&fdoData->IdleListLock);
+ KeInitializeTimer(&fdoData->IdleTimer);
+ KeInitializeDpc(&fdoData->IdleDpc, ClasspIdleTimerDpc, FdoExtension);
+ InitializeListHead(&fdoData->IdleIrpList);
+ fdoData->IdleTimerStarted = FALSE;
+ fdoData->IdleTimerInterval = (USHORT) (idleInterval / CLASS_IDLE_TIMER_TICKS);
+ fdoData->StarvationCount = CLASS_STARVATION_INTERVAL / fdoData->IdleTimerInterval;
+
+ //
+ // Due to the coarseness of the idle timer frequency, some variability in
+ // the idle interval will be tolerated such that it is the desired idle
+ // interval on average.
+ fdoData->IdleInterval =
+ (USHORT)(idleInterval - (fdoData->IdleTimerInterval / 2));
+
+ fdoData->IdleTimerTicks = 0;
+ fdoData->IdleTicks = 0;
+ fdoData->IdleIoCount = 0;
+ fdoData->ActiveIoCount = 0;
+ fdoData->ActiveIdleIoCount = 0;
+
+ ClassGetDeviceParameter(FdoExtension,
+ CLASSP_REG_SUBKEY_NAME,
+ CLASSP_REG_IDLE_ACTIVE_MAX,
+ &activeIdleIoMax);
+
+ activeIdleIoMax = max(activeIdleIoMax, 1);
+ activeIdleIoMax = min(activeIdleIoMax, USHORT_MAX);
+
+ fdoData->IdleActiveIoMax = (USHORT)activeIdleIoMax;
+
+ return;
+}
+
+/*++
+
+ClasspStartIdleTimer
+
+Routine Description:
+
+ Start the idle timer if not already running. Reset the
+ timer counters before starting the timer. Use the IdleInterval
+ in the private fdo data to setup the timer.
+
+Arguments:
+
+ FdoData - Pointer to the private fdo data
+
+ IdleInterval - Amount of time since the completion of the last non-idle request
+
+Return Value:
+
+ None
+
+--*/
+VOID
+ClasspStartIdleTimer(
+ IN PCLASS_PRIVATE_FDO_DATA FdoData,
+ IN ULONGLONG IdleInterval
+ )
+{
+ LARGE_INTEGER dueTime;
+ LONG mstotimer;
+ LONG timerStarted;
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_TIMER, "ClasspStartIdleTimer: Start idle timer\n"));
+
+ timerStarted = InterlockedCompareExchange(&FdoData->IdleTimerStarted, 1, 0);
+
+ if (!timerStarted) {
+
+ //
+ // Reset the anti-starvation timer tick counter and set the idle tick
+ // counter according to the actual amount of idle time. The latter is
+ // important to do to ensure that if the idle queue drains and the timer
+ // has to be stopped and started on the arrival of the next idle request,
+ // those requests don't get delayed unnecessarily due to IdleTicks not
+ // reflecting actual idle time.
+ //
+ FdoData->IdleTimerTicks = 0;
+ FdoData->IdleTicks = (ULONG)(IdleInterval / FdoData->IdleTimerInterval);
+
+ //
+ // convert milliseconds to a relative 100ns
+ //
+ mstotimer = (-10) * 1000;
+
+ //
+ // multiply the period
+ //
+ dueTime.QuadPart = Int32x32To64(FdoData->IdleTimerInterval, mstotimer);
+
+ KeSetTimerEx(&FdoData->IdleTimer,
+ dueTime,
+ FdoData->IdleTimerInterval,
+ &FdoData->IdleDpc);
+ }
+ return;
+}
+
+/*++
+
+ClasspStopIdleTimer
+
+Routine Description:
+
+ Stop the idle timer if running. Also reset the timer counters.
+
+Arguments:
+
+ FdoData - Pointer to the private fdo data
+
+Return Value:
+
+ None
+
+--*/
+VOID
+ClasspStopIdleTimer(
+ PCLASS_PRIVATE_FDO_DATA FdoData
+ )
+{
+ LONG timerStarted;
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_TIMER, "ClasspStopIdleTimer: Stop idle timer\n"));
+
+ timerStarted = InterlockedCompareExchange(&FdoData->IdleTimerStarted, 0, 1);
+
+ if (timerStarted) {
+ (VOID)KeCancelTimer(&FdoData->IdleTimer);
+ }
+ return;
+}
+
+/*++
+
+ClasspGetIdleTime
+
+Routine Description:
+
+ This routine returns how long it has been since the last non-idle request
+ completed by checking the actual time.
+
+Arguments:
+
+ FdoData - Pointer to the private fdo data
+
+Return Value:
+
+ The idle interval in ms.
+
+--*/
+ULONGLONG
+ClasspGetIdleTime (
+ IN PCLASS_PRIVATE_FDO_DATA FdoData
+ )
+{
+ ULONGLONG idleTime;
+ LARGE_INTEGER currentTime;
+ NTSTATUS status;
+
+ //
+ // If there are any outstanding non-idle requests, then there has been no
+ // idle time.
+ //
+ if (FdoData->ActiveIoCount > 0) {
+ return 0;
+ }
+
+ //
+ // Get the time difference between current time and last I/O
+ // complete time.
+ //
+ currentTime = ClasspGetCurrentTime(NULL);
+
+ status = RtlULongLongSub((ULONGLONG)currentTime.QuadPart,
+ (ULONGLONG)FdoData->LastIoTime.QuadPart,
+ &idleTime);
+
+ if (NT_SUCCESS(status)) {
+ //
+ // Convert the time to milliseconds.
+ //
+ idleTime = ClasspTimeDiffToMs(FdoData, idleTime);
+ } else {
+ //
+ // Failed to get time difference, assume enough time passed.
+ //
+ idleTime = FdoData->IdleInterval;
+ }
+
+ return idleTime;
+}
+
+/*++
+
+ClasspIdleTicksSufficient
+
+Routine Description:
+
+ This routine whether enough idle ticks have occurred since the completion of
+ the last non-idle request.
+
+Arguments:
+
+ FdoData - Pointer to the private fdo data
+
+Return Value:
+
+ TRUE if sufficient idle ticks have expired to issue the next idle request.
+
+--*/
+LOGICAL
+ClasspIdleTicksSufficient (
+ IN PCLASS_PRIVATE_FDO_DATA FdoData
+ )
+{
+ ULONGLONG idleInterval;
+
+ //
+ // If it has been more than enough idle timer ticks since the completion of
+ // the last non-idle request, enough idle time has passed.
+ //
+
+ if (FdoData->IdleTicks > CLASS_IDLE_TIMER_TICKS) {
+ return TRUE;
+ }
+
+ //
+ // If there have not been enough timer ticks, then there has not been
+ // enough idle time.
+ //
+ if (FdoData->IdleTicks < CLASS_IDLE_TIMER_TICKS) {
+ return FALSE;
+ }
+
+ //
+ // IdleTicks can reach CLASS_IDLE_TIMER_TICKS before FdoData->IdleInterval
+ // worth of time elapses from the completion of the last non-idle request.
+ // This can happen because when the idle timer is running, the last non-idle
+ // request can complete at any time in the middle of the timer period (half
+ // on average) so on the next timer expiration, IdleTicks will transition
+ // 0->1 without its full time having passed since the completion of the last
+ // non-idle request. So when IdleTicks is exactly CLASS_IDLE_TIMER_TICKS,
+ // explicitly check whether an idle request should be issued now or on the
+ // next timer expiration.
+ //
+ idleInterval = ClasspGetIdleTime(FdoData);
+
+ if (idleInterval >= FdoData->IdleInterval) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/*++
+
+ClasspIdleTimerDpc
+
+Routine Description:
+
+ Timer dpc function. This function will be called once every
+ IdleInterval. This will increment the IdleTicks and
+ if it goes above 1 (i.e., disk is in idle state) then
+ it will service an idle request.
+
+ This function will increment IdleTimerTicks if the IdleTicks
+ does not go above 1 (i.e., disk is not in idle state). When it
+ reaches the starvation idle count (1 second) it will process
+ one idle request.
+
+Arguments:
+
+ Dpc - Pointer to DPC object
+ Context - Pointer to the fdo device extension
+ SystemArgument1 - Not used
+ SystemArgument2 - Not used
+
+Return Value:
+
+ None
+
+--*/
+VOID
+ClasspIdleTimerDpc(
+ IN PKDPC Dpc,
+ IN PVOID Context,
+ IN PVOID SystemArgument1,
+ IN PVOID SystemArgument2
+ )
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Context;
+ PCLASS_PRIVATE_FDO_DATA fdoData;
+
+ UNREFERENCED_PARAMETER(Dpc);
+ UNREFERENCED_PARAMETER(SystemArgument1);
+ UNREFERENCED_PARAMETER(SystemArgument2);
+
+ if (fdoExtension == NULL) {
+ NT_ASSERT(fdoExtension != NULL);
+ return;
+ }
+
+ fdoData = fdoExtension->PrivateFdoData;
+
+ if ((fdoData->ActiveIoCount <= 0) &&
+ (++fdoData->IdleTicks >= CLASS_IDLE_TIMER_TICKS)) {
+
+ //
+ // If there are max active idle request, do not issue another one here.
+ //
+ if (fdoData->ActiveIdleIoCount >= fdoData->IdleActiveIoMax) {
+ return;
+ }
+
+ //
+ // Check whether enough idle time has passed since the last non-idle
+ // request has completed.
+ //
+
+ if (ClasspIdleTicksSufficient(fdoData)) {
+ //
+ // We are going to issue an idle request so reset the anti-starvation
+ // timer counter.
+ //
+ fdoData->IdleTimerTicks = 0;
+ ClasspServiceIdleRequest(fdoExtension, FALSE);
+ }
+ return;
+ }
+
+ //
+ // If the timer is running then there must be at least one idle priority I/O pending
+ //
+ if (++fdoData->IdleTimerTicks >= fdoData->StarvationCount) {
+ fdoData->IdleTimerTicks = 0;
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_TIMER, "ClasspIdleTimerDpc: Starvation timer. Send one idle request\n"));
+ ClasspServiceIdleRequest(fdoExtension, FALSE);
+ }
+ return;
+}
+
+/*++
+
+ClasspEnqueueIdleRequest
+
+Routine Description:
+
+ This function will insert the idle request into the list.
+ If the inserted reqeust is the first request then it will
+ start the timer.
+
+Arguments:
+
+ DeviceObject - Pointer to device object
+ Irp - Pointer to the idle I/O request packet
+
+Return Value:
+
+ NT status code.
+
+--*/
+NTSTATUS
+ClasspEnqueueIdleRequest(
+ PDEVICE_OBJECT DeviceObject,
+ PIRP Irp
+ )
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
+ KIRQL oldIrql;
+ BOOLEAN issueRequest = TRUE;
+ ULONGLONG idleInterval;
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_TIMER, "ClasspEnqueueIdleRequest: Queue idle request %p\n", Irp));
+
+ IoMarkIrpPending(Irp);
+
+ //
+ // Get the time difference between current time and last non-idle request
+ // complete time. If the there has been enough idle time, then issue the
+ // request (unless other factors prevent us from doing so below) and set the
+ // idle time such that we starting the timer below, it would start off with
+ // enough idle ticks.
+ //
+ idleInterval = ClasspGetIdleTime(fdoData);
+
+ if (idleInterval >= fdoData->IdleInterval) {
+ idleInterval = fdoData->IdleTimerInterval * CLASS_IDLE_TIMER_TICKS;
+ } else {
+ issueRequest = FALSE;
+ }
+
+ //
+ // If there are already max active idle requests in the port driver, then
+ // queue this idle request.
+ //
+ if (fdoData->ActiveIdleIoCount >= fdoData->IdleActiveIoMax) {
+ issueRequest = FALSE;
+ }
+
+ TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_TIMER, "ClasspEnqueueIdleRequest: Diff time %I64d\n", idleInterval));
+
+ KeAcquireSpinLock(&fdoData->IdleListLock, &oldIrql);
+ if (IsListEmpty(&fdoData->IdleIrpList)) {
+ NT_ASSERT(fdoData->IdleIoCount == 0);
+ }
+ InsertTailList(&fdoData->IdleIrpList, &Irp->Tail.Overlay.ListEntry);
+
+
+ fdoData->IdleIoCount++;
+ if (!fdoData->IdleTimerStarted) {
+ ClasspStartIdleTimer(fdoData, idleInterval);
+ }
+
+ if (fdoData->IdleIoCount != 1) {
+ issueRequest = FALSE;
+ }
+
+
+ KeReleaseSpinLock(&fdoData->IdleListLock, oldIrql);
+
+ if (issueRequest) {
+ ClasspServiceIdleRequest(fdoExtension, FALSE);
+ }
+
+ return STATUS_PENDING;
+}
+
+/*++
+
+ClasspDequeueIdleRequest
+
+Routine Description:
+
+ This function will remove the next idle request from the list.
+ If there are no requests in the queue, then it will return NULL.
+
+Arguments:
+
+ FdoExtension - Pointer to the functional device extension
+
+Return Value:
+
+ Pointer to removed IRP
+
+--*/
+PIRP
+ClasspDequeueIdleRequest(
+ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ )
+{
+ PCLASS_PRIVATE_FDO_DATA fdoData = FdoExtension->PrivateFdoData;
+ PLIST_ENTRY listEntry = NULL;
+ PIRP irp = NULL;
+ KIRQL oldIrql;
+
+ KeAcquireSpinLock(&fdoData->IdleListLock, &oldIrql);
+
+ if (fdoData->IdleIoCount > 0) {
+ listEntry = RemoveHeadList(&fdoData->IdleIrpList);
+ //
+ // Make sure we actaully removed a request from the list
+ //
+ NT_ASSERT(listEntry != &fdoData->IdleIrpList);
+ //
+ // Decrement the idle I/O count.
+ //
+ fdoData->IdleIoCount--;
+ //
+ // Stop the timer on last request
+ //
+ if (fdoData->IdleIoCount == 0) {
+ ClasspStopIdleTimer(fdoData);
+ }
+ irp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry);
+ NT_ASSERT(irp->Type == IO_TYPE_IRP);
+
+
+ InitializeListHead(&irp->Tail.Overlay.ListEntry);
+ }
+
+ KeReleaseSpinLock(&fdoData->IdleListLock, oldIrql);
+ return irp;
+}
+
+/*++
+
+ClasspCompleteIdleRequest
+
+Routine Description:
+
+ This function will be called every time an idle request is completed.
+ This will call ClasspServiceIdleRequest to process any other pending idle requests.
+
+Arguments:
+
+ FdoExtension - Pointer to the device extension
+
+Return Value:
+
+ None
+
+--*/
+VOID
+ClasspCompleteIdleRequest(
+ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension
+ )
+{
+ PCLASS_PRIVATE_FDO_DATA fdoData = FdoExtension->PrivateFdoData;
+
+ //
+ // Issue the next idle request if there are any left in the queue, there are
+ // no non-idle requests outstanding, there are less than max idle requests
+ // outstanding, and it has been long enough since the completion of the last
+ // non-idle request.
+ //
+ if ((fdoData->IdleIoCount > 0) &&
+ (fdoData->ActiveIdleIoCount < fdoData->IdleActiveIoMax) &&
+ (fdoData->ActiveIoCount <= 0) &&
+ (ClasspIdleTicksSufficient(fdoData))) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_TIMER, "ClasspCompleteIdleRequest: Service next idle reqeusts\n"));
+ ClasspServiceIdleRequest(FdoExtension, TRUE);
+ }
+
+ return;
+}
+
+/*++
+
+ClasspServiceIdleRequest
+
+Routine Description:
+
+ Remove the next pending idle request from the queue and process it.
+ If a request was removed then it will be processed otherwise it will
+ just return.
+
+Arguments:
+
+ FdoExtension - Pointer to the device extension
+ PostToDpc - Flag to pass to ServiceTransferRequest to indicate if request must be posted to a DPC
+
+Return Value:
+
+ None
+
+--*/
+VOID
+ClasspServiceIdleRequest(
+ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ BOOLEAN PostToDpc
+ )
+{
+ PIRP irp;
+
+ irp = ClasspDequeueIdleRequest(FdoExtension);
+ if (irp != NULL) {
+ ServiceTransferRequest(FdoExtension->DeviceObject, irp, PostToDpc);
+ }
+ return;
+}
+
+
+
diff --git a/storage/class/classpnp/src/create.c b/storage/class/classpnp/src/create.c
new file mode 100644
index 00000000..f871ab26
--- /dev/null
+++ b/storage/class/classpnp/src/create.c
@@ -0,0 +1,1018 @@
+/*++
+
+Copyright (C) Microsoft Corporation, 1991 - 2010
+
+Module Name:
+
+ class.c
+
+Abstract:
+
+ SCSI class driver routines
+
+Environment:
+
+ kernel mode only
+
+Notes:
+
+
+Revision History:
+
+--*/
+
+#define CLASS_INIT_GUID 0
+#include "classp.h"
+#include "debug.h"
+
+#ifdef DEBUG_USE_WPP
+#include "create.tmh"
+#endif
+
+ULONG BreakOnClose = 0;
+
+const PCSZ LockTypeStrings[] = {
+ "Simple",
+ "Secure",
+ "Internal"
+};
+
+
+VOID
+ClasspCleanupDisableMcn(
+ IN PFILE_OBJECT_EXTENSION FsContext
+ );
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, ClassCreateClose)
+#pragma alloc_text(PAGE, ClasspCreateClose)
+#pragma alloc_text(PAGE, ClasspCleanupProtectedLocks)
+#pragma alloc_text(PAGE, ClasspEjectionControl)
+#pragma alloc_text(PAGE, ClasspCleanupDisableMcn)
+#pragma alloc_text(PAGE, ClassGetFsContext)
+#endif
+
+NTSTATUS
+ClassCreateClose(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ SCSI class driver create and close routine. This is called by the I/O system
+ when the device is opened or closed.
+
+Arguments:
+
+ DriverObject - Pointer to driver object created by system.
+
+ Irp - IRP involved.
+
+Return Value:
+
+ Device-specific drivers return value or STATUS_SUCCESS.
+
+--*/
+
+{
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+ ULONG removeState;
+ NTSTATUS status;
+
+ PAGED_CODE();
+
+ //
+ // If we're getting a close request then we know the device object hasn't
+ // been completely destroyed. Let the driver cleanup if necessary.
+ //
+
+ removeState = ClassAcquireRemoveLock(DeviceObject, Irp);
+
+ //
+ // Invoke the device-specific routine, if one exists. Otherwise complete
+ // with SUCCESS
+ //
+
+ if((removeState == NO_REMOVE) ||
+ IS_CLEANUP_REQUEST(IoGetCurrentIrpStackLocation(Irp)->MajorFunction)) {
+
+ status = ClasspCreateClose(DeviceObject, Irp);
+
+ if((NT_SUCCESS(status)) &&
+ (commonExtension->DevInfo->ClassCreateClose)) {
+
+ return commonExtension->DevInfo->ClassCreateClose(DeviceObject, Irp);
+ }
+
+ } else {
+ status = STATUS_DEVICE_DOES_NOT_EXIST;
+ }
+
+ Irp->IoStatus.Status = status;
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ return status;
+}
+
+
+NTSTATUS
+ClasspCreateClose(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp
+ )
+/*++
+
+Routine Description:
+
+ This routine will handle create/close operations for a given classpnp
+ device if the class driver doesn't supply it's own handler. If there
+ is a file object supplied for our driver (if it's a FO_DIRECT_DEVICE_OPEN
+ file object) then it will initialize a file extension on create or destroy
+ the extension on a close.
+
+Arguments:
+
+ DeviceObject - the device object being opened or closed.
+
+ Irp - the create/close irp
+
+Return Value:
+
+ status
+
+--*/
+{
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+
+ PFILE_OBJECT fileObject = irpStack->FileObject;
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ PAGED_CODE();
+
+
+ //
+ // ISSUE-2000/3/28-henrygab - if lower stack fails create/close, we end up
+ // in an inconsistent state. re-write to verify all args and allocate all
+ // required resources, then pass the irp down, then complete the
+ // transaction. this is because we also cannot forward the irp, then fail
+ // it after it has succeeded a lower-level driver.
+ //
+
+ if(irpStack->MajorFunction == IRP_MJ_CREATE) {
+
+ PIO_SECURITY_CONTEXT securityContext =
+ irpStack->Parameters.Create.SecurityContext;
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,
+ "ClasspCREATEClose: create received for device %p\n",
+ DeviceObject));
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,
+ "ClasspCREATEClose: desired access %lx\n",
+ securityContext->DesiredAccess));
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,
+ "ClasspCREATEClose: file object %p\n",
+ irpStack->FileObject));
+
+ NT_ASSERT(BreakOnClose == FALSE);
+
+ if(irpStack->FileObject != NULL) {
+
+ PFILE_OBJECT_EXTENSION fsContext;
+
+ //
+ // Allocate our own file object extension for this device object.
+ //
+
+ status = AllocateDictionaryEntry(
+ &commonExtension->FileObjectDictionary,
+ (ULONGLONG) irpStack->FileObject,
+ sizeof(FILE_OBJECT_EXTENSION),
+ CLASS_TAG_FILE_OBJECT_EXTENSION,
+ &fsContext);
+
+ if(NT_SUCCESS(status)) {
+
+ RtlZeroMemory(fsContext,
+ sizeof(FILE_OBJECT_EXTENSION));
+
+ fsContext->FileObject = irpStack->FileObject;
+ fsContext->DeviceObject = DeviceObject;
+ } else if (status == STATUS_OBJECT_NAME_COLLISION) {
+ status = STATUS_SUCCESS;
+ }
+ }
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,
+ "ClasspCreateCLOSE: close received for device %p\n",
+ DeviceObject));
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,
+ "ClasspCreateCLOSE: file object %p\n",
+ fileObject));
+
+ if(irpStack->FileObject != NULL) {
+
+ PFILE_OBJECT_EXTENSION fsContext =
+ ClassGetFsContext(commonExtension, irpStack->FileObject);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,
+ "ClasspCreateCLOSE: file extension %p\n",
+ fsContext));
+
+ if(fsContext != NULL) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,
+ "ClasspCreateCLOSE: extension is ours - "
+ "freeing\n"));
+ NT_ASSERT(BreakOnClose == FALSE);
+
+ ClasspCleanupProtectedLocks(fsContext);
+
+ ClasspCleanupDisableMcn(fsContext);
+
+ FreeDictionaryEntry(&(commonExtension->FileObjectDictionary),
+ fsContext);
+ }
+ }
+ }
+
+ //
+ // Notify the lower levels about the create or close operation - give them
+ // a chance to cleanup too.
+ //
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,
+ "ClasspCreateClose: %s for devobj %p\n",
+ (NT_SUCCESS(status) ? "Success" : "FAILED"),
+ DeviceObject));
+
+
+ if(NT_SUCCESS(status)) {
+
+ KEVENT event;
+
+ //
+ // Set up the event to wait on
+ //
+
+ KeInitializeEvent(&event, SynchronizationEvent, FALSE);
+
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+ IoSetCompletionRoutine( Irp, ClassSignalCompletion, &event,
+ TRUE, TRUE, TRUE);
+
+ status = IoCallDriver(commonExtension->LowerDeviceObject, Irp);
+
+ if(status == STATUS_PENDING) {
+ (VOID)KeWaitForSingleObject(&event,
+ Executive,
+ KernelMode,
+ FALSE,
+ NULL);
+ status = Irp->IoStatus.Status;
+ }
+
+ if (!NT_SUCCESS(status)) {
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT,
+ "ClasspCreateClose: Lower driver failed, but we "
+ "succeeded. This is a problem, lock counts will be "
+ "out of sync between levels.\n"));
+ }
+
+ }
+
+
+ return status;
+}
+
+
+VOID
+ClasspCleanupProtectedLocks(
+ IN PFILE_OBJECT_EXTENSION FsContext
+ )
+{
+ PCOMMON_DEVICE_EXTENSION commonExtension =
+ FsContext->DeviceObject->DeviceExtension;
+
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension =
+ commonExtension->PartitionZeroExtension;
+
+ ULONG newDeviceLockCount = 1;
+
+ PAGED_CODE();
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,
+ "ClasspCleanupProtectedLocks called for %p\n",
+ FsContext->DeviceObject));
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,
+ "ClasspCleanupProtectedLocks - FsContext %p is locked "
+ "%d times\n", FsContext, FsContext->LockCount));
+
+ NT_ASSERT(BreakOnClose == FALSE);
+
+ //
+ // Synchronize with ejection and ejection control requests.
+ //
+
+ KeEnterCriticalRegion();
+ (VOID)KeWaitForSingleObject(&(fdoExtension->EjectSynchronizationEvent),
+ UserRequest,
+ KernelMode,
+ FALSE,
+ NULL);
+
+ //
+ // For each secure lock on this handle decrement the secured lock count
+ // for the FDO. Keep track of the new value.
+ //
+
+ if (FsContext->LockCount != 0) {
+
+ do {
+
+ InterlockedDecrement((volatile LONG *)&FsContext->LockCount);
+
+ newDeviceLockCount =
+ InterlockedDecrement(&fdoExtension->ProtectedLockCount);
+
+ } while (FsContext->LockCount > 0);
+
+ //
+ // If the new lock count has been dropped to zero then issue a lock
+ // command to the device.
+ //
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,
+ "ClasspCleanupProtectedLocks: FDO secured lock count = %d "
+ "lock count = %d\n",
+ fdoExtension->ProtectedLockCount,
+ fdoExtension->LockCount));
+
+ if ((newDeviceLockCount == 0) && (fdoExtension->LockCount == 0)) {
+
+ SCSI_REQUEST_BLOCK srb = {0};
+ UCHAR srbExBuffer[CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE] = {0};
+ PSTORAGE_REQUEST_BLOCK srbEx = (PSTORAGE_REQUEST_BLOCK)srbExBuffer;
+ PCDB cdb = NULL;
+ NTSTATUS status;
+ PSCSI_REQUEST_BLOCK srbPtr;
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,
+ "ClasspCleanupProtectedLocks: FDO lock count dropped "
+ "to zero\n"));
+
+ if (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+ #pragma prefast(suppress:26015, "InitializeStorageRequestBlock ensures buffer access is bounded")
+ status = InitializeStorageRequestBlock(srbEx,
+ STORAGE_ADDRESS_TYPE_BTL8,
+ sizeof(srbExBuffer),
+ 1,
+ SrbExDataTypeScsiCdb16);
+ if (NT_SUCCESS(status)) {
+ srbEx->TimeOutValue = fdoExtension->TimeOutValue;
+ SrbSetCdbLength(srbEx, 6);
+ cdb = SrbGetCdb(srbEx);
+ srbPtr = (PSCSI_REQUEST_BLOCK)srbEx;
+ } else {
+ //
+ // Should not happen. Revert to legacy SRB.
+ //
+ NT_ASSERT(FALSE);
+ srb.TimeOutValue = fdoExtension->TimeOutValue;
+ srb.CdbLength = 6;
+ cdb = (PCDB) &(srb.Cdb);
+ srbPtr = &srb;
+ }
+
+ } else {
+
+ srb.TimeOutValue = fdoExtension->TimeOutValue;
+ srb.CdbLength = 6;
+ cdb = (PCDB) &(srb.Cdb);
+ srbPtr = &srb;
+
+ }
+
+ cdb->MEDIA_REMOVAL.OperationCode = SCSIOP_MEDIUM_REMOVAL;
+
+ //
+ // TRUE - prevent media removal.
+ // FALSE - allow media removal.
+ //
+
+ cdb->MEDIA_REMOVAL.Prevent = FALSE;
+
+ status = ClassSendSrbSynchronous(fdoExtension->DeviceObject,
+ srbPtr,
+ NULL,
+ 0,
+ FALSE);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,
+ "ClasspCleanupProtectedLocks: unlock request to drive "
+ "returned status %lx\n", status));
+ }
+ }
+
+ KeSetEvent(&fdoExtension->EjectSynchronizationEvent,
+ IO_NO_INCREMENT,
+ FALSE);
+ KeLeaveCriticalRegion();
+ return;
+}
+
+
+VOID
+ClasspCleanupDisableMcn(
+ IN PFILE_OBJECT_EXTENSION FsContext
+ )
+{
+ PCOMMON_DEVICE_EXTENSION commonExtension =
+ FsContext->DeviceObject->DeviceExtension;
+
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension =
+ commonExtension->PartitionZeroExtension;
+
+ PAGED_CODE();
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "ClasspCleanupDisableMcn called for %p\n",
+ FsContext->DeviceObject));
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
+ "ClasspCleanupDisableMcn - FsContext %p is disabled "
+ "%d times\n", FsContext, FsContext->McnDisableCount));
+
+ //
+ // For each secure lock on this handle decrement the secured lock count
+ // for the FDO. Keep track of the new value.
+ //
+
+ while(FsContext->McnDisableCount != 0) {
+ FsContext->McnDisableCount--;
+ ClassEnableMediaChangeDetection(fdoExtension);
+ }
+
+ return;
+}
+
+
+#if 1
+/*
+ * ISSUE: REMOVE this old function implementation as soon as the
+ * boottime pagefile problems with the new one (below)
+ * are resolved.
+ */
+NTSTATUS
+ClasspEjectionControl(
+ IN PDEVICE_OBJECT Fdo,
+ IN PIRP Irp,
+ IN MEDIA_LOCK_TYPE LockType,
+ IN BOOLEAN Lock
+ )
+{
+ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension = Fdo->DeviceExtension;
+ PCOMMON_DEVICE_EXTENSION commonExtension =
+ (PCOMMON_DEVICE_EXTENSION) FdoExtension;
+
+ PFILE_OBJECT_EXTENSION fsContext = NULL;
+ NTSTATUS status;
+ PSCSI_REQUEST_BLOCK srb = NULL;
+ BOOLEAN countChanged = FALSE;
+
+ PAGED_CODE();
+
+ /*
+ * Ensure that the user thread is not suspended while we are holding EjectSynchronizationEvent.
+ */
+ KeEnterCriticalRegion();
+
+ status = KeWaitForSingleObject(
+ &(FdoExtension->EjectSynchronizationEvent),
+ UserRequest,
+ KernelMode,
+ FALSE,
+ NULL);
+
+ NT_ASSERT(status == STATUS_SUCCESS);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL,
+ "ClasspEjectionControl: "
+ "Received request for %s lock type\n",
+ LockTypeStrings[LockType]
+ ));
+
+ try {
+ PCDB cdb = NULL;
+
+ //
+ // Determine if this is a "secured" request.
+ //
+
+ if (LockType == SecureMediaLock) {
+
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ PFILE_OBJECT fileObject = irpStack->FileObject;
+
+ //
+ // Make sure that the file object we are supplied has a
+ // proper FsContext before we try doing a secured lock.
+ //
+
+ if (fileObject != NULL) {
+ fsContext = ClassGetFsContext(commonExtension, fileObject);
+ }
+
+ if (fsContext == NULL) {
+
+ //
+ // This handle isn't setup correctly. We can't let the
+ // operation go.
+ //
+
+ status = STATUS_INVALID_PARAMETER;
+ leave;
+ }
+ }
+
+ if (Lock) {
+
+ //
+ // This is a lock command. Reissue the command in case bus or
+ // device was reset and the lock was cleared.
+ // note: may need to decrement count if actual lock operation
+ // failed....
+ //
+
+ switch (LockType) {
+
+ case SimpleMediaLock: {
+ FdoExtension->LockCount++;
+ countChanged = TRUE;
+ break;
+ }
+
+ case SecureMediaLock: {
+ fsContext->LockCount++;
+ FdoExtension->ProtectedLockCount++;
+ countChanged = TRUE;
+ break;
+ }
+
+ case InternalMediaLock: {
+ FdoExtension->InternalLockCount++;
+ countChanged = TRUE;
+ break;
+ }
+ }
+
+ } else {
+
+ //
+ // This is an unlock command. If it's a secured one then make sure
+ // the caller has a lock outstanding or return an error.
+ // note: may need to re-increment the count if actual unlock
+ // operation fails....
+ //
+
+ switch (LockType) {
+
+ case SimpleMediaLock: {
+ if(FdoExtension->LockCount != 0) {
+ FdoExtension->LockCount--;
+ countChanged = TRUE;
+ }
+ break;
+ }
+
+ case SecureMediaLock: {
+ if(fsContext->LockCount == 0) {
+ status = STATUS_INVALID_DEVICE_STATE;
+ leave;
+ }
+ fsContext->LockCount--;
+ FdoExtension->ProtectedLockCount--;
+ countChanged = TRUE;
+ break;
+ }
+
+ case InternalMediaLock: {
+ NT_ASSERT(FdoExtension->InternalLockCount != 0);
+ FdoExtension->InternalLockCount--;
+ countChanged = TRUE;
+ break;
+ }
+ }
+
+ //
+ // We only send an unlock command to the drive if both the
+ // secured and unsecured lock counts have dropped to zero.
+ //
+
+ if ((FdoExtension->ProtectedLockCount != 0) ||
+ (FdoExtension->InternalLockCount != 0) ||
+ (FdoExtension->LockCount != 0)) {
+
+ status = STATUS_SUCCESS;
+ leave;
+ }
+ }
+
+ status = STATUS_SUCCESS;
+ if (TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA)) {
+
+ srb = (PSCSI_REQUEST_BLOCK)ClasspAllocateSrb(FdoExtension);
+
+ if (srb == NULL) {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ leave;
+ }
+
+ if (FdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+
+ //
+ // NOTE - this is based on size used in ClasspAllocateSrb
+ //
+
+ status = InitializeStorageRequestBlock((PSTORAGE_REQUEST_BLOCK)srb,
+ STORAGE_ADDRESS_TYPE_BTL8,
+ CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE,
+ 1,
+ SrbExDataTypeScsiCdb16);
+ if (!NT_SUCCESS(status)) {
+ NT_ASSERT(FALSE);
+ leave;
+ }
+
+ } else {
+ RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK));
+ }
+
+ SrbSetCdbLength(srb, 6);
+ cdb = SrbGetCdb(srb);
+ NT_ASSERT(cdb != NULL);
+
+ cdb->MEDIA_REMOVAL.OperationCode = SCSIOP_MEDIUM_REMOVAL;
+
+ //
+ // TRUE - prevent media removal.
+ // FALSE - allow media removal.
+ //
+
+ cdb->MEDIA_REMOVAL.Prevent = Lock;
+
+ //
+ // Set timeout value.
+ //
+
+ SrbSetTimeOutValue(srb, FdoExtension->TimeOutValue);
+
+ //
+ // The actual lock operation on the device isn't so important
+ // as the internal lock counts. Ignore failures.
+ //
+
+ status = ClassSendSrbSynchronous(FdoExtension->DeviceObject,
+ srb,
+ NULL,
+ 0,
+ FALSE);
+ }
+
+ } finally {
+
+ if (!NT_SUCCESS(status)) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL,
+ "ClasspEjectionControl: FAILED status %x -- "
+ "reverting lock counts\n", status));
+
+ if (countChanged) {
+
+ //
+ // have to revert to previous counts if the
+ // lock/unlock operation actually failed.
+ //
+
+ if (Lock) {
+
+ switch (LockType) {
+
+ case SimpleMediaLock: {
+ FdoExtension->LockCount--;
+ break;
+ }
+
+ case SecureMediaLock: {
+ fsContext->LockCount--;
+ FdoExtension->ProtectedLockCount--;
+ break;
+ }
+
+ case InternalMediaLock: {
+ FdoExtension->InternalLockCount--;
+ break;
+ }
+ }
+
+ } else {
+
+ switch (LockType) {
+
+ case SimpleMediaLock: {
+ FdoExtension->LockCount++;
+ break;
+ }
+
+ case SecureMediaLock: {
+ fsContext->LockCount++;
+ FdoExtension->ProtectedLockCount++;
+ break;
+ }
+
+ case InternalMediaLock: {
+ FdoExtension->InternalLockCount++;
+ break;
+ }
+ }
+ }
+
+ }
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL,
+ "ClasspEjectionControl: Succeeded\n"));
+
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL,
+ "ClasspEjectionControl: "
+ "Current Counts: Internal: %x Secure: %x Simple: %x\n",
+ FdoExtension->InternalLockCount,
+ FdoExtension->ProtectedLockCount,
+ FdoExtension->LockCount
+ ));
+
+ KeSetEvent(&(FdoExtension->EjectSynchronizationEvent),
+ IO_NO_INCREMENT,
+ FALSE);
+ KeLeaveCriticalRegion();
+
+ if (srb) {
+ ClassFreeOrReuseSrb(FdoExtension, srb);
+ }
+
+ }
+ return status;
+}
+
+#else
+
+/*
+ * ISSUE: RESTORE this (see above)
+ * This is a new implementation of the function that doesn't thrash memory
+ * or depend on the srbLookasideList.
+ * HOWEVER, it seems to cause pagefile initialization to fail during boot
+ * for some reason. Need to resolve this before switching to this function.
+ */
+NTSTATUS
+ClasspEjectionControl(
+ IN PDEVICE_OBJECT Fdo,
+ IN PIRP Irp,
+ IN MEDIA_LOCK_TYPE LockType,
+ IN BOOLEAN Lock
+ )
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Fdo->DeviceExtension;
+ PFILE_OBJECT_EXTENSION fsContext;
+ BOOLEAN fileHandleOk = TRUE;
+ BOOLEAN countChanged = FALSE;
+ NTSTATUS status;
+
+ PAGED_CODE();
+
+ status = KeWaitForSingleObject(
+ &fdoExt->EjectSynchronizationEvent,
+ UserRequest,
+ KernelMode,
+ FALSE,
+ NULL);
+ NT_ASSERT(status == STATUS_SUCCESS);
+
+ /*
+ * If this is a "secured" request, we have to make sure
+ * that the file handle is valid.
+ */
+ if (LockType == SecureMediaLock){
+ PIO_STACK_LOCATION thisSp = IoGetCurrentIrpStackLocation(Irp);
+
+ /*
+ * Make sure that the file object we are supplied has a
+ * proper FsContext before we try doing a secured lock.
+ */
+ if (thisSp->FileObject){
+ PCOMMON_DEVICE_EXTENSION commonExt = (PCOMMON_DEVICE_EXTENSION)fdoExt;
+ fsContext = ClassGetFsContext(commonExt, thisSp->FileObject);
+ }
+ else {
+ fsContext = NULL;
+ }
+
+ if (!fsContext){
+ NT_ASSERT(fsContext);
+ fileHandleOk = FALSE;
+ }
+ }
+
+ if (fileHandleOk){
+
+ /*
+ * Adjust the lock counts and make sure they make sense.
+ */
+ status = STATUS_SUCCESS;
+ if (Lock){
+ switch(LockType) {
+ case SimpleMediaLock:
+ fdoExt->LockCount++;
+ countChanged = TRUE;
+ break;
+ case SecureMediaLock:
+ fsContext->LockCount++;
+ fdoExt->ProtectedLockCount++;
+ countChanged = TRUE;
+ break;
+ case InternalMediaLock:
+ fdoExt->InternalLockCount++;
+ countChanged = TRUE;
+ break;
+ }
+ }
+ else {
+ /*
+ * This is an unlock command. If it's a secured one then make sure
+ * the caller has a lock outstanding or return an error.
+ */
+ switch (LockType){
+ case SimpleMediaLock:
+ if (fdoExt->LockCount > 0){
+ fdoExt->LockCount--;
+ countChanged = TRUE;
+ }
+ else {
+ NT_ASSERT(fdoExt->LockCount > 0);
+ status = STATUS_INTERNAL_ERROR;
+ }
+ break;
+ case SecureMediaLock:
+ if (fsContext->LockCount > 0){
+ NT_ASSERT(fdoExt->ProtectedLockCount > 0);
+ fsContext->LockCount--;
+ fdoExt->ProtectedLockCount--;
+ countChanged = TRUE;
+ }
+ else {
+ NT_ASSERT(fsContext->LockCount > 0);
+ status = STATUS_INVALID_DEVICE_STATE;
+ }
+ break;
+ case InternalMediaLock:
+ NT_ASSERT(fdoExt->InternalLockCount > 0);
+ fdoExt->InternalLockCount--;
+ countChanged = TRUE;
+ break;
+ }
+ }
+
+ if (NT_SUCCESS(status)){
+ /*
+ * We only send an unlock command to the drive if
+ * all the lock counts have dropped to zero.
+ */
+ if (!Lock &&
+ (fdoExt->ProtectedLockCount ||
+ fdoExt->InternalLockCount ||
+ fdoExt->LockCount)){
+
+ /*
+ * The lock count is still positive, so don't unlock yet.
+ */
+ status = STATUS_SUCCESS;
+ }
+ else if (!TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA)) {
+ /*
+ * The device isn't removable media. don't send a cmd.
+ */
+ status = STATUS_SUCCESS;
+ }
+ else {
+ TRANSFER_PACKET *pkt;
+
+ pkt = DequeueFreeTransferPacket(Fdo, TRUE);
+ if (pkt){
+ KEVENT event;
+
+ /*
+ * Store the number of packets servicing the irp (one)
+ * inside the original IRP. It will be used to counted down
+ * to zero when the packet completes.
+ * Initialize the original IRP's status to success.
+ * If the packet fails, we will set it to the error status.
+ */
+ Irp->Tail.Overlay.DriverContext[0] = LongToPtr(1);
+ Irp->IoStatus.Status = STATUS_SUCCESS;
+
+ /*
+ * Set this up as a SYNCHRONOUS transfer, submit it,
+ * and wait for the packet to complete. The result
+ * status will be written to the original irp.
+ */
+ KeInitializeEvent(&event, SynchronizationEvent, FALSE);
+ SetupEjectionTransferPacket(pkt, Lock, &event, Irp);
+ SubmitTransferPacket(pkt);
+ (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
+ status = Irp->IoStatus.Status;
+ }
+ else {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ }
+ }
+ }
+ else {
+ status = STATUS_INVALID_PARAMETER;
+ }
+
+ if (!NT_SUCCESS(status) && countChanged) {
+
+ //
+ // have to revert to previous counts if the
+ // lock/unlock operation actually failed.
+ //
+
+ if(Lock) {
+
+ switch(LockType) {
+
+ case SimpleMediaLock: {
+ FdoExtension->LockCount--;
+ break;
+ }
+
+ case SecureMediaLock: {
+ fsContext->LockCount--;
+ FdoExtension->ProtectedLockCount--;
+ break;
+ }
+
+ case InternalMediaLock: {
+ FdoExtension->InternalLockCount--;
+ break;
+ }
+ }
+
+ } else {
+
+ switch(LockType) {
+
+ case SimpleMediaLock: {
+ FdoExtension->LockCount++;
+ break;
+ }
+
+ case SecureMediaLock: {
+ fsContext->LockCount++;
+ FdoExtension->ProtectedLockCount++;
+ break;
+ }
+
+ case InternalMediaLock: {
+ FdoExtension->InternalLockCount++;
+ break;
+ }
+ }
+ }
+ }
+
+
+
+ KeSetEvent(&fdoExt->EjectSynchronizationEvent, IO_NO_INCREMENT, FALSE);
+
+ return status;
+}
+#endif
+
+_IRQL_requires_max_(PASSIVE_LEVEL)
+PFILE_OBJECT_EXTENSION
+ClassGetFsContext(
+ _In_ PCOMMON_DEVICE_EXTENSION CommonExtension,
+ _In_ PFILE_OBJECT FileObject
+ )
+{
+ PAGED_CODE();
+ return GetDictionaryEntry(&(CommonExtension->FileObjectDictionary),
+ (ULONGLONG) FileObject);
+}
diff --git a/storage/class/classpnp/src/data.c b/storage/class/classpnp/src/data.c
new file mode 100644
index 00000000..0faa333f
--- /dev/null
+++ b/storage/class/classpnp/src/data.c
@@ -0,0 +1,222 @@
+/*++
+
+Copyright (C) Microsoft Corporation, 1991 - 2010
+
+Module Name:
+
+ disk.c
+
+Abstract:
+
+ SCSI disk class driver
+
+Environment:
+
+ kernel mode only
+
+Notes:
+
+Revision History:
+
+--*/
+
+#include "classp.h"
+
+
+/*
+ * Entry in static list used by debug extension to quickly find all class FDOs.
+ */
+LIST_ENTRY AllFdosList = {&AllFdosList, &AllFdosList};
+
+#ifdef ALLOC_DATA_PRAGMA
+ #pragma data_seg("PAGEDATA")
+#endif
+
+/*
+#define FDO_HACK_CANNOT_LOCK_MEDIA (0x00000001)
+#define FDO_HACK_GESN_IS_BAD (0x00000002)
+#define FDO_HACK_NO_SYNC_CACHE (0x00000004)
+#define FDO_HACK_NO_RESERVE6 (0x00000008)
+#define FDO_HACK_GESN_IGNORE_OPCHANGE (0x00000010)
+*/
+
+CLASSPNP_SCAN_FOR_SPECIAL_INFO ClassBadItems[] = { // Type (HH, slim) + WHQL Date, if known
+ { "" , "MITSUMI CD-ROM FX240" , NULL , 0x02 },
+ { "" , "MITSUMI CD-ROM FX320" , NULL , 0x02 },
+ { "" , "MITSUMI CD-ROM FX322" , NULL , 0x02 },
+ { "" , "TEAC DV-28E-A" , "2.0A", 0x02 },
+ { "" , "HP CD-Writer cd16h" , "Q000", 0x02 },
+ { "" , "_NEC NR-7800A" , "1.33", 0x02 },
+ { "" , "COMPAQ CRD-8481B" , NULL , 0x04 },
+ // The following is a list of device that report too many OpChange/Add events.
+ // They require ignoring (or not sending) the OpChange flag in the GESN command.
+ // This list contains vendor ID and product ID as separate strings for USB/1394 interface.
+ { "HL-DT-ST", "DVDRAM GMA-4020B" , NULL , 0x10 }, // hh , 2002/04/22
+ { "HL-DT-ST", "DVD-RW GCA-4020B" , NULL , 0x10 }, // hh , 2002/05/14
+ { "HL-DT-ST", "DVDRAM GSA-4040B" , NULL , 0x10 }, // hh , 2003/05/06
+ { "HL-DT-ST", "DVDRAM GMA-4040B" , NULL , 0x10 }, // hh , 2003/07/27
+ { "HL-DT-ST", "DVD-RW GWA-4040B" , NULL , 0x10 }, // hh , 2003/11/18
+ { "HL-DT-ST", "DVDRAM GSA-4081B" , NULL , 0x10 }, // hh , 2003/11/06
+ { "HL-DT-ST", "DVDRAM GSA-4082B" , NULL , 0x10 }, // hh , 2004/01/27
+ { "HL-DT-ST", "DVD-RW GWA-4082B" , NULL , 0x10 }, // hh , 2004/03/11
+ { "HL-DT-ST", "DVDRAM GSA-4120B" , NULL , 0x10 }, // hh , 2004/05/16
+ { "HL-DT-ST", "DVD+RW GRA-4120B" , NULL , 0x10 }, // hh , 2004/04/28
+ { "HL-DT-ST", "DVDRAM GSA-4160B" , NULL , 0x10 }, // hh , 2004/08/12
+ { "HL-DT-ST", "DVD-RW GWA-4160B" , NULL , 0x10 }, // hh , 2004/08/24
+ { "HL-DT-ST", "DVDRAM GSA-4163B" , NULL , 0x10 }, // hh , 2004/11/09
+ { "HL-DT-ST", "DVD-RW GWA-4163B" , NULL , 0x10 }, // hh , 2004/12/29
+ { "HL-DT-ST", "DVDRAM GSA-4165B" , NULL , 0x10 }, // hh , 2005/06/09
+ { "HL-DT-ST", "DVDRAM_GSA-4165B" , NULL , 0x10 }, // hh , 2005/06/28
+ { "HL-DT-ST", "DVD-RW GWA-4165B" , NULL , 0x10 }, // hh , 2005/08/23
+ { "HL-DT-ST", "DVDRAM GSA-4167B" , NULL , 0x10 }, // hh , 2005/07/01
+ { "HL-DT-ST", "DVDRAM GSA-H10N" , NULL , 0x10 }, // hh , 2006/02/16
+ { "HL-DT-ST", "DVDRAM_GSA-H10N" , NULL , 0x10 }, // hh , 2006/02/16
+ { "HL-DT-ST", "DVDRAM GSA-H10L" , NULL , 0x10 }, // hh , 2006/02/27
+ { "HL-DT-ST", "DVDRAM_GSA-H10L" , NULL , 0x10 }, // hh , 2006/04/21
+ { "HL-DT-ST", "DVDRAM GSA-H10A" , NULL , 0x10 }, // hh , 2006/01/03
+ { "HL-DT-ST", "DVDRAM_GSA-H10A" , NULL , 0x10 }, // hh , 2006/05/14
+ { "HL-DT-ST", "DVD-RW GSA-H11N" , NULL , 0x10 }, // hh , 2006/04/28
+ { "HL-DT-ST", "DVD-RW_GSA-H11N" , NULL , 0x10 }, // hh , 2006/02/22
+
+ { "HL-DT-ST", "DVDRAM GSA-4080N" , NULL , 0x10 }, // slim, 2004/08/08
+ { "HL-DT-ST", "DVDRAM GMA-4080N" , NULL , 0x10 }, // slim, 2004/11/09
+ { "HL-DT-ST", "DVD-RW GCA-4080N" , NULL , 0x10 }, // slim, 2004/11/22
+ { "HL-DT-ST", "DVD-RW GWA-4080N" , NULL , 0x10 }, // slim, 2004/08/17
+ { "HL-DT-ST", "DVDRAM GSA-4082N" , NULL , 0x10 }, // slim, 2005/07/12
+ { "HL-DT-ST", "DVDRAM_GSA-4082N" , NULL , 0x10 }, // slim, 2005/09/21
+ { "HL-DT-ST", "DVDRAM GMA-4082N" , NULL , 0x10 }, // slim, 2005/10/20
+ { "HL-DT-ST", "DVD-RW GRA-4082N" , NULL , 0x10 }, // slim, 2006/06/07
+ { "HL-DT-ST", "DVD-RW GWA-4082N" , NULL , 0x10 }, // slim, 2005/05/24
+ { "HL-DT-ST", "DVDRAM GMA4082Nf" , NULL , 0x10 }, // slim, 2006/02/28
+ { "HL-DT-ST", "DVDRAM GMA4082Nj" , NULL , 0x10 }, // slim, 2006/01/26
+
+ { "HL-DT-ST", "DVDRAM GSA-4084N" , NULL , 0x10 }, // slim, 2005/12/21
+ { "HL-DT-ST", "DVDRAM GMA-4084N" , NULL , 0x10 }, // slim, 2006/02/15
+ { "HP" , "DVD Writer 550s" , NULL , 0x10 }, // slim, 2006/05/08
+ { "HL-DT-ST", "DVDRAM GSA-T10N" , NULL , 0x10 }, // slim, 2006/07/26
+ { "HL-DT-ST", "DVDRAM_GSA-T10N" , NULL , 0x10 }, // slim, 2006/07/26
+ { "HL-DT-ST", "DVD+-RW GSA-T11N" , NULL , 0x10 }, // slim, 2006/07/25
+
+ { "HL-DT-ST", "DVD-ROM GDR8160B" , NULL , 0x10 }, // hh , 2001/10/12
+ { "COMPAQ" , "DVD-ROM GDR8160B" , NULL , 0x10 }, // hh , 2001/11/08
+ { "HL-DT-ST", "DVD-ROM GDR8161B" , NULL , 0x10 }, // hh , 2002/07/19
+ { "HL-DT-ST", "DVD-ROM GDR8162B" , NULL , 0x10 }, // hh , 2003/04/22
+ { "HL-DT-ST", "DVD-ROM GDR8163B" , NULL , 0x10 }, // hh , 2004/05/19
+ { "HL-DT-ST", "DVD-ROM GDR8164B" , NULL , 0x10 }, // hh , 2005/06/29
+ { "HL-DT-ST", "DVD-ROM GDRH10N" , NULL , 0x10 }, // hh , 2006/03/07
+
+ { "HL-DT-ST", "DVD-ROM GDR8081N" , NULL , 0x10 }, // slim, 2001/08/27
+ { "HL-DT-ST", "DVD-ROM GDR8082N" , NULL , 0x10 }, // slim, 2003/02/02
+ { "HL-DT-ST", "DVD-ROM GDR8083N" , NULL , 0x10 }, // slim, 2003/02/02
+ { "HL-DT-ST", "DVD-ROM GDR8085N" , NULL , 0x10 }, // slim, 2005/11/10
+
+ { "HL-DT-ST", "RW/DVD GCC-4080N" , NULL , 0x10 }, // slim, 2001/08/21
+ { "HL-DT-ST", "RW/DVD_GCC-4080N" , NULL , 0x10 }, // slim,
+ { "HL-DT-ST", "RW/DVD GCC-4160N" , NULL , 0x10 }, // slim, 2002/04/08
+ { "HL-DT-ST", "RW/DVD GCC-4240N" , NULL , 0x10 }, // slim, 2002/04/26
+ { "HL-DT-ST", "RW/DVD GCC-4241N" , NULL , 0x10 }, // slim, 2003/04/07
+ { "HL-DT-ST", "RW/DVD_GCC-4241N" , NULL , 0x10 }, // slim, 2004/03/07
+ { "HL-DT-ST", "RW/DVD GCC-4242N" , NULL , 0x10 }, // slim, 2003/12/21
+ { "HL-DT-ST", "RW/DVD GCC-4246N" , NULL , 0x10 }, // slim, 2005/05/23
+ { "HL-DT-ST", "BD-RE GBW-H10N" , NULL , 0x10 }, // hh , 2006/06/27
+
+ { "HL-DT-ST", "DVDRAM GSA-4083N" , NULL , 0x10 }, // hh , 2006/05/17
+ { "HL-DT-ST", "DVD+-RW GWA4083N" , NULL , 0x10 }, // hh , 2006/06/05
+
+ { "PIONEER", "DVD-RW DVR-106D" , NULL , 0x10 }, // hh , ?
+ { "ASUS", "DVD-RW DRW-0402P" , NULL , 0x10 }, // hh , ?
+
+ //
+ // This list contains vendor ID and product ID as a single string for ATAPI interface.
+ //
+
+ { "", "HL-DT-ST DVDRAM GMA-4020B" , NULL , 0x10 }, // hh , 2002/04/22
+ { "", "HL-DT-ST DVD-RW GCA-4020B" , NULL , 0x10 }, // hh , 2002/05/14
+ { "", "HL-DT-ST DVDRAM GSA-4040B" , NULL , 0x10 }, // hh , 2003/05/06
+ { "", "HL-DT-ST DVDRAM GMA-4040B" , NULL , 0x10 }, // hh , 2003/07/27
+ { "", "HL-DT-ST DVD-RW GWA-4040B" , NULL , 0x10 }, // hh , 2003/11/18
+ { "", "HL-DT-ST DVDRAM GSA-4081B" , NULL , 0x10 }, // hh , 2003/11/06
+ { "", "HL-DT-ST DVDRAM GSA-4082B" , NULL , 0x10 }, // hh , 2004/01/27
+ { "", "HL-DT-ST DVD-RW GWA-4082B" , NULL , 0x10 }, // hh , 2004/03/11
+ { "", "HL-DT-ST DVDRAM GSA-4120B" , NULL , 0x10 }, // hh , 2004/05/16
+ { "", "HL-DT-ST DVD+RW GRA-4120B" , NULL , 0x10 }, // hh , 2004/04/28
+ { "", "HL-DT-ST DVDRAM GSA-4160B" , NULL , 0x10 }, // hh , 2004/08/12
+ { "", "HL-DT-ST DVD-RW GWA-4160B" , NULL , 0x10 }, // hh , 2004/08/24
+ { "", "HL-DT-ST DVDRAM GSA-4163B" , NULL , 0x10 }, // hh , 2004/11/09
+ { "", "HL-DT-ST DVD-RW GWA-4163B" , NULL , 0x10 }, // hh , 2004/12/29
+ { "", "HL-DT-ST DVDRAM GSA-4165B" , NULL , 0x10 }, // hh , 2005/06/09
+ { "", "HL-DT-ST DVDRAM_GSA-4165B" , NULL , 0x10 }, // hh , 2005/06/28
+ { "", "HL-DT-ST DVD-RW GWA-4165B" , NULL , 0x10 }, // hh , 2005/08/23
+ { "", "HL-DT-ST DVDRAM GSA-4167B" , NULL , 0x10 }, // hh , 2005/07/01
+ { "", "HL-DT-ST DVDRAM GSA-H10N" , NULL , 0x10 }, // hh , 2006/02/16
+ { "", "HL-DT-ST DVDRAM_GSA-H10N" , NULL , 0x10 }, // hh , 2006/02/16
+ { "", "HL-DT-ST DVDRAM GSA-H10L" , NULL , 0x10 }, // hh , 2006/02/27
+ { "", "HL-DT-ST DVDRAM_GSA-H10L" , NULL , 0x10 }, // hh , 2006/04/21
+ { "", "HL-DT-ST DVDRAM GSA-H10A" , NULL , 0x10 }, // hh , 2006/01/03
+ { "", "HL-DT-ST DVDRAM_GSA-H10A" , NULL , 0x10 }, // hh , 2006/05/14
+ { "", "HL-DT-ST DVD-RW GSA-H11N" , NULL , 0x10 }, // hh , 2006/04/28
+ { "", "HL-DT-ST DVD-RW_GSA-H11N" , NULL , 0x10 }, // hh , 2006/02/22
+
+ { "", "HL-DT-ST DVDRAM GSA-4080N" , NULL , 0x10 }, // slim, 2004/08/08
+ { "", "HL-DT-ST DVDRAM GMA-4080N" , NULL , 0x10 }, // slim, 2004/11/09
+ { "", "HL-DT-ST DVD-RW GCA-4080N" , NULL , 0x10 }, // slim, 2004/11/22
+ { "", "HL-DT-ST DVD-RW GWA-4080N" , NULL , 0x10 }, // slim, 2004/08/17
+ { "", "HL-DT-ST DVDRAM GSA-4082N" , NULL , 0x10 }, // slim, 2005/07/12
+ { "", "HL-DT-ST DVDRAM_GSA-4082N" , NULL , 0x10 }, // slim, 2005/09/21
+ { "", "HL-DT-ST DVDRAM GMA-4082N" , NULL , 0x10 }, // slim, 2005/10/20
+ { "", "HL-DT-ST DVD-RW GRA-4082N" , NULL , 0x10 }, // slim, 2006/06/07
+ { "", "HL-DT-ST DVD-RW GWA-4082N" , NULL , 0x10 }, // slim, 2005/05/24
+ { "", "HL-DT-ST DVDRAM GMA4082Nf" , NULL , 0x10 }, // slim, 2006/02/28
+ { "", "HL-DT-ST DVDRAM GMA4082Nj" , NULL , 0x10 }, // slim, 2006/01/26
+
+ { "", "HL-DT-ST DVDRAM GSA-4084N" , NULL , 0x10 }, // slim, 2005/12/21
+ { "", "HL-DT-ST DVDRAM GMA-4084N" , NULL , 0x10 }, // slim, 2006/02/15
+ { "", "HP DVD Writer 550s" , NULL , 0x10 }, // slim, 2006/05/08
+ { "", "HL-DT-ST DVDRAM GSA-T10N" , NULL , 0x10 }, // slim, 2006/07/26
+ { "", "HL-DT-ST DVDRAM_GSA-T10N" , NULL , 0x10 }, // slim, 2006/07/26
+ { "", "HL-DT-ST DVD+-RW GSA-T11N" , NULL , 0x10 }, // slim, 2006/07/25
+
+ { "", "HL-DT-ST DVD-ROM GDR8160B" , NULL , 0x10 }, // hh , 2001/10/12
+ { "", "COMPAQ DVD-ROM GDR8160B" , NULL , 0x10 }, // hh , 2001/11/08
+ { "", "HL-DT-ST DVD-ROM GDR8161B" , NULL , 0x10 }, // hh , 2002/07/19
+ { "", "HL-DT-ST DVD-ROM GDR8162B" , NULL , 0x10 }, // hh , 2003/04/22
+ { "", "HL-DT-ST DVD-ROM GDR8163B" , NULL , 0x10 }, // hh , 2004/05/19
+ { "", "HL-DT-ST DVD-ROM GDR8164B" , NULL , 0x10 }, // hh , 2005/06/29
+ { "", "HL-DT-ST DVD-ROM GDRH10N" , NULL , 0x10 }, // hh , 2006/03/07
+
+ { "", "HL-DT-ST DVD-ROM GDR8081N" , NULL , 0x10 }, // slim, 2001/08/27
+ { "", "HL-DT-ST DVD-ROM GDR8082N" , NULL , 0x10 }, // slim, 2003/02/02
+ { "", "HL-DT-ST DVD-ROM GDR8083N" , NULL , 0x10 }, // slim, 2003/02/02
+ { "", "HL-DT-ST DVD-ROM GDR8085N" , NULL , 0x10 }, // slim, 2005/11/10
+
+ { "", "HL-DT-ST RW/DVD GCC-4080N" , NULL , 0x10 }, // slim, 2001/08/21
+ { "", "HL-DT-ST RW/DVD_GCC-4080N" , NULL , 0x10 }, // slim,
+ { "", "HL-DT-ST RW/DVD GCC-4160N" , NULL , 0x10 }, // slim, 2002/04/08
+ { "", "HL-DT-ST RW/DVD GCC-4240N" , NULL , 0x10 }, // slim, 2002/04/26
+ { "", "HL-DT-ST RW/DVD GCC-4241N" , NULL , 0x10 }, // slim, 2003/04/07
+ { "", "HL-DT-ST RW/DVD_GCC-4241N" , NULL , 0x10 }, // slim, 2004/03/07
+ { "", "HL-DT-ST RW/DVD GCC-4242N" , NULL , 0x10 }, // slim, 2003/12/21
+ { "", "HL-DT-ST RW/DVD GCC-4246N" , NULL , 0x10 }, // slim, 2005/05/23
+ { "", "HL-DT-ST BD-RE GBW-H10N" , NULL , 0x10 }, // hh , 2006/06/27
+
+ { "", "HL-DT-ST DVDRAM GSA-4083N" , NULL , 0x10 }, // hh , 2006/05/17
+ { "", "HL-DT-ST DVD+-RW GWA4083N" , NULL , 0x10 }, // hh , 2006/06/05
+
+ { "", "PIONEER DVD-RW DVR-106D" , NULL , 0x10 }, // hh , ?
+ { "", "ASUS DVD-RW DRW-0402P" , NULL , 0x10 }, // hh , ?
+
+
+ // Sony sourced some drives from LG also....
+
+ { NULL , NULL , NULL , 0x00 },
+};
+
+
+GUID ClassGuidQueryRegInfoEx = GUID_CLASSPNP_QUERY_REGINFOEX;
+GUID ClassGuidSenseInfo2 = GUID_CLASSPNP_SENSEINFO2;
+GUID ClassGuidWorkingSet = GUID_CLASSPNP_WORKING_SET;
+GUID ClassGuidSrbSupport = GUID_CLASSPNP_SRB_SUPPORT;
+
+#ifdef ALLOC_DATA_PRAGMA
+ #pragma data_seg()
+#endif
diff --git a/storage/class/classpnp/src/debug.c b/storage/class/classpnp/src/debug.c
new file mode 100644
index 00000000..cc288ec6
--- /dev/null
+++ b/storage/class/classpnp/src/debug.c
@@ -0,0 +1,966 @@
+/*++
+
+Copyright (C) Microsoft Corporation, 1991 - 2010
+
+Module Name:
+
+ debug.c
+
+Abstract:
+
+ CLASSPNP debug code and data
+
+Environment:
+
+ kernel mode only
+
+Notes:
+
+
+Revision History:
+
+--*/
+
+
+#include "classp.h"
+#include "debug.h"
+
+#ifdef DEBUG_USE_WPP
+#include "debug.tmh"
+#endif
+
+#if DBG
+
+ //
+ // default to not breaking in for lost irps, five minutes before we even
+ // bother checking for lost irps, using standard debug print macros, and
+ // using a 64k debug print buffer
+ //
+
+ #ifndef CLASS_GLOBAL_BREAK_ON_LOST_IRPS
+ #error "CLASS_GLOBAL_BREAK_ON_LOST_IRPS undefined"
+ #define CLASS_GLOBAL_BREAK_ON_LOST_IRPS 0
+ #endif // CLASS_GLOBAL_BREAK_ON_LOST_IRPS
+
+ #ifndef CLASS_GLOBAL_SECONDS_TO_WAIT_FOR_SYNCHRONOUS_SRB
+ #error "CLASS_GLOBAL_SECONDS_TO_WAIT_FOR_SYNCHRONOUS_SRB undefined"
+ #define CLASS_GLOBAL_SECONDS_TO_WAIT_FOR_SYNCHRONOUS_SRB 300
+ #endif // CLASS_GLOBAL_SECONDS_TO_WAIT_FOR_SYNCHRONOUS_SRB
+
+ #ifndef CLASS_GLOBAL_BUFFERED_DEBUG_PRINT
+ #error "CLASS_GLOBAL_BUFFERED_DEBUG_PRINT undefined"
+ #define CLASS_GLOBAL_BUFFERED_DEBUG_PRINT 0
+ #endif // CLASS_GLOBAL_BUFFERED_DEBUG_PRINT
+
+ #ifndef CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFER_SIZE
+ #error "CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFER_SIZE undefined"
+ #define CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFER_SIZE 512
+ #endif // CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFER_SIZE
+
+ #ifndef CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFERS
+ #error "CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFERS undefined"
+ #define CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFERS 512
+ #endif // CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFERS
+
+ #pragma data_seg("NONPAGE")
+
+
+
+ CLASSPNP_GLOBALS ClasspnpGlobals;
+
+ //
+ // the low sixteen bits are used to see if the debug level is high enough
+ // the high sixteen bits are used to singly enable debug levels 1-16
+ //
+ LONG ClassDebug = 0x00000000;
+
+ BOOLEAN DebugTrapOnWarn = FALSE;
+
+ //
+ // Used to track callers when we receive an access and the disk
+ // is powered down.
+ //
+ ULONG DiskSpinupIndex = 0;
+ DISK_SPINUP_TRACES DiskSpinupTraces[NUMBER_OF_DISK_SPINUP_TRACES];
+
+ VOID ClasspInitializeDebugGlobals()
+ {
+ KIRQL irql;
+
+ if (InterlockedCompareExchange(&ClasspnpGlobals.Initializing, 1, 0) == 0) {
+
+ KeInitializeSpinLock(&ClasspnpGlobals.SpinLock);
+
+ KeAcquireSpinLock(&ClasspnpGlobals.SpinLock, &irql);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "CLASSPNP.SYS => Initializing ClasspnpGlobals...\n"));
+
+ ClasspnpGlobals.Buffer = NULL;
+ ClasspnpGlobals.Index = (ULONG)-1;
+ ClasspnpGlobals.BreakOnLostIrps = CLASS_GLOBAL_BREAK_ON_LOST_IRPS;
+ ClasspnpGlobals.EachBufferSize = CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFER_SIZE;
+ ClasspnpGlobals.NumberOfBuffers = CLASS_GLOBAL_BUFFERED_DEBUG_PRINT_BUFFERS;
+ ClasspnpGlobals.SecondsToWaitForIrps = CLASS_GLOBAL_SECONDS_TO_WAIT_FOR_SYNCHRONOUS_SRB;
+
+ //
+ // this should be the last item set
+ //
+
+ ClasspnpGlobals.UseBufferedDebugPrint = CLASS_GLOBAL_BUFFERED_DEBUG_PRINT;
+
+ KeReleaseSpinLock(&ClasspnpGlobals.SpinLock, irql);
+
+ InterlockedExchange(&ClasspnpGlobals.Initialized, 1);
+
+ }
+ }
+
+ /*++////////////////////////////////////////////////////////////////////////////
+
+ ClassDebugPrint()
+
+ Routine Description:
+
+ Debug print for all class drivers, NOOP on FRE versions.
+ Allows printing to a debug buffer (with auto fallback to kdprint) by
+ properly setting the Globals in classpnp on CHK versions.
+
+ Arguments:
+
+ Debug print level, or from 0 to 3 for legacy drivers.
+
+ Return Value:
+
+ None
+
+ --*/
+ VOID ClassDebugPrint(_In_ CLASS_DEBUG_LEVEL DebugPrintLevel, _In_z_ PCCHAR DebugMessage, ...)
+ {
+ va_list ap;
+ va_start(ap, DebugMessage);
+
+ if ((DebugPrintLevel <= (ClassDebug & 0x0000ffff)) ||
+ ((1 << (DebugPrintLevel + 15)) & ClassDebug)) {
+
+ if (ClasspnpGlobals.UseBufferedDebugPrint &&
+ ClasspnpGlobals.Buffer == NULL) {
+
+ //
+ // this double-check prevents always taking
+ // a spinlock just to ensure we have a buffer
+ //
+
+ KIRQL irql;
+
+ KeAcquireSpinLock(&ClasspnpGlobals.SpinLock, &irql);
+ if (ClasspnpGlobals.Buffer == NULL) {
+
+ SIZE_T bufferSize;
+ if (NT_SUCCESS(
+ RtlSIZETMult(ClasspnpGlobals.NumberOfBuffers,
+ ClasspnpGlobals.EachBufferSize,
+ &bufferSize))) {
+
+ DbgPrintEx(DPFLTR_CLASSPNP_ID, DPFLTR_ERROR_LEVEL,
+ "ClassDebugPrint: Allocating %x bytes for "
+ "classdebugprint buffer\n", (ULONG)bufferSize);
+ ClasspnpGlobals.Index = (ULONG)-1;
+ ClasspnpGlobals.Buffer =
+ ExAllocatePoolWithTag(NonPagedPoolNx, bufferSize, 'bDcS');
+ DbgPrintEx(DPFLTR_CLASSPNP_ID, DPFLTR_ERROR_LEVEL,
+ "ClassDebugPrint: Allocated buffer at %p\n",
+ ClasspnpGlobals.Buffer);
+
+ if (ClasspnpGlobals.Buffer) {
+ RtlZeroMemory(ClasspnpGlobals.Buffer, bufferSize);
+ }
+ }
+
+ }
+ KeReleaseSpinLock(&ClasspnpGlobals.SpinLock, irql);
+
+ }
+
+ if (ClasspnpGlobals.UseBufferedDebugPrint &&
+ ClasspnpGlobals.Buffer != NULL) {
+
+ //
+ // we never free the buffer, so once it exists,
+ // we can just print to it with immunity
+ //
+
+ ULONG index;
+ PUCHAR buffer;
+ NTSTATUS status;
+ index = InterlockedIncrement((volatile LONG *)&ClasspnpGlobals.Index);
+ index %= ClasspnpGlobals.NumberOfBuffers;
+ index *= (ULONG)ClasspnpGlobals.EachBufferSize;
+
+ buffer = ClasspnpGlobals.Buffer;
+ buffer += index;
+
+ RtlZeroMemory(buffer, ClasspnpGlobals.EachBufferSize);
+
+ status = RtlStringCchVPrintfA((NTSTRSAFE_PSTR)buffer, ClasspnpGlobals.EachBufferSize, DebugMessage, ap);
+ if (!NT_SUCCESS(status))
+ {
+ *buffer = 0; // force-null on failure
+ }
+
+ } else {
+
+ //
+ // either we could not allocate a buffer for debug prints
+ // or buffered debug prints are disabled
+ //
+
+ vDbgPrintEx(DPFLTR_CLASSPNP_ID, DPFLTR_INFO_LEVEL, DebugMessage, ap);
+
+ }
+
+ }
+
+ va_end(ap);
+
+ }
+
+
+ /*
+ * DbgCheckReturnedPkt
+ *
+ * Check a completed TRANSFER_PACKET for all sorts of error conditions
+ * and warn/trap appropriately.
+ */
+ VOID DbgCheckReturnedPkt(TRANSFER_PACKET *Pkt)
+ {
+ PCDB pCdb = ClasspTransferPacketGetCdb(Pkt);
+
+ NT_ASSERT(SrbGetOriginalRequest(Pkt->Srb) == Pkt->Irp);
+ NT_ASSERT(SrbGetDataBuffer(Pkt->Srb) == Pkt->BufPtrCopy);
+ NT_ASSERT(SrbGetDataTransferLength(Pkt->Srb) <= Pkt->BufLenCopy);
+ NT_ASSERT(!Pkt->Irp->CancelRoutine);
+
+ if (SRB_STATUS(Pkt->Srb->SrbStatus) == SRB_STATUS_PENDING){
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_RW, "SRB completed with status PENDING in packet %ph: (op=%s srbstat=%s(%xh), irpstat=%xh)",
+ Pkt,
+ DBGGETSCSIOPSTR(Pkt->Srb),
+ DBGGETSRBSTATUSSTR(Pkt->Srb),
+ (ULONG)Pkt->Srb->SrbStatus,
+ Pkt->Irp->IoStatus.Status));
+ }
+ else if (SRB_STATUS(Pkt->Srb->SrbStatus) == SRB_STATUS_SUCCESS){
+ /*
+ * Make sure SRB and IRP status match.
+ */
+ if (!NT_SUCCESS(Pkt->Irp->IoStatus.Status)){
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_RW, "SRB and IRP status don't match in packet %ph: (op=%s srbstat=%s(%xh), irpstat=%xh)",
+ Pkt,
+ DBGGETSCSIOPSTR(Pkt->Srb),
+ DBGGETSRBSTATUSSTR(Pkt->Srb),
+ (ULONG)Pkt->Srb->SrbStatus,
+ Pkt->Irp->IoStatus.Status));
+ }
+
+ if (Pkt->Irp->IoStatus.Information != SrbGetDataTransferLength(Pkt->Srb)){
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_RW, "SRB and IRP result transfer lengths don't match in succeeded packet %ph: (op=%s, SrbStatus=%s, Srb.DataTransferLength=%xh, Irp->IoStatus.Information=%Ixh).",
+ Pkt,
+ DBGGETSCSIOPSTR(Pkt->Srb),
+ DBGGETSRBSTATUSSTR(Pkt->Srb),
+ SrbGetDataTransferLength(Pkt->Srb),
+ Pkt->Irp->IoStatus.Information));
+ }
+ }
+ else {
+ if (NT_SUCCESS(Pkt->Irp->IoStatus.Status)){
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_RW, "SRB and IRP status don't match in packet %ph: (op=%s srbstat=%s(%xh), irpstat=%xh)",
+ Pkt,
+ DBGGETSCSIOPSTR(Pkt->Srb),
+ DBGGETSRBSTATUSSTR(Pkt->Srb),
+ (ULONG)Pkt->Srb->SrbStatus,
+ Pkt->Irp->IoStatus.Status));
+ }
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_RW, "Packet %ph failed (op=%s srbstat=%s(%xh), irpstat=%xh, sense=%s/%s/%s)",
+ Pkt,
+ DBGGETSCSIOPSTR(Pkt->Srb),
+ DBGGETSRBSTATUSSTR(Pkt->Srb),
+ (ULONG)Pkt->Srb->SrbStatus,
+ Pkt->Irp->IoStatus.Status,
+ DBGGETSENSECODESTR(Pkt->Srb),
+ DBGGETADSENSECODESTR(Pkt->Srb),
+ DBGGETADSENSEQUALIFIERSTR(Pkt->Srb)));
+
+ /*
+ * If the SRB failed with underrun or overrun, then the actual
+ * transferred length should be returned in both SRB and IRP.
+ * (SRB's only have an error status for overrun, so it's overloaded).
+ */
+ if ((SRB_STATUS(Pkt->Srb->SrbStatus) == SRB_STATUS_DATA_OVERRUN) &&
+ (Pkt->Irp->IoStatus.Information != SrbGetDataTransferLength(Pkt->Srb))){
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_RW, "SRB and IRP result transfer lengths don't match in failed packet %ph: (op=%s, SrbStatus=%s, Srb.DataTransferLength=%xh, Irp->IoStatus.Information=%Ixh).",
+ Pkt,
+ DBGGETSCSIOPSTR(Pkt->Srb),
+ DBGGETSRBSTATUSSTR(Pkt->Srb),
+ SrbGetDataTransferLength(Pkt->Srb),
+ Pkt->Irp->IoStatus.Information));
+ }
+ }
+
+ /*
+ * If the port driver returned STATUS_INSUFFICIENT_RESOURCES,
+ * make sure this is also the InternalStatus in the SRB so that we process it correctly.
+ */
+ if (Pkt->Irp->IoStatus.Status == STATUS_INSUFFICIENT_RESOURCES){
+ NT_ASSERT(SRB_STATUS(Pkt->Srb->SrbStatus) == SRB_STATUS_INTERNAL_ERROR);
+ NT_ASSERT(SrbGetSystemStatus(Pkt->Srb) == STATUS_INSUFFICIENT_RESOURCES);
+ }
+
+ /*
+ * Some miniport drivers have been caught changing the SCSI operation
+ * code in the SRB. This is absolutely disallowed as it breaks our error handling.
+ */
+ switch (pCdb->CDB10.OperationCode){
+ case SCSIOP_MEDIUM_REMOVAL:
+ case SCSIOP_MODE_SENSE:
+ case SCSIOP_READ_CAPACITY:
+ case SCSIOP_READ:
+ case SCSIOP_WRITE:
+ case SCSIOP_START_STOP_UNIT:
+ case SCSIOP_READ_CAPACITY16:
+ case SCSIOP_READ16:
+ case SCSIOP_WRITE16:
+ break;
+ default:
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_RW, "Miniport illegally changed Srb.Cdb.OperationCode in packet %ph failed (op=%s srbstat=%s(%xh), irpstat=%xh, sense=%s/%s/%s)",
+ Pkt,
+ DBGGETSCSIOPSTR(Pkt->Srb),
+ DBGGETSRBSTATUSSTR(Pkt->Srb),
+ (ULONG)Pkt->Srb->SrbStatus,
+ Pkt->Irp->IoStatus.Status,
+ DBGGETSENSECODESTR(Pkt->Srb),
+ DBGGETADSENSECODESTR(Pkt->Srb),
+ DBGGETADSENSEQUALIFIERSTR(Pkt->Srb)));
+ break;
+ }
+
+ }
+
+
+ VOID DbgLogSendPacket(TRANSFER_PACKET *Pkt)
+ {
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Pkt->Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
+ KIRQL oldIrql;
+
+ if (Pkt->OriginalIrp){
+ Pkt->DbgOriginalIrpCopy = *Pkt->OriginalIrp;
+ if (Pkt->OriginalIrp->MdlAddress){
+ Pkt->DbgMdlCopy = *Pkt->OriginalIrp->MdlAddress;
+ }
+ }
+
+ KeQueryTickCount(&Pkt->DbgTimeSent);
+ Pkt->DbgTimeReturned.QuadPart = 0L;
+
+ KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
+ fdoData->DbgPacketLogs[fdoData->DbgPacketLogNextIndex] = *Pkt;
+ fdoData->DbgPacketLogNextIndex++;
+ fdoData->DbgPacketLogNextIndex %= DBG_NUM_PACKET_LOG_ENTRIES;
+ KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
+ }
+
+ VOID DbgLogReturnPacket(TRANSFER_PACKET *Pkt)
+ {
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Pkt->Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
+ KIRQL oldIrql;
+
+ KeQueryTickCount(&Pkt->DbgTimeReturned);
+
+ #if 0
+ // ISSUE: there are some problems with this check (e.g. multiproc), so don't include it yet
+ if (Pkt->OriginalIrp){
+ /*
+ * No one should have touched the original irp while the packet was outstanding,
+ * except for a couple fields that we ourselves update during the transfer
+ * or that are allowed to change;
+ * make those couple fields the same and then to a bytewise compare
+ */
+ ULONG lenSame;
+
+ Pkt->DbgOriginalIrpCopy.IoStatus.Status = Pkt->OriginalIrp->IoStatus.Status;
+ Pkt->DbgOriginalIrpCopy.IoStatus.Information = Pkt->OriginalIrp->IoStatus.Information;
+ Pkt->DbgOriginalIrpCopy.Tail.Overlay.DriverContext[0] = Pkt->OriginalIrp->Tail.Overlay.DriverContext[0];
+ Pkt->DbgOriginalIrpCopy.ThreadListEntry = Pkt->OriginalIrp->ThreadListEntry;
+ Pkt->DbgOriginalIrpCopy.Cancel = Pkt->OriginalIrp->Cancel;
+
+ lenSame = (ULONG)RtlCompareMemory(Pkt->OriginalIrp, &Pkt->DbgOriginalIrpCopy, sizeof(IRP));
+ NT_ASSERT(lenSame == sizeof(IRP));
+ }
+ #endif
+
+ KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
+ fdoData->DbgPacketLogs[fdoData->DbgPacketLogNextIndex] = *Pkt;
+ fdoData->DbgPacketLogNextIndex++;
+ fdoData->DbgPacketLogNextIndex %= DBG_NUM_PACKET_LOG_ENTRIES;
+ KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
+ }
+
+
+ /*++////////////////////////////////////////////////////////////////////////////
+
+ DbgSafeInc()
+
+ Routine Description:
+
+ Safely increments a ULONG. If the increment would result in an overflow,
+ the value is unchanged.
+
+ Arguments:
+
+ A pointer to the value to be incremented.
+
+ --*/
+ __inline VOID DbgSafeInc(PULONG pValue)
+ {
+ ULONG incrementResult;
+ if(NT_SUCCESS(RtlULongAdd(*pValue, 1, &incrementResult))) {
+ *pValue = incrementResult;
+ } else {
+ //
+ // Leave *pValue unchanged (i.e. at ULONG_MAX).
+ //
+ }
+ }
+
+ VOID DbgLogFlushInfo(PCLASS_PRIVATE_FDO_DATA FdoData, BOOLEAN IsIO, BOOLEAN IsFUA, BOOLEAN IsFlush)
+ {
+
+ /*
+ * Reset all FUA/Flush logging fields.
+ */
+ if (FdoData->DbgInitFlushLogging){
+ FdoData->DbgNumIORequests = 0;
+ FdoData->DbgNumFUAs = 0;
+ FdoData->DbgNumFlushes = 0;
+ FdoData->DbgIOsSinceFUA = 0;
+ FdoData->DbgIOsSinceFlush = 0;
+ FdoData->DbgAveIOsToFUA = 0;
+ FdoData->DbgAveIOsToFlush = 0;
+ FdoData->DbgMaxIOsToFUA = 0;
+ FdoData->DbgMaxIOsToFlush = 0;
+ FdoData->DbgMinIOsToFUA = 0xffffffff;
+ FdoData->DbgMinIOsToFlush = 0xffffffff;
+ FdoData->DbgInitFlushLogging = FALSE;
+ }
+
+ //
+ // Using DbgSafeInc for all increments (instead of ++) guarantees
+ // that there will be no overflow hence no division by 0. All counters
+ // are capped at ULONG_MAX.
+ //
+
+ if (IsIO){
+ DbgSafeInc(&FdoData->DbgNumIORequests);
+ DbgSafeInc(&FdoData->DbgIOsSinceFlush);
+ if (IsFUA){
+ if (FdoData->DbgNumFUAs > 0){
+ FdoData->DbgMinIOsToFUA = min(FdoData->DbgMinIOsToFUA, FdoData->DbgIOsSinceFUA);
+ }
+ DbgSafeInc(&FdoData->DbgNumFUAs);
+ FdoData->DbgAveIOsToFUA = FdoData->DbgNumIORequests/FdoData->DbgNumFUAs;
+ FdoData->DbgIOsSinceFUA = 0;
+ }
+ else {
+ DbgSafeInc(&FdoData->DbgIOsSinceFUA);
+ FdoData->DbgMaxIOsToFUA = max(FdoData->DbgMaxIOsToFUA, FdoData->DbgIOsSinceFUA);
+ }
+ FdoData->DbgMaxIOsToFlush = max(FdoData->DbgMaxIOsToFlush, FdoData->DbgIOsSinceFlush);
+ }
+ else if (IsFlush){
+ if (FdoData->DbgNumFlushes > 0){
+ FdoData->DbgMinIOsToFlush = min(FdoData->DbgMinIOsToFlush, FdoData->DbgIOsSinceFlush);
+ }
+ DbgSafeInc(&FdoData->DbgNumFlushes);
+ FdoData->DbgAveIOsToFlush = FdoData->DbgNumIORequests/FdoData->DbgNumFlushes;
+ FdoData->DbgIOsSinceFlush = 0;
+ }
+
+ }
+
+
+ /*++////////////////////////////////////////////////////////////////////////////
+
+ SnapDiskStartup()
+
+ Routine Description:
+
+ This function will attempt to record the caller responsible for spinning
+ up the disk.
+
+ Arguments:
+
+ NONE.
+
+ Return Value:
+
+ NONE.
+
+ --*/
+ VOID
+ SnapDiskStartup(
+ VOID
+ )
+ {
+ ULONG Index;
+ PDISK_SPINUP_TRACES Entry;
+ LARGE_INTEGER SpinUpTime;
+
+#pragma warning(push)
+#pragma warning(disable:4210) // nonstandard extension used : function given file scope
+ extern NTSYSAPI USHORT NTAPI RtlCaptureStackBackTrace(
+ _In_ ULONG FramesToSkip,
+ _In_ ULONG FramesToCapture,
+ _Out_writes_to_(FramesToCapture, return) PVOID * BackTrace,
+ _Out_opt_ PULONG BackTraceHash );
+#pragma warning(pop)
+
+ //
+ // Grab the current count, then mod it so that it
+ // becomes an index into the DiskSpinupTraces array.
+ //
+ Index = InterlockedIncrement( (volatile LONG *)&DiskSpinupIndex );
+ Index = Index & (NUMBER_OF_DISK_SPINUP_TRACES - 1);
+ Entry = &DiskSpinupTraces[Index];
+
+ //
+ // Timestamp the instance.
+ //
+ KeQueryTickCount(&SpinUpTime);
+ SpinUpTime.QuadPart = (SpinUpTime.QuadPart * KeQueryTimeIncrement())/(10000000);
+
+
+ //
+ // Ask the kernel to read back up our stack by
+ // DISK_SPINUP_BACKTRACE_LENGTH frames.
+ //
+ Entry->TimeStamp.QuadPart = SpinUpTime.QuadPart;
+ RtlZeroMemory( &Entry->StackTrace[0], DISK_SPINUP_BACKTRACE_LENGTH * sizeof(PVOID) );
+ RtlCaptureStackBackTrace( 5, // stacks to skip
+ DISK_SPINUP_BACKTRACE_LENGTH, // buffer size
+ Entry->StackTrace,
+ &Index );
+ }
+
+#else
+
+ // We have to keep this in the retail build for legacy.
+ VOID ClassDebugPrint(_In_ CLASS_DEBUG_LEVEL DebugPrintLevel, _In_z_ PCCHAR DebugMessage, ...)
+ {
+ UNREFERENCED_PARAMETER(DebugPrintLevel);
+ UNREFERENCED_PARAMETER(DebugMessage);
+ }
+
+#endif
+
+ char *DbgGetIoctlStr(ULONG ioctl)
+ {
+ char *ioctlStr = "?";
+
+ switch (ioctl){
+
+ #undef MAKE_CASE
+ #define MAKE_CASE(ioctlCode) case ioctlCode: ioctlStr = #ioctlCode; break;
+
+ MAKE_CASE(IOCTL_STORAGE_CHECK_VERIFY)
+ MAKE_CASE(IOCTL_STORAGE_CHECK_VERIFY2)
+ MAKE_CASE(IOCTL_STORAGE_MEDIA_REMOVAL)
+ MAKE_CASE(IOCTL_STORAGE_EJECT_MEDIA)
+ MAKE_CASE(IOCTL_STORAGE_LOAD_MEDIA)
+ MAKE_CASE(IOCTL_STORAGE_LOAD_MEDIA2)
+ MAKE_CASE(IOCTL_STORAGE_RESERVE)
+ MAKE_CASE(IOCTL_STORAGE_RELEASE)
+ MAKE_CASE(IOCTL_STORAGE_PERSISTENT_RESERVE_IN)
+ MAKE_CASE(IOCTL_STORAGE_PERSISTENT_RESERVE_OUT)
+ MAKE_CASE(IOCTL_STORAGE_FIND_NEW_DEVICES)
+ MAKE_CASE(IOCTL_STORAGE_EJECTION_CONTROL)
+ MAKE_CASE(IOCTL_STORAGE_MCN_CONTROL)
+ MAKE_CASE(IOCTL_STORAGE_GET_MEDIA_TYPES)
+ MAKE_CASE(IOCTL_STORAGE_GET_MEDIA_TYPES_EX)
+ MAKE_CASE(IOCTL_STORAGE_GET_MEDIA_SERIAL_NUMBER)
+ MAKE_CASE(IOCTL_STORAGE_GET_HOTPLUG_INFO)
+ MAKE_CASE(IOCTL_STORAGE_RESET_BUS)
+ MAKE_CASE(IOCTL_STORAGE_RESET_DEVICE)
+ MAKE_CASE(IOCTL_STORAGE_GET_DEVICE_NUMBER)
+ MAKE_CASE(IOCTL_STORAGE_PREDICT_FAILURE)
+ MAKE_CASE(IOCTL_STORAGE_QUERY_PROPERTY)
+ MAKE_CASE(OBSOLETE_IOCTL_STORAGE_RESET_BUS)
+ MAKE_CASE(OBSOLETE_IOCTL_STORAGE_RESET_DEVICE)
+ }
+
+ return ioctlStr;
+ }
+
+ char *DbgGetScsiOpStr(PSTORAGE_REQUEST_BLOCK_HEADER Srb)
+ {
+ PCDB pCdb = SrbGetCdb(Srb);
+ char *scsiOpStr = "?";
+
+ if (pCdb) {
+
+ switch (pCdb->CDB6GENERIC.OperationCode){
+
+ #undef MAKE_CASE
+ #define MAKE_CASE(scsiOpCode) case scsiOpCode: scsiOpStr = #scsiOpCode; break;
+
+ MAKE_CASE(SCSIOP_TEST_UNIT_READY)
+ MAKE_CASE(SCSIOP_REWIND) // aka SCSIOP_REZERO_UNIT
+ MAKE_CASE(SCSIOP_REQUEST_BLOCK_ADDR)
+ MAKE_CASE(SCSIOP_REQUEST_SENSE)
+ MAKE_CASE(SCSIOP_FORMAT_UNIT)
+ MAKE_CASE(SCSIOP_READ_BLOCK_LIMITS)
+ MAKE_CASE(SCSIOP_INIT_ELEMENT_STATUS) // aka SCSIOP_REASSIGN_BLOCKS
+ MAKE_CASE(SCSIOP_RECEIVE) // aka SCSIOP_READ6
+ MAKE_CASE(SCSIOP_SEND) // aka SCSIOP_WRITE6, SCSIOP_PRINT
+ MAKE_CASE(SCSIOP_SLEW_PRINT) // aka SCSIOP_SEEK6, SCSIOP_TRACK_SELECT
+ MAKE_CASE(SCSIOP_SEEK_BLOCK)
+ MAKE_CASE(SCSIOP_PARTITION)
+ MAKE_CASE(SCSIOP_READ_REVERSE)
+ MAKE_CASE(SCSIOP_FLUSH_BUFFER) // aka SCSIOP_WRITE_FILEMARKS
+ MAKE_CASE(SCSIOP_SPACE)
+ MAKE_CASE(SCSIOP_INQUIRY)
+ MAKE_CASE(SCSIOP_VERIFY6)
+ MAKE_CASE(SCSIOP_RECOVER_BUF_DATA)
+ MAKE_CASE(SCSIOP_MODE_SELECT)
+ MAKE_CASE(SCSIOP_RESERVE_UNIT)
+ MAKE_CASE(SCSIOP_RELEASE_UNIT)
+ MAKE_CASE(SCSIOP_COPY)
+ MAKE_CASE(SCSIOP_ERASE)
+ MAKE_CASE(SCSIOP_MODE_SENSE)
+ MAKE_CASE(SCSIOP_START_STOP_UNIT) // aka SCSIOP_STOP_PRINT, SCSIOP_LOAD_UNLOAD
+ MAKE_CASE(SCSIOP_RECEIVE_DIAGNOSTIC)
+ MAKE_CASE(SCSIOP_SEND_DIAGNOSTIC)
+ MAKE_CASE(SCSIOP_MEDIUM_REMOVAL)
+ MAKE_CASE(SCSIOP_READ_FORMATTED_CAPACITY)
+ MAKE_CASE(SCSIOP_READ_CAPACITY)
+ MAKE_CASE(SCSIOP_READ)
+ MAKE_CASE(SCSIOP_WRITE)
+ MAKE_CASE(SCSIOP_SEEK) // aka SCSIOP_LOCATE, SCSIOP_POSITION_TO_ELEMENT
+ MAKE_CASE(SCSIOP_WRITE_VERIFY)
+ MAKE_CASE(SCSIOP_VERIFY)
+ MAKE_CASE(SCSIOP_SEARCH_DATA_HIGH)
+ MAKE_CASE(SCSIOP_SEARCH_DATA_EQUAL)
+ MAKE_CASE(SCSIOP_SEARCH_DATA_LOW)
+ MAKE_CASE(SCSIOP_SET_LIMITS)
+ MAKE_CASE(SCSIOP_READ_POSITION)
+ MAKE_CASE(SCSIOP_SYNCHRONIZE_CACHE)
+ MAKE_CASE(SCSIOP_COMPARE)
+ MAKE_CASE(SCSIOP_COPY_COMPARE)
+ MAKE_CASE(SCSIOP_WRITE_DATA_BUFF)
+ MAKE_CASE(SCSIOP_READ_DATA_BUFF)
+ MAKE_CASE(SCSIOP_CHANGE_DEFINITION)
+ MAKE_CASE(SCSIOP_READ_SUB_CHANNEL)
+ MAKE_CASE(SCSIOP_READ_TOC)
+ MAKE_CASE(SCSIOP_READ_HEADER)
+ MAKE_CASE(SCSIOP_PLAY_AUDIO)
+ MAKE_CASE(SCSIOP_GET_CONFIGURATION)
+ MAKE_CASE(SCSIOP_PLAY_AUDIO_MSF)
+ MAKE_CASE(SCSIOP_PLAY_TRACK_INDEX)
+ MAKE_CASE(SCSIOP_PLAY_TRACK_RELATIVE)
+ MAKE_CASE(SCSIOP_GET_EVENT_STATUS)
+ MAKE_CASE(SCSIOP_PAUSE_RESUME)
+ MAKE_CASE(SCSIOP_LOG_SELECT)
+ MAKE_CASE(SCSIOP_LOG_SENSE)
+ MAKE_CASE(SCSIOP_STOP_PLAY_SCAN)
+ MAKE_CASE(SCSIOP_READ_DISK_INFORMATION)
+ MAKE_CASE(SCSIOP_READ_TRACK_INFORMATION)
+ MAKE_CASE(SCSIOP_RESERVE_TRACK_RZONE)
+ MAKE_CASE(SCSIOP_SEND_OPC_INFORMATION)
+ MAKE_CASE(SCSIOP_MODE_SELECT10)
+ MAKE_CASE(SCSIOP_MODE_SENSE10)
+ MAKE_CASE(SCSIOP_CLOSE_TRACK_SESSION)
+ MAKE_CASE(SCSIOP_READ_BUFFER_CAPACITY)
+ MAKE_CASE(SCSIOP_SEND_CUE_SHEET)
+ MAKE_CASE(SCSIOP_PERSISTENT_RESERVE_IN)
+ MAKE_CASE(SCSIOP_PERSISTENT_RESERVE_OUT)
+ MAKE_CASE(SCSIOP_REPORT_LUNS)
+ MAKE_CASE(SCSIOP_BLANK)
+ MAKE_CASE(SCSIOP_SEND_KEY)
+ MAKE_CASE(SCSIOP_REPORT_KEY)
+ MAKE_CASE(SCSIOP_MOVE_MEDIUM)
+ MAKE_CASE(SCSIOP_LOAD_UNLOAD_SLOT) // aka SCSIOP_EXCHANGE_MEDIUM
+ MAKE_CASE(SCSIOP_SET_READ_AHEAD)
+ MAKE_CASE(SCSIOP_READ_DVD_STRUCTURE)
+ MAKE_CASE(SCSIOP_REQUEST_VOL_ELEMENT)
+ MAKE_CASE(SCSIOP_SEND_VOLUME_TAG)
+ MAKE_CASE(SCSIOP_READ_ELEMENT_STATUS)
+ MAKE_CASE(SCSIOP_READ_CD_MSF)
+ MAKE_CASE(SCSIOP_SCAN_CD)
+ MAKE_CASE(SCSIOP_SET_CD_SPEED)
+ MAKE_CASE(SCSIOP_PLAY_CD)
+ MAKE_CASE(SCSIOP_MECHANISM_STATUS)
+ MAKE_CASE(SCSIOP_READ_CD)
+ MAKE_CASE(SCSIOP_SEND_DVD_STRUCTURE)
+ MAKE_CASE(SCSIOP_INIT_ELEMENT_RANGE)
+ MAKE_CASE(SCSIOP_READ16)
+ MAKE_CASE(SCSIOP_WRITE16)
+ MAKE_CASE(SCSIOP_VERIFY16)
+ MAKE_CASE(SCSIOP_SYNCHRONIZE_CACHE16)
+ MAKE_CASE(SCSIOP_READ_CAPACITY16)
+ }
+ }
+
+ return scsiOpStr;
+ }
+
+
+ char *DbgGetSrbStatusStr(PSTORAGE_REQUEST_BLOCK_HEADER Srb)
+ {
+ char *srbStatStr = "?";
+
+ switch (Srb->SrbStatus){
+
+ #undef MAKE_CASE
+ #define MAKE_CASE(srbStat) \
+ case srbStat: \
+ srbStatStr = #srbStat; \
+ break; \
+ case srbStat|SRB_STATUS_QUEUE_FROZEN: \
+ srbStatStr = #srbStat "|SRB_STATUS_QUEUE_FROZEN"; \
+ break; \
+ case srbStat|SRB_STATUS_AUTOSENSE_VALID: \
+ srbStatStr = #srbStat "|SRB_STATUS_AUTOSENSE_VALID"; \
+ break; \
+ case srbStat|SRB_STATUS_QUEUE_FROZEN|SRB_STATUS_AUTOSENSE_VALID: \
+ srbStatStr = #srbStat "|SRB_STATUS_QUEUE_FROZEN|SRB_STATUS_AUTOSENSE_VALID"; \
+ break;
+
+ MAKE_CASE(SRB_STATUS_PENDING)
+ MAKE_CASE(SRB_STATUS_SUCCESS)
+ MAKE_CASE(SRB_STATUS_ABORTED)
+ MAKE_CASE(SRB_STATUS_ABORT_FAILED)
+ MAKE_CASE(SRB_STATUS_ERROR)
+ MAKE_CASE(SRB_STATUS_BUSY)
+ MAKE_CASE(SRB_STATUS_INVALID_REQUEST)
+ MAKE_CASE(SRB_STATUS_INVALID_PATH_ID)
+ MAKE_CASE(SRB_STATUS_NO_DEVICE)
+ MAKE_CASE(SRB_STATUS_TIMEOUT)
+ MAKE_CASE(SRB_STATUS_SELECTION_TIMEOUT)
+ MAKE_CASE(SRB_STATUS_COMMAND_TIMEOUT)
+ MAKE_CASE(SRB_STATUS_MESSAGE_REJECTED)
+ MAKE_CASE(SRB_STATUS_BUS_RESET)
+ MAKE_CASE(SRB_STATUS_PARITY_ERROR)
+ MAKE_CASE(SRB_STATUS_REQUEST_SENSE_FAILED)
+ MAKE_CASE(SRB_STATUS_NO_HBA)
+ MAKE_CASE(SRB_STATUS_DATA_OVERRUN)
+ MAKE_CASE(SRB_STATUS_UNEXPECTED_BUS_FREE)
+ MAKE_CASE(SRB_STATUS_PHASE_SEQUENCE_FAILURE)
+ MAKE_CASE(SRB_STATUS_BAD_SRB_BLOCK_LENGTH)
+ MAKE_CASE(SRB_STATUS_REQUEST_FLUSHED)
+ MAKE_CASE(SRB_STATUS_INVALID_LUN)
+ MAKE_CASE(SRB_STATUS_INVALID_TARGET_ID)
+ MAKE_CASE(SRB_STATUS_BAD_FUNCTION)
+ MAKE_CASE(SRB_STATUS_ERROR_RECOVERY)
+ MAKE_CASE(SRB_STATUS_NOT_POWERED)
+ MAKE_CASE(SRB_STATUS_INTERNAL_ERROR)
+ }
+
+ return srbStatStr;
+ }
+
+
+ char *DbgGetSenseCodeStr(PSTORAGE_REQUEST_BLOCK_HEADER Srb)
+ {
+ char *senseCodeStr = "?";
+
+ if (Srb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID){
+
+ PVOID senseData;
+ UCHAR senseCode;
+ BOOLEAN validSense;
+
+ senseData = SrbGetSenseInfoBuffer(Srb);
+ NT_ASSERT(senseData);
+
+ validSense = ScsiGetSenseKeyAndCodes(senseData,
+ SrbGetSenseInfoBufferLength(Srb),
+ SCSI_SENSE_OPTIONS_FIXED_FORMAT_IF_UNKNOWN_FORMAT_INDICATED,
+ &senseCode,
+ NULL,
+ NULL);
+ if (validSense) {
+ switch (senseCode){
+
+ #undef MAKE_CASE
+ #define MAKE_CASE(snsCod) case snsCod: senseCodeStr = #snsCod; break;
+
+ MAKE_CASE(SCSI_SENSE_NO_SENSE)
+ MAKE_CASE(SCSI_SENSE_RECOVERED_ERROR)
+ MAKE_CASE(SCSI_SENSE_NOT_READY)
+ MAKE_CASE(SCSI_SENSE_MEDIUM_ERROR)
+ MAKE_CASE(SCSI_SENSE_HARDWARE_ERROR)
+ MAKE_CASE(SCSI_SENSE_ILLEGAL_REQUEST)
+ MAKE_CASE(SCSI_SENSE_UNIT_ATTENTION)
+ MAKE_CASE(SCSI_SENSE_DATA_PROTECT)
+ MAKE_CASE(SCSI_SENSE_BLANK_CHECK)
+ MAKE_CASE(SCSI_SENSE_UNIQUE)
+ MAKE_CASE(SCSI_SENSE_COPY_ABORTED)
+ MAKE_CASE(SCSI_SENSE_ABORTED_COMMAND)
+ MAKE_CASE(SCSI_SENSE_EQUAL)
+ MAKE_CASE(SCSI_SENSE_VOL_OVERFLOW)
+ MAKE_CASE(SCSI_SENSE_MISCOMPARE)
+ MAKE_CASE(SCSI_SENSE_RESERVED)
+ }
+ }
+ }
+
+ return senseCodeStr;
+ }
+
+
+ char *DbgGetAdditionalSenseCodeStr(PSTORAGE_REQUEST_BLOCK_HEADER Srb)
+ {
+ char *adSenseCodeStr = "?";
+
+ if (Srb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID){
+ PVOID senseData;
+ UCHAR adSenseCode;
+ BOOLEAN validSense;
+
+ senseData = SrbGetSenseInfoBuffer(Srb);
+ NT_ASSERT(senseData);
+
+ validSense = ScsiGetSenseKeyAndCodes(senseData,
+ SrbGetSenseInfoBufferLength(Srb),
+ SCSI_SENSE_OPTIONS_FIXED_FORMAT_IF_UNKNOWN_FORMAT_INDICATED,
+ NULL,
+ &adSenseCode,
+ NULL);
+
+ if (validSense) {
+ switch (adSenseCode){
+
+ #undef MAKE_CASE
+ #define MAKE_CASE(adSnsCod) case adSnsCod: adSenseCodeStr = #adSnsCod; break;
+
+ MAKE_CASE(SCSI_ADSENSE_NO_SENSE)
+ MAKE_CASE(SCSI_ADSENSE_LUN_NOT_READY)
+ MAKE_CASE(SCSI_ADSENSE_TRACK_ERROR)
+ MAKE_CASE(SCSI_ADSENSE_SEEK_ERROR)
+ MAKE_CASE(SCSI_ADSENSE_REC_DATA_NOECC)
+ MAKE_CASE(SCSI_ADSENSE_REC_DATA_ECC)
+ MAKE_CASE(SCSI_ADSENSE_ILLEGAL_COMMAND)
+ MAKE_CASE(SCSI_ADSENSE_ILLEGAL_BLOCK)
+ MAKE_CASE(SCSI_ADSENSE_INVALID_CDB)
+ MAKE_CASE(SCSI_ADSENSE_INVALID_LUN)
+ MAKE_CASE(SCSI_ADSENSE_WRITE_PROTECT) // aka SCSI_ADWRITE_PROTECT
+ MAKE_CASE(SCSI_ADSENSE_MEDIUM_CHANGED)
+ MAKE_CASE(SCSI_ADSENSE_BUS_RESET)
+ MAKE_CASE(SCSI_ADSENSE_INVALID_MEDIA)
+ MAKE_CASE(SCSI_ADSENSE_NO_MEDIA_IN_DEVICE)
+ MAKE_CASE(SCSI_ADSENSE_POSITION_ERROR)
+ MAKE_CASE(SCSI_ADSENSE_OPERATOR_REQUEST)
+ MAKE_CASE(SCSI_ADSENSE_FAILURE_PREDICTION_THRESHOLD_EXCEEDED)
+ MAKE_CASE(SCSI_ADSENSE_COPY_PROTECTION_FAILURE)
+ MAKE_CASE(SCSI_ADSENSE_VENDOR_UNIQUE)
+ MAKE_CASE(SCSI_ADSENSE_MUSIC_AREA)
+ MAKE_CASE(SCSI_ADSENSE_DATA_AREA)
+ MAKE_CASE(SCSI_ADSENSE_VOLUME_OVERFLOW)
+ }
+ }
+ }
+
+ return adSenseCodeStr;
+ }
+
+
+ char *DbgGetAdditionalSenseCodeQualifierStr(PSTORAGE_REQUEST_BLOCK_HEADER Srb)
+ {
+ char *adSenseCodeQualStr = "?";
+
+ if (Srb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID){
+ PVOID senseData;
+ UCHAR adSenseCode;
+ UCHAR adSenseCodeQual;
+ BOOLEAN validSense;
+
+ senseData = SrbGetSenseInfoBuffer(Srb);
+ NT_ASSERT(senseData);
+
+ validSense = ScsiGetSenseKeyAndCodes(senseData,
+ SrbGetSenseInfoBufferLength(Srb),
+ SCSI_SENSE_OPTIONS_FIXED_FORMAT_IF_UNKNOWN_FORMAT_INDICATED,
+ NULL,
+ &adSenseCode,
+ &adSenseCodeQual);
+ if (validSense) {
+ switch (adSenseCode){
+
+ #undef MAKE_CASE
+ #define MAKE_CASE(adSnsCodQual) case adSnsCodQual: adSenseCodeQualStr = #adSnsCodQual; break;
+
+ case SCSI_ADSENSE_LUN_NOT_READY:
+ switch (adSenseCodeQual){
+ MAKE_CASE(SCSI_SENSEQ_CAUSE_NOT_REPORTABLE)
+ MAKE_CASE(SCSI_SENSEQ_BECOMING_READY)
+ MAKE_CASE(SCSI_SENSEQ_INIT_COMMAND_REQUIRED)
+ MAKE_CASE(SCSI_SENSEQ_MANUAL_INTERVENTION_REQUIRED)
+ MAKE_CASE(SCSI_SENSEQ_FORMAT_IN_PROGRESS)
+ MAKE_CASE(SCSI_SENSEQ_REBUILD_IN_PROGRESS)
+ MAKE_CASE(SCSI_SENSEQ_RECALCULATION_IN_PROGRESS)
+ MAKE_CASE(SCSI_SENSEQ_OPERATION_IN_PROGRESS)
+ MAKE_CASE(SCSI_SENSEQ_LONG_WRITE_IN_PROGRESS)
+ }
+ break;
+ case SCSI_ADSENSE_NO_SENSE:
+ switch (adSenseCodeQual){
+ MAKE_CASE(SCSI_SENSEQ_FILEMARK_DETECTED)
+ MAKE_CASE(SCSI_SENSEQ_END_OF_MEDIA_DETECTED)
+ MAKE_CASE(SCSI_SENSEQ_SETMARK_DETECTED)
+ MAKE_CASE(SCSI_SENSEQ_BEGINNING_OF_MEDIA_DETECTED)
+ }
+ break;
+ case SCSI_ADSENSE_ILLEGAL_BLOCK:
+ switch (adSenseCodeQual){
+ MAKE_CASE(SCSI_SENSEQ_ILLEGAL_ELEMENT_ADDR)
+ }
+ break;
+ case SCSI_ADSENSE_POSITION_ERROR:
+ switch (adSenseCodeQual){
+ MAKE_CASE(SCSI_SENSEQ_DESTINATION_FULL)
+ MAKE_CASE(SCSI_SENSEQ_SOURCE_EMPTY)
+ }
+ break;
+ case SCSI_ADSENSE_INVALID_MEDIA:
+ switch (adSenseCodeQual){
+ MAKE_CASE(SCSI_SENSEQ_INCOMPATIBLE_MEDIA_INSTALLED)
+ MAKE_CASE(SCSI_SENSEQ_UNKNOWN_FORMAT)
+ MAKE_CASE(SCSI_SENSEQ_INCOMPATIBLE_FORMAT)
+ MAKE_CASE(SCSI_SENSEQ_CLEANING_CARTRIDGE_INSTALLED)
+ }
+ break;
+ case SCSI_ADSENSE_OPERATOR_REQUEST:
+ switch (adSenseCodeQual){
+ MAKE_CASE(SCSI_SENSEQ_STATE_CHANGE_INPUT)
+ MAKE_CASE(SCSI_SENSEQ_MEDIUM_REMOVAL)
+ MAKE_CASE(SCSI_SENSEQ_WRITE_PROTECT_ENABLE)
+ MAKE_CASE(SCSI_SENSEQ_WRITE_PROTECT_DISABLE)
+ }
+ break;
+ case SCSI_ADSENSE_COPY_PROTECTION_FAILURE:
+ switch (adSenseCodeQual){
+ MAKE_CASE(SCSI_SENSEQ_AUTHENTICATION_FAILURE)
+ MAKE_CASE(SCSI_SENSEQ_KEY_NOT_PRESENT)
+ MAKE_CASE(SCSI_SENSEQ_KEY_NOT_ESTABLISHED)
+ MAKE_CASE(SCSI_SENSEQ_READ_OF_SCRAMBLED_SECTOR_WITHOUT_AUTHENTICATION)
+ MAKE_CASE(SCSI_SENSEQ_MEDIA_CODE_MISMATCHED_TO_LOGICAL_UNIT)
+ MAKE_CASE(SCSI_SENSEQ_LOGICAL_UNIT_RESET_COUNT_ERROR)
+ }
+ break;
+ }
+ }
+ }
+
+ return adSenseCodeQualStr;
+ }
+
+
diff --git a/storage/class/classpnp/src/debug.h b/storage/class/classpnp/src/debug.h
new file mode 100644
index 00000000..bd2e04ba
--- /dev/null
+++ b/storage/class/classpnp/src/debug.h
@@ -0,0 +1,132 @@
+/*++
+
+Copyright (C) Microsoft Corporation, 1991 - 1999
+
+Module Name:
+
+ debug.h
+
+Abstract:
+
+
+Author:
+
+Environment:
+
+ kernel mode only
+
+Notes:
+
+
+Revision History:
+
+--*/
+
+#define DBGGETIOCTLSTR(_ioctl) DbgGetIoctlStr(_ioctl)
+#define DBGGETSCSIOPSTR(_pSrb) DbgGetScsiOpStr(_pSrb)
+#define DBGGETSRBSTATUSSTR(_pSrb) DbgGetSrbStatusStr(_pSrb)
+#define DBGGETSENSECODESTR(_pSrb) DbgGetSenseCodeStr(_pSrb)
+#define DBGGETADSENSECODESTR(_pSrb) DbgGetAdditionalSenseCodeStr(_pSrb)
+#define DBGGETADSENSEQUALIFIERSTR(_pSrb) DbgGetAdditionalSenseCodeQualifierStr(_pSrb)
+
+char *DbgGetIoctlStr(ULONG ioctl);
+char *DbgGetScsiOpStr(PSTORAGE_REQUEST_BLOCK_HEADER Srb);
+char *DbgGetSrbStatusStr(PSTORAGE_REQUEST_BLOCK_HEADER Srb);
+char *DbgGetSenseCodeStr(PSTORAGE_REQUEST_BLOCK_HEADER Srb);
+char *DbgGetAdditionalSenseCodeStr(PSTORAGE_REQUEST_BLOCK_HEADER Srb);
+char *DbgGetAdditionalSenseCodeQualifierStr(PSTORAGE_REQUEST_BLOCK_HEADER Srb);
+
+#if DBG
+
+ typedef struct _CLASSPNP_GLOBALS {
+
+ //
+ // whether or not to NT_ASSERT for lost irps
+ //
+
+ ULONG BreakOnLostIrps;
+ ULONG SecondsToWaitForIrps;
+
+ //
+ // use a buffered debug print to help
+ // catch timing issues that do not
+ // reproduce with std debugprints enabled
+ //
+
+ ULONG UseBufferedDebugPrint;
+ ULONG UseDelayedRetry;
+
+ //
+ // the next four are the buffered printing support
+ // (currently unimplemented) and require the spinlock
+ // to use
+ //
+
+ ULONG Index; // index into buffer
+ KSPIN_LOCK SpinLock;
+ PUCHAR Buffer; // requires spinlock to access
+ ULONG NumberOfBuffers; // number of buffers available
+ SIZE_T EachBufferSize; // size of each buffer
+
+ //
+ // interlocked variables to initialize
+ // this data only once
+ //
+
+ LONG Initializing;
+ LONG Initialized;
+
+ } CLASSPNP_GLOBALS, *PCLASSPNP_GLOBALS;
+
+
+ //
+ // Define a structure used to capture stack traces when we
+ // get an access request and the disks are powered off. This
+ // will help us determine who's causing disk respins.
+ //
+
+ //
+ // How many stack frames to capture each time?
+ //
+ #define DISK_SPINUP_BACKTRACE_LENGTH (0x18)
+
+ //
+ // How many stack traces can we capture before
+ // out buffer wraps? (needs to be power of 2)
+ //
+ #define NUMBER_OF_DISK_SPINUP_TRACES (0x10)
+
+ typedef struct _DISK_SPINUP_TRACES {
+
+ LARGE_INTEGER TimeStamp; // timestamp of the spinup event.
+ PVOID StackTrace[DISK_SPINUP_BACKTRACE_LENGTH]; // Holds stack trace
+ } DISK_SPINUP_TRACES, *PDISK_SPINUP_TRACES;
+
+
+ #define DBGCHECKRETURNEDPKT(_pkt) DbgCheckReturnedPkt(_pkt)
+ #define DBGLOGSENDPACKET(_pkt) DbgLogSendPacket(_pkt)
+ #define DBGLOGRETURNPACKET(_pkt) DbgLogReturnPacket(_pkt)
+ #define DBGLOGFLUSHINFO(_fdoData, _isIO, _isFUA, _isFlush) DbgLogFlushInfo(_fdoData, _isIO, _isFUA, _isFlush)
+
+ VOID ClasspInitializeDebugGlobals();
+ VOID DbgCheckReturnedPkt(TRANSFER_PACKET *Pkt);
+ VOID DbgLogSendPacket(TRANSFER_PACKET *Pkt);
+ VOID DbgLogReturnPacket(TRANSFER_PACKET *Pkt);
+ VOID DbgLogFlushInfo(PCLASS_PRIVATE_FDO_DATA FdoData, BOOLEAN IsIO, BOOLEAN IsFUA, BOOLEAN IsFlush);
+ VOID SnapDiskStartup(VOID);
+ extern CLASSPNP_GLOBALS ClasspnpGlobals;
+ extern LONG ClassDebug;
+ extern BOOLEAN DebugTrapOnWarn;
+
+#else
+
+ #define ClasspInitializeDebugGlobals()
+ #define SnapDiskStartup()
+
+ #define DBGCHECKRETURNEDPKT(_pkt)
+ #define DBGLOGSENDPACKET(_pkt)
+ #define DBGLOGRETURNPACKET(_pkt)
+ #define DBGLOGFLUSHINFO(_fdoData, _isIO, _isFUA, _isFlush)
+
+#endif
+
diff --git a/storage/class/classpnp/src/dictlib.c b/storage/class/classpnp/src/dictlib.c
new file mode 100644
index 00000000..90a67117
--- /dev/null
+++ b/storage/class/classpnp/src/dictlib.c
@@ -0,0 +1,218 @@
+/*++
+
+Copyright (C) Microsoft Corporation, 1990 - 1999
+
+Module Name:
+
+ dictlib.c
+
+Abstract:
+
+ Support library for maintaining a dictionary list (list of objects
+ referenced by a key value).
+
+Environment:
+
+ kernel mode only
+
+Notes:
+
+ This module generates a static library
+
+Revision History:
+
+--*/
+
+#include <ntddk.h>
+#include <classpnp.h>
+
+#define DICTIONARY_SIGNATURE 'tciD'
+
+#pragma warning(push)
+#pragma warning(disable:4200) // nonstandard extension used : zero-sized array in struct/union
+struct _DICTIONARY_HEADER {
+ PDICTIONARY_HEADER Next;
+ ULONGLONG Key;
+ UCHAR Data[0];
+};
+#pragma warning(pop)
+
+struct _DICTIONARY_HEADER;
+typedef struct _DICTIONARY_HEADER DICTIONARY_HEADER, *PDICTIONARY_HEADER;
+
+
+VOID
+InitializeDictionary(
+ IN PDICTIONARY Dictionary
+ )
+{
+ RtlZeroMemory(Dictionary, sizeof(DICTIONARY));
+ Dictionary->Signature = DICTIONARY_SIGNATURE;
+ KeInitializeSpinLock(&Dictionary->SpinLock);
+ return;
+}
+
+
+BOOLEAN
+TestDictionarySignature(
+ IN PDICTIONARY Dictionary
+ )
+{
+ return Dictionary->Signature == DICTIONARY_SIGNATURE;
+}
+
+NTSTATUS
+AllocateDictionaryEntry(
+ IN PDICTIONARY Dictionary,
+ IN ULONGLONG Key,
+ _In_range_(0, sizeof(FILE_OBJECT_EXTENSION)) IN ULONG Size,
+ IN ULONG Tag,
+ OUT PVOID *Entry
+ )
+{
+ PDICTIONARY_HEADER header;
+ KIRQL oldIrql;
+ PDICTIONARY_HEADER *entry;
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ *Entry = NULL;
+
+ header = ExAllocatePoolWithTag(NonPagedPoolNx,
+ Size + sizeof(DICTIONARY_HEADER),
+ Tag);
+
+ if(header == NULL) {
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ RtlZeroMemory(header, sizeof(DICTIONARY_HEADER) + Size);
+ header->Key = Key;
+
+ //
+ // Find the correct location for this entry in the dictionary.
+ //
+
+ KeAcquireSpinLock(&(Dictionary->SpinLock), &oldIrql);
+
+ TRY {
+
+ entry = &(Dictionary->List);
+
+ while(*entry != NULL) {
+ if((*entry)->Key == Key) {
+
+ //
+ // Dictionary must have unique keys.
+ //
+
+ status = STATUS_OBJECT_NAME_COLLISION;
+ LEAVE;
+
+ } else if ((*entry)->Key < Key) {
+
+ //
+ // We will go ahead and insert the key in here.
+ //
+ break;
+ } else {
+ entry = &((*entry)->Next);
+ }
+ }
+
+ //
+ // If we make it here then we will go ahead and do the insertion.
+ //
+
+ header->Next = *entry;
+ *entry = header;
+
+ } FINALLY {
+ KeReleaseSpinLock(&(Dictionary->SpinLock), oldIrql);
+
+ if(!NT_SUCCESS(status)) {
+ FREE_POOL(header);
+ } else {
+ *Entry = (PVOID) header->Data;
+ }
+ }
+ return status;
+}
+
+
+PVOID
+GetDictionaryEntry(
+ IN PDICTIONARY Dictionary,
+ IN ULONGLONG Key
+ )
+{
+ PDICTIONARY_HEADER entry;
+ PVOID data;
+ KIRQL oldIrql;
+
+
+ data = NULL;
+
+ KeAcquireSpinLock(&(Dictionary->SpinLock), &oldIrql);
+
+ entry = Dictionary->List;
+ while (entry != NULL) {
+
+ if (entry->Key == Key) {
+ data = entry->Data;
+ break;
+ } else {
+ entry = entry->Next;
+ }
+ }
+
+ KeReleaseSpinLock(&(Dictionary->SpinLock), oldIrql);
+
+ return data;
+}
+
+
+VOID
+FreeDictionaryEntry(
+ IN PDICTIONARY Dictionary,
+ IN PVOID Entry
+ )
+{
+ PDICTIONARY_HEADER header;
+ PDICTIONARY_HEADER *entry;
+ KIRQL oldIrql;
+ BOOLEAN found;
+
+ found = FALSE;
+ header = CONTAINING_RECORD(Entry, DICTIONARY_HEADER, Data);
+
+ KeAcquireSpinLock(&(Dictionary->SpinLock), &oldIrql);
+
+ entry = &(Dictionary->List);
+ while(*entry != NULL) {
+
+ if(*entry == header) {
+ *entry = header->Next;
+ found = TRUE;
+ break;
+ } else {
+ entry = &(*entry)->Next;
+ }
+ }
+
+ KeReleaseSpinLock(&(Dictionary->SpinLock), oldIrql);
+
+ //
+ // calling this w/an invalid pointer invalidates the dictionary system,
+ // so NT_ASSERT() that we never try to Free something not in the list
+ //
+
+ NT_ASSERT(found);
+ if (found) {
+ FREE_POOL(header);
+ }
+
+ return;
+
+}
+
diff --git a/storage/class/classpnp/src/dispatch.c b/storage/class/classpnp/src/dispatch.c
new file mode 100644
index 00000000..c0296d34
--- /dev/null
+++ b/storage/class/classpnp/src/dispatch.c
@@ -0,0 +1,131 @@
+/*++
+
+Copyright (C) Microsoft Corporation, 1991 - 2005
+
+Module Name:
+
+ dispatch.c
+
+Abstract:
+
+ Code to support multiple dispatch tables.
+
+Environment:
+
+ kernel mode only
+
+Notes:
+
+
+Revision History:
+
+--*/
+
+#include "classp.h"
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, ClassInitializeDispatchTables)
+#endif
+
+DRIVER_DISPATCH ClassDispatchUnimplemented;
+
+//
+// Routines start
+//
+
+
+VOID
+ClassInitializeDispatchTables(
+ PCLASS_DRIVER_EXTENSION DriverExtension
+ )
+{
+ ULONG idx;
+
+ PAGED_CODE();
+
+ //
+ // Initialize the standard device dispatch table
+ //
+
+ for (idx = 0; idx <= IRP_MJ_MAXIMUM_FUNCTION; idx++) {
+ DriverExtension->DeviceMajorFunctionTable[idx] = ClassDispatchUnimplemented;
+ }
+
+ DriverExtension->DeviceMajorFunctionTable[IRP_MJ_CREATE] = ClassCreateClose;
+ DriverExtension->DeviceMajorFunctionTable[IRP_MJ_CLOSE] = ClassCreateClose;
+ DriverExtension->DeviceMajorFunctionTable[IRP_MJ_READ] = ClassReadWrite;
+ DriverExtension->DeviceMajorFunctionTable[IRP_MJ_WRITE] = ClassReadWrite;
+ DriverExtension->DeviceMajorFunctionTable[IRP_MJ_DEVICE_CONTROL] = ClassDeviceControlDispatch;
+ DriverExtension->DeviceMajorFunctionTable[IRP_MJ_SCSI] = ClassInternalIoControl;
+ DriverExtension->DeviceMajorFunctionTable[IRP_MJ_SHUTDOWN] = ClassShutdownFlush;
+ DriverExtension->DeviceMajorFunctionTable[IRP_MJ_FLUSH_BUFFERS] = ClassShutdownFlush;
+ DriverExtension->DeviceMajorFunctionTable[IRP_MJ_PNP] = ClassDispatchPnp;
+ DriverExtension->DeviceMajorFunctionTable[IRP_MJ_POWER] = ClassDispatchPower;
+ DriverExtension->DeviceMajorFunctionTable[IRP_MJ_SYSTEM_CONTROL] = ClassSystemControl;
+
+
+ return;
+}
+
+
+NTSTATUS
+ClassGlobalDispatch(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp
+ )
+
+{
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ // Code Analysis cannot analyze the code paths specific to clients.
+ _Analysis_assume_(FALSE);
+ return (commonExtension->DispatchTable[irpStack->MajorFunction])(DeviceObject, Irp);
+
+}
+
+NTSTATUS
+ClassDispatchUnimplemented(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp
+ )
+/*++
+
+Routine Description:
+
+ This function is the default dispatch routine. Its
+ responsibility is simply to set the status in the packet to indicate
+ that the operation requested is invalid for this device type, and then
+ complete the packet.
+
+Arguments:
+
+ DeviceObject - Specifies the device object for which this request is
+ bound. Ignored by this routine.
+
+ Irp - Specifies the address of the I/O Request Packet (IRP) for this
+ request.
+
+Return Value:
+
+ The final status is always STATUS_INVALID_DEVICE_REQUEST.
+
+
+--*/
+
+{
+ UNREFERENCED_PARAMETER( DeviceObject );
+
+ //
+ // Simply store the appropriate status, complete the request, and return
+ // the same status stored in the packet.
+ //
+
+ if ((IoGetCurrentIrpStackLocation(Irp))->MajorFunction == IRP_MJ_POWER) {
+ PoStartNextPowerIrp(Irp);
+ }
+ Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
+ IoCompleteRequest( Irp, IO_NO_INCREMENT );
+ return STATUS_INVALID_DEVICE_REQUEST;
+}
+
+
diff --git a/storage/class/classpnp/src/history.c b/storage/class/classpnp/src/history.c
new file mode 100644
index 00000000..c8505774
--- /dev/null
+++ b/storage/class/classpnp/src/history.c
@@ -0,0 +1,146 @@
+/*++
+
+
+Copyright (C) Microsoft Corporation, 1991 - 1999
+
+Module Name:
+
+ history.c
+
+Abstract:
+
+ Packet history routines for CLASSPNP
+
+Environment:
+
+ kernel mode only
+
+Notes:
+
+
+Revision History:
+
+--*/
+
+#include "classp.h"
+#include "debug.h"
+
+#ifdef DEBUG_USE_WPP
+#include "history.tmh"
+#endif
+
+//#ifdef ALLOC_PRAGMA
+// #pragma alloc_text(PAGE, InitializeTransferPackets)
+//#endif
+
+VOID HistoryInitializeRetryLogs(_Out_ PSRB_HISTORY History, ULONG HistoryCount) {
+ ULONG tmpSize = HistoryCount * sizeof(SRB_HISTORY_ITEM);
+ tmpSize += sizeof(SRB_HISTORY) - sizeof(SRB_HISTORY_ITEM);
+ RtlZeroMemory(History, tmpSize);
+ History->TotalHistoryCount = HistoryCount;
+ return;
+}
+
+
+VOID HistoryLogSendPacket(TRANSFER_PACKET * Pkt) {
+
+ PSRB_HISTORY history;
+ PSRB_HISTORY_ITEM item;
+
+ NT_ASSERT( Pkt->RetryHistory != NULL );
+ history = Pkt->RetryHistory;
+ // sending a packet implies a new history unit is to be used.
+ NT_ASSERT( history->UsedHistoryCount <= history->TotalHistoryCount );
+
+ // if already all used up, request class driver to remove at least one history unit
+ if (history->UsedHistoryCount == history->TotalHistoryCount )
+ {
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Pkt->Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
+ NT_ASSERT( fdoData->InterpretSenseInfo != NULL );
+ NT_ASSERT( fdoData->InterpretSenseInfo->Compress != NULL );
+ fdoData->InterpretSenseInfo->Compress( fdoExtension->DeviceObject, history );
+ NT_ASSERT( history->UsedHistoryCount < history->TotalHistoryCount );
+ }
+
+ // thus, since we are about to increment the count, it must now be less...
+ NT_ASSERT( history->UsedHistoryCount < history->TotalHistoryCount );
+
+ // increment the number of history units in use
+ history->UsedHistoryCount++;
+
+ // determine index to use
+ item = &( history->History[ history->UsedHistoryCount-1 ] );
+
+ // zero out the history item
+ RtlZeroMemory(item, sizeof(SRB_HISTORY_ITEM));
+
+ // Query the tick count and store in the history
+ KeQueryTickCount(&item->TickCountSent);
+ return;
+}
+
+VOID HistoryLogReturnedPacket(TRANSFER_PACKET *Pkt) {
+
+ PSRB_HISTORY history;
+ PSRB_HISTORY_ITEM item;
+ UCHAR senseSize;
+ PVOID senseInfoBuffer;
+ UCHAR senseInfoBufferLength;
+ SENSE_DATA convertedSenseBuffer = {0};
+ BOOLEAN validSense = TRUE;
+
+ NT_ASSERT( Pkt->RetryHistory != NULL );
+ history = Pkt->RetryHistory;
+ NT_ASSERT( history->UsedHistoryCount <= history->TotalHistoryCount );
+ item = &( history->History[ history->UsedHistoryCount-1 ] );
+
+ // Query the tick count and store in the history
+ KeQueryTickCount(&item->TickCountCompleted);
+
+ // Copy the SRB Status...
+ item->SrbStatus = Pkt->Srb->SrbStatus;
+
+ //
+ // Process sense data
+ //
+
+ senseInfoBuffer = ClasspTransferPacketGetSenseInfoBuffer(Pkt);
+ senseInfoBufferLength = ClasspTransferPacketGetSenseInfoBufferLength(Pkt);
+
+ if (IsDescriptorSenseDataFormat(senseInfoBuffer)) {
+
+ validSense = ScsiConvertToFixedSenseFormat(senseInfoBuffer,
+ senseInfoBufferLength,
+ (PVOID)&convertedSenseBuffer,
+ sizeof(convertedSenseBuffer));
+
+ if (validSense) {
+ senseInfoBuffer = (PVOID)&convertedSenseBuffer;
+ senseInfoBufferLength = sizeof(convertedSenseBuffer);
+ }
+ }
+
+ RtlZeroMemory(&(item->NormalizedSenseData), sizeof(item->NormalizedSenseData));
+
+ if (validSense) {
+
+ // Determine the amount of valid sense data
+
+ if (!ScsiGetTotalSenseByteCountIndicated(senseInfoBuffer,
+ senseInfoBufferLength,
+ &senseSize)) {
+ senseSize = senseInfoBufferLength;
+ }
+
+ // Normalize the sense data copy in the history
+ senseSize = min(senseSize, sizeof(item->NormalizedSenseData));
+ RtlCopyMemory(&(item->NormalizedSenseData),
+ senseInfoBuffer,
+ senseSize
+ );
+ }
+
+ return;
+}
+
diff --git a/storage/class/classpnp/src/lock.c b/storage/class/classpnp/src/lock.c
new file mode 100644
index 00000000..1b5a8862
--- /dev/null
+++ b/storage/class/classpnp/src/lock.c
@@ -0,0 +1,550 @@
+/*++
+
+Copyright (C) Microsoft Corporation, 1990 - 1998
+
+Module Name:
+
+ lock.c
+
+Abstract:
+
+ This is the NT SCSI port driver.
+
+Environment:
+
+ kernel mode only
+
+Notes:
+
+ This module is a driver dll for scsi miniports.
+
+Revision History:
+
+--*/
+
+#include "classp.h"
+#include "debug.h"
+
+#ifdef DEBUG_USE_WPP
+#include "lock.tmh"
+#endif
+
+
+LONG LockHighWatermark = 0;
+LONG LockLowWatermark = 0;
+LONG MaxLockedMinutes = 5;
+
+//
+// Structure used for tracking remove lock allocations in checked builds
+//
+typedef struct _REMOVE_TRACKING_BLOCK {
+ PVOID Tag;
+ LARGE_INTEGER TimeLocked;
+ PCSTR File;
+ ULONG Line;
+} REMOVE_TRACKING_BLOCK, *PREMOVE_TRACKING_BLOCK;
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+Classpnp RemoveLockRundown
+
+RemoveLockRundown is a cacheaware rundown protection for the classpnp device object. While this
+rundown protection is held successfully, the caller can assume that no pending pnp REMOVE
+requests will be completed.
+
+The RemoveLockRundown is a replacement of the original RemoveLock to improve the scalability.
+For backward compatibility, we still keep the RemoveLock field in the device common extension structure.
+However, the old RemoveLock is only being used in the DBG build.
+
+The usage of the RemoveLockRundown is slightly different from the normal rundown protection usage.
+The RemoveLockRundown is acquired via ClassAcquireRemoveLockEx() function
+and released via ClassReleaseRemoveLock() function. Usually, we bail out when the acquisition
+of rundown protection fails (calls to ExAcquireRundownProtectionCacheAware returns FALSE) and
+will not release the rundown protection in acquisition failure. For the RemoveLockRundown,
+the caller will always call ClassAcquireRemoveLockEx() and ClassReleaseRemoveLock() in a pair no
+matter the return value of ClassAcquireRemoveLockEx(). Therefore, a thread may still call
+ClassReleaseRemoveLock() even the previous acquisition RemoveLockRundown protection failed.
+
+To deal with the previous acquisition failure case, we introduced a new field RemoveLockFailAcquire
+as a counter for rundown acquisition failures. In the ClassReleaseRemoveLock() function, we only
+release the rundown protection when this counter is decremented to zero. Since the change of RemoveLockFailAcquire
+and release rundown protection is not protected by a lock as an atomic operation, we use a while loop over
+InterlockedCompareExchange operation to make sure when we release the rundown protection, this counter is
+actually zero.
+
+--*/
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassAcquireRemoveLockEx()
+
+Routine Description:
+
+ This routine is called to acquire the remove lock on the device object.
+ While the lock is held, the caller can assume that no pending pnp REMOVE
+ requests will be completed.
+
+ The lock should be acquired immediately upon entering a dispatch routine.
+ It should also be acquired before creating any new reference to the
+ device object if there's a chance of releasing the reference before the
+ new one is done.
+
+ This routine will return TRUE if the lock was successfully acquired or
+ FALSE if it cannot be because the device object has already been removed.
+
+Arguments:
+
+ DeviceObject - the device object to lock
+
+ Tag - Used for tracking lock allocation and release. If an irp is
+ specified when acquiring the lock then the same Tag must be
+ used to release the lock before the Tag is completed.
+
+Return Value:
+
+ The value of the IsRemoved flag in the device extension. If this is
+ non-zero then the device object has received a Remove irp and non-cleanup
+ IRP's should fail.
+
+ If the value is REMOVE_COMPLETE, the caller should not even release the
+ lock.
+
+--*/
+ULONG
+ClassAcquireRemoveLockEx(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PVOID Tag,
+ _In_ PCSTR File,
+ _In_ ULONG Line
+ )
+// This function implements the acquisition of Tag
+#pragma warning(suppress:28104)
+{
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+ BOOLEAN rundownAcquired;
+ PEX_RUNDOWN_REF_CACHE_AWARE removeLockRundown = NULL;
+
+ //
+ // Grab the remove lock
+ //
+
+ #if DBG
+
+ LONG lockValue;
+
+ lockValue = InterlockedIncrement(&commonExtension->RemoveLock);
+
+
+ TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_LOCK, "ClassAcquireRemoveLock: "
+ "Acquired for Object %p & irp %p - count is %d\n",
+ DeviceObject, Tag, lockValue));
+
+ NT_ASSERTMSG("ClassAcquireRemoveLock - lock value was negative : ",
+ (lockValue > 0));
+
+ NT_ASSERTMSG("RemoveLock increased to meet LockHighWatermark",
+ ((LockHighWatermark == 0) ||
+ (lockValue != LockHighWatermark)));
+
+ if (commonExtension->IsRemoved != REMOVE_COMPLETE) {
+ PRTL_GENERIC_TABLE removeTrackingList = NULL;
+ REMOVE_TRACKING_BLOCK trackingBlock;
+ PREMOVE_TRACKING_BLOCK insertedTrackingBlock = NULL;
+ BOOLEAN newElement = FALSE;
+
+ KIRQL oldIrql;
+
+ trackingBlock.Tag = Tag;
+
+ trackingBlock.File = File;
+ trackingBlock.Line = Line;
+
+ KeQueryTickCount((&trackingBlock.TimeLocked));
+
+ KeAcquireSpinLock(&commonExtension->RemoveTrackingSpinlock,
+ &oldIrql);
+
+ removeTrackingList = commonExtension->RemoveTrackingList;
+
+ if (removeTrackingList != NULL) {
+ insertedTrackingBlock = RtlInsertElementGenericTable(removeTrackingList,
+ &trackingBlock,
+ sizeof(REMOVE_TRACKING_BLOCK),
+ &newElement);
+ }
+
+ if (insertedTrackingBlock != NULL) {
+ if (!newElement) {
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_LOCK, ">>>>>ClassAcquireRemoveLock: "
+ "already tracking Tag %p\n", Tag));
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_LOCK, ">>>>>ClassAcquireRemoveLock: "
+ "acquired in file %s on line %d\n",
+ insertedTrackingBlock->File, insertedTrackingBlock->Line));
+// NT_ASSERT(FALSE);
+
+ }
+ } else {
+ commonExtension->RemoveTrackingUntrackedCount++;
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_LOCK, ">>>>>ClassAcquireRemoveLock: "
+ "Cannot track Tag %p - currently %d untracked requsts\n",
+ Tag, commonExtension->RemoveTrackingUntrackedCount));
+ }
+
+ KeReleaseSpinLock(&commonExtension->RemoveTrackingSpinlock, oldIrql);
+ }
+ #else
+
+ UNREFERENCED_PARAMETER(Tag);
+ UNREFERENCED_PARAMETER(File);
+ UNREFERENCED_PARAMETER(Line);
+
+ #endif
+
+ removeLockRundown = (PEX_RUNDOWN_REF_CACHE_AWARE)
+ ((PCHAR)commonExtension->PrivateCommonData + sizeof(CLASS_PRIVATE_COMMON_DATA));
+ rundownAcquired = ExAcquireRundownProtectionCacheAware(removeLockRundown);
+ if (!rundownAcquired) {
+ InterlockedIncrement((volatile LONG*) &(commonExtension->PrivateCommonData->RemoveLockFailAcquire));
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_LOCK,
+ "ClassAcquireRemoveLockEx: RemoveLockRundown acquisition failed"
+ "RemoveLockFailAcquire = %d\n",
+ commonExtension->PrivateCommonData->RemoveLockFailAcquire));
+ }
+
+ return (commonExtension->IsRemoved);
+}
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassReleaseRemoveLock()
+
+Routine Description:
+
+ This routine is called to release the remove lock on the device object. It
+ must be called when finished using a previously locked reference to the
+ device object. If an Tag was specified when acquiring the lock then the
+ same Tag must be specified when releasing the lock.
+
+ When the lock count reduces to zero, this routine will signal the waiting
+ remove Tag to delete the device object. As a result the DeviceObject
+ pointer should not be used again once the lock has been released.
+
+Arguments:
+
+ DeviceObject - the device object to lock
+
+ Tag - The irp (if any) specified when acquiring the lock. This is used
+ for lock tracking purposes
+
+Return Value:
+
+ none
+
+--*/
+VOID
+ClassReleaseRemoveLock(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_opt_ PIRP Tag
+ )
+// This function implements the release of Tag
+#pragma warning(suppress:28103)
+{
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+ LONG lockValue;
+ LONG oldValue;
+ PEX_RUNDOWN_REF_CACHE_AWARE removeLockRundown = NULL;
+
+ #if DBG
+ PRTL_GENERIC_TABLE removeTrackingList = NULL;
+ REMOVE_TRACKING_BLOCK searchDataBlock;
+
+ BOOLEAN found = FALSE;
+
+ BOOLEAN isRemoved = (commonExtension->IsRemoved == REMOVE_COMPLETE);
+
+ KIRQL oldIrql;
+
+ if (isRemoved) {
+ TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_LOCK, "ClassReleaseRemoveLock: REMOVE_COMPLETE set; this should never happen"));
+ InterlockedDecrement(&(commonExtension->RemoveLock));
+ return;
+ }
+
+ KeAcquireSpinLock(&commonExtension->RemoveTrackingSpinlock,
+ &oldIrql);
+
+ removeTrackingList = commonExtension->RemoveTrackingList;
+
+ if (removeTrackingList != NULL) {
+ searchDataBlock.Tag = Tag;
+ found = RtlDeleteElementGenericTable(removeTrackingList, &searchDataBlock);
+ }
+
+ if (!found) {
+ if(commonExtension->RemoveTrackingUntrackedCount == 0) {
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_LOCK, ">>>>>ClassReleaseRemoveLock: "
+ "Couldn't find Tag %p in the lock tracking list\n", Tag));
+ //
+ // This might happen if the device is being removed and the tracking list
+ // has already been freed. Don't assert if that is the case.
+ //
+ NT_ASSERT((removeTrackingList == NULL) && (commonExtension->IsRemoved != NO_REMOVE));
+ } else {
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_LOCK, ">>>>>ClassReleaseRemoveLock: "
+ "Couldn't find Tag %p in the lock tracking list - "
+ "may be one of the %d untracked requests still outstanding\n",
+ Tag, commonExtension->RemoveTrackingUntrackedCount));
+
+ commonExtension->RemoveTrackingUntrackedCount--;
+ NT_ASSERT(commonExtension->RemoveTrackingUntrackedCount >= 0);
+ }
+ }
+
+ KeReleaseSpinLock(&commonExtension->RemoveTrackingSpinlock,
+ oldIrql);
+
+ lockValue = InterlockedDecrement(&commonExtension->RemoveLock);
+
+ TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_LOCK, "ClassReleaseRemoveLock: "
+ "Released for Object %p & irp %p - count is %d\n",
+ DeviceObject, Tag, lockValue));
+
+ NT_ASSERT(lockValue >= 0);
+
+ NT_ASSERTMSG("RemoveLock decreased to meet LockLowWatermark",
+ ((LockLowWatermark == 0) || !(lockValue == LockLowWatermark)));
+
+ if (lockValue == 0) {
+
+ NT_ASSERT(commonExtension->IsRemoved);
+
+ //
+ // The device needs to be removed. Signal the remove event
+ // that it's safe to go ahead.
+ //
+
+ TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_LOCK, "ClassReleaseRemoveLock: "
+ "Release for object %p & irp %p caused lock to go to zero\n",
+ DeviceObject, Tag));
+
+ }
+
+ #else
+
+ UNREFERENCED_PARAMETER(Tag);
+
+ #endif
+
+ //
+ // Decrement the RemoveLockFailAcquire by 1 when RemoveLockFailAcquire is non-zero.
+ // Release the RemoveLockRundown only when RemoveLockFailAcquire is zero.
+ //
+
+ oldValue = 1;
+ lockValue = commonExtension->PrivateCommonData->RemoveLockFailAcquire;
+ while (lockValue != 0) {
+ oldValue =
+ InterlockedCompareExchange((volatile LONG *) &commonExtension->PrivateCommonData->RemoveLockFailAcquire,
+ lockValue - 1,
+ lockValue);
+
+ if (oldValue == lockValue) {
+ break;
+ }
+
+ lockValue = oldValue;
+ }
+
+ if (lockValue == 0) {
+ removeLockRundown = (PEX_RUNDOWN_REF_CACHE_AWARE)
+ ((PCHAR)commonExtension->PrivateCommonData + sizeof(CLASS_PRIVATE_COMMON_DATA));
+ ExReleaseRundownProtectionCacheAware(removeLockRundown);
+ }
+
+ return;
+}
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassCompleteRequest()
+
+Routine Description:
+
+ This routine is a wrapper around (and should be used instead of)
+ IoCompleteRequest. It is used primarily for debugging purposes.
+ The routine will assert if the Irp being completed is still holding
+ the release lock.
+
+Arguments:
+
+ DeviceObject - the device object that was handling this request
+
+ Irp - the irp to be completed by IoCompleteRequest
+
+ PriorityBoost - the priority boost to pass to IoCompleteRequest
+
+Return Value:
+
+ none
+
+--*/
+VOID
+ClassCompleteRequest(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PIRP Irp,
+ _In_ CCHAR PriorityBoost
+ )
+{
+ #if DBG
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+
+ PRTL_GENERIC_TABLE removeTrackingList = NULL;
+ REMOVE_TRACKING_BLOCK searchDataBlock;
+ PREMOVE_TRACKING_BLOCK foundTrackingBlock;
+
+ KIRQL oldIrql;
+
+ KeAcquireSpinLock(&commonExtension->RemoveTrackingSpinlock, &oldIrql);
+
+ removeTrackingList = commonExtension->RemoveTrackingList;
+
+ if (removeTrackingList != NULL)
+ {
+ searchDataBlock.Tag = Irp;
+
+ foundTrackingBlock = RtlLookupElementGenericTable(removeTrackingList, &searchDataBlock);
+
+ if(foundTrackingBlock != NULL) {
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_LOCK, ">>>>>ClassCompleteRequest: "
+ "Irp %p completed while still holding the remove lock\n", Irp));
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_LOCK, ">>>>>ClassCompleteRequest: "
+ "Lock acquired in file %s on line %d\n",
+ foundTrackingBlock->File, foundTrackingBlock->Line));
+ NT_ASSERT(FALSE);
+ }
+ }
+
+ KeReleaseSpinLock(&commonExtension->RemoveTrackingSpinlock, oldIrql);
+ #endif
+
+
+ UNREFERENCED_PARAMETER(DeviceObject);
+
+ IoCompleteRequest(Irp, PriorityBoost);
+ return;
+} // end ClassCompleteRequest()
+
+
+RTL_GENERIC_COMPARE_RESULTS
+RemoveTrackingCompareRoutine(
+ PRTL_GENERIC_TABLE Table,
+ PVOID FirstStruct,
+ PVOID SecondStruct
+ )
+{
+ PVOID tag1, tag2;
+
+ UNREFERENCED_PARAMETER(Table);
+
+ tag1 = ((PREMOVE_TRACKING_BLOCK)FirstStruct)->Tag;
+ tag2 = ((PREMOVE_TRACKING_BLOCK)SecondStruct)->Tag;
+
+ if (tag1 < tag2)
+ {
+ return GenericLessThan;
+ }
+ else if (tag1 > tag2)
+ {
+ return GenericGreaterThan;
+ }
+
+ return GenericEqual;
+}
+
+PVOID
+RemoveTrackingAllocateRoutine(
+ PRTL_GENERIC_TABLE Table,
+ CLONG ByteSize
+ )
+{
+ UNREFERENCED_PARAMETER(Table);
+
+ return ExAllocatePoolWithTag(NonPagedPoolNx, ByteSize, CLASS_TAG_LOCK_TRACKING);
+}
+
+VOID
+RemoveTrackingFreeRoutine(
+ PRTL_GENERIC_TABLE Table,
+ PVOID Buffer
+ )
+{
+ UNREFERENCED_PARAMETER(Table);
+
+ FREE_POOL(Buffer);
+}
+
+VOID
+ClasspInitializeRemoveTracking(
+ _In_ PDEVICE_OBJECT DeviceObject
+ )
+{
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+
+ #if DBG
+ KeInitializeSpinLock(&commonExtension->RemoveTrackingSpinlock);
+
+ commonExtension->RemoveTrackingList = ExAllocatePoolWithTag(NonPagedPoolNx, sizeof(RTL_GENERIC_TABLE), CLASS_TAG_LOCK_TRACKING);
+
+ if (commonExtension->RemoveTrackingList != NULL)
+ {
+ RtlInitializeGenericTable(commonExtension->RemoveTrackingList,
+ RemoveTrackingCompareRoutine,
+ RemoveTrackingAllocateRoutine,
+ RemoveTrackingFreeRoutine,
+ NULL);
+ }
+ #else
+
+ UNREFERENCED_PARAMETER(DeviceObject);
+
+ commonExtension->RemoveTrackingSpinlock = (ULONG_PTR) -1;
+ commonExtension->RemoveTrackingList = NULL;
+ #endif
+}
+
+VOID
+ClasspUninitializeRemoveTracking(
+ _In_ PDEVICE_OBJECT DeviceObject
+ )
+{
+ #if DBG
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+ PRTL_GENERIC_TABLE removeTrackingList = commonExtension->RemoveTrackingList;
+
+ ASSERTMSG("Removing the device while still holding remove locks",
+ commonExtension->RemoveTrackingUntrackedCount == 0 &&
+ removeTrackingList != NULL ? RtlNumberGenericTableElements(removeTrackingList) == 0 : TRUE);
+
+ if (removeTrackingList != NULL)
+ {
+ KIRQL oldIrql;
+ KeAcquireSpinLock(&commonExtension->RemoveTrackingSpinlock, &oldIrql);
+
+ FREE_POOL(removeTrackingList);
+ commonExtension->RemoveTrackingList = NULL;
+
+ KeReleaseSpinLock(&commonExtension->RemoveTrackingSpinlock, oldIrql);
+ }
+
+ #else
+
+ UNREFERENCED_PARAMETER(DeviceObject);
+ #endif
+}
+
+
+
+
diff --git a/storage/class/classpnp/src/obsolete.c b/storage/class/classpnp/src/obsolete.c
new file mode 100644
index 00000000..aeeef81d
--- /dev/null
+++ b/storage/class/classpnp/src/obsolete.c
@@ -0,0 +1,1125 @@
+/*++
+
+Copyright (C) Microsoft Corporation, 1991 - 2010
+
+Module Name:
+
+ obsolete.c
+
+Abstract:
+
+ THESE ARE EXPORTED CLASSPNP FUNCTIONS (and their subroutines)
+ WHICH ARE NOW OBSOLETE.
+ BUT WE NEED TO KEEP THEM AROUND FOR LEGACY REASONS.
+
+Environment:
+
+ kernel mode only
+
+Notes:
+
+
+Revision History:
+
+--*/
+
+#include "classp.h"
+#include "debug.h"
+
+#ifdef DEBUG_USE_WPP
+#include "obsolete.tmh"
+#endif
+
+PIRP ClassRemoveCScanList(IN PCSCAN_LIST List);
+VOID ClasspInitializeCScanList(IN PCSCAN_LIST List);
+
+#ifdef ALLOC_PRAGMA
+ #pragma alloc_text(PAGE, ClassDeleteSrbLookasideList)
+ #pragma alloc_text(PAGE, ClassInitializeSrbLookasideList)
+ #pragma alloc_text(PAGE, ClasspInitializeCScanList)
+#endif
+
+typedef struct _CSCAN_LIST_ENTRY {
+ LIST_ENTRY Entry;
+ ULONGLONG BlockNumber;
+} CSCAN_LIST_ENTRY, *PCSCAN_LIST_ENTRY;
+
+
+
+
+
+/*
+ * ClassSplitRequest
+ *
+ * This is a legacy exported function.
+ * It is called by storage miniport driver that have their own
+ * StartIo routine when the transfer size is too large for the hardware.
+ * We map it to our new read/write handler.
+ */
+VOID ClassSplitRequest(_In_ PDEVICE_OBJECT Fdo, _In_ PIRP Irp, _In_ ULONG MaximumBytes)
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
+
+ if (MaximumBytes > fdoData->HwMaxXferLen) {
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_RW, "ClassSplitRequest - driver requesting split to size that "
+ "hardware is unable to handle!\n"));
+ }
+
+ if (MaximumBytes < fdoData->HwMaxXferLen){
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_RW, "ClassSplitRequest - driver requesting smaller HwMaxXferLen "
+ "than required"));
+ fdoData->HwMaxXferLen = MAX(MaximumBytes, PAGE_SIZE);
+ }
+
+ ServiceTransferRequest(Fdo, Irp, FALSE);
+}
+
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassIoCompleteAssociated()
+
+Routine Description:
+
+ This routine executes when the port driver has completed a request.
+ It looks at the SRB status in the completing SRB and if not success
+ it checks for valid request sense buffer information. If valid, the
+ info is used to update status with more precise message of type of
+ error. This routine deallocates the SRB. This routine is used for
+ requests which were build by split request. After it has processed
+ the request it decrements the Irp count in the master Irp. If the
+ count goes to zero then the master Irp is completed.
+
+Arguments:
+
+ Fdo - Supplies the functional device object which represents the target.
+
+ Irp - Supplies the Irp which has completed.
+
+ Context - Supplies a pointer to the SRB.
+
+Return Value:
+
+ NT status
+
+--*/
+NTSTATUS
+ClassIoCompleteAssociated(
+ IN PDEVICE_OBJECT Fdo,
+ IN PIRP Irp,
+ IN PVOID Context
+ )
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
+
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ PSCSI_REQUEST_BLOCK srb = Context;
+
+ PIRP originalIrp = Irp->AssociatedIrp.MasterIrp;
+ LONG irpCount;
+
+ NTSTATUS status;
+ BOOLEAN retry;
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassIoCompleteAssociated is OBSOLETE !"));
+
+ //
+ // Check SRB status for success of completing request.
+ //
+ if (SRB_STATUS(srb->SrbStatus) != SRB_STATUS_SUCCESS) {
+
+ LONGLONG retryInterval;
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassIoCompleteAssociated: IRP %p, SRB %p", Irp, srb));
+
+ //
+ // Release the queue if it is frozen.
+ //
+
+ if (srb->SrbStatus & SRB_STATUS_QUEUE_FROZEN) {
+ ClassReleaseQueue(Fdo);
+ }
+
+ retry = InterpretSenseInfoWithoutHistory(
+ Fdo,
+ Irp,
+ srb,
+ irpStack->MajorFunction,
+ irpStack->MajorFunction == IRP_MJ_DEVICE_CONTROL ?
+ irpStack->Parameters.DeviceIoControl.IoControlCode :
+ 0,
+ MAXIMUM_RETRIES -
+ ((ULONG)(ULONG_PTR)irpStack->Parameters.Others.Argument4),
+ &status,
+ &retryInterval);
+
+ //
+ // If the status is verified required and the this request
+ // should bypass verify required then retry the request.
+ //
+
+ if (irpStack->Flags & SL_OVERRIDE_VERIFY_VOLUME &&
+ status == STATUS_VERIFY_REQUIRED) {
+
+ status = STATUS_IO_DEVICE_ERROR;
+ retry = TRUE;
+ }
+
+#pragma warning(suppress:4213) // okay to cast Arg4 as a ulong for this use case
+ if (retry && ((ULONG)(ULONG_PTR)irpStack->Parameters.Others.Argument4)--) {
+
+ //
+ // Retry request. If the class driver has supplied a StartIo,
+ // call it directly for retries.
+ //
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "Retry request %p\n", Irp));
+
+ if (PORT_ALLOCATED_SENSE(fdoExtension, srb)) {
+ FREE_PORT_ALLOCATED_SENSE_BUFFER(fdoExtension, srb);
+ }
+
+ RetryRequest(Fdo, Irp, srb, TRUE, retryInterval);
+
+ return STATUS_MORE_PROCESSING_REQUIRED;
+ }
+
+ } else {
+
+ //
+ // Set status for successful request.
+ //
+
+ status = STATUS_SUCCESS;
+
+ } // end if (SRB_STATUS(srb->SrbStatus) ...
+
+ //
+ // Return SRB to list.
+ //
+
+ if (PORT_ALLOCATED_SENSE(fdoExtension, srb)) {
+ FREE_PORT_ALLOCATED_SENSE_BUFFER(fdoExtension, srb);
+ }
+
+ ClassFreeOrReuseSrb(fdoExtension, srb);
+
+ //
+ // Set status in completing IRP.
+ //
+
+ Irp->IoStatus.Status = status;
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassIoCompleteAssociated: Partial xfer IRP %p\n", Irp));
+
+ //
+ // Get next stack location. This original request is unused
+ // except to keep track of the completing partial IRPs so the
+ // stack location is valid.
+ //
+
+ irpStack = IoGetNextIrpStackLocation(originalIrp);
+
+ //
+ // Update status only if error so that if any partial transfer
+ // completes with error, then the original IRP will return with
+ // error. If any of the asynchronous partial transfer IRPs fail,
+ // with an error then the original IRP will return 0 bytes transfered.
+ // This is an optimization for successful transfers.
+ //
+
+ if (!NT_SUCCESS(status)) {
+
+ originalIrp->IoStatus.Status = status;
+ originalIrp->IoStatus.Information = 0;
+
+ //
+ // Set the hard error if necessary.
+ //
+
+ if (IoIsErrorUserInduced(status) &&
+ (originalIrp->Tail.Overlay.Thread != NULL)) {
+
+ //
+ // Store DeviceObject for filesystem.
+ //
+
+ IoSetHardErrorOrVerifyDevice(originalIrp, Fdo);
+ }
+ }
+
+ //
+ // Decrement and get the count of remaining IRPs.
+ //
+
+ irpCount = InterlockedDecrement(
+ (PLONG)&irpStack->Parameters.Others.Argument1);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassIoCompleteAssociated: Partial IRPs left %d\n",
+ irpCount));
+
+ //
+ // Ensure that the irpCount doesn't go negative. This was happening once
+ // because classpnp would get confused if it ran out of resources when
+ // splitting the request.
+ //
+
+ NT_ASSERT(irpCount >= 0);
+
+ if (irpCount == 0) {
+
+ //
+ // All partial IRPs have completed.
+ //
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,
+ "ClassIoCompleteAssociated: All partial IRPs complete %p\n",
+ originalIrp));
+
+ if (fdoExtension->CommonExtension.DriverExtension->InitData.ClassStartIo) {
+
+ //
+ // Acquire a separate copy of the remove lock so the debugging code
+ // works okay and we don't have to hold up the completion of this
+ // irp until after we start the next packet(s).
+ //
+
+ KIRQL oldIrql;
+ UCHAR uniqueAddress = 0;
+ ClassAcquireRemoveLock(Fdo, (PIRP)&uniqueAddress);
+ ClassReleaseRemoveLock(Fdo, originalIrp);
+ ClassCompleteRequest(Fdo, originalIrp, IO_DISK_INCREMENT);
+
+ KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
+ IoStartNextPacket(Fdo, TRUE); // yes, some IO is now cancellable
+ KeLowerIrql(oldIrql);
+
+ ClassReleaseRemoveLock(Fdo, (PIRP)&uniqueAddress);
+
+ } else {
+
+ //
+ // just complete this request
+ //
+
+ ClassReleaseRemoveLock(Fdo, originalIrp);
+ ClassCompleteRequest(Fdo, originalIrp, IO_DISK_INCREMENT);
+
+ }
+
+ }
+
+ //
+ // Deallocate IRP and indicate the I/O system should not attempt any more
+ // processing.
+ //
+
+ IoFreeIrp(Irp);
+ return STATUS_MORE_PROCESSING_REQUIRED;
+
+} // end ClassIoCompleteAssociated()
+
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+RetryRequest()
+
+Routine Description:
+
+ This is a wrapper around the delayed retry DPC routine, RetryRequestDPC.
+ This reinitalizes the necessary fields, queues the request, and sets
+ a timer to call the DPC if someone hasn't already done so.
+
+Arguments:
+
+ DeviceObject - Supplies the device object associated with this request.
+
+ Irp - Supplies the request to be retried.
+
+ Srb - Supplies a Pointer to the SCSI request block to be retied.
+
+ Assocaiated - Indicates this is an assocatied Irp created by split request.
+
+ TimeDelta100ns - How long, in 100ns units, before retrying the request.
+
+Return Value:
+
+ None
+
+--*/
+VOID
+RetryRequest(
+ PDEVICE_OBJECT DeviceObject,
+ PIRP Irp,
+ PSCSI_REQUEST_BLOCK Srb,
+ BOOLEAN Associated,
+ LONGLONG TimeDelta100ns
+ )
+{
+ PIO_STACK_LOCATION currentIrpStack = IoGetCurrentIrpStackLocation(Irp);
+ PIO_STACK_LOCATION nextIrpStack = IoGetNextIrpStackLocation(Irp);
+ ULONG transferByteCount;
+ ULONG dataTransferLength;
+ PSTORAGE_REQUEST_BLOCK_HEADER srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)Srb;
+
+ // This function is obsolete but is still used by some of our class drivers.
+ // TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "RetryRequest is OBSOLETE !"));
+
+ //
+ // Determine the transfer count of the request. If this is a read or a
+ // write then the transfer count is in the Irp stack. Otherwise assume
+ // the MDL contains the correct length. If there is no MDL then the
+ // transfer length must be zero.
+ //
+
+ dataTransferLength = SrbGetDataTransferLength(srbHeader);
+ if (currentIrpStack->MajorFunction == IRP_MJ_READ ||
+ currentIrpStack->MajorFunction == IRP_MJ_WRITE) {
+
+ _Analysis_assume_(currentIrpStack->Parameters.Read.Length <= dataTransferLength);
+ transferByteCount = currentIrpStack->Parameters.Read.Length;
+
+ } else if (Irp->MdlAddress != NULL) {
+
+ //
+ // Note this assumes that only read and write requests are spilt and
+ // other request do not need to be. If the data buffer address in
+ // the MDL and the SRB don't match then transfer length is most
+ // likely incorrect.
+ //
+
+ NT_ASSERT(SrbGetDataBuffer(srbHeader) == MmGetMdlVirtualAddress(Irp->MdlAddress));
+ _Analysis_assume_(Irp->MdlAddress->ByteCount <= dataTransferLength);
+ transferByteCount = Irp->MdlAddress->ByteCount;
+
+ } else {
+
+ transferByteCount = 0;
+ }
+
+ //
+ // this is a safety net. this should not normally be hit, since we are
+ // not guaranteed to be an fdoExtension
+ //
+
+ NT_ASSERT(!TEST_FLAG(SrbGetSrbFlags(srbHeader), SRB_FLAGS_FREE_SENSE_BUFFER));
+
+ //
+ // Reset byte count of transfer in SRB Extension.
+ //
+
+ SrbSetDataTransferLength(srbHeader, transferByteCount);
+
+ //
+ // Zero SRB statuses.
+ //
+
+ srbHeader->SrbStatus = 0;
+ SrbSetScsiStatus(srbHeader, 0);
+
+ //
+ // If this is the last retry, then disable all the special flags.
+ //
+
+ if ( 0 == (ULONG)(ULONG_PTR)currentIrpStack->Parameters.Others.Argument4 ) {
+ //
+ // Set the no disconnect flag, disable synchronous data transfers and
+ // disable tagged queuing. This fixes some errors.
+ // NOTE: Cannot clear these flags, just add to them
+ //
+
+ SrbSetSrbFlags(srbHeader,
+ SRB_FLAGS_DISABLE_DISCONNECT | SRB_FLAGS_DISABLE_SYNCH_TRANSFER);
+ SrbClearSrbFlags(srbHeader, SRB_FLAGS_QUEUE_ACTION_ENABLE);
+
+ SrbSetQueueTag(srbHeader, SP_UNTAGGED);
+ }
+
+
+ //
+ // Set up major SCSI function.
+ //
+
+ nextIrpStack->MajorFunction = IRP_MJ_SCSI;
+
+ //
+ // Save SRB address in next stack for port driver.
+ //
+
+ nextIrpStack->Parameters.Scsi.Srb = Srb;
+
+ if (Associated){
+ IoSetCompletionRoutine(Irp, ClassIoCompleteAssociated, Srb, TRUE, TRUE, TRUE);
+ }
+ else {
+ IoSetCompletionRoutine(Irp, ClassIoComplete, Srb, TRUE, TRUE, TRUE);
+ }
+
+ ClassRetryRequest(DeviceObject, Irp, TimeDelta100ns);
+ return;
+} // end RetryRequest()
+
+
+/*++
+
+ClassBuildRequest()
+
+Routine Description:
+
+ This routine allocates an SRB for the specified request then calls
+ ClasspBuildRequestEx to create a SCSI operation to read or write the device.
+
+ If no SRB is available then the request will be queued to be issued later
+ when requests are available. Drivers which do not want the queueing
+ behavior should allocate the SRB themselves and call ClasspBuildRequestEx
+ to issue it.
+
+Arguments:
+
+ Fdo - Supplies the functional device object associated with this request.
+
+ Irp - Supplies the request to be retried.
+
+Note:
+
+ If the IRP is for a disk transfer, the byteoffset field
+ will already have been adjusted to make it relative to
+ the beginning of the disk.
+
+
+Return Value:
+
+ NT Status
+
+--*/
+NTSTATUS
+ClassBuildRequest(
+ _In_ PDEVICE_OBJECT Fdo,
+ _In_ PIRP Irp
+ )
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
+
+ PSCSI_REQUEST_BLOCK srb;
+
+ // This function is obsolete, but still called by CDROM.SYS .
+ // TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassBuildRequest is OBSOLETE !"));
+
+ //
+ // Allocate an Srb.
+ //
+
+ srb = ClasspAllocateSrb(fdoExtension);
+
+ if (srb == NULL) {
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ ClasspBuildRequestEx(fdoExtension, Irp, srb);
+ return STATUS_SUCCESS;
+
+} // end ClassBuildRequest()
+
+
+VOID
+#pragma prefast(suppress:28194) // Srb may not be aliased if it is NULL
+ClasspBuildRequestEx(
+ _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ _In_ PIRP Irp,
+ _In_ __drv_aliasesMem PSCSI_REQUEST_BLOCK Srb
+ )
+
+/*++
+
+ClasspBuildRequestEx()
+
+Routine Description:
+
+ This routine allocates and builds an Srb for a read or write request.
+ The block address and length are supplied by the Irp. The retry count
+ is stored in the current stack for use by ClassIoComplete which
+ processes these requests when they complete. The Irp is ready to be
+ passed to the port driver when this routine returns.
+
+Arguments:
+
+ FdoExtension - Supplies the device extension associated with this request.
+
+ Irp - Supplies the request to be issued.
+
+ Srb - Supplies an SRB to be used for the request.
+
+Note:
+
+ If the IRP is for a disk transfer, the byteoffset field
+ will already have been adjusted to make it relative to
+ the beginning of the disk.
+
+
+Return Value:
+
+ NT Status
+
+--*/
+{
+ PIO_STACK_LOCATION currentIrpStack = IoGetCurrentIrpStackLocation(Irp);
+ PIO_STACK_LOCATION nextIrpStack = IoGetNextIrpStackLocation(Irp);
+
+ LARGE_INTEGER startingOffset = currentIrpStack->Parameters.Read.ByteOffset;
+
+ PCDB cdb;
+ ULONG logicalBlockAddress;
+ USHORT transferBlocks;
+ NTSTATUS status;
+ PSTORAGE_REQUEST_BLOCK_HEADER srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)Srb;
+
+ // This function is obsolete, but still called by CDROM.SYS .
+ // TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClasspBuildRequestEx is OBSOLETE !"));
+
+ if (Srb == NULL) {
+ NT_ASSERT(FALSE);
+ return;
+ }
+
+ //
+ // Calculate relative sector address.
+ //
+
+ logicalBlockAddress =
+ (ULONG)(Int64ShrlMod32(startingOffset.QuadPart,
+ FdoExtension->SectorShift));
+
+ //
+ // Prepare the SRB.
+ // NOTE - for extended SRB, size used is based on allocation in ClasspAllocateSrb.
+ //
+
+ 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)) {
+ NT_ASSERT(FALSE);
+ return;
+ }
+
+ ((PSTORAGE_REQUEST_BLOCK)Srb)->SrbFunction = SRB_FUNCTION_EXECUTE_SCSI;
+ } else {
+ RtlZeroMemory(Srb, sizeof(SCSI_REQUEST_BLOCK));
+
+ //
+ // Write length to SRB.
+ //
+
+ Srb->Length = sizeof(SCSI_REQUEST_BLOCK);
+
+ Srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
+ }
+
+
+ //
+ // Set up IRP Address.
+ //
+
+ SrbSetOriginalRequest(srbHeader, Irp);
+
+ //
+ // Set up data buffer
+ //
+
+ SrbSetDataBuffer(srbHeader,
+ MmGetMdlVirtualAddress(Irp->MdlAddress));
+
+ //
+ // Save byte count of transfer in SRB Extension.
+ //
+
+ SrbSetDataTransferLength(srbHeader,
+ currentIrpStack->Parameters.Read.Length);
+
+ //
+ // Initialize the queue actions field.
+ //
+
+ SrbSetRequestAttribute(srbHeader, SRB_SIMPLE_TAG_REQUEST);
+
+ //
+ // Queue sort key is Relative Block Address.
+ //
+
+ SrbSetQueueSortKey(srbHeader, logicalBlockAddress);
+
+ //
+ // Indicate auto request sense by specifying buffer and size.
+ //
+
+ SrbSetSenseInfoBuffer(srbHeader, FdoExtension->SenseData);
+ SrbSetSenseInfoBufferLength(srbHeader, GET_FDO_EXTENSON_SENSE_DATA_LENGTH(FdoExtension));
+
+ //
+ // Set timeout value of one unit per 64k bytes of data.
+ //
+
+ SrbSetTimeOutValue(srbHeader,
+ ((SrbGetDataTransferLength(srbHeader) + 0xFFFF) >> 16) *
+ FdoExtension->TimeOutValue);
+
+ //
+ // Indicate that 10-byte CDB's will be used.
+ //
+
+ SrbSetCdbLength(srbHeader, 10);
+
+ //
+ // Fill in CDB fields.
+ //
+
+ cdb = SrbGetCdb(srbHeader);
+ NT_ASSERT(cdb != NULL);
+
+ transferBlocks = (USHORT)(currentIrpStack->Parameters.Read.Length >>
+ FdoExtension->SectorShift);
+
+ //
+ // Move little endian values into CDB in big endian format.
+ //
+
+ cdb->CDB10.LogicalBlockByte0 = ((PFOUR_BYTE)&logicalBlockAddress)->Byte3;
+ cdb->CDB10.LogicalBlockByte1 = ((PFOUR_BYTE)&logicalBlockAddress)->Byte2;
+ cdb->CDB10.LogicalBlockByte2 = ((PFOUR_BYTE)&logicalBlockAddress)->Byte1;
+ cdb->CDB10.LogicalBlockByte3 = ((PFOUR_BYTE)&logicalBlockAddress)->Byte0;
+
+ cdb->CDB10.TransferBlocksMsb = ((PFOUR_BYTE)&transferBlocks)->Byte1;
+ cdb->CDB10.TransferBlocksLsb = ((PFOUR_BYTE)&transferBlocks)->Byte0;
+
+ //
+ // Set transfer direction flag and Cdb command.
+ //
+
+ if (currentIrpStack->MajorFunction == IRP_MJ_READ) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_RW, "ClassBuildRequest: Read Command\n"));
+
+ SrbSetSrbFlags(srbHeader, SRB_FLAGS_DATA_IN);
+ cdb->CDB10.OperationCode = SCSIOP_READ;
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_RW, "ClassBuildRequest: Write Command\n"));
+
+ SrbSetSrbFlags(srbHeader, SRB_FLAGS_DATA_OUT);
+ cdb->CDB10.OperationCode = SCSIOP_WRITE;
+
+ }
+
+ //
+ // If this is not a write-through request, then allow caching.
+ //
+
+ if (!(currentIrpStack->Flags & SL_WRITE_THROUGH)) {
+
+ SrbSetSrbFlags(srbHeader, SRB_FLAGS_ADAPTER_CACHE_ENABLE);
+
+ } else {
+
+ //
+ // If write caching is enable then force media access in the
+ // cdb.
+ //
+
+ cdb->CDB10.ForceUnitAccess = FdoExtension->CdbForceUnitAccess;
+ }
+
+ if (TEST_FLAG(Irp->Flags, (IRP_PAGING_IO | IRP_SYNCHRONOUS_PAGING_IO))) {
+ SrbSetSrbFlags(srbHeader, SRB_CLASS_FLAGS_PAGING);
+ }
+
+ //
+ // OR in the default flags from the device object.
+ //
+
+ SrbSetSrbFlags(srbHeader, FdoExtension->SrbFlags);
+
+ //
+ // Set up major SCSI function.
+ //
+
+ nextIrpStack->MajorFunction = IRP_MJ_SCSI;
+
+ //
+ // Save SRB address in next stack for port driver.
+ //
+
+ nextIrpStack->Parameters.Scsi.Srb = Srb;
+
+ //
+ // Save retry count in current IRP stack.
+ //
+
+ currentIrpStack->Parameters.Others.Argument4 = (PVOID)MAXIMUM_RETRIES;
+
+ //
+ // Set up IoCompletion routine address.
+ //
+
+ IoSetCompletionRoutine(Irp, ClassIoComplete, Srb, TRUE, TRUE, TRUE);
+
+}
+
+
+VOID ClasspInsertCScanList(IN PLIST_ENTRY ListHead, IN PCSCAN_LIST_ENTRY Entry)
+{
+ PCSCAN_LIST_ENTRY t;
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClasspInsertCScanList is OBSOLETE !"));
+
+ //
+ // Iterate through the list. Insert this entry in the sorted list in
+ // order (after other requests for the same block). At each stop if
+ // blockNumber(Entry) >= blockNumber(t) then move on.
+ //
+
+ for(t = (PCSCAN_LIST_ENTRY) ListHead->Flink;
+ t != (PCSCAN_LIST_ENTRY) ListHead;
+ t = (PCSCAN_LIST_ENTRY) t->Entry.Flink) {
+
+ if(Entry->BlockNumber < t->BlockNumber) {
+
+ //
+ // Set the pointers in entry to the right location.
+ //
+
+ Entry->Entry.Flink = &(t->Entry);
+ Entry->Entry.Blink = t->Entry.Blink;
+
+ //
+ // Set the pointers in the surrounding elements to refer to us.
+ //
+
+ t->Entry.Blink->Flink = &(Entry->Entry);
+ t->Entry.Blink = &(Entry->Entry);
+ return;
+ }
+ }
+
+ //
+ // Insert this entry at the tail of the list. If the list was empty this
+ // will also be the head of the list.
+ //
+
+ InsertTailList(ListHead, &(Entry->Entry));
+
+}
+
+
+VOID ClassInsertCScanList(IN PCSCAN_LIST List, IN PIRP Irp, IN ULONGLONG BlockNumber, IN BOOLEAN LowPriority)
+/*++
+
+Routine Description:
+
+ This routine inserts an entry into the CScan list based on it's block number
+ and priority. It is assumed that the caller is providing synchronization
+ to the access of the list.
+
+ Low priority requests are always scheduled to run on the next sweep across
+ the disk. Normal priority requests will be inserted into the current or
+ next sweep based on the standard C-SCAN algorithm.
+
+Arguments:
+
+ List - the list to insert into
+
+ Irp - the irp to be inserted.
+
+ BlockNumber - the block number for this request.
+
+ LowPriority - indicates that the request is lower priority and should be
+ done on the next sweep across the disk.
+
+Return Value:
+
+ none
+
+--*/
+{
+ PCSCAN_LIST_ENTRY entry = (PCSCAN_LIST_ENTRY)Irp->Tail.Overlay.DriverContext;
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInsertCScanList is OBSOLETE !"));
+
+ //
+ // Set the block number in the entry. We need this to keep the list sorted.
+ //
+ entry->BlockNumber = BlockNumber;
+
+ //
+ // If it's a normal priority request and further down the disk than our
+ // current position then insert this entry into the current sweep.
+ //
+
+ if((LowPriority != TRUE) && (BlockNumber > List->BlockNumber)) {
+ ClasspInsertCScanList(&(List->CurrentSweep), entry);
+ } else {
+ ClasspInsertCScanList(&(List->NextSweep), entry);
+ }
+ return;
+}
+
+
+
+VOID ClassFreeOrReuseSrb( IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension,
+ IN __drv_freesMem(mem) PSCSI_REQUEST_BLOCK Srb)
+/*++
+
+Routine Description:
+
+ This routine will attempt to reuse the provided SRB to start a blocked
+ read/write request.
+ If there is no need to reuse the request it will be returned
+ to the SRB lookaside list.
+
+Arguments:
+
+ Fdo - the device extension
+
+ Srb - the SRB which is to be reused or freed.
+
+Return Value:
+
+ none.
+
+--*/
+
+{
+ PCOMMON_DEVICE_EXTENSION commonExt = &FdoExtension->CommonExtension;
+
+ // This function is obsolete, but still called by DISK.SYS .
+ // TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassFreeOrReuseSrb is OBSOLETE !"));
+
+ //
+ // safety net. this should never occur. if it does, it's a potential
+ // memory leak.
+ //
+ NT_ASSERT(!TEST_FLAG(SrbGetSrbFlags(Srb), SRB_FLAGS_FREE_SENSE_BUFFER));
+
+ if (commonExt->IsSrbLookasideListInitialized){
+ /*
+ * Put the SRB back in our lookaside list.
+ *
+ * Note: Some class drivers use ClassIoComplete
+ * to complete SRBs that they themselves allocated.
+ * So we may be putting a "foreign" SRB
+ * (e.g. with a different pool tag) into our lookaside list.
+ */
+ ClasspFreeSrb(FdoExtension, Srb);
+ }
+ else {
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,"ClassFreeOrReuseSrb: someone is trying to use an uninitialized SrbLookasideList !!!"));
+ FREE_POOL(Srb);
+ }
+}
+
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassDeleteSrbLookasideList()
+
+Routine Description:
+
+ This routine deletes a lookaside listhead for srbs, and should be called
+ only during the final removal.
+
+ If called at other times, the caller is responsible for
+ synchronization and removal issues.
+
+Arguments:
+
+ CommonExtension - Pointer to the CommonExtension containing the listhead.
+
+Return Value:
+
+ None
+
+--*/
+_IRQL_requires_max_(PASSIVE_LEVEL)
+VOID ClassDeleteSrbLookasideList(_Inout_ PCOMMON_DEVICE_EXTENSION CommonExtension)
+{
+ PAGED_CODE();
+
+ // This function is obsolete, but is still called by some of our code.
+ // TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassDeleteSrbLookasideList is OBSOLETE !"));
+
+ if (CommonExtension->IsSrbLookasideListInitialized){
+ CommonExtension->IsSrbLookasideListInitialized = FALSE;
+ ExDeleteNPagedLookasideList(&CommonExtension->SrbLookasideList);
+ }
+ else {
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassDeleteSrbLookasideList: attempt to delete uninitialized or freed srblookasidelist"));
+ }
+}
+
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassInitializeSrbLookasideList()
+
+Routine Description:
+
+ This routine sets up a lookaside listhead for srbs, and should be called
+ only from the ClassInitDevice() routine to prevent race conditions.
+
+ If called from other locations, the caller is responsible for
+ synchronization and removal issues.
+
+Arguments:
+
+ CommonExtension - Pointer to the CommonExtension containing the listhead.
+
+ NumberElements - Supplies the maximum depth of the lookaside list.
+
+
+Note:
+
+ The Windows 2000 version of classpnp did not return any status value from
+ this call.
+
+--*/
+
+_IRQL_requires_max_(PASSIVE_LEVEL)
+VOID ClassInitializeSrbLookasideList( _Inout_ PCOMMON_DEVICE_EXTENSION CommonExtension,
+ _In_ ULONG NumberElements)
+{
+ size_t sizeNeeded;
+ PFUNCTIONAL_DEVICE_EXTENSION fdo;
+
+ PAGED_CODE();
+
+ // This function is obsolete, but still called by DISK.SYS .
+ // TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInitializeSrbLookasideList is OBSOLETE !"));
+
+ NT_ASSERT(!CommonExtension->IsSrbLookasideListInitialized);
+ if (!CommonExtension->IsSrbLookasideListInitialized){
+
+ if (CommonExtension->IsFdo == TRUE) {
+ fdo = (PFUNCTIONAL_DEVICE_EXTENSION)CommonExtension;
+
+ //
+ // Check FDO extension on the SRB type supported
+ //
+ if (fdo->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+
+ //
+ // It's 16 byte CDBs for now. Need to change when classpnp uses >16
+ // byte CDBs or support new address types.
+ //
+ sizeNeeded = CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE;
+
+ } else {
+ sizeNeeded = sizeof(SCSI_REQUEST_BLOCK);
+ }
+
+ } else {
+
+ //
+ // For PDO, use the max of old and new SRB as can't guarantee we can get
+ // corresponding FDO to determine SRB support.
+ //
+ sizeNeeded = max(sizeof(SCSI_REQUEST_BLOCK), CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE);
+ }
+
+ ExInitializeNPagedLookasideList(&CommonExtension->SrbLookasideList,
+ NULL,
+ NULL,
+ POOL_NX_ALLOCATION,
+ sizeNeeded,
+ '$scS',
+ (USHORT)NumberElements);
+
+ CommonExtension->IsSrbLookasideListInitialized = TRUE;
+ }
+
+}
+
+
+
+
+VOID ClasspInitializeCScanList(IN PCSCAN_LIST List)
+{
+ PAGED_CODE();
+ RtlZeroMemory(List, sizeof(CSCAN_LIST));
+ InitializeListHead(&(List->CurrentSweep));
+ InitializeListHead(&(List->NextSweep));
+}
+
+
+
+VOID ClasspStartNextSweep(PCSCAN_LIST List)
+{
+ NT_ASSERT(IsListEmpty(&(List->CurrentSweep)) == TRUE);
+
+ //
+ // If the next sweep is empty then there's nothing to do.
+ //
+
+ if(IsListEmpty(&(List->NextSweep))) {
+ return;
+ }
+
+ //
+ // Copy the next sweep list head into the current sweep list head.
+ //
+
+ List->CurrentSweep = List->NextSweep;
+
+ //
+ // Unlink the next sweep list from the list head now that we have a copy
+ // of it.
+ //
+
+ InitializeListHead(&(List->NextSweep));
+
+ //
+ // Update the next sweep list to point back to the current sweep list head.
+ //
+
+ List->CurrentSweep.Flink->Blink = &(List->CurrentSweep);
+ List->CurrentSweep.Blink->Flink = &(List->CurrentSweep);
+
+ return;
+}
+
+
+
+PIRP ClassRemoveCScanList(IN PCSCAN_LIST List)
+{
+ PCSCAN_LIST_ENTRY entry;
+
+ //
+ // If the current sweep is empty then promote the next sweep.
+ //
+
+ if(IsListEmpty(&(List->CurrentSweep))) {
+ ClasspStartNextSweep(List);
+ }
+
+ //
+ // If the current sweep is still empty then we're done.
+ //
+
+ if(IsListEmpty(&(List->CurrentSweep))) {
+ return NULL;
+ }
+
+ //
+ // Remove the head entry from the current sweep. Record it's block number
+ // so that nothing before it on the disk gets into the current sweep.
+ //
+
+ entry = (PCSCAN_LIST_ENTRY) RemoveHeadList(&(List->CurrentSweep));
+
+ List->BlockNumber = entry->BlockNumber;
+
+ return CONTAINING_RECORD(entry, IRP, Tail.Overlay.DriverContext);
+}
diff --git a/storage/class/classpnp/src/power.c b/storage/class/classpnp/src/power.c
new file mode 100644
index 00000000..790dba4a
--- /dev/null
+++ b/storage/class/classpnp/src/power.c
@@ -0,0 +1,2650 @@
+/*++
+
+Copyright (C) Microsoft Corporation, 1991 - 2010
+
+Module Name:
+
+ power.c
+
+Abstract:
+
+ SCSI class driver routines
+
+Environment:
+
+ kernel mode only
+
+Notes:
+
+
+Revision History:
+
+--*/
+
+#include "stddef.h"
+#include "ntddk.h"
+#include "scsi.h"
+#include "classp.h"
+
+#include <stdarg.h>
+
+#ifdef DEBUG_USE_WPP
+#include "power.tmh"
+#endif
+
+#define CLASS_TAG_POWER 'WLcS'
+
+// constants for power transition process. (UNIT: seconds)
+#define DEFAULT_POWER_IRP_TIMEOUT_VALUE 10*60
+#define TIME_LEFT_FOR_LOWER_DRIVERS 30
+#define TIME_LEFT_FOR_UPPER_DRIVERS 5
+#define DEFAULT_IO_TIMEOUT_VALUE 10
+#define MINIMUM_STOP_UNIT_TIMEOUT_VALUE 2
+
+//
+// MINIMAL value is one that has some slack and is the value to use
+// if there is a shortened POWER IRP timeout value. If time remaining
+// is less than MINIMAL, we will use the MINIMUM value. Both values
+// are in the same unit as above (seconds).
+//
+#define MINIMAL_START_UNIT_TIMEOUT_VALUE 60
+#define MINIMUM_START_UNIT_TIMEOUT_VALUE 30
+
+// PoQueryWatchdogTime was introduced in Windows 7.
+// Returns TRUE if a watchdog-enabled power IRP is found, otherwise FALSE.
+#if (NTDDI_VERSION < NTDDI_WIN7)
+#define PoQueryWatchdogTime(A, B) FALSE
+#endif
+
+IO_COMPLETION_ROUTINE ClasspPowerDownCompletion;
+
+IO_COMPLETION_ROUTINE ClasspPowerUpCompletion;
+
+IO_COMPLETION_ROUTINE ClasspStartNextPowerIrpCompletion;
+IO_COMPLETION_ROUTINE ClasspDeviceLockFailurePowerIrpCompletion;
+
+NTSTATUS
+ClasspPowerHandler(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN CLASS_POWER_OPTIONS Options
+ );
+
+VOID
+RetryPowerRequest(
+ PDEVICE_OBJECT DeviceObject,
+ PIRP Irp,
+ PCLASS_POWER_CONTEXT Context
+ );
+
+#ifdef ALLOC_PRAGMA
+ #pragma alloc_text(PAGE, ClasspPowerSettingCallback)
+#endif
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassDispatchPower()
+
+Routine Description:
+
+ This routine acquires the removelock for the irp and then calls the
+ appropriate power callback.
+
+Arguments:
+
+ DeviceObject -
+ Irp -
+
+Return Value:
+
+--*/
+NTSTATUS
+ClassDispatchPower(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp
+ )
+{
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+ ULONG isRemoved;
+
+ //
+ // NOTE: This code may be called at PASSIVE or DISPATCH, depending
+ // upon the device object it is being called for.
+ // don't do anything that would break under either circumstance.
+ //
+
+ //
+ // If device is added but not yet started, we need to send the Power
+ // request down the stack. If device is started and then stopped,
+ // we have enough state to process the power request.
+ //
+
+ if (!commonExtension->IsInitialized) {
+
+ PoStartNextPowerIrp(Irp);
+ IoSkipCurrentIrpStackLocation(Irp);
+ return PoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ }
+
+ isRemoved = ClassAcquireRemoveLock(DeviceObject, Irp);
+
+ if (isRemoved) {
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST;
+ PoStartNextPowerIrp(Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ return STATUS_DEVICE_DOES_NOT_EXIST;
+ }
+
+ return commonExtension->DevInfo->ClassPowerDevice(DeviceObject, Irp);
+} // end ClassDispatchPower()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClasspPowerUpCompletion()
+
+Routine Description:
+
+ This routine is used for intermediate completion of a power up request.
+ PowerUp requires four requests to be sent to the lower driver in sequence.
+
+ * The queue is "power locked" to ensure that the class driver power-up
+ work can be done before request processing resumes.
+
+ * The power irp is sent down the stack for any filter drivers and the
+ port driver to return power and resume command processing for the
+ device. Since the queue is locked, no queued irps will be sent
+ immediately.
+
+ * A start unit command is issued to the device with appropriate flags
+ to override the "power locked" queue.
+
+ * The queue is "power unlocked" to start processing requests again.
+
+ This routine uses the function in the srb which just completed to determine
+ which state it is in.
+
+Arguments:
+
+ DeviceObject - the device object being powered up
+
+ Irp - Context->Irp: original power irp; fdoExtension->PrivateFdoData->PowerProcessIrp: power process irp
+
+ Context - Class power context used to perform port/class operations.
+
+Return Value:
+
+ STATUS_MORE_PROCESSING_REQUIRED or
+ STATUS_SUCCESS
+
+--*/
+NTSTATUS
+ClasspPowerUpCompletion(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN PVOID Context
+ )
+{
+ PCLASS_POWER_CONTEXT PowerContext = (PCLASS_POWER_CONTEXT)Context;
+ PCOMMON_DEVICE_EXTENSION commonExtension;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
+ PIRP OriginalIrp;
+ PIO_STACK_LOCATION currentStack;
+ PIO_STACK_LOCATION nextStack;
+
+ NTSTATUS status = STATUS_MORE_PROCESSING_REQUIRED;
+ PSTORAGE_REQUEST_BLOCK_HEADER srbHeader;
+ ULONG srbFlags;
+ BOOLEAN FailurePredictionEnabled = FALSE;
+
+ UNREFERENCED_PARAMETER(DeviceObject);
+
+ if (PowerContext == NULL) {
+ NT_ASSERT(PowerContext != NULL);
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ commonExtension = PowerContext->DeviceObject->DeviceExtension;
+ fdoExtension = PowerContext->DeviceObject->DeviceExtension;
+ OriginalIrp = PowerContext->Irp;
+
+ // currentStack - from original power irp
+ // nextStack - from power process irp
+ currentStack = IoGetCurrentIrpStackLocation(OriginalIrp);
+ nextStack = IoGetNextIrpStackLocation(fdoExtension->PrivateFdoData->PowerProcessIrp);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "ClasspPowerUpCompletion: Device Object %p, Irp %p, "
+ "Context %p\n",
+ PowerContext->DeviceObject, Irp, Context));
+
+ if (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+ srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)&(fdoExtension->PrivateFdoData->PowerSrb.SrbEx);
+
+ //
+ // Check if reverted to using legacy SRB.
+ //
+ if (PowerContext->Srb.Length == sizeof(SCSI_REQUEST_BLOCK)) {
+ srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)&(PowerContext->Srb);
+ }
+ } else {
+ srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)&(PowerContext->Srb);
+ }
+
+ srbFlags = SrbGetSrbFlags(srbHeader);
+ NT_ASSERT(!TEST_FLAG(srbFlags, SRB_FLAGS_FREE_SENSE_BUFFER));
+ NT_ASSERT(!TEST_FLAG(srbFlags, SRB_FLAGS_PORT_DRIVER_ALLOCSENSE));
+ NT_ASSERT(PowerContext->Options.PowerDown == FALSE);
+ NT_ASSERT(PowerContext->Options.HandleSpinUp);
+
+ if ((Irp == OriginalIrp) && (Irp->PendingReturned)) {
+ // only for original power irp
+ IoMarkIrpPending(Irp);
+ }
+
+ PowerContext->PowerChangeState.PowerUp++;
+
+ switch (PowerContext->PowerChangeState.PowerUp) {
+
+ case PowerUpDeviceLocked: {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tPreviously sent power lock\n", Irp));
+
+ //
+ // Lock Queue operation has been sent.
+ // Now, send the original power irp down to get lower driver and device ready.
+ //
+
+ IoCopyCurrentIrpStackLocationToNext(OriginalIrp);
+
+ if ((PowerContext->Options.LockQueue == TRUE) &&
+ (!NT_SUCCESS(Irp->IoStatus.Status))) {
+
+ //
+ // Lock was not successful:
+ // Issue the original power request to the lower driver and next power irp will be started in completion routine.
+ //
+
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tIrp status was %lx\n",
+ Irp, Irp->IoStatus.Status));
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tSrb status was %lx\n",
+ Irp, srbHeader->SrbStatus));
+
+ IoSetCompletionRoutine(OriginalIrp,
+ ClasspDeviceLockFailurePowerIrpCompletion,
+ PowerContext,
+ TRUE,
+ TRUE,
+ TRUE);
+
+ PoCallDriver(commonExtension->LowerDeviceObject, OriginalIrp);
+
+ return STATUS_MORE_PROCESSING_REQUIRED;
+
+ } else {
+ PowerContext->QueueLocked = (UCHAR)PowerContext->Options.LockQueue;
+ }
+
+ Irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
+
+ PowerContext->PowerChangeState.PowerUp = PowerUpDeviceLocked;
+
+ IoSetCompletionRoutine(OriginalIrp,
+ ClasspPowerUpCompletion,
+ PowerContext,
+ TRUE,
+ TRUE,
+ TRUE);
+
+ status = PoCallDriver(commonExtension->LowerDeviceObject, OriginalIrp);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tIoCallDriver returned %lx\n", OriginalIrp, status));
+ break;
+ }
+
+ case PowerUpDeviceOn: {
+
+ //
+ // Original power irp has been completed by lower driver.
+ //
+
+ if (NT_SUCCESS(Irp->IoStatus.Status)) {
+ //
+ // If power irp succeeded, START UNIT command will be sent.
+ //
+ PCDB cdb;
+ ULONG secondsRemaining = 0;
+ ULONG timeoutValue = 0;
+ ULONG startUnitTimeout;
+
+ if (PoQueryWatchdogTime(fdoExtension->LowerPdo, &secondsRemaining)) {
+
+ // do not exceed DEFAULT_POWER_IRP_TIMEOUT_VALUE.
+ secondsRemaining = min(secondsRemaining, DEFAULT_POWER_IRP_TIMEOUT_VALUE);
+
+ //
+ // It's possible for POWER IRP timeout value to be smaller than default of
+ // START_UNIT_TIMEOUT. If this is the case, use a smaller timeout value.
+ //
+ if (secondsRemaining >= START_UNIT_TIMEOUT) {
+ startUnitTimeout = START_UNIT_TIMEOUT;
+ } else {
+ startUnitTimeout = MINIMAL_START_UNIT_TIMEOUT_VALUE;
+ }
+
+ // plan to leave (TIME_LEFT_FOR_UPPER_DRIVERS) seconds to upper level drivers
+ // for processing original power irp.
+ if (secondsRemaining >= (TIME_LEFT_FOR_UPPER_DRIVERS + startUnitTimeout)) {
+ fdoExtension->PrivateFdoData->MaxPowerOperationRetryCount =
+ (secondsRemaining - TIME_LEFT_FOR_UPPER_DRIVERS) / startUnitTimeout;
+
+ // * No 'short' timeouts
+ //
+ //
+ // timeoutValue = (secondsRemaining - TIME_LEFT_FOR_UPPER_DRIVERS) %
+ // startUnitTimeout;
+ //
+
+ if (--fdoExtension->PrivateFdoData->MaxPowerOperationRetryCount)
+ {
+ timeoutValue = startUnitTimeout;
+ } else {
+ timeoutValue = secondsRemaining - TIME_LEFT_FOR_UPPER_DRIVERS;
+ }
+ } else {
+ // issue the command with minimum timeout value and do not retry on it.
+ // case of (secondsRemaining < DEFAULT_IO_TIMEOUT_VALUE) is ignored as it should not happen.
+ NT_ASSERT(secondsRemaining >= DEFAULT_IO_TIMEOUT_VALUE);
+
+ fdoExtension->PrivateFdoData->MaxPowerOperationRetryCount = 0;
+ timeoutValue = MINIMUM_START_UNIT_TIMEOUT_VALUE; // use the minimum value for this corner case.
+ }
+
+ } else {
+ // don't know how long left, do not exceed DEFAULT_POWER_IRP_TIMEOUT_VALUE.
+ fdoExtension->PrivateFdoData->MaxPowerOperationRetryCount =
+ DEFAULT_POWER_IRP_TIMEOUT_VALUE / START_UNIT_TIMEOUT - 1;
+ timeoutValue = START_UNIT_TIMEOUT;
+ }
+
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tSending start unit to device\n", Irp));
+
+ //
+ // Issue the start unit command to the device.
+ //
+
+ PowerContext->RetryCount = fdoExtension->PrivateFdoData->MaxPowerOperationRetryCount;
+
+ if (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+ status = InitializeStorageRequestBlock((PSTORAGE_REQUEST_BLOCK)srbHeader,
+ STORAGE_ADDRESS_TYPE_BTL8,
+ CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE,
+ 1,
+ SrbExDataTypeScsiCdb16);
+ if (NT_SUCCESS(status)) {
+ ((PSTORAGE_REQUEST_BLOCK)srbHeader)->SrbFunction = SRB_FUNCTION_EXECUTE_SCSI;
+
+ //
+ // Set length field in Power Context SRB so we know legacy SRB is not being used.
+ //
+ PowerContext->Srb.Length = 0;
+
+ } else {
+ //
+ // Should not happen. Revert to legacy SRB.
+ //
+ NT_ASSERT(FALSE);
+ srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)&(PowerContext->Srb);
+ RtlZeroMemory(srbHeader, sizeof(SCSI_REQUEST_BLOCK));
+ srbHeader->Length = sizeof(SCSI_REQUEST_BLOCK);
+ srbHeader->Function = SRB_FUNCTION_EXECUTE_SCSI;
+ }
+
+ } else {
+ RtlZeroMemory(srbHeader, sizeof(SCSI_REQUEST_BLOCK));
+ srbHeader->Length = sizeof(SCSI_REQUEST_BLOCK);
+ srbHeader->Function = SRB_FUNCTION_EXECUTE_SCSI;
+ }
+
+ SrbSetOriginalRequest(srbHeader, fdoExtension->PrivateFdoData->PowerProcessIrp);
+ SrbSetSenseInfoBuffer(srbHeader, commonExtension->PartitionZeroExtension->SenseData);
+ SrbSetSenseInfoBufferLength(srbHeader, GET_FDO_EXTENSON_SENSE_DATA_LENGTH(commonExtension->PartitionZeroExtension));
+
+ SrbSetTimeOutValue(srbHeader, timeoutValue);
+ SrbAssignSrbFlags(srbHeader,
+ (SRB_FLAGS_NO_DATA_TRANSFER |
+ SRB_FLAGS_DISABLE_AUTOSENSE |
+ SRB_FLAGS_DISABLE_SYNCH_TRANSFER |
+ SRB_FLAGS_NO_QUEUE_FREEZE));
+
+ if (PowerContext->Options.LockQueue) {
+ SrbSetSrbFlags(srbHeader, SRB_FLAGS_BYPASS_LOCKED_QUEUE);
+ }
+
+ SrbSetCdbLength(srbHeader, 6);
+
+ cdb = SrbGetCdb(srbHeader);
+ RtlZeroMemory(cdb, sizeof(CDB));
+
+ cdb->START_STOP.OperationCode = SCSIOP_START_STOP_UNIT;
+ cdb->START_STOP.Start = 1;
+
+ PowerContext->PowerChangeState.PowerUp = PowerUpDeviceOn;
+
+ IoSetCompletionRoutine(fdoExtension->PrivateFdoData->PowerProcessIrp,
+ ClasspPowerUpCompletion,
+ PowerContext,
+ TRUE,
+ TRUE,
+ TRUE);
+
+ nextStack->Parameters.Scsi.Srb = (PSCSI_REQUEST_BLOCK)srbHeader;
+ nextStack->MajorFunction = IRP_MJ_SCSI;
+
+ status = IoCallDriver(commonExtension->LowerDeviceObject, fdoExtension->PrivateFdoData->PowerProcessIrp);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tIoCallDriver returned %lx\n", fdoExtension->PrivateFdoData->PowerProcessIrp, status));
+
+ } else {
+
+ //
+ // power irp is failed by lower driver. we're done.
+ //
+
+ PowerContext->FinalStatus = Irp->IoStatus.Status;
+ goto ClasspPowerUpCompletionFailure;
+ }
+
+ break;
+ }
+
+ case PowerUpDeviceStarted: { // 3
+
+ //
+ // First deal with an error if one occurred.
+ //
+
+ if (SRB_STATUS(srbHeader->SrbStatus) != SRB_STATUS_SUCCESS) {
+
+ BOOLEAN retry;
+ LONGLONG delta100nsUnits = 0;
+ ULONG secondsRemaining = 0;
+ ULONG startUnitTimeout = START_UNIT_TIMEOUT;
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_POWER, "%p\tError occured when issuing START_UNIT "
+ "command to device. Srb %p, Status %x\n",
+ Irp,
+ srbHeader,
+ srbHeader->SrbStatus));
+
+ NT_ASSERT(!(TEST_FLAG(srbHeader->SrbStatus, SRB_STATUS_QUEUE_FROZEN)));
+ NT_ASSERT((srbHeader->Function == SRB_FUNCTION_EXECUTE_SCSI) ||
+ (((PSTORAGE_REQUEST_BLOCK)srbHeader)->SrbFunction == SRB_FUNCTION_EXECUTE_SCSI));
+
+ PowerContext->RetryInterval = 0;
+ retry = InterpretSenseInfoWithoutHistory(
+ fdoExtension->DeviceObject,
+ Irp,
+ (PSCSI_REQUEST_BLOCK)srbHeader,
+ IRP_MJ_SCSI,
+ IRP_MJ_POWER,
+ fdoExtension->PrivateFdoData->MaxPowerOperationRetryCount - PowerContext->RetryCount,
+ &status,
+ &delta100nsUnits);
+
+ // NOTE: Power context is a public structure, and thus cannot be
+ // updated to use 100ns units. Therefore, must store the
+ // one-second equivalent. Round up to ensure minimum delay
+ // requirements have been met.
+ delta100nsUnits += (10*1000*1000) - 1;
+ delta100nsUnits /= (10*1000*1000);
+ // guaranteed not to have high bits set per SAL annotations
+ PowerContext->RetryInterval = (ULONG)(delta100nsUnits);
+
+
+ if ((retry == TRUE) && (PowerContext->RetryCount-- != 0)) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tRetrying failed request\n", Irp));
+
+ //
+ // Decrement the state so we come back through here the
+ // next time.
+ //
+
+ PowerContext->PowerChangeState.PowerUp--;
+
+ //
+ // Adjust start unit timeout based on remaining time if needed.
+ //
+ if (PoQueryWatchdogTime(fdoExtension->LowerPdo, &secondsRemaining)) {
+
+ if (secondsRemaining >= TIME_LEFT_FOR_UPPER_DRIVERS) {
+ secondsRemaining -= TIME_LEFT_FOR_UPPER_DRIVERS;
+ }
+
+ if (secondsRemaining < MINIMAL_START_UNIT_TIMEOUT_VALUE) {
+ startUnitTimeout = MINIMUM_START_UNIT_TIMEOUT_VALUE;
+ } else if (secondsRemaining < START_UNIT_TIMEOUT) {
+ startUnitTimeout = MINIMAL_START_UNIT_TIMEOUT_VALUE;
+ }
+ }
+
+ SrbSetTimeOutValue(srbHeader, startUnitTimeout);
+
+ RetryPowerRequest(commonExtension->DeviceObject,
+ Irp,
+ PowerContext);
+
+ break;
+
+ }
+
+ // reset retry count for UNLOCK command.
+ fdoExtension->PrivateFdoData->MaxPowerOperationRetryCount = MAXIMUM_RETRIES;
+ PowerContext->RetryCount = MAXIMUM_RETRIES;
+ }
+
+ClasspPowerUpCompletionFailure:
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tPreviously spun device up\n", Irp));
+
+ if (PowerContext->QueueLocked) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tUnlocking queue\n", Irp));
+
+ if (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+ //
+ // Will reuse SRB for a non-SCSI SRB.
+ //
+ status = InitializeStorageRequestBlock((PSTORAGE_REQUEST_BLOCK)srbHeader,
+ STORAGE_ADDRESS_TYPE_BTL8,
+ CLASS_SRBEX_NO_SRBEX_DATA_BUFFER_SIZE,
+ 0);
+ if (NT_SUCCESS(status)) {
+ ((PSTORAGE_REQUEST_BLOCK)srbHeader)->SrbFunction = SRB_FUNCTION_UNLOCK_QUEUE;
+
+ //
+ // Set length field in Power Context SRB so we know legacy SRB is not being used.
+ //
+ PowerContext->Srb.Length = 0;
+
+ } else {
+ //
+ // Should not occur. Revert to legacy SRB.
+ NT_ASSERT(FALSE);
+ srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)&(PowerContext->Srb);
+ RtlZeroMemory(srbHeader, sizeof(SCSI_REQUEST_BLOCK));
+ srbHeader->Length = sizeof(SCSI_REQUEST_BLOCK);
+ srbHeader->Function = SRB_FUNCTION_UNLOCK_QUEUE;
+ }
+ } else {
+ RtlZeroMemory(srbHeader, sizeof(SCSI_REQUEST_BLOCK));
+ srbHeader->Length = sizeof(SCSI_REQUEST_BLOCK);
+ srbHeader->Function = SRB_FUNCTION_UNLOCK_QUEUE;
+ }
+ SrbAssignSrbFlags(srbHeader, SRB_FLAGS_BYPASS_LOCKED_QUEUE);
+ SrbSetOriginalRequest(srbHeader, fdoExtension->PrivateFdoData->PowerProcessIrp);
+
+ nextStack->Parameters.Scsi.Srb = (PSCSI_REQUEST_BLOCK)srbHeader;
+ nextStack->MajorFunction = IRP_MJ_SCSI;
+
+ PowerContext->PowerChangeState.PowerUp = PowerUpDeviceStarted;
+
+ IoSetCompletionRoutine(fdoExtension->PrivateFdoData->PowerProcessIrp,
+ ClasspPowerUpCompletion,
+ PowerContext,
+ TRUE,
+ TRUE,
+ TRUE);
+
+ status = IoCallDriver(commonExtension->LowerDeviceObject, fdoExtension->PrivateFdoData->PowerProcessIrp);
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tIoCallDriver returned %lx\n",
+ fdoExtension->PrivateFdoData->PowerProcessIrp, status));
+ break;
+ }
+
+ // Fall-through to next case...
+
+ }
+
+ case PowerUpDeviceUnlocked: {
+
+ //
+ // This is the end of the dance.
+ // We're ignoring possible intermediate error conditions ....
+ //
+
+ if (PowerContext->QueueLocked) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tPreviously unlocked queue\n", OriginalIrp));
+
+ //
+ // If the lower device is being removed, the IRP's status may be STATUS_DELETE_PENDING or
+ // STATUS_DEVICE_DOES_NOT_EXIST.
+ //
+ if((NT_SUCCESS(Irp->IoStatus.Status) == FALSE) &&
+ (Irp->IoStatus.Status != STATUS_DELETE_PENDING) &&
+ (Irp->IoStatus.Status != STATUS_DEVICE_DOES_NOT_EXIST)) {
+
+
+ NT_ASSERT(FALSE);
+ }
+
+ } else {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tFall-through (queue not locked)\n", OriginalIrp));
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tFreeing srb and completing\n", OriginalIrp));
+
+ status = PowerContext->FinalStatus;
+ OriginalIrp->IoStatus.Status = status;
+
+ //
+ // Set the new power state
+ //
+
+ if (NT_SUCCESS(status)) {
+ fdoExtension->DevicePowerState = currentStack->Parameters.Power.State.DeviceState;
+ }
+
+ //
+ // Check whether failure detection is enabled
+ //
+
+ if ((fdoExtension->FailurePredictionInfo != NULL) &&
+ (fdoExtension->FailurePredictionInfo->Method != FailurePredictionNone)) {
+ FailurePredictionEnabled = TRUE;
+ }
+
+ //
+ // Enable tick timer at end of D0 processing if it was previously enabled.
+ //
+
+ if ((commonExtension->DriverExtension->InitData.ClassTick != NULL) ||
+ ((fdoExtension->MediaChangeDetectionInfo != NULL) &&
+ (fdoExtension->FunctionSupportInfo != NULL) &&
+ (fdoExtension->FunctionSupportInfo->AsynchronousNotificationSupported == FALSE)) ||
+ (FailurePredictionEnabled)) {
+
+
+ //
+ // If failure prediction is turned on and we've been powered
+ // off longer than the failure prediction query period then
+ // force the query on the next timer tick.
+ //
+
+ if ((FailurePredictionEnabled) && (ClasspFailurePredictionPeriodMissed(fdoExtension))) {
+ fdoExtension->FailurePredictionInfo->CountDown = 1;
+ }
+
+ //
+ // Finally, enable the timer.
+ //
+
+ ClasspEnableTimer(fdoExtension);
+ }
+
+ //
+ // Indicate to Po that we've been successfully powered up so
+ // it can do it's notification stuff.
+ //
+
+ PoSetPowerState(PowerContext->DeviceObject,
+ currentStack->Parameters.Power.Type,
+ currentStack->Parameters.Power.State);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tStarting next power irp\n", OriginalIrp));
+
+ ClassReleaseRemoveLock(PowerContext->DeviceObject, OriginalIrp);
+
+ PowerContext->InUse = FALSE;
+
+ PoStartNextPowerIrp(OriginalIrp);
+
+ // prevent from completing the irp allocated by ourselves
+ if ((fdoExtension->PrivateFdoData) && (Irp == fdoExtension->PrivateFdoData->PowerProcessIrp)) {
+ // complete original irp if we are processing powerprocess irp,
+ // otherwise, by returning status other than STATUS_MORE_PROCESSING_REQUIRED, IO manager will complete it.
+ ClassCompleteRequest(commonExtension->DeviceObject, OriginalIrp, IO_NO_INCREMENT);
+ status = STATUS_MORE_PROCESSING_REQUIRED;
+ }
+
+ return status;
+ }
+ }
+
+ return STATUS_MORE_PROCESSING_REQUIRED;
+} // end ClasspPowerUpCompletion()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClasspPowerDownCompletion()
+
+Routine Description:
+
+ This routine is used for intermediate completion of a power down request.
+ PowerDown performs the following sequence to power down the device.
+
+ 1. The queue(s) in the lower stack is/are "power locked" to ensure new
+ requests are held until the power-down process is complete.
+
+ 2. A request to the lower layers to wait for all outstanding IO to
+ complete ("quiescence") is sent. This ensures we don't power down
+ the device while it's in the middle of handling IO.
+
+ 3. A request to flush the device's cache is sent. The device may lose
+ power when we forward the D-IRP so any data in volatile storage must
+ be committed to non-volatile storage first.
+
+ 4. A "stop unit" request is sent to the device to notify it that it
+ is about to be powered down.
+
+ 5. The D-IRP is forwarded down the stack. If D3Cold is supported and
+ enabled via ACPI, the ACPI filter driver may power off the device.
+
+ 6. Once the D-IRP is completed by the lower stack, we will "power
+ unlock" the queue(s). (It is the lower stack's responsibility to
+ continue to queue any IO that requires hardware access until the
+ device is powered up again.)
+
+Arguments:
+
+ DeviceObject - the device object being powered down
+
+ Irp - the IO_REQUEST_PACKET containing the power request
+
+ Context - the class power context used to perform port/class operations.
+
+Return Value:
+
+ STATUS_MORE_PROCESSING_REQUIRED or
+ STATUS_SUCCESS
+
+--*/
+NTSTATUS
+ClasspPowerDownCompletion(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN PVOID Context
+ )
+{
+ PCLASS_POWER_CONTEXT PowerContext = (PCLASS_POWER_CONTEXT)Context;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = PowerContext->DeviceObject->DeviceExtension;
+ PCOMMON_DEVICE_EXTENSION commonExtension = PowerContext->DeviceObject->DeviceExtension;
+ PIRP OriginalIrp = PowerContext->Irp;
+
+ // currentStack is for original power irp
+ // nextStack is for power process irp
+ PIO_STACK_LOCATION currentStack = IoGetCurrentIrpStackLocation(OriginalIrp);
+ PIO_STACK_LOCATION nextStack = IoGetNextIrpStackLocation(fdoExtension->PrivateFdoData->PowerProcessIrp);
+
+ NTSTATUS status = STATUS_MORE_PROCESSING_REQUIRED;
+ PSTORAGE_REQUEST_BLOCK_HEADER srbHeader;
+ ULONG srbFlags;
+
+ UNREFERENCED_PARAMETER(DeviceObject);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "ClasspPowerDownCompletion: Device Object %p, "
+ "Irp %p, Context %p\n",
+ PowerContext->DeviceObject, Irp, Context));
+
+ if (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+ srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)&(fdoExtension->PrivateFdoData->PowerSrb.SrbEx);
+
+ //
+ // Check if reverted to using legacy SRB.
+ //
+ if (PowerContext->Srb.Length == sizeof(SCSI_REQUEST_BLOCK)) {
+ srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)&(PowerContext->Srb);
+ }
+ } else {
+ srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)&(PowerContext->Srb);
+ }
+
+ srbFlags = SrbGetSrbFlags(srbHeader);
+ NT_ASSERT(!TEST_FLAG(srbFlags, SRB_FLAGS_FREE_SENSE_BUFFER));
+ NT_ASSERT(!TEST_FLAG(srbFlags, SRB_FLAGS_PORT_DRIVER_ALLOCSENSE));
+ NT_ASSERT(PowerContext->Options.PowerDown == TRUE);
+ NT_ASSERT(PowerContext->Options.HandleSpinDown);
+
+ if ((Irp == OriginalIrp) && (Irp->PendingReturned)) {
+ // only for original power irp
+ IoMarkIrpPending(Irp);
+ }
+
+ PowerContext->PowerChangeState.PowerDown3++;
+
+ switch(PowerContext->PowerChangeState.PowerDown3) {
+
+ case PowerDownDeviceLocked3: {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tPreviously sent power lock\n", Irp));
+
+ if ((PowerContext->Options.LockQueue == TRUE) &&
+ (!NT_SUCCESS(Irp->IoStatus.Status))) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tIrp status was %lx\n",
+ Irp,
+ Irp->IoStatus.Status));
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tSrb status was %lx\n",
+ Irp,
+ srbHeader->SrbStatus));
+
+
+
+ //
+ // Lock was not successful - throw down the power IRP
+ // by itself and don't try to spin down the drive or unlock
+ // the queue.
+ //
+
+ //
+ // Set the new power state
+ //
+
+ fdoExtension->DevicePowerState =
+ currentStack->Parameters.Power.State.DeviceState;
+
+ //
+ // Indicate to Po that we've been successfully powered down
+ // so it can do it's notification stuff.
+ //
+
+ IoCopyCurrentIrpStackLocationToNext(OriginalIrp);
+ IoSetCompletionRoutine(OriginalIrp,
+ ClasspStartNextPowerIrpCompletion,
+ PowerContext,
+ TRUE,
+ TRUE,
+ TRUE);
+
+ PoSetPowerState(PowerContext->DeviceObject,
+ currentStack->Parameters.Power.Type,
+ currentStack->Parameters.Power.State);
+
+ fdoExtension->PowerDownInProgress = FALSE;
+
+ ClassReleaseRemoveLock(commonExtension->DeviceObject,
+ OriginalIrp);
+
+ PoCallDriver(commonExtension->LowerDeviceObject, OriginalIrp);
+
+ return STATUS_MORE_PROCESSING_REQUIRED;
+
+ } else {
+ //
+ // Lock the device queue succeeded. Now wait for all outstanding IO to complete.
+ // To do this, Srb with SRB_FUNCTION_QUIESCE_DEVICE will be sent down with default timeout value.
+ // We need to tolerant failure of this request, no retry will be made.
+ //
+ PowerContext->QueueLocked = (UCHAR) PowerContext->Options.LockQueue;
+
+ //
+ // No retry on device quiescence reqeust
+ //
+ fdoExtension->PrivateFdoData->MaxPowerOperationRetryCount = 0;
+ PowerContext->RetryCount = 0;
+
+ if (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+ srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)&(fdoExtension->PrivateFdoData->PowerSrb.SrbEx);
+
+ //
+ // Initialize extended SRB for a SRB_FUNCTION_LOCK_QUEUE
+ //
+ status = InitializeStorageRequestBlock((PSTORAGE_REQUEST_BLOCK)srbHeader,
+ STORAGE_ADDRESS_TYPE_BTL8,
+ CLASS_SRBEX_NO_SRBEX_DATA_BUFFER_SIZE,
+ 0);
+ if (NT_SUCCESS(status)) {
+ ((PSTORAGE_REQUEST_BLOCK)srbHeader)->SrbFunction = SRB_FUNCTION_QUIESCE_DEVICE;
+ } else {
+ //
+ // Should not happen. Revert to legacy SRB.
+ //
+ NT_ASSERT(FALSE);
+ srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)&(PowerContext->Srb);
+ srbHeader->Length = sizeof(SCSI_REQUEST_BLOCK);
+ srbHeader->Function = SRB_FUNCTION_QUIESCE_DEVICE;
+ }
+ } else {
+ srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)&(PowerContext->Srb);
+ srbHeader->Length = sizeof(SCSI_REQUEST_BLOCK);
+ srbHeader->Function = SRB_FUNCTION_QUIESCE_DEVICE;
+ }
+
+ SrbSetOriginalRequest(srbHeader, fdoExtension->PrivateFdoData->PowerProcessIrp);
+ SrbSetTimeOutValue(srbHeader, fdoExtension->TimeOutValue);
+
+ SrbAssignSrbFlags(srbHeader,
+ (SRB_FLAGS_NO_DATA_TRANSFER |
+ SRB_FLAGS_DISABLE_AUTOSENSE |
+ SRB_FLAGS_DISABLE_SYNCH_TRANSFER |
+ SRB_FLAGS_NO_QUEUE_FREEZE |
+ SRB_FLAGS_BYPASS_LOCKED_QUEUE |
+ SRB_FLAGS_D3_PROCESSING));
+
+ IoSetCompletionRoutine(fdoExtension->PrivateFdoData->PowerProcessIrp,
+ ClasspPowerDownCompletion,
+ PowerContext,
+ TRUE,
+ TRUE,
+ TRUE);
+
+ nextStack->Parameters.Scsi.Srb = (PSCSI_REQUEST_BLOCK)srbHeader;
+ nextStack->MajorFunction = IRP_MJ_SCSI;
+
+ status = IoCallDriver(commonExtension->LowerDeviceObject, fdoExtension->PrivateFdoData->PowerProcessIrp);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tIoCallDriver returned %lx\n", fdoExtension->PrivateFdoData->PowerProcessIrp, status));
+ break;
+ }
+
+ }
+
+ case PowerDownDeviceQuiesced3: {
+
+ PCDB cdb;
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tPreviously sent device quiesce\n", Irp));
+
+ //
+ // don't care the result of device quiesce, we've made the effort.
+ // continue on sending other SCSI commands anyway.
+ //
+
+
+ if (!TEST_FLAG(fdoExtension->PrivateFdoData->HackFlags,
+ FDO_HACK_NO_SYNC_CACHE)) {
+
+ //
+ // send SCSIOP_SYNCHRONIZE_CACHE
+ //
+
+ fdoExtension->PrivateFdoData->MaxPowerOperationRetryCount = MAXIMUM_RETRIES;
+ PowerContext->RetryCount = MAXIMUM_RETRIES;
+
+ if (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+ status = InitializeStorageRequestBlock((PSTORAGE_REQUEST_BLOCK)srbHeader,
+ STORAGE_ADDRESS_TYPE_BTL8,
+ CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE,
+ 1,
+ SrbExDataTypeScsiCdb16);
+ if (NT_SUCCESS(status)) {
+ ((PSTORAGE_REQUEST_BLOCK)srbHeader)->SrbFunction = SRB_FUNCTION_EXECUTE_SCSI;
+
+ //
+ // Set length field in Power Context SRB so we know legacy SRB is not being used.
+ //
+ PowerContext->Srb.Length = 0;
+
+ } else {
+ //
+ // Should not occur. Revert to legacy SRB.
+ NT_ASSERT(FALSE);
+ srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)&(PowerContext->Srb);
+ RtlZeroMemory(srbHeader, sizeof(SCSI_REQUEST_BLOCK));
+ srbHeader->Length = sizeof(SCSI_REQUEST_BLOCK);
+ srbHeader->Function = SRB_FUNCTION_EXECUTE_SCSI;
+ }
+
+ } else {
+ RtlZeroMemory(srbHeader, sizeof(SCSI_REQUEST_BLOCK));
+ srbHeader->Length = sizeof(SCSI_REQUEST_BLOCK);
+ srbHeader->Function = SRB_FUNCTION_EXECUTE_SCSI;
+ }
+
+
+ SrbSetOriginalRequest(srbHeader, fdoExtension->PrivateFdoData->PowerProcessIrp);
+ SrbSetSenseInfoBuffer(srbHeader, commonExtension->PartitionZeroExtension->SenseData);
+ SrbSetSenseInfoBufferLength(srbHeader, GET_FDO_EXTENSON_SENSE_DATA_LENGTH(commonExtension->PartitionZeroExtension));
+ SrbSetTimeOutValue(srbHeader, fdoExtension->TimeOutValue);
+
+ SrbAssignSrbFlags(srbHeader,
+ (SRB_FLAGS_NO_DATA_TRANSFER |
+ SRB_FLAGS_DISABLE_AUTOSENSE |
+ SRB_FLAGS_DISABLE_SYNCH_TRANSFER |
+ SRB_FLAGS_NO_QUEUE_FREEZE |
+ SRB_FLAGS_BYPASS_LOCKED_QUEUE |
+ SRB_FLAGS_D3_PROCESSING));
+
+ SrbSetCdbLength(srbHeader, 10);
+
+ cdb = SrbGetCdb(srbHeader);
+
+ RtlZeroMemory(cdb, sizeof(CDB));
+ cdb->SYNCHRONIZE_CACHE10.OperationCode = SCSIOP_SYNCHRONIZE_CACHE;
+
+ IoSetCompletionRoutine(fdoExtension->PrivateFdoData->PowerProcessIrp,
+ ClasspPowerDownCompletion,
+ PowerContext,
+ TRUE,
+ TRUE,
+ TRUE);
+
+ nextStack->Parameters.Scsi.Srb = (PSCSI_REQUEST_BLOCK)srbHeader;
+ nextStack->MajorFunction = IRP_MJ_SCSI;
+
+ status = IoCallDriver(commonExtension->LowerDeviceObject, fdoExtension->PrivateFdoData->PowerProcessIrp);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tIoCallDriver returned %lx\n", fdoExtension->PrivateFdoData->PowerProcessIrp, status));
+ break;
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_POWER, "(%p)\tPower Down: not sending SYNCH_CACHE\n",
+ PowerContext->DeviceObject));
+ PowerContext->PowerChangeState.PowerDown3++;
+ srbHeader->SrbStatus = SRB_STATUS_SUCCESS;
+ // and fall through....
+ }
+ // no break in case the device doesn't like synch_cache commands
+
+ }
+
+ case PowerDownDeviceFlushed3: {
+
+ PCDB cdb;
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tPreviously send SCSIOP_SYNCHRONIZE_CACHE\n",
+ Irp));
+
+ //
+ // SCSIOP_SYNCHRONIZE_CACHE was sent
+ //
+
+ if (SRB_STATUS(srbHeader->SrbStatus) != SRB_STATUS_SUCCESS) {
+
+ BOOLEAN retry;
+ LONGLONG delta100nsUnits = 0;
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_POWER, "(%p)\tError occured when issuing "
+ "SYNCHRONIZE_CACHE command to device. "
+ "Srb %p, Status %lx\n",
+ Irp,
+ srbHeader,
+ srbHeader->SrbStatus));
+
+ NT_ASSERT(!(TEST_FLAG(srbHeader->SrbStatus, SRB_STATUS_QUEUE_FROZEN)));
+ NT_ASSERT((srbHeader->Function == SRB_FUNCTION_EXECUTE_SCSI) ||
+ (((PSTORAGE_REQUEST_BLOCK)srbHeader)->SrbFunction == SRB_FUNCTION_EXECUTE_SCSI));
+
+ PowerContext->RetryInterval = 0;
+ retry = InterpretSenseInfoWithoutHistory(
+ fdoExtension->DeviceObject,
+ Irp,
+ (PSCSI_REQUEST_BLOCK)srbHeader,
+ IRP_MJ_SCSI,
+ IRP_MJ_POWER,
+ fdoExtension->PrivateFdoData->MaxPowerOperationRetryCount - PowerContext->RetryCount,
+ &status,
+ &delta100nsUnits);
+
+ // NOTE: Power context is a public structure, and thus cannot be
+ // updated to use 100ns units. Therefore, must store the
+ // one-second equivalent. Round up to ensure minimum delay
+ // requirements have been met.
+ delta100nsUnits += (10*1000*1000) - 1;
+ delta100nsUnits /= (10*1000*1000);
+ // guaranteed not to have high bits set per SAL annotations
+ PowerContext->RetryInterval = (ULONG)(delta100nsUnits);
+
+
+ if ((retry == TRUE) && (PowerContext->RetryCount-- != 0)) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tRetrying failed request\n", Irp));
+
+ //
+ // decrement the state so we come back through here
+ // the next time.
+ //
+
+ PowerContext->PowerChangeState.PowerDown3--;
+ RetryPowerRequest(commonExtension->DeviceObject,
+ Irp,
+ PowerContext);
+ break;
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tSYNCHRONIZE_CACHE not retried\n", Irp));
+ fdoExtension->PrivateFdoData->MaxPowerOperationRetryCount = MAXIMUM_RETRIES;
+ PowerContext->RetryCount = MAXIMUM_RETRIES;
+ } // end !SRB_STATUS_SUCCESS
+
+ //
+ // note: we are purposefully ignoring any errors. if the drive
+ // doesn't support a synch_cache, then we're up a creek
+ // anyways.
+ //
+
+ if ((currentStack->Parameters.Power.State.DeviceState == PowerDeviceD3) &&
+ (currentStack->Parameters.Power.ShutdownType == PowerActionHibernate) &&
+ (commonExtension->HibernationPathCount != 0)) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tPower Down: not sending SPIN DOWN due to hibernation path\n",
+ PowerContext->DeviceObject));
+
+ PowerContext->PowerChangeState.PowerDown3++;
+ srbHeader->SrbStatus = SRB_STATUS_SUCCESS;
+ status = STATUS_SUCCESS;
+
+ // Fall through to next case...
+
+ } else {
+ // Send STOP UNIT command. As "Imme" bit is set to '1', this command should be completed in short time.
+ // This command is at low importance, failure of this command has very small impact.
+
+ ULONG secondsRemaining;
+ ULONG timeoutValue;
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tSending stop unit to device\n", Irp));
+
+ if (PoQueryWatchdogTime(fdoExtension->LowerPdo, &secondsRemaining)) {
+ // plan to leave some time (TIME_LEFT_FOR_LOWER_DRIVERS) to lower level drivers
+ // for processing the original power irp.
+ if (secondsRemaining >= (TIME_LEFT_FOR_LOWER_DRIVERS + DEFAULT_IO_TIMEOUT_VALUE)) {
+ fdoExtension->PrivateFdoData->MaxPowerOperationRetryCount =
+ (secondsRemaining - TIME_LEFT_FOR_LOWER_DRIVERS) / DEFAULT_IO_TIMEOUT_VALUE;
+
+ // * No 'short' timeouts
+ //
+ // timeoutValue = (secondsRemaining - TIME_LEFT_FOR_LOWER_DRIVERS) %
+ // DEFAULT_IO_TIMEOUT_VALUE;
+ // if (timeoutValue < MINIMUM_STOP_UNIT_TIMEOUT_VALUE)
+ // {
+ if (--fdoExtension->PrivateFdoData->MaxPowerOperationRetryCount)
+ {
+ timeoutValue = DEFAULT_IO_TIMEOUT_VALUE;
+ } else {
+ timeoutValue = secondsRemaining - TIME_LEFT_FOR_LOWER_DRIVERS;
+ }
+ // }
+
+ // Limit to maximum retry count.
+ if (fdoExtension->PrivateFdoData->MaxPowerOperationRetryCount > MAXIMUM_RETRIES) {
+ fdoExtension->PrivateFdoData->MaxPowerOperationRetryCount = MAXIMUM_RETRIES;
+ }
+ } else {
+ // issue the command with minimum timeout value and do not retry on it.
+ fdoExtension->PrivateFdoData->MaxPowerOperationRetryCount = 0;
+
+ // minimum as MINIMUM_STOP_UNIT_TIMEOUT_VALUE.
+ if (secondsRemaining > 2 * MINIMUM_STOP_UNIT_TIMEOUT_VALUE) {
+ timeoutValue = secondsRemaining - MINIMUM_STOP_UNIT_TIMEOUT_VALUE;
+ } else {
+ timeoutValue = MINIMUM_STOP_UNIT_TIMEOUT_VALUE;
+ }
+
+ }
+
+ } else {
+ // do not know how long, use default values.
+ fdoExtension->PrivateFdoData->MaxPowerOperationRetryCount = MAXIMUM_RETRIES;
+ timeoutValue = DEFAULT_IO_TIMEOUT_VALUE;
+ }
+
+ //
+ // Issue STOP UNIT command to the device.
+ //
+
+ PowerContext->RetryCount = fdoExtension->PrivateFdoData->MaxPowerOperationRetryCount;
+
+ if (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+ status = InitializeStorageRequestBlock((PSTORAGE_REQUEST_BLOCK)srbHeader,
+ STORAGE_ADDRESS_TYPE_BTL8,
+ CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE,
+ 1,
+ SrbExDataTypeScsiCdb16);
+ if (NT_SUCCESS(status)) {
+ ((PSTORAGE_REQUEST_BLOCK)srbHeader)->SrbFunction = SRB_FUNCTION_EXECUTE_SCSI;
+
+ //
+ // Set length field in Power Context SRB so we know legacy SRB is not being used.
+ //
+ PowerContext->Srb.Length = 0;
+
+ } else {
+ //
+ // Should not occur. Revert to legacy SRB.
+ //
+ NT_ASSERT(FALSE);
+ srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)&(PowerContext->Srb);
+ RtlZeroMemory(srbHeader, sizeof(SCSI_REQUEST_BLOCK));
+ srbHeader->Length = sizeof(SCSI_REQUEST_BLOCK);
+ srbHeader->Function = SRB_FUNCTION_EXECUTE_SCSI;
+ }
+
+ } else {
+ RtlZeroMemory(srbHeader, sizeof(SCSI_REQUEST_BLOCK));
+ srbHeader->Length = sizeof(SCSI_REQUEST_BLOCK);
+ srbHeader->Function = SRB_FUNCTION_EXECUTE_SCSI;
+ }
+
+ SrbSetOriginalRequest(srbHeader, fdoExtension->PrivateFdoData->PowerProcessIrp);
+ SrbSetSenseInfoBuffer(srbHeader, commonExtension->PartitionZeroExtension->SenseData);
+ SrbSetSenseInfoBufferLength(srbHeader, GET_FDO_EXTENSON_SENSE_DATA_LENGTH(commonExtension->PartitionZeroExtension));
+ SrbSetTimeOutValue(srbHeader, timeoutValue);
+
+
+ SrbAssignSrbFlags(srbHeader,
+ (SRB_FLAGS_NO_DATA_TRANSFER |
+ SRB_FLAGS_DISABLE_AUTOSENSE |
+ SRB_FLAGS_DISABLE_SYNCH_TRANSFER |
+ SRB_FLAGS_NO_QUEUE_FREEZE |
+ SRB_FLAGS_BYPASS_LOCKED_QUEUE |
+ SRB_FLAGS_D3_PROCESSING));
+
+ SrbSetCdbLength(srbHeader, 6);
+
+ cdb = SrbGetCdb(srbHeader);
+ RtlZeroMemory(cdb, sizeof(CDB));
+
+ cdb->START_STOP.OperationCode = SCSIOP_START_STOP_UNIT;
+ cdb->START_STOP.Start = 0;
+ cdb->START_STOP.Immediate = 1;
+
+ IoSetCompletionRoutine(fdoExtension->PrivateFdoData->PowerProcessIrp,
+ ClasspPowerDownCompletion,
+ PowerContext,
+ TRUE,
+ TRUE,
+ TRUE);
+
+ nextStack->Parameters.Scsi.Srb = (PSCSI_REQUEST_BLOCK)srbHeader;
+ nextStack->MajorFunction = IRP_MJ_SCSI;
+
+ status = IoCallDriver(commonExtension->LowerDeviceObject, fdoExtension->PrivateFdoData->PowerProcessIrp);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tIoCallDriver returned %lx\n", fdoExtension->PrivateFdoData->PowerProcessIrp, status));
+ break;
+ }
+ }
+
+ case PowerDownDeviceStopped3: {
+
+ BOOLEAN ignoreError = TRUE;
+
+ //
+ // stop was sent
+ //
+
+ if (SRB_STATUS(srbHeader->SrbStatus) != SRB_STATUS_SUCCESS) {
+
+ BOOLEAN retry;
+ LONGLONG delta100nsUnits = 0;
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_POWER, "(%p)\tError occured when issueing STOP_UNIT "
+ "command to device. Srb %p, Status %lx\n",
+ Irp,
+ srbHeader,
+ srbHeader->SrbStatus));
+
+ NT_ASSERT(!(TEST_FLAG(srbHeader->SrbStatus, SRB_STATUS_QUEUE_FROZEN)));
+ NT_ASSERT((srbHeader->Function == SRB_FUNCTION_EXECUTE_SCSI) ||
+ (((PSTORAGE_REQUEST_BLOCK)srbHeader)->SrbFunction == SRB_FUNCTION_EXECUTE_SCSI));
+
+ PowerContext->RetryInterval = 0;
+ retry = InterpretSenseInfoWithoutHistory(
+ fdoExtension->DeviceObject,
+ Irp,
+ (PSCSI_REQUEST_BLOCK)srbHeader,
+ IRP_MJ_SCSI,
+ IRP_MJ_POWER,
+ fdoExtension->PrivateFdoData->MaxPowerOperationRetryCount - PowerContext->RetryCount,
+ &status,
+ &delta100nsUnits);
+
+ // NOTE: Power context is a public structure, and thus cannot be
+ // updated to use 100ns units. Therefore, must store the
+ // one-second equivalent. Round up to ensure minimum delay
+ // requirements have been met.
+ delta100nsUnits += (10*1000*1000) - 1;
+ delta100nsUnits /= (10*1000*1000);
+ // guaranteed not to have high bits set per SAL annotations
+ PowerContext->RetryInterval = (ULONG)(delta100nsUnits);
+
+
+ if ((retry == TRUE) && (PowerContext->RetryCount-- != 0)) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tRetrying failed request\n", Irp));
+
+ //
+ // decrement the state so we come back through here
+ // the next time.
+ //
+
+ PowerContext->PowerChangeState.PowerDown3--;
+
+ SrbSetTimeOutValue(srbHeader, DEFAULT_IO_TIMEOUT_VALUE);
+
+ RetryPowerRequest(commonExtension->DeviceObject,
+ Irp,
+ PowerContext);
+ break;
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tSTOP_UNIT not retried\n", Irp));
+ fdoExtension->PrivateFdoData->MaxPowerOperationRetryCount = MAXIMUM_RETRIES;
+ PowerContext->RetryCount = MAXIMUM_RETRIES;
+
+ } // end !SRB_STATUS_SUCCESS
+
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tPreviously sent stop unit\n", Irp));
+
+ //
+ // some operations, such as a physical format in progress,
+ // should not be ignored and should fail the power operation.
+ //
+
+ if (!NT_SUCCESS(status)) {
+
+ PVOID senseBuffer = SrbGetSenseInfoBuffer(srbHeader);
+
+ if (TEST_FLAG(srbHeader->SrbStatus, SRB_STATUS_AUTOSENSE_VALID) &&
+ (senseBuffer != NULL)) {
+
+ BOOLEAN validSense = FALSE;
+ UCHAR senseKey = 0;
+ UCHAR additionalSenseCode = 0;
+ UCHAR additionalSenseCodeQualifier = 0;
+
+ validSense = ScsiGetSenseKeyAndCodes(senseBuffer,
+ SrbGetSenseInfoBufferLength(srbHeader),
+ SCSI_SENSE_OPTIONS_FIXED_FORMAT_IF_UNKNOWN_FORMAT_INDICATED,
+ &senseKey,
+ &additionalSenseCode,
+ &additionalSenseCodeQualifier);
+
+ if (validSense) {
+ if ((senseKey == SCSI_SENSE_NOT_READY) &&
+ (additionalSenseCode == SCSI_ADSENSE_LUN_NOT_READY) &&
+ (additionalSenseCodeQualifier == SCSI_SENSEQ_FORMAT_IN_PROGRESS)) {
+
+ ignoreError = FALSE;
+ PowerContext->FinalStatus = STATUS_DEVICE_BUSY;
+ status = PowerContext->FinalStatus;
+ }
+ }
+ }
+ }
+
+ if (NT_SUCCESS(status) || ignoreError) {
+
+ //
+ // Issue the original power request to the lower driver.
+ //
+
+ IoCopyCurrentIrpStackLocationToNext(OriginalIrp);
+
+ IoSetCompletionRoutine(OriginalIrp,
+ ClasspPowerDownCompletion,
+ PowerContext,
+ TRUE,
+ TRUE,
+ TRUE);
+
+ status = PoCallDriver(commonExtension->LowerDeviceObject, OriginalIrp);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tPoCallDriver returned %lx\n", OriginalIrp, status));
+ break;
+ }
+
+ // else fall through w/o sending the power irp, since the device
+ // is reporting an error that would be "really bad" to power down
+ // during.
+
+ }
+
+ case PowerDownDeviceOff3: {
+
+ //
+ // SpinDown request completed ... whether it succeeded or not is
+ // another matter entirely.
+ //
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tPreviously sent power irp\n", OriginalIrp));
+
+ if (PowerContext->QueueLocked) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tUnlocking queue\n", OriginalIrp));
+
+ if (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+ //
+ // Will reuse SRB for a non-SCSI SRB.
+ //
+ status = InitializeStorageRequestBlock((PSTORAGE_REQUEST_BLOCK)srbHeader,
+ STORAGE_ADDRESS_TYPE_BTL8,
+ CLASS_SRBEX_NO_SRBEX_DATA_BUFFER_SIZE,
+ 0);
+ if (NT_SUCCESS(status)) {
+ ((PSTORAGE_REQUEST_BLOCK)srbHeader)->SrbFunction = SRB_FUNCTION_UNLOCK_QUEUE;
+
+ //
+ // Set length field in Power Context SRB so we know legacy SRB is not being used.
+ //
+ PowerContext->Srb.Length = 0;
+
+ } else {
+ //
+ // Should not occur. Revert to legacy SRB.
+ //
+ NT_ASSERT(FALSE);
+ srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)&(PowerContext->Srb);
+ RtlZeroMemory(srbHeader, sizeof(SCSI_REQUEST_BLOCK));
+ srbHeader->Length = sizeof(SCSI_REQUEST_BLOCK);
+ srbHeader->Function = SRB_FUNCTION_UNLOCK_QUEUE;
+ }
+ } else {
+ RtlZeroMemory(srbHeader, sizeof(SCSI_REQUEST_BLOCK));
+ srbHeader->Length = sizeof(SCSI_REQUEST_BLOCK);
+ srbHeader->Function = SRB_FUNCTION_UNLOCK_QUEUE;
+ }
+
+ SrbSetOriginalRequest(srbHeader, fdoExtension->PrivateFdoData->PowerProcessIrp);
+ SrbAssignSrbFlags(srbHeader, (SRB_FLAGS_BYPASS_LOCKED_QUEUE |
+ SRB_FLAGS_D3_PROCESSING));
+
+ nextStack->Parameters.Scsi.Srb = (PSCSI_REQUEST_BLOCK)srbHeader;
+ nextStack->MajorFunction = IRP_MJ_SCSI;
+
+ IoSetCompletionRoutine(fdoExtension->PrivateFdoData->PowerProcessIrp,
+ ClasspPowerDownCompletion,
+ PowerContext,
+ TRUE,
+ TRUE,
+ TRUE);
+
+ status = IoCallDriver(commonExtension->LowerDeviceObject, fdoExtension->PrivateFdoData->PowerProcessIrp);
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tIoCallDriver returned %lx\n",
+ fdoExtension->PrivateFdoData->PowerProcessIrp,
+ status));
+ break;
+ }
+
+ }
+
+ case PowerDownDeviceUnlocked3: {
+
+ //
+ // This is the end of the dance.
+ // We're ignoring possible intermediate error conditions ....
+ //
+
+ if (PowerContext->QueueLocked == FALSE) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tFall through (queue not locked)\n", OriginalIrp));
+ } else {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tPreviously unlocked queue\n", OriginalIrp));
+ NT_ASSERT(NT_SUCCESS(Irp->IoStatus.Status));
+ NT_ASSERT(srbHeader->SrbStatus == SRB_STATUS_SUCCESS);
+
+ if (NT_SUCCESS(Irp->IoStatus.Status)) {
+ PowerContext->QueueLocked = FALSE;
+ }
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tFreeing srb and completing\n", OriginalIrp));
+ status = PowerContext->FinalStatus; // allow failure to propogate
+
+ OriginalIrp->IoStatus.Status = status;
+ OriginalIrp->IoStatus.Information = 0;
+
+ if (NT_SUCCESS(status)) {
+
+ //
+ // Set the new power state
+ //
+
+ fdoExtension->DevicePowerState =
+ currentStack->Parameters.Power.State.DeviceState;
+
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tStarting next power irp\n", OriginalIrp));
+
+ ClassReleaseRemoveLock(PowerContext->DeviceObject, OriginalIrp);
+
+ PowerContext->InUse = FALSE;
+
+ PoStartNextPowerIrp(OriginalIrp);
+
+ fdoExtension->PowerDownInProgress = FALSE;
+
+ // prevent from completing the irp allocated by ourselves
+ if (Irp == fdoExtension->PrivateFdoData->PowerProcessIrp) {
+ // complete original irp if we are processing powerprocess irp,
+ // otherwise, by returning status other than STATUS_MORE_PROCESSING_REQUIRED, IO manager will complete it.
+ ClassCompleteRequest(commonExtension->DeviceObject, OriginalIrp, IO_NO_INCREMENT);
+ status = STATUS_MORE_PROCESSING_REQUIRED;
+ }
+
+ return status;
+ }
+ }
+
+ return STATUS_MORE_PROCESSING_REQUIRED;
+} // end ClasspPowerDownCompletion()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClasspPowerHandler()
+
+Routine Description:
+
+ This routine reduces the number of useless spinups and spindown requests
+ sent to a given device by ignoring transitions to power states we are
+ currently in.
+
+ ISSUE-2000/02/20-henrygab - by ignoring spin-up requests, we may be
+ allowing the drive
+
+Arguments:
+
+ DeviceObject - the device object which is transitioning power states
+ Irp - the power irp
+ Options - a set of flags indicating what the device handles
+
+Return Value:
+
+--*/
+NTSTATUS
+ClasspPowerHandler(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN CLASS_POWER_OPTIONS Options // ISSUE-2000/02/20-henrygab - pass pointer, not whole struct
+ )
+{
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+ PDEVICE_OBJECT lowerDevice = commonExtension->LowerDeviceObject;
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ PIO_STACK_LOCATION nextIrpStack;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension;
+ PCLASS_POWER_CONTEXT context;
+ PSTORAGE_REQUEST_BLOCK_HEADER srbHeader;
+ ULONG srbFlags;
+ NTSTATUS status;
+
+ _Analysis_assume_(fdoExtension);
+ _Analysis_assume_(fdoExtension->PrivateFdoData);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "ClasspPowerHandler: Power irp %p to %s %p\n",
+ Irp, (commonExtension->IsFdo ? "fdo" : "pdo"), DeviceObject));
+
+ if (!commonExtension->IsFdo) {
+
+ //
+ // certain assumptions are made here,
+ // particularly: having the fdoExtension
+ //
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_POWER, "ClasspPowerHandler: Called for PDO %p???\n",
+ DeviceObject));
+ NT_ASSERT(!"PDO using ClasspPowerHandler");
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ Irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
+ PoStartNextPowerIrp(Irp);
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ return STATUS_NOT_SUPPORTED;
+ }
+
+ switch (irpStack->MinorFunction) {
+
+ case IRP_MN_SET_POWER: {
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tIRP_MN_SET_POWER\n", Irp));
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tSetting %s state to %d\n",
+ Irp,
+ (irpStack->Parameters.Power.Type == SystemPowerState ?
+ "System" : "Device"),
+ irpStack->Parameters.Power.State.SystemState));
+
+ switch (irpStack->Parameters.Power.ShutdownType){
+
+ case PowerActionNone:
+
+ //
+ // Skip if device doesn't need volume verification during idle power
+ // transitions.
+ //
+ if ((fdoExtension->FunctionSupportInfo) &&
+ (fdoExtension->FunctionSupportInfo->IdlePower.NoVerifyDuringIdlePower)) {
+ break;
+ }
+
+ case PowerActionSleep:
+ case PowerActionHibernate:
+ if (fdoData->HotplugInfo.MediaRemovable || fdoData->HotplugInfo.MediaHotplug) {
+ /*
+ * We are suspending device and this drive is either hot-pluggable
+ * or contains removeable media.
+ * Set the media dirty bit, since the media may change while
+ * we are suspended.
+ */
+ SET_FLAG(DeviceObject->Flags, DO_VERIFY_VOLUME);
+
+ //
+ // Bumping the media change count will force the
+ // file system to verify the volume when we resume
+ //
+
+ InterlockedIncrement((volatile LONG *)&fdoExtension->MediaChangeCount);
+ }
+
+ break;
+ }
+
+ break;
+ }
+
+ default: {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tIrp minor code = %#x\n",
+ Irp, irpStack->MinorFunction));
+ break;
+ }
+ }
+
+ if (irpStack->Parameters.Power.Type != DevicePowerState ||
+ irpStack->MinorFunction != IRP_MN_SET_POWER) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tSending to lower device\n", Irp));
+
+ goto ClasspPowerHandlerCleanup;
+
+ }
+
+ //
+ // already in exact same state, don't work to transition to it.
+ //
+
+ if (irpStack->Parameters.Power.State.DeviceState ==
+ fdoExtension->DevicePowerState) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tAlready in device state %x\n",
+ Irp, fdoExtension->DevicePowerState));
+ goto ClasspPowerHandlerCleanup;
+
+ }
+
+ //
+ // or powering down from non-d0 state (device already stopped)
+ // NOTE -- we're not sure whether this case can exist or not (the
+ // power system may never send this sort of request) but it's trivial
+ // to deal with.
+ //
+
+ if ((irpStack->Parameters.Power.State.DeviceState != PowerDeviceD0) &&
+ (fdoExtension->DevicePowerState != PowerDeviceD0)) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tAlready powered down to %x???\n",
+ Irp, fdoExtension->DevicePowerState));
+ fdoExtension->DevicePowerState =
+ irpStack->Parameters.Power.State.DeviceState;
+ goto ClasspPowerHandlerCleanup;
+ }
+
+ //
+ // or when not handling powering up and are powering up
+ //
+
+ if ((!Options.HandleSpinUp) &&
+ (irpStack->Parameters.Power.State.DeviceState == PowerDeviceD0)) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tNot handling spinup to state %x\n",
+ Irp, fdoExtension->DevicePowerState));
+ fdoExtension->DevicePowerState =
+ irpStack->Parameters.Power.State.DeviceState;
+ goto ClasspPowerHandlerCleanup;
+
+ }
+
+ //
+ // or when not handling powering down and are powering down
+ //
+
+ if ((!Options.HandleSpinDown) &&
+ (irpStack->Parameters.Power.State.DeviceState != PowerDeviceD0)) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tNot handling spindown to state %x\n",
+ Irp, fdoExtension->DevicePowerState));
+ fdoExtension->DevicePowerState =
+ irpStack->Parameters.Power.State.DeviceState;
+ goto ClasspPowerHandlerCleanup;
+
+ }
+
+ //
+ // validation completed, start the real work.
+ //
+
+ IoReuseIrp(fdoExtension->PrivateFdoData->PowerProcessIrp, STATUS_SUCCESS);
+ IoSetNextIrpStackLocation(fdoExtension->PrivateFdoData->PowerProcessIrp);
+ nextIrpStack = IoGetNextIrpStackLocation(fdoExtension->PrivateFdoData->PowerProcessIrp);
+
+ context = &(fdoExtension->PowerContext);
+
+ NT_ASSERT(context->InUse == FALSE);
+
+ RtlZeroMemory(context, sizeof(CLASS_POWER_CONTEXT));
+ context->InUse = TRUE;
+
+ if (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+ srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)&(fdoExtension->PrivateFdoData->PowerSrb.SrbEx);
+
+ //
+ // Initialize extended SRB for a SRB_FUNCTION_LOCK_QUEUE
+ //
+ status = InitializeStorageRequestBlock((PSTORAGE_REQUEST_BLOCK)srbHeader,
+ STORAGE_ADDRESS_TYPE_BTL8,
+ CLASS_SRBEX_NO_SRBEX_DATA_BUFFER_SIZE,
+ 0);
+ if (NT_SUCCESS(status)) {
+ ((PSTORAGE_REQUEST_BLOCK)srbHeader)->SrbFunction = SRB_FUNCTION_LOCK_QUEUE;
+ } else {
+ //
+ // Should not happen. Revert to legacy SRB.
+ //
+ NT_ASSERT(FALSE);
+ srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)&(context->Srb);
+ srbHeader->Length = sizeof(SCSI_REQUEST_BLOCK);
+ srbHeader->Function = SRB_FUNCTION_LOCK_QUEUE;
+ }
+ } else {
+ srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)&(context->Srb);
+ srbHeader->Length = sizeof(SCSI_REQUEST_BLOCK);
+ srbHeader->Function = SRB_FUNCTION_LOCK_QUEUE;
+ }
+ nextIrpStack->Parameters.Scsi.Srb = (PSCSI_REQUEST_BLOCK)srbHeader;
+ nextIrpStack->MajorFunction = IRP_MJ_SCSI;
+
+ context->FinalStatus = STATUS_SUCCESS;
+
+ SrbSetOriginalRequest(srbHeader, fdoExtension->PrivateFdoData->PowerProcessIrp);
+ SrbSetSrbFlags(srbHeader, (SRB_FLAGS_BYPASS_LOCKED_QUEUE | SRB_FLAGS_NO_QUEUE_FREEZE));
+
+ fdoExtension->PrivateFdoData->MaxPowerOperationRetryCount = MAXIMUM_RETRIES;
+ context->RetryCount = MAXIMUM_RETRIES;
+
+ context->Options = Options;
+ context->DeviceObject = DeviceObject;
+ context->Irp = Irp;
+
+ if (irpStack->Parameters.Power.State.DeviceState == PowerDeviceD0) {
+
+ NT_ASSERT(Options.HandleSpinUp);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tpower up - locking queue\n", Irp));
+
+ //
+ // We need to issue a queue lock request so that we
+ // can spin the drive back up after the power is restored
+ // but before any requests are processed.
+ //
+
+ context->Options.PowerDown = FALSE;
+ context->PowerChangeState.PowerUp = PowerUpDeviceInitial;
+ context->CompletionRoutine = ClasspPowerUpCompletion;
+
+ } else {
+
+ NT_ASSERT(Options.HandleSpinDown);
+
+ fdoExtension->PowerDownInProgress = TRUE;
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tPowering down - locking queue\n", Irp));
+
+ //
+ // Disable tick timer at beginning of D3 processing if running.
+ //
+ if ((fdoExtension->PrivateFdoData->TickTimerEnabled)) {
+ ClasspDisableTimer(fdoExtension);
+ }
+
+ PoSetPowerState(DeviceObject,
+ irpStack->Parameters.Power.Type,
+ irpStack->Parameters.Power.State);
+
+ context->Options.PowerDown = TRUE;
+ context->PowerChangeState.PowerDown3 = PowerDownDeviceInitial3;
+ context->CompletionRoutine = ClasspPowerDownCompletion;
+
+ }
+
+ //
+ // we are not dealing with port-allocated sense in these routines.
+ //
+
+ srbFlags = SrbGetSrbFlags(srbHeader);
+ NT_ASSERT(!TEST_FLAG(srbFlags, SRB_FLAGS_FREE_SENSE_BUFFER));
+ NT_ASSERT(!TEST_FLAG(srbFlags, SRB_FLAGS_PORT_DRIVER_ALLOCSENSE));
+
+ //
+ // Mark the original power irp pending.
+ //
+
+ IoMarkIrpPending(Irp);
+
+ if (Options.LockQueue) {
+
+ //
+ // Send the lock irp down.
+ //
+
+ IoSetCompletionRoutine(fdoExtension->PrivateFdoData->PowerProcessIrp,
+ context->CompletionRoutine,
+ context,
+ TRUE,
+ TRUE,
+ TRUE);
+
+ IoCallDriver(lowerDevice, fdoExtension->PrivateFdoData->PowerProcessIrp);
+
+ } else {
+
+ //
+ // Call the completion routine directly. It won't care what the
+ // status of the "lock" was - it will just go and do the next
+ // step of the operation.
+ //
+
+ context->CompletionRoutine(DeviceObject, fdoExtension->PrivateFdoData->PowerProcessIrp, context);
+ }
+
+ return STATUS_PENDING;
+
+ClasspPowerHandlerCleanup:
+
+ //
+ // Send the original power irp down, we will start the next power irp in completion routine.
+ //
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tStarting next power irp\n", Irp));
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+ IoSetCompletionRoutine(Irp,
+ ClasspStartNextPowerIrpCompletion,
+ NULL,
+ TRUE,
+ TRUE,
+ TRUE);
+ return PoCallDriver(lowerDevice, Irp);
+} // end ClasspPowerHandler()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassMinimalPowerHandler()
+
+Routine Description:
+
+ This routine is the minimum power handler for a storage driver. It does
+ the least amount of work possible.
+
+--*/
+NTSTATUS
+ClassMinimalPowerHandler(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp
+ )
+{
+ PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
+ PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
+ NTSTATUS status;
+
+ ClassReleaseRemoveLock(DeviceObject, Irp);
+ PoStartNextPowerIrp(Irp);
+
+ switch (irpStack->MinorFunction)
+ {
+ case IRP_MN_SET_POWER:
+ {
+ switch (irpStack->Parameters.Power.ShutdownType)
+ {
+ case PowerActionNone:
+ case PowerActionSleep:
+ case PowerActionHibernate:
+ {
+ if (TEST_FLAG(DeviceObject->Characteristics, FILE_REMOVABLE_MEDIA))
+ {
+ if ((ClassGetVpb(DeviceObject) != NULL) && (ClassGetVpb(DeviceObject)->Flags & VPB_MOUNTED))
+ {
+ //
+ // This flag will cause the filesystem to verify the
+ // volume when coming out of hibernation or standby or runtime power
+ //
+ SET_FLAG(DeviceObject->Flags, DO_VERIFY_VOLUME);
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ //
+ // Fall through
+ //
+
+ case IRP_MN_QUERY_POWER:
+ {
+ if (!commonExtension->IsFdo)
+ {
+ Irp->IoStatus.Status = STATUS_SUCCESS;
+ Irp->IoStatus.Information = 0;
+ }
+ }
+ break;
+ }
+
+ if (commonExtension->IsFdo)
+ {
+ IoCopyCurrentIrpStackLocationToNext(Irp);
+ status = PoCallDriver(commonExtension->LowerDeviceObject, Irp);
+ }
+ else
+ {
+ status = Irp->IoStatus.Status;
+ ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT);
+ }
+
+ return status;
+} // end ClassMinimalPowerHandler()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassSpinDownPowerHandler()
+
+Routine Description:
+
+ This routine is a callback for disks and other things which require both
+ a start and a stop to be sent to the device. (actually the starts are
+ almost always optional, since most device power themselves on to process
+ commands, but i digress).
+
+ Determines proper use of spinup, spindown, and queue locking based upon
+ ScanForSpecialFlags in the FdoExtension. This is the most common power
+ handler passed into classpnp.sys
+
+Arguments:
+
+ DeviceObject - Supplies the functional device object
+
+ Irp - Supplies the request to be retried.
+
+Return Value:
+
+ None
+
+--*/
+__control_entrypoint(DeviceDriver)
+NTSTATUS
+ClassSpinDownPowerHandler(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PIRP Irp
+ )
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
+ CLASS_POWER_OPTIONS options = {0};
+
+ fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
+
+ //
+ // check the flags to see what options we need to worry about
+ //
+
+ if (!TEST_FLAG(fdoExtension->ScanForSpecialFlags,
+ CLASS_SPECIAL_DISABLE_SPIN_DOWN)) {
+ options.HandleSpinDown = TRUE;
+ }
+
+ if (!TEST_FLAG(fdoExtension->ScanForSpecialFlags,
+ CLASS_SPECIAL_DISABLE_SPIN_UP)) {
+ options.HandleSpinUp = TRUE;
+ }
+
+ if (!TEST_FLAG(fdoExtension->ScanForSpecialFlags,
+ CLASS_SPECIAL_NO_QUEUE_LOCK)) {
+ options.LockQueue = TRUE;
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "ClasspPowerHandler: Devobj %p\n"
+ "\t%shandling spin down\n"
+ "\t%shandling spin up\n"
+ "\t%slocking queue\n",
+ DeviceObject,
+ (options.HandleSpinDown ? "" : "not "),
+ (options.HandleSpinUp ? "" : "not "),
+ (options.LockQueue ? "" : "not ")
+ ));
+
+ //
+ // do all the dirty work
+ //
+
+ return ClasspPowerHandler(DeviceObject, Irp, options);
+} // end ClassSpinDownPowerHandler()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClassStopUnitPowerHandler()
+
+Routine Description:
+
+ This routine is an outdated call. To achieve equivalent functionality,
+ the driver should set the following flags in ScanForSpecialFlags in the
+ FdoExtension:
+
+ CLASS_SPECIAL_DISABLE_SPIN_UP
+ CLASS_SPECIAL_NO_QUEUE_LOCK
+
+--*/
+NTSTATUS
+ClassStopUnitPowerHandler(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PIRP Irp
+ )
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_POWER, "ClassStopUnitPowerHandler - Devobj %p using outdated call\n"
+ "Drivers should set the following flags in ScanForSpecialFlags "
+ " in the FDO extension:\n"
+ "\tCLASS_SPECIAL_DISABLE_SPIN_UP\n"
+ "\tCLASS_SPECIAL_NO_QUEUE_LOCK\n"
+ "This will provide equivalent functionality if the power "
+ "routine is then set to ClassSpinDownPowerHandler\n\n",
+ DeviceObject));
+
+ fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
+
+ SET_FLAG(fdoExtension->ScanForSpecialFlags,
+ CLASS_SPECIAL_DISABLE_SPIN_UP);
+ SET_FLAG(fdoExtension->ScanForSpecialFlags,
+ CLASS_SPECIAL_NO_QUEUE_LOCK);
+
+ return ClassSpinDownPowerHandler(DeviceObject, Irp);
+} // end ClassStopUnitPowerHandler()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+RetryPowerRequest()
+
+Routine Description:
+
+ This routine reinitalizes the necessary fields, and sends the request
+ to the lower driver.
+
+Arguments:
+
+ DeviceObject - Supplies the device object associated with this request.
+
+ Irp - Supplies the request to be retried.
+
+ Context - Supplies a pointer to the power up context for this request.
+
+Return Value:
+
+ None
+
+--*/
+VOID
+RetryPowerRequest(
+ PDEVICE_OBJECT DeviceObject,
+ PIRP Irp,
+ PCLASS_POWER_CONTEXT Context
+ )
+{
+ PIO_STACK_LOCATION nextIrpStack = IoGetNextIrpStackLocation(Irp);
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension =
+ (PFUNCTIONAL_DEVICE_EXTENSION)Context->DeviceObject->DeviceExtension;
+ PSTORAGE_REQUEST_BLOCK_HEADER srb;
+ LONGLONG dueTime;
+ ULONG srbFlags;
+ ULONG srbFunction;
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tDelaying retry by queueing DPC\n", Irp));
+
+ //NT_ASSERT(Context->Irp == Irp);
+ if (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+ srb = (PSTORAGE_REQUEST_BLOCK_HEADER)&(fdoExtension->PrivateFdoData->PowerSrb.SrbEx);
+
+ //
+ // Check if reverted to using legacy SRB.
+ //
+ if (Context->Srb.Length == sizeof(SCSI_REQUEST_BLOCK)) {
+ srb = (PSTORAGE_REQUEST_BLOCK_HEADER)&(Context->Srb);
+ srbFunction = srb->Function;
+ } else {
+ srbFunction = ((PSTORAGE_REQUEST_BLOCK)srb)->SrbFunction;
+ }
+ } else {
+ srb = (PSTORAGE_REQUEST_BLOCK_HEADER)&(Context->Srb);
+ srbFunction = srb->Function;
+ }
+
+ NT_ASSERT(Context->DeviceObject == DeviceObject);
+ srbFlags = SrbGetSrbFlags(srb);
+ NT_ASSERT(!TEST_FLAG(srbFlags, SRB_FLAGS_FREE_SENSE_BUFFER));
+ NT_ASSERT(!TEST_FLAG(srbFlags, SRB_FLAGS_PORT_DRIVER_ALLOCSENSE));
+
+ if (Context->RetryInterval == 0) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tDelaying minimum time (.2 sec)\n", Irp));
+ dueTime = (LONGLONG)1000000 * 2;
+
+ } else {
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, "(%p)\tDelaying %x seconds\n",
+ Irp, Context->RetryInterval));
+ dueTime = (LONGLONG)1000000 * 10 * Context->RetryInterval;
+
+ }
+
+ //
+ // reset the retry interval
+ //
+
+ Context->RetryInterval = 0;
+
+ //
+ // Reset byte count of transfer in SRB Extension.
+ //
+
+ SrbSetDataTransferLength(srb, 0);
+
+ //
+ // Zero SRB statuses.
+ //
+
+ srb->SrbStatus = 0;
+ if (srbFunction == SRB_FUNCTION_EXECUTE_SCSI) {
+ SrbSetScsiStatus(srb, 0);
+ }
+
+ //
+ // Set up major SCSI function.
+ //
+
+ nextIrpStack->MajorFunction = IRP_MJ_SCSI;
+
+ //
+ // Save SRB address in next stack for port driver.
+ //
+
+ nextIrpStack->Parameters.Scsi.Srb = (PSCSI_REQUEST_BLOCK)srb;
+
+ //
+ // Set the completion routine up again.
+ //
+
+ IoSetCompletionRoutine(Irp, Context->CompletionRoutine, Context,
+ TRUE, TRUE, TRUE);
+
+ ClassRetryRequest(DeviceObject, Irp, dueTime);
+
+ return;
+
+} // end RetryRequest()
+
+/*++////////////////////////////////////////////////////////////////////////////
+
+ClasspStartNextPowerIrpCompletion()
+
+Routine Description:
+
+ This routine guarantees that the next power irp (power up or down) is not
+ sent until the previous one has fully completed.
+
+--*/
+NTSTATUS
+ClasspStartNextPowerIrpCompletion(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN PVOID Context
+ )
+{
+ PCLASS_POWER_CONTEXT PowerContext = (PCLASS_POWER_CONTEXT)Context;
+
+ UNREFERENCED_PARAMETER(DeviceObject);
+
+ if (Irp->PendingReturned) {
+ IoMarkIrpPending(Irp);
+ }
+
+ if (PowerContext != NULL)
+ {
+ PowerContext->InUse = FALSE;
+ }
+
+
+ PoStartNextPowerIrp(Irp);
+ return STATUS_SUCCESS;
+} // end ClasspStartNextPowerIrpCompletion()
+
+NTSTATUS
+ClasspDeviceLockFailurePowerIrpCompletion(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN PVOID Context
+ )
+{
+ PCLASS_POWER_CONTEXT PowerContext = (PCLASS_POWER_CONTEXT)Context;
+ PCOMMON_DEVICE_EXTENSION commonExtension;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
+ PIO_STACK_LOCATION currentStack;
+ BOOLEAN FailurePredictionEnabled = FALSE;
+
+ UNREFERENCED_PARAMETER(DeviceObject);
+
+ commonExtension = PowerContext->DeviceObject->DeviceExtension;
+ fdoExtension = PowerContext->DeviceObject->DeviceExtension;
+
+ currentStack = IoGetCurrentIrpStackLocation(Irp);
+
+ //
+ // Set the new power state
+ //
+
+ fdoExtension->DevicePowerState = currentStack->Parameters.Power.State.DeviceState;
+
+ //
+ // We reach here becasue LockQueue operation was not successful.
+ // However, media change detection would not happen in case of resume becasue we
+ // had disabled the timer while going into lower power state.
+ // So, if the device goes into D0 then enable the tick timer.
+ //
+
+ if (fdoExtension->DevicePowerState == PowerDeviceD0) {
+ //
+ // Check whether failure detection is enabled
+ //
+
+ if ((fdoExtension->FailurePredictionInfo != NULL) &&
+ (fdoExtension->FailurePredictionInfo->Method != FailurePredictionNone)) {
+ FailurePredictionEnabled = TRUE;
+ }
+
+ //
+ // Enable tick timer at end of D0 processing if it was previously enabled.
+ //
+
+ if ((commonExtension->DriverExtension->InitData.ClassTick != NULL) ||
+ ((fdoExtension->MediaChangeDetectionInfo != NULL) &&
+ (fdoExtension->FunctionSupportInfo != NULL) &&
+ (fdoExtension->FunctionSupportInfo->AsynchronousNotificationSupported == FALSE)) ||
+ (FailurePredictionEnabled)) {
+
+ //
+ // If failure prediction is turned on and we've been powered
+ // off longer than the failure prediction query period then
+ // force the query on the next timer tick.
+ //
+
+ if ((FailurePredictionEnabled) && (ClasspFailurePredictionPeriodMissed(fdoExtension))) {
+ fdoExtension->FailurePredictionInfo->CountDown = 1;
+ }
+
+ //
+ // Finally, enable the timer.
+ //
+
+ ClasspEnableTimer(fdoExtension);
+ }
+ }
+
+ //
+ // Indicate to Po that we've been successfully powered up so
+ // it can do it's notification stuff.
+ //
+
+ PoSetPowerState(PowerContext->DeviceObject,
+ currentStack->Parameters.Power.Type,
+ currentStack->Parameters.Power.State);
+
+ PowerContext->InUse = FALSE;
+
+
+ ClassReleaseRemoveLock(commonExtension->DeviceObject, Irp);
+
+ //
+ // Start the next power IRP
+ //
+
+ if (Irp->PendingReturned) {
+ IoMarkIrpPending(Irp);
+ }
+
+ PoStartNextPowerIrp(Irp);
+
+ return STATUS_SUCCESS;
+}
+
+
+_IRQL_requires_same_
+NTSTATUS
+ClasspSendEnableIdlePowerIoctl(
+ _In_ PDEVICE_OBJECT DeviceObject
+ )
+/*++
+Description:
+
+ This function is used to send IOCTL_STORAGE_ENABLE_IDLE_POWER to the port
+ driver. It pulls the relevant idle power management properties from the
+ FDO's device extension.
+
+Arguments:
+
+ DeviceObject - The class FDO.
+
+Return Value:
+
+ The NTSTATUS code returned from the port driver. STATUS_SUCCESS indicates
+ this device is now enabled for idle (runtime) power management.
+
+--*/
+{
+ NTSTATUS status;
+ STORAGE_IDLE_POWER idlePower = {0};
+ IO_STATUS_BLOCK ioStatus = {0};
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
+ PCOMMON_DEVICE_EXTENSION commonExtension = &(fdoExtension->CommonExtension);
+
+ idlePower.Version = 1;
+ idlePower.Size = sizeof(STORAGE_IDLE_POWER);
+ idlePower.WakeCapableHint = fdoExtension->FunctionSupportInfo->IdlePower.DeviceWakeable;
+ idlePower.D3ColdSupported = fdoExtension->FunctionSupportInfo->IdlePower.D3ColdSupported;
+ idlePower.D3IdleTimeout = fdoExtension->FunctionSupportInfo->IdlePower.D3IdleTimeout;
+
+ ClassSendDeviceIoControlSynchronous(
+ IOCTL_STORAGE_ENABLE_IDLE_POWER,
+ commonExtension->LowerDeviceObject,
+ &idlePower,
+ sizeof(STORAGE_IDLE_POWER),
+ 0,
+ FALSE,
+ &ioStatus
+ );
+
+ status = ioStatus.Status;
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_POWER,
+ "ClasspSendEnableIdlePowerIoctl: Port driver returned status (%x) for FDO (%p)\n"
+ "\tWakeCapableHint: %u\n"
+ "\tD3ColdSupported: %u\n"
+ "\tD3IdleTimeout: %u (ms)",
+ status,
+ DeviceObject,
+ idlePower.WakeCapableHint,
+ idlePower.D3ColdSupported,
+ idlePower.D3IdleTimeout));
+
+ return status;
+}
+
+_Function_class_(POWER_SETTING_CALLBACK)
+_IRQL_requires_same_
+NTSTATUS
+ClasspPowerSettingCallback(
+ _In_ LPCGUID SettingGuid,
+ _In_reads_bytes_(ValueLength) PVOID Value,
+ _In_ ULONG ValueLength,
+ _Inout_opt_ PVOID Context
+)
+/*++
+Description:
+
+ This function is the callback for power setting notifications (registered
+ when ClasspGetD3IdleTimeout() is called for the first time).
+
+ Currently, this function is used to get the disk idle timeout value from
+ the system power settings.
+
+ This function is guaranteed to be called at PASSIVE_LEVEL.
+
+Arguments:
+
+ SettingGuid - The power setting GUID.
+ Value - Pointer to the power setting value.
+ ValueLength - Size of the Value buffer.
+ Context - The FDO's device extension.
+
+Return Value:
+
+ STATUS_SUCCESS
+
+--*/
+{
+ PIDLE_POWER_FDO_LIST_ENTRY fdoEntry = NULL;
+
+#pragma warning(suppress:4054) // okay to type cast function pointer to PIRP for this use case
+ PIRP removeLockTag = (PIRP)&ClasspPowerSettingCallback;
+
+ UNREFERENCED_PARAMETER(Context);
+
+ PAGED_CODE();
+
+ if (IsEqualGUID(SettingGuid, &GUID_DISK_IDLE_TIMEOUT)) {
+ if (ValueLength != sizeof(ULONG) || Value == NULL) {
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // The value supplied by this GUID is already in milliseconds.
+ //
+ DiskIdleTimeoutInMS = *((PULONG)Value);
+
+ //
+ // For each FDO on the idle power list, grab the remove lock and send
+ // IOCTL_STORAGE_ENABLE_IDLE_POWER to the port driver to update the
+ // idle timeout value.
+ //
+ KeAcquireGuardedMutex(&IdlePowerFDOListMutex);
+ fdoEntry = (PIDLE_POWER_FDO_LIST_ENTRY)IdlePowerFDOList.Flink;
+ while ((PLIST_ENTRY)fdoEntry != &IdlePowerFDOList) {
+
+ ULONG isRemoved = ClassAcquireRemoveLock(fdoEntry->Fdo, removeLockTag);
+
+ if (!isRemoved) {
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)fdoEntry->Fdo->DeviceExtension;
+
+ //
+ // Apply the new timeout if the user hasn't overridden it via the registry.
+ //
+ if (!fdoExtension->FunctionSupportInfo->IdlePower.D3IdleTimeoutOverridden) {
+ fdoExtension->FunctionSupportInfo->IdlePower.D3IdleTimeout = DiskIdleTimeoutInMS;
+ ClasspSendEnableIdlePowerIoctl(fdoEntry->Fdo);
+ }
+ }
+
+ ClassReleaseRemoveLock(fdoEntry->Fdo, removeLockTag);
+
+ fdoEntry = (PIDLE_POWER_FDO_LIST_ENTRY)fdoEntry->ListEntry.Flink;
+ }
+ KeReleaseGuardedMutex(&IdlePowerFDOListMutex);
+
+ } else if (IsEqualGUID(SettingGuid, &GUID_CONSOLE_DISPLAY_STATE)) {
+
+ //
+ // If monitor is off, change media change requests to not
+ // keep device active. This allows removable media devices to
+ // go to sleep if there are no other active requests. Otherwise,
+ // let media change requests keep the device active.
+ //
+ if ((ValueLength == sizeof(ULONG)) && (Value != NULL)) {
+ if (*((PULONG)Value) == PowerMonitorOff) {
+ ClasspScreenOff = TRUE;
+ } else {
+ ClasspScreenOff = FALSE;
+ }
+
+ KeAcquireGuardedMutex(&IdlePowerFDOListMutex);
+ fdoEntry = (PIDLE_POWER_FDO_LIST_ENTRY)IdlePowerFDOList.Flink;
+ while ((PLIST_ENTRY)fdoEntry != &IdlePowerFDOList) {
+
+ ULONG isRemoved = ClassAcquireRemoveLock(fdoEntry->Fdo, removeLockTag);
+ if (!isRemoved) {
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)fdoEntry->Fdo->DeviceExtension;
+
+ if (ClasspScreenOff == FALSE) {
+ //
+ // Now that the screen is on, we may need to check for media
+ // for devices that are not in D0 and may have removable media.
+ // This is because the media change polling has been disabled
+ // for devices in D3 and now that the screen is on the user may
+ // have inserted some media that they want to interact with.
+ //
+ if ((fdoExtension->DevicePowerState != PowerDeviceD0) &&
+ (fdoExtension->MediaChangeDetectionInfo != NULL) &&
+ (fdoExtension->FunctionSupportInfo->AsynchronousNotificationSupported == FALSE)) {
+ ClassCheckMediaState(fdoExtension);
+ }
+
+ //
+ // We disabled failure prediction polling during screen-off
+ // so now check to see if we missed a failure prediction
+ // period and if so, force the IOCTL to be sent now.
+ //
+ if ((fdoExtension->FailurePredictionInfo != NULL) &&
+ (fdoExtension->FailurePredictionInfo->Method != FailurePredictionNone)) {
+ if (ClasspFailurePredictionPeriodMissed(fdoExtension)) {
+ fdoExtension->FailurePredictionInfo->CountDown = 1;
+ }
+ }
+ }
+
+#if (NTDDI_VERSION >= NTDDI_WINBLUE)
+ //
+ // Screen state has changed so attempt to update the tick
+ // timer's no-wake tolerance accordingly.
+ //
+ ClasspUpdateTimerNoWakeTolerance(fdoExtension);
+#endif
+ }
+ ClassReleaseRemoveLock(fdoEntry->Fdo, removeLockTag);
+
+ fdoEntry = (PIDLE_POWER_FDO_LIST_ENTRY)fdoEntry->ListEntry.Flink;
+ }
+ KeReleaseGuardedMutex(&IdlePowerFDOListMutex);
+ }
+
+ }
+
+ return STATUS_SUCCESS;
+}
+
+
+_IRQL_requires_same_
+NTSTATUS
+ClasspEnableIdlePower(
+ _In_ PDEVICE_OBJECT DeviceObject
+ )
+/*++
+Description:
+
+ This function is used to enable idle (runtime) power management for the
+ device. It will do the work to determine D3Cold support, idle timeout,
+ etc. and then notify the port driver that it wants to enable idle power
+ management.
+
+ This function may modify some of the idle power fields in the FDO's device
+ extension.
+
+Arguments:
+
+ DeviceObject - The class FDO.
+
+Return Value:
+
+ An NTSTATUS code indicating the status of the operation.
+
+--*/
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ ULONG d3ColdDisabledByUser = FALSE;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
+ ULONG idleTimeoutOverrideInSeconds = 0;
+
+ //
+ // This function should only be called once.
+ //
+ NT_ASSERT(fdoExtension->FunctionSupportInfo->IdlePower.IdlePowerEnabled == FALSE);
+
+ ClassGetDeviceParameter(fdoExtension,
+ CLASSP_REG_SUBKEY_NAME,
+ CLASSP_REG_DISABLE_D3COLD,
+ &d3ColdDisabledByUser);
+
+ //
+ // If the device is hot-pluggable or the user has explicitly
+ // disabled D3Cold, do not enable D3Cold for this device.
+ //
+ if (d3ColdDisabledByUser || fdoExtension->PrivateFdoData->HotplugInfo.DeviceHotplug) {
+ fdoExtension->FunctionSupportInfo->IdlePower.D3ColdSupported = 0;
+ }
+
+ ClassGetDeviceParameter(fdoExtension,
+ CLASSP_REG_SUBKEY_NAME,
+ CLASSP_REG_IDLE_TIMEOUT_IN_SECONDS,
+ &idleTimeoutOverrideInSeconds);
+
+ //
+ // Set the idle timeout. If the user has not specified an override value,
+ // this will either be a default value or will have been updated by the
+ // power setting notification callback.
+ //
+ if (idleTimeoutOverrideInSeconds != 0) {
+ fdoExtension->FunctionSupportInfo->IdlePower.D3IdleTimeout = (idleTimeoutOverrideInSeconds * 1000);
+ fdoExtension->FunctionSupportInfo->IdlePower.D3IdleTimeoutOverridden = TRUE;
+ } else {
+ fdoExtension->FunctionSupportInfo->IdlePower.D3IdleTimeout = DiskIdleTimeoutInMS;
+ }
+
+ //
+ // We don't allow disks to be wakeable.
+ //
+ fdoExtension->FunctionSupportInfo->IdlePower.DeviceWakeable = FALSE;
+
+ //
+ // Send IOCTL_STORAGE_ENABLE_IDLE_POWER to the port driver to enable idle
+ // power management by the port driver.
+ //
+ status = ClasspSendEnableIdlePowerIoctl(DeviceObject);
+
+ if (NT_SUCCESS(status)) {
+ PIDLE_POWER_FDO_LIST_ENTRY fdoEntry = NULL;
+
+ //
+ // Put this FDO on the list of devices that are idle power managed.
+ //
+ fdoEntry = ExAllocatePoolWithTag(NonPagedPoolNx, sizeof(IDLE_POWER_FDO_LIST_ENTRY), CLASS_TAG_POWER);
+ if (fdoEntry) {
+
+ fdoExtension->FunctionSupportInfo->IdlePower.IdlePowerEnabled = TRUE;
+
+ fdoEntry->Fdo = DeviceObject;
+
+ KeAcquireGuardedMutex(&IdlePowerFDOListMutex);
+ InsertHeadList(&IdlePowerFDOList, &(fdoEntry->ListEntry));
+ KeReleaseGuardedMutex(&IdlePowerFDOListMutex);
+
+ //
+ // If not registered already, register for disk idle timeout power
+ // setting notifications. The power manager will call our power
+ // setting callback very soon to set the idle timeout to the actual
+ // value.
+ //
+ if (PowerSettingNotificationHandle == NULL) {
+ PoRegisterPowerSettingCallback(DeviceObject,
+ &GUID_DISK_IDLE_TIMEOUT,
+ &ClasspPowerSettingCallback,
+ NULL,
+ &(PowerSettingNotificationHandle));
+ }
+ } else {
+ fdoExtension->FunctionSupportInfo->IdlePower.IdlePowerEnabled = FALSE;
+ status = STATUS_UNSUCCESSFUL;
+ }
+ }
+
+ return status;
+}
+
diff --git a/storage/class/classpnp/src/retry.c b/storage/class/classpnp/src/retry.c
new file mode 100644
index 00000000..90d70ce3
--- /dev/null
+++ b/storage/class/classpnp/src/retry.c
@@ -0,0 +1,758 @@
+/*++
+
+Copyright (C) Microsoft Corporation, 1991 - 2010
+
+Module Name:
+
+ retry.c
+
+Abstract:
+
+ Packet retry routines for CLASSPNP
+
+Environment:
+
+ kernel mode only
+
+Notes:
+
+
+Revision History:
+
+--*/
+
+#include "classp.h"
+#include "debug.h"
+
+#ifdef DEBUG_USE_WPP
+#include "retry.tmh"
+#endif
+
+
+/*
+ * InterpretTransferPacketError
+ *
+ * Interpret the SRB error into a meaningful IRP status.
+ * ClassInterpretSenseInfo also may modify the SRB for the retry.
+ *
+ * Return TRUE iff packet should be retried.
+ */
+BOOLEAN InterpretTransferPacketError(PTRANSFER_PACKET Pkt)
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Pkt->Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
+ ULONG timesAlreadyRetried;
+ BOOLEAN shouldRetry = FALSE;
+ PCDB pCdb = ClasspTransferPacketGetCdb(Pkt);
+
+ /*
+ * Interpret the error using the returned sense info first.
+ */
+ Pkt->RetryIn100nsUnits = 0;
+
+
+ /*
+ * Pre-calculate the number of times the IO has already been
+ * retried, so that all InterpretSenseInfo routines get the right value.
+ */
+ if (ClasspTransferPacketGetNumberOfRetriesDone(Pkt, pCdb, &timesAlreadyRetried) == FALSE)
+ {
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "Unhandled SRB Function %xh in error path for packet %p (did miniport change Srb.Cdb.OperationCode ?)", (ULONG)pCdb->CDB10.OperationCode, Pkt));
+ }
+
+ if (fdoData->InterpretSenseInfo != NULL) {
+
+ SCSI_REQUEST_BLOCK tempSrb = { 0 };
+ PSCSI_REQUEST_BLOCK srbPtr = (PSCSI_REQUEST_BLOCK)Pkt->Srb;
+
+ // SAL annotation and ClassInitializeEx() both validate this
+ NT_ASSERT(fdoData->InterpretSenseInfo->Interpret != NULL);
+
+ //
+ // If class driver does not support extended SRB and this is
+ // an extended SRB, convert to legacy SRB and pass to class
+ // driver.
+ //
+ if ((Pkt->Srb->Function == SRB_FUNCTION_STORAGE_REQUEST_BLOCK) &&
+ ((fdoExtension->CommonExtension.DriverExtension->SrbSupport &
+ CLASS_SRB_STORAGE_REQUEST_BLOCK) == 0)) {
+ ClasspConvertToScsiRequestBlock(&tempSrb, (PSTORAGE_REQUEST_BLOCK)Pkt->Srb);
+ srbPtr = &tempSrb;
+ }
+
+ shouldRetry = fdoData->InterpretSenseInfo->Interpret(Pkt->Fdo,
+ Pkt->OriginalIrp,
+ srbPtr,
+ IRP_MJ_SCSI,
+ 0,
+ timesAlreadyRetried,
+ Pkt->RetryHistory,
+ &Pkt->Irp->IoStatus.Status,
+ &Pkt->RetryIn100nsUnits);
+
+
+ } else {
+
+ //
+ // In this case, fdoData->InterpretSenseInfo == NULL so we must do our
+ // own error code and sense info processing.
+ //
+
+ PVOID senseInfoBuffer = ClasspTransferPacketGetSenseInfoBuffer(Pkt);
+ UCHAR senseInfoBufferLength = ClasspTransferPacketGetSenseInfoBufferLength(Pkt);
+ BOOLEAN validSense = FALSE;
+ UCHAR senseKey = 0;
+ UCHAR additionalSenseCode = 0;
+ UCHAR additionalSenseQual = 0;
+
+ NT_ASSERT(senseInfoBuffer);
+
+ validSense = ScsiGetSenseKeyAndCodes(senseInfoBuffer,
+ senseInfoBufferLength,
+ SCSI_SENSE_OPTIONS_FIXED_FORMAT_IF_UNKNOWN_FORMAT_INDICATED,
+ &senseKey,
+ &additionalSenseCode,
+ &additionalSenseQual);
+
+ if (pCdb->MEDIA_REMOVAL.OperationCode == SCSIOP_MEDIUM_REMOVAL) {
+
+ ULONG retryIntervalSeconds = 0;
+ /*
+ * This is an Ejection Control SRB. Interpret its sense info specially.
+ */
+ shouldRetry = ClassInterpretSenseInfo(
+ Pkt->Fdo,
+ (PSCSI_REQUEST_BLOCK)Pkt->Srb,
+ IRP_MJ_SCSI,
+ 0,
+ timesAlreadyRetried,
+ &Pkt->Irp->IoStatus.Status,
+ &retryIntervalSeconds);
+
+ if (shouldRetry) {
+ /*
+ * If the device is not ready, wait at least 2 seconds before retrying.
+ */
+ BOOLEAN setRetryIntervalSeconds = FALSE;
+
+ if (validSense) {
+
+ if ((Pkt->Irp->IoStatus.Status == STATUS_DEVICE_NOT_READY) &&
+ (additionalSenseCode == SCSI_ADSENSE_LUN_NOT_READY)) {
+ setRetryIntervalSeconds = TRUE;
+ }
+ }
+
+ if (!setRetryIntervalSeconds && (SRB_STATUS(Pkt->Srb->SrbStatus) == SRB_STATUS_SELECTION_TIMEOUT)) {
+ setRetryIntervalSeconds = TRUE;
+ }
+
+ if (setRetryIntervalSeconds) {
+ retryIntervalSeconds = MAX(retryIntervalSeconds, 2);
+ }
+ }
+
+ if (shouldRetry)
+ {
+ Pkt->RetryIn100nsUnits = retryIntervalSeconds;
+ Pkt->RetryIn100nsUnits *= 1000 * 1000 * 10;
+ }
+
+ }
+ else if ((pCdb->MODE_SENSE.OperationCode == SCSIOP_MODE_SENSE) ||
+ (pCdb->MODE_SENSE.OperationCode == SCSIOP_MODE_SENSE10)) {
+
+ ULONG retryIntervalSeconds = 0;
+ /*
+ * This is an Mode Sense SRB. Interpret its sense info specially.
+ */
+ shouldRetry = ClassInterpretSenseInfo(
+ Pkt->Fdo,
+ (PSCSI_REQUEST_BLOCK)Pkt->Srb,
+ IRP_MJ_SCSI,
+ 0,
+ timesAlreadyRetried,
+ &Pkt->Irp->IoStatus.Status,
+ &retryIntervalSeconds);
+ if (shouldRetry) {
+ /*
+ * If the device is not ready, wait at least 2 seconds before retrying.
+ */
+ BOOLEAN setRetryIntervalSeconds = FALSE;
+
+ if (validSense) {
+ if ((Pkt->Irp->IoStatus.Status == STATUS_DEVICE_NOT_READY) &&
+ (additionalSenseCode == SCSI_ADSENSE_LUN_NOT_READY)) {
+ setRetryIntervalSeconds = TRUE;
+ }
+ }
+
+ if (!setRetryIntervalSeconds && (SRB_STATUS(Pkt->Srb->SrbStatus) == SRB_STATUS_SELECTION_TIMEOUT)) {
+ setRetryIntervalSeconds = TRUE;
+ }
+
+ if (setRetryIntervalSeconds) {
+ retryIntervalSeconds = MAX(retryIntervalSeconds, 2);
+ }
+ }
+
+ /*
+ * Some special cases for mode sense.
+ */
+ if (Pkt->Irp->IoStatus.Status == STATUS_VERIFY_REQUIRED) {
+ shouldRetry = TRUE;
+ }
+ else if (SRB_STATUS(Pkt->Srb->SrbStatus) == SRB_STATUS_DATA_OVERRUN) {
+ /*
+ * This is a HACK.
+ * Atapi returns SRB_STATUS_DATA_OVERRUN when it really means
+ * underrun (i.e. success, and the buffer is longer than needed).
+ * So treat this as a success.
+ * When the caller of this function sees that the status was changed to success,
+ * it will add the transferred length to the original irp.
+ */
+ Pkt->Irp->IoStatus.Status = STATUS_SUCCESS;
+ shouldRetry = FALSE;
+ }
+
+ if (shouldRetry)
+ {
+ Pkt->RetryIn100nsUnits = retryIntervalSeconds;
+ Pkt->RetryIn100nsUnits *= 1000 * 1000 * 10;
+ }
+
+ }
+ else if ((pCdb->CDB10.OperationCode == SCSIOP_READ_CAPACITY) ||
+ (pCdb->CDB16.OperationCode == SCSIOP_READ_CAPACITY16)) {
+
+ ULONG retryIntervalSeconds = 0;
+
+ /*
+ * This is a Drive Capacity SRB. Interpret its sense info specially.
+ */
+ shouldRetry = ClassInterpretSenseInfo(
+ Pkt->Fdo,
+ (PSCSI_REQUEST_BLOCK)Pkt->Srb,
+ IRP_MJ_SCSI,
+ 0,
+ timesAlreadyRetried,
+ &Pkt->Irp->IoStatus.Status,
+ &retryIntervalSeconds);
+ if (Pkt->Irp->IoStatus.Status == STATUS_VERIFY_REQUIRED) {
+ shouldRetry = TRUE;
+ }
+
+ if (shouldRetry)
+ {
+ Pkt->RetryIn100nsUnits = retryIntervalSeconds;
+ Pkt->RetryIn100nsUnits *= 1000 * 1000 * 10;
+ }
+
+ }
+ else if (IS_SCSIOP_READWRITE(pCdb->CDB10.OperationCode)) {
+
+ ULONG retryIntervalSeconds = 0;
+ /*
+ * This is a Read/Write Data packet.
+ */
+ PIO_STACK_LOCATION origCurrentSp = IoGetCurrentIrpStackLocation(Pkt->OriginalIrp);
+
+ shouldRetry = ClassInterpretSenseInfo(Pkt->Fdo,
+ (PSCSI_REQUEST_BLOCK)Pkt->Srb,
+ origCurrentSp->MajorFunction,
+ 0,
+ timesAlreadyRetried,
+ &Pkt->Irp->IoStatus.Status,
+ &retryIntervalSeconds);
+
+ /*
+ * Deal with some special cases.
+ */
+ if (Pkt->Irp->IoStatus.Status == STATUS_INSUFFICIENT_RESOURCES) {
+ /*
+ * We are in extreme low-memory stress.
+ * We will retry in smaller chunks.
+ */
+ shouldRetry = TRUE;
+ }
+ else if (TEST_FLAG(origCurrentSp->Flags, SL_OVERRIDE_VERIFY_VOLUME) &&
+ (Pkt->Irp->IoStatus.Status == STATUS_VERIFY_REQUIRED)) {
+ /*
+ * We are still verifying a (possibly) reloaded disk/cdrom.
+ * So retry the request.
+ */
+ Pkt->Irp->IoStatus.Status = STATUS_IO_DEVICE_ERROR;
+ shouldRetry = TRUE;
+
+ }
+#if (NTDDI_VERSION >= NTDDI_WINBLUE)
+ else if (SrbGetSrbStatus(Pkt->Srb) == SRB_STATUS_BUS_RESET ||
+ SrbGetSrbStatus(Pkt->Srb) == SRB_STATUS_TIMEOUT ||
+ SrbGetSrbStatus(Pkt->Srb) == SRB_STATUS_COMMAND_TIMEOUT ||
+ SrbGetSrbStatus(Pkt->Srb) == SRB_STATUS_ABORTED) {
+
+ Pkt->TimedOut = TRUE;
+
+ if (shouldRetry) {
+ //
+ // For requests that have timed-out we may only perform a limited
+ // number of retries. This is typically less than the general
+ // number of retries allowed.
+ //
+ if (Pkt->NumIoTimeoutRetries == 0) {
+ shouldRetry = FALSE;
+ } else {
+ Pkt->NumIoTimeoutRetries--;
+ //
+ // We expect to be able to retry if there are some general retries remaining.
+ //
+ NT_ASSERT(Pkt->NumRetries > 0);
+ }
+ }
+ }
+#endif
+
+ if (shouldRetry)
+ {
+ Pkt->RetryIn100nsUnits = retryIntervalSeconds;
+ Pkt->RetryIn100nsUnits *= 1000 * 1000 * 10;
+ }
+
+ } else if (ClasspIsOffloadDataTransferCommand(pCdb)) {
+
+ ULONG retryIntervalSeconds = 0;
+
+ Pkt->TransferCount = 0;
+
+ shouldRetry = ClassInterpretSenseInfo(
+ Pkt->Fdo,
+ (PSCSI_REQUEST_BLOCK)Pkt->Srb,
+ IRP_MJ_SCSI,
+ 0,
+ timesAlreadyRetried,
+ &Pkt->Irp->IoStatus.Status,
+ &retryIntervalSeconds);
+
+ if (shouldRetry) {
+
+ Pkt->RetryIn100nsUnits = retryIntervalSeconds;
+ Pkt->RetryIn100nsUnits *= 1000 * 1000 * 10;
+
+ } else {
+
+ if (ClasspIsTokenOperation(pCdb)) {
+
+ BOOLEAN isInformationValid = FALSE;
+ ULONGLONG information = 0;
+
+ if (validSense) {
+
+ //
+ // If this is a data underrun condition (i.e. target truncated the offload data transfer),
+ // the SenseData's Information field may have the TransferCount.
+ //
+ if ((senseKey == SCSI_SENSE_COPY_ABORTED || senseKey == SCSI_SENSE_ABORTED_COMMAND) &&
+ (additionalSenseCode == SCSI_ADSENSE_COPY_TARGET_DEVICE_ERROR && additionalSenseQual == SCSI_SENSEQ_DATA_UNDERRUN)) {
+
+ //
+ // Sense data in Descriptor format
+ //
+ if (IsDescriptorSenseDataFormat(senseInfoBuffer)) {
+
+ PVOID startBuffer = NULL;
+ UCHAR startBufferLength = 0;
+
+
+ if (ScsiGetSenseDescriptor(senseInfoBuffer,
+ SrbGetSenseInfoBufferLength(Pkt->Srb),
+ &startBuffer,
+ &startBufferLength)) {
+ UCHAR outType;
+ PVOID outBuffer = NULL;
+ UCHAR outBufferLength = 0;
+
+ UCHAR typeList[1] = { SCSI_SENSE_DESCRIPTOR_TYPE_INFORMATION };
+
+ if (ScsiGetNextSenseDescriptorByType(startBuffer,
+ startBufferLength,
+ typeList,
+ ARRAYSIZE(typeList),
+ &outType,
+ &outBuffer,
+ &outBufferLength)) {
+
+ if (outType == SCSI_SENSE_DESCRIPTOR_TYPE_INFORMATION) {
+
+ if (ScsiValidateInformationSenseDescriptor(outBuffer, outBufferLength)) {
+ REVERSE_BYTES_QUAD(&information, &(((PSCSI_SENSE_DESCRIPTOR_INFORMATION)outBuffer)->Information));
+ isInformationValid = TRUE;
+ }
+
+ } else {
+
+ //
+ // ScsiGetNextDescriptorByType should only return a type that is specified by us.
+ //
+ NT_ASSERT(FALSE);
+ }
+ }
+ }
+ } else {
+
+ //
+ // Sense data in Fixed format
+ //
+ REVERSE_BYTES(&information, &(((PFIXED_SENSE_DATA)senseInfoBuffer)->Information));
+ isInformationValid = TRUE;
+ }
+
+ if (isInformationValid) {
+ Pkt->TransferCount = information;
+ }
+ }
+ }
+ }
+ }
+
+ }
+ else {
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "Unhandled SRB Function %xh in error path for packet %p (did miniport change Srb.Cdb.OperationCode ?)", (ULONG)pCdb->CDB10.OperationCode, Pkt));
+ }
+ }
+
+ return shouldRetry;
+}
+
+
+/*
+ * RetryTransferPacket
+ *
+ * Retry sending a TRANSFER_PACKET.
+ *
+ * Return TRUE iff the packet is complete.
+ * (if so the status in pkt->irp is the final status).
+ */
+BOOLEAN RetryTransferPacket(PTRANSFER_PACKET Pkt)
+{
+ BOOLEAN packetDone;
+ BOOLEAN scaleDown = FALSE;
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "retrying failed transfer (pkt=%ph, op=%s)", Pkt, DBGGETSCSIOPSTR(Pkt->Srb)));
+
+ NT_ASSERT(Pkt->NumRetries > 0 || Pkt->RetryHistory);
+ Pkt->NumRetries--;
+
+ //
+ // If this is the last retry, then turn off disconnect, sync transfer,
+ // and tagged queuing. On all other retries, leave the original settings.
+ //
+ if (Pkt->NumRetries == 0) {
+ scaleDown = TRUE;
+ }
+
+#if (NTDDI_VERSION >= NTDDI_WINBLUE)
+ //
+ // If this request previously timed-out and there are no more retries left
+ // for timed-out requests then we should also apply the scale down.
+ //
+ if (Pkt->TimedOut &&
+ Pkt->NumIoTimeoutRetries == 0) {
+ scaleDown = TRUE;
+ }
+#endif
+
+ if (scaleDown) {
+ /*
+ * Tone down performance on the retry.
+ * This increases the chance for success on the retry.
+ * We've seen instances of drives that fail consistently but then start working
+ * once this scale-down is applied.
+ */
+ SrbSetSrbFlags(Pkt->Srb, SRB_FLAGS_DISABLE_DISCONNECT);
+ SrbSetSrbFlags(Pkt->Srb, SRB_FLAGS_DISABLE_SYNCH_TRANSFER);
+ SrbClearSrbFlags(Pkt->Srb, SRB_FLAGS_QUEUE_ACTION_ENABLE);
+ SrbSetRequestTag(Pkt->Srb, SP_UNTAGGED);
+ }
+
+ if (Pkt->Irp->IoStatus.Status == STATUS_INSUFFICIENT_RESOURCES) {
+
+ PCDB pCdb = SrbGetCdb(Pkt->Srb);
+ UCHAR cdbOpcode = 0;
+ BOOLEAN isReadWrite = FALSE;
+
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Pkt->Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
+
+ if (pCdb) {
+ cdbOpcode = pCdb->CDB10.OperationCode;
+ isReadWrite = IS_SCSIOP_READWRITE(cdbOpcode);
+ }
+
+ if ((Pkt->DriverUsesStartIO) &&
+ ( (cdbOpcode == SCSIOP_WRITE6 ) ||
+ (cdbOpcode == SCSIOP_WRITE ) ||
+ (cdbOpcode == SCSIOP_WRITE12) ||
+ (cdbOpcode == SCSIOP_WRITE16) )) {
+
+ /* don't retry writes in super-low-memory conditions if the
+ * driver must serialize against StartIO. This is because
+ * some write methods used in such drivers cannot accept
+ * random-sized writes. (i.e CD-RW in packet writing mode)
+ * Reads, however, are always safe to split up.
+ */
+ SET_FLAG(fdoData->TrackingFlags, TRACKING_FORWARD_PROGRESS_PATH1);
+ packetDone = TRUE;
+ }
+ else if (Pkt->InLowMemRetry || !isReadWrite){
+ /*
+ * This should never happen under normal circumstances.
+ * The memory manager guarantees that at least four pages will
+ * be available to allow forward progress in the port driver.
+ * So a one-page transfer should never fail with insufficient resources.
+ *
+ * However, it is possible to get in here with virtual storage
+ * or thin provisioned storage for example.
+ * A single sector write can trigger an allocation request and
+ * presently a forward progress guarantee is not provided.
+ * VHD also may have some limitations in forward progress guarantee.
+ * And USB too might also fall into this category.
+ */
+ SET_FLAG(fdoData->TrackingFlags, TRACKING_FORWARD_PROGRESS_PATH2);
+ packetDone = TRUE;
+ }
+ else {
+ /*
+ * We are in low-memory stress.
+ * Start the low-memory retry state machine, which tries to
+ * resend the packet in little one-page chunks.
+ */
+ SET_FLAG(fdoData->TrackingFlags, TRACKING_FORWARD_PROGRESS_PATH3);
+ InitLowMemRetry(Pkt,
+ Pkt->BufPtrCopy,
+ Pkt->BufLenCopy,
+ Pkt->TargetLocationCopy);
+ StepLowMemRetry(Pkt);
+ packetDone = FALSE;
+ }
+ }
+ else {
+ /*
+ * Retry the packet by simply resending it after a delay.
+ * Put the packet back in the pending queue and
+ * schedule a timer to retry the transfer.
+ *
+ * Do not call SetupReadWriteTransferPacket again because:
+ * (1) The minidriver may have set some bits
+ * in the SRB that it needs again and
+ * (2) doing so would reset numRetries.
+ *
+ * BECAUSE we do not call SetupReadWriteTransferPacket again,
+ * we have to reset a couple fields in the SRB that
+ * some miniports overwrite when they fail an SRB.
+ */
+
+ SrbSetDataBuffer(Pkt->Srb, Pkt->BufPtrCopy);
+ SrbSetDataTransferLength(Pkt->Srb, Pkt->BufLenCopy);
+
+ TransferPacketQueueRetryDpc(Pkt);
+
+ packetDone = FALSE;
+ }
+
+ return packetDone;
+}
+
+
+VOID TransferPacketQueueRetryDpc(PTRANSFER_PACKET Pkt)
+{
+ KeInitializeDpc(&Pkt->RetryTimerDPC, TransferPacketRetryTimerDpc, Pkt);
+
+ if (Pkt->RetryIn100nsUnits == 0){
+ KeInsertQueueDpc(&Pkt->RetryTimerDPC, NULL, NULL);
+ }
+ else {
+ LARGE_INTEGER timerPeriod;
+
+ NT_ASSERT(Pkt->RetryIn100nsUnits < 100 * 1000 * 1000 * 10); // sanity check -- 100 seconds is normally too long
+ timerPeriod.QuadPart = -(Pkt->RetryIn100nsUnits);
+ KeInitializeTimer(&Pkt->RetryTimer);
+ KeSetTimer(&Pkt->RetryTimer, timerPeriod, &Pkt->RetryTimerDPC);
+ }
+}
+
+
+VOID TransferPacketRetryTimerDpc( IN PKDPC Dpc,
+ IN PVOID DeferredContext,
+ IN PVOID SystemArgument1,
+ IN PVOID SystemArgument2)
+{
+ PTRANSFER_PACKET pkt;
+ PDEVICE_OBJECT fdo;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension;
+
+ _Analysis_assume_(DeferredContext != NULL);
+
+ pkt = (PTRANSFER_PACKET)DeferredContext;
+
+ fdo = pkt->Fdo;
+ fdoExtension = fdo->DeviceExtension;
+
+ UNREFERENCED_PARAMETER(Dpc);
+ UNREFERENCED_PARAMETER(SystemArgument1);
+ UNREFERENCED_PARAMETER(SystemArgument2);
+
+
+ /*
+ * Sometimes the port driver can allocates a new 'sense' buffer
+ * to report transfer errors, e.g. when the default sense buffer
+ * is too small. If so, it is up to us to free it.
+ * Now that we're done using the sense info, free it if appropriate.
+ * Then clear the sense buffer so it doesn't pollute future errors returned in this packet.
+ */
+ if (PORT_ALLOCATED_SENSE_EX(fdoExtension, pkt->Srb)) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_RW, "Freeing port-allocated sense buffer for pkt %ph.", pkt));
+ FREE_PORT_ALLOCATED_SENSE_BUFFER_EX(fdoExtension, pkt->Srb);
+ SrbSetSenseInfoBuffer(pkt->Srb, &pkt->SrbErrorSenseData);
+ SrbSetSenseInfoBufferLength(pkt->Srb, sizeof(pkt->SrbErrorSenseData));
+ }
+ else {
+ NT_ASSERT(SrbGetSenseInfoBuffer(pkt->Srb) == &pkt->SrbErrorSenseData);
+ NT_ASSERT(SrbGetSenseInfoBufferLength(pkt->Srb) <= sizeof(pkt->SrbErrorSenseData));
+ }
+
+ RtlZeroMemory(&pkt->SrbErrorSenseData, sizeof(pkt->SrbErrorSenseData));
+
+ SubmitTransferPacket(pkt);
+
+}
+
+
+VOID InitLowMemRetry(PTRANSFER_PACKET Pkt, PVOID BufPtr, ULONG Len, LARGE_INTEGER TargetLocation)
+{
+ NT_ASSERT(Len > 0);
+ NT_ASSERT(!Pkt->InLowMemRetry);
+
+ if (Pkt->DriverUsesStartIO)
+ {
+ /*
+ * special case: StartIO-based writing must stay serialized for performance
+ * and proper operations (i.e. sequential writing mode). If need more than
+ * one transfer to perform this operation, and it's a StartIO-based driver
+ * (such as CDROM), then just use a single packet and use the retry logic
+ * that's already built-in to the packet engine. Note that low-mem retry
+ * cannot be used directly because some write methods do not work if the
+ * writes are only PAGE_SIZE (i.e. packet writing may corrupt data).
+ */
+ Pkt->InLowMemRetry = FALSE;
+ }
+ else
+ {
+ Pkt->InLowMemRetry = TRUE;
+ }
+ Pkt->LowMemRetry_remainingBufPtr = BufPtr;
+ Pkt->LowMemRetry_remainingBufLen = Len;
+ Pkt->LowMemRetry_nextChunkTargetLocation = TargetLocation;
+}
+
+
+/*
+ * StepLowMemRetry
+ *
+ * During extreme low-memory stress, this function retries
+ * a packet in small one-page chunks, sent serially.
+ *
+ * Returns TRUE iff the packet is done.
+ */
+BOOLEAN StepLowMemRetry(PTRANSFER_PACKET Pkt)
+{
+ BOOLEAN packetDone;
+
+ if (Pkt->LowMemRetry_remainingBufLen == 0){
+ packetDone = TRUE;
+ }
+ else {
+ ULONG thisChunkLen;
+ if (Pkt->DriverUsesStartIO)
+ {
+ /*
+ * Need the fdoData for the HwMaxXferLen
+ */
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Pkt->Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
+
+ /*
+ * Need the adapterDesc to limit transfers based on byte count
+ */
+ PCOMMON_DEVICE_EXTENSION commonExtension = Pkt->Fdo->DeviceExtension;
+ PSTORAGE_ADAPTER_DESCRIPTOR adapterDesc = commonExtension->PartitionZeroExtension->AdapterDescriptor;
+
+ ULONG hwMaxXferLen;
+
+ /*
+ * special case: StartIO-based writing must stay serialized for performance
+ * and proper operations (i.e. sequential writing mode). If need more than
+ * one transfer to perform this operation, and it's a StartIO-based driver
+ * (such as CDROM), then just use a single packet and use the retry logic
+ * that's already built-in to the packet engine. Note that low-mem retry
+ * cannot be used directly because some write methods do not work if the
+ * writes are only PAGE_SIZE (i.e. packet writing may corrupt data).
+ */
+ NT_ASSERT(!Pkt->InLowMemRetry);
+
+ /*
+ * We precomputed fdoData->HwMaxXferLen using (MaximumPhysicalPages-1).
+ * If the buffer is page-aligned, that's one less page crossing so we can add the page back in.
+ * Note: adapters that return MaximumPhysicalPages=0x10 depend on this to
+ * transfer aligned 64K requests in one piece.
+ * Also note: make sure adding PAGE_SIZE back in doesn't wrap to zero.
+ */
+
+ if (((ULONG_PTR)(Pkt->LowMemRetry_remainingBufPtr) & (PAGE_SIZE-1)) || (fdoData->HwMaxXferLen > 0xffffffff-PAGE_SIZE)){
+ hwMaxXferLen = fdoData->HwMaxXferLen;
+ }
+ else {
+ NT_ASSERT((PAGE_SIZE%fdoExt->DiskGeometry.BytesPerSector) == 0);
+ hwMaxXferLen = min(fdoData->HwMaxXferLen+PAGE_SIZE, adapterDesc->MaximumTransferLength);
+ }
+ thisChunkLen = MIN(Pkt->LowMemRetry_remainingBufLen, hwMaxXferLen);
+ }
+ else {
+ /*
+ * Make sure the little chunk we send is <= a page length
+ * AND that it does not cross any page boundaries.
+ */
+ ULONG bytesToNextPageBoundary;
+ bytesToNextPageBoundary = PAGE_SIZE-(ULONG)((ULONG_PTR)Pkt->LowMemRetry_remainingBufPtr%PAGE_SIZE);
+ thisChunkLen = MIN(Pkt->LowMemRetry_remainingBufLen, bytesToNextPageBoundary);
+ NT_ASSERT(Pkt->InLowMemRetry);
+ }
+
+
+ /*
+ * Set up the transfer packet for the new little chunk.
+ * This will reset numRetries so that we retry each chunk as required.
+ */
+ SetupReadWriteTransferPacket(Pkt,
+ Pkt->LowMemRetry_remainingBufPtr,
+ thisChunkLen,
+ Pkt->LowMemRetry_nextChunkTargetLocation,
+ Pkt->OriginalIrp);
+
+ Pkt->LowMemRetry_remainingBufPtr += thisChunkLen;
+ Pkt->LowMemRetry_remainingBufLen -= thisChunkLen;
+ Pkt->LowMemRetry_nextChunkTargetLocation.QuadPart += thisChunkLen;
+
+ //
+ // When running in low-memory stress, always use a partial MDL.
+ // This allows lower drivers to potentially map a smaller buffer.
+ //
+ Pkt->UsePartialMdl = TRUE;
+
+ TransferPacketQueueRetryDpc(Pkt);
+
+ packetDone = FALSE;
+ }
+
+ return packetDone;
+}
+
diff --git a/storage/class/classpnp/src/srblib.c b/storage/class/classpnp/src/srblib.c
new file mode 100644
index 00000000..a888d390
--- /dev/null
+++ b/storage/class/classpnp/src/srblib.c
@@ -0,0 +1,374 @@
+/*++
+
+Copyright (C) Microsoft Corporation 2010
+
+Module Name:
+
+ srblib.c
+
+Abstract:
+
+ Header for SRB utility functions
+
+Environment:
+
+ kernel mode only
+
+Notes:
+
+
+Revision History:
+
+--*/
+
+
+#include <classp.h>
+
+PVOID
+DefaultStorageRequestBlockAllocateRoutine(
+ _In_ CLONG ByteSize
+ )
+/*++
+
+Routine Description:
+
+ Default allocation routine.
+
+Arguments:
+
+ ByteSize - SRB size in bytes.
+
+Return Value:
+
+ Pointer to the SRB buffer. NULL if SRB buffer could not be allocated.
+
+--*/
+{
+ return ExAllocatePoolWithTag(NonPagedPoolNx, ByteSize, '+brs');
+}
+
+
+NTSTATUS
+pInitializeStorageRequestBlock(
+ _Inout_bytecount_(ByteSize) PSTORAGE_REQUEST_BLOCK Srb,
+ _In_ USHORT AddressType,
+ _In_ ULONG ByteSize,
+ _In_ ULONG NumSrbExData,
+ _In_ va_list ap
+ )
+/*++
+
+Routine Description:
+
+ Initialize a STORAGE_REQUEST_BLOCK.
+
+Arguments:
+
+ Srb - Pointer to STORAGE_REQUEST_BLOCK to initialize.
+
+ AddressType - Storage address type.
+
+ ByteSize - STORAGE_REQUEST_BLOCK size in bytes.
+
+ NumSrbExData - Number of SRB extended data.
+
+ ap - Variable argument list matching the SRB extended data in the
+ STORAGE_REQUEST_BLOCK.
+
+Return Value:
+
+ NTSTATUS
+
+--*/
+{
+ NTSTATUS status = STATUS_SUCCESS;
+ PSTOR_ADDRESS address;
+ PSRBEX_DATA srbExData;
+ ULONG offset;
+ ULONG length = (ULONG)-1;
+ SRBEXDATATYPE type;
+ ULONG srbExDataLength = (ULONG)-1;
+ ULONG varLength;
+ ULONG i;
+
+ if (ByteSize < sizeof(STORAGE_REQUEST_BLOCK)) {
+ return STATUS_BUFFER_OVERFLOW;
+ }
+
+ RtlZeroMemory(Srb, ByteSize);
+
+ Srb->Length = FIELD_OFFSET(STORAGE_REQUEST_BLOCK, Signature);
+ Srb->Function = SRB_FUNCTION_STORAGE_REQUEST_BLOCK;
+ Srb->Signature = SRB_SIGNATURE;
+ Srb->Version = STORAGE_REQUEST_BLOCK_VERSION_1;
+ Srb->SrbLength = ByteSize;
+ Srb->NumSrbExData = NumSrbExData;
+
+ offset = sizeof(STORAGE_REQUEST_BLOCK);
+ if (NumSrbExData > 0) {
+ offset += ((NumSrbExData - 1) * sizeof(ULONG));
+
+ // Ensure offset is pointer type aligned
+ if (offset % sizeof(PVOID)) {
+ offset += (sizeof(PVOID) - (offset % sizeof(PVOID)));
+ }
+ }
+ Srb->AddressOffset = offset;
+
+ if (AddressType == STORAGE_ADDRESS_TYPE_BTL8)
+ {
+ if ((ByteSize < offset) ||
+ (ByteSize < (offset + sizeof(STOR_ADDR_BTL8)))) {
+ return STATUS_BUFFER_OVERFLOW;
+ }
+ address = (PSTOR_ADDRESS)((PUCHAR)Srb + offset);
+ address->Type = STOR_ADDRESS_TYPE_BTL8;
+ address->AddressLength = STOR_ADDR_BTL8_ADDRESS_LENGTH;
+ offset += sizeof(STOR_ADDR_BTL8);
+ } else
+ {
+ status = STATUS_INVALID_PARAMETER;
+ }
+
+ for (i = 0; i < NumSrbExData && status == STATUS_SUCCESS; i++)
+ {
+ if (ByteSize <= offset) {
+ status = STATUS_BUFFER_OVERFLOW;
+ break;
+ }
+ srbExData = (PSRBEX_DATA)((PUCHAR)Srb + offset);
+ Srb->SrbExDataOffset[i] = offset;
+
+ type = va_arg(ap, SRBEXDATATYPE);
+
+ switch (type)
+ {
+ case SrbExDataTypeBidirectional:
+ length = sizeof(SRBEX_DATA_BIDIRECTIONAL);
+ srbExDataLength = SRBEX_DATA_BIDIRECTIONAL_LENGTH;
+ break;
+ case SrbExDataTypeScsiCdb16:
+ length = sizeof(SRBEX_DATA_SCSI_CDB16);
+ srbExDataLength = SRBEX_DATA_SCSI_CDB16_LENGTH;
+ break;
+ case SrbExDataTypeScsiCdb32:
+ length = sizeof(SRBEX_DATA_SCSI_CDB32);
+ srbExDataLength = SRBEX_DATA_SCSI_CDB32_LENGTH;
+ break;
+ case SrbExDataTypeScsiCdbVar:
+ varLength = va_arg(ap, ULONG);
+ length = sizeof(SRBEX_DATA_SCSI_CDB_VAR) + varLength;
+ srbExDataLength = SRBEX_DATA_SCSI_CDB_VAR_LENGTH_MIN + varLength;
+ break;
+ case SrbExDataTypeWmi:
+ length = sizeof(SRBEX_DATA_WMI);
+ srbExDataLength = SRBEX_DATA_WMI_LENGTH;
+ break;
+ case SrbExDataTypePower:
+ length = sizeof(SRBEX_DATA_POWER);
+ srbExDataLength = SRBEX_DATA_POWER_LENGTH;
+ break;
+ case SrbExDataTypePnP:
+ length = sizeof(SRBEX_DATA_PNP);
+ srbExDataLength = SRBEX_DATA_PNP_LENGTH;
+ break;
+ case SrbExDataTypeIoInfo:
+ length = sizeof(SRBEX_DATA_IO_INFO);
+ srbExDataLength = SRBEX_DATA_IO_INFO_LENGTH;
+ break;
+ default:
+ status = STATUS_INVALID_PARAMETER;
+ break;
+ }
+
+ if (status == STATUS_SUCCESS)
+ {
+ NT_ASSERT(length != (ULONG)-1);
+
+ if (ByteSize < (offset + length)) {
+ status = STATUS_BUFFER_OVERFLOW;
+ break;
+ }
+
+ NT_ASSERT(srbExDataLength != (ULONG)-1);
+
+ srbExData->Type = type;
+ srbExData->Length = srbExDataLength;
+ offset += length;
+ }
+ }
+
+ return status;
+}
+
+
+NTSTATUS
+InitializeStorageRequestBlock(
+ _Inout_bytecount_(ByteSize) PSTORAGE_REQUEST_BLOCK Srb,
+ _In_ USHORT AddressType,
+ _In_ ULONG ByteSize,
+ _In_ ULONG NumSrbExData,
+ ...
+ )
+/*++
+
+Routine Description:
+
+ Initialize an extended SRB.
+
+Arguments:
+
+ Srb - Pointer to SRB buffer to initialize.
+
+ AddressType - Storage address type.
+
+ ByteSize - STORAGE_REQUEST_BLOCK size in bytes.
+
+ NumSrbExData - Number of SRB extended data.
+
+ ... - Variable argument list matching the SRB extended data in the
+ STORAGE_REQUEST_BLOCK.
+
+Return Value:
+
+ NTSTATUS
+
+--*/
+{
+ NTSTATUS status;
+ va_list ap;
+ va_start(ap, NumSrbExData);
+ status = pInitializeStorageRequestBlock(Srb, AddressType, ByteSize, NumSrbExData, ap);
+ va_end(ap);
+ return status;
+}
+
+
+
+NTSTATUS
+CreateStorageRequestBlock(
+ _Inout_ PSTORAGE_REQUEST_BLOCK *Srb,
+ _In_ USHORT AddressType,
+ _In_opt_ PSRB_ALLOCATE_ROUTINE AllocateRoutine,
+ _Inout_opt_ ULONG *ByteSize,
+ _In_ ULONG NumSrbExData,
+ ...
+ )
+/*++
+
+Routine Description:
+
+ Create an extended SRB.
+
+Arguments:
+
+ Srb - Pointer to buffer to store SRB pointer.
+
+ AddressType - Storage address type.
+
+ AllocateRoutine - Buffer allocation function (optional).
+
+ ByteSize - Pointer to ULONG to store size of SRB in bytes (optional).
+
+ NumSrbExData - Number of SRB extended data.
+
+ ... - Variable argument list matching the SRB extended data in the
+ STORAGE_REQUEST_BLOCK.
+
+Return Value:
+
+ NTSTATUS
+
+--*/
+{
+ ULONG sizeNeeded = 0;
+ va_list ap;
+ ULONG i;
+ NTSTATUS status = STATUS_SUCCESS;
+
+ // Ensure SrbExData offsets are pointer type aligned
+ sizeNeeded = sizeof(STORAGE_REQUEST_BLOCK);
+ if (NumSrbExData > 0) {
+ sizeNeeded += ((NumSrbExData - 1) * sizeof(ULONG));
+ if (sizeNeeded % sizeof(PVOID)) {
+ sizeNeeded += (sizeof(PVOID) - (sizeNeeded % sizeof(PVOID)));
+ }
+ }
+
+ if (AddressType == STORAGE_ADDRESS_TYPE_BTL8)
+ {
+ sizeNeeded += sizeof(STOR_ADDR_BTL8);
+ } else
+ {
+ status = STATUS_INVALID_PARAMETER;
+ }
+
+ va_start(ap, NumSrbExData);
+
+ for (i = 0; i < NumSrbExData && status == STATUS_SUCCESS; i++)
+ {
+ switch (va_arg(ap, SRBEXDATATYPE))
+ {
+ case SrbExDataTypeBidirectional:
+ sizeNeeded += sizeof(SRBEX_DATA_BIDIRECTIONAL);
+ break;
+ case SrbExDataTypeScsiCdb16:
+ sizeNeeded += sizeof(SRBEX_DATA_SCSI_CDB16);
+ break;
+ case SrbExDataTypeScsiCdb32:
+ sizeNeeded += sizeof(SRBEX_DATA_SCSI_CDB32);
+ break;
+ case SrbExDataTypeScsiCdbVar:
+ sizeNeeded += sizeof(SRBEX_DATA_SCSI_CDB_VAR) + va_arg(ap, ULONG);
+ break;
+ case SrbExDataTypeWmi:
+ sizeNeeded += sizeof(SRBEX_DATA_WMI);
+ break;
+ case SrbExDataTypePower:
+ sizeNeeded += sizeof(SRBEX_DATA_POWER);
+ break;
+ case SrbExDataTypePnP:
+ sizeNeeded += sizeof(SRBEX_DATA_PNP);
+ break;
+ case SrbExDataTypeIoInfo:
+ sizeNeeded += sizeof(SRBEX_DATA_IO_INFO);
+ break;
+ default:
+ status = STATUS_INVALID_PARAMETER;
+ break;
+ }
+ }
+ va_end(ap);
+
+ if (status == STATUS_SUCCESS)
+ {
+ if (AllocateRoutine)
+ {
+ *Srb = AllocateRoutine(sizeNeeded);
+ if (*Srb == NULL)
+ {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ }
+
+ if (ByteSize != NULL)
+ {
+ *ByteSize = sizeNeeded;
+ }
+
+ if (*Srb)
+ {
+ va_start(ap, NumSrbExData);
+ #pragma prefast(suppress:26015, "pInitializeStorageRequestBlock will set the SrbLength field")
+ status = pInitializeStorageRequestBlock(*Srb, AddressType, sizeNeeded, NumSrbExData, ap);
+ va_end(ap);
+ }
+
+ }
+
+ return status;
+}
+
+
+
+
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;
+}
+
+
diff --git a/storage/class/classpnp/src/xferpkt.c b/storage/class/classpnp/src/xferpkt.c
new file mode 100644
index 00000000..e4a051ed
--- /dev/null
+++ b/storage/class/classpnp/src/xferpkt.c
@@ -0,0 +1,2047 @@
+/*++
+
+Copyright (C) Microsoft Corporation, 1991 - 2010
+
+Module Name:
+
+ xferpkt.c
+
+Abstract:
+
+ Packet routines for CLASSPNP
+
+Environment:
+
+ kernel mode only
+
+Notes:
+
+
+Revision History:
+
+--*/
+
+#include "classp.h"
+#include "debug.h"
+
+#ifdef DEBUG_USE_WPP
+#include "xferpkt.tmh"
+#endif
+
+#ifdef ALLOC_PRAGMA
+ #pragma alloc_text(PAGE, InitializeTransferPackets)
+ #pragma alloc_text(PAGE, DestroyAllTransferPackets)
+ #pragma alloc_text(PAGE, SetupEjectionTransferPacket)
+ #pragma alloc_text(PAGE, SetupModeSenseTransferPacket)
+ #pragma alloc_text(PAGE, CleanupTransferPacketToWorkingSetSizeWorker)
+ #pragma alloc_text(PAGE, ClasspSetupPopulateTokenTransferPacket)
+#endif
+
+/*
+ * InitializeTransferPackets
+ *
+ * Allocate/initialize TRANSFER_PACKETs and related resources.
+ */
+NTSTATUS InitializeTransferPackets(PDEVICE_OBJECT Fdo)
+{
+ PCOMMON_DEVICE_EXTENSION commonExt = Fdo->DeviceExtension;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
+ PSTORAGE_ADAPTER_DESCRIPTOR adapterDesc = commonExt->PartitionZeroExtension->AdapterDescriptor;
+ PSTORAGE_DEVICE_IO_CAPABILITY_DESCRIPTOR devIoCapabilityDesc = NULL;
+ STORAGE_PROPERTY_ID propertyId;
+ OSVERSIONINFOEXW osVersionInfo;
+ ULONG hwMaxPages;
+ ULONG arraySize;
+ ULONG index;
+ ULONG maxOutstandingIOPerLUN;
+ ULONG minWorkingSetTransferPackets;
+ ULONG maxWorkingSetTransferPackets;
+
+ NTSTATUS status = STATUS_SUCCESS;
+
+ PAGED_CODE();
+
+ //
+ // Precompute the maximum transfer length
+ //
+ NT_ASSERT(adapterDesc->MaximumTransferLength);
+
+ hwMaxPages = adapterDesc->MaximumPhysicalPages ? adapterDesc->MaximumPhysicalPages-1 : 0;
+
+ fdoData->HwMaxXferLen = MIN(adapterDesc->MaximumTransferLength, hwMaxPages << PAGE_SHIFT);
+ fdoData->HwMaxXferLen = MAX(fdoData->HwMaxXferLen, PAGE_SIZE);
+
+ //
+ // Allocate per-node free packet lists
+ //
+ arraySize = KeQueryHighestNodeNumber() + 1;
+ fdoData->FreeTransferPacketsLists =
+ ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned,
+ sizeof(PNL_SLIST_HEADER) * arraySize,
+ CLASS_TAG_PRIVATE_DATA);
+
+ if (fdoData->FreeTransferPacketsLists == NULL) {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ return status;
+ }
+
+ for (index = 0; index < arraySize; index++) {
+ InitializeSListHead(&(fdoData->FreeTransferPacketsLists[index].SListHeader));
+ fdoData->FreeTransferPacketsLists[index].NumTotalTransferPackets = 0;
+ fdoData->FreeTransferPacketsLists[index].NumFreeTransferPackets = 0;
+ }
+
+ InitializeListHead(&fdoData->AllTransferPacketsList);
+
+ //
+ // Set the packet threshold numbers based on the Windows Client or Server SKU.
+ //
+
+ osVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXW);
+ status = RtlGetVersion((POSVERSIONINFOW) &osVersionInfo);
+
+ NT_ASSERT( NT_SUCCESS(status));
+
+ //
+ // Retrieve info on IO capability supported by port drivers
+ //
+
+ propertyId = StorageDeviceIoCapabilityProperty;
+ status = ClassGetDescriptor(fdoExt->CommonExtension.LowerDeviceObject,
+ &propertyId,
+ &devIoCapabilityDesc);
+
+ if (NT_SUCCESS(status) && (devIoCapabilityDesc != NULL)) {
+ maxOutstandingIOPerLUN = devIoCapabilityDesc->LunMaxIoCount;
+ FREE_POOL(devIoCapabilityDesc);
+
+#if DBG
+ fdoData->MaxOutstandingIOPerLUN = maxOutstandingIOPerLUN;
+#endif
+
+ } else {
+ maxOutstandingIOPerLUN = MAX_OUTSTANDING_IO_PER_LUN_DEFAULT;
+
+#if DBG
+ fdoData->MaxOutstandingIOPerLUN = 0;
+#endif
+
+ }
+
+ //
+ // StorageDeviceIoCapabilityProperty support is optional so
+ // ignore any failures.
+ //
+
+ status = STATUS_SUCCESS;
+
+
+ if ((osVersionInfo.wProductType != VER_NT_DOMAIN_CONTROLLER) &&
+ (osVersionInfo.wProductType != VER_NT_SERVER)) {
+
+ // this is Client SKU
+
+ minWorkingSetTransferPackets = MIN_WORKINGSET_TRANSFER_PACKETS_Client;
+
+ // Note: the reason we use max here is to guarantee a reasonable large max number
+ // in the case where the port driver may return a very small supported outstanding
+ // IOs. For example, even EMMC drive only reports 1 outstanding IO supported, we
+ // may still want to set this value to be at least
+ // MAX_WORKINGSET_TRANSFER_PACKETS_Client.
+ maxWorkingSetTransferPackets = max(MAX_WORKINGSET_TRANSFER_PACKETS_Client,
+ 2 * maxOutstandingIOPerLUN);
+
+ } else {
+
+ // this is Server SKU
+ // Note: the addition max here to make sure we set the min to be at least
+ // MIN_WORKINGSET_TRANSFER_PACKETS_Server_LowerBound no matter what maxOutstandingIOPerLUN
+ // reported. We shouldn�t set this value to be smaller than client system.
+ // In other words, the minWorkingSetTransferPackets for server will always between
+ // MIN_WORKINGSET_TRANSFER_PACKETS_Server_LowerBound and MIN_WORKINGSET_TRANSFER_PACKETS_Server_UpperBound
+
+ minWorkingSetTransferPackets =
+ max(MIN_WORKINGSET_TRANSFER_PACKETS_Server_LowerBound,
+ min(MIN_WORKINGSET_TRANSFER_PACKETS_Server_UpperBound,
+ maxOutstandingIOPerLUN));
+
+ maxWorkingSetTransferPackets = max(MAX_WORKINGSET_TRANSFER_PACKETS_Server,
+ 2 * maxOutstandingIOPerLUN);
+ }
+
+
+ fdoData->LocalMinWorkingSetTransferPackets = minWorkingSetTransferPackets;
+ fdoData->LocalMaxWorkingSetTransferPackets = maxWorkingSetTransferPackets;
+
+ //
+ // Allow class driver to override the settings
+ //
+ if (commonExt->DriverExtension->WorkingSet != NULL) {
+ PCLASS_WORKING_SET workingSet = commonExt->DriverExtension->WorkingSet;
+
+ // override only if non-zero
+ if (workingSet->XferPacketsWorkingSetMinimum != 0)
+ {
+ fdoData->LocalMinWorkingSetTransferPackets = workingSet->XferPacketsWorkingSetMinimum;
+ // adjust maximum upwards if needed
+ if (fdoData->LocalMaxWorkingSetTransferPackets < fdoData->LocalMinWorkingSetTransferPackets)
+ {
+ fdoData->LocalMaxWorkingSetTransferPackets = fdoData->LocalMinWorkingSetTransferPackets;
+ }
+ }
+ // override only if non-zero
+ if (workingSet->XferPacketsWorkingSetMaximum != 0)
+ {
+ fdoData->LocalMaxWorkingSetTransferPackets = workingSet->XferPacketsWorkingSetMaximum;
+ // adjust minimum downwards if needed
+ if (fdoData->LocalMinWorkingSetTransferPackets > fdoData->LocalMaxWorkingSetTransferPackets)
+ {
+ fdoData->LocalMinWorkingSetTransferPackets = fdoData->LocalMaxWorkingSetTransferPackets;
+ }
+ }
+ // that's all the adjustments required/allowed
+ } // end working set size special code
+
+ for (index = 0; index < arraySize; index++) {
+ while (fdoData->FreeTransferPacketsLists[index].NumFreeTransferPackets < MIN_INITIAL_TRANSFER_PACKETS){
+ PTRANSFER_PACKET pkt = NewTransferPacket(Fdo);
+ if (pkt) {
+ InterlockedIncrement((volatile LONG *)&(fdoData->FreeTransferPacketsLists[index].NumTotalTransferPackets));
+ pkt->AllocateNode = index;
+ EnqueueFreeTransferPacket(Fdo, pkt);
+ } else {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ break;
+ }
+ }
+ fdoData->FreeTransferPacketsLists[index].DbgPeakNumTransferPackets = fdoData->FreeTransferPacketsLists[index].NumTotalTransferPackets;
+ }
+
+ //
+ // Pre-initialize our SCSI_REQUEST_BLOCK template with all
+ // the constant fields. This will save a little time for each xfer.
+ // NOTE: a CdbLength field of 10 may not always be appropriate
+ //
+
+ if (NT_SUCCESS(status)) {
+ if (fdoExt->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+ ULONG ByteSize = 0;
+
+ #if (NTDDI_VERSION >= NTDDI_WINBLUE)
+ if ((fdoExt->MiniportDescriptor != NULL) &&
+ (fdoExt->MiniportDescriptor->Size >= RTL_SIZEOF_THROUGH_FIELD(STORAGE_MINIPORT_DESCRIPTOR, ExtraIoInfoSupported)) &&
+ (fdoExt->MiniportDescriptor->ExtraIoInfoSupported == TRUE)) {
+ status = CreateStorageRequestBlock((PSTORAGE_REQUEST_BLOCK *)&fdoData->SrbTemplate,
+ fdoExt->AdapterDescriptor->AddressType,
+ DefaultStorageRequestBlockAllocateRoutine,
+ &ByteSize,
+ 2,
+ SrbExDataTypeScsiCdb16,
+ SrbExDataTypeIoInfo
+ );
+ } else {
+ status = CreateStorageRequestBlock((PSTORAGE_REQUEST_BLOCK *)&fdoData->SrbTemplate,
+ fdoExt->AdapterDescriptor->AddressType,
+ DefaultStorageRequestBlockAllocateRoutine,
+ &ByteSize,
+ 1,
+ SrbExDataTypeScsiCdb16
+ );
+ }
+ #else
+ status = CreateStorageRequestBlock((PSTORAGE_REQUEST_BLOCK *)&fdoData->SrbTemplate,
+ fdoExt->AdapterDescriptor->AddressType,
+ DefaultStorageRequestBlockAllocateRoutine,
+ &ByteSize,
+ 1,
+ SrbExDataTypeScsiCdb16
+ );
+ #endif
+ if (NT_SUCCESS(status)) {
+ ((PSTORAGE_REQUEST_BLOCK) fdoData->SrbTemplate)->SrbFunction = SRB_FUNCTION_EXECUTE_SCSI;
+ } else {
+ NT_ASSERT(FALSE);
+ }
+ } else {
+ fdoData->SrbTemplate = ExAllocatePoolWithTag(NonPagedPoolNx, sizeof(SCSI_REQUEST_BLOCK), '-brs');
+ if (fdoData->SrbTemplate == NULL) {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ } else {
+ RtlZeroMemory(fdoData->SrbTemplate, sizeof(SCSI_REQUEST_BLOCK));
+ fdoData->SrbTemplate->Length = sizeof(SCSI_REQUEST_BLOCK);
+ fdoData->SrbTemplate->Function = SRB_FUNCTION_EXECUTE_SCSI;
+ }
+ }
+ }
+
+ if (status == STATUS_SUCCESS) {
+ SrbSetRequestAttribute(fdoData->SrbTemplate, SRB_SIMPLE_TAG_REQUEST);
+ SrbSetSenseInfoBufferLength(fdoData->SrbTemplate, SENSE_BUFFER_SIZE_EX);
+ SrbSetCdbLength(fdoData->SrbTemplate, 10);
+ }
+
+ return status;
+}
+
+
+VOID DestroyAllTransferPackets(PDEVICE_OBJECT Fdo)
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
+ TRANSFER_PACKET *pkt;
+ ULONG index;
+ ULONG arraySize;
+
+ PAGED_CODE();
+
+ //
+ // fdoData->FreeTransferPacketsLists could be NULL if
+ // there was an error during start device.
+ //
+ if (fdoData->FreeTransferPacketsLists != NULL) {
+
+ NT_ASSERT(IsListEmpty(&fdoData->DeferredClientIrpList));
+
+ arraySize = KeQueryHighestNodeNumber() + 1;
+ for (index = 0; index < arraySize; index++) {
+ pkt = DequeueFreeTransferPacketEx(Fdo, FALSE, index);
+ while (pkt) {
+ DestroyTransferPacket(pkt);
+ InterlockedDecrement((volatile LONG *)&(fdoData->FreeTransferPacketsLists[index].NumTotalTransferPackets));
+ pkt = DequeueFreeTransferPacketEx(Fdo, FALSE, index);
+ }
+
+ NT_ASSERT(fdoData->FreeTransferPacketsLists[index].NumTotalTransferPackets == 0);
+ }
+ }
+
+ FREE_POOL(fdoData->SrbTemplate);
+}
+
+__drv_allocatesMem(Mem)
+#pragma warning(suppress:28195) // This function may not allocate memory in some error cases.
+PTRANSFER_PACKET NewTransferPacket(PDEVICE_OBJECT Fdo)
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
+ PTRANSFER_PACKET newPkt = NULL;
+ ULONG transferLength = (ULONG)-1;
+ NTSTATUS status = STATUS_SUCCESS;
+
+ if (NT_SUCCESS(status)) {
+ status = RtlULongAdd(fdoData->HwMaxXferLen, PAGE_SIZE, &transferLength);
+ if (!NT_SUCCESS(status)) {
+
+ TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_RW, "Integer overflow in calculating transfer packet size."));
+ status = STATUS_INTEGER_OVERFLOW;
+ }
+ }
+
+ /*
+ * Allocate the actual packet.
+ */
+ if (NT_SUCCESS(status)) {
+ newPkt = ExAllocatePoolWithTag(NonPagedPoolNx, sizeof(TRANSFER_PACKET), 'pnPC');
+ if (newPkt == NULL) {
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_RW, "Failed to allocate transfer packet."));
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ } else {
+ RtlZeroMemory(newPkt, sizeof(TRANSFER_PACKET));
+ newPkt->AllocateNode = KeGetCurrentNodeNumber();
+ if (fdoExt->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+#if (NTDDI_VERSION >= NTDDI_WINBLUE)
+ if ((fdoExt->MiniportDescriptor != NULL) &&
+ (fdoExt->MiniportDescriptor->Size >= RTL_SIZEOF_THROUGH_FIELD(STORAGE_MINIPORT_DESCRIPTOR, ExtraIoInfoSupported)) &&
+ (fdoExt->MiniportDescriptor->ExtraIoInfoSupported == TRUE)) {
+ status = CreateStorageRequestBlock((PSTORAGE_REQUEST_BLOCK *)&newPkt->Srb,
+ fdoExt->AdapterDescriptor->AddressType,
+ DefaultStorageRequestBlockAllocateRoutine,
+ NULL,
+ 2,
+ SrbExDataTypeScsiCdb16,
+ SrbExDataTypeIoInfo
+ );
+ } else {
+ status = CreateStorageRequestBlock((PSTORAGE_REQUEST_BLOCK *)&newPkt->Srb,
+ fdoExt->AdapterDescriptor->AddressType,
+ DefaultStorageRequestBlockAllocateRoutine,
+ NULL,
+ 1,
+ SrbExDataTypeScsiCdb16
+ );
+ }
+#else
+ status = CreateStorageRequestBlock((PSTORAGE_REQUEST_BLOCK *)&newPkt->Srb,
+ fdoExt->AdapterDescriptor->AddressType,
+ DefaultStorageRequestBlockAllocateRoutine,
+ NULL,
+ 1,
+ SrbExDataTypeScsiCdb16
+ );
+#endif
+ } else {
+#pragma prefast(suppress:6014, "The allocated memory that Pkt->Srb points to will be freed in DestroyTransferPacket().")
+ newPkt->Srb = ExAllocatePoolWithTag(NonPagedPoolNx, sizeof(SCSI_REQUEST_BLOCK), '-brs');
+ if (newPkt->Srb == NULL) {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ }
+
+ if (status != STATUS_SUCCESS)
+ {
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_RW, "Failed to allocate SRB."));
+ FREE_POOL(newPkt);
+ }
+ }
+ }
+
+ /*
+ * Allocate Irp for the packet.
+ */
+ if (NT_SUCCESS(status) && newPkt != NULL) {
+ newPkt->Irp = IoAllocateIrp(Fdo->StackSize, FALSE);
+ if (newPkt->Irp == NULL) {
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_RW, "Failed to allocate IRP for transfer packet."));
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ }
+ }
+
+ /*
+ * Allocate a MDL. Add one page to the length to insure an extra page
+ * entry is allocated if the buffer does not start on page boundaries.
+ */
+ if (NT_SUCCESS(status) && newPkt != NULL) {
+
+ NT_ASSERT(transferLength != (ULONG)-1);
+
+ newPkt->PartialMdl = IoAllocateMdl(NULL,
+ transferLength,
+ FALSE,
+ FALSE,
+ NULL);
+ if (newPkt->PartialMdl == NULL) {
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_RW, "Failed to allocate MDL for transfer packet."));
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ } else {
+ NT_ASSERT(newPkt->PartialMdl->Size >= (CSHORT)(sizeof(MDL) + BYTES_TO_PAGES(fdoData->HwMaxXferLen) * sizeof(PFN_NUMBER)));
+ }
+
+ }
+
+ /*
+ * Allocate per-packet retry history, if required
+ */
+ if (NT_SUCCESS(status) &&
+ (fdoData->InterpretSenseInfo != NULL) &&
+ (newPkt != NULL)
+ ) {
+ // attempt to allocate also the history
+ ULONG historyByteCount = 0;
+
+ // SAL annotation and ClassInitializeEx() should both catch this case
+ NT_ASSERT(fdoData->InterpretSenseInfo->HistoryCount != 0);
+ _Analysis_assume_(fdoData->InterpretSenseInfo->HistoryCount != 0);
+
+ historyByteCount = sizeof(SRB_HISTORY_ITEM) * fdoData->InterpretSenseInfo->HistoryCount;
+ historyByteCount += sizeof(SRB_HISTORY) - sizeof(SRB_HISTORY_ITEM);
+
+ newPkt->RetryHistory = (PSRB_HISTORY)ExAllocatePoolWithTag(NonPagedPoolNx, historyByteCount, 'hrPC');
+
+ if (newPkt->RetryHistory == NULL) {
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_RW, "Failed to allocate MDL for transfer packet."));
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ } else {
+ // call this routine directly once since it's the first initialization of
+ // the structure and the internal maximum count field is not yet setup.
+ HistoryInitializeRetryLogs(newPkt->RetryHistory, fdoData->InterpretSenseInfo->HistoryCount);
+ }
+ }
+
+ /*
+ * Enqueue the packet in our static AllTransferPacketsList
+ * (just so we can find it during debugging if its stuck somewhere).
+ */
+ if (NT_SUCCESS(status) && newPkt != NULL)
+ {
+ KIRQL oldIrql;
+ newPkt->Fdo = Fdo;
+#if DBG
+ newPkt->DbgPktId = InterlockedIncrement((volatile LONG *)&fdoData->DbgMaxPktId);
+#endif
+ KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
+ InsertTailList(&fdoData->AllTransferPacketsList, &newPkt->AllPktsListEntry);
+ KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
+
+ } else {
+ // free any resources acquired above (in reverse order)
+ if (newPkt != NULL) {
+ FREE_POOL(newPkt->RetryHistory);
+ if (newPkt->PartialMdl != NULL) { IoFreeMdl(newPkt->PartialMdl); }
+ if (newPkt->Irp != NULL) { IoFreeIrp(newPkt->Irp); }
+ if (newPkt->Srb != NULL) { FREE_POOL(newPkt->Srb); }
+ FREE_POOL(newPkt);
+ }
+ }
+
+ return newPkt;
+}
+
+
+/*
+ * DestroyTransferPacket
+ *
+ */
+VOID DestroyTransferPacket(_In_ __drv_freesMem(mem) PTRANSFER_PACKET Pkt)
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Pkt->Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
+ KIRQL oldIrql;
+
+ NT_ASSERT(!Pkt->SlistEntry.Next);
+// NT_ASSERT(!Pkt->OriginalIrp);
+
+ KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
+
+ /*
+ * Delete the packet from our all-packets queue.
+ */
+ NT_ASSERT(!IsListEmpty(&Pkt->AllPktsListEntry));
+ NT_ASSERT(!IsListEmpty(&fdoData->AllTransferPacketsList));
+ RemoveEntryList(&Pkt->AllPktsListEntry);
+ InitializeListHead(&Pkt->AllPktsListEntry);
+
+ KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
+
+ IoFreeMdl(Pkt->PartialMdl);
+ IoFreeIrp(Pkt->Irp);
+ FREE_POOL(Pkt->RetryHistory);
+ FREE_POOL(Pkt->Srb);
+ FREE_POOL(Pkt);
+}
+
+
+VOID EnqueueFreeTransferPacket(PDEVICE_OBJECT Fdo, __drv_aliasesMem PTRANSFER_PACKET Pkt)
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
+ ULONG allocateNode;
+ KIRQL oldIrql;
+
+ NT_ASSERT(!Pkt->SlistEntry.Next);
+
+ allocateNode = Pkt->AllocateNode;
+ InterlockedPushEntrySList(&(fdoData->FreeTransferPacketsLists[allocateNode].SListHeader), &Pkt->SlistEntry);
+ InterlockedIncrement((volatile LONG *)&(fdoData->FreeTransferPacketsLists[allocateNode].NumFreeTransferPackets));
+
+ /*
+ * If the total number of packets is larger than LocalMinWorkingSetTransferPackets,
+ * that means that we've been in stress. If all those packets are now
+ * free, then we are now out of stress and can free the extra packets.
+ * Attempt to free down to LocalMaxWorkingSetTransferPackets immediately, and
+ * down to LocalMinWorkingSetTransferPackets lazily (one at a time).
+ * However, since we're at DPC, do this is a work item. If the device is removed
+ * or we are unable to allocate the work item, do NOT free more than
+ * MAX_CLEANUP_TRANSFER_PACKETS_AT_ONCE. Subsequent IO completions will end up freeing
+ * up the rest, even if it is MAX_CLEANUP_TRANSFER_PACKETS_AT_ONCE at a time.
+ */
+ if (fdoData->FreeTransferPacketsLists[allocateNode].NumFreeTransferPackets >=
+ fdoData->FreeTransferPacketsLists[allocateNode].NumTotalTransferPackets) {
+
+ /*
+ * 1. Immediately snap down to our UPPER threshold.
+ */
+ if (fdoData->FreeTransferPacketsLists[allocateNode].NumTotalTransferPackets >
+ fdoData->LocalMaxWorkingSetTransferPackets) {
+
+ ULONG isRemoved;
+ PIO_WORKITEM workItem = NULL;
+
+ workItem = IoAllocateWorkItem(Fdo);
+
+ //
+ // Acquire a remove lock in order to make sure the device object and its
+ // private data structures will exist when the workitem fires.
+ // The remove lock will be released by the workitem (CleanupTransferPacketToWorkingSetSize).
+ //
+ isRemoved = ClassAcquireRemoveLock(Fdo, (PIRP)workItem);
+
+ if (workItem && !isRemoved) {
+
+ TracePrint((TRACE_LEVEL_INFORMATION,
+ TRACE_FLAG_GENERAL,
+ "EnqueueFreeTransferPacket: Device (%p), queuing work item to clean up free transfer packets.\n",
+ Fdo));
+
+ //
+ // Queue a work item to trim down the total number of transfer packets to with the
+ // working size.
+ //
+ IoQueueWorkItemEx(workItem, CleanupTransferPacketToWorkingSetSizeWorker, DelayedWorkQueue, (PVOID) allocateNode);
+
+ } else {
+
+ if (workItem) {
+ IoFreeWorkItem(workItem);
+ }
+
+ if (isRemoved != REMOVE_COMPLETE) {
+ ClassReleaseRemoveLock(Fdo, (PIRP)workItem);
+ }
+
+ TracePrint((TRACE_LEVEL_ERROR,
+ TRACE_FLAG_GENERAL,
+ "EnqueueFreeTransferPacket: Device (%p), Failed to allocate memory for the work item.\n",
+ Fdo));
+
+ CleanupTransferPacketToWorkingSetSize(Fdo, TRUE, allocateNode);
+ }
+ }
+
+ /*
+ * 2. Lazily work down to our LOWER threshold (by only freeing one packet at a time).
+ */
+ if (fdoData->FreeTransferPacketsLists[allocateNode].NumTotalTransferPackets >
+ fdoData->LocalMinWorkingSetTransferPackets){
+ /*
+ * Check the counter again with lock held. This eliminates a race condition
+ * while still allowing us to not grab the spinlock in the common codepath.
+ *
+ * Note that the spinlock does not synchronize with threads dequeuing free
+ * packets to send (DequeueFreeTransferPacket does that with a lightweight
+ * interlocked exchange); the spinlock prevents multiple threads in this function
+ * from deciding to free too many extra packets at once.
+ */
+ PTRANSFER_PACKET pktToDelete = NULL;
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_RW, "Exiting stress, lazily freeing one of %d/%d packets from node %d.",
+ fdoData->FreeTransferPacketsLists[allocateNode].NumTotalTransferPackets,
+ fdoData->LocalMinWorkingSetTransferPackets,
+ allocateNode));
+
+ KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
+ if ((fdoData->FreeTransferPacketsLists[allocateNode].NumFreeTransferPackets >=
+ fdoData->FreeTransferPacketsLists[allocateNode].NumTotalTransferPackets) &&
+ (fdoData->FreeTransferPacketsLists[allocateNode].NumTotalTransferPackets >
+ fdoData->LocalMinWorkingSetTransferPackets)){
+
+ pktToDelete = DequeueFreeTransferPacketEx(Fdo, FALSE, allocateNode);
+ if (pktToDelete) {
+ InterlockedDecrement((volatile LONG *)&(fdoData->FreeTransferPacketsLists[allocateNode].NumTotalTransferPackets));
+ } else {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_RW,
+ "Extremely unlikely condition (non-fatal): %d packets dequeued at once for Fdo %p. NumTotalTransferPackets=%d (2). Node=%d",
+ fdoData->LocalMinWorkingSetTransferPackets,
+ Fdo,
+ fdoData->FreeTransferPacketsLists[allocateNode].NumTotalTransferPackets,
+ allocateNode));
+ }
+ }
+ KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
+
+ if (pktToDelete) {
+ DestroyTransferPacket(pktToDelete);
+ }
+ }
+
+ }
+
+}
+
+PTRANSFER_PACKET DequeueFreeTransferPacket(PDEVICE_OBJECT Fdo, BOOLEAN AllocIfNeeded)
+{
+ return DequeueFreeTransferPacketEx(Fdo, AllocIfNeeded, KeGetCurrentNodeNumber());
+}
+
+PTRANSFER_PACKET DequeueFreeTransferPacketEx(
+ _In_ PDEVICE_OBJECT Fdo,
+ _In_ BOOLEAN AllocIfNeeded,
+ _In_ ULONG Node)
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
+ PTRANSFER_PACKET pkt;
+ PSLIST_ENTRY slistEntry;
+
+ slistEntry = InterlockedPopEntrySList(&(fdoData->FreeTransferPacketsLists[Node].SListHeader));
+
+ if (slistEntry) {
+ slistEntry->Next = NULL;
+ pkt = CONTAINING_RECORD(slistEntry, TRANSFER_PACKET, SlistEntry);
+ InterlockedDecrement((volatile LONG *)&(fdoData->FreeTransferPacketsLists[Node].NumFreeTransferPackets));
+
+ // when dequeuing the packet, also reset the history data
+ HISTORYINITIALIZERETRYLOGS(pkt);
+
+ } else {
+ if (AllocIfNeeded) {
+ /*
+ * We are in stress and have run out of lookaside packets.
+ * In order to service the current transfer,
+ * allocate an extra packet.
+ * We will free it lazily when we are out of stress.
+ */
+ pkt = NewTransferPacket(Fdo);
+ if (pkt) {
+ InterlockedIncrement((volatile LONG *)&fdoData->FreeTransferPacketsLists[Node].NumTotalTransferPackets);
+ fdoData->FreeTransferPacketsLists[Node].DbgPeakNumTransferPackets =
+ max(fdoData->FreeTransferPacketsLists[Node].DbgPeakNumTransferPackets,
+ fdoData->FreeTransferPacketsLists[Node].NumTotalTransferPackets);
+ } else {
+ TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_RW, "DequeueFreeTransferPacket: packet allocation failed"));
+ }
+ } else {
+ pkt = NULL;
+ }
+ }
+
+ return pkt;
+}
+
+
+/*
+ * SetupReadWriteTransferPacket
+ *
+ * This function is called once to set up the first attempt to send a packet.
+ * It is not called before a retry, as SRB fields may be modified for the retry.
+ *
+ * Set up the Srb of the TRANSFER_PACKET for the transfer.
+ * The Irp is set up in SubmitTransferPacket because it must be reset
+ * for each packet submission.
+ */
+VOID SetupReadWriteTransferPacket( PTRANSFER_PACKET Pkt,
+ PVOID Buf,
+ ULONG Len,
+ LARGE_INTEGER DiskLocation,
+ PIRP OriginalIrp)
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Pkt->Fdo->DeviceExtension;
+ PCOMMON_DEVICE_EXTENSION commonExtension = Pkt->Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
+ PIO_STACK_LOCATION origCurSp = IoGetCurrentIrpStackLocation(OriginalIrp);
+ UCHAR majorFunc = origCurSp->MajorFunction;
+ LARGE_INTEGER logicalBlockAddr;
+ ULONG numTransferBlocks;
+ PCDB pCdb;
+ ULONG srbLength;
+ ULONG timeoutValue = fdoExt->TimeOutValue;
+
+ logicalBlockAddr.QuadPart = Int64ShrlMod32(DiskLocation.QuadPart, fdoExt->SectorShift);
+ numTransferBlocks = Len >> fdoExt->SectorShift;
+
+ /*
+ * This field is useful when debugging, since low-memory conditions are
+ * handled differently for CDROM (which is the only driver using StartIO)
+ */
+ Pkt->DriverUsesStartIO = (commonExtension->DriverExtension->InitData.ClassStartIo != NULL);
+
+ /*
+ * Slap the constant SRB fields in from our pre-initialized template.
+ * We'll then only have to fill in the unique fields for this transfer.
+ * Tell lower drivers to sort the SRBs by the logical block address
+ * so that disk seeks are minimized.
+ */
+ if (fdoExt->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+ srbLength = ((PSTORAGE_REQUEST_BLOCK) fdoData->SrbTemplate)->SrbLength;
+ NT_ASSERT(((PSTORAGE_REQUEST_BLOCK) Pkt->Srb)->SrbLength >= srbLength);
+ } else {
+ srbLength = fdoData->SrbTemplate->Length;
+ }
+ RtlCopyMemory(Pkt->Srb, fdoData->SrbTemplate, srbLength); // copies _contents_ of SRB blocks
+ SrbSetDataBuffer(Pkt->Srb, Buf);
+ SrbSetDataTransferLength(Pkt->Srb, Len);
+ SrbSetQueueSortKey(Pkt->Srb, logicalBlockAddr.LowPart);
+ if (logicalBlockAddr.QuadPart > 0xFFFFFFFF) {
+ //
+ // If the requested LBA is more than max ULONG set the
+ // QueueSortKey to the maximum value, so that these
+ // requests can be added towards the end of the queue.
+ //
+
+ SrbSetQueueSortKey(Pkt->Srb, 0xFFFFFFFF);
+ }
+ SrbSetOriginalRequest(Pkt->Srb, Pkt->Irp);
+ SrbSetSenseInfoBuffer(Pkt->Srb, &Pkt->SrbErrorSenseData);
+
+
+ SrbSetTimeOutValue(Pkt->Srb, timeoutValue);
+
+ /*
+ * Arrange values in CDB in big-endian format.
+ */
+ pCdb = SrbGetCdb(Pkt->Srb);
+ if (pCdb) {
+ if (TEST_FLAG(fdoExt->DeviceFlags, DEV_USE_16BYTE_CDB)) {
+ REVERSE_BYTES_QUAD(&pCdb->CDB16.LogicalBlock, &logicalBlockAddr);
+ REVERSE_BYTES(&pCdb->CDB16.TransferLength, &numTransferBlocks);
+ pCdb->CDB16.OperationCode = (majorFunc==IRP_MJ_READ) ? SCSIOP_READ16 : SCSIOP_WRITE16;
+ SrbSetCdbLength(Pkt->Srb, 16);
+ } else {
+ pCdb->CDB10.LogicalBlockByte0 = ((PFOUR_BYTE)&logicalBlockAddr.LowPart)->Byte3;
+ pCdb->CDB10.LogicalBlockByte1 = ((PFOUR_BYTE)&logicalBlockAddr.LowPart)->Byte2;
+ pCdb->CDB10.LogicalBlockByte2 = ((PFOUR_BYTE)&logicalBlockAddr.LowPart)->Byte1;
+ pCdb->CDB10.LogicalBlockByte3 = ((PFOUR_BYTE)&logicalBlockAddr.LowPart)->Byte0;
+ pCdb->CDB10.TransferBlocksMsb = ((PFOUR_BYTE)&numTransferBlocks)->Byte1;
+ pCdb->CDB10.TransferBlocksLsb = ((PFOUR_BYTE)&numTransferBlocks)->Byte0;
+ pCdb->CDB10.OperationCode = (majorFunc==IRP_MJ_READ) ? SCSIOP_READ : SCSIOP_WRITE;
+ }
+ }
+
+ /*
+ * Set SRB and IRP flags
+ */
+ SrbAssignSrbFlags(Pkt->Srb, fdoExt->SrbFlags);
+ if (TEST_FLAG(OriginalIrp->Flags, IRP_PAGING_IO) ||
+ TEST_FLAG(OriginalIrp->Flags, IRP_SYNCHRONOUS_PAGING_IO)){
+ SrbSetSrbFlags(Pkt->Srb, SRB_CLASS_FLAGS_PAGING);
+ }
+ SrbSetSrbFlags(Pkt->Srb, (majorFunc==IRP_MJ_READ) ? SRB_FLAGS_DATA_IN : SRB_FLAGS_DATA_OUT);
+
+ /*
+ * Allow caching only if this is not a write-through request.
+ * If write-through and caching is enabled on the device, force
+ * media access.
+ * Ignore SL_WRITE_THROUGH for reads; it's only set because the file handle was opened with WRITE_THROUGH.
+ */
+ if ((majorFunc == IRP_MJ_WRITE) && TEST_FLAG(origCurSp->Flags, SL_WRITE_THROUGH) && pCdb) {
+ pCdb->CDB10.ForceUnitAccess = fdoExt->CdbForceUnitAccess;
+ } else {
+ SrbSetSrbFlags(Pkt->Srb, SRB_FLAGS_ADAPTER_CACHE_ENABLE);
+ }
+
+ /*
+ * Remember the buf and len in the SRB because miniports
+ * can overwrite SRB.DataTransferLength and we may need it again
+ * for the retry.
+ */
+ Pkt->BufPtrCopy = Buf;
+ Pkt->BufLenCopy = Len;
+ Pkt->TargetLocationCopy = DiskLocation;
+
+ Pkt->OriginalIrp = OriginalIrp;
+ Pkt->NumRetries = fdoData->MaxNumberOfIoRetries;
+ Pkt->SyncEventPtr = NULL;
+ Pkt->CompleteOriginalIrpWhenLastPacketCompletes = TRUE;
+
+
+ if (pCdb) {
+ DBGLOGFLUSHINFO(fdoData, TRUE, (BOOLEAN)(pCdb->CDB10.ForceUnitAccess), FALSE);
+ } else {
+ DBGLOGFLUSHINFO(fdoData, TRUE, FALSE, FALSE);
+ }
+}
+
+
+/*
+ * SubmitTransferPacket
+ *
+ * Set up the IRP for the TRANSFER_PACKET submission and send it down.
+ */
+NTSTATUS SubmitTransferPacket(PTRANSFER_PACKET Pkt)
+{
+ PCOMMON_DEVICE_EXTENSION commonExtension = Pkt->Fdo->DeviceExtension;
+ PDEVICE_OBJECT nextDevObj = commonExtension->LowerDeviceObject;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Pkt->Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData;
+ BOOLEAN idleRequest = FALSE;
+ PIO_STACK_LOCATION nextSp;
+
+ NT_ASSERT(Pkt->Irp->CurrentLocation == Pkt->Irp->StackCount+1);
+
+ /*
+ * Attach the SRB to the IRP.
+ * The reused IRP's stack location has to be rewritten for each retry
+ * call because IoCompleteRequest clears the stack locations.
+ */
+ IoReuseIrp(Pkt->Irp, STATUS_NOT_SUPPORTED);
+
+
+ nextSp = IoGetNextIrpStackLocation(Pkt->Irp);
+ nextSp->MajorFunction = IRP_MJ_SCSI;
+ nextSp->Parameters.Scsi.Srb = (PSCSI_REQUEST_BLOCK)Pkt->Srb;
+
+ SrbSetScsiStatus(Pkt->Srb, 0);
+ Pkt->Srb->SrbStatus = 0;
+ SrbSetSenseInfoBufferLength(Pkt->Srb, SENSE_BUFFER_SIZE_EX);
+
+ if (Pkt->CompleteOriginalIrpWhenLastPacketCompletes) {
+ /*
+ * Only dereference the "original IRP"'s stack location
+ * if its a real client irp (as opposed to a static irp
+ * we're using just for result status for one of the non-IO scsi commands).
+ *
+ * For read/write, propagate the storage-specific IRP stack location flags
+ * (e.g. SL_OVERRIDE_VERIFY_VOLUME, SL_WRITE_THROUGH).
+ */
+ PIO_STACK_LOCATION origCurSp = IoGetCurrentIrpStackLocation(Pkt->OriginalIrp);
+ nextSp->Flags = origCurSp->Flags;
+ }
+
+ //
+ // If the request is not split, we can use the original IRP MDL. If the
+ // request needs to be split, we need to use a partial MDL. The partial MDL
+ // is needed because more than one driver might be mapping the same MDL
+ // and this causes problems.
+ //
+ if (Pkt->UsePartialMdl == FALSE) {
+ Pkt->Irp->MdlAddress = Pkt->OriginalIrp->MdlAddress;
+ } else {
+ IoBuildPartialMdl(Pkt->OriginalIrp->MdlAddress, Pkt->PartialMdl, SrbGetDataBuffer(Pkt->Srb), SrbGetDataTransferLength(Pkt->Srb));
+ Pkt->Irp->MdlAddress = Pkt->PartialMdl;
+ }
+
+
+ DBGLOGSENDPACKET(Pkt);
+ HISTORYLOGSENDPACKET(Pkt);
+
+ //
+ // Set the original irp here for SFIO.
+ //
+ ClasspSrbSetOriginalIrp(Pkt->Srb, (PVOID) (Pkt->OriginalIrp));
+
+ //
+ // No need to lock for IdlePrioritySupported, since it will
+ // be modified only at initialization time.
+ //
+ if (fdoData->IdlePrioritySupported == TRUE) {
+ idleRequest = ClasspIsIdleRequest(Pkt->OriginalIrp);
+ if (idleRequest) {
+ InterlockedIncrement(&fdoData->ActiveIdleIoCount);
+ } else {
+ InterlockedIncrement(&fdoData->ActiveIoCount);
+ }
+ }
+
+ IoSetCompletionRoutine(Pkt->Irp, TransferPktComplete, Pkt, TRUE, TRUE, TRUE);
+ return IoCallDriver(nextDevObj, Pkt->Irp);
+}
+
+
+NTSTATUS TransferPktComplete(IN PDEVICE_OBJECT NullFdo, IN PIRP Irp, IN PVOID Context)
+{
+ PTRANSFER_PACKET pkt = (PTRANSFER_PACKET)Context;
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt = pkt->Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
+ BOOLEAN packetDone = FALSE;
+ BOOLEAN idleRequest = FALSE;
+ ULONG transferLength;
+
+ UNREFERENCED_PARAMETER(NullFdo);
+
+ /*
+ * Put all the assertions and spew in here so we don't have to look at them.
+ */
+ DBGLOGRETURNPACKET(pkt);
+ DBGCHECKRETURNEDPKT(pkt);
+ HISTORYLOGRETURNEDPACKET(pkt);
+
+
+ if (fdoData->IdlePrioritySupported == TRUE) {
+ idleRequest = ClasspIsIdleRequest(pkt->OriginalIrp);
+ if (idleRequest) {
+ InterlockedDecrement(&fdoData->ActiveIdleIoCount);
+ NT_ASSERT(fdoData->ActiveIdleIoCount >= 0);
+ } else {
+ fdoData->LastIoTime = ClasspGetCurrentTime(NULL);
+ fdoData->IdleTicks = 0;
+ InterlockedDecrement(&fdoData->ActiveIoCount);
+ NT_ASSERT(fdoData->ActiveIoCount >= 0);
+ }
+ }
+
+ //
+ // If partial MDL was used, unmap the pages. When the packet is retried, the
+ // MDL will be recreated. If the packet is done, the MDL will be ready to be reused.
+ //
+ if (pkt->UsePartialMdl) {
+ MmPrepareMdlForReuse(pkt->PartialMdl);
+ }
+
+ if (SRB_STATUS(pkt->Srb->SrbStatus) == SRB_STATUS_SUCCESS) {
+
+ NT_ASSERT(NT_SUCCESS(Irp->IoStatus.Status));
+
+ transferLength = SrbGetDataTransferLength(pkt->Srb);
+
+ fdoData->LoggedTURFailureSinceLastIO = FALSE;
+
+ /*
+ * The port driver should not have allocated a sense buffer
+ * if the SRB succeeded.
+ */
+ NT_ASSERT(!PORT_ALLOCATED_SENSE_EX(fdoExt, pkt->Srb));
+
+ /*
+ * Add this packet's transferred length to the original IRP's.
+ */
+ InterlockedExchangeAdd((PLONG)&pkt->OriginalIrp->IoStatus.Information,
+ (LONG)transferLength);
+
+ if ((pkt->InLowMemRetry) ||
+ (pkt->DriverUsesStartIO && pkt->LowMemRetry_remainingBufLen > 0)) {
+ packetDone = StepLowMemRetry(pkt);
+ } else {
+ packetDone = TRUE;
+ }
+
+ }
+ else {
+ /*
+ * The packet failed. We may retry it if possible.
+ */
+ BOOLEAN shouldRetry;
+
+ /*
+ * Make sure IRP status matches SRB error status (since we propagate it).
+ */
+ if (NT_SUCCESS(Irp->IoStatus.Status)){
+ Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
+ }
+
+ /*
+ * The packet failed.
+ * So when sending the packet down we either saw either an error or STATUS_PENDING,
+ * and so we returned STATUS_PENDING for the original IRP.
+ * So now we must mark the original irp pending to match that, _regardless_ of
+ * whether we actually switch threads here by retrying.
+ * (We also have to mark the irp pending if the lower driver marked the irp pending;
+ * that is dealt with farther down).
+ */
+ if (pkt->CompleteOriginalIrpWhenLastPacketCompletes){
+ IoMarkIrpPending(pkt->OriginalIrp);
+ }
+
+ /*
+ * Interpret the SRB error (to a meaningful IRP status)
+ * and determine if we should retry this packet.
+ * This call looks at the returned SENSE info to figure out what to do.
+ */
+ shouldRetry = InterpretTransferPacketError(pkt);
+
+ /*
+ * If the SRB queue is locked-up, release it.
+ * Do this after calling the error handler.
+ */
+ if (pkt->Srb->SrbStatus & SRB_STATUS_QUEUE_FROZEN){
+ ClassReleaseQueue(pkt->Fdo);
+ }
+
+ if (NT_SUCCESS(Irp->IoStatus.Status)){
+ /*
+ * The error was recovered above in the InterpretTransferPacketError() call.
+ */
+
+ NT_ASSERT(!shouldRetry);
+
+ /*
+ * In the case of a recovered error,
+ * add the transfer length to the original Irp as we would in the success case.
+ */
+ InterlockedExchangeAdd((PLONG)&pkt->OriginalIrp->IoStatus.Information,
+ (LONG)SrbGetDataTransferLength(pkt->Srb));
+
+ if ((pkt->InLowMemRetry) ||
+ (pkt->DriverUsesStartIO && pkt->LowMemRetry_remainingBufLen > 0)) {
+ packetDone = StepLowMemRetry(pkt);
+ } else {
+ packetDone = TRUE;
+ }
+ } else {
+ if (shouldRetry && (pkt->NumRetries > 0)){
+ packetDone = RetryTransferPacket(pkt);
+ } else if (shouldRetry && (pkt->RetryHistory != NULL)){
+ // don't limit retries if class driver has custom interpretation routines
+ packetDone = RetryTransferPacket(pkt);
+ } else {
+ packetDone = TRUE;
+ }
+ }
+ }
+
+ /*
+ * If the packet is completed, put it back in the free list.
+ * If it is the last packet servicing the original request, complete the original irp.
+ */
+ if (packetDone){
+ LONG numPacketsRemaining;
+ PIRP deferredIrp;
+ PDEVICE_OBJECT Fdo = pkt->Fdo;
+ UCHAR uniqueAddr = 0;
+
+ /*
+ * In case a remove is pending, bump the lock count so we don't get freed
+ * right after we complete the original irp.
+ */
+ ClassAcquireRemoveLock(Fdo, (PVOID)&uniqueAddr);
+
+
+ /*
+ * Sometimes the port driver can allocates a new 'sense' buffer
+ * to report transfer errors, e.g. when the default sense buffer
+ * is too small. If so, it is up to us to free it.
+ * Now that we're done using the sense info, free it if appropriate.
+ * Then clear the sense buffer so it doesn't pollute future errors returned in this packet.
+ */
+ if (PORT_ALLOCATED_SENSE_EX(fdoExt, pkt->Srb)) {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_RW, "Freeing port-allocated sense buffer for pkt %ph.", pkt));
+ FREE_PORT_ALLOCATED_SENSE_BUFFER_EX(fdoExt, pkt->Srb);
+ SrbSetSenseInfoBuffer(pkt->Srb, &pkt->SrbErrorSenseData);
+ SrbSetSenseInfoBufferLength(pkt->Srb, sizeof(pkt->SrbErrorSenseData));
+ } else {
+ NT_ASSERT(SrbGetSenseInfoBuffer(pkt->Srb) == &pkt->SrbErrorSenseData);
+ NT_ASSERT(SrbGetSenseInfoBufferLength(pkt->Srb) <= sizeof(pkt->SrbErrorSenseData));
+ }
+
+ RtlZeroMemory(&pkt->SrbErrorSenseData, sizeof(pkt->SrbErrorSenseData));
+
+ /*
+ * Call IoSetMasterIrpStatus to set appropriate status
+ * for the Master IRP.
+ */
+ IoSetMasterIrpStatus(pkt->OriginalIrp, Irp->IoStatus.Status);
+
+ if (!NT_SUCCESS(Irp->IoStatus.Status)){
+ /*
+ * If the original I/O originated in user space (i.e. it is thread-queued),
+ * and the error is user-correctable (e.g. media is missing, for removable media),
+ * alert the user.
+ * Since this is only one of possibly several packets completing for the original IRP,
+ * we may do this more than once for a single request. That's ok; this allows
+ * us to test each returned status with IoIsErrorUserInduced().
+ */
+ if (IoIsErrorUserInduced(Irp->IoStatus.Status) &&
+ pkt->CompleteOriginalIrpWhenLastPacketCompletes &&
+ pkt->OriginalIrp->Tail.Overlay.Thread){
+
+ IoSetHardErrorOrVerifyDevice(pkt->OriginalIrp, Fdo);
+ }
+ }
+
+ /*
+ * We use a field in the original IRP to count
+ * down the transfer pieces as they complete.
+ */
+ numPacketsRemaining = InterlockedDecrement(
+ (PLONG)&pkt->OriginalIrp->Tail.Overlay.DriverContext[0]);
+
+ if (numPacketsRemaining > 0){
+ /*
+ * More transfer pieces remain for the original request.
+ * Wait for them to complete before completing the original irp.
+ */
+ } else {
+
+ /*
+ * All the transfer pieces are done.
+ * Complete the original irp if appropriate.
+ */
+ NT_ASSERT(numPacketsRemaining == 0);
+ if (pkt->CompleteOriginalIrpWhenLastPacketCompletes){
+
+ IO_PAGING_PRIORITY priority = (TEST_FLAG(pkt->OriginalIrp->Flags, IRP_PAGING_IO)) ? IoGetPagingIoPriority(pkt->OriginalIrp) : IoPagingPriorityInvalid;
+ KIRQL oldIrql;
+
+ if (NT_SUCCESS(pkt->OriginalIrp->IoStatus.Status)){
+ NT_ASSERT((ULONG)pkt->OriginalIrp->IoStatus.Information == IoGetCurrentIrpStackLocation(pkt->OriginalIrp)->Parameters.Read.Length);
+ ClasspPerfIncrementSuccessfulIo(fdoExt);
+ }
+ ClassReleaseRemoveLock(Fdo, pkt->OriginalIrp);
+
+ /*
+ * We submitted all the downward irps, including this last one, on the thread
+ * that the OriginalIrp came in on. So the OriginalIrp is completing on a
+ * different thread iff this last downward irp is completing on a different thread.
+ * If BlkCache is loaded, for example, it will often complete
+ * requests out of the cache on the same thread, therefore not marking the downward
+ * irp pending and not requiring us to do so here. If the downward request is completing
+ * on the same thread, then by not marking the OriginalIrp pending we can save an APC
+ * and get extra perf benefit out of BlkCache.
+ * Note that if the packet ever cycled due to retry or LowMemRetry,
+ * we set the pending bit in those codepaths.
+ */
+ if (pkt->Irp->PendingReturned){
+ IoMarkIrpPending(pkt->OriginalIrp);
+ }
+
+
+ ClassCompleteRequest(Fdo, pkt->OriginalIrp, IO_DISK_INCREMENT);
+
+ //
+ // Drop the count only after completing the request, to give
+ // Mm some amount of time to issue its next critical request
+ //
+
+ if (priority == IoPagingPriorityHigh)
+ {
+ KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
+
+ if (fdoData->MaxInterleavedNormalIo < ClassMaxInterleavePerCriticalIo)
+ {
+ fdoData->MaxInterleavedNormalIo = 0;
+ } else {
+ fdoData->MaxInterleavedNormalIo -= ClassMaxInterleavePerCriticalIo;
+ }
+
+ fdoData->NumHighPriorityPagingIo--;
+
+ if (fdoData->NumHighPriorityPagingIo == 0)
+ {
+ LARGE_INTEGER period;
+
+ //
+ // Exiting throttle mode
+ //
+
+ KeQuerySystemTime(&fdoData->ThrottleStopTime);
+
+ period.QuadPart = fdoData->ThrottleStopTime.QuadPart - fdoData->ThrottleStartTime.QuadPart;
+ fdoData->LongestThrottlePeriod.QuadPart = max(fdoData->LongestThrottlePeriod.QuadPart, period.QuadPart);
+ }
+
+ KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
+ }
+
+ if (idleRequest) {
+ ClasspCompleteIdleRequest(fdoExt);
+ }
+
+ /*
+ * We may have been called by one of the class drivers (e.g. cdrom)
+ * via the legacy API ClassSplitRequest.
+ * This is the only case for which the packet engine is called for an FDO
+ * with a StartIo routine; in that case, we have to call IoStartNextPacket
+ * now that the original irp has been completed.
+ */
+ if (fdoExt->CommonExtension.DriverExtension->InitData.ClassStartIo) {
+ if (TEST_FLAG(SrbGetSrbFlags(pkt->Srb), SRB_FLAGS_DONT_START_NEXT_PACKET)){
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_RW, "SRB_FLAGS_DONT_START_NEXT_PACKET should never be set here (??)"));
+ } else {
+ KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
+ IoStartNextPacket(Fdo, TRUE); // yes, some IO is now cancellable
+ KeLowerIrql(oldIrql);
+ }
+ }
+ }
+ }
+
+ /*
+ * If the packet was synchronous, write the final result back to the issuer's status buffer
+ * and signal his event.
+ */
+ if (pkt->SyncEventPtr){
+ KeSetEvent(pkt->SyncEventPtr, 0, FALSE);
+ pkt->SyncEventPtr = NULL;
+ }
+
+ /*
+ * If the operation isn't a normal read/write, but needs to do more
+ * operation-specific processing, call the operation's continuation
+ * routine. The operation may create and queue another transfer packet
+ * within this routine, but pkt is still freed after returning from the
+ * continuation routine.
+ */
+ if (pkt->ContinuationRoutine != NULL){
+ pkt->ContinuationRoutine(pkt->ContinuationContext);
+ pkt->ContinuationRoutine = NULL;
+ }
+
+ /*
+ * Free the completed packet.
+ */
+ pkt->UsePartialMdl = FALSE;
+// pkt->OriginalIrp = NULL;
+ pkt->InLowMemRetry = FALSE;
+ EnqueueFreeTransferPacket(Fdo, pkt);
+
+ /*
+ * Now that we have freed some resources,
+ * try again to send one of the previously deferred irps.
+ */
+ deferredIrp = DequeueDeferredClientIrp(Fdo);
+ if (deferredIrp){
+ ServiceTransferRequest(Fdo, deferredIrp, TRUE);
+ }
+
+ ClassReleaseRemoveLock(Fdo, (PVOID)&uniqueAddr);
+ }
+
+ return STATUS_MORE_PROCESSING_REQUIRED;
+}
+
+
+/*
+ * SetupEjectionTransferPacket
+ *
+ * Set up a transferPacket for a synchronous Ejection Control transfer.
+ */
+VOID SetupEjectionTransferPacket( TRANSFER_PACKET *Pkt,
+ BOOLEAN PreventMediaRemoval,
+ PKEVENT SyncEventPtr,
+ PIRP OriginalIrp)
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Pkt->Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
+ PCDB pCdb;
+ ULONG srbLength;
+
+ PAGED_CODE();
+
+ if (fdoExt->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+ srbLength = ((PSTORAGE_REQUEST_BLOCK) fdoData->SrbTemplate)->SrbLength;
+ NT_ASSERT(((PSTORAGE_REQUEST_BLOCK) Pkt->Srb)->SrbLength >= srbLength);
+ } else {
+ srbLength = fdoData->SrbTemplate->Length;
+ }
+ RtlCopyMemory(Pkt->Srb, fdoData->SrbTemplate, srbLength); // copies _contents_ of SRB blocks
+
+ SrbSetRequestAttribute(Pkt->Srb, SRB_SIMPLE_TAG_REQUEST);
+ SrbSetCdbLength(Pkt->Srb, 6);
+ SrbSetOriginalRequest(Pkt->Srb, Pkt->Irp);
+ SrbSetSenseInfoBuffer(Pkt->Srb, &Pkt->SrbErrorSenseData);
+ SrbSetSenseInfoBufferLength(Pkt->Srb, sizeof(Pkt->SrbErrorSenseData));
+ SrbSetTimeOutValue(Pkt->Srb, fdoExt->TimeOutValue);
+
+ SrbAssignSrbFlags(Pkt->Srb, fdoExt->SrbFlags | SRB_FLAGS_DISABLE_SYNCH_TRANSFER | SRB_FLAGS_NO_QUEUE_FREEZE);
+
+ pCdb = SrbGetCdb(Pkt->Srb);
+ if (pCdb) {
+ pCdb->MEDIA_REMOVAL.OperationCode = SCSIOP_MEDIUM_REMOVAL;
+ pCdb->MEDIA_REMOVAL.Prevent = PreventMediaRemoval;
+ }
+
+ Pkt->BufPtrCopy = NULL;
+ Pkt->BufLenCopy = 0;
+
+ Pkt->OriginalIrp = OriginalIrp;
+ Pkt->NumRetries = NUM_LOCKMEDIAREMOVAL_RETRIES;
+ Pkt->SyncEventPtr = SyncEventPtr;
+ Pkt->CompleteOriginalIrpWhenLastPacketCompletes = FALSE;
+}
+
+
+/*
+ * SetupModeSenseTransferPacket
+ *
+ * Set up a transferPacket for a synchronous Mode Sense transfer.
+ */
+VOID SetupModeSenseTransferPacket(TRANSFER_PACKET *Pkt,
+ PKEVENT SyncEventPtr,
+ PVOID ModeSenseBuffer,
+ UCHAR ModeSenseBufferLen,
+ UCHAR PageMode,
+ UCHAR SubPage,
+ PIRP OriginalIrp,
+ UCHAR PageControl)
+
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Pkt->Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
+ PCDB pCdb;
+ ULONG srbLength;
+
+ PAGED_CODE();
+
+ if (fdoExt->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+ srbLength = ((PSTORAGE_REQUEST_BLOCK) fdoData->SrbTemplate)->SrbLength;
+ NT_ASSERT(((PSTORAGE_REQUEST_BLOCK) Pkt->Srb)->SrbLength >= srbLength);
+ } else {
+ srbLength = fdoData->SrbTemplate->Length;
+ }
+ RtlCopyMemory(Pkt->Srb, fdoData->SrbTemplate, srbLength); // copies _contents_ of SRB blocks
+
+ SrbSetRequestAttribute(Pkt->Srb, SRB_SIMPLE_TAG_REQUEST);
+ SrbSetCdbLength(Pkt->Srb, 6);
+ SrbSetOriginalRequest(Pkt->Srb, Pkt->Irp);
+ SrbSetSenseInfoBuffer(Pkt->Srb, &Pkt->SrbErrorSenseData);
+ SrbSetSenseInfoBufferLength(Pkt->Srb, sizeof(Pkt->SrbErrorSenseData));
+ SrbSetTimeOutValue(Pkt->Srb, fdoExt->TimeOutValue);
+ SrbSetDataBuffer(Pkt->Srb, ModeSenseBuffer);
+ SrbSetDataTransferLength(Pkt->Srb, ModeSenseBufferLen);
+
+
+ SrbAssignSrbFlags(Pkt->Srb, fdoExt->SrbFlags | SRB_FLAGS_DATA_IN | SRB_FLAGS_DISABLE_SYNCH_TRANSFER | SRB_FLAGS_NO_QUEUE_FREEZE);
+
+ pCdb = SrbGetCdb(Pkt->Srb);
+ if (pCdb) {
+ pCdb->MODE_SENSE.OperationCode = SCSIOP_MODE_SENSE;
+ pCdb->MODE_SENSE.PageCode = PageMode;
+ pCdb->MODE_SENSE.SubPageCode = SubPage;
+ pCdb->MODE_SENSE.Pc = PageControl;
+ pCdb->MODE_SENSE.AllocationLength = (UCHAR)ModeSenseBufferLen;
+ }
+
+ Pkt->BufPtrCopy = ModeSenseBuffer;
+ Pkt->BufLenCopy = ModeSenseBufferLen;
+
+ Pkt->OriginalIrp = OriginalIrp;
+ Pkt->NumRetries = NUM_MODESENSE_RETRIES;
+ Pkt->SyncEventPtr = SyncEventPtr;
+ Pkt->CompleteOriginalIrpWhenLastPacketCompletes = FALSE;
+}
+
+/*
+ * SetupModeSelectTransferPacket
+ *
+ * Set up a transferPacket for a synchronous Mode Select transfer.
+ */
+VOID SetupModeSelectTransferPacket(TRANSFER_PACKET *Pkt,
+ PKEVENT SyncEventPtr,
+ PVOID ModeSelectBuffer,
+ UCHAR ModeSelectBufferLen,
+ BOOLEAN SavePages,
+ PIRP OriginalIrp)
+
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Pkt->Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
+ PCDB pCdb;
+ ULONG srbLength;
+
+ if (fdoExt->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+ srbLength = ((PSTORAGE_REQUEST_BLOCK) fdoData->SrbTemplate)->SrbLength;
+ NT_ASSERT(((PSTORAGE_REQUEST_BLOCK) Pkt->Srb)->SrbLength >= srbLength);
+ } else {
+ srbLength = fdoData->SrbTemplate->Length;
+ }
+ RtlCopyMemory(Pkt->Srb, fdoData->SrbTemplate, srbLength); // copies _contents_ of SRB blocks
+
+ SrbSetRequestAttribute(Pkt->Srb, SRB_SIMPLE_TAG_REQUEST);
+ SrbSetCdbLength(Pkt->Srb, 6);
+ SrbSetOriginalRequest(Pkt->Srb, Pkt->Irp);
+ SrbSetSenseInfoBuffer(Pkt->Srb, &Pkt->SrbErrorSenseData);
+ SrbSetSenseInfoBufferLength(Pkt->Srb, sizeof(Pkt->SrbErrorSenseData));
+ SrbSetTimeOutValue(Pkt->Srb, fdoExt->TimeOutValue);
+ SrbSetDataBuffer(Pkt->Srb, ModeSelectBuffer);
+ SrbSetDataTransferLength(Pkt->Srb, ModeSelectBufferLen);
+
+ SrbAssignSrbFlags(Pkt->Srb, fdoExt->SrbFlags | SRB_FLAGS_DATA_OUT | SRB_FLAGS_DISABLE_SYNCH_TRANSFER | SRB_FLAGS_NO_QUEUE_FREEZE);
+
+ pCdb = SrbGetCdb(Pkt->Srb);
+ if (pCdb) {
+ pCdb->MODE_SELECT.OperationCode = SCSIOP_MODE_SELECT;
+ pCdb->MODE_SELECT.SPBit = SavePages;
+ pCdb->MODE_SELECT.PFBit = 1;
+ pCdb->MODE_SELECT.ParameterListLength = (UCHAR)ModeSelectBufferLen;
+ }
+
+ Pkt->BufPtrCopy = ModeSelectBuffer;
+ Pkt->BufLenCopy = ModeSelectBufferLen;
+
+ Pkt->OriginalIrp = OriginalIrp;
+ Pkt->NumRetries = NUM_MODESELECT_RETRIES;
+ Pkt->SyncEventPtr = SyncEventPtr;
+ Pkt->CompleteOriginalIrpWhenLastPacketCompletes = FALSE;
+}
+
+
+/*
+ * SetupDriveCapacityTransferPacket
+ *
+ * Set up a transferPacket for a synchronous Drive Capacity transfer.
+ */
+VOID SetupDriveCapacityTransferPacket( TRANSFER_PACKET *Pkt,
+ PVOID ReadCapacityBuffer,
+ ULONG ReadCapacityBufferLen,
+ PKEVENT SyncEventPtr,
+ PIRP OriginalIrp,
+ BOOLEAN Use16ByteCdb)
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Pkt->Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
+ PCDB pCdb;
+ ULONG srbLength;
+
+ if (fdoExt->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+ srbLength = ((PSTORAGE_REQUEST_BLOCK) fdoData->SrbTemplate)->SrbLength;
+ NT_ASSERT(((PSTORAGE_REQUEST_BLOCK) Pkt->Srb)->SrbLength >= srbLength);
+ } else {
+ srbLength = fdoData->SrbTemplate->Length;
+ }
+ RtlCopyMemory(Pkt->Srb, fdoData->SrbTemplate, srbLength); // copies _contents_ of SRB blocks
+
+ SrbSetRequestAttribute(Pkt->Srb, SRB_SIMPLE_TAG_REQUEST);
+ SrbSetOriginalRequest(Pkt->Srb, Pkt->Irp);
+ SrbSetSenseInfoBuffer(Pkt->Srb, &Pkt->SrbErrorSenseData);
+ SrbSetSenseInfoBufferLength(Pkt->Srb, sizeof(Pkt->SrbErrorSenseData));
+ SrbSetTimeOutValue(Pkt->Srb, fdoExt->TimeOutValue);
+ SrbSetDataBuffer(Pkt->Srb, ReadCapacityBuffer);
+ SrbSetDataTransferLength(Pkt->Srb, ReadCapacityBufferLen);
+
+ SrbAssignSrbFlags(Pkt->Srb, fdoExt->SrbFlags | SRB_FLAGS_DATA_IN | SRB_FLAGS_DISABLE_SYNCH_TRANSFER | SRB_FLAGS_NO_QUEUE_FREEZE);
+
+ pCdb = SrbGetCdb(Pkt->Srb);
+ if (pCdb) {
+ if (Use16ByteCdb == TRUE) {
+ NT_ASSERT(ReadCapacityBufferLen >= sizeof(READ_CAPACITY_DATA_EX));
+ SrbSetCdbLength(Pkt->Srb, 16);
+ pCdb->CDB16.OperationCode = SCSIOP_READ_CAPACITY16;
+ REVERSE_BYTES(&pCdb->CDB16.TransferLength, &ReadCapacityBufferLen);
+ pCdb->AsByte[1] = 0x10; // Service Action
+ } else {
+ SrbSetCdbLength(Pkt->Srb, 10);
+ pCdb->CDB10.OperationCode = SCSIOP_READ_CAPACITY;
+ }
+ }
+
+ Pkt->BufPtrCopy = ReadCapacityBuffer;
+ Pkt->BufLenCopy = ReadCapacityBufferLen;
+
+ Pkt->OriginalIrp = OriginalIrp;
+ Pkt->NumRetries = NUM_DRIVECAPACITY_RETRIES;
+ Pkt->SyncEventPtr = SyncEventPtr;
+ Pkt->CompleteOriginalIrpWhenLastPacketCompletes = FALSE;
+}
+
+
+#if 0
+ /*
+ * SetupSendStartUnitTransferPacket
+ *
+ * Set up a transferPacket for a synchronous Send Start Unit transfer.
+ */
+ VOID SetupSendStartUnitTransferPacket( TRANSFER_PACKET *Pkt,
+ PIRP OriginalIrp)
+ {
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Pkt->Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
+ PCDB pCdb;
+
+ PAGED_CODE();
+
+ RtlZeroMemory(&Pkt->Srb, sizeof(SCSI_REQUEST_BLOCK));
+
+ /*
+ * Initialize the SRB.
+ * Use a very long timeout value to give the drive time to spin up.
+ */
+ Pkt->Srb->Length = sizeof(SCSI_REQUEST_BLOCK);
+ Pkt->Srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
+ Pkt->Srb->TimeOutValue = START_UNIT_TIMEOUT;
+ Pkt->Srb->CdbLength = 6;
+ Pkt->Srb->OriginalRequest = Pkt->Irp;
+ Pkt->Srb->SenseInfoBuffer = &Pkt->SrbErrorSenseData;
+ Pkt->Srb->SenseInfoBufferLength = sizeof(Pkt->SrbErrorSenseData);
+ Pkt->Srb->Lun = 0;
+
+ SET_FLAG(Pkt->Srb->SrbFlags, SRB_FLAGS_NO_DATA_TRANSFER);
+ SET_FLAG(Pkt->Srb->SrbFlags, SRB_FLAGS_DISABLE_AUTOSENSE);
+ SET_FLAG(Pkt->Srb->SrbFlags, SRB_FLAGS_DISABLE_SYNCH_TRANSFER);
+
+ pCdb = (PCDB)Pkt->Srb->Cdb;
+ pCdb->START_STOP.OperationCode = SCSIOP_START_STOP_UNIT;
+ pCdb->START_STOP.Start = 1;
+ pCdb->START_STOP.Immediate = 0;
+ pCdb->START_STOP.LogicalUnitNumber = 0;
+
+ Pkt->OriginalIrp = OriginalIrp;
+ Pkt->NumRetries = 0;
+ Pkt->SyncEventPtr = NULL;
+ Pkt->CompleteOriginalIrpWhenLastPacketCompletes = FALSE;
+ }
+#endif
+
+
+VOID
+CleanupTransferPacketToWorkingSetSizeWorker(
+ _In_ PVOID Fdo,
+ _In_opt_ PVOID Context,
+ _In_ PIO_WORKITEM IoWorkItem
+ )
+{
+ ULONG node = (ULONG) Context;
+
+ PAGED_CODE();
+
+ CleanupTransferPacketToWorkingSetSize((PDEVICE_OBJECT)Fdo, FALSE, node);
+
+ //
+ // Release the remove lock acquired in EnqueueFreeTransferPacket
+ //
+ ClassReleaseRemoveLock((PDEVICE_OBJECT)Fdo, (PIRP)IoWorkItem);
+
+ if (IoWorkItem != NULL) {
+ IoFreeWorkItem(IoWorkItem);
+ }
+}
+
+
+VOID
+CleanupTransferPacketToWorkingSetSize(
+ _In_ PDEVICE_OBJECT Fdo,
+ _In_ BOOLEAN LimitNumPktToDelete,
+ _In_ ULONG Node
+ )
+
+/*
+Routine Description:
+
+ This function frees the resources for the free transfer packets attempting
+ to bring them down within the working set size.
+
+Arguments:
+ Fdo: The FDO that represents the device whose transfer packet size needs to be trimmed.
+ LimitNumPktToDelete: Flag to indicate if the number of packets freed in one call should be capped.
+ Node: NUMA node transfer packet is associated with.
+
+--*/
+
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Fdo->DeviceExtension;
+ PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData;
+ KIRQL oldIrql;
+ SINGLE_LIST_ENTRY pktList;
+ PSINGLE_LIST_ENTRY slistEntry;
+ PTRANSFER_PACKET pktToDelete;
+ ULONG requiredNumPktToDelete = fdoData->FreeTransferPacketsLists[Node].NumTotalTransferPackets -
+ fdoData->LocalMaxWorkingSetTransferPackets;
+
+ if (LimitNumPktToDelete) {
+ requiredNumPktToDelete = MIN(requiredNumPktToDelete, MAX_CLEANUP_TRANSFER_PACKETS_AT_ONCE);
+ }
+
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_RW, "CleanupTransferPacketToWorkingSetSize (%p): Exiting stress, block freeing %d packets.", Fdo, requiredNumPktToDelete));
+
+ /*
+ * Check the counter again with lock held. This eliminates a race condition
+ * while still allowing us to not grab the spinlock in the common codepath.
+ *
+ * Note that the spinlock does not synchronize with threads dequeuing free
+ * packets to send (DequeueFreeTransferPacket does that with a lightweight
+ * interlocked exchange); the spinlock prevents multiple threads in this function
+ * from deciding to free too many extra packets at once.
+ */
+ SimpleInitSlistHdr(&pktList);
+ KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
+ while ((fdoData->FreeTransferPacketsLists[Node].NumFreeTransferPackets >= fdoData->FreeTransferPacketsLists[Node].NumTotalTransferPackets) &&
+ (fdoData->FreeTransferPacketsLists[Node].NumTotalTransferPackets > fdoData->LocalMaxWorkingSetTransferPackets) &&
+ (requiredNumPktToDelete--)){
+
+ pktToDelete = DequeueFreeTransferPacketEx(Fdo, FALSE, Node);
+ if (pktToDelete){
+ SimplePushSlist(&pktList,
+ (PSINGLE_LIST_ENTRY)&pktToDelete->SlistEntry);
+ InterlockedDecrement((volatile LONG *)&fdoData->FreeTransferPacketsLists[Node].NumTotalTransferPackets);
+ } else {
+ TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_RW,
+ "Extremely unlikely condition (non-fatal): %d packets dequeued at once for Fdo %p. NumTotalTransferPackets=%d (1). Node=%d",
+ fdoData->LocalMaxWorkingSetTransferPackets,
+ Fdo,
+ fdoData->FreeTransferPacketsLists[Node].NumTotalTransferPackets,
+ Node));
+ break;
+ }
+ }
+ KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
+
+ slistEntry = SimplePopSlist(&pktList);
+ while (slistEntry) {
+ pktToDelete = CONTAINING_RECORD(slistEntry, TRANSFER_PACKET, SlistEntry);
+ DestroyTransferPacket(pktToDelete);
+ slistEntry = SimplePopSlist(&pktList);
+ }
+
+ return;
+}
+
+
+_IRQL_requires_max_(APC_LEVEL)
+_IRQL_requires_min_(PASSIVE_LEVEL)
+_IRQL_requires_same_
+VOID
+ClasspSetupPopulateTokenTransferPacket(
+ _In_ __drv_aliasesMem POFFLOAD_READ_CONTEXT OffloadReadContext,
+ _In_ PTRANSFER_PACKET Pkt,
+ _In_ ULONG Length,
+ _In_reads_bytes_(Length) PUCHAR PopulateTokenBuffer,
+ _In_ PIRP OriginalIrp,
+ _In_ ULONG ListIdentifier
+ )
+
+/*++
+
+Routine description:
+
+ This routine is called once to set up a packet for PopulateToken.
+ It builds up the SRB by setting the appropriate fields.
+
+Arguments:
+
+ Pkt - The transfer packet to be sent down to the lower driver
+ SyncEventPtr - The event that gets signaled once the IRP contained in the packet completes
+ Length - Length of the buffer being sent as part of the command
+ PopulateTokenBuffer - The buffer that contains the LBA ranges information for the PopulateToken operation
+ OriginalIrp - The Io request to be processed
+ ListIdentifier - The identifier that will be used to correlate a subsequent command to retrieve the token
+
+Return Value:
+
+ Nothing
+
+--*/
+
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt;
+ PCLASS_PRIVATE_FDO_DATA fdoData;
+ PCDB pCdb;
+ ULONG srbLength;
+
+ PAGED_CODE();
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspSetupPopulateTokenTransferPacket (%p): Entering function. Irp %p\n",
+ Pkt->Fdo,
+ OriginalIrp));
+
+ fdoExt = Pkt->Fdo->DeviceExtension;
+ fdoData = fdoExt->PrivateFdoData;
+
+ if (fdoExt->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+ srbLength = ((PSTORAGE_REQUEST_BLOCK) fdoData->SrbTemplate)->SrbLength;
+ NT_ASSERT(((PSTORAGE_REQUEST_BLOCK) Pkt->Srb)->SrbLength >= srbLength);
+ } else {
+ srbLength = fdoData->SrbTemplate->Length;
+ }
+ RtlCopyMemory(Pkt->Srb, fdoData->SrbTemplate, srbLength); // copies _contents_ of SRB blocks
+
+ SrbSetRequestAttribute(Pkt->Srb, SRB_SIMPLE_TAG_REQUEST);
+ SrbSetCdbLength(Pkt->Srb, 16);
+ SrbSetOriginalRequest(Pkt->Srb, Pkt->Irp);
+ SrbSetSenseInfoBuffer(Pkt->Srb, &Pkt->SrbErrorSenseData);
+ SrbSetSenseInfoBufferLength(Pkt->Srb, sizeof(Pkt->SrbErrorSenseData));
+ SrbSetTimeOutValue(Pkt->Srb, fdoExt->TimeOutValue);
+ SrbSetDataBuffer(Pkt->Srb, PopulateTokenBuffer);
+ SrbSetDataTransferLength(Pkt->Srb, Length);
+
+ SrbAssignSrbFlags(Pkt->Srb, fdoExt->SrbFlags | SRB_FLAGS_DATA_OUT | SRB_FLAGS_DISABLE_SYNCH_TRANSFER | SRB_FLAGS_NO_QUEUE_FREEZE);
+
+ pCdb = SrbGetCdb(Pkt->Srb);
+ if (pCdb) {
+ pCdb->TOKEN_OPERATION.OperationCode = SCSIOP_POPULATE_TOKEN;
+ pCdb->TOKEN_OPERATION.ServiceAction = SERVICE_ACTION_POPULATE_TOKEN;
+
+ REVERSE_BYTES(&pCdb->TOKEN_OPERATION.ListIdentifier, &ListIdentifier);
+ REVERSE_BYTES(&pCdb->TOKEN_OPERATION.ParameterListLength, &Length);
+ }
+
+ Pkt->BufPtrCopy = PopulateTokenBuffer;
+ Pkt->BufLenCopy = Length;
+
+ Pkt->OriginalIrp = OriginalIrp;
+ Pkt->NumRetries = NUM_POPULATE_TOKEN_RETRIES;
+ Pkt->CompleteOriginalIrpWhenLastPacketCompletes = FALSE;
+
+ Pkt->ContinuationRoutine = ClasspPopulateTokenTransferPacketDone;
+ Pkt->ContinuationContext = OffloadReadContext;
+
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspSetupPopulateTokenTransferPacket (%p): Exiting function with Irp %p\n",
+ Pkt->Fdo,
+ OriginalIrp));
+
+ return;
+}
+
+
+_IRQL_requires_max_(APC_LEVEL)
+_IRQL_requires_min_(PASSIVE_LEVEL)
+_IRQL_requires_same_
+VOID
+ClasspSetupReceivePopulateTokenInformationTransferPacket(
+ _In_ POFFLOAD_READ_CONTEXT OffloadReadContext,
+ _In_ PTRANSFER_PACKET Pkt,
+ _In_ ULONG Length,
+ _In_reads_bytes_(Length) PUCHAR ReceivePopulateTokenInformationBuffer,
+ _In_ PIRP OriginalIrp,
+ _In_ ULONG ListIdentifier
+ )
+
+/*++
+
+Routine description:
+
+ This routine is called once to set up a packet for read token retrieval.
+ It builds up the SRB by setting the appropriate fields.
+
+Arguments:
+
+ Pkt - The transfer packet to be sent down to the lower driver
+ Length - Length of the buffer being sent as part of the command
+ ReceivePopulateTokenInformationBuffer - The buffer into which the target will pass back the token
+ OriginalIrp - The Io request to be processed
+ ListIdentifier - The identifier that will be used to correlate this command with its corresponding previous populate token operation
+
+Return Value:
+
+ Nothing
+
+--*/
+
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt;
+ PCLASS_PRIVATE_FDO_DATA fdoData;
+ PCDB pCdb;
+ ULONG srbLength;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspSetupReceivePopulateTokenInformationTransferPacket (%p): Entering function. Irp %p\n",
+ Pkt->Fdo,
+ OriginalIrp));
+
+ fdoExt = Pkt->Fdo->DeviceExtension;
+ fdoData = fdoExt->PrivateFdoData;
+
+ if (fdoExt->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+ srbLength = ((PSTORAGE_REQUEST_BLOCK) fdoData->SrbTemplate)->SrbLength;
+ NT_ASSERT(((PSTORAGE_REQUEST_BLOCK) Pkt->Srb)->SrbLength >= srbLength);
+ } else {
+ srbLength = fdoData->SrbTemplate->Length;
+ }
+ RtlCopyMemory(Pkt->Srb, fdoData->SrbTemplate, srbLength); // copies _contents_ of SRB blocks
+
+ SrbSetRequestAttribute(Pkt->Srb, SRB_SIMPLE_TAG_REQUEST);
+ SrbSetCdbLength(Pkt->Srb, 16);
+ SrbSetOriginalRequest(Pkt->Srb, Pkt->Irp);
+ SrbSetSenseInfoBuffer(Pkt->Srb, &Pkt->SrbErrorSenseData);
+ SrbSetSenseInfoBufferLength(Pkt->Srb, sizeof(Pkt->SrbErrorSenseData));
+ SrbSetTimeOutValue(Pkt->Srb, fdoExt->TimeOutValue);
+ SrbSetDataBuffer(Pkt->Srb, ReceivePopulateTokenInformationBuffer);
+ SrbSetDataTransferLength(Pkt->Srb, Length);
+
+ SrbAssignSrbFlags(Pkt->Srb, fdoExt->SrbFlags | SRB_FLAGS_DATA_IN | SRB_FLAGS_DISABLE_SYNCH_TRANSFER | SRB_FLAGS_NO_QUEUE_FREEZE);
+
+ pCdb = SrbGetCdb(Pkt->Srb);
+ if (pCdb) {
+ pCdb->RECEIVE_TOKEN_INFORMATION.OperationCode = SCSIOP_RECEIVE_ROD_TOKEN_INFORMATION;
+ pCdb->RECEIVE_TOKEN_INFORMATION.ServiceAction = SERVICE_ACTION_RECEIVE_TOKEN_INFORMATION;
+
+ REVERSE_BYTES(&pCdb->RECEIVE_TOKEN_INFORMATION.ListIdentifier, &ListIdentifier);
+ REVERSE_BYTES(&pCdb->RECEIVE_TOKEN_INFORMATION.AllocationLength, &Length);
+ }
+
+ Pkt->BufPtrCopy = ReceivePopulateTokenInformationBuffer;
+ Pkt->BufLenCopy = Length;
+
+ Pkt->OriginalIrp = OriginalIrp;
+ Pkt->NumRetries = NUM_POPULATE_TOKEN_RETRIES;
+ Pkt->CompleteOriginalIrpWhenLastPacketCompletes = FALSE;
+
+ Pkt->ContinuationRoutine = ClasspReceivePopulateTokenInformationTransferPacketDone;
+ Pkt->ContinuationContext = OffloadReadContext;
+
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspSetupReceivePopulateTokenInformationTransferPacket (%p): Exiting function with Irp %p\n",
+ Pkt->Fdo,
+ OriginalIrp));
+
+ return;
+}
+
+
+_IRQL_requires_max_(APC_LEVEL)
+_IRQL_requires_min_(PASSIVE_LEVEL)
+_IRQL_requires_same_
+VOID
+ClasspSetupWriteUsingTokenTransferPacket(
+ _In_ __drv_aliasesMem POFFLOAD_WRITE_CONTEXT OffloadWriteContext,
+ _In_ PTRANSFER_PACKET Pkt,
+ _In_ ULONG Length,
+ _In_reads_bytes_ (Length) PUCHAR WriteUsingTokenBuffer,
+ _In_ PIRP OriginalIrp,
+ _In_ ULONG ListIdentifier
+ )
+
+/*++
+
+Routine description:
+
+ This routine is called once to set up a packet for WriteUsingToken.
+ It builds up the SRB by setting the appropriate fields. It is not called
+ before a retry as the SRB fields may be modified for the retry.
+
+ The IRP is set up in SubmitTransferPacket because it must be reset for
+ each packet submission.
+
+Arguments:
+
+ Pkt - The transfer packet to be sent down to the lower driver
+ Length - Length of the buffer being sent as part of the command
+ WriteUsingTokenBuffer - The buffer that contains the read token and the write LBA ranges information for the WriteUsingToken operation
+ OriginalIrp - The Io request to be processed
+ ListIdentifier - The identifier that will be used to correlate a subsequent command to retrieve extended results in case of command failure
+
+Return Value:
+
+ Nothing
+
+--*/
+
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt;
+ PCLASS_PRIVATE_FDO_DATA fdoData;
+ PCDB pCdb;
+ ULONG srbLength;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspSetupWriteUsingTokenTransferPacket (%p): Entering function. Irp %p\n",
+ Pkt->Fdo,
+ OriginalIrp));
+
+ fdoExt = Pkt->Fdo->DeviceExtension;
+ fdoData = fdoExt->PrivateFdoData;
+
+ if (fdoExt->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+ srbLength = ((PSTORAGE_REQUEST_BLOCK) fdoData->SrbTemplate)->SrbLength;
+ NT_ASSERT(((PSTORAGE_REQUEST_BLOCK) Pkt->Srb)->SrbLength >= srbLength);
+ } else {
+ srbLength = fdoData->SrbTemplate->Length;
+ }
+ RtlCopyMemory(Pkt->Srb, fdoData->SrbTemplate, srbLength); // copies _contents_ of SRB blocks
+
+ SrbSetRequestAttribute(Pkt->Srb, SRB_SIMPLE_TAG_REQUEST);
+ SrbSetCdbLength(Pkt->Srb, 16);
+ SrbSetOriginalRequest(Pkt->Srb, Pkt->Irp);
+ SrbSetSenseInfoBuffer(Pkt->Srb, &Pkt->SrbErrorSenseData);
+ SrbSetSenseInfoBufferLength(Pkt->Srb, sizeof(Pkt->SrbErrorSenseData));
+ SrbSetTimeOutValue(Pkt->Srb, fdoExt->TimeOutValue);
+ SrbSetDataBuffer(Pkt->Srb, WriteUsingTokenBuffer);
+ SrbSetDataTransferLength(Pkt->Srb, Length);
+
+ SrbAssignSrbFlags(Pkt->Srb, fdoExt->SrbFlags | SRB_FLAGS_DATA_OUT | SRB_FLAGS_DISABLE_SYNCH_TRANSFER | SRB_FLAGS_NO_QUEUE_FREEZE);
+
+ pCdb = SrbGetCdb(Pkt->Srb);
+ if (pCdb) {
+ pCdb->TOKEN_OPERATION.OperationCode = SCSIOP_WRITE_USING_TOKEN;
+ pCdb->TOKEN_OPERATION.ServiceAction = SERVICE_ACTION_WRITE_USING_TOKEN;
+
+ REVERSE_BYTES(&pCdb->TOKEN_OPERATION.ParameterListLength, &Length);
+ REVERSE_BYTES(&pCdb->TOKEN_OPERATION.ListIdentifier, &ListIdentifier);
+ }
+
+ Pkt->BufPtrCopy = WriteUsingTokenBuffer;
+ Pkt->BufLenCopy = Length;
+
+ Pkt->OriginalIrp = OriginalIrp;
+ Pkt->NumRetries = NUM_WRITE_USING_TOKEN_RETRIES;
+ Pkt->CompleteOriginalIrpWhenLastPacketCompletes = FALSE;
+
+ Pkt->ContinuationRoutine = ClasspWriteUsingTokenTransferPacketDone;
+ Pkt->ContinuationContext = OffloadWriteContext;
+
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspSetupWriteUsingTokenTransferPacket (%p): Exiting function with Irp %p\n",
+ Pkt->Fdo,
+ OriginalIrp));
+
+ return;
+}
+
+
+_IRQL_requires_max_(APC_LEVEL)
+_IRQL_requires_min_(PASSIVE_LEVEL)
+_IRQL_requires_same_
+VOID
+ClasspSetupReceiveWriteUsingTokenInformationTransferPacket(
+ _In_ POFFLOAD_WRITE_CONTEXT OffloadWriteContext,
+ _In_ PTRANSFER_PACKET Pkt,
+ _In_ ULONG Length,
+ _In_reads_bytes_ (Length) PUCHAR ReceiveWriteUsingTokenInformationBuffer,
+ _In_ PIRP OriginalIrp,
+ _In_ ULONG ListIdentifier
+ )
+
+/*++
+
+Routine description:
+
+ This routine is called once to set up a packet for extended results for
+ WriteUsingToken operation. It builds up the SRB by setting the appropriate fields.
+
+Arguments:
+
+ Pkt - The transfer packet to be sent down to the lower driver
+ SyncEventPtr - The event that gets signaled once the IRP contained in the packet completes
+ Length - Length of the buffer being sent as part of the command
+ ReceiveWriteUsingTokenInformationBuffer - The buffer into which the target will pass back the extended results
+ OriginalIrp - The Io request to be processed
+ ListIdentifier - The identifier that will be used to correlate this command with its corresponding previous write using token operation
+
+Return Value:
+
+ Nothing
+
+--*/
+
+{
+ PFUNCTIONAL_DEVICE_EXTENSION fdoExt;
+ PCLASS_PRIVATE_FDO_DATA fdoData;
+ PCDB pCdb;
+ ULONG srbLength;
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspSetupReceiveWriteUsingTokenInformationTransferPacket (%p): Entering function. Irp %p\n",
+ Pkt->Fdo,
+ OriginalIrp));
+
+ fdoExt = Pkt->Fdo->DeviceExtension;
+ fdoData = fdoExt->PrivateFdoData;
+
+ if (fdoExt->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) {
+ srbLength = ((PSTORAGE_REQUEST_BLOCK) fdoData->SrbTemplate)->SrbLength;
+ NT_ASSERT(((PSTORAGE_REQUEST_BLOCK) Pkt->Srb)->SrbLength >= srbLength);
+ } else {
+ srbLength = fdoData->SrbTemplate->Length;
+ }
+ RtlCopyMemory(Pkt->Srb, fdoData->SrbTemplate, srbLength); // copies _contents_ of SRB blocks
+
+ SrbSetRequestAttribute(Pkt->Srb, SRB_SIMPLE_TAG_REQUEST);
+ SrbSetCdbLength(Pkt->Srb, 16);
+ SrbSetOriginalRequest(Pkt->Srb, Pkt->Irp);
+ SrbSetSenseInfoBuffer(Pkt->Srb, &Pkt->SrbErrorSenseData);
+ SrbSetSenseInfoBufferLength(Pkt->Srb, sizeof(Pkt->SrbErrorSenseData));
+ SrbSetTimeOutValue(Pkt->Srb, fdoExt->TimeOutValue);
+ SrbSetDataBuffer(Pkt->Srb, ReceiveWriteUsingTokenInformationBuffer);
+ SrbSetDataTransferLength(Pkt->Srb, Length);
+
+ SrbAssignSrbFlags(Pkt->Srb, fdoExt->SrbFlags | SRB_FLAGS_DATA_IN | SRB_FLAGS_DISABLE_SYNCH_TRANSFER | SRB_FLAGS_NO_QUEUE_FREEZE);
+
+ pCdb = SrbGetCdb(Pkt->Srb);
+ if (pCdb) {
+ pCdb->RECEIVE_TOKEN_INFORMATION.OperationCode = SCSIOP_RECEIVE_ROD_TOKEN_INFORMATION;
+ pCdb->RECEIVE_TOKEN_INFORMATION.ServiceAction = SERVICE_ACTION_RECEIVE_TOKEN_INFORMATION;
+
+ REVERSE_BYTES(&pCdb->RECEIVE_TOKEN_INFORMATION.AllocationLength, &Length);
+ REVERSE_BYTES(&pCdb->RECEIVE_TOKEN_INFORMATION.ListIdentifier, &ListIdentifier);
+ }
+
+ Pkt->BufPtrCopy = ReceiveWriteUsingTokenInformationBuffer;
+ Pkt->BufLenCopy = Length;
+
+ Pkt->OriginalIrp = OriginalIrp;
+ Pkt->NumRetries = NUM_WRITE_USING_TOKEN_RETRIES;
+ Pkt->CompleteOriginalIrpWhenLastPacketCompletes = FALSE;
+
+ Pkt->ContinuationRoutine = ClasspReceiveWriteUsingTokenInformationTransferPacketDone;
+ Pkt->ContinuationContext = OffloadWriteContext;
+
+
+ TracePrint((TRACE_LEVEL_VERBOSE,
+ TRACE_FLAG_IOCTL,
+ "ClasspSetupReceiveWriteUsingTokenInformationTransferPacket (%p): Exiting function with Irp %p\n",
+ Pkt->Fdo,
+ OriginalIrp));
+
+ return;
+}
+
+