Files
mongoose/src/ota_mcxn.c
T
2026-05-27 13:18:01 +01:00

211 lines
7.0 KiB
C

#include "flash.h"
#include "log.h"
#include "ota.h"
#if MG_OTA == MG_OTA_MCXN
// - Flash phrase: 16 bytes; smallest portion programmed in one operation.
// - Flash page: 128 bytes; largest portion programmed in one operation.
// - Flash sector: 8 KB; smallest portion that can be erased in one operation.
// - Flash API mg_flash_driver->program: "start" and "len" must be page-size
// aligned; to use 'phrase', FMU register access is needed. Using ROM
static bool mg_mcxn_write(void *, const void *, size_t);
static bool mg_mcxn_swap(void);
static struct mg_flash s_mg_flash_mcxn = {
(void *) 0, // Start, filled at init
0, // Size, filled at init
0, // Sector size, filled at init
0, // Align, filled at init
mg_mcxn_write,
mg_mcxn_swap,
};
struct mg_flash_config {
uint32_t addr;
uint32_t size;
uint32_t blocks;
uint32_t page_size;
uint32_t sector_size;
uint32_t ffr[6];
uint32_t reserved0[5];
uint32_t *bootctx;
bool useahb;
};
struct mg_flash_driver_interface {
uint32_t version;
uint32_t (*init)(struct mg_flash_config *);
uint32_t (*erase)(struct mg_flash_config *, uint32_t start, uint32_t len,
uint32_t key);
uint32_t (*program)(struct mg_flash_config *, uint32_t start, uint8_t *src,
uint32_t len);
uint32_t (*verify_erase)(struct mg_flash_config *, uint32_t start,
uint32_t len);
uint32_t (*verify_program)(struct mg_flash_config *, uint32_t start,
uint32_t len, const uint8_t *expected,
uint32_t *addr, uint32_t *failed);
uint32_t reserved1[12];
uint32_t (*read)(struct mg_flash_config *, uint32_t start, uint8_t *dest,
uint32_t len);
uint32_t reserved2[4];
uint32_t (*deinit)(struct mg_flash_config *);
};
#define mg_flash_driver \
((struct mg_flash_driver_interface *) (*((uint32_t *) 0x1303fc00 + 4)))
#define MG_MCXN_FLASK_KEY (('k' << 24) | ('e' << 16) | ('f' << 8) | 'l')
MG_IRAM static bool flash_sector_start(volatile uint32_t *dst) {
char *base = (char *) s_mg_flash_mcxn.start,
*end = base + s_mg_flash_mcxn.size;
volatile char *p = (char *) dst;
return p >= base && p < end && ((p - base) % s_mg_flash_mcxn.secsz) == 0;
}
MG_IRAM static bool flash_erase(struct mg_flash_config *config, void *addr) {
if (flash_sector_start(addr) == false) {
MG_ERROR(("%p is not on a sector boundary", addr));
return false;
}
uint32_t dst =
(uint32_t) addr - (uint32_t) s_mg_flash_mcxn.start; // future-proof
uint32_t status = mg_flash_driver->erase(config, dst, s_mg_flash_mcxn.secsz,
MG_MCXN_FLASK_KEY);
bool ok = (status == 0);
if (!ok) MG_ERROR(("Flash write error: %lu", status));
MG_DEBUG(("Sector starting at %p erasure: %s", addr, ok ? "ok" : "fail"));
return ok;
}
#if 0
// read-while-write, no need to disable IRQs for standalone usage
MG_IRAM static bool mg_mcxn_erase(void *addr) {
uint32_t status;
struct mg_flash_config config;
if ((status = mg_flash_driver->init(&config)) != 0) {
MG_ERROR(("Flash driver init error: %lu", status));
return false;
}
bool ok = flash_erase(&config, addr);
mg_flash_driver->deinit(&config);
return ok;
}
#endif
MG_IRAM static bool mg_mcxn_swap(void) {
// TODO(): no devices so far
return true;
}
static bool s_flash_irq_disabled;
MG_IRAM static bool mg_mcxn_write(void *addr, const void *buf, size_t len) {
bool ok = false;
uint32_t status;
struct mg_flash_config config;
if ((status = mg_flash_driver->init(&config)) != 0) {
MG_ERROR(("Flash driver init error: %lu", status));
return false;
}
if ((len % s_mg_flash_mcxn.align) != 0) {
MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_mcxn.align));
goto fwxit;
}
if ((((size_t) addr - (size_t) s_mg_flash_mcxn.start) %
s_mg_flash_mcxn.align) != 0) {
MG_ERROR(("%p is not on a page boundary", addr));
goto fwxit;
}
uint32_t *dst = (uint32_t *) addr;
uint32_t *src = (uint32_t *) buf;
uint32_t *end = (uint32_t *) ((char *) buf + len);
ok = true;
MG_ARM_DISABLE_IRQ();
while (ok && src < end) {
if (flash_sector_start(dst) && flash_erase(&config, dst) == false) {
ok = false;
break;
}
uint32_t dst_ofs = (uint32_t) dst - (uint32_t) s_mg_flash_mcxn.start;
// assume source is in RAM or in a different bank or read-while-write
status = mg_flash_driver->program(&config, dst_ofs, (uint8_t *) src,
s_mg_flash_mcxn.align);
src = (uint32_t *) ((char *) src + s_mg_flash_mcxn.align);
dst = (uint32_t *) ((char *) dst + s_mg_flash_mcxn.align);
if (status != 0) {
MG_ERROR(("Flash write error: %lu", status));
ok = false;
}
}
if (!s_flash_irq_disabled) MG_ARM_ENABLE_IRQ();
MG_DEBUG(("Flash write %lu bytes @ %p: %s.", len, dst, ok ? "ok" : "fail"));
fwxit:
mg_flash_driver->deinit(&config);
return ok;
}
// try to swap (honor dual image), otherwise just overwrite
MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss) {
char *tmp = mg_calloc(1, ss);
// no stdlib calls here
for (size_t ofs = 0; ofs < s; ofs += ss) {
if (tmp != NULL)
for (size_t i = 0; i < ss; i++) tmp[i] = p1[ofs + i];
mg_mcxn_write(p1 + ofs, p2 + ofs, ss);
if (tmp != NULL) mg_mcxn_write(p2 + ofs, tmp, ss);
}
*(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
}
bool mg_ota_begin(size_t new_firmware_size) {
uint32_t status;
struct mg_flash_config config;
if ((status = mg_flash_driver->init(&config)) != 0) {
MG_ERROR(("Flash driver init error: %lu", status));
return false;
}
s_mg_flash_mcxn.start = (void *) config.addr;
s_mg_flash_mcxn.size = config.size;
s_mg_flash_mcxn.secsz = config.sector_size;
s_mg_flash_mcxn.align = config.page_size;
mg_flash_driver->deinit(&config);
MG_DEBUG(
("%lu-byte flash @%p, using %lu-byte sectors with %lu-byte-aligned pages",
s_mg_flash_mcxn.size, s_mg_flash_mcxn.start, s_mg_flash_mcxn.secsz,
s_mg_flash_mcxn.align));
return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_mcxn);
}
bool mg_ota_write(const void *buf, size_t len) {
return mg_ota_flash_write(buf, len, &s_mg_flash_mcxn);
}
bool mg_ota_end(void) {
if (mg_ota_flash_end(&s_mg_flash_mcxn)) {
if (0) { // is_dualbank()
// TODO(): no devices so far
*(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
} else {
// Swap partitions. Pray power does not go away
MG_INFO(("Swapping partitions, size %u (%u sectors)",
s_mg_flash_mcxn.size,
s_mg_flash_mcxn.size / s_mg_flash_mcxn.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
single_bank_swap(
(char *) s_mg_flash_mcxn.start,
(char *) s_mg_flash_mcxn.start + s_mg_flash_mcxn.size / 2,
s_mg_flash_mcxn.size / 2, s_mg_flash_mcxn.secsz);
}
}
return false;
}
struct mg_flash *mg_flash = &s_mg_flash_mcxn;
#endif