Windows: Bluetooth and WriteFile API issues #513

Closed
opened 2023-02-26 15:24:08 -06:00 by DJm00n · 10 comments
DJm00n commented 2023-02-26 15:24:08 -06:00 (Migrated from github.com)
Looks like Windows Bluetooth drivers have issues with sending output reports with `WriteFile`. Need to investigate this and try to use `HidD_SetOutputReport` as a fallback. https://stackoverflow.com/questions/29480144/hidapi-sending-packet-smaller-than-caps-outputreportbytelength https://social.msdn.microsoft.com/Forums/ie/en-US/834d2cf2-672d-424b-a126-dbc69f3f75a2/hiddsetoutputreportwritefile-bluetooth-low-energy-windows-81 Related https://github.com/libsdl-org/SDL/issues/7224
DJm00n commented 2023-02-26 15:58:21 -06:00 (Migrated from github.com)

The GATT Read Characteristic Value or Read Long Characteristic Value subprocedures are used to read a Report characteristic containing Output Report data. This procedure maps to a Get_Report (Output) request in USB HID [2].

The GATT Write Characteristic Value sub-procedure is used to write to a Report characteristic containing Output Report data. This procedure maps to a Set_Report (Output) request in USB HID [2]. The GATT Write Without Response sub-procedure is
also used to write to a Report characteristic containing Output Report data, and this procedure maps to Data Output in USB HID [2].

https://www.bluetooth.com/specifications/specs/human-interface-device-service-1-0/

>The GATT Read Characteristic Value or Read Long Characteristic Value subprocedures are used to read a Report characteristic containing Output Report data. This procedure maps to a Get_Report (Output) request in USB HID [2]. >The GATT Write Characteristic Value sub-procedure is used to write to a Report characteristic containing Output Report data. This procedure maps to a Set_Report (Output) request in USB HID [2]. The GATT Write Without Response sub-procedure is also used to write to a Report characteristic containing Output Report data, and this procedure maps to Data Output in USB HID [2]. https://www.bluetooth.com/specifications/specs/human-interface-device-service-1-0/
DJm00n commented 2023-02-26 16:48:13 -06:00 (Migrated from github.com)

HidD_SetOutputReport becomes IOCTL_HID_SET_OUTPUT_REPORT
WriteFile becomes IOCTL_HID_WRITE_REPORT

https://stackoverflow.com/questions/63115984/what-is-the-difference-between-read-report-get-input-report-write-report

Output items make Output reports accessible via the Control pipe with a Set_Report (Output) command.
Output type reports can optionally be sent via an Interrupt Out pipe.

The Control pipe is used for: ... Transmitting data when polled by the HID class driver (using the Get_Report
request).

The Interrupt Out pipe is optional. If a device declares an Interrupt Out endpoint then Output reports are transmitted by the host to the device through the Interrupt Out endpoint. If no Interrupt Out endpoint is declared then Output reports are transmitted to a device through the Control endpoint, using Set_Report(Output) requests.

https://www.usb.org/document-library/device-class-definition-hid-111

>HidD_SetOutputReport becomes IOCTL_HID_SET_OUTPUT_REPORT >WriteFile becomes IOCTL_HID_WRITE_REPORT https://stackoverflow.com/questions/63115984/what-is-the-difference-between-read-report-get-input-report-write-report >Output items make Output reports accessible via the Control pipe with a Set_Report (Output) command. >Output type reports can optionally be sent via an Interrupt Out pipe. >The Control pipe is used for: ... Transmitting data when polled by the HID class driver (using the Get_Report request). >The Interrupt Out pipe is optional. If a device declares an Interrupt Out endpoint then Output reports are transmitted by the host to the device through the Interrupt Out endpoint. If no Interrupt Out endpoint is declared then Output reports are transmitted to a device through the Control endpoint, using Set_Report(Output) requests. https://www.usb.org/document-library/device-class-definition-hid-111
Youw commented 2023-02-26 17:26:07 -06:00 (Migrated from github.com)

The later only proves that we cannot unconditionally replace WriteFile with SetOutputReport, I guess.

As for bug "WriteFile() doesn't work correctly" I think we could have a per-device, WinAPI-specific option to use HidD_SetOutputReport as hid_write implementation.

Or do you think we can predict for which devices WriteFile is going to fail? Is it known to be specific to BLE or smth?

The later only proves that we cannot unconditionally replace `WriteFile` with `SetOutputReport`, I guess. As for bug "WriteFile() doesn't work correctly" I think we could have a per-device, WinAPI-specific option to use `HidD_SetOutputReport` as `hid_write` implementation. Or do you think we can predict for which devices `WriteFile` is going to fail? Is it known to be specific to BLE or smth?
DJm00n commented 2023-02-27 07:45:04 -06:00 (Migrated from github.com)

As far as I understand, in BLE case API works like this:
WriteFile -> IOCTL_HID_WRITE_REPORT -> Data Output -> GATT Write Without Response -> ATT_OP_WRITE_CMD -> 0x52
HidD_SetOutputReport -> IOCTL_HID_SET_OUTPUT_REPORT -> Set_Report (Output) -> GATT Write Characteristic Value -> ATT_OP_WRITE_REQ -> 0x12

According to the HIDS spec Read/Write/Write Without Response are mandatory for Report: Output Report Type characteristic.

image

But seems some devices are not declaring Write Without Response and in result WriteFile call is failing (for example Stadia Controller with new "Bluetooth mode" firmware is known to have such issues).

I'll try to debug this to find out more.

As far as I understand, in BLE case API works like this: `WriteFile` -> `IOCTL_HID_WRITE_REPORT` -> `Data Output` -> `GATT Write Without Response` -> `ATT_OP_WRITE_CMD` -> `0x52` `HidD_SetOutputReport` -> `IOCTL_HID_SET_OUTPUT_REPORT` -> `Set_Report (Output)` -> `GATT Write Characteristic Value` -> `ATT_OP_WRITE_REQ` -> `0x12` According to the [HIDS spec](https://www.bluetooth.com/specifications/specs/human-interface-device-service-1-0/) `Read/Write/Write Without Response` are mandatory for `Report: Output Report Type` characteristic. ![image](https://user-images.githubusercontent.com/1285934/221578304-aef7b87b-f6fd-4d79-b8d2-b778a6a0fbd8.png) But seems some devices are not declaring `Write Without Response` and in result `WriteFile` call is failing (for example Stadia Controller with new "Bluetooth mode" firmware is known to have such issues). I'll try to debug this to find out more.
Youw commented 2023-02-27 08:09:37 -06:00 (Migrated from github.com)

But seems some devices are not declaring Write Without Response

Would be great to figure out if it is possible to check its support over WinAPI before trying to use WriteFile (e.g. if it is a part of some descriptor, or just device fails to respond on a specific command).

> But seems some devices are not declaring `Write Without Response` Would be great to figure out if it is possible to check its support over WinAPI before trying to use `WriteFile` (e.g. if it is a part of some descriptor, or just device fails to respond on a specific command).
mirh commented 2025-02-01 15:23:58 -06:00 (Migrated from github.com)
See also https://github.com/signal11/hidapi/issues/378
anagnorisis2peripeteia commented 2026-04-25 20:45:16 -05:00 (Migrated from github.com)

For DS4 controllers specifically: a fix has been submitted to the active DS4Windows fork that switches BT output from WriteFile (interrupt pipe) to HidD_SetOutputReport (control pipe): https://github.com/ds4windowsapp/DS4Windows/pull/9

Confirmed working on Realtek RTL8761B (TP-Link UB500) + Windows 11. The transport mapping analysis by @DJm00n in this issue was key to understanding the root cause — the unacknowledged interrupt path silently drops reports on these adapters.

For DS4 controllers specifically: a fix has been submitted to the active DS4Windows fork that switches BT output from WriteFile (interrupt pipe) to HidD_SetOutputReport (control pipe): https://github.com/ds4windowsapp/DS4Windows/pull/9 Confirmed working on Realtek RTL8761B (TP-Link UB500) + Windows 11. The transport mapping analysis by @DJm00n in this issue was key to understanding the root cause — the unacknowledged interrupt path silently drops reports on these adapters.
Youw commented 2026-04-26 08:56:32 -05:00 (Migrated from github.com)

Considering that we have a hid_send_output_report and this is not really a HIDAPI issue (a Windows Driver/Device-specific issue) I'd call out to close it w/o any specific fixes to HIDAPI itself (let it be as thin proxy as feasible), and, maybe, have a NOTE about this somewhere.

Considering that we have a [hid_send_output_report](https://github.com/libusb/hidapi/commit/4578ea24613af03e5631e400ed1167462702a112) and this is not really a HIDAPI issue (a Windows Driver/Device-specific issue) I'd call out to close it w/o any specific fixes to HIDAPI itself (let it be as thin proxy as feasible), and, maybe, have a NOTE about this somewhere.
mcuee commented 2026-04-26 09:02:53 -05:00 (Migrated from github.com)

maybe, have a NOTE about this somewhere.

Either the document or Wiki.

> maybe, have a NOTE about this somewhere. Either the document or Wiki.
Youw commented 2026-04-26 10:08:28 -05:00 (Migrated from github.com)
https://github.com/libusb/hidapi/wiki/Known-Issues#bluetooth-hid_write-may-silently-drop-output-reports-on-some-ble-adaptersdevices
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
gh-libusb/hidapi#513
No description provided.