Merge pull request #3574 from cesanta/rollback

Add MG_OTA_{ROLLBACK,STATE_SET,STATE_GET}
This commit is contained in:
Sergey Lyubka authored and GitHub committed 2026-06-01 12:05:44 +01:00
commit c783cfade8
16 files changed
+345 -131

No files matched your search

+2 -1
View File
@@ -27,7 +27,7 @@ bool mg_ota_flash_begin(size_t new_firmware_size, struct mg_flash *flash) {
s_size = new_firmware_size;
MG_INFO(("Starting OTA, firmware size %lu", s_size));
} else {
MG_ERROR(("Firmware %lu is too big to fit %lu", new_firmware_size, half));
MG_ERROR(("Firmware %lu is too big to fit %lu", new_firmware_size, half - flash->align));
}
}
return ok;
@@ -76,6 +76,7 @@ bool mg_ota_flash_end(struct mg_flash *flash) {
}
#endif
s_size = 0;
if (ok) MG_OTA_STATE_SET(MG_OTA_TESTING);
if (ok) ok = flash->swap_fn();
}
MG_INFO(("Finishing OTA: %s", ok ? "ok" : "fail"));
+7
View File
@@ -1041,6 +1041,11 @@ static void mg_upload_handler(struct mg_connection *c, int ev, void *ev_data) {
(void) ev_data;
}
static void mg_upload_default_cb(struct mg_connection *c, const char *status) {
MG_INFO(("%lu %s", c->id, status ? status : "ok"));
mg_http_reply(c, status ? 500 : 200, "", "%s\n", status ? status : "ok");
}
void mg_http_start_upload(struct mg_connection *c, struct mg_http_message *hm,
struct mg_str name, struct mg_str dir,
struct mg_fs *fs,
@@ -1048,6 +1053,7 @@ void mg_http_start_upload(struct mg_connection *c, struct mg_http_message *hm,
struct mg_upload_priv *p = (struct mg_upload_priv *) c->data;
char path[MG_PATH_MAX];
struct mg_fd *fd;
if (fn == NULL) fn = mg_upload_default_cb;
if (sizeof(*p) > sizeof(c->data)) { fn(c, "data too small"); return; }
if (!mg_path_is_sane(name)) { fn(c, "bad name"); return; }
mg_snprintf(path, sizeof(path), "%.*s%c%.*s", (int) dir.len, dir.buf,
@@ -1067,6 +1073,7 @@ void mg_http_start_upload(struct mg_connection *c, struct mg_http_message *hm,
void mg_http_start_ota(struct mg_connection *c, struct mg_http_message *hm,
void (*fn)(struct mg_connection *, const char *)) {
struct mg_upload_priv *p = (struct mg_upload_priv *) c->data;
if (fn == NULL) fn = mg_upload_default_cb;
if (sizeof(*p) > sizeof(c->data)) { fn(c, "data too small"); return; }
if (!mg_ota_begin(hm->body.len)) { fn(c, "ota begin failed"); return; }
p->expected = hm->body.len;
+80 -25
View File
@@ -6,24 +6,24 @@
#include "arch.h"
#include "net.h"
#define MG_OTA_NONE 0 // No OTA support
#define MG_OTA_STM32H5 1 // STM32 H5
#define MG_OTA_STM32H7 2 // STM32 H7
#define MG_OTA_STM32H7_DUAL_CORE 3 // STM32 H7 dual core
#define MG_OTA_STM32F 4 // STM32 F7/F4/F2
#define MG_OTA_CH32V307 100 // WCH CH32V307
#define MG_OTA_U2A 200 // Renesas U2A16, U2A8, U2A6
#define MG_OTA_RT1020 300 // IMXRT1020
#define MG_OTA_RT1050 301 // IMXRT1050
#define MG_OTA_RT1060 302 // IMXRT1060
#define MG_OTA_RT1064 303 // IMXRT1064
#define MG_OTA_RT1170 304 // IMXRT1170
#define MG_OTA_MCXN 310 // MCXN947
#define MG_OTA_RW612 320 // FRDM-RW612
#define MG_OTA_FLASH 900 // OTA via an internal flash
#define MG_OTA_ESP32 910 // ESP32 OTA implementation
#define MG_OTA_PICOSDK 920 // RP2040/2350 using Pico-SDK hardware_flash
#define MG_OTA_CUSTOM 1000 // Custom implementation
#define MG_OTA_NONE 0 // No OTA support
#define MG_OTA_STM32H5 1 // STM32 H5
#define MG_OTA_STM32H7 2 // STM32 H7
#define MG_OTA_STM32H7_DUAL_CORE 3 // STM32 H7 dual core
#define MG_OTA_STM32F 4 // STM32 F7/F4/F2
#define MG_OTA_CH32V307 100 // WCH CH32V307
#define MG_OTA_U2A 200 // Renesas U2A16, U2A8, U2A6
#define MG_OTA_RT1020 300 // IMXRT1020
#define MG_OTA_RT1050 301 // IMXRT1050
#define MG_OTA_RT1060 302 // IMXRT1060
#define MG_OTA_RT1064 303 // IMXRT1064
#define MG_OTA_RT1170 304 // IMXRT1170
#define MG_OTA_MCXN 310 // MCXN947
#define MG_OTA_RW612 320 // FRDM-RW612
#define MG_OTA_FLASH 900 // OTA via an internal flash
#define MG_OTA_ESP32 910 // ESP32 OTA implementation
#define MG_OTA_PICOSDK 920 // RP2040/2350 using Pico-SDK hardware_flash
#define MG_OTA_CUSTOM 1000 // Custom implementation
#ifndef MG_OTA
#define MG_OTA MG_OTA_NONE
@@ -33,15 +33,70 @@
#define MG_IRAM __attribute__((noinline, section(".iram")))
#else
#define MG_IRAM
#endif // compiler
#endif // IRAM
#endif // OTA
#endif // compiler
#endif // IRAM
#endif // OTA
// Firmware update API
bool mg_ota_begin(size_t new_firmware_size); // Start writing
bool mg_ota_write(const void *buf, size_t len); // Write chunk, aligned to 1k
bool mg_ota_end(void); // Stop writing
void mg_ota_url_check(struct mg_mgr *mgr,
const char *current_version,
const char *metadata_url,
void (*fn)(const char *status));
// MG_OTA_ROLLBACK: swap firmware banks and reset. On flash-based OTA the
// default calls swap_fn() (no-op on single-bank) then resets. Override in
// mongoose_config.h for platforms without mg_flash (ESP32, PicoSDK, etc.).
#ifndef MG_OTA_ROLLBACK
#define MG_OTA_ROLLBACK() \
do { \
mg_flash->swap_fn(); \
NVIC_SystemReset(); \
} while (0)
#endif
// MG_OTA_ROLLBACK_TIMER_START(seconds): arm a hardware watchdog as the rollback
// deadline. If it fires before MG_OTA_STATE_SET(MG_OTA_CONFIRMED), the device
// resets and MG_OTA_STATE_GET() == MG_OTA_FAILED triggers MG_OTA_ROLLBACK().
#ifndef MG_OTA_ROLLBACK_TIMER_START
#define MG_OTA_ROLLBACK_TIMER_START(seconds) (void) (seconds)
#endif
// OTA state values for MG_OTA_STATE_GET / MG_OTA_STATE_SET.
// MG_OTA_CONFIRMED: firmware is committed, normal operation.
// MG_OTA_TESTING: new firmware booted for the first time; IWDG is armed.
// If the device reboots while in this state, it transitions
// to MG_OTA_FAILED and rolls back on the next boot.
// MG_OTA_FAILED: previous boot did not commit in time; rollback on next
// boot.
enum { MG_OTA_CONFIRMED = 0, MG_OTA_TESTING = 1, MG_OTA_FAILED = 2 };
// Persistent OTA state storage. Define in mongoose_config.h to use a
// location that survives resets
#ifndef MG_OTA_STATE_GET
#define MG_OTA_STATE_GET() 0
#endif
#ifndef MG_OTA_STATE_SET
#define MG_OTA_STATE_SET(val) (void) (val)
#endif
// Call once at boot. Arms the rollback watchdog for (seconds) seconds when
// the new firmware is in TESTING state. Rolls back immediately if FAILED.
// boot (TESTING) → set FAILED → arm IWDG → run firmware
// ├── commit called → set CONFIRMED → NVIC_SystemReset() → clean boot
// └── IWDG fires → hard reset → boot → state is still FAILED → rollback
#define MG_OTA_BOOT_CHECK(seconds) \
do { \
if (MG_OTA_STATE_GET() == MG_OTA_FAILED) { \
MG_OTA_STATE_SET(MG_OTA_CONFIRMED); \
MG_INFO(("Commit deadline expired, rolling back")); \
MG_OTA_ROLLBACK(); \
} else if (MG_OTA_STATE_GET() == MG_OTA_TESTING) { \
MG_OTA_STATE_SET(MG_OTA_FAILED); \
MG_INFO(("New firmware: commit within %u sec or rolls back", \
(unsigned) (seconds))); \
MG_OTA_ROLLBACK_TIMER_START(seconds); \
} \
} while (0)
// Pull based OTA over HTTP
void mg_ota_url_check(struct mg_mgr *mgr, const char *current_version,
const char *metadata_url, void (*fn)(const char *status));
+3 -6
View File
@@ -96,10 +96,8 @@ static bool mg_stm32h5_swap(void) {
uint32_t desired = flash_bank_is_swapped() ? 0 : MG_BIT(31);
flash_unlock();
flash_clear_err();
// printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG);
MG_SET_BITS(MG_REG(FLASH_OPTSR_PRG), MG_BIT(31), desired);
// printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG);
MG_REG(FLASH_OPTCR) |= MG_BIT(1); // OPTSTART
MG_REG(FLASH_OPTCR) |= MG_BIT(1); // OPTSTART; triggers auto-reset on H5
while ((MG_REG(FLASH_OPTSR_CUR) & MG_BIT(31)) != desired) (void) 0;
return true;
}
@@ -146,10 +144,9 @@ bool mg_ota_write(const void *buf, size_t len) {
return mg_ota_flash_write(buf, len, &s_mg_flash_stm32h5);
}
// Actual bank swap is deferred until reset, it is safe to execute in flash
bool mg_ota_end(void) {
if(!mg_ota_flash_end(&s_mg_flash_stm32h5)) return false;
*(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
if (!mg_ota_flash_end(&s_mg_flash_stm32h5)) return false;
*(volatile uint32_t *) 0xe000ed0c = 0x5fa0004U; // NVIC_SystemReset()
return true;
}
struct mg_flash *mg_flash = &s_mg_flash_stm32h5;
+7 -7
View File
@@ -112,6 +112,7 @@ MG_IRAM static bool mg_stm32h7_erase(void *addr) {
MG_REG(bank + FLASH_CR) |= (sector & 7U) << 8U; // Sector to erase
MG_REG(bank + FLASH_CR) |= MG_BIT(2); // Sector erase bit
MG_REG(bank + FLASH_CR) |= MG_BIT(7); // Start erasing
flash_wait(bank);
ok = !flash_is_err(bank);
MG_DEBUG(("Erase sector %lu @ %p %s. CR %#lx SR %#lx", sector, addr,
ok ? "ok" : "fail", MG_REG(bank + FLASH_CR),
@@ -127,9 +128,7 @@ MG_IRAM static bool mg_stm32h7_swap(void) {
uint32_t desired = flash_bank_is_swapped(bank) ? 0 : MG_BIT(31);
flash_unlock();
flash_clear_err(bank);
// printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG);
MG_SET_BITS(MG_REG(bank + FLASH_OPTSR_PRG), MG_BIT(31), desired);
// printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG);
MG_REG(bank + FLASH_OPTCR) |= MG_BIT(1); // OPTSTART
while ((MG_REG(bank + FLASH_OPTSR_CUR) & MG_BIT(31)) != desired) (void) 0;
return true;
@@ -162,7 +161,7 @@ MG_IRAM static bool mg_stm32h7_write(void *addr, const void *buf, size_t len) {
if (flash_is_err(bank)) ok = false;
}
if (!s_flash_irq_disabled) MG_ARM_ENABLE_IRQ();
MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst,
MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, addr,
ok ? "ok" : "fail", MG_REG(bank + FLASH_CR),
MG_REG(bank + FLASH_SR)));
MG_REG(bank + FLASH_CR) &= ~MG_BIT(1); // Clear programming flag
@@ -175,7 +174,7 @@ MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss) {
for (size_t ofs = 0; ofs < s; ofs += ss) {
mg_stm32h7_write(p1 + ofs, p2 + ofs, ss);
}
*(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
*(volatile uint32_t *) 0xe000ed0cU = 0x5fa0004U; // NVIC_SystemReset()
}
bool mg_ota_begin(size_t new_firmware_size) {
@@ -185,8 +184,8 @@ bool mg_ota_begin(size_t new_firmware_size) {
s_mg_flash_stm32h7.size /= 2;
}
#ifdef __ZEPHYR__
*((uint32_t *)0xE000ED94) = 0;
MG_DEBUG(("Jailbreak %s", *((uint32_t *)0xE000ED94) == 0 ? "successful" : "failed"));
*((uint32_t *) 0xE000ED94) = 0;
MG_DEBUG(("Jailbreak %s", *((uint32_t *) 0xE000ED94) == 0 ? "ok" : "failed"));
#endif
return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_stm32h7);
}
@@ -199,7 +198,7 @@ bool mg_ota_end(void) {
if (mg_ota_flash_end(&s_mg_flash_stm32h7)) {
if (is_dualbank()) {
// Bank swap is deferred until reset, been executing in flash, reset
*(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
*(volatile uint32_t *) 0xe000ed0cU = 0x5fa0004U; // NVIC_SystemReset()
} else {
// Swap partitions. Pray power does not go away
MG_INFO(("Swapping partitions, size %u (%u sectors)",
@@ -217,5 +216,6 @@ bool mg_ota_end(void) {
}
return false;
}
struct mg_flash *mg_flash = &s_mg_flash_stm32h7;
#endif
+1 -1
View File
@@ -3,7 +3,7 @@
#include "tls_uecc.h"
#include "tls.h"
#if MG_TLS == MG_TLS_BUILTIN
#if MG_TLS == MG_TLS_BUILTIN || defined(MG_OTA_PUBLIC_KEY)
#ifndef MG_UECC_RNG_MAX_TRIES
#define MG_UECC_RNG_MAX_TRIES 64