1
0
Files
DmfBusFilterExtension/src/Dmf_BusFilter.h
T
2026-07-02 21:44:13 +02:00

661 lines
19 KiB
C

/*++
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)