/*++ Copyright (c) Nefarius Software Solutions e.U. All rights reserved. Licensed under the MIT license. Module Name: Dmf_BusFilter.h Abstract: Public API for the DMF Bus Filter extension. This module 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 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. Typical client driver usage flow: 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. Environment: Kernel-mode Driver Framework --*/ #pragma once ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Bus Filter support ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// // #if defined(DMF_KERNEL_MODE) // Opaque handle representing a filtered child PDO. Passed to client callbacks // and to the DMF_BusFilter_Wdm*Get accessor functions. // DECLARE_HANDLE(DMFBUSCHILDDEVICE); /*++ Callback: EVT_DMF_BusFilter_PreBusDeviceAdd Purpose: Called by DMF_BusFilter_DeviceAdd before the bus filter FDO is created. The client may customize DeviceInit (e.g. set PnP capabilities, set 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. Arguments: Driver - The WDFDRIVER for this driver. DeviceInit - The WDFDEVICE_INIT being prepared for the bus FDO. 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. Pass NULL if not needed. IRQL: PASSIVE_LEVEL Return Value: STATUS_SUCCESS or an error status. A non-success value aborts DMF_BusFilter_DeviceAdd. --*/ typedef _Function_class_(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 ); /*++ Callback: EVT_DMF_BusFilter_PostBusDeviceAdd Purpose: 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. Arguments: Device - The newly created bus filter WDFDEVICE. DmfDeviceInit - The PDMFDEVICE_INIT that was optionally allocated in EVT_DMF_BusFilter_PreBusDeviceAdd, or NULL if the client did not request DMF module support. IRQL: PASSIVE_LEVEL Return Value: STATUS_SUCCESS or an error status. A non-success value aborts DMF_BusFilter_DeviceAdd and causes the device object to be deleted. --*/ typedef _Function_class_(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 ); /*++ Callback: EVT_DMF_BusFilter_DeviceAdd Purpose: 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 child ChildDevice handle becomes invalid. NOTE: 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. Arguments: Device - The parent bus filter WDFDEVICE. ChildDevice - Opaque handle representing the newly created proxy child. IRQL: <= APC_LEVEL (in practice: PASSIVE_LEVEL) Return Value: STATUS_SUCCESS, or an error status to abort creation of this proxy child. --*/ typedef _Function_class_(EVT_DMF_BusFilter_DeviceAdd) _IRQL_requires_max_(APC_LEVEL) _IRQL_requires_same_ NTSTATUS EVT_DMF_BusFilter_DeviceAdd( _In_ WDFDEVICE Device, _In_ DMFBUSCHILDDEVICE ChildDevice ); /*++ 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. 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. Arguments: Device - The parent bus filter WDFDEVICE. ChildDevice - Opaque handle for the proxy child being removed. IRQL: PASSIVE_LEVEL Return Value: None --*/ typedef _Function_class_(EVT_DMF_BusFilter_DeviceRemove) _IRQL_requires_max_(PASSIVE_LEVEL) _IRQL_requires_same_ VOID EVT_DMF_BusFilter_DeviceRemove( _In_ WDFDEVICE Device, _In_ DMFBUSCHILDDEVICE ChildDevice ); /*++ Callback: EVT_DMF_BusFilter_DeviceStarted Purpose: 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 (e.g. sending I/O, enabling interfaces). Arguments: ChildDevice - Opaque handle for the proxy child that has started. Irp - The IRP_MN_START_DEVICE IRP (for inspection only). IRQL: PASSIVE_LEVEL Return Value: None --*/ typedef _Function_class_(EVT_DMF_BusFilter_DeviceStarted) _IRQL_requires_max_(PASSIVE_LEVEL) _IRQL_requires_same_ VOID EVT_DMF_BusFilter_DeviceStarted( _In_ DMFBUSCHILDDEVICE ChildDevice, _In_ IRP* Irp ); /*++ Callback: EVT_DMF_BusFilter_DeviceEnumerated Purpose: 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. Arguments: ChildDevice - Opaque handle for the proxy child being enumerated. Irp - The IRP_MN_DEVICE_ENUMERATED IRP (for inspection only). IRQL: PASSIVE_LEVEL Return Value: None --*/ typedef _Function_class_(EVT_DMF_BusFilter_DeviceEnumerated) _IRQL_requires_max_(PASSIVE_LEVEL) _IRQL_requires_same_ VOID EVT_DMF_BusFilter_DeviceEnumerated( _In_ DMFBUSCHILDDEVICE ChildDevice, _In_ IRP* Irp ); /*++ Callback: EVT_DMF_BusFilter_DeviceQueryId Purpose: 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. Arguments: ChildDevice - Opaque handle for the proxy child. Irp - The IRP_MN_QUERY_ID IRP. IRQL: PASSIVE_LEVEL Return Value: TRUE if the client handled the IRP; FALSE to let the library forward it. --*/ typedef _Function_class_(EVT_DMF_BusFilter_DeviceQueryId) _IRQL_requires_max_(PASSIVE_LEVEL) _IRQL_requires_same_ BOOLEAN EVT_DMF_BusFilter_DeviceQueryId( _In_ DMFBUSCHILDDEVICE ChildDevice, _In_ IRP* Irp ); /*++ Callback: EVT_DMF_BusFilter_DeviceQueryInterface Purpose: 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. Arguments: ChildDevice - Opaque handle for the proxy child. Irp - The IRP_MN_QUERY_INTERFACE IRP. IRQL: PASSIVE_LEVEL Return Value: TRUE if the client handled the IRP; FALSE to let the library forward it. --*/ typedef _Function_class_(EVT_DMF_BusFilter_DeviceQueryInterface) _IRQL_requires_max_(PASSIVE_LEVEL) _IRQL_requires_same_ BOOLEAN EVT_DMF_BusFilter_DeviceQueryInterface( _In_ DMFBUSCHILDDEVICE ChildDevice, _In_ IRP* Irp ); /*++ Structure: DMF_BusFilter_CONFIG Purpose: Configuration block passed to DMF_BusFilter_Initialize. The client fills this in (using DMF_BusFilter_CONFIG_INIT to zero-initialize it first) and sets optional callback pointers before calling Initialize. All EVT_* callback pointers are optional; set only the ones the client needs. The library silently skips callbacks that are NULL. Fields: DriverObject - The WDM DRIVER_OBJECT for this driver. Required. DeviceType - Device type passed to IoCreateDevice for each proxy child filter DO. If 0, the library inherits the type from the PDO (FILE_DEVICE_UNKNOWN is the typical PDO type for USB devices). Leaving this at 0 is 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 masked off by the library before use. EvtPreBusDeviceAdd - Optional. See EVT_DMF_BusFilter_PreBusDeviceAdd. EvtPostBusDeviceAdd - Optional. See EVT_DMF_BusFilter_PostBusDeviceAdd. EvtDeviceAdd - Optional. See EVT_DMF_BusFilter_DeviceAdd. EvtDeviceRemove - Optional. See EVT_DMF_BusFilter_DeviceRemove. EvtDeviceStarted - Optional. See EVT_DMF_BusFilter_DeviceStarted. EvtDeviceEnumerated - Optional. See EVT_DMF_BusFilter_DeviceEnumerated. EvtDeviceQueryId - Optional. See EVT_DMF_BusFilter_DeviceQueryId. EvtDeviceQueryInterface - Optional. See EVT_DMF_BusFilter_DeviceQueryInterface. --*/ typedef struct { // The driver object. // _In_ DRIVER_OBJECT* DriverObject; // Device type for proxy child filter DOs. 0 inherits from the PDO. // _In_ DEVICE_TYPE DeviceType; // Device characteristics for proxy child filter DOs. // _In_ ULONG DeviceCharacteristics; // Called before bus device object is created. // _In_opt_ EVT_DMF_BusFilter_PreBusDeviceAdd* EvtPreBusDeviceAdd; // Called after bus device object was created. // _In_opt_ EVT_DMF_BusFilter_PostBusDeviceAdd* EvtPostBusDeviceAdd; // Called when child proxy device was created. // _In_opt_ EVT_DMF_BusFilter_DeviceAdd* EvtDeviceAdd; // Called when child proxy device gets removed. // _In_opt_ EVT_DMF_BusFilter_DeviceRemove* EvtDeviceRemove; // Called when IRP_MN_START_DEVICE is sent to the child device. // _In_opt_ EVT_DMF_BusFilter_DeviceStarted* EvtDeviceStarted; // Called when IRP_MN_DEVICE_ENUMERATED is sent to the child device. // _In_opt_ EVT_DMF_BusFilter_DeviceEnumerated* EvtDeviceEnumerated; // Called when IRP_MN_QUERY_ID is sent to the child device. // _In_opt_ EVT_DMF_BusFilter_DeviceQueryId* EvtDeviceQueryId; // Called when IRP_MN_QUERY_INTERFACE is sent to the child device. // _In_opt_ EVT_DMF_BusFilter_DeviceQueryInterface* EvtDeviceQueryInterface; } DMF_BusFilter_CONFIG; /*++ Routine Description: Zero-initializes a DMF_BusFilter_CONFIG structure and sets the required DriverObject field. The client must call this before setting any optional callback pointers and before passing the config to DMF_BusFilter_Initialize. All optional callback pointers default to NULL (disabled). DeviceType defaults to 0, which causes the library to inherit the device type from each child PDO at attach time. Arguments: BusFilterConfig - Pointer to the config structure to initialize. DriverObject - The WDM DRIVER_OBJECT for this driver. IRQL: Any (typically PASSIVE_LEVEL from DriverEntry) Return Value: None --*/ __forceinline VOID DMF_BusFilter_CONFIG_INIT( _Out_ DMF_BusFilter_CONFIG* BusFilterConfig, _In_ PDRIVER_OBJECT DriverObject ) { RtlZeroMemory(BusFilterConfig, sizeof(DMF_BusFilter_CONFIG)); BusFilterConfig->DriverObject = DriverObject; } /*++ Routine Description: 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. It also stores the client's configuration for use in subsequent callbacks. Arguments: BusFilterConfig - Pointer to a fully populated DMF_BusFilter_CONFIG. The caller retains ownership but the library keeps a copy for the lifetime of the driver. IRQL: PASSIVE_LEVEL Return Value: STATUS_SUCCESS, or: STATUS_INVALID_PARAMETER - BusFilterConfig or DriverObject is NULL. STATUS_NOT_SUPPORTED - WdfGetDriver returned NULL (WDF not yet ready). Any WDF context-allocation failure. --*/ _IRQL_requires_max_(PASSIVE_LEVEL) _Must_inspect_result_ NTSTATUS DMF_BusFilter_Initialize( _In_ DMF_BusFilter_CONFIG* BusFilterConfig ); /*++ Routine Description: 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. Arguments: Driver - The WDFDRIVER. DeviceInit - The WDFDEVICE_INIT prepared by the framework. IRQL: PASSIVE_LEVEL Return Value: STATUS_SUCCESS, or a failure status from WdfDeviceCreate or supporting framework calls. On failure any partially created device is cleaned up. --*/ EVT_WDF_DRIVER_DEVICE_ADD DMF_BusFilter_DeviceAdd; /*++ Routine Description: 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. Arguments: ChildDevice - Opaque handle for a proxy child device. Return Value: The proxy filter DEVICE_OBJECT, or NULL if the context is missing. --*/ PDEVICE_OBJECT DMF_BusFilter_WdmDeviceObjectGet( _In_ DMFBUSCHILDDEVICE ChildDevice ); /*++ Routine Description: 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. Arguments: ChildDevice - Opaque handle for a proxy child device. Return Value: The attached (lower) DEVICE_OBJECT, or NULL if the child has an unrecognised signature or a missing context. --*/ PDEVICE_OBJECT DMF_BusFilter_WdmAttachedDeviceGet( _In_ DMFBUSCHILDDEVICE ChildDevice ); /*++ Routine Description: 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. Arguments: ChildDevice - Opaque handle for a proxy child device. Return Value: The PDO DEVICE_OBJECT, or NULL if the child has an unrecognised signature or a missing context. --*/ PDEVICE_OBJECT 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)