Skip to content

Commit

Permalink
Add OTA for MCXN947
Browse files Browse the repository at this point in the history
  • Loading branch information
scaprile committed Oct 18, 2024
1 parent 8eabf43 commit de404df
Show file tree
Hide file tree
Showing 10 changed files with 438 additions and 34 deletions.
235 changes: 219 additions & 16 deletions mongoose.c
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,8 @@ bool mg_flash_erase(void *location) {
(void) location;
return false;
}
bool mg_flash_swap_bank(void) {
bool mg_flash_swap_bank(size_t size) {
(void) size;
return true;
}
bool mg_flash_write(void *addr, const void *buf, size_t len) {
Expand All @@ -239,8 +240,7 @@ void mg_device_reset(void) {
#endif


#if MG_DEVICE == MG_DEVICE_STM32H7 || MG_DEVICE == MG_DEVICE_STM32H5 || \
MG_DEVICE == MG_DEVICE_RT1020 || MG_DEVICE == MG_DEVICE_RT1060
#if MG_DEVICE != MG_DEVICE_NONE && MG_DEVICE != MG_DEVICE_CUSTOM
// Flash can be written only if it is erased. Erased flash is 0xff (all bits 1)
// Writes must be mg_flash_write_align() - aligned. Thus if we want to save an
// object, we pad it at the end for alignment.
Expand Down Expand Up @@ -664,7 +664,8 @@ MG_IRAM bool mg_flash_erase(void *addr) {
return ok;
}

MG_IRAM bool mg_flash_swap_bank(void) {
MG_IRAM bool mg_flash_swap_bank(size_t size) {
(void) size;
return true;
}

Expand Down Expand Up @@ -711,6 +712,7 @@ MG_IRAM bool mg_flash_write(void *addr, const void *buf, size_t len) {

while (ok && src < end) {
if (flash_page_start(dst) && mg_flash_erase(dst) == false) {
ok = false;
break;
}
uint32_t status;
Expand Down Expand Up @@ -753,6 +755,180 @@ MG_IRAM void mg_device_reset(void) {

#endif

#ifdef MG_ENABLE_LINES
#line 1 "src/device_mcxn.c"
#endif



#if MG_DEVICE == MG_DEVICE_MCXN

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')

static bool s_flash_irq_disabled;

// Flash phrase: 16 bytes; smallest portion that can be programmed in one operation.
// Flash page: 128 bytes; largest portion that can be 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
MG_IRAM void *mg_flash_start(void) {
return (void *) 0;
}
MG_IRAM size_t mg_flash_size(void) {
return 2 * 1024 * 1024; // 2MB
}
MG_IRAM size_t mg_flash_sector_size(void) {
return 8 * 1024; // 8KB
}
MG_IRAM size_t mg_flash_write_align(void) {
return 128; // use 128 instead of 16, using ROM API instead of FMU
}
MG_IRAM int mg_flash_bank(void) {
return 0;
}

MG_IRAM static bool flash_sector_start(volatile uint32_t *dst) {
char *base = (char *) mg_flash_start(), *end = base + mg_flash_size();
volatile char *p = (char *) dst;
return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 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) mg_flash_start(); // future-proof
uint32_t status = mg_flash_driver->erase(config, dst, mg_flash_sector_size(), 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;
}

MG_IRAM bool mg_flash_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;
}

MG_IRAM bool mg_flash_swap_bank(size_t size) {
// Swap partitions. Pray power does not go away
size_t fs = mg_flash_size(), ss = mg_flash_sector_size();
char *partition1 = mg_flash_start();
char *partition2 = mg_flash_start() + fs / 2;
size_t ofs, max = size == 0 ? fs / 2 - ss : size;
char *tmpsector = malloc(ss);
bool ramtmp = (tmpsector != NULL);
if (!ramtmp) {
MG_ERROR(("OOM, swapping in flash"));
// We use the last sector of partition2 for OTA data/config storage
// Therefore we can use last sector of partition1 for swapping
tmpsector = partition1 + fs / 2 - ss; // Last sector of partition1
}
MG_INFO(("Swapping partitions, size %u (%u sectors)", max, max / ss));
MG_INFO(("Do NOT power off..."));
mg_log_level = MG_LL_NONE;
s_flash_irq_disabled = true;
for (ofs = 0; ofs < max; ofs += ss) { // no stdlib calls here
if (ramtmp) {
for (size_t i = 0; i < ss; i++) tmpsector[i] = partition1[ofs + i];
} else {
mg_flash_write(tmpsector, partition1 + ofs, ss);
}
mg_flash_write(partition1 + ofs, partition2 + ofs, ss);
mg_flash_write(partition2 + ofs, tmpsector, ss);
}
mg_device_reset();
return true;
}

MG_IRAM bool mg_flash_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 % mg_flash_write_align()) != 0) {
MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align()));
goto fwxit;
}
if (((size_t)addr - (size_t)mg_flash_start()) % mg_flash_write_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_DEBUG(("Writing flash @ %p, %lu bytes", addr, len));
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) mg_flash_start();
// assume source is in RAM or in a different bank or read-while-write
// TODO(scaprile): maybe optimize calls doing one per sector ?
status = mg_flash_driver->program(&config, dst_ofs, (uint8_t *)src, mg_flash_write_align());
src = (uint32_t *) ((char *) src + mg_flash_write_align());
dst = (uint32_t *) ((char *) dst + mg_flash_write_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;
}

MG_IRAM void mg_device_reset(void) {
MG_DEBUG(("Resetting device..."));
*(volatile unsigned long *) 0xe000ed0c = 0x5fa0004;
}

#endif

#ifdef MG_ENABLE_LINES
#line 1 "src/device_stm32h5.c"
#endif
Expand Down Expand Up @@ -852,7 +1028,7 @@ bool mg_flash_erase(void *location) {
return ok;
}

bool mg_flash_swap_bank(void) {
bool mg_flash_swap_bank(size_t size) {
uint32_t desired = flash_bank_is_swapped() ? 0 : MG_BIT(31);
flash_unlock();
flash_clear_err();
Expand All @@ -861,6 +1037,7 @@ bool mg_flash_swap_bank(void) {
// printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG);
MG_REG(FLASH_OPTCR) |= MG_BIT(1); // OPTSTART
while ((MG_REG(FLASH_OPTSR_CUR) & MG_BIT(31)) != desired) (void) 0;
(void) size;
return true;
}

Expand All @@ -879,7 +1056,10 @@ 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_flash_erase(dst) == false) {
ok = false;
break;
}
*(volatile uint32_t *) dst++ = *src++;
flash_wait();
if (flash_is_err()) ok = false;
Expand Down Expand Up @@ -1007,7 +1187,7 @@ MG_IRAM bool mg_flash_erase(void *addr) {
return ok;
}

MG_IRAM bool mg_flash_swap_bank(void) {
MG_IRAM bool mg_flash_swap_bank(size_t size) {
if (mg_flash_bank() == 0) return true;
uint32_t bank = FLASH_BASE1;
uint32_t desired = flash_bank_is_swapped(bank) ? 0 : MG_BIT(31);
Expand All @@ -1018,6 +1198,7 @@ MG_IRAM bool mg_flash_swap_bank(void) {
// 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;
(void) size;
return true;
}

Expand All @@ -1038,7 +1219,10 @@ MG_IRAM bool mg_flash_write(void *addr, const void *buf, size_t len) {
MG_DEBUG(("Writing flash @ %p, %lu bytes", addr, len));
MG_ARM_DISABLE_IRQ();
while (ok && src < end) {
if (flash_page_start(dst) && mg_flash_erase(dst) == false) break;
if (flash_page_start(dst) && mg_flash_erase(dst) == false) {
ok = false;
break;
}
*(volatile uint32_t *) dst++ = *src++;
flash_wait(bank);
if (flash_is_err(bank)) ok = false;
Expand Down Expand Up @@ -6295,6 +6479,8 @@ MG_IRAM static uint32_t mg_fwkey(int fw) {
return key;
}

static size_t swap_size(void);

bool mg_ota_end(void) {
char *base = (char *) mg_flash_start() + mg_flash_size() / 2;
bool ok = false;
Expand All @@ -6310,7 +6496,14 @@ bool mg_ota_end(void) {
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 = mg_flash_swap_bank();
if (ok) {
size_t sz = 0;
if (mg_flash_bank() == 0) {
// No dual bank support.
sz = swap_size();
}
ok = mg_flash_swap_bank(sz);
}
}
MG_INFO(("Finishing OTA: %s", ok ? "ok" : "fail"));
return ok;
Expand All @@ -6326,6 +6519,17 @@ MG_IRAM static struct mg_otadata mg_otadata(int fw) {
return od;
}

static size_t swap_size(void) {
struct mg_otadata curr = mg_otadata(MG_FIRMWARE_CURRENT);
struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS);
size_t sz = 0;
if (curr.status != MG_OTA_UNAVAILABLE && prev.status != MG_OTA_UNAVAILABLE) {
// We know exact sizes of both firmwares, use MAX(firmware1, firmware2)
sz = (size_t) (curr.size > prev.size ? curr.size : prev.size);
}
return sz;
}

int mg_ota_status(int fw) {
struct mg_otadata od = mg_otadata(fw);
return od.status;
Expand Down Expand Up @@ -6356,15 +6560,12 @@ MG_IRAM bool mg_ota_commit(void) {

bool mg_ota_rollback(void) {
MG_DEBUG(("Rolling firmware back"));
size_t sz = 0;
if (mg_flash_bank() == 0) {
// No dual bank support. Mark previous firmware as FIRST_BOOT
struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS);
prev.status = MG_OTA_FIRST_BOOT;
return mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_PREVIOUS, &prev,
sizeof(prev));
} else {
return mg_flash_swap_bank();
// No dual bank support.
sz = swap_size();
}
return mg_flash_swap_bank(sz);
}

MG_IRAM void mg_ota_boot(void) {
Expand All @@ -6381,6 +6582,7 @@ MG_IRAM void mg_ota_boot(void) {
curr.status = MG_OTA_UNCOMMITTED;
MG_INFO(("First boot, setting status to UNCOMMITTED"));
mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_CURRENT), &curr, sizeof(curr));
#if 0
} else if (prev.status == MG_OTA_FIRST_BOOT && mg_flash_bank() == 0) {
// Swap paritions. Pray power does not disappear
size_t fs = mg_flash_size(), ss = mg_flash_sector_size();
Expand Down Expand Up @@ -6420,6 +6622,7 @@ MG_IRAM void mg_ota_boot(void) {
mg_flash_write(partition2 + ofs, tmpsector, ss);
}
mg_device_reset();
#endif
}
}
#endif
Expand Down
3 changes: 2 additions & 1 deletion mongoose.h
Original file line number Diff line number Diff line change
Expand Up @@ -2691,6 +2691,7 @@ MG_IRAM void mg_ota_boot(void); // Bootloader function
#define MG_DEVICE_U2A 200 // Renesas U2A16, U2A8, U2A6
#define MG_DEVICE_RT1020 300 // IMXRT1020
#define MG_DEVICE_RT1060 301 // IMXRT1060
#define MG_DEVICE_MCXN 310 // MCXN947
#define MG_DEVICE_CUSTOM 1000 // Custom implementation

#ifndef MG_DEVICE
Expand All @@ -2707,7 +2708,7 @@ int mg_flash_bank(void); // 0: not dual bank, 1: bank1, 2: bank2
// Write, erase, swap bank
bool mg_flash_write(void *addr, const void *buf, size_t len);
bool mg_flash_erase(void *sector);
bool mg_flash_swap_bank(void);
bool mg_flash_swap_bank(size_t size);

// Convenience functions to store data on a flash sector with wear levelling
// If `sector` is NULL, then the last sector of flash is used
Expand Down
3 changes: 2 additions & 1 deletion src/device.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#define MG_DEVICE_U2A 200 // Renesas U2A16, U2A8, U2A6
#define MG_DEVICE_RT1020 300 // IMXRT1020
#define MG_DEVICE_RT1060 301 // IMXRT1060
#define MG_DEVICE_MCXN 310 // MCXN947
#define MG_DEVICE_CUSTOM 1000 // Custom implementation

#ifndef MG_DEVICE
Expand All @@ -29,7 +30,7 @@ int mg_flash_bank(void); // 0: not dual bank, 1: bank1, 2: bank2
// Write, erase, swap bank
bool mg_flash_write(void *addr, const void *buf, size_t len);
bool mg_flash_erase(void *sector);
bool mg_flash_swap_bank(void);
bool mg_flash_swap_bank(size_t size);

// Convenience functions to store data on a flash sector with wear levelling
// If `sector` is NULL, then the last sector of flash is used
Expand Down
3 changes: 2 additions & 1 deletion src/device_dummy.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ bool mg_flash_erase(void *location) {
(void) location;
return false;
}
bool mg_flash_swap_bank(void) {
bool mg_flash_swap_bank(size_t size) {
(void) size;
return true;
}
bool mg_flash_write(void *addr, const void *buf, size_t len) {
Expand Down
Loading

0 comments on commit de404df

Please sign in to comment.