-
Notifications
You must be signed in to change notification settings - Fork 11
Windows Implementation
The Windows implementation is by far the most complex implementation, mainly due to the complex model how composite USB devices are presented in APIs. A particular style of APIs using variable length structs and functions with many arguments do not make it easier.
Also see Windows reference code.
For device enumeration, the Setup API is used. As the name already implies, it was built for a different purpose and it's not obvious from the documentation how to get information about USB devices.
In order to get details about a USB device, a DeviceIoControl request is made using IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX. The request is not issued to the device but rather to the USB hub. So the USB hub must be opened first. That way the USB device and configuration descriptors can be received without directly opening the USB device, which is not possible for devices already in use or devices not using the WinUSB driver.
Composite devices are represented as a parent and multiple child devices. The code assumes that a device is a composite device if usbccgp (USB Generic Parent Driver) is used as the device's driver (Windows calls it "device service").
Enumerating the child devices is not too difficult but determining the device path is. The implemented solution is to open the registry key for device configuration information (SetupDiOpenDiRegKey), lookup the registry value DeviceInterfaceGUIDs, split it into a list of GUIDs and finally use the Setup API for each one to look up the device path until the first one succeeds.
A further challenges is to figure out which USB interfaces the child device is responsible for as this information will be needed to use the WinUSB APIs. A child device can be responsible for a single USB interface, or for multiple ones. In the case of multiple ones, they must have consecutive numbers (given how interface associations work). The implemented solution determines uses the list of hardware IDs and scans them for the pattern USB\VID_nnnn&PID_nnnn&MI_nn. The last 2 digits (nn) in this pattern represent the number of the child device's first interface. The pattern is documented by Microsoft in Multiple-Interface USB Devices. But it is unclear if this will always stay like this.
Notifications about connected and disconnected devices are registered with the Kernel32 function RegisterDeviceNotification, and not with a Setup API function as one would expect. In line with the original Windows architecture, notification messages are sent to a window. To receive them, a so called Message-Only Window is created. It never appears on screen and is excluded from most enumeration of windows to not confuse UI applications.
An upcall stub of the Foreign Function & Memory API is used to register Java code as the message handler for the window.
The background process waits in a window message loop for the next event.
As described in Composite Devices and Interface Associations, a composite USB device consists of multiple function and each function is represented as a separate Windows device in Windows. Before communicating with an endpoint, the correct child device and the correct interface must be opened.
There are no APIs to retrieve the list of functions. So the configuration descriptor (including the interface association descriptors) is parsed. The result is a list of functions. Each function consists of the interface number of the first interface and the count of interfaces. This is matched with the details of the child devices.
Managing the device and interface handles (including a reference count for device handles) requires additional code not necessary on Linux and macOS.
This complexity also affects control transfers. If the recipient is an interface or endpoint, they must be sent using the interface handle of the particular interface or endpoint. If the recipient is the device, they can be sent using any open interface handle.
Aside from the challenges with composite devices and multiple interface, the WinUSB APIs are straightforward to use from Java. They have limitations though as they do not support changing the device configuration or using USB 3 streams.
The Windows implementation suffers from a short-coming in the current state of the Foreign Function & Memory (FFM) API: the last error code is sometimes overwritten by the JVM. In JDK 19, there is no solution for it.
As a workaround, error handling code is reduced, e.g. if a function call indicates an error, it is assumed that it was a timeout as the real error reason cannot be determined reliably.
In JDK 20, the FFM supports linker options for GetLastError(). They add an additional parameter to the method call for safely retrieving the last error. Unfortunately, it is not yet support by jextract. So a large number of wrapper functions for Windows SDK had to be created manually. See jdk20 branch for more details.