nvs_pagemanager.cpp 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  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. ESP_ERROR_CHECK( mPageList.back().getSeqNumber(lastSeqNo) );
  46. mSeqNumber = lastSeqNo + 1;
  47. }
  48. // if power went out after a new item for the given key was written,
  49. // but before the old one was erased, we end up with a duplicate item
  50. Page& lastPage = back();
  51. size_t lastItemIndex = SIZE_MAX;
  52. Item item;
  53. size_t itemIndex = 0;
  54. while (lastPage.findItem(Page::NS_ANY, ItemType::ANY, nullptr, itemIndex, item) == ESP_OK) {
  55. itemIndex += item.span;
  56. lastItemIndex = itemIndex;
  57. }
  58. if (lastItemIndex != SIZE_MAX) {
  59. auto last = PageManager::TPageListIterator(&lastPage);
  60. TPageListIterator it;
  61. for (it = begin(); it != last; ++it) {
  62. if ((it->state() != Page::PageState::FREEING) &&
  63. (it->eraseItem(item.nsIndex, item.datatype, item.key, item.chunkIndex) == ESP_OK)) {
  64. break;
  65. }
  66. }
  67. if ((it == last) && (item.datatype == ItemType::BLOB_IDX)) {
  68. /* Rare case in which the blob was stored using old format, but power went just after writing
  69. * blob index during modification. Loop again and delete the old version blob*/
  70. for (it = begin(); it != last; ++it) {
  71. if ((it->state() != Page::PageState::FREEING) &&
  72. (it->eraseItem(item.nsIndex, ItemType::BLOB, item.key, item.chunkIndex) == ESP_OK)) {
  73. break;
  74. }
  75. }
  76. }
  77. }
  78. // check if power went out while page was being freed
  79. for (auto it = begin(); it!= end(); ++it) {
  80. if (it->state() == Page::PageState::FREEING) {
  81. Page* newPage = &mPageList.back();
  82. if (newPage->state() == Page::PageState::ACTIVE) {
  83. auto err = newPage->erase();
  84. if (err != ESP_OK) {
  85. return err;
  86. }
  87. mPageList.erase(newPage);
  88. mFreePageList.push_back(newPage);
  89. }
  90. auto err = activatePage();
  91. if (err != ESP_OK) {
  92. return err;
  93. }
  94. newPage = &mPageList.back();
  95. err = it->copyItems(*newPage);
  96. if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) {
  97. return err;
  98. }
  99. err = it->erase();
  100. if (err != ESP_OK) {
  101. return err;
  102. }
  103. Page* p = static_cast<Page*>(it);
  104. mPageList.erase(it);
  105. mFreePageList.push_back(p);
  106. break;
  107. }
  108. }
  109. // partition should have at least one free page
  110. if (mFreePageList.empty()) {
  111. return ESP_ERR_NVS_NO_FREE_PAGES;
  112. }
  113. return ESP_OK;
  114. }
  115. esp_err_t PageManager::requestNewPage()
  116. {
  117. if (mFreePageList.empty()) {
  118. return ESP_ERR_NVS_INVALID_STATE;
  119. }
  120. // do we have at least two free pages? in that case no erasing is required
  121. if (mFreePageList.size() >= 2) {
  122. return activatePage();
  123. }
  124. // find the page with the higest number of erased items
  125. TPageListIterator maxUnusedItemsPageIt;
  126. size_t maxUnusedItems = 0;
  127. for (auto it = begin(); it != end(); ++it) {
  128. auto unused = Page::ENTRY_COUNT - it->getUsedEntryCount();
  129. if (unused > maxUnusedItems) {
  130. maxUnusedItemsPageIt = it;
  131. maxUnusedItems = unused;
  132. }
  133. }
  134. if (maxUnusedItems == 0) {
  135. return ESP_ERR_NVS_NOT_ENOUGH_SPACE;
  136. }
  137. esp_err_t err = activatePage();
  138. if (err != ESP_OK) {
  139. return err;
  140. }
  141. Page* newPage = &mPageList.back();
  142. Page* erasedPage = maxUnusedItemsPageIt;
  143. #ifndef NDEBUG
  144. size_t usedEntries = erasedPage->getUsedEntryCount();
  145. #endif
  146. err = erasedPage->markFreeing();
  147. if (err != ESP_OK) {
  148. return err;
  149. }
  150. err = erasedPage->copyItems(*newPage);
  151. if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) {
  152. return err;
  153. }
  154. err = erasedPage->erase();
  155. if (err != ESP_OK) {
  156. return err;
  157. }
  158. #ifndef NDEBUG
  159. NVS_ASSERT_OR_RETURN(usedEntries == newPage->getUsedEntryCount(), ESP_FAIL);
  160. #endif
  161. mPageList.erase(maxUnusedItemsPageIt);
  162. mFreePageList.push_back(erasedPage);
  163. return ESP_OK;
  164. }
  165. esp_err_t PageManager::activatePage()
  166. {
  167. if (mFreePageList.empty()) {
  168. return ESP_ERR_NVS_NOT_ENOUGH_SPACE;
  169. }
  170. Page* p = &mFreePageList.front();
  171. if (p->state() == Page::PageState::CORRUPT) {
  172. auto err = p->erase();
  173. if (err != ESP_OK) {
  174. return err;
  175. }
  176. }
  177. mFreePageList.pop_front();
  178. mPageList.push_back(p);
  179. p->setSeqNumber(mSeqNumber);
  180. ++mSeqNumber;
  181. return ESP_OK;
  182. }
  183. esp_err_t PageManager::fillStats(nvs_stats_t& nvsStats)
  184. {
  185. nvsStats.used_entries = 0;
  186. nvsStats.free_entries = 0;
  187. nvsStats.total_entries = 0;
  188. esp_err_t err = ESP_OK;
  189. // list of used pages
  190. for (auto p = mPageList.begin(); p != mPageList.end(); ++p) {
  191. err = p->calcEntries(nvsStats);
  192. if (err != ESP_OK) {
  193. return err;
  194. }
  195. }
  196. // free pages
  197. nvsStats.total_entries += mFreePageList.size() * Page::ENTRY_COUNT;
  198. nvsStats.free_entries += mFreePageList.size() * Page::ENTRY_COUNT;
  199. return err;
  200. }
  201. } // namespace nvs