libusb / hidapi

A Simple cross-platform library for communicating with HID devices
https://libusb.info/hidapi/
Other
1.64k stars 394 forks source link

Windows: Bluetooth and WriteFile API issues #513

Open DJm00n opened 1 year ago

DJm00n commented 1 year ago

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 1 year ago

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 1 year ago

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 1 year ago

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 1 year ago

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.

Youw commented 1 year ago

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).