Files
espurna/code/espurna/storage_eeprom.cpp
Maxim Prokhorov 1a7f519bcc settings(internal): remove inline funcs & fixup crash handler
implement relevant funcs within the handler, as it is the main consumer
2025-06-21 00:50:28 +03:00

268 lines
6.7 KiB
C++

/*
EEPROM MODULE
*/
#include "espurna.h"
#include "storage_eeprom.h"
namespace {
bool _eeprom_commit = false;
size_t _eeprom_commit_count = 0;
bool _eeprom_last_commit_result = false;
bool _eeprom_ready = false;
bool _eepromCommitResult(bool value) {
// note that even an no-op commit is a 'success'
if (value) {
_eeprom_commit_count += 1;
}
_eeprom_last_commit_result = value;
return value;
}
} // namespace
StorageEEPROM_Rotate& eepromInstance() {
static StorageEEPROM_Rotate instance;
return instance;
}
StorageEEPROM_Rotate::StorageEEPROM_Rotate() :
EEPROM_Rotate()
{
#ifdef EEPROM_ROTATE_SECTORS
static_assert(EEPROM_ROTATE_SECTORS > 0, "");
_pool_size = EEPROM_ROTATE_SECTORS;
#else
// ESP8266 environment uses fixed addresses for globally accessible `FS` & `EEPROM`.
// By default, last() is `(FLASH_SIZE / SECTOR_SIZE) - 5` aka base EEPROM address.
//
// EEPROM_Rotate also tries to check whether the `_FS_end` allows for any sectors
// between it and `_EEPROM_start`. But, this is generally never true.
const auto last = this->last();
// Thus, override original behaviour in regards to size limits and auto-size detection
// `last()` returns the number of available sectors MINUS reserved by the SDK
// Prefer to ignore the embedded numbers and use generic `SIZE * 2`.
if (last > 4000) { // 16Mb
_pool_size = 32;
} else if (last > 2000) { // 8Mb
_pool_size = 16;
} else if (last > 1000) { // 4Mb
_pool_size = 8;
} else if (last > 500) { // 2Mb
_pool_size = 4;
} else if (last > 250) { // 1Mb
_pool_size = 2;
}
#endif
_offset = EepromRotateOffset;
as_rotate()->begin(EepromSize);
// With all other things equal, make sure this sector could be written to later
if (!_checkCRC()) {
fill(0xff);
writeReservedData();
_eepromCommitResult(as_base()->commit());
}
}
void StorageEEPROM_Rotate::fill(uint8_t value) {
auto* begin = &_data[0] + _offset + EepromRotateReservedSize;
auto* end = &_data[0] + _size;
if (_size && (begin < end)) {
_dirty = true;
std::fill(begin, end, value);
}
}
void StorageEEPROM_Rotate::writeReservedData() {
uint16_t crc = _calculateCRC(); // TODO use poly version vs. sum?
write(_offset + EEPROM_ROTATE_CRC_OFFSET, (crc >> 8) & 0xFF);
write(_offset + EEPROM_ROTATE_CRC_OFFSET + 1, crc & 0xFF);
write(_offset + EEPROM_ROTATE_COUNTER_OFFSET, _sector_value);
}
bool eepromReady() {
return _eeprom_ready;
}
void eepromRotate(bool value) {
// Only matters when the instance actually allows rotation.
// Because .rotate(false) marks EEPROM as dirty, this is equivalent to the .backup(0)
auto& instance = eepromInstance();
if (instance.canRotate()) {
DEBUG_MSG_P(PSTR("[EEPROM] %s EEPROM rotation\n"),
value ? PSTR("Enabling") : PSTR("Disabling"));
instance.rotate(value);
eepromCommit();
}
}
static String _eepromAvailableSectors(StorageEEPROM_Rotate& instance) {
const auto current_sector = instance.current();
String out;
for (uint8_t i = 0; i < instance.size(); ++i) {
if (i > 0) {
out += STRING_VIEW(", ").toString();
}
const auto sector = instance.getSector(i);
if (sector == current_sector) {
out += '(';
}
out += String(sector);
if (sector == current_sector) {
out += ')';
}
}
return out;
}
static String _eepromAvailableSectors() {
return _eepromAvailableSectors(eepromInstance());
}
String eepromSectors() {
return _eepromAvailableSectors();
}
static bool _eepromCommit(StorageEEPROM_Rotate& instance) {
return _eepromCommitResult(instance.commit());
}
static bool _eepromCommit() {
return _eepromCommit(eepromInstance());
}
void eepromForceCommit(StorageEEPROM_Rotate& instance) {
_eepromCommit(instance);
}
void eepromForceCommit() {
_eepromCommit();
}
void eepromCommit() {
_eeprom_commit = true;
}
size_t eepromSpace() {
auto& instance = eepromInstance();
constexpr auto SectorSize = size_t{ SPI_FLASH_SEC_SIZE };
const size_t size = instance.size();
return size * SectorSize;
}
void eepromClear() {
auto& instance = eepromInstance();
instance.fill(0xFF);
instance.commit();
}
void eepromBackup(uint32_t index){
auto& instance = eepromInstance();
instance.backup(index);
}
#if TERMINAL_SUPPORT
STRING_VIEW_INLINE(EepromCommand, "EEPROM");
static void _eepromCommand(::terminal::CommandContext&& ctx) {
auto& instance = eepromInstance();
ctx.output.printf_P(PSTR("Sector%s: %s\n"),
instance.canRotate() ? "s" : "",
_eepromAvailableSectors(instance).c_str());
if (_eeprom_commit_count > 0) {
ctx.output.printf_P(PSTR("Commits done: %lu, last: %s\n"),
_eeprom_commit_count,
_eeprom_last_commit_result
? PSTR("OK")
: PSTR("ERROR"));
}
terminalOK(ctx);
}
STRING_VIEW_INLINE(EepromCommit, "EEPROM.COMMIT");
static void _eepromCommandCommit(::terminal::CommandContext&& ctx) {
if (_eepromCommit()) {
terminalOK(ctx);
} else {
terminalError(ctx);
}
}
STRING_VIEW_INLINE(EepromDump, "EEPROM.DUMP");
static void _eepromCommandDump(::terminal::CommandContext&& ctx) {
auto& instance = eepromInstance();
instance.dump(ctx.output);
terminalOK(ctx);
}
STRING_VIEW_INLINE(FlashDump, "FLASH.DUMP");
static void _flashCommandDump(::terminal::CommandContext&& ctx) {
auto& instance = eepromInstance();
static const uint32_t Sectors = ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE;
if (ctx.argv.size() < 2) {
terminalError(ctx, _eepromAvailableSectors(instance));
return;
}
uint32_t sector = espurna::settings::internal::convert<uint32>(ctx.argv[1]);
if (sector >= Sectors) {
terminalError(ctx, STRING_VIEW("Sector out of range").toString());
return;
}
instance.dump(ctx.output, sector);
terminalOK(ctx);
}
static constexpr ::terminal::Command EepromCommands[] PROGMEM {
{EepromCommand, _eepromCommand},
{EepromCommit, _eepromCommandCommit},
{EepromDump, _eepromCommandDump},
{FlashDump, _flashCommandDump},
};
static void _eepromCommandsSetup() {
espurna::terminal::add(EepromCommands);
}
#endif
// -----------------------------------------------------------------------------
void eepromLoop() {
if (_eeprom_commit) {
_eepromCommit();
_eeprom_commit = false;
}
}
void eepromSetup() {
#if TERMINAL_SUPPORT
_eepromCommandsSetup();
#endif
espurnaRegisterLoop(eepromLoop);
_eeprom_ready = true;
}