From 73751b89d1ec1e505010541677821fd06f05ded3 Mon Sep 17 00:00:00 2001 From: graham sanderson Date: Sat, 27 Feb 2021 14:01:39 -0600 Subject: [PATCH] Add support for resetting RP2040 via the USB connection when using pico_stdio_usb - setting baud rate to magic value (default=1200) will cause a reset to BOOTSEL mode - a VENDOR interface along side the CDC interface can be used to reset via refular flash boot, or into BOOTSEL mode with control for the reset_usb_boot parameters for the latter either method can be configured/enabled/disabled via #define --- .../include/pico/binary_info.h | 2 + src/rp2_common/pico_stdio_usb/CMakeLists.txt | 1 + .../pico_stdio_usb/include/pico/stdio_usb.h | 34 ++++++- .../include/pico/stdio_usb/reset_interface.h | 23 +++++ .../pico_stdio_usb/include/tusb_config.h | 4 + .../pico_stdio_usb/reset_interface.c | 95 +++++++++++++++++++ src/rp2_common/pico_stdio_usb/stdio_usb.c | 1 - .../pico_stdio_usb/stdio_usb_descriptors.c | 27 +++++- 8 files changed, 183 insertions(+), 4 deletions(-) create mode 100644 src/rp2_common/pico_stdio_usb/include/pico/stdio_usb/reset_interface.h create mode 100644 src/rp2_common/pico_stdio_usb/reset_interface.c diff --git a/src/common/pico_binary_info/include/pico/binary_info.h b/src/common/pico_binary_info/include/pico/binary_info.h index 1e2a4fc..3fca34c 100644 --- a/src/common/pico_binary_info/include/pico/binary_info.h +++ b/src/common/pico_binary_info/include/pico/binary_info.h @@ -23,5 +23,7 @@ #if !PICO_ON_DEVICE && !defined(PICO_NO_BINARY_INFO) #define PICO_NO_BINARY_INFO 1 #endif +#if !PICO_NO_BINARY_INFO #include "pico/binary_info/code.h" +#endif #endif \ No newline at end of file diff --git a/src/rp2_common/pico_stdio_usb/CMakeLists.txt b/src/rp2_common/pico_stdio_usb/CMakeLists.txt index 22e932f..8bc8853 100644 --- a/src/rp2_common/pico_stdio_usb/CMakeLists.txt +++ b/src/rp2_common/pico_stdio_usb/CMakeLists.txt @@ -4,6 +4,7 @@ if (TARGET tinyusb_device_unmarked) target_include_directories(pico_stdio_usb INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) target_sources(pico_stdio_usb INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/reset_interface.c ${CMAKE_CURRENT_LIST_DIR}/stdio_usb.c ${CMAKE_CURRENT_LIST_DIR}/stdio_usb_descriptors.c ) diff --git a/src/rp2_common/pico_stdio_usb/include/pico/stdio_usb.h b/src/rp2_common/pico_stdio_usb/include/pico/stdio_usb.h index f8b645f..5ec976b 100644 --- a/src/rp2_common/pico_stdio_usb/include/pico/stdio_usb.h +++ b/src/rp2_common/pico_stdio_usb/include/pico/stdio_usb.h @@ -19,6 +19,8 @@ * Note this library is a developer convenience. It is not applicable in all cases; for one it takes full control of the USB device precluding your * use of the USB in device or host mode. For this reason, this library will automatically disengage if you try to using it alongside \ref tinyusb_device or * \ref tinyusb_host. It also takes control of a lower level IRQ and sets up a periodic background task. + * + * This library also includes (by default) functionality to enable the RP2040 to be reset over the USB interface. */ // PICO_CONFIG: PICO_STDIO_USB_DEFAULT_CRLF, Default state of CR/LF translation for USB output, type=bool, default=PICO_STDIO_DEFAULT_CRLF, group=pico_stdio_usb @@ -31,7 +33,7 @@ #define PICO_STDIO_USB_STDOUT_TIMEOUT_US 500000 #endif -// todo perhaps unnecessarily high? +// todo perhaps unnecessarily frequent? // PICO_CONFIG: PICO_STDIO_USB_TASK_INTERVAL_US, Period of microseconds between calling tud_task in the background, default=1000, advanced=true, group=pico_stdio_usb #ifndef PICO_STDIO_USB_TASK_INTERVAL_US #define PICO_STDIO_USB_TASK_INTERVAL_US 1000 @@ -42,6 +44,36 @@ #define PICO_STDIO_USB_LOW_PRIORITY_IRQ 31 #endif +// PICO_CONFIG: PICO_STDIO_USB_ENABLE_RESET_VIA_BAUD_RATE, Enable/disable resetting into BOOTSEL mode if the host sets the baud rate to a magic value (PICO_STDIO_USB_RESET_MAGIC_BAUD_RATE), type=bool, default=1, group=pico_stdio_usb +#ifndef PICO_STDIO_USB_ENABLE_RESET_VIA_BAUD_RATE +#define PICO_STDIO_USB_ENABLE_RESET_VIA_BAUD_RATE 1 +#endif + +// PICO_CONFIG: PICO_STDIO_USB_RESET_MAGIC_BAUD_RATE, baud rate that if selected causes a reset into BOOTSEL mode (if PICO_STDIO_USB_ENABLE_RESET_VIA_BAUD_RATE==1), default=1200, group=pico_stdio_usb +#ifndef PICO_STDIO_USB_RESET_MAGIC_BAUD_RATE +#define PICO_STDIO_USB_RESET_MAGIC_BAUD_RATE 1200 +#endif + +// PICO_CONFIG: PICO_STDIO_USB_ENABLE_RESET_VIA_BAUD_RATE, Enable/disable resetting into BOOTSEL mode via an additional VENDOR USB interface - enables picotool based reset, type=bool, default=1, group=pico_stdio_usb +#ifndef PICO_STDIO_USB_ENABLE_RESET_VIA_VENDOR_INTERFACE +#define PICO_STDIO_USB_ENABLE_RESET_VIA_VENDOR_INTERFACE 1 +#endif + +// PICO_CONFIG: PICO_STDIO_USB_RESET_INTERFACE_SUPPORT_RESET_TO_BOOTSEL, If vendor reset interface is included allow rebooting to BOOTSEL mode, type=bool, default=1, group=pico_stdio_usb +#ifndef PICO_STDIO_USB_RESET_INTERFACE_SUPPORT_RESET_TO_BOOTSEL +#define PICO_STDIO_USB_RESET_INTERFACE_SUPPORT_RESET_TO_BOOTSEL 1 +#endif + +// PICO_CONFIG: PICO_STDIO_USB_RESET_INTERFACE_SUPPORT_RESET_TO_FLASH_BOOT, If vendor reset interface is included allow rebooting with regular flash boot, type=bool, default=1, group=pico_stdio_usb +#ifndef PICO_STDIO_USB_RESET_INTERFACE_SUPPORT_RESET_TO_FLASH_BOOT +#define PICO_STDIO_USB_RESET_INTERFACE_SUPPORT_RESET_TO_FLASH_BOOT 1 +#endif + +// PICO_CONFIG: PICO_STDIO_USB_RESET_RESET_TO_FLASH_DELAY_MS, delays in ms before rebooting via regular flash boot, default=100, group=pico_stdio_usb +#ifndef PICO_STDIO_USB_RESET_RESET_TO_FLASH_DELAY_MS +#define PICO_STDIO_USB_RESET_RESET_TO_FLASH_DELAY_MS 100 +#endif + #ifdef __cplusplus extern "C" { #endif diff --git a/src/rp2_common/pico_stdio_usb/include/pico/stdio_usb/reset_interface.h b/src/rp2_common/pico_stdio_usb/include/pico/stdio_usb/reset_interface.h new file mode 100644 index 0000000..c8e248d --- /dev/null +++ b/src/rp2_common/pico_stdio_usb/include/pico/stdio_usb/reset_interface.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2021 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _PICO_STDIO_USB_RESET_INTERFACE_H +#define _PICO_STDIO_USB_RESET_INTERFACE_H + +// We use VENDOR, 0, 0 for PICOBOOT, so lets use VENDOR, 0, 1 for RESET + +// VENDOR sub-class for the reset interface +#define RESET_INTERFACE_SUBCLASS 0x00 +// VENDOR protocol for the reset interface +#define RESET_INTERFACE_PROTOCOL 0x01 + +// CONTROL requests: + +// reset to BOOTSEL +#define RESET_REQUEST_BOOTSEL 0x01 +// regular flash boot +#define RESET_REQUEST_FLASH 0x02 +#endif \ No newline at end of file diff --git a/src/rp2_common/pico_stdio_usb/include/tusb_config.h b/src/rp2_common/pico_stdio_usb/include/tusb_config.h index f974112..c0a2843 100644 --- a/src/rp2_common/pico_stdio_usb/include/tusb_config.h +++ b/src/rp2_common/pico_stdio_usb/include/tusb_config.h @@ -27,10 +27,14 @@ #ifndef _PICO_STDIO_USB_TUSB_CONFIG_H #define _PICO_STDIO_USB_TUSB_CONFIG_H +#include "pico/stdio_usb.h" + #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE) #define CFG_TUD_CDC (1) #define CFG_TUD_CDC_RX_BUFSIZE (256) #define CFG_TUD_CDC_TX_BUFSIZE (256) +// We use a vendor specific interface but with our own driver +#define CFG_TUD_VENDOR (0) #endif diff --git a/src/rp2_common/pico_stdio_usb/reset_interface.c b/src/rp2_common/pico_stdio_usb/reset_interface.c new file mode 100644 index 0000000..e042e71 --- /dev/null +++ b/src/rp2_common/pico_stdio_usb/reset_interface.c @@ -0,0 +1,95 @@ +/** + * Copyright (c) 2021 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "tusb.h" + +#if PICO_STDIO_USB_ENABLE_RESET_VIA_VENDOR_INTERFACE +#include "pico/bootrom.h" +#include "pico/stdio_usb/reset_interface.h" +#include "hardware/watchdog.h" +#include "device/usbd_pvt.h" + +static uint8_t itf_num; + +static void resetd_init(void) { +} + +static void resetd_reset(uint8_t __unused rhport) { + itf_num = 0; +} + +static uint16_t resetd_open(uint8_t __unused rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len) { + TU_VERIFY(TUSB_CLASS_VENDOR_SPECIFIC == itf_desc->bInterfaceClass && + RESET_INTERFACE_SUBCLASS == itf_desc->bInterfaceSubClass && + RESET_INTERFACE_PROTOCOL == itf_desc->bInterfaceProtocol, 0); + + uint16_t const drv_len = sizeof(tusb_desc_interface_t); + TU_VERIFY(max_len >= drv_len, 0); + + itf_num = itf_desc->bInterfaceNumber; + return drv_len; +} + +// Support for parameterized reset via vendor interface control request +static bool resetd_control_request_cb(uint8_t __unused rhport, tusb_control_request_t const *request) { + if (request->wIndex == itf_num) { +#if PICO_STDIO_USB_RESET_INTERFACE_SUPPORT_RESET_TO_BOOTSEL + if (request->bRequest == RESET_REQUEST_BOOTSEL) { + uint gpio_mask = 0; + if (request->wValue & 0x100) { + gpio_mask = 1u << (request->wValue >> 9u); + } + reset_usb_boot(gpio_mask, request->wValue & 0x7f); + // does not return, otherwise we'd return true + } +#endif +#if PICO_STDIO_USB_RESET_INTERFACE_SUPPORT_RESET_TO_FLASH_BOOT + if (request->bRequest == RESET_REQUEST_FLASH) { + watchdog_reboot(0, SRAM_END, PICO_STDIO_USB_RESET_RESET_TO_FLASH_DELAY_MS); + return true; + } +#endif + } + return false; +} + +static bool resetd_control_complete_cb(uint8_t __unused rhport, tusb_control_request_t __unused const *request) { + return true; +} + +static bool resetd_xfer_cb(uint8_t __unused rhport, uint8_t __unused ep_addr, xfer_result_t __unused result, uint32_t __unused xferred_bytes) { + return true; +} + +static usbd_class_driver_t const _resetd_driver = +{ +#if CFG_TUSB_DEBUG >= 2 + .name = "RESET", +#endif + .init = resetd_init, + .reset = resetd_reset, + .open = resetd_open, + .control_request = resetd_control_request_cb, + .control_complete = resetd_control_complete_cb, + .xfer_cb = resetd_xfer_cb, + .sof = NULL +}; + +// Implement callback to add our custom driver +usbd_class_driver_t const *usbd_app_driver_get_cb(uint8_t *driver_count) { + *driver_count = 1; + return &_resetd_driver; +} +#endif + +#if PICO_STDIO_USB_ENABLE_RESET_VIA_BAUD_RATE +// Support for default BOOTSEL reset by changing baud rate +void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const* p_line_coding) { + if (p_line_coding->bit_rate == PICO_STDIO_USB_RESET_MAGIC_BAUD_RATE) { + reset_usb_boot(0, 0); + } +} +#endif + diff --git a/src/rp2_common/pico_stdio_usb/stdio_usb.c b/src/rp2_common/pico_stdio_usb/stdio_usb.c index 7bb9aea..c122b44 100644 --- a/src/rp2_common/pico_stdio_usb/stdio_usb.c +++ b/src/rp2_common/pico_stdio_usb/stdio_usb.c @@ -8,7 +8,6 @@ #include "tusb.h" #include "pico/time.h" -#include "pico/stdio_usb.h" #include "pico/stdio/driver.h" #include "pico/binary_info.h" #include "hardware/irq.h" diff --git a/src/rp2_common/pico_stdio_usb/stdio_usb_descriptors.c b/src/rp2_common/pico_stdio_usb/stdio_usb_descriptors.c index 3199886..c66df42 100644 --- a/src/rp2_common/pico_stdio_usb/stdio_usb_descriptors.c +++ b/src/rp2_common/pico_stdio_usb/stdio_usb_descriptors.c @@ -29,15 +29,26 @@ #if !defined(TINYUSB_HOST_LINKED) && !defined(TINYUSB_DEVICE_LINKED) #include "tusb.h" +#include "pico/stdio_usb/reset_interface.h" #define USBD_VID (0x2E8A) // Raspberry Pi #define USBD_PID (0x000a) // Raspberry Pi Pico SDK CDC +#define TUD_RPI_RESET_DESC_LEN 9 +#if !PICO_STDIO_USB_ENABLE_RESET_VIA_VENDOR_INTERFACE #define USBD_DESC_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN) +#else +#define USBD_DESC_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_RPI_RESET_DESC_LEN) +#endif #define USBD_MAX_POWER_MA (250) -#define USBD_ITF_CDC (0) // needs 2 interfaces -#define USBD_ITF_MAX (2) +#define USBD_ITF_CDC (0) // needs 2 interfaces +#if !PICO_STDIO_USB_ENABLE_RESET_VIA_VENDOR_INTERFACE +#define USBD_ITF_MAX (2) +#else +#define USBD_ITF_RPI_RESET (2) +#define USBD_ITF_MAX (3) +#endif #define USBD_CDC_EP_CMD (0x81) #define USBD_CDC_EP_OUT (0x02) @@ -50,6 +61,7 @@ #define USBD_STR_PRODUCT (0x02) #define USBD_STR_SERIAL (0x03) #define USBD_STR_CDC (0x04) +#define USBD_STR_RPI_RESET (0x05) // Note: descriptors returned from callbacks must exist long enough for transfer to complete @@ -70,12 +82,20 @@ static const tusb_desc_device_t usbd_desc_device = { .bNumConfigurations = 1, }; +#define TUD_RPI_RESET_DESCRIPTOR(_itfnum, _stridx) \ + /* Interface */\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 0, TUSB_CLASS_VENDOR_SPECIFIC, RESET_INTERFACE_SUBCLASS, RESET_INTERFACE_PROTOCOL, _stridx, + static const uint8_t usbd_desc_cfg[USBD_DESC_LEN] = { TUD_CONFIG_DESCRIPTOR(1, USBD_ITF_MAX, USBD_STR_0, USBD_DESC_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, USBD_MAX_POWER_MA), 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), + +#if PICO_STDIO_USB_ENABLE_RESET_VIA_VENDOR_INTERFACE + TUD_RPI_RESET_DESCRIPTOR(USBD_ITF_RPI_RESET, USBD_STR_RPI_RESET) +#endif }; static const char *const usbd_desc_str[] = { @@ -83,6 +103,9 @@ static const char *const usbd_desc_str[] = { [USBD_STR_PRODUCT] = "Pico", [USBD_STR_SERIAL] = "000000000000", // TODO [USBD_STR_CDC] = "Board CDC", +#if PICO_STDIO_USB_ENABLE_RESET_VIA_VENDOR_INTERFACE + [USBD_STR_RPI_RESET] = "Reset", +#endif }; const uint8_t *tud_descriptor_device_cb(void) {