mirror of https://github.com/ARMmbed/mbed-os.git
Merge pull request #5900 from davidsaada/david_sotp_cpp
Add NVStore (A.K.A SOTP) featurepull/6251/head
commit
25321961fb
|
@ -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```
|
|
@ -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);
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue