diff --git a/components/nvs_flash/.build-test-rules.yml b/components/nvs_flash/.build-test-rules.yml index e939aa9bbd..61085c2382 100644 --- a/components/nvs_flash/.build-test-rules.yml +++ b/components/nvs_flash/.build-test-rules.yml @@ -4,6 +4,7 @@ components/nvs_flash/host_test: - nvs_flash - nvs_sec_provider - esp_partition + - esp_blockdev enable: - if: IDF_TARGET == "linux" @@ -12,6 +13,7 @@ components/nvs_flash/test_apps: - spi_flash - nvs_flash - nvs_sec_provider + - esp_blockdev - esp_partition disable_test: - if: IDF_TARGET not in ["esp32", "esp32c3"] diff --git a/components/nvs_flash/CMakeLists.txt b/components/nvs_flash/CMakeLists.txt index a18ea32f42..d657ad7d3f 100644 --- a/components/nvs_flash/CMakeLists.txt +++ b/components/nvs_flash/CMakeLists.txt @@ -32,7 +32,7 @@ elseif(esp_tee_build) "src/nvs_types.cpp" "src/nvs_platform.cpp") - set(requires esp_partition mbedtls) + set(requires esp_partition esp_blockdev mbedtls) set(priv_requires spi_flash esp_libc cxx) idf_component_register(SRCS "${srcs}" @@ -60,7 +60,7 @@ else() "src/nvs_platform.cpp" "src/nvs_bootloader.c") - set(requires esp_partition) + set(requires esp_partition esp_blockdev) set(priv_requires spi_flash) if(NOT ${target} STREQUAL "linux") list(APPEND priv_requires esp_libc esptool_py nvs_sec_provider) diff --git a/components/nvs_flash/Kconfig b/components/nvs_flash/Kconfig index 5862f3af2b..ad1cb87f68 100644 --- a/components/nvs_flash/Kconfig +++ b/components/nvs_flash/Kconfig @@ -45,4 +45,10 @@ menu "NVS" instead of internal RAM. It can help applications using large nvs partitions or large number of keys to save heap space in internal RAM. SPIRAM heap allocation negatively impacts speed of NVS operations as the CPU accesses NVS cache via SPI instead of direct access to the internal RAM. + + config NVS_BDL_STACK + bool "Run NVS on BDL instead of ESP_Partition" + default n + help + This option enforces internal use of Block Device Layer instead ESP_Partition. endmenu diff --git a/components/nvs_flash/host_test/nvs_host_test/main/test_nvs_initialization.cpp b/components/nvs_flash/host_test/nvs_host_test/main/test_nvs_initialization.cpp index 1d06c5b7a5..9e994e46ad 100644 --- a/components/nvs_flash/host_test/nvs_host_test/main/test_nvs_initialization.cpp +++ b/components/nvs_flash/host_test/nvs_host_test/main/test_nvs_initialization.cpp @@ -3,6 +3,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ +#include "sdkconfig.h" #include #include "nvs.hpp" #include "nvs_partition_manager.hpp" diff --git a/components/nvs_flash/include/nvs_flash.h b/components/nvs_flash/include/nvs_flash.h index b5332fa107..e0828f0a36 100644 --- a/components/nvs_flash/include/nvs_flash.h +++ b/components/nvs_flash/include/nvs_flash.h @@ -92,7 +92,11 @@ esp_err_t nvs_flash_init(void); esp_err_t nvs_flash_init_partition(const char *partition_label); /** - * @brief Initialize NVS flash storage for the partition specified by partition pointer. + * @brief Initialize NVS flash storage on the partition specified by esp_partition pointer. + * + * This API initialises the NVS storage on an esp_partition. The storage is identified + * by the label of the respective partition. + * Note: this API is only available when the block device support is disabled in the menuconfig. * * @param[in] partition pointer to a partition obtained by the ESP partition API. * @@ -106,6 +110,30 @@ esp_err_t nvs_flash_init_partition(const char *partition_label); */ esp_err_t nvs_flash_init_partition_ptr(const esp_partition_t *partition); +/** + * @brief Initialize NVS flash storage on the specified block device handle. + * + * This API initialises the NVS storage on a bdl device and identifies it with the given label. + * Caller of this API is responsible for creating and managing the lifetime of the block device handle. + * NVS component will stop using the handle when nvs_flash_deinit_partition() is called for the last + * partition label using this block device handle. + * + * Note: to use this API, the block device support must be enabled in the menuconfig option NVS_BDL_STACK + * + * @param[in] partition_label label of the partition to initialize + * @param[in] bdl block device handle for the partition + * + * @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_INVALID_ARG in case partition_label or bdl is NULL + * - ESP_ERR_NO_MEM in case memory could not be allocated for the internal structures + * - ESP_ERR_NOT_SUPPORTED if the bdl handle does not fulfill the NVS compliance requirements + * - one of the error codes from the underlying flash storage driver + */ +esp_err_t nvs_flash_init_partition_bdl(const char* partition_label, esp_blockdev_handle_t bdl); + /** * @brief Deinitialize NVS storage for the default NVS partition * diff --git a/components/nvs_flash/src/nvs_api.cpp b/components/nvs_flash/src/nvs_api.cpp index bef404ccba..0639aef56c 100644 --- a/components/nvs_flash/src/nvs_api.cpp +++ b/components/nvs_flash/src/nvs_api.cpp @@ -17,6 +17,7 @@ #include "esp_err.h" #include #include "nvs_internal.h" +#include "nvs_partition_lookup.hpp" // Uncomment this line to force output from this module // #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG @@ -91,6 +92,7 @@ static esp_err_t close_handles_and_deinit(const char* part_name) return NVSPartitionManager::get_instance()->deinit_partition(part_name); } +#ifndef CONFIG_NVS_BDL_STACK extern "C" esp_err_t nvs_flash_init_partition_ptr(const esp_partition_t *partition) { esp_err_t lock_result = Lock::init(); @@ -108,10 +110,12 @@ extern "C" esp_err_t nvs_flash_init_partition_ptr(const esp_partition_t *partiti return ESP_ERR_NO_MEM; } - const uint32_t sec_size = esp_partition_get_main_flash_sector_size(); + uint32_t sec_size = NVS_CONST_PAGE_SIZE; + uint32_t size = part->get_size(); + esp_err_t init_res = NVSPartitionManager::get_instance()->init_custom(part, 0, - partition->size / sec_size); + size / sec_size); if (init_res != ESP_OK) { delete part; @@ -119,6 +123,46 @@ extern "C" esp_err_t nvs_flash_init_partition_ptr(const esp_partition_t *partiti return init_res; } +#else +extern "C" esp_err_t nvs_flash_init_partition_bdl(const char* partition_label, esp_blockdev_handle_t bdl) +{ + esp_err_t lock_result = Lock::init(); + if (lock_result != ESP_OK) { + return lock_result; + } + Lock lock; + + if (partition_label == nullptr) { + return ESP_ERR_INVALID_ARG; + } + + if (bdl == nullptr) { + return ESP_ERR_INVALID_ARG; + } + + if (!NVSPartition::is_bdl_nvs_compliant(partition_label, bdl)) { + return ESP_ERR_NOT_SUPPORTED; + } + + NVSPartition *part = new (std::nothrow) NVSPartition(partition_label, bdl, false); + if (part == nullptr) { + return ESP_ERR_NO_MEM; + } + + uint32_t sec_size = NVS_CONST_PAGE_SIZE; + uint32_t size = part->get_size(); + + esp_err_t init_res = NVSPartitionManager::get_instance()->init_custom(part, + 0, + size / sec_size); + + if (init_res != ESP_OK) { + delete part; + } + + return init_res; +} +#endif #ifndef LINUX_HOST_LEGACY_TEST extern "C" esp_err_t nvs_flash_init_partition(const char *part_name) @@ -129,7 +173,6 @@ extern "C" esp_err_t nvs_flash_init_partition(const char *part_name) } Lock lock; - assert(nvs::Page::SEC_SIZE == esp_partition_get_main_flash_sector_size()); return NVSPartitionManager::get_instance()->init_partition(part_name); } @@ -170,7 +213,6 @@ extern "C" esp_err_t nvs_flash_secure_init_partition(const char *part_name, nvs_ } Lock lock; - assert(nvs::Page::SEC_SIZE == esp_partition_get_main_flash_sector_size()); return NVSPartitionManager::get_instance()->secure_init_partition(part_name, cfg); } @@ -197,38 +239,23 @@ 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, part_name); - if (partition == nullptr) { - return ESP_ERR_NOT_FOUND; + // reuse the partition lookup code to find the NVS partition to streamline the BDL and non-BDL code paths + nvs::Partition* part = nullptr; + esp_err_t err = nvs::partition_lookup::lookup_nvs_partition(part_name, &part); + if (err != ESP_OK || part == nullptr) { + return err; } - return esp_partition_erase_range(partition, 0, partition->size); + // erase the partition + err = part->erase_range(0, part->get_size()); + + // No need to delete the partition here, as it is managed by the NVSPartitionManager. + return err; } extern "C" esp_err_t nvs_flash_erase_partition_ptr(const esp_partition_t *partition) { - esp_err_t lock_result = Lock::init(); - if (lock_result != ESP_OK) { - return lock_result; - } - Lock lock; - - if (partition == nullptr) { - return ESP_ERR_INVALID_ARG; - } - - // if the partition is initialized, uninitialize it first - if (NVSPartitionManager::get_instance()->lookup_storage_from_name(partition->label)) { - const esp_err_t err = close_handles_and_deinit(partition->label); - - // only hypothetical/future case, deinit_partition() only fails if partition is uninitialized - if (err != ESP_OK) { - return err; - } - } - - return esp_partition_erase_range(partition, 0, partition->size); + return nvs_flash_erase_partition(partition->label); } extern "C" esp_err_t nvs_flash_erase(void) diff --git a/components/nvs_flash/src/nvs_encrypted_partition.cpp b/components/nvs_flash/src/nvs_encrypted_partition.cpp index 335c8d42a8..a3b0eb572a 100644 --- a/components/nvs_flash/src/nvs_encrypted_partition.cpp +++ b/components/nvs_flash/src/nvs_encrypted_partition.cpp @@ -3,15 +3,20 @@ * * SPDX-License-Identifier: Apache-2.0 */ - #include #include "nvs_encrypted_partition.hpp" #include "nvs_types.hpp" +#include "nvs_constants.h" namespace nvs { +#ifdef CONFIG_NVS_BDL_STACK +NVSEncryptedPartition::NVSEncryptedPartition(const char* label, const esp_blockdev_handle_t bdl, const bool managed_bdl) + : NVSPartition(label, bdl, managed_bdl) { } +#else NVSEncryptedPartition::NVSEncryptedPartition(const esp_partition_t *partition) : NVSPartition(partition) { } +#endif // CONFIG_NVS_BDL_STACK esp_err_t NVSEncryptedPartition::init(nvs_sec_cfg_t* cfg) { @@ -33,19 +38,20 @@ esp_err_t NVSEncryptedPartition::init(nvs_sec_cfg_t* cfg) esp_err_t NVSEncryptedPartition::read(size_t src_offset, void* dst, size_t size) { - /** Currently upper layer of NVS reads entries one by one even for variable size - * multi-entry data types. So length should always be equal to size of an entry.*/ - if (size != sizeof(Item)) return ESP_ERR_INVALID_SIZE; + // Currently upper layer of NVS reads entries one by one even for variable size + // multi-entry data types. So length should always be equal to size of an entry. + // here we make sure that the size is really compliant with the minimal encryption block size. + if (size % NVS_ENCRYPT_BLOCK_SIZE != 0) return ESP_ERR_INVALID_SIZE; // read data - esp_err_t read_result = esp_partition_read(mESPPartition, src_offset, dst, size); + esp_err_t read_result = NVSPartition::read(src_offset, dst, size); if (read_result != ESP_OK) { return read_result; } // decrypt data //sector num required as an arr by mbedtls. Should have been just uint64/32. - uint8_t data_unit[16]; + uint8_t data_unit[NVS_ENCRYPT_BLOCK_SIZE]; uint32_t relAddr = src_offset; @@ -64,7 +70,7 @@ esp_err_t NVSEncryptedPartition::read(size_t src_offset, void* dst, size_t size) esp_err_t NVSEncryptedPartition::write(size_t addr, const void* src, size_t size) { - if (size % ESP_ENCRYPT_BLOCK_SIZE != 0) return ESP_ERR_INVALID_SIZE; + if (size % NVS_ENCRYPT_BLOCK_SIZE != 0) return ESP_ERR_INVALID_SIZE; // copy data to buffer for encryption uint8_t* buf = new (std::nothrow) uint8_t [size]; @@ -77,7 +83,7 @@ esp_err_t NVSEncryptedPartition::write(size_t addr, const void* src, size_t size uint8_t entrySize = sizeof(Item); //sector num required as an arr by mbedtls. Should have been just uint64/32. - uint8_t data_unit[16]; + uint8_t data_unit[NVS_ENCRYPT_BLOCK_SIZE]; /* Use relative address instead of absolute address (relocatable), so that host-generated * encrypted nvs images can be used*/ @@ -103,7 +109,7 @@ esp_err_t NVSEncryptedPartition::write(size_t addr, const void* src, size_t size } // write data - esp_err_t result = esp_partition_write(mESPPartition, addr, buf, size); + esp_err_t result = NVSPartition::write(addr, buf, size); delete [] buf; diff --git a/components/nvs_flash/src/nvs_encrypted_partition.hpp b/components/nvs_flash/src/nvs_encrypted_partition.hpp index 00c7bb1bb5..6cf128203d 100644 --- a/components/nvs_flash/src/nvs_encrypted_partition.hpp +++ b/components/nvs_flash/src/nvs_encrypted_partition.hpp @@ -5,27 +5,70 @@ */ #pragma once +#include "sdkconfig.h" // For CONFIG_NVS_BDL_STACK #include "mbedtls/aes.h" // For mbedtls_aes_xts_context #include "nvs_flash.h" // For nvs_sec_cfg_t #include "nvs_partition.hpp" namespace nvs { +/** + * Implementation of encrypted esp_partition or bdl storage wrapper for NVS. + */ class NVSEncryptedPartition : public NVSPartition { public: +#ifdef CONFIG_NVS_BDL_STACK + NVSEncryptedPartition(const char* label, const esp_blockdev_handle_t bdl, const bool managed_bdl = true); +#else NVSEncryptedPartition(const esp_partition_t *partition); +#endif virtual ~NVSEncryptedPartition() { } + /** + * Initializes the AES encryption components with the provided configuration. + * + * @param cfg the configuration for the AES encryption + * + * @return + * - ESP_OK on success + * - other error codes from the AES component initialization + * - ESP_ERR_NVS_XTS_CFG_FAILED if the AES key configuration is invalid + */ esp_err_t init(nvs_sec_cfg_t* cfg); + /** + * Reads and decrypts the data from the storage + * This function is intended for reading data aligned to the size of NVS entry. + * + * @param src_offset the offset in the storage to read from + * @param dst an already allocated buffer to read data into + * @param size the size of the data to read in bytes + * + * @return + * - ESP_OK on success + * - other error codes from the esp_partition or BDL API + */ esp_err_t read(size_t src_offset, void* dst, size_t size) override; + /** + * Encrypts the data from the src buffer and writes it to the storage. + * This function is intended for writing data aligned to the size of NVS entry. + * Repeated writes to the same address are not expected to be called. + * + * @param dst_offset the offset in the storage to write to + * @param src pointer to the buffer to write data from + * @param size the size of the data to write in bytes + * + * @return + * - ESP_OK on success + * - other error codes from the esp_partition or BDL API + */ esp_err_t write(size_t dst_offset, const void* src, size_t size) override; protected: - mbedtls_aes_xts_context mEctxt; - mbedtls_aes_xts_context mDctxt; + mbedtls_aes_xts_context mEctxt; // AES context for encryption + mbedtls_aes_xts_context mDctxt; // AES context for decryption }; } // nvs diff --git a/components/nvs_flash/src/nvs_partition.cpp b/components/nvs_flash/src/nvs_partition.cpp index dd2547d518..372a031cad 100644 --- a/components/nvs_flash/src/nvs_partition.cpp +++ b/components/nvs_flash/src/nvs_partition.cpp @@ -3,12 +3,20 @@ * * SPDX-License-Identifier: Apache-2.0 */ - -#include #include "nvs_partition.hpp" +#include +#include "esp_log.h" +#include "nvs_constants.h" // For NVS_CONST_PAGE_SIZE + +#ifdef CONFIG_NVS_BDL_STACK + #include "esp_partition.h" // For esp_blockdev_handle_t +#endif // CONFIG_NVS_BDL_STACK + +#define TAG "NVSPartition" namespace nvs { +#ifndef CONFIG_NVS_BDL_STACK NVSPartition::NVSPartition(const esp_partition_t* partition) : mESPPartition(partition) { @@ -18,6 +26,11 @@ NVSPartition::NVSPartition(const esp_partition_t* partition) } } +NVSPartition::~NVSPartition() +{ + // No need to de-initialize mESPPartition here, if you used esp_partition_find_first. +} + const char *NVSPartition::get_partition_name() { return mESPPartition->label; @@ -30,10 +43,6 @@ esp_err_t NVSPartition::read_raw(size_t src_offset, void* dst, size_t size) esp_err_t NVSPartition::read(size_t src_offset, void* dst, size_t size) { - if (size % ESP_ENCRYPT_BLOCK_SIZE != 0) { - return ESP_ERR_INVALID_ARG; - } - return esp_partition_read(mESPPartition, src_offset, dst, size); } @@ -44,10 +53,6 @@ esp_err_t NVSPartition::write_raw(size_t dst_offset, const void* src, size_t siz esp_err_t NVSPartition::write(size_t dst_offset, const void* src, size_t size) { - if (size % ESP_ENCRYPT_BLOCK_SIZE != 0) { - return ESP_ERR_INVALID_ARG; - } - return esp_partition_write(mESPPartition, dst_offset, src, size); } @@ -71,4 +76,141 @@ bool NVSPartition::get_readonly() return mESPPartition->readonly; } +#else // CONFIG_NVS_BDL_STACK +// BDL implementation of NVSPartition +NVSPartition::NVSPartition(const char* label, const esp_blockdev_handle_t bdl, const bool managed_bdl) + : mBDL(bdl), mManagedBDLbyInstance(managed_bdl) +{ + if (bdl == nullptr || label == nullptr) { + std::abort(); + } + strlcpy(mLabel, label, NVS_PART_NAME_MAX_SIZE); + mLabel[NVS_PART_NAME_MAX_SIZE] = '\0'; +} + +NVSPartition::~NVSPartition() +{ + if(mManagedBDLbyInstance) { + // If the BDL was managed by this instance, we need to de-initialize it. + if (mBDL != nullptr) { + mBDL->ops->release(mBDL); + } + } +} + +const char* NVSPartition::get_partition_name() +{ + return mLabel; +} + +esp_err_t NVSPartition::read_raw(size_t src_offset, void* dst, size_t size) +{ + return mBDL->ops->read(mBDL, (uint8_t*) dst, size, src_offset, size); +} + +esp_err_t NVSPartition::read(size_t src_offset, void* dst, size_t size) +{ + return mBDL->ops->read(mBDL, (uint8_t*) dst, size, src_offset, size); +} + +esp_err_t NVSPartition::write_raw(size_t dst_offset, const void* src, size_t size) +{ + return mBDL->ops->write(mBDL, (const uint8_t*) src, dst_offset, size); +} + +esp_err_t NVSPartition::write(size_t dst_offset, const void* src, size_t size) +{ + return mBDL->ops->write(mBDL, (const uint8_t*) src, dst_offset, size); +} + +esp_err_t NVSPartition::erase_range(size_t dst_offset, size_t size) +{ + return mBDL->ops->erase(mBDL, dst_offset, size); +} + +uint32_t NVSPartition::get_address() +{ + // BDL cannot be used directly to get the address, as it is not a partition in the traditional sense. + ESP_LOGE(TAG, "get_address() is not supported"); + std::abort(); + return 0; +} + +uint32_t NVSPartition::get_size() +{ + return mBDL->geometry.disk_size; +} + +bool NVSPartition::get_readonly() +{ + return mBDL->device_flags.read_only; +} + +bool NVSPartition::is_bdl_nvs_compliant(const char* label, const esp_blockdev_handle_t bdl) +{ + if (bdl == nullptr) { + ESP_LOGV(TAG, "Block device layer handle is null for label %s", label); + return false; + } + bool is_compliant = true; + + if(label == nullptr) { + ESP_LOGV(TAG, "Label is null, cannot check compliance"); + return false; + } + + if(strlen(label) >= NVS_PART_NAME_MAX_SIZE) { + ESP_LOGV(TAG, "Label %s is too long, it must be less than %d characters", label, NVS_PART_NAME_MAX_SIZE); + is_compliant = false; + } + + if(bdl->geometry.read_size != 1){ + ESP_LOGV(TAG, "Block device read size is not 1, it is %zu", bdl->geometry.read_size); + is_compliant = false; + } + + if(bdl->geometry.write_size != 1) { + ESP_LOGV(TAG, "Block device write size is not 1, it is %zu", bdl->geometry.write_size); + is_compliant = false; + } + + if(bdl->geometry.erase_size % NVS_CONST_PAGE_SIZE != 0) { + ESP_LOGV(TAG, "Block device erase size is not a multiple of %d, it is %zu", NVS_CONST_PAGE_SIZE, bdl->geometry.erase_size); + is_compliant = false; + } + + if(bdl->geometry.disk_size % NVS_CONST_PAGE_SIZE != 0) { + ESP_LOGV(TAG, "Block device size is not a multiple of %d, it is %zu", NVS_CONST_PAGE_SIZE, bdl->geometry.disk_size); + is_compliant = false; + } + + if(bdl->ops->read == nullptr) { + ESP_LOGV(TAG, "Block device read operation is not present"); + is_compliant = false; + } + + if(bdl->ops->write == nullptr) { + ESP_LOGV(TAG, "Block device write operation is not present"); + is_compliant = false; + } + + if(bdl->ops->erase == nullptr) { + ESP_LOGV(TAG, "Block device erase operation is not present"); + is_compliant = false; + } + + if(bdl->device_flags.default_val_after_erase != 1) { + ESP_LOGV(TAG, "Block device default value after erase has to be 1, it is %d", bdl->device_flags.default_val_after_erase); + is_compliant = false; + } + + if(bdl->device_flags.encrypted) { + ESP_LOGV(TAG, "Block device is encrypted, NVS does not support encrypted BDLs"); + is_compliant = false; + } + + return is_compliant; +} + +#endif // CONFIG_NVS_BDL_STACK } // nvs diff --git a/components/nvs_flash/src/nvs_partition.hpp b/components/nvs_flash/src/nvs_partition.hpp index 77d7c51c9a..b6889f1a0a 100644 --- a/components/nvs_flash/src/nvs_partition.hpp +++ b/components/nvs_flash/src/nvs_partition.hpp @@ -5,106 +5,188 @@ */ #pragma once - -#include "esp_partition.h" -#include "intrusive_list.h" +#include "sdkconfig.h" // For CONFIG_NVS_BDL_STACK #include "partition.hpp" -#include "nvs_memory_management.hpp" -#define ESP_ENCRYPT_BLOCK_SIZE 16 - -#define PART_NAME_MAX_SIZE 16 /*!< maximum length of partition name (excluding null terminator) */ +#ifdef CONFIG_NVS_BDL_STACK + #include "esp_blockdev.h" // For esp_blockdev_handle_t + #include "nvs.h" // For NVS_PART_NAME_MAX_SIZE +#else + #include "esp_partition.h" +#endif // CONFIG_NVS_BDL_STACK namespace nvs { /** - * Implementation of Partition for NVS. - * - * It is implemented as an intrusive_list_node to easily store instances of it. NVSStorage and NVSPage take pointer - * references of this class to abstract their partition operations. + * Implementation of non-encrypted esp_partition or bdl storage wrapper for NVS. */ -class NVSPartition : public Partition, public intrusive_list_node, public ExceptionlessAllocatable { +class NVSPartition : public Partition { public: +#ifdef CONFIG_NVS_BDL_STACK /** - * Copy partition_name to mPartitionName and initialize mESPPartition. + * Constructor copies storage name to mLabel and initializes BDL reference. + * See function is_bdl_nvs_compliant for the requirements on the BDL. * - * @param partition_name the name of the partition as in the partition table, must be non-NULL! - * @param partition an already initialized partition structure + * @param label the label of the partition corresponding to the referenced BDL handle. + * @param bdl an already initialized bdl handle + * @param managed_bdl if true, the BDL handle will be released in the destructor of this class. + */ + NVSPartition(const char* label, const esp_blockdev_handle_t bdl, const bool managed_bdl = true); + + /** + * Function allowing to check if the block device is compliant with NVS requirements. + * This static function shall be called prior to creating an instance of NVSPartition. + * Following checks are performed: + * 1. The block device handle must not be null + * 2. The block device label must not be null + * 3. The block device label must fit to the statically allocated buffer (NVS_PART_NAME_MAX_SIZE) + * 4. The block device read block size must be 1 + * 5. The block device write block size must be 1 + * 6. The block device erase block size must be a multiple of NVS_CONST_PAGE_SIZE (NVS_CONST_PAGE_SIZE is 4kB) + * 7. The size of the block device must be a multiple of the NVS_CONST_PAGE_SIZE (NVS_CONST_PAGE_SIZE is 4kB) + * 8. BDL operations read / write / erase must be supported + * 9. The block device write operation type must be at least NOR flash compliant. Regular write, or write setting to zero are supported. + * 10. The block device must not be encrypted + * + * @param label the label of the storage corresponding to the referenced BDL handle. + * @param bdl an already initialized bdl handle + * + * @return true if the block device is NVS compliant and can be used for NVS operations, false otherwise. + */ + static bool is_bdl_nvs_compliant(const char* label, const esp_blockdev_handle_t bdl); + +#else + /** + * Constructor initializes the mPartition. + * + * @param partition an already initialized esp_partition structure */ NVSPartition(const esp_partition_t* partition); +#endif /** - * No need to de-initialize mESPPartition here, if you used esp_partition_find_first. - * Otherwise, the user is responsible for de-initializing it. + * Destructor cleans up the wrapper. + * If BDL is used and constructor was called with managed_bdl = true, + * the BDL handle will be released. */ - virtual ~NVSPartition() { } + virtual ~NVSPartition(); + /** + * Return the name (label) of the storage. + * In esp_partition implementation it returns the partition label. + * In BDL implementation it returns the BDL label handed over to the constructor. + * + * @return + * - the label of the storage + */ const char *get_partition_name() override; /** - * Look into \c esp_partition_read_raw for more details. + * Reads data from the storage. + * Raw read is used for reading data aligned down to the size of 1 byte + * + * @param src_offset the offset in the storage to read from + * @param dst an already allocated buffer to read data into + * @param size the size of the data to read in bytes * * @return * - ESP_OK on success - * - other error codes from the esp_partition API + * - other error codes from the esp_partition or BDL API */ esp_err_t read_raw(size_t src_offset, void* dst, size_t size) override; /** - * Look into \c esp_partition_read for more details. + * Reads data from the storage + * This function is intended for reading data aligned to the size of NVS entry. + * This, not encrypted, implementation does not require the size to be aligned to the NVS entry size though. + * + * @param src_offset the offset in the storage to read from + * @param dst an already allocated buffer to read data into + * @param size the size of the data to read in bytes * * @return * - ESP_OK on success - * - ESP_ERR_INVALID_ARG if size isn't a multiple of ESP_ENCRYPT_BLOCK_SIZE - * - other error codes from the esp_partition API + * - other error codes from the esp_partition or BDL API */ esp_err_t read(size_t src_offset, void* dst, size_t size) override; /** - * Look into \c esp_partition_write_raw for more details. + * Writes data from the src buffer to the storage. + * Raw write is used for writing data aligned down to the size of 1 byte + * Even repeated writes to the same address can be called. + * + * @param dst_offset the offset in the storage to write to + * @param src pointer to the buffer to write data from + * @param size the size of the data to write in bytes * * @return * - ESP_OK on success - * - error codes from the esp_partition API + * - other error codes from the esp_partition or BDL API */ esp_err_t write_raw(size_t dst_offset, const void* src, size_t size) override; /** - * Look into \c esp_partition_write for more details. + * Writes data from the src buffer to the storage. + * This function is intended for writing data aligned to the size of NVS entry. + * This, not encrypted, implementation does not require the size to be aligned to the NVS entry size though. + * Repeated writes to the same address are not expected to be called. + * + * @param dst_offset the offset in the storage to write to + * @param src pointer to the buffer to write data from + * @param size the size of the data to write in bytes * * @return * - ESP_OK on success - * - ESP_ERR_INVALID_ARG if size isn't a multiple of ESP_ENCRYPT_BLOCK_SIZE - * - other error codes from the esp_partition API + * - other error codes from the esp_partition or BDL API */ esp_err_t write(size_t dst_offset, const void* src, size_t size) override; /** - * Look into \c esp_partition_erase_range for more details. + * Erases the address range in the storage. + * Erased data is set to 0xFF. + * + * @param dst_offset the offset in the storage to erase + * @param size the size of the data to erase in bytes * * @return * - ESP_OK on success - * - error codes from the esp_partition API + * - other error codes from the esp_partition or BDL API */ esp_err_t erase_range(size_t dst_offset, size_t size) override; /** - * @return the base address of the partition. + * Returns the RAM address of the beginning of the memory mapped storage. + * Not available if the block device layer is enabled. + * + * @return + * - ESP_OK on success + * - other error codes from the esp_partition or BDL API */ uint32_t get_address() override; /** - * @return the size of the partition in bytes. + * Returns total size of the storage in bytes. + * + * @return size of the storage in bytes. */ uint32_t get_size() override; /** - * @return true if the partition is read-only. + * Returns the read-only status of the storage. + * + * @return true if the storage is read-only. */ bool get_readonly() override; protected: - const esp_partition_t* mESPPartition; + +#ifdef CONFIG_NVS_BDL_STACK + esp_blockdev_handle_t mBDL; // Block Device Layer handle for the storage + char mLabel[NVS_PART_NAME_MAX_SIZE + 1]; // Label of the storage, used for matching in NVS open operations + bool mManagedBDLbyInstance; // True if the BDL is managed by this instance, false if it is managed externally +#else + const esp_partition_t* mESPPartition; // Pointer to the ESP partition structure for the storage +#endif // CONFIG_NVS_BDL_STACK }; } // nvs diff --git a/components/nvs_flash/src/nvs_partition_lookup.cpp b/components/nvs_flash/src/nvs_partition_lookup.cpp index 8f2ac6816a..18c6cc3dc9 100644 --- a/components/nvs_flash/src/nvs_partition_lookup.cpp +++ b/components/nvs_flash/src/nvs_partition_lookup.cpp @@ -11,18 +11,17 @@ #endif // ! LINUX_TARGET namespace nvs { - namespace partition_lookup { -esp_err_t lookup_nvs_partition(const char* label, NVSPartition **p) +esp_err_t lookup_nvs_partition(const char* label, Partition **p) { +#ifndef CONFIG_NVS_BDL_STACK const esp_partition_t* esp_partition = esp_partition_find_first( - ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, label); + ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, label); if (esp_partition == nullptr) { return ESP_ERR_NOT_FOUND; } - if (esp_partition->encrypted) { return ESP_ERR_NVS_WRONG_ENCRYPTION; } @@ -33,15 +32,36 @@ esp_err_t lookup_nvs_partition(const char* label, NVSPartition **p) } *p = partition; - return ESP_OK; +#else + esp_blockdev_handle_t bdl = NULL; + esp_err_t err = esp_partition_get_blockdev( + ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, label, &bdl); + + if (err != ESP_OK) { + return ESP_ERR_NOT_FOUND; + } + if (!NVSPartition::is_bdl_nvs_compliant(label, bdl)) { + if(bdl->ops->release) bdl->ops->release(bdl); + return ESP_ERR_NVS_PART_NOT_FOUND; + } + + NVSPartition *partition = new (std::nothrow) NVSPartition(label, bdl); + if (partition == nullptr) { + return ESP_ERR_NO_MEM; + } + + *p = partition; + return ESP_OK; +#endif } #ifndef LINUX_TARGET -esp_err_t lookup_nvs_encrypted_partition(const char* label, nvs_sec_cfg_t* cfg, NVSPartition **p) +esp_err_t lookup_nvs_encrypted_partition(const char* label, nvs_sec_cfg_t* cfg, Partition **p) { +#ifndef CONFIG_NVS_BDL_STACK const esp_partition_t* esp_partition = esp_partition_find_first( - ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, label); + ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, label); if (esp_partition == nullptr) { return ESP_ERR_NOT_FOUND; @@ -51,23 +71,47 @@ esp_err_t lookup_nvs_encrypted_partition(const char* label, nvs_sec_cfg_t* cfg, return ESP_ERR_NVS_WRONG_ENCRYPTION; } - NVSEncryptedPartition *enc_p = new (std::nothrow) NVSEncryptedPartition(esp_partition); - if (enc_p == nullptr) { + NVSEncryptedPartition *partition = new (std::nothrow) NVSEncryptedPartition(esp_partition); + if (partition == nullptr) { return ESP_ERR_NO_MEM; } - esp_err_t result = enc_p->init(cfg); + esp_err_t result = partition->init(cfg); if (result != ESP_OK) { - delete enc_p; + delete partition; return result; } - - *p = enc_p; - + *p = partition; return ESP_OK; +#else + esp_blockdev_handle_t bdl = NULL; + esp_err_t err = esp_partition_get_blockdev( + ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, label, &bdl); + + if (err != ESP_OK) { + return ESP_ERR_NOT_FOUND; + } + + if (!NVSPartition::is_bdl_nvs_compliant(label, bdl)) { + if(bdl->ops->release) bdl->ops->release(bdl); + return ESP_ERR_NVS_PART_NOT_FOUND; + } + + NVSEncryptedPartition *partition = new (std::nothrow) NVSEncryptedPartition(label, bdl); + if (partition == nullptr) { + return ESP_ERR_NO_MEM; + } + + esp_err_t result = partition->init(cfg); + if (result != ESP_OK) { + delete partition; + return result; + } + *p = partition; + return ESP_OK; +#endif } -#endif // ! LINUX_TARGET +#endif // !LINUX_TARGET -} // partition_lookup - -} // nvs +} // namespace partition_lookup +} // namespace nvs diff --git a/components/nvs_flash/src/nvs_partition_lookup.hpp b/components/nvs_flash/src/nvs_partition_lookup.hpp index 215288ace4..47333867e2 100644 --- a/components/nvs_flash/src/nvs_partition_lookup.hpp +++ b/components/nvs_flash/src/nvs_partition_lookup.hpp @@ -13,9 +13,9 @@ namespace nvs { namespace partition_lookup { -esp_err_t lookup_nvs_partition(const char* label, NVSPartition **p); +esp_err_t lookup_nvs_partition(const char* label, Partition **p); -esp_err_t lookup_nvs_encrypted_partition(const char* label, nvs_sec_cfg_t* cfg, NVSPartition **p); +esp_err_t lookup_nvs_encrypted_partition(const char* label, nvs_sec_cfg_t* cfg, Partition **p); } // partition_lookup diff --git a/components/nvs_flash/src/nvs_partition_manager.cpp b/components/nvs_flash/src/nvs_partition_manager.cpp index 01851be813..c9a14356c7 100644 --- a/components/nvs_flash/src/nvs_partition_manager.cpp +++ b/components/nvs_flash/src/nvs_partition_manager.cpp @@ -3,7 +3,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ -#include "esp_partition.h" + #include "nvs_partition_manager.hpp" #include "nvs_partition_lookup.hpp" #include "nvs_internal.h" @@ -27,15 +27,12 @@ NVSPartitionManager* NVSPartitionManager::get_instance() return instance; } -#ifdef ESP_PLATFORM esp_err_t NVSPartitionManager::init_partition(const char *partition_label) { if (strlen(partition_label) > NVS_PART_NAME_MAX_SIZE) { return ESP_ERR_INVALID_ARG; } - uint32_t size; - const uint32_t sec_size = esp_partition_get_main_flash_sector_size(); Storage* mStorage; mStorage = lookup_storage_from_name(partition_label); @@ -43,17 +40,20 @@ esp_err_t NVSPartitionManager::init_partition(const char *partition_label) return ESP_OK; } - NVS_ASSERT_OR_RETURN(sec_size != 0, ESP_FAIL); - - NVSPartition *p = nullptr; + Partition *p = nullptr; esp_err_t result = partition_lookup::lookup_nvs_partition(partition_label, &p); + if (result != ESP_OK) { + return result; + } + + uint32_t size = p->get_size(); + uint32_t sec_size = NVS_CONST_PAGE_SIZE; + if (result != ESP_OK) { goto error; } - size = p->get_size(); - result = init_custom(p, 0, size / sec_size); if (result != ESP_OK) { goto error; @@ -67,7 +67,6 @@ error: delete p; return result; } -#endif // ESP_PLATFORM esp_err_t NVSPartitionManager::init_custom(Partition *partition, uint32_t baseSector, uint32_t sectorCount) { @@ -117,7 +116,7 @@ esp_err_t NVSPartitionManager::secure_init_partition(const char *part_name, nvs_ return ESP_OK; } - NVSPartition *p; + Partition *p; esp_err_t result; if (cfg != nullptr) { result = partition_lookup::lookup_nvs_encrypted_partition(part_name, cfg, &p); @@ -130,7 +129,7 @@ esp_err_t NVSPartitionManager::secure_init_partition(const char *part_name, nvs_ } uint32_t size = p->get_size(); - const uint32_t sec_size = esp_partition_get_main_flash_sector_size(); + uint32_t sec_size = NVS_CONST_PAGE_SIZE; result = init_custom(p, 0, size / sec_size); if (result != ESP_OK) { @@ -165,7 +164,7 @@ esp_err_t NVSPartitionManager::deinit_partition(const char *partition_label) for (auto it = nvs_partition_list.begin(); it != nvs_partition_list.end(); ++it) { if (strcmp(it->get_partition_name(), partition_label) == 0) { - NVSPartition *p = it; + Partition *p = it; nvs_partition_list.erase(it); delete p; break; diff --git a/components/nvs_flash/src/nvs_partition_manager.hpp b/components/nvs_flash/src/nvs_partition_manager.hpp index a4ce0c0bd1..81fc52ad0b 100644 --- a/components/nvs_flash/src/nvs_partition_manager.hpp +++ b/components/nvs_flash/src/nvs_partition_manager.hpp @@ -6,9 +6,7 @@ #pragma once #include "nvs_handle_simple.hpp" -#include "nvs_storage.hpp" -#include "nvs_partition.hpp" -#include "nvs_memory_management.hpp" +#include "partition.hpp" #include "nvs_flash.h" namespace nvs { @@ -44,7 +42,7 @@ protected: intrusive_list nvs_storage_list; - intrusive_list nvs_partition_list; + intrusive_list nvs_partition_list; }; } // nvs diff --git a/components/nvs_flash/src/partition.hpp b/components/nvs_flash/src/partition.hpp index 975a3579c1..e94c6835df 100644 --- a/components/nvs_flash/src/partition.hpp +++ b/components/nvs_flash/src/partition.hpp @@ -6,45 +6,127 @@ #pragma once #include "esp_err.h" +#include "intrusive_list.h" +#include "nvs_memory_management.hpp" namespace nvs { /** - * @brief Abstract interface for partition related operations, currently in NVS. + * @brief Abstract interface for permanent storage (partition) related operations, currently in NVS. * - * It resembles the main operations according to esp_partition.h. + * It resembles the main operations required by NVS to operate on permanent storage. + * It is implemented as an intrusive_list_node to easily store instances of it. + * It also allows to create instances of this class without throwing exceptions using custom heap allocator */ -class Partition { +class Partition : public intrusive_list_node, public ExceptionlessAllocatable { public: virtual ~Partition() { } /** - * Return the partition name as in the partition table. + * Return the name (label) of the storage. + * + * @return + * - the name of the storage, e.g. "nvs". */ virtual const char *get_partition_name() = 0; + /** + * Reads data from the storage. + * Raw read is used for reading data aligned down to the size of 1 byte + * No decryption is applied. + * + * @param src_offset the offset in the storage to read from + * @param dst an already allocated buffer to read data into + * @param size the size of the data to read in bytes + * + * @return + * - ESP_OK on success + * - other error codes from the implementation of the storage + */ virtual esp_err_t read_raw(size_t src_offset, void* dst, size_t size) = 0; + /** + * Reads data from the storage + * This function is intended for reading data aligned to the size of NVS entry. + * Implementations of this function may leverage the minimal size to read efficiently. + * For implementation with encryption, this function will decrypt the data. + * + * @param src_offset the offset in the storage to read from + * @param dst an already allocated buffer to read data into + * @param size the size of the data to read in bytes + * + * @return + * - ESP_OK on success + * - other error codes from the implementation of the storage + */ virtual esp_err_t read(size_t src_offset, void* dst, size_t size) = 0; + /** + * Writes data from the src buffer to the storage. + * Raw write is used for writing data aligned down to the size of 1 byte + * Repeated writes to the same address can be called thus no encryption is applied. + * + * @param dst_offset the offset in the storage to write to + * @param src pointer to the buffer to write data from + * @param size the size of the data to write in bytes + * + * @return + * - ESP_OK on success + * - other error codes from the implementation of the storage + */ virtual esp_err_t write_raw(size_t dst_offset, const void* src, size_t size) = 0; + /** + * Writes data from the src buffer to the storage. + * This function is intended for writing data aligned to the size of NVS entry. + * This, not encrypted, implementation does not require the size to be aligned to the NVS entry size though. + * Repeated writes to the same address are not expected to be called. + * For implementation with encryption, this function will encrypt the data. + * + * @param dst_offset the offset in the storage to write to + * @param src pointer to the buffer to write data from + * @param size the size of the data to write in bytes + * + * @return + * - ESP_OK on success + * - other error codes from the implementation of the storage + */ virtual esp_err_t write(size_t dst_offset, const void* src, size_t size) = 0; + /** + * Erases the address range in the storage. + * Erased data is set to 0xFF. + * + * @param dst_offset the offset in the storage to erase + * @param size the size of the data to erase in bytes + * + * @return + * - ESP_OK on success + * - other error codes from the implementation of the storage + */ virtual esp_err_t erase_range(size_t dst_offset, size_t size) = 0; /** - * Return the address of the beginning of the partition. + * Returns the RAM address of the beginning of the memory mapped storage. + * Not available if the block device layer is enabled. + * + * @return + * - ESP_OK on success + * - other error codes from the implementation of the storage */ virtual uint32_t get_address() = 0; /** - * Return the partition size in bytes. + * Returns total size of the storage in bytes. + * + * @return size of the storage in bytes. */ virtual uint32_t get_size() = 0; /** - * Return true if the partition is read-only. + * Returns the read-only status of the storage. + * + * @return true if the storage is read-only. */ virtual bool get_readonly() = 0; }; diff --git a/components/nvs_flash/test_apps/main/test_nvs.c b/components/nvs_flash/test_apps/main/test_nvs.c index e16691057d..04e4efc061 100644 --- a/components/nvs_flash/test_apps/main/test_nvs.c +++ b/components/nvs_flash/test_apps/main/test_nvs.c @@ -136,7 +136,8 @@ TEST_CASE("flash erase deinitializes initialized partition", "[nvs]") } #ifndef CONFIG_NVS_ENCRYPTION -// NOTE: `nvs_flash_init_partition_ptr` does not support NVS encryption +#ifndef CONFIG_NVS_BDL_STACK +// NOTE: `nvs_flash_init_partition_ptr` does not support NVS encryption nor BDL stack TEST_CASE("nvs_flash_init_partition_ptr() works correctly", "[nvs]") { // First, open and write to partition using normal initialization @@ -165,6 +166,7 @@ TEST_CASE("nvs_flash_init_partition_ptr() works correctly", "[nvs]") nvs_flash_deinit(); } +#endif // !CONFIG_NVS_BDL_STACK #ifdef CONFIG_SOC_HMAC_SUPPORTED TEST_CASE("test nvs encryption with HMAC-based scheme without toggling any config options", "[nvs_encr_hmac]")