From 37b151111fd2887f4b4586665e7f91211f86253c Mon Sep 17 00:00:00 2001 From: Gunar Schorcht Date: Mon, 5 Dec 2022 17:30:51 +0100 Subject: [PATCH] pkg/tinyusb: add DFU and DFU_RT device class implementation --- Makefile.dep | 4 + pkg/tinyusb/Kconfig | 37 +++++-- pkg/tinyusb/Makefile | 3 + pkg/tinyusb/Makefile.dep | 16 +++ pkg/tinyusb/Makefile.include | 4 + pkg/tinyusb/contrib/tinyusb.c | 4 + pkg/tinyusb/dfu/Kconfig.dfu | 50 +++++++++ pkg/tinyusb/dfu/Kconfig.dfu_rt | 30 +++++ pkg/tinyusb/dfu/Makefile | 3 + pkg/tinyusb/dfu/include/tinyusb_dfu.h | 48 ++++++++ pkg/tinyusb/dfu/tinyusb_dfu.c | 153 ++++++++++++++++++++++++++ pkg/tinyusb/doc.txt | 1 + 12 files changed, 343 insertions(+), 10 deletions(-) create mode 100644 pkg/tinyusb/dfu/Kconfig.dfu create mode 100644 pkg/tinyusb/dfu/Kconfig.dfu_rt create mode 100644 pkg/tinyusb/dfu/Makefile create mode 100644 pkg/tinyusb/dfu/include/tinyusb_dfu.h create mode 100644 pkg/tinyusb/dfu/tinyusb_dfu.c diff --git a/Makefile.dep b/Makefile.dep index ad9c764824..8c6b96497d 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/pkg/tinyusb/Kconfig b/pkg/tinyusb/Kconfig index 9a6e2a64f9..5ea5dd4d61 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 9fc37cb78b..f2b183a67a 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 9413afc7e9..d772ece099 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 diff --git a/pkg/tinyusb/Makefile.include b/pkg/tinyusb/Makefile.include index 4e7782348a..bf64577352 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/tinyusb.c b/pkg/tinyusb/contrib/tinyusb.c index 98138f7d9c..5e858620c4 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/dfu/Kconfig.dfu b/pkg/tinyusb/dfu/Kconfig.dfu new file mode 100644 index 0000000000..11733d4ead --- /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 0000000000..9a86680741 --- /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 0000000000..f6ceaeee0a --- /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 0000000000..e03a2bd76b --- /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 0000000000..b5d0412413 --- /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 d0e92d1710..20065a1f2e 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. *