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
+1
View File
@@ -0,0 +1 @@
test/test_nvs
+14
View File
@@ -0,0 +1,14 @@
#
# Component Makefile
#
# This Makefile should, at the very least, just include $(SDK_PATH)/Makefile. By default,
# this will take the sources in this directory, compile them and link them into
# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable,
# please read the SDK documents if you need to do this.
#
COMPONENT_ADD_INCLUDEDIRS := include
COMPONENT_SRCDIRS := src
include $(SDK_PATH)/make/component.mk
+219
View File
@@ -0,0 +1,219 @@
Non-volatile storage library
============================
Introduction
------------
Non-volatile storage (NVS) library is designed to store key-value pairs in flash. This sections introduces some concepts used by NVS.
Underlying storage
~~~~~~~~~~~~~~~~~~
Currently NVS uses a portion of main flash memory through ``spi_flash_{read|write|erase}`` APIs. The range of flash sectors to be used by the library is provided to ``nvs_flash_init`` function.
Future versions of this library may add other storage backends to keep data in another flash chip (SPI or I2C), RTC, FRAM, etc.
Keys and values
~~~~~~~~~~~~~~~
NVS operates on key-value pairs. Keys are ASCII strings, maximum key length is currently 15 characters. Values can have one of the following types:
- integer types: ``uint8_t``, ``int8_t``, ``uint16_t``, ``int16_t``, ``uint32_t``, ``int32_t``, ``uint64_t``, ``int64_t``
- zero-terminated string
- variable length binary data (blob)
Additional types, such as ``float`` and ``double`` may be added later.
Keys are required to be unique. Writing a value for a key which already exists behaves as follows:
- if the new value is of the same type as old one, value is updated
- if the new value has different data type, an error is returned
Data type check is also performed when reading a value. An error is returned if data type of read operation doesn’t match the data type of the value.
Namespaces
~~~~~~~~~~
To mitigate potential conflicts in key names between different components, NVS assigns each key-value pair to one of namespaces. Namespace names follow the same rules as key names, i.e. 15 character maximum length. Namespace name is specified in the ``nvs_open`` call. This call returns an opaque handle, which is used in subsequent calls to ``nvs_read_*``, ``nvs_write_*``, and ``nvs_commit`` functions. This way, handle is associated with a namespace, and key names will not collide with same names in other namespaces.
Security, tampering, and robustness
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
NVS library doesn't implement tamper prevention measures. It is possible for anyone with physical access to the flash chip to alter, erase, or add key-value pairs.
NVS is compatible with the ESP32 flash encryption system, and it can store key-value pairs in an encrypted form. Some metadata, like page state and write/erase flags of individual entries can not be encrypted as they are represented as bits of flash memory for efficient access and manipulation. Flash encryption can prevent some forms of modification:
- replacing keys or values with arbitrary data
- changing data types of values
The following forms of modification are still possible when flash encryption is used:
- erasing a page completely, removing all key-value pairs which were stored in that page
- corrupting data in a page, which will cause the page to be erased automatically when such condition is detected
- rolling back the contents of flash memory to an earlier snapshot
- merging two snapshots of flash memory, rolling back some key-value pairs to an earlier state (although this is possible to mitigate with the current design — TODO)
The library does try to recover from conditions when flash memory is in an inconsistent state. In particular, one should be able to power off the device at any point and time and then power it back on. This should not result in loss of data, expect for the new key-value pair if it was being written at the moment of power off. The library should also be able to initialize properly with any random data present in flash memory.
Internals
---------
Log of key-value pairs
~~~~~~~~~~~~~~~~~~~~~~
NVS stores key-value pairs sequentially, with new key-value pairs being added at the end. When a value of any given key has to be updated, old key-value pair is marked as erased and new key-value pair is added at the end of the log.
Pages and entries
~~~~~~~~~~~~~~~~~
NVS library uses two main entities in its operation: pages and entries. Page is a logical structure which stores a portion of the overall log. Logical page corresponds to one physical sector of flash memory. Pages which are in use have a *sequence number* associated with them. Sequence numbers impose an ordering on pages. Higher sequence numbers correspond to pages which were created later. Each page can be in one of the following states:
Empty/uninitialized
Flash storage for the page is empty (all bytes are ``0xff``). Page isn't used to store any data at this point and doesn’t have
Active
Flash storage is initialized, page header has been written to flash, page has a valid sequence number. Page has some empty entries and data can be written there. Normally only one page can be in this state.
Full
Flash storage is in a consistent state and is filled with key-value pairs.
Writing new key-value pairs into this page is not possible. It is still possible to mark some key-value pairs as erased.
Erasing
Non-erased key-value pairs are being moved into another page so that the current page can be erased. This is a transient state, i.e. page should never stay in this state when any API call returns. In case of a sudden power off, move-and-erase process will be completed upon next power on.
Corrupted
Page header contains invalid data, and further parsing of page data was canceled. Any items previously written into this page will not be accessible. Corresponding flash sector will not be erased immediately, and will be kept along with sectors in *uninitialized* state for later use. This may be useful for debugging.
Mapping from flash sectors to logical pages doesn't have any particular order. Library will inspect sequence numbers of pages found in each flash sector and organize pages in a list based on these numbers.
::
+--------+ +--------+ +--------+ +--------+
| Page 1 | | Page 2 | | Page 3 | | Page 4 |
| Full +---> | Full +---> | Active | | Empty | <- states
| #11 | | #12 | | #14 | | | <- sequence numbers
+---+----+ +----+---+ +----+---+ +---+----+
| | | |
| | | |
| | | |
+---v------+ +-----v----+ +------v---+ +------v---+
| Sector 3 | | Sector 0 | | Sector 2 | | Sector 1 | <- physical sectors
+----------+ +----------+ +----------+ +----------+
Structure of a page
~~~~~~~~~~~~~~~~~~~
For now we assume that flash sector size is 4096 bytes and that ESP32 flash encryption hardware operates on 32-byte blocks. It is possible to introduce some settings configurable at compile-time (e.g. via menuconfig) to accommodate flash chips with different sector sizes (although it is not clear if other components in the system, e.g. SPI flash driver and SPI flash cache can support these other sizes).
Page consists of three parts: header, entry state bitmap, and entries themselves. To be compatible with ESP32 flash encryption, entry size is 32 bytes. For integer types, entry holds one key-value pair. For strings and blobs, an entry holds part of key-value pair (more on that in the entry structure description).
The following diagram illustrates page structure. Numbers in parentheses indicate size of each part in bytes. ::
+-----------+--------------+-------------+-----------+
| State (4) | Seq. no. (4) | Unused (20) | CRC32 (4) | Header (32)
+-----------+--------------+-------------+-----------+
| Entry state bitmap (32) |
+----------------------------------------------------+
| Entry 0 (32) |
+----------------------------------------------------+
| Entry 1 (32) |
+----------------------------------------------------+
/ /
/ /
+----------------------------------------------------+
| Entry 125 (32) |
+----------------------------------------------------+
Page header and entry state bitmap are always written to flash unencrypted. Entries are encrypted if flash encryption feature of the ESP32 is used.
Page state values are defined in such a way that changing state is possible by writing 0 into some of the bits. Therefore it not necessary to erase the page to change page state, unless that is a change to *erased* state.
CRC32 value in header is calculated over the part which doesn't include state value (bytes 4 to 28). Unused part is currently filled with ``0xff`` bytes. Future versions of the library may store format version there.
The following sections describe structure of entry state bitmap and entry itself.
Entry and entry state bitmap
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Each entry can be in one of the following three states. Each state is represented with two bits in the entry state bitmap. Final four bits in the bitmap (256 - 2 * 126) are unused.
Empty (2'b11)
Nothing is written into the specific entry yet. It is in an uninitialized state (all bytes ``0xff``).
Written (2'b10)
A key-value pair (or part of key-value pair which spans multiple entries) has been written into the entry.
Erased (2'b00)
A key-value pair in this entry has been discarded. Contents of this entry will not be parsed anymore.
Structure of entry
~~~~~~~~~~~~~~~~~~
For values of primitive types (currently integers from 1 to 8 bytes long), entry holds one key-value pair. For string and blob types, entry holds part of the whole key-value pair. In case when a key-value pair spans multiple entries, all entries are stored in the same page.
::
+--------+----------+----------+---------+-----------+---------------+----------+
| NS (1) | Type (1) | Span (1) | Rsv (1) | CRC32 (4) | Key (16) | Data (8) |
+--------+----------+----------+---------+-----------+---------------+----------+
+--------------------------------+
+-> Fixed length: | Data (8) |
| +--------------------------------+
Data format ---+
| +----------+---------+-----------+
+-> Variable length: | Size (2) | Rsv (2) | CRC32 (4) |
+----------+---------+-----------+
Individual fields in entry structure have the following meanings:
NS
Namespace index for this entry. See section on namespaces implementation for explanation of this value.
Type
One byte indicating data type of value. See ``ItemType`` enumeration in ``nvs_types.h`` for possible values.
Span
Number of entries used by this key-value pair. For integer types, this is equal to 1. For strings and blobs this depends on value length.
Rsv
Unused field, should be ``0xff``.
CRC32
Checksum calculated over all the bytes in this entry, except for the CRC32 field itself.
Key
Zero-terminated ASCII string containing key name. Maximum string length is 15 bytes, excluding zero terminator.
Data
For integer types, this field contains the value itself. If the value itself is shorter than 8 bytes it is padded to the right, with unused bytes filled with ``0xff``. For string and blob values, these 8 bytes hold additional data about the value, described next:
Size
(Only for strings and blobs.) Size, in bytes, of actual data. For strings, this includes zero terminator.
CRC32
(Only for strings and blobs.) Checksum calculated over all bytes of data.
Variable length values (strings and blobs) are written into subsequent entries, 32 bytes per entry. `Span` field of the first entry indicates how many entries are used.
Namespaces
~~~~~~~~~~
As mentioned above, each key-value pair belongs to one of the namespaces. Namespaces identifiers (strings) are stored as keys of key-value pairs in namespace with index 0. Values corresponding to these keys are indexes of these namespaces.
::
+-------------------------------------------+
| NS=0 Type=uint8_t Key="wifi" Value=1 | Entry describing namespace "wifi"
+-------------------------------------------+
| NS=1 Type=uint32_t Key="channel" Value=6 | Key "channel" in namespace "wifi"
+-------------------------------------------+
| NS=0 Type=uint8_t Key="pwm" Value=2 | Entry describing namespace "pwm"
+-------------------------------------------+
| NS=0 Type=uint16_t Key="channel" Value=20 | Key "channel" in namespace "pwm"
+-------------------------------------------+
+212
View File
@@ -0,0 +1,212 @@
// 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 ESP_NVS_H
#define ESP_NVS_H
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <esp_err.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Opaque pointer type representing non-volatile storage handle
*/
typedef uint32_t nvs_handle;
#define ESP_ERR_NVS_BASE 0x1100
#define ESP_ERR_NVS_NOT_INITIALIZED (ESP_ERR_NVS_BASE + 0x01)
#define ESP_ERR_NVS_NOT_FOUND (ESP_ERR_NVS_BASE + 0x02)
#define ESP_ERR_NVS_TYPE_MISMATCH (ESP_ERR_NVS_BASE + 0x03)
#define ESP_ERR_NVS_READ_ONLY (ESP_ERR_NVS_BASE + 0x04)
#define ESP_ERR_NVS_NOT_ENOUGH_SPACE (ESP_ERR_NVS_BASE + 0x05)
#define ESP_ERR_NVS_INVALID_NAME (ESP_ERR_NVS_BASE + 0x06)
#define ESP_ERR_NVS_INVALID_HANDLE (ESP_ERR_NVS_BASE + 0x07)
#define ESP_ERR_NVS_KEY_TOO_LONG (ESP_ERR_NVS_BASE + 0x09)
#define ESP_ERR_NVS_PAGE_FULL (ESP_ERR_NVS_BASE + 0x0a)
#define ESP_ERR_NVS_INVALID_STATE (ESP_ERR_NVS_BASE + 0x0b)
#define ESP_ERR_NVS_INVALID_LENGTH (ESP_ERR_NVS_BASE + 0x0c)
typedef enum {
NVS_READONLY,
NVS_READWRITE
} nvs_open_mode;
/**
* @brief Open non-volatile storage with a given namespace
*
* Multiple SDK and application modules can store their key-value pairs in the
* NVS module. In order to reduce possible conflicts on key names, each module
* can use its own namespace.
*
* @param[in] name Namespace name. Maximal length is determined by the
* underlying implementation, but is guaranteed to be
* at least 16 characters. Shouldn't be empty.
* @param[in] open_mode NVS_READWRITE or NVS_READONLY. If NVS_READONLY, will
* open a handle for reading only. All write requests will
* be rejected for this handle.
* @param[out] out_handle If successful (return code is zero), handle will be
* returned in this argument.
*
* @return - ESP_OK if storage handle was opened successfully
* - ESP_ERR_NVS_NOT_INITIALIZED if the storage driver is not initialized
* - ESP_ERR_NVS_NOT_FOUND id namespace doesn't exist yet and
* mode is NVS_READONLY
* - ESP_ERR_NVS_INVALID_NAME if namespace name doesn't satisfy constraints
* - other error codes from the underlying storage driver
*/
esp_err_t nvs_open(const char* name, nvs_open_mode open_mode, nvs_handle *out_handle);
/**
* @brief nvs_set_X - set value for given key
*
* This family of functions set value for the key, given its name. Note that
* actual storage will not be updated until nvs_commit function is called.
*
* @param[in] handle Handle obtained from nvs_open function. If the handle was
* opened with read_only set to true, nvs_set_X functions will
* fail with ESP_ERR_NVS_READONLY.
* @param[in] key Key name. Maximal length is determined by the underlying
* implementation, but is guaranteed to be at least
* 16 characters. Shouldn't be empty.
* @param[in] value The value to set.
* @param[in] length For nvs_set_blob: length of binary value to set, in bytes.
*
* @return - ESP_OK if value was set successfully
* - ESP_ERR_NVS_INVALID_HANDLE if handle has been closed or is NULL
* - ESP_ERR_NVS_READ_ONLY if storage handle was opened as read only
* - ESP_ERR_NVS_INVALID_NAME if key name doesn't satisfy constraints
* - ESP_ERR_NVS_NOT_ENOUGH_SPACE if there is not enough space in the
* underlying storage to save the value
*/
esp_err_t nvs_set_i8 (nvs_handle handle, const char* key, int8_t value);
esp_err_t nvs_set_u8 (nvs_handle handle, const char* key, uint8_t value);
esp_err_t nvs_set_i16 (nvs_handle handle, const char* key, int16_t value);
esp_err_t nvs_set_u16 (nvs_handle handle, const char* key, uint16_t value);
esp_err_t nvs_set_i32 (nvs_handle handle, const char* key, int32_t value);
esp_err_t nvs_set_u32 (nvs_handle handle, const char* key, uint32_t value);
esp_err_t nvs_set_i64 (nvs_handle handle, const char* key, int64_t value);
esp_err_t nvs_set_u64 (nvs_handle handle, const char* key, uint64_t value);
esp_err_t nvs_set_str (nvs_handle handle, const char* key, const char* value);
esp_err_t nvs_set_blob(nvs_handle handle, const char* key, const void* value, size_t length);
/**
* @brief nvs_get_X - get value for given key
*
* These functions retrieve value for the key, given its name. If key does not
* exist, or the requested variable type doesn't match the type which was used
* when setting a value, an error is returned.
*
* In case of any error, out_value is not modified.
*
* All functions expect out_value to be a pointer to an already allocated variable
* of the given type.
* Additionally, nvs_get_str and nvs_get_blob support WinAPI-style length queries.
* To get the size necessary to store the value, call nvs_get_str or nvs_get_blob
* with zero out_value and non-zero pointer to length. Variable pointed to
* by length argument will be set to the required length. For nvs_get_str,
* this length includes the zero terminator. When calling nvs_get_str and
* nvs_get_blob with non-zero out_value, length has to be non-zero and has to
* point to the length available in out_value.
* It is suggested that nvs_get/set_str is used for zero-terminated C strings, and
* nvs_get/set_blob used for arbitrary data structures.
*
* Example of using nvs_get_i32:
* int32_t max_buffer_size = 4096; // default value
* esp_err_t err = nvs_get_i32(my_handle, "max_buffer_size", &max_buffer_size);
* assert(err == ESP_OK || err == ESP_ERR_NVS_NOT_FOUND);
* // if ESP_ERR_NVS_NOT_FOUND was returned, max_buffer_size will still
* // have its default value.
*
* Example (without error checking) of using nvs_get_str to get a string into dynamic array:
* size_t required_size;
* nvs_get_str(my_handle, "server_name", NULL, &required_size);
* char* server_name = malloc(required_size);
* nvs_get_str(my_handle, "server_name", server_name, &required_size);
*
* Example (without error checking) of using nvs_get_blob to get a binary data
* into a static array:
* uint8_t mac_addr[6];
* size_t size = sizeof(mac_addr);
* nvs_get_blob(my_handle, "dst_mac_addr", mac_addr, &size);
*
* @param[in] handle Handle obtained from nvs_open function.
* @param[in] key Key name. Maximal length is determined by the underlying
* implementation, but is guaranteed to be at least
* 16 characters. Shouldn't be empty.
* @param out_value Pointer to the output value.
* May be NULL for nvs_get_str and nvs_get_blob, in this
* case required length will be returned in length argument.
* @param[inout] length For nvs_get_str and nvs_get_blob, non-zero pointer
* to the variable holding the length of out_value.
* In case out_value a zero, will be set to the length
* required to hold the value. In case out_value is not
* zero, will be set to the actual length of the value
* written. For nvs_get_str this includes zero terminator.
*
* @return - ESP_OK if the value was retrieved successfully
* - ESP_ERR_NVS_NOT_FOUND if the requested key doesn't exist
* - ESP_ERR_NVS_INVALID_HANDLE if handle has been closed or is NULL
* - ESP_ERR_NVS_INVALID_NAME if key name doesn't satisfy constraints
* - ESP_ERR_NVS_INVALID_LENGTH if length is not sufficient to store data
*/
esp_err_t nvs_get_i8 (nvs_handle handle, const char* key, int8_t* out_value);
esp_err_t nvs_get_u8 (nvs_handle handle, const char* key, uint8_t* out_value);
esp_err_t nvs_get_i16 (nvs_handle handle, const char* key, int16_t* out_value);
esp_err_t nvs_get_u16 (nvs_handle handle, const char* key, uint16_t* out_value);
esp_err_t nvs_get_i32 (nvs_handle handle, const char* key, int32_t* out_value);
esp_err_t nvs_get_u32 (nvs_handle handle, const char* key, uint32_t* out_value);
esp_err_t nvs_get_i64 (nvs_handle handle, const char* key, int64_t* out_value);
esp_err_t nvs_get_u64 (nvs_handle handle, const char* key, uint64_t* out_value);
esp_err_t nvs_get_str (nvs_handle handle, const char* key, char* out_value, size_t* length);
esp_err_t nvs_get_blob(nvs_handle handle, const char* key, void* out_value, size_t* length);
/**
* @brief Write any pending changes to non-volatile storage
*
* After setting any values, nvs_commit() must be called to ensure changes are written
* to non-volatile storage. Individual implementations may write to storage at other times,
* but this is not guaranteed.
*
* @param[in] handle Storage handle obtained with nvs_open. If handle has to be
* opened as not read only for this call to succeed.
*
* @return - ESP_OK if the changes have been written successfully
* - ESP_ERR_NVS_INVALID_HANDLE if handle has been closed or is NULL
* - other error codes from the underlying storage driver
*/
esp_err_t nvs_commit(nvs_handle handle);
/**
* @brief Close the storage handle and free any allocated resources
*
* This function should be called for each handle opened with nvs_open once
* the handle is not in use any more. Closing the handle may not automatically
* write the changes to nonvolatile storage. This has to be done explicitly using
* nvs_commit function.
* Once this function is called on a handle, the handle should no longer be used.
*
* @param[in] handle Storage handle to close
*/
void nvs_close(nvs_handle handle);
#ifdef __cplusplus
} // extern "C"
#endif
#endif //ESP_NVS_H
+29
View File
@@ -0,0 +1,29 @@
// 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_flash_h
#define nvs_flash_h
#ifdef __cplusplus
extern "C" {
#endif
esp_err_t nvs_flash_init(uint32_t baseSector, uint32_t sectorCount);
#ifdef __cplusplus
}
#endif
#endif /* nvs_flash_h */
@@ -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 */
+40
View File
@@ -0,0 +1,40 @@
TEST_PROGRAM=test_nvs
all: test
SOURCE_FILES = \
$(addprefix ../src/, \
nvs_types.cpp \
nvs_api.cpp \
nvs_page.cpp \
nvs_pagemanager.cpp \
nvs_storage.cpp \
) \
spi_flash_emulation.cpp \
test_compressed_enum_table.cpp \
test_spi_flash_emulation.cpp \
test_intrusive_list.cpp \
test_nvs.cpp \
crc.cpp \
main.cpp
CPPFLAGS += -I../include -I../src -I./ -I../../esp32/include -I ../../spi_flash/include
CXXFLAGS += -std=c++11 -Wall -Werror
LDFLAGS += -lstdc++
OBJ_FILES = $(SOURCE_FILES:.cpp=.o)
$(OBJ_FILES): %.o: %.cpp
$(TEST_PROGRAM): $(OBJ_FILES)
gcc $(LDFLAGS) -o $(TEST_PROGRAM) $(OBJ_FILES)
$(OUTPUT_DIR):
mkdir -p $(OUTPUT_DIR)
test: $(TEST_PROGRAM)
./$(TEST_PROGRAM)
clean:
rm -f $(OBJ_FILES) $(TEST_PROGRAM)
.PHONY: clean all test
File diff suppressed because it is too large Load Diff
+38
View File
@@ -0,0 +1,38 @@
// 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 <stdint.h>
#include <stdbool.h>
extern "C" unsigned long crc32_le(unsigned long crc_in, unsigned char const* data, unsigned int length)
{
uint32_t i;
bool bit;
uint8_t c;
uint32_t crc = (uint32_t) crc_in;
while (length--) {
c = *data++;
for (i = 0x80; i > 0; i >>= 1) {
bit = crc & 0x80000000;
if (c & i) {
bit = !bit;
}
crc <<= 1;
if (bit) {
crc ^= 0x04c11db7;
}
}
}
return crc;
}
+30
View File
@@ -0,0 +1,30 @@
// 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 crc_h
#define crc_h
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
uint32_t crc32_le(uint32_t crc, const uint8_t* buf, size_t len);
#ifdef __cplusplus
}
#endif
#endif /* crc_h */
+2
View File
@@ -0,0 +1,2 @@
#define CATCH_CONFIG_MAIN
#include "catch.hpp"
@@ -0,0 +1,97 @@
// 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 "esp_spi_flash.h"
#include "spi_flash_emulation.h"
static SpiFlashEmulator* s_emulator = nullptr;
void spi_flash_emulator_set(SpiFlashEmulator* e)
{
s_emulator = e;
}
esp_err_t spi_flash_erase_sector(uint16_t sec)
{
if (!s_emulator) {
return ESP_ERR_FLASH_OP_TIMEOUT;
}
if (!s_emulator->erase(sec)) {
return ESP_ERR_FLASH_OP_FAIL;
}
return ESP_OK;
}
esp_err_t spi_flash_write(uint32_t des_addr, const uint32_t *src_addr, uint32_t size)
{
if (!s_emulator) {
return ESP_ERR_FLASH_OP_TIMEOUT;
}
if (!s_emulator->write(des_addr, src_addr, size)) {
return ESP_ERR_FLASH_OP_FAIL;
}
return ESP_OK;
}
esp_err_t spi_flash_read(uint32_t src_addr, uint32_t *des_addr, uint32_t size)
{
if (!s_emulator) {
return ESP_ERR_FLASH_OP_TIMEOUT;
}
if (!s_emulator->read(des_addr, src_addr, size)) {
return ESP_ERR_FLASH_OP_FAIL;
}
return ESP_OK;
}
// timing data for ESP8266, 160MHz CPU frequency, 80MHz flash requency
// all values in microseconds
// values are for block sizes starting at 4 bytes and going up to 4096 bytes
static size_t readTimes[] = {7, 5, 6, 7, 11, 18, 32, 60, 118, 231, 459};
static size_t writeTimes[] = {19, 23, 35, 57, 106, 205, 417, 814, 1622, 3200, 6367};
static size_t blockEraseTime = 37142;
static size_t timeInterp(uint32_t bytes, size_t* lut)
{
int lz = __builtin_clz(bytes / 4);
int log_size = 32 - lz;
size_t x2 = 1 << (log_size + 2);
size_t y2 = lut[log_size];
size_t x1 = 1 << (log_size + 1);
size_t y1 = lut[log_size - 1];
return (bytes - x1) * (y2 - y1) / (x2 - x1) + y1;
}
size_t SpiFlashEmulator::getReadOpTime(uint32_t bytes)
{
return timeInterp(bytes, readTimes);
}
size_t SpiFlashEmulator::getWriteOpTime(uint32_t bytes)
{
return timeInterp(bytes, writeTimes);
}
size_t SpiFlashEmulator::getEraseOpTime()
{
return blockEraseTime;
}
@@ -0,0 +1,198 @@
// 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 spi_flash_emulation_h
#define spi_flash_emulation_h
#include <vector>
#include <cassert>
#include <algorithm>
#include <random>
#include "esp_spi_flash.h"
#include "catch.hpp"
using std::copy;
using std::begin;
using std::end;
using std::fill_n;
class SpiFlashEmulator;
void spi_flash_emulator_set(SpiFlashEmulator*);
class SpiFlashEmulator
{
public:
SpiFlashEmulator(size_t sectorCount) : mUpperSectorBound(sectorCount)
{
mData.resize(sectorCount * SPI_FLASH_SEC_SIZE / 4, 0xffffffff);
spi_flash_emulator_set(this);
}
~SpiFlashEmulator()
{
spi_flash_emulator_set(nullptr);
}
bool read(uint32_t* dest, uint32_t srcAddr, size_t size) const
{
if (srcAddr % 4 != 0 ||
size % 4 != 0 ||
srcAddr + size > mData.size() * 4) {
return false;
}
copy(begin(mData) + srcAddr / 4, begin(mData) + (srcAddr + size) / 4, dest);
++mReadOps;
mReadBytes += size;
mTotalTime += getReadOpTime(static_cast<uint32_t>(size));
return true;
}
bool write(uint32_t dstAddr, const uint32_t* src, size_t size)
{
uint32_t sectorNumber = dstAddr/SPI_FLASH_SEC_SIZE;
if (sectorNumber < mLowerSectorBound || sectorNumber >= mUpperSectorBound) {
WARN("invalid flash operation detected: erase sector=" << sectorNumber);
return false;
}
if (dstAddr % 4 != 0 ||
size % 4 != 0 ||
dstAddr + size > mData.size() * 4) {
return false;
}
for (size_t i = 0; i < size / 4; ++i) {
uint32_t sv = src[i];
size_t pos = dstAddr / 4 + i;
uint32_t& dv = mData[pos];
if (((~dv) & sv) != 0) { // are we trying to set some 0 bits to 1?
WARN("invalid flash operation detected: dst=" << dstAddr << " size=" << size << " i=" << i);
return false;
}
dv = sv;
}
++mWriteOps;
mWriteBytes += size;
mTotalTime += getWriteOpTime(static_cast<uint32_t>(size));
return true;
}
bool erase(uint32_t sectorNumber)
{
size_t offset = sectorNumber * SPI_FLASH_SEC_SIZE / 4;
if (offset > mData.size()) {
return false;
}
if (sectorNumber < mLowerSectorBound || sectorNumber >= mUpperSectorBound) {
WARN("invalid flash operation detected: erase sector=" << sectorNumber);
return false;
}
std::fill_n(begin(mData) + offset, SPI_FLASH_SEC_SIZE / 4, 0xffffffff);
++mEraseOps;
mTotalTime += getEraseOpTime();
return true;
}
void randomize(uint32_t seed)
{
std::random_device rd;
std::mt19937 gen(rd());
gen.seed(seed);
std::generate_n(mData.data(), mData.size(), gen);
}
size_t size() const
{
return mData.size() * 4;
}
const uint32_t* words() const
{
return mData.data();
}
const uint8_t* bytes() const
{
return reinterpret_cast<const uint8_t*>(mData.data());
}
void clearStats()
{
mReadBytes = 0;
mWriteBytes = 0;
mEraseOps = 0;
mReadOps = 0;
mWriteOps = 0;
mTotalTime = 0;
}
size_t getReadOps() const
{
return mReadOps;
}
size_t getWriteOps() const
{
return mWriteOps;
}
size_t getEraseOps() const
{
return mEraseOps;
}
size_t getReadBytes() const
{
return mReadBytes;
}
size_t getWriteBytes() const
{
return mWriteBytes;
}
size_t getTotalTime() const
{
return mTotalTime;
}
void setBounds(uint32_t lowerSector, uint32_t upperSector) {
mLowerSectorBound = lowerSector;
mUpperSectorBound = upperSector;
}
protected:
static size_t getReadOpTime(uint32_t bytes);
static size_t getWriteOpTime(uint32_t bytes);
static size_t getEraseOpTime();
std::vector<uint32_t> mData;
mutable size_t mReadOps = 0;
mutable size_t mWriteOps = 0;
mutable size_t mReadBytes = 0;
mutable size_t mWriteBytes = 0;
mutable size_t mEraseOps = 0;
mutable size_t mTotalTime = 0;
size_t mLowerSectorBound = 0;
size_t mUpperSectorBound = 0;
};
#endif /* spi_flash_emulation_h */
@@ -0,0 +1,56 @@
// 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 "catch.hpp"
#include "compressed_enum_table.hpp"
#include <cstring>
TEST_CASE("test if CompressedEnumTable works as expected", "[enumtable]")
{
enum class TEnum1 : uint32_t {
ZERO = 0,
ONE = 1,
TWO = 2,
THREE = 3,
};
CompressedEnumTable<TEnum1, 2, 252> table;
memset(table.data(), 0xff, table.byteSize());
for (size_t i = 0; i < table.count(); ++i) {
CHECK(table.get(i) == TEnum1::THREE);
}
table.set(0, TEnum1::ONE);
table.set(1, TEnum1::TWO);
table.set(2, TEnum1::ZERO);
table.set(3, TEnum1::ONE);
table.set(4, TEnum1::TWO);
table.set(5, TEnum1::ZERO);
table.set(6, TEnum1::ONE);
table.set(7, TEnum1::TWO);
table.set(8, TEnum1::ZERO);
table.set(9, TEnum1::ZERO);
table.set(10, TEnum1::ONE);
table.set(11, TEnum1::TWO);
// table.set(12, ...
table.set(13, TEnum1::ZERO);
table.set(14, TEnum1::ONE);
table.set(15, TEnum1::TWO);
// b10010011100100001001001001001001
// h 9 3 9 0 9 2 4 9
CHECK(table.data()[0] == 0x93909249);
}
@@ -0,0 +1,212 @@
// 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 "catch.hpp"
#include <algorithm>
#include <cstring>
#include "intrusive_list.h"
struct TestNode : public intrusive_list_node<TestNode> {
TestNode(const char* name_ = "", int num_ = 0) : num(num_)
{
strlcpy(name, name_, sizeof(name));
}
char name[32];
int num;
};
typedef intrusive_list<TestNode> TestList;
TEST_CASE("can add items to the list", "[list]")
{
TestList list;
TestNode n1("one", 1);
TestNode n2("two", 2);
TestNode n3("three", 3);
list.push_back(&n1);
REQUIRE(list.begin()->num == 1);
REQUIRE(list.front().num == 1);
REQUIRE(list.back().num == 1);
list.push_front(&n2);
REQUIRE(list.begin()->num == 2);
REQUIRE(list.front().num == 2);
REQUIRE(list.back().num == 1);
list.insert(list.begin(), &n3);
REQUIRE(list.begin()->num == 3);
REQUIRE(list.front().num == 3);
REQUIRE(list.back().num == 1);
auto second = ++list.begin();
REQUIRE(second->num == 2);
second++;
REQUIRE(second->num == 1);
}
TEST_CASE("can iterate over items", "[list]")
{
TestList list;
TestNode n1("one", 1);
TestNode n2("two", 2);
TestNode n3("three", 3);
list.push_back(&n1);
list.push_back(&n2);
list.push_back(&n3);
int val = 1;
for (auto it = std::begin(list); it != std::end(list); ++it) {
REQUIRE(it->num == val);
++val;
}
}
TEST_CASE("iterator's prefix and postfix increments and decrements behave as expected", "[list]")
{
TestList list;
TestNode n1("one", 1);
TestNode n2("two", 2);
TestNode n3("three", 3);
list.push_back(&n1);
list.push_back(&n2);
list.push_back(&n3);
auto it = std::begin(list);
REQUIRE((++it)->num == 2);
REQUIRE(it++->num == 2);
REQUIRE((--it)->num == 2);
REQUIRE(it--->num == 2);
}
TEST_CASE("can pop_front from the list", "[list]")
{
TestList list;
TestNode n1("one", 1);
TestNode n2("two", 2);
TestNode n3("three", 3);
list.push_back(&n1);
list.push_back(&n2);
list.push_back(&n3);
list.pop_front();
list.pop_front();
list.pop_front();
REQUIRE(std::begin(list) == std::end(list));
}
TEST_CASE("can erase first item in the list", "[list]")
{
TestList list;
TestNode n1("one", 1);
TestNode n2("two", 2);
TestNode n3("three", 3);
list.push_back(&n1);
list.push_back(&n2);
list.push_back(&n3);
list.erase(std::begin(list));
REQUIRE(list.front().num == 2);
REQUIRE(list.back().num == 3);
}
TEST_CASE("can erase last item in the list", "[list]")
{
TestList list;
TestNode n1("one", 1);
TestNode n2("two", 2);
TestNode n3("three", 3);
list.push_back(&n1);
list.push_back(&n2);
list.push_back(&n3);
list.erase(&list.back());
REQUIRE(list.front().num == 1);
REQUIRE(list.back().num == 2);
}
TEST_CASE("can erase item in the middle of the list", "[list]")
{
TestList list;
TestNode n1("one", 1);
TestNode n2("two", 2);
TestNode n3("three", 3);
list.push_back(&n1);
list.push_back(&n2);
list.push_back(&n3);
list.erase(++std::begin(list));
REQUIRE(list.front().num == 1);
REQUIRE(list.back().num == 3);
}
TEST_CASE("can erase all items in the list", "[list]")
{
TestList list;
TestNode n1("one", 1);
TestNode n2("two", 2);
TestNode n3("three", 3);
list.push_back(&n1);
list.push_back(&n2);
list.push_back(&n3);
list.erase(std::begin(list));
list.erase(std::begin(list));
list.erase(std::begin(list));
REQUIRE(std::begin(list) == std::end(list));
}
TEST_CASE("can erase all items in the list using clear method", "[list]")
{
TestList list;
TestNode n1("one", 1);
TestNode n2("two", 2);
TestNode n3("three", 3);
TestNode n4("four", 4);
TestNode n5("five", 5);
TestNode n6("six", 6);
list.push_back(&n1);
list.push_back(&n2);
list.insert(++list.begin(), &n3);
list.insert(++list.begin(), &n4);
list.push_front(&n5);
list.insert(list.begin(), &n6);
list.clear();
REQUIRE(std::begin(list) == std::end(list));
}
+715
View File
@@ -0,0 +1,715 @@
// 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 "catch.hpp"
#include "nvs.hpp"
#include "nvs_flash.h"
#include "spi_flash_emulation.h"
#include <sstream>
#include <iostream>
using namespace std;
using namespace nvs;
stringstream s_perf;
void dumpBytes(const uint8_t* data, size_t count)
{
for (uint32_t i = 0; i < count; ++i) {
if (i % 32 == 0) {
printf("%08x ", i);
}
printf("%02x ", data[i]);
if ((i + 1) % 32 == 0) {
printf("\n");
}
}
}
TEST_CASE("crc32 behaves as expected", "[nvs]")
{
Item item1;
item1.datatype = ItemType::I32;
item1.nsIndex = 1;
item1.crc32 = 0;
item1.reserved = 0xff;
fill_n(item1.key, sizeof(item1.key), 0xbb);
fill_n(item1.data, sizeof(item1.data), 0xaa);
auto crc32_1 = item1.calculateCrc32();
Item item2 = item1;
item2.crc32 = crc32_1;
CHECK(crc32_1 == item2.calculateCrc32());
item2 = item1;
item2.nsIndex = 2;
CHECK(crc32_1 != item2.calculateCrc32());
item2 = item1;
item2.datatype = ItemType::U32;
CHECK(crc32_1 != item2.calculateCrc32());
item2 = item1;
strlcpy(item2.key, "foo", Item::MAX_KEY_LENGTH);
CHECK(crc32_1 != item2.calculateCrc32());
}
TEST_CASE("starting with empty flash, page is in uninitialized state", "[nvs]")
{
SpiFlashEmulator emu(1);
Page page;
CHECK(page.state() == Page::PageState::INVALID);
CHECK(page.load(0) == ESP_OK);
CHECK(page.state() == Page::PageState::UNINITIALIZED);
}
TEST_CASE("can distinguish namespaces", "[nvs]")
{
SpiFlashEmulator emu(1);
Page page;
CHECK(page.load(0) == ESP_OK);
int32_t val1 = 0x12345678;
CHECK(page.writeItem(1, ItemType::I32, "intval1", &val1, sizeof(val1)) == ESP_OK);
int32_t val2 = 0x23456789;
CHECK(page.writeItem(2, ItemType::I32, "intval1", &val2, sizeof(val2)) == ESP_OK);
int32_t readVal;
CHECK(page.readItem(2, ItemType::I32, "intval1", &readVal, sizeof(readVal)) == ESP_OK);
CHECK(readVal == val2);
}
TEST_CASE("reading with different type causes type mismatch error", "[nvs]")
{
SpiFlashEmulator emu(1);
Page page;
CHECK(page.load(0) == ESP_OK);
int32_t val = 0x12345678;
CHECK(page.writeItem(1, ItemType::I32, "intval1", &val, sizeof(val)) == ESP_OK);
CHECK(page.readItem(1, ItemType::U32, "intval1", &val, sizeof(val)) == ESP_ERR_NVS_TYPE_MISMATCH);
}
TEST_CASE("when page is erased, it's state becomes UNITIALIZED", "[nvs]")
{
SpiFlashEmulator emu(1);
Page page;
CHECK(page.load(0) == ESP_OK);
int32_t val = 0x12345678;
CHECK(page.writeItem(1, ItemType::I32, "intval1", &val, sizeof(val)) == ESP_OK);
CHECK(page.erase() == ESP_OK);
CHECK(page.state() == Page::PageState::UNINITIALIZED);
}
TEST_CASE("when writing and erasing, used/erased counts are updated correctly", "[nvs]")
{
SpiFlashEmulator emu(1);
Page page;
CHECK(page.load(0) == ESP_OK);
CHECK(page.getUsedEntryCount() == 0);
CHECK(page.getErasedEntryCount() == 0);
uint32_t foo1 = 0;
CHECK(page.writeItem(1, "foo1", foo1) == ESP_OK);
CHECK(page.getUsedEntryCount() == 1);
CHECK(page.writeItem(2, "foo1", foo1) == ESP_OK);
CHECK(page.getUsedEntryCount() == 2);
CHECK(page.eraseItem<uint32_t>(2, "foo1") == ESP_OK);
CHECK(page.getUsedEntryCount() == 1);
CHECK(page.getErasedEntryCount() == 1);
for (size_t i = 0; i < Page::ENTRY_COUNT - 2; ++i) {
char name[16];
snprintf(name, sizeof(name), "i%ld", i);
CHECK(page.writeItem(1, name, i) == ESP_OK);
}
CHECK(page.getUsedEntryCount() == Page::ENTRY_COUNT - 1);
CHECK(page.getErasedEntryCount() == 1);
for (size_t i = 0; i < Page::ENTRY_COUNT - 2; ++i) {
char name[16];
snprintf(name, sizeof(name), "i%ld", i);
CHECK(page.eraseItem(1, itemTypeOf<size_t>(), name) == ESP_OK);
}
CHECK(page.getUsedEntryCount() == 1);
CHECK(page.getErasedEntryCount() == Page::ENTRY_COUNT - 1);
}
TEST_CASE("when page is full, adding an element fails", "[nvs]")
{
SpiFlashEmulator emu(1);
Page page;
CHECK(page.load(0) == ESP_OK);
for (size_t i = 0; i < Page::ENTRY_COUNT; ++i) {
char name[16];
snprintf(name, sizeof(name), "i%ld", i);
CHECK(page.writeItem(1, name, i) == ESP_OK);
}
CHECK(page.state() == Page::PageState::FULL);
CHECK(page.writeItem(1, "foo", 64UL) == ESP_ERR_NVS_PAGE_FULL);
CHECK(page.state() == Page::PageState::FULL);
}
TEST_CASE("page maintains its seq number")
{
SpiFlashEmulator emu(1);
{
Page page;
CHECK(page.load(0) == ESP_OK);
CHECK(page.setSeqNumber(123) == ESP_OK);
int32_t val = 42;
CHECK(page.writeItem(1, ItemType::I32, "dummy", &val, sizeof(val)) == ESP_OK);
}
{
Page page;
CHECK(page.load(0) == ESP_OK);
uint32_t seqno;
CHECK(page.getSeqNumber(seqno) == ESP_OK);
CHECK(seqno == 123);
}
}
TEST_CASE("can write and read variable length data", "[nvs]")
{
SpiFlashEmulator emu(1);
Page page;
CHECK(page.load(0) == ESP_OK);
const char str[] = "foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234";
size_t len = strlen(str);
CHECK(page.writeItem(1, "stuff1", 42) == ESP_OK);
CHECK(page.writeItem(1, "stuff2", 1) == ESP_OK);
CHECK(page.writeItem(1, ItemType::SZ, "foobaar", str, len + 1) == ESP_OK);
CHECK(page.writeItem(1, "stuff3", 2) == ESP_OK);
CHECK(page.writeItem(1, ItemType::BLOB, "baz", str, len) == ESP_OK);
CHECK(page.writeItem(1, "stuff4", 0x7abbccdd) == ESP_OK);
char buf[sizeof(str) + 16];
int32_t value;
CHECK(page.readItem(1, "stuff1", value) == ESP_OK);
CHECK(value == 42);
CHECK(page.readItem(1, "stuff2", value) == ESP_OK);
CHECK(value == 1);
CHECK(page.readItem(1, "stuff3", value) == ESP_OK);
CHECK(value == 2);
CHECK(page.readItem(1, "stuff4", value) == ESP_OK);
CHECK(value == 0x7abbccdd);
fill_n(buf, sizeof(buf), 0xff);
CHECK(page.readItem(1, ItemType::SZ, "foobaar", buf, sizeof(buf)) == ESP_OK);
CHECK(memcmp(buf, str, strlen(str) + 1) == 0);
fill_n(buf, sizeof(buf), 0xff);
CHECK(page.readItem(1, ItemType::BLOB, "baz", buf, sizeof(buf)) == ESP_OK);
CHECK(memcmp(buf, str, strlen(str)) == 0);
}
TEST_CASE("can init PageManager in empty flash", "[nvs]")
{
SpiFlashEmulator emu(4);
PageManager pm;
CHECK(pm.load(0, 4) == ESP_OK);
}
TEST_CASE("PageManager adds page in the correct order", "[nvs]")
{
const size_t pageCount = 8;
SpiFlashEmulator emu(pageCount);
uint32_t pageNo[pageCount] = { -1U, 50, 11, -1U, 23, 22, 24, 49};
for (uint32_t i = 0; i < pageCount; ++i) {
Page p;
p.load(i);
if (pageNo[i] != -1U) {
p.setSeqNumber(pageNo[i]);
p.writeItem(1, "foo", 10U);
}
}
PageManager pageManager;
CHECK(pageManager.load(0, pageCount) == ESP_OK);
uint32_t lastSeqNo = 0;
for (auto it = std::begin(pageManager); it != std::end(pageManager); ++it) {
uint32_t seqNo;
CHECK(it->getSeqNumber(seqNo) == ESP_OK);
CHECK(seqNo > lastSeqNo);
}
}
TEST_CASE("can init storage in empty flash", "[nvs]")
{
SpiFlashEmulator emu(8);
Storage storage;
emu.setBounds(4, 8);
CHECK(storage.init(4, 4) == ESP_OK);
s_perf << "Time to init empty storage (4 sectors): " << emu.getTotalTime() << " us" << std::endl;
}
TEST_CASE("storage doesn't add duplicates within one page", "[nvs]")
{
SpiFlashEmulator emu(8);
Storage storage;
emu.setBounds(4, 8);
CHECK(storage.init(4, 4) == ESP_OK);
int bar = 0;
CHECK(storage.writeItem(1, "bar", bar) == ESP_OK);
CHECK(storage.writeItem(1, "bar", bar) == ESP_OK);
Page page;
page.load(4);
CHECK(page.getUsedEntryCount() == 1);
CHECK(page.getErasedEntryCount() == 1);
}
TEST_CASE("can write one item a thousand times", "[nvs]")
{
SpiFlashEmulator emu(8);
Storage storage;
emu.setBounds(4, 8);
CHECK(storage.init(4, 4) == ESP_OK);
for (size_t i = 0; i < Page::ENTRY_COUNT * 4 * 2; ++i) {
REQUIRE(storage.writeItem(1, "i", static_cast<int>(i)) == ESP_OK);
}
s_perf << "Time to write one item a thousand times: " << emu.getTotalTime() << " us (" << emu.getEraseOps() << " " << emu.getWriteOps() << " " << emu.getReadOps() << " " << emu.getWriteBytes() << " " << emu.getReadBytes() << ")" << std::endl;
}
TEST_CASE("storage doesn't add duplicates within multiple pages", "[nvs]")
{
SpiFlashEmulator emu(8);
Storage storage;
emu.setBounds(4, 8);
CHECK(storage.init(4, 4) == ESP_OK);
int bar = 0;
CHECK(storage.writeItem(1, "bar", bar) == ESP_OK);
for (size_t i = 0; i < Page::ENTRY_COUNT; ++i) {
CHECK(storage.writeItem(1, "foo", static_cast<int>(bar)) == ESP_OK);
}
CHECK(storage.writeItem(1, "bar", bar) == ESP_OK);
Page page;
page.load(4);
CHECK(page.findItem(1, itemTypeOf<int>(), "bar") == ESP_ERR_NVS_NOT_FOUND);
page.load(5);
CHECK(page.findItem(1, itemTypeOf<int>(), "bar") == ESP_OK);
}
TEST_CASE("can write and read variable length data lots of times", "[nvs]")
{
SpiFlashEmulator emu(8);
Storage storage;
emu.setBounds(4, 8);
CHECK(storage.init(4, 4) == ESP_OK);
const char str[] = "foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234";
char buf[sizeof(str) + 16];
size_t len = strlen(str);
for (size_t i = 0; i < Page::ENTRY_COUNT * 4 * 2; ++i) {
CAPTURE(i);
CHECK(storage.writeItem(1, ItemType::SZ, "foobaar", str, len + 1) == ESP_OK);
CHECK(storage.writeItem(1, "foo", static_cast<uint32_t>(i)) == ESP_OK);
uint32_t value;
CHECK(storage.readItem(1, "foo", value) == ESP_OK);
CHECK(value == i);
fill_n(buf, sizeof(buf), 0xff);
CHECK(storage.readItem(1, ItemType::SZ, "foobaar", buf, sizeof(buf)) == ESP_OK);
CHECK(memcmp(buf, str, strlen(str) + 1) == 0);
}
s_perf << "Time to write one string and one integer a thousand times: " << emu.getTotalTime() << " us (" << emu.getEraseOps() << " " << emu.getWriteOps() << " " << emu.getReadOps() << " " << emu.getWriteBytes() << " " << emu.getReadBytes() << ")" << std::endl;
}
TEST_CASE("can get length of variable length data", "[nvs]")
{
SpiFlashEmulator emu(8);
emu.randomize(200);
Storage storage;
emu.setBounds(4, 8);
CHECK(storage.init(4, 4) == ESP_OK);
const char str[] = "foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234";
size_t len = strlen(str);
CHECK(storage.writeItem(1, ItemType::SZ, "foobaar", str, len + 1) == ESP_OK);
size_t dataSize;
CHECK(storage.getItemDataSize(1, ItemType::SZ, "foobaar", dataSize) == ESP_OK);
CHECK(dataSize == len + 1);
CHECK(storage.writeItem(2, ItemType::BLOB, "foobaar", str, len) == ESP_OK);
CHECK(storage.getItemDataSize(2, ItemType::BLOB, "foobaar", dataSize) == ESP_OK);
CHECK(dataSize == len);
}
TEST_CASE("can create namespaces", "[nvs]")
{
SpiFlashEmulator emu(8);
Storage storage;
emu.setBounds(4, 8);
CHECK(storage.init(4, 4) == ESP_OK);
uint8_t nsi;
CHECK(storage.createOrOpenNamespace("wifi", false, nsi) == ESP_ERR_NVS_NOT_FOUND);
CHECK(storage.createOrOpenNamespace("wifi", true, nsi) == ESP_OK);
Page page;
page.load(4);
CHECK(page.findItem(Page::NS_INDEX, ItemType::U8, "wifi") == ESP_OK);
}
TEST_CASE("storage may become full", "[nvs]")
{
SpiFlashEmulator emu(8);
Storage storage;
emu.setBounds(4, 8);
CHECK(storage.init(4, 4) == ESP_OK);
for (size_t i = 0; i < Page::ENTRY_COUNT * 3; ++i) {
char name[Item::MAX_KEY_LENGTH + 1];
snprintf(name, sizeof(name), "key%05d", static_cast<int>(i));
REQUIRE(storage.writeItem(1, name, static_cast<int>(i)) == ESP_OK);
}
REQUIRE(storage.writeItem(1, "foo", 10) == ESP_ERR_NVS_NOT_ENOUGH_SPACE);
}
TEST_CASE("can modify an item on a page which will be erased", "[nvs]")
{
SpiFlashEmulator emu(2);
Storage storage;
CHECK(storage.init(0, 2) == ESP_OK);
for (size_t i = 0; i < Page::ENTRY_COUNT * 3 + 1; ++i) {
REQUIRE(storage.writeItem(1, "foo", 42U) == ESP_OK);
}
}
#define TEST_ESP_ERR(rc, res) CHECK((rc) == (res))
#define TEST_ESP_OK(rc) CHECK((rc) == ESP_OK)
TEST_CASE("nvs api tests", "[nvs]")
{
SpiFlashEmulator emu(10);
emu.randomize(100);
nvs_handle handle_1;
const uint32_t NVS_FLASH_SECTOR = 6;
const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3;
emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);
TEST_ESP_ERR(nvs_open("namespace1", NVS_READWRITE, &handle_1), ESP_ERR_NVS_NOT_INITIALIZED);
for (uint16_t i = NVS_FLASH_SECTOR; i <NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN; ++i) {
spi_flash_erase_sector(i);
}
TEST_ESP_OK(nvs_flash_init(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN));
TEST_ESP_ERR(nvs_open("namespace1", NVS_READONLY, &handle_1), ESP_ERR_NVS_NOT_FOUND);
// TEST_ESP_ERR(nvs_set_i32(handle_1, "foo", 0x12345678), ESP_ERR_NVS_READ_ONLY);
// nvs_close(handle_1);
TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle_1));
TEST_ESP_OK(nvs_set_i32(handle_1, "foo", 0x12345678));
TEST_ESP_OK(nvs_set_i32(handle_1, "foo", 0x23456789));
nvs_handle handle_2;
TEST_ESP_OK(nvs_open("namespace2", NVS_READWRITE, &handle_2));
TEST_ESP_OK(nvs_set_i32(handle_2, "foo", 0x3456789a));
const char* str = "value 0123456789abcdef0123456789abcdef";
TEST_ESP_OK(nvs_set_str(handle_2, "key", str));
int32_t v1;
TEST_ESP_OK(nvs_get_i32(handle_1, "foo", &v1));
CHECK(0x23456789 == v1);
int32_t v2;
TEST_ESP_OK(nvs_get_i32(handle_2, "foo", &v2));
CHECK(0x3456789a == v2);
char buf[strlen(str) + 1];
size_t buf_len = sizeof(buf);
TEST_ESP_OK(nvs_get_str(handle_2, "key", buf, &buf_len));
CHECK(0 == strcmp(buf, str));
}
TEST_CASE("wifi test", "[nvs]")
{
SpiFlashEmulator emu(10);
emu.randomize(10);
nvs_handle handle;
const uint32_t NVS_FLASH_SECTOR = 5;
const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3;
emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);
TEST_ESP_OK(nvs_flash_init(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN));
TEST_ESP_OK(nvs_open("nvs.net80211", NVS_READWRITE, &handle));
uint8_t opmode = 2;
if (nvs_get_u8(handle, "wifi.opmode", &opmode) != ESP_OK) {
TEST_ESP_OK(nvs_set_u8(handle, "wifi.opmode", opmode));
}
}
TEST_CASE("can init storage from flash with random contents", "[nvs]")
{
SpiFlashEmulator emu(10);
emu.randomize(42);
nvs_handle handle;
const uint32_t NVS_FLASH_SECTOR = 5;
const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3;
emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);
TEST_ESP_OK(nvs_flash_init(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN));
TEST_ESP_OK(nvs_open("nvs.net80211", NVS_READWRITE, &handle));
uint8_t opmode = 2;
if (nvs_get_u8(handle, "wifi.opmode", &opmode) != ESP_OK) {
TEST_ESP_OK(nvs_set_u8(handle, "wifi.opmode", opmode));
}
}
TEST_CASE("nvs api tests, starting with random data in flash", "[nvs][.][long]")
{
for (size_t count = 0; count < 10000; ++count) {
SpiFlashEmulator emu(10);
emu.randomize(static_cast<uint32_t>(count));
const uint32_t NVS_FLASH_SECTOR = 6;
const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3;
emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);
TEST_ESP_OK(nvs_flash_init(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN));
nvs_handle handle_1;
TEST_ESP_ERR(nvs_open("namespace1", NVS_READONLY, &handle_1), ESP_ERR_NVS_NOT_FOUND);
TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle_1));
TEST_ESP_OK(nvs_set_i32(handle_1, "foo", 0x12345678));
for (size_t i = 0; i < 500; ++i) {
nvs_handle handle_2;
TEST_ESP_OK(nvs_open("namespace2", NVS_READWRITE, &handle_2));
TEST_ESP_OK(nvs_set_i32(handle_1, "foo", 0x23456789 % (i + 1)));
TEST_ESP_OK(nvs_set_i32(handle_2, "foo", static_cast<int32_t>(i)));
const char* str = "value 0123456789abcdef0123456789abcdef %09d";
char str_buf[128];
snprintf(str_buf, sizeof(str_buf), str, i + count * 1024);
TEST_ESP_OK(nvs_set_str(handle_2, "key", str_buf));
int32_t v1;
TEST_ESP_OK(nvs_get_i32(handle_1, "foo", &v1));
CHECK(0x23456789 % (i + 1) == v1);
int32_t v2;
TEST_ESP_OK(nvs_get_i32(handle_2, "foo", &v2));
CHECK(static_cast<int32_t>(i) == v2);
char buf[128];
size_t buf_len = sizeof(buf);
TEST_ESP_OK(nvs_get_str(handle_2, "key", buf, &buf_len));
CHECK(0 == strcmp(buf, str_buf));
nvs_close(handle_2);
}
nvs_close(handle_1);
}
}
template<typename TGen>
esp_err_t doRandomThings(nvs_handle handle, TGen gen, size_t count) {
const char* keys[] = {"foo", "bar", "longkey_0123456", "another key", "param1", "param2", "param3", "param4", "param5"};
const ItemType types[] = {ItemType::I32, ItemType::I32, ItemType::U64, ItemType::U64, ItemType::SZ, ItemType::SZ, ItemType::SZ, ItemType::SZ, ItemType::SZ};
int32_t v1 = 0, v2 = 0;
uint64_t v3 = 0, v4 = 0;
const size_t strBufLen = 1024;
char v5[strBufLen], v6[strBufLen], v7[strBufLen], v8[strBufLen], v9[strBufLen];
void* values[] = {&v1, &v2, &v3, &v4, &v5, &v6, &v7, &v8, &v9};
const size_t nKeys = sizeof(keys) / sizeof(keys[0]);
static_assert(nKeys == sizeof(types) / sizeof(types[0]), "");
static_assert(nKeys == sizeof(values) / sizeof(values[0]), "");
bool written[nKeys];
std::fill_n(written, nKeys, false);
auto generateRandomString = [](char* dst, size_t size) {
size_t len = 0;
};
auto randomRead = [&](size_t index) -> esp_err_t {
switch (types[index]) {
case ItemType::I32:
{
int32_t val;
auto err = nvs_get_i32(handle, keys[index], &val);
if (err == ESP_ERR_FLASH_OP_FAIL) {
return err;
}
if (!written[index]) {
REQUIRE(err == ESP_ERR_NVS_NOT_FOUND);
}
else {
REQUIRE(val == *reinterpret_cast<int32_t*>(values[index]));
}
break;
}
case ItemType::U64:
{
uint64_t val;
auto err = nvs_get_u64(handle, keys[index], &val);
if (err == ESP_ERR_FLASH_OP_FAIL) {
return err;
}
if (!written[index]) {
REQUIRE(err == ESP_ERR_NVS_NOT_FOUND);
}
else {
REQUIRE(val == *reinterpret_cast<uint64_t*>(values[index]));
}
break;
}
case ItemType::SZ:
{
char buf[strBufLen];
size_t len = strBufLen;
auto err = nvs_get_str(handle, keys[index], buf, &len);
if (err == ESP_ERR_FLASH_OP_FAIL) {
return err;
}
if (!written[index]) {
REQUIRE(err == ESP_ERR_NVS_NOT_FOUND);
}
else {
REQUIRE(strncmp(buf, reinterpret_cast<const char*>(values[index]), strBufLen));
}
break;
}
default:
assert(0);
}
return ESP_OK;
};
auto randomWrite = [&](size_t index) -> esp_err_t {
switch (types[index]) {
case ItemType::I32:
{
int32_t val = static_cast<int32_t>(gen());
*reinterpret_cast<int32_t*>(values[index]) = val;
auto err = nvs_set_i32(handle, keys[index], val);
if (err == ESP_ERR_FLASH_OP_FAIL) {
return err;
}
REQUIRE(err == ESP_OK);
written[index] = true;
break;
}
case ItemType::U64:
{
uint64_t val = static_cast<uint64_t>(gen());
*reinterpret_cast<uint64_t*>(values[index]) = val;
auto err = nvs_set_u64(handle, keys[index], val);
if (err == ESP_ERR_FLASH_OP_FAIL) {
return err;
}
REQUIRE(err == ESP_OK);
written[index] = true;
break;
}
case ItemType::SZ:
{
char buf[strBufLen];
size_t len = strBufLen;
size_t strLen = gen() % (strBufLen - 1);
std::generate_n(buf, strLen, [&]() -> char {
const char c = static_cast<char>(gen() % 127);
return (c < 32) ? 32 : c;
});
auto err = nvs_set_str(handle, keys[index], buf);
if (err == ESP_ERR_FLASH_OP_FAIL) {
return err;
}
REQUIRE(err == ESP_OK);
written[index] = true;
break;
}
default:
assert(0);
}
return ESP_OK;
};
for (size_t i = 0; i < count; ++i) {
size_t index = gen() % nKeys;
switch (gen() % 3) {
case 0: // read, 1/3
if (randomRead(index) == ESP_ERR_FLASH_OP_FAIL) {
return ESP_ERR_FLASH_OP_FAIL;
}
break;
default: // write, 2/3
if (randomWrite(index) == ESP_ERR_FLASH_OP_FAIL) {
return ESP_ERR_FLASH_OP_FAIL;
}
break;
}
}
return ESP_OK;
}
TEST_CASE("monkey test", "[nvs][monkey]")
{
std::random_device rd;
std::mt19937 gen(rd());
uint32_t seed = 3;
gen.seed(seed);
SpiFlashEmulator emu(10);
emu.randomize(seed);
const uint32_t NVS_FLASH_SECTOR = 6;
const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3;
emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);
TEST_ESP_OK(nvs_flash_init(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN));
nvs_handle handle;
TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle));
CHECK(doRandomThings(handle, gen, 10000) == ESP_OK);
}
TEST_CASE("dump all performance data", "[nvs]")
{
std::cout << "====================" << std::endl << "Dumping benchmarks" << std::endl;
std::cout << s_perf.str() << std::endl;
std::cout << "====================" << std::endl;
}
@@ -0,0 +1,154 @@
// 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 "catch.hpp"
#include "esp_spi_flash.h"
#include "spi_flash_emulation.h"
using namespace std;
template <typename Tit>
bool range_empty_n(Tit it_begin, size_t n)
{
return all_of(it_begin, it_begin + n, bind(equal_to<uint32_t>(), placeholders::_1, 0xffffffff));
}
TEST_CASE("flash starts with all bytes == 0xff", "[spi_flash_emu]")
{
SpiFlashEmulator emu(4);
uint8_t sector[SPI_FLASH_SEC_SIZE];
for (int i = 0; i < 4; ++i) {
CHECK(spi_flash_read(0, reinterpret_cast<uint32_t*>(sector), sizeof(sector)) == ESP_OK);
for (auto v: sector) {
CHECK(v == 0xff);
}
}
}
TEST_CASE("invalid writes are checked", "[spi_flash_emu]")
{
SpiFlashEmulator emu(1);
uint32_t val = 0;
CHECK(spi_flash_write(0, &val, 4) == ESP_OK);
val = 1;
CHECK(spi_flash_write(0, &val, 4) == ESP_ERR_FLASH_OP_FAIL);
}
TEST_CASE("out of bounds writes fail", "[spi_flash_emu]")
{
SpiFlashEmulator emu(4);
uint32_t vals[8];
std::fill_n(vals, 8, 0);
CHECK(spi_flash_write(0, vals, sizeof(vals)) == ESP_OK);
CHECK(spi_flash_write(4*4096 - sizeof(vals), vals, sizeof(vals)) == ESP_OK);
CHECK(spi_flash_write(4*4096 - sizeof(vals) + 4, vals, sizeof(vals)) == ESP_ERR_FLASH_OP_FAIL);
}
TEST_CASE("after erase the sector is set to 0xff", "[spi_flash_emu]")
{
SpiFlashEmulator emu(4);
uint32_t val1 = 0xab00cd12;
CHECK(spi_flash_write(0, &val1, sizeof(val1)) == ESP_OK);
uint32_t val2 = 0x5678efab;
CHECK(spi_flash_write(4096 - 4, &val2, sizeof(val2)) == ESP_OK);
CHECK(emu.words()[0] == val1);
CHECK(range_empty_n(emu.words() + 1, 4096 / 4 - 2));
CHECK(emu.words()[4096 / 4 - 1] == val2);
CHECK(spi_flash_erase_sector(0) == ESP_OK);
CHECK(emu.words()[0] == 0xffffffff);
CHECK(range_empty_n(emu.words() + 1, 4096 / 4 - 2));
CHECK(emu.words()[4096 / 4 - 1] == 0xffffffff);
}
TEST_CASE("read/write/erase operation times are calculated correctly", "[spi_flash_emu]")
{
SpiFlashEmulator emu(1);
uint32_t data[128];
spi_flash_read(0, data, 4);
CHECK(emu.getTotalTime() == 7);
CHECK(emu.getReadOps() == 1);
CHECK(emu.getReadBytes() == 4);
emu.clearStats();
spi_flash_read(0, data, 8);
CHECK(emu.getTotalTime() == 5);
CHECK(emu.getReadOps() == 1);
CHECK(emu.getReadBytes() == 8);
emu.clearStats();
spi_flash_read(0, data, 16);
CHECK(emu.getTotalTime() == 6);
CHECK(emu.getReadOps() == 1);
CHECK(emu.getReadBytes() == 16);
emu.clearStats();
spi_flash_read(0, data, 128);
CHECK(emu.getTotalTime() == 18);
CHECK(emu.getReadOps() == 1);
CHECK(emu.getReadBytes() == 128);
emu.clearStats();
spi_flash_read(0, data, 256);
CHECK(emu.getTotalTime() == 32);
emu.clearStats();
spi_flash_read(0, data, (128+256)/2);
CHECK(emu.getTotalTime() == (18+32)/2);
emu.clearStats();
spi_flash_write(0, data, 4);
CHECK(emu.getTotalTime() == 19);
CHECK(emu.getWriteOps() == 1);
CHECK(emu.getWriteBytes() == 4);
emu.clearStats();
CHECK(emu.getWriteOps() == 0);
CHECK(emu.getWriteBytes() == 0);
spi_flash_write(0, data, 8);
CHECK(emu.getTotalTime() == 23);
emu.clearStats();
spi_flash_write(0, data, 16);
CHECK(emu.getTotalTime() == 35);
CHECK(emu.getWriteOps() == 1);
CHECK(emu.getWriteBytes() == 16);
emu.clearStats();
spi_flash_write(0, data, 128);
CHECK(emu.getTotalTime() == 205);
emu.clearStats();
spi_flash_write(0, data, 256);
CHECK(emu.getTotalTime() == 417);
emu.clearStats();
spi_flash_write(0, data, (128+256)/2);
CHECK(emu.getTotalTime() == (205+417)/2);
emu.clearStats();
spi_flash_erase_sector(0);
CHECK(emu.getEraseOps() == 1);
CHECK(emu.getTotalTime() == 37142);
}
TEST_CASE("data is randomized predicatbly", "[spi_flash_emu]")
{
SpiFlashEmulator emu1(3);
emu1.randomize(0x12345678);
SpiFlashEmulator emu2(3);
emu2.randomize(0x12345678);
CHECK(std::equal(emu1.bytes(), emu1.bytes() + emu1.size(), emu2.bytes()));
}