[IMM32] Improve ImmGetImeInfoEx (#3833)
[reactos.git] / drivers / storage / class / cdrom / mmc.c
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
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
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
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
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
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
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
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
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
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
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
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