xref: /reactos/drivers/storage/class/cdrom/mmc.c (revision 3088717b)
1 /*--
2 
3 Copyright (C) Microsoft Corporation. All rights reserved.
4 
5 Module Name:
6 
7     mmc.c
8 
9 Abstract:
10 
11     Include all funtions relate to MMC
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 "scratch.h"
32 
33 #ifdef DEBUG_USE_WPP
34 #include "mmc.tmh"
35 #endif
36 
37 #ifdef ALLOC_PRAGMA
38 
39 #pragma alloc_text(PAGE, DeviceDeallocateMmcResources)
40 #pragma alloc_text(PAGE, DeviceAllocateMmcResources)
41 #pragma alloc_text(PAGE, DeviceUpdateMmcCapabilities)
42 #pragma alloc_text(PAGE, DeviceGetConfigurationWithAlloc)
43 #pragma alloc_text(PAGE, DeviceGetConfiguration)
44 #pragma alloc_text(PAGE, DeviceUpdateMmcWriteCapability)
45 #pragma alloc_text(PAGE, MmcDataFindFeaturePage)
46 #pragma alloc_text(PAGE, MmcDataFindProfileInProfiles)
47 #pragma alloc_text(PAGE, DeviceRetryTimeGuessBasedOnProfile)
48 #pragma alloc_text(PAGE, DeviceRetryTimeDetectionBasedOnModePage2A)
49 #pragma alloc_text(PAGE, DeviceRetryTimeDetectionBasedOnGetPerformance)
50 
51 #endif
52 
53 #pragma warning(push)
54 #pragma warning(disable:4214) // nonstandard extension used : bit field types other than int
55 
_IRQL_requires_max_(APC_LEVEL)56 _IRQL_requires_max_(APC_LEVEL)
57 VOID
58 DeviceDeallocateMmcResources(
59     _In_ WDFDEVICE Device
60     )
61 /*++
62 
63 Routine Description:
64 
65    release MMC resources
66 
67 Arguments:
68 
69     Device - device object
70 
71 Return Value:
72 
73     none
74 
75 --*/
76 {
77     PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(Device);
78     PCDROM_DATA             cddata = &(deviceExtension->DeviceAdditionalData);
79     PCDROM_MMC_EXTENSION    mmcData = &cddata->Mmc;
80 
81     PAGED_CODE();
82 
83     if (mmcData->CapabilitiesIrp)
84     {
85         IoFreeIrp(mmcData->CapabilitiesIrp);
86         mmcData->CapabilitiesIrp = NULL;
87     }
88     if (mmcData->CapabilitiesMdl)
89     {
90         IoFreeMdl(mmcData->CapabilitiesMdl);
91         mmcData->CapabilitiesMdl = NULL;
92     }
93     if (mmcData->CapabilitiesBuffer)
94     {
95         ExFreePool(mmcData->CapabilitiesBuffer);
96         mmcData->CapabilitiesBuffer = NULL;
97     }
98     if (mmcData->CapabilitiesRequest)
99     {
100         WdfObjectDelete(mmcData->CapabilitiesRequest);
101         mmcData->CapabilitiesRequest = NULL;
102     }
103     mmcData->CapabilitiesBufferSize = 0;
104     mmcData->IsMmc = FALSE;
105     mmcData->WriteAllowed = FALSE;
106 
107     return;
108 }
109 
110 
_IRQL_requires_max_(PASSIVE_LEVEL)111 _IRQL_requires_max_(PASSIVE_LEVEL)
112 NTSTATUS
113 DeviceAllocateMmcResources(
114     _In_ WDFDEVICE Device
115     )
116 /*++
117 
118 Routine Description:
119 
120    allocate all MMC resources needed
121 
122 Arguments:
123 
124     Device - device object
125 
126 Return Value:
127 
128     NTSTATUS
129 
130 --*/
131 {
132     NTSTATUS status                         = STATUS_SUCCESS;
133     PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(Device);
134     PCDROM_DATA cddata                      = &(deviceExtension->DeviceAdditionalData);
135     PCDROM_MMC_EXTENSION mmcData            = &(cddata->Mmc);
136     WDF_OBJECT_ATTRIBUTES attributes        = {0};
137 
138     PAGED_CODE();
139 
140     NT_ASSERT(mmcData->CapabilitiesBuffer == NULL);
141     NT_ASSERT(mmcData->CapabilitiesBufferSize == 0);
142 
143     // allocate the buffer and set the buffer size.
144     // retrieve drive configuration information.
145     status = DeviceGetConfigurationWithAlloc(Device,
146                                              &mmcData->CapabilitiesBuffer,
147                                              &mmcData->CapabilitiesBufferSize,
148                                              FeatureProfileList,
149                                              SCSI_GET_CONFIGURATION_REQUEST_TYPE_ALL);
150     if (!NT_SUCCESS(status))
151     {
152         NT_ASSERT(mmcData->CapabilitiesBuffer     == NULL);
153         NT_ASSERT(mmcData->CapabilitiesBufferSize == 0);
154         return status;
155     }
156 
157     NT_ASSERT(mmcData->CapabilitiesBuffer     != NULL);
158     NT_ASSERT(mmcData->CapabilitiesBufferSize != 0);
159 
160     // Create an MDL over the new Buffer (allocated by DeviceGetConfiguration)
161     mmcData->CapabilitiesMdl = IoAllocateMdl(mmcData->CapabilitiesBuffer,
162                                              mmcData->CapabilitiesBufferSize,
163                                              FALSE, FALSE, NULL);
164     if (mmcData->CapabilitiesMdl == NULL)
165     {
166         ExFreePool(mmcData->CapabilitiesBuffer);
167         mmcData->CapabilitiesBuffer = NULL;
168         mmcData->CapabilitiesBufferSize = 0;
169         return STATUS_INSUFFICIENT_RESOURCES;
170     }
171 
172     // Create an IRP from which we will create a WDFREQUEST
173     mmcData->CapabilitiesIrp = IoAllocateIrp(deviceExtension->DeviceObject->StackSize + 1, FALSE);
174     if (mmcData->CapabilitiesIrp == NULL)
175     {
176         IoFreeMdl(mmcData->CapabilitiesMdl);
177         mmcData->CapabilitiesMdl = NULL;
178         ExFreePool(mmcData->CapabilitiesBuffer);
179         mmcData->CapabilitiesBuffer = NULL;
180         mmcData->CapabilitiesBufferSize = 0;
181         return STATUS_INSUFFICIENT_RESOURCES;
182     }
183 
184     // create WDF request object
185     WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes,
186                                             CDROM_REQUEST_CONTEXT);
187     status = WdfRequestCreateFromIrp(&attributes,
188                                      mmcData->CapabilitiesIrp,
189                                      FALSE,
190                                      &mmcData->CapabilitiesRequest);
191     if (!NT_SUCCESS(status))
192     {
193         return status;
194     }
195 
196     return STATUS_SUCCESS;
197 }
198 
_IRQL_requires_max_(PASSIVE_LEVEL)199 _IRQL_requires_max_(PASSIVE_LEVEL)
200 NTSTATUS
201 DeviceUpdateMmcCapabilities(
202     _In_ WDFDEVICE Device
203     )
204 /*++
205 
206 Routine Description:
207 
208    issue get congiguration command ans save result in device extension
209 
210 Arguments:
211 
212     Device - device object
213 
214 Return Value:
215 
216     NTSTATUS
217 
218 --*/
219 {
220     NTSTATUS                 status = STATUS_SUCCESS;
221     PCDROM_DEVICE_EXTENSION  deviceExtension = DeviceGetExtension(Device);
222     PCDROM_DATA              cdData = &(deviceExtension->DeviceAdditionalData);
223     PCDROM_MMC_EXTENSION     mmcData = &(cdData->Mmc);
224     ULONG                    returnedBytes = 0;
225     LONG                     updateState;
226 
227     PAGED_CODE();
228 
229     // first of all, check if we're still in the CdromMmcUpdateRequired state
230     // and, if yes, change it to CdromMmcUpdateStarted.
231     updateState = InterlockedCompareExchange((PLONG)&(cdData->Mmc.UpdateState),
232                                              CdromMmcUpdateStarted,
233                                              CdromMmcUpdateRequired);
234     if (updateState != CdromMmcUpdateRequired) {
235         // Mmc capabilities have been already updated or are in the process of
236         // being updated - just return STATUS_SUCCESS
237         return STATUS_SUCCESS;
238     }
239 
240     // default to read-only, no Streaming, non-blank
241     mmcData->WriteAllowed = FALSE;
242     mmcData->StreamingReadSupported = FALSE;
243     mmcData->StreamingWriteSupported = FALSE;
244 
245     // Issue command to update the drive capabilities.
246     // The failure of MMC update is not considered critical,
247     // so that we'll continue to process I/O even MMC update fails.
248     status = DeviceGetConfiguration(Device,
249                                     mmcData->CapabilitiesBuffer,
250                                     mmcData->CapabilitiesBufferSize,
251                                     &returnedBytes,
252                                     FeatureProfileList,
253                                     SCSI_GET_CONFIGURATION_REQUEST_TYPE_CURRENT);
254 
255     if (NT_SUCCESS(status) &&                               // succeeded.
256         (mmcData->CapabilitiesBufferSize >= returnedBytes)) // not overflow.
257     {
258         // update whether or not writes are allowed
259         // this should be the *ONLY* place writes are set to allowed
260         {
261             BOOLEAN         writeAllowed = FALSE;
262             FEATURE_NUMBER  validationSchema = 0;
263             ULONG           blockingFactor = 1;
264 
265             DeviceUpdateMmcWriteCapability(mmcData->CapabilitiesBuffer,
266                                            returnedBytes,
267                                            TRUE,
268                                            &writeAllowed,
269                                            &validationSchema,
270                                            &blockingFactor);
271 
272             mmcData->WriteAllowed = writeAllowed;
273             mmcData->ValidationSchema = validationSchema;
274             mmcData->Blocking = blockingFactor;
275         }
276 
277         // Check if Streaming reads/writes are supported and cache
278         // this information for later use.
279         {
280             PFEATURE_HEADER header;
281             ULONG           minAdditionalLength;
282 
283             minAdditionalLength = FIELD_OFFSET(FEATURE_DATA_REAL_TIME_STREAMING, Reserved2) -
284                                   sizeof(FEATURE_HEADER);
285 
286             header = MmcDataFindFeaturePage(mmcData->CapabilitiesBuffer,
287                                             returnedBytes,
288                                             FeatureRealTimeStreaming);
289 
290             if ((header != NULL) &&
291                 (header->Current) &&
292                 (header->AdditionalLength >= minAdditionalLength))
293             {
294                 PFEATURE_DATA_REAL_TIME_STREAMING feature = (PFEATURE_DATA_REAL_TIME_STREAMING)header;
295 
296                 // If Real-Time feature is current, then Streaming reads are supported for sure.
297                 mmcData->StreamingReadSupported = TRUE;
298 
299                 // Streaming writes are supported if an appropriate bit is set in the feature page.
300                 mmcData->StreamingWriteSupported = (feature->StreamRecording == 1);
301             }
302         }
303 
304         // update the flag to reflect that if the media is CSS protected DVD or CPPM-protected DVDAudio
305         {
306             PFEATURE_HEADER header;
307 
308             header = DeviceFindFeaturePage(mmcData->CapabilitiesBuffer,
309                                            returnedBytes,
310                                            FeatureDvdCSS);
311 
312             mmcData->IsCssDvd = (header != NULL) && (header->Current);
313         }
314 
315         // Update the guesstimate for the drive's write speed
316         // Use the GetConfig profile first as a quick-guess based
317         // on media "type", then continue with media-specific
318         // queries for older media types, and use GET_PERFORMANCE
319         // for all unknown/future media types.
320         {
321             // pseudo-code:
322             // 1) Determine default based on profile (slowest for media)
323             // 2) Determine default based on MODE PAGE 2Ah
324             // 3) Determine default based on GET PERFORMANCE data
325             // 4) Choose fastest reported speed (-1 == none reported)
326             // 5) If all failed (returned -1), go with very safe (slow) default
327             //
328             // This ensures that the retries do not overload the drive's processor.
329             // Sending at highest possible speed for the media is OK, because the
330             // major downside is drive processor usage.  (bus usage too, but most
331             // storage is becoming a point-to-point link.)
332 
333             FEATURE_PROFILE_TYPE const profile =
334                                             mmcData->CapabilitiesBuffer->CurrentProfile[0] << (8*1) |
335                                             mmcData->CapabilitiesBuffer->CurrentProfile[1] << (8*0) ;
336             LONGLONG t1 = (LONGLONG)-1;
337             LONGLONG t2 = (LONGLONG)-1;
338             LONGLONG t3 = (LONGLONG)-1;
339             LONGLONG t4 = (LONGLONG)-1;
340             LONGLONG final;
341 
342             t1 = DeviceRetryTimeGuessBasedOnProfile(profile);
343             t2 = DeviceRetryTimeDetectionBasedOnModePage2A(deviceExtension);
344             t3 = DeviceRetryTimeDetectionBasedOnGetPerformance(deviceExtension, TRUE);
345             t4 = DeviceRetryTimeDetectionBasedOnGetPerformance(deviceExtension, FALSE);
346 
347             // use the "fastest" value returned
348             final = MAXLONGLONG;
349             if (t4 != -1)
350             {
351                 final = min(final, t4);
352             }
353             if (t3 != -1)
354             {
355                 final = min(final, t3);
356             }
357             if (t2 != -1)
358             {
359                 final = min(final, t2);
360             }
361             if (t1 != -1)
362             {
363                 final = min(final, t1);
364             }
365             if (final == MAXLONGLONG)
366             {
367                 // worst case -- use relatively slow default....
368                 final = WRITE_RETRY_DELAY_CD_4x;
369             }
370 
371             cdData->ReadWriteRetryDelay100nsUnits = final;
372         }
373 
374     }
375     else
376     {
377         // Rediscovery of MMC capabilities has failed - we'll need to retry
378         cdData->Mmc.UpdateState = CdromMmcUpdateRequired;
379     }
380 
381     // Change the state to CdromMmcUpdateComplete if it is CdromMmcUpdateStarted.
382     // If it is not, some error must have happened while this function was executed
383     // and the state is CdromMmcUpdateRequired now. In that case, we want to perform
384     // everything again, so we do not set CdromMmcUpdateComplete.
385     InterlockedCompareExchange((PLONG)&(cdData->Mmc.UpdateState),
386                                CdromMmcUpdateComplete,
387                                CdromMmcUpdateStarted);
388 
389     return status;
390 }
391 
_IRQL_requires_max_(PASSIVE_LEVEL)392 _IRQL_requires_max_(PASSIVE_LEVEL)
393 NTSTATUS
394 DeviceGetConfigurationWithAlloc(
395     _In_ WDFDEVICE               Device,
396     _Outptr_result_bytebuffer_all_(*BytesReturned)
397      PGET_CONFIGURATION_HEADER*  Buffer,  // this routine allocates this memory
398     _Out_ PULONG                 BytesReturned,
399     FEATURE_NUMBER const         StartingFeature,
400     ULONG const                  RequestedType
401     )
402 /*++
403 
404 Routine Description:
405 
406     This function will allocates configuration buffer and set the size.
407 
408 Arguments:
409 
410     Device - device object
411     Buffer - to be allocated by this function
412     BytesReturned - size of the buffer
413     StartingFeature - the starting point of the feature list
414     RequestedType -
415 
416 Return Value:
417 
418     NTSTATUS
419 
420 NOTE: does not handle case where more than 65000 bytes are returned,
421       which requires multiple calls with different starting feature
422       numbers.
423 
424 --*/
425 {
426     NTSTATUS                    status = STATUS_SUCCESS;
427     GET_CONFIGURATION_HEADER    header = {0};  // eight bytes, not a lot
428     PGET_CONFIGURATION_HEADER   buffer = NULL;
429     ULONG                       returned = 0;
430     ULONG                       size = 0;
431     ULONG                       i = 0;
432 
433     PAGED_CODE();
434 
435     *Buffer = NULL;
436     *BytesReturned = 0;
437 
438     // send the first request down to just get the header
439     status = DeviceGetConfiguration(Device,
440                                     &header,
441                                     sizeof(header),
442                                     &returned,
443                                     StartingFeature,
444                                     RequestedType);
445 
446     // now send command again, using information returned to allocate just enough memory
447     if (NT_SUCCESS(status))
448     {
449         size = header.DataLength[0] << 24 |
450                header.DataLength[1] << 16 |
451                header.DataLength[2] <<  8 |
452                header.DataLength[3] <<  0 ;
453 
454         // the loop is in case that the retrieved data length is bigger than last time reported.
455         for (i = 0; (i < 4) && NT_SUCCESS(status); i++)
456         {
457             // the datalength field is the size *following* itself, so adjust accordingly
458             size += 4*sizeof(UCHAR);
459 
460             // make sure the size is reasonable
461             if (size <= sizeof(FEATURE_HEADER))
462             {
463                 TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_IOCTL,
464                            "DeviceGetConfigurationWithAlloc: drive reports only %x bytes?\n",
465                            size));
466                 status = STATUS_UNSUCCESSFUL;
467             }
468 
469             if (NT_SUCCESS(status))
470             {
471                 // allocate the memory
472                 buffer = (PGET_CONFIGURATION_HEADER)ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned,
473                                                                           size,
474                                                                           CDROM_TAG_FEATURE);
475 
476                 if (buffer == NULL)
477                 {
478                     status = STATUS_INSUFFICIENT_RESOURCES;
479                 }
480             }
481 
482             if (NT_SUCCESS(status))
483             {
484                 // send the first request down to just get the header
485                 status = DeviceGetConfiguration(Device,
486                                                 buffer,
487                                                 size,
488                                                 &returned,
489                                                 StartingFeature,
490                                                 RequestedType);
491 
492                 if (!NT_SUCCESS(status))
493                 {
494                     ExFreePool(buffer);
495                 }
496                 else if (returned > size)
497                 {
498                     ExFreePool(buffer);
499                     status = STATUS_INTERNAL_ERROR;
500                 }
501             }
502 
503             // command succeeded.
504             if (NT_SUCCESS(status))
505             {
506                 returned = buffer->DataLength[0] << 24 |
507                            buffer->DataLength[1] << 16 |
508                            buffer->DataLength[2] <<  8 |
509                            buffer->DataLength[3] <<  0 ;
510                 returned += 4*sizeof(UCHAR);
511 
512                 if (returned <= size)
513                 {
514                     *Buffer = buffer;
515                     *BytesReturned = returned;  // amount of 'safe' memory
516                     // succes, get out of loop.
517                     status = STATUS_SUCCESS;
518                     break;
519                 }
520                 else
521                 {
522                     // the data size is bigger than the buffer size, retry using new size....
523                     size = returned;
524                     ExFreePool(buffer);
525                     buffer = NULL;
526                 }
527             }
528         } // end of for() loop
529     }
530 
531     if (!NT_SUCCESS(status))
532     {
533         // it failed after a number of attempts, so just fail.
534         TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_IOCTL,
535                    "DeviceGetConfigurationWithAlloc: Failed %d attempts to get all feature "
536                    "information\n", i));
537     }
538 
539     return status;
540 }
541 
_IRQL_requires_max_(PASSIVE_LEVEL)542 _IRQL_requires_max_(PASSIVE_LEVEL)
543 NTSTATUS
544 DeviceGetConfiguration(
545     _In_  WDFDEVICE                 Device,
546     _Out_writes_bytes_to_(BufferSize, *ValidBytes)
547           PGET_CONFIGURATION_HEADER Buffer,
548     _In_  ULONG const               BufferSize,
549     _Out_ PULONG                    ValidBytes,
550     _In_  FEATURE_NUMBER const      StartingFeature,
551     _In_  ULONG const               RequestedType
552     )
553 /*++
554 
555 Routine Description:
556 
557     This function is used to get configuration data.
558 
559 Arguments:
560 
561     Device - device object
562     Buffer - buffer address to hold data.
563     BufferSize - size of the buffer
564     ValidBytes - valid data size in buffer
565     StartingFeature - the starting point of the feature list
566     RequestedType -
567 
568 Return Value:
569 
570     NTSTATUS
571 
572 NOTE: does not handle case where more than 64k bytes are returned,
573       which requires multiple calls with different starting feature
574       numbers.
575 
576 --*/
577 {
578     NTSTATUS                status;
579     PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(Device);
580     SCSI_REQUEST_BLOCK      srb;
581     PCDB                    cdb = (PCDB)srb.Cdb;
582 
583     PAGED_CODE();
584 
585     NT_ASSERT(ValidBytes);
586 
587     // when system is low resources we can receive empty buffer
588     if (Buffer == NULL || BufferSize < sizeof(GET_CONFIGURATION_HEADER))
589     {
590         return STATUS_BUFFER_TOO_SMALL;
591     }
592 
593     *ValidBytes = 0;
594 
595     RtlZeroMemory(&srb, sizeof(SCSI_REQUEST_BLOCK));
596     RtlZeroMemory(Buffer, BufferSize);
597 
598     if (TEST_FLAG(deviceExtension->DeviceAdditionalData.HackFlags, CDROM_HACK_BAD_GET_CONFIG_SUPPORT))
599     {
600         return STATUS_INVALID_DEVICE_REQUEST;
601     }
602 
603 #pragma warning(push)
604 #pragma warning(disable: 6386) // OACR will complain buffer overrun: the writable size is 'BufferSize' bytes, but '65532'
605                                // bytes might be written, which is impossible because BufferSize > 0xFFFC.
606 
607     if (BufferSize > 0xFFFC)
608     {
609         // cannot request more than 0xFFFC bytes in one request
610         // Eventually will "stitch" together multiple requests if needed
611         // Today, no drive has anywhere close to 4k.....
612         return DeviceGetConfiguration(Device,
613                                       Buffer,
614                                       0xFFFC,
615                                       ValidBytes,
616                                       StartingFeature,
617                                       RequestedType);
618     }
619 #pragma warning(pop)
620 
621     //Start real work
622     srb.TimeOutValue = CDROM_GET_CONFIGURATION_TIMEOUT;
623     srb.CdbLength = 10;
624 
625     cdb->GET_CONFIGURATION.OperationCode = SCSIOP_GET_CONFIGURATION;
626     cdb->GET_CONFIGURATION.RequestType = (UCHAR)RequestedType;
627     cdb->GET_CONFIGURATION.StartingFeature[0] = (UCHAR)(StartingFeature >> 8);
628     cdb->GET_CONFIGURATION.StartingFeature[1] = (UCHAR)(StartingFeature & 0xff);
629     cdb->GET_CONFIGURATION.AllocationLength[0] = (UCHAR)(BufferSize >> 8);
630     cdb->GET_CONFIGURATION.AllocationLength[1] = (UCHAR)(BufferSize & 0xff);
631 
632     status = DeviceSendSrbSynchronously(Device,
633                                         &srb,
634                                         Buffer,
635                                         BufferSize,
636                                         FALSE,
637                                         NULL);
638 
639     TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL,
640                "DeviceGetConfiguration: Status was %x\n", status));
641 
642     if (NT_SUCCESS(status) ||
643         (status == STATUS_BUFFER_OVERFLOW) ||
644         (status == STATUS_DATA_OVERRUN))
645     {
646         ULONG                       returned = srb.DataTransferLength;
647         PGET_CONFIGURATION_HEADER   header = (PGET_CONFIGURATION_HEADER)Buffer;
648         ULONG                       available = (header->DataLength[0] << (8*3)) |
649                                                 (header->DataLength[1] << (8*2)) |
650                                                 (header->DataLength[2] << (8*1)) |
651                                                 (header->DataLength[3] << (8*0)) ;
652 
653         available += RTL_SIZEOF_THROUGH_FIELD(GET_CONFIGURATION_HEADER, DataLength);
654 
655         _Analysis_assume_(srb.DataTransferLength <= BufferSize);
656 
657         // The true usable amount of data returned is the lesser of
658         // * the returned data per the srb.DataTransferLength field
659         // * the total size per the GET_CONFIGURATION_HEADER
660         // This is because ATAPI can't tell how many bytes really
661         // were transferred on success when using DMA.
662         if (available < returned)
663         {
664             returned = available;
665         }
666 
667         NT_ASSERT(returned <= BufferSize);
668         *ValidBytes = (ULONG)returned;
669 
670         //This is succeed case
671         status = STATUS_SUCCESS;
672     }
673     else
674     {
675         TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_IOCTL,
676                    "DeviceGetConfiguration: failed %x\n", status));
677     }
678 
679     return status;
680 }
681 
682 
_IRQL_requires_max_(APC_LEVEL)683 _IRQL_requires_max_(APC_LEVEL)
684 VOID
685 DeviceUpdateMmcWriteCapability(
686     _In_reads_bytes_(BufferSize)
687         PGET_CONFIGURATION_HEADER   Buffer,
688     ULONG const                     BufferSize,
689     BOOLEAN const                   CurrentOnly, // TRUE == can drive write now, FALSE == can drive ever write
690     _Out_ PBOOLEAN                  Writable,
691     _Out_ PFEATURE_NUMBER           ValidationSchema,
692     _Out_ PULONG                    BlockingFactor
693     )
694 /*++
695 
696 Routine Description:
697 
698     This function will allocates configuration buffer and set the size.
699 
700 Arguments:
701 
702     Buffer -
703     BufferSize - size of the buffer
704     CurrentOnly - valid data size in buffer
705     Writable - the buffer is allocationed in non-paged pool.
706     validationSchema - the starting point of the feature list
707     BlockingFactor -
708 
709 Return Value:
710 
711     NTSTATUS
712 
713 NOTE: does not handle case where more than 64k bytes are returned,
714       which requires multiple calls with different starting feature
715       numbers.
716 
717 --*/
718 {
719     //
720     // this routine is used to check if the drive can currently (current==TRUE)
721     // or can ever (current==FALSE) write to media with the current CDROM.SYS
722     // driver.  this check parses the GET_CONFIGURATION response data to search
723     // for the appropriate features and/or if they are current.
724     //
725     // this function should not allocate any resources, and thus may safely
726     // return from any point within the function.
727     //
728     PAGED_CODE();
729 
730     *Writable = FALSE;
731     *ValidationSchema = 0;
732     *BlockingFactor = 1;
733 
734     //
735     // if the drive supports hardware defect management and random writes, that's
736     // sufficient to allow writes.
737     //
738     {
739         PFEATURE_HEADER defectHeader;
740         PFEATURE_HEADER writableHeader;
741 
742         defectHeader   = MmcDataFindFeaturePage(Buffer,
743                                                 BufferSize,
744                                                 FeatureDefectManagement);
745         writableHeader = MmcDataFindFeaturePage(Buffer,
746                                                 BufferSize,
747                                                 FeatureRandomWritable);
748 
749         if (defectHeader == NULL || writableHeader == NULL)
750         {
751             // cannot write this way
752         }
753         else if (!CurrentOnly)
754         {
755             TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,
756                         "DeviceUpdateMmcWriteCapability => Writes supported (defect management)\n"));
757             *Writable = TRUE;
758             return;
759         }
760         else if (defectHeader->Current && writableHeader->Current)
761         {
762             TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,
763                         "DeviceUpdateMmcWriteCapability => Writes *allowed* (defect management)\n"));
764             *Writable = TRUE;
765             *ValidationSchema = FeatureDefectManagement;
766             return;
767         }
768     }
769 
770     // Certain validation schema require the blocking factor
771     // This is a best-effort attempt to ensure that illegal
772     // requests do not make it to drive
773     {
774         PFEATURE_HEADER header;
775         ULONG           additionalLength;
776 
777         // Certain validation schema require the blocking factor
778         // This is a best-effort attempt to ensure  that illegal
779         // requests do not make it to drive
780         additionalLength = RTL_SIZEOF_THROUGH_FIELD(FEATURE_DATA_RANDOM_READABLE, Blocking) - sizeof(FEATURE_HEADER);
781 
782         header = MmcDataFindFeaturePage(Buffer,
783                                         BufferSize,
784                                         FeatureRandomReadable);
785 
786         if ((header != NULL) &&
787             (header->Current) &&
788             (header->AdditionalLength >= additionalLength))
789         {
790             PFEATURE_DATA_RANDOM_READABLE feature = (PFEATURE_DATA_RANDOM_READABLE)header;
791             *BlockingFactor = (feature->Blocking[0] << 8) | feature->Blocking[1];
792         }
793     }
794 
795     // the majority of features to indicate write capability
796     // indicate this by a single feature existance/current bit.
797     // thus, can use a table-based method for the majority
798     // of the detection....
799     {
800         typedef struct {
801             FEATURE_NUMBER  FeatureToFind;    // the ones allowed
802             FEATURE_NUMBER  ValidationSchema; // and their related schema
803         } FEATURE_TO_WRITE_SCHEMA_MAP;
804 
805         static FEATURE_TO_WRITE_SCHEMA_MAP const FeaturesToAllowWritesWith[] = {
806             { FeatureRandomWritable,               FeatureRandomWritable               },
807             { FeatureRigidRestrictedOverwrite,     FeatureRigidRestrictedOverwrite     },
808             { FeatureRestrictedOverwrite,          FeatureRestrictedOverwrite          },
809             { FeatureIncrementalStreamingWritable, FeatureIncrementalStreamingWritable },
810         };
811 
812         ULONG count;
813         for (count = 0; count < RTL_NUMBER_OF(FeaturesToAllowWritesWith); count++)
814         {
815             PFEATURE_HEADER header = MmcDataFindFeaturePage(Buffer,
816                                                             BufferSize,
817                                                             FeaturesToAllowWritesWith[count].FeatureToFind);
818             if (header == NULL)
819             {
820                 // cannot write using this method
821             }
822             else if (!CurrentOnly)
823             {
824                 TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,
825                             "DeviceUpdateMmcWriteCapability => Writes supported (feature %04x)\n",
826                             FeaturesToAllowWritesWith[count].FeatureToFind
827                             ));
828                 *Writable = TRUE;
829                 return;
830             }
831             else if (header->Current)
832             {
833                 TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,
834                             "DeviceUpdateMmcWriteCapability => Writes *allowed* (feature %04x)\n",
835                             FeaturesToAllowWritesWith[count].FeatureToFind
836                             ));
837                 *Writable = TRUE;
838                 *ValidationSchema = FeaturesToAllowWritesWith[count].ValidationSchema;
839                 return;
840             }
841         } // end count loop
842     }
843 
844     // unfortunately, DVD+R media doesn't require IncrementalStreamingWritable feature
845     // to be explicitly set AND it has a seperate bit in the feature to indicate
846     // being able to write to this media type. Thus, use a special case of the above code.
847     {
848         PFEATURE_DATA_DVD_PLUS_R header;
849         ULONG                    additionalLength = FIELD_OFFSET(FEATURE_DATA_DVD_PLUS_R, Reserved2[0]) - sizeof(FEATURE_HEADER);
850         header = MmcDataFindFeaturePage(Buffer,
851                                         BufferSize,
852                                         FeatureDvdPlusR);
853 
854         if (header == NULL || (header->Header.AdditionalLength < additionalLength) || (!header->Write))
855         {
856             // cannot write this way
857         }
858         else if (!CurrentOnly)
859         {
860             TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,
861                         "DeviceUpdateMmcWriteCapability => Writes supported (feature %04x)\n",
862                         FeatureDvdPlusR
863                         ));
864             *Writable = TRUE;
865             return;
866         }
867         else if (header->Header.Current)
868         {
869             TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,
870                         "DeviceUpdateMmcWriteCapability => Writes *allowed* (feature %04x)\n",
871                         FeatureDvdPlusR
872                         ));
873             *Writable = TRUE;
874             *ValidationSchema = FeatureIncrementalStreamingWritable;
875             return;
876         }
877     }
878 
879     // unfortunately, DVD+R DL media doesn't require IncrementalStreamingWritable feature
880     // to be explicitly set AND it has a seperate bit in the feature to indicate
881     // being able to write to this media type. Thus, use a special case of the above code.
882     {
883         PFEATURE_DATA_DVD_PLUS_R_DUAL_LAYER header;
884         ULONG additionalLength = FIELD_OFFSET(FEATURE_DATA_DVD_PLUS_R_DUAL_LAYER, Reserved2[0]) - sizeof(FEATURE_HEADER);
885         header = MmcDataFindFeaturePage(Buffer,
886                                         BufferSize,
887                                         FeatureDvdPlusRDualLayer);
888 
889         if (header == NULL || (header->Header.AdditionalLength < additionalLength) || (!header->Write))
890         {
891             // cannot write this way
892         }
893         else if (!CurrentOnly)
894         {
895             TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,
896                         "DeviceUpdateMmcWriteCapability => Writes supported (feature %04x)\n",
897                         FeatureDvdPlusRDualLayer
898                         ));
899             *Writable = TRUE;
900             return;
901         }
902         else if (header->Header.Current)
903         {
904             TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,
905                         "DeviceUpdateMmcWriteCapability => Writes *allowed* (feature %04x)\n",
906                         FeatureDvdPlusRDualLayer
907                         ));
908             *Writable = TRUE;
909             *ValidationSchema = FeatureIncrementalStreamingWritable;
910             return;
911         }
912     }
913 
914     // There are currently a number of drives on the market
915     // that fail to report:
916     // (a) FeatureIncrementalStreamingWritable as current
917     //     for CD-R / DVD-R profile.
918     // (b) FeatureRestrictedOverwrite as current for CD-RW
919     //     profile
920     // (c) FeatureRigidRestrictedOverwrite as current for
921     //     DVD-RW profile
922     //
923     // Thus, use the profiles also.
924     {
925         PFEATURE_HEADER header;
926         header = MmcDataFindFeaturePage(Buffer,
927                                         BufferSize,
928                                         FeatureProfileList);
929 
930         if (header != NULL && header->Current)
931         {
932             // verify buffer bounds -- the below routine presumes full profile list provided
933             PUCHAR bufferEnd = ((PUCHAR)Buffer) + BufferSize;
934             PUCHAR headerEnd = ((PUCHAR)header) + header->AdditionalLength + RTL_SIZEOF_THROUGH_FIELD(FEATURE_HEADER, AdditionalLength);
935             if (bufferEnd >= headerEnd) // this _should_ never occurr, but....
936             {
937                 // Profiles don't contain any data other than current/not current.
938                 // thus, can generically loop through them to see if any of the
939                 // below (in order of preference) are current.
940                 typedef struct {
941                     FEATURE_PROFILE_TYPE ProfileToFind;    // the ones allowed
942                     FEATURE_NUMBER       ValidationSchema; // and their related schema
943                 } PROFILE_TO_WRITE_SCHEMA_MAP;
944 
945                 static PROFILE_TO_WRITE_SCHEMA_MAP const ProfilesToAllowWritesWith[] = {
946                     { ProfileDvdRewritable,  FeatureRigidRestrictedOverwrite     },
947                     { ProfileCdRewritable,   FeatureRestrictedOverwrite },
948                     { ProfileDvdRecordable,  FeatureIncrementalStreamingWritable },
949                     { ProfileCdRecordable,   FeatureIncrementalStreamingWritable },
950                 };
951 
952                 ULONG count;
953                 for (count = 0; count < RTL_NUMBER_OF(ProfilesToAllowWritesWith); count++)
954                 {
955                     BOOLEAN exists = FALSE;
956                     MmcDataFindProfileInProfiles((PFEATURE_DATA_PROFILE_LIST)header,
957                                                  ProfilesToAllowWritesWith[count].ProfileToFind,
958                                                  CurrentOnly,
959                                                  &exists);
960                     if (exists)
961                     {
962                         TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT,
963                                     "DeviceUpdateMmcWriteCapability => Writes %s (profile %04x)\n",
964                                     (CurrentOnly ? "*allowed*" : "supported"),
965                                     FeatureDvdPlusR
966                                     ));
967 
968                         *Writable = TRUE;
969                         *ValidationSchema = ProfilesToAllowWritesWith[count].ValidationSchema;
970                         return;
971                     }
972                 } // end count loop
973             } // end if (bufferEnd >= headerEnd)
974 
975         } // end if (header != NULL && header->Current)
976     }
977 
978     // nothing matched to say it's writable.....
979     return;
980 }
981 
_IRQL_requires_max_(APC_LEVEL)982 _IRQL_requires_max_(APC_LEVEL)
983 PVOID
984 MmcDataFindFeaturePage(
985     _In_reads_bytes_(Length)
986         PGET_CONFIGURATION_HEADER   FeatureBuffer,
987     ULONG const                     Length,
988     FEATURE_NUMBER const            Feature
989     )
990 /*++
991 
992 Routine Description:
993 
994     search the specific feature from feature list buffer
995 
996 Arguments:
997 
998     FeatureBuffer - buffer of feature list
999     Length - size of the buffer
1000     Feature - feature wanted to find
1001 
1002 Return Value:
1003 
1004     PVOID - if found, pointer of starting address of the specific feature.
1005             otherwise, NULL.
1006 
1007 --*/
1008 {
1009     PUCHAR buffer;
1010     PUCHAR limit;
1011     ULONG  validLength;
1012 
1013     PAGED_CODE();
1014 
1015     if (Length < sizeof(GET_CONFIGURATION_HEADER) + sizeof(FEATURE_HEADER)) {
1016         return NULL;
1017     }
1018 
1019     // Calculate the length of valid data available in the
1020     // capabilities buffer from the DataLength field
1021     REVERSE_BYTES(&validLength, FeatureBuffer->DataLength);
1022     validLength += RTL_SIZEOF_THROUGH_FIELD(GET_CONFIGURATION_HEADER, DataLength);
1023 
1024     // set limit to point to first illegal address
1025     limit  = (PUCHAR)FeatureBuffer;
1026     limit += min(Length, validLength);
1027 
1028     // set buffer to point to first page
1029     buffer = FeatureBuffer->Data;
1030 
1031     // loop through each page until we find the requested one, or
1032     // until it's not safe to access the entire feature header
1033     // (if equal, have exactly enough for the feature header)
1034     while (buffer + sizeof(FEATURE_HEADER) <= limit)
1035     {
1036         PFEATURE_HEADER header = (PFEATURE_HEADER)buffer;
1037         FEATURE_NUMBER  thisFeature;
1038 
1039         thisFeature  = (header->FeatureCode[0] << 8) |
1040                        (header->FeatureCode[1]);
1041 
1042         if (thisFeature == Feature)
1043         {
1044             PUCHAR temp;
1045 
1046             // if don't have enough memory to safely access all the feature
1047             // information, return NULL
1048             temp = buffer;
1049             temp += sizeof(FEATURE_HEADER);
1050             temp += header->AdditionalLength;
1051 
1052             if (temp > limit)
1053             {
1054                 // this means the transfer was cut-off, an insufficiently
1055                 // small buffer was given, or other arbitrary error.  since
1056                 // it's not safe to view the amount of data (even though
1057                 // the header is safe) in this feature, pretend it wasn't
1058                 // transferred at all...
1059                 TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL,
1060                            "Feature %x exists, but not safe to access all its "
1061                            "data.  returning NULL\n", Feature));
1062                 return NULL;
1063             }
1064             else
1065             {
1066                 return buffer;
1067             }
1068         }
1069 
1070         if ((header->AdditionalLength % 4) &&
1071             !(Feature >= 0xff00 && Feature <= 0xffff))
1072         {
1073             return NULL;
1074         }
1075 
1076         buffer += sizeof(FEATURE_HEADER);
1077         buffer += header->AdditionalLength;
1078 
1079     }
1080     return NULL;
1081 }
1082 
_IRQL_requires_max_(APC_LEVEL)1083 _IRQL_requires_max_(APC_LEVEL)
1084 VOID
1085 MmcDataFindProfileInProfiles(
1086     _In_ FEATURE_DATA_PROFILE_LIST const* ProfileHeader,
1087     _In_ FEATURE_PROFILE_TYPE const       ProfileToFind,
1088     _In_ BOOLEAN const                    CurrentOnly,
1089     _Out_ PBOOLEAN                        Found
1090     )
1091 /*++
1092 
1093 Routine Description:
1094 
1095     search the specific feature from feature list buffer
1096 
1097 Arguments:
1098 
1099     ProfileHeader - buffer of profile list
1100     ProfileToFind - profile to be found
1101     CurrentOnly -
1102 
1103 Return Value:
1104 
1105     Found - found or not
1106 
1107 --*/
1108 {
1109     FEATURE_DATA_PROFILE_LIST_EX const * profile;
1110     ULONG numberOfProfiles;
1111     ULONG i;
1112 
1113     PAGED_CODE();
1114 
1115     // initialize output
1116     *Found = FALSE;
1117 
1118     // sanity check
1119     if (ProfileHeader->Header.AdditionalLength % 2 != 0)
1120     {
1121         TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL,
1122                    "Profile total length %x is not integral multiple of 4\n",
1123                    ProfileHeader->Header.AdditionalLength));
1124         NT_ASSERT(FALSE);
1125         return;
1126     }
1127 
1128     // calculate number of profiles
1129     numberOfProfiles = ProfileHeader->Header.AdditionalLength / 4;
1130     profile = ProfileHeader->Profiles; // zero-sized array
1131 
1132     // loop through profiles
1133     for (i = 0; i < numberOfProfiles; i++)
1134     {
1135         FEATURE_PROFILE_TYPE currentProfile;
1136 
1137         currentProfile = (profile->ProfileNumber[0] << 8) |
1138                          (profile->ProfileNumber[1] & 0xff);
1139 
1140         if (currentProfile == ProfileToFind)
1141         {
1142             if (profile->Current || (!CurrentOnly))
1143             {
1144                 *Found = TRUE;
1145             }
1146         }
1147 
1148         profile++;
1149     }
1150 
1151     return;
1152 }
1153 
1154 _IRQL_requires_max_(APC_LEVEL)
1155 _Ret_range_(-1,MAXIMUM_RETRY_FOR_SINGLE_IO_IN_100NS_UNITS)
1156 LONGLONG
DeviceRetryTimeGuessBasedOnProfile(FEATURE_PROFILE_TYPE const Profile)1157 DeviceRetryTimeGuessBasedOnProfile(
1158     FEATURE_PROFILE_TYPE const Profile
1159     )
1160 /*++
1161 
1162 Routine Description:
1163 
1164     determine the retry time based on profile
1165 
1166 Arguments:
1167 
1168     Profile -
1169 
1170 Return Value:
1171 
1172     LONGLONG - retry time
1173 
1174 --*/
1175 {
1176     LONGLONG result = -1; // this means we have no idea
1177 
1178     PAGED_CODE();
1179 
1180     switch (Profile)
1181     {
1182     case ProfileInvalid:               // = 0x0000,
1183     case ProfileNonRemovableDisk:      // = 0x0001,
1184     case ProfileRemovableDisk:         // = 0x0002,
1185     case ProfileMOErasable:            // = 0x0003,
1186     case ProfileMOWriteOnce:           // = 0x0004,
1187     case ProfileAS_MO:                 // = 0x0005,
1188     // Reserved                             0x0006 - 0x0007,
1189     // Reserved                             0x000b - 0x000f,
1190     // Reserved                             0x0017 - 0x0019
1191     // Reserved                             0x001C - 001F
1192     // Reserved                             0x0023 - 0x0029
1193     // Reserved                             0x002C - 0x003F
1194     // Reserved                             0x0044 - 0x004F
1195     // Reserved                             0x0053 - 0xfffe
1196     case ProfileNonStandard:          //  = 0xffff
1197     default:
1198     {
1199         NOTHING; // no default
1200         break;
1201     }
1202 
1203     case ProfileCdrom:                 // = 0x0008,
1204     case ProfileCdRecordable:          // = 0x0009,
1205     case ProfileCdRewritable:          // = 0x000a,
1206     case ProfileDDCdrom:               // = 0x0020, // obsolete
1207     case ProfileDDCdRecordable:        // = 0x0021, // obsolete
1208     case ProfileDDCdRewritable:        // = 0x0022, // obsolete
1209     {
1210         // 4x is ok as all CD drives have
1211         // at least 64k*4 (256k) buffer
1212         // and this is just a first-pass
1213         // guess based only on profile
1214         result = WRITE_RETRY_DELAY_CD_4x;
1215         break;
1216     }
1217     case ProfileDvdRom:                // = 0x0010,
1218     case ProfileDvdRecordable:         // = 0x0011,
1219     case ProfileDvdRam:                // = 0x0012,
1220     case ProfileDvdRewritable:         // = 0x0013,  // restricted overwrite
1221     case ProfileDvdRWSequential:       // = 0x0014,
1222     case ProfileDvdDashRLayerJump:     // = 0x0016,
1223     case ProfileDvdPlusRW:             // = 0x001A,
1224     case ProfileDvdPlusR:              // = 0x001B,
1225     {
1226         result = WRITE_RETRY_DELAY_DVD_1x;
1227         break;
1228     }
1229     case ProfileDvdDashRDualLayer:     // = 0x0015,
1230     case ProfileDvdPlusRWDualLayer:    // = 0x002A,
1231     case ProfileDvdPlusRDualLayer:     // = 0x002B,
1232     {
1233         result = WRITE_RETRY_DELAY_DVD_1x;
1234         break;
1235     }
1236 
1237     case ProfileBDRom:                 // = 0x0040,
1238     case ProfileBDRSequentialWritable: // = 0x0041,  // BD-R 'SRM'
1239     case ProfileBDRRandomWritable:     // = 0x0042,  // BD-R 'RRM'
1240     case ProfileBDRewritable:          // = 0x0043,
1241     {
1242         // I could not find specifications for the
1243         // minimal 1x data rate for BD media.  Use
1244         // HDDVD values for now, since they are
1245         // likely to be similar.  Also, all media
1246         // except for CD, DVD, and AS-MO should
1247         // already fully support GET_CONFIG, so
1248         // this guess is only used if we fail to
1249         // get a performance descriptor....
1250         result = WRITE_RETRY_DELAY_HDDVD_1x;
1251         break;
1252     }
1253 
1254     case ProfileHDDVDRom:              // = 0x0050,
1255     case ProfileHDDVDRecordable:       // = 0x0051,
1256     case ProfileHDDVDRam:              // = 0x0052,
1257     {
1258         // All HDDVD drives support GET_PERFORMANCE
1259         // so this guess is fine at 1x....
1260         result = WRITE_RETRY_DELAY_HDDVD_1x;
1261         break;
1262     }
1263 
1264     // addition of any further profile types is not
1265     // technically required as GET PERFORMANCE
1266     // should succeed for all future drives.  However,
1267     // it is useful in case GET PERFORMANCE does
1268     // fail for other reasons (i.e. bus resets, etc)
1269 
1270     } // end switch(Profile)
1271 
1272     return result;
1273 }
1274 
1275 _IRQL_requires_max_(APC_LEVEL)
1276 _Ret_range_(-1,MAXIMUM_RETRY_FOR_SINGLE_IO_IN_100NS_UNITS)
1277 LONGLONG
DeviceRetryTimeDetectionBasedOnModePage2A(_In_ PCDROM_DEVICE_EXTENSION DeviceExtension)1278 DeviceRetryTimeDetectionBasedOnModePage2A(
1279     _In_ PCDROM_DEVICE_EXTENSION DeviceExtension
1280     )
1281 /*++
1282 
1283 Routine Description:
1284 
1285     determine the retry time based on mode sense data
1286 
1287 Arguments:
1288 
1289     DeviceExtension - device context
1290 
1291 Return Value:
1292 
1293     LONGLONG - retry time
1294 
1295 --*/
1296 {
1297     NTSTATUS    status;
1298     ULONG       transferSize = min(0xFFF0, DeviceExtension->ScratchContext.ScratchBufferSize);
1299     CDB         cdb;
1300     LONGLONG    result = -1;
1301 
1302     PAGED_CODE();
1303 
1304     ScratchBuffer_BeginUse(DeviceExtension);
1305 
1306     RtlZeroMemory(&cdb, sizeof(CDB));
1307     // Set up the CDB
1308     cdb.MODE_SENSE10.OperationCode = SCSIOP_MODE_SENSE10;
1309     cdb.MODE_SENSE10.Dbd           = 1;
1310     cdb.MODE_SENSE10.PageCode      = MODE_PAGE_CAPABILITIES;
1311     cdb.MODE_SENSE10.AllocationLength[0] = (UCHAR)(transferSize >> 8);
1312     cdb.MODE_SENSE10.AllocationLength[1] = (UCHAR)(transferSize & 0xFF);
1313 
1314     status = ScratchBuffer_ExecuteCdb(DeviceExtension, NULL, transferSize, TRUE, &cdb, 10);
1315 
1316     // analyze the data on success....
1317     if (NT_SUCCESS(status))
1318     {
1319         MODE_PARAMETER_HEADER10 const* header = DeviceExtension->ScratchContext.ScratchBuffer;
1320         CDVD_CAPABILITIES_PAGE const*  page = NULL;
1321         ULONG dataLength = (header->ModeDataLength[0] << (8*1)) |
1322                            (header->ModeDataLength[1] << (8*0)) ;
1323 
1324         // no possible overflow
1325         if (dataLength != 0)
1326         {
1327             dataLength += RTL_SIZEOF_THROUGH_FIELD(MODE_PARAMETER_HEADER10, ModeDataLength);
1328         }
1329 
1330         // If it's not abundantly clear, we really don't trust the drive
1331         // to be returning valid data.  Get the page pointer and usable
1332         // size of the page here...
1333         if (dataLength < sizeof(MODE_PARAMETER_HEADER10))
1334         {
1335             dataLength = 0;
1336         }
1337         else if (dataLength > DeviceExtension->ScratchContext.ScratchBufferSize)
1338         {
1339             dataLength = 0;
1340         }
1341         else if ((header->BlockDescriptorLength[1] == 0) &&
1342                  (header->BlockDescriptorLength[0] == 0))
1343         {
1344             dataLength -= sizeof(MODE_PARAMETER_HEADER10);
1345             page = (CDVD_CAPABILITIES_PAGE const *)(header + 1);
1346         }
1347         else if ((header->BlockDescriptorLength[1] == 0) &&
1348                  (header->BlockDescriptorLength[0] == sizeof(MODE_PARAMETER_BLOCK)))
1349         {
1350             dataLength -= sizeof(MODE_PARAMETER_HEADER10);
1351             dataLength -= min(dataLength, sizeof(MODE_PARAMETER_BLOCK));
1352             page = (CDVD_CAPABILITIES_PAGE const *)
1353                                             ( ((PUCHAR)header) +
1354                                               sizeof(MODE_PARAMETER_HEADER10) +
1355                                               sizeof(MODE_PARAMETER_BLOCK)
1356                                               );
1357         }
1358 
1359         // Change dataLength from the size available per the header to
1360         // the size available per the page itself.
1361         if ((page != NULL) &&
1362             (dataLength >= RTL_SIZEOF_THROUGH_FIELD(CDVD_CAPABILITIES_PAGE, PageLength))
1363             )
1364         {
1365             dataLength = min(dataLength, ((ULONG)(page->PageLength) + 2));
1366         }
1367 
1368         // Ignore the page if the fastest write speed field isn't available.
1369         if ((page != NULL) &&
1370             (dataLength < RTL_SIZEOF_THROUGH_FIELD(CDVD_CAPABILITIES_PAGE, WriteSpeedMaximum))
1371             )
1372         {
1373             TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT,
1374                         "ModePage 2Ah was requested, but drive reported "
1375                         "only %x bytes (%x needed). Ignoring.\n",
1376                         dataLength,
1377                         RTL_SIZEOF_THROUGH_FIELD(CDVD_CAPABILITIES_PAGE, WriteSpeedMaximum)
1378                         ));
1379             page = NULL;
1380         }
1381 
1382         // Verify the page we requested is the one the drive actually provided
1383         if ((page != NULL) && (page->PageCode != MODE_PAGE_CAPABILITIES))
1384         {
1385             TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT,
1386                         "ModePage 2Ah was requested, but drive reported "
1387                         "page %x\n",
1388                         page->PageCode
1389                         ));
1390             page = NULL;
1391         }
1392 
1393         // If _everything_ succeeded, then use the speed value in the page!
1394         if (page != NULL)
1395         {
1396             ULONG temp =
1397                 (page->WriteSpeedMaximum[0] << (8*1)) |
1398                 (page->WriteSpeedMaximum[1] << (8*0)) ;
1399             // stored as 1,000 byte increments...
1400             temp *= 1000;
1401             // typically stored at 2448 bytes/sector due to CD media
1402             // error up to 20% high by presuming it returned 2048 data
1403             // and convert to sectors/second
1404             temp /= 2048;
1405             // currently: sectors/sec
1406             // ignore too-small or zero values
1407             if (temp != 0)
1408             {
1409                 result = ConvertSectorsPerSecondTo100nsUnitsFor64kWrite(temp);
1410             }
1411         }
1412     }
1413 
1414     ScratchBuffer_EndUse(DeviceExtension);
1415 
1416     return result;
1417 }
1418 
1419 
1420 _IRQL_requires_max_(APC_LEVEL)
1421 _Ret_range_(-1,MAXIMUM_RETRY_FOR_SINGLE_IO_IN_100NS_UNITS)
1422 LONGLONG
DeviceRetryTimeDetectionBasedOnGetPerformance(_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,_In_ BOOLEAN UseLegacyNominalPerformance)1423 DeviceRetryTimeDetectionBasedOnGetPerformance(
1424     _In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
1425     _In_ BOOLEAN                 UseLegacyNominalPerformance
1426     )
1427 /*++
1428 
1429 Routine Description:
1430 
1431     determine the retry time based on get performance data
1432 
1433 Arguments:
1434 
1435     DeviceExtension - device context
1436     UseLegacyNominalPerformance -
1437 
1438 Return Value:
1439 
1440     LONGLONG - retry time
1441 
1442 --*/
1443 {
1444     typedef struct _GET_PERFORMANCE_HEADER {
1445         UCHAR TotalDataLength[4]; // not including this field
1446         UCHAR Except : 1;
1447         UCHAR Write  : 1;
1448         UCHAR Reserved0 : 6;
1449         UCHAR Reserved1[3];
1450     } GET_PERFORMANCE_HEADER, *PGET_PERFORMANCE_HEADER;
1451     C_ASSERT( sizeof(GET_PERFORMANCE_HEADER) == 8);
1452 
1453     typedef struct _GET_PERFORMANCE_NOMINAL_PERFORMANCE_DESCRIPTOR {
1454         UCHAR StartLba[4];
1455         UCHAR StartPerformance[4];
1456         UCHAR EndLba[4];
1457         UCHAR EndPerformance[4];
1458     } GET_PERFORMANCE_NOMINAL_PERFORMANCE_DESCRIPTOR, *PGET_PERFORMANCE_NOMINAL_PERFORMANCE_DESCRIPTOR;
1459     C_ASSERT( sizeof(GET_PERFORMANCE_NOMINAL_PERFORMANCE_DESCRIPTOR) == 16);
1460 
1461 
1462     typedef struct _GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR {
1463         UCHAR MixedReadWrite          : 1;
1464         UCHAR GuaranteedForWholeMedia : 1;
1465         UCHAR Reserved0_RDD           : 1;
1466         UCHAR WriteRotationControl    : 2;
1467         UCHAR Reserved1               : 3;
1468         UCHAR Reserved2[3];
1469 
1470         UCHAR MediaCapacity[4];
1471         UCHAR ReadSpeedKilobytesPerSecond[4];
1472         UCHAR WriteSpeedKilobytesPerSecond[4];
1473     } GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR, *PGET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR;
1474     C_ASSERT( sizeof(GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR) == 16);
1475 
1476     //////
1477 
1478     NTSTATUS    status;
1479     LONGLONG    result = -1;
1480 
1481     // transfer size -- descriptors + 8 byte header
1482     // Note: this size is identical for both descriptor types
1483     C_ASSERT( sizeof(GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR) == sizeof(GET_PERFORMANCE_NOMINAL_PERFORMANCE_DESCRIPTOR));
1484 
1485     ULONG const maxDescriptors = min(200, (DeviceExtension->ScratchContext.ScratchBufferSize-sizeof(GET_PERFORMANCE_HEADER))/sizeof(GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR));
1486     ULONG       validDescriptors = 0;
1487     ULONG       transferSize = sizeof(GET_PERFORMANCE_HEADER) + (maxDescriptors*sizeof(GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR));
1488     CDB         cdb;
1489 
1490     PAGED_CODE();
1491 
1492     ScratchBuffer_BeginUse(DeviceExtension);
1493 
1494     RtlZeroMemory(&cdb, sizeof(CDB));
1495     // Set up the CDB
1496     if (UseLegacyNominalPerformance)
1497     {
1498         cdb.GET_PERFORMANCE.OperationCode = SCSIOP_GET_PERFORMANCE;
1499         cdb.GET_PERFORMANCE.Except = 0;
1500         cdb.GET_PERFORMANCE.Write = 1;
1501         cdb.GET_PERFORMANCE.Tolerance = 2; // only defined option
1502         cdb.GET_PERFORMANCE.MaximumNumberOfDescriptors[1] = (UCHAR)maxDescriptors;
1503         cdb.GET_PERFORMANCE.Type = 0; // legacy nominal descriptors
1504     }
1505     else
1506     {
1507         cdb.GET_PERFORMANCE.OperationCode = SCSIOP_GET_PERFORMANCE;
1508         cdb.GET_PERFORMANCE.MaximumNumberOfDescriptors[1] = (UCHAR)maxDescriptors;
1509         cdb.GET_PERFORMANCE.Type = 3; // write speed
1510     }
1511 
1512     status = ScratchBuffer_ExecuteCdbEx(DeviceExtension, NULL, transferSize, TRUE, &cdb, 12, CDROM_GET_PERFORMANCE_TIMEOUT);
1513 
1514     // determine how many valid descriptors there actually are
1515     if (NT_SUCCESS(status))
1516     {
1517         GET_PERFORMANCE_HEADER const* header = (GET_PERFORMANCE_HEADER const*)DeviceExtension->ScratchContext.ScratchBuffer;
1518         ULONG temp1 = (header->TotalDataLength[0] << (8*3)) |
1519                       (header->TotalDataLength[1] << (8*2)) |
1520                       (header->TotalDataLength[2] << (8*1)) |
1521                       (header->TotalDataLength[3] << (8*0)) ;
1522 
1523         // adjust data size for header
1524         if (temp1 + (ULONG)RTL_SIZEOF_THROUGH_FIELD(GET_PERFORMANCE_HEADER, TotalDataLength) < temp1)
1525         {
1526             temp1 = 0;
1527         }
1528         else if (temp1 != 0)
1529         {
1530             temp1 += RTL_SIZEOF_THROUGH_FIELD(GET_PERFORMANCE_HEADER, TotalDataLength);
1531         }
1532 
1533         if (temp1 == 0)
1534         {
1535             // no data returned
1536         }
1537         else if (temp1 <= sizeof(GET_PERFORMANCE_HEADER))
1538         {
1539             // only the header returned, no descriptors
1540         }
1541         else if (UseLegacyNominalPerformance &&
1542                  ((header->Except != 0) || (header->Write == 0))
1543                  )
1544         {
1545             // bad data being returned -- ignore it
1546         }
1547         else if (!UseLegacyNominalPerformance &&
1548                  ((header->Except != 0) || (header->Write != 0))
1549                  )
1550         {
1551             // returning Performance (Type 0) data, not requested Write Speed (Type 3) data
1552         }
1553         else if ( (temp1 - sizeof(GET_PERFORMANCE_HEADER)) % sizeof(GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR) != 0)
1554         {
1555             // Note: this size is identical for both descriptor types
1556             C_ASSERT( sizeof(GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR) == sizeof(GET_PERFORMANCE_NOMINAL_PERFORMANCE_DESCRIPTOR));
1557 
1558             // not returning valid data....
1559         }
1560         else // save how many are usable
1561         {
1562             // Note: this size is identical for both descriptor types
1563             C_ASSERT( sizeof(GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR) == sizeof(GET_PERFORMANCE_NOMINAL_PERFORMANCE_DESCRIPTOR));
1564 
1565             // take the smaller usable value
1566             temp1 = min(temp1, DeviceExtension->ScratchContext.ScratchSrb->DataTransferLength);
1567             // then determine the usable descriptors
1568             validDescriptors = (temp1 - sizeof(GET_PERFORMANCE_HEADER)) / sizeof(GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR);
1569         }
1570     }
1571 
1572     // The drive likely supports this command.
1573     // Verify the data makes sense.
1574     if (NT_SUCCESS(status))
1575     {
1576         ULONG       i;
1577         GET_PERFORMANCE_HEADER const*                 header = (GET_PERFORMANCE_HEADER const*)DeviceExtension->ScratchContext.ScratchBuffer;
1578         GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR const* descriptor = (GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR const*)(header+1); // pointer math
1579 
1580         // NOTE: We could write this loop twice, once for each write descriptor type
1581         //       However, the only fields of interest are the writeKBps field (Type 3) and
1582         //       the EndPerformance field (Type 0), which both exist in the same exact
1583         //       location and have essentially the same meaning.  So, just use the same
1584         //       loop/structure pointers for both of the to simplify the readability of
1585         //       this code.  The C_ASSERT()s here verify this at compile-time.
1586 
1587         C_ASSERT( sizeof(GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR) == sizeof(GET_PERFORMANCE_NOMINAL_PERFORMANCE_DESCRIPTOR));
1588         C_ASSERT( FIELD_OFFSET(GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR, WriteSpeedKilobytesPerSecond) ==
1589                   FIELD_OFFSET(GET_PERFORMANCE_NOMINAL_PERFORMANCE_DESCRIPTOR, EndPerformance)
1590                   );
1591         C_ASSERT( RTL_FIELD_SIZE(GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR, WriteSpeedKilobytesPerSecond) ==
1592                   RTL_FIELD_SIZE(GET_PERFORMANCE_NOMINAL_PERFORMANCE_DESCRIPTOR, EndPerformance)
1593                   );
1594 
1595         // loop through them all, and find the fastest listed write speed
1596         for (i = 0; NT_SUCCESS(status) && (i <validDescriptors); descriptor++, i++)
1597         {
1598             ULONG const writeKBps =
1599                 (descriptor->WriteSpeedKilobytesPerSecond[0] << (8*3)) |
1600                 (descriptor->WriteSpeedKilobytesPerSecond[1] << (8*2)) |
1601                 (descriptor->WriteSpeedKilobytesPerSecond[2] << (8*1)) |
1602                 (descriptor->WriteSpeedKilobytesPerSecond[3] << (8*0)) ;
1603 
1604             // Avoid overflow and still have good estimates
1605             // 0x1 0000 0000 / 1000  == 0x00418937 == maximum writeKBps to multiple first
1606             ULONG const sectorsPerSecond =
1607                 (writeKBps > 0x00418937) ? // would overflow occur by multiplying by 1000?
1608                 ((writeKBps / 2048) * 1000) : // must divide first, minimal loss of accuracy
1609                 ((writeKBps * 1000) / 2048) ; // must multiply first, avoid loss of accuracy
1610 
1611             if (sectorsPerSecond <= 0)
1612             {
1613                 break; // out of the loop -- no longer valid data (very defensive programming)
1614             }
1615 
1616             // we have at least one valid result, so prevent returning -1 as our result
1617             if (result == -1) { result = MAXIMUM_RETRY_FOR_SINGLE_IO_IN_100NS_UNITS; }
1618 
1619             // take the fastest speed (smallest wait time) we've found thus far
1620             result = min(result, ConvertSectorsPerSecondTo100nsUnitsFor64kWrite(sectorsPerSecond));
1621         }
1622     }
1623 
1624     ScratchBuffer_EndUse(DeviceExtension);
1625 
1626     return result;
1627 }
1628 
1629 #pragma warning(pop) // un-sets any local warning changes
1630