From 27f0a55f0efc4a6245c97745e24e5723f04fd981 Mon Sep 17 00:00:00 2001 From: Evgeny Boger Date: Tue, 1 Sep 2026 18:23:00 +0300 Subject: [PATCH 1/2] usb: gadget: configfs: add MS OS 2.0 descriptors support Windows 8.1 and later read the MS OS 2.0 descriptor set announced through a BOS platform capability descriptor and ignore the older MS OS 1.0 descriptors the gadget stack already supports. Without MS OS 2.0 there is no way to tell Windows the DeviceInterfaceGUID of a function of a composite gadget: the MS OS 1.0 extended properties are not picked up for composite functions, and REG_MULTI_SZ properties cannot be emitted at all (composite.c rejects USB_EXT_PROP_UNICODE_MULTI). Without a registered interface GUID Windows creates no device interface for the function, so applications cannot obtain a device path for it and cannot talk to the device at all -- for instance Chromium fails to read the WebUSB landing page of a composite gadget. Add an "msos20" configfs group, modelled after the existing "webusb" one: echo 0x02 > msos20/bVendorCode cat descriptor_set.bin > msos20/descriptor_set echo 1 > msos20/use The descriptor set itself is opaque to the kernel and is supplied by userspace, which keeps the interface small and lets gadgets describe any number of functions and properties. Only the set header is validated on write, so that a truncated or mismatched blob is rejected there rather than confusing the host later. When enabled, the platform capability descriptor is added to the BOS descriptor and the device reports bcdUSB 0x0210, as the specification requires. Tested on a Wiren Board 8 (musb high-speed UDC) with a composite RNDIS + mass storage + FunctionFS gadget and Chromium 152 on Windows 11: the WinUSB function gets its DeviceInterfaceGUIDs registered, the RNDIS function keeps its compatible ID and the network interface keeps working, and the WebUSB landing page is read by the browser, which it is not without this change. Assisted-by: LLM Signed-off-by: Evgeny Boger --- Documentation/ABI/testing/configfs-usb-gadget | 22 +++ drivers/usb/gadget/composite.c | 40 ++++- drivers/usb/gadget/configfs.c | 148 ++++++++++++++++++ include/linux/usb/composite.h | 9 ++ include/linux/usb/msos20.h | 49 ++++++ 5 files changed, 266 insertions(+), 2 deletions(-) create mode 100644 include/linux/usb/msos20.h diff --git a/Documentation/ABI/testing/configfs-usb-gadget b/Documentation/ABI/testing/configfs-usb-gadget index a8bb896def544..2387b2640b4bc 100644 --- a/Documentation/ABI/testing/configfs-usb-gadget +++ b/Documentation/ABI/testing/configfs-usb-gadget @@ -156,3 +156,25 @@ Description: bVendorCode one-byte value used for custom per-device landingPage UTF-8 encoded URL of the device's landing page ============= =============================================== + +What: /config/usb-gadget/gadget/msos20 +Date: Sep 2026 +KernelVersion: 6.8 +Description: + This group contains "Microsoft OS 2.0 Descriptors" specific + attributes. + + =============== ============================================= + use flag turning MS OS 2.0 descriptors on/off + bVendorCode one-byte value used for the vendor request + that fetches the descriptor set + descriptor_set binary attribute holding the MS OS 2.0 + descriptor set; its contents come from + userspace, the kernel validates only the + set header + =============== ============================================= + + When enabled, a platform capability descriptor announcing the + descriptor set is added to the BOS descriptor, and the device + reports bcdUSB 0x0210 as the specification requires. + diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 7f7579d11843d..6b47d2ae5698c 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include "u_os_desc.h" @@ -903,6 +904,26 @@ static int bos_desc(struct usb_composite_dev *cdev) webusb_cap_data->iLandingPage = WEBUSB_LANDING_PAGE_NOT_PRESENT; } + if (cdev->use_msos20 && cdev->msos20_desc_set_len) { + struct usb_msos20_platform_descriptor *msos20_cap; + + msos20_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength); + bos->bNumDeviceCaps++; + le16_add_cpu(&bos->wTotalLength, USB_DT_USB_MSOS20_CAP_SIZE); + + msos20_cap->bLength = USB_DT_USB_MSOS20_CAP_SIZE; + msos20_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY; + msos20_cap->bDevCapabilityType = USB_PLAT_DEV_CAP_TYPE; + msos20_cap->bReserved = 0; + memcpy(msos20_cap->PlatformCapabilityUUID, MSOS20_UUID, + sizeof(MSOS20_UUID)); + msos20_cap->dwWindowsVersion = cpu_to_le32(MSOS20_WINDOWS_VERSION_8_1); + msos20_cap->wMSOSDescriptorSetTotalLength = + cpu_to_le16(cdev->msos20_desc_set_len); + msos20_cap->bMS_VendorCode = cdev->b_msos20_vendor_code; + msos20_cap->bAltEnumCode = 0; + } + return le16_to_cpu(bos->wTotalLength); } @@ -1842,7 +1863,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) * WebUSB requires bcdUSB >= 2.10; Chromium does not * read the landing page from a device below that. */ - if (cdev->use_webusb) + if (cdev->use_webusb || cdev->use_msos20) cdev->desc.bcdUSB = cpu_to_le16(0x0210); else if (gadget->lpm_capable) cdev->desc.bcdUSB = cpu_to_le16(0x0201); @@ -1879,7 +1900,8 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) break; case USB_DT_BOS: if (gadget_is_superspeed(gadget) || - gadget->lpm_capable || cdev->use_webusb) { + gadget->lpm_capable || cdev->use_webusb || + cdev->use_msos20) { value = bos_desc(cdev); value = min(w_length, (u16) value); } @@ -2164,6 +2186,20 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) * WebUSB URL descriptor handling, following: * https://wicg.github.io/webusb/#device-requests */ + /* + * MS OS 2.0 descriptor set, requested with the vendor code + * announced in the BOS platform capability descriptor. See the + * "Microsoft OS 2.0 Descriptors Specification". + */ + if (cdev->use_msos20 && cdev->msos20_desc_set && + ctrl->bRequestType == (USB_DIR_IN | USB_TYPE_VENDOR) && + ctrl->bRequest == cdev->b_msos20_vendor_code && + w_index == MSOS20_DESCRIPTOR_INDEX) { + value = min_t(u16, w_length, cdev->msos20_desc_set_len); + memcpy(cdev->req->buf, cdev->msos20_desc_set, value); + goto check_value; + } + if (cdev->use_webusb && ctrl->bRequestType == (USB_DIR_IN | USB_TYPE_VENDOR) && w_index == WEBUSB_GET_URL && diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c index ce3cfa1f36f51..35c1418b32419 100644 --- a/drivers/usb/gadget/configfs.c +++ b/drivers/usb/gadget/configfs.c @@ -8,6 +8,7 @@ #include #include #include +#include #include "configfs.h" #include "u_f.h" #include "u_os_desc.h" @@ -41,6 +42,7 @@ struct gadget_info { struct config_group strings_group; struct config_group os_desc_group; struct config_group webusb_group; + struct config_group msos20_group; struct mutex lock; struct usb_gadget_strings *gstrings[MAX_USB_STRING_LANGS + 1]; @@ -55,6 +57,11 @@ struct gadget_info { bool use_webusb; u16 bcd_webusb_version; u8 b_webusb_vendor_code; + + bool use_msos20; + u8 b_msos20_vendor_code; + u8 *msos20_desc_set; + u16 msos20_desc_set_len; char landing_page[WEBUSB_URL_RAW_MAX_LENGTH]; spinlock_t spinlock; @@ -1090,6 +1097,136 @@ static ssize_t webusb_landingPage_store(struct config_item *item, const char *pa return len; } +static inline struct gadget_info *msos20_item_to_gadget_info( + struct config_item *item) +{ + return container_of(to_config_group(item), + struct gadget_info, msos20_group); +} + +static ssize_t msos20_use_show(struct config_item *item, char *page) +{ + return sysfs_emit(page, "%d\n", + msos20_item_to_gadget_info(item)->use_msos20); +} + +static ssize_t msos20_use_store(struct config_item *item, const char *page, + size_t len) +{ + struct gadget_info *gi = msos20_item_to_gadget_info(item); + int ret; + bool use; + + ret = kstrtobool(page, &use); + if (ret) + return ret; + + mutex_lock(&gi->lock); + gi->use_msos20 = use; + mutex_unlock(&gi->lock); + + return len; +} + +static ssize_t msos20_bVendorCode_show(struct config_item *item, char *page) +{ + return sysfs_emit(page, "0x%02x\n", + msos20_item_to_gadget_info(item)->b_msos20_vendor_code); +} + +static ssize_t msos20_bVendorCode_store(struct config_item *item, + const char *page, size_t len) +{ + struct gadget_info *gi = msos20_item_to_gadget_info(item); + int ret; + u8 b_vendor_code; + + ret = kstrtou8(page, 0, &b_vendor_code); + if (ret) + return ret; + + mutex_lock(&gi->lock); + gi->b_msos20_vendor_code = b_vendor_code; + mutex_unlock(&gi->lock); + + return len; +} + +/* + * The descriptor set is opaque to the kernel: userspace is responsible for its + * contents, the kernel only checks the set header so that a truncated or + * mismatched blob is rejected at write time rather than confusing the host. + */ +static ssize_t msos20_descriptor_set_read(struct config_item *item, void *buf, + size_t count) +{ + struct gadget_info *gi = msos20_item_to_gadget_info(item); + ssize_t ret; + + mutex_lock(&gi->lock); + if (!buf) { + ret = gi->msos20_desc_set_len; + } else { + ret = min_t(size_t, count, gi->msos20_desc_set_len); + memcpy(buf, gi->msos20_desc_set, ret); + } + mutex_unlock(&gi->lock); + + return ret; +} + +static ssize_t msos20_descriptor_set_write(struct config_item *item, + const void *buf, size_t count) +{ + struct gadget_info *gi = msos20_item_to_gadget_info(item); + const __le16 *hdr = buf; + u8 *desc_set; + + if (count < MSOS20_SET_HEADER_SIZE || count > MSOS20_DESC_SET_MAX_LENGTH) + return -EINVAL; + + /* set header: wLength == 10, wDescriptorType == MS_OS_20_SET_HEADER */ + if (le16_to_cpu(hdr[0]) != MSOS20_SET_HEADER_SIZE || le16_to_cpu(hdr[1]) != 0) + return -EINVAL; + + /* wTotalLength must describe the whole blob */ + if (le16_to_cpu(hdr[4]) != count) + return -EINVAL; + + desc_set = kmemdup(buf, count, GFP_KERNEL); + if (!desc_set) + return -ENOMEM; + + mutex_lock(&gi->lock); + kfree(gi->msos20_desc_set); + gi->msos20_desc_set = desc_set; + gi->msos20_desc_set_len = count; + mutex_unlock(&gi->lock); + + return count; +} + +CONFIGFS_ATTR(msos20_, use); +CONFIGFS_ATTR(msos20_, bVendorCode); +CONFIGFS_BIN_ATTR(msos20_, descriptor_set, NULL, MSOS20_DESC_SET_MAX_LENGTH); + +static struct configfs_attribute *msos20_attrs[] = { + &msos20_attr_use, + &msos20_attr_bVendorCode, + NULL, +}; + +static struct configfs_bin_attribute *msos20_bin_attrs[] = { + &msos20_attr_descriptor_set, + NULL, +}; + +static struct config_item_type msos20_type = { + .ct_attrs = msos20_attrs, + .ct_bin_attrs = msos20_bin_attrs, + .ct_owner = THIS_MODULE, +}; + CONFIGFS_ATTR(webusb_, use); CONFIGFS_ATTR(webusb_, bVendorCode); CONFIGFS_ATTR(webusb_, bcdVersion); @@ -1728,6 +1865,13 @@ static int configfs_composite_bind(struct usb_gadget *gadget, gi->cdev.usb_strings = s; } + if (gi->use_msos20 && gi->msos20_desc_set_len) { + cdev->use_msos20 = true; + cdev->b_msos20_vendor_code = gi->b_msos20_vendor_code; + cdev->msos20_desc_set = gi->msos20_desc_set; + cdev->msos20_desc_set_len = gi->msos20_desc_set_len; + } + if (gi->use_webusb) { cdev->use_webusb = true; cdev->bcd_webusb_version = gi->bcd_webusb_version; @@ -2005,6 +2149,10 @@ static struct config_group *gadgets_make( &webusb_type); configfs_add_default_group(&gi->webusb_group, &gi->group); + config_group_init_type_name(&gi->msos20_group, "msos20", + &msos20_type); + configfs_add_default_group(&gi->msos20_group, &gi->group); + gi->composite.bind = configfs_do_nothing; gi->composite.unbind = configfs_do_nothing; gi->composite.suspend = NULL; diff --git a/include/linux/usb/composite.h b/include/linux/usb/composite.h index af3cd2aae4bcb..bb73afe29439c 100644 --- a/include/linux/usb/composite.h +++ b/include/linux/usb/composite.h @@ -452,6 +452,10 @@ static inline struct usb_composite_driver *to_cdriver( * @b_webusb_vendor_code: 0x0 by default, vendor code for WebUSB * @landing_page: empty by default, landing page to announce in WebUSB * @use_webusb: false by default, interested gadgets set it + * @b_msos20_vendor_code: vendor code for the MS OS 2.0 descriptor set request + * @msos20_desc_set: MS OS 2.0 descriptor set to announce, or NULL + * @msos20_desc_set_len: length of @msos20_desc_set + * @use_msos20: false by default, interested gadgets set it * @os_desc_config: the configuration to be used with OS descriptors * @setup_pending: true when setup request is queued but not completed * @os_desc_pending: true when os_desc request is queued but not completed @@ -478,6 +482,11 @@ struct usb_composite_dev { char landing_page[WEBUSB_URL_RAW_MAX_LENGTH]; unsigned int use_webusb:1; + u8 b_msos20_vendor_code; + const u8 *msos20_desc_set; + u16 msos20_desc_set_len; + unsigned int use_msos20:1; + /* private: */ /* internals */ unsigned int suspended:1; diff --git a/include/linux/usb/msos20.h b/include/linux/usb/msos20.h new file mode 100644 index 0000000000000..8ce6a6876a6b1 --- /dev/null +++ b/include/linux/usb/msos20.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Microsoft OS 2.0 descriptors + * + * Definitions for the BOS platform capability descriptor that announces an + * MS OS 2.0 descriptor set, per the "Microsoft OS 2.0 Descriptors + * Specification". The descriptor set itself is opaque to the kernel and is + * supplied by the gadget driver (see struct usb_composite_dev). + */ + +#ifndef __LINUX_USB_MSOS20_H +#define __LINUX_USB_MSOS20_H + +#include + +/* wIndex of the vendor request that returns the descriptor set */ +#define MSOS20_DESCRIPTOR_INDEX 0x07 + +/* dwWindowsVersion: NTDDI_WINBLUE, the minimum version that supports MS OS 2.0 */ +#define MSOS20_WINDOWS_VERSION_8_1 0x06030000 + +/* {D8DD60DF-4589-4CC7-9CD2-659D9E648A9F} in little endian byte order */ +#define MSOS20_UUID \ + ((const u8[16]) { \ + 0xdf, 0x60, 0xdd, 0xd8, 0x89, 0x45, 0xc7, 0x4c, \ + 0x9c, 0xd2, 0x65, 0x9d, 0x9e, 0x64, 0x8a, 0x9f }) + +#define USB_DT_USB_MSOS20_CAP_SIZE 28 + +/* Descriptor set header: wLength, wDescriptorType, dwWindowsVersion, wTotalLength */ +#define MSOS20_SET_HEADER_SIZE 10 + +/* The whole set has to fit into the control endpoint buffer */ +#define MSOS20_DESC_SET_MAX_LENGTH 1024 + +/* MS OS 2.0 platform capability descriptor, kept in the BOS descriptor */ +struct usb_msos20_platform_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDevCapabilityType; + __u8 bReserved; + __u8 PlatformCapabilityUUID[16]; + __le32 dwWindowsVersion; + __le16 wMSOSDescriptorSetTotalLength; + __u8 bMS_VendorCode; + __u8 bAltEnumCode; +} __packed; + +#endif /* __LINUX_USB_MSOS20_H */ From 518058e4931808ad3d2c1af0bd129ed9f831c626 Mon Sep 17 00:00:00 2001 From: Evgeny Boger Date: Tue, 1 Sep 2026 19:51:03 +0300 Subject: [PATCH 2/2] Bump version --- debian/changelog | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/debian/changelog b/debian/changelog index 1c0a27e8b8b5f..c1fb098be238a 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +linux-wb (6.8.0-wb163) stable; urgency=medium + + * usb: gadget: configfs: add MS OS 2.0 descriptors support, without which + Windows cannot be told the DeviceInterfaceGUID of a composite gadget + function and applications get no device path for it + + -- Evgeny Boger Tue, 01 Sep 2026 19:51:03 +0300 + linux-wb (6.8.0-wb162) stable; urgency=medium * usb: gadget: composite: report bcdUSB 0x0210 when WebUSB is enabled,