Merge pull request #5900 from davidsaada/david_sotp_cpp

Add NVStore (A.K.A SOTP) feature
pull/6251/head
Cruz Monrreal 2018-03-01 19:38:28 -06:00 committed by GitHub
commit 25321961fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 1970 additions and 0 deletions

View File

@ -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```

View File

@ -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 <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <algorithm>
#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);
}

View File

@ -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"
}
}
}

View File

@ -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 <algorithm>
#include <string.h>
#include <stdio.h>
// --------------------------------------------------------- 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

View File

@ -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 <stdint.h>
#include <stdio.h>
#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<NVStore> {
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