Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions Documentation/ABI/testing/configfs-usb-gadget
Original file line number Diff line number Diff line change
Expand Up @@ -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.

8 changes: 8 additions & 0 deletions debian/changelog
Original file line number Diff line number Diff line change
@@ -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 <boger@wirenboard.com> 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,
Expand Down
40 changes: 38 additions & 2 deletions drivers/usb/gadget/composite.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <linux/usb/composite.h>
#include <linux/usb/otg.h>
#include <linux/usb/webusb.h>
#include <linux/usb/msos20.h>
#include <asm/unaligned.h>

#include "u_os_desc.h"
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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 &&
Expand Down
148 changes: 148 additions & 0 deletions drivers/usb/gadget/configfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <linux/usb/composite.h>
#include <linux/usb/gadget_configfs.h>
#include <linux/usb/webusb.h>
#include <linux/usb/msos20.h>
#include "configfs.h"
#include "u_f.h"
#include "u_os_desc.h"
Expand Down Expand Up @@ -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];
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
9 changes: 9 additions & 0 deletions include/linux/usb/composite.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand Down
49 changes: 49 additions & 0 deletions include/linux/usb/msos20.h
Original file line number Diff line number Diff line change
@@ -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 <linux/types.h>

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