#ifdef USE_RP2040 #include #include #include #include "preferences.h" #include #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 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; } 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