Are there any online samples for how to develop at HID client driver that speaks HID below and exposes itself as something else above?
I've already examined the vhidmini2 & hidusbfx2 samples on Windows-driver-samples/hid at main · microsoft/Windows-driver-samples · GitHub . Unfortunately, both samples expose themselves as HID devices above, which is something else than what I'm trying to do. I've also tried reading Microsoft's "Minidrivers and the HID class driver" documentation, but remain unsure if a WDF HID minidriver is really the right approach for my problem in the first place. Any comments on that?
The reason for asking is that I'm working on addressing limitations in Microsoft's "HidBatt" driver for battery packs that communicate over the "HID Power Device" protocol. I've already developed a filter driver on HidBattery/HidBattExt at master · forderud/HidBattery · GitHub for parsing missing parameters. However, it doesn't address other shortcomings, like a few missing WMI interfaces and the driver being missing from ARM64 builds of Windows. I've already opened support cases to Microsoft for all discovered limitations, and am hoping for a root-cause fix, but would like a short-term solution meanwhile.
There probably aren't any helpful samples, because there's nothing particularly unique in this situation. You need an INF that matches the "HID\VID_xxxx&PID_xxxx\COL_xxx" collection that you want to claim, and that driver needs to create a PDO with a hardware ID that looks like a battery device. You'll need to translate the APIs, but I presume you know how to do that.
Thanks for clarifying the situation Tim. I then assume I should develop the HID client driver as a normal function driver, and not as a "WDF HID minidriver" that runs beneath MsHidKmdf?
Both approaches are KMDF-based, so this is not what differs between them. I've already tried to pursue both approaches, but have then bumped into the following obstacles:
If developing a "regular" KMDF function driver on top of HidUsb, then PDO opening starts failing with STATUS_SHARING_VIOLATION after removing the WdfFdoInitSetFilter(DeviceInit) call. I've tried tweaking the ShareAccess field in multiple ways, but it doesn't seem to make any difference. Attempting to instead use the default I/O target leads to STATUS_PRIVILEGE_NOT_HELD.
If instead developing a WDF HID minidriver beneath MsHidKmdf, then the driver is auto-unloaded after EvtDriverDeviceAdd have completed without any errors from within my driver. Windows Device Manager does report a STATUS_DEVICE_DATA_ERROR that I guess originates from MsHidKmdf above in the device stack. By examining the hidusbfx2 & vhidmini samples it looks like WDF HID minidriver are expected to provide device- and report-descriptor tables for the HID device. This only makes sense for "virtual device" drivers that simulate a HID device, since these tables are otherwise delivered by the HID device and not by the driver. This makes me question if a WDF HID minidriver is really the right approach for my problem.
There are probably good reasons for the problems listed above, but the terse error messages and lack of sample code makes it challenging to figure out what is missing or wrong. It feels like I'm struggling in the dark here.
Small correction: I'm observing 0xc0000010 (STATUS_INVALID_DEVICE_REQUEST) for HID IOCTL requests after removingWdfFdoInitSetFilter(DeviceInit) to convert the KMDF driver into a function driver.
I am somewhat catching up on the thread. i don't think you want to be a hid miniport driver as your hardware is already HID, yes? you want to consume HID data and transform it into something else. Basically the same function as kbhdid for keyboards, but for batteries. So to be a HID mapper driver....
HID requires you open a file handle to send io. The default wdfiotarget does not open a file handle, so you can't use it. To make matters complicated, you are not allowed to open a handle until the ENTIRE stack has processed the pnp start irp, so you can't open your custom wdfiotarget in any of the init/power up callbacks. the most deterministic way to open the handle is to register for device interace arrival notifications (IoRegisterPlugPlayNotification) and open the handle from that callback. The question is which device interface to register notifications for
if the HID device interface, you need to attempt to open each one and then query for the PDO or pdo name and compare it to your own stack's PDO
register a custom device interface and register for notifications on it. If there is only 1 instance of your driver, it is deterministic and easy. if there can be > 1, you have the same problem as choice 1 as you need to determine the PDO.
Note that opening your custom device interface notification is the same as opening the HID device interface since it is the same stack // PDO you are opening, so you need to have the sharing and access writes set appropriately.
You will also need to configure your FDO to forward open/clean/close down the stack by calling WdfDeviceInitSetFileObjectConfig where WDF_FILEOBJECT_CONFIG.AutoForwardCleanupClose = WdfTrue and read the remarks section about what you need to do if you register your own EvtDeviceFileCreate callback, _WDF_FILEOBJECT_CONFIG (wdfdevice.h) - Windows drivers | Microsoft Learn
Thanks a lot for giving clear advise on this matter Doron.
Correct. I'm trying to develop a driver that consumes HID data and transforms it into something else. Thank you for confirming that a HID miniport driver is then probably not the best approach.
This is the same limitation as for HID filter drivers, right? I think I'm already tackling that sub-problem by deferring PDO opening to a timer running at IRQ passive after EvtDeviceSelfManagedIoInit have completed. It might be that replacing the timer with IoRegisterPlugPlayNotification would be preferable by reducing the time window of the driver being partially initialized though(?) Or am I misunderstanding something?
I'm currently not performing any PDO searches, just retrieving the PDO name based on DevicePropertyPhysicalDeviceObjectName and passing the string to WdfIoTargetOpen with access=FILE_READ_ACCESS|FILE_WRITE_ACCESS and ShareAccess=FILE_SHARE_WRITE|FILE_SHARE_READ|FILE_SHARE_DELETE. This call then succeeds, but later IOCTL_HID_GET_FEATURE requests fail with STATUS_INVALID_DEVICE_REQUEST. Do you think adopting IoRegisterPlugPlayNotification would help on this?
Thanks a lot for making me aware of this requirement Doron. Is this the type of code I should add to EvtDriverDeviceAdd?
WDF_FILEOBJECT_CONFIG fileConfig{};
WDF_FILEOBJECT_CONFIG_INIT(&fileConfig, WDF_NO_EVENT_CALLBACK, WDF_NO_EVENT_CALLBACK, WDF_NO_EVENT_CALLBACK);
fileConfig.AutoForwardCleanupClose = WdfTrue; // forward open/clean/close down the stack
WdfDeviceInitSetFileObjectConfig(DeviceInit, &fileConfig, WDF_NO_OBJECT_ATTRIBUTES);
It unfortunately doesn't seem to make any difference in addressing the failing IOCTL_HID_GET_FEATURE. I've tried adding a custom EvtDeviceFileCreate callback with WdfRequestFormatRequestUsingCurrentType(Request); BOOLEAN res = WdfRequestSend(Request, WdfDeviceGetIoTarget(Device), NULL); if (res == FALSE) {..} logic for request forwarding, but it only makes matters worse by causing WdfIoTargetOpen of the PDO to fail.
The timer is problematic. You don't know when the start irp completes back to the pnp manager and thus allows creates. You would have to requeue the timer if you can't open it the first time (and deal with cleanup if the start fails or the device is immediately removed). In hindsight you may not want to forward the creates down the stack. that is what kbdhid does as it doesn't open a handle on its own, it relies on the handle opened to the stack by the input manager to start reading. Your situation is probably different.
Do any HID operations succeed other than IOCTL_HID_GET_FEATURE? have you tried stepping into the hidclass IOCTL handler and see what it is checking for?
Understood. I'll then update my driver to replace the timer with IoRegisterPlugPlayNotification-based PDO opening if it eliminates the hypothetical risk of sporadic failures. Having to open each added HID interface, querying for PDO and comparing PDO strings feels a bit clunky though, but I guess I'll have to live with it.
Nice to hear that I can probably skip that step.
I'm still able to query IOCTL_HID_GET_COLLECTION_INFORMATION and IOCTL_HID_GET_COLLECTION_DESCRIPTOR from the local I/O target. However, all IOCTL_HID_GET_FEATURE requests against the PDO started failing with STATUS_INVALID_DEVICE_REQUEST after converting my HID driver from a filter to a function driver by removing the WdfFdoInitSetFilter call.
I've seen you mention elsewhere on this forum a need for copying the IRP FileObject after formatting requests. I've already tried doing that, but it seems like IoGetCurrentIrpStackLocation(irp)->FileObject is NULL for the requests I create. Not sure what to do about that(?)
Interesting. I was not aware that this was a possibility. Are the hidclass sources and symbols available somewhere?
You need a file object for get feature. The iotarget you open in the interface notification callback will have a file open when you open the device interface. You have to delay the init logic which uses get feature until that point
Debugging: no source. You set a BP on the hidclass ioctl dispatch handler and start stepping through asm. But first start sending the request with a iotarget with a file object
Correction: I'm getting a FileObject, but the FsContext field becomes null after removing the WdfFdoInitSetFilter call to convert the filter driver into a function driver.
This is what I'm observing when opening the PDO directly from the IoRegisterPlugPlayNotification callback:
sorry for the indecision about how to handle creates. this is where you need to end up:
when you open the HID device the create arrives in your driver first since you are attached to the HID.
you need to forward that create down the stack so that HIDclass clan process it and initialize its file object state
the cleanup and close need to be forwarded to hidclass as well for the teardown of the file objec state
I don't remember if hidclass uses FsContext or FsContext2 for its state stored in the FO, you need to configure WDF to store its FO state with the other field with WDF_FILEOBJECT_CLASS so it doesn't conflict with hidclass
so you need to configure WDF like i described previously plus take into account point 4
Thanks for very helpful advise, Doron. The HID client drivers docs state that the FsContext field is reserved for the HID class driver, and other drivers in the stack should therefore instead use FsContext2. I've therefore updated my driver with the following code:
// configure handling of device create, close & cleanup requests
WDF_FILEOBJECT_CONFIG fileConfig{};
WDF_FILEOBJECT_CONFIG_INIT(&fileConfig, WDF_NO_EVENT_CALLBACK, WDF_NO_EVENT_CALLBACK, WDF_NO_EVENT_CALLBACK); // keep default callbacks
fileConfig.AutoForwardCleanupClose = WdfTrue; // forward requests down the stack
fileConfig.FileObjectClass = WdfFileObjectWdfCanUseFsContext2; // cannot use FsContext, since it's reserved by HIDclass
WdfDeviceInitSetFileObjectConfig(DeviceInit, &fileConfig, WDF_NO_OBJECT_ATTRIBUTES);
I'm now observing that both the FsContext and FsContext2 fields are set when opening the PDO - which is great! The bad news is that IOCTL_HID_GET_FEATURE requests are still failing with STATUS_INVALID_DEVICE_REQUEST...