Need help with HID client driver for a HID device with custom collection

Hi,

I have a firmware that is recognized as a HID device. Below is the HID report descriptor being used:

0x05U, 0x81U, /* Usage Page (Vendor defined) /
0x09U, 0x82U, /
Usage (Vendor defined) /
0xA1U, 0x01U, /
Collection (Application) /
0x09U, 0x83U, /
Usage (Vendor defined) */

0x09U, 0x84U, /* Usage (Vendor defined) /
0x15U, 0x80U, /
Logical Minimum (-128) /
0x25U, 0x7FU, /
Logical Maximum (127) /
0x75U, 0x08U, /
Report Size (8) /
0x95U, 64, /
Report Count (64) /
0x81U, 0x02U, /
Input (Data, Variable, Absolute) */

0x09U, 0x84U, /* Usage (Vendor defined) /
0x15U, 0x80U, /
Logical Minimum (-128) /
0x25U, 0x7FU, /
Logical Maximum (127) /
0x75U, 0x08U, /
Report Size (8) /
0x95U, 64, /
Report Count (64) /
0x91U, 0x02U, /
Output (Data, Variable, Absolute) /
0xC0U /
End collection */

Now, I am trying to create a function driver to extend its functionality. Below are the steps I've followed so far:

  1. Created a default KMDF driver using Visual Studio.
  2. Set power policy ownership to FALSE using:

WdfDeviceInitSetPowerPolicyOwnership(DeviceInit, FALSE);
(Without this, the system bugchecks.)
3. Bypassed the device I/O control. When a request is received, I use WdfRequestSend with SEND_AND_FORGET.

With this setup, the device enumerates as a HID device in Device Manager. However, when I try to get the device's capabilities (GetCaps) from a user-mode application using the following code:

PHIDP_PREPARSED_DATA preparsedData;
if (HidD_GetPreparsedData(deviceHandle, &preparsedData))
{
HIDP_CAPS caps;
if (HidP_GetCaps(preparsedData, &caps) == HIDP_STATUS_SUCCESS)
{
// Processing caps
}
}

The application hangs, and I don’t see any logs from the driver when this call is made.

What could be causing this issue?

TIA,
Chandra

You said:

Now, I am trying to create a function driver to extend its functionality.

Did you mean "filter driver"? If you write a function driver, then you are totally replacing the stock HID driver, and there won't be anything to extend. No one will respond to your ioctls. Exactly how did you install this? Did you use an INF? Perhaps you should show us.

Tim, Thanks for the response.

Here is my inf file:
;
; HIDFnDriver.inf
;

[Version]
Signature = "$WINDOWS NT$"
Class=HIDClass
ClassGuid={745A17A0-74D3-11D0-B6FE-00A0C90F57DA}
Provider = %ManufacturerName%
CatalogFile = HIDFnDriver.cat
DriverVer = ; TODO: set DriverVer in stampinf property pages
PnpLockdown = 1

[DestinationDirs]
DefaultDestDir = 13

[SourceDisksNames]
1 = %DiskName%,,,""

[SourceDisksFiles]
HIDFnDriver.sys = 1,,

;*****************************************
; Install Section
;*****************************************

[Manufacturer]
%ManufacturerName% = Standard,NT$ARCH$.10.0...16299 ; %13% support introduced in build 16299

[Standard.NT$ARCH$.10.0...16299]
%HIDFnDriver.DeviceDesc% = HIDFnDriver_Device, HID\VID_03F0&PID_1388
;%HIDFnDriver.DeviceDesc% = HIDFnDriver_Device, HID\VID_1FC9&PID_00A2&Col01
;%HIDFnDriver.DeviceDesc% = HIDFnDriver_Device, HID\VID_1FC9&PID_00A2&Col02

[HIDFnDriver_Device.NT]
CopyFiles = File_Copy
Include=hidclass.sys
AddReg = HIDDevice.AddReg ; Add this line to associate security settings

[HIDDevice.AddReg]
HKR,,Security,"D:P(A;;GA;;;SY)(A;;GA;;;BA)(A;;GR;;;WD)"

[File_Copy]
HIDFnDriver.sys

;-------------- Service installation
[HIDFnDriver_Device.NT.Services]
AddService = HIDFnDriver,%SPSVCINST_ASSOCSERVICE%, HIDFnDriver_Service_Inst

; -------------- HIDFnDriver driver install sections
[HIDFnDriver_Service_Inst]
DisplayName = %HIDFnDriver.SVCDESC%
ServiceType = 1 ; SERVICE_KERNEL_DRIVER
StartType = 3 ; SERVICE_DEMAND_START
ErrorControl = 1 ; SERVICE_ERROR_NORMAL
ServiceBinary = %13%\HIDFnDriver.sys

[HIDFnDriver_Device.NT.Wdf]
KmdfService = HIDFnDriver, HIDFnDriver_wdfsect

[HIDFnDriver_wdfsect]
KmdfLibraryVersion = $KMDFVERSION$

[Strings]
SPSVCINST_ASSOCSERVICE = 0x00000002
ManufacturerName = "" ;TODO: Replace with your manufacturer name
DiskName = "HIDFnDriver Installation Disk"
HIDFnDriver.DeviceDesc = "HIDFnDriver Device"
HIDFnDriver.SVCDESC = "HIDFnDriver Service"

I’m working on creating a function driver for my HID device, and my understanding was that a function driver doesn’t fully replace the stock HID driver. Instead, it complements the existing HID stack by interacting with the HID class driver to provide device-specific functionality. I believed that, by forwarding IOCTLs to the lower driver, the function driver would still receive responses. I expected my function driver to behave similarly, adding custom functionality while still leveraging the stock driver’s core capabilities.
Please correct me if I am wrong.

Thanks,
Chandra

No, you understand incorrectly. A function driver REPLACES the standard function driver. What you described would be a filter driver.

The driver above you is sending you HID ioctls. The driver below you is expecting USB Request Blocks (URBs). It doesn't understand HID at all. Your function driver is responsible for converting them.

What are you expecting to do in your driver? If you give us a general description, perhaps we can guide you in the right direction. There is very little point in writing a function driver for a purely vendor-defined top-level collection. The generic driver is just going to pass blocks of 64 bytes up and down, so you can do whatever processing you need to do in user-mode. Kernel Driver Rule #1 is "never do anything in kernel mode that can be done just as easily in user mode."

The HID client driver I have created works exactly like you have pointed out here.

However, according to the HID architecture(HID Architecture - Windows drivers | Microsoft Learn) documentation, the HidClient driver interacts with the HidClass.sys driver to represent specific types of devices (such as sensors, keyboards, mice, etc.).

If the HidClient driver replaces the HidClass driver, I was wondering how it represents the devices using HWID as mentioned in the documentation.

I’m aiming to gain a deeper understanding of the HID architecture. To that end, I am developing a HID client function driver to explore its internals.

I have flashed a hardware with a custom HID device with a single top-level collection, as shown in post 1. While I’m able to access reports using the HIDClass driver, I would like to investigate how to use a HID client driver with HIDClass driver to do the same.

Your reasoning is faulty. The REASON one chooses to make a vendor-class HID device is precisely because you do not need any kernel code at all. The stock HID class drivers can handle it, on ALL the major operating systems, and you can talk to your device through the user-mode calls. That's the POINT. There is nothing to be gained by writing your own HID class driver. It is a waste of time.

Even if you choose to toss out all of the HID overhead and just create a USB Vendor Class (not HID Class) device with a couple of bulk pipes, you can still handle all in user mode using WinUSB. There is very little justification for writing any USB drivers in kernel mode these days, and that's a Good Thing.

Thanks for the clarification regarding why a hid client driver is unnecessary.
One question I still have is: What exactly are kbdhid.sys and mouhid.sys in the architecture below? If they are HID client function drivers, it seems they don’t replace hidclass.sys but instead operate on top of it, according to the architecture. However, in the device manager driver stack, it appears that kbdhid.sys replaces hidclass.sys. If hidclass.sys already manages HID communication, what is the need for additional drivers like kbdhid.sys and mouhid.sys?

It's all about layering. In a device stack like this, each driver essentially acts as a "filter", converting some interface/API at its top to some interface/API at its bottom.

So, the USB HID layers translate the HID interface from above to the USB interface below. The top layer there translated keyboard and mouse interfaces to the HID interface below. Remember that Windows supported keyboards and mice long before USB was ever a twinkle in the eye. They can't do the HID calls from user-mode, because the pre-existing Windows keyboard and mouse interfaces require translation to USB HID.

Your explanation about the layering in the device stack is very clear, especially regarding how each driver acts as a "filter" between the interfaces and how USB HID layers translate interfaces.

Given this, I have a question:

Is there a sample for kbdhid.sys, if I want to create something similar for my own device? Specifically, I'd like to build a driver that works alongside hidclass.sys and represents my custom device in the OS, similar to how kbdhid.sys does for keyboards.

I know that it is not needed. But if I ever come across such situation, I wanted to know how to create a driver similar to kbdhid?

That's a tricky question, because both the keyboard and mouse stacks are serving very specific clients -- the Windows UI subsystem. No other client driver is ever going to be that specific.

Here's my crude suggestion. First, write user-mode code to talk to your device's top-level collection. Make sure you can read and write. Now, if you want, write a very simple filter driver to sit on top and below hidclass or hidusb. Now you can see what ioctls they receive when your application operates, and what URBs they become. Perhaps that will make the flow more clear.

That's a great suggestion.
I have used the hclient app from \Windows-driver-samples\hid\hclient. With that I was able to list the hidcaps etc. as shown below.

image

I’ve now installed both upper and lower filter drivers for HIDClass and captured the following IOCTLs around my device during enumeration. Below is a screenshot of the TraceView logs from both filter drivers:

Next, I reviewed the driver stack of the function driver, which was installed over HidUSB.

0: kd> !devstack ffffa40453565ce0
!DevObj !DrvObj !DevExt ObjectName

ffffa40453565ce0 \Driver\OAIFnDriverffffa40453586fb0
ffffa4045559e120 \Driver\HidUsb ffffa4045559e270 000000c1

After installing OAIFnDriver, here are the logs:

After installing the function driver, the hclient app hangs and HidD_GetPreparsedData does not return.
Probably, the error in the log "IOCTL_HID_GET_COLLECTION_DESCRIPTOR could not retrieve output buffer - Status: 0xC0000010" is the reason.

Any suggestions?

0xC0000010 is STATUS_INVALID_DEVICE_REQUEST. Are you handling that ioctl? Remember, if you are the function driver, you have to handle ALL the ioctls that come your way.

True. Here is how I am handling IOCTL_HID_GET_COLLECTION_DESCRIPTOR.

case IOCTL_HID_GET_COLLECTION_DESCRIPTOR:
{
PHIDP_PREPARSED_DATA preparsedData = NULL; // Use PHIDP_PREPARSED_DATA
size_t outputBufferLength = 0;
WDF_MEMORY_DESCRIPTOR outputDescriptor;
ULONG_PTR bytesReturned = 0;

   TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "IOCTL_HID_GET_COLLECTION_DESCRIPTOR");

   // Retrieve the output buffer for the request and get its length
   status = WdfRequestRetrieveOutputBuffer(Request, sizeof(PHIDP_PREPARSED_DATA), (PVOID*)&preparsedData, &outputBufferLength);
   if (!NT_SUCCESS(status)) {
       TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE,
           "IOCTL_HID_GET_COLLECTION_DESCRIPTOR could not retrieve output buffer - Status: 0x%X",
           status);
       WdfRequestComplete(Request, status);
       return;
   }

   // Ensure the buffer length matches the expected size of PHIDP_PREPARSED_DATA
   if (outputBufferLength < sizeof(PHIDP_PREPARSED_DATA)) {
       TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE,
           "IOCTL_HID_GET_COLLECTION_DESCRIPTOR outputBufferLength too small - %lu",
           (ULONG)outputBufferLength);
       WdfRequestComplete(Request, STATUS_BUFFER_TOO_SMALL);
       return;
   }

   // Initialize the output buffer descriptor
   WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&outputDescriptor, preparsedData, (ULONG)outputBufferLength);

   TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE,
       "IOCTL_HID_GET_COLLECTION_DESCRIPTOR outputBufferLength - %lu",
       (ULONG)outputBufferLength);

   // Forward the IOCTL to the lower driver to get the collection descriptor (preparsed data)
   status = WdfIoTargetSendIoctlSynchronously(
       hidTarget,                               // The I/O target (lower driver)
       NULL,                                    // Request (optional)
       IOCTL_HID_GET_COLLECTION_DESCRIPTOR,     // The IOCTL to send
       NULL,                                    // Input buffer (not needed here)
       &outputDescriptor,                       // Output buffer descriptor
       NULL,                                    // Optional timeouts or send options
       &bytesReturned);                         // Correct type: ULONG_PTR*

   if (!NT_SUCCESS(status)) {
       TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE,
           "IOCTL_HID_GET_COLLECTION_DESCRIPTOR could not send IOCTL - Status: 0x%X",
           status);
       WdfRequestComplete(Request, status);
       return;
   }

   // Optional: Check if the bytes returned match the expected size
   if (bytesReturned < (ULONG_PTR)sizeof(PHIDP_PREPARSED_DATA)) {
       TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE,"IOCTL_HID_GET_COLLECTION_DESCRIPTOR returned insufficient data.");
       status = STATUS_BUFFER_TOO_SMALL;
       WdfRequestComplete(Request, status);
       return;
   }

   // Set the number of bytes transferred (size of the preparsed data)
   WdfRequestSetInformation(Request, (ULONG)bytesReturned);

   // Complete the request successfully
   TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE,
       "IOCTL_HID_GET_COLLECTION_DESCRIPTOR request complete success with bytesReturned: %llu",
       bytesReturned);
   WdfRequestComplete(Request, STATUS_SUCCESS);
   break;

}

I was able to print getcaps from the preparsedData and print them. I was able to complete the request successfully for the first time. When the request is received second time, the output buffer could not be retrieved with status: 0xC0000010

If I use the INF file I posted earlier, it appears that my function driver replaces the HIDClass stock driver for the specific device, as suggested by the logs. For this device, I don't see any logs from the HIDClass filter drivers. I am wondering what modifications are needed in the INF file so that my function driver can work alongside HIDClass, similar to how the kbdhid driver coexists with HIDClass.