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