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/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, 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 */