153 lines
4.2 KiB
C++
153 lines
4.2 KiB
C++
#ifdef USE_RP2040
|
|
|
|
#include <Arduino.h>
|
|
|
|
#include <hardware/flash.h>
|
|
#include <hardware/sync.h>
|
|
|
|
#include "preferences.h"
|
|
|
|
#include <cstring>
|
|
|
|
#include "esphome/core/helpers.h"
|
|
#include "esphome/core/log.h"
|
|
|
|
namespace esphome::rp2040 {
|
|
|
|
static const char *const TAG = "preferences";
|
|
|
|
static constexpr uint32_t RP2040_FLASH_STORAGE_SIZE = 512;
|
|
|
|
static bool s_prevent_write = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
|
static uint8_t
|
|
s_flash_storage[RP2040_FLASH_STORAGE_SIZE]; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
|
static bool s_flash_dirty = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
|
|
|
// No preference can exceed the total flash storage, so stack buffer covers all cases.
|
|
static constexpr size_t PREF_MAX_BUFFER_SIZE = RP2040_FLASH_STORAGE_SIZE;
|
|
|
|
extern "C" uint8_t _EEPROM_start;
|
|
|
|
template<class It> uint8_t calculate_crc(It first, It last, uint32_t type) {
|
|
std::array<uint8_t, 4> type_array = decode_value(type);
|
|
uint8_t crc = type_array[0] ^ type_array[1] ^ type_array[2] ^ type_array[3];
|
|
while (first != last) {
|
|
crc ^= (*first++);
|
|
}
|
|
return crc;
|
|
}
|
|
|
|
bool RP2040PreferenceBackend::save(const uint8_t *data, size_t len) {
|
|
const size_t buffer_size = len + 1;
|
|
if (buffer_size > PREF_MAX_BUFFER_SIZE)
|
|
return false;
|
|
uint8_t buffer[PREF_MAX_BUFFER_SIZE];
|
|
memcpy(buffer, data, len);
|
|
buffer[len] = calculate_crc(buffer, buffer + len, this->type);
|
|
|
|
for (size_t i = 0; i < buffer_size; i++) {
|
|
uint32_t j = this->offset + i;
|
|
if (j >= RP2040_FLASH_STORAGE_SIZE)
|
|
return false;
|
|
uint8_t v = buffer[i];
|
|
uint8_t *ptr = &s_flash_storage[j];
|
|
if (*ptr != v)
|
|
s_flash_dirty = true;
|
|
*ptr = v;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool RP2040PreferenceBackend::load(uint8_t *data, size_t len) {
|
|
const size_t buffer_size = len + 1;
|
|
if (buffer_size > PREF_MAX_BUFFER_SIZE)
|
|
return false;
|
|
uint8_t buffer[PREF_MAX_BUFFER_SIZE];
|
|
|
|
for (size_t i = 0; i < buffer_size; i++) {
|
|
uint32_t j = this->offset + i;
|
|
if (j >= RP2040_FLASH_STORAGE_SIZE)
|
|
return false;
|
|
buffer[i] = s_flash_storage[j];
|
|
}
|
|
|
|
uint8_t crc = calculate_crc(buffer, buffer + len, this->type);
|
|
if (buffer[len] != crc) {
|
|
return false;
|
|
}
|
|
|
|
memcpy(data, buffer, len);
|
|
return true;
|
|
}
|
|
|
|
RP2040Preferences::RP2040Preferences() : eeprom_sector_(&_EEPROM_start) {}
|
|
|
|
void RP2040Preferences::setup() {
|
|
ESP_LOGVV(TAG, "Loading preferences from flash");
|
|
memcpy(s_flash_storage, this->eeprom_sector_, RP2040_FLASH_STORAGE_SIZE);
|
|
}
|
|
|
|
ESPPreferenceObject RP2040Preferences::make_preference(size_t length, uint32_t type) {
|
|
uint32_t start = this->current_flash_offset;
|
|
uint32_t end = start + length + 1;
|
|
if (end > RP2040_FLASH_STORAGE_SIZE) {
|
|
return {};
|
|
}
|
|
auto *pref = new RP2040PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
|
|
pref->offset = start;
|
|
pref->type = type;
|
|
this->current_flash_offset = end;
|
|
return ESPPreferenceObject(pref);
|
|
}
|
|
|
|
bool RP2040Preferences::sync() {
|
|
if (!s_flash_dirty)
|
|
return true;
|
|
if (s_prevent_write)
|
|
return false;
|
|
|
|
ESP_LOGD(TAG, "Saving");
|
|
|
|
{
|
|
InterruptLock lock;
|
|
::rp2040.idleOtherCore();
|
|
flash_range_erase((intptr_t) this->eeprom_sector_ - (intptr_t) XIP_BASE, 4096);
|
|
flash_range_program((intptr_t) this->eeprom_sector_ - (intptr_t) XIP_BASE, s_flash_storage,
|
|
RP2040_FLASH_STORAGE_SIZE);
|
|
::rp2040.resumeOtherCore();
|
|
}
|
|
|
|
s_flash_dirty = false;
|
|
return true;
|
|
}
|
|
|
|
bool RP2040Preferences::reset() {
|
|
ESP_LOGD(TAG, "Erasing storage");
|
|
{
|
|
InterruptLock lock;
|
|
::rp2040.idleOtherCore();
|
|
flash_range_erase((intptr_t) this->eeprom_sector_ - (intptr_t) XIP_BASE, 4096);
|
|
::rp2040.resumeOtherCore();
|
|
}
|
|
s_prevent_write = true;
|
|
return true;
|
|
}
|
|
|
|
static RP2040Preferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
|
|
|
RP2040Preferences *get_preferences() { return &s_preferences; }
|
|
|
|
void setup_preferences() {
|
|
s_preferences.setup();
|
|
global_preferences = &s_preferences;
|
|
}
|
|
void preferences_prevent_write(bool prevent) { s_prevent_write = prevent; }
|
|
|
|
} // namespace esphome::rp2040
|
|
|
|
namespace esphome {
|
|
ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
|
} // namespace esphome
|
|
|
|
#endif // USE_RP2040
|