Sample Code

windows driver samples/ CDROM Storage Class Driver/ C++/ src/ autorun.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
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
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);

        // 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
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
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

Our Services

  • What our customers say about us?

© 2011-2025 All Rights Reserved. Joya Systems. 4425 South Mopac Building II Suite 101 Austin, TX 78735 Tel: 800-DEV-KERNEL

Privacy Policy. Terms of use. Valid XHTML & CSS