diff --git a/mongoose.c b/mongoose.c index 284f6662..e895c9c9 100644 --- a/mongoose.c +++ b/mongoose.c @@ -756,10 +756,11 @@ MG_IRAM void mg_device_reset(void) { #ifdef MG_ENABLE_LINES #line 1 "src/device_stm32h5.c" #endif +// -#if MG_DEVICE == MG_DEVICE_STM32H5 +#if MG_OTA == MG_OTA_STM32H5 #define FLASH_BASE 0x40022000 // Base address of the flash controller #define FLASH_KEYR (FLASH_BASE + 0x4) // See RM0481 7.11 @@ -771,6 +772,7 @@ MG_IRAM void mg_device_reset(void) { #define FLASH_OPTSR_CUR (FLASH_BASE + 0x50) #define FLASH_OPTSR_PRG (FLASH_BASE + 0x54) +#if 0 void *mg_flash_start(void) { return (void *) 0x08000000; } @@ -786,6 +788,7 @@ size_t mg_flash_write_align(void) { int mg_flash_bank(void) { return MG_REG(FLASH_OPTCR) & MG_BIT(31) ? 2 : 1; } +#endif static void flash_unlock(void) { static bool unlocked = false; @@ -824,14 +827,14 @@ static bool flash_bank_is_swapped(void) { return MG_REG(FLASH_OPTCR) & MG_BIT(31); // RM0481 7.11.8 } -bool mg_flash_erase(void *location) { +static bool mg_stm32h5_erase(void *location) { bool ok = false; if (flash_page_start(location) == false) { MG_ERROR(("%p is not on a sector boundary")); } else { uintptr_t diff = (char *) location - (char *) mg_flash_start(); uint32_t sector = diff / mg_flash_sector_size(); - uint32_t saved_cr = MG_REG(FLASH_NSCR); // Save CR value + uint32_t saved_cr = MG_REG(FLASH_NSCR); // Save CR value flash_unlock(); flash_clear_err(); MG_REG(FLASH_NSCR) = 0; @@ -847,12 +850,12 @@ bool mg_flash_erase(void *location) { MG_DEBUG(("Erase sector %lu @ %p: %s. CR %#lx SR %#lx", sector, location, ok ? "ok" : "fail", MG_REG(FLASH_NSCR), MG_REG(FLASH_NSSR))); // mg_hexdump(location, 32); - MG_REG(FLASH_NSCR) = saved_cr; // Restore saved CR + MG_REG(FLASH_NSCR) = saved_cr; // Restore saved CR } return ok; } -bool mg_flash_swap_bank(void) { +static bool mg_stm32h5_swap(void) { uint32_t desired = flash_bank_is_swapped() ? 0 : MG_BIT(31); flash_unlock(); flash_clear_err(); @@ -864,7 +867,7 @@ bool mg_flash_swap_bank(void) { return true; } -bool mg_flash_write(void *addr, const void *buf, size_t len) { +static bool mg_stm32h5_write(void *addr, const void *buf, size_t len) { if ((len % mg_flash_write_align()) != 0) { MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align())); return false; @@ -879,7 +882,7 @@ bool mg_flash_write(void *addr, const void *buf, size_t len) { // MG_DEBUG(("Starting flash write %lu bytes @ %p", len, addr)); MG_REG(FLASH_NSCR) = MG_BIT(1); // Set programming flag while (ok && src < end) { - if (flash_page_start(dst) && mg_flash_erase(dst) == false) break; + if (flash_page_start(dst) && mg_stm32h5_erase(dst) == false) break; *(volatile uint32_t *) dst++ = *src++; flash_wait(); if (flash_is_err()) ok = false; @@ -892,9 +895,24 @@ bool mg_flash_write(void *addr, const void *buf, size_t len) { return ok; } -void mg_device_reset(void) { - // SCB->AIRCR = ((0x5fa << SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk); - *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; +static struct mg_flash s_mg_flash_stm32h5 = { + (void *) 0x08000000, // Start + 2 * 1024 * 1024, // Size, 2Mb + 16, // Align, 128 bit + mg_stm32h5_write, + mg_stm32h5_swap, +}; + +bool mg_ota_begin(size_t new_firmware_size) { + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_stm32h5); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_stm32h5); +} + +bool mg_ota_end(void) { + return mg_ota_flash_end(&s_mg_flash_stm32h5); } #endif @@ -1365,6 +1383,75 @@ void mg_error(struct mg_connection *c, const char *fmt, ...) { mg_call(c, MG_EV_ERROR, buf); // Let user handler override it } +#ifdef MG_ENABLE_LINES +#line 1 "src/flash.c" +#endif + + + + + +static char *s_addr; // Current address to write to +static size_t s_size; // Firmware size to flash. In-progress indicator +static uint32_t s_crc32; // Firmware checksum + +bool mg_ota_flash_begin(size_t new_firmware_size, struct mg_flash *flash) { + bool ok = false; + if (s_size) { + MG_ERROR(("OTA already in progress. Call mg_ota_end()")); + } else { + size_t half = flash->size / 2; + s_crc32 = 0; + s_addr = (char *) flash->start + half; + MG_DEBUG(("FW %lu bytes, max %lu", new_firmware_size, half)); + if (new_firmware_size < half) { + ok = true; + 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)); + } + } + return ok; +} + +bool mg_ota_flash_write(const void *buf, size_t len, struct mg_flash *flash) { + bool ok = false; + if (s_size == 0) { + MG_ERROR(("OTA is not started, call mg_ota_begin()")); + } else { + size_t len_aligned_down = MG_ROUND_DOWN(len, flash->align); + if (len_aligned_down) ok = flash->write_fn(s_addr, buf, len_aligned_down); + if (len_aligned_down < len) { + size_t left = len - len_aligned_down; + char tmp[flash->align]; + memset(tmp, 0xff, sizeof(tmp)); + memcpy(tmp, (char *) buf + len_aligned_down, left); + ok = flash->write_fn(s_addr + len_aligned_down, tmp, sizeof(tmp)); + } + s_crc32 = mg_crc32(s_crc32, (char *) buf, len); // Update CRC + MG_DEBUG(("%#x %p %lu -> %d", s_addr - len, buf, len, ok)); + s_addr += len; + } + return ok; +} + +bool mg_ota_flash_end(struct mg_flash *flash) { + char *base = (char *) flash->start + flash->size / 2; + bool ok = false; + if (s_size) { + size_t size = (size_t) (s_addr - base); + uint32_t crc32 = mg_crc32(0, base, s_size); + if (size == s_size && crc32 == s_crc32) ok = true; + MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, s_size, + size, ok ? "ok" : "fail")); + s_size = 0; + if (ok) ok = flash->swap_fn(); + } + MG_INFO(("Finishing OTA: %s", ok ? "ok" : "fail")); + return ok; +} + #ifdef MG_ENABLE_LINES #line 1 "src/fmt.c" #endif diff --git a/mongoose.h b/mongoose.h index 727e5557..52a454fc 100644 --- a/mongoose.h +++ b/mongoose.h @@ -2643,6 +2643,7 @@ void mg_rpc_list(struct mg_rpc_req *r); #define MG_OTA_NONE 0 // No OTA support #define MG_OTA_FLASH 1 // OTA via an internal flash #define MG_OTA_ESP32 2 // ESP32 OTA implementation +#define MG_OTA_STM32H5 3 // STM32H5 OTA implementation #define MG_OTA_CUSTOM 100 // Custom implementation #ifndef MG_OTA @@ -2676,6 +2677,19 @@ size_t mg_ota_size(int firmware); // Firmware size bool mg_ota_commit(void); // Commit current firmware bool mg_ota_rollback(void); // Rollback to the previous firmware MG_IRAM void mg_ota_boot(void); // Bootloader function + + +struct mg_flash { + void *start; // Address at which flash starts + size_t size; // Flash size + size_t align; // Write alignment + bool (*write_fn)(void *, const void *, size_t); // Write function + bool (*swap_fn)(void); // Swap partitions +}; + +bool mg_ota_flash_begin(size_t new_firmware_size, struct mg_flash *flash); +bool mg_ota_flash_write(const void *buf, size_t len, struct mg_flash *flash); +bool mg_ota_flash_end(struct mg_flash *flash); // Copyright (c) 2023 Cesanta Software Limited // All rights reserved diff --git a/src/device_stm32h5.c b/src/device_stm32h5.c index e0b1d4b5..8da8fe45 100644 --- a/src/device_stm32h5.c +++ b/src/device_stm32h5.c @@ -1,7 +1,8 @@ -#include "device.h" +// #include "device.h" +#include "flash.h" #include "log.h" -#if MG_DEVICE == MG_DEVICE_STM32H5 +#if MG_OTA == MG_OTA_STM32H5 #define FLASH_BASE 0x40022000 // Base address of the flash controller #define FLASH_KEYR (FLASH_BASE + 0x4) // See RM0481 7.11 @@ -13,6 +14,7 @@ #define FLASH_OPTSR_CUR (FLASH_BASE + 0x50) #define FLASH_OPTSR_PRG (FLASH_BASE + 0x54) +#if 0 void *mg_flash_start(void) { return (void *) 0x08000000; } @@ -28,6 +30,7 @@ size_t mg_flash_write_align(void) { int mg_flash_bank(void) { return MG_REG(FLASH_OPTCR) & MG_BIT(31) ? 2 : 1; } +#endif static void flash_unlock(void) { static bool unlocked = false; @@ -66,14 +69,14 @@ static bool flash_bank_is_swapped(void) { return MG_REG(FLASH_OPTCR) & MG_BIT(31); // RM0481 7.11.8 } -bool mg_flash_erase(void *location) { +static bool mg_stm32h5_erase(void *location) { bool ok = false; if (flash_page_start(location) == false) { MG_ERROR(("%p is not on a sector boundary")); } else { uintptr_t diff = (char *) location - (char *) mg_flash_start(); uint32_t sector = diff / mg_flash_sector_size(); - uint32_t saved_cr = MG_REG(FLASH_NSCR); // Save CR value + uint32_t saved_cr = MG_REG(FLASH_NSCR); // Save CR value flash_unlock(); flash_clear_err(); MG_REG(FLASH_NSCR) = 0; @@ -89,12 +92,12 @@ bool mg_flash_erase(void *location) { MG_DEBUG(("Erase sector %lu @ %p: %s. CR %#lx SR %#lx", sector, location, ok ? "ok" : "fail", MG_REG(FLASH_NSCR), MG_REG(FLASH_NSSR))); // mg_hexdump(location, 32); - MG_REG(FLASH_NSCR) = saved_cr; // Restore saved CR + MG_REG(FLASH_NSCR) = saved_cr; // Restore saved CR } return ok; } -bool mg_flash_swap_bank(void) { +static bool mg_stm32h5_swap(void) { uint32_t desired = flash_bank_is_swapped() ? 0 : MG_BIT(31); flash_unlock(); flash_clear_err(); @@ -106,7 +109,7 @@ bool mg_flash_swap_bank(void) { return true; } -bool mg_flash_write(void *addr, const void *buf, size_t len) { +static bool mg_stm32h5_write(void *addr, const void *buf, size_t len) { if ((len % mg_flash_write_align()) != 0) { MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align())); return false; @@ -121,7 +124,7 @@ bool mg_flash_write(void *addr, const void *buf, size_t len) { // MG_DEBUG(("Starting flash write %lu bytes @ %p", len, addr)); MG_REG(FLASH_NSCR) = MG_BIT(1); // Set programming flag while (ok && src < end) { - if (flash_page_start(dst) && mg_flash_erase(dst) == false) break; + if (flash_page_start(dst) && mg_stm32h5_erase(dst) == false) break; *(volatile uint32_t *) dst++ = *src++; flash_wait(); if (flash_is_err()) ok = false; @@ -134,8 +137,23 @@ bool mg_flash_write(void *addr, const void *buf, size_t len) { return ok; } -void mg_device_reset(void) { - // SCB->AIRCR = ((0x5fa << SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk); - *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; +static struct mg_flash s_mg_flash_stm32h5 = { + (void *) 0x08000000, // Start + 2 * 1024 * 1024, // Size, 2Mb + 16, // Align, 128 bit + mg_stm32h5_write, + mg_stm32h5_swap, +}; + +bool mg_ota_begin(size_t new_firmware_size) { + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_stm32h5); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_stm32h5); +} + +bool mg_ota_end(void) { + return mg_ota_flash_end(&s_mg_flash_stm32h5); } #endif diff --git a/src/flash.c b/src/flash.c new file mode 100644 index 00000000..08494394 --- /dev/null +++ b/src/flash.c @@ -0,0 +1,65 @@ +#include "arch.h" +#include "flash.h" +#include "log.h" +#include "ota.h" + +static char *s_addr; // Current address to write to +static size_t s_size; // Firmware size to flash. In-progress indicator +static uint32_t s_crc32; // Firmware checksum + +bool mg_ota_flash_begin(size_t new_firmware_size, struct mg_flash *flash) { + bool ok = false; + if (s_size) { + MG_ERROR(("OTA already in progress. Call mg_ota_end()")); + } else { + size_t half = flash->size / 2; + s_crc32 = 0; + s_addr = (char *) flash->start + half; + MG_DEBUG(("FW %lu bytes, max %lu", new_firmware_size, half)); + if (new_firmware_size < half) { + ok = true; + 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)); + } + } + return ok; +} + +bool mg_ota_flash_write(const void *buf, size_t len, struct mg_flash *flash) { + bool ok = false; + if (s_size == 0) { + MG_ERROR(("OTA is not started, call mg_ota_begin()")); + } else { + size_t len_aligned_down = MG_ROUND_DOWN(len, flash->align); + if (len_aligned_down) ok = flash->write_fn(s_addr, buf, len_aligned_down); + if (len_aligned_down < len) { + size_t left = len - len_aligned_down; + char tmp[flash->align]; + memset(tmp, 0xff, sizeof(tmp)); + memcpy(tmp, (char *) buf + len_aligned_down, left); + ok = flash->write_fn(s_addr + len_aligned_down, tmp, sizeof(tmp)); + } + s_crc32 = mg_crc32(s_crc32, (char *) buf, len); // Update CRC + MG_DEBUG(("%#x %p %lu -> %d", s_addr - len, buf, len, ok)); + s_addr += len; + } + return ok; +} + +bool mg_ota_flash_end(struct mg_flash *flash) { + char *base = (char *) flash->start + flash->size / 2; + bool ok = false; + if (s_size) { + size_t size = (size_t) (s_addr - base); + uint32_t crc32 = mg_crc32(0, base, s_size); + if (size == s_size && crc32 == s_crc32) ok = true; + MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, s_size, + size, ok ? "ok" : "fail")); + s_size = 0; + if (ok) ok = flash->swap_fn(); + } + MG_INFO(("Finishing OTA: %s", ok ? "ok" : "fail")); + return ok; +} diff --git a/src/flash.h b/src/flash.h new file mode 100644 index 00000000..6f6d8a64 --- /dev/null +++ b/src/flash.h @@ -0,0 +1,13 @@ +#include "arch.h" + +struct mg_flash { + void *start; // Address at which flash starts + size_t size; // Flash size + size_t align; // Write alignment + bool (*write_fn)(void *, const void *, size_t); // Write function + bool (*swap_fn)(void); // Swap partitions +}; + +bool mg_ota_flash_begin(size_t new_firmware_size, struct mg_flash *flash); +bool mg_ota_flash_write(const void *buf, size_t len, struct mg_flash *flash); +bool mg_ota_flash_end(struct mg_flash *flash); diff --git a/src/ota.h b/src/ota.h index 5109fc60..d8b63273 100644 --- a/src/ota.h +++ b/src/ota.h @@ -8,6 +8,7 @@ #define MG_OTA_NONE 0 // No OTA support #define MG_OTA_FLASH 1 // OTA via an internal flash #define MG_OTA_ESP32 2 // ESP32 OTA implementation +#define MG_OTA_STM32H5 3 // STM32H5 OTA implementation #define MG_OTA_CUSTOM 100 // Custom implementation #ifndef MG_OTA diff --git a/test/Makefile b/test/Makefile index def1a243..8d753908 100644 --- a/test/Makefile +++ b/test/Makefile @@ -203,7 +203,7 @@ mongoose.c: Makefile $(wildcard ../src/*.c) $(wildcard ../src/drivers/*.c) cd .. && (export LC_ALL=C ; cat src/license.h; echo; echo '#include "mongoose.h"' ; (for F in src/*.c src/drivers/*.c ; do echo; echo '#ifdef MG_ENABLE_LINES'; echo "#line 1 \"$$F\""; echo '#endif'; cat $$F | sed -e 's,#include ".*,,'; done))> $@ mongoose.h: $(HDRS) Makefile - cd .. && (cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/net_ft.h src/net_lwip.h src/net_rl.h src/config.h src/str.h src/queue.h src/fmt.h src/printf.h src/log.h src/timer.h src/fs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/sha256.h src/tls_x25519.h src/tls_aes128.h src/tls_uecc.h src/tls_chacha20.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/json.h src/rpc.h src/ota.h src/device.h src/net_builtin.h src/profile.h src/drivers/*.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@ + cd .. && (cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/net_ft.h src/net_lwip.h src/net_rl.h src/config.h src/str.h src/queue.h src/fmt.h src/printf.h src/log.h src/timer.h src/fs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/sha256.h src/tls_x25519.h src/tls_aes128.h src/tls_uecc.h src/tls_chacha20.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/json.h src/rpc.h src/ota.h src/flash.h src/device.h src/net_builtin.h src/profile.h src/drivers/*.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@ clean: clean_examples clean_refprojs clean_tutorials clean_examples_embedded