ESPHome  2024.5.5
preferences.cpp
Go to the documentation of this file.
1 #ifdef USE_ESP8266
2 
3 #include <c_types.h>
4 extern "C" {
5 #include "spi_flash.h"
6 }
7 
8 #include "esphome/core/defines.h"
9 #include "esphome/core/helpers.h"
10 #include "esphome/core/log.h"
12 #include "preferences.h"
13 
14 #include <cstring>
15 #include <vector>
16 
17 namespace esphome {
18 namespace esp8266 {
19 
20 static const char *const TAG = "esp8266.preferences";
21 
22 static bool s_prevent_write = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
23 static uint32_t *s_flash_storage = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
24 static bool s_flash_dirty = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
25 
26 static const uint32_t ESP_RTC_USER_MEM_START = 0x60001200;
27 #define ESP_RTC_USER_MEM ((uint32_t *) ESP_RTC_USER_MEM_START)
28 static const uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = 128;
29 static const uint32_t ESP_RTC_USER_MEM_SIZE_BYTES = ESP_RTC_USER_MEM_SIZE_WORDS * 4;
30 
31 #ifdef USE_ESP8266_PREFERENCES_FLASH
32 static const uint32_t ESP8266_FLASH_STORAGE_SIZE = 128;
33 #else
34 static const uint32_t ESP8266_FLASH_STORAGE_SIZE = 64;
35 #endif
36 
37 static inline bool esp_rtc_user_mem_read(uint32_t index, uint32_t *dest) {
38  if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) {
39  return false;
40  }
41  *dest = ESP_RTC_USER_MEM[index]; // NOLINT(performance-no-int-to-ptr)
42  return true;
43 }
44 
45 static inline bool esp_rtc_user_mem_write(uint32_t index, uint32_t value) {
46  if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) {
47  return false;
48  }
49  if (index < 32 && s_prevent_write) {
50  return false;
51  }
52 
53  auto *ptr = &ESP_RTC_USER_MEM[index]; // NOLINT(performance-no-int-to-ptr)
54  *ptr = value;
55  return true;
56 }
57 
58 extern "C" uint32_t _SPIFFS_end; // NOLINT
59 
60 static uint32_t get_esp8266_flash_sector() {
61  union {
62  uint32_t *ptr;
63  uint32_t uint;
64  } data{};
65  data.ptr = &_SPIFFS_end;
66  return (data.uint - 0x40200000) / SPI_FLASH_SEC_SIZE;
67 }
68 static uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; }
69 
70 template<class It> uint32_t calculate_crc(It first, It last, uint32_t type) {
71  uint32_t crc = type;
72  while (first != last) {
73  crc ^= (*first++ * 2654435769UL) >> 1;
74  }
75  return crc;
76 }
77 
78 static bool save_to_flash(size_t offset, const uint32_t *data, size_t len) {
79  for (uint32_t i = 0; i < len; i++) {
80  uint32_t j = offset + i;
81  if (j >= ESP8266_FLASH_STORAGE_SIZE)
82  return false;
83  uint32_t v = data[i];
84  uint32_t *ptr = &s_flash_storage[j];
85  if (*ptr != v)
86  s_flash_dirty = true;
87  *ptr = v;
88  }
89  return true;
90 }
91 
92 static bool load_from_flash(size_t offset, uint32_t *data, size_t len) {
93  for (size_t i = 0; i < len; i++) {
94  uint32_t j = offset + i;
95  if (j >= ESP8266_FLASH_STORAGE_SIZE)
96  return false;
97  data[i] = s_flash_storage[j];
98  }
99  return true;
100 }
101 
102 static bool save_to_rtc(size_t offset, const uint32_t *data, size_t len) {
103  for (uint32_t i = 0; i < len; i++) {
104  if (!esp_rtc_user_mem_write(offset + i, data[i]))
105  return false;
106  }
107  return true;
108 }
109 
110 static bool load_from_rtc(size_t offset, uint32_t *data, size_t len) {
111  for (uint32_t i = 0; i < len; i++) {
112  if (!esp_rtc_user_mem_read(offset + i, &data[i]))
113  return false;
114  }
115  return true;
116 }
117 
118 class ESP8266PreferenceBackend : public ESPPreferenceBackend {
119  public:
120  size_t offset = 0;
121  uint32_t type = 0;
122  bool in_flash = false;
123  size_t length_words = 0;
124 
125  bool save(const uint8_t *data, size_t len) override {
126  if ((len + 3) / 4 != length_words) {
127  return false;
128  }
129  std::vector<uint32_t> buffer;
130  buffer.resize(length_words + 1);
131  memcpy(buffer.data(), data, len);
132  buffer[buffer.size() - 1] = calculate_crc(buffer.begin(), buffer.end() - 1, type);
133 
134  if (in_flash) {
135  return save_to_flash(offset, buffer.data(), buffer.size());
136  } else {
137  return save_to_rtc(offset, buffer.data(), buffer.size());
138  }
139  }
140  bool load(uint8_t *data, size_t len) override {
141  if ((len + 3) / 4 != length_words) {
142  return false;
143  }
144  std::vector<uint32_t> buffer;
145  buffer.resize(length_words + 1);
146  bool ret;
147  if (in_flash) {
148  ret = load_from_flash(offset, buffer.data(), buffer.size());
149  } else {
150  ret = load_from_rtc(offset, buffer.data(), buffer.size());
151  }
152  if (!ret)
153  return false;
154 
155  uint32_t crc = calculate_crc(buffer.begin(), buffer.end() - 1, type);
156  if (buffer[buffer.size() - 1] != crc) {
157  return false;
158  }
159 
160  memcpy(data, buffer.data(), len);
161  return true;
162  }
163 };
164 
165 class ESP8266Preferences : public ESPPreferences {
166  public:
167  uint32_t current_offset = 0;
168  uint32_t current_flash_offset = 0; // in words
169 
170  void setup() {
171  s_flash_storage = new uint32_t[ESP8266_FLASH_STORAGE_SIZE]; // NOLINT
172  ESP_LOGVV(TAG, "Loading preferences from flash...");
173 
174  {
175  InterruptLock lock;
176  spi_flash_read(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4);
177  }
178  }
179 
180  ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override {
181  uint32_t length_words = (length + 3) / 4;
182  if (in_flash) {
183  uint32_t start = current_flash_offset;
184  uint32_t end = start + length_words + 1;
185  if (end > ESP8266_FLASH_STORAGE_SIZE)
186  return {};
187  auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
188  pref->offset = start;
189  pref->type = type;
190  pref->length_words = length_words;
191  pref->in_flash = true;
192  current_flash_offset = end;
193  return {pref};
194  }
195 
196  uint32_t start = current_offset;
197  uint32_t end = start + length_words + 1;
198  bool in_normal = start < 96;
199  // Normal: offset 0-95 maps to RTC offset 32 - 127,
200  // Eboot: offset 96-127 maps to RTC offset 0 - 31 words
201  if (in_normal && end > 96) {
202  // start is in normal but end is not -> switch to Eboot
203  current_offset = start = 96;
204  end = start + length_words + 1;
205  in_normal = false;
206  }
207 
208  if (end > 128) {
209  // Doesn't fit in data, return uninitialized preference obj.
210  return {};
211  }
212 
213  uint32_t rtc_offset = in_normal ? start + 32 : start - 96;
214 
215  auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
216  pref->offset = rtc_offset;
217  pref->type = type;
218  pref->length_words = length_words;
219  pref->in_flash = false;
220  current_offset += length_words + 1;
221  return pref;
222  }
223 
224  ESPPreferenceObject make_preference(size_t length, uint32_t type) override {
225 #ifdef USE_ESP8266_PREFERENCES_FLASH
226  return make_preference(length, type, true);
227 #else
228  return make_preference(length, type, false);
229 #endif
230  }
231 
232  bool sync() override {
233  if (!s_flash_dirty)
234  return true;
235  if (s_prevent_write)
236  return false;
237 
238  ESP_LOGD(TAG, "Saving preferences to flash...");
239  SpiFlashOpResult erase_res, write_res = SPI_FLASH_RESULT_OK;
240  {
241  InterruptLock lock;
242  erase_res = spi_flash_erase_sector(get_esp8266_flash_sector());
243  if (erase_res == SPI_FLASH_RESULT_OK) {
244  write_res = spi_flash_write(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4);
245  }
246  }
247  if (erase_res != SPI_FLASH_RESULT_OK) {
248  ESP_LOGE(TAG, "Erase ESP8266 flash failed!");
249  return false;
250  }
251  if (write_res != SPI_FLASH_RESULT_OK) {
252  ESP_LOGE(TAG, "Write ESP8266 flash failed!");
253  return false;
254  }
255 
256  s_flash_dirty = false;
257  return true;
258  }
259 
260  bool reset() override {
261  ESP_LOGD(TAG, "Cleaning up preferences in flash...");
262  SpiFlashOpResult erase_res;
263  {
264  InterruptLock lock;
265  erase_res = spi_flash_erase_sector(get_esp8266_flash_sector());
266  }
267  if (erase_res != SPI_FLASH_RESULT_OK) {
268  ESP_LOGE(TAG, "Erase ESP8266 flash failed!");
269  return false;
270  }
271 
272  // Protect flash from writing till restart
273  s_prevent_write = true;
274  return true;
275  }
276 };
277 
279  auto *pref = new ESP8266Preferences(); // NOLINT(cppcoreguidelines-owning-memory)
280  pref->setup();
281  global_preferences = pref;
282 }
283 void preferences_prevent_write(bool prevent) { s_prevent_write = prevent; }
284 
285 } // namespace esp8266
286 
287 ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
288 
289 } // namespace esphome
290 
291 #endif // USE_ESP8266
void setup()
uint16_t sync
Definition: sun_gtil2.cpp:14
uint32_t calculate_crc(It first, It last, uint32_t type)
Definition: preferences.cpp:70
ESPPreferences * global_preferences
uint8_t type
uint16_t reset
Definition: ina226.h:39
uint32_t _SPIFFS_end
Definition: preferences.cpp:58
Helper class to disable interrupts.
Definition: helpers.h:593
std::string size_t len
Definition: helpers.h:292
void preferences_prevent_write(bool prevent)
uint16_t length
Definition: tt21100.cpp:12
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
Definition: a01nyub.cpp:7
uint8_t end[39]
Definition: sun_gtil2.cpp:31
void setup_preferences()