nvs_pagemanager.cpp 6.6 KB

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