1
0

Moved bus device clean-up internal and automatic

This commit is contained in:
2026-07-04 22:45:43 +02:00
parent 861813776b
commit 53a210e011
4 changed files with 81 additions and 122 deletions
+1
View File
@@ -398,3 +398,4 @@ FodyWeavers.xsd
# JetBrains Rider
*.sln.iml
/.dual-graph
+58 -8
View File
@@ -40,7 +40,7 @@ Environment:
// --------------
// Each WDM filter device object created by this library lives between
// IoAttachDeviceToDeviceStackSafe (end of Relations_AddDevice) and
// IoDeleteDevice (end of Relations_RemoveDevice or DeviceCleanup).
// IoDeleteDevice (end of Relations_RemoveDevice or BusFilter_TeardownChildDevices).
// The mandatory teardown sequence is:
// 1. IoReleaseRemoveLockAndWait -- drain all in-flight I/O holders
// 2. IoDetachDevice -- detach filter from the PDO stack
@@ -189,6 +189,17 @@ DMF_BusFilter_Relations_AddDevice(
_In_ PDEVICE_OBJECT PhysicalDeviceObject
);
#pragma code_seg("PAGE")
_IRQL_requires_max_(PASSIVE_LEVEL)
static
VOID
BusFilter_TeardownChildDevices(
_In_ WDFDEVICE Device
);
#pragma code_seg()
EVT_WDF_OBJECT_CONTEXT_CLEANUP DMF_BusFilter_EvtBusDeviceContextCleanup;
///////////////////////////////////////////////////////////////////////////////////////////////////////
// IRP_MN_REMOVE_DEVICE teardown
///////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -1468,6 +1479,12 @@ Return Value:
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes,
PARENT_BUS_DEVICE_CONTEXT);
// Register the context cleanup callback so proxy children are always torn
// down automatically when the bus FDO is deleted, without requiring the
// client to wire up EvtDeviceReleaseHardware or EvtCleanupCallback.
//
attributes.EvtCleanupCallback = DMF_BusFilter_EvtBusDeviceContextCleanup;
// Add bus device context.
//
ntStatus = WdfObjectAllocateContext(device,
@@ -1623,8 +1640,9 @@ Return Value:
#pragma code_seg("PAGE")
_IRQL_requires_max_(PASSIVE_LEVEL)
static
VOID
DMF_BusFilter_DeviceCleanup(
BusFilter_TeardownChildDevices(
_In_ WDFDEVICE Device
)
/*++
@@ -1632,13 +1650,12 @@ DMF_BusFilter_DeviceCleanup(
Routine Description:
Tears down all remaining child filter devices associated with a bus device.
Invoked automatically from the bus FDO context cleanup callback; never called
directly by the client.
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).
Under normal PnP operation the ChildList is empty by the time this runs (each
IRP_MN_REMOVE_DEVICE already drained it). This is the safety net for abnormal
paths such as parent surprise-removed before all children received REMOVE.
For each remaining child:
- Acquires the per-child remove lock (prevents new I/O from entering).
@@ -1762,4 +1779,37 @@ Exit:
}
#pragma code_seg()
#pragma code_seg("PAGE")
_Use_decl_annotations_
VOID
DMF_BusFilter_EvtBusDeviceContextCleanup(
_In_ WDFOBJECT Object
)
/*++
Routine Description:
WDF context EvtCleanupCallback registered on the PARENT_BUS_DEVICE_CONTEXT.
Automatically tears down any remaining proxy child filter devices when the
bus FDO is deleted. This fires at PASSIVE_LEVEL after all child devnodes have
been given IRP_MN_REMOVE_DEVICE (normal path) or on surprise-removal paths
where some children may not have received REMOVE before the parent is deleted.
Clients do not call this function.
Arguments:
Object - The bus filter WDFDEVICE whose context is being cleaned up.
Return Value:
None
--*/
{
PAGED_CODE();
BusFilter_TeardownChildDevices((WDFDEVICE)Object);
}
#pragma code_seg()
#endif // defined(DMF_KERNEL_MODE)
+7 -49
View File
@@ -20,9 +20,10 @@ Abstract:
1. DriverEntry: fill in DMF_BusFilter_CONFIG, call DMF_BusFilter_Initialize.
2. EvtDriverDeviceAdd: call DMF_BusFilter_DeviceAdd.
3. EvtDeviceReleaseHardware or EvtCleanupCallback: call
DMF_BusFilter_DeviceCleanup to drain and destroy any remaining proxy
child devices and prevent resource leaks.
Proxy child teardown is automatic: the library registers an internal WDF
context cleanup callback on the bus FDO that drains and destroys remaining
proxy children when the FDO is deleted. No client-side cleanup call is needed.
Environment:
@@ -172,9 +173,9 @@ Callback: EVT_DMF_BusFilter_DeviceRemove
Purpose:
Called when a proxy child device is about to be destroyed. This fires both
during the normal IRP_MN_REMOVE_DEVICE path and during forced teardown in
DMF_BusFilter_DeviceCleanup. At this point in-flight I/O to the child has
already been drained (via IO_REMOVE_LOCK) so the child is quiescent.
during the normal IRP_MN_REMOVE_DEVICE path and during the automatic teardown
that runs when the bus FDO is deleted. At this point in-flight I/O to the
child has already been drained (via IO_REMOVE_LOCK) so the child is quiescent.
The callback must not attempt to forward any IRPs to the child or access
the child's WDM stack; both are being torn down. After this callback
@@ -614,47 +615,4 @@ DMF_BusFilter_WdmPhysicalDeviceGet(
_In_ DMFBUSCHILDDEVICE ChildDevice
);
/*++
Routine Description:
Tears down all proxy child filter devices still associated with the bus
device. The client MUST call this from its EvtDeviceReleaseHardware or
EvtCleanupCallback to prevent WDM device object leaks. Without this call,
any child devices remaining in the internal list at unload time will be
leaked and will fault when the I/O manager later dereferences them.
Under normal PnP operation the list is already empty when this function
runs (IRP_MN_REMOVE_DEVICE has been dispatched to every child). This
function is a safety net for abnormal paths such as surprise removal of
the parent before all children received REMOVE.
The function first waits for any outstanding bus-relations work items to
drain (preventing a race with a pending passive-level Relations_AddDevice
call), then for each remaining child:
- Acquires the per-child IO_REMOVE_LOCK to drain in-flight I/O.
- Calls EvtDeviceRemove (if set).
- Detaches and deletes the proxy filter device in the mandatory order.
If a child's IO_REMOVE_LOCK cannot be acquired (STATUS_DELETE_PENDING),
it means a concurrent IRP_MN_REMOVE_DEVICE is already processing that
child; this function skips it and lets the REMOVE path handle teardown.
Arguments:
Device - The bus filter WDFDEVICE whose proxy children should be cleaned up.
IRQL: PASSIVE_LEVEL
Return Value:
None
--*/
_IRQL_requires_max_(PASSIVE_LEVEL)
VOID
DMF_BusFilter_DeviceCleanup(
_In_ WDFDEVICE Device
);
#endif // defined(DMF_KERNEL_MODE)
+15 -65
View File
@@ -19,8 +19,9 @@ Typical client driver usage flow:
1. **DriverEntry** fill in `DMF_BusFilter_CONFIG` and call `DMF_BusFilter_Initialize` after `WdfDriverCreate` returns.
2. **EvtDriverDeviceAdd** register `DMF_BusFilter_DeviceAdd` as the `EvtDriverDeviceAdd` callback (or call it directly from a
wrapper).
3. **EvtDeviceReleaseHardware or EvtCleanupCallback** call `DMF_BusFilter_DeviceCleanup` to drain and destroy any remaining
proxy child devices and prevent resource leaks.
Proxy child teardown is automatic. The library registers an internal WDF context cleanup callback on the bus FDO so that any
remaining proxy children are drained and destroyed when the FDO is deleted. No client-side cleanup call is required.
Available only in kernel-mode builds (`DMF_KERNEL_MODE`).
@@ -244,8 +245,8 @@ EVT_DMF_BusFilter_DeviceRemove(
````
Called when a proxy child device is about to be destroyed. This fires both during the normal `IRP_MN_REMOVE_DEVICE` path and
during forced teardown in `DMF_BusFilter_DeviceCleanup`. At this point in-flight I/O to the child has already been drained via
the per-child `IO_REMOVE_LOCK`, so the child is quiescent.
during the automatic teardown that runs when the bus FDO is deleted. At this point in-flight I/O to the child has already been
drained via the per-child `IO_REMOVE_LOCK`, so the child is quiescent.
The callback must not attempt to forward any IRPs to the child or access the child's WDM stack; both are being torn down. After
this callback returns the `ChildDevice` handle is deleted by the library.
@@ -467,43 +468,6 @@ DeviceInit | The `WDFDEVICE_INIT` prepared by the framework.
-----------------------------------------------------------------------------------------------------------------------------------
##### DMF_BusFilter_DeviceCleanup
````
_IRQL_requires_max_(PASSIVE_LEVEL)
VOID
DMF_BusFilter_DeviceCleanup(
_In_ WDFDEVICE Device
);
````
Tears down all proxy child filter devices still associated with the bus device. The client **must** call this from its
`EvtDeviceReleaseHardware` or `EvtCleanupCallback` to prevent WDM device object leaks. Without this call, any child devices
remaining in the internal list at unload time will be leaked and will fault when the I/O manager later dereferences them.
Under normal PnP operation the child list is already empty when this function runs (`IRP_MN_REMOVE_DEVICE` has been dispatched
to every child). This function is a safety net for abnormal paths such as surprise removal of the parent before all children
received `REMOVE`.
The function first waits for any outstanding bus-relations work items to drain (preventing a race with a pending passive-level
`Relations_AddDevice` call), then for each remaining child: acquires the per-child `IO_REMOVE_LOCK` to drain in-flight I/O,
calls `EvtDeviceRemove` (if set), and detaches and deletes the proxy filter device in the mandatory order.
If a child's `IO_REMOVE_LOCK` cannot be acquired (`STATUS_DELETE_PENDING`), it means a concurrent `IRP_MN_REMOVE_DEVICE` is
already processing that child; this function skips it and lets the `REMOVE` path handle teardown.
##### Returns
None.
##### Parameters
Parameter | Description
----|----
Device | The bus filter `WDFDEVICE` whose proxy children should be cleaned up.
-----------------------------------------------------------------------------------------------------------------------------------
##### DMF_BusFilter_WdmDeviceObjectGet
````
@@ -587,8 +551,10 @@ ChildDevice | Opaque handle for a proxy child device.
* `DMF_BusFilter_Initialize` must be called before `DMF_BusFilter_DeviceAdd`. If `DeviceAdd` is called without a prior
successful `Initialize`, it returns `STATUS_INVALID_DEVICE_STATE`.
* `DMF_BusFilter_DeviceCleanup` must be called from `EvtDeviceReleaseHardware` or `EvtCleanupCallback`. Omitting this call
leaks WDM device objects that remain in the child list at driver unload.
* Proxy child teardown is automatic. The library registers an internal WDF context `EvtCleanupCallback` on the bus FDO's
`PARENT_BUS_DEVICE_CONTEXT`. When the FDO is deleted the callback drains and destroys any remaining proxy children. On the
normal PnP path the child list is already empty (every `IRP_MN_REMOVE_DEVICE` self-drained it), so the callback is a safe
no-op; it serves as the safety net for surprise-removal paths where children may not have received `REMOVE` before the parent.
* The mandatory WDM filter teardown order for each proxy child is: (1) `IoReleaseRemoveLockAndWait` to drain in-flight I/O,
(2) `IoDetachDevice` to detach the filter from the PDO stack, (3) `IoDeleteDevice` to lazy-free the device object. Nothing
may access the device extension after `IoDeleteDevice` returns.
@@ -650,8 +616,9 @@ manager dispatching `IRP_MN_START_DEVICE` to the child FDO. Attaching outside th
construction.
The parent bus device tracks outstanding passive-level workers with an atomic counter (`OutstandingWorkItems`) and a kernel
event (`WorkItemsIdle`, signaled when the counter reaches zero). `DMF_BusFilter_DeviceCleanup` waits on `WorkItemsIdle` before
draining the child list, preventing a race between a concurrent `Relations_AddDevice` worker and list teardown.
event (`WorkItemsIdle`, signaled when the counter reaches zero). The internal teardown routine (invoked by the FDO context
cleanup callback) waits on `WorkItemsIdle` before draining the child list, preventing a race between a concurrent
`Relations_AddDevice` worker and list teardown.
```mermaid
flowchart TD
@@ -686,11 +653,12 @@ the filter. Release rules:
-----------------------------------------------------------------------------------------------------------------------------------
The following sketch shows the minimum wiring for a client driver.
The following sketch shows the minimum wiring for a client driver. Proxy child teardown is handled automatically by the
library when the bus FDO is deleted, so no `EvtDeviceReleaseHardware` or explicit cleanup call is needed.
````c
//
// Optional: per-child callback.
// Optional: per-child callbacks.
//
static
NTSTATUS
@@ -719,24 +687,6 @@ MyDriver_EvtDeviceRemove(
UNREFERENCED_PARAMETER(ChildDevice);
}
//
// Called when the bus filter FDO's hardware is being released.
//
static
NTSTATUS
MyDriver_EvtDeviceReleaseHardware(
_In_ WDFDEVICE Device,
_In_ WDFCMRESLIST ResourcesTranslated
)
{
UNREFERENCED_PARAMETER(ResourcesTranslated);
// Mandatory: drain and destroy all remaining proxy children.
DMF_BusFilter_DeviceCleanup(Device);
return STATUS_SUCCESS;
}
//
// DriverEntry
//