diff --git a/components/nvs_flash/README.rst b/components/nvs_flash/README.rst index 7691cfd3ad..f04ac592ce 100644 --- a/components/nvs_flash/README.rst +++ b/components/nvs_flash/README.rst @@ -9,7 +9,7 @@ Non-volatile storage (NVS) library is designed to store key-value pairs in flash Underlying storage ^^^^^^^^^^^^^^^^^^ -Currently NVS uses a portion of main flash memory through ``spi_flash_{read|write|erase}`` APIs. The library uses the first partition with ``data`` type and ``nvs`` subtype. +Currently NVS uses a portion of main flash memory through ``spi_flash_{read|write|erase}`` APIs. The library uses the all the partitions with ``data`` type and ``nvs`` subtype. The application can choose to use the partition with label ``nvs`` through ``nvs_open`` API or any of the other partition by specifying its name through ``nvs_open_from_part`` API. Future versions of this library may add other storage backends to keep data in another flash chip (SPI or I2C), RTC, FRAM, etc. @@ -41,7 +41,8 @@ Data type check is also performed when reading a value. An error is returned if Namespaces ^^^^^^^^^^ -To mitigate potential conflicts in key names between different components, NVS assigns each key-value pair to one of namespaces. Namespace names follow the same rules as key names, i.e. 15 character maximum length. Namespace name is specified in the ``nvs_open`` call. This call returns an opaque handle, which is used in subsequent calls to ``nvs_read_*``, ``nvs_write_*``, and ``nvs_commit`` functions. This way, handle is associated with a namespace, and key names will not collide with same names in other namespaces. +To mitigate potential conflicts in key names between different components, NVS assigns each key-value pair to one of namespaces. Namespace names follow the same rules as key names, i.e. 15 character maximum length. Namespace name is specified in the ``nvs_open`` or ``nvs_open_from_part`` call. This call returns an opaque handle, which is used in subsequent calls to ``nvs_read_*``, ``nvs_write_*``, and ``nvs_commit`` functions. This way, handle is associated with a namespace, and key names will not collide with same names in other namespaces. +Please note that the namespaces with same name in different NVS partitions are considered as separate namespaces. Security, tampering, and robustness ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/components/nvs_flash/include/nvs.h b/components/nvs_flash/include/nvs.h index 5b469f764a..dfdd18c92c 100644 --- a/components/nvs_flash/include/nvs.h +++ b/components/nvs_flash/include/nvs.h @@ -43,7 +43,9 @@ typedef uint32_t nvs_handle; #define ESP_ERR_NVS_INVALID_LENGTH (ESP_ERR_NVS_BASE + 0x0c) /*!< String or blob length is not sufficient to store data */ #define ESP_ERR_NVS_NO_FREE_PAGES (ESP_ERR_NVS_BASE + 0x0d) /*!< NVS partition doesn't contain any empty pages. This may happen if NVS partition was truncated. Erase the whole partition and call nvs_flash_init again. */ #define ESP_ERR_NVS_VALUE_TOO_LONG (ESP_ERR_NVS_BASE + 0x0e) /*!< String or blob length is longer than supported by the implementation */ +#define ESP_ERR_NVS_PART_NOT_FOUND (ESP_ERR_NVS_BASE + 0x0f) /*!< Partition with specified name is not found in the partition table */ +#define NVS_DEFAULT_PART_NAME "nvs" /*!< Default partition name of the NVS partition in the partition table */ /** * @brief Mode of opening the non-volatile storage * @@ -54,15 +56,45 @@ typedef enum { } nvs_open_mode; /** - * @brief Open non-volatile storage with a given namespace + * @brief Open non-volatile storage with a given namespace from the default NVS partition * * Multiple internal ESP-IDF and third party application modules can store * their key-value pairs in the NVS module. In order to reduce possible * conflicts on key names, each module can use its own namespace. + * The default NVS partition is the one that is labelled "nvs" in the partition + * table. * * @param[in] name Namespace name. Maximal length is determined by the * underlying implementation, but is guaranteed to be * at least 15 characters. Shouldn't be empty. + * @param[in] open_mode NVS_READWRITE or NVS_READONLY. If NVS_READONLY, will + * open a handle for reading only. All write requests will + * be rejected for this handle. + * @param[out] out_handle If successful (return code is zero), handle will be + * returned in this argument. + * + * @return + * - ESP_OK if storage handle was opened successfully + * - ESP_ERR_NVS_NOT_INITIALIZED if the storage driver is not initialized + * - ESP_ERR_NVS_PART_NOT_FOUND if the partition with label "nvs" is not found + * - ESP_ERR_NVS_NOT_FOUND id namespace doesn't exist yet and + * mode is NVS_READONLY + * - ESP_ERR_NVS_INVALID_NAME if namespace name doesn't satisfy constraints + * - other error codes from the underlying storage driver + */ +esp_err_t nvs_open(const char* name, nvs_open_mode open_mode, nvs_handle *out_handle); + +/** + * @brief Open non-volatile storage with a given namespace from specified partition + * + * The behaviour is same as nvs_open() API. However this API can operate on a specified NVS + * partition instead of default NVS partition. Note that the specified partition must be registered + * with NVS using nvs_flash_init_partition() API. + * + * @param[in] part_name Label (name) of the partition of interest for object read/write/erase + * @param[in] name Namespace name. Maximal length is determined by the + * underlying implementation, but is guaranteed to be + * at least 15 characters. Shouldn't be empty. * @param[in] open_mode NVS_READWRITE or NVS_READONLY. If NVS_READONLY, will * open a handle for reading only. All write requests will * be rejected for this handle. @@ -72,12 +104,13 @@ typedef enum { * @return * - ESP_OK if storage handle was opened successfully * - ESP_ERR_NVS_NOT_INITIALIZED if the storage driver is not initialized + * - ESP_ERR_NVS_PART_NOT_FOUND if the partition with specified name is not found * - ESP_ERR_NVS_NOT_FOUND id namespace doesn't exist yet and * mode is NVS_READONLY * - ESP_ERR_NVS_INVALID_NAME if namespace name doesn't satisfy constraints * - other error codes from the underlying storage driver */ -esp_err_t nvs_open(const char* name, nvs_open_mode open_mode, nvs_handle *out_handle); +esp_err_t nvs_open_from_partition(const char *part_name, const char* name, nvs_open_mode open_mode, nvs_handle *out_handle); /**@{*/ /** diff --git a/components/nvs_flash/include/nvs_flash.h b/components/nvs_flash/include/nvs_flash.h index 9220f0d3ff..c9e4a72d74 100644 --- a/components/nvs_flash/include/nvs_flash.h +++ b/components/nvs_flash/include/nvs_flash.h @@ -21,28 +21,61 @@ extern "C" { #include "nvs.h" /** - * @brief Initialize NVS flash storage with layout given in the partition table. + * @brief Initialize the default NVS partition. + * + * This API initialises the default NVS partition. The default NVS partition + * is the one that is labelled "nvs" in the partition table. * * @return * - ESP_OK if storage was successfully initialized. * - ESP_ERR_NVS_NO_FREE_PAGES if the NVS storage contains no empty pages * (which may happen if NVS partition was truncated) + * - ESP_ERR_NOT_FOUND if no partition with label "nvs" is found in the partition table * - one of the error codes from the underlying flash storage driver */ esp_err_t nvs_flash_init(void); +/** + * @brief Initialize NVS flash storage for the specified partition. + * + * @param[in] partition_name Name (label) of the partition. Note that internally a reference to + * passed value is kept and it should be accessible for future operations + * + * @return + * - ESP_OK if storage was successfully initialized. + * - ESP_ERR_NVS_NO_FREE_PAGES if the NVS storage contains no empty pages + * (which may happen if NVS partition was truncated) + * - ESP_ERR_NOT_FOUND if specified partition is not found in the partition table + * - one of the error codes from the underlying flash storage driver + */ +esp_err_t nvs_flash_init_partition(const char *partition_name); /** - * @brief Erase NVS partition + * @brief Erase the default NVS partition * - * This function erases all contents of NVS partition + * This function erases all contents of the default NVS partition (one with label "nvs") * * @return * - ESP_OK on success - * - ESP_ERR_NOT_FOUND if there is no NVS partition in the partition table + * - ESP_ERR_NOT_FOUND if there is no NVS partition labeled "nvs" in the + * partition table */ esp_err_t nvs_flash_erase(void); +/** + * @brief Erase specified NVS partition + * + * This function erases all contents of specified NVS partition + * + * @param[in] part_name Name (label) of the partition to be erased + * + * @return + * - ESP_OK on success + * - ESP_ERR_NOT_FOUND if there is no NVS partition with the specified name + * in the partition table + */ +esp_err_t nvs_flash_erase_partition(const char *part_name); + #ifdef __cplusplus } #endif diff --git a/components/nvs_flash/src/nvs_api.cpp b/components/nvs_flash/src/nvs_api.cpp index 300ea28942..57759ecd83 100644 --- a/components/nvs_flash/src/nvs_api.cpp +++ b/components/nvs_flash/src/nvs_api.cpp @@ -11,6 +11,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +#include #include "nvs.hpp" #include "nvs_flash.h" #include "nvs_storage.hpp" @@ -30,19 +31,22 @@ static const char* TAG = "nvs"; class HandleEntry : public intrusive_list_node { + static uint32_t s_nvs_next_handle; public: HandleEntry() {} - HandleEntry(nvs_handle handle, bool readOnly, uint8_t nsIndex) : - mHandle(handle), + HandleEntry(bool readOnly, uint8_t nsIndex, nvs::Storage* StoragePtr) : + mHandle(++s_nvs_next_handle), // Begin the handle value with 1 mReadOnly(readOnly), - mNsIndex(nsIndex) + mNsIndex(nsIndex), + mStoragePtr(StoragePtr) { } nvs_handle mHandle; uint8_t mReadOnly; uint8_t mNsIndex; + nvs::Storage* mStoragePtr; }; #ifdef ESP_PLATFORM @@ -53,50 +57,91 @@ using namespace std; using namespace nvs; static intrusive_list s_nvs_handles; -static uint32_t s_nvs_next_handle = 1; -static nvs::Storage s_nvs_storage; +uint32_t HandleEntry::s_nvs_next_handle; +static intrusive_list s_nvs_storage_list; -extern "C" void nvs_dump() +static nvs::Storage* lookup_storage_from_name(const char *name) { - Lock lock; - s_nvs_storage.debugDump(); + auto it = find_if(begin(s_nvs_storage_list), end(s_nvs_storage_list), [=](Storage& e) -> bool { + return (strcmp(e.getPartName(), name) == 0); + }); + + if (it == end(s_nvs_storage_list)) { + return NULL; + } + return it; } -extern "C" esp_err_t nvs_flash_init_custom(uint32_t baseSector, uint32_t sectorCount) +extern "C" void nvs_dump(const char *partName) { - ESP_LOGD(TAG, "nvs_flash_init_custom start=%d count=%d", baseSector, sectorCount); - s_nvs_handles.clear(); - return s_nvs_storage.init(baseSector, sectorCount); + Lock lock; + nvs::Storage* pStorage; + + pStorage = lookup_storage_from_name(partName); + if (pStorage == NULL) { + return; + } + + pStorage->debugDump(); + return; +} + +extern "C" esp_err_t nvs_flash_init_custom(const char *partName, uint32_t baseSector, uint32_t sectorCount) +{ + ESP_LOGD(TAG, "nvs_flash_init_custom partition=%s start=%d count=%d", partName, baseSector, sectorCount); + nvs::Storage* mStorage; + + mStorage = lookup_storage_from_name(partName); + if (mStorage == NULL) { + mStorage = new nvs::Storage((const char *)partName); + s_nvs_storage_list.push_back(mStorage); + } + + return mStorage->init(baseSector, sectorCount); } #ifdef ESP_PLATFORM -extern "C" esp_err_t nvs_flash_init(void) +extern "C" esp_err_t nvs_flash_init_partition(const char *part_name) { Lock::init(); Lock lock; - if (s_nvs_storage.isValid()) { + nvs::Storage* mStorage; + + mStorage = lookup_storage_from_name(NVS_DEFAULT_PART_NAME); + if (mStorage) { return ESP_OK; } + const esp_partition_t* partition = esp_partition_find_first( - ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, NULL); + ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, part_name); if (partition == NULL) { return ESP_ERR_NOT_FOUND; } - return nvs_flash_init_custom(partition->address / SPI_FLASH_SEC_SIZE, + return nvs_flash_init_custom(part_name, partition->address / SPI_FLASH_SEC_SIZE, partition->size / SPI_FLASH_SEC_SIZE); } -extern "C" esp_err_t nvs_flash_erase() +extern "C" esp_err_t nvs_flash_init(void) +{ + return nvs_flash_init_partition(NVS_DEFAULT_PART_NAME); +} + +extern "C" esp_err_t nvs_flash_erase_partition(const char *part_name) { const esp_partition_t* partition = esp_partition_find_first( - ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, NULL); + ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, part_name); if (partition == NULL) { return ESP_ERR_NOT_FOUND; } return esp_partition_erase_range(partition, 0, partition->size); } + +extern "C" esp_err_t nvs_flash_erase() +{ + return nvs_flash_erase_partition(NVS_DEFAULT_PART_NAME); +} #endif static esp_err_t nvs_find_ns_handle(nvs_handle handle, HandleEntry& entry) @@ -111,24 +156,40 @@ static esp_err_t nvs_find_ns_handle(nvs_handle handle, HandleEntry& entry) return ESP_OK; } -extern "C" esp_err_t nvs_open(const char* name, nvs_open_mode open_mode, nvs_handle *out_handle) +extern "C" esp_err_t nvs_open_from_partition(const char *part_name, const char* name, nvs_open_mode open_mode, nvs_handle *out_handle) { Lock lock; ESP_LOGD(TAG, "%s %s %d", __func__, name, open_mode); uint8_t nsIndex; - esp_err_t err = s_nvs_storage.createOrOpenNamespace(name, open_mode == NVS_READWRITE, nsIndex); + nvs::Storage* sHandle; + + sHandle = lookup_storage_from_name(part_name); + if (sHandle == NULL) { + return ESP_ERR_NVS_PART_NOT_FOUND; + } + + esp_err_t err = sHandle->createOrOpenNamespace(name, open_mode == NVS_READWRITE, nsIndex); if (err != ESP_OK) { return err; } - uint32_t handle = s_nvs_next_handle; - ++s_nvs_next_handle; - *out_handle = handle; + HandleEntry *handle_entry = new HandleEntry(open_mode==NVS_READONLY, nsIndex, sHandle); + s_nvs_handles.push_back(handle_entry); + + *out_handle = handle_entry->mHandle; - s_nvs_handles.push_back(new HandleEntry(handle, open_mode==NVS_READONLY, nsIndex)); return ESP_OK; } +extern "C" esp_err_t nvs_open(const char* name, nvs_open_mode open_mode, nvs_handle *out_handle) +{ + if (s_nvs_storage_list.size() == 0) { + return ESP_ERR_NVS_NOT_INITIALIZED; + } + + return nvs_open_from_partition(NVS_DEFAULT_PART_NAME, name, open_mode, out_handle); +} + extern "C" void nvs_close(nvs_handle handle) { Lock lock; @@ -155,7 +216,7 @@ extern "C" esp_err_t nvs_erase_key(nvs_handle handle, const char* key) if (entry.mReadOnly) { return ESP_ERR_NVS_READ_ONLY; } - return s_nvs_storage.eraseItem(entry.mNsIndex, key); + return entry.mStoragePtr->eraseItem(entry.mNsIndex, key); } extern "C" esp_err_t nvs_erase_all(nvs_handle handle) @@ -170,7 +231,7 @@ extern "C" esp_err_t nvs_erase_all(nvs_handle handle) if (entry.mReadOnly) { return ESP_ERR_NVS_READ_ONLY; } - return s_nvs_storage.eraseNamespace(entry.mNsIndex); + return entry.mStoragePtr->eraseNamespace(entry.mNsIndex); } template @@ -186,7 +247,7 @@ static esp_err_t nvs_set(nvs_handle handle, const char* key, T value) if (entry.mReadOnly) { return ESP_ERR_NVS_READ_ONLY; } - return s_nvs_storage.writeItem(entry.mNsIndex, key, value); + return entry.mStoragePtr->writeItem(entry.mNsIndex, key, value); } extern "C" esp_err_t nvs_set_i8 (nvs_handle handle, const char* key, int8_t value) @@ -246,7 +307,7 @@ extern "C" esp_err_t nvs_set_str(nvs_handle handle, const char* key, const char* if (err != ESP_OK) { return err; } - return s_nvs_storage.writeItem(entry.mNsIndex, nvs::ItemType::SZ, key, value, strlen(value) + 1); + return entry.mStoragePtr->writeItem(entry.mNsIndex, nvs::ItemType::SZ, key, value, strlen(value) + 1); } extern "C" esp_err_t nvs_set_blob(nvs_handle handle, const char* key, const void* value, size_t length) @@ -258,7 +319,7 @@ extern "C" esp_err_t nvs_set_blob(nvs_handle handle, const char* key, const void if (err != ESP_OK) { return err; } - return s_nvs_storage.writeItem(entry.mNsIndex, nvs::ItemType::BLOB, key, value, length); + return entry.mStoragePtr->writeItem(entry.mNsIndex, nvs::ItemType::BLOB, key, value, length); } @@ -272,7 +333,7 @@ static esp_err_t nvs_get(nvs_handle handle, const char* key, T* out_value) if (err != ESP_OK) { return err; } - return s_nvs_storage.readItem(entry.mNsIndex, key, *out_value); + return entry.mStoragePtr->readItem(entry.mNsIndex, key, *out_value); } extern "C" esp_err_t nvs_get_i8 (nvs_handle handle, const char* key, int8_t* out_value) @@ -326,7 +387,7 @@ static esp_err_t nvs_get_str_or_blob(nvs_handle handle, nvs::ItemType type, cons } size_t dataSize; - err = s_nvs_storage.getItemDataSize(entry.mNsIndex, type, key, dataSize); + err = entry.mStoragePtr->getItemDataSize(entry.mNsIndex, type, key, dataSize); if (err != ESP_OK) { return err; } @@ -341,7 +402,7 @@ static esp_err_t nvs_get_str_or_blob(nvs_handle handle, nvs::ItemType type, cons return ESP_ERR_NVS_INVALID_LENGTH; } - return s_nvs_storage.readItem(entry.mNsIndex, type, key, out_value, dataSize); + return entry.mStoragePtr->readItem(entry.mNsIndex, type, key, out_value, dataSize); } extern "C" esp_err_t nvs_get_str(nvs_handle handle, const char* key, char* out_value, size_t* length) diff --git a/components/nvs_flash/src/nvs_storage.hpp b/components/nvs_flash/src/nvs_storage.hpp index ecf2651ae5..3c0e0c85a5 100644 --- a/components/nvs_flash/src/nvs_storage.hpp +++ b/components/nvs_flash/src/nvs_storage.hpp @@ -27,7 +27,7 @@ namespace nvs { -class Storage +class Storage : public intrusive_list_node { enum class StorageState : uint32_t { INVALID, @@ -45,6 +45,8 @@ class Storage public: ~Storage(); + Storage(const char *pName = NVS_DEFAULT_PART_NAME) : mPartitionName(pName) { }; + esp_err_t init(uint32_t baseSector, uint32_t sectorCount); bool isValid() const; @@ -78,6 +80,11 @@ public: esp_err_t eraseNamespace(uint8_t nsIndex); + const char *getPartName() const + { + return mPartitionName; + } + void debugDump(); void debugCheck(); @@ -95,6 +102,7 @@ protected: esp_err_t findItem(uint8_t nsIndex, ItemType datatype, const char* key, Page* &page, Item& item); protected: + const char *mPartitionName; size_t mPageCount; PageManager mPageManager; TNamespaces mNamespaces; diff --git a/components/nvs_flash/src/nvs_test_api.h b/components/nvs_flash/src/nvs_test_api.h index 97940092d5..3cf3e7fa18 100644 --- a/components/nvs_flash/src/nvs_test_api.h +++ b/components/nvs_flash/src/nvs_test_api.h @@ -25,12 +25,13 @@ extern "C" { * * @note This API is intended to be used in unit tests. * + * @param partName Partition name of the NVS partition as per partition table * @param baseSector Flash sector (units of 4096 bytes) offset to start NVS * @param sectorCount Length (in flash sectors) of NVS region. NVS partition must be at least 3 sectors long. * @return ESP_OK if flash was successfully initialized */ -esp_err_t nvs_flash_init_custom(uint32_t baseSector, uint32_t sectorCount); +esp_err_t nvs_flash_init_custom(const char *partName, uint32_t baseSector, uint32_t sectorCount); /** @@ -38,8 +39,10 @@ esp_err_t nvs_flash_init_custom(uint32_t baseSector, uint32_t sectorCount); * * This function may be used for debugging purposes to inspect the state * of NVS pages. For each page, list of entries is also dumped. + * + * @param partName Partition name of the NVS partition as per partition table */ -void nvs_dump(void); +void nvs_dump(const char *partName); #ifdef __cplusplus diff --git a/components/nvs_flash/test_nvs_host/test_nvs.cpp b/components/nvs_flash/test_nvs_host/test_nvs.cpp index 22d4c3b70b..c549eace2c 100644 --- a/components/nvs_flash/test_nvs_host/test_nvs.cpp +++ b/components/nvs_flash/test_nvs_host/test_nvs.cpp @@ -494,7 +494,7 @@ TEST_CASE("nvs api tests", "[nvs]") for (uint16_t i = NVS_FLASH_SECTOR; i