diff --git a/WEBUSB_README.md b/WEBUSB_README.md new file mode 100644 index 000000000000..a257d5259cad --- /dev/null +++ b/WEBUSB_README.md @@ -0,0 +1,94 @@ + + +# 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. diff --git a/docs/index.rst b/docs/index.rst index 6dadddfc1a25..3ee76fdd8e7c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -46,6 +46,7 @@ Full Table of Contents ../BUILDING ../CODE_OF_CONDUCT ../license.rst + ../WEBUSB_README Indices and tables ================== diff --git a/ports/esp32s2/Makefile b/ports/esp32s2/Makefile index aac93478240d..b4ff0612b3a6 100644 --- a/ports/esp32s2/Makefile +++ b/ports/esp32s2/Makefile @@ -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 ###################################### diff --git a/ports/esp32s2/boards/espressif_kaluga_1/mpconfigboard.mk b/ports/esp32s2/boards/espressif_kaluga_1/mpconfigboard.mk index 2dce0388199f..52c63cb9c712 100644 --- a/ports/esp32s2/boards/espressif_kaluga_1/mpconfigboard.mk +++ b/ports/esp32s2/boards/espressif_kaluga_1/mpconfigboard.mk @@ -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 diff --git a/ports/esp32s2/mpconfigport.mk b/ports/esp32s2/mpconfigport.mk index 562c60998c29..b273c02ee85c 100644 --- a/ports/esp32s2/mpconfigport.mk +++ b/ports/esp32s2/mpconfigport.mk @@ -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 diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index b208178f37a9..f907bf7ae632 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -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 diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index b8fe73eea999..d9c7c0d331a2 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -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) diff --git a/supervisor/shared/serial.c b/supervisor/shared/serial.c index 303f89e7521a..b9feb04f25e6 100644 --- a/supervisor/shared/serial.c +++ b/supervisor/shared/serial.c @@ -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); diff --git a/supervisor/shared/usb/tusb_config.h b/supervisor/shared/usb/tusb_config.h index 15d9fabafed0..d3a35f5374e0 100644 --- a/supervisor/shared/usb/tusb_config.h +++ b/supervisor/shared/usb/tusb_config.h @@ -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 /*------------------------------------------------------------------*/ diff --git a/supervisor/shared/usb/usb.c b/supervisor/shared/usb/usb.c index 07c6aee6c1a3..06b9e0c4ef42 100644 --- a/supervisor/shared/usb/usb.c +++ b/supervisor/shared/usb/usb.c @@ -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 /** diff --git a/supervisor/supervisor.mk b/supervisor/supervisor.mk index a59e99e3def2..f559e004ef06 100644 --- a/supervisor/supervisor.mk +++ b/supervisor/supervisor.mk @@ -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 diff --git a/tools/gen_usb_descriptor.py b/tools/gen_usb_descriptor.py index 672f09c88948..80dddeb0012e 100644 --- a/tools/gen_usb_descriptor.py +++ b/tools/gen_usb_descriptor.py @@ -13,7 +13,7 @@ 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 @@ # 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 @@ 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 '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 @@ def strings_in_order(cls): # 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 @@ def strings_in_order(cls): 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 @@ def strings_in_order(cls): # 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 @@ def strings_in_order(cls): c_file.write("""\ #include +#include "tusb.h" #include "py/objtuple.h" #include "shared-bindings/usb_hid/Device.h" #include "{H_FILE_NAME}" @@ -550,7 +602,7 @@ def strings_in_order(cls): 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 @@ def strings_in_order(cls): 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 @@ def strings_in_order(cls): }; """) -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))