mirror of
https://github.com/reactos/reactos.git
synced 2024-10-30 11:35:58 +00:00
15a7b9dd2f
CORE-17129
4437 lines
140 KiB
C
4437 lines
140 KiB
C
/*++
|
||
|
||
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.
|
||
//
|
||
static
|
||
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
|
||
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
|
||
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) {
|
||
#ifdef _MSC_VER
|
||
#pragma prefast(suppress:26015, "InitializeStorageRequestBlock ensures buffer access is bounded")
|
||
#endif
|
||
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;
|
||
PIO_WORKITEM workItem;
|
||
|
||
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;
|
||
}
|
||
|
||
//
|
||
// Inform PartMgr that the media changed. It will need to propagate
|
||
// DO_VERIFY_VOLUME to each partition. Ensure that only one work item
|
||
// updates the disk's properties at any given time.
|
||
//
|
||
if (InterlockedCompareExchange((volatile LONG *)&FdoExtension->PrivateFdoData->UpdateDiskPropertiesWorkItemActive, 1, 0) == 0) {
|
||
|
||
workItem = IoAllocateWorkItem(FdoExtension->DeviceObject);
|
||
|
||
if (workItem) {
|
||
|
||
IoQueueWorkItem(workItem, ClasspUpdateDiskProperties, DelayedWorkQueue, workItem);
|
||
|
||
} else {
|
||
|
||
InterlockedExchange((volatile LONG *)&FdoExtension->PrivateFdoData->UpdateDiskPropertiesWorkItemActive, 0);
|
||
}
|
||
}
|
||
|
||
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
|
||
#ifdef _MSC_VER
|
||
#pragma prefast(suppress:26165, "The mutex won't be acquired in the case of a timeout.")
|
||
#endif
|
||
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
|
||
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
|
||
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
|
||
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
|
||
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
|
||
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
|
||
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
|
||
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
|
||
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
|
||
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
|
||
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
|
||
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
|
||
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
|
||
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
|
||
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 | RTL_QUERY_REGISTRY_TYPECHECK;
|
||
queryTable[0].DefaultType = (REG_DWORD << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_NONE;
|
||
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);
|
||
|
||
#ifdef _MSC_VER
|
||
#pragma warning(suppress:6386) // Not an issue as deviceString.Buffer is of size deviceString.MaximumLength, which is equal to (deviceString.Length + 1)
|
||
#endif
|
||
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 << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | 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
|
||
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
|
||
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
|
||
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
|
||
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
|
||
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
|
||
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
|
||
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
|
||
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
|
||
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
|
||
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
|
||
//
|
||
#ifdef _MSC_VER
|
||
#pragma warning(suppress:4054) // okay to type cast function pointer to PIRP for this use case
|
||
#endif
|
||
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
|
||
|
||
#ifdef _MSC_VER
|
||
#pragma warning(suppress:4054) // okay to type cast function pointer to PIRP for this use case
|
||
#endif
|
||
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
|
||
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
|
||
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((volatile PVOID *)&(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
|
||
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
|
||
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))) {
|
||
|
||
#ifdef _MSC_VER
|
||
#pragma warning(suppress:4054) // okay to type cast function pointer as data pointer for this use case
|
||
#endif
|
||
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
|
||
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
|
||
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;
|
||
}
|
||
|
||
|