47
47
48
48
fs_user_mount_t fs_user_mount_flash ;
49
49
50
+ typedef enum {
51
+ NO_SAFE_MODE = 0 ,
52
+ BROWNOUT ,
53
+ HARD_CRASH ,
54
+ } safe_mode_t ;
55
+
50
56
void do_str (const char * src , mp_parse_input_kind_t input_kind ) {
51
57
mp_lexer_t * lex = mp_lexer_new_from_str_len (MP_QSTR__lt_stdin_gt_ , src , strlen (src ), 0 );
52
58
if (lex == NULL ) {
@@ -234,32 +240,41 @@ bool maybe_run(const char* filename, pyexec_result_t* exec_result) {
234
240
return true;
235
241
}
236
242
237
- bool start_mp (void ) {
243
+ bool start_mp (safe_mode_t safe_mode ) {
238
244
bool cdc_enabled_at_start = mp_cdc_enabled ;
239
245
#ifdef CIRCUITPY_AUTORELOAD_DELAY_MS
240
246
if (cdc_enabled_at_start ) {
241
247
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
+ }
243
253
}
244
254
#endif
245
- bool found_main = false;
255
+
246
256
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
+ }
253
271
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
256
276
}
257
277
258
- // If not is USB mode then do not skip the repl.
259
- #ifndef USB_REPL
260
- return false;
261
- #endif
262
-
263
278
// Wait for connection or character.
264
279
bool cdc_enabled_before = false;
265
280
#if defined(MICROPY_HW_NEOPIXEL ) || (defined(MICROPY_HW_APA102_MOSI ) && defined(MICROPY_HW_APA102_SCK ))
@@ -310,7 +325,18 @@ bool start_mp(void) {
310
325
} else {
311
326
mp_hal_stdout_tx_str ("Auto-reload is off.\r\n" );
312
327
}
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" );
314
340
}
315
341
if (cdc_enabled_before && !mp_cdc_enabled ) {
316
342
cdc_enabled_at_start = false;
@@ -330,7 +356,11 @@ bool start_mp(void) {
330
356
if (brightness > 255 ) {
331
357
brightness = 511 - brightness ;
332
358
}
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
+ }
334
364
} else {
335
365
if (tick_diff > total_exception_cycle ) {
336
366
pattern_start = ticks_ms ;
@@ -430,13 +460,37 @@ void load_serial_number(void) {
430
460
}
431
461
}
432
462
433
- void samd21_init (void ) {
463
+ // Provided by the linker;
464
+ extern uint32_t _ezero ;
465
+
466
+ safe_mode_t samd21_init (void ) {
434
467
#ifdef ENABLE_MICRO_TRACE_BUFFER
435
468
REG_MTB_POSITION = ((uint32_t ) (mtb - REG_MTB_BASE )) & 0xFFFFFFF8 ;
436
469
REG_MTB_FLOW = (((uint32_t ) mtb - REG_MTB_BASE ) + TRACE_BUFFER_SIZE_BYTES ) & 0xFFFFFFF8 ;
437
470
REG_MTB_MASTER = 0x80000000 + (TRACE_BUFFER_MAGNITUDE_PACKETS - 1 );
438
471
#endif
439
472
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
+
440
494
load_serial_number ();
441
495
442
496
irq_initialize_vectors ();
@@ -445,7 +499,6 @@ void samd21_init(void) {
445
499
// Initialize the sleep manager
446
500
sleepmgr_init ();
447
501
448
-
449
502
uint16_t dfll_fine_calibration = 0x1ff ;
450
503
#ifdef CALIBRATE_CRYSTALLESS
451
504
// This is stored in an NVM page after the text and data storage but before
@@ -484,14 +537,27 @@ void samd21_init(void) {
484
537
nvm_set_config (& config_nvm );
485
538
486
539
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 ;
487
553
}
488
554
489
555
extern uint32_t _estack ;
490
556
extern uint32_t _ebss ;
491
557
492
558
int main (void ) {
493
559
// initialise the cpu and peripherals
494
- samd21_init ();
560
+ safe_mode_t safe_mode = samd21_init ();
495
561
496
562
// Stack limit should be less than real stack size, so we have a chance
497
563
// to recover from limit hit. (Limit is measured in bytes.)
@@ -507,28 +573,33 @@ int main(void) {
507
573
reset_samd21 ();
508
574
reset_mp ();
509
575
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
520
585
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 ;
527
592
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
+ }
532
603
533
604
// Turn off local writing in favor of USB writing prior to initializing USB.
534
605
flash_set_usb_writeable (true);
@@ -540,11 +611,6 @@ int main(void) {
540
611
udc_start ();
541
612
#endif
542
613
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
-
548
614
// Boot script is finished, so now go into REPL/main mode.
549
615
int exit_code = PYEXEC_FORCED_EXIT ;
550
616
bool skip_repl = true;
@@ -567,7 +633,7 @@ int main(void) {
567
633
mp_hal_stdout_tx_str ("soft reboot\r\n" );
568
634
}
569
635
first_run = false;
570
- skip_repl = start_mp ();
636
+ skip_repl = start_mp (safe_mode );
571
637
reset_samd21 ();
572
638
reset_mp ();
573
639
} else if (exit_code != 0 ) {
0 commit comments