/*++ Copyright (c) Nefarius Software Solutions e.U. All rights reserved. Licensed under the MIT license. Module Name: Dmf_BusFilter.c Abstract: Creates the supporting plumbing for a Bus Filter Driver. Environment: Kernel-mode Driver Framework --*/ #include "DmfDefinitions.h" #include "Dmf_BusFilter.h" #include "DmfModules.Library.Trace.h" #include #if defined(DMF_INCLUDE_TMH) #include "Dmf_BusFilter.tmh" #endif /////////////////////////////////////////////////////////////////////////////////////////////////////// // Bus Filter /////////////////////////////////////////////////////////////////////////////////////////////////////// // #if defined(DMF_KERNEL_MODE) // WDM child device context // // LIFETIME NOTES // -------------- // Each WDM filter device object created by this library lives between // IoAttachDeviceToDeviceStackSafe (end of Relations_AddDevice) and // IoDeleteDevice (end of Relations_RemoveDevice or DeviceCleanup). // The mandatory teardown sequence is: // 1. IoReleaseRemoveLockAndWait -- drain all in-flight I/O holders // 2. IoDetachDevice -- detach filter from the PDO stack // 3. IoDeleteDevice -- lazy-free the device object (actual // memory release deferred to last reference) // NOTHING may touch DeviceExtension after IoDeleteDevice returns. // typedef struct _WDM_CHILD_DEVICE_EXTENSION { // GUID to identify WDM child device. // GUID Signature; // Remove lock serialises in-flight I/O against IRP_MN_REMOVE_DEVICE teardown. // Initialized in Relations_AddDevice; released-and-waited in Relations_RemoveDevice // (normal path) or DeviceCleanup (force path). // IO_REMOVE_LOCK RemoveLock; // Target Device Object (result of IoAttachDeviceToDeviceStackSafe) // PDEVICE_OBJECT TargetDeviceObject; // Physical Device Object (PDO of the bus child being filtered) // PDEVICE_OBJECT PhysicalDeviceObject; // Parent ChildList entry // LIST_ENTRY ListEntry; // Parent WDF device object // WDFDEVICE Parent; // Child WDF wrapper object // DMFBUSCHILDDEVICE Child; // TRUE if PDO appeared in the most recent bus-relations response // BOOLEAN IsExisting; } WDM_CHILD_DEVICE_EXTENSION; // Parent bus device context // typedef struct _PARENT_BUS_DEVICE_CONTEXT { // List of child device (relations) // LIST_ENTRY ChildList; // Spin lock protecting ChildList access // KSPIN_LOCK ChildListLock; // Count of bus-relations work items currently queued or executing. // Used to prevent DeviceCleanup from racing a pending passive-level // AddDevice worker. // volatile LONG OutstandingWorkItems; // Signaled when OutstandingWorkItems drops to zero (initially signaled). // KEVENT WorkItemsIdle; } PARENT_BUS_DEVICE_CONTEXT; WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(PARENT_BUS_DEVICE_CONTEXT, DMF_BusFilter_GetParentContext) // Bus child device context // typedef struct _BUS_CHILD_DEVICE_CONTEXT { // WDM device object // PDEVICE_OBJECT DeviceObject; } BUS_CHILD_DEVICE_CONTEXT; WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(BUS_CHILD_DEVICE_CONTEXT, DMF_BusFilter_GetChildContext) typedef _Function_class_(EVT_DMF_BusFilter_DispatchPnp) _IRQL_requires_same_ _IRQL_requires_max_(PASSIVE_LEVEL) NTSTATUS EVT_DMF_BusFilter_DispatchPnp( _In_ DMFBUSCHILDDEVICE ChildDevice, _In_ PIRP Irp ); // Module-internal context data // typedef struct _DMF_CONTEXT_BusFilter { // Copy of the module configuration // DMF_BusFilter_CONFIG Configuration; // Hooked dispatch table // PDRIVER_DISPATCH MajorDispatchFunctions[IRP_MJ_MAXIMUM_FUNCTION + 1]; // PNP minor functions dispatch routines // EVT_DMF_BusFilter_DispatchPnp* PnPMinorDispatchFunctions[IRP_MN_DEVICE_ENUMERATED + 1]; } BusFilter_Context; WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(BusFilter_Context, BusFilterContextGet); // {678CBB8D-019F-4D07-912A-73E2E568B148} DEFINE_GUID(GUID_DMF_BUSFILTER_SIGNATURE, 0x678cbb8d, 0x19f, 0x4d07, 0x91, 0x2a, 0x73, 0xe2, 0xe5, 0x68, 0xb1, 0x48); // Pool tag for allocations made by this module. // #define DMF_BUSFILTER_POOL_TAG 'tlfB' // Context passed to the passive-level work item used when the bus-relations // completion routine fires at DISPATCH_LEVEL. // typedef struct _BUS_RELATIONS_WORK_CONTEXT { PIO_WORKITEM WorkItem; WDFDEVICE Device; PIRP Irp; } BUS_RELATIONS_WORK_CONTEXT; // Forward declarations // #pragma code_seg("PAGE") _IRQL_requires_max_(PASSIVE_LEVEL) static void DMF_BusFilter_Relations_RemoveDevice( _In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP RemoveIrp ); #pragma code_seg() _IRQL_requires_max_(PASSIVE_LEVEL) static NTSTATUS DMF_BusFilter_Relations_AddDevice( _In_ WDFDEVICE Device, _In_ PDEVICE_OBJECT PhysicalDeviceObject ); /////////////////////////////////////////////////////////////////////////////////////////////////////// // IRP_MN_REMOVE_DEVICE teardown /////////////////////////////////////////////////////////////////////////////////////////////////////// // #pragma code_seg("PAGE") _IRQL_requires_max_(PASSIVE_LEVEL) static void DMF_BusFilter_Relations_RemoveDevice( _In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP RemoveIrp ) /*++ Routine Description: Processes child device removal. Always performs a full teardown regardless of the IsExisting flag so that surprise removals are handled correctly. Mandatory teardown order (WDM filter driver guidance): 1. IoReleaseRemoveLockAndWait -- drain all in-flight I/O holders; after this returns, no new I/O can enter the device. 2. RemoveEntryList -- unlink from the parent's ChildList. 3. EvtDeviceRemove callback -- client notification. 4. WdfObjectDelete -- destroy the WDF wrapper object. 5. IoDetachDevice -- detach filter from the PDO stack. 6. IoDeleteDevice -- lazy-free the filter device object (memory released when the last reference drops; do NOT touch DeviceExtension after this call). Arguments: DeviceObject - Filter device object whose stack is being torn down. RemoveIrp - The IRP_MN_REMOVE_DEVICE IRP; used as the remove-lock tag to match the IoAcquireRemoveLock call made in DispatchHandler. Return Value: None --*/ { KLOCK_QUEUE_HANDLE handle; WDM_CHILD_DEVICE_EXTENSION* extension = (WDM_CHILD_DEVICE_EXTENSION*)DeviceObject->DeviceExtension; PARENT_BUS_DEVICE_CONTEXT* parentContext = DMF_BusFilter_GetParentContext(extension->Parent); const BusFilter_Context* context = BusFilterContextGet(WdfGetDriver()); const DMF_BusFilter_CONFIG* config = &context->Configuration; FuncEntry(DMF_TRACE); PAGED_CODE(); // Step 1: release the remove lock (matching the IoAcquireRemoveLock in // DispatchHandler) AND wait for all other in-flight holders to drain. // After this call, no concurrent dispatcher can be accessing the extension // and no new dispatch will succeed (IoAcquireRemoveLock returns // STATUS_DELETE_PENDING). // IoReleaseRemoveLockAndWait(&extension->RemoveLock, RemoveIrp); TraceVerbose(DMF_TRACE, "%!FUNC! called at %!irql!", KeGetCurrentIrql()); // Step 2: unlink from parent's list while holding the spinlock. // #pragma warning(disable: 28150) // elevates to DISPATCH_LEVEL KeAcquireInStackQueuedSpinLock(&parentContext->ChildListLock, &handle); RemoveEntryList(&extension->ListEntry); KeReleaseInStackQueuedSpinLock(&handle); #pragma warning(default: 28150) // drops to PASSIVE_LEVEL // Steps 3-6: notify client, then destroy objects in the correct order. // if (config->EvtDeviceRemove) { config->EvtDeviceRemove(extension->Parent, extension->Child); } WdfObjectDelete(extension->Child); IoDetachDevice(extension->TargetDeviceObject); IoDeleteDevice(DeviceObject); // Extension is invalid after this point FuncExitNoReturn(DMF_TRACE); } #pragma code_seg() /////////////////////////////////////////////////////////////////////////////////////////////////////// // Child PnP dispatch /////////////////////////////////////////////////////////////////////////////////////////////////////// // _IRQL_requires_max_(DISPATCH_LEVEL) static NTSTATUS DMF_BusFilter_DispatchPnp( _In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp, _In_ UCHAR MinorCode ) /*++ Routine Description: Handles PnP requests for child filter devices. For IRP_MN_REMOVE_DEVICE the remove lock is released inside Relations_RemoveDevice via IoReleaseRemoveLockAndWait. Callers must NOT call IoReleaseRemoveLock again after this function returns for that minor code. Arguments: DeviceObject - Child filter device object. Irp - Irp with PnP request. MinorCode - PnP request minor code. Return Value: NTSTATUS --*/ { NTSTATUS ntStatus; const WDM_CHILD_DEVICE_EXTENSION* extension = (WDM_CHILD_DEVICE_EXTENSION*)DeviceObject->DeviceExtension; const BusFilter_Context* context = BusFilterContextGet(WdfGetDriver()); if (MinorCode == IRP_MN_REMOVE_DEVICE) { // WDM filter driver guidance: forward the IRP to the lower driver FIRST, // then perform our own teardown. Saving TargetDeviceObject to a local // before teardown prevents a use-after-free: Relations_RemoveDevice calls // IoDeleteDevice, making extension memory invalid before we could forward. // // PnP IRPs always arrive at PASSIVE_LEVEL so IoForwardIrpSynchronously // is safe here even though the function is annotated DISPATCH_LEVEL max // for other minor codes. // PDEVICE_OBJECT targetDevice = extension->TargetDeviceObject; if (!IoForwardIrpSynchronously(targetDevice, Irp)) { Irp->IoStatus.Status = STATUS_NO_SUCH_DEVICE; } ntStatus = Irp->IoStatus.Status; IoCompleteRequest(Irp, IO_NO_INCREMENT); // Tear down now that the IRP has been forwarded and completed. // Relations_RemoveDevice calls IoReleaseRemoveLockAndWait, which // releases the lock that was acquired in DispatchHandler for this IRP. // DMF_BusFilter_Relations_RemoveDevice(DeviceObject, Irp); return ntStatus; } if (MinorCode <= IRP_MN_DEVICE_ENUMERATED) { if (context->PnPMinorDispatchFunctions[MinorCode] != NULL) { return context->PnPMinorDispatchFunctions[MinorCode](extension->Child, Irp); } } // Forward to lower driver // IoSkipCurrentIrpStackLocation(Irp); return IoCallDriver(extension->TargetDeviceObject, Irp); } _IRQL_requires_max_(DISPATCH_LEVEL) static NTSTATUS DMF_BusFilter_DispatchHandler( _In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp ) /*++ Routine Description: Dispatch routine handler for all IRPs. For child devices (those tagged with GUID_DMF_BUSFILTER_SIGNATURE), the remove lock is acquired here to serialize all dispatch activity against IRP_MN_REMOVE_DEVICE teardown. Remove-lock release: - IRP_MN_REMOVE_DEVICE: released inside Relations_RemoveDevice via IoReleaseRemoveLockAndWait (do NOT release again here). - All other PnP minors: released after DispatchPnp returns. - Non-PnP IRPs: released BEFORE IoCallDriver (after saving TargetDeviceObject to a local) so the lock does not need to span an asynchronous IRP completion. Power IRP passthrough: IRP_MJ_POWER is forwarded with IoSkip + IoCallDriver without calling PoStartNextPowerIrp. This is correct on the Windows 10+ baseline targeted by this library (PoStartNextPowerIrp is obsolete; the I/O manager handles power IRP serialisation automatically). Arguments: DeviceObject - Target device object. Irp - Irp with request. Return Value: NTSTATUS --*/ { NTSTATUS ntStatus; WDM_CHILD_DEVICE_EXTENSION* extension = (WDM_CHILD_DEVICE_EXTENSION*)DeviceObject->DeviceExtension; const PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp); const BusFilter_Context* context = BusFilterContextGet(WdfGetDriver()); // If the device object does not have our signature it is the WDF FDO (or // another device object owned by this driver). Delegate to the saved handler. // if (!IsEqualGUID(&extension->Signature, &GUID_DMF_BUSFILTER_SIGNATURE)) { return context->MajorDispatchFunctions[stack->MajorFunction](DeviceObject, Irp); } // Acquire the remove lock before touching the extension or forwarding the IRP. // This prevents IRP_MN_REMOVE_DEVICE teardown from deleting the device object // while we are still inside the dispatch routine. // ntStatus = IoAcquireRemoveLock(&extension->RemoveLock, Irp); if (!NT_SUCCESS(ntStatus)) { // Teardown is in progress; reject new I/O. Irp->IoStatus.Status = STATUS_DELETE_PENDING; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_DELETE_PENDING; } if (stack->MajorFunction == IRP_MJ_PNP) { // Capture the minor code BEFORE dispatching. For IRP_MN_REMOVE_DEVICE, // DispatchPnp completes the IRP (invalidating `stack`) and deletes the // device object (invalidating `extension`). Re-reading stack->MinorFunction // afterwards is a use-after-free that can steer us into IoReleaseRemoveLock // on the freed extension. // const UCHAR minorFunction = stack->MinorFunction; ntStatus = DMF_BusFilter_DispatchPnp(DeviceObject, Irp, minorFunction); // For REMOVE_DEVICE, IoReleaseRemoveLockAndWait inside Relations_RemoveDevice // has already released the lock (and waited for all other holders). A second // release here would corrupt the lock count. // if (minorFunction != IRP_MN_REMOVE_DEVICE) { // Safe: this is a non-REMOVE minor, so no teardown path ran, and the // IoAcquireRemoveLock reference taken at function entry is still // outstanding here. That outstanding reference blocks any concurrent // IoReleaseRemoveLockAndWait (in Relations_RemoveDevice / DeviceCleanup) // from completing IoDeleteDevice, so `extension` is guaranteed alive at // the moment we release. We must not touch `extension` after this call. // IoReleaseRemoveLock(&extension->RemoveLock, Irp); } return ntStatus; } // Non-PnP pass-through: save TargetDeviceObject then release the lock before // IoCallDriver so the remove-lock does not span the asynchronous completion. // After the release, IoDeleteDevice may run concurrently but IoCallDriver does // not reference the filter extension -- only the already-saved target pointer. // PDEVICE_OBJECT targetDevice = extension->TargetDeviceObject; IoReleaseRemoveLock(&extension->RemoveLock, Irp); IoSkipCurrentIrpStackLocation(Irp); return IoCallDriver(targetDevice, Irp); } /////////////////////////////////////////////////////////////////////////////////////////////////////// // Child device creation /////////////////////////////////////////////////////////////////////////////////////////////////////// // // IoCreateDevice and IoAttachDeviceToDeviceStackSafe both require PASSIVE_LEVEL. // The annotation is corrected from the original APC_LEVEL. // Note: this function is NOT in the PAGE segment because it acquires a spinlock // (which momentarily elevates to DISPATCH_LEVEL), making paged memory unsafe. // _IRQL_requires_max_(PASSIVE_LEVEL) static NTSTATUS DMF_BusFilter_Relations_AddDevice( _In_ WDFDEVICE Device, _In_ PDEVICE_OBJECT PhysicalDeviceObject ) /*++ Routine Description: Creates a proxy filter device object for a bus PDO. Attach window: bus-relations completion time (i.e. after the lower bus driver has populated DEVICE_RELATIONS but before the PnP manager has dispatched IRP_MN_START_DEVICE to the child devnode's FDO) is the only safe window for a bus filter to attach. Attaching earlier races the PDO creation; attaching later races FDO construction and IRP dispatch. STATUS_MORE_PROCESSING_REQUIRED in the completion routine (which calls this function) keeps the DEVICE_RELATIONS array -- and therefore the bus driver's references on each PDO -- alive until we finish attaching. Without that hold, the PDO could be released and deleted while IoAttachDeviceToDeviceStackSafe is executing. Arguments: Device - Parent WDF bus filter device. PhysicalDeviceObject - Bus PDO to filter. Return Value: NTSTATUS --*/ { NTSTATUS ntStatus = STATUS_NOT_IMPLEMENTED; WDF_OBJECT_ATTRIBUTES attributes; PLIST_ENTRY entry = NULL; KLOCK_QUEUE_HANDLE handle; WDM_CHILD_DEVICE_EXTENSION* childExtension = NULL; PDEVICE_OBJECT filterDeviceObject = NULL; BOOLEAN preexisting = FALSE; DMFBUSCHILDDEVICE child = NULL; BUS_CHILD_DEVICE_CONTEXT* childContext = NULL; PARENT_BUS_DEVICE_CONTEXT* parentContext = DMF_BusFilter_GetParentContext(Device); const PDEVICE_OBJECT deviceObject = WdfDeviceWdmGetDeviceObject(Device); const BusFilter_Context* context = BusFilterContextGet(WdfGetDriver()); const DMF_BusFilter_CONFIG* config = &context->Configuration; FuncEntry(DMF_TRACE); if (parentContext == NULL) { ntStatus = STATUS_INVALID_DEVICE_STATE; goto Exit; } KeAcquireInStackQueuedSpinLock(&parentContext->ChildListLock, &handle); // Find and update PDO status // for ( entry = parentContext->ChildList.Flink; entry != &parentContext->ChildList; entry = entry->Flink ) { childExtension = CONTAINING_RECORD(entry, WDM_CHILD_DEVICE_EXTENSION, ListEntry); if (childExtension->PhysicalDeviceObject == PhysicalDeviceObject) { preexisting = TRUE; childExtension->IsExisting = TRUE; break; } } KeReleaseInStackQueuedSpinLock(&handle); if (preexisting) { ntStatus = STATUS_SUCCESS; goto Exit; } WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, BUS_CHILD_DEVICE_CONTEXT); attributes.ParentObject = Device; // Create piggyback framework object for WDM child device object // ntStatus = WdfObjectCreate(&attributes, (WDFOBJECT*)&child); if (!NT_SUCCESS(ntStatus)) { TraceError(DMF_TRACE, "WdfObjectCreate fails: ntStatus=%!STATUS!", ntStatus); goto Exit; } // Derive the device type from the PDO when the client did not specify one. // A filter whose device type differs from the stack it joins can cause // I/O manager policy mismatches (e.g. buffering mode checks). // const DEVICE_TYPE deviceType = (config->DeviceType != 0) ? config->DeviceType : PhysicalDeviceObject->DeviceType; // Create WDM device // ntStatus = IoCreateDevice(deviceObject->DriverObject, sizeof(WDM_CHILD_DEVICE_EXTENSION), NULL, deviceType, FILE_DEVICE_SECURE_OPEN | config->DeviceCharacteristics, FALSE, &filterDeviceObject); if (!NT_SUCCESS(ntStatus)) { TraceError(DMF_TRACE, "IoCreateDevice fails: ntStatus=%!STATUS!", ntStatus); goto Exit; } // Link WDM and WDF device together. // childContext = DMF_BusFilter_GetChildContext(child); childContext->DeviceObject = filterDeviceObject; childExtension = (WDM_CHILD_DEVICE_EXTENSION*)filterDeviceObject->DeviceExtension; RtlZeroMemory(childExtension, sizeof(WDM_CHILD_DEVICE_EXTENSION)); RtlCopyMemory(&childExtension->Signature, &GUID_DMF_BUSFILTER_SIGNATURE, sizeof(GUID)); // Initialise the remove lock before the device is attached to any stack // or its DO_DEVICE_INITIALIZING flag is cleared. The lock must be ready // before the first possible IoAcquireRemoveLock call. // IoInitializeRemoveLock(&childExtension->RemoveLock, DMF_BUSFILTER_POOL_TAG, 0, // MaxLockedMinutes (0 = no timeout in free builds) 0); // HighWatermark (0 = no per-lock tag tracking) childExtension->Parent = Device; childExtension->Child = child; childExtension->PhysicalDeviceObject = PhysicalDeviceObject; // Use the safe variant to avoid a stale-pointer race: IoAttachDeviceToDeviceStack // (non-safe) can return a pointer to a device that is concurrently being deleted. // IoAttachDeviceToDeviceStackSafe returns an NTSTATUS and a valid target pointer // only if the target is still alive when the attach completes. // ntStatus = IoAttachDeviceToDeviceStackSafe(filterDeviceObject, PhysicalDeviceObject, &childExtension->TargetDeviceObject); if (!NT_SUCCESS(ntStatus)) { TraceError(DMF_TRACE, "IoAttachDeviceToDeviceStackSafe fails: ntStatus=%!STATUS!", ntStatus); IoDeleteDevice(filterDeviceObject); filterDeviceObject = NULL; goto Exit; } filterDeviceObject->Flags |= childExtension->TargetDeviceObject->Flags & (DO_BUFFERED_IO | DO_DIRECT_IO | DO_POWER_INRUSH | DO_POWER_PAGABLE); if (config->EvtDeviceAdd) { ntStatus = config->EvtDeviceAdd(Device, child); if (!NT_SUCCESS(ntStatus)) { TraceError(DMF_TRACE, "EvtDeviceAdd fails: ntStatus=%!STATUS!", ntStatus); IoDetachDevice(childExtension->TargetDeviceObject); IoDeleteDevice(filterDeviceObject); filterDeviceObject = NULL; goto Exit; } } KeAcquireInStackQueuedSpinLock(&parentContext->ChildListLock, &handle); // Re-scan under the lock before inserting to guard against a duplicate created // by a concurrent bus-relations completion. // BOOLEAN duplicate = FALSE; for ( PLIST_ENTRY scanEntry = parentContext->ChildList.Flink; scanEntry != &parentContext->ChildList; scanEntry = scanEntry->Flink ) { WDM_CHILD_DEVICE_EXTENSION* scanExt = CONTAINING_RECORD(scanEntry, WDM_CHILD_DEVICE_EXTENSION, ListEntry); if (scanExt->PhysicalDeviceObject == PhysicalDeviceObject) { duplicate = TRUE; break; } } if (!duplicate) { childExtension->IsExisting = TRUE; InsertTailList(&parentContext->ChildList, &childExtension->ListEntry); } KeReleaseInStackQueuedSpinLock(&handle); if (duplicate) { // A concurrent AddDevice beat us to it; discard our newly created device. // IoDetachDevice(childExtension->TargetDeviceObject); IoDeleteDevice(filterDeviceObject); filterDeviceObject = NULL; WdfObjectDelete(child); child = NULL; ntStatus = STATUS_SUCCESS; goto Exit; } // The device is fully initialised; clear the initialising flag to allow // IRPs to be dispatched to it. // filterDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING; ntStatus = STATUS_SUCCESS; Exit: if (!NT_SUCCESS(ntStatus) && child != NULL) { WdfObjectDelete(child); } FuncExit(DMF_TRACE, "status=%!STATUS!", ntStatus); return ntStatus; } /////////////////////////////////////////////////////////////////////////////////////////////////////// // Bus-relations completion: passive-level processing /////////////////////////////////////////////////////////////////////////////////////////////////////// // // Helper that resets IsExisting flags and calls AddDevice for every PDO in the // DEVICE_RELATIONS returned by the lower bus driver. Must be called at PASSIVE_LEVEL // because DMF_BusFilter_Relations_AddDevice requires it. // #pragma code_seg("PAGE") _IRQL_requires_max_(PASSIVE_LEVEL) static void DMF_BusFilter_ProcessBusRelations( _In_ WDFDEVICE Device, _In_ PIRP Irp ) { KLOCK_QUEUE_HANDLE handle; WDM_CHILD_DEVICE_EXTENSION* childExtension = NULL; PARENT_BUS_DEVICE_CONTEXT* parentContext = DMF_BusFilter_GetParentContext(Device); PAGED_CODE(); if (parentContext == NULL) { return; } #pragma warning(disable: 28150) KeAcquireInStackQueuedSpinLock(&parentContext->ChildListLock, &handle); // Reset child states so stale entries can be detected later // for ( LIST_ENTRY* entry = parentContext->ChildList.Flink; entry != &parentContext->ChildList; entry = entry->Flink ) { childExtension = CONTAINING_RECORD(entry, WDM_CHILD_DEVICE_EXTENSION, ListEntry); childExtension->IsExisting = FALSE; } KeReleaseInStackQueuedSpinLock(&handle); #pragma warning(default: 28150) const PDEVICE_RELATIONS deviceRelations = (PDEVICE_RELATIONS)Irp->IoStatus.Information; if (deviceRelations == NULL) { return; } for (ULONG index = 0; index < deviceRelations->Count; index++) { TraceVerbose(DMF_TRACE, "%!FUNC! called at %!irql!", KeGetCurrentIrql()); NTSTATUS ntStatus = DMF_BusFilter_Relations_AddDevice(Device, deviceRelations->Objects[index]); if (!NT_SUCCESS(ntStatus)) { TraceError(DMF_TRACE, "DMF_BusFilter_Relations_AddDevice fails: ntStatus=%!STATUS!", ntStatus); } } } #pragma code_seg() // Work item routine: runs at PASSIVE_LEVEL when the completion routine fires at // DISPATCH_LEVEL and cannot call IoCreateDevice / IoAttachDeviceToDeviceStackSafe // directly. // static IO_WORKITEM_ROUTINE DMF_BusFilter_QueryBusRelationsWorker; static void DMF_BusFilter_QueryBusRelationsWorker( _In_ PDEVICE_OBJECT DeviceObject, _In_opt_ PVOID Context ) { UNREFERENCED_PARAMETER(DeviceObject); BUS_RELATIONS_WORK_CONTEXT* workCtx = (BUS_RELATIONS_WORK_CONTEXT*)Context; if (workCtx == NULL) { return; } PIRP irp = workCtx->Irp; WDFDEVICE device = workCtx->Device; DMF_BusFilter_ProcessBusRelations(device, irp); // Signal the idle event if this was the last outstanding work item, so that // DeviceCleanup can proceed once all workers have finished. // PARENT_BUS_DEVICE_CONTEXT* parentContext = DMF_BusFilter_GetParentContext(device); if (parentContext != NULL) { if (InterlockedDecrement(&parentContext->OutstandingWorkItems) == 0) { KeSetEvent(&parentContext->WorkItemsIdle, IO_NO_INCREMENT, FALSE); } } IoFreeWorkItem(workCtx->WorkItem); ExFreePoolWithTag(workCtx, DMF_BUSFILTER_POOL_TAG); // Resume IRP completion propagation upward through the stack. The completion // routine returned STATUS_MORE_PROCESSING_REQUIRED which halted it at our level. // IoCompleteRequest(irp, IO_NO_INCREMENT); } // Completion routine for IRP_MN_QUERY_DEVICE_RELATIONS / BusRelations. // // This routine is intentionally NOT in the PAGE segment and does NOT call // PAGED_CODE() because IO completion routines may run at DISPATCH_LEVEL. // When that happens the passive-level AddDevice work is deferred to a work item. // // Returning STATUS_MORE_PROCESSING_REQUIRED to hold the IRP is intentional and // load-bearing: it keeps the DEVICE_RELATIONS array (and the bus driver's // references on each PDO) alive until the passive-level worker has finished // attaching filter devices. Without this hold the PDOs could be released before // IoAttachDeviceToDeviceStackSafe completes. // _IRQL_requires_max_(DISPATCH_LEVEL) static NTSTATUS DMF_BusFilter_QueryBusRelationsCompleted( _In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp, _In_ WDFDEVICE Device ) /*++ Routine Description: Bus relations query completion routine. Arguments: DeviceObject - Parent device object (unused). Irp - Query Bus Relations IRP. Device - Target WDFDEVICE. Return Value: STATUS_CONTINUE_COMPLETION or STATUS_MORE_PROCESSING_REQUIRED --*/ { UNREFERENCED_PARAMETER(DeviceObject); FuncEntry(DMF_TRACE); if (Irp->PendingReturned) { IoMarkIrpPending(Irp); } if (!NT_SUCCESS(Irp->IoStatus.Status)) { goto Exit; } // If completion is running at DISPATCH_LEVEL, IoCreateDevice and // IoAttachDeviceToDeviceStackSafe cannot be called safely. Defer to a // passive-level work item. // if (KeGetCurrentIrql() > PASSIVE_LEVEL) { BUS_RELATIONS_WORK_CONTEXT* workCtx = (BUS_RELATIONS_WORK_CONTEXT*)ExAllocatePoolWithTag(NonPagedPool, sizeof(BUS_RELATIONS_WORK_CONTEXT), DMF_BUSFILTER_POOL_TAG); if (workCtx != NULL) { workCtx->Device = Device; workCtx->Irp = Irp; workCtx->WorkItem = IoAllocateWorkItem(WdfDeviceWdmGetDeviceObject(Device)); if (workCtx->WorkItem != NULL) { // Increment the outstanding-work counter BEFORE queuing. // The worker decrements it and signals the idle event when done. // This prevents DeviceCleanup from racing the worker. // PARENT_BUS_DEVICE_CONTEXT* parentContext = DMF_BusFilter_GetParentContext(Device); if (parentContext != NULL) { InterlockedIncrement(&parentContext->OutstandingWorkItems); KeClearEvent(&parentContext->WorkItemsIdle); } IoQueueWorkItem(workCtx->WorkItem, DMF_BusFilter_QueryBusRelationsWorker, DelayedWorkQueue, workCtx); FuncExitNoReturn(DMF_TRACE); return STATUS_MORE_PROCESSING_REQUIRED; } ExFreePoolWithTag(workCtx, DMF_BUSFILTER_POOL_TAG); } TraceError(DMF_TRACE, "Failed to allocate work item for bus relations at DISPATCH_LEVEL; skipping child creation"); goto Exit; } // PASSIVE_LEVEL path: process child devices inline. // DMF_BusFilter_ProcessBusRelations(Device, Irp); Exit: FuncExitNoReturn(DMF_TRACE); return STATUS_CONTINUE_COMPLETION; } _IRQL_requires_max_(DISPATCH_LEVEL) static NTSTATUS DMF_BusFilter_PreprocessQueryBusRelations( _In_ WDFDEVICE Device, _In_ PIRP Irp ) /*++ Routine Description: Pre-processes IRP_MJ_PNP / IRP_MN_QUERY_DEVICE_RELATIONS. Arguments: Device - Parent WDF device. Irp - IRP_MJ_PNP / IRP_MN_QUERY_DEVICE_RELATIONS request. Return Value: NTSTATUS --*/ { const PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp); if ( stack->MajorFunction != IRP_MJ_PNP || stack->MinorFunction != IRP_MN_QUERY_DEVICE_RELATIONS || stack->Parameters.QueryDeviceRelations.Type != BusRelations ) { IoSkipCurrentIrpStackLocation(Irp); } else { IoCopyCurrentIrpStackLocationToNext(Irp); IoSetCompletionRoutine(Irp, (PIO_COMPLETION_ROUTINE)DMF_BusFilter_QueryBusRelationsCompleted, Device, TRUE, TRUE, TRUE); } return WdfDeviceWdmDispatchPreprocessedIrp(Device, Irp); } /////////////////////////////////////////////////////////////////////////////////////////////////////// // PnP minor-code handlers /////////////////////////////////////////////////////////////////////////////////////////////////////// // #pragma code_seg("PAGE") static NTSTATUS DMF_BusFilter_PnP_StartDevice( _In_ DMFBUSCHILDDEVICE ChildDevice, _In_ PIRP Irp ) /*++ Routine Description: Handles IRP_MN_START_DEVICE Arguments: ChildDevice - Associated child device. Irp - IRP_MJ_PNP / IRP_MN_START_DEVICE request. Return Value: NTSTATUS --*/ { NTSTATUS ntStatus; const BusFilter_Context* context = BusFilterContextGet(WdfGetDriver()); const DMF_BusFilter_CONFIG* config = &context->Configuration; FuncEntry(DMF_TRACE); PAGED_CODE(); if (!IoForwardIrpSynchronously(DMF_BusFilter_WdmAttachedDeviceGet(ChildDevice), Irp)) { TraceError(DMF_TRACE, "IoForwardIrpSynchronously fails: Irp=0x%p", Irp); Irp->IoStatus.Status = STATUS_NO_SUCH_DEVICE; } else if (NT_SUCCESS(Irp->IoStatus.Status) && config->EvtDeviceStarted) { config->EvtDeviceStarted(ChildDevice, Irp); } ntStatus = Irp->IoStatus.Status; IoCompleteRequest(Irp, IO_NO_INCREMENT); FuncExit(DMF_TRACE, "ntStatus=%!STATUS!", ntStatus); return ntStatus; } #pragma code_seg() static NTSTATUS DMF_BusFilter_PnP_DeviceEnumerated( _In_ DMFBUSCHILDDEVICE ChildDevice, _In_ PIRP Irp ) /*++ Routine Description: Handles IRP_MN_DEVICE_ENUMERATED. Arguments: ChildDevice - Associated child device. Irp - IRP_MJ_PNP / IRP_MN_DEVICE_ENUMERATED request. Return Value: NTSTATUS --*/ { NTSTATUS ntStatus; const BusFilter_Context* context = BusFilterContextGet(WdfGetDriver()); const DMF_BusFilter_CONFIG* config = &context->Configuration; FuncEntry(DMF_TRACE); if (config->EvtDeviceEnumerated) { config->EvtDeviceEnumerated(ChildDevice, Irp); } // Forward to the parent bus driver // IoSkipCurrentIrpStackLocation(Irp); ntStatus = IoCallDriver(DMF_BusFilter_WdmAttachedDeviceGet(ChildDevice), Irp); FuncExit(DMF_TRACE, "ntStatus=%!STATUS!", ntStatus); return ntStatus; } static NTSTATUS DMF_BusFilter_PnP_QueryId( _In_ DMFBUSCHILDDEVICE ChildDevice, _In_ PIRP Irp ) /*++ Routine Description: Handles IRP_MN_QUERY_ID. Arguments: ChildDevice - Associated child device. Irp - IRP_MJ_PNP / IRP_MN_QUERY_ID request. Return Value: NTSTATUS --*/ { NTSTATUS ntStatus; const BusFilter_Context* context = BusFilterContextGet(WdfGetDriver()); const DMF_BusFilter_CONFIG* config = &context->Configuration; FuncEntry(DMF_TRACE); // Forward immediately if client driver has no handler // if (config->EvtDeviceQueryId == NULL) { IoSkipCurrentIrpStackLocation(Irp); ntStatus = IoCallDriver(DMF_BusFilter_WdmAttachedDeviceGet(ChildDevice), Irp); FuncExit(DMF_TRACE, "status=%!STATUS!", ntStatus); return ntStatus; } // If client driver didn't do anything with the IRP... // if (!config->EvtDeviceQueryId(ChildDevice, Irp)) { // ...forward it prior to completion // if (!IoForwardIrpSynchronously(DMF_BusFilter_WdmAttachedDeviceGet(ChildDevice), Irp)) { TraceError(DMF_TRACE, "IoForwardIrpSynchronously fails: Irp=0x%p", Irp); Irp->IoStatus.Status = STATUS_NO_SUCH_DEVICE; } } // Complete the Irp. // ntStatus = Irp->IoStatus.Status; IoCompleteRequest(Irp, IO_NO_INCREMENT); FuncExit(DMF_TRACE, "status=%!STATUS!", ntStatus); return ntStatus; } static NTSTATUS DMF_BusFilter_PnP_QueryInterface( _In_ DMFBUSCHILDDEVICE ChildDevice, _In_ PIRP Irp ) /*++ Routine Description: Handles IRP_MN_QUERY_INTERFACE. Arguments: ChildDevice - Associated child device. Irp - IRP_MJ_PNP / IRP_MN_QUERY_INTERFACE request. Return Value: NTSTATUS --*/ { NTSTATUS ntStatus; const BusFilter_Context* context = BusFilterContextGet(WdfGetDriver()); const DMF_BusFilter_CONFIG* config = &context->Configuration; FuncEntry(DMF_TRACE); // Forward immediately if client driver has no handler // if (config->EvtDeviceQueryInterface == NULL) { IoSkipCurrentIrpStackLocation(Irp); ntStatus = IoCallDriver(DMF_BusFilter_WdmAttachedDeviceGet(ChildDevice), Irp); FuncExit(DMF_TRACE, "status=%!STATUS!", ntStatus); return ntStatus; } // If client driver didn't do anything with the IRP... // if (!config->EvtDeviceQueryInterface(ChildDevice, Irp)) { // ...forward it prior to completion // if (!IoForwardIrpSynchronously(DMF_BusFilter_WdmAttachedDeviceGet(ChildDevice), Irp)) { TraceError(DMF_TRACE, "IoForwardIrpSynchronously fails: Irp=0x%p", Irp); Irp->IoStatus.Status = STATUS_NO_SUCH_DEVICE; } } // Complete the Irp // ntStatus = Irp->IoStatus.Status; IoCompleteRequest(Irp, IO_NO_INCREMENT); FuncExit(DMF_TRACE, "status=%!STATUS!", ntStatus); return ntStatus; } /////////////////////////////////////////////////////////////////////////////////////////////////////// // BusFilter Public Calls by Client // #pragma warning(disable:4995) #pragma code_seg("PAGE") _IRQL_requires_max_(PASSIVE_LEVEL) _Must_inspect_result_ NTSTATUS DMF_BusFilter_Initialize( _In_ DMF_BusFilter_CONFIG* BusFilterConfig ) /*++ Routine Description: Called by Client Driver to initialize DMF BusFilter operations from DriverEntry(). Arguments: BusFilterConfig - Client Driver configuration parameters. Return Value: NTSTATUS --*/ { NTSTATUS ntStatus; WDF_OBJECT_ATTRIBUTES attributes; BusFilter_Context* contextBusFilter; FuncEntry(DMF_TRACE); PAGED_CODE(); contextBusFilter = NULL; // Config is required // if (BusFilterConfig == NULL) { ntStatus = STATUS_INVALID_PARAMETER; goto Exit; } // Driver object must be already created // if (!WdfGetDriver()) { ntStatus = STATUS_NOT_SUPPORTED; goto Exit; } if (!BusFilterConfig->DriverObject) { ntStatus = STATUS_INVALID_PARAMETER; goto Exit; } WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, BusFilter_Context); // Attach context to driver object. // ntStatus = WdfObjectAllocateContext(WdfGetDriver(), &attributes, (void**)&contextBusFilter); if (!NT_SUCCESS(ntStatus)) { TraceError(DMF_TRACE, "WdfObjectAllocateContext fails: ntStatus=%!STATUS!", ntStatus); goto Exit; } // Save copy of config in context to invoke callback routines later. // RtlCopyMemory(&contextBusFilter->Configuration, BusFilterConfig, sizeof(DMF_BusFilter_CONFIG)); ULONG index; PDRIVER_DISPATCH* pDispatch; // Store original dispatch routine pointers and overwrite with our own // #pragma warning(disable:28175) for ( index = 0, pDispatch = BusFilterConfig->DriverObject->MajorFunction; index <= IRP_MJ_MAXIMUM_FUNCTION; index++, pDispatch++ ) { contextBusFilter->MajorDispatchFunctions[index] = *pDispatch; *pDispatch = DMF_BusFilter_DispatchHandler; } #pragma warning(default:28175) // PnP minor code dispatch routines // contextBusFilter->PnPMinorDispatchFunctions[IRP_MN_START_DEVICE] = DMF_BusFilter_PnP_StartDevice; contextBusFilter->PnPMinorDispatchFunctions[IRP_MN_DEVICE_ENUMERATED] = DMF_BusFilter_PnP_DeviceEnumerated; contextBusFilter->PnPMinorDispatchFunctions[IRP_MN_QUERY_ID] = DMF_BusFilter_PnP_QueryId; contextBusFilter->PnPMinorDispatchFunctions[IRP_MN_QUERY_INTERFACE] = DMF_BusFilter_PnP_QueryInterface; // Clear invalid characteristics (see MS docs) // BusFilterConfig->DeviceCharacteristics &= ~(FILE_AUTOGENERATED_DEVICE_NAME | FILE_CHARACTERISTIC_TS_DEVICE | FILE_CHARACTERISTIC_WEBDAV_DEVICE | FILE_DEVICE_IS_MOUNTED | FILE_VIRTUAL_VOLUME); ntStatus = STATUS_SUCCESS; Exit: FuncExit(DMF_TRACE, "status=%!STATUS!", ntStatus); return ntStatus; } #pragma code_seg() #pragma warning(default:4995) #pragma code_seg("PAGE") NTSTATUS DMF_BusFilter_DeviceAdd( _In_ WDFDRIVER Driver, _Inout_ PWDFDEVICE_INIT DeviceInit ) /*++ Routine Description: Creates bus WDF device. Arguments: Driver - Associated WDFDRIVER. DeviceInit - WDF PWDFDEVICE_INIT. Return Value: NTSTATUS --*/ { NTSTATUS ntStatus = STATUS_NOT_IMPLEMENTED; WDF_OBJECT_ATTRIBUTES attributes; WDFDEVICE device = NULL; UCHAR minorPnP = IRP_MN_QUERY_DEVICE_RELATIONS; PDMFDEVICE_INIT dmfDeviceInit = NULL; FuncEntry(DMF_TRACE); PAGED_CODE(); // Guard against DMF_BusFilter_Initialize not having been called. // const BusFilter_Context* context = BusFilterContextGet(Driver); if (context == NULL) { TraceError(DMF_TRACE, "BusFilterContextGet returns NULL: was DMF_BusFilter_Initialize called?"); ntStatus = STATUS_INVALID_DEVICE_STATE; goto Exit; } const DMF_BusFilter_CONFIG* config = &context->Configuration; WdfFdoInitSetFilter(DeviceInit); // Attach IRP preprocessor. // ntStatus = WdfDeviceInitAssignWdmIrpPreprocessCallback(DeviceInit, DMF_BusFilter_PreprocessQueryBusRelations, IRP_MJ_PNP, &minorPnP, 1); if (!NT_SUCCESS(ntStatus)) { TraceError(DMF_TRACE, "WdfDeviceInitAssignWdmIrpPreprocessCallback fails: ntStatus=%!STATUS!", ntStatus); goto Exit; } // Don't initialize with context here as client driver might decide // to set their own context memory in EvtPreBusDeviceAdd // WDF_OBJECT_ATTRIBUTES_INIT(&attributes); // Call pre-device-creation callback, if set. // if (config->EvtPreBusDeviceAdd) { ntStatus = config->EvtPreBusDeviceAdd(Driver, DeviceInit, &attributes, &dmfDeviceInit); if (!NT_SUCCESS(ntStatus)) { TraceError(DMF_TRACE, "EvtPreBusDeviceAdd fails: ntStatus=%!STATUS!", ntStatus); goto Exit; } } // Client driver is using DMF modules. // if (dmfDeviceInit != NULL) { DMF_DmfFdoSetFilter(dmfDeviceInit); } // Create device object // ntStatus = WdfDeviceCreate(&DeviceInit, &attributes, &device); if (!NT_SUCCESS(ntStatus)) { TraceError(DMF_TRACE, "WdfDeviceCreate fails: ntStatus=%!STATUS!", ntStatus); goto Exit; } PARENT_BUS_DEVICE_CONTEXT* parentContext = NULL; WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, PARENT_BUS_DEVICE_CONTEXT); // Add bus device context. // ntStatus = WdfObjectAllocateContext(device, &attributes, (void**)&parentContext); if (!NT_SUCCESS(ntStatus)) { TraceError(DMF_TRACE, "WdfObjectAllocateContext fails: ntStatus=%!STATUS!", ntStatus); goto Exit; } InitializeListHead(&parentContext->ChildList); KeInitializeSpinLock(&parentContext->ChildListLock); // Initialise work-item rundown state. OutstandingWorkItems starts at zero // and WorkItemsIdle starts signaled (no workers pending). // parentContext->OutstandingWorkItems = 0; KeInitializeEvent(&parentContext->WorkItemsIdle, NotificationEvent, TRUE); // Call post-device-creation callback, if set. // if (config->EvtPostBusDeviceAdd) { ntStatus = config->EvtPostBusDeviceAdd(device, dmfDeviceInit); if (!NT_SUCCESS(ntStatus)) { TraceError(DMF_TRACE, "EvtPostBusDeviceAdd fails: ntStatus=%!STATUS!", ntStatus); goto Exit; } } Exit: if (!NT_SUCCESS(ntStatus) && dmfDeviceInit != NULL) { DMF_DmfDeviceInitFree(&dmfDeviceInit); } if (!NT_SUCCESS(ntStatus) && device != NULL) { WdfObjectDelete(device); } FuncExit(DMF_TRACE, "status=%!STATUS!", ntStatus); return ntStatus; } #pragma code_seg() PDEVICE_OBJECT DMF_BusFilter_WdmDeviceObjectGet( _In_ DMFBUSCHILDDEVICE ChildDevice ) /*++ Routine Description: Returns DEVICE_OBJECT associated with a given DMFBUSCHILDDEVICE. Arguments: ChildDevice - The given DMFBUSCHILDDEVICE. Return Value: The associated DEVICE_OBJECT. --*/ { const BUS_CHILD_DEVICE_CONTEXT* childContext = DMF_BusFilter_GetChildContext(ChildDevice); if (childContext) { return childContext->DeviceObject; } return NULL; } PDEVICE_OBJECT DMF_BusFilter_WdmAttachedDeviceGet( _In_ DMFBUSCHILDDEVICE ChildDevice ) /*++ Routine Description: Returns the attached DEVICE_OBJECT associated with a given DMFBUSCHILDDEVICE. Arguments: ChildDevice - The given DMFBUSCHILDDEVICE. Return Value: The attached DEVICE_OBJECT. --*/ { const BUS_CHILD_DEVICE_CONTEXT* childContext = DMF_BusFilter_GetChildContext(ChildDevice); if (childContext) { const WDM_CHILD_DEVICE_EXTENSION* childExtension = (WDM_CHILD_DEVICE_EXTENSION*)childContext->DeviceObject->DeviceExtension; if (IsEqualGUID(&childExtension->Signature, &GUID_DMF_BUSFILTER_SIGNATURE)) { return childExtension->TargetDeviceObject; } } return NULL; } PDEVICE_OBJECT DMF_BusFilter_WdmPhysicalDeviceGet( _In_ DMFBUSCHILDDEVICE ChildDevice ) /*++ Routine Description: Returns the associated physical DEVICE_OBJECT associated with a given DMFBUSCHILDDEVICE. Arguments: ChildDevice - The given DMFBUSCHILDDEVICE. Return Value: The associated physical (parent) DEVICE_OBJECT. --*/ { const BUS_CHILD_DEVICE_CONTEXT* childContext = DMF_BusFilter_GetChildContext(ChildDevice); if (childContext) { const WDM_CHILD_DEVICE_EXTENSION* childExtension = (WDM_CHILD_DEVICE_EXTENSION*)childContext->DeviceObject->DeviceExtension; if (IsEqualGUID(&childExtension->Signature, &GUID_DMF_BUSFILTER_SIGNATURE)) { return childExtension->PhysicalDeviceObject; } } return NULL; } #pragma code_seg("PAGE") _IRQL_requires_max_(PASSIVE_LEVEL) VOID DMF_BusFilter_DeviceCleanup( _In_ WDFDEVICE Device ) /*++ Routine Description: Tears down all remaining child filter devices associated with a bus device. This function must be called from the client driver's EvtDeviceReleaseHardware or EvtCleanupCallback AFTER PnP has already dispatched IRP_MN_REMOVE_DEVICE to all child devnodes. Under normal operation the ChildList is empty by the time this function runs (each REMOVE_DEVICE already drained it). This function acts as a safety net for abnormal paths (e.g. parent surprise-removed before all children received REMOVE). For each remaining child: - Acquires the per-child remove lock (prevents new I/O from entering). - Calls IoReleaseRemoveLockAndWait to drain any in-flight I/O. - Detaches and deletes the filter device in the mandatory order. If IoAcquireRemoveLock returns STATUS_DELETE_PENDING for a child, it means an IRP_MN_REMOVE_DEVICE is concurrently processing that child; this function logs the anomaly and skips it (the REMOVE path owns teardown in that case). Arguments: Device - The bus WDF device whose children should be cleaned up. Return Value: None --*/ { KLOCK_QUEUE_HANDLE handle; PARENT_BUS_DEVICE_CONTEXT* parentContext = DMF_BusFilter_GetParentContext(Device); const BusFilter_Context* context = BusFilterContextGet(WdfGetDriver()); FuncEntry(DMF_TRACE); PAGED_CODE(); if (parentContext == NULL || context == NULL) { goto Exit; } // Wait for any bus-relations work items that are still executing to finish // before we start tearing down ChildList and its entries. Without this wait, // a passive-level worker could be calling Relations_AddDevice (touching the // list and child extensions) while we are freeing them. // KeWaitForSingleObject(&parentContext->WorkItemsIdle, Executive, KernelMode, FALSE, NULL); // Drain ChildList. The lock is released around per-child teardown because // IoReleaseRemoveLockAndWait, IoDetachDevice, and IoDeleteDevice cannot be // called while holding a spinlock. // #pragma warning(disable: 28150) KeAcquireInStackQueuedSpinLock(&parentContext->ChildListLock, &handle); while (!IsListEmpty(&parentContext->ChildList)) { // Peek at the head of the list without removing it until we know // we can safely take ownership of this child. // LIST_ENTRY* entry = parentContext->ChildList.Flink; WDM_CHILD_DEVICE_EXTENSION* childExtension = CONTAINING_RECORD(entry, WDM_CHILD_DEVICE_EXTENSION, ListEntry); PDEVICE_OBJECT filterDevice = DMF_BusFilter_WdmDeviceObjectGet(childExtension->Child); // Try to acquire the remove lock while still holding the spinlock. // IoAcquireRemoveLock is callable at DISPATCH_LEVEL. // NTSTATUS lockStatus = IoAcquireRemoveLock(&childExtension->RemoveLock, filterDevice); if (!NT_SUCCESS(lockStatus)) { // An IRP_MN_REMOVE_DEVICE is already processing this child. That path // will call RemoveEntryList under the spinlock, so we must not touch // this entry. Remove it from the list now to avoid looping forever, // and let the REMOVE path complete teardown on its own. // TraceWarning(DMF_TRACE, "%!FUNC! child device already removing (concurrent REMOVE_DEVICE)"); RemoveEntryList(entry); continue; // re-check IsListEmpty with lock still held } // We successfully acquired the lock; take the entry out of the list. // RemoveEntryList(entry); KeReleaseInStackQueuedSpinLock(&handle); // Save locals before any pointer might become invalid. // PDEVICE_OBJECT targetDevice = childExtension->TargetDeviceObject; DMFBUSCHILDDEVICE child = childExtension->Child; WDFDEVICE parent = childExtension->Parent; // Drain in-flight I/O: IoReleaseRemoveLockAndWait marks the lock // "removing" (so new IoAcquireRemoveLock calls fail), releases the // acquire we just made AND the implicit initial reference, then waits // for the count to reach zero. // IoReleaseRemoveLockAndWait(&childExtension->RemoveLock, filterDevice); // Notify client, then tear down in mandatory order. // if (context->Configuration.EvtDeviceRemove) { context->Configuration.EvtDeviceRemove(parent, child); } WdfObjectDelete(child); IoDetachDevice(targetDevice); if (filterDevice != NULL) { IoDeleteDevice(filterDevice); // Extension invalid after this } KeAcquireInStackQueuedSpinLock(&parentContext->ChildListLock, &handle); } KeReleaseInStackQueuedSpinLock(&handle); #pragma warning(default: 28150) Exit: FuncExitNoReturn(DMF_TRACE); } #pragma code_seg() #endif // defined(DMF_KERNEL_MODE)