Initial public version

This commit is contained in:
Ivan Grokhotkov
2016-08-17 23:08:22 +08:00
commit bd6ea4393c
628 changed files with 224814 additions and 0 deletions
@@ -0,0 +1,79 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef compressed_enum_table_h
#define compressed_enum_table_h
#include <cstdint>
#include <cassert>
#include <type_traits>
template<typename Tenum, size_t Nbits, size_t Nitems>
class CompressedEnumTable
{
public:
uint32_t* data()
{
return mData;
}
const uint32_t* data() const
{
return mData;
}
Tenum get(size_t index)
{
assert(index >= 0 && index < Nitems);
size_t wordIndex = index / ITEMS_PER_WORD;
size_t offset = (index % ITEMS_PER_WORD) * Nbits;
return static_cast<Tenum>((mData[wordIndex] >> offset) & VALUE_MASK);
}
void set(size_t index, Tenum val)
{
assert(index >= 0 && index < Nitems);
size_t wordIndex = index / ITEMS_PER_WORD;
size_t offset = (index % ITEMS_PER_WORD) * Nbits;
uint32_t v = static_cast<uint32_t>(val) << offset;
mData[wordIndex] = (mData[wordIndex] & ~(VALUE_MASK << offset)) | v;
}
static constexpr size_t getWordIndex(size_t index)
{
return index / ITEMS_PER_WORD;
}
static constexpr size_t byteSize()
{
return WORD_COUNT * 4;
}
static constexpr size_t count()
{
return Nitems;
}
protected:
static_assert(32 % Nbits == 0, "Nbits must divide 32");
static const size_t ITEMS_PER_WORD = 32 / Nbits;
static const size_t WORD_COUNT = ( Nbits * Nitems + 31 ) / 32;
static const uint32_t VALUE_MASK = (1 << Nbits) - 1;
uint32_t mData[WORD_COUNT];
};
#endif /* compressed_enum_table_h */
+248
View File
@@ -0,0 +1,248 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef intrusive_list_h
#define intrusive_list_h
#include <cassert>
template <typename T>
class intrusive_list;
template <typename T>
class intrusive_list_node
{
protected:
friend class intrusive_list<T>;
T* mPrev = nullptr;
T* mNext = nullptr;
};
template <typename T>
class intrusive_list
{
typedef intrusive_list_node<T> TNode;
static_assert(std::is_base_of<TNode, T>::value, "");
public:
class iterator : public std::iterator<std::forward_iterator_tag, T>
{
public:
iterator() : mPos(nullptr) {}
iterator(T* pos) : mPos(pos) {}
iterator operator++(int)
{
auto result = *this;
mPos = mPos->mNext;
return result;
}
iterator operator--(int)
{
auto result = *this;
mPos = mPos->mPrev;
return result;
}
iterator& operator++()
{
mPos = mPos->mNext;
return *this;
}
iterator& operator--()
{
mPos = mPos->mPrev;
return *this;
}
bool operator==(const iterator& other) const
{
return mPos == other.mPos;
}
bool operator!=(const iterator& other) const
{
return !(*this == other);
}
T& operator*()
{
return *mPos;
}
const T& operator*() const
{
return *mPos;
}
T* operator->()
{
return mPos;
}
const T* operator->() const
{
return mPos;
}
operator T*()
{
return mPos;
}
operator const T*() const
{
return mPos;
}
protected:
T* mPos;
};
void push_back(T* node)
{
if (mLast) {
mLast->mNext = node;
}
node->mPrev = mLast;
node->mNext = nullptr;
mLast = node;
if (mFirst == nullptr) {
mFirst = node;
}
++mSize;
}
void push_front(T* node)
{
node->mPrev = nullptr;
node->mNext = mFirst;
if (mFirst) {
mFirst->mPrev = node;
}
mFirst = node;
if (mLast == nullptr) {
mLast = node;
}
++mSize;
}
T& back()
{
return *mLast;
}
const T& back() const
{
return *mLast;
}
T& front()
{
return *mFirst;
}
const T& front() const
{
return *mFirst;
}
void pop_front()
{
erase(mFirst);
}
void pop_back()
{
erase(mLast);
}
void insert(iterator next, T* node)
{
if (static_cast<T*>(next) == nullptr) {
push_back(node);
} else {
auto prev = next->mPrev;
if (!prev) {
push_front(node);
} else {
prev->mNext = node;
next->mPrev = node;
node->mNext = next;
node->mPrev = &(*prev);
++mSize;
}
}
}
void erase(iterator it)
{
auto prev = it->mPrev;
auto next = it->mNext;
if (prev) {
prev->mNext = next;
} else {
mFirst = next;
}
if (next) {
next->mPrev = prev;
} else {
mLast = prev;
}
--mSize;
}
iterator begin()
{
return iterator(mFirst);
}
iterator end()
{
return iterator(nullptr);
}
size_t size() const
{
return mSize;
}
bool empty() const
{
return mSize == 0;
}
void clear()
{
while (mFirst) {
erase(mFirst);
}
}
protected:
T* mFirst = nullptr;
T* mLast = nullptr;
size_t mSize = 0;
};
#endif /* intrusive_list_h */
+25
View File
@@ -0,0 +1,25 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef nvs_hpp
#define nvs_hpp
#include <memory>
#include "nvs.h"
#include "nvs_types.hpp"
#include "nvs_page.hpp"
#include "nvs_pagemanager.hpp"
#include "nvs_storage.hpp"
#endif /* nvs_hpp */
+278
View File
@@ -0,0 +1,278 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "nvs.hpp"
#include "nvs_flash.h"
#include "nvs_storage.hpp"
#include "intrusive_list.h"
#include "nvs_platform.hpp"
class HandleEntry : public intrusive_list_node<HandleEntry>
{
public:
HandleEntry(){}
HandleEntry(nvs_handle handle, bool readOnly, uint8_t nsIndex) :
mHandle(handle),
mReadOnly(readOnly),
mNsIndex(nsIndex)
{
}
nvs_handle mHandle;
uint8_t mReadOnly;
uint8_t mNsIndex;
};
#ifdef ESP_PLATFORM
SemaphoreHandle_t nvs::Lock::mSemaphore = NULL;
#endif
using namespace std;
using namespace nvs;
static intrusive_list<HandleEntry> s_nvs_handles;
static uint32_t s_nvs_next_handle = 1;
static nvs::Storage s_nvs_storage;
extern "C" esp_err_t nvs_flash_init(uint32_t baseSector, uint32_t sectorCount)
{
Lock::init();
Lock lock;
NVS_DEBUGV("%s %d %d\r\n", __func__, baseSector, sectorCount);
return s_nvs_storage.init(baseSector, sectorCount);
}
static esp_err_t nvs_find_ns_handle(nvs_handle handle, HandleEntry& entry)
{
auto it = find_if(begin(s_nvs_handles), end(s_nvs_handles), [=](HandleEntry& e) -> bool {
return e.mHandle == handle;
});
if (it == end(s_nvs_handles)) {
return ESP_ERR_NVS_INVALID_HANDLE;
}
entry = *it;
return ESP_OK;
}
extern "C" esp_err_t nvs_open(const char* name, nvs_open_mode open_mode, nvs_handle *out_handle)
{
Lock lock;
NVS_DEBUGV("%s %s %d\r\n", __func__, name, open_mode);
uint8_t nsIndex;
esp_err_t err = s_nvs_storage.createOrOpenNamespace(name, open_mode == NVS_READWRITE, nsIndex);
if (err != ESP_OK) {
return err;
}
uint32_t handle = s_nvs_next_handle;
++s_nvs_next_handle;
*out_handle = handle;
s_nvs_handles.push_back(new HandleEntry(handle, open_mode==NVS_READONLY, nsIndex));
return ESP_OK;
}
extern "C" void nvs_close(nvs_handle handle)
{
Lock lock;
NVS_DEBUGV("%s %d\r\n", __func__, handle);
auto it = find_if(begin(s_nvs_handles), end(s_nvs_handles), [=](HandleEntry& e) -> bool {
return e.mHandle == handle;
});
if (it == end(s_nvs_handles)) {
return;
}
s_nvs_handles.erase(it);
}
template<typename T>
static esp_err_t nvs_set(nvs_handle handle, const char* key, T value)
{
Lock lock;
NVS_DEBUGV("%s %s %d %d\r\n", __func__, key, sizeof(T), (uint32_t) value);
HandleEntry entry;
auto err = nvs_find_ns_handle(handle, entry);
if (err != ESP_OK) {
return err;
}
if (entry.mReadOnly) {
return ESP_ERR_NVS_READ_ONLY;
}
return s_nvs_storage.writeItem(entry.mNsIndex, key, value);
}
extern "C" esp_err_t nvs_set_i8 (nvs_handle handle, const char* key, int8_t value)
{
return nvs_set(handle, key, value);
}
extern "C" esp_err_t nvs_set_u8 (nvs_handle handle, const char* key, uint8_t value)
{
return nvs_set(handle, key, value);
}
extern "C" esp_err_t nvs_set_i16 (nvs_handle handle, const char* key, int16_t value)
{
return nvs_set(handle, key, value);
}
extern "C" esp_err_t nvs_set_u16 (nvs_handle handle, const char* key, uint16_t value)
{
return nvs_set(handle, key, value);
}
extern "C" esp_err_t nvs_set_i32 (nvs_handle handle, const char* key, int32_t value)
{
return nvs_set(handle, key, value);
}
extern "C" esp_err_t nvs_set_u32 (nvs_handle handle, const char* key, uint32_t value)
{
return nvs_set(handle, key, value);
}
extern "C" esp_err_t nvs_set_i64 (nvs_handle handle, const char* key, int64_t value)
{
return nvs_set(handle, key, value);
}
extern "C" esp_err_t nvs_set_u64 (nvs_handle handle, const char* key, uint64_t value)
{
return nvs_set(handle, key, value);
}
extern "C" esp_err_t nvs_commit(nvs_handle handle)
{
Lock lock;
// no-op for now, to be used when intermediate cache is added
HandleEntry entry;
return nvs_find_ns_handle(handle, entry);
}
extern "C" esp_err_t nvs_set_str(nvs_handle handle, const char* key, const char* value)
{
Lock lock;
NVS_DEBUGV("%s %s %s\r\n", __func__, key, value);
HandleEntry entry;
auto err = nvs_find_ns_handle(handle, entry);
if (err != ESP_OK) {
return err;
}
return s_nvs_storage.writeItem(entry.mNsIndex, nvs::ItemType::SZ, key, value, strlen(value) + 1);
}
extern "C" esp_err_t nvs_set_blob(nvs_handle handle, const char* key, const void* value, size_t length)
{
Lock lock;
NVS_DEBUGV("%s %s %d\r\n", __func__, key, length);
HandleEntry entry;
auto err = nvs_find_ns_handle(handle, entry);
if (err != ESP_OK) {
return err;
}
return s_nvs_storage.writeItem(entry.mNsIndex, nvs::ItemType::BLOB, key, value, length);
}
template<typename T>
static esp_err_t nvs_get(nvs_handle handle, const char* key, T* out_value)
{
Lock lock;
NVS_DEBUGV("%s %s %d\r\n", __func__, key, sizeof(T));
HandleEntry entry;
auto err = nvs_find_ns_handle(handle, entry);
if (err != ESP_OK) {
return err;
}
return s_nvs_storage.readItem(entry.mNsIndex, key, *out_value);
}
extern "C" esp_err_t nvs_get_i8 (nvs_handle handle, const char* key, int8_t* out_value)
{
return nvs_get(handle, key, out_value);
}
extern "C" esp_err_t nvs_get_u8 (nvs_handle handle, const char* key, uint8_t* out_value)
{
return nvs_get(handle, key, out_value);
}
extern "C" esp_err_t nvs_get_i16 (nvs_handle handle, const char* key, int16_t* out_value)
{
return nvs_get(handle, key, out_value);
}
extern "C" esp_err_t nvs_get_u16 (nvs_handle handle, const char* key, uint16_t* out_value)
{
return nvs_get(handle, key, out_value);
}
extern "C" esp_err_t nvs_get_i32 (nvs_handle handle, const char* key, int32_t* out_value)
{
return nvs_get(handle, key, out_value);
}
extern "C" esp_err_t nvs_get_u32 (nvs_handle handle, const char* key, uint32_t* out_value)
{
return nvs_get(handle, key, out_value);
}
extern "C" esp_err_t nvs_get_i64 (nvs_handle handle, const char* key, int64_t* out_value)
{
return nvs_get(handle, key, out_value);
}
extern "C" esp_err_t nvs_get_u64 (nvs_handle handle, const char* key, uint64_t* out_value)
{
return nvs_get(handle, key, out_value);
}
static esp_err_t nvs_get_str_or_blob(nvs_handle handle, nvs::ItemType type, const char* key, void* out_value, size_t* length)
{
Lock lock;
NVS_DEBUGV("%s %s\r\n", __func__, key);
HandleEntry entry;
auto err = nvs_find_ns_handle(handle, entry);
if (err != ESP_OK) {
return err;
}
size_t dataSize;
err = s_nvs_storage.getItemDataSize(entry.mNsIndex, type, key, dataSize);
if (err != ESP_OK) {
return err;
}
if (length != nullptr && out_value == nullptr) {
*length = dataSize;
return ESP_OK;
}
if (length == nullptr || *length < dataSize) {
return ESP_ERR_NVS_INVALID_LENGTH;
}
return s_nvs_storage.readItem(entry.mNsIndex, type, key, out_value, dataSize);
}
extern "C" esp_err_t nvs_get_str(nvs_handle handle, const char* key, char* out_value, size_t* length)
{
return nvs_get_str_or_blob(handle, nvs::ItemType::SZ, key, out_value, length);
}
extern "C" esp_err_t nvs_get_blob(nvs_handle handle, const char* key, void* out_value, size_t* length)
{
return nvs_get_str_or_blob(handle, nvs::ItemType::BLOB, key, out_value, length);
}
+689
View File
@@ -0,0 +1,689 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "nvs_page.hpp"
#if defined(ESP_PLATFORM)
#include <rom/crc.h>
#else
#include "crc.h"
#endif
namespace nvs
{
uint32_t Page::Header::calculateCrc32()
{
return crc32_le(0xffffffff,
reinterpret_cast<uint8_t*>(this) + offsetof(Header, mSeqNumber),
offsetof(Header, mCrc32) - offsetof(Header, mSeqNumber));
}
esp_err_t Page::load(uint32_t sectorNumber)
{
mBaseAddress = sectorNumber * SEC_SIZE;
mUsedEntryCount = 0;
mErasedEntryCount = 0;
Header header;
auto rc = spi_flash_read(mBaseAddress, reinterpret_cast<uint32_t*>(&header), sizeof(header));
if (rc != ESP_OK) {
mState = PageState::INVALID;
return rc;
}
if (header.mState == PageState::UNINITIALIZED) {
mState = header.mState;
// check if the whole page is really empty
// reading the whole page takes ~40 times less than erasing it
uint32_t line[8];
for (uint32_t i = 0; i < SPI_FLASH_SEC_SIZE; i += sizeof(line)) {
rc = spi_flash_read(mBaseAddress + i, line, sizeof(line));
if (rc != ESP_OK) {
mState = PageState::INVALID;
return rc;
}
if (std::any_of(line, line + 4, [](uint32_t val) -> bool { return val != 0xffffffff; })) {
// page isn't as empty after all, mark it as corrupted
mState = PageState::CORRUPT;
break;
}
}
}
else if (header.mCrc32 != header.calculateCrc32()) {
header.mState = PageState::CORRUPT;
}
else {
mState = header.mState;
mSeqNumber = header.mSeqNumber;
}
switch (mState) {
case PageState::UNINITIALIZED:
break;
case PageState::FULL:
case PageState::ACTIVE:
case PageState::FREEING:
mLoadEntryTable();
break;
default:
mState = PageState::CORRUPT;
break;
}
return ESP_OK;
}
esp_err_t Page::writeEntry(const Item& item)
{
auto rc = spi_flash_write(getEntryAddress(mNextFreeEntry), reinterpret_cast<const uint32_t*>(&item), sizeof(item));
if (rc != ESP_OK) {
mState = PageState::INVALID;
return rc;
}
auto err = alterEntryState(mNextFreeEntry, EntryState::WRITTEN);
if (err != ESP_OK) {
return err;
}
if (mNextFreeEntry == 0) {
mFirstUsedEntry = 0;
}
++mUsedEntryCount;
if (++mNextFreeEntry == ENTRY_COUNT) {
alterPageState(PageState::FULL);
}
return ESP_OK;
}
esp_err_t Page::writeItem(uint8_t nsIndex, ItemType datatype, const char* key, const void* data, size_t dataSize)
{
Item item;
esp_err_t err;
if (mState == PageState::UNINITIALIZED) {
err = initialize();
if (err != ESP_OK) {
return err;
}
}
if (mState == PageState::FULL) {
return ESP_ERR_NVS_PAGE_FULL;
}
const size_t keySize = strlen(key);
if (keySize > Item::MAX_KEY_LENGTH) {
return ESP_ERR_NVS_KEY_TOO_LONG;
}
size_t totalSize = ENTRY_SIZE;
size_t entriesCount = 1;
if (datatype == ItemType::SZ || datatype == ItemType::BLOB) {
size_t roundedSize = (dataSize + ENTRY_SIZE - 1) & ~(ENTRY_SIZE - 1);
totalSize += roundedSize;
entriesCount += roundedSize / ENTRY_SIZE;
}
// primitive types should fit into one entry
assert(totalSize == ENTRY_SIZE || datatype == ItemType::BLOB || datatype == ItemType::SZ);
if (mNextFreeEntry + entriesCount > ENTRY_COUNT) {
// page will not fit this amount of data
return ESP_ERR_NVS_PAGE_FULL;
}
// write first item
item.nsIndex = nsIndex;
item.datatype = datatype;
item.span = (totalSize + ENTRY_SIZE - 1) / ENTRY_SIZE;
item.reserved = 0xff;
std::fill_n(reinterpret_cast<uint32_t*>(item.key), sizeof(item.key) / 4, 0xffffffff);
std::fill_n(reinterpret_cast<uint32_t*>(item.data), sizeof(item.data) / 4, 0xffffffff);
strlcpy(item.key, key, Item::MAX_KEY_LENGTH + 1);
if (datatype != ItemType::SZ && datatype != ItemType::BLOB) {
memcpy(item.data, data, dataSize);
item.crc32 = item.calculateCrc32();
err = writeEntry(item);
if (err != ESP_OK) {
return err;
}
} else {
const uint8_t* src = reinterpret_cast<const uint8_t*>(data);
item.varLength.dataCrc32 = Item::calculateCrc32(src, dataSize);
item.varLength.dataSize = dataSize;
item.varLength.reserved2 = 0xffff;
item.crc32 = item.calculateCrc32();
err = writeEntry(item);
if (err != ESP_OK) {
return err;
}
size_t left = dataSize;
while (left != 0) {
size_t willWrite = Page::ENTRY_SIZE;
willWrite = (left < willWrite)?left:willWrite;
memcpy(item.rawData, src, willWrite);
src += willWrite;
left -= willWrite;
err = writeEntry(item);
if (err != ESP_OK) {
return err;
}
}
}
return ESP_OK;
}
esp_err_t Page::readItem(uint8_t nsIndex, ItemType datatype, const char* key, void* data, size_t dataSize)
{
size_t index = 0;
Item item;
esp_err_t rc = findItem(nsIndex, datatype, key, index, item);
if (rc != ESP_OK) {
return rc;
}
if (datatype != ItemType::SZ && datatype != ItemType::BLOB) {
if (dataSize != getAlignmentForType(datatype)) {
return ESP_ERR_NVS_TYPE_MISMATCH;
}
memcpy(data, item.data, dataSize);
return ESP_OK;
}
if (dataSize < static_cast<size_t>(item.varLength.dataSize)) {
return ESP_ERR_NVS_INVALID_LENGTH;
}
uint8_t* dst = reinterpret_cast<uint8_t*>(data);
size_t left = item.varLength.dataSize;
for (size_t i = index + 1; i < index + item.span; ++i) {
Item ditem;
rc = readEntry(i, ditem);
if (rc != ESP_OK) {
return rc;
}
size_t willCopy = ENTRY_SIZE;
willCopy = (left < willCopy)?left:willCopy;
memcpy(dst, ditem.rawData, willCopy);
left -= willCopy;
dst += willCopy;
}
if (Item::calculateCrc32(reinterpret_cast<uint8_t*>(data), item.varLength.dataSize) != item.varLength.dataCrc32) {
rc = eraseEntryAndSpan(index);
if (rc != ESP_OK) {
return rc;
}
return ESP_ERR_NVS_NOT_FOUND;
}
return ESP_OK;
}
esp_err_t Page::eraseItem(uint8_t nsIndex, ItemType datatype, const char* key)
{
size_t index = 0;
Item item;
esp_err_t rc = findItem(nsIndex, datatype, key, index, item);
if (rc != ESP_OK) {
return rc;
}
if (CachedFindInfo(nsIndex, datatype, key) == mFindInfo) {
invalidateCache();
}
return eraseEntryAndSpan(index);
}
esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key)
{
size_t index = 0;
Item item;
return findItem(nsIndex, datatype, key, index, item);
}
esp_err_t Page::eraseEntry(size_t index)
{
auto state = mEntryTable.get(index);
assert(state == EntryState::WRITTEN || state == EntryState::EMPTY);
auto rc = alterEntryState(index, EntryState::ERASED);
if (rc != ESP_OK) {
return rc;
}
return ESP_OK;
}
esp_err_t Page::eraseEntryAndSpan(size_t index)
{
auto state = mEntryTable.get(index);
assert(state == EntryState::WRITTEN || state == EntryState::EMPTY);
size_t span = 1;
if (state == EntryState::WRITTEN) {
Item item;
auto rc = readEntry(index, item);
if (rc != ESP_OK) {
return rc;
}
if (item.calculateCrc32() != item.crc32) {
rc = alterEntryState(index, EntryState::ERASED);
if (rc != ESP_OK) {
return rc;
}
} else {
span = item.span;
for (ptrdiff_t i = index + span - 1; i >= static_cast<ptrdiff_t>(index); --i) {
rc = alterEntryState(i, EntryState::ERASED);
if (rc != ESP_OK) {
return rc;
}
}
}
}
else {
auto rc = alterEntryState(index, EntryState::ERASED);
if (rc != ESP_OK) {
return rc;
}
}
if (index == mFirstUsedEntry) {
updateFirstUsedEntry(index, span);
}
mErasedEntryCount += span;
mUsedEntryCount -= span;
return ESP_OK;
}
void Page::updateFirstUsedEntry(size_t index, size_t span)
{
assert(index == mFirstUsedEntry);
mFirstUsedEntry = INVALID_ENTRY;
for (size_t i = index + span; i < mNextFreeEntry; ++i) {
if (mEntryTable.get(i) == EntryState::WRITTEN) {
mFirstUsedEntry = i;
break;
}
}
}
esp_err_t Page::moveItem(Page& other)
{
if (mFirstUsedEntry == INVALID_ENTRY) {
return ESP_ERR_NVS_NOT_FOUND;
}
if (mFindInfo.itemIndex() == mFirstUsedEntry) {
invalidateCache();
}
if (other.mState == PageState::UNINITIALIZED) {
auto err = other.initialize();
if (err != ESP_OK) {
return err;
}
}
Item entry;
auto err = readEntry(mFirstUsedEntry, entry);
if (err != ESP_OK) {
return err;
}
err = other.writeEntry(entry);
if (err != ESP_OK) {
return err;
}
err = eraseEntry(mFirstUsedEntry);
if (err != ESP_OK) {
return err;
}
size_t span = entry.span;
size_t end = mFirstUsedEntry + span;
assert(mFirstUsedEntry != INVALID_ENTRY || span == 1);
for (size_t i = mFirstUsedEntry + 1; i < end; ++i) {
readEntry(i, entry);
err = other.writeEntry(entry);
if (err != ESP_OK) {
return err;
}
err = eraseEntry(i);
if (err != ESP_OK) {
return err;
}
}
updateFirstUsedEntry(mFirstUsedEntry, span);
mErasedEntryCount += span;
mUsedEntryCount -= span;
return ESP_OK;
}
esp_err_t Page::mLoadEntryTable()
{
// for states where we actually care about data in the page, read entry state table
if (mState == PageState::ACTIVE ||
mState == PageState::FULL ||
mState == PageState::FREEING) {
auto rc = spi_flash_read(mBaseAddress + ENTRY_TABLE_OFFSET, mEntryTable.data(),
static_cast<uint32_t>(mEntryTable.byteSize()));
if (rc != ESP_OK) {
mState = PageState::INVALID;
return rc;
}
}
mErasedEntryCount = 0;
mUsedEntryCount = 0;
for (size_t i = 0; i < ENTRY_COUNT; ++i) {
auto s = mEntryTable.get(i);
if (s == EntryState::WRITTEN) {
if (mFirstUsedEntry == INVALID_ENTRY) {
mFirstUsedEntry = i;
}
++mUsedEntryCount;
} else if (s == EntryState::ERASED) {
++mErasedEntryCount;
}
}
// for PageState::ACTIVE, we may have more data written to this page
// as such, we need to figure out where the first unused entry is
if (mState == PageState::ACTIVE) {
for (size_t i = 0; i < ENTRY_COUNT; ++i) {
if (mEntryTable.get(i) == EntryState::EMPTY) {
mNextFreeEntry = i;
break;
}
}
// however, if power failed after some data was written into the entry.
// but before the entry state table was altered, the entry locacted via
// entry state table may actually be half-written.
// this is easy to check by reading EntryHeader (i.e. first word)
uint32_t entryAddress = mBaseAddress + ENTRY_DATA_OFFSET +
static_cast<uint32_t>(mNextFreeEntry) * ENTRY_SIZE;
uint32_t header;
auto rc = spi_flash_read(entryAddress, &header, sizeof(header));
if (rc != ESP_OK) {
mState = PageState::INVALID;
return rc;
}
if (header != 0xffffffff) {
auto err = alterEntryState(mNextFreeEntry, EntryState::ERASED);
if (err != ESP_OK) {
mState = PageState::INVALID;
return err;
}
++mNextFreeEntry;
}
// check that all variable-length items are written or erased fully
for (size_t i = 0; i < mNextFreeEntry; ++i) {
if (mEntryTable.get(i) == EntryState::ERASED) {
continue;
}
Item item;
auto err = readEntry(i, item);
if (err != ESP_OK) {
mState = PageState::INVALID;
return err;
}
if (item.crc32 != item.calculateCrc32()) {
err = eraseEntryAndSpan(i);
if (err != ESP_OK) {
mState = PageState::INVALID;
return err;
}
continue;
}
if (item.datatype != ItemType::BLOB && item.datatype != ItemType::SZ) {
continue;
}
size_t span = item.span;
bool needErase = false;
for (size_t j = i; j < i + span; ++j) {
if (mEntryTable.get(j) != EntryState::WRITTEN) {
needErase = true;
break;
}
}
if (needErase) {
eraseEntryAndSpan(i);
}
i += span - 1;
}
}
return ESP_OK;
}
esp_err_t Page::initialize()
{
assert(mState == PageState::UNINITIALIZED);
mState = PageState::ACTIVE;
Header header;
header.mState = mState;
header.mSeqNumber = mSeqNumber;
header.mCrc32 = header.calculateCrc32();
auto rc = spi_flash_write(mBaseAddress, reinterpret_cast<uint32_t*>(&header), sizeof(header));
if (rc != ESP_OK) {
mState = PageState::INVALID;
return rc;
}
mNextFreeEntry = 0;
std::fill_n(mEntryTable.data(), mEntryTable.byteSize() / sizeof(uint32_t), 0xffffffff);
invalidateCache();
return ESP_OK;
}
esp_err_t Page::alterEntryState(size_t index, EntryState state)
{
assert(index < ENTRY_COUNT);
mEntryTable.set(index, state);
size_t wordToWrite = mEntryTable.getWordIndex(index);
uint32_t word = mEntryTable.data()[wordToWrite];
auto rc = spi_flash_write(mBaseAddress + ENTRY_TABLE_OFFSET + static_cast<uint32_t>(wordToWrite) * 4, &word, 4);
if (rc != ESP_OK) {
mState = PageState::INVALID;
return rc;
}
return ESP_OK;
}
esp_err_t Page::alterPageState(PageState state)
{
auto rc = spi_flash_write(mBaseAddress, reinterpret_cast<uint32_t*>(&state), sizeof(state));
if (rc != ESP_OK) {
mState = PageState::INVALID;
return rc;
}
mState = (PageState) state;
return ESP_OK;
}
esp_err_t Page::readEntry(size_t index, Item& dst)
{
auto rc = spi_flash_read(getEntryAddress(index), reinterpret_cast<uint32_t*>(&dst), sizeof(dst));
if (rc != ESP_OK) {
return rc;
}
return ESP_OK;
}
esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key, size_t &itemIndex, Item& item)
{
if (mState == PageState::CORRUPT || mState == PageState::INVALID || mState == PageState::UNINITIALIZED) {
return ESP_ERR_NVS_NOT_FOUND;
}
CachedFindInfo findInfo(nsIndex, datatype, key);
if (mFindInfo == findInfo) {
itemIndex = mFindInfo.itemIndex();
}
size_t start = mFirstUsedEntry;
if (itemIndex > mFirstUsedEntry && itemIndex < ENTRY_COUNT) {
start = itemIndex;
}
size_t next;
for (size_t i = start; i < mNextFreeEntry; i = next) {
next = i + 1;
if (mEntryTable.get(i) != EntryState::WRITTEN) {
continue;
}
auto rc = readEntry(i, item);
if (rc != ESP_OK) {
mState = PageState::INVALID;
return rc;
}
auto crc32 = item.calculateCrc32();
if (item.crc32 != crc32) {
eraseEntryAndSpan(i);
continue;
}
if (item.datatype == ItemType::BLOB || item.datatype == ItemType::SZ) {
next = i + item.span;
}
if (nsIndex != NS_ANY && item.nsIndex != nsIndex) {
continue;
}
if (key != nullptr && strncmp(key, item.key, Item::MAX_KEY_LENGTH) != 0) {
continue;
}
if (datatype != ItemType::ANY && item.datatype != datatype) {
return ESP_ERR_NVS_TYPE_MISMATCH;
}
itemIndex = i;
findInfo.setItemIndex(static_cast<uint32_t>(itemIndex));
mFindInfo = findInfo;
return ESP_OK;
}
return ESP_ERR_NVS_NOT_FOUND;
}
esp_err_t Page::getSeqNumber(uint32_t& seqNumber) const
{
if (mState != PageState::UNINITIALIZED && mState != PageState::INVALID && mState != PageState::CORRUPT) {
seqNumber = mSeqNumber;
return ESP_OK;
}
return ESP_ERR_NVS_NOT_INITIALIZED;
}
esp_err_t Page::setSeqNumber(uint32_t seqNumber)
{
if (mState != PageState::UNINITIALIZED) {
return ESP_ERR_NVS_INVALID_STATE;
}
mSeqNumber = seqNumber;
return ESP_OK;
}
esp_err_t Page::erase()
{
auto sector = mBaseAddress / SPI_FLASH_SEC_SIZE;
auto rc = spi_flash_erase_sector(sector);
if (rc != ESP_OK) {
mState = PageState::INVALID;
return rc;
}
return load(sector);
}
esp_err_t Page::markFreeing()
{
if (mState != PageState::FULL && mState != PageState::ACTIVE) {
return ESP_ERR_NVS_INVALID_STATE;
}
return alterPageState(PageState::FREEING);
}
esp_err_t Page::markFull()
{
if (mState != PageState::ACTIVE) {
return ESP_ERR_NVS_INVALID_STATE;
}
return alterPageState(PageState::FULL);
}
void Page::invalidateCache()
{
mFindInfo = CachedFindInfo();
}
void Page::debugDump()
{
printf("state=%x addr=%x seq=%d\nfirstUsed=%d nextFree=%d used=%d erased=%d\n", mState, mBaseAddress, mSeqNumber, static_cast<int>(mFirstUsedEntry), static_cast<int>(mNextFreeEntry), mUsedEntryCount, mErasedEntryCount);
size_t skip = 0;
for (size_t i = 0; i < ENTRY_COUNT; ++i) {
printf("%3d: ", static_cast<int>(i));
EntryState state = mEntryTable.get(i);
if (state == EntryState::EMPTY) {
printf("E\n");
}
else if (state == EntryState::ERASED) {
printf("X\n");
}
else if (state == EntryState::WRITTEN) {
Item item;
readEntry(i, item);
if (skip == 0) {
printf("W ns=%2u type=%2u span=%3u key=\"%s\"\n", item.nsIndex, static_cast<unsigned>(item.datatype), item.span, item.key);
skip = item.span - 1;
}
else {
printf("D\n");
skip--;
}
}
}
}
} // namespace nvs
+246
View File
@@ -0,0 +1,246 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef nvs_page_hpp
#define nvs_page_hpp
#include "nvs.h"
#include "nvs_types.hpp"
#include <cstdint>
#include <type_traits>
#include <cstring>
#include <algorithm>
#include "esp_spi_flash.h"
#include "compressed_enum_table.hpp"
#include "intrusive_list.h"
namespace nvs
{
class CachedFindInfo
{
public:
CachedFindInfo() { }
CachedFindInfo(uint8_t nsIndex, ItemType type, const char* key) :
mKeyPtr(key),
mNsIndex(nsIndex),
mType(type)
{
}
bool operator==(const CachedFindInfo& other) const
{
return mKeyPtr == other.mKeyPtr && mType == other.mType && mNsIndex == other.mNsIndex;
}
void setItemIndex(uint32_t index)
{
mItemIndex = index;
}
uint32_t itemIndex() const
{
return mItemIndex;
}
protected:
uint32_t mItemIndex = 0;
const char* mKeyPtr = nullptr;
uint8_t mNsIndex = 0;
ItemType mType;
};
class Page : public intrusive_list_node<Page>
{
public:
static const uint32_t PSB_INIT = 0x1;
static const uint32_t PSB_FULL = 0x2;
static const uint32_t PSB_FREEING = 0x4;
static const uint32_t PSB_CORRUPT = 0x8;
static const uint32_t ESB_WRITTEN = 0x1;
static const uint32_t ESB_ERASED = 0x2;
static const uint32_t SEC_SIZE = SPI_FLASH_SEC_SIZE;
static const size_t ENTRY_SIZE = 32;
static const size_t ENTRY_COUNT = 126;
static const uint32_t INVALID_ENTRY = 0xffffffff;
static const uint8_t NS_INDEX = 0;
static const uint8_t NS_ANY = 255;
enum class PageState : uint32_t {
// All bits set, default state after flash erase. Page has not been initialized yet.
UNINITIALIZED = 0xffffffff,
// Page is initialized, and will accept writes.
ACTIVE = UNINITIALIZED & ~PSB_INIT,
// Page is marked as full and will not accept new writes.
FULL = ACTIVE & ~PSB_FULL,
// Data is being moved from this page to a new one.
FREEING = FULL & ~PSB_FREEING,
// Page was found to be in a corrupt and unrecoverable state.
// Instead of being erased immediately, it will be kept for diagnostics and data recovery.
// It will be erased once we run out out free pages.
CORRUPT = FREEING & ~PSB_CORRUPT,
// Page object wasn't loaded from flash memory
INVALID = 0
};
PageState state() const
{
return mState;
}
esp_err_t load(uint32_t sectorNumber);
esp_err_t getSeqNumber(uint32_t& seqNumber) const;
esp_err_t setSeqNumber(uint32_t seqNumber);
esp_err_t writeItem(uint8_t nsIndex, ItemType datatype, const char* key, const void* data, size_t dataSize);
esp_err_t readItem(uint8_t nsIndex, ItemType datatype, const char* key, void* data, size_t dataSize);
esp_err_t eraseItem(uint8_t nsIndex, ItemType datatype, const char* key);
esp_err_t findItem(uint8_t nsIndex, ItemType datatype, const char* key);
esp_err_t findItem(uint8_t nsIndex, ItemType datatype, const char* key, size_t &itemIndex, Item& item);
template<typename T>
esp_err_t writeItem(uint8_t nsIndex, const char* key, const T& value)
{
return writeItem(nsIndex, itemTypeOf(value), key, &value, sizeof(value));
}
template<typename T>
esp_err_t readItem(uint8_t nsIndex, const char* key, T& value)
{
return readItem(nsIndex, itemTypeOf(value), key, &value, sizeof(value));
}
template<typename T>
esp_err_t eraseItem(uint8_t nsIndex, const char* key)
{
return eraseItem(nsIndex, itemTypeOf<T>(), key);
}
size_t getUsedEntryCount() const
{
return mUsedEntryCount;
}
size_t getErasedEntryCount() const
{
return mErasedEntryCount;
}
esp_err_t markFull();
esp_err_t markFreeing();
esp_err_t moveItem(Page& other);
esp_err_t erase();
void invalidateCache();
void debugDump();
protected:
class Header {
public:
Header() {
std::fill_n(mReserved, sizeof(mReserved)/sizeof(mReserved[0]), UINT32_MAX);
}
PageState mState; // page state
uint32_t mSeqNumber; // sequence number of this page
uint32_t mReserved[5]; // unused, must be 0xffffffff
uint32_t mCrc32; // crc of everything except mState
uint32_t calculateCrc32();
};
enum class EntryState {
EMPTY = 0x3, // 0b11, default state after flash erase
WRITTEN = EMPTY & ~ESB_WRITTEN, // entry was written
ERASED = WRITTEN & ~ESB_ERASED, // entry was written and then erased
INVALID = 0x4 // entry is in inconsistent state (write started but ESB_WRITTEN has not been set yet)
};
esp_err_t mLoadEntryTable();
esp_err_t initialize();
esp_err_t alterEntryState(size_t index, EntryState state);
esp_err_t alterPageState(PageState state);
esp_err_t readEntry(size_t index, Item& dst);
esp_err_t writeEntry(const Item& item);
esp_err_t eraseEntry(size_t index);
esp_err_t eraseEntryAndSpan(size_t index);
void updateFirstUsedEntry(size_t index, size_t span);
static constexpr size_t getAlignmentForType(ItemType type)
{
return static_cast<uint8_t>(type) & 0x0f;
}
uint32_t getEntryAddress(size_t entry)
{
assert(entry < ENTRY_COUNT);
return mBaseAddress + ENTRY_DATA_OFFSET + static_cast<uint32_t>(entry) * ENTRY_SIZE;
}
protected:
uint32_t mBaseAddress = 0;
PageState mState = PageState::INVALID;
uint32_t mSeqNumber = UINT32_MAX;
typedef CompressedEnumTable<EntryState, 2, ENTRY_COUNT> TEntryTable;
TEntryTable mEntryTable;
size_t mNextFreeEntry = INVALID_ENTRY;
size_t mFirstUsedEntry = INVALID_ENTRY;
uint16_t mUsedEntryCount = 0;
uint16_t mErasedEntryCount = 0;
CachedFindInfo mFindInfo;
static const uint32_t HEADER_OFFSET = 0;
static const uint32_t ENTRY_TABLE_OFFSET = HEADER_OFFSET + 32;
static const uint32_t ENTRY_DATA_OFFSET = ENTRY_TABLE_OFFSET + 32;
static_assert(sizeof(Header) == 32, "header size must be 32 bytes");
static_assert(ENTRY_TABLE_OFFSET % 32 == 0, "entry table offset should be aligned");
static_assert(ENTRY_DATA_OFFSET % 32 == 0, "entry data offset should be aligned");
}; // class Page
} // namespace nvs
#endif /* nvs_page_hpp */
@@ -0,0 +1,137 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "nvs_pagemanager.hpp"
namespace nvs
{
esp_err_t PageManager::load(uint32_t baseSector, uint32_t sectorCount)
{
mBaseSector = baseSector;
mPageCount = sectorCount;
mPageList.clear();
mFreePageList.clear();
mPages.reset(new Page[sectorCount]);
for (uint32_t i = 0; i < sectorCount; ++i) {
auto err = mPages[i].load(baseSector + i);
if (err != ESP_OK) {
return err;
}
uint32_t seqNumber;
if (mPages[i].getSeqNumber(seqNumber) != ESP_OK) {
mFreePageList.push_back(&mPages[i]);
} else {
auto pos = std::find_if(std::begin(mPageList), std::end(mPageList), [=](const Page& page) -> bool {
uint32_t otherSeqNumber;
return page.getSeqNumber(otherSeqNumber) == ESP_OK && otherSeqNumber > seqNumber;
});
if (pos == mPageList.end()) {
mPageList.push_back(&mPages[i]);
} else {
mPageList.insert(pos, &mPages[i]);
}
}
}
if (mPageList.empty()) {
mSeqNumber = 0;
return activatePage();
}
else {
uint32_t lastSeqNo;
assert(mPageList.back().getSeqNumber(lastSeqNo) == ESP_OK);
mSeqNumber = lastSeqNo + 1;
}
return ESP_OK;
}
esp_err_t PageManager::requestNewPage()
{
if (mFreePageList.empty()) {
return ESP_ERR_NVS_INVALID_STATE;
}
// do we have at least two free pages? in that case no erasing is required
if (mFreePageList.size() >= 2) {
return activatePage();
}
// find the page with the higest number of erased items
TPageListIterator maxErasedItemsPageIt;
size_t maxErasedItems = 0;
for (auto it = begin(); it != end(); ++it) {
auto erased = it->getErasedEntryCount();
if (erased > maxErasedItems) {
maxErasedItemsPageIt = it;
maxErasedItems = erased;
}
}
if (maxErasedItems == 0) {
return ESP_ERR_NVS_NOT_ENOUGH_SPACE;
}
esp_err_t err = activatePage();
if (err != ESP_OK) {
return err;
}
Page* newPage = &mPageList.back();
Page* erasedPage = maxErasedItemsPageIt;
err = erasedPage->markFreeing();
if (err != ESP_OK) {
return err;
}
while (true) {
err = erasedPage->moveItem(*newPage);
if (err == ESP_ERR_NVS_NOT_FOUND) {
break;
} else if (err != ESP_OK) {
return err;
}
}
err = erasedPage->erase();
if (err != ESP_OK) {
return err;
}
mPageList.erase(maxErasedItemsPageIt);
mFreePageList.push_back(erasedPage);
return ESP_OK;
}
esp_err_t PageManager::activatePage()
{
if (mFreePageList.empty()) {
return ESP_ERR_NVS_NOT_ENOUGH_SPACE;
}
Page* p = &mFreePageList.front();
if (p->state() == Page::PageState::CORRUPT) {
auto err = p->erase();
if (err != ESP_OK) {
return err;
}
}
mFreePageList.pop_front();
mPageList.push_back(p);
p->setSeqNumber(mSeqNumber);
++mSeqNumber;
return ESP_OK;
}
} // namespace nvs
@@ -0,0 +1,70 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef nvs_pagemanager_hpp
#define nvs_pagemanager_hpp
#include <memory>
#include <list>
#include "nvs_types.hpp"
#include "nvs_page.hpp"
#include "nvs_pagemanager.hpp"
#include "intrusive_list.h"
namespace nvs
{
class PageManager
{
using TPageList = intrusive_list<Page>;
using TPageListIterator = TPageList::iterator;
public:
PageManager() {}
esp_err_t load(uint32_t baseSector, uint32_t sectorCount);
TPageListIterator begin()
{
return mPageList.begin();
}
TPageListIterator end()
{
return mPageList.end();
}
Page& back()
{
return mPageList.back();
}
esp_err_t requestNewPage();
protected:
friend class Iterator;
esp_err_t activatePage();
TPageList mPageList;
TPageList mFreePageList;
std::unique_ptr<Page[]> mPages;
uint32_t mBaseSector;
uint32_t mPageCount;
uint32_t mSeqNumber;
}; // class PageManager
} // namespace nvs
#endif /* nvs_pagemanager_hpp */
+84
View File
@@ -0,0 +1,84 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef nvs_platform_h
#define nvs_platform_h
#ifdef ESP_PLATFORM
#define NVS_DEBUGV(...) ets_printf(__VA_ARGS__)
#include "rom/ets_sys.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
namespace nvs
{
class Lock
{
public:
Lock()
{
assert(mSemaphore);
xSemaphoreTake(mSemaphore, portMAX_DELAY);
}
~Lock()
{
assert(mSemaphore);
xSemaphoreGive(mSemaphore);
}
static esp_err_t init()
{
assert(mSemaphore == nullptr);
mSemaphore = xSemaphoreCreateMutex();
if (!mSemaphore) {
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}
static void uninit()
{
vSemaphoreDelete(mSemaphore);
mSemaphore = nullptr;
}
static SemaphoreHandle_t mSemaphore;
};
} // namespace nvs
#else // ESP_PLATFORM
#define NVS_DEBUGV(...) printf(__VA_ARGS__)
namespace nvs
{
class Lock
{
public:
Lock() { }
~Lock() { }
static void init() {}
static void uninit() {}
};
} // namespace nvs
#endif // ESP_PLATFORM
#ifndef CONFIG_NVS_DEBUG
#undef NVS_DEBUGV
#define NVS_DEBUGV(...)
#endif
#endif /* nvs_platform_h */
+195
View File
@@ -0,0 +1,195 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "nvs_storage.hpp"
namespace nvs
{
Storage::~Storage()
{
clearNamespaces();
}
void Storage::clearNamespaces()
{
for (auto it = std::begin(mNamespaces); it != std::end(mNamespaces); ) {
auto tmp = it;
++it;
mNamespaces.erase(tmp);
delete static_cast<NamespaceEntry*>(tmp);
}
}
esp_err_t Storage::init(uint32_t baseSector, uint32_t sectorCount)
{
auto err = mPageManager.load(baseSector, sectorCount);
if (err != ESP_OK) {
mState = StorageState::INVALID;
return err;
}
clearNamespaces();
std::fill_n(mNamespaceUsage.data(), mNamespaceUsage.byteSize() / 4, 0);
for (auto it = mPageManager.begin(); it != mPageManager.end(); ++it) {
Page& p = *it;
size_t itemIndex = 0;
Item item;
while(p.findItem(Page::NS_INDEX, ItemType::U8, nullptr, itemIndex, item) == ESP_OK) {
NamespaceEntry* entry = new NamespaceEntry;
item.getKey(entry->mName, sizeof(entry->mName) - 1);
item.getValue(entry->mIndex);
mNamespaces.push_back(entry);
mNamespaceUsage.set(entry->mIndex, true);
}
}
mNamespaceUsage.set(0, true);
mNamespaceUsage.set(255, true);
mState = StorageState::ACTIVE;
return ESP_OK;
}
esp_err_t Storage::writeItem(uint8_t nsIndex, ItemType datatype, const char* key, const void* data, size_t dataSize)
{
if (mState != StorageState::ACTIVE) {
return ESP_ERR_NVS_NOT_INITIALIZED;
}
Page* findPage = nullptr;
Item item;
auto err = findItem(nsIndex, datatype, key, findPage, item);
if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) {
return err;
}
Page& page = getCurrentPage();
err = page.writeItem(nsIndex, datatype, key, data, dataSize);
if (err == ESP_ERR_NVS_PAGE_FULL) {
page.markFull();
err = mPageManager.requestNewPage();
if (err != ESP_OK) {
return err;
}
err = getCurrentPage().writeItem(nsIndex, datatype, key, data, dataSize);
if (err != ESP_OK) {
return err;
}
}
if (findPage) {
if (findPage->state() == Page::PageState::UNINITIALIZED) {
auto err = findItem(nsIndex, datatype, key, findPage, item);
assert(err == ESP_OK);
}
err = findPage->eraseItem(nsIndex, datatype, key);
if (err != ESP_OK) {
return err;
}
}
return ESP_OK;
}
esp_err_t Storage::createOrOpenNamespace(const char* nsName, bool canCreate, uint8_t& nsIndex)
{
if (mState != StorageState::ACTIVE) {
return ESP_ERR_NVS_NOT_INITIALIZED;
}
auto it = std::find_if(mNamespaces.begin(), mNamespaces.end(), [=] (const NamespaceEntry& e) -> bool {
return strncmp(nsName, e.mName, sizeof(e.mName) - 1) == 0;
});
if (it == std::end(mNamespaces)) {
if (!canCreate) {
return ESP_ERR_NVS_NOT_FOUND;
}
uint8_t ns;
for (ns = 1; ns < 255; ++ns) {
if (mNamespaceUsage.get(ns) == false) {
break;
}
}
if (ns == 255) {
return ESP_ERR_NVS_NOT_ENOUGH_SPACE;
}
auto err = writeItem(Page::NS_INDEX, ItemType::U8, nsName, &ns, sizeof(ns));
if (err != ESP_OK) {
return err;
}
mNamespaceUsage.set(ns, true);
nsIndex = ns;
NamespaceEntry* entry = new NamespaceEntry;
entry->mIndex = ns;
strlcpy(entry->mName, nsName, sizeof(entry->mName));
mNamespaces.push_back(entry);
} else {
nsIndex = it->mIndex;
}
return ESP_OK;
}
esp_err_t Storage::readItem(uint8_t nsIndex, ItemType datatype, const char* key, void* data, size_t dataSize)
{
if (mState != StorageState::ACTIVE) {
return ESP_ERR_NVS_NOT_INITIALIZED;
}
Item item;
Page* findPage = nullptr;
auto err = findItem(nsIndex, datatype, key, findPage, item);
if (err != ESP_OK) {
return err;
}
return findPage->readItem(nsIndex, datatype, key, data, dataSize);
}
esp_err_t Storage::eraseItem(uint8_t nsIndex, ItemType datatype, const char* key)
{
if (mState != StorageState::ACTIVE) {
return ESP_ERR_NVS_NOT_INITIALIZED;
}
Item item;
Page* findPage = nullptr;
auto err = findItem(nsIndex, datatype, key, findPage, item);
if (err != ESP_OK) {
return err;
}
return findPage->eraseItem(nsIndex, datatype, key);
}
esp_err_t Storage::getItemDataSize(uint8_t nsIndex, ItemType datatype, const char* key, size_t& dataSize)
{
if (mState != StorageState::ACTIVE) {
return ESP_ERR_NVS_NOT_INITIALIZED;
}
Item item;
Page* findPage = nullptr;
auto err = findItem(nsIndex, datatype, key, findPage, item);
if (err != ESP_OK) {
return err;
}
dataSize = item.varLength.dataSize;
return ESP_OK;
}
}
+114
View File
@@ -0,0 +1,114 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef nvs_storage_hpp
#define nvs_storage_hpp
#include <memory>
#include <string>
#include <unordered_map>
#include "nvs.hpp"
#include "nvs_types.hpp"
#include "nvs_page.hpp"
#include "nvs_pagemanager.hpp"
//extern void dumpBytes(const uint8_t* data, size_t count);
namespace nvs
{
class Storage
{
enum class StorageState : uint32_t {
INVALID,
ACTIVE,
};
struct NamespaceEntry : public intrusive_list_node<NamespaceEntry> {
public:
char mName[Item::MAX_KEY_LENGTH + 1];
uint8_t mIndex;
};
typedef intrusive_list<NamespaceEntry> TNamespaces;
public:
~Storage();
esp_err_t init(uint32_t baseSector, uint32_t sectorCount);
esp_err_t createOrOpenNamespace(const char* nsName, bool canCreate, uint8_t& nsIndex);
esp_err_t writeItem(uint8_t nsIndex, ItemType datatype, const char* key, const void* data, size_t dataSize);
esp_err_t readItem(uint8_t nsIndex, ItemType datatype, const char* key, void* data, size_t dataSize);
esp_err_t getItemDataSize(uint8_t nsIndex, ItemType datatype, const char* key, size_t& dataSize);
esp_err_t eraseItem(uint8_t nsIndex, ItemType datatype, const char* key);
template<typename T>
esp_err_t writeItem(uint8_t nsIndex, const char* key, const T& value)
{
return writeItem(nsIndex, itemTypeOf(value), key, &value, sizeof(value));
}
template<typename T>
esp_err_t readItem(uint8_t nsIndex, const char* key, T& value)
{
return readItem(nsIndex, itemTypeOf(value), key, &value, sizeof(value));
}
template<typename T>
esp_err_t eraseItem(uint8_t nsIndex, const char* key)
{
return eraseItem(nsIndex, itemTypeOf<T>(), key);
}
protected:
Page& getCurrentPage()
{
return mPageManager.back();
}
void clearNamespaces();
esp_err_t findItem(uint8_t nsIndex, ItemType datatype, const char* key, Page* &page, Item& item)
{
size_t itemIndex = 0;
for (auto it = std::begin(mPageManager); it != std::end(mPageManager); ++it) {
auto err = it->findItem(nsIndex, datatype, key, itemIndex, item);
if (err == ESP_OK) {
page = it;
return ESP_OK;
}
}
return ESP_ERR_NVS_NOT_FOUND;
}
protected:
size_t mPageCount;
PageManager mPageManager;
TNamespaces mNamespaces;
CompressedEnumTable<bool, 1, 256> mNamespaceUsage;
StorageState mState = StorageState::INVALID;
};
} // namespace nvs
#endif /* nvs_storage_hpp */
+42
View File
@@ -0,0 +1,42 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "nvs_types.hpp"
#if defined(ESP_PLATFORM)
#include <rom/crc.h>
#else
#include "crc.h"
#endif
namespace nvs
{
uint32_t Item::calculateCrc32()
{
uint32_t result = 0xffffffff;
const uint8_t* p = reinterpret_cast<const uint8_t*>(this);
result = crc32_le(result, p + offsetof(Item, nsIndex),
offsetof(Item, crc32) - offsetof(Item, nsIndex));
result = crc32_le(result, p + offsetof(Item, key), sizeof(key));
result = crc32_le(result, p + offsetof(Item, data), sizeof(data));
return result;
}
uint32_t Item::calculateCrc32(const uint8_t* data, size_t size)
{
uint32_t result = 0xffffffff;
result = crc32_le(result, data, size);
return result;
}
} // namespace nvs
+100
View File
@@ -0,0 +1,100 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef nvs_types_h
#define nvs_types_h
#include <cstdint>
#include <type_traits>
#include <cstring>
#include <cassert>
#include <algorithm>
#include "nvs.h"
#include "compressed_enum_table.hpp"
namespace nvs
{
enum class ItemType : uint8_t {
U8 = 0x01,
I8 = 0x11,
U16 = 0x02,
I16 = 0x12,
U32 = 0x04,
I32 = 0x14,
U64 = 0x08,
I64 = 0x18,
SZ = 0x21,
BLOB = 0x41,
ANY = 0xff
};
template<typename T, typename std::enable_if<std::is_integral<T>::value, void*>::type = nullptr>
constexpr ItemType itemTypeOf()
{
return static_cast<ItemType>(((std::is_signed<T>::value)?0x10:0x00) | sizeof(T));
}
template<typename T>
constexpr ItemType itemTypeOf(const T&)
{
return itemTypeOf<T>();
}
class Item
{
public:
union {
struct {
uint8_t nsIndex;
ItemType datatype;
uint8_t span;
uint8_t reserved;
uint32_t crc32;
char key[16];
union {
struct {
uint16_t dataSize;
uint16_t reserved2;
uint32_t dataCrc32;
} varLength;
uint8_t data[8];
};
};
uint8_t rawData[32];
};
static const size_t MAX_KEY_LENGTH = sizeof(key) - 1;
uint32_t calculateCrc32();
static uint32_t calculateCrc32(const uint8_t* data, size_t size);
void getKey(char* dst, size_t dstSize)
{
strncpy(dst, key, (dstSize<MAX_KEY_LENGTH)?dstSize:MAX_KEY_LENGTH);
}
template<typename T>
void getValue(T& dst)
{
assert(itemTypeOf(dst) == datatype);
dst = *reinterpret_cast<T*>(data);
}
};
} // namespace nvs
#endif /* nvs_types_h */