atmel-samd: Add a safe mode which detects hard faults and reboots without running user code again.
This commit is contained in:
parent
790c38e18c
commit
974847ac8d
@ -122,6 +122,8 @@ CFLAGS_CORTEX_M0 = \
|
|||||||
-DEVENTS_INTERRUPT_HOOKS_MODE=false \
|
-DEVENTS_INTERRUPT_HOOKS_MODE=false \
|
||||||
-DTC_ASYNC=true \
|
-DTC_ASYNC=true \
|
||||||
-DUSB_DEVICE_LPM_SUPPORT \
|
-DUSB_DEVICE_LPM_SUPPORT \
|
||||||
|
-DCIRCUITPY_CANARY_WORD=0xADAF00 \
|
||||||
|
-DCIRCUITPY_SAFE_RESTART_WORD=0xDEADBEEF \
|
||||||
--param max-inline-insns-single=500
|
--param max-inline-insns-single=500
|
||||||
CFLAGS = $(INC) -Wall -Werror -std=gnu11 -nostdlib $(CFLAGS_CORTEX_M0) $(COPT)
|
CFLAGS = $(INC) -Wall -Werror -std=gnu11 -nostdlib $(CFLAGS_CORTEX_M0) $(COPT)
|
||||||
|
|
||||||
@ -276,7 +278,7 @@ OBJ += $(addprefix $(BUILD)/, $(SRC_SHARED_MODULE_EXPANDED:.c=.o))
|
|||||||
|
|
||||||
SRC_QSTR += $(SRC_C) $(SRC_BINDINGS_EXPANDED) $(SRC_SHARED_MODULE_EXPANDED) $(STM_SRC_C)
|
SRC_QSTR += $(SRC_C) $(SRC_BINDINGS_EXPANDED) $(SRC_SHARED_MODULE_EXPANDED) $(STM_SRC_C)
|
||||||
|
|
||||||
all: $(BUILD)/firmware.bin
|
all: $(BUILD)/firmware.bin $(BUILD)/firmware.uf2
|
||||||
|
|
||||||
$(BUILD)/firmware.elf: $(OBJ)
|
$(BUILD)/firmware.elf: $(OBJ)
|
||||||
$(STEPECHO) "LINK $@"
|
$(STEPECHO) "LINK $@"
|
||||||
@ -287,6 +289,10 @@ $(BUILD)/firmware.bin: $(BUILD)/firmware.elf
|
|||||||
$(ECHO) "Create $@"
|
$(ECHO) "Create $@"
|
||||||
$(Q)$(OBJCOPY) -O binary -j .vectors -j .text -j .data $^ $@
|
$(Q)$(OBJCOPY) -O binary -j .vectors -j .text -j .data $^ $@
|
||||||
|
|
||||||
|
$(BUILD)/firmware.uf2: $(BUILD)/firmware.bin
|
||||||
|
$(ECHO) "Create $@"
|
||||||
|
python2 ../tools/uf2/utils/uf2conv.py -c -o $@ $^
|
||||||
|
|
||||||
deploy: $(BUILD)/firmware.bin
|
deploy: $(BUILD)/firmware.bin
|
||||||
$(ECHO) "Writing $< to the board"
|
$(ECHO) "Writing $< to the board"
|
||||||
$(BOSSAC) -u $<
|
$(BOSSAC) -u $<
|
||||||
|
@ -275,12 +275,22 @@ __attribute__ ((used))void Reset_Handler(void)
|
|||||||
while (true);
|
while (true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern uint32_t _ezero;
|
||||||
|
|
||||||
void HardFault_Handler(void)
|
void HardFault_Handler(void)
|
||||||
{
|
{
|
||||||
#ifdef ENABLE_MICRO_TRACE_BUFFER
|
#ifdef ENABLE_MICRO_TRACE_BUFFER
|
||||||
// Turn off the micro trace buffer so we don't fill it up in the infinite
|
// Turn off the micro trace buffer so we don't fill it up in the infinite
|
||||||
// loop below.
|
// loop below.
|
||||||
REG_MTB_MASTER = 0x00000000 + 6;
|
REG_MTB_MASTER = 0x00000000 + 6;
|
||||||
|
#endif
|
||||||
|
#ifdef CIRCUITPY_CANARY_WORD
|
||||||
|
// If the canary is intact, then kill it and reset so we have a chance to
|
||||||
|
// read our files.
|
||||||
|
if (_ezero == CIRCUITPY_CANARY_WORD) {
|
||||||
|
_ezero = CIRCUITPY_SAFE_RESTART_WORD;
|
||||||
|
NVIC_SystemReset();
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
while(true) {}
|
while(true) {}
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,12 @@
|
|||||||
|
|
||||||
fs_user_mount_t fs_user_mount_flash;
|
fs_user_mount_t fs_user_mount_flash;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
NO_SAFE_MODE = 0,
|
||||||
|
BROWNOUT,
|
||||||
|
HARD_CRASH,
|
||||||
|
} safe_mode_t;
|
||||||
|
|
||||||
void do_str(const char *src, mp_parse_input_kind_t input_kind) {
|
void do_str(const char *src, mp_parse_input_kind_t input_kind) {
|
||||||
mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, src, strlen(src), 0);
|
mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, src, strlen(src), 0);
|
||||||
if (lex == NULL) {
|
if (lex == NULL) {
|
||||||
@ -234,32 +240,41 @@ bool maybe_run(const char* filename, pyexec_result_t* exec_result) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool start_mp(void) {
|
bool start_mp(safe_mode_t safe_mode) {
|
||||||
bool cdc_enabled_at_start = mp_cdc_enabled;
|
bool cdc_enabled_at_start = mp_cdc_enabled;
|
||||||
#ifdef CIRCUITPY_AUTORELOAD_DELAY_MS
|
#ifdef CIRCUITPY_AUTORELOAD_DELAY_MS
|
||||||
if (cdc_enabled_at_start) {
|
if (cdc_enabled_at_start) {
|
||||||
mp_hal_stdout_tx_str("\r\n");
|
mp_hal_stdout_tx_str("\r\n");
|
||||||
mp_hal_stdout_tx_str("Auto-reload is on. Simply save files over USB to run them or enter REPL to disable.\r\n");
|
if (autoreload_is_enabled()) {
|
||||||
|
mp_hal_stdout_tx_str("Auto-reload is on. Simply save files over USB to run them or enter REPL to disable.\r\n");
|
||||||
|
} else if (safe_mode != NO_SAFE_MODE) {
|
||||||
|
mp_hal_stdout_tx_str("Running in safe mode! Auto-reload is off.\r\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
bool found_main = false;
|
|
||||||
pyexec_result_t result;
|
pyexec_result_t result;
|
||||||
new_status_color(MAIN_RUNNING);
|
bool found_main = false;
|
||||||
found_main = maybe_run("code.txt", &result) ||
|
if (safe_mode != NO_SAFE_MODE) {
|
||||||
maybe_run("code.py", &result) ||
|
mp_hal_stdout_tx_str("Running in safe mode! Not running saved code.\r\n");
|
||||||
maybe_run("main.py", &result) ||
|
} else {
|
||||||
maybe_run("main.txt", &result);
|
new_status_color(MAIN_RUNNING);
|
||||||
reset_status_led();
|
found_main = maybe_run("code.txt", &result) ||
|
||||||
|
maybe_run("code.py", &result) ||
|
||||||
|
maybe_run("main.py", &result) ||
|
||||||
|
maybe_run("main.txt", &result);
|
||||||
|
reset_status_led();
|
||||||
|
|
||||||
if (result.return_code & PYEXEC_FORCED_EXIT) {
|
if (result.return_code & PYEXEC_FORCED_EXIT) {
|
||||||
return reload_next_character;
|
return reload_next_character;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not is USB mode then do not skip the repl.
|
||||||
|
#ifndef USB_REPL
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not is USB mode then do not skip the repl.
|
|
||||||
#ifndef USB_REPL
|
|
||||||
return false;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Wait for connection or character.
|
// Wait for connection or character.
|
||||||
bool cdc_enabled_before = false;
|
bool cdc_enabled_before = false;
|
||||||
#if defined(MICROPY_HW_NEOPIXEL) || (defined(MICROPY_HW_APA102_MOSI) && defined(MICROPY_HW_APA102_SCK))
|
#if defined(MICROPY_HW_NEOPIXEL) || (defined(MICROPY_HW_APA102_MOSI) && defined(MICROPY_HW_APA102_SCK))
|
||||||
@ -310,7 +325,18 @@ bool start_mp(void) {
|
|||||||
} else {
|
} else {
|
||||||
mp_hal_stdout_tx_str("Auto-reload is off.\r\n");
|
mp_hal_stdout_tx_str("Auto-reload is off.\r\n");
|
||||||
}
|
}
|
||||||
mp_hal_stdout_tx_str("Press any key to enter the REPL. Use CTRL-D to reload.\r\n");
|
if (safe_mode != NO_SAFE_MODE) {
|
||||||
|
mp_hal_stdout_tx_str("\r\nYou are running in safe mode which means something really bad happened.\r\n");
|
||||||
|
if (safe_mode == HARD_CRASH) {
|
||||||
|
mp_hal_stdout_tx_str("Looks like our core CircuitPython code crashed hard. Whoops!\r\n");
|
||||||
|
mp_hal_stdout_tx_str("Please file an issue here with the contents of your CIRCUITPY drive:\r\n");
|
||||||
|
mp_hal_stdout_tx_str("https://github.com/adafruit/circuitpython/issues\r\n");
|
||||||
|
} else if (safe_mode == BROWNOUT) {
|
||||||
|
mp_hal_stdout_tx_str("The microcontroller's power dipped. Please make sure your power supply provides \r\n");
|
||||||
|
mp_hal_stdout_tx_str("enough power for the whole circuit and press reset (after ejecting CIRCUITPY).\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mp_hal_stdout_tx_str("\r\nPress any key to enter the REPL. Use CTRL-D to reload.\r\n");
|
||||||
}
|
}
|
||||||
if (cdc_enabled_before && !mp_cdc_enabled) {
|
if (cdc_enabled_before && !mp_cdc_enabled) {
|
||||||
cdc_enabled_at_start = false;
|
cdc_enabled_at_start = false;
|
||||||
@ -330,7 +356,11 @@ bool start_mp(void) {
|
|||||||
if (brightness > 255) {
|
if (brightness > 255) {
|
||||||
brightness = 511 - brightness;
|
brightness = 511 - brightness;
|
||||||
}
|
}
|
||||||
new_status_color(color_brightness(ALL_DONE, brightness));
|
if (safe_mode == NO_SAFE_MODE) {
|
||||||
|
new_status_color(color_brightness(ALL_DONE, brightness));
|
||||||
|
} else {
|
||||||
|
new_status_color(color_brightness(SAFE_MODE, brightness));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (tick_diff > total_exception_cycle) {
|
if (tick_diff > total_exception_cycle) {
|
||||||
pattern_start = ticks_ms;
|
pattern_start = ticks_ms;
|
||||||
@ -430,13 +460,37 @@ void load_serial_number(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void samd21_init(void) {
|
// Provided by the linker;
|
||||||
|
extern uint32_t _ezero;
|
||||||
|
|
||||||
|
safe_mode_t samd21_init(void) {
|
||||||
#ifdef ENABLE_MICRO_TRACE_BUFFER
|
#ifdef ENABLE_MICRO_TRACE_BUFFER
|
||||||
REG_MTB_POSITION = ((uint32_t) (mtb - REG_MTB_BASE)) & 0xFFFFFFF8;
|
REG_MTB_POSITION = ((uint32_t) (mtb - REG_MTB_BASE)) & 0xFFFFFFF8;
|
||||||
REG_MTB_FLOW = (((uint32_t) mtb - REG_MTB_BASE) + TRACE_BUFFER_SIZE_BYTES) & 0xFFFFFFF8;
|
REG_MTB_FLOW = (((uint32_t) mtb - REG_MTB_BASE) + TRACE_BUFFER_SIZE_BYTES) & 0xFFFFFFF8;
|
||||||
REG_MTB_MASTER = 0x80000000 + (TRACE_BUFFER_MAGNITUDE_PACKETS - 1);
|
REG_MTB_MASTER = 0x80000000 + (TRACE_BUFFER_MAGNITUDE_PACKETS - 1);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// On power on start or external reset, set _ezero to the canary word. If it
|
||||||
|
// gets killed, we boot in safe mod. _ezero is the boundary between statically
|
||||||
|
// allocated memory including the fixed MicroPython heap and the stack. If either
|
||||||
|
// misbehaves, the canary will not be in tact after soft reset.
|
||||||
|
#ifdef CIRCUITPY_CANARY_WORD
|
||||||
|
if (PM->RCAUSE.bit.POR == 1 || PM->RCAUSE.bit.EXT == 1) {
|
||||||
|
_ezero = CIRCUITPY_CANARY_WORD;
|
||||||
|
} else if (PM->RCAUSE.bit.SYST == 1) {
|
||||||
|
// If we're starting from a system reset we're likely coming from the
|
||||||
|
// bootloader or hard fault handler. If we're coming from the handler
|
||||||
|
// the canary will be CIRCUITPY_SAFE_RESTART_WORD and we don't want to
|
||||||
|
// revive the canary so that a second hard fault won't restart. Resets
|
||||||
|
// from anywhere else are ok.
|
||||||
|
if (_ezero == CIRCUITPY_SAFE_RESTART_WORD) {
|
||||||
|
_ezero = ~CIRCUITPY_CANARY_WORD;
|
||||||
|
} else {
|
||||||
|
_ezero = CIRCUITPY_CANARY_WORD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
load_serial_number();
|
load_serial_number();
|
||||||
|
|
||||||
irq_initialize_vectors();
|
irq_initialize_vectors();
|
||||||
@ -445,7 +499,6 @@ void samd21_init(void) {
|
|||||||
// Initialize the sleep manager
|
// Initialize the sleep manager
|
||||||
sleepmgr_init();
|
sleepmgr_init();
|
||||||
|
|
||||||
|
|
||||||
uint16_t dfll_fine_calibration = 0x1ff;
|
uint16_t dfll_fine_calibration = 0x1ff;
|
||||||
#ifdef CALIBRATE_CRYSTALLESS
|
#ifdef CALIBRATE_CRYSTALLESS
|
||||||
// This is stored in an NVM page after the text and data storage but before
|
// This is stored in an NVM page after the text and data storage but before
|
||||||
@ -484,6 +537,19 @@ void samd21_init(void) {
|
|||||||
nvm_set_config(&config_nvm);
|
nvm_set_config(&config_nvm);
|
||||||
|
|
||||||
init_shared_dma();
|
init_shared_dma();
|
||||||
|
|
||||||
|
#ifdef CIRCUITPY_CANARY_WORD
|
||||||
|
// Run in safe mode if the canary is corrupt.
|
||||||
|
if (_ezero != CIRCUITPY_CANARY_WORD) {
|
||||||
|
return HARD_CRASH;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (PM->RCAUSE.bit.BOD33 == 1 || PM->RCAUSE.bit.BOD12 == 1) {
|
||||||
|
return BROWNOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NO_SAFE_MODE;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern uint32_t _estack;
|
extern uint32_t _estack;
|
||||||
@ -491,7 +557,7 @@ extern uint32_t _ebss;
|
|||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
// initialise the cpu and peripherals
|
// initialise the cpu and peripherals
|
||||||
samd21_init();
|
safe_mode_t safe_mode = samd21_init();
|
||||||
|
|
||||||
// Stack limit should be less than real stack size, so we have a chance
|
// Stack limit should be less than real stack size, so we have a chance
|
||||||
// to recover from limit hit. (Limit is measured in bytes.)
|
// to recover from limit hit. (Limit is measured in bytes.)
|
||||||
@ -507,28 +573,33 @@ int main(void) {
|
|||||||
reset_samd21();
|
reset_samd21();
|
||||||
reset_mp();
|
reset_mp();
|
||||||
|
|
||||||
// Run boot before initing USB and capture output in a file.
|
// If not in safe mode, run boot before initing USB and capture output in a
|
||||||
new_status_color(BOOT_RUNNING);
|
// file.
|
||||||
#ifdef CIRCUITPY_BOOT_OUTPUT_FILE
|
if (safe_mode == NO_SAFE_MODE) {
|
||||||
FIL file_pointer;
|
new_status_color(BOOT_RUNNING);
|
||||||
boot_output_file = &file_pointer;
|
#ifdef CIRCUITPY_BOOT_OUTPUT_FILE
|
||||||
FRESULT result = f_open(boot_output_file, CIRCUITPY_BOOT_OUTPUT_FILE, FA_WRITE | FA_CREATE_ALWAYS);
|
FIL file_pointer;
|
||||||
if (result != FR_OK) {
|
boot_output_file = &file_pointer;
|
||||||
while (true) {}
|
f_open(boot_output_file, CIRCUITPY_BOOT_OUTPUT_FILE, FA_WRITE | FA_CREATE_ALWAYS);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// TODO(tannewt): Re-add support for flashing boot error output.
|
||||||
|
bool found_boot = maybe_run("settings.txt", NULL) ||
|
||||||
|
maybe_run("settings.py", NULL) ||
|
||||||
|
maybe_run("boot.py", NULL) ||
|
||||||
|
maybe_run("boot.txt", NULL);
|
||||||
|
(void) found_boot;
|
||||||
|
|
||||||
|
#ifdef CIRCUITPY_BOOT_OUTPUT_FILE
|
||||||
|
f_close(boot_output_file);
|
||||||
|
boot_output_file = NULL;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Reset to remove any state that boot.py setup. It should only be used to
|
||||||
|
// change internal state thats not in the heap.
|
||||||
|
reset_samd21();
|
||||||
|
reset_mp();
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
// TODO(tannewt): Re-add support for flashing boot error output.
|
|
||||||
bool found_boot = maybe_run("settings.txt", NULL) ||
|
|
||||||
maybe_run("settings.py", NULL) ||
|
|
||||||
maybe_run("boot.py", NULL) ||
|
|
||||||
maybe_run("boot.txt", NULL);
|
|
||||||
(void) found_boot;
|
|
||||||
|
|
||||||
#ifdef CIRCUITPY_BOOT_OUTPUT_FILE
|
|
||||||
f_close(boot_output_file);
|
|
||||||
boot_output_file = NULL;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Turn off local writing in favor of USB writing prior to initializing USB.
|
// Turn off local writing in favor of USB writing prior to initializing USB.
|
||||||
flash_set_usb_writeable(true);
|
flash_set_usb_writeable(true);
|
||||||
@ -540,11 +611,6 @@ int main(void) {
|
|||||||
udc_start();
|
udc_start();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Reset to remove any state that boot.py setup. It should only be used to
|
|
||||||
// change internal state thats not in the heap.
|
|
||||||
reset_samd21();
|
|
||||||
reset_mp();
|
|
||||||
|
|
||||||
// Boot script is finished, so now go into REPL/main mode.
|
// Boot script is finished, so now go into REPL/main mode.
|
||||||
int exit_code = PYEXEC_FORCED_EXIT;
|
int exit_code = PYEXEC_FORCED_EXIT;
|
||||||
bool skip_repl = true;
|
bool skip_repl = true;
|
||||||
@ -567,7 +633,7 @@ int main(void) {
|
|||||||
mp_hal_stdout_tx_str("soft reboot\r\n");
|
mp_hal_stdout_tx_str("soft reboot\r\n");
|
||||||
}
|
}
|
||||||
first_run = false;
|
first_run = false;
|
||||||
skip_repl = start_mp();
|
skip_repl = start_mp(safe_mode);
|
||||||
reset_samd21();
|
reset_samd21();
|
||||||
reset_mp();
|
reset_mp();
|
||||||
} else if (exit_code != 0) {
|
} else if (exit_code != 0) {
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#define BOOT_RUNNING BLUE
|
#define BOOT_RUNNING BLUE
|
||||||
#define MAIN_RUNNING GREEN
|
#define MAIN_RUNNING GREEN
|
||||||
|
#define SAFE_MODE YELLOW
|
||||||
#define ALL_DONE GREEN
|
#define ALL_DONE GREEN
|
||||||
#define REPL_RUNNING WHITE
|
#define REPL_RUNNING WHITE
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user