mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-01 05:52:32 +07:00
211 lines
7.0 KiB
C
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
|