Skip to content

Commit 1d0bc5e

Browse files
Allow GZIP compressed flash updates (#6820)
* Allow GZIP compressed flash updates Modified the bootloader to be able to take stored updates in compressed GZIP format (i.e. the output of "gzip -9 xxx.bin") and decompress them on-the-fly to their final destination. This can work for apps and for filesystems (when used with the 2-step update option). Allow eboot to be built using -Os/2 optimizations by fixing some portions which failed when any optimizations were used. Add -Wall and use data and function sections to reduce size. Use -Os to minimize size. Remove obsolete esptool-ck calls to build a .ROM image, we don't use it. Move all uninitted variables to RAM from IRAM, allowing 8-bit access. Hook in @d-a-v and @pfalcon's uzlib port to actually do the decompression. Do not use any CRC checking which saves space. Since we have overwritten all of flash by the time we know id the CRC matches, there's nothing we could have done anyway. Adjust the Updater class to support GZIP files and not attempt to patch them. Bootloader builds to 0xd90 out of 0xfff bytes. * Add @d-a-v's patch for httpupdate #6820 (review) * Update uzlib to point to pfalcon++ For now, because there are some self-test failures with @d-a-v's esp8266 branch (whose cool new features we don't actually use in eboot now) start with pfalcon's 2.9 release and add the 2 patches (clcidx to code from IRAM/RODATA, and the Windows test file renaming) needed to build and run successfully. * Add (c) notice for uzlib to README
1 parent d40dbb4 commit 1d0bc5e

File tree

10 files changed

+132
-42
lines changed

10 files changed

+132
-42
lines changed

Diff for: .gitmodules

+3
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,6 @@
1919
[submodule "tools/esptool"]
2020
path = tools/esptool
2121
url = https://github.com/espressif/esptool.git
22+
[submodule "tools/sdk/uzlib"]
23+
path = tools/sdk/uzlib
24+
url = https://github.com/earlephilhower/uzlib.git

Diff for: README.md

+2
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ ESP8266 core files are licensed under LGPL.
124124

125125
[LittleFS](https://github.com/ARMmbed/littlefs) library written by ARM Limited and released under the [BSD 3-clause license](https://github.com/ARMmbed/littlefs/blob/master/LICENSE.md).
126126

127+
[uzlib](https://github.com/pfalcon/uzlib) library written and (c) 2014-2018 Paul Sokolovsky, licensed under the ZLib license (https://www.zlib.net/zlib_license.html).
128+
127129
### Other useful links ###
128130

129131
[Toolchain repo](https://github.com/earlephilhower/esp-quick-toolchain)

Diff for: bootloaders/eboot/Makefile

+19-12
Original file line numberDiff line numberDiff line change
@@ -6,44 +6,51 @@ TARGET_DIR := ./
66

77
TARGET_OBJ_FILES := \
88
eboot.o \
9-
eboot_command.o \
10-
9+
eboot_command.o
1110

1211
TARGET_OBJ_PATHS := $(addprefix $(TARGET_DIR)/,$(TARGET_OBJ_FILES))
1312

13+
UZLIB_PATH := ../../tools/sdk/uzlib/src
14+
UZLIB_FLAGS := -DRUNTIME_BITS_TABLES
15+
1416
CC := $(XTENSA_TOOLCHAIN)xtensa-lx106-elf-gcc
1517
CXX := $(XTENSA_TOOLCHAIN)xtensa-lx106-elf-g++
1618
AR := $(XTENSA_TOOLCHAIN)xtensa-lx106-elf-ar
1719
LD := $(XTENSA_TOOLCHAIN)xtensa-lx106-elf-gcc
1820
OBJDUMP := $(XTENSA_TOOLCHAIN)xtensa-lx106-elf-objdump
1921

20-
INC += -I../../tools/sdk/include
22+
INC += -I../../tools/sdk/include -I../../tools/sdk/uzlib/src
23+
2124
CFLAGS += -std=gnu99
2225

23-
CFLAGS += -O0 -g -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -mlongcalls -mno-text-section-literals
26+
CFLAGS += -Os -g -Wall -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -mlongcalls -mno-text-section-literals -ffunction-sections -fdata-sections
2427

2528
CFLAGS += $(INC)
2629

27-
LDFLAGS += -nostdlib -Wl,--no-check-sections -umain
30+
CFLAGS += $(UZLIB_FLAGS)
31+
32+
LDFLAGS += -nostdlib -Wl,--no-check-sections -Wl,--gc-sections -umain
2833

2934
LD_SCRIPT := -Teboot.ld
3035

3136
APP_OUT:= eboot.elf
3237
APP_AR := eboot.a
3338
APP_FW := eboot.bin
3439

35-
all: $(APP_FW)
3640

37-
$(APP_AR): $(TARGET_OBJ_PATHS)
38-
$(AR) cru $@ $^
41+
all: $(APP_OUT)
3942

43+
tinflate.o: $(UZLIB_PATH)/tinflate.c $(UZLIB_PATH)/uzlib.h $(UZLIB_PATH)/uzlib_conf.h
44+
$(CC) $(CFLAGS) -c -o tinflate.o $(UZLIB_PATH)/tinflate.c
4045

41-
$(APP_OUT): $(APP_AR)
42-
$(LD) $(LD_SCRIPT) $(LDFLAGS) -Wl,--start-group -Wl,--whole-archive $(APP_AR) -Wl,--end-group -o $@
46+
tinfgzip.o: $(UZLIB_PATH)/tinfgzip.c $(UZLIB_PATH)/uzlib.h $(UZLIB_PATH)/uzlib_conf.h
47+
$(CC) $(CFLAGS) -c -o tinfgzip.o $(UZLIB_PATH)/tinfgzip.c
4348

44-
$(APP_FW): $(APP_OUT)
45-
$(ESPTOOL) -vvv -eo $(APP_OUT) -bo $@ -bs .text -bs .data -bs .rodata -bc -ec || true
49+
$(APP_AR): $(TARGET_OBJ_PATHS) tinflate.o tinfgzip.o
50+
$(AR) cru $@ $^
4651

52+
$(APP_OUT): $(APP_AR)
53+
$(LD) $(LD_SCRIPT) $(LDFLAGS) -Wl,--start-group -Wl,--whole-archive $(APP_AR) -Wl,--end-group -o $@
4754

4855
clean:
4956
rm -f *.o

Diff for: bootloaders/eboot/eboot.c

+85-17
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
#include <string.h>
1313
#include "flash.h"
1414
#include "eboot_command.h"
15+
#include <uzlib.h>
16+
17+
extern unsigned char _gzip_dict;
1518

1619
#define SWRST do { (*((volatile uint32_t*) 0x60000700)) |= 0x80000000; } while(0);
1720

@@ -24,10 +27,14 @@ int print_version(const uint32_t flash_addr)
2427
if (SPIRead(flash_addr + APP_START_OFFSET + sizeof(image_header_t) + sizeof(section_header_t), &ver, sizeof(ver))) {
2528
return 1;
2629
}
27-
const char* __attribute__ ((aligned (4))) fmtt = "v%08x\n\0\0";
28-
uint32_t fmt[2];
29-
fmt[0] = ((uint32_t*) fmtt)[0];
30-
fmt[1] = ((uint32_t*) fmtt)[1];
30+
char fmt[16];
31+
fmt[0] = 'v';
32+
fmt[1] = '%';
33+
fmt[2] = '0';
34+
fmt[3] = '8';
35+
fmt[4] = 'x';
36+
fmt[5] = '\n';
37+
fmt[6] = '0';
3138
ets_printf((const char*) fmt, ver);
3239
return 0;
3340
}
@@ -80,37 +87,96 @@ int load_app_from_flash_raw(const uint32_t flash_addr)
8087
pos += section_header.size;
8188
}
8289

83-
register uint32_t sp asm("a1") = 0x3ffffff0;
84-
register uint32_t pc asm("a3") = image_header.entry;
85-
__asm__ __volatile__ ("jx a3");
90+
asm volatile("" ::: "memory");
91+
asm volatile ("mov.n a1, %0\n"
92+
"mov.n a3, %1\n"
93+
"jx a3\n" : : "r" (0x3ffffff0), "r" (image_header.entry) );
8694

95+
__builtin_unreachable(); // Save a few bytes by letting GCC know no need to pop regs/return
8796
return 0;
8897
}
8998

99+
uint8_t read_flash_byte(const uint32_t addr)
100+
{
101+
uint8_t __attribute__((aligned(4))) buff[4];
102+
SPIRead(addr & ~3, buff, 4);
103+
return buff[addr & 3];
104+
}
105+
unsigned char __attribute__((aligned(4))) uzlib_flash_read_cb_buff[4096];
106+
uint32_t uzlib_flash_read_cb_addr;
107+
int uzlib_flash_read_cb(struct uzlib_uncomp *m)
108+
{
109+
m->source = uzlib_flash_read_cb_buff;
110+
m->source_limit = uzlib_flash_read_cb_buff + sizeof(uzlib_flash_read_cb_buff);
111+
SPIRead(uzlib_flash_read_cb_addr, uzlib_flash_read_cb_buff, sizeof(uzlib_flash_read_cb_buff));
112+
uzlib_flash_read_cb_addr += sizeof(uzlib_flash_read_cb_buff);
113+
return *(m->source++);
114+
}
90115

116+
unsigned char gzip_dict[32768];
91117

92118
int copy_raw(const uint32_t src_addr,
93119
const uint32_t dst_addr,
94120
const uint32_t size)
95121
{
96122
// require regions to be aligned
97-
if (src_addr & 0xfff != 0 ||
98-
dst_addr & 0xfff != 0) {
123+
if ((src_addr & 0xfff) != 0 ||
124+
(dst_addr & 0xfff) != 0) {
99125
return 1;
100126
}
101127

102128
const uint32_t buffer_size = FLASH_SECTOR_SIZE;
103129
uint8_t buffer[buffer_size];
104-
uint32_t left = ((size+buffer_size-1) & ~(buffer_size-1));
130+
int32_t left = ((size+buffer_size-1) & ~(buffer_size-1));
105131
uint32_t saddr = src_addr;
106132
uint32_t daddr = dst_addr;
107-
108-
while (left) {
133+
struct uzlib_uncomp m_uncomp;
134+
bool gzip = false;
135+
136+
// Check if we are uncompressing a GZIP upload or not
137+
if ((read_flash_byte(saddr) == 0x1f) && (read_flash_byte(saddr + 1) == 0x8b)) {
138+
// GZIP signature matched. Find real size as encoded at the end
139+
left = read_flash_byte(saddr + size - 4);
140+
left += read_flash_byte(saddr + size - 3)<<8;
141+
left += read_flash_byte(saddr + size - 2)<<16;
142+
left += read_flash_byte(saddr + size - 1)<<24;
143+
144+
uzlib_init();
145+
146+
/* all 3 fields below must be initialized by user */
147+
m_uncomp.source = NULL;
148+
m_uncomp.source_limit = NULL;
149+
uzlib_flash_read_cb_addr = src_addr;
150+
m_uncomp.source_read_cb = uzlib_flash_read_cb;
151+
uzlib_uncompress_init(&m_uncomp, gzip_dict, sizeof(gzip_dict));
152+
153+
int res = uzlib_gzip_parse_header(&m_uncomp);
154+
if (res != TINF_OK) {
155+
return 5; // Error uncompress header read
156+
}
157+
gzip = true;
158+
}
159+
while (left > 0) {
109160
if (SPIEraseSector(daddr/buffer_size)) {
110161
return 2;
111162
}
112-
if (SPIRead(saddr, buffer, buffer_size)) {
113-
return 3;
163+
if (!gzip) {
164+
if (SPIRead(saddr, buffer, buffer_size)) {
165+
return 3;
166+
}
167+
} else {
168+
m_uncomp.dest_start = buffer;
169+
m_uncomp.dest = buffer;
170+
int to_read = (left > buffer_size) ? buffer_size : left;
171+
m_uncomp.dest_limit = buffer + to_read;
172+
int res = uzlib_uncompress(&m_uncomp);
173+
if ((res != TINF_DONE) && (res != TINF_OK)) {
174+
return 6;
175+
}
176+
// Fill any remaining with 0xff
177+
for (int i = to_read; i < buffer_size; i++) {
178+
buffer[i] = 0xff;
179+
}
114180
}
115181
if (SPIWrite(daddr, buffer, buffer_size)) {
116182
return 4;
@@ -124,13 +190,12 @@ int copy_raw(const uint32_t src_addr,
124190
}
125191

126192

127-
128-
void main()
193+
int main()
129194
{
130195
int res = 9;
131196
bool clear_cmd = false;
132197
struct eboot_command cmd;
133-
198+
134199
print_version(0);
135200

136201
if (eboot_command_read(&cmd) == 0) {
@@ -172,4 +237,7 @@ void main()
172237
}
173238

174239
while(true){}
240+
241+
__builtin_unreachable();
242+
return 0;
175243
}

Diff for: bootloaders/eboot/eboot.elf

19.8 KB
Binary file not shown.

Diff for: bootloaders/eboot/eboot.ld

+2-1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ SECTIONS
6868

6969
.data : ALIGN(4)
7070
{
71+
*(COMMON) /* Global vars */
72+
. = ALIGN(4);
7173
_heap_start = ABSOLUTE(.);
7274
/* _stack_sentry = ALIGN(0x8); */
7375
} >dram0_0_seg :dram0_0_bss_phdr
@@ -150,7 +152,6 @@ SECTIONS
150152
*(.bss)
151153
*(.bss.*)
152154
*(.gnu.linkonce.b.*)
153-
*(COMMON)
154155
. = ALIGN (8);
155156
_bss_end = ABSOLUTE(.);
156157
} >iram1_0_seg :iram1_0_phdr

Diff for: bootloaders/eboot/eboot_command.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ int eboot_command_read(struct eboot_command* cmd)
3737
}
3838

3939
uint32_t crc32 = eboot_command_calculate_crc32(cmd);
40-
if (cmd->magic & EBOOT_MAGIC_MASK != EBOOT_MAGIC ||
40+
if ((cmd->magic & EBOOT_MAGIC_MASK) != EBOOT_MAGIC ||
4141
cmd->crc32 != crc32) {
4242
return 1;
4343
}

Diff for: cores/esp8266/Updater.cpp

+9-3
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,8 @@ bool UpdaterClass::_writeBuffer(){
329329
bool modifyFlashMode = false;
330330
FlashMode_t flashMode = FM_QIO;
331331
FlashMode_t bufferFlashMode = FM_QIO;
332-
if ((_currentAddress == _startAddress + FLASH_MODE_PAGE) && (_command == U_FLASH)) {
332+
//TODO - GZIP can't do this
333+
if ((_currentAddress == _startAddress + FLASH_MODE_PAGE) && (_buffer[0] != 0x1f) && (_command == U_FLASH)) {
333334
flashMode = ESP.getFlashChipMode();
334335
#ifdef DEBUG_UPDATER
335336
DEBUG_UPDATER.printf_P(PSTR("Header: 0x%1X %1X %1X %1X\n"), _buffer[0], _buffer[1], _buffer[2], _buffer[3]);
@@ -411,7 +412,7 @@ size_t UpdaterClass::write(uint8_t *data, size_t len) {
411412
bool UpdaterClass::_verifyHeader(uint8_t data) {
412413
if(_command == U_FLASH) {
413414
// check for valid first magic byte (is always 0xE9)
414-
if(data != 0xE9) {
415+
if ((data != 0xE9) && (data != 0x1f)) {
415416
_currentAddress = (_startAddress + _size);
416417
_setError(UPDATE_ERROR_MAGIC_BYTE);
417418
return false;
@@ -435,7 +436,12 @@ bool UpdaterClass::_verifyEnd() {
435436
}
436437

437438
// check for valid first magic byte
438-
if(buf[0] != 0xE9) {
439+
//
440+
// TODO: GZIP compresses the chipsize flags, so can't do check here
441+
if ((buf[0] == 0x1f) && (buf[1] == 0x8b)) {
442+
// GZIP, just assume OK
443+
return true;
444+
} else if (buf[0] != 0xE9) {
439445
_currentAddress = (_startAddress);
440446
_setError(UPDATE_ERROR_MAGIC_BYTE);
441447
return false;

Diff for: libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp

+10-8
Original file line numberDiff line numberDiff line change
@@ -370,22 +370,24 @@ HTTPUpdateResult ESP8266HTTPUpdate::handleUpdate(HTTPClient& http, const String&
370370
}
371371

372372
// check for valid first magic byte
373-
if(buf[0] != 0xE9) {
373+
if(buf[0] != 0xE9 && buf[0] != 0x1f) {
374374
DEBUG_HTTP_UPDATE("[httpUpdate] Magic header does not start with 0xE9\n");
375375
_setLastError(HTTP_UE_BIN_VERIFY_HEADER_FAILED);
376376
http.end();
377377
return HTTP_UPDATE_FAILED;
378378

379379
}
380380

381-
uint32_t bin_flash_size = ESP.magicFlashChipSize((buf[3] & 0xf0) >> 4);
381+
if (buf[0] == 0xe9) {
382+
uint32_t bin_flash_size = ESP.magicFlashChipSize((buf[3] & 0xf0) >> 4);
382383

383-
// check if new bin fits to SPI flash
384-
if(bin_flash_size > ESP.getFlashChipRealSize()) {
385-
DEBUG_HTTP_UPDATE("[httpUpdate] New binary does not fit SPI Flash size\n");
386-
_setLastError(HTTP_UE_BIN_FOR_WRONG_FLASH);
387-
http.end();
388-
return HTTP_UPDATE_FAILED;
384+
// check if new bin fits to SPI flash
385+
if(bin_flash_size > ESP.getFlashChipRealSize()) {
386+
DEBUG_HTTP_UPDATE("[httpUpdate] New binary does not fit SPI Flash size\n");
387+
_setLastError(HTTP_UE_BIN_FOR_WRONG_FLASH);
388+
http.end();
389+
return HTTP_UPDATE_FAILED;
390+
}
389391
}
390392
}
391393
if(runUpdate(*tcp, len, http.header("x-MD5"), command)) {

Diff for: tools/sdk/uzlib

Submodule uzlib added at 80765a4

0 commit comments

Comments
 (0)