8000 atmel-samd: Add a safe mode which detects hard faults and reboots wit… · sparkfun/circuitpython@974847a · GitHub
[go: up one dir, main page]

Skip to content

Commit 974847a

Browse files
committed
atmel-samd: Add a safe mode which detects hard faults and reboots without running user code again.
1 parent 790c38e commit 974847a

File tree

4 files changed

+131
-48
lines changed

4 files changed

+131
-48
lines changed

atmel-samd/Makefile

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ CFLAGS_CORTEX_M0 = \
122122
-DEVENTS_INTERRUPT_HOOKS_MODE=false \
123123
-DTC_ASYNC=true \
124124
-DUSB_DEVICE_LPM_SUPPORT \
125+
-DCIRCUITPY_CANARY_WORD=0xADAF00 \
126+
-DCIRCUITPY_SAFE_RESTART_WORD=0xDEADBEEF \
125127
--param max-inline-insns-single=500
126128
CFLAGS = $(INC) -Wall -Werror -std=gnu11 -nostdlib $(CFLAGS_CORTEX_M0) $(COPT)
127129

@@ -276,7 +278,7 @@ OBJ += $(addprefix $(BUILD)/, $(SRC_SHARED_MODULE_EXPANDED:.c=.o))
276278

277279
SRC_QSTR += $(SRC_C) $(SRC_BINDINGS_EXPANDED) $(SRC_SHARED_MODULE_EXPANDED) $(STM_SRC_C)
278280

279-
all: $(BUILD)/firmware.bin
281+
all: $(BUILD)/firmware.bin $(BUILD)/firmware.uf2
280282

281283
$(BUILD)/firmware.elf: $(OBJ)
282284
$(STEPECHO) "LINK $@"
@@ -287,6 +289,10 @@ $(BUILD)/firmware.bin: $(BUILD)/firmware.elf
287289
$(ECHO) "Create $@"
288290
$(Q)$(OBJCOPY) -O binary -j .vectors -j .text -j .data $^ $@
289291

292+
$(BUILD)/firmware.uf2: $(BUILD)/firmware.bin
293+
$(ECHO) "Create $@"
294+
python2 ../tools/uf2/utils/uf2conv.py -c -o $@ $^
295+
290296
deploy: $(BUILD)/firmware.bin
291297
$(ECHO) "Writing $< to the board"
292298
$(BOSSAC) -u $<

atmel-samd/asf/sam0/utils/cmsis/samd21/source/gcc/startup_samd21.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,12 +275,22 @@ __attribute__ ((used))void Reset_Handler(void)
275275
while (true);
276276
}
277277

278+
extern uint32_t _ezero;
279+
278280
void HardFault_Handler(void)
279281
{
280282
#ifdef ENABLE_MICRO_TRACE_BUFFER
281283
// Turn off the micro trace buffer so we don't fill it up in the infinite
282284
// loop below.
283285
REG_MTB_MASTER = 0x00000000 + 6;
286+
#endif
287+
#ifdef CIRCUITPY_CANARY_WORD
288+
// If the canary is intact, then kill it and reset so we have a chance to
289+
// read our files.
290+
if (_ezero == CIRCUITPY_CANARY_WORD) {
291+
_ezero = CIRCUITPY_SAFE_RESTART_WORD;
292+
NVIC_SystemReset();
293+
}
284294
#endif
285295
while(true) {}
286296
}

atmel-samd/main.c

Lines changed: 113 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@
4747

4848
fs_user_mount_t fs_user_mount_flash;
4949

50+
typedef enum {
51+
NO_SAFE_MODE = 0,
52+
BROWNOUT,
53+
HARD_CRASH,
54+
} safe_mode_t;
55+
5056
void do_str(const char *src, mp_parse_input_kind_t input_kind) {
5157
mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, src, strlen(src), 0);
5258
if (lex == NULL) {
@@ -234,32 +240,41 @@ bool maybe_run(const char* filename, pyexec_result_t* exec_result) {
234240
return true;
235241
}
236242

237-
bool start_mp(void) {
243+
bool start_mp(safe_mode_t safe_mode) {
238244
bool cdc_enabled_at_start = mp_cdc_enabled;
239245
#ifdef CIRCUITPY_AUTORELOAD_DELAY_MS
240246
if (cdc_enabled_at_start) {
241247
mp_hal_stdout_tx_str("\r\n");
242-
mp_hal_stdout_tx_str("Auto-reload is on. Simply save files over USB to run them or enter REPL to disable.\r\n");
248+
if (autoreload_is_enabled()) {
249+
mp_hal_stdout_tx_str("Auto-reload is on. Simply save files over USB to run them or enter REPL to disable.\r\n");
250+
} else if (safe_mode != NO_SAFE_MODE) {
251+
mp_hal_stdout_tx_str("Running in safe mode! Auto-reload is off.\r\n");
252+
}
243253
}
244254
#endif
245-
bool found_main = false;
255+
246256
pyexec_result_t result;
247-
new_status_color(MAIN_RUNNING);
248-
found_main = maybe_run("code.txt", &result) ||
249-
maybe_run("code.py", &result) ||
250-
maybe_run("main.py", &result) ||
251-
maybe_run("main.txt", &result);
252-
reset_status_led();
257+
bool found_main = false;
258+
if (safe_mode != NO_SAFE_MODE) {
259+
mp_hal_stdout_tx_str("Running in safe mode! Not running saved code.\r\n");
260+
} else {
261+
new_status_color(MAIN_RUNNING);
262+
found_main = maybe_run("code.txt", &result) ||
263+
maybe_run("code.py", &result) ||
264+
maybe_run("main.py", &result) ||
265+
maybe_run("main.txt", &result);
266+
reset_status_led();
267+
268+
if (result.return_code & PYEXEC_FORCED_EXIT) {
269+
return reload_next_character;
270+
}
253271

254-
if (result.return_code & PYEXEC_FORCED_EXIT) {
255-
return reload_next_character;
272+
// If not is USB mode then do not skip the repl.
273+
#ifndef USB_REPL
274+
return false;
275+
#endif
256276
}
257277

258-
// If not is USB mode then do not skip the repl.
259-
#ifndef USB_REPL
260-
return false;
261-
#endif
262-
263278
// Wait for connection or character.
264279
bool cdc_enabled_before = false;
265280
#if defined(MICROPY_HW_NEOPIXEL) || (defined(MICROPY_HW_APA102_MOSI) && defined(MICROPY_HW_APA102_SCK))
@@ -310,7 +325,18 @@ bool start_mp(void) {
310325
} else {
311326
mp_hal_stdout_tx_str("Auto-reload is off.\r\n");
312327
}
313-
mp_hal_stdout_tx_str("Press any key to enter the REPL. Use CTRL-D to reload.\r\n");
328+
if (safe_mode != NO_SAFE_MODE) {
329+
mp_hal_stdout_tx_str("\r\nYou are running in safe mode which means something really bad happened.\r\n");
330+
if (safe_mode == HARD_CRASH) {
331+
mp_hal_stdout_tx_str("Looks like our core CircuitPython code crashed hard. Whoops!\r\n");
332+
mp_hal_stdout_tx_str("Please file an issue here with the contents of your CIRCUITPY drive:\r\n");
333+
mp_hal_stdout_tx_str("https://github.com/adafruit/circuitpython/issues\r\n");
334+
} else if (safe_mode == BROWNOUT) {
335+
mp_hal_stdout_tx_str("The microcontroller's power dipped. Please make sure your power supply provides \r\n");
336+
mp_hal_stdout_tx_str("enough power for the whole circuit and press reset (after ejecting CIRCUITPY).\r\n");
337+
}
338+
}
339+
mp_hal_stdout_tx_str("\r\nPress any key to enter the REPL. Use CTRL-D to reload.\r\n");
314340
}
315341
if (cdc_enabled_before && !mp_cdc_enabled) {
316342
cdc_enabled_at_start = false;
@@ -330,7 +356,11 @@ bool start_mp(void) {
330356
if (brightness > 255) {
331357
brightness = 511 - brightness;
332358
}
333-
new_status_color(color_brightness(ALL_DONE, brightness));
359+
if (safe_mode == NO_SAFE_MODE) {
360+
new_status_color(color_brightness(ALL_DONE, brightness));
361+
} else {
362+
new_status_color(color_brightness(SAFE_MODE, brightness));
363+
}
334364
} else {
335365
if (tick_diff > total_exception_cycle) {
336366
pattern_start = ticks_ms;
@@ -430,13 +460,37 @@ void load_serial_number(void) {
430460
}
431461
}
432462

433-
void samd21_init(void) {
463+
// Provided by the linker;
464+
extern uint32_t _ezero;
465+
466+
safe_mode_t samd21_init(void) {
434467
#ifdef ENABLE_MICRO_TRACE_BUFFER
435468
REG_MTB_POSITION = ((uint32_t) (mtb - REG_MTB_BASE)) & 0xFFFFFFF8;
436469
REG_MTB_FLOW = (((uint32_t) mtb - REG_MTB_BASE) + TRACE_BUFFER_SIZE_BYTES) & 0xFFFFFFF8;
437470
REG_MTB_MASTER = 0x80000000 + (TRACE_BUFFER_MAGNITUDE_PACKETS - 1);
438471
#endif
439472

473+
// On power on start or external reset, set _ezero to the canary word. If it
474+
// gets killed, we boot in safe mod. _ezero is the boundary between statically
475+
// allocated memory including the fixed MicroPython heap and the stack. If either
476+
// misbehaves, the canary will not be in tact after soft reset.
477+
#ifdef CIRCUITPY_CANARY_WORD
478+
if (PM->RCAUSE.bit.POR == 1 || PM->RCAUSE.bit.EXT == 1) {
479+
_ezero = CIRCUITPY_CANARY_WORD;
480+
} else if (PM->RCAUSE.bit.SYST == 1) {
481+
// If we're starting from a system reset we're likely coming from the
482+
// bootloader or hard fault handler. If we're coming from the handler
483+
// the canary will be CIRCUITPY_SAFE_RESTART_WORD and we don't want to
484+
// revive the canary so that a second hard fault won't restart. Resets
485+
// from anywhere else are ok.
486+
if (_ezero == CIRCUITPY_SAFE_RESTART_WORD) {
487+
_ezero = ~CIRCUITPY_CANARY_WORD;
488+
} else {
489+
_ezero = CIRCUITPY_CANARY_WORD;
490+
}
491+
}
492+
#endif
493+
440494
load_serial_number();
441495

442496
irq_initialize_vectors();
@@ -445,7 +499,6 @@ void samd21_init(void) {
445499
// Initialize the sleep manager
446500
sleepmgr_init();
447501

448-
449502
uint16_t dfll_fine_calibration = 0x1ff;
450503
#ifdef CALIBRATE_CRYSTALLESS
451504
// This is stored in an NVM page after the text and data storage but before
@@ -484,14 +537,27 @@ void samd21_init(void) {
484537
nvm_set_config(&config_nvm);
485538

486539
init_shared_dma();
540+
541+
#ifdef CIRCUITPY_CANARY_WORD
542+
// Run in safe mode if the canary is corrupt.
543+
if (_ezero != CIRCUITPY_CANARY_WORD) {
544+
return HARD_CRASH;
545+
}
546+
#endif
547+
548+
if (PM->RCAUSE.bit.BOD33 == 1 || PM->RCAUSE.bit.BOD12 == 1) {
549+
return BROWNOUT;
550+
}
551+
552+
return NO_SAFE_MODE;
487553
}
488554

489555
extern uint32_t _estack;
490556
extern uint32_t _ebss;
491557

492558
int main(void) {
493559
// initialise the cpu and peripherals
494-
samd21_init();
560+
safe_mode_t safe_mode = samd21_init();
495561

496562
// Stack limit should be less than real stack size, so we have a chance
497563
// to recover from limit hit. (Limit is measured in bytes.)
@@ -507,28 +573,33 @@ int main(void) {
507573
reset_samd21();
508574
reset_mp();
509575

510-
// Run boot before initing USB and capture output in a file.
511-
new_status_color(BOOT_RUNNING);
512-
#ifdef CIRCUITPY_BOOT_OUTPUT_FILE
513-
FIL file_pointer;
514-
boot_output_file = &file_pointer;
515-
FRESULT result = f_open(boot_output_file, CIRCUITPY_BOOT_OUTPUT_FILE, FA_WRITE | FA_CREATE_ALWAYS);
516-
if (result != FR_OK) {
517-
while (true) {}
518-
}
519-
#endif
576+
// If not in safe mode, run boot before initing USB and capture output in a
577+
// file.
578+
if (safe_mode == NO_SAFE_MODE) {
579+
new_status_color(BOOT_RUNNING);
580+
#ifdef CIRCUITPY_BOOT_OUTPUT_FILE
581+
FIL file_pointer;
582+
boot_output_file = &file_pointer;
583+
f_open(boot_output_file, CIRCUITPY_BOOT_OUTPUT_FILE, FA_WRITE | FA_CREATE_ALWAYS);
584+
#endif
520585

521-
// TODO(tannewt): Re-add support for flashing boot error output.
522-
bool found_boot = maybe_run("settings.txt", NULL) ||
523-
maybe_run("settings.py", NULL) ||
524-
maybe_run("boot.py", NULL) ||
525-
maybe_run("boot.txt", NULL);
526-
(void) found_boot;
586+
// TODO(tannewt): Re-add support for flashing boot error output.
587+
bool found_boot = maybe_run("settings.txt", NULL) ||
588+
maybe_run("settings.py", NULL) ||
589+
maybe_run("boot.py", NULL) ||
590+
maybe_run("boot.txt", NULL);
591+
(void) found_boot;
527592

528-
#ifdef CIRCUITPY_BOOT_OUTPUT_FILE
529-
f_close(boot_output_file);
530-
boot_output_file = NULL;
531-
#endif
593+
#ifdef CIRCUITPY_BOOT_OUTPUT_FILE
594+
f_close(boot_output_file);
595+
boot_output_file = NULL;
596+
#endif
597+
598+
// Reset to remove any state that boot.py setup. It should only be used to
599+
// change internal state thats not in the heap.
600+
reset_samd21();
601+
reset_mp();
602+
}
532603

533604
// Turn off local writing in favor of USB writing prior to initializing USB.
534605
flash_set_usb_writeable(true);
@@ -540,11 +611,6 @@ int main(void) {
540611
udc_start();
541612
#endif
542613

543-
// Reset to remove any state that boot.py setup. It should only be used to
544-
// change internal state thats not in the heap.
545-
reset_samd21();
546-
reset_mp();
547-
548614
// Boot script is finished, so now go into REPL/main mode.
549615
int exit_code = PYEXEC_FORCED_EXIT;
550616
bool skip_repl = true;
@@ -567,7 +633,7 @@ int main(void) {
567633
mp_hal_stdout_tx_str("soft reboot\r\n");
568634
}
569635
first_run = false;
570-
skip_repl = start_mp();
636+
skip_repl = start_mp(safe_mode);
571637
reset_samd21();
572638
reset_mp();
573639
} else if (exit_code != 0) {

atmel-samd/rgb_led_colors.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#define BOOT_RUNNING BLUE
1212
#define MAIN_RUNNING GREEN
13+
#define SAFE_MODE YELLOW
1314
#define ALL_DONE GREEN
1415
#define REPL_RUNNING WHITE
1516

0 commit comments

Comments
 (0)
0