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 1630