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

3878 lines
120 KiB
C
Raw Blame History

/*++
Copyright (C) Microsoft Corporation. All rights reserved.
Module Name:
common.c
Abstract:
shared private routines for cdrom.sys
Environment:
kernel mode only
Notes:
Revision History:
--*/
#include "ntddk.h"
#include "ntddstor.h"
#include "ntstrsafe.h"
#include "cdrom.h"
#include "scratch.h"
#ifdef DEBUG_USE_WPP
#include "common.tmh"
#endif
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, DeviceGetParameter)
#pragma alloc_text(PAGE, DeviceSetParameter)
#pragma alloc_text(PAGE, DeviceSendSrbSynchronously)
#pragma alloc_text(PAGE, DevicePickDvdRegion)
#pragma alloc_text(PAGE, StringsAreMatched)
#pragma alloc_text(PAGE, PerformEjectionControl)
#pragma alloc_text(PAGE, DeviceFindFeaturePage)
#pragma alloc_text(PAGE, DevicePrintAllFeaturePages)
#pragma alloc_text(PAGE, DeviceRegisterInterface)
#pragma alloc_text(PAGE, DeviceRestoreDefaultSpeed)
#pragma alloc_text(PAGE, DeviceSendRequestSynchronously)
#pragma alloc_text(PAGE, MediaReadCapacity)
#pragma alloc_text(PAGE, MediaReadCapacityDataInterpret)
#pragma alloc_text(PAGE, DeviceRetrieveModeSenseUsingScratch)
#pragma alloc_text(PAGE, ModeSenseFindSpecificPage)
#pragma alloc_text(PAGE, DeviceUnlockExclusive)
#endif
LPCSTR LockTypeStrings[] = {"Simple",
"Secure",
"Internal"
};
VOID
RequestSetReceivedTime(
_In_ WDFREQUEST Request
)
{
PCDROM_REQUEST_CONTEXT requestContext = RequestGetContext(Request);
LARGE_INTEGER temp;
KeQueryTickCount(&temp);
requestContext->TimeReceived = temp;
return;
}
VOID
RequestSetSentTime(
_In_ WDFREQUEST Request
)
{
PCDROM_REQUEST_CONTEXT requestContext = RequestGetContext(Request);
LARGE_INTEGER temp;
KeQueryTickCount(&temp);
if (requestContext->TimeSentDownFirstTime.QuadPart == 0)
{
requestContext->TimeSentDownFirstTime = temp;
}
requestContext->TimeSentDownLasttTime = temp;
if (requestContext->OriginalRequest != NULL)
{
PCDROM_REQUEST_CONTEXT originalRequestContext = RequestGetContext(requestContext->OriginalRequest);
if (originalRequestContext->TimeSentDownFirstTime.QuadPart == 0)
{
originalRequestContext->TimeSentDownFirstTime = temp;
}
originalRequestContext->TimeSentDownLasttTime = temp;
}
return;
}
VOID
RequestClearSendTime(
_In_ WDFREQUEST Request
)
/*
Routine Description:
This function is used to clean SentTime fields in reusable request context.
Arguments:
Request -
Return Value:
N/A
*/
{
PCDROM_REQUEST_CONTEXT requestContext = RequestGetContext(Request);
requestContext->TimeSentDownFirstTime.QuadPart = 0;
requestContext->TimeSentDownLasttTime.QuadPart = 0;
return;
}
_IRQL_requires_max_(PASSIVE_LEVEL)
VOID
DeviceGetParameter(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_In_opt_ PWSTR SubkeyName,
_In_ PWSTR ParameterName,
_Inout_ PULONG ParameterValue // also default value
)
/*++
Routine Description:
retrieve device parameter from registry.
Arguments:
DeviceExtension - device context.
SubkeyName - name of subkey
ParameterName - the registry parameter to be retrieved
Return Value:
ParameterValue - registry value retrieved
--*/
{
NTSTATUS status;
WDFKEY rootKey = NULL;
WDFKEY subKey = NULL;
UNICODE_STRING registrySubKeyName;
UNICODE_STRING registryValueName;
ULONG defaultParameterValue;
PAGED_CODE();
RtlInitUnicodeString(&registryValueName, ParameterName);
if (SubkeyName != NULL)
{
RtlInitUnicodeString(&registrySubKeyName, SubkeyName);
}
// open the hardware key
status = WdfDeviceOpenRegistryKey(DeviceExtension->Device,
PLUGPLAY_REGKEY_DEVICE,
KEY_READ,
WDF_NO_OBJECT_ATTRIBUTES,
&rootKey);
// open the sub key
if (NT_SUCCESS(status) && (SubkeyName != NULL))
{
status = WdfRegistryOpenKey(rootKey,
&registrySubKeyName,
KEY_READ,
WDF_NO_OBJECT_ATTRIBUTES,
&subKey);
if (!NT_SUCCESS(status))
{
WdfRegistryClose(rootKey);
rootKey = NULL;
}
}
if (NT_SUCCESS(status) && (rootKey != NULL))
{
defaultParameterValue = *ParameterValue;
status = WdfRegistryQueryULong((subKey != NULL) ? subKey : rootKey,
&registryValueName,
ParameterValue);
if (!NT_SUCCESS(status))
{
*ParameterValue = defaultParameterValue; // use default value
}
}
// close what we open
if (subKey != NULL)
{
WdfRegistryClose(subKey);
subKey = NULL;
}
if (rootKey != NULL)
{
WdfRegistryClose(rootKey);
rootKey = NULL;
}
// Windows 2000 SP3 uses the driver-specific key, so look in there
if (!NT_SUCCESS(status))
{
// open the software key
status = WdfDeviceOpenRegistryKey(DeviceExtension->Device,
PLUGPLAY_REGKEY_DRIVER,
KEY_READ,
WDF_NO_OBJECT_ATTRIBUTES,
&rootKey);
// open the sub key
if (NT_SUCCESS(status) && (SubkeyName != NULL))
{
status = WdfRegistryOpenKey(rootKey,
&registrySubKeyName,
KEY_READ,
WDF_NO_OBJECT_ATTRIBUTES,
&subKey);
if (!NT_SUCCESS(status))
{
WdfRegistryClose(rootKey);
rootKey = NULL;
}
}
if (NT_SUCCESS(status) && (rootKey != NULL))
{
defaultParameterValue = *ParameterValue;
status = WdfRegistryQueryULong((subKey != NULL) ? subKey : rootKey,
&registryValueName,
ParameterValue);
if (!NT_SUCCESS(status))
{
*ParameterValue = defaultParameterValue; // use default value
}
else
{
// Migrate the value over to the device-specific key
DeviceSetParameter(DeviceExtension, SubkeyName, ParameterName, *ParameterValue);
}
}
// close what we open
if (subKey != NULL)
{
WdfRegistryClose(subKey);
subKey = NULL;
}
if (rootKey != NULL)
{
WdfRegistryClose(rootKey);
rootKey = NULL;
}
}
return;
} // end DeviceetParameter()
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS
DeviceSetParameter(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_In_opt_z_ PWSTR SubkeyName,
_In_ PWSTR ParameterName,
_In_ ULONG ParameterValue
)
/*++
Routine Description:
set parameter to registry.
Arguments:
DeviceExtension - device context.
SubkeyName - name of subkey
ParameterName - the registry parameter to be retrieved
ParameterValue - registry value to be set
Return Value:
NTSTATUS
--*/
{
NTSTATUS status;
WDFKEY rootKey = NULL;
WDFKEY subKey = NULL;
UNICODE_STRING registrySubKeyName;
UNICODE_STRING registryValueName;
PAGED_CODE();
RtlInitUnicodeString(&registryValueName, ParameterName);
if (SubkeyName != NULL)
{
RtlInitUnicodeString(&registrySubKeyName, SubkeyName);
}
// open the hardware key
status = WdfDeviceOpenRegistryKey(DeviceExtension->Device,
PLUGPLAY_REGKEY_DEVICE,
KEY_READ | KEY_WRITE,
WDF_NO_OBJECT_ATTRIBUTES,
&rootKey);
// open the sub key
if (NT_SUCCESS(status) && (SubkeyName != NULL))
{
status = WdfRegistryOpenKey(rootKey,
&registrySubKeyName,
KEY_READ | KEY_WRITE,
WDF_NO_OBJECT_ATTRIBUTES,
&subKey);
if (!NT_SUCCESS(status))
{
WdfRegistryClose(rootKey);
rootKey = NULL;
}
}
if (NT_SUCCESS(status) && (rootKey != NULL))
{
status = WdfRegistryAssignULong((subKey != NULL) ? subKey : rootKey,
&registryValueName,
ParameterValue);
}
// close what we open
if (subKey != NULL)
{
WdfRegistryClose(subKey);
subKey = NULL;
}
if (rootKey != NULL)
{
WdfRegistryClose(rootKey);
rootKey = NULL;
}
return status;
} // end DeviceSetParameter()
_IRQL_requires_max_(APC_LEVEL)
NTSTATUS
DeviceSendRequestSynchronously(
_In_ WDFDEVICE Device,
_In_ WDFREQUEST Request,
_In_ BOOLEAN RequestFormated
)
/*++
Routine Description:
send a request to lower driver synchronously.
Arguments:
Device - device object.
Request - request object
RequestFormated - if the request is already formatted, will no do it in this function
Return Value:
NTSTATUS
--*/
{
NTSTATUS status = STATUS_SUCCESS;
PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(Device);
BOOLEAN requestCancelled = FALSE;
PCDROM_REQUEST_CONTEXT requestContext = RequestGetContext(Request);
PAGED_CODE();
if (!RequestFormated)
{
// set request up for sending down
WdfRequestFormatRequestUsingCurrentType(Request);
}
// get cancellation status for the original request
if (requestContext->OriginalRequest != NULL)
{
requestCancelled = WdfRequestIsCanceled(requestContext->OriginalRequest);
}
if (!requestCancelled)
{
status = RequestSend(deviceExtension,
Request,
deviceExtension->IoTarget,
WDF_REQUEST_SEND_OPTION_SYNCHRONOUS,
NULL);
}
else
{
status = STATUS_CANCELLED;
}
return status;
}
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS
DeviceSendSrbSynchronously(
_In_ WDFDEVICE Device,
_In_ PSCSI_REQUEST_BLOCK Srb,
_In_opt_ PVOID BufferAddress,
_In_ ULONG BufferLength,
_In_ BOOLEAN WriteToDevice,
_In_opt_ WDFREQUEST OriginalRequest
)
/*++
Routine Description:
Send a SRB structure to lower driver synchronously.
Process of this function:
1. Allocate SenseBuffer; Create Request; Allocate MDL
2. Do following loop if necessary
2.1 Reuse Request
2.2 Format Srb, Irp
2.3 Send Request
2.4 Error Intepret and retry decision making.
3. Release all allocated resosurces.
Arguments:
Device - device object.
Request - request object
RequestFormated - if the request is already formatted, will no do it in this function
Return Value:
NTSTATUS
NOTE:
The caller needs to setup following fields before calling this routine.
srb.CdbLength
srb.TimeOutValue
cdb
BufferLength and WriteToDevice to control the data direction of the device
BufferLength = 0: No data transfer
BufferLenth != 0 && !WriteToDevice: get data from device
BufferLenth != 0 && WriteToDevice: send data to device
--*/
{
NTSTATUS status;
PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(Device);
PCDROM_PRIVATE_FDO_DATA fdoData = deviceExtension->PrivateFdoData;
PUCHAR senseInfoBuffer = NULL;
ULONG retryCount = 0;
BOOLEAN retry = FALSE;
ULONG ioctlCode = 0;
WDFREQUEST request = NULL;
PIRP irp = NULL;
PIO_STACK_LOCATION nextStack = NULL;
PMDL mdlAddress = NULL;
BOOLEAN memoryLocked = FALSE;
WDF_OBJECT_ATTRIBUTES attributes;
PZERO_POWER_ODD_INFO zpoddInfo = deviceExtension->ZeroPowerODDInfo;
PAGED_CODE();
// NOTE: This code is only pagable because we are not freezing
// the queue. Allowing the queue to be frozen from a pagable
// routine could leave the queue frozen as we try to page in
// the code to unfreeze the queue. The result would be a nice
// case of deadlock. Therefore, since we are unfreezing the
// queue regardless of the result, just set the NO_FREEZE_QUEUE
// flag in the SRB.
NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
//1. allocate SenseBuffer and initiate Srb common fields
// these fields will not be changed by lower driver.
{
// Write length to SRB.
Srb->Length = sizeof(SCSI_REQUEST_BLOCK);
Srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
Srb->SenseInfoBufferLength = SENSE_BUFFER_SIZE;
// Sense buffer is in aligned nonpaged pool.
senseInfoBuffer = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned,
SENSE_BUFFER_SIZE,
CDROM_TAG_SENSE_INFO);
if (senseInfoBuffer == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
"DeviceSendSrbSynchronously: Can't allocate MDL\n"));
goto Exit;
}
Srb->SenseInfoBuffer = senseInfoBuffer;
Srb->DataBuffer = BufferAddress;
// set timeout value to default value if it's not specifically set by caller.
if (Srb->TimeOutValue == 0)
{
Srb->TimeOutValue = deviceExtension->TimeOutValue;
}
}
//2. Create Request object
{
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes,
CDROM_REQUEST_CONTEXT);
status = WdfRequestCreate(&attributes,
deviceExtension->IoTarget,
&request);
if (!NT_SUCCESS(status))
{
TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
"DeviceSendSrbSynchronously: Can't create request: %lx\n",
status));
goto Exit;
}
irp = WdfRequestWdmGetIrp(request);
}
// 3. Build an MDL for the data buffer and stick it into the irp.
if (BufferAddress != NULL)
{
mdlAddress = IoAllocateMdl( BufferAddress,
BufferLength,
FALSE,
FALSE,
irp );
if (mdlAddress == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
"DeviceSendSrbSynchronously: Can't allocate MDL\n"));
goto Exit;
}
_SEH2_TRY
{
MmProbeAndLockPages(mdlAddress,
KernelMode,
(WriteToDevice ? IoReadAccess : IoWriteAccess));
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
status = _SEH2_GetExceptionCode();
TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
"DeviceSendSrbSynchronously: Exception %lx locking buffer\n", status));
_SEH2_YIELD(goto Exit);
}
_SEH2_END;
memoryLocked = TRUE;
}
// 4. Format Srb, Irp; Send request and retry when necessary
do
{
// clear the control variable.
retry = FALSE;
// 4.1 reuse the request object; set originalRequest field.
{
WDF_REQUEST_REUSE_PARAMS params;
PCDROM_REQUEST_CONTEXT requestContext = NULL;
// deassign the MdlAddress, this is the value we assign explicitly.
// doing this can prevent WdfRequestReuse to release the Mdl unexpectly.
if (irp->MdlAddress)
{
irp->MdlAddress = NULL;
}
WDF_REQUEST_REUSE_PARAMS_INIT(&params,
WDF_REQUEST_REUSE_NO_FLAGS,
STATUS_SUCCESS);
status = WdfRequestReuse(request, &params);
if (!NT_SUCCESS(status))
{
TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
"DeviceSendSrbSynchronously: WdfRequestReuse failed, %!STATUS!\n",
status));
// exit the loop.
break;
}
// WDF requests to format the request befor sending it
status = WdfIoTargetFormatRequestForInternalIoctlOthers(deviceExtension->IoTarget,
request,
ioctlCode,
NULL, NULL,
NULL, NULL,
NULL, NULL);
if (!NT_SUCCESS(status))
{
TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
"DeviceSendSrbSynchronously: WdfIoTargetFormatRequestForInternalIoctlOthers failed, %!STATUS!\n",
status));
// exit the loop.
break;
}
requestContext = RequestGetContext(request);
requestContext->OriginalRequest = OriginalRequest;
}
// 4.2 Format Srb and Irp
{
Srb->OriginalRequest = irp;
Srb->QueueAction = SRB_SIMPLE_TAG_REQUEST;
Srb->DataTransferLength = BufferLength;
Srb->SrbFlags = deviceExtension->SrbFlags;
// Disable synchronous transfer for these requests.
SET_FLAG(Srb->SrbFlags, SRB_FLAGS_DISABLE_SYNCH_TRANSFER);
SET_FLAG(Srb->SrbFlags, SRB_FLAGS_NO_QUEUE_FREEZE);
if (BufferAddress != NULL)
{
if (WriteToDevice)
{
SET_FLAG(Srb->SrbFlags, SRB_FLAGS_DATA_OUT);
ioctlCode = IOCTL_SCSI_EXECUTE_OUT;
}
else
{
SET_FLAG(Srb->SrbFlags, SRB_FLAGS_DATA_IN);
ioctlCode = IOCTL_SCSI_EXECUTE_IN;
}
}
else
{
ioctlCode = IOCTL_SCSI_EXECUTE_NONE;
}
// Zero out status.
Srb->ScsiStatus = 0;
Srb->SrbStatus = 0;
Srb->NextSrb = NULL;
// irp related fields
irp->MdlAddress = mdlAddress;
nextStack = IoGetNextIrpStackLocation(irp);
nextStack->MajorFunction = IRP_MJ_SCSI;
nextStack->Parameters.DeviceIoControl.IoControlCode = ioctlCode;
nextStack->Parameters.Scsi.Srb = Srb;
}
// 4.3 send Request to lower driver.
status = DeviceSendRequestSynchronously(Device, request, TRUE);
if (status != STATUS_CANCELLED)
{
NT_ASSERT(SRB_STATUS(Srb->SrbStatus) != SRB_STATUS_PENDING);
NT_ASSERT(status != STATUS_PENDING);
NT_ASSERT(!(Srb->SrbStatus & SRB_STATUS_QUEUE_FROZEN));
// 4.4 error process.
if (SRB_STATUS(Srb->SrbStatus) != SRB_STATUS_SUCCESS)
{
LONGLONG retryIntervalIn100ns = 0;
// Update status and determine if request should be retried.
retry = RequestSenseInfoInterpret(deviceExtension,
request,
Srb,
retryCount,
&status,
&retryIntervalIn100ns);
if (retry)
{
LARGE_INTEGER t;
t.QuadPart = -retryIntervalIn100ns;
retryCount++;
KeDelayExecutionThread(KernelMode, FALSE, &t);
}
}
else
{
// Request succeeded.
fdoData->LoggedTURFailureSinceLastIO = FALSE;
status = STATUS_SUCCESS;
retry = FALSE;
}
}
} while(retry);
if ((zpoddInfo != NULL) &&
(zpoddInfo->MonitorStartStopUnit != FALSE) &&
(SRB_STATUS(Srb->SrbStatus) == SRB_STATUS_SUCCESS))
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER,
"DeviceSendSrbSynchronously: soft eject detected, device marked as active\n"));
DeviceMarkActive(deviceExtension, TRUE, FALSE);
}
// 5. Release all allocated resources.
// required even though we allocated our own, since the port driver may
// have allocated one also
if (PORT_ALLOCATED_SENSE(deviceExtension, Srb))
{
FREE_PORT_ALLOCATED_SENSE_BUFFER(deviceExtension, Srb);
}
Exit:
if (senseInfoBuffer != NULL)
{
FREE_POOL(senseInfoBuffer);
}
Srb->SenseInfoBuffer = NULL;
Srb->SenseInfoBufferLength = 0;
if (mdlAddress)
{
if (memoryLocked)
{
MmUnlockPages(mdlAddress);
memoryLocked = FALSE;
}
IoFreeMdl(mdlAddress);
irp->MdlAddress = NULL;
}
if (request)
{
WdfObjectDelete(request);
}
return status;
}
VOID
DeviceSendNotification(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_In_ const GUID* Guid,
_In_ ULONG ExtraDataSize,
_In_opt_ PVOID ExtraData
)
/*++
Routine Description:
send notification to other components
Arguments:
DeviceExtension - device context.
Guid - GUID for the notification
ExtraDataSize - data size along with notification
ExtraData - data buffer send with notification
Return Value:
None
--*/
{
PTARGET_DEVICE_CUSTOM_NOTIFICATION notification;
ULONG requiredSize;
NTSTATUS status;
status = RtlULongAdd((sizeof(TARGET_DEVICE_CUSTOM_NOTIFICATION) - sizeof(UCHAR)),
ExtraDataSize,
&requiredSize);
if (!(NT_SUCCESS(status)) || (requiredSize > 0x0000ffff))
{
// MAX_USHORT, max total size for these events!
TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_MCN,
"Error sending event: size too large! (%x)\n",
requiredSize));
return;
}
notification = ExAllocatePoolWithTag(NonPagedPoolNx,
requiredSize,
CDROM_TAG_NOTIFICATION);
// if none allocated, exit
if (notification == NULL)
{
return;
}
// Prepare and send the request!
RtlZeroMemory(notification, requiredSize);
notification->Version = 1;
notification->Size = (USHORT)(requiredSize);
notification->FileObject = NULL;
notification->NameBufferOffset = -1;
notification->Event = *Guid;
if (ExtraData != NULL)
{
RtlCopyMemory(notification->CustomDataBuffer, ExtraData, ExtraDataSize);
}
IoReportTargetDeviceChangeAsynchronous(DeviceExtension->LowerPdo,
notification,
NULL,
NULL);
FREE_POOL(notification);
return;
}
VOID
DeviceSendStartUnit(
_In_ WDFDEVICE Device
)
/*++
Routine Description:
Send command to SCSI unit to start or power up.
Because this command is issued asynchronounsly, that is, without
waiting on it to complete, the IMMEDIATE flag is not set. This
means that the CDB will not return until the drive has powered up.
This should keep subsequent requests from being submitted to the
device before it has completely spun up.
This routine is called from the InterpretSense routine, when a
request sense returns data indicating that a drive must be
powered up.
This routine may also be called from a class driver's error handler,
or anytime a non-critical start device should be sent to the device.
Arguments:
Device - The device object.
Return Value:
None.
--*/
{
NTSTATUS status = STATUS_SUCCESS;
PCDROM_DEVICE_EXTENSION deviceExtension = NULL;
WDF_OBJECT_ATTRIBUTES attributes;
WDFREQUEST startUnitRequest = NULL;
WDFMEMORY inputMemory = NULL;
PCOMPLETION_CONTEXT context = NULL;
PSCSI_REQUEST_BLOCK srb = NULL;
PCDB cdb = NULL;
deviceExtension = DeviceGetExtension(Device);
if (NT_SUCCESS(status))
{
// Allocate Srb from nonpaged pool.
context = ExAllocatePoolWithTag(NonPagedPoolNx,
sizeof(COMPLETION_CONTEXT),
CDROM_TAG_COMPLETION_CONTEXT);
if (context == NULL)
{
TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
"DeviceSendStartUnit: Failed to allocate completion context\n"));
status = STATUS_INTERNAL_ERROR;
}
}
if (NT_SUCCESS(status))
{
// Save the device object in the context for use by the completion
// routine.
context->Device = Device;
srb = &context->Srb;
// Zero out srb.
RtlZeroMemory(srb, sizeof(SCSI_REQUEST_BLOCK));
// setup SRB structure.
srb->Length = sizeof(SCSI_REQUEST_BLOCK);
srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
srb->TimeOutValue = START_UNIT_TIMEOUT;
srb->SrbFlags = SRB_FLAGS_NO_DATA_TRANSFER |
SRB_FLAGS_DISABLE_SYNCH_TRANSFER;
// setup CDB
srb->CdbLength = 6;
cdb = (PCDB)srb->Cdb;
cdb->START_STOP.OperationCode = SCSIOP_START_STOP_UNIT;
cdb->START_STOP.Start = 1;
cdb->START_STOP.Immediate = 0;
cdb->START_STOP.LogicalUnitNumber = srb->Lun;
//Create Request for sending down to port driver
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes,
CDROM_REQUEST_CONTEXT);
attributes.ParentObject = deviceExtension->IoTarget;
status = WdfRequestCreate(&attributes,
deviceExtension->IoTarget,
&startUnitRequest);
}
if (NT_SUCCESS(status))
{
srb->OriginalRequest = WdfRequestWdmGetIrp(startUnitRequest);
NT_ASSERT(srb->OriginalRequest != NULL);
//Prepare the request
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
attributes.ParentObject = startUnitRequest;
status = WdfMemoryCreatePreallocated(&attributes,
(PVOID)srb,
sizeof(SCSI_REQUEST_BLOCK),
&inputMemory);
}
if (NT_SUCCESS(status))
{
status = WdfIoTargetFormatRequestForInternalIoctlOthers(deviceExtension->IoTarget,
startUnitRequest,
IOCTL_SCSI_EXECUTE_NONE,
inputMemory,
NULL,
NULL,
NULL,
NULL,
NULL);
}
if (NT_SUCCESS(status))
{
// Set a CompletionRoutine callback function.
WdfRequestSetCompletionRoutine(startUnitRequest,
DeviceAsynchronousCompletion,
context);
status = RequestSend(deviceExtension,
startUnitRequest,
deviceExtension->IoTarget,
0,
NULL);
}
// release resources when failed.
if (!NT_SUCCESS(status))
{
FREE_POOL(context);
if (startUnitRequest != NULL)
{
WdfObjectDelete(startUnitRequest);
}
}
return;
} // end StartUnit()
VOID
DeviceSendIoctlAsynchronously(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_In_ ULONG IoControlCode,
_In_ PDEVICE_OBJECT TargetDeviceObject
)
/*++
Routine Description:
Send an IOCTL asynchronously
Arguments:
DeviceExtension - device context.
IoControlCode - IOCTL code.
TargetDeviceObject - target device object.
Return Value:
None.
--*/
{
PIRP irp = NULL;
PIO_STACK_LOCATION nextIrpStack = NULL;
irp = IoAllocateIrp(DeviceExtension->DeviceObject->StackSize, FALSE);
if (irp != NULL)
{
nextIrpStack = IoGetNextIrpStackLocation(irp);
nextIrpStack->MajorFunction = IRP_MJ_DEVICE_CONTROL;
nextIrpStack->Parameters.DeviceIoControl.OutputBufferLength = 0;
nextIrpStack->Parameters.DeviceIoControl.InputBufferLength = 0;
nextIrpStack->Parameters.DeviceIoControl.IoControlCode = IoControlCode;
nextIrpStack->Parameters.DeviceIoControl.Type3InputBuffer = NULL;
IoSetCompletionRoutine(irp,
RequestAsynchronousIrpCompletion,
DeviceExtension,
TRUE,
TRUE,
TRUE);
(VOID) IoCallDriver(TargetDeviceObject, irp);
}
}
NTSTATUS
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
RequestAsynchronousIrpCompletion(
_In_ PDEVICE_OBJECT DeviceObject,
_In_ PIRP Irp,
_In_reads_opt_(_Inexpressible_("varies")) PVOID Context
)
/*++
Routine Description:
Free the Irp.
Arguments:
DeviceObject - device that the completion routine fires on.
Irp - The irp to be completed.
Context - IRP context
Return Value:
NTSTATUS
--*/
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Context);
IoFreeIrp(Irp);
return STATUS_MORE_PROCESSING_REQUIRED;
}
VOID
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
DeviceAsynchronousCompletion(
_In_ WDFREQUEST Request,
_In_ WDFIOTARGET Target,
_In_ PWDF_REQUEST_COMPLETION_PARAMS Params,
_In_ WDFCONTEXT Context
)
/*++
Routine Description:
This routine is called when an asynchronous I/O request
which was issused by the class driver completes. Examples of such requests
are release queue or START UNIT. This routine releases the queue if
necessary. It then frees the context and the IRP.
Arguments:
DeviceObject - The device object for the logical unit; however since this
is the top stack location the value is NULL.
Irp - Supplies a pointer to the Irp to be processed.
Context - Supplies the context to be used to process this request.
Return Value:
None.
--*/
{
PCOMPLETION_CONTEXT context = (PCOMPLETION_CONTEXT)Context;
PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(context->Device);
UNREFERENCED_PARAMETER(Target);
UNREFERENCED_PARAMETER(Params);
// If this is an execute srb, then check the return status and make sure.
// the queue is not frozen.
if (context->Srb.Function == SRB_FUNCTION_EXECUTE_SCSI)
{
// Check for a frozen queue.
if (context->Srb.SrbStatus & SRB_STATUS_QUEUE_FROZEN)
{
// Unfreeze the queue getting the device object from the context.
DeviceReleaseQueue(context->Device);
}
}
// free port-allocated sense buffer if we can detect
//
if (PORT_ALLOCATED_SENSE(deviceExtension, &context->Srb))
{
FREE_PORT_ALLOCATED_SENSE_BUFFER(deviceExtension, &context->Srb);
}
FREE_POOL(context);
WdfObjectDelete(Request);
} // end DeviceAsynchronousCompletion()
VOID
DeviceReleaseQueue(
_In_ WDFDEVICE Device
)
/*++
Routine Description:
This routine issues an internal device control command
to the port driver to release a frozen queue. The call
is issued asynchronously as DeviceReleaseQueue will be invoked
from the IO completion DPC (and will have no context to
wait for a synchronous call to complete).
This routine must be called with the remove lock held.
Arguments:
Device - The functional device object for the device with the frozen queue.
Return Value:
None.
--*/
{
PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(Device);
PSCSI_REQUEST_BLOCK srb = NULL;
KIRQL currentIrql;
// we raise irql seperately so we're not swapped out or suspended
// while holding the release queue irp in this routine. this lets
// us release the spin lock before lowering irql.
KeRaiseIrql(DISPATCH_LEVEL, &currentIrql);
WdfSpinLockAcquire(deviceExtension->ReleaseQueueSpinLock);
if (deviceExtension->ReleaseQueueInProgress)
{
// Someone is already doing this work - just set the flag to indicate that
// we need to release the queue again.
deviceExtension->ReleaseQueueNeeded = TRUE;
WdfSpinLockRelease(deviceExtension->ReleaseQueueSpinLock);
KeLowerIrql(currentIrql);
return;
}
// Mark that there is a release queue in progress and drop the spinlock.
deviceExtension->ReleaseQueueInProgress = TRUE;
WdfSpinLockRelease(deviceExtension->ReleaseQueueSpinLock);
srb = &(deviceExtension->ReleaseQueueSrb);
// Optical media are removable, so we just flush the queue. This will also release it.
srb->Function = SRB_FUNCTION_FLUSH_QUEUE;
srb->OriginalRequest = WdfRequestWdmGetIrp(deviceExtension->ReleaseQueueRequest);
// Set a CompletionRoutine callback function.
WdfRequestSetCompletionRoutine(deviceExtension->ReleaseQueueRequest,
DeviceReleaseQueueCompletion,
Device);
// Send the request. If an error occurs, complete the request.
RequestSend(deviceExtension,
deviceExtension->ReleaseQueueRequest,
deviceExtension->IoTarget,
WDF_REQUEST_SEND_OPTION_IGNORE_TARGET_STATE,
NULL);
KeLowerIrql(currentIrql);
return;
} // end DeviceReleaseQueue()
VOID
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
DeviceReleaseQueueCompletion(
_In_ WDFREQUEST Request,
_In_ WDFIOTARGET Target,
_In_ PWDF_REQUEST_COMPLETION_PARAMS Params,
_In_ WDFCONTEXT Context
)
/*++
Routine Description:
This routine is called when an asynchronous release queue request which
was issused in DeviceReleaseQueue completes. This routine prepares for
the next release queue request and resends it if necessary.
Arguments:
Request - The completed request.
Target - IoTarget object
Params - Completion parameters
Context - WDFDEVICE object handle.
Return Value:
None.
--*/
{
NTSTATUS status;
WDFDEVICE device = Context;
PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(device);
BOOLEAN releaseQueueNeeded = FALSE;
WDF_REQUEST_REUSE_PARAMS params = {0};
UNREFERENCED_PARAMETER(Target);
UNREFERENCED_PARAMETER(Params);
WDF_REQUEST_REUSE_PARAMS_INIT(&params,
WDF_REQUEST_REUSE_NO_FLAGS,
STATUS_SUCCESS);
// Grab the spinlock and clear the release queue in progress flag so others
// can run. Save (and clear) the state of the release queue needed flag
// so that we can issue a new release queue outside the spinlock.
WdfSpinLockAcquire(deviceExtension->ReleaseQueueSpinLock);
releaseQueueNeeded = deviceExtension->ReleaseQueueNeeded;
deviceExtension->ReleaseQueueNeeded = FALSE;
deviceExtension->ReleaseQueueInProgress = FALSE;
// Reuse the ReleaseQueueRequest for the next time.
status = WdfRequestReuse(Request,&params);
if (NT_SUCCESS(status))
{
// Preformat the ReleaseQueueRequest for the next time.
// This should always succeed because it was already preformatted once during device initialization
status = WdfIoTargetFormatRequestForInternalIoctlOthers(deviceExtension->IoTarget,
Request,
IOCTL_SCSI_EXECUTE_NONE,
deviceExtension->ReleaseQueueInputMemory,
NULL,
NULL,
NULL,
NULL,
NULL);
}
if (!NT_SUCCESS(status))
{
TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
"DeviceReleaseQueueCompletion: WdfIoTargetFormatRequestForInternalIoctlOthers failed, %!STATUS!\n",
status));
}
RequestClearSendTime(Request);
WdfSpinLockRelease(deviceExtension->ReleaseQueueSpinLock);
// If we need a release queue then issue one now. Another processor may
// have already started one in which case we'll try to issue this one after
// it is done - but we should never recurse more than one deep.
if (releaseQueueNeeded)
{
DeviceReleaseQueue(device);
}
return;
} // DeviceReleaseQueueCompletion()
//
// In order to provide better performance without the need to reboot,
// we need to implement a self-adjusting method to set and clear the
// srb flags based upon current performance.
//
// whenever there is an error, immediately grab the spin lock. the
// MP perf hit here is acceptable, since we're in an error path. this
// is also neccessary because we are guaranteed to be modifying the
// SRB flags here, setting SuccessfulIO to zero, and incrementing the
// actual error count (which is always done within this spinlock).
//
// whenever there is no error, increment a counter. if there have been
// errors on the device, and we've enabled dynamic perf, *and* we've
// just crossed the perf threshhold, then grab the spin lock and
// double check that the threshhold has, indeed been hit(*). then
// decrement the error count, and if it's dropped sufficiently, undo
// some of the safety changes made in the SRB flags due to the errors.
//
// * this works in all cases. even if lots of ios occur after the
// previous guy went in and cleared the successfulio counter, that
// just means that we've hit the threshhold again, and so it's proper
// to run the inner loop again.
//
VOID
DevicePerfIncrementErrorCount(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension
)
{
PCDROM_PRIVATE_FDO_DATA fdoData = DeviceExtension->PrivateFdoData;
KIRQL oldIrql;
ULONG errors;
KeAcquireSpinLock(&fdoData->SpinLock, &oldIrql);
fdoData->Perf.SuccessfulIO = 0; // implicit interlock
errors = InterlockedIncrement((PLONG)&DeviceExtension->ErrorCount);
if (errors >= CLASS_ERROR_LEVEL_1)
{
// If the error count has exceeded the error limit, then disable
// any tagged queuing, multiple requests per lu queueing
// and sychronous data transfers.
//
// Clearing the no queue freeze flag prevents the port driver
// from sending multiple requests per logical unit.
CLEAR_FLAG(DeviceExtension->SrbFlags, SRB_FLAGS_NO_QUEUE_FREEZE);
CLEAR_FLAG(DeviceExtension->SrbFlags, SRB_FLAGS_QUEUE_ACTION_ENABLE);
SET_FLAG(DeviceExtension->SrbFlags, SRB_FLAGS_DISABLE_SYNCH_TRANSFER);
TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
"PerfIncrementErrorCount: Too many errors; disabling tagged queuing and "
"synchronous data tranfers.\n"));
}
if (errors >= CLASS_ERROR_LEVEL_2)
{
// If a second threshold is reached, disable disconnects.
SET_FLAG(DeviceExtension->SrbFlags, SRB_FLAGS_DISABLE_DISCONNECT);
TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_GENERAL,
"PerfIncrementErrorCount: Too many errors; disabling disconnects.\n"));
}
KeReleaseSpinLock(&fdoData->SpinLock, oldIrql);
return;
}
_IRQL_requires_max_(APC_LEVEL)
PVOID
DeviceFindFeaturePage(
_In_reads_bytes_(Length) PGET_CONFIGURATION_HEADER FeatureBuffer,
_In_ ULONG const Length,
_In_ FEATURE_NUMBER const Feature
)
/*++
Routine Description:
find the specific feature page in the buffer
Arguments:
FeatureBuffer - buffer contains the device feature set.
Length - buffer length
Feature - the feature number looking for.
Return Value:
PVOID - pointer to the starting location of the specific feature in buffer.
--*/
{
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
DevicePrintAllFeaturePages(
_In_reads_bytes_(Usable) PGET_CONFIGURATION_HEADER Buffer,
_In_ ULONG const Usable
)
/*++
Routine Description:
print out all feature pages in the buffer
Arguments:
Buffer - buffer contains the device feature set.
Usable -
Return Value:
none
--*/
{
#if DBG
PFEATURE_HEADER header;
PAGED_CODE();
////////////////////////////////////////////////////////////////////////////////
// items expected to ALWAYS be current if they exist
////////////////////////////////////////////////////////////////////////////////
header = DeviceFindFeaturePage(Buffer, Usable, FeatureProfileList);
if (header != NULL) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: CurrentProfile %x "
"with %x bytes of data at %p\n",
Buffer->CurrentProfile[0] << 8 |
Buffer->CurrentProfile[1],
Usable, Buffer));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureCore);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"CORE Features"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureMorphing);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"Morphing"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureRemovableMedium);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"Removable Medium"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeaturePowerManagement);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"Power Management"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureEmbeddedChanger);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"Embedded Changer"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureMicrocodeUpgrade);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"Microcode Update"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureTimeout);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"Timeouts"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureLogicalUnitSerialNumber);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"LUN Serial Number"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureFirmwareDate);
if (header) {
ULONG featureSize = header->AdditionalLength;
featureSize += RTL_SIZEOF_THROUGH_FIELD(FEATURE_HEADER, AdditionalLength);
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"Firmware Date"
));
if (featureSize >= RTL_SIZEOF_THROUGH_FIELD(FEATURE_DATA_FIRMWARE_DATE, Minute))
{
PFEATURE_DATA_FIRMWARE_DATE date = (PFEATURE_DATA_FIRMWARE_DATE)header;
// show date as "YYYY/MM/DD hh:mm", which is 18 chars (17+NULL)
UCHAR dateString[18] = { 0 };
dateString[ 0] = date->Year[0];
dateString[ 1] = date->Year[1];
dateString[ 2] = date->Year[2];
dateString[ 3] = date->Year[3];
dateString[ 4] = '/';
dateString[ 5] = date->Month[0];
dateString[ 6] = date->Month[1];
dateString[ 7] = '/';
dateString[ 8] = date->Day[0];
dateString[ 9] = date->Day[1];
dateString[10] = ' ';
dateString[11] = ' ';
dateString[12] = date->Hour[0];
dateString[13] = date->Hour[1];
dateString[14] = ':';
dateString[15] = date->Minute[0];
dateString[16] = date->Minute[1];
dateString[17] = 0;
// SECONDS IS NOT AVAILABLE ON EARLY IMPLEMENTATIONS -- ignore it
//dateString[17] = ':';
//dateString[18] = date->Seconds[0];
//dateString[19] = date->Seconds[1];
//dateString[20] = 0;
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: Firmware Date/Time %s (UTC)\n",
(PCSTR)dateString
));
}
}
////////////////////////////////////////////////////////////////////////////////
// items expected not to always be current
////////////////////////////////////////////////////////////////////////////////
header = DeviceFindFeaturePage(Buffer, Usable, FeatureWriteProtect);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_GENERAL,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"Software Write Protect"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureRandomReadable);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"Random Reads"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureMultiRead);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"Multi-Read"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureCdRead);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"reading from CD-ROM/R/RW"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureDvdRead);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"DVD Structure Reads"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureRandomWritable);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"Random Writes"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureIncrementalStreamingWritable);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"Incremental Streaming Writing"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureSectorErasable);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"Sector Erasable Media"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureFormattable);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"Formatting"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureDefectManagement);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"defect management"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureWriteOnce);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"Write Once Media"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureRestrictedOverwrite);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"Restricted Overwrites"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureCdrwCAVWrite);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"CD-RW CAV recording"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureMrw);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"Mount Rainier media"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureEnhancedDefectReporting);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"Enhanced Defect Reporting"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureDvdPlusRW);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"DVD+RW media"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureRigidRestrictedOverwrite);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"Rigid Restricted Overwrite"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureCdTrackAtOnce);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"CD Recording (Track At Once)"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureCdMastering);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"CD Recording (Mastering)"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureDvdRecordableWrite);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"DVD Recording (Mastering)"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureDDCDRead);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"DD CD Reading"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureDDCDRWrite);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"DD CD-R Writing"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureDDCDRWWrite);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"DD CD-RW Writing"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureLayerJumpRecording);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"Layer Jump Recording"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureHDDVDRead);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"HD-DVD Reading"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureHDDVDWrite);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"HD-DVD Writing"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureSMART);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"S.M.A.R.T."
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureCDAudioAnalogPlay);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"Analogue CD Audio Operations"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureDvdCSS);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"DVD CSS"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureRealTimeStreaming);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"Real-time Streaming Reads"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureDiscControlBlocks);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"DVD Disc Control Blocks"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureDvdCPRM);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"DVD CPRM"
));
}
header = DeviceFindFeaturePage(Buffer, Usable, FeatureAACS);
if (header) {
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT,
"CdromGetConfiguration: %s %s\n",
(header->Current ?
"Currently supports" : "Is able to support"),
"AACS"
));
}
#else
PAGED_CODE();
UNREFERENCED_PARAMETER(Usable);
UNREFERENCED_PARAMETER(Buffer);
#endif // DBG
return;
}
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS
MediaReadCapacity(
_In_ WDFDEVICE Device
)
/*++
Routine Description:
Get media capacity
Arguments:
Device - the device that owns the media
Return Value:
NTSTATUS
--*/
{
NTSTATUS status;
SCSI_REQUEST_BLOCK srb;
PCDB cdb = NULL;
READ_CAPACITY_DATA capacityData;
PAGED_CODE();
RtlZeroMemory(&srb, sizeof(srb));
RtlZeroMemory(&capacityData, sizeof(capacityData));
cdb = (PCDB)(&srb.Cdb);
//Prepare SCSI command fields
srb.CdbLength = 10;
srb.TimeOutValue = CDROM_READ_CAPACITY_TIMEOUT;
cdb->CDB10.OperationCode = SCSIOP_READ_CAPACITY;
status = DeviceSendSrbSynchronously(Device,
&srb,
&capacityData,
sizeof(READ_CAPACITY_DATA),
FALSE,
NULL);
//Remember the result
if (!NT_SUCCESS(status))
{
//Set the BytesPerBlock to zero, this is for safe as if error happens this field should stay zero (no change).
//it will be treated as error case in MediaReadCapacityDataInterpret()
capacityData.BytesPerBlock = 0;
}
MediaReadCapacityDataInterpret(Device, &capacityData);
return status;
}
_IRQL_requires_max_(APC_LEVEL)
VOID
MediaReadCapacityDataInterpret(
_In_ WDFDEVICE Device,
_In_ PREAD_CAPACITY_DATA ReadCapacityBuffer
)
/*++
Routine Description:
Interpret media capacity and set corresponding fields in device context
Arguments:
Device - the device that owns the media
ReadCapacityBuffer - data buffer of capacity
Return Value:
none
--*/
{
PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(Device);
ULONG lastSector = 0;
ULONG bps = 0;
ULONG lastBit = 0;
ULONG bytesPerBlock = 0;
BOOLEAN errorHappened = FALSE;
PAGED_CODE();
NT_ASSERT(ReadCapacityBuffer);
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,
"MediaReadCapacityDataInterpret: Entering\n"));
// Swizzle bytes from Read Capacity and translate into
// the necessary geometry information in the device extension.
bytesPerBlock = ReadCapacityBuffer->BytesPerBlock;
((PFOUR_BYTE)&bps)->Byte0 = ((PFOUR_BYTE)&bytesPerBlock)->Byte3;
((PFOUR_BYTE)&bps)->Byte1 = ((PFOUR_BYTE)&bytesPerBlock)->Byte2;
((PFOUR_BYTE)&bps)->Byte2 = ((PFOUR_BYTE)&bytesPerBlock)->Byte1;
((PFOUR_BYTE)&bps)->Byte3 = ((PFOUR_BYTE)&bytesPerBlock)->Byte0;
// Insure that bps is a power of 2.
// This corrects a problem with the HP 4020i CDR where it
// returns an incorrect number for bytes per sector.
if (!bps)
{
// Set disk geometry to default values (per ISO 9660).
bps = 2048;
errorHappened = TRUE;
}
else
{
lastBit = (ULONG)(-1);
while (bps)
{
lastBit++;
bps = (bps >> 1);
}
bps = (1 << lastBit);
}
deviceExtension->DiskGeometry.BytesPerSector = bps;
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,
"MediaReadCapacityDataInterpret: Calculated bps %#x\n",
deviceExtension->DiskGeometry.BytesPerSector));
// Copy last sector in reverse byte order.
bytesPerBlock = ReadCapacityBuffer->LogicalBlockAddress;
((PFOUR_BYTE)&lastSector)->Byte0 = ((PFOUR_BYTE)&bytesPerBlock)->Byte3;
((PFOUR_BYTE)&lastSector)->Byte1 = ((PFOUR_BYTE)&bytesPerBlock)->Byte2;
((PFOUR_BYTE)&lastSector)->Byte2 = ((PFOUR_BYTE)&bytesPerBlock)->Byte1;
((PFOUR_BYTE)&lastSector)->Byte3 = ((PFOUR_BYTE)&bytesPerBlock)->Byte0;
// Calculate sector to byte shift.
WHICH_BIT(bps, deviceExtension->SectorShift);
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_GENERAL,
"MediaReadCapacityDataInterpret: Sector size is %d\n",
deviceExtension->DiskGeometry.BytesPerSector));
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,
"MediaReadCapacityDataInterpret: Number of Sectors is %d\n",
lastSector + 1));
// Calculate media capacity in bytes.
if (errorHappened)
{
// Set disk geometry to default values (per ISO 9660).
deviceExtension->PartitionLength.QuadPart = (LONGLONG)(0x7fffffff);
}
else
{
deviceExtension->PartitionLength.QuadPart = (LONGLONG)(lastSector + 1);
deviceExtension->PartitionLength.QuadPart =
(deviceExtension->PartitionLength.QuadPart << deviceExtension->SectorShift);
}
// we've defaulted to 32/64 forever. don't want to change this now...
deviceExtension->DiskGeometry.TracksPerCylinder = 0x40;
deviceExtension->DiskGeometry.SectorsPerTrack = 0x20;
// Calculate number of cylinders.
deviceExtension->DiskGeometry.Cylinders.QuadPart = (LONGLONG)((lastSector + 1) / (32 * 64));
deviceExtension->DiskGeometry.MediaType = RemovableMedia;
return;
}
_IRQL_requires_max_(APC_LEVEL)
VOID
DevicePickDvdRegion(
_In_ WDFDEVICE Device
)
/*++
Routine Description:
pick a default dvd region
Arguments:
Device - Device Object
Return Value:
none
--*/
{
NTSTATUS status;
PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(Device);
// these five pointers all point to dvdReadStructure or part of
// its data, so don't deallocate them more than once!
PDVD_READ_STRUCTURE dvdReadStructure;
PDVD_COPY_PROTECT_KEY copyProtectKey;
PDVD_COPYRIGHT_DESCRIPTOR dvdCopyRight;
PDVD_RPC_KEY rpcKey;
PDVD_SET_RPC_KEY dvdRpcKey;
size_t bytesReturned = 0;
ULONG bufferLen = 0;
UCHAR mediaRegion = 0;
ULONG pickDvdRegion = 0;
ULONG defaultDvdRegion = 0;
ULONG dvdRegion = 0;
WDFKEY registryKey = NULL;
DECLARE_CONST_UNICODE_STRING(registryValueName, DVD_DEFAULT_REGION);
PAGED_CODE();
if ((pickDvdRegion = InterlockedExchange((PLONG)&deviceExtension->DeviceAdditionalData.PickDvdRegion, 0)) == 0)
{
// it was non-zero, so either another thread will do this, or
// we no longer need to pick a region
return;
}
bufferLen = max(
max(sizeof(DVD_DESCRIPTOR_HEADER) +
sizeof(DVD_COPYRIGHT_DESCRIPTOR),
sizeof(DVD_READ_STRUCTURE)
),
max(DVD_RPC_KEY_LENGTH,
DVD_SET_RPC_KEY_LENGTH
)
);
dvdReadStructure = (PDVD_READ_STRUCTURE)
ExAllocatePoolWithTag(PagedPool, bufferLen, DVD_TAG_DVD_REGION);
if (dvdReadStructure == NULL)
{
InterlockedExchange((PLONG)&deviceExtension->DeviceAdditionalData.PickDvdRegion, pickDvdRegion);
return;
}
copyProtectKey = (PDVD_COPY_PROTECT_KEY)dvdReadStructure;
dvdCopyRight = (PDVD_COPYRIGHT_DESCRIPTOR)
((PDVD_DESCRIPTOR_HEADER)dvdReadStructure)->Data;
// get the media region
RtlZeroMemory (dvdReadStructure, bufferLen);
dvdReadStructure->Format = DvdCopyrightDescriptor;
// Build and send a request for READ_KEY
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_IOCTL,
"DevicePickDvdRegion (%p): Getting Copyright Descriptor\n",
Device));
status = ReadDvdStructure(deviceExtension,
NULL,
dvdReadStructure,
sizeof(DVD_READ_STRUCTURE),
dvdReadStructure,
sizeof(DVD_DESCRIPTOR_HEADER) + sizeof(DVD_COPYRIGHT_DESCRIPTOR),
&bytesReturned);
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_IOCTL,
"DevicePickDvdRegion (%p): Got Copyright Descriptor %x\n",
Device, status));
if ((NT_SUCCESS(status)) &&
(dvdCopyRight->CopyrightProtectionType == 0x01))
{
// keep the media region bitmap around
// a 1 means ok to play
if (dvdCopyRight->RegionManagementInformation == 0xff)
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL,
"DevicePickDvdRegion (%p): RegionManagementInformation "
"is set to dis-allow playback for all regions. This is "
"most likely a poorly authored disc. defaulting to all "
"region disc for purpose of choosing initial region\n",
Device));
dvdCopyRight->RegionManagementInformation = 0;
}
mediaRegion = ~dvdCopyRight->RegionManagementInformation;
}
else
{
// can't automatically pick a default region on a drive without media, so just exit
TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL,
"DevicePickDvdRegion (%p): failed to auto-choose a region due to status %x getting copyright descriptor\n",
Device, status));
goto getout;
}
// get the device region
RtlZeroMemory (copyProtectKey, bufferLen);
copyProtectKey->KeyLength = DVD_RPC_KEY_LENGTH;
copyProtectKey->KeyType = DvdGetRpcKey;
// Build and send a request for READ_KEY for RPC key
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_IOCTL,
"DevicePickDvdRegion (%p): Getting RpcKey\n",
Device));
status = DvdStartSessionReadKey(deviceExtension,
IOCTL_DVD_READ_KEY,
NULL,
copyProtectKey,
DVD_RPC_KEY_LENGTH,
copyProtectKey,
DVD_RPC_KEY_LENGTH,
&bytesReturned);
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_IOCTL,
"DevicePickDvdRegion (%p): Got RpcKey %x\n",
Device, status));
if (!NT_SUCCESS(status))
{
TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL,
"DevicePickDvdRegion (%p): failed to get RpcKey from "
"a DVD Device\n", Device));
goto getout;
}
// so we now have what we can get for the media region and the
// drive region. we will not set a region if the drive has one
// set already (mask is not all 1's), nor will we set a region
// if there are no more user resets available.
rpcKey = (PDVD_RPC_KEY)copyProtectKey->KeyData;
if (rpcKey->RegionMask != 0xff)
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL,
"DevicePickDvdRegion (%p): not picking a region since "
"it is already chosen\n", Device));
goto getout;
}
if (rpcKey->UserResetsAvailable <= 1)
{
TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL,
"DevicePickDvdRegion (%p): not picking a region since "
"only one change remains\n", Device));
goto getout;
}
// OOBE sets this key based upon the system locale
status = WdfDriverOpenParametersRegistryKey(WdfGetDriver(),
KEY_READ,
WDF_NO_OBJECT_ATTRIBUTES,
&registryKey);
if (NT_SUCCESS(status))
{
status = WdfRegistryQueryULong(registryKey,
&registryValueName,
&defaultDvdRegion);
WdfRegistryClose(registryKey);
}
if (!NT_SUCCESS(status))
{
TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL,
"DevicePickDvdRegion (%p): failed to read registry value due to status %x\n",
Device, status));
// by default the default Dvd region is 0
defaultDvdRegion = 0;
status = STATUS_SUCCESS;
}
if (defaultDvdRegion > DVD_MAX_REGION)
{
// the registry has a bogus default
TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL,
"DevicePickDvdRegion (%p): registry has a bogus default "
"region value of %x\n", Device, defaultDvdRegion));
defaultDvdRegion = 0;
}
// if defaultDvdRegion == 0, it means no default.
// we will select the initial dvd region for the user
if ((defaultDvdRegion != 0) &&
(mediaRegion & (1 << (defaultDvdRegion - 1))))
{
// first choice:
// the media has region that matches
// the default dvd region.
dvdRegion = (1 << (defaultDvdRegion - 1));
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL,
"DevicePickDvdRegion (%p): Choice #1: media matches "
"drive's default, chose region %x\n", Device, dvdRegion));
}
else if (mediaRegion)
{
// second choice:
// pick the lowest region number from the media
UCHAR mask = 1;
dvdRegion = 0;
while (mediaRegion && !dvdRegion)
{
// pick the lowest bit
dvdRegion = mediaRegion & mask;
mask <<= 1;
}
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL,
"DevicePickDvdRegion (%p): Choice #2: choosing lowest "
"media region %x\n", Device, dvdRegion));
}
else if (defaultDvdRegion)
{
// third choice:
// default dvd region from the dvd class installer
dvdRegion = (1 << (defaultDvdRegion - 1));
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL,
"DevicePickDvdRegion (%p): Choice #3: using default "
"region for this install %x\n", Device, dvdRegion));
}
else
{
// unable to pick one for the user -- this should rarely
// happen, since the proppage dvd class installer sets
// the key based upon the system locale
TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL,
"DevicePickDvdRegion (%p): Choice #4: failed to choose "
"a media region\n", Device));
goto getout;
}
// now that we've chosen a region, set the region by sending the
// appropriate request to the drive
RtlZeroMemory (copyProtectKey, bufferLen);
copyProtectKey->KeyLength = DVD_SET_RPC_KEY_LENGTH;
copyProtectKey->KeyType = DvdSetRpcKey;
dvdRpcKey = (PDVD_SET_RPC_KEY)copyProtectKey->KeyData;
dvdRpcKey->PreferredDriveRegionCode = (UCHAR)~dvdRegion;
// Build and send request for SEND_KEY
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_IOCTL,
"DevicePickDvdRegion (%p): Sending new Rpc Key to region %x\n",
Device, dvdRegion));
status = DvdSendKey(deviceExtension,
NULL,
copyProtectKey,
DVD_SET_RPC_KEY_LENGTH,
&bytesReturned);
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_IOCTL,
"DevicePickDvdRegion (%p): Sent new Rpc Key %x\n",
Device, status));
if (!NT_SUCCESS(status))
{
TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, "DevicePickDvdRegion (%p): unable to set dvd initial "
" region code (%x)\n", Device, status));
}
else
{
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_IOCTL, "DevicePickDvdRegion (%p): Successfully set dvd "
"initial region\n", Device));
pickDvdRegion = 0;
}
getout:
if (dvdReadStructure)
{
FREE_POOL(dvdReadStructure);
}
// update the new PickDvdRegion value
InterlockedExchange((PLONG)&deviceExtension->DeviceAdditionalData.PickDvdRegion, pickDvdRegion);
return;
}
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS
DeviceRegisterInterface(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_In_ CDROM_DEVICE_INTERFACES InterfaceType
)
/*++
Routine Description:
used to register device class interface or mount device interface
Arguments:
DeviceExtension - device context
InterfaceType - interface type to be registered.
Return Value:
NTSTATUS
--*/
{
NTSTATUS status;
WDFSTRING string = NULL;
GUID* interfaceGuid = NULL;
PUNICODE_STRING savingString = NULL;
BOOLEAN setRestricted = FALSE;
UNICODE_STRING localString;
PAGED_CODE();
//Get parameters
switch(InterfaceType)
{
case CdRomDeviceInterface:
interfaceGuid = (LPGUID)&GUID_DEVINTERFACE_CDROM;
setRestricted = TRUE;
savingString = &localString;
break;
case MountedDeviceInterface:
interfaceGuid = (LPGUID)&MOUNTDEV_MOUNTED_DEVICE_GUID;
savingString = &(DeviceExtension->MountedDeviceInterfaceName);
break;
default:
return STATUS_INVALID_PARAMETER;
}
status = WdfDeviceCreateDeviceInterface(DeviceExtension->Device,
interfaceGuid,
NULL);
if (!NT_SUCCESS(status))
{
TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_INIT,
"DeviceRegisterInterface: Unable to register cdrom "
"DCA for fdo %p type: %s [%lx]\n",
DeviceExtension->Device,
(InterfaceType == CdRomDeviceInterface)? "CdRom Interface" : "Mounted Device Interface",
status));
}
// Retrieve interface string
if (NT_SUCCESS(status))
{
// The string object will be released when its parent object is released.
WDF_OBJECT_ATTRIBUTES attributes;
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
attributes.ParentObject = DeviceExtension->Device;
status = WdfStringCreate(WDF_NO_OBJECT_ATTRIBUTES,
NULL,
&string);
}
if (NT_SUCCESS(status))
{
status = WdfDeviceRetrieveDeviceInterfaceString(DeviceExtension->Device,
interfaceGuid,
NULL,
string);
}
if (NT_SUCCESS(status))
{
WdfStringGetUnicodeString(string, savingString);
if (setRestricted) {
WdfObjectDelete(string);
}
}
return status;
} // end DeviceRegisterInterface()
VOID
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
DeviceRestoreDefaultSpeed(
_In_ WDFWORKITEM WorkItem
)
/*++
Routine Description:
This workitem is called on a media change when the CDROM device
speed should be restored to the default value.
Arguments:
Fdo - Supplies the device object for the CDROM device.
WorkItem - Supplies the pointer to the workitem.
Return Value:
None
--*/
{
NTSTATUS status;
WDFDEVICE device = WdfWorkItemGetParentObject(WorkItem);
PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(device);
PPERFORMANCE_DESCRIPTOR perfDescriptor;
ULONG transferLength = sizeof(PERFORMANCE_DESCRIPTOR);
SCSI_REQUEST_BLOCK srb = {0};
PCDB cdb = (PCDB)srb.Cdb;
PAGED_CODE();
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, "DeviceRestoreDefaultSpeed: Restore device speed for %p\n", device));
perfDescriptor = ExAllocatePoolWithTag(NonPagedPoolNxCacheAligned,
transferLength,
CDROM_TAG_STREAM);
if (perfDescriptor == NULL)
{
return;
}
RtlZeroMemory(perfDescriptor, transferLength);
perfDescriptor->RestoreDefaults = TRUE;
srb.TimeOutValue = deviceExtension->TimeOutValue;
srb.CdbLength = 12;
cdb->SET_STREAMING.OperationCode = SCSIOP_SET_STREAMING;
REVERSE_BYTES_SHORT(&cdb->SET_STREAMING.ParameterListLength, &transferLength);
status = DeviceSendSrbSynchronously(device,
&srb,
perfDescriptor,
transferLength,
TRUE,
NULL);
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL,
"DeviceRestoreDefaultSpeed: Set Streaming command completed with status: 0x%X\n", status));
FREE_POOL(perfDescriptor);
WdfObjectDelete(WorkItem);
return;
}
// custom string match -- careful!
_IRQL_requires_max_(APC_LEVEL)
BOOLEAN
StringsAreMatched(
_In_opt_z_ PCHAR StringToMatch,
_In_z_ PCHAR TargetString
)
/*++
Routine Description:
compares if two strings are identical
Arguments:
StringToMatch - source string.
TargetString - target string.
Return Value:
BOOLEAN - TRUE (identical); FALSE (not match)
--*/
{
size_t length;
PAGED_CODE();
NT_ASSERT(TargetString);
// if no match requested, return TRUE
if (StringToMatch == NULL)
{
return TRUE;
}
// cache the string length for efficiency
length = strlen(StringToMatch);
// ZERO-length strings may only match zero-length strings
if (length == 0)
{
return (strlen(TargetString) == 0);
}
// strncmp returns zero if the strings match
return (strncmp(StringToMatch, TargetString, length) == 0);
}
NTSTATUS
RequestSetContextFields(
_In_ WDFREQUEST Request,
_In_ PSYNC_HANDLER Handler
)
/*++
Routine Description:
set the request object context fields
Arguments:
Request - request object.
Handler - the function that finally handles this request.
Return Value:
NTSTATUS
--*/
{
NTSTATUS status = STATUS_SUCCESS;
PCDROM_REQUEST_CONTEXT requestContext = RequestGetContext(Request);
PKEVENT syncEvent = NULL;
syncEvent = ExAllocatePoolWithTag(NonPagedPoolNx,
sizeof(KEVENT),
CDROM_TAG_SYNC_EVENT);
if (syncEvent == NULL)
{
// memory allocation failed.
status = STATUS_INSUFFICIENT_RESOURCES;
}
else
{
// now, put the special synchronization information into the context
requestContext->SyncRequired = TRUE;
requestContext->SyncEvent = syncEvent;
requestContext->SyncCallback = Handler;
status = STATUS_SUCCESS;
}
return status;
}
NTSTATUS
RequestDuidGetDeviceIdProperty(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_In_ WDFREQUEST Request,
_In_ WDF_REQUEST_PARAMETERS RequestParameters,
_Out_ size_t * DataLength
)
/*++
Routine Description:
Arguments:
DeviceExtension - device context
Request - request object.
RequestParameters - request parameter
DataLength - transferred data length.
Return Value:
NTSTATUS
--*/
{
NTSTATUS status = STATUS_SUCCESS;
PSTORAGE_DEVICE_ID_DESCRIPTOR deviceIdDescriptor = NULL;
PSTORAGE_DESCRIPTOR_HEADER descHeader = NULL;
STORAGE_PROPERTY_ID propertyId = StorageDeviceIdProperty;
*DataLength = 0;
// Get the VPD page 83h data.
status = DeviceRetrieveDescriptor(DeviceExtension->Device,
&propertyId,
(PSTORAGE_DESCRIPTOR_HEADER*)&deviceIdDescriptor);
if (NT_SUCCESS(status) && (deviceIdDescriptor == NULL))
{
status = STATUS_NOT_FOUND;
}
if (NT_SUCCESS(status))
{
status = WdfRequestRetrieveOutputBuffer(Request,
RequestParameters.Parameters.DeviceIoControl.OutputBufferLength,
&descHeader,
NULL);
}
if (NT_SUCCESS(status))
{
PSTORAGE_DEVICE_UNIQUE_IDENTIFIER storageDuid = NULL;
ULONG offset = descHeader->Size;
PUCHAR dest = (PUCHAR)descHeader + offset;
size_t outputBufferSize;
outputBufferSize = RequestParameters.Parameters.DeviceIoControl.OutputBufferLength;
// Adjust required size and potential destination location.
status = RtlULongAdd(descHeader->Size, deviceIdDescriptor->Size, &descHeader->Size);
if (NT_SUCCESS(status) &&
(outputBufferSize < descHeader->Size))
{
// Output buffer is too small. Return error and make sure
// the caller gets info about required buffer size.
*DataLength = descHeader->Size;
status = STATUS_BUFFER_OVERFLOW;
}
if (NT_SUCCESS(status))
{
storageDuid = (PSTORAGE_DEVICE_UNIQUE_IDENTIFIER)descHeader;
storageDuid->StorageDeviceIdOffset = offset;
RtlCopyMemory(dest,
deviceIdDescriptor,
deviceIdDescriptor->Size);
*DataLength = storageDuid->Size;
status = STATUS_SUCCESS;
}
FREE_POOL(deviceIdDescriptor);
}
return status;
}
NTSTATUS
RequestDuidGetDeviceProperty(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_In_ WDFREQUEST Request,
_In_ WDF_REQUEST_PARAMETERS RequestParameters,
_Out_ size_t * DataLength
)
/*++
Routine Description:
Arguments:
DeviceExtension - device context
Request - request object.
RequestParameters - request parameter
DataLength - transferred data length.
Return Value:
NTSTATUS
--*/
{
NTSTATUS status = STATUS_SUCCESS;
PSTORAGE_DEVICE_DESCRIPTOR deviceDescriptor = DeviceExtension->DeviceDescriptor;
PSTORAGE_DESCRIPTOR_HEADER descHeader = NULL;
PSTORAGE_DEVICE_UNIQUE_IDENTIFIER storageDuid;
PUCHAR dest = NULL;
if (deviceDescriptor == NULL)
{
status = STATUS_NOT_FOUND;
}
if (NT_SUCCESS(status))
{
status = WdfRequestRetrieveOutputBuffer(Request,
RequestParameters.Parameters.DeviceIoControl.OutputBufferLength,
&descHeader,
NULL);
}
if (NT_SUCCESS(status) &&
(deviceDescriptor->SerialNumberOffset == 0))
{
status = STATUS_NOT_FOUND;
}
// Use this info only if serial number is available.
if (NT_SUCCESS(status))
{
ULONG offset = descHeader->Size;
size_t outputBufferSize = RequestParameters.Parameters.DeviceIoControl.OutputBufferLength;
// Adjust required size and potential destination location.
dest = (PUCHAR)descHeader + offset;
status = RtlULongAdd(descHeader->Size, deviceDescriptor->Size, &descHeader->Size);
if (NT_SUCCESS(status) &&
(outputBufferSize < descHeader->Size))
{
// Output buffer is too small. Return error and make sure
// the caller get info about required buffer size.
*DataLength = descHeader->Size;
status = STATUS_BUFFER_OVERFLOW;
}
if (NT_SUCCESS(status))
{
storageDuid = (PSTORAGE_DEVICE_UNIQUE_IDENTIFIER)descHeader;
storageDuid->StorageDeviceOffset = offset;
RtlCopyMemory(dest,
deviceDescriptor,
deviceDescriptor->Size);
*DataLength = storageDuid->Size;
status = STATUS_SUCCESS;
}
}
return status;
}
_IRQL_requires_max_(APC_LEVEL)
ULONG
DeviceRetrieveModeSenseUsingScratch(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_In_reads_bytes_(Length) PCHAR ModeSenseBuffer,
_In_ ULONG Length,
_In_ UCHAR PageCode,
_In_ UCHAR PageControl
)
/*++
Routine Description:
retrieve mode sense informaiton of the device
Arguments:
DeviceExtension - device context
ModeSenseBuffer - buffer to savee the mode sense info.
Length - buffer length
PageCode - .
PageControl -
Return Value:
ULONG - transferred data length
--*/
{
NTSTATUS status = STATUS_SUCCESS;
ULONG transferSize = min(Length, DeviceExtension->ScratchContext.ScratchBufferSize);
CDB cdb;
PAGED_CODE();
ScratchBuffer_BeginUse(DeviceExtension);
RtlZeroMemory(&cdb, sizeof(CDB));
// Set up the CDB
cdb.MODE_SENSE.OperationCode = SCSIOP_MODE_SENSE;
cdb.MODE_SENSE.PageCode = PageCode;
cdb.MODE_SENSE.Pc = PageControl;
cdb.MODE_SENSE.AllocationLength = (UCHAR)transferSize;
status = ScratchBuffer_ExecuteCdb(DeviceExtension, NULL, transferSize, TRUE, &cdb, 6);
if (NT_SUCCESS(status))
{
transferSize = min(Length, DeviceExtension->ScratchContext.ScratchSrb->DataTransferLength);
RtlCopyMemory(ModeSenseBuffer,
DeviceExtension->ScratchContext.ScratchBuffer,
transferSize);
}
ScratchBuffer_EndUse(DeviceExtension);
return transferSize;
}
_IRQL_requires_max_(APC_LEVEL)
PVOID
ModeSenseFindSpecificPage(
_In_reads_bytes_(Length) PCHAR ModeSenseBuffer,
_In_ size_t Length,
_In_ UCHAR PageMode,
_In_ BOOLEAN Use6BytesCdb
)
/*++
Routine Description:
This routine scans through the mode sense data and finds the requested
mode sense page code.
Arguments:
ModeSenseBuffer - Supplies a pointer to the mode sense data.
Length - Indicates the length of valid data.
PageMode - Supplies the page mode to be searched for.
Use6BytesCdb - Indicates whether 6 or 10 byte mode sense was used.
Return Value:
A pointer to the the requested mode page. If the mode page was not found
then NULL is return.
--*/
{
PCHAR limit;
ULONG parameterHeaderLength;
PVOID result = NULL;
PAGED_CODE();
limit = ModeSenseBuffer + Length;
parameterHeaderLength = (Use6BytesCdb)
? sizeof(MODE_PARAMETER_HEADER)
: sizeof(MODE_PARAMETER_HEADER10);
if (Length >= parameterHeaderLength)
{
PMODE_PARAMETER_HEADER10 modeParam10;
ULONG blockDescriptorLength;
// Skip the mode select header and block descriptors.
if (Use6BytesCdb)
{
blockDescriptorLength = ((PMODE_PARAMETER_HEADER)ModeSenseBuffer)->BlockDescriptorLength;
}
else
{
modeParam10 = (PMODE_PARAMETER_HEADER10) ModeSenseBuffer;
blockDescriptorLength = modeParam10->BlockDescriptorLength[1];
}
ModeSenseBuffer += parameterHeaderLength + blockDescriptorLength;
// ModeSenseBuffer now points at pages. Walk the pages looking for the
// requested page until the limit is reached.
while (ModeSenseBuffer +
RTL_SIZEOF_THROUGH_FIELD(MODE_DISCONNECT_PAGE, PageLength) < limit)
{
if (((PMODE_DISCONNECT_PAGE) ModeSenseBuffer)->PageCode == PageMode)
{
// found the mode page. make sure it's safe to touch it all
// before returning the pointer to caller
if (ModeSenseBuffer + ((PMODE_DISCONNECT_PAGE)ModeSenseBuffer)->PageLength > limit)
{
// Return NULL since the page is not safe to access in full
result = NULL;
}
else
{
result = ModeSenseBuffer;
}
break;
}
// Advance to the next page which is 4-byte-aligned offset after this page.
ModeSenseBuffer += ((PMODE_DISCONNECT_PAGE) ModeSenseBuffer)->PageLength +
RTL_SIZEOF_THROUGH_FIELD(MODE_DISCONNECT_PAGE, PageLength);
}
}
return result;
} // end ModeSenseFindSpecificPage()
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS
PerformEjectionControl(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_In_ WDFREQUEST Request,
_In_ MEDIA_LOCK_TYPE LockType,
_In_ BOOLEAN Lock
)
/*++
Routine Description:
ejection control process
Arguments:
DeviceExtension - device extension
Request - WDF request to be used for communication with the device
LockType - the type of lock
Lock - if TRUE, lock the device; if FALSE, unlock it
Return Value:
NTSTATUS
--*/
{
NTSTATUS status;
PFILE_OBJECT_CONTEXT fileObjectContext = NULL;
SCSI_REQUEST_BLOCK srb;
PCDB cdb = NULL;
LONG newLockCount = 0;
LONG newProtectedLockCount = 0;
LONG newInternalLockCount = 0;
LONG newFileLockCount = 0;
BOOLEAN countChanged = FALSE;
BOOLEAN previouslyLocked = FALSE;
BOOLEAN nowLocked = FALSE;
PAGED_CODE();
// Prevent race conditions while working with lock counts
status = WdfWaitLockAcquire(DeviceExtension->EjectSynchronizationLock, NULL);
if (!NT_SUCCESS(status))
{
NT_ASSERT(FALSE);
}
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL,
"PerformEjectionControl: "
"Received request for %s lock type\n",
LockTypeStrings[LockType]
));
// If this is a "secured" request, retrieve the file object context
if (LockType == SecureMediaLock)
{
WDFFILEOBJECT fileObject = NULL;
fileObject = WdfRequestGetFileObject(Request);
if (fileObject == NULL)
{
status = STATUS_INVALID_HANDLE;
TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_IOCTL,
"FileObject does not match to the one in IRP_MJ_CREATE, KMDF returns NULL\n"));
goto Exit;
}
fileObjectContext = FileObjectGetContext(fileObject);
NT_ASSERT(fileObjectContext != NULL);
}
// Lock counts should never fall below 0
NT_ASSERT(DeviceExtension->LockCount >= 0);
NT_ASSERT(DeviceExtension->ProtectedLockCount >= 0);
NT_ASSERT(DeviceExtension->InternalLockCount >= 0);
// Get the current lock counts
newLockCount = DeviceExtension->LockCount;
newProtectedLockCount = DeviceExtension->ProtectedLockCount;
newInternalLockCount = DeviceExtension->InternalLockCount;
if (fileObjectContext)
{
// fileObjectContext->LockCount is ULONG and should always >= 0
newFileLockCount = fileObjectContext->LockCount;
}
// Determine which lock counts need to be changed and how
if (Lock && LockType == SimpleMediaLock)
{
newLockCount++;
countChanged = TRUE;
}
else if (Lock && LockType == SecureMediaLock)
{
newFileLockCount++;
newProtectedLockCount++;
countChanged = TRUE;
}
else if (Lock && LockType == InternalMediaLock)
{
newInternalLockCount++;
countChanged = TRUE;
}
else if (!Lock && LockType == SimpleMediaLock)
{
if (newLockCount != 0)
{
newLockCount--;
countChanged = TRUE;
}
}
else if (!Lock && LockType == SecureMediaLock)
{
if ( (newFileLockCount == 0) || (newProtectedLockCount == 0) )
{
status = STATUS_INVALID_DEVICE_STATE;
goto Exit;
}
newFileLockCount--;
newProtectedLockCount--;
countChanged = TRUE;
}
else if (!Lock && LockType == InternalMediaLock)
{
NT_ASSERT(newInternalLockCount != 0);
newInternalLockCount--;
countChanged = TRUE;
}
if ( (DeviceExtension->LockCount != 0) ||
(DeviceExtension->ProtectedLockCount != 0) ||
(DeviceExtension->InternalLockCount != 0) )
{
previouslyLocked = TRUE;
}
if ( (newLockCount != 0) ||
(newProtectedLockCount != 0) ||
(newInternalLockCount != 0) )
{
nowLocked = TRUE;
}
// Only send command down to device when necessary
if (previouslyLocked != nowLocked)
{
// Compose and send the PREVENT ALLOW MEDIA REMOVAL command.
RtlZeroMemory(&srb, sizeof(SCSI_REQUEST_BLOCK));
srb.CdbLength = 6;
srb.TimeOutValue = DeviceExtension->TimeOutValue;
cdb = (PCDB)&srb.Cdb;
cdb->MEDIA_REMOVAL.OperationCode = SCSIOP_MEDIUM_REMOVAL;
cdb->MEDIA_REMOVAL.Prevent = Lock;
status = DeviceSendSrbSynchronously(DeviceExtension->Device,
&srb,
NULL,
0,
FALSE,
Request);
}
Exit:
// Store the updated lock counts on success
if (countChanged && NT_SUCCESS(status))
{
DeviceExtension->LockCount = newLockCount;
DeviceExtension->ProtectedLockCount = newProtectedLockCount;
DeviceExtension->InternalLockCount = newInternalLockCount;
if (fileObjectContext)
{
fileObjectContext->LockCount = newFileLockCount;
}
}
WdfWaitLockRelease(DeviceExtension->EjectSynchronizationLock);
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL,
"PerformEjectionControl: %!STATUS!, "
"Count Changed: %d, Command Sent: %d, "
"Current Counts: Internal: %x Secure: %x Simple: %x\n",
status,
countChanged,
previouslyLocked != nowLocked,
DeviceExtension->InternalLockCount,
DeviceExtension->ProtectedLockCount,
DeviceExtension->LockCount
));
return status;
}
_IRQL_requires_max_(APC_LEVEL)
NTSTATUS
DeviceUnlockExclusive(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_In_ WDFFILEOBJECT FileObject,
_In_ BOOLEAN IgnorePreviousMediaChanges
)
/*++
Routine Description:
to unlock the exclusive lock
Arguments:
DeviceExtension - device context
FileObject - file object that currently holds the lock
IgnorePreviousMediaChanges - if TRUE, ignore previously accumulated media changes
Return Value:
NTSTATUS
--*/
{
NTSTATUS status = STATUS_SUCCESS;
PCDROM_DATA cdData = &DeviceExtension->DeviceAdditionalData;
PMEDIA_CHANGE_DETECTION_INFO info = DeviceExtension->MediaChangeDetectionInfo;
BOOLEAN ANPending = 0;
LONG requestInUse = 0;
PAGED_CODE();
if (!EXCLUSIVE_MODE(cdData))
{
// Device is not locked for exclusive access.
// Can not process unlock request.
TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_IOCTL,
"RequestHandleExclusiveAccessUnlockDevice: Device not locked for exclusive access, can't unlock device.\n"));
status = STATUS_INVALID_DEVICE_REQUEST;
}
else if (!EXCLUSIVE_OWNER(cdData, FileObject))
{
// Request not from the exclusive owner, can't unlock the device.
TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_IOCTL,
"RequestHandleExclusiveAccessUnlockDevice: Unable to unlock device, invalid file object\n"));
status = STATUS_INVALID_HANDLE;
}
if (NT_SUCCESS(status))
{
// Unless we were explicitly requested not to do so, generate a media removal notification
// followed by a media arrival notification similar to volume lock/unlock file system events.
if (!IgnorePreviousMediaChanges)
{
MEDIA_CHANGE_DETECTION_STATE previousMediaState = MediaUnknown;
// Change the media state to "unavailable", which will cause a removal notification if the media
// was previously present. At the same time, store the previous state in previousMediaState.
DeviceSetMediaChangeStateEx(DeviceExtension, MediaUnavailable, &previousMediaState);
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL,
"DeviceUnlockExclusive: Changing the media state to MediaUnavailable\n"));
// Restore the previous media state, which will cause a media arrival notification if the media
// was originally present.
DeviceSetMediaChangeStateEx(DeviceExtension, previousMediaState, NULL);
}
// Set DO_VERIFY_VOLUME so that the file system will remount on it.
if (IsVolumeMounted(DeviceExtension->DeviceObject))
{
SET_FLAG(DeviceExtension->DeviceObject->Flags, DO_VERIFY_VOLUME);
}
// Set MMC state to update required
cdData->Mmc.WriteAllowed = FALSE;
cdData->Mmc.UpdateState = CdromMmcUpdateRequired;
// Send unlock notification
DeviceSendNotification(DeviceExtension,
&GUID_IO_CDROM_EXCLUSIVE_UNLOCK,
0,
NULL);
InterlockedExchangePointer((PVOID)&cdData->ExclusiveOwner, NULL);
if ((info != NULL) && (info->AsynchronousNotificationSupported != FALSE))
{
ANPending = info->ANSignalPendingDueToExclusiveLock;
info->ANSignalPendingDueToExclusiveLock = FALSE;
if ((ANPending != FALSE) && (info->MediaChangeDetectionDisableCount == 0))
{
// if the request is not in use, mark it as such.
requestInUse = InterlockedCompareExchange((PLONG)&info->MediaChangeRequestInUse, 1, 0);
if (requestInUse == 0)
{
// The last MCN finished. ok to issue the new one.
RequestSetupMcnSyncIrp(DeviceExtension);
// The irp will go into KMDF framework and a request will be created there to represent it.
IoCallDriver(DeviceExtension->DeviceObject, info->MediaChangeSyncIrp);
}
}
}
}
return status;
}
VOID
RequestCompletion(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_In_ WDFREQUEST Request,
_In_ NTSTATUS Status,
_In_ ULONG_PTR Information
)
{
#ifdef DBG
ULONG ioctlCode = 0;
WDF_REQUEST_PARAMETERS requestParameters;
// Get the Request parameters
WDF_REQUEST_PARAMETERS_INIT(&requestParameters);
WdfRequestGetParameters(Request, &requestParameters);
if (requestParameters.Type == WdfRequestTypeDeviceControl)
{
ioctlCode = requestParameters.Parameters.DeviceIoControl.IoControlCode;
if (requestParameters.Parameters.DeviceIoControl.IoControlCode != IOCTL_MCN_SYNC_FAKE_IOCTL)
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,
"Request complete - IOCTL - code: %X; Status: %X; Information: %X\n",
ioctlCode,
Status,
(ULONG)Information));
}
else
{
TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_GENERAL,
"Request complete - IOCTL - code: %X; Status: %X; Information: %X\n",
ioctlCode,
Status,
(ULONG)Information));
}
}
else if (requestParameters.Type == WdfRequestTypeRead)
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,
"Request complete - READ - Starting Offset: %X; Length: %X; Transferred Length: %X; Status: %X\n",
(ULONG)requestParameters.Parameters.Read.DeviceOffset,
(ULONG)requestParameters.Parameters.Read.Length,
(ULONG)Information,
Status));
}
else if (requestParameters.Type == WdfRequestTypeWrite)
{
TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_GENERAL,
"Request complete - WRITE - Starting Offset: %X; Length: %X; Transferred Length: %X; Status: %X\n",
(ULONG)requestParameters.Parameters.Write.DeviceOffset,
(ULONG)requestParameters.Parameters.Write.Length,
(ULONG)Information,
Status));
}
#endif
if (IoIsErrorUserInduced(Status))
{
PIRP irp = WdfRequestWdmGetIrp(Request);
if (irp->Tail.Overlay.Thread)
{
IoSetHardErrorOrVerifyDevice(irp, DeviceExtension->DeviceObject);
}
}
if (!NT_SUCCESS(Status) && DeviceExtension->SurpriseRemoved == TRUE)
{
// IMAPI expects ERROR_DEV_NOT_EXISTS if recorder has been surprised removed,
// or it will retry WRITE commands for up to 3 minutes
// CDROM behavior should be consistent for all requests, including SCSI pass-through
Status = STATUS_DEVICE_DOES_NOT_EXIST;
}
WdfRequestCompleteWithInformation(Request, Status, Information);
return;
}
VOID
NTAPI /* ReactOS Change: GCC Does not support STDCALL by default */
RequestDummyCompletionRoutine(
_In_ WDFREQUEST Request,
_In_ WDFIOTARGET Target,
_In_ PWDF_REQUEST_COMPLETION_PARAMS Params,
_In_ WDFCONTEXT Context
)
/*++
Routine Description:
This is a dummy competion routine that simply calls WdfRequestComplete. We have to use
this dummy competion routine instead of WDF_REQUEST_SEND_OPTION_SEND_AND_FORGET, because
the latter causes the framework to not check if the I/O target is closed or not.
Arguments:
Request - completed request
Target - the I/O target that completed the request
Params - request parameters
Context - not used
Return Value:
none
--*/
{
UNREFERENCED_PARAMETER(Target);
UNREFERENCED_PARAMETER(Params);
UNREFERENCED_PARAMETER(Context);
WdfRequestCompleteWithInformation(Request,
WdfRequestGetStatus(Request),
WdfRequestGetInformation(Request));
}
_IRQL_requires_max_(DISPATCH_LEVEL)
NTSTATUS
DeviceSendPowerDownProcessRequest(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_In_opt_ PFN_WDF_REQUEST_COMPLETION_ROUTINE CompletionRoutine,
_In_opt_ PVOID Context
)
/*++
Routine Description:
This function is called during processing power down request.
It is used to send either SYNC CACHE command or STOP UNIT command.
Caller should set proper value in deviceExtension->PowerContext.PowerChangeState.PowerDown
to trigger the correct command be sent.
Arguments:
DeviceExtension -
CompletionRoutine - Completion routine that needs to be set for the request
Context - Completion context associated with the completion routine
Return Value:
NTSTATUS
--*/
{
NTSTATUS status;
BOOLEAN requestSent = FALSE;
BOOLEAN shouldRetry = TRUE;
PCDB cdb = (PCDB)DeviceExtension->PowerContext.Srb.Cdb;
ULONG timeoutValue = DeviceExtension->TimeOutValue;
ULONG retryCount = 1;
// reset some fields.
DeviceExtension->PowerContext.RetryIntervalIn100ns = 0;
status = PowerContextReuseRequest(DeviceExtension);
RequestClearSendTime(DeviceExtension->PowerContext.PowerRequest);
if (!NT_SUCCESS(status))
{
return status;
}
// set proper timeout value and max retry count.
switch(DeviceExtension->PowerContext.PowerChangeState.PowerDown)
{
case PowerDownDeviceInitial:
case PowerDownDeviceQuiesced:
case PowerDownDeviceStopped:
break;
case PowerDownDeviceLocked:
// Case of issuing SYNC CACHE command. Do not use power irp timeout remaining time in this case
// as we want to give best try on SYNC CACHE command.
retryCount = MAXIMUM_RETRIES;
timeoutValue = DeviceExtension->TimeOutValue;
break;
case PowerDownDeviceFlushed:
{
// Case of issuing STOP UNIT command
// As "Imme" bit is set to '1', this command should be completed in short time.
// This command is at low importance, failure of this command has very small impact.
ULONG secondsRemaining = 0;
#if (WINVER >= 0x0601)
// this API is introduced in Windows7
PoQueryWatchdogTime(DeviceExtension->LowerPdo, &secondsRemaining);
#endif
if (secondsRemaining == 0)
{
// not able to retrieve remaining time from PoQueryWatchdogTime API, use default values.
retryCount = MAXIMUM_RETRIES;
timeoutValue = SCSI_CDROM_TIMEOUT;
}
else
{
// plan to leave about 30 seconds to lower level drivers if possible.
if (secondsRemaining >= 32)
{
retryCount = (secondsRemaining - 30)/SCSI_CDROM_TIMEOUT + 1;
timeoutValue = SCSI_CDROM_TIMEOUT;
if (retryCount > MAXIMUM_RETRIES)
{
retryCount = MAXIMUM_RETRIES;
}
if (retryCount == 1)
{
timeoutValue = secondsRemaining - 30;
}
}
else
{
// issue the command with minimal timeout value and do not retry on it.
retryCount = 1;
timeoutValue = 2;
}
}
}
break;
default:
NT_ASSERT( FALSE );
status = STATUS_NOT_IMPLEMENTED;
return status;
}
DeviceExtension->PowerContext.RetryCount = retryCount;
// issue command.
while (shouldRetry)
{
// set SRB fields.
DeviceExtension->PowerContext.Srb.SrbFlags = SRB_FLAGS_NO_DATA_TRANSFER |
SRB_FLAGS_DISABLE_SYNCH_TRANSFER |
SRB_FLAGS_NO_QUEUE_FREEZE |
SRB_FLAGS_BYPASS_LOCKED_QUEUE |
SRB_FLAGS_D3_PROCESSING;
DeviceExtension->PowerContext.Srb.Function = SRB_FUNCTION_EXECUTE_SCSI;
DeviceExtension->PowerContext.Srb.TimeOutValue = timeoutValue;
if (DeviceExtension->PowerContext.PowerChangeState.PowerDown == PowerDownDeviceInitial)
{
DeviceExtension->PowerContext.Srb.Function = SRB_FUNCTION_LOCK_QUEUE;
}
else if (DeviceExtension->PowerContext.PowerChangeState.PowerDown == PowerDownDeviceLocked)
{
DeviceExtension->PowerContext.Srb.Function = SRB_FUNCTION_QUIESCE_DEVICE;
}
else if (DeviceExtension->PowerContext.PowerChangeState.PowerDown == PowerDownDeviceQuiesced)
{
// Case of issuing SYNC CACHE command.
DeviceExtension->PowerContext.Srb.CdbLength = 10;
cdb->SYNCHRONIZE_CACHE10.OperationCode = SCSIOP_SYNCHRONIZE_CACHE;
}
else if (DeviceExtension->PowerContext.PowerChangeState.PowerDown == PowerDownDeviceFlushed)
{
// Case of issuing STOP UNIT command.
DeviceExtension->PowerContext.Srb.CdbLength = 6;
cdb->START_STOP.OperationCode = SCSIOP_START_STOP_UNIT;
cdb->START_STOP.Start = 0;
cdb->START_STOP.Immediate = 1;
}
else if (DeviceExtension->PowerContext.PowerChangeState.PowerDown == PowerDownDeviceStopped)
{
DeviceExtension->PowerContext.Srb.Function = SRB_FUNCTION_UNLOCK_QUEUE;
}
// Set up completion routine and context if requested
if (CompletionRoutine)
{
WdfRequestSetCompletionRoutine(DeviceExtension->PowerContext.PowerRequest,
CompletionRoutine,
Context);
}
status = RequestSend(DeviceExtension,
DeviceExtension->PowerContext.PowerRequest,
DeviceExtension->IoTarget,
CompletionRoutine ? 0 : WDF_REQUEST_SEND_OPTION_SYNCHRONOUS,
&requestSent);
if (requestSent)
{
if ((CompletionRoutine == NULL) &&
(SRB_STATUS(DeviceExtension->PowerContext.Srb.SrbStatus) != SRB_STATUS_SUCCESS))
{
TracePrint((TRACE_LEVEL_ERROR,
TRACE_FLAG_POWER,
"%p\tError occured when issuing %s command to device. Srb %p, Status %x\n",
DeviceExtension->PowerContext.PowerRequest,
(DeviceExtension->PowerContext.PowerChangeState.PowerDown == PowerDownDeviceQuiesced) ? "SYNC CACHE" : "STOP UNIT",
&DeviceExtension->PowerContext.Srb,
DeviceExtension->PowerContext.Srb.SrbStatus));
NT_ASSERT(!(TEST_FLAG(DeviceExtension->PowerContext.Srb.SrbStatus, SRB_STATUS_QUEUE_FROZEN)));
shouldRetry = RequestSenseInfoInterpret(DeviceExtension,
DeviceExtension->PowerContext.PowerRequest,
&(DeviceExtension->PowerContext.Srb),
retryCount - DeviceExtension->PowerContext.RetryCount,
&status,
&(DeviceExtension->PowerContext.RetryIntervalIn100ns));
if (shouldRetry && (DeviceExtension->PowerContext.RetryCount-- == 0))
{
shouldRetry = FALSE;
}
}
else
{
// succeeded, do not need to retry.
shouldRetry = FALSE;
}
}
else
{
// request failed to be sent
shouldRetry = FALSE;
}
if (shouldRetry)
{
LARGE_INTEGER t;
t.QuadPart = -DeviceExtension->PowerContext.RetryIntervalIn100ns;
KeDelayExecutionThread(KernelMode, FALSE, &t);
status = PowerContextReuseRequest(DeviceExtension);
if (!NT_SUCCESS(status))
{
shouldRetry = FALSE;
}
}
}
if (DeviceExtension->PowerContext.PowerChangeState.PowerDown == PowerDownDeviceQuiesced)
{
// record SYNC CACHE command completion time stamp.
KeQueryTickCount(&DeviceExtension->PowerContext.Step1CompleteTime);
}
return status;
}
NTSTATUS
RequestSend(
_In_ PCDROM_DEVICE_EXTENSION DeviceExtension,
_In_ WDFREQUEST Request,
_In_ WDFIOTARGET IoTarget,
_In_ ULONG Flags,
_Out_opt_ PBOOLEAN RequestSent
)
/*++
Routine Description:
Send the request to the target, wake up the device from Zero Power state if necessary.
Arguments:
DeviceExtension - device extension
Request - the request to be sent
IoTarget - target of the above request
Flags - flags for the operation
RequestSent - optional, if the request was sent
Return Value:
NTSTATUS
--*/
{
NTSTATUS status = STATUS_SUCCESS;
BOOLEAN requestSent = FALSE;
WDF_REQUEST_SEND_OPTIONS options;
UNREFERENCED_PARAMETER(DeviceExtension);
if ((DeviceExtension->ZeroPowerODDInfo != NULL) &&
(DeviceExtension->ZeroPowerODDInfo->InZeroPowerState != FALSE))
{
}
// Now send down the request
if (NT_SUCCESS(status))
{
WDF_REQUEST_SEND_OPTIONS_INIT(&options, Flags);
RequestSetSentTime(Request);
// send request and check status
// Disable SDV warning about infinitely waiting in caller's context:
// 1. Some requests (such as SCSI_PASS_THROUGH, contains buffer from user space) need to be sent down in caller<65>s context.
// Consequently, these requests wait in caller<65>s context until they are allowed to be sent down.
// 2. Considering the situation that during sleep, a request can be hold by storage port driver. When system resumes, any time out value (if we set using KMDF time out value) might be expires.
// This will cause the waiting request being failed (behavior change). We<57>d rather not set time out value.
_Analysis_assume_(options.Timeout != 0);
requestSent = WdfRequestSend(Request, IoTarget, &options);
_Analysis_assume_(options.Timeout == 0);
// If WdfRequestSend fails, or if the WDF_REQUEST_SEND_OPTION_SYNCHRONOUS flag is set,
// the driver can call WdfRequestGetStatus immediately after calling WdfRequestSend.
if ((requestSent == FALSE) ||
(Flags & WDF_REQUEST_SEND_OPTION_SYNCHRONOUS))
{
status = WdfRequestGetStatus(Request);
if (requestSent == FALSE)
{
TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_GENERAL,
"WdfRequestSend failed: %lx\n",
status
));
}
}
else
{
status = STATUS_SUCCESS;
}
if (RequestSent != NULL)
{
*RequestSent = requestSent;
}
}
return status;
}