mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
ac4d9888e0
add: private include header add: macro encapsulation for assertion or error check add: ESP_FAIL return code documentation in public headers change: replaced all assertions by NVS_ASSERT_OR_RETURN macro change: few internal function return values from void to esp_err_t change: ESP_ERR_NVS_VALUE_TOO_LONG macro comment
237 lines
6.6 KiB
C++
237 lines
6.6 KiB
C++
/*
|
|
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
#include "nvs_pagemanager.hpp"
|
|
|
|
namespace nvs
|
|
{
|
|
esp_err_t PageManager::load(Partition *partition, uint32_t baseSector, uint32_t sectorCount)
|
|
{
|
|
if (partition == nullptr) {
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
mBaseSector = baseSector;
|
|
mPageCount = sectorCount;
|
|
mPageList.clear();
|
|
mFreePageList.clear();
|
|
mPages.reset(new (nothrow) Page[sectorCount]);
|
|
|
|
if (!mPages) return ESP_ERR_NO_MEM;
|
|
|
|
for (uint32_t i = 0; i < sectorCount; ++i) {
|
|
auto err = mPages[i].load(partition, baseSector + i);
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
uint32_t seqNumber;
|
|
if (mPages[i].getSeqNumber(seqNumber) != ESP_OK) {
|
|
mFreePageList.push_back(&mPages[i]);
|
|
} else {
|
|
auto pos = std::find_if(std::begin(mPageList), std::end(mPageList), [=](const Page& page) -> bool {
|
|
uint32_t otherSeqNumber;
|
|
return page.getSeqNumber(otherSeqNumber) == ESP_OK && otherSeqNumber > seqNumber;
|
|
});
|
|
if (pos == mPageList.end()) {
|
|
mPageList.push_back(&mPages[i]);
|
|
} else {
|
|
mPageList.insert(pos, &mPages[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mPageList.empty()) {
|
|
mSeqNumber = 0;
|
|
return activatePage();
|
|
} else {
|
|
uint32_t lastSeqNo;
|
|
ESP_ERROR_CHECK( mPageList.back().getSeqNumber(lastSeqNo) );
|
|
mSeqNumber = lastSeqNo + 1;
|
|
}
|
|
|
|
// if power went out after a new item for the given key was written,
|
|
// but before the old one was erased, we end up with a duplicate item
|
|
Page& lastPage = back();
|
|
size_t lastItemIndex = SIZE_MAX;
|
|
Item item;
|
|
size_t itemIndex = 0;
|
|
while (lastPage.findItem(Page::NS_ANY, ItemType::ANY, nullptr, itemIndex, item) == ESP_OK) {
|
|
itemIndex += item.span;
|
|
lastItemIndex = itemIndex;
|
|
}
|
|
|
|
if (lastItemIndex != SIZE_MAX) {
|
|
auto last = PageManager::TPageListIterator(&lastPage);
|
|
TPageListIterator it;
|
|
|
|
for (it = begin(); it != last; ++it) {
|
|
|
|
if ((it->state() != Page::PageState::FREEING) &&
|
|
(it->eraseItem(item.nsIndex, item.datatype, item.key, item.chunkIndex) == ESP_OK)) {
|
|
break;
|
|
}
|
|
}
|
|
if ((it == last) && (item.datatype == ItemType::BLOB_IDX)) {
|
|
/* Rare case in which the blob was stored using old format, but power went just after writing
|
|
* blob index during modification. Loop again and delete the old version blob*/
|
|
for (it = begin(); it != last; ++it) {
|
|
|
|
if ((it->state() != Page::PageState::FREEING) &&
|
|
(it->eraseItem(item.nsIndex, ItemType::BLOB, item.key, item.chunkIndex) == ESP_OK)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// check if power went out while page was being freed
|
|
for (auto it = begin(); it!= end(); ++it) {
|
|
if (it->state() == Page::PageState::FREEING) {
|
|
Page* newPage = &mPageList.back();
|
|
if (newPage->state() == Page::PageState::ACTIVE) {
|
|
auto err = newPage->erase();
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
mPageList.erase(newPage);
|
|
mFreePageList.push_back(newPage);
|
|
}
|
|
auto err = activatePage();
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
newPage = &mPageList.back();
|
|
|
|
err = it->copyItems(*newPage);
|
|
if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) {
|
|
return err;
|
|
}
|
|
|
|
err = it->erase();
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
|
|
Page* p = static_cast<Page*>(it);
|
|
mPageList.erase(it);
|
|
mFreePageList.push_back(p);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// partition should have at least one free page
|
|
if (mFreePageList.empty()) {
|
|
return ESP_ERR_NVS_NO_FREE_PAGES;
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t PageManager::requestNewPage()
|
|
{
|
|
if (mFreePageList.empty()) {
|
|
return ESP_ERR_NVS_INVALID_STATE;
|
|
}
|
|
|
|
// do we have at least two free pages? in that case no erasing is required
|
|
if (mFreePageList.size() >= 2) {
|
|
return activatePage();
|
|
}
|
|
|
|
// find the page with the higest number of erased items
|
|
TPageListIterator maxUnusedItemsPageIt;
|
|
size_t maxUnusedItems = 0;
|
|
for (auto it = begin(); it != end(); ++it) {
|
|
|
|
auto unused = Page::ENTRY_COUNT - it->getUsedEntryCount();
|
|
if (unused > maxUnusedItems) {
|
|
maxUnusedItemsPageIt = it;
|
|
maxUnusedItems = unused;
|
|
}
|
|
}
|
|
|
|
if (maxUnusedItems == 0) {
|
|
return ESP_ERR_NVS_NOT_ENOUGH_SPACE;
|
|
}
|
|
|
|
esp_err_t err = activatePage();
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
|
|
Page* newPage = &mPageList.back();
|
|
|
|
Page* erasedPage = maxUnusedItemsPageIt;
|
|
|
|
#ifndef NDEBUG
|
|
size_t usedEntries = erasedPage->getUsedEntryCount();
|
|
#endif
|
|
err = erasedPage->markFreeing();
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
err = erasedPage->copyItems(*newPage);
|
|
if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) {
|
|
return err;
|
|
}
|
|
|
|
err = erasedPage->erase();
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
NVS_ASSERT_OR_RETURN(usedEntries == newPage->getUsedEntryCount(), ESP_FAIL);
|
|
#endif
|
|
|
|
mPageList.erase(maxUnusedItemsPageIt);
|
|
mFreePageList.push_back(erasedPage);
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t PageManager::activatePage()
|
|
{
|
|
if (mFreePageList.empty()) {
|
|
return ESP_ERR_NVS_NOT_ENOUGH_SPACE;
|
|
}
|
|
Page* p = &mFreePageList.front();
|
|
if (p->state() == Page::PageState::CORRUPT) {
|
|
auto err = p->erase();
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
}
|
|
mFreePageList.pop_front();
|
|
mPageList.push_back(p);
|
|
p->setSeqNumber(mSeqNumber);
|
|
++mSeqNumber;
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t PageManager::fillStats(nvs_stats_t& nvsStats)
|
|
{
|
|
nvsStats.used_entries = 0;
|
|
nvsStats.free_entries = 0;
|
|
nvsStats.total_entries = 0;
|
|
esp_err_t err = ESP_OK;
|
|
|
|
// list of used pages
|
|
for (auto p = mPageList.begin(); p != mPageList.end(); ++p) {
|
|
err = p->calcEntries(nvsStats);
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
// free pages
|
|
nvsStats.total_entries += mFreePageList.size() * Page::ENTRY_COUNT;
|
|
nvsStats.free_entries += mFreePageList.size() * Page::ENTRY_COUNT;
|
|
|
|
return err;
|
|
}
|
|
|
|
} // namespace nvs
|