reactos/drivers/storage/class/cdrom/mmc.c

1630 lines
57 KiB
C

/*--
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 <validDescriptors); descriptor++, i++)
{
ULONG const writeKBps =
(descriptor->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