├── .gitattributes ├── .github └── workflows │ └── sonar.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── sonar-project.properties ├── src ├── entry.c ├── entry.h ├── include │ └── uwlkv.h ├── map.c ├── map.h ├── storage.c ├── storage.h └── uwlkv.c └── tests ├── nvram_mock.cpp ├── nvram_mock.h └── tests.cpp /.gitattributes: -------------------------------------------------------------------------------- 1 | tests/* linguist-vendored -------------------------------------------------------------------------------- /.github/workflows/sonar.yml: -------------------------------------------------------------------------------- 1 | name: SonarQube 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | types: [opened, synchronize, reopened] 8 | jobs: 9 | build: 10 | name: Build and analyze 11 | runs-on: ubuntu-latest 12 | env: 13 | BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 0 18 | - name: Install Build Wrapper 19 | uses: SonarSource/sonarqube-scan-action/install-build-wrapper@v5 20 | - name: Run Build Wrapper 21 | run: | 22 | mkdir build 23 | cd build 24 | cmake ../ 25 | build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} make all 26 | ./tests 27 | make coverage 28 | - name: SonarQube Scan 29 | uses: SonarSource/sonarqube-scan-action@v5 30 | env: 31 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 32 | with: 33 | args: > 34 | --define sonar.cfamily.compile-commands="build/${{ env.BUILD_WRAPPER_OUT_DIR }}/compile_commands.json" 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .vs/ 3 | tests/CMakeSettings.json 4 | .vscode/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11) 2 | project(tests LANGUAGES C CXX) 3 | 4 | # General settings 5 | set(CMAKE_CXX_STANDARD 14) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | set(CMAKE_C_STANDARD 99) 8 | set(CMAKE_C_STANDARD_REQUIRED ON) 9 | 10 | cmake_policy(SET CMP0076 NEW) 11 | 12 | # Enable testing and code coverage 13 | enable_testing() 14 | 15 | include_directories(src src/include) 16 | 17 | # Fetch Catch2 for testing 18 | Include(FetchContent) 19 | FetchContent_Declare( 20 | Catch2 21 | GIT_REPOSITORY https://github.com/catchorg/Catch2.git 22 | GIT_TAG v3.4.0 23 | ) 24 | FetchContent_MakeAvailable(Catch2) 25 | 26 | # Core library 27 | add_library(uwlkv STATIC 28 | src/uwlkv.c 29 | src/map.c 30 | src/entry.c 31 | src/storage.c 32 | ) 33 | 34 | # Test executable 35 | add_executable(tests 36 | tests/tests.cpp 37 | tests/nvram_mock.cpp 38 | ) 39 | target_link_libraries(tests PRIVATE Catch2::Catch2WithMain uwlkv) 40 | 41 | if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") 42 | target_compile_options(uwlkv PRIVATE 43 | -Wall -Wextra -Werror -pedantic 44 | --coverage 45 | ) 46 | target_compile_options(tests PRIVATE 47 | -Wall -Wextra -Werror -pedantic 48 | -fstrict-aliasing 49 | -Wdouble-promotion -Wswitch-enum -Wfloat-equal -Wundef 50 | -Wconversion -Wsign-promo -Wsign-conversion -Wcast-align 51 | -Wtype-limits -Wzero-as-null-pointer-constant -Wnon-virtual-dtor 52 | -Woverloaded-virtual 53 | --coverage 54 | -g -O0 55 | ) 56 | target_link_options(tests PRIVATE --coverage) 57 | 58 | set(LCOV_REMOVE_EXTRA "'test/*'") 59 | add_custom_target(coverage COMMAND gcov ${CMAKE_BINARY_DIR}/CMakeFiles/uwlkv.dir/src/*.c.o) 60 | elseif(MSVC) 61 | # MSVC-specific warning levels 62 | target_compile_options(uwlkv PRIVATE /W4 /WX) 63 | target_compile_options(tests PRIVATE /W4 /WX) 64 | endif() 65 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Alexander 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.com/Gordon01/uWLKV.svg?branch=master)](https://travis-ci.com/Gordon01/uWLKV) 2 | [![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=Gordon01_uWLKV&metric=code_smells)](https://sonarcloud.io/dashboard?id=Gordon01_uWLKV) 3 | [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=Gordon01_uWLKV&metric=coverage)](https://sonarcloud.io/dashboard?id=Gordon01_uWLKV) 4 | 5 | # uWLKV 6 | 7 | Microcontroller Key–Value Storage with Wear-Leveling 8 | 9 | This library makes implementing wear-leveling on microcontrollers effortless. Many MCUs provide only a small NVRAM area (Flash or EEPROM) organized as a byte array, and each byte typically endures ≈100 000 writes. If your application writes data frequently, or you don’t want to limit user actions, this write limit can be problematic. By spreading writes across unused space, wear-leveling extends the usable life of the memory without restricting functionality. 10 | 11 | [Full project documentation on DeepWiki](https://deepwiki.com/Gordon01/uWLKV). 12 | 13 | ## How it works 14 | 15 | * __Sequential writes__: Each new key–value record is appended to the next free NVRAM block rather than overwriting old data. This evens out wear across all bytes. 16 | * __Dynamic space allocation__: Frequently changing parameters naturally occupy more free blocks, maximizing their available write-cycles without manual tuning. 17 | * __Page-wise erase and defragmentation__: When no free blocks remain, the library performs a single erase of the entire NVRAM region (or Flash page), compacts the latest key–value pairs back to the start, and then resumes writing. This suits microcontrollers that can only erase large Flash pages and avoids per-parameter erases. 18 | 19 | ## Trade-offs 20 | 21 | * __Space overhead__: Every record stores its key alongside its value. 22 | * __Occasional latency__: Erase-and-compact cycles add a brief pause, but only when the main region is full. 23 | 24 | # How to use 25 | 26 | ## Define your NVRAM interface 27 | 28 | ```cpp 29 | uwlkv_nvram_interface interface; 30 | 31 | interface.read = &flash_read; 32 | interface.write = &flash_write; 33 | interface.erase_main = &flash_erase_main; 34 | interface.erase_reserve = &flash_erase_reserve; 35 | interface.size = 1024; // Total NVRAM size, in bytes 36 | interface.reserved = 256; // Size of the reserved area within NVRAM, in bytes 37 | ``` 38 | 39 | ### Read and write 40 | 41 | ```cpp 42 | int(* read)(uint8_t * data, uwlkv_offset start, uwlkv_offset size); 43 | int(* write)(uint8_t * data, uwlkv_offset start, uwlkv_offset size); 44 | ``` 45 | 46 | Where: 47 | * `data` - buffer pointer 48 | * `start` - byte-offset (0-based) into the NVRAM region 49 | * `size` - number of bytes to read or write (must match buffer size) 50 | 51 | ### Erase 52 | 53 | ```cpp 54 | int (*erase_main)(void); // Must erase bytes [ 0 .. size − reserved − 1 ] 55 | int (*erase_reserve)(void); // Must erase bytes [ size − reserved .. size − 1 ] 56 | ``` 57 | 58 | ### `reserved` 59 | 60 | A backup area (in bytes) that holds up to `UWLKV_MAX_ENTRIES` plus metadata. During a full-region erase, the library uses this space to temporarily store valid entries in case of an unexpected reset. If your Flash can only erase in page-sized chunks, set reserved to exactly one page. 61 | 62 | ## Initialize 63 | 64 | ```cpp 65 | int entries = uwlkv_init(&interface); 66 | ``` 67 | 68 | * Returns 0 if `size` or `reserved` values are invalid. 69 | * Otherwise returns the maximum number of entries that can fit in the main area. 70 | 71 | You can estimate the wear-leveling factor as 72 | 73 | ``` 74 | wear_factor ≃ (entries) / UWLKV_MAX_ENTRIES 75 | ``` 76 | 77 | ## Store and retrieve values 78 | 79 | After successful initialization, use: 80 | 81 | ```cpp 82 | uwlkv_error uwlkv_get_value(uwlkv_key key, uwlkv_value *value_out); 83 | uwlkv_error uwlkv_set_value(uwlkv_key key, uwlkv_value value_in); 84 | ``` 85 | 86 | * Keys are unique identifiers (e.g. integers or enums). 87 | * Values default to `int32_t`. 88 | * To change the erase-state byte from default `0xFF`, redefine `UWLKV_ERASED_BYTE_VALUE` in `uwlkv.h`.* 89 | 90 | ## Limits 91 | 92 | The number of stored parameters is capped by `UWLKV_MAX_ENTRIES` (default 20), not by the raw NVRAM size. 93 | 94 | # Tuning for Lower RAM and Storage Overhead 95 | 96 | Adjust these in uwlkv.h to shrink RAM or NVRAM overhead: 97 | 98 | * `UWLKV_MAX_ENTRIES`: Reduce if you need fewer unique keys to shrink the static cache. 99 | * __Shrink key or value types__. By default, `uwlkv_key` is `uint16_t` and `uwlkv_value` is `int32_t`. If your keys never exceed 0–255, you can redefine `uwlkv_key` as `uint8_t`. Likewise, if stored values fit in 16 bits, redefine `uwlkv_value` as `int16_t` (or smaller). 100 | * __Reduce offset width__. The type uwlkv_offset determines how you address bytes in NVRAM. If your total NVRAM size is ≤ 65 535 bytes, change `uwlkv_offset` to `uint16_t` instead of `uint32_t` to cut RAM used by index calculations. 101 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=Gordon01_uWLKV 2 | sonar.organization=gordon01 3 | 4 | # This is the name and version displayed in the SonarCloud UI. 5 | #sonar.projectName=uWLKV 6 | #sonar.projectVersion=1.0 7 | 8 | sonar.sources=src 9 | sonar.cfamily.gcov.reportsPath=build 10 | sonar.sourceEncoding=UTF-8 11 | -------------------------------------------------------------------------------- /src/entry.c: -------------------------------------------------------------------------------- 1 | /* This module accesess NVRAM and serializes/deserializes data. 2 | */ 3 | 4 | #include "uwlkv.h" 5 | #include "entry.h" 6 | 7 | extern uwlkv_nvram_interface nvram_interface; 8 | 9 | /** 10 | * @brief Read entry from NVRAM by offset. 11 | * 12 | * @param offset Offset in bytes. 13 | * @param [out] key Entry key. 14 | * @param [out] value Entry value. 15 | * 16 | * @returns UWLKV_E_SUCCESS on successeful read. 17 | */ 18 | uwlkv_error uwlkv_read_entry(const uwlkv_offset offset, uwlkv_key * key, uwlkv_value * value) 19 | { 20 | if ((offset + UWLKV_ENTRY_SIZE) > nvram_interface.size) 21 | { 22 | return UWLKV_E_WRONG_OFFSET; 23 | } 24 | 25 | uint8_t block[UWLKV_ENTRY_SIZE]; 26 | if (nvram_interface.read((uint8_t *)&block, offset, UWLKV_ENTRY_SIZE)) 27 | { 28 | return UWLKV_E_NVRAM_ERROR; 29 | } 30 | 31 | if (uwlkv_is_block_erased(block, UWLKV_ENTRY_SIZE)) 32 | { 33 | return UWLKV_E_NOT_EXIST; 34 | } 35 | 36 | *key = *(uwlkv_key*)&block[0]; 37 | *value = *(uwlkv_value*)&block[sizeof(uwlkv_key)]; 38 | 39 | return UWLKV_E_SUCCESS; 40 | } 41 | 42 | /** 43 | * @brief Write entry to NVRAM by offset. 44 | * 45 | * @param offset Offset in bytes. 46 | * @param key Entry key. 47 | * @param value Entry value. 48 | * 49 | * @returns UWLKV_E_SUCCESS on successeful write. 50 | */ 51 | uwlkv_error uwlkv_write_entry(uwlkv_offset offset, uwlkv_key key, uwlkv_value value) 52 | { 53 | if ((offset + UWLKV_ENTRY_SIZE) > nvram_interface.size) 54 | { 55 | return UWLKV_E_WRONG_OFFSET; 56 | } 57 | 58 | uint8_t block[UWLKV_ENTRY_SIZE]; 59 | uwlkv_key * key_in_block = (uwlkv_key*)&block[0]; 60 | uwlkv_value * value_in_block = (uwlkv_value*)&block[sizeof(uwlkv_key)]; 61 | 62 | *key_in_block = key; 63 | *value_in_block = value; 64 | 65 | if (nvram_interface.write((uint8_t *)&block, offset, UWLKV_ENTRY_SIZE)) 66 | { 67 | return UWLKV_E_NVRAM_ERROR; 68 | } 69 | 70 | return UWLKV_E_SUCCESS; 71 | } 72 | 73 | /** 74 | * @brief Checks that given block is fully erased (filled with UWLKV_ERASED_BYTE_VALUE) 75 | * 76 | * @param [in] data Data to be tested. 77 | * @param size Size of data (in bytes). 78 | * 79 | * @returns - 0 block is not erased 80 | * - 1 block is erased. 81 | */ 82 | uint8_t uwlkv_is_block_erased(const uint8_t * data, const uwlkv_offset size) 83 | { 84 | uint8_t erased_bytes = 0; 85 | for (uwlkv_offset i = 0; i < size; i++) 86 | { 87 | if (data[i] == UWLKV_ERASED_BYTE_VALUE) 88 | { 89 | erased_bytes += 1; 90 | } 91 | } 92 | 93 | return erased_bytes == size; 94 | } 95 | -------------------------------------------------------------------------------- /src/entry.h: -------------------------------------------------------------------------------- 1 | #ifndef UWLKV_ENTRY_H 2 | #define UWLKV_ENTRY_H 3 | 4 | uwlkv_error uwlkv_read_entry(uwlkv_offset offset, uwlkv_key * key, uwlkv_value * value); 5 | uwlkv_error uwlkv_write_entry(uwlkv_offset offset, uwlkv_key key, uwlkv_value value); 6 | uint8_t uwlkv_is_block_erased(const uint8_t * data, const uwlkv_offset size); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /src/include/uwlkv.h: -------------------------------------------------------------------------------- 1 | #ifndef UWLKV_NVRAM_LIB 2 | #define UWLKV_NVRAM_LIB 3 | 4 | #include 5 | 6 | /* You can change a storage type if you don't need big values for key or value to save some space */ 7 | typedef uint16_t uwlkv_key; /* Record key */ 8 | typedef int32_t uwlkv_value; /* Record value */ 9 | typedef uint32_t uwlkv_offset; /* NVRAM address. Can be reduced to match memory size and save some RAM */ 10 | typedef int(* uwlkv_erase)(void); /* NVRAM erase function prototype */ 11 | 12 | #define UWLKV_O_ERASE_STARTED (0) /* Offset of ERASE_STARTED flag */ 13 | #define UWLKV_O_ERASE_FINISHED (1) /* Offset of ERASE_FINISHED flag */ 14 | #define UWLKV_METADATA_SIZE (2) /* Number of bytes, that library use in the beginning of each area */ 15 | #define UWLKV_NVRAM_ERASE_STARTED (0xE2) /* Magic for ERASE_STARTED flag */ 16 | #define UWLKV_NVRAM_ERASE_FINISHED (0x3E) /* Magic for ERASE_FINISHED flag */ 17 | 18 | #define UWLKV_ENTRY_SIZE (sizeof(uwlkv_key) + sizeof(uwlkv_value)) 19 | #define UWLKV_MINIMAL_SIZE (UWLKV_ENTRY_SIZE + UWLKV_METADATA_SIZE) 20 | #define UWLKV_MAX_ENTRIES (20) /* Maximum amount of unique keys. Increases RAM consumption */ 21 | #define UWLKV_ERASED_BYTE_VALUE (0xFF) /* Value of erased byte of NVRAM */ 22 | 23 | typedef struct 24 | { 25 | uwlkv_key key; 26 | uwlkv_offset offset; 27 | } uwlkv_entry; 28 | 29 | /* You must provide an interface to access storage device. 30 | * Read/write functions should use logical address (starting from 0). 31 | * erase_main() should erase a main (large) area, without touching reserved area. 32 | * erase_reserve() should erase only a reserved area. 33 | */ 34 | typedef struct 35 | { 36 | int(* read)(uint8_t * data, uwlkv_offset start, uwlkv_offset size); 37 | int(* write)(uint8_t * data, uwlkv_offset start, uwlkv_offset size); 38 | uwlkv_erase erase_main; 39 | uwlkv_erase erase_reserve; 40 | uwlkv_offset size; /* Total size of provided memory */ 41 | uwlkv_offset reserved; /* Reserved area size in that memory */ 42 | } uwlkv_nvram_interface; 43 | 44 | typedef enum 45 | { 46 | UWLKV_E_SUCCESS, 47 | UWLKV_E_NOT_EXIST, /* Requested entry does not exist on NVRAM */ 48 | UWLKV_E_NVRAM_ERROR, /* NVRAM interface signalled an error during the operation */ 49 | UWLKV_E_NOT_STARTED, /* UWLKV haven't been initialized */ 50 | UWLKV_E_NO_SPACE, /* No free space in map for new entry */ 51 | UWLKV_E_WRONG_OFFSET, /* Provided offset is out of NVRAM bounds */ 52 | } uwlkv_error; 53 | 54 | typedef enum 55 | { 56 | UWLKV_MAIN, 57 | UWLKV_RESERVED 58 | } uwlkv_area; 59 | 60 | typedef enum 61 | { 62 | UWLKV_S_BLANK, /* NVRAM is fully erased (new) */ 63 | UWLKV_S_CLEAN, /* Last shutdown was clean */ 64 | UWLKV_S_MAIN_ERASE_INTERRUPTED, /* Main area erase was interrupted */ 65 | UWLKV_S_RESERVE_ERASE_INTERRUPTED /* Reserved area erase was interrupted */ 66 | } uwlkv_nvram_state; 67 | 68 | #ifdef __cplusplus 69 | extern "C" { 70 | #endif 71 | 72 | uwlkv_offset uwlkv_init(const uwlkv_nvram_interface * nvram_interface); 73 | uwlkv_key uwlkv_get_entries_number(void); 74 | uwlkv_key uwlkv_get_free_entries(void); 75 | uwlkv_error uwlkv_get_value(uwlkv_key key, uwlkv_value * value); 76 | uwlkv_error uwlkv_set_value(uwlkv_key key, uwlkv_value value); 77 | 78 | #ifdef __cplusplus 79 | } 80 | #endif 81 | 82 | #endif 83 | -------------------------------------------------------------------------------- /src/map.c: -------------------------------------------------------------------------------- 1 | /* This module implements a simple cache to track block positions in NVRAM. Key may have 2 | * any value within it's type range (uwlkv_key). Currently the cache is implemented as an array. 3 | * Due to linear retrieval the access may become slower because of the large amount of unique keys 4 | * (defined by UWLKV_MAX_ENTRIES). 5 | * Also it is stored in RAM so if you want to reduce RAM usage, you may adjust UWLKV_MAX_ENTRIES 6 | * and data types uwlkv_key and uwlkv_offset. Also you may need to make struct uwlkv_entry packed. 7 | */ 8 | 9 | #include "uwlkv.h" 10 | #include "map.h" 11 | #include "entry.h" 12 | 13 | static uwlkv_entry uwlkv_entries[UWLKV_MAX_ENTRIES]; 14 | static uwlkv_key used_entries; 15 | 16 | /** 17 | * @brief Returns a pointer to an entry with provided key. 18 | * 19 | * @param key The key. 20 | * @param [out] entry On success would be pointing to entry in uwlkv_entries. 21 | * 22 | * @returns - UWLKV_E_SUCCESS or 23 | * - UWLKV_E_NOT_EXIST if entry with this key is not found. 24 | */ 25 | uwlkv_error uwlkv_get_entry(const uwlkv_key key, uwlkv_entry ** entry) 26 | { 27 | for(uwlkv_key i = 0; i < used_entries; i++) 28 | { 29 | *entry = &uwlkv_entries[i]; 30 | if (key == uwlkv_entries[i].key) 31 | { 32 | return UWLKV_E_SUCCESS; 33 | } 34 | } 35 | 36 | return UWLKV_E_NOT_EXIST; 37 | } 38 | 39 | /** 40 | * @brief Returns a pointer to an entry by it's position in cache 41 | * 42 | * @param number Entry number 43 | * 44 | * @returns Null if it fails, else a pointer to an uwlkv_entry. 45 | */ 46 | uwlkv_entry * uwlkv_get_entry_by_id(const uwlkv_key number) 47 | { 48 | if (number >= UWLKV_MAX_ENTRIES) 49 | { 50 | return 0; 51 | } 52 | 53 | return &uwlkv_entries[number]; 54 | } 55 | 56 | /** 57 | * @brief Reserves space for one entry and returns a pointer to it. This function does not 58 | * check for free space in uwlkv_entries. 59 | * 60 | * @returns Pointer to an uwlkv_entry. 61 | */ 62 | uwlkv_entry * uwlkv_create_entry(void) 63 | { 64 | used_entries += 1; 65 | 66 | return &uwlkv_entries[used_entries - 1]; 67 | } 68 | 69 | /** 70 | * @brief Updates the entry information. Creates a new one if entry with provided key currently 71 | * not exist in map. 72 | * 73 | * @param key Entry with this specified key would be modified. 74 | * @param offset Logical offset of an entry in bytes. 75 | * 76 | * @returns An uwlkv_error. 77 | */ 78 | uwlkv_error uwlkv_update_entry(const uwlkv_key key, const uwlkv_offset offset) 79 | { 80 | uwlkv_entry *entry; 81 | if (UWLKV_E_NOT_EXIST == uwlkv_get_entry(key, &entry)) 82 | { 83 | if (0 == uwlkv_map_free_entries()) 84 | { 85 | return UWLKV_E_NO_SPACE; 86 | } 87 | 88 | entry = uwlkv_create_entry(); 89 | entry->key = key; 90 | } 91 | 92 | entry->offset = offset; 93 | 94 | return UWLKV_E_SUCCESS; 95 | } 96 | 97 | /** @brief Resets map state to default (not containing any entry) */ 98 | void uwlkv_reset_map(void) 99 | { 100 | used_entries = 0; 101 | } 102 | 103 | /** 104 | * @brief Returns a number of stored unique keys. 105 | * 106 | * @returns Number of entries. 107 | */ 108 | uwlkv_key uwlkv_get_used_entries(void) 109 | { 110 | return used_entries; 111 | } 112 | 113 | /** 114 | * @brief Returns a number of available unique keys. 115 | * 116 | * @returns Number of entries. 117 | */ 118 | uwlkv_key uwlkv_map_free_entries(void) 119 | { 120 | return UWLKV_MAX_ENTRIES - used_entries; 121 | } 122 | -------------------------------------------------------------------------------- /src/map.h: -------------------------------------------------------------------------------- 1 | #ifndef UWLKV_MAP_H 2 | #define UWLKV_MAP_H 3 | 4 | uwlkv_error uwlkv_get_entry(const uwlkv_key key, uwlkv_entry ** entry); 5 | uwlkv_entry * uwlkv_get_entry_by_id(const uwlkv_key number); 6 | uwlkv_entry * uwlkv_create_entry(void); 7 | uwlkv_error uwlkv_update_entry(const uwlkv_key key, const uwlkv_offset offset); 8 | void uwlkv_reset_map(void); 9 | uwlkv_key uwlkv_get_used_entries(void); 10 | uwlkv_key uwlkv_map_free_entries(void); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /src/storage.c: -------------------------------------------------------------------------------- 1 | /* This module handles a state of two areas of NVRAM: main and reserved. Main is used for normal 2 | * operations and reserved is used as a defragmented copy of main when wrap-around is performed 3 | * to have an ability to restore data in case of power loss. 4 | */ 5 | 6 | #include "uwlkv.h" 7 | #include "entry.h" 8 | #include "map.h" 9 | #include "storage.h" 10 | 11 | extern uwlkv_nvram_interface nvram_interface; 12 | static uwlkv_offset next_block; 13 | 14 | static uwlkv_nvram_state get_nvram_state(void); 15 | static void load_map(void); 16 | static void prepare_for_first_use(void); 17 | static void recover_after_iterrupted_main_erase(void); 18 | static void recover_after_interrupted_reserve_erase(void); 19 | static void prepare_area(uwlkv_area area); 20 | static void transfer_main_to_reserve(void); 21 | static void transfer_reserve_to_main(void); 22 | 23 | /** @brief Calculates current state of NVRAM and starts appropirate initialization procedure. */ 24 | void uwlkv_cold_boot(void) 25 | { 26 | uwlkv_reset_map(); 27 | 28 | const uwlkv_nvram_state nvram_state = get_nvram_state(); 29 | switch (nvram_state) 30 | { 31 | case UWLKV_S_CLEAN: 32 | load_map(); 33 | break; 34 | 35 | case UWLKV_S_BLANK: 36 | prepare_for_first_use(); 37 | break; 38 | 39 | case UWLKV_S_MAIN_ERASE_INTERRUPTED: 40 | recover_after_iterrupted_main_erase(); 41 | break; 42 | 43 | case UWLKV_S_RESERVE_ERASE_INTERRUPTED: 44 | recover_after_interrupted_reserve_erase(); 45 | break; 46 | 47 | default: 48 | prepare_for_first_use(); 49 | break; 50 | } 51 | } 52 | 53 | /** 54 | * @brief Scans a main area and indexes its content to uwlkv_entries. It uses linear search 55 | * and stops on a first free memory block. Block considered free if all of its bytes are 56 | * equal to UWLKV_ERASED_BYTE_VALUE. 57 | */ 58 | static void load_map(void) 59 | { 60 | uwlkv_reset_map(); 61 | 62 | uwlkv_offset offset; 63 | for (offset = UWLKV_METADATA_SIZE; 64 | (offset + UWLKV_ENTRY_SIZE) <= (nvram_interface.size - nvram_interface.reserved); 65 | offset += UWLKV_ENTRY_SIZE) 66 | { 67 | uwlkv_key key; 68 | uwlkv_value value; 69 | uwlkv_error ret = uwlkv_read_entry(offset, &key, &value); 70 | 71 | if (UWLKV_E_NOT_EXIST == ret) 72 | { 73 | break; 74 | } 75 | 76 | if (UWLKV_E_SUCCESS == ret) 77 | { 78 | uwlkv_update_entry(key, offset); 79 | } 80 | } 81 | next_block = offset; 82 | } 83 | 84 | static void prepare_for_first_use(void) 85 | { 86 | nvram_interface.erase_main(); 87 | nvram_interface.erase_reserve(); 88 | 89 | uint8_t main_metadata[UWLKV_METADATA_SIZE] = { UWLKV_NVRAM_ERASE_STARTED, UWLKV_NVRAM_ERASE_FINISHED }; 90 | nvram_interface.write(main_metadata, 0, UWLKV_METADATA_SIZE); 91 | 92 | next_block = UWLKV_METADATA_SIZE; 93 | } 94 | 95 | static void recover_after_iterrupted_main_erase(void) 96 | { 97 | nvram_interface.erase_main(); 98 | transfer_reserve_to_main(); 99 | prepare_area(UWLKV_RESERVED); 100 | } 101 | 102 | static void recover_after_interrupted_reserve_erase(void) 103 | { 104 | nvram_interface.erase_reserve(); 105 | load_map(); 106 | } 107 | 108 | /** 109 | * @brief Returns reserve data address with given offset 110 | * 111 | * @param offset The offset (0 means first byte of reserved area) 112 | * 113 | * @returns Absolute offset in NVRAM 114 | */ 115 | static inline uwlkv_offset get_reserve_offset(uwlkv_offset offset) 116 | { 117 | return nvram_interface.size - nvram_interface.reserved + offset; 118 | } 119 | 120 | static void transfer_reserve_to_main(void) 121 | { 122 | const uwlkv_offset reserve_offset = get_reserve_offset(0); 123 | 124 | uwlkv_offset offset; 125 | for (offset = UWLKV_METADATA_SIZE; 126 | (offset + UWLKV_ENTRY_SIZE) <= (nvram_interface.reserved); 127 | offset += UWLKV_ENTRY_SIZE) 128 | { 129 | uwlkv_key key; 130 | uwlkv_value value; 131 | uwlkv_error ret = uwlkv_read_entry(reserve_offset + offset, &key, &value); 132 | 133 | if (UWLKV_E_NOT_EXIST == ret) 134 | { 135 | break; 136 | } 137 | 138 | if (UWLKV_E_SUCCESS == ret) 139 | { 140 | uwlkv_write_entry(offset, key, value); 141 | uwlkv_update_entry(key, offset); 142 | } 143 | } 144 | 145 | next_block = offset; 146 | } 147 | 148 | static void transfer_main_to_reserve(void) 149 | { 150 | uwlkv_offset reserve_offset = get_reserve_offset(UWLKV_METADATA_SIZE); 151 | for(uwlkv_key i = 0; i < uwlkv_get_used_entries(); i++) 152 | { 153 | const uwlkv_entry * entry = uwlkv_get_entry_by_id(i); 154 | uwlkv_key key; 155 | uwlkv_value value; 156 | uwlkv_read_entry(entry->offset, &key, &value); 157 | uwlkv_write_entry(reserve_offset, key, value); 158 | reserve_offset += UWLKV_ENTRY_SIZE; 159 | } 160 | } 161 | 162 | /** 163 | * @brief Calculates current NVRAM state, checking for unclean shutdown 164 | * 165 | * @returns See uwlkv_nvram_state enum documentation. 166 | */ 167 | static uwlkv_nvram_state get_nvram_state(void) 168 | { 169 | uint8_t main_metadata[UWLKV_MINIMAL_SIZE]; 170 | uint8_t reserve_metadata[UWLKV_MINIMAL_SIZE]; 171 | nvram_interface.read(main_metadata, 0, UWLKV_MINIMAL_SIZE); 172 | nvram_interface.read(reserve_metadata, get_reserve_offset(0), UWLKV_MINIMAL_SIZE); 173 | 174 | const uint8_t main_started = UWLKV_NVRAM_ERASE_STARTED == reserve_metadata[UWLKV_O_ERASE_STARTED]; 175 | const uint8_t reserve_started = UWLKV_NVRAM_ERASE_STARTED == main_metadata[UWLKV_O_ERASE_STARTED]; 176 | const uint8_t main_finished = UWLKV_NVRAM_ERASE_FINISHED == reserve_metadata[UWLKV_O_ERASE_FINISHED]; 177 | const uint8_t reserve_finished = UWLKV_NVRAM_ERASE_FINISHED == main_metadata[UWLKV_O_ERASE_FINISHED]; 178 | const uint8_t main_clean = uwlkv_is_block_erased(main_metadata, UWLKV_MINIMAL_SIZE); 179 | const uint8_t reserve_clean = uwlkv_is_block_erased(reserve_metadata, UWLKV_MINIMAL_SIZE); 180 | 181 | if (reserve_finished && reserve_clean) 182 | { 183 | return UWLKV_S_CLEAN; 184 | } 185 | 186 | if ((main_started || main_finished) && !main_clean) 187 | { 188 | return UWLKV_S_MAIN_ERASE_INTERRUPTED; 189 | } 190 | 191 | if ( (reserve_finished && !reserve_clean) 192 | || (reserve_started && !reserve_finished) ) 193 | { 194 | return UWLKV_S_RESERVE_ERASE_INTERRUPTED; 195 | } 196 | 197 | return UWLKV_S_BLANK; 198 | } 199 | 200 | /** 201 | * @brief Erases specified area with progress indication in metadata. 202 | * 203 | * @param area Area to be erased (UWLKV_MAIN or UWLKV_RESERVED) 204 | */ 205 | static void prepare_area(uwlkv_area area) 206 | { 207 | uint8_t operation_flag = UWLKV_NVRAM_ERASE_STARTED; 208 | uwlkv_offset base_address = get_reserve_offset(0); 209 | uwlkv_erase erase_function = nvram_interface.erase_main; 210 | 211 | if (UWLKV_RESERVED == area) 212 | { 213 | base_address = 0; 214 | erase_function = nvram_interface.erase_reserve; 215 | } 216 | 217 | nvram_interface.write(&operation_flag, base_address, 1); 218 | erase_function(); 219 | operation_flag = UWLKV_NVRAM_ERASE_FINISHED; 220 | nvram_interface.write(&operation_flag, base_address + 1, 1); 221 | } 222 | 223 | /** 224 | * @brief Performs a backup of all parameters to reserved area, erases main and starts writing 225 | * from beginning. All data would be defragmented as a result. 226 | */ 227 | static void restart_map(void) 228 | { 229 | transfer_main_to_reserve(); 230 | prepare_area(UWLKV_MAIN); 231 | transfer_reserve_to_main(); 232 | prepare_area(UWLKV_RESERVED); 233 | } 234 | 235 | /** 236 | * @brief Reserves memory for one data block and returns an offset to its first byte. If all 237 | * NVRAM is used, it would be erased. 238 | * 239 | * @returns Starting position of new block. 240 | */ 241 | uwlkv_offset uwlkv_get_next_block(void) 242 | { 243 | if ((next_block + UWLKV_ENTRY_SIZE) > (nvram_interface.size - nvram_interface.reserved)) 244 | { 245 | restart_map(); 246 | } 247 | 248 | next_block += UWLKV_ENTRY_SIZE; 249 | 250 | return next_block - (uwlkv_offset)UWLKV_ENTRY_SIZE; 251 | } 252 | -------------------------------------------------------------------------------- /src/storage.h: -------------------------------------------------------------------------------- 1 | #ifndef UWLKV_STORAGE_H 2 | #define UWLKV_STORAGE_H 3 | 4 | void uwlkv_cold_boot(void); 5 | uwlkv_offset uwlkv_get_next_block(void); 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /src/uwlkv.c: -------------------------------------------------------------------------------- 1 | #include "uwlkv.h" 2 | #include "entry.h" 3 | #include "map.h" 4 | #include "storage.h" 5 | 6 | uwlkv_nvram_interface nvram_interface; 7 | uint8_t uwlkv_initialized = 0; 8 | 9 | /** 10 | * @brief Reads NVRAM content and builds its map 11 | * 12 | * @param [in] interface NVRAM access insterface. 13 | * 14 | * @returns - NVRAM capacity in entries. This value, divided by UWLKV_MAX_ENTRIES gives you an 15 | * expected leveling factor or write cycles multiplier. 16 | * - 0 if NVRAM size is too small to fit all entries. 17 | */ 18 | uwlkv_offset uwlkv_init(const uwlkv_nvram_interface * interface) 19 | { 20 | const uwlkv_offset main_size = interface->size - interface->reserved; 21 | const uwlkv_offset reserve_capacity = interface->reserved / UWLKV_ENTRY_SIZE; 22 | const uwlkv_offset main_capacity = main_size / UWLKV_ENTRY_SIZE; 23 | 24 | const uint8_t reserve_size_wrong = interface->reserved >= interface->size; 25 | const uint8_t main_smaller_reserve = main_capacity < reserve_capacity; 26 | 27 | if ( (reserve_size_wrong) 28 | || (main_smaller_reserve) 29 | || (main_capacity <= UWLKV_MAX_ENTRIES) 30 | || (reserve_capacity <= UWLKV_MAX_ENTRIES) ) 31 | { 32 | return 0; 33 | } 34 | 35 | nvram_interface = *interface; 36 | 37 | uwlkv_cold_boot(); 38 | 39 | uwlkv_initialized = 1; 40 | 41 | return main_capacity; 42 | } 43 | 44 | /** 45 | * @brief Get value of specifiend key 46 | * 47 | * @param key The key. 48 | * @param [out] value Read value if success. 49 | * 50 | * @returns UWLKV_E_SUCCESS on sucesseful read. 51 | */ 52 | uwlkv_error uwlkv_get_value(uwlkv_key key, uwlkv_value * value) 53 | { 54 | if (0 == uwlkv_initialized) 55 | { 56 | return UWLKV_E_NOT_STARTED; 57 | } 58 | 59 | uwlkv_entry * entry; 60 | if (uwlkv_get_entry(key, &entry)) 61 | { 62 | return UWLKV_E_NOT_EXIST; 63 | } 64 | 65 | return uwlkv_read_entry(entry->offset, &key, value); 66 | } 67 | 68 | /** 69 | * @brief Set value of specified key. 70 | * 71 | * @param key The key. 72 | * @param value Value to be written. 73 | * 74 | * @returns UWLKV_E_SUCCESS on sucesseful read. 75 | */ 76 | uwlkv_error uwlkv_set_value(uwlkv_key key, uwlkv_value value) 77 | { 78 | if (0 == uwlkv_initialized) 79 | { 80 | return UWLKV_E_NOT_STARTED; 81 | } 82 | 83 | uwlkv_offset offset = uwlkv_get_next_block(); 84 | uwlkv_entry *entry; 85 | if (UWLKV_E_NOT_EXIST == uwlkv_get_entry(key, &entry) 86 | && (0 == uwlkv_map_free_entries())) 87 | { 88 | return UWLKV_E_NO_SPACE; 89 | } 90 | 91 | uwlkv_error write = uwlkv_write_entry(offset, key, value); 92 | if (UWLKV_E_SUCCESS == write) 93 | { 94 | uwlkv_update_entry(key, offset); 95 | } 96 | 97 | return write; 98 | } 99 | 100 | /** 101 | * @brief Returns number of unique key values in use. 102 | * 103 | * @returns Number of keys. 104 | */ 105 | uwlkv_key uwlkv_get_entries_number(void) 106 | { 107 | return uwlkv_get_used_entries(); 108 | } 109 | 110 | /** 111 | * @brief Returns number of free unique key values. 112 | * 113 | * @returns Number of keys. 114 | */ 115 | uwlkv_key uwlkv_get_free_entries(void) 116 | { 117 | return uwlkv_map_free_entries(); 118 | } 119 | -------------------------------------------------------------------------------- /tests/nvram_mock.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "nvram_mock.h" 7 | 8 | static uint8_t flash_memory[FLASH_REGION_SIZE]; 9 | static mock_nvram_erase main_erase_status, reserve_erase_status; 10 | static bool write_enabled = true; 11 | 12 | void mock_nvram_init(void) 13 | { 14 | memset(flash_memory, 0xFF, FLASH_REGION_SIZE); 15 | 16 | main_erase_status = ERASE_ENABLED; 17 | reserve_erase_status = ERASE_ENABLED; 18 | } 19 | 20 | int mock_flash_read(uint8_t * data, uint32_t start, uint32_t length) 21 | { 22 | if ((start + length) > FLASH_REGION_SIZE) 23 | { 24 | return 1; 25 | } 26 | 27 | memcpy(data, flash_memory + start, length); 28 | return 0; 29 | } 30 | 31 | int mock_flash_write(uint8_t * data, uint32_t start, uint32_t length) 32 | { 33 | if (!write_enabled) { 34 | return 2; 35 | } 36 | 37 | if ((start + length) > FLASH_REGION_SIZE) 38 | { 39 | return 1; 40 | } 41 | 42 | /* Real flash memory should be erased before writing. To simulate this, 43 | * we temporarily read a requested block and check that it filled with 0xFF */ 44 | uint8_t * tmp_data = (uint8_t *)alloca(length); 45 | mock_flash_read(tmp_data, start, length); 46 | for (uint32_t i = 0; i < length; i++) 47 | { 48 | if (tmp_data[i] != 0xFF) 49 | { 50 | return 2; 51 | } 52 | } 53 | 54 | memcpy(flash_memory + start, data, length); 55 | return 0; 56 | } 57 | 58 | // Prohibits write operations by `mock_flash_write()`. It will always return an error. 59 | void mock_nvram_disable_write(void) 60 | { 61 | write_enabled = false; 62 | } 63 | 64 | void mock_nvram_enable_write(void) 65 | { 66 | write_enabled = true; 67 | } 68 | 69 | int mock_flash_erase_main(void) 70 | { 71 | if (ERASE_ENABLED == main_erase_status) 72 | { 73 | memset(flash_memory, 0xFF, FLASH_REGION_SIZE - FLASH_RESERVE_SIZE); 74 | } 75 | 76 | return 0; 77 | } 78 | 79 | int mock_flash_erase_reserve(void) 80 | { 81 | if (ERASE_ENABLED == reserve_erase_status) 82 | { 83 | memset(flash_memory + (FLASH_REGION_SIZE - FLASH_RESERVE_SIZE), 84 | 0xFF, FLASH_RESERVE_SIZE); 85 | } 86 | 87 | return 0; 88 | } 89 | 90 | void mock_flash_set(mock_nvram_area area, uint32_t offset, uint8_t value) 91 | { 92 | if (RESERVED_AREA == area) 93 | { 94 | offset += FLASH_REGION_SIZE - FLASH_RESERVE_SIZE; 95 | } 96 | 97 | flash_memory[offset] = value; 98 | } 99 | 100 | void mock_flash_fill_with_random(mock_nvram_area area) 101 | { 102 | uint32_t offset; 103 | uint32_t end; 104 | 105 | if (MAIN_AREA == area) 106 | { 107 | offset = 0; 108 | end = FLASH_REGION_SIZE - FLASH_RESERVE_SIZE; 109 | } 110 | else 111 | { 112 | offset = FLASH_REGION_SIZE - FLASH_RESERVE_SIZE; 113 | end = FLASH_REGION_SIZE; 114 | } 115 | 116 | for(; offset < end; offset++) 117 | { 118 | flash_memory[offset] = (uint8_t)(rand() % 255); 119 | } 120 | } 121 | 122 | void mock_flash_set_erase(mock_nvram_area area, mock_nvram_erase state) 123 | { 124 | if (MAIN_AREA == area) 125 | { 126 | main_erase_status = state; 127 | } 128 | else 129 | { 130 | reserve_erase_status = state; 131 | } 132 | } -------------------------------------------------------------------------------- /tests/nvram_mock.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define FLASH_REGION_SIZE (512) 4 | #define FLASH_RESERVE_SIZE (256) 5 | 6 | typedef enum 7 | { 8 | MAIN_AREA, 9 | RESERVED_AREA 10 | } mock_nvram_area; 11 | 12 | typedef enum 13 | { 14 | ERASE_DISABLED, 15 | ERASE_ENABLED 16 | } mock_nvram_erase; 17 | 18 | void mock_nvram_init(void); 19 | 20 | int mock_flash_read(uint8_t * data, uint32_t start, uint32_t length); 21 | int mock_flash_write(uint8_t * data, uint32_t start, uint32_t length); 22 | void mock_nvram_disable_write(void); 23 | void mock_nvram_enable_write(void); 24 | int mock_flash_erase_main(void); 25 | int mock_flash_erase_reserve(void); 26 | 27 | void mock_flash_set(mock_nvram_area area, uint32_t offset, uint8_t value); 28 | void mock_flash_fill_with_random(mock_nvram_area area); 29 | void mock_flash_set_erase(mock_nvram_area area, mock_nvram_erase state); 30 | -------------------------------------------------------------------------------- /tests/tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "nvram_mock.h" 7 | #include "uwlkv.h" 8 | 9 | inline std::ostream &operator<<(std::ostream &os, uwlkv_error e) 10 | { 11 | switch (e) 12 | { 13 | case UWLKV_E_SUCCESS: 14 | return os << "OK"; 15 | case UWLKV_E_NOT_EXIST: 16 | return os << "Requested entry does not exist on NVRAM"; 17 | case UWLKV_E_NVRAM_ERROR: 18 | return os << "NVRAM interface signalled an error during the operation"; 19 | case UWLKV_E_NOT_STARTED: 20 | return os << "UWLKV haven't been initialized"; 21 | case UWLKV_E_NO_SPACE: 22 | return os << "No free space in map for new entry"; 23 | case UWLKV_E_WRONG_OFFSET: 24 | return os << "Provided offset is out of NVRAM bounds"; 25 | default: 26 | return os << "uwlkv_error(" << e << ")"; 27 | } 28 | } 29 | 30 | #include 31 | #include 32 | 33 | uwlkv_offset init_uwlkv(uwlkv_offset size, uwlkv_offset reserved) 34 | { 35 | uwlkv_nvram_interface interface; 36 | interface.read = &mock_flash_read; 37 | interface.write = &mock_flash_write; 38 | interface.erase_main = &mock_flash_erase_main; 39 | interface.erase_reserve = &mock_flash_erase_reserve; 40 | /* NVRAM size and reserved space should always match actual sizes of memory 41 | * which your erase function uses. Here we have an option to override default 42 | * only for test purposes */ 43 | interface.size = FLASH_REGION_SIZE; 44 | interface.reserved = FLASH_RESERVE_SIZE; 45 | 46 | if (size) 47 | { 48 | interface.size = size < FLASH_REGION_SIZE 49 | ? size : FLASH_REGION_SIZE; 50 | } 51 | 52 | if (reserved) 53 | { 54 | interface.reserved = reserved != FLASH_RESERVE_SIZE 55 | ? reserved : FLASH_RESERVE_SIZE; 56 | } 57 | 58 | return uwlkv_init(&interface); 59 | } 60 | 61 | uwlkv_offset erase_nvram(uwlkv_offset size, uwlkv_offset reserved) 62 | { 63 | mock_nvram_init(); 64 | 65 | return init_uwlkv(size, reserved); 66 | } 67 | 68 | TEST_CASE("Initialization", "[init]") 69 | { 70 | auto ret = erase_nvram(100, 90); 71 | CHECK(0 == ret); 72 | ret = init_uwlkv(0, 0); 73 | CHECK(((FLASH_REGION_SIZE - FLASH_RESERVE_SIZE) / UWLKV_ENTRY_SIZE) == ret); 74 | 75 | auto entries = uwlkv_get_entries_number(); 76 | CHECK(0 == entries); 77 | auto free = uwlkv_get_free_entries(); 78 | CHECK(UWLKV_MAX_ENTRIES == free); 79 | } 80 | 81 | TEST_CASE("Writing and reading values", "[read_write]") 82 | { 83 | SECTION("Easy values") 84 | { 85 | auto test_key = GENERATE(as{}, 0, 10, 100, 40000); 86 | auto test_value = GENERATE(100, 1000, 65000, 0); 87 | 88 | auto ret = uwlkv_set_value(test_key, test_value); 89 | CHECK(UWLKV_E_SUCCESS == ret); 90 | 91 | uwlkv_value read_value; 92 | ret = uwlkv_get_value(test_key, &read_value); 93 | CHECK(UWLKV_E_SUCCESS == ret); 94 | CHECK(read_value == test_value); 95 | } 96 | 97 | SECTION("Counters") 98 | { 99 | auto entries = uwlkv_get_entries_number(); 100 | CHECK(4 == entries); 101 | } 102 | 103 | SECTION("Failed write") 104 | { 105 | auto start = uwlkv_get_entries_number(); 106 | mock_nvram_disable_write(); 107 | uwlkv_value initial, actual; 108 | 109 | // Existing value 110 | CHECK(uwlkv_get_value(0, &initial) == UWLKV_E_SUCCESS); 111 | CHECK(UWLKV_E_NVRAM_ERROR == uwlkv_set_value(0, initial + 1)); 112 | CHECK(uwlkv_get_value(0, &actual) == UWLKV_E_SUCCESS); 113 | CHECK(initial == actual); 114 | 115 | // New value 116 | CHECK(uwlkv_get_value(1, &initial) == UWLKV_E_NOT_EXIST); 117 | CHECK(UWLKV_E_NVRAM_ERROR == uwlkv_set_value(1, initial + 1)); 118 | CHECK(uwlkv_get_value(1, &actual) == UWLKV_E_NOT_EXIST); 119 | 120 | CHECK(uwlkv_get_entries_number() == start); 121 | mock_nvram_enable_write(); 122 | } 123 | 124 | SECTION("Using all keys") 125 | { 126 | uwlkv_value test_value = 0; 127 | for(uwlkv_key key = 0; key < UWLKV_MAX_ENTRIES; key++) 128 | { 129 | test_value = key + 10000; 130 | uwlkv_set_value(key, test_value); 131 | } 132 | auto free = uwlkv_get_free_entries(); 133 | CHECK(0 == free); 134 | 135 | // Existing key 136 | auto ret = uwlkv_set_value(1, test_value); 137 | CHECK(UWLKV_E_SUCCESS == ret); 138 | // New one 139 | ret = uwlkv_set_value(UWLKV_MAX_ENTRIES, test_value); 140 | CHECK(UWLKV_E_NO_SPACE == ret); 141 | } 142 | } 143 | 144 | uint8_t compare_stored_values(std::map &map) 145 | { 146 | auto errors = 0; 147 | for (auto const& entry : map) 148 | { 149 | uwlkv_value value; 150 | uwlkv_get_value(entry.first, &value); 151 | if (value != entry.second) 152 | { 153 | errors += 1; 154 | } 155 | } 156 | 157 | return (errors != 0); 158 | } 159 | 160 | void fill_main(std::map &map, uwlkv_offset number, uwlkv_value starting_value) 161 | { 162 | for(uwlkv_offset i = 0; i < number; i++) 163 | { 164 | const uwlkv_key key = (uwlkv_key)(i % UWLKV_MAX_ENTRIES); 165 | const uwlkv_value value = (uwlkv_value)i + starting_value; 166 | if (UWLKV_E_SUCCESS == uwlkv_set_value(key, value)) 167 | { 168 | map[key] = value; 169 | } 170 | } 171 | } 172 | 173 | TEST_CASE("Data wraps", "[wraps]") 174 | { 175 | const auto capacity = erase_nvram(0, 0); 176 | std::map values; 177 | 178 | // Prepare a state where main area is fully filled 179 | fill_main(values, capacity, 0); 180 | 181 | const auto entries = uwlkv_get_entries_number(); 182 | CHECK(entries == values.size()); 183 | CHECK(entries == UWLKV_MAX_ENTRIES); 184 | 185 | SECTION("No wrap") 186 | { 187 | init_uwlkv(0, 0); 188 | CHECK(0 == compare_stored_values(values)); 189 | } 190 | 191 | SECTION("Basic wrap") 192 | { 193 | // Add one value to force a rewrite 194 | uwlkv_set_value(10, 10000); 195 | values[10] = 10000; 196 | init_uwlkv(0, 0); 197 | CHECK(0 == compare_stored_values(values)); 198 | } 199 | 200 | SECTION("Interrupted erase of main area") 201 | { 202 | mock_flash_set_erase(RESERVED_AREA, ERASE_DISABLED); 203 | uwlkv_set_value(10, 10000); 204 | 205 | mock_flash_set_erase(RESERVED_AREA, ERASE_ENABLED); 206 | mock_flash_fill_with_random(MAIN_AREA); 207 | mock_flash_set(RESERVED_AREA, UWLKV_O_ERASE_STARTED, UWLKV_NVRAM_ERASE_STARTED); 208 | mock_flash_set(RESERVED_AREA, UWLKV_O_ERASE_FINISHED, UWLKV_ERASED_BYTE_VALUE); 209 | 210 | init_uwlkv(0, 0); 211 | CHECK(0 == compare_stored_values(values)); 212 | } 213 | 214 | SECTION("Interrupted erase of reserved area") 215 | { 216 | mock_flash_fill_with_random(RESERVED_AREA); 217 | mock_flash_set(MAIN_AREA, UWLKV_O_ERASE_STARTED, UWLKV_NVRAM_ERASE_STARTED); 218 | mock_flash_set(MAIN_AREA, UWLKV_O_ERASE_FINISHED, UWLKV_ERASED_BYTE_VALUE); 219 | 220 | init_uwlkv(0, 0); 221 | CHECK(0 == compare_stored_values(values)); 222 | } 223 | 224 | SECTION("Interrupted transfer from main to reserve") 225 | { 226 | /* Power loss is simulated by filling reserved area with random data but not 227 | * setting flags MAIN_ERASE_STARTED and MAIN_ERASE_FINISHED. Therefore, library 228 | * should discard data in reserved area and erase it */ 229 | mock_flash_fill_with_random(RESERVED_AREA); 230 | mock_flash_set(MAIN_AREA, UWLKV_O_ERASE_STARTED, UWLKV_NVRAM_ERASE_STARTED); 231 | mock_flash_set(MAIN_AREA, UWLKV_O_ERASE_FINISHED, UWLKV_NVRAM_ERASE_FINISHED); 232 | mock_flash_set(RESERVED_AREA, UWLKV_O_ERASE_STARTED, UWLKV_ERASED_BYTE_VALUE); 233 | mock_flash_set(RESERVED_AREA, UWLKV_O_ERASE_FINISHED, UWLKV_ERASED_BYTE_VALUE); 234 | 235 | init_uwlkv(0, 0); 236 | CHECK(0 == compare_stored_values(values)); 237 | } 238 | 239 | SECTION("Interrupted tranfer from reserve to main") 240 | { 241 | mock_flash_set_erase(RESERVED_AREA, ERASE_DISABLED); 242 | uwlkv_set_value(10, 10000); 243 | 244 | mock_flash_set_erase(RESERVED_AREA, ERASE_ENABLED); 245 | mock_flash_fill_with_random(MAIN_AREA); 246 | mock_flash_set(MAIN_AREA, UWLKV_O_ERASE_STARTED, UWLKV_ERASED_BYTE_VALUE); 247 | mock_flash_set(MAIN_AREA, UWLKV_O_ERASE_FINISHED, UWLKV_ERASED_BYTE_VALUE); 248 | mock_flash_set(RESERVED_AREA, UWLKV_O_ERASE_STARTED, UWLKV_NVRAM_ERASE_STARTED); 249 | mock_flash_set(RESERVED_AREA, UWLKV_O_ERASE_FINISHED, UWLKV_NVRAM_ERASE_FINISHED); 250 | 251 | init_uwlkv(0, 0); 252 | CHECK(0 == compare_stored_values(values)); 253 | } 254 | 255 | // Finally making sure that library works normally after recovery 256 | fill_main(values, UWLKV_MAX_ENTRIES, 100); 257 | CHECK(0 == compare_stored_values(values)); 258 | 259 | init_uwlkv(0, 0); 260 | CHECK(0 == compare_stored_values(values)); 261 | fill_main(values, (capacity * 2), 10000); 262 | CHECK(0 == compare_stored_values(values)); 263 | init_uwlkv(0, 0); 264 | CHECK(0 == compare_stored_values(values)); 265 | } --------------------------------------------------------------------------------