nvs_page.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  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. // http://www.apache.org/licenses/LICENSE-2.0
  7. //
  8. // Unless required by applicable law or agreed to in writing, software
  9. // distributed under the License is distributed on an "AS IS" BASIS,
  10. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. // See the License for the specific language governing permissions and
  12. // limitations under the License.
  13. #include "nvs_page.hpp"
  14. #if defined(ESP_PLATFORM)
  15. #include <rom/crc.h>
  16. #else
  17. #include "crc.h"
  18. #endif
  19. namespace nvs
  20. {
  21. uint32_t Page::Header::calculateCrc32()
  22. {
  23. return crc32_le(0xffffffff,
  24. reinterpret_cast<uint8_t*>(this) + offsetof(Header, mSeqNumber),
  25. offsetof(Header, mCrc32) - offsetof(Header, mSeqNumber));
  26. }
  27. esp_err_t Page::load(uint32_t sectorNumber)
  28. {
  29. mBaseAddress = sectorNumber * SEC_SIZE;
  30. mUsedEntryCount = 0;
  31. mErasedEntryCount = 0;
  32. Header header;
  33. auto rc = spi_flash_read(mBaseAddress, reinterpret_cast<uint32_t*>(&header), sizeof(header));
  34. if (rc != ESP_OK) {
  35. mState = PageState::INVALID;
  36. return rc;
  37. }
  38. if (header.mState == PageState::UNINITIALIZED) {
  39. mState = header.mState;
  40. // check if the whole page is really empty
  41. // reading the whole page takes ~40 times less than erasing it
  42. uint32_t line[8];
  43. for (uint32_t i = 0; i < SPI_FLASH_SEC_SIZE; i += sizeof(line)) {
  44. rc = spi_flash_read(mBaseAddress + i, line, sizeof(line));
  45. if (rc != ESP_OK) {
  46. mState = PageState::INVALID;
  47. return rc;
  48. }
  49. if (std::any_of(line, line + 4, [](uint32_t val) -> bool { return val != 0xffffffff; })) {
  50. // page isn't as empty after all, mark it as corrupted
  51. mState = PageState::CORRUPT;
  52. break;
  53. }
  54. }
  55. }
  56. else if (header.mCrc32 != header.calculateCrc32()) {
  57. header.mState = PageState::CORRUPT;
  58. }
  59. else {
  60. mState = header.mState;
  61. mSeqNumber = header.mSeqNumber;
  62. }
  63. switch (mState) {
  64. case PageState::UNINITIALIZED:
  65. break;
  66. case PageState::FULL:
  67. case PageState::ACTIVE:
  68. case PageState::FREEING:
  69. mLoadEntryTable();
  70. break;
  71. default:
  72. mState = PageState::CORRUPT;
  73. break;
  74. }
  75. return ESP_OK;
  76. }
  77. esp_err_t Page::writeEntry(const Item& item)
  78. {
  79. auto rc = spi_flash_write(getEntryAddress(mNextFreeEntry), reinterpret_cast<const uint32_t*>(&item), sizeof(item));
  80. if (rc != ESP_OK) {
  81. mState = PageState::INVALID;
  82. return rc;
  83. }
  84. auto err = alterEntryState(mNextFreeEntry, EntryState::WRITTEN);
  85. if (err != ESP_OK) {
  86. return err;
  87. }
  88. if (mNextFreeEntry == 0) {
  89. mFirstUsedEntry = 0;
  90. }
  91. ++mUsedEntryCount;
  92. if (++mNextFreeEntry == ENTRY_COUNT) {
  93. alterPageState(PageState::FULL);
  94. }
  95. return ESP_OK;
  96. }
  97. esp_err_t Page::writeItem(uint8_t nsIndex, ItemType datatype, const char* key, const void* data, size_t dataSize)
  98. {
  99. Item item;
  100. esp_err_t err;
  101. if (mState == PageState::UNINITIALIZED) {
  102. err = initialize();
  103. if (err != ESP_OK) {
  104. return err;
  105. }
  106. }
  107. if (mState == PageState::FULL) {
  108. return ESP_ERR_NVS_PAGE_FULL;
  109. }
  110. const size_t keySize = strlen(key);
  111. if (keySize > Item::MAX_KEY_LENGTH) {
  112. return ESP_ERR_NVS_KEY_TOO_LONG;
  113. }
  114. size_t totalSize = ENTRY_SIZE;
  115. size_t entriesCount = 1;
  116. if (datatype == ItemType::SZ || datatype == ItemType::BLOB) {
  117. size_t roundedSize = (dataSize + ENTRY_SIZE - 1) & ~(ENTRY_SIZE - 1);
  118. totalSize += roundedSize;
  119. entriesCount += roundedSize / ENTRY_SIZE;
  120. }
  121. // primitive types should fit into one entry
  122. assert(totalSize == ENTRY_SIZE || datatype == ItemType::BLOB || datatype == ItemType::SZ);
  123. if (mNextFreeEntry + entriesCount > ENTRY_COUNT) {
  124. // page will not fit this amount of data
  125. return ESP_ERR_NVS_PAGE_FULL;
  126. }
  127. // write first item
  128. item.nsIndex = nsIndex;
  129. item.datatype = datatype;
  130. item.span = (totalSize + ENTRY_SIZE - 1) / ENTRY_SIZE;
  131. item.reserved = 0xff;
  132. std::fill_n(reinterpret_cast<uint32_t*>(item.key), sizeof(item.key) / 4, 0xffffffff);
  133. std::fill_n(reinterpret_cast<uint32_t*>(item.data), sizeof(item.data) / 4, 0xffffffff);
  134. strlcpy(item.key, key, Item::MAX_KEY_LENGTH + 1);
  135. if (datatype != ItemType::SZ && datatype != ItemType::BLOB) {
  136. memcpy(item.data, data, dataSize);
  137. item.crc32 = item.calculateCrc32();
  138. err = writeEntry(item);
  139. if (err != ESP_OK) {
  140. return err;
  141. }
  142. } else {
  143. const uint8_t* src = reinterpret_cast<const uint8_t*>(data);
  144. item.varLength.dataCrc32 = Item::calculateCrc32(src, dataSize);
  145. item.varLength.dataSize = dataSize;
  146. item.varLength.reserved2 = 0xffff;
  147. item.crc32 = item.calculateCrc32();
  148. err = writeEntry(item);
  149. if (err != ESP_OK) {
  150. return err;
  151. }
  152. size_t left = dataSize;
  153. while (left != 0) {
  154. size_t willWrite = Page::ENTRY_SIZE;
  155. willWrite = (left < willWrite)?left:willWrite;
  156. memcpy(item.rawData, src, willWrite);
  157. src += willWrite;
  158. left -= willWrite;
  159. err = writeEntry(item);
  160. if (err != ESP_OK) {
  161. return err;
  162. }
  163. }
  164. }
  165. return ESP_OK;
  166. }
  167. esp_err_t Page::readItem(uint8_t nsIndex, ItemType datatype, const char* key, void* data, size_t dataSize)
  168. {
  169. size_t index = 0;
  170. Item item;
  171. esp_err_t rc = findItem(nsIndex, datatype, key, index, item);
  172. if (rc != ESP_OK) {
  173. return rc;
  174. }
  175. if (datatype != ItemType::SZ && datatype != ItemType::BLOB) {
  176. if (dataSize != getAlignmentForType(datatype)) {
  177. return ESP_ERR_NVS_TYPE_MISMATCH;
  178. }
  179. memcpy(data, item.data, dataSize);
  180. return ESP_OK;
  181. }
  182. if (dataSize < static_cast<size_t>(item.varLength.dataSize)) {
  183. return ESP_ERR_NVS_INVALID_LENGTH;
  184. }
  185. uint8_t* dst = reinterpret_cast<uint8_t*>(data);
  186. size_t left = item.varLength.dataSize;
  187. for (size_t i = index + 1; i < index + item.span; ++i) {
  188. Item ditem;
  189. rc = readEntry(i, ditem);
  190. if (rc != ESP_OK) {
  191. return rc;
  192. }
  193. size_t willCopy = ENTRY_SIZE;
  194. willCopy = (left < willCopy)?left:willCopy;
  195. memcpy(dst, ditem.rawData, willCopy);
  196. left -= willCopy;
  197. dst += willCopy;
  198. }
  199. if (Item::calculateCrc32(reinterpret_cast<uint8_t*>(data), item.varLength.dataSize) != item.varLength.dataCrc32) {
  200. rc = eraseEntryAndSpan(index);
  201. if (rc != ESP_OK) {
  202. return rc;
  203. }
  204. return ESP_ERR_NVS_NOT_FOUND;
  205. }
  206. return ESP_OK;
  207. }
  208. esp_err_t Page::eraseItem(uint8_t nsIndex, ItemType datatype, const char* key)
  209. {
  210. size_t index = 0;
  211. Item item;
  212. esp_err_t rc = findItem(nsIndex, datatype, key, index, item);
  213. if (rc != ESP_OK) {
  214. return rc;
  215. }
  216. if (CachedFindInfo(nsIndex, datatype, key) == mFindInfo) {
  217. invalidateCache();
  218. }
  219. return eraseEntryAndSpan(index);
  220. }
  221. esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key)
  222. {
  223. size_t index = 0;
  224. Item item;
  225. return findItem(nsIndex, datatype, key, index, item);
  226. }
  227. esp_err_t Page::eraseEntry(size_t index)
  228. {
  229. auto state = mEntryTable.get(index);
  230. assert(state == EntryState::WRITTEN || state == EntryState::EMPTY);
  231. auto rc = alterEntryState(index, EntryState::ERASED);
  232. if (rc != ESP_OK) {
  233. return rc;
  234. }
  235. return ESP_OK;
  236. }
  237. esp_err_t Page::eraseEntryAndSpan(size_t index)
  238. {
  239. auto state = mEntryTable.get(index);
  240. assert(state == EntryState::WRITTEN || state == EntryState::EMPTY);
  241. size_t span = 1;
  242. if (state == EntryState::WRITTEN) {
  243. Item item;
  244. auto rc = readEntry(index, item);
  245. if (rc != ESP_OK) {
  246. return rc;
  247. }
  248. if (item.calculateCrc32() != item.crc32) {
  249. rc = alterEntryState(index, EntryState::ERASED);
  250. if (rc != ESP_OK) {
  251. return rc;
  252. }
  253. } else {
  254. span = item.span;
  255. for (ptrdiff_t i = index + span - 1; i >= static_cast<ptrdiff_t>(index); --i) {
  256. rc = alterEntryState(i, EntryState::ERASED);
  257. if (rc != ESP_OK) {
  258. return rc;
  259. }
  260. }
  261. }
  262. }
  263. else {
  264. auto rc = alterEntryState(index, EntryState::ERASED);
  265. if (rc != ESP_OK) {
  266. return rc;
  267. }
  268. }
  269. if (index == mFirstUsedEntry) {
  270. updateFirstUsedEntry(index, span);
  271. }
  272. mErasedEntryCount += span;
  273. mUsedEntryCount -= span;
  274. return ESP_OK;
  275. }
  276. void Page::updateFirstUsedEntry(size_t index, size_t span)
  277. {
  278. assert(index == mFirstUsedEntry);
  279. mFirstUsedEntry = INVALID_ENTRY;
  280. for (size_t i = index + span; i < mNextFreeEntry; ++i) {
  281. if (mEntryTable.get(i) == EntryState::WRITTEN) {
  282. mFirstUsedEntry = i;
  283. break;
  284. }
  285. }
  286. }
  287. esp_err_t Page::moveItem(Page& other)
  288. {
  289. if (mFirstUsedEntry == INVALID_ENTRY) {
  290. return ESP_ERR_NVS_NOT_FOUND;
  291. }
  292. if (mFindInfo.itemIndex() == mFirstUsedEntry) {
  293. invalidateCache();
  294. }
  295. if (other.mState == PageState::UNINITIALIZED) {
  296. auto err = other.initialize();
  297. if (err != ESP_OK) {
  298. return err;
  299. }
  300. }
  301. Item entry;
  302. auto err = readEntry(mFirstUsedEntry, entry);
  303. if (err != ESP_OK) {
  304. return err;
  305. }
  306. err = other.writeEntry(entry);
  307. if (err != ESP_OK) {
  308. return err;
  309. }
  310. err = eraseEntry(mFirstUsedEntry);
  311. if (err != ESP_OK) {
  312. return err;
  313. }
  314. size_t span = entry.span;
  315. size_t end = mFirstUsedEntry + span;
  316. assert(mFirstUsedEntry != INVALID_ENTRY || span == 1);
  317. for (size_t i = mFirstUsedEntry + 1; i < end; ++i) {
  318. readEntry(i, entry);
  319. err = other.writeEntry(entry);
  320. if (err != ESP_OK) {
  321. return err;
  322. }
  323. err = eraseEntry(i);
  324. if (err != ESP_OK) {
  325. return err;
  326. }
  327. }
  328. updateFirstUsedEntry(mFirstUsedEntry, span);
  329. mErasedEntryCount += span;
  330. mUsedEntryCount -= span;
  331. return ESP_OK;
  332. }
  333. esp_err_t Page::mLoadEntryTable()
  334. {
  335. // for states where we actually care about data in the page, read entry state table
  336. if (mState == PageState::ACTIVE ||
  337. mState == PageState::FULL ||
  338. mState == PageState::FREEING) {
  339. auto rc = spi_flash_read(mBaseAddress + ENTRY_TABLE_OFFSET, mEntryTable.data(),
  340. static_cast<uint32_t>(mEntryTable.byteSize()));
  341. if (rc != ESP_OK) {
  342. mState = PageState::INVALID;
  343. return rc;
  344. }
  345. }
  346. mErasedEntryCount = 0;
  347. mUsedEntryCount = 0;
  348. for (size_t i = 0; i < ENTRY_COUNT; ++i) {
  349. auto s = mEntryTable.get(i);
  350. if (s == EntryState::WRITTEN) {
  351. if (mFirstUsedEntry == INVALID_ENTRY) {
  352. mFirstUsedEntry = i;
  353. }
  354. ++mUsedEntryCount;
  355. } else if (s == EntryState::ERASED) {
  356. ++mErasedEntryCount;
  357. }
  358. }
  359. // for PageState::ACTIVE, we may have more data written to this page
  360. // as such, we need to figure out where the first unused entry is
  361. if (mState == PageState::ACTIVE) {
  362. for (size_t i = 0; i < ENTRY_COUNT; ++i) {
  363. if (mEntryTable.get(i) == EntryState::EMPTY) {
  364. mNextFreeEntry = i;
  365. break;
  366. }
  367. }
  368. // however, if power failed after some data was written into the entry.
  369. // but before the entry state table was altered, the entry locacted via
  370. // entry state table may actually be half-written.
  371. // this is easy to check by reading EntryHeader (i.e. first word)
  372. uint32_t entryAddress = mBaseAddress + ENTRY_DATA_OFFSET +
  373. static_cast<uint32_t>(mNextFreeEntry) * ENTRY_SIZE;
  374. uint32_t header;
  375. auto rc = spi_flash_read(entryAddress, &header, sizeof(header));
  376. if (rc != ESP_OK) {
  377. mState = PageState::INVALID;
  378. return rc;
  379. }
  380. if (header != 0xffffffff) {
  381. auto err = alterEntryState(mNextFreeEntry, EntryState::ERASED);
  382. if (err != ESP_OK) {
  383. mState = PageState::INVALID;
  384. return err;
  385. }
  386. ++mNextFreeEntry;
  387. }
  388. // check that all variable-length items are written or erased fully
  389. for (size_t i = 0; i < mNextFreeEntry; ++i) {
  390. if (mEntryTable.get(i) == EntryState::ERASED) {
  391. continue;
  392. }
  393. Item item;
  394. auto err = readEntry(i, item);
  395. if (err != ESP_OK) {
  396. mState = PageState::INVALID;
  397. return err;
  398. }
  399. if (item.crc32 != item.calculateCrc32()) {
  400. err = eraseEntryAndSpan(i);
  401. if (err != ESP_OK) {
  402. mState = PageState::INVALID;
  403. return err;
  404. }
  405. continue;
  406. }
  407. if (item.datatype != ItemType::BLOB && item.datatype != ItemType::SZ) {
  408. continue;
  409. }
  410. size_t span = item.span;
  411. bool needErase = false;
  412. for (size_t j = i; j < i + span; ++j) {
  413. if (mEntryTable.get(j) != EntryState::WRITTEN) {
  414. needErase = true;
  415. break;
  416. }
  417. }
  418. if (needErase) {
  419. eraseEntryAndSpan(i);
  420. }
  421. i += span - 1;
  422. }
  423. }
  424. return ESP_OK;
  425. }
  426. esp_err_t Page::initialize()
  427. {
  428. assert(mState == PageState::UNINITIALIZED);
  429. mState = PageState::ACTIVE;
  430. Header header;
  431. header.mState = mState;
  432. header.mSeqNumber = mSeqNumber;
  433. header.mCrc32 = header.calculateCrc32();
  434. auto rc = spi_flash_write(mBaseAddress, reinterpret_cast<uint32_t*>(&header), sizeof(header));
  435. if (rc != ESP_OK) {
  436. mState = PageState::INVALID;
  437. return rc;
  438. }
  439. mNextFreeEntry = 0;
  440. std::fill_n(mEntryTable.data(), mEntryTable.byteSize() / sizeof(uint32_t), 0xffffffff);
  441. invalidateCache();
  442. return ESP_OK;
  443. }
  444. esp_err_t Page::alterEntryState(size_t index, EntryState state)
  445. {
  446. assert(index < ENTRY_COUNT);
  447. mEntryTable.set(index, state);
  448. size_t wordToWrite = mEntryTable.getWordIndex(index);
  449. uint32_t word = mEntryTable.data()[wordToWrite];
  450. auto rc = spi_flash_write(mBaseAddress + ENTRY_TABLE_OFFSET + static_cast<uint32_t>(wordToWrite) * 4, &word, 4);
  451. if (rc != ESP_OK) {
  452. mState = PageState::INVALID;
  453. return rc;
  454. }
  455. return ESP_OK;
  456. }
  457. esp_err_t Page::alterPageState(PageState state)
  458. {
  459. auto rc = spi_flash_write(mBaseAddress, reinterpret_cast<uint32_t*>(&state), sizeof(state));
  460. if (rc != ESP_OK) {
  461. mState = PageState::INVALID;
  462. return rc;
  463. }
  464. mState = (PageState) state;
  465. return ESP_OK;
  466. }
  467. esp_err_t Page::readEntry(size_t index, Item& dst) const
  468. {
  469. auto rc = spi_flash_read(getEntryAddress(index), reinterpret_cast<uint32_t*>(&dst), sizeof(dst));
  470. if (rc != ESP_OK) {
  471. return rc;
  472. }
  473. return ESP_OK;
  474. }
  475. esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key, size_t &itemIndex, Item& item)
  476. {
  477. if (mState == PageState::CORRUPT || mState == PageState::INVALID || mState == PageState::UNINITIALIZED) {
  478. return ESP_ERR_NVS_NOT_FOUND;
  479. }
  480. CachedFindInfo findInfo(nsIndex, datatype, key);
  481. if (mFindInfo == findInfo) {
  482. itemIndex = mFindInfo.itemIndex();
  483. }
  484. size_t start = mFirstUsedEntry;
  485. if (itemIndex > mFirstUsedEntry && itemIndex < ENTRY_COUNT) {
  486. start = itemIndex;
  487. }
  488. size_t next;
  489. for (size_t i = start; i < mNextFreeEntry; i = next) {
  490. next = i + 1;
  491. if (mEntryTable.get(i) != EntryState::WRITTEN) {
  492. continue;
  493. }
  494. auto rc = readEntry(i, item);
  495. if (rc != ESP_OK) {
  496. mState = PageState::INVALID;
  497. return rc;
  498. }
  499. auto crc32 = item.calculateCrc32();
  500. if (item.crc32 != crc32) {
  501. eraseEntryAndSpan(i);
  502. continue;
  503. }
  504. if (item.datatype == ItemType::BLOB || item.datatype == ItemType::SZ) {
  505. next = i + item.span;
  506. }
  507. if (nsIndex != NS_ANY && item.nsIndex != nsIndex) {
  508. continue;
  509. }
  510. if (key != nullptr && strncmp(key, item.key, Item::MAX_KEY_LENGTH) != 0) {
  511. continue;
  512. }
  513. if (datatype != ItemType::ANY && item.datatype != datatype) {
  514. return ESP_ERR_NVS_TYPE_MISMATCH;
  515. }
  516. itemIndex = i;
  517. findInfo.setItemIndex(static_cast<uint32_t>(itemIndex));
  518. mFindInfo = findInfo;
  519. return ESP_OK;
  520. }
  521. return ESP_ERR_NVS_NOT_FOUND;
  522. }
  523. esp_err_t Page::getSeqNumber(uint32_t& seqNumber) const
  524. {
  525. if (mState != PageState::UNINITIALIZED && mState != PageState::INVALID && mState != PageState::CORRUPT) {
  526. seqNumber = mSeqNumber;
  527. return ESP_OK;
  528. }
  529. return ESP_ERR_NVS_NOT_INITIALIZED;
  530. }
  531. esp_err_t Page::setSeqNumber(uint32_t seqNumber)
  532. {
  533. if (mState != PageState::UNINITIALIZED) {
  534. return ESP_ERR_NVS_INVALID_STATE;
  535. }
  536. mSeqNumber = seqNumber;
  537. return ESP_OK;
  538. }
  539. esp_err_t Page::erase()
  540. {
  541. auto sector = mBaseAddress / SPI_FLASH_SEC_SIZE;
  542. auto rc = spi_flash_erase_sector(sector);
  543. if (rc != ESP_OK) {
  544. mState = PageState::INVALID;
  545. return rc;
  546. }
  547. return load(sector);
  548. }
  549. esp_err_t Page::markFreeing()
  550. {
  551. if (mState != PageState::FULL && mState != PageState::ACTIVE) {
  552. return ESP_ERR_NVS_INVALID_STATE;
  553. }
  554. return alterPageState(PageState::FREEING);
  555. }
  556. esp_err_t Page::markFull()
  557. {
  558. if (mState != PageState::ACTIVE) {
  559. return ESP_ERR_NVS_INVALID_STATE;
  560. }
  561. return alterPageState(PageState::FULL);
  562. }
  563. void Page::invalidateCache()
  564. {
  565. mFindInfo = CachedFindInfo();
  566. }
  567. void Page::debugDump() const
  568. {
  569. printf("state=%x addr=%x seq=%d\nfirstUsed=%d nextFree=%d used=%d erased=%d\n", mState, mBaseAddress, mSeqNumber, static_cast<int>(mFirstUsedEntry), static_cast<int>(mNextFreeEntry), mUsedEntryCount, mErasedEntryCount);
  570. size_t skip = 0;
  571. for (size_t i = 0; i < ENTRY_COUNT; ++i) {
  572. printf("%3d: ", static_cast<int>(i));
  573. EntryState state = mEntryTable.get(i);
  574. if (state == EntryState::EMPTY) {
  575. printf("E\n");
  576. }
  577. else if (state == EntryState::ERASED) {
  578. printf("X\n");
  579. }
  580. else if (state == EntryState::WRITTEN) {
  581. Item item;
  582. readEntry(i, item);
  583. if (skip == 0) {
  584. printf("W ns=%2u type=%2u span=%3u key=\"%s\"\n", item.nsIndex, static_cast<unsigned>(item.datatype), item.span, item.key);
  585. skip = item.span - 1;
  586. }
  587. else {
  588. printf("D\n");
  589. skip--;
  590. }
  591. }
  592. }
  593. }
  594. } // namespace nvs