diff --git a/cores/esp8266/Arduino.h b/cores/esp8266/Arduino.h index ee9a7964f0..9b8f064cd1 100644 --- a/cores/esp8266/Arduino.h +++ b/cores/esp8266/Arduino.h @@ -40,6 +40,7 @@ extern "C" { #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 d517f6a496..6db52f0bb3 100644 --- a/cores/esp8266/Esp.cpp +++ b/cores/esp8266/Esp.cpp @@ -327,7 +327,39 @@ 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; + } +} +#endif + #if !FLASH_MAP_SUPPORT +#ifdef ERASE_CONFIG_H +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) @@ -349,6 +381,7 @@ uint32_t EspClass::magicFlashChipSize(uint8_t byte) { } } #endif +#endif uint32_t EspClass::magicFlashChipSpeed(uint8_t byte) { switch(byte & 0x0F) { diff --git a/cores/esp8266/Updater.cpp b/cores/esp8266/Updater.cpp index c194c1d23f..862fc4e0aa 100644 --- a/cores/esp8266/Updater.cpp +++ b/cores/esp8266/Updater.cpp @@ -62,6 +62,11 @@ void UpdaterClass::_reset() { } bool UpdaterClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) { +#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 + if(_size > 0){ #ifdef DEBUG_UPDATER DEBUG_UPDATER.println(F("[begin] already running")); @@ -82,7 +87,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.")); @@ -129,7 +134,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; } } @@ -312,10 +317,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 @@ -325,6 +337,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] = FS_start - 0x40200000; @@ -373,7 +386,7 @@ bool UpdaterClass::_writeBuffer(){ modifyFlashMode = true; } } - + if (eraseResult) { if(!_async) yield(); writeResult = ESP.flashWrite(_currentAddress, _buffer, _bufferLen); @@ -457,7 +470,7 @@ bool UpdaterClass::_verifyEnd() { uint8_t buf[4] __attribute__((aligned(4))); if(!ESP.flashRead(_startAddress, (uint32_t *) &buf[0], 4)) { _currentAddress = (_startAddress); - _setError(UPDATE_ERROR_READ); + _setError(UPDATE_ERROR_READ); return false; } @@ -469,7 +482,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; } @@ -481,7 +494,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; } #endif diff --git a/cores/esp8266/Updater.h b/cores/esp8266/Updater.h index 911ddf7784..d6293611ea 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) @@ -52,7 +53,7 @@ class UpdaterVerifyClass { class UpdaterClass { public: typedef std::function THandlerFunction_Progress; - + UpdaterClass(); ~UpdaterClass(); @@ -65,6 +66,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 */ @@ -181,7 +189,7 @@ class UpdaterClass { bool _verifyHeader(uint8_t data); bool _verifyEnd(); - void _setError(int error); + void _setError(int error); bool _async = false; uint8_t _error = 0; @@ -199,6 +207,9 @@ class UpdaterClass { int _ledPin = -1; uint8_t _ledOn; +#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/cores/esp8266/core_esp8266_main.cpp b/cores/esp8266/core_esp8266_main.cpp index 430808f19d..281cdd8649 100644 --- a/cores/esp8266/core_esp8266_main.cpp +++ b/cores/esp8266/core_esp8266_main.cpp @@ -354,6 +354,17 @@ void init_done() { https://github.com/esp8266/Arduino/pull/4889 */ +#if defined(DEBUG_ERASE_CONFIG) +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 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 + extern "C" void app_entry_redefinable(void) __attribute__((weak)); extern "C" void app_entry_redefinable(void) @@ -363,6 +374,9 @@ extern "C" void app_entry_redefinable(void) cont_t s_cont __attribute__((aligned(16))); g_pcont = &s_cont; + ERASE_CFG__FIX_DIVIDER(); + ERASE_CFG__ETS_PRINTF("\n\ncall_user_start()\n"); + /* Doing umm_init just once before starting the SDK, allowed us to remove test and init calls at each malloc API entry point, saving IRAM. */ #ifdef UMM_INIT_USE_IRAM @@ -408,7 +422,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(DEBUG_ERASE_CONFIG) + uart_div_modify(0, UART_CLK_FREQ / (74880)); + ERASE_CFG__ETS_DELAY_US(150); + ERASE_CFG__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/erase_config.cpp b/cores/esp8266/erase_config.cpp new file mode 100644 index 0000000000..b0b3e93001 --- /dev/null +++ b/cores/esp8266/erase_config.cpp @@ -0,0 +1,477 @@ +#include +#include +#include +#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. + +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 +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 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 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: + +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 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. 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. It also gathers some WiFi signal/connection statistics. + +*/ + +#define ERASE_CONFIG_METHOD 3 + +#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 DEBUG_ERASE_CONFIG +#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 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" + +#if (ERASE_CONFIG_METHOD == 1) +void __real_system_restart_local(); +#define IRAM_MAYBE +#elif (ERASE_CONFIG_METHOD == 2) +#define IRAM_MAYBE IRAM_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); + +_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 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(); + 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 */ + 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 (ERASE_CONFIG_METHOD == 1) + if (0 != spi_flash_erase_sector(sector)) { +#elif (ERASE_CONFIG_METHOD == 2) + if (0 != SPIEraseSector(sector)) { +#elif (ERASE_CONFIG_METHOD == 3) + if (0 != erase_config__erase_sector(sector)) { +#endif + ERASE_CFG__ETS_PRINTF("Erase sector 0x%04X failed!\n", sector); + return false; + } else { + ERASE_CFG__ETS_PRINTF("Erased sector 0x%04X\n", sector); + } + } + } + + return true; +} + +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) { + ERASE_CFG__ETS_PRINTF("\nerase_config(0x%03X)\n", erase_flash_option); +#if (ERASE_CONFIG_METHOD == 1) + erase_config(erase_flash_option); + 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) + return erase_config(erase_flash_option); +#endif + } + } else { + ERASE_CFG__ETS_PRINTF("\nNo OTA erase flags\n"); + } + 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*/ +} ROM_RST_REASON_t; + +#define RTC_SYS ((volatile uint32_t*)0x60001100) +extern uint32_t rtc_get_reset_reason(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) { + return false; // REASON_EXT_SYS_RST + } + if (REASON_EXT_SYS_RST < rtc_sys_reason) { + return false; // REASON_DEFAULT_RST + } + return true; +} + +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 + 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; + } + /* + 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); +} + +/* + Something to see when we are in the SDK initialization. +*/ +extern "C" void IRAM_ATTR _Z22__run_user_rf_pre_initv(void) { + 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); +} + +#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) +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) + +#include "cont.h" + +int eboot_two_shots __attribute__((section(".noinit"))); +extern cont_t* g_pcont; +extern "C" void call_user_start(); + +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; + + eboot_two_shots = 2; + + ERASE_CFG__FIX_DIVIDER(); + ERASE_CFG__ETS_PRINTF("\n\ncall_user_start()\n"); + ERASE_CFG__ETS_FLUSH(0); + + /* Doing umm_init just once before starting the SDK, allowed us to remove + test and init calls at each malloc API entry point, saving IRAM. */ + umm_init(); + /* Call the entry point of the SDK code. */ + call_user_start(); +} + + +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; + (void)err; +} + +#ifndef ROM_SPIRead +#define ROM_SPIRead 0x40004b1cU +#endif +typedef int (*fp_SPIRead_t)(uint32_t addr, void *dest, size_t size); +#define real_SPIRead ((fp_SPIRead_t)ROM_SPIRead) + +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 + 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(); + ERASE_CFG__ETS_FLUSH(0); + } + } + + int err = real_SPIRead(addr, dest, size); + dbg_log_SPIRead(addr, dest, size, err); + 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() { + 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 +#define PRINT_FLASHCHIP() do{}while(false) +#endif + +_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 + 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. */ + 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; + /* + 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(); + } + flashchip->chip_size = old_flash_size; +} + +void IRAM_ATTR erase_config_method3(void) { + Cache_Read_Enable(0, 0, 0); // 16K ICACHE + set_flashchip_and_check_erase_config(); + Cache_Read_Disable(); +} + +#include "cont.h" + +extern cont_t* g_pcont; +extern "C" void call_user_start(); + +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; + + ERASE_CFG__FIX_DIVIDER(); + erase_config_method3(); + + ERASE_CFG__ETS_PRINTF("\n\ncall_user_start()\n"); + ERASE_CFG__ETS_FLUSH(0); + + /* Doing umm_init just once before starting the SDK, allowed us to remove + test and init calls at each malloc API entry point, saving IRAM. */ + umm_init(); + /* 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 new file mode 100644 index 0000000000..6677d5a182 --- /dev/null +++ b/cores/esp8266/erase_config.h @@ -0,0 +1,49 @@ +#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. +// 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 | +// 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); +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 + +#endif 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..1fbb753bf7 --- /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..98a5519910 --- /dev/null +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/FlashInfo.ino @@ -0,0 +1,17 @@ +#include +#include +#define String_F(a) String(F(a)) + +void printFlashInfo(Print& oStream) { + 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 new file mode 100644 index 0000000000..c50b351281 --- /dev/null +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/OTAConfigErase.ino @@ -0,0 +1,299 @@ +/* + 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 + +#include "WifiHealth.h" +#include +#include "AddOns.h" + +#ifndef STASSID +#pragma message("Using default SSID: your-ssid, this is probably not what you want.") +#define STASSID "your-ssid" +#define STAPSK "your-password" +#endif + +#ifndef LOCALTZ +#define LOCALTZ TZ_America_Los_Angeles +#endif + +const char* ssid = STASSID; +const char* password = STAPSK; + +#ifdef ERASE_CONFIG_H +/* 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_RF_CAL; //ERASE_CONFIG_BLANK_BIN; +#endif + +#define String_F(a) String(F(a)) + + +// Make sure handler's never fall out of scope +WiFiEventHandler handler1; +WiFiEventHandler handler2; + +#define LED_BUILTIN_ON (0) +void wifiLedOn(void) { + digitalWrite(LED_BUILTIN, (LED_BUILTIN_ON) ? 1 : 0); +} +void wifiLedOff(void) { + digitalWrite(LED_BUILTIN, (LED_BUILTIN_ON) ? 0 : 1); +} + +void setup() { +#ifdef DEBUG_ERASE_CONFIG + Serial.begin(74880); +#else + Serial.begin(74880); + // Serial.begin(115200); +#endif + 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); + configTime(LOCALTZ, "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..."); + delay(5000); + ESP.restart(); + } + + // 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"; +#ifdef ERASE_CONFIG_H + Update.setEraseConfigOption(eraseConfigOption); +#endif + } 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("setup complete"); + 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(); +#ifdef ERASE_CONFIG_H + 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(); +#endif + 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")); + oStream.println(F(" f - flash info")); + printHelpAddOn(oStream); + oStream.println(F(" ? - This help message")); + oStream.println(); +} + +void printTimes(Print& out) { + { + 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(); + } + { + 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(); + } + { + 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(); + } +} + +int cmdLoop(Print& oStream, char hotKey) { + switch (hotKey) { + case 't': + printLocalTime(oStream); + oStream.println(); + printTimes(oStream); + oStream.println(); + break; + case '?': + printHelp(oStream); + break; + case 'u': + umm_info(NULL, true); + break; + case 'f': + printFlashInfo(oStream); + oStream.println(); + break; + case 'w': + printWiFiStats(oStream); + oStream.println(); + break; + case 'R': + oStream.println(F("Restart ...")); + delay(20); + WiFi.mode(WIFI_OFF); + ESP.restart(); + break; +#ifdef ERASE_CONFIG_H + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + if (queueEraseConfig(hotKey)) { + 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; +#endif + default: + return hotKeyHandlerAddOn(oStream, hotKey); + break; + } + oStream.println(); + return 1; +} + +void serialClientLoop(void) { + if (Serial.available() > 0) { + char hotKey = Serial.read(); + cmdLoop(Serial, hotKey); + } +} + +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..04bcc456e2 --- /dev/null +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/TelnetAgent.ino @@ -0,0 +1,105 @@ +#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]; + 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) + // 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..597c8afad2 --- /dev/null +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.h @@ -0,0 +1,49 @@ +#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; + +typedef struct _WIFI_HEALTH { + 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; + + +#ifndef MAX_CONNECTION_LOST_TIME_LOG +#define MAX_CONNECTION_LOST_TIME_LOG (8) +#endif +typedef struct _WIFI_DISCONNECT_LOG { + 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]; + +// 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..542d981228 --- /dev/null +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/WifiHealth.ino @@ -0,0 +1,351 @@ + +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; + + +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) { + wifiHealth.channel = data.channel; + memcpy(wifiHealth.bssid, data.bssid, sizeof(wifiHealth.bssid)); + wifiHealth.connected_count++; + time_t stime = (time_t)(micros64() / 1000000); + wifiHealth.connected_time = stime; + wifiHealth.rssi = WiFi.RSSI(); + WifiUp = true; + 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) { + /* After a disconnect, an attempt is made about every 3 secs to reconnect. + On each failed attempt, this function is called. */ + WifiUp = false; + time_t gtime; + time(>ime); + time_t stime = (time_t)(micros64() / 1000000); + 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; + time_t uptime = stime - wifiHealth.connected_time; + wifiHealth.uptime_sum += uptime; + wifiHealth.uptime_max = max(wifiHealth.uptime_max, uptime); + wifiHealth.uptime_min = min(wifiHealth.uptime_min, uptime); + wifiHealth.connected_time = (time_t)0; + } +} + + +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(); + + 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(); + } + } + + 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.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..ef42428670 --- /dev/null +++ b/libraries/ArduinoOTA/examples/OTAConfigErase/rtc.ino @@ -0,0 +1,75 @@ +#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