esphome/esphome/components/esp8266/preferences.cpp
2021-09-22 13:55:49 +12:00

270 lines
7.7 KiB
C++

#ifdef USE_ESP8266
#include <c_types.h>
extern "C" {
#include "spi_flash.h"
}
#include "preferences.h"
#include <cstring>
#include "esphome/core/preferences.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/defines.h"
namespace esphome {
namespace esp8266 {
static const char *const TAG = "esp8266.preferences";
static bool s_prevent_write = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static uint32_t *s_flash_storage = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static bool s_flash_dirty = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static const uint32_t ESP_RTC_USER_MEM_START = 0x60001200;
#define ESP_RTC_USER_MEM ((uint32_t *) ESP_RTC_USER_MEM_START)
static const uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = 128;
static const uint32_t ESP_RTC_USER_MEM_SIZE_BYTES = ESP_RTC_USER_MEM_SIZE_WORDS * 4;
#ifdef USE_ESP8266_PREFERENCES_FLASH
static const uint32_t ESP8266_FLASH_STORAGE_SIZE = 128;
#else
static const uint32_t ESP8266_FLASH_STORAGE_SIZE = 64;
#endif
static inline bool esp_rtc_user_mem_read(uint32_t index, uint32_t *dest) {
if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) {
return false;
}
*dest = ESP_RTC_USER_MEM[index]; // NOLINT(performance-no-int-to-ptr)
return true;
}
static inline bool esp_rtc_user_mem_write(uint32_t index, uint32_t value) {
if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) {
return false;
}
if (index < 32 && s_prevent_write) {
return false;
}
auto *ptr = &ESP_RTC_USER_MEM[index]; // NOLINT(performance-no-int-to-ptr)
*ptr = value;
return true;
}
extern "C" uint32_t _SPIFFS_end; // NOLINT
static const uint32_t get_esp8266_flash_sector() {
union {
uint32_t *ptr;
uint32_t uint;
} data{};
data.ptr = &_SPIFFS_end;
return (data.uint - 0x40200000) / SPI_FLASH_SEC_SIZE;
}
static const uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; }
template<class It> uint32_t calculate_crc(It first, It last, uint32_t type) {
uint32_t crc = type;
while (first != last) {
crc ^= (*first++ * 2654435769UL) >> 1;
}
return crc;
}
static bool save_to_flash(size_t offset, const uint32_t *data, size_t len) {
for (uint32_t i = 0; i < len; i++) {
uint32_t j = offset + i;
if (j >= ESP8266_FLASH_STORAGE_SIZE)
return false;
uint32_t v = data[i];
uint32_t *ptr = &s_flash_storage[j];
if (*ptr != v)
s_flash_dirty = true;
*ptr = v;
}
return true;
}
static bool load_from_flash(size_t offset, uint32_t *data, size_t len) {
for (size_t i = 0; i < len; i++) {
uint32_t j = offset + i;
if (j >= ESP8266_FLASH_STORAGE_SIZE)
return false;
data[i] = s_flash_storage[j];
}
return true;
}
static bool save_to_rtc(size_t offset, const uint32_t *data, size_t len) {
for (uint32_t i = 0; i < len; i++)
if (!esp_rtc_user_mem_write(offset + i, data[i]))
return false;
return true;
}
static bool load_from_rtc(size_t offset, uint32_t *data, size_t len) {
for (uint32_t i = 0; i < len; i++)
if (!esp_rtc_user_mem_read(offset + i, &data[i]))
return false;
return true;
}
class ESP8266PreferenceBackend : public ESPPreferenceBackend {
public:
size_t offset = 0;
uint32_t type = 0;
bool in_flash = false;
size_t length_words = 0;
bool save(const uint8_t *data, size_t len) override {
if ((len + 3) / 4 != length_words) {
return false;
}
std::vector<uint32_t> buffer;
buffer.resize(length_words + 1);
memcpy(buffer.data(), data, len);
buffer[buffer.size() - 1] = calculate_crc(buffer.begin(), buffer.end() - 1, type);
if (in_flash) {
return save_to_flash(offset, buffer.data(), buffer.size());
} else {
return save_to_rtc(offset, buffer.data(), buffer.size());
}
}
bool load(uint8_t *data, size_t len) override {
if ((len + 3) / 4 != length_words) {
return false;
}
std::vector<uint32_t> buffer;
buffer.resize(length_words + 1);
bool ret;
if (in_flash) {
ret = load_from_flash(offset, buffer.data(), buffer.size());
} else {
ret = load_from_rtc(offset, buffer.data(), buffer.size());
}
if (!ret)
return false;
uint32_t crc = calculate_crc(buffer.begin(), buffer.end() - 1, type);
if (buffer[buffer.size() - 1] != crc) {
return false;
}
memcpy(data, buffer.data(), len);
return true;
}
};
class ESP8266Preferences : public ESPPreferences {
public:
uint32_t current_offset = 0;
uint32_t current_flash_offset = 0; // in words
void setup() {
s_flash_storage = new uint32_t[ESP8266_FLASH_STORAGE_SIZE]; // NOLINT
ESP_LOGVV(TAG, "Loading preferences from flash...");
{
InterruptLock lock;
spi_flash_read(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4);
}
}
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override {
uint32_t length_words = (length + 3) / 4;
if (in_flash) {
uint32_t start = current_flash_offset;
uint32_t end = start + length_words + 1;
if (end > ESP8266_FLASH_STORAGE_SIZE)
return {};
auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
pref->offset = start;
pref->type = type;
pref->length_words = length_words;
pref->in_flash = true;
current_flash_offset = end;
return {pref};
}
uint32_t start = current_offset;
uint32_t end = start + length_words + 1;
bool in_normal = start < 96;
// Normal: offset 0-95 maps to RTC offset 32 - 127,
// Eboot: offset 96-127 maps to RTC offset 0 - 31 words
if (in_normal && end > 96) {
// start is in normal but end is not -> switch to Eboot
current_offset = start = 96;
end = start + length_words + 1;
in_normal = false;
}
if (end > 128) {
// Doesn't fit in data, return uninitialized preference obj.
return {};
}
uint32_t rtc_offset = in_normal ? start + 32 : start - 96;
auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
pref->offset = rtc_offset;
pref->type = type;
pref->length_words = length_words;
pref->in_flash = false;
current_offset += length_words + 1;
return pref;
}
ESPPreferenceObject make_preference(size_t length, uint32_t type) override {
#ifdef USE_ESP8266_PREFERENCES_FLASH
return make_preference(length, type, true);
#else
return make_preference(length, type, false);
#endif
}
bool sync() override {
if (!s_flash_dirty)
return true;
if (s_prevent_write)
return false;
ESP_LOGD(TAG, "Saving preferences to flash...");
SpiFlashOpResult erase_res, write_res = SPI_FLASH_RESULT_OK;
{
InterruptLock lock;
erase_res = spi_flash_erase_sector(get_esp8266_flash_sector());
if (erase_res == SPI_FLASH_RESULT_OK) {
write_res = spi_flash_write(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4);
}
}
if (erase_res != SPI_FLASH_RESULT_OK) {
ESP_LOGV(TAG, "Erase ESP8266 flash failed!");
return false;
}
if (write_res != SPI_FLASH_RESULT_OK) {
ESP_LOGV(TAG, "Write ESP8266 flash failed!");
return false;
}
s_flash_dirty = false;
return true;
}
};
void setup_preferences() {
auto *pref = new ESP8266Preferences(); // NOLINT(cppcoreguidelines-owning-memory)
pref->setup();
global_preferences = pref;
}
void preferences_prevent_write(bool prevent) { s_prevent_write = prevent; }
} // namespace esp8266
ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace esphome
#endif // USE_ESP8266