diff options
| author | Dave Wilson <[email protected]> | 2015-03-17 19:50:07 -0700 |
|---|---|---|
| committer | Dave Wilson <[email protected]> | 2015-03-17 19:50:07 -0700 |
| commit | 97cf5197cf5b882b2c689d8dc2b555f2edf8f418 (patch) | |
| tree | 46f3701832d70b420eb0fc0eb93261f9da45db3f /storage/class/classpnp/src | |
| parent | ef1905bf1e8825bb31120dfb27e0daf3154d859a (diff) | |
Initial publish
Diffstat (limited to 'storage/class/classpnp/src')
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(¶mStr, L"Parameters"); + + InitializeObjectAttributes(&objectAttributes, + ¶mStr, + OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, + serviceKey, + NULL); + + status = ZwOpenKey(¶metersKey, + 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(¶meters); + parameters.NoWakeTolerance = noWakeTolerance; + fdoData->CurrentNoWakeTolerance = noWakeTolerance; + + ExSetTimer(fdoData->TickTimer, + dueTime, + period, + ¶meters); + + 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(¶meters); + ExDeleteTimer(fdoData->TickTimer, TRUE, FALSE, ¶meters); + 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(¶meters); + + // + // 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, + ¶meters); + + 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(¤tTime); + 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), + ¬Ready); + break; + } + + case SCSI_SENSEQ_MANUAL_INTERVENTION_REQUIRED: { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Manual intervention required\n")); + *Status = STATUS_NO_MEDIA_IN_DEVICE; + retry = FALSE; + break; + } + + case SCSI_SENSEQ_FORMAT_IN_PROGRESS: { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Format in progress\n")); + retry = FALSE; + break; + } + + case SCSI_SENSEQ_OPERATION_IN_PROGRESS: { + DEVICE_EVENT_BECOMING_READY notReady = {0}; + + logRetryableError = FALSE; + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Operation In Progress\n")); + + notReady.Version = 1; + notReady.Reason = 2; + notReady.Estimated100msToReady = retryInterval * 10; + ClassSendNotification(fdoExtension, + &GUID_IO_DEVICE_BECOMING_READY, + sizeof(DEVICE_EVENT_BECOMING_READY), + ¬Ready); + + break; + } + + case SCSI_SENSEQ_LONG_WRITE_IN_PROGRESS: { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Long write in progress\n")); + // + // This has been seen as a transcient failure on some cdrom + // drives. The cdrom class driver is going to override this + // setting but has no way of dropping the retry interval + // + retry = FALSE; + retryInterval = 1; + break; + } + + case SCSI_SENSEQ_SPACE_ALLOC_IN_PROGRESS: { + logRetryableError = FALSE; + TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "The device (%p) is busy allocating space.\n", + Fdo)); + + // + // This indicates that a thinly-provisioned device has hit + // a temporary resource exhaustion and is busy allocating + // more space. We need to retry the request as the device + // will eventually be able to service it. + // + *Status = STATUS_RETRY; + retry = TRUE; + + break; + } + + case SCSI_SENSEQ_CAUSE_NOT_REPORTABLE: { + + if (!TEST_FLAG(fdoExtension->ScanForSpecialFlags, + CLASS_SPECIAL_CAUSE_NOT_REPORTABLE_HACK)) { + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, + "ClassInterpretSenseInfo: " + "not ready, cause unknown\n")); + /* + Many non-WHQL certified drives (mostly CD-RW) return + this when they have no media instead of the obvious + choice of: + + SCSI_SENSE_NOT_READY/SCSI_ADSENSE_NO_MEDIA_IN_DEVICE + + These drives should not pass WHQL certification due + to this discrepency. + + */ + retry = FALSE; + break; + + } else { + + // + // Treat this as init command required and fall through. + // + } + } + + case SCSI_SENSEQ_INIT_COMMAND_REQUIRED: + default: { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Initializing command required\n")); + retryInterval = 0; // go back to default + logRetryableError = FALSE; + + // + // This sense code/additional sense code + // combination may indicate that the device + // needs to be started. Send an start unit if this + // is a disk device. + // + if (TEST_FLAG(fdoExtension->DeviceFlags, DEV_SAFE_START_UNIT) && + !TEST_FLAG(SrbGetSrbFlags(Srb), SRB_CLASS_FLAGS_LOW_PRIORITY)){ + + ClassSendStartUnit(Fdo); + } + break; + } + + } // end switch (addlSenseCodeQual) + break; + } + + case SCSI_ADSENSE_NO_MEDIA_IN_DEVICE: { + TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "No Media in device.\n")); + *Status = STATUS_NO_MEDIA_IN_DEVICE; + retry = FALSE; + + // + // signal MCN that there isn't any media in the device + // + if (!TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA)) { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "No Media in a non-removable device %p\n", + Fdo)); + } + + if (addlSenseCodeQual == 0xCC){ + /* + * The IMAPI filter returns this ASCQ value when it is burning CD-R media. + * We want to indicate that the media is not present to most applications; + * but RSM has to know that the media is still in the drive (i.e. the drive is not free). + */ + ClassSetMediaChangeState(fdoExtension, MediaUnavailable, FALSE); + } + else { + ClassSetMediaChangeState(fdoExtension, MediaNotPresent, FALSE); + } + + break; + } + } // end switch (addlSenseCode) + + break; + } // end SCSI_SENSE_NOT_READY + + case SCSI_SENSE_MEDIUM_ERROR: { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Medium Error (bad block)\n")); + *Status = STATUS_DEVICE_DATA_ERROR; + + retry = FALSE; + logError = TRUE; + uniqueId = 256; + logStatus = IO_ERR_BAD_BLOCK; + + // + // Check if this error is due to unknown format + // + if (addlSenseCode == SCSI_ADSENSE_INVALID_MEDIA) { + + switch (addlSenseCodeQual) { + + case SCSI_SENSEQ_UNKNOWN_FORMAT: { + + *Status = STATUS_UNRECOGNIZED_MEDIA; + + // + // Log error only if this is a paging request + // + if (!TEST_FLAG(SrbGetSrbFlags(Srb), SRB_CLASS_FLAGS_PAGING)) { + logError = FALSE; + } + break; + } + + case SCSI_SENSEQ_CLEANING_CARTRIDGE_INSTALLED: { + + *Status = STATUS_CLEANER_CARTRIDGE_INSTALLED; + logError = FALSE; + break; + + } + default: { + break; + } + } // end switch addlSenseCodeQual + + } // end SCSI_ADSENSE_INVALID_MEDIA + + break; + + } // end SCSI_SENSE_MEDIUM_ERROR + + case SCSI_SENSE_HARDWARE_ERROR: { + BOOLEAN logHardwareError = TRUE; + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Hardware error\n")); + + if (fdoData->LegacyErrorHandling == FALSE) { + // + // Hardware errors indicate something has seriously gone + // wrong with the device and retries are very unlikely to + // succeed so fail this request back immediately. + // + retry = FALSE; + *Status = STATUS_DEVICE_HARDWARE_ERROR; + logError = FALSE; + + } else { + // + // Revert to legacy behavior. That is, retry everything by default. + // + retry = TRUE; + *Status = STATUS_IO_DEVICE_ERROR; + logError = TRUE; + uniqueId = 257; + logStatus = IO_ERR_CONTROLLER_ERROR; + logHardwareError = FALSE; + + // + // This indicates the possibility of a dropped FC packet. + // + if ((addlSenseCode == SCSI_ADSENSE_LOGICAL_UNIT_ERROR && addlSenseCodeQual == SCSI_SENSEQ_TIMEOUT_ON_LOGICAL_UNIT) || + (addlSenseCode == SCSI_ADSENSE_DATA_TRANSFER_ERROR && addlSenseCodeQual == SCSI_SENSEQ_INITIATOR_RESPONSE_TIMEOUT)) { + // + // Fail requests that report this error back to the application. + // + retry = FALSE; + + // + // Log a more descriptive error and avoid a second + // error message (IO_ERR_CONTROLLER_ERROR) being logged. + // + logHardwareError = TRUE; + logError = FALSE; + } + } + + // + // If CRC error was returned, retry after a slight delay. + // + if (addlSenseCode == SCSI_ADSENSE_LUN_COMMUNICATION && + addlSenseCodeQual == SCSI_SESNEQ_COMM_CRC_ERROR) { + retry = TRUE; + retryInterval = 1; + logHardwareError = FALSE; + logError = FALSE; + } + + // + // Hardware errors warrant a more descriptive error. + // Specifically, we need to ensure this disk is easily + // identifiable. + // + if (logHardwareError) { + UCHAR senseInfoBufferLength = SrbGetSenseInfoBufferLength(Srb); + UCHAR senseBufferSize = 0; + + if (ScsiGetTotalSenseByteCountIndicated(senseBuffer, + senseInfoBufferLength, + &senseBufferSize)) { + + senseBufferSize = min(senseBufferSize, senseInfoBufferLength); + + } else { + // + // it's smaller than required to read the total number of + // valid bytes, so just use the SenseInfoBufferLength field. + // + senseBufferSize = senseInfoBufferLength; + } + + ClasspQueueLogIOEventWithContextWorker(Fdo, + senseBufferSize, + senseBuffer, + SRB_STATUS(Srb->SrbStatus), + SrbGetScsiStatus(Srb), + (ULONG)IO_ERROR_IO_HARDWARE_ERROR, + cdbLength, + cdb, + NULL); + } + + break; + } // end SCSI_SENSE_HARDWARE_ERROR + + case SCSI_SENSE_ILLEGAL_REQUEST: { + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Illegal SCSI request\n")); + *Status = STATUS_INVALID_DEVICE_REQUEST; + retry = FALSE; + + switch (addlSenseCode) { + + case SCSI_ADSENSE_NO_SENSE: { + + switch (addlSenseCodeQual) { + + // + // 1. Duplicate List Identifier + // + case SCSI_SENSEQ_OPERATION_IS_IN_PROGRESS: { + + // + // XCOPY, READ BUFFER and CHANGE ALIASES return this sense combination under + // certain conditions. Since these commands aren't sent down natively by the + // Windows OS, return the default error for them and only handle this sense + // combination for offload data transfer commands. + // + if (ClasspIsOffloadDataTransferCommand(cdb)) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassInterpretSenseInfo (%p): Duplicate List Identifier (command %x, parameter field offset 0x%016llx)\n", + Fdo, + cdbOpcode, + information)); + + NT_ASSERTMSG("Duplicate list identifier specified", FALSE); + + // + // The host should ensure that it uses unique list id for each TokenOperation request. + // + *Status = STATUS_OPERATION_IN_PROGRESS; + } + break; + } + } + break; + } + + case SCSI_ADSENSE_LUN_COMMUNICATION: { + + switch (addlSenseCodeQual) { + + // + // 1. Source/Destination pairing can't communicate with each other or the copy manager. + // + case SCSI_SENSEQ_UNREACHABLE_TARGET: { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassInterpretSenseInfo (%p): Source-Destination LUNs can't communicate (command %x)\n", + Fdo, + cdbOpcode)); + + *Status = STATUS_DEVICE_UNREACHABLE; + break; + } + } + break; + } + + case SCSI_ADSENSE_COPY_TARGET_DEVICE_ERROR: { + + switch (addlSenseCodeQual) { + + // + // 1. Sum of logical block fields in all block device range descriptors is greater than number + // of logical blocks in the ROD minus block offset into ROD + // + case SCSI_SENSEQ_DATA_UNDERRUN: { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassInterpretSenseInfo (%p): Host specified a transfer length greater than what is represented by the token (considering the offset) [command %x]\n", + Fdo, + cdbOpcode)); + + NT_ASSERTMSG("Host specified blocks to write beyond what is represented by the token", FALSE); + + *Status = STATUS_DATA_OVERRUN; + break; + } + } + break; + } + + // + // 1. Parameter data truncation (e.g. last descriptor was not fully specified) + // + case SCSI_ADSENSE_PARAMETER_LIST_LENGTH: { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassInterpretSenseInfo (%p): Target truncated the block device range descriptors in the parameter list (command %x)\n", + Fdo, + cdbOpcode)); + + NT_ASSERTMSG("Parameter data truncation", FALSE); + + *Status = STATUS_DATA_OVERRUN; + break; + } + + case SCSI_ADSENSE_ILLEGAL_COMMAND: { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Illegal command\n")); + break; + } + + case SCSI_ADSENSE_ILLEGAL_BLOCK: { + + LARGE_INTEGER logicalBlockAddr; + LARGE_INTEGER lastLBA; + ULONG numTransferBlocks = 0; + + logicalBlockAddr.QuadPart = 0; + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: Illegal block address\n")); + + *Status = STATUS_NONEXISTENT_SECTOR; + + if (Fdo->DeviceType == FILE_DEVICE_DISK) { + + if (IS_SCSIOP_READWRITE(cdbOpcode) && cdb) { + + if (TEST_FLAG(fdoExtension->DeviceFlags, DEV_USE_16BYTE_CDB)) { + REVERSE_BYTES_QUAD(&logicalBlockAddr, &cdb->CDB16.LogicalBlock); + REVERSE_BYTES(&numTransferBlocks, &cdb->CDB16.TransferLength); + } else { + REVERSE_BYTES(&logicalBlockAddr.LowPart, &cdb->CDB10.LogicalBlockByte0); + REVERSE_BYTES_SHORT((PUSHORT)&numTransferBlocks, &cdb->CDB10.TransferBlocksMsb); + } + + REVERSE_BYTES_QUAD(&lastLBA, &fdoData->LastKnownDriveCapacityData.LogicalBlockAddress); + + if ((logicalBlockAddr.QuadPart > lastLBA.QuadPart) || + ((logicalBlockAddr.QuadPart + numTransferBlocks - 1) > lastLBA.QuadPart)) { + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Request beyond boundary. Last LBA: 0x%I64X Read LBA: 0x%I64X Length: 0x%X\n", + (__int64) lastLBA.QuadPart, (__int64) logicalBlockAddr.QuadPart, numTransferBlocks)); + } else { + // + // Should only retry these if the request was + // truly within our expected size. + // + // Fujitsu IDE drives have been observed to + // return this error transiently for a legal LBA; + // manual retry in the debugger then works, so + // there is a good chance that a programmed retry + // will also work. + // + + retry = TRUE; + retryInterval = 5; + } + } else if (ClasspIsOffloadDataTransferCommand(cdb)) { + + // + // 1. Number of logical blocks of block device range descriptor exceeds capacity of the medium + // + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassInterpretSenseInfo (%p): LBA out of range (command %x, parameter field offset 0x%016llx)\n", + Fdo, + cdbOpcode, + information)); + + NT_ASSERTMSG("Number of blocks specified exceeds LUN capacity", FALSE); + } + } + break; + } + + // + // 1. Generic error - cause not reportable + // 2. Insufficient resources to create ROD + // 3. Insufficient resources to create Token + // 4. Max number of tokens exceeded + // 5. Remote Token creation not supported + // 6. Token expired + // 7. Token unknown + // 8. Unsupported Token type + // 9. Token corrupt + // 10. Token revoked + // 11. Token cancelled + // 12. Remote Token usage not supported + // + case SCSI_ADSENSE_INVALID_TOKEN: { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassInterpretSenseInfo (%p): Invalid/Expired/Modified token specified (command %x, parameter field offset 0x%016llx)\n", + Fdo, + cdbOpcode, + information)); + + *Status = STATUS_INVALID_TOKEN; + break; + } + + case SCSI_ADSENSE_INVALID_CDB: { + if (ClasspIsOffloadDataTransferCommand(cdb)) { + + // + // 1. Mismatched I_T nexus and list identifier + // + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassInterpretSenseInfo (%p): Incorrect I_T nexus likely used (command %x)\n", + Fdo, + cdbOpcode)); + + // + // The host should ensure that it sends TokenOperation and ReceiveTokenInformation for the same + // list Id using the same I_T nexus. + // + *Status = STATUS_INVALID_INITIATOR_TARGET_PATH; + + } else { + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Invalid CDB\n")); + + // + // Note: the retry interval is not typically used. + // it is set here only because a ClassErrorHandler + // cannot set the retryInterval, and the error may + // require a few commands to be sent to clear whatever + // caused this condition (i.e. disk clears the write + // cache, requiring at least two commands) + // + // hopefully, this shortcoming can be changed for + // blackcomb. + // + + retryInterval = 3; + } + break; + } + + case SCSI_ADSENSE_INVALID_LUN: { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Invalid LUN\n")); + *Status = STATUS_NO_SUCH_DEVICE; + break; + } + + case SCSI_ADSENSE_INVALID_FIELD_PARAMETER_LIST: { + + switch (addlSenseCodeQual) { + + // + // 1. Alignment violation (e.g. copy manager is unable to copy because destination offset is NOT aligned to LUN's granularity/alignment) + // + case SCSI_SENSEQ_INVALID_RELEASE_OF_PERSISTENT_RESERVATION: { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassInterpretSenseInfo (%p): Alignment violation for command %x.\n", + Fdo, + cdbOpcode)); + + NT_ASSERTMSG("Specified offset is not aligned to LUN's granularity", FALSE); + + *Status = STATUS_INVALID_OFFSET_ALIGNMENT; + break; + } + + // + // 1. Number of block device range descriptors is greater than maximum range descriptors + // + case SCSI_SENSEQ_TOO_MANY_SEGMENT_DESCRIPTORS: { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassInterpretSenseInfo (%p): Too many descriptors in parameter list for command %x (parameter field offset 0x%016llx)\n", + Fdo, + cdbOpcode, + information)); + + NT_ASSERTMSG("Too many descriptors specified", FALSE); + + *Status = STATUS_TOO_MANY_SEGMENT_DESCRIPTORS; + break; + } + + default: { + + if (ClasspIsOffloadDataTransferCommand(cdb)) { + + // + // 1. (Various) Invalid parameter length + // 2. Requested inactivity timeout is greater than maximum inactivity timeout + // 3. Same LBA is included in more than one block device range descriptor (overlapping LBAs) + // 4. Total number of logical blocks of all block range descriptors is greater than the maximum transfer size + // 5. Total number of logical blocks of all block range descriptors is greater than maximum token transfer size + // (e.g. WriteUsingToken descriptors specify a cumulative total block count that exceeds the PopulateToken that created the token) + // 6. Block offset into ROD specified an offset that is greater than or equal to the number of logical blocks in the ROD + // 7. Number of logical blocks in a block device range descriptor is greater than maximum transfer length in blocks + // + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassInterpretSenseInfo (%p): Illegal field in parameter list for command %x (parameter field offset 0x%016llx) [AddSense %x, AddSenseQ %x]\n", + Fdo, + cdbOpcode, + information, + addlSenseCode, + addlSenseCodeQual)); + + NT_ASSERTMSG("Invalid field in parameter list", FALSE); + + *Status = STATUS_INVALID_FIELD_IN_PARAMETER_LIST; + } + + break; + } + } + break; + } + + case SCSI_ADSENSE_COPY_PROTECTION_FAILURE: { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Copy protection failure\n")); + + *Status = STATUS_COPY_PROTECTION_FAILURE; + + switch (addlSenseCodeQual) { + case SCSI_SENSEQ_AUTHENTICATION_FAILURE: + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, + "ClassInterpretSenseInfo: " + "Authentication failure\n")); + *Status = STATUS_CSS_AUTHENTICATION_FAILURE; + break; + case SCSI_SENSEQ_KEY_NOT_PRESENT: + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, + "ClassInterpretSenseInfo: " + "Key not present\n")); + *Status = STATUS_CSS_KEY_NOT_PRESENT; + break; + case SCSI_SENSEQ_KEY_NOT_ESTABLISHED: + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, + "ClassInterpretSenseInfo: " + "Key not established\n")); + *Status = STATUS_CSS_KEY_NOT_ESTABLISHED; + break; + case SCSI_SENSEQ_READ_OF_SCRAMBLED_SECTOR_WITHOUT_AUTHENTICATION: + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, + "ClassInterpretSenseInfo: " + "Read of scrambled sector w/o " + "authentication\n")); + *Status = STATUS_CSS_SCRAMBLED_SECTOR; + break; + case SCSI_SENSEQ_MEDIA_CODE_MISMATCHED_TO_LOGICAL_UNIT: + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, + "ClassInterpretSenseInfo: " + "Media region does not logical unit " + "region\n")); + *Status = STATUS_CSS_REGION_MISMATCH; + break; + case SCSI_SENSEQ_LOGICAL_UNIT_RESET_COUNT_ERROR: + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, + "ClassInterpretSenseInfo: " + "Region set error -- region may " + "be permanent\n")); + *Status = STATUS_CSS_RESETS_EXHAUSTED; + break; + } // end switch of ASCQ for COPY_PROTECTION_FAILURE + + break; + } + + case SCSI_ADSENSE_MUSIC_AREA: { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Music area\n")); + break; + } + + case SCSI_ADSENSE_DATA_AREA: { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Data area\n")); + break; + } + + case SCSI_ADSENSE_VOLUME_OVERFLOW: { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Volume overflow\n")); + break; + } + + } // end switch (addlSenseCode) + + break; + } // end SCSI_SENSE_ILLEGAL_REQUEST + + case SCSI_SENSE_UNIT_ATTENTION: { + + ULONG count; + + // + // A media change may have occured so increment the change + // count for the physical device + // + + count = InterlockedIncrement((volatile LONG *)&fdoExtension->MediaChangeCount); + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClassInterpretSenseInfo: " + "Media change count for device %d incremented to %#lx\n", + fdoExtension->DeviceNumber, count)); + + + switch (addlSenseCode) { + case SCSI_ADSENSE_MEDIUM_CHANGED: { + logRetryableError = FALSE; + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "ClassInterpretSenseInfo: " + "Media changed\n")); + + if (!TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA)) { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_MCN, "ClassInterpretSenseInfo: " + "Media Changed on non-removable device %p\n", + Fdo)); + } + ClassSetMediaChangeState(fdoExtension, MediaPresent, FALSE); + break; + } + + case SCSI_ADSENSE_BUS_RESET: { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Bus reset\n")); + break; + } + + case SCSI_ADSENSE_PARAMETERS_CHANGED: { + logRetryableError = FALSE; + if (addlSenseCodeQual == SCSI_SENSEQ_CAPACITY_DATA_CHANGED) { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Device capacity changed (e.g. thinly provisioned LUN). Retry the request.\n")); + + ClassQueueCapacityChangedEventWorker(Fdo); + + // + // Retry with 1 second delay as ClassQueueCapacityChangedEventWorker may trigger a couple of commands sent to disk. + // + retryInterval = 1; + retry = TRUE; + } + break; + } + + case SCSI_ADSENSE_LB_PROVISIONING: { + + switch (addlSenseCodeQual) { + + case SCSI_SENSEQ_SOFT_THRESHOLD_REACHED: { + + logRetryableError = FALSE; + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Device (%p) has hit a soft threshold.\n", + Fdo)); + + // + // This indicates that a resource provisioned or thinly + // provisioned device has hit a soft threshold. Queue a + // worker thread to log a system event and then retry the + // original request. + // + ClassQueueThresholdEventWorker(Fdo); + break; + } + default: { + retry = FALSE; + break; + } + + } // end switch (addlSenseCodeQual) + break; + } + + case SCSI_ADSENSE_OPERATING_CONDITIONS_CHANGED: { + + if (addlSenseCodeQual == SCSI_SENSEQ_MICROCODE_CHANGED) { + // + // Device firmware has been changed. Retry the request. + // + logRetryableError = TRUE; + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Device firmware has been changed.\n")); + + retryInterval = 1; + retry = TRUE; + } else { + // + // Device information has changed, we need to rescan the + // bus for changed information such as the capacity. + // + logRetryableError = FALSE; + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Device information changed. Invalidate the bus\n")); + + if (addlSenseCodeQual == SCSI_SENSEQ_INQUIRY_DATA_CHANGED) { + + ClassQueueProvisioningTypeChangedEventWorker(Fdo); + } + + if (addlSenseCodeQual == SCSI_SENSEQ_INQUIRY_DATA_CHANGED || + addlSenseCodeQual == SCSI_SENSEQ_OPERATING_DEFINITION_CHANGED) { + + // + // Since either the LB provisioning type changed, or the block/slab size + // changed, next time anyone trying to query the FunctionSupportInfo, we + // will requery the device. + // + InterlockedIncrement((volatile LONG *)&fdoExtension->FunctionSupportInfo->ChangeRequestCount); + } + + IoInvalidateDeviceRelations(fdoExtension->LowerPdo, BusRelations); + retryInterval = 5; + } + break; + } + + case SCSI_ADSENSE_OPERATOR_REQUEST: { + switch (addlSenseCodeQual) { + + case SCSI_SENSEQ_MEDIUM_REMOVAL: { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Ejection request received!\n")); + ClassSendEjectionNotification(fdoExtension); + break; + } + + case SCSI_SENSEQ_WRITE_PROTECT_ENABLE: { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Operator selected write permit?! " + "(unsupported!)\n")); + break; + } + + case SCSI_SENSEQ_WRITE_PROTECT_DISABLE: { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Operator selected write protect?! " + "(unsupported!)\n")); + break; + } + } + } + + case SCSI_ADSENSE_FAILURE_PREDICTION_THRESHOLD_EXCEEDED: { + + UCHAR wmiEventData[sizeof(ULONG)+sizeof(UCHAR)] = {0}; + + *((PULONG)wmiEventData) = sizeof(UCHAR); + wmiEventData[sizeof(ULONG)] = addlSenseCodeQual; + + // + // Don't log another eventlog if we have already logged once + // NOTE: this should have been interlocked, but the structure + // was publicly defined to use a BOOLEAN (char). Since + // media only reports these errors once per X minutes, + // the potential race condition is nearly non-existant. + // the worst case is duplicate log entries, so ignore. + // + + logError = FALSE; + if (fdoExtension->FailurePredicted == 0) { + logError = TRUE; + } + fdoExtension->FailureReason = addlSenseCodeQual; + logStatus = IO_WRN_FAILURE_PREDICTED; + + ClassNotifyFailurePredicted(fdoExtension, + (PUCHAR)wmiEventData, + sizeof(wmiEventData), + FALSE, // do not log error + 4, // unique error value + SrbGetPathId(Srb), + SrbGetTargetId(Srb), + SrbGetLun(Srb)); + + fdoExtension->FailurePredicted = TRUE; + + // + // Since this is a Unit Attention we need to make + // sure we retry this request. + // + retry = TRUE; + + break; + } + + default: { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Unit attention\n")); + break; + } + + } // end switch (addlSenseCode) + + if (TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA)) + { + + if ((ClassGetVpb(Fdo) != NULL) && (ClassGetVpb(Fdo)->Flags & VPB_MOUNTED)) + { + // + // Set bit to indicate that media may have changed + // and volume needs verification. + // + + SET_FLAG(Fdo->Flags, DO_VERIFY_VOLUME); + + *Status = STATUS_VERIFY_REQUIRED; + retry = FALSE; + } + else { + *Status = STATUS_IO_DEVICE_ERROR; + } + } + else + { + *Status = STATUS_IO_DEVICE_ERROR; + } + + break; + + } // end SCSI_SENSE_UNIT_ATTENTION + + case SCSI_SENSE_DATA_PROTECT: { + + retry = FALSE; + + if (addlSenseCode == SCSI_ADSENSE_WRITE_PROTECT) + { + switch (addlSenseCodeQual) { + + case SCSI_SENSEQ_SPACE_ALLOC_FAILED_WRITE_PROTECT: { + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Device's (%p) resources are exhausted.\n", + Fdo)); + + ClassQueueResourceExhaustionEventWorker(Fdo); + + // + // This indicates that a thinly-provisioned device has + // hit a permanent resource exhaustion. We need to + // return this status code so that patmgr can take the + // disk offline. + // + *Status = STATUS_DISK_RESOURCES_EXHAUSTED; + break; + } + default: + { + break; + } + + } // end switch addlSenseCodeQual + } + else + { + if (IS_SCSIOP_WRITE(cdbOpcode)) { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Media write protected\n")); + *Status = STATUS_MEDIA_WRITE_PROTECTED; + } else { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Access denied\n")); + *Status = STATUS_ACCESS_DENIED; + } + } + break; + } // end SCSI_SENSE_DATA_PROTECT + + case SCSI_SENSE_BLANK_CHECK: { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Media blank check\n")); + retry = FALSE; + *Status = STATUS_NO_DATA_DETECTED; + break; + } // end SCSI_SENSE_BLANK_CHECK + + case SCSI_SENSE_COPY_ABORTED: { + + switch (addlSenseCode) { + + case SCSI_ADSENSE_COPY_TARGET_DEVICE_ERROR: { + + switch (addlSenseCodeQual) { + + // + // 1. Target truncated the data transfer. + // + case SCSI_SENSEQ_DATA_UNDERRUN: { + + TracePrint((TRACE_LEVEL_WARNING, + TRACE_FLAG_IOCTL, + "ClassInterpretSenseInfo (%p): Data transfer was truncated (command %x)\n", + Fdo, + cdbOpcode)); + + *Status = STATUS_SUCCESS; + retry = FALSE; + break; + } + } + break; + } + } + break; + } + + case SCSI_SENSE_ABORTED_COMMAND: { + if (ClasspIsOffloadDataTransferCommand(cdb)) { + + switch (addlSenseCode) { + + case SCSI_ADSENSE_COPY_TARGET_DEVICE_ERROR: { + + switch (addlSenseCodeQual) { + + // + // 1. Target truncated the data transfer. + // + case SCSI_SENSEQ_DATA_UNDERRUN: { + + TracePrint((TRACE_LEVEL_WARNING, + TRACE_FLAG_IOCTL, + "ClassInterpretSenseInfo (%p): Target has truncated the data transfer (command %x)\n", + Fdo, + cdbOpcode)); + + *Status = STATUS_SUCCESS; + retry = FALSE; + break; + } + } + break; + } + + case SCSI_ADSENSE_RESOURCE_FAILURE: { + + switch (addlSenseCodeQual) { + + // + // 1. Copy manager wasn't able to finish the operation because of insuffient resources + // (e.g. microsnapshot failure on read, no space on write, etc.) + // + case SCSI_SENSEQ_INSUFFICIENT_RESOURCES: { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassInterpretSenseInfo (%p): Target has insufficient resources (command %x)\n", + Fdo, + cdb->CDB6GENERIC.OperationCode)); + + *Status = STATUS_INSUFFICIENT_RESOURCES; + retry = FALSE; + break; + } + } + break; + } + } + } else { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Command aborted\n")); + *Status = STATUS_IO_DEVICE_ERROR; + retryInterval = 1; + } + break; + } // end SCSI_SENSE_ABORTED_COMMAND + + default: { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Unrecognized sense code\n")); + *Status = STATUS_IO_DEVICE_ERROR; + break; + } + + } // end switch (senseKey) + + + + // + // Try to determine bad sector information from sense data + // + + if (((IS_SCSIOP_READWRITE(cdbOpcode)) || + (cdbOpcode == SCSIOP_VERIFY) || + (cdbOpcode == SCSIOP_VERIFY16)) && cdb) { + + if (isInformationValid) + { + readSector = 0; + badSector = information; + + if (cdbOpcode == SCSIOP_READ16 || cdbOpcode == SCSIOP_WRITE16 || cdbOpcode == SCSIOP_VERIFY16) { + REVERSE_BYTES_QUAD(&readSector, &(cdb->AsByte[2])); + } else { + REVERSE_BYTES(&readSector, &(cdb->AsByte[2])); + } + + if (cdbOpcode == SCSIOP_READ || cdbOpcode == SCSIOP_WRITE || cdbOpcode == SCSIOP_VERIFY) { + REVERSE_BYTES_SHORT(&index, &(cdb->CDB10.TransferBlocksMsb)); + } else if (cdbOpcode == SCSIOP_READ6 || cdbOpcode == SCSIOP_WRITE6) { + index = cdb->CDB6READWRITE.TransferBlocks; + } else if(cdbOpcode == SCSIOP_READ12 || cdbOpcode == SCSIOP_WRITE12) { + REVERSE_BYTES(&index, &(cdb->CDB12.TransferLength)); + } else { + REVERSE_BYTES(&index, &(cdb->CDB16.TransferLength)); + } + + // + // Make sure the bad sector is within the read sectors. + // + + if (!(badSector >= readSector && badSector < readSector + index)) { + badSector = readSector; + } + } + } + } + +__ClassInterpretSenseInfo_ProcessingInvalidSenseBuffer: + + if (!validSense) { + + // + // Request sense buffer not valid. No sense information + // to pinpoint the error. Return general request fail. + // + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassInterpretSenseInfo: " + "Request sense info not valid. SrbStatus %2x\n", + SRB_STATUS(Srb->SrbStatus))); + retry = TRUE; + + + switch (SRB_STATUS(Srb->SrbStatus)) { + case SRB_STATUS_ABORTED: { + + // + // Update the error count for the device. + // + + incrementErrorCount = TRUE; + *Status = STATUS_IO_TIMEOUT; + retryInterval = 1; + retry = TRUE; + break; + } + + case SRB_STATUS_ERROR: { + + *Status = STATUS_IO_DEVICE_ERROR; + if (SrbGetScsiStatus(Srb) == SCSISTAT_GOOD) { + + // + // This is some strange return code. Update the error + // count for the device. + // + + incrementErrorCount = TRUE; + + } else if (SrbGetScsiStatus(Srb) == SCSISTAT_BUSY) { + + *Status = STATUS_DEVICE_NOT_READY; + logRetryableError = FALSE; + } + + break; + } + + case SRB_STATUS_INVALID_REQUEST: { + *Status = STATUS_INVALID_DEVICE_REQUEST; + retry = FALSE; + break; + } + + case SRB_STATUS_INVALID_PATH_ID: + case SRB_STATUS_NO_DEVICE: + case SRB_STATUS_NO_HBA: + case SRB_STATUS_INVALID_LUN: + case SRB_STATUS_INVALID_TARGET_ID: { + *Status = STATUS_NO_SUCH_DEVICE; + retry = FALSE; + break; + } + + case SRB_STATUS_SELECTION_TIMEOUT: { + logError = TRUE; + logStatus = IO_ERR_NOT_READY; + uniqueId = 260; + *Status = STATUS_DEVICE_NOT_CONNECTED; + retry = FALSE; + break; + } + + case SRB_STATUS_TIMEOUT: + case SRB_STATUS_COMMAND_TIMEOUT: { + + // + // Update the error count for the device. + // + incrementErrorCount = TRUE; + *Status = STATUS_IO_TIMEOUT; + break; + } + + case SRB_STATUS_PARITY_ERROR: + case SRB_STATUS_UNEXPECTED_BUS_FREE: + + // + // Update the error count for the device + // and fall through to below + // + incrementErrorCount = TRUE; + + case SRB_STATUS_BUS_RESET: { + + *Status = STATUS_IO_DEVICE_ERROR; + logRetryableError = FALSE; + break; + } + + case SRB_STATUS_DATA_OVERRUN: { + + *Status = STATUS_DATA_OVERRUN; + retry = FALSE; + + // + // For some commands, we allocate a buffer that may be + // larger than necessary. In these cases, the SRB may be + // returned with SRB_STATUS_DATA_OVERRUN to indicate a + // buffer *underrun*. However, the command was still + // successful so we ensure STATUS_SUCCESS is returned. + // We will also prevent these errors from causing noise in + // the error logs. + // + if ((cdbOpcode == SCSIOP_MODE_SENSE && SrbGetDataTransferLength(Srb) <= cdb->MODE_SENSE.AllocationLength) || + (cdbOpcode == SCSIOP_INQUIRY && SrbGetDataTransferLength(Srb) <= cdb->CDB6INQUIRY.AllocationLength)) { + *Status = STATUS_SUCCESS; + logErrorInternal = FALSE; + logError = FALSE; + } else if (cdbOpcode == SCSIOP_MODE_SENSE10) { + USHORT allocationLength; + REVERSE_BYTES_SHORT(&(cdb->MODE_SENSE10.AllocationLength), &allocationLength); + if (SrbGetDataTransferLength(Srb) <= allocationLength) { + *Status = STATUS_SUCCESS; + logErrorInternal = FALSE; + logError = FALSE; + } + } else if (ClasspIsReceiveTokenInformation(cdb)) { + ULONG allocationLength; + REVERSE_BYTES(&(cdb->RECEIVE_TOKEN_INFORMATION.AllocationLength), &allocationLength); + if (SrbGetDataTransferLength(Srb) <= allocationLength) { + *Status = STATUS_SUCCESS; + logErrorInternal = FALSE; + logError = FALSE; + } + } + + break; + } + + case SRB_STATUS_PHASE_SEQUENCE_FAILURE: { + + // + // Update the error count for the device. + // + + incrementErrorCount = TRUE; + *Status = STATUS_IO_DEVICE_ERROR; + + // + // If there was phase sequence error then limit the number of + // retries. + // + + if (RetryCount > 1 ) { + retry = FALSE; + } + + break; + } + + case SRB_STATUS_REQUEST_FLUSHED: { + + // + // If the status needs verification bit is set. Then set + // the status to need verification and no retry; otherwise, + // just retry the request. + // + + if (TEST_FLAG(Fdo->Flags, DO_VERIFY_VOLUME)) { + + *Status = STATUS_VERIFY_REQUIRED; + retry = FALSE; + + } else { + *Status = STATUS_IO_DEVICE_ERROR; + logRetryableError = FALSE; + } + + break; + } + + default: { + logError = TRUE; + logStatus = IO_ERR_CONTROLLER_ERROR; + uniqueId = 259; + *Status = STATUS_IO_DEVICE_ERROR; + unhandledError = TRUE; + logRetryableError = FALSE; + break; + } + } + + + // + // NTRAID #183546 - if we support GESN subtype NOT_READY events, and + // we know from a previous poll when the device will be ready (ETA) + // we should delay the retry more appropriately than just guessing. + // + /* + if (fdoExtension->MediaChangeDetectionInfo && + fdoExtension->MediaChangeDetectionInfo->Gesn.Supported && + TEST_FLAG(fdoExtension->MediaChangeDetectionInfo->Gesn.EventMask, + NOTIFICATION_DEVICE_BUSY_CLASS_MASK) + ) { + // check if Gesn.ReadyTime if greater than current tick count + // if so, delay that long (from 1 to 30 seconds max?) + // else, leave the guess of time alone. + } + */ + + } + + } + + if (incrementErrorCount) { + + // + // if any error count occurred, delay the retry of this io by + // at least one second, if caller supports it. + // + + if (retryInterval == 0) { + retryInterval = 1; + } + ClasspPerfIncrementErrorCount(fdoExtension); + } + + // + // If there is a class specific error handler call it. + // + + if (fdoExtension->CommonExtension.DevInfo->ClassError != NULL) { + + SCSI_REQUEST_BLOCK tempSrb = {0}; + PSCSI_REQUEST_BLOCK srbPtr = (PSCSI_REQUEST_BLOCK)Srb; + + // + // If class driver does not support extended SRB and this is + // an extended SRB, convert to legacy SRB and pass to class + // driver. + // + if ((Srb->Function == SRB_FUNCTION_STORAGE_REQUEST_BLOCK) && + ((fdoExtension->CommonExtension.DriverExtension->SrbSupport & + CLASS_SRB_STORAGE_REQUEST_BLOCK) == 0)) { + ClasspConvertToScsiRequestBlock(&tempSrb, (PSTORAGE_REQUEST_BLOCK)Srb); + srbPtr = &tempSrb; + } + + fdoExtension->CommonExtension.DevInfo->ClassError(Fdo, + srbPtr, + Status, + &retry); + } + + // + // If the caller wants to know the suggested retry interval tell them. + // + + if (ARGUMENT_PRESENT(RetryInterval)) { + *RetryInterval = retryInterval; + } + + // + // The RESERVE(6) / RELEASE(6) commands are optional. So + // if they aren't supported, try the 10-byte equivalents + // + + cdb = SrbGetCdb(Srb); + if (cdb) { + cdbOpcode = cdb->CDB6GENERIC.OperationCode; + } + + if ((cdbOpcode == SCSIOP_RESERVE_UNIT || + cdbOpcode == SCSIOP_RELEASE_UNIT) && cdb) + { + if (*Status == STATUS_INVALID_DEVICE_REQUEST) + { + SrbSetCdbLength(Srb, 10); + cdb->CDB10.OperationCode = (cdb->CDB6GENERIC.OperationCode == SCSIOP_RESERVE_UNIT) ? SCSIOP_RESERVE_UNIT10 : SCSIOP_RELEASE_UNIT10; + + SET_FLAG(fdoExtension->PrivateFdoData->HackFlags, FDO_HACK_NO_RESERVE6); + retry = TRUE; + } + } + +#if DBG + + // + // Ensure that for read/write requests, only return STATUS_DEVICE_BUSY if + // reservation conflict. + // + if (IS_SCSIOP_READWRITE(cdbOpcode) && (*Status == STATUS_DEVICE_BUSY)) { + NT_ASSERT(isReservationConflict == TRUE); + } + +#endif + + /* + * LOG the error: + * If logErrorInternal is set, log the error in our internal log. + * If logError is set, also log the error in the system log. + */ + if (logErrorInternal || logError) { + ULONG totalSize; + ULONG senseBufferSize = 0; + IO_ERROR_LOG_PACKET staticErrLogEntry = {0}; + CLASS_ERROR_LOG_DATA staticErrLogData = {0}; + SENSE_DATA convertedSenseBuffer = {0}; + UCHAR convertedSenseBufferLength = 0; + BOOLEAN senseDataConverted = FALSE; + + // + // Logic below assumes that IO_ERROR_LOG_PACKET + CLASS_ERROR_LOG_DATA + // is less than ERROR_LOG_MAXIMUM_SIZE which is not true for extended SRB. + // Given that classpnp currently does not use >16 byte CDB, we'll convert + // an extended SRB to SCSI_REQUEST_BLOCK instead of changing this code. + // More changes will need to be made when classpnp starts using >16 byte + // CDBs. + // + + // + // Calculate the total size of the error log entry. + // add to totalSize in the order that they are used. + // the advantage to calculating all the sizes here is + // that we don't have to do a bunch of extraneous checks + // later on in this code path. + // + totalSize = sizeof(IO_ERROR_LOG_PACKET) // required + + sizeof(CLASS_ERROR_LOG_DATA);// struct for ease + + // + // also save any available extra sense data, up to the maximum errlog + // packet size . WMI should be used for real-time analysis. + // the event log should only be used for post-mortem debugging. + // + if ((TEST_FLAG(Srb->SrbStatus, SRB_STATUS_AUTOSENSE_VALID)) && senseBuffer) { + + UCHAR validSenseBytes = 0; + UCHAR senseInfoBufferLength = 0; + + senseInfoBufferLength = SrbGetSenseInfoBufferLength(Srb); + + // + // If sense data is in Descriptor format, convert it to Fixed format + // for the private log. + // + + if (IsDescriptorSenseDataFormat(senseBuffer)) { + + convertedSenseBufferLength = sizeof(convertedSenseBuffer); + + senseDataConverted = ScsiConvertToFixedSenseFormat(senseBuffer, + senseInfoBufferLength, + (PVOID)&convertedSenseBuffer, + convertedSenseBufferLength); + } + + // + // For System Log, copy the maximum amount of available sense data + // + + if (ScsiGetTotalSenseByteCountIndicated(senseBuffer, + senseInfoBufferLength, + &validSenseBytes)) { + + // + // If it is able to determine number of valid bytes, + // copy the maximum amount of available + // sense data that can be saved into the the errlog. + // + + // + // set to save the most sense buffer possible + // + + senseBufferSize = max(validSenseBytes, sizeof(staticErrLogData.SenseData)); + senseBufferSize = min(senseBufferSize, senseInfoBufferLength); + + } else { + // + // it's smaller than required to read the total number of + // valid bytes, so just use the SenseInfoBufferLength field. + // + senseBufferSize = senseInfoBufferLength; + } + + /* + * Bump totalSize by the number of extra senseBuffer bytes + * (beyond the default sense buffer within CLASS_ERROR_LOG_DATA). + * Make sure to never allocate more than ERROR_LOG_MAXIMUM_SIZE. + */ + if (senseBufferSize > sizeof(staticErrLogData.SenseData)){ + totalSize += senseBufferSize-sizeof(staticErrLogData.SenseData); + if (totalSize > ERROR_LOG_MAXIMUM_SIZE){ + senseBufferSize -= totalSize-ERROR_LOG_MAXIMUM_SIZE; + totalSize = ERROR_LOG_MAXIMUM_SIZE; + } + } + } + + // + // If we've used up all of our retry attempts, set the final status to + // reflect the appropriate result. + // + // ISSUE: the test below should also check RetryCount to determine if we will actually retry, + // but there is no easy test because we'd have to consider the original retry count + // for the op; besides, InterpretTransferPacketError sometimes ignores the retry + // decision returned by this function. So just ErrorRetried to be true in the majority case. + // + if (retry){ + staticErrLogEntry.FinalStatus = STATUS_SUCCESS; + staticErrLogData.ErrorRetried = TRUE; + } else { + staticErrLogEntry.FinalStatus = *Status; + } + + // + // Don't log generic IO_WARNING_PAGING_FAILURE message if either the + // I/O is retried, or it completed successfully. + // + if (logStatus == IO_WARNING_PAGING_FAILURE && + (retry || NT_SUCCESS(*Status)) ) { + logError = FALSE; + } + + if (TEST_FLAG(SrbGetSrbFlags(Srb), SRB_CLASS_FLAGS_PAGING)) { + staticErrLogData.ErrorPaging = TRUE; + } + if (unhandledError) { + staticErrLogData.ErrorUnhandled = TRUE; + } + + // + // Calculate the device offset if there is a geometry. + // + staticErrLogEntry.DeviceOffset.QuadPart = (LONGLONG)badSector; + staticErrLogEntry.DeviceOffset.QuadPart *= (LONGLONG)fdoExtension->DiskGeometry.BytesPerSector; + if (logStatus == -1){ + staticErrLogEntry.ErrorCode = STATUS_IO_DEVICE_ERROR; + } else { + staticErrLogEntry.ErrorCode = logStatus; + } + + /* + * The dump data follows the IO_ERROR_LOG_PACKET + */ + staticErrLogEntry.DumpDataSize = (USHORT)totalSize - sizeof(IO_ERROR_LOG_PACKET); + + staticErrLogEntry.SequenceNumber = 0; + staticErrLogEntry.MajorFunctionCode = MajorFunctionCode; + staticErrLogEntry.IoControlCode = IoDeviceCode; + staticErrLogEntry.RetryCount = (UCHAR) RetryCount; + staticErrLogEntry.UniqueErrorValue = uniqueId; + + KeQueryTickCount(&staticErrLogData.TickCount); + staticErrLogData.PortNumber = (ULONG)-1; + + /* + * Save the entire contents of the SRB. + */ + if (Srb->Function == SRB_FUNCTION_STORAGE_REQUEST_BLOCK) { + ClasspConvertToScsiRequestBlock(&staticErrLogData.Srb, (PSTORAGE_REQUEST_BLOCK)Srb); + } else { + staticErrLogData.Srb = *(PSCSI_REQUEST_BLOCK)Srb; + } + + /* + * For our private log, save just the default length of the SENSE_DATA. + */ + + if ((senseBufferSize != 0) && senseBuffer) { + + // + // If sense buffer is in Fixed format, put it in the private log + // + // If sense buffer is in Descriptor format, put it in the private log if conversion to Fixed format + // succeeded. Otherwise, do not put it in the private log. + // + // If sense buffer is in unknown format, the device or the driver probably does not populate + // the first byte of sense data, we probably still want to log error in this case assuming + // it's fixed format, so that its sense key, its additional sense code, and its additional sense code + // qualifier would be shown in the debugger extension output. By doing so, it minimizes any potential + // negative impacts to our ability to diagnose issue. + // + if (IsDescriptorSenseDataFormat(senseBuffer)) { + if (senseDataConverted) { + RtlCopyMemory(&staticErrLogData.SenseData, &convertedSenseBuffer, min(convertedSenseBufferLength, sizeof(staticErrLogData.SenseData))); + } + } else { + RtlCopyMemory(&staticErrLogData.SenseData, senseBuffer, min(senseBufferSize, sizeof(staticErrLogData.SenseData))); + } + } + + /* + * Save the error log in our context. + * We only save the default sense buffer length. + */ + if (logErrorInternal) { + KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql); + fdoData->ErrorLogs[fdoData->ErrorLogNextIndex] = staticErrLogData; + fdoData->ErrorLogNextIndex++; + fdoData->ErrorLogNextIndex %= NUM_ERROR_LOG_ENTRIES; + KeReleaseSpinLock(&fdoData->SpinLock, oldIrql); + } + + /* + * Log an event if an IO is being retried for reasons that may indicate + * a transient/permanent problem with the I_T_L nexus. But log the event + * only once per retried IO. + */ + if (!IS_SCSIOP_READWRITE(cdbOpcode) || + !retry || + (RetryCount != 0)) { + + logRetryableError = FALSE; + } + + if (logRetryableError) { + + logError = TRUE; + } + + /* + * If logError is set, also save this log in the system's error log. + * But make sure we don't log TUR failures over and over + * (e.g. if an external drive was switched off and we're still sending TUR's to it every second). + */ + + if (logError) + { + // + // We do not want to log certain system events repetitively + // + + cdb = SrbGetCdb(Srb); + if (cdb) { + switch (cdb->CDB10.OperationCode) + { + case SCSIOP_TEST_UNIT_READY: + { + if (fdoData->LoggedTURFailureSinceLastIO) + { + logError = FALSE; + } + else + { + fdoData->LoggedTURFailureSinceLastIO = TRUE; + } + + break; + } + + case SCSIOP_SYNCHRONIZE_CACHE: + { + if (fdoData->LoggedSYNCFailure) + { + logError = FALSE; + } + else + { + fdoData->LoggedSYNCFailure = TRUE; + } + + break; + } + } + } + } + + if (logError){ + + if (logRetryableError) { + + NT_ASSERT(IS_SCSIOP_READWRITE(cdbOpcode)); + + // + // A large Disk TimeOutValue (like 60 seconds) results in giving a command a + // large window to complete in. However, if the target returns a retryable error + // just prior to the command timing out, and if multiple retries kick in, it may + // take a significantly long time for the request to complete back to the + // application, leading to a user perception of a hung system. So log an event + // for retried IO so that an admin can help explain the reason for this behavior. + // + ClasspQueueLogIOEventWithContextWorker(Fdo, + senseBufferSize, + senseBuffer, + SRB_STATUS(Srb->SrbStatus), + SrbGetScsiStatus(Srb), + (ULONG)IO_WARNING_IO_OPERATION_RETRIED, + cdbLength, + cdb, + NULL); + + } else { + + PIO_ERROR_LOG_PACKET errorLogEntry; + PCLASS_ERROR_LOG_DATA errlogData; + + errorLogEntry = (PIO_ERROR_LOG_PACKET)IoAllocateErrorLogEntry(Fdo, (UCHAR)totalSize); + if (errorLogEntry){ + errlogData = (PCLASS_ERROR_LOG_DATA)errorLogEntry->DumpData; + + *errorLogEntry = staticErrLogEntry; + *errlogData = staticErrLogData; + + /* + * For the system log, copy as much of the sense buffer as possible. + */ + if ((senseBufferSize != 0) && senseBuffer) { + RtlCopyMemory(&errlogData->SenseData, senseBuffer, senseBufferSize); + } + + /* + * Write the error log packet to the system error logging thread. + * It will be freed by the kernel. + */ + IoWriteErrorLogEntry(errorLogEntry); + } + } + } + } + + return retry; + +} // end ClassInterpretSenseInfo() + + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassModeSense() + +Routine Description: + + This routine sends a mode sense command to a target ID and returns + when it is complete. + +Arguments: + + Fdo - Supplies the functional device object associated with this request. + + ModeSenseBuffer - Supplies a buffer to store the sense data. + + Length - Supplies the length in bytes of the mode sense buffer. + + PageMode - Supplies the page or pages of mode sense data to be retrived. + +Return Value: + + Length of the transferred data is returned. + +--*/ +ULONG ClassModeSense( + _In_ PDEVICE_OBJECT Fdo, + _In_reads_bytes_(Length) PCHAR ModeSenseBuffer, + _In_ ULONG Length, + _In_ UCHAR PageMode + ) +{ + PAGED_CODE(); + + return ClasspModeSense(Fdo, + ModeSenseBuffer, + Length, + PageMode, + MODE_SENSE_CURRENT_VALUES); +} + + +ULONG +ClassModeSenseEx( + _In_ PDEVICE_OBJECT Fdo, + _In_reads_bytes_(Length) PCHAR ModeSenseBuffer, + _In_ ULONG Length, + _In_ UCHAR PageMode, + _In_ UCHAR PageControl + ) +{ + PAGED_CODE(); +#if (NTDDI_VERSION >= NTDDI_WINBLUE) + return ClasspModeSense(Fdo, + ModeSenseBuffer, + Length, + PageMode, + PageControl); +#else + UNREFERENCED_PARAMETER(Fdo); + UNREFERENCED_PARAMETER(ModeSenseBuffer); + UNREFERENCED_PARAMETER(Length); + UNREFERENCED_PARAMETER(PageMode); + UNREFERENCED_PARAMETER(PageControl); + return 0; +#endif +} + +ULONG ClasspModeSense( + _In_ PDEVICE_OBJECT Fdo, + _In_reads_bytes_(Length) PCHAR ModeSenseBuffer, + _In_ ULONG Length, + _In_ UCHAR PageMode, + _In_ UCHAR PageControl + ) +/* +Routine Description: + + This routine sends a mode sense command to a target ID and returns + when it is complete. + +Arguments: + + Fdo - Supplies the functional device object associated with this request. + + ModeSenseBuffer - Supplies a buffer to store the sense data. + + Length - Supplies the length in bytes of the mode sense buffer. + + PageMode - Supplies the page or pages of mode sense data to be retrived. + + PageControl - Supplies the page control value of the request, which is + one of the following: + MODE_SENSE_CURRENT_VALUES + MODE_SENSE_CHANGEABLE_VALUES + MODE_SENSE_DEFAULT_VAULES + MODE_SENSE_SAVED_VALUES + +Return Value: + + Length of the transferred data is returned. + +--*/ +{ + ULONG lengthTransferred = 0; + PMDL senseBufferMdl; + + PAGED_CODE(); + + senseBufferMdl = BuildDeviceInputMdl(ModeSenseBuffer, Length); + if (senseBufferMdl){ + + TRANSFER_PACKET *pkt = DequeueFreeTransferPacket(Fdo, TRUE); + if (pkt){ + KEVENT event; + IRP pseudoIrp = {0}; + + /* + * Store the number of packets servicing the irp (one) + * inside the original IRP. It will be used to counted down + * to zero when the packet completes. + * Initialize the original IRP's status to success. + * If the packet fails, we will set it to the error status. + */ + pseudoIrp.Tail.Overlay.DriverContext[0] = LongToPtr(1); + pseudoIrp.IoStatus.Status = STATUS_SUCCESS; + pseudoIrp.IoStatus.Information = 0; + pseudoIrp.MdlAddress = senseBufferMdl; + + /* + * Set this up as a SYNCHRONOUS transfer, submit it, + * and wait for the packet to complete. The result + * status will be written to the original irp. + */ + NT_ASSERT(Length <= 0x0ff); + KeInitializeEvent(&event, SynchronizationEvent, FALSE); + SetupModeSenseTransferPacket(pkt, &event, ModeSenseBuffer, (UCHAR)Length, PageMode, 0, &pseudoIrp, PageControl); + SubmitTransferPacket(pkt); + (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); + + if (NT_SUCCESS(pseudoIrp.IoStatus.Status)){ + lengthTransferred = (ULONG)pseudoIrp.IoStatus.Information; + } + else { + /* + * This request can sometimes fail legitimately + * (e.g. when a SCSI device is attached but turned off) + * so this is not necessarily a device/driver bug. + */ + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClasspModeSense on Fdo %ph failed with status %xh.", Fdo, pseudoIrp.IoStatus.Status)); + } + } + + FreeDeviceInputMdl(senseBufferMdl); + } + + return lengthTransferred; +} + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassFindModePage() + +Routine Description: + + This routine scans through the mode sense data and finds the requested + mode sense page code. + +Arguments: + ModeSenseBuffer - Supplies a pointer to the mode sense data. + + Length - Indicates the length of valid data. + + PageMode - Supplies the page mode to be searched for. + + Use6Byte - Indicates whether 6 or 10 byte mode sense was used. + +Return Value: + + A pointer to the the requested mode page. If the mode page was not found + then NULL is return. + +--*/ +PVOID +ClassFindModePage( + _In_reads_bytes_(Length) PCHAR ModeSenseBuffer, + _In_ ULONG Length, + _In_ UCHAR PageMode, + _In_ BOOLEAN Use6Byte + ) +{ + PUCHAR limit; + ULONG parameterHeaderLength; + PVOID result = NULL; + + limit = (PUCHAR)ModeSenseBuffer + Length; + parameterHeaderLength = (Use6Byte) ? sizeof(MODE_PARAMETER_HEADER) : sizeof(MODE_PARAMETER_HEADER10); + + if (Length >= parameterHeaderLength) { + + PMODE_PARAMETER_HEADER10 modeParam10; + ULONG blockDescriptorLength; + + /* + * Skip the mode select header and block descriptors. + */ + if (Use6Byte){ + blockDescriptorLength = ((PMODE_PARAMETER_HEADER) ModeSenseBuffer)->BlockDescriptorLength; + } + else { + modeParam10 = (PMODE_PARAMETER_HEADER10) ModeSenseBuffer; + blockDescriptorLength = modeParam10->BlockDescriptorLength[1]; + } + + ModeSenseBuffer += parameterHeaderLength + blockDescriptorLength; + + // + // ModeSenseBuffer now points at pages. Walk the pages looking for the + // requested page until the limit is reached. + // + + while (ModeSenseBuffer + + RTL_SIZEOF_THROUGH_FIELD(MODE_DISCONNECT_PAGE, PageLength) < (PCHAR)limit) { + + if (((PMODE_DISCONNECT_PAGE) ModeSenseBuffer)->PageCode == PageMode) { + + /* + * found the mode page. make sure it's safe to touch it all + * before returning the pointer to caller + */ + + if (ModeSenseBuffer + ((PMODE_DISCONNECT_PAGE)ModeSenseBuffer)->PageLength > (PCHAR)limit) { + /* + * Return NULL since the page is not safe to access in full + */ + result = NULL; + } + else { + result = ModeSenseBuffer; + } + break; + } + + // + // Advance to the next page which is 4-byte-aligned offset after this page. + // + ModeSenseBuffer += + ((PMODE_DISCONNECT_PAGE) ModeSenseBuffer)->PageLength + + RTL_SIZEOF_THROUGH_FIELD(MODE_DISCONNECT_PAGE, PageLength); + + } + } + + return result; +} // end ClassFindModePage() + + +NTSTATUS +ClassModeSelect( + _In_ PDEVICE_OBJECT Fdo, + _In_reads_bytes_(Length) PCHAR ModeSelectBuffer, + _In_ ULONG Length, + _In_ BOOLEAN SavePages + ) +{ +#if (NTDDI_VERSION >= NTDDI_WINBLUE) + return ClasspModeSelect(Fdo, + ModeSelectBuffer, + Length, + SavePages); +#else + UNREFERENCED_PARAMETER(Fdo); + UNREFERENCED_PARAMETER(ModeSelectBuffer); + UNREFERENCED_PARAMETER(Length); + UNREFERENCED_PARAMETER(SavePages); + return STATUS_NOT_SUPPORTED; +#endif +} + +/*++ +ClasspModeSelect() + +Routine Description: + + This routine sends a mode select command to a target ID and returns + when it is complete. + +Arguments: + + Fdo - Supplies the functional device object associated with this request. + + ModeSelectBuffer - Supplies a buffer to the select data. + + Length - Supplies the length in bytes of the mode select buffer. + + SavePages - Specifies the value of the save pages (SP) bit in the mode + select command. + +Return Value: + + NTSTATUS code of the request. + +--*/ +NTSTATUS +ClasspModeSelect( + _In_ PDEVICE_OBJECT Fdo, + _In_reads_bytes_(Length) PCHAR ModeSelectBuffer, + _In_ ULONG Length, + _In_ BOOLEAN SavePages + ) +{ + + PMDL senseBufferMdl; + NTSTATUS status = STATUS_UNSUCCESSFUL; + + senseBufferMdl = BuildDeviceInputMdl(ModeSelectBuffer, Length); + if (senseBufferMdl) { + + TRANSFER_PACKET *pkt = DequeueFreeTransferPacket(Fdo, TRUE); + if (pkt){ + KEVENT event; + IRP pseudoIrp = {0}; + + /* + * Store the number of packets servicing the irp (one) + * inside the original IRP. It will be used to counted down + * to zero when the packet completes. + * Initialize the original IRP's status to success. + * If the packet fails, we will set it to the error status. + */ + pseudoIrp.Tail.Overlay.DriverContext[0] = LongToPtr(1); + pseudoIrp.IoStatus.Status = STATUS_SUCCESS; + pseudoIrp.IoStatus.Information = 0; + pseudoIrp.MdlAddress = senseBufferMdl; + + /* + * Set this up as a SYNCHRONOUS transfer, submit it, + * and wait for the packet to complete. The result + * status will be written to the original irp. + */ + NT_ASSERT(Length <= 0x0ff); + KeInitializeEvent(&event, SynchronizationEvent, FALSE); + SetupModeSelectTransferPacket(pkt, &event, ModeSelectBuffer, (UCHAR)Length, SavePages, &pseudoIrp); + SubmitTransferPacket(pkt); + (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); + + if (!NT_SUCCESS(pseudoIrp.IoStatus.Status)){ + /* + * This request can sometimes fail legitimately + * (e.g. when a SCSI device is attached but turned off) + * so this is not necessarily a device/driver bug. + */ + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "ClassModeSelect on Fdo %ph failed with status %xh.", Fdo, pseudoIrp.IoStatus.Status)); + } + + status = pseudoIrp.IoStatus.Status; + } + + FreeDeviceInputMdl(senseBufferMdl); + } else { + status = STATUS_INSUFFICIENT_RESOURCES; + } + + return status; +} + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassSendSrbAsynchronous() + +Routine Description: + + This routine takes a partially built Srb and an Irp and sends it down to + the port driver. + + This routine must be called with the remove lock held for the specified + Irp. + +Arguments: + + Fdo - Supplies the functional device object for the orginal request. + + Srb - Supplies a paritally build ScsiRequestBlock. In particular, the + CDB and the SRB timeout value must be filled in. The SRB must not be + allocated from zone. + + Irp - Supplies the requesting Irp. + + BufferAddress - Supplies a pointer to the buffer to be transfered. + + BufferLength - Supplies the length of data transfer. + + WriteToDevice - Indicates the data transfer will be from system memory to + device. + +Return Value: + + Returns STATUS_PENDING if the request is dispatched (since the + completion routine may change the irp's status value we cannot simply + return the value of the dispatch) + + or returns a status value to indicate why it failed. + +--*/ +_Success_(return == STATUS_PENDING) +NTSTATUS +ClassSendSrbAsynchronous( + _In_ PDEVICE_OBJECT Fdo, + _Inout_ __on_failure(__drv_freesMem(Mem)) __drv_aliasesMem PSCSI_REQUEST_BLOCK _Srb, + _In_ PIRP Irp, + _In_reads_bytes_opt_(BufferLength) __drv_aliasesMem PVOID BufferAddress, + _In_ ULONG BufferLength, + _In_ BOOLEAN WriteToDevice + ) +{ + + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension; + PIO_STACK_LOCATION irpStack; + PSTORAGE_REQUEST_BLOCK_HEADER Srb = (PSTORAGE_REQUEST_BLOCK_HEADER)_Srb; + + ULONG savedFlags; + + if (Srb->Function != SRB_FUNCTION_STORAGE_REQUEST_BLOCK) { + // + // Write length to SRB. + // + + Srb->Length = sizeof(SCSI_REQUEST_BLOCK); + + // + // Set SCSI bus address. + // + + Srb->Function = SRB_FUNCTION_EXECUTE_SCSI; + } + + // + // This is a violation of the SCSI spec but it is required for + // some targets. + // + + // Srb->Cdb[1] |= deviceExtension->Lun << 5; + + // + // Indicate auto request sense by specifying buffer and size. + // + + SrbSetSenseInfoBuffer(Srb, fdoExtension->SenseData); + SrbSetSenseInfoBufferLength(Srb, GET_FDO_EXTENSON_SENSE_DATA_LENGTH(fdoExtension)); + + SrbSetDataBuffer(Srb, BufferAddress); + + // + // Set the transfer length. + // + SrbSetDataTransferLength(Srb, BufferLength); + + // + // Save the class driver specific flags away. + // + + savedFlags = SrbGetSrbFlags(Srb) & SRB_FLAGS_CLASS_DRIVER_RESERVED; + + // + // Allow the caller to specify that they do not wish + // IoStartNextPacket() to be called in the completion routine. + // + + SET_FLAG(savedFlags, (SrbGetSrbFlags(Srb) & SRB_FLAGS_DONT_START_NEXT_PACKET)); + + // + // If caller wants to this request to be tagged, save this fact. + // + + if ( TEST_FLAG(SrbGetSrbFlags(Srb), SRB_FLAGS_QUEUE_ACTION_ENABLE) && + ( SRB_SIMPLE_TAG_REQUEST == SrbGetRequestAttribute(Srb) || + SRB_HEAD_OF_QUEUE_TAG_REQUEST == SrbGetRequestAttribute(Srb) || + SRB_ORDERED_QUEUE_TAG_REQUEST == SrbGetRequestAttribute(Srb) ) ) { + + SET_FLAG(savedFlags, SRB_FLAGS_QUEUE_ACTION_ENABLE); + if (TEST_FLAG(SrbGetSrbFlags(Srb), SRB_FLAGS_NO_QUEUE_FREEZE)) { + SET_FLAG(savedFlags, SRB_FLAGS_NO_QUEUE_FREEZE); + } + } + + if (BufferAddress != NULL) { + + // + // Build Mdl if necessary. + // + + if (Irp->MdlAddress == NULL) { + + PMDL mdl; + + mdl = IoAllocateMdl(BufferAddress, + BufferLength, + FALSE, + FALSE, + Irp); + + if ((mdl == NULL) || (Irp->MdlAddress == NULL)) { + + Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES; + + // + // ClassIoComplete() would have free'd the srb + // + + if (PORT_ALLOCATED_SENSE_EX(fdoExtension, Srb)) { + FREE_PORT_ALLOCATED_SENSE_BUFFER_EX(fdoExtension, Srb); + } + ClassFreeOrReuseSrb(fdoExtension, (PSCSI_REQUEST_BLOCK)Srb); + ClassReleaseRemoveLock(Fdo, Irp); + ClassCompleteRequest(Fdo, Irp, IO_NO_INCREMENT); + + return STATUS_INSUFFICIENT_RESOURCES; + } + + SET_FLAG(savedFlags, SRB_CLASS_FLAGS_FREE_MDL); + + MmBuildMdlForNonPagedPool(Irp->MdlAddress); + + } else { + + // + // Make sure the buffer requested matches the MDL. + // + + NT_ASSERT(BufferAddress == MmGetMdlVirtualAddress(Irp->MdlAddress)); + } + + // + // Set read flag. + // + + SrbAssignSrbFlags(Srb, WriteToDevice ? SRB_FLAGS_DATA_OUT : SRB_FLAGS_DATA_IN); + + } else { + + // + // Clear flags. + // + + SrbAssignSrbFlags(Srb, SRB_FLAGS_NO_DATA_TRANSFER); + } + + // + // Restore saved flags. + // + + SrbSetSrbFlags(Srb, savedFlags); + + // + // Disable synchronous transfer for these requests. + // + + SrbSetSrbFlags(Srb, SRB_FLAGS_DISABLE_SYNCH_TRANSFER); + + // + // Zero out status. + // + + SrbSetScsiStatus(Srb, 0); + Srb->SrbStatus = 0; + + SrbSetNextSrb(Srb, NULL); + + // + // Save a few parameters in the current stack location. + // + + irpStack = IoGetCurrentIrpStackLocation(Irp); + + // + // Save retry count in current Irp stack. + // + + irpStack->Parameters.Others.Argument4 = (PVOID)MAXIMUM_RETRIES; + + // + // Set up IoCompletion routine address. + // + + IoSetCompletionRoutine(Irp, ClassIoComplete, Srb, TRUE, TRUE, TRUE); + + // + // Get next stack location and + // set major function code. + // + + irpStack = IoGetNextIrpStackLocation(Irp); + + irpStack->MajorFunction = IRP_MJ_SCSI; + + // + // Save SRB address in next stack for port driver. + // + + irpStack->Parameters.Scsi.Srb = (PSCSI_REQUEST_BLOCK)Srb; + + // + // Set up Irp Address. + // + + SrbSetOriginalRequest(Srb, Irp); + + // + // Call the port driver to process the request. + // + + IoMarkIrpPending(Irp); + + IoCallDriver(fdoExtension->CommonExtension.LowerDeviceObject, Irp); + + return STATUS_PENDING; + +} // end ClassSendSrbAsynchronous() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassDeviceControlDispatch() + +Routine Description: + + The routine is the common class driver device control dispatch entry point. + This routine is invokes the device-specific drivers DeviceControl routine, + (which may call the Class driver's common DeviceControl routine). + +Arguments: + + DeviceObject - Supplies a pointer to the device object for this request. + + Irp - Supplies the Irp making the request. + +Return Value: + + Returns the status returned from the device-specific driver. + +--*/ +NTSTATUS +ClassDeviceControlDispatch( + PDEVICE_OBJECT DeviceObject, + PIRP Irp + ) +{ + + PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; + ULONG isRemoved; + + isRemoved = ClassAcquireRemoveLock(DeviceObject, Irp); + _Analysis_assume_(isRemoved); + if(isRemoved) { + + ClassReleaseRemoveLock(DeviceObject, Irp); + + Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST; + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + return STATUS_DEVICE_DOES_NOT_EXIST; + } + + // + // Call the class specific driver DeviceControl routine. + // If it doesn't handle it, it will call back into ClassDeviceControl. + // + + NT_ASSERT(commonExtension->DevInfo->ClassDeviceControl); + + return commonExtension->DevInfo->ClassDeviceControl(DeviceObject,Irp); +} // end ClassDeviceControlDispatch() + + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassDeviceControl() + +Routine Description: + + The routine is the common class driver device control dispatch function. + This routine is called by a class driver when it get an unrecognized + device control request. This routine will perform the correct action for + common requests such as lock media. If the device request is unknown it + passed down to the next level. + + This routine must be called with the remove lock held for the specified + irp. + +Arguments: + + DeviceObject - Supplies a pointer to the device object for this request. + + Irp - Supplies the Irp making the request. + +Return Value: + + Returns back a STATUS_PENDING or a completion status. + +--*/ +NTSTATUS +ClassDeviceControl( + _In_ PDEVICE_OBJECT DeviceObject, + _Inout_ PIRP Irp + ) +{ + PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; + + PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp); + PIO_STACK_LOCATION nextStack = NULL; + + ULONG controlCode = irpStack->Parameters.DeviceIoControl.IoControlCode; + + PSCSI_REQUEST_BLOCK srb = NULL; + PCDB cdb = NULL; + + NTSTATUS status; + ULONG modifiedIoControlCode = 0; + + + // + // If this is a pass through I/O control, set the minor function code + // and device address and pass it to the port driver. + // + + if ( (controlCode == IOCTL_SCSI_PASS_THROUGH) || + (controlCode == IOCTL_SCSI_PASS_THROUGH_DIRECT) || + (controlCode == IOCTL_SCSI_PASS_THROUGH_EX) || + (controlCode == IOCTL_SCSI_PASS_THROUGH_DIRECT_EX) ) { + + + + // + // Validiate the user buffer for SCSI pass through. + // For pass through EX: as the handler will validate the size anyway, + // do not apply the similar check and leave the work to the handler. + // + if ( (controlCode == IOCTL_SCSI_PASS_THROUGH) || + (controlCode == IOCTL_SCSI_PASS_THROUGH_DIRECT) ) { + + #if BUILD_WOW64_ENABLED && defined(_WIN64) + + if (IoIs32bitProcess(Irp)) { + + if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(SCSI_PASS_THROUGH32)){ + + Irp->IoStatus.Status = STATUS_INVALID_PARAMETER; + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + + status = STATUS_INVALID_PARAMETER; + goto SetStatusAndReturn; + } + } + else + + #endif + + { + if (irpStack->Parameters.DeviceIoControl.InputBufferLength < + sizeof(SCSI_PASS_THROUGH)) { + + Irp->IoStatus.Status = STATUS_INVALID_PARAMETER; + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + + status = STATUS_INVALID_PARAMETER; + goto SetStatusAndReturn; + } + } + } + + + IoCopyCurrentIrpStackLocationToNext(Irp); + + nextStack = IoGetNextIrpStackLocation(Irp); + nextStack->MinorFunction = 1; + + ClassReleaseRemoveLock(DeviceObject, Irp); + + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + goto SetStatusAndReturn; + + } + + Irp->IoStatus.Information = 0; + + + switch (controlCode) { + + case IOCTL_MOUNTDEV_QUERY_UNIQUE_ID: { + + PMOUNTDEV_UNIQUE_ID uniqueId; + + if (!commonExtension->MountedDeviceInterfaceName.Buffer) { + status = STATUS_INVALID_PARAMETER; + break; + } + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < + sizeof(MOUNTDEV_UNIQUE_ID)) { + + status = STATUS_BUFFER_TOO_SMALL; + Irp->IoStatus.Information = sizeof(MOUNTDEV_UNIQUE_ID); + break; + } + + uniqueId = Irp->AssociatedIrp.SystemBuffer; + RtlZeroMemory(uniqueId, sizeof(MOUNTDEV_UNIQUE_ID)); + uniqueId->UniqueIdLength = + commonExtension->MountedDeviceInterfaceName.Length; + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < + sizeof(USHORT) + uniqueId->UniqueIdLength) { + + status = STATUS_BUFFER_OVERFLOW; + Irp->IoStatus.Information = sizeof(MOUNTDEV_UNIQUE_ID); + break; + } + + RtlCopyMemory(uniqueId->UniqueId, + commonExtension->MountedDeviceInterfaceName.Buffer, + uniqueId->UniqueIdLength); + + status = STATUS_SUCCESS; + Irp->IoStatus.Information = sizeof(USHORT) + + uniqueId->UniqueIdLength; + break; + } + + case IOCTL_MOUNTDEV_QUERY_DEVICE_NAME: { + + PMOUNTDEV_NAME name; + + NT_ASSERT(commonExtension->DeviceName.Buffer); + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < + sizeof(MOUNTDEV_NAME)) { + + status = STATUS_BUFFER_TOO_SMALL; + Irp->IoStatus.Information = sizeof(MOUNTDEV_NAME); + break; + } + + name = Irp->AssociatedIrp.SystemBuffer; + RtlZeroMemory(name, sizeof(MOUNTDEV_NAME)); + name->NameLength = commonExtension->DeviceName.Length; + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < + sizeof(USHORT) + name->NameLength) { + + status = STATUS_BUFFER_OVERFLOW; + Irp->IoStatus.Information = sizeof(MOUNTDEV_NAME); + break; + } + + RtlCopyMemory(name->Name, commonExtension->DeviceName.Buffer, + name->NameLength); + + status = STATUS_SUCCESS; + Irp->IoStatus.Information = sizeof(USHORT) + name->NameLength; + break; + } + + case IOCTL_MOUNTDEV_QUERY_SUGGESTED_LINK_NAME: { + + PMOUNTDEV_SUGGESTED_LINK_NAME suggestedName; + WCHAR driveLetterNameBuffer[10] = {0}; + RTL_QUERY_REGISTRY_TABLE queryTable[2] = {0}; + PWSTR valueName; + UNICODE_STRING driveLetterName; + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < + sizeof(MOUNTDEV_SUGGESTED_LINK_NAME)) { + + status = STATUS_BUFFER_TOO_SMALL; + Irp->IoStatus.Information = sizeof(MOUNTDEV_SUGGESTED_LINK_NAME); + break; + } + + valueName = ExAllocatePoolWithTag( + PagedPool, + commonExtension->DeviceName.Length + sizeof(WCHAR), + '8CcS'); + + if (!valueName) { + status = STATUS_INSUFFICIENT_RESOURCES; + break; + } + + RtlCopyMemory(valueName, commonExtension->DeviceName.Buffer, + commonExtension->DeviceName.Length); + valueName[commonExtension->DeviceName.Length/sizeof(WCHAR)] = 0; + + driveLetterName.Buffer = driveLetterNameBuffer; + driveLetterName.MaximumLength = sizeof(driveLetterNameBuffer); + driveLetterName.Length = 0; + + queryTable[0].Flags = RTL_QUERY_REGISTRY_REQUIRED | + RTL_QUERY_REGISTRY_DIRECT; + queryTable[0].Name = valueName; + queryTable[0].EntryContext = &driveLetterName; + + status = RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE, + L"\\Registry\\Machine\\System\\DISK", + queryTable, NULL, NULL); + + if (!NT_SUCCESS(status)) { + FREE_POOL(valueName); + break; + } + + if (driveLetterName.Length == 4 && + driveLetterName.Buffer[0] == '%' && + driveLetterName.Buffer[1] == ':') { + + driveLetterName.Buffer[0] = 0xFF; + + } else if (driveLetterName.Length != 4 || + driveLetterName.Buffer[0] < FirstDriveLetter || + driveLetterName.Buffer[0] > LastDriveLetter || + driveLetterName.Buffer[1] != ':') { + + status = STATUS_NOT_FOUND; + FREE_POOL(valueName); + break; + } + + suggestedName = Irp->AssociatedIrp.SystemBuffer; + RtlZeroMemory(suggestedName, sizeof(MOUNTDEV_SUGGESTED_LINK_NAME)); + suggestedName->UseOnlyIfThereAreNoOtherLinks = TRUE; + suggestedName->NameLength = 28; + + Irp->IoStatus.Information = + FIELD_OFFSET(MOUNTDEV_SUGGESTED_LINK_NAME, Name) + 28; + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < + Irp->IoStatus.Information) { + + Irp->IoStatus.Information = + sizeof(MOUNTDEV_SUGGESTED_LINK_NAME); + status = STATUS_BUFFER_OVERFLOW; + FREE_POOL(valueName); + break; + } + + RtlDeleteRegistryValue(RTL_REGISTRY_ABSOLUTE, + L"\\Registry\\Machine\\System\\DISK", + valueName); + + FREE_POOL(valueName); + + RtlCopyMemory(suggestedName->Name, L"\\DosDevices\\", 24); + suggestedName->Name[12] = driveLetterName.Buffer[0]; + suggestedName->Name[13] = ':'; + + // + // NT_SUCCESS(status) based on RtlQueryRegistryValues + // + status = STATUS_SUCCESS; + + break; + } + + default: + status = STATUS_PENDING; + break; + } + + if (status != STATUS_PENDING) { + ClassReleaseRemoveLock(DeviceObject, Irp); + Irp->IoStatus.Status = status; + + + IoCompleteRequest(Irp, IO_NO_INCREMENT); + return status; + } + + if (commonExtension->IsFdo){ + + PULONG_PTR function; + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)commonExtension; + size_t sizeNeeded; + + // + // Allocate a SCSI SRB for handling various IOCTLs. + // NOTE - there is a case where an IOCTL is sent to classpnp before AdapterDescriptor + // is initialized. In this case, default to legacy SRB. + // + if ((fdoExtension->AdapterDescriptor != NULL) && + (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK)) { + sizeNeeded = CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE; + } else { + sizeNeeded = sizeof(SCSI_REQUEST_BLOCK); + } + + srb = ExAllocatePoolWithTag(NonPagedPoolNx, + sizeNeeded + + (sizeof(ULONG_PTR) * 2), + '9CcS'); + + if (srb == NULL) { + + Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES; + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + status = STATUS_INSUFFICIENT_RESOURCES; + goto SetStatusAndReturn; + } + + if ((fdoExtension->AdapterDescriptor != NULL) && + (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK)) { + status = InitializeStorageRequestBlock((PSTORAGE_REQUEST_BLOCK)srb, + STORAGE_ADDRESS_TYPE_BTL8, + CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE, + 1, + SrbExDataTypeScsiCdb16); + if (NT_SUCCESS(status)) { + ((PSTORAGE_REQUEST_BLOCK)srb)->SrbFunction = SRB_FUNCTION_EXECUTE_SCSI; + function = (PULONG_PTR)((PCHAR)srb + sizeNeeded); + } else { + // + // Should not occur. + // + NT_ASSERT(FALSE); + goto SetStatusAndReturn; + } + } else { + RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK)); + srb->Length = sizeof(SCSI_REQUEST_BLOCK); + srb->Function = SRB_FUNCTION_EXECUTE_SCSI; + function = (PULONG_PTR) ((PSCSI_REQUEST_BLOCK) (srb + 1)); + } + + // + // Save the function code and the device object in the memory after + // the SRB. + // + + *function = (ULONG_PTR) DeviceObject; + function++; + *function = (ULONG_PTR) controlCode; + + } else { + srb = NULL; + } + + // + // Change the device type to storage for the switch statement, but only + // if from a legacy device type + // + + if (((controlCode & 0xffff0000) == (IOCTL_DISK_BASE << 16)) || + ((controlCode & 0xffff0000) == (IOCTL_TAPE_BASE << 16)) || + ((controlCode & 0xffff0000) == (IOCTL_CDROM_BASE << 16)) + ) { + + modifiedIoControlCode = (controlCode & ~0xffff0000); + modifiedIoControlCode |= (IOCTL_STORAGE_BASE << 16); + + } else { + + modifiedIoControlCode = controlCode; + + } + + TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_GENERAL, "> ioctl %xh (%s)", modifiedIoControlCode, DBGGETIOCTLSTR(modifiedIoControlCode))); + + + switch (modifiedIoControlCode) { + + case IOCTL_STORAGE_GET_HOTPLUG_INFO: { + + FREE_POOL(srb); + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < + sizeof(STORAGE_HOTPLUG_INFO)) { + + // + // Indicate unsuccessful status and no data transferred. + // + + Irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL; + Irp->IoStatus.Information = sizeof(STORAGE_HOTPLUG_INFO); + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + status = STATUS_BUFFER_TOO_SMALL; + + } else if (!commonExtension->IsFdo) { + + + // + // Just forward this down and return + // + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + + } else { + + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension; + PSTORAGE_HOTPLUG_INFO info; + + fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)commonExtension; + info = Irp->AssociatedIrp.SystemBuffer; + + *info = fdoExtension->PrivateFdoData->HotplugInfo; + Irp->IoStatus.Status = STATUS_SUCCESS; + Irp->IoStatus.Information = sizeof(STORAGE_HOTPLUG_INFO); + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + status = STATUS_SUCCESS; + } + break; + } + + case IOCTL_STORAGE_SET_HOTPLUG_INFO: { + + FREE_POOL(srb); + + if (irpStack->Parameters.DeviceIoControl.InputBufferLength < + sizeof(STORAGE_HOTPLUG_INFO)) { + + // + // Indicate unsuccessful status and no data transferred. + // + + Irp->IoStatus.Status = STATUS_INFO_LENGTH_MISMATCH; + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + status = STATUS_INFO_LENGTH_MISMATCH; + goto SetStatusAndReturn; + + } + + if (!commonExtension->IsFdo) { + + + // + // Just forward this down and return + // + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + + } else { + + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PFUNCTIONAL_DEVICE_EXTENSION)commonExtension; + PSTORAGE_HOTPLUG_INFO info = Irp->AssociatedIrp.SystemBuffer; + + status = STATUS_SUCCESS; + + if (info->Size != fdoExtension->PrivateFdoData->HotplugInfo.Size) + { + status = STATUS_INVALID_PARAMETER_1; + } + + if (info->MediaRemovable != fdoExtension->PrivateFdoData->HotplugInfo.MediaRemovable) + { + status = STATUS_INVALID_PARAMETER_2; + } + + if (info->MediaHotplug != fdoExtension->PrivateFdoData->HotplugInfo.MediaHotplug) + { + status = STATUS_INVALID_PARAMETER_3; + } + + if (NT_SUCCESS(status)) + { + if (info->WriteCacheEnableOverride != fdoExtension->PrivateFdoData->HotplugInfo.WriteCacheEnableOverride) + { + fdoExtension->PrivateFdoData->HotplugInfo.WriteCacheEnableOverride = info->WriteCacheEnableOverride; + + // + // Store the user-defined override in the registry + // + + ClassSetDeviceParameter(fdoExtension, + CLASSP_REG_SUBKEY_NAME, + CLASSP_REG_WRITE_CACHE_VALUE_NAME, + info->WriteCacheEnableOverride); + } + + fdoExtension->PrivateFdoData->HotplugInfo.DeviceHotplug = info->DeviceHotplug; + + // + // Store the user-defined override in the registry + // + + ClassSetDeviceParameter(fdoExtension, + CLASSP_REG_SUBKEY_NAME, + CLASSP_REG_REMOVAL_POLICY_VALUE_NAME, + (info->DeviceHotplug) ? RemovalPolicyExpectSurpriseRemoval : RemovalPolicyExpectOrderlyRemoval); + } + + Irp->IoStatus.Status = status; + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + } + + break; + } + + case IOCTL_STORAGE_CHECK_VERIFY: + case IOCTL_STORAGE_CHECK_VERIFY2: { + + PIRP irp2 = NULL; + PIO_STACK_LOCATION newStack; + + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = NULL; + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "DeviceIoControl: Check verify\n")); + + // + // If a buffer for a media change count was provided, make sure it's + // big enough to hold the result + // + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength) { + + // + // If the buffer is too small to hold the media change count + // then return an error to the caller + // + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < + sizeof(ULONG)) { + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, "DeviceIoControl: media count " + "buffer too small\n")); + + Irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL; + Irp->IoStatus.Information = sizeof(ULONG); + + FREE_POOL(srb); + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + + status = STATUS_BUFFER_TOO_SMALL; + goto SetStatusAndReturn; + } + } + + if (!commonExtension->IsFdo) { + + + // + // If this is a PDO then we should just forward the request down + // + NT_ASSERT(!srb); + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + + goto SetStatusAndReturn; + + } else { + + fdoExtension = DeviceObject->DeviceExtension; + + } + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength) { + + // + // The caller has provided a valid buffer. Allocate an additional + // irp and stick the CheckVerify completion routine on it. We will + // then send this down to the port driver instead of the irp the + // caller sent in + // + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "DeviceIoControl: Check verify wants " + "media count\n")); + + // + // Allocate a new irp to send the TestUnitReady to the port driver + // + + irp2 = IoAllocateIrp((CCHAR) (DeviceObject->StackSize + 3), FALSE); + + if (irp2 == NULL) { + Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES; + Irp->IoStatus.Information = 0; + FREE_POOL(srb); + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + status = STATUS_INSUFFICIENT_RESOURCES; + goto SetStatusAndReturn; + + break; + } + + // + // Make sure to acquire the lock for the new irp. + // + + ClassAcquireRemoveLock(DeviceObject, irp2); + + irp2->Tail.Overlay.Thread = Irp->Tail.Overlay.Thread; + IoSetNextIrpStackLocation(irp2); + + // + // Set the top stack location and shove the master Irp into the + // top location + // + + newStack = IoGetCurrentIrpStackLocation(irp2); + newStack->Parameters.Others.Argument1 = Irp; + newStack->DeviceObject = DeviceObject; + + // + // Stick the check verify completion routine onto the stack + // and prepare the irp for the port driver + // + + IoSetCompletionRoutine(irp2, + ClassCheckVerifyComplete, + NULL, + TRUE, + TRUE, + TRUE); + + IoSetNextIrpStackLocation(irp2); + newStack = IoGetCurrentIrpStackLocation(irp2); + newStack->DeviceObject = DeviceObject; + newStack->MajorFunction = irpStack->MajorFunction; + newStack->MinorFunction = irpStack->MinorFunction; + newStack->Flags = irpStack->Flags; + + + // + // Mark the master irp as pending - whether the lower level + // driver completes it immediately or not this should allow it + // to go all the way back up. + // + + IoMarkIrpPending(Irp); + + Irp = irp2; + + } + + // + // Test Unit Ready + // + + SrbSetCdbLength(srb, 6); + cdb = SrbGetCdb(srb); + cdb->CDB6GENERIC.OperationCode = SCSIOP_TEST_UNIT_READY; + + // + // Set timeout value. + // + + SrbSetTimeOutValue(srb, fdoExtension->TimeOutValue); + + // + // If this was a CV2 then mark the request as low-priority so we don't + // spin up the drive just to satisfy it. + // + + if (controlCode == IOCTL_STORAGE_CHECK_VERIFY2) { + SrbSetSrbFlags(srb, SRB_CLASS_FLAGS_LOW_PRIORITY); + } + + // + // Since this routine will always hand the request to the + // port driver if there isn't a data transfer to be done + // we don't have to worry about completing the request here + // on an error + // + + // + // This routine uses a completion routine so we don't want to release + // the remove lock until then. + // + + status = ClassSendSrbAsynchronous(DeviceObject, + srb, + Irp, + NULL, + 0, + FALSE); + + break; + } + + case IOCTL_STORAGE_MEDIA_REMOVAL: + case IOCTL_STORAGE_EJECTION_CONTROL: { + + PPREVENT_MEDIA_REMOVAL mediaRemoval = Irp->AssociatedIrp.SystemBuffer; + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "DiskIoControl: ejection control\n")); + + FREE_POOL(srb); + + if (irpStack->Parameters.DeviceIoControl.InputBufferLength < + sizeof(PREVENT_MEDIA_REMOVAL)) { + + // + // Indicate unsuccessful status and no data transferred. + // + + Irp->IoStatus.Status = STATUS_INFO_LENGTH_MISMATCH; + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + status = STATUS_INFO_LENGTH_MISMATCH; + goto SetStatusAndReturn; + } + + if (!commonExtension->IsFdo) { + + + // + // Just forward this down and return + // + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + } + else { + + // i don't believe this assertion is valid. this is a request + // from user-mode, so they could request this for any device + // they want? also, we handle it properly. + // NT_ASSERT(TEST_FLAG(DeviceObject->Characteristics, FILE_REMOVABLE_MEDIA)); + status = ClasspEjectionControl( + DeviceObject, + Irp, + ((modifiedIoControlCode == + IOCTL_STORAGE_EJECTION_CONTROL) ? SecureMediaLock : + SimpleMediaLock), + mediaRemoval->PreventMediaRemoval); + + Irp->IoStatus.Status = status; + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + } + + break; + } + + case IOCTL_STORAGE_MCN_CONTROL: { + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "DiskIoControl: MCN control\n")); + + if (irpStack->Parameters.DeviceIoControl.InputBufferLength < + sizeof(PREVENT_MEDIA_REMOVAL)) { + + // + // Indicate unsuccessful status and no data transferred. + // + + Irp->IoStatus.Status = STATUS_INFO_LENGTH_MISMATCH; + Irp->IoStatus.Information = 0; + + FREE_POOL(srb); + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + status = STATUS_INFO_LENGTH_MISMATCH; + goto SetStatusAndReturn; + } + + if (!commonExtension->IsFdo) { + + + // + // Just forward this down and return + // + + FREE_POOL(srb); + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + + } else { + + // + // Call to the FDO - handle the ejection control. + // + + status = ClasspMcnControl(DeviceObject->DeviceExtension, + Irp, + srb); + } + goto SetStatusAndReturn; + } + + case IOCTL_STORAGE_RESERVE: + case IOCTL_STORAGE_RELEASE: { + + // + // Reserve logical unit. + // + + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = NULL; + + if (!commonExtension->IsFdo) { + + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + goto SetStatusAndReturn; + + } else { + fdoExtension = DeviceObject->DeviceExtension; + } + + if (TEST_FLAG(fdoExtension->PrivateFdoData->HackFlags, FDO_HACK_NO_RESERVE6)) + { + SrbSetCdbLength(srb, 10); + cdb = SrbGetCdb(srb); + cdb->CDB10.OperationCode = (modifiedIoControlCode == IOCTL_STORAGE_RESERVE) ? SCSIOP_RESERVE_UNIT10 : SCSIOP_RELEASE_UNIT10; + } + else + { + SrbSetCdbLength(srb, 6); + cdb = SrbGetCdb(srb); + cdb->CDB6GENERIC.OperationCode = (modifiedIoControlCode == IOCTL_STORAGE_RESERVE) ? SCSIOP_RESERVE_UNIT : SCSIOP_RELEASE_UNIT; + } + + // + // Set timeout value. + // + + SrbSetTimeOutValue(srb, fdoExtension->TimeOutValue); + + // + // Send reserves as tagged requests. + // + + if ( IOCTL_STORAGE_RESERVE == modifiedIoControlCode ) { + SrbSetSrbFlags(srb, SRB_FLAGS_QUEUE_ACTION_ENABLE); + SrbSetRequestAttribute(srb, SRB_SIMPLE_TAG_REQUEST); + } + + status = ClassSendSrbAsynchronous(DeviceObject, + srb, + Irp, + NULL, + 0, + FALSE); + + break; + } + + case IOCTL_STORAGE_PERSISTENT_RESERVE_IN: + case IOCTL_STORAGE_PERSISTENT_RESERVE_OUT: { + + if (!commonExtension->IsFdo) { + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + goto SetStatusAndReturn; + } + + // + // Process Persistent Reserve + // + + status = ClasspPersistentReserve(DeviceObject, Irp, srb); + + break; + + } + + case IOCTL_STORAGE_EJECT_MEDIA: + case IOCTL_STORAGE_LOAD_MEDIA: + case IOCTL_STORAGE_LOAD_MEDIA2:{ + + // + // Eject media. + // + + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = NULL; + + if (!commonExtension->IsFdo) { + + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + goto SetStatusAndReturn; + } else { + fdoExtension = DeviceObject->DeviceExtension; + } + + if (commonExtension->PagingPathCount != 0) { + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, "ClassDeviceControl: call to eject paging device - " + "failure\n")); + + status = STATUS_FILES_OPEN; + Irp->IoStatus.Status = status; + + Irp->IoStatus.Information = 0; + + FREE_POOL(srb); + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + goto SetStatusAndReturn; + } + + // + // Synchronize with ejection control and ejection cleanup code as + // well as other eject/load requests. + // + + KeEnterCriticalRegion(); + (VOID)KeWaitForSingleObject(&(fdoExtension->EjectSynchronizationEvent), + UserRequest, + KernelMode, + FALSE, + NULL); + + if (fdoExtension->ProtectedLockCount != 0) { + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, "ClassDeviceControl: call to eject protected locked " + "device - failure\n")); + + status = STATUS_DEVICE_BUSY; + Irp->IoStatus.Status = status; + Irp->IoStatus.Information = 0; + + FREE_POOL(srb); + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + + KeSetEvent(&fdoExtension->EjectSynchronizationEvent, + IO_NO_INCREMENT, + FALSE); + KeLeaveCriticalRegion(); + + goto SetStatusAndReturn; + } + + SrbSetCdbLength(srb, 6); + cdb = SrbGetCdb(srb); + + cdb->START_STOP.OperationCode = SCSIOP_START_STOP_UNIT; + cdb->START_STOP.LoadEject = 1; + + if (modifiedIoControlCode == IOCTL_STORAGE_EJECT_MEDIA) { + cdb->START_STOP.Start = 0; + } else { + cdb->START_STOP.Start = 1; + } + + // + // Set timeout value. + // + + SrbSetTimeOutValue(srb, fdoExtension->TimeOutValue); + status = ClassSendSrbAsynchronous(DeviceObject, + srb, + Irp, + NULL, + 0, + FALSE); + + KeSetEvent(&fdoExtension->EjectSynchronizationEvent, IO_NO_INCREMENT, FALSE); + KeLeaveCriticalRegion(); + + break; + } + + case IOCTL_STORAGE_FIND_NEW_DEVICES: { + + FREE_POOL(srb); + + if (commonExtension->IsFdo) { + + IoInvalidateDeviceRelations( + ((PFUNCTIONAL_DEVICE_EXTENSION) commonExtension)->LowerPdo, + BusRelations); + + status = STATUS_SUCCESS; + Irp->IoStatus.Status = status; + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + } + else { + + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + } + break; + } + + case IOCTL_STORAGE_GET_DEVICE_NUMBER: { + + FREE_POOL(srb); + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength >= + sizeof(STORAGE_DEVICE_NUMBER)) { + + PSTORAGE_DEVICE_NUMBER deviceNumber = + Irp->AssociatedIrp.SystemBuffer; + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = + commonExtension->PartitionZeroExtension; + + deviceNumber->DeviceType = fdoExtension->CommonExtension.DeviceObject->DeviceType; + deviceNumber->DeviceNumber = fdoExtension->DeviceNumber; + deviceNumber->PartitionNumber = commonExtension->PartitionNumber; + + status = STATUS_SUCCESS; + Irp->IoStatus.Information = sizeof(STORAGE_DEVICE_NUMBER); + + } else { + status = STATUS_BUFFER_TOO_SMALL; + Irp->IoStatus.Information = sizeof(STORAGE_DEVICE_NUMBER); + } + + Irp->IoStatus.Status = status; + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + + break; + } + + + case IOCTL_STORAGE_READ_CAPACITY: { + + FREE_POOL(srb); + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < + sizeof(STORAGE_READ_CAPACITY)) { + + // + // Indicate unsuccessful status and no data transferred. + // + + Irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL; + Irp->IoStatus.Information = sizeof(STORAGE_READ_CAPACITY); + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + status = STATUS_BUFFER_TOO_SMALL; + break; + } + + if (!commonExtension->IsFdo) { + + + // + // Just forward this down and return + // + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + } + else { + + PFUNCTIONAL_DEVICE_EXTENSION fdoExt = DeviceObject->DeviceExtension; + PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData; + PSTORAGE_READ_CAPACITY readCapacity = Irp->AssociatedIrp.SystemBuffer; + LARGE_INTEGER diskLength; + + status = ClassReadDriveCapacity(DeviceObject); + if (NT_SUCCESS(status) && fdoData->IsCachedDriveCapDataValid) { + + readCapacity->Version = sizeof(STORAGE_READ_CAPACITY); + readCapacity->Size = sizeof(STORAGE_READ_CAPACITY); + + REVERSE_BYTES(&readCapacity->BlockLength, + &fdoData->LastKnownDriveCapacityData.BytesPerBlock); + REVERSE_BYTES_QUAD(&readCapacity->NumberOfBlocks, + &fdoData->LastKnownDriveCapacityData.LogicalBlockAddress); + readCapacity->NumberOfBlocks.QuadPart++; + + readCapacity->DiskLength = fdoExt->CommonExtension.PartitionLength; + + // + // Make sure the lengths are equal. + // Remove this after testing. + // + diskLength.QuadPart = readCapacity->NumberOfBlocks.QuadPart * + readCapacity->BlockLength; + + Irp->IoStatus.Status = STATUS_SUCCESS; + Irp->IoStatus.Information = sizeof(STORAGE_READ_CAPACITY); + + } else { + // + // Read capacity request failed. + // + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, "ClassDeviceControl: ClassReadDriveCapacity failed: 0x%X IsCachedDriveCapDataValid: %d\n", + status, fdoData->IsCachedDriveCapDataValid)); + Irp->IoStatus.Status = status; + Irp->IoStatus.Information = 0; + } + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + } + + break; + } + + case IOCTL_STORAGE_QUERY_PROPERTY: { + + PSTORAGE_PROPERTY_QUERY query = Irp->AssociatedIrp.SystemBuffer; + + if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(STORAGE_PROPERTY_QUERY)) { + + status = STATUS_INFO_LENGTH_MISMATCH; + Irp->IoStatus.Status = status; + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + FREE_POOL(srb); + break; + } + + if (!commonExtension->IsFdo) { + + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + FREE_POOL(srb); + break; + } + + // + // Determine PropertyId type and either call appropriate routine + // or pass request to lower drivers. + // + + switch ( query->PropertyId ) { + + case StorageDeviceUniqueIdProperty: { + + status = ClasspDuidQueryProperty(DeviceObject, Irp); + break; + } + + case StorageDeviceWriteCacheProperty: { + + status = ClasspWriteCacheProperty(DeviceObject, Irp, srb); + break; + } + + // these propertyId has been implemented in some port driver and filter drivers. + // to keep the backwards compatibility, classpnp will send the request down if it's supported by lower layer. + // otherwise, classpnp sends SCSI command and then interprets the result. + case StorageAccessAlignmentProperty: { + + status = ClasspAccessAlignmentProperty(DeviceObject, Irp, srb); + break; + } + + case StorageDeviceSeekPenaltyProperty: { + + status = ClasspDeviceSeekPenaltyProperty(DeviceObject, Irp, srb); + break; + } + + case StorageDeviceTrimProperty: { + + status = ClasspDeviceTrimProperty(DeviceObject, Irp, srb); + break; + } + + case StorageDeviceLBProvisioningProperty: { + + status = ClasspDeviceLBProvisioningProperty(DeviceObject, Irp, srb); + break; + } + + case StorageDeviceCopyOffloadProperty: { + + status = ClasspDeviceCopyOffloadProperty(DeviceObject, Irp, srb); + break; + } + + case StorageDeviceMediumProductType: { + + status = ClasspDeviceMediaTypeProperty(DeviceObject, Irp, srb); + break; + } + + default: { + + // + // Copy the Irp stack parameters to the next stack location. + // + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + break; + } + } // end switch + + FREE_POOL(srb); + break; + } + + case IOCTL_STORAGE_CHECK_PRIORITY_HINT_SUPPORT: { + + FREE_POOL(srb); + + if (!commonExtension->IsFdo) { + + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + break; + } + + // + // Process priority hit request + // + + status = ClasspPriorityHint(DeviceObject, Irp); + break; + } + + case IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES: { + + PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes = Irp->AssociatedIrp.SystemBuffer; + + if (irpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES)) { + + status = STATUS_INVALID_PARAMETER; + Irp->IoStatus.Status = status; + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + FREE_POOL(srb); + break; + } + + if (irpStack->Parameters.DeviceIoControl.InputBufferLength < + (sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES) + dsmAttributes->ParameterBlockLength + dsmAttributes->DataSetRangesLength)) { + + status = STATUS_INVALID_PARAMETER; + Irp->IoStatus.Status = status; + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + FREE_POOL(srb); + break; + } + + if (!commonExtension->IsFdo) { + + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + FREE_POOL(srb); + break; + } + + switch(dsmAttributes->Action) { + + // only process Trim action in class layer if possible. + case DeviceDsmAction_Trim: { + status = ClasspDeviceTrimProcess(DeviceObject, Irp, srb); + break; + } + + case DeviceDsmAction_OffloadRead: { + status = ClassDeviceProcessOffloadRead(DeviceObject, Irp, srb); + break; + } + + case DeviceDsmAction_OffloadWrite: { + status = ClassDeviceProcessOffloadWrite(DeviceObject, Irp, srb); + break; + } + + case DeviceDsmAction_Allocation: { + status = ClasspDeviceGetLBAStatus(DeviceObject, Irp, srb); + break; + } + + + default: { + + + // + // Copy the Irp stack parameters to the next stack location. + // + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + break; + } + } // end switch + + FREE_POOL(srb); + break; + } + + case IOCTL_STORAGE_GET_LB_PROVISIONING_MAP_RESOURCES: { + + if (commonExtension->IsFdo) { + + status = ClassDeviceGetLBProvisioningResources(DeviceObject, Irp, srb); + + } else { + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + } + + FREE_POOL(srb); + + break; + } + + case IOCTL_STORAGE_EVENT_NOTIFICATION: { + + FREE_POOL(srb); + + status = ClasspStorageEventNotification(DeviceObject, Irp); + break; + } + +#if (NTDDI_VERSION >= NTDDI_WINTRHESHOLD) + case IOCTL_STORAGE_FIRMWARE_GET_INFO: { + FREE_POOL(srb); + + status = ClassDeviceHwFirmwareGetInfoProcess(DeviceObject, Irp); + break; + } + + case IOCTL_STORAGE_FIRMWARE_DOWNLOAD: { + if (!commonExtension->IsFdo) { + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + goto SetStatusAndReturn; + } + + status = ClassDeviceHwFirmwareDownloadProcess(DeviceObject, Irp, srb); + break; + } + + case IOCTL_STORAGE_FIRMWARE_ACTIVATE: { + if (!commonExtension->IsFdo) { + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + goto SetStatusAndReturn; + } + + status = ClassDeviceHwFirmwareActivateProcess(DeviceObject, Irp, srb); + break; + } +#endif + + + default: { + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_IOCTL, "IoDeviceControl: Unsupported device IOCTL %x for %p\n", + controlCode, DeviceObject)); + + + // + // Pass the device control to the next driver. + // + + FREE_POOL(srb); + + // + // Copy the Irp stack parameters to the next stack location. + // + + IoCopyCurrentIrpStackLocationToNext(Irp); + + ClassReleaseRemoveLock(DeviceObject, Irp); + status = IoCallDriver(commonExtension->LowerDeviceObject, Irp); + break; + } + + } // end switch( ... + +SetStatusAndReturn: + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "< ioctl %xh (%s): status %xh.", modifiedIoControlCode, DBGGETIOCTLSTR(modifiedIoControlCode), status)); + + return status; +} // end ClassDeviceControl() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassShutdownFlush() + +Routine Description: + + This routine is called for a shutdown and flush IRPs. These are sent by the + system before it actually shuts down or when the file system does a flush. + If it exists, the device-specific driver's routine will be invoked. If there + wasn't one specified, the Irp will be completed with an Invalid device request. + +Arguments: + + DriverObject - Pointer to device object to being shutdown by system. + + Irp - IRP involved. + +Return Value: + + NT Status + +--*/ +NTSTATUS +ClassShutdownFlush( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP Irp + ) +{ + PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; + + ULONG isRemoved; + + isRemoved = ClassAcquireRemoveLock(DeviceObject, Irp); + _Analysis_assume_(isRemoved); + if(isRemoved) { + + ClassReleaseRemoveLock(DeviceObject, Irp); + + Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST; + + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + + return STATUS_DEVICE_DOES_NOT_EXIST; + } + + if (commonExtension->DevInfo->ClassShutdownFlush) { + + // + // Call the device-specific driver's routine. + // + + return commonExtension->DevInfo->ClassShutdownFlush(DeviceObject, Irp); + } + + // + // Device-specific driver doesn't support this. + // + + Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST; + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + + return STATUS_INVALID_DEVICE_REQUEST; +} // end ClassShutdownFlush() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClasspIsPortable() + +Routine Description: + + This routine is called during start device to determine whether the PDO + for a stack reports itself as portable. + +Arguments: + + FdoExtension - Pointer to FDO whose PDO we check for portability. + + IsPortable - Boolean pointer in which to store result. + +Return Value: + + NT Status + +--*/ +NTSTATUS +ClasspIsPortable( + _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension, + _Out_ PBOOLEAN IsPortable + ) +{ + DEVPROP_BOOLEAN isInternal = DEVPROP_FALSE; + BOOLEAN isPortable = FALSE; + ULONG size = 0; + NTSTATUS status = STATUS_SUCCESS; + DEVPROPTYPE type = DEVPROP_TYPE_EMPTY; + + PAGED_CODE(); + + *IsPortable = FALSE; + + // + // Check to see if the underlying device + // object is in local machine container + // + + status = IoGetDevicePropertyData(FdoExtension->LowerPdo, + &DEVPKEY_Device_InLocalMachineContainer, + 0, + 0, + sizeof(isInternal), + &isInternal, + &size, + &type); + + if (!NT_SUCCESS(status)) { + goto cleanup; + } + + NT_ASSERT(size == sizeof(isInternal)); + NT_ASSERT(type == DEVPROP_TYPE_BOOLEAN); + + // + // Volume is hot-pluggable if the disk pdo + // container id differs from that of root device + // + + if (isInternal == DEVPROP_TRUE) { + goto cleanup; + } + + isPortable = TRUE; + + // + // Examine the bus type to ensure + // that this really is a fixed disk + // + + if (FdoExtension->DeviceDescriptor->BusType == BusTypeFibre || + FdoExtension->DeviceDescriptor->BusType == BusTypeiScsi || + FdoExtension->DeviceDescriptor->BusType == BusTypeRAID) { + + isPortable = FALSE; + } + + *IsPortable = isPortable; + +cleanup: + + return status; +} + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassCreateDeviceObject() + +Routine Description: + + This routine creates an object for the physical device specified and + sets up the deviceExtension's function pointers for each entry point + in the device-specific driver. + +Arguments: + + DriverObject - Pointer to driver object created by system. + + ObjectNameBuffer - Dir. name of the object to create. + + LowerDeviceObject - Pointer to the lower device object + + IsFdo - should this be an fdo or a pdo + + DeviceObject - Pointer to the device object pointer we will return. + +Return Value: + + NTSTATUS + +--*/ + +_IRQL_requires_max_(PASSIVE_LEVEL) +_Must_inspect_result_ +_Post_satisfies_(return <= 0) +SCSIPORT_API +NTSTATUS +ClassCreateDeviceObject( + _In_ PDRIVER_OBJECT DriverObject, + _In_z_ PCCHAR ObjectNameBuffer, + _In_ PDEVICE_OBJECT LowerDevice, + _In_ BOOLEAN IsFdo, + _Outptr_result_nullonfailure_ + _At_(*DeviceObject, __drv_allocatesMem(Mem) __drv_aliasesMem) + PDEVICE_OBJECT *DeviceObject + ) +{ + BOOLEAN isPartitionable; + STRING ntNameString; + UNICODE_STRING ntUnicodeString; + NTSTATUS status; + PDEVICE_OBJECT deviceObject = NULL; + + ULONG characteristics; + SIZE_T rundownSize = ExSizeOfRundownProtectionCacheAware(); + PCHAR rundownAddr = NULL; + ULONG devExtSize; + +#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case + PCLASS_DRIVER_EXTENSION driverExtension = IoGetDriverObjectExtension(DriverObject, CLASS_DRIVER_EXTENSION_KEY); + + PCLASS_DEV_INFO devInfo; + + PAGED_CODE(); + + _Analysis_assume_(driverExtension != NULL); + + *DeviceObject = NULL; + RtlInitUnicodeString(&ntUnicodeString, NULL); + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "ClassCreateFdo: Create device object\n")); + + NT_ASSERT(LowerDevice); + + // + // Make sure that if we're making PDO's we have an enumeration routine + // + + isPartitionable = (driverExtension->InitData.ClassEnumerateDevice != NULL); + + NT_ASSERT(IsFdo || isPartitionable); + + // + // Grab the correct dev-info structure out of the init data + // + + if (IsFdo) { + devInfo = &(driverExtension->InitData.FdoData); + } else { + devInfo = &(driverExtension->InitData.PdoData); + } + + characteristics = devInfo->DeviceCharacteristics; + + if (ARGUMENT_PRESENT(ObjectNameBuffer)) { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "ClassCreateFdo: Name is %s\n", ObjectNameBuffer)); + + RtlInitString(&ntNameString, ObjectNameBuffer); + + status = RtlAnsiStringToUnicodeString(&ntUnicodeString, &ntNameString, TRUE); + + if (!NT_SUCCESS(status)) { + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, + "ClassCreateFdo: Cannot convert string %s\n", + ObjectNameBuffer)); + + ntUnicodeString.Buffer = NULL; + return status; + } + } else { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "ClassCreateFdo: Object will be unnamed\n")); + + if (IsFdo == FALSE) { + + // + // PDO's have to have some sort of name. + // + + SET_FLAG(characteristics, FILE_AUTOGENERATED_DEVICE_NAME); + } + + RtlInitUnicodeString(&ntUnicodeString, NULL); + } + + devExtSize = devInfo->DeviceExtensionSize + + (ULONG)sizeof(CLASS_PRIVATE_COMMON_DATA) + (ULONG)rundownSize; + status = IoCreateDevice(DriverObject, + devExtSize, + &ntUnicodeString, + devInfo->DeviceType, + characteristics, + FALSE, + &deviceObject); + + if (!NT_SUCCESS(status)) { + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClassCreateFdo: Can not create device object %lx\n", + status)); + NT_ASSERT(deviceObject == NULL); + + // + // buffer is not used any longer here. + // + + if (ntUnicodeString.Buffer != NULL) { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, "ClassCreateFdo: Freeing unicode name buffer\n")); + FREE_POOL(ntUnicodeString.Buffer); + RtlInitUnicodeString(&ntUnicodeString, NULL); + } + + } else { + + PCOMMON_DEVICE_EXTENSION commonExtension = deviceObject->DeviceExtension; + + RtlZeroMemory( + deviceObject->DeviceExtension, + devExtSize); + + // + // Setup version code + // + + commonExtension->Version = 0x03; + + // + // Setup the remove lock and event + // + + commonExtension->IsRemoved = NO_REMOVE; + +#if DBG + + commonExtension->RemoveLock = 0; + +#endif + + KeInitializeEvent(&commonExtension->RemoveEvent, + SynchronizationEvent, + FALSE); + + + ClasspInitializeRemoveTracking(deviceObject); + + // + // Initialize the PrivateCommonData + // + + commonExtension->PrivateCommonData = (PCLASS_PRIVATE_COMMON_DATA) + ((PCHAR)deviceObject->DeviceExtension + devInfo->DeviceExtensionSize); + rundownAddr = (PCHAR)commonExtension->PrivateCommonData + sizeof(CLASS_PRIVATE_COMMON_DATA); + ExInitializeRundownProtectionCacheAware((PEX_RUNDOWN_REF_CACHE_AWARE)rundownAddr, rundownSize); + commonExtension->PrivateCommonData->RemoveLockFailAcquire = 0; + + // + // Acquire the lock once. This reference will be released when the + // remove IRP has been received. + // + + ClassAcquireRemoveLock(deviceObject, (PIRP) deviceObject); + + // + // Store a pointer to the driver extension so we don't have to do + // lookups to get it. + // + + commonExtension->DriverExtension = driverExtension; + + // + // Fill in entry points + // + + commonExtension->DevInfo = devInfo; + + // + // Initialize some of the common values in the structure + // + + commonExtension->DeviceObject = deviceObject; + + commonExtension->LowerDeviceObject = NULL; + + commonExtension->DispatchTable = driverExtension->DeviceMajorFunctionTable; + + if(IsFdo) { + + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = (PVOID) commonExtension; + + commonExtension->PartitionZeroExtension = deviceObject->DeviceExtension; + + // + // Set the initial device object flags. + // + + SET_FLAG(deviceObject->Flags, DO_POWER_PAGABLE); + // + // Clear the PDO list + // + + commonExtension->ChildList = NULL; + + commonExtension->DriverData = + ((PFUNCTIONAL_DEVICE_EXTENSION) deviceObject->DeviceExtension + 1); + + // + // The disk class driver creates only FDO. The partition number + // for the FDO must be 0. + // + + if ((isPartitionable == TRUE) || + (devInfo->DeviceType == FILE_DEVICE_DISK)) { + + commonExtension->PartitionNumber = 0; + } else { + commonExtension->PartitionNumber = (ULONG) (-1L); + } + + fdoExtension->DevicePowerState = PowerDeviceD0; + + KeInitializeEvent(&fdoExtension->EjectSynchronizationEvent, + SynchronizationEvent, + TRUE); + + KeInitializeEvent(&fdoExtension->ChildLock, + SynchronizationEvent, + TRUE); + + status = ClasspAllocateReleaseRequest(deviceObject); + + if(!NT_SUCCESS(status)) { + IoDeleteDevice(deviceObject); + *DeviceObject = NULL; + + if (ntUnicodeString.Buffer != NULL) { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "ClassCreateFdo: Freeing unicode name buffer\n")); + FREE_POOL(ntUnicodeString.Buffer); + RtlInitUnicodeString(&ntUnicodeString, NULL); + } + + return status; + } + + } else { + + PPHYSICAL_DEVICE_EXTENSION pdoExtension = + deviceObject->DeviceExtension; + + PFUNCTIONAL_DEVICE_EXTENSION p0Extension = + LowerDevice->DeviceExtension; + + SET_FLAG(deviceObject->Flags, DO_POWER_PAGABLE); + + commonExtension->PartitionZeroExtension = p0Extension; + + // + // Stick this onto the PDO list + // + + ClassAddChild(p0Extension, pdoExtension, TRUE); + + commonExtension->DriverData = (PVOID) (pdoExtension + 1); + + // + // Get the top of stack for the lower device - this allows + // filters to get stuck in between the partitions and the + // physical disk. + // + + commonExtension->LowerDeviceObject = + IoGetAttachedDeviceReference(LowerDevice); + + // + // Pnp will keep a reference to the lower device object long + // after this partition has been deleted. Dereference now so + // we don't have to deal with it later. + // + + ObDereferenceObject(commonExtension->LowerDeviceObject); + } + + KeInitializeEvent(&commonExtension->PathCountEvent, SynchronizationEvent, TRUE); + + commonExtension->IsFdo = IsFdo; + + commonExtension->DeviceName = ntUnicodeString; + + commonExtension->PreviousState = 0xff; + + InitializeDictionary(&(commonExtension->FileObjectDictionary)); + + commonExtension->CurrentState = IRP_MN_STOP_DEVICE; + + if (commonExtension->DriverExtension->InitData.ClassStartIo) { + IoSetStartIoAttributes(deviceObject, TRUE, TRUE); + } + } + + *DeviceObject = deviceObject; + + return status; +} // end ClassCreateDeviceObject() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassClaimDevice() + +Routine Description: + + This function claims a device in the port driver. The port driver object + is updated with the correct driver object if the device is successfully + claimed. + +Arguments: + + LowerDeviceObject - Supplies the base port device object. + + Release - Indicates the logical unit should be released rather than claimed. + +Return Value: + + Returns a status indicating success or failure of the operation. + +--*/ +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS +ClassClaimDevice( + _In_ PDEVICE_OBJECT LowerDeviceObject, + _In_ BOOLEAN Release + ) +{ + IO_STATUS_BLOCK ioStatus; + PIRP irp; + PIO_STACK_LOCATION irpStack; + KEVENT event; + NTSTATUS status; + SCSI_REQUEST_BLOCK srb = {0}; + + PAGED_CODE(); + + // + // WORK ITEM - MPIO related. Need to think about how to handle. + // + + srb.Length = sizeof(SCSI_REQUEST_BLOCK); + + srb.Function = Release ? SRB_FUNCTION_RELEASE_DEVICE : + SRB_FUNCTION_CLAIM_DEVICE; + + // + // Set the event object to the unsignaled state. + // It will be used to signal request completion + // + + KeInitializeEvent(&event, SynchronizationEvent, FALSE); + + // + // Build synchronous request with no transfer. + // + + irp = IoBuildDeviceIoControlRequest(IOCTL_SCSI_EXECUTE_NONE, + LowerDeviceObject, + NULL, + 0, + NULL, + 0, + TRUE, + &event, + &ioStatus); + + if (irp == NULL) { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, "ClassClaimDevice: Can't allocate Irp\n")); + return STATUS_INSUFFICIENT_RESOURCES; + } + + irpStack = IoGetNextIrpStackLocation(irp); + + // + // Save SRB address in next stack for port driver. + // + + irpStack->Parameters.Scsi.Srb = &srb; + + // + // Set up IRP Address. + // + + srb.OriginalRequest = irp; + + // + // Call the port driver with the request and wait for it to complete. + // + + status = IoCallDriver(LowerDeviceObject, irp); + if (status == STATUS_PENDING) { + + (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); + status = ioStatus.Status; + } + + // + // If this is a release request, then just decrement the reference count + // and return. The status does not matter. + // + + if (Release) { + + // ObDereferenceObject(LowerDeviceObject); + return STATUS_SUCCESS; + } + + if (!NT_SUCCESS(status)) { + return status; + } + + NT_ASSERT(srb.DataBuffer != NULL); + NT_ASSERT(!TEST_FLAG(srb.SrbFlags, SRB_FLAGS_FREE_SENSE_BUFFER)); + + return status; +} // end ClassClaimDevice() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassInternalIoControl() + +Routine Description: + + This routine passes internal device controls to the port driver. + Internal device controls are used by higher level drivers both for ioctls + and to pass through scsi requests. + + If the IoControlCode does not match any of the handled ioctls and is + a valid system address then the request will be treated as an SRB and + passed down to the lower driver. If the IoControlCode is not a valid + system address the ioctl will be failed. + + Callers must therefore be extremely cautious to pass correct, initialized + values to this function. + +Arguments: + + DeviceObject - Supplies a pointer to the device object for this request. + + Irp - Supplies the Irp making the request. + +Return Value: + + Returns back a STATUS_PENDING or a completion status. + +--*/ +NTSTATUS +ClassInternalIoControl( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP Irp + ) +{ + PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; + + PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp); + PIO_STACK_LOCATION nextStack = IoGetNextIrpStackLocation(Irp); + + ULONG isRemoved; + + PSCSI_REQUEST_BLOCK srb; + + isRemoved = ClassAcquireRemoveLock(DeviceObject, Irp); + _Analysis_assume_(isRemoved); + if(isRemoved) { + + Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST; + + ClassReleaseRemoveLock(DeviceObject, Irp); + + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + + return STATUS_DEVICE_DOES_NOT_EXIST; + } + + // + // Get a pointer to the SRB. + // + + srb = irpStack->Parameters.Scsi.Srb; + + // + // Set the parameters in the next stack location. + // + + if(commonExtension->IsFdo) { + nextStack->Parameters.Scsi.Srb = srb; + nextStack->MajorFunction = IRP_MJ_SCSI; + nextStack->MinorFunction = IRP_MN_SCSI_CLASS; + + } else { + + IoCopyCurrentIrpStackLocationToNext(Irp); + } + + ClassReleaseRemoveLock(DeviceObject, Irp); + + return IoCallDriver(commonExtension->LowerDeviceObject, Irp); +} // end ClassInternalIoControl() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassQueryTimeOutRegistryValue() + +Routine Description: + + This routine determines whether a reg key for a user-specified timeout + value exists. This should be called at initialization time. + +Arguments: + + DeviceObject - Pointer to the device object we are retrieving the timeout + value for + +Return Value: + + None, but it sets a new default timeout for a class of devices. + +--*/ +_IRQL_requires_max_(PASSIVE_LEVEL) +ULONG +ClassQueryTimeOutRegistryValue( + _In_ PDEVICE_OBJECT DeviceObject + ) +{ + // + // Find the appropriate reg. key + // + +#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case + PCLASS_DRIVER_EXTENSION driverExtension = IoGetDriverObjectExtension(DeviceObject->DriverObject, CLASS_DRIVER_EXTENSION_KEY); + + PUNICODE_STRING registryPath = &(driverExtension->RegistryPath); + + PRTL_QUERY_REGISTRY_TABLE parameters = NULL; + PWSTR path; + NTSTATUS status; + LONG timeOut = 0; + ULONG zero = 0; + ULONG size; + + PAGED_CODE(); + + if (!registryPath) { + return 0; + } + + parameters = ExAllocatePoolWithTag(NonPagedPoolNx, + sizeof(RTL_QUERY_REGISTRY_TABLE)*2, + '1BcS'); + + if (!parameters) { + return 0; + } + + size = registryPath->MaximumLength + sizeof(WCHAR); + path = ExAllocatePoolWithTag(NonPagedPoolNx, size, '2BcS'); + + if (!path) { + FREE_POOL(parameters); + return 0; + } + + RtlZeroMemory(path,size); + RtlCopyMemory(path, registryPath->Buffer, size - sizeof(WCHAR)); + + + // + // Check for the Timeout value. + // + + RtlZeroMemory(parameters, + (sizeof(RTL_QUERY_REGISTRY_TABLE)*2)); + + parameters[0].Flags = RTL_QUERY_REGISTRY_DIRECT; + parameters[0].Name = L"TimeOutValue"; + parameters[0].EntryContext = &timeOut; + parameters[0].DefaultType = REG_DWORD; + parameters[0].DefaultData = &zero; + parameters[0].DefaultLength = sizeof(ULONG); + + status = RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE | RTL_REGISTRY_OPTIONAL, + path, + parameters, + NULL, + NULL); + + if (!(NT_SUCCESS(status))) { + timeOut = 0; + } + + FREE_POOL(parameters); + FREE_POOL(path); + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, + "ClassQueryTimeOutRegistryValue: Timeout value %d\n", + timeOut)); + + + return timeOut; + +} // end ClassQueryTimeOutRegistryValue() + + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassCheckVerifyComplete() ISSUE-2000/02/18-henrygab - why public?! + +Routine Description: + + This routine executes when the port driver has completed a check verify + ioctl. It will set the status of the master Irp, copy the media change + count and complete the request. + +Arguments: + + Fdo - Supplies the functional device object which represents the logical unit. + + Irp - Supplies the Irp which has completed. + + Context - NULL + +Return Value: + + NT status + +--*/ +NTSTATUS +ClassCheckVerifyComplete( + IN PDEVICE_OBJECT Fdo, + IN PIRP Irp, + IN PVOID Context + ) +{ + PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp); + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension; + + PIRP originalIrp; + + UNREFERENCED_PARAMETER(Context); + + ASSERT_FDO(Fdo); + + originalIrp = irpStack->Parameters.Others.Argument1; + + // + // Copy the media change count and status + // + + *((PULONG) (originalIrp->AssociatedIrp.SystemBuffer)) = + fdoExtension->MediaChangeCount; + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "ClassCheckVerifyComplete - Media change count for" + "device %d is %lx - saved as %lx\n", + fdoExtension->DeviceNumber, + fdoExtension->MediaChangeCount, + *((PULONG) originalIrp->AssociatedIrp.SystemBuffer))); + + originalIrp->IoStatus.Status = Irp->IoStatus.Status; + originalIrp->IoStatus.Information = sizeof(ULONG); + + ClassReleaseRemoveLock(Fdo, originalIrp); + ClassCompleteRequest(Fdo, originalIrp, IO_DISK_INCREMENT); + + IoFreeIrp(Irp); + + return STATUS_MORE_PROCESSING_REQUIRED; + +} // end ClassCheckVerifyComplete() + + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassGetDescriptor() + +Routine Description: + + This routine will perform a query for the specified property id and will + allocate a non-paged buffer to store the data in. It is the responsibility + of the caller to ensure that this buffer is freed. + + This routine must be run at IRQL_PASSIVE_LEVEL + +Arguments: + + DeviceObject - the device to query + DeviceInfo - a location to store a pointer to the buffer we allocate + +Return Value: + + status + if status is unsuccessful *DeviceInfo will be set to NULL, else the + buffer allocated on behalf of the caller. + +--*/ +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS +ClassGetDescriptor( + _In_ PDEVICE_OBJECT DeviceObject, + _In_ PSTORAGE_PROPERTY_ID PropertyId, + _Outptr_ PVOID *Descriptor + ) +{ + STORAGE_PROPERTY_QUERY query = {0}; + IO_STATUS_BLOCK ioStatus; + + PSTORAGE_DESCRIPTOR_HEADER descriptor = NULL; + ULONG length; + + PAGED_CODE(); + + // + // Set the passed-in descriptor pointer to NULL as default + // + + *Descriptor = NULL; + + query.PropertyId = *PropertyId; + query.QueryType = PropertyStandardQuery; + + // + // On the first pass we just want to get the first few + // bytes of the descriptor so we can read it's size + // + + descriptor = (PVOID)&query; + + NT_ASSERT(sizeof(STORAGE_PROPERTY_QUERY) >= (sizeof(ULONG)*2)); + + ClassSendDeviceIoControlSynchronous( + IOCTL_STORAGE_QUERY_PROPERTY, + DeviceObject, + &query, + sizeof(STORAGE_PROPERTY_QUERY), + sizeof(ULONG) * 2, + FALSE, + &ioStatus + ); + + if(!NT_SUCCESS(ioStatus.Status)) { + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClassGetDescriptor: error %lx trying to " + "query properties #1\n", ioStatus.Status)); + return ioStatus.Status; + } + + if (descriptor->Size == 0) { + + // + // This DebugPrint is to help third-party driver writers + // + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClassGetDescriptor: size returned was zero?! (status " + "%x\n", ioStatus.Status)); + return STATUS_UNSUCCESSFUL; + + } + + // + // This time we know how much data there is so we can + // allocate a buffer of the correct size + // + + length = descriptor->Size; + NT_ASSERT(length >= sizeof(STORAGE_PROPERTY_QUERY)); + length = max(length, sizeof(STORAGE_PROPERTY_QUERY)); + + descriptor = ExAllocatePoolWithTag(NonPagedPoolNx, length, '4BcS'); + + if(descriptor == NULL) { + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClassGetDescriptor: unable to memory for descriptor " + "(%d bytes)\n", length)); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // + // setup the query again, as it was overwritten above + // + + RtlZeroMemory(&query, sizeof(STORAGE_PROPERTY_QUERY)); + query.PropertyId = *PropertyId; + query.QueryType = PropertyStandardQuery; + + // + // copy the input to the new outputbuffer + // + + RtlZeroMemory(descriptor, length); + + RtlCopyMemory(descriptor, + &query, + sizeof(STORAGE_PROPERTY_QUERY) + ); + + ClassSendDeviceIoControlSynchronous( + IOCTL_STORAGE_QUERY_PROPERTY, + DeviceObject, + descriptor, + sizeof(STORAGE_PROPERTY_QUERY), + length, + FALSE, + &ioStatus + ); + + if(!NT_SUCCESS(ioStatus.Status)) { + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_INIT, "ClassGetDescriptor: error %lx trying to " + "query properties #1\n", ioStatus.Status)); + FREE_POOL(descriptor); + return ioStatus.Status; + } + + // + // return the memory we've allocated to the caller + // + + *Descriptor = descriptor; + return ioStatus.Status; +} // end ClassGetDescriptor() + + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassSignalCompletion() + +Routine Description: + + This completion routine will signal the event given as context and then + return STATUS_MORE_PROCESSING_REQUIRED to stop event completion. It is + the responsibility of the routine waiting on the event to complete the + request and free the event. + +Arguments: + + DeviceObject - a pointer to the device object + + Irp - a pointer to the irp + + Event - a pointer to the event to signal + +Return Value: + + STATUS_MORE_PROCESSING_REQUIRED + +--*/ +NTSTATUS +ClassSignalCompletion( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP Irp, + IN PVOID Context + ) +{ + PKEVENT Event = (PKEVENT)Context; + + UNREFERENCED_PARAMETER(DeviceObject); + UNREFERENCED_PARAMETER(Irp); + + if (Context == NULL) { + NT_ASSERT(Context != NULL); + return STATUS_INVALID_PARAMETER; + } + + KeSetEvent(Event, IO_NO_INCREMENT, FALSE); + + return STATUS_MORE_PROCESSING_REQUIRED; +} // end ClassSignalCompletion() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassPnpQueryFdoRelations() + +Routine Description: + + This routine will call the driver's enumeration routine to update the + list of PDO's. It will then build a response to the + IRP_MN_QUERY_DEVICE_RELATIONS and place it into the information field in + the irp. + +Arguments: + + Fdo - a pointer to the functional device object we are enumerating + + Irp - a pointer to the enumeration request + +Return Value: + + status + +--*/ +NTSTATUS +ClassPnpQueryFdoRelations( + IN PDEVICE_OBJECT Fdo, + IN PIRP Irp + ) +{ + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension; +#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case + PCLASS_DRIVER_EXTENSION driverExtension = IoGetDriverObjectExtension(Fdo->DriverObject, CLASS_DRIVER_EXTENSION_KEY); + + PAGED_CODE(); + + _Analysis_assume_(driverExtension != NULL); + + // + // If there's already an enumeration in progress then don't start another + // one. + // + + if(InterlockedIncrement((volatile LONG *)&(fdoExtension->EnumerationInterlock)) == 1) { + driverExtension->InitData.ClassEnumerateDevice(Fdo); + } + + Irp->IoStatus.Status = ClassRetrieveDeviceRelations( + Fdo, + BusRelations, + &((PDEVICE_RELATIONS) Irp->IoStatus.Information)); + InterlockedDecrement((volatile LONG *)&(fdoExtension->EnumerationInterlock)); + + return Irp->IoStatus.Status; +} // end ClassPnpQueryFdoRelations() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassMarkChildrenMissing() + +Routine Description: + + This routine will call ClassMarkChildMissing() for all children. + It acquires the ChildLock before calling ClassMarkChildMissing(). + +Arguments: + + Fdo - the "bus's" device object, such as the disk FDO for non-removable + disks with multiple partitions. + +Return Value: + + None + +--*/ +_IRQL_requires_max_(PASSIVE_LEVEL) +VOID +ClassMarkChildrenMissing( + _In_ PFUNCTIONAL_DEVICE_EXTENSION Fdo + ) +{ + PCOMMON_DEVICE_EXTENSION commonExtension = &(Fdo->CommonExtension); + PPHYSICAL_DEVICE_EXTENSION nextChild = commonExtension->ChildList; + + PAGED_CODE(); + + ClassAcquireChildLock(Fdo); + + while (nextChild){ + PPHYSICAL_DEVICE_EXTENSION tmpChild; + + /* + * ClassMarkChildMissing will also dequeue the child extension. + * So get the next pointer before calling ClassMarkChildMissing. + */ + tmpChild = nextChild; + nextChild = tmpChild->CommonExtension.ChildList; + ClassMarkChildMissing(tmpChild, FALSE); + } + ClassReleaseChildLock(Fdo); + return; +} // end ClassMarkChildrenMissing() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassMarkChildMissing() + +Routine Description: + + This routine will make an active child "missing." If the device has never + been enumerated then it will be deleted on the spot. If the device has + not been enumerated then it will be marked as missing so that we can + not report it in the next device enumeration. + +Arguments: + + Child - the child device to be marked as missing. + + AcquireChildLock - TRUE if the child lock should be acquired before removing + the missing child. FALSE if the child lock is already + acquired by this thread. + +Return Value: + + returns whether or not the child device object has previously been reported + to PNP. + +--*/ +_IRQL_requires_max_(PASSIVE_LEVEL) +BOOLEAN +ClassMarkChildMissing( + _In_ PPHYSICAL_DEVICE_EXTENSION Child, + _In_ BOOLEAN AcquireChildLock + ) +{ + BOOLEAN returnValue = Child->IsEnumerated; + + PAGED_CODE(); + ASSERT_PDO(Child->DeviceObject); + + Child->IsMissing = TRUE; + + // + // Make sure this child is not in the active list. + // + + ClassRemoveChild(Child->CommonExtension.PartitionZeroExtension, + Child, + AcquireChildLock); + + if(Child->IsEnumerated == FALSE) { + PCOMMON_DEVICE_EXTENSION commonExtension = Child->DeviceObject->DeviceExtension; + commonExtension->IsRemoved = REMOVE_PENDING; + ClassRemoveDevice(Child->DeviceObject, IRP_MN_REMOVE_DEVICE); + } + + return returnValue; +} // end ClassMarkChildMissing() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassRetrieveDeviceRelations() + +Routine Description: + + This routine will allocate a buffer to hold the specified list of + relations. It will then fill in the list with referenced device pointers + and will return the request. + +Arguments: + + Fdo - pointer to the FDO being queried + + RelationType - what type of relations are being queried + + DeviceRelations - a location to store a pointer to the response + +Return Value: + + status + +--*/ +NTSTATUS +ClassRetrieveDeviceRelations( + IN PDEVICE_OBJECT Fdo, + IN DEVICE_RELATION_TYPE RelationType, + OUT PDEVICE_RELATIONS *DeviceRelations + ) +{ + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension; + + ULONG count = 0; + ULONG i; + + PPHYSICAL_DEVICE_EXTENSION nextChild; + + ULONG relationsSize; + PDEVICE_RELATIONS deviceRelations = NULL; + PDEVICE_RELATIONS oldRelations = *DeviceRelations; + + NTSTATUS status; + + UNREFERENCED_PARAMETER(RelationType); + + PAGED_CODE(); + + ClassAcquireChildLock(fdoExtension); + + nextChild = fdoExtension->CommonExtension.ChildList; + + // + // Count the number of PDO's attached to this disk + // + + while (nextChild != NULL) { + PCOMMON_DEVICE_EXTENSION commonExtension; + + commonExtension = &(nextChild->CommonExtension); + + NT_ASSERTMSG("ClassPnp internal error: missing child on active list\n", + (nextChild->IsMissing == FALSE)); + + nextChild = commonExtension->ChildList; + + count++; + }; + + // + // If relations already exist in the QDR, adjust the current count + // to include the previous list. + // + + if (oldRelations) { + count += oldRelations->Count; + } + + relationsSize = (sizeof(DEVICE_RELATIONS) + + (count * sizeof(PDEVICE_OBJECT))); + + deviceRelations = ExAllocatePoolWithTag(PagedPool, relationsSize, '5BcS'); + + if (deviceRelations == NULL) { + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_ENUM, "ClassRetrieveDeviceRelations: unable to allocate " + "%d bytes for device relations\n", relationsSize)); + + ClassReleaseChildLock(fdoExtension); + + return STATUS_INSUFFICIENT_RESOURCES; + } + + RtlZeroMemory(deviceRelations, relationsSize); + + if (oldRelations) { + + // + // Copy the old relations to the new list and free the old list. + // + + for (i = 0; i < oldRelations->Count; i++) { + deviceRelations->Objects[i] = oldRelations->Objects[i]; + } + + FREE_POOL(oldRelations); + } + + nextChild = fdoExtension->CommonExtension.ChildList; + i = count; + + while (nextChild != NULL) { + PCOMMON_DEVICE_EXTENSION commonExtension; + + commonExtension = &(nextChild->CommonExtension); + + NT_ASSERTMSG("ClassPnp internal error: missing child on active list\n", + (nextChild->IsMissing == FALSE)); + + _Analysis_assume_(i >= 1); + deviceRelations->Objects[--i] = nextChild->DeviceObject; + + status = ObReferenceObjectByPointer( + nextChild->DeviceObject, + 0, + NULL, + KernelMode); + if (!NT_SUCCESS(status)) { + NT_ASSERT(!"Error referencing child device by pointer"); + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_ENUM, + "ClassRetrieveDeviceRelations: Error referencing child " + "device %p by pointer\n", nextChild->DeviceObject)); + + } + nextChild->IsEnumerated = TRUE; + nextChild = commonExtension->ChildList; + } + + NT_ASSERTMSG("Child list has changed: ", i == 0); + + deviceRelations->Count = count; + *DeviceRelations = deviceRelations; + + ClassReleaseChildLock(fdoExtension); + return STATUS_SUCCESS; +} // end ClassRetrieveDeviceRelations() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassGetPdoId() + +Routine Description: + + This routine will call into the driver to retrieve a copy of one of it's + id strings. + +Arguments: + + Pdo - a pointer to the pdo being queried + + IdType - which type of id string is being queried + + IdString - an allocated unicode string structure which the driver + can fill in. + +Return Value: + + status + +--*/ +NTSTATUS +ClassGetPdoId( + IN PDEVICE_OBJECT Pdo, + IN BUS_QUERY_ID_TYPE IdType, + IN PUNICODE_STRING IdString + ) +{ +#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case + PCLASS_DRIVER_EXTENSION driverExtension = IoGetDriverObjectExtension(Pdo->DriverObject, CLASS_DRIVER_EXTENSION_KEY); + + _Analysis_assume_(driverExtension != NULL); + + ASSERT_PDO(Pdo); + NT_ASSERT(driverExtension->InitData.ClassQueryId); + + PAGED_CODE(); + + return driverExtension->InitData.ClassQueryId( Pdo, IdType, IdString); +} // end ClassGetPdoId() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassQueryPnpCapabilities() + +Routine Description: + + This routine will call into the class driver to retrieve it's pnp + capabilities. + +Arguments: + + PhysicalDeviceObject - The physical device object to retrieve properties + for. + +Return Value: + + status + +--*/ +NTSTATUS +ClassQueryPnpCapabilities( + IN PDEVICE_OBJECT DeviceObject, + IN PDEVICE_CAPABILITIES Capabilities + ) +{ + PCLASS_DRIVER_EXTENSION driverExtension = + ClassGetDriverExtension(DeviceObject->DriverObject); + PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; + + PCLASS_QUERY_PNP_CAPABILITIES queryRoutine = NULL; + + PAGED_CODE(); + + NT_ASSERT(DeviceObject); + NT_ASSERT(Capabilities); + + if(commonExtension->IsFdo) { + queryRoutine = driverExtension->InitData.FdoData.ClassQueryPnpCapabilities; + } else { + queryRoutine = driverExtension->InitData.PdoData.ClassQueryPnpCapabilities; + } + + if(queryRoutine) { + return queryRoutine(DeviceObject, + Capabilities); + } else { + return STATUS_NOT_IMPLEMENTED; + } +} // end ClassQueryPnpCapabilities() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassInvalidateBusRelations() + +Routine Description: + + This routine re-enumerates the devices on the "bus". It will call into + the driver's ClassEnumerate routine to update the device objects + immediately. It will then schedule a bus re-enumeration for pnp by calling + IoInvalidateDeviceRelations. + +Arguments: + + Fdo - a pointer to the functional device object for this bus + +Return Value: + + none + +--*/ +_IRQL_requires_max_(PASSIVE_LEVEL) +VOID +ClassInvalidateBusRelations( + _In_ PDEVICE_OBJECT Fdo + ) +{ + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension; +#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case + PCLASS_DRIVER_EXTENSION driverExtension = IoGetDriverObjectExtension(Fdo->DriverObject, CLASS_DRIVER_EXTENSION_KEY); + + NTSTATUS status = STATUS_SUCCESS; + + PAGED_CODE(); + + _Analysis_assume_(driverExtension != NULL); + + ASSERT_FDO(Fdo); + NT_ASSERT(driverExtension->InitData.ClassEnumerateDevice != NULL); + + if(InterlockedIncrement((volatile LONG *)&(fdoExtension->EnumerationInterlock)) == 1) { + status = driverExtension->InitData.ClassEnumerateDevice(Fdo); + } + InterlockedDecrement((volatile LONG *)&(fdoExtension->EnumerationInterlock)); + + if(!NT_SUCCESS(status)) { + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_ENUM, "ClassInvalidateBusRelations: EnumerateDevice routine " + "returned %lx\n", status)); + } + + IoInvalidateDeviceRelations(fdoExtension->LowerPdo, BusRelations); + + return; +} // end ClassInvalidateBusRelations() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassRemoveDevice() ISSUE-2000/02/18-henrygab - why public?! + +Routine Description: + + This routine is called to handle the "removal" of a device. It will + forward the request downwards if necesssary, call into the driver + to release any necessary resources (memory, events, etc) and then + will delete the device object. + +Arguments: + + DeviceObject - a pointer to the device object being removed + + RemoveType - indicates what type of remove this is (regular or surprise). + +Return Value: + + status + +--*/ +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS +ClassRemoveDevice( + _In_ PDEVICE_OBJECT DeviceObject, + _In_ UCHAR RemoveType + ) +{ +#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case + PCLASS_DRIVER_EXTENSION driverExtension = IoGetDriverObjectExtension(DeviceObject->DriverObject, CLASS_DRIVER_EXTENSION_KEY); + PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; + PDEVICE_OBJECT lowerDeviceObject = commonExtension->LowerDeviceObject; + BOOLEAN proceedWithRemove = TRUE; + NTSTATUS status; + PEX_RUNDOWN_REF_CACHE_AWARE removeLockRundown = NULL; + + + PAGED_CODE(); + + _Analysis_assume_(driverExtension != NULL); + + /* + * Deregister from WMI. + */ + if (commonExtension->IsFdo || + driverExtension->InitData.PdoData.ClassWmiInfo.GuidRegInfo) { + status = IoWMIRegistrationControl(DeviceObject, WMIREG_ACTION_DEREGISTER); + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_WMI, "ClassRemoveDevice: IoWMIRegistrationControl(%p, WMI_ACTION_DEREGISTER) --> %lx", DeviceObject, status)); + } + + /* + * If we exposed a "shingle" (a named device interface openable by CreateFile) + * then delete it now. + */ + if (commonExtension->MountedDeviceInterfaceName.Buffer){ + (VOID)IoSetDeviceInterfaceState(&commonExtension->MountedDeviceInterfaceName, FALSE); + RtlFreeUnicodeString(&commonExtension->MountedDeviceInterfaceName); + RtlInitUnicodeString(&commonExtension->MountedDeviceInterfaceName, NULL); + } + + // + // If this is a surprise removal we leave the device around - which means + // we don't have to (or want to) drop the remove lock and wait for pending + // requests to complete. + // + + if (RemoveType == IRP_MN_REMOVE_DEVICE) { + + // + // Release the lock we acquired when the device object was created. + // + + ClassReleaseRemoveLock(DeviceObject, (PIRP) DeviceObject); + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP, "ClasspRemoveDevice - Reference count is now %d\n", + commonExtension->RemoveLock)); + + // + // The RemoveLockRundown allows fast protection of the device object + // structure that is torn down by a single thread. While in the rundown process (the call to + // ExWaitForRundownProtectionReleaseCacheAware returns), the rundown object becomes + // invalid and the subsequent calls to ExAcquireRundownProtectionCacheAware will return FALSE. + // ExReInitializeRundownProtectionCacheAware needs to be called to re-initialize the + // RemoveLockRundown protection. + // + + removeLockRundown = (PEX_RUNDOWN_REF_CACHE_AWARE) + ((PCHAR)commonExtension->PrivateCommonData + + sizeof(CLASS_PRIVATE_COMMON_DATA)); + ExWaitForRundownProtectionReleaseCacheAware(removeLockRundown); + + KeSetEvent(&commonExtension->RemoveEvent, + IO_NO_INCREMENT, + FALSE); + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP, "ClasspRemoveDevice - removing device %p\n", DeviceObject)); + + if (commonExtension->IsFdo) { + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP, "ClasspRemoveDevice - FDO %p has received a " + "remove request.\n", DeviceObject)); + + } else { + PPHYSICAL_DEVICE_EXTENSION pdoExtension = DeviceObject->DeviceExtension; + + if (pdoExtension->IsMissing) { + /* + * The child partition PDO is missing, so we are going to go ahead + * and delete it for the remove. + */ + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP, "ClasspRemoveDevice - PDO %p is missing and will be removed", DeviceObject)); + } else { + /* + * We got a remove for a child partition PDO which is not actually missing. + * So we will NOT actually delete it. + */ + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_PNP, "ClasspRemoveDevice - PDO %p still exists and will be removed when it disappears", DeviceObject)); + + ExReInitializeRundownProtectionCacheAware(removeLockRundown); + + // + // Reacquire the remove lock for the next time this comes around. + // + + ClassAcquireRemoveLock(DeviceObject, (PIRP) DeviceObject); + + // + // the device wasn't missing so it's not really been removed. + // + + commonExtension->IsRemoved = NO_REMOVE; + + IoInvalidateDeviceRelations( + commonExtension->PartitionZeroExtension->LowerPdo, + BusRelations); + + proceedWithRemove = FALSE; + } + } + } + + + if (proceedWithRemove) { + + /* + * Call the class driver's remove handler. + * All this is supposed to do is clean up its data and device interfaces. + */ + NT_ASSERT(commonExtension->DevInfo->ClassRemoveDevice); + status = commonExtension->DevInfo->ClassRemoveDevice(DeviceObject, RemoveType); + NT_ASSERT(NT_SUCCESS(status)); + status = STATUS_SUCCESS; + UNREFERENCED_PARAMETER(status); // disables prefast warning; defensive coding... + + if (commonExtension->IsFdo) { + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension; + + ClasspDisableTimer(fdoExtension); + + if (RemoveType == IRP_MN_REMOVE_DEVICE) { + + PPHYSICAL_DEVICE_EXTENSION child; + + // + // If this FDO is idle power managed, remove it from the + // list of idle power managed FDOs. + // + if (fdoExtension->FunctionSupportInfo && + fdoExtension->FunctionSupportInfo->IdlePower.IdlePowerEnabled) { + PIDLE_POWER_FDO_LIST_ENTRY fdoEntry; + + KeAcquireGuardedMutex(&IdlePowerFDOListMutex); + fdoEntry = (PIDLE_POWER_FDO_LIST_ENTRY)IdlePowerFDOList.Flink; + while ((PLIST_ENTRY)fdoEntry != &IdlePowerFDOList) { + PIDLE_POWER_FDO_LIST_ENTRY nextEntry = (PIDLE_POWER_FDO_LIST_ENTRY)fdoEntry->ListEntry.Flink; + if (fdoEntry->Fdo == DeviceObject) { + RemoveEntryList(&(fdoEntry->ListEntry)); + ExFreePool(fdoEntry); + break; + } + fdoEntry = nextEntry; + } + KeReleaseGuardedMutex(&IdlePowerFDOListMutex); + } + + // + // Cleanup the media detection resources now that the class driver + // has stopped it's timer (if any) and we can be sure they won't + // call us to do detection again. + // + + ClassCleanupMediaChangeDetection(fdoExtension); + + // + // Cleanup any Failure Prediction stuff + // + FREE_POOL(fdoExtension->FailurePredictionInfo); + + /* + * Ordinarily all child PDOs will be removed by the time + * that the parent gets the REMOVE_DEVICE. + * However, if a child PDO has been created but has not + * been announced in a QueryDeviceRelations, then it is + * just a private data structure unknown to pnp, and we have + * to delete it ourselves. + */ + ClassAcquireChildLock(fdoExtension); + child = ClassRemoveChild(fdoExtension, NULL, FALSE); + while (child) { + PCOMMON_DEVICE_EXTENSION childCommonExtension = child->DeviceObject->DeviceExtension; + + // + // Yank the pdo. This routine will unlink the device from the + // pdo list so NextPdo will point to the next one when it's + // complete. + // + child->IsMissing = TRUE; + childCommonExtension->IsRemoved = REMOVE_PENDING; + ClassRemoveDevice(child->DeviceObject, IRP_MN_REMOVE_DEVICE); + child = ClassRemoveChild(fdoExtension, NULL, FALSE); + } + ClassReleaseChildLock(fdoExtension); + } + else if (RemoveType == IRP_MN_SURPRISE_REMOVAL){ + /* + * This is a surprise-remove on the parent FDO. + * We will mark the child PDOs as missing so that they + * will actually get deleted when they get a REMOVE_DEVICE. + */ + ClassMarkChildrenMissing(fdoExtension); + } + + if (RemoveType == IRP_MN_REMOVE_DEVICE) { + + ClasspFreeReleaseRequest(DeviceObject); + + // + // Free FDO-specific data structs + // + if (fdoExtension->PrivateFdoData) { + // + // Only remove the entry if the list has been initialized, or + // else we will access invalid memory. + // + PLIST_ENTRY allFdosListEntry = &fdoExtension->PrivateFdoData->AllFdosListEntry; + if (allFdosListEntry->Flink && allFdosListEntry->Blink) { + // + // Remove the FDO from the static list. + // Pnp is synchronized so this shouldn't need any synchronization. + // + RemoveEntryList(allFdosListEntry); + } + InitializeListHead(allFdosListEntry); + + DestroyAllTransferPackets(DeviceObject); + + // + // Delete the tick timer now. + // + ClasspDeleteTimer(fdoExtension); + + + FREE_POOL(fdoExtension->PrivateFdoData->PowerProcessIrp); + FREE_POOL(fdoExtension->PrivateFdoData->FreeTransferPacketsLists); + FREE_POOL(fdoExtension->PrivateFdoData); + } + +#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD) + FREE_POOL(fdoExtension->AdditionalFdoData); +#endif + + if (commonExtension->DeviceName.Buffer) { + FREE_POOL(commonExtension->DeviceName.Buffer); + RtlInitUnicodeString(&commonExtension->DeviceName, NULL); + } + +#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD) + if (fdoExtension->FunctionSupportInfo != NULL) { + FREE_POOL(fdoExtension->FunctionSupportInfo->HwFirmwareInfo); + } +#endif + FREE_POOL(fdoExtension->FunctionSupportInfo); + + FREE_POOL(fdoExtension->MiniportDescriptor); + + FREE_POOL(fdoExtension->AdapterDescriptor); + + FREE_POOL(fdoExtension->DeviceDescriptor); + + // + // Detach our device object from the stack - there's no reason + // to hold off our cleanup any longer. + // + + IoDetachDevice(lowerDeviceObject); + } + } + else { + /* + * This is a child partition PDO. + * We have already determined that it was previously marked + * as missing. So if this is a REMOVE_DEVICE, we will actually + * delete it. + */ + if (RemoveType == IRP_MN_REMOVE_DEVICE) { + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = commonExtension->PartitionZeroExtension; + PPHYSICAL_DEVICE_EXTENSION pdoExtension = (PPHYSICAL_DEVICE_EXTENSION)commonExtension; + + // + // See if this device is in the child list (if this was a suprise + // removal it might be) and remove it. + // + ClassRemoveChild(fdoExtension, pdoExtension, TRUE); + } + } + + commonExtension->PartitionLength.QuadPart = 0; + + if (RemoveType == IRP_MN_REMOVE_DEVICE) { + + ClasspUninitializeRemoveTracking(DeviceObject); + + IoDeleteDevice(DeviceObject); + } + } + + return STATUS_SUCCESS; +} // end ClassRemoveDevice() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassGetDriverExtension() + +Routine Description: + + This routine will return the classpnp's driver extension. + +Arguments: + + DriverObject - the driver object for which to get classpnp's extension + +Return Value: + + Either NULL if none, or a pointer to the driver extension + +--*/ +__drv_aliasesMem +_IRQL_requires_max_(DISPATCH_LEVEL) +PCLASS_DRIVER_EXTENSION +ClassGetDriverExtension( + _In_ PDRIVER_OBJECT DriverObject + ) +{ +#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case + return IoGetDriverObjectExtension(DriverObject, CLASS_DRIVER_EXTENSION_KEY); +} // end ClassGetDriverExtension() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClasspStartIo() + +Routine Description: + + This routine wraps the class driver's start io routine. If the device + is being removed it will complete any requests with + STATUS_DEVICE_DOES_NOT_EXIST and fire up the next packet. + +Arguments: + +Return Value: + + none + +--*/ +VOID +ClasspStartIo( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP Irp + ) +{ + PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; + + // + // We're already holding the remove lock so just check the variable and + // see what's going on. + // + + if(commonExtension->IsRemoved) { + + Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST; + +#pragma warning(suppress:4054) // okay to type cast function pointer to PIRP for this use case + ClassAcquireRemoveLock(DeviceObject, (PIRP) ClasspStartIo); + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_DISK_INCREMENT); + IoStartNextPacket(DeviceObject, TRUE); // Some IO is cancellable + +#pragma warning(suppress:4054) // okay to type cast function pointer to PIRP for this use case + ClassReleaseRemoveLock(DeviceObject, (PIRP) ClasspStartIo); + + return; + } + + commonExtension->DriverExtension->InitData.ClassStartIo( + DeviceObject, + Irp); + + return; +} // ClasspStartIo() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassUpdateInformationInRegistry() + +Routine Description: + + This routine has knowledge about the layout of the device map information + in the registry. It will update this information to include a value + entry specifying the dos device name that is assumed to get assigned + to this NT device name. For more information on this assigning of the + dos device name look in the drive support routine in the hal that assigns + all dos names. + + Since some versions of some device's firmware did not work and some + vendors did not bother to follow the specification, the entire inquiry + information must also be stored in the registry so than someone can + figure out the firmware version. + +Arguments: + + DeviceObject - A pointer to the device object for the tape device. + +Return Value: + + None + +--*/ +_IRQL_requires_max_(PASSIVE_LEVEL) +VOID +ClassUpdateInformationInRegistry( + _In_ PDEVICE_OBJECT Fdo, + _In_ PCHAR DeviceName, + _In_ ULONG DeviceNumber, + _In_reads_bytes_opt_(InquiryDataLength) PINQUIRYDATA InquiryData, + _In_ ULONG InquiryDataLength + ) +{ + NTSTATUS status; + SCSI_ADDRESS scsiAddress = {0}; + OBJECT_ATTRIBUTES objectAttributes = {0}; + STRING string; + UNICODE_STRING unicodeName = {0}; + UNICODE_STRING unicodeRegistryPath = {0}; + UNICODE_STRING unicodeData = {0}; + HANDLE targetKey; + IO_STATUS_BLOCK ioStatus; + UCHAR buffer[256] = {0}; + + PAGED_CODE(); + + NT_ASSERT(DeviceName); + targetKey = NULL; + + TRY { + + // + // Issue GET_ADDRESS Ioctl to determine path, target, and lun information. + // + + ClassSendDeviceIoControlSynchronous( + IOCTL_SCSI_GET_ADDRESS, + Fdo, + &scsiAddress, + 0, + sizeof(SCSI_ADDRESS), + FALSE, + &ioStatus + ); + + if (!NT_SUCCESS(ioStatus.Status)) { + + status = ioStatus.Status; + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, + "UpdateInformationInRegistry: Get Address failed %lx\n", + status)); + LEAVE; + + } else { + + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, + "GetAddress: Port %x, Path %x, Target %x, Lun %x\n", + scsiAddress.PortNumber, + scsiAddress.PathId, + scsiAddress.TargetId, + scsiAddress.Lun)); + + } + + status = RtlStringCchPrintfA((NTSTRSAFE_PSTR)buffer, + sizeof(buffer)-1, + "\\Registry\\Machine\\Hardware\\DeviceMap\\Scsi\\Scsi Port %d\\Scsi Bus %d\\Target Id %d\\Logical Unit Id %d", + scsiAddress.PortNumber, + scsiAddress.PathId, + scsiAddress.TargetId, + scsiAddress.Lun); + + if (!NT_SUCCESS(status)) { + LEAVE; + } + + RtlInitString(&string, (PCSZ)buffer); + + status = RtlAnsiStringToUnicodeString(&unicodeRegistryPath, + &string, + TRUE); + + if (!NT_SUCCESS(status)) { + LEAVE; + } + + // + // Open the registry key for the scsi information for this + // scsibus, target, lun. + // + + InitializeObjectAttributes(&objectAttributes, + &unicodeRegistryPath, + OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, + NULL, + NULL); + + status = ZwOpenKey(&targetKey, + KEY_READ | KEY_WRITE, + &objectAttributes); + + if (!NT_SUCCESS(status)) { + LEAVE; + } + + // + // Now construct and attempt to create the registry value + // specifying the device name in the appropriate place in the + // device map. + // + + RtlInitUnicodeString(&unicodeName, L"DeviceName"); + + status = RtlStringCchPrintfA((NTSTRSAFE_PSTR)buffer, sizeof(buffer)-1, "%s%d", DeviceName, DeviceNumber); + if (!NT_SUCCESS(status)) { + LEAVE; + } + + RtlInitString(&string, (PCSZ)buffer); + status = RtlAnsiStringToUnicodeString(&unicodeData, + &string, + TRUE); + if (NT_SUCCESS(status)) { + status = ZwSetValueKey(targetKey, + &unicodeName, + 0, + REG_SZ, + unicodeData.Buffer, + unicodeData.Length); + } + + // + // if they sent in data, update the registry + // + + if (NT_SUCCESS(status) && InquiryDataLength) { + + NT_ASSERT(InquiryData); + + RtlInitUnicodeString(&unicodeName, L"InquiryData"); + status = ZwSetValueKey(targetKey, + &unicodeName, + 0, + REG_BINARY, + InquiryData, + InquiryDataLength); + } + + // that's all, except to clean up. + + } FINALLY { + + if (!NT_SUCCESS(status)) { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, + "Failure to update information in registry: %08x\n", + status + )); + } + + if (unicodeData.Buffer) { + RtlFreeUnicodeString(&unicodeData); + } + if (unicodeRegistryPath.Buffer) { + RtlFreeUnicodeString(&unicodeRegistryPath); + } + if (targetKey) { + ZwClose(targetKey); + } + + } + +} // end ClassUpdateInformationInRegistry() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClasspSendSynchronousCompletion() + +Routine Description: + + This completion routine will set the user event in the irp after + freeing the irp and the associated MDL (if any). + +Arguments: + + DeviceObject - the device object which requested the completion routine + + Irp - the irp being completed + + Context - unused + +Return Value: + + STATUS_MORE_PROCESSING_REQUIRED + +--*/ +NTSTATUS +ClasspSendSynchronousCompletion( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP Irp, + IN PVOID Context + ) +{ + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClasspSendSynchronousCompletion: %p %p %p\n", + DeviceObject, Irp, Context)); + // + // First set the status and information fields in the io status block + // provided by the caller. + // + + *(Irp->UserIosb) = Irp->IoStatus; + + // + // Unlock the pages for the data buffer. + // + + if(Irp->MdlAddress) { + MmUnlockPages(Irp->MdlAddress); + IoFreeMdl(Irp->MdlAddress); + } + + // + // Signal the caller's event. + // + + KeSetEvent(Irp->UserEvent, IO_NO_INCREMENT, FALSE); + + // + // Free the MDL and the IRP. + // + + IoFreeIrp(Irp); + + return STATUS_MORE_PROCESSING_REQUIRED; +} // end ClasspSendSynchronousCompletion() + +/*++ + + ISSUE-2000/02/20-henrygab Not documented ClasspRegisterMountedDeviceInterface + +--*/ +VOID +ClasspRegisterMountedDeviceInterface( + IN PDEVICE_OBJECT DeviceObject + ) +{ + + PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension; + BOOLEAN isFdo = commonExtension->IsFdo; + PDEVICE_OBJECT pdo; + UNICODE_STRING interfaceName; + + NTSTATUS status; + + PAGED_CODE(); + + if(isFdo) { + + PFUNCTIONAL_DEVICE_EXTENSION functionalExtension; + + functionalExtension = + (PFUNCTIONAL_DEVICE_EXTENSION) commonExtension; + pdo = functionalExtension->LowerPdo; + } else { + pdo = DeviceObject; + } + +#pragma prefast(suppress:6014, "The allocated memory that interfaceName points to will be freed in ClassRemoveDevice().") + status = IoRegisterDeviceInterface( + pdo, + &MOUNTDEV_MOUNTED_DEVICE_GUID, + NULL, + &interfaceName + ); + + if(NT_SUCCESS(status)) { + + // + // Copy the interface name before setting the interface state - the + // name is needed by the components we notify. + // + + commonExtension->MountedDeviceInterfaceName = interfaceName; + status = IoSetDeviceInterfaceState(&interfaceName, TRUE); + + if(!NT_SUCCESS(status)) { + RtlFreeUnicodeString(&interfaceName); + } + } + + if(!NT_SUCCESS(status)) { + RtlInitUnicodeString(&(commonExtension->MountedDeviceInterfaceName), + NULL); + } + return; +} // end ClasspRegisterMountedDeviceInterface() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassSendDeviceIoControlSynchronous() + +Routine Description: + + This routine is based upon IoBuildDeviceIoControlRequest(). It has been + modified to reduce code and memory by not double-buffering the io, using + the same buffer for both input and output, allocating and deallocating + the mdl on behalf of the caller, and waiting for the io to complete. + + This routine also works around the rare cases in which APC's are disabled. + Since IoBuildDeviceIoControl() used APC's to signal completion, this had + led to a number of difficult-to-detect hangs, where the irp was completed, + but the event passed to IoBuild..() was still being waited upon by the + caller. + +Arguments: + + IoControlCode - the IOCTL to send + + TargetDeviceObject - the device object that should handle the ioctl + + Buffer - the input and output buffer, or NULL if no input/output + + InputBufferLength - the number of bytes prepared for the IOCTL in Buffer + + OutputBufferLength - the number of bytes to be filled in upon success + + InternalDeviceIoControl - if TRUE, uses IRP_MJ_INTERNAL_DEVICE_CONTROL + + IoStatus - the status block that contains the results of the operation + +Return Value: + +--*/ +VOID +ClassSendDeviceIoControlSynchronous( + _In_ ULONG IoControlCode, + _In_ PDEVICE_OBJECT TargetDeviceObject, + _Inout_updates_opt_(_Inexpressible_(max(InputBufferLength, OutputBufferLength))) PVOID Buffer, + _In_ ULONG InputBufferLength, + _In_ ULONG OutputBufferLength, + _In_ BOOLEAN InternalDeviceIoControl, + _Out_ PIO_STATUS_BLOCK IoStatus + ) +{ + PIRP irp; + PIO_STACK_LOCATION irpSp; + ULONG method; + + PAGED_CODE(); + + irp = NULL; + method = IoControlCode & 3; + + #if DBG // Begin Argument Checking (nop in fre version) + + NT_ASSERT(ARGUMENT_PRESENT(IoStatus)); + + if ((InputBufferLength != 0) || (OutputBufferLength != 0)) { + NT_ASSERT(ARGUMENT_PRESENT(Buffer)); + } + else { + NT_ASSERT(!ARGUMENT_PRESENT(Buffer)); + } + #endif + + // + // Begin by allocating the IRP for this request. Do not charge quota to + // the current process for this IRP. + // + + irp = IoAllocateIrp(TargetDeviceObject->StackSize, FALSE); + if (!irp) { + IoStatus->Information = 0; + IoStatus->Status = STATUS_INSUFFICIENT_RESOURCES; + return; + } + + // + // Get a pointer to the stack location of the first driver which will be + // invoked. This is where the function codes and the parameters are set. + // + + irpSp = IoGetNextIrpStackLocation(irp); + + // + // Set the major function code based on the type of device I/O control + // function the caller has specified. + // + + if (InternalDeviceIoControl) { + irpSp->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL; + } else { + irpSp->MajorFunction = IRP_MJ_DEVICE_CONTROL; + } + + // + // Copy the caller's parameters to the service-specific portion of the + // IRP for those parameters that are the same for all four methods. + // + + irpSp->Parameters.DeviceIoControl.OutputBufferLength = OutputBufferLength; + irpSp->Parameters.DeviceIoControl.InputBufferLength = InputBufferLength; + irpSp->Parameters.DeviceIoControl.IoControlCode = IoControlCode; + + // + // Get the method bits from the I/O control code to determine how the + // buffers are to be passed to the driver. + // + + switch (method) + { + // + // case 0 + // + case METHOD_BUFFERED: + { + if ((InputBufferLength != 0) || (OutputBufferLength != 0)) + { + irp->AssociatedIrp.SystemBuffer = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned, + max(InputBufferLength, OutputBufferLength), + CLASS_TAG_DEVICE_CONTROL); + if (irp->AssociatedIrp.SystemBuffer == NULL) + { + IoFreeIrp(irp); + + IoStatus->Information = 0; + IoStatus->Status = STATUS_INSUFFICIENT_RESOURCES; + return; + } + + if (InputBufferLength != 0) + { + RtlCopyMemory(irp->AssociatedIrp.SystemBuffer, Buffer, InputBufferLength); + } + } + + irp->UserBuffer = Buffer; + + break; + } + + // + // case 1, case 2 + // + case METHOD_IN_DIRECT: + case METHOD_OUT_DIRECT: + { + if (InputBufferLength != 0) + { + irp->AssociatedIrp.SystemBuffer = Buffer; + } + + if (OutputBufferLength != 0) + { + irp->MdlAddress = IoAllocateMdl(Buffer, + OutputBufferLength, + FALSE, + FALSE, + (PIRP) NULL); + if (irp->MdlAddress == NULL) + { + IoFreeIrp(irp); + + IoStatus->Information = 0; + IoStatus->Status = STATUS_INSUFFICIENT_RESOURCES; + return; + } + + try + { + MmProbeAndLockPages(irp->MdlAddress, + KernelMode, + (method == METHOD_IN_DIRECT) ? IoReadAccess : IoWriteAccess); + } + #pragma warning(suppress: 6320) // We want to handle any exception that MmProbeAndLockPages might throw + except(EXCEPTION_EXECUTE_HANDLER) + { + IoFreeMdl(irp->MdlAddress); + IoFreeIrp(irp); + + IoStatus->Information = 0; + IoStatus->Status = GetExceptionCode(); + return; + } + } + + break; + } + + // + // case 3 + // + case METHOD_NEITHER: + { + NT_ASSERT(!"ClassSendDeviceIoControlSynchronous does not support METHOD_NEITHER Ioctls"); + + IoFreeIrp(irp); + + IoStatus->Information = 0; + IoStatus->Status = STATUS_NOT_SUPPORTED; + return; + } + } + + irp->Tail.Overlay.Thread = PsGetCurrentThread(); + + // + // send the irp synchronously + // + + ClassSendIrpSynchronous(TargetDeviceObject, irp); + + // + // copy the iostatus block for the caller + // + + *IoStatus = irp->IoStatus; + + // + // free any allocated resources + // + + switch (method) { + case METHOD_BUFFERED: { + + NT_ASSERT(irp->UserBuffer == Buffer); + + // + // first copy the buffered result, if any + // Note that there are no security implications in + // not checking for success since only drivers can + // call into this routine anyways... + // + + if (OutputBufferLength != 0) { + #pragma warning(suppress: 6386) // Buffer's size is max(InputBufferLength, OutputBufferLength) + RtlCopyMemory(Buffer, // irp->UserBuffer + irp->AssociatedIrp.SystemBuffer, + OutputBufferLength + ); + } + + // + // then free the memory allocated to buffer the io + // + + if ((InputBufferLength !=0) || (OutputBufferLength != 0)) { + FREE_POOL(irp->AssociatedIrp.SystemBuffer); + } + break; + } + + case METHOD_IN_DIRECT: + case METHOD_OUT_DIRECT: { + + // + // we alloc a mdl if there is an output buffer specified + // free it here after unlocking the pages + // + + if (OutputBufferLength != 0) { + NT_ASSERT(irp->MdlAddress != NULL); + MmUnlockPages(irp->MdlAddress); + IoFreeMdl(irp->MdlAddress); + irp->MdlAddress = (PMDL) NULL; + } + break; + } + + case METHOD_NEITHER: { + NT_ASSERT(!"Code is out of date"); + break; + } + } + + // + // we always have allocated an irp. free it here. + // + + IoFreeIrp(irp); + irp = (PIRP) NULL; + + // + // return the io status block's status to the caller + // + + return; +} // end ClassSendDeviceIoControlSynchronous() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassForwardIrpSynchronous() + +Routine Description: + + Forwards a given irp to the next lower device object. + +Arguments: + + CommonExtension - the common class extension + + Irp - the request to forward down the stack + +Return Value: + +--*/ +NTSTATUS +ClassForwardIrpSynchronous( + _In_ PCOMMON_DEVICE_EXTENSION CommonExtension, + _In_ PIRP Irp + ) +{ + IoCopyCurrentIrpStackLocationToNext(Irp); + return ClassSendIrpSynchronous(CommonExtension->LowerDeviceObject, Irp); +} // end ClassForwardIrpSynchronous() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassSendIrpSynchronous() + +Routine Description: + + This routine sends the given irp to the given device object, and waits for + it to complete. On debug versions, will print out a debug message and + optionally assert for "lost" irps based upon classpnp's globals + +Arguments: + + TargetDeviceObject - the device object to handle this irp + + Irp - the request to be sent + +Return Value: + +--*/ +NTSTATUS +ClassSendIrpSynchronous( + _In_ PDEVICE_OBJECT TargetDeviceObject, + _In_ PIRP Irp + ) +{ + KEVENT event; + NTSTATUS status; + + NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL); + NT_ASSERT(TargetDeviceObject != NULL); + NT_ASSERT(Irp != NULL); + NT_ASSERT(Irp->StackCount >= TargetDeviceObject->StackSize); + + // + // ISSUE-2000/02/20-henrygab What if APCs are disabled? + // May need to enter critical section before IoCallDriver() + // until the event is hit? + // + + KeInitializeEvent(&event, SynchronizationEvent, FALSE); + IoSetCompletionRoutine(Irp, ClassSignalCompletion, &event, + TRUE, TRUE, TRUE); + + status = IoCallDriver(TargetDeviceObject, Irp); + _Analysis_assume_(status!=STATUS_PENDING); + if (status == STATUS_PENDING) { + + #if DBG + LARGE_INTEGER timeout; + + timeout.QuadPart = (LONGLONG)(-1 * 10 * 1000 * (LONGLONG)1000 * + ClasspnpGlobals.SecondsToWaitForIrps); + + do { + status = KeWaitForSingleObject(&event, + Executive, + KernelMode, + FALSE, + &timeout); + + + if (status == STATUS_TIMEOUT) { + + // + // This DebugPrint should almost always be investigated by the + // party who sent the irp and/or the current owner of the irp. + // Synchronous Irps should not take this long (currently 30 + // seconds) without good reason. This points to a potentially + // serious problem in the underlying device stack. + // + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassSendIrpSynchronous: (%p) irp %p did not " + "complete within %x seconds\n", + TargetDeviceObject, Irp, + ClasspnpGlobals.SecondsToWaitForIrps + )); + + if (ClasspnpGlobals.BreakOnLostIrps != 0) { + NT_ASSERT(!" - Irp failed to complete within 30 seconds - "); + } + } + + + } while (status==STATUS_TIMEOUT); + #else + (VOID)KeWaitForSingleObject(&event, + Executive, + KernelMode, + FALSE, + NULL); + #endif + + status = Irp->IoStatus.Status; + } + + return status; +} // end ClassSendIrpSynchronous() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassGetVpb() + +Routine Description: + + This routine returns the current VPB (Volume Parameter Block) for the + given device object. + The Vpb field is only visible in the ntddk.h (not the wdm.h) definition + of DEVICE_OBJECT; hence this exported function. + +Arguments: + + DeviceObject - the device to get the VPB for + +Return Value: + + the VPB, or NULL if none. + +--*/ +PVPB +ClassGetVpb( + _In_ PDEVICE_OBJECT DeviceObject + ) +{ +#pragma prefast(suppress:28175) + return DeviceObject->Vpb; +} // end ClassGetVpb() + +/*++ + + ISSUE-2000/02/20-henrygab Not documented ClasspAllocateReleaseRequest + +--*/ +NTSTATUS +ClasspAllocateReleaseRequest( + IN PDEVICE_OBJECT Fdo + ) +{ + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension; + + PAGED_CODE(); + + KeInitializeSpinLock(&(fdoExtension->ReleaseQueueSpinLock)); + + fdoExtension->ReleaseQueueNeeded = FALSE; + fdoExtension->ReleaseQueueInProgress = FALSE; + fdoExtension->ReleaseQueueIrpFromPool = FALSE; + + // + // The class driver is responsible for allocating a properly sized irp, + // or ClassReleaseQueue will attempt to do it on the first error. + // + + fdoExtension->ReleaseQueueIrp = NULL; + + // + // Write length to SRB. + // + + fdoExtension->ReleaseQueueSrb.Length = sizeof(SCSI_REQUEST_BLOCK); + + return STATUS_SUCCESS; +} // end ClasspAllocateReleaseRequest() + +/*++ + + ISSUE-2000/02/20-henrygab Not documented ClasspFreeReleaseRequest + +--*/ +VOID +ClasspFreeReleaseRequest( + IN PDEVICE_OBJECT Fdo + ) +{ + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension; + + PAGED_CODE(); + + //KIRQL oldIrql; + + NT_ASSERT(fdoExtension->CommonExtension.IsRemoved != NO_REMOVE); + + // + // free anything the driver allocated + // + + if (fdoExtension->ReleaseQueueIrp) { + if (fdoExtension->ReleaseQueueIrpFromPool) { + FREE_POOL(fdoExtension->ReleaseQueueIrp); + } else { + IoFreeIrp(fdoExtension->ReleaseQueueIrp); + } + fdoExtension->ReleaseQueueIrp = NULL; + } + + // + // free anything that we allocated + // + + if ((fdoExtension->PrivateFdoData) && + (fdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated)) { + + FREE_POOL(fdoExtension->PrivateFdoData->ReleaseQueueIrp); + fdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated = FALSE; + } + + return; +} // end ClasspFreeReleaseRequest() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassReleaseQueue() + +Routine Description: + + This routine issues an internal device control command + to the port driver to release a frozen queue. The call + is issued asynchronously as ClassReleaseQueue will be invoked + from the IO completion DPC (and will have no context to + wait for a synchronous call to complete). + + This routine must be called with the remove lock held. + +Arguments: + + Fdo - The functional device object for the device with the frozen queue. + +Return Value: + + None. + +--*/ +VOID +ClassReleaseQueue( + _In_ PDEVICE_OBJECT Fdo + ) +{ + ClasspReleaseQueue(Fdo, NULL); + return; +} // end ClassReleaseQueue() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClasspAllocateReleaseQueueIrp() + +Routine Description: + + This routine allocates the release queue irp held in classpnp's private + extension. This was added to allow no-memory conditions to be more + survivable. + +Return Value: + + NT_SUCCESS value. + +Notes: + + Does not grab the spinlock. Should only be called from StartDevice() + routine. May be called elsewhere for poorly-behaved drivers that cause + the queue to lockup before the device is started. This should *never* + occur, since it's illegal to send a request to a non-started PDO. This + condition is checked for in ClasspReleaseQueue(). + +--*/ +NTSTATUS +ClasspAllocateReleaseQueueIrp( + PFUNCTIONAL_DEVICE_EXTENSION FdoExtension + ) +{ + UCHAR lowerStackSize; + + // + // do an initial check w/o the spinlock + // + + if (FdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated) { + return STATUS_SUCCESS; + } + + + lowerStackSize = FdoExtension->CommonExtension.LowerDeviceObject->StackSize; + + // + // don't allocate one if one is in progress! this means whoever called + // this routine didn't check if one was in progress. + // + + NT_ASSERT(!(FdoExtension->ReleaseQueueInProgress)); + + FdoExtension->PrivateFdoData->ReleaseQueueIrp = + ExAllocatePoolWithTag(NonPagedPoolNx, + IoSizeOfIrp(lowerStackSize), + CLASS_TAG_RELEASE_QUEUE + ); + + if (FdoExtension->PrivateFdoData->ReleaseQueueIrp == NULL) { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_PNP, "ClassPnpStartDevice: Cannot allocate for " + "release queue irp\n")); + return STATUS_INSUFFICIENT_RESOURCES; + } + IoInitializeIrp(FdoExtension->PrivateFdoData->ReleaseQueueIrp, + IoSizeOfIrp(lowerStackSize), + lowerStackSize); + FdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated = TRUE; + + return STATUS_SUCCESS; +} + +/*++//////////////////////////////////////////////////////////////////////////// + +ClasspAllocatePowerProcessIrp() + +Routine Description: + + This routine allocates the power process irp. + This routine should be called after PrivateFdoData is allocated. + +Return Value: + + NTSTATUS value. + +Notes: + + Should only be called from StartDevice() + +--*/ +NTSTATUS +ClasspAllocatePowerProcessIrp( + PFUNCTIONAL_DEVICE_EXTENSION FdoExtension + ) +{ + UCHAR stackSize; + + NT_ASSERT(FdoExtension->PrivateFdoData != NULL); + + stackSize = FdoExtension->CommonExtension.LowerDeviceObject->StackSize + 1; + + FdoExtension->PrivateFdoData->PowerProcessIrp = ExAllocatePoolWithTag(NonPagedPoolNx, + IoSizeOfIrp(stackSize), + CLASS_TAG_POWER + ); + + if (FdoExtension->PrivateFdoData->PowerProcessIrp == NULL) { + + return STATUS_INSUFFICIENT_RESOURCES; + } else { + + IoInitializeIrp(FdoExtension->PrivateFdoData->PowerProcessIrp, + IoSizeOfIrp(stackSize), + stackSize); + + return STATUS_SUCCESS; + } +} + + +/*++//////////////////////////////////////////////////////////////////////////// + +ClasspReleaseQueue() + +Routine Description: + + This routine issues an internal device control command + to the port driver to release a frozen queue. The call + is issued asynchronously as ClassReleaseQueue will be invoked + from the IO completion DPC (and will have no context to + wait for a synchronous call to complete). + + This routine must be called with the remove lock held. + +Arguments: + + Fdo - The functional device object for the device with the frozen queue. + + ReleaseQueueIrp - If this irp is supplied then the test to determine whether + a release queue request is in progress will be ignored. + The irp provided must be the IRP originally allocated + for release queue requests (so this parameter can only + really be provided by the release queue completion + routine.) + +Return Value: + + None. + +--*/ +VOID +ClasspReleaseQueue( + IN PDEVICE_OBJECT Fdo, + IN PIRP ReleaseQueueIrp OPTIONAL + ) +{ + PIO_STACK_LOCATION irpStack; + PIRP irp; + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension; + PDEVICE_OBJECT lowerDevice; + PSTORAGE_REQUEST_BLOCK_HEADER srb; + KIRQL currentIrql; + ULONG function; + + lowerDevice = fdoExtension->CommonExtension.LowerDeviceObject; + + // + // we raise irql seperately so we're not swapped out or suspended + // while holding the release queue irp in this routine. this lets + // us release the spin lock before lowering irql. + // + + KeRaiseIrql(DISPATCH_LEVEL, ¤tIrql); + + KeAcquireSpinLockAtDpcLevel(&(fdoExtension->ReleaseQueueSpinLock)); + + // + // make sure that if they passed us an irp, it matches our allocated irp. + // + + NT_ASSERT((ReleaseQueueIrp == NULL) || + (ReleaseQueueIrp == fdoExtension->PrivateFdoData->ReleaseQueueIrp)); + + // + // ASSERT that we've already allocated this. (should not occur) + // try to allocate it anyways, then finally bugcheck if + // there's still no memory... + // + + NT_ASSERT(fdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated); + if (!fdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated) { + ClasspAllocateReleaseQueueIrp(fdoExtension); + } + if (!fdoExtension->PrivateFdoData->ReleaseQueueIrpAllocated) { + KeBugCheckEx(SCSI_DISK_DRIVER_INTERNAL, 0x12, (ULONG_PTR)Fdo, 0x0, 0x0); + } + + if ((fdoExtension->ReleaseQueueInProgress) && (ReleaseQueueIrp == NULL)) { + + // + // Someone is already using the irp - just set the flag to indicate that + // we need to release the queue again. + // + + fdoExtension->ReleaseQueueNeeded = TRUE; + KeReleaseSpinLockFromDpcLevel(&(fdoExtension->ReleaseQueueSpinLock)); + KeLowerIrql(currentIrql); + return; + + } + + // + // Mark that there is a release queue in progress and drop the spinlock. + // + + fdoExtension->ReleaseQueueInProgress = TRUE; + if (ReleaseQueueIrp) { + irp = ReleaseQueueIrp; + } else { + irp = fdoExtension->PrivateFdoData->ReleaseQueueIrp; + } + + if (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) { + srb = (PSTORAGE_REQUEST_BLOCK_HEADER)&(fdoExtension->PrivateFdoData->ReleaseQueueSrb.SrbEx); + } else { + srb = (PSTORAGE_REQUEST_BLOCK_HEADER)&(fdoExtension->ReleaseQueueSrb); + } + + KeReleaseSpinLockFromDpcLevel(&(fdoExtension->ReleaseQueueSpinLock)); + + NT_ASSERT(irp != NULL); + + irpStack = IoGetNextIrpStackLocation(irp); + + irpStack->MajorFunction = IRP_MJ_SCSI; + + SrbSetOriginalRequest(srb, irp); + + // + // Store the SRB address in next stack for port driver. + // + + irpStack->Parameters.Scsi.Srb = (PSCSI_REQUEST_BLOCK)srb; + + // + // If this device is removable then flush the queue. This will also + // release it. + // + + if (TEST_FLAG(Fdo->Characteristics, FILE_REMOVABLE_MEDIA)){ + function = SRB_FUNCTION_FLUSH_QUEUE; + } + else { + function = SRB_FUNCTION_RELEASE_QUEUE; + } + + if (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) { + ((PSTORAGE_REQUEST_BLOCK)srb)->SrbFunction = function; + } else { + srb->Function = (UCHAR)function; + } + + ClassAcquireRemoveLock(Fdo, irp); + + IoSetCompletionRoutine(irp, + ClassReleaseQueueCompletion, + Fdo, + TRUE, + TRUE, + TRUE); + + IoCallDriver(lowerDevice, irp); + + KeLowerIrql(currentIrql); + + return; + +} // end ClassReleaseQueue() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassReleaseQueueCompletion() + +Routine Description: + + This routine is called when an asynchronous I/O request + which was issused by the class driver completes. Examples of such requests + are release queue or START UNIT. This routine releases the queue if + necessary. It then frees the context and the IRP. + +Arguments: + + DeviceObject - The device object for the logical unit; however since this + is the top stack location the value is NULL. + + Irp - Supplies a pointer to the Irp to be processed. + + Context - Supplies the context to be used to process this request. + +Return Value: + + None. + +--*/ +NTSTATUS +ClassReleaseQueueCompletion( + PDEVICE_OBJECT DeviceObject, + PIRP Irp, + PVOID Context + ) +{ + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension; + KIRQL oldIrql; + + BOOLEAN releaseQueueNeeded; + + if (Context == NULL) { + NT_ASSERT(Context != NULL); + return STATUS_INVALID_PARAMETER; + } + + DeviceObject = Context; + + fdoExtension = DeviceObject->DeviceExtension; + + ClassReleaseRemoveLock(DeviceObject, Irp); + + // + // Grab the spinlock and clear the release queue in progress flag so others + // can run. Save (and clear) the state of the release queue needed flag + // so that we can issue a new release queue outside the spinlock. + // + + KeAcquireSpinLock(&(fdoExtension->ReleaseQueueSpinLock), &oldIrql); + + releaseQueueNeeded = fdoExtension->ReleaseQueueNeeded; + + fdoExtension->ReleaseQueueNeeded = FALSE; + fdoExtension->ReleaseQueueInProgress = FALSE; + + KeReleaseSpinLock(&(fdoExtension->ReleaseQueueSpinLock), oldIrql); + + // + // If we need a release queue then issue one now. Another processor may + // have already started one in which case we'll try to issue this one after + // it is done - but we should never recurse more than one deep. + // + + if(releaseQueueNeeded) { + ClasspReleaseQueue(DeviceObject, Irp); + } + + // + // Indicate the I/O system should stop processing the Irp completion. + // + + return STATUS_MORE_PROCESSING_REQUIRED; + +} // ClassAsynchronousCompletion() + + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassAcquireChildLock() + +Routine Description: + + This routine acquires the lock protecting children PDOs. It may be + acquired recursively by the same thread, but must be release by the + thread once for each acquisition. + +Arguments: + + FdoExtension - the device whose child list is protected. + +Return Value: + + None + +--*/ +_IRQL_requires_max_(PASSIVE_LEVEL) +VOID +ClassAcquireChildLock( + _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension + ) +{ + PAGED_CODE(); + + if(FdoExtension->ChildLockOwner != KeGetCurrentThread()) { + (VOID)KeWaitForSingleObject(&FdoExtension->ChildLock, + Executive, KernelMode, + FALSE, NULL); + + NT_ASSERT(FdoExtension->ChildLockOwner == NULL); + NT_ASSERT(FdoExtension->ChildLockAcquisitionCount == 0); + + FdoExtension->ChildLockOwner = KeGetCurrentThread(); + } else { + NT_ASSERT(FdoExtension->ChildLockAcquisitionCount != 0); + } + + FdoExtension->ChildLockAcquisitionCount++; + return; +} + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassReleaseChildLock() ISSUE-2000/02/18-henrygab - not documented + +Routine Description: + + This routine releases the lock protecting children PDOs. It must be + called once for each time ClassAcquireChildLock was called. + +Arguments: + + FdoExtension - the device whose child list is protected + +Return Value: + + None. + +--*/ +VOID +ClassReleaseChildLock( + _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension + ) +{ + NT_ASSERT(FdoExtension->ChildLockOwner == KeGetCurrentThread()); + NT_ASSERT(FdoExtension->ChildLockAcquisitionCount != 0); + + FdoExtension->ChildLockAcquisitionCount -= 1; + + if(FdoExtension->ChildLockAcquisitionCount == 0) { + FdoExtension->ChildLockOwner = NULL; + KeSetEvent(&FdoExtension->ChildLock, IO_NO_INCREMENT, FALSE); + } + + return; +} // end ClassReleaseChildLock( + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassAddChild() + +Routine Description: + + This routine will insert a new child into the head of the child list. + +Arguments: + + Parent - the child's parent (contains the head of the list) + Child - the child to be inserted. + AcquireLock - whether the child lock should be acquired (TRUE) or whether + it's already been acquired by or on behalf of the caller + (FALSE). + +Return Value: + + None. + +--*/ +VOID +ClassAddChild( + _In_ PFUNCTIONAL_DEVICE_EXTENSION Parent, + _In_ PPHYSICAL_DEVICE_EXTENSION Child, + _In_ BOOLEAN AcquireLock + ) +{ + if(AcquireLock) { + ClassAcquireChildLock(Parent); + } + + #if DBG + // + // Make sure this child's not already in the list. + // + { + PPHYSICAL_DEVICE_EXTENSION testChild; + + for (testChild = Parent->CommonExtension.ChildList; + testChild != NULL; + testChild = testChild->CommonExtension.ChildList) { + + NT_ASSERT(testChild != Child); + } + } + #endif + + Child->CommonExtension.ChildList = Parent->CommonExtension.ChildList; + Parent->CommonExtension.ChildList = Child; + + if(AcquireLock) { + ClassReleaseChildLock(Parent); + } + return; +} // end ClassAddChild() + +/*++//////////////////////////////////////////////////////////////////////////// + +ClassRemoveChild() + +Routine Description: + + This routine will remove a child from the child list. + +Arguments: + + Parent - the parent to be removed from. + + Child - the child to be removed or NULL if the first child should be + removed. + + AcquireLock - whether the child lock should be acquired (TRUE) or whether + it's already been acquired by or on behalf of the caller + (FALSE). + +Return Value: + + A pointer to the child which was removed or NULL if no such child could + be found in the list (or if Child was NULL but the list is empty). + +--*/ +PPHYSICAL_DEVICE_EXTENSION +ClassRemoveChild( + IN PFUNCTIONAL_DEVICE_EXTENSION Parent, + IN PPHYSICAL_DEVICE_EXTENSION Child, + IN BOOLEAN AcquireLock + ) +{ + if(AcquireLock) { + ClassAcquireChildLock(Parent); + } + + TRY { + PCOMMON_DEVICE_EXTENSION previousChild = &Parent->CommonExtension; + + // + // If the list is empty then bail out now. + // + + if(Parent->CommonExtension.ChildList == NULL) { + Child = NULL; + LEAVE; + } + + // + // If the caller specified a child then find the child object before + // it. If none was specified then the FDO is the child object before + // the one we want to remove. + // + + if(Child != NULL) { + + // + // Scan through the child list to find the entry which points to + // this one. + // + + do { + NT_ASSERT(previousChild != &Child->CommonExtension); + + if(previousChild->ChildList == Child) { + break; + } + + previousChild = &previousChild->ChildList->CommonExtension; + } while(previousChild != NULL); + + if(previousChild == NULL) { + Child = NULL; + LEAVE; + } + } + + // + // Save the next child away then unlink it from the list. + // + + Child = previousChild->ChildList; + previousChild->ChildList = Child->CommonExtension.ChildList; + Child->CommonExtension.ChildList = NULL; + + } FINALLY { + if(AcquireLock) { + ClassReleaseChildLock(Parent); + } + } + return Child; +} // end ClassRemoveChild() + + +/*++ + + ISSUE-2000/02/20-henrygab Not documented ClasspRetryRequestDpc + +--*/ +VOID +ClasspRetryRequestDpc( + IN PKDPC Dpc, + IN PVOID DeferredContext, + IN PVOID Arg1, + IN PVOID Arg2 + ) +{ + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension; + PCOMMON_DEVICE_EXTENSION commonExtension; + PCLASS_PRIVATE_FDO_DATA fdoData; + PCLASS_RETRY_INFO retryList; + KIRQL irql; + PDEVICE_OBJECT DeviceObject = (PDEVICE_OBJECT)DeferredContext; + + UNREFERENCED_PARAMETER(Dpc); + UNREFERENCED_PARAMETER(Arg1); + UNREFERENCED_PARAMETER(Arg2); + + if (DeferredContext == NULL) { + NT_ASSERT(DeferredContext != NULL); + return; + } + + commonExtension = DeviceObject->DeviceExtension; + NT_ASSERT(commonExtension->IsFdo); + fdoExtension = DeviceObject->DeviceExtension; + fdoData = fdoExtension->PrivateFdoData; + + + KeAcquireSpinLock(&fdoData->Retry.Lock, &irql); + { + LARGE_INTEGER now; + KeQueryTickCount(&now); + + // + // if CurrentTick is less than now + // fire another DPC + // else + // retry entire list + // endif + // + + if (now.QuadPart < fdoData->Retry.Tick.QuadPart) { + + ClasspRetryDpcTimer(fdoData); + retryList = NULL; + + } else { + + retryList = fdoData->Retry.ListHead; + fdoData->Retry.ListHead = NULL; + fdoData->Retry.Delta.QuadPart = (LONGLONG)0; + fdoData->Retry.Tick.QuadPart = (LONGLONG)0; + + } + } + KeReleaseSpinLock(&fdoData->Retry.Lock, irql); + + while (retryList != NULL) { + + PIRP irp; + + + irp = CONTAINING_RECORD(retryList, IRP, Tail.Overlay.DriverContext[0]); + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassRetry: -- %p\n", irp)); + retryList = retryList->Next; + #if DBG + irp->Tail.Overlay.DriverContext[0] = ULongToPtr(0xdddddddd); // invalidate data + irp->Tail.Overlay.DriverContext[1] = ULongToPtr(0xdddddddd); // invalidate data + irp->Tail.Overlay.DriverContext[2] = ULongToPtr(0xdddddddd); // invalidate data + irp->Tail.Overlay.DriverContext[3] = ULongToPtr(0xdddddddd); // invalidate data + #endif + + + if (NO_REMOVE == InterlockedCompareExchange((volatile LONG *)&commonExtension->IsRemoved, REMOVE_PENDING, REMOVE_PENDING)) { + + IoCallDriver(commonExtension->LowerDeviceObject, irp); + + } else { + + PIO_STACK_LOCATION irpStack; + + // + // Ensure that we don't skip a completion routine (equivalent of sending down a request + // to a device after it has received a remove and it completes the request. We need to + // mimic that behavior here). + // + IoSetNextIrpStackLocation(irp); + + irpStack = IoGetCurrentIrpStackLocation(irp); + + if (irpStack->MajorFunction == IRP_MJ_SCSI) { + + PSCSI_REQUEST_BLOCK srb = irpStack->Parameters.Scsi.Srb; + + if (srb) { + srb->SrbStatus = SRB_STATUS_NO_DEVICE; + } + } + + // + // Ensure that no retries will take place. This takes care of requests that are either + // not IRP_MJ_SCSI or for ones where the SRB was passed in to the completion routine + // as a context as opposed to an argument on the IRP stack location. + // + irpStack->Parameters.Others.Argument4 = (PVOID)0; + + irp->IoStatus.Status = STATUS_NO_SUCH_DEVICE; + irp->IoStatus.Information = 0; + IoCompleteRequest(irp, IO_NO_INCREMENT); + } + + } + return; + +} // end ClasspRetryRequestDpc() + +/*++ + + ISSUE-2000/02/20-henrygab Not documented ClassRetryRequest + +--*/ +VOID +ClassRetryRequest( + IN PDEVICE_OBJECT SelfDeviceObject, + IN PIRP Irp, + _In_ _In_range_(0,MAXIMUM_RETRY_FOR_SINGLE_IO_IN_100NS_UNITS) // 100 seconds; already an assert on this... + IN LONGLONG TimeDelta100ns // in 100ns units + ) +{ + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension; + PCLASS_PRIVATE_FDO_DATA fdoData; + PCLASS_RETRY_INFO retryInfo; + LARGE_INTEGER delta; + KIRQL irql; + + // + // this checks we aren't destroying irps + // + NT_ASSERT(sizeof(CLASS_RETRY_INFO) <= (4*sizeof(PVOID))); + + fdoExtension = SelfDeviceObject->DeviceExtension; + + if (!fdoExtension->CommonExtension.IsFdo) { + + // + // this debug print/assertion should ALWAYS be investigated. + // ClassRetryRequest can currently only be used by FDO's + // + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL, "ClassRetryRequestEx: LOST IRP %p\n", Irp)); + NT_ASSERT(!"ClassRetryRequestEx Called From PDO? LOST IRP"); + return; + + } + + fdoData = fdoExtension->PrivateFdoData; + + if (TimeDelta100ns < 0) { + NT_ASSERT(!"ClassRetryRequest - must use positive delay"); + TimeDelta100ns *= -1; + } + + /* + * We are going to queue the irp and send it down in a timer DPC. + * This means that we may be causing the irp to complete on a different thread than the issuing thread. + * So mark the irp pending. + */ + IoMarkIrpPending(Irp); + + // + // prepare what we can out of the loop + // + + retryInfo = (PCLASS_RETRY_INFO)(&Irp->Tail.Overlay.DriverContext[0]); + RtlZeroMemory(retryInfo, sizeof(CLASS_RETRY_INFO)); + + delta.QuadPart = (TimeDelta100ns / fdoData->Retry.Granularity); + if (TimeDelta100ns % fdoData->Retry.Granularity) { + delta.QuadPart ++; // round up to next tick + } + if (delta.QuadPart == (LONGLONG)0) { + delta.QuadPart = MINIMUM_RETRY_UNITS; + } + + // + // now determine if we should fire another DPC or not + // + + KeAcquireSpinLock(&fdoData->Retry.Lock, &irql); + + // + // always add request to the list + // + + retryInfo->Next = fdoData->Retry.ListHead; + fdoData->Retry.ListHead = retryInfo; + + if (fdoData->Retry.Delta.QuadPart == (LONGLONG)0) { + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassRetry: +++ %p\n", Irp)); + + // + // must be exactly one item on list + // + + NT_ASSERT(fdoData->Retry.ListHead != NULL); + NT_ASSERT(fdoData->Retry.ListHead->Next == NULL); + + // + // if currentDelta is zero, always fire a DPC + // + + KeQueryTickCount(&fdoData->Retry.Tick); + fdoData->Retry.Tick.QuadPart += delta.QuadPart; + fdoData->Retry.Delta.QuadPart = delta.QuadPart; + ClasspRetryDpcTimer(fdoData); + + } else if (delta.QuadPart > fdoData->Retry.Delta.QuadPart) { + + // + // if delta is greater than the list's current delta, + // increase the DPC handling time by difference + // and update the delta to new larger value + // allow the DPC to re-fire itself if needed + // + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassRetry: ++ %p\n", Irp)); + + // + // must be at least two items on list + // + + NT_ASSERT(fdoData->Retry.ListHead != NULL); + NT_ASSERT(fdoData->Retry.ListHead->Next != NULL); + + fdoData->Retry.Tick.QuadPart -= fdoData->Retry.Delta.QuadPart; + fdoData->Retry.Tick.QuadPart += delta.QuadPart; + + fdoData->Retry.Delta.QuadPart = delta.QuadPart; + + } else { + + // + // just inserting it on the list was enough + // + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, "ClassRetry: ++ %p\n", Irp)); + + } + + + KeReleaseSpinLock(&fdoData->Retry.Lock, irql); + + +} // end ClassRetryRequest() + +/*++ + + ISSUE-2000/02/20-henrygab Not documented ClasspRetryDpcTimer + +--*/ +VOID +ClasspRetryDpcTimer( + IN PCLASS_PRIVATE_FDO_DATA FdoData + ) +{ + LARGE_INTEGER fire; + + NT_ASSERT(FdoData->Retry.Tick.QuadPart != (LONGLONG)0); + NT_ASSERT(FdoData->Retry.ListHead != NULL); // never fire an empty list + + // + // fire == (CurrentTick - now) * (100ns per tick) + // + // NOTE: Overflow is nearly impossible and is ignored here + // + + KeQueryTickCount(&fire); + fire.QuadPart = FdoData->Retry.Tick.QuadPart - fire.QuadPart; + fire.QuadPart *= FdoData->Retry.Granularity; + + // + // fire is now multiples of 100ns until should fire the timer. + // if timer should already have expired, or would fire too quickly, + // fire it in some arbitrary number of ticks to prevent infinitely + // recursing. + // + + if (fire.QuadPart < MINIMUM_RETRY_UNITS) { + fire.QuadPart = MINIMUM_RETRY_UNITS; + } + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL, + "ClassRetry: ======= %I64x ticks\n", + fire.QuadPart)); + + // + // must use negative to specify relative time to fire + // + + fire.QuadPart = fire.QuadPart * ((LONGLONG)-1); + + // + // set the timer, since this is the first addition + // + + KeSetTimerEx(&FdoData->Retry.Timer, fire, 0, &FdoData->Retry.Dpc); + + return; +} // end ClasspRetryDpcTimer() + +NTSTATUS +ClasspInitializeHotplugInfo( + IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension + ) +{ + PCLASS_PRIVATE_FDO_DATA fdoData = FdoExtension->PrivateFdoData; + DEVICE_REMOVAL_POLICY deviceRemovalPolicy = 0; + NTSTATUS status; + ULONG resultLength = 0; + ULONG writeCacheOverride; + + PAGED_CODE(); + + // + // start with some default settings + // + RtlZeroMemory(&(fdoData->HotplugInfo), sizeof(STORAGE_HOTPLUG_INFO)); + + // + // set the size (aka version) + // + + fdoData->HotplugInfo.Size = sizeof(STORAGE_HOTPLUG_INFO); + + // + // set if the device has removable media + // + + if (FdoExtension->DeviceDescriptor->RemovableMedia) { + fdoData->HotplugInfo.MediaRemovable = TRUE; + } else { + fdoData->HotplugInfo.MediaRemovable = FALSE; + } + + // + // this refers to devices which, for reasons not yet understood, + // do not fail PREVENT_MEDIA_REMOVAL requests even though they + // have no way to lock the media into the drive. this allows + // the filesystems to turn off delayed-write caching for these + // devices as well. + // + + if (TEST_FLAG(FdoExtension->PrivateFdoData->HackFlags, + FDO_HACK_CANNOT_LOCK_MEDIA)) { + fdoData->HotplugInfo.MediaHotplug = TRUE; + } else { + fdoData->HotplugInfo.MediaHotplug = FALSE; + } + + // + // Query the default removal policy from the kernel + // + + status = IoGetDeviceProperty(FdoExtension->LowerPdo, + DevicePropertyRemovalPolicy, + sizeof(DEVICE_REMOVAL_POLICY), + (PVOID)&deviceRemovalPolicy, + &resultLength); + if (!NT_SUCCESS(status)) { + return status; + } + + if (resultLength != sizeof(DEVICE_REMOVAL_POLICY)) { + return STATUS_UNSUCCESSFUL; + } + + // + // Look into the registry to see if the user has chosen + // to override the default setting for the removal policy. + // User can override only if the default removal policy is + // orderly or suprise removal. + + if ((deviceRemovalPolicy == RemovalPolicyExpectOrderlyRemoval) || + (deviceRemovalPolicy == RemovalPolicyExpectSurpriseRemoval)) { + + DEVICE_REMOVAL_POLICY userRemovalPolicy = 0; + + ClassGetDeviceParameter(FdoExtension, + CLASSP_REG_SUBKEY_NAME, + CLASSP_REG_REMOVAL_POLICY_VALUE_NAME, + (PULONG)&userRemovalPolicy); + + // + // Validate the override value and use it only if it is an + // allowed value. + // + if ((userRemovalPolicy == RemovalPolicyExpectOrderlyRemoval) || + (userRemovalPolicy == RemovalPolicyExpectSurpriseRemoval)) { + deviceRemovalPolicy = userRemovalPolicy; + } + } + + // + // use this info to set the DeviceHotplug setting + // don't rely on DeviceCapabilities, since it can't properly + // determine device relations, etc. let the kernel figure this + // stuff out instead. + // + + if (deviceRemovalPolicy == RemovalPolicyExpectSurpriseRemoval) { + fdoData->HotplugInfo.DeviceHotplug = TRUE; + } else { + fdoData->HotplugInfo.DeviceHotplug = FALSE; + } + + // + // this refers to the *filesystem* caching, but has to be included + // here since it's a per-device setting. this may change to be + // stored by the system in the future. + // + + writeCacheOverride = FALSE; + ClassGetDeviceParameter(FdoExtension, + CLASSP_REG_SUBKEY_NAME, + CLASSP_REG_WRITE_CACHE_VALUE_NAME, + &writeCacheOverride); + + if (writeCacheOverride) { + fdoData->HotplugInfo.WriteCacheEnableOverride = TRUE; + } else { + fdoData->HotplugInfo.WriteCacheEnableOverride = FALSE; + } + + return STATUS_SUCCESS; +} + +VOID +ClasspScanForClassHacks( + IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension, + IN ULONG_PTR Data + ) +{ + PAGED_CODE(); + + // + // remove invalid flags and save + // + + CLEAR_FLAG(Data, FDO_HACK_INVALID_FLAGS); + SET_FLAG(FdoExtension->PrivateFdoData->HackFlags, Data); + return; +} + +VOID +ClasspScanForSpecialInRegistry( + IN PFUNCTIONAL_DEVICE_EXTENSION FdoExtension + ) +{ + HANDLE deviceParameterHandle; // device instance key + HANDLE classParameterHandle; // classpnp subkey + OBJECT_ATTRIBUTES objectAttributes = {0}; + UNICODE_STRING subkeyName; + NTSTATUS status; + + // + // seeded in the ENUM tree by ClassInstaller + // + ULONG deviceHacks; + RTL_QUERY_REGISTRY_TABLE queryTable[2] = {0}; // null terminated array + + PAGED_CODE(); + + deviceParameterHandle = NULL; + classParameterHandle = NULL; + deviceHacks = 0; + + status = IoOpenDeviceRegistryKey(FdoExtension->LowerPdo, + PLUGPLAY_REGKEY_DEVICE, + KEY_WRITE, + &deviceParameterHandle + ); + + if (!NT_SUCCESS(status)) { + goto cleanupScanForSpecial; + } + + RtlInitUnicodeString(&subkeyName, CLASSP_REG_SUBKEY_NAME); + InitializeObjectAttributes(&objectAttributes, + &subkeyName, + OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, + deviceParameterHandle, + NULL + ); + + status = ZwOpenKey( &classParameterHandle, + KEY_READ, + &objectAttributes + ); + + if (!NT_SUCCESS(status)) { + goto cleanupScanForSpecial; + } + + // + // Setup the structure to read + // + + queryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT; + queryTable[0].Name = CLASSP_REG_HACK_VALUE_NAME; + queryTable[0].EntryContext = &deviceHacks; + queryTable[0].DefaultType = REG_DWORD; + queryTable[0].DefaultData = &deviceHacks; + queryTable[0].DefaultLength = 0; + + // + // read values + // + + status = RtlQueryRegistryValues(RTL_REGISTRY_HANDLE, + (PWSTR)classParameterHandle, + &queryTable[0], + NULL, + NULL + ); + if (!NT_SUCCESS(status)) { + goto cleanupScanForSpecial; + } + + // + // remove unknown values and save... + // + + CLEAR_FLAG(deviceHacks, FDO_HACK_INVALID_FLAGS); + SET_FLAG(FdoExtension->PrivateFdoData->HackFlags, deviceHacks); + + +cleanupScanForSpecial: + + if (deviceParameterHandle) { + ZwClose(deviceParameterHandle); + } + + if (classParameterHandle) { + ZwClose(classParameterHandle); + } + + // + // we should modify the system hive to include another key for us to grab + // settings from. in this case: Classpnp\HackFlags + // + // the use of a DWORD value for the HackFlags allows 32 hacks w/o + // significant use of the registry, and also reduces OEM exposure. + // + // definition of bit flags: + // 0x00000001 -- Device succeeds PREVENT_MEDIUM_REMOVAL, but + // cannot actually prevent removal. + // 0x00000002 -- Device hard-hangs or times out for GESN requests. + // 0xfffffffc -- Currently reserved, may be used later. + // + + return; +} + +/*++//////////////////////////////////////////////////////////////////////////// + +ClasspUpdateDiskProperties() + +Routine Description: + + This routine will send IOCTL_DISK_UPDATE_PROPERTIES to top of stack - Partition Manager + to invalidate the cached geometry. + +Arguments: + + Fdo - The device object whose capacity needs to be verified. + +Return Value: + + NTSTATUS + +--*/ + +VOID +ClasspUpdateDiskProperties( + IN PDEVICE_OBJECT Fdo, + IN PVOID Context + ) +{ + PDEVICE_OBJECT topOfStack; + IO_STATUS_BLOCK ioStatus; + NTSTATUS status = STATUS_SUCCESS; + KEVENT event; + PIRP irp = NULL; + PFUNCTIONAL_DEVICE_EXTENSION fdoExt = Fdo->DeviceExtension; + PCLASS_PRIVATE_FDO_DATA fdoData = fdoExt->PrivateFdoData; + PIO_WORKITEM WorkItem = (PIO_WORKITEM)Context; + + KeInitializeEvent(&event, SynchronizationEvent, FALSE); + + topOfStack = IoGetAttachedDeviceReference(Fdo); + + // + // Send down irp to update properties + // + + irp = IoBuildDeviceIoControlRequest( + IOCTL_DISK_UPDATE_PROPERTIES, + topOfStack, + NULL, + 0, + NULL, + 0, + FALSE, + &event, + &ioStatus); + + if (irp != NULL) { + + + status = IoCallDriver(topOfStack, irp); + if (status == STATUS_PENDING) { + (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); + status = ioStatus.Status; + } + + } else { + status = STATUS_INSUFFICIENT_RESOURCES; + } + + InterlockedExchange((volatile LONG *)&fdoData->UpdateDiskPropertiesWorkItemActive, 0); + + if (!NT_SUCCESS(status)) { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, "ClasspUpdateDiskProperties: Disk property update for fdo %p failed with status 0x%X.\n", Fdo, status)); + } else { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "ClasspUpdateDiskProperties: Drive capacity has changed for %p.\n", Fdo)); + } + ObDereferenceObject(topOfStack); + + if (WorkItem != NULL) { + IoFreeWorkItem(WorkItem); + } + + return; +} + +BOOLEAN +InterpretSenseInfoWithoutHistory( + _In_ PDEVICE_OBJECT Fdo, + _In_opt_ PIRP OriginalRequest, + _In_ PSCSI_REQUEST_BLOCK Srb, + UCHAR MajorFunctionCode, + ULONG IoDeviceCode, + ULONG PreviousRetryCount, + _Out_ NTSTATUS * Status, + _Out_opt_ _Deref_out_range_(0,MAXIMUM_RETRY_FOR_SINGLE_IO_IN_100NS_UNITS) + LONGLONG * RetryIn100nsUnits + ) +{ + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension; + PCLASS_PRIVATE_FDO_DATA fdoData = fdoExtension->PrivateFdoData; + LONGLONG tmpRetry = 0; + BOOLEAN retry = FALSE; + + if (fdoData->InterpretSenseInfo != NULL) + { + SCSI_REQUEST_BLOCK tempSrb = {0}; + PSCSI_REQUEST_BLOCK srbPtr = Srb; + + // SAL annotations and ClassInitializeEx() both validate this + NT_ASSERT(fdoData->InterpretSenseInfo->Interpret != NULL); + + // + // If class driver does not support extended SRB and this is + // an extended SRB, convert to legacy SRB and pass to class + // driver. + // + if ((Srb->Function == SRB_FUNCTION_STORAGE_REQUEST_BLOCK) && + ((fdoExtension->CommonExtension.DriverExtension->SrbSupport & + CLASS_SRB_STORAGE_REQUEST_BLOCK) == 0)) { + ClasspConvertToScsiRequestBlock(&tempSrb, (PSTORAGE_REQUEST_BLOCK)Srb); + srbPtr = &tempSrb; + } + + retry = fdoData->InterpretSenseInfo->Interpret(Fdo, + OriginalRequest, + srbPtr, + MajorFunctionCode, + IoDeviceCode, + PreviousRetryCount, + NULL, + Status, + &tmpRetry); + } + else + { + ULONG seconds = 0; + + retry = ClassInterpretSenseInfo(Fdo, + Srb, + MajorFunctionCode, + IoDeviceCode, + PreviousRetryCount, + Status, + &seconds); + tmpRetry = ((LONGLONG)seconds) * 1000 * 1000 * 10; + } + + + if (RetryIn100nsUnits != NULL) + { + *RetryIn100nsUnits = tmpRetry; + } + return retry; +} + +VOID +ClasspGetInquiryVpdSupportInfo( + _Inout_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension + ) +{ + NTSTATUS status; + PCOMMON_DEVICE_EXTENSION commonExtension = (PCOMMON_DEVICE_EXTENSION)FdoExtension; + SCSI_REQUEST_BLOCK srb = {0}; + PCDB cdb; + PVPD_SUPPORTED_PAGES_PAGE supportedPages = NULL; + UCHAR bufferLength = VPD_MAX_BUFFER_SIZE; + ULONG allocationBufferLength = bufferLength; + UCHAR srbExBuffer[CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE] = {0}; + PSTORAGE_REQUEST_BLOCK_HEADER srbHeader; + +#if defined(_ARM_) || defined(_ARM64_) + // + // ARM has specific alignment requirements, although this will not have a functional impact on x86 or amd64 + // based platforms. We are taking the conservative approach here. + // + allocationBufferLength = ALIGN_UP_BY(allocationBufferLength,KeGetRecommendedSharedDataAlignment()); + supportedPages = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned, + allocationBufferLength, + '3CcS' + ); + +#else + supportedPages = ExAllocatePoolWithTag(NonPagedPoolNx, + bufferLength, + '3CcS' + ); +#endif + + if (supportedPages == NULL) { + // memory allocation failure. + return; + } + + RtlZeroMemory(supportedPages, allocationBufferLength); + + // prepare the Srb + if (FdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK) { + + #pragma prefast(suppress:26015, "InitializeStorageRequestBlock ensures buffer access is bounded") + status = InitializeStorageRequestBlock((PSTORAGE_REQUEST_BLOCK)srbExBuffer, + STORAGE_ADDRESS_TYPE_BTL8, + sizeof(srbExBuffer), + 1, + SrbExDataTypeScsiCdb16); + + if (!NT_SUCCESS(status)) { + // + // Should not happen. Revert to legacy SRB. + NT_ASSERT(FALSE); + srb.Length = SCSI_REQUEST_BLOCK_SIZE; + srb.Function = SRB_FUNCTION_EXECUTE_SCSI; + srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)&srb; + } else { + ((PSTORAGE_REQUEST_BLOCK)srbExBuffer)->SrbFunction = SRB_FUNCTION_EXECUTE_SCSI; + srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)srbExBuffer; + } + + } else { + srb.Length = SCSI_REQUEST_BLOCK_SIZE; + srb.Function = SRB_FUNCTION_EXECUTE_SCSI; + srbHeader = (PSTORAGE_REQUEST_BLOCK_HEADER)&srb; + } + + SrbSetTimeOutValue(srbHeader, FdoExtension->TimeOutValue); + SrbSetRequestTag(srbHeader, SP_UNTAGGED); + SrbSetRequestAttribute(srbHeader, SRB_SIMPLE_TAG_REQUEST); + SrbAssignSrbFlags(srbHeader, FdoExtension->SrbFlags); + SrbSetCdbLength(srbHeader, 6); + + cdb = SrbGetCdb(srbHeader); + cdb->CDB6INQUIRY3.OperationCode = SCSIOP_INQUIRY; + cdb->CDB6INQUIRY3.EnableVitalProductData = 1; //EVPD bit + cdb->CDB6INQUIRY3.PageCode = VPD_SUPPORTED_PAGES; + cdb->CDB6INQUIRY3.AllocationLength = bufferLength; //AllocationLength field in CDB6INQUIRY3 is only one byte. + + status = ClassSendSrbSynchronous(commonExtension->DeviceObject, + (PSCSI_REQUEST_BLOCK)srbHeader, + supportedPages, + allocationBufferLength, + FALSE); + + // + // Handle the case where we get back STATUS_DATA_OVERRUN b/c the input + // buffer was larger than necessary. + // + if (status == STATUS_DATA_OVERRUN && SrbGetDataTransferLength(srbHeader) < bufferLength) + { + status = STATUS_SUCCESS; + } + + if ( NT_SUCCESS(status) && + (supportedPages->PageLength > 0) && + (supportedPages->SupportedPageList[0] > 0) ) { + // ataport treats all INQUIRY command as standard INQUIRY command, thus fills invalid info + // If VPD INQUIRY is supported, the first page reported (additional length field for standard INQUIRY data) should be '00' + status = STATUS_NOT_SUPPORTED; + } + + if (NT_SUCCESS(status)) { + int i; + + for (i = 0; i < supportedPages->PageLength; i++) { + if ( (i > 0) && (supportedPages->SupportedPageList[i] <= supportedPages->SupportedPageList[i - 1]) ) { + // shall be in ascending order beginning with page code 00h. + FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits = FALSE; + FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockLimits = FALSE; + FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceCharacteristics = FALSE; + FdoExtension->FunctionSupportInfo->ValidInquiryPages.LBProvisioning = FALSE; + + + break; + } + switch (supportedPages->SupportedPageList[i]) { + case VPD_THIRD_PARTY_COPY: + FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits = TRUE; + break; + + case VPD_BLOCK_LIMITS: + FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockLimits = TRUE; + break; + + case VPD_BLOCK_DEVICE_CHARACTERISTICS: + FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceCharacteristics = TRUE; + break; + + case VPD_LOGICAL_BLOCK_PROVISIONING: + FdoExtension->FunctionSupportInfo->ValidInquiryPages.LBProvisioning = TRUE; + break; + + } + } + } + + ExFreePool(supportedPages); + + return; +} + +// +// ClasspGetLBProvisioningInfo +// +// Description: This function does the work to get Logical Block Provisioning +// (LBP) info for a given LUN, including UNMAP support and parameters. It +// will attempt to get both the Logical Block Provisioning (0xB2) VPD page +// and the Block Limits (0xB0) VPD page and cache the relevant values in +// the given FDO extension. +// +// After calling this function, you can use the ClasspIsThinProvisioned() +// function to determine if the device is thinly provisioned and the +// ClasspSupportsUnmap() function to determine if the device supports the +// UNMAP command. +// +// Arguments: +// - FdoExtension: The FDO extension associated with the LUN for which Thin +// Provisioning info is desired. The Thin Provisioning info is stored +// in the FunctionSupportInfo member of this FDO extension. +// +// Returns: +// - STATUS_INVALID_PARAMETER if the given FDO extension has not been +// allocated properly. +// - STATUS_INSUFFICIENT_RESOURCES if this function was unable to allocate +// an SRB used to get the necessary VPD pages. +// - STATUS_SUCCESS in all other cases. If any of the incidental functions +// don't return STATUS_SUCCESS this function will just assume Thin +// Provisioning is not enabled and return STATUS_SUCCESS. +// +NTSTATUS +ClasspGetLBProvisioningInfo( + _Inout_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension + ) +{ + PSCSI_REQUEST_BLOCK srb = NULL; + ULONG srbSize = 0; + + // + // Make sure we actually have data structures to work with. + // + if (FdoExtension == NULL || + FdoExtension->FunctionSupportInfo == NULL) { + return STATUS_INVALID_PARAMETER; + } + + // + // Allocate an SRB for querying the device for LBP-related info if either + // the Logical Block Provisioning (0xB2) or Block Limits (0xB0) VPD page + // exists. + // + if ((FdoExtension->FunctionSupportInfo->ValidInquiryPages.LBProvisioning == TRUE) || + (FdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockLimits == TRUE)) { + + if ((FdoExtension->AdapterDescriptor != NULL) && + (FdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK)) { + srbSize = CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE; + } else { + srbSize = sizeof(SCSI_REQUEST_BLOCK); + } + + srb = ExAllocatePoolWithTag(NonPagedPoolNx, srbSize, '0DcS'); + + if (srb == NULL) { + return STATUS_INSUFFICIENT_RESOURCES; + } + } + + // + // Get the Logical Block Provisioning VPD page (0xB2). This function will + // set some default values if the LBP VPD page is not supported. + // + ClasspDeviceGetLBProvisioningVPDPage(FdoExtension->DeviceObject, srb); + + + // + // Get the Block Limits VPD page (0xB0), which may override the default + // UNMAP parameter values set above. + // + ClasspDeviceGetBlockLimitsVPDPage(FdoExtension, + srb, + srbSize, + &FdoExtension->FunctionSupportInfo->BlockLimitsData); + + FREE_POOL(srb); + + return STATUS_SUCCESS; +} + + + +_IRQL_requires_(PASSIVE_LEVEL) +_IRQL_requires_same_ +NTSTATUS +ClassDetermineTokenOperationCommandSupport( + _In_ PDEVICE_OBJECT DeviceObject + ) + +/*++ + +Routine Description: + + This routine determines if Token Operation and Receive ROD Token Information commands + are supported by this device and updates the internal structures to reflect this. + In addition, it also queries the registry to determine the maximum listIdentifier + to use for TokenOperation commands. + + This function must be called at IRQL == PASSIVE_LEVEL. + +Arguments: + + DeviceObject - The device object for which we want to determine command support. + +Return Value: + + Nothing + +--*/ + +{ + NTSTATUS status = STATUS_SUCCESS; + + PAGED_CODE(); + + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_PNP, + "ClassDetermineTokenOperationCommandSupport (%p): Entering function.\n", + DeviceObject)); + + // + // Send down Inquiry for VPD_THIRD_PARTY_COPY_PAGE and cache away the device parameters + // from WINDOWS_BLOCK_DEVICE_TOKEN_LIMITS_DESCRIPTOR. + // + status = ClasspGetBlockDeviceTokenLimitsInfo(DeviceObject); + + if (NT_SUCCESS(status)) { + + ULONG maxListIdentifier = MaxTokenOperationListIdentifier; + + // + // Query the maximum list identifier to use for TokenOperation commands. + // + if (NT_SUCCESS(ClasspGetMaximumTokenListIdentifier(DeviceObject, REG_DISK_CLASS_CONTROL, &maxListIdentifier))) { + if (maxListIdentifier >= MIN_TOKEN_LIST_IDENTIFIERS) { + + NT_ASSERT(maxListIdentifier <= MAX_TOKEN_LIST_IDENTIFIERS); + MaxTokenOperationListIdentifier = maxListIdentifier; + } + } + + } + + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_PNP, + "ClassDetermineTokenOperationCommandSupport (%p): Exiting function with status %x.\n", + DeviceObject, + status)); + + return status; +} + + +_IRQL_requires_same_ +NTSTATUS +ClasspGetBlockDeviceTokenLimitsInfo( + _Inout_ PDEVICE_OBJECT DeviceObject + ) + +/*++ + +Routine Description: + + This routine does the work to get the Block Device Token Limits info for a given LUN. + This is done by sending Inquiry for the Third Party Copy VPD page. + +Arguments: + + DeviceObject - The FDO associated with the LUN for which Block Device Token + limits info is desired. + +Return Value: + + STATUS_DEVICE_FEATURE_NOT_SUPPORTED if either the Inquiry fails or validations fail. + STATUS_SUCCESS otherwise. + +--*/ + +{ + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = DeviceObject->DeviceExtension; + NTSTATUS status; + PSCSI_REQUEST_BLOCK srb = NULL; + ULONG srbSize = 0; + USHORT pageLength = 0; + USHORT descriptorLength = 0; + PVOID dataBuffer = NULL; + UCHAR bufferLength = VPD_MAX_BUFFER_SIZE; + ULONG allocationBufferLength = bufferLength; + PCDB cdb; + ULONG dataTransferLength = 0; + PVPD_THIRD_PARTY_COPY_PAGE operatingParameters = NULL; + PWINDOWS_BLOCK_DEVICE_TOKEN_LIMITS_DESCRIPTOR blockDeviceTokenLimits = NULL; + + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_PNP, + "ClasspGetBlockDeviceTokenLimitsInfo (%p): Entering function.\n", + DeviceObject)); + + // + // Allocate an SRB for querying the device for LBP-related info. + // + if ((fdoExtension->AdapterDescriptor != NULL) && + (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK)) { + srbSize = CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE; + } else { + srbSize = sizeof(SCSI_REQUEST_BLOCK); + } + + srb = ExAllocatePoolWithTag(NonPagedPoolNx, srbSize, CLASSPNP_POOL_TAG_SRB); + + if (!srb) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_PNP, + "ClasspGetBlockDeviceTokenLimitsInfo (%p): Couldn't allocate SRB.\n", + DeviceObject)); + + status = STATUS_INSUFFICIENT_RESOURCES; + goto __ClasspGetBlockDeviceTokenLimitsInfo_Exit; + } + +#if defined(_ARM_) || defined(_ARM64_) + // + // ARM has specific alignment requirements, although this will not have a functional impact on x86 or amd64 + // based platforms. We are taking the conservative approach here. + // + allocationBufferLength = ALIGN_UP_BY(allocationBufferLength, KeGetRecommendedSharedDataAlignment()); + dataBuffer = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned, allocationBufferLength, CLASSPNP_POOL_TAG_VPD); +#else + dataBuffer = ExAllocatePoolWithTag(NonPagedPoolNx, bufferLength, CLASSPNP_POOL_TAG_VPD); +#endif + + if (!dataBuffer) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_PNP, + "ClasspGetBlockDeviceTokenLimitsInfo (%p): Couldn't allocate dataBuffer.\n", + DeviceObject)); + + status = STATUS_INSUFFICIENT_RESOURCES; + goto __ClasspGetBlockDeviceTokenLimitsInfo_Exit; + } + + operatingParameters = (PVPD_THIRD_PARTY_COPY_PAGE)dataBuffer; + + RtlZeroMemory(dataBuffer, allocationBufferLength); + + if ((fdoExtension->AdapterDescriptor != NULL) && + (fdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK)) { + status = InitializeStorageRequestBlock((PSTORAGE_REQUEST_BLOCK)srb, + STORAGE_ADDRESS_TYPE_BTL8, + CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE, + 1, + SrbExDataTypeScsiCdb16); + if (NT_SUCCESS(status)) { + + ((PSTORAGE_REQUEST_BLOCK)srb)->SrbFunction = SRB_FUNCTION_EXECUTE_SCSI; + + } else { + + // + // Should not occur. Revert to legacy SRB. + // + NT_ASSERT(FALSE); + + TracePrint((TRACE_LEVEL_WARNING, + TRACE_FLAG_PNP, + "ClasspGetBlockDeviceTokenLimitsInfo (%p): Falling back to using SRB (instead of SRB_EX).\n", + DeviceObject)); + + RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK)); + srb->Length = sizeof(SCSI_REQUEST_BLOCK); + srb->Function = SRB_FUNCTION_EXECUTE_SCSI; + } + } else { + + RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK)); + srb->Length = sizeof(SCSI_REQUEST_BLOCK); + srb->Function = SRB_FUNCTION_EXECUTE_SCSI; + } + + SrbSetTimeOutValue(srb, fdoExtension->TimeOutValue); + SrbSetRequestTag(srb, SP_UNTAGGED); + SrbSetRequestAttribute(srb, SRB_SIMPLE_TAG_REQUEST); + SrbAssignSrbFlags(srb, fdoExtension->SrbFlags); + + SrbSetCdbLength(srb, 6); + + cdb = SrbGetCdb(srb); + cdb->CDB6INQUIRY3.OperationCode = SCSIOP_INQUIRY; + cdb->CDB6INQUIRY3.EnableVitalProductData = 1; + cdb->CDB6INQUIRY3.PageCode = VPD_THIRD_PARTY_COPY; + cdb->CDB6INQUIRY3.AllocationLength = bufferLength; + + status = ClassSendSrbSynchronous(fdoExtension->DeviceObject, + srb, + dataBuffer, + allocationBufferLength, + FALSE); + + // + // Handle the case where we get back STATUS_DATA_OVERRUN because the input + // buffer was larger than necessary. + // + dataTransferLength = SrbGetDataTransferLength(srb); + + if (status == STATUS_DATA_OVERRUN && dataTransferLength < bufferLength) { + + status = STATUS_SUCCESS; + } + + if (NT_SUCCESS(status)) { + + REVERSE_BYTES_SHORT(&pageLength, &operatingParameters->PageLength); + + } else { + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_PNP, + "ClasspGetBlockDeviceTokenLimitsInfo (%p): Inquiry for TPC VPD failed with %x.\n", + DeviceObject, + status)); + } + + if ((NT_SUCCESS(status)) && + (pageLength >= sizeof(WINDOWS_BLOCK_DEVICE_TOKEN_LIMITS_DESCRIPTOR))) { + + USHORT descriptorType; + + blockDeviceTokenLimits = (PWINDOWS_BLOCK_DEVICE_TOKEN_LIMITS_DESCRIPTOR)operatingParameters->ThirdPartyCopyDescriptors; + REVERSE_BYTES_SHORT(&descriptorType, &blockDeviceTokenLimits->DescriptorType); + REVERSE_BYTES_SHORT(&descriptorLength, &blockDeviceTokenLimits->DescriptorLength); + + if ((descriptorType == BLOCK_DEVICE_TOKEN_LIMITS_DESCRIPTOR_TYPE_WINDOWS) && + (VPD_PAGE_HEADER_SIZE + descriptorLength == sizeof(WINDOWS_BLOCK_DEVICE_TOKEN_LIMITS_DESCRIPTOR))) { + + fdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits = TRUE; + + REVERSE_BYTES_SHORT(&fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumRangeDescriptors, &blockDeviceTokenLimits->MaximumRangeDescriptors); + REVERSE_BYTES(&fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumInactivityTimer, &blockDeviceTokenLimits->MaximumInactivityTimer); + REVERSE_BYTES(&fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.DefaultInactivityTimer, &blockDeviceTokenLimits->DefaultInactivityTimer); + REVERSE_BYTES_QUAD(&fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumTokenTransferSize, &blockDeviceTokenLimits->MaximumTokenTransferSize); + REVERSE_BYTES_QUAD(&fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount, &blockDeviceTokenLimits->OptimalTransferCount); + + TracePrint((TRACE_LEVEL_INFORMATION, + TRACE_FLAG_PNP, + "ClasspGetBlockDeviceTokenLimitsInfo (%p): %s %s (rev %s) reported following parameters: \ + \n\t\t\tMaxRangeDescriptors: %u\n\t\t\tMaxIAT: %u\n\t\t\tDefaultIAT: %u \ + \n\t\t\tMaxTokenTransferSize: %I64u\n\t\t\tOptimalTransferCount: %I64u\n\t\t\tOptimalTransferLengthGranularity: %u \ + \n\t\t\tOptimalTransferLength: %u\n\t\t\tMaxTransferLength: %u\n", + DeviceObject, + (PCSZ)(((PUCHAR)fdoExtension->DeviceDescriptor) + fdoExtension->DeviceDescriptor->VendorIdOffset), + (PCSZ)(((PUCHAR)fdoExtension->DeviceDescriptor) + fdoExtension->DeviceDescriptor->ProductIdOffset), + (PCSZ)(((PUCHAR)fdoExtension->DeviceDescriptor) + fdoExtension->DeviceDescriptor->ProductRevisionOffset), + fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumRangeDescriptors, + fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumInactivityTimer, + fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.DefaultInactivityTimer, + fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumTokenTransferSize, + fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount, + fdoExtension->FunctionSupportInfo->BlockLimitsData.OptimalTransferLengthGranularity, + fdoExtension->FunctionSupportInfo->BlockLimitsData.OptimalTransferLength, + fdoExtension->FunctionSupportInfo->BlockLimitsData.MaximumTransferLength)); + } else { + + TracePrint((TRACE_LEVEL_WARNING, + TRACE_FLAG_PNP, + "ClasspGetBlockDeviceTokenLimitsInfo (%p): ThirdPartyCopy VPD data doesn't have Windows OffloadDataTransfer descriptor.\n", + DeviceObject)); + + fdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits = FALSE; + status = STATUS_DEVICE_FEATURE_NOT_SUPPORTED; + } + } else { + + TracePrint((TRACE_LEVEL_WARNING, + TRACE_FLAG_PNP, + "ClasspGetBlockDeviceTokenLimitsInfo (%p): TPC VPD data didn't return TPC descriptors of interest.\n", + DeviceObject)); + + fdoExtension->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits = FALSE; + status = STATUS_DEVICE_FEATURE_NOT_SUPPORTED; + } + +__ClasspGetBlockDeviceTokenLimitsInfo_Exit: + + FREE_POOL(dataBuffer); + FREE_POOL(srb); + fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus = status; + + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_PNP, + "ClasspGetBlockDeviceTokenLimitsInfo (%p): Exiting function with status %x.\n", + DeviceObject, + status)); + + return status; +} + + +_IRQL_requires_max_(APC_LEVEL) +_IRQL_requires_min_(PASSIVE_LEVEL) +_IRQL_requires_same_ +NTSTATUS +ClassDeviceProcessOffloadRead( + _In_ PDEVICE_OBJECT DeviceObject, + _In_ PIRP Irp, + _Inout_ PSCSI_REQUEST_BLOCK Srb + ) + +/*++ + +Routine Description: + + This routine services IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES for CopyOffload + Read. If the device supports copy offload, it performs the translation of the + IOCTL into the appropriate SCSI commands to complete the operation. + + This function must be called at IRQL < DISPATCH_LEVEL. + +Arguments: + + DeviceObject - Supplies the device object associated with this request + Irp - The IRP to be processed + Srb - An SRB that can be optinally used to process this request + +Return Value: + + NTSTATUS code + +--*/ + +{ + PIO_STACK_LOCATION irpStack; + NTSTATUS status; + PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes; + PDEVICE_DSM_OFFLOAD_READ_PARAMETERS offloadReadParameters; + PFUNCTIONAL_DEVICE_EXTENSION fdoExtension; + + UNREFERENCED_PARAMETER(Srb); + + // + // This function must be called at less than dispatch level. + // Fail if IRQL >= DISPATCH_LEVEL. + // + PAGED_CODE(); + + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_IOCTL, + "ClassDeviceProcessOffloadRead (%p): Entering function. Irp %p\n", + DeviceObject, + Irp)); + + + irpStack = IoGetCurrentIrpStackLocation (Irp); + + // + // Validations + // + status = ClasspValidateOffloadSupported(DeviceObject, Irp); + if (!NT_SUCCESS(status)) { + goto __ClassDeviceProcessOffloadRead_CompleteAndExit; + } + + if (KeGetCurrentIrql() >= DISPATCH_LEVEL) { + + NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL); + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassDeviceProcessOffloadRead (%p): Called at raised IRQL.\n", + DeviceObject)); + + status = STATUS_INVALID_LEVEL; + goto __ClassDeviceProcessOffloadRead_CompleteAndExit; + } + + // + // Ensure that this DSM IOCTL was generated in kernel + // + if (Irp->RequestorMode != KernelMode) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassDeviceProcessOffloadRead (%p): Called from user mode.\n", + DeviceObject)); + + status = STATUS_ACCESS_DENIED; + goto __ClassDeviceProcessOffloadRead_CompleteAndExit; + } + + status = ClasspValidateOffloadInputParameters(DeviceObject, Irp); + if (!NT_SUCCESS(status)) { + goto __ClassDeviceProcessOffloadRead_CompleteAndExit; + } + + dsmAttributes = Irp->AssociatedIrp.SystemBuffer; + + // + // Validate that we were passed in correct sized parameter block. + // + if (dsmAttributes->ParameterBlockLength < sizeof(DEVICE_DSM_OFFLOAD_READ_PARAMETERS)) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassDeviceProcessOffloadRead (%p): Parameter block size (%u) too small. Required %u.\n", + DeviceObject, + dsmAttributes->ParameterBlockLength, + sizeof(DEVICE_DSM_OFFLOAD_READ_PARAMETERS))); + + status = STATUS_INVALID_PARAMETER; + goto __ClassDeviceProcessOffloadRead_CompleteAndExit; + } + + offloadReadParameters = Add2Ptr(dsmAttributes, dsmAttributes->ParameterBlockOffset); + + fdoExtension = DeviceObject->DeviceExtension; + + // + // If the request TTL is greater than the max supported by this storage, the target will + // end up failing this command, so might as well do the check up front. + // + if ((fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumInactivityTimer > 0) && + (offloadReadParameters->TimeToLive > fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumInactivityTimer)) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassDeviceProcessOffloadRead (%p): Requested TTL (%u) greater than max supported (%u).\n", + DeviceObject, + offloadReadParameters->TimeToLive, + fdoExtension->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumInactivityTimer)); + + status = STATUS_INVALID_PARAMETER; + goto __ClassDeviceProcessOffloadRead_CompleteAndExit; + } + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(STORAGE_OFFLOAD_READ_OUTPUT)) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassDeviceProcessOffloadRead (%p): Output buffer size (%u) too small.\n", + DeviceObject, + irpStack->Parameters.DeviceIoControl.OutputBufferLength)); + + status = STATUS_BUFFER_TOO_SMALL; + goto __ClassDeviceProcessOffloadRead_CompleteAndExit; + } + + + + status = ClasspServicePopulateTokenTransferRequest(DeviceObject, Irp); + + if (status == STATUS_PENDING) { + goto __ClassDeviceProcessOffloadRead_Exit; + } + +__ClassDeviceProcessOffloadRead_CompleteAndExit: + ClasspCompleteOffloadRequest(DeviceObject, Irp, status); +__ClassDeviceProcessOffloadRead_Exit: + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_IOCTL, + "ClassDeviceProcessOffloadRead (%p): Exiting function Irp %p with status %x.\n", + DeviceObject, + Irp, + status)); + + return status; +} + +VOID +ClasspCompleteOffloadRequest( + _In_ PDEVICE_OBJECT DeviceObject, + _In_ PIRP Irp, + _In_ NTSTATUS CompletionStatus) +{ + NTSTATUS status = CompletionStatus; + + + Irp->IoStatus.Status = status; + + ClassReleaseRemoveLock(DeviceObject, Irp); + ClassCompleteRequest(DeviceObject, Irp, IO_NO_INCREMENT); + + return; +} + + +_IRQL_requires_max_(APC_LEVEL) +_IRQL_requires_min_(PASSIVE_LEVEL) +_IRQL_requires_same_ +NTSTATUS +ClassDeviceProcessOffloadWrite( + _In_ PDEVICE_OBJECT DeviceObject, + _In_ PIRP Irp, + _Inout_ PSCSI_REQUEST_BLOCK Srb + ) + +/*++ + +Routine Description: + + This routine services IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES for CopyOffload + Write. If the device supports copy offload, it performs the translation of the + IOCTL into the appropriate SCSI commands to complete the operation. + + This function must be called at IRQL < DISPATCH_LEVEL. + +Arguments: + + DeviceObject - Supplies the device object associated with this request + Irp - The IRP to be processed + Srb - An SRB that can be optinally used to process this request + +Return Value: + + NTSTATUS code + +--*/ + +{ + PIO_STACK_LOCATION irpStack; + NTSTATUS status; + PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes; + + UNREFERENCED_PARAMETER(Srb); + + // + // This function must be called at less than dispatch level. + // + PAGED_CODE(); + + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_IOCTL, + "ClassDeviceProcessOffloadWrite (%p): Entering function. Irp %p\n", + DeviceObject, + Irp)); + + + irpStack = IoGetCurrentIrpStackLocation(Irp); + + // + // Validations + // + status = ClasspValidateOffloadSupported(DeviceObject, Irp); + if (!NT_SUCCESS(status)) { + goto __ClassDeviceProcessOffloadWrite_CompleteAndExit; + } + + if (KeGetCurrentIrql() >= DISPATCH_LEVEL) { + + NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL); + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassDeviceProcessOffloadWrite (%p): Called at raised IRQL.\n", + DeviceObject)); + + status = STATUS_INVALID_LEVEL; + goto __ClassDeviceProcessOffloadWrite_CompleteAndExit; + } + + // + // Ensure that this DSM IOCTL was generated in kernel + // + if (Irp->RequestorMode != KernelMode) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassDeviceProcessOffloadWrite (%p): Called from user mode.\n", + DeviceObject)); + + status = STATUS_ACCESS_DENIED; + goto __ClassDeviceProcessOffloadWrite_CompleteAndExit; + } + + status = ClasspValidateOffloadInputParameters(DeviceObject, Irp); + if (!NT_SUCCESS(status)) { + goto __ClassDeviceProcessOffloadWrite_CompleteAndExit; + } + + dsmAttributes = Irp->AssociatedIrp.SystemBuffer; + + // + // Validate that we were passed in correct sized parameter block. + // + if (dsmAttributes->ParameterBlockLength < sizeof(DEVICE_DSM_OFFLOAD_WRITE_PARAMETERS)) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassDeviceProcessOffloadWrite (%p): Parameter block size (%u) too small. Required %u.\n", + DeviceObject, + dsmAttributes->ParameterBlockLength, + sizeof(DEVICE_DSM_OFFLOAD_WRITE_PARAMETERS))); + + status = STATUS_INVALID_PARAMETER; + goto __ClassDeviceProcessOffloadWrite_CompleteAndExit; + } + + if (irpStack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(STORAGE_OFFLOAD_WRITE_OUTPUT)) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClassDeviceProcessOffloadWrite (%p): Output buffer size (%u) too small.\n", + DeviceObject, + irpStack->Parameters.DeviceIoControl.OutputBufferLength)); + + status = STATUS_BUFFER_TOO_SMALL; + goto __ClassDeviceProcessOffloadWrite_CompleteAndExit; + } + + + + status = ClasspServiceWriteUsingTokenTransferRequest(DeviceObject, Irp); + + if (status == STATUS_PENDING) { + goto __ClassDeviceProcessOffloadWrite_Exit; + } + +__ClassDeviceProcessOffloadWrite_CompleteAndExit: + ClasspCompleteOffloadRequest(DeviceObject, Irp, status); +__ClassDeviceProcessOffloadWrite_Exit: + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_IOCTL, + "ClassDeviceProcessOffloadWrite (%p): Exiting function Irp %p with status %x\n", + DeviceObject, + Irp, + status)); + + return status; +} + + +_IRQL_requires_max_(APC_LEVEL) +_IRQL_requires_min_(PASSIVE_LEVEL) +_IRQL_requires_same_ +NTSTATUS +ClasspServicePopulateTokenTransferRequest( + _In_ PDEVICE_OBJECT Fdo, + _In_ PIRP Irp + ) + +/*++ + +Routine description: + + This routine processes offload read requests by building the SRB + for PopulateToken and its result (i.e. token). + +Arguments: + + Fdo - The functional device object processing the request + Irp - The Io request to be processed + +Return Value: + + STATUS_SUCCESS if successful, an error code otherwise + +--*/ + +{ + BOOLEAN allDataSetRangeFullyConverted; + UINT32 allocationSize; + ULONG blockDescrIndex; + PBLOCK_DEVICE_RANGE_DESCRIPTOR blockDescrPointer; + PVOID buffer; + ULONG bufferLength; + ULONG dataSetRangeIndex; + PDEVICE_DATA_SET_RANGE dataSetRanges; + ULONG dataSetRangesCount; + PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes; + ULONGLONG entireXferLen; + PFUNCTIONAL_DEVICE_EXTENSION fdoExt; + ULONG i; + ULONG lbaCount; + ULONG listIdentifier; + ULONG maxBlockDescrCount; + ULONGLONG maxLbaCount; + POFFLOAD_READ_CONTEXT offloadReadContext; + PDEVICE_DSM_OFFLOAD_READ_PARAMETERS offloadReadParameters; + PTRANSFER_PACKET pkt; + USHORT populateTokenDataLength; + USHORT populateTokenDescriptorsLength; + PMDL populateTokenMdl; + PIRP pseudoIrp; + ULONG receiveTokenInformationBufferLength; + NTSTATUS status; + DEVICE_DATA_SET_RANGE tempDataSetRange; + BOOLEAN tempDataSetRangeFullyConverted; + PUCHAR token; + ULONG tokenLength; + ULONG tokenOperationBufferLength; + ULONGLONG totalSectorCount; + ULONGLONG totalSectorsToProcess; + ULONG transferSize; + + PAGED_CODE(); + + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_IOCTL, + "ClasspServicePopulateTokenTransferRequest (%p): Entering function. Irp %p\n", + Fdo, + Irp)); + + fdoExt = Fdo->DeviceExtension; + status = STATUS_SUCCESS; + dsmAttributes = Irp->AssociatedIrp.SystemBuffer; + buffer = NULL; + pkt = NULL; + populateTokenMdl = NULL; + offloadReadParameters = Add2Ptr(dsmAttributes, dsmAttributes->ParameterBlockOffset); + dataSetRanges = Add2Ptr(dsmAttributes, dsmAttributes->DataSetRangesOffset); + dataSetRangesCount = dsmAttributes->DataSetRangesLength / sizeof(DEVICE_DATA_SET_RANGE); + totalSectorsToProcess = 0; + token = NULL; + tokenLength = 0; + bufferLength = 0; + offloadReadContext = NULL; + + NT_ASSERT(fdoExt->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits && + NT_SUCCESS(fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus)); + + + for (i = 0, entireXferLen = 0; i < dataSetRangesCount; i++) { + entireXferLen += dataSetRanges[i].LengthInBytes; + } + + // + // We need to truncate the read request based on the following hardware limitations: + // 1. The size of the data buffer containing the TokenOperation command's parameters must + // not exceed the MaximumTransferLength (and max physical pages) of the underlying + // adapter. + // 2. The number of descriptors specified in the TokenOperation command must not exceed + // the MaximumRangeDescriptors. + // 3. The cumulative total of the number of transfer blocks in all the descriptors in + // the TokenOperation command must not exceed the MaximumTokenTransferSize. + // 4. If the TTL has been set to the 0 (i.e. indicates the use of the default TLT), + // limit the cumulative total of the number of transfer blocks in all the descriptors + // in the TokenOperation command to the OptimalTransferCount. + // NOTE: only a TTL of 0 has an implicit indication that the initiator of the + // request would like the storage stack to be smart about the amount of data + // transfer. + // + // In addition to the above, we need to ensure that for each of the descriptors in the + // TokenOperation command: + // 1. The number of blocks specified is an exact multiple of the OptimalTransferLengthGranularity. + // 2. The number of blocks specified is limited to the MaximumTransferLength. (We shall + // however, limit the number of blocks specified in each descriptor to be a maximum of + // OptimalTransferLength or MaximumTransferLength, whichever is lesser). + // + // Finally, we shall always send down the PopulateToken command using IMMED = 0 for this + // release. This makes it simpler to handle multi-initiator scenarios since we won't need + // to deal with the PopulateToken (with IMMED = 1) succeeding but ReceiveRODTokenInformation + // failing due to the path/node failing, thus making it impossible to retrieve the token. + // + // The LBA ranges is in DEVICE_DATA_SET_RANGE format, it needs to be converted into + // WINDOWS_RANGE_DESCRIPTOR Block Descriptors. + // + + ClasspGetTokenOperationCommandBufferLength(Fdo, + SERVICE_ACTION_POPULATE_TOKEN, + &bufferLength, + &tokenOperationBufferLength, + &receiveTokenInformationBufferLength); + + allocationSize = sizeof(OFFLOAD_READ_CONTEXT) + bufferLength; + + offloadReadContext = ExAllocatePoolWithTag( + NonPagedPoolNx, + allocationSize, + CLASSPNP_POOL_TAG_TOKEN_OPERATION); + + if (!offloadReadContext) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspServicePopulateTokenTransferRequest (%p): Failed to allocate buffer for PopulateToken operations.\n", + Fdo)); + + status = STATUS_INSUFFICIENT_RESOURCES; + goto __ClasspServicePopulateTokenTransferRequest_ErrorExit; + } + + RtlZeroMemory(offloadReadContext, allocationSize); + + offloadReadContext->Fdo = Fdo; + offloadReadContext->OffloadReadDsmIrp = Irp; + + // + // The buffer for the commands is after the offloadReadContext. + // + buffer = (offloadReadContext + 1); + + // + // No matter how large the offload read request, we'll be sending it down in one shot. + // So we need only one transfer packet. + // + pkt = DequeueFreeTransferPacket(Fdo, TRUE); + if (!pkt){ + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspServicePopulateTokenTransferRequest (%p): Failed to retrieve transfer packet for TokenOperation (PopulateToken) operation.\n", + Fdo)); + + status = STATUS_INSUFFICIENT_RESOURCES; + goto __ClasspServicePopulateTokenTransferRequest_ErrorExit; + } + + offloadReadContext->Pkt = pkt; + + ClasspGetTokenOperationDescriptorLimits(Fdo, + SERVICE_ACTION_POPULATE_TOKEN, + tokenOperationBufferLength, + &maxBlockDescrCount, + &maxLbaCount); + + // + // We will limit the maximum data transfer in an offload read operation to: + // - OTC if TTL was specified as 0 + // - MaximumTransferTransferSize if lesser than above, or if TTL was specified was non-zero + // + if ((offloadReadParameters->TimeToLive == 0) && (fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount > 0)) { + + maxLbaCount = MIN(maxLbaCount, fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount); + } + + // + // Since we do not want very fragmented files to end up causing the PopulateToken command to take + // too long (and potentially timeout), we will limit the max number of descriptors sent down in a + // command. + // + maxBlockDescrCount = MIN(maxBlockDescrCount, MAX_NUMBER_BLOCK_DEVICE_DESCRIPTORS); + + TracePrint((TRACE_LEVEL_INFORMATION, + TRACE_FLAG_IOCTL, + "ClasspServicePopulateTokenTransferRequest (%p): Using MaxBlockDescrCount %u and MaxLbaCount %I64u.\n", + Fdo, + maxBlockDescrCount, + maxLbaCount)); + + allDataSetRangeFullyConverted = FALSE; + tempDataSetRangeFullyConverted = TRUE; + dataSetRangeIndex = (ULONG)-1; + + blockDescrPointer = (PBLOCK_DEVICE_RANGE_DESCRIPTOR) + &((PPOPULATE_TOKEN_HEADER)buffer)->BlockDeviceRangeDescriptor[0]; + + RtlZeroMemory(&tempDataSetRange, sizeof(tempDataSetRange)); + + blockDescrIndex = 0; + lbaCount = 0; + + // + // Send PopulateToken command when the buffer is full or all input entries are converted. + // + while (!((blockDescrIndex == maxBlockDescrCount) || // buffer full or block descriptor count reached + (lbaCount == maxLbaCount) || // block LBA count reached + (allDataSetRangeFullyConverted))) { // all DataSetRanges have been converted + + // + // If the previous entry conversion completed, continue the next one; + // Otherwise, still process the left part of the un-completed entry. + // + if (tempDataSetRangeFullyConverted) { + dataSetRangeIndex++; + tempDataSetRange.StartingOffset = dataSetRanges[dataSetRangeIndex].StartingOffset; + tempDataSetRange.LengthInBytes = dataSetRanges[dataSetRangeIndex].LengthInBytes; + } + + totalSectorCount = 0; + + ClasspConvertDataSetRangeToBlockDescr(Fdo, + blockDescrPointer, + &blockDescrIndex, + maxBlockDescrCount, + &lbaCount, + maxLbaCount, + &tempDataSetRange, + &totalSectorCount); + + tempDataSetRangeFullyConverted = (tempDataSetRange.LengthInBytes == 0) ? TRUE : FALSE; + + allDataSetRangeFullyConverted = tempDataSetRangeFullyConverted && ((dataSetRangeIndex + 1) == dataSetRangesCount); + + totalSectorsToProcess += totalSectorCount; + } + + // + // Calculate transfer size, including the header + // + transferSize = (blockDescrIndex * sizeof(BLOCK_DEVICE_RANGE_DESCRIPTOR)) + FIELD_OFFSET(POPULATE_TOKEN_HEADER, BlockDeviceRangeDescriptor); + + NT_ASSERT(transferSize <= MAX_TOKEN_OPERATION_PARAMETER_DATA_LENGTH); + + populateTokenDataLength = (USHORT)transferSize - RTL_SIZEOF_THROUGH_FIELD(POPULATE_TOKEN_HEADER, PopulateTokenDataLength); + REVERSE_BYTES_SHORT(((PPOPULATE_TOKEN_HEADER)buffer)->PopulateTokenDataLength, &populateTokenDataLength); + + ((PPOPULATE_TOKEN_HEADER)buffer)->Immediate = 0; + + REVERSE_BYTES(((PPOPULATE_TOKEN_HEADER)buffer)->InactivityTimeout, &offloadReadParameters->TimeToLive); + + populateTokenDescriptorsLength = (USHORT)transferSize - FIELD_OFFSET(POPULATE_TOKEN_HEADER, BlockDeviceRangeDescriptor); + REVERSE_BYTES_SHORT(((PPOPULATE_TOKEN_HEADER)buffer)->BlockDeviceRangeDescriptorListLength, &populateTokenDescriptorsLength); + + // + // Reuse a single buffer for both TokenOperation and ReceiveTokenInformation. This has the one disadvantage + // that we'll be marking the page(s) as IoWriteAccess even though we only need read access for token + // operation command. However, the advantage is that we eliminate the possibility of any potential failures + // when trying to allocate an MDL for the ReceiveTokenInformation later on. + // + bufferLength = max(transferSize, receiveTokenInformationBufferLength); + + populateTokenMdl = ClasspBuildDeviceMdl(buffer, bufferLength, FALSE); + if (!populateTokenMdl) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspServicePopulateTokenTransferRequest (%p): Failed to allocate MDL for PopulateToken operations.\n", + Fdo)); + + status = STATUS_INSUFFICIENT_RESOURCES; + goto __ClasspServicePopulateTokenTransferRequest_ErrorExit; + } + + offloadReadContext->PopulateTokenMdl = populateTokenMdl; + + pseudoIrp = &offloadReadContext->PseudoIrp; + + + pseudoIrp->IoStatus.Status = STATUS_SUCCESS; + pseudoIrp->IoStatus.Information = 0; + pseudoIrp->Tail.Overlay.DriverContext[0] = LongToPtr(1); + pseudoIrp->MdlAddress = populateTokenMdl; + + InterlockedCompareExchange((volatile LONG *)&TokenOperationListIdentifier, -1, MaxTokenOperationListIdentifier); + listIdentifier = InterlockedIncrement((volatile LONG *)&TokenOperationListIdentifier); + + ClasspSetupPopulateTokenTransferPacket( + offloadReadContext, + pkt, + transferSize, + (PUCHAR)buffer, + pseudoIrp, + listIdentifier); + + TracePrint((TRACE_LEVEL_INFORMATION, + TRACE_FLAG_IOCTL, + "ClasspServicePopulateTokenTransferRequest (%p): Generate token for %I64u bytes (versus %I64u) [via %u descriptors]. \ + \n\t\t\tDataLength: %u, DescriptorsLength: %u. Pkt %p (list id %x). Requested TTL: %u secs.\n", + Fdo, + totalSectorsToProcess * fdoExt->DiskGeometry.BytesPerSector, + entireXferLen, + blockDescrIndex, + populateTokenDataLength, + populateTokenDescriptorsLength, + pkt, + listIdentifier, + offloadReadParameters->TimeToLive)); + + // + // Save (into the offloadReadContext) any remaining things that + // ClasspPopulateTokenTransferPacketDone() will need. + // + + offloadReadContext->ListIdentifier = listIdentifier; + offloadReadContext->BufferLength = bufferLength; + offloadReadContext->ReceiveTokenInformationBufferLength = receiveTokenInformationBufferLength; + offloadReadContext->TotalSectorsToProcess = totalSectorsToProcess; // so far + offloadReadContext->EntireXferLen = entireXferLen; + + NT_ASSERT(status == STATUS_SUCCESS); // so far. + + IoMarkIrpPending(Irp); + SubmitTransferPacket(pkt); + + status = STATUS_PENDING; + goto __ClasspServicePopulateTokenTransferRequest_Exit; + + // + // Error cleanup label only - not used in success case: + // + +__ClasspServicePopulateTokenTransferRequest_ErrorExit: + + NT_ASSERT(status != STATUS_PENDING); + + if (offloadReadContext != NULL) { + ClasspCleanupOffloadReadContext(offloadReadContext); + offloadReadContext = NULL; + } + +__ClasspServicePopulateTokenTransferRequest_Exit: + + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_IOCTL, + "ClasspServicePopulateTokenTransferRequest (%p): Exiting function (Irp %p) with status %x.\n", + Fdo, + Irp, + status)); + + return status; +} + + +VOID +ClasspPopulateTokenTransferPacketDone( + _In_ PVOID Context + ) + +/*++ + +Routine description: + + This routine continues an offload read operation on completion of the + populate token transfer packet. + + This function is responsible for continuing or completing the offload read + operation. + +Arguments: + + Context - Pointer to the OFFLOAD_READ_CONTEXT for the offload read + operation. + +Return Value: + + None. + +--*/ + +{ + PDEVICE_OBJECT fdo; + ULONG listIdentifier; + POFFLOAD_READ_CONTEXT offloadReadContext; + PTRANSFER_PACKET pkt; + PIRP pseudoIrp; + NTSTATUS status; + + offloadReadContext = Context; + pseudoIrp = &offloadReadContext->PseudoIrp; + pkt = offloadReadContext->Pkt; + fdo = offloadReadContext->Fdo; + listIdentifier = offloadReadContext->ListIdentifier; + + offloadReadContext->Pkt = NULL; + + + status = pseudoIrp->IoStatus.Status; + NT_ASSERT(status != STATUS_PENDING); + + if (!NT_SUCCESS(status)) { + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspPopulateTokenTransferPacketDone (%p): Generate token for list Id %x failed with %x (Pkt %p).\n", + fdo, + listIdentifier, + status, + pkt)); + goto __ClasspPopulateTokenTransferPacketDone_ErrorExit; + } + + // + // If a token was successfully generated, it is now time to retrieve the token. + // The called function is responsible for completing the offload read DSM IRP. + // + ClasspReceivePopulateTokenInformation(offloadReadContext); + + // + // ClasspReceivePopulateTokenInformation() takes care of completing the IRP, + // so this function is done regardless of success or failure in + // ClasspReceivePopulateTokenInformation(). + // + + return; + + // + // Error cleanup label only - not used in success case: + // + +__ClasspPopulateTokenTransferPacketDone_ErrorExit: + + NT_ASSERT(!NT_SUCCESS(status)); + + // + // ClasspCompleteOffloadRead also cleans up offloadReadContext. + // + + ClasspCompleteOffloadRead(offloadReadContext, status); + + return; +} + + +VOID +ClasspCompleteOffloadRead( + _In_ POFFLOAD_READ_CONTEXT OffloadReadContext, + _In_ NTSTATUS CompletionStatus + ) + +/*++ + +Routine description: + + This routine completes an offload read operation with given status, and + cleans up the OFFLOAD_READ_CONTEXT. + +Arguments: + + OffloadReadContext - Pointer to the OFFLOAD_READ_CONTEXT for the offload + read operation. + + CompletionStatus - The completion status for the offload read operation. + +Return Value: + + None. + +--*/ + +{ + PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes; + ULONGLONG entireXferLen; + PDEVICE_OBJECT fdo; + PFUNCTIONAL_DEVICE_EXTENSION fdoExt; + PIRP irp; + NTSTATUS status; + PUCHAR token; + ULONGLONG totalSectorsProcessed; + + status = CompletionStatus; + dsmAttributes = OffloadReadContext->OffloadReadDsmIrp->AssociatedIrp.SystemBuffer; + totalSectorsProcessed = OffloadReadContext->TotalSectorsProcessed; + fdoExt = OffloadReadContext->Fdo->DeviceExtension; + entireXferLen = OffloadReadContext->EntireXferLen; + token = OffloadReadContext->Token; + irp = OffloadReadContext->OffloadReadDsmIrp; + fdo = OffloadReadContext->Fdo; + + ((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->OffloadReadFlags = 0; + ((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->Reserved = 0; + + if (NT_SUCCESS(status)) { + ULONGLONG totalBytesProcessed = totalSectorsProcessed * fdoExt->DiskGeometry.BytesPerSector; + + TracePrint((totalBytesProcessed == entireXferLen ? TRACE_LEVEL_INFORMATION : TRACE_LEVEL_WARNING, + TRACE_FLAG_IOCTL, + "ClasspCompleteOffloadRead (%p): Successfully populated token with %I64u (out of %I64u) bytes (list Id %x).\n", + fdo, + totalBytesProcessed, + entireXferLen, + OffloadReadContext->ListIdentifier)); + + if (totalBytesProcessed < entireXferLen) { + SET_FLAG(((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->OffloadReadFlags, STORAGE_OFFLOAD_READ_RANGE_TRUNCATED); + } + ((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->LengthProtected = totalBytesProcessed; + ((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->TokenLength = STORAGE_OFFLOAD_MAX_TOKEN_LENGTH; + RtlCopyMemory(&((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->Token, token, STORAGE_OFFLOAD_MAX_TOKEN_LENGTH); + } else { + ((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->LengthProtected = 0; + ((PSTORAGE_OFFLOAD_READ_OUTPUT)dsmAttributes)->TokenLength = 0; + } + + irp->IoStatus.Information = sizeof(STORAGE_OFFLOAD_READ_OUTPUT); + + ClasspCompleteOffloadRequest(fdo, irp, status); + ClasspCleanupOffloadReadContext(OffloadReadContext); + OffloadReadContext = NULL; + + return; +} + + +VOID +ClasspCleanupOffloadReadContext( + _In_ __drv_freesMem(mem) POFFLOAD_READ_CONTEXT OffloadReadContext + ) + +/*++ + +Routine description: + + This routine cleans up an OFFLOAD_READ_CONTEXT. + +Arguments: + + OffloadReadContext - Pointer to the OFFLOAD_READ_CONTEXT for the offload + read operation. + +Return Value: + + None. + +--*/ + +{ + PMDL populateTokenMdl; + + populateTokenMdl = OffloadReadContext->PopulateTokenMdl; + + NT_ASSERT(OffloadReadContext != NULL); + + if (populateTokenMdl) { + ClasspFreeDeviceMdl(populateTokenMdl); + } + FREE_POOL(OffloadReadContext); + + return; +} + + +_IRQL_requires_same_ +VOID +ClasspReceivePopulateTokenInformation( + _In_ POFFLOAD_READ_CONTEXT OffloadReadContext + ) + +/*++ + +Routine description: + + This routine retrieves the token after a PopulateToken command + has been sent down. + + Can also be called repeatedly for a single offload op when a previous + RECEIVE ROD TOKEN INFORMATION for that op indicated that the operation was + not yet complete. + + This function is responsible for continuing or completing the offload read + operation. + +Arguments: + + OffloadReadContext - Pointer to the OFFLOAD_READ_CONTEXT for the offload + read operation. + +Return Value: + + None. + +--*/ + +{ + PVOID buffer; + ULONG bufferLength; + ULONG cdbLength; + PDEVICE_OBJECT fdo; + PIRP irp; + ULONG listIdentifier; + PTRANSFER_PACKET pkt; + PIRP pseudoIrp; + ULONG receiveTokenInformationBufferLength; + PSCSI_REQUEST_BLOCK srb; + NTSTATUS status; + ULONG tempSizeUlong; + PULONGLONG totalSectorsProcessed; + + totalSectorsProcessed = &OffloadReadContext->TotalSectorsProcessed; + buffer = OffloadReadContext + 1; + bufferLength = OffloadReadContext->BufferLength; + fdo = OffloadReadContext->Fdo; + irp = OffloadReadContext->OffloadReadDsmIrp; + receiveTokenInformationBufferLength = OffloadReadContext->ReceiveTokenInformationBufferLength; + listIdentifier = OffloadReadContext->ListIdentifier; + + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_IOCTL, + "ClasspReceivePopulateTokenInformation (%p): Entering function. Irp %p\n", + fdo, + irp)); + + srb = &OffloadReadContext->Srb; + *totalSectorsProcessed = 0; + + pkt = DequeueFreeTransferPacket(fdo, TRUE); + if (!pkt){ + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspReceivePopulateTokenInformation (%p): Failed to retrieve transfer packet for ReceiveTokenInformation (PopulateToken) operation.\n", + fdo)); + + status = STATUS_INSUFFICIENT_RESOURCES; + goto __ClasspReceivePopulateTokenInformation_ErrorExit; + } + + OffloadReadContext->Pkt = pkt; + + RtlZeroMemory(buffer, bufferLength); + + tempSizeUlong = receiveTokenInformationBufferLength - 4; + REVERSE_BYTES(((PRECEIVE_TOKEN_INFORMATION_HEADER)buffer)->AvailableData, &tempSizeUlong); + + pseudoIrp = &OffloadReadContext->PseudoIrp; + RtlZeroMemory(pseudoIrp, sizeof(IRP)); + + + pseudoIrp->IoStatus.Status = STATUS_SUCCESS; + pseudoIrp->IoStatus.Information = 0; + pseudoIrp->Tail.Overlay.DriverContext[0] = LongToPtr(1); + pseudoIrp->MdlAddress = OffloadReadContext->PopulateTokenMdl; + + ClasspSetupReceivePopulateTokenInformationTransferPacket( + OffloadReadContext, + pkt, + receiveTokenInformationBufferLength, + (PUCHAR)buffer, + pseudoIrp, + listIdentifier); + + // + // Cache away the CDB as it may be required for forwarded sense data + // after this command completes. + // + RtlZeroMemory(srb, sizeof(*srb)); + cdbLength = SrbGetCdbLength(pkt->Srb); + if (cdbLength <= 16) { + RtlCopyMemory(&srb->Cdb, SrbGetCdb(pkt->Srb), cdbLength); + } + + SubmitTransferPacket(pkt); + + return; + + // + // Error cleanup label only - not used in success case: + // + +__ClasspReceivePopulateTokenInformation_ErrorExit: + + NT_ASSERT(!NT_SUCCESS(status)); + + // + // ClasspCompleteOffloadRead also cleans up offloadReadContext. + // + + ClasspCompleteOffloadRead(OffloadReadContext, status); + + return; +} + + +VOID +ClasspReceivePopulateTokenInformationTransferPacketDone( + _In_ PVOID Context + ) + +/*++ + +Routine description: + + This routine continues an offload read operation on completion of the + RECEIVE ROD TOKEN INFORMATION transfer packet. + + This routine is responsible for continuing or completing the offload read + operation. + +Arguments: + + Context - Pointer to the OFFLOAD_READ_CONTEXT for the offload read + operation. + +Return Value: + + None. + +--*/ + +{ + ULONG availableData; + PVOID buffer; + UCHAR completionStatus; + ULONG estimatedRetryInterval; + PDEVICE_OBJECT fdo; + PFUNCTIONAL_DEVICE_EXTENSION fdoExt; + PIRP irp; + ULONG listIdentifier; + POFFLOAD_READ_CONTEXT offloadReadContext; + BOOLEAN operationCompleted; + UCHAR operationStatus; + PIRP pseudoIrp; + USHORT segmentsProcessed; + PSENSE_DATA senseData; + ULONG senseDataFieldLength; + UCHAR senseDataLength; + PSCSI_REQUEST_BLOCK srb; + NTSTATUS status; + PUCHAR token; + PVOID tokenAscii; + PBLOCK_DEVICE_TOKEN_DESCRIPTOR tokenDescriptor; + ULONG tokenDescriptorLength; + PRECEIVE_TOKEN_INFORMATION_HEADER tokenInformationResults; + PRECEIVE_TOKEN_INFORMATION_RESPONSE_HEADER tokenInformationResultsResponse; + ULONG tokenLength; + ULONG tokenSize; + PULONGLONG totalSectorsProcessed; + ULONGLONG totalSectorsToProcess; + ULONGLONG transferBlockCount; + + offloadReadContext = Context; + fdo = offloadReadContext->Fdo; + fdoExt = fdo->DeviceExtension; + buffer = offloadReadContext + 1; + listIdentifier = offloadReadContext->ListIdentifier; + irp = offloadReadContext->OffloadReadDsmIrp; + totalSectorsToProcess = offloadReadContext->TotalSectorsToProcess; + totalSectorsProcessed = &offloadReadContext->TotalSectorsProcessed; + srb = &offloadReadContext->Srb; + tokenAscii = NULL; + tokenSize = BLOCK_DEVICE_TOKEN_SIZE; + tokenInformationResults = (PRECEIVE_TOKEN_INFORMATION_HEADER)buffer; + senseData = (PSENSE_DATA)((PUCHAR)tokenInformationResults + FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_HEADER, SenseData)); + transferBlockCount = 0; + tokenInformationResultsResponse = NULL; + tokenDescriptor = NULL; + operationCompleted = FALSE; + tokenDescriptorLength = 0; + tokenLength = 0; + token = NULL; + pseudoIrp = &offloadReadContext->PseudoIrp; + + status = pseudoIrp->IoStatus.Status; + NT_ASSERT(status != STATUS_PENDING); + + // + // The buffer we hand allows for the max sizes for all the fields whereas the returned + // data may be lesser (e.g. sense data info will almost never be MAX_SENSE_BUFFER_SIZE, etc. + // so handle underrun "error" + // + if (status == STATUS_DATA_OVERRUN) { + + status = STATUS_SUCCESS; + } + + if (!NT_SUCCESS(status)) { + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): Token retrieval failed for list Id %x with %x.\n", + fdo, + listIdentifier, + status)); + goto __ClasspReceivePopulateTokenInformationTransferPacketDone_Exit; + } + + REVERSE_BYTES(&availableData, &tokenInformationResults->AvailableData); + + NT_ASSERT(availableData <= FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_HEADER, SenseData) + MAX_SENSE_BUFFER_SIZE + + FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_RESPONSE_HEADER, TokenDescriptor) + BLOCK_DEVICE_TOKEN_SIZE); + + NT_ASSERT(tokenInformationResults->ResponseToServiceAction == SERVICE_ACTION_POPULATE_TOKEN); + + operationStatus = tokenInformationResults->OperationStatus; + operationCompleted = ClasspIsTokenOperationComplete(operationStatus); + NT_ASSERT(operationCompleted); + + REVERSE_BYTES(&estimatedRetryInterval, &tokenInformationResults->EstimatedStatusUpdateDelay); + + completionStatus = tokenInformationResults->CompletionStatus; + + NT_ASSERT(tokenInformationResults->TransferCountUnits == TRANSFER_COUNT_UNITS_NUMBER_BLOCKS); + REVERSE_BYTES_QUAD(&transferBlockCount, &tokenInformationResults->TransferCount); + + REVERSE_BYTES_SHORT(&segmentsProcessed, &tokenInformationResults->SegmentsProcessed); + NT_ASSERT(segmentsProcessed == 0); + + if (operationCompleted) { + + if (transferBlockCount > totalSectorsToProcess) { + + // + // Buggy or hostile target. Don't let it claim more was procesed + // than was requested. Since this is likely a bug and it's unknown + // how much was actually transferred, assume no data was + // transferred. + // + + NT_ASSERT(transferBlockCount <= totalSectorsToProcess); + transferBlockCount = 0; + } + + if (operationStatus != OPERATION_COMPLETED_WITH_SUCCESS && + operationStatus != OPERATION_COMPLETED_WITH_RESIDUAL_DATA) { + + // + // Assert on buggy response from target, but in any case, make sure not + // to claim that any data was written. + // + + NT_ASSERT(transferBlockCount == 0); + transferBlockCount = 0; + } + + // + // Since the TokenOperation was sent down synchronously, the operation is complete as soon as the command returns. + // + + senseDataFieldLength = tokenInformationResults->SenseDataFieldLength; + senseDataLength = tokenInformationResults->SenseDataLength; + NT_ASSERT(senseDataFieldLength >= senseDataLength); + + tokenInformationResultsResponse = (PRECEIVE_TOKEN_INFORMATION_RESPONSE_HEADER)((PUCHAR)tokenInformationResults + + FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_HEADER, SenseData) + + tokenInformationResults->SenseDataFieldLength); + + REVERSE_BYTES(&tokenDescriptorLength, &tokenInformationResultsResponse->TokenDescriptorsLength); + + if (tokenDescriptorLength > 0) { + + NT_ASSERT(tokenDescriptorLength == sizeof(BLOCK_DEVICE_TOKEN_DESCRIPTOR)); + + if (tokenDescriptorLength != sizeof(BLOCK_DEVICE_TOKEN_DESCRIPTOR)) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): Bad firmware, token descriptor length %u.\n", + fdo, + tokenDescriptorLength)); + + NT_ASSERT((*totalSectorsProcessed) == 0); + NT_ASSERT(tokenLength == 0); + + } else { + + USHORT restrictedId; + + tokenDescriptor = (PBLOCK_DEVICE_TOKEN_DESCRIPTOR)tokenInformationResultsResponse->TokenDescriptor; + + REVERSE_BYTES_SHORT(&restrictedId, &tokenDescriptor->TokenIdentifier); + NT_ASSERT(restrictedId == 0); + + tokenLength = BLOCK_DEVICE_TOKEN_SIZE; + token = tokenDescriptor->Token; + + *totalSectorsProcessed = transferBlockCount; + + if (transferBlockCount < totalSectorsToProcess) { + + NT_ASSERT(operationStatus == OPERATION_COMPLETED_WITH_RESIDUAL_DATA || + operationStatus == OPERATION_COMPLETED_WITH_ERROR || + operationStatus == OPERATION_TERMINATED); + + if (transferBlockCount == 0) { + // + // Treat the same as not getting a token. + // + + tokenLength = 0; + } + + } else { + + NT_ASSERT(operationStatus == OPERATION_COMPLETED_WITH_SUCCESS); + NT_ASSERT(transferBlockCount == totalSectorsToProcess); + } + + // + // Need to convert to ascii. + // + tokenAscii = ClasspBinaryToAscii((PUCHAR)token, + tokenSize, + &tokenSize); + + TracePrint((transferBlockCount == totalSectorsToProcess ? TRACE_LEVEL_INFORMATION : TRACE_LEVEL_WARNING, + TRACE_FLAG_IOCTL, + "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): %wsToken %s generated successfully for list Id %x for data size %I64u bytes.\n", + fdo, + transferBlockCount == totalSectorsToProcess ? L"" : L"Target truncated read. ", + (tokenAscii == NULL) ? "" : tokenAscii, + listIdentifier, + (*totalSectorsProcessed) * fdoExt->DiskGeometry.BytesPerSector)); + + FREE_POOL(tokenAscii); + } + } else { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): Target failed to generate a token for list Id %x for data size %I64u bytes (requested %I64u bytes).\n", + fdo, + listIdentifier, + transferBlockCount * fdoExt->DiskGeometry.BytesPerSector, + totalSectorsToProcess * fdoExt->DiskGeometry.BytesPerSector)); + + *totalSectorsProcessed = 0; + + NT_ASSERT(operationStatus == OPERATION_COMPLETED_WITH_ERROR); + } + + // + // Operation that completes with success can have sense data (for target to pass on some extra info) + // but we don't care about such sense info. + // Operation that complete but not with success, may not have sense data associated, but may + // have valid CompletionStatus. + // + // The "status" may be overriden by ClassInterpretSenseInfo(). Final + // status is determined a bit later - this is just the default status + // when ClassInterpretSenseInfo() doesn't get to run here. + // + status = STATUS_SUCCESS; + if (operationStatus == OPERATION_COMPLETED_WITH_ERROR || + operationStatus == OPERATION_COMPLETED_WITH_RESIDUAL_DATA || + operationStatus == OPERATION_TERMINATED) { + + SrbSetScsiStatus((PSTORAGE_REQUEST_BLOCK_HEADER)srb, completionStatus); + + if (senseDataLength) { + + ULONG retryInterval; + + NT_ASSERT(senseDataLength <= sizeof(SENSE_DATA)); + + srb->Function = SRB_FUNCTION_EXECUTE_SCSI; + + srb->SrbStatus = SRB_STATUS_AUTOSENSE_VALID | SRB_STATUS_ERROR; + SrbSetSenseInfoBuffer((PSTORAGE_REQUEST_BLOCK_HEADER)srb, senseData); + SrbSetSenseInfoBufferLength((PSTORAGE_REQUEST_BLOCK_HEADER)srb, senseDataLength); + + ClassInterpretSenseInfo(fdo, + srb, + IRP_MJ_SCSI, + 0, + 0, + &status, + &retryInterval); + + TracePrint((TRACE_LEVEL_WARNING, + TRACE_FLAG_IOCTL, + "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): Reason for truncation/failure: %x - for list Id %x for data size %I64u bytes.\n", + fdo, + status, + listIdentifier, + transferBlockCount * fdoExt->DiskGeometry.BytesPerSector)); + } else { + + TracePrint((TRACE_LEVEL_WARNING, + TRACE_FLAG_IOCTL, + "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): No sense data available but reason for truncation/failure, possibly: %x - for list Id %x for data size %I64u bytes.\n", + fdo, + completionStatus, + listIdentifier, + transferBlockCount * fdoExt->DiskGeometry.BytesPerSector)); + } + } + + if (tokenLength > 0) { + + offloadReadContext->Token = token; + + // + // Even if target returned an error, from the OS upper layers' perspective, + // it is a success (with truncation) if any data at all was read. + // + status = STATUS_SUCCESS; + + } else { + + if (NT_SUCCESS(status)) { + // + // Make sure status is a failing status, without throwing away an + // already-failing status obtained from sense data. + // + status = STATUS_UNSUCCESSFUL; + } + } + + // + // Done with the operation. + // + + NT_ASSERT(status != STATUS_PENDING); + goto __ClasspReceivePopulateTokenInformationTransferPacketDone_Exit; + + } else { + + status = STATUS_UNSUCCESSFUL; + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): Token retrieval failed for list Id %x with %x.\n", + fdo, + listIdentifier, + status)); + + NT_ASSERT(*totalSectorsProcessed == 0); + goto __ClasspReceivePopulateTokenInformationTransferPacketDone_Exit; + } + +__ClasspReceivePopulateTokenInformationTransferPacketDone_Exit: + + if (status != STATUS_PENDING) { + + // + // The "status" value can be success or failure at this point, as + // appropriate. + // + + ClasspCompleteOffloadRead(offloadReadContext, status); + } + + // + // Due to tracing a potentially freed pointer value "Irp", this trace could + // be delayed beyond another offload op picking up the same pointer value + // for its Irp. This function exits after the operation is complete when + // status != STATUS_PENDING. + // + + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_IOCTL, + "ClasspReceivePopulateTokenInformationTransferPacketDone (%p): Exiting function (Irp %p) with internal status %x.\n", + fdo, + irp, + status)); + + return; +} + + +_IRQL_requires_max_(APC_LEVEL) +_IRQL_requires_min_(PASSIVE_LEVEL) +_IRQL_requires_same_ +NTSTATUS +ClasspServiceWriteUsingTokenTransferRequest( + _In_ PDEVICE_OBJECT Fdo, + _In_ PIRP Irp + ) + +/*++ + +Routine description: + + This routine processes offload write requests by building the SRB + for WriteUsingToken. + +Arguments: + + Fdo - The functional device object processing the request + Irp - The Io request to be processed + +Return Value: + + STATUS_SUCCESS if successful, an error code otherwise + +--*/ + +{ + ULONG allocationSize; + PVOID buffer; + ULONG bufferLength; + PDEVICE_DATA_SET_RANGE dataSetRanges; + ULONG dataSetRangesCount; + PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes; + ULONGLONG entireXferLen; + PFUNCTIONAL_DEVICE_EXTENSION fdoExt; + ULONG i; + ULONGLONG logicalBlockOffset; + ULONG maxBlockDescrCount; + ULONGLONG maxLbaCount; + POFFLOAD_WRITE_CONTEXT offloadWriteContext; + PDEVICE_DSM_OFFLOAD_WRITE_PARAMETERS offloadWriteParameters; + ULONG receiveTokenInformationBufferLength; + NTSTATUS status; + NTSTATUS tempStatus; + BOOLEAN tokenInvalidated; + ULONG tokenOperationBufferLength; + PMDL writeUsingTokenMdl; + + PAGED_CODE(); + + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_IOCTL, + "ClasspServiceWriteUsingTokenTransferRequest (%p): Entering function. Irp %p.\n", + Fdo, + Irp)); + + fdoExt = Fdo->DeviceExtension; + status = STATUS_SUCCESS; + tempStatus = STATUS_SUCCESS; + dsmAttributes = Irp->AssociatedIrp.SystemBuffer; + buffer = NULL; + writeUsingTokenMdl = NULL; + offloadWriteParameters = Add2Ptr(dsmAttributes, dsmAttributes->ParameterBlockOffset); + dataSetRanges = Add2Ptr(dsmAttributes, dsmAttributes->DataSetRangesOffset); + dataSetRangesCount = dsmAttributes->DataSetRangesLength / sizeof(DEVICE_DATA_SET_RANGE); + logicalBlockOffset = offloadWriteParameters->TokenOffset / fdoExt->DiskGeometry.BytesPerSector; + tokenInvalidated = FALSE; + bufferLength = 0; + + + NT_ASSERT(fdoExt->FunctionSupportInfo->ValidInquiryPages.BlockDeviceRODLimits && + NT_SUCCESS(fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.CommandStatus)); + + for (i = 0, entireXferLen = 0; i < dataSetRangesCount; i++) { + entireXferLen += dataSetRanges[i].LengthInBytes; + } + + // + // We need to split the write request based on the following hardware limitations: + // 1. The size of the data buffer containing the TokenOperation command's parameters must + // not exceed the MaximumTransferLength (and max physical pages) of the underlying + // adapter. + // 2. The number of descriptors specified in the TokenOperation command must not exceed + // the MaximumRangeDescriptors. + // 3. The cumulative total of the number of transfer blocks in all the descriptors in + // the TokenOperation command must not exceed the MaximumTokenTransferSize. + // + // In addition to the above, we need to ensure that for each of the descriptors in the + // TokenOperation command: + // 1. The number of blocks specified is an exact multiple of the OptimalTransferLengthGranularity. + // 2. The number of blocks specified is limited to the MaximumTransferLength. (We shall + // however, limit the number of blocks specified in each descriptor to be a maximum of + // OptimalTransferLength or MaximumTransferLength, whichever is lesser). + // + // Finally, we shall always send down the WriteUsingToken command using IMMED = 0 for this + // release. This makes it simpler to handle multi-initiator scenarios since we won't need + // to deal with the WriteUsingToken (with IMMED = 1) succeeding but ReceiveRODTokenInformation + // failing due to the path/node failing, thus making it impossible to retrieve results of + // the data transfer operation, or to cancel the data transfer via CopyOperationAbort. + // Since a write data transfer of a large amount of data may take a long time when sent + // down IMMED = 0, we shall limit the size to a maximum of 256MB even if it means truncating + // the original requested size to this capped size. The application is expected to deal with + // this truncation. + // (NOTE: the cap of 256MB is chosen to match with the Copy Engine's chunk size). + // + // The LBA ranges is in DEVICE_DATA_SET_RANGE format, it needs to be converted into + // WINDOWS_BLOCK_DEVICE_RANGE_DESCRIPTOR Block Descriptors. + // + + ClasspGetTokenOperationCommandBufferLength(Fdo, + SERVICE_ACTION_WRITE_USING_TOKEN, + &bufferLength, + &tokenOperationBufferLength, + &receiveTokenInformationBufferLength); + + allocationSize = sizeof(OFFLOAD_WRITE_CONTEXT) + bufferLength; + + offloadWriteContext = ExAllocatePoolWithTag( + NonPagedPoolNx, + allocationSize, + CLASSPNP_POOL_TAG_TOKEN_OPERATION); + + if (!offloadWriteContext) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspServiceWriteUsingTokenTransferRequest (%p): Failed to allocate buffer for WriteUsingToken operations.\n", + Fdo)); + + status = STATUS_INSUFFICIENT_RESOURCES; + goto __ClasspServiceWriteUsingTokenTransferRequest_ErrorExit; + } + + // + // Only zero the context portion here. The buffer portion is zeroed for + // each sub-request. + // + RtlZeroMemory(offloadWriteContext, sizeof(OFFLOAD_WRITE_CONTEXT)); + + offloadWriteContext->Fdo = Fdo; + offloadWriteContext->OffloadWriteDsmIrp = Irp; + offloadWriteContext->OperationStartTime = KeQueryInterruptTime(); + + // + // The buffer for the commands is after the offloadWriteContext. + // + buffer = (offloadWriteContext + 1); + + // + // Set up fields that allow iterating through whole request, by issuing sub- + // requests which each do some of the writing. Because of truncation by the + // target, it's not known exactly how many bytes will be written by the + // target in each sub-request (can be less than requested), so the + // progress through the outer request must only commit the move through the + // upper DSM ranges when a lower request is done and the number of written + // sectors is known. + // + + NT_ASSERT(offloadWriteContext->TotalSectorsProcessedSuccessfully == 0); + offloadWriteContext->TotalRequestSizeSectors = entireXferLen / fdoExt->DiskGeometry.BytesPerSector; + NT_ASSERT(offloadWriteContext->DataSetRangeIndex == 0); + NT_ASSERT(offloadWriteContext->DataSetRangeByteOffset == 0); + offloadWriteContext->DataSetRangesCount = dataSetRangesCount; + + offloadWriteContext->DsmAttributes = dsmAttributes; + offloadWriteContext->OffloadWriteParameters = offloadWriteParameters; + offloadWriteContext->DataSetRanges = dataSetRanges; + offloadWriteContext->LogicalBlockOffset = logicalBlockOffset; + + ClasspGetTokenOperationDescriptorLimits(Fdo, + SERVICE_ACTION_WRITE_USING_TOKEN, + tokenOperationBufferLength, + &maxBlockDescrCount, + &maxLbaCount); + + if (fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount && fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumTokenTransferSize) { + + NT_ASSERT(fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount <= fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.MaximumTokenTransferSize); + } + + // + // We will limit the maximum data transfer in an offload write operation to: + // - 64MB if OptimalTransferCount = 0 + // - OptimalTransferCount if < 256MB + // - 256MB if OptimalTransferCount >= 256MB + // - MaximumTokenTransferSize if lesser than above chosen size + // + if (0 == fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount) { + + maxLbaCount = MIN(maxLbaCount, (DEFAULT_MAX_NUMBER_BYTES_PER_SYNC_WRITE_USING_TOKEN / (ULONGLONG)fdoExt->DiskGeometry.BytesPerSector)); + + } else if (fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount < (MAX_NUMBER_BYTES_PER_SYNC_WRITE_USING_TOKEN / (ULONGLONG)fdoExt->DiskGeometry.BytesPerSector)) { + + maxLbaCount = MIN(maxLbaCount, fdoExt->FunctionSupportInfo->BlockDeviceRODLimitsData.OptimalTransferCount); + + } else { + + maxLbaCount = MIN(maxLbaCount, (MAX_NUMBER_BYTES_PER_SYNC_WRITE_USING_TOKEN / (ULONGLONG)fdoExt->DiskGeometry.BytesPerSector)); + } + + // + // Since we do not want very fragmented files to end up causing the WriteUsingToken command to take + // too long (and potentially timeout), we will limit the max number of descriptors sent down in a + // command. + // + maxBlockDescrCount = MIN(maxBlockDescrCount, MAX_NUMBER_BLOCK_DEVICE_DESCRIPTORS); + + TracePrint((TRACE_LEVEL_INFORMATION, + TRACE_FLAG_IOCTL, + "ClasspServiceWriteUsingTokenTransferRequest (%p): Using MaxBlockDescrCount %u and MaxLbaCount %I64u.\n", + Fdo, + maxBlockDescrCount, + maxLbaCount)); + + offloadWriteContext->MaxBlockDescrCount = maxBlockDescrCount; + offloadWriteContext->MaxLbaCount = maxLbaCount; + + // + // Reuse a single buffer for both TokenOperation and ReceiveTokenInformation. This has the one disadvantage + // that we'll be marking the page(s) as IoWriteAccess even though we only need read access for token + // operation command and we may not even need to send down a ReceiveTokenInformation (in case of a successful + // synchronous transfer). However, the advantage is that we eliminate the possibility of any potential + // failures when trying to allocate an MDL for the ReceiveTokenInformation later on if we need to send it. + // + writeUsingTokenMdl = ClasspBuildDeviceMdl(buffer, bufferLength, FALSE); + if (!writeUsingTokenMdl) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspServiceWriteUsingTokenTransferRequest (%p): Failed to allocate MDL for WriteUsingToken operations.\n", + Fdo)); + + status = STATUS_INSUFFICIENT_RESOURCES; + goto __ClasspServiceWriteUsingTokenTransferRequest_ErrorExit; + } + + offloadWriteContext->WriteUsingTokenMdl = writeUsingTokenMdl; + + // + // There are potentially two approaches that we can take: + // 1. Determine how many transfer packets we need (in case we need to split the request), get + // them all up-front and then send down all the split WriteUsingToken commands in parallel. + // The benefit of this approach is that the performance will be improved in the success case. + // But error case handling becomes very complex to handle, since if one of the intermediate + // write fails, there is no way to cancel the remaining writes that were sent. Waiting for + // such requests to complete in geo-distributed source and target cases can be very time + // consuming. The complexity gets worse in the case that the target succeeds only a partial + // amount of data for one the of intermediate split commands. + // [OR] + // 2. Until the entire data set range is processed, build the command for as much of the range as + // possible, send down a packet, and once it completes, repeat sequentially in a loop. + // The advantage of this approach is its simplistic nature. In the success case, it will + // be less performant as compared to the previous approach, but since the gain of offload + // copy is so significant compared to native buffered-copy, the tradeoff is acceptable. + // In the failure case the simplicity offers the following benefit - if any command fails, + // there is no further processing that is needed. And the cumulative total bytes that succeeded + // (until the failing split WriteUsingToken command) is easily tracked. + // + // Given the above, we're going with the second approach. + // + + NT_ASSERT(status == STATUS_SUCCESS); // so far + + // + // Save (into the offloadReadContext) any remaining things that + // ClasspPopulateTokenTransferPacketDone() will need. + // + + offloadWriteContext->BufferLength = bufferLength; + offloadWriteContext->ReceiveTokenInformationBufferLength = receiveTokenInformationBufferLength; + offloadWriteContext->EntireXferLen = entireXferLen; + + IoMarkIrpPending(Irp); + ClasspContinueOffloadWrite(offloadWriteContext); + + status = STATUS_PENDING; + goto __ClasspServiceWriteUsingTokenTransferRequest_Exit; + + // + // Error label only - not used in success case: + // + +__ClasspServiceWriteUsingTokenTransferRequest_ErrorExit: + + NT_ASSERT(status != STATUS_PENDING); + + if (offloadWriteContext != NULL) { + ClasspCleanupOffloadWriteContext(offloadWriteContext); + offloadWriteContext = NULL; + } + +__ClasspServiceWriteUsingTokenTransferRequest_Exit: + + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_IOCTL, + "ClasspServiceWriteUsingTokenTransferRequest (%p): Exiting function (Irp %p) with status %x.\n", + Fdo, + Irp, + status)); + + return status; +} + + +VOID +#pragma warning(suppress: 28194) // This function will either alias or free OffloadWriteContext +ClasspContinueOffloadWrite( + _In_ __drv_aliasesMem POFFLOAD_WRITE_CONTEXT OffloadWriteContext + ) + +/*++ + +Routine description: + + This routine continues an offload write operation. This routine expects the + offload write operation to be set up and ready to start a WRITE USING TOKEN, + but with no WRITE USING TOKEN currently in flight. + + This routine is responsible for continuing or completing the offload write + operation. + +Arguments: + + OffloadWriteContext - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload + write operation. + +Return Value: + + None. + +--*/ + +{ + BOOLEAN allDataSetRangeFullyConverted; + ULONG blockDescrIndex; + PBLOCK_DEVICE_RANGE_DESCRIPTOR blockDescrPointer; + PVOID buffer; + ULONG bufferLength; + ULONGLONG dataSetRangeByteOffset; + ULONG dataSetRangeIndex; + PDEVICE_DATA_SET_RANGE dataSetRanges; + ULONG dataSetRangesCount; + ULONGLONG entireXferLen; + PDEVICE_OBJECT fdo; + PFUNCTIONAL_DEVICE_EXTENSION fdoExt; + PIRP irp; + ULONG lbaCount; + ULONG listIdentifier; + ULONGLONG logicalBlockOffset; + ULONG maxBlockDescrCount; + ULONGLONG maxLbaCount; + PDEVICE_DSM_OFFLOAD_WRITE_PARAMETERS offloadWriteParameters; + PTRANSFER_PACKET pkt; + PIRP pseudoIrp; + NTSTATUS status; + DEVICE_DATA_SET_RANGE tempDataSetRange; + BOOLEAN tempDataSetRangeFullyConverted; + PVOID tokenAscii; + ULONG tokenSize; + ULONGLONG totalSectorCount; + ULONGLONG totalSectorsProcessedSuccessfully; + ULONGLONG totalSectorsToProcess; + ULONG transferSize; + USHORT writeUsingTokenDataLength; + USHORT writeUsingTokenDescriptorsLength; + PMDL writeUsingTokenMdl; + + tokenAscii = NULL; + tokenSize = BLOCK_DEVICE_TOKEN_SIZE; + tempDataSetRangeFullyConverted = FALSE; + allDataSetRangeFullyConverted = FALSE; + fdo = OffloadWriteContext->Fdo; + fdoExt = fdo->DeviceExtension; + irp = OffloadWriteContext->OffloadWriteDsmIrp; + buffer = OffloadWriteContext + 1; + bufferLength = OffloadWriteContext->BufferLength; + dataSetRanges = OffloadWriteContext->DataSetRanges; + offloadWriteParameters = OffloadWriteContext->OffloadWriteParameters; + pseudoIrp = &OffloadWriteContext->PseudoIrp; + writeUsingTokenMdl = OffloadWriteContext->WriteUsingTokenMdl; + entireXferLen = OffloadWriteContext->EntireXferLen; + + RtlZeroMemory(buffer, bufferLength); + + pkt = DequeueFreeTransferPacket(fdo, TRUE); + if (!pkt){ + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspContinueOffloadWrite (%p): Failed to retrieve transfer packet for TokenOperation (WriteUsingToken) operation.\n", + fdo)); + + status = STATUS_INSUFFICIENT_RESOURCES; + goto __ClasspContinueOffloadWrite_ErrorExit; + } + + OffloadWriteContext->Pkt = pkt; + + blockDescrPointer = (PBLOCK_DEVICE_RANGE_DESCRIPTOR) + &((PWRITE_USING_TOKEN_HEADER)buffer)->BlockDeviceRangeDescriptor[0]; + + blockDescrIndex = 0; + lbaCount = 0; + + totalSectorsToProcess = 0; + + maxBlockDescrCount = OffloadWriteContext->MaxBlockDescrCount; + maxLbaCount = OffloadWriteContext->MaxLbaCount; + + // + // The OffloadWriteContext->DataSetRangeIndex, DataSetRangeByteOffset, and + // TotalSectorsProcessedSuccessfully don't move forward until RRTI has + // reported the actual amount written. + // + // For that reason, this function only updates + // OffloadWriteContext->TotalSectorsProcessed, which tracks the number of + // sectors requested to be written by the current WRITE USING TOKEN command + // (not all will necessarily be written). + // + + dataSetRangeIndex = OffloadWriteContext->DataSetRangeIndex; + dataSetRangesCount = OffloadWriteContext->DataSetRangesCount; + dataSetRangeByteOffset = OffloadWriteContext->DataSetRangeByteOffset; + totalSectorsProcessedSuccessfully = OffloadWriteContext->TotalSectorsProcessedSuccessfully; + + // + // Send WriteUsingToken commands when the buffer is full or all input entries are converted. + // + while (!((blockDescrIndex == maxBlockDescrCount) || // buffer full or block descriptor count reached + (lbaCount == maxLbaCount) || // block LBA count reached + (allDataSetRangeFullyConverted))) { // all DataSetRanges have been converted + + NT_ASSERT(dataSetRangeIndex < dataSetRangesCount); + NT_ASSERT(dataSetRangeByteOffset < dataSetRanges[dataSetRangeIndex].LengthInBytes); + + tempDataSetRange.StartingOffset = dataSetRanges[dataSetRangeIndex].StartingOffset + dataSetRangeByteOffset; + tempDataSetRange.LengthInBytes = dataSetRanges[dataSetRangeIndex].LengthInBytes - dataSetRangeByteOffset; + + totalSectorCount = 0; + + ClasspConvertDataSetRangeToBlockDescr(fdo, + blockDescrPointer, + &blockDescrIndex, + maxBlockDescrCount, + &lbaCount, + maxLbaCount, + &tempDataSetRange, + &totalSectorCount); + + tempDataSetRangeFullyConverted = (tempDataSetRange.LengthInBytes == 0) ? TRUE : FALSE; + + allDataSetRangeFullyConverted = tempDataSetRangeFullyConverted && ((dataSetRangeIndex + 1) == dataSetRangesCount); + + if (tempDataSetRangeFullyConverted) { + dataSetRangeIndex += 1; + dataSetRangeByteOffset = 0; + NT_ASSERT(dataSetRangeIndex <= dataSetRangesCount); + } else { + dataSetRangeByteOffset += totalSectorCount * fdoExt->DiskGeometry.BytesPerSector; + NT_ASSERT(dataSetRangeByteOffset < dataSetRanges[dataSetRangeIndex].LengthInBytes); + } + + totalSectorsToProcess += totalSectorCount; + } + + // + // Save the number of sectors being attempted in this WRITE USING TOKEN + // command, so that a success return from the command will know how much + // was written, without needing to issue a RECEIVE ROD TOKEN INFORMATION + // command. + // + OffloadWriteContext->TotalSectorsToProcess = totalSectorsToProcess; + OffloadWriteContext->TotalSectorsProcessed = 0; + + // + // Calculate transfer size, including the header + // + transferSize = (blockDescrIndex * sizeof(BLOCK_DEVICE_RANGE_DESCRIPTOR)) + FIELD_OFFSET(WRITE_USING_TOKEN_HEADER, BlockDeviceRangeDescriptor); + + NT_ASSERT(transferSize <= MAX_TOKEN_OPERATION_PARAMETER_DATA_LENGTH); + + writeUsingTokenDataLength = (USHORT)transferSize - RTL_SIZEOF_THROUGH_FIELD(WRITE_USING_TOKEN_HEADER, WriteUsingTokenDataLength); + REVERSE_BYTES_SHORT(((PWRITE_USING_TOKEN_HEADER)buffer)->WriteUsingTokenDataLength, &writeUsingTokenDataLength); + + ((PWRITE_USING_TOKEN_HEADER)buffer)->Immediate = 0; + + logicalBlockOffset = OffloadWriteContext->LogicalBlockOffset + totalSectorsProcessedSuccessfully; + REVERSE_BYTES_QUAD(((PWRITE_USING_TOKEN_HEADER)buffer)->BlockOffsetIntoToken, &logicalBlockOffset); + + RtlCopyMemory(((PWRITE_USING_TOKEN_HEADER)buffer)->Token, + &offloadWriteParameters->Token, + BLOCK_DEVICE_TOKEN_SIZE); + + writeUsingTokenDescriptorsLength = (USHORT)transferSize - FIELD_OFFSET(WRITE_USING_TOKEN_HEADER, BlockDeviceRangeDescriptor); + REVERSE_BYTES_SHORT(((PWRITE_USING_TOKEN_HEADER)buffer)->BlockDeviceRangeDescriptorListLength, &writeUsingTokenDescriptorsLength); + + RtlZeroMemory(pseudoIrp, sizeof(IRP)); + + pseudoIrp->IoStatus.Status = STATUS_SUCCESS; + pseudoIrp->IoStatus.Information = 0; + pseudoIrp->Tail.Overlay.DriverContext[0] = LongToPtr(1); + pseudoIrp->MdlAddress = writeUsingTokenMdl; + + InterlockedCompareExchange((volatile LONG *)&TokenOperationListIdentifier, -1, MaxTokenOperationListIdentifier); + listIdentifier = InterlockedIncrement((volatile LONG *)&TokenOperationListIdentifier); + + ClasspSetupWriteUsingTokenTransferPacket( + OffloadWriteContext, + pkt, + transferSize, + (PUCHAR)buffer, + pseudoIrp, + listIdentifier); + + tokenAscii = ClasspBinaryToAscii((PUCHAR)(((PWRITE_USING_TOKEN_HEADER)buffer)->Token), + tokenSize, + &tokenSize); + + TracePrint((TRACE_LEVEL_INFORMATION, + TRACE_FLAG_IOCTL, + "ClasspContinueOffloadWrite (%p): Offloading write for %I64u bytes (versus %I64u) [via %u descriptors]. \ + \n\t\t\tDataLength: %u, DescriptorsLength: %u. Pkt %p (list id %x) [Token: %s]\n", + fdo, + totalSectorsToProcess * fdoExt->DiskGeometry.BytesPerSector, + entireXferLen, + blockDescrIndex, + writeUsingTokenDataLength, + writeUsingTokenDescriptorsLength, + pkt, + listIdentifier, + (tokenAscii == NULL) ? "" : tokenAscii)); + + FREE_POOL(tokenAscii); + + OffloadWriteContext->ListIdentifier = listIdentifier; + + SubmitTransferPacket(pkt); + + // + // ClasspWriteUsingTokenTransferPacketDone() takes care of completing the + // IRP, so this function is done. + // + + return; + + // + // Error cleaup label only - not used in success case: + // + +__ClasspContinueOffloadWrite_ErrorExit: + + NT_ASSERT(!NT_SUCCESS(status)); + + // + // ClasspCompleteOffloadWrite also cleans up offloadWriteContext. + // + + ClasspCompleteOffloadWrite(OffloadWriteContext, status); + + return; +} + + +VOID +ClasspAdvanceOffloadWritePosition( + _In_ POFFLOAD_WRITE_CONTEXT OffloadWriteContext, + _In_ ULONGLONG SectorsToAdvance + ) + +/*++ + +Routine description: + + After the target has responded to WRITE USING TOKEN with success, or RRTI + with a specific TRANSFER COUNT, this routine is used to update the relative + position within the overall offload write request. This position includes + the TotalSectorsProcessedSuccessfully, the DataSetRangeIndex, and the + DataSetRangeByteOffset. + + The caller is responsible for continuing or completing the offload write + operation (this routine doesn't do that). + +Arguments: + + OffloadWriteContext - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload + write operation. + + SectorsToAdvance - The number of sectors which were just written + successfully (not the total for the offload write operation overall, + just the number done by the most recent WRITE USING TOKEN). + +Return Value: + + None. + +--*/ + +{ + ULONGLONG bytesToAdvance; + ULONGLONG bytesToDo; + PULONGLONG dataSetRangeByteOffset; + PULONG dataSetRangeIndex; + PDEVICE_DATA_SET_RANGE dataSetRanges; + PDEVICE_OBJECT fdo; + PFUNCTIONAL_DEVICE_EXTENSION fdoExt; + PULONGLONG totalSectorsProcessedSuccessfully; + + fdo = OffloadWriteContext->Fdo; + fdoExt = fdo->DeviceExtension; + dataSetRanges = OffloadWriteContext->DataSetRanges; + dataSetRangeByteOffset = &OffloadWriteContext->DataSetRangeByteOffset; + dataSetRangeIndex = &OffloadWriteContext->DataSetRangeIndex; + totalSectorsProcessedSuccessfully = &OffloadWriteContext->TotalSectorsProcessedSuccessfully; + bytesToAdvance = SectorsToAdvance * fdoExt->DiskGeometry.BytesPerSector; + + (*totalSectorsProcessedSuccessfully) += SectorsToAdvance; + NT_ASSERT((*totalSectorsProcessedSuccessfully) <= OffloadWriteContext->TotalRequestSizeSectors); + + while (bytesToAdvance != 0) { + bytesToDo = dataSetRanges[*dataSetRangeIndex].LengthInBytes - *dataSetRangeByteOffset; + if (bytesToDo > bytesToAdvance) { + bytesToDo = bytesToAdvance; + } + (*dataSetRangeByteOffset) += bytesToDo; + bytesToAdvance -= bytesToDo; + if ((*dataSetRangeByteOffset) == dataSetRanges[*dataSetRangeIndex].LengthInBytes) { + (*dataSetRangeIndex) += 1; + (*dataSetRangeByteOffset) = 0; + } + } + + NT_ASSERT((*dataSetRangeIndex) <= OffloadWriteContext->DataSetRangesCount); + + return; +} + + +VOID +ClasspWriteUsingTokenTransferPacketDone( + _In_ PVOID Context + ) + +/*++ + +Routine description: + + This routine continues an offload write operation when the WRITE USING + TOKEN transfer packet completes. + + This routine may be able to determine that all requested sectors were + written if the WRITE USING TOKEN completed with success, or may need to + issue a RECEIVE ROD TOKEN INFORMATION if the WRITE USING TOKEN indicated + check condition. + + This routine is responsible for continuing or completing the offload write + operation. + +Arguments: + + Context - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload write + operation. + +Return Value: + + None. + +--*/ + +{ + ULONGLONG entireXferLen; + PDEVICE_OBJECT fdo; + PFUNCTIONAL_DEVICE_EXTENSION fdoExt; + ULONG listIdentifier; + POFFLOAD_WRITE_CONTEXT offloadWriteContext; + PTRANSFER_PACKET pkt; + PIRP pseudoIrp; + NTSTATUS status; + PBOOLEAN tokenInvalidated; + ULONGLONG totalSectorsToProcess; + + offloadWriteContext = Context; + pseudoIrp = &offloadWriteContext->PseudoIrp; + fdo = offloadWriteContext->Fdo; + fdoExt = fdo->DeviceExtension; + listIdentifier = offloadWriteContext->ListIdentifier; + totalSectorsToProcess = offloadWriteContext->TotalSectorsToProcess; + entireXferLen = offloadWriteContext->EntireXferLen; + tokenInvalidated = &offloadWriteContext->TokenInvalidated; + pkt = offloadWriteContext->Pkt; + + offloadWriteContext->Pkt = NULL; + + + status = pseudoIrp->IoStatus.Status; + NT_ASSERT(status != STATUS_PENDING); + + // + // If the request failed with any of the following errors, then it is meaningless to send + // down a ReceiveTokenInformation (regardless of whether the transfer was requested as sync + // or async), since the target has no saved information about the command: + // - STATUS_INVALID_TOKEN + // - STATUS_INVALID_PARAMETER + // + if (status == STATUS_INVALID_PARAMETER || + status == STATUS_INVALID_TOKEN) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspWriteUsingTokenTransferPacketDone (%p): Write failed with %x (list id %x).\n", + fdo, + status, + listIdentifier)); + + // + // If the token isn't valid any longer, we should let the upper layers know so that + // they don't waste time retrying the write with the same token. + // + if (status == STATUS_INVALID_TOKEN) { + + *tokenInvalidated = TRUE; + } + + NT_ASSERT(status != STATUS_PENDING && !NT_SUCCESS(status)); + goto __ClasspWriteUsingTokenTransferPacketDone_Exit; + + } else if ((NT_SUCCESS(status)) && + (pkt->Srb->SrbStatus == SRB_STATUS_SUCCESS || pkt->TransferCount != 0)) { + + // + // If the TokenOperation command was sent to the target requesting synchronous data + // transfer, a success indicates that the command is complete. + // This could either be because of a successful completion of the entire transfer + // or because of a partial transfer due to target truncation. If it is the latter, + // and the information field of the sense data has returned the TransferCount, we + // can avoid sending down an RRTI. + // + if (pkt->Srb->SrbStatus == SRB_STATUS_SUCCESS) { + + // + // The entire transfer has completed successfully. + // + offloadWriteContext->TotalSectorsProcessed = totalSectorsToProcess; + TracePrint((TRACE_LEVEL_INFORMATION, + TRACE_FLAG_IOCTL, + "ClasspWriteUsingTokenTransferPacketDone (%p): Successfully wrote using token %I64u (out of %I64u) bytes (list Id %x).\n", + fdo, + totalSectorsToProcess * fdoExt->DiskGeometry.BytesPerSector, + entireXferLen, + listIdentifier)); + } else { + + // + // The target has returned how much data it transferred in the response to the + // WUT command itself, allowing us to optimize by removing the necessaity for + // sending down an RRTI to query the TransferCount. + // + NT_ASSERT(pkt->TransferCount); + + offloadWriteContext->TotalSectorsProcessed = totalSectorsToProcess = pkt->TransferCount; + TracePrint((TRACE_LEVEL_INFORMATION, + TRACE_FLAG_IOCTL, + "ClasspWriteUsingTokenTransferPacketDone (%p): Target truncated write using token %I64u (out of %I64u) bytes (list Id %x).\n", + fdo, + totalSectorsToProcess * fdoExt->DiskGeometry.BytesPerSector, + entireXferLen, + listIdentifier)); + } + + ClasspAdvanceOffloadWritePosition(offloadWriteContext, totalSectorsToProcess); + + NT_ASSERT(status != STATUS_PENDING); + + // + // ClasspReceiveWriteUsingTokenInformationDone() takes care of + // completing the operation (eventually), so pending from point of view + // of this function. + // + + ClasspReceiveWriteUsingTokenInformationDone(offloadWriteContext, status); + status = STATUS_PENDING; + + goto __ClasspWriteUsingTokenTransferPacketDone_Exit; + + } else { + + // + // Since the TokenOperation was failed (or the target truncated the transfer but + // didn't indicate the amount), we need to send down ReceiveTokenInformation. + // + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspWriteUsingTokenTransferPacketDone (%p): Write failed with status %x, %x (list id %x).\n", + fdo, + status, + pkt->Srb->SrbStatus, + listIdentifier)); + + ClasspReceiveWriteUsingTokenInformation(offloadWriteContext); + + status = STATUS_PENDING; + goto __ClasspWriteUsingTokenTransferPacketDone_Exit; + } + +__ClasspWriteUsingTokenTransferPacketDone_Exit: + + if (status != STATUS_PENDING) { + ClasspCompleteOffloadWrite(offloadWriteContext, status); + } + + return; +} + + +VOID +ClasspReceiveWriteUsingTokenInformationDone( + _In_ POFFLOAD_WRITE_CONTEXT OffloadWriteContext, + _In_ NTSTATUS CompletionCausingStatus + ) + +/*++ + +Routine description: + + This routine continues an offload write operation when a WRITE USING TOKEN + and possible associated RECEIVE ROD TOKEN INFORMATION have both fully + completed and the RRTI has indicated completion of the WUT. + + This routine checks to see if the total sectors written is already equal to + the overall total requested sector count, and if so, completes the offload + write operation. If not, this routine continues the operation by issuing + another WUT. + + This routine is responsible for continuing or completing the offload write + operation. + +Arguments: + + OffloadWriteContext - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload + write operation. + +Return Value: + + None. + +--*/ + +{ + NT_ASSERT( + OffloadWriteContext->TotalSectorsProcessedSuccessfully <= + OffloadWriteContext->TotalRequestSizeSectors); + + // Time taken in 100 ns units + ULONGLONG durationIn100ns = (KeQueryInterruptTime() - OffloadWriteContext->OperationStartTime); + + + if (OffloadWriteContext->TotalSectorsProcessedSuccessfully == OffloadWriteContext->TotalRequestSizeSectors) { + + ClasspCompleteOffloadWrite(OffloadWriteContext, CompletionCausingStatus); + + goto __ClasspReceiveWriteUsingTokenInformationDone_Exit; + } + + // + // Since we don't want a layered timeout mechanism (e.g. guest and parent OS in Hyper-V scenarios) + // to cause a SCSI timeout for the higher layer token operations. + // + if (MAX_TARGET_DURATION <= durationIn100ns) { + + TracePrint((TRACE_LEVEL_WARNING, + TRACE_FLAG_IOCTL, + "ClasspReceiveWriteUsingTokenInformationDone (%p): Truncating write (list id %x) because of max-duration-rule.\n", + OffloadWriteContext->Fdo, + OffloadWriteContext->ListIdentifier)); + + // + // We could technically pass in STATUS_IO_OPERATION_TIMEOUT, but ClasspCompleteOffloadWrite + // won't end up doing anything (useful) with this status, since some bytes would already + // have been transferred. + // + ClasspCompleteOffloadWrite(OffloadWriteContext, STATUS_UNSUCCESSFUL); + + goto __ClasspReceiveWriteUsingTokenInformationDone_Exit; + } + + NT_ASSERT( + OffloadWriteContext->TotalSectorsProcessedSuccessfully < + OffloadWriteContext->TotalRequestSizeSectors); + + // + // Keep going with the next sub-request. + // + + ClasspContinueOffloadWrite(OffloadWriteContext); + +__ClasspReceiveWriteUsingTokenInformationDone_Exit: + + return; +} + + +VOID +ClasspCompleteOffloadWrite( + _In_ __drv_freesMem(Mem) POFFLOAD_WRITE_CONTEXT OffloadWriteContext, + _In_ NTSTATUS CompletionCausingStatus + ) + +/*++ + +Routine description: + + This routine is used to complete an offload write operation. + + The input CompletionCausingStatus doesn't necessarily drive the completion + status of the offload write operation overall, if the offload write + operation overall has previously written some sectors successfully. + + This routine completes the offload write operation. + +Arguments: + + OffloadWriteContext - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload + write operation. + + CompletionCausingStatus - Status code indicating a reason that the offload + write operation is completing. For success this will be STATUS_SUCCESS, + but for a failure, this status will indicate what failure occurred. + This status doesn't directly propagate to the completion status of the + overall offload write operation if this status is failure and the + overall offload write operation has already previously written some + sectors successfully. + +Return Value: + + None. + +--*/ + +{ + PDEVICE_OBJECT fdo; + PFUNCTIONAL_DEVICE_EXTENSION fdoExt; + PDEVICE_MANAGE_DATA_SET_ATTRIBUTES dsmAttributes; + PULONGLONG totalSectorsProcessedSuccessfully; + ULONGLONG entireXferLen; + PIRP irp; + PBOOLEAN tokenInvalidated; + ULONG listIdentifier; + ULONGLONG totalSectorsProcessed; + NTSTATUS status; + ULONGLONG totalBytesProcessed; + + fdo = OffloadWriteContext->Fdo; + fdoExt = fdo->DeviceExtension; + dsmAttributes = OffloadWriteContext->DsmAttributes; + totalSectorsProcessedSuccessfully = &OffloadWriteContext->TotalSectorsProcessedSuccessfully; + entireXferLen = OffloadWriteContext->EntireXferLen; + irp = OffloadWriteContext->OffloadWriteDsmIrp; + tokenInvalidated = &OffloadWriteContext->TokenInvalidated; + listIdentifier = OffloadWriteContext->ListIdentifier; + totalSectorsProcessed = OffloadWriteContext->TotalSectorsProcessed; + status = CompletionCausingStatus; + + ((PSTORAGE_OFFLOAD_WRITE_OUTPUT)dsmAttributes)->OffloadWriteFlags = 0; + ((PSTORAGE_OFFLOAD_WRITE_OUTPUT)dsmAttributes)->Reserved = 0; + + totalBytesProcessed = (*totalSectorsProcessedSuccessfully) * fdoExt->DiskGeometry.BytesPerSector; + + TracePrint((totalBytesProcessed == entireXferLen ? TRACE_LEVEL_INFORMATION : TRACE_LEVEL_WARNING, + TRACE_FLAG_IOCTL, + "ClasspCompleteOffloadWrite (%p): %ws wrote using token %I64u (out of %I64u) bytes (Irp %p).\n", + fdo, + NT_SUCCESS(status) ? L"Successful" : L"Failed", + totalBytesProcessed, + entireXferLen, + irp)); + + if (totalBytesProcessed > 0 && totalBytesProcessed < entireXferLen) { + SET_FLAG(((PSTORAGE_OFFLOAD_WRITE_OUTPUT)dsmAttributes)->OffloadWriteFlags, STORAGE_OFFLOAD_WRITE_RANGE_TRUNCATED); + } + if (*tokenInvalidated) { + SET_FLAG(((PSTORAGE_OFFLOAD_WRITE_OUTPUT)dsmAttributes)->OffloadWriteFlags, STORAGE_OFFLOAD_TOKEN_INVALID); + } + ((PSTORAGE_OFFLOAD_WRITE_OUTPUT)dsmAttributes)->LengthCopied = totalBytesProcessed; + + + if (!NT_SUCCESS(status)) { + + TracePrint((TRACE_LEVEL_WARNING, + TRACE_FLAG_IOCTL, + "ClasspCompleteOffloadWrite (%p): TokenOperation for WriteUsingToken (list Id %u) completed with %x writing %I64u blocks (currentTotal %I64u blocks).\n", + fdo, + listIdentifier, + status, + totalSectorsProcessed, + *totalSectorsProcessedSuccessfully)); + + // + // Even if target returned an error, from the OS upper layers' perspective, + // it is a success (with truncation) if any data at all was written. + // + if (*totalSectorsProcessedSuccessfully) { + status = STATUS_SUCCESS; + } + } + + irp->IoStatus.Information = sizeof(STORAGE_OFFLOAD_WRITE_OUTPUT); + + ClasspCompleteOffloadRequest(fdo, irp, status); + ClasspCleanupOffloadWriteContext(OffloadWriteContext); + OffloadWriteContext = NULL; + + return; +} + + +VOID +ClasspCleanupOffloadWriteContext( + _In_ __drv_freesMem(mem) POFFLOAD_WRITE_CONTEXT OffloadWriteContext + ) + +/*++ + +Routine description: + + This routine cleans up an offload write context. + +Arguments: + + OffloadWriteContext - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload + write operation. + +Return Value: + + None. + +--*/ + +{ + PMDL writeUsingTokenMdl = OffloadWriteContext->WriteUsingTokenMdl; + + if (writeUsingTokenMdl) { + ClasspFreeDeviceMdl(writeUsingTokenMdl); + } + FREE_POOL(OffloadWriteContext); + + return; +} + + +_IRQL_requires_same_ +VOID +ClasspReceiveWriteUsingTokenInformation( + _In_ POFFLOAD_WRITE_CONTEXT OffloadWriteContext + ) + +/*++ + +Routine description: + + This routine retrieves the token after a WriteUsingToken command + has been sent down in case of an error or if there is a need to + poll for the result. + + This routine is responsible for continuing or completing the offload write + operation. + +Arguments: + + OffloadWriteContext - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload + write operation. + +Return Value: + + None. + +--*/ + +{ + PVOID buffer; + ULONG bufferLength; + ULONG cdbLength; + PDEVICE_OBJECT fdo; + PIRP irp; + ULONG listIdentifier; + PTRANSFER_PACKET pkt; + PIRP pseudoIrp; + ULONG receiveTokenInformationBufferLength; + PSCSI_REQUEST_BLOCK srb; + NTSTATUS status; + ULONG tempSizeUlong; + PMDL writeUsingTokenMdl; + + fdo = OffloadWriteContext->Fdo; + irp = OffloadWriteContext->OffloadWriteDsmIrp; + pseudoIrp = &OffloadWriteContext->PseudoIrp; + buffer = OffloadWriteContext + 1; + bufferLength = OffloadWriteContext->BufferLength; + receiveTokenInformationBufferLength = OffloadWriteContext->ReceiveTokenInformationBufferLength; + writeUsingTokenMdl = OffloadWriteContext->WriteUsingTokenMdl; + listIdentifier = OffloadWriteContext->ListIdentifier; + srb = &OffloadWriteContext->Srb; + status = STATUS_SUCCESS; + + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_IOCTL, + "ClasspReceiveWriteUsingTokenInformation (%p): Entering function. Irp %p\n", + fdo, + irp)); + + // + // The WRITE USING TOKEN wasn't immediately fully successful, so that means + // the only way to find out how many sectors were processed by the WRITE + // USING TOKEN is to get a successful RECEIVE ROD TOKEN INFORMATION that + // indicates the operation is complete. + // + + NT_ASSERT(OffloadWriteContext->TotalSectorsProcessed == 0); + + pkt = DequeueFreeTransferPacket(fdo, TRUE); + + if (!pkt) { + + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspReceiveWriteUsingTokenInformation (%p): Failed to retrieve transfer packet for ReceiveTokenInformation (WriteUsingToken) operation.\n", + fdo)); + + status = STATUS_INSUFFICIENT_RESOURCES; + + goto __ClasspReceiveWriteUsingTokenInformation_ErrorExit; + } + + RtlZeroMemory(buffer, bufferLength); + + tempSizeUlong = receiveTokenInformationBufferLength - 4; + REVERSE_BYTES(((PRECEIVE_TOKEN_INFORMATION_HEADER)buffer)->AvailableData, &tempSizeUlong); + + RtlZeroMemory(pseudoIrp, sizeof(IRP)); + + pseudoIrp->IoStatus.Status = STATUS_SUCCESS; + pseudoIrp->IoStatus.Information = 0; + pseudoIrp->Tail.Overlay.DriverContext[0] = LongToPtr(1); + pseudoIrp->MdlAddress = writeUsingTokenMdl; + + ClasspSetupReceiveWriteUsingTokenInformationTransferPacket( + OffloadWriteContext, + pkt, + bufferLength, + (PUCHAR)buffer, + pseudoIrp, + listIdentifier); + + // + // Cache away the CDB as it may be required for forwarded sense data + // after this command completes. + // + RtlZeroMemory(srb, sizeof(*srb)); + cdbLength = SrbGetCdbLength(pkt->Srb); + if (cdbLength <= 16) { + RtlCopyMemory(&srb->Cdb, SrbGetCdb(pkt->Srb), cdbLength); + } + + SubmitTransferPacket(pkt); + + return; + + // + // Error label only - not used by success cases: + // + +__ClasspReceiveWriteUsingTokenInformation_ErrorExit: + + NT_ASSERT(!NT_SUCCESS(status)); + + // + // ClasspCompleteOffloadWrite also cleans up OffloadWriteContext. + // + + ClasspCompleteOffloadWrite(OffloadWriteContext, status); + + return; +} + + +VOID +ClasspReceiveWriteUsingTokenInformationTransferPacketDone( + _In_ POFFLOAD_WRITE_CONTEXT OffloadWriteContext + ) + +/*++ + +Routine description: + + This routine continues an offload write operation when a RECEIVE ROD TOKEN + INFORMATION transfer packet is done. + + This routine may need to send another RRTI, or it may be able to indicate + that this WUT is done via call to + ClasspReceiveWriteUsingTokenInformationDone(). + + This routine is responsible for continuing or completing the offload write + operation. + +Arguments: + + OffloadWriteContext - Pointer to the OFFLOAD_WRITE_CONTEXT for the offload + write operation. + +Return Value: + + None. + +--*/ + +{ + ULONG availableData; + PVOID buffer; + UCHAR completionStatus; + ULONG estimatedRetryInterval; + PDEVICE_OBJECT fdo; + PFUNCTIONAL_DEVICE_EXTENSION fdoExt; + PIRP irp; + ULONG listIdentifier; + BOOLEAN operationCompleted; + UCHAR operationStatus; + PIRP pseudoIrp; + USHORT segmentsProcessed; + PSENSE_DATA senseData; + ULONG senseDataFieldLength; + UCHAR senseDataLength; + PSCSI_REQUEST_BLOCK srb; + NTSTATUS status; + ULONG tokenDescriptorLength; + PRECEIVE_TOKEN_INFORMATION_HEADER tokenInformationResults; + PRECEIVE_TOKEN_INFORMATION_RESPONSE_HEADER tokenInformationResponsePadding; + PBOOLEAN tokenInvalidated; + PULONGLONG totalSectorsProcessed; + ULONGLONG totalSectorsToProcess; + ULONGLONG transferBlockCount; + + fdo = OffloadWriteContext->Fdo; + fdoExt = fdo->DeviceExtension; + listIdentifier = OffloadWriteContext->ListIdentifier; + totalSectorsProcessed = &OffloadWriteContext->TotalSectorsProcessed; + totalSectorsToProcess = OffloadWriteContext->TotalSectorsToProcess; + irp = OffloadWriteContext->OffloadWriteDsmIrp; + pseudoIrp = &OffloadWriteContext->PseudoIrp; + tokenInvalidated = &OffloadWriteContext->TokenInvalidated; + srb = &OffloadWriteContext->Srb; + operationCompleted = FALSE; + buffer = OffloadWriteContext + 1; + tokenInformationResults = (PRECEIVE_TOKEN_INFORMATION_HEADER)buffer; + senseData = (PSENSE_DATA)((PUCHAR)tokenInformationResults + FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_HEADER, SenseData)); + transferBlockCount = 0; + tokenInformationResponsePadding = NULL; + tokenDescriptorLength = 0; + + NT_ASSERT((*totalSectorsProcessed) == 0); + + OffloadWriteContext->Pkt = NULL; + + + status = pseudoIrp->IoStatus.Status; + NT_ASSERT(status != STATUS_PENDING); + + // + // The buffer we hand allows for the max sizes for all the fields whereas the returned + // data may be lesser (e.g. sense data info will almost never be MAX_SENSE_BUFFER_SIZE, etc. + // so handle underrun "error" + // + if (status == STATUS_DATA_OVERRUN) { + + status = STATUS_SUCCESS; + } + + if (!NT_SUCCESS(status)) { + TracePrint((TRACE_LEVEL_ERROR, + TRACE_FLAG_IOCTL, + "ClasspReceiveWriteUsingTokenInformationTransferPacketDone (%p): Failed with %x to retrieve write results for list Id %x for data size %I64u bytes.\n", + fdo, + status, + listIdentifier, + totalSectorsToProcess * fdoExt->DiskGeometry.BytesPerSector)); + + NT_ASSERT((*totalSectorsProcessed) == 0); + + goto __ClasspReceiveWriteUsingTokenInformationTransferPacketDone_ErrorExit; + } + + REVERSE_BYTES(&availableData, &tokenInformationResults->AvailableData); + + NT_ASSERT(availableData <= FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_HEADER, SenseData) + MAX_SENSE_BUFFER_SIZE); + + NT_ASSERT(tokenInformationResults->ResponseToServiceAction == SERVICE_ACTION_WRITE_USING_TOKEN); + + operationStatus = tokenInformationResults->OperationStatus; + operationCompleted = ClasspIsTokenOperationComplete(operationStatus); + NT_ASSERT(operationCompleted); + + REVERSE_BYTES(&estimatedRetryInterval, &tokenInformationResults->EstimatedStatusUpdateDelay); + + completionStatus = tokenInformationResults->CompletionStatus; + + senseDataFieldLength = tokenInformationResults->SenseDataFieldLength; + senseDataLength = tokenInformationResults->SenseDataLength; + NT_ASSERT(senseDataFieldLength >= senseDataLength); + + tokenInformationResponsePadding = (PRECEIVE_TOKEN_INFORMATION_RESPONSE_HEADER)((PUCHAR)tokenInformationResults + + FIELD_OFFSET(RECEIVE_TOKEN_INFORMATION_HEADER, SenseData) + + tokenInformationResults->SenseDataFieldLength); + + REVERSE_BYTES(&tokenDescriptorLength, &tokenInformationResponsePadding->TokenDescriptorsLength); + NT_ASSERT(tokenDescriptorLength == 0); + + NT_ASSERT(tokenInformationResults->TransferCountUnits == TRANSFER_COUNT_UNITS_NUMBER_BLOCKS); + REVERSE_BYTES_QUAD(&transferBlockCount, &tokenInformationResults->TransferCount); + + REVERSE_BYTES_SHORT(&segmentsProcessed, &tokenInformationResults->SegmentsProcessed); + NT_ASSERT(segmentsProcessed == 0); + + if (operationCompleted) { + + if (transferBlockCount > totalSectorsToProcess) { + + // + // Buggy or hostile target. Don't let it claim more was procesed + // than was requested. Since this is likely a bug and it's unknown + // how much was actually transferred, assume no data was + // transferred. + // + + NT_ASSERT(transferBlockCount <= totalSectorsToProcess); + transferBlockCount = 0; + } + + if (operationStatus != OPERATION_COMPLETED_WITH_SUCCESS && + operationStatus != OPERATION_COMPLETED_WITH_RESIDUAL_DATA) { + + // + // Assert on buggy response from target, but in any case, make sure not + // to claim that any data was written. + // + + NT_ASSERT(transferBlockCount == 0); + transferBlockCount = 0; + } + + // + // Since the TokenOperation was sent down synchronously but failed, the operation is complete as soon as the + // ReceiveTokenInformation command returns. + // + + NT_ASSERT((*totalSectorsProcessed) == 0); + *totalSectorsProcessed = transferBlockCount; + ClasspAdvanceOffloadWritePosition(OffloadWriteContext, transferBlockCount); + + if (transferBlockCount < totalSectorsToProcess) { + + NT_ASSERT(operationStatus == OPERATION_COMPLETED_WITH_RESIDUAL_DATA || + operationStatus == OPERATION_COMPLETED_WITH_ERROR || + operationStatus == OPERATION_TERMINATED); + + } else { + + NT_ASSERT(operationStatus == OPERATION_COMPLETED_WITH_SUCCESS); + } + + TracePrint((transferBlockCount == totalSectorsToProcess ? TRACE_LEVEL_INFORMATION : TRACE_LEVEL_WARNING, + TRACE_FLAG_IOCTL, + "ClasspReceiveWriteUsingTokenInformationTransferPacketDone (%p): %wsSuccessfully wrote (for list Id %x) for data size %I64u bytes\n", + fdo, + transferBlockCount == totalSectorsToProcess ? L"" : L"Target truncated write. ", + listIdentifier, + (*totalSectorsProcessed) * fdoExt->DiskGeometry.BytesPerSector)); + + // + // Operation that completes with success can have sense data (for target to pass on some extra info) + // but we don't care about such sense info. + // Operation that complete but not with success, may not have sense data associated, but may + // have valid CompletionStatus. + // + // The "status" may be overriden by ClassInterpretSenseInfo(). Final + // status is determined a bit later - this is just the default status + // when ClassInterpretSenseInfo() doesn't get to run here. + // + status = STATUS_SUCCESS; + if (operationStatus == OPERATION_COMPLETED_WITH_ERROR || + operationStatus == OPERATION_COMPLETED_WITH_RESIDUAL_DATA || + operationStatus == OPERATION_TERMINATED) { + + SrbSetScsiStatus((PSTORAGE_REQUEST_BLOCK_HEADER)srb, completionStatus); + + if (senseDataLength) { + + ULONG retryInterval; + + NT_ASSERT(senseDataLength <= sizeof(SENSE_DATA)); + + srb->Function = SRB_FUNCTION_EXECUTE_SCSI; + + srb->SrbStatus = SRB_STATUS_AUTOSENSE_VALID | SRB_STATUS_ERROR; + SrbSetSenseInfoBuffer((PSTORAGE_REQUEST_BLOCK_HEADER)srb, senseData); + SrbSetSenseInfoBufferLength((PSTORAGE_REQUEST_BLOCK_HEADER)srb, senseDataLength); + + ClassInterpretSenseInfo(fdo, + srb, + IRP_MJ_SCSI, + 0, + 0, + &status, + &retryInterval); + + TracePrint((TRACE_LEVEL_WARNING, + TRACE_FLAG_IOCTL, + "ClasspReceiveWriteUsingTokenInformationTransferPacketDone (%p): Reason for truncation/failure: %x - for list Id %x for data size %I64u bytes.\n", + fdo, + status, + listIdentifier, + transferBlockCount * fdoExt->DiskGeometry.BytesPerSector)); + + // + // If the token isn't valid any longer, we should let the upper layers know so that + // they don't waste time retrying the write with the same token. + // + if (status == STATUS_INVALID_TOKEN) { + + *tokenInvalidated = TRUE; + } + } else { + + TracePrint((TRACE_LEVEL_WARNING, + TRACE_FLAG_IOCTL, + "ClasspReceiveWriteUsingTokenInformationTransferPacketDone (%p): No sense data available but reason for truncation/failure, possibly: %x - for list Id %x for data size %I64u bytes.\n", + fdo, + completionStatus, + listIdentifier, + transferBlockCount * fdoExt->DiskGeometry.BytesPerSector)); + } + } + + // + // Initialize status. Upper layer needs to know if command failed because it + // timed out without doing any writing. ClasspCompleteOffloadWrite() will + // force status to success if any data was written, so it's this function's + // job to set the status appropriately based on the outcome of this + // WRITE USING TOKEN command, and then ClasspCompleteOffloadWrite() + // can override with success if previos WRITE USING TOKEN commands + // issued for the same upper request were able to write some data. + // + if (transferBlockCount != 0) { + status = STATUS_SUCCESS; + } else { + + if (NT_SUCCESS(status)) { + // + // Make sure status is a failing status, without throwing away an + // already-failing status obtained from sense data. + // + status = STATUS_UNSUCCESSFUL; + } + } + + NT_ASSERT(status != STATUS_PENDING); + + if (!NT_SUCCESS(status)) { + goto __ClasspReceiveWriteUsingTokenInformationTransferPacketDone_ErrorExit; + } + + ClasspReceiveWriteUsingTokenInformationDone(OffloadWriteContext, status); + status = STATUS_PENDING; + goto __ClasspReceiveWriteUsingTokenInformationTransferPacketDone_Exit; + + } else { + + status = STATUS_UNSUCCESSFUL; + + goto __ClasspReceiveWriteUsingTokenInformationTransferPacketDone_ErrorExit; + } + + // + // Error label only - not used in success case: + // + +__ClasspReceiveWriteUsingTokenInformationTransferPacketDone_ErrorExit: + + NT_ASSERT(!NT_SUCCESS(status)); + + ClasspCompleteOffloadWrite(OffloadWriteContext, status); + +__ClasspReceiveWriteUsingTokenInformationTransferPacketDone_Exit: + + // + // Due to tracing a potentially freed pointer value "Irp", this trace could + // be delayed beyond another offload op picking up the same pointer value + // for its Irp. + // + + TracePrint((TRACE_LEVEL_VERBOSE, + TRACE_FLAG_IOCTL, + "ClasspReceiveWriteUsingTokenInformationTransferPacketDone (%p): Exiting function (Irp %p) with status %x.\n", + fdo, + irp, + status)); + + return; +} + + +NTSTATUS +ClasspRefreshFunctionSupportInfo( + _Inout_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension, + _In_ BOOLEAN ForceQuery + ) +/* +Routine Description: + + This function is to update various properties described in FDO extension's + CLASS_FUNCTION_SUPPORT_INFO structure by requerying the device's VPD pages. + Although this function is capable of updating any properties in the + CLASS_FUNCTION_SUPPORT_INFO structure, it will initially support only + a small number of proporties in the block limit data + +Arguments: + + FdoExtension : FDO extension + + ForceQuery : TRUE if the caller wants to force a query of the device's + VPD pages. Otherwise, the function may use cached data. + +Return Value: + + STATUS_SUCCESS or an error status + +--*/ +{ + NTSTATUS status; + PSCSI_REQUEST_BLOCK srb = NULL; + ULONG srbSize; + CLASS_VPD_B0_DATA blockLimitsDataNew; + PCLASS_VPD_B0_DATA blockLimitsDataOriginal; + KLOCK_QUEUE_HANDLE lockHandle; + ULONG generationCount; + ULONG changeRequestCount; + + // + // ChangeRequestCount is incremented every time we get an unit attention with + // SCSI_ADSENSE_OPERATING_CONDITIONS_CHANGED. GenerationCount will be set to + // ChangeRequestCount after CLASS_FUNCTION_SUPPORT_INFO is refreshed with the latest + // VPD data. i.e. if both values are the same, data in + // CLASS_FUNCTION_SUPPORT_INFO is current + // + + generationCount = FdoExtension->FunctionSupportInfo->GenerationCount; + changeRequestCount = FdoExtension->FunctionSupportInfo->ChangeRequestCount; + if (!ForceQuery && generationCount == changeRequestCount) { + return STATUS_SUCCESS; + } + + // + // Allocate an SRB for querying the device for LBP-related info if either + // the Logical Block Provisioning (0xB2) or Block Limits (0xB0) VPD page + // exists. + // + if ((FdoExtension->AdapterDescriptor != NULL) && + (FdoExtension->AdapterDescriptor->SrbType == SRB_TYPE_STORAGE_REQUEST_BLOCK)) { + srbSize = CLASS_SRBEX_SCSI_CDB16_BUFFER_SIZE; + } else { + srbSize = sizeof(SCSI_REQUEST_BLOCK); + } + + srb = ExAllocatePoolWithTag(NonPagedPoolNx, srbSize, '1DcS'); + if (srb == NULL) { + return STATUS_INSUFFICIENT_RESOURCES; + } + + status = ClasspDeviceGetBlockLimitsVPDPage(FdoExtension, + srb, + srbSize, + &blockLimitsDataNew); + + if (NT_SUCCESS(status)) { + + KeAcquireInStackQueuedSpinLock(&FdoExtension->FunctionSupportInfo->SyncLock, &lockHandle); + + // + // If the generationCount didn't change since we looked at it last time, it means + // no one has tried to update the CLASS_FUNCTION_SUPPORT_INFO data; otherwise, someone + // else has beat us to it. + // + if (generationCount == FdoExtension->FunctionSupportInfo->GenerationCount) { + + blockLimitsDataOriginal = &FdoExtension->FunctionSupportInfo->BlockLimitsData; + if (blockLimitsDataOriginal->CommandStatus == -1) { + // + // CommandStatus == -1 means this is the first time we have + // gotten the block limits data. + // + *blockLimitsDataOriginal = blockLimitsDataNew; + } else { + // + // We only expect the Optimal Unmap Granularity (and alignment) + // to change, so those are the only parameters we update. + // + blockLimitsDataOriginal->UGAVALID = blockLimitsDataNew.UGAVALID; + blockLimitsDataOriginal->UnmapGranularityAlignment = blockLimitsDataNew.UnmapGranularityAlignment; + blockLimitsDataOriginal->OptimalUnmapGranularity = blockLimitsDataNew.OptimalUnmapGranularity; + } + FdoExtension->FunctionSupportInfo->GenerationCount = changeRequestCount; + } + + KeReleaseInStackQueuedSpinLock(&lockHandle); + } + + FREE_POOL(srb); + return status; +} + +NTSTATUS +ClasspBlockLimitsDataSnapshot( + _In_ PFUNCTIONAL_DEVICE_EXTENSION FdoExtension, + _In_ BOOLEAN ForceQuery, + _Out_ PCLASS_VPD_B0_DATA BlockLimitsData, + _Out_ PULONG GenerationCount + ) +/* +Routine Description: + + This function is to get a copy of the latest block limits data. + + When this function is called multiple times, GenerationCount can change (value always goes up) + while BlockLimitsData stays the same. In this case, the caller should assume BlockLimitsData + has changed to different values and eventually changed back to the same state when the first + call to this function was made. + +Arguments: + + FdoExtension : FDO extension + + ForceQuery : TRUE if the caller wants to force a query of the device's + VPD pages. Otherwise, the function may use cached data. + + BlockLimitsData : pointer to memory that will receive the block limits data + + GenerationCount : generation count of the block limit data. + + DataIsOutdated: set to TRUE if the BlockLimitsData is old but this function fails to + query the latest data from the device due to insufficient resources + +Return Value: + + STATUS_SUCCESS or an error status + +--*/ +{ + NTSTATUS status; + KLOCK_QUEUE_HANDLE lockHandle; + + status = ClasspRefreshFunctionSupportInfo(FdoExtension, ForceQuery); + + KeAcquireInStackQueuedSpinLock(&FdoExtension->FunctionSupportInfo->SyncLock, &lockHandle); + *BlockLimitsData = FdoExtension->FunctionSupportInfo->BlockLimitsData; + *GenerationCount = FdoExtension->FunctionSupportInfo->GenerationCount; + KeReleaseInStackQueuedSpinLock(&lockHandle); + + return status; +} + 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> </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> </o:p></pre><pre><o:p> </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> </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> </o:p></p> + </td> + </tr> +</table> + +<pre><o:p> </o:p></pre><pre><o:p> </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 "%(CustomBuildTarget0Input.Identity)" copy ".\%(CustomBuildTarget0Input.Filename)%(CustomBuildTarget0Input.Extension)" "%(CustomBuildTarget0Input.Identity)"" WorkingDirectory="$(MSBuildProjectDirectory)" /> + <Exec Command="mofcomp -Amendment:ms_409 -MFL:$(IntDir)\MFL.MFL -MOF:$(IntDir)\MOF.MOF "%(CustomBuildTarget0Input.Identity)"" WorkingDirectory="$(MSBuildProjectDirectory)" /> + <Exec Command="wmimofck -y$(IntDir)\MOF.MOF -z$(IntDir)\MFL.MFL $(IntDir)\MOFMFL.MOF" WorkingDirectory="$(MSBuildProjectDirectory)" /> + <Exec Command="mofcomp -B:".\$(IntDir)\classlog.bmf" $(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, ×AlreadyRetried) == 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)§orCount); + + 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(¶meterCode, &(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, §orCount); + + 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; +} + + |
