Skip to content

Commit 8a71956

Browse files
committed
Windows: improved handling of composite devices
1 parent b31121c commit 8a71956

File tree

10 files changed

+239
-200
lines changed

10 files changed

+239
-200
lines changed

java-does-usb/pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,18 @@
149149
<version>3.24.2</version>
150150
<scope>test</scope>
151151
</dependency>
152+
<dependency>
153+
<groupId>org.tinylog</groupId>
154+
<artifactId>tinylog-impl</artifactId>
155+
<version>2.6.2</version>
156+
<scope>test</scope>
157+
</dependency>
158+
<dependency>
159+
<groupId>org.tinylog</groupId>
160+
<artifactId>jsl-tinylog</artifactId>
161+
<version>2.6.2</version>
162+
<scope>test</scope>
163+
</dependency>
152164
</dependencies>
153165

154166
</project>

java-does-usb/src/main/java/net/codecrete/usb/windows/InterfaceHandle.java

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
* Handles for WinUSB devices and interfaces
1414
*/
1515
class InterfaceHandle {
16+
InterfaceHandle(int interfaceNumber, int firstInterfaceNumber) {
17+
this.interfaceNumber = interfaceNumber;
18+
this.firstInterfaceNumber = firstInterfaceNumber;
19+
}
1620
/**
1721
* The number of this interface.
1822
*/
@@ -21,13 +25,6 @@ class InterfaceHandle {
2125
* The number of the first interface in the same composite function.
2226
*/
2327
int firstInterfaceNumber;
24-
/**
25-
* The device path.
26-
* <p>
27-
* This is only set for the first interface in a composite function.
28-
* </p>
29-
*/
30-
String devicePath;
3128
/**
3229
* The file handle of the device.
3330
* <p>
@@ -39,7 +36,7 @@ class InterfaceHandle {
3936
* The WinUSB handle of the interface.
4037
*/
4138
@SuppressWarnings("java:S1700")
42-
MemorySegment interfaceHandle;
39+
MemorySegment winusbHandle;
4340
/**
4441
* Count indicating how many interface depend on the device being open.
4542
*/

java-does-usb/src/main/java/net/codecrete/usb/windows/WindowsUSBDevice.java

Lines changed: 189 additions & 79 deletions
Large diffs are not rendered by default.

java-does-usb/src/main/java/net/codecrete/usb/windows/WindowsUSBDeviceRegistry.java

Lines changed: 4 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,7 @@
3131
import java.lang.invoke.MethodHandles;
3232
import java.lang.invoke.MethodType;
3333
import java.util.*;
34-
import java.util.regex.Pattern;
35-
import java.util.stream.Collectors;
3634

37-
import static java.lang.System.Logger.Level.DEBUG;
3835
import static java.lang.System.Logger.Level.INFO;
3936
import static java.lang.foreign.MemorySegment.NULL;
4037
import static java.lang.foreign.ValueLayout.*;
@@ -177,61 +174,19 @@ private USBDevice createDeviceFromDeviceInfo(DeviceInfoSet deviceInfoSet, String
177174
hubHandles.put(hubPath, hubHandle);
178175
}
179176

180-
// check for composite device
181-
var children = getChildDevices(deviceInfoSet, devicePath);
182-
183-
return createDevice(devicePath, children, hubHandle, usbPortNum);
184-
}
185-
}
186-
187-
@SuppressWarnings({"java:S106", "java:S1168"})
188-
private Map<Integer, String> getChildDevices(DeviceInfoSet deviceInfoSet, String devicePath) {
189-
if (!deviceInfoSet.isCompositeDevice())
190-
return null;
191-
192-
// For certain devices, it seems to take some time until the "Device_Children"
193-
// entry is present. So we retry a few times if needed and pause in between.
194-
List<String> childrenInstanceIDs;
195-
var numTries = 5;
196-
while (true) {
197-
numTries -= 1;
198-
childrenInstanceIDs = deviceInfoSet.getStringListProperty(Children);
199-
if (childrenInstanceIDs != null || numTries == 0)
200-
break;
201-
202-
// sleep and retry
203-
try {
204-
LOG.log(DEBUG, "Sleeping for 200ms (after unsuccessfully retrieving DEVPKEY_Device_Children)");
205-
//noinspection BusyWait
206-
Thread.sleep(200);
207-
} catch (InterruptedException e) {
208-
Thread.currentThread().interrupt();
209-
}
177+
return createDevice(devicePath, hubHandle, usbPortNum);
210178
}
211-
212-
if (childrenInstanceIDs == null) {
213-
LOG.log(DEBUG, "unable to retrieve information about children of device {0} - ignoring", devicePath);
214-
return null;
215-
}
216-
217-
// create children map (interface number -> device path)
218-
return childrenInstanceIDs.stream()
219-
.map(WindowsUSBDeviceRegistry::getNumberPathTuple)
220-
.filter(Objects::nonNull)
221-
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
222179
}
223180

224181
/**
225182
* Retrieve device descriptor and create {@code USBDevice} instance
226183
*
227184
* @param devicePath the device path
228-
* @param children map of child device paths, indexed by the first interface number
229185
* @param hubHandle the hub handle (parent)
230186
* @param usbPortNum the USB port number
231187
* @return the {@code USBDevice} instance
232188
*/
233-
private USBDevice createDevice(String devicePath, Map<Integer, String> children, MemorySegment hubHandle,
234-
int usbPortNum) {
189+
private USBDevice createDevice(String devicePath, MemorySegment hubHandle, int usbPortNum) {
235190

236191
try (var arena = Arena.ofConfined()) {
237192

@@ -253,7 +208,8 @@ private USBDevice createDevice(String devicePath, Map<Integer, String> children,
253208

254209
var configDesc = getDescriptor(hubHandle, usbPortNum, CONFIGURATION_DESCRIPTOR_TYPE, 0, (short) 0, arena);
255210

256-
var device = new WindowsUSBDevice(devicePath, children, vendorId, productId, configDesc);
211+
// create new device
212+
var device = new WindowsUSBDevice(devicePath, vendorId, productId, configDesc);
257213
device.setFromDeviceDescriptor(descriptorSegment);
258214
device.setProductString(descriptorSegment, index -> getStringDescriptor(hubHandle, usbPortNum, index));
259215

@@ -397,54 +353,4 @@ protected int findDeviceIndex(List<USBDevice> deviceList, Object deviceId) {
397353
}
398354
return -1;
399355
}
400-
401-
/**
402-
* Looks up the interface number and device path for the child device with the given instance ID.
403-
*
404-
* @param instanceId child instance ID
405-
* @return tuple consisting of interface number and device path, or {@code null} if unsuccessful
406-
*/
407-
private static Map.Entry<Integer, String> getNumberPathTuple(String instanceId) {
408-
try (var deviceInfoSet = DeviceInfoSet.ofInstance(instanceId)) {
409-
410-
// get hardware IDs (to extract interface number)
411-
var hardwareIds = deviceInfoSet.getStringListProperty(HardwareIds);
412-
if (hardwareIds == null)
413-
throwException("internal error (device property 'HardwareIds' is missing)");
414-
var interfaceNumber = extractInterfaceNumber(hardwareIds);
415-
if (interfaceNumber == -1) {
416-
LOG.log(DEBUG, "Child device {0} has no interface number", instanceId);
417-
return null;
418-
}
419-
420-
var devicePath = deviceInfoSet.getDevicePathByGUID(instanceId);
421-
if (devicePath == null) {
422-
LOG.log(DEBUG, "Child device {0} has no device path", instanceId);
423-
return null;
424-
}
425-
426-
return new AbstractMap.SimpleImmutableEntry<>(interfaceNumber, devicePath);
427-
}
428-
}
429-
430-
private static final Pattern MULTIPLE_INTERFACE_ID = Pattern.compile(
431-
"USB\\\\VID_[0-9A-Fa-f]{4}&PID_[0-9A-Fa-f]{4}&MI_([0-9A-Fa-f]{2})");
432-
433-
private static int extractInterfaceNumber(List<String> hardwareIds) {
434-
// Also see https://docs.microsoft.com/en-us/windows-hardware/drivers/install/standard-usb-identifiers#multiple-interface-usb-devices
435-
436-
for (var id : hardwareIds) {
437-
var matcher = MULTIPLE_INTERFACE_ID.matcher(id);
438-
if (matcher.find()) {
439-
var intfHexNumber = matcher.group(1);
440-
try {
441-
return Integer.parseInt(intfHexNumber, 16);
442-
} catch (NumberFormatException e) {
443-
// ignore and try next one
444-
}
445-
}
446-
}
447-
448-
return -1;
449-
}
450356
}

java-does-usb/src/main/java/net/codecrete/usb/windows/winsdk/WinUSB2.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,20 @@ public static int WinUsb_Initialize(MemorySegment DeviceHandle, MemorySegment In
5151
}
5252
}
5353

54+
private static final FunctionDescriptor WinUsb_GetAssociatedInterface$FUNC = FunctionDescriptor.of(JAVA_INT, ADDRESS, JAVA_BYTE, ADDRESS);
55+
56+
private static final MethodHandle WinUsb_GetAssociatedInterface$MH =
57+
LINKER.downcallHandle(LOOKUP.find("WinUsb_GetAssociatedInterface").get(), WinUsb_GetAssociatedInterface$FUNC, Win.LAST_ERROR_STATE);
58+
59+
public static int WinUsb_GetAssociatedInterface(MemorySegment InterfaceHandle, byte AssociatedInterfaceIndex,
60+
MemorySegment AssociatedInterfaceHandle, MemorySegment lastErrorState) {
61+
try {
62+
return (int) WinUsb_GetAssociatedInterface$MH.invokeExact(lastErrorState, InterfaceHandle, AssociatedInterfaceIndex, AssociatedInterfaceHandle);
63+
} catch (Throwable ex) {
64+
throw new AssertionError(ex);
65+
}
66+
}
67+
5468
private static final FunctionDescriptor WinUsb_SetCurrentAlternateSetting$FUNC = FunctionDescriptor.of(JAVA_INT,
5569
ADDRESS, JAVA_BYTE);
5670

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
writer = console
2+
writer.format = {date: HH:mm:ss.SSS} {level|size=5} [{class|size=40}] {message}
3+
writer.level = debug

reference/windows/USB/usb_device.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
#include <regex>
2323
#include <thread>
2424

25-
usb_device::usb_device(usb_registry* registry, std::wstring&& device_path, int vendor_id, int product_id, const std::vector<uint8_t>& config_desc, bool is_composite)
26-
: registry_(registry), vendor_id_(vendor_id), product_id_(product_id), is_open_(false), device_path_(std::move(device_path)), is_composite_(is_composite) {
25+
usb_device::usb_device(usb_registry* registry, std::wstring&& device_path, int vendor_id, int product_id, const std::vector<uint8_t>& config_desc)
26+
: registry_(registry), vendor_id_(vendor_id), product_id_(product_id), is_open_(false), device_path_(std::move(device_path)) {
2727

2828
config_parser parser{};
2929
parser.parse(config_desc.data(), static_cast<int>(config_desc.size()));
@@ -513,27 +513,27 @@ int usb_device::get_child_device_path(const std::wstring& child_id, int interfac
513513
auto hardware_ids = dev_info_set.get_device_property_string_list(DEVPKEY_Device_HardwareIds);
514514
if (hardware_ids.empty()) {
515515
std::wcerr << "child device " << child_id << " has no hardware IDs" << std::endl;
516-
return 0;
516+
return 0; // continue with next child
517517
}
518518

519519
auto intf_num = extract_interface_number(hardware_ids);
520520
if (intf_num == -1) {
521521
std::wcerr << "child device " << child_id << " has no interface number" << std::endl;
522-
return 0;
522+
return 0; // continue with next child
523523
}
524524

525525
if (intf_num != interface_num)
526-
return 0;
526+
return 0; // continue with next child
527527

528528
device_path = dev_info_set.get_device_path_by_guid(child_id);
529529
if (device_path.empty()) {
530530
std::wcerr << "child device " << child_id << " has no device path" << std::endl;
531-
return -1;
531+
return -1; // retry later
532532
}
533533

534534
std::wcerr << "child device: interface=" << intf_num << ", device path=" << device_path << std::endl;
535535
interface_device_paths_[interface_num] = device_path;
536-
return 1;
536+
return 1; // success
537537
}
538538

539539
static const std::wregex multiple_interface_id_pattern(L"USB\\\\VID_[0-9A-Fa-f]{4}&PID_[0-9A-Fa-f]{4}&MI_([0-9A-Fa-f]{2})");

reference/windows/USB/usb_device.hpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ class usb_device {
248248
interface_handle(int intf_num, int first_num, std::wstring&& path);
249249
};
250250

251-
usb_device(usb_registry* registry, std::wstring&& device_path, int vendor_id, int product_id, const std::vector<uint8_t>& config_desc, bool is_composite);
251+
usb_device(usb_registry* registry, std::wstring&& device_path, int vendor_id, int product_id, const std::vector<uint8_t>& config_desc);
252252
void set_product_names(const std::string& manufacturer, const std::string& product, const std::string& serial_number);
253253
void build_handles(const std::wstring& device_path);
254254
std::wstring get_interface_device_path(int interface_num);
@@ -289,7 +289,6 @@ class usb_device {
289289
std::vector<usb_composite_function> functions_;
290290
std::vector<interface_handle> interface_handles_;
291291
std::map<int, std::wstring> interface_device_paths_;
292-
bool is_composite_;
293292

294293
friend class usb_registry;
295294
friend class usb_istreambuf;

reference/windows/USB/usb_registry.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -167,10 +167,10 @@ std::shared_ptr<usb_device> usb_registry::create_device_from_device_info(device_
167167
hub_handles[hub_path] = hub_handle;
168168
}
169169

170-
return create_device(std::move(device_path), dev_info_set.is_composite_device(), hub_handle, usb_port_num);
170+
return create_device(std::move(device_path), hub_handle, usb_port_num);
171171
}
172172

173-
std::shared_ptr<usb_device> usb_registry::create_device(std::wstring&& device_path, bool is_composite, HANDLE hub_handle, DWORD usb_port_num) {
173+
std::shared_ptr<usb_device> usb_registry::create_device(std::wstring&& device_path, HANDLE hub_handle, DWORD usb_port_num) {
174174

175175
// get device descriptor
176176
USB_NODE_CONNECTION_INFORMATION_EX conn_info = { 0 };
@@ -186,8 +186,7 @@ std::shared_ptr<usb_device> usb_registry::create_device(std::wstring&& device_pa
186186
auto config_desc = get_descriptor(hub_handle, usb_port_num, USB_CONFIGURATION_DESCRIPTOR_TYPE, 0, 0);
187187

188188
// Create new device
189-
// usb_registry* registry, std::wstring&& device_path, int vendor_id, int product_id, const std::vector<uint8_t>& config_desc, std::map<int, std::wstring>&& children
190-
std::shared_ptr<usb_device> device(new usb_device(this, std::move(device_path), vendorId, productId, config_desc, is_composite));
189+
std::shared_ptr<usb_device> device(new usb_device(this, std::move(device_path), vendorId, productId, config_desc));
191190
device->set_product_names(
192191
get_string(hub_handle, usb_port_num, conn_info.DeviceDescriptor.iManufacturer),
193192
get_string(hub_handle, usb_port_num, conn_info.DeviceDescriptor.iProduct),

reference/windows/USB/usb_registry.hpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,10 @@ class usb_registry {
5353

5454
void detect_present_devices();
5555
std::shared_ptr<usb_device> create_device_from_device_info(device_info_set& dev_info_set, std::wstring&& device_path, std::map<std::wstring, HANDLE>& hub_handles);
56-
std::shared_ptr<usb_device> create_device(std::wstring&& device_path, bool is_composite, HANDLE hub_handle, DWORD usb_port_num);
56+
std::shared_ptr<usb_device> create_device(std::wstring&& device_path, HANDLE hub_handle, DWORD usb_port_num);
5757

5858
static std::string get_string(HANDLE hub_handle, ULONG usb_port_num, int index);
5959
static std::vector<uint8_t> get_descriptor(HANDLE hub_handle, ULONG usb_port_num, uint16_t descriptor_type, int index, int language_id, int request_size = 0);
60-
static int extract_interface_number(const std::vector<std::wstring>& hardware_ids);
6160

6261
void on_device_connected(const WCHAR* path);
6362
void on_device_disconnected(const WCHAR* path);

0 commit comments

Comments
 (0)