mongoose/src/device_flash.c
2024-01-19 02:47:23 -05:00

173 lines
6.9 KiB
C

#include "device.h"
#if MG_DEVICE == MG_DEVICE_STM32H7 || MG_DEVICE == MG_DEVICE_STM32H5 || \
MG_DEVICE == MG_DEVICE_RT1020 || MG_DEVICE == MG_DEVICE_RT1060
// 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.
//
// Objects in the flash sector are stored sequentially:
// | 32-bit size | 32-bit KEY | ..data.. | ..pad.. | 32-bit size | ......
//
// In order to get to the next object, read its size, then align up.
// Traverse the list of saved objects
size_t mg_flash_next(char *p, char *end, uint32_t *key, size_t *size) {
size_t aligned_size = 0, align = mg_flash_write_align(), left = end - p;
uint32_t *p32 = (uint32_t *) p, min_size = sizeof(uint32_t) * 2;
if (p32[0] != 0xffffffff && left > MG_ROUND_UP(min_size, align)) {
if (size) *size = (size_t) p32[0];
if (key) *key = p32[1];
aligned_size = MG_ROUND_UP(p32[0] + sizeof(uint32_t) * 2, align);
if (left < aligned_size) aligned_size = 0; // Out of bounds, fail
}
return aligned_size;
}
// Return the last sector of Bank 2
static char *flash_last_sector(void) {
size_t ss = mg_flash_sector_size(), size = mg_flash_size();
char *base = (char *) mg_flash_start(), *last = base + size - ss;
if (mg_flash_bank() == 2) last -= size / 2;
return last;
}
// Find a saved object with a given key
bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len) {
char *base = (char *) mg_flash_start(), *s = (char *) sector, *res = NULL;
size_t ss = mg_flash_sector_size(), ofs = 0, n, sz;
bool ok = false;
if (s == NULL) s = flash_last_sector();
if (s < base || s >= base + mg_flash_size()) {
MG_ERROR(("%p is outsize of flash", sector));
} else if (((s - base) % ss) != 0) {
MG_ERROR(("%p is not a sector boundary", sector));
} else {
uint32_t k, scanned = 0;
while ((n = mg_flash_next(s + ofs, s + ss, &k, &sz)) > 0) {
// MG_DEBUG((" > obj %lu, ofs %lu, key %x/%x", scanned, ofs, k, key));
// mg_hexdump(s + ofs, n);
if (k == key && sz == len) {
res = s + ofs + sizeof(uint32_t) * 2;
memcpy(buf, res, len); // Copy object
ok = true; // Keep scanning for the newer versions of it
}
ofs += n, scanned++;
}
MG_DEBUG(("Scanned %u objects, key %x is @ %p", scanned, key, res));
}
return ok;
}
// For all saved objects in the sector, delete old versions of objects
static void mg_flash_sector_cleanup(char *sector) {
// Buffer all saved objects into an IO buffer (backed by RAM)
// erase sector, and re-save them.
struct mg_iobuf io = {0, 0, 0, 2048};
size_t ss = mg_flash_sector_size();
size_t n, size, size2, ofs = 0, hs = sizeof(uint32_t) * 2;
uint32_t key;
// Traverse all objects
MG_DEBUG(("Cleaning up sector %p", sector));
while ((n = mg_flash_next(sector + ofs, sector + ss, &key, &size)) > 0) {
// Delete an old copy of this object in the cache
for (size_t o = 0; o < io.len; o += size2 + hs) {
uint32_t k = *(uint32_t *) (io.buf + o + sizeof(uint32_t));
size2 = *(uint32_t *) (io.buf + o);
if (k == key) {
mg_iobuf_del(&io, o, size2 + hs);
break;
}
}
// And add the new copy
mg_iobuf_add(&io, io.len, sector + ofs, size + hs);
ofs += n;
}
// All objects are cached in RAM now
if (mg_flash_erase(sector)) { // Erase sector. If successful,
for (ofs = 0; ofs < io.len; ofs += size + hs) { // Traverse cached objects
size = *(uint32_t *) (io.buf + ofs);
key = *(uint32_t *) (io.buf + ofs + sizeof(uint32_t));
mg_flash_save(sector, key, io.buf + ofs + hs, size); // Save to flash
}
}
mg_iobuf_free(&io);
}
// Save an object with a given key - append to the end of an object list
bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len) {
char *base = (char *) mg_flash_start(), *s = (char *) sector;
size_t ss = mg_flash_sector_size(), ofs = 0, n;
bool ok = false;
if (s == NULL) s = flash_last_sector();
if (s < base || s >= base + mg_flash_size()) {
MG_ERROR(("%p is outsize of flash", sector));
} else if (((s - base) % ss) != 0) {
MG_ERROR(("%p is not a sector boundary", sector));
} else {
char ab[mg_flash_write_align()]; // Aligned write block
uint32_t hdr[2] = {(uint32_t) len, key};
size_t needed = sizeof(hdr) + len;
size_t needed_aligned = MG_ROUND_UP(needed, sizeof(ab));
while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0) ofs += n;
// If there is not enough space left, cleanup sector and re-eval ofs
if (ofs + needed_aligned >= ss) {
mg_flash_sector_cleanup(s);
ofs = 0;
while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0) ofs += n;
}
if (ofs + needed_aligned <= ss) {
// Enough space to save this object
if (sizeof(ab) < sizeof(hdr)) {
// Flash write granularity is 32 bit or less, write with no buffering
ok = mg_flash_write(s + ofs, hdr, sizeof(hdr));
if (ok) mg_flash_write(s + ofs + sizeof(hdr), buf, len);
} else {
// Flash granularity is sizeof(hdr) or more. We need to save in
// 3 chunks: initial block, bulk, rest. This is because we have
// two memory chunks to write: hdr and buf, on aligned boundaries.
n = sizeof(ab) - sizeof(hdr); // Initial chunk that we write
if (n > len) n = len; // is
memset(ab, 0xff, sizeof(ab)); // initialized to all-one
memcpy(ab, hdr, sizeof(hdr)); // contains the header (key + size)
memcpy(ab + sizeof(hdr), buf, n); // and an initial part of buf
MG_INFO(("saving initial block of %lu", sizeof(ab)));
ok = mg_flash_write(s + ofs, ab, sizeof(ab));
if (ok && len > n) {
size_t n2 = MG_ROUND_DOWN(len - n, sizeof(ab));
if (n2 > 0) {
MG_INFO(("saving bulk, %lu", n2));
ok = mg_flash_write(s + ofs + sizeof(ab), (char *) buf + n, n2);
}
if (ok && len > n) {
size_t n3 = len - n - n2;
if (n3 > sizeof(ab)) n3 = sizeof(ab);
memset(ab, 0xff, sizeof(ab));
memcpy(ab, (char *) buf + n + n2, n3);
MG_INFO(("saving rest, %lu", n3));
ok = mg_flash_write(s + ofs + sizeof(ab) + n2, ab, sizeof(ab));
}
}
}
MG_DEBUG(("Saved %lu/%lu bytes @ %p, key %x: %d", len, needed_aligned,
s + ofs, key, ok));
MG_DEBUG(("Sector space left: %lu bytes", ss - ofs - needed_aligned));
} else {
MG_ERROR(("Sector is full"));
}
}
return ok;
}
#else
bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len) {
(void) sector, (void) key, (void) buf, (void) len;
return false;
}
bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len) {
(void) sector, (void) key, (void) buf, (void) len;
return false;
}
#endif