reactos/drivers/storage/class/cdrom/autorun.c

3081 lines
102 KiB
C

/*++
Copyright (C) Microsoft Corporation. All rights reserved.
Module Name:
autorun.c
Abstract:
Code for support of media change detection in the cd/dvd driver
Environment:
kernel mode only
Notes:
Revision History:
--*/
#include "stddef.h"
#include "string.h"
#include "ntddk.h"
#include "ntddstor.h"
#include "cdrom.h"
#include "mmc.h"
#include "ioctl.h"
#include "ntstrsafe.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")
//
// Only send polling irp when device is fully powered up and a
// power down irp is not in progress.
//
// 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.
//
_IRQL_requires_max_(PASSIVE_LEVEL)
BOOLEAN
DeviceIsMediaChangeDisabledDueToHardwareLimitation(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension
);
_IRQL_requires_max_(PASSIVE_LEVEL)
BOOLEAN
DeviceIsMediaChangeDisabledForClass(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension
);
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS
DeviceMediaChangeDeviceInstanceOverride(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_Out_ PBOOLEAN Enabled
);
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS
DeviceInitializeMcn(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_In_ BOOLEAN AllowDriveToSleep
);
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS
DeviceInitializeGesn(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension
);
_IRQL_requires_max_(APC_LEVEL)
NTSTATUS
GesnDataInterpret(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_In_ PNOTIFICATION_EVENT_STATUS_HEADER Header,
_Out_ PBOOLEAN ResendImmediately
);
RTL_QUERY_REGISTRY_ROUTINE DeviceMediaChangeRegistryCallBack;
EVT_WDF_WORKITEM DeviceDisableGesn;
#if ALLOC_PRAGMA
#pragma alloc_text(PAGE, DeviceInitializeMediaChangeDetection)
#pragma alloc_text(PAGE, DeviceEnableMediaChangeDetection)
#pragma alloc_text(PAGE, DeviceDisableMediaChangeDetection)
#pragma alloc_text(PAGE, DeviceSendDelayedMediaChangeNotifications)
#pragma alloc_text(PAGE, DeviceReleaseMcnResources)
#pragma alloc_text(PAGE, DeviceMediaChangeRegistryCallBack)
#pragma alloc_text(PAGE, DeviceInitializeMcn)
#pragma alloc_text(PAGE, DeviceDisableGesn)
#pragma alloc_text(PAGE, DeviceIsMediaChangeDisabledDueToHardwareLimitation)
#pragma alloc_text(PAGE, DeviceMediaChangeDeviceInstanceOverride)
#pragma alloc_text(PAGE, DeviceIsMediaChangeDisabledForClass)
#pragma alloc_text(PAGE, DeviceDisableMainTimer)
#pragma alloc_text(PAGE, GesnDataInterpret)
#pragma alloc_text(PAGE, RequestSetupMcnRequest)
#pragma alloc_text(PAGE, RequestPostWorkMcnRequest)
#pragma alloc_text(PAGE, RequestSendMcnRequest)
//
// DeviceEnableMainTimer is called by EvtDeviceD0Entry which can't be made pageable
// so neither is DeviceEnableMainTimer
//
//#pragma alloc_text(PAGE, DeviceEnableMainTimer)
#pragma alloc_text(PAGE, DeviceInitializeGesn)
#pragma alloc_text(PAGE, RequestHandleMcnControl)
#endif
#pragma warning(push)
#pragma warning(disable:4152) // nonstandard extension, function/data pointer conversion in expression
_IRQL_requires_max_(APC_LEVEL)
NTSTATUS
GesnDataInterpret(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_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:
DeviceExtension - the device extension
Header - 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.
--*/
{
NTSTATUS status = STATUS_SUCCESS;
PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo;
LONG dataLength = 0;
LONG requiredLength = 0;
BOOLEAN inHomePosition = FALSE;
PAGED_CODE();
// 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,
"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,
"error - GESN returned only %x bytes data for fdo %p\n",
dataLength, DeviceExtension->DeviceObject));
return STATUS_DEVICE_PROTOCOL_ERROR;
}
if (dataLength > requiredLength)
{
TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN,
"error - GESN returned too many (%x) bytes data for fdo %p\n",
dataLength, DeviceExtension->DeviceObject));
}
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;
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,
"GESN OpChange events are broken. Working around this problem in software (for WDFDEVICE %p)\n",
DeviceExtension->Device));
// 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,
"GESN says features added/changed for WDFDEVICE %p\n",
DeviceExtension->Device));
// don't notify that new media arrived, just set the
// DO_VERIFY to force a FS reload.
if (IsVolumeMounted(DeviceExtension->DeviceObject))
{
SET_FLAG(DeviceExtension->DeviceObject->Flags, DO_VERIFY_VOLUME);
}
// Call error handler with
// a "fake" media change error in case it needs to update
// internal structures as though a media change occurred.
{
SCSI_REQUEST_BLOCK srb = {0};
SENSE_DATA sense = {0};
NTSTATUS tempStatus;
BOOLEAN retry;
tempStatus = STATUS_MEDIA_CHANGED;
retry = 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);
sense.AdditionalSenseLength = sizeof(SENSE_DATA) -
RTL_SIZEOF_THROUGH_FIELD(SENSE_DATA, AdditionalSenseLength);
sense.SenseKey = SCSI_SENSE_UNIT_ATTENTION;
sense.AdditionalSenseCode = SCSI_ADSENSE_MEDIUM_CHANGED;
if (DeviceExtension->DeviceAdditionalData.ErrorHandler)
{
DeviceExtension->DeviceAdditionalData.ErrorHandler(DeviceExtension,
&srb,
&tempStatus,
&retry);
}
} // end 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,
"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();
DeviceSendNotification(DeviceExtension,
&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);
if ((mediaInfo->MediaEvent == NOTIFICATION_MEDIA_EVENT_NEW_MEDIA) ||
(mediaInfo->MediaEvent == NOTIFICATION_MEDIA_EVENT_MEDIA_CHANGE))
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"GESN::MEDIA ARRIVAL, Status %x\n",
mediaInfo->MediaStatus));
if (IsVolumeMounted(DeviceExtension->DeviceObject))
{
SET_FLAG(DeviceExtension->DeviceObject->Flags, DO_VERIFY_VOLUME);
}
DeviceSetMediaChangeStateEx(DeviceExtension,
MediaPresent,
NULL);
// If media is inserted into slot loading type, mark the device active
// to not power off.
if ((DeviceExtension->ZeroPowerODDInfo != NULL) &&
(DeviceExtension->ZeroPowerODDInfo->LoadingMechanism == LOADING_MECHANISM_CADDY) &&
(DeviceExtension->ZeroPowerODDInfo->Load == 0)) // Slot
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER,
"GesnDataInterpret: MediaArrival event detected, device marked as active\n"));
DeviceMarkActive(DeviceExtension, TRUE, FALSE);
}
}
else if (mediaInfo->MediaEvent == NOTIFICATION_MEDIA_EVENT_MEDIA_REMOVAL)
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"GESN::MEDIA REMOVAL, Status %x\n",
mediaInfo->MediaStatus));
DeviceSetMediaChangeStateEx(DeviceExtension,
MediaNotPresent,
NULL);
// If media is removed from slot loading type, start powering off the device
// if it is ZPODD capable.
if ((DeviceExtension->ZeroPowerODDInfo != NULL) &&
(DeviceExtension->ZeroPowerODDInfo->LoadingMechanism == LOADING_MECHANISM_CADDY) &&
(DeviceExtension->ZeroPowerODDInfo->Load == 0)) // Slot
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER,
"GesnDataInterpret: MediaRemoval event detected, device marked as idle\n"));
DeviceMarkActive(DeviceExtension, FALSE, FALSE);
}
}
else if (mediaInfo->MediaEvent == NOTIFICATION_MEDIA_EVENT_EJECT_REQUEST)
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"GESN::MEDIA EJECTION, Status %x\n",
mediaInfo->MediaStatus));
DeviceSendNotification(DeviceExtension,
&GUID_IO_MEDIA_EJECT_REQUEST,
0,
NULL);
}
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};
// 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,
"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)
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"GesnDataInterpret: media BECOMING_READY\n"));
DeviceSendNotification(DeviceExtension,
&GUID_IO_DEVICE_BECOMING_READY,
sizeof(DEVICE_EVENT_BECOMING_READY),
&busyData);
}
// If manual loading operation is observed for slot loading type, start powering off the device
// if it is ZPODD capable.
if ((DeviceExtension->ZeroPowerODDInfo != NULL) &&
(DeviceExtension->ZeroPowerODDInfo->LoadingMechanism == LOADING_MECHANISM_TRAY) &&
(DeviceExtension->ZeroPowerODDInfo->Load == 0) && // Drawer
(busyInfo->DeviceBusyEvent == NOTIFICATION_BUSY_EVENT_LO_CHANGE) &&
(busyInfo->DeviceBusyStatus == NOTIFICATION_BUSY_STATUS_NO_EVENT))
{
inHomePosition = DeviceZPODDIsInHomePosition(DeviceExtension);
if (inHomePosition == FALSE)
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER,
"GesnDataInterpret: LoChange event detected, device marked as active\n"));
DeviceMarkActive(DeviceExtension, TRUE, FALSE);
}
else
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER,
"GesnDataInterpret: LoChange event detected, device marked as idle\n"));
DeviceMarkActive(DeviceExtension, FALSE, FALSE);
}
}
break;
}
default:
{
break;
}
} // end switch on notification class
return status;
}
VOID
DeviceInternalSetMediaChangeState(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_In_ MEDIA_CHANGE_DETECTION_STATE NewState,
_Inout_opt_ PMEDIA_CHANGE_DETECTION_STATE OldState
)
/*++
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:
DeviceExtension - the device extension
NewState - new state for setting
OldState - optional storage for the old state
Return Value:
none
--*/
{
#if DBG
LPCSTR states[] = {"Unknown", "Present", "Not Present", "Unavailable"};
#endif
MEDIA_CHANGE_DETECTION_STATE oldMediaState;
PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo;
CLASS_MEDIA_CHANGE_CONTEXT mcnContext;
if (!((NewState >= MediaUnknown) && (NewState <= MediaUnavailable)))
{
return;
}
if (info == NULL)
{
return;
}
oldMediaState = info->LastKnownMediaDetectionState;
if (OldState)
{
*OldState = oldMediaState;
}
info->LastKnownMediaDetectionState = NewState;
// Increment MediaChangeCount on transition to MediaPresent
if (NewState == MediaPresent && oldMediaState != NewState)
{
InterlockedIncrement((PLONG)&DeviceExtension->MediaChangeCount);
}
if (info->MediaChangeDetectionDisableCount != 0)
{
#if DBG
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"DeviceInternalSetMediaChangeState: 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,
"DeviceInternalSetMediaChangeState: State change from %s to %s\n",
states[oldMediaState], states[NewState]));
#endif
if (info->LastReportedMediaDetectionState == info->LastKnownMediaDetectionState)
{
// Media is in the same state as we reported last time, no need to report again.
return;
}
// make the data useful -- it used to always be zero.
mcnContext.MediaChangeCount = DeviceExtension->MediaChangeCount;
mcnContext.NewState = NewState;
if (NewState == MediaPresent)
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"DeviceInternalSetMediaChangeState: Reporting media ARRIVAL\n"));
DeviceSendNotification(DeviceExtension,
&GUID_IO_MEDIA_ARRIVAL,
sizeof(CLASS_MEDIA_CHANGE_CONTEXT),
&mcnContext);
}
else if ((NewState == MediaNotPresent) || (NewState == MediaUnavailable))
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"DeviceInternalSetMediaChangeState: Reporting media REMOVAL\n"));
DeviceSendNotification(DeviceExtension,
&GUID_IO_MEDIA_REMOVAL,
sizeof(CLASS_MEDIA_CHANGE_CONTEXT),
&mcnContext);
}
else
{
// Don't notify of changed going to unknown.
return;
}
info->LastReportedMediaDetectionState = info->LastKnownMediaDetectionState;
return;
} // end DeviceInternalSetMediaChangeState()
VOID
DeviceSetMediaChangeStateEx(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_In_ MEDIA_CHANGE_DETECTION_STATE NewState,
_Inout_opt_ PMEDIA_CHANGE_DETECTION_STATE OldState
)
/*++
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.
Arguments:
DeviceExtension - the device extension
NewState - new state for setting
OldState - optional storage for the old state
Return Value:
none
--*/
{
PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo;
LARGE_INTEGER zero;
NTSTATUS status;
// timeout value must be 0, as this function can be called at DISPATCH_LEVEL.
zero.QuadPart = 0;
if (info == NULL)
{
return;
}
status = KeWaitForMutexObject(&info->MediaChangeMutex,
Executive,
KernelMode,
FALSE,
&zero);
if (status == STATUS_TIMEOUT)
{
// Someone else is in the process of setting the media state.
return;
}
// Change the media present state and signal an event, if applicable
DeviceInternalSetMediaChangeState(DeviceExtension, NewState, OldState);
KeReleaseMutex(&info->MediaChangeMutex, FALSE);
return;
} // end DeviceSetMediaChangeStateEx()
_IRQL_requires_max_(APC_LEVEL)
VOID
DeviceSendDelayedMediaChangeNotifications(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension
)
/*++
Routine Description:
This routine sends the so-called delayed media change notifications.
These notifications get accumulated while the MCN mechanism is disabled
and need to be sent to the application on MCN enabling, if MCN enabling
happens as a part of exclusive access unlock and the application has not
requested us explicitly to not send the delayed notifications.
Arguments:
DeviceExtension - the device extension
Return Value:
none
--*/
{
PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo;
LARGE_INTEGER zero;
NTSTATUS status;
PAGED_CODE();
zero.QuadPart = 0;
if (info == NULL)
{
return;
}
status = KeWaitForMutexObject(&info->MediaChangeMutex,
Executive,
KernelMode,
FALSE,
&zero);
if (status == STATUS_TIMEOUT)
{
// Someone else is in the process of setting the media state.
// That's totally okay, we'll send delayed notifications later.
return;
}
// If the last reported state and the last known state are different and
// MCN is enabled, generate a notification based on the last known state.
if ((info->LastKnownMediaDetectionState != info->LastReportedMediaDetectionState) &&
(info->MediaChangeDetectionDisableCount == 0))
{
DeviceInternalSetMediaChangeState(DeviceExtension, info->LastKnownMediaDetectionState, NULL);
}
KeReleaseMutex(&info->MediaChangeMutex, FALSE);
return;
}
_IRQL_requires_max_(APC_LEVEL)
NTSTATUS
RequestSetupMcnRequest(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_In_ BOOLEAN UseGesn
)
/*++
Routine Description:
This routine sets up the fields of the request for MCN
Arguments:
DeviceExtension - device context
UseGesn - If TRUE, the device supports GESN and it's currently the mechanism for MCN
Return Value:
NTSTATUS
--*/
{
NTSTATUS status = STATUS_SUCCESS;
PSCSI_REQUEST_BLOCK srb;
PIRP irp;
PIO_STACK_LOCATION nextIrpStack;
PCDB cdb;
PVOID buffer;
WDF_REQUEST_REUSE_PARAMS params;
PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo;
PAGED_CODE();
irp = WdfRequestWdmGetIrp(info->MediaChangeRequest);
NT_ASSERT(irp != NULL);
// deassign the MdlAddress, this is the value we assign explicitly.
// this is to prevent WdfRequestReuse to release the Mdl unexpectly.
if (irp->MdlAddress)
{
irp->MdlAddress = NULL;
}
if (NT_SUCCESS(status))
{
// Setup the IRP to perform a test unit ready.
WDF_REQUEST_REUSE_PARAMS_INIT(&params,
WDF_REQUEST_REUSE_NO_FLAGS,
STATUS_NOT_SUPPORTED);
status = WdfRequestReuse(info->MediaChangeRequest, &params);
}
if (NT_SUCCESS(status))
{
// Format the request.
status = WdfIoTargetFormatRequestForInternalIoctlOthers(DeviceExtension->IoTarget,
info->MediaChangeRequest,
IOCTL_SCSI_EXECUTE_IN,
NULL, NULL,
NULL, NULL,
NULL, NULL);
if (!NT_SUCCESS(status))
{
TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
"RequestSetupMcnRequest: WdfIoTargetFormatRequestForInternalIoctlOthers failed, %!STATUS!\n",
status));
}
}
if (NT_SUCCESS(status))
{
RequestClearSendTime(info->MediaChangeRequest);
nextIrpStack = IoGetNextIrpStackLocation(irp);
nextIrpStack->Flags = SL_OVERRIDE_VERIFY_VOLUME;
nextIrpStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
nextIrpStack->Parameters.Scsi.Srb = &(info->MediaChangeSrb);
// Prepare the SRB for execution.
srb = nextIrpStack->Parameters.Scsi.Srb;
buffer = info->SenseBuffer;
RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK));
RtlZeroMemory(buffer, SENSE_BUFFER_SIZE);
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->SrbStatus = 0;
srb->ScsiStatus = 0;
srb->OriginalRequest = irp;
srb->SenseInfoBufferLength = SENSE_BUFFER_SIZE;
srb->SrbFlags = DeviceExtension->SrbFlags;
SET_FLAG(srb->SrbFlags, info->SrbFlags);
if (!UseGesn)
{
srb->TimeOutValue = CDROM_TEST_UNIT_READY_TIMEOUT;
srb->CdbLength = 6;
srb->DataTransferLength = 0;
SET_FLAG(srb->SrbFlags, SRB_FLAGS_NO_DATA_TRANSFER);
nextIrpStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_SCSI_EXECUTE_NONE;
srb->DataBuffer = NULL;
srb->DataTransferLength = 0;
irp->MdlAddress = NULL;
cdb = (PCDB) &srb->Cdb[0];
cdb->CDB6GENERIC.OperationCode = SCSIOP_TEST_UNIT_READY;
}
else
{
NT_ASSERT(info->Gesn.Buffer);
srb->TimeOutValue = GESN_TIMEOUT_VALUE; // much shorter timeout for GESN
srb->CdbLength = 10;
SET_FLAG(srb->SrbFlags, SRB_FLAGS_DATA_IN);
nextIrpStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_SCSI_EXECUTE_IN;
srb->DataBuffer = info->Gesn.Buffer;
srb->DataTransferLength = info->Gesn.BufferSize;
irp->MdlAddress = info->Gesn.Mdl;
cdb = (PCDB) &srb->Cdb[0];
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;
}
}
return status;
}
VOID
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
DeviceDisableGesn(
_In_ WDFWORKITEM WorkItem
)
/*++
Routine Description:
Work item routine to set the hack flag in the registry to disable GESN
This routine is invoked when the device reports TOO many events that affects system
Arguments:
WorkItem - the work item be perfromed.
Return Value:
None
--*/
{
WDFDEVICE device = WdfWorkItemGetParentObject(WorkItem);
PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(device);
PAGED_CODE();
//
// Set the hack flag in the registry
//
DeviceSetParameter(deviceExtension,
CLASSP_REG_SUBKEY_NAME,
CLASSP_REG_MMC_DETECTION_VALUE_NAME,
CdromDetectionUnsupported);
WdfObjectDelete(WorkItem);
return;
}
_IRQL_requires_max_(APC_LEVEL)
BOOLEAN
RequestPostWorkMcnRequest(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension
)
/*++
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:
DeviceExtension - the device context
Return Value:
BOOLEAN - TRUE (needs retry); FALSE (shoule not retry)
--*/
{
NTSTATUS status = STATUS_SUCCESS;
PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo;
PIRP irp;
BOOLEAN retryImmediately = FALSE;
PAGED_CODE();
NT_ASSERT(info->MediaChangeRequest != NULL);
irp = WdfRequestWdmGetIrp(info->MediaChangeRequest);
NT_ASSERT(!TEST_FLAG(info->MediaChangeSrb.SrbStatus, SRB_STATUS_QUEUE_FROZEN));
// use InterpretSenseInfo routine to check for media state, and also
// to call ClassError() with correct parameters.
if (SRB_STATUS(info->MediaChangeSrb.SrbStatus) != SRB_STATUS_SUCCESS)
{
TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN, "MCN request - failed - srb status=%x, sense=%x/%x/%x.\n",
info->MediaChangeSrb.SrbStatus,
((PSENSE_DATA)(info->MediaChangeSrb.SenseInfoBuffer))->SenseKey,
((PSENSE_DATA)(info->MediaChangeSrb.SenseInfoBuffer))->AdditionalSenseCode,
((PSENSE_DATA)(info->MediaChangeSrb.SenseInfoBuffer))->AdditionalSenseCodeQualifier));
if (SRB_STATUS(info->MediaChangeSrb.SrbStatus) != SRB_STATUS_NOT_POWERED)
{
// Release the queue if it is frozen.
if (info->MediaChangeSrb.SrbStatus & SRB_STATUS_QUEUE_FROZEN)
{
DeviceReleaseQueue(DeviceExtension->Device);
}
RequestSenseInfoInterpret(DeviceExtension,
info->MediaChangeRequest,
&info->MediaChangeSrb,
0,
&status,
NULL);
}
}
else
{
DeviceExtension->PrivateFdoData->LoggedTURFailureSinceLastIO = FALSE;
if (!info->Gesn.Supported)
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"MCN request - succeeded (GESN NOT supported, setting MediaPresent).\n"));
// success != media for GESN case
DeviceSetMediaChangeStateEx(DeviceExtension,
MediaPresent,
NULL);
}
else
{
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN,
"MCN request - succeeded (GESN supported).\n"));
}
}
if (info->Gesn.Supported)
{
if (status == STATUS_DATA_OVERRUN)
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "MCN Request - Data Overrun\n"));
status = STATUS_SUCCESS;
}
if (!NT_SUCCESS(status))
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "MCN Request: GESN failed with status %x\n", status));
}
else
{
// for GESN, need to interpret the results of the data.
// this may also require an immediate retry
if (irp->IoStatus.Information == 8 )
{
GesnDataInterpret(DeviceExtension,
(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(DeviceExtension, &info->MediaChangeSrb))
{
FREE_PORT_ALLOCATED_SENSE_BUFFER(DeviceExtension, &info->MediaChangeSrb);
}
// Remember the IRP and SRB for use the next time.
NT_ASSERT(IoGetNextIrpStackLocation(irp));
IoGetNextIrpStackLocation(irp)->Parameters.Scsi.Srb = &info->MediaChangeSrb;
// 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
WDF_OBJECT_ATTRIBUTES attributes;
WDF_WORKITEM_CONFIG workitemConfig;
WDFWORKITEM workItem;
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
attributes.ParentObject = DeviceExtension->Device;
WDF_WORKITEM_CONFIG_INIT(&workitemConfig, DeviceDisableGesn);
workitemConfig.AutomaticSerialization = FALSE;
status = WdfWorkItemCreate(&workitemConfig,
&attributes,
&workItem);
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "MCN Request: Disabling GESN for WDFDEVICE %p\n", DeviceExtension->Device));
if (NT_SUCCESS(status))
{
WdfWorkItemEnqueue(workItem);
}
info->Gesn.Supported = FALSE;
info->Gesn.EventMask = 0;
info->Gesn.BufferSize = 0;
info->MediaChangeRetryCount = 0;
retryImmediately = FALSE;
// should we log an error in event log?
}
}
else
{
info->MediaChangeRetryCount = 0;
}
return retryImmediately;
}
_IRQL_requires_max_(APC_LEVEL)
BOOLEAN
RequestSendMcnRequest(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension
)
/*++
Routine Description:
This routine sends the formatted MCN request sychronizely to lower driver.
Arguments:
DeviceExtension - the device context
Return Value:
BOOLEAN - TRUE (requst successfully sent); FALSE (request failed to send)
--*/
{
BOOLEAN requestSent = FALSE;
PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo;
PAGED_CODE();
RequestSend(DeviceExtension,
info->MediaChangeRequest,
DeviceExtension->IoTarget,
WDF_REQUEST_SEND_OPTION_SYNCHRONOUS,
&requestSent);
return requestSent;
} // end RequestSendMcnRequest()
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS
DeviceInitializeMcn(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_In_ BOOLEAN AllowDriveToSleep
)
/*++
Routine Description:
This routine initialize the contents of MCN structure.
Arguments:
DeviceExtension - the device extension
AllowDriveToSleep - for CDROM, this parameter should be always FALSE
Return Value:
NTSTATUS
--*/
{
NTSTATUS status = STATUS_SUCCESS;
PMEDIA_CHANGE_DETECTION_INFO mediaChangeInfo = NULL;
PIRP irp = NULL;
PVOID senseBuffer = NULL;
WDF_OBJECT_ATTRIBUTES attributes;
PAGED_CODE();
if (DeviceExtension->MediaChangeDetectionInfo != NULL)
{
//Already initialized.
return STATUS_SUCCESS;
}
DeviceExtension->KernelModeMcnContext.FileObject = (PVOID)-1;
DeviceExtension->KernelModeMcnContext.DeviceObject = (PVOID)-1;
DeviceExtension->KernelModeMcnContext.LockCount = 0;
DeviceExtension->KernelModeMcnContext.McnDisableCount = 0;
mediaChangeInfo = ExAllocatePoolWithTag(NonPagedPoolNx,
sizeof(MEDIA_CHANGE_DETECTION_INFO),
CDROM_TAG_MEDIA_CHANGE_DETECTION);
if (mediaChangeInfo == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
}
else
{
RtlZeroMemory(mediaChangeInfo, sizeof(MEDIA_CHANGE_DETECTION_INFO));
}
if (NT_SUCCESS(status))
{
if ((DeviceExtension->PowerDescriptor != NULL) &&
(DeviceExtension->PowerDescriptor->AsynchronousNotificationSupported != FALSE) &&
(!TEST_FLAG(DeviceExtension->PrivateFdoData->HackFlags, FDO_HACK_NO_ASYNCHRONOUS_NOTIFICATION)))
{
mediaChangeInfo->AsynchronousNotificationSupported = TRUE;
}
}
// Allocate an IRP to carry the IOCTL_MCN_SYNC_FAKE_IOCTL.
if (NT_SUCCESS(status))
{
irp = IoAllocateIrp(DeviceExtension->DeviceObject->StackSize, FALSE);
if (irp == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
}
}
if (NT_SUCCESS(status))
{
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes,
CDROM_REQUEST_CONTEXT);
attributes.ParentObject = DeviceExtension->Device;
status = WdfRequestCreate(&attributes,
DeviceExtension->IoTarget,
&mediaChangeInfo->MediaChangeRequest);
}
if (NT_SUCCESS(status))
{
// Preformat the media change request. With this being done, we never need to worry about
// WdfIoTargetFormatRequestForInternalIoctlOthers ever failing later.
status = WdfIoTargetFormatRequestForInternalIoctlOthers(DeviceExtension->IoTarget,
mediaChangeInfo->MediaChangeRequest,
IOCTL_SCSI_EXECUTE_IN,
NULL, NULL,
NULL, NULL,
NULL, NULL);
}
if (NT_SUCCESS(status))
{
senseBuffer = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned,
SENSE_BUFFER_SIZE,
CDROM_TAG_MEDIA_CHANGE_DETECTION);
if (senseBuffer == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
}
}
if (NT_SUCCESS(status))
{
mediaChangeInfo->MediaChangeSyncIrp = irp;
mediaChangeInfo->SenseBuffer = senseBuffer;
// Set default values for the media change notification
// configuration.
mediaChangeInfo->MediaChangeDetectionDisableCount = 0;
// Assume that there is initially no media in the device
// only notify upper layers if there is something there
mediaChangeInfo->LastKnownMediaDetectionState = MediaUnknown;
mediaChangeInfo->LastReportedMediaDetectionState = MediaUnknown;
// setup all extra flags we'll be setting for this irp
mediaChangeInfo->SrbFlags = 0;
SET_FLAG(mediaChangeInfo->SrbFlags, SRB_CLASS_FLAGS_LOW_PRIORITY);
SET_FLAG(mediaChangeInfo->SrbFlags, SRB_FLAGS_NO_QUEUE_FREEZE);
SET_FLAG(mediaChangeInfo->SrbFlags, SRB_FLAGS_DISABLE_SYNCH_TRANSFER);
if (AllowDriveToSleep) //FALSE for CD/DVD devices
{
SET_FLAG(mediaChangeInfo->SrbFlags, SRB_FLAGS_NO_KEEP_AWAKE);
}
KeInitializeMutex(&mediaChangeInfo->MediaChangeMutex, 0x100);
// It is ok to support media change events on this device.
DeviceExtension->MediaChangeDetectionInfo = mediaChangeInfo;
// check the device supports GESN or not, initialize GESN structure if it supports.
{
// This is only valid for type5 devices.
NTSTATUS tempStatus = STATUS_SUCCESS;
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"DeviceInitializeMcn: Testing for GESN\n"));
tempStatus = DeviceInitializeGesn(DeviceExtension);
if (NT_SUCCESS(tempStatus))
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"DeviceInitializeMcn: GESN available for %p\n",
DeviceExtension->DeviceObject));
NT_ASSERT(mediaChangeInfo->Gesn.Supported );
NT_ASSERT(mediaChangeInfo->Gesn.Buffer != NULL);
NT_ASSERT(mediaChangeInfo->Gesn.BufferSize != 0);
NT_ASSERT(mediaChangeInfo->Gesn.EventMask != 0);
}
else
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"DeviceInitializeMcn: GESN *NOT* available for %p\n",
DeviceExtension->DeviceObject));
NT_ASSERT(!mediaChangeInfo->Gesn.Supported);
NT_ASSERT(mediaChangeInfo->Gesn.Buffer == NULL);
NT_ASSERT(mediaChangeInfo->Gesn.BufferSize == 0);
NT_ASSERT(mediaChangeInfo->Gesn.EventMask == 0);
mediaChangeInfo->Gesn.Supported = FALSE; // just in case....
}
}
}
if (NT_SUCCESS(status))
{
// Register for display state change on AOAC capable systems so we can put the
// device to low power state when not required.
if (mediaChangeInfo->DisplayStateCallbackHandle == NULL)
{
POWER_PLATFORM_INFORMATION PlatformInfo = {0};
status = ZwPowerInformation(PlatformInformation,
NULL,
0,
&PlatformInfo,
sizeof(PlatformInfo));
if (NT_SUCCESS(status) && PlatformInfo.AoAc)
{
PoRegisterPowerSettingCallback(DeviceExtension->DeviceObject,
&GUID_CONSOLE_DISPLAY_STATE,
&DevicePowerSettingCallback,
DeviceExtension,
&mediaChangeInfo->DisplayStateCallbackHandle);
}
// Ignore any failures above.
status = STATUS_SUCCESS;
}
}
if (!NT_SUCCESS(status))
{
if (irp != NULL)
{
IoFreeIrp(irp);
}
FREE_POOL(senseBuffer);
FREE_POOL(mediaChangeInfo);
}
return status;
} // end DeviceInitializeMcn()
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS
DeviceInitializeGesn(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension
)
/*++
Routine Description:
This routine initialize the contents of GESN structure.
Arguments:
DeviceExtension - the device extension
Return Value:
NTSTATUS
--*/
{
NTSTATUS status = STATUS_SUCCESS;
PNOTIFICATION_EVENT_STATUS_HEADER header = NULL;
CDROM_DETECTION_STATE detectionState = CdromDetectionUnknown;
PSTORAGE_DEVICE_DESCRIPTOR deviceDescriptor = DeviceExtension->DeviceDescriptor;
BOOLEAN retryImmediately = TRUE;
ULONG i = 0;
ULONG atapiResets = 0;
PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo;
PAGED_CODE();
NT_ASSERT(info != NULL);
// read if we already know the abilities of the device
DeviceGetParameter(DeviceExtension,
CLASSP_REG_SUBKEY_NAME,
CLASSP_REG_MMC_DETECTION_VALUE_NAME,
(PULONG)&detectionState);
if (detectionState == CdromDetectionUnsupported)
{
status = STATUS_NOT_SUPPORTED;
}
// check if the device has a hack flag saying never to try this.
if (NT_SUCCESS(status) &&
(TEST_FLAG(DeviceExtension->PrivateFdoData->HackFlags, FDO_HACK_GESN_IS_BAD)) )
{
DeviceSetParameter(DeviceExtension,
CLASSP_REG_SUBKEY_NAME,
CLASSP_REG_MMC_DETECTION_VALUE_NAME,
CdromDetectionUnsupported);
status = STATUS_NOT_SUPPORTED;
}
// else go through the process since we allocate buffers and
// get all sorts of device settings.
if (NT_SUCCESS(status))
{
if (info->Gesn.Buffer == NULL)
{
info->Gesn.Buffer = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned,
GESN_BUFFER_SIZE,
CDROM_TAG_GESN);
}
if (info->Gesn.Buffer == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
}
}
if (NT_SUCCESS(status))
{
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;
}
}
if (NT_SUCCESS(status))
{
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.
for (i = 0; (i < 16) && retryImmediately; i++)
{
status = RequestSetupMcnRequest(DeviceExtension, TRUE);
if (!NT_SUCCESS(status))
{
TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN,
"Setup Mcn request failed %x for WDFDEVICE %p\n",
status, DeviceExtension->Device));
break;
}
NT_ASSERT(TEST_FLAG(info->MediaChangeSrb.SrbFlags, SRB_FLAGS_NO_QUEUE_FREEZE));
status = DeviceSendRequestSynchronously(DeviceExtension->Device, info->MediaChangeRequest, TRUE);
if (SRB_STATUS(info->MediaChangeSrb.SrbStatus) != SRB_STATUS_SUCCESS)
{
// Release the queue if it is frozen.
if (info->MediaChangeSrb.SrbStatus & SRB_STATUS_QUEUE_FROZEN)
{
DeviceReleaseQueue(DeviceExtension->Device);
}
RequestSenseInfoInterpret(DeviceExtension,
info->MediaChangeRequest,
&(info->MediaChangeSrb),
0,
&status,
NULL);
}
if ((deviceDescriptor->BusType == BusTypeAtapi) &&
(info->MediaChangeSrb.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;
break;
}
}
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.
DeviceSetParameter( DeviceExtension,
CLASSP_REG_SUBKEY_NAME,
CLASSP_REG_MMC_DETECTION_VALUE_NAME,
CdromDetectionUnsupported);
TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN,
"GESN test failed %x for WDFDEVICE %p\n",
status, DeviceExtension->Device));
break;
}
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,
"GESN test failed %x for WDFDEVICE %p\n",
status, DeviceExtension->Device));
break;
}
else 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,
"WDFDEVICE %p supports event mask %x\n",
DeviceExtension->Device, header->SupportedEventClasses));
if (TEST_FLAG(header->SupportedEventClasses,
NOTIFICATION_MEDIA_STATUS_CLASS_MASK))
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"GESN supports MCN\n"));
}
if (TEST_FLAG(header->SupportedEventClasses,
NOTIFICATION_DEVICE_BUSY_CLASS_MASK))
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"GESN supports DeviceBusy\n"));
}
if (TEST_FLAG(header->SupportedEventClasses,
NOTIFICATION_OPERATIONAL_CHANGE_CLASS_MASK))
{
if (TEST_FLAG(DeviceExtension->PrivateFdoData->HackFlags,
FDO_HACK_GESN_IGNORE_OPCHANGE))
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"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,
"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,
"GESN supported, but not mask we care about (%x) for FDO %p\n",
header->SupportedEventClasses,
DeviceExtension->DeviceObject));
// NOTE: the status is still status_sucess.
break;
}
else if (CountOfSetBitsUChar(info->Gesn.EventMask) == 1)
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"GESN hack not required for FDO %p\n",
DeviceExtension->DeviceObject));
}
else
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"GESN hack enabled for FDO %p\n",
DeviceExtension->DeviceObject));
info->Gesn.HackEventMask = 1;
}
}
else
{
// i > 0; not the first time looping through, so interpret the results.
status = GesnDataInterpret(DeviceExtension,
(PVOID)info->Gesn.Buffer,
&retryImmediately);
if (!NT_SUCCESS(status))
{
// This drive does not support GESN correctly
DeviceSetParameter( DeviceExtension,
CLASSP_REG_SUBKEY_NAME,
CLASSP_REG_MMC_DETECTION_VALUE_NAME,
CdromDetectionUnsupported);
break;
}
}
} // end 'for' loop of GESN requests....
}
if (NT_SUCCESS(status))
{
//
// 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,
"Enabling GESN support for WDFDEVICE %p\n",
DeviceExtension->Device));
info->Gesn.Supported = TRUE;
DeviceSetParameter( DeviceExtension,
CLASSP_REG_SUBKEY_NAME,
CLASSP_REG_MMC_DETECTION_VALUE_NAME,
CdromDetectionSupported);
}
else
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"GESN available but not enabled for WDFDEVICE %p\n",
DeviceExtension->Device));
status = STATUS_NOT_SUPPORTED;
}
}
if (!NT_SUCCESS(status))
{
TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN,
"GESN support detection failed for WDFDEVICE %p with status %08x\n",
DeviceExtension->Device, status));
if (info->Gesn.Mdl)
{
PIRP irp = WdfRequestWdmGetIrp(info->MediaChangeRequest);
IoFreeMdl(info->Gesn.Mdl);
info->Gesn.Mdl = NULL;
irp->MdlAddress = NULL;
}
FREE_POOL(info->Gesn.Buffer);
info->Gesn.Supported = FALSE;
info->Gesn.EventMask = 0;
info->Gesn.BufferSize = 0;
}
return status;
}
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS
DeviceInitializeMediaChangeDetection(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension
)
/*++
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 DeviceInit...() .
Arguments:
DeviceExtension - the device to initialize MCN for, if appropriate
Return Value:
NTSTATUS
--*/
{
NTSTATUS status = STATUS_SUCCESS;
BOOLEAN disabled = FALSE;
BOOLEAN instanceOverride;
PAGED_CODE();
// NOTE: This assumes that DeviceInitializeMediaChangeDetection is always
// called in the context of the DeviceInitDevicePhase2. If called
// after then this check will have already been made and the
// once a second timer will not have been enabled.
disabled = DeviceIsMediaChangeDisabledDueToHardwareLimitation(DeviceExtension);
if (disabled)
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"DeviceInitializeMediaChangeDetection: Disabled due to hardware"
"limitations for this device\n"));
}
else
{
// autorun should now be enabled by default for all media types.
disabled = DeviceIsMediaChangeDisabledForClass(DeviceExtension);
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"DeviceInitializeMediaChangeDetection: MCN is %s\n",
(disabled ? "disabled" : "enabled")));
status = DeviceMediaChangeDeviceInstanceOverride(DeviceExtension,
&instanceOverride); // default value
if (!NT_SUCCESS(status))
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"DeviceInitializeMediaChangeDetection: Instance using default\n"));
}
else
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"DeviceInitializeMediaChangeDetection: Instance override: %s MCN\n",
(instanceOverride ? "Enabling" : "Disabling")));
disabled = !instanceOverride;
}
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"DeviceInitializeMediaChangeDetection: Instance MCN is %s\n",
(disabled ? "disabled" : "enabled")));
}
if (!disabled)
{
// if the drive is not a CDROM, allow the drive to sleep
status = DeviceInitializeMcn(DeviceExtension, FALSE);
}
return status;
} // end DeviceInitializeMediaChangeDetection()
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS
DeviceMediaChangeDeviceInstanceOverride(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_Out_ PBOOLEAN Enabled
)
/*++
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:
DeviceExtension - the device to set/get the value for
Enabled - TRUE (Autorun is enabled)
FALSE (Autorun is disabled)
Return Value:
NTSTATUS
--*/
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
WDFKEY deviceKey = NULL;
WDFKEY subKey = NULL;
UNICODE_STRING subkeyName;
UNICODE_STRING enableMcnValueName;
UNICODE_STRING disableMcnValueName;
ULONG alwaysEnable = 0;
ULONG alwaysDisable = 0;
PAGED_CODE();
status = WdfDeviceOpenRegistryKey(DeviceExtension->Device,
PLUGPLAY_REGKEY_DEVICE,
KEY_ALL_ACCESS,
WDF_NO_OBJECT_ATTRIBUTES,
&deviceKey);
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,
"DeviceMediaChangeDeviceInstanceOverride: "
"Could not open device registry key [%lx]\n", status));
}
else
{
RtlInitUnicodeString(&subkeyName, MCN_REG_SUBKEY_NAME);
status = WdfRegistryOpenKey(deviceKey,
&subkeyName,
KEY_READ,
WDF_NO_OBJECT_ATTRIBUTES,
&subKey);
}
if (!NT_SUCCESS(status))
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"DeviceMediaChangeDeviceInstanceOverride: "
"subkey could not be created. %lx\n", status));
}
else
{
// Default to not changing autorun behavior, based upon setting
// registryValue to zero.
RtlInitUnicodeString(&enableMcnValueName, MCN_REG_AUTORUN_ENABLE_INSTANCE_NAME);
RtlInitUnicodeString(&disableMcnValueName, MCN_REG_AUTORUN_DISABLE_INSTANCE_NAME);
// Ignore failures on reading of subkeys
(VOID) WdfRegistryQueryULong(subKey,
&enableMcnValueName,
&alwaysEnable);
(VOID) WdfRegistryQueryULong(subKey,
&disableMcnValueName,
&alwaysDisable);
}
// set return value and cleanup
if (subKey != NULL)
{
WdfRegistryClose(subKey);
}
if (deviceKey != NULL)
{
WdfRegistryClose(deviceKey);
}
if (alwaysEnable && alwaysDisable)
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"DeviceMediaChangeDeviceInstanceOverride: %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,
"DeviceMediaChangeDeviceInstanceOverride: %s selected\n",
"DISABLE"));
NT_ASSERT(NT_SUCCESS(status));
status = STATUS_SUCCESS;
*Enabled = FALSE;
}
else if (alwaysEnable)
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"DeviceMediaChangeDeviceInstanceOverride: %s selected\n",
"ENABLE"));
NT_ASSERT(NT_SUCCESS(status));
status = STATUS_SUCCESS;
*Enabled = TRUE;
}
else
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"DeviceMediaChangeDeviceInstanceOverride: %s selected\n",
"DEFAULT"));
status = STATUS_UNSUCCESSFUL;
}
return status;
} // end DeviceMediaChangeDeviceInstanceOverride()
NTSTATUS
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
DeviceMediaChangeRegistryCallBack(
_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
)
/*++
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
--*/
{
PULONG valueFound;
PUNICODE_STRING deviceString;
PWSTR keyValue;
PAGED_CODE();
UNREFERENCED_PARAMETER(ValueName);
if ((Context == NULL) || (EntryContext == NULL))
{
TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN,
"DeviceMediaChangeRegistryCallBack: NULL context should never be passed to registry call-back!\n"));
return STATUS_SUCCESS;
}
// if we have already set the value to true, exit
valueFound = EntryContext;
if ((*valueFound) != 0)
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"DeviceMediaChangeRegistryCallBack: already set to true\n"));
return STATUS_SUCCESS;
}
if (ValueLength == sizeof(WCHAR))
{
TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN,
"DeviceMediaChangeRegistryCallBack: 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 (keyValue == NULL)
{
return STATUS_SUCCESS;
}
// if the strings match, disable autorun
if (RtlCompareMemory(deviceString->Buffer, keyValue, ValueLength) == ValueLength)
{
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN, "DeviceMediaChangeRegistryCallBack: Match found\n"));
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN, "DeviceMediaChangeRegistryCallBack: DeviceString at %p\n",
deviceString->Buffer));
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN,
"DeviceMediaChangeRegistryCallBack: KeyValue at %p\n",
keyValue));
(*valueFound) = TRUE;
}
return STATUS_SUCCESS;
} // end DeviceMediaChangeRegistryCallBack()
_IRQL_requires_max_(PASSIVE_LEVEL)
BOOLEAN
DeviceIsMediaChangeDisabledDueToHardwareLimitation(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension
)
/*++
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.
NOTE: It's intended that not use WdfRegistryQueryMultiString in this funciton,
as it's much more complicated.(needs WDFCOLLECTION, WDFSTRING other than
UNICODE_STRING)
Arguments:
FdoExtension -
RegistryPath - pointer to the unicode string inside
...\CurrentControlSet\Services\Cdrom
Return Value:
TRUE - no autorun.
FALSE - Autorun may be enabled
--*/
{
NTSTATUS status;
PSTORAGE_DEVICE_DESCRIPTOR deviceDescriptor = DeviceExtension->DeviceDescriptor;
WDFKEY wdfKey;
HANDLE serviceKey = NULL;
RTL_QUERY_REGISTRY_TABLE parameters[2] = {0};
UNICODE_STRING deviceUnicodeString = {0};
ANSI_STRING deviceString = {0};
ULONG mediaChangeNotificationDisabled = 0;
PAGED_CODE();
// open the service key.
status = WdfDriverOpenParametersRegistryKey(WdfGetDriver(),
KEY_ALL_ACCESS,
WDF_NO_OBJECT_ATTRIBUTES,
&wdfKey);
if(!NT_SUCCESS(status))
{
// NT_ASSERT(FALSE); __REACTOS__ : allow to fail (for 1st stage setup)
// always take the safe path. if we can't open the service key, disable autorun
return TRUE;
}
if(NT_SUCCESS(status))
{
// 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 = NULL;
PUCHAR productId = NULL;
PUCHAR revisionId = NULL;
size_t length;
size_t offset;
deviceString.Buffer = NULL;
deviceUnicodeString.Buffer = NULL;
serviceKey = WdfRegistryWdmGetHandle(wdfKey);
// there may be nothing to check against
if ((deviceDescriptor->VendorIdOffset == 0) &&
(deviceDescriptor->ProductIdOffset == 0))
{
// no valid data in device extension.
status = STATUS_INTERNAL_ERROR;
}
// build deviceString using VendorId, Model and Revision.
// this string will be used to checked if it's one of devices in registry disable list.
if (NT_SUCCESS(status))
{
length = 0;
if (deviceDescriptor->VendorIdOffset == 0)
{
vendorId = NULL;
}
else
{
vendorId = (PUCHAR) deviceDescriptor + deviceDescriptor->VendorIdOffset;
length = strlen((LPCSTR)vendorId);
}
if ( deviceDescriptor->ProductIdOffset == 0 )
{
productId = NULL;
}
else
{
productId = (PUCHAR) deviceDescriptor + deviceDescriptor->ProductIdOffset;
length += strlen((LPCSTR)productId);
}
if ( deviceDescriptor->ProductRevisionOffset == 0 )
{
revisionId = NULL;
}
else
{
revisionId = (PUCHAR) deviceDescriptor + deviceDescriptor->ProductRevisionOffset;
length += strlen((LPCSTR)revisionId);
}
// allocate a buffer for the string
deviceString.Length = (USHORT)( length );
deviceString.MaximumLength = deviceString.Length + 1;
deviceString.Buffer = (PCHAR)ExAllocatePoolWithTag( NonPagedPoolNx,
deviceString.MaximumLength,
CDROM_TAG_AUTORUN_DISABLE
);
if (deviceString.Buffer == NULL)
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"DeviceIsMediaChangeDisabledDueToHardwareLimitation: Unable to alloc string buffer\n" ));
status = STATUS_INTERNAL_ERROR;
}
}
if (NT_SUCCESS(status))
{
// copy strings to the buffer
offset = 0;
if (vendorId != NULL)
{
RtlCopyMemory(deviceString.Buffer + offset,
vendorId,
strlen((LPCSTR)vendorId));
offset += strlen((LPCSTR)vendorId);
}
if ( productId != NULL )
{
RtlCopyMemory(deviceString.Buffer + offset,
productId,
strlen((LPCSTR)productId));
offset += strlen((LPCSTR)productId);
}
if ( revisionId != NULL )
{
RtlCopyMemory(deviceString.Buffer + offset,
revisionId,
strlen((LPCSTR)revisionId));
offset += strlen((LPCSTR)revisionId);
}
NT_ASSERT(offset == deviceString.Length);
#pragma warning(suppress:6386) // Not an issue as deviceString.Buffer is of size deviceString.MaximumLength, which is equal to (deviceString.Length + 1)
deviceString.Buffer[deviceString.Length] = '\0'; // Null-terminated
// convert to unicode as registry deals with unicode strings
status = RtlAnsiStringToUnicodeString( &deviceUnicodeString,
&deviceString,
TRUE
);
if (!NT_SUCCESS(status))
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"DeviceIsMediaChangeDisabledDueToHardwareLimitation: cannot convert "
"to unicode %lx\n", status));
}
}
if (NT_SUCCESS(status))
{
// query the value, setting valueFound to true if found
nullMultiSz = L"\0";
parameters[0].QueryRoutine = DeviceMediaChangeRegistryCallBack;
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);
UNREFERENCED_PARAMETER(status); //defensive coding, avoid PREFAST warning.
}
}
// Cleanup
{
FREE_POOL( deviceString.Buffer );
if (deviceUnicodeString.Buffer != NULL)
{
RtlFreeUnicodeString( &deviceUnicodeString );
}
// handle serviceKey will be closed by framework while it closes registry key.
WdfRegistryClose(wdfKey);
}
if (mediaChangeNotificationDisabled > 0)
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"DeviceIsMediaChangeDisabledDueToHardwareLimitation: Device is on MCN disable list\n"));
}
return (mediaChangeNotificationDisabled > 0);
} // end DeviceIsMediaChangeDisabledDueToHardwareLimitation()
_IRQL_requires_max_(PASSIVE_LEVEL)
BOOLEAN
DeviceIsMediaChangeDisabledForClass(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension
)
/*++
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:
DeviceExtension - device extension
Return Value:
TRUE - Autorun is disabled for this class
FALSE - Autorun is enabled for this class
--*/
{
NTSTATUS status;
WDFKEY serviceKey = NULL;
WDFKEY parametersKey = NULL;
UNICODE_STRING parameterKeyName;
UNICODE_STRING valueName;
// Default to ENABLING MediaChangeNotification (!)
ULONG mcnRegistryValue = 1;
PAGED_CODE();
// open the service key.
status = WdfDriverOpenParametersRegistryKey(WdfGetDriver(),
KEY_ALL_ACCESS,
WDF_NO_OBJECT_ATTRIBUTES,
&serviceKey);
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,
"DeviceIsMediaChangeDisabledForClass: Defaulting to %s\n",
(mcnRegistryValue ? "Enabled" : "Disabled")));
return (BOOLEAN)(mcnRegistryValue == 0);
}
else
{
// Open the parameters key (if any) beneath the services key.
RtlInitUnicodeString(&parameterKeyName, L"Parameters");
status = WdfRegistryOpenKey(serviceKey,
&parameterKeyName,
KEY_READ,
WDF_NO_OBJECT_ATTRIBUTES,
&parametersKey);
}
if (!NT_SUCCESS(status))
{
parametersKey = NULL;
}
RtlInitUnicodeString(&valueName, L"Autorun");
// ignore failures
status = WdfRegistryQueryULong(serviceKey,
&valueName,
&mcnRegistryValue);
if (NT_SUCCESS(status))
{
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN,
"DeviceIsMediaChangeDisabledForClass: <Service>/Autorun flag = %d\n",
mcnRegistryValue));
}
if (parametersKey != NULL)
{
status = WdfRegistryQueryULong(parametersKey,
&valueName,
&mcnRegistryValue);
if (NT_SUCCESS(status))
{
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN,
"DeviceIsMediaChangeDisabledForClass: <Service>/Parameters/Autorun flag = %d\n",
mcnRegistryValue));
}
WdfRegistryClose(parametersKey);
}
WdfRegistryClose(serviceKey);
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"DeviceIsMediaChangeDisabledForClass: Autoplay for device %p is %s\n",
DeviceExtension->DeviceObject,
(mcnRegistryValue ? "on" : "off")
));
// return if it is _disabled_, which is the
// inverse of the registry setting
return (BOOLEAN)(!mcnRegistryValue);
} // end DeviceIsMediaChangeDisabledForClass()
_IRQL_requires_max_(APC_LEVEL)
VOID
DeviceEnableMediaChangeDetection(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_Inout_ PFILE_OBJECT_CONTEXT FileObjectContext,
_In_ BOOLEAN IgnorePreviousMediaChanges
)
/*++
Routine Description:
When the disable count decrease to 0, enable the MCN
Arguments:
DeviceExtension - the device context
FileObjectContext - the file object context
IgnorePreviousMediaChanges - ignore all previous media changes
Return Value:
None.
--*/
{
PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo;
LONG oldCount;
PAGED_CODE();
if (FileObjectContext)
{
InterlockedDecrement((PLONG)&(FileObjectContext->McnDisableCount));
}
if (info == NULL)
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"DeviceEnableMediaChangeDetection: not initialized\n"));
return;
}
(VOID) KeWaitForMutexObject(&info->MediaChangeMutex,
UserRequest,
KernelMode,
FALSE,
NULL);
oldCount = --info->MediaChangeDetectionDisableCount;
NT_ASSERT(oldCount >= 0);
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN,
"DeviceEnableMediaChangeDetection: Disable count reduced to %d - \n",
info->MediaChangeDetectionDisableCount));
if (oldCount == 0)
{
if (IgnorePreviousMediaChanges)
{
info->LastReportedMediaDetectionState = info->LastKnownMediaDetectionState;
}
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "MCN is enabled\n"));
}
else
{
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN, "MCD still disabled\n"));
}
// Let something else run.
KeReleaseMutex(&info->MediaChangeMutex, FALSE);
return;
} // end DeviceEnableMediaChangeDetection()
_IRQL_requires_max_(APC_LEVEL)
VOID
DeviceDisableMediaChangeDetection(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_Inout_ PFILE_OBJECT_CONTEXT FileObjectContext
)
/*++
Routine Description:
Increase the disable count.
Arguments:
DeviceExtension - the device context
FileObjectContext - the file object context
Return Value:
None.
--*/
{
PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo;
PAGED_CODE();
if (FileObjectContext)
{
InterlockedIncrement((PLONG)&(FileObjectContext->McnDisableCount));
}
if (info == NULL)
{
return;
}
(VOID) KeWaitForMutexObject(&info->MediaChangeMutex,
UserRequest,
KernelMode,
FALSE,
NULL);
info->MediaChangeDetectionDisableCount++;
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN,
"DisableMediaChangeDetection: disable count is %d\n",
info->MediaChangeDetectionDisableCount));
KeReleaseMutex(&info->MediaChangeMutex, FALSE);
return;
} // end DeviceDisableMediaChangeDetection()
_IRQL_requires_max_(APC_LEVEL)
VOID
DeviceReleaseMcnResources(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension
)
/*++
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:
DeviceExtension - the device context
Return Value:
None.
--*/
{
PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo;
PAGED_CODE()
if(info == NULL)
{
return;
}
if (info->Gesn.Mdl)
{
PIRP irp = WdfRequestWdmGetIrp(info->MediaChangeRequest);
IoFreeMdl(info->Gesn.Mdl);
irp->MdlAddress = NULL;
}
IoFreeIrp(info->MediaChangeSyncIrp);
FREE_POOL(info->Gesn.Buffer);
FREE_POOL(info->SenseBuffer);
if (info->DisplayStateCallbackHandle)
{
PoUnregisterPowerSettingCallback(info->DisplayStateCallbackHandle);
info->DisplayStateCallbackHandle = NULL;
}
FREE_POOL(info);
DeviceExtension->MediaChangeDetectionInfo = NULL;
return;
} // end DeviceReleaseMcnResources()
IO_COMPLETION_ROUTINE RequestMcnSyncIrpCompletion;
NTSTATUS
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
RequestMcnSyncIrpCompletion(
_In_ PDEVICE_OBJECT DeviceObject,
_In_ PIRP Irp,
_In_reads_opt_(_Inexpressible_("varies")) PVOID Context
)
/*++
Routine Description:
The MCN work finishes, reset the fields to allow another MCN request
be scheduled.
Arguments:
DeviceObject - device that the completion routine fires on.
Irp - The irp to be completed.
Context - IRP context
Return Value:
NTSTATUS
--*/
{
PCDROM_DEVICE_EXTENSION DeviceExtension = NULL;
PMEDIA_CHANGE_DETECTION_INFO info = NULL;
if (Context == NULL)
{
// this will never happen, but code must be there to prevent OACR warnings.
return STATUS_MORE_PROCESSING_REQUIRED;
}
DeviceExtension = (PCDROM_DEVICE_EXTENSION) Context;
info = DeviceExtension->MediaChangeDetectionInfo;
#ifndef DEBUG
UNREFERENCED_PARAMETER(Irp);
#endif
UNREFERENCED_PARAMETER(DeviceObject);
NT_ASSERT(Irp == info->MediaChangeSyncIrp);
IoReuseIrp(info->MediaChangeSyncIrp, STATUS_NOT_SUPPORTED);
// reset the value to let timer routine be able to send the next request.
InterlockedCompareExchange((PLONG)&(info->MediaChangeRequestInUse), 0, 1);
return STATUS_MORE_PROCESSING_REQUIRED;
}
VOID
RequestSetupMcnSyncIrp(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension
)
/*++
Routine Description:
setup the MCN synchronization irp.
Arguments:
DeviceExtension - the device context
Return Value:
None
--*/
{
PIRP irp = NULL;
PIO_STACK_LOCATION irpStack = NULL;
PIO_STACK_LOCATION nextIrpStack = NULL;
irp = DeviceExtension->MediaChangeDetectionInfo->MediaChangeSyncIrp;
NT_ASSERT(irp != NULL);
//
// 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 = DeviceExtension->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
//
nextIrpStack = IoGetNextIrpStackLocation(irp);
SET_FLAG(nextIrpStack->Flags, SL_OVERRIDE_VERIFY_VOLUME);
nextIrpStack->MajorFunction = IRP_MJ_DEVICE_CONTROL;
// pick up this IOCTL code as it's not normaly seen for CD/DVD drive and does not require input.
// set other fields to make this IOCTL recognizable by CDROM.SYS
nextIrpStack->Parameters.Others.Argument1 = RequestSetupMcnSyncIrp;
nextIrpStack->Parameters.Others.Argument2 = RequestSetupMcnSyncIrp;
nextIrpStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_MCN_SYNC_FAKE_IOCTL; //Argument3.
nextIrpStack->Parameters.Others.Argument4 = RequestSetupMcnSyncIrp;
IoSetCompletionRoutine(irp,
RequestMcnSyncIrpCompletion,
DeviceExtension,
TRUE,
TRUE,
TRUE);
return;
}
VOID
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
DeviceMainTimerTickHandler(
_In_ WDFTIMER Timer
)
/*++
Routine Description:
This routine setup a sync irp and send it to the serial queue.
Serial queue will process MCN when receive this sync irp.
Arguments:
Timer - the timer object that fires.
Return Value:
None
--*/
{
PCDROM_DEVICE_EXTENSION deviceExtension = NULL;
size_t dataLength = 0;
deviceExtension = WdfObjectGetTypedContext(WdfTimerGetParentObject(Timer), CDROM_DEVICE_EXTENSION);
(void) RequestHandleEventNotification(deviceExtension, NULL, NULL, &dataLength);
return;
} // end DeviceMainTimerTickHandler()
_IRQL_requires_max_(APC_LEVEL)
NTSTATUS
DeviceEnableMainTimer(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension
)
/*++
Routine Description:
This routine will allocate timer related resources on the first time call.
Start the timer.
Arguments:
DeviceExtension - the device context
Return Value:
NTSTATUS
--*/
{
NTSTATUS status = STATUS_SUCCESS;
if ((DeviceExtension->MediaChangeDetectionInfo == NULL) ||
(DeviceExtension->MediaChangeDetectionInfo->AsynchronousNotificationSupported != FALSE))
{
// Asynchronous Notification is enabled, timer not needed.
return status;
}
if (DeviceExtension->MainTimer == NULL)
{
//create main timer object.
WDF_TIMER_CONFIG timerConfig;
WDF_OBJECT_ATTRIBUTES timerAttributes;
WDF_TIMER_CONFIG_INIT(&timerConfig, DeviceMainTimerTickHandler);
// Polling frequently on virtual optical devices created by Hyper-V will
// cause a significant perf / power hit. These devices need to be polled
// less frequently for device state changes.
if (TEST_FLAG(DeviceExtension->DeviceAdditionalData.HackFlags, CDROM_HACK_MSFT_VIRTUAL_ODD))
{
timerConfig.Period = 2000; // 2 seconds, in milliseconds.
}
else
{
timerConfig.Period = 1000; // 1 second, in milliseconds.
}
timerConfig.TolerableDelay = 500; // 0.5 seconds, in milliseconds
//Set the autoSerialization to FALSE, as the parent device's
//execute level is WdfExecutionLevelPassive.
timerConfig.AutomaticSerialization = FALSE;
WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes);
timerAttributes.ParentObject = DeviceExtension->Device;
timerAttributes.ExecutionLevel = WdfExecutionLevelInheritFromParent;
status = WdfTimerCreate(&timerConfig,
&timerAttributes,
&DeviceExtension->MainTimer);
}
if (NT_SUCCESS(status))
{
WdfTimerStart(DeviceExtension->MainTimer,WDF_REL_TIMEOUT_IN_MS(100));
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"DeviceEnableMainTimer: Once a second timer enabled for WDFDEVICE %p\n",
DeviceExtension->Device));
}
else
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"DeviceEnableMainTimer: WDFDEVICE %p, Status %lx initializing timer\n",
DeviceExtension->Device, status));
}
return status;
} // end DeviceEnableMainTimer()
_IRQL_requires_max_(PASSIVE_LEVEL)
VOID
DeviceDisableMainTimer(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension
)
/*++
Routine Description:
stop the timer.
Arguments:
DeviceExtension - device context
Return Value:
None
--*/
{
PAGED_CODE();
if ((DeviceExtension->MediaChangeDetectionInfo == NULL) ||
(DeviceExtension->MediaChangeDetectionInfo->AsynchronousNotificationSupported != FALSE))
{
// Asynchronous Notification is enabled, timer not needed.
return;
}
if (DeviceExtension->MainTimer != NULL)
{
//
// we are only going to stop the actual timer in remove device routine.
// 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.
//
WdfTimerStop(DeviceExtension->MainTimer,TRUE);
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
"DeviceDisableMainTimer: Once a second timer disabled for device %p\n",
DeviceExtension->Device));
}
else
{
TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN,
"DeviceDisableMainTimer: Timer never enabled\n"));
}
return;
} // end DeviceDisableMainTimer()
_IRQL_requires_max_(APC_LEVEL)
NTSTATUS
RequestHandleMcnControl(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_In_ WDFREQUEST Request,
_Out_ size_t * DataLength
)
/*++
Routine Description:
This routine handles the process of IOCTL_STORAGE_MCN_CONTROL
Arguments:
DeviceExtension - device context
Request - request object
RequestParameters - request parameters
DataLength - data transferred
Return Value:
NTSTATUS
--*/
{
NTSTATUS status = STATUS_SUCCESS;
WDFFILEOBJECT fileObject = NULL;
PFILE_OBJECT_CONTEXT fileObjectContext = NULL;
PPREVENT_MEDIA_REMOVAL mediaRemoval = NULL;
PAGED_CODE();
*DataLength = 0;
status = WdfRequestRetrieveInputBuffer(Request,
sizeof(PREVENT_MEDIA_REMOVAL),
&mediaRemoval,
NULL);
if (NT_SUCCESS(status))
{
fileObject = WdfRequestGetFileObject(Request);
// Check to make sure we have a file object extension to keep track of this
// request. If not we'll fail it before synchronizing.
if (fileObject != NULL)
{
fileObjectContext = FileObjectGetContext(fileObject);
}
if ((fileObjectContext == NULL) &&
(WdfRequestGetRequestorMode(Request) == KernelMode))
{
fileObjectContext = &DeviceExtension->KernelModeMcnContext;
}
if (fileObjectContext == NULL)
{
// This handle isn't setup correctly. We can't let the
// operation go.
status = STATUS_INVALID_PARAMETER;
}
}
if (NT_SUCCESS(status))
{
if (mediaRemoval->PreventMediaRemoval)
{
// This is a lock command. Reissue the command in case bus or
// device was reset and the lock was cleared.
DeviceDisableMediaChangeDetection(DeviceExtension, fileObjectContext);
}
else
{
if (fileObjectContext->McnDisableCount == 0)
{
status = STATUS_INVALID_DEVICE_STATE;
}
else
{
DeviceEnableMediaChangeDetection(DeviceExtension, fileObjectContext, TRUE);
}
}
}
return status;
} // end RequestHandleMcnControl()
#pragma warning(pop) // un-sets any local warning changes