xref: /reactos/drivers/storage/class/cdrom/autorun.c (revision 84344399)
1 /*++
2 
3 Copyright (C) Microsoft Corporation. All rights reserved.
4 
5 Module Name:
6 
7     autorun.c
8 
9 Abstract:
10 
11     Code for support of media change detection in the cd/dvd driver
12 
13 Environment:
14 
15     kernel mode only
16 
17 Notes:
18 
19 
20 Revision History:
21 
22 --*/
23 
24 #include "stddef.h"
25 #include "string.h"
26 
27 #include "ntddk.h"
28 #include "ntddstor.h"
29 #include "cdrom.h"
30 #include "mmc.h"
31 #include "ioctl.h"
32 
33 #include "ntstrsafe.h"
34 
35 #ifdef DEBUG_USE_WPP
36 #include "autorun.tmh"
37 #endif
38 
39 #define GESN_TIMEOUT_VALUE (0x4)
40 #define GESN_BUFFER_SIZE (0x8)
41 #define GESN_DEVICE_BUSY_LOWER_THRESHOLD_100_MS   (2)
42 
43 #define MAXIMUM_IMMEDIATE_MCN_RETRIES (0x20)
44 #define MCN_REG_SUBKEY_NAME                   (L"MediaChangeNotification")
45 #define MCN_REG_AUTORUN_DISABLE_INSTANCE_NAME (L"AlwaysDisableMCN")
46 #define MCN_REG_AUTORUN_ENABLE_INSTANCE_NAME  (L"AlwaysEnableMCN")
47 
48 //
49 // Only send polling irp when device is fully powered up and a
50 // power down irp is not in progress.
51 //
52 // NOTE:   This helps close a window in time where a polling irp could cause
53 //         a drive to spin up right after it has powered down. The problem is
54 //         that SCSIPORT, ATAPI and SBP2 will be in the process of powering
55 //         down (which may take a few seconds), but won't know that. It would
56 //         then get a polling irp which will be put into its queue since it
57 //         the disk isn't powered down yet. Once the disk is powered down it
58 //         will find the polling irp in the queue and then power up the
59 //         device to do the poll. They do not want to check if the polling
60 //         irp has the SRB_NO_KEEP_AWAKE flag here since it is in a critical
61 //         path and would slow down all I/Os. A better way to fix this
62 //         would be to serialize the polling and power down irps so that
63 //         only one of them is sent to the device at a time.
64 //
65 
66 _IRQL_requires_max_(PASSIVE_LEVEL)
67 BOOLEAN
68 DeviceIsMediaChangeDisabledDueToHardwareLimitation(
69     _In_ PCDROM_DEVICE_EXTENSION DeviceExtension
70     );
71 
72 _IRQL_requires_max_(PASSIVE_LEVEL)
73 BOOLEAN
74 DeviceIsMediaChangeDisabledForClass(
75     _In_ PCDROM_DEVICE_EXTENSION DeviceExtension
76     );
77 
78 _IRQL_requires_max_(PASSIVE_LEVEL)
79 NTSTATUS
80 DeviceMediaChangeDeviceInstanceOverride(
81     _In_ PCDROM_DEVICE_EXTENSION  DeviceExtension,
82     _Out_ PBOOLEAN                Enabled
83     );
84 
85 _IRQL_requires_max_(PASSIVE_LEVEL)
86 NTSTATUS
87 DeviceInitializeMcn(
88     _In_ PCDROM_DEVICE_EXTENSION  DeviceExtension,
89     _In_ BOOLEAN                  AllowDriveToSleep
90     );
91 
92 _IRQL_requires_max_(PASSIVE_LEVEL)
93 NTSTATUS
94 DeviceInitializeGesn(
95     _In_ PCDROM_DEVICE_EXTENSION      DeviceExtension
96     );
97 
98 _IRQL_requires_max_(APC_LEVEL)
99 NTSTATUS
100 GesnDataInterpret(
101     _In_  PCDROM_DEVICE_EXTENSION             DeviceExtension,
102     _In_  PNOTIFICATION_EVENT_STATUS_HEADER   Header,
103     _Out_ PBOOLEAN                            ResendImmediately
104     );
105 
106 RTL_QUERY_REGISTRY_ROUTINE DeviceMediaChangeRegistryCallBack;
107 
108 EVT_WDF_WORKITEM DeviceDisableGesn;
109 
110 #if ALLOC_PRAGMA
111 
112 #pragma alloc_text(PAGE, DeviceInitializeMediaChangeDetection)
113 #pragma alloc_text(PAGE, DeviceEnableMediaChangeDetection)
114 #pragma alloc_text(PAGE, DeviceDisableMediaChangeDetection)
115 #pragma alloc_text(PAGE, DeviceSendDelayedMediaChangeNotifications)
116 #pragma alloc_text(PAGE, DeviceReleaseMcnResources)
117 #pragma alloc_text(PAGE, DeviceMediaChangeRegistryCallBack)
118 #pragma alloc_text(PAGE, DeviceInitializeMcn)
119 #pragma alloc_text(PAGE, DeviceDisableGesn)
120 
121 #pragma alloc_text(PAGE, DeviceIsMediaChangeDisabledDueToHardwareLimitation)
122 #pragma alloc_text(PAGE, DeviceMediaChangeDeviceInstanceOverride)
123 #pragma alloc_text(PAGE, DeviceIsMediaChangeDisabledForClass)
124 
125 #pragma alloc_text(PAGE, DeviceDisableMainTimer)
126 
127 #pragma alloc_text(PAGE, GesnDataInterpret)
128 
129 #pragma alloc_text(PAGE, RequestSetupMcnRequest)
130 #pragma alloc_text(PAGE, RequestPostWorkMcnRequest)
131 #pragma alloc_text(PAGE, RequestSendMcnRequest)
132 
133 //
134 // DeviceEnableMainTimer is called by EvtDeviceD0Entry which can't be made pageable
135 // so neither is DeviceEnableMainTimer
136 //
137 //#pragma alloc_text(PAGE, DeviceEnableMainTimer)
138 
139 #pragma alloc_text(PAGE, DeviceInitializeGesn)
140 
141 #pragma alloc_text(PAGE, RequestHandleMcnControl)
142 
143 #endif
144 
145 #pragma warning(push)
146 #pragma warning(disable:4152) // nonstandard extension, function/data pointer conversion in expression
147 
148 
149 _IRQL_requires_max_(APC_LEVEL)
150 NTSTATUS
151 GesnDataInterpret(
152     _In_  PCDROM_DEVICE_EXTENSION             DeviceExtension,
153     _In_  PNOTIFICATION_EVENT_STATUS_HEADER   Header,
154     _Out_ PBOOLEAN                            ResendImmediately
155     )
156 /*++
157 
158 Routine Description:
159 
160     This routine will interpret the data returned for a GESN command, and
161     (if appropriate) set the media change event, and broadcast the
162     appropriate events to user mode for applications who care.
163 
164 Arguments:
165 
166     DeviceExtension - the device extension
167 
168     Header - the resulting data from a GESN event.
169         requires at least EIGHT valid bytes (header == 4, data == 4)
170 
171     ResendImmediately - whether or not to immediately resend the request.
172         this should be FALSE if there was no event, FALSE if the reported
173         event was of the DEVICE BUSY class, else true.
174 
175 Return Value:
176 
177     STATUS_SUCCESS if successful, an error code otherwise
178 
179 Notes:
180 
181     DataBuffer must be at least four bytes of valid data (header == 4 bytes),
182     and have at least eight bytes of allocated memory (all events == 4 bytes).
183 
184     The call to StartNextPacket may occur before this routine is completed.
185     the operational change notifications are informational in nature, and
186     while useful, are not neccessary to ensure proper operation.  For example,
187     if the device morphs to no longer supporting WRITE commands, all further
188     write commands will fail.  There exists a small timing window wherein
189     IOCTL_IS_DISK_WRITABLE may be called and get an incorrect response.  If
190     a device supports software write protect, it is expected that the
191     application can handle such a case.
192 
193     NOTE: perhaps setting the updaterequired byte to one should be done here.
194     if so, it relies upon the setting of a 32-byte value to be an atomic
195     operation.  unfortunately, there is no simple way to notify a class driver
196     which wants to know that the device behavior requires updating.
197 
198     Not ready events may be sent every second.  For example, if we were
199     to minimize the number of asynchronous notifications, an application may
200     register just after a large busy time was reported.  This would then
201     prevent the application from knowing the device was busy until some
202     arbitrarily chosen timeout has occurred.  Also, the GESN request would
203     have to still occur, since it checks for non-busy events (such as user
204     keybutton presses and media change events) as well.  The specification
205     states that the lower-numered events get reported first, so busy events,
206     while repeating, will only be reported when all other events have been
207     cleared from the device.
208 
209 --*/
210 {
211     NTSTATUS                        status = STATUS_SUCCESS;
212     PMEDIA_CHANGE_DETECTION_INFO    info = DeviceExtension->MediaChangeDetectionInfo;
213     LONG                            dataLength = 0;
214     LONG                            requiredLength = 0;
215     BOOLEAN                         inHomePosition = FALSE;
216 
217     PAGED_CODE();
218 
219     // note: don't allocate anything in this routine so that we can
220     //       always just 'return'.
221     *ResendImmediately = FALSE;
222 
223     if (Header->NEA)
224     {
225         return status;
226     }
227     if (Header->NotificationClass == NOTIFICATION_NO_CLASS_EVENTS)
228     {
229         return status;
230     }
231 
232     // HACKHACK - REF #0001
233     // This loop is only taken initially, due to the inability to reliably
234     // auto-detect drives that report events correctly at boot.  When we
235     // detect this behavior during the normal course of running, we will
236     // disable the hack, allowing more efficient use of the system.  This
237     // should occur "nearly" instantly, as the drive should have multiple
238     // events queue'd (ie. power, morphing, media).
239     if (info->Gesn.HackEventMask)
240     {
241         // all events use the low four bytes of zero to indicate
242         // that there was no change in status.
243         UCHAR thisEvent = Header->ClassEventData[0] & 0xf;
244         UCHAR lowestSetBit;
245         UCHAR thisEventBit = (1 << Header->NotificationClass);
246 
247         if (!TEST_FLAG(info->Gesn.EventMask, thisEventBit))
248         {
249             // The drive is reporting an event that wasn't requested
250             return STATUS_DEVICE_PROTOCOL_ERROR;
251         }
252 
253         // some bit magic here... this results in the lowest set bit only
254         lowestSetBit = info->Gesn.EventMask;
255         lowestSetBit &= (info->Gesn.EventMask - 1);
256         lowestSetBit ^= (info->Gesn.EventMask);
257 
258         if (thisEventBit != lowestSetBit)
259         {
260             // HACKHACK - REF #0001
261             // the first time we ever see an event set that is not the lowest
262             // set bit in the request (iow, highest priority), we know that the
263             // hack is no longer required, as the device is ignoring "no change"
264             // events when a real event is waiting in the other requested queues.
265             TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
266                        "GESN::NONE: Compliant drive found, "
267                        "removing GESN hack (%x, %x)\n",
268                        thisEventBit, info->Gesn.EventMask));
269 
270             info->Gesn.HackEventMask = FALSE;
271         }
272         else if (thisEvent == 0)  // NOTIFICATION_*_EVENT_NO_CHANGE
273         {
274             // HACKHACK - REF #0001
275             // note: this hack prevents poorly implemented firmware from constantly
276             //       returning "No Event".  we do this by cycling through the
277             //       supported list of events here.
278             SET_FLAG(info->Gesn.NoChangeEventMask, thisEventBit);
279             CLEAR_FLAG(info->Gesn.EventMask, thisEventBit);
280 
281             // if we have cycled through all supported event types, then
282             // we need to reset the events we are asking about. else we
283             // want to resend this request immediately in case there was
284             // another event pending.
285             if (info->Gesn.EventMask == 0)
286             {
287                 info->Gesn.EventMask         = info->Gesn.NoChangeEventMask;
288                 info->Gesn.NoChangeEventMask = 0;
289             }
290             else
291             {
292                 *ResendImmediately = TRUE;
293             }
294             return status;
295         }
296 
297     } // end if (info->Gesn.HackEventMask)
298 
299     dataLength = (Header->EventDataLength[0] << 8) |
300                  (Header->EventDataLength[1] & 0xff);
301     dataLength -= 2;
302     requiredLength = 4; // all events are four bytes
303 
304     if (dataLength < requiredLength)
305     {
306         TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN,
307                    "error - GESN returned only %x bytes data for fdo %p\n",
308                    dataLength, DeviceExtension->DeviceObject));
309 
310         return STATUS_DEVICE_PROTOCOL_ERROR;
311     }
312 
313     if (dataLength > requiredLength)
314     {
315         TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN,
316                    "error - GESN returned too many (%x) bytes data for fdo %p\n",
317                    dataLength, DeviceExtension->DeviceObject));
318     }
319 
320     if ((Header->ClassEventData[0] & 0xf) == 0)
321     {
322         // a zero event is a "no change event, so do not retry
323         return status;
324     }
325 
326     // because a event other than "no change" occurred,
327     // we should immediately resend this request.
328     *ResendImmediately = TRUE;
329 
330     switch (Header->NotificationClass)
331     {
332 
333     case NOTIFICATION_OPERATIONAL_CHANGE_CLASS_EVENTS:  // 0x01
334     {
335         PNOTIFICATION_OPERATIONAL_STATUS opChangeInfo =
336                                             (PNOTIFICATION_OPERATIONAL_STATUS)(Header->ClassEventData);
337         ULONG event;
338 
339         if (opChangeInfo->OperationalEvent == NOTIFICATION_OPERATIONAL_EVENT_CHANGE_REQUESTED)
340         {
341             break;
342         }
343 
344         event = (opChangeInfo->Operation[0] << 8) |
345                 (opChangeInfo->Operation[1]     ) ;
346 
347         // Workaround some hardware that is buggy but prevalent in the market
348         // This hardware has the property that it will report OpChange events repeatedly,
349         // causing us to retry immediately so quickly that we will eventually disable
350         // GESN to prevent an infinite loop.
351         // (only one valid OpChange event type now, only two ever defined)
352         if (info->MediaChangeRetryCount >= 4)
353         {
354             //
355             // HACKHACK - REF #0002
356             // Some drives incorrectly report OpChange/Change (001b/0001h) events
357             // continuously when the tray has been ejected.  This causes this routine
358             // to set ResendImmediately to "TRUE", and that results in our cycling
359             // 32 times immediately resending.  At that point, we give up detecting
360             // the infinite retry loop, and disable GESN on these drives.  This
361             // prevents Media Eject Request (from eject button) from being reported.
362             // Thus, instead we should attempt to workaround this issue by detecting
363             // this behavior.
364             //
365 
366             static UCHAR const OpChangeMask = 0x02;
367 
368             // At least one device reports "temporarily busy" (which is useless) on eject
369             // At least one device reports "OpChange" repeatedly when re-inserting media
370             // All seem to work well using this workaround
371 
372             TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_MCN,
373                         "GESN OpChange events are broken.  Working around this problem in software (for WDFDEVICE %p)\n",
374                         DeviceExtension->Device));
375 
376             // OpChange is not the only bit set -- Media class is required....
377             NT_ASSERT(CountOfSetBitsUChar(info->Gesn.EventMask) != 1);
378 
379             // Force the use of the hackhack (ref #0001) to workaround the
380             // issue noted this hackhack (ref #0002).
381             SET_FLAG(info->Gesn.NoChangeEventMask, OpChangeMask);
382             CLEAR_FLAG(info->Gesn.EventMask, OpChangeMask);
383             info->Gesn.HackEventMask = TRUE;
384 
385             // don't request the opChange event again.  use the method
386             // defined by hackhack (ref #0001) as the workaround.
387             if (info->Gesn.EventMask == 0)
388             {
389                 info->Gesn.EventMask = info->Gesn.NoChangeEventMask;
390                 info->Gesn.NoChangeEventMask = 0;
391                 *ResendImmediately = FALSE;
392             }
393             else
394             {
395                 *ResendImmediately = TRUE;
396             }
397 
398             break;
399         }
400 
401 
402         if ((event == NOTIFICATION_OPERATIONAL_OPCODE_FEATURE_ADDED) |
403             (event == NOTIFICATION_OPERATIONAL_OPCODE_FEATURE_CHANGE))
404         {
405             TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
406                        "GESN says features added/changed for WDFDEVICE %p\n",
407                        DeviceExtension->Device));
408 
409             // don't notify that new media arrived, just set the
410             // DO_VERIFY to force a FS reload.
411 
412             if (IsVolumeMounted(DeviceExtension->DeviceObject))
413             {
414                 SET_FLAG(DeviceExtension->DeviceObject->Flags, DO_VERIFY_VOLUME);
415             }
416 
417             // Call error handler with
418             // a "fake" media change error in case it needs to update
419             // internal structures as though a media change occurred.
420             {
421                 SCSI_REQUEST_BLOCK  srb = {0};
422                 SENSE_DATA          sense = {0};
423                 NTSTATUS            tempStatus;
424                 BOOLEAN             retry;
425 
426                 tempStatus = STATUS_MEDIA_CHANGED;
427                 retry = FALSE;
428 
429                 srb.CdbLength = 6;
430                 srb.Length    = sizeof(SCSI_REQUEST_BLOCK);
431                 srb.SrbStatus = SRB_STATUS_AUTOSENSE_VALID | SRB_STATUS_ERROR;
432                 srb.SenseInfoBuffer = &sense;
433                 srb.SenseInfoBufferLength = sizeof(SENSE_DATA);
434 
435                 sense.AdditionalSenseLength = sizeof(SENSE_DATA) -
436                                             RTL_SIZEOF_THROUGH_FIELD(SENSE_DATA, AdditionalSenseLength);
437 
438                 sense.SenseKey = SCSI_SENSE_UNIT_ATTENTION;
439                 sense.AdditionalSenseCode = SCSI_ADSENSE_MEDIUM_CHANGED;
440 
441                 if (DeviceExtension->DeviceAdditionalData.ErrorHandler)
442                 {
443                     DeviceExtension->DeviceAdditionalData.ErrorHandler(DeviceExtension,
444                                                                        &srb,
445                                                                        &tempStatus,
446                                                                        &retry);
447                 }
448             } // end error handler
449 
450         }
451         break;
452     }
453 
454     case NOTIFICATION_EXTERNAL_REQUEST_CLASS_EVENTS:  // 0x3
455     {
456         PNOTIFICATION_EXTERNAL_STATUS externalInfo =
457                                         (PNOTIFICATION_EXTERNAL_STATUS)(Header->ClassEventData);
458         DEVICE_EVENT_EXTERNAL_REQUEST externalData = {0};
459 
460         // unfortunately, due to time constraints, we will only notify
461         // about keys being pressed, and not released.  this makes keys
462         // single-function, but simplifies the code significantly.
463         if (externalInfo->ExternalEvent != NOTIFICATION_EXTERNAL_EVENT_BUTTON_DOWN)
464         {
465             break;
466         }
467 
468         TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
469                    "GESN::EXTERNAL: Event: %x Status %x Req %x\n",
470                    externalInfo->ExternalEvent, externalInfo->ExternalStatus,
471                    (externalInfo->Request[0] << 8) | externalInfo->Request[1]
472                    ));
473 
474         externalData.Version = 1;
475         externalData.DeviceClass = 0;
476         externalData.ButtonStatus = externalInfo->ExternalEvent;
477         externalData.Request = (externalInfo->Request[0] << 8) |
478                                (externalInfo->Request[1] & 0xff);
479         KeQuerySystemTime(&(externalData.SystemTime));
480         externalData.SystemTime.QuadPart *= (LONGLONG)KeQueryTimeIncrement();
481 
482         DeviceSendNotification(DeviceExtension,
483                                &GUID_IO_DEVICE_EXTERNAL_REQUEST,
484                                sizeof(DEVICE_EVENT_EXTERNAL_REQUEST),
485                                &externalData);
486 
487         return status;
488     }
489 
490     case NOTIFICATION_MEDIA_STATUS_CLASS_EVENTS:  // 0x4
491     {
492         PNOTIFICATION_MEDIA_STATUS mediaInfo =
493                                     (PNOTIFICATION_MEDIA_STATUS)(Header->ClassEventData);
494 
495         if ((mediaInfo->MediaEvent == NOTIFICATION_MEDIA_EVENT_NEW_MEDIA) ||
496             (mediaInfo->MediaEvent == NOTIFICATION_MEDIA_EVENT_MEDIA_CHANGE))
497         {
498             TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
499                        "GESN::MEDIA ARRIVAL, Status %x\n",
500                        mediaInfo->MediaStatus));
501 
502             if (IsVolumeMounted(DeviceExtension->DeviceObject))
503             {
504                 SET_FLAG(DeviceExtension->DeviceObject->Flags, DO_VERIFY_VOLUME);
505             }
506             DeviceSetMediaChangeStateEx(DeviceExtension,
507                                         MediaPresent,
508                                         NULL);
509 
510             // If media is inserted into slot loading type, mark the device active
511             // to not power off.
512             if ((DeviceExtension->ZeroPowerODDInfo != NULL) &&
513                 (DeviceExtension->ZeroPowerODDInfo->LoadingMechanism == LOADING_MECHANISM_CADDY) &&
514                 (DeviceExtension->ZeroPowerODDInfo->Load == 0))                                     // Slot
515             {
516                 TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER,
517                            "GesnDataInterpret: MediaArrival event detected, device marked as active\n"));
518 
519                 DeviceMarkActive(DeviceExtension, TRUE, FALSE);
520             }
521         }
522         else if (mediaInfo->MediaEvent == NOTIFICATION_MEDIA_EVENT_MEDIA_REMOVAL)
523         {
524             TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
525                        "GESN::MEDIA REMOVAL, Status %x\n",
526                        mediaInfo->MediaStatus));
527 
528             DeviceSetMediaChangeStateEx(DeviceExtension,
529                                         MediaNotPresent,
530                                         NULL);
531 
532             // If media is removed from slot loading type, start powering off the device
533             // if it is ZPODD capable.
534             if ((DeviceExtension->ZeroPowerODDInfo != NULL) &&
535                 (DeviceExtension->ZeroPowerODDInfo->LoadingMechanism == LOADING_MECHANISM_CADDY) &&
536                 (DeviceExtension->ZeroPowerODDInfo->Load == 0))                                    // Slot
537             {
538                 TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER,
539                            "GesnDataInterpret: MediaRemoval event detected, device marked as idle\n"));
540 
541                 DeviceMarkActive(DeviceExtension, FALSE, FALSE);
542             }
543         }
544         else if (mediaInfo->MediaEvent == NOTIFICATION_MEDIA_EVENT_EJECT_REQUEST)
545         {
546             TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
547                        "GESN::MEDIA EJECTION, Status %x\n",
548                        mediaInfo->MediaStatus));
549 
550             DeviceSendNotification(DeviceExtension,
551                                    &GUID_IO_MEDIA_EJECT_REQUEST,
552                                    0,
553                                    NULL);
554         }
555 
556         break;
557     }
558 
559     case NOTIFICATION_DEVICE_BUSY_CLASS_EVENTS:  // lowest priority events...
560     {
561         PNOTIFICATION_BUSY_STATUS busyInfo =
562                                     (PNOTIFICATION_BUSY_STATUS)(Header->ClassEventData);
563         DEVICE_EVENT_BECOMING_READY busyData = {0};
564 
565         // else we want to report the approximated time till it's ready.
566         busyData.Version = 1;
567         busyData.Reason = busyInfo->DeviceBusyStatus;
568         busyData.Estimated100msToReady = (busyInfo->Time[0] << 8) |
569                                          (busyInfo->Time[1] & 0xff);
570 
571         TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
572                    "GESN::BUSY: Event: %x Status %x Time %x\n",
573                    busyInfo->DeviceBusyEvent, busyInfo->DeviceBusyStatus,
574                    busyData.Estimated100msToReady
575                    ));
576 
577         // Ignore the notification if the time is small
578         if (busyData.Estimated100msToReady >= GESN_DEVICE_BUSY_LOWER_THRESHOLD_100_MS)
579         {
580             TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
581                        "GesnDataInterpret: media BECOMING_READY\n"));
582 
583             DeviceSendNotification(DeviceExtension,
584                                    &GUID_IO_DEVICE_BECOMING_READY,
585                                    sizeof(DEVICE_EVENT_BECOMING_READY),
586                                    &busyData);
587         }
588 
589         // If manual loading operation is observed for slot loading type, start powering off the device
590         // if it is ZPODD capable.
591         if ((DeviceExtension->ZeroPowerODDInfo != NULL) &&
592             (DeviceExtension->ZeroPowerODDInfo->LoadingMechanism == LOADING_MECHANISM_TRAY) &&
593             (DeviceExtension->ZeroPowerODDInfo->Load == 0) &&                                   // Drawer
594             (busyInfo->DeviceBusyEvent == NOTIFICATION_BUSY_EVENT_LO_CHANGE) &&
595             (busyInfo->DeviceBusyStatus == NOTIFICATION_BUSY_STATUS_NO_EVENT))
596         {
597             inHomePosition = DeviceZPODDIsInHomePosition(DeviceExtension);
598 
599             if (inHomePosition == FALSE)
600             {
601                 TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER,
602                            "GesnDataInterpret: LoChange event detected, device marked as active\n"));
603 
604                 DeviceMarkActive(DeviceExtension, TRUE, FALSE);
605             }
606             else
607             {
608                 TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER,
609                            "GesnDataInterpret: LoChange event detected, device marked as idle\n"));
610 
611                 DeviceMarkActive(DeviceExtension, FALSE, FALSE);
612             }
613         }
614 
615         break;
616     }
617 
618     default:
619     {
620         break;
621     }
622 
623     } // end switch on notification class
624 
625     return status;
626 }
627 
628 
629 VOID
630 DeviceInternalSetMediaChangeState(
631     _In_        PCDROM_DEVICE_EXTENSION       DeviceExtension,
632     _In_        MEDIA_CHANGE_DETECTION_STATE  NewState,
633     _Inout_opt_ PMEDIA_CHANGE_DETECTION_STATE OldState
634     )
635 /*++
636 
637 Routine Description:
638 
639     This routine will (if appropriate) set the media change event for the
640     device.  The event will be set if the media state is changed and
641     media change events are enabled.  Otherwise the media state will be
642     tracked but the event will not be set.
643 
644     This routine will lock out the other media change routines if possible
645     but if not a media change notification may be lost after the enable has
646     been completed.
647 
648 Arguments:
649 
650     DeviceExtension - the device extension
651 
652     NewState - new state for setting
653 
654     OldState - optional storage for the old state
655 
656 Return Value:
657 
658     none
659 
660 --*/
661 {
662 #if DBG
663     LPCSTR states[] = {"Unknown", "Present", "Not Present", "Unavailable"};
664 #endif
665     MEDIA_CHANGE_DETECTION_STATE oldMediaState;
666     PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo;
667     CLASS_MEDIA_CHANGE_CONTEXT   mcnContext;
668 
669     if (!((NewState >= MediaUnknown) && (NewState <= MediaUnavailable)))
670     {
671         return;
672     }
673 
674     if (info == NULL)
675     {
676         return;
677     }
678 
679     oldMediaState = info->LastKnownMediaDetectionState;
680     if (OldState)
681     {
682         *OldState = oldMediaState;
683     }
684 
685     info->LastKnownMediaDetectionState = NewState;
686 
687     // Increment MediaChangeCount on transition to MediaPresent
688     if (NewState == MediaPresent && oldMediaState != NewState)
689     {
690         InterlockedIncrement((PLONG)&DeviceExtension->MediaChangeCount);
691     }
692 
693     if (info->MediaChangeDetectionDisableCount != 0)
694     {
695 #if DBG
696         TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
697                     "DeviceInternalSetMediaChangeState: MCN not enabled, state "
698                     "changed from %s to %s\n",
699                     states[oldMediaState], states[NewState]));
700 #endif
701         return;
702     }
703 #if DBG
704     TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
705                 "DeviceInternalSetMediaChangeState: State change from %s to %s\n",
706                 states[oldMediaState], states[NewState]));
707 #endif
708 
709     if (info->LastReportedMediaDetectionState == info->LastKnownMediaDetectionState)
710     {
711         // Media is in the same state as we reported last time, no need to report again.
712         return;
713     }
714 
715     // make the data useful -- it used to always be zero.
716     mcnContext.MediaChangeCount = DeviceExtension->MediaChangeCount;
717     mcnContext.NewState = NewState;
718 
719     if (NewState == MediaPresent)
720     {
721         TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
722                    "DeviceInternalSetMediaChangeState: Reporting media ARRIVAL\n"));
723 
724         DeviceSendNotification(DeviceExtension,
725                                &GUID_IO_MEDIA_ARRIVAL,
726                                sizeof(CLASS_MEDIA_CHANGE_CONTEXT),
727                                &mcnContext);
728     }
729     else if ((NewState == MediaNotPresent) || (NewState == MediaUnavailable))
730     {
731         TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
732                    "DeviceInternalSetMediaChangeState: Reporting media REMOVAL\n"));
733         DeviceSendNotification(DeviceExtension,
734                                &GUID_IO_MEDIA_REMOVAL,
735                                sizeof(CLASS_MEDIA_CHANGE_CONTEXT),
736                                &mcnContext);
737     }
738     else
739     {
740         // Don't notify of changed going to unknown.
741         return;
742     }
743 
744     info->LastReportedMediaDetectionState = info->LastKnownMediaDetectionState;
745 
746     return;
747 } // end DeviceInternalSetMediaChangeState()
748 
749 
750 VOID
751 DeviceSetMediaChangeStateEx(
752     _In_        PCDROM_DEVICE_EXTENSION       DeviceExtension,
753     _In_        MEDIA_CHANGE_DETECTION_STATE  NewState,
754     _Inout_opt_ PMEDIA_CHANGE_DETECTION_STATE OldState
755     )
756 /*++
757 
758 Routine Description:
759 
760     This routine will (if appropriate) set the media change event for the
761     device.  The event will be set if the media state is changed and
762     media change events are enabled.  Otherwise the media state will be
763     tracked but the event will not be set.
764 
765 Arguments:
766 
767     DeviceExtension - the device extension
768 
769     NewState - new state for setting
770 
771     OldState - optional storage for the old state
772 
773 Return Value:
774 
775     none
776 
777 --*/
778 {
779     PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo;
780     LARGE_INTEGER                zero;
781     NTSTATUS                     status;
782 
783     // timeout value must be 0, as this function can be called at DISPATCH_LEVEL.
784     zero.QuadPart = 0;
785 
786     if (info == NULL)
787     {
788         return;
789     }
790 
791     status = KeWaitForMutexObject(&info->MediaChangeMutex,
792                                 Executive,
793                                 KernelMode,
794                                 FALSE,
795                                 &zero);
796 
797     if (status == STATUS_TIMEOUT)
798     {
799         // Someone else is in the process of setting the media state.
800         return;
801     }
802 
803     // Change the media present state and signal an event, if applicable
804     DeviceInternalSetMediaChangeState(DeviceExtension, NewState, OldState);
805 
806     KeReleaseMutex(&info->MediaChangeMutex, FALSE);
807 
808     return;
809 } // end DeviceSetMediaChangeStateEx()
810 
811 
812 _IRQL_requires_max_(APC_LEVEL)
813 VOID
814 DeviceSendDelayedMediaChangeNotifications(
815     _In_ PCDROM_DEVICE_EXTENSION DeviceExtension
816     )
817 /*++
818 
819 Routine Description:
820 
821     This routine sends the so-called delayed media change notifications.
822     These notifications get accumulated while the MCN mechanism is disabled
823     and need to be sent to the application on MCN enabling, if MCN enabling
824     happens as a part of exclusive access unlock and the application has not
825     requested us explicitly to not send the delayed notifications.
826 
827 Arguments:
828 
829     DeviceExtension - the device extension
830 
831 Return Value:
832 
833     none
834 
835 --*/
836 {
837     PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo;
838     LARGE_INTEGER                zero;
839     NTSTATUS                     status;
840 
841     PAGED_CODE();
842 
843     zero.QuadPart = 0;
844 
845     if (info == NULL)
846     {
847         return;
848     }
849 
850     status = KeWaitForMutexObject(&info->MediaChangeMutex,
851                                   Executive,
852                                   KernelMode,
853                                   FALSE,
854                                   &zero);
855 
856     if (status == STATUS_TIMEOUT)
857     {
858         // Someone else is in the process of setting the media state.
859         // That's totally okay, we'll send delayed notifications later.
860         return;
861     }
862 
863     // If the last reported state and the last known state are different and
864     // MCN is enabled, generate a notification based on the last known state.
865     if ((info->LastKnownMediaDetectionState != info->LastReportedMediaDetectionState) &&
866         (info->MediaChangeDetectionDisableCount == 0))
867     {
868         DeviceInternalSetMediaChangeState(DeviceExtension, info->LastKnownMediaDetectionState, NULL);
869     }
870 
871     KeReleaseMutex(&info->MediaChangeMutex, FALSE);
872 
873     return;
874 }
875 
876 
877 _IRQL_requires_max_(APC_LEVEL)
878 NTSTATUS
879 RequestSetupMcnRequest(
880     _In_ PCDROM_DEVICE_EXTENSION      DeviceExtension,
881     _In_ BOOLEAN                      UseGesn
882 )
883 /*++
884 
885 Routine Description:
886 
887     This routine sets up the fields of the request for MCN
888 
889 Arguments:
890     DeviceExtension - device context
891 
892     UseGesn - If TRUE, the device supports GESN and it's currently the mechanism for MCN
893 
894 Return Value:
895     NTSTATUS
896 
897 --*/
898 {
899     NTSTATUS                     status = STATUS_SUCCESS;
900     PSCSI_REQUEST_BLOCK          srb;
901     PIRP                         irp;
902     PIO_STACK_LOCATION           nextIrpStack;
903     PCDB                         cdb;
904     PVOID                        buffer;
905     WDF_REQUEST_REUSE_PARAMS     params;
906     PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo;
907 
908     PAGED_CODE();
909 
910     irp = WdfRequestWdmGetIrp(info->MediaChangeRequest);
911     NT_ASSERT(irp != NULL);
912 
913     // deassign the MdlAddress, this is the value we assign explicitly.
914     // this is to prevent WdfRequestReuse to release the Mdl unexpectly.
915     if (irp->MdlAddress)
916     {
917         irp->MdlAddress = NULL;
918     }
919 
920     if (NT_SUCCESS(status))
921     {
922         // Setup the IRP to perform a test unit ready.
923         WDF_REQUEST_REUSE_PARAMS_INIT(&params,
924                                       WDF_REQUEST_REUSE_NO_FLAGS,
925                                       STATUS_NOT_SUPPORTED);
926 
927         status = WdfRequestReuse(info->MediaChangeRequest, &params);
928     }
929 
930     if (NT_SUCCESS(status))
931     {
932         // Format the request.
933         status = WdfIoTargetFormatRequestForInternalIoctlOthers(DeviceExtension->IoTarget,
934                                                                 info->MediaChangeRequest,
935                                                                 IOCTL_SCSI_EXECUTE_IN,
936                                                                 NULL, NULL,
937                                                                 NULL, NULL,
938                                                                 NULL, NULL);
939 
940         if (!NT_SUCCESS(status))
941         {
942             TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
943                        "RequestSetupMcnRequest: WdfIoTargetFormatRequestForInternalIoctlOthers failed, %!STATUS!\n",
944                        status));
945         }
946     }
947 
948     if (NT_SUCCESS(status))
949     {
950         RequestClearSendTime(info->MediaChangeRequest);
951 
952         nextIrpStack = IoGetNextIrpStackLocation(irp);
953 
954         nextIrpStack->Flags = SL_OVERRIDE_VERIFY_VOLUME;
955         nextIrpStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
956         nextIrpStack->Parameters.Scsi.Srb = &(info->MediaChangeSrb);
957 
958         // Prepare the SRB for execution.
959         srb    = nextIrpStack->Parameters.Scsi.Srb;
960         buffer = info->SenseBuffer;
961         RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK));
962         RtlZeroMemory(buffer, SENSE_BUFFER_SIZE);
963 
964         srb->QueueTag        = SP_UNTAGGED;
965         srb->QueueAction     = SRB_SIMPLE_TAG_REQUEST;
966         srb->Length          = sizeof(SCSI_REQUEST_BLOCK);
967         srb->Function        = SRB_FUNCTION_EXECUTE_SCSI;
968         srb->SenseInfoBuffer = buffer;
969         srb->SrbStatus       = 0;
970         srb->ScsiStatus      = 0;
971         srb->OriginalRequest = irp;
972         srb->SenseInfoBufferLength = SENSE_BUFFER_SIZE;
973 
974         srb->SrbFlags        = DeviceExtension->SrbFlags;
975         SET_FLAG(srb->SrbFlags, info->SrbFlags);
976 
977         if (!UseGesn)
978         {
979             srb->TimeOutValue = CDROM_TEST_UNIT_READY_TIMEOUT;
980             srb->CdbLength = 6;
981             srb->DataTransferLength = 0;
982             SET_FLAG(srb->SrbFlags, SRB_FLAGS_NO_DATA_TRANSFER);
983             nextIrpStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_SCSI_EXECUTE_NONE;
984             srb->DataBuffer = NULL;
985             srb->DataTransferLength = 0;
986             irp->MdlAddress = NULL;
987 
988             cdb = (PCDB) &srb->Cdb[0];
989             cdb->CDB6GENERIC.OperationCode = SCSIOP_TEST_UNIT_READY;
990         }
991         else
992         {
993             NT_ASSERT(info->Gesn.Buffer);
994 
995             srb->TimeOutValue = GESN_TIMEOUT_VALUE; // much shorter timeout for GESN
996 
997             srb->CdbLength = 10;
998             SET_FLAG(srb->SrbFlags, SRB_FLAGS_DATA_IN);
999             nextIrpStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_SCSI_EXECUTE_IN;
1000             srb->DataBuffer = info->Gesn.Buffer;
1001             srb->DataTransferLength = info->Gesn.BufferSize;
1002             irp->MdlAddress = info->Gesn.Mdl;
1003 
1004             cdb = (PCDB) &srb->Cdb[0];
1005             cdb->GET_EVENT_STATUS_NOTIFICATION.OperationCode = SCSIOP_GET_EVENT_STATUS;
1006             cdb->GET_EVENT_STATUS_NOTIFICATION.Immediate = 1;
1007             cdb->GET_EVENT_STATUS_NOTIFICATION.EventListLength[0] = (UCHAR)((info->Gesn.BufferSize) >> 8);
1008             cdb->GET_EVENT_STATUS_NOTIFICATION.EventListLength[1] = (UCHAR)((info->Gesn.BufferSize) & 0xff);
1009             cdb->GET_EVENT_STATUS_NOTIFICATION.NotificationClassRequest = info->Gesn.EventMask;
1010         }
1011     }
1012 
1013     return status;
1014 }
1015 
1016 
1017 VOID
1018 NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
1019 DeviceDisableGesn(
1020     _In_ WDFWORKITEM  WorkItem
1021     )
1022 /*++
1023 
1024 Routine Description:
1025 
1026     Work item routine to set the hack flag in the registry to disable GESN
1027     This routine is invoked when the device reports TOO many events that affects system
1028 
1029 Arguments:
1030     WorkItem - the work item be perfromed.
1031 
1032 Return Value:
1033     None
1034 
1035 --*/
1036 {
1037     WDFDEVICE               device = WdfWorkItemGetParentObject(WorkItem);
1038     PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(device);
1039 
1040     PAGED_CODE();
1041 
1042     //
1043     // Set the hack flag in the registry
1044     //
1045     DeviceSetParameter(deviceExtension,
1046                        CLASSP_REG_SUBKEY_NAME,
1047                        CLASSP_REG_MMC_DETECTION_VALUE_NAME,
1048                        CdromDetectionUnsupported);
1049 
1050     WdfObjectDelete(WorkItem);
1051 
1052     return;
1053 }
1054 
1055 _IRQL_requires_max_(APC_LEVEL)
1056 BOOLEAN
1057 RequestPostWorkMcnRequest(
1058     _In_ PCDROM_DEVICE_EXTENSION      DeviceExtension
1059     )
1060 /*++
1061 
1062 Routine Description:
1063 
1064     This routine handles the completion of the test unit ready irps used to
1065     determine if the media has changed.  If the media has changed, this code
1066     signals the named event to wake up other system services that react to
1067     media change (aka AutoPlay).
1068 
1069 Arguments:
1070 
1071     DeviceExtension - the device context
1072 
1073 Return Value:
1074 
1075     BOOLEAN - TRUE (needs retry); FALSE (shoule not retry)
1076 
1077 --*/
1078 {
1079     NTSTATUS                        status = STATUS_SUCCESS;
1080     PMEDIA_CHANGE_DETECTION_INFO    info = DeviceExtension->MediaChangeDetectionInfo;
1081     PIRP                            irp;
1082     BOOLEAN                         retryImmediately = FALSE;
1083 
1084     PAGED_CODE();
1085 
1086     NT_ASSERT(info->MediaChangeRequest != NULL);
1087     irp = WdfRequestWdmGetIrp(info->MediaChangeRequest);
1088 
1089     NT_ASSERT(!TEST_FLAG(info->MediaChangeSrb.SrbStatus, SRB_STATUS_QUEUE_FROZEN));
1090 
1091     // use InterpretSenseInfo routine to check for media state, and also
1092     // to call ClassError() with correct parameters.
1093     if (SRB_STATUS(info->MediaChangeSrb.SrbStatus) != SRB_STATUS_SUCCESS)
1094     {
1095         TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN, "MCN request - failed - srb status=%x, sense=%x/%x/%x.\n",
1096                    info->MediaChangeSrb.SrbStatus,
1097                    ((PSENSE_DATA)(info->MediaChangeSrb.SenseInfoBuffer))->SenseKey,
1098                    ((PSENSE_DATA)(info->MediaChangeSrb.SenseInfoBuffer))->AdditionalSenseCode,
1099                    ((PSENSE_DATA)(info->MediaChangeSrb.SenseInfoBuffer))->AdditionalSenseCodeQualifier));
1100 
1101         if (SRB_STATUS(info->MediaChangeSrb.SrbStatus) != SRB_STATUS_NOT_POWERED)
1102         {
1103             // Release the queue if it is frozen.
1104             if (info->MediaChangeSrb.SrbStatus & SRB_STATUS_QUEUE_FROZEN)
1105             {
1106                 DeviceReleaseQueue(DeviceExtension->Device);
1107             }
1108 
1109             RequestSenseInfoInterpret(DeviceExtension,
1110                                       info->MediaChangeRequest,
1111                                       &info->MediaChangeSrb,
1112                                       0,
1113                                       &status,
1114                                       NULL);
1115         }
1116     }
1117     else
1118     {
1119         DeviceExtension->PrivateFdoData->LoggedTURFailureSinceLastIO = FALSE;
1120 
1121         if (!info->Gesn.Supported)
1122         {
1123             TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
1124                        "MCN request - succeeded (GESN NOT supported, setting MediaPresent).\n"));
1125 
1126             // success != media for GESN case
1127             DeviceSetMediaChangeStateEx(DeviceExtension,
1128                                         MediaPresent,
1129                                         NULL);
1130         }
1131         else
1132         {
1133             TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN,
1134                        "MCN request - succeeded (GESN supported).\n"));
1135         }
1136     }
1137 
1138     if (info->Gesn.Supported)
1139     {
1140         if (status == STATUS_DATA_OVERRUN)
1141         {
1142             TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "MCN Request - Data Overrun\n"));
1143             status = STATUS_SUCCESS;
1144         }
1145 
1146         if (!NT_SUCCESS(status))
1147         {
1148             TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "MCN Request: GESN failed with status %x\n", status));
1149         }
1150         else
1151         {
1152             // for GESN, need to interpret the results of the data.
1153             // this may also require an immediate retry
1154             if (irp->IoStatus.Information == 8 )
1155             {
1156                 GesnDataInterpret(DeviceExtension,
1157                                   (PVOID)info->Gesn.Buffer,
1158                                   &retryImmediately);
1159             }
1160 
1161         } // end of NT_SUCCESS(status)
1162 
1163     } // end of Info->Gesn.Supported
1164 
1165     // free port-allocated sense buffer, if any.
1166     if (PORT_ALLOCATED_SENSE(DeviceExtension, &info->MediaChangeSrb))
1167     {
1168         FREE_PORT_ALLOCATED_SENSE_BUFFER(DeviceExtension, &info->MediaChangeSrb);
1169     }
1170 
1171     // Remember the IRP and SRB for use the next time.
1172     NT_ASSERT(IoGetNextIrpStackLocation(irp));
1173     IoGetNextIrpStackLocation(irp)->Parameters.Scsi.Srb = &info->MediaChangeSrb;
1174 
1175     // run a sanity check to make sure we're not recursing continuously
1176     if (retryImmediately)
1177     {
1178         info->MediaChangeRetryCount++;
1179 
1180         if (info->MediaChangeRetryCount > MAXIMUM_IMMEDIATE_MCN_RETRIES)
1181         {
1182             // Disable GESN on this device.
1183             // Create a work item to set the value in the registry
1184             WDF_OBJECT_ATTRIBUTES   attributes;
1185             WDF_WORKITEM_CONFIG     workitemConfig;
1186             WDFWORKITEM             workItem;
1187 
1188             WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
1189             attributes.ParentObject = DeviceExtension->Device;
1190 
1191             WDF_WORKITEM_CONFIG_INIT(&workitemConfig, DeviceDisableGesn);
1192             workitemConfig.AutomaticSerialization = FALSE;
1193 
1194             status = WdfWorkItemCreate(&workitemConfig,
1195                                        &attributes,
1196                                        &workItem);
1197 
1198             TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "MCN Request: Disabling GESN for WDFDEVICE %p\n", DeviceExtension->Device));
1199 
1200             if (NT_SUCCESS(status))
1201             {
1202                 WdfWorkItemEnqueue(workItem);
1203             }
1204 
1205             info->Gesn.Supported  = FALSE;
1206             info->Gesn.EventMask  = 0;
1207             info->Gesn.BufferSize = 0;
1208             info->MediaChangeRetryCount = 0;
1209             retryImmediately = FALSE;
1210             // should we log an error in event log?
1211         }
1212     }
1213     else
1214     {
1215         info->MediaChangeRetryCount = 0;
1216     }
1217 
1218     return retryImmediately;
1219 }
1220 
1221 
1222 _IRQL_requires_max_(APC_LEVEL)
1223 BOOLEAN
1224 RequestSendMcnRequest(
1225     _In_ PCDROM_DEVICE_EXTENSION  DeviceExtension
1226     )
1227 /*++
1228 
1229 Routine Description:
1230 
1231     This routine sends the formatted MCN request sychronizely to lower driver.
1232 
1233 Arguments:
1234 
1235     DeviceExtension - the device context
1236 
1237 Return Value:
1238     BOOLEAN - TRUE (requst successfully sent); FALSE (request failed to send)
1239 
1240 --*/
1241 {
1242     BOOLEAN                      requestSent = FALSE;
1243     PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo;
1244 
1245     PAGED_CODE();
1246 
1247     RequestSend(DeviceExtension,
1248                 info->MediaChangeRequest,
1249                 DeviceExtension->IoTarget,
1250                 WDF_REQUEST_SEND_OPTION_SYNCHRONOUS,
1251                 &requestSent);
1252 
1253     return requestSent;
1254 } // end RequestSendMcnRequest()
1255 
1256 
1257 
1258 _IRQL_requires_max_(PASSIVE_LEVEL)
1259 NTSTATUS
1260 DeviceInitializeMcn(
1261     _In_ PCDROM_DEVICE_EXTENSION  DeviceExtension,
1262     _In_ BOOLEAN                  AllowDriveToSleep
1263     )
1264 /*++
1265 
1266 Routine Description:
1267 
1268     This routine initialize the contents of MCN structure.
1269 
1270 Arguments:
1271 
1272     DeviceExtension - the device extension
1273 
1274     AllowDriveToSleep - for CDROM, this parameter should be always FALSE
1275 
1276 Return Value:
1277     NTSTATUS
1278 
1279 --*/
1280 {
1281     NTSTATUS                        status = STATUS_SUCCESS;
1282     PMEDIA_CHANGE_DETECTION_INFO    mediaChangeInfo = NULL;
1283     PIRP                            irp = NULL;
1284     PVOID                           senseBuffer = NULL;
1285     WDF_OBJECT_ATTRIBUTES           attributes;
1286 
1287     PAGED_CODE();
1288 
1289     if (DeviceExtension->MediaChangeDetectionInfo != NULL)
1290     {
1291         //Already initialized.
1292         return STATUS_SUCCESS;
1293     }
1294 
1295     DeviceExtension->KernelModeMcnContext.FileObject      = (PVOID)-1;
1296     DeviceExtension->KernelModeMcnContext.DeviceObject    = (PVOID)-1;
1297     DeviceExtension->KernelModeMcnContext.LockCount       = 0;
1298     DeviceExtension->KernelModeMcnContext.McnDisableCount = 0;
1299 
1300     mediaChangeInfo = ExAllocatePoolWithTag(NonPagedPoolNx,
1301                                             sizeof(MEDIA_CHANGE_DETECTION_INFO),
1302                                             CDROM_TAG_MEDIA_CHANGE_DETECTION);
1303 
1304     if (mediaChangeInfo == NULL)
1305     {
1306         status = STATUS_INSUFFICIENT_RESOURCES;
1307     }
1308     else
1309     {
1310         RtlZeroMemory(mediaChangeInfo, sizeof(MEDIA_CHANGE_DETECTION_INFO));
1311     }
1312 
1313     if (NT_SUCCESS(status))
1314     {
1315         if ((DeviceExtension->PowerDescriptor != NULL) &&
1316             (DeviceExtension->PowerDescriptor->AsynchronousNotificationSupported != FALSE) &&
1317             (!TEST_FLAG(DeviceExtension->PrivateFdoData->HackFlags, FDO_HACK_NO_ASYNCHRONOUS_NOTIFICATION)))
1318         {
1319             mediaChangeInfo->AsynchronousNotificationSupported = TRUE;
1320         }
1321     }
1322 
1323     //  Allocate an IRP to carry the IOCTL_MCN_SYNC_FAKE_IOCTL.
1324     if (NT_SUCCESS(status))
1325     {
1326         irp = IoAllocateIrp(DeviceExtension->DeviceObject->StackSize, FALSE);
1327 
1328         if (irp == NULL)
1329         {
1330             status = STATUS_INSUFFICIENT_RESOURCES;
1331         }
1332     }
1333 
1334     if (NT_SUCCESS(status))
1335     {
1336         WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes,
1337                                                 CDROM_REQUEST_CONTEXT);
1338         attributes.ParentObject = DeviceExtension->Device;
1339         status = WdfRequestCreate(&attributes,
1340                                   DeviceExtension->IoTarget,
1341                                   &mediaChangeInfo->MediaChangeRequest);
1342     }
1343 
1344     if (NT_SUCCESS(status))
1345     {
1346         // Preformat the media change request. With this being done, we never need to worry about
1347         // WdfIoTargetFormatRequestForInternalIoctlOthers ever failing later.
1348         status = WdfIoTargetFormatRequestForInternalIoctlOthers(DeviceExtension->IoTarget,
1349                                                                 mediaChangeInfo->MediaChangeRequest,
1350                                                                 IOCTL_SCSI_EXECUTE_IN,
1351                                                                 NULL, NULL,
1352                                                                 NULL, NULL,
1353                                                                 NULL, NULL);
1354     }
1355 
1356     if (NT_SUCCESS(status))
1357     {
1358         senseBuffer = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned,
1359                                             SENSE_BUFFER_SIZE,
1360                                             CDROM_TAG_MEDIA_CHANGE_DETECTION);
1361         if (senseBuffer == NULL)
1362         {
1363             status = STATUS_INSUFFICIENT_RESOURCES;
1364         }
1365     }
1366 
1367     if (NT_SUCCESS(status))
1368     {
1369         mediaChangeInfo->MediaChangeSyncIrp = irp;
1370         mediaChangeInfo->SenseBuffer = senseBuffer;
1371 
1372         // Set default values for the media change notification
1373         // configuration.
1374         mediaChangeInfo->MediaChangeDetectionDisableCount = 0;
1375 
1376         // Assume that there is initially no media in the device
1377         // only notify upper layers if there is something there
1378         mediaChangeInfo->LastKnownMediaDetectionState = MediaUnknown;
1379         mediaChangeInfo->LastReportedMediaDetectionState = MediaUnknown;
1380 
1381         // setup all extra flags we'll be setting for this irp
1382         mediaChangeInfo->SrbFlags = 0;
1383 
1384         SET_FLAG(mediaChangeInfo->SrbFlags, SRB_CLASS_FLAGS_LOW_PRIORITY);
1385         SET_FLAG(mediaChangeInfo->SrbFlags, SRB_FLAGS_NO_QUEUE_FREEZE);
1386         SET_FLAG(mediaChangeInfo->SrbFlags, SRB_FLAGS_DISABLE_SYNCH_TRANSFER);
1387 
1388         if (AllowDriveToSleep)  //FALSE for CD/DVD devices
1389         {
1390             SET_FLAG(mediaChangeInfo->SrbFlags, SRB_FLAGS_NO_KEEP_AWAKE);
1391         }
1392 
1393         KeInitializeMutex(&mediaChangeInfo->MediaChangeMutex, 0x100);
1394 
1395         // It is ok to support media change events on this device.
1396         DeviceExtension->MediaChangeDetectionInfo = mediaChangeInfo;
1397 
1398         // check the device supports GESN or not, initialize GESN structure if it supports.
1399         {
1400             // This is only valid for type5 devices.
1401             NTSTATUS tempStatus = STATUS_SUCCESS;
1402 
1403             TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
1404                        "DeviceInitializeMcn: Testing for GESN\n"));
1405             tempStatus = DeviceInitializeGesn(DeviceExtension);
1406 
1407             if (NT_SUCCESS(tempStatus))
1408             {
1409                 TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
1410                            "DeviceInitializeMcn: GESN available for %p\n",
1411                            DeviceExtension->DeviceObject));
1412                 NT_ASSERT(mediaChangeInfo->Gesn.Supported );
1413                 NT_ASSERT(mediaChangeInfo->Gesn.Buffer     != NULL);
1414                 NT_ASSERT(mediaChangeInfo->Gesn.BufferSize != 0);
1415                 NT_ASSERT(mediaChangeInfo->Gesn.EventMask  != 0);
1416             }
1417             else
1418             {
1419                 TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
1420                            "DeviceInitializeMcn: GESN *NOT* available for %p\n",
1421                            DeviceExtension->DeviceObject));
1422                 NT_ASSERT(!mediaChangeInfo->Gesn.Supported);
1423                 NT_ASSERT(mediaChangeInfo->Gesn.Buffer == NULL);
1424                 NT_ASSERT(mediaChangeInfo->Gesn.BufferSize == 0);
1425                 NT_ASSERT(mediaChangeInfo->Gesn.EventMask  == 0);
1426                 mediaChangeInfo->Gesn.Supported = FALSE; // just in case....
1427             }
1428         }
1429     }
1430 
1431     if (NT_SUCCESS(status))
1432     {
1433         // Register for display state change on AOAC capable systems so we can put the
1434         // device to low power state when not required.
1435         if (mediaChangeInfo->DisplayStateCallbackHandle == NULL)
1436         {
1437             POWER_PLATFORM_INFORMATION PlatformInfo = {0};
1438 
1439             status = ZwPowerInformation(PlatformInformation,
1440                                         NULL,
1441                                         0,
1442                                         &PlatformInfo,
1443                                         sizeof(PlatformInfo));
1444 
1445             if (NT_SUCCESS(status) && PlatformInfo.AoAc)
1446             {
1447                 PoRegisterPowerSettingCallback(DeviceExtension->DeviceObject,
1448                                                &GUID_CONSOLE_DISPLAY_STATE,
1449                                                &DevicePowerSettingCallback,
1450                                                DeviceExtension,
1451                                                &mediaChangeInfo->DisplayStateCallbackHandle);
1452             }
1453 
1454             // Ignore any failures above.
1455             status = STATUS_SUCCESS;
1456         }
1457     }
1458 
1459     if (!NT_SUCCESS(status))
1460     {
1461         if (irp != NULL)
1462         {
1463             IoFreeIrp(irp);
1464         }
1465         FREE_POOL(senseBuffer);
1466         FREE_POOL(mediaChangeInfo);
1467     }
1468 
1469     return status;
1470 
1471 } // end DeviceInitializeMcn()
1472 
1473 
1474 _IRQL_requires_max_(PASSIVE_LEVEL)
1475 NTSTATUS
1476 DeviceInitializeGesn(
1477     _In_ PCDROM_DEVICE_EXTENSION  DeviceExtension
1478     )
1479 /*++
1480 
1481 Routine Description:
1482 
1483     This routine initialize the contents of GESN structure.
1484 
1485 Arguments:
1486 
1487     DeviceExtension - the device extension
1488 
1489 Return Value:
1490     NTSTATUS
1491 
1492 --*/
1493 {
1494     NTSTATUS                            status = STATUS_SUCCESS;
1495     PNOTIFICATION_EVENT_STATUS_HEADER   header = NULL;
1496     CDROM_DETECTION_STATE               detectionState = CdromDetectionUnknown;
1497     PSTORAGE_DEVICE_DESCRIPTOR          deviceDescriptor = DeviceExtension->DeviceDescriptor;
1498     BOOLEAN                             retryImmediately = TRUE;
1499     ULONG                               i = 0;
1500     ULONG                               atapiResets = 0;
1501     PMEDIA_CHANGE_DETECTION_INFO        info = DeviceExtension->MediaChangeDetectionInfo;
1502 
1503     PAGED_CODE();
1504 
1505     NT_ASSERT(info != NULL);
1506 
1507     // read if we already know the abilities of the device
1508     DeviceGetParameter(DeviceExtension,
1509                        CLASSP_REG_SUBKEY_NAME,
1510                        CLASSP_REG_MMC_DETECTION_VALUE_NAME,
1511                        (PULONG)&detectionState);
1512 
1513     if (detectionState == CdromDetectionUnsupported)
1514     {
1515         status = STATUS_NOT_SUPPORTED;
1516     }
1517 
1518     // check if the device has a hack flag saying never to try this.
1519     if (NT_SUCCESS(status) &&
1520         (TEST_FLAG(DeviceExtension->PrivateFdoData->HackFlags, FDO_HACK_GESN_IS_BAD)) )
1521     {
1522         DeviceSetParameter(DeviceExtension,
1523                            CLASSP_REG_SUBKEY_NAME,
1524                            CLASSP_REG_MMC_DETECTION_VALUE_NAME,
1525                            CdromDetectionUnsupported);
1526         status = STATUS_NOT_SUPPORTED;
1527     }
1528 
1529     // else go through the process since we allocate buffers and
1530     // get all sorts of device settings.
1531     if (NT_SUCCESS(status))
1532     {
1533         if (info->Gesn.Buffer == NULL)
1534         {
1535             info->Gesn.Buffer = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned,
1536                                                       GESN_BUFFER_SIZE,
1537                                                       CDROM_TAG_GESN);
1538         }
1539 
1540         if (info->Gesn.Buffer == NULL)
1541         {
1542             status = STATUS_INSUFFICIENT_RESOURCES;
1543         }
1544     }
1545 
1546     if (NT_SUCCESS(status))
1547     {
1548         if (info->Gesn.Mdl != NULL)
1549         {
1550             IoFreeMdl(info->Gesn.Mdl);
1551         }
1552 
1553         info->Gesn.Mdl = IoAllocateMdl(info->Gesn.Buffer,
1554                                        GESN_BUFFER_SIZE,
1555                                        FALSE,
1556                                        FALSE,
1557                                        NULL);
1558         if (info->Gesn.Mdl == NULL)
1559         {
1560             status = STATUS_INSUFFICIENT_RESOURCES;
1561         }
1562     }
1563 
1564     if (NT_SUCCESS(status))
1565     {
1566         MmBuildMdlForNonPagedPool(info->Gesn.Mdl);
1567         info->Gesn.BufferSize = GESN_BUFFER_SIZE;
1568         info->Gesn.EventMask = 0;
1569 
1570         // all items are prepared to use GESN (except the event mask, so don't
1571         // optimize this part out!).
1572         //
1573         // now see if it really works. we have to loop through this because
1574         // many SAMSUNG (and one COMPAQ) drives timeout when requesting
1575         // NOT_READY events, even when the IMMEDIATE bit is set. :(
1576         //
1577         // using a drive list is cumbersome, so this might fix the problem.
1578         for (i = 0; (i < 16) && retryImmediately; i++)
1579         {
1580             status = RequestSetupMcnRequest(DeviceExtension, TRUE);
1581 
1582             if (!NT_SUCCESS(status))
1583             {
1584                 TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN,
1585                            "Setup Mcn request failed %x for WDFDEVICE %p\n",
1586                            status, DeviceExtension->Device));
1587                 break;
1588             }
1589 
1590             NT_ASSERT(TEST_FLAG(info->MediaChangeSrb.SrbFlags, SRB_FLAGS_NO_QUEUE_FREEZE));
1591 
1592             status = DeviceSendRequestSynchronously(DeviceExtension->Device, info->MediaChangeRequest, TRUE);
1593 
1594             if (SRB_STATUS(info->MediaChangeSrb.SrbStatus) != SRB_STATUS_SUCCESS)
1595             {
1596                 // Release the queue if it is frozen.
1597                 if (info->MediaChangeSrb.SrbStatus & SRB_STATUS_QUEUE_FROZEN)
1598                 {
1599                     DeviceReleaseQueue(DeviceExtension->Device);
1600                 }
1601 
1602                 RequestSenseInfoInterpret(DeviceExtension,
1603                                           info->MediaChangeRequest,
1604                                           &(info->MediaChangeSrb),
1605                                           0,
1606                                           &status,
1607                                           NULL);
1608             }
1609 
1610             if ((deviceDescriptor->BusType == BusTypeAtapi) &&
1611                 (info->MediaChangeSrb.SrbStatus == SRB_STATUS_BUS_RESET))
1612             {
1613                 //
1614                 // ATAPI unfortunately returns SRB_STATUS_BUS_RESET instead
1615                 // of SRB_STATUS_TIMEOUT, so we cannot differentiate between
1616                 // the two.  if we get this status four time consecutively,
1617                 // stop trying this command.  it is too late to change ATAPI
1618                 // at this point, so special-case this here. (07/10/2001)
1619                 // NOTE: any value more than 4 may cause the device to be
1620                 //       marked missing.
1621                 //
1622                 atapiResets++;
1623                 if (atapiResets >= 4)
1624                 {
1625                     status = STATUS_IO_DEVICE_ERROR;
1626                     break;
1627                 }
1628             }
1629 
1630             if (status == STATUS_DATA_OVERRUN)
1631             {
1632                 status = STATUS_SUCCESS;
1633             }
1634 
1635             if ((status == STATUS_INVALID_DEVICE_REQUEST) ||
1636                 (status == STATUS_TIMEOUT) ||
1637                 (status == STATUS_IO_DEVICE_ERROR) ||
1638                 (status == STATUS_IO_TIMEOUT))
1639             {
1640                 // with these error codes, we don't ever want to try this command
1641                 // again on this device, since it reacts poorly.
1642                 DeviceSetParameter( DeviceExtension,
1643                                     CLASSP_REG_SUBKEY_NAME,
1644                                     CLASSP_REG_MMC_DETECTION_VALUE_NAME,
1645                                     CdromDetectionUnsupported);
1646                 TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN,
1647                            "GESN test failed %x for WDFDEVICE %p\n",
1648                            status, DeviceExtension->Device));
1649                 break;
1650             }
1651 
1652             if (!NT_SUCCESS(status))
1653             {
1654                 // this may be other errors that should not disable GESN
1655                 // for all future start_device calls.
1656                 TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN,
1657                            "GESN test failed %x for WDFDEVICE %p\n",
1658                            status, DeviceExtension->Device));
1659                 break;
1660             }
1661             else if (i == 0)
1662             {
1663                 // the first time, the request was just retrieving a mask of
1664                 // available bits.  use this to mask future requests.
1665                 header = (PNOTIFICATION_EVENT_STATUS_HEADER)(info->Gesn.Buffer);
1666 
1667                 TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
1668                            "WDFDEVICE %p supports event mask %x\n",
1669                            DeviceExtension->Device, header->SupportedEventClasses));
1670 
1671                 if (TEST_FLAG(header->SupportedEventClasses,
1672                               NOTIFICATION_MEDIA_STATUS_CLASS_MASK))
1673                 {
1674                     TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
1675                                "GESN supports MCN\n"));
1676                 }
1677                 if (TEST_FLAG(header->SupportedEventClasses,
1678                               NOTIFICATION_DEVICE_BUSY_CLASS_MASK))
1679                 {
1680                     TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
1681                                "GESN supports DeviceBusy\n"));
1682                 }
1683                 if (TEST_FLAG(header->SupportedEventClasses,
1684                               NOTIFICATION_OPERATIONAL_CHANGE_CLASS_MASK))
1685                 {
1686                     if (TEST_FLAG(DeviceExtension->PrivateFdoData->HackFlags,
1687                                   FDO_HACK_GESN_IGNORE_OPCHANGE))
1688                     {
1689                         TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
1690                                     "GESN supports OpChange, but must ignore these events for compatibility\n"));
1691                         CLEAR_FLAG(header->SupportedEventClasses,
1692                                    NOTIFICATION_OPERATIONAL_CHANGE_CLASS_MASK);
1693                     }
1694                     else
1695                     {
1696                         TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
1697                                     "GESN supports OpChange\n"));
1698                     }
1699                 }
1700                 info->Gesn.EventMask = header->SupportedEventClasses;
1701 
1702                 //
1703                 // realistically, we are only considering the following events:
1704                 //    EXTERNAL REQUEST - this is being tested for play/stop/etc.
1705                 //    MEDIA STATUS - autorun and ejection requests.
1706                 //    DEVICE BUSY - to allow us to predict when media will be ready.
1707                 // therefore, we should not bother querying for the other,
1708                 // unknown events. clear all but the above flags.
1709                 //
1710                 info->Gesn.EventMask &= NOTIFICATION_OPERATIONAL_CHANGE_CLASS_MASK |
1711                                         NOTIFICATION_EXTERNAL_REQUEST_CLASS_MASK   |
1712                                         NOTIFICATION_MEDIA_STATUS_CLASS_MASK       |
1713                                         NOTIFICATION_DEVICE_BUSY_CLASS_MASK        ;
1714 
1715 
1716                 //
1717                 // HACKHACK - REF #0001
1718                 // Some devices will *never* report an event if we've also requested
1719                 // that it report lower-priority events.  this is due to a
1720                 // misunderstanding in the specification wherein a "No Change" is
1721                 // interpreted to be a real event.  what should occur is that the
1722                 // device should ignore "No Change" events when multiple event types
1723                 // are requested unless there are no other events waiting.  this
1724                 // greatly reduces the number of requests that the host must send
1725                 // to determine if an event has occurred. Since we must work on all
1726                 // drives, default to enabling the hack until we find evidence of
1727                 // proper firmware.
1728                 //
1729                 if (info->Gesn.EventMask == 0)
1730                 {
1731                     TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
1732                                "GESN supported, but not mask we care about (%x) for FDO %p\n",
1733                                header->SupportedEventClasses,
1734                                DeviceExtension->DeviceObject));
1735                     // NOTE: the status is still status_sucess.
1736                     break;
1737                 }
1738                 else if (CountOfSetBitsUChar(info->Gesn.EventMask) == 1)
1739                 {
1740                     TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
1741                                "GESN hack not required for FDO %p\n",
1742                                DeviceExtension->DeviceObject));
1743                 }
1744                 else
1745                 {
1746                     TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
1747                                "GESN hack enabled for FDO %p\n",
1748                                DeviceExtension->DeviceObject));
1749                     info->Gesn.HackEventMask = 1;
1750                 }
1751             }
1752             else
1753             {
1754                 // i > 0; not the first time looping through, so interpret the results.
1755                 status = GesnDataInterpret(DeviceExtension,
1756                                            (PVOID)info->Gesn.Buffer,
1757                                            &retryImmediately);
1758 
1759                 if (!NT_SUCCESS(status))
1760                 {
1761                     // This drive does not support GESN correctly
1762                     DeviceSetParameter( DeviceExtension,
1763                                         CLASSP_REG_SUBKEY_NAME,
1764                                         CLASSP_REG_MMC_DETECTION_VALUE_NAME,
1765                                         CdromDetectionUnsupported);
1766                     break;
1767                 }
1768             }
1769         } // end 'for' loop of GESN requests....
1770     }
1771 
1772     if (NT_SUCCESS(status))
1773     {
1774         //
1775         // we can only use this if it can be relied upon for media changes,
1776         // since we are (by definition) no longer going to be polling via
1777         // a TEST_UNIT_READY irp, and drives will not report UNIT ATTENTION
1778         // for this command (although a filter driver, such as one for burning
1779         // cd's, might still fake those errors).
1780         //
1781         // since we also rely upon NOT_READY events to change the cursor
1782         // into a "wait" cursor; GESN is still more reliable than other
1783         // methods, and includes eject button requests, so we'll use it
1784         // without DEVICE_BUSY in Windows Vista.
1785         //
1786 
1787         if (TEST_FLAG(info->Gesn.EventMask, NOTIFICATION_MEDIA_STATUS_CLASS_MASK))
1788         {
1789             TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
1790                        "Enabling GESN support for WDFDEVICE %p\n",
1791                        DeviceExtension->Device));
1792             info->Gesn.Supported = TRUE;
1793 
1794             DeviceSetParameter( DeviceExtension,
1795                                 CLASSP_REG_SUBKEY_NAME,
1796                                 CLASSP_REG_MMC_DETECTION_VALUE_NAME,
1797                                 CdromDetectionSupported);
1798         }
1799         else
1800         {
1801             TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
1802                        "GESN available but not enabled for WDFDEVICE %p\n",
1803                        DeviceExtension->Device));
1804             status = STATUS_NOT_SUPPORTED;
1805         }
1806     }
1807 
1808     if (!NT_SUCCESS(status))
1809     {
1810         TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN,
1811                    "GESN support detection failed  for WDFDEVICE %p with status %08x\n",
1812                    DeviceExtension->Device, status));
1813 
1814         if (info->Gesn.Mdl)
1815         {
1816             PIRP irp = WdfRequestWdmGetIrp(info->MediaChangeRequest);
1817 
1818             IoFreeMdl(info->Gesn.Mdl);
1819             info->Gesn.Mdl = NULL;
1820             irp->MdlAddress = NULL;
1821         }
1822 
1823         FREE_POOL(info->Gesn.Buffer);
1824         info->Gesn.Supported  = FALSE;
1825         info->Gesn.EventMask  = 0;
1826         info->Gesn.BufferSize = 0;
1827     }
1828 
1829     return status;
1830 }
1831 
1832 
1833 _IRQL_requires_max_(PASSIVE_LEVEL)
1834 NTSTATUS
1835 DeviceInitializeMediaChangeDetection(
1836     _In_ PCDROM_DEVICE_EXTENSION  DeviceExtension
1837     )
1838 /*++
1839 
1840 Routine Description:
1841 
1842     This routine checks to see if it is safe to initialize MCN (the back end
1843     to autorun) for a given device.  It will then check the device-type wide
1844     key "Autorun" in the service key (for legacy reasons), and then look in
1845     the device-specific key to potentially override that setting.
1846 
1847     If MCN is to be enabled, all neccessary structures and memory are
1848     allocated and initialized.
1849 
1850     This routine MUST be called only from the DeviceInit...() .
1851 
1852 Arguments:
1853 
1854     DeviceExtension - the device to initialize MCN for, if appropriate
1855 
1856 Return Value:
1857 
1858     NTSTATUS
1859 
1860 --*/
1861 {
1862     NTSTATUS    status = STATUS_SUCCESS;
1863 
1864     BOOLEAN     disabled = FALSE;
1865     BOOLEAN     instanceOverride;
1866 
1867     PAGED_CODE();
1868 
1869     // NOTE: This assumes that DeviceInitializeMediaChangeDetection is always
1870     //       called in the context of the DeviceInitDevicePhase2. If called
1871     //       after then this check will have already been made and the
1872     //       once a second timer will not have been enabled.
1873 
1874     disabled = DeviceIsMediaChangeDisabledDueToHardwareLimitation(DeviceExtension);
1875 
1876     if (disabled)
1877     {
1878         TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
1879                     "DeviceInitializeMediaChangeDetection: Disabled due to hardware"
1880                     "limitations for this device\n"));
1881     }
1882     else
1883     {
1884         // autorun should now be enabled by default for all media types.
1885         disabled = DeviceIsMediaChangeDisabledForClass(DeviceExtension);
1886 
1887         TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
1888                     "DeviceInitializeMediaChangeDetection: MCN is %s\n",
1889                     (disabled ? "disabled" : "enabled")));
1890 
1891         status = DeviceMediaChangeDeviceInstanceOverride(DeviceExtension,
1892                                                          &instanceOverride);  // default value
1893 
1894         if (!NT_SUCCESS(status))
1895         {
1896             TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
1897                         "DeviceInitializeMediaChangeDetection: Instance using default\n"));
1898         }
1899         else
1900         {
1901             TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
1902                         "DeviceInitializeMediaChangeDetection: Instance override: %s MCN\n",
1903                         (instanceOverride ? "Enabling" : "Disabling")));
1904             disabled = !instanceOverride;
1905         }
1906 
1907         TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
1908                     "DeviceInitializeMediaChangeDetection: Instance MCN is %s\n",
1909                     (disabled ? "disabled" : "enabled")));
1910     }
1911 
1912     if (!disabled)
1913     {
1914         // if the drive is not a CDROM, allow the drive to sleep
1915         status = DeviceInitializeMcn(DeviceExtension, FALSE);
1916     }
1917 
1918     return status;
1919 } // end DeviceInitializeMediaChangeDetection()
1920 
1921 
1922 _IRQL_requires_max_(PASSIVE_LEVEL)
1923 NTSTATUS
1924 DeviceMediaChangeDeviceInstanceOverride(
1925     _In_ PCDROM_DEVICE_EXTENSION  DeviceExtension,
1926     _Out_ PBOOLEAN                Enabled
1927     )
1928 /*++
1929 
1930 Routine Description:
1931 
1932     The user can override the global setting to enable or disable Autorun on a
1933     specific cdrom device via the control panel.  This routine checks and/or
1934     sets this value.
1935 
1936 Arguments:
1937 
1938     DeviceExtension - the device to set/get the value for
1939 
1940     Enabled - TRUE (Autorun is enabled)
1941               FALSE (Autorun is disabled)
1942 Return Value:
1943     NTSTATUS
1944 
1945 --*/
1946 {
1947     NTSTATUS        status = STATUS_UNSUCCESSFUL;
1948     WDFKEY          deviceKey = NULL;
1949     WDFKEY          subKey = NULL;
1950 
1951     UNICODE_STRING  subkeyName;
1952     UNICODE_STRING  enableMcnValueName;
1953     UNICODE_STRING  disableMcnValueName;
1954     ULONG           alwaysEnable = 0;
1955     ULONG           alwaysDisable = 0;
1956 
1957     PAGED_CODE();
1958 
1959     status = WdfDeviceOpenRegistryKey(DeviceExtension->Device,
1960                                       PLUGPLAY_REGKEY_DEVICE,
1961                                       KEY_ALL_ACCESS,
1962                                       WDF_NO_OBJECT_ATTRIBUTES,
1963                                       &deviceKey);
1964     if (!NT_SUCCESS(status))
1965     {
1966         // this can occur when a new device is added to the system
1967         // this is due to cdrom.sys being an 'essential' driver
1968         TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
1969                     "DeviceMediaChangeDeviceInstanceOverride: "
1970                     "Could not open device registry key [%lx]\n", status));
1971     }
1972     else
1973     {
1974         RtlInitUnicodeString(&subkeyName, MCN_REG_SUBKEY_NAME);
1975 
1976         status = WdfRegistryOpenKey(deviceKey,
1977                                     &subkeyName,
1978                                     KEY_READ,
1979                                     WDF_NO_OBJECT_ATTRIBUTES,
1980                                     &subKey);
1981     }
1982 
1983     if (!NT_SUCCESS(status))
1984     {
1985         TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
1986                     "DeviceMediaChangeDeviceInstanceOverride: "
1987                     "subkey could not be created. %lx\n", status));
1988     }
1989     else
1990     {
1991         // Default to not changing autorun behavior, based upon setting
1992         // registryValue to zero.
1993         RtlInitUnicodeString(&enableMcnValueName, MCN_REG_AUTORUN_ENABLE_INSTANCE_NAME);
1994         RtlInitUnicodeString(&disableMcnValueName, MCN_REG_AUTORUN_DISABLE_INSTANCE_NAME);
1995 
1996         // Ignore failures on reading of subkeys
1997         (VOID) WdfRegistryQueryULong(subKey,
1998                                      &enableMcnValueName,
1999                                      &alwaysEnable);
2000         (VOID) WdfRegistryQueryULong(subKey,
2001                                      &disableMcnValueName,
2002                                      &alwaysDisable);
2003     }
2004 
2005     // set return value and cleanup
2006 
2007     if (subKey != NULL)
2008     {
2009         WdfRegistryClose(subKey);
2010     }
2011 
2012     if (deviceKey != NULL)
2013     {
2014         WdfRegistryClose(deviceKey);
2015     }
2016 
2017     if (alwaysEnable && alwaysDisable)
2018     {
2019         TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
2020                     "DeviceMediaChangeDeviceInstanceOverride: %s selected\n",
2021                     "Both Enable and Disable set -- DISABLE"));
2022         NT_ASSERT(NT_SUCCESS(status));
2023         status = STATUS_SUCCESS;
2024         *Enabled = FALSE;
2025     }
2026     else if (alwaysDisable)
2027     {
2028         TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
2029                     "DeviceMediaChangeDeviceInstanceOverride: %s selected\n",
2030                     "DISABLE"));
2031         NT_ASSERT(NT_SUCCESS(status));
2032         status = STATUS_SUCCESS;
2033         *Enabled = FALSE;
2034     }
2035     else if (alwaysEnable)
2036     {
2037         TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
2038                     "DeviceMediaChangeDeviceInstanceOverride: %s selected\n",
2039                     "ENABLE"));
2040         NT_ASSERT(NT_SUCCESS(status));
2041         status = STATUS_SUCCESS;
2042         *Enabled = TRUE;
2043     }
2044     else
2045     {
2046         TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
2047                     "DeviceMediaChangeDeviceInstanceOverride: %s selected\n",
2048                     "DEFAULT"));
2049         status = STATUS_UNSUCCESSFUL;
2050     }
2051 
2052     return status;
2053 } // end DeviceMediaChangeDeviceInstanceOverride()
2054 
2055 
2056 NTSTATUS
2057 NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
2058 DeviceMediaChangeRegistryCallBack(
2059     _In_z_ PWSTR ValueName,
2060     _In_ ULONG ValueType,
2061     _In_reads_bytes_opt_(ValueLength) PVOID ValueData,
2062     _In_ ULONG ValueLength,
2063     _In_opt_ PVOID Context,
2064     _In_opt_ PVOID EntryContext
2065     )
2066 /*++
2067 
2068 Routine Description:
2069 
2070     This callback for a registry SZ or MULTI_SZ is called once for each
2071     SZ in the value.  It will attempt to match the data with the
2072     UNICODE_STRING passed in as Context, and modify EntryContext if a
2073     match is found.  Written for ClasspCheckRegistryForMediaChangeCompletion
2074 
2075 Arguments:
2076 
2077     ValueName     - name of the key that was opened
2078     ValueType     - type of data stored in the value (REG_SZ for this routine)
2079     ValueData     - data in the registry, in this case a wide string
2080     ValueLength   - length of the data including the terminating null
2081     Context       - unicode string to compare against ValueData
2082     EntryContext  - should be initialized to 0, will be set to 1 if match found
2083 
2084 Return Value:
2085 
2086     STATUS_SUCCESS
2087     EntryContext will be 1 if found
2088 
2089 --*/
2090 {
2091     PULONG          valueFound;
2092     PUNICODE_STRING deviceString;
2093     PWSTR           keyValue;
2094 
2095     PAGED_CODE();
2096 
2097     UNREFERENCED_PARAMETER(ValueName);
2098 
2099     if ((Context == NULL) || (EntryContext == NULL))
2100     {
2101         TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN,
2102                    "DeviceMediaChangeRegistryCallBack: NULL context should never be passed to registry call-back!\n"));
2103 
2104         return STATUS_SUCCESS;
2105     }
2106 
2107     // if we have already set the value to true, exit
2108     valueFound = EntryContext;
2109     if ((*valueFound) != 0)
2110     {
2111         TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
2112                    "DeviceMediaChangeRegistryCallBack: already set to true\n"));
2113         return STATUS_SUCCESS;
2114     }
2115 
2116     if (ValueLength == sizeof(WCHAR))
2117     {
2118         TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN,
2119                    "DeviceMediaChangeRegistryCallBack: NULL string should never be passed to registry call-back!\n"));
2120         return STATUS_SUCCESS;
2121     }
2122 
2123     // if the data is not a terminated string, exit
2124     if (ValueType != REG_SZ)
2125     {
2126         return STATUS_SUCCESS;
2127     }
2128 
2129     deviceString = Context;
2130     keyValue = ValueData;
2131     ValueLength -= sizeof(WCHAR); // ignore the null character
2132 
2133     // do not compare more memory than is in deviceString
2134     if (ValueLength > deviceString->Length)
2135     {
2136         ValueLength = deviceString->Length;
2137     }
2138 
2139     if (keyValue == NULL)
2140     {
2141         return STATUS_SUCCESS;
2142     }
2143 
2144     // if the strings match, disable autorun
2145     if (RtlCompareMemory(deviceString->Buffer, keyValue, ValueLength) == ValueLength)
2146     {
2147         TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN, "DeviceMediaChangeRegistryCallBack: Match found\n"));
2148         TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN, "DeviceMediaChangeRegistryCallBack: DeviceString at %p\n",
2149                     deviceString->Buffer));
2150         TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN,
2151                     "DeviceMediaChangeRegistryCallBack: KeyValue at %p\n",
2152                     keyValue));
2153         (*valueFound) = TRUE;
2154     }
2155 
2156     return STATUS_SUCCESS;
2157 } // end DeviceMediaChangeRegistryCallBack()
2158 
2159 
2160 _IRQL_requires_max_(PASSIVE_LEVEL)
2161 BOOLEAN
2162 DeviceIsMediaChangeDisabledDueToHardwareLimitation(
2163     _In_ PCDROM_DEVICE_EXTENSION DeviceExtension
2164     )
2165 /*++
2166 
2167 Routine Description:
2168 
2169     The key AutoRunAlwaysDisable contains a MULTI_SZ of hardware IDs for
2170     which to never enable MediaChangeNotification.
2171 
2172     The user can override the global setting to enable or disable Autorun on a
2173     specific cdrom device via the control panel.
2174 
2175     NOTE: It's intended that not use WdfRegistryQueryMultiString in this funciton,
2176           as it's much more complicated.(needs WDFCOLLECTION, WDFSTRING other than
2177           UNICODE_STRING)
2178 
2179 Arguments:
2180 
2181     FdoExtension -
2182     RegistryPath - pointer to the unicode string inside
2183                    ...\CurrentControlSet\Services\Cdrom
2184 
2185 Return Value:
2186 
2187     TRUE - no autorun.
2188     FALSE - Autorun may be enabled
2189 
2190 --*/
2191 {
2192     NTSTATUS                    status;
2193 
2194     PSTORAGE_DEVICE_DESCRIPTOR  deviceDescriptor = DeviceExtension->DeviceDescriptor;
2195     WDFKEY                      wdfKey;
2196     HANDLE                      serviceKey = NULL;
2197     RTL_QUERY_REGISTRY_TABLE    parameters[2] = {0};
2198 
2199     UNICODE_STRING              deviceUnicodeString = {0};
2200     ANSI_STRING                 deviceString = {0};
2201     ULONG                       mediaChangeNotificationDisabled = 0;
2202 
2203     PAGED_CODE();
2204 
2205     // open the service key.
2206     status = WdfDriverOpenParametersRegistryKey(WdfGetDriver(),
2207                                                 KEY_ALL_ACCESS,
2208                                                 WDF_NO_OBJECT_ATTRIBUTES,
2209                                                 &wdfKey);
2210 
2211     if(!NT_SUCCESS(status))
2212     {
2213         // NT_ASSERT(FALSE); __REACTOS__ : allow to fail (for 1st stage setup)
2214 
2215         // always take the safe path.  if we can't open the service key, disable autorun
2216         return TRUE;
2217     }
2218 
2219     if(NT_SUCCESS(status))
2220     {
2221         // Determine if drive is in a list of those requiring
2222         // autorun to be disabled.  this is stored in a REG_MULTI_SZ
2223         // named AutoRunAlwaysDisable.  this is required as some autochangers
2224         // must load the disc to reply to ChkVerify request, causing them
2225         // to cycle discs continuously.
2226 
2227         PWSTR   nullMultiSz;
2228         PUCHAR  vendorId = NULL;
2229         PUCHAR  productId = NULL;
2230         PUCHAR  revisionId = NULL;
2231         size_t  length;
2232         size_t  offset;
2233 
2234         deviceString.Buffer        = NULL;
2235         deviceUnicodeString.Buffer = NULL;
2236 
2237         serviceKey = WdfRegistryWdmGetHandle(wdfKey);
2238 
2239         // there may be nothing to check against
2240         if ((deviceDescriptor->VendorIdOffset == 0) &&
2241             (deviceDescriptor->ProductIdOffset == 0))
2242         {
2243             // no valid data in device extension.
2244             status = STATUS_INTERNAL_ERROR;
2245         }
2246 
2247         // build deviceString using VendorId, Model and Revision.
2248         // this string will be used to checked if it's one of devices in registry disable list.
2249         if (NT_SUCCESS(status))
2250         {
2251             length = 0;
2252 
2253             if (deviceDescriptor->VendorIdOffset == 0)
2254             {
2255                 vendorId = NULL;
2256             }
2257             else
2258             {
2259                 vendorId = (PUCHAR) deviceDescriptor + deviceDescriptor->VendorIdOffset;
2260                 length = strlen((LPCSTR)vendorId);
2261             }
2262 
2263             if ( deviceDescriptor->ProductIdOffset == 0 )
2264             {
2265                 productId = NULL;
2266             }
2267             else
2268             {
2269                 productId = (PUCHAR) deviceDescriptor + deviceDescriptor->ProductIdOffset;
2270                 length += strlen((LPCSTR)productId);
2271             }
2272 
2273             if ( deviceDescriptor->ProductRevisionOffset == 0 )
2274             {
2275                 revisionId = NULL;
2276             }
2277             else
2278             {
2279                 revisionId = (PUCHAR) deviceDescriptor + deviceDescriptor->ProductRevisionOffset;
2280                 length += strlen((LPCSTR)revisionId);
2281             }
2282 
2283             // allocate a buffer for the string
2284             deviceString.Length = (USHORT)( length );
2285             deviceString.MaximumLength = deviceString.Length + 1;
2286             deviceString.Buffer = (PCHAR)ExAllocatePoolWithTag( NonPagedPoolNx,
2287                                                                  deviceString.MaximumLength,
2288                                                                  CDROM_TAG_AUTORUN_DISABLE
2289                                                                  );
2290             if (deviceString.Buffer == NULL)
2291             {
2292                 TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
2293                             "DeviceIsMediaChangeDisabledDueToHardwareLimitation: Unable to alloc string buffer\n" ));
2294                 status = STATUS_INTERNAL_ERROR;
2295             }
2296         }
2297 
2298         if (NT_SUCCESS(status))
2299         {
2300             // copy strings to the buffer
2301             offset = 0;
2302 
2303             if (vendorId != NULL)
2304             {
2305                 RtlCopyMemory(deviceString.Buffer + offset,
2306                               vendorId,
2307                               strlen((LPCSTR)vendorId));
2308                 offset += strlen((LPCSTR)vendorId);
2309             }
2310 
2311             if ( productId != NULL )
2312             {
2313                 RtlCopyMemory(deviceString.Buffer + offset,
2314                               productId,
2315                               strlen((LPCSTR)productId));
2316                 offset += strlen((LPCSTR)productId);
2317             }
2318             if ( revisionId != NULL )
2319             {
2320                 RtlCopyMemory(deviceString.Buffer + offset,
2321                               revisionId,
2322                               strlen((LPCSTR)revisionId));
2323                 offset += strlen((LPCSTR)revisionId);
2324             }
2325 
2326             NT_ASSERT(offset == deviceString.Length);
2327 
2328             #pragma warning(suppress:6386) // Not an issue as deviceString.Buffer is of size deviceString.MaximumLength, which is equal to (deviceString.Length + 1)
2329             deviceString.Buffer[deviceString.Length] = '\0';  // Null-terminated
2330 
2331             // convert to unicode as registry deals with unicode strings
2332             status = RtlAnsiStringToUnicodeString( &deviceUnicodeString,
2333                                                    &deviceString,
2334                                                    TRUE
2335                                                    );
2336             if (!NT_SUCCESS(status))
2337             {
2338                 TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
2339                             "DeviceIsMediaChangeDisabledDueToHardwareLimitation: cannot convert "
2340                             "to unicode %lx\n", status));
2341             }
2342         }
2343 
2344         if (NT_SUCCESS(status))
2345         {
2346             // query the value, setting valueFound to true if found
2347             nullMultiSz = L"\0";
2348             parameters[0].QueryRoutine  = DeviceMediaChangeRegistryCallBack;
2349             parameters[0].Flags         = RTL_QUERY_REGISTRY_REQUIRED;
2350             parameters[0].Name          = L"AutoRunAlwaysDisable";
2351             parameters[0].EntryContext  = &mediaChangeNotificationDisabled;
2352             parameters[0].DefaultType   = REG_MULTI_SZ;
2353             parameters[0].DefaultData   = nullMultiSz;
2354             parameters[0].DefaultLength = 0;
2355 
2356             status = RtlQueryRegistryValues(RTL_REGISTRY_HANDLE,
2357                                             serviceKey,
2358                                             parameters,
2359                                             &deviceUnicodeString,
2360                                             NULL);
2361             UNREFERENCED_PARAMETER(status); //defensive coding, avoid PREFAST warning.
2362         }
2363     }
2364 
2365     // Cleanup
2366     {
2367 
2368         FREE_POOL( deviceString.Buffer );
2369         if (deviceUnicodeString.Buffer != NULL)
2370         {
2371             RtlFreeUnicodeString( &deviceUnicodeString );
2372         }
2373 
2374         // handle serviceKey will be closed by framework while it closes registry key.
2375         WdfRegistryClose(wdfKey);
2376     }
2377 
2378     if (mediaChangeNotificationDisabled > 0)
2379     {
2380         TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
2381                     "DeviceIsMediaChangeDisabledDueToHardwareLimitation: Device is on MCN disable list\n"));
2382     }
2383 
2384     return (mediaChangeNotificationDisabled > 0);
2385 
2386 } // end DeviceIsMediaChangeDisabledDueToHardwareLimitation()
2387 
2388 
2389 _IRQL_requires_max_(PASSIVE_LEVEL)
2390 BOOLEAN
2391 DeviceIsMediaChangeDisabledForClass(
2392     _In_ PCDROM_DEVICE_EXTENSION DeviceExtension
2393     )
2394 /*++
2395 
2396 Routine Description:
2397 
2398     The user must specify that AutoPlay is to run on the platform
2399     by setting the registry value HKEY_LOCAL_MACHINE\System\CurrentControlSet\
2400     Services\<SERVICE>\Autorun:REG_DWORD:1.
2401 
2402     The user can override the global setting to enable or disable Autorun on a
2403     specific cdrom device via the control panel.
2404 
2405 Arguments:
2406 
2407     DeviceExtension - device extension
2408 
2409 Return Value:
2410 
2411     TRUE - Autorun is disabled for this class
2412     FALSE - Autorun is enabled for this class
2413 
2414 --*/
2415 {
2416     NTSTATUS                 status;
2417     WDFKEY                   serviceKey = NULL;
2418     WDFKEY                   parametersKey = NULL;
2419 
2420     UNICODE_STRING           parameterKeyName;
2421     UNICODE_STRING           valueName;
2422 
2423     //  Default to ENABLING MediaChangeNotification (!)
2424     ULONG                    mcnRegistryValue = 1;
2425 
2426     PAGED_CODE();
2427 
2428     // open the service key.
2429     status = WdfDriverOpenParametersRegistryKey(WdfGetDriver(),
2430                                                 KEY_ALL_ACCESS,
2431                                                 WDF_NO_OBJECT_ATTRIBUTES,
2432                                                 &serviceKey);
2433     if(!NT_SUCCESS(status))
2434     {
2435         // return the default value, which is the inverse of the registry setting default
2436         // since this routine asks if it's disabled
2437         TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
2438                    "DeviceIsMediaChangeDisabledForClass: Defaulting to %s\n",
2439                    (mcnRegistryValue ? "Enabled" : "Disabled")));
2440         return (BOOLEAN)(mcnRegistryValue == 0);
2441     }
2442     else
2443     {
2444         // Open the parameters key (if any) beneath the services key.
2445         RtlInitUnicodeString(&parameterKeyName, L"Parameters");
2446 
2447         status = WdfRegistryOpenKey(serviceKey,
2448                                     &parameterKeyName,
2449                                     KEY_READ,
2450                                     WDF_NO_OBJECT_ATTRIBUTES,
2451                                     &parametersKey);
2452     }
2453 
2454     if (!NT_SUCCESS(status))
2455     {
2456         parametersKey = NULL;
2457     }
2458 
2459     RtlInitUnicodeString(&valueName, L"Autorun");
2460     // ignore failures
2461     status = WdfRegistryQueryULong(serviceKey,
2462                                    &valueName,
2463                                    &mcnRegistryValue);
2464 
2465     if (NT_SUCCESS(status))
2466     {
2467         TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN,
2468                    "DeviceIsMediaChangeDisabledForClass: <Service>/Autorun flag = %d\n",
2469                    mcnRegistryValue));
2470     }
2471 
2472     if (parametersKey != NULL)
2473     {
2474         status = WdfRegistryQueryULong(parametersKey,
2475                                        &valueName,
2476                                        &mcnRegistryValue);
2477 
2478         if (NT_SUCCESS(status))
2479         {
2480             TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN,
2481                        "DeviceIsMediaChangeDisabledForClass: <Service>/Parameters/Autorun flag = %d\n",
2482                         mcnRegistryValue));
2483         }
2484 
2485         WdfRegistryClose(parametersKey);
2486     }
2487 
2488     WdfRegistryClose(serviceKey);
2489 
2490     TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
2491                "DeviceIsMediaChangeDisabledForClass: Autoplay for device %p is %s\n",
2492                DeviceExtension->DeviceObject,
2493                (mcnRegistryValue ? "on" : "off")
2494                 ));
2495 
2496     // return if it is _disabled_, which is the
2497     // inverse of the registry setting
2498 
2499     return (BOOLEAN)(!mcnRegistryValue);
2500 } // end DeviceIsMediaChangeDisabledForClass()
2501 
2502 
2503 _IRQL_requires_max_(APC_LEVEL)
2504 VOID
2505 DeviceEnableMediaChangeDetection(
2506     _In_    PCDROM_DEVICE_EXTENSION DeviceExtension,
2507     _Inout_ PFILE_OBJECT_CONTEXT    FileObjectContext,
2508     _In_    BOOLEAN                 IgnorePreviousMediaChanges
2509     )
2510 /*++
2511 
2512 Routine Description:
2513 
2514     When the disable count decrease to 0, enable the MCN
2515 
2516 Arguments:
2517 
2518     DeviceExtension - the device context
2519 
2520     FileObjectContext - the file object context
2521 
2522     IgnorePreviousMediaChanges - ignore all previous media changes
2523 
2524 Return Value:
2525     None.
2526 
2527 --*/
2528 {
2529     PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo;
2530     LONG                         oldCount;
2531 
2532     PAGED_CODE();
2533 
2534     if (FileObjectContext)
2535     {
2536         InterlockedDecrement((PLONG)&(FileObjectContext->McnDisableCount));
2537     }
2538 
2539     if (info == NULL)
2540     {
2541         TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
2542                     "DeviceEnableMediaChangeDetection: not initialized\n"));
2543         return;
2544     }
2545 
2546     (VOID) KeWaitForMutexObject(&info->MediaChangeMutex,
2547                                 UserRequest,
2548                                 KernelMode,
2549                                 FALSE,
2550                                 NULL);
2551 
2552     oldCount = --info->MediaChangeDetectionDisableCount;
2553 
2554     NT_ASSERT(oldCount >= 0);
2555 
2556     TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN,
2557                "DeviceEnableMediaChangeDetection: Disable count reduced to %d - \n",
2558                info->MediaChangeDetectionDisableCount));
2559 
2560     if (oldCount == 0)
2561     {
2562         if (IgnorePreviousMediaChanges)
2563         {
2564             info->LastReportedMediaDetectionState = info->LastKnownMediaDetectionState;
2565         }
2566 
2567         TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN, "MCN is enabled\n"));
2568     }
2569     else
2570     {
2571         TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN, "MCD still disabled\n"));
2572     }
2573 
2574     // Let something else run.
2575     KeReleaseMutex(&info->MediaChangeMutex, FALSE);
2576 
2577     return;
2578 } // end DeviceEnableMediaChangeDetection()
2579 
2580 
2581 _IRQL_requires_max_(APC_LEVEL)
2582 VOID
2583 DeviceDisableMediaChangeDetection(
2584     _In_    PCDROM_DEVICE_EXTENSION DeviceExtension,
2585     _Inout_ PFILE_OBJECT_CONTEXT    FileObjectContext
2586     )
2587 /*++
2588 
2589 Routine Description:
2590 
2591     Increase the disable count.
2592 
2593 Arguments:
2594 
2595     DeviceExtension - the device context
2596 
2597     FileObjectContext - the file object context
2598 
2599 Return Value:
2600     None.
2601 
2602 --*/
2603 {
2604     PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo;
2605 
2606     PAGED_CODE();
2607 
2608     if (FileObjectContext)
2609     {
2610         InterlockedIncrement((PLONG)&(FileObjectContext->McnDisableCount));
2611     }
2612 
2613     if (info == NULL)
2614     {
2615         return;
2616     }
2617 
2618     (VOID) KeWaitForMutexObject(&info->MediaChangeMutex,
2619                                 UserRequest,
2620                                 KernelMode,
2621                                 FALSE,
2622                                 NULL);
2623 
2624     info->MediaChangeDetectionDisableCount++;
2625 
2626     TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_MCN,
2627                "DisableMediaChangeDetection: disable count is %d\n",
2628                info->MediaChangeDetectionDisableCount));
2629 
2630     KeReleaseMutex(&info->MediaChangeMutex, FALSE);
2631 
2632     return;
2633 } // end DeviceDisableMediaChangeDetection()
2634 
2635 
2636 _IRQL_requires_max_(APC_LEVEL)
2637 VOID
2638 DeviceReleaseMcnResources(
2639     _In_ PCDROM_DEVICE_EXTENSION DeviceExtension
2640     )
2641 /*++
2642 
2643 Routine Description:
2644 
2645     This routine will cleanup any resources allocated for MCN.  It is called
2646     by classpnp during remove device, and therefore is not typically required
2647     by external drivers.
2648 
2649 Arguments:
2650 
2651     DeviceExtension - the device context
2652 
2653 Return Value:
2654     None.
2655 
2656 --*/
2657 {
2658     PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo;
2659 
2660     PAGED_CODE()
2661 
2662     if(info == NULL)
2663     {
2664         return;
2665     }
2666 
2667     if (info->Gesn.Mdl)
2668     {
2669         PIRP irp = WdfRequestWdmGetIrp(info->MediaChangeRequest);
2670         IoFreeMdl(info->Gesn.Mdl);
2671         irp->MdlAddress = NULL;
2672     }
2673     IoFreeIrp(info->MediaChangeSyncIrp);
2674     FREE_POOL(info->Gesn.Buffer);
2675     FREE_POOL(info->SenseBuffer);
2676 
2677     if (info->DisplayStateCallbackHandle)
2678     {
2679         PoUnregisterPowerSettingCallback(info->DisplayStateCallbackHandle);
2680         info->DisplayStateCallbackHandle = NULL;
2681     }
2682 
2683     FREE_POOL(info);
2684 
2685     DeviceExtension->MediaChangeDetectionInfo = NULL;
2686 
2687     return;
2688 } // end DeviceReleaseMcnResources()
2689 
2690 
2691 IO_COMPLETION_ROUTINE RequestMcnSyncIrpCompletion;
2692 
2693 NTSTATUS
2694 NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
2695 RequestMcnSyncIrpCompletion(
2696     _In_ PDEVICE_OBJECT DeviceObject,
2697     _In_ PIRP Irp,
2698     _In_reads_opt_(_Inexpressible_("varies")) PVOID Context
2699     )
2700 /*++
2701 
2702 Routine Description:
2703 
2704     The MCN work finishes, reset the fields to allow another MCN request
2705     be scheduled.
2706 
2707 Arguments:
2708 
2709     DeviceObject - device that the completion routine fires on.
2710 
2711     Irp - The irp to be completed.
2712 
2713     Context - IRP context
2714 
2715 Return Value:
2716     NTSTATUS
2717 
2718 --*/
2719 {
2720     PCDROM_DEVICE_EXTENSION DeviceExtension = NULL;
2721     PMEDIA_CHANGE_DETECTION_INFO info = NULL;
2722 
2723     if (Context == NULL)
2724     {
2725         // this will never happen, but code must be there to prevent OACR warnings.
2726         return STATUS_MORE_PROCESSING_REQUIRED;
2727     }
2728 
2729     DeviceExtension = (PCDROM_DEVICE_EXTENSION) Context;
2730     info = DeviceExtension->MediaChangeDetectionInfo;
2731 
2732 #ifndef DEBUG
2733     UNREFERENCED_PARAMETER(Irp);
2734 #endif
2735     UNREFERENCED_PARAMETER(DeviceObject);
2736 
2737     NT_ASSERT(Irp == info->MediaChangeSyncIrp);
2738 
2739     IoReuseIrp(info->MediaChangeSyncIrp, STATUS_NOT_SUPPORTED);
2740 
2741     // reset the value to let timer routine be able to send the next request.
2742     InterlockedCompareExchange((PLONG)&(info->MediaChangeRequestInUse), 0, 1);
2743 
2744     return STATUS_MORE_PROCESSING_REQUIRED;
2745 }
2746 
2747 
2748 VOID
2749 RequestSetupMcnSyncIrp(
2750     _In_ PCDROM_DEVICE_EXTENSION DeviceExtension
2751     )
2752 /*++
2753 
2754 Routine Description:
2755 
2756     setup the MCN synchronization irp.
2757 
2758 Arguments:
2759 
2760     DeviceExtension - the device context
2761 
2762 Return Value:
2763     None
2764 
2765 --*/
2766 {
2767     PIRP                irp = NULL;
2768     PIO_STACK_LOCATION  irpStack = NULL;
2769     PIO_STACK_LOCATION  nextIrpStack = NULL;
2770 
2771     irp = DeviceExtension->MediaChangeDetectionInfo->MediaChangeSyncIrp;
2772     NT_ASSERT(irp != NULL);
2773 
2774     //
2775     //  For the driver that creates an IRP, there is no 'current' stack location.
2776     //  Step down one IRP stack location so that the extra top one
2777     //  becomes our 'current' one.
2778     //
2779     IoSetNextIrpStackLocation(irp);
2780 
2781     /*
2782      *  Cache our device object in the extra top IRP stack location
2783      *  so we have it in our completion routine.
2784      */
2785     irpStack = IoGetCurrentIrpStackLocation(irp);
2786     irpStack->DeviceObject = DeviceExtension->DeviceObject;
2787 
2788     //
2789     // If the irp is sent down when the volume needs to be
2790     // verified, CdRomUpdateGeometryCompletion won't complete
2791     // it since it's not associated with a thread.  Marking
2792     // it to override the verify causes it always be sent
2793     // to the port driver
2794     //
2795     nextIrpStack = IoGetNextIrpStackLocation(irp);
2796 
2797     SET_FLAG(nextIrpStack->Flags, SL_OVERRIDE_VERIFY_VOLUME);
2798 
2799     nextIrpStack->MajorFunction = IRP_MJ_DEVICE_CONTROL;
2800     // pick up this IOCTL code as it's not normaly seen for CD/DVD drive and does not require input.
2801     // set other fields to make this IOCTL recognizable by CDROM.SYS
2802     nextIrpStack->Parameters.Others.Argument1 = RequestSetupMcnSyncIrp;
2803     nextIrpStack->Parameters.Others.Argument2 = RequestSetupMcnSyncIrp;
2804     nextIrpStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_MCN_SYNC_FAKE_IOCTL; //Argument3.
2805     nextIrpStack->Parameters.Others.Argument4 = RequestSetupMcnSyncIrp;
2806 
2807     IoSetCompletionRoutine(irp,
2808                            RequestMcnSyncIrpCompletion,
2809                            DeviceExtension,
2810                            TRUE,
2811                            TRUE,
2812                            TRUE);
2813 
2814     return;
2815 }
2816 
2817 
2818 VOID
2819 NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
2820 DeviceMainTimerTickHandler(
2821     _In_ WDFTIMER  Timer
2822     )
2823 /*++
2824 
2825 Routine Description:
2826 
2827     This routine setup a sync irp and send it to the serial queue.
2828     Serial queue will process MCN when receive this sync irp.
2829 
2830 Arguments:
2831 
2832     Timer - the timer object that fires.
2833 
2834 Return Value:
2835     None
2836 
2837 --*/
2838 {
2839     PCDROM_DEVICE_EXTENSION      deviceExtension = NULL;
2840     size_t                       dataLength = 0;
2841 
2842     deviceExtension = WdfObjectGetTypedContext(WdfTimerGetParentObject(Timer), CDROM_DEVICE_EXTENSION);
2843 
2844     (void) RequestHandleEventNotification(deviceExtension, NULL, NULL, &dataLength);
2845 
2846     return;
2847 } // end DeviceMainTimerTickHandler()
2848 
2849 
2850 _IRQL_requires_max_(APC_LEVEL)
2851 NTSTATUS
2852 DeviceEnableMainTimer(
2853     _In_ PCDROM_DEVICE_EXTENSION DeviceExtension
2854     )
2855 /*++
2856 
2857 Routine Description:
2858 
2859     This routine will allocate timer related resources on the first time call.
2860     Start the timer.
2861 
2862 Arguments:
2863 
2864     DeviceExtension - the device context
2865 
2866 Return Value:
2867     NTSTATUS
2868 
2869 --*/
2870 {
2871     NTSTATUS status = STATUS_SUCCESS;
2872 
2873     if ((DeviceExtension->MediaChangeDetectionInfo == NULL) ||
2874         (DeviceExtension->MediaChangeDetectionInfo->AsynchronousNotificationSupported != FALSE))
2875     {
2876         // Asynchronous Notification is enabled, timer not needed.
2877         return status;
2878     }
2879 
2880     if (DeviceExtension->MainTimer == NULL)
2881     {
2882         //create main timer object.
2883         WDF_TIMER_CONFIG        timerConfig;
2884         WDF_OBJECT_ATTRIBUTES   timerAttributes;
2885 
2886         WDF_TIMER_CONFIG_INIT(&timerConfig, DeviceMainTimerTickHandler);
2887 
2888         // Polling frequently on virtual optical devices created by Hyper-V will
2889         // cause a significant perf / power hit. These devices need to be polled
2890         // less frequently for device state changes.
2891         if (TEST_FLAG(DeviceExtension->DeviceAdditionalData.HackFlags, CDROM_HACK_MSFT_VIRTUAL_ODD))
2892         {
2893             timerConfig.Period = 2000; // 2 seconds, in milliseconds.
2894         }
2895         else
2896         {
2897             timerConfig.Period = 1000; // 1 second, in milliseconds.
2898         }
2899 
2900         timerConfig.TolerableDelay = 500; // 0.5 seconds, in milliseconds
2901 
2902         //Set the autoSerialization to FALSE, as the parent device's
2903         //execute level is WdfExecutionLevelPassive.
2904         timerConfig.AutomaticSerialization = FALSE;
2905 
2906         WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes);
2907         timerAttributes.ParentObject = DeviceExtension->Device;
2908         timerAttributes.ExecutionLevel = WdfExecutionLevelInheritFromParent;
2909 
2910         status = WdfTimerCreate(&timerConfig,
2911                                 &timerAttributes,
2912                                 &DeviceExtension->MainTimer);
2913     }
2914 
2915     if (NT_SUCCESS(status))
2916     {
2917         WdfTimerStart(DeviceExtension->MainTimer,WDF_REL_TIMEOUT_IN_MS(100));
2918 
2919         TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
2920                    "DeviceEnableMainTimer: Once a second timer enabled  for WDFDEVICE %p\n",
2921                    DeviceExtension->Device));
2922     }
2923     else
2924     {
2925         TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
2926                    "DeviceEnableMainTimer: WDFDEVICE %p, Status %lx  initializing timer\n",
2927                    DeviceExtension->Device, status));
2928     }
2929 
2930     return status;
2931 } // end DeviceEnableMainTimer()
2932 
2933 
2934 _IRQL_requires_max_(PASSIVE_LEVEL)
2935 VOID
2936 DeviceDisableMainTimer(
2937     _In_ PCDROM_DEVICE_EXTENSION DeviceExtension
2938     )
2939 /*++
2940 
2941 Routine Description:
2942 
2943     stop the timer.
2944 
2945 Arguments:
2946 
2947     DeviceExtension - device context
2948 
2949 Return Value:
2950     None
2951 
2952 --*/
2953 {
2954     PAGED_CODE();
2955 
2956     if ((DeviceExtension->MediaChangeDetectionInfo == NULL) ||
2957         (DeviceExtension->MediaChangeDetectionInfo->AsynchronousNotificationSupported != FALSE))
2958     {
2959         // Asynchronous Notification is enabled, timer not needed.
2960         return;
2961     }
2962 
2963     if (DeviceExtension->MainTimer != NULL)
2964     {
2965         //
2966         // we are only going to stop the actual timer in remove device routine.
2967         // it is the responsibility of the code within the timer routine to
2968         // check if the device is removed and not processing io for the final
2969         // call.
2970         // this keeps the code clean and prevents lots of bugs.
2971         //
2972         WdfTimerStop(DeviceExtension->MainTimer,TRUE);
2973 
2974         TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_MCN,
2975                    "DeviceDisableMainTimer: Once a second timer disabled for device %p\n",
2976                    DeviceExtension->Device));
2977     }
2978     else
2979     {
2980         TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN,
2981                    "DeviceDisableMainTimer: Timer never enabled\n"));
2982     }
2983 
2984     return;
2985 } // end DeviceDisableMainTimer()
2986 
2987 
2988 _IRQL_requires_max_(APC_LEVEL)
2989 NTSTATUS
2990 RequestHandleMcnControl(
2991     _In_  PCDROM_DEVICE_EXTENSION  DeviceExtension,
2992     _In_  WDFREQUEST               Request,
2993     _Out_ size_t *                 DataLength
2994     )
2995 /*++
2996 
2997 Routine Description:
2998 
2999     This routine handles the process of IOCTL_STORAGE_MCN_CONTROL
3000 
3001 Arguments:
3002 
3003     DeviceExtension - device context
3004 
3005     Request - request object
3006 
3007     RequestParameters - request parameters
3008 
3009     DataLength - data transferred
3010 
3011 Return Value:
3012     NTSTATUS
3013 
3014 --*/
3015 {
3016     NTSTATUS                status = STATUS_SUCCESS;
3017     WDFFILEOBJECT           fileObject = NULL;
3018     PFILE_OBJECT_CONTEXT    fileObjectContext = NULL;
3019     PPREVENT_MEDIA_REMOVAL  mediaRemoval = NULL;
3020 
3021     PAGED_CODE();
3022 
3023     *DataLength = 0;
3024 
3025     status = WdfRequestRetrieveInputBuffer(Request,
3026                                            sizeof(PREVENT_MEDIA_REMOVAL),
3027                                            &mediaRemoval,
3028                                            NULL);
3029 
3030     if (NT_SUCCESS(status))
3031     {
3032         fileObject = WdfRequestGetFileObject(Request);
3033 
3034         // Check to make sure we have a file object extension to keep track of this
3035         // request.  If not we'll fail it before synchronizing.
3036         if (fileObject != NULL)
3037         {
3038             fileObjectContext = FileObjectGetContext(fileObject);
3039         }
3040 
3041         if ((fileObjectContext == NULL) &&
3042             (WdfRequestGetRequestorMode(Request) == KernelMode))
3043         {
3044             fileObjectContext = &DeviceExtension->KernelModeMcnContext;
3045         }
3046 
3047         if (fileObjectContext == NULL)
3048         {
3049             // This handle isn't setup correctly.  We can't let the
3050             // operation go.
3051             status = STATUS_INVALID_PARAMETER;
3052         }
3053     }
3054 
3055     if (NT_SUCCESS(status))
3056     {
3057         if (mediaRemoval->PreventMediaRemoval)
3058         {
3059             // This is a lock command.  Reissue the command in case bus or
3060             // device was reset and the lock was cleared.
3061             DeviceDisableMediaChangeDetection(DeviceExtension, fileObjectContext);
3062         }
3063         else
3064         {
3065             if (fileObjectContext->McnDisableCount == 0)
3066             {
3067                 status = STATUS_INVALID_DEVICE_STATE;
3068             }
3069             else
3070             {
3071                 DeviceEnableMediaChangeDetection(DeviceExtension, fileObjectContext, TRUE);
3072             }
3073         }
3074     }
3075 
3076     return status;
3077 } // end RequestHandleMcnControl()
3078 
3079 #pragma warning(pop) // un-sets any local warning changes
3080 
3081