From 74a6edff4de212de11a435be12d19b5817ab8d2d Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Thu, 3 May 2018 15:53:14 -0400 Subject: [PATCH] handle bad power on reset better --- atmel-samd/asf/sam0/drivers/tc/tc.h | 7 +- atmel-samd/common-hal/storage/__init__.c | 8 ++ atmel-samd/main.c | 104 +++++++++++++++++------ atmel-samd/main.h | 30 +++++++ esp8266/common-hal/storage/__init__.c | 4 + shared-bindings/storage/__init__.c | 20 +++++ shared-bindings/storage/__init__.h | 1 + 7 files changed, 147 insertions(+), 27 deletions(-) create mode 100644 atmel-samd/main.h diff --git a/atmel-samd/asf/sam0/drivers/tc/tc.h b/atmel-samd/asf/sam0/drivers/tc/tc.h index 7bf7420461..28f77511f7 100644 --- a/atmel-samd/asf/sam0/drivers/tc/tc.h +++ b/atmel-samd/asf/sam0/drivers/tc/tc.h @@ -48,7 +48,7 @@ #define TC_H_INCLUDED /** - * \defgroup asfdoc_sam0_tc_group SAM Timer/Counter (TC) Driver + * \defgroup asfdoc_sam0_tc_group SAM Timer/Counter (TC) Driver * * This driver for Atmel® | SMART ARM®-based microcontrollers provides an interface for the configuration * and management of the timer modules within the device, for waveform @@ -1146,12 +1146,14 @@ static inline void tc_enable_events( /* Sanity check arguments */ Assert(module_inst); Assert(module_inst->hw); - Assert(events); + Assert(events); Tc *const tc_module = module_inst->hw; uint32_t event_mask = 0; + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" if (events->invert_event_input == true) { event_mask |= TC_EVCTRL_TCINV; } @@ -1171,6 +1173,7 @@ static inline void tc_enable_events( } tc_module->COUNT8.EVCTRL.reg |= event_mask | events->event_action; + #pragma GCC diagnostic pop } /** diff --git a/atmel-samd/common-hal/storage/__init__.c b/atmel-samd/common-hal/storage/__init__.c index b7c4fa6dd6..8a739bb8d9 100644 --- a/atmel-samd/common-hal/storage/__init__.c +++ b/atmel-samd/common-hal/storage/__init__.c @@ -27,8 +27,10 @@ #include #include "flash_api.h" +#include "main.h" #include "py/mperrno.h" #include "py/runtime.h" +#include "shared-bindings/microcontroller/__init__.h" #include "shared-bindings/storage/__init__.h" extern volatile bool mp_msc_enabled; @@ -44,3 +46,9 @@ void common_hal_storage_remount(const char* mount_path, bool readonly) { flash_set_usb_writeable(readonly); } + +void common_hal_storage_erase_filesystem(void) { + init_flash_fs(false, true); // Force a re-format. + common_hal_mcu_reset(); + // We won't actually get here, since we're resetting. +} diff --git a/atmel-samd/main.c b/atmel-samd/main.c index 2cbe6f88d2..0eb5a30570 100644 --- a/atmel-samd/main.c +++ b/atmel-samd/main.c @@ -66,6 +66,7 @@ #include "autoreload.h" #include "flash_api.h" +#include "main.h" #include "mpconfigboard.h" #include "rgb_led_status.h" #include "shared_dma.h" @@ -110,7 +111,7 @@ void do_str(const char *src, mp_parse_input_kind_t input_kind) { // we don't make this function static because it needs a lot of stack and we // want it to be executed without using stack within main() function -void init_flash_fs(bool create_allowed) { +void init_flash_fs(bool create_allowed, bool force_create) { // init the vfs object fs_user_mount_t *vfs_fat = &fs_user_mount_flash; vfs_fat->flags = 0; @@ -119,8 +120,8 @@ void init_flash_fs(bool create_allowed) { // try to mount the flash FRESULT res = f_mount(&vfs_fat->fatfs); - if (res == FR_NO_FILESYSTEM && create_allowed) { - // no filesystem so create a fresh one + if ((res == FR_NO_FILESYSTEM && create_allowed) || force_create) { + // No filesystem so create a fresh one, or reformat has been requested. // Wait two seconds before creating. Jittery power might // fail before we're ready. This can happen if someone @@ -129,10 +130,10 @@ void init_flash_fs(bool create_allowed) { // Then try one more time to mount the flash in case it was late coming up. res = f_mount(&vfs_fat->fatfs); - if (res == FR_NO_FILESYSTEM) { + if (res == FR_NO_FILESYSTEM || force_create) { uint8_t working_buf[_MAX_SS]; res = f_mkfs(&vfs_fat->fatfs, FM_FAT, 0, working_buf, sizeof(working_buf)); - // Flush the new file system to make sure its repaired immediately. + // Flush the new file system to make sure it's repaired immediately. flash_flush(); if (res != FR_OK) { return; @@ -528,6 +529,14 @@ void load_serial_number(void) { extern uint32_t _ezero; safe_mode_t samd21_init(void) { + + // Set brownout detection to ~2.7V. Default from factory is 1.7V, + // which is too low for proper operation of external SPI flash chips (they are 2.7-3.6V). + // Disable while changing level. + SYSCTRL->BOD33.bit.ENABLE = 0; + SYSCTRL->BOD33.bit.LEVEL = 39; // 2.77V with hysteresis off. Table 37.20 in datasheet. + SYSCTRL->BOD33.bit.ENABLE = 1; + #ifdef ENABLE_MICRO_TRACE_BUFFER REG_MTB_POSITION = ((uint32_t) (mtb - REG_MTB_BASE)) & 0xFFFFFFF8; REG_MTB_FLOW = (((uint32_t) mtb - REG_MTB_BASE) + TRACE_BUFFER_SIZE_BYTES) & 0xFFFFFFF8; @@ -683,9 +692,9 @@ int main(void) { // Create a new filesystem only if we're not in a safe mode. // A power brownout here could make it appear as if there's // no SPI flash filesystem, and we might erase the existing one. - init_flash_fs(safe_mode == NO_SAFE_MODE); + init_flash_fs(safe_mode == NO_SAFE_MODE, false); - // Reset everything and prep MicroPython to run boot.py. + // Reset everything and prep to run boot.py. reset_samd21(); reset_board(); reset_mp(); @@ -694,38 +703,83 @@ int main(void) { autoreload_enable(); // By default our internal flash is readonly to local python code and - // writeable over USB. Set it here so that boot.py can change it. + // writable over USB. flash_set_usb_writeable(true); // If not in safe mode, run boot before initing USB and capture output in a // file. if (safe_mode == NO_SAFE_MODE && MP_STATE_VM(vfs_mount_table) != NULL) { new_status_color(BOOT_RUNNING); + + // Run the first of these files found at boot time (if any). + static char const* BOOT_PY_FILENAMES[] = { + "settings.txt", + "settings.py", + "boot.py", + "boot.txt", + }; + + // Check for existance of any of the BOOT_PY_FILENAMES. + char const* boot_py_to_use = NULL; + for (size_t i = 0; i < MP_ARRAY_SIZE(BOOT_PY_FILENAMES); i++) { + if (mp_import_stat(BOOT_PY_FILENAMES[i]) == MP_IMPORT_STAT_FILE) { + boot_py_to_use = BOOT_PY_FILENAMES[i]; + break; + } + } + #ifdef CIRCUITPY_BOOT_OUTPUT_FILE - // Since USB isn't up yet we can cheat and let ourselves write the boot - // output file. - flash_set_usb_writeable(false); FIL file_pointer; boot_output_file = &file_pointer; - f_open(&((fs_user_mount_t *) MP_STATE_VM(vfs_mount_table)->obj)->fatfs, - boot_output_file, CIRCUITPY_BOOT_OUTPUT_FILE, FA_WRITE | FA_CREATE_ALWAYS); - flash_set_usb_writeable(true); - // Write version info to boot_out.txt. - mp_hal_stdout_tx_str(MICROPY_FULL_VERSION_INFO); - mp_hal_stdout_tx_str("\r\n"); + + // Get the base filesystem. + FATFS *fs = &((fs_user_mount_t *) MP_STATE_VM(vfs_mount_table)->obj)->fatfs; + + char file_contents[512]; + UINT chars_read = 0; + bool skip_boot_output = false; + + // If there's no boot.py file that might write some changing output, + // read the existing copy of CIRCUITPY_BOOT_OUTPUT_FILE and see if its contents + // match the version info we would print anyway. If so, skip writing CIRCUITPY_BOOT_OUTPUT_FILE. + // This saves wear and tear on the flash and also prevents filesystem damage if power is lost + // during the write, which may happen due to bobbling the power connector or weak power. + + if (!boot_py_to_use && f_open(fs, boot_output_file, CIRCUITPY_BOOT_OUTPUT_FILE, FA_READ) == FR_OK) { + f_read(boot_output_file, file_contents, 512, &chars_read); + f_close(boot_output_file); + skip_boot_output = + // + 2 accounts for \r\n. + chars_read == strlen(MICROPY_FULL_VERSION_INFO) + 2 && + strncmp(file_contents, MICROPY_FULL_VERSION_INFO, strlen(MICROPY_FULL_VERSION_INFO)) == 0; + } + + if (!skip_boot_output) { + // Wait 1.5 seconds before opening CIRCUITPY_BOOT_OUTPUT_FILE for write, + // in case power is momentary or will fail shortly due to, say a low, battery. + mp_hal_delay_ms(1500); + + // USB isn't up, so we can write the file. + flash_set_usb_writeable(false); + f_open(fs, boot_output_file, CIRCUITPY_BOOT_OUTPUT_FILE, FA_WRITE | FA_CREATE_ALWAYS); + // Write version info to boot_out.txt. + mp_hal_stdout_tx_str(MICROPY_FULL_VERSION_INFO); + mp_hal_stdout_tx_str("\r\n"); + } #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; + if (boot_py_to_use) { + maybe_run(boot_py_to_use, NULL); + } #ifdef CIRCUITPY_BOOT_OUTPUT_FILE - f_close(boot_output_file); - flash_flush(); - boot_output_file = NULL; + if (!skip_boot_output) { + f_close(boot_output_file); + flash_flush(); + boot_output_file = NULL; + } + flash_set_usb_writeable(true); #endif // Reset to remove any state that boot.py setup. It should only be used to diff --git a/atmel-samd/main.h b/atmel-samd/main.h new file mode 100644 index 0000000000..f4255efa9e --- /dev/null +++ b/atmel-samd/main.h @@ -0,0 +1,30 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Dan Halbert for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// Functions defined in main.c that are useful elsewhere. +// Eventually they should be factored out. + +void init_flash_fs(bool create_allowed, bool force_create); diff --git a/esp8266/common-hal/storage/__init__.c b/esp8266/common-hal/storage/__init__.c index a4c3a387ac..b800ad2bd9 100644 --- a/esp8266/common-hal/storage/__init__.c +++ b/esp8266/common-hal/storage/__init__.c @@ -32,3 +32,7 @@ void common_hal_storage_remount(const char* mount_path, bool readonly) { mp_raise_NotImplementedError(""); } + +void common_hal_storage_erase_filesystem() { + mp_raise_NotImplementedError("Use esptool to erase flash and re-upload Python instead"); +} diff --git a/shared-bindings/storage/__init__.c b/shared-bindings/storage/__init__.c index ccf8ff511f..b5086e3522 100644 --- a/shared-bindings/storage/__init__.c +++ b/shared-bindings/storage/__init__.c @@ -115,12 +115,32 @@ mp_obj_t storage_remount(mp_obj_t mount_path, mp_obj_t readonly) { } MP_DEFINE_CONST_FUN_OBJ_2(storage_remount_obj, storage_remount); +//| .. function:: erase_filesystem() +//| +//| Erase and re-create the ``CIRCUITPY`` filesystem. Then call +//| `microcontroller.reset()` to restart CircuitPython and have the +//| host computer remount CIRCUITPY. +//| +//| This function can be called from the REPL when ``CIRCUITPY`` +//| has become corrupted. +//| +//| .. warning:: All the data on ``CIRCUITPY`` will be lost, and +//| CircuitPython will restart. + +mp_obj_t storage_erase_filesystem(void) { + common_hal_storage_erase_filesystem(); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_0(storage_erase_filesystem_obj, storage_erase_filesystem); + + STATIC const mp_rom_map_elem_t storage_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_storage) }, { MP_ROM_QSTR(MP_QSTR_mount), MP_ROM_PTR(&storage_mount_obj) }, { MP_ROM_QSTR(MP_QSTR_umount), MP_ROM_PTR(&storage_umount_obj) }, { MP_ROM_QSTR(MP_QSTR_remount), MP_ROM_PTR(&storage_remount_obj) }, + { MP_ROM_QSTR(MP_QSTR_erase_filesystem), MP_ROM_PTR(&storage_erase_filesystem_obj) }, //| .. class:: VfsFat(block_device) //| diff --git a/shared-bindings/storage/__init__.h b/shared-bindings/storage/__init__.h index 574b5ff8dd..fe78696a6f 100644 --- a/shared-bindings/storage/__init__.h +++ b/shared-bindings/storage/__init__.h @@ -34,5 +34,6 @@ void common_hal_storage_mount(mp_obj_t vfs_obj, const char* path, bool readonly) void common_hal_storage_umount_path(const char* path); void common_hal_storage_umount_object(mp_obj_t vfs_obj); void common_hal_storage_remount(const char* path, bool readonly); +void common_hal_storage_erase_filesystem(void); #endif // MICROPY_INCLUDED_SHARED_BINDINGS_STORAGE___INIT___H