diff --git a/src/Dmf_BusFilter.h b/src/Dmf_BusFilter.h index da8c4d7..9b5f69f 100644 --- a/src/Dmf_BusFilter.h +++ b/src/Dmf_BusFilter.h @@ -9,7 +9,20 @@ Module Name: Abstract: - Companion file to Dmf_BusFilter.c. + 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: @@ -26,10 +39,42 @@ Environment: #if defined(DMF_KERNEL_MODE) -// Declare an opaque handle representing a filtered PDO. +// 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_ @@ -42,6 +87,32 @@ EVT_DMF_BusFilter_PreBusDeviceAdd( _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_ @@ -52,6 +123,38 @@ EVT_DMF_BusFilter_PostBusDeviceAdd( _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) @@ -62,6 +165,33 @@ EVT_DMF_BusFilter_DeviceAdd( _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) @@ -72,6 +202,33 @@ EVT_DMF_BusFilter_DeviceRemove( _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) @@ -82,6 +239,32 @@ EVT_DMF_BusFilter_DeviceStarted( _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) @@ -92,6 +275,38 @@ EVT_DMF_BusFilter_DeviceEnumerated( _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) @@ -102,6 +317,38 @@ EVT_DMF_BusFilter_DeviceQueryId( _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) @@ -112,53 +359,117 @@ EVT_DMF_BusFilter_DeviceQueryInterface( _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; - // The device type. - // + // Device type for proxy child filter DOs. 0 inherits from the PDO. + // _In_ DEVICE_TYPE DeviceType; - // The device characteristics. - // + // 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 set to child device. - // + // 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 child device. - // + // 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 child device. - // + // 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 child device. - // + // 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( @@ -171,6 +482,34 @@ DMF_BusFilter_CONFIG_INIT( 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 @@ -178,23 +517,140 @@ 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(