1766 lines
52 KiB
C
1766 lines
52 KiB
C
/*++
|
|
|
|
Copyright (c) Nefarius Software Solutions e.U. All rights reserved.
|
|
Licensed under the MIT license.
|
|
|
|
Module Name:
|
|
|
|
Dmf_BusFilter.c
|
|
|
|
Abstract:
|
|
|
|
Creates the supporting plumbing for a Bus Filter Driver.
|
|
|
|
Environment:
|
|
|
|
Kernel-mode Driver Framework
|
|
|
|
--*/
|
|
|
|
#include "DmfDefinitions.h"
|
|
#include "Dmf_BusFilter.h"
|
|
#include "DmfModules.Library.Trace.h"
|
|
|
|
#include <initguid.h>
|
|
|
|
#if defined(DMF_INCLUDE_TMH)
|
|
#include "Dmf_BusFilter.tmh"
|
|
#endif
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Bus Filter
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
|
|
#if defined(DMF_KERNEL_MODE)
|
|
|
|
// WDM child device context
|
|
//
|
|
// LIFETIME NOTES
|
|
// --------------
|
|
// Each WDM filter device object created by this library lives between
|
|
// IoAttachDeviceToDeviceStackSafe (end of Relations_AddDevice) and
|
|
// IoDeleteDevice (end of Relations_RemoveDevice or DeviceCleanup).
|
|
// The mandatory teardown sequence is:
|
|
// 1. IoReleaseRemoveLockAndWait -- drain all in-flight I/O holders
|
|
// 2. IoDetachDevice -- detach filter from the PDO stack
|
|
// 3. IoDeleteDevice -- lazy-free the device object (actual
|
|
// memory release deferred to last reference)
|
|
// NOTHING may touch DeviceExtension after IoDeleteDevice returns.
|
|
//
|
|
typedef struct _WDM_CHILD_DEVICE_EXTENSION
|
|
{
|
|
// GUID to identify WDM child device.
|
|
//
|
|
GUID Signature;
|
|
|
|
// Remove lock serialises in-flight I/O against IRP_MN_REMOVE_DEVICE teardown.
|
|
// Initialized in Relations_AddDevice; released-and-waited in Relations_RemoveDevice
|
|
// (normal path) or DeviceCleanup (force path).
|
|
//
|
|
IO_REMOVE_LOCK RemoveLock;
|
|
|
|
// Target Device Object (result of IoAttachDeviceToDeviceStackSafe)
|
|
//
|
|
PDEVICE_OBJECT TargetDeviceObject;
|
|
|
|
// Physical Device Object (PDO of the bus child being filtered)
|
|
//
|
|
PDEVICE_OBJECT PhysicalDeviceObject;
|
|
|
|
// Parent ChildList entry
|
|
//
|
|
LIST_ENTRY ListEntry;
|
|
|
|
// Parent WDF device object
|
|
//
|
|
WDFDEVICE Parent;
|
|
|
|
// Child WDF wrapper object
|
|
//
|
|
DMFBUSCHILDDEVICE Child;
|
|
|
|
// TRUE if PDO appeared in the most recent bus-relations response
|
|
//
|
|
BOOLEAN IsExisting;
|
|
} WDM_CHILD_DEVICE_EXTENSION;
|
|
|
|
// Parent bus device context
|
|
//
|
|
typedef struct _PARENT_BUS_DEVICE_CONTEXT
|
|
{
|
|
// List of child device (relations)
|
|
//
|
|
LIST_ENTRY ChildList;
|
|
|
|
// Spin lock protecting ChildList access
|
|
//
|
|
KSPIN_LOCK ChildListLock;
|
|
|
|
// Count of bus-relations work items currently queued or executing.
|
|
// Used to prevent DeviceCleanup from racing a pending passive-level
|
|
// AddDevice worker.
|
|
//
|
|
volatile LONG OutstandingWorkItems;
|
|
|
|
// Signaled when OutstandingWorkItems drops to zero (initially signaled).
|
|
//
|
|
KEVENT WorkItemsIdle;
|
|
} PARENT_BUS_DEVICE_CONTEXT;
|
|
|
|
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(PARENT_BUS_DEVICE_CONTEXT, DMF_BusFilter_GetParentContext)
|
|
|
|
// Bus child device context
|
|
//
|
|
typedef struct _BUS_CHILD_DEVICE_CONTEXT
|
|
{
|
|
// WDM device object
|
|
//
|
|
PDEVICE_OBJECT DeviceObject;
|
|
} BUS_CHILD_DEVICE_CONTEXT;
|
|
|
|
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(BUS_CHILD_DEVICE_CONTEXT, DMF_BusFilter_GetChildContext)
|
|
|
|
typedef
|
|
_Function_class_(EVT_DMF_BusFilter_DispatchPnp)
|
|
_IRQL_requires_same_
|
|
_IRQL_requires_max_(PASSIVE_LEVEL)
|
|
NTSTATUS
|
|
EVT_DMF_BusFilter_DispatchPnp(
|
|
_In_ DMFBUSCHILDDEVICE ChildDevice,
|
|
_In_ PIRP Irp
|
|
);
|
|
|
|
// Module-internal context data
|
|
//
|
|
typedef struct _DMF_CONTEXT_BusFilter
|
|
{
|
|
// Copy of the module configuration
|
|
//
|
|
DMF_BusFilter_CONFIG Configuration;
|
|
|
|
// Hooked dispatch table
|
|
//
|
|
PDRIVER_DISPATCH MajorDispatchFunctions[IRP_MJ_MAXIMUM_FUNCTION + 1];
|
|
|
|
// PNP minor functions dispatch routines
|
|
//
|
|
EVT_DMF_BusFilter_DispatchPnp* PnPMinorDispatchFunctions[IRP_MN_DEVICE_ENUMERATED + 1];
|
|
} BusFilter_Context;
|
|
|
|
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(BusFilter_Context,
|
|
BusFilterContextGet);
|
|
|
|
// {678CBB8D-019F-4D07-912A-73E2E568B148}
|
|
DEFINE_GUID(GUID_DMF_BUSFILTER_SIGNATURE,
|
|
0x678cbb8d, 0x19f, 0x4d07, 0x91, 0x2a, 0x73, 0xe2, 0xe5, 0x68, 0xb1, 0x48);
|
|
|
|
// Pool tag for allocations made by this module.
|
|
//
|
|
#define DMF_BUSFILTER_POOL_TAG 'tlfB'
|
|
|
|
// Context passed to the passive-level work item used when the bus-relations
|
|
// completion routine fires at DISPATCH_LEVEL.
|
|
//
|
|
typedef struct _BUS_RELATIONS_WORK_CONTEXT
|
|
{
|
|
PIO_WORKITEM WorkItem;
|
|
WDFDEVICE Device;
|
|
PIRP Irp;
|
|
} BUS_RELATIONS_WORK_CONTEXT;
|
|
|
|
// Forward declarations
|
|
//
|
|
#pragma code_seg("PAGE")
|
|
_IRQL_requires_max_(PASSIVE_LEVEL)
|
|
static
|
|
void
|
|
DMF_BusFilter_Relations_RemoveDevice(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_ PIRP RemoveIrp
|
|
);
|
|
#pragma code_seg()
|
|
|
|
_IRQL_requires_max_(PASSIVE_LEVEL)
|
|
static
|
|
NTSTATUS
|
|
DMF_BusFilter_Relations_AddDevice(
|
|
_In_ WDFDEVICE Device,
|
|
_In_ PDEVICE_OBJECT PhysicalDeviceObject
|
|
);
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// IRP_MN_REMOVE_DEVICE teardown
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
|
|
#pragma code_seg("PAGE")
|
|
_IRQL_requires_max_(PASSIVE_LEVEL)
|
|
static
|
|
void
|
|
DMF_BusFilter_Relations_RemoveDevice(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_ PIRP RemoveIrp
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Processes child device removal. Always performs a full teardown regardless of
|
|
the IsExisting flag so that surprise removals are handled correctly.
|
|
|
|
Mandatory teardown order (WDM filter driver guidance):
|
|
1. IoReleaseRemoveLockAndWait -- drain all in-flight I/O holders; after this
|
|
returns, no new I/O can enter the device.
|
|
2. RemoveEntryList -- unlink from the parent's ChildList.
|
|
3. EvtDeviceRemove callback -- client notification.
|
|
4. WdfObjectDelete -- destroy the WDF wrapper object.
|
|
5. IoDetachDevice -- detach filter from the PDO stack.
|
|
6. IoDeleteDevice -- lazy-free the filter device object (memory
|
|
released when the last reference drops; do
|
|
NOT touch DeviceExtension after this call).
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Filter device object whose stack is being torn down.
|
|
RemoveIrp - The IRP_MN_REMOVE_DEVICE IRP; used as the remove-lock tag to
|
|
match the IoAcquireRemoveLock call made in DispatchHandler.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
KLOCK_QUEUE_HANDLE handle;
|
|
WDM_CHILD_DEVICE_EXTENSION* extension = (WDM_CHILD_DEVICE_EXTENSION*)DeviceObject->DeviceExtension;
|
|
PARENT_BUS_DEVICE_CONTEXT* parentContext = DMF_BusFilter_GetParentContext(extension->Parent);
|
|
const BusFilter_Context* context = BusFilterContextGet(WdfGetDriver());
|
|
const DMF_BusFilter_CONFIG* config = &context->Configuration;
|
|
|
|
FuncEntry(DMF_TRACE);
|
|
|
|
PAGED_CODE();
|
|
|
|
// Step 1: release the remove lock (matching the IoAcquireRemoveLock in
|
|
// DispatchHandler) AND wait for all other in-flight holders to drain.
|
|
// After this call, no concurrent dispatcher can be accessing the extension
|
|
// and no new dispatch will succeed (IoAcquireRemoveLock returns
|
|
// STATUS_DELETE_PENDING).
|
|
//
|
|
IoReleaseRemoveLockAndWait(&extension->RemoveLock, RemoveIrp);
|
|
|
|
TraceVerbose(DMF_TRACE, "%!FUNC! called at %!irql!", KeGetCurrentIrql());
|
|
|
|
// Step 2: unlink from parent's list while holding the spinlock.
|
|
//
|
|
#pragma warning(disable: 28150) // elevates to DISPATCH_LEVEL
|
|
KeAcquireInStackQueuedSpinLock(&parentContext->ChildListLock,
|
|
&handle);
|
|
RemoveEntryList(&extension->ListEntry);
|
|
KeReleaseInStackQueuedSpinLock(&handle);
|
|
#pragma warning(default: 28150) // drops to PASSIVE_LEVEL
|
|
|
|
// Steps 3-6: notify client, then destroy objects in the correct order.
|
|
//
|
|
if (config->EvtDeviceRemove)
|
|
{
|
|
config->EvtDeviceRemove(extension->Parent, extension->Child);
|
|
}
|
|
|
|
WdfObjectDelete(extension->Child);
|
|
IoDetachDevice(extension->TargetDeviceObject);
|
|
IoDeleteDevice(DeviceObject); // Extension is invalid after this point
|
|
|
|
FuncExitNoReturn(DMF_TRACE);
|
|
}
|
|
#pragma code_seg()
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Child PnP dispatch
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
|
|
_IRQL_requires_max_(DISPATCH_LEVEL)
|
|
static
|
|
NTSTATUS
|
|
DMF_BusFilter_DispatchPnp(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_ PIRP Irp,
|
|
_In_ UCHAR MinorCode
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Handles PnP requests for child filter devices.
|
|
|
|
For IRP_MN_REMOVE_DEVICE the remove lock is released inside
|
|
Relations_RemoveDevice via IoReleaseRemoveLockAndWait. Callers must NOT
|
|
call IoReleaseRemoveLock again after this function returns for that minor code.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Child filter device object.
|
|
Irp - Irp with PnP request.
|
|
MinorCode - PnP request minor code.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS ntStatus;
|
|
const WDM_CHILD_DEVICE_EXTENSION* extension = (WDM_CHILD_DEVICE_EXTENSION*)DeviceObject->DeviceExtension;
|
|
const BusFilter_Context* context = BusFilterContextGet(WdfGetDriver());
|
|
|
|
if (MinorCode == IRP_MN_REMOVE_DEVICE)
|
|
{
|
|
// WDM filter driver guidance: forward the IRP to the lower driver FIRST,
|
|
// then perform our own teardown. Saving TargetDeviceObject to a local
|
|
// before teardown prevents a use-after-free: Relations_RemoveDevice calls
|
|
// IoDeleteDevice, making extension memory invalid before we could forward.
|
|
//
|
|
// PnP IRPs always arrive at PASSIVE_LEVEL so IoForwardIrpSynchronously
|
|
// is safe here even though the function is annotated DISPATCH_LEVEL max
|
|
// for other minor codes.
|
|
//
|
|
PDEVICE_OBJECT targetDevice = extension->TargetDeviceObject;
|
|
|
|
if (!IoForwardIrpSynchronously(targetDevice, Irp))
|
|
{
|
|
Irp->IoStatus.Status = STATUS_NO_SUCH_DEVICE;
|
|
}
|
|
|
|
ntStatus = Irp->IoStatus.Status;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
|
|
// Tear down now that the IRP has been forwarded and completed.
|
|
// Relations_RemoveDevice calls IoReleaseRemoveLockAndWait, which
|
|
// releases the lock that was acquired in DispatchHandler for this IRP.
|
|
//
|
|
DMF_BusFilter_Relations_RemoveDevice(DeviceObject, Irp);
|
|
|
|
return ntStatus;
|
|
}
|
|
|
|
if (MinorCode <= IRP_MN_DEVICE_ENUMERATED)
|
|
{
|
|
if (context->PnPMinorDispatchFunctions[MinorCode] != NULL)
|
|
{
|
|
return context->PnPMinorDispatchFunctions[MinorCode](extension->Child, Irp);
|
|
}
|
|
}
|
|
|
|
// Forward to lower driver
|
|
//
|
|
IoSkipCurrentIrpStackLocation(Irp);
|
|
return IoCallDriver(extension->TargetDeviceObject,
|
|
Irp);
|
|
}
|
|
|
|
_IRQL_requires_max_(DISPATCH_LEVEL)
|
|
static
|
|
NTSTATUS
|
|
DMF_BusFilter_DispatchHandler(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_ PIRP Irp
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Dispatch routine handler for all IRPs.
|
|
|
|
For child devices (those tagged with GUID_DMF_BUSFILTER_SIGNATURE), the
|
|
remove lock is acquired here to serialize all dispatch activity against
|
|
IRP_MN_REMOVE_DEVICE teardown.
|
|
|
|
Remove-lock release:
|
|
- IRP_MN_REMOVE_DEVICE: released inside Relations_RemoveDevice via
|
|
IoReleaseRemoveLockAndWait (do NOT release again here).
|
|
- All other PnP minors: released after DispatchPnp returns.
|
|
- Non-PnP IRPs: released BEFORE IoCallDriver (after saving TargetDeviceObject
|
|
to a local) so the lock does not need to span an asynchronous IRP completion.
|
|
|
|
Power IRP passthrough: IRP_MJ_POWER is forwarded with IoSkip + IoCallDriver
|
|
without calling PoStartNextPowerIrp. This is correct on the Windows 10+
|
|
baseline targeted by this library (PoStartNextPowerIrp is obsolete; the I/O
|
|
manager handles power IRP serialisation automatically).
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Target device object.
|
|
Irp - Irp with request.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS ntStatus;
|
|
WDM_CHILD_DEVICE_EXTENSION* extension = (WDM_CHILD_DEVICE_EXTENSION*)DeviceObject->DeviceExtension;
|
|
const PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
|
|
const BusFilter_Context* context = BusFilterContextGet(WdfGetDriver());
|
|
|
|
// If the device object does not have our signature it is the WDF FDO (or
|
|
// another device object owned by this driver). Delegate to the saved handler.
|
|
//
|
|
if (!IsEqualGUID(&extension->Signature,
|
|
&GUID_DMF_BUSFILTER_SIGNATURE))
|
|
{
|
|
return context->MajorDispatchFunctions[stack->MajorFunction](DeviceObject, Irp);
|
|
}
|
|
|
|
// Acquire the remove lock before touching the extension or forwarding the IRP.
|
|
// This prevents IRP_MN_REMOVE_DEVICE teardown from deleting the device object
|
|
// while we are still inside the dispatch routine.
|
|
//
|
|
ntStatus = IoAcquireRemoveLock(&extension->RemoveLock, Irp);
|
|
if (!NT_SUCCESS(ntStatus))
|
|
{
|
|
// Teardown is in progress; reject new I/O.
|
|
Irp->IoStatus.Status = STATUS_DELETE_PENDING;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
return STATUS_DELETE_PENDING;
|
|
}
|
|
|
|
if (stack->MajorFunction == IRP_MJ_PNP)
|
|
{
|
|
// Capture the minor code BEFORE dispatching. For IRP_MN_REMOVE_DEVICE,
|
|
// DispatchPnp completes the IRP (invalidating `stack`) and deletes the
|
|
// device object (invalidating `extension`). Re-reading stack->MinorFunction
|
|
// afterwards is a use-after-free that can steer us into IoReleaseRemoveLock
|
|
// on the freed extension.
|
|
//
|
|
const UCHAR minorFunction = stack->MinorFunction;
|
|
|
|
ntStatus = DMF_BusFilter_DispatchPnp(DeviceObject, Irp, minorFunction);
|
|
|
|
// For REMOVE_DEVICE, IoReleaseRemoveLockAndWait inside Relations_RemoveDevice
|
|
// has already released the lock (and waited for all other holders). A second
|
|
// release here would corrupt the lock count.
|
|
//
|
|
if (minorFunction != IRP_MN_REMOVE_DEVICE)
|
|
{
|
|
// Safe: this is a non-REMOVE minor, so no teardown path ran, and the
|
|
// IoAcquireRemoveLock reference taken at function entry is still
|
|
// outstanding here. That outstanding reference blocks any concurrent
|
|
// IoReleaseRemoveLockAndWait (in Relations_RemoveDevice / DeviceCleanup)
|
|
// from completing IoDeleteDevice, so `extension` is guaranteed alive at
|
|
// the moment we release. We must not touch `extension` after this call.
|
|
//
|
|
IoReleaseRemoveLock(&extension->RemoveLock, Irp);
|
|
}
|
|
|
|
return ntStatus;
|
|
}
|
|
|
|
// Non-PnP pass-through: save TargetDeviceObject then release the lock before
|
|
// IoCallDriver so the remove-lock does not span the asynchronous completion.
|
|
// After the release, IoDeleteDevice may run concurrently but IoCallDriver does
|
|
// not reference the filter extension -- only the already-saved target pointer.
|
|
//
|
|
PDEVICE_OBJECT targetDevice = extension->TargetDeviceObject;
|
|
IoReleaseRemoveLock(&extension->RemoveLock, Irp);
|
|
|
|
IoSkipCurrentIrpStackLocation(Irp);
|
|
return IoCallDriver(targetDevice, Irp);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Child device creation
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
|
|
// IoCreateDevice and IoAttachDeviceToDeviceStackSafe both require PASSIVE_LEVEL.
|
|
// The annotation is corrected from the original APC_LEVEL.
|
|
// Note: this function is NOT in the PAGE segment because it acquires a spinlock
|
|
// (which momentarily elevates to DISPATCH_LEVEL), making paged memory unsafe.
|
|
//
|
|
_IRQL_requires_max_(PASSIVE_LEVEL)
|
|
static
|
|
NTSTATUS
|
|
DMF_BusFilter_Relations_AddDevice(
|
|
_In_ WDFDEVICE Device,
|
|
_In_ PDEVICE_OBJECT PhysicalDeviceObject
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Creates a proxy filter device object for a bus PDO.
|
|
|
|
Attach window: bus-relations completion time (i.e. after the lower bus
|
|
driver has populated DEVICE_RELATIONS but before the PnP manager has
|
|
dispatched IRP_MN_START_DEVICE to the child devnode's FDO) is the only
|
|
safe window for a bus filter to attach. Attaching earlier races the PDO
|
|
creation; attaching later races FDO construction and IRP dispatch.
|
|
|
|
STATUS_MORE_PROCESSING_REQUIRED in the completion routine (which calls this
|
|
function) keeps the DEVICE_RELATIONS array -- and therefore the bus driver's
|
|
references on each PDO -- alive until we finish attaching. Without that hold,
|
|
the PDO could be released and deleted while IoAttachDeviceToDeviceStackSafe
|
|
is executing.
|
|
|
|
Arguments:
|
|
|
|
Device - Parent WDF bus filter device.
|
|
PhysicalDeviceObject - Bus PDO to filter.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS ntStatus = STATUS_NOT_IMPLEMENTED;
|
|
WDF_OBJECT_ATTRIBUTES attributes;
|
|
PLIST_ENTRY entry = NULL;
|
|
KLOCK_QUEUE_HANDLE handle;
|
|
WDM_CHILD_DEVICE_EXTENSION* childExtension = NULL;
|
|
PDEVICE_OBJECT filterDeviceObject = NULL;
|
|
BOOLEAN preexisting = FALSE;
|
|
DMFBUSCHILDDEVICE child = NULL;
|
|
BUS_CHILD_DEVICE_CONTEXT* childContext = NULL;
|
|
PARENT_BUS_DEVICE_CONTEXT* parentContext = DMF_BusFilter_GetParentContext(Device);
|
|
const PDEVICE_OBJECT deviceObject = WdfDeviceWdmGetDeviceObject(Device);
|
|
const BusFilter_Context* context = BusFilterContextGet(WdfGetDriver());
|
|
const DMF_BusFilter_CONFIG* config = &context->Configuration;
|
|
|
|
FuncEntry(DMF_TRACE);
|
|
|
|
if (parentContext == NULL)
|
|
{
|
|
ntStatus = STATUS_INVALID_DEVICE_STATE;
|
|
goto Exit;
|
|
}
|
|
|
|
KeAcquireInStackQueuedSpinLock(&parentContext->ChildListLock,
|
|
&handle);
|
|
|
|
// Find and update PDO status
|
|
//
|
|
for (
|
|
entry = parentContext->ChildList.Flink;
|
|
entry != &parentContext->ChildList;
|
|
entry = entry->Flink
|
|
)
|
|
{
|
|
childExtension = CONTAINING_RECORD(entry,
|
|
WDM_CHILD_DEVICE_EXTENSION,
|
|
ListEntry);
|
|
|
|
if (childExtension->PhysicalDeviceObject == PhysicalDeviceObject)
|
|
{
|
|
preexisting = TRUE;
|
|
childExtension->IsExisting = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
KeReleaseInStackQueuedSpinLock(&handle);
|
|
|
|
if (preexisting)
|
|
{
|
|
ntStatus = STATUS_SUCCESS;
|
|
goto Exit;
|
|
}
|
|
|
|
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes,
|
|
BUS_CHILD_DEVICE_CONTEXT);
|
|
attributes.ParentObject = Device;
|
|
|
|
// Create piggyback framework object for WDM child device object
|
|
//
|
|
ntStatus = WdfObjectCreate(&attributes, (WDFOBJECT*)&child);
|
|
if (!NT_SUCCESS(ntStatus))
|
|
{
|
|
TraceError(DMF_TRACE, "WdfObjectCreate fails: ntStatus=%!STATUS!", ntStatus);
|
|
goto Exit;
|
|
}
|
|
|
|
// Derive the device type from the PDO when the client did not specify one.
|
|
// A filter whose device type differs from the stack it joins can cause
|
|
// I/O manager policy mismatches (e.g. buffering mode checks).
|
|
//
|
|
const DEVICE_TYPE deviceType = (config->DeviceType != 0)
|
|
? config->DeviceType
|
|
: PhysicalDeviceObject->DeviceType;
|
|
|
|
// Create WDM device
|
|
//
|
|
ntStatus = IoCreateDevice(deviceObject->DriverObject,
|
|
sizeof(WDM_CHILD_DEVICE_EXTENSION),
|
|
NULL,
|
|
deviceType,
|
|
FILE_DEVICE_SECURE_OPEN | config->DeviceCharacteristics,
|
|
FALSE,
|
|
&filterDeviceObject);
|
|
if (!NT_SUCCESS(ntStatus))
|
|
{
|
|
TraceError(DMF_TRACE, "IoCreateDevice fails: ntStatus=%!STATUS!", ntStatus);
|
|
goto Exit;
|
|
}
|
|
|
|
// Link WDM and WDF device together.
|
|
//
|
|
childContext = DMF_BusFilter_GetChildContext(child);
|
|
childContext->DeviceObject = filterDeviceObject;
|
|
|
|
childExtension = (WDM_CHILD_DEVICE_EXTENSION*)filterDeviceObject->DeviceExtension;
|
|
RtlZeroMemory(childExtension,
|
|
sizeof(WDM_CHILD_DEVICE_EXTENSION));
|
|
RtlCopyMemory(&childExtension->Signature,
|
|
&GUID_DMF_BUSFILTER_SIGNATURE,
|
|
sizeof(GUID));
|
|
|
|
// Initialise the remove lock before the device is attached to any stack
|
|
// or its DO_DEVICE_INITIALIZING flag is cleared. The lock must be ready
|
|
// before the first possible IoAcquireRemoveLock call.
|
|
//
|
|
IoInitializeRemoveLock(&childExtension->RemoveLock,
|
|
DMF_BUSFILTER_POOL_TAG,
|
|
0, // MaxLockedMinutes (0 = no timeout in free builds)
|
|
0); // HighWatermark (0 = no per-lock tag tracking)
|
|
|
|
childExtension->Parent = Device;
|
|
childExtension->Child = child;
|
|
childExtension->PhysicalDeviceObject = PhysicalDeviceObject;
|
|
|
|
// Use the safe variant to avoid a stale-pointer race: IoAttachDeviceToDeviceStack
|
|
// (non-safe) can return a pointer to a device that is concurrently being deleted.
|
|
// IoAttachDeviceToDeviceStackSafe returns an NTSTATUS and a valid target pointer
|
|
// only if the target is still alive when the attach completes.
|
|
//
|
|
ntStatus = IoAttachDeviceToDeviceStackSafe(filterDeviceObject,
|
|
PhysicalDeviceObject,
|
|
&childExtension->TargetDeviceObject);
|
|
if (!NT_SUCCESS(ntStatus))
|
|
{
|
|
TraceError(DMF_TRACE, "IoAttachDeviceToDeviceStackSafe fails: ntStatus=%!STATUS!", ntStatus);
|
|
IoDeleteDevice(filterDeviceObject);
|
|
filterDeviceObject = NULL;
|
|
goto Exit;
|
|
}
|
|
|
|
filterDeviceObject->Flags |= childExtension->TargetDeviceObject->Flags &
|
|
(DO_BUFFERED_IO | DO_DIRECT_IO | DO_POWER_INRUSH | DO_POWER_PAGABLE);
|
|
|
|
if (config->EvtDeviceAdd)
|
|
{
|
|
ntStatus = config->EvtDeviceAdd(Device, child);
|
|
|
|
if (!NT_SUCCESS(ntStatus))
|
|
{
|
|
TraceError(DMF_TRACE, "EvtDeviceAdd fails: ntStatus=%!STATUS!", ntStatus);
|
|
|
|
IoDetachDevice(childExtension->TargetDeviceObject);
|
|
IoDeleteDevice(filterDeviceObject);
|
|
filterDeviceObject = NULL;
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
KeAcquireInStackQueuedSpinLock(&parentContext->ChildListLock,
|
|
&handle);
|
|
|
|
// Re-scan under the lock before inserting to guard against a duplicate created
|
|
// by a concurrent bus-relations completion.
|
|
//
|
|
BOOLEAN duplicate = FALSE;
|
|
for (
|
|
PLIST_ENTRY scanEntry = parentContext->ChildList.Flink;
|
|
scanEntry != &parentContext->ChildList;
|
|
scanEntry = scanEntry->Flink
|
|
)
|
|
{
|
|
WDM_CHILD_DEVICE_EXTENSION* scanExt = CONTAINING_RECORD(scanEntry,
|
|
WDM_CHILD_DEVICE_EXTENSION,
|
|
ListEntry);
|
|
if (scanExt->PhysicalDeviceObject == PhysicalDeviceObject)
|
|
{
|
|
duplicate = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!duplicate)
|
|
{
|
|
childExtension->IsExisting = TRUE;
|
|
InsertTailList(&parentContext->ChildList,
|
|
&childExtension->ListEntry);
|
|
}
|
|
|
|
KeReleaseInStackQueuedSpinLock(&handle);
|
|
|
|
if (duplicate)
|
|
{
|
|
// A concurrent AddDevice beat us to it; discard our newly created device.
|
|
//
|
|
IoDetachDevice(childExtension->TargetDeviceObject);
|
|
IoDeleteDevice(filterDeviceObject);
|
|
filterDeviceObject = NULL;
|
|
WdfObjectDelete(child);
|
|
child = NULL;
|
|
ntStatus = STATUS_SUCCESS;
|
|
goto Exit;
|
|
}
|
|
|
|
// The device is fully initialised; clear the initialising flag to allow
|
|
// IRPs to be dispatched to it.
|
|
//
|
|
filterDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
|
|
|
|
ntStatus = STATUS_SUCCESS;
|
|
|
|
Exit:
|
|
|
|
if (!NT_SUCCESS(ntStatus) && child != NULL)
|
|
{
|
|
WdfObjectDelete(child);
|
|
}
|
|
|
|
FuncExit(DMF_TRACE, "status=%!STATUS!", ntStatus);
|
|
|
|
return ntStatus;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Bus-relations completion: passive-level processing
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
|
|
// Helper that resets IsExisting flags and calls AddDevice for every PDO in the
|
|
// DEVICE_RELATIONS returned by the lower bus driver. Must be called at PASSIVE_LEVEL
|
|
// because DMF_BusFilter_Relations_AddDevice requires it.
|
|
//
|
|
#pragma code_seg("PAGE")
|
|
_IRQL_requires_max_(PASSIVE_LEVEL)
|
|
static
|
|
void
|
|
DMF_BusFilter_ProcessBusRelations(
|
|
_In_ WDFDEVICE Device,
|
|
_In_ PIRP Irp
|
|
)
|
|
{
|
|
KLOCK_QUEUE_HANDLE handle;
|
|
WDM_CHILD_DEVICE_EXTENSION* childExtension = NULL;
|
|
PARENT_BUS_DEVICE_CONTEXT* parentContext = DMF_BusFilter_GetParentContext(Device);
|
|
|
|
PAGED_CODE();
|
|
|
|
if (parentContext == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
#pragma warning(disable: 28150)
|
|
KeAcquireInStackQueuedSpinLock(&parentContext->ChildListLock,
|
|
&handle);
|
|
|
|
// Reset child states so stale entries can be detected later
|
|
//
|
|
for (
|
|
LIST_ENTRY* entry = parentContext->ChildList.Flink;
|
|
entry != &parentContext->ChildList;
|
|
entry = entry->Flink
|
|
)
|
|
{
|
|
childExtension = CONTAINING_RECORD(entry,
|
|
WDM_CHILD_DEVICE_EXTENSION,
|
|
ListEntry);
|
|
childExtension->IsExisting = FALSE;
|
|
}
|
|
|
|
KeReleaseInStackQueuedSpinLock(&handle);
|
|
#pragma warning(default: 28150)
|
|
|
|
const PDEVICE_RELATIONS deviceRelations = (PDEVICE_RELATIONS)Irp->IoStatus.Information;
|
|
|
|
if (deviceRelations == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (ULONG index = 0; index < deviceRelations->Count; index++)
|
|
{
|
|
TraceVerbose(DMF_TRACE, "%!FUNC! called at %!irql!", KeGetCurrentIrql());
|
|
|
|
NTSTATUS ntStatus = DMF_BusFilter_Relations_AddDevice(Device,
|
|
deviceRelations->Objects[index]);
|
|
if (!NT_SUCCESS(ntStatus))
|
|
{
|
|
TraceError(DMF_TRACE, "DMF_BusFilter_Relations_AddDevice fails: ntStatus=%!STATUS!", ntStatus);
|
|
}
|
|
}
|
|
}
|
|
#pragma code_seg()
|
|
|
|
// Work item routine: runs at PASSIVE_LEVEL when the completion routine fires at
|
|
// DISPATCH_LEVEL and cannot call IoCreateDevice / IoAttachDeviceToDeviceStackSafe
|
|
// directly.
|
|
//
|
|
static
|
|
IO_WORKITEM_ROUTINE DMF_BusFilter_QueryBusRelationsWorker;
|
|
|
|
static
|
|
void
|
|
DMF_BusFilter_QueryBusRelationsWorker(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_opt_ PVOID Context
|
|
)
|
|
{
|
|
UNREFERENCED_PARAMETER(DeviceObject);
|
|
|
|
BUS_RELATIONS_WORK_CONTEXT* workCtx = (BUS_RELATIONS_WORK_CONTEXT*)Context;
|
|
|
|
if (workCtx == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
PIRP irp = workCtx->Irp;
|
|
WDFDEVICE device = workCtx->Device;
|
|
|
|
DMF_BusFilter_ProcessBusRelations(device, irp);
|
|
|
|
// Signal the idle event if this was the last outstanding work item, so that
|
|
// DeviceCleanup can proceed once all workers have finished.
|
|
//
|
|
PARENT_BUS_DEVICE_CONTEXT* parentContext = DMF_BusFilter_GetParentContext(device);
|
|
if (parentContext != NULL)
|
|
{
|
|
if (InterlockedDecrement(&parentContext->OutstandingWorkItems) == 0)
|
|
{
|
|
KeSetEvent(&parentContext->WorkItemsIdle, IO_NO_INCREMENT, FALSE);
|
|
}
|
|
}
|
|
|
|
IoFreeWorkItem(workCtx->WorkItem);
|
|
ExFreePoolWithTag(workCtx, DMF_BUSFILTER_POOL_TAG);
|
|
|
|
// Resume IRP completion propagation upward through the stack. The completion
|
|
// routine returned STATUS_MORE_PROCESSING_REQUIRED which halted it at our level.
|
|
//
|
|
IoCompleteRequest(irp, IO_NO_INCREMENT);
|
|
}
|
|
|
|
// Completion routine for IRP_MN_QUERY_DEVICE_RELATIONS / BusRelations.
|
|
//
|
|
// This routine is intentionally NOT in the PAGE segment and does NOT call
|
|
// PAGED_CODE() because IO completion routines may run at DISPATCH_LEVEL.
|
|
// When that happens the passive-level AddDevice work is deferred to a work item.
|
|
//
|
|
// Returning STATUS_MORE_PROCESSING_REQUIRED to hold the IRP is intentional and
|
|
// load-bearing: it keeps the DEVICE_RELATIONS array (and the bus driver's
|
|
// references on each PDO) alive until the passive-level worker has finished
|
|
// attaching filter devices. Without this hold the PDOs could be released before
|
|
// IoAttachDeviceToDeviceStackSafe completes.
|
|
//
|
|
_IRQL_requires_max_(DISPATCH_LEVEL)
|
|
static
|
|
NTSTATUS
|
|
DMF_BusFilter_QueryBusRelationsCompleted(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_ PIRP Irp,
|
|
_In_ WDFDEVICE Device
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Bus relations query completion routine.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Parent device object (unused).
|
|
Irp - Query Bus Relations IRP.
|
|
Device - Target WDFDEVICE.
|
|
|
|
Return Value:
|
|
|
|
STATUS_CONTINUE_COMPLETION or STATUS_MORE_PROCESSING_REQUIRED
|
|
|
|
--*/
|
|
{
|
|
UNREFERENCED_PARAMETER(DeviceObject);
|
|
|
|
FuncEntry(DMF_TRACE);
|
|
|
|
if (Irp->PendingReturned)
|
|
{
|
|
IoMarkIrpPending(Irp);
|
|
}
|
|
|
|
if (!NT_SUCCESS(Irp->IoStatus.Status))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// If completion is running at DISPATCH_LEVEL, IoCreateDevice and
|
|
// IoAttachDeviceToDeviceStackSafe cannot be called safely. Defer to a
|
|
// passive-level work item.
|
|
//
|
|
if (KeGetCurrentIrql() > PASSIVE_LEVEL)
|
|
{
|
|
BUS_RELATIONS_WORK_CONTEXT* workCtx =
|
|
(BUS_RELATIONS_WORK_CONTEXT*)ExAllocatePoolWithTag(NonPagedPool,
|
|
sizeof(BUS_RELATIONS_WORK_CONTEXT),
|
|
DMF_BUSFILTER_POOL_TAG);
|
|
if (workCtx != NULL)
|
|
{
|
|
workCtx->Device = Device;
|
|
workCtx->Irp = Irp;
|
|
workCtx->WorkItem = IoAllocateWorkItem(WdfDeviceWdmGetDeviceObject(Device));
|
|
|
|
if (workCtx->WorkItem != NULL)
|
|
{
|
|
// Increment the outstanding-work counter BEFORE queuing.
|
|
// The worker decrements it and signals the idle event when done.
|
|
// This prevents DeviceCleanup from racing the worker.
|
|
//
|
|
PARENT_BUS_DEVICE_CONTEXT* parentContext = DMF_BusFilter_GetParentContext(Device);
|
|
if (parentContext != NULL)
|
|
{
|
|
InterlockedIncrement(&parentContext->OutstandingWorkItems);
|
|
KeClearEvent(&parentContext->WorkItemsIdle);
|
|
}
|
|
|
|
IoQueueWorkItem(workCtx->WorkItem,
|
|
DMF_BusFilter_QueryBusRelationsWorker,
|
|
DelayedWorkQueue,
|
|
workCtx);
|
|
|
|
FuncExitNoReturn(DMF_TRACE);
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
|
|
ExFreePoolWithTag(workCtx, DMF_BUSFILTER_POOL_TAG);
|
|
}
|
|
|
|
TraceError(DMF_TRACE, "Failed to allocate work item for bus relations at DISPATCH_LEVEL; skipping child creation");
|
|
goto Exit;
|
|
}
|
|
|
|
// PASSIVE_LEVEL path: process child devices inline.
|
|
//
|
|
DMF_BusFilter_ProcessBusRelations(Device, Irp);
|
|
|
|
Exit:
|
|
|
|
FuncExitNoReturn(DMF_TRACE);
|
|
|
|
return STATUS_CONTINUE_COMPLETION;
|
|
}
|
|
|
|
_IRQL_requires_max_(DISPATCH_LEVEL)
|
|
static
|
|
NTSTATUS
|
|
DMF_BusFilter_PreprocessQueryBusRelations(
|
|
_In_ WDFDEVICE Device,
|
|
_In_ PIRP Irp
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Pre-processes IRP_MJ_PNP / IRP_MN_QUERY_DEVICE_RELATIONS.
|
|
|
|
Arguments:
|
|
|
|
Device - Parent WDF device.
|
|
Irp - IRP_MJ_PNP / IRP_MN_QUERY_DEVICE_RELATIONS request.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS
|
|
|
|
--*/
|
|
{
|
|
const PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
|
|
|
|
if (
|
|
stack->MajorFunction != IRP_MJ_PNP ||
|
|
stack->MinorFunction != IRP_MN_QUERY_DEVICE_RELATIONS ||
|
|
stack->Parameters.QueryDeviceRelations.Type != BusRelations
|
|
)
|
|
{
|
|
IoSkipCurrentIrpStackLocation(Irp);
|
|
}
|
|
else
|
|
{
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
IoSetCompletionRoutine(Irp,
|
|
(PIO_COMPLETION_ROUTINE)DMF_BusFilter_QueryBusRelationsCompleted,
|
|
Device,
|
|
TRUE,
|
|
TRUE,
|
|
TRUE);
|
|
}
|
|
|
|
return WdfDeviceWdmDispatchPreprocessedIrp(Device,
|
|
Irp);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// PnP minor-code handlers
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
|
|
#pragma code_seg("PAGE")
|
|
static
|
|
NTSTATUS
|
|
DMF_BusFilter_PnP_StartDevice(
|
|
_In_ DMFBUSCHILDDEVICE ChildDevice,
|
|
_In_ PIRP Irp
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Handles IRP_MN_START_DEVICE
|
|
|
|
Arguments:
|
|
|
|
ChildDevice - Associated child device.
|
|
Irp - IRP_MJ_PNP / IRP_MN_START_DEVICE request.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS ntStatus;
|
|
const BusFilter_Context* context = BusFilterContextGet(WdfGetDriver());
|
|
const DMF_BusFilter_CONFIG* config = &context->Configuration;
|
|
|
|
FuncEntry(DMF_TRACE);
|
|
|
|
PAGED_CODE();
|
|
|
|
if (!IoForwardIrpSynchronously(DMF_BusFilter_WdmAttachedDeviceGet(ChildDevice),
|
|
Irp))
|
|
{
|
|
TraceError(DMF_TRACE, "IoForwardIrpSynchronously fails: Irp=0x%p", Irp);
|
|
|
|
Irp->IoStatus.Status = STATUS_NO_SUCH_DEVICE;
|
|
}
|
|
else if (NT_SUCCESS(Irp->IoStatus.Status) && config->EvtDeviceStarted)
|
|
{
|
|
config->EvtDeviceStarted(ChildDevice,
|
|
Irp);
|
|
}
|
|
|
|
ntStatus = Irp->IoStatus.Status;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
|
|
FuncExit(DMF_TRACE, "ntStatus=%!STATUS!", ntStatus);
|
|
|
|
return ntStatus;
|
|
}
|
|
#pragma code_seg()
|
|
|
|
static
|
|
NTSTATUS
|
|
DMF_BusFilter_PnP_DeviceEnumerated(
|
|
_In_ DMFBUSCHILDDEVICE ChildDevice,
|
|
_In_ PIRP Irp
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Handles IRP_MN_DEVICE_ENUMERATED.
|
|
|
|
Arguments:
|
|
|
|
ChildDevice - Associated child device.
|
|
Irp - IRP_MJ_PNP / IRP_MN_DEVICE_ENUMERATED request.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS ntStatus;
|
|
const BusFilter_Context* context = BusFilterContextGet(WdfGetDriver());
|
|
const DMF_BusFilter_CONFIG* config = &context->Configuration;
|
|
|
|
FuncEntry(DMF_TRACE);
|
|
|
|
if (config->EvtDeviceEnumerated)
|
|
{
|
|
config->EvtDeviceEnumerated(ChildDevice,
|
|
Irp);
|
|
}
|
|
|
|
// Forward to the parent bus driver
|
|
//
|
|
IoSkipCurrentIrpStackLocation(Irp);
|
|
ntStatus = IoCallDriver(DMF_BusFilter_WdmAttachedDeviceGet(ChildDevice),
|
|
Irp);
|
|
|
|
FuncExit(DMF_TRACE, "ntStatus=%!STATUS!", ntStatus);
|
|
|
|
return ntStatus;
|
|
}
|
|
|
|
static
|
|
NTSTATUS
|
|
DMF_BusFilter_PnP_QueryId(
|
|
_In_ DMFBUSCHILDDEVICE ChildDevice,
|
|
_In_ PIRP Irp
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Handles IRP_MN_QUERY_ID.
|
|
|
|
Arguments:
|
|
|
|
ChildDevice - Associated child device.
|
|
Irp - IRP_MJ_PNP / IRP_MN_QUERY_ID request.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS ntStatus;
|
|
const BusFilter_Context* context = BusFilterContextGet(WdfGetDriver());
|
|
const DMF_BusFilter_CONFIG* config = &context->Configuration;
|
|
|
|
FuncEntry(DMF_TRACE);
|
|
|
|
// Forward immediately if client driver has no handler
|
|
//
|
|
if (config->EvtDeviceQueryId == NULL)
|
|
{
|
|
IoSkipCurrentIrpStackLocation(Irp);
|
|
ntStatus = IoCallDriver(DMF_BusFilter_WdmAttachedDeviceGet(ChildDevice),
|
|
Irp);
|
|
|
|
FuncExit(DMF_TRACE, "status=%!STATUS!", ntStatus);
|
|
return ntStatus;
|
|
}
|
|
|
|
// If client driver didn't do anything with the IRP...
|
|
//
|
|
if (!config->EvtDeviceQueryId(ChildDevice, Irp))
|
|
{
|
|
// ...forward it prior to completion
|
|
//
|
|
if (!IoForwardIrpSynchronously(DMF_BusFilter_WdmAttachedDeviceGet(ChildDevice),
|
|
Irp))
|
|
{
|
|
TraceError(DMF_TRACE, "IoForwardIrpSynchronously fails: Irp=0x%p", Irp);
|
|
|
|
Irp->IoStatus.Status = STATUS_NO_SUCH_DEVICE;
|
|
}
|
|
}
|
|
|
|
// Complete the Irp.
|
|
//
|
|
ntStatus = Irp->IoStatus.Status;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
|
|
FuncExit(DMF_TRACE, "status=%!STATUS!", ntStatus);
|
|
|
|
return ntStatus;
|
|
}
|
|
|
|
static
|
|
NTSTATUS
|
|
DMF_BusFilter_PnP_QueryInterface(
|
|
_In_ DMFBUSCHILDDEVICE ChildDevice,
|
|
_In_ PIRP Irp
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Handles IRP_MN_QUERY_INTERFACE.
|
|
|
|
Arguments:
|
|
|
|
ChildDevice - Associated child device.
|
|
Irp - IRP_MJ_PNP / IRP_MN_QUERY_INTERFACE request.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS ntStatus;
|
|
const BusFilter_Context* context = BusFilterContextGet(WdfGetDriver());
|
|
const DMF_BusFilter_CONFIG* config = &context->Configuration;
|
|
|
|
FuncEntry(DMF_TRACE);
|
|
|
|
// Forward immediately if client driver has no handler
|
|
//
|
|
if (config->EvtDeviceQueryInterface == NULL)
|
|
{
|
|
IoSkipCurrentIrpStackLocation(Irp);
|
|
ntStatus = IoCallDriver(DMF_BusFilter_WdmAttachedDeviceGet(ChildDevice),
|
|
Irp);
|
|
|
|
FuncExit(DMF_TRACE, "status=%!STATUS!", ntStatus);
|
|
return ntStatus;
|
|
}
|
|
|
|
// If client driver didn't do anything with the IRP...
|
|
//
|
|
if (!config->EvtDeviceQueryInterface(ChildDevice, Irp))
|
|
{
|
|
// ...forward it prior to completion
|
|
//
|
|
if (!IoForwardIrpSynchronously(DMF_BusFilter_WdmAttachedDeviceGet(ChildDevice),
|
|
Irp))
|
|
{
|
|
TraceError(DMF_TRACE, "IoForwardIrpSynchronously fails: Irp=0x%p", Irp);
|
|
|
|
Irp->IoStatus.Status = STATUS_NO_SUCH_DEVICE;
|
|
}
|
|
}
|
|
|
|
// Complete the Irp
|
|
//
|
|
ntStatus = Irp->IoStatus.Status;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
|
|
FuncExit(DMF_TRACE, "status=%!STATUS!", ntStatus);
|
|
|
|
return ntStatus;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// BusFilter Public Calls by Client
|
|
//
|
|
|
|
#pragma warning(disable:4995)
|
|
#pragma code_seg("PAGE")
|
|
_IRQL_requires_max_(PASSIVE_LEVEL)
|
|
_Must_inspect_result_
|
|
NTSTATUS
|
|
DMF_BusFilter_Initialize(
|
|
_In_ DMF_BusFilter_CONFIG* BusFilterConfig
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Called by Client Driver to initialize DMF BusFilter operations from DriverEntry().
|
|
|
|
Arguments:
|
|
|
|
BusFilterConfig - Client Driver configuration parameters.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS ntStatus;
|
|
WDF_OBJECT_ATTRIBUTES attributes;
|
|
BusFilter_Context* contextBusFilter;
|
|
|
|
FuncEntry(DMF_TRACE);
|
|
|
|
PAGED_CODE();
|
|
|
|
contextBusFilter = NULL;
|
|
|
|
// Config is required
|
|
//
|
|
if (BusFilterConfig == NULL)
|
|
{
|
|
ntStatus = STATUS_INVALID_PARAMETER;
|
|
goto Exit;
|
|
}
|
|
|
|
// Driver object must be already created
|
|
//
|
|
if (!WdfGetDriver())
|
|
{
|
|
ntStatus = STATUS_NOT_SUPPORTED;
|
|
goto Exit;
|
|
}
|
|
|
|
if (!BusFilterConfig->DriverObject)
|
|
{
|
|
ntStatus = STATUS_INVALID_PARAMETER;
|
|
goto Exit;
|
|
}
|
|
|
|
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes,
|
|
BusFilter_Context);
|
|
|
|
// Attach context to driver object.
|
|
//
|
|
ntStatus = WdfObjectAllocateContext(WdfGetDriver(),
|
|
&attributes,
|
|
(void**)&contextBusFilter);
|
|
if (!NT_SUCCESS(ntStatus))
|
|
{
|
|
TraceError(DMF_TRACE, "WdfObjectAllocateContext fails: ntStatus=%!STATUS!", ntStatus);
|
|
goto Exit;
|
|
}
|
|
|
|
// Save copy of config in context to invoke callback routines later.
|
|
//
|
|
RtlCopyMemory(&contextBusFilter->Configuration,
|
|
BusFilterConfig,
|
|
sizeof(DMF_BusFilter_CONFIG));
|
|
|
|
ULONG index;
|
|
PDRIVER_DISPATCH* pDispatch;
|
|
|
|
// Store original dispatch routine pointers and overwrite with our own
|
|
//
|
|
#pragma warning(disable:28175)
|
|
for (
|
|
index = 0,
|
|
pDispatch = BusFilterConfig->DriverObject->MajorFunction;
|
|
index <= IRP_MJ_MAXIMUM_FUNCTION;
|
|
index++, pDispatch++
|
|
)
|
|
{
|
|
contextBusFilter->MajorDispatchFunctions[index] = *pDispatch;
|
|
*pDispatch = DMF_BusFilter_DispatchHandler;
|
|
}
|
|
#pragma warning(default:28175)
|
|
|
|
// PnP minor code dispatch routines
|
|
//
|
|
contextBusFilter->PnPMinorDispatchFunctions[IRP_MN_START_DEVICE] = DMF_BusFilter_PnP_StartDevice;
|
|
contextBusFilter->PnPMinorDispatchFunctions[IRP_MN_DEVICE_ENUMERATED] = DMF_BusFilter_PnP_DeviceEnumerated;
|
|
contextBusFilter->PnPMinorDispatchFunctions[IRP_MN_QUERY_ID] = DMF_BusFilter_PnP_QueryId;
|
|
contextBusFilter->PnPMinorDispatchFunctions[IRP_MN_QUERY_INTERFACE] = DMF_BusFilter_PnP_QueryInterface;
|
|
|
|
// Clear invalid characteristics (see MS docs)
|
|
//
|
|
BusFilterConfig->DeviceCharacteristics &= ~(FILE_AUTOGENERATED_DEVICE_NAME |
|
|
FILE_CHARACTERISTIC_TS_DEVICE |
|
|
FILE_CHARACTERISTIC_WEBDAV_DEVICE |
|
|
FILE_DEVICE_IS_MOUNTED |
|
|
FILE_VIRTUAL_VOLUME);
|
|
|
|
ntStatus = STATUS_SUCCESS;
|
|
|
|
Exit:
|
|
|
|
FuncExit(DMF_TRACE, "status=%!STATUS!", ntStatus);
|
|
|
|
return ntStatus;
|
|
}
|
|
#pragma code_seg()
|
|
#pragma warning(default:4995)
|
|
|
|
#pragma code_seg("PAGE")
|
|
NTSTATUS
|
|
DMF_BusFilter_DeviceAdd(
|
|
_In_ WDFDRIVER Driver,
|
|
_Inout_ PWDFDEVICE_INIT DeviceInit
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Creates bus WDF device.
|
|
|
|
Arguments:
|
|
|
|
Driver - Associated WDFDRIVER.
|
|
DeviceInit - WDF PWDFDEVICE_INIT.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS ntStatus = STATUS_NOT_IMPLEMENTED;
|
|
WDF_OBJECT_ATTRIBUTES attributes;
|
|
WDFDEVICE device = NULL;
|
|
UCHAR minorPnP = IRP_MN_QUERY_DEVICE_RELATIONS;
|
|
PDMFDEVICE_INIT dmfDeviceInit = NULL;
|
|
|
|
FuncEntry(DMF_TRACE);
|
|
|
|
PAGED_CODE();
|
|
|
|
// Guard against DMF_BusFilter_Initialize not having been called.
|
|
//
|
|
const BusFilter_Context* context = BusFilterContextGet(Driver);
|
|
if (context == NULL)
|
|
{
|
|
TraceError(DMF_TRACE, "BusFilterContextGet returns NULL: was DMF_BusFilter_Initialize called?");
|
|
ntStatus = STATUS_INVALID_DEVICE_STATE;
|
|
goto Exit;
|
|
}
|
|
|
|
const DMF_BusFilter_CONFIG* config = &context->Configuration;
|
|
|
|
WdfFdoInitSetFilter(DeviceInit);
|
|
|
|
// Attach IRP preprocessor.
|
|
//
|
|
ntStatus = WdfDeviceInitAssignWdmIrpPreprocessCallback(DeviceInit,
|
|
DMF_BusFilter_PreprocessQueryBusRelations,
|
|
IRP_MJ_PNP,
|
|
&minorPnP,
|
|
1);
|
|
if (!NT_SUCCESS(ntStatus))
|
|
{
|
|
TraceError(DMF_TRACE, "WdfDeviceInitAssignWdmIrpPreprocessCallback fails: ntStatus=%!STATUS!", ntStatus);
|
|
goto Exit;
|
|
}
|
|
|
|
// Don't initialize with context here as client driver might decide
|
|
// to set their own context memory in EvtPreBusDeviceAdd
|
|
//
|
|
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
|
|
|
|
// Call pre-device-creation callback, if set.
|
|
//
|
|
if (config->EvtPreBusDeviceAdd)
|
|
{
|
|
ntStatus = config->EvtPreBusDeviceAdd(Driver,
|
|
DeviceInit,
|
|
&attributes,
|
|
&dmfDeviceInit);
|
|
if (!NT_SUCCESS(ntStatus))
|
|
{
|
|
TraceError(DMF_TRACE, "EvtPreBusDeviceAdd fails: ntStatus=%!STATUS!", ntStatus);
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// Client driver is using DMF modules.
|
|
//
|
|
if (dmfDeviceInit != NULL)
|
|
{
|
|
DMF_DmfFdoSetFilter(dmfDeviceInit);
|
|
}
|
|
|
|
// Create device object
|
|
//
|
|
ntStatus = WdfDeviceCreate(&DeviceInit,
|
|
&attributes,
|
|
&device);
|
|
if (!NT_SUCCESS(ntStatus))
|
|
{
|
|
TraceError(DMF_TRACE, "WdfDeviceCreate fails: ntStatus=%!STATUS!", ntStatus);
|
|
goto Exit;
|
|
}
|
|
|
|
PARENT_BUS_DEVICE_CONTEXT* parentContext = NULL;
|
|
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes,
|
|
PARENT_BUS_DEVICE_CONTEXT);
|
|
|
|
// Add bus device context.
|
|
//
|
|
ntStatus = WdfObjectAllocateContext(device,
|
|
&attributes,
|
|
(void**)&parentContext);
|
|
if (!NT_SUCCESS(ntStatus))
|
|
{
|
|
TraceError(DMF_TRACE, "WdfObjectAllocateContext fails: ntStatus=%!STATUS!", ntStatus);
|
|
goto Exit;
|
|
}
|
|
|
|
InitializeListHead(&parentContext->ChildList);
|
|
KeInitializeSpinLock(&parentContext->ChildListLock);
|
|
|
|
// Initialise work-item rundown state. OutstandingWorkItems starts at zero
|
|
// and WorkItemsIdle starts signaled (no workers pending).
|
|
//
|
|
parentContext->OutstandingWorkItems = 0;
|
|
KeInitializeEvent(&parentContext->WorkItemsIdle, NotificationEvent, TRUE);
|
|
|
|
// Call post-device-creation callback, if set.
|
|
//
|
|
if (config->EvtPostBusDeviceAdd)
|
|
{
|
|
ntStatus = config->EvtPostBusDeviceAdd(device,
|
|
dmfDeviceInit);
|
|
if (!NT_SUCCESS(ntStatus))
|
|
{
|
|
TraceError(DMF_TRACE, "EvtPostBusDeviceAdd fails: ntStatus=%!STATUS!", ntStatus);
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
if (!NT_SUCCESS(ntStatus) && dmfDeviceInit != NULL)
|
|
{
|
|
DMF_DmfDeviceInitFree(&dmfDeviceInit);
|
|
}
|
|
|
|
if (!NT_SUCCESS(ntStatus) && device != NULL)
|
|
{
|
|
WdfObjectDelete(device);
|
|
}
|
|
|
|
FuncExit(DMF_TRACE, "status=%!STATUS!", ntStatus);
|
|
|
|
return ntStatus;
|
|
}
|
|
#pragma code_seg()
|
|
|
|
PDEVICE_OBJECT
|
|
DMF_BusFilter_WdmDeviceObjectGet(
|
|
_In_ DMFBUSCHILDDEVICE ChildDevice
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Returns DEVICE_OBJECT associated with a given DMFBUSCHILDDEVICE.
|
|
|
|
Arguments:
|
|
|
|
ChildDevice - The given DMFBUSCHILDDEVICE.
|
|
|
|
Return Value:
|
|
|
|
The associated DEVICE_OBJECT.
|
|
|
|
--*/
|
|
{
|
|
const BUS_CHILD_DEVICE_CONTEXT* childContext = DMF_BusFilter_GetChildContext(ChildDevice);
|
|
|
|
if (childContext)
|
|
{
|
|
return childContext->DeviceObject;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
PDEVICE_OBJECT
|
|
DMF_BusFilter_WdmAttachedDeviceGet(
|
|
_In_ DMFBUSCHILDDEVICE ChildDevice
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Returns the attached DEVICE_OBJECT associated with a given DMFBUSCHILDDEVICE.
|
|
|
|
Arguments:
|
|
|
|
ChildDevice - The given DMFBUSCHILDDEVICE.
|
|
|
|
Return Value:
|
|
|
|
The attached DEVICE_OBJECT.
|
|
|
|
--*/
|
|
{
|
|
const BUS_CHILD_DEVICE_CONTEXT* childContext = DMF_BusFilter_GetChildContext(ChildDevice);
|
|
|
|
if (childContext)
|
|
{
|
|
const WDM_CHILD_DEVICE_EXTENSION* childExtension = (WDM_CHILD_DEVICE_EXTENSION*)childContext->DeviceObject->DeviceExtension;
|
|
|
|
if (IsEqualGUID(&childExtension->Signature,
|
|
&GUID_DMF_BUSFILTER_SIGNATURE))
|
|
{
|
|
return childExtension->TargetDeviceObject;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
PDEVICE_OBJECT
|
|
DMF_BusFilter_WdmPhysicalDeviceGet(
|
|
_In_ DMFBUSCHILDDEVICE ChildDevice
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Returns the associated physical DEVICE_OBJECT associated with a given DMFBUSCHILDDEVICE.
|
|
|
|
Arguments:
|
|
|
|
ChildDevice - The given DMFBUSCHILDDEVICE.
|
|
|
|
Return Value:
|
|
|
|
The associated physical (parent) DEVICE_OBJECT.
|
|
|
|
--*/
|
|
{
|
|
const BUS_CHILD_DEVICE_CONTEXT* childContext = DMF_BusFilter_GetChildContext(ChildDevice);
|
|
|
|
if (childContext)
|
|
{
|
|
const WDM_CHILD_DEVICE_EXTENSION* childExtension = (WDM_CHILD_DEVICE_EXTENSION*)childContext->DeviceObject->DeviceExtension;
|
|
|
|
if (IsEqualGUID(&childExtension->Signature,
|
|
&GUID_DMF_BUSFILTER_SIGNATURE))
|
|
{
|
|
return childExtension->PhysicalDeviceObject;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
#pragma code_seg("PAGE")
|
|
_IRQL_requires_max_(PASSIVE_LEVEL)
|
|
VOID
|
|
DMF_BusFilter_DeviceCleanup(
|
|
_In_ WDFDEVICE Device
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Tears down all remaining child filter devices associated with a bus device.
|
|
|
|
This function must be called from the client driver's EvtDeviceReleaseHardware
|
|
or EvtCleanupCallback AFTER PnP has already dispatched IRP_MN_REMOVE_DEVICE to
|
|
all child devnodes. Under normal operation the ChildList is empty by the time
|
|
this function runs (each REMOVE_DEVICE already drained it). This function acts
|
|
as a safety net for abnormal paths (e.g. parent surprise-removed before all
|
|
children received REMOVE).
|
|
|
|
For each remaining child:
|
|
- Acquires the per-child remove lock (prevents new I/O from entering).
|
|
- Calls IoReleaseRemoveLockAndWait to drain any in-flight I/O.
|
|
- Detaches and deletes the filter device in the mandatory order.
|
|
|
|
If IoAcquireRemoveLock returns STATUS_DELETE_PENDING for a child, it means an
|
|
IRP_MN_REMOVE_DEVICE is concurrently processing that child; this function logs
|
|
the anomaly and skips it (the REMOVE path owns teardown in that case).
|
|
|
|
Arguments:
|
|
|
|
Device - The bus WDF device whose children should be cleaned up.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
KLOCK_QUEUE_HANDLE handle;
|
|
PARENT_BUS_DEVICE_CONTEXT* parentContext = DMF_BusFilter_GetParentContext(Device);
|
|
const BusFilter_Context* context = BusFilterContextGet(WdfGetDriver());
|
|
|
|
FuncEntry(DMF_TRACE);
|
|
|
|
PAGED_CODE();
|
|
|
|
if (parentContext == NULL || context == NULL)
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Wait for any bus-relations work items that are still executing to finish
|
|
// before we start tearing down ChildList and its entries. Without this wait,
|
|
// a passive-level worker could be calling Relations_AddDevice (touching the
|
|
// list and child extensions) while we are freeing them.
|
|
//
|
|
KeWaitForSingleObject(&parentContext->WorkItemsIdle,
|
|
Executive,
|
|
KernelMode,
|
|
FALSE,
|
|
NULL);
|
|
|
|
// Drain ChildList. The lock is released around per-child teardown because
|
|
// IoReleaseRemoveLockAndWait, IoDetachDevice, and IoDeleteDevice cannot be
|
|
// called while holding a spinlock.
|
|
//
|
|
#pragma warning(disable: 28150)
|
|
KeAcquireInStackQueuedSpinLock(&parentContext->ChildListLock, &handle);
|
|
|
|
while (!IsListEmpty(&parentContext->ChildList))
|
|
{
|
|
// Peek at the head of the list without removing it until we know
|
|
// we can safely take ownership of this child.
|
|
//
|
|
LIST_ENTRY* entry = parentContext->ChildList.Flink;
|
|
WDM_CHILD_DEVICE_EXTENSION* childExtension =
|
|
CONTAINING_RECORD(entry, WDM_CHILD_DEVICE_EXTENSION, ListEntry);
|
|
|
|
PDEVICE_OBJECT filterDevice = DMF_BusFilter_WdmDeviceObjectGet(childExtension->Child);
|
|
|
|
// Try to acquire the remove lock while still holding the spinlock.
|
|
// IoAcquireRemoveLock is callable at DISPATCH_LEVEL.
|
|
//
|
|
NTSTATUS lockStatus = IoAcquireRemoveLock(&childExtension->RemoveLock, filterDevice);
|
|
if (!NT_SUCCESS(lockStatus))
|
|
{
|
|
// An IRP_MN_REMOVE_DEVICE is already processing this child. That path
|
|
// will call RemoveEntryList under the spinlock, so we must not touch
|
|
// this entry. Remove it from the list now to avoid looping forever,
|
|
// and let the REMOVE path complete teardown on its own.
|
|
//
|
|
TraceWarning(DMF_TRACE, "%!FUNC! child device already removing (concurrent REMOVE_DEVICE)");
|
|
RemoveEntryList(entry);
|
|
continue; // re-check IsListEmpty with lock still held
|
|
}
|
|
|
|
// We successfully acquired the lock; take the entry out of the list.
|
|
//
|
|
RemoveEntryList(entry);
|
|
KeReleaseInStackQueuedSpinLock(&handle);
|
|
|
|
// Save locals before any pointer might become invalid.
|
|
//
|
|
PDEVICE_OBJECT targetDevice = childExtension->TargetDeviceObject;
|
|
DMFBUSCHILDDEVICE child = childExtension->Child;
|
|
WDFDEVICE parent = childExtension->Parent;
|
|
|
|
// Drain in-flight I/O: IoReleaseRemoveLockAndWait marks the lock
|
|
// "removing" (so new IoAcquireRemoveLock calls fail), releases the
|
|
// acquire we just made AND the implicit initial reference, then waits
|
|
// for the count to reach zero.
|
|
//
|
|
IoReleaseRemoveLockAndWait(&childExtension->RemoveLock, filterDevice);
|
|
|
|
// Notify client, then tear down in mandatory order.
|
|
//
|
|
if (context->Configuration.EvtDeviceRemove)
|
|
{
|
|
context->Configuration.EvtDeviceRemove(parent, child);
|
|
}
|
|
|
|
WdfObjectDelete(child);
|
|
IoDetachDevice(targetDevice);
|
|
|
|
if (filterDevice != NULL)
|
|
{
|
|
IoDeleteDevice(filterDevice); // Extension invalid after this
|
|
}
|
|
|
|
KeAcquireInStackQueuedSpinLock(&parentContext->ChildListLock, &handle);
|
|
}
|
|
|
|
KeReleaseInStackQueuedSpinLock(&handle);
|
|
#pragma warning(default: 28150)
|
|
|
|
Exit:
|
|
|
|
FuncExitNoReturn(DMF_TRACE);
|
|
}
|
|
#pragma code_seg()
|
|
|
|
#endif // defined(DMF_KERNEL_MODE)
|