From 6fde10f0bbfc3af62418aac38d0feb0a09e7d219 Mon Sep 17 00:00:00 2001 From: M Hightower <27247790+mhightower83@users.noreply.github.com> Date: Fri, 27 Dec 2019 19:44:09 -0800 Subject: [PATCH 01/24] Proof of concept for post OTA 1st boot erase config. --- cores/esp8266/Arduino.h | 1 + cores/esp8266/Esp.cpp | 6 ++++ cores/esp8266/Esp.h | 4 +++ cores/esp8266/Updater.cpp | 24 ++++++++++---- cores/esp8266/Updater.h | 13 ++++++-- cores/esp8266/erase_config.cpp | 60 ++++++++++++++++++++++++++++++++++ cores/esp8266/erase_config.h | 43 ++++++++++++++++++++++++ 7 files changed, 143 insertions(+), 8 deletions(-) create mode 100644 cores/esp8266/erase_config.cpp create mode 100644 cores/esp8266/erase_config.h diff --git a/cores/esp8266/Arduino.h b/cores/esp8266/Arduino.h index 54919ba100..26a6bc3f21 100644 --- a/cores/esp8266/Arduino.h +++ b/cores/esp8266/Arduino.h @@ -39,6 +39,7 @@ extern "C" { #include "twi.h" #include "core_esp8266_features.h" #include "core_esp8266_version.h" +#include "erase_config.h" #define HIGH 0x1 #define LOW 0x0 diff --git a/cores/esp8266/Esp.cpp b/cores/esp8266/Esp.cpp index 04c83518f6..123c891e43 100644 --- a/cores/esp8266/Esp.cpp +++ b/cores/esp8266/Esp.cpp @@ -502,6 +502,11 @@ struct rst_info * EspClass::getResetInfoPtr(void) { return &resetInfo; } +#ifdef ERASE_CONFIG_H +bool EspClass::eraseConfig(const uint32_t flash_erase_mask) { + return erase_config(flash_erase_mask); +} +#else bool EspClass::eraseConfig(void) { const size_t cfgSize = 0x4000; size_t cfgAddr = ESP.getFlashChipSize() - cfgSize; @@ -514,6 +519,7 @@ bool EspClass::eraseConfig(void) { return true; } +#endif uint32_t EspClass::getSketchSize() { static uint32_t result = 0; diff --git a/cores/esp8266/Esp.h b/cores/esp8266/Esp.h index f4529c839a..26b7f21206 100644 --- a/cores/esp8266/Esp.h +++ b/cores/esp8266/Esp.h @@ -191,7 +191,11 @@ class EspClass { String getResetInfo(); struct rst_info * getResetInfoPtr(); +#ifdef ERASE_CONFIG_H + bool eraseConfig(const uint32_t flash_erase_mask = ERASE_CONFIG_SDK_DATA); +#else bool eraseConfig(); +#endif #ifndef CORE_MOCK inline uint32_t getCycleCount() __attribute__((always_inline)); diff --git a/cores/esp8266/Updater.cpp b/cores/esp8266/Updater.cpp index ebb893aabd..e8567e85a2 100644 --- a/cores/esp8266/Updater.cpp +++ b/cores/esp8266/Updater.cpp @@ -1,6 +1,7 @@ #include "Updater.h" #include "eboot_command.h" #include +#include //#define DEBUG_UPDATER Serial @@ -34,6 +35,9 @@ UpdaterClass::UpdaterClass() , _startAddress(0) , _currentAddress(0) , _command(U_FLASH) +#ifdef ERASE_CONFIG_H +, _eraseConfigOption(ERASE_CONFIG_BLANK_BIN) +#endif , _hash(nullptr) , _verify(nullptr) , _progress_callback(nullptr) @@ -84,7 +88,7 @@ bool UpdaterClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) { _setError(UPDATE_ERROR_BOOTSTRAP); return false; } - + #ifdef DEBUG_UPDATER if (command == U_FS) { DEBUG_UPDATER.println(F("[begin] Update Filesystem.")); @@ -127,7 +131,7 @@ bool UpdaterClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) { //make sure that the size of both sketches is less than the total space (updateEndAddress) if(updateStartAddress < currentSketchSize) { - _setError(UPDATE_ERROR_SPACE); + _setError(UPDATE_ERROR_SPACE); return false; } } @@ -284,10 +288,17 @@ bool UpdaterClass::end(bool evenIfRemaining){ if (_command == U_FLASH) { eboot_command ebcmd; + memset(&ebcmd, 0, sizeof(ebcmd)); ebcmd.action = ACTION_COPY_RAW; ebcmd.args[0] = _startAddress; ebcmd.args[1] = 0x00000; ebcmd.args[2] = _size; +#ifdef ERASE_CONFIG_H + ebcmd.args[4] = _eraseConfigOption; + ebcmd.args[5] = ~_eraseConfigOption; + ebcmd.args[6] = _eraseConfigOption; + ebcmd.args[7] = ~_eraseConfigOption; +#endif eboot_command_write(&ebcmd); #ifdef DEBUG_UPDATER @@ -297,6 +308,7 @@ bool UpdaterClass::end(bool evenIfRemaining){ else if (_command == U_FS) { #ifdef ATOMIC_FS_UPDATE eboot_command ebcmd; + memset(&ebcmd, 0, sizeof(ebcmd)); ebcmd.action = ACTION_COPY_RAW; ebcmd.args[0] = _startAddress; ebcmd.args[1] = (uintptr_t)&_FS_start - 0x40200000; @@ -345,7 +357,7 @@ bool UpdaterClass::_writeBuffer(){ modifyFlashMode = true; } } - + if (eraseResult) { if(!_async) yield(); writeResult = ESP.flashWrite(_currentAddress, (uint32_t*) _buffer, _bufferLen); @@ -431,7 +443,7 @@ bool UpdaterClass::_verifyEnd() { uint8_t buf[4]; if(!ESP.flashRead(_startAddress, (uint32_t *) &buf[0], 4)) { _currentAddress = (_startAddress); - _setError(UPDATE_ERROR_READ); + _setError(UPDATE_ERROR_READ); return false; } @@ -443,7 +455,7 @@ bool UpdaterClass::_verifyEnd() { return true; } else if (buf[0] != 0xE9) { _currentAddress = (_startAddress); - _setError(UPDATE_ERROR_MAGIC_BYTE); + _setError(UPDATE_ERROR_MAGIC_BYTE); return false; } @@ -452,7 +464,7 @@ bool UpdaterClass::_verifyEnd() { // check if new bin fits to SPI flash if(bin_flash_size > ESP.getFlashChipRealSize()) { _currentAddress = (_startAddress); - _setError(UPDATE_ERROR_NEW_FLASH_CONFIG); + _setError(UPDATE_ERROR_NEW_FLASH_CONFIG); return false; } diff --git a/cores/esp8266/Updater.h b/cores/esp8266/Updater.h index d47c613330..663d967886 100644 --- a/cores/esp8266/Updater.h +++ b/cores/esp8266/Updater.h @@ -51,7 +51,7 @@ class UpdaterVerifyClass { class UpdaterClass { public: typedef std::function THandlerFunction_Progress; - + UpdaterClass(); /* Optionally add a cryptographic signature verification hash and method */ @@ -63,6 +63,13 @@ class UpdaterClass { */ bool begin(size_t size, int command = U_FLASH, int ledPin = -1, uint8_t ledOn = LOW); +#ifdef ERASE_CONFIG_H + /* + */ + inline void setEraseConfigOption(ERASE_CONFIG_MASK_t eraseOption) { + _eraseConfigOption = eraseOption; + } +#endif /* Run Updater from asynchronous callbacs */ @@ -179,7 +186,7 @@ class UpdaterClass { bool _verifyHeader(uint8_t data); bool _verifyEnd(); - void _setError(int error); + void _setError(int error); bool _async; uint8_t _error; @@ -197,6 +204,8 @@ class UpdaterClass { int _ledPin; uint8_t _ledOn; + uint32_t _eraseConfigOption; + // Optional signed binary verification UpdaterHashClass *_hash; UpdaterVerifyClass *_verify; diff --git a/cores/esp8266/erase_config.cpp b/cores/esp8266/erase_config.cpp new file mode 100644 index 0000000000..b05f93c3c3 --- /dev/null +++ b/cores/esp8266/erase_config.cpp @@ -0,0 +1,60 @@ +#include +#include +#include +#include + +#ifdef ERASE_CONFIG_H + +extern "C" { +#include "user_interface.h" + +bool erase_config(const uint32_t flash_erase_mask) { + // This is really the active configured size + uint32_t flash_size = flashchip->chip_size; + uint32_t erase_mask = (flash_erase_mask & (uint32_t)ERASE_CONFIG_ALL_DATA); + uint32_t sector = flash_size/SPI_FLASH_SEC_SIZE - 1U; + + for (; !!erase_mask; erase_mask >>= 1U, sector--) { + if ((erase_mask & 1U)) { + if (0 != spi_flash_erase_sector(sector)) { + return false; + } + } + } + + return true; +} + +bool check_and_erase_config(void) { + // This should work since each element of the structure is a word. + eboot_command volatile * ebcmd = (eboot_command volatile *)RTC_MEM; + + // We want to run after an OTA has completed and the bin has been moved to its + // final resting place in flash. We want to catch the moment of the 1st boot + // of this new sketch. Then verify we have a valid erase option. + if (0U == ebcmd->magic && + 0U == ebcmd->crc32 && + ACTION_COPY_RAW == ebcmd->action && + ebcmd->args[4] == ebcmd->args[6] && + ebcmd->args[5] == ebcmd->args[7] && + ebcmd->args[4] == ~ebcmd->args[5] && + 0U == (ebcmd->args[4] & ~ERASE_CONFIG_ALL_DATA)) { + + uint32_t erase_flash_option = ebcmd->args[4]; + + // Make sure we don't repeat + for (size_t i=4; i<=7; i++) + ebcmd->args[i] = 0U; + + if (erase_flash_option) { + erase_config(erase_flash_option); + system_restart(); + while(true){} + } + } + return true; +} + +}; + +#endif // ERASE_CONFIG_H diff --git a/cores/esp8266/erase_config.h b/cores/esp8266/erase_config.h new file mode 100644 index 0000000000..525c60f02c --- /dev/null +++ b/cores/esp8266/erase_config.h @@ -0,0 +1,43 @@ +#ifndef ERASE_CONFIG_H +#define ERASE_CONFIG_H + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum ERASE_CONFIG { +// Note, The Arduino ESP8266 flash memory map differs from Espressif's. +// Espressive has separate sectors for PHY init data and RF_CAL. In Arduino +// ESP8266 RF_CAL and PHY INIT share the same sector. Between two RF init user +// calls the PHY init data is overlayed. Thus avoiding the waste of a 4K sector +// to store only 128 bytes. +// +// Mapping of Sectors at end of Flash, adapted for the Arduino ESP8266. +// Defaults to using the IDE configured flash size for placement +//_______________________________________________________________________________________ +//_Bit_number_for_Mask______|_____4_____|_____3_____|_____2_____|_____1_____|_____0_____| +// | | RF_CAL | SDK Parameter Area | +// Overlay at RF init | | PHY INIT | | | | +// Persistant data | | | | SSID/PW | | +// User storage | EEPROM | | | | | +// Often shown downloaded | | BLANK.BIN | | BLANK.BIN | | +//__________________________|___________|___________|___________|___________|___________| +ERASE_CONFIG_NONE = 0, //| | | | | | +ERASE_CONFIG_EEPROM = ( BIT(4) ), +ERASE_CONFIG_RF_CAL = ( BIT(3) ), +ERASE_CONFIG_PERSISTANT = ( BIT(1) ), +ERASE_CONFIG_BLANK_BIN = ( BIT(3) | BIT(1) ), +ERASE_CONFIG_SDK_DATA = ( BIT(3) | BIT(2) | BIT(1) | BIT(0) ), +ERASE_CONFIG_ALL_DATA = ( BIT(4) | BIT(3) | BIT(2) | BIT(1) | BIT(0) ) +//__________________________|___________|___________|___________|___________|___________| +} ERASE_CONFIG_MASK_t; // Use one of these with eraseConfig + +bool check_and_erase_config(void); +bool erase_config(const uint32_t flash_erase_mask); + +#ifdef __cplusplus +}; +#endif + +#endif From 93fb3d3d3ed1374ab53ccfed3464231d8bed1826 Mon Sep 17 00:00:00 2001 From: M Hightower <27247790+mhightower83@users.noreply.github.com> Date: Sun, 29 Dec 2019 17:08:21 -0800 Subject: [PATCH 02/24] Added a 2nd method --- cores/esp8266/Arduino.h | 2 + cores/esp8266/core_esp8266_main.cpp | 24 ++++++++++ cores/esp8266/erase_config.cpp | 69 +++++++++++++++++++++++++++-- 3 files changed, 92 insertions(+), 3 deletions(-) diff --git a/cores/esp8266/Arduino.h b/cores/esp8266/Arduino.h index 26a6bc3f21..571c848bbc 100644 --- a/cores/esp8266/Arduino.h +++ b/cores/esp8266/Arduino.h @@ -39,6 +39,8 @@ extern "C" { #include "twi.h" #include "core_esp8266_features.h" #include "core_esp8266_version.h" + +#define ERASE_CONFIG_METHOD 2 #include "erase_config.h" #define HIGH 0x1 diff --git a/cores/esp8266/core_esp8266_main.cpp b/cores/esp8266/core_esp8266_main.cpp index 850d9b95a8..cc7392e1a8 100644 --- a/cores/esp8266/core_esp8266_main.cpp +++ b/cores/esp8266/core_esp8266_main.cpp @@ -283,6 +283,10 @@ void init_done() { */ +#if (ERASE_CONFIG_METHOD == 2) +int eboot_two_shots __attribute__((section(".noinit"))); +#endif + extern "C" void app_entry_redefinable(void) __attribute__((weak)); extern "C" void app_entry_redefinable(void) { @@ -291,6 +295,10 @@ extern "C" void app_entry_redefinable(void) cont_t s_cont __attribute__((aligned(16))); g_pcont = &s_cont; +#if (ERASE_CONFIG_METHOD == 2) + eboot_two_shots = 2; +#endif + /* Call the entry point of the SDK code. */ call_user_start(); } @@ -308,12 +316,28 @@ extern "C" void preinit (void) /* do nothing by default */ } +#if (ERASE_CONFIG_METHOD == 1) +extern "C" void __pinMode(uint8_t pin, uint8_t mode); +#endif + extern "C" void user_init(void) { struct rst_info *rtc_info_ptr = system_get_rst_info(); memcpy((void *) &resetInfo, (void *) rtc_info_ptr, sizeof(resetInfo)); uart_div_modify(0, UART_CLK_FREQ / (115200)); +#if (ERASE_CONFIG_METHOD == 1) + if (0 != resetInfo.reason) { // Not Power On + // 1st 3 lines are trying to silence or reduce the unreadable serial + // prints. Not sure of the effectivness. Need to reverify. + system_set_os_print(0); + U0IE = 0; + U1IE = 0; + __pinMode(1, INPUT_PULLUP); // Silence U0TXD + check_and_erase_config(); + } +#endif + init(); // in core_esp8266_wiring.c, inits hw regs and sdk timer initVariant(); diff --git a/cores/esp8266/erase_config.cpp b/cores/esp8266/erase_config.cpp index b05f93c3c3..edc439def5 100644 --- a/cores/esp8266/erase_config.cpp +++ b/cores/esp8266/erase_config.cpp @@ -3,12 +3,32 @@ #include #include + +#ifndef VAR_NAME_VALUE +#define VALUE(x) __STRINGIFY(x) +#define VAR_NAME_VALUE(var) #var " = " VALUE(var) +#endif +#pragma message(VAR_NAME_VALUE(ERASE_CONFIG_METHOD)) + + +#if (ERASE_CONFIG_METHOD == 0) +#undef ERASE_CONFIG_H +#endif + #ifdef ERASE_CONFIG_H extern "C" { #include "user_interface.h" -bool erase_config(const uint32_t flash_erase_mask) { +#if (ERASE_CONFIG_METHOD == 1) +#define IRAM_MAYBE +#elif (ERASE_CONFIG_METHOD == 2) +#define IRAM_MAYBE ICACHE_RAM_ATTR +#else +#pragma GCC error "Unsupported ERASE_CONFIG_METHOD" +#endif + +bool IRAM_MAYBE erase_config(const uint32_t flash_erase_mask) { // This is really the active configured size uint32_t flash_size = flashchip->chip_size; uint32_t erase_mask = (flash_erase_mask & (uint32_t)ERASE_CONFIG_ALL_DATA); @@ -16,7 +36,11 @@ bool erase_config(const uint32_t flash_erase_mask) { for (; !!erase_mask; erase_mask >>= 1U, sector--) { if ((erase_mask & 1U)) { +#if (ERASE_CONFIG_METHOD == 1) if (0 != spi_flash_erase_sector(sector)) { +#elif (ERASE_CONFIG_METHOD == 2) + if (0 != SPIEraseSector(sector)) { +#endif return false; } } @@ -25,7 +49,7 @@ bool erase_config(const uint32_t flash_erase_mask) { return true; } -bool check_and_erase_config(void) { +bool IRAM_MAYBE check_and_erase_config(void) { // This should work since each element of the structure is a word. eboot_command volatile * ebcmd = (eboot_command volatile *)RTC_MEM; @@ -47,14 +71,53 @@ bool check_and_erase_config(void) { ebcmd->args[i] = 0U; if (erase_flash_option) { +#if (ERASE_CONFIG_METHOD == 1) erase_config(erase_flash_option); system_restart(); while(true){} +#elif (ERASE_CONFIG_METHOD == 2) + return erase_config(erase_flash_option); +#endif } } return true; } -}; +#if (ERASE_CONFIG_METHOD == 2) //Another option +extern int eboot_two_shots; + +void ICACHE_RAM_ATTR dbg_log_SPIRead(uint32_t addr, void *dest, size_t size, int err) __attribute__((weak)); +void ICACHE_RAM_ATTR dbg_log_SPIRead(uint32_t addr, void *dest, size_t size, int err) { + (void)addr; + (void)dest; + (void)size; + (void)err; +} + +#ifndef ROM_SPIRead +#define ROM_SPIRead 0x40004b1cU +#endif +typedef int (*fp_SPIRead_t)(uint32_t addr, void *dest, size_t size); +constexpr fp_SPIRead_t real_SPIRead = (fp_SPIRead_t)ROM_SPIRead; + +int ICACHE_RAM_ATTR SPIRead(uint32_t addr, void *dest, size_t size) { + // The very 1st read that goes by is to get the config flash size from + // image header. The NONOS SDK will update flashchip->chip_size. Then, a + // verification read is performed. Before this read is passed on we erase + // config sectors. + if (eboot_two_shots) { + if (eboot_two_shots == 1) + check_and_erase_config(); + + eboot_two_shots--; + } + + int err = real_SPIRead(addr, dest, size); + dbg_log_SPIRead(addr, dest, size, err); + return err; +} +#endif + +}; #endif // ERASE_CONFIG_H From 6d882e4c3c98831d116dccccab20b91948b6b5e8 Mon Sep 17 00:00:00 2001 From: M Hightower <27247790+mhightower83@users.noreply.github.com> Date: Mon, 30 Dec 2019 08:41:02 -0800 Subject: [PATCH 03/24] Added two_shots init to noextra4k --- cores/esp8266/core_esp8266_app_entry_noextra4k.cpp | 8 ++++++++ cores/esp8266/erase_config.cpp | 5 ++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/cores/esp8266/core_esp8266_app_entry_noextra4k.cpp b/cores/esp8266/core_esp8266_app_entry_noextra4k.cpp index 56e4b1d598..dbefcbe0ad 100644 --- a/cores/esp8266/core_esp8266_app_entry_noextra4k.cpp +++ b/cores/esp8266/core_esp8266_app_entry_noextra4k.cpp @@ -27,10 +27,18 @@ extern "C" void call_user_start(); /* this is the default NONOS-SDK user's heap location */ static cont_t g_cont __attribute__ ((aligned (16))); +#if (ERASE_CONFIG_METHOD == 2) +extern int eboot_two_shots; +#endif + extern "C" void app_entry_redefinable(void) { g_pcont = &g_cont; +#if (ERASE_CONFIG_METHOD == 2) + eboot_two_shots = 2; +#endif + /* Call the entry point of the SDK code. */ call_user_start(); } diff --git a/cores/esp8266/erase_config.cpp b/cores/esp8266/erase_config.cpp index edc439def5..67a0b4d21f 100644 --- a/cores/esp8266/erase_config.cpp +++ b/cores/esp8266/erase_config.cpp @@ -107,10 +107,9 @@ int ICACHE_RAM_ATTR SPIRead(uint32_t addr, void *dest, size_t size) { // verification read is performed. Before this read is passed on we erase // config sectors. if (eboot_two_shots) { - if (eboot_two_shots == 1) - check_and_erase_config(); - eboot_two_shots--; + if (0 == eboot_two_shots) + check_and_erase_config(); } int err = real_SPIRead(addr, dest, size); From 500b7cc60bceb984c10be23dbaa08375d1ee4d32 Mon Sep 17 00:00:00 2001 From: M Hightower <27247790+mhightower83@users.noreply.github.com> Date: Mon, 30 Dec 2019 18:34:00 -0800 Subject: [PATCH 04/24] Removed cleaned up debug crud and added test example. --- cores/esp8266/Arduino.h | 2 - cores/esp8266/Esp.cpp | 6 - cores/esp8266/Esp.h | 4 - .../core_esp8266_app_entry_noextra4k.cpp | 8 - cores/esp8266/core_esp8266_main.cpp | 24 -- cores/esp8266/erase_config.cpp | 124 ++++--- .../OTAConfigErase/OTAConfigErase.ino | 314 ++++++++++++++++++ .../examples/OTAConfigErase/TelnetAgent.ino | 104 ++++++ .../examples/OTAConfigErase/WifiHealth.h | 48 +++ .../examples/OTAConfigErase/WifiHealth.ino | 305 +++++++++++++++++ .../examples/OTAConfigErase/rtc.ino | 74 +++++ 11 files changed, 923 insertions(+), 90 deletions(-) create mode 100644 libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino create mode 100644 libraries/ArduinoOTA/examples/OTAConfigErase/TelnetAgent.ino create mode 100644 libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.h create mode 100644 libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.ino create mode 100644 libraries/ArduinoOTA/examples/OTAConfigErase/rtc.ino diff --git a/cores/esp8266/Arduino.h b/cores/esp8266/Arduino.h index 571c848bbc..26a6bc3f21 100644 --- a/cores/esp8266/Arduino.h +++ b/cores/esp8266/Arduino.h @@ -39,8 +39,6 @@ extern "C" { #include "twi.h" #include "core_esp8266_features.h" #include "core_esp8266_version.h" - -#define ERASE_CONFIG_METHOD 2 #include "erase_config.h" #define HIGH 0x1 diff --git a/cores/esp8266/Esp.cpp b/cores/esp8266/Esp.cpp index 123c891e43..04c83518f6 100644 --- a/cores/esp8266/Esp.cpp +++ b/cores/esp8266/Esp.cpp @@ -502,11 +502,6 @@ struct rst_info * EspClass::getResetInfoPtr(void) { return &resetInfo; } -#ifdef ERASE_CONFIG_H -bool EspClass::eraseConfig(const uint32_t flash_erase_mask) { - return erase_config(flash_erase_mask); -} -#else bool EspClass::eraseConfig(void) { const size_t cfgSize = 0x4000; size_t cfgAddr = ESP.getFlashChipSize() - cfgSize; @@ -519,7 +514,6 @@ bool EspClass::eraseConfig(void) { return true; } -#endif uint32_t EspClass::getSketchSize() { static uint32_t result = 0; diff --git a/cores/esp8266/Esp.h b/cores/esp8266/Esp.h index 26b7f21206..f4529c839a 100644 --- a/cores/esp8266/Esp.h +++ b/cores/esp8266/Esp.h @@ -191,11 +191,7 @@ class EspClass { String getResetInfo(); struct rst_info * getResetInfoPtr(); -#ifdef ERASE_CONFIG_H - bool eraseConfig(const uint32_t flash_erase_mask = ERASE_CONFIG_SDK_DATA); -#else bool eraseConfig(); -#endif #ifndef CORE_MOCK inline uint32_t getCycleCount() __attribute__((always_inline)); diff --git a/cores/esp8266/core_esp8266_app_entry_noextra4k.cpp b/cores/esp8266/core_esp8266_app_entry_noextra4k.cpp index dbefcbe0ad..56e4b1d598 100644 --- a/cores/esp8266/core_esp8266_app_entry_noextra4k.cpp +++ b/cores/esp8266/core_esp8266_app_entry_noextra4k.cpp @@ -27,18 +27,10 @@ extern "C" void call_user_start(); /* this is the default NONOS-SDK user's heap location */ static cont_t g_cont __attribute__ ((aligned (16))); -#if (ERASE_CONFIG_METHOD == 2) -extern int eboot_two_shots; -#endif - extern "C" void app_entry_redefinable(void) { g_pcont = &g_cont; -#if (ERASE_CONFIG_METHOD == 2) - eboot_two_shots = 2; -#endif - /* Call the entry point of the SDK code. */ call_user_start(); } diff --git a/cores/esp8266/core_esp8266_main.cpp b/cores/esp8266/core_esp8266_main.cpp index cc7392e1a8..850d9b95a8 100644 --- a/cores/esp8266/core_esp8266_main.cpp +++ b/cores/esp8266/core_esp8266_main.cpp @@ -283,10 +283,6 @@ void init_done() { */ -#if (ERASE_CONFIG_METHOD == 2) -int eboot_two_shots __attribute__((section(".noinit"))); -#endif - extern "C" void app_entry_redefinable(void) __attribute__((weak)); extern "C" void app_entry_redefinable(void) { @@ -295,10 +291,6 @@ extern "C" void app_entry_redefinable(void) cont_t s_cont __attribute__((aligned(16))); g_pcont = &s_cont; -#if (ERASE_CONFIG_METHOD == 2) - eboot_two_shots = 2; -#endif - /* Call the entry point of the SDK code. */ call_user_start(); } @@ -316,28 +308,12 @@ extern "C" void preinit (void) /* do nothing by default */ } -#if (ERASE_CONFIG_METHOD == 1) -extern "C" void __pinMode(uint8_t pin, uint8_t mode); -#endif - extern "C" void user_init(void) { struct rst_info *rtc_info_ptr = system_get_rst_info(); memcpy((void *) &resetInfo, (void *) rtc_info_ptr, sizeof(resetInfo)); uart_div_modify(0, UART_CLK_FREQ / (115200)); -#if (ERASE_CONFIG_METHOD == 1) - if (0 != resetInfo.reason) { // Not Power On - // 1st 3 lines are trying to silence or reduce the unreadable serial - // prints. Not sure of the effectivness. Need to reverify. - system_set_os_print(0); - U0IE = 0; - U1IE = 0; - __pinMode(1, INPUT_PULLUP); // Silence U0TXD - check_and_erase_config(); - } -#endif - init(); // in core_esp8266_wiring.c, inits hw regs and sdk timer initVariant(); diff --git a/cores/esp8266/erase_config.cpp b/cores/esp8266/erase_config.cpp index 67a0b4d21f..f9a6961c85 100644 --- a/cores/esp8266/erase_config.cpp +++ b/cores/esp8266/erase_config.cpp @@ -4,19 +4,20 @@ #include +#define ERASE_CONFIG_METHOD 1 + + #ifndef VAR_NAME_VALUE #define VALUE(x) __STRINGIFY(x) #define VAR_NAME_VALUE(var) #var " = " VALUE(var) #endif #pragma message(VAR_NAME_VALUE(ERASE_CONFIG_METHOD)) - - #if (ERASE_CONFIG_METHOD == 0) #undef ERASE_CONFIG_H #endif -#ifdef ERASE_CONFIG_H +#ifdef ERASE_CONFIG_H extern "C" { #include "user_interface.h" @@ -28,7 +29,7 @@ extern "C" { #pragma GCC error "Unsupported ERASE_CONFIG_METHOD" #endif -bool IRAM_MAYBE erase_config(const uint32_t flash_erase_mask) { +bool IRAM_MAYBE erase_config2(const uint32_t flash_erase_mask) { // This is really the active configured size uint32_t flash_size = flashchip->chip_size; uint32_t erase_mask = (flash_erase_mask & (uint32_t)ERASE_CONFIG_ALL_DATA); @@ -50,42 +51,73 @@ bool IRAM_MAYBE erase_config(const uint32_t flash_erase_mask) { } bool IRAM_MAYBE check_and_erase_config(void) { - // This should work since each element of the structure is a word. - eboot_command volatile * ebcmd = (eboot_command volatile *)RTC_MEM; - - // We want to run after an OTA has completed and the bin has been moved to its - // final resting place in flash. We want to catch the moment of the 1st boot - // of this new sketch. Then verify we have a valid erase option. - if (0U == ebcmd->magic && - 0U == ebcmd->crc32 && - ACTION_COPY_RAW == ebcmd->action && - ebcmd->args[4] == ebcmd->args[6] && - ebcmd->args[5] == ebcmd->args[7] && - ebcmd->args[4] == ~ebcmd->args[5] && - 0U == (ebcmd->args[4] & ~ERASE_CONFIG_ALL_DATA)) { - - uint32_t erase_flash_option = ebcmd->args[4]; - - // Make sure we don't repeat - for (size_t i=4; i<=7; i++) - ebcmd->args[i] = 0U; - - if (erase_flash_option) { + // This should work since each element of the structure is a word. + eboot_command volatile * ebcmd = (eboot_command volatile *)RTC_MEM; + + // We want to run after an OTA has completed and the bin has been moved to its + // final resting place in flash. We want to catch the moment of the 1st boot + // of this new sketch. Then verify we have a valid erase option. + if (0U == ebcmd->magic && + 0U == ebcmd->crc32 && + ACTION_COPY_RAW == ebcmd->action && + ebcmd->args[4] == ebcmd->args[6] && + ebcmd->args[5] == ebcmd->args[7] && + ebcmd->args[4] == ~ebcmd->args[5] && + 0U == (ebcmd->args[4] & ~ERASE_CONFIG_ALL_DATA)) { + + uint32_t erase_flash_option = ebcmd->args[4]; + + // Make sure we don't repeat + for (size_t i=4; i<=7; i++) + ebcmd->args[i] = 0U; + + if (erase_flash_option) { #if (ERASE_CONFIG_METHOD == 1) - erase_config(erase_flash_option); - system_restart(); - while(true){} + erase_config2(erase_flash_option); + system_restart(); + while(true){} #elif (ERASE_CONFIG_METHOD == 2) - return erase_config(erase_flash_option); + return erase_config2(erase_flash_option); #endif + } } - } - return true; + return true; } -#if (ERASE_CONFIG_METHOD == 2) //Another option -extern int eboot_two_shots; +#if (ERASE_CONFIG_METHOD == 1) +extern struct rst_info resetInfo; + +extern "C" void preinit (void) +{ + /* do nothing On power up */ + if (0 != resetInfo.reason) { + check_and_erase_config(); + } +} + + + +#elif (ERASE_CONFIG_METHOD == 2) //Another option +#include "cont.h" + +int eboot_two_shots __attribute__((section(".noinit"))); +extern cont_t* g_pcont; +extern "C" void call_user_start(); + +extern "C" void app_entry_redefinable(void) +{ + /* Allocate continuation context on this SYS stack, + and save pointer to it. */ + cont_t s_cont __attribute__((aligned(16))); + g_pcont = &s_cont; + + eboot_two_shots = 2; + + /* Call the entry point of the SDK code. */ + call_user_start(); +} + void ICACHE_RAM_ATTR dbg_log_SPIRead(uint32_t addr, void *dest, size_t size, int err) __attribute__((weak)); void ICACHE_RAM_ATTR dbg_log_SPIRead(uint32_t addr, void *dest, size_t size, int err) { @@ -102,19 +134,19 @@ typedef int (*fp_SPIRead_t)(uint32_t addr, void *dest, size_t size); constexpr fp_SPIRead_t real_SPIRead = (fp_SPIRead_t)ROM_SPIRead; int ICACHE_RAM_ATTR SPIRead(uint32_t addr, void *dest, size_t size) { - // The very 1st read that goes by is to get the config flash size from - // image header. The NONOS SDK will update flashchip->chip_size. Then, a - // verification read is performed. Before this read is passed on we erase - // config sectors. - if (eboot_two_shots) { - eboot_two_shots--; - if (0 == eboot_two_shots) - check_and_erase_config(); - } - - int err = real_SPIRead(addr, dest, size); - dbg_log_SPIRead(addr, dest, size, err); - return err; + // The very 1st read that goes by is to get the config flash size from + // image header. The NONOS SDK will update flashchip->chip_size. Then, a + // verification read is performed. Before this read is passed on we erase + // config sectors. + if (eboot_two_shots) { + eboot_two_shots--; + if (0 == eboot_two_shots) + check_and_erase_config(); + } + + int err = real_SPIRead(addr, dest, size); + dbg_log_SPIRead(addr, dest, size, err); + return err; } #endif diff --git a/libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino b/libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino new file mode 100644 index 0000000000..8d74f25c3c --- /dev/null +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino @@ -0,0 +1,314 @@ + +#include +#include +#include +#include +#include +#include + +#include "WifiHealth.h" + +#include +#if HEAP_STATIC_RESERVE_SIZE +#include +#else +#define EVLOG1(a) do{ (void)a; } while(false) +#endif +#if ENABLE_FLASH_STATS +#include +#endif + +#ifndef STASSID +#pragma message("Using default SSID: your-ssid") +#define STASSID "your-ssid" +#define STAPSK "your-password" +#endif + +const char* ssid = STASSID; +const char* password = STAPSK; + +/* Erase Config Options + ERASE_CONFIG_NONE + ERASE_CONFIG_EEPROM + ERASE_CONFIG_RF_CAL + ERASE_CONFIG_PERSISTANT + ERASE_CONFIG_BLANK_BIN + ERASE_CONFIG_SDK_DATA + ERASE_CONFIG_ALL_DATA +*/ +ERASE_CONFIG_MASK_t eraseConfigOption = ERASE_CONFIG_BLANK_BIN; + +#define String_F(a) String(F(a)) + + +// Make sure handler's never fall out of scope +WiFiEventHandler handler1; +WiFiEventHandler handler2; + +extern char **environ; +void setTimeTZ(const char *tz) { + char **old_environ = environ; + char * tzEnv[2] = { (char *)tz, NULL }; + environ = &tzEnv[0]; + tzset(); + environ = old_environ; +} + +void setTimeTZ(String& tz) { + setTimeTZ(tz.c_str() ); +} + +void setup() { + Serial.begin(115200); + delay(20); + Serial.println(); + Serial.println("Booting"); + + WiFi.persistent(false); // w/o this a flash write occurs at every boot + WiFi.mode(WIFI_OFF); + + String tz = F("TZ=PST+8PDT+7,M3.2.0M11.1.0"); + setTimeTZ(tz); + configTime(0, 0, "pool.ntp.org"); + + // Register wifi Event to control connection LED + handler1 = WiFi.onStationModeConnected([](WiFiEventStationModeConnected data) { + onWiFiConnected(data); + }); + handler2 = WiFi.onStationModeDisconnected([](WiFiEventStationModeDisconnected data) { + onWiFiDisconnected(data); + }); + + WifiUp = WiFi.isConnected(); + if (WifiUp) { + Serial.println(String_F("WiFi was already connected. We are now disconnecting.")); + WiFi.disconnect(); + WifiUp = false; + } + + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + while (WiFi.waitForConnectResult() != WL_CONNECTED) { + Serial.println("Connection Failed! Rebooting..."); + EVLOG1("Connection Failed! Rebooting..."); + delay(5000); + ESP.restart(); + } + EVLOG1("WiFi Up!"); + + // Port defaults to 8266 + // ArduinoOTA.setPort(8266); + + // Hostname defaults to esp8266-[ChipID] + // ArduinoOTA.setHostname("myesp8266"); + + // No authentication by default + // ArduinoOTA.setPassword("admin"); + + // Password can be set with it's md5 value as well + // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3 + // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3"); + + ArduinoOTA.onStart([]() { + String type; + if (ArduinoOTA.getCommand() == U_FLASH) { + type = "sketch"; + + Update.setEraseConfigOption(eraseConfigOption); + + } else { // U_FS + type = "filesystem"; + } + + // NOTE: if updating FS this would be the place to unmount FS using FS.end() + Serial.println("Start updating " + type); + }); + ArduinoOTA.onEnd([]() { + Serial.println("\nEnd"); + }); + ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { + Serial.printf("Progress: %u%%\r\n", (progress / (total / 100))); + }); + ArduinoOTA.onError([](ota_error_t error) { + Serial.printf("Error[%u]: ", error); + if (error == OTA_AUTH_ERROR) { + Serial.println("Auth Failed"); + } else if (error == OTA_BEGIN_ERROR) { + Serial.println("Begin Failed"); + } else if (error == OTA_CONNECT_ERROR) { + Serial.println("Connect Failed"); + } else if (error == OTA_RECEIVE_ERROR) { + Serial.println("Receive Failed"); + } else if (error == OTA_END_ERROR) { + Serial.println("End Failed"); + } + }); + ArduinoOTA.begin(); + Serial.println("Ready"); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + Serial.println(); + Serial.println(); + scheduleCheck.attach_ms(1000, updateWiFiStats); + + telnetAgentSetup(); +} + +void printHelp(Print& oStream) { + oStream.println(); + oStream.println(F("Hot key help:")); + oStream.println(); + oStream.println(F(" Erase Config Options")); + oStream.println(F(" 0 - ERASE_CONFIG_NONE")); + oStream.println(F(" 1 - ERASE_CONFIG_EEPROM")); + oStream.println(F(" 2 - ERASE_CONFIG_RF_CAL")); + oStream.println(F(" 3 - ERASE_CONFIG_PERSISTANT")); + oStream.println(F(" 4 - ERASE_CONFIG_BLANK_BIN")); + oStream.println(F(" 5 - ERASE_CONFIG_SDK_DATA")); + oStream.println(F(" 6 - ERASE_CONFIG_ALL_DATA")); + oStream.println(F(" 9 - print detailed sector map")); + oStream.println(); + oStream.println(F(" Other Options")); + oStream.println(F(" t - time information")); + oStream.println(F(" u - umm_info")); + oStream.println(F(" w - WiFi Stats")); + oStream.println(F(" R - Restart")); +#if ENABLE_FLASH_STATS + oStream.println(F(" f - flash Stats")); +#endif +#if HEAP_STATIC_RESERVE_SIZE + oStream.println(F(" m - static reserve memory information")); + oStream.println(F(" e - EvLog Report")); + oStream.println(F(" E - EvLog Clear")); +#endif + oStream.println(F(" ? - This help message")); + oStream.println(); +} + +void printTimes(Print& out) { + // #if (EVLOG_TIMESTAMP == EVLOG_TIMESTAMP_CLOCKCYCLES) + { + uint32_t fraction = esp_get_cycle_count(); + fraction /= clockCyclesPerMicrosecond(); + time_t gtime = (time_t)(fraction / 1000000U); + fraction %= 1000000; + const char *ts_fmt = PSTR("%s.%06u: ccount/%d"); + struct tm *tv = gmtime(>ime); + char buf[10]; + strftime(buf, sizeof(buf), "%T", tv); + out.printf_P(ts_fmt, buf, fraction, clockCyclesPerMicrosecond()); + out.println(); + } + // #elif (EVLOG_TIMESTAMP == EVLOG_TIMESTAMP_MICROS) + { + uint32_t fraction = micros(); + time_t gtime = (time_t)(fraction / 1000000U); + fraction %= 1000000; + const char *ts_fmt = PSTR("%s.%06u: micros()"); + struct tm *tv = gmtime(>ime); + char buf[10]; + strftime(buf, sizeof(buf), "%T", tv); + out.printf_P(ts_fmt, buf, fraction); + out.println(); + } + // #elif (EVLOG_TIMESTAMP == EVLOG_TIMESTAMP_MILLIS) + { + uint32_t fraction = millis(); + time_t gtime = (time_t)(fraction / 1000U); + fraction %= 1000U; + const char *ts_fmt = PSTR("%s.%03u: millis()"); + char buf[10]; + struct tm *tv = gmtime(>ime); + strftime(buf, sizeof(buf), "%T", tv); + out.printf_P(ts_fmt, buf, fraction); + out.println(); + } + // #endif +} + +int cmdLoop(Print& oStream, char inChar) { + switch (inChar) { + case 't': + printLocalTime(oStream); + oStream.println(); + printTimes(oStream); + oStream.println(); + break; +#if HEAP_STATIC_RESERVE_SIZE + case 'm': + oStream.printf_P(PSTR("umm_get_static_reserve_size %u"), umm_get_static_reserve_size()); + oStream.println(); + oStream.printf_P(PSTR("umm_get_static_reserve_addr %p"), umm_get_static_reserve_addr()); + oStream.println(); + oStream.println(); + break; +#endif + case '?': + printHelp(oStream); + break; + case 'u': + umm_info(NULL, true); + break; + +#if ENABLE_FLASH_STATS + case 'f': + printFlashStatsReport(oStream); + oStream.println(); + break; +#endif + case 'w': + printWiFiStats(oStream); + oStream.println(); + break; + case 'R': + oStream.println(F("Restart ...")); + delay(20); + WiFi.mode(WIFI_OFF); + ESP.restart(); + break; + +#if HEAP_STATIC_RESERVE_SIZE + case 'E': + evlog_restart(1); + oStream.println(F("EvLog restarted.")); + break; + case 'e': + evlogPrintReport(oStream); + oStream.println(); + break; +#endif + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + if (queueEraseConfig(inChar)) { + oStream.println(F("Erase config request queued. Press 'R' to process or start OTA Update.")); + } + oStream.println(); + break; + case '9': + printFlashEraseMap(oStream); + oStream.println(); + break; + default: + oStream.println(); + break; + } + return 0; +} + +void serialClientLoop(void) { + if (Serial.available() > 0) { + char inChar = Serial.read(); + cmdLoop(Serial, inChar); + } +} + +void loop() { + ArduinoOTA.handle(); + serialClientLoop(); + handleTelnetAgent(); +} diff --git a/libraries/ArduinoOTA/examples/OTAConfigErase/TelnetAgent.ino b/libraries/ArduinoOTA/examples/OTAConfigErase/TelnetAgent.ino new file mode 100644 index 0000000000..0edd0048f1 --- /dev/null +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/TelnetAgent.ino @@ -0,0 +1,104 @@ +#include + +//how many clients should be able to telnet to this ESP8266 +#define MAX_SRV_CLIENTS 2 +const unsigned telnetPort = 23; +#define STACK_PROTECTOR 512 // bytes + +WiFiServer server(telnetPort); +WiFiClient serverClients[MAX_SRV_CLIENTS]; +StreamString telnetOut; + +void telnetAgentSetup(void) { + //start server + server.begin(); + server.setNoDelay(true); +} + +void handleTelnetAgent(void) { + //check if there are any new clients + if (server.hasClient()) { + + //find free/disconnected spot + int i; + for (i = 0; i < MAX_SRV_CLIENTS; i++) + if (!serverClients[i]) { // equivalent to !serverClients[i].connected() + serverClients[i] = server.available(); + Serial.print("New client: index "); + Serial.print(i); + break; + } + + //no free/disconnected spot so reject + if (i == MAX_SRV_CLIENTS) { + server.available().println("busy"); + // hints: server.available() is a WiFiClient with short-term scope + // when out of scope, a WiFiClient will + // - flush() - all data will be sent + // - stop() - automatically too + Serial.printf("server is busy with %d active connections\n", MAX_SRV_CLIENTS); + } + } + + //check TCP clients for data +#if 1 + for (int i = 0; i < MAX_SRV_CLIENTS; i++) + while (serverClients[i].available()) { + int hotKey = serverClients[i].read(); + + if ((hotKey > 0 && hotKey >' ') || hotKey == '\r') + cmdLoop(telnetOut, hotKey); + } + +#else + for (int i = 0; i < MAX_SRV_CLIENTS; i++) + while (serverClients[i].available()) { + size_t maxToSerial = 1; //std::min(serverClients[i].available(), Serial.availableForWrite()); + maxToSerial = std::min(maxToSerial, (size_t)STACK_PROTECTOR); + uint8_t buf[maxToSerial]; + size_t tcp_got = serverClients[i].read(buf, maxToSerial); + size_t serial_sent = 1; //Serial.write(buf, tcp_got); + cmdLoop(telnetOut, *buf); + if (serial_sent != maxToSerial) { + Serial.printf("len mismatch: available:%zd tcp-read:%zd serial-write:%zd\n", maxToSerial, tcp_got, serial_sent); + } + } +#endif + + // determine maximum output size "fair TCP use" + // client.availableForWrite() returns 0 when !client.connected() + size_t maxToTcp = 0; + for (int i = 0; i < MAX_SRV_CLIENTS; i++) + if (serverClients[i]) { + size_t afw = serverClients[i].availableForWrite(); + if (afw) { + if (!maxToTcp) { + maxToTcp = afw; + } else { + maxToTcp = std::min(maxToTcp, afw); + } + } else { + // warn but ignore congested clients + Serial.println("one client is congested"); + } + } + + //check responce buffer telnetOut + size_t len = std::min((size_t)telnetOut.available(), maxToTcp); + len = std::min(len, (size_t)STACK_PROTECTOR); + if (len) { + uint8_t sbuf[len]; + size_t serial_got = telnetOut.readBytes(sbuf, len); + // push UART data to all connected telnet clients + for (int i = 0; i < MAX_SRV_CLIENTS; i++) + // if client.availableForWrite() was 0 (congested) + // and increased since then, + // ensure write space is sufficient: + if (serverClients[i].availableForWrite() >= serial_got) { + size_t tcp_sent = serverClients[i].write(sbuf, serial_got); + if (tcp_sent != len) { + Serial.printf("len mismatch: available:%zd serial-read:%zd tcp-write:%zd\n", len, serial_got, tcp_sent); + } + } + } +} diff --git a/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.h b/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.h new file mode 100644 index 0000000000..775f01d6c9 --- /dev/null +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.h @@ -0,0 +1,48 @@ +#ifndef WIFI_HEALTH_H +#define WIFI_HEALTH_H + +bool printLocalTime(Print& oPrint, time_t timeIn = 0, const char *fmt = "%I:%M:%S %p %Z, %a %b %d, %Y"); +bool printLocalTime(Print& oPrint, time_t timeIn, const char *fmt); +extern bool WifiUp; + +//static unsigned long last_garp = 0; + +typedef struct _WIFI_HEALTH { + int32_t connected_count; + int32_t rssi_sum; + uint32_t rssi_count; + time_t gtime_adjust; + time_t connected_time; + time_t disconnected_time; + time_t uptime_sum; + time_t uptime_max; + time_t uptime_min; + uint8 bssid[6]; + uint8_t channel; + int8_t rssi_max; + int8_t rssi_min; + int8_t rssi; +} wifi_health_t; +extern wifi_health_t wifiHealth; + + +#ifndef MAX_CONNECTION_LOST_TIME_LOG +#define MAX_CONNECTION_LOST_TIME_LOG (8) +#endif +typedef struct _WIFI_DISCONNECT_LOG { + time_t time; + uint8_t channel; + uint8_t reason; + int8_t rssi; +} WiFiDisconnectLog_t; +extern WiFiDisconnectLog_t wifi_disconnect_log[MAX_CONNECTION_LOST_TIME_LOG]; + +// void updateWiFiStats(void); +// bool printWiFiStats(Print& oStream); +// void onWiFiConnected(WiFiEventStationModeConnected data); +// void onWiFiDisconnected(WiFiEventStationModeDisconnected data); + + +extern Ticker scheduleCheck; + +#endif diff --git a/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.ino b/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.ino new file mode 100644 index 0000000000..dc50d66c81 --- /dev/null +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.ino @@ -0,0 +1,305 @@ + +wifi_health_t wifiHealth = {0, 0, 0, 0, 0, 0, 0, 0, ~(time_t)0, "", 0, INT8_MIN, INT8_MAX, 0}; +WiFiDisconnectLog_t wifi_disconnect_log[MAX_CONNECTION_LOST_TIME_LOG] = {0, 0, 0, 0}; +Ticker scheduleCheck; +bool WifiUp = false; + + +bool printLocalTime(Print& oPrint, time_t timeIn, const char *fmt) { + time_t gtime = timeIn; + if (0 == gtime) + time(>ime); + + char ephemeralBuffer[64]; + if (strftime(ephemeralBuffer, sizeof(ephemeralBuffer), fmt, localtime(>ime)) > 0) { + oPrint.print(ephemeralBuffer); + return true; + } + return false; +} + +bool printUpTime(Print& oPrint, time_t timeIn) { + time_t gtime; + if (timeIn) + gtime = timeIn; + else + gtime = (time_t)(micros64() / 1000000); + + char ephemeralBuffer[64]; + struct tm *tv = gmtime(>ime); + if (strftime(ephemeralBuffer, sizeof(ephemeralBuffer), "%T", tv) > 0) { + if (tv->tm_yday) + oPrint.print(String((tv->tm_yday)) + " day" + ((tv->tm_yday == 1) ? " " : "s ") ); + + oPrint.print(ephemeralBuffer); + return true; + } + return false; +} + +#define StringF(a) String(F(a)) +String getWiFiDisconnectReasonString(uint32_t reason) { + const __FlashStringHelper *r; + switch (reason) { + case WIFI_DISCONNECT_REASON_UNSPECIFIED: + r = F("UNSPECIFIED"); + break; + + case WIFI_DISCONNECT_REASON_AUTH_EXPIRE: + r = F("AUTH_EXPIRE"); + break; + + case WIFI_DISCONNECT_REASON_AUTH_LEAVE: + r = F("AUTH_LEAVE"); + break; + + case WIFI_DISCONNECT_REASON_ASSOC_EXPIRE: + r = F("ASSOC_EXPIRE"); + break; + + case WIFI_DISCONNECT_REASON_ASSOC_TOOMANY: + r = F("ASSOC_TOOMANY"); + break; + + case WIFI_DISCONNECT_REASON_NOT_AUTHED: + r = F("NOT_AUTHED"); + break; + + case WIFI_DISCONNECT_REASON_NOT_ASSOCED: + r = F("NOT_ASSOCED"); + break; + + case WIFI_DISCONNECT_REASON_ASSOC_LEAVE: + r = F("ASSOC_LEAVE"); + break; + + case WIFI_DISCONNECT_REASON_ASSOC_NOT_AUTHED: + r = F("ASSOC_NOT_AUTHED"); + break; + + case WIFI_DISCONNECT_REASON_DISASSOC_PWRCAP_BAD: + r = F("DISASSOC_PWRCAP_BAD"); + break; + + case WIFI_DISCONNECT_REASON_DISASSOC_SUPCHAN_BAD: + r = F("DISASSOC_SUPCHAN_BAD"); + break; + + case WIFI_DISCONNECT_REASON_IE_INVALID: + r = F("IE_INVALID"); + break; + + case WIFI_DISCONNECT_REASON_MIC_FAILURE: + r = F("MIC_FAILURE"); + break; + + case WIFI_DISCONNECT_REASON_4WAY_HANDSHAKE_TIMEOUT: + r = F("4WAY_HANDSHAKE_TIMEOUT"); + break; + + case WIFI_DISCONNECT_REASON_GROUP_KEY_UPDATE_TIMEOUT: + r = F("GROUP_KEY_UPDATE_TIMEOUT"); + break; + + case WIFI_DISCONNECT_REASON_IE_IN_4WAY_DIFFERS: + r = F("IE_IN_4WAY_DIFFERS"); + break; + + case WIFI_DISCONNECT_REASON_GROUP_CIPHER_INVALID: + r = F("GROUP_CIPHER_INVALID"); + break; + + case WIFI_DISCONNECT_REASON_PAIRWISE_CIPHER_INVALID: + r = F("PAIRWISE_CIPHER_INVALID"); + break; + + case WIFI_DISCONNECT_REASON_AKMP_INVALID: + r = F("AKMP_INVALID"); + break; + + case WIFI_DISCONNECT_REASON_UNSUPP_RSN_IE_VERSION: + r = F("UNSUPP_RSN_IE_VERSION"); + break; + + case WIFI_DISCONNECT_REASON_INVALID_RSN_IE_CAP: + r = F("INVALID_RSN_IE_CAP"); + break; + + case WIFI_DISCONNECT_REASON_802_1X_AUTH_FAILED: + r = F("802_1X_AUTH_FAILED"); + break; + + case WIFI_DISCONNECT_REASON_CIPHER_SUITE_REJECTED: + r = F("CIPHER_SUITE_REJECTED"); + break; + + case WIFI_DISCONNECT_REASON_BEACON_TIMEOUT: + r = F("BEACON_TIMEOUT"); + break; + + case WIFI_DISCONNECT_REASON_NO_AP_FOUND: + r = F("NO_AP_FOUND"); + break; + + case WIFI_DISCONNECT_REASON_AUTH_FAIL: + r = F("AUTH_FAIL"); + break; + + case WIFI_DISCONNECT_REASON_ASSOC_FAIL: + r = F("ASSOC_FAIL"); + break; + + case WIFI_DISCONNECT_REASON_HANDSHAKE_TIMEOUT: + r = F("HANDSHAKE_TIMEOUT"); + break; + + default: + return String(F("Unknown: ")) + String(reason, HEX); + } + return String(r); +} + +char getPhyModeChar(WiFiPhyMode_t i) { + switch (i) { + case WIFI_PHY_MODE_11B: + return 'b'; // = 1 + case WIFI_PHY_MODE_11G: + return 'g'; // = 2, + case WIFI_PHY_MODE_11N: + return 'n'; // = 3, + default: + break; + } + return '?'; +} + +void updateWiFiStats(void) { + int32_t rssi = WiFi.RSSI(); + if (rssi < 10) { + wifiHealth.rssi_max = max((int8_t)rssi, wifiHealth.rssi_max); + wifiHealth.rssi_min = min((int8_t)rssi, wifiHealth.rssi_min); + wifiHealth.rssi = rssi; + wifiHealth.rssi_sum += rssi; + wifiHealth.rssi_count++; + } +} + +void onWiFiConnected(WiFiEventStationModeConnected data) { + (void) data; + wifiHealth.channel = data.channel; + memcpy(wifiHealth.bssid, data.bssid, sizeof(wifiHealth.bssid)); + wifiHealth.connected_count++; + wifiHealth.connected_time = (time_t)(micros64() / 1000000); + wifiHealth.rssi = WiFi.RSSI(); + WifiUp = true; +// wifiLed.strobe(0); +// wifiLed.on(); + +// last_garp = millis(); // init for doGarp(NULL); +} + +void onWiFiDisconnected(WiFiEventStationModeDisconnected data) { + (void) data; + WifiUp = false; +// wifiLed.strobe(0); +// wifiLed.off(); + time_t gtime; + time(>ime); + time_t stime = (time_t)(micros64() / 1000000); + wifiHealth.disconnected_time = stime; + wifiHealth.gtime_adjust = gtime - stime; + + if (wifiHealth.connected_time) { + size_t last = wifiHealth.connected_count % MAX_CONNECTION_LOST_TIME_LOG; + wifi_disconnect_log[last].time = stime; // Note, 1st entry is at [1]. + wifi_disconnect_log[last].reason = data.reason; + wifi_disconnect_log[last].channel = wifiHealth.channel; + wifi_disconnect_log[last].rssi = wifiHealth.rssi; + time_t uptime = stime - wifiHealth.connected_time; + wifiHealth.uptime_sum += uptime; + wifiHealth.uptime_max = max(wifiHealth.uptime_max, uptime); + if (~(time_t)0 != wifiHealth.uptime_min) { + wifiHealth.uptime_min = min(wifiHealth.uptime_min, uptime); + } else { + wifiHealth.uptime_min = uptime; + } + } +} + + +bool printWiFiStats(Print& oStream) { + bool bSuccess = false; + + if (WiFi.status() == WL_CONNECTED) { + oStream.println(String_F("\nWiFi connected: '") + WiFi.SSID() + "'"); + oStream.println(String_F(" SDK Version: ") + String(ESP.getSdkVersion()) ); + oStream.printf_P( PSTR(" BSSID: %02X:%02X:%02X:%02X:%02X:%02X\r\n"), + wifiHealth.bssid[0], wifiHealth.bssid[1], wifiHealth.bssid[2], + wifiHealth.bssid[3], wifiHealth.bssid[4], wifiHealth.bssid[5] ); + oStream.println(String_F(" PHY Mode: 802.11") + (getPhyModeChar(WiFi.getPhyMode())) ); + oStream.println(String_F(" Channel: ") + (WiFi.channel()) ); + oStream.println(String_F(" RSSI: ") + (WiFi.RSSI()) ); + oStream.println(String_F(" MAX: ") + (wifiHealth.rssi_max) ); + oStream.println(String_F(" MIN: ") + (wifiHealth.rssi_min) ); + if (wifiHealth.rssi_count) + oStream.println(String_F(" AVG: ") + (wifiHealth.rssi_sum/(int32_t)wifiHealth.rssi_count) ); + + oStream.println(String_F(" sample count: ") + (wifiHealth.rssi_count) ); + if (0 != wifiHealth.connected_time) { + oStream.print(String_F(" Connection Uptime: ")); + time_t uptime = (time_t)(micros64() / 1000000); + uptime -= wifiHealth.connected_time; + printUpTime(oStream, uptime); + oStream.println(); + + if (wifiHealth.uptime_max) { + oStream.print(String_F(" MAX: ")); + printUpTime(oStream, wifiHealth.uptime_max); + oStream.println(); + } + if (~(time_t)0 != wifiHealth.uptime_min) { + oStream.print(String_F(" MIN: ")); + printUpTime(oStream, wifiHealth.uptime_min); + oStream.println(); + } + oStream.print(String_F(" AVG: ")); + uptime += wifiHealth.uptime_sum; + uptime /= (decltype(uptime))wifiHealth.connected_count; + printUpTime(oStream, uptime); + oStream.println(); + oStream.println(String_F(" Reconnects: ") + (wifiHealth.connected_count - 1) ); + + if (wifiHealth.connected_count > 1) { + oStream.println(String_F(" Recent Disconnect times:")); + ssize_t back_count = wifiHealth.connected_count; + for (ssize_t i=1; i <= MAX_CONNECTION_LOST_TIME_LOG; i++) { + back_count -= 1; + if (back_count < 1) + break; + + ssize_t iLog = back_count % MAX_CONNECTION_LOST_TIME_LOG; + oStream.print(String_F(" ")); + printLocalTime(oStream, wifi_disconnect_log[iLog].time + wifiHealth.gtime_adjust); + oStream.println(); + oStream.println(String_F(" Reason: ") + getWiFiDisconnectReasonString(wifi_disconnect_log[iLog].reason)); + oStream.println(String_F(" Channel: ") + (wifi_disconnect_log[iLog].channel) ); + oStream.println(String_F(" RSSI: ") + (wifi_disconnect_log[iLog].rssi) ); + } + } + } + + oStream.println(String_F(" IP Address: ") + (WiFi.localIP().toString()) ); + oStream.println(String_F(" Network Mask: ") + (WiFi.subnetMask().toString()) ); + oStream.println(String_F(" Gateway: ") + (WiFi.gatewayIP().toString()) ); + oStream.println(String_F(" DNS1: ") + (WiFi.dnsIP(0).toString()) ); + oStream.println(String_F(" DNS2: ") + (WiFi.dnsIP(1).toString()) ); + + + bSuccess = true; + + } else { + oStream.println(String_F("WiFi not connected.")); + } + + return bSuccess; +} diff --git a/libraries/ArduinoOTA/examples/OTAConfigErase/rtc.ino b/libraries/ArduinoOTA/examples/OTAConfigErase/rtc.ino new file mode 100644 index 0000000000..dda13df973 --- /dev/null +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/rtc.ino @@ -0,0 +1,74 @@ +#include +#include + +#ifdef ERASE_CONFIG_H +void printFlashEraseMap(Print& out) { + out.printf_P(PSTR( + "_________________________________________________________________________________________\r\n" + "| Address of sectors | | | | | |\r\n" + "| at the end of the Flash | ...FB000 | ...FC000 | ...FD000 | ...FE000 | ...FF000 |\r\n" + "|___________________________|___________|___________|___________|___________|___________|\r\n" + "|__Bit_number_for_Mask______|_____4_____|_____3_____|_____2_____|_____1_____|_____0_____|\r\n" + "| | | RF_CAL | SDK Parameter Area |\r\n" + "| Overlay at RF init | | PHY INIT | | | |\r\n" + "| Persistant data | | | | SSID/PW | |\r\n" + "| User storage | EEPROM | | | | |\r\n" + "| Often shown downloaded | | BLANK.BIN | | BLANK.BIN | |\r\n" + "|___________________________|___________|___________|___________|___________|___________|\r\n" + "| 0 ERASE_CONFIG_NONE | | | | | |\r\n" + "| 1 ERASE_CONFIG_EEPROM | BIT(4) | | | | |\r\n" + "| 2 ERASE_CONFIG_RF_CAL | | BIT(3) | | | |\r\n" + "| 3 ERASE_CONFIG_PERSISTANT | | | | BIT(1) | |\r\n" + "| 4 ERASE_CONFIG_BLANK_BIN | | BIT(3) | | BIT(1) | |\r\n" + "| 5 ERASE_CONFIG_SDK_DATA | | BIT(3) | BIT(2) | BIT(1) | BIT(0) |\r\n" + "| 6 ERASE_CONFIG_ALL_DATA | BIT(4) | BIT(3) | BIT(2) | BIT(1) | BIT(0) |\r\n" + "|___________________________|___________|___________|___________|___________|___________|\r\n" +)); +} + +bool queueEraseConfig(int hotKey) { + switch (hotKey) { + case '0': + requestEraseConfig(ERASE_CONFIG_NONE); + break; + case '1': + requestEraseConfig(ERASE_CONFIG_EEPROM); + break; + case '2': + requestEraseConfig(ERASE_CONFIG_RF_CAL); + break; + case '3': + requestEraseConfig(ERASE_CONFIG_PERSISTANT); + break; + case '4': + requestEraseConfig(ERASE_CONFIG_BLANK_BIN); + break; + case '5': + requestEraseConfig(ERASE_CONFIG_SDK_DATA); + break; + case '6': + requestEraseConfig(ERASE_CONFIG_ALL_DATA); + break; + default: + return false; + } + return true; +} + +void requestEraseConfig(uint32_t mask) { + eraseConfigOption = (ERASE_CONFIG_MASK_t)mask; // Save in case they do an OTA instead of a restart. + + eboot_command volatile * ebcmd = (eboot_command volatile *)RTC_MEM; + + for (size_t i = 0; i < sizeof(eboot_command) / sizeof(uint32_t) ; i++) + RTC_MEM[i] = 0U; + + // Finish fake post OTA flash copy complete state + ebcmd->args[4] = mask; + ebcmd->args[5] = ~mask; + ebcmd->args[6] = mask; + ebcmd->args[7] = ~mask; + ebcmd->action = ACTION_COPY_RAW; +} + +#endif From bfb21b1518f6edfb76dbdf828134ed27f896ffd2 Mon Sep 17 00:00:00 2001 From: M Hightower <27247790+mhightower83@users.noreply.github.com> Date: Mon, 30 Dec 2019 20:14:23 -0800 Subject: [PATCH 05/24] A proof of concept for handling Erase WiFi Setting after OTA Issue: Sometimes when an ESP8266 is reflashed/upgraded the WiFi does not work. Then a serial flash with Erase Flash with WiFi setting is recommended. I have seen this more often when changing SDK by OTA. We don't have an erase WiFi for OTA. The PR tries to present a proof of concept solution. Actually it describes two different methods. There are 3 cases to consider when the firmware is updated by OTA. The new firmware: 1. has the same Flash Configuration as the old. 2. has a larger Flash Configuration than the old. 3. has a smaller Flash Configuration then the old. In theory after an OTA and before a restart, the flash could be erased for _case 1_. _Case 2_ is a problem because the size exceeds what the SPIEraseSector expects and fails. We have to wait for a restart, after which the SDK will have updated the values in `flashchip`. _Case 3_ is potentially unsafe because we could be erasing the code that is running. At app_entry() `flashchip` properties appear to be reset to a default 4MByte flash. Even an ESP8285 reported 4MByte. The value of `flashchip->chip_size` was changed at the 2nd SPIRead. The 1st read was to address 0, 4 bytes. To erase the flash WiFi area, I choose to wait until the SDK has finished its adjustments to the `flashchip` structure. Then to begin erasing WiFi sectors. I implemented two methods: 1. The first runs after the SDK calls `user_init()` and flash code execution is available (No IRAM needed), but restarts to be sure the SDK does not get confused about its sectors being erased. 2. The 2nd method runs as early as possible required IRAM. The sectors are erased before that 2nd read is processed. The SDK allowing the SDK to start off thinking the sectors were blank at boot. I also added an example/test sketch for exercising the feature. It is OTAEraseConfig. --- cores/esp8266/erase_config.cpp | 42 ++++++++++++++++++++++++++++++++++ cores/esp8266/erase_config.h | 3 ++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/cores/esp8266/erase_config.cpp b/cores/esp8266/erase_config.cpp index f9a6961c85..d66f5deeef 100644 --- a/cores/esp8266/erase_config.cpp +++ b/cores/esp8266/erase_config.cpp @@ -3,6 +3,48 @@ #include #include +/* + +Issue: Sometimes when an ESP8266 is reflashed/upgraded the WiFi does not work. +Then a serial flash with Erase Flash with WiFi setting is recommended. I have +seen this more often when changing SDK by OTA. We don't have an erase WiFi for +OTA. + +The PR tries to present a proof of concept solution. Actually it describes two +different methods. + +There are 3 cases to consider when the firmware is updated by OTA. The new +firmware: +1. has the same Flash Configuration as the old. +2. has a larger Flash Configuration than the old. +3. has a smaller Flash Configuration then the old. + +In theory after an OTA and before a restart, the flash could be erased for +_case 1_. _Case 2_ is a problem because the size exceeds what the SPIEraseSector +expects and fails. We have to wait for a restart, after which the SDK will have +updated the values in `flashchip`. _Case 3_ is potentially unsafe because we +could be erasing the code that is running. + +At app_entry() `flashchip` properties appear to be reset to a default 4MByte +flash. Even an ESP8285 reported 4MByte. The value of `flashchip->chip_size` was +changed at the 2nd SPIRead. The 1st read was to address 0, 4 bytes. + +To erase the flash WiFi area, I choose to wait until the SDK has finished its +adjustments to the `flashchip` structure. Then to begin erasing WiFi sectors. + +I implemented two methods: +1. The first runs after the SDK calls `user_init()` and flash code execution is +available (No IRAM needed), but restarts to be sure the SDK does not get +confused about its sectors being erased. +2. The 2nd method runs as early as possible required IRAM. The sectors are +erased before that 2nd read is processed. The SDK allowing the SDK to start off +thinking the sectors were blank at boot. + +I also added an example/test sketch for exercising the feature. It is +OTAEraseConfig. + + +*/ #define ERASE_CONFIG_METHOD 1 diff --git a/cores/esp8266/erase_config.h b/cores/esp8266/erase_config.h index 525c60f02c..09f9c629d7 100644 --- a/cores/esp8266/erase_config.h +++ b/cores/esp8266/erase_config.h @@ -14,7 +14,8 @@ typedef enum ERASE_CONFIG { // to store only 128 bytes. // // Mapping of Sectors at end of Flash, adapted for the Arduino ESP8266. -// Defaults to using the IDE configured flash size for placement +// The IDE configured flash size defines where the actual last sector used by +// the SDK is located. //_______________________________________________________________________________________ //_Bit_number_for_Mask______|_____4_____|_____3_____|_____2_____|_____1_____|_____0_____| // | | RF_CAL | SDK Parameter Area | From e51ab08a524b5189c1693c0bf4bc857984126255 Mon Sep 17 00:00:00 2001 From: M Hightower <27247790+mhightower83@users.noreply.github.com> Date: Mon, 30 Dec 2019 21:27:51 -0800 Subject: [PATCH 06/24] Addressed Tavis CI style issues. --- .../OTAConfigErase/OTAConfigErase.ino | 6 +- .../examples/OTAConfigErase/TelnetAgent.ino | 11 +- .../examples/OTAConfigErase/WifiHealth.ino | 117 +++++++++--------- .../examples/OTAConfigErase/rtc.ino | 45 +++---- 4 files changed, 92 insertions(+), 87 deletions(-) diff --git a/libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino b/libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino index 8d74f25c3c..0bea82b959 100644 --- a/libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino @@ -55,7 +55,7 @@ void setTimeTZ(const char *tz) { } void setTimeTZ(String& tz) { - setTimeTZ(tz.c_str() ); + setTimeTZ(tz.c_str()); } void setup() { @@ -115,7 +115,7 @@ void setup() { type = "sketch"; Update.setEraseConfigOption(eraseConfigOption); - + } else { // U_FS type = "filesystem"; } @@ -255,7 +255,7 @@ int cmdLoop(Print& oStream, char inChar) { printFlashStatsReport(oStream); oStream.println(); break; -#endif +#endif case 'w': printWiFiStats(oStream); oStream.println(); diff --git a/libraries/ArduinoOTA/examples/OTAConfigErase/TelnetAgent.ino b/libraries/ArduinoOTA/examples/OTAConfigErase/TelnetAgent.ino index 0edd0048f1..8339a36254 100644 --- a/libraries/ArduinoOTA/examples/OTAConfigErase/TelnetAgent.ino +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/TelnetAgent.ino @@ -18,7 +18,7 @@ void telnetAgentSetup(void) { void handleTelnetAgent(void) { //check if there are any new clients if (server.hasClient()) { - + //find free/disconnected spot int i; for (i = 0; i < MAX_SRV_CLIENTS; i++) @@ -28,7 +28,7 @@ void handleTelnetAgent(void) { Serial.print(i); break; } - + //no free/disconnected spot so reject if (i == MAX_SRV_CLIENTS) { server.available().println("busy"); @@ -45,11 +45,12 @@ void handleTelnetAgent(void) { for (int i = 0; i < MAX_SRV_CLIENTS; i++) while (serverClients[i].available()) { int hotKey = serverClients[i].read(); - - if ((hotKey > 0 && hotKey >' ') || hotKey == '\r') + + if ((hotKey > 0 && hotKey >' ') || hotKey == '\r') { cmdLoop(telnetOut, hotKey); + } } - + #else for (int i = 0; i < MAX_SRV_CLIENTS; i++) while (serverClients[i].available()) { diff --git a/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.ino b/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.ino index dc50d66c81..d65b6e51ef 100644 --- a/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.ino +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.ino @@ -7,8 +7,9 @@ bool WifiUp = false; bool printLocalTime(Print& oPrint, time_t timeIn, const char *fmt) { time_t gtime = timeIn; - if (0 == gtime) + if (0 == gtime) { time(>ime); + } char ephemeralBuffer[64]; if (strftime(ephemeralBuffer, sizeof(ephemeralBuffer), fmt, localtime(>ime)) > 0) { @@ -20,16 +21,18 @@ bool printLocalTime(Print& oPrint, time_t timeIn, const char *fmt) { bool printUpTime(Print& oPrint, time_t timeIn) { time_t gtime; - if (timeIn) + if (timeIn) { gtime = timeIn; - else + } else { gtime = (time_t)(micros64() / 1000000); + } char ephemeralBuffer[64]; struct tm *tv = gmtime(>ime); if (strftime(ephemeralBuffer, sizeof(ephemeralBuffer), "%T", tv) > 0) { - if (tv->tm_yday) + if (tv->tm_yday) { oPrint.print(String((tv->tm_yday)) + " day" + ((tv->tm_yday == 1) ? " " : "s ") ); + } oPrint.print(ephemeralBuffer); return true; @@ -185,45 +188,45 @@ void updateWiFiStats(void) { } void onWiFiConnected(WiFiEventStationModeConnected data) { - (void) data; - wifiHealth.channel = data.channel; - memcpy(wifiHealth.bssid, data.bssid, sizeof(wifiHealth.bssid)); - wifiHealth.connected_count++; - wifiHealth.connected_time = (time_t)(micros64() / 1000000); - wifiHealth.rssi = WiFi.RSSI(); - WifiUp = true; -// wifiLed.strobe(0); -// wifiLed.on(); - -// last_garp = millis(); // init for doGarp(NULL); + (void) data; + wifiHealth.channel = data.channel; + memcpy(wifiHealth.bssid, data.bssid, sizeof(wifiHealth.bssid)); + wifiHealth.connected_count++; + wifiHealth.connected_time = (time_t)(micros64() / 1000000); + wifiHealth.rssi = WiFi.RSSI(); + WifiUp = true; + + // wifiLed.strobe(0); + // wifiLed.on(); + // last_garp = millis(); // init for doGarp(NULL); } void onWiFiDisconnected(WiFiEventStationModeDisconnected data) { - (void) data; - WifiUp = false; -// wifiLed.strobe(0); -// wifiLed.off(); - time_t gtime; - time(>ime); - time_t stime = (time_t)(micros64() / 1000000); - wifiHealth.disconnected_time = stime; - wifiHealth.gtime_adjust = gtime - stime; - - if (wifiHealth.connected_time) { - size_t last = wifiHealth.connected_count % MAX_CONNECTION_LOST_TIME_LOG; - wifi_disconnect_log[last].time = stime; // Note, 1st entry is at [1]. - wifi_disconnect_log[last].reason = data.reason; - wifi_disconnect_log[last].channel = wifiHealth.channel; - wifi_disconnect_log[last].rssi = wifiHealth.rssi; - time_t uptime = stime - wifiHealth.connected_time; - wifiHealth.uptime_sum += uptime; - wifiHealth.uptime_max = max(wifiHealth.uptime_max, uptime); - if (~(time_t)0 != wifiHealth.uptime_min) { - wifiHealth.uptime_min = min(wifiHealth.uptime_min, uptime); - } else { - wifiHealth.uptime_min = uptime; - } + (void) data; + WifiUp = false; + // wifiLed.strobe(0); + // wifiLed.off(); + time_t gtime; + time(>ime); + time_t stime = (time_t)(micros64() / 1000000); + wifiHealth.disconnected_time = stime; + wifiHealth.gtime_adjust = gtime - stime; + + if (wifiHealth.connected_time) { + size_t last = wifiHealth.connected_count % MAX_CONNECTION_LOST_TIME_LOG; + wifi_disconnect_log[last].time = stime; // Note, 1st entry is at [1]. + wifi_disconnect_log[last].reason = data.reason; + wifi_disconnect_log[last].channel = wifiHealth.channel; + wifi_disconnect_log[last].rssi = wifiHealth.rssi; + time_t uptime = stime - wifiHealth.connected_time; + wifiHealth.uptime_sum += uptime; + wifiHealth.uptime_max = max(wifiHealth.uptime_max, uptime); + if (~(time_t)0 != wifiHealth.uptime_min) { + wifiHealth.uptime_min = min(wifiHealth.uptime_min, uptime); + } else { + wifiHealth.uptime_min = uptime; } + } } @@ -232,19 +235,19 @@ bool printWiFiStats(Print& oStream) { if (WiFi.status() == WL_CONNECTED) { oStream.println(String_F("\nWiFi connected: '") + WiFi.SSID() + "'"); - oStream.println(String_F(" SDK Version: ") + String(ESP.getSdkVersion()) ); - oStream.printf_P( PSTR(" BSSID: %02X:%02X:%02X:%02X:%02X:%02X\r\n"), - wifiHealth.bssid[0], wifiHealth.bssid[1], wifiHealth.bssid[2], - wifiHealth.bssid[3], wifiHealth.bssid[4], wifiHealth.bssid[5] ); - oStream.println(String_F(" PHY Mode: 802.11") + (getPhyModeChar(WiFi.getPhyMode())) ); - oStream.println(String_F(" Channel: ") + (WiFi.channel()) ); - oStream.println(String_F(" RSSI: ") + (WiFi.RSSI()) ); + oStream.println(String_F(" SDK Version: ") + String(ESP.getSdkVersion())); + oStream.printf_P(PSTR(" BSSID: %02X:%02X:%02X:%02X:%02X:%02X\r\n"), + wifiHealth.bssid[0], wifiHealth.bssid[1], wifiHealth.bssid[2], + wifiHealth.bssid[3], wifiHealth.bssid[4], wifiHealth.bssid[5]); + oStream.println(String_F(" PHY Mode: 802.11") + (getPhyModeChar(WiFi.getPhyMode()))); + oStream.println(String_F(" Channel: ") + (WiFi.channel())); + oStream.println(String_F(" RSSI: ") + (WiFi.RSSI())); oStream.println(String_F(" MAX: ") + (wifiHealth.rssi_max) ); oStream.println(String_F(" MIN: ") + (wifiHealth.rssi_min) ); if (wifiHealth.rssi_count) - oStream.println(String_F(" AVG: ") + (wifiHealth.rssi_sum/(int32_t)wifiHealth.rssi_count) ); + oStream.println(String_F(" AVG: ") + (wifiHealth.rssi_sum/(int32_t)wifiHealth.rssi_count)); - oStream.println(String_F(" sample count: ") + (wifiHealth.rssi_count) ); + oStream.println(String_F(" sample count: ") + (wifiHealth.rssi_count)); if (0 != wifiHealth.connected_time) { oStream.print(String_F(" Connection Uptime: ")); time_t uptime = (time_t)(micros64() / 1000000); @@ -267,12 +270,12 @@ bool printWiFiStats(Print& oStream) { uptime /= (decltype(uptime))wifiHealth.connected_count; printUpTime(oStream, uptime); oStream.println(); - oStream.println(String_F(" Reconnects: ") + (wifiHealth.connected_count - 1) ); + oStream.println(String_F(" Reconnects: ") + (wifiHealth.connected_count - 1)); if (wifiHealth.connected_count > 1) { oStream.println(String_F(" Recent Disconnect times:")); ssize_t back_count = wifiHealth.connected_count; - for (ssize_t i=1; i <= MAX_CONNECTION_LOST_TIME_LOG; i++) { + for (ssize_t i = 1; i <= MAX_CONNECTION_LOST_TIME_LOG; i++) { back_count -= 1; if (back_count < 1) break; @@ -282,17 +285,17 @@ bool printWiFiStats(Print& oStream) { printLocalTime(oStream, wifi_disconnect_log[iLog].time + wifiHealth.gtime_adjust); oStream.println(); oStream.println(String_F(" Reason: ") + getWiFiDisconnectReasonString(wifi_disconnect_log[iLog].reason)); - oStream.println(String_F(" Channel: ") + (wifi_disconnect_log[iLog].channel) ); - oStream.println(String_F(" RSSI: ") + (wifi_disconnect_log[iLog].rssi) ); + oStream.println(String_F(" Channel: ") + (wifi_disconnect_log[iLog].channel)); + oStream.println(String_F(" RSSI: ") + (wifi_disconnect_log[iLog].rssi)); } } } - oStream.println(String_F(" IP Address: ") + (WiFi.localIP().toString()) ); - oStream.println(String_F(" Network Mask: ") + (WiFi.subnetMask().toString()) ); - oStream.println(String_F(" Gateway: ") + (WiFi.gatewayIP().toString()) ); - oStream.println(String_F(" DNS1: ") + (WiFi.dnsIP(0).toString()) ); - oStream.println(String_F(" DNS2: ") + (WiFi.dnsIP(1).toString()) ); + oStream.println(String_F(" IP Address: ") + (WiFi.localIP().toString())); + oStream.println(String_F(" Network Mask: ") + (WiFi.subnetMask().toString())); + oStream.println(String_F(" Gateway: ") + (WiFi.gatewayIP().toString())); + oStream.println(String_F(" DNS1: ") + (WiFi.dnsIP(0).toString())); + oStream.println(String_F(" DNS2: ") + (WiFi.dnsIP(1).toString())); bSuccess = true; diff --git a/libraries/ArduinoOTA/examples/OTAConfigErase/rtc.ino b/libraries/ArduinoOTA/examples/OTAConfigErase/rtc.ino index dda13df973..fde66a66f4 100644 --- a/libraries/ArduinoOTA/examples/OTAConfigErase/rtc.ino +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/rtc.ino @@ -4,26 +4,26 @@ #ifdef ERASE_CONFIG_H void printFlashEraseMap(Print& out) { out.printf_P(PSTR( - "_________________________________________________________________________________________\r\n" - "| Address of sectors | | | | | |\r\n" - "| at the end of the Flash | ...FB000 | ...FC000 | ...FD000 | ...FE000 | ...FF000 |\r\n" - "|___________________________|___________|___________|___________|___________|___________|\r\n" - "|__Bit_number_for_Mask______|_____4_____|_____3_____|_____2_____|_____1_____|_____0_____|\r\n" - "| | | RF_CAL | SDK Parameter Area |\r\n" - "| Overlay at RF init | | PHY INIT | | | |\r\n" - "| Persistant data | | | | SSID/PW | |\r\n" - "| User storage | EEPROM | | | | |\r\n" - "| Often shown downloaded | | BLANK.BIN | | BLANK.BIN | |\r\n" - "|___________________________|___________|___________|___________|___________|___________|\r\n" - "| 0 ERASE_CONFIG_NONE | | | | | |\r\n" - "| 1 ERASE_CONFIG_EEPROM | BIT(4) | | | | |\r\n" - "| 2 ERASE_CONFIG_RF_CAL | | BIT(3) | | | |\r\n" - "| 3 ERASE_CONFIG_PERSISTANT | | | | BIT(1) | |\r\n" - "| 4 ERASE_CONFIG_BLANK_BIN | | BIT(3) | | BIT(1) | |\r\n" - "| 5 ERASE_CONFIG_SDK_DATA | | BIT(3) | BIT(2) | BIT(1) | BIT(0) |\r\n" - "| 6 ERASE_CONFIG_ALL_DATA | BIT(4) | BIT(3) | BIT(2) | BIT(1) | BIT(0) |\r\n" - "|___________________________|___________|___________|___________|___________|___________|\r\n" -)); + "_________________________________________________________________________________________\r\n" + "| Address of sectors | | | | | |\r\n" + "| at the end of the Flash | ...FB000 | ...FC000 | ...FD000 | ...FE000 | ...FF000 |\r\n" + "|___________________________|___________|___________|___________|___________|___________|\r\n" + "|__Bit_number_for_Mask______|_____4_____|_____3_____|_____2_____|_____1_____|_____0_____|\r\n" + "| | | RF_CAL | SDK Parameter Area |\r\n" + "| Overlay at RF init | | PHY INIT | | | |\r\n" + "| Persistant data | | | | SSID/PW | |\r\n" + "| User storage | EEPROM | | | | |\r\n" + "| Often shown downloaded | | BLANK.BIN | | BLANK.BIN | |\r\n" + "|___________________________|___________|___________|___________|___________|___________|\r\n" + "| 0 ERASE_CONFIG_NONE | | | | | |\r\n" + "| 1 ERASE_CONFIG_EEPROM | BIT(4) | | | | |\r\n" + "| 2 ERASE_CONFIG_RF_CAL | | BIT(3) | | | |\r\n" + "| 3 ERASE_CONFIG_PERSISTANT | | | | BIT(1) | |\r\n" + "| 4 ERASE_CONFIG_BLANK_BIN | | BIT(3) | | BIT(1) | |\r\n" + "| 5 ERASE_CONFIG_SDK_DATA | | BIT(3) | BIT(2) | BIT(1) | BIT(0) |\r\n" + "| 6 ERASE_CONFIG_ALL_DATA | BIT(4) | BIT(3) | BIT(2) | BIT(1) | BIT(0) |\r\n" + "|___________________________|___________|___________|___________|___________|___________|\r\n" + )); } bool queueEraseConfig(int hotKey) { @@ -57,11 +57,12 @@ bool queueEraseConfig(int hotKey) { void requestEraseConfig(uint32_t mask) { eraseConfigOption = (ERASE_CONFIG_MASK_t)mask; // Save in case they do an OTA instead of a restart. - + eboot_command volatile * ebcmd = (eboot_command volatile *)RTC_MEM; - for (size_t i = 0; i < sizeof(eboot_command) / sizeof(uint32_t) ; i++) + for (size_t i = 0; i < sizeof(eboot_command) / sizeof(uint32_t) ; i++) { RTC_MEM[i] = 0U; + } // Finish fake post OTA flash copy complete state ebcmd->args[4] = mask; From a284b0a6abd78bc8395f5e047c8df659e7bf70ab Mon Sep 17 00:00:00 2001 From: M Hightower <27247790+mhightower83@users.noreply.github.com> Date: Mon, 30 Dec 2019 22:30:49 -0800 Subject: [PATCH 07/24] Take two --- .../examples/OTAConfigErase/TelnetAgent.ino | 2 +- .../examples/OTAConfigErase/WifiHealth.ino | 14 ++++--- .../examples/OTAConfigErase/rtc.ino | 40 +++++++++---------- 3 files changed, 29 insertions(+), 27 deletions(-) diff --git a/libraries/ArduinoOTA/examples/OTAConfigErase/TelnetAgent.ino b/libraries/ArduinoOTA/examples/OTAConfigErase/TelnetAgent.ino index 8339a36254..9fdfbb9473 100644 --- a/libraries/ArduinoOTA/examples/OTAConfigErase/TelnetAgent.ino +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/TelnetAgent.ino @@ -46,7 +46,7 @@ void handleTelnetAgent(void) { while (serverClients[i].available()) { int hotKey = serverClients[i].read(); - if ((hotKey > 0 && hotKey >' ') || hotKey == '\r') { + if ((hotKey > 0 && hotKey > ' ') || hotKey == '\r') { cmdLoop(telnetOut, hotKey); } } diff --git a/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.ino b/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.ino index d65b6e51ef..dfcb23770c 100644 --- a/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.ino +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.ino @@ -31,7 +31,7 @@ bool printUpTime(Print& oPrint, time_t timeIn) { struct tm *tv = gmtime(>ime); if (strftime(ephemeralBuffer, sizeof(ephemeralBuffer), "%T", tv) > 0) { if (tv->tm_yday) { - oPrint.print(String((tv->tm_yday)) + " day" + ((tv->tm_yday == 1) ? " " : "s ") ); + oPrint.print(String((tv->tm_yday)) + " day" + ((tv->tm_yday == 1) ? " " : "s ")); } oPrint.print(ephemeralBuffer); @@ -242,10 +242,11 @@ bool printWiFiStats(Print& oStream) { oStream.println(String_F(" PHY Mode: 802.11") + (getPhyModeChar(WiFi.getPhyMode()))); oStream.println(String_F(" Channel: ") + (WiFi.channel())); oStream.println(String_F(" RSSI: ") + (WiFi.RSSI())); - oStream.println(String_F(" MAX: ") + (wifiHealth.rssi_max) ); - oStream.println(String_F(" MIN: ") + (wifiHealth.rssi_min) ); - if (wifiHealth.rssi_count) - oStream.println(String_F(" AVG: ") + (wifiHealth.rssi_sum/(int32_t)wifiHealth.rssi_count)); + oStream.println(String_F(" MAX: ") + (wifiHealth.rssi_max)); + oStream.println(String_F(" MIN: ") + (wifiHealth.rssi_min)); + if (wifiHealth.rssi_count) { + oStream.println(String_F(" AVG: ") + (wifiHealth.rssi_sum / (int32_t)wifiHealth.rssi_count)); + } oStream.println(String_F(" sample count: ") + (wifiHealth.rssi_count)); if (0 != wifiHealth.connected_time) { @@ -277,8 +278,9 @@ bool printWiFiStats(Print& oStream) { ssize_t back_count = wifiHealth.connected_count; for (ssize_t i = 1; i <= MAX_CONNECTION_LOST_TIME_LOG; i++) { back_count -= 1; - if (back_count < 1) + if (back_count < 1) { break; + } ssize_t iLog = back_count % MAX_CONNECTION_LOST_TIME_LOG; oStream.print(String_F(" ")); diff --git a/libraries/ArduinoOTA/examples/OTAConfigErase/rtc.ino b/libraries/ArduinoOTA/examples/OTAConfigErase/rtc.ino index fde66a66f4..ef42428670 100644 --- a/libraries/ArduinoOTA/examples/OTAConfigErase/rtc.ino +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/rtc.ino @@ -4,26 +4,26 @@ #ifdef ERASE_CONFIG_H void printFlashEraseMap(Print& out) { out.printf_P(PSTR( - "_________________________________________________________________________________________\r\n" - "| Address of sectors | | | | | |\r\n" - "| at the end of the Flash | ...FB000 | ...FC000 | ...FD000 | ...FE000 | ...FF000 |\r\n" - "|___________________________|___________|___________|___________|___________|___________|\r\n" - "|__Bit_number_for_Mask______|_____4_____|_____3_____|_____2_____|_____1_____|_____0_____|\r\n" - "| | | RF_CAL | SDK Parameter Area |\r\n" - "| Overlay at RF init | | PHY INIT | | | |\r\n" - "| Persistant data | | | | SSID/PW | |\r\n" - "| User storage | EEPROM | | | | |\r\n" - "| Often shown downloaded | | BLANK.BIN | | BLANK.BIN | |\r\n" - "|___________________________|___________|___________|___________|___________|___________|\r\n" - "| 0 ERASE_CONFIG_NONE | | | | | |\r\n" - "| 1 ERASE_CONFIG_EEPROM | BIT(4) | | | | |\r\n" - "| 2 ERASE_CONFIG_RF_CAL | | BIT(3) | | | |\r\n" - "| 3 ERASE_CONFIG_PERSISTANT | | | | BIT(1) | |\r\n" - "| 4 ERASE_CONFIG_BLANK_BIN | | BIT(3) | | BIT(1) | |\r\n" - "| 5 ERASE_CONFIG_SDK_DATA | | BIT(3) | BIT(2) | BIT(1) | BIT(0) |\r\n" - "| 6 ERASE_CONFIG_ALL_DATA | BIT(4) | BIT(3) | BIT(2) | BIT(1) | BIT(0) |\r\n" - "|___________________________|___________|___________|___________|___________|___________|\r\n" - )); + "_________________________________________________________________________________________\r\n" + "| Address of sectors | | | | | |\r\n" + "| at the end of the Flash | ...FB000 | ...FC000 | ...FD000 | ...FE000 | ...FF000 |\r\n" + "|___________________________|___________|___________|___________|___________|___________|\r\n" + "|__Bit_number_for_Mask______|_____4_____|_____3_____|_____2_____|_____1_____|_____0_____|\r\n" + "| | | RF_CAL | SDK Parameter Area |\r\n" + "| Overlay at RF init | | PHY INIT | | | |\r\n" + "| Persistant data | | | | SSID/PW | |\r\n" + "| User storage | EEPROM | | | | |\r\n" + "| Often shown downloaded | | BLANK.BIN | | BLANK.BIN | |\r\n" + "|___________________________|___________|___________|___________|___________|___________|\r\n" + "| 0 ERASE_CONFIG_NONE | | | | | |\r\n" + "| 1 ERASE_CONFIG_EEPROM | BIT(4) | | | | |\r\n" + "| 2 ERASE_CONFIG_RF_CAL | | BIT(3) | | | |\r\n" + "| 3 ERASE_CONFIG_PERSISTANT | | | | BIT(1) | |\r\n" + "| 4 ERASE_CONFIG_BLANK_BIN | | BIT(3) | | BIT(1) | |\r\n" + "| 5 ERASE_CONFIG_SDK_DATA | | BIT(3) | BIT(2) | BIT(1) | BIT(0) |\r\n" + "| 6 ERASE_CONFIG_ALL_DATA | BIT(4) | BIT(3) | BIT(2) | BIT(1) | BIT(0) |\r\n" + "|___________________________|___________|___________|___________|___________|___________|\r\n" + )); } bool queueEraseConfig(int hotKey) { From 95eefede20f4d6313ab499e8f8e00130ecae7dca Mon Sep 17 00:00:00 2001 From: M Hightower <27247790+mhightower83@users.noreply.github.com> Date: Wed, 8 Jan 2020 12:30:19 -0800 Subject: [PATCH 08/24] Update example. Removed some debug crud. Added a Flash Info hotkey option. --- .../examples/OTAConfigErase/AddOns.h | 6 ++ .../examples/OTAConfigErase/AddOns.ino | 28 ++++++++ .../examples/OTAConfigErase/FlashInfo.ino | 10 +++ .../OTAConfigErase/OTAConfigErase.ino | 67 ++++--------------- 4 files changed, 57 insertions(+), 54 deletions(-) create mode 100644 libraries/ArduinoOTA/examples/OTAConfigErase/AddOns.h create mode 100644 libraries/ArduinoOTA/examples/OTAConfigErase/AddOns.ino create mode 100644 libraries/ArduinoOTA/examples/OTAConfigErase/FlashInfo.ino diff --git a/libraries/ArduinoOTA/examples/OTAConfigErase/AddOns.h b/libraries/ArduinoOTA/examples/OTAConfigErase/AddOns.h new file mode 100644 index 0000000000..5532da39a0 --- /dev/null +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/AddOns.h @@ -0,0 +1,6 @@ +#ifndef ADDONS_H +#define ADDONS_H + +// Add additional includes here. + +#endif diff --git a/libraries/ArduinoOTA/examples/OTAConfigErase/AddOns.ino b/libraries/ArduinoOTA/examples/OTAConfigErase/AddOns.ino new file mode 100644 index 0000000000..0b75076278 --- /dev/null +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/AddOns.ino @@ -0,0 +1,28 @@ + +#ifdef ADDON_DEMO +void printHelpAddOn(Print& oStream) { + oStream.println(F(" x - use this space to add on extra hot key options")); +} + +int hotKeyHandlerAddOn(Print& oStream, char hotKey) { + switch (hotKey) { + case 'x': + oStream.printf_P(PSTR("This could be an extra option")); + break; + default: + oStream.println(); + return 0; + } + oStream.println(); + return 1; +} +#else +void printHelpAddOn(Print& oStream) { + (void)oStream; +} +int hotKeyHandlerAddOn(Print& oStream, char inChar) { + (void)oStream; + (void)inChar; + return 0; +} +#endif diff --git a/libraries/ArduinoOTA/examples/OTAConfigErase/FlashInfo.ino b/libraries/ArduinoOTA/examples/OTAConfigErase/FlashInfo.ino new file mode 100644 index 0000000000..92219944e0 --- /dev/null +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/FlashInfo.ino @@ -0,0 +1,10 @@ +#include +#include +#define String_F(a) String(F(a)) + +void printFlashInfo(Print& oStream) { + oStream.println(String_F("Flash Size as reported by:")); + oStream.println(String_F(" flashchip->chip_size: 0x0") + String(flashchip->chip_size, HEX) + (", ") + String(flashchip->chip_size)); + oStream.println(String_F(" ESP.getFlashChipSize: 0x0") + String(ESP.getFlashChipSize(), HEX) + (", ") + String(ESP.getFlashChipSize())); + oStream.println(String_F(" ESP.getFlashChipRealSize: 0x0") + String(ESP.getFlashChipRealSize(), HEX) + (", ") + String(ESP.getFlashChipRealSize())); +} diff --git a/libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino b/libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino index 0bea82b959..7db54082c8 100644 --- a/libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino @@ -9,17 +9,10 @@ #include "WifiHealth.h" #include -#if HEAP_STATIC_RESERVE_SIZE -#include -#else -#define EVLOG1(a) do{ (void)a; } while(false) -#endif -#if ENABLE_FLASH_STATS -#include -#endif +#include "AddOns.h" #ifndef STASSID -#pragma message("Using default SSID: your-ssid") +#pragma message("Using default SSID: your-ssid, this is probably not what you want.") #define STASSID "your-ssid" #define STAPSK "your-password" #endif @@ -90,11 +83,9 @@ void setup() { WiFi.begin(ssid, password); while (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("Connection Failed! Rebooting..."); - EVLOG1("Connection Failed! Rebooting..."); delay(5000); ESP.restart(); } - EVLOG1("WiFi Up!"); // Port defaults to 8266 // ArduinoOTA.setPort(8266); @@ -173,20 +164,13 @@ void printHelp(Print& oStream) { oStream.println(F(" u - umm_info")); oStream.println(F(" w - WiFi Stats")); oStream.println(F(" R - Restart")); -#if ENABLE_FLASH_STATS - oStream.println(F(" f - flash Stats")); -#endif -#if HEAP_STATIC_RESERVE_SIZE - oStream.println(F(" m - static reserve memory information")); - oStream.println(F(" e - EvLog Report")); - oStream.println(F(" E - EvLog Clear")); -#endif + oStream.println(F(" f - flash info")); + printHelpAddOn(oStream); oStream.println(F(" ? - This help message")); oStream.println(); } void printTimes(Print& out) { - // #if (EVLOG_TIMESTAMP == EVLOG_TIMESTAMP_CLOCKCYCLES) { uint32_t fraction = esp_get_cycle_count(); fraction /= clockCyclesPerMicrosecond(); @@ -199,7 +183,6 @@ void printTimes(Print& out) { out.printf_P(ts_fmt, buf, fraction, clockCyclesPerMicrosecond()); out.println(); } - // #elif (EVLOG_TIMESTAMP == EVLOG_TIMESTAMP_MICROS) { uint32_t fraction = micros(); time_t gtime = (time_t)(fraction / 1000000U); @@ -211,7 +194,6 @@ void printTimes(Print& out) { out.printf_P(ts_fmt, buf, fraction); out.println(); } - // #elif (EVLOG_TIMESTAMP == EVLOG_TIMESTAMP_MILLIS) { uint32_t fraction = millis(); time_t gtime = (time_t)(fraction / 1000U); @@ -223,39 +205,26 @@ void printTimes(Print& out) { out.printf_P(ts_fmt, buf, fraction); out.println(); } - // #endif } -int cmdLoop(Print& oStream, char inChar) { - switch (inChar) { +int cmdLoop(Print& oStream, char hotKey) { + switch (hotKey) { case 't': printLocalTime(oStream); oStream.println(); printTimes(oStream); oStream.println(); break; -#if HEAP_STATIC_RESERVE_SIZE - case 'm': - oStream.printf_P(PSTR("umm_get_static_reserve_size %u"), umm_get_static_reserve_size()); - oStream.println(); - oStream.printf_P(PSTR("umm_get_static_reserve_addr %p"), umm_get_static_reserve_addr()); - oStream.println(); - oStream.println(); - break; -#endif case '?': printHelp(oStream); break; case 'u': umm_info(NULL, true); break; - -#if ENABLE_FLASH_STATS case 'f': - printFlashStatsReport(oStream); + printFlashInfo(oStream); oStream.println(); break; -#endif case 'w': printWiFiStats(oStream); oStream.println(); @@ -266,17 +235,6 @@ int cmdLoop(Print& oStream, char inChar) { WiFi.mode(WIFI_OFF); ESP.restart(); break; - -#if HEAP_STATIC_RESERVE_SIZE - case 'E': - evlog_restart(1); - oStream.println(F("EvLog restarted.")); - break; - case 'e': - evlogPrintReport(oStream); - oStream.println(); - break; -#endif case '0': case '1': case '2': @@ -284,7 +242,7 @@ int cmdLoop(Print& oStream, char inChar) { case '4': case '5': case '6': - if (queueEraseConfig(inChar)) { + if (queueEraseConfig(hotKey)) { oStream.println(F("Erase config request queued. Press 'R' to process or start OTA Update.")); } oStream.println(); @@ -294,16 +252,17 @@ int cmdLoop(Print& oStream, char inChar) { oStream.println(); break; default: - oStream.println(); + return hotKeyHandlerAddOn(oStream, hotKey); break; } - return 0; + oStream.println(); + return 1; } void serialClientLoop(void) { if (Serial.available() > 0) { - char inChar = Serial.read(); - cmdLoop(Serial, inChar); + char hotKey = Serial.read(); + cmdLoop(Serial, hotKey); } } From 4554a7c8b4d0ec72808b47252b475bec5efa501f Mon Sep 17 00:00:00 2001 From: M Hightower <27247790+mhightower83@users.noreply.github.com> Date: Wed, 8 Jan 2020 21:53:06 -0800 Subject: [PATCH 09/24] style fix --- libraries/ArduinoOTA/examples/OTAConfigErase/AddOns.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ArduinoOTA/examples/OTAConfigErase/AddOns.ino b/libraries/ArduinoOTA/examples/OTAConfigErase/AddOns.ino index 0b75076278..1fbb753bf7 100644 --- a/libraries/ArduinoOTA/examples/OTAConfigErase/AddOns.ino +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/AddOns.ino @@ -1,7 +1,7 @@ #ifdef ADDON_DEMO void printHelpAddOn(Print& oStream) { - oStream.println(F(" x - use this space to add on extra hot key options")); + oStream.println(F(" x - use this space to add on extra hot key options")); } int hotKeyHandlerAddOn(Print& oStream, char hotKey) { From 9c4e44bc8406eb3b4a7eab9260765f824e8b8749 Mon Sep 17 00:00:00 2001 From: M Hightower <27247790+mhightower83@users.noreply.github.com> Date: Thu, 6 Feb 2020 13:31:41 -0800 Subject: [PATCH 10/24] Added print messages to show progress. And code to ensure the UART speed stayed at 74880 for a more complete viewing of messages. Well at least, it stays at 74880 for a longer time than it was. Added pinMode filter to prevent UART TX pin from being disabled during user_init(). --- cores/esp8266/Updater.cpp | 3 + cores/esp8266/core_esp8266_main.cpp | 19 +++ cores/esp8266/core_esp8266_postmortem.cpp | 6 +- cores/esp8266/erase_config.cpp | 144 +++++++++++++++++- cores/esp8266/erase_config.h | 1 + .../OTAConfigErase/OTAConfigErase.ino | 9 +- 6 files changed, 167 insertions(+), 15 deletions(-) diff --git a/cores/esp8266/Updater.cpp b/cores/esp8266/Updater.cpp index 426317e3c7..9659136590 100644 --- a/cores/esp8266/Updater.cpp +++ b/cores/esp8266/Updater.cpp @@ -68,6 +68,9 @@ void UpdaterClass::_reset() { } bool UpdaterClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) { + // Empty call so erase_config.cpp module to ensure it is built and linked in. + enable_erase_config_at_link_time(); + if(_size > 0){ #ifdef DEBUG_UPDATER DEBUG_UPDATER.println(F("[begin] already running")); diff --git a/cores/esp8266/core_esp8266_main.cpp b/cores/esp8266/core_esp8266_main.cpp index 850d9b95a8..805a662c86 100644 --- a/cores/esp8266/core_esp8266_main.cpp +++ b/cores/esp8266/core_esp8266_main.cpp @@ -282,6 +282,17 @@ void init_done() { https://github.com/esp8266/Arduino/pull/4889 */ +#if defined(ERASE_CONFIG_H) +extern "C" void fix_divider(void); +#define FIX_DIVIDER fix_divider +#define ETS_PRINTF(...) ets_uart_printf(__VA_ARGS__) +#define ETS_DELAY_US(a) ets_delay_us(a) +#else +#define FIX_DIVIDER() do {} while(0) +#define ETS_PRINTF(...) do {} while(0) +#define ETS_DELAY_US(a) do {} while(0) +#endif + extern "C" void app_entry_redefinable(void) __attribute__((weak)); extern "C" void app_entry_redefinable(void) @@ -291,6 +302,8 @@ extern "C" void app_entry_redefinable(void) cont_t s_cont __attribute__((aligned(16))); g_pcont = &s_cont; + FIX_DIVIDER(); + ETS_PRINTF("\n\ncall_user_start()\n"); /* Call the entry point of the SDK code. */ call_user_start(); } @@ -312,7 +325,13 @@ extern "C" void user_init(void) { struct rst_info *rtc_info_ptr = system_get_rst_info(); memcpy((void *) &resetInfo, (void *) rtc_info_ptr, sizeof(resetInfo)); +#if defined(ERASE_CONFIG_H) + uart_div_modify(0, UART_CLK_FREQ / (74880)); + ETS_DELAY_US(150); + ETS_PRINTF("\nuser_init()\n"); +#else uart_div_modify(0, UART_CLK_FREQ / (115200)); +#endif init(); // in core_esp8266_wiring.c, inits hw regs and sdk timer diff --git a/cores/esp8266/core_esp8266_postmortem.cpp b/cores/esp8266/core_esp8266_postmortem.cpp index 0486ee5735..79074d8b20 100644 --- a/cores/esp8266/core_esp8266_postmortem.cpp +++ b/cores/esp8266/core_esp8266_postmortem.cpp @@ -89,7 +89,7 @@ static void ets_printf_P(const char *str, ...) { vsnprintf(destStr, sizeof(destStr), str, argPtr); va_end(argPtr); while (*c) { - ets_putc(*(c++)); + ets_uart_putc1(*(c++)); // This will send "\r\n" for each '\n' } } @@ -147,10 +147,10 @@ void __wrap_system_restart_local() { // (determined empirically, might break) uint32_t offset = 0; if (rst_info.reason == REASON_SOFT_WDT_RST) { - offset = 0x1b0; + offset = 0x190; } else if (rst_info.reason == REASON_EXCEPTION_RST) { - offset = 0x1a0; + offset = 0x180; } else if (rst_info.reason == REASON_WDT_RST) { offset = 0x10; diff --git a/cores/esp8266/erase_config.cpp b/cores/esp8266/erase_config.cpp index d66f5deeef..1353884e63 100644 --- a/cores/esp8266/erase_config.cpp +++ b/cores/esp8266/erase_config.cpp @@ -58,10 +58,18 @@ OTAEraseConfig. #undef ERASE_CONFIG_H #endif +#if 1 +#define ETS_PRINTF(...) ets_uart_printf(__VA_ARGS__) +#define ETS_DELAY_US(a) ets_delay_us(a) +#else +#define ETS_PRINTF(...) do {} while(0) +#define ETS_DELAY_US(a) do {} while(0) +#endif #ifdef ERASE_CONFIG_H extern "C" { #include "user_interface.h" +void __real_system_restart_local(); #if (ERASE_CONFIG_METHOD == 1) #define IRAM_MAYBE @@ -71,7 +79,15 @@ extern "C" { #pragma GCC error "Unsupported ERASE_CONFIG_METHOD" #endif -bool IRAM_MAYBE erase_config2(const uint32_t flash_erase_mask) { + +void enable_erase_config_at_link_time(void) { + /* + This has to be called from somewhere for this module to get + linked into the build. + */ +} + +bool IRAM_MAYBE erase_config(const uint32_t flash_erase_mask) { // This is really the active configured size uint32_t flash_size = flashchip->chip_size; uint32_t erase_mask = (flash_erase_mask & (uint32_t)ERASE_CONFIG_ALL_DATA); @@ -84,7 +100,10 @@ bool IRAM_MAYBE erase_config2(const uint32_t flash_erase_mask) { #elif (ERASE_CONFIG_METHOD == 2) if (0 != SPIEraseSector(sector)) { #endif + ETS_PRINTF("Erase sector 0x%04X failed!\n", sector); return false; + } else { + ETS_PRINTF("Erased sector 0x%04X\n", sector); } } } @@ -114,18 +133,82 @@ bool IRAM_MAYBE check_and_erase_config(void) { ebcmd->args[i] = 0U; if (erase_flash_option) { + ETS_PRINTF("\nerase_config(0x%03X)\n", erase_flash_option); #if (ERASE_CONFIG_METHOD == 1) - erase_config2(erase_flash_option); - system_restart(); + erase_config(erase_flash_option); + ETS_PRINTF("\n__real_system_restart_local\n\n"); + ETS_DELAY_US(12000); + __real_system_restart_local(); while(true){} #elif (ERASE_CONFIG_METHOD == 2) - return erase_config2(erase_flash_option); + return erase_config(erase_flash_option); #endif } + } else { + ETS_PRINTF("\nNo OTA erase flags\n"); + } + return true; +} + + + + +typedef enum ROM_RST_REASON { /* Comments on the right are from RTOS SDK */ + NO_MEAN = 0, /* Undefined */ + POWERON_RESET = 1, /* Power on boot *//**<1, Vbat power on reset */ + EXT_RESET = 2, /* External reset or wake-up from Deep-sleep */ + /**<2, external system reset */ + SW_RESET = 3, /* *//**<3, Software reset digital core */ + OWDT_RESET = 4, /* Hardware WDT reset *//**<4, Legacy watch dog reset digital core */ + DEEPSLEEP_RESET = 5, /* *//**<5, Deep Sleep reset digital core */ + SDIO_RESET = 6, /* *//**<6, Reset by SLC module, reset digital core*/ +} ROM_RST_REASON_t; + +constexpr volatile uint32_t *RTC_SYS = (volatile uint32_t*)0x60001100; +extern uint32_t rtc_get_reset_reason(void); + +bool ICACHE_RAM_ATTR is_cpu_freq_cal(void) { + const uint32_t rtc_sys_reason = RTC_SYS[0]; + const uint32_t rom_api_reason = rtc_get_reset_reason(); + if (1 >= rtc_sys_reason && OWDT_RESET != rom_api_reason) { + return false; // REASON_EXT_SYS_RST + } + if (REASON_EXT_SYS_RST < rtc_sys_reason) { + return false; // REASON_DEFAULT_RST } return true; } +/* + * Here we use uart_div_modify in the Boot ROM. Note the Boot ROM version does + * not do any input validation. + * + * We cannot use the SDKs replacement. It is not in IRAM. + */ +#ifndef ROM_uart_div_modify +#define ROM_uart_div_modify 0x400039d8 +#endif +typedef void (*fp_uart_div_modify_t)(uint32_t uart_no, uint32 DivLatchValue); +constexpr fp_uart_div_modify_t real_uart_div_modify = (fp_uart_div_modify_t)ROM_uart_div_modify; + +void ICACHE_RAM_ATTR fix_divider(void) { + /* + When reset cause is not power-on or EXT_RST the CPU crystal calibration + has been done and there is no need to correct. With the exception of some + early callback(s) from the SDK like run_user_rf_pre_init(). The UART speed + appears to always be based on the precalibrated crystal frequency. + */ + uint32_t divider = UART_CLK_FREQ / 115200; + if (is_cpu_freq_cal()) { + divider = UART_CLK_FREQ / 74880; + } + real_uart_div_modify(0, divider); + ETS_DELAY_US(150); +} + + + + #if (ERASE_CONFIG_METHOD == 1) extern struct rst_info resetInfo; @@ -138,16 +221,15 @@ extern "C" void preinit (void) } } - - #elif (ERASE_CONFIG_METHOD == 2) //Another option + #include "cont.h" int eboot_two_shots __attribute__((section(".noinit"))); extern cont_t* g_pcont; extern "C" void call_user_start(); -extern "C" void app_entry_redefinable(void) +extern "C" void ICACHE_RAM_ATTR app_entry_redefinable(void) { /* Allocate continuation context on this SYS stack, and save pointer to it. */ @@ -156,6 +238,10 @@ extern "C" void app_entry_redefinable(void) eboot_two_shots = 2; + fix_divider(); + ETS_PRINTF("\n\ncall_user_start()\n"); + ETS_DELAY_US(12000); + /* Call the entry point of the SDK code. */ call_user_start(); } @@ -182,15 +268,57 @@ int ICACHE_RAM_ATTR SPIRead(uint32_t addr, void *dest, size_t size) { // config sectors. if (eboot_two_shots) { eboot_two_shots--; - if (0 == eboot_two_shots) + if (0 == eboot_two_shots) { + fix_divider(); check_and_erase_config(); + ETS_DELAY_US(12000); + } } int err = real_SPIRead(addr, dest, size); dbg_log_SPIRead(addr, dest, size, err); return err; } + + #endif }; #endif // ERASE_CONFIG_H + + +extern "C" void ICACHE_RAM_ATTR _Z22__run_user_rf_pre_initv(void) { + // fix_divider(); + real_uart_div_modify(0, UART_CLK_FREQ / 115200); + ETS_DELAY_US(150); + ets_uart_printf("\n__run_user_rf_pre_init()\n"); + ETS_DELAY_US(12000); +} + +/* + This helps keep the UART enabled longer so we can get a few more messages + printed. +*/ +#if 1 +extern struct rst_info resetInfo; +extern "C" void __pinMode( uint8_t pin, uint8_t mode ); + +inline bool is_gpio_persistent(void) { + return REASON_EXCEPTION_RST <= resetInfo.reason && + REASON_SOFT_RESTART >= resetInfo.reason; +} + +extern "C" void pinMode( uint8_t pin, uint8_t mode ) { + static bool in_initPins = true; + if (in_initPins && (1 == pin)) { + if (!is_gpio_persistent()) { + // Restore pin to TX after Power-on and EXT_RST + __pinMode(pin, FUNCTION_0); + } + in_initPins = false; + return; + } + + __pinMode( pin, mode ); +} +#endif diff --git a/cores/esp8266/erase_config.h b/cores/esp8266/erase_config.h index 09f9c629d7..af94ff2a68 100644 --- a/cores/esp8266/erase_config.h +++ b/cores/esp8266/erase_config.h @@ -36,6 +36,7 @@ ERASE_CONFIG_ALL_DATA = ( BIT(4) | BIT(3) | BIT(2) | BIT(1) | bool check_and_erase_config(void); bool erase_config(const uint32_t flash_erase_mask); +void enable_erase_config_at_link_time(void); #ifdef __cplusplus }; diff --git a/libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino b/libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino index 7db54082c8..0c4bc5231a 100644 --- a/libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino @@ -29,7 +29,7 @@ const char* password = STAPSK; ERASE_CONFIG_SDK_DATA ERASE_CONFIG_ALL_DATA */ -ERASE_CONFIG_MASK_t eraseConfigOption = ERASE_CONFIG_BLANK_BIN; +ERASE_CONFIG_MASK_t eraseConfigOption = ERASE_CONFIG_RF_CAL; //ERASE_CONFIG_BLANK_BIN; #define String_F(a) String(F(a)) @@ -52,10 +52,11 @@ void setTimeTZ(String& tz) { } void setup() { - Serial.begin(115200); + Serial.begin(74880); + // Serial.begin(115200); delay(20); Serial.println(); - Serial.println("Booting"); + Serial.println("setup ..."); WiFi.persistent(false); // w/o this a flash write occurs at every boot WiFi.mode(WIFI_OFF); @@ -135,7 +136,7 @@ void setup() { } }); ArduinoOTA.begin(); - Serial.println("Ready"); + Serial.println("setup complete"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); Serial.println(); From 560ddc5cb115eb7af37f85bbc793955975ef88c9 Mon Sep 17 00:00:00 2001 From: M Hightower <27247790+mhightower83@users.noreply.github.com> Date: Thu, 6 Feb 2020 17:52:50 -0800 Subject: [PATCH 11/24] Set default Method to 2, to side step travis issue of using preinit(). This is just a PoC an alternate for preinit() can be created later. --- cores/esp8266/erase_config.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cores/esp8266/erase_config.cpp b/cores/esp8266/erase_config.cpp index 1353884e63..645f8842f8 100644 --- a/cores/esp8266/erase_config.cpp +++ b/cores/esp8266/erase_config.cpp @@ -46,7 +46,7 @@ OTAEraseConfig. */ -#define ERASE_CONFIG_METHOD 1 +#define ERASE_CONFIG_METHOD 2 #ifndef VAR_NAME_VALUE @@ -213,8 +213,7 @@ void ICACHE_RAM_ATTR fix_divider(void) { #if (ERASE_CONFIG_METHOD == 1) extern struct rst_info resetInfo; -extern "C" void preinit (void) -{ +extern "C" void preinit (void) { /* do nothing On power up */ if (0 != resetInfo.reason) { check_and_erase_config(); From 049d91bca8147fa1f0a5e675b6d27e9e470fda5f Mon Sep 17 00:00:00 2001 From: M Hightower <27247790+mhightower83@users.noreply.github.com> Date: Tue, 18 Feb 2020 12:30:38 -0800 Subject: [PATCH 12/24] Added a Third method. This method runs before the NONOS SDK is started. Patches the flashchip structure that the ROM APIs reference for validating input before calling SPIEraseSector. Extracted logic from EspClass::magicFlashChipSize() to make a "C" callable function and made EspClass::magicFlashChipSize() a thin wrapper to avoid duplicating code. Use Cache_Read_Enable() to enable flash code execution to reduce IRAM usage by pre SDK starup code. Improved comments and made comment formating more consistant in erase_config.cpp. Improved issolation of debug printing. --- cores/esp8266/Esp.cpp | 31 ++ cores/esp8266/Updater.cpp | 2 + cores/esp8266/core_esp8266_main.cpp | 4 +- cores/esp8266/erase_config.cpp | 311 +++++++++++++----- cores/esp8266/erase_config.h | 4 + .../examples/OTAConfigErase/FlashInfo.ino | 15 +- .../OTAConfigErase/OTAConfigErase.ino | 27 +- 7 files changed, 299 insertions(+), 95 deletions(-) diff --git a/cores/esp8266/Esp.cpp b/cores/esp8266/Esp.cpp index a0e525f2bb..e90f4d42bf 100644 --- a/cores/esp8266/Esp.cpp +++ b/cores/esp8266/Esp.cpp @@ -326,6 +326,36 @@ FlashMode_t EspClass::getFlashChipMode(void) return mode; } +#ifdef ERASE_CONFIG_H +// This change is only needed for method 3 +extern "C" uint32_t esp_c_magic_flash_chip_size(uint8_t byte) +{ + switch(byte & 0x0F) { + case 0x0: // 4 Mbit (512KB) + return (512_kB); + case 0x1: // 2 MBit (256KB) + return (256_kB); + case 0x2: // 8 MBit (1MB) + return (1_MB); + case 0x3: // 16 MBit (2MB) + return (2_MB); + case 0x4: // 32 MBit (4MB) + return (4_MB); + case 0x8: // 64 MBit (8MB) + return (8_MB); + case 0x9: // 128 MBit (16MB) + return (16_MB); + default: // fail? + return 0; + } +} + +uint32_t EspClass::magicFlashChipSize(uint8_t byte) +{ + return esp_c_magic_flash_chip_size(byte); +} + +#else uint32_t EspClass::magicFlashChipSize(uint8_t byte) { switch(byte & 0x0F) { case 0x0: // 4 Mbit (512KB) @@ -346,6 +376,7 @@ uint32_t EspClass::magicFlashChipSize(uint8_t byte) { return 0; } } +#endif uint32_t EspClass::magicFlashChipSpeed(uint8_t byte) { switch(byte & 0x0F) { diff --git a/cores/esp8266/Updater.cpp b/cores/esp8266/Updater.cpp index 9659136590..c8d7d6cac1 100644 --- a/cores/esp8266/Updater.cpp +++ b/cores/esp8266/Updater.cpp @@ -68,8 +68,10 @@ void UpdaterClass::_reset() { } bool UpdaterClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) { +#ifdef ERASE_CONFIG_H // Empty call so erase_config.cpp module to ensure it is built and linked in. enable_erase_config_at_link_time(); +#endif if(_size > 0){ #ifdef DEBUG_UPDATER diff --git a/cores/esp8266/core_esp8266_main.cpp b/cores/esp8266/core_esp8266_main.cpp index 805a662c86..ca03cfb536 100644 --- a/cores/esp8266/core_esp8266_main.cpp +++ b/cores/esp8266/core_esp8266_main.cpp @@ -282,7 +282,7 @@ void init_done() { https://github.com/esp8266/Arduino/pull/4889 */ -#if defined(ERASE_CONFIG_H) +#if defined(DEBUG_ERASE_CONFIG) extern "C" void fix_divider(void); #define FIX_DIVIDER fix_divider #define ETS_PRINTF(...) ets_uart_printf(__VA_ARGS__) @@ -325,7 +325,7 @@ extern "C" void user_init(void) { struct rst_info *rtc_info_ptr = system_get_rst_info(); memcpy((void *) &resetInfo, (void *) rtc_info_ptr, sizeof(resetInfo)); -#if defined(ERASE_CONFIG_H) +#if defined(DEBUG_ERASE_CONFIG) uart_div_modify(0, UART_CLK_FREQ / (74880)); ETS_DELAY_US(150); ETS_PRINTF("\nuser_init()\n"); diff --git a/cores/esp8266/erase_config.cpp b/cores/esp8266/erase_config.cpp index 645f8842f8..081651ad23 100644 --- a/cores/esp8266/erase_config.cpp +++ b/cores/esp8266/erase_config.cpp @@ -10,7 +10,7 @@ Then a serial flash with Erase Flash with WiFi setting is recommended. I have seen this more often when changing SDK by OTA. We don't have an erase WiFi for OTA. -The PR tries to present a proof of concept solution. Actually it describes two +This PR presents a proof of concept solution. Actually it describes three different methods. There are 3 cases to consider when the firmware is updated by OTA. The new @@ -20,34 +20,48 @@ There are 3 cases to consider when the firmware is updated by OTA. The new 3. has a smaller Flash Configuration then the old. In theory after an OTA and before a restart, the flash could be erased for -_case 1_. _Case 2_ is a problem because the size exceeds what the SPIEraseSector -expects and fails. We have to wait for a restart, after which the SDK will have -updated the values in `flashchip`. _Case 3_ is potentially unsafe because we -could be erasing the code that is running. +_case 1_. _Case 2_ is a problem because the size exceeds the size specified in +flashchip. That size is used by SPIEraseSector to validate the callers request +and fails when too large. We have to wait for a restart, after which the SDK +will have updated the values in `flashchip`. _Case 3_ is potentially unsafe +because we could be erasing the code that is running. At app_entry() `flashchip` properties appear to be reset to a default 4MByte flash. Even an ESP8285 reported 4MByte. The value of `flashchip->chip_size` was changed at the 2nd SPIRead. The 1st read was to address 0, 4 bytes. -To erase the flash WiFi area, I choose to wait until the SDK has finished its -adjustments to the `flashchip` structure. Then to begin erasing WiFi sectors. +To erase the flash WiFi area for methods 1 and 2, I choose to wait until the SDK +has finished its adjustments to the `flashchip` structure. Then to begin erasing +WiFi sectors. Method 3 runs before the SDK starts. It temporarily updates +`flashchip->chip_size`. Then does the erase and puts everything back before +starting the SDK. + +Summary of the three methods: -I implemented two methods: 1. The first runs after the SDK calls `user_init()` and flash code execution is available (No IRAM needed), but restarts to be sure the SDK does not get confused about its sectors being erased. -2. The 2nd method runs as early as possible required IRAM. The sectors are -erased before that 2nd read is processed. The SDK allowing the SDK to start off + +2. The 2nd method runs as early as possible requires IRAM. The sectors are +erased before the 2nd read is processed. This allows the SDK to start off thinking the sectors were blank at boot. +3. Similar to method 2 runs as early as possible, turns on flash code execution +so more of the initialization code can be moved to flash. Directly modifies the +size element in `flashchip` structure in ROM data, dRAM. This allows the flash +erase to succeed. + +The original flash size is restored before starting the SDK. With the assumption +that the SDK will handle the size change properly. It should be noted that +only changing the size value in the flashchip table, is equivalent to what the +esptool.py is doing. + I also added an example/test sketch for exercising the feature. It is OTAEraseConfig. - */ -#define ERASE_CONFIG_METHOD 2 - +#define ERASE_CONFIG_METHOD 3 #ifndef VAR_NAME_VALUE #define VALUE(x) __STRINGIFY(x) @@ -58,37 +72,60 @@ OTAEraseConfig. #undef ERASE_CONFIG_H #endif -#if 1 +#ifdef DEBUG_ERASE_CONFIG #define ETS_PRINTF(...) ets_uart_printf(__VA_ARGS__) #define ETS_DELAY_US(a) ets_delay_us(a) +#define ETS_FLUSH(a) while((USS(a) >> USTXC) & 0xff){} +#define FIX_DIVIDER() fix_divider() + #else #define ETS_PRINTF(...) do {} while(0) #define ETS_DELAY_US(a) do {} while(0) +#define ETS_FLUSH(a) do {} while(false) +#define FIX_DIVIDER() do {} while(false) #endif #ifdef ERASE_CONFIG_H extern "C" { #include "user_interface.h" -void __real_system_restart_local(); #if (ERASE_CONFIG_METHOD == 1) +void __real_system_restart_local(); #define IRAM_MAYBE #elif (ERASE_CONFIG_METHOD == 2) #define IRAM_MAYBE ICACHE_RAM_ATTR +#elif (ERASE_CONFIG_METHOD == 3) +#define IRAM_MAYBE #else #pragma GCC error "Unsupported ERASE_CONFIG_METHOD" #endif void enable_erase_config_at_link_time(void) { + /* This has to be called from somewhere for this module to get + linked into the build. */ +} + +#if (ERASE_CONFIG_METHOD == 3) +extern "C" void Cache_Read_Enable_2(void); +extern "C" void Cache_Read_Disable_2(void); + +int ICACHE_RAM_ATTR erase_sector(const uint32_t sector) { /* - This has to be called from somewhere for this module to get - linked into the build. - */ + Toggle Flash execution off and on, around ROM flash function calls. + The SDK APIs would have normally handled this operation; however, + it is too early for it to take calls. + */ + int rc; + Cache_Read_Disable_2(); + rc = SPIEraseSector(sector); + Cache_Read_Enable_2(); + return rc; } +#endif bool IRAM_MAYBE erase_config(const uint32_t flash_erase_mask) { - // This is really the active configured size + /* This is really the active configured size */ uint32_t flash_size = flashchip->chip_size; uint32_t erase_mask = (flash_erase_mask & (uint32_t)ERASE_CONFIG_ALL_DATA); uint32_t sector = flash_size/SPI_FLASH_SEC_SIZE - 1U; @@ -99,6 +136,8 @@ bool IRAM_MAYBE erase_config(const uint32_t flash_erase_mask) { if (0 != spi_flash_erase_sector(sector)) { #elif (ERASE_CONFIG_METHOD == 2) if (0 != SPIEraseSector(sector)) { +#elif (ERASE_CONFIG_METHOD == 3) + if (0 != erase_sector(sector)) { #endif ETS_PRINTF("Erase sector 0x%04X failed!\n", sector); return false; @@ -112,12 +151,14 @@ bool IRAM_MAYBE erase_config(const uint32_t flash_erase_mask) { } bool IRAM_MAYBE check_and_erase_config(void) { - // This should work since each element of the structure is a word. + /* This should work since each element of the structure is a word. */ eboot_command volatile * ebcmd = (eboot_command volatile *)RTC_MEM; - // We want to run after an OTA has completed and the bin has been moved to its - // final resting place in flash. We want to catch the moment of the 1st boot - // of this new sketch. Then verify we have a valid erase option. + /* + We want to run after an OTA has completed and the bin has been moved to + its final resting place in flash. We want to catch the moment of the 1st + boot of this new sketch. Then verify we have a valid erase option. + */ if (0U == ebcmd->magic && 0U == ebcmd->crc32 && ACTION_COPY_RAW == ebcmd->action && @@ -137,10 +178,10 @@ bool IRAM_MAYBE check_and_erase_config(void) { #if (ERASE_CONFIG_METHOD == 1) erase_config(erase_flash_option); ETS_PRINTF("\n__real_system_restart_local\n\n"); - ETS_DELAY_US(12000); + ETS_FLUSH(0); __real_system_restart_local(); while(true){} -#elif (ERASE_CONFIG_METHOD == 2) +#elif (ERASE_CONFIG_METHOD == 2) || (ERASE_CONFIG_METHOD == 3) return erase_config(erase_flash_option); #endif } @@ -150,18 +191,42 @@ bool IRAM_MAYBE check_and_erase_config(void) { return true; } +#if defined(DEBUG_ERASE_CONFIG) +/****************************************************************************** + Adjustments for Debug Printing + Start of stuff to get serial printing to work just a little longer + This block of code helps maintain the UART data rate of 74880 for debug + printing. This is only needed when the CPU Crystal Frequency is not 40MHz. + When a 26MHz Crystal is used, the UART is underclocked from 115200 to an + effective data rate of 74880 bps. When the NONOS SDK initializes, the PLL + circuit for the CPU Clock is corrected to work with the 26MHz Crystal. And + the UART rate then shifts to being 115200 bps. It appears that the PLL + adjustments are cleared after a EXT_RST, PD_EN, and power-on. On soft + restarts, including exceptions and soft/hardware WDT reset, it appears the + PLL adjustments are preserved, the UART date rate is 115200 bps. + + The solution impelmented here is to adjust the UART data rate to an + effective rate of 74880 based on the kind of reset. This minimizes the + garbled printing. + + Assumes a data rate of 74880. + miniterm.py does a good job of handling this rate on Linux. + */ + +/* ROM reset reason values returned by `rtc_get_reset_reason` are different from + the NONOS SDK reset reasons. */ typedef enum ROM_RST_REASON { /* Comments on the right are from RTOS SDK */ - NO_MEAN = 0, /* Undefined */ - POWERON_RESET = 1, /* Power on boot *//**<1, Vbat power on reset */ - EXT_RESET = 2, /* External reset or wake-up from Deep-sleep */ - /**<2, external system reset */ - SW_RESET = 3, /* *//**<3, Software reset digital core */ - OWDT_RESET = 4, /* Hardware WDT reset *//**<4, Legacy watch dog reset digital core */ - DEEPSLEEP_RESET = 5, /* *//**<5, Deep Sleep reset digital core */ - SDIO_RESET = 6, /* *//**<6, Reset by SLC module, reset digital core*/ + NO_MEAN = 0, /* Undefined */ + POWERON_RESET = 1, /* Power on boot *//**<1, Vbat power on reset */ + EXT_RESET = 2, /* External reset or wake-up from Deep-sleep */ + /**<2, external system reset */ + SW_RESET = 3, /* *//**<3, Software reset digital core */ + OWDT_RESET = 4, /* Hardware WDT reset *//**<4, Legacy watch dog reset digital core */ + DEEPSLEEP_RESET = 5, /* *//**<5, Deep Sleep reset digital core */ + SDIO_RESET = 6, /* *//**<6, Reset by SLC module, reset digital core*/ } ROM_RST_REASON_t; constexpr volatile uint32_t *RTC_SYS = (volatile uint32_t*)0x60001100; @@ -171,19 +236,20 @@ bool ICACHE_RAM_ATTR is_cpu_freq_cal(void) { const uint32_t rtc_sys_reason = RTC_SYS[0]; const uint32_t rom_api_reason = rtc_get_reset_reason(); if (1 >= rtc_sys_reason && OWDT_RESET != rom_api_reason) { - return false; // REASON_EXT_SYS_RST + return false; // REASON_EXT_SYS_RST } if (REASON_EXT_SYS_RST < rtc_sys_reason) { - return false; // REASON_DEFAULT_RST + return false; // REASON_DEFAULT_RST } return true; } /* - * Here we use uart_div_modify in the Boot ROM. Note the Boot ROM version does - * not do any input validation. - * - * We cannot use the SDKs replacement. It is not in IRAM. + Here we use uart_div_modify in the Boot ROM. Note the Boot ROM version does + not do any input validation. + + The SDK has an overide on uart_div_modify. We cannot use its replacement. It + is not in IRAM and reauires SDK initialization. */ #ifndef ROM_uart_div_modify #define ROM_uart_div_modify 0x400039d8 @@ -193,11 +259,11 @@ constexpr fp_uart_div_modify_t real_uart_div_modify = (fp_uart_div_modify_t)ROM_ void ICACHE_RAM_ATTR fix_divider(void) { /* - When reset cause is not power-on or EXT_RST the CPU crystal calibration - has been done and there is no need to correct. With the exception of some - early callback(s) from the SDK like run_user_rf_pre_init(). The UART speed - appears to always be based on the precalibrated crystal frequency. - */ + When reset cause is not power-on or EXT_RST the CPU crystal calibration + has been done and there is no need to correct. With the exception of some + early callback(s) from the SDK like run_user_rf_pre_init(). The UART + speed appears to always be based on the precalibrated crystal frequency. + */ uint32_t divider = UART_CLK_FREQ / 115200; if (is_cpu_freq_cal()) { divider = UART_CLK_FREQ / 74880; @@ -206,8 +272,47 @@ void ICACHE_RAM_ATTR fix_divider(void) { ETS_DELAY_US(150); } +/* + Something to see when we are in the SDK initialization. +*/ +extern "C" void ICACHE_RAM_ATTR _Z22__run_user_rf_pre_initv(void) { + real_uart_div_modify(0, UART_CLK_FREQ / 115200); + ETS_DELAY_US(150); + ETS_PRINTF("\n__run_user_rf_pre_init()\n"); + ETS_FLUSH(0); +} +#if 1 +/* + This helps keep the UART enabled at user_init() so we can get a few more + messages printed. +*/ +extern struct rst_info resetInfo; +extern "C" void __pinMode( uint8_t pin, uint8_t mode ); +inline bool is_gpio_persistent(void) { + return REASON_EXCEPTION_RST <= resetInfo.reason && + REASON_SOFT_RESTART >= resetInfo.reason; +} + +extern "C" void pinMode( uint8_t pin, uint8_t mode ) { + static bool in_initPins = true; + if (in_initPins && (1 == pin)) { + if (!is_gpio_persistent()) { + /* Restore pin to TX after Power-on and EXT_RST */ + __pinMode(pin, FUNCTION_0); + } + in_initPins = false; + return; + } + + __pinMode( pin, mode ); +} +#endif +/* + End of stuff to help printing work. + *****************************************************************************/ +#endif #if (ERASE_CONFIG_METHOD == 1) @@ -220,7 +325,7 @@ extern "C" void preinit (void) { } } -#elif (ERASE_CONFIG_METHOD == 2) //Another option +#elif (ERASE_CONFIG_METHOD == 2) #include "cont.h" @@ -228,8 +333,7 @@ int eboot_two_shots __attribute__((section(".noinit"))); extern cont_t* g_pcont; extern "C" void call_user_start(); -extern "C" void ICACHE_RAM_ATTR app_entry_redefinable(void) -{ +extern "C" void ICACHE_RAM_ATTR app_entry_redefinable(void) { /* Allocate continuation context on this SYS stack, and save pointer to it. */ cont_t s_cont __attribute__((aligned(16))); @@ -237,9 +341,9 @@ extern "C" void ICACHE_RAM_ATTR app_entry_redefinable(void) eboot_two_shots = 2; - fix_divider(); + FIX_DIVIDER(); ETS_PRINTF("\n\ncall_user_start()\n"); - ETS_DELAY_US(12000); + ETS_FLUSH(0); /* Call the entry point of the SDK code. */ call_user_start(); @@ -261,16 +365,17 @@ typedef int (*fp_SPIRead_t)(uint32_t addr, void *dest, size_t size); constexpr fp_SPIRead_t real_SPIRead = (fp_SPIRead_t)ROM_SPIRead; int ICACHE_RAM_ATTR SPIRead(uint32_t addr, void *dest, size_t size) { - // The very 1st read that goes by is to get the config flash size from - // image header. The NONOS SDK will update flashchip->chip_size. Then, a - // verification read is performed. Before this read is passed on we erase - // config sectors. + /* + The very 1st read that goes by is to get the config flash size from + image header. The NONOS SDK will update flashchip->chip_size. Then, a + verification read is performed. Before this read is passed on we erase + config sectors. + */ if (eboot_two_shots) { eboot_two_shots--; if (0 == eboot_two_shots) { - fix_divider(); check_and_erase_config(); - ETS_DELAY_US(12000); + ETS_FLUSH(0); } } @@ -279,45 +384,77 @@ int ICACHE_RAM_ATTR SPIRead(uint32_t addr, void *dest, size_t size) { return err; } - +#elif (ERASE_CONFIG_METHOD == 3) // Newest option +extern "C" void Cache_Read_Enable(uint8_t map, uint8_t p, uint8_t v); +extern "C" void Cache_Read_Disable(); +extern "C" uint32_t esp_c_magic_flash_chip_size(uint8_t byte); + +#if defined(DEBUG_ERASE_CONFIG) +void print_flashchip() { + ETS_PRINTF("\nflashchip->deviceId: 0x%08X, %8u\n", flashchip->deviceId, flashchip->deviceId); + ETS_PRINTF("flashchip->chip_size: 0x%08X, %8u\n", flashchip->chip_size, flashchip->chip_size); + ETS_PRINTF("flashchip->block_size: 0x%08X, %8u\n", flashchip->block_size, flashchip->block_size); + ETS_PRINTF("flashchip->sector_size: 0x%08X, %8u\n", flashchip->sector_size, flashchip->sector_size); + ETS_PRINTF("flashchip->page_size: 0x%08X, %8u\n", flashchip->page_size, flashchip->page_size); + ETS_PRINTF("flashchip->status_mask: 0x%08X, %8u\n", flashchip->status_mask, flashchip->status_mask); +} +#define PRINT_FLASHCHIP() print_flashchip() +#else +#define PRINT_FLASHCHIP() do{}while(false) #endif -}; -#endif // ERASE_CONFIG_H - +void set_flashchip_and_check_erase_config(uint8_t byte) { + /* + We patch and restore chip_size here. It should be noted that the ROM APIs + use both the chip_size and sector_size to validate calling parameters. + From what I see at least with esptool.py and core, there is a general + assumption that sector_size is always 4K. + + I don't see a need to set and restore sector_size at this time. + */ + PRINT_FLASHCHIP(); + uint32_t old_flash_size = flashchip->chip_size; + flashchip->chip_size = esp_c_magic_flash_chip_size((byte >> 4) & 0x0F); + PRINT_FLASHCHIP(); + if (flashchip->chip_size) { + check_and_erase_config(); + } + flashchip->chip_size = old_flash_size; +} -extern "C" void ICACHE_RAM_ATTR _Z22__run_user_rf_pre_initv(void) { - // fix_divider(); - real_uart_div_modify(0, UART_CLK_FREQ / 115200); - ETS_DELAY_US(150); - ets_uart_printf("\n__run_user_rf_pre_init()\n"); - ETS_DELAY_US(12000); +void ICACHE_RAM_ATTR erase_config_method3(void) { + /* Read configuration from flash before enabling Flash code execution. */ + uint8_t bytes[4] __attribute__((aligned(4))); + if (SPI_FLASH_RESULT_OK == SPIRead(0x0000, &bytes[0], sizeof(bytes))) { + Cache_Read_Enable(0, 0, 0); + set_flashchip_and_check_erase_config(bytes[3]); + Cache_Read_Disable(); + } } -/* - This helps keep the UART enabled longer so we can get a few more messages - printed. -*/ -#if 1 -extern struct rst_info resetInfo; -extern "C" void __pinMode( uint8_t pin, uint8_t mode ); -inline bool is_gpio_persistent(void) { - return REASON_EXCEPTION_RST <= resetInfo.reason && - REASON_SOFT_RESTART >= resetInfo.reason; -} +#include "cont.h" -extern "C" void pinMode( uint8_t pin, uint8_t mode ) { - static bool in_initPins = true; - if (in_initPins && (1 == pin)) { - if (!is_gpio_persistent()) { - // Restore pin to TX after Power-on and EXT_RST - __pinMode(pin, FUNCTION_0); - } - in_initPins = false; - return; - } +extern cont_t* g_pcont; +extern "C" void call_user_start(); - __pinMode( pin, mode ); +extern "C" void ICACHE_RAM_ATTR app_entry_redefinable(void) { + /* Allocate continuation context on this SYS stack, + and save pointer to it. */ + cont_t s_cont __attribute__((aligned(16))); + g_pcont = &s_cont; + + FIX_DIVIDER(); + erase_config_method3(); + + ETS_PRINTF("\n\ncall_user_start()\n"); + ETS_FLUSH(0); + + /* Call the entry point of the SDK code. */ + call_user_start(); } + #endif + +}; +#endif // ERASE_CONFIG_H diff --git a/cores/esp8266/erase_config.h b/cores/esp8266/erase_config.h index af94ff2a68..6677d5a182 100644 --- a/cores/esp8266/erase_config.h +++ b/cores/esp8266/erase_config.h @@ -38,6 +38,10 @@ bool check_and_erase_config(void); bool erase_config(const uint32_t flash_erase_mask); void enable_erase_config_at_link_time(void); +// Enable debug printing for verification at a near consistant 74880 bps across +// various reset causes. Also, reduces garbled printing. +// #define DEBUG_ERASE_CONFIG + #ifdef __cplusplus }; #endif diff --git a/libraries/ArduinoOTA/examples/OTAConfigErase/FlashInfo.ino b/libraries/ArduinoOTA/examples/OTAConfigErase/FlashInfo.ino index 92219944e0..98a5519910 100644 --- a/libraries/ArduinoOTA/examples/OTAConfigErase/FlashInfo.ino +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/FlashInfo.ino @@ -3,8 +3,15 @@ #define String_F(a) String(F(a)) void printFlashInfo(Print& oStream) { - oStream.println(String_F("Flash Size as reported by:")); - oStream.println(String_F(" flashchip->chip_size: 0x0") + String(flashchip->chip_size, HEX) + (", ") + String(flashchip->chip_size)); - oStream.println(String_F(" ESP.getFlashChipSize: 0x0") + String(ESP.getFlashChipSize(), HEX) + (", ") + String(ESP.getFlashChipSize())); - oStream.println(String_F(" ESP.getFlashChipRealSize: 0x0") + String(ESP.getFlashChipRealSize(), HEX) + (", ") + String(ESP.getFlashChipRealSize())); + oStream.println(String_F("Flash Info/Size as reported by:")); + oStream.println(String_F(" ESP.getFlashChipId: 0x0") + String(ESP.getFlashChipId(), HEX) + (", ") + String(ESP.getFlashChipId())); + oStream.println(String_F(" ESP.getFlashChipSizeByChipId: 0x0") + String(ESP.getFlashChipSizeByChipId(), HEX) + (", ") + String(ESP.getFlashChipSizeByChipId())); + oStream.println(String_F(" ESP.getFlashChipRealSize: 0x0") + String(ESP.getFlashChipRealSize(), HEX) + (", ") + String(ESP.getFlashChipRealSize())); + oStream.println(String_F(" ESP.getFlashChipSize: 0x0") + String(ESP.getFlashChipSize(), HEX) + (", ") + String(ESP.getFlashChipSize())); + oStream.println(String_F(" flashchip->deviceId: 0x0") + String(flashchip->deviceId, HEX) + (", ") + String(flashchip->deviceId)); + oStream.println(String_F(" flashchip->chip_size: 0x0") + String(flashchip->chip_size, HEX) + (", ") + String(flashchip->chip_size)); + oStream.println(String_F(" flashchip->block_size: 0x0") + String(flashchip->block_size, HEX) + (", ") + String(flashchip->block_size)); + oStream.println(String_F(" flashchip->sector_size: 0x0") + String(flashchip->sector_size, HEX) + (", ") + String(flashchip->sector_size)); + oStream.println(String_F(" flashchip->page_size: 0x0") + String(flashchip->page_size, HEX) + (", ") + String(flashchip->page_size)); + oStream.println(String_F(" flashchip->status_mask: 0x0") + String(flashchip->status_mask, HEX) + (", ") + String(flashchip->status_mask)); } diff --git a/libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino b/libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino index 0c4bc5231a..5a96715d48 100644 --- a/libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino @@ -1,4 +1,17 @@ +/* + This config_erase example used BasicOTA as a start point. + Build customization for this PoC: + * To select one of three erase config methods: + Update `#define ERASE_CONFIG_METHOD` in `cores/esp8266/erase_config.cpp` + A description of the different method is also there. + * To turn on debug printing: + Uncomment `#define DEBUG_ERASE_CONFIG` in `cores/esp8266/erase_config.h` + This requires Serial speed 74880 bps. + * To build w/o erase config option: + Comment out `#include "erase_config.h"` in both `cores/esp8266/Update.cpp` + and `Arduino.h` + */ #include #include #include @@ -20,6 +33,7 @@ const char* ssid = STASSID; const char* password = STAPSK; +#ifdef ERASE_CONFIG_H /* Erase Config Options ERASE_CONFIG_NONE ERASE_CONFIG_EEPROM @@ -30,6 +44,7 @@ const char* password = STAPSK; ERASE_CONFIG_ALL_DATA */ ERASE_CONFIG_MASK_t eraseConfigOption = ERASE_CONFIG_RF_CAL; //ERASE_CONFIG_BLANK_BIN; +#endif #define String_F(a) String(F(a)) @@ -52,8 +67,12 @@ void setTimeTZ(String& tz) { } void setup() { +#ifdef DEBUG_ERASE_CONFIG + Serial.begin(74880); +#else Serial.begin(74880); // Serial.begin(115200); +#endif delay(20); Serial.println(); Serial.println("setup ..."); @@ -105,9 +124,9 @@ void setup() { String type; if (ArduinoOTA.getCommand() == U_FLASH) { type = "sketch"; - +#ifdef ERASE_CONFIG_H Update.setEraseConfigOption(eraseConfigOption); - +#endif } else { // U_FS type = "filesystem"; } @@ -150,6 +169,7 @@ void printHelp(Print& oStream) { oStream.println(); oStream.println(F("Hot key help:")); oStream.println(); +#ifdef ERASE_CONFIG_H oStream.println(F(" Erase Config Options")); oStream.println(F(" 0 - ERASE_CONFIG_NONE")); oStream.println(F(" 1 - ERASE_CONFIG_EEPROM")); @@ -160,6 +180,7 @@ void printHelp(Print& oStream) { oStream.println(F(" 6 - ERASE_CONFIG_ALL_DATA")); oStream.println(F(" 9 - print detailed sector map")); oStream.println(); +#endif oStream.println(F(" Other Options")); oStream.println(F(" t - time information")); oStream.println(F(" u - umm_info")); @@ -236,6 +257,7 @@ int cmdLoop(Print& oStream, char hotKey) { WiFi.mode(WIFI_OFF); ESP.restart(); break; +#ifdef ERASE_CONFIG_H case '0': case '1': case '2': @@ -252,6 +274,7 @@ int cmdLoop(Print& oStream, char hotKey) { printFlashEraseMap(oStream); oStream.println(); break; +#endif default: return hotKeyHandlerAddOn(oStream, hotKey); break; From 4e2493ad6e667b5cdcd9ff026e6679de7d40e0e1 Mon Sep 17 00:00:00 2001 From: M Hightower <27247790+mhightower83@users.noreply.github.com> Date: Tue, 18 Feb 2020 17:52:16 -0800 Subject: [PATCH 13/24] Resolve merge conflict. Changed method for reading image header, freeing 16 bytes of iRAM" --- cores/esp8266/core_esp8266_postmortem.cpp | 4 +-- cores/esp8266/erase_config.cpp | 32 +++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/cores/esp8266/core_esp8266_postmortem.cpp b/cores/esp8266/core_esp8266_postmortem.cpp index 79074d8b20..36284011fb 100644 --- a/cores/esp8266/core_esp8266_postmortem.cpp +++ b/cores/esp8266/core_esp8266_postmortem.cpp @@ -147,10 +147,10 @@ void __wrap_system_restart_local() { // (determined empirically, might break) uint32_t offset = 0; if (rst_info.reason == REASON_SOFT_WDT_RST) { - offset = 0x190; + offset = 0x1a0; } else if (rst_info.reason == REASON_EXCEPTION_RST) { - offset = 0x180; + offset = 0x190; } else if (rst_info.reason == REASON_WDT_RST) { offset = 0x10; diff --git a/cores/esp8266/erase_config.cpp b/cores/esp8266/erase_config.cpp index 081651ad23..6c2d8d9646 100644 --- a/cores/esp8266/erase_config.cpp +++ b/cores/esp8266/erase_config.cpp @@ -403,6 +403,7 @@ void print_flashchip() { #define PRINT_FLASHCHIP() do{}while(false) #endif +#if 0 void set_flashchip_and_check_erase_config(uint8_t byte) { /* We patch and restore chip_size here. It should be noted that the ROM APIs @@ -432,6 +433,37 @@ void ICACHE_RAM_ATTR erase_config_method3(void) { } } +#else +void set_flashchip_and_check_erase_config(void) { + /* + We patch and restore chip_size here. It should be noted that the ROM APIs + use both the chip_size and sector_size to validate calling parameters. + From what I see at least with esptool.py and core, there is a general + assumption that sector_size is always 4K. + + I don't see a need to set and restore sector_size at this time. + */ + PRINT_FLASHCHIP(); + /* Since Flash code has been mapped for execution, we can just address the + flash image header located at the beginning of flash as iCACHE like memory. */ + uint32_t word32 = *((uint32_t *)0x40200000); + uint8_t *bytes = (uint8_t *)&word32; + ETS_PRINTF("\n*((uint32_t *)0x40200000) == 0x%08X\n", word32); + uint32_t old_flash_size = flashchip->chip_size; + flashchip->chip_size = esp_c_magic_flash_chip_size((bytes[3] >> 4) & 0x0F); + PRINT_FLASHCHIP(); + if (flashchip->chip_size) { + check_and_erase_config(); + } + flashchip->chip_size = old_flash_size; +} + +void ICACHE_RAM_ATTR erase_config_method3(void) { + Cache_Read_Enable(0, 0, 0); + set_flashchip_and_check_erase_config(); + Cache_Read_Disable(); +} +#endif #include "cont.h" From 39160c8fb970fd7bf890bff8be67fa55984149a0 Mon Sep 17 00:00:00 2001 From: M Hightower <27247790+mhightower83@users.noreply.github.com> Date: Tue, 18 Feb 2020 17:57:29 -0800 Subject: [PATCH 14/24] one more :( --- cores/esp8266/core_esp8266_postmortem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp8266/core_esp8266_postmortem.cpp b/cores/esp8266/core_esp8266_postmortem.cpp index 36284011fb..84aad50a0c 100644 --- a/cores/esp8266/core_esp8266_postmortem.cpp +++ b/cores/esp8266/core_esp8266_postmortem.cpp @@ -89,7 +89,7 @@ static void ets_printf_P(const char *str, ...) { vsnprintf(destStr, sizeof(destStr), str, argPtr); va_end(argPtr); while (*c) { - ets_uart_putc1(*(c++)); // This will send "\r\n" for each '\n' + ets_uart_putc1(*(c++)); } } From f1acb40ea46c544675952284c0f8eaf2bf7d32e9 Mon Sep 17 00:00:00 2001 From: M Hightower <27247790+mhightower83@users.noreply.github.com> Date: Tue, 18 Feb 2020 21:11:37 -0800 Subject: [PATCH 15/24] Access bin image header through ICACHE reduces IRAM usage by 16. Latest merge broke my TZ logic. Updated to the new configTime method. Updated WifiHealth to handle multiple/duplicate disconnects w/o connects. When a AP goes off line, a disconnect callback is done for each failed connect attempt. Before the total connect time was getting inflated. Now uses LED_BUILTIN for WiFi Up/Down state. Updated WifiHealth.ino to report total time connected and disconnected. "Connection Uptime" is the time since last connect. "Total Uptime" is the sum of all past uptimes. "Total Downtime" is the sum of all past downtimes. "min/max/avg" reportings are of past uptimes or past downtimes. Adjust comments for style? --- cores/esp8266/erase_config.cpp | 41 +------- .../OTAConfigErase/OTAConfigErase.ino | 33 +++---- .../examples/OTAConfigErase/WifiHealth.h | 7 +- .../examples/OTAConfigErase/WifiHealth.ino | 94 +++++++++++++------ 4 files changed, 91 insertions(+), 84 deletions(-) diff --git a/cores/esp8266/erase_config.cpp b/cores/esp8266/erase_config.cpp index 6c2d8d9646..1afee626fd 100644 --- a/cores/esp8266/erase_config.cpp +++ b/cores/esp8266/erase_config.cpp @@ -403,37 +403,6 @@ void print_flashchip() { #define PRINT_FLASHCHIP() do{}while(false) #endif -#if 0 -void set_flashchip_and_check_erase_config(uint8_t byte) { - /* - We patch and restore chip_size here. It should be noted that the ROM APIs - use both the chip_size and sector_size to validate calling parameters. - From what I see at least with esptool.py and core, there is a general - assumption that sector_size is always 4K. - - I don't see a need to set and restore sector_size at this time. - */ - PRINT_FLASHCHIP(); - uint32_t old_flash_size = flashchip->chip_size; - flashchip->chip_size = esp_c_magic_flash_chip_size((byte >> 4) & 0x0F); - PRINT_FLASHCHIP(); - if (flashchip->chip_size) { - check_and_erase_config(); - } - flashchip->chip_size = old_flash_size; -} - -void ICACHE_RAM_ATTR erase_config_method3(void) { - /* Read configuration from flash before enabling Flash code execution. */ - uint8_t bytes[4] __attribute__((aligned(4))); - if (SPI_FLASH_RESULT_OK == SPIRead(0x0000, &bytes[0], sizeof(bytes))) { - Cache_Read_Enable(0, 0, 0); - set_flashchip_and_check_erase_config(bytes[3]); - Cache_Read_Disable(); - } -} - -#else void set_flashchip_and_check_erase_config(void) { /* We patch and restore chip_size here. It should be noted that the ROM APIs @@ -446,11 +415,10 @@ void set_flashchip_and_check_erase_config(void) { PRINT_FLASHCHIP(); /* Since Flash code has been mapped for execution, we can just address the flash image header located at the beginning of flash as iCACHE like memory. */ - uint32_t word32 = *((uint32_t *)0x40200000); - uint8_t *bytes = (uint8_t *)&word32; - ETS_PRINTF("\n*((uint32_t *)0x40200000) == 0x%08X\n", word32); + const uint32_t imghdr_4bytes = *((uint32_t *)0x40200000); + const image_header_t *partial_imghdr = (const image_header_t *)&imghdr_4bytes; uint32_t old_flash_size = flashchip->chip_size; - flashchip->chip_size = esp_c_magic_flash_chip_size((bytes[3] >> 4) & 0x0F); + flashchip->chip_size = esp_c_magic_flash_chip_size((partial_imghdr->flash_size_freq >> 4) & 0x0F); PRINT_FLASHCHIP(); if (flashchip->chip_size) { check_and_erase_config(); @@ -459,11 +427,10 @@ void set_flashchip_and_check_erase_config(void) { } void ICACHE_RAM_ATTR erase_config_method3(void) { - Cache_Read_Enable(0, 0, 0); + Cache_Read_Enable(0, 0, 0); // 16K ICACHE set_flashchip_and_check_erase_config(); Cache_Read_Disable(); } -#endif #include "cont.h" diff --git a/libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino b/libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino index 5a96715d48..3f4da9e75d 100644 --- a/libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino @@ -2,25 +2,28 @@ This config_erase example used BasicOTA as a start point. Build customization for this PoC: + * To select one of three erase config methods: Update `#define ERASE_CONFIG_METHOD` in `cores/esp8266/erase_config.cpp` A description of the different method is also there. + * To turn on debug printing: Uncomment `#define DEBUG_ERASE_CONFIG` in `cores/esp8266/erase_config.h` This requires Serial speed 74880 bps. + * To build w/o erase config option: Comment out `#include "erase_config.h"` in both `cores/esp8266/Update.cpp` and `Arduino.h` - */ +*/ #include #include #include #include #include #include +#include #include "WifiHealth.h" - #include #include "AddOns.h" @@ -30,6 +33,10 @@ #define STAPSK "your-password" #endif +#ifndef LOCALTZ +#define LOCALTZ TZ_America_Los_Angeles +#endif + const char* ssid = STASSID; const char* password = STAPSK; @@ -53,17 +60,12 @@ ERASE_CONFIG_MASK_t eraseConfigOption = ERASE_CONFIG_RF_CAL; //ERASE_CONFIG_BLAN WiFiEventHandler handler1; WiFiEventHandler handler2; -extern char **environ; -void setTimeTZ(const char *tz) { - char **old_environ = environ; - char * tzEnv[2] = { (char *)tz, NULL }; - environ = &tzEnv[0]; - tzset(); - environ = old_environ; +#define LED_BUILTIN_ON (0) +void wifiLedOn(void) { + digitalWrite(LED_BUILTIN, (LED_BUILTIN_ON) ? 1 : 0); } - -void setTimeTZ(String& tz) { - setTimeTZ(tz.c_str()); +void wifiLedOff(void) { + digitalWrite(LED_BUILTIN, (LED_BUILTIN_ON) ? 0 : 1); } void setup() { @@ -76,13 +78,12 @@ void setup() { delay(20); Serial.println(); Serial.println("setup ..."); + pinMode(LED_BUILTIN, OUTPUT); + wifiLedOff(); WiFi.persistent(false); // w/o this a flash write occurs at every boot WiFi.mode(WIFI_OFF); - - String tz = F("TZ=PST+8PDT+7,M3.2.0M11.1.0"); - setTimeTZ(tz); - configTime(0, 0, "pool.ntp.org"); + configTime(LOCALTZ, "pool.ntp.org"); // Register wifi Event to control connection LED handler1 = WiFi.onStationModeConnected([](WiFiEventStationModeConnected data) { diff --git a/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.h b/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.h index 775f01d6c9..3af73a8fbb 100644 --- a/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.h +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.h @@ -5,18 +5,19 @@ bool printLocalTime(Print& oPrint, time_t timeIn = 0, const char *fmt = "%I:%M:% bool printLocalTime(Print& oPrint, time_t timeIn, const char *fmt); extern bool WifiUp; -//static unsigned long last_garp = 0; - typedef struct _WIFI_HEALTH { int32_t connected_count; int32_t rssi_sum; uint32_t rssi_count; time_t gtime_adjust; time_t connected_time; - time_t disconnected_time; time_t uptime_sum; time_t uptime_max; time_t uptime_min; + time_t disconnected_time; + time_t downtime_sum; + time_t downtime_max; + time_t downtime_min; uint8 bssid[6]; uint8_t channel; int8_t rssi_max; diff --git a/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.ino b/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.ino index dfcb23770c..f00eb8559c 100644 --- a/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.ino +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.ino @@ -1,5 +1,9 @@ -wifi_health_t wifiHealth = {0, 0, 0, 0, 0, 0, 0, 0, ~(time_t)0, "", 0, INT8_MIN, INT8_MAX, 0}; +wifi_health_t wifiHealth = { + 0, 0, 0, 0, + 0, 0, 0, (time_t)LONG_MAX, + 0, 0, 0, (time_t)LONG_MAX, + "", 0, INT8_MIN, INT8_MAX, 0}; WiFiDisconnectLog_t wifi_disconnect_log[MAX_CONNECTION_LOST_TIME_LOG] = {0, 0, 0, 0}; Ticker scheduleCheck; bool WifiUp = false; @@ -188,44 +192,46 @@ void updateWiFiStats(void) { } void onWiFiConnected(WiFiEventStationModeConnected data) { - (void) data; wifiHealth.channel = data.channel; memcpy(wifiHealth.bssid, data.bssid, sizeof(wifiHealth.bssid)); wifiHealth.connected_count++; - wifiHealth.connected_time = (time_t)(micros64() / 1000000); + time_t stime = (time_t)(micros64() / 1000000); + wifiHealth.connected_time = stime; wifiHealth.rssi = WiFi.RSSI(); WifiUp = true; - - // wifiLed.strobe(0); - // wifiLed.on(); - // last_garp = millis(); // init for doGarp(NULL); + wifiLedOn(); + + if (wifiHealth.disconnected_time) { + time_t downtime = stime - wifiHealth.disconnected_time; + wifiHealth.downtime_sum += downtime; + wifiHealth.downtime_max = max(wifiHealth.downtime_max, downtime); + wifiHealth.downtime_min = min(wifiHealth.downtime_min, downtime); + wifiHealth.disconnected_time = (time_t)0; + } } void onWiFiDisconnected(WiFiEventStationModeDisconnected data) { - (void) data; + /* After a disconnect, an attempt is made about every 3 secs to reconnect. + On each failed attempt, this function is called. */ WifiUp = false; - // wifiLed.strobe(0); - // wifiLed.off(); time_t gtime; time(>ime); time_t stime = (time_t)(micros64() / 1000000); - wifiHealth.disconnected_time = stime; wifiHealth.gtime_adjust = gtime - stime; if (wifiHealth.connected_time) { + wifiLedOff(); + wifiHealth.disconnected_time = stime; size_t last = wifiHealth.connected_count % MAX_CONNECTION_LOST_TIME_LOG; wifi_disconnect_log[last].time = stime; // Note, 1st entry is at [1]. wifi_disconnect_log[last].reason = data.reason; wifi_disconnect_log[last].channel = wifiHealth.channel; - wifi_disconnect_log[last].rssi = wifiHealth.rssi; + wifi_disconnect_log[last].rssi = wifiHealth.rssi; time_t uptime = stime - wifiHealth.connected_time; wifiHealth.uptime_sum += uptime; wifiHealth.uptime_max = max(wifiHealth.uptime_max, uptime); - if (~(time_t)0 != wifiHealth.uptime_min) { - wifiHealth.uptime_min = min(wifiHealth.uptime_min, uptime); - } else { - wifiHealth.uptime_min = uptime; - } + wifiHealth.uptime_min = min(wifiHealth.uptime_min, uptime); + wifiHealth.connected_time = (time_t)0; } } @@ -256,21 +262,53 @@ bool printWiFiStats(Print& oStream) { printUpTime(oStream, uptime); oStream.println(); - if (wifiHealth.uptime_max) { - oStream.print(String_F(" MAX: ")); - printUpTime(oStream, wifiHealth.uptime_max); + uptime = wifiHealth.uptime_sum; + if (uptime) { + oStream.print(String_F(" Total Uptime: ")); + printUpTime(oStream, uptime); oStream.println(); + + if (2 < wifiHealth.connected_count) { + if (wifiHealth.uptime_max) { + oStream.print(String_F(" MAX: ")); + printUpTime(oStream, wifiHealth.uptime_max); + oStream.println(); + } + if ((time_t)LONG_MAX != wifiHealth.uptime_min) { + oStream.print(String_F(" MIN: ")); + printUpTime(oStream, wifiHealth.uptime_min); + oStream.println(); + } + oStream.print(String_F(" AVG: ")); + uptime /= (decltype(uptime))(wifiHealth.connected_count - 1); + printUpTime(oStream, uptime); + oStream.println(); + } } - if (~(time_t)0 != wifiHealth.uptime_min) { - oStream.print(String_F(" MIN: ")); - printUpTime(oStream, wifiHealth.uptime_min); + + time_t downtime = wifiHealth.downtime_sum; + if (downtime) { + oStream.print(String_F(" Total Downtime: ")); + printUpTime(oStream, downtime); oStream.println(); + + if (2 < wifiHealth.connected_count) { + if (wifiHealth.downtime_max) { + oStream.print(String_F(" MAX: ")); + printUpTime(oStream, wifiHealth.downtime_max); + oStream.println(); + } + if ((time_t)LONG_MAX != wifiHealth.downtime_min) { + oStream.print(String_F(" MIN: ")); + printUpTime(oStream, wifiHealth.downtime_min); + oStream.println(); + } + oStream.print(String_F(" AVG: ")); + downtime /= (decltype(uptime))(wifiHealth.connected_count - 1); + printUpTime(oStream, downtime); + oStream.println(); + } } - oStream.print(String_F(" AVG: ")); - uptime += wifiHealth.uptime_sum; - uptime /= (decltype(uptime))wifiHealth.connected_count; - printUpTime(oStream, uptime); - oStream.println(); oStream.println(String_F(" Reconnects: ") + (wifiHealth.connected_count - 1)); if (wifiHealth.connected_count > 1) { From 4553009e6d0ebc6c255dfcfb56ed31ed88a64cd4 Mon Sep 17 00:00:00 2001 From: M Hightower <27247790+mhightower83@users.noreply.github.com> Date: Fri, 21 Feb 2020 20:02:58 -0800 Subject: [PATCH 16/24] Made function name longer to avoid possible name conflicts in the future. Using static caused the compiler to inline with functions with different IRAM attributes. Updated some comments. Maybe fixed CI host issue with Update.cpp. --- cores/esp8266/Updater.cpp | 2 +- cores/esp8266/erase_config.cpp | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/cores/esp8266/Updater.cpp b/cores/esp8266/Updater.cpp index c8d7d6cac1..26e0add8b9 100644 --- a/cores/esp8266/Updater.cpp +++ b/cores/esp8266/Updater.cpp @@ -68,7 +68,7 @@ void UpdaterClass::_reset() { } bool UpdaterClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) { -#ifdef ERASE_CONFIG_H +#if defined(ERASE_CONFIG_H) && !defined(HOST_MOCK) // Empty call so erase_config.cpp module to ensure it is built and linked in. enable_erase_config_at_link_time(); #endif diff --git a/cores/esp8266/erase_config.cpp b/cores/esp8266/erase_config.cpp index 1afee626fd..9c2cebe95e 100644 --- a/cores/esp8266/erase_config.cpp +++ b/cores/esp8266/erase_config.cpp @@ -52,12 +52,12 @@ size element in `flashchip` structure in ROM data, dRAM. This allows the flash erase to succeed. The original flash size is restored before starting the SDK. With the assumption -that the SDK will handle the size change properly. It should be noted that -only changing the size value in the flashchip table, is equivalent to what the -esptool.py is doing. +that the SDK will handle the size change properly. Note that only changing the +size value in the `flashchip` structure, is equivalent to what the esptool.py is +doing. I also added an example/test sketch for exercising the feature. It is -OTAEraseConfig. +OTAEraseConfig. It also gathers some WiFi signal/connection statistics. */ @@ -110,7 +110,9 @@ void enable_erase_config_at_link_time(void) { extern "C" void Cache_Read_Enable_2(void); extern "C" void Cache_Read_Disable_2(void); -int ICACHE_RAM_ATTR erase_sector(const uint32_t sector) { +/* Cannot be made static. The compiler will inline into erase_config and lose + ICACHE_RAM_ATTR. Make name longer to be more unique */ +int ICACHE_RAM_ATTR erase_config__erase_sector(const uint32_t sector) { /* Toggle Flash execution off and on, around ROM flash function calls. The SDK APIs would have normally handled this operation; however, @@ -137,7 +139,7 @@ bool IRAM_MAYBE erase_config(const uint32_t flash_erase_mask) { #elif (ERASE_CONFIG_METHOD == 2) if (0 != SPIEraseSector(sector)) { #elif (ERASE_CONFIG_METHOD == 3) - if (0 != erase_sector(sector)) { + if (0 != erase_config__erase_sector(sector)) { #endif ETS_PRINTF("Erase sector 0x%04X failed!\n", sector); return false; @@ -403,6 +405,8 @@ void print_flashchip() { #define PRINT_FLASHCHIP() do{}while(false) #endif +/* Cannot be made static, compiler will inline into erase_config_method3 and use + more IRAM. */ void set_flashchip_and_check_erase_config(void) { /* We patch and restore chip_size here. It should be noted that the ROM APIs From 39b9508ae5fcbcc2a931d54f82fb1203ee326a0b Mon Sep 17 00:00:00 2001 From: M Hightower <27247790+mhightower83@users.noreply.github.com> Date: Wed, 11 Mar 2020 12:45:56 -0700 Subject: [PATCH 17/24] CI issues --- .../ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino | 7 ++++--- .../ArduinoOTA/examples/OTAConfigErase/WifiHealth.ino | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino b/libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino index 3f4da9e75d..c50b351281 100644 --- a/libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino @@ -3,18 +3,19 @@ Build customization for this PoC: - * To select one of three erase config methods: + @ To select one of three erase config methods: Update `#define ERASE_CONFIG_METHOD` in `cores/esp8266/erase_config.cpp` A description of the different method is also there. - * To turn on debug printing: + @ To turn on debug printing: Uncomment `#define DEBUG_ERASE_CONFIG` in `cores/esp8266/erase_config.h` This requires Serial speed 74880 bps. - * To build w/o erase config option: + @ To build w/o erase config option: Comment out `#include "erase_config.h"` in both `cores/esp8266/Update.cpp` and `Arduino.h` */ +#include #include #include #include diff --git a/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.ino b/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.ino index f00eb8559c..0a6cf91e0f 100644 --- a/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.ino +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.ino @@ -3,7 +3,8 @@ wifi_health_t wifiHealth = { 0, 0, 0, 0, 0, 0, 0, (time_t)LONG_MAX, 0, 0, 0, (time_t)LONG_MAX, - "", 0, INT8_MIN, INT8_MAX, 0}; + "", 0, INT8_MIN, INT8_MAX, 0 +}; WiFiDisconnectLog_t wifi_disconnect_log[MAX_CONNECTION_LOST_TIME_LOG] = {0, 0, 0, 0}; Ticker scheduleCheck; bool WifiUp = false; From 2b2def228eba7b96a6a0c604d14ca96995630258 Mon Sep 17 00:00:00 2001 From: M Hightower <27247790+mhightower83@users.noreply.github.com> Date: Fri, 10 Jul 2020 11:53:03 -0700 Subject: [PATCH 18/24] Corrected new errors after upgrade to GCC 10.1 toolchain. Method 3 was failing with an unaligned access due to 32-bit word access being optomized to an 8-bit when accessing ICACHE memory. Added noinline with IRAM_CACHE attribute in module erase_config.cpp. Also, revised print macros to be more globally unique. --- cores/esp8266/core_esp8266_main.cpp | 22 +++--- cores/esp8266/erase_config.cpp | 102 +++++++++++++++------------- 2 files changed, 64 insertions(+), 60 deletions(-) diff --git a/cores/esp8266/core_esp8266_main.cpp b/cores/esp8266/core_esp8266_main.cpp index c55decfe10..29c80b6ef4 100644 --- a/cores/esp8266/core_esp8266_main.cpp +++ b/cores/esp8266/core_esp8266_main.cpp @@ -300,14 +300,14 @@ void init_done() { */ #if defined(DEBUG_ERASE_CONFIG) -extern "C" void fix_divider(void); -#define FIX_DIVIDER fix_divider -#define ETS_PRINTF(...) ets_uart_printf(__VA_ARGS__) -#define ETS_DELAY_US(a) ets_delay_us(a) +extern "C" void erase_config__fix_divider(void); +#define ERASE_CFG__FIX_DIVIDER erase_config__fix_divider +#define ERASE_CFG__ETS_PRINTF(...) ets_uart_printf(__VA_ARGS__) +#define ERASE_CFG__ETS_DELAY_US(a) ets_delay_us(a) #else -#define FIX_DIVIDER() do {} while(0) -#define ETS_PRINTF(...) do {} while(0) -#define ETS_DELAY_US(a) do {} while(0) +#define ERASE_CFG__FIX_DIVIDER() do {} while(0) +#define ERASE_CFG__ETS_PRINTF(...) do {} while(0) +#define ERASE_CFG__ETS_DELAY_US(a) do {} while(0) #endif @@ -319,8 +319,8 @@ extern "C" void app_entry_redefinable(void) cont_t s_cont __attribute__((aligned(16))); g_pcont = &s_cont; - FIX_DIVIDER(); - ETS_PRINTF("\n\ncall_user_start()\n"); + ERASE_CFG__FIX_DIVIDER(); + ERASE_CFG__ETS_PRINTF("\n\ncall_user_start()\n"); /* Call the entry point of the SDK code. */ call_user_start(); } @@ -344,8 +344,8 @@ extern "C" void user_init(void) { #if defined(DEBUG_ERASE_CONFIG) uart_div_modify(0, UART_CLK_FREQ / (74880)); - ETS_DELAY_US(150); - ETS_PRINTF("\nuser_init()\n"); + ERASE_CFG__ETS_DELAY_US(150); + ERASE_CFG__ETS_PRINTF("\nuser_init()\n"); #else uart_div_modify(0, UART_CLK_FREQ / (115200)); #endif diff --git a/cores/esp8266/erase_config.cpp b/cores/esp8266/erase_config.cpp index 9c2cebe95e..e58e344465 100644 --- a/cores/esp8266/erase_config.cpp +++ b/cores/esp8266/erase_config.cpp @@ -73,27 +73,29 @@ OTAEraseConfig. It also gathers some WiFi signal/connection statistics. #endif #ifdef DEBUG_ERASE_CONFIG -#define ETS_PRINTF(...) ets_uart_printf(__VA_ARGS__) -#define ETS_DELAY_US(a) ets_delay_us(a) -#define ETS_FLUSH(a) while((USS(a) >> USTXC) & 0xff){} -#define FIX_DIVIDER() fix_divider() +#define ERASE_CFG__ETS_PRINTF(...) ets_uart_printf(__VA_ARGS__) +#define ERASE_CFG__ETS_DELAY_US(a) ets_delay_us(a) +#define ERASE_CFG__ETS_FLUSH(a) while((USS(a) >> USTXC) & 0xff){} +#define ERASE_CFG__FIX_DIVIDER() erase_config__fix_divider() #else -#define ETS_PRINTF(...) do {} while(0) -#define ETS_DELAY_US(a) do {} while(0) -#define ETS_FLUSH(a) do {} while(false) -#define FIX_DIVIDER() do {} while(false) +#define ERASE_CFG__ETS_PRINTF(...) do {} while(0) +#define ERASE_CFG__ETS_DELAY_US(a) do {} while(0) +#define ERASE_CFG__ETS_FLUSH(a) do {} while(0) +#define ERASE_CFG__FIX_DIVIDER() do {} while(0) #endif #ifdef ERASE_CONFIG_H extern "C" { #include "user_interface.h" +#undef IRAM_ATTR +#define IRAM_ATTR __attribute__((noinline)) ICACHE_RAM_ATTR #if (ERASE_CONFIG_METHOD == 1) void __real_system_restart_local(); #define IRAM_MAYBE #elif (ERASE_CONFIG_METHOD == 2) -#define IRAM_MAYBE ICACHE_RAM_ATTR +#define IRAM_MAYBE IRAM_ATTR #elif (ERASE_CONFIG_METHOD == 3) #define IRAM_MAYBE #else @@ -110,9 +112,9 @@ void enable_erase_config_at_link_time(void) { extern "C" void Cache_Read_Enable_2(void); extern "C" void Cache_Read_Disable_2(void); -/* Cannot be made static. The compiler will inline into erase_config and lose - ICACHE_RAM_ATTR. Make name longer to be more unique */ -int ICACHE_RAM_ATTR erase_config__erase_sector(const uint32_t sector) { +/* The compiler will inline into erase_config and lose ICACHE_RAM_ATTR unless + __attribute__((noinline)) is added. IRAM_ATTR is modified to include noinline */ +static int IRAM_ATTR erase_config__erase_sector(const uint32_t sector) { /* Toggle Flash execution off and on, around ROM flash function calls. The SDK APIs would have normally handled this operation; however, @@ -141,10 +143,10 @@ bool IRAM_MAYBE erase_config(const uint32_t flash_erase_mask) { #elif (ERASE_CONFIG_METHOD == 3) if (0 != erase_config__erase_sector(sector)) { #endif - ETS_PRINTF("Erase sector 0x%04X failed!\n", sector); + ERASE_CFG__ETS_PRINTF("Erase sector 0x%04X failed!\n", sector); return false; } else { - ETS_PRINTF("Erased sector 0x%04X\n", sector); + ERASE_CFG__ETS_PRINTF("Erased sector 0x%04X\n", sector); } } } @@ -176,11 +178,11 @@ bool IRAM_MAYBE check_and_erase_config(void) { ebcmd->args[i] = 0U; if (erase_flash_option) { - ETS_PRINTF("\nerase_config(0x%03X)\n", erase_flash_option); + ERASE_CFG__ETS_PRINTF("\nerase_config(0x%03X)\n", erase_flash_option); #if (ERASE_CONFIG_METHOD == 1) erase_config(erase_flash_option); - ETS_PRINTF("\n__real_system_restart_local\n\n"); - ETS_FLUSH(0); + ERASE_CFG__ETS_PRINTF("\n__real_system_restart_local\n\n"); + ERASE_CFG__ETS_FLUSH(0); __real_system_restart_local(); while(true){} #elif (ERASE_CONFIG_METHOD == 2) || (ERASE_CONFIG_METHOD == 3) @@ -188,7 +190,7 @@ bool IRAM_MAYBE check_and_erase_config(void) { #endif } } else { - ETS_PRINTF("\nNo OTA erase flags\n"); + ERASE_CFG__ETS_PRINTF("\nNo OTA erase flags\n"); } return true; } @@ -231,10 +233,10 @@ typedef enum ROM_RST_REASON { /* Comments on the right are from RTOS SDK */ SDIO_RESET = 6, /* *//**<6, Reset by SLC module, reset digital core*/ } ROM_RST_REASON_t; -constexpr volatile uint32_t *RTC_SYS = (volatile uint32_t*)0x60001100; +#define RTC_SYS ((volatile uint32_t*)0x60001100) extern uint32_t rtc_get_reset_reason(void); -bool ICACHE_RAM_ATTR is_cpu_freq_cal(void) { +bool IRAM_ATTR is_cpu_freq_cal(void) { const uint32_t rtc_sys_reason = RTC_SYS[0]; const uint32_t rom_api_reason = rtc_get_reset_reason(); if (1 >= rtc_sys_reason && OWDT_RESET != rom_api_reason) { @@ -254,12 +256,12 @@ bool ICACHE_RAM_ATTR is_cpu_freq_cal(void) { is not in IRAM and reauires SDK initialization. */ #ifndef ROM_uart_div_modify -#define ROM_uart_div_modify 0x400039d8 +#define ROM_uart_div_modify 0x400039d8U #endif typedef void (*fp_uart_div_modify_t)(uint32_t uart_no, uint32 DivLatchValue); -constexpr fp_uart_div_modify_t real_uart_div_modify = (fp_uart_div_modify_t)ROM_uart_div_modify; +#define real_uart_div_modify ((fp_uart_div_modify_t)ROM_uart_div_modify) -void ICACHE_RAM_ATTR fix_divider(void) { +void IRAM_ATTR erase_config__fix_divider(void) { /* When reset cause is not power-on or EXT_RST the CPU crystal calibration has been done and there is no need to correct. With the exception of some @@ -271,17 +273,17 @@ void ICACHE_RAM_ATTR fix_divider(void) { divider = UART_CLK_FREQ / 74880; } real_uart_div_modify(0, divider); - ETS_DELAY_US(150); + ERASE_CFG__ETS_DELAY_US(150); } /* Something to see when we are in the SDK initialization. */ -extern "C" void ICACHE_RAM_ATTR _Z22__run_user_rf_pre_initv(void) { +extern "C" void IRAM_ATTR _Z22__run_user_rf_pre_initv(void) { real_uart_div_modify(0, UART_CLK_FREQ / 115200); - ETS_DELAY_US(150); - ETS_PRINTF("\n__run_user_rf_pre_init()\n"); - ETS_FLUSH(0); + ERASE_CFG__ETS_DELAY_US(150); + ERASE_CFG__ETS_PRINTF("\n__run_user_rf_pre_init()\n"); + ERASE_CFG__ETS_FLUSH(0); } #if 1 @@ -335,7 +337,7 @@ int eboot_two_shots __attribute__((section(".noinit"))); extern cont_t* g_pcont; extern "C" void call_user_start(); -extern "C" void ICACHE_RAM_ATTR app_entry_redefinable(void) { +extern "C" void IRAM_ATTR app_entry_redefinable(void) { /* Allocate continuation context on this SYS stack, and save pointer to it. */ cont_t s_cont __attribute__((aligned(16))); @@ -343,17 +345,17 @@ extern "C" void ICACHE_RAM_ATTR app_entry_redefinable(void) { eboot_two_shots = 2; - FIX_DIVIDER(); - ETS_PRINTF("\n\ncall_user_start()\n"); - ETS_FLUSH(0); + ERASE_CFG__FIX_DIVIDER(); + ERASE_CFG__ETS_PRINTF("\n\ncall_user_start()\n"); + ERASE_CFG__ETS_FLUSH(0); /* Call the entry point of the SDK code. */ call_user_start(); } -void ICACHE_RAM_ATTR dbg_log_SPIRead(uint32_t addr, void *dest, size_t size, int err) __attribute__((weak)); -void ICACHE_RAM_ATTR dbg_log_SPIRead(uint32_t addr, void *dest, size_t size, int err) { +void IRAM_ATTR dbg_log_SPIRead(uint32_t addr, void *dest, size_t size, int err) __attribute__((weak)); +void IRAM_ATTR dbg_log_SPIRead(uint32_t addr, void *dest, size_t size, int err) { (void)addr; (void)dest; (void)size; @@ -364,9 +366,9 @@ void ICACHE_RAM_ATTR dbg_log_SPIRead(uint32_t addr, void *dest, size_t size, int #define ROM_SPIRead 0x40004b1cU #endif typedef int (*fp_SPIRead_t)(uint32_t addr, void *dest, size_t size); -constexpr fp_SPIRead_t real_SPIRead = (fp_SPIRead_t)ROM_SPIRead; +#define real_SPIRead ((fp_SPIRead_t)ROM_SPIRead) -int ICACHE_RAM_ATTR SPIRead(uint32_t addr, void *dest, size_t size) { +int IRAM_ATTR SPIRead(uint32_t addr, void *dest, size_t size) { /* The very 1st read that goes by is to get the config flash size from image header. The NONOS SDK will update flashchip->chip_size. Then, a @@ -377,7 +379,7 @@ int ICACHE_RAM_ATTR SPIRead(uint32_t addr, void *dest, size_t size) { eboot_two_shots--; if (0 == eboot_two_shots) { check_and_erase_config(); - ETS_FLUSH(0); + ERASE_CFG__ETS_FLUSH(0); } } @@ -393,12 +395,12 @@ extern "C" uint32_t esp_c_magic_flash_chip_size(uint8_t byte); #if defined(DEBUG_ERASE_CONFIG) void print_flashchip() { - ETS_PRINTF("\nflashchip->deviceId: 0x%08X, %8u\n", flashchip->deviceId, flashchip->deviceId); - ETS_PRINTF("flashchip->chip_size: 0x%08X, %8u\n", flashchip->chip_size, flashchip->chip_size); - ETS_PRINTF("flashchip->block_size: 0x%08X, %8u\n", flashchip->block_size, flashchip->block_size); - ETS_PRINTF("flashchip->sector_size: 0x%08X, %8u\n", flashchip->sector_size, flashchip->sector_size); - ETS_PRINTF("flashchip->page_size: 0x%08X, %8u\n", flashchip->page_size, flashchip->page_size); - ETS_PRINTF("flashchip->status_mask: 0x%08X, %8u\n", flashchip->status_mask, flashchip->status_mask); + ERASE_CFG__ETS_PRINTF("\nflashchip->deviceId: 0x%08X, %8u\n", flashchip->deviceId, flashchip->deviceId); + ERASE_CFG__ETS_PRINTF("flashchip->chip_size: 0x%08X, %8u\n", flashchip->chip_size, flashchip->chip_size); + ERASE_CFG__ETS_PRINTF("flashchip->block_size: 0x%08X, %8u\n", flashchip->block_size, flashchip->block_size); + ERASE_CFG__ETS_PRINTF("flashchip->sector_size: 0x%08X, %8u\n", flashchip->sector_size, flashchip->sector_size); + ERASE_CFG__ETS_PRINTF("flashchip->page_size: 0x%08X, %8u\n", flashchip->page_size, flashchip->page_size); + ERASE_CFG__ETS_PRINTF("flashchip->status_mask: 0x%08X, %8u\n", flashchip->status_mask, flashchip->status_mask); } #define PRINT_FLASHCHIP() print_flashchip() #else @@ -419,7 +421,9 @@ void set_flashchip_and_check_erase_config(void) { PRINT_FLASHCHIP(); /* Since Flash code has been mapped for execution, we can just address the flash image header located at the beginning of flash as iCACHE like memory. */ - const uint32_t imghdr_4bytes = *((uint32_t *)0x40200000); + /* the tool chain update is optomizing out my 32-bit access to an 8-bit + unless I use volatile */ + const uint32_t imghdr_4bytes = *((const uint32_t volatile *)0x40200000); const image_header_t *partial_imghdr = (const image_header_t *)&imghdr_4bytes; uint32_t old_flash_size = flashchip->chip_size; flashchip->chip_size = esp_c_magic_flash_chip_size((partial_imghdr->flash_size_freq >> 4) & 0x0F); @@ -430,7 +434,7 @@ void set_flashchip_and_check_erase_config(void) { flashchip->chip_size = old_flash_size; } -void ICACHE_RAM_ATTR erase_config_method3(void) { +void IRAM_ATTR erase_config_method3(void) { Cache_Read_Enable(0, 0, 0); // 16K ICACHE set_flashchip_and_check_erase_config(); Cache_Read_Disable(); @@ -441,17 +445,17 @@ void ICACHE_RAM_ATTR erase_config_method3(void) { extern cont_t* g_pcont; extern "C" void call_user_start(); -extern "C" void ICACHE_RAM_ATTR app_entry_redefinable(void) { +extern "C" void IRAM_ATTR app_entry_redefinable(void) { /* Allocate continuation context on this SYS stack, and save pointer to it. */ cont_t s_cont __attribute__((aligned(16))); g_pcont = &s_cont; - FIX_DIVIDER(); + ERASE_CFG__FIX_DIVIDER(); erase_config_method3(); - ETS_PRINTF("\n\ncall_user_start()\n"); - ETS_FLUSH(0); + ERASE_CFG__ETS_PRINTF("\n\ncall_user_start()\n"); + ERASE_CFG__ETS_FLUSH(0); /* Call the entry point of the SDK code. */ call_user_start(); From ab9b6b2051d6280362bce67de48a810d9d0ab47a Mon Sep 17 00:00:00 2001 From: M Hightower <27247790+mhightower83@users.noreply.github.com> Date: Wed, 2 Sep 2020 10:08:09 -0700 Subject: [PATCH 19/24] Corrected manual merge error, with init order of class. --- cores/esp8266/Updater.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp8266/Updater.cpp b/cores/esp8266/Updater.cpp index 7fb0de7a58..6f9c6935ef 100644 --- a/cores/esp8266/Updater.cpp +++ b/cores/esp8266/Updater.cpp @@ -36,10 +36,10 @@ UpdaterClass::UpdaterClass() , _startAddress(0) , _currentAddress(0) , _command(U_FLASH) +, _ledPin(-1) #ifdef ERASE_CONFIG_H , _eraseConfigOption(ERASE_CONFIG_BLANK_BIN) #endif -, _ledPin(-1) , _hash(nullptr) , _verify(nullptr) , _progress_callback(nullptr) From 94365e191743cf7eb9a80af85f44743a3e2ef66c Mon Sep 17 00:00:00 2001 From: M Hightower <27247790+mhightower83@users.noreply.github.com> Date: Tue, 10 Nov 2020 22:51:41 -0800 Subject: [PATCH 20/24] Fixed merge errors and code rot. --- cores/esp8266/Updater.cpp | 4 ---- cores/esp8266/Updater.h | 6 ++++-- .../ArduinoOTA/examples/OTAConfigErase/TelnetAgent.ino | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/cores/esp8266/Updater.cpp b/cores/esp8266/Updater.cpp index 720bae2f47..a340ab9ed0 100644 --- a/cores/esp8266/Updater.cpp +++ b/cores/esp8266/Updater.cpp @@ -1,7 +1,6 @@ #include "Updater.h" #include "eboot_command.h" #include -#include #include "StackThunk.h" //#define DEBUG_UPDATER Serial @@ -28,9 +27,6 @@ extern "C" uint32_t _FS_start; extern "C" uint32_t _FS_end; UpdaterClass::UpdaterClass() -#ifdef ERASE_CONFIG_H -, _eraseConfigOption(ERASE_CONFIG_BLANK_BIN) -#endif { #if ARDUINO_SIGNING installSignature(&esp8266::updaterSigningHash, &esp8266::updaterSigningVerifier); diff --git a/cores/esp8266/Updater.h b/cores/esp8266/Updater.h index 7264fcbe52..f3c01df35f 100644 --- a/cores/esp8266/Updater.h +++ b/cores/esp8266/Updater.h @@ -5,6 +5,7 @@ #include #include #include +#include #define UPDATE_ERROR_OK (0) #define UPDATE_ERROR_WRITE (1) @@ -205,8 +206,9 @@ class UpdaterClass { int _ledPin = -1; uint8_t _ledOn; - uint32_t _eraseConfigOption; - +#ifdef ERASE_CONFIG_H + uint32_t _eraseConfigOption = ERASE_CONFIG_BLANK_BIN; +#endif // Optional signed binary verification UpdaterHashClass *_hash = nullptr; UpdaterVerifyClass *_verify = nullptr; diff --git a/libraries/ArduinoOTA/examples/OTAConfigErase/TelnetAgent.ino b/libraries/ArduinoOTA/examples/OTAConfigErase/TelnetAgent.ino index 9fdfbb9473..04bcc456e2 100644 --- a/libraries/ArduinoOTA/examples/OTAConfigErase/TelnetAgent.ino +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/TelnetAgent.ino @@ -89,7 +89,7 @@ void handleTelnetAgent(void) { len = std::min(len, (size_t)STACK_PROTECTOR); if (len) { uint8_t sbuf[len]; - size_t serial_got = telnetOut.readBytes(sbuf, len); + int serial_got = telnetOut.readBytes(sbuf, len); // push UART data to all connected telnet clients for (int i = 0; i < MAX_SRV_CLIENTS; i++) // if client.availableForWrite() was 0 (congested) From 5234f3fda5204d7e2b61ccdc39b18f8a7171a093 Mon Sep 17 00:00:00 2001 From: M Hightower <27247790+mhightower83@users.noreply.github.com> Date: Wed, 11 Nov 2020 09:41:00 -0800 Subject: [PATCH 21/24] Updated wifi_health_t and WiFiDisconnectLog_t to take advange of C++ element initialization options. --- .../examples/OTAConfigErase/WifiHealth.h | 42 +++++++++---------- .../examples/OTAConfigErase/WifiHealth.ino | 16 +++---- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.h b/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.h index 3af73a8fbb..597c8afad2 100644 --- a/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.h +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.h @@ -6,23 +6,23 @@ bool printLocalTime(Print& oPrint, time_t timeIn, const char *fmt); extern bool WifiUp; typedef struct _WIFI_HEALTH { - int32_t connected_count; - int32_t rssi_sum; - uint32_t rssi_count; - time_t gtime_adjust; - time_t connected_time; - time_t uptime_sum; - time_t uptime_max; - time_t uptime_min; - time_t disconnected_time; - time_t downtime_sum; - time_t downtime_max; - time_t downtime_min; - uint8 bssid[6]; - uint8_t channel; - int8_t rssi_max; - int8_t rssi_min; - int8_t rssi; + int32_t connected_count = 0; + int32_t rssi_sum = 0; + uint32_t rssi_count = 0; + time_t gtime_adjust = 0; + time_t connected_time = 0; + time_t uptime_sum = 0; + time_t uptime_max = 0; + time_t uptime_min = (time_t)LONG_MAX; + time_t disconnected_time = 0; + time_t downtime_sum = 0; + time_t downtime_max = 0; + time_t downtime_min = (time_t)LONG_MAX; + uint8 bssid[6] = ""; + uint8_t channel = 0; + int8_t rssi_max = INT8_MIN; + int8_t rssi_min = INT8_MAX; + int8_t rssi = 0; } wifi_health_t; extern wifi_health_t wifiHealth; @@ -31,10 +31,10 @@ extern wifi_health_t wifiHealth; #define MAX_CONNECTION_LOST_TIME_LOG (8) #endif typedef struct _WIFI_DISCONNECT_LOG { - time_t time; - uint8_t channel; - uint8_t reason; - int8_t rssi; + time_t time = 0; + uint8_t channel = 0; + uint8_t reason = 0; + int8_t rssi = 0; } WiFiDisconnectLog_t; extern WiFiDisconnectLog_t wifi_disconnect_log[MAX_CONNECTION_LOST_TIME_LOG]; diff --git a/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.ino b/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.ino index 0a6cf91e0f..542d981228 100644 --- a/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.ino +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.ino @@ -1,11 +1,13 @@ -wifi_health_t wifiHealth = { - 0, 0, 0, 0, - 0, 0, 0, (time_t)LONG_MAX, - 0, 0, 0, (time_t)LONG_MAX, - "", 0, INT8_MIN, INT8_MAX, 0 -}; -WiFiDisconnectLog_t wifi_disconnect_log[MAX_CONNECTION_LOST_TIME_LOG] = {0, 0, 0, 0}; +wifi_health_t wifiHealth; +//D = { +//D 0, 0, 0, 0, +//D 0, 0, 0, (time_t)LONG_MAX, +//D 0, 0, 0, (time_t)LONG_MAX, +//D "", 0, INT8_MIN, INT8_MAX, 0 +//D }; +WiFiDisconnectLog_t wifi_disconnect_log[MAX_CONNECTION_LOST_TIME_LOG]; +//D = {0, 0, 0, 0}; Ticker scheduleCheck; bool WifiUp = false; From 0b015182483747a520fcd0c05c4277080b0fa8da Mon Sep 17 00:00:00 2001 From: M Hightower <27247790+mhightower83@users.noreply.github.com> Date: Sun, 14 Mar 2021 16:03:02 -0700 Subject: [PATCH 22/24] Replaced ROM_uart_div_modify and associated logic, with the current method of handling direct ROM calls. --- cores/esp8266/erase_config.cpp | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/cores/esp8266/erase_config.cpp b/cores/esp8266/erase_config.cpp index e58e344465..db0bb20b4b 100644 --- a/cores/esp8266/erase_config.cpp +++ b/cores/esp8266/erase_config.cpp @@ -248,19 +248,6 @@ bool IRAM_ATTR is_cpu_freq_cal(void) { return true; } -/* - Here we use uart_div_modify in the Boot ROM. Note the Boot ROM version does - not do any input validation. - - The SDK has an overide on uart_div_modify. We cannot use its replacement. It - is not in IRAM and reauires SDK initialization. - */ -#ifndef ROM_uart_div_modify -#define ROM_uart_div_modify 0x400039d8U -#endif -typedef void (*fp_uart_div_modify_t)(uint32_t uart_no, uint32 DivLatchValue); -#define real_uart_div_modify ((fp_uart_div_modify_t)ROM_uart_div_modify) - void IRAM_ATTR erase_config__fix_divider(void) { /* When reset cause is not power-on or EXT_RST the CPU crystal calibration @@ -272,7 +259,14 @@ void IRAM_ATTR erase_config__fix_divider(void) { if (is_cpu_freq_cal()) { divider = UART_CLK_FREQ / 74880; } - real_uart_div_modify(0, divider); + /* + Here we use uart_div_modify in the Boot ROM. Note the Boot ROM version does + not do any input validation. + + The SDK has an overide on uart_div_modify. We cannot use its replacement. It + is not in IRAM and reauires SDK initialization. + */ + rom_uart_div_modify(0, divider); ERASE_CFG__ETS_DELAY_US(150); } From 217b2d288fb393081687636cc836c4da0a3f4e08 Mon Sep 17 00:00:00 2001 From: M Hightower <27247790+mhightower83@users.noreply.github.com> Date: Sat, 3 Apr 2021 15:31:16 -0700 Subject: [PATCH 23/24] Cleanup merge conflict. Now use IRAM_ATTR and _NOINLINE_STATIC w/o changes. Fixed missed edit (rom_uart_div_modify) for debug build. --- cores/esp8266/erase_config.cpp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/cores/esp8266/erase_config.cpp b/cores/esp8266/erase_config.cpp index db0bb20b4b..c1b1871f2e 100644 --- a/cores/esp8266/erase_config.cpp +++ b/cores/esp8266/erase_config.cpp @@ -89,8 +89,6 @@ OTAEraseConfig. It also gathers some WiFi signal/connection statistics. extern "C" { #include "user_interface.h" -#undef IRAM_ATTR -#define IRAM_ATTR __attribute__((noinline)) ICACHE_RAM_ATTR #if (ERASE_CONFIG_METHOD == 1) void __real_system_restart_local(); #define IRAM_MAYBE @@ -112,13 +110,14 @@ void enable_erase_config_at_link_time(void) { extern "C" void Cache_Read_Enable_2(void); extern "C" void Cache_Read_Disable_2(void); -/* The compiler will inline into erase_config and lose ICACHE_RAM_ATTR unless - __attribute__((noinline)) is added. IRAM_ATTR is modified to include noinline */ -static int IRAM_ATTR erase_config__erase_sector(const uint32_t sector) { +_NOINLINE_STATIC // Stop static inlining which would have discard IRAM_ATTR +int IRAM_ATTR erase_config__erase_sector(const uint32_t sector) { /* - Toggle Flash execution off and on, around ROM flash function calls. - The SDK APIs would have normally handled this operation; however, - it is too early for it to take calls. + Toggle Flash execution off and on, around ROM flash function calls. The + SDK APIs system calls would have normally handled this operation; + however, it is too early for it to take calls. Note, the functions + Cache_Read_Disable_2 and Cache_Read_Enable_2 are from the SDK; however, + they need no prior initialization to work. */ int rc; Cache_Read_Disable_2(); @@ -274,7 +273,7 @@ void IRAM_ATTR erase_config__fix_divider(void) { Something to see when we are in the SDK initialization. */ extern "C" void IRAM_ATTR _Z22__run_user_rf_pre_initv(void) { - real_uart_div_modify(0, UART_CLK_FREQ / 115200); + rom_uart_div_modify(0, UART_CLK_FREQ / 115200); ERASE_CFG__ETS_DELAY_US(150); ERASE_CFG__ETS_PRINTF("\n__run_user_rf_pre_init()\n"); ERASE_CFG__ETS_FLUSH(0); @@ -401,8 +400,7 @@ void print_flashchip() { #define PRINT_FLASHCHIP() do{}while(false) #endif -/* Cannot be made static, compiler will inline into erase_config_method3 and use - more IRAM. */ +_NOINLINE_STATIC // Keep code in Flash to save IRAM void set_flashchip_and_check_erase_config(void) { /* We patch and restore chip_size here. It should be noted that the ROM APIs From c44a3f9db218b84db6612a9068fa42bcfadb21a3 Mon Sep 17 00:00:00 2001 From: M Hightower <27247790+mhightower83@users.noreply.github.com> Date: Sat, 21 Aug 2021 20:58:37 -0700 Subject: [PATCH 24/24] Changed to comply with strict-aliasing rules. Changes based on discussions in https://github.com/esp8266/Arduino/issues/8261#issuecomment-903348302 --- cores/esp8266/erase_config.cpp | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/cores/esp8266/erase_config.cpp b/cores/esp8266/erase_config.cpp index 4a0615f792..b0b3e93001 100644 --- a/cores/esp8266/erase_config.cpp +++ b/cores/esp8266/erase_config.cpp @@ -417,12 +417,23 @@ void set_flashchip_and_check_erase_config(void) { PRINT_FLASHCHIP(); /* Since Flash code has been mapped for execution, we can just address the flash image header located at the beginning of flash as iCACHE like memory. */ - /* the tool chain update is optomizing out my 32-bit access to an 8-bit - unless I use volatile */ - const uint32_t imghdr_4bytes = *((const uint32_t volatile *)0x40200000); - const image_header_t *partial_imghdr = (const image_header_t *)&imghdr_4bytes; + const uint32_t *icache_flash = (const uint32_t *)0x40200000u; + union { // to comply with strict-aliasing rules + image_header_t hdr; // total size 8 bytes + uint32_t u32; // we only need the 1st 4 bytets + } imghdr_4bytes; + // read first 4 byte (magic byte + flash config) + imghdr_4bytes.u32 = *icache_flash; uint32_t old_flash_size = flashchip->chip_size; - flashchip->chip_size = esp_c_magic_flash_chip_size((partial_imghdr->flash_size_freq >> 4) & 0x0F); + /* + ICACHE memory read requires aligned word transfers. Because + imghdr_4bytes.hdr.flash_size_freq is a byte value, the GCC 10.3 compiler + tends to optimize out our 32-bit access for 8-bit access. If we reference + the 32-bit word from Extended ASM, this persuades the compiler to keep the + 32-bit register load and extract the 8-bit value later. + */ + asm volatile("# imghdr_4bytes.u32 => %0" ::"r"(imghdr_4bytes.u32)); + flashchip->chip_size = esp_c_magic_flash_chip_size(imghdr_4bytes.hdr.flash_size_freq >> 4); PRINT_FLASHCHIP(); if (flashchip->chip_size) { check_and_erase_config();