diff options
| author | Dave Wilson <[email protected]> | 2015-04-29 09:48:49 -0700 |
|---|---|---|
| committer | Dave Wilson <[email protected]> | 2015-04-29 10:47:29 -0700 |
| commit | d40232638faaab19de557e70a3ee938da7a35295 (patch) | |
| tree | 14e61c973745949af186b7940e3a53d25322f5de /storage/class/cdrom/src/cdrom.c | |
| parent | 02e5b090a6131a7cc1b90f4a9373117c9e3c7088 (diff) | |
samples update for //build
Diffstat (limited to 'storage/class/cdrom/src/cdrom.c')
| -rw-r--r-- | storage/class/cdrom/src/cdrom.c | 4225 |
1 files changed, 4225 insertions, 0 deletions
diff --git a/storage/class/cdrom/src/cdrom.c b/storage/class/cdrom/src/cdrom.c new file mode 100644 index 00000000..a93646ba --- /dev/null +++ b/storage/class/cdrom/src/cdrom.c @@ -0,0 +1,4225 @@ +/*-- + +Copyright (C) Microsoft Corporation. All rights reserved. + +Module Name: + + cdrom.c + +Abstract: + + The CDROM class driver tranlates IRPs to SRBs with embedded CDBs + and sends them to its devices through the port driver. + +Environment: + + kernel mode only + +Notes: + + +Revision History: + +--*/ + +// this definition is used to link _StorDebugPrint() function. +#define DEBUG_MAIN_SOURCE 1 + + +#include "ntddk.h" +#include "ntstrsafe.h" + +#include "ntddstor.h" +#include "ntddtape.h" +#include "wdfcore.h" +#include "devpkey.h" + +#include "cdrom.h" +#include "ioctl.h" +#include "mmc.h" +#include "scratch.h" + + +#ifdef DEBUG_USE_WPP +#include "cdrom.tmh" +#endif + +BOOLEAN +BootEnvironmentIsWinPE( + VOID + ); + +#ifdef ALLOC_PRAGMA + +#pragma alloc_text(INIT, DriverEntry) +#pragma alloc_text(INIT, BootEnvironmentIsWinPE) + +#pragma alloc_text(PAGE, DriverEvtCleanup) +#pragma alloc_text(PAGE, DriverEvtDeviceAdd) +#pragma alloc_text(PAGE, DeviceEvtCleanup) +#pragma alloc_text(PAGE, DeviceEvtSelfManagedIoCleanup) +#pragma alloc_text(PAGE, DeviceEvtD0Exit) +#pragma alloc_text(PAGE, CreateQueueEvtIoDefault) +#pragma alloc_text(PAGE, DeviceEvtFileClose) +#pragma alloc_text(PAGE, DeviceCleanupProtectedLocks) +#pragma alloc_text(PAGE, DeviceCleanupDisableMcn) +#pragma alloc_text(PAGE, RequestProcessSerializedIoctl) +#pragma alloc_text(PAGE, ReadWriteWorkItemRoutine) +#pragma alloc_text(PAGE, IoctlWorkItemRoutine) +#pragma alloc_text(PAGE, DeviceEvtSurpriseRemoval) + +#endif + +NTSTATUS +DriverEntry( + _In_ PDRIVER_OBJECT DriverObject, + _In_ PUNICODE_STRING RegistryPath + ) +/*++ + +Routine Description: + + Installable driver initialization entry point. + This entry point is called directly by the I/O system. + +Arguments: + + DriverObject - pointer to the driver object + + RegistryPath - pointer to a unicode string representing the path, + to driver-specific key in the registry. + +Return Value: + + STATUS_SUCCESS if successful, + STATUS_UNSUCCESSFUL otherwise. + +--*/ +{ + NTSTATUS status; + WDF_DRIVER_CONFIG config; + WDF_OBJECT_ATTRIBUTES attributes; + WDFDRIVER driverObject = NULL; + + PAGED_CODE(); + + // Initialize WPP Tracing + WPP_INIT_TRACING(DriverObject, RegistryPath); + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, + "CDROM.SYS DriverObject %p loading\n", + DriverObject)); + + // Register DeviceAdd and DriverEvtCleanup callback. + // WPP_CLEANUP will be called in DriverEvtCleanup + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CDROM_DRIVER_EXTENSION); + attributes.EvtCleanupCallback = DriverEvtCleanup; + + WDF_DRIVER_CONFIG_INIT(&config, DriverEvtDeviceAdd); + + status = WdfDriverCreate(DriverObject, + RegistryPath, + &attributes, + &config, + &driverObject); + + if (!NT_SUCCESS(status)) + { + TracePrint((TRACE_LEVEL_FATAL, TRACE_FLAG_INIT, + "WdfDriverCreate failed. %x\n", + status)); + + // Cleanup tracing here because DriverUnload will not be called + // as we have failed to create WDFDRIVER object itself. + WPP_CLEANUP(DriverObject); + + } + else + { + PCDROM_DRIVER_EXTENSION driverExtension = DriverGetExtension(driverObject); + + // Copy the registry path into the driver extension so we can use it later + driverExtension->Version = 0x01; + driverExtension->DriverObject = DriverObject; + + if (BootEnvironmentIsWinPE()) { + + SET_FLAG(driverExtension->Flags, CDROM_FLAG_WINPE_MODE); + } + + } + + return status; +} + + +BOOLEAN +BootEnvironmentIsWinPE( + VOID + ) +/*++ + +Routine Description: + + This routine determines if the boot enviroment is WinPE + +Arguments: + + None + +Return Value: + + BOOLEAN - TRUE if the environment is WinPE; FALSE otherwise + +--*/ +{ + NTSTATUS status; + WDFKEY registryKey = NULL; + + DECLARE_CONST_UNICODE_STRING(registryKeyName, WINPE_REG_KEY_NAME); + + PAGED_CODE(); + + status = WdfRegistryOpenKey(NULL, + ®istryKeyName, + KEY_READ, + WDF_NO_OBJECT_ATTRIBUTES, + ®istryKey); + + if (!NT_SUCCESS(status)) + { + return FALSE; + } + + WdfRegistryClose(registryKey); + return TRUE; +} // end BootEnvironmentIsWinPE() + + +VOID +DriverEvtCleanup( + _In_ WDFOBJECT Driver + ) +/*++ +Routine Description: + + Free all the resources allocated in DriverEntry. + +Arguments: + + Driver - handle to a WDF Driver object. + +Return Value: + + VOID. + +--*/ +{ + WDFDRIVER driver = (WDFDRIVER)Driver; + + PAGED_CODE (); + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, + "CDROM.SYS DriverObject %p cleanup. Will be unloaded soon\n", + driver)); + + // Stop WPP Tracing + WPP_CLEANUP( WdfDriverWdmGetDriverObject(driver) ); + + + return; +} + + +NTSTATUS +DriverEvtDeviceAdd( + _In_ WDFDRIVER Driver, + _Inout_ PWDFDEVICE_INIT DeviceInit + ) +/*++ + +Routine Description: + + EvtDeviceAdd is called by the framework in response to AddDevice + call from the PnP manager. + + +Arguments: + + Driver - Handle to a framework driver object created in DriverEntry + + DeviceInit - Pointer to a framework-allocated WDFDEVICE_INIT structure. + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + PCDROM_DRIVER_EXTENSION driverExtension = NULL; + WDFDEVICE device = NULL; + PCDROM_DEVICE_EXTENSION deviceExtension = NULL; + BOOLEAN deviceClaimed = FALSE; + + WDF_OBJECT_ATTRIBUTES attributes; + WDF_FILEOBJECT_CONFIG fileObjectConfig; + WDF_IO_TARGET_OPEN_PARAMS ioTargetOpenParams; + WDF_IO_QUEUE_CONFIG queueConfig; + WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks; + WDF_REMOVE_LOCK_OPTIONS removeLockOptions; + PWCHAR wideDeviceName = NULL; + UNICODE_STRING unicodeDeviceName; + PDEVICE_OBJECT lowerPdo = NULL; + ULONG deviceNumber = 0; + ULONG devicePropertySessionId = INVALID_SESSION; + ULONG devicePropertySize = 0; + DEVPROPTYPE devicePropertyType = DEVPROP_TYPE_EMPTY; + + PAGED_CODE(); + + driverExtension = DriverGetExtension(Driver); + + // 0. Initialize the objects that we're going to use + RtlInitUnicodeString(&unicodeDeviceName, NULL); + + // 1. Register PnP&Power callbacks for any we are interested in. + // If a callback isn't set, Framework will take the default action by itself. + { + // Zero out the PnpPowerCallbacks structure. + WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks); + + // Use this callback to init resources that are used by the device and only needs to be called once. + pnpPowerCallbacks.EvtDeviceSelfManagedIoInit = DeviceEvtSelfManagedIoInit; + + // Use this callback to prepare device for coming back from a lower power mode to D0. + pnpPowerCallbacks.EvtDeviceD0Entry = DeviceEvtD0Entry; + + // Use this callback to prepare device for entering into a lower power mode. + pnpPowerCallbacks.EvtDeviceD0Exit = DeviceEvtD0Exit; + + // Use this callback to free any resources used by device and will be called when the device is + // powered down. + pnpPowerCallbacks.EvtDeviceSelfManagedIoCleanup = DeviceEvtSelfManagedIoCleanup; + + pnpPowerCallbacks.EvtDeviceSurpriseRemoval = DeviceEvtSurpriseRemoval; + + // Register the PnP and power callbacks. + WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks); + } + + // 2. Register the EvtIoInCallerContext to deal with IOCTLs that need to stay in original context. + WdfDeviceInitSetIoInCallerContextCallback(DeviceInit, + DeviceEvtIoInCallerContext); + + // 3. Register PreprocessCallback for IRP_MJ_POWER, IRP_MJ_FLUSH_BUFFERS and IRP_MJ_SHUTDOWN + { + UCHAR minorFunctions[1]; + + minorFunctions[0] = IRP_MN_SET_POWER; + + status = WdfDeviceInitAssignWdmIrpPreprocessCallback(DeviceInit, + RequestProcessSetPower, + IRP_MJ_POWER, + minorFunctions, + RTL_NUMBER_OF(minorFunctions)); + if (!NT_SUCCESS(status)) + { + TracePrint((TRACE_LEVEL_FATAL, TRACE_FLAG_PNP, + "DriverEvtDeviceAdd: Assign IrpPreprocessCallback for IRP_MJ_POWER failed, " + "status: 0x%X\n", status)); + + goto Exit; + } + + status = WdfDeviceInitAssignWdmIrpPreprocessCallback(DeviceInit, + RequestProcessShutdownFlush, + IRP_MJ_FLUSH_BUFFERS, + NULL, + 0); + if (!NT_SUCCESS(status)) + { + TracePrint((TRACE_LEVEL_FATAL, TRACE_FLAG_PNP, + "DriverEvtDeviceAdd: Assign IrpPreprocessCallback for IRP_MJ_FLUSH_BUFFERS failed, " + "status: 0x%X\n", status)); + + goto Exit; + } + + status = WdfDeviceInitAssignWdmIrpPreprocessCallback(DeviceInit, + RequestProcessShutdownFlush, + IRP_MJ_SHUTDOWN, + NULL, + 0); + if (!NT_SUCCESS(status)) + { + TracePrint((TRACE_LEVEL_FATAL, TRACE_FLAG_PNP, + "DriverEvtDeviceAdd: Assign IrpPreprocessCallback for IRP_MJ_SHUTDOWN failed, " + "status: 0x%X\n", status)); + + goto Exit; + } + } + + // 4. Set attributes to create Request Context area. + { + //Reuse this structure. + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, + CDROM_REQUEST_CONTEXT); + attributes.EvtCleanupCallback = RequestEvtCleanup; + + WdfDeviceInitSetRequestAttributes(DeviceInit, + &attributes); + } + + // 5. Register FileObject related callbacks + { + // Add FILE_OBJECT_EXTENSION as the context to the file object. + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, FILE_OBJECT_CONTEXT); + + // Set Entry points for Create and Close.. + + // The framework doesn't sync the file create requests with pnp/power + // state. Re-direct all the file create requests to a dedicated + // queue, which will be purged manually during removal. + WDF_FILEOBJECT_CONFIG_INIT(&fileObjectConfig, + NULL, //CreateQueueEvtIoDefault, + DeviceEvtFileClose, + WDF_NO_EVENT_CALLBACK); // No callback for Cleanup + + fileObjectConfig.FileObjectClass = WdfFileObjectWdfCannotUseFsContexts; + + // Since we are registering file events and fowarding create request + // ourself, we must also set AutoForwardCleanupClose so that cleanup + // and close can also get forwarded. + fileObjectConfig.AutoForwardCleanupClose = WdfTrue; + attributes.SynchronizationScope = WdfSynchronizationScopeNone; + attributes.ExecutionLevel = WdfExecutionLevelPassive; + + // Indicate that file object is optional. + fileObjectConfig.FileObjectClass |= WdfFileObjectCanBeOptional; + + WdfDeviceInitSetFileObjectConfig(DeviceInit, + &fileObjectConfig, + &attributes); + } + + // 6. Initialize device-wide attributes + { + // Declare ourselves as NOT owning power policy. + // The power policy owner in storage stack is port driver. + WdfDeviceInitSetPowerPolicyOwnership(DeviceInit, FALSE); + + // Set other DeviceInit attributes. + WdfDeviceInitSetExclusive(DeviceInit, FALSE); + WdfDeviceInitSetDeviceType(DeviceInit, FILE_DEVICE_CD_ROM); + WdfDeviceInitSetCharacteristics(DeviceInit, FILE_REMOVABLE_MEDIA, FALSE); + WdfDeviceInitSetIoType(DeviceInit, WdfDeviceIoDirect); + WdfDeviceInitSetPowerPageable(DeviceInit); + + // We require the framework to acquire a remove lock before delivering all IRP types + WDF_REMOVE_LOCK_OPTIONS_INIT(&removeLockOptions, + WDF_REMOVE_LOCK_OPTION_ACQUIRE_FOR_IO); + + WdfDeviceInitSetRemoveLockOptions(DeviceInit, &removeLockOptions); + + // save the PDO for later reference + lowerPdo = WdfFdoInitWdmGetPhysicalDevice(DeviceInit); + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, + CDROM_DEVICE_EXTENSION); + + // We have a parallel queue, so WdfSynchronizationScopeNone is our only choice. + attributes.SynchronizationScope = WdfSynchronizationScopeNone; + attributes.ExecutionLevel = WdfExecutionLevelDispatch; + + // Provide a cleanup callback which will release memory allocated with ExAllocatePool* + attributes.EvtCleanupCallback = DeviceEvtCleanup; + } + + // 7. Now, the device can be created. + { + wideDeviceName = ExAllocatePoolWithTag(NonPagedPoolNx, + 64 * sizeof(WCHAR), + CDROM_TAG_STRINGS); + + if (wideDeviceName == NULL) + { + status = STATUS_INSUFFICIENT_RESOURCES; + goto Exit; + } + + // Find the lowest device number currently available. + do { + status = RtlStringCchPrintfW((NTSTRSAFE_PWSTR)wideDeviceName, + 64, + L"\\Device\\CdRom%d", + deviceNumber); + + if (!NT_SUCCESS(status)) { + TracePrint((TRACE_LEVEL_FATAL, TRACE_FLAG_PNP, + "DriverEvtDeviceAdd: Format device name failed with error: 0x%X\n", status)); + + goto Exit; + } + + RtlInitUnicodeString(&unicodeDeviceName, wideDeviceName); + + status = WdfDeviceInitAssignName(DeviceInit, &unicodeDeviceName); + if (!NT_SUCCESS(status)) + { + TracePrint((TRACE_LEVEL_FATAL, TRACE_FLAG_PNP, + "DriverEvtDeviceAdd: WdfDeviceInitAssignName() failed with error: 0x%X\n", status)); + + goto Exit; + } + + status = WdfDeviceCreate(&DeviceInit, &attributes, &device); + + deviceNumber++; + + } while (status == STATUS_OBJECT_NAME_COLLISION); + + // When this loop exits the count is inflated by one - fix that. + deviceNumber--; + + if (!NT_SUCCESS(status)) + { + TracePrint((TRACE_LEVEL_FATAL, TRACE_FLAG_PNP, + "DriverEvtDeviceAdd: Can not create a new device, status: 0x%X\n", + status)); + + goto Exit; + } + } + + // 8. Fill up basic Device Extension settings and create a remote I/O target for the next-lower driver. + // The reason why we do not use the local I/O target is because we want to be able to close the + // I/O target on surprise removal. + { + deviceExtension = DeviceGetExtension(device); + + deviceExtension->Version = 0x01; + deviceExtension->Size = sizeof(CDROM_DEVICE_EXTENSION); + + deviceExtension->DeviceObject = WdfDeviceWdmGetDeviceObject(device); + deviceExtension->Device = device; + deviceExtension->DriverExtension = driverExtension; + + deviceExtension->LowerPdo = lowerPdo; + + deviceExtension->DeviceType = FILE_DEVICE_CD_ROM; //Always a FILE_DEVICE_CD_ROM for all device it manages. + deviceExtension->DeviceName = unicodeDeviceName; + + deviceExtension->DeviceNumber = deviceNumber; + } + { + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + attributes.ParentObject = deviceExtension->Device; + + status = WdfIoTargetCreate(deviceExtension->Device, + &attributes, + &deviceExtension->IoTarget); + + if (!NT_SUCCESS(status)) + { + TracePrint((TRACE_LEVEL_FATAL, TRACE_FLAG_PNP, + "DriverEvtDeviceAdd: Can not create a remote I/O target object, status: 0x%X\n", + status)); + goto Exit; + } + + WDF_IO_TARGET_OPEN_PARAMS_INIT_EXISTING_DEVICE(&ioTargetOpenParams, + WdfDeviceWdmGetAttachedDevice(deviceExtension->Device)); + + status = WdfIoTargetOpen(deviceExtension->IoTarget, + &ioTargetOpenParams); + if (!NT_SUCCESS(status)) + { + TracePrint((TRACE_LEVEL_FATAL, TRACE_FLAG_PNP, + "DriverEvtDeviceAdd: Can not open a remote I/O target for the next-lower device, status: 0x%X\n", + status)); + + WdfObjectDelete(deviceExtension->IoTarget); + deviceExtension->IoTarget = NULL; + + goto Exit; + } + } + + // 9. Claim the device, so that port driver will only accept the commands from CDROM.SYS for this device. + // NOTE: The I/O should be issued after the device is started. But we would like to claim + // the device as soon as possible, so this legacy behavior is kept. + status = DeviceClaimRelease(deviceExtension, FALSE); + + if (!NT_SUCCESS(status)) + { + // Someone already had this device - we're in trouble + goto Exit; + } + else + { + deviceClaimed = TRUE; + } + + // + // CDROM Queueing Structure + // + // a. EvtIoInCallerContext (prior to queueing): + // This event will be used ONLY to forward down IOCTLs that come in at PASSIVE LEVEL + // and need to be forwarded down the stack in the original context. + // + // b. Main input queue: serial queue for main dispatching + // This queue will be used to do all dispatching of requests to serialize + // access to the device. Any request that was previously completed in + // the Dispatch routines will be completed here. Anything requiring device + // I/O will be sent through the serial I/O queue. + // + // 10. Set up IO queues after device being created. + // + { + WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig, + WdfIoQueueDispatchSequential); + + queueConfig.PowerManaged = WdfFalse; + +#pragma prefast(push) +#pragma prefast(disable: 28155, "a joint handler for read/write cannot be EVT_WDF_IO_QUEUE_IO_READ and EVT_WDF_IO_QUEUE_IO_WRITE simultaneously") +#pragma prefast(disable: 28023, "a joint handler for read/write cannot be EVT_WDF_IO_QUEUE_IO_READ and EVT_WDF_IO_QUEUE_IO_WRITE simultaneously") + queueConfig.EvtIoRead = SequentialQueueEvtIoReadWrite; + queueConfig.EvtIoWrite = SequentialQueueEvtIoReadWrite; +#pragma prefast(pop) + + queueConfig.EvtIoDeviceControl = SequentialQueueEvtIoDeviceControl; + queueConfig.EvtIoCanceledOnQueue = SequentialQueueEvtCanceledOnQueue; + + status = WdfIoQueueCreate(device, + &queueConfig, + WDF_NO_OBJECT_ATTRIBUTES, + &(deviceExtension->SerialIOQueue)); + if (!NT_SUCCESS(status)) + { + goto Exit; + } + + // this queue is dedicated for file create requests. + WDF_IO_QUEUE_CONFIG_INIT(&queueConfig, WdfIoQueueDispatchParallel); + + queueConfig.PowerManaged = WdfFalse; + queueConfig.EvtIoDefault = CreateQueueEvtIoDefault; + + //Reuse this structure. + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, + CDROM_REQUEST_CONTEXT); + + attributes.SynchronizationScope = WdfSynchronizationScopeNone; + attributes.ExecutionLevel = WdfExecutionLevelPassive; + + status = WdfIoQueueCreate(device, + &queueConfig, + &attributes, + &(deviceExtension->CreateQueue)); + + if (!NT_SUCCESS(status)) + { + goto Exit; + } + + // Configure the device to use driver created queue for dispatching create. + status = WdfDeviceConfigureRequestDispatching(device, + deviceExtension->CreateQueue, + WdfRequestTypeCreate); + + if (!NT_SUCCESS(status)) + { + goto Exit; + } + } + + // 11. Set the alignment requirements for the device based on the host adapter requirements. + // + // NOTE: this should have been set when device is attached on device stack, + // by keeping this legacy code, we could avoid issue that this value was not correctly set at that time. + if (deviceExtension->LowerPdo->AlignmentRequirement > deviceExtension->DeviceObject->AlignmentRequirement) + { + WdfDeviceSetAlignmentRequirement(deviceExtension->Device, + deviceExtension->LowerPdo->AlignmentRequirement); + } + + // 12. Initialization of miscellaneous internal properties + + // CDROMs are not partitionable so starting offset is 0. + deviceExtension->StartingOffset.LowPart = 0; + deviceExtension->StartingOffset.HighPart = 0; + + // Set the default geometry for the cdrom to match what NT 4 used. + // these values will be used to compute the cylinder count rather + // than using it's NT 5.0 defaults. + deviceExtension->DiskGeometry.MediaType = RemovableMedia; + deviceExtension->DiskGeometry.TracksPerCylinder = 0x40; + deviceExtension->DiskGeometry.SectorsPerTrack = 0x20; + + deviceExtension->DeviceAdditionalData.ReadWriteRetryDelay100nsUnits = WRITE_RETRY_DELAY_DVD_1x; + + // Clear the SrbFlags and disable synchronous transfers + deviceExtension->SrbFlags = SRB_FLAGS_DISABLE_SYNCH_TRANSFER; + + // Set timeout value in seconds. + deviceExtension->TimeOutValue = DeviceGetTimeOutValueFromRegistry(); + if ((deviceExtension->TimeOutValue > 30 * 60) || // longer than 30 minutes + (deviceExtension->TimeOutValue == 0)) + { + deviceExtension->TimeOutValue = SCSI_CDROM_TIMEOUT; + } + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, + "DriverEvtDeviceAdd: device timeout is set to %x seconds", + deviceExtension->TimeOutValue + )); + +#if (NTDDI_VERSION >= NTDDI_WIN8) + deviceExtension->IsVolumeOnlinePending = TRUE; + + WDF_IO_QUEUE_CONFIG_INIT(&queueConfig, WdfIoQueueDispatchManual); + + queueConfig.PowerManaged = WdfFalse; + + status = WdfIoQueueCreate(device, + &queueConfig, + WDF_NO_OBJECT_ATTRIBUTES, + &deviceExtension->ManualVolumeReadyQueue); + + if (!NT_SUCCESS(status)) + { + goto Exit; + } +#endif + + // 13. Initialize the stuff related to media locking + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + attributes.ParentObject = deviceExtension->Device; + status = WdfWaitLockCreate(&attributes, + &deviceExtension->EjectSynchronizationLock); + + deviceExtension->LockCount = 0; + + // 14. Initialize context structures needed for asynchronous release queue and power requests + + if (NT_SUCCESS(status)) + { + status = DeviceInitReleaseQueueContext(deviceExtension); + } + + if (NT_SUCCESS(status)) + { + status = DeviceInitPowerContext(deviceExtension); + } + + // 15. Create external access points other than device name. + if (NT_SUCCESS(status)) + { + status = DeviceCreateWellKnownName(deviceExtension); + } + + // 16. Query session id from the PDO. + if (NT_SUCCESS(status)) + { + status = IoGetDevicePropertyData(deviceExtension->LowerPdo, + &DEVPKEY_Device_SessionId, + 0, + 0, + sizeof(devicePropertySessionId), + &devicePropertySessionId, + &devicePropertySize, + &devicePropertyType); + + if (!NT_SUCCESS(status)) + { + // The device is global. + devicePropertySessionId = INVALID_SESSION; + status = STATUS_SUCCESS; + } + } + + // 17. Register interfaces for this device. + if (NT_SUCCESS(status)) + { + status = DeviceRegisterInterface(deviceExtension, CdRomDeviceInterface); + } + + if (NT_SUCCESS(status)) + { + // If this is a per-session DO, don't register for mount interface so that + // mountmgr will not automatically assign a drive letter. + if (devicePropertySessionId == INVALID_SESSION) + { + status = DeviceRegisterInterface(deviceExtension, MountedDeviceInterface); + } + } + + // 18. Initialize the shutdown/flush lock + if (NT_SUCCESS(status)) + { + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + attributes.ParentObject = deviceExtension->Device; + + status = WdfWaitLockCreate(&attributes, &deviceExtension->ShutdownFlushWaitLock); + if (!NT_SUCCESS(status)) + { + TracePrint((TRACE_LEVEL_FATAL, TRACE_FLAG_PNP, + "DriverEvtDeviceAdd: Cannot create shutdown/flush waitlock, status: 0x%X\n", + status)); + } + } + + // 19. Initialize the work item that is used to initiate asynchronous reads/writes + if (NT_SUCCESS(status)) + { + WDF_WORKITEM_CONFIG workItemConfig; + WDF_OBJECT_ATTRIBUTES workItemAttributes; + + WDF_WORKITEM_CONFIG_INIT(&workItemConfig, + ReadWriteWorkItemRoutine + ); + + WDF_OBJECT_ATTRIBUTES_INIT(&workItemAttributes); + workItemAttributes.ParentObject = deviceExtension->Device; + + status = WdfWorkItemCreate(&workItemConfig, + &workItemAttributes, + &deviceExtension->ReadWriteWorkItem + ); + + if (!NT_SUCCESS(status)) + { + TracePrint((TRACE_LEVEL_FATAL, TRACE_FLAG_INIT, + "DriverEvtDeviceAdd: Cannot create read/write work item, status: 0x%X\n", + status)); + } + } + + // 20. Initialize the work item that is used to process most IOCTLs at PASSIVE_LEVEL. + if (NT_SUCCESS(status)) + { + WDF_WORKITEM_CONFIG workItemConfig; + WDF_OBJECT_ATTRIBUTES workItemAttributes; + + WDF_WORKITEM_CONFIG_INIT(&workItemConfig, + IoctlWorkItemRoutine + ); + + WDF_OBJECT_ATTRIBUTES_INIT(&workItemAttributes); + workItemAttributes.ParentObject = deviceExtension->Device; + + status = WdfWorkItemCreate(&workItemConfig, + &workItemAttributes, + &deviceExtension->IoctlWorkItem + ); + + if (!NT_SUCCESS(status)) + { + TracePrint((TRACE_LEVEL_FATAL, TRACE_FLAG_INIT, + "DriverEvtDeviceAdd: Cannot create ioctl work item, status: 0x%X\n", + status)); + } + } + + +Exit: + + if (!NT_SUCCESS(status)) + { + FREE_POOL(wideDeviceName); + + if (deviceExtension != NULL) + { + RtlInitUnicodeString(&deviceExtension->DeviceName, NULL); + } + + // Release the device with the port driver, if it was claimed + if ((deviceExtension != NULL) && deviceClaimed) + { + DeviceClaimRelease(deviceExtension, TRUE); + } + deviceClaimed = FALSE; + } + + return status; +} + + +VOID +DeviceEvtCleanup( + _In_ WDFOBJECT Device + ) +/*++ +Routine Description: + + Free all the resources allocated in DriverEvtDeviceAdd. + +Arguments: + + Device - handle to a WDF Device object. + +Return Value: + + VOID. + +--*/ +{ + WDFDEVICE device = (WDFDEVICE)Device; + PCDROM_DEVICE_EXTENSION deviceExtension = NULL; + + PAGED_CODE (); + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, + "WDFDEVICE %p cleanup: The device is about to be destroyed.\n", + device)); + + deviceExtension = DeviceGetExtension(device); + + FREE_POOL(deviceExtension->DeviceName.Buffer); + RtlInitUnicodeString(&deviceExtension->DeviceName, NULL); + + if (deviceExtension->DeviceAdditionalData.WellKnownName.Buffer != NULL) + { + IoDeleteSymbolicLink(&deviceExtension->DeviceAdditionalData.WellKnownName); + } + + FREE_POOL(deviceExtension->DeviceAdditionalData.WellKnownName.Buffer); + RtlInitUnicodeString(&deviceExtension->DeviceAdditionalData.WellKnownName, NULL); + + return; +} + + +VOID +DeviceEvtSelfManagedIoCleanup( + _In_ WDFDEVICE Device + ) +/*++ + +Routine Description: + + this function is called when the device is removed. + release the ownership of the device, release allocated resources. + +Arguments: + + Device - Handle to device object + +Return Value: + + None. + +--*/ +{ + NTSTATUS status; + PCDROM_DEVICE_EXTENSION deviceExtension = NULL; + + PAGED_CODE (); + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_PNP, + "DeviceEvtSelfManagedIoCleanup: WDFDEVICE %p is being stopped.\n", + Device)); + + // extract the device and driver extensions + deviceExtension = DeviceGetExtension(Device); + + // Purge unprocessed requests, stop the IO queues. + // Incoming request will be completed with STATUS_INVALID_DEVICE_STATE status. + WdfIoQueuePurge(deviceExtension->SerialIOQueue, WDF_NO_EVENT_CALLBACK, WDF_NO_CONTEXT); + WdfIoQueuePurge(deviceExtension->CreateQueue, WDF_NO_EVENT_CALLBACK, WDF_NO_CONTEXT); + + // Close the IoTarget so that we are sure there are no outstanding I/Os in the stack. + if (deviceExtension->IoTarget) + { + WdfIoTargetClose(deviceExtension->IoTarget); + } + + // Release the device + if (!deviceExtension->SurpriseRemoved) + { + status = DeviceClaimRelease(deviceExtension, TRUE); //status is mainly for debugging. we don't really care. + UNREFERENCED_PARAMETER(status); //defensive coding, avoid PREFAST warning. + } + + // Be sure to flush the DPCs as the READ/WRITE timer routine may still be running + // during device removal. This call may take a while to complete. + KeFlushQueuedDpcs(); + + // Release all the memory that we have allocated. + + DeviceDeallocateMmcResources(Device); + ScratchBuffer_Deallocate(deviceExtension); + RtlZeroMemory(&(deviceExtension->DeviceAdditionalData.Mmc), sizeof(CDROM_MMC_EXTENSION)); + + FREE_POOL(deviceExtension->DeviceDescriptor); + FREE_POOL(deviceExtension->AdapterDescriptor); + FREE_POOL(deviceExtension->PowerDescriptor); + FREE_POOL(deviceExtension->SenseData); + + if (deviceExtension->DeviceAdditionalData.CachedInquiryData != NULL) + { + FREE_POOL(deviceExtension->DeviceAdditionalData.CachedInquiryData); + deviceExtension->DeviceAdditionalData.CachedInquiryDataByteCount = 0; + } + + FREE_POOL(deviceExtension->PrivateFdoData); + + DeviceReleaseMcnResources(deviceExtension); + + DeviceReleaseZPODDResources(deviceExtension); + + // Keep the system-wide CDROM count accurate, as programs use this info to + // know when they have found all the cdroms in a system. + IoGetConfigurationInformation()->CdRomCount--; + + deviceExtension->PartitionLength.QuadPart = 0; + + // All WDF objects related to Device will be automatically released + // when the root object is deleted. No need to release them manually. + + return; +} + +NTSTATUS +DeviceEvtD0Entry( + _In_ WDFDEVICE Device, + _In_ WDF_POWER_DEVICE_STATE PreviousState + ) +/*++ + +Routine Description: + + This function is called when the device is coming back from a lower power state to D0. + This function cannot be placed in a pageable section. + +Arguments: + + Device - Handle to device object + PreviousState - Power state the device was in. + +Return Value: + + NTSTATUS: alway STATUS_SUCCESS + +--*/ +{ + PCDROM_DEVICE_EXTENSION deviceExtension; + NTSTATUS status = STATUS_SUCCESS; + PZERO_POWER_ODD_INFO zpoddInfo = NULL; + STORAGE_IDLE_POWERUP_REASON powerupReason = {0}; + + UNREFERENCED_PARAMETER(PreviousState); + deviceExtension = DeviceGetExtension(Device); + + // Make certain not to do anything before properly initialized + if (deviceExtension->IsInitialized) + { + zpoddInfo = deviceExtension->ZeroPowerODDInfo; + + if (zpoddInfo != NULL) + { + if (zpoddInfo->InZeroPowerState != FALSE) + { + // We just woke up from Zero Power state + zpoddInfo->InZeroPowerState = FALSE; + zpoddInfo->RetryFirstCommand = TRUE; + zpoddInfo->BecomingReadyRetryCount = BECOMING_READY_RETRY_COUNT; + + status = DeviceZPODDGetPowerupReason(deviceExtension, &powerupReason); + + if (NT_SUCCESS(status) && + (powerupReason.PowerupReason == StoragePowerupDeviceAttention)) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, + "DeviceEvtD0Entry: Device has left zero power state due to eject button pressed\n" + )); + + // This wake-up is caused by user pressing the eject button. + // In case of drawer type, we need to soft eject the tray to emulate the effect. + // Note that the first command to the device after power resumed will + // be terminated with CHECK CONDITION status with sense code 6/29/00, + // but we already have a retry logic to handle this. + if ((zpoddInfo->LoadingMechanism == LOADING_MECHANISM_TRAY) && (zpoddInfo->Load == 0)) // Drawer + { + DeviceSendIoctlAsynchronously(deviceExtension, IOCTL_STORAGE_EJECT_MEDIA, deviceExtension->DeviceObject); + } + } + else + { + // This wake-up is caused by non-cached CDB received or a 3rd-party driver + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, + "DeviceEvtD0Entry: Device has left zero power state due to IO received\n" + )); + + } + } + } + + DeviceEnableMainTimer(deviceExtension); + } + + return STATUS_SUCCESS; +} + +NTSTATUS +DeviceEvtD0Exit( + _In_ WDFDEVICE Device, + _In_ WDF_POWER_DEVICE_STATE TargetState + ) +/*++ + +Routine Description: + + This function is called when the device is entering lower powe state from D0 or it's removed. + We only care about the case of device entering D3. + The purpose of this function is to send SYNC CACHE command and STOP UNIT command if it's necessary. + +Arguments: + + Device - Handle to device object + TargetState - Power state the device is entering. + +Return Value: + + NTSTATUS: alway STATUS_SUCCESS + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + PCDROM_DEVICE_EXTENSION deviceExtension = NULL; + PZERO_POWER_ODD_INFO zpoddInfo = NULL; + + PAGED_CODE (); + + deviceExtension = DeviceGetExtension(Device); + zpoddInfo = deviceExtension->ZeroPowerODDInfo; + + // we only process the situation that the device is going into D3. + if ((TargetState != WdfPowerDeviceD3) && + (TargetState != WdfPowerDeviceD3Final)) + { + return STATUS_SUCCESS; + } + + // Stop the main timer + DeviceDisableMainTimer(deviceExtension); + + // note: do not stop CreateQueue as the create request can be handled by port driver even the device is in D3 status. + + // If initialization was not finished or the device was removed, we cannot interact + // with it device, so we have to exit + if ((!deviceExtension->IsInitialized) || deviceExtension->SurpriseRemoved) + { + return STATUS_SUCCESS; + } + + +#ifdef DBG + #if (WINVER >= 0x0601) + // this API is introduced in Windows7 + { + ULONG secondsRemaining = 0; + BOOLEAN watchdogTimeSupported = FALSE; + + watchdogTimeSupported = PoQueryWatchdogTime(deviceExtension->LowerPdo, &secondsRemaining); + UNREFERENCED_PARAMETER(watchdogTimeSupported); + } + #endif +#endif + + deviceExtension->PowerDownInProgress = TRUE; + + status = PowerContextBeginUse(deviceExtension); + + deviceExtension->PowerContext.Options.PowerDown = TRUE; + + // Step 1. LOCK QUEUE + if (NT_SUCCESS(status) && + (TargetState != WdfPowerDeviceD3Final)) + { + status = DeviceSendPowerDownProcessRequest(deviceExtension, NULL, NULL); + + if (NT_SUCCESS(status)) + { + deviceExtension->PowerContext.Options.LockQueue = TRUE; + } + + // Ignore failure. + status = STATUS_SUCCESS; + } + + deviceExtension->PowerContext.PowerChangeState.PowerDown++; + + // Step 2. QUIESCE QUEUE + if (NT_SUCCESS(status) && + (TargetState != WdfPowerDeviceD3Final)) + { + status = DeviceSendPowerDownProcessRequest(deviceExtension, NULL, NULL); + UNREFERENCED_PARAMETER(status); + // We don't care about the status. + status = STATUS_SUCCESS; + } + + deviceExtension->PowerContext.PowerChangeState.PowerDown++; + + // Step 3. SYNC CACHE command should be sent to drive if the media is currently writable. + if (NT_SUCCESS(status) && + deviceExtension->DeviceAdditionalData.Mmc.WriteAllowed) + { + status = DeviceSendPowerDownProcessRequest(deviceExtension, NULL, NULL); + UNREFERENCED_PARAMETER(status); + status = STATUS_SUCCESS; + } + + deviceExtension->PowerContext.PowerChangeState.PowerDown++; + + // Step 4. STOP UNIT + if (NT_SUCCESS(status) && + !TEST_FLAG(deviceExtension->ScanForSpecialFlags, CDROM_SPECIAL_DISABLE_SPIN_DOWN)) + { + status = DeviceSendPowerDownProcessRequest(deviceExtension, NULL, NULL); + UNREFERENCED_PARAMETER(status); + status = STATUS_SUCCESS; + } + + if (TargetState == WdfPowerDeviceD3Final) + { + // We're done with the power context. + PowerContextEndUse(deviceExtension); + } + + // Bumping the media change count will force the file system to verify the volume when we resume + SET_FLAG(deviceExtension->DeviceObject->Flags, DO_VERIFY_VOLUME); + InterlockedIncrement((PLONG)&deviceExtension->MediaChangeCount); + + // If this power down is caused by Zero Power ODD, we should mark the device as in ZPODD mode. + if (zpoddInfo != NULL) + { + zpoddInfo->InZeroPowerState = TRUE; + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_POWER, + "Device has entered zero power state\n" + )); + } + + deviceExtension->PowerDownInProgress = FALSE; + + return STATUS_SUCCESS; +} + + +VOID +DeviceEvtSurpriseRemoval( + _In_ WDFDEVICE Device + ) +/*++ + +Routine Description: + + this function is called when the device is surprisely removed. + Stop all IO queues so that there will be no more request being sent down. + +Arguments: + + Device - Handle to device object + +Return Value: + + None. + +--*/ +{ + PCDROM_DEVICE_EXTENSION deviceExtension = NULL; + + PAGED_CODE(); + + deviceExtension = DeviceGetExtension(Device); + + deviceExtension->SurpriseRemoved = TRUE; + + // Stop the main timer + DeviceDisableMainTimer(deviceExtension); + + // legacy behavior to set partition length to be 0. + deviceExtension->PartitionLength.QuadPart = 0; + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, + "Surprisely remove a WDFDEVICE %p\n", Device)); + + return; +} + + +VOID +CreateQueueEvtIoDefault( + _In_ WDFQUEUE Queue, + _In_ WDFREQUEST Request + ) +/*++ + +Routine Description: + + this function is called when CREATE irp comes. + setup FileObject context fields, so it can be used to track MCN or exclusive lock/unlock. + +Arguments: + + Queue - Handle to device queue + + Request - the creation request + +Return Value: + + None + +--*/ +{ + WDFFILEOBJECT fileObject = WdfRequestGetFileObject(Request); + WDFDEVICE device = WdfIoQueueGetDevice(Queue); + NTSTATUS status = STATUS_SUCCESS; + PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(device); + PFILE_OBJECT_CONTEXT fileObjectContext = NULL; + + PAGED_CODE(); + + if (fileObject == NULL) { + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_QUEUE, + "Error: received a file create request with file object set to NULL\n")); + + RequestCompletion(deviceExtension, Request, STATUS_INTERNAL_ERROR, 0); + return; + } + + fileObjectContext = FileObjectGetContext(fileObject); + + // Initialize this WDFFILEOBJECT's context + fileObjectContext->DeviceObject = device; + fileObjectContext->FileObject = fileObject; + fileObjectContext->LockCount = 0; + fileObjectContext->McnDisableCount = 0; + fileObjectContext->EnforceStreamingRead = FALSE; + fileObjectContext->EnforceStreamingWrite = FALSE; + + // send down the create synchronously + status = DeviceSendRequestSynchronously(device, Request, FALSE); + + // Need to complete the request in this routine. + RequestCompletion(deviceExtension, Request, status, WdfRequestGetInformation(Request)); + + return; +} + +VOID +DeviceEvtFileClose( + _In_ WDFFILEOBJECT FileObject + ) +/*++ + +Routine Description: + + this function is called when CLOSE irp comes. + clean up MCN / Lock if necessary + +Arguments: + + FileObject - WDF file object created for the irp. + +Return Value: + + None + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + + PAGED_CODE(); + + if (FileObject != NULL) + { + WDFDEVICE device = WdfFileObjectGetDevice(FileObject); + PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(device); + PCDROM_DATA cdData = &(deviceExtension->DeviceAdditionalData); + PFILE_OBJECT_CONTEXT fileObjectContext = FileObjectGetContext(FileObject); + + // cleanup locked media tray + status = DeviceCleanupProtectedLocks(deviceExtension, fileObjectContext); + if (!NT_SUCCESS(status)) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, + "Failed to cleanup protected locks for WDFDEVICE %p, %!STATUS!\n", device, status)); + } + + // cleanup disabled MCN + status = DeviceCleanupDisableMcn(deviceExtension, fileObjectContext); + if (!NT_SUCCESS(status)) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, + "Failed to disable MCN for WDFDEVICE %p, %!STATUS!\n", device, status)); + } + + // cleanup exclusive access + if (EXCLUSIVE_MODE(cdData) && EXCLUSIVE_OWNER(cdData, FileObject)) + { + status = DeviceUnlockExclusive(deviceExtension, FileObject, FALSE); + if (!NT_SUCCESS(status)) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, + "Failed to release exclusive lock for WDFDEVICE %p, %!STATUS!\n", device, status)); + } + } + } + + return; +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS +DeviceCleanupProtectedLocks( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension, + _In_ PFILE_OBJECT_CONTEXT FileObjectContext + ) +/*++ + +Routine Description: + + this function removes protected locks for the handle + +Arguments: + + DeviceExtension - device context + + FileObject - WDF file object created for the irp. + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + + PAGED_CODE(); + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, + "CleanupProtectedLocks called for WDFDEVICE %p, WDFFILEOBJECT %p, locked %d times.\n", + DeviceExtension->Device, FileObjectContext->FileObject, FileObjectContext->LockCount)); + + // Synchronize with ejection and ejection control requests. + WdfWaitLockAcquire(DeviceExtension->EjectSynchronizationLock, NULL); + + // For each secure lock on this handle decrement the secured lock count + // for the FDO. Keep track of the new value. + if (FileObjectContext->LockCount != 0) + { + DeviceExtension->ProtectedLockCount -= FileObjectContext->LockCount; + FileObjectContext->LockCount = 0; + + // If the new lock count has been dropped to zero then issue a lock + // command to the device. + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, + "FDO secured lock count = %d " + "lock count = %d\n", + DeviceExtension->ProtectedLockCount, + DeviceExtension->LockCount)); + + if ((DeviceExtension->ProtectedLockCount == 0) && (DeviceExtension->LockCount == 0)) + { + SCSI_REQUEST_BLOCK srb = {0}; + PCDB cdb = (PCDB) &(srb.Cdb); + + srb.CdbLength = 6; + + cdb->MEDIA_REMOVAL.OperationCode = SCSIOP_MEDIUM_REMOVAL; + + // TRUE - prevent media removal. + // FALSE - allow media removal. + cdb->MEDIA_REMOVAL.Prevent = FALSE; + + // Set timeout value. + srb.TimeOutValue = DeviceExtension->TimeOutValue; + status = DeviceSendSrbSynchronously(DeviceExtension->Device, + &srb, + NULL, + 0, + FALSE, + NULL); + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_INIT, + "Allow media removal (unlock) request to drive returned %!STATUS!\n", + status)); + } + } + + WdfWaitLockRelease(DeviceExtension->EjectSynchronizationLock); + + return status; +} + + +_IRQL_requires_max_(APC_LEVEL) +NTSTATUS +DeviceCleanupDisableMcn( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension, + _In_ PFILE_OBJECT_CONTEXT FileObjectContext + ) +/*++ + +Routine Description: + + cleanup the MCN disable count for the handle + +Arguments: + + DeviceExtension - device context + + FileObject - WDF file object created for the irp. + +Return Value: + + NTSTATUS + +--*/ +{ + PAGED_CODE(); + + TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_INIT, + "CleanupDisableMcn called for WDFDEVICE %p, WDFFILEOBJECT %p, locked %d times.\n", + DeviceExtension->Device, FileObjectContext->FileObject, FileObjectContext->McnDisableCount)); + + // For each secure lock on this handle decrement the secured lock count + // for the FDO. Keep track of the new value. + while (FileObjectContext->McnDisableCount != 0) + { + DeviceEnableMediaChangeDetection(DeviceExtension, FileObjectContext, TRUE); + } + + return STATUS_SUCCESS; +} + +VOID +NormalizeIoctl( + _Inout_ PWDF_REQUEST_PARAMETERS requestParameters + ) +{ + ULONG ioctlCode; + ULONG baseCode; + ULONG functionCode; + + // if this is a class driver ioctl then we need to change the base code + // to IOCTL_STORAGE_BASE so that the switch statement can handle it. + // + // WARNING - currently the scsi class ioctl function codes are between + // 0x200 & 0x300. this routine depends on that fact + ioctlCode = requestParameters->Parameters.DeviceIoControl.IoControlCode; + baseCode = DEVICE_TYPE_FROM_CTL_CODE(ioctlCode); + functionCode = (ioctlCode & (~0xffffc003)) >> 2; + + if ((baseCode == IOCTL_SCSI_BASE) || + (baseCode == IOCTL_DISK_BASE) || + (baseCode == IOCTL_TAPE_BASE) || + (baseCode == IOCTL_DVD_BASE) || + (baseCode == IOCTL_CDROM_BASE)) + //IOCTL_STORAGE_BASE does not need to be converted. + { + if((functionCode >= 0x200) && (functionCode <= 0x300)) + { + ioctlCode = (ioctlCode & 0x0000ffff) | CTL_CODE(IOCTL_STORAGE_BASE, 0, 0, 0); + + TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_IOCTL, + "IOCTL code recalibrate, New ioctl code is %lx\n", + ioctlCode)); + + // Set the code into request parameters, then "requestParameters" needs to be used for dispatch functions. + requestParameters->Parameters.DeviceIoControl.IoControlCode = ioctlCode; + } + } +} + +VOID +DeviceEvtIoInCallerContext( + _In_ WDFDEVICE Device, + _In_ WDFREQUEST Request + ) +/*++ +Routine Description: + + Responds to EvtIoInCallerContext events from KMDF + It calls different functions to process different type of IOCTLs. + +Arguments: + + Device - handle to a WDF Device object + + Request - handle to the incoming WDF Request object + +Return Value: + + VOID. + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(Device); + PCDROM_REQUEST_CONTEXT requestContext = RequestGetContext(Request); + WDF_REQUEST_PARAMETERS requestParameters; + + requestContext->DeviceExtension = deviceExtension; + + // set the received time + RequestSetReceivedTime(Request); + + // get the request parameters + WDF_REQUEST_PARAMETERS_INIT(&requestParameters); + WdfRequestGetParameters(Request, &requestParameters); + + if (requestParameters.Type == WdfRequestTypeDeviceControl) + { + BOOLEAN processed = FALSE; + PCDROM_DATA cdData = &(deviceExtension->DeviceAdditionalData); + PMEDIA_CHANGE_DETECTION_INFO info = deviceExtension->MediaChangeDetectionInfo; + + if (requestParameters.Parameters.DeviceIoControl.IoControlCode != IOCTL_MCN_SYNC_FAKE_IOCTL) + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "Receiving IOCTL: %lx\n", + requestParameters.Parameters.DeviceIoControl.IoControlCode)); + } + else + { + TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_IOCTL, + "Receiving IOCTL: %lx\n", + requestParameters.Parameters.DeviceIoControl.IoControlCode)); + } + + // If the device is in exclusive mode, check whether the request is from + // the handle that locked the device. + if (EXCLUSIVE_MODE(cdData) && !EXCLUSIVE_OWNER(cdData, WdfRequestGetFileObject(Request))) + { + BOOLEAN isBlocked = FALSE; + + status = RequestIsIoctlBlockedByExclusiveAccess(Request, &isBlocked); + UNREFERENCED_PARAMETER(status); //defensive coding, avoid PREFAST warning. + + if (isBlocked) + { + if ((requestParameters.Parameters.DeviceIoControl.IoControlCode == IOCTL_STORAGE_EVENT_NOTIFICATION) && + (info != NULL) && (info->AsynchronousNotificationSupported != FALSE)) + { + // If AN is supported and we receive a signal but we can't send down GESN + // due to exclusive lock, we should save it and fire a GESN when it's unlocked. + // We just need true/false here and don't need count because we will keep sending + // GESN until we deplete all events. + info->ANSignalPendingDueToExclusiveLock = TRUE; + } + + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_IOCTL, + "Access Denied! Device in exclusive mode.Failing Ioctl %lx\n", + requestParameters.Parameters.DeviceIoControl.IoControlCode)); + RequestCompletion(deviceExtension, Request, STATUS_ACCESS_DENIED, 0); + + return; + } + } + + NormalizeIoctl(&requestParameters); + + // 1. All requests that don't need to access device can be processed immediately + if (!processed) + { + processed = RequestDispatchProcessDirectly(Device, Request, requestParameters); + } + + // 2. Requests that should be put in sequential queue. + if (!processed) + { + processed = RequestDispatchToSequentialQueue(Device, Request, requestParameters); + } + + // 3. Requests that need to be processed sequentially and in caller's context. + if (!processed) + { + processed = RequestDispatchSyncWithSequentialQueue(Device, Request, requestParameters); + } + + // 4. Special requests that needs different process in different cases. + if (!processed) + { + processed = RequestDispatchSpecialIoctls(Device, Request, requestParameters); + } + + // 5. This is default behavior for unknown IOCTLs. To pass it to lower level. + if (!processed) + { + processed = RequestDispatchUnknownRequests(Device, Request, requestParameters); + } + + // All requests should be processed already. + NT_ASSERT(processed); + UNREFERENCED_PARAMETER(processed); //defensive coding, avoid PREFAST warning. + } + else if (requestParameters.Type == WdfRequestTypeDeviceControlInternal) + { + RequestProcessInternalDeviceControl(Request, deviceExtension); + } + else + { + // Requests other than IOCTLs will be forwarded to default queue. + status = WdfDeviceEnqueueRequest(Device, Request); + if (!NT_SUCCESS(status)) + { + RequestCompletion(deviceExtension, Request, status, WdfRequestGetInformation(Request)); + } + } + + return; +} + + +BOOLEAN +RequestDispatchProcessDirectly( + _In_ WDFDEVICE Device, + _In_ WDFREQUEST Request, + _In_ WDF_REQUEST_PARAMETERS RequestParameters + ) +/*++ +Routine Description: + + These requests can be processed in a non-serialized manner, most of them don't need to access device. + +Arguments: + + Device - handle to a WDF Device object + + Request - handle to the incoming WDF Request object + + RequestParameters - request parameters + +Return Value: + + BOOLEAN - TRUE (request processed); FALSE (request is not processed in this function). + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + BOOLEAN processed = FALSE; + size_t dataLength = 0; + + PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(Device); + ULONG ioctlCode = RequestParameters.Parameters.DeviceIoControl.IoControlCode; + + switch (ioctlCode) + { + + case IOCTL_CDROM_GET_INQUIRY_DATA: + { + status = RequestHandleGetInquiryData(deviceExtension, Request, RequestParameters, &dataLength); + + processed = TRUE; + break; // complete the irp + } + + case IOCTL_STORAGE_GET_MEDIA_TYPES_EX: + { + status = RequestHandleGetMediaTypeEx(deviceExtension, Request, &dataLength); + + processed = TRUE; + break; // complete the irp + } + + case IOCTL_MOUNTDEV_QUERY_UNIQUE_ID: + { + status = RequestHandleMountQueryUniqueId(deviceExtension, Request, RequestParameters, &dataLength); + + processed = TRUE; + break; // complete the irp + } + + case IOCTL_MOUNTDEV_QUERY_DEVICE_NAME: + { + status = RequestHandleMountQueryDeviceName(deviceExtension, Request, RequestParameters, &dataLength); + + processed = TRUE; + break; // complete the irp + } + + case IOCTL_MOUNTDEV_QUERY_SUGGESTED_LINK_NAME: + { + status = RequestHandleMountQuerySuggestedLinkName(deviceExtension, Request, RequestParameters, &dataLength); + + processed = TRUE; + break; // complete the irp + } + + case IOCTL_STORAGE_GET_DEVICE_NUMBER: + { + status = RequestHandleGetDeviceNumber(deviceExtension, Request, RequestParameters, &dataLength); + + processed = TRUE; + break; // complete the irp + } + + case IOCTL_STORAGE_GET_HOTPLUG_INFO: + { + status = RequestHandleGetHotPlugInfo(deviceExtension, Request, RequestParameters, &dataLength); + + processed = TRUE; + break; // complete the irp + } + + case IOCTL_STORAGE_SET_HOTPLUG_INFO: + { + status = RequestHandleSetHotPlugInfo(deviceExtension, Request, RequestParameters, &dataLength); + + processed = TRUE; + break; // complete the irp + } + + case IOCTL_STORAGE_EVENT_NOTIFICATION: + { + status = RequestHandleEventNotification(deviceExtension, Request, &RequestParameters, &dataLength); + + processed = TRUE; + break; // complete the irp + } + +#if (NTDDI_VERSION >= NTDDI_WIN8) + case IOCTL_VOLUME_ONLINE: + { + // + // Mount manager and volume manager will + // follow this online with a post online + // but other callers may not. In those + // cases, we process this request right + // away. We approximate that these other + // callers are from user mode + // + + if (WdfRequestGetRequestorMode(Request) == KernelMode) + { + processed = TRUE; + } + break; + } +#endif + + default: + { + processed = FALSE; + break; + } + + } //end of switch (ioctlCode) + + if (processed) + { + UCHAR currentStackLocationFlags = 0; + currentStackLocationFlags = RequestGetCurrentStackLocationFlags(Request); + + if ((status == STATUS_VERIFY_REQUIRED) && + (currentStackLocationFlags & SL_OVERRIDE_VERIFY_VOLUME)) + { + // If the status is verified required and this request + // should bypass verify required then retry the request. + status = STATUS_IO_DEVICE_ERROR; + UNREFERENCED_PARAMETER(status); // disables prefast warning; defensive coding... + + processed = RequestDispatchProcessDirectly(Device, Request, RequestParameters); + } + else + { + // Complete the request after processing it. + RequestCompletion(deviceExtension, Request, status, dataLength); + } + } + + return processed; +} + + +BOOLEAN +RequestDispatchToSequentialQueue( + _In_ WDFDEVICE Device, + _In_ WDFREQUEST Request, + _In_ WDF_REQUEST_PARAMETERS RequestParameters + ) +/*++ +Routine Description: + + These requests can be processed in a non-serialized manner, most of them don't need to access device. + +Arguments: + + Device - handle to a WDF Device object + + Request - handle to the incoming WDF Request object + + RequestParameters - request parameters + +Return Value: + + BOOLEAN - TRUE (request processed); FALSE (request is not processed in this function). + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + BOOLEAN processed = FALSE; + size_t dataLength = 0; + BOOLEAN inZeroPowerState = FALSE; + + PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(Device); + ULONG ioctlCode = RequestParameters.Parameters.DeviceIoControl.IoControlCode; + PZERO_POWER_ODD_INFO zpoddInfo = deviceExtension->ZeroPowerODDInfo; + + if ((zpoddInfo != NULL) && + (zpoddInfo->InZeroPowerState != FALSE)) + { + inZeroPowerState = TRUE; + } + + switch (ioctlCode) + { + + case IOCTL_CDROM_RAW_READ: + { + status = RequestValidateRawRead(deviceExtension, Request, RequestParameters, &dataLength); + + processed = TRUE; + break; + } + + case IOCTL_DISK_GET_DRIVE_GEOMETRY_EX: + case IOCTL_CDROM_GET_DRIVE_GEOMETRY_EX: + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "RequestDispatchToSequentialQueue: Get drive geometryEx\n")); + if ( RequestParameters.Parameters.DeviceIoControl.OutputBufferLength < + (ULONG)FIELD_OFFSET(DISK_GEOMETRY_EX, Data)) + { + status = STATUS_BUFFER_TOO_SMALL; + dataLength = FIELD_OFFSET(DISK_GEOMETRY_EX, Data); + } + else if (inZeroPowerState != FALSE) + { + status = STATUS_NO_MEDIA_IN_DEVICE; + } + else + { + status = STATUS_SUCCESS; + } + + processed = TRUE; + break; + } + + case IOCTL_DISK_GET_DRIVE_GEOMETRY: + case IOCTL_CDROM_GET_DRIVE_GEOMETRY: + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "RequestDispatchToSequentialQueue: Get drive geometry\n")); + if (RequestParameters.Parameters.DeviceIoControl.OutputBufferLength < + sizeof(DISK_GEOMETRY)) + { + status = STATUS_BUFFER_TOO_SMALL; + dataLength = sizeof(DISK_GEOMETRY); + } + else if (inZeroPowerState != FALSE) + { + status = STATUS_NO_MEDIA_IN_DEVICE; + } + else + { + status = STATUS_SUCCESS; + } + + processed = TRUE; + break; + } + + case IOCTL_CDROM_READ_TOC_EX: + { + status = RequestValidateReadTocEx(deviceExtension, Request, RequestParameters, &dataLength); + + if (inZeroPowerState != FALSE) + { + status = STATUS_NO_MEDIA_IN_DEVICE; + } + + processed = TRUE; + break; + } + + case IOCTL_CDROM_READ_TOC: + { + status = RequestValidateReadToc(deviceExtension, RequestParameters, &dataLength); + + if (inZeroPowerState != FALSE) + { + status = STATUS_NO_MEDIA_IN_DEVICE; + } + + processed = TRUE; + break; + } + + case IOCTL_CDROM_GET_LAST_SESSION: + { + status = RequestValidateGetLastSession(deviceExtension, RequestParameters, &dataLength); + + processed = TRUE; + break; + } + + case IOCTL_CDROM_PLAY_AUDIO_MSF: + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "RequestDispatchToSequentialQueue: Play audio MSF\n")); + + if (RequestParameters.Parameters.DeviceIoControl.InputBufferLength < + sizeof(CDROM_PLAY_AUDIO_MSF)) + { + status = STATUS_INFO_LENGTH_MISMATCH; + } + else + { + status = STATUS_SUCCESS; + } + + processed = TRUE; + break; + } + + case IOCTL_CDROM_SEEK_AUDIO_MSF: + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "RequestDispatchToSequentialQueue: Seek audio MSF\n")); + + if (RequestParameters.Parameters.DeviceIoControl.InputBufferLength < + sizeof(CDROM_SEEK_AUDIO_MSF)) + { + status = STATUS_INFO_LENGTH_MISMATCH; + } + else + { + status = STATUS_SUCCESS; + } + + processed = TRUE; + break; + } + + case IOCTL_CDROM_PAUSE_AUDIO: + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "RequestDispatchToSequentialQueue: Pause audio\n")); + + status = STATUS_SUCCESS; + processed = TRUE; + break; + } + + case IOCTL_CDROM_RESUME_AUDIO: + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "RequestDispatchToSequentialQueue: Resume audio\n")); + + status = STATUS_SUCCESS; + processed = TRUE; + break; + } + + case IOCTL_CDROM_READ_Q_CHANNEL: + { + status = RequestValidateReadQChannel(Request, RequestParameters, &dataLength); + + processed = TRUE; + break; + } + + case IOCTL_CDROM_GET_VOLUME: + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "RequestDispatchToSequentialQueue: Get volume control\n")); + + if (RequestParameters.Parameters.DeviceIoControl.OutputBufferLength < + sizeof(VOLUME_CONTROL)) + { + status = STATUS_BUFFER_TOO_SMALL; + dataLength = sizeof(VOLUME_CONTROL); + } + else + { + status = STATUS_SUCCESS; + } + + processed = TRUE; + break; + } + + case IOCTL_CDROM_SET_VOLUME: + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "RequestDispatchToSequentialQueue: Set volume control\n")); + + if (RequestParameters.Parameters.DeviceIoControl.InputBufferLength < + sizeof(VOLUME_CONTROL)) + { + status = STATUS_INFO_LENGTH_MISMATCH; + } + else + { + status = STATUS_SUCCESS; + } + + processed = TRUE; + break; + } + + case IOCTL_CDROM_STOP_AUDIO: + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "RequestDispatchToSequentialQueue: Stop audio\n")); + + status = STATUS_SUCCESS; + processed = TRUE; + break; + } + + case IOCTL_STORAGE_CHECK_VERIFY: + case IOCTL_STORAGE_CHECK_VERIFY2: + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "RequestDispatchToSequentialQueue: [%p] Check Verify\n", Request)); + + // Following check will let the condition "OutputBufferLength == 0" pass. + // Since it's legacy behavior in classpnp, we need to keep it. + if ((RequestParameters.Parameters.DeviceIoControl.OutputBufferLength > 0) && + (RequestParameters.Parameters.DeviceIoControl.OutputBufferLength < sizeof(ULONG))) + { + status = STATUS_BUFFER_TOO_SMALL; + dataLength = sizeof(ULONG); + } + else if (inZeroPowerState != FALSE) + { + status = STATUS_NO_MEDIA_IN_DEVICE; + } + else + { + status = STATUS_SUCCESS; + } + + processed = TRUE; + break; + } + + case IOCTL_DVD_GET_REGION: + { + // validation will be done when process it. + status = STATUS_SUCCESS; + processed = TRUE; + break; + } + + case IOCTL_DVD_READ_STRUCTURE: + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "RequestDispatchToSequentialQueue: [%p] IOCTL_DVD_READ_STRUCTURE\n", Request)); + + status = RequestValidateDvdReadStructure(deviceExtension, RequestParameters, &dataLength); + + processed = TRUE; + break; + } + + case IOCTL_DVD_READ_KEY: + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "RequestDispatchToSequentialQueue: [%p] IOCTL_DVD_READ_KEY\n", Request)); + + status = RequestValidateDvdReadKey(deviceExtension, Request, RequestParameters, &dataLength); + + processed = TRUE; + break; + } + + case IOCTL_DVD_START_SESSION: + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "RequestDispatchToSequentialQueue: [%p] IOCTL_DVD_START_SESSION\n", Request)); + + status = RequestValidateDvdStartSession(deviceExtension, RequestParameters, &dataLength); + + processed = TRUE; + break; + } + + case IOCTL_DVD_SEND_KEY: + case IOCTL_DVD_SEND_KEY2: + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "RequestDispatchToSequentialQueue: [%p] IOCTL_DVD_SEND_KEY\n", Request)); + + status = RequestValidateDvdSendKey(deviceExtension, Request, RequestParameters, &dataLength); + + processed = TRUE; + break; + } + + case IOCTL_STORAGE_SET_READ_AHEAD: + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "RequestDispatchToSequentialQueue: [%p] SetReadAhead\n", Request)); + + if (RequestParameters.Parameters.DeviceIoControl.InputBufferLength < + sizeof(STORAGE_SET_READ_AHEAD)) + { + status = STATUS_INVALID_PARAMETER; + } + else + { + status = STATUS_SUCCESS; + } + + processed = TRUE; + break; + } + + case IOCTL_DISK_IS_WRITABLE: + { + status = STATUS_SUCCESS; + + processed = TRUE; + break; + } + + case IOCTL_DISK_GET_DRIVE_LAYOUT: + { + ULONG requiredSize = 0; + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "RequestDispatchToSequentialQueue: Get drive layout\n")); + + requiredSize = FIELD_OFFSET(DRIVE_LAYOUT_INFORMATION, PartitionEntry[1]); + + if (RequestParameters.Parameters.DeviceIoControl.OutputBufferLength < + requiredSize) + { + status = STATUS_BUFFER_TOO_SMALL; + dataLength = requiredSize; + } + else + { + status = STATUS_SUCCESS; + } + + processed = TRUE; + break; + } + + case IOCTL_DISK_GET_DRIVE_LAYOUT_EX: + { + ULONG requiredSize = 0; + + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "RequestDispatchToSequentialQueue: Get drive layoutEx\n")); + + requiredSize = FIELD_OFFSET(DRIVE_LAYOUT_INFORMATION_EX, PartitionEntry[1]); + + if (RequestParameters.Parameters.DeviceIoControl.OutputBufferLength < + requiredSize) + { + status = STATUS_BUFFER_TOO_SMALL; + dataLength = requiredSize; + } + else + { + status = STATUS_SUCCESS; + } + + processed = TRUE; + break; + } + + case IOCTL_DISK_GET_PARTITION_INFO: + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "RequestDispatchToSequentialQueue: Get Partition Info\n")); + + if (RequestParameters.Parameters.DeviceIoControl.OutputBufferLength < + sizeof(PARTITION_INFORMATION)) + { + status = STATUS_BUFFER_TOO_SMALL; + dataLength = sizeof(PARTITION_INFORMATION); + } + else + { + status = STATUS_SUCCESS; + } + + processed = TRUE; + break; + } + + case IOCTL_DISK_GET_PARTITION_INFO_EX: + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "RequestDispatchToSequentialQueue: Get Partition InfoEx\n")); + + if (RequestParameters.Parameters.DeviceIoControl.OutputBufferLength < + sizeof(PARTITION_INFORMATION_EX)) + { + status = STATUS_BUFFER_TOO_SMALL; + dataLength = sizeof(PARTITION_INFORMATION_EX); + } + else + { + status = STATUS_SUCCESS; + } + + processed = TRUE; + break; + } + + case IOCTL_DISK_VERIFY: + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "RequestDispatchToSequentialQueue: IOCTL_DISK_VERIFY to device %p through request %p\n", + Device, + Request)); + + if (RequestParameters.Parameters.DeviceIoControl.InputBufferLength < + sizeof(VERIFY_INFORMATION)) + { + status = STATUS_INVALID_PARAMETER; + } + else + { + status = STATUS_SUCCESS; + } + + processed = TRUE; + break; + } + + case IOCTL_DISK_GET_LENGTH_INFO: + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "RequestDispatchToSequentialQueue: Disk Get Length InfoEx\n")); + + if (RequestParameters.Parameters.DeviceIoControl.OutputBufferLength < + sizeof(GET_LENGTH_INFORMATION)) + { + status = STATUS_BUFFER_TOO_SMALL; + dataLength = sizeof(GET_LENGTH_INFORMATION); + } + else if (inZeroPowerState != FALSE) + { + status = STATUS_NO_MEDIA_IN_DEVICE; + } + else + { + status = STATUS_SUCCESS; + } + + processed = TRUE; + break; + } + + case IOCTL_CDROM_GET_CONFIGURATION: + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "RequestDispatchToSequentialQueue: [%p] IOCTL_CDROM_GET_CONFIGURATION\n", Request)); + + status = RequestValidateGetConfiguration(deviceExtension, Request, RequestParameters, &dataLength); + + processed = TRUE; + break; + } + + case IOCTL_CDROM_SET_SPEED: + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "RequestDispatchToSequentialQueue: [%p] IOCTL_CDROM_SET_SPEED\n", Request)); + + status = RequestValidateSetSpeed(deviceExtension, Request, RequestParameters, &dataLength); + + processed = TRUE; + break; + } + + case IOCTL_DVD_END_SESSION: + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "RequestDispatchToSequentialQueue: [%p] IOCTL_DVD_END_SESSION\n", Request)); + + status = RequestValidateDvdEndSession(deviceExtension, Request, RequestParameters, &dataLength); + + processed = TRUE; + break; + } + + case IOCTL_AACS_END_SESSION: + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "RequestDispatchToSequentialQueue: [%p] IOCTL_AACS_END_SESSION\n", Request)); + + status = RequestValidateAacsEndSession(deviceExtension, Request, RequestParameters, &dataLength); + + processed = TRUE; + break; + } + + case IOCTL_AACS_READ_MEDIA_KEY_BLOCK: + { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, + "AACS: Querying full MKB with bufferSize of %x bytes\n", + (int)RequestParameters.Parameters.DeviceIoControl.OutputBufferLength + )); + + status = RequestValidateAacsReadMediaKeyBlock(deviceExtension, Request, RequestParameters, &dataLength); + + processed = TRUE; + break; + } + + case IOCTL_AACS_START_SESSION: + { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, + "AACS: Requesting AGID\n" + )); + + status = RequestValidateAacsStartSession(deviceExtension, RequestParameters, &dataLength); + + processed = TRUE; + break; + } + + case IOCTL_AACS_SEND_CERTIFICATE: + { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, + "AACS: Sending host certificate to drive\n" + )); + + status = RequestValidateAacsSendCertificate(deviceExtension, Request, RequestParameters, &dataLength); + + processed = TRUE; + break; + } + + case IOCTL_AACS_GET_CERTIFICATE: + { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, + "AACS: Querying drive certificate\n" + )); + + status = RequestValidateAacsGetCertificate(deviceExtension, Request, RequestParameters, &dataLength); + + processed = TRUE; + break; + } + + case IOCTL_AACS_GET_CHALLENGE_KEY: + { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, + "AACS: Querying drive challenge key\n" + )); + + status = RequestValidateAacsGetChallengeKey(deviceExtension, Request, RequestParameters, &dataLength); + + processed = TRUE; + break; + } + + case IOCTL_AACS_SEND_CHALLENGE_KEY: + { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, + "AACS: Sending drive challenge key\n" + )); + + status = RequestValidateAacsSendChallengeKey(deviceExtension, Request, RequestParameters, &dataLength); + + processed = TRUE; + break; + } + + case IOCTL_AACS_READ_VOLUME_ID: + { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, + "AACS: Reading volume ID\n" + )); + + status = RequestValidateAacsReadVolumeId(deviceExtension, Request, RequestParameters, &dataLength); + + processed = TRUE; + break; + } + + case IOCTL_AACS_READ_SERIAL_NUMBER: + { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, + "AACS: Reading Serial Number\n" + )); + + status = RequestValidateAacsReadSerialNumber(deviceExtension, Request, RequestParameters, &dataLength); + + processed = TRUE; + break; + } + + case IOCTL_AACS_READ_MEDIA_ID: + { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, + "AACS: Reading media ID\n" + )); + + status = RequestValidateAacsReadMediaId(deviceExtension, Request, RequestParameters, &dataLength); + + processed = TRUE; + break; + } + + case IOCTL_AACS_READ_BINDING_NONCE: + case IOCTL_AACS_GENERATE_BINDING_NONCE: + { + if (ioctlCode == IOCTL_AACS_GENERATE_BINDING_NONCE) + { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, + "AACS: Generating new binding nonce\n" + )); + } + else + { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, + "AACS: Reading existing binding nonce\n" + )); + } + + status = RequestValidateAacsBindingNonce(deviceExtension, Request, RequestParameters, &dataLength); + + processed = TRUE; + break; + } + + case IOCTL_CDROM_ENABLE_STREAMING: + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "RequestDispatchToSequentialQueue: [%p] IOCTL_CDROM_ENABLE_STREAMING\n", Request)); + + status = RequestValidateEnableStreaming(Request, RequestParameters, &dataLength); + + processed = TRUE; + break; + } + + case IOCTL_CDROM_SEND_OPC_INFORMATION: + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "RequestDispatchToSequentialQueue: [%p] IOCTL_CDROM_SEND_OPC_INFORMATION\n", Request)); + + status = RequestValidateSendOpcInformation(Request, RequestParameters, &dataLength); + + processed = TRUE; + break; + } + + case IOCTL_CDROM_GET_PERFORMANCE: + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "RequestDispatchToSequentialQueue: [%p] IOCTL_CDROM_GET_PERFORMANCE\n", Request)); + + status = RequestValidateGetPerformance(Request, RequestParameters, &dataLength); + + processed = TRUE; + break; + } + + case IOCTL_STORAGE_MEDIA_REMOVAL: + case IOCTL_STORAGE_EJECTION_CONTROL: + { + if(RequestParameters.Parameters.DeviceIoControl.InputBufferLength < + sizeof(PREVENT_MEDIA_REMOVAL)) + { + status = STATUS_INFO_LENGTH_MISMATCH; + } + else + { + status = STATUS_SUCCESS; + } + + processed = TRUE; + break; // complete the irp + } + + case IOCTL_STORAGE_MCN_CONTROL: + { + if(RequestParameters.Parameters.DeviceIoControl.InputBufferLength < + sizeof(PREVENT_MEDIA_REMOVAL)) + { + status = STATUS_INFO_LENGTH_MISMATCH; + } + else + { + status = STATUS_SUCCESS; + } + + processed = TRUE; + break; // complete the irp + } + + case IOCTL_STORAGE_RESERVE: + case IOCTL_STORAGE_RELEASE: + { + // there is no validate check currently. + status = STATUS_SUCCESS; + processed = TRUE; + break; + } + + case IOCTL_STORAGE_PERSISTENT_RESERVE_IN: + case IOCTL_STORAGE_PERSISTENT_RESERVE_OUT: + { + status = RequestValidatePersistentReserve(deviceExtension, Request, RequestParameters, &dataLength); + + processed = TRUE; + break; + } + + case IOCTL_STORAGE_EJECT_MEDIA: + case IOCTL_STORAGE_LOAD_MEDIA: + case IOCTL_STORAGE_LOAD_MEDIA2: + { + status = STATUS_SUCCESS; + + processed = TRUE; + break; // complete the irp + } + + case IOCTL_STORAGE_FIND_NEW_DEVICES: + { + // process it. + IoInvalidateDeviceRelations(deviceExtension->LowerPdo, BusRelations); + + status = STATUS_SUCCESS; + + processed = TRUE; + break; // complete the irp + } + + case IOCTL_STORAGE_READ_CAPACITY: + { + if (RequestParameters.Parameters.DeviceIoControl.OutputBufferLength < sizeof(STORAGE_READ_CAPACITY)) + { + dataLength = sizeof(STORAGE_READ_CAPACITY); + status = STATUS_BUFFER_TOO_SMALL; + } + else if (inZeroPowerState != FALSE) + { + status = STATUS_NO_MEDIA_IN_DEVICE; + } + else + { + status = STATUS_SUCCESS; + } + + processed = TRUE; + break; // complete the irp + } + + case IOCTL_STORAGE_CHECK_PRIORITY_HINT_SUPPORT: + { + // for disk.sys only in original classpnp + status = STATUS_NOT_SUPPORTED; + + processed = TRUE; + break; // complete the irp + } + +#if (NTDDI_VERSION >= NTDDI_WIN8) + case IOCTL_DISK_ARE_VOLUMES_READY: + { + // this request doesn't access device at all, so seemingly it can be processed + // directly; however, in case volume online is not received, we will need to + // park these requests in a queue, and the only way a request can be queued is + // if the request came out of another queue. + status = STATUS_SUCCESS; + + processed = TRUE; + break; + } + + case IOCTL_VOLUME_ONLINE: + case IOCTL_VOLUME_POST_ONLINE: + { + status = STATUS_SUCCESS; + + processed = TRUE; + break; + } +#endif + + default: + { + processed = FALSE; + break; + } + } //end of switch (ioctlCode) + + if (processed) + { + UCHAR currentStackLocationFlags = 0; + currentStackLocationFlags = RequestGetCurrentStackLocationFlags(Request); + + if ((status == STATUS_VERIFY_REQUIRED) && + (currentStackLocationFlags & SL_OVERRIDE_VERIFY_VOLUME)) + { + // If the status is verified required and this request + // should bypass verify required then retry the request. + status = STATUS_IO_DEVICE_ERROR; + UNREFERENCED_PARAMETER(status); // disables prefast warning; defensive coding... + + processed = RequestDispatchToSequentialQueue(Device, Request, RequestParameters); + } + else + { + if (NT_SUCCESS(status)) + { + // Forward the request to serialized queue. + status = WdfDeviceEnqueueRequest(Device, Request); + } + + if (!NT_SUCCESS(status)) + { + // Validation failed / forward failed, complete the request. + RequestCompletion(deviceExtension, Request, status, dataLength); + } + } + } + + return processed; +} + + +BOOLEAN +RequestDispatchSyncWithSequentialQueue( + _In_ WDFDEVICE Device, + _In_ WDFREQUEST Request, + _In_ WDF_REQUEST_PARAMETERS RequestParameters + ) +/*++ +Routine Description: + + These requests need to stay in caller's context and be processed in serialized manner. + +Arguments: + + Device - handle to a WDF Device object + + Request - handle to the incoming WDF Request object + + RequestParameters - request parameters + +Return Value: + + BOOLEAN - TRUE (request processed); FALSE (request is not processed in this function). + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + BOOLEAN processed = FALSE; + size_t dataLength = 0; + + PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(Device); + ULONG ioctlCode = RequestParameters.Parameters.DeviceIoControl.IoControlCode; + + switch (ioctlCode) + { + + case IOCTL_CDROM_EXCLUSIVE_ACCESS: + { + //1. Validate + status = RequestValidateExclusiveAccess(Request, RequestParameters, &dataLength); + + //2. keep the request in serialized manner and stay in user's context. + if (NT_SUCCESS(status)) + { + PCDROM_EXCLUSIVE_ACCESS exclusiveAccess = NULL; + + status = WdfRequestRetrieveInputBuffer(Request, + RequestParameters.Parameters.DeviceIoControl.InputBufferLength, + &exclusiveAccess, + NULL); + + if (NT_SUCCESS(status)) + { + // do not need to check "status" as it passed validation and cannot fail in WdfRequestRetrieveInputBuffer() + switch (exclusiveAccess->RequestType) + { + + case ExclusiveAccessQueryState: + { + status = RequestSetContextFields(Request, RequestHandleExclusiveAccessQueryLockState); + break; + } + + case ExclusiveAccessLockDevice: + { + status = RequestSetContextFields(Request, RequestHandleExclusiveAccessLockDevice); + break; + } + + case ExclusiveAccessUnlockDevice: + { + status = RequestSetContextFields(Request, RequestHandleExclusiveAccessUnlockDevice); + break; + } + default: + { + // already valicated in RequestValidateExclusiveAccess() + NT_ASSERT(FALSE); + break; + } + } + } + + if (NT_SUCCESS(status)) + { + // now, put the special synchronization information into the context + status = RequestSynchronizeProcessWithSerialQueue(Device, Request); + + // "status" is used for debugging in above statement, reset to success to avoid further work in this function. + status = STATUS_SUCCESS; + } + } + + processed = TRUE; + break; // complete the irp + } + + default: + { + processed = FALSE; + break; + } + } //end of switch (ioctlCode) + + // Following process is only valid if the request is not really processed. (failed in validation) + if (processed && !NT_SUCCESS(status)) + { + UCHAR currentStackLocationFlags = 0; + currentStackLocationFlags = RequestGetCurrentStackLocationFlags(Request); + + if ((status == STATUS_VERIFY_REQUIRED) && + (currentStackLocationFlags & SL_OVERRIDE_VERIFY_VOLUME)) + { + // + // If the status is verified required and this request + // should bypass verify required then retry the request. + // + status = STATUS_IO_DEVICE_ERROR; + UNREFERENCED_PARAMETER(status); // disables prefast warning; defensive coding... + + processed = RequestDispatchSyncWithSequentialQueue(Device, Request, RequestParameters); + } + else + { + // Validation failed / forward failed, complete the request. + RequestCompletion(deviceExtension, Request, status, dataLength); + } + } + + return processed; +} + + +BOOLEAN +RequestDispatchSpecialIoctls( + _In_ WDFDEVICE Device, + _In_ WDFREQUEST Request, + _In_ WDF_REQUEST_PARAMETERS RequestParameters + ) +/*++ +Routine Description: + + These requests need to be processed in different manner according to input parameters + +Arguments: + + Device - handle to a WDF Device object + + Request - handle to the incoming WDF Request object + + RequestParameters - request parameters + +Return Value: + + BOOLEAN - TRUE (request processed); FALSE (request is not processed in this function). + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + BOOLEAN processed = FALSE; + size_t dataLength = 0; + BOOLEAN requestCompleted = FALSE; + + PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(Device); + PCDROM_DATA cdData = &(deviceExtension->DeviceAdditionalData); + ULONG ioctlCode = RequestParameters.Parameters.DeviceIoControl.IoControlCode; + + switch (ioctlCode) + { + case IOCTL_SCSI_PASS_THROUGH: + case IOCTL_SCSI_PASS_THROUGH_DIRECT: + case IOCTL_SCSI_PASS_THROUGH_EX: + case IOCTL_SCSI_PASS_THROUGH_DIRECT_EX: + { + // SPTI is considered special case as we need to set the MinorFunction before pass to low level. + +#if defined (_WIN64) + if (WdfRequestIsFrom32BitProcess(Request)) + { + if ((ioctlCode == IOCTL_SCSI_PASS_THROUGH) || (ioctlCode == IOCTL_SCSI_PASS_THROUGH_DIRECT)) + { + if (RequestParameters.Parameters.DeviceIoControl.InputBufferLength < sizeof(SCSI_PASS_THROUGH32)) + { + status = STATUS_INVALID_PARAMETER; + } + } + else + { + if (RequestParameters.Parameters.DeviceIoControl.InputBufferLength < sizeof(SCSI_PASS_THROUGH32_EX)) + { + status = STATUS_INVALID_PARAMETER; + } + } + } + else +#endif + { + if ((ioctlCode == IOCTL_SCSI_PASS_THROUGH) || (ioctlCode == IOCTL_SCSI_PASS_THROUGH_DIRECT)) + { + if (RequestParameters.Parameters.DeviceIoControl.InputBufferLength < sizeof(SCSI_PASS_THROUGH)) + { + status = STATUS_INVALID_PARAMETER; + } + } + else + { + if (RequestParameters.Parameters.DeviceIoControl.InputBufferLength < sizeof(SCSI_PASS_THROUGH_EX)) + { + status = STATUS_INVALID_PARAMETER; + } + } + } + + if (!NT_SUCCESS(status)) + { + // validation failed. + RequestCompletion(deviceExtension, Request, status, dataLength); + } + else + { + // keep the request in serialized manner and stay in user's context. + status = RequestSetContextFields(Request, RequestHandleScsiPassThrough); + + if (NT_SUCCESS(status)) + { + status = RequestSynchronizeProcessWithSerialQueue(Device, Request); + } + else + { + RequestCompletion(deviceExtension, Request, status, 0); + } + } + + requestCompleted = TRUE; + processed = TRUE; + break; + } + + case IOCTL_STORAGE_QUERY_PROPERTY: + { + if (RequestParameters.Parameters.DeviceIoControl.InputBufferLength < sizeof(STORAGE_PROPERTY_QUERY)) + { + status = STATUS_INFO_LENGTH_MISMATCH; + } + else + { + PSTORAGE_PROPERTY_QUERY inputBuffer = NULL; + + status = WdfRequestRetrieveInputBuffer(Request, + RequestParameters.Parameters.DeviceIoControl.InputBufferLength, + &inputBuffer, + NULL); + + if (NT_SUCCESS(status)) + { + if (!EXCLUSIVE_MODE(cdData) || // not locked + EXCLUSIVE_OWNER(cdData, WdfRequestGetFileObject(Request)) || // request is from lock owner + (inputBuffer->QueryType == PropertyExistsQuery)) // request not access device + { + if (inputBuffer->PropertyId == StorageDeviceUniqueIdProperty) + { + // previously handled in classpnp + // keep the request in serialized manner and stay in user's context. + status = RequestSetContextFields(Request, RequestHandleQueryPropertyDeviceUniqueId); + + if (NT_SUCCESS(status)) + { + status = RequestSynchronizeProcessWithSerialQueue(Device, Request); + // remeber that the request has been completed. + requestCompleted = TRUE; + } + } + else if (inputBuffer->PropertyId == StorageDeviceWriteCacheProperty) + { + // previously handled in classpnp + // keep the request in serialized manner and stay in user's context. + status = RequestSetContextFields(Request, RequestHandleQueryPropertyWriteCache); + + if (NT_SUCCESS(status)) + { + status = RequestSynchronizeProcessWithSerialQueue(Device, Request); + // remeber that the request has been completed. + requestCompleted = TRUE; + } + } + else + { + // Pass to port driver for handling + RequestDispatchUnknownRequests(Device, Request, RequestParameters); + + // remeber that the request has been completed. + requestCompleted = TRUE; + } + } + else + { + // If cached data exists, return cached data. Otherwise, fail the request. + if ((inputBuffer->QueryType == PropertyStandardQuery) && + ((inputBuffer->PropertyId == StorageDeviceProperty) || (inputBuffer->PropertyId == StorageAdapterProperty)) ) + { + status = RequestHandleQueryPropertyRetrieveCachedData(deviceExtension, Request, RequestParameters, &dataLength); + } + else + { + status = STATUS_ACCESS_DENIED; + } + } + } + } + + processed = TRUE; + break; + } + + // this IOCTL is a fake one, used for MCN process sync-ed with serial queue. + case IOCTL_MCN_SYNC_FAKE_IOCTL: + { + PIRP irp = WdfRequestWdmGetIrp(Request); + + if ((deviceExtension->MediaChangeDetectionInfo != NULL) && + (irp == deviceExtension->MediaChangeDetectionInfo->MediaChangeSyncIrp) && + (WdfRequestGetRequestorMode(Request) == KernelMode) && + (RequestParameters.Parameters.Others.Arg1 == RequestSetupMcnSyncIrp) && + (RequestParameters.Parameters.Others.Arg2 == RequestSetupMcnSyncIrp) && + (RequestParameters.Parameters.Others.Arg4 == RequestSetupMcnSyncIrp)) + { + // This is the requset we use to sync Media Change Detection with sequential queue. + status = WdfDeviceEnqueueRequest(Device, Request); + + if (!NT_SUCCESS(status)) + { + RequestCompletion(deviceExtension, Request, status, dataLength); + } + + requestCompleted = TRUE; + processed = TRUE; + } + else + { + // process as an unknown request. + processed = FALSE; + } + break; + } + + default: + { + processed = FALSE; + break; + } + } //end of switch (ioctlCode) + + if (processed && !requestCompleted) + { + UCHAR currentStackLocationFlags = 0; + currentStackLocationFlags = RequestGetCurrentStackLocationFlags(Request); + + if ((status == STATUS_VERIFY_REQUIRED) && + (currentStackLocationFlags & SL_OVERRIDE_VERIFY_VOLUME)) + { + // If the status is verified required and this request + // should bypass verify required then retry the request. + status = STATUS_IO_DEVICE_ERROR; + UNREFERENCED_PARAMETER(status); // disables prefast warning; defensive coding... + + processed = RequestDispatchSpecialIoctls(Device, Request, RequestParameters); + } + else + { + RequestCompletion(deviceExtension, Request, status, dataLength); + } + } + + return processed; +} + + +BOOLEAN +RequestDispatchUnknownRequests( + _In_ WDFDEVICE Device, + _In_ WDFREQUEST Request, + _In_ WDF_REQUEST_PARAMETERS RequestParameters + ) +/*++ +Routine Description: + + All unknown requests will be pass to lower level driver. + If IRQL is PASSIVE_LEVEL, the request will be serialized; + Otherwise, it'll be sent and forget. + +Arguments: + + Device - handle to a WDF Device object + + Request - handle to the incoming WDF Request object + + RequestParameters - request parameters + +Return Value: + + BOOLEAN - TRUE (request processed); FALSE (request is not processed in this function). + +--*/ +{ + NTSTATUS status = STATUS_UNSUCCESSFUL; + PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(Device); + + ULONG baseCode = DEVICE_TYPE_FROM_CTL_CODE(RequestParameters.Parameters.DeviceIoControl.IoControlCode); + + if ((KeGetCurrentIrql() != PASSIVE_LEVEL) || + (baseCode == FILE_DEVICE_ACPI)) + { + // 1. When IRQL is higher than PASSIVE_LEVEL, + // 2. ataport sends IOCTL_ACPI_ASYNC_EVAL_METHOD before queue starts, + // send request directly to lower driver. + status = RequestHandleUnknownIoctl(Device, Request); + } + else + { + // keep the request in serialized manner and stay in user's context. + status = RequestSetContextFields(Request, RequestHandleUnknownIoctl); + + if (NT_SUCCESS(status)) + { + status = RequestSynchronizeProcessWithSerialQueue(Device, Request); + } + else + { + RequestCompletion(deviceExtension, Request, status, 0); + } + } + + UNREFERENCED_PARAMETER(status); //defensive coding, avoid PREFAST warning. + + // All unknown IOCTLs are processed in this function. + return TRUE; //processed +} + +VOID +RequestProcessInternalDeviceControl( + _In_ WDFREQUEST Request, + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension + ) +/*++ +Routine Description: + + all internal IOCTL will be send to lower driver asynchronously. + +Arguments: + + Request - handle to the incoming WDF Request object + DeviceExtension - device extension structure + +Return Value: + + None + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + PIRP irp = NULL; + PIO_STACK_LOCATION irpStack = NULL; + PIO_STACK_LOCATION nextStack = NULL; + BOOLEAN requestSent = FALSE; + + irp = WdfRequestWdmGetIrp(Request); + irpStack = IoGetCurrentIrpStackLocation(irp); + nextStack = IoGetNextIrpStackLocation(irp); + + // Set the parameters in the next stack location. + nextStack->Parameters.Scsi.Srb = irpStack->Parameters.Scsi.Srb; + nextStack->MajorFunction = IRP_MJ_SCSI; + nextStack->MinorFunction = IRP_MN_SCSI_CLASS; + + WdfRequestSetCompletionRoutine(Request, RequestDummyCompletionRoutine, NULL); + + status = RequestSend(DeviceExtension, + Request, + DeviceExtension->IoTarget, + 0, + &requestSent); + + // send the request straight down (asynchronously) + if (!requestSent) + { + // fail the request + RequestCompletion(DeviceExtension, Request, status, WdfRequestGetInformation(Request)); + } + + return; +} + + + +// +// Serial I/O Queue Event callbacks +// + +VOID +SequentialQueueEvtIoReadWrite( + _In_ WDFQUEUE Queue, + _In_ WDFREQUEST Request, + _In_ size_t Length + ) +/*++ +Routine Description: + + validate and process read/write request. + +Arguments: + + Queue - parallel queue itself + + Request - handle to the incoming WDF Request object + + Length - read / write lenght + +Return Value: + + None + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + WDFDEVICE device = WdfIoQueueGetDevice(Queue); + PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(device); + WDF_REQUEST_PARAMETERS requestParameters; + PIRP wdmIrp = WdfRequestWdmGetIrp(Request); + PIO_STACK_LOCATION currentIrpStack = IoGetCurrentIrpStackLocation(wdmIrp); + PCDROM_DATA cdData = &(deviceExtension->DeviceAdditionalData); + + // Get the request parameters + WDF_REQUEST_PARAMETERS_INIT(&requestParameters); + WdfRequestGetParameters(Request, &requestParameters); + + if (requestParameters.Type == WdfRequestTypeRead) + { + TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_GENERAL, + "Receiving READ, Length %Ix\n", (ULONG) Length)); + } + else + { + TracePrint((TRACE_LEVEL_VERBOSE, TRACE_FLAG_GENERAL, + "Receiving WRITE, Length %Ix\n", (ULONG) Length)); + } + + // Check if a verify is required before a READ/WRITE + if (TEST_FLAG(deviceExtension->DeviceObject->Flags, DO_VERIFY_VOLUME) && + (requestParameters.MinorFunction != CDROM_VOLUME_VERIFY_CHECKED) && + !TEST_FLAG(currentIrpStack->Flags, SL_OVERRIDE_VERIFY_VOLUME)) + { + // DO_VERIFY_VOLUME is set for the device object, + // but this request is not itself a verify request. + // So fail this request. + RequestCompletion(deviceExtension, Request, STATUS_VERIFY_REQUIRED, 0); + } + else + { + // Since we've bypassed the verify-required tests we don't need to repeat + // them with this IRP - in particular we don't want to worry about + // hitting them at the partition 0 level if the request has come through + // a non-zero partition. + currentIrpStack->MinorFunction = CDROM_VOLUME_VERIFY_CHECKED; + + // Fail READ/WRITE requests when music is playing + if (deviceExtension->DeviceAdditionalData.PlayActive) + { + RequestCompletion(deviceExtension, Request, STATUS_DEVICE_BUSY, 0); + + return; + } + + // Fail READ/WRITE requests from non-owners if the drive is locked + if (EXCLUSIVE_MODE(cdData) && !EXCLUSIVE_OWNER(cdData, WdfRequestGetFileObject(Request))) + { + RequestCompletion(deviceExtension, Request, STATUS_ACCESS_DENIED, 0); + + return; + } + + // Succeed READ/WRITE requests of length 0 + if (Length == 0) + { + // Several parts of the code turn 0 into 0xffffffff, + // so don't process a zero-length request any further. + RequestCompletion(deviceExtension, Request, STATUS_SUCCESS, Length); + + return; + } + + // If there is an unexpected write request, we want to rediscover MMC capabilities + if (!deviceExtension->DeviceAdditionalData.Mmc.WriteAllowed && + (requestParameters.Type == WdfRequestTypeWrite)) + { + // Schedule MMC capabilities update now, but perform it later in a work item + deviceExtension->DeviceAdditionalData.Mmc.UpdateState = CdromMmcUpdateRequired; + } + + // If MMC capabilities update is required, we create a separate work item to avoid blocking + // the current thread; otherwise, we initiate an async read/write in the current thread. + if (DeviceIsMmcUpdateRequired(deviceExtension->Device)) + { + deviceExtension->ReadWriteWorkItemContext.OriginalRequest = Request; + WdfWorkItemEnqueue(deviceExtension->ReadWriteWorkItem); + + status = STATUS_SUCCESS; + } + else + { + status = RequestValidateReadWrite(deviceExtension, Request, requestParameters); + + if (NT_SUCCESS(status)) + { + status = RequestHandleReadWrite(deviceExtension, Request, requestParameters); + } + } + + if (!NT_SUCCESS(status)) + { + RequestCompletion(deviceExtension, Request, status, 0); + } + } + + return; +} + + +VOID +ReadWriteWorkItemRoutine( + _In_ WDFWORKITEM WorkItem + ) +/*++ + +Routine Description: + + Work item routine for validating and initiating read and write requests. + The reason why we do that from a work item is because we may need to update MMC + capabilities before validating a read/write request and that is a sync operation. + +Arguments: + + WorkItem - WDF work item + +Return Value: + + none + +--*/ +{ + PCDROM_DEVICE_EXTENSION deviceExtension = NULL; + WDFREQUEST readWriteRequest = NULL; + WDF_REQUEST_PARAMETERS readWriteRequestParameters; + NTSTATUS status = STATUS_SUCCESS; + + PAGED_CODE (); + + deviceExtension = WdfObjectGetTypedContext(WdfWorkItemGetParentObject(WorkItem), CDROM_DEVICE_EXTENSION); + readWriteRequest = deviceExtension->ReadWriteWorkItemContext.OriginalRequest; + deviceExtension->ReadWriteWorkItemContext.OriginalRequest = NULL; + + WDF_REQUEST_PARAMETERS_INIT(&readWriteRequestParameters); + WdfRequestGetParameters(readWriteRequest, &readWriteRequestParameters); + + if (DeviceIsMmcUpdateRequired(deviceExtension->Device)) + { + // Issue command to update the drive capabilities. + // The failure of MMC update is not considered critical, so we'll + // continue to process the request even if MMC update fails. + (VOID) DeviceUpdateMmcCapabilities(deviceExtension->Device); + } + + // Now verify and process the request + if (NT_SUCCESS(status)) + { + status = RequestValidateReadWrite(deviceExtension, readWriteRequest, readWriteRequestParameters); + } + if (NT_SUCCESS(status)) + { + status = RequestHandleReadWrite(deviceExtension, readWriteRequest, readWriteRequestParameters); + } + + // Complete the request immediately on failure + if (!NT_SUCCESS(status)) + { + RequestCompletion(deviceExtension, readWriteRequest, status, 0); + } +} + + +VOID +SequentialQueueEvtIoDeviceControl( + _In_ WDFQUEUE Queue, + _In_ WDFREQUEST Request, + _In_ size_t OutputBufferLength, + _In_ size_t InputBufferLength, + _In_ ULONG IoControlCode + ) +/*++ +Routine Description: + + validate and process IOCTL request. + +Arguments: + + Queue - sequential queue + + Request - handle to the incoming WDF Request object + +Return Value: + + None + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + WDFDEVICE device = WdfIoQueueGetDevice(Queue); + PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(device); + PCDROM_REQUEST_CONTEXT requestContext = RequestGetContext(Request); + PCDROM_DATA cdData = &(deviceExtension->DeviceAdditionalData); + WDF_REQUEST_PARAMETERS requestParameters; + + UNREFERENCED_PARAMETER(OutputBufferLength); + UNREFERENCED_PARAMETER(InputBufferLength); + UNREFERENCED_PARAMETER(IoControlCode); + + // get the request parameters + WDF_REQUEST_PARAMETERS_INIT(&requestParameters); + WdfRequestGetParameters(Request, &requestParameters); + + // If the device is in exclusive mode, check whether the request is from + // the handle that locked the device + if (EXCLUSIVE_MODE(cdData) && !EXCLUSIVE_OWNER(cdData, WdfRequestGetFileObject(Request))) + { + BOOLEAN isBlocked = FALSE; + + status = RequestIsIoctlBlockedByExclusiveAccess(Request, &isBlocked); + if (NT_SUCCESS(status) && isBlocked) + { + if (requestContext->SyncRequired) + { + // set the following event, so RequestSynchronizeProcessWithSerialQueue() can contintue run to process the real request. + // this function will wait for the request process finishes. + KeSetEvent(requestContext->SyncEvent, IO_CD_ROM_INCREMENT, FALSE); + } + else + { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_IOCTL, + "DeviceEvtIoInCallerContext: Access Denied! Device in exclusive mode.Failing Ioctl %lx\n", + requestParameters.Parameters.DeviceIoControl.IoControlCode)); + RequestCompletion(deviceExtension, Request, STATUS_ACCESS_DENIED, 0); + } + + return; + } + } + + if (!cdData->Mmc.WriteAllowed && + ((requestParameters.Parameters.DeviceIoControl.IoControlCode == IOCTL_DISK_IS_WRITABLE) || + (requestParameters.Parameters.DeviceIoControl.IoControlCode == IOCTL_DISK_VERIFY))) + { + cdData->Mmc.UpdateState = CdromMmcUpdateRequired; + } + + // check if this is a synchronized ioctl + if (requestContext->SyncRequired) + { + // set the following event, so RequestSynchronizeProcessWithSerialQueue() can contintue run to process the real request. + // this function will wait for the request process finishes. + KeSetEvent(requestContext->SyncEvent, IO_CD_ROM_INCREMENT, FALSE); + } + else + { + deviceExtension->IoctlWorkItemContext.OriginalRequest = Request; + + // all other IOCTL processing is currently processed via a + // work item running at PASSIVE_LEVEL. + WdfWorkItemEnqueue(deviceExtension->IoctlWorkItem); + } + + return; +} + + +VOID +IoctlWorkItemRoutine( + _In_ WDFWORKITEM WorkItem + ) +/*++ + +Routine Description: + + Work item routine for processing ioctl requests. + This is needed because event callbacks are called at DISPATCH_LEVEL and ioctl + requests are currently processed synchronously and not asynchronously. + +Arguments: + + WorkItem - WDF work item + +Return Value: + + none + +--*/ +{ + PCDROM_DEVICE_EXTENSION deviceExtension = NULL; + + PAGED_CODE (); + + deviceExtension = WdfObjectGetTypedContext(WdfWorkItemGetParentObject(WorkItem), CDROM_DEVICE_EXTENSION); + + if (DeviceIsMmcUpdateRequired(deviceExtension->Device)) + { + // 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. + DeviceUpdateMmcCapabilities(deviceExtension->Device); + } + + RequestProcessSerializedIoctl(deviceExtension, deviceExtension->IoctlWorkItemContext.OriginalRequest); +} + + +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS +RequestProcessSerializedIoctl( + _In_ PCDROM_DEVICE_EXTENSION DeviceExtension, + _In_ WDFREQUEST Request + ) +/*++ +Routine Description: + + a dispatch routine for all functions to process IOCTLs. + +Arguments: + + DeviceExtension - device context + + Request - handle to the incoming WDF Request object + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + size_t information = 0; + WDF_REQUEST_PARAMETERS requestParameters; + BOOLEAN completeRequest = TRUE; + + PAGED_CODE (); + + // Get the Request parameters + WDF_REQUEST_PARAMETERS_INIT(&requestParameters); + WdfRequestGetParameters(Request, &requestParameters); + + NormalizeIoctl(&requestParameters); + + // process IOCTLs + switch (requestParameters.Parameters.DeviceIoControl.IoControlCode) + { + case IOCTL_CDROM_READ_TOC: + case IOCTL_CDROM_GET_LAST_SESSION: + status = RequestHandleReadTOC(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_CDROM_READ_TOC_EX: + status = RequestHandleReadTocEx(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_CDROM_GET_CONFIGURATION: + status = RequestHandleGetConfiguration(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_CDROM_RAW_READ: + status = DeviceHandleRawRead(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_DISK_GET_LENGTH_INFO: + case IOCTL_DISK_GET_DRIVE_GEOMETRY: + case IOCTL_DISK_GET_DRIVE_GEOMETRY_EX: + case IOCTL_CDROM_GET_DRIVE_GEOMETRY: + case IOCTL_CDROM_GET_DRIVE_GEOMETRY_EX: + case IOCTL_STORAGE_READ_CAPACITY: + status = RequestHandleGetDriveGeometry(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_DISK_VERIFY: + status = RequestHandleDiskVerify(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_STORAGE_CHECK_VERIFY: + // IOCTL_STORAGE_CHECK_VERIFY2 was processed including send a Test Unit Read + // with srb flag SRB_CLASS_FLAGS_LOW_PRIORITY to port driver asynchronizelly. + // The original request was completed after TUR finishes. + // As CDROM.SYS serializes IOs need accessing device, it's not a big difference from above behavior to + // just process it in serialized manner. So I put it here and treat it as same as IOCTL_STORAGE_CHECK_VERIFY. + case IOCTL_STORAGE_CHECK_VERIFY2: + status = RequestHandleCheckVerify(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_DISK_GET_DRIVE_LAYOUT: + case IOCTL_DISK_GET_DRIVE_LAYOUT_EX: + case IOCTL_DISK_GET_PARTITION_INFO: + case IOCTL_DISK_GET_PARTITION_INFO_EX: + status = RequestHandleFakePartitionInfo(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_DISK_IS_WRITABLE: + // + // Even though this media is writable, the requester of this IOCTL really + // wants to know if thw media behaves like any other disk or not. This is + // so if FeatureDefectManagement and FeatureRandomWritable are current on + // the drive-represented by the FeatureDefectManagement validation schema + // + if (DeviceExtension->DeviceAdditionalData.Mmc.WriteAllowed && + (DeviceExtension->DeviceAdditionalData.Mmc.ValidationSchema == FeatureDefectManagement)) + { + status = STATUS_SUCCESS; + } + else + { + status = STATUS_MEDIA_WRITE_PROTECTED; + } + information = 0; + break; + + case IOCTL_CDROM_PLAY_AUDIO_MSF: + status = DeviceHandlePlayAudioMsf(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_CDROM_READ_Q_CHANNEL: + status = DeviceHandleReadQChannel(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_CDROM_PAUSE_AUDIO: + status = DeviceHandlePauseAudio(DeviceExtension, Request, &information); + break; + + case IOCTL_CDROM_RESUME_AUDIO: + status = DeviceHandleResumeAudio(DeviceExtension, Request, &information); + break; + + case IOCTL_CDROM_SEEK_AUDIO_MSF: + status = DeviceHandleSeekAudioMsf(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_CDROM_STOP_AUDIO: + status = DeviceHandleStopAudio(DeviceExtension, Request, &information); + break; + + case IOCTL_CDROM_GET_VOLUME: + case IOCTL_CDROM_SET_VOLUME: + status = DeviceHandleGetSetVolume(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_DVD_GET_REGION: + status = RequestHandleGetDvdRegion(DeviceExtension, Request, &information); + break; + + case IOCTL_DVD_READ_STRUCTURE: + status = DeviceHandleReadDvdStructure(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_DVD_END_SESSION: + status = DeviceHandleDvdEndSession(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_DVD_START_SESSION: + case IOCTL_DVD_READ_KEY: + status = DeviceHandleDvdStartSessionReadKey(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_DVD_SEND_KEY: + case IOCTL_DVD_SEND_KEY2: + status = DeviceHandleDvdSendKey(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_STORAGE_SET_READ_AHEAD: + status = DeviceHandleSetReadAhead(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_CDROM_SET_SPEED: + status = DeviceHandleSetSpeed(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_AACS_READ_MEDIA_KEY_BLOCK_SIZE: + case IOCTL_AACS_READ_MEDIA_KEY_BLOCK: + status = DeviceHandleAacsReadMediaKeyBlock(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_AACS_START_SESSION: + status = DeviceHandleAacsStartSession(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_AACS_END_SESSION: + status = DeviceHandleAacsEndSession(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_AACS_SEND_CERTIFICATE: + status = DeviceHandleAacsSendCertificate(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_AACS_GET_CERTIFICATE: + status = DeviceHandleAacsGetCertificate(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_AACS_GET_CHALLENGE_KEY: + status = DeviceHandleAacsGetChallengeKey(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_AACS_SEND_CHALLENGE_KEY: + status = DeviceHandleSendChallengeKey(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_AACS_READ_VOLUME_ID: + status = DeviceHandleReadVolumeId(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_AACS_READ_SERIAL_NUMBER: + status = DeviceHandleAacsReadSerialNumber(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_AACS_READ_MEDIA_ID: + status = DeviceHandleAacsReadMediaId(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_AACS_READ_BINDING_NONCE: + status = DeviceHandleAacsReadBindingNonce(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_AACS_GENERATE_BINDING_NONCE: + status = DeviceHandleAacsGenerateBindingNonce(DeviceExtension, Request, requestParameters, &information); + break; + + case IOCTL_CDROM_ENABLE_STREAMING: + status = RequestHandleEnableStreaming(DeviceExtension, Request, &information); + break; + + case IOCTL_CDROM_SEND_OPC_INFORMATION: + status = RequestHandleSendOpcInformation(DeviceExtension, Request, &information); + break; + + case IOCTL_CDROM_GET_PERFORMANCE: + status = RequestHandleGetPerformance(DeviceExtension, Request, requestParameters, &information); + break; + + // This IOCTL is a fake one, used for MCN process sync-ed with serial queue. + case IOCTL_MCN_SYNC_FAKE_IOCTL: + status = RequestHandleMcnSyncFakeIoctl(DeviceExtension, &information); + break; + + case IOCTL_STORAGE_MEDIA_REMOVAL: + case IOCTL_STORAGE_EJECTION_CONTROL: + { + status = RequestHandleEjectionControl(DeviceExtension, Request, requestParameters, &information); + + break; + } + + case IOCTL_STORAGE_EJECT_MEDIA: + case IOCTL_STORAGE_LOAD_MEDIA: + case IOCTL_STORAGE_LOAD_MEDIA2: + { + status = RequestHandleLoadEjectMedia(DeviceExtension, Request, requestParameters, &information); + + break; + } + + case IOCTL_STORAGE_MCN_CONTROL: + { + status = RequestHandleMcnControl(DeviceExtension, Request, &information); + + break; + } + + case IOCTL_STORAGE_RESERVE: + case IOCTL_STORAGE_RELEASE: + { + status = RequestHandleReserveRelease(DeviceExtension, Request, requestParameters, &information); + + break; + } + + case IOCTL_STORAGE_PERSISTENT_RESERVE_IN: + case IOCTL_STORAGE_PERSISTENT_RESERVE_OUT: + { + status = RequestHandlePersistentReserve(DeviceExtension, Request, requestParameters, &information); + + break; + } + +#if (NTDDI_VERSION >= NTDDI_WIN8) + case IOCTL_DISK_ARE_VOLUMES_READY: + { + status = RequestHandleAreVolumesReady(DeviceExtension, Request, requestParameters, &information); + + completeRequest = FALSE; + + break; + } + + case IOCTL_VOLUME_ONLINE: + case IOCTL_VOLUME_POST_ONLINE: + { + status = RequestHandleVolumeOnline(DeviceExtension, Request, requestParameters, &information); + + break; + } +#endif + + default: + { + status = STATUS_ACCESS_DENIED; + break; + } + } // end of switch(ioctl) + + if (completeRequest) + { + RequestCompletion(DeviceExtension, Request, status, information); + } + + return status; +} + +VOID +SequentialQueueEvtCanceledOnQueue( + _In_ WDFQUEUE Queue, + _In_ WDFREQUEST Request + ) +/*++ +Routine Description: + + Perform cancellation when request is still in queue. + + If request is sych-ed in another thread, signal the event to let that thread be able to complete the request. + Otherwise, complete the request. + +Arguments: + + Queue - serial queue + Request - handle to the incoming WDF Request object + +Return Value: + + None + +--*/ +{ + PCDROM_REQUEST_CONTEXT requestContext = RequestGetContext(Request); + + if (requestContext->SyncRequired) + { + KeSetEvent(requestContext->SyncEvent, IO_CD_ROM_INCREMENT, FALSE); + } + else + { + PCDROM_DEVICE_EXTENSION deviceExtension = NULL; + WDFDEVICE device = WdfIoQueueGetDevice(Queue); + + deviceExtension = DeviceGetExtension(device); + + RequestCompletion(deviceExtension, Request, STATUS_CANCELLED, 0); + + } + + return; +} + + +NTSTATUS +RequestSynchronizeProcessWithSerialQueue( + _In_ WDFDEVICE Device, + _In_ WDFREQUEST Request + ) +/*++ +Routine Description: + + This is the mechanism to sync a request process in original thread with serialize queue. + initialize a EVENT and put the request inot serialize queue; + waiting for serialize queue processes to this request and signal the EVENT; + call the request handler to process this request. + +Arguments: + + DeviceExtension - device context + + Request - handle to the incoming WDF Request object + +Return Value: + + NTSTATUS + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(Device); + PCDROM_REQUEST_CONTEXT requestContext = RequestGetContext(Request); + PKEVENT bufferToFree = requestContext->SyncEvent; + + if (KeGetCurrentIrql() >= DISPATCH_LEVEL) { + // cannot block at or above DISPATCH_LEVEL + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_IOCTL, + "RequestSynchronousProcessWithSerialQueue called at DISPATCH_LEVEL or above")); + NT_ASSERT(FALSE); + RequestCompletion(deviceExtension, Request, STATUS_INVALID_LEVEL, 0); + return STATUS_INVALID_LEVEL; + } + + // init the synchronization event + KeInitializeEvent(requestContext->SyncEvent, NotificationEvent, FALSE); + + // do we still need to do something like this? + // SET_FLAG(nextStack->Flags, SL_OVERRIDE_VERIFY_VOLUME); + + // NOTE: this mechanism relies on that KMDF will not complete request by itself. + // Doing that will cause the syncEvent not fired thus this thread will stuck. + // This should not really happen: our EvtCanceledOnQueue callbacks should be + // called even if queues are purged for some reason. The only case when these + // callbacks are not called is when a request is owned by the driver (i.e. has + // already been passed to one of the registered handlers). In this case, it is + // our responsibility to cancel such requests properly. + status = WdfDeviceEnqueueRequest(Device, Request); + + if (!NT_SUCCESS(status)) + { + // Failed to forward request! Pretend the sync event already occured, otherwise we'll hit + // an assert in RequestEvtCleanup. + KeSetEvent(requestContext->SyncEvent, IO_CD_ROM_INCREMENT, FALSE); + RequestCompletion(deviceExtension, Request, status, WdfRequestGetInformation(Request)); + } + else + { + NTSTATUS waitStatus = STATUS_UNSUCCESSFUL; + PCDROM_DATA cdData = &(deviceExtension->DeviceAdditionalData); + BOOLEAN fCallSyncCallback = FALSE; + PIRP irp = WdfRequestWdmGetIrp(Request); + + // ok, now wait on the event + while (waitStatus != STATUS_SUCCESS) + { + waitStatus = KeWaitForSingleObject(requestContext->SyncEvent, Executive, KernelMode, TRUE, NULL); + if (waitStatus == STATUS_SUCCESS) // must check equality -- STATUS_ALERTED is success code + { + // do nothing + } + else if (waitStatus != STATUS_ALERTED) + { + // do nothing + TracePrint((TRACE_LEVEL_FATAL, TRACE_FLAG_IOCTL, + "Request %p on device object %p had a non-alert, non-success result from wait (%!HRESULT!)\n", + Request, Device, waitStatus)); + NT_ASSERT(FALSE); + } + else if (PsIsThreadTerminating(PsGetCurrentThread())) + { + // the thread was alerted and is terminating, so cancel the irp + // this will cause EvtIoCanceledOnQueue to be called, which will signal the event, + // so we will get out of the while loop and eventually complete the request. + if (IoCancelIrp(irp)) + { + // cancellation routine was called + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, + "Sychronize Ioctl: request %p cancelled from device %p\n", + Request, Device)); + } + else + { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, + "Sychronize Ioctl: request %p could not be cancelled from device %p\n", + Request, Device)); + } + } + else + { + TracePrint((TRACE_LEVEL_WARNING, TRACE_FLAG_IOCTL, + "SPURIOUS ALERT waiting for Request %p on device %p (%!STATUS!)\n", + Request, Device, status)); + } + } // end of wait loop on the event + + // because we've waited an unknown amount of time, should check + // the cancelled flag to immediately fail the irp as appropriate + if (WdfRequestIsCanceled(Request)) + { + // the request was cancelled, thus we should always stop + // processing here if possible. + status = STATUS_CANCELLED; + RequestCompletion(deviceExtension, Request, status, 0); + } + else if (EXCLUSIVE_MODE(cdData) && !EXCLUSIVE_OWNER(cdData, WdfRequestGetFileObject(Request))) + { + WDF_REQUEST_PARAMETERS requestParameters; + BOOLEAN isBlocked = FALSE; + + // get the request parameters + WDF_REQUEST_PARAMETERS_INIT(&requestParameters); + WdfRequestGetParameters(Request, &requestParameters); + + status = RequestIsIoctlBlockedByExclusiveAccess(Request, &isBlocked); + if (isBlocked) + { + TracePrint((TRACE_LEVEL_ERROR, TRACE_FLAG_IOCTL, + "Access Denied! Device in exclusive mode.Failing Ioctl %lx\n", + requestParameters.Parameters.DeviceIoControl.IoControlCode)); + RequestCompletion(deviceExtension, Request, STATUS_ACCESS_DENIED, 0); + } + else + { + TracePrint((TRACE_LEVEL_INFORMATION, TRACE_FLAG_IOCTL, + "Ioctl %lx not blocked by cdrom being in exclusive mode\n", + requestParameters.Parameters.DeviceIoControl.IoControlCode)); + fCallSyncCallback = TRUE; + } + } + else + { + fCallSyncCallback = TRUE; + } + + if (fCallSyncCallback) + { + // Synchronization completed successfully. Call the requested routine + status = requestContext->SyncCallback(Device, Request); + } + } + + // The next SequentialQueue evt routine will not be triggered until the current request is completed. + + // clean up the request context setting. + FREE_POOL(bufferToFree); + + return status; +} + +NTSTATUS +RequestIsIoctlBlockedByExclusiveAccess( + _In_ WDFREQUEST Request, + _Out_ PBOOLEAN IsBlocked + ) +/*++ +Routine Description: + + Check if the IOCTL request should be blocked or not according to + the exclusive lock stat. + +Arguments: + + Request - handle to the incoming WDF Request object + +Return Value: + + NTSTATUS + + IsBlocked - TRUE (be blocked); FALSE (not blocked) + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + ULONG ioctlCode = 0; + ULONG baseCode = 0; + WDF_REQUEST_PARAMETERS requestParameters; + + // Get the Request parameters + WDF_REQUEST_PARAMETERS_INIT(&requestParameters); + WdfRequestGetParameters(Request, &requestParameters); + + // check and initialize parameter + if (IsBlocked == NULL) + { + //This is an internal function and this parameter must be supplied. + NT_ASSERT(FALSE); + + return STATUS_INVALID_PARAMETER; + } + else + { + *IsBlocked = FALSE; + } + + // check if this is an IOCTL + if ((requestParameters.Type == WdfRequestTypeDeviceControl) || + (requestParameters.Type == WdfRequestTypeDeviceControlInternal)) + { + // + // Allow minimum set of commands that are required for the disk manager + // to show the CD device, while in exclusive mode. + // Note: These commands should not generate any requests to the device, + // and thus must be handled directly in StartIO during exclusive + // access (except for the exclusive owner, of course). + // + ioctlCode = requestParameters.Parameters.DeviceIoControl.IoControlCode; + baseCode = DEVICE_TYPE_FROM_CTL_CODE(ioctlCode); + + if (ioctlCode == IOCTL_SCSI_GET_ADDRESS || + ioctlCode == IOCTL_STORAGE_GET_HOTPLUG_INFO || + ioctlCode == IOCTL_STORAGE_GET_DEVICE_NUMBER || + ioctlCode == IOCTL_STORAGE_GET_MEDIA_TYPES_EX || + ioctlCode == IOCTL_CDROM_EXCLUSIVE_ACCESS || + ioctlCode == IOCTL_CDROM_GET_INQUIRY_DATA + ) + { + *IsBlocked = FALSE; + } + + // + // Handle IOCTL_STORAGE_QUERY_PROPERTY special because: + // (1) PropertyExistsQuery should not generate device i/o + // (2) Queries for StorageDeviceProperty and StorageAdapterDescriptor + // will return cache'd data + else if (ioctlCode == IOCTL_STORAGE_QUERY_PROPERTY) + { + PSTORAGE_PROPERTY_QUERY query = NULL; + status = WdfRequestRetrieveInputBuffer(Request, + requestParameters.Parameters.DeviceIoControl.InputBufferLength, + (PVOID*)&query, + NULL); + + if (NT_SUCCESS(status)) + { + if (query != NULL) + { + if (query->QueryType == PropertyExistsQuery) + { + *IsBlocked = FALSE; + } + else if ((query->QueryType == PropertyStandardQuery) && + ((query->PropertyId == StorageDeviceProperty) || + (query->PropertyId == StorageAdapterProperty))) + { + *IsBlocked = FALSE; + } + } + } + } + + // Return TRUE for unknown IOCTLs with STORAGE bases + else if (baseCode == IOCTL_SCSI_BASE || + baseCode == IOCTL_DISK_BASE || + baseCode == IOCTL_CDROM_BASE || + baseCode == IOCTL_STORAGE_BASE || + baseCode == IOCTL_DVD_BASE ) + { + *IsBlocked = TRUE; + } + } + else + { + // this should only be called with an IOCTL + NT_ASSERT(FALSE); + + status = STATUS_INVALID_PARAMETER; + } + + return status; +} + +BOOLEAN +DeviceIsMmcUpdateRequired( + _In_ WDFDEVICE Device + ) +/*++ +Routine Description: + + Check if the device needs to update its MMC information. + +Arguments: + + Device - device to be checked. + +Return Value: + + TRUE (require update); FALSE (not require update) + +--*/ +{ + PCDROM_DEVICE_EXTENSION deviceExtension = DeviceGetExtension(Device); + PCDROM_DATA cdData = &(deviceExtension->DeviceAdditionalData); + + if ((cdData->Mmc.IsMmc) && + (cdData->Mmc.UpdateState == CdromMmcUpdateRequired)) + { + return TRUE; + } + else + { + // no update required: just proceed + return FALSE; + } +} + +VOID +RequestEvtCleanup( + _In_ WDFOBJECT Request + ) +/*++ +Routine Description: + + Request cleanup callback. + +Arguments: + + Request - request to clean up. + +Return Value: + + None + +--*/ +{ + WDFREQUEST request = (WDFREQUEST)Request; + PCDROM_REQUEST_CONTEXT requestContext = RequestGetContext(request); + + if (requestContext->SyncRequired) + { + // the event should have been signaled, just check that + NT_ASSERT(KeReadStateEvent(requestContext->SyncEvent) != 0); + } +} + |
