/*-- Copyright (C) Microsoft Corporation. All rights reserved. Module Name: mmc.c Abstract: Include all funtions relate to MMC Environment: kernel mode only Notes: Revision History: --*/ #include "stddef.h" #include "string.h" #include "ntddk.h" #include "ntddstor.h" #include "cdrom.h" #include "mmc.h" #include "scratch.h" #ifdef DEBUG_USE_WPP #include "mmc.tmh" #endif #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, DeviceDeallocateMmcResources) #pragma alloc_text(PAGE, DeviceAllocateMmcResources) #pragma alloc_text(PAGE, DeviceUpdateMmcCapabilities) #pragma alloc_text(PAGE, DeviceGetConfigurationWithAlloc) #pragma alloc_text(PAGE, DeviceGetConfiguration) #pragma alloc_text(PAGE, DeviceUpdateMmcWriteCapability) #pragma alloc_text(PAGE, MmcDataFindFeaturePage) #pragma alloc_text(PAGE, MmcDataFindProfileInProfiles) #pragma alloc_text(PAGE, DeviceRetryTimeGuessBasedOnProfile) #pragma alloc_text(PAGE, DeviceRetryTimeDetectionBasedOnModePage2A) #pragma alloc_text(PAGE, DeviceRetryTimeDetectionBasedOnGetPerformance) #endif #pragma warning(push) #pragma warning(disable:4214) // nonstandard extension used : bit field types other than int _IRQL_requires_max_(APC_LEVEL) VOID DeviceDeallocateMmcResources( _In_ WDFDEVICE Device ) /*++ Routine Description: release MMC resources Arguments: Device - device object Return Value: none --*/ { PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(Device); PCDROM_DATA cddata = &(deviceExtension->DeviceAdditionalData); PCDROM_MMC_EXTENSION mmcData = &cddata->Mmc; PAGED_CODE(); if (mmcData->CapabilitiesIrp) { IoFreeIrp(mmcData->CapabilitiesIrp); mmcData->CapabilitiesIrp = NULL; } if (mmcData->CapabilitiesMdl) { IoFreeMdl(mmcData->CapabilitiesMdl); mmcData->CapabilitiesMdl = NULL; } if (mmcData->CapabilitiesBuffer) { ExFreePool(mmcData->CapabilitiesBuffer); mmcData->CapabilitiesBuffer = NULL; } if (mmcData->CapabilitiesRequest) { WdfObjectDelete(mmcData->CapabilitiesRequest); mmcData->CapabilitiesRequest = NULL; } mmcData->CapabilitiesBufferSize = 0; mmcData->IsMmc = FALSE; mmcData->WriteAllowed = FALSE; return; } _IRQL_requires_max_(PASSIVE_LEVEL) NTSTATUS DeviceAllocateMmcResources( _In_ WDFDEVICE Device ) /*++ Routine Description: allocate all MMC resources needed Arguments: Device - device object Return Value: NTSTATUS --*/ { NTSTATUS status = STATUS_SUCCESS; PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(Device); PCDROM_DATA cddata = &(deviceExtension->DeviceAdditionalData); PCDROM_MMC_EXTENSION mmcData = &(cddata->Mmc); WDF_OBJECT_ATTRIBUTES attributes = {0}; PAGED_CODE(); NT_ASSERT(mmcData->CapabilitiesBuffer == NULL); NT_ASSERT(mmcData->CapabilitiesBufferSize == 0); // allocate the buffer and set the buffer size. // retrieve drive configuration information. status = DeviceGetConfigurationWithAlloc(Device, &mmcData->CapabilitiesBuffer, &mmcData->CapabilitiesBufferSize, FeatureProfileList, SCSI_GET_CONFIGURATION_REQUEST_TYPE_ALL); if (!NT_SUCCESS(status)) { NT_ASSERT(mmcData->CapabilitiesBuffer == NULL); NT_ASSERT(mmcData->CapabilitiesBufferSize == 0); return status; } NT_ASSERT(mmcData->CapabilitiesBuffer != NULL); NT_ASSERT(mmcData->CapabilitiesBufferSize != 0); // Create an MDL over the new Buffer (allocated by DeviceGetConfiguration) mmcData->CapabilitiesMdl = IoAllocateMdl(mmcData->CapabilitiesBuffer, mmcData->CapabilitiesBufferSize, FALSE, FALSE, NULL); if (mmcData->CapabilitiesMdl == NULL) { ExFreePool(mmcData->CapabilitiesBuffer); mmcData->CapabilitiesBuffer = NULL; mmcData->CapabilitiesBufferSize = 0; return STATUS_INSUFFICIENT_RESOURCES; } // Create an IRP from which we will create a WDFREQUEST mmcData->CapabilitiesIrp = IoAllocateIrp(deviceExtension->DeviceObject->StackSize + 1, FALSE); if (mmcData->CapabilitiesIrp == NULL) { IoFreeMdl(mmcData->CapabilitiesMdl); mmcData->CapabilitiesMdl = NULL; ExFreePool(mmcData->CapabilitiesBuffer); mmcData->CapabilitiesBuffer = NULL; mmcData->CapabilitiesBufferSize = 0; return STATUS_INSUFFICIENT_RESOURCES; } // create WDF request object WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CDROM_REQUEST_CONTEXT); status = WdfRequestCreateFromIrp(&attributes, mmcData->CapabilitiesIrp, FALSE, &mmcData->CapabilitiesRequest); if (!NT_SUCCESS(status)) { return status; } return STATUS_SUCCESS; } _IRQL_requires_max_(PASSIVE_LEVEL) NTSTATUS DeviceUpdateMmcCapabilities( _In_ WDFDEVICE Device ) /*++ Routine Description: issue get congiguration command ans save result in device extension Arguments: Device - device object Return Value: NTSTATUS --*/ { NTSTATUS status = STATUS_SUCCESS; PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(Device); PCDROM_DATA cdData = &(deviceExtension->DeviceAdditionalData); PCDROM_MMC_EXTENSION mmcData = &(cdData->Mmc); ULONG returnedBytes = 0; LONG updateState; PAGED_CODE(); // first of all, check if we're still in the CdromMmcUpdateRequired state // and, if yes, change it to CdromMmcUpdateStarted. updateState = InterlockedCompareExchange((PLONG)&(cdData->Mmc.UpdateState), CdromMmcUpdateStarted, CdromMmcUpdateRequired); if (updateState != CdromMmcUpdateRequired) { // Mmc capabilities have been already updated or are in the process of // being updated - just return STATUS_SUCCESS return STATUS_SUCCESS; } // default to read-only, no Streaming, non-blank mmcData->WriteAllowed = FALSE; mmcData->StreamingReadSupported = FALSE; mmcData->StreamingWriteSupported = FALSE; // Issue command to update the drive capabilities. // The failure of MMC update is not considered critical, // so that we'll continue to process I/O even MMC update fails. status = DeviceGetConfiguration(Device, mmcData->CapabilitiesBuffer, mmcData->CapabilitiesBufferSize, &returnedBytes, FeatureProfileList, SCSI_GET_CONFIGURATION_REQUEST_TYPE_CURRENT); if (NT_SUCCESS(status) && // succeeded. (mmcData->CapabilitiesBufferSize >= returnedBytes)) // not overflow. { // update whether or not writes are allowed // this should be the *ONLY* place writes are set to allowed { BOOLEAN writeAllowed = FALSE; FEATURE_NUMBER validationSchema = 0; ULONG blockingFactor = 1; DeviceUpdateMmcWriteCapability(mmcData->CapabilitiesBuffer, returnedBytes, TRUE, &writeAllowed, &validationSchema, &blockingFactor); mmcData->WriteAllowed = writeAllowed; mmcData->ValidationSchema = validationSchema; mmcData->Blocking = blockingFactor; } // Check if Streaming reads/writes are supported and cache // this information for later use. { PFEATURE_HEADER header; ULONG minAdditionalLength; minAdditionalLength = FIELD_OFFSET(FEATURE_DATA_REAL_TIME_STREAMING, Reserved2) - sizeof(FEATURE_HEADER); header = MmcDataFindFeaturePage(mmcData->CapabilitiesBuffer, returnedBytes, FeatureRealTimeStreaming); if ((header != NULL) && (header->Current) && (header->AdditionalLength >= minAdditionalLength)) { PFEATURE_DATA_REAL_TIME_STREAMING feature = (PFEATURE_DATA_REAL_TIME_STREAMING)header; // If Real-Time feature is current, then Streaming reads are supported for sure. mmcData->StreamingReadSupported = TRUE; // Streaming writes are supported if an appropriate bit is set in the feature page. mmcData->StreamingWriteSupported = (feature->StreamRecording == 1); } } // update the flag to reflect that if the media is CSS protected DVD or CPPM-protected DVDAudio { PFEATURE_HEADER header; header = DeviceFindFeaturePage(mmcData->CapabilitiesBuffer, returnedBytes, FeatureDvdCSS); mmcData->IsCssDvd = (header != NULL) && (header->Current); } // Update the guesstimate for the drive's write speed // Use the GetConfig profile first as a quick-guess based // on media "type", then continue with media-specific // queries for older media types, and use GET_PERFORMANCE // for all unknown/future media types. { // pseudo-code: // 1) Determine default based on profile (slowest for media) // 2) Determine default based on MODE PAGE 2Ah // 3) Determine default based on GET PERFORMANCE data // 4) Choose fastest reported speed (-1 == none reported) // 5) If all failed (returned -1), go with very safe (slow) default // // This ensures that the retries do not overload the drive's processor. // Sending at highest possible speed for the media is OK, because the // major downside is drive processor usage. (bus usage too, but most // storage is becoming a point-to-point link.) FEATURE_PROFILE_TYPE const profile = mmcData->CapabilitiesBuffer->CurrentProfile[0] << (8*1) | mmcData->CapabilitiesBuffer->CurrentProfile[1] << (8*0) ; LONGLONG t1 = (LONGLONG)-1; LONGLONG t2 = (LONGLONG)-1; LONGLONG t3 = (LONGLONG)-1; LONGLONG t4 = (LONGLONG)-1; LONGLONG final; t1 = DeviceRetryTimeGuessBasedOnProfile(profile); t2 = DeviceRetryTimeDetectionBasedOnModePage2A(deviceExtension); t3 = DeviceRetryTimeDetectionBasedOnGetPerformance(deviceExtension, TRUE); t4 = DeviceRetryTimeDetectionBasedOnGetPerformance(deviceExtension, FALSE); // use the "fastest" value returned final = MAXLONGLONG; if (t4 != -1) { final = min(final, t4); } if (t3 != -1) { final = min(final, t3); } if (t2 != -1) { final = min(final, t2); } if (t1 != -1) { final = min(final, t1); } if (final == MAXLONGLONG) { // worst case -- use relatively slow default.... final = WRITE_RETRY_DELAY_CD_4x; } cdData->ReadWriteRetryDelay100nsUnits = final; } } else { // Rediscovery of MMC capabilities has failed - we'll need to retry cdData->Mmc.UpdateState = CdromMmcUpdateRequired; } // Change the state to CdromMmcUpdateComplete if it is CdromMmcUpdateStarted. // If it is not, some error must have happened while this function was executed // and the state is CdromMmcUpdateRequired now. In that case, we want to perform // everything again, so we do not set CdromMmcUpdateComplete. InterlockedCompareExchange((PLONG)&(cdData->Mmc.UpdateState), CdromMmcUpdateComplete, CdromMmcUpdateStarted); return status; } _IRQL_requires_max_(PASSIVE_LEVEL) NTSTATUS DeviceGetConfigurationWithAlloc( _In_ WDFDEVICE Device, _Outptr_result_bytebuffer_all_(*BytesReturned) PGET_CONFIGURATION_HEADER* Buffer, // this routine allocates this memory _Out_ PULONG BytesReturned, FEATURE_NUMBER const StartingFeature, ULONG const RequestedType ) /*++ Routine Description: This function will allocates configuration buffer and set the size. Arguments: Device - device object Buffer - to be allocated by this function BytesReturned - size of the buffer StartingFeature - the starting point of the feature list RequestedType - Return Value: NTSTATUS NOTE: does not handle case where more than 65000 bytes are returned, which requires multiple calls with different starting feature numbers. --*/ { NTSTATUS status = STATUS_SUCCESS; GET_CONFIGURATION_HEADER header = {0}; // eight bytes, not a lot PGET_CONFIGURATION_HEADER buffer = NULL; ULONG returned = 0; ULONG size = 0; ULONG i = 0; PAGED_CODE(); *Buffer = NULL; *BytesReturned = 0; // send the first request down to just get the header status = DeviceGetConfiguration(Device, &header, sizeof(header), &returned, StartingFeature, RequestedType); // now send command again, using information returned to allocate just enough memory if (NT_SUCCESS(status)) { size = header.DataLength[0] << 24 | header.DataLength[1] << 16 | header.DataLength[2] << 8 | header.DataLength[3] << 0 ; // the loop is in case that the retrieved data length is bigger than last time reported. for (i = 0; (i < 4) && NT_SUCCESS(status); i++) { // the datalength field is the size *following* itself, so adjust accordingly size += 4*sizeof(UCHAR); // make sure the size is reasonable if (size <= sizeof(FEATURE_HEADER)) { TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_IOCTL, "DeviceGetConfigurationWithAlloc: drive reports only %x bytes?\n", size)); status = STATUS_UNSUCCESSFUL; } if (NT_SUCCESS(status)) { // allocate the memory buffer = (PGET_CONFIGURATION_HEADER)ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned, size, CDROM_TAG_FEATURE); if (buffer == NULL) { status = STATUS_INSUFFICIENT_RESOURCES; } } if (NT_SUCCESS(status)) { // send the first request down to just get the header status = DeviceGetConfiguration(Device, buffer, size, &returned, StartingFeature, RequestedType); if (!NT_SUCCESS(status)) { ExFreePool(buffer); } else if (returned > size) { ExFreePool(buffer); status = STATUS_INTERNAL_ERROR; } } // command succeeded. if (NT_SUCCESS(status)) { returned = buffer->DataLength[0] << 24 | buffer->DataLength[1] << 16 | buffer->DataLength[2] << 8 | buffer->DataLength[3] << 0 ; returned += 4*sizeof(UCHAR); if (returned <= size) { *Buffer = buffer; *BytesReturned = returned; // amount of 'safe' memory // succes, get out of loop. status = STATUS_SUCCESS; break; } else { // the data size is bigger than the buffer size, retry using new size.... size = returned; ExFreePool(buffer); buffer = NULL; } } } // end of for() loop } if (!NT_SUCCESS(status)) { // it failed after a number of attempts, so just fail. TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_IOCTL, "DeviceGetConfigurationWithAlloc: Failed %d attempts to get all feature " "information\n", i)); } return status; } _IRQL_requires_max_(PASSIVE_LEVEL) NTSTATUS DeviceGetConfiguration( _In_ WDFDEVICE Device, _Out_writes_bytes_to_(BufferSize, *ValidBytes) PGET_CONFIGURATION_HEADER Buffer, _In_ ULONG const BufferSize, _Out_ PULONG ValidBytes, _In_ FEATURE_NUMBER const StartingFeature, _In_ ULONG const RequestedType ) /*++ Routine Description: This function is used to get configuration data. Arguments: Device - device object Buffer - buffer address to hold data. BufferSize - size of the buffer ValidBytes - valid data size in buffer StartingFeature - the starting point of the feature list RequestedType - Return Value: NTSTATUS NOTE: does not handle case where more than 64k bytes are returned, which requires multiple calls with different starting feature numbers. --*/ { NTSTATUS status; PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(Device); SCSI_REQUEST_BLOCK srb; PCDB cdb = (PCDB)srb.Cdb; PAGED_CODE(); NT_ASSERT(ValidBytes); // when system is low resources we can receive empty buffer if (Buffer == NULL || BufferSize < sizeof(GET_CONFIGURATION_HEADER)) { return STATUS_BUFFER_TOO_SMALL; } *ValidBytes = 0; RtlZeroMemory(&srb, sizeof(SCSI_REQUEST_BLOCK)); RtlZeroMemory(Buffer, BufferSize); if (TEST_FLAG(deviceExtension->DeviceAdditionalData.HackFlags, CDROM_HACK_BAD_GET_CONFIG_SUPPORT)) { return STATUS_INVALID_DEVICE_REQUEST; } #pragma warning(push) #pragma warning(disable: 6386) // OACR will complain buffer overrun: the writable size is 'BufferSize' bytes, but '65532' // bytes might be written, which is impossible because BufferSize > 0xFFFC. if (BufferSize > 0xFFFC) { // cannot request more than 0xFFFC bytes in one request // Eventually will "stitch" together multiple requests if needed // Today, no drive has anywhere close to 4k..... return DeviceGetConfiguration(Device, Buffer, 0xFFFC, ValidBytes, StartingFeature, RequestedType); } #pragma warning(pop) //Start real work srb.TimeOutValue = CDROM_GET_CONFIGURATION_TIMEOUT; srb.CdbLength = 10; cdb->GET_CONFIGURATION.OperationCode = SCSIOP_GET_CONFIGURATION; cdb->GET_CONFIGURATION.RequestType = (UCHAR)RequestedType; cdb->GET_CONFIGURATION.StartingFeature[0] = (UCHAR)(StartingFeature >> 8); cdb->GET_CONFIGURATION.StartingFeature[1] = (UCHAR)(StartingFeature & 0xff); cdb->GET_CONFIGURATION.AllocationLength[0] = (UCHAR)(BufferSize >> 8); cdb->GET_CONFIGURATION.AllocationLength[1] = (UCHAR)(BufferSize & 0xff); status = DeviceSendSrbSynchronously(Device, &srb, Buffer, BufferSize, FALSE, NULL); TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "DeviceGetConfiguration: Status was %x\n", status)); if (NT_SUCCESS(status) || (status == STATUS_BUFFER_OVERFLOW) || (status == STATUS_DATA_OVERRUN)) { ULONG returned = srb.DataTransferLength; PGET_CONFIGURATION_HEADER header = (PGET_CONFIGURATION_HEADER)Buffer; ULONG available = (header->DataLength[0] << (8*3)) | (header->DataLength[1] << (8*2)) | (header->DataLength[2] << (8*1)) | (header->DataLength[3] << (8*0)) ; available += RTL_SIZEOF_THROUGH_FIELD(GET_CONFIGURATION_HEADER, DataLength); _Analysis_assume_(srb.DataTransferLength <= BufferSize); // The true usable amount of data returned is the lesser of // * the returned data per the srb.DataTransferLength field // * the total size per the GET_CONFIGURATION_HEADER // This is because ATAPI can't tell how many bytes really // were transferred on success when using DMA. if (available < returned) { returned = available; } NT_ASSERT(returned <= BufferSize); *ValidBytes = (ULONG)returned; //This is succeed case status = STATUS_SUCCESS; } else { TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_IOCTL, "DeviceGetConfiguration: failed %x\n", status)); } return status; } _IRQL_requires_max_(APC_LEVEL) VOID DeviceUpdateMmcWriteCapability( _In_reads_bytes_(BufferSize) PGET_CONFIGURATION_HEADER Buffer, ULONG const BufferSize, BOOLEAN const CurrentOnly, // TRUE == can drive write now, FALSE == can drive ever write _Out_ PBOOLEAN Writable, _Out_ PFEATURE_NUMBER ValidationSchema, _Out_ PULONG BlockingFactor ) /*++ Routine Description: This function will allocates configuration buffer and set the size. Arguments: Buffer - BufferSize - size of the buffer CurrentOnly - valid data size in buffer Writable - the buffer is allocationed in non-paged pool. validationSchema - the starting point of the feature list BlockingFactor - Return Value: NTSTATUS NOTE: does not handle case where more than 64k bytes are returned, which requires multiple calls with different starting feature numbers. --*/ { // // this routine is used to check if the drive can currently (current==TRUE) // or can ever (current==FALSE) write to media with the current CDROM.SYS // driver. this check parses the GET_CONFIGURATION response data to search // for the appropriate features and/or if they are current. // // this function should not allocate any resources, and thus may safely // return from any point within the function. // PAGED_CODE(); *Writable = FALSE; *ValidationSchema = 0; *BlockingFactor = 1; // // if the drive supports hardware defect management and random writes, that's // sufficient to allow writes. // { PFEATURE_HEADER defectHeader; PFEATURE_HEADER writableHeader; defectHeader = MmcDataFindFeaturePage(Buffer, BufferSize, FeatureDefectManagement); writableHeader = MmcDataFindFeaturePage(Buffer, BufferSize, FeatureRandomWritable); if (defectHeader == NULL || writableHeader == NULL) { // cannot write this way } else if (!CurrentOnly) { TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "DeviceUpdateMmcWriteCapability => Writes supported (defect management)\n")); *Writable = TRUE; return; } else if (defectHeader->Current && writableHeader->Current) { TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "DeviceUpdateMmcWriteCapability => Writes *allowed* (defect management)\n")); *Writable = TRUE; *ValidationSchema = FeatureDefectManagement; return; } } // Certain validation schema require the blocking factor // This is a best-effort attempt to ensure that illegal // requests do not make it to drive { PFEATURE_HEADER header; ULONG additionalLength; // Certain validation schema require the blocking factor // This is a best-effort attempt to ensure that illegal // requests do not make it to drive additionalLength = RTL_SIZEOF_THROUGH_FIELD(FEATURE_DATA_RANDOM_READABLE, Blocking) - sizeof(FEATURE_HEADER); header = MmcDataFindFeaturePage(Buffer, BufferSize, FeatureRandomReadable); if ((header != NULL) && (header->Current) && (header->AdditionalLength >= additionalLength)) { PFEATURE_DATA_RANDOM_READABLE feature = (PFEATURE_DATA_RANDOM_READABLE)header; *BlockingFactor = (feature->Blocking[0] << 8) | feature->Blocking[1]; } } // the majority of features to indicate write capability // indicate this by a single feature existance/current bit. // thus, can use a table-based method for the majority // of the detection.... { typedef struct { FEATURE_NUMBER FeatureToFind; // the ones allowed FEATURE_NUMBER ValidationSchema; // and their related schema } FEATURE_TO_WRITE_SCHEMA_MAP; static FEATURE_TO_WRITE_SCHEMA_MAP const FeaturesToAllowWritesWith[] = { { FeatureRandomWritable, FeatureRandomWritable }, { FeatureRigidRestrictedOverwrite, FeatureRigidRestrictedOverwrite }, { FeatureRestrictedOverwrite, FeatureRestrictedOverwrite }, { FeatureIncrementalStreamingWritable, FeatureIncrementalStreamingWritable }, }; ULONG count; for (count = 0; count < RTL_NUMBER_OF(FeaturesToAllowWritesWith); count++) { PFEATURE_HEADER header = MmcDataFindFeaturePage(Buffer, BufferSize, FeaturesToAllowWritesWith[count].FeatureToFind); if (header == NULL) { // cannot write using this method } else if (!CurrentOnly) { TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "DeviceUpdateMmcWriteCapability => Writes supported (feature %04x)\n", FeaturesToAllowWritesWith[count].FeatureToFind )); *Writable = TRUE; return; } else if (header->Current) { TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "DeviceUpdateMmcWriteCapability => Writes *allowed* (feature %04x)\n", FeaturesToAllowWritesWith[count].FeatureToFind )); *Writable = TRUE; *ValidationSchema = FeaturesToAllowWritesWith[count].ValidationSchema; return; } } // end count loop } // unfortunately, DVD+R media doesn't require IncrementalStreamingWritable feature // to be explicitly set AND it has a seperate bit in the feature to indicate // being able to write to this media type. Thus, use a special case of the above code. { PFEATURE_DATA_DVD_PLUS_R header; ULONG additionalLength = FIELD_OFFSET(FEATURE_DATA_DVD_PLUS_R, Reserved2[0]) - sizeof(FEATURE_HEADER); header = MmcDataFindFeaturePage(Buffer, BufferSize, FeatureDvdPlusR); if (header == NULL || (header->Header.AdditionalLength < additionalLength) || (!header->Write)) { // cannot write this way } else if (!CurrentOnly) { TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "DeviceUpdateMmcWriteCapability => Writes supported (feature %04x)\n", FeatureDvdPlusR )); *Writable = TRUE; return; } else if (header->Header.Current) { TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "DeviceUpdateMmcWriteCapability => Writes *allowed* (feature %04x)\n", FeatureDvdPlusR )); *Writable = TRUE; *ValidationSchema = FeatureIncrementalStreamingWritable; return; } } // unfortunately, DVD+R DL media doesn't require IncrementalStreamingWritable feature // to be explicitly set AND it has a seperate bit in the feature to indicate // being able to write to this media type. Thus, use a special case of the above code. { PFEATURE_DATA_DVD_PLUS_R_DUAL_LAYER header; ULONG additionalLength = FIELD_OFFSET(FEATURE_DATA_DVD_PLUS_R_DUAL_LAYER, Reserved2[0]) - sizeof(FEATURE_HEADER); header = MmcDataFindFeaturePage(Buffer, BufferSize, FeatureDvdPlusRDualLayer); if (header == NULL || (header->Header.AdditionalLength < additionalLength) || (!header->Write)) { // cannot write this way } else if (!CurrentOnly) { TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "DeviceUpdateMmcWriteCapability => Writes supported (feature %04x)\n", FeatureDvdPlusRDualLayer )); *Writable = TRUE; return; } else if (header->Header.Current) { TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "DeviceUpdateMmcWriteCapability => Writes *allowed* (feature %04x)\n", FeatureDvdPlusRDualLayer )); *Writable = TRUE; *ValidationSchema = FeatureIncrementalStreamingWritable; return; } } // There are currently a number of drives on the market // that fail to report: // (a) FeatureIncrementalStreamingWritable as current // for CD-R / DVD-R profile. // (b) FeatureRestrictedOverwrite as current for CD-RW // profile // (c) FeatureRigidRestrictedOverwrite as current for // DVD-RW profile // // Thus, use the profiles also. { PFEATURE_HEADER header; header = MmcDataFindFeaturePage(Buffer, BufferSize, FeatureProfileList); if (header != NULL && header->Current) { // verify buffer bounds -- the below routine presumes full profile list provided PUCHAR bufferEnd = ((PUCHAR)Buffer) + BufferSize; PUCHAR headerEnd = ((PUCHAR)header) + header->AdditionalLength + RTL_SIZEOF_THROUGH_FIELD(FEATURE_HEADER, AdditionalLength); if (bufferEnd >= headerEnd) // this _should_ never occurr, but.... { // Profiles don't contain any data other than current/not current. // thus, can generically loop through them to see if any of the // below (in order of preference) are current. typedef struct { FEATURE_PROFILE_TYPE ProfileToFind; // the ones allowed FEATURE_NUMBER ValidationSchema; // and their related schema } PROFILE_TO_WRITE_SCHEMA_MAP; static PROFILE_TO_WRITE_SCHEMA_MAP const ProfilesToAllowWritesWith[] = { { ProfileDvdRewritable, FeatureRigidRestrictedOverwrite }, { ProfileCdRewritable, FeatureRestrictedOverwrite }, { ProfileDvdRecordable, FeatureIncrementalStreamingWritable }, { ProfileCdRecordable, FeatureIncrementalStreamingWritable }, }; ULONG count; for (count = 0; count < RTL_NUMBER_OF(ProfilesToAllowWritesWith); count++) { BOOLEAN exists = FALSE; MmcDataFindProfileInProfiles((PFEATURE_DATA_PROFILE_LIST)header, ProfilesToAllowWritesWith[count].ProfileToFind, CurrentOnly, &exists); if (exists) { TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, "DeviceUpdateMmcWriteCapability => Writes %s (profile %04x)\n", (CurrentOnly ? "*allowed*" : "supported"), FeatureDvdPlusR )); *Writable = TRUE; *ValidationSchema = ProfilesToAllowWritesWith[count].ValidationSchema; return; } } // end count loop } // end if (bufferEnd >= headerEnd) } // end if (header != NULL && header->Current) } // nothing matched to say it's writable..... return; } _IRQL_requires_max_(APC_LEVEL) PVOID MmcDataFindFeaturePage( _In_reads_bytes_(Length) PGET_CONFIGURATION_HEADER FeatureBuffer, ULONG const Length, FEATURE_NUMBER const Feature ) /*++ Routine Description: search the specific feature from feature list buffer Arguments: FeatureBuffer - buffer of feature list Length - size of the buffer Feature - feature wanted to find Return Value: PVOID - if found, pointer of starting address of the specific feature. otherwise, NULL. --*/ { PUCHAR buffer; PUCHAR limit; ULONG validLength; PAGED_CODE(); if (Length < sizeof(GET_CONFIGURATION_HEADER) + sizeof(FEATURE_HEADER)) { return NULL; } // Calculate the length of valid data available in the // capabilities buffer from the DataLength field REVERSE_BYTES(&validLength, FeatureBuffer->DataLength); validLength += RTL_SIZEOF_THROUGH_FIELD(GET_CONFIGURATION_HEADER, DataLength); // set limit to point to first illegal address limit = (PUCHAR)FeatureBuffer; limit += min(Length, validLength); // set buffer to point to first page buffer = FeatureBuffer->Data; // loop through each page until we find the requested one, or // until it's not safe to access the entire feature header // (if equal, have exactly enough for the feature header) while (buffer + sizeof(FEATURE_HEADER) <= limit) { PFEATURE_HEADER header = (PFEATURE_HEADER)buffer; FEATURE_NUMBER thisFeature; thisFeature = (header->FeatureCode[0] << 8) | (header->FeatureCode[1]); if (thisFeature == Feature) { PUCHAR temp; // if don't have enough memory to safely access all the feature // information, return NULL temp = buffer; temp += sizeof(FEATURE_HEADER); temp += header->AdditionalLength; if (temp > limit) { // this means the transfer was cut-off, an insufficiently // small buffer was given, or other arbitrary error. since // it's not safe to view the amount of data (even though // the header is safe) in this feature, pretend it wasn't // transferred at all... TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "Feature %x exists, but not safe to access all its " "data. returning NULL\n", Feature)); return NULL; } else { return buffer; } } if ((header->AdditionalLength % 4) && !(Feature >= 0xff00 && Feature <= 0xffff)) { return NULL; } buffer += sizeof(FEATURE_HEADER); buffer += header->AdditionalLength; } return NULL; } _IRQL_requires_max_(APC_LEVEL) VOID MmcDataFindProfileInProfiles( _In_ FEATURE_DATA_PROFILE_LIST const* ProfileHeader, _In_ FEATURE_PROFILE_TYPE const ProfileToFind, _In_ BOOLEAN const CurrentOnly, _Out_ PBOOLEAN Found ) /*++ Routine Description: search the specific feature from feature list buffer Arguments: ProfileHeader - buffer of profile list ProfileToFind - profile to be found CurrentOnly - Return Value: Found - found or not --*/ { FEATURE_DATA_PROFILE_LIST_EX const * profile; ULONG numberOfProfiles; ULONG i; PAGED_CODE(); // initialize output *Found = FALSE; // sanity check if (ProfileHeader->Header.AdditionalLength % 2 != 0) { TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL, "Profile total length %x is not integral multiple of 4\n", ProfileHeader->Header.AdditionalLength)); NT_ASSERT(FALSE); return; } // calculate number of profiles numberOfProfiles = ProfileHeader->Header.AdditionalLength / 4; profile = ProfileHeader->Profiles; // zero-sized array // loop through profiles for (i = 0; i < numberOfProfiles; i++) { FEATURE_PROFILE_TYPE currentProfile; currentProfile = (profile->ProfileNumber[0] << 8) | (profile->ProfileNumber[1] & 0xff); if (currentProfile == ProfileToFind) { if (profile->Current || (!CurrentOnly)) { *Found = TRUE; } } profile++; } return; } _IRQL_requires_max_(APC_LEVEL) _Ret_range_(-1,MAXIMUM_RETRY_FOR_SINGLE_IO_IN_100NS_UNITS) LONGLONG DeviceRetryTimeGuessBasedOnProfile( FEATURE_PROFILE_TYPE const Profile ) /*++ Routine Description: determine the retry time based on profile Arguments: Profile - Return Value: LONGLONG - retry time --*/ { LONGLONG result = -1; // this means we have no idea PAGED_CODE(); switch (Profile) { case ProfileInvalid: // = 0x0000, case ProfileNonRemovableDisk: // = 0x0001, case ProfileRemovableDisk: // = 0x0002, case ProfileMOErasable: // = 0x0003, case ProfileMOWriteOnce: // = 0x0004, case ProfileAS_MO: // = 0x0005, // Reserved 0x0006 - 0x0007, // Reserved 0x000b - 0x000f, // Reserved 0x0017 - 0x0019 // Reserved 0x001C - 001F // Reserved 0x0023 - 0x0029 // Reserved 0x002C - 0x003F // Reserved 0x0044 - 0x004F // Reserved 0x0053 - 0xfffe case ProfileNonStandard: // = 0xffff default: { NOTHING; // no default break; } case ProfileCdrom: // = 0x0008, case ProfileCdRecordable: // = 0x0009, case ProfileCdRewritable: // = 0x000a, case ProfileDDCdrom: // = 0x0020, // obsolete case ProfileDDCdRecordable: // = 0x0021, // obsolete case ProfileDDCdRewritable: // = 0x0022, // obsolete { // 4x is ok as all CD drives have // at least 64k*4 (256k) buffer // and this is just a first-pass // guess based only on profile result = WRITE_RETRY_DELAY_CD_4x; break; } case ProfileDvdRom: // = 0x0010, case ProfileDvdRecordable: // = 0x0011, case ProfileDvdRam: // = 0x0012, case ProfileDvdRewritable: // = 0x0013, // restricted overwrite case ProfileDvdRWSequential: // = 0x0014, case ProfileDvdDashRLayerJump: // = 0x0016, case ProfileDvdPlusRW: // = 0x001A, case ProfileDvdPlusR: // = 0x001B, { result = WRITE_RETRY_DELAY_DVD_1x; break; } case ProfileDvdDashRDualLayer: // = 0x0015, case ProfileDvdPlusRWDualLayer: // = 0x002A, case ProfileDvdPlusRDualLayer: // = 0x002B, { result = WRITE_RETRY_DELAY_DVD_1x; break; } case ProfileBDRom: // = 0x0040, case ProfileBDRSequentialWritable: // = 0x0041, // BD-R 'SRM' case ProfileBDRRandomWritable: // = 0x0042, // BD-R 'RRM' case ProfileBDRewritable: // = 0x0043, { // I could not find specifications for the // minimal 1x data rate for BD media. Use // HDDVD values for now, since they are // likely to be similar. Also, all media // except for CD, DVD, and AS-MO should // already fully support GET_CONFIG, so // this guess is only used if we fail to // get a performance descriptor.... result = WRITE_RETRY_DELAY_HDDVD_1x; break; } case ProfileHDDVDRom: // = 0x0050, case ProfileHDDVDRecordable: // = 0x0051, case ProfileHDDVDRam: // = 0x0052, { // All HDDVD drives support GET_PERFORMANCE // so this guess is fine at 1x.... result = WRITE_RETRY_DELAY_HDDVD_1x; break; } // addition of any further profile types is not // technically required as GET PERFORMANCE // should succeed for all future drives. However, // it is useful in case GET PERFORMANCE does // fail for other reasons (i.e. bus resets, etc) } // end switch(Profile) return result; } _IRQL_requires_max_(APC_LEVEL) _Ret_range_(-1,MAXIMUM_RETRY_FOR_SINGLE_IO_IN_100NS_UNITS) LONGLONG DeviceRetryTimeDetectionBasedOnModePage2A( _In_ PCDROM_DEVICE_EXTENSION DeviceExtension ) /*++ Routine Description: determine the retry time based on mode sense data Arguments: DeviceExtension - device context Return Value: LONGLONG - retry time --*/ { NTSTATUS status; ULONG transferSize = min(0xFFF0, DeviceExtension->ScratchContext.ScratchBufferSize); CDB cdb; LONGLONG result = -1; PAGED_CODE(); ScratchBuffer_BeginUse(DeviceExtension); RtlZeroMemory(&cdb, sizeof(CDB)); // Set up the CDB cdb.MODE_SENSE10.OperationCode = SCSIOP_MODE_SENSE10; cdb.MODE_SENSE10.Dbd = 1; cdb.MODE_SENSE10.PageCode = MODE_PAGE_CAPABILITIES; cdb.MODE_SENSE10.AllocationLength[0] = (UCHAR)(transferSize >> 8); cdb.MODE_SENSE10.AllocationLength[1] = (UCHAR)(transferSize & 0xFF); status = ScratchBuffer_ExecuteCdb(DeviceExtension, NULL, transferSize, TRUE, &cdb, 10); // analyze the data on success.... if (NT_SUCCESS(status)) { MODE_PARAMETER_HEADER10 const* header = DeviceExtension->ScratchContext.ScratchBuffer; CDVD_CAPABILITIES_PAGE const* page = NULL; ULONG dataLength = (header->ModeDataLength[0] << (8*1)) | (header->ModeDataLength[1] << (8*0)) ; // no possible overflow if (dataLength != 0) { dataLength += RTL_SIZEOF_THROUGH_FIELD(MODE_PARAMETER_HEADER10, ModeDataLength); } // If it's not abundantly clear, we really don't trust the drive // to be returning valid data. Get the page pointer and usable // size of the page here... if (dataLength < sizeof(MODE_PARAMETER_HEADER10)) { dataLength = 0; } else if (dataLength > DeviceExtension->ScratchContext.ScratchBufferSize) { dataLength = 0; } else if ((header->BlockDescriptorLength[1] == 0) && (header->BlockDescriptorLength[0] == 0)) { dataLength -= sizeof(MODE_PARAMETER_HEADER10); page = (CDVD_CAPABILITIES_PAGE const *)(header + 1); } else if ((header->BlockDescriptorLength[1] == 0) && (header->BlockDescriptorLength[0] == sizeof(MODE_PARAMETER_BLOCK))) { dataLength -= sizeof(MODE_PARAMETER_HEADER10); dataLength -= min(dataLength, sizeof(MODE_PARAMETER_BLOCK)); page = (CDVD_CAPABILITIES_PAGE const *) ( ((PUCHAR)header) + sizeof(MODE_PARAMETER_HEADER10) + sizeof(MODE_PARAMETER_BLOCK) ); } // Change dataLength from the size available per the header to // the size available per the page itself. if ((page != NULL) && (dataLength >= RTL_SIZEOF_THROUGH_FIELD(CDVD_CAPABILITIES_PAGE, PageLength)) ) { dataLength = min(dataLength, ((ULONG)(page->PageLength) + 2)); } // Ignore the page if the fastest write speed field isn't available. if ((page != NULL) && (dataLength < RTL_SIZEOF_THROUGH_FIELD(CDVD_CAPABILITIES_PAGE, WriteSpeedMaximum)) ) { TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, "ModePage 2Ah was requested, but drive reported " "only %x bytes (%x needed). Ignoring.\n", dataLength, RTL_SIZEOF_THROUGH_FIELD(CDVD_CAPABILITIES_PAGE, WriteSpeedMaximum) )); page = NULL; } // Verify the page we requested is the one the drive actually provided if ((page != NULL) && (page->PageCode != MODE_PAGE_CAPABILITIES)) { TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT, "ModePage 2Ah was requested, but drive reported " "page %x\n", page->PageCode )); page = NULL; } // If _everything_ succeeded, then use the speed value in the page! if (page != NULL) { ULONG temp = (page->WriteSpeedMaximum[0] << (8*1)) | (page->WriteSpeedMaximum[1] << (8*0)) ; // stored as 1,000 byte increments... temp *= 1000; // typically stored at 2448 bytes/sector due to CD media // error up to 20% high by presuming it returned 2048 data // and convert to sectors/second temp /= 2048; // currently: sectors/sec // ignore too-small or zero values if (temp != 0) { result = ConvertSectorsPerSecondTo100nsUnitsFor64kWrite(temp); } } } ScratchBuffer_EndUse(DeviceExtension); return result; } _IRQL_requires_max_(APC_LEVEL) _Ret_range_(-1,MAXIMUM_RETRY_FOR_SINGLE_IO_IN_100NS_UNITS) LONGLONG DeviceRetryTimeDetectionBasedOnGetPerformance( _In_ PCDROM_DEVICE_EXTENSION DeviceExtension, _In_ BOOLEAN UseLegacyNominalPerformance ) /*++ Routine Description: determine the retry time based on get performance data Arguments: DeviceExtension - device context UseLegacyNominalPerformance - Return Value: LONGLONG - retry time --*/ { typedef struct _GET_PERFORMANCE_HEADER { UCHAR TotalDataLength[4]; // not including this field UCHAR Except : 1; UCHAR Write : 1; UCHAR Reserved0 : 6; UCHAR Reserved1[3]; } GET_PERFORMANCE_HEADER, *PGET_PERFORMANCE_HEADER; C_ASSERT( sizeof(GET_PERFORMANCE_HEADER) == 8); typedef struct _GET_PERFORMANCE_NOMINAL_PERFORMANCE_DESCRIPTOR { UCHAR StartLba[4]; UCHAR StartPerformance[4]; UCHAR EndLba[4]; UCHAR EndPerformance[4]; } GET_PERFORMANCE_NOMINAL_PERFORMANCE_DESCRIPTOR, *PGET_PERFORMANCE_NOMINAL_PERFORMANCE_DESCRIPTOR; C_ASSERT( sizeof(GET_PERFORMANCE_NOMINAL_PERFORMANCE_DESCRIPTOR) == 16); typedef struct _GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR { UCHAR MixedReadWrite : 1; UCHAR GuaranteedForWholeMedia : 1; UCHAR Reserved0_RDD : 1; UCHAR WriteRotationControl : 2; UCHAR Reserved1 : 3; UCHAR Reserved2[3]; UCHAR MediaCapacity[4]; UCHAR ReadSpeedKilobytesPerSecond[4]; UCHAR WriteSpeedKilobytesPerSecond[4]; } GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR, *PGET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR; C_ASSERT( sizeof(GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR) == 16); ////// NTSTATUS status; LONGLONG result = -1; // transfer size -- descriptors + 8 byte header // Note: this size is identical for both descriptor types C_ASSERT( sizeof(GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR) == sizeof(GET_PERFORMANCE_NOMINAL_PERFORMANCE_DESCRIPTOR)); ULONG const maxDescriptors = min(200, (DeviceExtension->ScratchContext.ScratchBufferSize-sizeof(GET_PERFORMANCE_HEADER))/sizeof(GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR)); ULONG validDescriptors = 0; ULONG transferSize = sizeof(GET_PERFORMANCE_HEADER) + (maxDescriptors*sizeof(GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR)); CDB cdb; PAGED_CODE(); ScratchBuffer_BeginUse(DeviceExtension); RtlZeroMemory(&cdb, sizeof(CDB)); // Set up the CDB if (UseLegacyNominalPerformance) { cdb.GET_PERFORMANCE.OperationCode = SCSIOP_GET_PERFORMANCE; cdb.GET_PERFORMANCE.Except = 0; cdb.GET_PERFORMANCE.Write = 1; cdb.GET_PERFORMANCE.Tolerance = 2; // only defined option cdb.GET_PERFORMANCE.MaximumNumberOfDescriptors[1] = (UCHAR)maxDescriptors; cdb.GET_PERFORMANCE.Type = 0; // legacy nominal descriptors } else { cdb.GET_PERFORMANCE.OperationCode = SCSIOP_GET_PERFORMANCE; cdb.GET_PERFORMANCE.MaximumNumberOfDescriptors[1] = (UCHAR)maxDescriptors; cdb.GET_PERFORMANCE.Type = 3; // write speed } status = ScratchBuffer_ExecuteCdbEx(DeviceExtension, NULL, transferSize, TRUE, &cdb, 12, CDROM_GET_PERFORMANCE_TIMEOUT); // determine how many valid descriptors there actually are if (NT_SUCCESS(status)) { GET_PERFORMANCE_HEADER const* header = (GET_PERFORMANCE_HEADER const*)DeviceExtension->ScratchContext.ScratchBuffer; ULONG temp1 = (header->TotalDataLength[0] << (8*3)) | (header->TotalDataLength[1] << (8*2)) | (header->TotalDataLength[2] << (8*1)) | (header->TotalDataLength[3] << (8*0)) ; // adjust data size for header if (temp1 + (ULONG)RTL_SIZEOF_THROUGH_FIELD(GET_PERFORMANCE_HEADER, TotalDataLength) < temp1) { temp1 = 0; } else if (temp1 != 0) { temp1 += RTL_SIZEOF_THROUGH_FIELD(GET_PERFORMANCE_HEADER, TotalDataLength); } if (temp1 == 0) { // no data returned } else if (temp1 <= sizeof(GET_PERFORMANCE_HEADER)) { // only the header returned, no descriptors } else if (UseLegacyNominalPerformance && ((header->Except != 0) || (header->Write == 0)) ) { // bad data being returned -- ignore it } else if (!UseLegacyNominalPerformance && ((header->Except != 0) || (header->Write != 0)) ) { // returning Performance (Type 0) data, not requested Write Speed (Type 3) data } else if ( (temp1 - sizeof(GET_PERFORMANCE_HEADER)) % sizeof(GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR) != 0) { // Note: this size is identical for both descriptor types C_ASSERT( sizeof(GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR) == sizeof(GET_PERFORMANCE_NOMINAL_PERFORMANCE_DESCRIPTOR)); // not returning valid data.... } else // save how many are usable { // Note: this size is identical for both descriptor types C_ASSERT( sizeof(GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR) == sizeof(GET_PERFORMANCE_NOMINAL_PERFORMANCE_DESCRIPTOR)); // take the smaller usable value temp1 = min(temp1, DeviceExtension->ScratchContext.ScratchSrb->DataTransferLength); // then determine the usable descriptors validDescriptors = (temp1 - sizeof(GET_PERFORMANCE_HEADER)) / sizeof(GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR); } } // The drive likely supports this command. // Verify the data makes sense. if (NT_SUCCESS(status)) { ULONG i; GET_PERFORMANCE_HEADER const* header = (GET_PERFORMANCE_HEADER const*)DeviceExtension->ScratchContext.ScratchBuffer; GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR const* descriptor = (GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR const*)(header+1); // pointer math // NOTE: We could write this loop twice, once for each write descriptor type // However, the only fields of interest are the writeKBps field (Type 3) and // the EndPerformance field (Type 0), which both exist in the same exact // location and have essentially the same meaning. So, just use the same // loop/structure pointers for both of the to simplify the readability of // this code. The C_ASSERT()s here verify this at compile-time. C_ASSERT( sizeof(GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR) == sizeof(GET_PERFORMANCE_NOMINAL_PERFORMANCE_DESCRIPTOR)); C_ASSERT( FIELD_OFFSET(GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR, WriteSpeedKilobytesPerSecond) == FIELD_OFFSET(GET_PERFORMANCE_NOMINAL_PERFORMANCE_DESCRIPTOR, EndPerformance) ); C_ASSERT( RTL_FIELD_SIZE(GET_PERFORMANCE_WRITE_SPEED_DESCRIPTOR, WriteSpeedKilobytesPerSecond) == RTL_FIELD_SIZE(GET_PERFORMANCE_NOMINAL_PERFORMANCE_DESCRIPTOR, EndPerformance) ); // loop through them all, and find the fastest listed write speed for (i = 0; NT_SUCCESS(status) && (i WriteSpeedKilobytesPerSecond[0] << (8*3)) | (descriptor->WriteSpeedKilobytesPerSecond[1] << (8*2)) | (descriptor->WriteSpeedKilobytesPerSecond[2] << (8*1)) | (descriptor->WriteSpeedKilobytesPerSecond[3] << (8*0)) ; // Avoid overflow and still have good estimates // 0x1 0000 0000 / 1000 == 0x00418937 == maximum writeKBps to multiple first ULONG const sectorsPerSecond = (writeKBps > 0x00418937) ? // would overflow occur by multiplying by 1000? ((writeKBps / 2048) * 1000) : // must divide first, minimal loss of accuracy ((writeKBps * 1000) / 2048) ; // must multiply first, avoid loss of accuracy if (sectorsPerSecond <= 0) { break; // out of the loop -- no longer valid data (very defensive programming) } // we have at least one valid result, so prevent returning -1 as our result if (result == -1) { result = MAXIMUM_RETRY_FOR_SINGLE_IO_IN_100NS_UNITS; } // take the fastest speed (smallest wait time) we've found thus far result = min(result, ConvertSectorsPerSecondTo100nsUnitsFor64kWrite(sectorsPerSecond)); } } ScratchBuffer_EndUse(DeviceExtension); return result; } #pragma warning(pop) // un-sets any local warning changes