From 67d0f5f4aee73c7c1f11f20c3b61a0b5a0ac79d3 Mon Sep 17 00:00:00 2001 From: "Sergio R. Caprile" Date: Fri, 1 Nov 2024 14:27:24 -0300 Subject: [PATCH] Add OTA for Pico-SDK (RP2040) --- mongoose.c | 174 ++++++++++++++++++++++++++++++++++++++++++++++ mongoose.h | 9 +++ src/arch_rp2040.h | 8 +++ src/ota.h | 1 + src/ota_picosdk.c | 170 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 362 insertions(+) create mode 100644 src/ota_picosdk.c diff --git a/mongoose.c b/mongoose.c index a172c0e9..56b7d73f 100644 --- a/mongoose.c +++ b/mongoose.c @@ -6037,6 +6037,180 @@ bool mg_ota_end(void) { } #endif +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_picosdk.c" +#endif + + + + +#if MG_OTA == MG_OTA_PICOSDK + +// Both RP2040 and RP2350 have no flash, low-level flash access support in +// bootrom, and high-level support in Pico-SDK (2.0+ for the RP2350) +// - The RP2350 in RISC-V mode is not yet (fully) supported (nor tested) + +static bool mg_picosdk_write(void *, const void *, size_t); +static bool mg_picosdk_swap(void); + +static struct mg_flash s_mg_flash_picosdk = { + (void *) 0x10000000, // Start, not used here; functions handle offset +#ifdef PICO_FLASH_SIZE_BYTES + PICO_FLASH_SIZE_BYTES, // Size, from board definitions +#else + 0x200000, // Size, guess... is 2M enough ? +#endif + FLASH_SECTOR_SIZE, // Sector size, from hardware_flash + FLASH_PAGE_SIZE, // Align, from hardware_flash + mg_picosdk_write, mg_picosdk_swap, +}; + +#define MG_MODULO2(x, m) ((x) & ((m) -1)) + +static bool __no_inline_not_in_flash_func(flash_sector_start)( + volatile uint32_t *dst) { + char *base = (char *) s_mg_flash_picosdk.start, + *end = base + s_mg_flash_picosdk.size; + volatile char *p = (char *) dst; + return p >= base && p < end && + MG_MODULO2(p - base, s_mg_flash_picosdk.secsz) == 0; +} + +static bool __no_inline_not_in_flash_func(flash_erase)(void *addr) { + if (flash_sector_start(addr) == false) { + MG_ERROR(("%p is not on a sector boundary", addr)); + return false; + } + void *dst = (void *) ((char *) addr - (char *) s_mg_flash_picosdk.start); + flash_range_erase((uint32_t) dst, s_mg_flash_picosdk.secsz); + MG_DEBUG(("Sector starting at %p erasure", addr)); + return true; +} + +static bool __no_inline_not_in_flash_func(mg_picosdk_swap)(void) { + // TODO(): RP2350 might have some A/B functionality (DS 5.1) + return true; +} + +static bool s_flash_irq_disabled; + +static bool __no_inline_not_in_flash_func(mg_picosdk_write)(void *addr, + const void *buf, + size_t len) { + if ((len % s_mg_flash_picosdk.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_picosdk.align)); + return false; + } + if ((((size_t) addr - (size_t) s_mg_flash_picosdk.start) % + s_mg_flash_picosdk.align) != 0) { + MG_ERROR(("%p is not on a page boundary", addr)); + return false; + } + + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); + +#ifndef __riscv + MG_ARM_DISABLE_IRQ(); +#else + asm volatile("csrrc zero, mstatus, %0" : : "i"(1 << 3) : "memory"); +#endif + while (src < end) { + uint32_t dst_ofs = (uint32_t) dst - (uint32_t) s_mg_flash_picosdk.start; + if (flash_sector_start(dst) && flash_erase(dst) == false) break; + // flash_range_program() runs in RAM and handles writing up to + // FLASH_PAGE_SIZE bytes. Source must not be in flash + flash_range_program((uint32_t) dst_ofs, (uint8_t *) src, + s_mg_flash_picosdk.align); + src = (uint32_t *) ((char *) src + s_mg_flash_picosdk.align); + dst = (uint32_t *) ((char *) dst + s_mg_flash_picosdk.align); + } + if (!s_flash_irq_disabled) { +#ifndef __riscv + MG_ARM_ENABLE_IRQ(); +#else + asm volatile("csrrs mstatus, %0" : : "i"(1 << 3) : "memory"); +#endif + } + MG_DEBUG(("Flash write %lu bytes @ %p.", len, dst)); + return true; +} + +// just overwrite instead of swap +static void __no_inline_not_in_flash_func(single_bank_swap)(char *p1, char *p2, + size_t s, + size_t ss) { + char *tmp = malloc(ss); + if (tmp == NULL) return; +#if PICO_RP2040 + uint32_t xip[256 / sizeof(uint32_t)]; + void *dst = (void *) ((char *) p1 - (char *) s_mg_flash_picosdk.start); + size_t count = MG_ROUND_UP(s, ss); + // use SDK function calls to get BootROM function pointers + rom_connect_internal_flash_fn connect = (rom_connect_internal_flash_fn) rom_func_lookup_inline(ROM_FUNC_CONNECT_INTERNAL_FLASH); + rom_flash_exit_xip_fn xit = (rom_flash_exit_xip_fn) rom_func_lookup_inline(ROM_FUNC_FLASH_EXIT_XIP); + rom_flash_range_program_fn program = (rom_flash_range_program_fn) rom_func_lookup_inline(ROM_FUNC_FLASH_RANGE_PROGRAM); + rom_flash_flush_cache_fn flush = (rom_flash_flush_cache_fn) rom_func_lookup_inline(ROM_FUNC_FLASH_FLUSH_CACHE); + // no stdlib calls here. + MG_ARM_DISABLE_IRQ(); + // 2nd bootloader (XIP) is in flash, SDK functions copy it to RAM on entry + for (size_t i = 0; i < 256 / sizeof(uint32_t); i++) + xip[i] = ((uint32_t *) (s_mg_flash_picosdk.start))[i]; + flash_range_erase((uint32_t) dst, count); + // flash has been erased, no XIP to copy. Only BootROM calls possible + for (uint32_t ofs = 0; ofs < s; ofs += ss) { + for (size_t i = 0; i < ss; i++) tmp[i] = p2[ofs + i]; + __compiler_memory_barrier(); + connect(); + xit(); + program((uint32_t) dst + ofs, tmp, ss); + flush(); + ((void (*)(void))((intptr_t) xip + 1))(); // enter XIP again + } + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; // AIRCR = SYSRESETREQ +#else + // RP2350 has bootram and copies second bootloader there, SDK uses that copy, + // It might also be able to take advantage of partition swapping + for (size_t ofs = 0; ofs < s; ofs += ss) { + for (size_t i = 0; i < ss; i++) tmp[i] = p2[ofs + i]; + mg_picosdk_write(p1 + ofs, tmp, ss); + } +#ifndef __riscv + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; // AIRCR = SYSRESETREQ +#else + // TODO(): find a way to do a system reset, like block resets and watchdog +#endif +#endif +} + +bool mg_ota_begin(size_t new_firmware_size) { + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_picosdk); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_picosdk); +} + +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_picosdk)) { + // Swap partitions. Pray power does not go away + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_picosdk.size, + s_mg_flash_picosdk.size / s_mg_flash_picosdk.secsz)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + s_flash_irq_disabled = true; + // Runs in RAM, will reset when finished or return on failure + single_bank_swap( + (char *) s_mg_flash_picosdk.start, + (char *) s_mg_flash_picosdk.start + s_mg_flash_picosdk.size / 2, + s_mg_flash_picosdk.size / 2, s_mg_flash_picosdk.secsz); + } + return false; +} +#endif + #ifdef MG_ENABLE_LINES #line 1 "src/ota_stm32f.c" #endif diff --git a/mongoose.h b/mongoose.h index 3e86e15d..19709fa6 100644 --- a/mongoose.h +++ b/mongoose.h @@ -255,6 +255,14 @@ static inline int mg_mkdir(const char *path, mode_t mode) { #include int mkdir(const char *, mode_t); + +#if MG_OTA == MG_OTA_PICOSDK +#include +#if PICO_RP2040 +#include +#endif +#endif + #endif @@ -2650,6 +2658,7 @@ void mg_rpc_list(struct mg_rpc_req *r); #define MG_OTA_MCXN 310 // MCXN947 #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 diff --git a/src/arch_rp2040.h b/src/arch_rp2040.h index ace069bf..e9c1eee3 100644 --- a/src/arch_rp2040.h +++ b/src/arch_rp2040.h @@ -12,4 +12,12 @@ #include int mkdir(const char *, mode_t); + +#if MG_OTA == MG_OTA_PICOSDK +#include +#if PICO_RP2040 +#include +#endif +#endif + #endif diff --git a/src/ota.h b/src/ota.h index a5a837f6..116ef9d6 100644 --- a/src/ota.h +++ b/src/ota.h @@ -16,6 +16,7 @@ #define MG_OTA_MCXN 310 // MCXN947 #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 diff --git a/src/ota_picosdk.c b/src/ota_picosdk.c new file mode 100644 index 00000000..62528e25 --- /dev/null +++ b/src/ota_picosdk.c @@ -0,0 +1,170 @@ +#include "flash.h" +#include "log.h" +#include "ota.h" + +#if MG_OTA == MG_OTA_PICOSDK + +// Both RP2040 and RP2350 have no flash, low-level flash access support in +// bootrom, and high-level support in Pico-SDK (2.0+ for the RP2350) +// - The RP2350 in RISC-V mode is not yet (fully) supported (nor tested) + +static bool mg_picosdk_write(void *, const void *, size_t); +static bool mg_picosdk_swap(void); + +static struct mg_flash s_mg_flash_picosdk = { + (void *) 0x10000000, // Start, not used here; functions handle offset +#ifdef PICO_FLASH_SIZE_BYTES + PICO_FLASH_SIZE_BYTES, // Size, from board definitions +#else + 0x200000, // Size, guess... is 2M enough ? +#endif + FLASH_SECTOR_SIZE, // Sector size, from hardware_flash + FLASH_PAGE_SIZE, // Align, from hardware_flash + mg_picosdk_write, mg_picosdk_swap, +}; + +#define MG_MODULO2(x, m) ((x) & ((m) -1)) + +static bool __no_inline_not_in_flash_func(flash_sector_start)( + volatile uint32_t *dst) { + char *base = (char *) s_mg_flash_picosdk.start, + *end = base + s_mg_flash_picosdk.size; + volatile char *p = (char *) dst; + return p >= base && p < end && + MG_MODULO2(p - base, s_mg_flash_picosdk.secsz) == 0; +} + +static bool __no_inline_not_in_flash_func(flash_erase)(void *addr) { + if (flash_sector_start(addr) == false) { + MG_ERROR(("%p is not on a sector boundary", addr)); + return false; + } + void *dst = (void *) ((char *) addr - (char *) s_mg_flash_picosdk.start); + flash_range_erase((uint32_t) dst, s_mg_flash_picosdk.secsz); + MG_DEBUG(("Sector starting at %p erasure", addr)); + return true; +} + +static bool __no_inline_not_in_flash_func(mg_picosdk_swap)(void) { + // TODO(): RP2350 might have some A/B functionality (DS 5.1) + return true; +} + +static bool s_flash_irq_disabled; + +static bool __no_inline_not_in_flash_func(mg_picosdk_write)(void *addr, + const void *buf, + size_t len) { + if ((len % s_mg_flash_picosdk.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_picosdk.align)); + return false; + } + if ((((size_t) addr - (size_t) s_mg_flash_picosdk.start) % + s_mg_flash_picosdk.align) != 0) { + MG_ERROR(("%p is not on a page boundary", addr)); + return false; + } + + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); + +#ifndef __riscv + MG_ARM_DISABLE_IRQ(); +#else + asm volatile("csrrc zero, mstatus, %0" : : "i"(1 << 3) : "memory"); +#endif + while (src < end) { + uint32_t dst_ofs = (uint32_t) dst - (uint32_t) s_mg_flash_picosdk.start; + if (flash_sector_start(dst) && flash_erase(dst) == false) break; + // flash_range_program() runs in RAM and handles writing up to + // FLASH_PAGE_SIZE bytes. Source must not be in flash + flash_range_program((uint32_t) dst_ofs, (uint8_t *) src, + s_mg_flash_picosdk.align); + src = (uint32_t *) ((char *) src + s_mg_flash_picosdk.align); + dst = (uint32_t *) ((char *) dst + s_mg_flash_picosdk.align); + } + if (!s_flash_irq_disabled) { +#ifndef __riscv + MG_ARM_ENABLE_IRQ(); +#else + asm volatile("csrrs mstatus, %0" : : "i"(1 << 3) : "memory"); +#endif + } + MG_DEBUG(("Flash write %lu bytes @ %p.", len, dst)); + return true; +} + +// just overwrite instead of swap +static void __no_inline_not_in_flash_func(single_bank_swap)(char *p1, char *p2, + size_t s, + size_t ss) { + char *tmp = malloc(ss); + if (tmp == NULL) return; +#if PICO_RP2040 + uint32_t xip[256 / sizeof(uint32_t)]; + void *dst = (void *) ((char *) p1 - (char *) s_mg_flash_picosdk.start); + size_t count = MG_ROUND_UP(s, ss); + // use SDK function calls to get BootROM function pointers + rom_connect_internal_flash_fn connect = (rom_connect_internal_flash_fn) rom_func_lookup_inline(ROM_FUNC_CONNECT_INTERNAL_FLASH); + rom_flash_exit_xip_fn xit = (rom_flash_exit_xip_fn) rom_func_lookup_inline(ROM_FUNC_FLASH_EXIT_XIP); + rom_flash_range_program_fn program = (rom_flash_range_program_fn) rom_func_lookup_inline(ROM_FUNC_FLASH_RANGE_PROGRAM); + rom_flash_flush_cache_fn flush = (rom_flash_flush_cache_fn) rom_func_lookup_inline(ROM_FUNC_FLASH_FLUSH_CACHE); + // no stdlib calls here. + MG_ARM_DISABLE_IRQ(); + // 2nd bootloader (XIP) is in flash, SDK functions copy it to RAM on entry + for (size_t i = 0; i < 256 / sizeof(uint32_t); i++) + xip[i] = ((uint32_t *) (s_mg_flash_picosdk.start))[i]; + flash_range_erase((uint32_t) dst, count); + // flash has been erased, no XIP to copy. Only BootROM calls possible + for (uint32_t ofs = 0; ofs < s; ofs += ss) { + for (size_t i = 0; i < ss; i++) tmp[i] = p2[ofs + i]; + __compiler_memory_barrier(); + connect(); + xit(); + program((uint32_t) dst + ofs, tmp, ss); + flush(); + ((void (*)(void))((intptr_t) xip + 1))(); // enter XIP again + } + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; // AIRCR = SYSRESETREQ +#else + // RP2350 has bootram and copies second bootloader there, SDK uses that copy, + // It might also be able to take advantage of partition swapping + for (size_t ofs = 0; ofs < s; ofs += ss) { + for (size_t i = 0; i < ss; i++) tmp[i] = p2[ofs + i]; + mg_picosdk_write(p1 + ofs, tmp, ss); + } +#ifndef __riscv + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; // AIRCR = SYSRESETREQ +#else + // TODO(): find a way to do a system reset, like block resets and watchdog +#endif +#endif +} + +bool mg_ota_begin(size_t new_firmware_size) { + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_picosdk); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_picosdk); +} + +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_picosdk)) { + // Swap partitions. Pray power does not go away + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_picosdk.size, + s_mg_flash_picosdk.size / s_mg_flash_picosdk.secsz)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + s_flash_irq_disabled = true; + // Runs in RAM, will reset when finished or return on failure + single_bank_swap( + (char *) s_mg_flash_picosdk.start, + (char *) s_mg_flash_picosdk.start + s_mg_flash_picosdk.size / 2, + s_mg_flash_picosdk.size / 2, s_mg_flash_picosdk.secsz); + } + return false; +} +#endif