Skip to content

Commit 52d84b1

Browse files
Add a CRC32 over progmem and ESP.checkFlashCRC (#6566)
* 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 * Add example that corrupts 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 * 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. * 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. * Only single, flash/ram friendly crc32() function * 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.
1 parent 9985a32 commit 52d84b1

File tree

7 files changed

+124
-1
lines changed

7 files changed

+124
-1
lines changed

Diff for: bootloaders/eboot/eboot.ld

+6
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,12 @@ SECTIONS
154154
*(.gnu.linkonce.b.*)
155155
. = ALIGN (8);
156156
_bss_end = ABSOLUTE(.);
157+
/* CRC stored in last 8 bytes */
158+
ASSERT((. < 4096 - 8), "Error: No space for CRC in bootloader sector.");
159+
. = _stext + 4096 - 8;
160+
_crc_size = .;
161+
. = . + 4;
162+
_crc_val = .;
157163
} >iram1_0_seg :iram1_0_phdr
158164

159165
.lit4 : ALIGN(4)

Diff for: cores/esp8266/Esp.cpp

+20
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "MD5Builder.h"
2727
#include "umm_malloc/umm_malloc.h"
2828
#include "cont.h"
29+
#include "coredecls.h"
2930

3031
extern "C" {
3132
#include "user_interface.h"
@@ -447,6 +448,25 @@ bool EspClass::checkFlashConfig(bool needsEquals) {
447448
return false;
448449
}
449450

451+
bool EspClass::checkFlashCRC() {
452+
// The CRC and total length are placed in extra space at the end of the 4K chunk
453+
// of flash occupied by the bootloader. If the bootloader grows to >4K-8 bytes,
454+
// we'll need to adjust this.
455+
uint32_t flashsize = *((uint32_t*)(0x40200000 + 4088)); // Start of PROGMEM plus 4K-8
456+
uint32_t flashcrc = *((uint32_t*)(0x40200000 + 4092)); // Start of PROGMEM plus 4K-4
457+
uint32_t z[2];
458+
z[0] = z[1] = 0;
459+
460+
// Start the checksum
461+
uint32_t crc = crc32((const void*)0x40200000, 4096-8, 0xffffffff);
462+
// Pretend the 2 words of crc/len are zero to be idempotent
463+
crc = crc32(z, 8, crc);
464+
// Finish the CRC calculation over the rest of flash
465+
crc = crc32((const void*)0x40201000, flashsize-4096, crc);
466+
return crc == flashcrc;
467+
}
468+
469+
450470
String EspClass::getResetReason(void) {
451471
char buff[32];
452472
if (resetInfo.reason == REASON_DEFAULT_RST) { // normal startup by power on

Diff for: cores/esp8266/Esp.h

+2
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ class EspClass {
176176

177177
bool checkFlashConfig(bool needsEquals = false);
178178

179+
bool checkFlashCRC();
180+
179181
bool flashEraseSector(uint32_t sector);
180182
bool flashWrite(uint32_t offset, uint32_t *data, size_t size);
181183
bool flashRead(uint32_t offset, uint32_t *data, size_t size);

Diff for: cores/esp8266/crc32.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,15 @@
2020
*/
2121

2222
#include "coredecls.h"
23+
#include "pgmspace.h"
2324

2425
// moved from core_esp8266_eboot_command.cpp
2526
uint32_t crc32 (const void* data, size_t length, uint32_t crc /*= 0xffffffff*/)
2627
{
2728
const uint8_t* ldata = (const uint8_t*)data;
2829
while (length--)
2930
{
30-
uint8_t c = *ldata++;
31+
uint8_t c = pgm_read_byte(ldata++);
3132
for (uint32_t i = 0x80; i > 0; i >>= 1)
3233
{
3334
bool bit = crc & 0x80000000;

Diff for: doc/libraries.rst

+2
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ Some ESP-specific APIs related to deep sleep, RTC and flash memories are availab
113113

114114
``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.
115115

116+
``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.
117+
116118
``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``:
117119

118120
.. code:: cpp
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
Demonstrate CRC check passing and failing by simulating a bit flip in flash.
3+
WARNING!!! You would never want to actually do this in a real application!
4+
5+
Released to the Public Domain by Earle F. Philhower, III <[email protected]>
6+
*/
7+
8+
extern "C" {
9+
#include "spi_flash.h"
10+
}
11+
// Artificially create a space in PROGMEM that fills multipe sectors so
12+
// we can corrupt one without crashing the system
13+
const int corruptme[SPI_FLASH_SEC_SIZE * 4] PROGMEM = { 0 };
14+
15+
void setup() {
16+
Serial.begin(115200);
17+
Serial.printf("Starting\n");
18+
Serial.printf("CRC check: %s\n", ESP.checkFlashCRC() ? "OK" : "ERROR");
19+
Serial.printf("...Corrupting a portion of flash in the array...\n");
20+
21+
uint32_t ptr = (uint32_t)corruptme;
22+
// Find a page aligned spot inside the array
23+
ptr += 2 * SPI_FLASH_SEC_SIZE;
24+
ptr &= ~(SPI_FLASH_SEC_SIZE - 1); // Sectoralign
25+
uint32_t sector = ((((uint32_t)ptr - 0x40200000) / SPI_FLASH_SEC_SIZE));
26+
27+
// Create a sector with 1 bit set (i.e. fake corruption)
28+
uint32_t *space = (uint32_t*)calloc(SPI_FLASH_SEC_SIZE, 1);
29+
space[42] = 64;
30+
31+
// Write it into flash at the spot in question
32+
spi_flash_erase_sector(sector);
33+
spi_flash_write(sector * SPI_FLASH_SEC_SIZE, (uint32_t*)space, SPI_FLASH_SEC_SIZE);
34+
Serial.printf("CRC check: %s\n", ESP.checkFlashCRC() ? "OK" : "ERROR");
35+
36+
Serial.printf("...Correcting the flash...\n");
37+
memset(space, 0, SPI_FLASH_SEC_SIZE);
38+
spi_flash_erase_sector(sector);
39+
spi_flash_write(sector * SPI_FLASH_SEC_SIZE, (uint32_t*)space, SPI_FLASH_SEC_SIZE);
40+
Serial.printf("CRC check: %s\n", ESP.checkFlashCRC() ? "OK" : "ERROR");
41+
}
42+
43+
44+
void loop() {
45+
}

Diff for: tools/elf2bin.py

+47
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
ffreqb = { '40': 0, '26': 1, '20': 2, '80': 15 }
3131
fsizeb = { '512K': 0, '256K': 1, '1M': 2, '2M': 3, '4M': 4, '8M': 8, '16M': 9 }
3232

33+
crcsize_offset = 4088
34+
crcval_offset = 4092
35+
3336
def get_elf_entry(elf, path):
3437
p = subprocess.Popen([path + "/xtensa-lx106-elf-readelf", '-h', elf], stdout=subprocess.PIPE, universal_newlines=True )
3538
lines = p.stdout.readlines()
@@ -94,6 +97,47 @@ def write_bin(out, elf, segments, to_addr, flash_mode, flash_size, flash_freq, p
9497
out.write(bytearray([0xaa]))
9598
total_size += 1
9699

100+
def crc8266(ldata):
101+
"Return the CRC of ldata using same algorithm as eboot"
102+
crc = 0xffffffff
103+
idx = 0
104+
while idx < len(ldata):
105+
byte = int(ldata[idx])
106+
idx = idx + 1
107+
for i in [0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01]:
108+
bit = crc & 0x80000000
109+
if (byte & i) != 0:
110+
if bit == 0:
111+
bit = 1
112+
else:
113+
bit = 0
114+
crc = int(crc << 1) & 0xffffffff
115+
if bit != 0:
116+
crc = int(crc ^ 0x04c11db7)
117+
return crc
118+
119+
def store_word(raw, offset, val):
120+
"Place a 4-byte word in 8266-dependent order in the raw image"
121+
raw[offset] = val & 255
122+
raw[offset + 1] = (val >> 8) & 255
123+
raw[offset + 2] = (val >> 16) & 255
124+
raw[offset + 3] = (val >> 24) & 255
125+
return raw
126+
127+
def add_crc(out):
128+
with open(out, "rb") as binfile:
129+
raw = bytearray(binfile.read())
130+
131+
# Zero out the spots we're going to overwrite to be idempotent
132+
raw = store_word(raw, crcsize_offset, 0)
133+
raw = store_word(raw, crcval_offset, 0)
134+
crc = crc8266(raw)
135+
raw = store_word(raw, crcsize_offset, len(raw))
136+
raw = store_word(raw, crcval_offset, int(crc))
137+
138+
with open(out, "wb") as binfile:
139+
binfile.write(raw)
140+
97141
def main():
98142
parser = argparse.ArgumentParser(description='Create a BIN file from eboot.elf and Arduino sketch.elf for upload by esptool.py')
99143
parser.add_argument('-e', '--eboot', action='store', required=True, help='Path to the Arduino eboot.elf bootloader')
@@ -113,6 +157,9 @@ def main():
113157
write_bin(out, args.app, ['.irom0.text', '.text', '.text1', '.data', '.rodata'], 0, args.flash_mode, args.flash_size, args.flash_freq, args.path)
114158
out.close()
115159

160+
# Because the CRC includes both eboot and app, can only calculate it after the entire BIN generated
161+
add_crc(args.out)
162+
116163
return 0
117164

118165

0 commit comments

Comments
 (0)