diff --git a/.gitignore b/.gitignore index ca1c7a3..24f497b 100644 --- a/.gitignore +++ b/.gitignore @@ -398,3 +398,4 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml +/.dual-graph diff --git a/src/Dmf_BusFilter.c b/src/Dmf_BusFilter.c index 9c323a4..7113733 100644 --- a/src/Dmf_BusFilter.c +++ b/src/Dmf_BusFilter.c @@ -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) diff --git a/src/Dmf_BusFilter.h b/src/Dmf_BusFilter.h index 9b5f69f..3717273 100644 --- a/src/Dmf_BusFilter.h +++ b/src/Dmf_BusFilter.h @@ -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) diff --git a/src/Dmf_BusFilter.md b/src/Dmf_BusFilter.md index c3b988e..d3249a5 100644 --- a/src/Dmf_BusFilter.md +++ b/src/Dmf_BusFilter.md @@ -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 //