Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bootloaders/riotboot: add tinyUSB DFU support #19010

Merged
merged 11 commits into from
Jan 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Makefile.dep
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
19 changes: 19 additions & 0 deletions bootloaders/riotboot_tinyusb_dfu/Makefile
Original file line number Diff line number Diff line change
@@ -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
86 changes: 86 additions & 0 deletions bootloaders/riotboot_tinyusb_dfu/doc.txt
Original file line number Diff line number Diff line change
@@ -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`.

*/
86 changes: 86 additions & 0 deletions bootloaders/riotboot_tinyusb_dfu/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright (C) 2017 Kaspar Schleiser <kaspar@schleiser.de>
* 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 <kaspar@schleiser.de>
* @author Francisco Acosta <francisco.acosta@inria.fr>
* @author Dylan Laduranty <dylan.laduranty@mesotic.com>
*
* @}
*/

#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) {}
}
4 changes: 2 additions & 2 deletions cpu/cortexm_common/Makefile.include
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion cpu/nrf52/Makefile.include
Original file line number Diff line number Diff line change
@@ -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
Expand Down
7 changes: 4 additions & 3 deletions makefiles/boot/riotboot_dfu-util.mk
Original file line number Diff line number Diff line change
@@ -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/
Expand Down
37 changes: 27 additions & 10 deletions pkg/tinyusb/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"

Expand Down Expand Up @@ -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

Expand Down
3 changes: 3 additions & 0 deletions pkg/tinyusb/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
34 changes: 25 additions & 9 deletions pkg/tinyusb/Makefile.dep
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions pkg/tinyusb/Makefile.include
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading