Installing a standard filter driver on the Audio Device stack

Hi All,
I need to write a simple filter driver which would allow or block audio or video devices based on their VID/PID. I took generic WDF filter sample from the latest WDK (Windows-driver-samples\general\toaster\toastDrv\kmdf\filter\generic), built and installed the service myfilter as a lower filter to the media class by adding a LowerFilter entry to HKLM\SYSTEM\CurrentControlSet\Control\Class{4d36e96c-e325-11ce-bfc1-08002be10318} and plugged in my USB speaker.

The USB speaker gets enumerated properly. But either adjusting the volume through tray volume control or playing sound simply breaks the entire USB sub-system so badly that the hub(s) it is connected to go into reset cycles and things go crazy from that point. Entire PnP process becomes excruciatingly slow (unplug re-plug of other USB devices would take several minutes), some USB devices just won’t get enumerated at all, or enumeration - re-enumeration cycle would repeat for hours. The only way to recover is to unplug the USB speaker, and unplug and re-plug the external hub(s) to which the USB speaker was plugged into, and wait for them to enumerate properly - typically 10+ minutes. In few occasions, computer was frozen, it needed a reboot. Classic WDM filter from WDK 7.x produces the outcome as well.

My setup is: Dell laptop → USB xHCI Host Controller → Powered USB-C external hub (with HDMI video) → Powered USB 3.0 external hub → USB speaker (+ mouse, keyboard etc). Windows 10 x64 22H2 19045. Installation is fairly clean, only the chipset driver from dell and the drivers installed by Windows update are present and nothing more.

I am assuming that this is something to do with the fact that I am installing standard filter on the audio KS streaming stack. I arrived at this conclusion because, the filter when installed as lower filter to audio device causes problem, but not when installed as an upper filter to its usbccgp composite parent. This led me into the world of KS streaming, which apparently is very special and complex for my purposes so I didn’t get much further to be to understand its nuances. Also noticed that MSDN has references to WDM streaming filters, it made me naively think that any standard pass-through filter should work on KS stack, apparently it is not the case. The question is what the special rule about KS driver stack where a standard pass through would cause issues? Why passing everything through is insufficient? Why would it kill the entire USB subsystem? I can replicate this with other computers running standard Windows 10 x64 22H2 19045 as well, but NOT all computers.

Lower filter to Media class which causes issues:
0: kd> !devstack 0xFFFFC18D6034C2E0
!DevObj !DrvObj !DevExt ObjectName
ffffc18d61d9e280 \Driver\ksthunk ffffc18d61d9e3d0 000000fc
ffffc18d4d87ca50 \Driver\usbaudio ffffc18d4d87cbc0

ffffc18d6034c2e0 \Driver\myfilter ffffc18d6034c430
ffffc18d633d77f0 \Driver\usbccgp ffffc18d633d7940 000000fa
!DevNode ffffc18d5fe58cc0 :
DeviceInst is “USB\VID_0B0E&PID_0410&MI_00\7&10769164&0&0000”
ServiceName is “usbaudio”

Upper filter to usb (ccgp) where there is no issue:
0: kd> !devstack 0xFFFFC18D659AC080
!DevObj !DrvObj !DevExt ObjectName

ffffc18d659ac080 \Driver\myfilter ffffc18d659ac1d0
ffffc18d5eacf290 \Driver\usbccgp ffffc18d5eacf3e0 00000102
ffffc18d5f2f3060 \Driver\USBHUB3 ffffc18d60f047b0 USBPDO-4
!DevNode ffffc18d64ddf5d0 :
DeviceInst is “USB\VID_0B0E&PID_0410\70BF9213A8FAx011200”
ServiceName is “usbccgp”

Thank you!

I would also like to know how to debug what exactly is causing the hubs to re-enumerate. I understand that I could use USB bus analyzer, but is there any other way using software?

I would appreciate if someone answers whether a standard pass through filter driver can be installed to filter the Audio stack. Thanks.

How are you doing your passthrough? Saying “standard passthrough” doesn’t really mean anything.

It’s true that KS drivers are somewhat special, because requests all get sent using METHOD_NEITHER with data placed in unusual locations in the IRP. If you aren’t trying to access the IRPs, then this shouldn’t matter. However, your stack shows you BELOW usbaudio, and that interface is not KS at all – it is URBs. You would have to be on the upper side, with ksthunk.

How are you doing your passthrough? Saying “standard passthrough” doesn’t really mean anything.
Filter Device was created with an IO queue and CB for EvtIoDeviceControl only.
ioQueueConfig.EvtIoDeviceControl = FilterEvtIoDeviceControl;
status = WdfIoQueueCreate(device, &ioQueueConfig, WDF_NO_OBJECT_ATTRIBUTES, WDF_NO_HANDLE);

EvtIoDeviceControl CB forwards the requests as below.
ret = WdfRequestSend(Request, WdfDeviceGetIoTarget(device), &options);
No other queues or CB's of other types of requests, thus I am expecting the framework to pass all other requests down to the next driver in the stack.

It’s true that KS drivers are somewhat special, because requests all get sent using METHOD_NEITHER with data placed in unusual locations in the IRP. If you aren’t trying to access the IRPs, then this shouldn’t matter.
That’s correct. I am not interested in filtering any IRP’s except PnP IRP’s

However, your stack shows you BELOW usbaudio, and that interface is not KS at all – it is URBs. You would have to be on the upper side, with ksthunk.
Yes, that’s the intent, to be a lower filter. Thank you for clarifying that its only URB’s that myfilter would see.

ioQueueConfig.EvtIoDeviceControl = FilterEvtIoDeviceControl;

If all you’re interested in is PnP requests, then you don’t need to filter ioctls at all. PnP requests come in through a different path. The mapping is not always clear; what requests are you trying to intercept?

In the position you’re in, I don’t think you’ll see any ioctls at all.

ret = WdfRequestSend(Request, WdfDeviceGetIoTarget(device), &options);

What options are you using? That’s an important aspect.

Thank you for clarifying that its only URB’s that myfilter would see.

Yep. And for what it’s worth, URBs are sent using IRP_MJ_INTERNAL_DEVICE_CONTROL, not IRP_MJ_DEVICE_CONTROL.

If you are only interested in PnP Irps, then call WdfDeviceInitAssignWdmIrpPreprocessCallback specifying IRP_MJ_PNP and optionally an array of minor PnP IRP codes.

Thank you both Tim and Mark

Yes, I do not need IOCTL code at all. All I needed was prepare HW and release HW for my puposes. I posted the code snippet of the stock WDF generic sample which had them.

To make it clear, I stripped everything that I do not need from the sample and only left the prepare and relase HW function, I can still reproduce the issue when I install the driver as a lower filter in the audio stack.

I am posting the entire code as is that reproduces the problem to clear things out. Sorry, its close to 100 lines, but is probably would help!

#include “filter.h”

#ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, DriverEntry)
#pragma alloc_text (PAGE, DriverUnload)
#pragma alloc_text (PAGE, DeviceAdd)
#pragma alloc_text (PAGE, PrepareHardware)
#pragma alloc_text (PAGE, ReleaseHardware)
#endif

NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
WDF_DRIVER_CONFIG config;
NTSTATUS status;
WDFDRIVER hDriver;

DbgPrint("DriverEntry(0x%p, %wZ)\n", DriverObject, RegistryPath);

WDF_DRIVER_CONFIG_INIT(&config, DeviceAdd);

config.EvtDriverUnload = DriverUnload;

status = WdfDriverCreate(DriverObject, RegistryPath, WDF_NO_OBJECT_ATTRIBUTES, &config,&hDriver);
if (!NT_SUCCESS(status)) {
    DbgPrint("WdfDriverCreate failed with status 0x%x\n", status);
}

DbgPrint("DriverEntry: WdfDriver 0x%p !drvobj 0x%p \n", hDriver, DriverObject);


return status;

}

VOID DriverUnload( IN WDFDRIVER Driver)
{
PAGED_CODE();

DbgPrint("DriverUnload (0x%p) !drvobj 0x%p \n", Driver, WdfDriverWdmGetDriverObject(Driver));

}

NTSTATUS DeviceAdd( IN WDFDRIVER Driver, IN PWDFDEVICE_INIT DeviceInit)
{
WDF_OBJECT_ATTRIBUTES deviceAttributes;
PFILTER_EXTENSION filterExt;
NTSTATUS status;
WDFDEVICE device;
WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;

PAGED_CODE ();

UNREFERENCED_PARAMETER(Driver);
DbgPrint("FilterEvtDeviceAdd (0x%p, 0x%p)\n", Driver, DeviceInit);

WdfFdoInitSetFilter(DeviceInit);

WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, FILTER_EXTENSION);

WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);

pnpPowerCallbacks.EvtDevicePrepareHardware = PrepareHardware;
pnpPowerCallbacks.EvtDeviceReleaseHardware = ReleaseHardware;
WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks);

status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);
if (!NT_SUCCESS(status)) {
    DbgPrint("WdfDeviceCreate failed with status code 0x%x\n", status);
    return status;
}

DbgPrint("AddDevice: PDO: !wdfdevice 0x%p, FDO: (!wdfdevice 0x%p !devobj 0x%p) \n", WdfDeviceWdmGetPhysicalDevice(device), device, WdfDeviceWdmGetDeviceObject(device));

filterExt = FilterGetData(device);
RtlZeroMemory(filterExt, sizeof(FILTER_EXTENSION));

return status;

}

NTSTATUS PrepareHardware( WDFDEVICE Device, WDFCMRESLIST ResourcesRaw, WDFCMRESLIST ResourcesTranslated)
{
UNREFERENCED_PARAMETER(Device);
UNREFERENCED_PARAMETER(ResourcesRaw);
UNREFERENCED_PARAMETER(ResourcesTranslated);

PAGED_CODE();
DbgPrint("PrepareHardware: !wdfdevice 0x%p\n", Device);

return STATUS_SUCCESS;

}

NTSTATUS ReleaseHardware(IN WDFDEVICE Device, IN WDFCMRESLIST ResourcesTranslated)
{
UNREFERENCED_PARAMETER(Device);
UNREFERENCED_PARAMETER(ResourcesTranslated);

PAGED_CODE();
DbgPrint("ReleaseHardware: !wdfdevice 0x%p\n", Device);
return STATUS_SUCCESS;

}

Plain WDM filter exhibits identical behavior. There is something fundamental that I am missing, any clues?

for (ulIndex = 0, dispatch = DriverObject->MajorFunction;
     ulIndex <= IRP_MJ_MAXIMUM_FUNCTION;
     ulIndex++, dispatch++) {

    *dispatch = FilterPass;
}

NTSTATUS
FilterPass (
PDEVICE_OBJECT DeviceObject,
PIRP Irp
)
{
PDEVICE_EXTENSION deviceExtension;
NTSTATUS status;

deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
status = IoAcquireRemoveLock (&deviceExtension->RemoveLock, Irp);
if (!NT_SUCCESS (status)) {
    Irp->IoStatus.Status = status;
    IoCompleteRequest (Irp, IO_NO_INCREMENT);
    return status;
}

IoSkipCurrentIrpStackLocation (Irp);
status = IoCallDriver (deviceExtension->NextLowerDriver, Irp);
IoReleaseRemoveLock(&deviceExtension->RemoveLock, Irp);
return status;
}

How are you doing your passthrough? Saying “standard passthrough” doesn’t really mean anything.
Filter Device was created with an IO queue and CB for EvtIoDeviceControl only.
ioQueueConfig.EvtIoDeviceControl = FilterEvtIoDeviceControl;
status = WdfIoQueueCreate(device, &ioQueueConfig, WDF_NO_OBJECT_ATTRIBUTES, WDF_NO_HANDLE);

EvtIoDeviceControl CB forwards the requests as below.
ret = WdfRequestSend(Request, WdfDeviceGetIoTarget(device), &options);
No other queues or CB’s of other types of requests, thus I am expecting the framework to pass all other requests down to the next driver in the stack.

It’s true that KS drivers are somewhat special, because requests all get sent using METHOD_NEITHER with data placed in unusual locations in the IRP. If you aren’t trying to access the IRPs, then this shouldn’t matter.
That’s correct. I am not interesting in filtering any IRP’s except PnP IRP’s

However, your stack shows you BELOW usbaudio, and that interface is not KS at all – it is URBs. You would have to be on the upper side, with ksthunk.
Yes, that’s the intent, to be a lower filter. Thank you for clarifying that its only URB’s that myfilter would see.

Hi All, I am still stuck with this, any suggestions/advise would be helpful. I made the driver sit below UsbAudio.sys and above UsbCcgp.sys, so all the traffic should be the URB’s, so I am simply clueless at this point as to what is going on…

Thanks.

Maybe you accidentally installed your driver in more than one stack or deeper in the usb hierarchy? !drvobj (driver object) 1 to list your device objects For each devobj, !devstack

Maybe you accidentally installed your driver in more than one stack or deeper in the usb hierarchy?

Not really.

0: kd> !drvobj 0xFFFFC18D64928910
Driver object (ffffc18d64928910) is for:
\Driver\myfilter

Driver Extension List: (id , addr)

Device Object list:
ffffc18d5f12a500
0: kd> !devstack ffffc18d5f12a500
!DevObj !DrvObj !DevExt ObjectName
ffffc18d62d922f0 \Driver\ksthunk ffffc18d62d92440 000000fc
ffffc18d6323a4e0 \Driver\usbaudio ffffc18d6323a650
ffffc18d5f12a500 \Driver\myfilter ffffc18d5f12a650
ffffc18d62f044b0 \Driver\usbccgp ffffc18d62f04600 000000fa
!DevNode ffffc18d5fdb06e0 :
DeviceInst is “USB\VID_0B0E&PID_0410&MI_00\7&10769164&0&0000”
ServiceName is “usbaudio”

And what is the problem again? Your device no longer operates when the filter is present? I’ve had that happen with a USB MIDI device (the filter for a USB monitor product caused the device to break), but never with an audio device.

And what is the problem again?

Installing a plain filter on the audio stack (lower or upper filter to usbaudio) causes the parent hub(s) and all devices connected to them to disconnect/reconnect cycle and dramatically slows down functioning of the PnP manager. The only way to recover is to unplug the USB speaker, and unplug and re-plug the external hub(s) to which the USB speaker was plugged into. Complete source code for this filter is above.

I’ve had that happen with a USB MIDI device (the filter for a USB monitor product caused the device to break), but never with an audio device.
Is it just the device or the underlying USB HW tree as well? Nevertheless, it sounds somewhat similar to what I observe. Do you recall what the root cause and the resolution was?