How to continuously obtain HID input reports from non-HIDClass driver?

Hi, I'm considering to write a KMDF battery miniclass driver for HID-based batteries to act as as replacement for the in-build Microsoft "HidBatt" driver. The reason for wanting to do so is that HidBatt doesn't seem to support all relevant battery parameters. Battery parameters like "CycleCount" and "Temperature" (from HID Power Device spec.) are for some reason not parsed by the HidBatt driver.

My tentative plan is to start with the Microsoft "simbatt" sample (Class=Battery) and modify the INF HWID to "HID_DEVICE_UP:0084_U:0004", so that it matches the HidBatt driver. I'm hoping that I'll afterwards will be able to query HID FEATURE reports to populate static battery parameters on device initialization. However, I'm unsure how to make the driver continuously obtain HID input reports to update dynamic battery parameters like "RemainingCapacity". Microsoft's "Obtaining HID Reports by kernel-mode drivers" documentation suggests using IRP_MJ_READ requests for this, but I'm unsure how to implement it.

Is there any sample code online for a non-HIDClass KMDF driver that is continuously obtaining HID input reports?

Another possibility if to instead write an upper filter driver on top of "HidBatt" and only populates the missing parameters. However, I'm not sure if that is possible. The Battery mini class documentation does at least only mention lower filter driver drivers, and not upper.

Any advise is highly appreciated,
Fredrik

You don't really want a kernel driver to be a HID client. You should take some time to figure out how you can do this with a filter driver above or below HidBatt. That would put you in the same driver stack, and let you monitor/modify the IRP flow without reinventing the wheel.

Thanks for the suggestion Tim.

I'll then attempt to develop a filter driver on top if the "HidBatt" driver that fills in the missing parameters. I guess my filter driver will then also need to be a battery miniclass driver like HidBatt, or is HIDClass then also an option?

I'll still need to perform HID calls to the underlying battery though, just not as many as if developing a complete battery miniclass driver..

A PnP filter driver is not in a class on its own. It is just extending the driver it is filtering. HidBatt speaks battery on the upper side, and HID on the lower side.

I've now managed to obtain both HID FEATURE and INPUT reports from a KMDF filter driver based on the Microsoft "firefly" sample. I've also managed to change the INF HWID to "HID_DEVICE_UP:0084_U:0004", so that the driver loads when attaching a HID Power Device. All good so far...

Still, my filter driver is now loading instead of the HidBatt driver, whereas I want to load it above or below the HitBatt driver. The driver already calls WdfFdoInitSetFilter() and the INF contain a AddFilter directive with FilterPosition=Upper or FilterPosition=Lower (tried both). I've also changed my driver INF Class/ClassGuid to match HidBatt (is this required?). I'm therefore struggling to understand why HidBatt will no longer load.

Not sure what I'm missing here. Do I for instance need to update my driver INF with Include/Needs references to HidBatt somehow, or is there something else that I'm missing?

Thanks in advance,
Fredrik

If you are a device filter you need to reference the device inf file, as the firefly sample does.

Thanks for your quick help Mark. Adding Include/Needs references to HidBatt in my driver INF did indeed fix the problem of HidBatt no longer loading.

With the latest suggestions I'm able to run my filter driver either above or below HidBatt in the device stack - which is great. However, filtering Read requests to capture HID INPUT reports only works if running below HidBatt. This makes sense, since HID communication is no longer exposed above the HidBatt driver.

My driver needs to both filter HID Read requests below HidBatt and send IOCTL to the HidBatt driver. To achieve this I think I either need to:

  1. Run in an upper filter driver and figure out a way of creating IO queues based on a WDF device below in the device stack. I've already tried calling WdfWdmDeviceGetWdfDeviceHandle(WdfDeviceWdmGetAttachedDevice(device)) to get the HidBatt device, but it just leads to a bugcheck crash. Is this possible in some other way?
  2. Run in a lower filter driver and figure out how to send IOCTL with results to the HidBatt device above. Not sure how to do this, since I don't see and WDF functions for navigating upwards in the device stack. Am I missing something here?

Thanks again for all your support Tim & Mark. I really appreciate it.

WdfWdmDeviceGetWdfDeviceHandle(WdfDeviceWdmGetAttachedDevice(device))

you can't reach into other driver's data structures. and WDF handles are scoped to the driver, so even if you got a WDFDEVICE for hidbatt, it wouldn't work.

If all you need to do is send an IO request to hidbatt/battery port driver, call IoGetAttachedDeviceReference and send the irp. remember to dereference the device object when you are done. If you need to open a handle/file object to hidbatt (some drivers require a valid file object to process a request) you can call

  1. IoGetDeviceProperty (DevicePropertyPhysicalDeviceObjectName) to get the name of the PDO
  2. WdfIoTargetCreate
  3. WdfIoTargetOpen and init the open params with WDF_IO_TARGET_OPEN_PARAMS_INIT_CREATE_BY_NAME using the PDO name
  4. create a wdfrequest
  5. use the wdfiotarget apis to format it and send it

Thanks a lot for helpful suggestions Doron! I've now managed to submit IO requests to the PDO by following your advise.

However, after some further investigation if seems like I might need to filter the communication both above and below HidBatt. My tentative plan is therefore to capture HID INPUT reports below the HidBatt driver and use them to modify IOCTL_BATTERY_QUERY_STATUS & IOCTL_BATTERY_QUERY_INFORMATION IOCTL requests above HidBatt. This is because I've so far have been unable to find a way of modify the HidBatt state directly with IO requests.

I just tried to create a IO queue for filtering PDO requests with WdfIoQueueCreate(WdfIoTargetGetDevice(pdoTarget),..), but this led to 0xc0000184 (STATUS_INVALID_DEVICE_STATE). Do you know if this is due to a bug on my end, or if there's inherent limitations on a drivers ability to create IO queues for filtering of remote IO targets like the PDO?

You can only create a WDFIOQUEUE on your WDFDEVICE's. You can't attach WDF objects to other driver's objects (or their WDF objects). Simply, you need two instances of your driver which understand where they are in the stack (above or below) OR two completely different driver images (simpler config and at runtime, but now you have two moving pieces). If you need the above hidbatt driver to initiate communicate with the below hidbatt driver you will have another level complexity since you will need to create a sideband channel that circumvents hidbatt/battery class driver which blocks sending your custom requests down the stack.

Thanks for confirming what I was starting to suspect Doron! It saves me a lot of work to have this limitation confirmed.

Developing two separate drivers for filtering communication above &. below HidBatt sounds quite complex to me. I therefore think I'll revisit my initial plan of instead creating a Battery miniclass driver based on the "SimBatt" sample to replace HidBatt. This will involve more parameter parsing but the overall "driver architecture" will at least be simpler.

I've now spent some days trying to convert my "firefly"-based HID filter driver into a HID client function driver to act as a drop-in replacement for the Microsoft "HidBatt" driver.

However, IOCTL_HID_GET_FEATURE calls against the PDO suddenly start failing with STATUS_INVALID_DEVICE_REQUEST when converting my driver from a "filter" driver to a "function" driver. I've tried instead using the FDO, but that just leads to STATUS_PRIVILEGE_NOT_HELD. There's obviously something going on that I'm not understanding here...

Are anyone aware of any sample code for a "HID client" function driver that I can study to learn how to get started?

Thanks in advance,
Fredrik

Just to go back a step, you don't actually need two separate drivers. You can have one driver that understands that it has two roles: upper filter and lower filter. Doron did mention this. For any given battery fdo device, your lower filter fdo is attached first (to the pdo,) then the fdo is attached to your lower filter, then your upper filter fdo is attached. (I've ignored the complication of other upper or lower filters, but that doesn't really change anything.) The ordering is predetermined. You maintain separate role states in your device contexts. You likely have some shared state between the two, at a minimum to understand if this is the first filter attach (lower) or the second (upper) for any specific battery pdo.

Thanks for reminding me about this option Mark. Are you then thinking about two driver INF files sharing a single SYS binary, or something else? Can I find sample code for this pattern somewhere online?
Any recommendation for how to "best" communicate state between the upper and lower driver instance?

Sorry for asking all the newbie questions. I'm trying to build competency in this area by reading the Windows Driver Foundation book and discovering online resources. However, it's proving a bit more challenging than first anticipated.

are you opening a file object against the PDO and verifying that the request you are sending includes that file object? (taken care of for you by a WDFIOTARGET if you ask it to open the handle). HID requires a file handle open against the PDO to send process IO on the PDO.

I'm using the following code to open the PDO that works fine when building as a filter driver:

// open PDO in shared read-write mode
NTSTATUS status = WdfIoTargetCreate(Device, WDF_NO_OBJECT_ATTRIBUTES, &pdoTarget);
// error handling here
WDF_IO_TARGET_OPEN_PARAMS openParams = {};
WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME(&openParams, &deviceContext->PdoName, FILE_READ_ACCESS | FILE_WRITE_ACCESS);
openParams.ShareAccess = FILE_SHARE_WRITE | FILE_SHARE_READ;
status = WdfIoTargetOpen(pdoTarget, &openParams);
// error handling here

Properties for the function driver PDO target:

kd> !wdfiotarget 0x000044725fc0bd38
Treating handle as a KMDF handle!

WDFIOTARGET 000044725fc0bd38
=========================
!wdfdevice 0x0000447261437ca8
Target Device: !devobj  0xffffbb8da02bba50
Target PDO: !devobj  0xffffbb8da07a14b0

Type: Remote target
State:  WdfIoTargetStarted

Requests pending: 0

Requests sent: 0

Requests sent with ignore-target-state: 0

Target name:  \Device\00000038
Target FileObject: dt nt!_FILE_OBJECT  0xffffbb8da2002340
WDF file !handle  0xffffffff80001cc8. Search for 'Object: xxxx Type: File', run '!fileobj xxxx'
Open type:  WdfIoTargetOpenByName

kd> dt nt!_FILE_OBJECT  0xffffbb8da2002340
   +0x000 Type             : 0n5
   +0x002 Size             : 0n216
   +0x008 DeviceObject     : 0xffffbb8d`a07a14b0 _DEVICE_OBJECT
   +0x010 Vpb              : (null) 
   +0x018 FsContext        : (null) 
   +0x020 FsContext2       : (null) 
   +0x028 SectionObjectPointer : (null) 
   +0x030 PrivateCacheMap  : (null) 
   +0x038 FinalStatus      : 0n0
   +0x040 RelatedFileObject : (null) 
   +0x048 LockOperation    : 0 ''
   +0x049 DeletePending    : 0 ''
   +0x04a ReadAccess       : 0 ''
   +0x04b WriteAccess      : 0 ''
   +0x04c DeleteAccess     : 0 ''
   +0x04d SharedRead       : 0 ''
   +0x04e SharedWrite      : 0 ''
   +0x04f SharedDelete     : 0 ''
   +0x050 Flags            : 0x40000
   +0x058 FileName         : _UNICODE_STRING ""
   +0x068 CurrentByteOffset : _LARGE_INTEGER 0x0
   +0x070 Waiters          : 0
   +0x074 Busy             : 0
   +0x078 LastLock         : (null) 
   +0x080 Lock             : _KEVENT
   +0x098 Event            : _KEVENT
   +0x0b0 CompletionContext : (null) 
   +0x0b8 IrpListLock      : 0
   +0x0c0 IrpList          : _LIST_ENTRY [ 0xffffbb8d`a2002400 - 0xffffbb8d`a2002400 ]
   +0x0d0 FileObjectExtension : (null)  

I see that FsContext is null in the function driver, whereas it was set when building as a filter driver. The problem might be tied to that, but I don't know how to fix it.

Status update regarding Doron & Marks suggestions for two filter driver instances:
I was able to get my filter driver to load both above and below HidBatt in the device stack with the following INF modifications:

[MyDriver_Inst.NT.HW]
AddReg = MyDriver_Inst.AddReg

[MyDriver_Inst.AddReg]
HKR,,"UpperFilters", %REG_TYPE_MULTI_SZ%, "MyDriver"
HKR,,"LowerFilters", %REG_TYPE_MULTI_SZ%, "MyDriver"

This was quite neat and less complex than anticipated. Thanks a lot for suggesting this approach Doron & Mark! I wasn't able to achieve the same setup with the new INF AddFilter directive though, but that's not really a problem. This just leaves me with the challenge of creating a sideband channel for communication between the driver instances. However, that sounds like a straight-forward problem since the ordering is predetermined.

The design pattern described in Using Driver-Defined Interfaces - Windows drivers | Microsoft Learn would allow the upper and lower filter driver to communicate.

Thanks a lot Tim Roberts, Mark Roddy & Doron Holan for providing valuable feedback to all my newbie questions on driver development. I really appreciate your support on this!

I've just published sources for the "HidBatt extension" driver that I'm working on on HidBattery/HidBattExt at master · forderud/HidBattery · GitHub together with a Arduino battery emulator project for testing. The driver does work, but please be warned that it's work in progress and not yet ready for production usage. The sources are open-source under a permissive license. Anyone are also free to contribute by reporting issues or submitting merge requests if interested.

1 Like