2022-12-30 17:10:39

by Jó Ágila Bitsch

[permalink] [raw]
Subject: [PATCH] usb: gadget: add WebUSB support

There is a custom (non-USB IF) extension to the USB standard:

https://wicg.github.io/webusb/

This specification is published under the W3C Community Contributor
Agreement, which in particular allows to implement the specification
without any royalties.

The specification allows USB gadgets to announce an URL to landing
page and describes a Javascript interface for websites to interact
with the USB gadget, if the user allows it. It is currently
supported by Chromium-based browsers, such as Chrome, Edge and
Opera on all major operating systems including Linux.

This patch adds optional support for Linux-based USB gadgets
wishing to expose such a landing page.

During device enumeration, a host recognizes that the announced
USB version is at least 2.01, which means, that there are BOS
descriptors available. The device than announces WebUSB support
using a platform device capability. This includes a vendor code
under which the landing page URL can be retrieved using a
vendor-specific request.

Usage is modeled after os_desc descriptors:
echo 1 > webusb/use
echo "https://www.kernel.org" > webusb/landingPage

lsusb will report the device with the following lines:
Platform Device Capability:
bLength 24
bDescriptorType 16
bDevCapabilityType 5
bReserved 0
PlatformCapabilityUUID {3408b638-09a9-47a0-8bfd-a0768815b665}
WebUSB:
bcdVersion 1.00
bVendorCode 0
iLandingPage 1 https://www.kernel.org

Signed-off-by: J? ?gila Bitsch <[email protected]>
---
Documentation/ABI/testing/configfs-usb-gadget | 13 ++
drivers/usb/gadget/composite.c | 102 +++++++++++--
drivers/usb/gadget/configfs.c | 139 ++++++++++++++++++
include/linux/usb/composite.h | 6 +
include/uapi/linux/usb/ch9.h | 33 +++++
5 files changed, 283 insertions(+), 10 deletions(-)

diff --git a/Documentation/ABI/testing/configfs-usb-gadget b/Documentation/ABI/testing/configfs-usb-gadget
index b7943aa7e997..8399e0f0ed1c 100644
--- a/Documentation/ABI/testing/configfs-usb-gadget
+++ b/Documentation/ABI/testing/configfs-usb-gadget
@@ -143,3 +143,16 @@ Description:
qw_sign an identifier to be reported as "OS String"
proper
============= ===============================================
+
+What: /config/usb-gadget/gadget/webusb
+Date: Dec 2022
+KernelVersion: 6.2
+Description:
+ This group contains "WebUSB" extension handling attributes.
+
+ ============= ===============================================
+ use flag turning "WebUSB" support on/off
+ bcdVersion bcd WebUSB specification version number
+ b_vendor_code one-byte value used for custom per-device
+ landing_page UTF-8 encoded URL of the device's landing page
+ ============= ===============================================
diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c
index 403563c06477..937695dc5366 100644
--- a/drivers/usb/gadget/composite.c
+++ b/drivers/usb/gadget/composite.c
@@ -14,6 +14,7 @@
#include <linux/device.h>
#include <linux/utsname.h>
#include <linux/bitfield.h>
+#include <linux/uuid.h>

#include <linux/usb/composite.h>
#include <linux/usb/otg.h>
@@ -713,14 +714,16 @@ static int bos_desc(struct usb_composite_dev *cdev)
* A SuperSpeed device shall include the USB2.0 extension descriptor
* and shall support LPM when operating in USB2.0 HS mode.
*/
- usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
- bos->bNumDeviceCaps++;
- le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
- usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
- usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
- usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
- usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT |
- USB_BESL_SUPPORT | besl);
+ if (cdev->gadget->lpm_capable) {
+ usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
+ bos->bNumDeviceCaps++;
+ le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
+ usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
+ usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
+ usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
+ usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT |
+ USB_BESL_SUPPORT | besl);
+ }

/*
* The Superspeed USB Capability descriptor shall be implemented by all
@@ -821,6 +824,41 @@ static int bos_desc(struct usb_composite_dev *cdev)
}
}

+ /*
+ * Following the specifaction at:
+ * https://wicg.github.io/webusb/#webusb-platform-capability-descriptor
+ */
+ if (cdev->use_webusb) {
+ struct usb_webusb_cap_descriptor *webusb_cap;
+ /*
+ * little endian PlatformCapablityUUID for WebUSB:
+ * 3408b638-09a9-47a0-8bfd-a0768815b665
+ */
+#define WEBUSB_UUID UUID_INIT(0x38b60834, 0xa909, 0xa047, \
+ 0x8b, 0xfd, 0xa0, 0x76, 0x88, 0x15, 0xb6, 0x65)
+ uuid_t webusb_uuid = WEBUSB_UUID;
+
+ webusb_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
+ bos->bNumDeviceCaps++;
+
+ le16_add_cpu(&bos->wTotalLength, USB_DT_WEBUSB_SIZE);
+ webusb_cap->bLength = USB_DT_WEBUSB_SIZE;
+ webusb_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
+ webusb_cap->bDevCapabilityType = USB_PLAT_DEV_CAP_TYPE;
+ webusb_cap->bReserved = 0;
+ export_uuid(webusb_cap->UUID, &webusb_uuid);
+ if (cdev->bcd_webusb_version != 0)
+ webusb_cap->bcdVersion = cpu_to_le16(cdev->bcd_webusb_version);
+ else
+ webusb_cap->bcdVersion = cpu_to_le16(0x0100);
+
+ webusb_cap->bVendorCode = cdev->b_webusb_vendor_code;
+ if (strnlen(cdev->landing_page, sizeof(cdev->landing_page)) > 0)
+ webusb_cap->iLandingPage = 1;
+ else
+ webusb_cap->iLandingPage = 0;
+ }
+
return le16_to_cpu(bos->wTotalLength);
}

@@ -1744,7 +1782,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
cdev->desc.bcdUSB = cpu_to_le16(0x0210);
}
} else {
- if (gadget->lpm_capable)
+ if (gadget->lpm_capable || cdev->use_webusb)
cdev->desc.bcdUSB = cpu_to_le16(0x0201);
else
cdev->desc.bcdUSB = cpu_to_le16(0x0200);
@@ -1779,7 +1817,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
break;
case USB_DT_BOS:
if (gadget_is_superspeed(gadget) ||
- gadget->lpm_capable) {
+ gadget->lpm_capable || cdev->use_webusb) {
value = bos_desc(cdev);
value = min(w_length, (u16) value);
}
@@ -2013,6 +2051,50 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
goto check_value;
}

+ /*
+ * webusb descriptor handling, following:
+ * https://wicg.github.io/webusb/#device-requests
+ */
+ #define WEBUSB_GET_URL 2
+ if (cdev->use_webusb &&
+ (ctrl->bRequestType & USB_TYPE_VENDOR) &&
+ ctrl->wIndex == WEBUSB_GET_URL &&
+ ctrl->bRequest == cdev->b_webusb_vendor_code) {
+ /*
+ * specification of the url descriptor in WebUSB:
+ * https://wicg.github.io/webusb/#webusb-descriptors
+ */
+ u8 *buf;
+ unsigned int schema_http;
+ unsigned int schema_https;
+ unsigned int landing_page_length;
+
+ buf = cdev->req->buf;
+ #define WEBUSB_URL 3
+ buf[1] = WEBUSB_URL;
+
+ landing_page_length = strnlen(cdev->landing_page, 252);
+ schema_https = (strncmp("https://", cdev->landing_page, 8) == 0);
+ schema_http = (strncmp("http://", cdev->landing_page, 7) == 0);
+ if (schema_https) {
+ buf[2] = 0x01;
+ memcpy(buf+3, cdev->landing_page+8, landing_page_length-8);
+ buf[0] = landing_page_length - 8 + 3;
+ } else if (schema_http) {
+ buf[2] = 0x00;
+ memcpy(buf+3, cdev->landing_page+7, landing_page_length-7);
+ buf[0] = landing_page_length - 7 + 3;
+ } else {
+ buf[2] = 0xFF;
+ memcpy(buf+3, cdev->landing_page, landing_page_length);
+ buf[0] = landing_page_length + 3;
+ }
+
+ value = buf[0];
+
+ goto check_value;
+ }
+
VDBG(cdev,
"non-core control req%02x.%02x v%04x i%04x l%d\n",
ctrl->bRequestType, ctrl->bRequest,
diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c
index 96121d1c8df4..0c1a78d9b5a0 100644
--- a/drivers/usb/gadget/configfs.c
+++ b/drivers/usb/gadget/configfs.c
@@ -39,6 +39,7 @@ struct gadget_info {
struct config_group configs_group;
struct config_group strings_group;
struct config_group os_desc_group;
+ struct config_group webusb_group;

struct mutex lock;
struct usb_gadget_strings *gstrings[MAX_USB_STRING_LANGS + 1];
@@ -50,6 +51,11 @@ struct gadget_info {
bool use_os_desc;
char b_vendor_code;
char qw_sign[OS_STRING_QW_SIGN_LEN];
+ bool use_webusb;
+ u16 bcd_webusb_version;
+ u8 b_webusb_vendor_code;
+ char landing_page[255];
+
spinlock_t spinlock;
bool unbind;
};
@@ -780,6 +786,125 @@ static void gadget_strings_attr_release(struct config_item *item)
USB_CONFIG_STRING_RW_OPS(gadget_strings);
USB_CONFIG_STRINGS_LANG(gadget_strings, gadget_info);

+static inline struct gadget_info *webusb_item_to_gadget_info(
+ struct config_item *item)
+{
+ return container_of(to_config_group(item),
+ struct gadget_info, webusb_group);
+}
+
+static ssize_t webusb_use_show(struct config_item *item, char *page)
+{
+ return sprintf(page, "%d\n",
+ webusb_item_to_gadget_info(item)->use_webusb);
+}
+
+static ssize_t webusb_use_store(struct config_item *item, const char *page,
+ size_t len)
+{
+ struct gadget_info *gi = webusb_item_to_gadget_info(item);
+ int ret;
+ bool use;
+
+ mutex_lock(&gi->lock);
+ ret = kstrtobool(page, &use);
+ if (!ret) {
+ gi->use_webusb = use;
+ ret = len;
+ }
+ mutex_unlock(&gi->lock);
+
+ return ret;
+}
+
+static ssize_t webusb_bcdVersion_show(struct config_item *item, char *page)
+{
+ return snprintf(page, PAGE_SIZE, "0x%04x\n",
+ webusb_item_to_gadget_info(item)->bcd_webusb_version);
+}
+
+static ssize_t webusb_bcdVersion_store(struct config_item *item,
+ const char *page, size_t len)
+{
+ struct gadget_info *gi = webusb_item_to_gadget_info(item);
+ u16 bcdVersion;
+ int ret;
+
+ ret = kstrtou16(page, 0, &bcdVersion);
+ if (ret)
+ return ret;
+ ret = is_valid_bcd(bcdVersion);
+ if (ret)
+ return ret;
+
+ gi->bcd_webusb_version = bcdVersion;
+ return len;
+}
+
+static ssize_t webusb_bVendorCode_show(struct config_item *item, char *page)
+{
+ return sprintf(page, "0x%02x\n",
+ webusb_item_to_gadget_info(item)->b_webusb_vendor_code);
+}
+
+static ssize_t webusb_bVendorCode_store(struct config_item *item,
+ const char *page, size_t len)
+{
+ struct gadget_info *gi = webusb_item_to_gadget_info(item);
+ int ret;
+ u8 b_vendor_code;
+
+ mutex_lock(&gi->lock);
+ ret = kstrtou8(page, 0, &b_vendor_code);
+ if (!ret) {
+ gi->b_webusb_vendor_code = b_vendor_code;
+ ret = len;
+ }
+ mutex_unlock(&gi->lock);
+
+ return ret;
+}
+
+static ssize_t webusb_landingPage_show(struct config_item *item, char *page)
+{
+ return snprintf(page, PAGE_SIZE, "%s\n", webusb_item_to_gadget_info(item)->landing_page);
+}
+
+static ssize_t webusb_landingPage_store(struct config_item *item, const char *page,
+ size_t len)
+{
+ struct gadget_info *gi = webusb_item_to_gadget_info(item);
+ int l;
+
+ l = min(len, sizeof(gi->landing_page));
+ if (page[l - 1] == '\n')
+ --l;
+
+ mutex_lock(&gi->lock);
+ memcpy(gi->landing_page, page, l);
+ mutex_unlock(&gi->lock);
+
+ return len;
+}
+
+CONFIGFS_ATTR(webusb_, use);
+CONFIGFS_ATTR(webusb_, bVendorCode);
+CONFIGFS_ATTR(webusb_, bcdVersion);
+CONFIGFS_ATTR(webusb_, landingPage);
+
+static struct configfs_attribute *webusb_attrs[] = {
+ &webusb_attr_use,
+ &webusb_attr_bcdVersion,
+ &webusb_attr_bVendorCode,
+ &webusb_attr_landingPage,
+ NULL,
+};
+
+static struct config_item_type webusb_type = {
+ .ct_attrs = webusb_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
static inline struct gadget_info *os_desc_item_to_gadget_info(
struct config_item *item)
{
@@ -1341,6 +1466,16 @@ static int configfs_composite_bind(struct usb_gadget *gadget,
gi->cdev.desc.iSerialNumber = s[USB_GADGET_SERIAL_IDX].id;
}

+ if (gi->use_webusb) {
+ cdev->use_webusb = true;
+ cdev->bcd_webusb_version = gi->bcd_webusb_version;
+ cdev->b_webusb_vendor_code = gi->b_webusb_vendor_code;
+ memcpy(cdev->landing_page, gi->landing_page,
+ strnlen(gi->landing_page,
+ min(sizeof(cdev->landing_page),
+ sizeof(gi->landing_page))));
+ }
+
if (gi->use_os_desc) {
cdev->use_os_string = true;
cdev->b_vendor_code = gi->b_vendor_code;
@@ -1605,6 +1740,10 @@ static struct config_group *gadgets_make(
&os_desc_type);
configfs_add_default_group(&gi->os_desc_group, &gi->group);

+ config_group_init_type_name(&gi->webusb_group, "webusb",
+ &webusb_type);
+ configfs_add_default_group(&gi->webusb_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 43ac3fa760db..44f90c37dda9 100644
--- a/include/linux/usb/composite.h
+++ b/include/linux/usb/composite.h
@@ -474,6 +474,12 @@ struct usb_composite_dev {
struct usb_configuration *os_desc_config;
unsigned int use_os_string:1;

+ /* WebUSB */
+ u16 bcd_webusb_version;
+ u8 b_webusb_vendor_code;
+ char landing_page[255];
+ unsigned int use_webusb:1;
+
/* private: */
/* internals */
unsigned int suspended:1;
diff --git a/include/uapi/linux/usb/ch9.h b/include/uapi/linux/usb/ch9.h
index 31fcfa084e63..3a36550297bc 100644
--- a/include/uapi/linux/usb/ch9.h
+++ b/include/uapi/linux/usb/ch9.h
@@ -947,6 +947,39 @@ struct usb_ss_container_id_descriptor {

#define USB_DT_USB_SS_CONTN_ID_SIZE 20

+/*
+ * Platform Device Capability descriptor: Defines platform specific device
+ * capabilities
+ */
+#define USB_PLAT_DEV_CAP_TYPE 5
+struct usb_plat_dev_cap_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDevCapabilityType;
+ __u8 bReserved;
+ __u8 UUID[16];
+} __attribute__((packed));
+
+#define USB_DT_USB_PLAT_DEV_CAP_SIZE 20
+
+/*
+ * WebUSB Platform Capability descriptor: A device announces support for the
+ * WebUSB command set by including the following Platform Descriptor in its
+ * Binary Object Store
+ * https://wicg.github.io/webusb/#webusb-platform-capability-descriptor
+ */
+struct usb_webusb_cap_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDevCapabilityType;
+ __u8 bReserved;
+ __u8 UUID[16];
+ __u16 bcdVersion;
+ __u8 bVendorCode;
+ __u8 iLandingPage;
+} __attribute__((packed));
+#define USB_DT_WEBUSB_SIZE (USB_DT_USB_PLAT_DEV_CAP_SIZE + 4)
+
/*
* SuperSpeed Plus USB Capability descriptor: Defines the set of
* SuperSpeed Plus USB specific device level capabilities
--
2.37.2


2022-12-30 17:55:28

by Christophe JAILLET

[permalink] [raw]
Subject: Re: [PATCH] usb: gadget: add WebUSB support

Le 30/12/2022 à 17:58, Jó Ágila Bitsch a écrit :
> There is a custom (non-USB IF) extension to the USB standard:
>
> https://wicg.github.io/webusb/
>
> This specification is published under the W3C Community Contributor
> Agreement, which in particular allows to implement the specification
> without any royalties.
>
> The specification allows USB gadgets to announce an URL to landing
> page and describes a Javascript interface for websites to interact
> with the USB gadget, if the user allows it. It is currently
> supported by Chromium-based browsers, such as Chrome, Edge and
> Opera on all major operating systems including Linux.
>
> This patch adds optional support for Linux-based USB gadgets
> wishing to expose such a landing page.
>
> During device enumeration, a host recognizes that the announced
> USB version is at least 2.01, which means, that there are BOS
> descriptors available. The device than announces WebUSB support
> using a platform device capability. This includes a vendor code
> under which the landing page URL can be retrieved using a
> vendor-specific request.
>
> Usage is modeled after os_desc descriptors:
> echo 1 > webusb/use
> echo "https://www.kernel.org" > webusb/landingPage
>
> lsusb will report the device with the following lines:
> Platform Device Capability:
> bLength 24
> bDescriptorType 16
> bDevCapabilityType 5
> bReserved 0
> PlatformCapabilityUUID {3408b638-09a9-47a0-8bfd-a0768815b665}
> WebUSB:
> bcdVersion 1.00
> bVendorCode 0
> iLandingPage 1 https://www.kernel.org
>
> Signed-off-by: Jó Ágila Bitsch <[email protected]>

Hi,

a few nits below.

CJ

> ---
> Documentation/ABI/testing/configfs-usb-gadget | 13 ++
> drivers/usb/gadget/composite.c | 102 +++++++++++--
> drivers/usb/gadget/configfs.c | 139 ++++++++++++++++++
> include/linux/usb/composite.h | 6 +
> include/uapi/linux/usb/ch9.h | 33 +++++
> 5 files changed, 283 insertions(+), 10 deletions(-)
>
> diff --git a/Documentation/ABI/testing/configfs-usb-gadget b/Documentation/ABI/testing/configfs-usb-gadget
> index b7943aa7e997..8399e0f0ed1c 100644
> --- a/Documentation/ABI/testing/configfs-usb-gadget
> +++ b/Documentation/ABI/testing/configfs-usb-gadget
> @@ -143,3 +143,16 @@ Description:
> qw_sign an identifier to be reported as "OS String"
> proper
> ============= ===============================================
> +
> +What: /config/usb-gadget/gadget/webusb
> +Date: Dec 2022
> +KernelVersion: 6.2
> +Description:
> + This group contains "WebUSB" extension handling attributes.
> +
> + ============= ===============================================
> + use flag turning "WebUSB" support on/off
> + bcdVersion bcd WebUSB specification version number
> + b_vendor_code one-byte value used for custom per-device
> + landing_page UTF-8 encoded URL of the device's landing page
> + ============= ===============================================
> diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c
> index 403563c06477..937695dc5366 100644
> --- a/drivers/usb/gadget/composite.c
> +++ b/drivers/usb/gadget/composite.c
> @@ -14,6 +14,7 @@
> #include <linux/device.h>
> #include <linux/utsname.h>
> #include <linux/bitfield.h>
> +#include <linux/uuid.h>
>
> #include <linux/usb/composite.h>
> #include <linux/usb/otg.h>
> @@ -713,14 +714,16 @@ static int bos_desc(struct usb_composite_dev *cdev)
> * A SuperSpeed device shall include the USB2.0 extension descriptor
> * and shall support LPM when operating in USB2.0 HS mode.
> */
> - usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
> - bos->bNumDeviceCaps++;
> - le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
> - usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
> - usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
> - usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
> - usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT |
> - USB_BESL_SUPPORT | besl);
> + if (cdev->gadget->lpm_capable) {
> + usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
> + bos->bNumDeviceCaps++;
> + le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
> + usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
> + usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
> + usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
> + usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT |
> + USB_BESL_SUPPORT | besl);
> + }
>
> /*
> * The Superspeed USB Capability descriptor shall be implemented by all
> @@ -821,6 +824,41 @@ static int bos_desc(struct usb_composite_dev *cdev)
> }
> }
>
> + /*
> + * Following the specifaction at:
> + * https://wicg.github.io/webusb/#webusb-platform-capability-descriptor
> + */
> + if (cdev->use_webusb) {
> + struct usb_webusb_cap_descriptor *webusb_cap;
> + /*
> + * little endian PlatformCapablityUUID for WebUSB:
> + * 3408b638-09a9-47a0-8bfd-a0768815b665
> + */
> +#define WEBUSB_UUID UUID_INIT(0x38b60834, 0xa909, 0xa047, \
> + 0x8b, 0xfd, 0xa0, 0x76, 0x88, 0x15, 0xb6, 0x65)
> + uuid_t webusb_uuid = WEBUSB_UUID;
> +
> + webusb_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
> + bos->bNumDeviceCaps++;
> +
> + le16_add_cpu(&bos->wTotalLength, USB_DT_WEBUSB_SIZE);
> + webusb_cap->bLength = USB_DT_WEBUSB_SIZE;
> + webusb_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
> + webusb_cap->bDevCapabilityType = USB_PLAT_DEV_CAP_TYPE;
> + webusb_cap->bReserved = 0;
> + export_uuid(webusb_cap->UUID, &webusb_uuid);
> + if (cdev->bcd_webusb_version != 0)
> + webusb_cap->bcdVersion = cpu_to_le16(cdev->bcd_webusb_version);
> + else
> + webusb_cap->bcdVersion = cpu_to_le16(0x0100);
> +
> + webusb_cap->bVendorCode = cdev->b_webusb_vendor_code;
> + if (strnlen(cdev->landing_page, sizeof(cdev->landing_page)) > 0)
> + webusb_cap->iLandingPage = 1;
> + else
> + webusb_cap->iLandingPage = 0;
> + }
> +
> return le16_to_cpu(bos->wTotalLength);
> }
>
> @@ -1744,7 +1782,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
> cdev->desc.bcdUSB = cpu_to_le16(0x0210);
> }
> } else {
> - if (gadget->lpm_capable)
> + if (gadget->lpm_capable || cdev->use_webusb)
> cdev->desc.bcdUSB = cpu_to_le16(0x0201);
> else
> cdev->desc.bcdUSB = cpu_to_le16(0x0200);
> @@ -1779,7 +1817,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
> break;
> case USB_DT_BOS:
> if (gadget_is_superspeed(gadget) ||
> - gadget->lpm_capable) {
> + gadget->lpm_capable || cdev->use_webusb) {
> value = bos_desc(cdev);
> value = min(w_length, (u16) value);
> }
> @@ -2013,6 +2051,50 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
> goto check_value;
> }
>
> + /*
> + * webusb descriptor handling, following:
> + * https://wicg.github.io/webusb/#device-requests
> + */
> + #define WEBUSB_GET_URL 2
> + if (cdev->use_webusb &&
> + (ctrl->bRequestType & USB_TYPE_VENDOR) &&
> + ctrl->wIndex == WEBUSB_GET_URL &&
> + ctrl->bRequest == cdev->b_webusb_vendor_code) {
> + /*
> + * specification of the url descriptor in WebUSB:
> + * https://wicg.github.io/webusb/#webusb-descriptors
> + */
> + u8 *buf;
> + unsigned int schema_http;
> + unsigned int schema_https;
> + unsigned int landing_page_length;
> +
> + buf = cdev->req->buf;
> + #define WEBUSB_URL 3
> + buf[1] = WEBUSB_URL;
> +
> + landing_page_length = strnlen(cdev->landing_page, 252);
> + schema_https = (strncmp("https://", cdev->landing_page, 8) == 0);
> + schema_http = (strncmp("http://", cdev->landing_page, 7) == 0);
> + if (schema_https) {
> + buf[2] = 0x01;
> + memcpy(buf+3, cdev->landing_page+8, landing_page_length-8);
> + buf[0] = landing_page_length - 8 + 3;
> + } else if (schema_http) {
> + buf[2] = 0x00;
> + memcpy(buf+3, cdev->landing_page+7, landing_page_length-7);
> + buf[0] = landing_page_length - 7 + 3;
> + } else {
> + buf[2] = 0xFF;
> + memcpy(buf+3, cdev->landing_page, landing_page_length);
> + buf[0] = landing_page_length + 3;
> + }
> +
> + value = buf[0];
> +
> + goto check_value;
> + }
> +
> VDBG(cdev,
> "non-core control req%02x.%02x v%04x i%04x l%d\n",
> ctrl->bRequestType, ctrl->bRequest,
> diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c
> index 96121d1c8df4..0c1a78d9b5a0 100644
> --- a/drivers/usb/gadget/configfs.c
> +++ b/drivers/usb/gadget/configfs.c
> @@ -39,6 +39,7 @@ struct gadget_info {
> struct config_group configs_group;
> struct config_group strings_group;
> struct config_group os_desc_group;
> + struct config_group webusb_group;
>
> struct mutex lock;
> struct usb_gadget_strings *gstrings[MAX_USB_STRING_LANGS + 1];
> @@ -50,6 +51,11 @@ struct gadget_info {
> bool use_os_desc;
> char b_vendor_code;
> char qw_sign[OS_STRING_QW_SIGN_LEN];
> + bool use_webusb;
> + u16 bcd_webusb_version;
> + u8 b_webusb_vendor_code;
> + char landing_page[255];
> +
> spinlock_t spinlock;
> bool unbind;
> };
> @@ -780,6 +786,125 @@ static void gadget_strings_attr_release(struct config_item *item)
> USB_CONFIG_STRING_RW_OPS(gadget_strings);
> USB_CONFIG_STRINGS_LANG(gadget_strings, gadget_info);
>
> +static inline struct gadget_info *webusb_item_to_gadget_info(
> + struct config_item *item)
> +{
> + return container_of(to_config_group(item),
> + struct gadget_info, webusb_group);
> +}
> +
> +static ssize_t webusb_use_show(struct config_item *item, char *page)
> +{
> + return sprintf(page, "%d\n",
> + webusb_item_to_gadget_info(item)->use_webusb);
> +}

sysfs_emit()?

> +
> +static ssize_t webusb_use_store(struct config_item *item, const char *page,
> + size_t len)
> +{
> + struct gadget_info *gi = webusb_item_to_gadget_info(item);
> + int ret;
> + bool use;
> +
> + mutex_lock(&gi->lock);
> + ret = kstrtobool(page, &use);
> + if (!ret) {
> + gi->use_webusb = use;
> + ret = len;
> + }
> + mutex_unlock(&gi->lock);
> +
> + return ret;
> +}
> +
> +static ssize_t webusb_bcdVersion_show(struct config_item *item, char *page)
> +{
> + return snprintf(page, PAGE_SIZE, "0x%04x\n",
> + webusb_item_to_gadget_info(item)->bcd_webusb_version);
> +}

sysfs_emit()?

> +
> +static ssize_t webusb_bcdVersion_store(struct config_item *item,
> + const char *page, size_t len)
> +{
> + struct gadget_info *gi = webusb_item_to_gadget_info(item);
> + u16 bcdVersion;

Why camelCase here?

> + int ret;

no mutex_lock(&gi->lock); here?
Other _store functions have.

> +
> + ret = kstrtou16(page, 0, &bcdVersion);
> + if (ret)
> + return ret;
> + ret = is_valid_bcd(bcdVersion);
> + if (ret)
> + return ret;
> +
> + gi->bcd_webusb_version = bcdVersion;
> + return len;
> +}
> +
> +static ssize_t webusb_bVendorCode_show(struct config_item *item, char *page)
> +{
> + return sprintf(page, "0x%02x\n",
> + webusb_item_to_gadget_info(item)->b_webusb_vendor_code);
> +}

sysfs_emit()?

> +
> +static ssize_t webusb_bVendorCode_store(struct config_item *item,
> + const char *page, size_t len)
> +{
> + struct gadget_info *gi = webusb_item_to_gadget_info(item);
> + int ret;
> + u8 b_vendor_code;
> +
> + mutex_lock(&gi->lock);
> + ret = kstrtou8(page, 0, &b_vendor_code);
> + if (!ret) {
> + gi->b_webusb_vendor_code = b_vendor_code;
> + ret = len;
> + }
> + mutex_unlock(&gi->lock);
> +
> + return ret;
> +}
> +
> +static ssize_t webusb_landingPage_show(struct config_item *item, char *page)
> +{
> + return snprintf(page, PAGE_SIZE, "%s\n", webusb_item_to_gadget_info(item)->landing_page);
> +}

sysfs_emit()?

> +
> +static ssize_t webusb_landingPage_store(struct config_item *item, const char *page,
> + size_t len)
> +{
> + struct gadget_info *gi = webusb_item_to_gadget_info(item);
> + int l;
> +
> + l = min(len, sizeof(gi->landing_page));
> + if (page[l - 1] == '\n')
> + --l;
> +
> + mutex_lock(&gi->lock);
> + memcpy(gi->landing_page, page, l);
> + mutex_unlock(&gi->lock);
> +
> + return len;
> +}
> +
> +CONFIGFS_ATTR(webusb_, use);
> +CONFIGFS_ATTR(webusb_, bVendorCode);
> +CONFIGFS_ATTR(webusb_, bcdVersion);
> +CONFIGFS_ATTR(webusb_, landingPage);
> +
> +static struct configfs_attribute *webusb_attrs[] = {
> + &webusb_attr_use,
> + &webusb_attr_bcdVersion,
> + &webusb_attr_bVendorCode,
> + &webusb_attr_landingPage,
> + NULL,
> +};
> +
> +static struct config_item_type webusb_type = {
> + .ct_attrs = webusb_attrs,
> + .ct_owner = THIS_MODULE,
> +};
> +
> static inline struct gadget_info *os_desc_item_to_gadget_info(
> struct config_item *item)
> {
> @@ -1341,6 +1466,16 @@ static int configfs_composite_bind(struct usb_gadget *gadget,
> gi->cdev.desc.iSerialNumber = s[USB_GADGET_SERIAL_IDX].id;
> }
>
> + if (gi->use_webusb) {
> + cdev->use_webusb = true;
> + cdev->bcd_webusb_version = gi->bcd_webusb_version;
> + cdev->b_webusb_vendor_code = gi->b_webusb_vendor_code;
> + memcpy(cdev->landing_page, gi->landing_page,
> + strnlen(gi->landing_page,
> + min(sizeof(cdev->landing_page),
> + sizeof(gi->landing_page))));
> + }
> +
> if (gi->use_os_desc) {
> cdev->use_os_string = true;
> cdev->b_vendor_code = gi->b_vendor_code;
> @@ -1605,6 +1740,10 @@ static struct config_group *gadgets_make(
> &os_desc_type);
> configfs_add_default_group(&gi->os_desc_group, &gi->group);
>
> + config_group_init_type_name(&gi->webusb_group, "webusb",
> + &webusb_type);
> + configfs_add_default_group(&gi->webusb_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 43ac3fa760db..44f90c37dda9 100644
> --- a/include/linux/usb/composite.h
> +++ b/include/linux/usb/composite.h
> @@ -474,6 +474,12 @@ struct usb_composite_dev {
> struct usb_configuration *os_desc_config;
> unsigned int use_os_string:1;
>
> + /* WebUSB */
> + u16 bcd_webusb_version;
> + u8 b_webusb_vendor_code;
> + char landing_page[255];
> + unsigned int use_webusb:1;
> +
> /* private: */
> /* internals */
> unsigned int suspended:1;
> diff --git a/include/uapi/linux/usb/ch9.h b/include/uapi/linux/usb/ch9.h
> index 31fcfa084e63..3a36550297bc 100644
> --- a/include/uapi/linux/usb/ch9.h
> +++ b/include/uapi/linux/usb/ch9.h
> @@ -947,6 +947,39 @@ struct usb_ss_container_id_descriptor {
>
> #define USB_DT_USB_SS_CONTN_ID_SIZE 20
>
> +/*
> + * Platform Device Capability descriptor: Defines platform specific device
> + * capabilities
> + */
> +#define USB_PLAT_DEV_CAP_TYPE 5
> +struct usb_plat_dev_cap_descriptor {
> + __u8 bLength;
> + __u8 bDescriptorType;
> + __u8 bDevCapabilityType;
> + __u8 bReserved;
> + __u8 UUID[16];
> +} __attribute__((packed));
> +
> +#define USB_DT_USB_PLAT_DEV_CAP_SIZE 20
> +
> +/*
> + * WebUSB Platform Capability descriptor: A device announces support for the
> + * WebUSB command set by including the following Platform Descriptor in its
> + * Binary Object Store
> + * https://wicg.github.io/webusb/#webusb-platform-capability-descriptor
> + */
> +struct usb_webusb_cap_descriptor {
> + __u8 bLength;
> + __u8 bDescriptorType;
> + __u8 bDevCapabilityType;
> + __u8 bReserved;
> + __u8 UUID[16];
> + __u16 bcdVersion;
> + __u8 bVendorCode;
> + __u8 iLandingPage;
> +} __attribute__((packed));
> +#define USB_DT_WEBUSB_SIZE (USB_DT_USB_PLAT_DEV_CAP_SIZE + 4)
> +
> /*
> * SuperSpeed Plus USB Capability descriptor: Defines the set of
> * SuperSpeed Plus USB specific device level capabilities

2022-12-30 19:59:59

by Jó Ágila Bitsch

[permalink] [raw]
Subject: Re: [PATCH] usb: gadget: add WebUSB support

On Fri, Dec 30, 2022 at 6:51 PM Christophe JAILLET
<[email protected]> wrote:
>
> Le 30/12/2022 à 17:58, Jó Ágila Bitsch a écrit :
> > There is a custom (non-USB IF) extension to the USB standard:
> >
> > https://wicg.github.io/webusb/
> >
> > This specification is published under the W3C Community Contributor
> > Agreement, which in particular allows to implement the specification
> > without any royalties.
> >
> > The specification allows USB gadgets to announce an URL to landing
> > page and describes a Javascript interface for websites to interact
> > with the USB gadget, if the user allows it. It is currently
> > supported by Chromium-based browsers, such as Chrome, Edge and
> > Opera on all major operating systems including Linux.
> >
> > This patch adds optional support for Linux-based USB gadgets
> > wishing to expose such a landing page.
> >
> > During device enumeration, a host recognizes that the announced
> > USB version is at least 2.01, which means, that there are BOS
> > descriptors available. The device than announces WebUSB support
> > using a platform device capability. This includes a vendor code
> > under which the landing page URL can be retrieved using a
> > vendor-specific request.
> >
> > Usage is modeled after os_desc descriptors:
> > echo 1 > webusb/use
> > echo "https://www.kernel.org" > webusb/landingPage
> >
> > lsusb will report the device with the following lines:
> > Platform Device Capability:
> > bLength 24
> > bDescriptorType 16
> > bDevCapabilityType 5
> > bReserved 0
> > PlatformCapabilityUUID {3408b638-09a9-47a0-8bfd-a0768815b665}
> > WebUSB:
> > bcdVersion 1.00
> > bVendorCode 0
> > iLandingPage 1 https://www.kernel.org
> >
> > Signed-off-by: Jó Ágila Bitsch <[email protected]>
>
> Hi,
>
> a few nits below.
>
> CJ

Thanks for your quick review. I answered inline and will post a new
version with the points addressed.

Best,


>
> > ---
> > Documentation/ABI/testing/configfs-usb-gadget | 13 ++
> > drivers/usb/gadget/composite.c | 102 +++++++++++--
> > drivers/usb/gadget/configfs.c | 139 ++++++++++++++++++
> > include/linux/usb/composite.h | 6 +
> > include/uapi/linux/usb/ch9.h | 33 +++++
> > 5 files changed, 283 insertions(+), 10 deletions(-)
> >
> > diff --git a/Documentation/ABI/testing/configfs-usb-gadget b/Documentation/ABI/testing/configfs-usb-gadget
> > index b7943aa7e997..8399e0f0ed1c 100644
> > --- a/Documentation/ABI/testing/configfs-usb-gadget
> > +++ b/Documentation/ABI/testing/configfs-usb-gadget
> > @@ -143,3 +143,16 @@ Description:
> > qw_sign an identifier to be reported as "OS String"
> > proper
> > ============= ===============================================
> > +
> > +What: /config/usb-gadget/gadget/webusb
> > +Date: Dec 2022
> > +KernelVersion: 6.2
> > +Description:
> > + This group contains "WebUSB" extension handling attributes.
> > +
> > + ============= ===============================================
> > + use flag turning "WebUSB" support on/off
> > + bcdVersion bcd WebUSB specification version number
> > + b_vendor_code one-byte value used for custom per-device
> > + landing_page UTF-8 encoded URL of the device's landing page
> > + ============= ===============================================
> > diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c
> > index 403563c06477..937695dc5366 100644
> > --- a/drivers/usb/gadget/composite.c
> > +++ b/drivers/usb/gadget/composite.c
> > @@ -14,6 +14,7 @@
> > #include <linux/device.h>
> > #include <linux/utsname.h>
> > #include <linux/bitfield.h>
> > +#include <linux/uuid.h>
> >
> > #include <linux/usb/composite.h>
> > #include <linux/usb/otg.h>
> > @@ -713,14 +714,16 @@ static int bos_desc(struct usb_composite_dev *cdev)
> > * A SuperSpeed device shall include the USB2.0 extension descriptor
> > * and shall support LPM when operating in USB2.0 HS mode.
> > */
> > - usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
> > - bos->bNumDeviceCaps++;
> > - le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
> > - usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
> > - usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
> > - usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
> > - usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT |
> > - USB_BESL_SUPPORT | besl);
> > + if (cdev->gadget->lpm_capable) {
> > + usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
> > + bos->bNumDeviceCaps++;
> > + le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
> > + usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
> > + usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
> > + usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
> > + usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT |
> > + USB_BESL_SUPPORT | besl);
> > + }
> >
> > /*
> > * The Superspeed USB Capability descriptor shall be implemented by all
> > @@ -821,6 +824,41 @@ static int bos_desc(struct usb_composite_dev *cdev)
> > }
> > }
> >
> > + /*
> > + * Following the specifaction at:
> > + * https://wicg.github.io/webusb/#webusb-platform-capability-descriptor
> > + */
> > + if (cdev->use_webusb) {
> > + struct usb_webusb_cap_descriptor *webusb_cap;
> > + /*
> > + * little endian PlatformCapablityUUID for WebUSB:
> > + * 3408b638-09a9-47a0-8bfd-a0768815b665
> > + */
> > +#define WEBUSB_UUID UUID_INIT(0x38b60834, 0xa909, 0xa047, \
> > + 0x8b, 0xfd, 0xa0, 0x76, 0x88, 0x15, 0xb6, 0x65)
> > + uuid_t webusb_uuid = WEBUSB_UUID;
> > +
> > + webusb_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
> > + bos->bNumDeviceCaps++;
> > +
> > + le16_add_cpu(&bos->wTotalLength, USB_DT_WEBUSB_SIZE);
> > + webusb_cap->bLength = USB_DT_WEBUSB_SIZE;
> > + webusb_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
> > + webusb_cap->bDevCapabilityType = USB_PLAT_DEV_CAP_TYPE;
> > + webusb_cap->bReserved = 0;
> > + export_uuid(webusb_cap->UUID, &webusb_uuid);
> > + if (cdev->bcd_webusb_version != 0)
> > + webusb_cap->bcdVersion = cpu_to_le16(cdev->bcd_webusb_version);
> > + else
> > + webusb_cap->bcdVersion = cpu_to_le16(0x0100);
> > +
> > + webusb_cap->bVendorCode = cdev->b_webusb_vendor_code;
> > + if (strnlen(cdev->landing_page, sizeof(cdev->landing_page)) > 0)
> > + webusb_cap->iLandingPage = 1;
> > + else
> > + webusb_cap->iLandingPage = 0;
> > + }
> > +
> > return le16_to_cpu(bos->wTotalLength);
> > }
> >
> > @@ -1744,7 +1782,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
> > cdev->desc.bcdUSB = cpu_to_le16(0x0210);
> > }
> > } else {
> > - if (gadget->lpm_capable)
> > + if (gadget->lpm_capable || cdev->use_webusb)
> > cdev->desc.bcdUSB = cpu_to_le16(0x0201);
> > else
> > cdev->desc.bcdUSB = cpu_to_le16(0x0200);
> > @@ -1779,7 +1817,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
> > break;
> > case USB_DT_BOS:
> > if (gadget_is_superspeed(gadget) ||
> > - gadget->lpm_capable) {
> > + gadget->lpm_capable || cdev->use_webusb) {
> > value = bos_desc(cdev);
> > value = min(w_length, (u16) value);
> > }
> > @@ -2013,6 +2051,50 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
> > goto check_value;
> > }
> >
> > + /*
> > + * webusb descriptor handling, following:
> > + * https://wicg.github.io/webusb/#device-requests
> > + */
> > + #define WEBUSB_GET_URL 2
> > + if (cdev->use_webusb &&
> > + (ctrl->bRequestType & USB_TYPE_VENDOR) &&
> > + ctrl->wIndex == WEBUSB_GET_URL &&
> > + ctrl->bRequest == cdev->b_webusb_vendor_code) {
> > + /*
> > + * specification of the url descriptor in WebUSB:
> > + * https://wicg.github.io/webusb/#webusb-descriptors
> > + */
> > + u8 *buf;
> > + unsigned int schema_http;
> > + unsigned int schema_https;
> > + unsigned int landing_page_length;
> > +
> > + buf = cdev->req->buf;
> > + #define WEBUSB_URL 3
> > + buf[1] = WEBUSB_URL;
> > +
> > + landing_page_length = strnlen(cdev->landing_page, 252);
> > + schema_https = (strncmp("https://", cdev->landing_page, 8) == 0);
> > + schema_http = (strncmp("http://", cdev->landing_page, 7) == 0);
> > + if (schema_https) {
> > + buf[2] = 0x01;
> > + memcpy(buf+3, cdev->landing_page+8, landing_page_length-8);
> > + buf[0] = landing_page_length - 8 + 3;
> > + } else if (schema_http) {
> > + buf[2] = 0x00;
> > + memcpy(buf+3, cdev->landing_page+7, landing_page_length-7);
> > + buf[0] = landing_page_length - 7 + 3;
> > + } else {
> > + buf[2] = 0xFF;
> > + memcpy(buf+3, cdev->landing_page, landing_page_length);
> > + buf[0] = landing_page_length + 3;
> > + }
> > +
> > + value = buf[0];
> > +
> > + goto check_value;
> > + }
> > +
> > VDBG(cdev,
> > "non-core control req%02x.%02x v%04x i%04x l%d\n",
> > ctrl->bRequestType, ctrl->bRequest,
> > diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c
> > index 96121d1c8df4..0c1a78d9b5a0 100644
> > --- a/drivers/usb/gadget/configfs.c
> > +++ b/drivers/usb/gadget/configfs.c
> > @@ -39,6 +39,7 @@ struct gadget_info {
> > struct config_group configs_group;
> > struct config_group strings_group;
> > struct config_group os_desc_group;
> > + struct config_group webusb_group;
> >
> > struct mutex lock;
> > struct usb_gadget_strings *gstrings[MAX_USB_STRING_LANGS + 1];
> > @@ -50,6 +51,11 @@ struct gadget_info {
> > bool use_os_desc;
> > char b_vendor_code;
> > char qw_sign[OS_STRING_QW_SIGN_LEN];
> > + bool use_webusb;
> > + u16 bcd_webusb_version;
> > + u8 b_webusb_vendor_code;
> > + char landing_page[255];
> > +
> > spinlock_t spinlock;
> > bool unbind;
> > };
> > @@ -780,6 +786,125 @@ static void gadget_strings_attr_release(struct config_item *item)
> > USB_CONFIG_STRING_RW_OPS(gadget_strings);
> > USB_CONFIG_STRINGS_LANG(gadget_strings, gadget_info);
> >
> > +static inline struct gadget_info *webusb_item_to_gadget_info(
> > + struct config_item *item)
> > +{
> > + return container_of(to_config_group(item),
> > + struct gadget_info, webusb_group);
> > +}
> > +
> > +static ssize_t webusb_use_show(struct config_item *item, char *page)
> > +{
> > + return sprintf(page, "%d\n",
> > + webusb_item_to_gadget_info(item)->use_webusb);
> > +}
>
> sysfs_emit()?
>

I will replace all sprintf calls with sysfs_emit calls in my next
revision. Thanks for the pointer.

> > +
> > +static ssize_t webusb_use_store(struct config_item *item, const char *page,
> > + size_t len)
> > +{
> > + struct gadget_info *gi = webusb_item_to_gadget_info(item);
> > + int ret;
> > + bool use;
> > +
> > + mutex_lock(&gi->lock);
> > + ret = kstrtobool(page, &use);
> > + if (!ret) {
> > + gi->use_webusb = use;
> > + ret = len;
> > + }
> > + mutex_unlock(&gi->lock);
> > +
> > + return ret;
> > +}
> > +
> > +static ssize_t webusb_bcdVersion_show(struct config_item *item, char *page)
> > +{
> > + return snprintf(page, PAGE_SIZE, "0x%04x\n",
> > + webusb_item_to_gadget_info(item)->bcd_webusb_version);
> > +}
>
> sysfs_emit()?
>
> > +
> > +static ssize_t webusb_bcdVersion_store(struct config_item *item,
> > + const char *page, size_t len)
> > +{
> > + struct gadget_info *gi = webusb_item_to_gadget_info(item);
> > + u16 bcdVersion;
>
> Why camelCase here?

Actually, I was unsure about that. I modelled my code on the os_desc
implementation, which uses kebab_casing. However, all the other usb
attributes (e.g. bcdUSB, bDeviceClass) use camelCase. So I thought it
would be more consistent for users to have camelCase. That's why I
stuck with camelCase for the attribute names (and the function names,
so that I can use the default macros).

>
> > + int ret;
>
> no mutex_lock(&gi->lock); here?
> Other _store functions have.

Thanks for pointing that one out. I missed that one.

> > +
> > + ret = kstrtou16(page, 0, &bcdVersion);
> > + if (ret)
> > + return ret;
> > + ret = is_valid_bcd(bcdVersion);
> > + if (ret)
> > + return ret;
> > +
> > + gi->bcd_webusb_version = bcdVersion;
> > + return len;
> > +}
> > +
> > +static ssize_t webusb_bVendorCode_show(struct config_item *item, char *page)
> > +{
> > + return sprintf(page, "0x%02x\n",
> > + webusb_item_to_gadget_info(item)->b_webusb_vendor_code);
> > +}
>
> sysfs_emit()?
>
> > +
> > +static ssize_t webusb_bVendorCode_store(struct config_item *item,
> > + const char *page, size_t len)
> > +{
> > + struct gadget_info *gi = webusb_item_to_gadget_info(item);
> > + int ret;
> > + u8 b_vendor_code;
> > +
> > + mutex_lock(&gi->lock);
> > + ret = kstrtou8(page, 0, &b_vendor_code);
> > + if (!ret) {
> > + gi->b_webusb_vendor_code = b_vendor_code;
> > + ret = len;
> > + }
> > + mutex_unlock(&gi->lock);
> > +
> > + return ret;
> > +}
> > +
> > +static ssize_t webusb_landingPage_show(struct config_item *item, char *page)
> > +{
> > + return snprintf(page, PAGE_SIZE, "%s\n", webusb_item_to_gadget_info(item)->landing_page);
> > +}
>
> sysfs_emit()?
>
> > +
> > +static ssize_t webusb_landingPage_store(struct config_item *item, const char *page,
> > + size_t len)
> > +{
> > + struct gadget_info *gi = webusb_item_to_gadget_info(item);
> > + int l;
> > +
> > + l = min(len, sizeof(gi->landing_page));
> > + if (page[l - 1] == '\n')
> > + --l;
> > +
> > + mutex_lock(&gi->lock);
> > + memcpy(gi->landing_page, page, l);
> > + mutex_unlock(&gi->lock);
> > +
> > + return len;
> > +}
> > +
> > +CONFIGFS_ATTR(webusb_, use);
> > +CONFIGFS_ATTR(webusb_, bVendorCode);
> > +CONFIGFS_ATTR(webusb_, bcdVersion);
> > +CONFIGFS_ATTR(webusb_, landingPage);
> > +
> > +static struct configfs_attribute *webusb_attrs[] = {
> > + &webusb_attr_use,
> > + &webusb_attr_bcdVersion,
> > + &webusb_attr_bVendorCode,
> > + &webusb_attr_landingPage,
> > + NULL,
> > +};
> > +
> > +static struct config_item_type webusb_type = {
> > + .ct_attrs = webusb_attrs,
> > + .ct_owner = THIS_MODULE,
> > +};
> > +
> > static inline struct gadget_info *os_desc_item_to_gadget_info(
> > struct config_item *item)
> > {
> > @@ -1341,6 +1466,16 @@ static int configfs_composite_bind(struct usb_gadget *gadget,
> > gi->cdev.desc.iSerialNumber = s[USB_GADGET_SERIAL_IDX].id;
> > }
> >
> > + if (gi->use_webusb) {
> > + cdev->use_webusb = true;
> > + cdev->bcd_webusb_version = gi->bcd_webusb_version;
> > + cdev->b_webusb_vendor_code = gi->b_webusb_vendor_code;
> > + memcpy(cdev->landing_page, gi->landing_page,
> > + strnlen(gi->landing_page,
> > + min(sizeof(cdev->landing_page),
> > + sizeof(gi->landing_page))));
> > + }
> > +
> > if (gi->use_os_desc) {
> > cdev->use_os_string = true;
> > cdev->b_vendor_code = gi->b_vendor_code;
> > @@ -1605,6 +1740,10 @@ static struct config_group *gadgets_make(
> > &os_desc_type);
> > configfs_add_default_group(&gi->os_desc_group, &gi->group);
> >
> > + config_group_init_type_name(&gi->webusb_group, "webusb",
> > + &webusb_type);
> > + configfs_add_default_group(&gi->webusb_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 43ac3fa760db..44f90c37dda9 100644
> > --- a/include/linux/usb/composite.h
> > +++ b/include/linux/usb/composite.h
> > @@ -474,6 +474,12 @@ struct usb_composite_dev {
> > struct usb_configuration *os_desc_config;
> > unsigned int use_os_string:1;
> >
> > + /* WebUSB */
> > + u16 bcd_webusb_version;
> > + u8 b_webusb_vendor_code;
> > + char landing_page[255];
> > + unsigned int use_webusb:1;
> > +
> > /* private: */
> > /* internals */
> > unsigned int suspended:1;
> > diff --git a/include/uapi/linux/usb/ch9.h b/include/uapi/linux/usb/ch9.h
> > index 31fcfa084e63..3a36550297bc 100644
> > --- a/include/uapi/linux/usb/ch9.h
> > +++ b/include/uapi/linux/usb/ch9.h
> > @@ -947,6 +947,39 @@ struct usb_ss_container_id_descriptor {
> >
> > #define USB_DT_USB_SS_CONTN_ID_SIZE 20
> >
> > +/*
> > + * Platform Device Capability descriptor: Defines platform specific device
> > + * capabilities
> > + */
> > +#define USB_PLAT_DEV_CAP_TYPE 5
> > +struct usb_plat_dev_cap_descriptor {
> > + __u8 bLength;
> > + __u8 bDescriptorType;
> > + __u8 bDevCapabilityType;
> > + __u8 bReserved;
> > + __u8 UUID[16];
> > +} __attribute__((packed));
> > +
> > +#define USB_DT_USB_PLAT_DEV_CAP_SIZE 20
> > +
> > +/*
> > + * WebUSB Platform Capability descriptor: A device announces support for the
> > + * WebUSB command set by including the following Platform Descriptor in its
> > + * Binary Object Store
> > + * https://wicg.github.io/webusb/#webusb-platform-capability-descriptor
> > + */
> > +struct usb_webusb_cap_descriptor {
> > + __u8 bLength;
> > + __u8 bDescriptorType;
> > + __u8 bDevCapabilityType;
> > + __u8 bReserved;
> > + __u8 UUID[16];
> > + __u16 bcdVersion;
> > + __u8 bVendorCode;
> > + __u8 iLandingPage;
> > +} __attribute__((packed));
> > +#define USB_DT_WEBUSB_SIZE (USB_DT_USB_PLAT_DEV_CAP_SIZE + 4)
> > +
> > /*
> > * SuperSpeed Plus USB Capability descriptor: Defines the set of
> > * SuperSpeed Plus USB specific device level capabilities
>

2022-12-30 20:24:24

by Jó Ágila Bitsch

[permalink] [raw]
Subject: [PATCH v1] usb: gadget: add WebUSB support

There is a custom (non-USB IF) extension to the USB standard:

https://wicg.github.io/webusb/

This specification is published under the W3C Community Contributor
Agreement, which in particular allows to implement the specification
without any royalties.

The specification allows USB gadgets to announce an URL to landing
page and describes a Javascript interface for websites to interact
with the USB gadget, if the user allows it. It is currently
supported by Chromium-based browsers, such as Chrome, Edge and
Opera on all major operating systems including Linux.

This patch adds optional support for Linux-based USB gadgets
wishing to expose such a landing page.

During device enumeration, a host recognizes that the announced
USB version is at least 2.01, which means, that there are BOS
descriptors available. The device than announces WebUSB support
using a platform device capability. This includes a vendor code
under which the landing page URL can be retrieved using a
vendor-specific request.

Usage is modeled after os_desc descriptors:
echo 1 > webusb/use
echo "https://www.kernel.org" > webusb/landingPage

lsusb will report the device with the following lines:
Platform Device Capability:
bLength 24
bDescriptorType 16
bDevCapabilityType 5
bReserved 0
PlatformCapabilityUUID {3408b638-09a9-47a0-8bfd-a0768815b665}
WebUSB:
bcdVersion 1.00
bVendorCode 0
iLandingPage 1 https://www.kernel.org

Signed-off-by: J? ?gila Bitsch <[email protected]>
---
Documentation/ABI/testing/configfs-usb-gadget | 13 ++
drivers/usb/gadget/composite.c | 102 ++++++++++--
drivers/usb/gadget/configfs.c | 145 ++++++++++++++++++
include/linux/usb/composite.h | 6 +
include/uapi/linux/usb/ch9.h | 33 ++++
5 files changed, 289 insertions(+), 10 deletions(-)

diff --git a/Documentation/ABI/testing/configfs-usb-gadget b/Documentation/ABI/testing/configfs-usb-gadget
index b7943aa7e997..8399e0f0ed1c 100644
--- a/Documentation/ABI/testing/configfs-usb-gadget
+++ b/Documentation/ABI/testing/configfs-usb-gadget
@@ -143,3 +143,16 @@ Description:
qw_sign an identifier to be reported as "OS String"
proper
============= ===============================================
+
+What: /config/usb-gadget/gadget/webusb
+Date: Dec 2022
+KernelVersion: 6.2
+Description:
+ This group contains "WebUSB" extension handling attributes.
+
+ ============= ===============================================
+ use flag turning "WebUSB" support on/off
+ bcdVersion bcd WebUSB specification version number
+ b_vendor_code one-byte value used for custom per-device
+ landing_page UTF-8 encoded URL of the device's landing page
+ ============= ===============================================
diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c
index 403563c06477..937695dc5366 100644
--- a/drivers/usb/gadget/composite.c
+++ b/drivers/usb/gadget/composite.c
@@ -14,6 +14,7 @@
#include <linux/device.h>
#include <linux/utsname.h>
#include <linux/bitfield.h>
+#include <linux/uuid.h>

#include <linux/usb/composite.h>
#include <linux/usb/otg.h>
@@ -713,14 +714,16 @@ static int bos_desc(struct usb_composite_dev *cdev)
* A SuperSpeed device shall include the USB2.0 extension descriptor
* and shall support LPM when operating in USB2.0 HS mode.
*/
- usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
- bos->bNumDeviceCaps++;
- le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
- usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
- usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
- usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
- usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT |
- USB_BESL_SUPPORT | besl);
+ if (cdev->gadget->lpm_capable) {
+ usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
+ bos->bNumDeviceCaps++;
+ le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
+ usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
+ usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
+ usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
+ usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT |
+ USB_BESL_SUPPORT | besl);
+ }

/*
* The Superspeed USB Capability descriptor shall be implemented by all
@@ -821,6 +824,41 @@ static int bos_desc(struct usb_composite_dev *cdev)
}
}

+ /*
+ * Following the specifaction at:
+ * https://wicg.github.io/webusb/#webusb-platform-capability-descriptor
+ */
+ if (cdev->use_webusb) {
+ struct usb_webusb_cap_descriptor *webusb_cap;
+ /*
+ * little endian PlatformCapablityUUID for WebUSB:
+ * 3408b638-09a9-47a0-8bfd-a0768815b665
+ */
+#define WEBUSB_UUID UUID_INIT(0x38b60834, 0xa909, 0xa047, \
+ 0x8b, 0xfd, 0xa0, 0x76, 0x88, 0x15, 0xb6, 0x65)
+ uuid_t webusb_uuid = WEBUSB_UUID;
+
+ webusb_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
+ bos->bNumDeviceCaps++;
+
+ le16_add_cpu(&bos->wTotalLength, USB_DT_WEBUSB_SIZE);
+ webusb_cap->bLength = USB_DT_WEBUSB_SIZE;
+ webusb_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
+ webusb_cap->bDevCapabilityType = USB_PLAT_DEV_CAP_TYPE;
+ webusb_cap->bReserved = 0;
+ export_uuid(webusb_cap->UUID, &webusb_uuid);
+ if (cdev->bcd_webusb_version != 0)
+ webusb_cap->bcdVersion = cpu_to_le16(cdev->bcd_webusb_version);
+ else
+ webusb_cap->bcdVersion = cpu_to_le16(0x0100);
+
+ webusb_cap->bVendorCode = cdev->b_webusb_vendor_code;
+ if (strnlen(cdev->landing_page, sizeof(cdev->landing_page)) > 0)
+ webusb_cap->iLandingPage = 1;
+ else
+ webusb_cap->iLandingPage = 0;
+ }
+
return le16_to_cpu(bos->wTotalLength);
}

@@ -1744,7 +1782,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
cdev->desc.bcdUSB = cpu_to_le16(0x0210);
}
} else {
- if (gadget->lpm_capable)
+ if (gadget->lpm_capable || cdev->use_webusb)
cdev->desc.bcdUSB = cpu_to_le16(0x0201);
else
cdev->desc.bcdUSB = cpu_to_le16(0x0200);
@@ -1779,7 +1817,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
break;
case USB_DT_BOS:
if (gadget_is_superspeed(gadget) ||
- gadget->lpm_capable) {
+ gadget->lpm_capable || cdev->use_webusb) {
value = bos_desc(cdev);
value = min(w_length, (u16) value);
}
@@ -2013,6 +2051,50 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
goto check_value;
}

+ /*
+ * webusb descriptor handling, following:
+ * https://wicg.github.io/webusb/#device-requests
+ */
+ #define WEBUSB_GET_URL 2
+ if (cdev->use_webusb &&
+ (ctrl->bRequestType & USB_TYPE_VENDOR) &&
+ ctrl->wIndex == WEBUSB_GET_URL &&
+ ctrl->bRequest == cdev->b_webusb_vendor_code) {
+ /*
+ * specification of the url descriptor in WebUSB:
+ * https://wicg.github.io/webusb/#webusb-descriptors
+ */
+ u8 *buf;
+ unsigned int schema_http;
+ unsigned int schema_https;
+ unsigned int landing_page_length;
+
+ buf = cdev->req->buf;
+ #define WEBUSB_URL 3
+ buf[1] = WEBUSB_URL;
+
+ landing_page_length = strnlen(cdev->landing_page, 252);
+ schema_https = (strncmp("https://", cdev->landing_page, 8) == 0);
+ schema_http = (strncmp("http://", cdev->landing_page, 7) == 0);
+ if (schema_https) {
+ buf[2] = 0x01;
+ memcpy(buf+3, cdev->landing_page+8, landing_page_length-8);
+ buf[0] = landing_page_length - 8 + 3;
+ } else if (schema_http) {
+ buf[2] = 0x00;
+ memcpy(buf+3, cdev->landing_page+7, landing_page_length-7);
+ buf[0] = landing_page_length - 7 + 3;
+ } else {
+ buf[2] = 0xFF;
+ memcpy(buf+3, cdev->landing_page, landing_page_length);
+ buf[0] = landing_page_length + 3;
+ }
+
+ value = buf[0];
+
+ goto check_value;
+ }
+
VDBG(cdev,
"non-core control req%02x.%02x v%04x i%04x l%d\n",
ctrl->bRequestType, ctrl->bRequest,
diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c
index 96121d1c8df4..da49b36f7033 100644
--- a/drivers/usb/gadget/configfs.c
+++ b/drivers/usb/gadget/configfs.c
@@ -39,6 +39,7 @@ struct gadget_info {
struct config_group configs_group;
struct config_group strings_group;
struct config_group os_desc_group;
+ struct config_group webusb_group;

struct mutex lock;
struct usb_gadget_strings *gstrings[MAX_USB_STRING_LANGS + 1];
@@ -50,6 +51,11 @@ struct gadget_info {
bool use_os_desc;
char b_vendor_code;
char qw_sign[OS_STRING_QW_SIGN_LEN];
+ bool use_webusb;
+ u16 bcd_webusb_version;
+ u8 b_webusb_vendor_code;
+ char landing_page[255];
+
spinlock_t spinlock;
bool unbind;
};
@@ -780,6 +786,131 @@ static void gadget_strings_attr_release(struct config_item *item)
USB_CONFIG_STRING_RW_OPS(gadget_strings);
USB_CONFIG_STRINGS_LANG(gadget_strings, gadget_info);

+static inline struct gadget_info *webusb_item_to_gadget_info(
+ struct config_item *item)
+{
+ return container_of(to_config_group(item),
+ struct gadget_info, webusb_group);
+}
+
+static ssize_t webusb_use_show(struct config_item *item, char *page)
+{
+ return sysfs_emit(page, "%d\n",
+ webusb_item_to_gadget_info(item)->use_webusb);
+}
+
+static ssize_t webusb_use_store(struct config_item *item, const char *page,
+ size_t len)
+{
+ struct gadget_info *gi = webusb_item_to_gadget_info(item);
+ int ret;
+ bool use;
+
+ mutex_lock(&gi->lock);
+ ret = kstrtobool(page, &use);
+ if (!ret) {
+ gi->use_webusb = use;
+ ret = len;
+ }
+ mutex_unlock(&gi->lock);
+
+ return ret;
+}
+
+static ssize_t webusb_bcdVersion_show(struct config_item *item, char *page)
+{
+ return sysfs_emit(page, "0x%04x\n",
+ webusb_item_to_gadget_info(item)->bcd_webusb_version);
+}
+
+static ssize_t webusb_bcdVersion_store(struct config_item *item,
+ const char *page, size_t len)
+{
+ struct gadget_info *gi = webusb_item_to_gadget_info(item);
+ u16 bcdVersion;
+ int ret;
+
+ mutex_lock(&gi->lock);
+ ret = kstrtou16(page, 0, &bcdVersion);
+ if (ret)
+ goto out;
+ ret = is_valid_bcd(bcdVersion);
+ if (ret)
+ goto out;
+
+ gi->bcd_webusb_version = bcdVersion;
+ ret = len;
+
+out:
+ mutex_unlock(&gi->lock);
+
+ return ret;
+}
+
+static ssize_t webusb_bVendorCode_show(struct config_item *item, char *page)
+{
+ return sysfs_emit(page, "0x%02x\n",
+ webusb_item_to_gadget_info(item)->b_webusb_vendor_code);
+}
+
+static ssize_t webusb_bVendorCode_store(struct config_item *item,
+ const char *page, size_t len)
+{
+ struct gadget_info *gi = webusb_item_to_gadget_info(item);
+ int ret;
+ u8 b_vendor_code;
+
+ mutex_lock(&gi->lock);
+ ret = kstrtou8(page, 0, &b_vendor_code);
+ if (!ret) {
+ gi->b_webusb_vendor_code = b_vendor_code;
+ ret = len;
+ }
+ mutex_unlock(&gi->lock);
+
+ return ret;
+}
+
+static ssize_t webusb_landingPage_show(struct config_item *item, char *page)
+{
+ return sysfs_emit(page, "%s\n", webusb_item_to_gadget_info(item)->landing_page);
+}
+
+static ssize_t webusb_landingPage_store(struct config_item *item, const char *page,
+ size_t len)
+{
+ struct gadget_info *gi = webusb_item_to_gadget_info(item);
+ int l;
+
+ l = min(len, sizeof(gi->landing_page));
+ if (page[l - 1] == '\n')
+ --l;
+
+ mutex_lock(&gi->lock);
+ memcpy(gi->landing_page, page, l);
+ mutex_unlock(&gi->lock);
+
+ return len;
+}
+
+CONFIGFS_ATTR(webusb_, use);
+CONFIGFS_ATTR(webusb_, bVendorCode);
+CONFIGFS_ATTR(webusb_, bcdVersion);
+CONFIGFS_ATTR(webusb_, landingPage);
+
+static struct configfs_attribute *webusb_attrs[] = {
+ &webusb_attr_use,
+ &webusb_attr_bcdVersion,
+ &webusb_attr_bVendorCode,
+ &webusb_attr_landingPage,
+ NULL,
+};
+
+static struct config_item_type webusb_type = {
+ .ct_attrs = webusb_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
static inline struct gadget_info *os_desc_item_to_gadget_info(
struct config_item *item)
{
@@ -1341,6 +1472,16 @@ static int configfs_composite_bind(struct usb_gadget *gadget,
gi->cdev.desc.iSerialNumber = s[USB_GADGET_SERIAL_IDX].id;
}

+ if (gi->use_webusb) {
+ cdev->use_webusb = true;
+ cdev->bcd_webusb_version = gi->bcd_webusb_version;
+ cdev->b_webusb_vendor_code = gi->b_webusb_vendor_code;
+ memcpy(cdev->landing_page, gi->landing_page,
+ strnlen(gi->landing_page,
+ min(sizeof(cdev->landing_page),
+ sizeof(gi->landing_page))));
+ }
+
if (gi->use_os_desc) {
cdev->use_os_string = true;
cdev->b_vendor_code = gi->b_vendor_code;
@@ -1605,6 +1746,10 @@ static struct config_group *gadgets_make(
&os_desc_type);
configfs_add_default_group(&gi->os_desc_group, &gi->group);

+ config_group_init_type_name(&gi->webusb_group, "webusb",
+ &webusb_type);
+ configfs_add_default_group(&gi->webusb_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 43ac3fa760db..44f90c37dda9 100644
--- a/include/linux/usb/composite.h
+++ b/include/linux/usb/composite.h
@@ -474,6 +474,12 @@ struct usb_composite_dev {
struct usb_configuration *os_desc_config;
unsigned int use_os_string:1;

+ /* WebUSB */
+ u16 bcd_webusb_version;
+ u8 b_webusb_vendor_code;
+ char landing_page[255];
+ unsigned int use_webusb:1;
+
/* private: */
/* internals */
unsigned int suspended:1;
diff --git a/include/uapi/linux/usb/ch9.h b/include/uapi/linux/usb/ch9.h
index 31fcfa084e63..3a36550297bc 100644
--- a/include/uapi/linux/usb/ch9.h
+++ b/include/uapi/linux/usb/ch9.h
@@ -947,6 +947,39 @@ struct usb_ss_container_id_descriptor {

#define USB_DT_USB_SS_CONTN_ID_SIZE 20

+/*
+ * Platform Device Capability descriptor: Defines platform specific device
+ * capabilities
+ */
+#define USB_PLAT_DEV_CAP_TYPE 5
+struct usb_plat_dev_cap_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDevCapabilityType;
+ __u8 bReserved;
+ __u8 UUID[16];
+} __attribute__((packed));
+
+#define USB_DT_USB_PLAT_DEV_CAP_SIZE 20
+
+/*
+ * WebUSB Platform Capability descriptor: A device announces support for the
+ * WebUSB command set by including the following Platform Descriptor in its
+ * Binary Object Store
+ * https://wicg.github.io/webusb/#webusb-platform-capability-descriptor
+ */
+struct usb_webusb_cap_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDevCapabilityType;
+ __u8 bReserved;
+ __u8 UUID[16];
+ __u16 bcdVersion;
+ __u8 bVendorCode;
+ __u8 iLandingPage;
+} __attribute__((packed));
+#define USB_DT_WEBUSB_SIZE (USB_DT_USB_PLAT_DEV_CAP_SIZE + 4)
+
/*
* SuperSpeed Plus USB Capability descriptor: Defines the set of
* SuperSpeed Plus USB specific device level capabilities
--
2.37.2

2022-12-31 20:27:10

by Alan Stern

[permalink] [raw]
Subject: Re: [PATCH v1] usb: gadget: add WebUSB support

On Fri, Dec 30, 2022 at 09:11:43PM +0100, J? ?gila Bitsch wrote:
> There is a custom (non-USB IF) extension to the USB standard:
>
> https://wicg.github.io/webusb/
>
> This specification is published under the W3C Community Contributor
> Agreement, which in particular allows to implement the specification
> without any royalties.
>
> The specification allows USB gadgets to announce an URL to landing
> page and describes a Javascript interface for websites to interact
> with the USB gadget, if the user allows it. It is currently
> supported by Chromium-based browsers, such as Chrome, Edge and
> Opera on all major operating systems including Linux.
>
> This patch adds optional support for Linux-based USB gadgets
> wishing to expose such a landing page.
>
> During device enumeration, a host recognizes that the announced
> USB version is at least 2.01, which means, that there are BOS

Where is this 2.01 value specified? I don't remember seeing it in the
usual USBIF documents.

> descriptors available. The device than announces WebUSB support
> using a platform device capability. This includes a vendor code
> under which the landing page URL can be retrieved using a
> vendor-specific request.
>
> Usage is modeled after os_desc descriptors:
> echo 1 > webusb/use
> echo "https://www.kernel.org" > webusb/landingPage
>
> lsusb will report the device with the following lines:
> Platform Device Capability:
> bLength 24
> bDescriptorType 16
> bDevCapabilityType 5
> bReserved 0
> PlatformCapabilityUUID {3408b638-09a9-47a0-8bfd-a0768815b665}
> WebUSB:
> bcdVersion 1.00
> bVendorCode 0
> iLandingPage 1 https://www.kernel.org
>
> Signed-off-by: J? ?gila Bitsch <[email protected]>
> ---
> Documentation/ABI/testing/configfs-usb-gadget | 13 ++
> drivers/usb/gadget/composite.c | 102 ++++++++++--
> drivers/usb/gadget/configfs.c | 145 ++++++++++++++++++
> include/linux/usb/composite.h | 6 +
> include/uapi/linux/usb/ch9.h | 33 ++++
> 5 files changed, 289 insertions(+), 10 deletions(-)
>
> diff --git a/Documentation/ABI/testing/configfs-usb-gadget b/Documentation/ABI/testing/configfs-usb-gadget
> index b7943aa7e997..8399e0f0ed1c 100644
> --- a/Documentation/ABI/testing/configfs-usb-gadget
> +++ b/Documentation/ABI/testing/configfs-usb-gadget
> @@ -143,3 +143,16 @@ Description:
> qw_sign an identifier to be reported as "OS String"
> proper
> ============= ===============================================
> +
> +What: /config/usb-gadget/gadget/webusb
> +Date: Dec 2022
> +KernelVersion: 6.2
> +Description:
> + This group contains "WebUSB" extension handling attributes.
> +
> + ============= ===============================================
> + use flag turning "WebUSB" support on/off
> + bcdVersion bcd WebUSB specification version number
> + b_vendor_code one-byte value used for custom per-device
> + landing_page UTF-8 encoded URL of the device's landing page
> + ============= ===============================================
> diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c
> index 403563c06477..937695dc5366 100644
> --- a/drivers/usb/gadget/composite.c
> +++ b/drivers/usb/gadget/composite.c
> @@ -14,6 +14,7 @@
> #include <linux/device.h>
> #include <linux/utsname.h>
> #include <linux/bitfield.h>
> +#include <linux/uuid.h>
>
> #include <linux/usb/composite.h>
> #include <linux/usb/otg.h>
> @@ -713,14 +714,16 @@ static int bos_desc(struct usb_composite_dev *cdev)
> * A SuperSpeed device shall include the USB2.0 extension descriptor
> * and shall support LPM when operating in USB2.0 HS mode.
> */
> - usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
> - bos->bNumDeviceCaps++;
> - le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
> - usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
> - usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
> - usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
> - usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT |
> - USB_BESL_SUPPORT | besl);
> + if (cdev->gadget->lpm_capable) {

This change doesn't seem to be related to the purpose of this patch.

> + usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
> + bos->bNumDeviceCaps++;
> + le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
> + usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
> + usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
> + usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
> + usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT |
> + USB_BESL_SUPPORT | besl);
> + }
>
> /*
> * The Superspeed USB Capability descriptor shall be implemented by all
> @@ -821,6 +824,41 @@ static int bos_desc(struct usb_composite_dev *cdev)
> }
> }
>
> + /*
> + * Following the specifaction at:
> + * https://wicg.github.io/webusb/#webusb-platform-capability-descriptor
> + */
> + if (cdev->use_webusb) {
> + struct usb_webusb_cap_descriptor *webusb_cap;
> + /*
> + * little endian PlatformCapablityUUID for WebUSB:
> + * 3408b638-09a9-47a0-8bfd-a0768815b665
> + */
> +#define WEBUSB_UUID UUID_INIT(0x38b60834, 0xa909, 0xa047, \
> + 0x8b, 0xfd, 0xa0, 0x76, 0x88, 0x15, 0xb6, 0x65)
> + uuid_t webusb_uuid = WEBUSB_UUID;

This #define seems to be pointless. It is used nowhere but in the
immediately following line. You might as well eliminate it.

Or you might define this UUID at the same place in the header file
where you define struct usb_webusb_cap_descriptor.

> +
> + webusb_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
> + bos->bNumDeviceCaps++;
> +
> + le16_add_cpu(&bos->wTotalLength, USB_DT_WEBUSB_SIZE);
> + webusb_cap->bLength = USB_DT_WEBUSB_SIZE;
> + webusb_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
> + webusb_cap->bDevCapabilityType = USB_PLAT_DEV_CAP_TYPE;
> + webusb_cap->bReserved = 0;
> + export_uuid(webusb_cap->UUID, &webusb_uuid);
> + if (cdev->bcd_webusb_version != 0)
> + webusb_cap->bcdVersion = cpu_to_le16(cdev->bcd_webusb_version);
> + else
> + webusb_cap->bcdVersion = cpu_to_le16(0x0100);
> +
> + webusb_cap->bVendorCode = cdev->b_webusb_vendor_code;
> + if (strnlen(cdev->landing_page, sizeof(cdev->landing_page)) > 0)
> + webusb_cap->iLandingPage = 1;
> + else
> + webusb_cap->iLandingPage = 0;
> + }
> +
> return le16_to_cpu(bos->wTotalLength);
> }
>
> @@ -1744,7 +1782,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
> cdev->desc.bcdUSB = cpu_to_le16(0x0210);
> }
> } else {
> - if (gadget->lpm_capable)
> + if (gadget->lpm_capable || cdev->use_webusb)
> cdev->desc.bcdUSB = cpu_to_le16(0x0201);
> else
> cdev->desc.bcdUSB = cpu_to_le16(0x0200);
> @@ -1779,7 +1817,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
> break;
> case USB_DT_BOS:
> if (gadget_is_superspeed(gadget) ||
> - gadget->lpm_capable) {
> + gadget->lpm_capable || cdev->use_webusb) {
> value = bos_desc(cdev);
> value = min(w_length, (u16) value);
> }
> @@ -2013,6 +2051,50 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
> goto check_value;
> }
>
> + /*
> + * webusb descriptor handling, following:
> + * https://wicg.github.io/webusb/#device-requests
> + */
> + #define WEBUSB_GET_URL 2

A similar comment applies to this #define. In this case it might make
sense to put all the WebUSB-related #defines in an appropriate header
file.

> + if (cdev->use_webusb &&
> + (ctrl->bRequestType & USB_TYPE_VENDOR) &&
> + ctrl->wIndex == WEBUSB_GET_URL &&
> + ctrl->bRequest == cdev->b_webusb_vendor_code) {

Bad indentation. Visually this makes it look like the last two tests
are part of the conditional block, because they share its indentation
level.

> + /*
> + * specification of the url descriptor in WebUSB:
> + * https://wicg.github.io/webusb/#webusb-descriptors
> + */
> + u8 *buf;
> + unsigned int schema_http;

*buf and schema_http should be indented by the same amount.

> + unsigned int schema_https;
> + unsigned int landing_page_length;
> +
> + buf = cdev->req->buf;
> + #define WEBUSB_URL 3
> + buf[1] = WEBUSB_URL;
> +
> + landing_page_length = strnlen(cdev->landing_page, 252);
> + schema_https = (strncmp("https://", cdev->landing_page, 8) == 0);
> + schema_http = (strncmp("http://", cdev->landing_page, 7) == 0);

Do you really need these temporary variables? Why not put the tests
directly into the "if" conditions?

Also, should the comparisons be case-insensitive?

> + if (schema_https) {
> + buf[2] = 0x01;
> + memcpy(buf+3, cdev->landing_page+8, landing_page_length-8);
> + buf[0] = landing_page_length - 8 + 3;
> + } else if (schema_http) {
> + buf[2] = 0x00;
> + memcpy(buf+3, cdev->landing_page+7, landing_page_length-7);
> + buf[0] = landing_page_length - 7 + 3;
> + } else {
> + buf[2] = 0xFF;
> + memcpy(buf+3, cdev->landing_page, landing_page_length);
> + buf[0] = landing_page_length + 3;
> + }
> +
> + value = buf[0];
> +
> + goto check_value;
> + }
> +
> VDBG(cdev,
> "non-core control req%02x.%02x v%04x i%04x l%d\n",
> ctrl->bRequestType, ctrl->bRequest,
> diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c
> index 96121d1c8df4..da49b36f7033 100644
> --- a/drivers/usb/gadget/configfs.c
> +++ b/drivers/usb/gadget/configfs.c
> @@ -39,6 +39,7 @@ struct gadget_info {
> struct config_group configs_group;
> struct config_group strings_group;
> struct config_group os_desc_group;
> + struct config_group webusb_group;
>
> struct mutex lock;
> struct usb_gadget_strings *gstrings[MAX_USB_STRING_LANGS + 1];
> @@ -50,6 +51,11 @@ struct gadget_info {
> bool use_os_desc;
> char b_vendor_code;
> char qw_sign[OS_STRING_QW_SIGN_LEN];
> + bool use_webusb;
> + u16 bcd_webusb_version;
> + u8 b_webusb_vendor_code;

Again, improper indentation.

> + char landing_page[255];
> +
> spinlock_t spinlock;
> bool unbind;
> };

...

> diff --git a/include/linux/usb/composite.h b/include/linux/usb/composite.h
> index 43ac3fa760db..44f90c37dda9 100644
> --- a/include/linux/usb/composite.h
> +++ b/include/linux/usb/composite.h
> @@ -474,6 +474,12 @@ struct usb_composite_dev {
> struct usb_configuration *os_desc_config;
> unsigned int use_os_string:1;
>
> + /* WebUSB */
> + u16 bcd_webusb_version;
> + u8 b_webusb_vendor_code;
> + char landing_page[255];

Improper indentation.

> + unsigned int use_webusb:1;
> +
> /* private: */
> /* internals */
> unsigned int suspended:1;

Alan Stern

2022-12-31 21:35:18

by Jó Ágila Bitsch

[permalink] [raw]
Subject: Re: [PATCH v1] usb: gadget: add WebUSB support

On Sat, Dec 31, 2022 at 8:47 PM Alan Stern <[email protected]> wrote:
>
> On Fri, Dec 30, 2022 at 09:11:43PM +0100, Jó Ágila Bitsch wrote:
> > There is a custom (non-USB IF) extension to the USB standard:
> >
> > https://wicg.github.io/webusb/
> >
> > This specification is published under the W3C Community Contributor
> > Agreement, which in particular allows to implement the specification
> > without any royalties.
> >
> > The specification allows USB gadgets to announce an URL to landing
> > page and describes a Javascript interface for websites to interact
> > with the USB gadget, if the user allows it. It is currently
> > supported by Chromium-based browsers, such as Chrome, Edge and
> > Opera on all major operating systems including Linux.
> >
> > This patch adds optional support for Linux-based USB gadgets
> > wishing to expose such a landing page.
> >
> > During device enumeration, a host recognizes that the announced
> > USB version is at least 2.01, which means, that there are BOS
>
> Where is this 2.01 value specified? I don't remember seeing it in the
> usual USBIF documents.

This is actually from the backport of BOS descriptors to USB 2

Citing Randy Aull from
https://community.osr.com/discussion/comment/249742/#Comment_249742
> There is no specification called USB 2.1, however there is such a thing as a USB 2.1 device.
> The USB 2.0 ECN for LPM introduced a new descriptor request to the enumeration process
> for USB 2 devices (the BOS descriptor set). The problem is that software can't request a new
> descriptor type to old devices that don't understand it without introducing compatibility issues
> with some devices (more than you would probably expect). So, software needed a way to
> identify whether a device could support the host requesting a BOS descriptor set. The solution
> in this ECN was to require devices supporting the ECN to set their bcdUSB to 0x0201 (2.01).
>
> Now, when we created the USB 3.0 spec, we again wanted to leverage the BOS descriptor, not
> only because we wanted to mandate USB 2 LPM in 3.0 devices when operating at high-speed,
> but also so the device can indicate to a host that it can operate at SuperSpeed (to support
> everyone's favorite "your device can perform faster" popup). Knowing that when operating at
> high-speed these devices needed to report the BOS descriptor set, we knew that it couldn't
> set the bcdUSB to 0x0200. We mistakenly wrote it down as 0x0210 instead of 0x0201 in the
> 3.0 spec (perhaps we just said "two point one" which might have been ambiguous) when we
> were trying to just be consistent with the requirement from the LPM ECN. So, rather than
> changing it back to 0x0201 in the USB 3.0 spec, we just ran with it.
>
> So, there are USB 2.0 devices, USB 2.01 devices and USB 2.1 devices, even though the latest
> revision of the USB 2 spec is USB 2.0. I have recommended that the USB-IF actually create a
> USB 2.1 specification that captures all of the various errata, ECNs, etc from over the years, but
> it hasn't happened yet.

Btw, configfs already includes these version codes to support Link
Power Management (LPM) and
the associated BOS descriptor, so I didn't add that part, I only added
webusb as a condition as to
when to send BOS descriptors.

>
> > descriptors available. The device than announces WebUSB support
> > using a platform device capability. This includes a vendor code
> > under which the landing page URL can be retrieved using a
> > vendor-specific request.
> >
> > Usage is modeled after os_desc descriptors:
> > echo 1 > webusb/use
> > echo "https://www.kernel.org" > webusb/landingPage
> >
> > lsusb will report the device with the following lines:
> > Platform Device Capability:
> > bLength 24
> > bDescriptorType 16
> > bDevCapabilityType 5
> > bReserved 0
> > PlatformCapabilityUUID {3408b638-09a9-47a0-8bfd-a0768815b665}
> > WebUSB:
> > bcdVersion 1.00
> > bVendorCode 0
> > iLandingPage 1 https://www.kernel.org
> >
> > Signed-off-by: Jó Ágila Bitsch <[email protected]>
> > ---
> > Documentation/ABI/testing/configfs-usb-gadget | 13 ++
> > drivers/usb/gadget/composite.c | 102 ++++++++++--
> > drivers/usb/gadget/configfs.c | 145 ++++++++++++++++++
> > include/linux/usb/composite.h | 6 +
> > include/uapi/linux/usb/ch9.h | 33 ++++
> > 5 files changed, 289 insertions(+), 10 deletions(-)
> >
> > diff --git a/Documentation/ABI/testing/configfs-usb-gadget b/Documentation/ABI/testing/configfs-usb-gadget
> > index b7943aa7e997..8399e0f0ed1c 100644
> > --- a/Documentation/ABI/testing/configfs-usb-gadget
> > +++ b/Documentation/ABI/testing/configfs-usb-gadget
> > @@ -143,3 +143,16 @@ Description:
> > qw_sign an identifier to be reported as "OS String"
> > proper
> > ============= ===============================================
> > +
> > +What: /config/usb-gadget/gadget/webusb
> > +Date: Dec 2022
> > +KernelVersion: 6.2
> > +Description:
> > + This group contains "WebUSB" extension handling attributes.
> > +
> > + ============= ===============================================
> > + use flag turning "WebUSB" support on/off
> > + bcdVersion bcd WebUSB specification version number
> > + b_vendor_code one-byte value used for custom per-device
> > + landing_page UTF-8 encoded URL of the device's landing page
> > + ============= ===============================================
> > diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c
> > index 403563c06477..937695dc5366 100644
> > --- a/drivers/usb/gadget/composite.c
> > +++ b/drivers/usb/gadget/composite.c
> > @@ -14,6 +14,7 @@
> > #include <linux/device.h>
> > #include <linux/utsname.h>
> > #include <linux/bitfield.h>
> > +#include <linux/uuid.h>
> >
> > #include <linux/usb/composite.h>
> > #include <linux/usb/otg.h>
> > @@ -713,14 +714,16 @@ static int bos_desc(struct usb_composite_dev *cdev)
> > * A SuperSpeed device shall include the USB2.0 extension descriptor
> > * and shall support LPM when operating in USB2.0 HS mode.
> > */
> > - usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
> > - bos->bNumDeviceCaps++;
> > - le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
> > - usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
> > - usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
> > - usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
> > - usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT |
> > - USB_BESL_SUPPORT | besl);
> > + if (cdev->gadget->lpm_capable) {
>
> This change doesn't seem to be related to the purpose of this patch.

Actually, it is.

Previously, BOS descriptors would only ever be sent if the device was
lpm_capable.
For this reason, this descriptor was previously sent unconditionally.

With my patch in place, it could be that a device is not lpm_capable, but still
wants to send BOS descriptors to announce its WebUSB capability,
therefore I added
this condition.

>
> > + usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
> > + bos->bNumDeviceCaps++;
> > + le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
> > + usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
> > + usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
> > + usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
> > + usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT |
> > + USB_BESL_SUPPORT | besl);
> > + }
> >
> > /*
> > * The Superspeed USB Capability descriptor shall be implemented by all
> > @@ -821,6 +824,41 @@ static int bos_desc(struct usb_composite_dev *cdev)
> > }
> > }
> >
> > + /*
> > + * Following the specifaction at:
> > + * https://wicg.github.io/webusb/#webusb-platform-capability-descriptor
> > + */
> > + if (cdev->use_webusb) {
> > + struct usb_webusb_cap_descriptor *webusb_cap;
> > + /*
> > + * little endian PlatformCapablityUUID for WebUSB:
> > + * 3408b638-09a9-47a0-8bfd-a0768815b665
> > + */
> > +#define WEBUSB_UUID UUID_INIT(0x38b60834, 0xa909, 0xa047, \
> > + 0x8b, 0xfd, 0xa0, 0x76, 0x88, 0x15, 0xb6, 0x65)
> > + uuid_t webusb_uuid = WEBUSB_UUID;
>
> This #define seems to be pointless. It is used nowhere but in the
> immediately following line. You might as well eliminate it.
>
> Or you might define this UUID at the same place in the header file
> where you define struct usb_webusb_cap_descriptor.

I will update the patch accordingly.

>
> > +
> > + webusb_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
> > + bos->bNumDeviceCaps++;
> > +
> > + le16_add_cpu(&bos->wTotalLength, USB_DT_WEBUSB_SIZE);
> > + webusb_cap->bLength = USB_DT_WEBUSB_SIZE;
> > + webusb_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
> > + webusb_cap->bDevCapabilityType = USB_PLAT_DEV_CAP_TYPE;
> > + webusb_cap->bReserved = 0;
> > + export_uuid(webusb_cap->UUID, &webusb_uuid);
> > + if (cdev->bcd_webusb_version != 0)
> > + webusb_cap->bcdVersion = cpu_to_le16(cdev->bcd_webusb_version);
> > + else
> > + webusb_cap->bcdVersion = cpu_to_le16(0x0100);
> > +
> > + webusb_cap->bVendorCode = cdev->b_webusb_vendor_code;
> > + if (strnlen(cdev->landing_page, sizeof(cdev->landing_page)) > 0)
> > + webusb_cap->iLandingPage = 1;
> > + else
> > + webusb_cap->iLandingPage = 0;
> > + }
> > +
> > return le16_to_cpu(bos->wTotalLength);
> > }
> >
> > @@ -1744,7 +1782,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
> > cdev->desc.bcdUSB = cpu_to_le16(0x0210);
> > }
> > } else {
> > - if (gadget->lpm_capable)
> > + if (gadget->lpm_capable || cdev->use_webusb)

Btw: this is the location where the USB version 2.01 and 2.1 were
already in the code to support LPM.

> > cdev->desc.bcdUSB = cpu_to_le16(0x0201);
> > else
> > cdev->desc.bcdUSB = cpu_to_le16(0x0200);
> > @@ -1779,7 +1817,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
> > break;
> > case USB_DT_BOS:
> > if (gadget_is_superspeed(gadget) ||
> > - gadget->lpm_capable) {
> > + gadget->lpm_capable || cdev->use_webusb) {

And this is the location that the BOS descriptors are sent, if the
device supports webusb. Previously the
code would only send these descriptors when the device was lpm_capable
or superspeed. Thus requiring
the additional condition above in bos_desc.

> > value = bos_desc(cdev);
> > value = min(w_length, (u16) value);
> > }
> > @@ -2013,6 +2051,50 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
> > goto check_value;
> > }
> >
> > + /*
> > + * webusb descriptor handling, following:
> > + * https://wicg.github.io/webusb/#device-requests
> > + */
> > + #define WEBUSB_GET_URL 2
>
> A similar comment applies to this #define. In this case it might make
> sense to put all the WebUSB-related #defines in an appropriate header
> file.

Agreed. I'll add a comment instead.

>
> > + if (cdev->use_webusb &&
> > + (ctrl->bRequestType & USB_TYPE_VENDOR) &&
> > + ctrl->wIndex == WEBUSB_GET_URL &&
> > + ctrl->bRequest == cdev->b_webusb_vendor_code) {
>
> Bad indentation. Visually this makes it look like the last two tests
> are part of the conditional block, because they share its indentation
> level.

I'll fix this.

>
> > + /*
> > + * specification of the url descriptor in WebUSB:
> > + * https://wicg.github.io/webusb/#webusb-descriptors
> > + */
> > + u8 *buf;
> > + unsigned int schema_http;
>
> *buf and schema_http should be indented by the same amount.

I'll fix this.

I had accidentally set my tab indentation to 4 spaces.

>
> > + unsigned int schema_https;
> > + unsigned int landing_page_length;
> > +
> > + buf = cdev->req->buf;
> > + #define WEBUSB_URL 3
> > + buf[1] = WEBUSB_URL;
> > +
> > + landing_page_length = strnlen(cdev->landing_page, 252);
> > + schema_https = (strncmp("https://", cdev->landing_page, 8) == 0);
> > + schema_http = (strncmp("http://", cdev->landing_page, 7) == 0);
>
> Do you really need these temporary variables? Why not put the tests
> directly into the "if" conditions?

Good point.

> Also, should the comparisons be case-insensitive?

As URI schemes are case-insensitive as per
https://www.rfc-editor.org/rfc/rfc3986#section-3.1
you are right, so I will change this.

>
> > + if (schema_https) {
> > + buf[2] = 0x01;
> > + memcpy(buf+3, cdev->landing_page+8, landing_page_length-8);
> > + buf[0] = landing_page_length - 8 + 3;
> > + } else if (schema_http) {
> > + buf[2] = 0x00;
> > + memcpy(buf+3, cdev->landing_page+7, landing_page_length-7);
> > + buf[0] = landing_page_length - 7 + 3;
> > + } else {
> > + buf[2] = 0xFF;
> > + memcpy(buf+3, cdev->landing_page, landing_page_length);
> > + buf[0] = landing_page_length + 3;
> > + }
> > +
> > + value = buf[0];
> > +
> > + goto check_value;
> > + }
> > +
> > VDBG(cdev,
> > "non-core control req%02x.%02x v%04x i%04x l%d\n",
> > ctrl->bRequestType, ctrl->bRequest,
> > diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c
> > index 96121d1c8df4..da49b36f7033 100644
> > --- a/drivers/usb/gadget/configfs.c
> > +++ b/drivers/usb/gadget/configfs.c
> > @@ -39,6 +39,7 @@ struct gadget_info {
> > struct config_group configs_group;
> > struct config_group strings_group;
> > struct config_group os_desc_group;
> > + struct config_group webusb_group;
> >
> > struct mutex lock;
> > struct usb_gadget_strings *gstrings[MAX_USB_STRING_LANGS + 1];
> > @@ -50,6 +51,11 @@ struct gadget_info {
> > bool use_os_desc;
> > char b_vendor_code;
> > char qw_sign[OS_STRING_QW_SIGN_LEN];
> > + bool use_webusb;
> > + u16 bcd_webusb_version;
> > + u8 b_webusb_vendor_code;
>
> Again, improper indentation.

I'll fix this.

> > + char landing_page[255];
> > +
> > spinlock_t spinlock;
> > bool unbind;
> > };
>
> ...

I'll fix this.

>
> > diff --git a/include/linux/usb/composite.h b/include/linux/usb/composite.h
> > index 43ac3fa760db..44f90c37dda9 100644
> > --- a/include/linux/usb/composite.h
> > +++ b/include/linux/usb/composite.h
> > @@ -474,6 +474,12 @@ struct usb_composite_dev {
> > struct usb_configuration *os_desc_config;
> > unsigned int use_os_string:1;
> >
> > + /* WebUSB */
> > + u16 bcd_webusb_version;
> > + u8 b_webusb_vendor_code;
> > + char landing_page[255];
>
> Improper indentation.

I'll fix this.

>
> > + unsigned int use_webusb:1;
> > +
> > /* private: */
> > /* internals */
> > unsigned int suspended:1;
>
> Alan Stern

Thank you for your review! I will post an updated version of the patch soon.

2023-01-01 11:54:16

by Jó Ágila Bitsch

[permalink] [raw]
Subject: [PATCH v2] usb: gadget: add WebUSB support

There is a custom (non-USB IF) extension to the USB standard:

https://wicg.github.io/webusb/

This specification is published under the W3C Community Contributor
Agreement, which in particular allows to implement the specification
without any royalties.

The specification allows USB gadgets to announce an URL to landing
page and describes a Javascript interface for websites to interact
with the USB gadget, if the user allows it. It is currently
supported by Chromium-based browsers, such as Chrome, Edge and
Opera on all major operating systems including Linux.

This patch adds optional support for Linux-based USB gadgets
wishing to expose such a landing page.

During device enumeration, a host recognizes that the announced
USB version is at least 2.01, which means, that there are BOS
descriptors available. The device than announces WebUSB support
using a platform device capability. This includes a vendor code
under which the landing page URL can be retrieved using a
vendor-specific request.

Usage is modeled after os_desc descriptors:
echo 1 > webusb/use
echo "https://www.kernel.org" > webusb/landingPage

lsusb will report the device with the following lines:
Platform Device Capability:
bLength 24
bDescriptorType 16
bDevCapabilityType 5
bReserved 0
PlatformCapabilityUUID {3408b638-09a9-47a0-8bfd-a0768815b665}
WebUSB:
bcdVersion 1.00
bVendorCode 0
iLandingPage 1 https://www.kernel.org

Signed-off-by: J? ?gila Bitsch <[email protected]>
---
Documentation/ABI/testing/configfs-usb-gadget | 13 ++
drivers/usb/gadget/composite.c | 96 ++++++++++--
drivers/usb/gadget/configfs.c | 145 ++++++++++++++++++
include/linux/usb/composite.h | 6 +
include/uapi/linux/usb/ch9.h | 33 ++++
5 files changed, 283 insertions(+), 10 deletions(-)

diff --git a/Documentation/ABI/testing/configfs-usb-gadget b/Documentation/ABI/testing/configfs-usb-gadget
index b7943aa7e997..8399e0f0ed1c 100644
--- a/Documentation/ABI/testing/configfs-usb-gadget
+++ b/Documentation/ABI/testing/configfs-usb-gadget
@@ -143,3 +143,16 @@ Description:
qw_sign an identifier to be reported as "OS String"
proper
============= ===============================================
+
+What: /config/usb-gadget/gadget/webusb
+Date: Dec 2022
+KernelVersion: 6.2
+Description:
+ This group contains "WebUSB" extension handling attributes.
+
+ ============= ===============================================
+ use flag turning "WebUSB" support on/off
+ bcdVersion bcd WebUSB specification version number
+ b_vendor_code one-byte value used for custom per-device
+ landing_page UTF-8 encoded URL of the device's landing page
+ ============= ===============================================
diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c
index 403563c06477..b66dfdba6035 100644
--- a/drivers/usb/gadget/composite.c
+++ b/drivers/usb/gadget/composite.c
@@ -14,6 +14,7 @@
#include <linux/device.h>
#include <linux/utsname.h>
#include <linux/bitfield.h>
+#include <linux/uuid.h>

#include <linux/usb/composite.h>
#include <linux/usb/otg.h>
@@ -713,14 +714,16 @@ static int bos_desc(struct usb_composite_dev *cdev)
* A SuperSpeed device shall include the USB2.0 extension descriptor
* and shall support LPM when operating in USB2.0 HS mode.
*/
- usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
- bos->bNumDeviceCaps++;
- le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
- usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
- usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
- usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
- usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT |
- USB_BESL_SUPPORT | besl);
+ if (cdev->gadget->lpm_capable) {
+ usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
+ bos->bNumDeviceCaps++;
+ le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
+ usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
+ usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
+ usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
+ usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT |
+ USB_BESL_SUPPORT | besl);
+ }

/*
* The Superspeed USB Capability descriptor shall be implemented by all
@@ -821,6 +824,40 @@ static int bos_desc(struct usb_composite_dev *cdev)
}
}

+ /*
+ * Following the specifaction at:
+ * https://wicg.github.io/webusb/#webusb-platform-capability-descriptor
+ */
+ if (cdev->use_webusb) {
+ struct usb_webusb_cap_descriptor *webusb_cap;
+ /*
+ * little endian PlatformCapablityUUID for WebUSB:
+ * 3408b638-09a9-47a0-8bfd-a0768815b665
+ */
+ uuid_t webusb_uuid = UUID_INIT(0x38b60834, 0xa909, 0xa047, 0x8b, 0xfd, 0xa0, 0x76,
+ 0x88, 0x15, 0xb6, 0x65);
+
+ webusb_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
+ bos->bNumDeviceCaps++;
+
+ le16_add_cpu(&bos->wTotalLength, USB_DT_WEBUSB_SIZE);
+ webusb_cap->bLength = USB_DT_WEBUSB_SIZE;
+ webusb_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
+ webusb_cap->bDevCapabilityType = USB_PLAT_DEV_CAP_TYPE;
+ webusb_cap->bReserved = 0;
+ export_uuid(webusb_cap->UUID, &webusb_uuid);
+ if (cdev->bcd_webusb_version != 0)
+ webusb_cap->bcdVersion = cpu_to_le16(cdev->bcd_webusb_version);
+ else
+ webusb_cap->bcdVersion = cpu_to_le16(0x0100);
+
+ webusb_cap->bVendorCode = cdev->b_webusb_vendor_code;
+ if (strnlen(cdev->landing_page, sizeof(cdev->landing_page)) > 0)
+ webusb_cap->iLandingPage = 1;
+ else
+ webusb_cap->iLandingPage = 0;
+ }
+
return le16_to_cpu(bos->wTotalLength);
}

@@ -1744,7 +1781,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
cdev->desc.bcdUSB = cpu_to_le16(0x0210);
}
} else {
- if (gadget->lpm_capable)
+ if (gadget->lpm_capable || cdev->use_webusb)
cdev->desc.bcdUSB = cpu_to_le16(0x0201);
else
cdev->desc.bcdUSB = cpu_to_le16(0x0200);
@@ -1779,7 +1816,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
break;
case USB_DT_BOS:
if (gadget_is_superspeed(gadget) ||
- gadget->lpm_capable) {
+ gadget->lpm_capable || cdev->use_webusb) {
value = bos_desc(cdev);
value = min(w_length, (u16) value);
}
@@ -2013,6 +2050,45 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
goto check_value;
}

+ /*
+ * webusb descriptor handling, following:
+ * https://wicg.github.io/webusb/#device-requests
+ */
+ if (cdev->use_webusb &&
+ (ctrl->bRequestType & USB_TYPE_VENDOR) &&
+ ctrl->wIndex == /* WEBUSB_GET_URL*/ 2 &&
+ ctrl->bRequest == cdev->b_webusb_vendor_code) {
+ /*
+ * specification of the url descriptor in WebUSB:
+ * https://wicg.github.io/webusb/#webusb-descriptors
+ */
+ u8 *buf;
+ unsigned int landing_page_length;
+
+ buf = cdev->req->buf;
+ #define WEBUSB_URL 3
+ buf[1] = WEBUSB_URL;
+
+ landing_page_length = strnlen(cdev->landing_page, 252);
+ if (strncasecmp("https://", cdev->landing_page, 8) == 0) {
+ buf[2] = 0x01;
+ memcpy(buf+3, cdev->landing_page+8, landing_page_length-8);
+ buf[0] = landing_page_length - 8 + 3;
+ } else if (strncasecmp("http://", cdev->landing_page, 7) == 0) {
+ buf[2] = 0x00;
+ memcpy(buf+3, cdev->landing_page+7, landing_page_length-7);
+ buf[0] = landing_page_length - 7 + 3;
+ } else {
+ buf[2] = 0xFF;
+ memcpy(buf+3, cdev->landing_page, landing_page_length);
+ buf[0] = landing_page_length + 3;
+ }
+
+ value = buf[0];
+
+ goto check_value;
+ }
+
VDBG(cdev,
"non-core control req%02x.%02x v%04x i%04x l%d\n",
ctrl->bRequestType, ctrl->bRequest,
diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c
index 96121d1c8df4..2b7f01a9efff 100644
--- a/drivers/usb/gadget/configfs.c
+++ b/drivers/usb/gadget/configfs.c
@@ -39,6 +39,7 @@ struct gadget_info {
struct config_group configs_group;
struct config_group strings_group;
struct config_group os_desc_group;
+ struct config_group webusb_group;

struct mutex lock;
struct usb_gadget_strings *gstrings[MAX_USB_STRING_LANGS + 1];
@@ -50,6 +51,11 @@ struct gadget_info {
bool use_os_desc;
char b_vendor_code;
char qw_sign[OS_STRING_QW_SIGN_LEN];
+ bool use_webusb;
+ u16 bcd_webusb_version;
+ u8 b_webusb_vendor_code;
+ char landing_page[255];
+
spinlock_t spinlock;
bool unbind;
};
@@ -780,6 +786,131 @@ static void gadget_strings_attr_release(struct config_item *item)
USB_CONFIG_STRING_RW_OPS(gadget_strings);
USB_CONFIG_STRINGS_LANG(gadget_strings, gadget_info);

+static inline struct gadget_info *webusb_item_to_gadget_info(
+ struct config_item *item)
+{
+ return container_of(to_config_group(item),
+ struct gadget_info, webusb_group);
+}
+
+static ssize_t webusb_use_show(struct config_item *item, char *page)
+{
+ return sysfs_emit(page, "%d\n",
+ webusb_item_to_gadget_info(item)->use_webusb);
+}
+
+static ssize_t webusb_use_store(struct config_item *item, const char *page,
+ size_t len)
+{
+ struct gadget_info *gi = webusb_item_to_gadget_info(item);
+ int ret;
+ bool use;
+
+ mutex_lock(&gi->lock);
+ ret = kstrtobool(page, &use);
+ if (!ret) {
+ gi->use_webusb = use;
+ ret = len;
+ }
+ mutex_unlock(&gi->lock);
+
+ return ret;
+}
+
+static ssize_t webusb_bcdVersion_show(struct config_item *item, char *page)
+{
+ return sysfs_emit(page, "0x%04x\n",
+ webusb_item_to_gadget_info(item)->bcd_webusb_version);
+}
+
+static ssize_t webusb_bcdVersion_store(struct config_item *item,
+ const char *page, size_t len)
+{
+ struct gadget_info *gi = webusb_item_to_gadget_info(item);
+ u16 bcdVersion;
+ int ret;
+
+ mutex_lock(&gi->lock);
+ ret = kstrtou16(page, 0, &bcdVersion);
+ if (ret)
+ goto out;
+ ret = is_valid_bcd(bcdVersion);
+ if (ret)
+ goto out;
+
+ gi->bcd_webusb_version = bcdVersion;
+ ret = len;
+
+out:
+ mutex_unlock(&gi->lock);
+
+ return ret;
+}
+
+static ssize_t webusb_bVendorCode_show(struct config_item *item, char *page)
+{
+ return sysfs_emit(page, "0x%02x\n",
+ webusb_item_to_gadget_info(item)->b_webusb_vendor_code);
+}
+
+static ssize_t webusb_bVendorCode_store(struct config_item *item,
+ const char *page, size_t len)
+{
+ struct gadget_info *gi = webusb_item_to_gadget_info(item);
+ int ret;
+ u8 b_vendor_code;
+
+ mutex_lock(&gi->lock);
+ ret = kstrtou8(page, 0, &b_vendor_code);
+ if (!ret) {
+ gi->b_webusb_vendor_code = b_vendor_code;
+ ret = len;
+ }
+ mutex_unlock(&gi->lock);
+
+ return ret;
+}
+
+static ssize_t webusb_landingPage_show(struct config_item *item, char *page)
+{
+ return sysfs_emit(page, "%s\n", webusb_item_to_gadget_info(item)->landing_page);
+}
+
+static ssize_t webusb_landingPage_store(struct config_item *item, const char *page,
+ size_t len)
+{
+ struct gadget_info *gi = webusb_item_to_gadget_info(item);
+ int l;
+
+ l = min(len, sizeof(gi->landing_page));
+ if (page[l - 1] == '\n')
+ --l;
+
+ mutex_lock(&gi->lock);
+ memcpy(gi->landing_page, page, l);
+ mutex_unlock(&gi->lock);
+
+ return len;
+}
+
+CONFIGFS_ATTR(webusb_, use);
+CONFIGFS_ATTR(webusb_, bVendorCode);
+CONFIGFS_ATTR(webusb_, bcdVersion);
+CONFIGFS_ATTR(webusb_, landingPage);
+
+static struct configfs_attribute *webusb_attrs[] = {
+ &webusb_attr_use,
+ &webusb_attr_bcdVersion,
+ &webusb_attr_bVendorCode,
+ &webusb_attr_landingPage,
+ NULL,
+};
+
+static struct config_item_type webusb_type = {
+ .ct_attrs = webusb_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
static inline struct gadget_info *os_desc_item_to_gadget_info(
struct config_item *item)
{
@@ -1341,6 +1472,16 @@ static int configfs_composite_bind(struct usb_gadget *gadget,
gi->cdev.desc.iSerialNumber = s[USB_GADGET_SERIAL_IDX].id;
}

+ if (gi->use_webusb) {
+ cdev->use_webusb = true;
+ cdev->bcd_webusb_version = gi->bcd_webusb_version;
+ cdev->b_webusb_vendor_code = gi->b_webusb_vendor_code;
+ memcpy(cdev->landing_page, gi->landing_page,
+ strnlen(gi->landing_page,
+ min(sizeof(cdev->landing_page),
+ sizeof(gi->landing_page))));
+ }
+
if (gi->use_os_desc) {
cdev->use_os_string = true;
cdev->b_vendor_code = gi->b_vendor_code;
@@ -1605,6 +1746,10 @@ static struct config_group *gadgets_make(
&os_desc_type);
configfs_add_default_group(&gi->os_desc_group, &gi->group);

+ config_group_init_type_name(&gi->webusb_group, "webusb",
+ &webusb_type);
+ configfs_add_default_group(&gi->webusb_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 43ac3fa760db..eb6fac5bbcde 100644
--- a/include/linux/usb/composite.h
+++ b/include/linux/usb/composite.h
@@ -474,6 +474,12 @@ struct usb_composite_dev {
struct usb_configuration *os_desc_config;
unsigned int use_os_string:1;

+ /* WebUSB */
+ u16 bcd_webusb_version;
+ u8 b_webusb_vendor_code;
+ char landing_page[255];
+ unsigned int use_webusb:1;
+
/* private: */
/* internals */
unsigned int suspended:1;
diff --git a/include/uapi/linux/usb/ch9.h b/include/uapi/linux/usb/ch9.h
index 31fcfa084e63..3a36550297bc 100644
--- a/include/uapi/linux/usb/ch9.h
+++ b/include/uapi/linux/usb/ch9.h
@@ -947,6 +947,39 @@ struct usb_ss_container_id_descriptor {

#define USB_DT_USB_SS_CONTN_ID_SIZE 20

+/*
+ * Platform Device Capability descriptor: Defines platform specific device
+ * capabilities
+ */
+#define USB_PLAT_DEV_CAP_TYPE 5
+struct usb_plat_dev_cap_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDevCapabilityType;
+ __u8 bReserved;
+ __u8 UUID[16];
+} __attribute__((packed));
+
+#define USB_DT_USB_PLAT_DEV_CAP_SIZE 20
+
+/*
+ * WebUSB Platform Capability descriptor: A device announces support for the
+ * WebUSB command set by including the following Platform Descriptor in its
+ * Binary Object Store
+ * https://wicg.github.io/webusb/#webusb-platform-capability-descriptor
+ */
+struct usb_webusb_cap_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDevCapabilityType;
+ __u8 bReserved;
+ __u8 UUID[16];
+ __u16 bcdVersion;
+ __u8 bVendorCode;
+ __u8 iLandingPage;
+} __attribute__((packed));
+#define USB_DT_WEBUSB_SIZE (USB_DT_USB_PLAT_DEV_CAP_SIZE + 4)
+
/*
* SuperSpeed Plus USB Capability descriptor: Defines the set of
* SuperSpeed Plus USB specific device level capabilities
--
2.37.2

2023-01-01 15:40:48

by Greg Kroah-Hartman

[permalink] [raw]
Subject: Re: [PATCH v2] usb: gadget: add WebUSB support

On Sun, Jan 01, 2023 at 12:40:12PM +0100, J? ?gila Bitsch wrote:
> There is a custom (non-USB IF) extension to the USB standard:
>
> https://wicg.github.io/webusb/
>
> This specification is published under the W3C Community Contributor
> Agreement, which in particular allows to implement the specification
> without any royalties.
>
> The specification allows USB gadgets to announce an URL to landing
> page and describes a Javascript interface for websites to interact
> with the USB gadget, if the user allows it. It is currently
> supported by Chromium-based browsers, such as Chrome, Edge and
> Opera on all major operating systems including Linux.
>
> This patch adds optional support for Linux-based USB gadgets
> wishing to expose such a landing page.
>
> During device enumeration, a host recognizes that the announced
> USB version is at least 2.01, which means, that there are BOS
> descriptors available. The device than announces WebUSB support
> using a platform device capability. This includes a vendor code
> under which the landing page URL can be retrieved using a
> vendor-specific request.
>
> Usage is modeled after os_desc descriptors:
> echo 1 > webusb/use
> echo "https://www.kernel.org" > webusb/landingPage
>
> lsusb will report the device with the following lines:
> Platform Device Capability:
> bLength 24
> bDescriptorType 16
> bDevCapabilityType 5
> bReserved 0
> PlatformCapabilityUUID {3408b638-09a9-47a0-8bfd-a0768815b665}
> WebUSB:
> bcdVersion 1.00
> bVendorCode 0
> iLandingPage 1 https://www.kernel.org
>
> Signed-off-by: J? ?gila Bitsch <[email protected]>
> ---
> Documentation/ABI/testing/configfs-usb-gadget | 13 ++
> drivers/usb/gadget/composite.c | 96 ++++++++++--
> drivers/usb/gadget/configfs.c | 145 ++++++++++++++++++
> include/linux/usb/composite.h | 6 +
> include/uapi/linux/usb/ch9.h | 33 ++++
> 5 files changed, 283 insertions(+), 10 deletions(-)
>

Hi,

This is the friendly patch-bot of Greg Kroah-Hartman. You have sent him
a patch that has triggered this response. He used to manually respond
to these common problems, but in order to save his sanity (he kept
writing the same thing over and over, yet to different people), I was
created. Hopefully you will not take offence and will fix the problem
in your patch and resubmit it so that it can be accepted into the Linux
kernel tree.

You are receiving this message because of the following common error(s)
as indicated below:

- This looks like a new version of a previously submitted patch, but you
did not list below the --- line any changes from the previous version.
Please read the section entitled "The canonical patch format" in the
kernel file, Documentation/process/submitting-patches.rst for what
needs to be done here to properly describe this.

If you wish to discuss this problem further, or you have questions about
how to resolve this issue, please feel free to respond to this email and
Greg will reply once he has dug out from the pending patches received
from other developers.

thanks,

greg k-h's patch email bot

2023-01-01 17:39:39

by Alan Stern

[permalink] [raw]
Subject: Re: [PATCH v1] usb: gadget: add WebUSB support

On Sat, Dec 31, 2022 at 09:26:44PM +0100, J? ?gila Bitsch wrote:
> On Sat, Dec 31, 2022 at 8:47 PM Alan Stern <[email protected]> wrote:
> >
> > On Fri, Dec 30, 2022 at 09:11:43PM +0100, J? ?gila Bitsch wrote:
> > >
> > > During device enumeration, a host recognizes that the announced
> > > USB version is at least 2.01, which means, that there are BOS
> >
> > Where is this 2.01 value specified? I don't remember seeing it in the
> > usual USBIF documents.
>
> This is actually from the backport of BOS descriptors to USB 2
>
> Citing Randy Aull from
> https://community.osr.com/discussion/comment/249742/#Comment_249742
> > There is no specification called USB 2.1, however there is such a thing as a USB 2.1 device.
> > The USB 2.0 ECN for LPM introduced a new descriptor request to the enumeration process
> > for USB 2 devices (the BOS descriptor set). The problem is that software can't request a new
> > descriptor type to old devices that don't understand it without introducing compatibility issues
> > with some devices (more than you would probably expect). So, software needed a way to
> > identify whether a device could support the host requesting a BOS descriptor set. The solution
> > in this ECN was to require devices supporting the ECN to set their bcdUSB to 0x0201 (2.01).
> >
> > Now, when we created the USB 3.0 spec, we again wanted to leverage the BOS descriptor, not
> > only because we wanted to mandate USB 2 LPM in 3.0 devices when operating at high-speed,
> > but also so the device can indicate to a host that it can operate at SuperSpeed (to support
> > everyone's favorite "your device can perform faster" popup). Knowing that when operating at
> > high-speed these devices needed to report the BOS descriptor set, we knew that it couldn't
> > set the bcdUSB to 0x0200. We mistakenly wrote it down as 0x0210 instead of 0x0201 in the
> > 3.0 spec (perhaps we just said "two point one" which might have been ambiguous) when we
> > were trying to just be consistent with the requirement from the LPM ECN. So, rather than
> > changing it back to 0x0201 in the USB 3.0 spec, we just ran with it.
> >
> > So, there are USB 2.0 devices, USB 2.01 devices and USB 2.1 devices, even though the latest
> > revision of the USB 2 spec is USB 2.0. I have recommended that the USB-IF actually create a
> > USB 2.1 specification that captures all of the various errata, ECNs, etc from over the years, but
> > it hasn't happened yet.

Interesting history. And now that you point it out, I do see at the end
of section 3 of the USB 2.0 Link Power Management ECN:

Devices that support the BOS descriptor must have a bcdUSB value
of 0201H or larger.

> Btw, configfs already includes these version codes to support Link
> Power Management (LPM) and
> the associated BOS descriptor, so I didn't add that part, I only added
> webusb as a condition as to
> when to send BOS descriptors.

Right.

> > > @@ -713,14 +714,16 @@ static int bos_desc(struct usb_composite_dev *cdev)
> > > * A SuperSpeed device shall include the USB2.0 extension descriptor
> > > * and shall support LPM when operating in USB2.0 HS mode.
> > > */
> > > - usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
> > > - bos->bNumDeviceCaps++;
> > > - le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
> > > - usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
> > > - usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
> > > - usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
> > > - usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT |
> > > - USB_BESL_SUPPORT | besl);
> > > + if (cdev->gadget->lpm_capable) {
> >
> > This change doesn't seem to be related to the purpose of this patch.
>
> Actually, it is.
>
> Previously, BOS descriptors would only ever be sent if the device was
> lpm_capable.
> For this reason, this descriptor was previously sent unconditionally.
>
> With my patch in place, it could be that a device is not lpm_capable, but still
> wants to send BOS descriptors to announce its WebUSB capability,
> therefore I added
> this condition.

Okay. It would be good to explain this in the patch description.

Alan Stern

2023-01-01 17:58:17

by Jó Ágila Bitsch

[permalink] [raw]
Subject: [PATCH v3] usb: gadget: add WebUSB landing page support


There is a custom (non-USB IF) extension to the USB standard:

https://wicg.github.io/webusb/

This specification is published under the W3C Community Contributor
Agreement, which in particular allows to implement the specification
without any royalties.

The specification allows USB gadgets to announce an URL to landing
page and describes a Javascript interface for websites to interact
with the USB gadget, if the user allows it. It is currently
supported by Chromium-based browsers, such as Chrome, Edge and
Opera on all major operating systems including Linux.

This patch adds optional support for Linux-based USB gadgets
wishing to expose such a landing page.

During device enumeration, a host recognizes that the announced
USB version is at least 2.01, which means, that there are BOS
descriptors available. The device than announces WebUSB support
using a platform device capability. This includes a vendor code
under which the landing page URL can be retrieved using a
vendor-specific request.

Previously, the BOS descriptors would unconditionally include an
LPM related descriptor, as BOS descriptors were only ever sent
when the device was LPM capable. As this is no longer the case,
this patch puts this descriptor behind a lpm_capable condition.

Usage is modeled after os_desc descriptors:
echo 1 > webusb/use
echo "https://www.kernel.org" > webusb/landingPage

lsusb will report the device with the following lines:
Platform Device Capability:
bLength 24
bDescriptorType 16
bDevCapabilityType 5
bReserved 0
PlatformCapabilityUUID {3408b638-09a9-47a0-8bfd-a0768815b665}
WebUSB:
bcdVersion 1.00
bVendorCode 0
iLandingPage 1 https://www.kernel.org

Signed-off-by: J? ?gila Bitsch <[email protected]>
---
v2 -> V3: improved commit message to include additional condition in desc_bos as per comment
by Alan Stern
V1 -> V2: cleaned up coding style, made URL scheme comparison case insensitive, addressed review
comments by Alan Stern
V0 -> V1: use sysfs_emit instead of sprintf and use lock in webusb_bcdVersion_store, addressed review
comments by Christophe JAILLET

Documentation/ABI/testing/configfs-usb-gadget | 13 ++
drivers/usb/gadget/composite.c | 95 ++++++++++--
drivers/usb/gadget/configfs.c | 145 ++++++++++++++++++
include/linux/usb/composite.h | 6 +
include/uapi/linux/usb/ch9.h | 33 ++++
5 files changed, 282 insertions(+), 10 deletions(-)

diff --git a/Documentation/ABI/testing/configfs-usb-gadget b/Documentation/ABI/testing/configfs-usb-gadget
index b7943aa7e997..8399e0f0ed1c 100644
--- a/Documentation/ABI/testing/configfs-usb-gadget
+++ b/Documentation/ABI/testing/configfs-usb-gadget
@@ -143,3 +143,16 @@ Description:
qw_sign an identifier to be reported as "OS String"
proper
============= ===============================================
+
+What: /config/usb-gadget/gadget/webusb
+Date: Dec 2022
+KernelVersion: 6.2
+Description:
+ This group contains "WebUSB" extension handling attributes.
+
+ ============= ===============================================
+ use flag turning "WebUSB" support on/off
+ bcdVersion bcd WebUSB specification version number
+ b_vendor_code one-byte value used for custom per-device
+ landing_page UTF-8 encoded URL of the device's landing page
+ ============= ===============================================
diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c
index 403563c06477..9b209e69442d 100644
--- a/drivers/usb/gadget/composite.c
+++ b/drivers/usb/gadget/composite.c
@@ -14,6 +14,7 @@
#include <linux/device.h>
#include <linux/utsname.h>
#include <linux/bitfield.h>
+#include <linux/uuid.h>

#include <linux/usb/composite.h>
#include <linux/usb/otg.h>
@@ -713,14 +714,16 @@ static int bos_desc(struct usb_composite_dev *cdev)
* A SuperSpeed device shall include the USB2.0 extension descriptor
* and shall support LPM when operating in USB2.0 HS mode.
*/
- usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
- bos->bNumDeviceCaps++;
- le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
- usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
- usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
- usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
- usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT |
- USB_BESL_SUPPORT | besl);
+ if (cdev->gadget->lpm_capable) {
+ usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
+ bos->bNumDeviceCaps++;
+ le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
+ usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
+ usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
+ usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
+ usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT |
+ USB_BESL_SUPPORT | besl);
+ }

/*
* The Superspeed USB Capability descriptor shall be implemented by all
@@ -821,6 +824,40 @@ static int bos_desc(struct usb_composite_dev *cdev)
}
}

+ /*
+ * Following the specifaction at:
+ * https://wicg.github.io/webusb/#webusb-platform-capability-descriptor
+ */
+ if (cdev->use_webusb) {
+ struct usb_webusb_cap_descriptor *webusb_cap;
+ /*
+ * little endian PlatformCapablityUUID for WebUSB:
+ * 3408b638-09a9-47a0-8bfd-a0768815b665
+ */
+ uuid_t webusb_uuid = UUID_INIT(0x38b60834, 0xa909, 0xa047, 0x8b, 0xfd, 0xa0, 0x76,
+ 0x88, 0x15, 0xb6, 0x65);
+
+ webusb_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
+ bos->bNumDeviceCaps++;
+
+ le16_add_cpu(&bos->wTotalLength, USB_DT_WEBUSB_SIZE);
+ webusb_cap->bLength = USB_DT_WEBUSB_SIZE;
+ webusb_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
+ webusb_cap->bDevCapabilityType = USB_PLAT_DEV_CAP_TYPE;
+ webusb_cap->bReserved = 0;
+ export_uuid(webusb_cap->UUID, &webusb_uuid);
+ if (cdev->bcd_webusb_version != 0)
+ webusb_cap->bcdVersion = cpu_to_le16(cdev->bcd_webusb_version);
+ else
+ webusb_cap->bcdVersion = cpu_to_le16(0x0100);
+
+ webusb_cap->bVendorCode = cdev->b_webusb_vendor_code;
+ if (strnlen(cdev->landing_page, sizeof(cdev->landing_page)) > 0)
+ webusb_cap->iLandingPage = 1;
+ else
+ webusb_cap->iLandingPage = 0;
+ }
+
return le16_to_cpu(bos->wTotalLength);
}

@@ -1744,7 +1781,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
cdev->desc.bcdUSB = cpu_to_le16(0x0210);
}
} else {
- if (gadget->lpm_capable)
+ if (gadget->lpm_capable || cdev->use_webusb)
cdev->desc.bcdUSB = cpu_to_le16(0x0201);
else
cdev->desc.bcdUSB = cpu_to_le16(0x0200);
@@ -1779,7 +1816,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
break;
case USB_DT_BOS:
if (gadget_is_superspeed(gadget) ||
- gadget->lpm_capable) {
+ gadget->lpm_capable || cdev->use_webusb) {
value = bos_desc(cdev);
value = min(w_length, (u16) value);
}
@@ -2013,6 +2050,44 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
goto check_value;
}

+ /*
+ * webusb descriptor handling, following:
+ * https://wicg.github.io/webusb/#device-requests
+ */
+ if (cdev->use_webusb &&
+ (ctrl->bRequestType & USB_TYPE_VENDOR) &&
+ ctrl->wIndex == /* WEBUSB_GET_URL*/ 2 &&
+ ctrl->bRequest == cdev->b_webusb_vendor_code) {
+ /*
+ * specification of the url descriptor in WebUSB:
+ * https://wicg.github.io/webusb/#webusb-descriptors
+ */
+ u8 *buf;
+ unsigned int landing_page_length;
+
+ buf = cdev->req->buf;
+ buf[1] = /* WEBUSB_URL*/ 3;
+
+ landing_page_length = strnlen(cdev->landing_page, 252);
+ if (strncasecmp("https://", cdev->landing_page, 8) == 0) {
+ buf[2] = 0x01;
+ memcpy(buf+3, cdev->landing_page+8, landing_page_length-8);
+ buf[0] = landing_page_length - 8 + 3;
+ } else if (strncasecmp("http://", cdev->landing_page, 7) == 0) {
+ buf[2] = 0x00;
+ memcpy(buf+3, cdev->landing_page+7, landing_page_length-7);
+ buf[0] = landing_page_length - 7 + 3;
+ } else {
+ buf[2] = 0xFF;
+ memcpy(buf+3, cdev->landing_page, landing_page_length);
+ buf[0] = landing_page_length + 3;
+ }
+
+ value = buf[0];
+
+ goto check_value;
+ }
+
VDBG(cdev,
"non-core control req%02x.%02x v%04x i%04x l%d\n",
ctrl->bRequestType, ctrl->bRequest,
diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c
index 96121d1c8df4..2b7f01a9efff 100644
--- a/drivers/usb/gadget/configfs.c
+++ b/drivers/usb/gadget/configfs.c
@@ -39,6 +39,7 @@ struct gadget_info {
struct config_group configs_group;
struct config_group strings_group;
struct config_group os_desc_group;
+ struct config_group webusb_group;

struct mutex lock;
struct usb_gadget_strings *gstrings[MAX_USB_STRING_LANGS + 1];
@@ -50,6 +51,11 @@ struct gadget_info {
bool use_os_desc;
char b_vendor_code;
char qw_sign[OS_STRING_QW_SIGN_LEN];
+ bool use_webusb;
+ u16 bcd_webusb_version;
+ u8 b_webusb_vendor_code;
+ char landing_page[255];
+
spinlock_t spinlock;
bool unbind;
};
@@ -780,6 +786,131 @@ static void gadget_strings_attr_release(struct config_item *item)
USB_CONFIG_STRING_RW_OPS(gadget_strings);
USB_CONFIG_STRINGS_LANG(gadget_strings, gadget_info);

+static inline struct gadget_info *webusb_item_to_gadget_info(
+ struct config_item *item)
+{
+ return container_of(to_config_group(item),
+ struct gadget_info, webusb_group);
+}
+
+static ssize_t webusb_use_show(struct config_item *item, char *page)
+{
+ return sysfs_emit(page, "%d\n",
+ webusb_item_to_gadget_info(item)->use_webusb);
+}
+
+static ssize_t webusb_use_store(struct config_item *item, const char *page,
+ size_t len)
+{
+ struct gadget_info *gi = webusb_item_to_gadget_info(item);
+ int ret;
+ bool use;
+
+ mutex_lock(&gi->lock);
+ ret = kstrtobool(page, &use);
+ if (!ret) {
+ gi->use_webusb = use;
+ ret = len;
+ }
+ mutex_unlock(&gi->lock);
+
+ return ret;
+}
+
+static ssize_t webusb_bcdVersion_show(struct config_item *item, char *page)
+{
+ return sysfs_emit(page, "0x%04x\n",
+ webusb_item_to_gadget_info(item)->bcd_webusb_version);
+}
+
+static ssize_t webusb_bcdVersion_store(struct config_item *item,
+ const char *page, size_t len)
+{
+ struct gadget_info *gi = webusb_item_to_gadget_info(item);
+ u16 bcdVersion;
+ int ret;
+
+ mutex_lock(&gi->lock);
+ ret = kstrtou16(page, 0, &bcdVersion);
+ if (ret)
+ goto out;
+ ret = is_valid_bcd(bcdVersion);
+ if (ret)
+ goto out;
+
+ gi->bcd_webusb_version = bcdVersion;
+ ret = len;
+
+out:
+ mutex_unlock(&gi->lock);
+
+ return ret;
+}
+
+static ssize_t webusb_bVendorCode_show(struct config_item *item, char *page)
+{
+ return sysfs_emit(page, "0x%02x\n",
+ webusb_item_to_gadget_info(item)->b_webusb_vendor_code);
+}
+
+static ssize_t webusb_bVendorCode_store(struct config_item *item,
+ const char *page, size_t len)
+{
+ struct gadget_info *gi = webusb_item_to_gadget_info(item);
+ int ret;
+ u8 b_vendor_code;
+
+ mutex_lock(&gi->lock);
+ ret = kstrtou8(page, 0, &b_vendor_code);
+ if (!ret) {
+ gi->b_webusb_vendor_code = b_vendor_code;
+ ret = len;
+ }
+ mutex_unlock(&gi->lock);
+
+ return ret;
+}
+
+static ssize_t webusb_landingPage_show(struct config_item *item, char *page)
+{
+ return sysfs_emit(page, "%s\n", webusb_item_to_gadget_info(item)->landing_page);
+}
+
+static ssize_t webusb_landingPage_store(struct config_item *item, const char *page,
+ size_t len)
+{
+ struct gadget_info *gi = webusb_item_to_gadget_info(item);
+ int l;
+
+ l = min(len, sizeof(gi->landing_page));
+ if (page[l - 1] == '\n')
+ --l;
+
+ mutex_lock(&gi->lock);
+ memcpy(gi->landing_page, page, l);
+ mutex_unlock(&gi->lock);
+
+ return len;
+}
+
+CONFIGFS_ATTR(webusb_, use);
+CONFIGFS_ATTR(webusb_, bVendorCode);
+CONFIGFS_ATTR(webusb_, bcdVersion);
+CONFIGFS_ATTR(webusb_, landingPage);
+
+static struct configfs_attribute *webusb_attrs[] = {
+ &webusb_attr_use,
+ &webusb_attr_bcdVersion,
+ &webusb_attr_bVendorCode,
+ &webusb_attr_landingPage,
+ NULL,
+};
+
+static struct config_item_type webusb_type = {
+ .ct_attrs = webusb_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
static inline struct gadget_info *os_desc_item_to_gadget_info(
struct config_item *item)
{
@@ -1341,6 +1472,16 @@ static int configfs_composite_bind(struct usb_gadget *gadget,
gi->cdev.desc.iSerialNumber = s[USB_GADGET_SERIAL_IDX].id;
}

+ if (gi->use_webusb) {
+ cdev->use_webusb = true;
+ cdev->bcd_webusb_version = gi->bcd_webusb_version;
+ cdev->b_webusb_vendor_code = gi->b_webusb_vendor_code;
+ memcpy(cdev->landing_page, gi->landing_page,
+ strnlen(gi->landing_page,
+ min(sizeof(cdev->landing_page),
+ sizeof(gi->landing_page))));
+ }
+
if (gi->use_os_desc) {
cdev->use_os_string = true;
cdev->b_vendor_code = gi->b_vendor_code;
@@ -1605,6 +1746,10 @@ static struct config_group *gadgets_make(
&os_desc_type);
configfs_add_default_group(&gi->os_desc_group, &gi->group);

+ config_group_init_type_name(&gi->webusb_group, "webusb",
+ &webusb_type);
+ configfs_add_default_group(&gi->webusb_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 43ac3fa760db..eb6fac5bbcde 100644
--- a/include/linux/usb/composite.h
+++ b/include/linux/usb/composite.h
@@ -474,6 +474,12 @@ struct usb_composite_dev {
struct usb_configuration *os_desc_config;
unsigned int use_os_string:1;

+ /* WebUSB */
+ u16 bcd_webusb_version;
+ u8 b_webusb_vendor_code;
+ char landing_page[255];
+ unsigned int use_webusb:1;
+
/* private: */
/* internals */
unsigned int suspended:1;
diff --git a/include/uapi/linux/usb/ch9.h b/include/uapi/linux/usb/ch9.h
index 31fcfa084e63..3a36550297bc 100644
--- a/include/uapi/linux/usb/ch9.h
+++ b/include/uapi/linux/usb/ch9.h
@@ -947,6 +947,39 @@ struct usb_ss_container_id_descriptor {

#define USB_DT_USB_SS_CONTN_ID_SIZE 20

+/*
+ * Platform Device Capability descriptor: Defines platform specific device
+ * capabilities
+ */
+#define USB_PLAT_DEV_CAP_TYPE 5
+struct usb_plat_dev_cap_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDevCapabilityType;
+ __u8 bReserved;
+ __u8 UUID[16];
+} __attribute__((packed));
+
+#define USB_DT_USB_PLAT_DEV_CAP_SIZE 20
+
+/*
+ * WebUSB Platform Capability descriptor: A device announces support for the
+ * WebUSB command set by including the following Platform Descriptor in its
+ * Binary Object Store
+ * https://wicg.github.io/webusb/#webusb-platform-capability-descriptor
+ */
+struct usb_webusb_cap_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDevCapabilityType;
+ __u8 bReserved;
+ __u8 UUID[16];
+ __u16 bcdVersion;
+ __u8 bVendorCode;
+ __u8 iLandingPage;
+} __attribute__((packed));
+#define USB_DT_WEBUSB_SIZE (USB_DT_USB_PLAT_DEV_CAP_SIZE + 4)
+
/*
* SuperSpeed Plus USB Capability descriptor: Defines the set of
* SuperSpeed Plus USB specific device level capabilities
--
2.37.2

2023-01-09 18:10:51

by Andrzej Pietrasiewicz

[permalink] [raw]
Subject: Re: [PATCH v3] usb: gadget: add WebUSB landing page support

Hi Jó Ágila Bitsch,

W dniu 1.01.2023 o 17:59, Jó Ágila Bitsch pisze:
>
> There is a custom (non-USB IF) extension to the USB standard:
>
> https://wicg.github.io/webusb/
>
> This specification is published under the W3C Community Contributor
> Agreement, which in particular allows to implement the specification
> without any royalties.
>
> The specification allows USB gadgets to announce an URL to landing
> page and describes a Javascript interface for websites to interact
> with the USB gadget, if the user allows it. It is currently
> supported by Chromium-based browsers, such as Chrome, Edge and
> Opera on all major operating systems including Linux.
>

I envision an "evil" scenario, where you don't own your USB device any more.
Instead you are required to pay a subscription to keep accessing it
because its driver is somewhere on the net. I purposedly have put
evil in quotes, because such a scenario might be considered preferred
by some people. Maybe there are advantages, too, like not having to worry
about correct drivers. Anyway, the kernel is about mechanism, not policy,
so I'm probably in no position to judge such things.

Please see below.

> This patch adds optional support for Linux-based USB gadgets
> wishing to expose such a landing page.
>
> During device enumeration, a host recognizes that the announced
> USB version is at least 2.01, which means, that there are BOS
> descriptors available. The device than announces WebUSB support
> using a platform device capability. This includes a vendor code
> under which the landing page URL can be retrieved using a
> vendor-specific request.
>
> Previously, the BOS descriptors would unconditionally include an
> LPM related descriptor, as BOS descriptors were only ever sent
> when the device was LPM capable. As this is no longer the case,
> this patch puts this descriptor behind a lpm_capable condition.
>
> Usage is modeled after os_desc descriptors:
> echo 1 > webusb/use
> echo "https://www.kernel.org" > webusb/landingPage
>
> lsusb will report the device with the following lines:
> Platform Device Capability:
> bLength 24
> bDescriptorType 16
> bDevCapabilityType 5
> bReserved 0
> PlatformCapabilityUUID {3408b638-09a9-47a0-8bfd-a0768815b665}
> WebUSB:
> bcdVersion 1.00
> bVendorCode 0
> iLandingPage 1 https://www.kernel.org
>
> Signed-off-by: Jó Ágila Bitsch <[email protected]>
> ---
> v2 -> V3: improved commit message to include additional condition in desc_bos as per comment
> by Alan Stern
> V1 -> V2: cleaned up coding style, made URL scheme comparison case insensitive, addressed review
> comments by Alan Stern
> V0 -> V1: use sysfs_emit instead of sprintf and use lock in webusb_bcdVersion_store, addressed review
> comments by Christophe JAILLET
>
> Documentation/ABI/testing/configfs-usb-gadget | 13 ++
> drivers/usb/gadget/composite.c | 95 ++++++++++--
> drivers/usb/gadget/configfs.c | 145 ++++++++++++++++++
> include/linux/usb/composite.h | 6 +
> include/uapi/linux/usb/ch9.h | 33 ++++
> 5 files changed, 282 insertions(+), 10 deletions(-)
>
> diff --git a/Documentation/ABI/testing/configfs-usb-gadget b/Documentation/ABI/testing/configfs-usb-gadget
> index b7943aa7e997..8399e0f0ed1c 100644
> --- a/Documentation/ABI/testing/configfs-usb-gadget
> +++ b/Documentation/ABI/testing/configfs-usb-gadget
> @@ -143,3 +143,16 @@ Description:
> qw_sign an identifier to be reported as "OS String"
> proper
> ============= ===============================================
> +
> +What: /config/usb-gadget/gadget/webusb
> +Date: Dec 2022
> +KernelVersion: 6.2
> +Description:
> + This group contains "WebUSB" extension handling attributes.
> +
> + ============= ===============================================
> + use flag turning "WebUSB" support on/off
> + bcdVersion bcd WebUSB specification version number
> + b_vendor_code one-byte value used for custom per-device
> + landing_page UTF-8 encoded URL of the device's landing page
> + ============= ===============================================
> diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c
> index 403563c06477..9b209e69442d 100644
> --- a/drivers/usb/gadget/composite.c
> +++ b/drivers/usb/gadget/composite.c
> @@ -14,6 +14,7 @@
> #include <linux/device.h>
> #include <linux/utsname.h>
> #include <linux/bitfield.h>
> +#include <linux/uuid.h>
>
> #include <linux/usb/composite.h>
> #include <linux/usb/otg.h>
> @@ -713,14 +714,16 @@ static int bos_desc(struct usb_composite_dev *cdev)
> * A SuperSpeed device shall include the USB2.0 extension descriptor
> * and shall support LPM when operating in USB2.0 HS mode.
> */
> - usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
> - bos->bNumDeviceCaps++;
> - le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
> - usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
> - usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
> - usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
> - usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT |
> - USB_BESL_SUPPORT | besl);
> + if (cdev->gadget->lpm_capable) {
> + usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
> + bos->bNumDeviceCaps++;
> + le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
> + usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
> + usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
> + usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
> + usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT |
> + USB_BESL_SUPPORT | besl);
> + }
>
> /*
> * The Superspeed USB Capability descriptor shall be implemented by all
> @@ -821,6 +824,40 @@ static int bos_desc(struct usb_composite_dev *cdev)
> }
> }
>
> + /*
> + * Following the specifaction at:
> + * https://wicg.github.io/webusb/#webusb-platform-capability-descriptor
> + */
> + if (cdev->use_webusb) {
> + struct usb_webusb_cap_descriptor *webusb_cap;
> + /*
> + * little endian PlatformCapablityUUID for WebUSB:
> + * 3408b638-09a9-47a0-8bfd-a0768815b665
> + */
> + uuid_t webusb_uuid = UUID_INIT(0x38b60834, 0xa909, 0xa047, 0x8b, 0xfd, 0xa0, 0x76,
> + 0x88, 0x15, 0xb6, 0x65);
> +
> + webusb_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
> + bos->bNumDeviceCaps++;
> +
> + le16_add_cpu(&bos->wTotalLength, USB_DT_WEBUSB_SIZE);
> + webusb_cap->bLength = USB_DT_WEBUSB_SIZE;
> + webusb_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
> + webusb_cap->bDevCapabilityType = USB_PLAT_DEV_CAP_TYPE;
> + webusb_cap->bReserved = 0;
> + export_uuid(webusb_cap->UUID, &webusb_uuid);
> + if (cdev->bcd_webusb_version != 0)
> + webusb_cap->bcdVersion = cpu_to_le16(cdev->bcd_webusb_version);
> + else
> + webusb_cap->bcdVersion = cpu_to_le16(0x0100);
> +
> + webusb_cap->bVendorCode = cdev->b_webusb_vendor_code;
> + if (strnlen(cdev->landing_page, sizeof(cdev->landing_page)) > 0)
> + webusb_cap->iLandingPage = 1;
> + else
> + webusb_cap->iLandingPage = 0;
> + }
> +
> return le16_to_cpu(bos->wTotalLength);
> }
>
> @@ -1744,7 +1781,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
> cdev->desc.bcdUSB = cpu_to_le16(0x0210);
> }
> } else {
> - if (gadget->lpm_capable)
> + if (gadget->lpm_capable || cdev->use_webusb)
> cdev->desc.bcdUSB = cpu_to_le16(0x0201);
> else
> cdev->desc.bcdUSB = cpu_to_le16(0x0200);
> @@ -1779,7 +1816,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
> break;
> case USB_DT_BOS:
> if (gadget_is_superspeed(gadget) ||
> - gadget->lpm_capable) {
> + gadget->lpm_capable || cdev->use_webusb) {
> value = bos_desc(cdev);
> value = min(w_length, (u16) value);
> }
> @@ -2013,6 +2050,44 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
> goto check_value;
> }
>
> + /*
> + * webusb descriptor handling, following:
> + * https://wicg.github.io/webusb/#device-requests
> + */
> + if (cdev->use_webusb &&
> + (ctrl->bRequestType & USB_TYPE_VENDOR) &&
> + ctrl->wIndex == /* WEBUSB_GET_URL*/ 2 &&
> + ctrl->bRequest == cdev->b_webusb_vendor_code) {
> + /*
> + * specification of the url descriptor in WebUSB:
> + * https://wicg.github.io/webusb/#webusb-descriptors
> + */
> + u8 *buf;
> + unsigned int landing_page_length;
> +
> + buf = cdev->req->buf;
> + buf[1] = /* WEBUSB_URL*/ 3;

Why not #define WEBUSB_URL?

> +
> + landing_page_length = strnlen(cdev->landing_page, 252);
> + if (strncasecmp("https://", cdev->landing_page, 8) == 0) {

Maybe it's me, but it would be easier for me to understand why the "8" if the
comparison was in reverse order of arguments, like this:

strncasecmp(cdev->landing_page, "https://", 8)

because we want the entirety of "https://" compared, not its substring, so the
limit kind of applies to the landing_page in the first place.
Maybe the "8" (and "7" below) can be #define'd, too?

> + buf[2] = 0x01;

What is this magic 0x01?

> + memcpy(buf+3, cdev->landing_page+8, landing_page_length-8);

spaces around arithmetic operators?

> + buf[0] = landing_page_length - 8 + 3;
> + } else if (strncasecmp("http://", cdev->landing_page, 7) == 0) {
> + buf[2] = 0x00;

Magic 0x00?

> + memcpy(buf+3, cdev->landing_page+7, landing_page_length-7);
> + buf[0] = landing_page_length - 7 + 3;
> + } else {
> + buf[2] = 0xFF;

Magic 0xFF? (plus I'd expect lowercase hex digits).

There's "URL Prefixes" table in 4.3.1 URL Descriptor. Why not define
URL_PREFIX_HTTP/HTTPS/NONE?

> + memcpy(buf+3, cdev->landing_page, landing_page_length);
> + buf[0] = landing_page_length + 3;
> + }
> +
> + value = buf[0];
> +
> + goto check_value;
> + }
> +
> VDBG(cdev,
> "non-core control req%02x.%02x v%04x i%04x l%d\n",
> ctrl->bRequestType, ctrl->bRequest,
> diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c
> index 96121d1c8df4..2b7f01a9efff 100644
> --- a/drivers/usb/gadget/configfs.c
> +++ b/drivers/usb/gadget/configfs.c
> @@ -39,6 +39,7 @@ struct gadget_info {
> struct config_group configs_group;
> struct config_group strings_group;
> struct config_group os_desc_group;
> + struct config_group webusb_group;
>
> struct mutex lock;
> struct usb_gadget_strings *gstrings[MAX_USB_STRING_LANGS + 1];
> @@ -50,6 +51,11 @@ struct gadget_info {
> bool use_os_desc;
> char b_vendor_code;
> char qw_sign[OS_STRING_QW_SIGN_LEN];
> + bool use_webusb;
> + u16 bcd_webusb_version;
> + u8 b_webusb_vendor_code;
> + char landing_page[255];

A #define for the size?

> +
> spinlock_t spinlock;
> bool unbind;
> };
> @@ -780,6 +786,131 @@ static void gadget_strings_attr_release(struct config_item *item)
> USB_CONFIG_STRING_RW_OPS(gadget_strings);
> USB_CONFIG_STRINGS_LANG(gadget_strings, gadget_info);
>
> +static inline struct gadget_info *webusb_item_to_gadget_info(
> + struct config_item *item)
> +{
> + return container_of(to_config_group(item),
> + struct gadget_info, webusb_group);
> +}
> +
> +static ssize_t webusb_use_show(struct config_item *item, char *page)
> +{
> + return sysfs_emit(page, "%d\n",
> + webusb_item_to_gadget_info(item)->use_webusb);
> +}
> +
> +static ssize_t webusb_use_store(struct config_item *item, const char *page,
> + size_t len)
> +{
> + struct gadget_info *gi = webusb_item_to_gadget_info(item);
> + int ret;
> + bool use;
> +
> + mutex_lock(&gi->lock);
> + ret = kstrtobool(page, &use);
> + if (!ret) {
> + gi->use_webusb = use;
> + ret = len;
> + }
> + mutex_unlock(&gi->lock);
> +
> + return ret;
> +}
> +
> +static ssize_t webusb_bcdVersion_show(struct config_item *item, char *page)
> +{
> + return sysfs_emit(page, "0x%04x\n",
> + webusb_item_to_gadget_info(item)->bcd_webusb_version);
> +}
> +
> +static ssize_t webusb_bcdVersion_store(struct config_item *item,
> + const char *page, size_t len)
> +{
> + struct gadget_info *gi = webusb_item_to_gadget_info(item);
> + u16 bcdVersion;
> + int ret;
> +
> + mutex_lock(&gi->lock);
> + ret = kstrtou16(page, 0, &bcdVersion);
> + if (ret)
> + goto out;
> + ret = is_valid_bcd(bcdVersion);
> + if (ret)
> + goto out;
> +
> + gi->bcd_webusb_version = bcdVersion;
> + ret = len;
> +
> +out:
> + mutex_unlock(&gi->lock);
> +
> + return ret;
> +}
> +
> +static ssize_t webusb_bVendorCode_show(struct config_item *item, char *page)
> +{
> + return sysfs_emit(page, "0x%02x\n",
> + webusb_item_to_gadget_info(item)->b_webusb_vendor_code);
> +}
> +
> +static ssize_t webusb_bVendorCode_store(struct config_item *item,
> + const char *page, size_t len)
> +{
> + struct gadget_info *gi = webusb_item_to_gadget_info(item);
> + int ret;
> + u8 b_vendor_code;
> +
> + mutex_lock(&gi->lock);
> + ret = kstrtou8(page, 0, &b_vendor_code);
> + if (!ret) {
> + gi->b_webusb_vendor_code = b_vendor_code;
> + ret = len;
> + }
> + mutex_unlock(&gi->lock);
> +
> + return ret;
> +}
> +
> +static ssize_t webusb_landingPage_show(struct config_item *item, char *page)
> +{
> + return sysfs_emit(page, "%s\n", webusb_item_to_gadget_info(item)->landing_page);
> +}
> +
> +static ssize_t webusb_landingPage_store(struct config_item *item, const char *page,
> + size_t len)
> +{
> + struct gadget_info *gi = webusb_item_to_gadget_info(item);
> + int l;
> +
> + l = min(len, sizeof(gi->landing_page));
> + if (page[l - 1] == '\n')
> + --l;
> +
> + mutex_lock(&gi->lock);
> + memcpy(gi->landing_page, page, l);
> + mutex_unlock(&gi->lock);
> +
> + return len;
> +}
> +
> +CONFIGFS_ATTR(webusb_, use);
> +CONFIGFS_ATTR(webusb_, bVendorCode);
> +CONFIGFS_ATTR(webusb_, bcdVersion);
> +CONFIGFS_ATTR(webusb_, landingPage);
> +
> +static struct configfs_attribute *webusb_attrs[] = {
> + &webusb_attr_use,
> + &webusb_attr_bcdVersion,
> + &webusb_attr_bVendorCode,
> + &webusb_attr_landingPage,
> + NULL,
> +};
> +
> +static struct config_item_type webusb_type = {
> + .ct_attrs = webusb_attrs,
> + .ct_owner = THIS_MODULE,
> +};
> +
> static inline struct gadget_info *os_desc_item_to_gadget_info(
> struct config_item *item)
> {
> @@ -1341,6 +1472,16 @@ static int configfs_composite_bind(struct usb_gadget *gadget,
> gi->cdev.desc.iSerialNumber = s[USB_GADGET_SERIAL_IDX].id;
> }
>
> + if (gi->use_webusb) {
> + cdev->use_webusb = true;
> + cdev->bcd_webusb_version = gi->bcd_webusb_version;
> + cdev->b_webusb_vendor_code = gi->b_webusb_vendor_code;
> + memcpy(cdev->landing_page, gi->landing_page,
> + strnlen(gi->landing_page,
> + min(sizeof(cdev->landing_page),
> + sizeof(gi->landing_page))));

checkpatch now allows 100 colums. Plus strnlen() looks indented too far to the
right.

The sizeofs are both 255. Are they ever expected to be different? Maybe not?
Then a #defined constant ensures they are equal. Then there's no need to find
a minimum among equal values.

Regards,

Andrzej

> + }
> +
> if (gi->use_os_desc) {
> cdev->use_os_string = true;
> cdev->b_vendor_code = gi->b_vendor_code;
> @@ -1605,6 +1746,10 @@ static struct config_group *gadgets_make(
> &os_desc_type);
> configfs_add_default_group(&gi->os_desc_group, &gi->group);
>
> + config_group_init_type_name(&gi->webusb_group, "webusb",
> + &webusb_type);
> + configfs_add_default_group(&gi->webusb_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 43ac3fa760db..eb6fac5bbcde 100644
> --- a/include/linux/usb/composite.h
> +++ b/include/linux/usb/composite.h
> @@ -474,6 +474,12 @@ struct usb_composite_dev {
> struct usb_configuration *os_desc_config;
> unsigned int use_os_string:1;
>
> + /* WebUSB */
> + u16 bcd_webusb_version;
> + u8 b_webusb_vendor_code;
> + char landing_page[255];
> + unsigned int use_webusb:1;
> +
> /* private: */
> /* internals */
> unsigned int suspended:1;
> diff --git a/include/uapi/linux/usb/ch9.h b/include/uapi/linux/usb/ch9.h
> index 31fcfa084e63..3a36550297bc 100644
> --- a/include/uapi/linux/usb/ch9.h
> +++ b/include/uapi/linux/usb/ch9.h
> @@ -947,6 +947,39 @@ struct usb_ss_container_id_descriptor {
>
> #define USB_DT_USB_SS_CONTN_ID_SIZE 20
>
> +/*
> + * Platform Device Capability descriptor: Defines platform specific device
> + * capabilities
> + */
> +#define USB_PLAT_DEV_CAP_TYPE 5
> +struct usb_plat_dev_cap_descriptor {
> + __u8 bLength;
> + __u8 bDescriptorType;
> + __u8 bDevCapabilityType;
> + __u8 bReserved;
> + __u8 UUID[16];
> +} __attribute__((packed));
> +
> +#define USB_DT_USB_PLAT_DEV_CAP_SIZE 20
> +
> +/*
> + * WebUSB Platform Capability descriptor: A device announces support for the
> + * WebUSB command set by including the following Platform Descriptor in its
> + * Binary Object Store
> + * https://wicg.github.io/webusb/#webusb-platform-capability-descriptor
> + */
> +struct usb_webusb_cap_descriptor {
> + __u8 bLength;
> + __u8 bDescriptorType;
> + __u8 bDevCapabilityType;
> + __u8 bReserved;
> + __u8 UUID[16];
> + __u16 bcdVersion;
> + __u8 bVendorCode;
> + __u8 iLandingPage;
> +} __attribute__((packed));
> +#define USB_DT_WEBUSB_SIZE (USB_DT_USB_PLAT_DEV_CAP_SIZE + 4)
> +
> /*
> * SuperSpeed Plus USB Capability descriptor: Defines the set of
> * SuperSpeed Plus USB specific device level capabilities

2023-01-09 23:49:37

by Jó Ágila Bitsch

[permalink] [raw]
Subject: Re: [PATCH v3] usb: gadget: add WebUSB landing page support

Hi Andrzej,

> > The specification allows USB gadgets to announce an URL to landing
> > page and describes a Javascript interface for websites to interact
> > with the USB gadget, if the user allows it. It is currently
> > supported by Chromium-based browsers, such as Chrome, Edge and
> > Opera on all major operating systems including Linux.
> >
>
> I envision an "evil" scenario, where you don't own your USB device any more.
> Instead you are required to pay a subscription to keep accessing it
> because its driver is somewhere on the net. I purposedly have put
> evil in quotes, because such a scenario might be considered preferred
> by some people. Maybe there are advantages, too, like not having to worry
> about correct drivers. Anyway, the kernel is about mechanism, not policy,
> so I'm probably in no position to judge such things.

Unfortunately, the "evil" devices you envision very much already exist.

When I was still working at university in the early 2010s, we were
working with a mobile EEG headset (>1000USD) with a USB receiver
dongle that would present itself as a USB HID device, but the
transferred frames would be AES encrypted, with the key based on a
proprietary transformation of the serial number of the device. At
first the library that decoded it was available as a binary blob for a
one-time payment, where you paid a hefty premium if you wanted access
to the raw readings. Later the manufacturer changed their business
model to subscription based, where the data had to flow through their
cloud service - with a recurring license fee. I don't know what
happened afterwards, as I left university.
This all was implemented based on libusb back then before people even
thought of WebUSB.

I'm sure there are other examples.

So, "evil" devices are already real and don't depend on WebUSB. The
motivating example "Web Drivers" from the specs
(https://wicg.github.io/webusb/#app-drivers) might potentially lead to
companies asking for subscription fees for their Web Driver, but as
long as there is no strong encryption in place, everyone would be free
to write an alternative driver. The mechanisms would not at all
preclude you from writing your own kernel or userspace driver using
e.g. libusb. A kernel driver would block access from the browser
though, as the browser cannot detach the kernel driver. (No such
command is currently defined in WebUSB.) The JS implementation might
even guide you to understand aspects of the protocol used.

Citing from the motivating example in the design specs
(https://wicg.github.io/webusb/#app-updates-and-diagnostics):

> The landing page provides a way for the device manufacturer to direct the user to the right part of their website for help with their device.

The landing page mechanism in WebUSB improves discoverability for
devices, as Chrome and also lsusb directly point you to a webpage,
where the manufacturer or firmware producer can give more information
about the device. This webpage does not necessarily need to use a
WebUSB JS driver itself to access the device, but might just as well
point you to some git repository, where the hardware design and
firmware source are available.

For what it's worth, the pick-up of WebUSB I've seen has been mostly
in the maker scene with arduino, micro:bit and similar embedded
devices, where devices provide at least two interfaces: One for the OS
to claim and one for the Browser to claim.

Anyways, my biased opinion is that I would very much like a device to
tell me where I can find support documentation, updates and help also
when the device only implements standard interfaces that have kernel
drivers. I don't think that this URL will lead to more devices you can
only access with a subscription.

> Please see below.

Thanks for your excellent feedback. I answered inline.

> > This patch adds optional support for Linux-based USB gadgets
> > wishing to expose such a landing page.
> >
> > During device enumeration, a host recognizes that the announced
> > USB version is at least 2.01, which means, that there are BOS
> > descriptors available. The device than announces WebUSB support
> > using a platform device capability. This includes a vendor code
> > under which the landing page URL can be retrieved using a
> > vendor-specific request.
> >
> > Previously, the BOS descriptors would unconditionally include an
> > LPM related descriptor, as BOS descriptors were only ever sent
> > when the device was LPM capable. As this is no longer the case,
> > this patch puts this descriptor behind a lpm_capable condition.
> >
> > Usage is modeled after os_desc descriptors:
> > echo 1 > webusb/use
> > echo "https://www.kernel.org" > webusb/landingPage
> >
> > lsusb will report the device with the following lines:
> > Platform Device Capability:
> > bLength 24
> > bDescriptorType 16
> > bDevCapabilityType 5
> > bReserved 0
> > PlatformCapabilityUUID {3408b638-09a9-47a0-8bfd-a0768815b665}
> > WebUSB:
> > bcdVersion 1.00
> > bVendorCode 0
> > iLandingPage 1 https://www.kernel.org
> >
> > Signed-off-by: Jó Ágila Bitsch <[email protected]>
> > ---
> > v2 -> V3: improved commit message to include additional condition in desc_bos as per comment
> > by Alan Stern
> > V1 -> V2: cleaned up coding style, made URL scheme comparison case insensitive, addressed review
> > comments by Alan Stern
> > V0 -> V1: use sysfs_emit instead of sprintf and use lock in webusb_bcdVersion_store, addressed review
> > comments by Christophe JAILLET
> >
> > Documentation/ABI/testing/configfs-usb-gadget | 13 ++
> > drivers/usb/gadget/composite.c | 95 ++++++++++--
> > drivers/usb/gadget/configfs.c | 145 ++++++++++++++++++
> > include/linux/usb/composite.h | 6 +
> > include/uapi/linux/usb/ch9.h | 33 ++++
> > 5 files changed, 282 insertions(+), 10 deletions(-)
> >
> > diff --git a/Documentation/ABI/testing/configfs-usb-gadget b/Documentation/ABI/testing/configfs-usb-gadget
> > index b7943aa7e997..8399e0f0ed1c 100644
> > --- a/Documentation/ABI/testing/configfs-usb-gadget
> > +++ b/Documentation/ABI/testing/configfs-usb-gadget
> > @@ -143,3 +143,16 @@ Description:
> > qw_sign an identifier to be reported as "OS String"
> > proper
> > ============= ===============================================
> > +
> > +What: /config/usb-gadget/gadget/webusb
> > +Date: Dec 2022
> > +KernelVersion: 6.2
> > +Description:
> > + This group contains "WebUSB" extension handling attributes.
> > +
> > + ============= ===============================================
> > + use flag turning "WebUSB" support on/off
> > + bcdVersion bcd WebUSB specification version number
> > + b_vendor_code one-byte value used for custom per-device
> > + landing_page UTF-8 encoded URL of the device's landing page
> > + ============= ===============================================
> > diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c
> > index 403563c06477..9b209e69442d 100644
> > --- a/drivers/usb/gadget/composite.c
> > +++ b/drivers/usb/gadget/composite.c
> > @@ -14,6 +14,7 @@
> > #include <linux/device.h>
> > #include <linux/utsname.h>
> > #include <linux/bitfield.h>
> > +#include <linux/uuid.h>
> >
> > #include <linux/usb/composite.h>
> > #include <linux/usb/otg.h>
> > @@ -713,14 +714,16 @@ static int bos_desc(struct usb_composite_dev *cdev)
> > * A SuperSpeed device shall include the USB2.0 extension descriptor
> > * and shall support LPM when operating in USB2.0 HS mode.
> > */
> > - usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
> > - bos->bNumDeviceCaps++;
> > - le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
> > - usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
> > - usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
> > - usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
> > - usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT |
> > - USB_BESL_SUPPORT | besl);
> > + if (cdev->gadget->lpm_capable) {
> > + usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
> > + bos->bNumDeviceCaps++;
> > + le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
> > + usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
> > + usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
> > + usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
> > + usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT |
> > + USB_BESL_SUPPORT | besl);
> > + }
> >
> > /*
> > * The Superspeed USB Capability descriptor shall be implemented by all
> > @@ -821,6 +824,40 @@ static int bos_desc(struct usb_composite_dev *cdev)
> > }
> > }
> >
> > + /*
> > + * Following the specifaction at:
> > + * https://wicg.github.io/webusb/#webusb-platform-capability-descriptor
> > + */
> > + if (cdev->use_webusb) {
> > + struct usb_webusb_cap_descriptor *webusb_cap;
> > + /*
> > + * little endian PlatformCapablityUUID for WebUSB:
> > + * 3408b638-09a9-47a0-8bfd-a0768815b665
> > + */
> > + uuid_t webusb_uuid = UUID_INIT(0x38b60834, 0xa909, 0xa047, 0x8b, 0xfd, 0xa0, 0x76,
> > + 0x88, 0x15, 0xb6, 0x65);
> > +
> > + webusb_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
> > + bos->bNumDeviceCaps++;
> > +
> > + le16_add_cpu(&bos->wTotalLength, USB_DT_WEBUSB_SIZE);
> > + webusb_cap->bLength = USB_DT_WEBUSB_SIZE;
> > + webusb_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
> > + webusb_cap->bDevCapabilityType = USB_PLAT_DEV_CAP_TYPE;
> > + webusb_cap->bReserved = 0;
> > + export_uuid(webusb_cap->UUID, &webusb_uuid);
> > + if (cdev->bcd_webusb_version != 0)
> > + webusb_cap->bcdVersion = cpu_to_le16(cdev->bcd_webusb_version);
> > + else
> > + webusb_cap->bcdVersion = cpu_to_le16(0x0100);
> > +
> > + webusb_cap->bVendorCode = cdev->b_webusb_vendor_code;
> > + if (strnlen(cdev->landing_page, sizeof(cdev->landing_page)) > 0)
> > + webusb_cap->iLandingPage = 1;
> > + else
> > + webusb_cap->iLandingPage = 0;
> > + }
> > +
> > return le16_to_cpu(bos->wTotalLength);
> > }
> >
> > @@ -1744,7 +1781,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
> > cdev->desc.bcdUSB = cpu_to_le16(0x0210);
> > }
> > } else {
> > - if (gadget->lpm_capable)
> > + if (gadget->lpm_capable || cdev->use_webusb)
> > cdev->desc.bcdUSB = cpu_to_le16(0x0201);
> > else
> > cdev->desc.bcdUSB = cpu_to_le16(0x0200);
> > @@ -1779,7 +1816,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
> > break;
> > case USB_DT_BOS:
> > if (gadget_is_superspeed(gadget) ||
> > - gadget->lpm_capable) {
> > + gadget->lpm_capable || cdev->use_webusb) {
> > value = bos_desc(cdev);
> > value = min(w_length, (u16) value);
> > }
> > @@ -2013,6 +2050,44 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
> > goto check_value;
> > }
> >
> > + /*
> > + * webusb descriptor handling, following:
> > + * https://wicg.github.io/webusb/#device-requests
> > + */
> > + if (cdev->use_webusb &&
> > + (ctrl->bRequestType & USB_TYPE_VENDOR) &&
> > + ctrl->wIndex == /* WEBUSB_GET_URL*/ 2 &&
> > + ctrl->bRequest == cdev->b_webusb_vendor_code) {
> > + /*
> > + * specification of the url descriptor in WebUSB:
> > + * https://wicg.github.io/webusb/#webusb-descriptors
> > + */
> > + u8 *buf;
> > + unsigned int landing_page_length;
> > +
> > + buf = cdev->req->buf;
> > + buf[1] = /* WEBUSB_URL*/ 3;
>
> Why not #define WEBUSB_URL?

in a previous iteration, I had that exact #define in place, right
above. However, another reviewer found it pointless. Maybe I should
instead put the define into include/uapi/linux/usb/ch9.h

>
> > +
> > + landing_page_length = strnlen(cdev->landing_page, 252);
> > + if (strncasecmp("https://", cdev->landing_page, 8) == 0) {
>
> Maybe it's me, but it would be easier for me to understand why the "8" if the
> comparison was in reverse order of arguments, like this:
>
> strncasecmp(cdev->landing_page, "https://", 8)

I agree, this makes the 8 clearer.

> because we want the entirety of "https://" compared, not its substring, so the
> limit kind of applies to the landing_page in the first place.
> Maybe the "8" (and "7" below) can be #define'd, too?

depends on the name, but maybe something like

#define WEBUSB_PREFIX_HTTPS "https://"
#define WEBUSB_PREFIX_HTTPS_LENGTH 8
#define WEBUSB_PREFIX_HTTPS_SCHEME_ID 0x01

would work defined with the struct in include/uapi/linux/usb/ch9.h

>
> > + buf[2] = 0x01;
>
> What is this magic 0x01?

The field is specified in
https://wicg.github.io/webusb/#url-descriptor and would be the
WEBUSB_PREFIX_HTTPS_SCHEME_ID from above.
But yes, I should #define this.

>
> > + memcpy(buf+3, cdev->landing_page+8, landing_page_length-8);
>
> spaces around arithmetic operators?

will do


> > + buf[0] = landing_page_length - 8 + 3;
> > + } else if (strncasecmp("http://", cdev->landing_page, 7) == 0) {
> > + buf[2] = 0x00;
>
> Magic 0x00?

This would then be WEBUSB_PREFIX_HTTP_SCHEME_ID

>
> > + memcpy(buf+3, cdev->landing_page+7, landing_page_length-7);
> > + buf[0] = landing_page_length - 7 + 3;
> > + } else {
> > + buf[2] = 0xFF;
>
> Magic 0xFF? (plus I'd expect lowercase hex digits).

This would then be WEBUSB_PREFIX_NONE_SCHEME_ID

> There's "URL Prefixes" table in 4.3.1 URL Descriptor. Why not define
> URL_PREFIX_HTTP/HTTPS/NONE?

will do

> > + memcpy(buf+3, cdev->landing_page, landing_page_length);
> > + buf[0] = landing_page_length + 3;
> > + }
> > +
> > + value = buf[0];
> > +
> > + goto check_value;
> > + }
> > +
> > VDBG(cdev,
> > "non-core control req%02x.%02x v%04x i%04x l%d\n",
> > ctrl->bRequestType, ctrl->bRequest,
> > diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c
> > index 96121d1c8df4..2b7f01a9efff 100644
> > --- a/drivers/usb/gadget/configfs.c
> > +++ b/drivers/usb/gadget/configfs.c
> > @@ -39,6 +39,7 @@ struct gadget_info {
> > struct config_group configs_group;
> > struct config_group strings_group;
> > struct config_group os_desc_group;
> > + struct config_group webusb_group;
> >
> > struct mutex lock;
> > struct usb_gadget_strings *gstrings[MAX_USB_STRING_LANGS + 1];
> > @@ -50,6 +51,11 @@ struct gadget_info {
> > bool use_os_desc;
> > char b_vendor_code;
> > char qw_sign[OS_STRING_QW_SIGN_LEN];
> > + bool use_webusb;
> > + u16 bcd_webusb_version;
> > + u8 b_webusb_vendor_code;
> > + char landing_page[255];
>
> A #define for the size?

will do

>
> > +
> > spinlock_t spinlock;
> > bool unbind;
> > };
> > @@ -780,6 +786,131 @@ static void gadget_strings_attr_release(struct config_item *item)
> > USB_CONFIG_STRING_RW_OPS(gadget_strings);
> > USB_CONFIG_STRINGS_LANG(gadget_strings, gadget_info);
> >
> > +static inline struct gadget_info *webusb_item_to_gadget_info(
> > + struct config_item *item)
> > +{
> > + return container_of(to_config_group(item),
> > + struct gadget_info, webusb_group);
> > +}
> > +
> > +static ssize_t webusb_use_show(struct config_item *item, char *page)
> > +{
> > + return sysfs_emit(page, "%d\n",
> > + webusb_item_to_gadget_info(item)->use_webusb);
> > +}
> > +
> > +static ssize_t webusb_use_store(struct config_item *item, const char *page,
> > + size_t len)
> > +{
> > + struct gadget_info *gi = webusb_item_to_gadget_info(item);
> > + int ret;
> > + bool use;
> > +
> > + mutex_lock(&gi->lock);
> > + ret = kstrtobool(page, &use);
> > + if (!ret) {
> > + gi->use_webusb = use;
> > + ret = len;
> > + }
> > + mutex_unlock(&gi->lock);
> > +
> > + return ret;
> > +}
> > +
> > +static ssize_t webusb_bcdVersion_show(struct config_item *item, char *page)
> > +{
> > + return sysfs_emit(page, "0x%04x\n",
> > + webusb_item_to_gadget_info(item)->bcd_webusb_version);
> > +}
> > +
> > +static ssize_t webusb_bcdVersion_store(struct config_item *item,
> > + const char *page, size_t len)
> > +{
> > + struct gadget_info *gi = webusb_item_to_gadget_info(item);
> > + u16 bcdVersion;
> > + int ret;
> > +
> > + mutex_lock(&gi->lock);
> > + ret = kstrtou16(page, 0, &bcdVersion);
> > + if (ret)
> > + goto out;
> > + ret = is_valid_bcd(bcdVersion);
> > + if (ret)
> > + goto out;
> > +
> > + gi->bcd_webusb_version = bcdVersion;
> > + ret = len;
> > +
> > +out:
> > + mutex_unlock(&gi->lock);
> > +
> > + return ret;
> > +}
> > +
> > +static ssize_t webusb_bVendorCode_show(struct config_item *item, char *page)
> > +{
> > + return sysfs_emit(page, "0x%02x\n",
> > + webusb_item_to_gadget_info(item)->b_webusb_vendor_code);
> > +}
> > +
> > +static ssize_t webusb_bVendorCode_store(struct config_item *item,
> > + const char *page, size_t len)
> > +{
> > + struct gadget_info *gi = webusb_item_to_gadget_info(item);
> > + int ret;
> > + u8 b_vendor_code;
> > +
> > + mutex_lock(&gi->lock);
> > + ret = kstrtou8(page, 0, &b_vendor_code);
> > + if (!ret) {
> > + gi->b_webusb_vendor_code = b_vendor_code;
> > + ret = len;
> > + }
> > + mutex_unlock(&gi->lock);
> > +
> > + return ret;
> > +}
> > +
> > +static ssize_t webusb_landingPage_show(struct config_item *item, char *page)
> > +{
> > + return sysfs_emit(page, "%s\n", webusb_item_to_gadget_info(item)->landing_page);
> > +}
> > +
> > +static ssize_t webusb_landingPage_store(struct config_item *item, const char *page,
> > + size_t len)
> > +{
> > + struct gadget_info *gi = webusb_item_to_gadget_info(item);
> > + int l;
> > +
> > + l = min(len, sizeof(gi->landing_page));
> > + if (page[l - 1] == '\n')
> > + --l;
> > +
> > + mutex_lock(&gi->lock);
> > + memcpy(gi->landing_page, page, l);
> > + mutex_unlock(&gi->lock);
> > +
> > + return len;
> > +}
> > +
> > +CONFIGFS_ATTR(webusb_, use);
> > +CONFIGFS_ATTR(webusb_, bVendorCode);
> > +CONFIGFS_ATTR(webusb_, bcdVersion);
> > +CONFIGFS_ATTR(webusb_, landingPage);
> > +
> > +static struct configfs_attribute *webusb_attrs[] = {
> > + &webusb_attr_use,
> > + &webusb_attr_bcdVersion,
> > + &webusb_attr_bVendorCode,
> > + &webusb_attr_landingPage,
> > + NULL,
> > +};
> > +
> > +static struct config_item_type webusb_type = {
> > + .ct_attrs = webusb_attrs,
> > + .ct_owner = THIS_MODULE,
> > +};
> > +
> > static inline struct gadget_info *os_desc_item_to_gadget_info(
> > struct config_item *item)
> > {
> > @@ -1341,6 +1472,16 @@ static int configfs_composite_bind(struct usb_gadget *gadget,
> > gi->cdev.desc.iSerialNumber = s[USB_GADGET_SERIAL_IDX].id;
> > }
> >
> > + if (gi->use_webusb) {
> > + cdev->use_webusb = true;
> > + cdev->bcd_webusb_version = gi->bcd_webusb_version;
> > + cdev->b_webusb_vendor_code = gi->b_webusb_vendor_code;
> > + memcpy(cdev->landing_page, gi->landing_page,
> > + strnlen(gi->landing_page,
> > + min(sizeof(cdev->landing_page),
> > + sizeof(gi->landing_page))));
>
> checkpatch now allows 100 colums. Plus strnlen() looks indented too far to the
> right.

I will check this.

>
> The sizeofs are both 255. Are they ever expected to be different? Maybe not?
> Then a #defined constant ensures they are equal. Then there's no need to find
> a minimum among equal values.

Technically, if the url has the empty scheme, it can only be 252 bytes
long, since the maximum descriptor length is 255 because bLength is a
single byte, but 3 bytes are in the header of this descriptor, leaving
252 for the string.

However, if the landing page scheme is "https://", the maximum length
of this string could actually be 255 - 3 /*header*/ + 8 /* the length
of the prefix, which is stripped */ => 260 bytes.

I need to rework the validation here a bit. I would like to avoid for
userspace to explicitly set the scheme constants directly, as just
piping in the string seems so much more ergonomic.

>
> Regards,
>
> Andrzej

Thanks a lot for your review!

I hope my response to your initial concerns was not too long and windy.

I will post an updated version of my patch within the next few days.

Regards,



>
> > + }
> > +
> > if (gi->use_os_desc) {
> > cdev->use_os_string = true;
> > cdev->b_vendor_code = gi->b_vendor_code;
> > @@ -1605,6 +1746,10 @@ static struct config_group *gadgets_make(
> > &os_desc_type);
> > configfs_add_default_group(&gi->os_desc_group, &gi->group);
> >
> > + config_group_init_type_name(&gi->webusb_group, "webusb",
> > + &webusb_type);
> > + configfs_add_default_group(&gi->webusb_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 43ac3fa760db..eb6fac5bbcde 100644
> > --- a/include/linux/usb/composite.h
> > +++ b/include/linux/usb/composite.h
> > @@ -474,6 +474,12 @@ struct usb_composite_dev {
> > struct usb_configuration *os_desc_config;
> > unsigned int use_os_string:1;
> >
> > + /* WebUSB */
> > + u16 bcd_webusb_version;
> > + u8 b_webusb_vendor_code;
> > + char landing_page[255];
> > + unsigned int use_webusb:1;
> > +
> > /* private: */
> > /* internals */
> > unsigned int suspended:1;
> > diff --git a/include/uapi/linux/usb/ch9.h b/include/uapi/linux/usb/ch9.h
> > index 31fcfa084e63..3a36550297bc 100644
> > --- a/include/uapi/linux/usb/ch9.h
> > +++ b/include/uapi/linux/usb/ch9.h
> > @@ -947,6 +947,39 @@ struct usb_ss_container_id_descriptor {
> >
> > #define USB_DT_USB_SS_CONTN_ID_SIZE 20
> >
> > +/*
> > + * Platform Device Capability descriptor: Defines platform specific device
> > + * capabilities
> > + */
> > +#define USB_PLAT_DEV_CAP_TYPE 5
> > +struct usb_plat_dev_cap_descriptor {
> > + __u8 bLength;
> > + __u8 bDescriptorType;
> > + __u8 bDevCapabilityType;
> > + __u8 bReserved;
> > + __u8 UUID[16];
> > +} __attribute__((packed));
> > +
> > +#define USB_DT_USB_PLAT_DEV_CAP_SIZE 20
> > +
> > +/*
> > + * WebUSB Platform Capability descriptor: A device announces support for the
> > + * WebUSB command set by including the following Platform Descriptor in its
> > + * Binary Object Store
> > + * https://wicg.github.io/webusb/#webusb-platform-capability-descriptor
> > + */
> > +struct usb_webusb_cap_descriptor {
> > + __u8 bLength;
> > + __u8 bDescriptorType;
> > + __u8 bDevCapabilityType;
> > + __u8 bReserved;
> > + __u8 UUID[16];
> > + __u16 bcdVersion;
> > + __u8 bVendorCode;
> > + __u8 iLandingPage;
> > +} __attribute__((packed));
> > +#define USB_DT_WEBUSB_SIZE (USB_DT_USB_PLAT_DEV_CAP_SIZE + 4)
> > +
> > /*
> > * SuperSpeed Plus USB Capability descriptor: Defines the set of
> > * SuperSpeed Plus USB specific device level capabilities
>

2023-01-10 14:06:51

by Andrzej Pietrasiewicz

[permalink] [raw]
Subject: Re: [PATCH v3] usb: gadget: add WebUSB landing page support

Hi Jó,

W dniu 10.01.2023 o 00:19, Jó Ágila Bitsch pisze:
> Hi Andrzej,
>
>>> The specification allows USB gadgets to announce an URL to landing
>>> page and describes a Javascript interface for websites to interact
>>> with the USB gadget, if the user allows it. It is currently
>>> supported by Chromium-based browsers, such as Chrome, Edge and
>>> Opera on all major operating systems including Linux.
>>>
>>
>> I envision an "evil" scenario, where you don't own your USB device any more.
>> Instead you are required to pay a subscription to keep accessing it
>> because its driver is somewhere on the net. I purposedly have put
>> evil in quotes, because such a scenario might be considered preferred
>> by some people. Maybe there are advantages, too, like not having to worry
>> about correct drivers. Anyway, the kernel is about mechanism, not policy,
>> so I'm probably in no position to judge such things.
>
> Unfortunately, the "evil" devices you envision very much already exist.
>
> When I was still working at university in the early 2010s, we were
> working with a mobile EEG headset (>1000USD) with a USB receiver
> dongle that would present itself as a USB HID device, but the
> transferred frames would be AES encrypted, with the key based on a
> proprietary transformation of the serial number of the device. At
> first the library that decoded it was available as a binary blob for a
> one-time payment, where you paid a hefty premium if you wanted access
> to the raw readings. Later the manufacturer changed their business
> model to subscription based, where the data had to flow through their
> cloud service - with a recurring license fee. I don't know what
> happened afterwards, as I left university.
> This all was implemented based on libusb back then before people even
> thought of WebUSB.
>

Thanks for honestly acknowledging their existence. It confirms that
what I expressed was not a "conspiracy theory", but a "conspiracy
practice" :D

> I'm sure there are other examples.
>
> So, "evil" devices are already real and don't depend on WebUSB. The
> motivating example "Web Drivers" from the specs
> (https://wicg.github.io/webusb/#app-drivers) might potentially lead to
> companies asking for subscription fees for their Web Driver, but as
> long as there is no strong encryption in place, everyone would be free
> to write an alternative driver. The mechanisms would not at all
> preclude you from writing your own kernel or userspace driver using
> e.g. libusb. A kernel driver would block access from the browser
> though, as the browser cannot detach the kernel driver. (No such
> command is currently defined in WebUSB.) The JS implementation might
> even guide you to understand aspects of the protocol used.
>
> Citing from the motivating example in the design specs
> (https://wicg.github.io/webusb/#app-updates-and-diagnostics):
>
>> The landing page provides a way for the device manufacturer to direct the user to the right part of their website for help with their device.
>
> The landing page mechanism in WebUSB improves discoverability for
> devices, as Chrome and also lsusb directly point you to a webpage,
> where the manufacturer or firmware producer can give more information
> about the device. This webpage does not necessarily need to use a
> WebUSB JS driver itself to access the device, but might just as well
> point you to some git repository, where the hardware design and
> firmware source are available

I'm wondering why e.g. Firefox does not implement this? Chicken-and-egg
dilemma (no OS support at kernel level, so let's refrain from using it,
but then there's too few users, so let's not add OS support - this
circular depencency can be broken by applying your patch) or something
else?

>
> For what it's worth, the pick-up of WebUSB I've seen has been mostly
> in the maker scene with arduino, micro:bit and similar embedded
> devices, where devices provide at least two interfaces: One for the OS
> to claim and one for the Browser to claim.
>
> Anyways, my biased opinion is that I would very much like a device to
> tell me where I can find support documentation, updates and help also
> when the device only implements standard interfaces that have kernel
> drivers. I don't think that this URL will lead to more devices you can
> only access with a subscription.
>
>> Please see below.
>
> Thanks for your excellent feedback. I answered inline.
>
>>> This patch adds optional support for Linux-based USB gadgets
>>> wishing to expose such a landing page.
>>>
>>> During device enumeration, a host recognizes that the announced
>>> USB version is at least 2.01, which means, that there are BOS
>>> descriptors available. The device than announces WebUSB support
>>> using a platform device capability. This includes a vendor code
>>> under which the landing page URL can be retrieved using a
>>> vendor-specific request.
>>>
>>> Previously, the BOS descriptors would unconditionally include an
>>> LPM related descriptor, as BOS descriptors were only ever sent
>>> when the device was LPM capable. As this is no longer the case,
>>> this patch puts this descriptor behind a lpm_capable condition.
>>>
>>> Usage is modeled after os_desc descriptors:
>>> echo 1 > webusb/use
>>> echo "https://www.kernel.org" > webusb/landingPage
>>>
>>> lsusb will report the device with the following lines:
>>> Platform Device Capability:
>>> bLength 24
>>> bDescriptorType 16
>>> bDevCapabilityType 5
>>> bReserved 0
>>> PlatformCapabilityUUID {3408b638-09a9-47a0-8bfd-a0768815b665}
>>> WebUSB:
>>> bcdVersion 1.00
>>> bVendorCode 0
>>> iLandingPage 1 https://www.kernel.org
>>>
>>> Signed-off-by: Jó Ágila Bitsch <[email protected]>
>>> ---
>>> v2 -> V3: improved commit message to include additional condition in desc_bos as per comment
>>> by Alan Stern
>>> V1 -> V2: cleaned up coding style, made URL scheme comparison case insensitive, addressed review
>>> comments by Alan Stern
>>> V0 -> V1: use sysfs_emit instead of sprintf and use lock in webusb_bcdVersion_store, addressed review
>>> comments by Christophe JAILLET
>>>
>>> Documentation/ABI/testing/configfs-usb-gadget | 13 ++
>>> drivers/usb/gadget/composite.c | 95 ++++++++++--
>>> drivers/usb/gadget/configfs.c | 145 ++++++++++++++++++
>>> include/linux/usb/composite.h | 6 +
>>> include/uapi/linux/usb/ch9.h | 33 ++++
>>> 5 files changed, 282 insertions(+), 10 deletions(-)
>>>
>>> diff --git a/Documentation/ABI/testing/configfs-usb-gadget b/Documentation/ABI/testing/configfs-usb-gadget
>>> index b7943aa7e997..8399e0f0ed1c 100644
>>> --- a/Documentation/ABI/testing/configfs-usb-gadget
>>> +++ b/Documentation/ABI/testing/configfs-usb-gadget
>>> @@ -143,3 +143,16 @@ Description:
>>> qw_sign an identifier to be reported as "OS String"
>>> proper
>>> ============= ===============================================
>>> +
>>> +What: /config/usb-gadget/gadget/webusb
>>> +Date: Dec 2022
>>> +KernelVersion: 6.2
>>> +Description:
>>> + This group contains "WebUSB" extension handling attributes.
>>> +
>>> + ============= ===============================================
>>> + use flag turning "WebUSB" support on/off
>>> + bcdVersion bcd WebUSB specification version number
>>> + b_vendor_code one-byte value used for custom per-device
>>> + landing_page UTF-8 encoded URL of the device's landing page
>>> + ============= ===============================================
>>> diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c
>>> index 403563c06477..9b209e69442d 100644
>>> --- a/drivers/usb/gadget/composite.c
>>> +++ b/drivers/usb/gadget/composite.c
>>> @@ -14,6 +14,7 @@
>>> #include <linux/device.h>
>>> #include <linux/utsname.h>
>>> #include <linux/bitfield.h>
>>> +#include <linux/uuid.h>
>>>
>>> #include <linux/usb/composite.h>
>>> #include <linux/usb/otg.h>
>>> @@ -713,14 +714,16 @@ static int bos_desc(struct usb_composite_dev *cdev)
>>> * A SuperSpeed device shall include the USB2.0 extension descriptor
>>> * and shall support LPM when operating in USB2.0 HS mode.
>>> */
>>> - usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
>>> - bos->bNumDeviceCaps++;
>>> - le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
>>> - usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
>>> - usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
>>> - usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
>>> - usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT |
>>> - USB_BESL_SUPPORT | besl);
>>> + if (cdev->gadget->lpm_capable) {
>>> + usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
>>> + bos->bNumDeviceCaps++;
>>> + le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
>>> + usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
>>> + usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
>>> + usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
>>> + usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT |
>>> + USB_BESL_SUPPORT | besl);
>>> + }
>>>
>>> /*
>>> * The Superspeed USB Capability descriptor shall be implemented by all
>>> @@ -821,6 +824,40 @@ static int bos_desc(struct usb_composite_dev *cdev)
>>> }
>>> }
>>>
>>> + /*
>>> + * Following the specifaction at:
>>> + * https://wicg.github.io/webusb/#webusb-platform-capability-descriptor
>>> + */
>>> + if (cdev->use_webusb) {
>>> + struct usb_webusb_cap_descriptor *webusb_cap;
>>> + /*
>>> + * little endian PlatformCapablityUUID for WebUSB:
>>> + * 3408b638-09a9-47a0-8bfd-a0768815b665
>>> + */
>>> + uuid_t webusb_uuid = UUID_INIT(0x38b60834, 0xa909, 0xa047, 0x8b, 0xfd, 0xa0, 0x76,
>>> + 0x88, 0x15, 0xb6, 0x65);
>>> +
>>> + webusb_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
>>> + bos->bNumDeviceCaps++;
>>> +
>>> + le16_add_cpu(&bos->wTotalLength, USB_DT_WEBUSB_SIZE);
>>> + webusb_cap->bLength = USB_DT_WEBUSB_SIZE;
>>> + webusb_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
>>> + webusb_cap->bDevCapabilityType = USB_PLAT_DEV_CAP_TYPE;
>>> + webusb_cap->bReserved = 0;
>>> + export_uuid(webusb_cap->UUID, &webusb_uuid);
>>> + if (cdev->bcd_webusb_version != 0)
>>> + webusb_cap->bcdVersion = cpu_to_le16(cdev->bcd_webusb_version);
>>> + else
>>> + webusb_cap->bcdVersion = cpu_to_le16(0x0100);
>>> +
>>> + webusb_cap->bVendorCode = cdev->b_webusb_vendor_code;
>>> + if (strnlen(cdev->landing_page, sizeof(cdev->landing_page)) > 0)
>>> + webusb_cap->iLandingPage = 1;
>>> + else
>>> + webusb_cap->iLandingPage = 0;
>>> + }
>>> +
>>> return le16_to_cpu(bos->wTotalLength);
>>> }
>>>
>>> @@ -1744,7 +1781,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
>>> cdev->desc.bcdUSB = cpu_to_le16(0x0210);
>>> }
>>> } else {
>>> - if (gadget->lpm_capable)
>>> + if (gadget->lpm_capable || cdev->use_webusb)
>>> cdev->desc.bcdUSB = cpu_to_le16(0x0201);
>>> else
>>> cdev->desc.bcdUSB = cpu_to_le16(0x0200);
>>> @@ -1779,7 +1816,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
>>> break;
>>> case USB_DT_BOS:
>>> if (gadget_is_superspeed(gadget) ||
>>> - gadget->lpm_capable) {
>>> + gadget->lpm_capable || cdev->use_webusb) {
>>> value = bos_desc(cdev);
>>> value = min(w_length, (u16) value);
>>> }
>>> @@ -2013,6 +2050,44 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
>>> goto check_value;
>>> }
>>>
>>> + /*
>>> + * webusb descriptor handling, following:
>>> + * https://wicg.github.io/webusb/#device-requests
>>> + */
>>> + if (cdev->use_webusb &&
>>> + (ctrl->bRequestType & USB_TYPE_VENDOR) &&
>>> + ctrl->wIndex == /* WEBUSB_GET_URL*/ 2 &&
>>> + ctrl->bRequest == cdev->b_webusb_vendor_code) {
>>> + /*
>>> + * specification of the url descriptor in WebUSB:
>>> + * https://wicg.github.io/webusb/#webusb-descriptors
>>> + */
>>> + u8 *buf;
>>> + unsigned int landing_page_length;
>>> +
>>> + buf = cdev->req->buf;
>>> + buf[1] = /* WEBUSB_URL*/ 3;
>>
>> Why not #define WEBUSB_URL?
>
> in a previous iteration, I had that exact #define in place, right
> above. However, another reviewer found it pointless. Maybe I should
> instead put the define into include/uapi/linux/usb/ch9.h
>

Hmm. ch9.h had been called ch9.h for a reason. It corresponded to Chapter 9 of
the USB spec. Adding to it something that by design is outside the spec might
not be what we want. But I don't know.

>>
>>> +
>>> + landing_page_length = strnlen(cdev->landing_page, 252);
>>> + if (strncasecmp("https://", cdev->landing_page, 8) == 0) {
>>
>> Maybe it's me, but it would be easier for me to understand why the "8" if the
>> comparison was in reverse order of arguments, like this:
>>
>> strncasecmp(cdev->landing_page, "https://", 8)
>
> I agree, this makes the 8 clearer.
>
>> because we want the entirety of "https://" compared, not its substring, so the
>> limit kind of applies to the landing_page in the first place.
>> Maybe the "8" (and "7" below) can be #define'd, too?
>
> depends on the name, but maybe something like
>
> #define WEBUSB_PREFIX_HTTPS "https://"
> #define WEBUSB_PREFIX_HTTPS_LENGTH 8
> #define WEBUSB_PREFIX_HTTPS_SCHEME_ID 0x01
>
> would work defined with the struct in include/uapi/linux/usb/ch9.h
>
>>
>>> + buf[2] = 0x01;
>>
>> What is this magic 0x01?
>
> The field is specified in
> https://wicg.github.io/webusb/#url-descriptor and would be the
> WEBUSB_PREFIX_HTTPS_SCHEME_ID from above.
> But yes, I should #define this.
>
>>
>>> + memcpy(buf+3, cdev->landing_page+8, landing_page_length-8);
>>
>> spaces around arithmetic operators?
>
> will do
>
>
>>> + buf[0] = landing_page_length - 8 + 3;
>>> + } else if (strncasecmp("http://", cdev->landing_page, 7) == 0) {
>>> + buf[2] = 0x00;
>>
>> Magic 0x00?
>
> This would then be WEBUSB_PREFIX_HTTP_SCHEME_ID
>
>>
>>> + memcpy(buf+3, cdev->landing_page+7, landing_page_length-7);
>>> + buf[0] = landing_page_length - 7 + 3;
>>> + } else {
>>> + buf[2] = 0xFF;
>>
>> Magic 0xFF? (plus I'd expect lowercase hex digits).
>
> This would then be WEBUSB_PREFIX_NONE_SCHEME_ID
>
>> There's "URL Prefixes" table in 4.3.1 URL Descriptor. Why not define
>> URL_PREFIX_HTTP/HTTPS/NONE?
>
> will do
>
>>> + memcpy(buf+3, cdev->landing_page, landing_page_length);
>>> + buf[0] = landing_page_length + 3;
>>> + }
>>> +
>>> + value = buf[0];
>>> +
>>> + goto check_value;
>>> + }
>>> +
>>> VDBG(cdev,
>>> "non-core control req%02x.%02x v%04x i%04x l%d\n",
>>> ctrl->bRequestType, ctrl->bRequest,
>>> diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c
>>> index 96121d1c8df4..2b7f01a9efff 100644
>>> --- a/drivers/usb/gadget/configfs.c
>>> +++ b/drivers/usb/gadget/configfs.c
>>> @@ -39,6 +39,7 @@ struct gadget_info {
>>> struct config_group configs_group;
>>> struct config_group strings_group;
>>> struct config_group os_desc_group;
>>> + struct config_group webusb_group;
>>>
>>> struct mutex lock;
>>> struct usb_gadget_strings *gstrings[MAX_USB_STRING_LANGS + 1];
>>> @@ -50,6 +51,11 @@ struct gadget_info {
>>> bool use_os_desc;
>>> char b_vendor_code;
>>> char qw_sign[OS_STRING_QW_SIGN_LEN];
>>> + bool use_webusb;
>>> + u16 bcd_webusb_version;
>>> + u8 b_webusb_vendor_code;
>>> + char landing_page[255];
>>
>> A #define for the size?
>
> will do
>
>>
>>> +
>>> spinlock_t spinlock;
>>> bool unbind;
>>> };
>>> @@ -780,6 +786,131 @@ static void gadget_strings_attr_release(struct config_item *item)
>>> USB_CONFIG_STRING_RW_OPS(gadget_strings);
>>> USB_CONFIG_STRINGS_LANG(gadget_strings, gadget_info);
>>>
>>> +static inline struct gadget_info *webusb_item_to_gadget_info(
>>> + struct config_item *item)
>>> +{
>>> + return container_of(to_config_group(item),
>>> + struct gadget_info, webusb_group);
>>> +}
>>> +
>>> +static ssize_t webusb_use_show(struct config_item *item, char *page)
>>> +{
>>> + return sysfs_emit(page, "%d\n",
>>> + webusb_item_to_gadget_info(item)->use_webusb);
>>> +}
>>> +
>>> +static ssize_t webusb_use_store(struct config_item *item, const char *page,
>>> + size_t len)
>>> +{
>>> + struct gadget_info *gi = webusb_item_to_gadget_info(item);
>>> + int ret;
>>> + bool use;
>>> +
>>> + mutex_lock(&gi->lock);
>>> + ret = kstrtobool(page, &use);
>>> + if (!ret) {
>>> + gi->use_webusb = use;
>>> + ret = len;
>>> + }
>>> + mutex_unlock(&gi->lock);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static ssize_t webusb_bcdVersion_show(struct config_item *item, char *page)
>>> +{
>>> + return sysfs_emit(page, "0x%04x\n",
>>> + webusb_item_to_gadget_info(item)->bcd_webusb_version);
>>> +}
>>> +
>>> +static ssize_t webusb_bcdVersion_store(struct config_item *item,
>>> + const char *page, size_t len)
>>> +{
>>> + struct gadget_info *gi = webusb_item_to_gadget_info(item);
>>> + u16 bcdVersion;
>>> + int ret;
>>> +
>>> + mutex_lock(&gi->lock);
>>> + ret = kstrtou16(page, 0, &bcdVersion);
>>> + if (ret)
>>> + goto out;
>>> + ret = is_valid_bcd(bcdVersion);
>>> + if (ret)
>>> + goto out;
>>> +
>>> + gi->bcd_webusb_version = bcdVersion;
>>> + ret = len;
>>> +
>>> +out:
>>> + mutex_unlock(&gi->lock);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static ssize_t webusb_bVendorCode_show(struct config_item *item, char *page)
>>> +{
>>> + return sysfs_emit(page, "0x%02x\n",
>>> + webusb_item_to_gadget_info(item)->b_webusb_vendor_code);
>>> +}
>>> +
>>> +static ssize_t webusb_bVendorCode_store(struct config_item *item,
>>> + const char *page, size_t len)
>>> +{
>>> + struct gadget_info *gi = webusb_item_to_gadget_info(item);
>>> + int ret;
>>> + u8 b_vendor_code;
>>> +
>>> + mutex_lock(&gi->lock);
>>> + ret = kstrtou8(page, 0, &b_vendor_code);
>>> + if (!ret) {
>>> + gi->b_webusb_vendor_code = b_vendor_code;
>>> + ret = len;
>>> + }
>>> + mutex_unlock(&gi->lock);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static ssize_t webusb_landingPage_show(struct config_item *item, char *page)
>>> +{
>>> + return sysfs_emit(page, "%s\n", webusb_item_to_gadget_info(item)->landing_page);
>>> +}
>>> +
>>> +static ssize_t webusb_landingPage_store(struct config_item *item, const char *page,
>>> + size_t len)
>>> +{
>>> + struct gadget_info *gi = webusb_item_to_gadget_info(item);
>>> + int l;
>>> +
>>> + l = min(len, sizeof(gi->landing_page));
>>> + if (page[l - 1] == '\n')
>>> + --l;
>>> +
>>> + mutex_lock(&gi->lock);
>>> + memcpy(gi->landing_page, page, l);
>>> + mutex_unlock(&gi->lock);
>>> +
>>> + return len;
>>> +}
>>> +
>>> +CONFIGFS_ATTR(webusb_, use);
>>> +CONFIGFS_ATTR(webusb_, bVendorCode);
>>> +CONFIGFS_ATTR(webusb_, bcdVersion);
>>> +CONFIGFS_ATTR(webusb_, landingPage);
>>> +
>>> +static struct configfs_attribute *webusb_attrs[] = {
>>> + &webusb_attr_use,
>>> + &webusb_attr_bcdVersion,
>>> + &webusb_attr_bVendorCode,
>>> + &webusb_attr_landingPage,
>>> + NULL,
>>> +};
>>> +
>>> +static struct config_item_type webusb_type = {
>>> + .ct_attrs = webusb_attrs,
>>> + .ct_owner = THIS_MODULE,
>>> +};
>>> +
>>> static inline struct gadget_info *os_desc_item_to_gadget_info(
>>> struct config_item *item)
>>> {
>>> @@ -1341,6 +1472,16 @@ static int configfs_composite_bind(struct usb_gadget *gadget,
>>> gi->cdev.desc.iSerialNumber = s[USB_GADGET_SERIAL_IDX].id;
>>> }
>>>
>>> + if (gi->use_webusb) {
>>> + cdev->use_webusb = true;
>>> + cdev->bcd_webusb_version = gi->bcd_webusb_version;
>>> + cdev->b_webusb_vendor_code = gi->b_webusb_vendor_code;
>>> + memcpy(cdev->landing_page, gi->landing_page,
>>> + strnlen(gi->landing_page,
>>> + min(sizeof(cdev->landing_page),
>>> + sizeof(gi->landing_page))));
>>
>> checkpatch now allows 100 colums. Plus strnlen() looks indented too far to the
>> right.
>
> I will check this.
>
>>
>> The sizeofs are both 255. Are they ever expected to be different? Maybe not?
>> Then a #defined constant ensures they are equal. Then there's no need to find
>> a minimum among equal values.
>
> Technically, if the url has the empty scheme, it can only be 252 bytes
> long, since the maximum descriptor length is 255 because bLength is a
> single byte, but 3 bytes are in the header of this descriptor, leaving
> 252 for the string.

But you use sizeof() rather than strlen(). And the "landing_page" members
are defined as 255-element arrays of chars, so both sizeof() invocations
will yield 255, no?

Andrzej

>
> However, if the landing page scheme is "https://", the maximum length
> of this string could actually be 255 - 3 /*header*/ + 8 /* the length
> of the prefix, which is stripped */ => 260 bytes.
>
> I need to rework the validation here a bit. I would like to avoid for
> userspace to explicitly set the scheme constants directly, as just
> piping in the string seems so much more ergonomic.
>
>>
>> Regards,
>>
>> Andrzej
>
> Thanks a lot for your review!
>
> I hope my response to your initial concerns was not too long and windy.
>
> I will post an updated version of my patch within the next few days.
>
> Regards,
> Jó
>
>
>>
>>> + }
>>> +
>>> if (gi->use_os_desc) {
>>> cdev->use_os_string = true;
>>> cdev->b_vendor_code = gi->b_vendor_code;
>>> @@ -1605,6 +1746,10 @@ static struct config_group *gadgets_make(
>>> &os_desc_type);
>>> configfs_add_default_group(&gi->os_desc_group, &gi->group);
>>>
>>> + config_group_init_type_name(&gi->webusb_group, "webusb",
>>> + &webusb_type);
>>> + configfs_add_default_group(&gi->webusb_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 43ac3fa760db..eb6fac5bbcde 100644
>>> --- a/include/linux/usb/composite.h
>>> +++ b/include/linux/usb/composite.h
>>> @@ -474,6 +474,12 @@ struct usb_composite_dev {
>>> struct usb_configuration *os_desc_config;
>>> unsigned int use_os_string:1;
>>>
>>> + /* WebUSB */
>>> + u16 bcd_webusb_version;
>>> + u8 b_webusb_vendor_code;
>>> + char landing_page[255];
>>> + unsigned int use_webusb:1;
>>> +
>>> /* private: */
>>> /* internals */
>>> unsigned int suspended:1;
>>> diff --git a/include/uapi/linux/usb/ch9.h b/include/uapi/linux/usb/ch9.h
>>> index 31fcfa084e63..3a36550297bc 100644
>>> --- a/include/uapi/linux/usb/ch9.h
>>> +++ b/include/uapi/linux/usb/ch9.h
>>> @@ -947,6 +947,39 @@ struct usb_ss_container_id_descriptor {
>>>
>>> #define USB_DT_USB_SS_CONTN_ID_SIZE 20
>>>
>>> +/*
>>> + * Platform Device Capability descriptor: Defines platform specific device
>>> + * capabilities
>>> + */
>>> +#define USB_PLAT_DEV_CAP_TYPE 5
>>> +struct usb_plat_dev_cap_descriptor {
>>> + __u8 bLength;
>>> + __u8 bDescriptorType;
>>> + __u8 bDevCapabilityType;
>>> + __u8 bReserved;
>>> + __u8 UUID[16];
>>> +} __attribute__((packed));
>>> +
>>> +#define USB_DT_USB_PLAT_DEV_CAP_SIZE 20
>>> +
>>> +/*
>>> + * WebUSB Platform Capability descriptor: A device announces support for the
>>> + * WebUSB command set by including the following Platform Descriptor in its
>>> + * Binary Object Store
>>> + * https://wicg.github.io/webusb/#webusb-platform-capability-descriptor
>>> + */
>>> +struct usb_webusb_cap_descriptor {
>>> + __u8 bLength;
>>> + __u8 bDescriptorType;
>>> + __u8 bDevCapabilityType;
>>> + __u8 bReserved;
>>> + __u8 UUID[16];
>>> + __u16 bcdVersion;
>>> + __u8 bVendorCode;
>>> + __u8 iLandingPage;
>>> +} __attribute__((packed));
>>> +#define USB_DT_WEBUSB_SIZE (USB_DT_USB_PLAT_DEV_CAP_SIZE + 4)
>>> +
>>> /*
>>> * SuperSpeed Plus USB Capability descriptor: Defines the set of
>>> * SuperSpeed Plus USB specific device level capabilities
>>

2023-01-10 16:18:45

by Alan Stern

[permalink] [raw]
Subject: Re: [PATCH v3] usb: gadget: add WebUSB landing page support

On Tue, Jan 10, 2023 at 02:28:55PM +0100, Andrzej Pietrasiewicz wrote:
> Hi J?,
>
> W dniu 10.01.2023 o?00:19, J? ?gila Bitsch pisze:

> > > > + buf[1] = /* WEBUSB_URL*/ 3;
> > >
> > > Why not #define WEBUSB_URL?
> >
> > in a previous iteration, I had that exact #define in place, right
> > above. However, another reviewer found it pointless. Maybe I should
> > instead put the define into include/uapi/linux/usb/ch9.h
> >
>
> Hmm. ch9.h had been called ch9.h for a reason. It corresponded to Chapter 9 of
> the USB spec. Adding to it something that by design is outside the spec might
> not be what we want. But I don't know.

It wouldn't hurt to create a new file: include/uapi/linux/usb/webusb.h.

Alan Stern

2023-01-11 00:02:03

by Jó Ágila Bitsch

[permalink] [raw]
Subject: Re: [PATCH v3] usb: gadget: add WebUSB landing page support

Hi Andrzej,

On Tue, Jan 10, 2023 at 2:28 PM Andrzej Pietrasiewicz
<[email protected]> wrote:
>
> Hi Jó,
>
> W dniu 10.01.2023 o 00:19, Jó Ágila Bitsch pisze:
> > Hi Andrzej,
> >
> >>> The specification allows USB gadgets to announce an URL to landing
> >>> page and describes a Javascript interface for websites to interact
> >>> with the USB gadget, if the user allows it. It is currently
> >>> supported by Chromium-based browsers, such as Chrome, Edge and
> >>> Opera on all major operating systems including Linux.
> >>>
> >>
> >> I envision an "evil" scenario, where you don't own your USB device any more.
> >> Instead you are required to pay a subscription to keep accessing it
> >> because its driver is somewhere on the net. I purposedly have put
> >> evil in quotes, because such a scenario might be considered preferred
> >> by some people. Maybe there are advantages, too, like not having to worry
> >> about correct drivers. Anyway, the kernel is about mechanism, not policy,
> >> so I'm probably in no position to judge such things.
> >
> > Unfortunately, the "evil" devices you envision very much already exist.
> >
> > When I was still working at university in the early 2010s, we were
> > working with a mobile EEG headset (>1000USD) with a USB receiver
> > dongle that would present itself as a USB HID device, but the
> > transferred frames would be AES encrypted, with the key based on a
> > proprietary transformation of the serial number of the device. At
> > first the library that decoded it was available as a binary blob for a
> > one-time payment, where you paid a hefty premium if you wanted access
> > to the raw readings. Later the manufacturer changed their business
> > model to subscription based, where the data had to flow through their
> > cloud service - with a recurring license fee. I don't know what
> > happened afterwards, as I left university.
> > This all was implemented based on libusb back then before people even
> > thought of WebUSB.
> >
>
> Thanks for honestly acknowledging their existence. It confirms that
> what I expressed was not a "conspiracy theory", but a "conspiracy
> practice" :D
>
> > I'm sure there are other examples.
> >
> > So, "evil" devices are already real and don't depend on WebUSB. The
> > motivating example "Web Drivers" from the specs
> > (https://wicg.github.io/webusb/#app-drivers) might potentially lead to
> > companies asking for subscription fees for their Web Driver, but as
> > long as there is no strong encryption in place, everyone would be free
> > to write an alternative driver. The mechanisms would not at all
> > preclude you from writing your own kernel or userspace driver using
> > e.g. libusb. A kernel driver would block access from the browser
> > though, as the browser cannot detach the kernel driver. (No such
> > command is currently defined in WebUSB.) The JS implementation might
> > even guide you to understand aspects of the protocol used.
> >
> > Citing from the motivating example in the design specs
> > (https://wicg.github.io/webusb/#app-updates-and-diagnostics):
> >
> >> The landing page provides a way for the device manufacturer to direct the user to the right part of their website for help with their device.
> >
> > The landing page mechanism in WebUSB improves discoverability for
> > devices, as Chrome and also lsusb directly point you to a webpage,
> > where the manufacturer or firmware producer can give more information
> > about the device. This webpage does not necessarily need to use a
> > WebUSB JS driver itself to access the device, but might just as well
> > point you to some git repository, where the hardware design and
> > firmware source are available
>
> I'm wondering why e.g. Firefox does not implement this?

There has been an extended discussion within Firefox:
https://github.com/mozilla/standards-positions/issues/100
https://github.com/mozilla/standards-positions/pull/193

The main concerns center around (quoted from the link above):
> * security issues related to attacks on devices that, once compromised, could be used to attack the user's computer more generally
> * the permissions model (whether a question can be posed to the user that yields informed consent, whether there should be a CORS-like model for devices to say that they want to interact with particular domains which may improve security but hurts openness)

and tracking users across websites by exposing static identifiers

The webkit position is tracked here:
https://github.com/WebKit/standards-positions/issues/68

They are following Mozilla's position and also raising the issue of
device-independence.

However, there has been some recent discussion to reevaluate this
position, especially in the context of extensions and users are
repeatedly bringing up complaints that they would like to see this
implemented.

> Chicken-and-egg
> dilemma (no OS support at kernel level, so let's refrain from using it,
> but then there's too few users, so let's not add OS support - this
> circular depencency can be broken by applying your patch) or something
> else?

OS support at the kernel is not actually necessary for the host side
of the USB connection at all, as the browser would just access the
devices via the exposed character devices in /dev/bus/usb/*/*

My patch would only improve the device side of the USB connection,
namely, that the landing page can be announced in gadget mode. So this
would enable makers and manufacturers of USB gadgets based on embedded
linux devices to point to a landing page for their device.

The other aspects of USB communication are already well supported by
the USB gadget subsystem and don't need any changes to work with
WebUSB. Actually, they are already working fine with WebUSB. An
example with a Linux based gadget side is a WebAdb library
(https://webadb.github.io/), that allows Websites to access Android
phones via a website.

I'm imagining the use case of single board computers, such as
raspberry pi, orange pi, or other open hardware, such as a linux based
USB stick such as https://github.com/usbarmory/usbarmory.

But it might just as well be your home router, that is connected via
USB and points you to the configuration page directly. It knows its
own IP address on the local network (and/or provide a USB network
interface via RNDIS/ECM/NCM) and can dynamically point to the correct
one. The last one could probably also work with some network based
discovery mechanism but I want to open up the space a bit to things
that just need the landing page and not the other parts of webusb.

Actually this would combine well with what you (Andrzej) presented
back in 2019 on
https://www.collabora.com/news-and-blog/blog/2019/02/18/modern-usb-gadget-on-linux-and-how-to-integrate-it-with-systemd-part-1/

>
> >
> > For what it's worth, the pick-up of WebUSB I've seen has been mostly
> > in the maker scene with arduino, micro:bit and similar embedded
> > devices, where devices provide at least two interfaces: One for the OS
> > to claim and one for the Browser to claim.
> >
> > Anyways, my biased opinion is that I would very much like a device to
> > tell me where I can find support documentation, updates and help also
> > when the device only implements standard interfaces that have kernel
> > drivers. I don't think that this URL will lead to more devices you can
> > only access with a subscription.
> >
> >> Please see below.
> >
> > Thanks for your excellent feedback. I answered inline.
> >
> >>> This patch adds optional support for Linux-based USB gadgets
> >>> wishing to expose such a landing page.
> >>>
> >>> During device enumeration, a host recognizes that the announced
> >>> USB version is at least 2.01, which means, that there are BOS
> >>> descriptors available. The device than announces WebUSB support
> >>> using a platform device capability. This includes a vendor code
> >>> under which the landing page URL can be retrieved using a
> >>> vendor-specific request.
> >>>
> >>> Previously, the BOS descriptors would unconditionally include an
> >>> LPM related descriptor, as BOS descriptors were only ever sent
> >>> when the device was LPM capable. As this is no longer the case,
> >>> this patch puts this descriptor behind a lpm_capable condition.
> >>>
> >>> Usage is modeled after os_desc descriptors:
> >>> echo 1 > webusb/use
> >>> echo "https://www.kernel.org" > webusb/landingPage
> >>>
> >>> lsusb will report the device with the following lines:
> >>> Platform Device Capability:
> >>> bLength 24
> >>> bDescriptorType 16
> >>> bDevCapabilityType 5
> >>> bReserved 0
> >>> PlatformCapabilityUUID {3408b638-09a9-47a0-8bfd-a0768815b665}
> >>> WebUSB:
> >>> bcdVersion 1.00
> >>> bVendorCode 0
> >>> iLandingPage 1 https://www.kernel.org
> >>>
> >>> Signed-off-by: Jó Ágila Bitsch <[email protected]>
> >>> ---
> >>> v2 -> V3: improved commit message to include additional condition in desc_bos as per comment
> >>> by Alan Stern
> >>> V1 -> V2: cleaned up coding style, made URL scheme comparison case insensitive, addressed review
> >>> comments by Alan Stern
> >>> V0 -> V1: use sysfs_emit instead of sprintf and use lock in webusb_bcdVersion_store, addressed review
> >>> comments by Christophe JAILLET
> >>>
> >>> Documentation/ABI/testing/configfs-usb-gadget | 13 ++
> >>> drivers/usb/gadget/composite.c | 95 ++++++++++--
> >>> drivers/usb/gadget/configfs.c | 145 ++++++++++++++++++
> >>> include/linux/usb/composite.h | 6 +
> >>> include/uapi/linux/usb/ch9.h | 33 ++++
> >>> 5 files changed, 282 insertions(+), 10 deletions(-)
> >>>
> >>> diff --git a/Documentation/ABI/testing/configfs-usb-gadget b/Documentation/ABI/testing/configfs-usb-gadget
> >>> index b7943aa7e997..8399e0f0ed1c 100644
> >>> --- a/Documentation/ABI/testing/configfs-usb-gadget
> >>> +++ b/Documentation/ABI/testing/configfs-usb-gadget
> >>> @@ -143,3 +143,16 @@ Description:
> >>> qw_sign an identifier to be reported as "OS String"
> >>> proper
> >>> ============= ===============================================
> >>> +
> >>> +What: /config/usb-gadget/gadget/webusb
> >>> +Date: Dec 2022
> >>> +KernelVersion: 6.2
> >>> +Description:
> >>> + This group contains "WebUSB" extension handling attributes.
> >>> +
> >>> + ============= ===============================================
> >>> + use flag turning "WebUSB" support on/off
> >>> + bcdVersion bcd WebUSB specification version number
> >>> + b_vendor_code one-byte value used for custom per-device
> >>> + landing_page UTF-8 encoded URL of the device's landing page
> >>> + ============= ===============================================
> >>> diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c
> >>> index 403563c06477..9b209e69442d 100644
> >>> --- a/drivers/usb/gadget/composite.c
> >>> +++ b/drivers/usb/gadget/composite.c
> >>> @@ -14,6 +14,7 @@
> >>> #include <linux/device.h>
> >>> #include <linux/utsname.h>
> >>> #include <linux/bitfield.h>
> >>> +#include <linux/uuid.h>
> >>>
> >>> #include <linux/usb/composite.h>
> >>> #include <linux/usb/otg.h>
> >>> @@ -713,14 +714,16 @@ static int bos_desc(struct usb_composite_dev *cdev)
> >>> * A SuperSpeed device shall include the USB2.0 extension descriptor
> >>> * and shall support LPM when operating in USB2.0 HS mode.
> >>> */
> >>> - usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
> >>> - bos->bNumDeviceCaps++;
> >>> - le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
> >>> - usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
> >>> - usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
> >>> - usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
> >>> - usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT |
> >>> - USB_BESL_SUPPORT | besl);
> >>> + if (cdev->gadget->lpm_capable) {
> >>> + usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
> >>> + bos->bNumDeviceCaps++;
> >>> + le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
> >>> + usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
> >>> + usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
> >>> + usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
> >>> + usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT |
> >>> + USB_BESL_SUPPORT | besl);
> >>> + }
> >>>
> >>> /*
> >>> * The Superspeed USB Capability descriptor shall be implemented by all
> >>> @@ -821,6 +824,40 @@ static int bos_desc(struct usb_composite_dev *cdev)
> >>> }
> >>> }
> >>>
> >>> + /*
> >>> + * Following the specifaction at:
> >>> + * https://wicg.github.io/webusb/#webusb-platform-capability-descriptor
> >>> + */
> >>> + if (cdev->use_webusb) {
> >>> + struct usb_webusb_cap_descriptor *webusb_cap;
> >>> + /*
> >>> + * little endian PlatformCapablityUUID for WebUSB:
> >>> + * 3408b638-09a9-47a0-8bfd-a0768815b665
> >>> + */
> >>> + uuid_t webusb_uuid = UUID_INIT(0x38b60834, 0xa909, 0xa047, 0x8b, 0xfd, 0xa0, 0x76,
> >>> + 0x88, 0x15, 0xb6, 0x65);
> >>> +
> >>> + webusb_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
> >>> + bos->bNumDeviceCaps++;
> >>> +
> >>> + le16_add_cpu(&bos->wTotalLength, USB_DT_WEBUSB_SIZE);
> >>> + webusb_cap->bLength = USB_DT_WEBUSB_SIZE;
> >>> + webusb_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
> >>> + webusb_cap->bDevCapabilityType = USB_PLAT_DEV_CAP_TYPE;
> >>> + webusb_cap->bReserved = 0;
> >>> + export_uuid(webusb_cap->UUID, &webusb_uuid);
> >>> + if (cdev->bcd_webusb_version != 0)
> >>> + webusb_cap->bcdVersion = cpu_to_le16(cdev->bcd_webusb_version);
> >>> + else
> >>> + webusb_cap->bcdVersion = cpu_to_le16(0x0100);
> >>> +
> >>> + webusb_cap->bVendorCode = cdev->b_webusb_vendor_code;
> >>> + if (strnlen(cdev->landing_page, sizeof(cdev->landing_page)) > 0)
> >>> + webusb_cap->iLandingPage = 1;
> >>> + else
> >>> + webusb_cap->iLandingPage = 0;
> >>> + }
> >>> +
> >>> return le16_to_cpu(bos->wTotalLength);
> >>> }
> >>>
> >>> @@ -1744,7 +1781,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
> >>> cdev->desc.bcdUSB = cpu_to_le16(0x0210);
> >>> }
> >>> } else {
> >>> - if (gadget->lpm_capable)
> >>> + if (gadget->lpm_capable || cdev->use_webusb)
> >>> cdev->desc.bcdUSB = cpu_to_le16(0x0201);
> >>> else
> >>> cdev->desc.bcdUSB = cpu_to_le16(0x0200);
> >>> @@ -1779,7 +1816,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
> >>> break;
> >>> case USB_DT_BOS:
> >>> if (gadget_is_superspeed(gadget) ||
> >>> - gadget->lpm_capable) {
> >>> + gadget->lpm_capable || cdev->use_webusb) {
> >>> value = bos_desc(cdev);
> >>> value = min(w_length, (u16) value);
> >>> }
> >>> @@ -2013,6 +2050,44 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
> >>> goto check_value;
> >>> }
> >>>
> >>> + /*
> >>> + * webusb descriptor handling, following:
> >>> + * https://wicg.github.io/webusb/#device-requests
> >>> + */
> >>> + if (cdev->use_webusb &&
> >>> + (ctrl->bRequestType & USB_TYPE_VENDOR) &&
> >>> + ctrl->wIndex == /* WEBUSB_GET_URL*/ 2 &&
> >>> + ctrl->bRequest == cdev->b_webusb_vendor_code) {
> >>> + /*
> >>> + * specification of the url descriptor in WebUSB:
> >>> + * https://wicg.github.io/webusb/#webusb-descriptors
> >>> + */
> >>> + u8 *buf;
> >>> + unsigned int landing_page_length;
> >>> +
> >>> + buf = cdev->req->buf;
> >>> + buf[1] = /* WEBUSB_URL*/ 3;
> >>
> >> Why not #define WEBUSB_URL?
> >
> > in a previous iteration, I had that exact #define in place, right
> > above. However, another reviewer found it pointless. Maybe I should
> > instead put the define into include/uapi/linux/usb/ch9.h
> >
>
> Hmm. ch9.h had been called ch9.h for a reason. It corresponded to Chapter 9 of
> the USB spec. Adding to it something that by design is outside the spec might
> not be what we want. But I don't know.

Alan also agrees with you.

I will move it to a new header file: include/uapi/linux/usb/webusb.h.

>
> >>
> >>> +
> >>> + landing_page_length = strnlen(cdev->landing_page, 252);
> >>> + if (strncasecmp("https://", cdev->landing_page, 8) == 0) {
> >>
> >> Maybe it's me, but it would be easier for me to understand why the "8" if the
> >> comparison was in reverse order of arguments, like this:
> >>
> >> strncasecmp(cdev->landing_page, "https://", 8)
> >
> > I agree, this makes the 8 clearer.
> >
> >> because we want the entirety of "https://" compared, not its substring, so the
> >> limit kind of applies to the landing_page in the first place.
> >> Maybe the "8" (and "7" below) can be #define'd, too?
> >
> > depends on the name, but maybe something like
> >
> > #define WEBUSB_PREFIX_HTTPS "https://"
> > #define WEBUSB_PREFIX_HTTPS_LENGTH 8
> > #define WEBUSB_PREFIX_HTTPS_SCHEME_ID 0x01
> >
> > would work defined with the struct in include/uapi/linux/usb/ch9.h
> >
> >>
> >>> + buf[2] = 0x01;
> >>
> >> What is this magic 0x01?
> >
> > The field is specified in
> > https://wicg.github.io/webusb/#url-descriptor and would be the
> > WEBUSB_PREFIX_HTTPS_SCHEME_ID from above.
> > But yes, I should #define this.
> >
> >>
> >>> + memcpy(buf+3, cdev->landing_page+8, landing_page_length-8);
> >>
> >> spaces around arithmetic operators?
> >
> > will do
> >
> >
> >>> + buf[0] = landing_page_length - 8 + 3;
> >>> + } else if (strncasecmp("http://", cdev->landing_page, 7) == 0) {
> >>> + buf[2] = 0x00;
> >>
> >> Magic 0x00?
> >
> > This would then be WEBUSB_PREFIX_HTTP_SCHEME_ID
> >
> >>
> >>> + memcpy(buf+3, cdev->landing_page+7, landing_page_length-7);
> >>> + buf[0] = landing_page_length - 7 + 3;
> >>> + } else {
> >>> + buf[2] = 0xFF;
> >>
> >> Magic 0xFF? (plus I'd expect lowercase hex digits).
> >
> > This would then be WEBUSB_PREFIX_NONE_SCHEME_ID
> >
> >> There's "URL Prefixes" table in 4.3.1 URL Descriptor. Why not define
> >> URL_PREFIX_HTTP/HTTPS/NONE?
> >
> > will do
> >
> >>> + memcpy(buf+3, cdev->landing_page, landing_page_length);
> >>> + buf[0] = landing_page_length + 3;
> >>> + }
> >>> +
> >>> + value = buf[0];
> >>> +
> >>> + goto check_value;
> >>> + }
> >>> +
> >>> VDBG(cdev,
> >>> "non-core control req%02x.%02x v%04x i%04x l%d\n",
> >>> ctrl->bRequestType, ctrl->bRequest,
> >>> diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c
> >>> index 96121d1c8df4..2b7f01a9efff 100644
> >>> --- a/drivers/usb/gadget/configfs.c
> >>> +++ b/drivers/usb/gadget/configfs.c
> >>> @@ -39,6 +39,7 @@ struct gadget_info {
> >>> struct config_group configs_group;
> >>> struct config_group strings_group;
> >>> struct config_group os_desc_group;
> >>> + struct config_group webusb_group;
> >>>
> >>> struct mutex lock;
> >>> struct usb_gadget_strings *gstrings[MAX_USB_STRING_LANGS + 1];
> >>> @@ -50,6 +51,11 @@ struct gadget_info {
> >>> bool use_os_desc;
> >>> char b_vendor_code;
> >>> char qw_sign[OS_STRING_QW_SIGN_LEN];
> >>> + bool use_webusb;
> >>> + u16 bcd_webusb_version;
> >>> + u8 b_webusb_vendor_code;
> >>> + char landing_page[255];
> >>
> >> A #define for the size?
> >
> > will do
> >
> >>
> >>> +
> >>> spinlock_t spinlock;
> >>> bool unbind;
> >>> };
> >>> @@ -780,6 +786,131 @@ static void gadget_strings_attr_release(struct config_item *item)
> >>> USB_CONFIG_STRING_RW_OPS(gadget_strings);
> >>> USB_CONFIG_STRINGS_LANG(gadget_strings, gadget_info);
> >>>
> >>> +static inline struct gadget_info *webusb_item_to_gadget_info(
> >>> + struct config_item *item)
> >>> +{
> >>> + return container_of(to_config_group(item),
> >>> + struct gadget_info, webusb_group);
> >>> +}
> >>> +
> >>> +static ssize_t webusb_use_show(struct config_item *item, char *page)
> >>> +{
> >>> + return sysfs_emit(page, "%d\n",
> >>> + webusb_item_to_gadget_info(item)->use_webusb);
> >>> +}
> >>> +
> >>> +static ssize_t webusb_use_store(struct config_item *item, const char *page,
> >>> + size_t len)
> >>> +{
> >>> + struct gadget_info *gi = webusb_item_to_gadget_info(item);
> >>> + int ret;
> >>> + bool use;
> >>> +
> >>> + mutex_lock(&gi->lock);
> >>> + ret = kstrtobool(page, &use);
> >>> + if (!ret) {
> >>> + gi->use_webusb = use;
> >>> + ret = len;
> >>> + }
> >>> + mutex_unlock(&gi->lock);
> >>> +
> >>> + return ret;
> >>> +}
> >>> +
> >>> +static ssize_t webusb_bcdVersion_show(struct config_item *item, char *page)
> >>> +{
> >>> + return sysfs_emit(page, "0x%04x\n",
> >>> + webusb_item_to_gadget_info(item)->bcd_webusb_version);
> >>> +}
> >>> +
> >>> +static ssize_t webusb_bcdVersion_store(struct config_item *item,
> >>> + const char *page, size_t len)
> >>> +{
> >>> + struct gadget_info *gi = webusb_item_to_gadget_info(item);
> >>> + u16 bcdVersion;
> >>> + int ret;
> >>> +
> >>> + mutex_lock(&gi->lock);
> >>> + ret = kstrtou16(page, 0, &bcdVersion);
> >>> + if (ret)
> >>> + goto out;
> >>> + ret = is_valid_bcd(bcdVersion);
> >>> + if (ret)
> >>> + goto out;
> >>> +
> >>> + gi->bcd_webusb_version = bcdVersion;
> >>> + ret = len;
> >>> +
> >>> +out:
> >>> + mutex_unlock(&gi->lock);
> >>> +
> >>> + return ret;
> >>> +}
> >>> +
> >>> +static ssize_t webusb_bVendorCode_show(struct config_item *item, char *page)
> >>> +{
> >>> + return sysfs_emit(page, "0x%02x\n",
> >>> + webusb_item_to_gadget_info(item)->b_webusb_vendor_code);
> >>> +}
> >>> +
> >>> +static ssize_t webusb_bVendorCode_store(struct config_item *item,
> >>> + const char *page, size_t len)
> >>> +{
> >>> + struct gadget_info *gi = webusb_item_to_gadget_info(item);
> >>> + int ret;
> >>> + u8 b_vendor_code;
> >>> +
> >>> + mutex_lock(&gi->lock);
> >>> + ret = kstrtou8(page, 0, &b_vendor_code);
> >>> + if (!ret) {
> >>> + gi->b_webusb_vendor_code = b_vendor_code;
> >>> + ret = len;
> >>> + }
> >>> + mutex_unlock(&gi->lock);
> >>> +
> >>> + return ret;
> >>> +}
> >>> +
> >>> +static ssize_t webusb_landingPage_show(struct config_item *item, char *page)
> >>> +{
> >>> + return sysfs_emit(page, "%s\n", webusb_item_to_gadget_info(item)->landing_page);
> >>> +}
> >>> +
> >>> +static ssize_t webusb_landingPage_store(struct config_item *item, const char *page,
> >>> + size_t len)
> >>> +{
> >>> + struct gadget_info *gi = webusb_item_to_gadget_info(item);
> >>> + int l;
> >>> +
> >>> + l = min(len, sizeof(gi->landing_page));
> >>> + if (page[l - 1] == '\n')
> >>> + --l;
> >>> +
> >>> + mutex_lock(&gi->lock);
> >>> + memcpy(gi->landing_page, page, l);
> >>> + mutex_unlock(&gi->lock);
> >>> +
> >>> + return len;
> >>> +}
> >>> +
> >>> +CONFIGFS_ATTR(webusb_, use);
> >>> +CONFIGFS_ATTR(webusb_, bVendorCode);
> >>> +CONFIGFS_ATTR(webusb_, bcdVersion);
> >>> +CONFIGFS_ATTR(webusb_, landingPage);
> >>> +
> >>> +static struct configfs_attribute *webusb_attrs[] = {
> >>> + &webusb_attr_use,
> >>> + &webusb_attr_bcdVersion,
> >>> + &webusb_attr_bVendorCode,
> >>> + &webusb_attr_landingPage,
> >>> + NULL,
> >>> +};
> >>> +
> >>> +static struct config_item_type webusb_type = {
> >>> + .ct_attrs = webusb_attrs,
> >>> + .ct_owner = THIS_MODULE,
> >>> +};
> >>> +
> >>> static inline struct gadget_info *os_desc_item_to_gadget_info(
> >>> struct config_item *item)
> >>> {
> >>> @@ -1341,6 +1472,16 @@ static int configfs_composite_bind(struct usb_gadget *gadget,
> >>> gi->cdev.desc.iSerialNumber = s[USB_GADGET_SERIAL_IDX].id;
> >>> }
> >>>
> >>> + if (gi->use_webusb) {
> >>> + cdev->use_webusb = true;
> >>> + cdev->bcd_webusb_version = gi->bcd_webusb_version;
> >>> + cdev->b_webusb_vendor_code = gi->b_webusb_vendor_code;
> >>> + memcpy(cdev->landing_page, gi->landing_page,
> >>> + strnlen(gi->landing_page,
> >>> + min(sizeof(cdev->landing_page),
> >>> + sizeof(gi->landing_page))));
> >>
> >> checkpatch now allows 100 colums. Plus strnlen() looks indented too far to the
> >> right.
> >
> > I will check this.
> >
> >>
> >> The sizeofs are both 255. Are they ever expected to be different? Maybe not?
> >> Then a #defined constant ensures they are equal. Then there's no need to find
> >> a minimum among equal values.
> >
> > Technically, if the url has the empty scheme, it can only be 252 bytes
> > long, since the maximum descriptor length is 255 because bLength is a
> > single byte, but 3 bytes are in the header of this descriptor, leaving
> > 252 for the string.
>
> But you use sizeof() rather than strlen(). And the "landing_page" members
> are defined as 255-element arrays of chars, so both sizeof() invocations
> will yield 255, no?

You are correct. I was just thinking of the exact length limitations
when you wrote your comment. Before that I hadn't really considered
the different lengths based on the scheme, so this version of the
patch does not yet make this distinction.

Thanks for your patience with a first-time contributor.

Regards,


>
> Andrzej
>
> >
> > However, if the landing page scheme is "https://", the maximum length
> > of this string could actually be 255 - 3 /*header*/ + 8 /* the length
> > of the prefix, which is stripped */ => 260 bytes.
> >
> > I need to rework the validation here a bit. I would like to avoid for
> > userspace to explicitly set the scheme constants directly, as just
> > piping in the string seems so much more ergonomic.
> >
> >>
> >> Regards,
> >>
> >> Andrzej
> >
> > Thanks a lot for your review!
> >
> > I hope my response to your initial concerns was not too long and windy.
> >
> > I will post an updated version of my patch within the next few days.
> >
> > Regards,
> > Jó
> >
> >
> >>
> >>> + }
> >>> +
> >>> if (gi->use_os_desc) {
> >>> cdev->use_os_string = true;
> >>> cdev->b_vendor_code = gi->b_vendor_code;
> >>> @@ -1605,6 +1746,10 @@ static struct config_group *gadgets_make(
> >>> &os_desc_type);
> >>> configfs_add_default_group(&gi->os_desc_group, &gi->group);
> >>>
> >>> + config_group_init_type_name(&gi->webusb_group, "webusb",
> >>> + &webusb_type);
> >>> + configfs_add_default_group(&gi->webusb_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 43ac3fa760db..eb6fac5bbcde 100644
> >>> --- a/include/linux/usb/composite.h
> >>> +++ b/include/linux/usb/composite.h
> >>> @@ -474,6 +474,12 @@ struct usb_composite_dev {
> >>> struct usb_configuration *os_desc_config;
> >>> unsigned int use_os_string:1;
> >>>
> >>> + /* WebUSB */
> >>> + u16 bcd_webusb_version;
> >>> + u8 b_webusb_vendor_code;
> >>> + char landing_page[255];
> >>> + unsigned int use_webusb:1;
> >>> +
> >>> /* private: */
> >>> /* internals */
> >>> unsigned int suspended:1;
> >>> diff --git a/include/uapi/linux/usb/ch9.h b/include/uapi/linux/usb/ch9.h
> >>> index 31fcfa084e63..3a36550297bc 100644
> >>> --- a/include/uapi/linux/usb/ch9.h
> >>> +++ b/include/uapi/linux/usb/ch9.h
> >>> @@ -947,6 +947,39 @@ struct usb_ss_container_id_descriptor {
> >>>
> >>> #define USB_DT_USB_SS_CONTN_ID_SIZE 20
> >>>
> >>> +/*
> >>> + * Platform Device Capability descriptor: Defines platform specific device
> >>> + * capabilities
> >>> + */
> >>> +#define USB_PLAT_DEV_CAP_TYPE 5
> >>> +struct usb_plat_dev_cap_descriptor {
> >>> + __u8 bLength;
> >>> + __u8 bDescriptorType;
> >>> + __u8 bDevCapabilityType;
> >>> + __u8 bReserved;
> >>> + __u8 UUID[16];
> >>> +} __attribute__((packed));
> >>> +
> >>> +#define USB_DT_USB_PLAT_DEV_CAP_SIZE 20
> >>> +
> >>> +/*
> >>> + * WebUSB Platform Capability descriptor: A device announces support for the
> >>> + * WebUSB command set by including the following Platform Descriptor in its
> >>> + * Binary Object Store
> >>> + * https://wicg.github.io/webusb/#webusb-platform-capability-descriptor
> >>> + */
> >>> +struct usb_webusb_cap_descriptor {
> >>> + __u8 bLength;
> >>> + __u8 bDescriptorType;
> >>> + __u8 bDevCapabilityType;
> >>> + __u8 bReserved;
> >>> + __u8 UUID[16];
> >>> + __u16 bcdVersion;
> >>> + __u8 bVendorCode;
> >>> + __u8 iLandingPage;
> >>> +} __attribute__((packed));
> >>> +#define USB_DT_WEBUSB_SIZE (USB_DT_USB_PLAT_DEV_CAP_SIZE + 4)
> >>> +
> >>> /*
> >>> * SuperSpeed Plus USB Capability descriptor: Defines the set of
> >>> * SuperSpeed Plus USB specific device level capabilities
> >>
>

2023-01-13 01:03:56

by Jó Ágila Bitsch

[permalink] [raw]
Subject: [PATCH v4] usb: gadget: add WebUSB landing page support

There is a custom (non-USB IF) extension to the USB standard:

https://wicg.github.io/webusb/

This specification is published under the W3C Community Contributor
Agreement, which in particular allows to implement the specification
without any royalties.

The specification allows USB gadgets to announce an URL to landing
page and describes a Javascript interface for websites to interact
with the USB gadget, if the user allows it. It is currently
supported by Chromium-based browsers, such as Chrome, Edge and
Opera on all major operating systems including Linux.

This patch adds optional support for Linux-based USB gadgets
wishing to expose such a landing page.

During device enumeration, a host recognizes that the announced
USB version is at least 2.01, which means, that there are BOS
descriptors available. The device than announces WebUSB support
using a platform device capability. This includes a vendor code
under which the landing page URL can be retrieved using a
vendor-specific request.

Previously, the BOS descriptors would unconditionally include an
LPM related descriptor, as BOS descriptors were only ever sent
when the device was LPM capable. As this is no longer the case,
this patch puts this descriptor behind a lpm_capable condition.

Usage is modeled after os_desc descriptors:
echo 1 > webusb/use
echo "https://www.kernel.org" > webusb/landingPage

lsusb will report the device with the following lines:
Platform Device Capability:
bLength 24
bDescriptorType 16
bDevCapabilityType 5
bReserved 0
PlatformCapabilityUUID {3408b638-09a9-47a0-8bfd-a0768815b665}
WebUSB:
bcdVersion 1.00
bVendorCode 0
iLandingPage 1 https://www.kernel.org

Signed-off-by: J? ?gila Bitsch <[email protected]>
---
V3 -> V4: Addressed comments by Andrzej Pietrasiewicz and Alan Stern
* Moved WebUSB specific structs and constants to its own header file
* added structs for all descriptors sent
* added the magic numbers to the new header file with links to the specification
* added length validation to landingPage sysfs interface
V2 -> V3: improved commit message to include additional condition in desc_bos as per comment
by Alan Stern
V1 -> V2: cleaned up coding style, made URL scheme comparison case insensitive, addressed review
comments by Alan Stern
V0 -> V1: use sysfs_emit instead of sprintf and use lock in webusb_bcdVersion_store, addressed review
comments by Christophe JAILLET

Checkpatch currently gives a warning if MAINTAINERS should be updated. I didn't because I feel this patch is too small for adding me here.
Nevertheless, if you think I should, let me know.

Documentation/ABI/testing/configfs-usb-gadget | 13 ++
drivers/usb/gadget/composite.c | 102 +++++++++--
drivers/usb/gadget/configfs.c | 166 ++++++++++++++++++
include/linux/usb/composite.h | 7 +
include/linux/usb/webusb.h | 83 +++++++++
include/uapi/linux/usb/ch9.h | 16 ++
6 files changed, 377 insertions(+), 10 deletions(-)
create mode 100644 include/linux/usb/webusb.h

diff --git a/Documentation/ABI/testing/configfs-usb-gadget b/Documentation/ABI/testing/configfs-usb-gadget
index b7943aa7e997..a8bb896def54 100644
--- a/Documentation/ABI/testing/configfs-usb-gadget
+++ b/Documentation/ABI/testing/configfs-usb-gadget
@@ -143,3 +143,16 @@ Description:
qw_sign an identifier to be reported as "OS String"
proper
============= ===============================================
+
+What: /config/usb-gadget/gadget/webusb
+Date: Dec 2022
+KernelVersion: 6.3
+Description:
+ This group contains "WebUSB" extension handling attributes.
+
+ ============= ===============================================
+ use flag turning "WebUSB" support on/off
+ bcdVersion bcd WebUSB specification version number
+ bVendorCode one-byte value used for custom per-device
+ landingPage UTF-8 encoded URL of the device's landing page
+ ============= ===============================================
diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c
index 403563c06477..8e2603688016 100644
--- a/drivers/usb/gadget/composite.c
+++ b/drivers/usb/gadget/composite.c
@@ -14,9 +14,11 @@
#include <linux/device.h>
#include <linux/utsname.h>
#include <linux/bitfield.h>
+#include <linux/uuid.h>

#include <linux/usb/composite.h>
#include <linux/usb/otg.h>
+#include <linux/usb/webusb.h>
#include <asm/unaligned.h>

#include "u_os_desc.h"
@@ -713,14 +715,16 @@ static int bos_desc(struct usb_composite_dev *cdev)
* A SuperSpeed device shall include the USB2.0 extension descriptor
* and shall support LPM when operating in USB2.0 HS mode.
*/
- usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
- bos->bNumDeviceCaps++;
- le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
- usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
- usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
- usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
- usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT |
- USB_BESL_SUPPORT | besl);
+ if (cdev->gadget->lpm_capable) {
+ usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
+ bos->bNumDeviceCaps++;
+ le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
+ usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
+ usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
+ usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
+ usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT |
+ USB_BESL_SUPPORT | besl);
+ }

/*
* The Superspeed USB Capability descriptor shall be implemented by all
@@ -821,6 +825,37 @@ static int bos_desc(struct usb_composite_dev *cdev)
}
}

+ /* The WebUSB Platform Capability descriptor */
+ if (cdev->use_webusb) {
+ struct usb_plat_dev_cap_descriptor *webusb_cap;
+ struct usb_webusb_cap_data *webusb_cap_data;
+ uuid_t webusb_uuid = WEBUSB_UUID;
+
+ webusb_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
+ webusb_cap_data = (struct usb_webusb_cap_data *) webusb_cap->CapabilityData;
+ bos->bNumDeviceCaps++;
+ le16_add_cpu(&bos->wTotalLength,
+ USB_DT_USB_PLAT_DEV_CAP_SIZE(USB_WEBUSB_CAP_DATA_SIZE));
+
+ webusb_cap->bLength = USB_DT_USB_PLAT_DEV_CAP_SIZE(USB_WEBUSB_CAP_DATA_SIZE);
+ webusb_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
+ webusb_cap->bDevCapabilityType = USB_PLAT_DEV_CAP_TYPE;
+ webusb_cap->bReserved = 0;
+ export_uuid(webusb_cap->UUID, &webusb_uuid);
+
+ if (cdev->bcd_webusb_version != 0)
+ webusb_cap_data->bcdVersion = cpu_to_le16(cdev->bcd_webusb_version);
+ else
+ webusb_cap_data->bcdVersion = WEBUSB_VERSION_1_00;
+
+ webusb_cap_data->bVendorCode = cdev->b_webusb_vendor_code;
+
+ if (strnlen(cdev->landing_page, sizeof(cdev->landing_page)) > 0)
+ webusb_cap_data->iLandingPage = WEBUSB_LANDING_PAGE_PRESENT;
+ else
+ webusb_cap_data->iLandingPage = WEBUSB_LANDING_PAGE_NOT_PRESENT;
+ }
+
return le16_to_cpu(bos->wTotalLength);
}

@@ -1744,7 +1779,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
cdev->desc.bcdUSB = cpu_to_le16(0x0210);
}
} else {
- if (gadget->lpm_capable)
+ if (gadget->lpm_capable || cdev->use_webusb)
cdev->desc.bcdUSB = cpu_to_le16(0x0201);
else
cdev->desc.bcdUSB = cpu_to_le16(0x0200);
@@ -1779,7 +1814,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
break;
case USB_DT_BOS:
if (gadget_is_superspeed(gadget) ||
- gadget->lpm_capable) {
+ gadget->lpm_capable || cdev->use_webusb) {
value = bos_desc(cdev);
value = min(w_length, (u16) value);
}
@@ -2013,6 +2048,53 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
goto check_value;
}

+ /*
+ * WebUSB URL descriptor handling, following:
+ * https://wicg.github.io/webusb/#device-requests
+ */
+ if (cdev->use_webusb &&
+ ctrl->bRequestType == (USB_DIR_IN | USB_TYPE_VENDOR) &&
+ w_index == WEBUSB_GET_URL &&
+ w_value == WEBUSB_LANDING_PAGE_PRESENT &&
+ ctrl->bRequest == cdev->b_webusb_vendor_code) {
+ unsigned int landing_page_length;
+ unsigned int landing_page_offset;
+ struct webusb_url_descriptor *url_descriptor =
+ (struct webusb_url_descriptor *)cdev->req->buf;
+
+ url_descriptor->bDescriptorType = WEBUSB_URL_DESCRIPTOR_TYPE;
+
+ if (strncasecmp(cdev->landing_page, "https://", 8) == 0) {
+ landing_page_offset = 8;
+ url_descriptor->bScheme = WEBUSB_URL_SCHEME_HTTPS;
+ } else if (strncasecmp(cdev->landing_page, "http://", 7) == 0) {
+ landing_page_offset = 7;
+ url_descriptor->bScheme = WEBUSB_URL_SCHEME_HTTP;
+ } else {
+ landing_page_offset = 0;
+ url_descriptor->bScheme = WEBUSB_URL_SCHEME_NONE;
+ }
+
+ landing_page_length = strnlen(cdev->landing_page,
+ sizeof(url_descriptor->URL)
+ - WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + landing_page_offset);
+
+ if (ctrl->wLength < WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH
+ + landing_page_length)
+ landing_page_length = ctrl->wLength
+ - WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + landing_page_offset;
+
+ memcpy(url_descriptor->URL,
+ cdev->landing_page + landing_page_offset,
+ landing_page_length - landing_page_offset);
+ url_descriptor->bLength = landing_page_length
+ - landing_page_offset + WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH;
+
+ value = url_descriptor->bLength;
+
+ goto check_value;
+ }
+
VDBG(cdev,
"non-core control req%02x.%02x v%04x i%04x l%d\n",
ctrl->bRequestType, ctrl->bRequest,
diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c
index 96121d1c8df4..4a1236063c19 100644
--- a/drivers/usb/gadget/configfs.c
+++ b/drivers/usb/gadget/configfs.c
@@ -7,6 +7,7 @@
#include <linux/nls.h>
#include <linux/usb/composite.h>
#include <linux/usb/gadget_configfs.h>
+#include <linux/usb/webusb.h>
#include "configfs.h"
#include "u_f.h"
#include "u_os_desc.h"
@@ -39,6 +40,7 @@ struct gadget_info {
struct config_group configs_group;
struct config_group strings_group;
struct config_group os_desc_group;
+ struct config_group webusb_group;

struct mutex lock;
struct usb_gadget_strings *gstrings[MAX_USB_STRING_LANGS + 1];
@@ -50,6 +52,11 @@ struct gadget_info {
bool use_os_desc;
char b_vendor_code;
char qw_sign[OS_STRING_QW_SIGN_LEN];
+ bool use_webusb;
+ u16 bcd_webusb_version;
+ u8 b_webusb_vendor_code;
+ char landing_page[WEBUSB_URL_RAW_MAX_LENGTH];
+
spinlock_t spinlock;
bool unbind;
};
@@ -780,6 +787,154 @@ static void gadget_strings_attr_release(struct config_item *item)
USB_CONFIG_STRING_RW_OPS(gadget_strings);
USB_CONFIG_STRINGS_LANG(gadget_strings, gadget_info);

+static inline struct gadget_info *webusb_item_to_gadget_info(
+ struct config_item *item)
+{
+ return container_of(to_config_group(item),
+ struct gadget_info, webusb_group);
+}
+
+static ssize_t webusb_use_show(struct config_item *item, char *page)
+{
+ return sysfs_emit(page, "%d\n",
+ webusb_item_to_gadget_info(item)->use_webusb);
+}
+
+static ssize_t webusb_use_store(struct config_item *item, const char *page,
+ size_t len)
+{
+ struct gadget_info *gi = webusb_item_to_gadget_info(item);
+ int ret;
+ bool use;
+
+ mutex_lock(&gi->lock);
+ ret = kstrtobool(page, &use);
+ if (!ret) {
+ gi->use_webusb = use;
+ ret = len;
+ }
+ mutex_unlock(&gi->lock);
+
+ return ret;
+}
+
+static ssize_t webusb_bcdVersion_show(struct config_item *item, char *page)
+{
+ return sysfs_emit(page, "0x%04x\n",
+ webusb_item_to_gadget_info(item)->bcd_webusb_version);
+}
+
+static ssize_t webusb_bcdVersion_store(struct config_item *item,
+ const char *page, size_t len)
+{
+ struct gadget_info *gi = webusb_item_to_gadget_info(item);
+ u16 bcdVersion;
+ int ret;
+
+ mutex_lock(&gi->lock);
+ ret = kstrtou16(page, 0, &bcdVersion);
+ if (ret)
+ goto out;
+ ret = is_valid_bcd(bcdVersion);
+ if (ret)
+ goto out;
+
+ gi->bcd_webusb_version = bcdVersion;
+ ret = len;
+
+out:
+ mutex_unlock(&gi->lock);
+
+ return ret;
+}
+
+static ssize_t webusb_bVendorCode_show(struct config_item *item, char *page)
+{
+ return sysfs_emit(page, "0x%02x\n",
+ webusb_item_to_gadget_info(item)->b_webusb_vendor_code);
+}
+
+static ssize_t webusb_bVendorCode_store(struct config_item *item,
+ const char *page, size_t len)
+{
+ struct gadget_info *gi = webusb_item_to_gadget_info(item);
+ int ret;
+ u8 b_vendor_code;
+
+ mutex_lock(&gi->lock);
+ ret = kstrtou8(page, 0, &b_vendor_code);
+ if (!ret) {
+ gi->b_webusb_vendor_code = b_vendor_code;
+ ret = len;
+ }
+ mutex_unlock(&gi->lock);
+
+ return ret;
+}
+
+static ssize_t webusb_landingPage_show(struct config_item *item, char *page)
+{
+ return sysfs_emit(page, "%s\n", webusb_item_to_gadget_info(item)->landing_page);
+}
+
+static ssize_t webusb_landingPage_store(struct config_item *item, const char *page,
+ size_t len)
+{
+ struct gadget_info *gi = webusb_item_to_gadget_info(item);
+ unsigned int bytes_to_strip = 0;
+ int l = len;
+
+ if (page[l - 1] == '\n') {
+ --l;
+ ++bytes_to_strip;
+ }
+
+ if (l > sizeof(gi->landing_page)) {
+ pr_err("webusb: landingPage URL too long\n");
+ return -EINVAL;
+ }
+
+ // validation
+ if (strncasecmp(page, "https://", 8) == 0)
+ bytes_to_strip = 8;
+ else if (strncasecmp(page, "http://", 7) == 0)
+ bytes_to_strip = 7;
+ else
+ bytes_to_strip = 0;
+
+ if (l > U8_MAX - WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + bytes_to_strip) {
+ pr_err("webusb: landingPage URL %d bytes too long for given URL scheme\n",
+ l - U8_MAX + WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH - bytes_to_strip);
+ return -EINVAL;
+ }
+
+ mutex_lock(&gi->lock);
+ // ensure 0 bytes are set, in case the new landing page is shorter then the old one.
+ memset(gi->landing_page, 0, sizeof(gi->landing_page));
+ memcpy(gi->landing_page, page, l);
+ mutex_unlock(&gi->lock);
+
+ return len;
+}
+
+CONFIGFS_ATTR(webusb_, use);
+CONFIGFS_ATTR(webusb_, bVendorCode);
+CONFIGFS_ATTR(webusb_, bcdVersion);
+CONFIGFS_ATTR(webusb_, landingPage);
+
+static struct configfs_attribute *webusb_attrs[] = {
+ &webusb_attr_use,
+ &webusb_attr_bcdVersion,
+ &webusb_attr_bVendorCode,
+ &webusb_attr_landingPage,
+ NULL,
+};
+
+static struct config_item_type webusb_type = {
+ .ct_attrs = webusb_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
static inline struct gadget_info *os_desc_item_to_gadget_info(
struct config_item *item)
{
@@ -1341,6 +1496,13 @@ static int configfs_composite_bind(struct usb_gadget *gadget,
gi->cdev.desc.iSerialNumber = s[USB_GADGET_SERIAL_IDX].id;
}

+ if (gi->use_webusb) {
+ cdev->use_webusb = true;
+ cdev->bcd_webusb_version = gi->bcd_webusb_version;
+ cdev->b_webusb_vendor_code = gi->b_webusb_vendor_code;
+ memcpy(cdev->landing_page, gi->landing_page, WEBUSB_URL_RAW_MAX_LENGTH);
+ }
+
if (gi->use_os_desc) {
cdev->use_os_string = true;
cdev->b_vendor_code = gi->b_vendor_code;
@@ -1605,6 +1767,10 @@ static struct config_group *gadgets_make(
&os_desc_type);
configfs_add_default_group(&gi->os_desc_group, &gi->group);

+ config_group_init_type_name(&gi->webusb_group, "webusb",
+ &webusb_type);
+ configfs_add_default_group(&gi->webusb_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 43ac3fa760db..91d22c3ed458 100644
--- a/include/linux/usb/composite.h
+++ b/include/linux/usb/composite.h
@@ -25,6 +25,7 @@
#include <linux/version.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
+#include <linux/usb/webusb.h>
#include <linux/log2.h>
#include <linux/configfs.h>

@@ -474,6 +475,12 @@ struct usb_composite_dev {
struct usb_configuration *os_desc_config;
unsigned int use_os_string:1;

+ /* WebUSB */
+ u16 bcd_webusb_version;
+ u8 b_webusb_vendor_code;
+ char landing_page[WEBUSB_URL_RAW_MAX_LENGTH];
+ unsigned int use_webusb:1;
+
/* private: */
/* internals */
unsigned int suspended:1;
diff --git a/include/linux/usb/webusb.h b/include/linux/usb/webusb.h
new file mode 100644
index 000000000000..b430d84357f3
--- /dev/null
+++ b/include/linux/usb/webusb.h
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * WebUSB descriptors and constants
+ *
+ * Copyright (C) 2023 J? ?gila Bitsch <[email protected]>
+ */
+
+#ifndef __LINUX_USB_WEBUSB_H
+#define __LINUX_USB_WEBUSB_H
+
+#include "uapi/linux/usb/ch9.h"
+
+/*
+ * little endian PlatformCapablityUUID for WebUSB
+ * 3408b638-09a9-47a0-8bfd-a0768815b665
+ * to identify Platform Device Capability descriptors as referring to WebUSB
+ *
+ * the UUID above MUST be sent over the wire as the byte sequence:
+ * {0x38, 0xB6, 0x08, 0x34, 0xA9, 0x09, 0xA0, 0x47, 0x8B, 0xFD, 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65}.
+ */
+#define WEBUSB_UUID \
+ UUID_INIT(0x38b60834, 0xa909, 0xa047, 0x8b, 0xfd, 0xa0, 0x76, 0x88, 0x15, 0xb6, 0x65)
+
+/*
+ * WebUSB Platform Capability data
+ *
+ * A device announces support for the
+ * WebUSB command set by including the following Platform Descriptor Data in its
+ * Binary Object Store associated with the WebUSB_UUID above.
+ * See: https://wicg.github.io/webusb/#webusb-platform-capability-descriptor
+ */
+struct usb_webusb_cap_data {
+ __le16 bcdVersion;
+#define WEBUSB_VERSION_1_00 cpu_to_le16(0x0100) /* currently only version 1.00 is defined */
+ u8 bVendorCode;
+ u8 iLandingPage;
+#define WEBUSB_LANDING_PAGE_NOT_PRESENT 0
+#define WEBUSB_LANDING_PAGE_PRESENT 1 /* we chose the fixed index 1 for the URL descriptor */
+} __packed;
+
+#define USB_WEBUSB_CAP_DATA_SIZE 4
+
+/*
+ * Get URL Request
+ *
+ * The request to fetch an URL is defined in https://wicg.github.io/webusb/#get-url as:
+ * bmRequestType: (USB_DIR_IN | USB_TYPE_VENDOR) = 11000000B
+ * bRequest: bVendorCode
+ * wValue: iLandingPage
+ * wIndex: GET_URL = 2
+ * wLength: Descriptor Length (typically U8_MAX = 255)
+ * Data: URL Descriptor
+ */
+#define WEBUSB_GET_URL 2
+
+/*
+ * This descriptor contains a single URL and is returned by the Get URL request.
+ *
+ * See: https://wicg.github.io/webusb/#url-descriptor
+ */
+struct webusb_url_descriptor {
+ u8 bLength;
+#define WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH 3
+ u8 bDescriptorType;
+#define WEBUSB_URL_DESCRIPTOR_TYPE 3
+ u8 bScheme;
+#define WEBUSB_URL_SCHEME_HTTP 0
+#define WEBUSB_URL_SCHEME_HTTPS 1
+#define WEBUSB_URL_SCHEME_NONE 255
+ u8 URL[U8_MAX - WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH];
+} __packed;
+
+/*
+ * Buffer size to hold the longest URL that can be in an URL descriptor
+ *
+ * The descriptor can be U8_MAX bytes long.
+ * WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH bytes are used for a header.
+ * Since the longest prefix that might be stripped is "https://", we may accommodate an additional
+ * 8 bytes.
+ */
+#define WEBUSB_URL_RAW_MAX_LENGTH (U8_MAX - WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + 8)
+
+#endif /* __LINUX_USB_USBNET_H */
diff --git a/include/uapi/linux/usb/ch9.h b/include/uapi/linux/usb/ch9.h
index 31fcfa084e63..b17e3a21b15f 100644
--- a/include/uapi/linux/usb/ch9.h
+++ b/include/uapi/linux/usb/ch9.h
@@ -947,6 +947,22 @@ struct usb_ss_container_id_descriptor {

#define USB_DT_USB_SS_CONTN_ID_SIZE 20

+/*
+ * Platform Device Capability descriptor: Defines platform specific device
+ * capabilities
+ */
+#define USB_PLAT_DEV_CAP_TYPE 5
+struct usb_plat_dev_cap_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bDevCapabilityType;
+ __u8 bReserved;
+ __u8 UUID[16];
+ __u8 CapabilityData[];
+} __attribute__((packed));
+
+#define USB_DT_USB_PLAT_DEV_CAP_SIZE(capability_data_size) (20 + capability_data_size)
+
/*
* SuperSpeed Plus USB Capability descriptor: Defines the set of
* SuperSpeed Plus USB specific device level capabilities
--
2.37.2