diff --git a/src/rp2_common/CMakeLists.txt b/src/rp2_common/CMakeLists.txt index ae0561fd7..5cd20a77c 100644 --- a/src/rp2_common/CMakeLists.txt +++ b/src/rp2_common/CMakeLists.txt @@ -36,6 +36,7 @@ if (NOT PICO_BARE_METAL) # NOTE THE ORDERING HERE IS IMPORTANT AS SOME TARGETS CHECK ON EXISTENCE OF OTHER TARGETS pico_add_subdirectory(boot_stage2) + pico_add_subdirectory(pico_bootsel_via_double_reset) pico_add_subdirectory(pico_multicore) pico_add_subdirectory(pico_unique_id) diff --git a/src/rp2_common/pico_bootsel_via_double_reset/CMakeLists.txt b/src/rp2_common/pico_bootsel_via_double_reset/CMakeLists.txt new file mode 100644 index 000000000..74384c6fb --- /dev/null +++ b/src/rp2_common/pico_bootsel_via_double_reset/CMakeLists.txt @@ -0,0 +1,10 @@ +add_library(pico_bootsel_via_double_reset INTERFACE) + +target_sources(pico_bootsel_via_double_reset INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/pico_bootsel_via_double_reset.c + ) + +target_link_libraries(pico_bootsel_via_double_reset INTERFACE + pico_bootrom + pico_time + ) diff --git a/src/rp2_common/pico_bootsel_via_double_reset/pico_bootsel_via_double_reset.c b/src/rp2_common/pico_bootsel_via_double_reset/pico_bootsel_via_double_reset.c new file mode 100644 index 000000000..28c32ba6e --- /dev/null +++ b/src/rp2_common/pico_bootsel_via_double_reset/pico_bootsel_via_double_reset.c @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2021 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "pico.h" +#include "pico/time.h" +#include "pico/bootrom.h" + +// PICO_CONFIG: PICO_BOOTSEL_VIA_DOUBLE_RESET_TIMEOUT_MS, Window of opportunity for a second press of a reset button to enter BOOTSEL mode (milliseconds), type=int, default=200, group=pico_bootsel_via_double_reset +#ifndef PICO_BOOTSEL_VIA_DOUBLE_RESET_TIMEOUT_MS +#define PICO_BOOTSEL_VIA_DOUBLE_RESET_TIMEOUT_MS 200 +#endif + +// PICO_CONFIG: PICO_BOOTSEL_VIA_DOUBLE_RESET_ACTIVITY_LED, GPIO to use as bootloader activity LED when BOOTSEL mode is entered via reset double tap (or -1 for none), type=int, default=-1, group=pico_bootsel_via_double_reset +#ifndef PICO_BOOTSEL_VIA_DOUBLE_RESET_ACTIVITY_LED +#define PICO_BOOTSEL_VIA_DOUBLE_RESET_ACTIVITY_LED -1 +#endif + +// PICO_CONFIG: PICO_BOOTSEL_VIA_DOUBLE_RESET_INTERFACE_DISABLE_MASK, Optionally disable either the mass storage interface (bit 0) or the PICOBOOT interface (bit 1) when entering BOOTSEL mode via double reset, type=int, default=0, group=pico_bootsel_via_double_reset +#ifndef PICO_BOOTSEL_VIA_DOUBLE_RESET_INTERFACE_DISABLE_MASK +#define PICO_BOOTSEL_VIA_DOUBLE_RESET_INTERFACE_DISABLE_MASK 0u +#endif + +/** \defgroup pico_bootsel_via_double_reset + * + * When the 'pico_bootsel_via_double_reset' library is linked, a function is + * injected before main() which will detect when the system has been reset + * twice in quick succession, and enter the USB ROM bootloader (BOOTSEL mode) + * when this happens. This allows a double tap of a reset button on a + * development board to be used to enter the ROM bootloader, provided this + * library is always linked. + */ + +// Doesn't make any sense for a RAM only binary +#if !PICO_NO_FLASH +static const uint32_t magic_token[] = { + 0xf01681de, 0xbd729b29, 0xd359be7a, +}; + +static uint32_t __uninitialized_ram(magic_location)[count_of(magic_token)]; + +/*! \brief Check for double reset and enter BOOTSEL mode if detected + * \ingroup pico_bootsel_via_double_reset + * + * This function is registered to run automatically before main(). The + * algorithm is: + * + * 1. Check for magic token in memory; enter BOOTSEL mode if found. + * 2. Initialise that memory with that magic token. + * 3. Do nothing for a short while (few hundred ms). + * 4. Clear the magic token. + * 5. Continue with normal boot. + * + * Resetting the device twice quickly will interrupt step 3, leaving the token + * in place so that the second boot will go to the bootloader. + */ +static void __attribute__((constructor)) boot_double_tap_check(void) { + for (uint i = 0; i < count_of(magic_token); i++) { + if (magic_location[i] != magic_token[i]) { + // Arm, wait, then disarm and continue booting + for (i = 0; i < count_of(magic_token); i++) { + magic_location[i] = magic_token[i]; + } + busy_wait_us(PICO_BOOTSEL_VIA_DOUBLE_RESET_TIMEOUT_MS * 1000); + magic_location[0] = 0; + return; + } + } + // Detected a double reset, so enter USB bootloader + magic_location[0] = 0; + uint32_t led_mask = PICO_BOOTSEL_VIA_DOUBLE_RESET_ACTIVITY_LED >= 0 ? + 1u << PICO_BOOTSEL_VIA_DOUBLE_RESET_ACTIVITY_LED : 0u; + reset_usb_boot( + led_mask, + PICO_BOOTSEL_VIA_DOUBLE_RESET_INTERFACE_DISABLE_MASK + ); +} + +#endif diff --git a/test/kitchen_sink/CMakeLists.txt b/test/kitchen_sink/CMakeLists.txt index d3625a24a..2e522dd99 100644 --- a/test/kitchen_sink/CMakeLists.txt +++ b/test/kitchen_sink/CMakeLists.txt @@ -27,6 +27,7 @@ target_link_libraries(kitchen_sink_libs INTERFACE hardware_xosc pico_bit_ops pico_bootrom + pico_bootsel_via_double_reset pico_divider pico_double pico_fix_rp2040_usb_device_enumeration