Merge branch 'bugfix/nvs_init_check_empty_pages' into 'master'

Error handling in NVS initialization

Previously nvs_flash_init worked under an assumption that there should always be at least one free page available. This is true during normal operation, but in some cases (such as when changing application partition table from a non-OTA to an OTA one), NVS partition may get truncated, which will cause empty pages to be lost.

This MR adds error checks for this condition, and updates code which calls `nvs_flash_init` to check for the return code.
For most examples, a simple `ESP_ERROR_CHECK` is added around `nvs_flash_init`. For NVS examples and the OTA example, more robust error handling is added.

This change also removes nvs_flash_init calls from examples which don't use NVS.

See merge request !582
This commit is contained in:
Ivan Grokhotkov 2017-03-23 17:57:15 +08:00
commit ecee175962
28 changed files with 151 additions and 60 deletions

View File

@ -118,7 +118,7 @@ const esp_phy_init_data_t* esp_phy_get_init_data()
}
esp_err_t err = esp_partition_read(partition, 0, init_data_store, init_data_store_length);
if (err != ESP_OK) {
ESP_LOGE(TAG, "failed to read PHY data partition (%d)", err);
ESP_LOGE(TAG, "failed to read PHY data partition (0x%x)", err);
return NULL;
}
if (memcmp(init_data_store, PHY_INIT_MAGIC, sizeof(phy_init_magic_pre)) != 0 ||
@ -167,10 +167,15 @@ static esp_err_t store_cal_data_to_nvs_handle(nvs_handle handle,
esp_err_t esp_phy_load_cal_data_from_nvs(esp_phy_calibration_data_t* out_cal_data)
{
nvs_handle handle;
esp_err_t err = nvs_open(PHY_NAMESPACE, NVS_READONLY, &handle);
esp_err_t err = nvs_flash_init();
if (err != ESP_OK) {
ESP_LOGD(TAG, "%s: failed to open NVS namespace (%d)", __func__, err);
ESP_LOGW(TAG, "%s: failed to initialize NVS (0x%x)", __func__, err);
return err;
}
nvs_handle handle;
err = nvs_open(PHY_NAMESPACE, NVS_READONLY, &handle);
if (err != ESP_OK) {
ESP_LOGD(TAG, "%s: failed to open NVS namespace (0x%x)", __func__, err);
return err;
}
else {
@ -185,7 +190,7 @@ esp_err_t esp_phy_store_cal_data_to_nvs(const esp_phy_calibration_data_t* cal_da
nvs_handle handle;
esp_err_t err = nvs_open(PHY_NAMESPACE, NVS_READWRITE, &handle);
if (err != ESP_OK) {
ESP_LOGD(TAG, "%s: failed to open NVS namespace (%d)", __func__, err);
ESP_LOGD(TAG, "%s: failed to open NVS namespace (0x%x)", __func__, err);
return err;
}
else {
@ -202,7 +207,7 @@ static esp_err_t load_cal_data_from_nvs_handle(nvs_handle handle,
uint32_t cal_data_version;
err = nvs_get_u32(handle, PHY_CAL_VERSION_KEY, &cal_data_version);
if (err != ESP_OK) {
ESP_LOGD(TAG, "%s: failed to get cal_version (%d)", __func__, err);
ESP_LOGD(TAG, "%s: failed to get cal_version (0x%x)", __func__, err);
return err;
}
uint32_t cal_format_version = phy_get_rf_cal_version() & (~BIT(16));
@ -216,7 +221,7 @@ static esp_err_t load_cal_data_from_nvs_handle(nvs_handle handle,
size_t length = sizeof(cal_data_mac);
err = nvs_get_blob(handle, PHY_CAL_MAC_KEY, cal_data_mac, &length);
if (err != ESP_OK) {
ESP_LOGD(TAG, "%s: failed to get cal_mac (%d)", __func__, err);
ESP_LOGD(TAG, "%s: failed to get cal_mac (0x%x)", __func__, err);
return err;
}
if (length != sizeof(cal_data_mac)) {
@ -234,7 +239,7 @@ static esp_err_t load_cal_data_from_nvs_handle(nvs_handle handle,
length = sizeof(*out_cal_data);
err = nvs_get_blob(handle, PHY_CAL_DATA_KEY, out_cal_data, &length);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: failed to get cal_data(%d)", __func__, err);
ESP_LOGE(TAG, "%s: failed to get cal_data(0x%x)", __func__, err);
return err;
}
if (length != sizeof(*out_cal_data)) {
@ -267,7 +272,6 @@ static esp_err_t store_cal_data_to_nvs_handle(nvs_handle handle,
void esp_phy_load_cal_and_init(void)
{
#ifdef CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE
nvs_flash_init();
esp_phy_calibration_mode_t calibration_mode = PHY_RF_CAL_PARTIAL;
if (rtc_get_reset_reason(0) == DEEPSLEEP_RESET) {
calibration_mode = PHY_RF_CAL_NONE;
@ -285,7 +289,7 @@ void esp_phy_load_cal_and_init(void)
}
esp_err_t err = esp_phy_load_cal_data_from_nvs(cal_data);
if (err != ESP_OK) {
ESP_LOGW(TAG, "failed to load RF calibration data, falling back to full calibration");
ESP_LOGW(TAG, "failed to load RF calibration data (0x%x), falling back to full calibration", err);
calibration_mode = PHY_RF_CAL_FULL;
}

View File

@ -9,10 +9,12 @@ 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 range of flash sectors to be used by the library is provided to ``nvs_flash_init`` function.
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.
Future versions of this library may add other storage backends to keep data in another flash chip (SPI or I2C), RTC, FRAM, etc.
.. note:: if an NVS partition is truncated (for example, when the partition table layout is changed), its contents should be erased. ESP-IDF build system provides a ``make erase_flash`` target to erase all contents of the flash chip.
Keys and values
^^^^^^^^^^^^^^^

View File

@ -31,16 +31,17 @@ typedef uint32_t nvs_handle;
#define ESP_ERR_NVS_BASE 0x1100 /*!< Starting number of error codes */
#define ESP_ERR_NVS_NOT_INITIALIZED (ESP_ERR_NVS_BASE + 0x01) /*!< The storage driver is not initialized */
#define ESP_ERR_NVS_NOT_FOUND (ESP_ERR_NVS_BASE + 0x02) /*!< Id namespace doesnt exist yet and mode is NVS_READONLY */
#define ESP_ERR_NVS_TYPE_MISMATCH (ESP_ERR_NVS_BASE + 0x03) /*!< TBA */
#define ESP_ERR_NVS_TYPE_MISMATCH (ESP_ERR_NVS_BASE + 0x03) /*!< The type of set or get operation doesn't match the type of value stored in NVS */
#define ESP_ERR_NVS_READ_ONLY (ESP_ERR_NVS_BASE + 0x04) /*!< Storage handle was opened as read only */
#define ESP_ERR_NVS_NOT_ENOUGH_SPACE (ESP_ERR_NVS_BASE + 0x05) /*!< There is not enough space in the underlying storage to save the value */
#define ESP_ERR_NVS_INVALID_NAME (ESP_ERR_NVS_BASE + 0x06) /*!< Namespace name doesnt satisfy constraints */
#define ESP_ERR_NVS_INVALID_HANDLE (ESP_ERR_NVS_BASE + 0x07) /*!< Handle has been closed or is NULL */
#define ESP_ERR_NVS_REMOVE_FAILED (ESP_ERR_NVS_BASE + 0x08) /*!< The value wasnt updated because flash write operation has failed. The value was written however, and update will be finished after re-initialization of nvs, provided that flash operation doesnt fail again. */
#define ESP_ERR_NVS_KEY_TOO_LONG (ESP_ERR_NVS_BASE + 0x09) /*!< TBA */
#define ESP_ERR_NVS_PAGE_FULL (ESP_ERR_NVS_BASE + 0x0a) /*!< TBA */
#define ESP_ERR_NVS_INVALID_STATE (ESP_ERR_NVS_BASE + 0x0b) /*!< TBA */
#define ESP_ERR_NVS_INVALID_LENGTH (ESP_ERR_NVS_BASE + 0x0c) /*!< TBA */
#define ESP_ERR_NVS_KEY_TOO_LONG (ESP_ERR_NVS_BASE + 0x09) /*!< Key name is too long */
#define ESP_ERR_NVS_PAGE_FULL (ESP_ERR_NVS_BASE + 0x0a) /*!< Internal error; never returned by nvs_ API functions */
#define ESP_ERR_NVS_INVALID_STATE (ESP_ERR_NVS_BASE + 0x0b) /*!< NVS is in an inconsistent state due to a previous error. Call nvs_flash_init and nvs_open again, then retry. */
#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. */
/**
* @brief Mode of opening the non-volatile storage

View File

@ -21,7 +21,11 @@ extern "C" {
/**
* @brief Initialize NVS flash storage with layout given in the partition table.
*
* @return ESP_OK if storage was successfully initialized.
* @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)
* - one of the error codes from the underlying flash storage driver
*/
esp_err_t nvs_flash_init(void);

View File

@ -812,9 +812,36 @@ void Page::invalidateCache()
mFindInfo = CachedFindInfo();
}
const char* Page::pageStateToName(PageState ps)
{
switch (ps) {
case PageState::CORRUPT:
return "CORRUPT";
case PageState::ACTIVE:
return "ACTIVE";
case PageState::FREEING:
return "FREEING";
case PageState::FULL:
return "FULL";
case PageState::INVALID:
return "INVALID";
case PageState::UNINITIALIZED:
return "UNINITIALIZED";
default:
assert(0 && "invalid state value");
return "";
}
}
void Page::debugDump() const
{
printf("state=%x addr=%x seq=%d\nfirstUsed=%d nextFree=%d used=%d erased=%d\n", (int) mState, mBaseAddress, mSeqNumber, static_cast<int>(mFirstUsedEntry), static_cast<int>(mNextFreeEntry), mUsedEntryCount, mErasedEntryCount);
printf("state=%x (%s) addr=%x seq=%d\nfirstUsed=%d nextFree=%d used=%d erased=%d\n", (uint32_t) mState, pageStateToName(mState), mBaseAddress, mSeqNumber, static_cast<int>(mFirstUsedEntry), static_cast<int>(mNextFreeEntry), mUsedEntryCount, mErasedEntryCount);
size_t skip = 0;
for (size_t i = 0; i < ENTRY_COUNT; ++i) {
printf("%3d: ", static_cast<int>(i));

View File

@ -221,6 +221,8 @@ protected:
return mBaseAddress + ENTRY_DATA_OFFSET + static_cast<uint32_t>(entry) * ENTRY_SIZE;
}
static const char* pageStateToName(PageState ps);
protected:
uint32_t mBaseAddress = 0;

View File

@ -105,6 +105,11 @@ esp_err_t PageManager::load(uint32_t baseSector, uint32_t sectorCount)
}
}
// partition should have at least one free page
if (mFreePageList.size() == 0) {
return ESP_ERR_NVS_NO_FREE_PAGES;
}
return ESP_OK;
}

View File

@ -6,14 +6,26 @@
#include "unity.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "esp_spi_flash.h"
#include "esp_partition.h"
#include "esp_log.h"
#include <string.h>
static const char* TAG = "test_nvs";
TEST_CASE("various nvs tests", "[nvs]")
{
nvs_handle handle_1;
TEST_ESP_OK(nvs_flash_init());
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES) {
ESP_LOGW(TAG, "nvs_flash_init failed (0x%x), erasing partition and retrying", err);
const esp_partition_t* nvs_partition = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, NULL);
assert(nvs_partition && "partition table must have an NVS partition");
ESP_ERROR_CHECK( esp_partition_erase_range(nvs_partition, 0, nvs_partition->size) );
err = nvs_flash_init();
}
ESP_ERROR_CHECK( err );
TEST_ESP_ERR(nvs_open("test_namespace1", NVS_READONLY, &handle_1), ESP_ERR_NVS_NOT_FOUND);
TEST_ESP_ERR(nvs_set_i32(handle_1, "foo", 0x12345678), ESP_ERR_NVS_INVALID_HANDLE);

View File

@ -1108,6 +1108,23 @@ TEST_CASE("read/write failure (TW8406)", "[nvs]")
}
}
TEST_CASE("nvs_flash_init checks for an empty page", "[nvs]")
{
const size_t blob_size = 2048; // big enough so that only one can fit into a page
uint8_t blob[blob_size] = {0};
SpiFlashEmulator emu(5);
TEST_ESP_OK( nvs_flash_init_custom(0, 5) );
nvs_handle handle;
TEST_ESP_OK( nvs_open("test", NVS_READWRITE, &handle) );
TEST_ESP_OK( nvs_set_blob(handle, "1", blob, blob_size) );
TEST_ESP_OK( nvs_set_blob(handle, "2", blob, blob_size) );
TEST_ESP_OK( nvs_set_blob(handle, "3", blob, blob_size) );
TEST_ESP_OK( nvs_commit(handle) );
nvs_close(handle);
// first two pages are now full, third one is writable, last two are empty
// init should fail
TEST_ESP_ERR( nvs_flash_init_custom(0, 3), ESP_ERR_NVS_NO_FREE_PAGES );
}
TEST_CASE("dump all performance data", "[nvs]")
{

View File

@ -48,6 +48,7 @@ Macros
.. doxygendefine:: ESP_ERR_NVS_PAGE_FULL
.. doxygendefine:: ESP_ERR_NVS_INVALID_STATE
.. doxygendefine:: ESP_ERR_NVS_INVALID_LENGTH
.. doxygendefine:: ESP_ERR_NVS_NO_FREE_PAGES
Type Definitions
^^^^^^^^^^^^^^^^
@ -61,6 +62,7 @@ Enumerations
Functions
^^^^^^^^^
.. doxygenfunction:: nvs_flash_init
.. doxygenfunction:: nvs_open
.. doxygenfunction:: nvs_set_i8
.. doxygenfunction:: nvs_set_u8
@ -86,5 +88,5 @@ Functions
.. doxygenfunction:: nvs_erase_all
.. doxygenfunction:: nvs_commit
.. doxygenfunction:: nvs_close
.. doxygenfunction:: nvs_flash_init

View File

@ -312,7 +312,7 @@ void app_main()
{
esp_err_t ret;
nvs_flash_init();
ESP_ERROR_CHECK( nvs_flash_init() );
initialise_wifi();
esp_bt_controller_init();

View File

@ -9,8 +9,6 @@
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "driver/gpio.h"
#include "sdkconfig.h"
@ -42,6 +40,5 @@ void blink_task(void *pvParameter)
void app_main()
{
nvs_flash_init();
xTaskCreate(&blink_task, "blink_task", 512, NULL, 5, NULL);
}

View File

@ -10,7 +10,6 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "nvs_flash.h"
void hello_task(void *pvParameter)
{
@ -26,6 +25,5 @@ void hello_task(void *pvParameter)
void app_main()
{
nvs_flash_init();
xTaskCreate(&hello_task, "hello_task", 2048, NULL, 5, NULL);
}

View File

@ -11,8 +11,6 @@
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "driver/i2s.h"
#include <math.h>
@ -48,7 +46,6 @@ void app_main()
.data_in_num = -1 //Not used
};
nvs_flash_init();
i2s_driver_install(I2S_NUM, &i2s_config, 0, NULL);
i2s_set_pin(I2S_NUM, &pin_config);

View File

@ -9,8 +9,6 @@
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "esp_log.h"
#include "driver/touch_pad.h"
@ -95,9 +93,6 @@ static void touch_pad_rtc_intr(void * arg)
void app_main()
{
ESP_LOGI(TAG, "Starting");
nvs_flash_init();
// Initialize touch pad peripheral
ESP_LOGI(TAG, "Initializing touch pad");
touch_pad_init();

View File

@ -9,8 +9,6 @@
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "driver/touch_pad.h"
@ -34,8 +32,6 @@ void touch_pad_read_task(void *pvParameter)
void app_main()
{
nvs_flash_init();
// Initialize touch pad peripheral
touch_pad_init();

View File

@ -199,7 +199,7 @@ static void wifi_conn_init(void)
void app_main(void)
{
nvs_flash_init();
ESP_ERROR_CHECK( nvs_flash_init() );
wifi_conn_init();
xTaskCreate(coap_demo_thread, "coap", 2048, NULL, 5, NULL);
}

View File

@ -185,7 +185,7 @@ static void wifi_conn_init(void)
void app_main(void)
{
nvs_flash_init();
ESP_ERROR_CHECK( nvs_flash_init() );
wifi_conn_init();
xTaskCreate(coap_demo_thread, "coap", 2048, NULL, 5, NULL);

View File

@ -174,7 +174,7 @@ static void http_get_task(void *pvParameters)
void app_main()
{
nvs_flash_init();
ESP_ERROR_CHECK( nvs_flash_init() );
initialise_wifi();
xTaskCreate(&http_get_task, "http_get_task", 2048, NULL, 5, NULL);
}

View File

@ -325,7 +325,7 @@ static void https_get_task(void *pvParameters)
void app_main()
{
nvs_flash_init();
ESP_ERROR_CHECK( nvs_flash_init() );
initialise_wifi();
xTaskCreate(&https_get_task, "https_get_task", 8192, NULL, 5, NULL);
}

View File

@ -178,7 +178,7 @@ static void mdns_task(void *pvParameters)
void app_main()
{
nvs_flash_init();
ESP_ERROR_CHECK( nvs_flash_init() );
initialise_wifi();
xTaskCreate(&mdns_task, "mdns_task", 2048, NULL, 5, NULL);
}

View File

@ -220,6 +220,6 @@ static void wifi_conn_init(void)
void app_main(void)
{
nvs_flash_init();
ESP_ERROR_CHECK( nvs_flash_init() );
wifi_conn_init();
}

View File

@ -255,6 +255,6 @@ static void wifi_conn_init(void)
void app_main(void)
{
nvs_flash_init();
ESP_ERROR_CHECK( nvs_flash_init() );
wifi_conn_init();
}

View File

@ -93,7 +93,7 @@ void app_main()
static void obtain_time(void)
{
nvs_flash_init();
ESP_ERROR_CHECK( nvs_flash_init() );
initialise_wifi();
xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
false, true, portMAX_DELAY);

View File

@ -13,6 +13,7 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_partition.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "driver/gpio.h"
@ -146,9 +147,17 @@ esp_err_t print_what_saved(void)
void app_main()
{
nvs_flash_init();
esp_err_t err;
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES) {
// NVS partition was truncated and needs to be erased
const esp_partition_t* nvs_partition = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, NULL);
assert(nvs_partition && "partition table must have an NVS partition");
ESP_ERROR_CHECK( esp_partition_erase_range(nvs_partition, 0, nvs_partition->size) );
// Retry nvs_flash_init
err = nvs_flash_init();
}
ESP_ERROR_CHECK( err );
err = print_what_saved();
if (err != ESP_OK) printf("Error (%d) reading data from NVS!\n", err);

View File

@ -13,23 +13,32 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_partition.h"
#include "nvs_flash.h"
#include "nvs.h"
void app_main()
{
nvs_flash_init();
nvs_handle my_handle;
esp_err_t err;
printf("\n");
// Initialize NVS
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES) {
// NVS partition was truncated and needs to be erased
const esp_partition_t* nvs_partition = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, NULL);
assert(nvs_partition && "partition table must have an NVS partition");
ESP_ERROR_CHECK( esp_partition_erase_range(nvs_partition, 0, nvs_partition->size) );
// Retry nvs_flash_init
err = nvs_flash_init();
}
ESP_ERROR_CHECK( err );
// Open
printf("Opening Non-Volatile Storage (NVS) ... ");
printf("\n");
printf("Opening Non-Volatile Storage (NVS) handle... ");
nvs_handle my_handle;
err = nvs_open("storage", NVS_READWRITE, &my_handle);
if (err != ESP_OK) {
printf("Error (%d) opening NVS!\n", err);
printf("Error (%d) opening NVS handle!\n", err);
} else {
printf("Done\n");

View File

@ -21,6 +21,7 @@
#include "esp_ota_ops.h"
#include "esp_partition.h"
#include "nvs.h"
#include "nvs_flash.h"
#define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID
@ -279,7 +280,20 @@ void main_task(void *pvParameter)
void app_main()
{
nvs_flash_init();
// Initialize NVS.
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES) {
// OTA app partition table has a smaller NVS partition size than the non-OTA
// partition table. This size mismatch may cause NVS initialization to fail.
// If this happens, we erase NVS partition and initialize NVS again.
const esp_partition_t* nvs_partition = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, NULL);
assert(nvs_partition && "partition table must have an NVS partition");
ESP_ERROR_CHECK( esp_partition_erase_range(nvs_partition, 0, nvs_partition->size) );
err = nvs_flash_init();
}
ESP_ERROR_CHECK( err );
initialise_wifi();
xTaskCreate(&main_task, "main_task", 8192, NULL, 5, NULL);
}

View File

@ -148,7 +148,7 @@ static void wpa2_enterprise_task(void *pvParameters)
void app_main()
{
nvs_flash_init();
ESP_ERROR_CHECK( nvs_flash_init() );
initialise_wifi();
xTaskCreate(&wpa2_enterprise_task, "wpa2_enterprise_task", 4096, NULL, 5, NULL);
}