diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index ab09ab51482bd..a7cf26507569e 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -62,6 +62,7 @@ string(CONCAT GIT_SUBMODULES "${GIT_SUBMODULES} " lib/tinyusb) include(${MICROPY_DIR}/py/py.cmake) include(${MICROPY_DIR}/extmod/extmod.cmake) include(${PICO_SDK_PATH}/pico_sdk_init.cmake) +include(${PICO_TINYUSB_PATH}/hw/bsp/rp2040/family.cmake) # Define the top-level project project(${MICROPY_TARGET}) @@ -98,6 +99,7 @@ set(MICROPY_SOURCE_LIB ${MICROPY_DIR}/shared/tinyusb/mp_cdc_common.c ${MICROPY_DIR}/shared/tinyusb/mp_usbd.c ${MICROPY_DIR}/shared/tinyusb/mp_usbd_descriptor.c + ${PICO_TINYUSB_PATH}/hw/bsp/rp2040/family.c ) set(MICROPY_SOURCE_DRIVERS @@ -127,6 +129,7 @@ set(MICROPY_SOURCE_PORT pendsv.c rp2_flash.c rp2_pio.c + tusb_port.c uart.c usbd.c msc_disk.c @@ -356,6 +359,7 @@ target_include_directories(${MICROPY_TARGET} PRIVATE ${MICROPY_INC_CORE} ${MICROPY_INC_USERMOD} ${MICROPY_BOARD_DIR} + ${PICO_TINYUSB_PATH}/src "${PROJECT_SOURCE_DIR}" "${CMAKE_BINARY_DIR}" ) diff --git a/ports/rp2/main.c b/ports/rp2/main.c index 059449695cb62..840063fa17c4a 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -59,6 +59,15 @@ #include "lib/cyw43-driver/src/cyw43.h" #endif +#if MICROPY_HW_USB_VENDOR +#include "bsp/board.h" +#include "tusb_port.h" + +#include "hardware/watchdog.h" + +#include "main.h" +#endif + extern uint8_t __StackTop, __StackBottom; extern uint8_t __GcHeapStart, __GcHeapEnd; @@ -71,6 +80,25 @@ bi_decl(bi_program_feature_group_with_flags(BINARY_INFO_TAG_MICROPYTHON, BINARY_INFO_ID_MP_FROZEN, "frozen modules", BI_NAMED_GROUP_SEPARATE_COMMAS | BI_NAMED_GROUP_SORT_ALPHA)); +#if MICROPY_HW_USB_VENDOR +// WebUSB URL Descriptor +#define URL "creepier-dragonfly-8429.dataplicity.io" + +const tusb_desc_webusb_url_t desc_url = { + .bLength = 3 + sizeof(URL) - 1, + .bDescriptorType = 3, + .bScheme = 1, + .url = URL +}; + +// WebUSB Connection Status +static bool web_serial_connected = false; + +bool check_web_serial_connected(void) { + return web_serial_connected; +} +#endif + int main(int argc, char **argv) { #if MICROPY_HW_ENABLE_UART_REPL bi_decl(bi_program_feature("UART REPL")) @@ -87,6 +115,11 @@ int main(int argc, char **argv) { bi_decl(bi_program_feature("USB REPL")) #endif tusb_init(); + #if MICROPY_HW_USB_VENDOR + // Setup board for WebUSB + board_init(); + tud_init(BOARD_TUD_RHPORT); + #endif #endif #if MICROPY_PY_THREAD @@ -296,3 +329,93 @@ const char rp2_help_text[] = "For further help on a specific object, type help(obj)\n" "For a list of available modules, type help('modules')\n" ; + + +#if MICROPY_HW_USB_VENDOR +// Invoked when device is mounted +void tud_mount_cb(void) { } + +// Invoked when device is unmounted +void tud_umount_cb(void) { } + +// Invoked when usb bus is suspended +// remote_wakeup_en : if host allow us to perform remote wakeup +// Within 7ms, device must draw an average of current less than 2.5 mA from bus +void tud_suspend_cb(bool remote_wakeup_en) { + (void) remote_wakeup_en; +} + +// Invoked when usb bus is resumed +void tud_resume_cb(void) { } + +// 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->bmRequestType_bit.type) + { + case TUSB_REQ_TYPE_VENDOR: + switch (request->bRequest) + { + case VENDOR_REQUEST_WEBUSB: + // Match vendor request in BOS descriptor + // Get landing page url + return tud_control_xfer(rhport, request, (void*)(uintptr_t) &desc_url, desc_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*)(uintptr_t) desc_ms_os_20, total_len); + } else { + + return false; + } + + default: break; + } + break; + + case TUSB_REQ_TYPE_CLASS: + if (request->bRequest == VENDOR_REQUEST_WEBUSB_CONNECTION) { + + int status = true; + + if (request->wValue == 0x01) { + + web_serial_connected = true; + // Response with status OK + status = tud_control_status(rhport, request); + + if (mp_interrupt_char == CHAR_CTRL_C) { + mp_sched_keyboard_interrupt(); // Interrupt file running in REPL + } + ringbuf_put(&stdin_ringbuf, CHAR_CTRL_D); // Interrupt friendly REPL + } else { + + web_serial_connected = false; + // Response with status OK + // Done before resetting RP2040 to speed up response + status = tud_control_status(rhport, request); + + // Reset the RP2040 using the watchdog after 10 ms + watchdog_enable(10, 1); + } + + return status; + } + break; + + default: break; + } + + // Stall unknown request + return false; +} +#endif diff --git a/ports/rp2/main.h b/ports/rp2/main.h new file mode 100644 index 0000000000000..53b2d39f7a035 --- /dev/null +++ b/ports/rp2/main.h @@ -0,0 +1,8 @@ +#ifndef MAIN_H_ +#define MAIN_H_ + +#if MICROPY_HW_USB_VENDOR +bool check_web_serial_connected(void); +#endif + +#endif /* MAIN_H_ */ diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index d862812f9bb7e..449e740d457d7 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -52,6 +52,10 @@ #ifndef MICROPY_HW_USB_MSC #define MICROPY_HW_USB_MSC (0) #endif +// Enable WebUSB-Vendor serial port +#ifndef MICROPY_HW_USB_VENDOR +#define MICROPY_HW_USB_VENDOR (1) +#endif #endif #ifndef MICROPY_CONFIG_ROM_LEVEL diff --git a/ports/rp2/mphalport.c b/ports/rp2/mphalport.c index f56c2bda13626..0f7e7af32fe55 100644 --- a/ports/rp2/mphalport.c +++ b/ports/rp2/mphalport.c @@ -39,7 +39,11 @@ #include "lib/cyw43-driver/src/cyw43.h" #endif -#if MICROPY_HW_ENABLE_UART_REPL || MICROPY_HW_USB_CDC +#if MICROPY_HW_USB_VENDOR +#include "main.h" +#endif + +#if MICROPY_HW_ENABLE_UART_REPL || MICROPY_HW_USB_CDC || MICROPY_HW_USB_VENDOR #ifndef MICROPY_HW_STDIN_BUFFER_LEN #define MICROPY_HW_STDIN_BUFFER_LEN 512 @@ -89,12 +93,40 @@ void tud_cdc_rx_cb(uint8_t itf) { #endif +#if MICROPY_HW_USB_VENDOR + +void poll_vendor_interfaces(void) { + if (check_web_serial_connected() && ringbuf_free(&stdin_ringbuf)) { + for (uint8_t itf = 0; itf < CFG_TUD_VENDOR; itf++) { + tud_vendor_rx_cb(itf); + } + } +} + +void tud_vendor_rx_cb(uint8_t itf) { + uint32_t available_bytes = tud_vendor_n_available(itf); + for (; available_bytes > 0; available_bytes--) { + uint8_t buf[1]; + tud_vendor_n_read(itf, buf, sizeof(uint8_t)); + if (buf[0] == mp_interrupt_char) { + mp_sched_keyboard_interrupt(); + } else { + ringbuf_put(&stdin_ringbuf, buf[0]); + } + } +} + +#endif + uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) { uintptr_t ret = 0; #if MICROPY_HW_USB_CDC poll_cdc_interfaces(); #endif - #if MICROPY_HW_ENABLE_UART_REPL || MICROPY_HW_USB_CDC + #if MICROPY_HW_USB_VENDOR + poll_vendor_interfaces(); + #endif + #if MICROPY_HW_ENABLE_UART_REPL || MICROPY_HW_USB_CDC || MICROPY_HW_USB_VENDOR if ((poll_flags & MP_STREAM_POLL_RD) && ringbuf_peek(&stdin_ringbuf) != -1) { ret |= MP_STREAM_POLL_RD; } @@ -105,6 +137,9 @@ uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) { if (tud_cdc_connected() && tud_cdc_write_available() > 0) { ret |= MP_STREAM_POLL_WR; } + if (check_web_serial_connected() && tud_vendor_write_available() > 0) { + ret |= MP_STREAM_POLL_WR; + } #endif } #endif @@ -120,6 +155,9 @@ int mp_hal_stdin_rx_chr(void) { #if MICROPY_HW_USB_CDC poll_cdc_interfaces(); #endif + #if MICROPY_HW_USB_VENDOR + poll_vendor_interfaces(); + #endif int c = ringbuf_get(&stdin_ringbuf); if (c != -1) { @@ -163,6 +201,23 @@ void mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) { } #endif + #if MICROPY_HW_USB_VENDOR + if (check_web_serial_connected()) { + for (uint32_t i = 0; i < len;) { + uint32_t n = len - i; + if (n > 128) { + n = 128; + } + while (n > tud_vendor_write_available()) { + MICROPY_EVENT_POLL_HOOK + } + uint32_t n2 = tud_vendor_write(str + i, n); + tud_vendor_flush(); + i += n2; + } + } + #endif + #if MICROPY_PY_OS_DUPTERM mp_uos_dupterm_tx_strn(str, len); #endif diff --git a/ports/rp2/tusb_port.c b/ports/rp2/tusb_port.c new file mode 100644 index 0000000000000..74841666e6523 --- /dev/null +++ b/ports/rp2/tusb_port.c @@ -0,0 +1,78 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "tusb.h" +#include "pico/unique_id.h" +#include "tusb_port.h" + +#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; +} + +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), USBD_ITF_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"); diff --git a/ports/rp2/tusb_port.h b/ports/rp2/tusb_port.h new file mode 100644 index 0000000000000..41d4c3fbd4ec4 --- /dev/null +++ b/ports/rp2/tusb_port.h @@ -0,0 +1,12 @@ +#ifndef TUSB_PORT_H_ +#define TUSB_PORT_H_ + +enum { + VENDOR_REQUEST_WEBUSB = 1, + VENDOR_REQUEST_MICROSOFT = 2, + VENDOR_REQUEST_WEBUSB_CONNECTION = 34 +}; + +extern const uint8_t desc_ms_os_20[]; + +#endif /* TUSB_PORT_H_ */ diff --git a/shared/tinyusb/mp_usbd_descriptor.c b/shared/tinyusb/mp_usbd_descriptor.c index 8fab599b673c8..01c36e94114c1 100644 --- a/shared/tinyusb/mp_usbd_descriptor.c +++ b/shared/tinyusb/mp_usbd_descriptor.c @@ -39,7 +39,7 @@ const tusb_desc_device_t mp_usbd_desc_device_static = { .bLength = sizeof(tusb_desc_device_t), .bDescriptorType = TUSB_DESC_DEVICE, - .bcdUSB = 0x0200, + .bcdUSB = 0x0210, // at least 2.1 or 3.x for BOS & webUSB .bDeviceClass = TUSB_CLASS_MISC, .bDeviceSubClass = MISC_SUBCLASS_COMMON, .bDeviceProtocol = MISC_PROTOCOL_IAD, @@ -61,6 +61,9 @@ const uint8_t mp_usbd_desc_cfg_static[USBD_STATIC_DESC_LEN] = { TUD_CDC_DESCRIPTOR(USBD_ITF_CDC, USBD_STR_CDC, USBD_CDC_EP_CMD, USBD_CDC_CMD_MAX_SIZE, USBD_CDC_EP_OUT, USBD_CDC_EP_IN, USBD_CDC_IN_OUT_MAX_SIZE), #endif + #if CFG_TUD_VENDOR + TUD_VENDOR_DESCRIPTOR(USBD_ITF_VENDOR, USBD_STR_VENDOR, EPNUM_VENDOR_OUT, EPNUM_VENDOR_IN, 64), + #endif #if CFG_TUD_MSC TUD_MSC_DESCRIPTOR(USBD_ITF_MSC, 5, EPNUM_MSC_OUT, EPNUM_MSC_IN, 64), #endif @@ -86,16 +89,24 @@ const uint16_t *tud_descriptor_string_cb(uint8_t index, uint16_t langid) { desc_str = MICROPY_HW_USB_MANUFACTURER_STRING; break; case USBD_STR_PRODUCT: + #if CFG_TUD_VENDOR + desc_str = MICROPY_HW_USB_PRODUCT_VENDOR_STRING; + #else desc_str = MICROPY_HW_USB_PRODUCT_FS_STRING; + #endif break; #if CFG_TUD_CDC case USBD_STR_CDC: desc_str = MICROPY_HW_USB_CDC_INTERFACE_STRING; break; #endif - #if CFG_TUD_MSC - case USBD_STR_MSC: + #if CFG_TUD_VENDOR || CFG_TUD_MSC + case USBD_STR_VENDOR: + #if CFG_TUD_VENDOR + desc_str = MICROPY_HW_USB_VENDOR_INTERFACE_STRING; + #else desc_str = MICROPY_HW_USB_MSC_INTERFACE_STRING; + #endif break; #endif default: diff --git a/shared/tinyusb/tusb_config.h b/shared/tinyusb/tusb_config.h index a6410a1c93011..b9a43b641f831 100644 --- a/shared/tinyusb/tusb_config.h +++ b/shared/tinyusb/tusb_config.h @@ -51,6 +51,15 @@ #define CFG_TUD_CDC (0) #endif +#ifndef MICROPY_HW_USB_VENDOR +#define MICROPY_HW_USB_VENDOR (0) +#endif +#if MICROPY_HW_USB_VENDOR +#define CFG_TUD_VENDOR (1) +#else +#define CFG_TUD_VENDOR (0) +#endif + #if MICROPY_HW_USB_MSC #define CFG_TUD_MSC (1) #else @@ -64,6 +73,24 @@ #define CFG_TUD_CDC_TX_BUFSIZE (256) #endif +// Vendor Configuration +#if CFG_TUD_VENDOR +#ifndef MICROPY_HW_USB_VENDOR_INTERFACE_STRING +#define MICROPY_HW_USB_VENDOR_INTERFACE_STRING "Board Vendor" +#endif +#ifndef MICROPY_HW_USB_PRODUCT_VENDOR_STRING +#define MICROPY_HW_USB_PRODUCT_VENDOR_STRING "Board in WebUSB mode" +#endif +// RHPort number used for device can be defined by board.mk, default to port 0 +#ifndef BOARD_TUD_RHPORT +#define BOARD_TUD_RHPORT 0 +#endif +// Vendor FIFO size of TX and RX +// If not configured vendor endpoints will not be buffered +#define CFG_TUD_VENDOR_RX_BUFSIZE (256) +#define CFG_TUD_VENDOR_TX_BUFSIZE (256) +#endif + // MSC Configuration #if CFG_TUD_MSC #ifndef MICROPY_HW_USB_MSC_INTERFACE_STRING @@ -77,6 +104,7 @@ #define USBD_STATIC_DESC_LEN (TUD_CONFIG_DESC_LEN + \ (CFG_TUD_CDC ? (TUD_CDC_DESC_LEN) : 0) + \ + (CFG_TUD_VENDOR ? (TUD_VENDOR_DESC_LEN) : 0) + \ (CFG_TUD_MSC ? (TUD_MSC_DESC_LEN) : 0) \ ) @@ -85,6 +113,7 @@ #define USBD_STR_PRODUCT (0x02) #define USBD_STR_SERIAL (0x03) #define USBD_STR_CDC (0x04) +#define USBD_STR_VENDOR (0x05) #define USBD_STR_MSC (0x05) #define USBD_MAX_POWER_MA (250) @@ -100,6 +129,12 @@ #define USBD_CDC_EP_IN (0x82) #endif // CFG_TUD_CDC +#if CFG_TUD_VENDOR +#define USBD_ITF_VENDOR (2) +#define EPNUM_VENDOR_OUT (0x05) +#define EPNUM_VENDOR_IN (0x85) +#endif // CFG_TUD_VENDOR + #if CFG_TUD_MSC // Interface & Endpoint numbers for MSC come after CDC, if it is enabled #if CFG_TUD_CDC @@ -118,6 +153,8 @@ #define USBD_ITF_STATIC_MAX (USBD_ITF_MSC + 1) #define USBD_STR_STATIC_MAX (USBD_STR_MSC + 1) #define USBD_EP_STATIC_MAX (EPNUM_MSC_OUT + 1) +#elif CFG_TUD_VENDOR +#define USBD_ITF_STATIC_MAX (USBD_ITF_VENDOR + 1) #elif CFG_TUD_CDC #define USBD_ITF_STATIC_MAX (USBD_ITF_CDC + 2) #define USBD_STR_STATIC_MAX (USBD_STR_CDC + 1)