From fe5088c6e19bca24a13c524df9bcb7a9bae02e30 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 11 Feb 2026 07:27:24 -0600 Subject: [PATCH] [esp8266][rp2040] Eliminate heap fallback in preference save/load MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace SmallBufferWithHeapFallback with a fixed stack buffer sized to the physical storage limit. No single preference can exceed the storage it resides in, so heap allocation is never needed. ESP8266: buffer sized to max(flash_storage, rtc_normal_region) — 128 words (512B) with restore_from_flash, 96 words (384B) without. RP2040: buffer sized to flash storage (512B). Eliminates new[]/delete[] codegen from save()/load() virtual methods. --- esphome/components/esp8266/preferences.cpp | 29 +++++++++++----------- esphome/components/rp2040/preferences.cpp | 23 +++++++++-------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/esphome/components/esp8266/preferences.cpp b/esphome/components/esp8266/preferences.cpp index f037b881a8..e749b1f633 100644 --- a/esphome/components/esp8266/preferences.cpp +++ b/esphome/components/esp8266/preferences.cpp @@ -33,6 +33,10 @@ static constexpr uint32_t MAX_PREFERENCE_WORDS = 255; #define ESP_RTC_USER_MEM ((uint32_t *) ESP_RTC_USER_MEM_START) +// Flash storage size depends on esp8266 -> restore_from_flash YAML option (default: false). +// When enabled (USE_ESP8266_PREFERENCES_FLASH), all preferences default to flash and need +// 128 words (512 bytes). When disabled, only explicit flash prefs use this storage so +// 64 words (256 bytes) suffices since most preferences go to RTC memory instead. #ifdef USE_ESP8266_PREFERENCES_FLASH static constexpr uint32_t ESP8266_FLASH_STORAGE_SIZE = 128; #else @@ -127,9 +131,11 @@ static bool load_from_rtc(size_t offset, uint32_t *data, size_t len) { return true; } -// Stack buffer size - 16 words total: up to 15 words of preference data + 1 word CRC (60 bytes of preference data) -// This handles virtually all real-world preferences without heap allocation -static constexpr size_t PREF_BUFFER_WORDS = 16; +// Maximum buffer for any single preference - bounded by storage sizes. +// Flash prefs: bounded by ESP8266_FLASH_STORAGE_SIZE (128 or 64 words). +// RTC prefs: bounded by RTC_NORMAL_REGION_WORDS (96) - a single pref can't span both RTC regions. +static constexpr size_t PREF_MAX_BUFFER_WORDS = + ESP8266_FLASH_STORAGE_SIZE > RTC_NORMAL_REGION_WORDS ? ESP8266_FLASH_STORAGE_SIZE : RTC_NORMAL_REGION_WORDS; class ESP8266PreferenceBackend : public ESPPreferenceBackend { public: @@ -141,15 +147,13 @@ class ESP8266PreferenceBackend : public ESPPreferenceBackend { bool save(const uint8_t *data, size_t len) override { if (bytes_to_words(len) != this->length_words) return false; - const size_t buffer_size = static_cast(this->length_words) + 1; - SmallBufferWithHeapFallback buffer_alloc(buffer_size); - uint32_t *buffer = buffer_alloc.get(); + if (buffer_size > PREF_MAX_BUFFER_WORDS) + return false; + uint32_t buffer[PREF_MAX_BUFFER_WORDS]; memset(buffer, 0, buffer_size * sizeof(uint32_t)); - memcpy(buffer, data, len); buffer[this->length_words] = calculate_crc(buffer, buffer + this->length_words, this->type); - return this->in_flash ? save_to_flash(this->offset, buffer, buffer_size) : save_to_rtc(this->offset, buffer, buffer_size); } @@ -157,19 +161,16 @@ class ESP8266PreferenceBackend : public ESPPreferenceBackend { bool load(uint8_t *data, size_t len) override { if (bytes_to_words(len) != this->length_words) return false; - const size_t buffer_size = static_cast(this->length_words) + 1; - SmallBufferWithHeapFallback buffer_alloc(buffer_size); - uint32_t *buffer = buffer_alloc.get(); - + if (buffer_size > PREF_MAX_BUFFER_WORDS) + return false; + uint32_t buffer[PREF_MAX_BUFFER_WORDS]; bool ret = this->in_flash ? load_from_flash(this->offset, buffer, buffer_size) : load_from_rtc(this->offset, buffer, buffer_size); if (!ret) return false; - if (buffer[this->length_words] != calculate_crc(buffer, buffer + this->length_words, this->type)) return false; - memcpy(data, buffer, len); return true; } diff --git a/esphome/components/rp2040/preferences.cpp b/esphome/components/rp2040/preferences.cpp index 172da32adc..fa72fd9a24 100644 --- a/esphome/components/rp2040/preferences.cpp +++ b/esphome/components/rp2040/preferences.cpp @@ -25,8 +25,8 @@ 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) -// Stack buffer size for preferences - covers virtually all real-world preferences without heap allocation -static constexpr size_t PREF_BUFFER_SIZE = 64; +// 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; @@ -46,14 +46,14 @@ class RP2040PreferenceBackend : public ESPPreferenceBackend { bool save(const uint8_t *data, size_t len) override { const size_t buffer_size = len + 1; - SmallBufferWithHeapFallback buffer_alloc(buffer_size); - uint8_t *buffer = buffer_alloc.get(); - + 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, type); + buffer[len] = calculate_crc(buffer, buffer + len, this->type); for (size_t i = 0; i < buffer_size; i++) { - uint32_t j = offset + i; + uint32_t j = this->offset + i; if (j >= RP2040_FLASH_STORAGE_SIZE) return false; uint8_t v = buffer[i]; @@ -66,17 +66,18 @@ class RP2040PreferenceBackend : public ESPPreferenceBackend { } bool load(uint8_t *data, size_t len) override { const size_t buffer_size = len + 1; - SmallBufferWithHeapFallback buffer_alloc(buffer_size); - uint8_t *buffer = buffer_alloc.get(); + 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 = offset + 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, type); + uint8_t crc = calculate_crc(buffer, buffer + len, this->type); if (buffer[len] != crc) { return false; }