Merge pull request #4126 from FiriaCTO/webusb

WebUSB serial support (compile-time option, currently defaulted to OFF)
This commit is contained in:
Dan Halbert 2021-02-05 09:11:43 -05:00 committed by GitHub
commit 0a55cfbde8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 407 additions and 10 deletions

94
WEBUSB_README.md Normal file
View File

@ -0,0 +1,94 @@
<!--
SPDX-FileCopyrightText: 2014 MicroPython & CircuitPython contributors (https://github.com/adafruit/circuitpython/graphs/contributors)
SPDX-License-Identifier: MIT
-->
# WebUSB Serial Support
To date, this has only been tested on one port (esp32s2), on one board (espressif_kaluga_1).
## What it does
If you have ever used CircuitPython on a platform with a graphical LCD display, you have probably
already seen multiple "consoles" in use (although the LCD console is "output only").
New compile-time option CIRCUITPY_USB_VENDOR enables an additional "console" that can be used in
parallel with the original (CDC) serial console.
Web pages that support the WebUSB standard can connect to the "vendor" interface and activate
this WebUSB serial console at any time.
You can type into either console, and CircuitPython output is sent to all active consoles.
One example of a web page you can use to test drive this feature can be found at:
https://adafruit.github.io/Adafruit_TinyUSB_Arduino/examples/webusb-serial/index.html
## How to enable
Update your platform's mpconfigboard.mk file to enable and disable specific types of USB interfaces.
CIRCUITPY_USB_HID = xxx
CIRCUITPY_USB_MIDI = xxx
CIRCUITPY_USB_VENDOR = xxx
On at least some of the hardware platforms, the maximum number of USB endpoints is fixed.
For example, on the ESP32S2, you must pick only one of the above 3 interfaces to be enabled.
Original espressif_kaluga_1 mpconfigboard.mk settings:
CIRCUITPY_USB_HID = 1
CIRCUITPY_USB_MIDI = 0
CIRCUITPY_USB_VENDOR = 0
Settings to enable WebUSB instead:
CIRCUITPY_USB_HID = 0
CIRCUITPY_USB_MIDI = 0
CIRCUITPY_USB_VENDOR = 1
Notice that to enable VENDOR on ESP32-S2, we had to give up HID. There may be platforms that can have both, or even all three.
## Implementation Notes
CircuitPython uses the tinyusb library.
The tinyusb library already has support for WebUSB serial.
The tinyusb examples already include a "WebUSB serial" example.
Sidenote - The use of the term "vendor" instead of "WebUSB" was done to match tinyusb.
Basically, this feature was ported into CircuitPython by pulling code snippets out of the
tinyusb example, and putting them where they best belonged in the CircuitPython codebase.
There was one complication:
tinyusb uses C preprocessor macros to define things like USB descriptors.
CircuitPython uses a Python program (tools/gen_usb_descriptor.py) to create USB descriptors (etc.)
using "helper objects" from another repo (adafruit_usb_descriptor). This means some of the example
code had to be adapted to the new programing model, and gen_usb_descriptor gained new command-line
options to control the generated code.
The generated files go into the "build" directory, look for autogen_usb_descriptor.c and
genhdr/autogen_usb_descriptor.h.
Also worth pointing out - the re-use of the CDC connect/disconnect mechanism is not actually part
of the WebUSB standard, it's more of "common idiom". We make use of it here because we need to know
when we should be paying attention to the WebUSB serial interface, and when we should ignore it..
## Possible future work areas
The current code uses the existing Python infrastructure to create the Interface descriptor, but
simply outputs the code snippets from the original tinyusb demo code to create the WEBUSB_URL,
BOS, and MS_OS_20 descriptors. I suppose additional work could be done to add these to the
adafruit_usb_descriptor project, and then gen_usb_descriptor.py could be modified to make use
of them.
Program gen_usb_descriptor.py creates objects for most interface types, regardless of whether or
not they are actually enabled. This increases the size of a generated string table. I made the
new vendor-interface-related code not do this (because some of the ARM platforms would no longer
build), but I did not go back and do this for the other interface types (CDC, MIDI, HID, etc.)
Some FLASH savings are probably possible if this is done.

View File

@ -46,6 +46,7 @@ Full Table of Contents
../BUILDING
../CODE_OF_CONDUCT
../license.rst
../WEBUSB_README
Indices and tables
==================

View File

@ -164,7 +164,9 @@ LIBS += -lm
endif
# TinyUSB defines
CFLAGS += -DCFG_TUSB_MCU=OPT_MCU_ESP32S2 -DCFG_TUSB_OS=OPT_OS_FREERTOS -DCFG_TUD_CDC_RX_BUFSIZE=1024 -DCFG_TUD_CDC_TX_BUFSIZE=1024 -DCFG_TUD_MSC_BUFSIZE=4096 -DCFG_TUD_MIDI_RX_BUFSIZE=128 -DCFG_TUD_MIDI_TX_BUFSIZE=128
CFLAGS += -DCFG_TUSB_MCU=OPT_MCU_ESP32S2 -DCFG_TUSB_OS=OPT_OS_FREERTOS -DCFG_TUD_CDC_RX_BUFSIZE=1024 -DCFG_TUD_CDC_TX_BUFSIZE=1024
CFLAGS += -DCFG_TUD_MSC_BUFSIZE=4096 -DCFG_TUD_MIDI_RX_BUFSIZE=128 -DCFG_TUD_MIDI_TX_BUFSIZE=128
CFLAGS += -DCFG_TUD_VENDOR_RX_BUFSIZE=128 -DCFG_TUD_VENDOR_TX_BUFSIZE=128
######################################

View File

@ -14,4 +14,10 @@ CIRCUITPY_ESP_FLASH_MODE=dio
CIRCUITPY_ESP_FLASH_FREQ=80m
CIRCUITPY_ESP_FLASH_SIZE=4MB
# We only have enough endpoints available in hardware to
# enable ONE of these at a time.
CIRCUITPY_USB_MIDI = 1
CIRCUITPY_USB_HID = 0
CIRCUITPY_USB_VENDOR = 0
CIRCUITPY_MODULE=wrover

View File

@ -29,7 +29,10 @@ CIRCUITPY_I2CPERIPHERAL = 0
CIRCUITPY_ROTARYIO = 1
CIRCUITPY_NVM = 1
# We don't have enough endpoints to include MIDI.
CIRCUITPY_USB_MIDI = 0
CIRCUITPY_USB_MIDI ?= 0
CIRCUITPY_USB_HID ?= 1
# We have borrowed the VENDOR nomenclature from tinyusb. VENDOR AKA WEBUSB
CIRCUITPY_USB_VENDOR ?= 0
CIRCUITPY_WIFI = 1
CIRCUITPY_WATCHDOG ?= 1
CIRCUITPY_ESPIDF = 1

View File

@ -292,6 +292,9 @@ endif
ifeq ($(CIRCUITPY_USB_MIDI),1)
SRC_PATTERNS += usb_midi/%
endif
ifeq ($(CIRCUITPY_USB_VENDOR),1)
SRC_PATTERNS += usb_vendor/%
endif
ifeq ($(CIRCUITPY_USTACK),1)
SRC_PATTERNS += ustack/%
endif

View File

@ -288,6 +288,12 @@ CFLAGS += -DCIRCUITPY_USB_HID=$(CIRCUITPY_USB_HID)
CIRCUITPY_USB_MIDI ?= 1
CFLAGS += -DCIRCUITPY_USB_MIDI=$(CIRCUITPY_USB_MIDI)
# Defaulting this to OFF initially because it has only been tested on a
# limited number of platforms, and the other platforms do not have this
# setting in their mpconfigport.mk and/or mpconfigboard.mk files yet.
CIRCUITPY_USB_VENDOR ?= 0
CFLAGS += -DCIRCUITPY_USB_VENDOR=$(CIRCUITPY_USB_VENDOR)
CIRCUITPY_PEW ?= 0
CFLAGS += -DCIRCUITPY_PEW=$(CIRCUITPY_PEW)

View File

@ -47,6 +47,10 @@ busio_uart_obj_t debug_uart;
byte buf_array[64];
#endif
#if CIRCUITPY_USB_VENDOR
bool tud_vendor_connected(void);
#endif
void serial_early_init(void) {
#if defined(DEBUG_UART_TX) && defined(DEBUG_UART_RX)
debug_uart.base.type = &busio_uart_type;
@ -66,6 +70,12 @@ void serial_init(void) {
}
bool serial_connected(void) {
#if CIRCUITPY_USB_VENDOR
if (tud_vendor_connected()) {
return true;
}
#endif
#if defined(DEBUG_UART_TX) && defined(DEBUG_UART_RX)
return true;
#else
@ -74,6 +84,14 @@ bool serial_connected(void) {
}
char serial_read(void) {
#if CIRCUITPY_USB_VENDOR
if (tud_vendor_connected() && tud_vendor_available() > 0) {
char tiny_buffer;
tud_vendor_read(&tiny_buffer, 1);
return tiny_buffer;
}
#endif
#if defined(DEBUG_UART_TX) && defined(DEBUG_UART_RX)
if (tud_cdc_connected() && tud_cdc_available() > 0) {
return (char) tud_cdc_read_char();
@ -88,6 +106,12 @@ char serial_read(void) {
}
bool serial_bytes_available(void) {
#if CIRCUITPY_USB_VENDOR
if (tud_vendor_connected() && tud_vendor_available() > 0) {
return true;
}
#endif
#if defined(DEBUG_UART_TX) && defined(DEBUG_UART_RX)
return common_hal_busio_uart_rx_characters_available(&debug_uart) || (tud_cdc_available() > 0);
#else
@ -104,6 +128,12 @@ void serial_write_substring(const char* text, uint32_t length) {
common_hal_terminalio_terminal_write(&supervisor_terminal, (const uint8_t*) text, length, &errcode);
#endif
#if CIRCUITPY_USB_VENDOR
if (tud_vendor_connected()) {
tud_vendor_write(text, length);
}
#endif
uint32_t count = 0;
while (count < length && tud_cdc_connected()) {
count += tud_cdc_write(text + count, length - count);

View File

@ -68,6 +68,7 @@
#define CFG_TUD_MSC 1
#define CFG_TUD_HID CIRCUITPY_USB_HID
#define CFG_TUD_MIDI CIRCUITPY_USB_MIDI
#define CFG_TUD_VENDOR CIRCUITPY_USB_VENDOR
#define CFG_TUD_CUSTOM_CLASS 0
/*------------------------------------------------------------------*/

View File

@ -37,6 +37,19 @@
#include "tusb.h"
#if CIRCUITPY_USB_VENDOR
#include "genhdr/autogen_usb_descriptor.h"
// The WebUSB support being conditionally added to this file is based on the
// tinyusb demo examples/device/webusb_serial.
extern const tusb_desc_webusb_url_t desc_webusb_url;
static bool web_serial_connected = false;
#endif
// Serial number as hex characters. This writes directly to the USB
// descriptor.
extern uint16_t usb_serial_number[1 + COMMON_HAL_MCU_PROCESSOR_UID_LENGTH * 2];
@ -145,6 +158,62 @@ void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) {
}
}
#if CIRCUITPY_USB_VENDOR
//--------------------------------------------------------------------+
// WebUSB use vendor class
//--------------------------------------------------------------------+
bool tud_vendor_connected(void)
{
return web_serial_connected;
}
// Invoked when a control transfer occurred on an interface of this class
// Driver response accordingly to the request and the transfer stage (setup/data/ack)
// return false to stall control endpoint (e.g unsupported request)
bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request)
{
// nothing to with DATA & ACK stage
if (stage != CONTROL_STAGE_SETUP ) return true;
switch (request->bRequest)
{
case VENDOR_REQUEST_WEBUSB:
// match vendor request in BOS descriptor
// Get landing page url
return tud_control_xfer(rhport, request, (void*) &desc_webusb_url, desc_webusb_url.bLength);
case VENDOR_REQUEST_MICROSOFT:
if ( request->wIndex == 7 )
{
// Get Microsoft OS 2.0 compatible descriptor
uint16_t total_len;
memcpy(&total_len, desc_ms_os_20+8, 2);
return tud_control_xfer(rhport, request, (void*) desc_ms_os_20, total_len);
} else
{
return false;
}
case 0x22:
// Webserial simulate the CDC_REQUEST_SET_CONTROL_LINE_STATE (0x22) to
// connect and disconnect.
web_serial_connected = (request->wValue != 0);
// response with status OK
return tud_control_status(rhport, request);
default:
// stall unknown request
return false;
}
return true;
}
#endif CIRCUITPY_USB_VENDOR
#if MICROPY_KBD_EXCEPTION
/**

View File

@ -102,6 +102,11 @@ else
shared-module/usb_midi/PortOut.c
endif
ifeq ($(CIRCUITPY_USB_VENDOR), 1)
SRC_SUPERVISOR += \
lib/tinyusb/src/class/vendor/vendor_device.c
endif
CFLAGS += -DUSB_AVAILABLE
endif
@ -119,6 +124,12 @@ ifndef USB_INTERFACE_NAME
USB_INTERFACE_NAME = "CircuitPython"
endif
# In the following URL, don't include the https:// prefix.
# It gets added automatically.
ifndef USB_WEBUSB_URL
USB_WEBUSB_URL = "circuitpython.org"
endif
USB_DEVICES_COMPUTED := CDC,MSC
ifeq ($(CIRCUITPY_USB_MIDI),1)
USB_DEVICES_COMPUTED := $(USB_DEVICES_COMPUTED),AUDIO
@ -126,6 +137,9 @@ endif
ifeq ($(CIRCUITPY_USB_HID),1)
USB_DEVICES_COMPUTED := $(USB_DEVICES_COMPUTED),HID
endif
ifeq ($(CIRCUITPY_USB_VENDOR),1)
USB_DEVICES_COMPUTED := $(USB_DEVICES_COMPUTED),VENDOR
endif
USB_DEVICES ?= "$(USB_DEVICES_COMPUTED)"
ifndef USB_HID_DEVICES
@ -198,6 +212,12 @@ USB_DESCRIPTOR_ARGS = \
--output_c_file $(BUILD)/autogen_usb_descriptor.c\
--output_h_file $(BUILD)/genhdr/autogen_usb_descriptor.h
ifeq ($(CIRCUITPY_USB_VENDOR), 1)
USB_DESCRIPTOR_ARGS += \
--vendor_ep_num_out 0 --vendor_ep_num_in 0 \
--webusb_url $(USB_WEBUSB_URL)
endif
ifeq ($(USB_RENUMBER_ENDPOINTS), 0)
USB_DESCRIPTOR_ARGS += --no-renumber_endpoints
endif

View File

@ -13,7 +13,7 @@ from adafruit_usb_descriptor import audio, audio10, cdc, hid, midi, msc, standar
import hid_report_descriptors
DEFAULT_INTERFACE_NAME = 'CircuitPython'
ALL_DEVICES='CDC,MSC,AUDIO,HID'
ALL_DEVICES='CDC,MSC,AUDIO,HID,VENDOR'
ALL_DEVICES_SET=frozenset(ALL_DEVICES.split(','))
DEFAULT_DEVICES='CDC,MSC,AUDIO,HID'
@ -22,6 +22,9 @@ ALL_HID_DEVICES_SET=frozenset(ALL_HID_DEVICES.split(','))
# Digitizer works on Linux but conflicts with mouse, so omit it.
DEFAULT_HID_DEVICES='KEYBOARD,MOUSE,CONSUMER,GAMEPAD'
# In the following URL, don't include the https:// because that prefix gets added automatically
DEFAULT_WEBUSB_URL = 'circuitpython.org' # In the future, this may become a specific landing page
parser = argparse.ArgumentParser(description='Generate USB descriptors.')
parser.add_argument('--highspeed', default=False, action='store_true',
help='descriptor for highspeed device')
@ -62,6 +65,13 @@ parser.add_argument('--midi_ep_num_out', type=int, default=0,
help='endpoint number of MIDI OUT')
parser.add_argument('--midi_ep_num_in', type=int, default=0,
help='endpoint number of MIDI IN')
parser.add_argument('--webusb_url', type=str,
help='The URL to include in the WebUSB URL Descriptor',
default=DEFAULT_WEBUSB_URL)
parser.add_argument('--vendor_ep_num_out', type=int, default=0,
help='endpoint number of VENDOR OUT')
parser.add_argument('--vendor_ep_num_in', type=int, default=0,
help='endpoint number of VENDOR IN')
parser.add_argument('--max_ep', type=int, default=0,
help='total number of endpoints available')
parser.add_argument('--output_c_file', type=argparse.FileType('w', encoding='UTF-8'), required=True)
@ -89,21 +99,27 @@ if not args.renumber_endpoints:
if 'MSC' in args.devices:
if args.msc_ep_num_out == 0:
raise ValueError("MSC endpoint OUT number must not be 0")
elif args.msc_ep_num_in == 0:
elif args.msc_ep_num_in == 0:
raise ValueError("MSC endpoint IN number must not be 0")
if 'HID' in args.devices:
if args.args.hid_ep_num_out == 0:
raise ValueError("HID endpoint OUT number must not be 0")
elif args.hid_ep_num_in == 0:
elif args.hid_ep_num_in == 0:
raise ValueError("HID endpoint IN number must not be 0")
if 'AUDIO' in args.devices:
if args.args.midi_ep_num_out == 0:
raise ValueError("MIDI endpoint OUT number must not be 0")
elif args.midi_ep_num_in == 0:
elif args.midi_ep_num_in == 0:
raise ValueError("MIDI endpoint IN number must not be 0")
if 'VENDOR' in args.devices:
if args.vendor_ep_num_out == 0:
raise ValueError("VENDOR endpoint OUT number must not be 0")
elif args.vendor_ep_num_in == 0:
raise ValueError("VENDOR endpoint IN number must not be 0")
class StringIndex:
"""Assign a monotonically increasing index to each unique string. Start with 0."""
string_to_index = {}
@ -359,6 +375,35 @@ audio_control_interface = standard.InterfaceDescriptor(
# Audio streaming interfaces must occur before MIDI ones.
audio_interfaces = [audio_control_interface] + cs_ac_interface.audio_streaming_interfaces + cs_ac_interface.midi_streaming_interfaces
# Vendor-specific interface, for example WebUSB
vendor_endpoint_in_descriptor = standard.EndpointDescriptor(
description="VENDOR in",
bEndpointAddress=args.vendor_ep_num_in | standard.EndpointDescriptor.DIRECTION_IN,
bmAttributes=standard.EndpointDescriptor.TYPE_BULK,
bInterval=16)
vendor_endpoint_out_descriptor = standard.EndpointDescriptor(
description="VENDOR out",
bEndpointAddress=args.vendor_ep_num_out | standard.EndpointDescriptor.DIRECTION_OUT,
bmAttributes=standard.EndpointDescriptor.TYPE_BULK,
bInterval=16)
# We do the following conditionally to avoid adding unused entries to the StringIndex table
if 'VENDOR' in args.devices:
vendor_interface = standard.InterfaceDescriptor(
description="VENDOR",
bInterfaceClass=0xff, # Vendor-specific
bInterfaceSubClass=0x00,
bInterfaceProtocol=0x00,
iInterface=StringIndex.index("{} VENDOR".format(args.interface_name)),
subdescriptors=[
vendor_endpoint_in_descriptor,
vendor_endpoint_out_descriptor,
]
)
vendor_interfaces = [vendor_interface]
interfaces_to_join = []
if 'CDC' in args.devices:
@ -373,6 +418,9 @@ if 'HID' in args.devices:
if 'AUDIO' in args.devices:
interfaces_to_join.append(audio_interfaces)
if 'VENDOR' in args.devices:
interfaces_to_join.append(vendor_interfaces)
# util.join_interfaces() will renumber the endpoints to make them unique across descriptors,
# and renumber the interfaces in order. But we still need to fix up certain
# interface cross-references.
@ -425,6 +473,9 @@ if 'AUDIO' in args.devices:
# correct ordering.
descriptor_list.append(audio_control_interface)
if 'VENDOR' in args.devices:
descriptor_list.extend(vendor_interfaces)
# Finally, build the composite descriptor.
configuration = standard.ConfigurationDescriptor(
@ -444,6 +495,7 @@ h_file = args.output_h_file
c_file.write("""\
#include <stdint.h>
#include "tusb.h"
#include "py/objtuple.h"
#include "shared-bindings/usb_hid/Device.h"
#include "{H_FILE_NAME}"
@ -550,7 +602,7 @@ c_file.write("\n")
hid_descriptor_length = len(bytes(combined_hid_report_descriptor))
# Now we values we need for the .h file.
# Now the values we need for the .h file.
h_file.write("""\
#ifndef MICROPY_INCLUDED_AUTOGEN_USB_DESCRIPTOR_H
#define MICROPY_INCLUDED_AUTOGEN_USB_DESCRIPTOR_H
@ -586,7 +638,27 @@ extern const uint8_t hid_report_descriptor[{hid_report_descriptor_length}];
msc_vendor=args.manufacturer[:8],
msc_product=args.product[:16]))
if 'VENDOR' in args.devices:
h_file.write("""\
enum
{
VENDOR_REQUEST_WEBUSB = 1,
VENDOR_REQUEST_MICROSOFT = 2
};
extern uint8_t const desc_ms_os_20[];
// Currently getting compile-time errors in files like tusb_fifo.c
// if we try do define this here (TODO figure this out!)
//extern const tusb_desc_webusb_url_t desc_webusb_url;
""")
h_file.write("""\
#endif // MICROPY_INCLUDED_AUTOGEN_USB_DESCRIPTOR_H
""")
# Write out the report descriptor and info
c_file.write("""\
const uint8_t hid_report_descriptor[{HID_DESCRIPTOR_LENGTH}] = {{
""".format(HID_DESCRIPTOR_LENGTH=hid_descriptor_length))
@ -655,6 +727,96 @@ c_file.write("""\
};
""")
h_file.write("""\
#endif // MICROPY_INCLUDED_AUTOGEN_USB_DESCRIPTOR_H
""")
if 'VENDOR' in args.devices:
# Mimic what the tinyusb webusb demo does in it's main.c file
c_file.write("""
#define URL "{webusb_url}"
const tusb_desc_webusb_url_t desc_webusb_url =
{{
.bLength = 3 + sizeof(URL) - 1,
.bDescriptorType = 3, // WEBUSB URL type
.bScheme = 1, // 0: http, 1: https, 255: ""
.url = URL
}};
// These next two hardcoded descriptors were pulled from the usb_descriptor.c file
// of the tinyusb webusb_serial demo. TODO - this is probably something else to
// integrate into the adafruit_usb_descriptors project...
//--------------------------------------------------------------------+
// BOS Descriptor
//--------------------------------------------------------------------+
/* Microsoft OS 2.0 registry property descriptor
Per MS requirements https://msdn.microsoft.com/en-us/library/windows/hardware/hh450799(v=vs.85).aspx
device should create DeviceInterfaceGUIDs. It can be done by driver and
in case of real PnP solution device should expose MS "Microsoft OS 2.0
registry property descriptor". Such descriptor can insert any record
into Windows registry per device/configuration/interface. In our case it
will insert "DeviceInterfaceGUIDs" multistring property.
GUID is freshly generated and should be OK to use.
https://developers.google.com/web/fundamentals/native-hardware/build-for-webusb/
(Section Microsoft OS compatibility descriptors)
*/
#define BOS_TOTAL_LEN (TUD_BOS_DESC_LEN + TUD_BOS_WEBUSB_DESC_LEN + TUD_BOS_MICROSOFT_OS_DESC_LEN)
#define MS_OS_20_DESC_LEN 0xB2
// BOS Descriptor is required for webUSB
uint8_t const desc_bos[] =
{{
// total length, number of device caps
TUD_BOS_DESCRIPTOR(BOS_TOTAL_LEN, 2),
// Vendor Code, iLandingPage
TUD_BOS_WEBUSB_DESCRIPTOR(VENDOR_REQUEST_WEBUSB, 1),
// Microsoft OS 2.0 descriptor
TUD_BOS_MS_OS_20_DESCRIPTOR(MS_OS_20_DESC_LEN, VENDOR_REQUEST_MICROSOFT)
}};
uint8_t const * tud_descriptor_bos_cb(void)
{{
return desc_bos;
}}
#define ITF_NUM_VENDOR {webusb_interface} // used in this next descriptor
uint8_t const desc_ms_os_20[] =
{{
// Set header: length, type, windows version, total length
U16_TO_U8S_LE(0x000A), U16_TO_U8S_LE(MS_OS_20_SET_HEADER_DESCRIPTOR), U32_TO_U8S_LE(0x06030000), U16_TO_U8S_LE(MS_OS_20_DESC_LEN),
// Configuration subset header: length, type, configuration index, reserved, configuration total length
U16_TO_U8S_LE(0x0008), U16_TO_U8S_LE(MS_OS_20_SUBSET_HEADER_CONFIGURATION), 0, 0, U16_TO_U8S_LE(MS_OS_20_DESC_LEN-0x0A),
// Function Subset header: length, type, first interface, reserved, subset length
U16_TO_U8S_LE(0x0008), U16_TO_U8S_LE(MS_OS_20_SUBSET_HEADER_FUNCTION), ITF_NUM_VENDOR, 0, U16_TO_U8S_LE(MS_OS_20_DESC_LEN-0x0A-0x08),
// MS OS 2.0 Compatible ID descriptor: length, type, compatible ID, sub compatible ID
U16_TO_U8S_LE(0x0014), U16_TO_U8S_LE(MS_OS_20_FEATURE_COMPATBLE_ID), 'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // sub-compatible
// MS OS 2.0 Registry property descriptor: length, type
U16_TO_U8S_LE(MS_OS_20_DESC_LEN-0x0A-0x08-0x08-0x14), U16_TO_U8S_LE(MS_OS_20_FEATURE_REG_PROPERTY),
U16_TO_U8S_LE(0x0007), U16_TO_U8S_LE(0x002A), // wPropertyDataType, wPropertyNameLength and PropertyName "DeviceInterfaceGUIDs\0" in UTF-16
'D', 0x00, 'e', 0x00, 'v', 0x00, 'i', 0x00, 'c', 0x00, 'e', 0x00, 'I', 0x00, 'n', 0x00, 't', 0x00, 'e', 0x00,
'r', 0x00, 'f', 0x00, 'a', 0x00, 'c', 0x00, 'e', 0x00, 'G', 0x00, 'U', 0x00, 'I', 0x00, 'D', 0x00, 's', 0x00, 0x00, 0x00,
U16_TO_U8S_LE(0x0050), // wPropertyDataLength
//bPropertyData: {{975F44D9-0D08-43FD-8B3E-127CA8AFFF9D}}.
'{{', 0x00, '9', 0x00, '7', 0x00, '5', 0x00, 'F', 0x00, '4', 0x00, '4', 0x00, 'D', 0x00, '9', 0x00, '-', 0x00,
'0', 0x00, 'D', 0x00, '0', 0x00, '8', 0x00, '-', 0x00, '4', 0x00, '3', 0x00, 'F', 0x00, 'D', 0x00, '-', 0x00,
'8', 0x00, 'B', 0x00, '3', 0x00, 'E', 0x00, '-', 0x00, '1', 0x00, '2', 0x00, '7', 0x00, 'C', 0x00, 'A', 0x00,
'8', 0x00, 'A', 0x00, 'F', 0x00, 'F', 0x00, 'F', 0x00, '9', 0x00, 'D', 0x00, '}}', 0x00, 0x00, 0x00, 0x00, 0x00
}};
TU_VERIFY_STATIC(sizeof(desc_ms_os_20) == MS_OS_20_DESC_LEN, "Incorrect size");
// End of section about desc_ms_os_20
""".format(webusb_url=args.webusb_url, webusb_interface=vendor_interface.bInterfaceNumber))