/***************************************************************************//** * @file * @brief NVM3 API definition (Generic). ******************************************************************************* * # License * Copyright 2023 Silicon Laboratories Inc. www.silabs.com ******************************************************************************* * * SPDX-License-Identifier: Zlib * * The licensor of this software is Silicon Laboratories Inc. * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. * ******************************************************************************/ #ifndef NVM3_GENERIC_H #define NVM3_GENERIC_H #include #include #include #include "sl_status.h" #include "ecode.h" #include "nvm3_hal.h" #if defined(NVM3_SECURITY) #include "nvm3_hal_crypto.h" #endif #ifdef __cplusplus extern "C" { #endif /***************************************************************************//** * @addtogroup nvm3 * @{ ******************************************************************************/ #define ECODE_NVM3_OK (SL_STATUS_OK) ///< Success return value #define ECODE_NVM3_ERR_ALIGNMENT_INVALID (SL_STATUS_NVM3_ALIGNMENT_INVALID) ///< Invalid data alignment #define ECODE_NVM3_ERR_SIZE_TOO_SMALL (SL_STATUS_NVM3_SIZE_TOO_SMALL) ///< Not enough NVM memory specified #define ECODE_NVM3_ERR_NO_VALID_PAGES (SL_STATUS_NVM3_NO_VALID_PAGES) ///< Initialization aborted, no valid page found #define ECODE_NVM3_ERR_PAGE_SIZE_NOT_SUPPORTED (SL_STATUS_NVM3_PAGE_SIZE_NOT_SUPPORTED) ///< The page size is not supported #define ECODE_NVM3_ERR_OBJECT_SIZE_NOT_SUPPORTED (SL_STATUS_NVM3_OBJECT_SIZE_NOT_SUPPORTED) ///< The object size is not supported #define ECODE_NVM3_ERR_STORAGE_FULL (SL_STATUS_FULL) ///< No more NVM space available #define ECODE_NVM3_ERR_NOT_OPENED (SL_STATUS_NOT_INITIALIZED) ///< The module has not been successfully opened #define ECODE_NVM3_ERR_OPENED_WITH_OTHER_PARAMETERS (SL_STATUS_NVM3_OPENED_WITH_OTHER_PARAMETERS) ///< The module has already been opened with other parameters #define ECODE_NVM3_ERR_PARAMETER (SL_STATUS_INVALID_PARAMETER) ///< Illegal parameter #define ECODE_NVM3_ERR_KEY_INVALID (SL_STATUS_INVALID_KEY) ///< Invalid key value #define ECODE_NVM3_ERR_KEY_NOT_FOUND (SL_STATUS_NOT_FOUND) ///< Key not found #define ECODE_NVM3_ERR_OBJECT_IS_NOT_DATA (SL_STATUS_NVM3_OBJECT_IS_NOT_DATA) ///< Trying to access a data object which is currently a counter object #define ECODE_NVM3_ERR_OBJECT_IS_NOT_A_COUNTER (SL_STATUS_NVM3_OBJECT_IS_NOT_A_COUNTER) ///< Trying to access a counter object which is currently a data object #define ECODE_NVM3_ERR_ERASE_FAILED (SL_STATUS_FLASH_ERASE_FAILED) ///< Erase failed #define ECODE_NVM3_ERR_WRITE_DATA_SIZE (SL_STATUS_NVM3_WRITE_DATA_SIZE) ///< The object is too large #define ECODE_NVM3_ERR_WRITE_FAILED (SL_STATUS_FLASH_PROGRAM_FAILED) ///< Error in the write operation #define ECODE_NVM3_ERR_READ_DATA_SIZE (SL_STATUS_NVM3_READ_DATA_SIZE) ///< Trying to read with a length different from actual object size #define ECODE_NVM3_ERR_READ_FAILED (SL_STATUS_OBJECT_READ) ///< Error in the read operation #define ECODE_NVM3_ERR_INIT_WITH_FULL_NVM (SL_STATUS_NVM3_INIT_WITH_FULL_NVM) ///< The module was opened with a full NVM #define ECODE_NVM3_ERR_RESIZE_PARAMETER (SL_STATUS_NVM3_RESIZE_PARAMETER) ///< Illegal parameter #define ECODE_NVM3_ERR_RESIZE_NOT_ENOUGH_SPACE (SL_STATUS_NVM3_RESIZE_NOT_ENOUGH_SPACE) ///< Not enough NVM to complete resize #define ECODE_NVM3_ERR_ERASE_COUNT_ERROR (SL_STATUS_NVM3_ERASE_COUNT_ERROR) ///< Erase counts are not valid #define ECODE_NVM3_ERR_INT_WRITE_TO_NOT_ERASED (SL_STATUS_NVM3_WRITE_TO_NOT_ERASED) ///< Write to memory that is not erased #define ECODE_NVM3_ERR_INT_ADDR_INVALID (SL_STATUS_NVM3_INVALID_ADDR) ///< Invalid NVM address #define ECODE_NVM3_ERR_INT_KEY_MISMATCH (SL_STATUS_NVM3_KEY_MISMATCH) ///< Key validation failure #define ECODE_NVM3_ERR_INT_SIZE_ERROR (SL_STATUS_NVM3_SIZE_ERROR) ///< Size mismatch error #define ECODE_NVM3_ERR_INT_EMULATOR (SL_STATUS_NVM3_EMULATOR) ///< Emulator error #define ECODE_NVM3_ERR_INT_TEST (SL_STATUS_FAIL) ///< Internal Test error #define ECODE_NVM3_ERR_NVM_ACCESS (SL_STATUS_NVM3_NVM_ACCESS) ///< A NVM function call was failing #define ECODE_NVM3_ERR_ADDRESS_RANGE (SL_STATUS_NVM3_INVALID_ADDR) ///< Address and size is out of range of available NVM /***************************************************************************//** * @brief Definitions of NVM3 constraints. ******************************************************************************/ #define NVM3_MIN_PAGE_SIZE 4096U ///< The minimum page size supported #define NVM3_MAX_OBJECT_SIZE_LOW_LIMIT 204U ///< The minimum value for the maximum object size #define NVM3_MAX_OBJECT_SIZE_HIGH_LIMIT 4096U ///< The maximum value for the maximum object size #define NVM3_MAX_OBJECT_SIZE_DEFAULT 1900U ///< The default value for the maximum object size #if !defined(NVM3_MAX_OBJECT_SIZE) #define NVM3_MAX_OBJECT_SIZE NVM3_MAX_OBJECT_SIZE_DEFAULT ///< The maximum object size #endif #if defined(NVM3_SECURITY) #define NVM3_NONCE_SIZE (12U) #define NVM3_GCM_TAG_SIZE (4U) #endif /***************************************************************************//** * @brief NVM3 static data definition helper macro for applications using linker * script placement of the NVM memory area. This macro exports the section 'name'_section * to the linker. The user must place the section name in a linker script * at an address aligned with the page size of the underlying memory system. The size of * the NVM area must be a multiple of the page size. * @n This macro also allocates the static NVM3 cache. * @n Use this macro with @ref NVM3_DEFINE_SECTION_INIT_DATA() to create initialization * data for @ref nvm3_open(). See @ref nvm3_example section for usage examples. ******************************************************************************/ #define NVM3_DEFINE_SECTION_STATIC_DATA(name, nvmSize, cacheSize) \ static nvm3_CacheEntry_t name##_cache[cacheSize]; \ static const uint8_t name##_nvm[nvmSize] \ SL_ATTRIBUTE_SECTION(STRINGIZE(name##_section)) /***************************************************************************//** * @brief NVM3 initialization data helper macro to be used with @ref * NVM3_DEFINE_SECTION_STATIC_DATA(). The @p name parameter in both macros must * match. * @n Call @ref nvm3_open() after this macro to initialize NVM3. See @ref * nvm3_example section for code examples. ******************************************************************************/ #if defined(NVM3_SECURITY) #define NVM3_DEFINE_SECTION_INIT_DATA(name, flashHandle, cryptoHandle) \ nvm3_Init_t name = \ { \ (nvm3_HalPtr_t)name##_nvm, \ sizeof(name##_nvm), \ name##_cache, \ sizeof(name##_cache) / sizeof(nvm3_CacheEntry_t), \ NVM3_MAX_OBJECT_SIZE, \ 0, \ flashHandle, \ cryptoHandle, \ NVM3_DEFAULT_SECURITY_TYPE, \ } #else #define NVM3_DEFINE_SECTION_INIT_DATA(name, flashHandle) \ nvm3_Init_t name = \ { \ (nvm3_HalPtr_t)name##_nvm, \ sizeof(name##_nvm), \ name##_cache, \ sizeof(name##_cache) / sizeof(nvm3_CacheEntry_t), \ NVM3_MAX_OBJECT_SIZE, \ 0, \ flashHandle, \ } #endif #define NVM3_KEY_INVALID 0xFFFFFFFFU ///< Invalid key identifier #define NVM3_KEY_SIZE 20U ///< Unique object key identifier size in number of bits #define NVM3_KEY_MASK ((1U << NVM3_KEY_SIZE) - 1U) ///< Unique object key identifier mask #define NVM3_KEY_MIN 0U ///< Minimum object key value #define NVM3_KEY_MAX NVM3_KEY_MASK ///< Maximum object key value #define NVM3_OBJECTTYPE_DATA 0U ///< The object is data #define NVM3_OBJECTTYPE_COUNTER 1U ///< The object is a counter /// @brief The data type for object keys. Only the 20 least significant bits are used. typedef uint32_t nvm3_ObjectKey_t; /// @brief The datatype for each cache entry. The cache must be an array of these. typedef struct nvm3_CacheEntry { nvm3_ObjectKey_t key; ///< key void *ptr; ///< pointer } nvm3_CacheEntry_t; /// @cond DO_NOT_INCLUDE_WITH_DOXYGEN typedef struct nvm3_Cache { nvm3_CacheEntry_t *entryPtr; // Pointer to cache entry structure size_t entryCount; // Total cache size bool overflow; // Cache overflow status #if defined(NVM3_OPTIMIZATION) && (NVM3_OPTIMIZATION == 1) size_t usedCount; // Number of objects in cache #endif } nvm3_Cache_t; typedef struct nvm3_ObjFragDetail { void *adr; uint16_t len; uint8_t typ; } nvm3_ObjFragDetail_t; #if defined(NVM3_SECURITY) typedef enum { NVM3_SECURITY_INVALID = 0, NVM3_SECURITY_NONE = 1, NVM3_SECURITY_AEAD = 2, NVM3_SECURITY_ENC = 3 } nvm3_SecurityType_t; #endif typedef struct { nvm3_HalPtr_t nvmAdr; // NVM address size_t nvmSize; // NVM size nvm3_Cache_t cache; // Cache management data size_t maxObjectSize; // The maximum object size in bytes size_t repackHeadroom; // The size difference between the user and forced repacks size_t totalNvmPageCnt; // The total number of NVM pages size_t validNvmPageCnt; // The number of valid NVM pages size_t fifoFirstIdx; // FIFO bottom page void *fifoFirstObj; // The first object location void *fifoNextObj; // The next free object location size_t unusedNvmSize; // The size of the unused NVM bool hasBeenOpened; // Open status size_t minUnused; // The minimum value of the unusedNvmSize const nvm3_HalHandle_t *halHandle; // HAL handle nvm3_HalInfo_t halInfo; // HAL information #if defined(NVM3_SECURITY) const nvm3_HalCryptoHandle_t *halCryptoHandle; // HAL crypto handle nvm3_SecurityType_t secType; // Security type #endif } nvm3_Handle_t; /// @endcond /// @brief NVM3 initialization data. typedef struct { nvm3_HalPtr_t nvmAdr; ///< NVM memory area base address size_t nvmSize; ///< NVM memory area size in bytes nvm3_CacheEntry_t *cachePtr; ///< A pointer to cache size_t cacheEntryCount; ///< The size of the cache in number of elements size_t maxObjectSize; ///< The maximum object size in bytes size_t repackHeadroom; ///< The size difference between the user and forced repacks const nvm3_HalHandle_t *halHandle; ///< HAL handle #if defined(NVM3_SECURITY) const nvm3_HalCryptoHandle_t *halCryptoHandle; ///< HAL crypto handle nvm3_SecurityType_t secType; ///< Security type #endif } nvm3_Init_t; /***************************************************************************//** * @brief * Open an NVM3 driver instance, which is represented by a handle * keeping information about the state. A successful open will initialize * the handle and the cache with information about the objects already in the * NVM-memory. * Several NVM3 instances using different handles must NOT overlap NVM-memory. * To change some of the parameters, * first call @ref nvm3_close and then @ref nvm3_open. * @note The driver handle must be initialized to zero before it is used the * first time. The @ref nvm3_open can be called repeatedly with the same handle * and initialization data. In that case, the next calls will be regarded as a * "no operation" and the function will return the same status as the previous * call. * * @param[out] h * A pointer to an NVM3 driver handle. * * @param[in] i * A pointer to NVM3 driver initialization data. * * @return * @ref SL_STATUS_OK on success and a NVM3 @ref sl_status_t on failure. ******************************************************************************/ sl_status_t nvm3_open(nvm3_Handle_t *h, const nvm3_Init_t *i); /***************************************************************************//** * @brief * Close the NVM3 driver instance. * * @param[in] h * A pointer to the NVM3 driver handle. * * @return * @ref SL_STATUS_OK is always returned. ******************************************************************************/ sl_status_t nvm3_close(nvm3_Handle_t *h); /***************************************************************************//** * @brief * Write the object value identified with the key to NVM. * If the data object exists with the same length, its old content is compared * with the new and only if the new content is different from the old it will * be written. * * @param[in] h * A pointer to an NVM3 driver handle. * * @param[in] key * A 20-bit object identifier. * * @param[in] value * A pointer to the object data to write. * * @param[in] len * The size of the object data in number of bytes. * * @return * @ref SL_STATUS_OK on success or a NVM3 @ref sl_status_t on failure. ******************************************************************************/ sl_status_t nvm3_writeData(nvm3_Handle_t *h, nvm3_ObjectKey_t key, const void *value, size_t len); /***************************************************************************//** * @brief * Read the object data identified with a given key from NVM. * * @param[in] h * A pointer to an NVM3 driver handle. * * @param[in] key * A 20-bit object identifier. * * @param[out] value * A pointer to the application data buffer. The read function will copy * data to this location. For best performance, the buffer should be word-aligned. * * @param[in] len * The maximum object size in number of bytes. The @ref nvm3_getObjectInfo() function * can be used to find the actual size. * * @return * @ref SL_STATUS_OK on success or a NVM3 @ref sl_status_t on failure. ******************************************************************************/ sl_status_t nvm3_readData(nvm3_Handle_t *h, nvm3_ObjectKey_t key, void *value, size_t len); /***************************************************************************//** * @brief * Read parts of the object data identified with a given key from NVM. * * @param[in] h * A pointer to an NVM3 driver handle. * * @param[in] key * A 20-bit object identifier. * * @param[out] value * A pointer to the application data buffer. The read function will copy * data to this location. For best performance, the buffer should be word-aligned. * * @param[in] ofs * The offset where data shall be read from. * * @param[in] len * The number of bytes to read. * * @return * @ref SL_STATUS_OK on success or a NVM3 @ref sl_status_t on failure. ******************************************************************************/ sl_status_t nvm3_readPartialData(nvm3_Handle_t* h, nvm3_ObjectKey_t key, void* value, size_t ofs, size_t len); /***************************************************************************//** * @brief * Find the type and size of an object in NVM. * * @param[in] h * A pointer to an NVM3 driver handle. * * @param[in] key * A 20-bit object identifier. * * @param[out] type * A pointer to the location where NVM3 shall write the object type. The type can * be either @ref NVM3_OBJECTTYPE_DATA or @ref NVM3_OBJECTTYPE_COUNTER. * * @param[out] len * A pointer to the location where NVM3 writes the object size. * * @return * @ref SL_STATUS_OK on success or a NVM3 @ref sl_status_t on failure. ******************************************************************************/ sl_status_t nvm3_getObjectInfo(nvm3_Handle_t *h, nvm3_ObjectKey_t key, uint32_t *type, size_t *len); /***************************************************************************//** * @brief * Create a list of object keys for valid objects in NVM. * * @note * The function @ref nvm3_countObjects() is also provided to count the * number of valid objects. * * @param[in] h * A pointer to an NVM3 driver handle. * * @param[out] keyListPtr * A pointer to a buffer for the key list. * * @param[in] keyListSize * The number of elements in the key list buffer. If the keyListSize = 0, * the @p keyListPtr can be NULL and the function will return the total * number of objects. * * @param[in] keyMin * The lower search key. Set to @ref NVM3_KEY_MIN to match all keys. * * @param[in] keyMax * The upper search key. Set to @ref NVM3_KEY_MAX to match all keys. * * @return * The number of keys written to the key list. This value is less than or equal * to @p keyListSize. If the @p keyListSize = 0, the function will return the * total number of objects matching the key Min - Max pattern. ******************************************************************************/ size_t nvm3_enumObjects(nvm3_Handle_t *h, nvm3_ObjectKey_t *keyListPtr, size_t keyListSize, nvm3_ObjectKey_t keyMin, nvm3_ObjectKey_t keyMax); /***************************************************************************//** * @brief * Create a list of object keys for deleted objects in NVM. * * @note * The function @ref nvm3_countDeletedObjects() is also provided to count the * number of deleted objects. * * @param[in] h * A pointer to an NVM3 driver handle. * * @param[out] keyListPtr * A pointer to a buffer for the key list. * * @param[in] keyListSize * The number of elements in the key list buffer. If the keyListSize = 0, * the @p keyListPtr can be NULL and the function will return the total * number of objects. * * @param[in] keyMin * The lower search key. Set to @ref NVM3_KEY_MIN to match all keys. * * @param[in] keyMax * The upper search key. Set to @ref NVM3_KEY_MAX to match all keys. * * @return * The number of keys written to the key list. This value is less than or equal * to @p keyListSize. If the @p keyListSize = 0, the function will return the * total number of objects matching the key Min - Max pattern. ******************************************************************************/ size_t nvm3_enumDeletedObjects(nvm3_Handle_t *h, nvm3_ObjectKey_t *keyListPtr, size_t keyListSize, nvm3_ObjectKey_t keyMin, nvm3_ObjectKey_t keyMax); /***************************************************************************//** * @brief * Delete an object from NVM. * * @param[in] h * A pointer to an NVM3 driver handle. * * @param[in] key * A 20-bit object identifier. * * @return * @ref SL_STATUS_OK on success or a NVM3 @ref sl_status_t on failure. ******************************************************************************/ sl_status_t nvm3_deleteObject(nvm3_Handle_t *h, nvm3_ObjectKey_t key); /***************************************************************************//** * @brief * Store a counter in NVM. * * @param[in] h * A pointer to an NVM3 driver handle. * * @param[in] key * A 20-bit object identifier. * * @param[in] value * The counter value to write. * * @return * @ref SL_STATUS_OK on success or a NVM3 @ref sl_status_t on failure. ******************************************************************************/ sl_status_t nvm3_writeCounter(nvm3_Handle_t *h, nvm3_ObjectKey_t key, uint32_t value); /***************************************************************************//** * @brief * Read a counter value from NVM. * * @param[in] h * A pointer to an NVM3 driver handle. * * @param[in] key * A 20-bit object identifier. * * @param[out] value * A pointer to the counter location. The read function will copy * the counter value to this location. * * @return * @ref SL_STATUS_OK on success or a NVM3 @ref sl_status_t on failure. ******************************************************************************/ sl_status_t nvm3_readCounter(nvm3_Handle_t *h, nvm3_ObjectKey_t key, uint32_t *value); /***************************************************************************//** * @brief * Increment a counter object value by 1 and read out optionally. * * @param[in] h * A pointer to an NVM3 driver handle. * * @param[in] key * A 20-bit object identifier. * * @param[out] newValue * A pointer to the counter readout location. The counter is incremented before the value * is written to this location. Set this value to NULL to ignore readout. * * @return * @ref SL_STATUS_OK on success or a NVM3 @ref sl_status_t on failure. ******************************************************************************/ sl_status_t nvm3_incrementCounter(nvm3_Handle_t *h, nvm3_ObjectKey_t key, uint32_t *newValue); /***************************************************************************//** * @brief * Delete all objects in NVM. * * @note * Users don't need to call this function to get NVM3 into an * initial valid state. * * @warning * The execution time depends on the configured NVM size and may therefore be * significant. * * @param[in] h * A pointer to an NVM3 driver handle. * * @return * @ref SL_STATUS_OK on success or a NVM3 @ref sl_status_t on failure. ******************************************************************************/ sl_status_t nvm3_eraseAll(nvm3_Handle_t *h); /***************************************************************************//** * @brief * Get the number of page erases of the most erased page in the NVM area since * the first initialization. * * @note * Except for pages marked as bad, pages will have an erase count equal to the * most erased or one less because of the wear leveling algorithm. * * @param[in] h * A pointer to an NVM3 driver handle. * * @param[in] eraseCnt * A pointer to the location where the NVM3 shall place the page * erasure counter value. * * @return * @ref SL_STATUS_OK on success or a NVM3 @ref sl_status_t on failure. ******************************************************************************/ sl_status_t nvm3_getEraseCount(nvm3_Handle_t *h, uint32_t *eraseCnt); /***************************************************************************//** * @brief * Set the page erase count. * Normally, the application should not be concerned with the erase count value. * If NVM3 is substituting a previous solution, it is possible to transfer * the erase count to NVM3 when initializing the NVM for the first time. * The erase count must be set before the @ref nvm3_open is called and will * only take effect if the NVM is completely erased or contains unknown * data to NVM3. In that case, all pages will be initialized with the supplied * erase count. After @ref nvm3_open is called, the value will be consumed * and will have no effect on further calls to @ref nvm3_open. * * @param[in] eraseCnt * The erase count. ******************************************************************************/ void nvm3_setEraseCount(uint32_t eraseCnt); /***************************************************************************//** * @brief * Execute a repack operation. NVM3 will copy data or erase pages when repacking * is needed. Calling @ref nvm3_repack() may block access to the non-volatile * memory for up to one page erasure time plus an small execution overhead. * The exact worst-case timing characteristics can be found in the data sheet for the * part. * * @note * Calling @ref nvm3_repack() is not mandatory because the functions that * write data to NVM will trigger a repack if needed. Because a * repack operation may be time consuming, the application may want to be * in control of when repacking occurs by calling this function. * * More information about the repack operation can be found in the * @ref nvm3_repack section. * * @param[in] h * A pointer to an NVM3 driver handle. * * @return * @ref SL_STATUS_OK on success or a NVM3 @ref sl_status_t on failure. ******************************************************************************/ sl_status_t nvm3_repack(nvm3_Handle_t *h); /***************************************************************************//** * @brief * Check the internal status of NVM3 and return true if a repack * operation is required. The application must call @ref nvm3_repack() to * perform the actual repack operation. * * @param[in] h * A pointer to an NVM3 driver handle. * * @return * true if repacking is needed, false if repacking is not needed. ******************************************************************************/ bool nvm3_repackNeeded(nvm3_Handle_t *h); /***************************************************************************//** * @brief * Resize the NVM area used by an open NVM3 instance. * The area can be resized by changing the start or end address either up * or down in memory. Because the input parameters to NVM3 are start address * and size, users should be cautious. Either move the start address up or * down in memory and adjust the size accordingly to keep the end address, * or keep the address and change the size only. * It is not possible to resize the area by doing changes in both ends of the * NVM address range at the same time. * If the resize operation returns @ref SL_STATUS_OK, the instance is still * open and can be used to access objects in the resized NVM. * If the resize operation fails, the instance will still be open but with * unchanged size. * * @note * It is possible to decrease the NVM area to a new size that is not capable * of keeping the already stored objects. The result is loss of data. * * @param[in] h * A pointer to an NVM3 driver handle. * * @param[in] newAddr * The start address of the NVM after resize. * * @param[in] newSize * The size of the NVM after resize. * * @return * @ref SL_STATUS_OK on success or a NVM3 @ref sl_status_t on failure. ******************************************************************************/ sl_status_t nvm3_resize(nvm3_Handle_t *h, nvm3_HalPtr_t newAddr, size_t newSize); /***************************************************************************//** * @brief * Count valid objects. * * @param[in] h * A pointer to an NVM3 driver handle. * * @return * The number of valid objects. ******************************************************************************/ __STATIC_INLINE size_t nvm3_countObjects(nvm3_Handle_t *h) { return nvm3_enumObjects(h, NULL, 0, NVM3_KEY_MIN, NVM3_KEY_MAX); } /***************************************************************************//** * @brief * Count deleted objects. * * @param[in] h * A pointer to an NVM3 driver handle. * * @return * The number of deleted objects. ******************************************************************************/ __STATIC_INLINE size_t nvm3_countDeletedObjects(nvm3_Handle_t *h) { return nvm3_enumDeletedObjects(h, NULL, 0, NVM3_KEY_MIN, NVM3_KEY_MAX); } /** @} (end addtogroup nvm3) */ #ifdef __cplusplus } #endif #include "nvm3_default.h" /* *INDENT-OFF* */ /************ THIS SECTION IS FOR DOCUMENTATION ONLY !**********************//** * @addtogroup nvm3 NVM3 - NVM Data Manager * @brief NVM3 Non-Volatile Memory Data Management driver * @{ # Introduction {#nvm3_intro} The NVM3 driver provides a way for an application to safely store and retrieve variable-size objects in a page-based non-volatile memory (NVM). Objects are identified with 20-bit object identifiers denoted as keys. The driver is designed to use pages in a sequential order to provide equal usage and wear. The driver is resilient to power loss or reset events, ensuring that objects retrieved from the driver are in a valid state. A valid object will always be the last successfully stored object. NVM3 can detect NVM defects and mark pages as unusable. NVM3 will continue to operate on good pages after defect pages are detected. # Objects {#nvm3_objects} An NVM3 object is data that can be stored in NVM. The object is handled as an array of bytes up to @ref NVM3_MAX_OBJECT_SIZE in size. NVM3 can handle two types of objects. -# Regular data objects. Data objects can store information of any size up to maximum @ref NVM3_MAX_OBJECT_SIZE bytes. -# 32-bit counter objects. Counter objects can store 32-bit counters that are accessed with a separate set of API functions. The counter object is designed to be compact while minimizing memory wear in applications that require frequent persistent counter increments. See @ref nvm3_api for more details on the API. # Repacking {#nvm3_repack} As the NVM fills up, it reaches a point where it can no longer store additional objects, and a repacking operation is required to release out-of-date objects to free up NVM. Because both writing data and erasing pages take a long time, the NVM3 driver does not trigger the process by itself unless free memory reaches a critical low level. As an alternative, the application can trigger the repacking process by calling the @ref nvm3_repack() function. During the call, NVM3 will either move data to a new page or erase pages that can be reused. At most, the call will block for a period equal to a page erasure time or the time to write the largest size object (whatever is largest) plus a small execution overhead. Page erasure and flash write timing for the EFM32 or EFR32 parts can be found in the datasheet. NVM3 uses two thresholds for repacking: -# Forced threshold. This is the threshold used to force automatic repacking when free memory reaches a critical low level. -# User threshold. This is the threshold used by @ref nvm3_repackNeeded(). @ref nvm3_repack() will not perform repacking unless free memory is below this threshold. The user can define the user threshold by entering a value in the repackHeadroom member of the @ref nvm3_Init_t structure used by the @ref nvm3_open() function. The repackHeadroom value defines the difference between the user and forced threshold. The forced threshold is the minimum low memory threshold defined by the page size and maximum object size and can't be changed by the user. The default value for the repack headroom is 0, meaning that the forced and user thresholds are equal. An NVM3 function that deletes or modifies data or counter object will trigger an automatic repack operation when free memory is below the forced threshold. The check is done before the object is modified, not after. The application can use @ref nvm3_repackNeeded() to determine if repacking is needed. To initiate repacks, call @ref nvm3_repack(). Note that this function will perform repacks only if they are needed. @note The repack threshold can be changed to prevent multiple modifications of objects between user called repacks from causing forced repacks. Note that "high" values of the repack headroom may cause increased NVM wear from increased number of repacks. See @ref nvm3_timing section for more details on repack timing. # Caching {#nvm3_cache} NVM3 includes an object location lookup cache to speed up object access, as searching through the entire NVM3 contents for an object could otherwise be slow. It is important to note that this cache only stores the location of the object and not the object data itself. To ensure that cache can hold all necessary information, it must be configured to a size equivalent to or larger than the number of objects stored in NVM, including those deleted, as long as they are not discarded by the repack function. If the cache is available, the driver will first look in the cache to find the position of the object in NVM. If the object position is not found in the cache, the object position will be found by searching the NVM. The search will start at the last stored object and search all the way to the oldest object. If the object is found, the cache is updated accordingly. NVM3 Optimization improves the NVM3 initialization and object lookup time. Code size increases ~1248 bytes with NVM3 Optimization enabled. NVM3 driver provides a means to enable or disable Optimization from Simplicity Studio UC. The application must allocate and support data for the cache. See the @ref nvm3_open function for more details. The size of each cache element is one uint32_t and one pointer giving a total of 8 bytes (2 words) per entry for EFM32 and EFR32 devices. With Optimization enabled, the size of each cache element is two uint32_t and one pointer giving a total of 12 bytes (3 words) per entry. @note The cache is fully initialized by @ref nvm3_open() and automatically updated by any subsequent write, read, or delete function call. # Global Data (variables) {#nvm3_data} The NVM3 service uses global variables to store intermediate data during open, read, write, increment, and delete calls. The application must set the value of the @ref nvm3_maxFragmentCount at run-time before any NVM3 functions are called. NVM3 does not support overlapped calls. If there is any chance that the application can issue overlapped calls, the NVM3 locking mechanism must be present and protected from that. @note If the application uses more than one NVM3 instance, the variables will be shared between the instances. Be sure to allocate data that have a size that is large enough for the largest usage. # Stack Usage {#nvm3_stack} NVM3 function calls are nested several levels deep. The stack usage has been measured on some EFM32, and EFR32 targets with NVM3 source for IAR and ARM GCC. The maximum stack usage measured was 420 bytes for IAR, and 472 bytes for ARM GCC builds. The unit test used to validate the stack usage has a 10% margin and uses a stack limit of 462 bytes for IAR and 520 bytes for ARM GCC. The maximum stack usage measured on SIXX device was 540 bytes for IAR, and 520 bytes for ARM GCC builds with security. The unit test used to validate the stack usage has a 10% margin and uses a stack limit of 600 bytes for IAR and 576 bytes for ARM GCC builds with security. Note that the actual stack usage is a little different on the Cortex M4 and M33 with NVM3 source. # The API {#nvm3_api} The NVM3 API is defined in the nvm3.h file. The application code must include the nvm3.h header file to get access to all definitions, datatypes, and function prototypes defined by NVM3. This section contains brief descriptions of NVM3 functions. For more information about parameters and return values, see the Function documentation section. Most functions return an @ref sl_status_t that has the value @ref SL_STATUS_OK on success, or see nvm3.h for other values. @ref nvm3_open() and @ref nvm3_close(). @n These functions open and close an NVM3 instance. @ref nvm3_open() takes a handle of type nvm3_Handle_t and initialization data of type @ref nvm3_Init_t. The helper macro pair @ref NVM3_DEFINE_SECTION_STATIC_DATA() and @ref NVM3_DEFINE_SECTION_INIT_DATA() are provided to simplify initialization data definition. For usage examples, see the @ref nvm3_example section. @ref nvm3_getObjectInfo(), @ref nvm3_enumObjects(), @ref nvm3_deleteObject() and nvm3_countObjects() @n These functions work on all objects. @ref nvm3_enumObjects() gets a list of keys to valid objects in the NVM. The search can also be constrained by the function parameters. @ref nvm3_countObjects() can be useful at startup to distinguish between a first startup without any valid objects present and later reboots with valid objects persistently stored in NVM. @ref nvm3_writeData() and @ref nvm3_readData() @n Write and read data objects. @ref nvm3_writeCounter(), @ref nvm3_readCounter() and @ref nvm3_incrementCounter() @n Write, read, and increment 32-bit counter objects. @ref nvm3_eraseAll() @n Erase all objects in NVM. @ref nvm3_getEraseCount() @n Return the erasure count for the most erased page in NVM. @ref nvm3_repack() and @ref nvm3_repackNeeded() @n Manage NVM3 repacking operations. @ref nvm3_resize() @n Resize the NVM area used by an open NVM3 instance. # API Locking and Interrupt handling {#nvm3_locking} Common for all NVM3 API calls is that they are not re-entrant. By default, all functions are protected with protection functions that disable interrupts. @note The default NVM3 protection functions can be substituted by the application if other synchronization functions are available and disabling interrupts for extended periods is not desired. If the application does all the nvm3-calls from the same thread and guarantees no overlapping calls, the lock functions don't have to do anything. # Memory Placement {#nvm3_memory_placement} The application is responsible for placing the NVM area correctly. The minimum requirements for memory placement are as follows: -# NVM area start address must be aligned with a page of the underlying memory system. -# NVM area size must be a multiple of the page size. The minimum required NVM size is dependent on both the NVM page size and the NVM3_MAX_OBJECT_SIZE value. For a device with 4 kB page size and typical values for NVM3_MAX_OBJECT_SIZE, the following is the minimum required number of pages: - For NVM3_MAX_OBJECT_SIZE=208: 3 pages - For NVM3_MAX_OBJECT_SIZE=1900: 4 pages - For NVM3_MAX_OBJECT_SIZE=4096: 5 pages @ref NVM3_DEFINE_SECTION_STATIC_DATA() and @ref NVM3_DEFINE_SECTION_INIT_DATA() macros are provided to support the creation of the NVM area and initialization data. A linker section called 'name'_section is defined by @ref NVM3_DEFINE_SECTION_STATIC_DATA(). The NVM area is placed within the linker section. The application linker script must place the section according to the requirements above. An error is returned by @ref nvm3_open() on alignment or size violation. @note When the start address and size of the data area are defined and used by an application, make sure you use the same values at every program startup and also reuse by new versions of the software after an upgrade. If an application tries to open an instance with a start address or size that does not match the previous use, it can result in permanent data loss and failure. # Configuration Options {#nvm3_configuration} There are no compile-time configuration options for NVM3. All configuration parameters are contained in @ref nvm3_Init_t. @note The @ref nvm3_data must however be configured for correct size and have correct values for NVM3 to behave correctly. # Bad NVM Page Handling {#nvm3_bad_page_handling} NVM3 has been designed to detect page erase and write errors during normal operation and mark failing pages as BAD. If a write operation fails, all objects that have been written to the page prior to the write error are copied to the next free page before the page is marked as BAD and the write operation resumes. If the recovery operation is successful, the operation is regarded as complete, and the function will return @ref SL_STATUS_OK status. @note Erase and write errors may not be detected by NVM3 if the device is used until End-of-Life (EOL), where the failure mode can be that the NVM content is changing during a power cycle. # Error Handling {#nvm3_error_handling} The NVM3 error handling involves most functions returning an error code. The @ref nvm3_countObjects is different because it returns the actual number of objects found. The behavior and return values for most functions, such as @ref nvm3_readData, @ref nvm3_writeData, and so on should be self explanatory, while the @ref nvm3_open is slightly different. @ref nvm3_open will always try to recover from the previous state and continue without an error, if possible. In other words, if a valid NVM3 instance is established, @ref nvm3_open will recover from brownouts and power cycles at any time in any operation and bring the system to a valid state where all pages and objects are in a known state and return success whenever possible. From this state, normal operation can resume. If @ref nvm3_open returns an error, it's an indication of either a design or coding error or that many of the NVM pages have been marked as BAD, leaving insufficient space in the NVM to progress. Operation may not resume if @ref nvm3_open returns an error. @note Because the @ref nvm3_open may need to do recovery operations, the execution time will occasionally vary. # Storing Objects in Internal Flash {#nvm3_support_internal_flash} NVM3 has support for writing and reading objects in internal, i.e., memory-mapped Flash memory through the nvm3_hal_flash.c "driver". nvm3_hal_flash.c is using EMLIB functions to write and erase data while using regular memory functions to read data from Flash. @n The "driver" for internal Flash is selected by setting the halHandle in the nvm3_open initialization structure to point to nvm3_halFlashHandle. # Storing Objects in External Flash {#nvm3_support_external_flash} NVM3 has support for writing and reading objects in external Flash by using the nvm3_hal_flash_ext.c "driver". The nvm3_hal_flash_ext.c is using the SE manager functions to write and erase data while using regular memory functions to read data from external Flash. @n The "driver" for external Flash is selected by setting the halHandle in the nvm3_open initialization structure to point to nvm3_halFlashHandle. # Securing Objects in External Flash {#nvm3_support_secure_storage} All data objects are authenticated + encrypted using AES-GCM mode and counter objects are stored in plain. Each data object which is securely stored in external flash has a size overhead of 8 bytes. NVM3 uses device generated key for crypto operations. Reading the external memory directly (without using NVM3) will not give access to the unencrypted data objects. @n HAL for crypto is added to have the flexibility to use available crypto which can be selected by setting the halCryptoHandle in the nvm3_open initialization structure to point to desired crypto handle. @n NVM3 crypto key will be changed if "device erase" is triggered on a debug locked part, which will also clean up the NVM3 from the memory. In this case, if previously stored NVM3 data is reloaded to the same device, NVM3 data read will fail as NVM3 crypto key has changed. # NVM3 Source code {#nvm3_source_code} The NVM3 source code can be compiled for Cortex M4 and M33 with either ARM GCC or IAR toolchains. In addition, the NVM3 driver provides a means to securely store and retrieve the objects in external Flash using authenticated encryption. Objects in internal Flash need not be encrypted, while objects in external Flash must always be encrypted. # Storage Capacity {#nvm3_capacity} Basic storage is defined as the size of on instance of all objects, including any overhead stored with the data. For NVM3 the maximum amount of data you can store is dependent on the number of flash pages used for storage and the max object size used for NVM3. The following table shows the maximum allowed basic storage for a varying number of 4 kB or 8 kB flash pages and the minimum (204 bytes), default (254 bytes), high (1900 bytes) and maximum (4096 bytes) max object size. Note that this is a theoretical limit, and if the basic storage is at this limit, no space is left for wear-levelling, and page erases will be forced for every object written. The NVM3 instance should therefore be configured with enough flash pages to put the maximum allowed basic storage significantly higher than the actual basic storage. ## Max Allowed Basic Storage with 4 kB page size | Flash pages | Total size (bytes) | Max allowed basic storage (bytes) | | | | |-------------------------------|--------------------------------------|-----------------------------------------|-------------------------------------|--------------------------------------|--------------------------------------| | | | Max object size = 204 bytes | Max object size = 254 bytes | Max object size = 1900 bytes | Max object size = 4096 bytes | | 3 | 12228 | 3652 | 3552 | 260 | 0 | | 4 | 16304 | 7728 | 7628 | 4336 | 0 | | 5 | 20380 | 11804 | 11704 | 8412 | 4020 | | 6 | 24456 | 15880 | 15780 | 12488 | 8096 | | 7 | 28532 | 19956 | 19856 | 16564 | 12172 | | 8 | 32608 | 24032 | 23932 | 20640 | 16248 | | 9 | 36684 | 28108 | 28008 | 24716 | 20324 | | 10 | 40760 | 32184 | 32084 | 28792 | 24400 | ## Max Allowed Basic Storage with 8 kB page size | Flash pages | Total size (bytes) | Max allowed basic storage (bytes) | | | | |-------------------------------|--------------------------------------|-----------------------------------------|-------------------------------------|--------------------------------------|--------------------------------------| | | | Max object size = 204 bytes | Max object size = 254 bytes | Max object size = 1900 bytes | Max object size = 4096 bytes | | 3 | 24516 | 7748 | 7648 | 4356 | 0 | | 4 | 32688 | 15920 | 15820 | 12528 | 8136 | | 5 | 40860 | 24092 | 23992 | 20700 | 16308 | | 6 | 49032 | 32264 | 32164 | 28872 | 24480 | # Default Instance {#nvm3_default} Several NVM3 instances can be created on a device and live independently of each other, but to save memory, it is usually desirable to use only one NVM3 instance as each instance adds some overhead. For this reason, a default instance exists that is used by all Silicon Labs wireless stacks. The API to initialize the default instance and the handles to use with the regular NVM3 API are described in @ref nvm3default. # NVM3 in Simplicity Commander {#nvm3_commander} Simplicity Commander is a single, all-purpose tool to be used in a production environment. It is invoked using a simple Command Line Interface (CLI) that can also be scripted. Simplicity Commander supports reading out the NVM3 data area from a device and parsing the NVM3 data to extract stored values. This can be useful in a debugging scenario where you may need to find out the stored state of an application that has been running for some time. For more information about using the Simplicity Commander with NVM3, see UG162: Simplicity Commander Reference Guide. # Execution Timing {#nvm3_timing} There are several factors that affect the execution time for NVM3 calls that can update the NVM, described below. The primary factor when doing updates is that data must be written to flash. Writing to flash is relatively slow, and timing information for the particular device is available in the datasheet and can be used to calculate the approximately minimum execution time. Note that NVM3 will, in addition to the user data, write object headers, and the software will add some overhead. The relative overhead will be larger for smaller compared to larger objects. When updating the flash store, repacking must be done from time to time. See the @ref nvm3_repack section for more details about why repacking is needed and how it works. To minimize the time when repacks are executed, there are a few configurations that affect how NVM3 works. The most important configurations are listed below: -# The repackHeadroom parameter in the @ref nvm3_Init_t structure can be used to set the number of bytes that can be written before a forced repack is triggered. To make this work, the @ref nvm3_repack() function must be called until the @ref nvm3_repackNeeded() returns false before the actual write. -# Use as small objects as possible and define the NVM3_MAX_OBJECT_SIZE accordingly. Writing and repacking large objects is time-consuming. Limiting the maximum object size will limit the time spent in both write and repack functions. When triggered, the repack function will either copy data or erase a page. To limit the time spent when copying, the repack function will return when the NVM3_MAX_OBJECT_SIZE number of bytes have been copied. The copy operation will resume on the next call to repack, and the application may have to call the repack function several times to complete a full repack operation. @note Performing the @ref nvm3_repack()/@ref nvm3_repackNeeded() loop is highly recommended before any timing-sensitive procedure. # Examples {#nvm3_example} Example 1 shows initialization, usage of data objects, and repacking. @include nvm3_example_1.c Example 2 shows initialization and usage of counter objects. The counter object uses a compact way of storing a 32-bit counter value while minimizing NVM wear. @include nvm3_example_2.c * @} end group nvm3 ****************************************************/ #endif /* NVM3_GENERIC_H */