diff --git a/esphome/components/ota/ota_backend_arduino_rp2040.cpp b/esphome/components/ota/ota_backend_arduino_rp2040.cpp index 5a46b8f07e..260387cec1 100644 --- a/esphome/components/ota/ota_backend_arduino_rp2040.cpp +++ b/esphome/components/ota/ota_backend_arduino_rp2040.cpp @@ -15,6 +15,7 @@ namespace ota { OTAResponseTypes ArduinoRP2040OTABackend::begin(size_t image_size) { bool ret = Update.begin(image_size, U_FLASH); if (ret) { + rp2040::preferences_prevent_write(true); return OTA_RESPONSE_OK; } @@ -46,7 +47,10 @@ OTAResponseTypes ArduinoRP2040OTABackend::end() { return OTA_RESPONSE_OK; } -void ArduinoRP2040OTABackend::abort() { Update.end(); } +void ArduinoRP2040OTABackend::abort() { + Update.end(); + rp2040::preferences_prevent_write(false); +} } // namespace ota } // namespace esphome diff --git a/esphome/components/rp2040/preferences.cpp b/esphome/components/rp2040/preferences.cpp index 58ec57df2a..c98344541c 100644 --- a/esphome/components/rp2040/preferences.cpp +++ b/esphome/components/rp2040/preferences.cpp @@ -1,5 +1,10 @@ #ifdef USE_RP2040 +#include + +#include +#include + #include "preferences.h" #include @@ -12,33 +17,137 @@ namespace rp2040 { static const char *const TAG = "rp2040.preferences"; +static bool s_prevent_write = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static uint8_t *s_flash_storage = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static bool s_flash_dirty = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +static const uint32_t RP2040_FLASH_STORAGE_SIZE = 512; + +extern "C" uint8_t _EEPROM_start; + +template uint8_t calculate_crc(It first, It last, uint32_t type) { + std::array 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; +} + class RP2040PreferenceBackend : public ESPPreferenceBackend { public: - bool save(const uint8_t *data, size_t len) override { return true; } - bool load(uint8_t *data, size_t len) override { return false; } + size_t offset = 0; + uint32_t type = 0; + + bool save(const uint8_t *data, size_t len) override { + std::vector buffer; + buffer.resize(len + 1); + memcpy(buffer.data(), data, len); + buffer[buffer.size() - 1] = calculate_crc(buffer.begin(), buffer.end() - 1, type); + + for (uint32_t i = 0; i < len + 1; i++) { + uint32_t j = 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 load(uint8_t *data, size_t len) override { + std::vector buffer; + buffer.resize(len + 1); + + for (size_t i = 0; i < len + 1; i++) { + uint32_t j = offset + i; + if (j >= RP2040_FLASH_STORAGE_SIZE) + return false; + buffer[i] = s_flash_storage[j]; + } + + uint8_t crc = calculate_crc(buffer.begin(), buffer.end() - 1, type); + if (buffer[buffer.size() - 1] != crc) { + return false; + } + + memcpy(data, buffer.data(), len); + return true; + } }; class RP2040Preferences : public ESPPreferences { public: + uint32_t current_flash_offset = 0; + + RP2040Preferences() : eeprom_sector_(&_EEPROM_start) {} + void setup() { + s_flash_storage = new uint8_t[RP2040_FLASH_STORAGE_SIZE]; // NOLINT + ESP_LOGVV(TAG, "Loading preferences from flash..."); + memcpy(s_flash_storage, this->eeprom_sector_, RP2040_FLASH_STORAGE_SIZE); + } + ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { - auto *pref = new RP2040PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) - return ESPPreferenceObject(pref); + return make_preference(length, type); } ESPPreferenceObject make_preference(size_t length, uint32_t type) override { + 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) - return ESPPreferenceObject(pref); + pref->offset = start; + pref->type = type; + current_flash_offset = end; + return {pref}; } - bool sync() override { return true; } + bool sync() override { + if (!s_flash_dirty) + return true; + if (s_prevent_write) + return false; - bool reset() override { return true; } + ESP_LOGD(TAG, "Saving preferences to flash..."); + + { + InterruptLock lock; + ::rp2040.idleOtherCore(); + flash_range_erase((intptr_t) eeprom_sector_ - (intptr_t) XIP_BASE, 4096); + flash_range_program((intptr_t) eeprom_sector_ - (intptr_t) XIP_BASE, s_flash_storage, RP2040_FLASH_STORAGE_SIZE); + ::rp2040.resumeOtherCore(); + } + + s_flash_dirty = false; + return true; + } + + bool reset() override { + ESP_LOGD(TAG, "Cleaning up preferences in flash..."); + { + InterruptLock lock; + ::rp2040.idleOtherCore(); + flash_range_erase((intptr_t) eeprom_sector_ - (intptr_t) XIP_BASE, 4096); + ::rp2040.resumeOtherCore(); + } + s_prevent_write = true; + return true; + } + + protected: + uint8_t *eeprom_sector_; }; void setup_preferences() { auto *prefs = new RP2040Preferences(); // NOLINT(cppcoreguidelines-owning-memory) + prefs->setup(); global_preferences = prefs; } +void preferences_prevent_write(bool prevent) { s_prevent_write = prevent; } } // namespace rp2040 diff --git a/esphome/components/rp2040/preferences.h b/esphome/components/rp2040/preferences.h index 3c740a41c1..b815c6d58a 100644 --- a/esphome/components/rp2040/preferences.h +++ b/esphome/components/rp2040/preferences.h @@ -6,6 +6,7 @@ namespace esphome { namespace rp2040 { void setup_preferences(); +void preferences_prevent_write(bool prevent); } // namespace rp2040 } // namespace esphome