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 #include "nvs_pagemanager.hpp"
15
16 namespace nvs
17 {
load(Partition * partition,uint32_t baseSector,uint32_t sectorCount)18 esp_err_t PageManager::load(Partition *partition, uint32_t baseSector, uint32_t sectorCount)
19 {
20 if (partition == nullptr) {
21 return ESP_ERR_INVALID_ARG;
22 }
23
24 mBaseSector = baseSector;
25 mPageCount = sectorCount;
26 mPageList.clear();
27 mFreePageList.clear();
28 mPages.reset(new (nothrow) Page[sectorCount]);
29
30 if (!mPages) return ESP_ERR_NO_MEM;
31
32 for (uint32_t i = 0; i < sectorCount; ++i) {
33 auto err = mPages[i].load(partition, baseSector + i);
34 if (err != ESP_OK) {
35 return err;
36 }
37 uint32_t seqNumber;
38 if (mPages[i].getSeqNumber(seqNumber) != ESP_OK) {
39 mFreePageList.push_back(&mPages[i]);
40 } else {
41 auto pos = std::find_if(std::begin(mPageList), std::end(mPageList), [=](const Page& page) -> bool {
42 uint32_t otherSeqNumber;
43 return page.getSeqNumber(otherSeqNumber) == ESP_OK && otherSeqNumber > seqNumber;
44 });
45 if (pos == mPageList.end()) {
46 mPageList.push_back(&mPages[i]);
47 } else {
48 mPageList.insert(pos, &mPages[i]);
49 }
50 }
51 }
52
53 if (mPageList.empty()) {
54 mSeqNumber = 0;
55 return activatePage();
56 } else {
57 uint32_t lastSeqNo;
58 ESP_ERROR_CHECK( mPageList.back().getSeqNumber(lastSeqNo) );
59 mSeqNumber = lastSeqNo + 1;
60 }
61
62 // if power went out after a new item for the given key was written,
63 // but before the old one was erased, we end up with a duplicate item
64 Page& lastPage = back();
65 size_t lastItemIndex = SIZE_MAX;
66 Item item;
67 size_t itemIndex = 0;
68 while (lastPage.findItem(Page::NS_ANY, ItemType::ANY, nullptr, itemIndex, item) == ESP_OK) {
69 itemIndex += item.span;
70 lastItemIndex = itemIndex;
71 }
72
73 if (lastItemIndex != SIZE_MAX) {
74 auto last = PageManager::TPageListIterator(&lastPage);
75 TPageListIterator it;
76
77 for (it = begin(); it != last; ++it) {
78
79 if ((it->state() != Page::PageState::FREEING) &&
80 (it->eraseItem(item.nsIndex, item.datatype, item.key, item.chunkIndex) == ESP_OK)) {
81 break;
82 }
83 }
84 if ((it == last) && (item.datatype == ItemType::BLOB_IDX)) {
85 /* Rare case in which the blob was stored using old format, but power went just after writing
86 * blob index during modification. Loop again and delete the old version blob*/
87 for (it = begin(); it != last; ++it) {
88
89 if ((it->state() != Page::PageState::FREEING) &&
90 (it->eraseItem(item.nsIndex, ItemType::BLOB, item.key, item.chunkIndex) == ESP_OK)) {
91 break;
92 }
93 }
94 }
95 }
96
97 // check if power went out while page was being freed
98 for (auto it = begin(); it!= end(); ++it) {
99 if (it->state() == Page::PageState::FREEING) {
100 Page* newPage = &mPageList.back();
101 if (newPage->state() == Page::PageState::ACTIVE) {
102 auto err = newPage->erase();
103 if (err != ESP_OK) {
104 return err;
105 }
106 mPageList.erase(newPage);
107 mFreePageList.push_back(newPage);
108 }
109 auto err = activatePage();
110 if (err != ESP_OK) {
111 return err;
112 }
113 newPage = &mPageList.back();
114
115 err = it->copyItems(*newPage);
116 if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) {
117 return err;
118 }
119
120 err = it->erase();
121 if (err != ESP_OK) {
122 return err;
123 }
124
125 Page* p = static_cast<Page*>(it);
126 mPageList.erase(it);
127 mFreePageList.push_back(p);
128 break;
129 }
130 }
131
132 // partition should have at least one free page
133 if (mFreePageList.empty()) {
134 return ESP_ERR_NVS_NO_FREE_PAGES;
135 }
136
137 return ESP_OK;
138 }
139
requestNewPage()140 esp_err_t PageManager::requestNewPage()
141 {
142 if (mFreePageList.empty()) {
143 return ESP_ERR_NVS_INVALID_STATE;
144 }
145
146 // do we have at least two free pages? in that case no erasing is required
147 if (mFreePageList.size() >= 2) {
148 return activatePage();
149 }
150
151 // find the page with the higest number of erased items
152 TPageListIterator maxUnusedItemsPageIt;
153 size_t maxUnusedItems = 0;
154 for (auto it = begin(); it != end(); ++it) {
155
156 auto unused = Page::ENTRY_COUNT - it->getUsedEntryCount();
157 if (unused > maxUnusedItems) {
158 maxUnusedItemsPageIt = it;
159 maxUnusedItems = unused;
160 }
161 }
162
163 if (maxUnusedItems == 0) {
164 return ESP_ERR_NVS_NOT_ENOUGH_SPACE;
165 }
166
167 esp_err_t err = activatePage();
168 if (err != ESP_OK) {
169 return err;
170 }
171
172 Page* newPage = &mPageList.back();
173
174 Page* erasedPage = maxUnusedItemsPageIt;
175
176 #ifndef NDEBUG
177 size_t usedEntries = erasedPage->getUsedEntryCount();
178 #endif
179 err = erasedPage->markFreeing();
180 if (err != ESP_OK) {
181 return err;
182 }
183 err = erasedPage->copyItems(*newPage);
184 if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) {
185 return err;
186 }
187
188 err = erasedPage->erase();
189 if (err != ESP_OK) {
190 return err;
191 }
192
193 #ifndef NDEBUG
194 assert(usedEntries == newPage->getUsedEntryCount());
195 #endif
196
197 mPageList.erase(maxUnusedItemsPageIt);
198 mFreePageList.push_back(erasedPage);
199
200 return ESP_OK;
201 }
202
activatePage()203 esp_err_t PageManager::activatePage()
204 {
205 if (mFreePageList.empty()) {
206 return ESP_ERR_NVS_NOT_ENOUGH_SPACE;
207 }
208 Page* p = &mFreePageList.front();
209 if (p->state() == Page::PageState::CORRUPT) {
210 auto err = p->erase();
211 if (err != ESP_OK) {
212 return err;
213 }
214 }
215 mFreePageList.pop_front();
216 mPageList.push_back(p);
217 p->setSeqNumber(mSeqNumber);
218 ++mSeqNumber;
219 return ESP_OK;
220 }
221
fillStats(nvs_stats_t & nvsStats)222 esp_err_t PageManager::fillStats(nvs_stats_t& nvsStats)
223 {
224 nvsStats.used_entries = 0;
225 nvsStats.free_entries = 0;
226 nvsStats.total_entries = 0;
227 esp_err_t err = ESP_OK;
228
229 // list of used pages
230 for (auto p = mPageList.begin(); p != mPageList.end(); ++p) {
231 err = p->calcEntries(nvsStats);
232 if (err != ESP_OK) {
233 return err;
234 }
235 }
236
237 // free pages
238 nvsStats.total_entries += mFreePageList.size() * Page::ENTRY_COUNT;
239 nvsStats.free_entries += mFreePageList.size() * Page::ENTRY_COUNT;
240
241 return err;
242 }
243
244 } // namespace nvs
245