Docs overhaul
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
-----------------------------------------------------------------------------------------------------------------------------------
|
||||
Reference in New Issue
Block a user