diff --git a/DmfBusFilterExtension.sln b/DmfBusFilterExtension.sln index a190ddc..d3001aa 100644 --- a/DmfBusFilterExtension.sln +++ b/DmfBusFilterExtension.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.33328.57 +# Visual Studio Version 18 +VisualStudioVersion = 18.7.11925.98 stable MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DmfBusFilterExtension", "src\DmfBusFilterExtension.vcxproj", "{D384918D-A356-4325-B8DD-A2A10E4592A0}" EndProject @@ -9,6 +9,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig DmfBusFilterExtension.props = DmfBusFilterExtension.props + src\Dmf_BusFilter.md = src\Dmf_BusFilter.md LICENSE = LICENSE README.md = README.md EndProjectSection diff --git a/src/Dmf_BusFilter.md b/src/Dmf_BusFilter.md new file mode 100644 index 0000000..705d894 --- /dev/null +++ b/src/Dmf_BusFilter.md @@ -0,0 +1,793 @@ +## DMF_BusFilter + +----------------------------------------------------------------------------------------------------------------------------------- + +#### Module Summary + +----------------------------------------------------------------------------------------------------------------------------------- + +Implements a bus filter driver pattern that intercepts the `IRP_MN_QUERY_DEVICE_RELATIONS`/`BusRelations` completion path of a +WDF FDO and splices a proxy WDM filter device object onto every child PDO reported by the underlying bus driver. The proxy filter +sits between the bus driver's PDO and the child devnode's FDO, giving the client driver visibility into PnP, power, and I/O +traffic for each child device. + +This is a standalone dispatch-table-hooking library. It does not use the standard `DMFMODULE` handle or `DMF_*_Create` pattern. +Clients interact through a plain `DMF_BusFilter_CONFIG` structure and three lifecycle entry points. + +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. + +Available only in kernel-mode builds (`DMF_KERNEL_MODE`). + +----------------------------------------------------------------------------------------------------------------------------------- + +#### Module Configuration + +----------------------------------------------------------------------------------------------------------------------------------- + +##### DMF_BusFilter_CONFIG_INIT + +```` +VOID +DMF_BusFilter_CONFIG_INIT( + _Out_ DMF_BusFilter_CONFIG* BusFilterConfig, + _In_ PDRIVER_OBJECT DriverObject + ); +```` + +Zero-initializes a `DMF_BusFilter_CONFIG` structure and sets the required `DriverObject` field. The client must call this macro +before setting any optional callback pointers and before passing the config to `DMF_BusFilter_Initialize`. All optional callback +pointers default to `NULL`. `DeviceType` defaults to `0`, which causes the library to inherit the device type from each child PDO +at attach time. + +##### DMF_BusFilter_CONFIG + +```` +typedef struct +{ + // + // The WDM DRIVER_OBJECT for this driver. Required. + // + _In_ DRIVER_OBJECT* DriverObject; + + // + // Device type for proxy child filter DOs. + // 0 causes the library to inherit the type from the PDO (recommended). + // + _In_ DEVICE_TYPE DeviceType; + + // + // Device characteristics flags for proxy child filter DOs. + // FILE_AUTOGENERATED_DEVICE_NAME, FILE_CHARACTERISTIC_TS_DEVICE, + // FILE_CHARACTERISTIC_WEBDAV_DEVICE, FILE_DEVICE_IS_MOUNTED, and + // FILE_VIRTUAL_VOLUME are masked off by the library before use. + // + _In_ ULONG DeviceCharacteristics; + + // + // Called before the bus filter FDO is created. + // + _In_opt_ EVT_DMF_BusFilter_PreBusDeviceAdd* EvtPreBusDeviceAdd; + + // + // Called after the bus filter FDO has been successfully created. + // + _In_opt_ EVT_DMF_BusFilter_PostBusDeviceAdd* EvtPostBusDeviceAdd; + + // + // Called when a proxy child filter device object has been attached to a PDO. + // + _In_opt_ EVT_DMF_BusFilter_DeviceAdd* EvtDeviceAdd; + + // + // Called when a proxy child device is about to be destroyed. + // + _In_opt_ EVT_DMF_BusFilter_DeviceRemove* EvtDeviceRemove; + + // + // Called after IRP_MN_START_DEVICE has been forwarded and the lower stack returned success. + // + _In_opt_ EVT_DMF_BusFilter_DeviceStarted* EvtDeviceStarted; + + // + // Called when IRP_MN_DEVICE_ENUMERATED reaches the proxy child device. + // + _In_opt_ EVT_DMF_BusFilter_DeviceEnumerated* EvtDeviceEnumerated; + + // + // Called when IRP_MN_QUERY_ID reaches the proxy child device. + // + _In_opt_ EVT_DMF_BusFilter_DeviceQueryId* EvtDeviceQueryId; + + // + // Called when IRP_MN_QUERY_INTERFACE reaches the proxy child device. + // + _In_opt_ EVT_DMF_BusFilter_DeviceQueryInterface* EvtDeviceQueryInterface; + +} DMF_BusFilter_CONFIG; +```` + +Member | Description +----|---- +DriverObject | The WDM `DRIVER_OBJECT` for this driver, as received by `DriverEntry`. Required; `DMF_BusFilter_Initialize` returns `STATUS_INVALID_PARAMETER` if this is `NULL`. +DeviceType | Device type passed to `IoCreateDevice` for each proxy child filter DO. If `0`, the library inherits the type from the PDO at attach time (recommended for most bus filter scenarios). +DeviceCharacteristics | Characteristics flags passed to `IoCreateDevice`. `FILE_AUTOGENERATED_DEVICE_NAME`, `FILE_CHARACTERISTIC_TS_DEVICE`, `FILE_CHARACTERISTIC_WEBDAV_DEVICE`, `FILE_DEVICE_IS_MOUNTED`, and `FILE_VIRTUAL_VOLUME` are always masked off by the library. +EvtPreBusDeviceAdd | Optional callback invoked before the bus filter FDO is created. See `EVT_DMF_BusFilter_PreBusDeviceAdd`. +EvtPostBusDeviceAdd | Optional callback invoked after the bus filter FDO has been successfully created. See `EVT_DMF_BusFilter_PostBusDeviceAdd`. +EvtDeviceAdd | Optional callback invoked after a proxy child filter DO has been attached to a newly discovered PDO. See `EVT_DMF_BusFilter_DeviceAdd`. +EvtDeviceRemove | Optional callback invoked just before a proxy child device is destroyed. See `EVT_DMF_BusFilter_DeviceRemove`. +EvtDeviceStarted | Optional callback invoked after `IRP_MN_START_DEVICE` has been forwarded successfully to the lower stack. See `EVT_DMF_BusFilter_DeviceStarted`. +EvtDeviceEnumerated | Optional callback invoked when `IRP_MN_DEVICE_ENUMERATED` reaches the proxy child device. See `EVT_DMF_BusFilter_DeviceEnumerated`. +EvtDeviceQueryId | Optional callback invoked when `IRP_MN_QUERY_ID` reaches the proxy child device. See `EVT_DMF_BusFilter_DeviceQueryId`. +EvtDeviceQueryInterface | Optional callback invoked when `IRP_MN_QUERY_INTERFACE` reaches the proxy child device. See `EVT_DMF_BusFilter_DeviceQueryInterface`. + +----------------------------------------------------------------------------------------------------------------------------------- + +#### Module Callbacks + +----------------------------------------------------------------------------------------------------------------------------------- + +##### EVT_DMF_BusFilter_PreBusDeviceAdd + +```` +_IRQL_requires_same_ +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS +EVT_DMF_BusFilter_PreBusDeviceAdd( + _In_ WDFDRIVER Driver, + _Inout_ PWDFDEVICE_INIT DeviceInit, + _Out_ WDF_OBJECT_ATTRIBUTES* Attributes, + _Outptr_result_maybenull_ PDMFDEVICE_INIT* DmfDeviceInit + ); +```` + +Called by `DMF_BusFilter_DeviceAdd` before the bus filter FDO is created. The client may customize `DeviceInit` (e.g. set PnP +capabilities or power policy) and optionally allocate a `PDMFDEVICE_INIT` if it wants to use DMF modules on the bus FDO. If the +client allocates `*DmfDeviceInit` it must **not** free it; the library takes ownership regardless of success or failure. + +##### Returns + +`STATUS_SUCCESS`, or an error status. A non-success value aborts `DMF_BusFilter_DeviceAdd`. + +##### Parameters + +Parameter | Description +----|---- +Driver | The `WDFDRIVER` for this driver. +DeviceInit | The `WDFDEVICE_INIT` being prepared for the bus filter FDO. The client may call `WdfDeviceInitSet*` routines on it. +Attributes | `WDF_OBJECT_ATTRIBUTES` to apply to the created device. The client may set a context type or parent here. +DmfDeviceInit | On output: optionally set to a `PDMFDEVICE_INIT` if the client uses DMF modules on the bus FDO. Pass `NULL` if not needed. + +----------------------------------------------------------------------------------------------------------------------------------- + +##### EVT_DMF_BusFilter_PostBusDeviceAdd + +```` +_IRQL_requires_same_ +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS +EVT_DMF_BusFilter_PostBusDeviceAdd( + _In_ WDFDEVICE Device, + _In_opt_ PDMFDEVICE_INIT DmfDeviceInit + ); +```` + +Called by `DMF_BusFilter_DeviceAdd` after the bus filter FDO has been successfully created. The client may perform any +post-creation initialization that requires a valid `WDFDEVICE` handle, such as creating I/O queues or registering interfaces. + +##### Returns + +`STATUS_SUCCESS`, or an error status. A non-success value aborts `DMF_BusFilter_DeviceAdd` and causes the device object to be +deleted. + +##### Parameters + +Parameter | Description +----|---- +Device | The newly created bus filter `WDFDEVICE`. +DmfDeviceInit | The `PDMFDEVICE_INIT` that was optionally allocated in `EVT_DMF_BusFilter_PreBusDeviceAdd`, or `NULL` if DMF module support was not requested. + +----------------------------------------------------------------------------------------------------------------------------------- + +##### EVT_DMF_BusFilter_DeviceAdd + +```` +_IRQL_requires_max_(APC_LEVEL) +_IRQL_requires_same_ +NTSTATUS +EVT_DMF_BusFilter_DeviceAdd( + _In_ WDFDEVICE Device, + _In_ DMFBUSCHILDDEVICE ChildDevice + ); +```` + +Called when a proxy WDM filter device object has been created and attached to a newly discovered child PDO. The filter is already +in the stack (attached above the PDO) when this callback fires. The client may associate per-child state or WDF objects with the +`ChildDevice` handle at this point. + +If the callback returns a non-success status, the library immediately detaches and deletes the proxy filter device; the +`ChildDevice` handle becomes invalid after the callback returns. + +##### Returns + +`STATUS_SUCCESS`, or an error status to abort creation of this proxy child and roll back the attach. + +##### Parameters + +Parameter | Description +----|---- +Device | The parent bus filter `WDFDEVICE`. +ChildDevice | Opaque `DMFBUSCHILDDEVICE` handle representing the newly created proxy child. Valid only for the duration of this callback (and subsequent callbacks) until `EVT_DMF_BusFilter_DeviceRemove` returns. + +##### Remarks + +* The IRQL annotation is `APC_LEVEL` because this callback is invoked from `Relations_AddDevice`, which runs at `PASSIVE_LEVEL` + but holds no spinlock. The annotation is intentionally conservative; in practice this callback fires at `PASSIVE_LEVEL`. + +----------------------------------------------------------------------------------------------------------------------------------- + +##### EVT_DMF_BusFilter_DeviceRemove + +```` +_IRQL_requires_max_(PASSIVE_LEVEL) +_IRQL_requires_same_ +VOID +EVT_DMF_BusFilter_DeviceRemove( + _In_ WDFDEVICE Device, + _In_ DMFBUSCHILDDEVICE ChildDevice + ); +```` + +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. + +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. + +##### Returns + +None. + +##### Parameters + +Parameter | Description +----|---- +Device | The parent bus filter `WDFDEVICE`. +ChildDevice | Opaque handle for the proxy child being removed. Do not use after this callback returns. + +----------------------------------------------------------------------------------------------------------------------------------- + +##### EVT_DMF_BusFilter_DeviceStarted + +```` +_IRQL_requires_max_(PASSIVE_LEVEL) +_IRQL_requires_same_ +VOID +EVT_DMF_BusFilter_DeviceStarted( + _In_ DMFBUSCHILDDEVICE ChildDevice, + _In_ IRP* Irp + ); +```` + +Called after the library has successfully forwarded `IRP_MN_START_DEVICE` to the lower stack and the lower driver returned +success. The IRP is still held by the library and will be completed after this callback returns; the client must not complete it. + +The client may use this callback to perform any work that requires the child device to be in the started state, such as sending +I/O or enabling device interfaces. + +##### Returns + +None. + +##### Parameters + +Parameter | Description +----|---- +ChildDevice | Opaque handle for the proxy child that has started. +Irp | The `IRP_MN_START_DEVICE` IRP. For inspection only; the client must not complete it. + +----------------------------------------------------------------------------------------------------------------------------------- + +##### EVT_DMF_BusFilter_DeviceEnumerated + +```` +_IRQL_requires_max_(PASSIVE_LEVEL) +_IRQL_requires_same_ +VOID +EVT_DMF_BusFilter_DeviceEnumerated( + _In_ DMFBUSCHILDDEVICE ChildDevice, + _In_ IRP* Irp + ); +```` + +Called when `IRP_MN_DEVICE_ENUMERATED` reaches the proxy child device. This minor code is sent by the PnP manager after the +device has been enumerated and its instance ID has been assigned. It is informational; the library always forwards the IRP to the +lower stack after this callback returns. + +The client must not complete the IRP. + +##### Returns + +None. + +##### Parameters + +Parameter | Description +----|---- +ChildDevice | Opaque handle for the proxy child being enumerated. +Irp | The `IRP_MN_DEVICE_ENUMERATED` IRP. For inspection only; the client must not complete it. + +----------------------------------------------------------------------------------------------------------------------------------- + +##### EVT_DMF_BusFilter_DeviceQueryId + +```` +_IRQL_requires_max_(PASSIVE_LEVEL) +_IRQL_requires_same_ +BOOLEAN +EVT_DMF_BusFilter_DeviceQueryId( + _In_ DMFBUSCHILDDEVICE ChildDevice, + _In_ IRP* Irp + ); +```` + +Called when `IRP_MN_QUERY_ID` reaches the proxy child device. The client may inspect or modify the IRP stack location and set +`IoStatus` fields to supply identity strings (hardware ID, instance ID, etc.). + +**Return value contract (load-bearing):** + +* `TRUE` – The client has fully handled the IRP (set `IoStatus.Status` and `Information`). The library completes the IRP without + forwarding it to the lower stack. +* `FALSE` – The client did not handle the IRP. The library forwards it synchronously to the lower driver with + `IoForwardIrpSynchronously`, then completes it with the lower driver's status. + +##### Returns + +`TRUE` if the client handled the IRP; `FALSE` to let the library forward it to the lower stack. + +##### Parameters + +Parameter | Description +----|---- +ChildDevice | Opaque handle for the proxy child. +Irp | The `IRP_MN_QUERY_ID` IRP. + +----------------------------------------------------------------------------------------------------------------------------------- + +##### EVT_DMF_BusFilter_DeviceQueryInterface + +```` +_IRQL_requires_max_(PASSIVE_LEVEL) +_IRQL_requires_same_ +BOOLEAN +EVT_DMF_BusFilter_DeviceQueryInterface( + _In_ DMFBUSCHILDDEVICE ChildDevice, + _In_ IRP* Irp + ); +```` + +Called when `IRP_MN_QUERY_INTERFACE` reaches the proxy child device. The client may fill in the interface struct pointed to by +the IRP stack location's `Parameters.QueryInterface` field. + +**Return value contract (load-bearing):** + +* `TRUE` – The client has fully handled the IRP (set `IoStatus.Status` and filled in the interface). The library completes the + IRP without forwarding it to the lower stack. +* `FALSE` – The client did not handle the IRP. The library forwards it synchronously to the lower driver with + `IoForwardIrpSynchronously`, then completes it with the lower driver's status. + +##### Returns + +`TRUE` if the client handled the IRP; `FALSE` to let the library forward it to the lower stack. + +##### Parameters + +Parameter | Description +----|---- +ChildDevice | Opaque handle for the proxy child. +Irp | The `IRP_MN_QUERY_INTERFACE` IRP. + +----------------------------------------------------------------------------------------------------------------------------------- + +#### Module Methods + +----------------------------------------------------------------------------------------------------------------------------------- + +##### DMF_BusFilter_Initialize + +```` +_IRQL_requires_max_(PASSIVE_LEVEL) +_Must_inspect_result_ +NTSTATUS +DMF_BusFilter_Initialize( + _In_ DMF_BusFilter_CONFIG* BusFilterConfig + ); +```` + +Initializes the bus filter library for the current driver. Must be called once from `DriverEntry`, after `WdfDriverCreate` +returns, and before the first call to `DMF_BusFilter_DeviceAdd`. + +Internally this routine hooks the driver's entire `MajorFunction` dispatch table so that IRPs sent to proxy child device objects +are intercepted and routed through the library's dispatch handler. It also stores a copy of the client's configuration for use +in subsequent callbacks. + +##### Returns + +`STATUS_SUCCESS`, or: + +Status | Meaning +----|---- +`STATUS_INVALID_PARAMETER` | `BusFilterConfig` or `BusFilterConfig->DriverObject` is `NULL`. +`STATUS_NOT_SUPPORTED` | `WdfGetDriver` returned `NULL` (WDF is not yet ready; call after `WdfDriverCreate`). +Any WDF context-allocation failure | Returned from `WdfObjectAllocateContext`. + +##### Parameters + +Parameter | Description +----|---- +BusFilterConfig | Pointer to a fully populated `DMF_BusFilter_CONFIG`. Initialize with `DMF_BusFilter_CONFIG_INIT` before setting callbacks. The caller retains ownership; the library copies the config internally. + +##### Remarks + +* Call `DMF_BusFilter_CONFIG_INIT` to zero-initialize the config before setting optional fields. +* The library hooks `DriverObject->MajorFunction` for all major function codes. IRPs targeting devices that do not carry the + internal `GUID_DMF_BUSFILTER_SIGNATURE` tag are delegated to the previously installed handler transparently. + +----------------------------------------------------------------------------------------------------------------------------------- + +##### DMF_BusFilter_DeviceAdd + +```` +EVT_WDF_DRIVER_DEVICE_ADD DMF_BusFilter_DeviceAdd; +```` + +WDF `EvtDriverDeviceAdd` callback implementation for the bus filter FDO. The client should register this function as its +`EvtDriverDeviceAdd` (or call it directly from a wrapper). It creates the bus filter WDF device, installs the bus-relations IRP +preprocessor, and initializes the internal child device list. + +`DMF_BusFilter_Initialize` must have been called before this function. + +##### Returns + +`STATUS_SUCCESS`, or a failure status from `WdfDeviceCreate` or supporting framework calls. On failure, any partially created +device is cleaned up. + +##### Parameters + +Parameter | Description +----|---- +Driver | The `WDFDRIVER`. +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 + +```` +PDEVICE_OBJECT +DMF_BusFilter_WdmDeviceObjectGet( + _In_ DMFBUSCHILDDEVICE ChildDevice + ); +```` + +Returns the WDM `DEVICE_OBJECT` that represents the proxy child filter device itself (the filter DO created by `IoCreateDevice` +inside this library). Use this to obtain the raw WDM object when the WDF handle is not sufficient. + +##### Returns + +The proxy filter `DEVICE_OBJECT`, or `NULL` if the context is missing. + +##### Parameters + +Parameter | Description +----|---- +ChildDevice | Opaque handle for a proxy child device. + +----------------------------------------------------------------------------------------------------------------------------------- + +##### DMF_BusFilter_WdmAttachedDeviceGet + +```` +PDEVICE_OBJECT +DMF_BusFilter_WdmAttachedDeviceGet( + _In_ DMFBUSCHILDDEVICE ChildDevice + ); +```` + +Returns the WDM `DEVICE_OBJECT` to which the proxy child filter is attached (i.e. the result of +`IoAttachDeviceToDeviceStackSafe` — typically the PDO itself or the topmost device in the PDO's stack at attach time). This is +the object that IRPs are forwarded to when the filter does not handle them. + +##### Returns + +The attached (lower) `DEVICE_OBJECT`, or `NULL` if the child has an unrecognised signature or a missing context. + +##### Parameters + +Parameter | Description +----|---- +ChildDevice | Opaque handle for a proxy child device. + +----------------------------------------------------------------------------------------------------------------------------------- + +##### DMF_BusFilter_WdmPhysicalDeviceGet + +```` +PDEVICE_OBJECT +DMF_BusFilter_WdmPhysicalDeviceGet( + _In_ DMFBUSCHILDDEVICE ChildDevice + ); +```` + +Returns the Physical Device Object (PDO) associated with a proxy child device. This is the PDO reported by the bus driver in +the `DEVICE_RELATIONS` array that caused this proxy child to be created. + +##### Returns + +The PDO `DEVICE_OBJECT`, or `NULL` if the child has an unrecognised signature or a missing context. + +##### Parameters + +Parameter | Description +----|---- +ChildDevice | Opaque handle for a proxy child device. + +----------------------------------------------------------------------------------------------------------------------------------- + +#### Module IOCTLs + +* None + +----------------------------------------------------------------------------------------------------------------------------------- + +#### Module Remarks + +* `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. +* 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. +* The bus-relations completion routine (`DMF_BusFilter_QueryBusRelationsCompleted`) may fire at `DISPATCH_LEVEL`. Because + `IoCreateDevice` and `IoAttachDeviceToDeviceStackSafe` require `PASSIVE_LEVEL`, the library automatically defers child + creation to a passive-level work item in that case. +* The completion routine returns `STATUS_MORE_PROCESSING_REQUIRED` to hold the IRP (and therefore the bus driver's references + on each PDO in `DEVICE_RELATIONS`) alive until the passive-level worker has finished attaching all filter devices. +* `DeviceType` set to `0` in `DMF_BusFilter_CONFIG` causes the library to inherit the device type from each child PDO at + attach time. A filter whose device type differs from the stack it joins can cause I/O manager policy mismatches. +* All `EVT_*` callback pointers in `DMF_BusFilter_CONFIG` are optional. The library silently skips `NULL` callbacks. + +----------------------------------------------------------------------------------------------------------------------------------- + +#### Module Children + +* None + +----------------------------------------------------------------------------------------------------------------------------------- + +#### Module Implementation Details + +----------------------------------------------------------------------------------------------------------------------------------- + +##### Device Stack Placement + +The library attaches one proxy WDM filter DO above each PDO reported by the bus driver. The resulting per-child devnode stack +looks like: + +```mermaid +flowchart TD + FDO["Child FDO\n(loaded later by PnP)"] + FilterDO["Proxy Filter DO\n(created by this library,\ntagged with GUID_DMF_BUSFILTER_SIGNATURE)"] + PDO["Child PDO\n(reported by bus driver)"] + + FDO --> FilterDO --> PDO + + BusFDO["Bus Filter FDO\n(DMF_BusFilter_DeviceAdd)"] + BusFDO -. "preprocesses\nQUERY_DEVICE_RELATIONS" .-> PDO +``` + +The bus filter FDO hooks `IRP_MN_QUERY_DEVICE_RELATIONS`/`BusRelations` via a WDF IRP preprocessor callback. When the +completion fires it calls `IoAttachDeviceToDeviceStackSafe` once per PDO in `DEVICE_RELATIONS` that has not been seen before. + +##### Dispatch-Table Hooking and Child Identification + +`DMF_BusFilter_Initialize` replaces every slot of `DriverObject->MajorFunction` with the library's own +`DMF_BusFilter_DispatchHandler`. The original per-slot function pointer is saved in the driver-level context +(`BusFilter_Context.MajorDispatchFunctions`). + +For each incoming IRP, `DispatchHandler` inspects the target device object's extension for the tag GUID +`GUID_DMF_BUSFILTER_SIGNATURE` (`{678CBB8D-019F-4D07-912A-73E2E568B148}`). IRPs targeting non-child devices (e.g. the WDF FDO) +are delegated to the saved original handler transparently. IRPs targeting proxy children are handled by the library. + +##### Bus-Relations Attach Window and Work-Item Rundown + +The only safe window for a bus filter to attach to a PDO is between the bus driver populating `DEVICE_RELATIONS` and the PnP +manager dispatching `IRP_MN_START_DEVICE` to the child FDO. Attaching outside this window races PDO creation or FDO +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. + +```mermaid +flowchart TD + BusRelComp["BusRelations completion\n(may be DISPATCH_LEVEL)"] + Check{"IRQL > +PASSIVE_LEVEL?"} + WorkItem["Queue passive-level\nwork item"] + Inline["Process inline\nat PASSIVE_LEVEL"] + AddDevice["Relations_AddDevice\nper PDO in DEVICE_RELATIONS\n(IoCreateDevice +\nIoAttachDeviceToDeviceStackSafe +\nEvtDeviceAdd)"] + CompleteIrp["IoCompleteRequest\n(resume IRP propagation)"] + + BusRelComp --> Check + Check -- Yes --> WorkItem --> AddDevice + Check -- No --> Inline --> AddDevice + AddDevice --> CompleteIrp +``` + +##### Per-Child IO_REMOVE_LOCK Protocol + +Each proxy child device extension contains an `IO_REMOVE_LOCK`. `DispatchHandler` acquires the lock for every IRP that enters +the filter. Release rules: + +* `IRP_MN_REMOVE_DEVICE` — released inside `Relations_RemoveDevice` via `IoReleaseRemoveLockAndWait`, which also drains all + other in-flight holders. The dispatcher must not release the lock again after this call. +* All other PnP minors — released with `IoReleaseRemoveLock` after `DispatchPnp` returns. +* Non-PnP IRPs — released with `IoReleaseRemoveLock` **before** `IoCallDriver` so the lock does not span an asynchronous IRP + completion. + +----------------------------------------------------------------------------------------------------------------------------------- + +#### Examples + +----------------------------------------------------------------------------------------------------------------------------------- + +The following sketch shows the minimum wiring for a client driver. + +````c +// +// Global configuration block (or stored in driver context). +// +static DMF_BusFilter_CONFIG g_BusFilterConfig; + +// +// Optional: per-child callback. +// +static +NTSTATUS +MyDriver_EvtDeviceAdd( + _In_ WDFDEVICE Device, + _In_ DMFBUSCHILDDEVICE ChildDevice + ) +{ + UNREFERENCED_PARAMETER(Device); + + // Retrieve the raw PDO for this child if needed. + PDEVICE_OBJECT pdo = DMF_BusFilter_WdmPhysicalDeviceGet(ChildDevice); + UNREFERENCED_PARAMETER(pdo); + + return STATUS_SUCCESS; +} + +static +VOID +MyDriver_EvtDeviceRemove( + _In_ WDFDEVICE Device, + _In_ DMFBUSCHILDDEVICE ChildDevice + ) +{ + UNREFERENCED_PARAMETER(Device); + 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 +// +NTSTATUS +DriverEntry( + _In_ PDRIVER_OBJECT DriverObject, + _In_ PUNICODE_STRING RegistryPath + ) +{ + NTSTATUS ntStatus; + WDF_DRIVER_CONFIG driverConfig; + + WDF_DRIVER_CONFIG_INIT(&driverConfig, DMF_BusFilter_DeviceAdd); + + ntStatus = WdfDriverCreate(DriverObject, + RegistryPath, + WDF_NO_OBJECT_ATTRIBUTES, + &driverConfig, + WDF_NO_HANDLE); + if (!NT_SUCCESS(ntStatus)) + { + return ntStatus; + } + + // Initialize the bus filter library after WdfDriverCreate. + // + DMF_BusFilter_CONFIG_INIT(&g_BusFilterConfig, DriverObject); + g_BusFilterConfig.EvtDeviceAdd = MyDriver_EvtDeviceAdd; + g_BusFilterConfig.EvtDeviceRemove = MyDriver_EvtDeviceRemove; + + return DMF_BusFilter_Initialize(&g_BusFilterConfig); +} +```` + +----------------------------------------------------------------------------------------------------------------------------------- + +#### To Do + +----------------------------------------------------------------------------------------------------------------------------------- + +----------------------------------------------------------------------------------------------------------------------------------- + +#### Module Category + +----------------------------------------------------------------------------------------------------------------------------------- + +Driver Patterns + +-----------------------------------------------------------------------------------------------------------------------------------