1
0
Files
DmfBusFilterExtension/src/Dmf_BusFilter.c
T
2026-07-03 19:27:44 +02:00

1766 lines
52 KiB
C

/*++
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 <initguid.h>
#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)