#ifdef USE_RP2040 #include #include #include #include "preferences.h" #include #include #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "esphome/core/preferences.h" namespace esphome { 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: 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 { 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) pref->offset = start; pref->type = type; current_flash_offset = end; return {pref}; } bool sync() override { 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) 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, "Erasing storage"); { 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 ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace esphome #endif // USE_RP2040