From 3833040f4f1dcd36b83fde23c19862ec5a2aa2bf Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Sun, 29 Sep 2019 01:30:48 -0700 Subject: [PATCH 01/10] Add a CRC32 over progmem and ESP.checkFlashCRC Automatically embed a CRC32 of the program memory (including bootloader but excluding any filesystems) in all images in unused space in the bootloader block. Add a call, ESP.checkFlashCRC() which returns false if the calculated CRC doesn't match the one stored in the image (i.e. flash corruption). Fixes #4165 --- cores/esp8266/Esp.cpp | 14 ++++++++ cores/esp8266/Esp.h | 2 ++ cores/esp8266/coredecls.h | 1 + cores/esp8266/crc32.cpp | 21 ++++++++++++ doc/libraries.rst | 2 ++ platform.txt | 6 ++-- tools/crc32bin.py | 72 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 116 insertions(+), 2 deletions(-) create mode 100755 tools/crc32bin.py diff --git a/cores/esp8266/Esp.cpp b/cores/esp8266/Esp.cpp index a21dc4bc0f..3a0b14b379 100644 --- a/cores/esp8266/Esp.cpp +++ b/cores/esp8266/Esp.cpp @@ -26,6 +26,7 @@ #include "MD5Builder.h" #include "umm_malloc/umm_malloc.h" #include "cont.h" +#include "coredecls.h" extern "C" { #include "user_interface.h" @@ -439,6 +440,19 @@ bool EspClass::checkFlashConfig(bool needsEquals) { return false; } +bool EspClass::checkFlashCRC() { + uint32_t flashsize = *((uint32_t*)0x40200ff8); + uint32_t flashcrc = *((uint32_t*)0x40200ffc); + uint32_t z[2]; + z[0] = z[1] = 0; + + uint32_t crc = crc32_P((const void*)0x40200000, 4096-8, 0xffffffff); + crc = crc32_P(z, 8, crc); + crc = crc32_P((const void*)0x40201000, flashsize-4096, crc); + return crc == flashcrc; +} + + String EspClass::getResetReason(void) { char buff[32]; if (resetInfo.reason == REASON_DEFAULT_RST) { // normal startup by power on diff --git a/cores/esp8266/Esp.h b/cores/esp8266/Esp.h index 33c7f10ad5..7a0d9e3b63 100644 --- a/cores/esp8266/Esp.h +++ b/cores/esp8266/Esp.h @@ -185,6 +185,8 @@ class EspClass { bool checkFlashConfig(bool needsEquals = false); + bool checkFlashCRC(); + bool flashEraseSector(uint32_t sector); bool flashWrite(uint32_t offset, uint32_t *data, size_t size); bool flashRead(uint32_t offset, uint32_t *data, size_t size); diff --git a/cores/esp8266/coredecls.h b/cores/esp8266/coredecls.h index 4a70609bff..ecd7e260bd 100644 --- a/cores/esp8266/coredecls.h +++ b/cores/esp8266/coredecls.h @@ -22,6 +22,7 @@ void disable_extra4k_at_link_time (void) __attribute__((noinline)); uint32_t sqrt32 (uint32_t n); uint32_t crc32 (const void* data, size_t length, uint32_t crc = 0xffffffff); +uint32_t crc32_P (const void* data, size_t length, uint32_t crc = 0xffffffff); #ifdef __cplusplus } diff --git a/cores/esp8266/crc32.cpp b/cores/esp8266/crc32.cpp index cc2a2ee64e..50fb35f075 100644 --- a/cores/esp8266/crc32.cpp +++ b/cores/esp8266/crc32.cpp @@ -20,6 +20,7 @@ */ #include "coredecls.h" +#include "pgmspace.h" // moved from core_esp8266_eboot_command.cpp uint32_t crc32 (const void* data, size_t length, uint32_t crc /*= 0xffffffff*/) @@ -40,3 +41,23 @@ uint32_t crc32 (const void* data, size_t length, uint32_t crc /*= 0xffffffff*/) } return crc; } + + +uint32_t crc32_P (const void* data, size_t length, uint32_t crc /*= 0xffffffff*/) +{ + const uint8_t* ldata = (const uint8_t*)data; + while (length--) + { + uint8_t c = pgm_read_byte(ldata++); + for (uint32_t i = 0x80; i > 0; i >>= 1) + { + bool bit = crc & 0x80000000; + if (c & i) + bit = !bit; + crc <<= 1; + if (bit) + crc ^= 0x04c11db7; + } + } + return crc; +} diff --git a/doc/libraries.rst b/doc/libraries.rst index c902dc196f..b5ec9304f6 100644 --- a/doc/libraries.rst +++ b/doc/libraries.rst @@ -113,6 +113,8 @@ Some ESP-specific APIs related to deep sleep, RTC and flash memories are availab ``ESP.getCycleCount()`` returns the cpu instruction cycle count since start as an unsigned 32-bit. This is useful for accurate timing of very short actions like bit banging. +``ESP.checkFlashCRC()`` calculates the CRC of the program memory (not including any filesystems) and compares it to the one embedded in the image. If this call returns ``false`` then the flash has been corrupted. + ``ESP.getVcc()`` may be used to measure supply voltage. ESP needs to reconfigure the ADC at startup in order for this feature to be available. Add the following line to the top of your sketch to use ``getVcc``: .. code:: cpp diff --git a/platform.txt b/platform.txt index 8bccbe2afc..422f68fa3a 100644 --- a/platform.txt +++ b/platform.txt @@ -18,6 +18,7 @@ runtime.tools.elf2bin={runtime.platform.path}/tools/elf2bin.py runtime.tools.sizes={runtime.platform.path}/tools/sizes.py runtime.tools.makecorever={runtime.platform.path}/tools/makecorever.py runtime.tools.eboot={runtime.platform.path}/bootloaders/eboot/eboot.elf +runtime.tools.crc={runtime.platform.path}/tools/crc32bin.py compiler.warning_flags=-w compiler.warning_flags.none=-w @@ -112,8 +113,9 @@ recipe.objcopy.eep.pattern= ## Create hex recipe.objcopy.hex.1.pattern="{runtime.tools.python3.path}/python3" "{runtime.tools.elf2bin}" --eboot "{runtime.tools.eboot}" --app "{build.path}/{build.project_name}.elf" --flash_mode {build.flash_mode} --flash_freq {build.flash_freq} --flash_size {build.flash_size} --path "{runtime.tools.xtensa-lx106-elf-gcc.path}/bin" --out "{build.path}/{build.project_name}.bin" -recipe.objcopy.hex.2.pattern="{runtime.tools.python3.path}/python3" "{runtime.tools.signing}" --mode sign --privatekey "{build.source.path}/private.key" --bin "{build.path}/{build.project_name}.bin" --out "{build.path}/{build.project_name}.bin.signed" --legacy "{build.path}/{build.project_name}.bin.legacy_sig" -recipe.objcopy.hex.3.pattern="{runtime.tools.python3.path}/python3" "{runtime.tools.sizes}" --elf "{build.path}/{build.project_name}.elf" --path "{runtime.tools.xtensa-lx106-elf-gcc.path}/bin" +recipe.objcopy.hex.2.pattern="{runtime.tools.python3.path}/python3" "{runtime.tools.crc}" --bin "{build.path}/{build.project_name}.bin" --size 4088 --crc 4092 +recipe.objcopy.hex.3.pattern="{runtime.tools.python3.path}/python3" "{runtime.tools.signing}" --mode sign --privatekey "{build.source.path}/private.key" --bin "{build.path}/{build.project_name}.bin" --out "{build.path}/{build.project_name}.bin.signed" --legacy "{build.path}/{build.project_name}.bin.legacy_sig" +recipe.objcopy.hex.4.pattern="{runtime.tools.python3.path}/python3" "{runtime.tools.sizes}" --elf "{build.path}/{build.project_name}.elf" --path "{runtime.tools.xtensa-lx106-elf-gcc.path}/bin" ## Save hex recipe.output.tmp_file={build.project_name}.bin diff --git a/tools/crc32bin.py b/tools/crc32bin.py new file mode 100755 index 0000000000..fe41dfb0d0 --- /dev/null +++ b/tools/crc32bin.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +# +# Place a CRC32 checksum and length in a generated BIN file +# +# Copyright (C) 2019 - Earle F. Philhower, III +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from __future__ import print_function +import argparse +import sys + +def crc8266(ldata): + crc = 0xffffffff + idx = 0 + while idx < len(ldata): + c = int(ldata[idx]); + idx = idx + 1 + for i in [0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01]: + bit = crc & 0x80000000; + if (c & i) != 0: + if bit == 0: + bit = 1 + else: + bit = 0 + crc = int(crc << 1) & 0xffffffff + if bit != 0: + crc = int(crc ^ 0x04c11db7); + return crc + +def store_word(raw, offset, val): + raw[offset] = val & 255 + raw[offset + 1] = (val >> 8) & 255 + raw[offset + 2] = (val >> 16) & 255 + raw[offset + 3] = (val >> 24) & 255 + return raw + +def main(): + parser = argparse.ArgumentParser(description='Add a CRC32 and length in a generated flash BIN image.') + parser.add_argument('-b', '--bin', action='store', required=True, help='Path to the Arduino sketch.bin') + parser.add_argument('-s', '--size', action='store', required=True, help='Byte offset in bin to store the size') + parser.add_argument('-c', '--crc', action='store', required=True, help='Byte offset in bin to store the crc32') + args = parser.parse_args() + + with open(args.bin, "rb") as binfile: + raw = bytearray(binfile.read()) + + # Zero out the spots we're going to overwrite to be idempotent + raw = store_word(raw, int(args.size), 0) + raw = store_word(raw, int(args.crc), 0) + crc = crc8266(raw) + raw = store_word(raw, int(args.size), len(raw)) + raw = store_word(raw, int(args.crc), int(crc)) + + with open(args.bin, "wb") as binfile: + binfile.write(raw) + + print("Inserted length of " + hex(len(raw)) + " and CRC of " + hex(crc)+ " into " + args.bin) + +if __name__ == '__main__': + sys.exit(main()) From 79d01979c0723c3730546092544dd561d562a8b2 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Mon, 30 Sep 2019 16:18:44 -0700 Subject: [PATCH 02/10] Add example that currupts itself, comments Show CRC checking catch a 1-bit error in program code by corrupting a large array, and then return it to clean and verify the CRC matches once again. Add comments to the CRC check routine Clean up pylint complaints on crc32bin.py --- cores/esp8266/Esp.cpp | 10 +++- .../examples/CheckFlashCRC/CheckFlashCRC.ino | 47 +++++++++++++++++++ tools/crc32bin.py | 26 ++++++---- 3 files changed, 72 insertions(+), 11 deletions(-) create mode 100644 libraries/esp8266/examples/CheckFlashCRC/CheckFlashCRC.ino diff --git a/cores/esp8266/Esp.cpp b/cores/esp8266/Esp.cpp index 3a0b14b379..19ef9a247e 100644 --- a/cores/esp8266/Esp.cpp +++ b/cores/esp8266/Esp.cpp @@ -441,13 +441,19 @@ bool EspClass::checkFlashConfig(bool needsEquals) { } bool EspClass::checkFlashCRC() { - uint32_t flashsize = *((uint32_t*)0x40200ff8); - uint32_t flashcrc = *((uint32_t*)0x40200ffc); + // The CRC and total length are placed in extra space at the end of the 4K chunk + // of flash occupied by the bootloader. If the bootloader grows to >4K-8 bytes, + // we'll need to adjust this. + uint32_t flashsize = *((uint32_t*)i(0x40200000 + 4088); // Start of PROGMEM plus 4K-8 + uint32_t flashcrc = *((uint32_t*)0x40200000 + 4095); // Start of PROGMEM plus 4K-4 uint32_t z[2]; z[0] = z[1] = 0; + // Start the checksum uint32_t crc = crc32_P((const void*)0x40200000, 4096-8, 0xffffffff); + // Pretend the 2 words of crc/len are zero to be idempotent crc = crc32_P(z, 8, crc); + // Finish the CRC calculation over the rest of flash crc = crc32_P((const void*)0x40201000, flashsize-4096, crc); return crc == flashcrc; } diff --git a/libraries/esp8266/examples/CheckFlashCRC/CheckFlashCRC.ino b/libraries/esp8266/examples/CheckFlashCRC/CheckFlashCRC.ino new file mode 100644 index 0000000000..9ae4447d69 --- /dev/null +++ b/libraries/esp8266/examples/CheckFlashCRC/CheckFlashCRC.ino @@ -0,0 +1,47 @@ +/* + * Demonstrate CRC check passing and failing by simulating a bit flip in flash. + * WARNING!!! You would never want to actually do this in a real application! + * + * Released to the Public Domain by Earle F. Philhower, III + */ + +extern "C" { + #include "spi_flash.h" +} +// Artificially create a space in PROGMEM that fills multipe sectors so +// we can corrupt one without crashing the system +const int corruptme[SPI_FLASH_SEC_SIZE * 4] PROGMEM = { 0 }; + +void setup() +{ + Serial.begin(115200); + Serial.printf("Starting\n"); + Serial.printf("CRC check: %s\n", ESP.checkFlashCRC()?"OK":"ERROR"); + Serial.printf("...Corrupting a portion of flash in the array...\n"); + + uint32_t ptr = (uint32_t)corruptme; + // Find a page aligned spot inside the array + ptr += 2 * SPI_FLASH_SEC_SIZE; + ptr &= ~(SPI_FLASH_SEC_SIZE - 1); // Sectoralign + uint32_t sector = ((((uint32_t)ptr - 0x40200000) / SPI_FLASH_SEC_SIZE)); + + // Create a sector with 1 bit set (i.e. fake corruption) + uint32_t *space = (uint32_t*)calloc(SPI_FLASH_SEC_SIZE, 1); + space[42] = 64; + + // Write it into flash at the spot in question + spi_flash_erase_sector(sector); + spi_flash_write(sector * SPI_FLASH_SEC_SIZE, (uint32_t*)space, SPI_FLASH_SEC_SIZE); + Serial.printf("CRC check: %s\n", ESP.checkFlashCRC()?"OK":"ERROR"); + + Serial.printf("...Correcting the flash...\n"); + memset(space, 0, SPI_FLASH_SEC_SIZE); + spi_flash_erase_sector(sector); + spi_flash_write(sector * SPI_FLASH_SEC_SIZE, (uint32_t*)space, SPI_FLASH_SEC_SIZE); + Serial.printf("CRC check: %s\n", ESP.checkFlashCRC()?"OK":"ERROR"); +} + + +void loop() +{ +} diff --git a/tools/crc32bin.py b/tools/crc32bin.py index fe41dfb0d0..dd0f201a19 100755 --- a/tools/crc32bin.py +++ b/tools/crc32bin.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 -# + +""" # Place a CRC32 checksum and length in a generated BIN file # # Copyright (C) 2019 - Earle F. Philhower, III @@ -16,30 +17,33 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" from __future__ import print_function import argparse import sys def crc8266(ldata): + "Return the CRC of ldata using same algorithm as eboot" crc = 0xffffffff idx = 0 while idx < len(ldata): - c = int(ldata[idx]); + byte = int(ldata[idx]) idx = idx + 1 for i in [0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01]: - bit = crc & 0x80000000; - if (c & i) != 0: + bit = crc & 0x80000000 + if (byte & i) != 0: if bit == 0: bit = 1 else: bit = 0 crc = int(crc << 1) & 0xffffffff if bit != 0: - crc = int(crc ^ 0x04c11db7); + crc = int(crc ^ 0x04c11db7) return crc def store_word(raw, offset, val): + "Place a 4-byte word in 8266-dependent order in the raw image" raw[offset] = val & 255 raw[offset + 1] = (val >> 8) & 255 raw[offset + 2] = (val >> 16) & 255 @@ -47,10 +51,14 @@ def store_word(raw, offset, val): return raw def main(): - parser = argparse.ArgumentParser(description='Add a CRC32 and length in a generated flash BIN image.') - parser.add_argument('-b', '--bin', action='store', required=True, help='Path to the Arduino sketch.bin') - parser.add_argument('-s', '--size', action='store', required=True, help='Byte offset in bin to store the size') - parser.add_argument('-c', '--crc', action='store', required=True, help='Byte offset in bin to store the crc32') + "Main CLI interface" + parser = argparse.ArgumentParser(description='Embed CRC32 and length in a generated BIN image') + parser.add_argument('-b', '--bin', action='store', required=True, + help='Path to the Arduino sketch.bin') + parser.add_argument('-s', '--size', action='store', required=True, + help='Byte offset in bin to store the size') + parser.add_argument('-c', '--crc', action='store', required=True, + help='Byte offset in bin to store the crc32') args = parser.parse_args() with open(args.bin, "rb") as binfile: From 3fca89eb3939817a5071b7dd6a899f723e48865e Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Tue, 1 Oct 2019 13:52:54 -0700 Subject: [PATCH 03/10] Update Esp.cpp --- cores/esp8266/Esp.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cores/esp8266/Esp.cpp b/cores/esp8266/Esp.cpp index 19ef9a247e..ba232d61dc 100644 --- a/cores/esp8266/Esp.cpp +++ b/cores/esp8266/Esp.cpp @@ -444,8 +444,8 @@ bool EspClass::checkFlashCRC() { // The CRC and total length are placed in extra space at the end of the 4K chunk // of flash occupied by the bootloader. If the bootloader grows to >4K-8 bytes, // we'll need to adjust this. - uint32_t flashsize = *((uint32_t*)i(0x40200000 + 4088); // Start of PROGMEM plus 4K-8 - uint32_t flashcrc = *((uint32_t*)0x40200000 + 4095); // Start of PROGMEM plus 4K-4 + uint32_t flashsize = *((uint32_t*)(0x40200000 + 4088)); // Start of PROGMEM plus 4K-8 + uint32_t flashcrc = *((uint32_t*)(0x40200000 + 4095)); // Start of PROGMEM plus 4K-4 uint32_t z[2]; z[0] = z[1] = 0; From fcfa48c031b09a393274b7c8f44f47e85523b17f Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Tue, 1 Oct 2019 13:55:53 -0700 Subject: [PATCH 04/10] Update Esp.cpp --- cores/esp8266/Esp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp8266/Esp.cpp b/cores/esp8266/Esp.cpp index ba232d61dc..4931bcf86f 100644 --- a/cores/esp8266/Esp.cpp +++ b/cores/esp8266/Esp.cpp @@ -445,7 +445,7 @@ bool EspClass::checkFlashCRC() { // of flash occupied by the bootloader. If the bootloader grows to >4K-8 bytes, // we'll need to adjust this. uint32_t flashsize = *((uint32_t*)(0x40200000 + 4088)); // Start of PROGMEM plus 4K-8 - uint32_t flashcrc = *((uint32_t*)(0x40200000 + 4095)); // Start of PROGMEM plus 4K-4 + uint32_t flashcrc = *((uint32_t*)(0x40200000 + 4092)); // Start of PROGMEM plus 4K-4 uint32_t z[2]; z[0] = z[1] = 0; From 00929d1382c8ca2aeaabc154b1c03b5fa7b2dc11 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Tue, 1 Oct 2019 14:52:26 -0700 Subject: [PATCH 05/10] Fix example astyle problems --- .../examples/CheckFlashCRC/CheckFlashCRC.ino | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/libraries/esp8266/examples/CheckFlashCRC/CheckFlashCRC.ino b/libraries/esp8266/examples/CheckFlashCRC/CheckFlashCRC.ino index 9ae4447d69..b00194a46b 100644 --- a/libraries/esp8266/examples/CheckFlashCRC/CheckFlashCRC.ino +++ b/libraries/esp8266/examples/CheckFlashCRC/CheckFlashCRC.ino @@ -1,22 +1,21 @@ /* - * Demonstrate CRC check passing and failing by simulating a bit flip in flash. - * WARNING!!! You would never want to actually do this in a real application! - * - * Released to the Public Domain by Earle F. Philhower, III - */ + Demonstrate CRC check passing and failing by simulating a bit flip in flash. + WARNING!!! You would never want to actually do this in a real application! + + Released to the Public Domain by Earle F. Philhower, III +*/ extern "C" { - #include "spi_flash.h" +#include "spi_flash.h" } // Artificially create a space in PROGMEM that fills multipe sectors so // we can corrupt one without crashing the system const int corruptme[SPI_FLASH_SEC_SIZE * 4] PROGMEM = { 0 }; -void setup() -{ +void setup() { Serial.begin(115200); Serial.printf("Starting\n"); - Serial.printf("CRC check: %s\n", ESP.checkFlashCRC()?"OK":"ERROR"); + Serial.printf("CRC check: %s\n", ESP.checkFlashCRC() ? "OK" : "ERROR"); Serial.printf("...Corrupting a portion of flash in the array...\n"); uint32_t ptr = (uint32_t)corruptme; @@ -32,16 +31,15 @@ void setup() // Write it into flash at the spot in question spi_flash_erase_sector(sector); spi_flash_write(sector * SPI_FLASH_SEC_SIZE, (uint32_t*)space, SPI_FLASH_SEC_SIZE); - Serial.printf("CRC check: %s\n", ESP.checkFlashCRC()?"OK":"ERROR"); - + Serial.printf("CRC check: %s\n", ESP.checkFlashCRC() ? "OK" : "ERROR"); + Serial.printf("...Correcting the flash...\n"); memset(space, 0, SPI_FLASH_SEC_SIZE); spi_flash_erase_sector(sector); spi_flash_write(sector * SPI_FLASH_SEC_SIZE, (uint32_t*)space, SPI_FLASH_SEC_SIZE); - Serial.printf("CRC check: %s\n", ESP.checkFlashCRC()?"OK":"ERROR"); + Serial.printf("CRC check: %s\n", ESP.checkFlashCRC() ? "OK" : "ERROR"); } -void loop() -{ +void loop() { } From 49180aa33701f36d877157932e72b1ba7205e105 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Mon, 9 Dec 2019 15:43:34 -0800 Subject: [PATCH 06/10] Check linker script for CRC space in bootsector Add an assertion in the eboot linker file to guarantee that we have at least 8 bytes of unused space at the end of the boot sector to patch in the CRC. If not, the eboot link will fail. --- bootloaders/eboot/eboot.ld | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bootloaders/eboot/eboot.ld b/bootloaders/eboot/eboot.ld index 303ae8a56c..81adac8200 100644 --- a/bootloaders/eboot/eboot.ld +++ b/bootloaders/eboot/eboot.ld @@ -153,6 +153,12 @@ SECTIONS *(COMMON) . = ALIGN (8); _bss_end = ABSOLUTE(.); + /* CRC stored in last 8 bytes */ + ASSERT((. < 4096 - 8), "Error: No space for CRC in bootloader sector."); + . = _stext + 4096 - 8; + _crc_val = .; + . = . + 4; + _crc_len = .; } >iram1_0_seg :iram1_0_phdr .lit4 : ALIGN(4) From 800ebe55d83bb8dd86a5d973dcf3dba88fc63112 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Mon, 9 Dec 2019 16:00:57 -0800 Subject: [PATCH 07/10] Fix order of crc/len in linker script --- bootloaders/eboot/eboot.ld | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bootloaders/eboot/eboot.ld b/bootloaders/eboot/eboot.ld index 81adac8200..80715a581d 100644 --- a/bootloaders/eboot/eboot.ld +++ b/bootloaders/eboot/eboot.ld @@ -156,9 +156,9 @@ SECTIONS /* CRC stored in last 8 bytes */ ASSERT((. < 4096 - 8), "Error: No space for CRC in bootloader sector."); . = _stext + 4096 - 8; - _crc_val = .; + _crc_size = .; . = . + 4; - _crc_len = .; + _crc_val = .; } >iram1_0_seg :iram1_0_phdr .lit4 : ALIGN(4) From 77d617a95239c2c23cd1978e4e7a59d1f0042b1d Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Wed, 18 Dec 2019 21:01:41 -0800 Subject: [PATCH 08/10] Add note about what to do if CRC check fails Per discussion with @d-a-v. When the CRC check fails, you could *try* to do certain things (but may not succeed since there is known flash corruption at that point). List a few ideas for application authors. --- doc/libraries.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/libraries.rst b/doc/libraries.rst index b5ec9304f6..d99dfe71f0 100644 --- a/doc/libraries.rst +++ b/doc/libraries.rst @@ -113,7 +113,7 @@ Some ESP-specific APIs related to deep sleep, RTC and flash memories are availab ``ESP.getCycleCount()`` returns the cpu instruction cycle count since start as an unsigned 32-bit. This is useful for accurate timing of very short actions like bit banging. -``ESP.checkFlashCRC()`` calculates the CRC of the program memory (not including any filesystems) and compares it to the one embedded in the image. If this call returns ``false`` then the flash has been corrupted. +``ESP.checkFlashCRC()`` calculates the CRC of the program memory (not including any filesystems) and compares it to the one embedded in the image. If this call returns ``false`` then the flash has been corrupted. At that point, you may want to consider trying to send a MQTT message, to start a re-download of the application, blink a LED in an `SOS` pattern, etc. However, since the flash is known corrupted at this point there is no guarantee the app will be able to perform any of these operations, so in safety critical deployments an immediate shutdown to a fail-safe mode may be indicated. ``ESP.getVcc()`` may be used to measure supply voltage. ESP needs to reconfigure the ADC at startup in order for this feature to be available. Add the following line to the top of your sketch to use ``getVcc``: From 985b6c521ce10a6ce6c913a49408b4325b4679c5 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Fri, 20 Dec 2019 08:11:50 -0800 Subject: [PATCH 09/10] Only single, flash/ram friendly crc32() function --- cores/esp8266/crc32.cpp | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/cores/esp8266/crc32.cpp b/cores/esp8266/crc32.cpp index 50fb35f075..c2fdf928e7 100644 --- a/cores/esp8266/crc32.cpp +++ b/cores/esp8266/crc32.cpp @@ -24,26 +24,6 @@ // moved from core_esp8266_eboot_command.cpp uint32_t crc32 (const void* data, size_t length, uint32_t crc /*= 0xffffffff*/) -{ - const uint8_t* ldata = (const uint8_t*)data; - while (length--) - { - uint8_t c = *ldata++; - for (uint32_t i = 0x80; i > 0; i >>= 1) - { - bool bit = crc & 0x80000000; - if (c & i) - bit = !bit; - crc <<= 1; - if (bit) - crc ^= 0x04c11db7; - } - } - return crc; -} - - -uint32_t crc32_P (const void* data, size_t length, uint32_t crc /*= 0xffffffff*/) { const uint8_t* ldata = (const uint8_t*)data; while (length--) From 379b167249fff8fc924c113ddf97be8b1b6aeacb Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Fri, 20 Dec 2019 08:19:59 -0800 Subject: [PATCH 10/10] Combine the CRC calc and bin generation in 1 step Per discussion w/@mcspr, combine the CRC calculation with the binary generation, removing the additional build step. --- cores/esp8266/Esp.cpp | 6 +-- cores/esp8266/coredecls.h | 1 - platform.txt | 6 +-- tools/crc32bin.py | 80 --------------------------------------- tools/elf2bin.py | 47 +++++++++++++++++++++++ 5 files changed, 52 insertions(+), 88 deletions(-) delete mode 100755 tools/crc32bin.py diff --git a/cores/esp8266/Esp.cpp b/cores/esp8266/Esp.cpp index e21d9ac808..04c83518f6 100644 --- a/cores/esp8266/Esp.cpp +++ b/cores/esp8266/Esp.cpp @@ -458,11 +458,11 @@ bool EspClass::checkFlashCRC() { z[0] = z[1] = 0; // Start the checksum - uint32_t crc = crc32_P((const void*)0x40200000, 4096-8, 0xffffffff); + uint32_t crc = crc32((const void*)0x40200000, 4096-8, 0xffffffff); // Pretend the 2 words of crc/len are zero to be idempotent - crc = crc32_P(z, 8, crc); + crc = crc32(z, 8, crc); // Finish the CRC calculation over the rest of flash - crc = crc32_P((const void*)0x40201000, flashsize-4096, crc); + crc = crc32((const void*)0x40201000, flashsize-4096, crc); return crc == flashcrc; } diff --git a/cores/esp8266/coredecls.h b/cores/esp8266/coredecls.h index d534c03df8..a3ce60c393 100644 --- a/cores/esp8266/coredecls.h +++ b/cores/esp8266/coredecls.h @@ -22,7 +22,6 @@ void disable_extra4k_at_link_time (void) __attribute__((noinline)); uint32_t sqrt32 (uint32_t n); uint32_t crc32 (const void* data, size_t length, uint32_t crc = 0xffffffff); -uint32_t crc32_P (const void* data, size_t length, uint32_t crc = 0xffffffff); #ifdef __cplusplus } diff --git a/platform.txt b/platform.txt index d20f29c7f1..678be1ba9d 100644 --- a/platform.txt +++ b/platform.txt @@ -18,7 +18,6 @@ runtime.tools.elf2bin={runtime.platform.path}/tools/elf2bin.py runtime.tools.sizes={runtime.platform.path}/tools/sizes.py runtime.tools.makecorever={runtime.platform.path}/tools/makecorever.py runtime.tools.eboot={runtime.platform.path}/bootloaders/eboot/eboot.elf -runtime.tools.crc={runtime.platform.path}/tools/crc32bin.py compiler.warning_flags=-w compiler.warning_flags.none=-w @@ -118,9 +117,8 @@ recipe.objcopy.eep.pattern= ## Create hex recipe.objcopy.hex.1.pattern="{runtime.tools.python3.path}/python3" "{runtime.tools.elf2bin}" --eboot "{runtime.tools.eboot}" --app "{build.path}/{build.project_name}.elf" --flash_mode {build.flash_mode} --flash_freq {build.flash_freq} --flash_size {build.flash_size} --path "{runtime.tools.xtensa-lx106-elf-gcc.path}/bin" --out "{build.path}/{build.project_name}.bin" -recipe.objcopy.hex.2.pattern="{runtime.tools.python3.path}/python3" "{runtime.tools.crc}" --bin "{build.path}/{build.project_name}.bin" --size 4088 --crc 4092 -recipe.objcopy.hex.3.pattern="{runtime.tools.python3.path}/python3" "{runtime.tools.signing}" --mode sign --privatekey "{build.source.path}/private.key" --bin "{build.path}/{build.project_name}.bin" --out "{build.path}/{build.project_name}.bin.signed" --legacy "{build.path}/{build.project_name}.bin.legacy_sig" -recipe.objcopy.hex.4.pattern="{runtime.tools.python3.path}/python3" "{runtime.tools.sizes}" --elf "{build.path}/{build.project_name}.elf" --path "{runtime.tools.xtensa-lx106-elf-gcc.path}/bin" +recipe.objcopy.hex.2.pattern="{runtime.tools.python3.path}/python3" "{runtime.tools.signing}" --mode sign --privatekey "{build.source.path}/private.key" --bin "{build.path}/{build.project_name}.bin" --out "{build.path}/{build.project_name}.bin.signed" --legacy "{build.path}/{build.project_name}.bin.legacy_sig" +recipe.objcopy.hex.3.pattern="{runtime.tools.python3.path}/python3" "{runtime.tools.sizes}" --elf "{build.path}/{build.project_name}.elf" --path "{runtime.tools.xtensa-lx106-elf-gcc.path}/bin" ## Save hex recipe.output.tmp_file={build.project_name}.bin diff --git a/tools/crc32bin.py b/tools/crc32bin.py deleted file mode 100755 index dd0f201a19..0000000000 --- a/tools/crc32bin.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python3 - -""" -# Place a CRC32 checksum and length in a generated BIN file -# -# Copyright (C) 2019 - Earle F. Philhower, III -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -""" - -from __future__ import print_function -import argparse -import sys - -def crc8266(ldata): - "Return the CRC of ldata using same algorithm as eboot" - crc = 0xffffffff - idx = 0 - while idx < len(ldata): - byte = int(ldata[idx]) - idx = idx + 1 - for i in [0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01]: - bit = crc & 0x80000000 - if (byte & i) != 0: - if bit == 0: - bit = 1 - else: - bit = 0 - crc = int(crc << 1) & 0xffffffff - if bit != 0: - crc = int(crc ^ 0x04c11db7) - return crc - -def store_word(raw, offset, val): - "Place a 4-byte word in 8266-dependent order in the raw image" - raw[offset] = val & 255 - raw[offset + 1] = (val >> 8) & 255 - raw[offset + 2] = (val >> 16) & 255 - raw[offset + 3] = (val >> 24) & 255 - return raw - -def main(): - "Main CLI interface" - parser = argparse.ArgumentParser(description='Embed CRC32 and length in a generated BIN image') - parser.add_argument('-b', '--bin', action='store', required=True, - help='Path to the Arduino sketch.bin') - parser.add_argument('-s', '--size', action='store', required=True, - help='Byte offset in bin to store the size') - parser.add_argument('-c', '--crc', action='store', required=True, - help='Byte offset in bin to store the crc32') - args = parser.parse_args() - - with open(args.bin, "rb") as binfile: - raw = bytearray(binfile.read()) - - # Zero out the spots we're going to overwrite to be idempotent - raw = store_word(raw, int(args.size), 0) - raw = store_word(raw, int(args.crc), 0) - crc = crc8266(raw) - raw = store_word(raw, int(args.size), len(raw)) - raw = store_word(raw, int(args.crc), int(crc)) - - with open(args.bin, "wb") as binfile: - binfile.write(raw) - - print("Inserted length of " + hex(len(raw)) + " and CRC of " + hex(crc)+ " into " + args.bin) - -if __name__ == '__main__': - sys.exit(main()) diff --git a/tools/elf2bin.py b/tools/elf2bin.py index e4423176e0..607d074056 100755 --- a/tools/elf2bin.py +++ b/tools/elf2bin.py @@ -30,6 +30,9 @@ ffreqb = { '40': 0, '26': 1, '20': 2, '80': 15 } fsizeb = { '512K': 0, '256K': 1, '1M': 2, '2M': 3, '4M': 4, '8M': 8, '16M': 9 } +crcsize_offset = 4088 +crcval_offset = 4092 + def get_elf_entry(elf, path): p = subprocess.Popen([path + "/xtensa-lx106-elf-readelf", '-h', elf], stdout=subprocess.PIPE, universal_newlines=True ) lines = p.stdout.readlines() @@ -94,6 +97,47 @@ def write_bin(out, elf, segments, to_addr, flash_mode, flash_size, flash_freq, p out.write(bytearray([0xaa])) total_size += 1 +def crc8266(ldata): + "Return the CRC of ldata using same algorithm as eboot" + crc = 0xffffffff + idx = 0 + while idx < len(ldata): + byte = int(ldata[idx]) + idx = idx + 1 + for i in [0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01]: + bit = crc & 0x80000000 + if (byte & i) != 0: + if bit == 0: + bit = 1 + else: + bit = 0 + crc = int(crc << 1) & 0xffffffff + if bit != 0: + crc = int(crc ^ 0x04c11db7) + return crc + +def store_word(raw, offset, val): + "Place a 4-byte word in 8266-dependent order in the raw image" + raw[offset] = val & 255 + raw[offset + 1] = (val >> 8) & 255 + raw[offset + 2] = (val >> 16) & 255 + raw[offset + 3] = (val >> 24) & 255 + return raw + +def add_crc(out): + with open(out, "rb") as binfile: + raw = bytearray(binfile.read()) + + # Zero out the spots we're going to overwrite to be idempotent + raw = store_word(raw, crcsize_offset, 0) + raw = store_word(raw, crcval_offset, 0) + crc = crc8266(raw) + raw = store_word(raw, crcsize_offset, len(raw)) + raw = store_word(raw, crcval_offset, int(crc)) + + with open(out, "wb") as binfile: + binfile.write(raw) + def main(): parser = argparse.ArgumentParser(description='Create a BIN file from eboot.elf and Arduino sketch.elf for upload by esptool.py') parser.add_argument('-e', '--eboot', action='store', required=True, help='Path to the Arduino eboot.elf bootloader') @@ -113,6 +157,9 @@ def main(): write_bin(out, args.app, ['.irom0.text', '.text', '.text1', '.data', '.rodata'], 0, args.flash_mode, args.flash_size, args.flash_freq, args.path) out.close() + # Because the CRC includes both eboot and app, can only calculate it after the entire BIN generated + add_crc(args.out) + return 0