]> granicus.if.org Git - esp-idf/commitdiff
nvs: Check if an item is modified before writing out an identical copy
authorTim Nordell <tim.nordell@nimbelink.com>
Mon, 15 Apr 2019 19:06:57 +0000 (14:06 -0500)
committerTim Nordell <tim.nordell@nimbelink.com>
Tue, 30 Apr 2019 16:39:58 +0000 (11:39 -0500)
This prevents wear and tear on the flash, and it also is faster in some
cases since the read-out of flash is a cheaper operation than the erasure
of flash.  Some library modules (such as the esp_wifi) write out to NVS
upon every initialization without checking first that the existing value
is the same, and this speeds up initialization of modules that make
these design choices and moves it into a centralized place.

The comparison functions are based on the read-out functions of the same
name, and changes out the memcpy(...) operations for memcmp(...)
operations.

Signed-off-by: Tim Nordell <tim.nordell@nimbelink.com>
components/nvs_flash/include/nvs.h
components/nvs_flash/src/nvs_page.cpp
components/nvs_flash/src/nvs_page.hpp
components/nvs_flash/src/nvs_storage.cpp
components/nvs_flash/src/nvs_storage.hpp
components/nvs_flash/test_nvs_host/test_nvs.cpp

index 1d88217ee0b662fed22081a8cedabb9f58386cb2..4fe4408609fc408e7eb65dee9586f9817c923270 100644 (file)
@@ -54,6 +54,7 @@ typedef uint32_t nvs_handle;
 #define ESP_ERR_NVS_KEYS_NOT_INITIALIZED    (ESP_ERR_NVS_BASE + 0x16)  /*!< NVS key partition is uninitialized */
 #define ESP_ERR_NVS_CORRUPT_KEY_PART        (ESP_ERR_NVS_BASE + 0x17)  /*!< NVS key partition is corrupt */
 
+#define ESP_ERR_NVS_CONTENT_DIFFERS         (ESP_ERR_NVS_BASE + 0x18)  /*!< Internal error; never returned by nvs API functions.  NVS key is different in comparison */
 
 #define NVS_DEFAULT_PART_NAME           "nvs"   /*!< Default partition name of the NVS partition in the partition table */
 /**
index cfe6987f8e53bfa7fe4b7db354c31600826d8dce..e1fb4dbe076f11e02cd41b8d4d673fd91e5ebb9d 100644 (file)
@@ -308,6 +308,58 @@ esp_err_t Page::readItem(uint8_t nsIndex, ItemType datatype, const char* key, vo
     return ESP_OK;
 }
 
+esp_err_t Page::cmpItem(uint8_t nsIndex, ItemType datatype, const char* key, const void* data, size_t dataSize, uint8_t chunkIdx, VerOffset chunkStart)
+{
+    size_t index = 0;
+    Item item;
+
+    if (mState == PageState::INVALID) {
+        return ESP_ERR_NVS_INVALID_STATE;
+    }
+
+    esp_err_t rc = findItem(nsIndex, datatype, key, index, item, chunkIdx, chunkStart);
+    if (rc != ESP_OK) {
+        return rc;
+    }
+
+    if (!isVariableLengthType(datatype)) {
+        if (dataSize != getAlignmentForType(datatype)) {
+            return ESP_ERR_NVS_TYPE_MISMATCH;
+        }
+
+        if (memcmp(data, item.data, dataSize)) {
+            return ESP_ERR_NVS_CONTENT_DIFFERS;
+        }
+        return ESP_OK;
+    }
+
+    if (dataSize < static_cast<size_t>(item.varLength.dataSize)) {
+        return ESP_ERR_NVS_INVALID_LENGTH;
+    }
+
+    const uint8_t* dst = reinterpret_cast<const uint8_t*>(data);
+    size_t left = item.varLength.dataSize;
+    for (size_t i = index + 1; i < index + item.span; ++i) {
+        Item ditem;
+        rc = readEntry(i, ditem);
+        if (rc != ESP_OK) {
+            return rc;
+        }
+        size_t willCopy = ENTRY_SIZE;
+        willCopy = (left < willCopy)?left:willCopy;
+        if (memcmp(dst, ditem.rawData, willCopy)) {
+            return ESP_ERR_NVS_CONTENT_DIFFERS;
+        }
+        left -= willCopy;
+        dst += willCopy;
+    }
+    if (Item::calculateCrc32(reinterpret_cast<const uint8_t*>(data), item.varLength.dataSize) != item.varLength.dataCrc32) {
+        return ESP_ERR_NVS_NOT_FOUND;
+    }
+
+    return ESP_OK;
+}
+
 esp_err_t Page::eraseItem(uint8_t nsIndex, ItemType datatype, const char* key, uint8_t chunkIdx, VerOffset chunkStart)
 {
     size_t index = 0;
index e98deb22f5c76762b4abb6809a8cbd17307d66a6..a49175240673b2cbb91eb490e0782d0999070f27 100644 (file)
@@ -94,6 +94,8 @@ public:
 
     esp_err_t readItem(uint8_t nsIndex, ItemType datatype, const char* key, void* data, size_t dataSize, uint8_t chunkIdx = CHUNK_ANY, VerOffset chunkStart = VerOffset::VER_ANY);
 
+    esp_err_t cmpItem(uint8_t nsIndex, ItemType datatype, const char* key, const void* data, size_t dataSize, uint8_t chunkIdx = CHUNK_ANY, VerOffset chunkStart = VerOffset::VER_ANY);
+
     esp_err_t eraseItem(uint8_t nsIndex, ItemType datatype, const char* key, uint8_t chunkIdx = CHUNK_ANY, VerOffset chunkStart = VerOffset::VER_ANY);
 
     esp_err_t findItem(uint8_t nsIndex, ItemType datatype, const char* key, uint8_t chunkIdx = CHUNK_ANY, VerOffset chunkStart = VerOffset::VER_ANY);
@@ -112,6 +114,12 @@ public:
         return readItem(nsIndex, itemTypeOf(value), key, &value, sizeof(value));
     }
 
+    template<typename T>
+    esp_err_t cmpItem(uint8_t nsIndex, const char* key, const T& value)
+    {
+        return cmpItem(nsIndex, itemTypeOf(value), key, &value, sizeof(value));
+    }
+
     template<typename T>
     esp_err_t eraseItem(uint8_t nsIndex, const char* key)
     {
index be92da0726744c26d989ff18ea1252817a914b7b..b6399d458c2ce42908cfc6008904bb939d748e27 100644 (file)
@@ -269,6 +269,13 @@ esp_err_t Storage::writeItem(uint8_t nsIndex, ItemType datatype, const char* key
         VerOffset prevStart,  nextStart;
         prevStart = nextStart = VerOffset::VER_0_OFFSET;
         if (findPage) {
+            // Do a sanity check that the item in question is actually being modified.
+            // If it isn't, it is cheaper to purposefully not write out new data.
+            // since it may invoke an erasure of flash.
+            if (cmpMultiPageBlob(nsIndex, key, data, dataSize) == ESP_OK) {
+                return ESP_OK;
+            }
+
             if (findPage->state() == Page::PageState::UNINITIALIZED ||
                     findPage->state() == Page::PageState::INVALID) {
                 ESP_ERROR_CHECK(findItem(nsIndex, datatype, key, findPage, item));
@@ -311,6 +318,13 @@ esp_err_t Storage::writeItem(uint8_t nsIndex, ItemType datatype, const char* key
             }
         }
     } else {
+        // Do a sanity check that the item in question is actually being modified.
+        // If it isn't, it is cheaper to purposefully not write out new data.
+        // since it may invoke an erasure of flash.
+        if (findPage != nullptr &&
+                findPage->cmpItem(nsIndex, datatype, key, data, dataSize) == ESP_OK) {
+            return ESP_OK;
+        }
 
         Page& page = getCurrentPage();
         err = page.writeItem(nsIndex, datatype, key, data, dataSize);
@@ -443,6 +457,48 @@ esp_err_t Storage::readMultiPageBlob(uint8_t nsIndex, const char* key, void* dat
     return err;
 }
 
+esp_err_t Storage::cmpMultiPageBlob(uint8_t nsIndex, const char* key, const void* data, size_t dataSize)
+{
+    Item item;
+    Page* findPage = nullptr;
+
+    /* First read the blob index */
+    auto err = findItem(nsIndex, ItemType::BLOB_IDX, key, findPage, item);
+    if (err != ESP_OK) {
+        return err;
+    }
+
+    uint8_t chunkCount = item.blobIndex.chunkCount;
+    VerOffset chunkStart = item.blobIndex.chunkStart;
+    size_t readSize = item.blobIndex.dataSize;
+    size_t offset = 0;
+
+    if (dataSize != readSize) {
+        return ESP_ERR_NVS_CONTENT_DIFFERS;
+    }
+
+    /* Now read corresponding chunks */
+    for (uint8_t chunkNum = 0; chunkNum < chunkCount; chunkNum++) {
+        err = findItem(nsIndex, ItemType::BLOB_DATA, key, findPage, item, static_cast<uint8_t> (chunkStart) + chunkNum);
+        if (err != ESP_OK) {
+            if (err == ESP_ERR_NVS_NOT_FOUND) {
+                break;
+            }
+            return err;
+        }
+        err = findPage->cmpItem(nsIndex, ItemType::BLOB_DATA, key, static_cast<const uint8_t*>(data) + offset, item.varLength.dataSize, static_cast<uint8_t> (chunkStart) + chunkNum);
+        if (err != ESP_OK) {
+            return err;
+        }
+        assert(static_cast<uint8_t> (chunkStart) + chunkNum == item.chunkIndex);
+        offset += item.varLength.dataSize;
+    }
+    if (err == ESP_OK) {
+        assert(offset == dataSize);
+    }
+    return err;
+}
+
 esp_err_t Storage::readItem(uint8_t nsIndex, ItemType datatype, const char* key, void* data, size_t dataSize)
 {
     if (mState != StorageState::ACTIVE) {
index 3a79e705cc925cde6287c17c951266051c832a40..e5f78132602e00a0a860c13677e06e67a38026c2 100644 (file)
@@ -109,6 +109,8 @@ public:
 
     esp_err_t readMultiPageBlob(uint8_t nsIndex, const char* key, void* data, size_t dataSize);
 
+    esp_err_t cmpMultiPageBlob(uint8_t nsIndex, const char* key, const void* data, size_t dataSize);
+
     esp_err_t eraseMultiPageBlob(uint8_t nsIndex, const char* key, VerOffset chunkStart = VerOffset::VER_ANY);
 
     void debugDump();
index 90ff02e06190f396fab0e9100c7925bef29dc30f..126b1bc286de32296672eb1b24d94dde8236e707 100644 (file)
@@ -376,8 +376,8 @@ TEST_CASE("storage doesn't add duplicates within one page", "[nvs]")
     emu.setBounds(4, 8);
     CHECK(storage.init(4, 4) == ESP_OK);
     int bar = 0;
-    CHECK(storage.writeItem(1, "bar", bar) == ESP_OK);
-    CHECK(storage.writeItem(1, "bar", bar) == ESP_OK);
+    CHECK(storage.writeItem(1, "bar", ++bar) == ESP_OK);
+    CHECK(storage.writeItem(1, "bar", ++bar) == ESP_OK);
 
     Page page;
     page.load(4);
@@ -404,11 +404,11 @@ TEST_CASE("storage doesn't add duplicates within multiple pages", "[nvs]")
     emu.setBounds(4, 8);
     CHECK(storage.init(4, 4) == ESP_OK);
     int bar = 0;
-    CHECK(storage.writeItem(1, "bar", bar) == ESP_OK);
+    CHECK(storage.writeItem(1, "bar", ++bar) == ESP_OK);
     for (size_t i = 0; i < Page::ENTRY_COUNT; ++i) {
-        CHECK(storage.writeItem(1, "foo", static_cast<int>(bar)) == ESP_OK);
+        CHECK(storage.writeItem(1, "foo", static_cast<int>(++bar)) == ESP_OK);
     }
-    CHECK(storage.writeItem(1, "bar", bar) == ESP_OK);
+    CHECK(storage.writeItem(1, "bar", ++bar) == ESP_OK);
 
     Page page;
     page.load(4);
@@ -750,6 +750,58 @@ TEST_CASE("wifi test", "[nvs]")
 
 }
 
+TEST_CASE("writing the identical content does not write or erase", "[nvs]")
+{
+    SpiFlashEmulator emu(20);
+
+    const uint32_t NVS_FLASH_SECTOR = 5;
+    const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 10;
+    emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);
+    TEST_ESP_OK(nvs_flash_init_custom(NVS_DEFAULT_PART_NAME, NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN));
+
+    nvs_handle misc_handle;
+    TEST_ESP_OK(nvs_open("test", NVS_READWRITE, &misc_handle));
+
+    // Test writing a u8 twice, then changing it
+    nvs_set_u8(misc_handle, "test_u8", 8);
+    emu.clearStats();
+    nvs_set_u8(misc_handle, "test_u8", 8);
+    CHECK(emu.getWriteOps() == 0);
+    CHECK(emu.getEraseOps() == 0);
+    CHECK(emu.getReadOps()  != 0);
+    emu.clearStats();
+    nvs_set_u8(misc_handle, "test_u8", 9);
+    CHECK(emu.getWriteOps() != 0);
+    CHECK(emu.getReadOps()  != 0);
+
+    // Test writing a string twice, then changing it
+    static const char *test[2] = {"Hello world.", "Hello world!"};
+    nvs_set_str(misc_handle, "test_str", test[0]);
+    emu.clearStats();
+    nvs_set_str(misc_handle, "test_str", test[0]);
+    CHECK(emu.getWriteOps() == 0);
+    CHECK(emu.getEraseOps() == 0);
+    CHECK(emu.getReadOps()  != 0);
+    emu.clearStats();
+    nvs_set_str(misc_handle, "test_str", test[1]);
+    CHECK(emu.getWriteOps() != 0);
+    CHECK(emu.getReadOps()  != 0);
+
+    // Test writing a multi-page blob, then changing it
+    uint8_t blob[Page::CHUNK_MAX_SIZE * 3] = {0};
+    memset(blob, 1, sizeof(blob));
+    nvs_set_blob(misc_handle, "test_blob", blob, sizeof(blob));
+    emu.clearStats();
+    nvs_set_blob(misc_handle, "test_blob", blob, sizeof(blob));
+    CHECK(emu.getWriteOps() == 0);
+    CHECK(emu.getEraseOps() == 0);
+    CHECK(emu.getReadOps()  != 0);
+    blob[sizeof(blob) - 1]++;
+    emu.clearStats();
+    nvs_set_blob(misc_handle, "test_blob", blob, sizeof(blob));
+    CHECK(emu.getWriteOps() != 0);
+    CHECK(emu.getReadOps()  != 0);
+}
 
 TEST_CASE("can init storage from flash with random contents", "[nvs]")
 {