forked from jappel/VersaMCU
Hardware-tested the UF2 bootloader end to end on a real VersaPad v2 board. Found and fixed a real bug: the bootloader's jump into the app (__set_MSP -> SCB->VTOR -> bx) hard-faulted on every standalone boot, even with the debugger fully disconnected; identical register/VTOR values injected directly by a halted debugger ran fine, which pointed at the missing __DSB()/__ISB() barriers ARM's own guidance requires for this exact pattern. Also fixed a USB PID collision (0x0011 is Adafruit's own Gemma M0 bootloader PID, misidentified by Windows as a Circuit Playground COM port instead of exposing VERSABOOT). This board has no dedicated reset/boot button, so add a hardware boot entry that doesn't need one: holding the bottom-right Cherry MX key (key_id 24) during reset/power-on drives its matrix row and reads its column directly in the bootloader, before the app is even validated. Also corrected the app-side flash_with_bootloader.ld (was missing the NVM carve-out flash_without_bootloader.ld already has) and boards/versapad.json (wrong flash/RAM size, wrong MCU macro, stale PID), and enabled the previously-commented-out env:versapad_usb. Documented findings in bootloader/README.md, bootloader/TESTING.md, and doc/09_known_limitations.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
325 lines
9.5 KiB
C
325 lines
9.5 KiB
C
/* ----------------------------------------------------------------------------
|
|
* SAM Software Package License
|
|
* ----------------------------------------------------------------------------
|
|
* Copyright (c) 2011-2014, Atmel Corporation
|
|
*
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following condition is met:
|
|
*
|
|
* Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the disclaimer below.
|
|
*
|
|
* Atmel's name may not be used to endorse or promote products derived from
|
|
* this software without specific prior written permission.
|
|
*
|
|
* DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
|
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
|
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
/**
|
|
* --------------------
|
|
* SAM-BA Implementation on SAMD21
|
|
* --------------------
|
|
* Requirements to use SAM-BA :
|
|
*
|
|
* Supported communication interfaces :
|
|
* --------------------
|
|
*
|
|
* SERCOM5 : RX:PB23 TX:PB22
|
|
* Baudrate : 115200 8N1
|
|
*
|
|
* USB : D-:PA24 D+:PA25
|
|
*
|
|
* Pins Usage
|
|
* --------------------
|
|
* The following pins are used by the program :
|
|
* PA25 : input/output
|
|
* PA24 : input/output
|
|
* PB23 : input
|
|
* PB22 : output
|
|
* PA15 : input
|
|
*
|
|
* The application board shall avoid driving the PA25,PA24,PB23,PB22 and PA15
|
|
* signals
|
|
* while the boot program is running (after a POR for example)
|
|
*
|
|
* Clock system
|
|
* --------------------
|
|
* CPU clock source (GCLK_GEN_0) - 8MHz internal oscillator (OSC8M)
|
|
* SERCOM5 core GCLK source (GCLK_ID_SERCOM5_CORE) - GCLK_GEN_0 (i.e., OSC8M)
|
|
* GCLK Generator 1 source (GCLK_GEN_1) - 48MHz DFLL in Clock Recovery mode
|
|
* (DFLL48M)
|
|
* USB GCLK source (GCLK_ID_USB) - GCLK_GEN_1 (i.e., DFLL in CRM mode)
|
|
*
|
|
* Memory Mapping
|
|
* --------------------
|
|
* SAM-BA code will be located at 0x0 and executed before any applicative code.
|
|
*
|
|
* Applications compiled to be executed along with the bootloader will start at
|
|
* 0x2000 (samd21) or 0x4000 (samd51)
|
|
* The bootloader doesn't changes the VTOR register, application code is
|
|
* taking care of this.
|
|
*
|
|
*/
|
|
|
|
#include "uf2.h"
|
|
|
|
static void check_start_application(void);
|
|
|
|
static volatile bool main_b_cdc_enable = false;
|
|
extern int8_t led_tick_step;
|
|
|
|
#ifdef SAMD21
|
|
#define RESET_CONTROLLER PM
|
|
#endif
|
|
#ifdef SAMD51
|
|
#define RESET_CONTROLLER RSTC
|
|
#endif
|
|
|
|
#if defined(BOOT_KEY_ROW_PIN) && defined(BOOT_KEY_COL_PIN)
|
|
/**
|
|
* \brief Check whether the boot-hold key (bottom-right Cherry MX button) is
|
|
* held down. Drives its matrix row low and reads its matrix column back,
|
|
* the same polarity the app firmware's own matrix scan uses.
|
|
*/
|
|
static bool boot_key_pressed(void) {
|
|
PORT_PINCFG_Type col_cfg = {0};
|
|
col_cfg.bit.PMUXEN = false;
|
|
col_cfg.bit.INEN = true; // external 10k pull-up already on the board
|
|
|
|
PORT_PINCFG_Type row_cfg = {0};
|
|
row_cfg.bit.PMUXEN = false;
|
|
row_cfg.bit.DRVSTR = true;
|
|
|
|
PINCFG(BOOT_KEY_COL_PIN) = col_cfg.reg;
|
|
PINOP(BOOT_KEY_COL_PIN, DIRCLR); // column stays an input
|
|
|
|
PINOP(BOOT_KEY_ROW_PIN, OUTCLR); // pre-set drive level before enabling output
|
|
PINCFG(BOOT_KEY_ROW_PIN) = row_cfg.reg;
|
|
PINOP(BOOT_KEY_ROW_PIN, DIRSET); // row -> output, driving low
|
|
|
|
for (volatile int i = 0; i < 200; i++) {
|
|
} // let the row settle through the diode/pull-up RC
|
|
|
|
bool pressed = (PINIP(BOOT_KEY_COL_PIN) == 0);
|
|
|
|
PINOP(BOOT_KEY_ROW_PIN, DIRCLR); // release row back to high-Z
|
|
return pressed;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* \brief Check the application startup condition
|
|
*
|
|
*/
|
|
static void check_start_application(void) {
|
|
uint32_t app_start_address;
|
|
|
|
#if defined(BOOT_KEY_ROW_PIN) && defined(BOOT_KEY_COL_PIN)
|
|
if (boot_key_pressed()) {
|
|
/* Stay in bootloader */
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
// Check if there is an IO which will hold us inside the bootloader.
|
|
#if defined(HOLD_PIN) && defined(HOLD_STATE)
|
|
PORT_PINCFG_Type pincfg = {0};
|
|
pincfg.bit.PMUXEN = false;
|
|
pincfg.bit.INEN = true;
|
|
pincfg.bit.DRVSTR = true;
|
|
|
|
PINOP(HOLD_PIN, DIRCLR); // Pin is an input
|
|
|
|
#if defined(HOLD_PIN_PULLUP)
|
|
pincfg.bit.PULLEN = true;
|
|
PINOP(HOLD_PIN, OUTSET); // Pin is pulled up.
|
|
#elif defined(HOLD_PIN_PULLDOWN)
|
|
pincfg.bit.PULLEN = true;
|
|
PINOP(HOLD_PIN, OUTCLR); // Pin is pulled up.
|
|
#endif
|
|
PINCFG(HOLD_PIN) = pincfg.reg;
|
|
|
|
if (PINIP(HOLD_PIN) == HOLD_STATE) {
|
|
/* Stay in bootloader */
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/* Load the Reset Handler address of the application */
|
|
app_start_address = *(uint32_t *)(APP_START_ADDRESS + 4);
|
|
|
|
/**
|
|
* Test reset vector of application @APP_START_ADDRESS+4
|
|
* Sanity check on the Reset_Handler address
|
|
*/
|
|
if (app_start_address < APP_START_ADDRESS || app_start_address > FLASH_SIZE) {
|
|
/* Stay in bootloader */
|
|
return;
|
|
}
|
|
|
|
#if USE_SINGLE_RESET
|
|
if (SINGLE_RESET()) {
|
|
if (RESET_CONTROLLER->RCAUSE.bit.POR || *DBL_TAP_PTR != DBL_TAP_MAGIC_QUICK_BOOT) {
|
|
// the second tap on reset will go into app
|
|
*DBL_TAP_PTR = DBL_TAP_MAGIC_QUICK_BOOT;
|
|
// this will be cleared after succesful USB enumeration
|
|
// this is around 1.5s
|
|
resetHorizon = timerHigh + 50;
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (RESET_CONTROLLER->RCAUSE.bit.POR) {
|
|
*DBL_TAP_PTR = 0;
|
|
} else if (*DBL_TAP_PTR == DBL_TAP_MAGIC) {
|
|
*DBL_TAP_PTR = 0;
|
|
return; // stay in bootloader
|
|
} else {
|
|
if (*DBL_TAP_PTR != DBL_TAP_MAGIC_QUICK_BOOT) {
|
|
*DBL_TAP_PTR = DBL_TAP_MAGIC;
|
|
delay(500);
|
|
}
|
|
*DBL_TAP_PTR = 0;
|
|
}
|
|
|
|
LED_MSC_OFF();
|
|
#if defined(__SAMD21E18A__)
|
|
RGBLED_set_color(COLOR_LEAVE);
|
|
#endif
|
|
|
|
/* Rebase the Stack Pointer */
|
|
__set_MSP(*(uint32_t *)APP_START_ADDRESS);
|
|
|
|
/* Rebase the vector table base address */
|
|
SCB->VTOR = ((uint32_t)APP_START_ADDRESS & SCB_VTOR_TBLOFF_Msk);
|
|
|
|
/* Ensure the MSP/VTOR writes are visible before jumping; without these
|
|
* barriers the app's first fetch can race the pipeline (observed on
|
|
* real hardware: identical MSP/VTOR/PC values injected by a halted
|
|
* debugger boot fine, but the bootloader's own running jump hard-faults
|
|
* every time). */
|
|
__DSB();
|
|
__ISB();
|
|
|
|
/* Jump to application Reset Handler in the application */
|
|
asm("bx %0" ::"r"(app_start_address));
|
|
}
|
|
|
|
extern char _etext;
|
|
extern char _end;
|
|
|
|
/**
|
|
* \brief SAMD21 SAM-BA Main loop.
|
|
* \return Unused (ANSI-C compatibility).
|
|
*/
|
|
int main(void) {
|
|
// if VTOR is set, we're not running in bootloader mode; halt
|
|
if (SCB->VTOR)
|
|
while (1) {
|
|
}
|
|
|
|
#if (USB_VID == 0x239a) && (USB_PID == 0x0013) // Adafruit Metro M0
|
|
// Delay a bit so SWD programmer can have time to attach.
|
|
delay(15);
|
|
#endif
|
|
led_init();
|
|
|
|
logmsg("Start");
|
|
assert((uint32_t)&_etext < APP_START_ADDRESS);
|
|
// bossac writes at 0x20005000
|
|
assert(!USE_MONITOR || (uint32_t)&_end < 0x20005000);
|
|
|
|
assert(8 << NVMCTRL->PARAM.bit.PSZ == FLASH_PAGE_SIZE);
|
|
assert(FLASH_PAGE_SIZE * NVMCTRL->PARAM.bit.NVMP == FLASH_SIZE);
|
|
|
|
/* Jump in application if condition is satisfied */
|
|
check_start_application();
|
|
|
|
/* We have determined we should stay in the monitor. */
|
|
/* System initialization */
|
|
system_init();
|
|
|
|
__DMB();
|
|
__enable_irq();
|
|
|
|
#if USE_UART
|
|
/* UART is enabled in all cases */
|
|
usart_open();
|
|
#endif
|
|
|
|
logmsg("Before main loop");
|
|
|
|
usb_init();
|
|
|
|
// not enumerated yet
|
|
RGBLED_set_color(COLOR_START);
|
|
led_tick_step = 10;
|
|
|
|
/* Wait for a complete enum on usb or a '#' char on serial line */
|
|
while (1) {
|
|
if (USB_Ok()) {
|
|
if (!main_b_cdc_enable) {
|
|
#if USE_SINGLE_RESET
|
|
// this might have been set
|
|
resetHorizon = 0;
|
|
#endif
|
|
RGBLED_set_color(COLOR_USB);
|
|
led_tick_step = 1;
|
|
|
|
#if USE_SCREEN
|
|
screen_init();
|
|
draw_drag();
|
|
#endif
|
|
}
|
|
|
|
main_b_cdc_enable = true;
|
|
}
|
|
|
|
#if USE_MONITOR
|
|
// Check if a USB enumeration has succeeded
|
|
// And com port was opened
|
|
if (main_b_cdc_enable) {
|
|
logmsg("entering monitor loop");
|
|
// SAM-BA on USB loop
|
|
while (1) {
|
|
sam_ba_monitor_run();
|
|
}
|
|
}
|
|
#if USE_UART
|
|
/* Check if a '#' has been received */
|
|
if (!main_b_cdc_enable && usart_sharp_received()) {
|
|
RGBLED_set_color(COLOR_UART);
|
|
sam_ba_monitor_init(SAM_BA_INTERFACE_USART);
|
|
/* SAM-BA on UART loop */
|
|
while (1) {
|
|
sam_ba_monitor_run();
|
|
}
|
|
}
|
|
#endif
|
|
#else // no monitor
|
|
if (main_b_cdc_enable) {
|
|
process_msc();
|
|
}
|
|
#endif
|
|
|
|
if (!main_b_cdc_enable) {
|
|
// get more predictable timings before the USB is enumerated
|
|
for (int i = 1; i < 256; ++i) {
|
|
asm("nop");
|
|
}
|
|
}
|
|
}
|
|
}
|