question

AntonioCeccato-9865 avatar image
0 Votes"
AntonioCeccato-9865 asked DoronHolan commented

Beginner advice for USB HID filter driver

Hello everybody,

I have this issue: a USB mouse I recently bought has a total of 6 buttons, but the 6th button isn't recognized unless I install vendor software and bind it to extremely limited functions.

I inspected the raw USB HID report descriptor that the device reports, and two things are clear:
1. the Button usage page reports only 5 bits (so only 5 buttons) and declares 3 constant bits for padding to a full byte
2. when clicking the 6th button, the input report shows that bit 6 in the Button usage page is set (which is the first of the 3 declared as padding above)

Therefore, the device is hiding the 6th button by not declaring it in the Input Report.
I feel like the easiest workaround would be to intercept the USB HID report descriptor response and modify it on the fly to set 6 bits for buttons and 2 for padding instead of 5+3.

And here is where my knowledge stops: I have very little experience with Win32 / kernel low-level programming, my daily job involves mainly C# .NET and web environments.
I have found and read the official documentation about WDF, KMDF, UMDF, Virtual HID Framework, but I still don't have a clear enough picture to make an informed decision on where my code should be targeted. I understand I might have to write a "filter" driver, but I could also directly access HID reports from userspace (if that allows me to modify the descriptor, which I don't think so).

What advice can you give me on where to start with this driver? Or do you have a better idea with regards to solving my issue?

Thanks in advance

windows-hardwarewindows-hardware-wdkwindows-hardware-code-general
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

1 Answer

DoronHolan avatar image
1 Vote"
DoronHolan answered DoronHolan commented

You want a filter driver that is below hidusb.sys. You want to be able to modify the raw hid descriptor before hidclass (via hidusb) processes it. This also allows you to modify the incoming hid data as you want. This gets you far enough to properly report the 6th button and hidparse can find and extract it.....

BUT, you have another problem now. The mouse driver (mouhid.sys) does not know about the 6th button, so the higher-level driver will not query for the 6th button data. Mouhid.sys exclusively owns the mouse and is the only reader so there is no way for you to read the 6th button data in user mode nor does the 6th button get plumbed through the mouse input pipeline. So, you need another way to expose the 6th button data. Since the 6th button is not plumbed through the mouse input pipeline there is no generic solution to expose it to all applications, all consumption will be custom.

You can use the proposed (in the first paragraph) lower hidusb filter to expose the 6th button (and anything else) by exposing a raw PDO and routing the 6th button data through it. Your user mode application can then open the raw PDO through a device interface and read it. The moufiltr example shows how to create a raw PDO, you would have to go through the sample and extract the relevant code.

If you want to forgo modifying the raw hid descriptor and just want to get at the 6th button, there is another possible solution.

· 7
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Thank you very much for your useful answer!

My idea of modifying the raw report descriptor was mostly to avoid having to handle the 6th button separately (ideally I would have used X-Mouse Button Control to remap buttons and functions, which currently only shows buttons 4 and 5, but not 6). I thought I would be able to trick the standard driver (mouhid.sys?), since I believed it would have used the report descriptor to "discover" buttons. Is there really no way to trick mouhid and have it seamlessly plumb everything needed for the 6th button?

What is the alternative solution you hinted to? If modifying the raw descriptor is a "dead end" and I'd still have to write something to handle the 6th button separately, I'd rather take the easier route.

Thank you again

Edit:
I just found this revealing document: https://docs.microsoft.com/en-us/windows-hardware/drivers/hid/keyboard-and-mouse-hid-client-drivers
It's clear to me now that only 5 buttons are supported by mouhid.sys. Also I now understand your hypothesis of adding a filter between hidusb.sys (HID transport driver) and hidclass.sys.

So, what would the easiest way to get events from the 6th button be? It's ok if it won't work with X-Mouse, I can write a small utility to map custom functions to it.

Thanks

0 Votes 0 ·
DoronHolan avatar image DoronHolan AntonioCeccato-9865 ·

The design and strategy is the same for the two options, both are about the same complexity. As stated above, the design is the driver

  1. enumerates a raw PDO so that an application can open it (both hidclass and mouhid block your app open so you have to create a new path)

  2. inspects the data coming from the device and copies off the relevant state so that it can be reported through the raw PDO. This inspection will be hand written, you can't use the HID parsing APIs since the sixth button is not in the HID descriptor.

  3. optionally keep a ring buffer of state if the app is not reading fast enough (IOW, the device reports data when there is no queued IO from the app)

The app always has a query for data pending in the driver (could be more than one). That query can be a read request or an IOCTL (typically it is an IOCTL).

The driver can exist in either of these two locations in the stack

  1. As described above, below hidusb. In this case you will set a completion routine on the read IO request (a submit URB IOCTL) on the appropriate pipe.

  2. Below mouhid. In this case you will set a completion routine on the read request (IRP_MJ_READ).





1 Vote 1 ·

There is no perfect fit WDK sample to start from. The best fit is moufiltr. It is written to sit above mouhid and filters data in a different way. You can remove all that logic if you want, it would be inert below mouhid as the filter will not see the IOCTLs that mouclass sends. Keep the logic around creating the raw PDO and the creation of the queues for the PDO and the filter device. You will need to update the sample to present read requests . In the queue dispatch routine you will set a completion routine and then send the read down the stack (WdfRequestFormatRequestUsingCurrentType , WdfRequestSetCompletionRoutine , WdfIoTargetSend).

1 Vote 1 ·
Show more comments