mongoose/src/ota_flash.c
2023-12-15 11:32:46 +00:00

200 lines
6.7 KiB
C

#include "arch.h"
#include "device.h"
#include "log.h"
#include "ota.h"
// This OTA implementation uses the internal flash API outlined in device.h
// It splits flash into 2 equal partitions, and stores OTA status in the
// last sector of the partition.
#if MG_OTA == MG_OTA_FLASH
#define MG_OTADATA_KEY 0xb07afed0
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
struct mg_otadata {
uint32_t crc32, size, timestamp, status;
};
bool mg_ota_begin(size_t new_firmware_size) {
bool ok = false;
if (s_size) {
MG_ERROR(("OTA already in progress. Call mg_ota_end()"));
} else {
size_t half = mg_flash_size() / 2, max = half - mg_flash_sector_size();
s_crc32 = 0;
s_addr = (char *) mg_flash_start() + half;
MG_DEBUG(("Firmware %lu bytes, max %lu", new_firmware_size, max));
if (new_firmware_size < max) {
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, max));
}
}
return ok;
}
bool mg_ota_write(const void *buf, size_t len) {
bool ok = false;
if (s_size == 0) {
MG_ERROR(("OTA is not started, call mg_ota_begin()"));
} else {
size_t align = mg_flash_write_align();
size_t len_aligned_down = MG_ROUND_DOWN(len, align);
if (len_aligned_down) ok = mg_flash_write(s_addr, buf, len_aligned_down);
if (len_aligned_down < len) {
size_t left = len - len_aligned_down;
char tmp[align];
memset(tmp, 0xff, sizeof(tmp));
memcpy(tmp, (char *) buf + len_aligned_down, left);
ok = mg_flash_write(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;
}
MG_IRAM static uint32_t mg_fwkey(int fw) {
uint32_t key = MG_OTADATA_KEY + fw;
int bank = mg_flash_bank();
if (bank == 2 && fw == MG_FIRMWARE_PREVIOUS) key--;
if (bank == 2 && fw == MG_FIRMWARE_CURRENT) key++;
return key;
}
bool mg_ota_end(void) {
char *base = (char *) mg_flash_start() + mg_flash_size() / 2;
bool ok = false;
if (s_size) {
size_t size = s_addr - base;
uint32_t crc32 = mg_crc32(0, base, s_size);
if (size == s_size && crc32 == s_crc32) {
uint32_t now = (uint32_t) (mg_now() / 1000);
struct mg_otadata od = {crc32, size, now, MG_OTA_FIRST_BOOT};
uint32_t key = mg_fwkey(MG_FIRMWARE_PREVIOUS);
ok = mg_flash_save(NULL, key, &od, sizeof(od));
}
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();
}
MG_INFO(("Finishing OTA: %s", ok ? "ok" : "fail"));
return ok;
}
MG_IRAM static struct mg_otadata mg_otadata(int fw) {
uint32_t key = mg_fwkey(fw);
struct mg_otadata od = {};
MG_INFO(("Loading %s OTA data", fw == MG_FIRMWARE_CURRENT ? "curr" : "prev"));
mg_flash_load(NULL, key, &od, sizeof(od));
// MG_DEBUG(("Loaded OTA data. fw %d, bank %d, key %p", fw, bank, key));
// mg_hexdump(&od, sizeof(od));
return od;
}
int mg_ota_status(int fw) {
struct mg_otadata od = mg_otadata(fw);
return od.status;
}
uint32_t mg_ota_crc32(int fw) {
struct mg_otadata od = mg_otadata(fw);
return od.crc32;
}
uint32_t mg_ota_timestamp(int fw) {
struct mg_otadata od = mg_otadata(fw);
return od.timestamp;
}
size_t mg_ota_size(int fw) {
struct mg_otadata od = mg_otadata(fw);
return od.size;
}
MG_IRAM bool mg_ota_commit(void) {
bool ok = true;
struct mg_otadata od = mg_otadata(MG_FIRMWARE_CURRENT);
if (od.status != MG_OTA_COMMITTED) {
od.status = MG_OTA_COMMITTED;
MG_INFO(("Committing current firmware, OD size %lu", sizeof(od)));
ok = mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_CURRENT), &od, sizeof(od));
}
return ok;
}
bool mg_ota_rollback(void) {
MG_DEBUG(("Rolling firmware back"));
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();
}
}
MG_IRAM void mg_ota_boot(void) {
MG_INFO(("Booting. Flash bank: %d", mg_flash_bank()));
struct mg_otadata curr = mg_otadata(MG_FIRMWARE_CURRENT);
struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS);
if (curr.status == MG_OTA_FIRST_BOOT) {
if (prev.status == MG_OTA_UNAVAILABLE) {
MG_INFO(("Setting previous firmware state to committed"));
prev.status = MG_OTA_COMMITTED;
mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_PREVIOUS), &prev, sizeof(prev));
}
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));
} 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();
char *partition1 = mg_flash_start();
char *partition2 = mg_flash_start() + fs / 2;
size_t ofs, max = fs / 2 - ss; // Set swap size to the whole partition
if (curr.status != MG_OTA_UNAVAILABLE &&
prev.status != MG_OTA_UNAVAILABLE) {
// We know exact sizes of both firmwares.
// Shrink swap size to the MAX(firmware1, firmware2)
size_t sz = curr.size > prev.size ? curr.size : prev.size;
if (sz > 0 && sz < max) max = sz;
}
// MG_OTA_FIRST_BOOT -> MG_OTA_UNCOMMITTED
prev.status = MG_OTA_UNCOMMITTED;
mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_CURRENT, &prev,
sizeof(prev));
mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_PREVIOUS, &curr,
sizeof(curr));
MG_INFO(("Swapping partitions, size %u (%u sectors)", max, max / ss));
MG_INFO(("Do NOT power off..."));
mg_log_level = MG_LL_NONE;
// We use the last sector of partition2 for OTA data/config storage
// Therefore we can use last sector of partition1 for swapping
char *tmpsector = partition1 + fs / 2 - ss; // Last sector of partition1
(void) tmpsector;
for (ofs = 0; ofs < max; ofs += ss) {
// mg_flash_erase(tmpsector);
mg_flash_write(tmpsector, partition1 + ofs, ss);
// mg_flash_erase(partition1 + ofs);
mg_flash_write(partition1 + ofs, partition2 + ofs, ss);
// mg_flash_erase(partition2 + ofs);
mg_flash_write(partition2 + ofs, tmpsector, ss);
}
mg_device_reset();
}
}
#endif