diff --git a/Makefile.dep b/Makefile.dep index ad9c764824a2..8c6b96497d91 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -84,6 +84,10 @@ ifneq (,$(filter tinydtls_sock_dtls, $(USEMODULE))) USEPKG += tinydtls endif +ifneq (,$(filter tinyusb_%, $(USEMODULE))) + USEPKG += tinyusb +endif + # always select gpio (until explicit dependencies are sorted out) FEATURES_OPTIONAL += periph_gpio diff --git a/bootloaders/riotboot_tinyusb_dfu/Makefile b/bootloaders/riotboot_tinyusb_dfu/Makefile new file mode 100644 index 000000000000..82b3508791a1 --- /dev/null +++ b/bootloaders/riotboot_tinyusb_dfu/Makefile @@ -0,0 +1,19 @@ +# Default RIOT bootloader +APPLICATION = riotboot_tinyusb_dfu + +# Add RIOTBOOT tinyUSB DFU integration +USEMODULE += riotboot_tinyusb_dfu + +# Use xtimer for scheduled reboot +USEMODULE += ztimer +USEMODULE += ztimer_init + +# USB device vendor and product ID +# pid.codes test VID/PID, not globally unique + +# The VID/PID pair allocated for the RIOT bootloader +# as allocated on https://pid.codes/1209/7D02/ +USB_VID ?= 1209 +USB_PID ?= 7D02 + +include ../riotboot_common.mk diff --git a/bootloaders/riotboot_tinyusb_dfu/doc.txt b/bootloaders/riotboot_tinyusb_dfu/doc.txt new file mode 100644 index 000000000000..64fc6df43fc9 --- /dev/null +++ b/bootloaders/riotboot_tinyusb_dfu/doc.txt @@ -0,0 +1,86 @@ +/** +@defgroup pkg_tinyusb_dfu riotboot tinyUSB DFU +@ingroup pkg_tinyusb +@ingroup bootloaders + +# Overview + +`riotboot_tinyusb_dfu` is a variation of @ref bootloader_riotboot that adds +USB device firmware upgrade (DFU) based on the tinyUSB device stack package. +It uses a board's USB interface to allow firmware upgrades from inside the +bootloader. + +At startup, the DFU mode is entered when either + +- none of the slots contains a valid firmware image, or +- the first button was pressed when the board started (configurable at board + level using @ref BTN_BOOTLOADER_PIN), or +- the last running firmware asked the bootloader to go to DFU mode by using a + magic number (see @ref RIOTBOOT_MAGIC_ADDR). + +# Prerequisites + +- The board must have functional USB support and has to support the + `tinyusb_device` feature. +- The board must have functional `riotboot` support, see + @ref bootloader_riotboot. + +# Flashing riotboot_tinyusb_dfu + +The `riotboot_tinyusb_dfu` bootloader can be flashed using a regular programmer +like any other application: + +``` +$ make -C bootloaders/riotboot_tinyusb_dfu BOARD=... all flash +``` + +Depending on your setup, you may need to select the right `PROGRAMMER` +(and its details) in addition to your board. + +# DFU mode + +A device in riotboot DFU mode can be recognized in the USB device list by +its VID/PID pair 1209:7d02: + +``` +$ lsusb +Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub +Bus 001 Device 005: ID 138a:003f [...] +Bus 001 Device 004: ID 8087:0a2b [...] +Bus 001 Device 045: ID 1209:7d02 Generic USB device +Bus 001 Device 001: ID 1d6b:0002 [...] +``` + +When running in DFU mode, the bootloader allows writing to either of the +two firmware slots. + +When the device is attached and in DFU mode (or the current firmware uses the +`tinyusb_dfu` module), new firmware can be flashed to slot 0 using: + +``` +$ FEATURES_REQUIRED+=riotboot USEMODULE+=tinyusb_dfu make -C examples/saul BOARD=particle-xenon \ + PROGRAMMER=dfu-util USB_VID=1209 USB_PID=7d02 all riotboot/flash-slot0 +``` + +Instead of setting `USB_VID` and `USB_PID`, the variable `DFU_USB_ID` could also +be used to specify the DFU device to be used. + +``` +$ FEATURES_REQUIRED+=riotboot USEMODULE+=tinyusb_dfu make -C examples/saul BOARD=particle-xenon \ + PROGRAMMER=dfu-util DFU_USB_ID=1209:7d02 all riotboot/flash-slot0 +``` + +Note that when building and flashing a different slot (e.g. `flash-slot1`), +not only is the image built for that slot, but also `dfu-util` gets passed +`--alt 1` (via the `DFU_ALT` build variable) to store it in the right place. + +# Entering DFU mode + +When RIOT applications are built with `USEMODULE=tinyusb_dfu`, +they implement what is called "runtime mode" in DFU. + +In runtime mode, it is visible to the `dfu-util` that they are upgradable. +On firmware upgrades, the build system can send a command via USB to enter +DFU mode. This can also be done manually using `dfu-util -e`. + +*/ diff --git a/bootloaders/riotboot_tinyusb_dfu/main.c b/bootloaders/riotboot_tinyusb_dfu/main.c new file mode 100644 index 000000000000..e8be5935dec7 --- /dev/null +++ b/bootloaders/riotboot_tinyusb_dfu/main.c @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser + * Inria + * 2020 Mesotic SAS + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup bootloaders + * @{ + * + * @file + * @brief RIOT-based bootloader with USB-DFU support + * + * @author Kaspar Schleiser + * @author Francisco Acosta + * @author Dylan Laduranty + * + * @} + */ + +#include "cpu.h" +#include "panic.h" +#include "riotboot/slot.h" +#include "riotboot/usb_dfu.h" +#include "ztimer.h" + +#include "riotboot/bootloader_selection.h" + +#ifdef BTN_BOOTLOADER_PIN +#include "periph/gpio.h" +#endif + +static bool _bootloader_alternative_mode(void) +{ +#ifdef BTN_BOOTLOADER_PIN + gpio_init(BTN_BOOTLOADER_PIN, BTN_BOOTLOADER_MODE); + return (bool)gpio_read(BTN_BOOTLOADER_PIN) != BTN_BOOTLOADER_INVERTED; +#else + return false; +#endif +} + +void kernel_init(void) +{ + uint32_t version = 0; + int slot = -1; + + for (unsigned i = 0; i < riotboot_slot_numof; i++) { + const riotboot_hdr_t *riot_hdr = riotboot_slot_get_hdr(i); + if (riotboot_slot_validate(i)) { + /* skip slot if metadata broken */ + continue; + } + if (riot_hdr->start_addr != riotboot_slot_get_image_startaddr(i)) { + continue; + } + if (slot == -1 || riot_hdr->version > version) { + version = riot_hdr->version; + slot = i; + } + } + + /* Init ztimer before starting DFU mode */ + ztimer_init(); + + /* Flash the unused slot if magic word is set */ + riotboot_usb_dfu_init(0); + + if (slot != -1 && !_bootloader_alternative_mode()) { + riotboot_slot_jump(slot); + } + + /* Nothing to boot, stay in DFU mode to flash a slot */ + riotboot_usb_dfu_init(1); +} + +NORETURN void core_panic(core_panic_t crash_code, const char *message) +{ + (void)crash_code; + (void)message; + while (1) {} +} diff --git a/cpu/cortexm_common/Makefile.include b/cpu/cortexm_common/Makefile.include index 9fd250d98470..04789eaf5c11 100644 --- a/cpu/cortexm_common/Makefile.include +++ b/cpu/cortexm_common/Makefile.include @@ -70,8 +70,8 @@ else endif # Configure riotboot bootloader and slot lengths -# 4KB are currently enough, set it to 16KB if USB-DFU is used -ifneq (,$(filter usbus_dfu,$(USEMODULE))) +# 4KB are currently enough, set it to 16KB if USB-DFU or tinyUSB DFU is used +ifneq (,$(filter usbus_dfu tinyusb_dfu,$(USEMODULE))) RIOTBOOT_LEN ?= 0x4000 else RIOTBOOT_LEN ?= 0x1000 diff --git a/cpu/nrf52/Makefile.include b/cpu/nrf52/Makefile.include index 98e984bf8754..e0dd3beefdbf 100644 --- a/cpu/nrf52/Makefile.include +++ b/cpu/nrf52/Makefile.include @@ -1,7 +1,7 @@ # Slot size is determined by "((total_flash_size - RIOTBOOT_LEN) / 2)". # If RIOTBOOT_LEN uses an uneven number of flashpages, the remainder of the # flash cannot be divided by two slots while staying FLASHPAGE_SIZE aligned. -ifneq (,$(filter usbus_dfu,$(USEMODULE))) +ifneq (,$(filter usbus_dfu tinyusb_dfu,$(USEMODULE))) RIOTBOOT_LEN ?= 0x4000 else RIOTBOOT_LEN ?= 0x2000 diff --git a/makefiles/boot/riotboot_dfu-util.mk b/makefiles/boot/riotboot_dfu-util.mk index 457fe53a8004..b225c4ffd752 100644 --- a/makefiles/boot/riotboot_dfu-util.mk +++ b/makefiles/boot/riotboot_dfu-util.mk @@ -1,6 +1,7 @@ -ifneq (,$(filter usbus_dfu,$(USEMODULE))) - ifeq (,$(filter riotboot_usb_dfu,$(USEMODULE))) - # If module usbus_dfu is used but not module riotboot_usb_dfu, the +ifneq (,$(filter usbus_dfu tinyusb_dfu,$(USEMODULE))) + ifeq (,$(filter riotboot_usb_dfu riotboot_tinyusb_dfu,$(USEMODULE))) + # If module usbus_dfu or module tinyusb_dfu is used but neither + # module riotboot_usb_dfu nor module riotboot_tinyusb_dfu, the # application uses DFU Runtime and dfu-util as programmer to flash the # application with the bootloader riotboot_dfu which uses the VID/PID pair # allocated for the RIOT bootloader https://pid.codes/1209/7D02/ diff --git a/pkg/tinyusb/Kconfig b/pkg/tinyusb/Kconfig index 9a6e2a64f98b..5ea5dd4d61b1 100644 --- a/pkg/tinyusb/Kconfig +++ b/pkg/tinyusb/Kconfig @@ -17,6 +17,7 @@ config HAS_TINYUSB_HOST menuconfig PACKAGE_TINYUSB bool "TinyUSB stack package" + depends on TEST_KCONFIG depends on HAS_ARCH_32BIT depends on HAS_TINYUSB_DEVICE || HAS_TINYUSB_HOST select MODULE_FMT @@ -90,8 +91,8 @@ if PACKAGE_TINYUSB config MODULE_AUTO_INIT_TINYUSB bool "Auto-initialize the tinyUSB package" - default y depends on MODULE_AUTO_INIT + default y help The tinyUSB stack including the used peripherals are initialized automatically at startup. Additionally, the auto-initialization @@ -160,15 +161,8 @@ menu "Device Classes" depends on MODULE_TINYUSB_DEVICE rsource "Kconfig.cdc" - - config MODULE_TINYUSB_CLASS_DFU - bool "Device Firmware Update (DFU) Runtime" - depends on MODULE_TINYUSB_DEVICE - - config MODULE_TINYUSB_CLASS_DFU_RUNTIME - bool "Device Firmware Update (DFU)" - depends on MODULE_TINYUSB_DEVICE - + rsource "dfu/Kconfig.dfu" + rsource "dfu/Kconfig.dfu_rt" rsource "Kconfig.hid" rsource "Kconfig.msc" @@ -224,6 +218,29 @@ config TUSBD_USE_CUSTOM_DESC interface. In all other cases, custom descriptors must be implemented and handled. +config MODULE_TINYUSB_DFU + bool "tinyUSB DFU driver module" + select MODULE_TINYUSB_CLASS_DFU if MODULE_RIOTBOOT_TINYUSB_DFU + select MODULE_TINYUSB_CLASS_DFU_RUNTIME if !MODULE_RIOTBOOT_TINYUSB_DFU + help + Enable tinyUSB Device Firmware Upgrade driver implementation used + either in DFU mode by the bootloader or in DFU runtime mode by the + application. It is enabled by default, if the tinyUSB DFU variant + of the riotboot bootloader is used. + +config MODULE_RIOTBOOT_TINYUSB_DFU + # TODO move to sys/riotboot/Kconfig once it is modelled + bool "tinyUSB DFU variant of riotboot bootloader" + depends on HAS_NO_IDLE_THREAD + depends on HAS_PERIPH_PM + select MODULE_RIOTBOOT_FLASHWRITE + select MODULE_TINYUSB_DFU + select MODULE_TINYUSB_CLASS_DFU + select MODULE_ZTIMER_SEC + help + Enable this option to use the tinyUSB DFU variant of the riotboot + bootloader. + endif # MODULE_TINYUSB_DEVICE endif # PACKAGE_TINYUSB diff --git a/pkg/tinyusb/Makefile b/pkg/tinyusb/Makefile index 9fc37cb78bb8..f2b183a67aaf 100644 --- a/pkg/tinyusb/Makefile +++ b/pkg/tinyusb/Makefile @@ -20,6 +20,9 @@ stdio_tinyusb_cdc_acm: tinyusb_contrib: $(QQ)"$(MAKE)" -C $(RIOTPKG)/$(PKG_NAME)/contrib +tinyusb_dfu: + $(QQ)"$(MAKE)" -C $(RIOTPKG)/$(PKG_NAME)/dfu + tinyusb_hw: $(QQ)"$(MAKE)" -C $(RIOTPKG)/$(PKG_NAME)/hw diff --git a/pkg/tinyusb/Makefile.dep b/pkg/tinyusb/Makefile.dep index a66392368078..d772ece099be 100644 --- a/pkg/tinyusb/Makefile.dep +++ b/pkg/tinyusb/Makefile.dep @@ -8,12 +8,28 @@ USEMODULE += tinyusb_hw DEFAULT_MODULE += auto_init_tinyusb +ifneq (,$(filter riotboot_tinyusb_dfu, $(USEMODULE))) + FEATURES_REQUIRED += no_idle_thread + FEATURES_REQUIRED += periph_pm + USEMODULE += riotboot_flashwrite + USEMODULE += tinyusb_dfu + USEMODULE += ztimer_sec +endif + ifneq (,$(filter stdio_tinyusb_cdc_acm, $(USEMODULE))) USEMODULE += stdio_available USEMODULE += tinyusb_class_cdc USEMODULE += tinyusb_device endif +ifneq (,$(filter tinyusb_dfu,$(USEMODULE))) + ifneq (,$(filter riotboot_tinyusb_dfu,$(USEMODULE))) + USEMODULE += tinyusb_class_dfu + else + USEMODULE += tinyusb_class_dfu_runtime + endif +endif + ifeq (,$(filter tinyusb_class_%,$(USEMODULE))) $(error At least one tinyusb_class_* module has to be enabled) endif @@ -30,31 +46,31 @@ endif # Following device classes work only with tinyUSB device stack ifneq (,$(filter tinyusb_class_audio,$(USEMODULE))) - FEATURES_REQUIRED += tinyusb_device + USEMODULE += tinyusb_device endif ifneq (,$(filter tinyusb_class_bth,$(USEMODULE))) - FEATURES_REQUIRED += tinyusb_device + USEMODULE += tinyusb_device endif ifneq (,$(filter tinyusb_class_dfu,$(USEMODULE))) - FEATURES_REQUIRED += tinyusb_device + USEMODULE += tinyusb_device endif ifneq (,$(filter tinyusb_class_dfu_runtime,$(USEMODULE))) - FEATURES_REQUIRED += tinyusb_device + USEMODULE += tinyusb_device endif ifneq (,$(filter tinyusb_class_midi,$(USEMODULE))) - FEATURES_REQUIRED += tinyusb_device + USEMODULE += tinyusb_device endif ifneq (,$(filter tinyusb_class_net_ecm_rndis,$(USEMODULE))) - FEATURES_REQUIRED += tinyusb_device + USEMODULE += tinyusb_device endif ifneq (,$(filter tinyusb_class_net_ncm,$(USEMODULE))) - FEATURES_REQUIRED += tinyusb_device + USEMODULE += tinyusb_device endif ifneq (,$(filter tinyusb_class_usbtmc,$(USEMODULE))) - FEATURES_REQUIRED += tinyusb_device + USEMODULE += tinyusb_device endif ifneq (,$(filter tinyusb_class_video,$(USEMODULE))) - FEATURES_REQUIRED += tinyusb_device + USEMODULE += tinyusb_device endif # tinyUSB hardware driver selection diff --git a/pkg/tinyusb/Makefile.include b/pkg/tinyusb/Makefile.include index 4e7782348a34..bf64577352b2 100644 --- a/pkg/tinyusb/Makefile.include +++ b/pkg/tinyusb/Makefile.include @@ -33,3 +33,7 @@ endif ifneq (,$(filter tinyusb_class_net_ecm_rndis,$(USEMODULE))) INCLUDES += -I$(PKGDIRBASE)/tinyusb/lib/networking endif + +ifneq (,$(filter tinyusb_dfu,$(USEMODULE))) + INCLUDES += -I$(RIOTBASE)/pkg/tinyusb/dfu/include +endif diff --git a/pkg/tinyusb/contrib/include/tinyusb_descriptors.h b/pkg/tinyusb/contrib/include/tinyusb_descriptors.h index b163200fc49c..3b460c058d8c 100644 --- a/pkg/tinyusb/contrib/include/tinyusb_descriptors.h +++ b/pkg/tinyusb/contrib/include/tinyusb_descriptors.h @@ -41,6 +41,12 @@ enum { TUSBD_ITF_CDC_1, /**< CDC1 Notification interface */ TUSBD_ITF_CDC_1_DATA, /**< CDC1 Data interface */ #endif +#if CONFIG_TUSBD_DFU_NUMOF + TUSBD_ITF_DFU, /**< DFU interface */ +#endif +#if CONFIG_TUSBD_DFU_RT_NUMOF + TUSBD_ITF_DFU_RT, /**< DFU Runtime interface */ +#endif #if CONFIG_TUSBD_HID_NUMOF > 0 TUSBD_ITF_HID_0, /**< HID0 interface */ #endif @@ -101,6 +107,13 @@ enum { #if CONFIG_TUSBD_CDC_NUMOF > 1 TUSBD_STR_IDX_CDC_1, #endif +#if CONFIG_TUSBD_DFU_NUMOF + TUSBD_STR_IDX_DFU_SLOT_0, + TUSBD_STR_IDX_DFU_SLOT_1, +#endif +#if CONFIG_TUSBD_DFU_RT_NUMOF + TUSBD_STR_IDX_DFU_RT, +#endif #if CONFIG_TUSBD_HID_NUMOF > 0 TUSBD_STR_IDX_HID_0, #endif @@ -117,9 +130,14 @@ enum { }; #endif /* !defined(HAVE_TUSBD_STR_IDX_TYPE) */ +/* only two slots are supported */ +#define CONFIG_TUSBD_DFU_ALT_NUMOF 2 + #if !defined(TUSBD_DESC_TOTAL_LEN) #define TUSBD_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + \ (CONFIG_TUSBD_CDC_NUMOF * TUD_CDC_DESC_LEN) + \ + (CONFIG_TUSBD_DFU_NUMOF * TUD_DFU_DESC_LEN(CONFIG_TUSBD_DFU_ALT_NUMOF)) + \ + (CONFIG_TUSBD_DFU_RT_NUMOF * TUD_DFU_RT_DESC_LEN) + \ (CONFIG_TUSBD_HID_NUMOF * TUD_HID_INOUT_DESC_LEN) + \ (CONFIG_TUSBD_MSC_NUMOF * TUD_MSC_DESC_LEN) + \ (CONFIG_TUSBD_VENDOR_NUMOF * TUD_VENDOR_DESC_LEN)) @@ -127,6 +145,8 @@ enum { #define TUSBD_DESC_ALT_TOTAL_LEN (TUD_CONFIG_DESC_LEN + \ (CONFIG_TUSBD_CDC_NUMOF * TUD_CDC_DESC_LEN) + \ + (CONFIG_TUSBD_DFU_NUMOF * TUD_DFU_DESC_LEN(CONFIG_TUSBD_DFU_ALT_NUMOF)) + \ + (CONFIG_TUSBD_DFU_RT_NUMOF * TUD_DFU_RT_DESC_LEN) + \ (CONFIG_TUSBD_HID_NUMOF * TUD_HID_INOUT_DESC_LEN) + \ (CONFIG_TUSBD_MSC_NUMOF * TUD_MSC_DESC_LEN) + \ (CONFIG_TUSBD_VENDOR_NUMOF * TUD_VENDOR_DESC_LEN)) diff --git a/pkg/tinyusb/contrib/include/tusb_config.h b/pkg/tinyusb/contrib/include/tusb_config.h index df5b44dd5ce4..4ecd00881d4b 100644 --- a/pkg/tinyusb/contrib/include/tusb_config.h +++ b/pkg/tinyusb/contrib/include/tusb_config.h @@ -75,7 +75,7 @@ #endif #ifndef CONFIG_TUSBD_DFU_RT_NUMOF -#if MODULE_TINYUSB_DEVICE && MODULE_TINYUSB_CLASS_DFU_RT +#if MODULE_TINYUSB_DEVICE && MODULE_TINYUSB_CLASS_DFU_RUNTIME #define CONFIG_TUSBD_DFU_RT_NUMOF 1 #else #define CONFIG_TUSBD_DFU_RT_NUMOF 0 @@ -186,10 +186,6 @@ #endif #endif -#ifndef CONFIG_TUSBD_CDC_NOTIF_EP_SIZE -#define CONFIG_TUSBD_CDC_NOTIF_EP_SIZE 8 -#endif - #ifndef CONFIG_TUSBD_EP0_SIZE #define CONFIG_TUSBD_EP0_SIZE 64 #endif @@ -202,6 +198,10 @@ #define CONFIG_TUSBD_HS_EP_SIZE 512 #endif +#ifndef CONFIG_TUSBD_CDC_NOTIF_EP_SIZE +#define CONFIG_TUSBD_CDC_NOTIF_EP_SIZE 8 +#endif + #ifndef CONFIG_TUSBD_CDC_FS_EP_SIZE #define CONFIG_TUSBD_CDC_FS_EP_SIZE CONFIG_TUSBD_FS_EP_SIZE #endif @@ -210,6 +210,48 @@ #define CONFIG_TUSBD_CDC_HS_EP_SIZE CONFIG_TUSBD_HS_EP_SIZE #endif +#ifndef CONFIG_TUSBD_DFU_ATTR +#define CONFIG_TUSBD_DFU_ATTR (DFU_ATTR_CAN_DOWNLOAD | \ + DFU_ATTR_WILL_DETACH | \ + DFU_ATTR_MANIFESTATION_TOLERANT) +#endif + +#ifndef CONFIG_TUSBD_DFU_DETACH_TIMEOUT +#define CONFIG_TUSBD_DFU_DETACH_TIMEOUT 1000 +#endif + +#ifndef CONFIG_TUSBD_DFU_POLL_TIMEOUT +#define CONFIG_TUSBD_DFU_POLL_TIMEOUT 1 +#endif + +#ifndef CONFIG_TUSBD_DFU_RESET_DELAY +#define CONFIG_TUSBD_DFU_RESET_DELAY 2 +#endif + +#ifndef CONFIG_TUSBD_DFU_FS_XFER_SIZE +#define CONFIG_TUSBD_DFU_FS_XFER_SIZE CONFIG_TUSBD_FS_EP_SIZE +#endif + +#ifndef CONFIG_TUSBD_DFU_HS_XFER_SIZE +#define CONFIG_TUSBD_DFU_HS_XFER_SIZE CONFIG_TUSBD_HS_EP_SIZE +#endif + +#ifndef CONFIG_TUSBD_HID_EP_SIZE +#define CONFIG_TUSBD_HID_EP_SIZE CONFIG_TUSBD_FS_EP_SIZE +#endif + +#ifndef CONFIG_TUSBD_DFU_RT_DETACH_TIMEOUT +#define CONFIG_TUSBD_DFU_RT_DETACH_TIMEOUT 1000 +#endif + +#ifndef CONFIG_TUSBD_DFU_RT_FS_XFER_SIZE +#define CONFIG_TUSBD_DFU_RT_FS_XFER_SIZE CONFIG_TUSBD_FS_EP_SIZE +#endif + +#ifndef CONFIG_TUSBD_DFU_RT_HS_XFER_SIZE +#define CONFIG_TUSBD_DFU_RT_HS_XFER_SIZE CONFIG_TUSBD_HS_EP_SIZE +#endif + #ifndef CONFIG_TUSBD_HID_EP_SIZE #define CONFIG_TUSBD_HID_EP_SIZE CONFIG_TUSBD_FS_EP_SIZE #endif @@ -263,7 +305,9 @@ #define CFG_TUSB_OS OPT_OS_CUSTOM /** Debug log level */ +#ifndef CFG_TUSB_DEBUG #define CFG_TUSB_DEBUG 0 +#endif /** * @brief DMA memory section and alignment @@ -337,8 +381,8 @@ * @name Typical required DFU device class configurations * @{ */ -#define CFG_TUD_DFU_XFER_BUFSIZE (TUD_OPT_HIGH_SPEED ? CONFIG_TUSBD_HS_EP_SIZE \ - : CONFIG_TUSBD_FS_EP_SIZE) +#define CFG_TUD_DFU_XFER_BUFSIZE (TUD_OPT_HIGH_SPEED ? CONFIG_TUSBD_DFU_HS_XFER_SIZE \ + : CONFIG_TUSBD_DFU_FS_XFER_SIZE) /** @} */ /** diff --git a/pkg/tinyusb/contrib/tinyusb.c b/pkg/tinyusb/contrib/tinyusb.c index 98138f7d9cf6..5e858620c4c0 100644 --- a/pkg/tinyusb/contrib/tinyusb.c +++ b/pkg/tinyusb/contrib/tinyusb.c @@ -75,7 +75,11 @@ int tinyusb_setup(void) if ((res = thread_create(_tinyusb_thread_stack, sizeof(_tinyusb_thread_stack), TINYUSB_PRIORITY, +#if MODULE_RIOTBOOT_TINYUSB_DFU + THREAD_CREATE_STACKTEST, +#else THREAD_CREATE_WOUT_YIELD | THREAD_CREATE_STACKTEST, +#endif _tinyusb_thread_impl, NULL, "tinyusb")) < 0) { LOG_ERROR("tinyUSB thread couldn't be created, reason %d\n", res); return res; diff --git a/pkg/tinyusb/contrib/tinyusb_descriptors.c b/pkg/tinyusb/contrib/tinyusb_descriptors.c index 22d503011d00..18fe22974d8b 100644 --- a/pkg/tinyusb/contrib/tinyusb_descriptors.c +++ b/pkg/tinyusb/contrib/tinyusb_descriptors.c @@ -30,6 +30,10 @@ #include "tusb.h" #include "usb.h" +#ifdef MODULE_TINYUSB_DFU +#include "riotboot/usb_dfu.h" +#endif + #include "tinyusb_descriptors.h" #define ENABLE_DEBUG 0 @@ -40,16 +44,17 @@ #if (MODULE_TINYUSB_CLASS_AUDIO || \ MODULE_TINYUSB_CLASS_BTH || \ - MODULE_TINYUSB_CLASS_DFU || \ - MODULE_TINYUSB_CLASS_DFU_RUNTIME || \ MODULE_TINYUSB_CLASS_MIDI || \ MODULE_TINYUSB_CLASS_NET_ECM_RNDIS || \ MODULE_TINYUSB_CLASS_NET_NCM || \ MODULE_TINYUSB_CLASS_USBTMC || \ MODULE_TINYUSB_CLASS_VIDEO || \ (CONFIG_TUSBD_CDC_NUMOF > 2) || \ + (CONFIG_TUSBD_DFU_NUMOF > 1) || \ + (CONFIG_TUSBD_DFU_RT_NUMOF > 1) || \ (CONFIG_TUSBD_HID_NUMOF > 2) || \ - (CONFIG_TUSBD_MSC_NUMOF > 1)) + (CONFIG_TUSBD_MSC_NUMOF > 1) || \ + (CONFIG_TUSBD_VENDOR_NUMOF > 1)) #error Using generic descriptors is not possible for the selected combination \ of device class interfaces. Custom descriptors have to be implemented. #endif @@ -226,6 +231,24 @@ void tud_hid_set_report_cb(uint8_t itf, uint8_t report_id, speed ? CONFIG_TUSBD_CDC_HS_EP_SIZE \ : CONFIG_TUSBD_CDC_FS_EP_SIZE) +#define _TUD_DFU_DESC(speed) \ + /* Interface number, alternate count, starting string index, attributes, + * detach timeout, transfer size */ \ + TUD_DFU_DESCRIPTOR(TUSBD_ITF_DFU, CONFIG_TUSBD_DFU_ALT_NUMOF, \ + TUSBD_STR_IDX_DFU_SLOT_0, CONFIG_TUSBD_DFU_ATTR, \ + CONFIG_TUSBD_DFU_DETACH_TIMEOUT, \ + speed ? CONFIG_TUSBD_DFU_HS_XFER_SIZE \ + : CONFIG_TUSBD_DFU_FS_XFER_SIZE) + +#define _TUD_DFU_RT_DESC(speed) \ + /* Interface number, alternate count, starting string index, attributes, + * detach timeout, transfer size */ \ + TUD_DFU_RT_DESCRIPTOR(TUSBD_ITF_DFU_RT, \ + TUSBD_STR_IDX_DFU_RT, DFU_ATTR_WILL_DETACH, \ + CONFIG_TUSBD_DFU_RT_DETACH_TIMEOUT, \ + speed ? CONFIG_TUSBD_DFU_RT_HS_XFER_SIZE \ + : CONFIG_TUSBD_DFU_RT_FS_XFER_SIZE) + #define _TUD_HID_INOUT_DESC(speed, n) \ /* Interface number, string index, protocol, report descriptor len, * EP Out & EP In address, EP size, polling interval */ \ @@ -260,6 +283,12 @@ uint8_t const tusb_desc_fs_config[] = { #if CONFIG_TUSBD_CDC_NUMOF > 1 _TUD_CDC_DESC(_tusb_speed_fs, 1), #endif +#if CONFIG_TUSBD_DFU_NUMOF + _TUD_DFU_DESC(_tusb_speed_fs), +#endif +#if CONFIG_TUSBD_DFU_RT_NUMOF + _TUD_DFU_RT_DESC(_tusb_speed_fs), +#endif #if CONFIG_TUSBD_HID_NUMOF > 0 _TUD_HID_INOUT_DESC(_tusb_speed_fs, 0), #endif @@ -284,6 +313,12 @@ uint8_t const tusb_desc_fs_config_alt[] = { #if CONFIG_TUSBD_CDC_NUMOF > 1 _TUD_CDC_DESC(_tusb_speed_fs, 1), #endif +#if CONFIG_TUSBD_DFU_NUMOF + _TUD_DFU_DESC(_tusb_speed_fs), +#endif +#if CONFIG_TUSBD_DFU_RT_NUMOF + _TUD_DFU_RT_DESC(_tusb_speed_fs), +#endif #if CONFIG_TUSBD_HID_NUMOF > 0 _TUD_HID_INOUT_DESC(_tusb_speed_fs, 0), #endif @@ -313,6 +348,12 @@ uint8_t const tusb_desc_hs_config[] = { #if CONFIG_TUSBD_CDC_NUMOF > 1 _TUD_CDC_DESC(_tusb_speed_hs, 1), #endif +#if CONFIG_TUSBD_DFU + _TUD_DFU_DESC(_tusb_speed_hs), +#endif +#if CONFIG_TUSBD_DFU_RT_NUMOF + _TUD_DFU_RT_DESC(_tusb_speed_hs), +#endif #if CONFIG_TUSBD_HID_NUMOF > 0 _TUD_HID_INOUT_DESC(_tusb_speed_hs, 0), #endif @@ -337,6 +378,12 @@ uint8_t const tusb_desc_hs_config_alt[] = { #if CONFIG_TUSBD_CDC_NUMOF > 1 _TUD_CDC_DESC(_tusb_speed_hs, 1), #endif +#if CONFIG_TUSBD_DFU + _TUD_DFU_DESC(_tusb_speed_hs), +#endif +#if CONFIG_TUSBD_DFU_RT_NUMOF + _TUD_DFU_RT_DESC(_tusb_speed_hs), +#endif #if CONFIG_TUSBD_HID_NUMOF > 0 _TUD_HID_INOUT_DESC(_tusb_speed_hs, 0), #endif @@ -488,6 +535,18 @@ uint8_t const *tud_descriptor_configuration_cb(uint8_t index) #define CONFIG_TUSBD_CDC_1_STRING "TinyUSB CDC1" #endif +#ifndef CONFIG_TUSBD_DFU_0_STRING +#define CONFIG_TUSBD_DFU_0_STRING USB_DFU_MODE_SLOT0_NAME +#endif + +#ifndef CONFIG_TUSBD_DFU_1_STRING +#define CONFIG_TUSBD_DFU_1_STRING USB_DFU_MODE_SLOT1_NAME +#endif + +#ifndef CONFIG_TUSBD_DFU_RT_STRING +#define CONFIG_TUSBD_DFU_RT_STRING USB_APP_MODE_SLOT_NAME +#endif + #ifndef CONFIG_TUSBD_HID_0_STRING #define CONFIG_TUSBD_HID_0_STRING "TinyUSB HID0 (Generic In/Out)" #endif @@ -524,6 +583,13 @@ char const* tusb_string_desc_array[] = { #if CONFIG_TUSBD_CDC_NUMOF > 1 CONFIG_TUSBD_CDC_1_STRING, /* CDC Interface 1 */ #endif +#if CONFIG_TUSBD_DFU_NUMOF + CONFIG_TUSBD_DFU_0_STRING, /* DFU Firmware Slot 0 */ + CONFIG_TUSBD_DFU_1_STRING, /* DFU Firmware Slot 1 */ +#endif +#if CONFIG_TUSBD_DFU_RT_NUMOF + CONFIG_TUSBD_DFU_RT_STRING, /* APP mode */ +#endif #if CONFIG_TUSBD_HID_NUMOF > 0 CONFIG_TUSBD_HID_0_STRING, /* HID Interface 0 */ #endif diff --git a/pkg/tinyusb/dfu/Kconfig.dfu b/pkg/tinyusb/dfu/Kconfig.dfu new file mode 100644 index 000000000000..11733d4eadf9 --- /dev/null +++ b/pkg/tinyusb/dfu/Kconfig.dfu @@ -0,0 +1,50 @@ +# Copyright (c) 2022 Gunar Schorcht +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. +# + +menuconfig MODULE_TINYUSB_CLASS_DFU + bool "Device Firmware Update (DFU)" + depends on MODULE_TINYUSB_DEVICE && MODULE_TINYUSB_DFU && MODULE_RIOTBOOT_TINYUSB_DFU + +if MODULE_TINYUSB_CLASS_DFU + +config TUSBD_DFU_NUMOF + int + default 1 + +config TUSBD_DFU_FS_XFER_SIZE + int "DFU Full-Speed transfer size [byte]" + default 64 + +config TUSBD_DFU_HS_XFER_SIZE + int "DFU High-Speed transfer size [byte]" + default 512 + +config TUSBD_DFU_ALT_NUMOF + int + default 2 + help + Number of alternative DFU firmware slots. + +config TUSBD_DFU_DETACH_TIMEOUT + int "DFU detach timeout [ms]" + default 1000 + +config TUSBD_DFU_POLL_TIMEOUT + int "DFU poll timeout [ms]" + default 1 + help + DFU Poll Timeout is the time before the host requests the status + from the device during a firmware download or manifestation operation. + +config TUSBD_DFU_RESET_DELAY + int "DFU reset delay [s]" + default 2 + help + DFU reset delay is the time before the device is restarted after + a firmware download. + +endif # MODULE_TINYUSB_CLASS_DFU diff --git a/pkg/tinyusb/dfu/Kconfig.dfu_rt b/pkg/tinyusb/dfu/Kconfig.dfu_rt new file mode 100644 index 000000000000..9a86680741a8 --- /dev/null +++ b/pkg/tinyusb/dfu/Kconfig.dfu_rt @@ -0,0 +1,30 @@ +# Copyright (c) 2022 Gunar Schorcht +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. +# + +menuconfig MODULE_TINYUSB_CLASS_DFU_RUNTIME + bool "Device Firmware Update Runtime (DFU Runtime)" + depends on MODULE_TINYUSB_DEVICE && MODULE_TINYUSB_DFU && !MODULE_RIOTBOOT_TINYUSB_DFU + +if MODULE_TINYUSB_CLASS_DFU_RUNTIME + +config TUSBD_DFU_RT_NUMOF + int + default 1 + +config TUSBD_DFU_RT_FS_XFER_SIZE + int "DFU Full-Speed transfer size [byte]" + default 64 + +config TUSBD_DFU_RT_HS_XFER_SIZE + int "DFU High-Speed transfer size [byte]" + default 512 + +config TUSBD_DFU_RT_DETACH_TIMEOUT + int "DFU detach timeout [ms]" + default 1000 + +endif # MODULE_TINYUSB_CLASS_DFU_RUNTIME diff --git a/pkg/tinyusb/dfu/Makefile b/pkg/tinyusb/dfu/Makefile new file mode 100644 index 000000000000..f6ceaeee0a61 --- /dev/null +++ b/pkg/tinyusb/dfu/Makefile @@ -0,0 +1,3 @@ +MODULE = tinyusb_dfu + +include $(RIOTBASE)/Makefile.base diff --git a/pkg/tinyusb/dfu/include/tinyusb_dfu.h b/pkg/tinyusb/dfu/include/tinyusb_dfu.h new file mode 100644 index 000000000000..e03a2bd76b75 --- /dev/null +++ b/pkg/tinyusb/dfu/include/tinyusb_dfu.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2022 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup pkg_tinyusb_dfu + * @{ + * @file + * @brief TinyUSB specific DFU definitions + * + * @author Gunar Schorcht + */ + +#ifndef TINYUSB_DFU_H +#define TINYUSB_DFU_H + +#include "riotboot/flashwrite.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief tinyUSB DFU device interface context + */ +typedef struct tinyusb_dfu_device { + bool skip_signature; /**< Skip RIOTBOOT signature status */ + uint8_t slot; /**< Download slot */ +#ifdef MODULE_RIOTBOOT_TINYUSB_DFU + riotboot_flashwrite_t writer; /**< DFU firmware update state structure */ +#endif +} tinyusb_dfu_device_t; + +/** + * @brief Initialize the tinyUSB DFU device interface context + */ +void tinyusb_dfu_init(void); + +#ifdef __cplusplus +} +#endif + +#endif /* TINYUSB_DFU_H */ +/** @} */ diff --git a/pkg/tinyusb/dfu/tinyusb_dfu.c b/pkg/tinyusb/dfu/tinyusb_dfu.c new file mode 100644 index 000000000000..b5d041241328 --- /dev/null +++ b/pkg/tinyusb/dfu/tinyusb_dfu.c @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2022 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup pkg_tinyusb_dfu + * @{ + * @file TinyUSB DFU implementation + * + * @author Gunar Schorcht + * @} + */ + +#include "log.h" +#include "periph/pm.h" +#include "riotboot/magic.h" +#include "riotboot/slot.h" + +#ifdef MODULE_RIOTBOOT_TINYUSB_DFU +#include "ztimer.h" +#endif + +#include "device/usbd.h" +#include "class/dfu/dfu_device.h" + +#include "tinyusb_dfu.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#ifdef MODULE_RIOTBOOT_TINYUSB_DFU +static void _reboot(void *arg); +static ztimer_t scheduled_reboot = { .callback=_reboot }; +#endif + +/* there is only one instance of DFU devices */ +tinyusb_dfu_device_t _tusb_dfu_dev = { .skip_signature = true }; + +void tinyusb_dfu_init(void) +{ + _tusb_dfu_dev.skip_signature = true; + _tusb_dfu_dev.slot = 0; +} + +#ifdef MODULE_RIOTBOOT_TINYUSB_DFU + +uint32_t tud_dfu_get_timeout_cb(uint8_t alt, uint8_t state) +{ + (void)alt; + (void)state; + + /* Invoked before tud_dfu_download_cb() or tud_dfu_manifest_cb() is called + * to determine the poll timeout for download and manifest operation */ + return CONFIG_TUSBD_DFU_POLL_TIMEOUT; +} + +void tud_dfu_download_cb(uint8_t alt, uint16_t block_num, uint8_t const *data, uint16_t length) +{ + /* Invoked when a DFU_DNLOAD (wLength > 0) followed by a DFU_GETSTATUS + * (state = DFU_DNBUSY) requests was received. */ + + (void)block_num; + + int ret = 0; + + if (_tusb_dfu_dev.skip_signature) { + /* Avoid underflow condition */ + if (length < RIOTBOOT_FLASHWRITE_SKIPLEN) { + return; + } + riotboot_flashwrite_init(&_tusb_dfu_dev.writer, alt); + length -= RIOTBOOT_FLASHWRITE_SKIPLEN; + DEBUG("[tinyusb_dfu] starting the download with %u bytes for slot %u " + "with block %u\n", length, alt, block_num); + _tusb_dfu_dev.skip_signature = false; + _tusb_dfu_dev.slot = alt; + ret = riotboot_flashwrite_putbytes(&_tusb_dfu_dev.writer, + &data[RIOTBOOT_FLASHWRITE_SKIPLEN], + length, true); + } + else { + assert(alt == _tusb_dfu_dev.slot); + + DEBUG("[tinyusb_dfu] continue the download with %u bytes for slot %u " + "with block %u\n", length, alt, block_num); + ret = riotboot_flashwrite_putbytes(&_tusb_dfu_dev.writer, + data, length, true); + } + if (ret < 0) { + LOG_ERROR("[tinyusb_dfu] error on writing %d bytes for slot %u\n", + length, alt); + tud_dfu_finish_flashing(DFU_STATUS_ERR_WRITE); + } + else { + tud_dfu_finish_flashing(DFU_STATUS_OK); + } +} + +void tud_dfu_manifest_cb(uint8_t alt) +{ + /* Invoked when the download process is complete and + * DFU_DNLOAD (wLength = 0) followed by a DFU_GETSTATUS (state = Manifest) + * was received. */ + + (void)alt; + + assert(alt == _tusb_dfu_dev.slot); + + DEBUG("[tinyusb_dfu] download for slot %u complete, " + "enter manifestation phase\n", alt); + + /* the host indicates that the download process is complete */ + riotboot_flashwrite_flush(&_tusb_dfu_dev.writer); + riotboot_flashwrite_finish(&_tusb_dfu_dev.writer); + + /* indicate that flashing is finished */ + tud_dfu_finish_flashing(DFU_STATUS_OK); + + /* scheduled reboot after CONFIG_TUSBD_DFU_RESET_DELAY seconds to give + * enough time to finish manifestation */ + ztimer_set(ZTIMER_SEC, &scheduled_reboot, CONFIG_TUSBD_DFU_RESET_DELAY); +} + +static void _reboot(void *arg) +{ + DEBUG("[tinyusb_dfu] reboot\n"); + + (void)arg; + pm_reboot(); +} +#endif /* MODULE_RIOTBOOT_TINYUSB_DFU */ + +TU_ATTR_WEAK void tud_dfu_detach_cb(void) +{ + /* the host sent a DFU_DETACH request */ + + DEBUG("[tinyusb_dfu] DFU_DETACH request received\n"); + + uint32_t *reset_addr = (uint32_t *)RIOTBOOT_MAGIC_ADDR; + + *reset_addr = RIOTBOOT_MAGIC_NUMBER; + pm_reboot(); +} + +void tud_dfu_runtime_reboot_to_dfu_cb(void) +{ + /* the host sent a DFU_DETACH request */ + tud_dfu_detach_cb(); +} diff --git a/pkg/tinyusb/doc.txt b/pkg/tinyusb/doc.txt index d0e92d17105c..20065a1f2ee3 100644 --- a/pkg/tinyusb/doc.txt +++ b/pkg/tinyusb/doc.txt @@ -92,6 +92,7 @@ * callbacks for any combination of * - up to two interfaces of the class CDC ACM, * - up to two interfaces of the Generic In/Out HID class, + * - up to one DFU interface * - up to one MSC device interface and, * - up to one interface of the Vendor device class. * diff --git a/pkg/tinyusb/hw/hw_stm32_fsdev.c b/pkg/tinyusb/hw/hw_stm32_fsdev.c index e45b19122970..6576fdf9e83e 100644 --- a/pkg/tinyusb/hw/hw_stm32_fsdev.c +++ b/pkg/tinyusb/hw/hw_stm32_fsdev.c @@ -19,7 +19,7 @@ #include "periph_conf.h" #include "periph/gpio.h" -#include "periph/pm.h" +#include "pm_layered.h" #include "tusb.h" #include "device/usbd.h" diff --git a/pkg/tinyusb/hw/hw_stm32_otg.c b/pkg/tinyusb/hw/hw_stm32_otg.c index 2346f38d68a6..75b3f88e4c09 100644 --- a/pkg/tinyusb/hw/hw_stm32_otg.c +++ b/pkg/tinyusb/hw/hw_stm32_otg.c @@ -19,7 +19,7 @@ #include "periph_conf.h" #include "periph/gpio.h" -#include "periph/pm.h" +#include "pm_layered.h" #include "tusb.h" #include "device/usbd.h" diff --git a/sys/Makefile.dep b/sys/Makefile.dep index 7b3c8991d28e..9d3cc38446fd 100644 --- a/sys/Makefile.dep +++ b/sys/Makefile.dep @@ -859,6 +859,10 @@ ifneq (,$(filter riotboot_usb_dfu, $(USEMODULE))) FEATURES_REQUIRED += periph_pm endif +ifneq (,$(filter riotboot_tinyusb_dfu, $(USEMODULE))) + USEPKG += tinyusb +endif + ifneq (,$(filter irq_handler,$(USEMODULE))) USEMODULE += event endif diff --git a/sys/Makefile.include b/sys/Makefile.include index 8a266165a6e4..777e67f7e2d3 100644 --- a/sys/Makefile.include +++ b/sys/Makefile.include @@ -163,7 +163,7 @@ ifneq (,$(filter prng,$(USEMODULE))) include $(RIOTBASE)/sys/random/Makefile.include endif -ifneq (,$(filter usbus_dfu riotboot_reset,$(USEMODULE))) +ifneq (,$(filter tinyusb_dfu usbus_dfu riotboot_reset,$(USEMODULE))) CFLAGS += -DCPU_RAM_BASE=$(RAM_START_ADDR) CFLAGS += -DCPU_RAM_SIZE=$(RAM_LEN) endif diff --git a/sys/riotboot/tinyusb_dfu.c b/sys/riotboot/tinyusb_dfu.c new file mode 100644 index 000000000000..afb31ffed9c5 --- /dev/null +++ b/sys/riotboot/tinyusb_dfu.c @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup sys_riotboot_usb_dfu + * @ingroup pkg_tinyusb_dfu + * @{ + * @file tinyUSB Device Firmware Upgrade initialization for riotboot + * + * @author Gunar Schorcht + * @} + */ + +#include "riotboot/magic.h" + +#include "tinyusb.h" +#include "tinyusb_dfu.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +extern void tinyusb_dfu_init(void); + +void riotboot_usb_dfu_init(unsigned forced) +{ + uint32_t *reset_addr = (uint32_t *)RIOTBOOT_MAGIC_ADDR; + + if (forced == 1 || *reset_addr == RIOTBOOT_MAGIC_NUMBER) { + *reset_addr = 0; + tinyusb_setup(); + tinyusb_dfu_init(); + } +}