1 // Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 #ifndef nvs_page_hpp
15 #define nvs_page_hpp
16 
17 #include "nvs.h"
18 #include "nvs_types.hpp"
19 #include <cstdint>
20 #include <type_traits>
21 #include <cstring>
22 #include <algorithm>
23 #include "esp_spi_flash.h"
24 #include "compressed_enum_table.hpp"
25 #include "intrusive_list.h"
26 #include "nvs_item_hash_list.hpp"
27 #include "partition.hpp"
28 
29 namespace nvs
30 {
31 
32 
33 class Page : public intrusive_list_node<Page>
34 {
35 public:
36     static const uint32_t PSB_INIT = 0x1;
37     static const uint32_t PSB_FULL = 0x2;
38     static const uint32_t PSB_FREEING = 0x4;
39     static const uint32_t PSB_CORRUPT = 0x8;
40 
41     static const uint32_t ESB_WRITTEN = 0x1;
42     static const uint32_t ESB_ERASED = 0x2;
43 
44     static const uint32_t SEC_SIZE = SPI_FLASH_SEC_SIZE;
45 
46     static const size_t ENTRY_SIZE  = 32;
47     static const size_t ENTRY_COUNT = 126;
48     static const uint32_t INVALID_ENTRY = 0xffffffff;
49 
50     static const size_t CHUNK_MAX_SIZE = ENTRY_SIZE * (ENTRY_COUNT - 1);
51 
52     static const uint8_t NS_INDEX = 0;
53     static const uint8_t NS_ANY = 255;
54 
55     static const uint8_t CHUNK_ANY = Item::CHUNK_ANY;
56 
57     static const uint8_t NVS_VERSION = 0xfe; // Decrement to upgrade
58 
59     enum class PageState : uint32_t {
60         // All bits set, default state after flash erase. Page has not been initialized yet.
61         UNINITIALIZED = 0xffffffff,
62 
63         // Page is initialized, and will accept writes.
64         ACTIVE        = UNINITIALIZED & ~PSB_INIT,
65 
66         // Page is marked as full and will not accept new writes.
67         FULL          = ACTIVE & ~PSB_FULL,
68 
69         // Data is being moved from this page to a new one.
70         FREEING       = FULL & ~PSB_FREEING,
71 
72         // Page was found to be in a corrupt and unrecoverable state.
73         // Instead of being erased immediately, it will be kept for diagnostics and data recovery.
74         // It will be erased once we run out out free pages.
75         CORRUPT       = FREEING & ~PSB_CORRUPT,
76 
77         // Page object wasn't loaded from flash memory
78         INVALID       = 0
79     };
80 
81     Page();
82 
state() const83     PageState state() const
84     {
85         return mState;
86     }
87 
88     esp_err_t load(Partition *partition, uint32_t sectorNumber);
89 
90     esp_err_t getSeqNumber(uint32_t& seqNumber) const;
91 
92     esp_err_t setSeqNumber(uint32_t seqNumber);
93 
94     esp_err_t setVersion(uint8_t version);
95 
96     esp_err_t writeItem(uint8_t nsIndex, ItemType datatype, const char* key, const void* data, size_t dataSize, uint8_t chunkIdx = CHUNK_ANY);
97 
98     esp_err_t readItem(uint8_t nsIndex, ItemType datatype, const char* key, void* data, size_t dataSize, uint8_t chunkIdx = CHUNK_ANY, VerOffset chunkStart = VerOffset::VER_ANY);
99 
100     esp_err_t cmpItem(uint8_t nsIndex, ItemType datatype, const char* key, const void* data, size_t dataSize, uint8_t chunkIdx = CHUNK_ANY, VerOffset chunkStart = VerOffset::VER_ANY);
101 
102     esp_err_t eraseItem(uint8_t nsIndex, ItemType datatype, const char* key, uint8_t chunkIdx = CHUNK_ANY, VerOffset chunkStart = VerOffset::VER_ANY);
103 
104     esp_err_t findItem(uint8_t nsIndex, ItemType datatype, const char* key, uint8_t chunkIdx = CHUNK_ANY, VerOffset chunkStart = VerOffset::VER_ANY);
105 
106     esp_err_t findItem(uint8_t nsIndex, ItemType datatype, const char* key, size_t &itemIndex, Item& item, uint8_t chunkIdx = CHUNK_ANY, VerOffset chunkStart = VerOffset::VER_ANY);
107 
108     template<typename T>
writeItem(uint8_t nsIndex,const char * key,const T & value)109     esp_err_t writeItem(uint8_t nsIndex, const char* key, const T& value)
110     {
111         return writeItem(nsIndex, itemTypeOf(value), key, &value, sizeof(value));
112     }
113 
114     template<typename T>
readItem(uint8_t nsIndex,const char * key,T & value)115     esp_err_t readItem(uint8_t nsIndex, const char* key, T& value)
116     {
117         return readItem(nsIndex, itemTypeOf(value), key, &value, sizeof(value));
118     }
119 
120     template<typename T>
cmpItem(uint8_t nsIndex,const char * key,const T & value)121     esp_err_t cmpItem(uint8_t nsIndex, const char* key, const T& value)
122     {
123         return cmpItem(nsIndex, itemTypeOf(value), key, &value, sizeof(value));
124     }
125 
126     template<typename T>
eraseItem(uint8_t nsIndex,const char * key)127     esp_err_t eraseItem(uint8_t nsIndex, const char* key)
128     {
129         return eraseItem(nsIndex, itemTypeOf<T>(), key);
130     }
131 
getUsedEntryCount() const132     size_t getUsedEntryCount() const
133     {
134         return mUsedEntryCount;
135     }
136 
getErasedEntryCount() const137     size_t getErasedEntryCount() const
138     {
139         return mErasedEntryCount;
140     }
141     size_t getVarDataTailroom() const ;
142 
143     esp_err_t markFull();
144 
145     esp_err_t markFreeing();
146 
147     esp_err_t copyItems(Page& other);
148 
149     esp_err_t erase();
150 
151     void debugDump() const;
152 
153     esp_err_t calcEntries(nvs_stats_t &nvsStats);
154 
155 protected:
156 
157     class Header
158     {
159     public:
Header()160         Header()
161         {
162             std::fill_n(mReserved, sizeof(mReserved)/sizeof(mReserved[0]), UINT8_MAX);
163         }
164 
165         PageState mState;       // page state
166         uint32_t mSeqNumber;    // sequence number of this page
167         uint8_t mVersion;       // nvs format version
168         uint8_t mReserved[19];  // unused, must be 0xff
169         uint32_t mCrc32;        // crc of everything except mState
170 
171         uint32_t calculateCrc32();
172     };
173 
174     enum class EntryState {
175         EMPTY   = 0x3, // 0b11, default state after flash erase
176         WRITTEN = EMPTY & ~ESB_WRITTEN, // entry was written
177         ERASED  = WRITTEN & ~ESB_ERASED, // entry was written and then erased
178         INVALID = 0x4 // entry is in inconsistent state (write started but ESB_WRITTEN has not been set yet)
179     };
180 
181     esp_err_t mLoadEntryTable();
182 
183     esp_err_t initialize();
184 
185     esp_err_t alterEntryState(size_t index, EntryState state);
186 
187     esp_err_t alterEntryRangeState(size_t begin, size_t end, EntryState state);
188 
189     esp_err_t alterPageState(PageState state);
190 
191     esp_err_t readEntry(size_t index, Item& dst) const;
192 
193     esp_err_t writeEntry(const Item& item);
194 
195     esp_err_t writeEntryData(const uint8_t* data, size_t size);
196 
197     esp_err_t eraseEntryAndSpan(size_t index);
198 
199     void updateFirstUsedEntry(size_t index, size_t span);
200 
getAlignmentForType(ItemType type)201     static constexpr size_t getAlignmentForType(ItemType type)
202     {
203         return static_cast<uint8_t>(type) & 0x0f;
204     }
205 
getEntryAddress(size_t entry) const206     uint32_t getEntryAddress(size_t entry) const
207     {
208         assert(entry < ENTRY_COUNT);
209         return mBaseAddress + ENTRY_DATA_OFFSET + static_cast<uint32_t>(entry) * ENTRY_SIZE;
210     }
211 
212     static const char* pageStateToName(PageState ps);
213 
214 
215 protected:
216     uint32_t mBaseAddress = 0;
217     PageState mState = PageState::INVALID;
218     uint32_t mSeqNumber = UINT32_MAX;
219     uint8_t mVersion = NVS_VERSION;
220     typedef CompressedEnumTable<EntryState, 2, ENTRY_COUNT> TEntryTable;
221     TEntryTable mEntryTable;
222     size_t mNextFreeEntry = INVALID_ENTRY;
223     size_t mFirstUsedEntry = INVALID_ENTRY;
224     uint16_t mUsedEntryCount = 0;
225     uint16_t mErasedEntryCount = 0;
226 
227     /**
228      * This hash list stores hashes of namespace index, key, and ChunkIndex for quick lookup when searching items.
229      */
230     HashList mHashList;
231 
232     Partition *mPartition;
233 
234     static const uint32_t HEADER_OFFSET = 0;
235     static const uint32_t ENTRY_TABLE_OFFSET = HEADER_OFFSET + 32;
236     static const uint32_t ENTRY_DATA_OFFSET = ENTRY_TABLE_OFFSET + 32;
237 
238     static_assert(sizeof(Header) == 32, "header size must be 32 bytes");
239     static_assert(ENTRY_TABLE_OFFSET % 32 == 0, "entry table offset should be aligned");
240     static_assert(ENTRY_DATA_OFFSET % 32 == 0, "entry data offset should be aligned");
241 
242 }; // class Page
243 
244 } // namespace nvs
245 
246 
247 #endif /* nvs_page_hpp */
248