diff --git a/features/nvstore/README.md b/features/nvstore/README.md new file mode 100644 index 00000000000..bd31055ad31 --- /dev/null +++ b/features/nvstore/README.md @@ -0,0 +1,58 @@ +# NVStore + +NVStore is a lightweight module that stores data by keys in the internal flash for security purposes. + +## Description + +NVStore provides the ability to store a minimal set of system critical items in the internal flash. +For each item key, the NVStore module provides the ability to set the item data or get it. +Newly added values are added to the end of the existing data, superseding the previous value that was there for the same key. +The NVStore module ensures that power failures don't harm existing data during any operation. +The full interface can be found under `nvstore.h`. + +### Flash structure +NVStore uses two Flash areas, active and nonactive. Each area must consist of at least one erasable unit (sector). +Data is written to the active area until it becomes full. When it does, garbage collection is invoked. +This compacts items from the active area to the nonactive one and switches activity between areas. +Each item is kept in an entry containing a header and data, where the header holds the item key, size and CRC. + +### APIs +- init: Initialize NVStore (also lazily called by get, set, set_once and remove APIs). +- deinit: Deinitialize NVStore. +- get: Get the value of an item, given key. +- set: Set the value of an item, given key and value. +- set_once: Like set, but allows only a one time setting of this item (and disables deleting of this item). +- remove: Remove an item, given key. +- get_item_size: Get the item value size (in bytes). +- set_max_keys: Set maximal value of unique keys. Overriding the default of NVSTORE_MAX_KEYS. This affects RAM consumption, + as NVStore consumes 4 bytes per unique key. Reinitializes the module. + + +## Usage + +### Enabling NVStore and configuring it for your board +NVStore is enabled by default for all devices with the internal flash driver (have "FLASH" in the device_has attribute). +One can disable it by setting its "enabled" attribute to false. +Unless specifically configured by the user, NVStore selects the last two flash sectors as its areas, with the mininum size of 4KBs, +meaning that if the sectors are smaller, few continuous ones will be used for each area. +The user can override this by setting the addresses and sizes of both areas in` mbed_lib.json` on a per board basis. +In this case, all following four attributes need to be set: +- area_1_address +- area_1_size +- area_2_address +- area_2_size + +In addition, the `num_keys` value should be modified to change the default number of different keys. + +### Using NVStore +NVStore is a singleton class, meaning that the system can have only a single instance of it. +To instantiate NVStore, one needs to call its get_instance member function as following: +``` c++ + NVStore &nvstore = NVStore::get_instance(); +``` +After the NVStore instantiation, one can call the init API, but it is not necessary because all +NVStore APIs (get, set and so on) perform a "lazy initialization". + +### Testing NVStore +Run the NVStore functionality test with the `mbed` command as following: +```mbed test -n features-nvstore-tests-nvstore-functionality``` diff --git a/features/nvstore/TESTS/nvstore/functionality/main.cpp b/features/nvstore/TESTS/nvstore/functionality/main.cpp new file mode 100644 index 00000000000..32923c359bf --- /dev/null +++ b/features/nvstore/TESTS/nvstore/functionality/main.cpp @@ -0,0 +1,554 @@ +/* +* Copyright (c) 2018 ARM Limited. All rights reserved. +* SPDX-License-Identifier: Apache-2.0 +* Licensed under the Apache License, Version 2.0 (the License); you may +* not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an AS IS BASIS, WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "nvstore.h" +#ifdef MBED_CONF_RTOS_PRESENT +#include "Thread.h" +#endif +#include "mbed_wait_api.h" +#include "greentea-client/test_env.h" +#include "unity/unity.h" +#include "utest/utest.h" +#include +#include +#include +#include + +#if !NVSTORE_ENABLED +#error [NOT_SUPPORTED] NVSTORE needs to be enabled for this test +#endif + +using namespace utest::v1; + +static const uint16_t max_test_keys = 20; + +static const size_t basic_func_max_data_size = 128; + +static const int thr_test_num_buffs = 5; +static const int thr_test_num_secs = 5; +static const int thr_test_max_data_size = 32; +static const int thr_test_stack_size = 768; +static const int thr_test_num_threads = 3; + +typedef struct { + uint8_t *buffs[max_test_keys][thr_test_num_buffs]; + uint16_t sizes[max_test_keys][thr_test_num_buffs]; + int inds[max_test_keys]; + uint16_t max_keys; + uint16_t last_key; + int last_ind; +} thread_test_data_t; + +static thread_test_data_t *thr_test_data; + +static const int race_test_num_threads = 4; +static const int race_test_key = 1; +static const int race_test_data_size = 128; + +static void gen_random(uint8_t *s, int len) +{ + for (int i = 0; i < len; ++i) { + s[i] = rand() % 256; + } +} + +static void nvstore_basic_functionality_test() +{ + + uint16_t actual_len_bytes = 0; + NVStore &nvstore = NVStore::get_instance(); + + uint8_t *nvstore_testing_buf_set = new uint8_t[basic_func_max_data_size]; + uint8_t *nvstore_testing_buf_get = new uint8_t[basic_func_max_data_size]; + + int result; + + printf("NVStore areas:\n"); + for (uint8_t area = 0; area < NVSTORE_NUM_AREAS; area++) { + uint32_t area_address; + size_t area_size; + nvstore.get_area_params(area, area_address, area_size); + printf("Area %d: address 0x%08lx, size %d (0x%x)\n", area, area_address, area_size, area_size); + } + + gen_random(nvstore_testing_buf_set, basic_func_max_data_size); + + nvstore.set_max_keys(max_test_keys); + TEST_ASSERT_EQUAL(max_test_keys, nvstore.get_max_keys()); + + result = nvstore.reset(); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + + printf("Max keys %d (out of %d possible ones)\n", nvstore.get_max_keys(), nvstore.get_max_possible_keys()); + + result = nvstore.set(5, 18, nvstore_testing_buf_set); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + + result = nvstore.get(5, 22, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(18, actual_len_bytes); + TEST_ASSERT_EQUAL_UINT8_ARRAY(nvstore_testing_buf_set, nvstore_testing_buf_get, 15); + result = nvstore.remove(5); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + result = nvstore.get(5, 20, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_NOT_FOUND, result); + + result = nvstore.set(11, 0, NULL); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + result = nvstore.set(9, 20, NULL); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + result = nvstore.set(7, 0, nvstore_testing_buf_set); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + result = nvstore.set(10, 64, nvstore_testing_buf_set); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + result = nvstore.set(13, 3, &(nvstore_testing_buf_set[1])); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + result = nvstore.set(15, 15, &(nvstore_testing_buf_set[2])); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + result = nvstore.set(64, 15, &(nvstore_testing_buf_set[2])); + TEST_ASSERT_EQUAL(NVSTORE_BAD_VALUE, result); + result = nvstore.set(9, 20, &(nvstore_testing_buf_set[3])); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + result = nvstore.set_once(19, 12, &(nvstore_testing_buf_set[2])); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + result = nvstore.set(19, 10, &(nvstore_testing_buf_set[3])); + TEST_ASSERT_EQUAL(NVSTORE_ALREADY_EXISTS, result); + + // Make sure set items are also gotten OK after reset + result = nvstore.deinit(); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + result = nvstore.init(); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + + result = nvstore.get(14, 20, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_NOT_FOUND, result); + result = nvstore.get(7, 0, NULL, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(0, actual_len_bytes); + result = nvstore.get(7, 15, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(0, actual_len_bytes); + result = nvstore.get(7, 0, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(0, actual_len_bytes); + result = nvstore.get(9, 0, NULL, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_BUFF_TOO_SMALL, result); + result = nvstore.get(9, 150, NULL, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_BUFF_TOO_SMALL, result); + result = nvstore.get(10, 64, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(64, actual_len_bytes); + TEST_ASSERT_EQUAL_UINT8_ARRAY(nvstore_testing_buf_set, nvstore_testing_buf_get, 64); + + result = nvstore.get(10, 65, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(64, actual_len_bytes); + TEST_ASSERT_EQUAL_UINT8_ARRAY(nvstore_testing_buf_set, nvstore_testing_buf_get, 64); + + result = nvstore.get(64, 20, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_BAD_VALUE, result); + result = nvstore.get(9, 20, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(20, actual_len_bytes); + TEST_ASSERT_EQUAL_UINT8_ARRAY(&(nvstore_testing_buf_set[3]), nvstore_testing_buf_get, 20); + + result = nvstore.get(9, 21, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(20, actual_len_bytes); + TEST_ASSERT_EQUAL_UINT8_ARRAY(&(nvstore_testing_buf_set[3]), nvstore_testing_buf_get, 20); + + result = nvstore.get(9, 19, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_BUFF_TOO_SMALL, result); + result = nvstore.get(13, 3, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(3, actual_len_bytes); + TEST_ASSERT_EQUAL_UINT8_ARRAY(&(nvstore_testing_buf_set[1]), nvstore_testing_buf_get, 3); + result = nvstore.get_item_size(13, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(3, actual_len_bytes); + + result = nvstore.get(13, 4, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(3, actual_len_bytes); + TEST_ASSERT_EQUAL_UINT8_ARRAY(&(nvstore_testing_buf_set[1]), nvstore_testing_buf_get, 3); + + result = nvstore.get(13, 2, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_BUFF_TOO_SMALL, result); + result = nvstore.init(); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + // check all the expected keys + actual_len_bytes = 0; + result = nvstore.get(10, 64, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(64, actual_len_bytes); + TEST_ASSERT_EQUAL_UINT8_ARRAY(nvstore_testing_buf_set, nvstore_testing_buf_get, 64); + + actual_len_bytes = 0; + result = nvstore.get(11, 64, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(0, actual_len_bytes); + + actual_len_bytes = 0; + result = nvstore.get(13, 3, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(3, actual_len_bytes); + TEST_ASSERT_EQUAL_UINT8_ARRAY(&(nvstore_testing_buf_set[1]), nvstore_testing_buf_get, 3); + + actual_len_bytes = 0; + result = nvstore.get(9, 20, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(20, actual_len_bytes); + TEST_ASSERT_EQUAL_UINT8_ARRAY(&(nvstore_testing_buf_set[3]), nvstore_testing_buf_get, 20); + + actual_len_bytes = 0; + result = nvstore.get(7, 0, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(0, actual_len_bytes); + + actual_len_bytes = 0; + result = nvstore.get(15, 15, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(15, actual_len_bytes); + TEST_ASSERT_EQUAL_UINT8_ARRAY(&(nvstore_testing_buf_set[2]), nvstore_testing_buf_get, 15); + + actual_len_bytes = 0; + result = nvstore.get(19, 12, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(12, actual_len_bytes); + TEST_ASSERT_EQUAL_UINT8_ARRAY(&(nvstore_testing_buf_set[2]), nvstore_testing_buf_get, 12); + + // change the data for all keys + result = nvstore.set(10, 15, &(nvstore_testing_buf_set[4])); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + result = nvstore.set(11, 27, &(nvstore_testing_buf_set[5])); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + result = nvstore.set(13, 7, &(nvstore_testing_buf_set[6])); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + result = nvstore.set(9, 0, &(nvstore_testing_buf_set[7])); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + result = nvstore.set(7, 48, &(nvstore_testing_buf_set[8])); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + result = nvstore.set(14, 89, &(nvstore_testing_buf_set[9])); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + result = nvstore.set(15, 53, &(nvstore_testing_buf_set[10])); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + + actual_len_bytes = 0; + result = nvstore.get(10, 15, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(15, actual_len_bytes); + TEST_ASSERT_EQUAL_UINT8_ARRAY(&(nvstore_testing_buf_set[4]), nvstore_testing_buf_get, 15); + + actual_len_bytes = 0; + result = nvstore.get(11, 27, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(27, actual_len_bytes); + TEST_ASSERT_EQUAL_UINT8_ARRAY(&(nvstore_testing_buf_set[5]), nvstore_testing_buf_get, 27); + + actual_len_bytes = 0; + result = nvstore.get(13, 7, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(7, actual_len_bytes); + TEST_ASSERT_EQUAL_UINT8_ARRAY(&(nvstore_testing_buf_set[6]), nvstore_testing_buf_get, 7); + + actual_len_bytes = 0; + result = nvstore.get(9, 0, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(0, actual_len_bytes); + + actual_len_bytes = 0; + result = nvstore.get(7, 48, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(48, actual_len_bytes); + TEST_ASSERT_EQUAL_UINT8_ARRAY(&(nvstore_testing_buf_set[8]), nvstore_testing_buf_get, 48); + + actual_len_bytes = 0; + result = nvstore.get(14, 89, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(89, actual_len_bytes); + TEST_ASSERT_EQUAL_UINT8_ARRAY(&(nvstore_testing_buf_set[9]), nvstore_testing_buf_get, 89); + + actual_len_bytes = 0; + result = nvstore.get(15, 53, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(53, actual_len_bytes); + TEST_ASSERT_EQUAL_UINT8_ARRAY(&(nvstore_testing_buf_set[10]), nvstore_testing_buf_get, 53); + + result = nvstore.deinit(); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + + actual_len_bytes = 0; + result = nvstore.get(10, 64, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(15, actual_len_bytes); + TEST_ASSERT_EQUAL_UINT8_ARRAY(&(nvstore_testing_buf_set[4]), nvstore_testing_buf_get, 15); + + actual_len_bytes = 0; + result = nvstore.get(11, 27, nvstore_testing_buf_get, actual_len_bytes); // no care about the buf and len values + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(27, actual_len_bytes); + TEST_ASSERT_EQUAL_UINT8_ARRAY(&(nvstore_testing_buf_set[5]), nvstore_testing_buf_get, 27); + + actual_len_bytes = 0; + result = nvstore.get(13, 7, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(7, actual_len_bytes); + TEST_ASSERT_EQUAL_UINT8_ARRAY(&(nvstore_testing_buf_set[6]), nvstore_testing_buf_get, 7); + + actual_len_bytes = 0; + result = nvstore.get(9, 0, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(0, actual_len_bytes); + + actual_len_bytes = 0; + result = nvstore.get(7, 48, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(48, actual_len_bytes); + TEST_ASSERT_EQUAL_UINT8_ARRAY(&(nvstore_testing_buf_set[8]), nvstore_testing_buf_get, 48); + + actual_len_bytes = 0; + result = nvstore.get(14, 89, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(89, actual_len_bytes); + TEST_ASSERT_EQUAL_UINT8_ARRAY(&(nvstore_testing_buf_set[9]), nvstore_testing_buf_get, 89); + + actual_len_bytes = 0; + result = nvstore.get(15, 53, nvstore_testing_buf_get, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, result); + TEST_ASSERT_EQUAL(53, actual_len_bytes); + TEST_ASSERT_EQUAL_UINT8_ARRAY(&(nvstore_testing_buf_set[10]), nvstore_testing_buf_get, 53); + + delete[] nvstore_testing_buf_set; + delete[] nvstore_testing_buf_get; +} + + +static void thread_test_check_key(uint16_t key) +{ + uint8_t get_buff[thr_test_max_data_size]; + int ret; + uint16_t actual_len_bytes; + NVStore &nvstore = NVStore::get_instance(); + + ret = nvstore.get(key, basic_func_max_data_size, get_buff, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, ret); + TEST_ASSERT_NOT_EQUAL(0, actual_len_bytes); + + for (int i = 0; i < thr_test_num_buffs; i++) { + if (thr_test_data->sizes[key][i] != actual_len_bytes) { + continue; + } + + if (!memcmp(thr_test_data->buffs[key][i], get_buff, actual_len_bytes)) { + return; + } + } + + if (key == thr_test_data->last_key) { + if ((thr_test_data->sizes[key][thr_test_data->last_ind] == actual_len_bytes) && + (!memcmp(thr_test_data->buffs[key][thr_test_data->last_ind], get_buff, actual_len_bytes))) { + return; + } + } + + // Got here - always assert + TEST_ASSERT(0); + +} + +#ifdef MBED_CONF_RTOS_PRESENT +static void thread_test_worker() +{ + int ret; + int buf_num, is_set; + uint16_t key; + NVStore &nvstore = NVStore::get_instance(); + + for (;;) { + key = rand() % thr_test_data->max_keys; + is_set = rand() % 10; + + if (is_set) { + buf_num = rand() % thr_test_num_buffs; + thr_test_data->last_key = key; + thr_test_data->last_ind = buf_num; + ret = nvstore.set(key, thr_test_data->sizes[key][buf_num], thr_test_data->buffs[key][buf_num]); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, ret); + thr_test_data->inds[key] = buf_num; + } else { + thread_test_check_key(key); + } + + wait_ms(1); + } +} +#endif + +static void nvstore_multi_thread_test() +{ +#ifdef MBED_CONF_RTOS_PRESENT + int i; + int num_threads = thr_test_num_threads; + uint16_t size; + uint16_t key; + int ret; + + rtos::Thread **threads = new rtos::Thread*[num_threads]; + + NVStore &nvstore = NVStore::get_instance(); + + ret = nvstore.reset(); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, ret); + + thr_test_data = new thread_test_data_t; + thr_test_data->max_keys = max_test_keys / 2; + for (key = 0; key < thr_test_data->max_keys; key++) { + for (i = 0; i < thr_test_num_buffs; i++) { + size = 1 + rand() % thr_test_max_data_size; + thr_test_data->sizes[key][i] = size; + thr_test_data->buffs[key][i] = new uint8_t[size + 1]; + thr_test_data->inds[key] = 0; + gen_random(thr_test_data->buffs[key][i], size); + } + ret = nvstore.set(key, thr_test_data->sizes[key][0], thr_test_data->buffs[key][0]); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, ret); + } + + for (i = 0; i < num_threads; i++) { + threads[i] = new rtos::Thread((osPriority_t)((int)osPriorityBelowNormal-num_threads+i), thr_test_stack_size); + threads[i]->start(callback(thread_test_worker)); + } + + wait_ms(thr_test_num_secs * 1000); + + for (i = 0; i < num_threads; i++) { + threads[i]->terminate(); + delete threads[i]; + } + + delete[] threads; + + wait_ms(1000); + + nvstore.deinit(); + + nvstore.init(); + + for (key = 0; key < thr_test_data->max_keys; key++) { + thread_test_check_key(key); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, ret); + } + + for (key = 0; key < thr_test_data->max_keys; key++) { + for (i = 0; i < thr_test_num_buffs; i++) { + delete[] thr_test_data->buffs[key][i]; + } + } + + delete thr_test_data; + +#endif +} + +static void race_test_worker(void *buf) +{ + int ret; + NVStore &nvstore = NVStore::get_instance(); + + ret = nvstore.set(race_test_key, race_test_data_size, buf); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, ret); +} + +static void nvstore_race_test() +{ +#ifdef MBED_CONF_RTOS_PRESENT + int i; + uint16_t initial_buf_size; + int ret; + rtos::Thread *threads[race_test_num_threads]; + uint8_t *get_buff, *buffs[race_test_num_threads]; + uint16_t actual_len_bytes; + + NVStore &nvstore = NVStore::get_instance(); + + ret = nvstore.reset(); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, ret); + + initial_buf_size = std::min((nvstore.size() - race_test_data_size) / 2, (size_t) 256); + uint8_t *initial_buf = new uint8_t[initial_buf_size]; + int num_sets = (nvstore.size() - race_test_data_size) / initial_buf_size; + for (i = 0; i < num_sets; i++) { + ret = nvstore.set(0, initial_buf_size, initial_buf); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, ret); + } + delete[] initial_buf; + + for (i = 0; i < race_test_num_threads; i++) { + buffs[i] = new uint8_t[race_test_data_size]; + gen_random(buffs[i], race_test_data_size); + } + + for (i = 0; i < race_test_num_threads; i++) { + threads[i] = new rtos::Thread((osPriority_t)((int)osPriorityBelowNormal - race_test_num_threads + i), thr_test_stack_size); + threads[i]->start(callback(race_test_worker, (void *) buffs[i])); + threads[i]->join(); + } + + get_buff = new uint8_t[race_test_data_size]; + ret = nvstore.get(race_test_key, race_test_data_size, get_buff, actual_len_bytes); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, ret); + TEST_ASSERT_EQUAL(race_test_data_size, actual_len_bytes); + + for (i = 0; i < race_test_num_threads; i++) { + if (!memcmp(buffs[i], get_buff, actual_len_bytes)) { + break; + } + } + TEST_ASSERT_NOT_EQUAL(race_test_num_threads, i); + + for (i = 0; i < race_test_num_threads; i++) { + delete threads[i]; + delete[] buffs[i]; + } + delete[] get_buff; +#endif +} + + + +utest::v1::status_t greentea_failure_handler(const Case *const source, const failure_t reason) { + greentea_case_failure_abort_handler(source, reason); + return STATUS_CONTINUE; +} + +Case cases[] = { + Case("NVStore: Basic functionality", nvstore_basic_functionality_test, greentea_failure_handler), + Case("NVStore: Race test", nvstore_race_test, greentea_failure_handler), + Case("NVStore: Multiple thread test", nvstore_multi_thread_test, greentea_failure_handler), +}; + +utest::v1::status_t greentea_test_setup(const size_t number_of_cases) +{ + GREENTEA_SETUP(120, "default_auto"); + return greentea_test_setup_handler(number_of_cases); +} + +Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler); + +int main() +{ + return !Harness::run(specification); +} diff --git a/features/nvstore/mbed_lib.json b/features/nvstore/mbed_lib.json new file mode 100644 index 00000000000..d13cdbd4b2c --- /dev/null +++ b/features/nvstore/mbed_lib.json @@ -0,0 +1,31 @@ +{ + "name": "nvstore", + "config": { + "enabled": { + "macro_name": "NVSTORE_ENABLED", + "value": true, + "help": "Enabled" + }, + "max_keys": { + "macro_name": "NVSTORE_MAX_KEYS", + "value": 16, + "help": "Maximal number of allowed NVStore keys" + }, + "area_1_address": { + "macro_name": "NVSTORE_AREA_1_ADDRESS", + "help": "Area 1 address" + }, + "area_1_size": { + "macro_name": "NVSTORE_AREA_1_SIZE", + "help": "Area 1 size" + }, + "area_2_address": { + "macro_name": "NVSTORE_AREA_2_ADDRESS", + "help": "Area 2 address" + }, + "area_2_size": { + "macro_name": "NVSTORE_AREA_2_SIZE", + "help": "Area 2 size" + } + } +} diff --git a/features/nvstore/source/nvstore.cpp b/features/nvstore/source/nvstore.cpp new file mode 100644 index 00000000000..13d5f5d1fae --- /dev/null +++ b/features/nvstore/source/nvstore.cpp @@ -0,0 +1,920 @@ +/* + * Copyright (c) 2018 ARM Limited. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ----------------------------------------------------------- Includes ----------------------------------------------------------- + +#include "nvstore.h" + +#if NVSTORE_ENABLED + +#include "FlashIAP.h" +#include "mbed_critical.h" +#include "mbed_assert.h" +#include "Thread.h" +#include "mbed_wait_api.h" +#include +#include +#include + +// --------------------------------------------------------- Definitions ---------------------------------------------------------- + +static const uint16_t delete_item_flag = 0x8000; +static const uint16_t set_once_flag = 0x4000; +static const uint16_t header_flag_mask = 0xF000; + +static const uint16_t master_record_key = 0xFFE; +static const uint16_t no_key = 0xFFF; +static const uint16_t last_reserved_key = master_record_key; + +typedef struct +{ + uint16_t key_and_flags; + uint16_t size; + uint32_t crc; +} nvstore_record_header_t; + +static const uint32_t offs_by_key_area_mask = 0x80000000UL; +static const uint32_t offs_by_key_set_once_mask = 0x40000000UL; +static const uint32_t offs_by_key_flag_mask = 0xC0000000UL; +static const unsigned int offs_by_key_area_bit_pos = 31; +static const unsigned int offs_by_key_set_once_bit_pos = 30; + +typedef struct { + uint16_t version; + uint16_t reserved1; + uint32_t reserved2; +} master_record_data_t; + +static const uint32_t min_area_size = 4096; + +static const int num_write_retries = 16; + +static const uint8_t blank_flash_val = 0xFF; + +// See whether any of these defines are given (by config files) +// If so, this means that that area configuration is given by the user +#if defined(NVSTORE_AREA_1_ADDRESS) || defined(NVSTORE_AREA_1_SIZE) ||\ + defined(NVSTORE_AREA_2_ADDRESS) || defined(NVSTORE_AREA_2_SIZE) + +// Require all area configuration parameters if any one of them is present +#if !defined(NVSTORE_AREA_1_ADDRESS) || !defined(NVSTORE_AREA_1_SIZE) ||\ + !defined(NVSTORE_AREA_2_ADDRESS) || !defined(NVSTORE_AREA_2_SIZE) +#error Incomplete NVStore area configuration +#endif +#if (NVSTORE_AREA_1_SIZE == 0) || (NVSTORE_AREA_2_SIZE == 0) +#error NVStore area size cannot be 0 +#endif + +NVStore::nvstore_area_data_t NVStore::initial_area_params[] = {{NVSTORE_AREA_1_ADDRESS, NVSTORE_AREA_1_SIZE}, + {NVSTORE_AREA_2_ADDRESS, NVSTORE_AREA_2_SIZE}}; +#else +NVStore::nvstore_area_data_t NVStore::initial_area_params[] = {{0, 0}, + {0, 0}}; +#endif + +typedef enum { + NVSTORE_AREA_STATE_NONE = 0, + NVSTORE_AREA_STATE_EMPTY, + NVSTORE_AREA_STATE_VALID, +} area_state_e; + +static const uint32_t initial_crc = 0xFFFFFFFF; + + +// -------------------------------------------------- Local Functions Declaration ---------------------------------------------------- + +// -------------------------------------------------- Functions Implementation ---------------------------------------------------- + +// Align a value to a specified size. +// Parameters : +// val - [IN] Value. +// size - [IN] Size. +// Return : Aligned value. +static inline uint32_t align_up(uint32_t val, uint32_t size) +{ + return (((val - 1) / size) + 1) * size; +} + +// CRC32 calculation. Supports "rolling" calculation (using the initial value). +// Parameters : +// init_crc - [IN] Initial CRC. +// data_size - [IN] Buffer's data size. +// data_buf - [IN] Data buffer. +// Return : CRC. +static uint32_t crc32(uint32_t init_crc, uint32_t data_size, uint8_t *data_buf) +{ + uint32_t i, j; + uint32_t crc, mask; + + crc = init_crc; + for (i = 0; i < data_size; i++) { + crc = crc ^ (uint32_t) (data_buf[i]); + for (j = 0; j < 8; j++) { + mask = -(crc & 1); + crc = (crc >> 1) ^ (0xEDB88320 & mask); + } + } + return crc; +} + +NVStore::NVStore() : _init_done(0), _init_attempts(0), _active_area(0), _max_keys(NVSTORE_MAX_KEYS), + _active_area_version(0), _free_space_offset(0), _size(0), _mutex(0), _offset_by_key(0), _flash(0), + _min_prog_size(0), _page_buf(0) +{ +} + +NVStore::~NVStore() +{ + if (_init_done) { + deinit(); + } +} + +uint16_t NVStore::get_max_keys() const +{ + return _max_keys; +} + +uint16_t NVStore::get_max_possible_keys() +{ + if (!_init_done) { + init(); + } + + size_t max_possible_keys = _size / align_up(sizeof(nvstore_record_header_t) * 2, _min_prog_size) - 1; + + return (uint16_t)std::min(max_possible_keys, (size_t) last_reserved_key); +} + +void NVStore::set_max_keys(uint16_t num_keys) +{ + MBED_ASSERT(num_keys < get_max_possible_keys()); + _max_keys = num_keys; + // User is allowed to change number of keys. As this affects init, need to deinitialize now. + // Don't call init right away - it is lazily called by get/set functions if needed. + deinit(); +} + +int NVStore::flash_read_area(uint8_t area, uint32_t offset, uint32_t size, void *buf) +{ + return _flash->read(buf, _flash_area_params[area].address + offset, size); +} + +int NVStore::flash_write_area(uint8_t area, uint32_t offset, uint32_t size, const void *buf) +{ + int ret; + // On some boards, write action can fail due to HW limitations (like critical drivers + // that disable all other actions). Just retry a few times until success. + for (int i = 0; i < num_write_retries; i++) { + ret = _flash->program(buf, _flash_area_params[area].address + offset, size); + if (!ret) { + return ret; + } + wait_ms(1); + } + return ret; +} + +int NVStore::flash_erase_area(uint8_t area) +{ + int ret; + // On some boards, write action can fail due to HW limitations (like critical drivers + // that disable all other actions). Just retry a few times until success. + for (int i = 0; i < num_write_retries; i++) { + ret = _flash->erase(_flash_area_params[area].address, _flash_area_params[area].size); + if (!ret) { + return ret; + } + wait_ms(1); + } + return ret; +} + +void NVStore::calc_validate_area_params() +{ + int num_sectors = 0; + + size_t flash_addr = _flash->get_flash_start(); + size_t flash_size = _flash->get_flash_size(); + size_t sector_size; + int max_sectors = flash_size / _flash->get_sector_size(flash_addr) + 1; + size_t *sector_map = new size_t[max_sectors]; + + int area = 0; + size_t left_size = flash_size; + + memcpy(_flash_area_params, initial_area_params, sizeof(_flash_area_params)); + int user_config = (_flash_area_params[0].size != 0); + int in_area = 0; + size_t area_size = 0; + + while (left_size) { + sector_size = _flash->get_sector_size(flash_addr); + sector_map[num_sectors++] = flash_addr; + + if (user_config) { + // User configuration - here we validate it + // Check that address is on a sector boundary, that size covers complete sector sizes, + // and that areas don't overlap. + if (_flash_area_params[area].address == flash_addr) { + in_area = 1; + } + if (in_area) { + area_size += sector_size; + if (area_size == _flash_area_params[area].size) { + area++; + if (area == NVSTORE_NUM_AREAS) { + break; + } + in_area = 0; + area_size = 0; + } + } + } + + flash_addr += sector_size; + left_size -= sector_size; + } + sector_map[num_sectors] = flash_addr; + + if (user_config) { + // Valid areas were counted. Assert if not the expected number. + MBED_ASSERT(area == NVSTORE_NUM_AREAS); + } else { + // Not user configuration - calculate area parameters. + // Take last two sectors by default. If their sizes aren't big enough, take + // a few consecutive ones. + area = 1; + _flash_area_params[area].size = 0; + int i; + for (i = num_sectors - 1; i >= 0; i--) { + sector_size = sector_map[i+1] - sector_map[i]; + _flash_area_params[area].size += sector_size; + if (_flash_area_params[area].size >= min_area_size) { + _flash_area_params[area].address = sector_map[i]; + area--; + if (area < 0) { + break; + } + _flash_area_params[area].size = 0; + } + } + } + + delete[] sector_map; +} + + +int NVStore::calc_empty_space(uint8_t area, uint32_t &offset) +{ + uint32_t buf[32]; + uint8_t *chbuf; + uint32_t i, j; + int ret; + + offset = _size; + for (i = 0; i < _size / sizeof(buf); i++) { + offset -= sizeof(buf); + ret = flash_read_area(area, offset, sizeof(buf), buf); + if (ret) { + return ret; + } + chbuf = (uint8_t *) buf; + for (j = sizeof(buf); j > 0; j--) { + if (chbuf[j - 1] != blank_flash_val) { + offset += j; + return 0; + } + } + } + return 0; +} + +int NVStore::read_record(uint8_t area, uint32_t offset, uint16_t buf_size, void *buf, + uint16_t &actual_size, int validate_only, int &valid, + uint16_t &key, uint16_t &flags, uint32_t &next_offset) +{ + uint8_t int_buf[128]; + void *buf_ptr; + uint16_t data_size, chunk_size; + int os_ret; + nvstore_record_header_t header; + uint32_t crc = initial_crc; + + valid = 1; + + os_ret = flash_read_area(area, offset, sizeof(header), &header); + if (os_ret) { + return NVSTORE_READ_ERROR; + } + + crc = crc32(crc, sizeof(header) - sizeof(header.crc), (uint8_t *) &header); + + actual_size = 0; + key = header.key_and_flags & ~header_flag_mask; + flags = header.key_and_flags & header_flag_mask; + + if ((key >= _max_keys) && (key != master_record_key)) { + valid = 0; + return NVSTORE_SUCCESS; + } + + data_size = header.size; + offset += sizeof(header); + + // In case of validate only enabled, we use our internal buffer for data reading, + // instead of the user one. This allows us to use a smaller buffer, on which CRC + // is continuously calculated. + if (validate_only) { + buf_ptr = int_buf; + buf_size = sizeof(int_buf); + } else { + if (data_size > buf_size) { + offset += data_size; + actual_size = data_size; + next_offset = align_up(offset, _min_prog_size); + return NVSTORE_BUFF_TOO_SMALL; + } + buf_ptr = buf; + } + + while (data_size) { + chunk_size = std::min(data_size, buf_size); + os_ret = flash_read_area(area, offset, chunk_size, buf_ptr); + if (os_ret) { + return NVSTORE_READ_ERROR; + } + crc = crc32(crc, chunk_size, (uint8_t *) buf_ptr); + data_size -= chunk_size; + offset += chunk_size; + } + + if (header.crc != crc) { + valid = 0; + return NVSTORE_SUCCESS; + } + + actual_size = header.size; + next_offset = align_up(offset, _min_prog_size); + + return NVSTORE_SUCCESS; +} + +int NVStore::write_record(uint8_t area, uint32_t offset, uint16_t key, uint16_t flags, + uint32_t data_size, const void *data_buf, uint32_t &next_offset) +{ + nvstore_record_header_t header; + uint32_t crc = initial_crc; + int os_ret; + uint8_t *prog_buf; + + header.key_and_flags = key | flags; + header.size = data_size; + header.crc = 0; // Satisfy compiler + crc = crc32(crc, sizeof(header) - sizeof(header.crc), (uint8_t *) &header); + if (data_size) { + crc = crc32(crc, data_size, (uint8_t *) data_buf); + } + header.crc = crc; + + // In case page size is greater than header size, we can't write header and data + // separately. Instead, we need to copy header and start of data to our page buffer + // and write them together. Otherwise, simply write header and data separately. + uint32_t prog_size = sizeof(header); + uint32_t copy_size = 0; + if (_min_prog_size > sizeof(header)) { + prog_buf = _page_buf; + memcpy(prog_buf, &header, sizeof(header)); + if (data_size) { + memcpy(prog_buf, &header, sizeof(header)); + copy_size = std::min(data_size, _min_prog_size - sizeof(header)); + memcpy(prog_buf + sizeof(header), data_buf, copy_size); + data_size -= copy_size; + prog_size += copy_size; + } + } else { + prog_buf = (uint8_t *) &header; + } + + os_ret = flash_write_area(area, offset, prog_size, prog_buf); + if (os_ret) { + return NVSTORE_WRITE_ERROR; + } + offset += prog_size; + + if (data_size) { + prog_buf = (uint8_t *) data_buf + copy_size; + os_ret = flash_write_area(area, offset, data_size, prog_buf); + if (os_ret) { + return NVSTORE_WRITE_ERROR; + } + offset += data_size; + } + + next_offset = align_up(offset, _min_prog_size); + return NVSTORE_SUCCESS; +} + +int NVStore::write_master_record(uint8_t area, uint16_t version, uint32_t &next_offset) +{ + master_record_data_t master_rec; + + master_rec.version = version; + master_rec.reserved1 = 0; + master_rec.reserved2 = 0; + return write_record(area, 0, master_record_key, 0, sizeof(master_rec), + &master_rec, next_offset); +} + +int NVStore::copy_record(uint8_t from_area, uint32_t from_offset, uint32_t to_offset, + uint32_t &next_offset) +{ + uint8_t local_buf[128]; + uint16_t record_size, chunk_size, prog_buf_size; + int os_ret; + nvstore_record_header_t *header; + uint8_t *read_buf, *prog_buf; + + // This function assumes that the source record is valid, so no need to recalculate CRC. + + if (_min_prog_size > sizeof(nvstore_record_header_t)) { + prog_buf = _page_buf; + prog_buf_size = _min_prog_size; + } else { + prog_buf = local_buf; + prog_buf_size = sizeof(local_buf); + } + read_buf = prog_buf; + + os_ret = flash_read_area(from_area, from_offset, sizeof(nvstore_record_header_t), read_buf); + if (os_ret) { + return NVSTORE_READ_ERROR; + } + + header = (nvstore_record_header_t *) read_buf; + record_size = sizeof(nvstore_record_header_t) + header->size; + + // No need to copy records whose flags indicate deletion + if (header->key_and_flags & delete_item_flag) { + next_offset = align_up(to_offset, _min_prog_size); + return NVSTORE_SUCCESS; + } + + // no need to align record size here, as it won't change the outcome of this condition + if (to_offset + record_size >= _size) { + return NVSTORE_FLASH_AREA_TOO_SMALL; + } + + uint16_t start_size = sizeof(nvstore_record_header_t); + from_offset += start_size; + read_buf += start_size; + record_size -= start_size; + + do { + chunk_size = std::min(record_size, (uint16_t)(prog_buf_size - start_size)); + if (chunk_size) { + os_ret = flash_read_area(from_area, from_offset, chunk_size, read_buf); + if (os_ret) { + return NVSTORE_READ_ERROR; + } + } + os_ret = flash_write_area(1 - from_area, to_offset, chunk_size + start_size, prog_buf); + if (os_ret) { + return NVSTORE_WRITE_ERROR; + } + + read_buf = prog_buf; + record_size -= chunk_size; + from_offset += chunk_size; + to_offset += chunk_size + start_size; + start_size = 0; + } while (record_size); + + next_offset = align_up(to_offset, _min_prog_size); + return NVSTORE_SUCCESS; +} + +int NVStore::garbage_collection(uint16_t key, uint16_t flags, uint16_t buf_size, const void *buf) +{ + uint32_t curr_offset, new_area_offset, next_offset; + int ret; + uint8_t curr_area; + + new_area_offset = align_up(sizeof(nvstore_record_header_t) + sizeof(master_record_data_t), _min_prog_size); + + // If GC is triggered by a set item request, we need to first write that item in the new location, + // otherwise we may either write it twice (if already included), or lose it in case we decide + // to skip it at garbage collection phase (and the system crashes). + if ((key != no_key) && !(flags & delete_item_flag)) { + ret = write_record(1 - _active_area, new_area_offset, key, 0, buf_size, buf, next_offset); + if (ret != NVSTORE_SUCCESS) { + return ret; + } + _offset_by_key[key] = new_area_offset | (1 - _active_area) << offs_by_key_area_bit_pos | + (((flags & set_once_flag) != 0) << offs_by_key_set_once_bit_pos); + new_area_offset = next_offset; + } + + // Now iterate on all types, and copy the ones who have valid offsets (meaning that they exist) + // to the other area. + for (key = 0; key < _max_keys; key++) { + curr_offset = _offset_by_key[key]; + uint16_t save_flags = curr_offset & offs_by_key_area_mask; + curr_area = (uint8_t)(curr_offset >> offs_by_key_area_bit_pos) & 1; + curr_offset &= ~offs_by_key_flag_mask; + if ((!curr_offset) || (curr_area != _active_area)) { + continue; + } + ret = copy_record(curr_area, curr_offset, new_area_offset, next_offset); + if (ret != NVSTORE_SUCCESS) { + return ret; + } + _offset_by_key[key] = new_area_offset | (1 - curr_area) << offs_by_key_area_bit_pos | save_flags; + new_area_offset = next_offset; + } + + // Now write master record, with version incremented by 1. + _active_area_version++; + ret = write_master_record(1 - _active_area, _active_area_version, next_offset); + if (ret != NVSTORE_SUCCESS) { + return ret; + } + + _free_space_offset = new_area_offset; + + // Only now we can switch to the new active area + _active_area = 1 - _active_area; + + // The older area doesn't concern us now. Erase it now. + if (flash_erase_area(1 - _active_area)) { + return NVSTORE_WRITE_ERROR; + } + + return ret; +} + + +int NVStore::do_get(uint16_t key, uint16_t buf_size, void *buf, uint16_t &actual_size, + int validate_only) +{ + int ret = NVSTORE_SUCCESS; + int valid; + uint32_t record_offset, next_offset; + uint16_t read_type, flags; + uint8_t area; + + if (!_init_done) { + ret = init(); + if (ret != NVSTORE_SUCCESS) { + return ret; + } + } + + if (key >= _max_keys) { + return NVSTORE_BAD_VALUE; + } + + if (!buf) { + buf_size = 0; + } + + _mutex->lock(); + record_offset = _offset_by_key[key]; + + if (!record_offset) { + _mutex->unlock(); + return NVSTORE_NOT_FOUND; + } + + area = (uint8_t)(record_offset >> offs_by_key_area_bit_pos) & 1; + record_offset &= ~offs_by_key_flag_mask; + + ret = read_record(area, record_offset, buf_size, buf, + actual_size, validate_only, valid, + read_type, flags, next_offset); + if ((ret == NVSTORE_SUCCESS) && !valid) { + ret = NVSTORE_DATA_CORRUPT; + } + + _mutex->unlock(); + return ret; +} + +int NVStore::get(uint16_t key, uint16_t buf_size, void *buf, uint16_t &actual_size) +{ + return do_get(key, buf_size, buf, actual_size, 0); +} + +int NVStore::get_item_size(uint16_t key, uint16_t &actual_size) +{ + return do_get(key, 0, NULL, actual_size, 1); +} + +int NVStore::do_set(uint16_t key, uint16_t buf_size, const void *buf, uint16_t flags) +{ + int ret = NVSTORE_SUCCESS; + uint32_t record_offset, record_size, new_free_space; + uint32_t next_offset; + + if (!_init_done) { + ret = init(); + if (ret != NVSTORE_SUCCESS) { + return ret; + } + } + + if (key >= _max_keys) { + return NVSTORE_BAD_VALUE; + } + + if (!buf) { + buf_size = 0; + } + + if ((flags & delete_item_flag) && !_offset_by_key[key]) { + return NVSTORE_NOT_FOUND; + } + + if (_offset_by_key[key] & offs_by_key_set_once_mask) { + return NVSTORE_ALREADY_EXISTS; + } + + record_size = align_up(sizeof(nvstore_record_header_t) + buf_size, _min_prog_size); + + _mutex->lock(); + + new_free_space = core_util_atomic_incr_u32(&_free_space_offset, record_size); + record_offset = new_free_space - record_size; + + // If we cross the area limit, we need to invoke GC. + if (new_free_space >= _size) { + ret = garbage_collection(key, flags, buf_size, buf); + _mutex->unlock(); + return ret; + } + + // Now write the record + ret = write_record(_active_area, record_offset, key, flags, buf_size, buf, next_offset); + if (ret != NVSTORE_SUCCESS) { + _mutex->unlock(); + return ret; + } + + // Update _offset_by_key. High bit indicates area. + if (flags & delete_item_flag) { + _offset_by_key[key] = 0; + } else { + _offset_by_key[key] = record_offset | (_active_area << offs_by_key_area_bit_pos) | + (((flags & set_once_flag) != 0) << offs_by_key_set_once_bit_pos); + } + + _mutex->unlock(); + + return NVSTORE_SUCCESS; +} + +int NVStore::set(uint16_t key, uint16_t buf_size, const void *buf) +{ + return do_set(key, buf_size, buf, 0); +} + +int NVStore::set_once(uint16_t key, uint16_t buf_size, const void *buf) +{ + return do_set(key, buf_size, buf, set_once_flag); +} + +int NVStore::remove(uint16_t key) +{ + return do_set(key, 0, NULL, delete_item_flag); +} + +int NVStore::init() +{ + area_state_e area_state[NVSTORE_NUM_AREAS]; + uint32_t free_space_offset_of_area[NVSTORE_NUM_AREAS]; + uint32_t init_attempts_val; + uint32_t next_offset; + int os_ret; + int ret = NVSTORE_SUCCESS; + int valid; + uint16_t key; + uint16_t flags; + uint16_t versions[NVSTORE_NUM_AREAS]; + uint16_t actual_size; + + if (_init_done) { + return NVSTORE_SUCCESS; + } + + // This handles the case that init function is called by more than one thread concurrently. + // Only the one who gets the value of 1 in _init_attempts_val will proceed, while others will + // wait until init is finished. + init_attempts_val = core_util_atomic_incr_u32(&_init_attempts, 1); + if (init_attempts_val != 1) { + while (!_init_done) { + wait_ms(1); + } + return NVSTORE_SUCCESS; + } + + _offset_by_key = new uint32_t[_max_keys]; + MBED_ASSERT(_offset_by_key); + + for (key = 0; key < _max_keys; key++) { + _offset_by_key[key] = 0; + } + + _mutex = new PlatformMutex; + MBED_ASSERT(_mutex); + + _size = (uint32_t) -1; + _flash = new mbed::FlashIAP; + MBED_ASSERT(_flash); + _flash->init(); + + _min_prog_size = std::max(_flash->get_page_size(), (uint32_t)sizeof(nvstore_record_header_t)); + if (_min_prog_size > sizeof(nvstore_record_header_t)) { + _page_buf = new uint8_t[_min_prog_size]; + MBED_ASSERT(_page_buf); + } + + calc_validate_area_params(); + + for (uint8_t area = 0; area < NVSTORE_NUM_AREAS; area++) { + area_state[area] = NVSTORE_AREA_STATE_NONE; + free_space_offset_of_area[area] = 0; + versions[area] = 0; + + _size = std::min(_size, _flash_area_params[area].size); + + // Find start of empty space at the end of the area. This serves for both + // knowing whether the area is empty and for the record traversal at the end. + os_ret = calc_empty_space(area, free_space_offset_of_area[area]); + MBED_ASSERT(!os_ret); + + if (!free_space_offset_of_area[area]) { + area_state[area] = NVSTORE_AREA_STATE_EMPTY; + continue; + } + + // Check validity of master record + master_record_data_t master_rec; + ret = read_record(area, 0, sizeof(master_rec), &master_rec, + actual_size, 0, valid, + key, flags, next_offset); + MBED_ASSERT((ret == NVSTORE_SUCCESS) || (ret == NVSTORE_BUFF_TOO_SMALL)); + if (ret == NVSTORE_BUFF_TOO_SMALL) { + // Buf too small error means that we have a corrupt master record - + // treat it as such + valid = 0; + } + + // We have a non valid master record, in a non-empty area. Just erase the area. + if ((!valid) || (key != master_record_key)) { + os_ret = flash_erase_area(area); + MBED_ASSERT(!os_ret); + area_state[area] = NVSTORE_AREA_STATE_EMPTY; + continue; + } + versions[area] = master_rec.version; + + // Place _free_space_offset after the master record (for the traversal, + // which takes place after this loop). + _free_space_offset = next_offset; + area_state[area] = NVSTORE_AREA_STATE_VALID; + + // Unless both areas are valid (a case handled later), getting here means + // that we found our active area. + _active_area = area; + _active_area_version = versions[area]; + } + + // In case we have two empty areas, arbitrarily assign 0 to the active one. + if ((area_state[0] == NVSTORE_AREA_STATE_EMPTY) && (area_state[1] == NVSTORE_AREA_STATE_EMPTY)) { + _active_area = 0; + ret = write_master_record(_active_area, 1, _free_space_offset); + MBED_ASSERT(ret == NVSTORE_SUCCESS); + _init_done = 1; + return NVSTORE_SUCCESS; + } + + // In case we have two valid areas, choose the one having the higher version (or 0 + // in case of wrap around). Erase the other one. + if ((area_state[0] == NVSTORE_AREA_STATE_VALID) && (area_state[1] == NVSTORE_AREA_STATE_VALID)) { + if ((versions[0] > versions[1]) || (!versions[0])) { + _active_area = 0; + } else { + _active_area = 1; + } + _active_area_version = versions[_active_area]; + os_ret = flash_erase_area(1 - _active_area); + MBED_ASSERT(!os_ret); + } + + // Traverse area until reaching the empty space at the end or until reaching a faulty record + while (_free_space_offset < free_space_offset_of_area[_active_area]) { + ret = read_record(_active_area, _free_space_offset, 0, NULL, + actual_size, 1, valid, + key, flags, next_offset); + MBED_ASSERT(ret == NVSTORE_SUCCESS); + + // In case we have a faulty record, this probably means that the system crashed when written. + // Perform a garbage collection, to make the the other area valid. + if (!valid) { + ret = garbage_collection(no_key, 0, 0, NULL); + break; + } + if (flags & delete_item_flag) { + _offset_by_key[key] = 0; + } else { + _offset_by_key[key] = _free_space_offset | (_active_area << offs_by_key_area_bit_pos) | + (((flags & set_once_flag) != 0) << offs_by_key_set_once_bit_pos); + } + _free_space_offset = next_offset; + } + + _init_done = 1; + return NVSTORE_SUCCESS; +} + +int NVStore::deinit() +{ + if (_init_done) { + _flash->deinit(); + delete _flash; + delete _mutex; + delete[] _offset_by_key; + if (_page_buf) { + delete[] _page_buf; + _page_buf = 0; + } + } + + _init_attempts = 0; + _init_done = 0; + + return NVSTORE_SUCCESS; +} + +int NVStore::reset() +{ + uint8_t area; + int os_ret; + + if (!_init_done) { + init(); + } + + // Erase both areas, and reinitialize the module. This is totally not thread safe, + // as init doesn't take the case of re-initialization into account. It's OK, as this function + // should only be called in pre-production cases. + for (area = 0; area < NVSTORE_NUM_AREAS; area++) { + os_ret = flash_erase_area(area); + if (os_ret) { + return NVSTORE_WRITE_ERROR; + } + } + + deinit(); + return init(); +} + +int NVStore::get_area_params(uint8_t area, uint32_t &address, size_t &size) +{ + if (area >= NVSTORE_NUM_AREAS) { + return NVSTORE_BAD_VALUE; + } + + if (!_init_done) { + init(); + } + + address = _flash_area_params[area].address; + size = _flash_area_params[area].size; + + return NVSTORE_SUCCESS; +} + +size_t NVStore::size() +{ + if (!_init_done) { + init(); + } + + return _size; +} + +#endif // NVSTORE_ENABLED diff --git a/features/nvstore/source/nvstore.h b/features/nvstore/source/nvstore.h new file mode 100644 index 00000000000..e99e5e74fd3 --- /dev/null +++ b/features/nvstore/source/nvstore.h @@ -0,0 +1,407 @@ +/* + * Copyright (c) 2018 ARM Limited. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MBED_NVSTORE_H +#define MBED_NVSTORE_H + +// These addresses need to be configured according to board (in mbed_lib.json) +#ifndef DEVICE_FLASH +#undef NVSTORE_ENABLED +#define NVSTORE_ENABLED 0 +#endif + +#if NVSTORE_ENABLED +#include +#include +#include "platform/NonCopyable.h" +#include "PlatformMutex.h" +#include "FlashIAP.h" + +typedef enum { + NVSTORE_SUCCESS = 0, + NVSTORE_READ_ERROR = -1, + NVSTORE_WRITE_ERROR = -2, + NVSTORE_NOT_FOUND = -3, + NVSTORE_DATA_CORRUPT = -4, + NVSTORE_BAD_VALUE = -5, + NVSTORE_BUFF_TOO_SMALL = -6, + NVSTORE_FLASH_AREA_TOO_SMALL = -7, + NVSTORE_OS_ERROR = -8, + NVSTORE_ALREADY_EXISTS = -9, +} nvstore_status_e; + +#ifndef NVSTORE_MAX_KEYS +#define NVSTORE_MAX_KEYS 16 +#endif + +// defines 2 areas - active and nonactive, not configurable +#define NVSTORE_NUM_AREAS 2 + +class NVStore : private mbed::NonCopyable { +public: + + /** + * @brief As a singleton, return the single instance of the class. + * Reason for this class being a singleton is the following: + * - Ease the use for users of this class not having to coordinate instantiations. + * - Lazy instantiation of internal data (which we can't achieve with simple static classes). + * + * @returns Singleton instance reference. + */ + static NVStore& get_instance() + { + // Use this implementation of singleton (Meyer's) rather than the one that allocates + // the instance on the heap because it ensures destruction at program end (preventing warnings + // from memory checking tools). + static NVStore instance; + return instance; + } + + virtual ~NVStore(); + + /** + * @brief Returns number of keys. + * + * @returns Number of keys. + */ + uint16_t get_max_keys() const; + + /** + * @brief Set number of keys. + * + * @returns None. + */ + void set_max_keys(uint16_t num_keys); + + /** + * @brief Return maximal possible number of keys (in this flash configuration). + * + * @returns Max possible number of keys. + */ + uint16_t get_max_possible_keys(); + + /** + * @brief Returns one item of data programmed on Flash, given key. + * + * @param[in] key Key of stored item. + * @param[in] buf_size Length of input buffer in bytes. + * @param[in] buf Buffer to store data on. + * + * @param[out] actual_size Actual size of returned data. + * + * @returns NVSTORE_SUCCESS Value was found on Flash. + * NVSTORE_NOT_FOUND Value was not found on Flash. + * NVSTORE_READ_ERROR Physical error reading data. + * NVSTORE_DATA_CORRUPT Data on Flash is corrupt. + * NVSTORE_BAD_VALUE Bad value in any of the parameters. + * NVSTORE_BUFF_TOO_SMALL Not enough memory in user buffer. + */ + int get(uint16_t key, uint16_t buf_size, void *buf, uint16_t &actual_size); + + /** + * @brief Returns size of the data programmed on Flash, given key. + * + * @param[in] key Key of stored item. + * @param[out] actual_size Actual size of item + * + * @returns NVSTORE_SUCCESS Value was found on Flash. + * NVSTORE_NOT_FOUND Value was not found on Flash. + * NVSTORE_READ_ERROR Physical error reading data. + * NVSTORE_DATA_CORRUPT Data on Flash is corrupt. + * NVSTORE_BAD_VALUE Bad value in any of the parameters. + */ + int get_item_size(uint16_t key, uint16_t &actual_size); + + + /** + * @brief Programs one item of data on Flash, given key. + * + * @param[in] key Key of stored item. + * @param[in] buf_size Item size in bytes. + * @param[in] buf Buffer containing data. + * + * @returns NVSTORE_SUCCESS Value was successfully written on Flash. + * NVSTORE_WRITE_ERROR Physical error writing data. + * NVSTORE_BAD_VALUE Bad value in any of the parameters. + * NVSTORE_FLASH_AREA_TOO_SMALL + * Not enough space in Flash area. + * NVSTORE_ALREADY_EXISTS Item set with write once API already exists. + * + */ + int set(uint16_t key, uint16_t buf_size, const void *buf); + + /** + * @brief Programs one item of data on Flash, given key, allowing no consequent sets to this key. + * + * @param[in] key Key of stored item. + * @param[in] buf_size Item size in bytes. + * @param[in] buf Buffer containing data. + * + * @returns NVSTORE_SUCCESS Value was successfully written on Flash. + * NVSTORE_WRITE_ERROR Physical error writing data. + * NVSTORE_BAD_VALUE Bad value in any of the parameters. + * NVSTORE_FLASH_AREA_TOO_SMALL + * Not enough space in Flash area. + * NVSTORE_ALREADY_EXISTS Item set with write once API already exists. + * + */ + int set_once(uint16_t key, uint16_t buf_size, const void *buf); + + + /** + * @brief Remove an item from flash. + * + * @param[in] key Key of stored item. + * + * @returns NVSTORE_SUCCESS Value was successfully written on Flash. + * NVSTORE_WRITE_ERROR Physical error writing data. + * NVSTORE_BAD_VALUE Bad value in any of the parameters. + * NVSTORE_FLASH_AREA_TOO_SMALL + * Not enough space in Flash area. + * + */ + int remove(uint16_t key); + + /** + * @brief Initializes NVStore component. + * + * @returns NVSTORE_SUCCESS Initialization completed successfully. + * NVSTORE_READ_ERROR Physical error reading data. + * NVSTORE_WRITE_ERROR Physical error writing data (on recovery). + * NVSTORE_FLASH_AREA_TOO_SMALL + * Not enough space in Flash area. + */ + int init(); + + /** + * @brief Deinitializes NVStore component. + * Warning: This function is not thread safe and should not be called + * concurrently with other NVStore functions. + * + * @returns NVSTORE_SUCCESS Deinitialization completed successfully. + */ + int deinit(); + + /** + * @brief Reset Flash NVStore areas. + * Warning: This function is not thread safe and should not be called + * concurrently with other NVStore functions. + * + * @returns NVSTORE_SUCCESS Reset completed successfully. + * NVSTORE_READ_ERROR Physical error reading data. + * NVSTORE_WRITE_ERROR Physical error writing data. + */ + int reset(); + + /** + * @brief Return NVStore size (area size). + * + * @returns NVStore size. + */ + size_t size(); + + /** + * @brief Return address and size of an NVStore area. + * + * @param[in] area Area. + * @param[out] address Area address. + * @param[out] size Area size (bytes). + * + * @returns NVSTORE_SUCCESS Success. + * NVSTORE_BAD_VALUE Bad area parameter. + */ + int get_area_params(uint8_t area, uint32_t &address, size_t &size); + + +private: + typedef struct { + uint32_t address; + size_t size; + } nvstore_area_data_t; + + int _init_done; + uint32_t _init_attempts; + uint8_t _active_area; + uint16_t _max_keys; + uint16_t _active_area_version; + uint32_t _free_space_offset; + size_t _size; + PlatformMutex *_mutex; + uint32_t *_offset_by_key; + nvstore_area_data_t _flash_area_params[NVSTORE_NUM_AREAS]; + static nvstore_area_data_t initial_area_params[NVSTORE_NUM_AREAS]; + mbed::FlashIAP *_flash; + uint32_t _min_prog_size; + uint8_t *_page_buf; + + // Private constructor, as class is a singleton + NVStore(); + + /** + * @brief Read a block from an area. + * + * @param[in] area Area. + * @param[in] offset Offset in area. + * @param[in] size Number of bytes to read. + * @param[in] buf Output buffer. + * + * @returns 0 for success, nonzero for failure. + */ + int flash_read_area(uint8_t area, uint32_t offset, uint32_t size, void *buf); + + /** + * @brief Write a block to an area. + * + * @param[in] area Area. + * @param[in] offset Offset in area. + * @param[in] size Number of bytes to write. + * @param[in] buf Input buffer. + * + * @returns 0 for success, non-zero for failure. + */ + int flash_write_area(uint8_t area, uint32_t offset, uint32_t size, const void *buf); + + /** + * @brief Erase an area. + * + * @param[in] area Area. + * + * @returns 0 for success, nonzero for failure. + */ + int flash_erase_area(uint8_t area); + + /** + * @brief Calculate addresses and sizes of areas (in case no user configuration is given), + * or validate user configuration (if given). + * + * @param[in] area Area. + */ + void calc_validate_area_params(); + + /** + * @brief Calculate empty (unprogrammed) continuous space at the end of the area. + * + * @param[in] area Area. + * @param[out] offset Offset of empty space. + * + * @returns 0 for success, nonzero for failure. + */ + int calc_empty_space(uint8_t area, uint32_t &offset); + + /** + * @brief Read an NVStore record from a given location. + * + * @param[in] area Area. + * @param[in] offset Offset of record in area. + * @param[in] buf_size Buffer size (bytes). + * @param[in] buf Output Buffer. + * @param[out] actual_size Actual data size (bytes). + * @param[in] validate_only Just validate (without reading to buffer). + * @param[out] validate Is the record valid. + * @param[out] key Record key. + * @param[out] flags Record flags. + * @param[out] next_offset Offset of next record. + * + * @returns 0 for success, nonzero for failure. + */ + int read_record(uint8_t area, uint32_t offset, uint16_t buf_size, void *buf, + uint16_t &actual_size, int validate_only, int &valid, + uint16_t &key, uint16_t &flags, uint32_t &next_offset); + + /** + * @brief Write an NVStore record from a given location. + * + * @param[in] area Area. + * @param[in] offset Offset of record in area. + * @param[in] key Record key. + * @param[in] flags Record flags. + * @param[in] data_size Data size (bytes). + * @param[in] data_buf Data buffer. + * @param[out] next_offset Offset of next record. + * + * @returns 0 for success, nonzero for failure. + */ + int write_record(uint8_t area, uint32_t offset, uint16_t key, uint16_t flags, + uint32_t data_size, const void *data_buf, uint32_t &next_offset); + + /** + * @brief Write a master record of a given area. + * + * @param[in] area Area. + * @param[in] version Area version. + * @param[out] next_offset Offset of next record. + * + * @returns 0 for success, nonzero for failure. + */ + int write_master_record(uint8_t area, uint16_t version, uint32_t &next_offset); + + /** + * @brief Copy a record from one area to the other one. + * + * @param[in] from_area Area to copy record from. + * @param[in] from_offset Offset in source area. + * @param[in] to_offset Offset in destination area. + * @param[out] next_offset Offset of next record. + * + * @returns 0 for success, nonzero for failure. + */ + int copy_record(uint8_t from_area, uint32_t from_offset, uint32_t to_offset, + uint32_t &next_offset); + + /** + * @brief Garbage collection (compact all records from active area to nonactive ones). + * All parameters belong to a record that needs to be written before the process. + * + * @param[in] key Record key. + * @param[in] flags Record flags. + * @param[in] buf_size Data size (bytes). + * @param[in] buf Data buffer. + * + * @returns 0 for success, nonzero for failure. + */ + int garbage_collection(uint16_t key, uint16_t flags, uint16_t buf_size, const void *buf); + + /** + * @brief Actual logics of get API (covers also get size API). + * + * @param[in] key key. + * @param[in] buf_size Buffer size (bytes). + * @param[in] buf Output Buffer. + * @param[out] actual_size Actual data size (bytes). + * @param[in] validate_only Just validate (without reading to buffer). + * + * @returns 0 for success, nonzero for failure. + */ + int do_get(uint16_t key, uint16_t buf_size, void *buf, uint16_t &actual_size, + int validate_only); + + /** + * @brief Actual logics of set API (covers also set_once and remove APIs). + * + * @param[in] key key. + * @param[in] buf_size Buffer size (bytes). + * @param[in] buf Input Buffer. + * @param[in] flags Record flags. + * + * @returns 0 for success, nonzero for failure. + */ + int do_set(uint16_t key, uint16_t buf_size, const void *buf, uint16_t flags); + +}; + +#endif // NVSTORE_ENABLED + +#endif