Moved bus device clean-up internal and automatic
This commit is contained in:
@@ -398,3 +398,4 @@ FodyWeavers.xsd
|
||||
# JetBrains Rider
|
||||
*.sln.iml
|
||||
|
||||
/.dual-graph
|
||||
|
||||
+58
-8
@@ -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
@@ -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
@@ -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
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user