Sample Code
Windows Driver Samples/ CDROM Storage Class Driver/ C++/ src/ cdrom.c/
/*-- 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 < 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); } }
Our Services
-
What our customers say about us?
Read our customer testimonials to find out why our clients keep returning for their projects.
View Testimonials