diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index d64cff18591d9..e44892fc3b54c 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -936,6 +936,10 @@ msgstr "" msgid "Filters too complex" msgstr "" +#: ports/esp32s2/common-hal/dualbank/__init__.c +msgid "Firmware image is invalid" +msgstr "" + #: ports/cxd56/common-hal/camera/Camera.c msgid "Format not supported" msgstr "" @@ -1983,6 +1987,10 @@ msgstr "" msgid "Unsupported pull value." msgstr "" +#: ports/esp32s2/common-hal/dualbank/__init__.c +msgid "Update Failed" +msgstr "" + #: ports/nrf/common-hal/_bleio/Characteristic.c #: ports/nrf/common-hal/_bleio/Descriptor.c msgid "Value length != required fixed length" @@ -3211,6 +3219,10 @@ msgstr "" msgid "offset is too large" msgstr "" +#: shared-bindings/dualbank/__init__.c +msgid "offset must be >= 0" +msgstr "" + #: py/objstr.c py/objstrunicode.c msgid "offset out of bounds" msgstr "" diff --git a/ports/esp32s2/Makefile b/ports/esp32s2/Makefile index 01f8f48a78ee4..35f82dd8a1492 100644 --- a/ports/esp32s2/Makefile +++ b/ports/esp32s2/Makefile @@ -106,6 +106,8 @@ INC += -isystem esp-idf/components/heap/include INC += -isystem esp-idf/components/esp_system/include INC += -isystem esp-idf/components/spi_flash/include INC += -isystem esp-idf/components/nvs_flash/include +INC += -isystem esp-idf/components/app_update/include +INC += -isystem esp-idf/components/bootloader_support/include INC += -I$(BUILD)/esp-idf/config CFLAGS += -DHAVE_CONFIG_H \ diff --git a/ports/esp32s2/common-hal/dualbank/__init__.c b/ports/esp32s2/common-hal/dualbank/__init__.c new file mode 100644 index 0000000000000..0f468a036beb1 --- /dev/null +++ b/ports/esp32s2/common-hal/dualbank/__init__.c @@ -0,0 +1,139 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 microDev + * + * 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. + */ + +#include "common-hal/dualbank/__init__.h" +#include "shared-bindings/dualbank/__init__.h" + +#include + +#include "esp_log.h" +#include "esp_ota_ops.h" + +static const esp_partition_t *update_partition = NULL; +static esp_ota_handle_t update_handle = 0; + +static const char *TAG = "dualbank"; + +void dualbank_reset(void) { + // should use `abort` instead of `end` + // but not in idf v4.2 + // esp_ota_abort(update_handle); + if (esp_ota_end(update_handle) == ESP_OK) { + update_handle = 0; + update_partition = NULL; + } +} + +static void __attribute__((noreturn)) task_fatal_error(void) { + ESP_LOGE(TAG, "Exiting task due to fatal error..."); + mp_raise_RuntimeError(translate("Update Failed")); +} + +void common_hal_dualbank_flash(const void *buf, const size_t len, const size_t offset) { + esp_err_t err; + + const esp_partition_t *running = esp_ota_get_running_partition(); + const esp_partition_t *last_invalid = esp_ota_get_last_invalid_partition(); + + if (update_partition == NULL) { + update_partition = esp_ota_get_next_update_partition(NULL); + + ESP_LOGI(TAG, "Running partition type %d subtype %d (offset 0x%08x)", + running->type, running->subtype, running->address); + + ESP_LOGI(TAG, "Writing partition type %d subtype %d (offset 0x%08x)\n", + update_partition->type, update_partition->subtype, update_partition->address); + + assert(update_partition != NULL); + } + + if (update_handle == 0) { + if (len > sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t)) { + esp_app_desc_t new_app_info; + memcpy(&new_app_info, &((char *)buf)[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)], sizeof(esp_app_desc_t)); + ESP_LOGI(TAG, "New firmware version: %s", new_app_info.version); + + esp_app_desc_t running_app_info; + if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK) { + ESP_LOGI(TAG, "Running firmware version: %s", running_app_info.version); + } + + esp_app_desc_t invalid_app_info; + if (esp_ota_get_partition_description(last_invalid, &invalid_app_info) == ESP_OK) { + ESP_LOGI(TAG, "Last invalid firmware version: %s", invalid_app_info.version); + } + + // check new version with running version + if (memcmp(new_app_info.version, running_app_info.version, sizeof(new_app_info.version)) == 0) { + ESP_LOGW(TAG, "New version is the same as running version."); + task_fatal_error(); + } + + // check new version with last invalid partition + if (last_invalid != NULL) { + if (memcmp(new_app_info.version, invalid_app_info.version, sizeof(new_app_info.version)) == 0) { + ESP_LOGW(TAG, "New version is the same as invalid version."); + task_fatal_error(); + } + } + + err = esp_ota_begin(update_partition, OTA_WITH_SEQUENTIAL_WRITES, &update_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ota_begin failed (%s)", esp_err_to_name(err)); + task_fatal_error(); + } + } else { + ESP_LOGE(TAG, "received package is not fit len"); + task_fatal_error(); + } + } + + if (offset == 0) { + err = esp_ota_write(update_handle, buf, len); + } else { + err = esp_ota_write_with_offset(update_handle, buf, len, offset); + } + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ota_write failed (%s)", esp_err_to_name(err)); + task_fatal_error(); + } +} + +void common_hal_dualbank_switch(void) { + if (esp_ota_end(update_handle) == ESP_OK) { + update_handle = 0; + update_partition = NULL; + } + esp_err_t err = esp_ota_set_boot_partition(esp_ota_get_next_update_partition(NULL)); + if (err != ESP_OK) { + if (err == ESP_ERR_OTA_VALIDATE_FAILED) { + ESP_LOGE(TAG, "Image validation failed, image is corrupted"); + mp_raise_RuntimeError(translate("Firmware image is invalid")); + } + ESP_LOGE(TAG, "esp_ota_set_boot_partition failed (%s)!", esp_err_to_name(err)); + task_fatal_error(); + } +} diff --git a/ports/esp32s2/common-hal/dualbank/__init__.h b/ports/esp32s2/common-hal/dualbank/__init__.h new file mode 100644 index 0000000000000..8b18336c948b4 --- /dev/null +++ b/ports/esp32s2/common-hal/dualbank/__init__.h @@ -0,0 +1,32 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 microDev + * + * 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. + */ + +#ifndef MICROPY_INCLUDED_ESP32S2_COMMON_HAL_DUALBANK___INIT___H +#define MICROPY_INCLUDED_ESP32S2_COMMON_HAL_DUALBANK___INIT___H + +extern void dualbank_reset(void); + +#endif //MICROPY_INCLUDED_ESP32S2_COMMON_HAL_DUALBANK___INIT___H diff --git a/ports/esp32s2/mpconfigport.mk b/ports/esp32s2/mpconfigport.mk index b3b2a6d70021f..2ad8afb51cd1e 100644 --- a/ports/esp32s2/mpconfigport.mk +++ b/ports/esp32s2/mpconfigport.mk @@ -19,6 +19,7 @@ CIRCUITPY_AUDIOBUSIO = 0 CIRCUITPY_AUDIOIO = 0 CIRCUITPY_CANIO = 1 CIRCUITPY_COUNTIO = 1 +CIRCUITPY_DUALBANK = 1 CIRCUITPY_FREQUENCYIO = 1 CIRCUITPY_I2CPERIPHERAL = 0 CIRCUITPY_ROTARYIO = 1 diff --git a/ports/esp32s2/supervisor/port.c b/ports/esp32s2/supervisor/port.c index 7037b4f05132f..a56ac61c81b87 100644 --- a/ports/esp32s2/supervisor/port.c +++ b/ports/esp32s2/supervisor/port.c @@ -41,6 +41,7 @@ #include "common-hal/busio/I2C.h" #include "common-hal/busio/SPI.h" #include "common-hal/busio/UART.h" +#include "common-hal/dualbank/__init__.h" #include "common-hal/ps2io/Ps2.h" #include "common-hal/pulseio/PulseIn.h" #include "common-hal/pwmio/PWMOut.h" @@ -123,6 +124,10 @@ void reset_port(void) { analogout_reset(); #endif +#if CIRCUITPY_DUALBANK + dualbank_reset(); +#endif + #if CIRCUITPY_PS2IO ps2_reset(); #endif diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index 1eafce2595f96..94f8ba42e76b2 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -205,6 +205,9 @@ endif ifeq ($(CIRCUITPY_OS),1) SRC_PATTERNS += os/% endif +ifeq ($(CIRCUITPY_DUALBANK),1) +SRC_PATTERNS += dualbank/% +endif ifeq ($(CIRCUITPY_PIXELBUF),1) SRC_PATTERNS += _pixelbuf/% endif @@ -348,6 +351,7 @@ SRC_COMMON_HAL_ALL = \ nvm/ByteArray.c \ nvm/__init__.c \ os/__init__.c \ + dualbank/__init__.c \ ps2io/Ps2.c \ ps2io/__init__.c \ pulseio/PulseIn.c \ diff --git a/py/circuitpy_mpconfig.h b/py/circuitpy_mpconfig.h index 0bd889ccdfbdf..08c92a77ef03a 100644 --- a/py/circuitpy_mpconfig.h +++ b/py/circuitpy_mpconfig.h @@ -539,6 +539,13 @@ extern const struct _mp_obj_module_t os_module; #define OS_MODULE_ALT_NAME #endif +#if CIRCUITPY_DUALBANK +extern const struct _mp_obj_module_t dualbank_module; +#define DUALBANK_MODULE { MP_OBJ_NEW_QSTR(MP_QSTR_dualbank), (mp_obj_t)&dualbank_module }, +#else +#define DUALBANK_MODULE +#endif + #if CIRCUITPY_PEW extern const struct _mp_obj_module_t pew_module; #define PEW_MODULE { MP_OBJ_NEW_QSTR(MP_QSTR__pew),(mp_obj_t)&pew_module }, @@ -827,6 +834,7 @@ extern const struct _mp_obj_module_t wifi_module; NETWORK_MODULE \ SOCKET_MODULE \ WIZNET_MODULE \ + DUALBANK_MODULE \ PEW_MODULE \ PIXELBUF_MODULE \ PS2IO_MODULE \ diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index 370f13373904a..af1dccf0243f0 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -179,6 +179,9 @@ CFLAGS += -DCIRCUITPY_NVM=$(CIRCUITPY_NVM) CIRCUITPY_OS ?= 1 CFLAGS += -DCIRCUITPY_OS=$(CIRCUITPY_OS) +CIRCUITPY_DUALBANK ?= 0 +CFLAGS += -DCIRCUITPY_DUALBANK=$(CIRCUITPY_DUALBANK) + CIRCUITPY_PIXELBUF ?= $(CIRCUITPY_FULL_BUILD) CFLAGS += -DCIRCUITPY_PIXELBUF=$(CIRCUITPY_PIXELBUF) diff --git a/shared-bindings/dualbank/__init__.c b/shared-bindings/dualbank/__init__.c new file mode 100644 index 0000000000000..8021ab18b937d --- /dev/null +++ b/shared-bindings/dualbank/__init__.c @@ -0,0 +1,115 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 microDev + * + * 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. + */ + +#include "shared-bindings/dualbank/__init__.h" + +//| """DUALBANK Module +//| +//| The `dualbank` module adds ability to update and switch +//| between the two app partitions. +//| +//| There are two identical partitions, these contain different +//| firmware versions. +//| Having two partitions enables rollback functionality. +//| +//| The two partitions are defined as boot partition and +//| next-update partition. Calling `dualbank.flash()` writes +//| the next-update partition. +//| +//| After the next-update partition is written a validation +//| check is performed and on a successful validation this +//| partition is set as the boot partition. On next reset, +//| firmware will be loaded from this partition. +//| +//| Here is the sequence of commands to follow: +//| +//| .. code-block:: python +//| +//| import dualbank +//| +//| dualbank.flash(buffer, offset) +//| dualbank.switch() +//| """ +//| ... +//| + +//| def flash(*buffer: ReadableBuffer, offset: int=0) -> None: +//| """Writes one of two app partitions at the given offset. +//| +//| This can be called multiple times when flashing the firmware +//| in small chunks. +//| """ +//| ... +//| +STATIC mp_obj_t dualbank_flash(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_buffer, ARG_offset }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_buffer, MP_ARG_OBJ | MP_ARG_REQUIRED }, + { MP_QSTR_offset, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if (args[ARG_offset].u_int < 0) { + mp_raise_ValueError(translate("offset must be >= 0")); + } + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_READ); + + common_hal_dualbank_flash(bufinfo.buf, bufinfo.len, args[ARG_offset].u_int); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(dualbank_flash_obj, 0, dualbank_flash); + +//| def switch() -> None: +//| """Switches the boot partition. +//| +//| On next reset, firmware will be loaded from the partition +//| just switched over to. +//| """ +//| ... +//| +STATIC mp_obj_t dualbank_switch(void) { + common_hal_dualbank_switch(); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(dualbank_switch_obj, dualbank_switch); + +STATIC const mp_rom_map_elem_t dualbank_module_globals_table[] = { + // module name + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_dualbank) }, + // module functions + { MP_ROM_QSTR(MP_QSTR_flash), MP_ROM_PTR(&dualbank_flash_obj) }, + { MP_ROM_QSTR(MP_QSTR_switch), MP_ROM_PTR(&dualbank_switch_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(dualbank_module_globals, dualbank_module_globals_table); + +const mp_obj_module_t dualbank_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&dualbank_module_globals, +}; diff --git a/shared-bindings/dualbank/__init__.h b/shared-bindings/dualbank/__init__.h new file mode 100644 index 0000000000000..7edbc6d68afd3 --- /dev/null +++ b/shared-bindings/dualbank/__init__.h @@ -0,0 +1,35 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 microDev + * + * 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. + */ + +#ifndef MICROPY_INCLUDED_SHARED_BINDINGS_DUALBANK___INIT___H +#define MICROPY_INCLUDED_SHARED_BINDINGS_DUALBANK___INIT___H + +#include "py/runtime.h" + +extern void common_hal_dualbank_switch(void); +extern void common_hal_dualbank_flash(const void *buf, const size_t len, const size_t offset); + +#endif // MICROPY_INCLUDED_SHARED_BINDINGS_DUALBANK___INIT___H