ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
preferences.cpp
Go to the documentation of this file.
1#ifdef USE_ESP8266
2
3#include <c_types.h>
4extern "C" {
5#include "spi_flash.h"
6}
7
8#include "preferences.h"
9#include "rtc_layout.h"
11#include "esphome/core/log.h"
13
14#include <cstring>
15
16namespace esphome::esp8266 {
17
18ESPHOME_LOG_TAG(TAG, "preferences");
19
20static constexpr uint32_t ESP_RTC_USER_MEM_START = 0x60001200;
21static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = RTC_USER_MEM_WORDS;
22static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_BYTES = ESP_RTC_USER_MEM_SIZE_WORDS * 4;
23
24// RTC memory layout (sizes come from const.py through codegen):
25// - Eboot region: RTC words 0-31 (reserved, mapped from preference offset 78-109)
26// - Normal region: RTC words 32-109 (mapped from preference offset 0-77)
27// - Crash handler: RTC words 110-127 (reserved for crash_handler.cpp backtrace data)
28static constexpr uint32_t RTC_EBOOT_REGION_WORDS = ESP8266_PREFERENCES_RTC_EBOOT_WORDS;
29static constexpr uint32_t RTC_NORMAL_REGION_WORDS = ESP8266_PREFERENCES_RTC_NORMAL_WORDS;
30static constexpr uint32_t PREF_TOTAL_WORDS = RTC_EBOOT_REGION_WORDS + RTC_NORMAL_REGION_WORDS; // 110
31static_assert(PREF_TOTAL_WORDS <= RTC_CRASH_BASE - RTC_SYSTEM_USER_BASE,
32 "preference regions overlap the crash handler's RTC words");
33
34// Maximum preference size in words (limited by uint8_t length_words field)
35static constexpr uint32_t MAX_PREFERENCE_WORDS = 255;
36
37#define ESP_RTC_USER_MEM ((uint32_t *) ESP_RTC_USER_MEM_START)
38
39// Smaller without restore_from_flash, when only explicit flash preferences live here
40static constexpr uint32_t ESP8266_FLASH_STORAGE_SIZE = ESP8266_PREFERENCES_FLASH_WORDS;
41
42static uint32_t
43 s_flash_storage[ESP8266_FLASH_STORAGE_SIZE]; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
44static bool s_prevent_write = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
45static bool s_flash_dirty = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
46
47static inline bool esp_rtc_user_mem_read(uint32_t index, uint32_t *dest) {
48 if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) {
49 return false;
50 }
51 // NOLINTNEXTLINE(performance-no-int-to-ptr,clang-analyzer-core.FixedAddressDereference,clang-analyzer-optin.core.FixedAddressDereference)
52 *dest = ESP_RTC_USER_MEM[index];
53 return true;
54}
55
56static inline bool esp_rtc_user_mem_write(uint32_t index, uint32_t value) {
57 if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) {
58 return false;
59 }
60 if (index < RTC_EBOOT_REGION_WORDS && s_prevent_write) {
61 return false;
62 }
63
64 auto *ptr = &ESP_RTC_USER_MEM[index]; // NOLINT(performance-no-int-to-ptr)
65 // NOLINTNEXTLINE(clang-analyzer-core.FixedAddressDereference,clang-analyzer-optin.core.FixedAddressDereference)
66 *ptr = value;
67 return true;
68}
69
70extern "C" uint32_t _SPIFFS_end; // NOLINT
71
72static uint32_t get_esp8266_flash_sector() {
73 union {
74 uint32_t *ptr;
75 uint32_t uint;
76 } data{};
77 data.ptr = &_SPIFFS_end;
78 return (data.uint - 0x40200000) / SPI_FLASH_SEC_SIZE;
79}
80static uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; }
81
82static bool save_to_flash(size_t offset, const uint32_t *data, size_t len) {
83 for (uint32_t i = 0; i < len; i++) {
84 uint32_t j = offset + i;
85 if (j >= ESP8266_FLASH_STORAGE_SIZE)
86 return false;
87 uint32_t v = data[i];
88 uint32_t *ptr = &s_flash_storage[j];
89 if (*ptr != v)
90 s_flash_dirty = true;
91 *ptr = v;
92 }
93 return true;
94}
95
96static bool load_from_flash(size_t offset, uint32_t *data, size_t len) {
97 for (size_t i = 0; i < len; i++) {
98 uint32_t j = offset + i;
99 if (j >= ESP8266_FLASH_STORAGE_SIZE)
100 return false;
101 data[i] = s_flash_storage[j];
102 }
103 return true;
104}
105
106static bool save_to_rtc(size_t offset, const uint32_t *data, size_t len) {
107 for (uint32_t i = 0; i < len; i++) {
108 if (!esp_rtc_user_mem_write(offset + i, data[i]))
109 return false;
110 }
111 return true;
112}
113
114static bool load_from_rtc(size_t offset, uint32_t *data, size_t len) {
115 for (uint32_t i = 0; i < len; i++) {
116 if (!esp_rtc_user_mem_read(offset + i, &data[i]))
117 return false;
118 }
119 return true;
120}
121
122// Maximum buffer for any single preference - bounded by storage sizes.
123// Flash prefs: bounded by ESP8266_FLASH_STORAGE_SIZE (128 or 64 words).
124// RTC prefs: bounded by RTC_NORMAL_REGION_WORDS (78) - a single pref can't span both RTC regions.
125static constexpr size_t PREF_MAX_BUFFER_WORDS =
126 ESP8266_FLASH_STORAGE_SIZE > RTC_NORMAL_REGION_WORDS ? ESP8266_FLASH_STORAGE_SIZE : RTC_NORMAL_REGION_WORDS;
127
128bool ESP8266PreferenceBackend::save(const uint8_t *data, size_t len) {
130 return false;
131 const size_t buffer_size = static_cast<size_t>(this->length_words) + 1;
132 if (buffer_size > PREF_MAX_BUFFER_WORDS)
133 return false;
134 uint32_t buffer[PREF_MAX_BUFFER_WORDS];
135 rtc_pref_encode(buffer, this->type, this->length_words, data, len);
136 return this->in_flash ? save_to_flash(this->offset, buffer, buffer_size)
137 : save_to_rtc(this->offset, buffer, buffer_size);
138}
139
140bool ESP8266PreferenceBackend::load(uint8_t *data, size_t len) {
142 return false;
143 const size_t buffer_size = static_cast<size_t>(this->length_words) + 1;
144 if (buffer_size > PREF_MAX_BUFFER_WORDS)
145 return false;
146 uint32_t buffer[PREF_MAX_BUFFER_WORDS];
147 bool ret = this->in_flash ? load_from_flash(this->offset, buffer, buffer_size)
148 : load_from_rtc(this->offset, buffer, buffer_size);
149 if (!ret)
150 return false;
151 return rtc_pref_decode(buffer, this->type, this->length_words, data, len);
152}
153
155 ESP_LOGVV(TAG, "Loading preferences from flash");
156
157 {
159 spi_flash_read(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4);
160 }
161}
162
164 const uint32_t length_words = rtc_pref_bytes_to_words(length);
165 if (length_words > MAX_PREFERENCE_WORDS) {
166 ESP_LOGE(TAG, "Preference too large: %u words", static_cast<unsigned int>(length_words));
167 return {};
168 }
169
170 const uint32_t total_words = length_words + 1; // +1 for checksum
171 uint16_t offset;
172
173 if (in_flash) {
174 if (this->current_flash_offset + total_words > ESP8266_FLASH_STORAGE_SIZE)
175 return {};
176 offset = static_cast<uint16_t>(this->current_flash_offset);
177 this->current_flash_offset += total_words;
178 } else {
179 uint32_t start = this->current_offset;
180 bool in_normal = start < RTC_NORMAL_REGION_WORDS;
181 // Normal: offset 0-77 maps to RTC offset 32-109
182 // Eboot: offset 78-109 maps to RTC offset 0-31
183 if (in_normal && start + total_words > RTC_NORMAL_REGION_WORDS) {
184 // start is in normal but end is not -> switch to Eboot
185 this->current_offset = start = RTC_NORMAL_REGION_WORDS;
186 in_normal = false;
187 }
188 if (start + total_words > PREF_TOTAL_WORDS)
189 return {}; // Doesn't fit in RTC memory
190 // Convert preference offset to RTC memory offset
191 offset = static_cast<uint16_t>(in_normal ? start + RTC_EBOOT_REGION_WORDS : start - RTC_NORMAL_REGION_WORDS);
192 this->current_offset = start + total_words;
193 }
194
195 auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
196 pref->offset = offset;
197 pref->type = type;
198 pref->length_words = static_cast<uint8_t>(length_words);
199 pref->in_flash = in_flash;
200 return ESPPreferenceObject(pref);
201}
202
204 if (!s_flash_dirty)
205 return true;
206 if (s_prevent_write)
207 return false;
208
209 ESP_LOGD(TAG, "Saving");
210 SpiFlashOpResult erase_res, write_res = SPI_FLASH_RESULT_OK;
211 {
213 erase_res = spi_flash_erase_sector(get_esp8266_flash_sector());
214 if (erase_res == SPI_FLASH_RESULT_OK) {
215 write_res = spi_flash_write(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4);
216 }
217 }
218 if (erase_res != SPI_FLASH_RESULT_OK) {
219 ESP_LOGE(TAG, "Erasing failed");
220 return false;
221 }
222 if (write_res != SPI_FLASH_RESULT_OK) {
223 ESP_LOGE(TAG, "Writing failed");
224 return false;
225 }
226
227 s_flash_dirty = false;
228 return true;
229}
230
232 ESP_LOGD(TAG, "Erasing storage");
233 SpiFlashOpResult erase_res;
234 {
236 erase_res = spi_flash_erase_sector(get_esp8266_flash_sector());
237 }
238 if (erase_res != SPI_FLASH_RESULT_OK) {
239 ESP_LOGE(TAG, "Erasing failed");
240 return false;
241 }
242
243 // Protect flash from writing till restart
244 s_prevent_write = true;
245 return true;
246}
247
248static ESP8266Preferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
249
250ESP8266Preferences *get_preferences() { return &s_preferences; }
251
253 s_preferences.setup();
254 global_preferences = &s_preferences;
255}
256void preferences_prevent_write(bool prevent) { s_prevent_write = prevent; }
257
258} // namespace esphome::esp8266
259
260namespace esphome {
261ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
262} // namespace esphome
263
264#endif // USE_ESP8266
Helper class to disable interrupts.
Definition helpers.h:2164
bool save(const uint8_t *data, size_t len)
bool load(uint8_t *data, size_t len)
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash)
uint8_t type
uint32_t buffer
Definition gif.cpp:50
int ret
static void int int int int const uint8_t * ptr
Definition display.cpp:63
ESPHOME_LOG_TAG(TAG, "esp8266")
void preferences_prevent_write(bool prevent)
ESP8266Preferences * get_preferences()
std::span< const uint8_t > data
value[LAYOUT.day_index()]
Preferences ESPPreferences
Definition preferences.h:43
size_t rtc_pref_bytes_to_words(size_t bytes)
Round a byte count up to whole 32-bit words.
const void size_t len
Definition hal.h:68
bool rtc_pref_decode(const uint32_t *buffer, uint32_t type, uint8_t length_words, uint8_t *data, size_t len)
Verify the checksum of a record held in buffer (length_words data words + 1 checksum word) and,...
ESPPreferences * global_preferences
void rtc_pref_encode(uint32_t *buffer, uint32_t type, uint8_t length_words, const uint8_t *data, size_t len)
Encode len data bytes into buffer (length_words data words + 1 trailing checksum word).
static void uint32_t
uint16_t length
Definition tt21100.cpp:0
SemaphoreHandle_t lock