[PATCH v2 0/1] MR11205: Winebus usb compatible ids
### What winebus reports the USB class compatible IDs (`USB\Class_03&SubClass_xx&Prot_y`y and the two shorter forms) for USB devices, alongside the existing WINEBUS\\WINE_COMP_HID id. wineusb.sys already reports these IDs in this format for the devices it enumerates (dlls/wineusb.sys/wineusb.c, get_compatible_ids). winebus does not, so its synthesized USB-HID devices look different from a real Windows USB-HID device to any application that validates the USB parent. This makes winebus do the same. ### Why On Windows the USB parent carries these IDs and applications validate them through `CM_Get_Parent` or `SetupDi`. Wine omits them, so those applications fail. The Elgato Stream Deck app is one concrete case: it reports "no device found" for a connected Stream Deck Mini and recognises it once the IDs are present. winebus has no separate USB interface device to host the IDs. On a real stack the interface PDO from usbccgp is the HID device's parent; under winebus the IDs are reported on the HID device itself, which is the device an application reaches from the HID node. Only the hosting device differs from Windows; the IDs and their format match what wineusb already reports. ### How The interface class, subclass and protocol come from the udev usb_interface node (bInterfaceClass, bInterfaceSubClass, bInterfaceProtocol). get_compatible_ids formats them the same way wineusb.sys does, for USB devices that carry interface information, and keeps the existing ids ahead of them so driver binding is unchanged. ### Testing No automated test: winebus has no unit harness for the udev backend (the dinput HID test drives its own mock bus and never reaches this path), and the IDs derive from live sysfs values. Verified by hand: with the change the USB parent's CompatibleIds gains `USB\Class_03&SubClass_00&Prot_00` and the two shorter forms (read live from sysfs) alongside the existing WINE_COMP_HID, and the Elgato Stream Deck app then detects a connected Stream Deck Mini that it otherwise rejects. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=59891 Signed-off-by: Thomas Portal portal.thomas@protonmail.com -- v2: winebus.sys: Report USB class compatible IDs. https://gitlab.winehq.org/wine/wine/-/merge_requests/11205
From: Thomas Portal <portal.thomas@protonmail.com> Wine's synthesized HID devices only carry the WINEBUS\WINE_COMP_HID compatible ID. A real Windows USB-HID device also exposes the USB class compatible IDs (USB\Class_03&SubClass_xx&Prot_yy and the two shorter forms) on its USB interface parent. Applications that walk to that parent and validate the IDs, for example the Elgato Stream Deck app through CM_Get_Parent, reject the device when they are missing. winebus does not enumerate a separate USB interface device, so report the IDs on the HID device itself, which is what such an application reaches from the HID node. wineusb.sys already reports the same IDs in the same format for the devices it enumerates, so only the hosting device differs. The class, subclass and protocol come from the udev interface and are reported only for USB devices that carry interface information, which leaves the other backends unchanged. The existing WINE_COMP_XINPUT and WINE_COMP_HID ids stay ahead of them so driver binding does not change. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=59891 Signed-off-by: Thomas Portal <portal.thomas@protonmail.com> --- dlls/winebus.sys/bus_udev.c | 13 +++++++++++++ dlls/winebus.sys/main.c | 39 ++++++++++++++++++++++++++++++++----- dlls/winebus.sys/unixlib.h | 8 ++++++-- 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/dlls/winebus.sys/bus_udev.c b/dlls/winebus.sys/bus_udev.c index b307124c3be..e1714d0fa78 100644 --- a/dlls/winebus.sys/bus_udev.c +++ b/dlls/winebus.sys/bus_udev.c @@ -1174,6 +1174,17 @@ static void get_device_subsystem_info(struct udev_device *dev, const char *subsy } } +static void get_usb_interface_info(struct udev_device *dev, struct device_desc *desc) +{ + struct udev_device *iface; + const char *tmp; + + if (!(iface = udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_interface"))) return; + if ((tmp = udev_device_get_sysattr_value(iface, "bInterfaceClass"))) sscanf(tmp, "%x", &desc->class); + if ((tmp = udev_device_get_sysattr_value(iface, "bInterfaceSubClass"))) sscanf(tmp, "%x", &desc->subclass); + if ((tmp = udev_device_get_sysattr_value(iface, "bInterfaceProtocol"))) sscanf(tmp, "%x", &desc->protocol); +} + static NTSTATUS hidraw_device_create(struct udev_device *dev, int fd, const char *devnode, struct device_desc desc) { #ifdef HAVE_LINUX_HIDRAW_H @@ -1370,6 +1381,8 @@ static void udev_add_device(struct udev_device *dev, int fd) if (bus == BUS_BLUETOOTH) desc.bus_type = BUS_TYPE_BLUETOOTH; else if (bus == BUS_USB) desc.bus_type = BUS_TYPE_USB; + if (desc.bus_type == BUS_TYPE_USB) get_usb_interface_info(dev, &desc); + if (!(subsystem = udev_device_get_subsystem(dev))) { WARN("udev_device_get_subsystem failed for %s.\n", debugstr_a(devnode)); diff --git a/dlls/winebus.sys/main.c b/dlls/winebus.sys/main.c index a6227be9908..74b53b50c66 100644 --- a/dlls/winebus.sys/main.c +++ b/dlls/winebus.sys/main.c @@ -259,16 +259,45 @@ static WCHAR *get_compatible_ids(DEVICE_OBJECT *device) static const WCHAR xinput_compat[] = L"WINEBUS\\WINE_COMP_XINPUT"; static const WCHAR hid_compat[] = L"WINEBUS\\WINE_COMP_HID"; struct device_extension *ext = (struct device_extension *)device->DeviceExtension; - DWORD size = sizeof(hid_compat); - WCHAR *dst; + WCHAR usb_compat[3][40]; + DWORD usb_count = 0, size, len, i; + WCHAR *dst, *pos; + + /* A real Windows USB-HID device exposes the USB class compatible IDs on its USB + interface parent. winebus has no separate interface device, so report them on + the HID device, where an application reaches them from the HID node through + CM_Get_Parent. desc.class stays zero for backends that read no USB interface, + which leaves them unchanged. */ + if (ext->desc.bus_type == BUS_TYPE_USB && ext->desc.class) + { + swprintf(usb_compat[usb_count++], ARRAY_SIZE(usb_compat[0]), + L"USB\\Class_%02x&SubClass_%02x&Prot_%02x", ext->desc.class, ext->desc.subclass, ext->desc.protocol); + swprintf(usb_compat[usb_count++], ARRAY_SIZE(usb_compat[0]), + L"USB\\Class_%02x&SubClass_%02x", ext->desc.class, ext->desc.subclass); + swprintf(usb_compat[usb_count++], ARRAY_SIZE(usb_compat[0]), L"USB\\Class_%02x", ext->desc.class); + } + size = sizeof(hid_compat); if (ext->desc.is_gamepad) size += sizeof(xinput_compat); + for (i = 0; i < usb_count; i++) size += (wcslen(usb_compat[i]) + 1) * sizeof(WCHAR); if ((dst = ExAllocatePool(PagedPool, size + sizeof(WCHAR)))) { - if (ext->desc.is_gamepad) memcpy(dst, xinput_compat, sizeof(xinput_compat)); - memcpy((char *)dst + size - sizeof(hid_compat), hid_compat, sizeof(hid_compat)); - dst[size / sizeof(WCHAR)] = 0; + pos = dst; + if (ext->desc.is_gamepad) + { + memcpy(pos, xinput_compat, sizeof(xinput_compat)); + pos += sizeof(xinput_compat) / sizeof(WCHAR); + } + memcpy(pos, hid_compat, sizeof(hid_compat)); + pos += sizeof(hid_compat) / sizeof(WCHAR); + for (i = 0; i < usb_count; i++) + { + len = wcslen(usb_compat[i]) + 1; + memcpy(pos, usb_compat[i], len * sizeof(WCHAR)); + pos += len; + } + *pos = 0; } return dst; diff --git a/dlls/winebus.sys/unixlib.h b/dlls/winebus.sys/unixlib.h index d2b17f4c6d3..fa99c62ea40 100644 --- a/dlls/winebus.sys/unixlib.h +++ b/dlls/winebus.sys/unixlib.h @@ -39,6 +39,9 @@ struct device_desc UINT input; UINT uid; UINT bus_type; + UINT class; + UINT subclass; + UINT protocol; BOOL is_gamepad; BOOL is_hidraw; @@ -159,9 +162,10 @@ enum unix_funcs static inline const char *debugstr_device_desc(struct device_desc *desc) { if (!desc) return "(null)"; - return wine_dbg_sprintf("{vid %04x, pid %04x, version %04x, input %d, uid %08x, is_gamepad %u, is_hidraw %u, bus_type %u}", + return wine_dbg_sprintf("{vid %04x, pid %04x, version %04x, input %d, uid %08x, is_gamepad %u, is_hidraw %u, bus_type %u, class %02x, subclass %02x, protocol %02x}", desc->vid, desc->pid, desc->version, desc->input, desc->uid, - desc->is_gamepad, desc->is_hidraw, desc->bus_type); + desc->is_gamepad, desc->is_hidraw, desc->bus_type, + desc->class, desc->subclass, desc->protocol); } static inline BOOL is_xbox_gamepad(WORD vid, WORD pid) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11205
This is still current after the July 3 rebase, and CI remains green. The driver-binding concern appears addressed by retaining the WINEBUS compatible ID at higher priority. @rbernon, when convenient, could you confirm whether this compromise is acceptable, or whether you would prefer a separate USB interface PDO? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11205#note_147059
Rémi Bernon (@rbernon) commented about dlls/winebus.sys/unixlib.h:
UINT input; UINT uid; UINT bus_type; + UINT class; + UINT subclass; + UINT protocol;
A single, more generic, bus_id member seems more appropriate, as we have a bus_type already. The three bytes could be combined into an UINT for instance, and -1 could be used as an unset value, avoiding any conflict with another valid USB class/subclass/protocol combination. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11205#note_149334
Rémi Bernon (@rbernon) commented about dlls/winebus.sys/main.c:
static const WCHAR xinput_compat[] = L"WINEBUS\\WINE_COMP_XINPUT"; static const WCHAR hid_compat[] = L"WINEBUS\\WINE_COMP_HID"; struct device_extension *ext = (struct device_extension *)device->DeviceExtension; - DWORD size = sizeof(hid_compat); - WCHAR *dst; + WCHAR usb_compat[3][40];
This doesn't need to be an array, a single 71 WCHAR buffer would be simpler. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11205#note_149335
participants (3)
-
Rémi Bernon (@rbernon) -
Thomas Portal -
Thomas Portal (@OursCodeur)