ESPHome 2026.5.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"
10#include "esphome/core/log.h"
11
12#include <cstring>
13
14namespace esphome::esp8266 {
15
16static const char *const TAG = "preferences";
17
18static constexpr uint32_t ESP_RTC_USER_MEM_START = 0x60001200;
19static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = 128;
20static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_BYTES = ESP_RTC_USER_MEM_SIZE_WORDS * 4;
21
22// RTC memory layout:
23// - Eboot region: RTC words 0-31 (reserved, mapped from preference offset 78-109)
24// - Normal region: RTC words 32-109 (mapped from preference offset 0-77)
25// - Crash handler: RTC words 110-127 (reserved for crash_handler.cpp backtrace data)
26static constexpr uint32_t RTC_EBOOT_REGION_WORDS = 32; // Words 0-31 reserved for eboot
27static constexpr uint32_t RTC_NORMAL_REGION_WORDS = 78; // Words 32-109 for normal prefs
28static constexpr uint32_t PREF_TOTAL_WORDS = RTC_EBOOT_REGION_WORDS + RTC_NORMAL_REGION_WORDS; // 110
29
30// Maximum preference size in words (limited by uint8_t length_words field)
31static constexpr uint32_t MAX_PREFERENCE_WORDS = 255;
32
33#define ESP_RTC_USER_MEM ((uint32_t *) ESP_RTC_USER_MEM_START)
34
35// Flash storage size depends on esp8266 -> restore_from_flash YAML option (default: false).
36// When enabled (USE_ESP8266_PREFERENCES_FLASH), all preferences default to flash and need
37// 128 words (512 bytes). When disabled, only explicit flash prefs use this storage so
38// 64 words (256 bytes) suffices since most preferences go to RTC memory instead.
39#ifdef USE_ESP8266_PREFERENCES_FLASH
40static constexpr uint32_t ESP8266_FLASH_STORAGE_SIZE = 128;
41#else
42static constexpr uint32_t ESP8266_FLASH_STORAGE_SIZE = 64;
43#endif
44
45static uint32_t
46 s_flash_storage[ESP8266_FLASH_STORAGE_SIZE]; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
47static bool s_prevent_write = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
48static bool s_flash_dirty = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
49
50static inline bool esp_rtc_user_mem_read(uint32_t index, uint32_t *dest) {
51 if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) {
52 return false;
53 }
54 *dest = ESP_RTC_USER_MEM[index]; // NOLINT(performance-no-int-to-ptr)
55 return true;
56}
57
58static inline bool esp_rtc_user_mem_write(uint32_t index, uint32_t value) {
59 if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) {
60 return false;
61 }
62 if (index < 32 && s_prevent_write) {
63 return false;
64 }
65
66 auto *ptr = &ESP_RTC_USER_MEM[index]; // NOLINT(performance-no-int-to-ptr)
67 *ptr = value;
68 return true;
69}
70
71extern "C" uint32_t _SPIFFS_end; // NOLINT
72
73static uint32_t get_esp8266_flash_sector() {
74 union {
75 uint32_t *ptr;
76 uint32_t uint;
77 } data{};
78 data.ptr = &_SPIFFS_end;
79 return (data.uint - 0x40200000) / SPI_FLASH_SEC_SIZE;
80}
81static uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; }
82
83static inline size_t bytes_to_words(size_t bytes) { return (bytes + 3) / 4; }
84
85template<class It> uint32_t calculate_crc(It first, It last, uint32_t type) {
86 uint32_t crc = type;
87 while (first != last) {
88 crc ^= (*first++ * 2654435769UL) >> 1;
89 }
90 return crc;
91}
92
93static bool save_to_flash(size_t offset, const uint32_t *data, size_t len) {
94 for (uint32_t i = 0; i < len; i++) {
95 uint32_t j = offset + i;
96 if (j >= ESP8266_FLASH_STORAGE_SIZE)
97 return false;
98 uint32_t v = data[i];
99 uint32_t *ptr = &s_flash_storage[j];
100 if (*ptr != v)
101 s_flash_dirty = true;
102 *ptr = v;
103 }
104 return true;
105}
106
107static bool load_from_flash(size_t offset, uint32_t *data, size_t len) {
108 for (size_t i = 0; i < len; i++) {
109 uint32_t j = offset + i;
110 if (j >= ESP8266_FLASH_STORAGE_SIZE)
111 return false;
112 data[i] = s_flash_storage[j];
113 }
114 return true;
115}
116
117static bool save_to_rtc(size_t offset, const uint32_t *data, size_t len) {
118 for (uint32_t i = 0; i < len; i++) {
119 if (!esp_rtc_user_mem_write(offset + i, data[i]))
120 return false;
121 }
122 return true;
123}
124
125static bool load_from_rtc(size_t offset, uint32_t *data, size_t len) {
126 for (uint32_t i = 0; i < len; i++) {
127 if (!esp_rtc_user_mem_read(offset + i, &data[i]))
128 return false;
129 }
130 return true;
131}
132
133// Maximum buffer for any single preference - bounded by storage sizes.
134// Flash prefs: bounded by ESP8266_FLASH_STORAGE_SIZE (128 or 64 words).
135// RTC prefs: bounded by RTC_NORMAL_REGION_WORDS (96) - a single pref can't span both RTC regions.
136static constexpr size_t PREF_MAX_BUFFER_WORDS =
137 ESP8266_FLASH_STORAGE_SIZE > RTC_NORMAL_REGION_WORDS ? ESP8266_FLASH_STORAGE_SIZE : RTC_NORMAL_REGION_WORDS;
138
139bool ESP8266PreferenceBackend::save(const uint8_t *data, size_t len) {
140 if (bytes_to_words(len) != this->length_words)
141 return false;
142 const size_t buffer_size = static_cast<size_t>(this->length_words) + 1;
143 if (buffer_size > PREF_MAX_BUFFER_WORDS)
144 return false;
145 uint32_t buffer[PREF_MAX_BUFFER_WORDS];
146 memset(buffer, 0, buffer_size * sizeof(uint32_t));
147 memcpy(buffer, data, len);
148 buffer[this->length_words] = calculate_crc(buffer, buffer + this->length_words, this->type);
149 return this->in_flash ? save_to_flash(this->offset, buffer, buffer_size)
150 : save_to_rtc(this->offset, buffer, buffer_size);
151}
152
153bool ESP8266PreferenceBackend::load(uint8_t *data, size_t len) {
154 if (bytes_to_words(len) != this->length_words)
155 return false;
156 const size_t buffer_size = static_cast<size_t>(this->length_words) + 1;
157 if (buffer_size > PREF_MAX_BUFFER_WORDS)
158 return false;
159 uint32_t buffer[PREF_MAX_BUFFER_WORDS];
160 bool ret = this->in_flash ? load_from_flash(this->offset, buffer, buffer_size)
161 : load_from_rtc(this->offset, buffer, buffer_size);
162 if (!ret)
163 return false;
164 if (buffer[this->length_words] != calculate_crc(buffer, buffer + this->length_words, this->type))
165 return false;
166 memcpy(data, buffer, len);
167 return true;
168}
169
171 ESP_LOGVV(TAG, "Loading preferences from flash");
172
173 {
174 InterruptLock lock;
175 spi_flash_read(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4);
176 }
177}
178
180 const uint32_t length_words = bytes_to_words(length);
181 if (length_words > MAX_PREFERENCE_WORDS) {
182 ESP_LOGE(TAG, "Preference too large: %u words", static_cast<unsigned int>(length_words));
183 return {};
184 }
185
186 const uint32_t total_words = length_words + 1; // +1 for CRC
187 uint16_t offset;
188
189 if (in_flash) {
190 if (this->current_flash_offset + total_words > ESP8266_FLASH_STORAGE_SIZE)
191 return {};
192 offset = static_cast<uint16_t>(this->current_flash_offset);
193 this->current_flash_offset += total_words;
194 } else {
195 uint32_t start = this->current_offset;
196 bool in_normal = start < RTC_NORMAL_REGION_WORDS;
197 // Normal: offset 0-95 maps to RTC offset 32-127
198 // Eboot: offset 96-127 maps to RTC offset 0-31
199 if (in_normal && start + total_words > RTC_NORMAL_REGION_WORDS) {
200 // start is in normal but end is not -> switch to Eboot
201 this->current_offset = start = RTC_NORMAL_REGION_WORDS;
202 in_normal = false;
203 }
204 if (start + total_words > PREF_TOTAL_WORDS)
205 return {}; // Doesn't fit in RTC memory
206 // Convert preference offset to RTC memory offset
207 offset = static_cast<uint16_t>(in_normal ? start + RTC_EBOOT_REGION_WORDS : start - RTC_NORMAL_REGION_WORDS);
208 this->current_offset = start + total_words;
209 }
210
211 auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
212 pref->offset = offset;
213 pref->type = type;
214 pref->length_words = static_cast<uint8_t>(length_words);
215 pref->in_flash = in_flash;
216 return ESPPreferenceObject(pref);
217}
218
220 if (!s_flash_dirty)
221 return true;
222 if (s_prevent_write)
223 return false;
224
225 ESP_LOGD(TAG, "Saving");
226 SpiFlashOpResult erase_res, write_res = SPI_FLASH_RESULT_OK;
227 {
228 InterruptLock lock;
229 erase_res = spi_flash_erase_sector(get_esp8266_flash_sector());
230 if (erase_res == SPI_FLASH_RESULT_OK) {
231 write_res = spi_flash_write(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4);
232 }
233 }
234 if (erase_res != SPI_FLASH_RESULT_OK) {
235 ESP_LOGE(TAG, "Erasing failed");
236 return false;
237 }
238 if (write_res != SPI_FLASH_RESULT_OK) {
239 ESP_LOGE(TAG, "Writing failed");
240 return false;
241 }
242
243 s_flash_dirty = false;
244 return true;
245}
246
248 ESP_LOGD(TAG, "Erasing storage");
249 SpiFlashOpResult erase_res;
250 {
251 InterruptLock lock;
252 erase_res = spi_flash_erase_sector(get_esp8266_flash_sector());
253 }
254 if (erase_res != SPI_FLASH_RESULT_OK) {
255 ESP_LOGE(TAG, "Erasing failed");
256 return false;
257 }
258
259 // Protect flash from writing till restart
260 s_prevent_write = true;
261 return true;
262}
263
264static ESP8266Preferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
265
266ESP8266Preferences *get_preferences() { return &s_preferences; }
267
269 s_preferences.setup();
270 global_preferences = &s_preferences;
271}
272void preferences_prevent_write(bool prevent) { s_prevent_write = prevent; }
273
274} // namespace esphome::esp8266
275
276namespace esphome {
277ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
278} // namespace esphome
279
280#endif // USE_ESP8266
Helper class to disable interrupts.
Definition helpers.h:2104
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)
uint16_t type
void preferences_prevent_write(bool prevent)
ESP8266Preferences * get_preferences()
uint32_t calculate_crc(It first, It last, uint32_t type)
const std::vector< uint8_t > & data
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
Preferences ESPPreferences
Definition preferences.h:42
std::string size_t len
Definition helpers.h:1045
ESPPreferences * global_preferences
static void uint32_t
uint16_t length
Definition tt21100.cpp:0