/***********************************************************************************************//** * \file mtb_kvstore.c * * \brief * Utility library for storing key value pairs in memory. * *************************************************************************************************** * \copyright * Copyright 2018-2022 Cypress Semiconductor Corporation (an Infineon company) or * an affiliate of Cypress Semiconductor Corporation * * 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 #include #ifdef NIMBLE_SPARK_SUP #include "app_config_spark.h" #else #include "app_config_origin.h" #endif #include "mtb_kvstore.h" //#include "cy_utils.h" /***************************** free rtos start********************************/ //-------------------------------------------------------------------------------------------------- // convert_ms_to_ticks //-------------------------------------------------------------------------------------------------- cy_time_t convert_ms_to_ticks(cy_time_t timeout_ms) { // Don't convert away from CY_RTOS_NEVER_TIMEOUT due to it being max value // Also, skip conversion if timeout_ms is 0 if (timeout_ms == CY_RTOS_NEVER_TIMEOUT) { return CY_RTOS_NEVER_TIMEOUT; } else if (timeout_ms == 0) { return 0; } else { // Convert using our conversion to avoid overflow uint64_t ticks = ((uint64_t)(((uint64_t)(timeout_ms) * (uint64_t)configTICK_RATE_HZ) / (uint64_t)1000)); if (ticks >= UINT32_MAX) { // if ticks if more than 32 bits, change ticks to max possible value // that isn't CY_RTOS_NEVER_TIMEOUT. ticks = UINT32_MAX - 1; } return (cy_time_t)ticks; } } //-------------------------------------------------------------------------------------------------- // cy_rtos_mutex_init //-------------------------------------------------------------------------------------------------- cy_rslt_t cy_rtos_mutex_init(cy_mutex_t* mutex, bool recursive) { cy_rslt_t status; if (mutex == NULL) { status = CY_RTOS_BAD_PARAM; } else { mutex->is_recursive = recursive; mutex->mutex_handle = recursive ? xSemaphoreCreateRecursiveMutex() : xSemaphoreCreateMutex(); if (mutex->mutex_handle == NULL) { status = CY_RTOS_NO_MEMORY; } else { status = CY_RSLT_SUCCESS; } } return status; } //-------------------------------------------------------------------------------------------------- // cy_rtos_mutex_get //-------------------------------------------------------------------------------------------------- cy_rslt_t cy_rtos_mutex_get(cy_mutex_t* mutex, cy_time_t timeout_ms) { cy_rslt_t status; if (mutex == NULL) { status = CY_RTOS_BAD_PARAM; } else { TickType_t ticks = convert_ms_to_ticks(timeout_ms); BaseType_t result = (mutex->is_recursive) ? xSemaphoreTakeRecursive(mutex->mutex_handle, ticks) : xSemaphoreTake(mutex->mutex_handle, ticks); status = (result == pdFALSE) ? CY_RTOS_TIMEOUT : CY_RSLT_SUCCESS; } return status; } //-------------------------------------------------------------------------------------------------- // cy_rtos_mutex_set //-------------------------------------------------------------------------------------------------- cy_rslt_t cy_rtos_mutex_set(cy_mutex_t* mutex) { cy_rslt_t status; if (mutex == NULL) { status = CY_RTOS_BAD_PARAM; } else { BaseType_t result = (mutex->is_recursive) ? xSemaphoreGiveRecursive(mutex->mutex_handle) : xSemaphoreGive(mutex->mutex_handle); status = (result == pdFALSE) ? CY_RTOS_GENERAL_ERROR : CY_RSLT_SUCCESS; } return status; } //-------------------------------------------------------------------------------------------------- // cy_rtos_mutex_deinit //-------------------------------------------------------------------------------------------------- cy_rslt_t cy_rtos_mutex_deinit(cy_mutex_t* mutex) { cy_rslt_t status; if (mutex == NULL) { status = CY_RTOS_BAD_PARAM; } else { vSemaphoreDelete(mutex->mutex_handle); status = CY_RSLT_SUCCESS; } return status; } /***************************** free rtos end ********************************/ #define _MTB_KVSTORE_MIN_BUFF_SIZE (128U) #define _MTB_KVSTORE_HEADER_MAGIC (0xFACEFACEU) #define _MTB_KVSTORE_FORMAT_VERSION (0U) #define _MTB_KVSTORE_INITIAL_AREA_VERSION (1U) #define _MTB_KVSTORE_DELETE_FLAG (1U << 7) #define _MTB_KVSTORE_NO_FLAG (0U) #define _MTB_KVSTORE_INIT_MAX_KEYS (32U) #define _MTB_KVSTORE_AREA_SIZE(obj) (((obj)->length) / 2) #define _MTB_KVSTORE_AREA_HEADER_OFFSET (0U) #define _MTB_KVSTORE_CRC_INIT_VAL (0xFFFFU) /***************************** Internal Data Structures ********************************/ // Note: If the following structure is changed the _mtb_kvstore_get_header_crc function // must be changed to adjust the CRC calculation accordingly. typedef struct { uint32_t magic; /* A constant value, for quick validity checking. */ uint8_t format_version; /* Version of the record format */ uint8_t flags; /* Used to mark a record deleted. */ uint16_t header_size; /* Size of the header */ uint16_t key_size; /* Size of the key */ uint32_t data_size; /* Size of the data */ uint32_t crc; /* A 16-bit CRC, calculated on header (except CRC), key and data. */ } _mtb_kvstore_record_header_t; typedef struct { uint16_t version; /* Version of the area. Use to check if area is the active area */ uint16_t format_version; /* Version of the data format in the area header */ } _mtb_kvstore_area_record_data_t; typedef enum { _MTB_KVSTORE_OPER_ADD, _MTB_KVSTORE_OPER_DELETE, _MTB_KVSTORE_OPER_UPDATE } _mtb_kvstore_operation_t; typedef struct { uint32_t ram_tbl_idx; mtb_kvstore_ram_table_entry_t entry; } _mtb_kvstore_update_ram_table_info_t; typedef struct { uint32_t old_record_size; uint32_t new_record_size; } _mtb_kvstore_update_consumed_size_info_t; typedef struct { const char* key; const uint8_t* data; uint32_t data_size; uint16_t key_hash; } _mtb_kvstore_update_record_info_t; typedef struct { uint32_t ram_tbl_idx; _mtb_kvstore_update_consumed_size_info_t consumed_size_info; const _mtb_kvstore_update_record_info_t* update_rec_info; } _mtb_kvstore_record_info_t; static const char* _mtb_kvstore_area_rec_key = "MTBAREAIDX"; /*************************** Internal Helper Functions *****************************/ #if defined(CY_RTOS_AWARE) || defined(COMPONENT_RTOS_AWARE) //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_initlock //-------------------------------------------------------------------------------------------------- static cy_rslt_t _mtb_kvstore_initlock(mtb_kvstore_t* obj) { return cy_rtos_init_mutex(&(obj->mtb_kvstore_mutex)); } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_lock //-------------------------------------------------------------------------------------------------- static cy_rslt_t _mtb_kvstore_lock(mtb_kvstore_t* obj) { return cy_rtos_get_mutex(&(obj->mtb_kvstore_mutex), MTB_KVSTORE_MUTEX_TIMEOUT_MS); } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_lock_wait_forever //-------------------------------------------------------------------------------------------------- static void _mtb_kvstore_lock_wait_forever(mtb_kvstore_t* obj) { cy_rslt_t result = cy_rtos_get_mutex(&(obj->mtb_kvstore_mutex), CY_RTOS_NEVER_TIMEOUT); CY_ASSERT(result == CY_RSLT_SUCCESS); CY_UNUSED_PARAMETER(result); } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_unlock //-------------------------------------------------------------------------------------------------- static void _mtb_kvstore_unlock(mtb_kvstore_t* obj) { cy_rslt_t result = cy_rtos_set_mutex(&(obj->mtb_kvstore_mutex)); CY_ASSERT(result == CY_RSLT_SUCCESS); CY_UNUSED_PARAMETER(result); } #else // if defined(CY_RTOS_AWARE) || defined(COMPONENT_RTOS_AWARE) //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_initlock //-------------------------------------------------------------------------------------------------- static cy_rslt_t _mtb_kvstore_initlock(mtb_kvstore_t* obj) { CY_UNUSED_PARAMETER(obj); return CY_RSLT_SUCCESS; } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_lock //-------------------------------------------------------------------------------------------------- static cy_rslt_t _mtb_kvstore_lock(mtb_kvstore_t* obj) { CY_UNUSED_PARAMETER(obj); return CY_RSLT_SUCCESS; } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_lock_wait_forever //-------------------------------------------------------------------------------------------------- static void _mtb_kvstore_lock_wait_forever(mtb_kvstore_t* obj) { CY_UNUSED_PARAMETER(obj); } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_unlock //-------------------------------------------------------------------------------------------------- static void _mtb_kvstore_unlock(mtb_kvstore_t* obj) { CY_UNUSED_PARAMETER(obj); } #endif // if defined(CY_RTOS_AWARE) || defined(COMPONENT_RTOS_AWARE) //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_is_valid_key //-------------------------------------------------------------------------------------------------- static inline bool _mtb_kvstore_is_valid_key(const char* key) { if (NULL != key) { uint32_t key_size = strlen(key); return (key_size > 0) && (key_size < MTB_KVSTORE_MAX_KEY_SIZE); } return false; } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_align_up //-------------------------------------------------------------------------------------------------- static inline uint32_t _mtb_kvstore_align_up(uint32_t val, uint32_t size) { return (((val - 1) / size) + 1) * size; } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_is_aligned //-------------------------------------------------------------------------------------------------- static inline bool _mtb_kvstore_is_aligned(uint32_t val, uint32_t size) { return ((val & (size - 1)) == 0); } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_get_record_size //-------------------------------------------------------------------------------------------------- static uint32_t _mtb_kvstore_get_record_size(mtb_kvstore_t* obj, uint32_t record_offset, uint32_t key_size, uint32_t data_size) { uint32_t prog_size = obj->bd->program_size(obj->bd->context, record_offset); return _mtb_kvstore_align_up(sizeof(_mtb_kvstore_record_header_t) + key_size + data_size, prog_size); } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_get_area_header_record_size //-------------------------------------------------------------------------------------------------- static inline uint32_t _mtb_kvstore_get_area_header_record_size(mtb_kvstore_t* obj, uint32_t area_address) { return _mtb_kvstore_get_record_size(obj, area_address, strlen(_mtb_kvstore_area_rec_key), sizeof(_mtb_kvstore_area_record_data_t)); } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_increment_max_keys //-------------------------------------------------------------------------------------------------- uint16_t _mtb_kvstore_crc16(const uint8_t* data, uint32_t length, uint16_t init_crc) { //Using CRC-16/CCITT-0 Algorithm uint32_t crc = init_crc; int i; while (length--) { crc ^= ((uint32_t)*data++) << 8; for (i = 0; i < 8; i++) { crc = (crc & 0x8000) ? (crc << 1) ^ 0x1021 : (crc << 1); } } return crc & 0xffff; } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_increment_max_keys //-------------------------------------------------------------------------------------------------- static cy_rslt_t _mtb_kvstore_increment_max_keys(mtb_kvstore_t* obj) { cy_rslt_t result = CY_RSLT_SUCCESS; uint32_t new_entry_count = obj->max_entries * 2; mtb_kvstore_ram_table_entry_t* new_table = (mtb_kvstore_ram_table_entry_t*)pvPortMalloc( new_entry_count * sizeof(mtb_kvstore_ram_table_entry_t)); if (new_table != NULL) { memcpy(new_table, obj->ram_table, obj->max_entries * sizeof(mtb_kvstore_ram_table_entry_t)); free(obj->ram_table); obj->ram_table = new_table; obj->max_entries = new_entry_count; } else { result = MTB_KVSTORE_MEM_ALLOC_ERROR; } return result; } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_update_ram_table //-------------------------------------------------------------------------------------------------- static void _mtb_kvstore_update_ram_table(mtb_kvstore_t* obj, _mtb_kvstore_operation_t operation, const _mtb_kvstore_update_ram_table_info_t* info) { switch (operation) { case _MTB_KVSTORE_OPER_DELETE: CY_ASSERT(info->ram_tbl_idx < obj->num_entries); obj->num_entries--; if (info->ram_tbl_idx < obj->num_entries) { memmove(&obj->ram_table[info->ram_tbl_idx], &obj->ram_table[info->ram_tbl_idx + 1], (obj->num_entries - info->ram_tbl_idx) * sizeof(mtb_kvstore_ram_table_entry_t)); } break; case _MTB_KVSTORE_OPER_ADD: CY_ASSERT(obj->num_entries < obj->max_entries); CY_ASSERT(info->ram_tbl_idx <= obj->num_entries); if (info->ram_tbl_idx < obj->num_entries) { memmove(&obj->ram_table[info->ram_tbl_idx + 1], &obj->ram_table[info->ram_tbl_idx], (obj->num_entries - info->ram_tbl_idx) * sizeof(mtb_kvstore_ram_table_entry_t)); } obj->num_entries++; obj->ram_table[info->ram_tbl_idx].hash = info->entry.hash; obj->ram_table[info->ram_tbl_idx].offset = info->entry.offset; break; case _MTB_KVSTORE_OPER_UPDATE: obj->ram_table[info->ram_tbl_idx].hash = info->entry.hash; obj->ram_table[info->ram_tbl_idx].offset = info->entry.offset; break; default: CY_ASSERT(false); } } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_update_consumed_size //-------------------------------------------------------------------------------------------------- static void _mtb_kvstore_update_consumed_size(mtb_kvstore_t* obj, _mtb_kvstore_operation_t operation, const _mtb_kvstore_update_consumed_size_info_t* info) { switch (operation) { case _MTB_KVSTORE_OPER_DELETE: obj->consumed_size -= info->old_record_size; break; case _MTB_KVSTORE_OPER_UPDATE: obj->consumed_size = obj->consumed_size - info->old_record_size + info->new_record_size; break; case _MTB_KVSTORE_OPER_ADD: obj->consumed_size += info->new_record_size; break; default: CY_ASSERT(false); } } //-------------------------------------------------------------------------------------------------- //_mtb_kvstore_erase_area //-------------------------------------------------------------------------------------------------- static cy_rslt_t _mtb_kvstore_erase_area(mtb_kvstore_t* obj, uint32_t area_address) { // This function operates on the assumption that erasing a sector is atomic. We first erase // everything but the first sector. uint32_t erase_size = obj->bd->erase_size(obj->bd->context, area_address); // Erase from second sector to the end cy_rslt_t result = CY_RSLT_SUCCESS; if (erase_size < _MTB_KVSTORE_AREA_SIZE(obj)) { result = obj->bd->erase(obj->bd->context, area_address + erase_size, _MTB_KVSTORE_AREA_SIZE(obj) - erase_size); } if (result == CY_RSLT_SUCCESS) { // Erase the first sector. result = obj->bd->erase(obj->bd->context, area_address, erase_size); } return result; } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_get_header_crc //-------------------------------------------------------------------------------------------------- static uint16_t _mtb_kvstore_get_header_crc(_mtb_kvstore_record_header_t* record_header, uint16_t init_crc) { uint16_t crc = init_crc; // Compute CRC for header crc = _mtb_kvstore_crc16((uint8_t*)&record_header->magic, sizeof(record_header->magic), crc); crc = _mtb_kvstore_crc16((uint8_t*)&record_header->format_version, sizeof(record_header->format_version), crc); crc = _mtb_kvstore_crc16((uint8_t*)&record_header->flags, sizeof(record_header->flags), crc); crc = _mtb_kvstore_crc16((uint8_t*)&record_header->header_size, sizeof(record_header->header_size), crc); crc = _mtb_kvstore_crc16((uint8_t*)&record_header->key_size, sizeof(record_header->key_size), crc); crc = _mtb_kvstore_crc16((uint8_t*)&record_header->data_size, sizeof(record_header->data_size), crc); return crc; } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_get_record_crc //-------------------------------------------------------------------------------------------------- static uint16_t _mtb_kvstore_get_record_crc(_mtb_kvstore_record_header_t* record_header, const char* key, const uint8_t* data) { CY_ASSERT(record_header != NULL); CY_ASSERT(key != NULL); uint16_t crc = _MTB_KVSTORE_CRC_INIT_VAL; // Compute CRC for header crc = _mtb_kvstore_get_header_crc(record_header, crc); crc = _mtb_kvstore_crc16((uint8_t*)key, record_header->key_size, crc); if ((data != NULL) && (record_header->data_size != 0)) { crc = _mtb_kvstore_crc16(data, record_header->data_size, crc); } return crc; } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_buffered_crc_compute //-------------------------------------------------------------------------------------------------- static cy_rslt_t _mtb_kvstore_buffered_crc_compute(mtb_kvstore_t* obj, uint32_t address, uint32_t size, uint16_t* crc) { CY_ASSERT(obj != NULL); cy_rslt_t result = CY_RSLT_SUCCESS; uint32_t remaining_size = size; while (remaining_size > 0) { uint32_t transfer_size = (obj->transaction_buffer_size >= remaining_size) ? remaining_size : obj->transaction_buffer_size; result = obj->bd->read(obj->bd->context, address, transfer_size, obj->transaction_buffer); if (result != CY_RSLT_SUCCESS) { return result; } *crc = _mtb_kvstore_crc16(obj->transaction_buffer, transfer_size, *crc); address += transfer_size; remaining_size -= transfer_size; } return result; } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_validate_key //-------------------------------------------------------------------------------------------------- static cy_rslt_t _mtb_kvstore_validate_key(mtb_kvstore_t* obj, uint32_t key_addr, const char* user_key, uint32_t key_size) { CY_ASSERT(obj != NULL); if (strlen(user_key) != key_size) { return MTB_KVSTORE_ITEM_NOT_FOUND_ERROR; } cy_rslt_t result = CY_RSLT_SUCCESS; uint32_t remaining_size = key_size; while (remaining_size > 0) { uint32_t transfer_size = (obj->transaction_buffer_size >= remaining_size) ? remaining_size : obj->transaction_buffer_size; result = obj->bd->read(obj->bd->context, key_addr, transfer_size, obj->transaction_buffer); if (result != CY_RSLT_SUCCESS) { return result; } if (memcmp((uint8_t*)user_key, obj->transaction_buffer, transfer_size) != 0) { return MTB_KVSTORE_ITEM_NOT_FOUND_ERROR; } key_addr += transfer_size; remaining_size -= transfer_size; } return result; } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_read_record //-------------------------------------------------------------------------------------------------- static cy_rslt_t _mtb_kvstore_read_record(mtb_kvstore_t* obj, uint32_t area_address, uint32_t offset, _mtb_kvstore_record_header_t* record_header, const char* key, bool validate_key, uint8_t* data, uint32_t* data_size) { CY_ASSERT(obj != NULL); cy_rslt_t result = CY_RSLT_SUCCESS; uint16_t crc = _MTB_KVSTORE_CRC_INIT_VAL; uint32_t record_start_addr = area_address + offset; uint8_t header_size = sizeof(_mtb_kvstore_record_header_t); CY_ASSERT(record_header != NULL); // Read header for the record result = obj->bd->read(obj->bd->context, record_start_addr, header_size, (uint8_t*)record_header); if (result != CY_RSLT_SUCCESS) { return result; } if ((record_header->magic == 0xFFFFFFFFU) || (record_header->magic == 0)) { return MTB_KVSTORE_ERASED_DATA_ERROR; } if (record_header->magic != _MTB_KVSTORE_HEADER_MAGIC) { return MTB_KVSTORE_INVALID_DATA_ERROR; } if ((record_header->key_size == 0) || (record_header->key_size >= MTB_KVSTORE_MAX_KEY_SIZE)) { return MTB_KVSTORE_INVALID_DATA_ERROR; } // If a data buffer is provided and the size is less that what is in the storage // return error. if ((data != NULL) && (data_size != NULL) && (*data_size < record_header->data_size)) { *data_size = record_header->data_size; return MTB_KVSTORE_BUFFER_TOO_SMALL; } crc = _mtb_kvstore_get_header_crc(record_header, crc); // Copy key into the provided key area uint32_t key_addr = record_start_addr + record_header->header_size; if (key != NULL) { if (validate_key) { result = _mtb_kvstore_validate_key(obj, key_addr, key, record_header->key_size); if (result != CY_RSLT_SUCCESS) { return result; } } else { result = obj->bd->read(obj->bd->context, key_addr, record_header->key_size, (uint8_t*)key); if (result != CY_RSLT_SUCCESS) { return result; } } // If the user passes in a key for validation since at this point we have // validated that the key on the storage is the same as what was passes in // it should be ok to use the key passed in by the user for CRC calculation. crc = _mtb_kvstore_crc16((uint8_t*)key, record_header->key_size, crc); } else { // Start buffered CRC result = _mtb_kvstore_buffered_crc_compute(obj, key_addr, record_header->key_size, &crc); if (result != CY_RSLT_SUCCESS) { return result; } } uint32_t data_addr = key_addr + record_header->key_size; if ((data != NULL) && (data_size != NULL)) { // Copy data into the data buffer provided result = obj->bd->read(obj->bd->context, data_addr, record_header->data_size, data); if (result != CY_RSLT_SUCCESS) { return result; } crc = _mtb_kvstore_crc16(data, record_header->data_size, crc); } else { result = _mtb_kvstore_buffered_crc_compute(obj, data_addr, record_header->data_size, &crc); if (result != CY_RSLT_SUCCESS) { return result; } } // If the CRC did not match then record is corrupted. if (record_header->crc != crc) { return MTB_KVSTORE_INVALID_DATA_ERROR; } if (data_size != NULL) { *data_size = record_header->data_size; } return result; } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_read_partial_record //-------------------------------------------------------------------------------------------------- static cy_rslt_t _mtb_kvstore_read_partial_record(mtb_kvstore_t* obj, uint32_t area_address, uint32_t offset, _mtb_kvstore_record_header_t* record_header, const char* key, bool validate_key, uint8_t* data, uint32_t* data_size, const uint32_t offset_bytes) { CY_ASSERT(obj != NULL); cy_rslt_t result = CY_RSLT_SUCCESS; uint32_t record_start_addr = area_address + offset; uint8_t header_size = sizeof(_mtb_kvstore_record_header_t); CY_ASSERT(record_header != NULL); // Read header for the record result = obj->bd->read(obj->bd->context, record_start_addr, header_size, (uint8_t*)record_header); if (result != CY_RSLT_SUCCESS) { return result; } // Improper use case: reading after the value ended if (offset_bytes > record_header->data_size) { return MTB_KVSTORE_BAD_PARAM_ERROR; } if ((record_header->magic == 0xFFFFFFFFU) || (record_header->magic == 0U)) { return MTB_KVSTORE_ERASED_DATA_ERROR; } if (record_header->magic != _MTB_KVSTORE_HEADER_MAGIC) { return MTB_KVSTORE_INVALID_DATA_ERROR; } if ((record_header->key_size == 0U) || (record_header->key_size >= MTB_KVSTORE_MAX_KEY_SIZE)) { return MTB_KVSTORE_INVALID_DATA_ERROR; } // Copy key into the provided key area uint32_t key_addr = record_start_addr + record_header->header_size; if (key != NULL) { if (validate_key) { result = _mtb_kvstore_validate_key(obj, key_addr, key, record_header->key_size); if (result != CY_RSLT_SUCCESS) { return result; } } else { result = obj->bd->read(obj->bd->context, key_addr, record_header->key_size, (uint8_t*)key); if (result != CY_RSLT_SUCCESS) { return result; } } } uint32_t data_addr = key_addr + record_header->key_size; if ((data != NULL) && (data_size != NULL)) { // Don't read past the value in memory if (*data_size > (record_header->data_size - offset_bytes)) { *data_size = (record_header->data_size - offset_bytes); } // Copy data into the data buffer provided result = obj->bd->read(obj->bd->context, (data_addr + offset_bytes), *data_size, data); if (result != CY_RSLT_SUCCESS) { return result; } } if (data_size != NULL) { *data_size = record_header->data_size; } return result; } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_setup_record_header //-------------------------------------------------------------------------------------------------- static void _mtb_kvstore_setup_record_header(const char* key, const uint8_t* data, uint32_t data_size, uint8_t format_version, _mtb_kvstore_operation_t operation, _mtb_kvstore_record_header_t* record_header) { CY_ASSERT(key != NULL); memset(record_header, 0, sizeof(_mtb_kvstore_record_header_t)); record_header->magic = _MTB_KVSTORE_HEADER_MAGIC; record_header->format_version = format_version; record_header->header_size = sizeof(_mtb_kvstore_record_header_t); record_header->flags = (operation == _MTB_KVSTORE_OPER_DELETE) ? _MTB_KVSTORE_DELETE_FLAG : _MTB_KVSTORE_NO_FLAG; record_header->key_size = strlen(key); record_header->data_size = data_size; record_header->crc = _mtb_kvstore_get_record_crc(record_header, key, data); } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_buffered_write //-------------------------------------------------------------------------------------------------- static cy_rslt_t _mtb_kvstore_buffered_write(mtb_kvstore_t* obj, const uint8_t* data, uint32_t data_size, uint32_t* write_address, uint32_t* buffer_space_left, bool flush) { CY_ASSERT(obj != NULL); cy_rslt_t result = CY_RSLT_SUCCESS; uint8_t* buffer_offset_ptr = obj->transaction_buffer + (obj->transaction_buffer_size - *buffer_space_left); const uint8_t* current_data_ptr = data; uint32_t remaining_size = data_size; while (remaining_size > 0) { uint32_t transfer_size = (*buffer_space_left >= remaining_size) ? remaining_size : *buffer_space_left; memcpy(buffer_offset_ptr, current_data_ptr, transfer_size); *buffer_space_left -= transfer_size; buffer_offset_ptr += transfer_size; remaining_size -= transfer_size; current_data_ptr += transfer_size; if (*buffer_space_left == 0) { result = obj->bd->program(obj->bd->context, *write_address, obj->transaction_buffer_size, obj->transaction_buffer); if (result != CY_RSLT_SUCCESS) { return result; } *buffer_space_left = obj->transaction_buffer_size; buffer_offset_ptr = obj->transaction_buffer; *write_address += obj->transaction_buffer_size; } } if ((*buffer_space_left != obj->transaction_buffer_size) && flush) { uint32_t prog_size = obj->bd->program_size(obj->bd->context, *write_address); uint32_t size = _mtb_kvstore_align_up(obj->transaction_buffer_size - *buffer_space_left, prog_size); result = obj->bd->program(obj->bd->context, *write_address, size, obj->transaction_buffer); if (result != CY_RSLT_SUCCESS) { return result; } *buffer_space_left = obj->transaction_buffer_size; } return result; } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_write_record //-------------------------------------------------------------------------------------------------- static cy_rslt_t _mtb_kvstore_write_record(mtb_kvstore_t* obj, uint32_t area_address, uint32_t offset, const char* key, const uint8_t* data, uint32_t data_size, _mtb_kvstore_operation_t operation, const _mtb_kvstore_update_ram_table_info_t* ram_tbl_info, const _mtb_kvstore_update_consumed_size_info_t* size_info) { CY_ASSERT(obj != NULL); CY_ASSERT(key != NULL); cy_rslt_t result = CY_RSLT_SUCCESS; // Calculate write size uint32_t record_address = area_address + offset; size_t header_size = sizeof(_mtb_kvstore_record_header_t); uint32_t prog_size = obj->bd->program_size(obj->bd->context, record_address); // Ensure that the buffer is large enough CY_ASSERT(obj->transaction_buffer_size >= header_size); // Check that the address written to is aligned to program page boundary CY_ASSERT(_mtb_kvstore_is_aligned(area_address, prog_size)); // The following transactions assume that the buffer size is aligned to the program // size. We do that in init but just to make sure we check it below. CY_ASSERT((obj->transaction_buffer_size % prog_size) == 0); // Setup the area header. _mtb_kvstore_record_header_t record_header; _mtb_kvstore_setup_record_header(key, data, data_size, _MTB_KVSTORE_FORMAT_VERSION, operation, &record_header); // Check that total size does not exceed size of area. uint32_t record_size = _mtb_kvstore_get_record_size(obj, record_address, record_header.key_size, record_header.data_size); CY_ASSERT((offset + record_size) <= _MTB_KVSTORE_AREA_SIZE(obj)); CY_UNUSED_PARAMETER(prog_size); CY_UNUSED_PARAMETER(record_size); uint32_t buffer_space_left = obj->transaction_buffer_size; result = _mtb_kvstore_buffered_write(obj, (uint8_t*)&record_header, header_size, &record_address, &buffer_space_left, false); if (result != CY_RSLT_SUCCESS) { return result; } result = _mtb_kvstore_buffered_write(obj, (uint8_t*)key, record_header.key_size, &record_address, &buffer_space_left, false); if (result != CY_RSLT_SUCCESS) { return result; } result = _mtb_kvstore_buffered_write(obj, data, record_header.data_size, &record_address, &buffer_space_left, true); if (result != CY_RSLT_SUCCESS) { return result; } if (offset != _MTB_KVSTORE_AREA_HEADER_OFFSET) { CY_ASSERT((ram_tbl_info != NULL) && (size_info != NULL)); // If we wrote the record successfully then update the ram table and consumed size _mtb_kvstore_update_ram_table(obj, operation, ram_tbl_info); _mtb_kvstore_update_consumed_size(obj, operation, size_info); } return result; } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_check_area_valid //-------------------------------------------------------------------------------------------------- static cy_rslt_t _mtb_kvstore_check_area_valid(mtb_kvstore_t* obj, uint32_t area_address, uint16_t* version) { CY_ASSERT(obj != NULL); _mtb_kvstore_record_header_t header; _mtb_kvstore_area_record_data_t area_header_data; uint32_t data_size = sizeof(_mtb_kvstore_area_record_data_t); cy_rslt_t result = _mtb_kvstore_read_record(obj, area_address, 0, &header, _mtb_kvstore_area_rec_key, true, (uint8_t*)&area_header_data, &data_size); if (result == CY_RSLT_SUCCESS) { *version = area_header_data.version; } return result; } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_write_area_record //-------------------------------------------------------------------------------------------------- static cy_rslt_t _mtb_kvstore_write_area_record(mtb_kvstore_t* obj, uint32_t area_address, uint32_t area_version) { CY_ASSERT(obj != NULL); _mtb_kvstore_area_record_data_t area_header_data; area_header_data.format_version = _MTB_KVSTORE_FORMAT_VERSION; area_header_data.version = area_version; return _mtb_kvstore_write_record(obj, area_address, _MTB_KVSTORE_AREA_HEADER_OFFSET, _mtb_kvstore_area_rec_key, (uint8_t*)&area_header_data, sizeof(_mtb_kvstore_area_record_data_t), _MTB_KVSTORE_OPER_ADD, NULL, NULL); } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_find_record_in_ram_table //-------------------------------------------------------------------------------------------------- static cy_rslt_t _mtb_kvstore_find_record_in_ram_table(mtb_kvstore_t* obj, const char* key, uint32_t* ram_table_idx, uint16_t* key_hash, uint32_t* data_size) { CY_ASSERT(obj != NULL); cy_rslt_t result = MTB_KVSTORE_ITEM_NOT_FOUND_ERROR; *key_hash = _mtb_kvstore_crc16((uint8_t*)key, strlen(key), _MTB_KVSTORE_CRC_INIT_VAL); for (*ram_table_idx = 0; *ram_table_idx < obj->num_entries; (*ram_table_idx)++) { mtb_kvstore_ram_table_entry_t entry = obj->ram_table[*ram_table_idx]; if (*key_hash < entry.hash) { continue; } if (*key_hash > entry.hash) { result = MTB_KVSTORE_ITEM_NOT_FOUND_ERROR; break; } _mtb_kvstore_record_header_t header; result = _mtb_kvstore_read_record(obj, obj->active_area_addr, entry.offset, &header, key, true, NULL, data_size); // If there was a key mismatch then keep searching. if (result != MTB_KVSTORE_ITEM_NOT_FOUND_ERROR) { break; } } return result; } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_copy_record //-------------------------------------------------------------------------------------------------- static cy_rslt_t _mtb_kvstore_copy_record(mtb_kvstore_t* obj, uint32_t src_area_addr, uint32_t src_offset, uint32_t dst_area_addr, uint32_t dst_offset, uint32_t* next_dst_offset) { CY_ASSERT(obj != NULL); cy_rslt_t result = CY_RSLT_SUCCESS; uint32_t src_record_addr = src_area_addr + src_offset; uint32_t dst_record_addr = dst_area_addr + dst_offset; _mtb_kvstore_record_header_t header; uint32_t header_size = sizeof(_mtb_kvstore_record_header_t); // Read header for the record result = obj->bd->read(obj->bd->context, src_record_addr, header_size, (uint8_t*)&header); if (result != CY_RSLT_SUCCESS) { return result; } uint32_t record_size = _mtb_kvstore_get_record_size(obj, src_record_addr, header.key_size, header.data_size); if ((dst_offset + record_size) > (_MTB_KVSTORE_AREA_SIZE(obj))) { return MTB_KVSTORE_STORAGE_FULL_ERROR; } uint32_t remaining_size = record_size; uint32_t read_addr = src_record_addr; uint32_t write_addr = dst_record_addr; while (remaining_size > 0) { uint32_t transfer_size = (obj->transaction_buffer_size >= remaining_size) ? remaining_size : obj->transaction_buffer_size; result = obj->bd->read(obj->bd->context, read_addr, transfer_size, obj->transaction_buffer); if (result != CY_RSLT_SUCCESS) { return result; } result = obj->bd->program(obj->bd->context, write_addr, transfer_size, obj->transaction_buffer); if (result != CY_RSLT_SUCCESS) { return result; } remaining_size -= transfer_size; read_addr += transfer_size; write_addr += transfer_size; } *next_dst_offset = dst_offset + record_size; return result; } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_garbage_collection //-------------------------------------------------------------------------------------------------- static cy_rslt_t _mtb_kvstore_garbage_collection(mtb_kvstore_t* obj, const _mtb_kvstore_record_info_t* record_info) { CY_ASSERT(obj != NULL); cy_rslt_t result = CY_RSLT_SUCCESS; // If we need to update a record then that the new size fits the space remaining space. // Otherwise return area full error before copying over. We can do this because we track // the actual consumed size so we use that to check if there is enough space to accommodate // the updated record. if ((record_info != NULL) && (record_info->update_rec_info != NULL)) { // Note that the consumed size is not yet updated so we need to subtract the old record size // while checking for space left. uint32_t total_size = obj->consumed_size - record_info->consumed_size_info.old_record_size + record_info->consumed_size_info.new_record_size; if (total_size > _MTB_KVSTORE_AREA_SIZE(obj)) { return MTB_KVSTORE_STORAGE_FULL_ERROR; } } /* Erase the GC area */ result = _mtb_kvstore_erase_area(obj, obj->gc_area_addr); if (result != CY_RSLT_SUCCESS) { return result; } uint32_t dst_offset = _mtb_kvstore_get_area_header_record_size(obj, obj->gc_area_addr); for (uint32_t idx = 0; idx < obj->num_entries; idx++) { if ((record_info != NULL) && (idx == record_info->ram_tbl_idx)) { continue; } uint32_t dst_next_offset; uint32_t src_offset = obj->ram_table[idx].offset; result = _mtb_kvstore_copy_record(obj, obj->active_area_addr, src_offset, obj->gc_area_addr, dst_offset, &dst_next_offset); if (result != CY_RSLT_SUCCESS) { return result; } obj->ram_table[idx].offset = dst_offset; dst_offset = dst_next_offset; } // We need to inject a record or delete a record in the case where there may not be enough space // to add a new record for an update or delete operation. if (record_info != NULL) { if (record_info->update_rec_info != NULL) { _mtb_kvstore_update_ram_table_info_t ram_tbl_info = { .ram_tbl_idx = record_info->ram_tbl_idx, .entry.hash = record_info->update_rec_info->key_hash, .entry.offset = dst_offset }; result = _mtb_kvstore_write_record(obj, obj->gc_area_addr, dst_offset, record_info->update_rec_info->key, record_info->update_rec_info->data, record_info->update_rec_info->data_size, _MTB_KVSTORE_OPER_UPDATE, &ram_tbl_info, &record_info->consumed_size_info); if (result != CY_RSLT_SUCCESS) { return result; } dst_offset += record_info->consumed_size_info.new_record_size; } else { _mtb_kvstore_update_ram_table_info_t ram_tbl_info = { .ram_tbl_idx = record_info->ram_tbl_idx, .entry.hash = 0, .entry.offset = 0 }; _mtb_kvstore_update_ram_table(obj, _MTB_KVSTORE_OPER_DELETE, &ram_tbl_info); _mtb_kvstore_update_consumed_size(obj, _MTB_KVSTORE_OPER_DELETE, &record_info->consumed_size_info); } } obj->active_area_version++; result = _mtb_kvstore_write_area_record(obj, obj->gc_area_addr, obj->active_area_version); if (result != CY_RSLT_SUCCESS) { return result; } obj->free_space_offset = dst_offset; uint32_t new_gc_area_addr = obj->active_area_addr; obj->active_area_addr = obj->gc_area_addr; obj->gc_area_addr = new_gc_area_addr; return result; } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_build_ram_table //-------------------------------------------------------------------------------------------------- static cy_rslt_t _mtb_kvstore_build_ram_table(mtb_kvstore_t* obj) { CY_ASSERT(obj != NULL); cy_rslt_t result = CY_RSLT_SUCCESS; // Each key will occupy at least a page hence the max number of keys // that fit in the space would be area size / page size. obj->num_entries = 0; obj->max_entries = _MTB_KVSTORE_INIT_MAX_KEYS; obj->free_space_offset = _MTB_KVSTORE_AREA_SIZE(obj); //We initially allocate ram table for 32 entries obj->ram_table = (mtb_kvstore_ram_table_entry_t*)pvPortMalloc(obj->max_entries * sizeof(mtb_kvstore_ram_table_entry_t)); if (NULL == obj->ram_table) { return MTB_KVSTORE_MEM_ALLOC_ERROR; } // Start looking from the end of the area header. uint32_t record_size = _mtb_kvstore_get_area_header_record_size(obj, obj->active_area_addr); // Add area header size to consumed size. obj->consumed_size = record_size; uint32_t offset = record_size; while ((offset + sizeof(_mtb_kvstore_record_header_t)) < obj->free_space_offset) { _mtb_kvstore_record_header_t header; result = _mtb_kvstore_read_record(obj, obj->active_area_addr, offset, &header, obj->key_buffer, false, NULL, NULL); if (result != CY_RSLT_SUCCESS) { if (MTB_KVSTORE_ERASED_DATA_ERROR == result) { // This is a special case since it is expected that we stop populating // the ram table when we encounter free space. // Hence we return success even though read record return an error. result = CY_RSLT_SUCCESS; } else if (MTB_KVSTORE_INVALID_DATA_ERROR == result) { // If a corrupted record was found we run GC operation // which will copy all valid record until the current corrupted // record was encountered. result = _mtb_kvstore_garbage_collection(obj, NULL); // The above function will set the free space offset in the new area // so we directly return the result. The consumed size // is already updated in this loop and will not change even after the // GC operation. return result; } break; } // This should be safe as we allocate 1 extra byte in the key buffer than the max key size. obj->key_buffer[header.key_size] = '\0'; uint32_t ram_tbl_idx; uint16_t hash; uint32_t old_record_data_size = 0; result = _mtb_kvstore_find_record_in_ram_table(obj, obj->key_buffer, &ram_tbl_idx, &hash, &old_record_data_size); if ((result != CY_RSLT_SUCCESS) && (result != MTB_KVSTORE_ITEM_NOT_FOUND_ERROR)) { break; } uint32_t curr_offset = offset; // Add the current record size to the offset to set the next offset. record_size = _mtb_kvstore_get_record_size(obj, obj->active_area_addr + curr_offset, header.key_size, header.data_size); offset += record_size; bool delete = (header.flags & _MTB_KVSTORE_DELETE_FLAG) != 0; bool found_in_table = (result != MTB_KVSTORE_ITEM_NOT_FOUND_ERROR); // If key was not found in the RAM table and is marked for deletion // then do nothing. if (delete && !found_in_table) { continue; } _mtb_kvstore_operation_t operation = (delete) ? _MTB_KVSTORE_OPER_DELETE : (found_in_table) ? _MTB_KVSTORE_OPER_UPDATE : _MTB_KVSTORE_OPER_ADD; // If we have to add an entry to the ram table then check // if we need to increment keys. if ((operation == _MTB_KVSTORE_OPER_ADD) && (obj->num_entries >= obj->max_entries)) { result = _mtb_kvstore_increment_max_keys(obj); if (result != CY_RSLT_SUCCESS) { break; } } _mtb_kvstore_update_ram_table_info_t ram_tbl_info = { .ram_tbl_idx = ram_tbl_idx, .entry.hash = hash, .entry.offset = curr_offset }; _mtb_kvstore_update_ram_table(obj, operation, &ram_tbl_info); uint32_t old_record_size = (operation == _MTB_KVSTORE_OPER_ADD) ? 0 : _mtb_kvstore_get_record_size(obj, obj->active_area_addr, strlen( obj->key_buffer), old_record_data_size); _mtb_kvstore_update_consumed_size_info_t size_info = { .old_record_size = old_record_size, .new_record_size = record_size }; _mtb_kvstore_update_consumed_size(obj, operation, &size_info); result = CY_RSLT_SUCCESS; } obj->free_space_offset = offset; return result; } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_setup_areas //-------------------------------------------------------------------------------------------------- static cy_rslt_t _mtb_kvstore_setup_areas(mtb_kvstore_t* obj) { CY_ASSERT(obj != NULL); cy_rslt_t result = CY_RSLT_SUCCESS; // Divide the space into 2 equal sizes. uint32_t area1_start_addr = obj->start_addr; uint32_t area2_start_addr = obj->start_addr + _MTB_KVSTORE_AREA_SIZE(obj); bool area1_valid; bool area2_valid; uint16_t area1_version; uint16_t area2_version; // Read area 1 header cy_rslt_t area_valid_result = _mtb_kvstore_check_area_valid(obj, area1_start_addr, &area1_version); if ((CY_RSLT_SUCCESS != area_valid_result) && (MTB_KVSTORE_ERASED_DATA_ERROR != area_valid_result) && (MTB_KVSTORE_INVALID_DATA_ERROR != area_valid_result) && (MTB_KVSTORE_ITEM_NOT_FOUND_ERROR != area_valid_result)) { return area_valid_result; } area1_valid = (CY_RSLT_SUCCESS == area_valid_result); area_valid_result = _mtb_kvstore_check_area_valid(obj, area2_start_addr, &area2_version); if ((CY_RSLT_SUCCESS != area_valid_result) && (MTB_KVSTORE_ERASED_DATA_ERROR != area_valid_result) && (MTB_KVSTORE_INVALID_DATA_ERROR != area_valid_result) && (MTB_KVSTORE_ITEM_NOT_FOUND_ERROR != area_valid_result)) { return area_valid_result; } area2_valid = (CY_RSLT_SUCCESS == area_valid_result); // If both are valid, set the one area whose master record has the // higher version as active_area. Erase first sector of the other one. if (area1_valid && area2_valid) { // The versions should never be equal. CY_ASSERT(area1_version != area2_version); // Check if the area1 version is greater or 0 in case of wrap around. if ((area1_version > area2_version) || (area1_version == 0)) { obj->active_area_addr = area1_start_addr; obj->active_area_version = area1_version; obj->gc_area_addr = area2_start_addr; } else { obj->active_area_addr = area2_start_addr; obj->active_area_version = area2_version; obj->gc_area_addr = area1_start_addr; } } // If one is valid, set its area as active_area. else if (area1_valid) { obj->active_area_addr = area1_start_addr; obj->active_area_version = area1_version; obj->gc_area_addr = area2_start_addr; } else if (area2_valid) { obj->active_area_addr = area2_start_addr; obj->active_area_version = area2_version; obj->gc_area_addr = area1_start_addr; } // If none are valid, set area1 as active_area, and program area record //with version 1. else { // Erase first sector of area 1 to be able to write the header. result = _mtb_kvstore_erase_area(obj, area1_start_addr); if (result != CY_RSLT_SUCCESS) { return result; } // Write the area header into the area 1. result = _mtb_kvstore_write_area_record(obj, area1_start_addr, _MTB_KVSTORE_INITIAL_AREA_VERSION); if (result != CY_RSLT_SUCCESS) { return result; } obj->active_area_addr = area1_start_addr; obj->active_area_version = _MTB_KVSTORE_INITIAL_AREA_VERSION; obj->gc_area_addr = area2_start_addr; } return result; } //-------------------------------------------------------------------------------------------------- // _mtb_kvstore_write_with_flags //-------------------------------------------------------------------------------------------------- static cy_rslt_t _mtb_kvstore_write_with_flags(mtb_kvstore_t* obj, const char* key, const uint8_t* data, uint32_t size, bool delete) { CY_ASSERT(obj != NULL); cy_rslt_t result = CY_RSLT_SUCCESS; uint32_t ram_tbl_idx; uint16_t hash; uint32_t old_record_data_size = 0; result = _mtb_kvstore_find_record_in_ram_table(obj, key, &ram_tbl_idx, &hash, &old_record_data_size); if ((result != CY_RSLT_SUCCESS) && (result != MTB_KVSTORE_ITEM_NOT_FOUND_ERROR)) { return result; } bool found_in_table = (result != MTB_KVSTORE_ITEM_NOT_FOUND_ERROR); // If we are trying to delete a record and it is not found in the RAM table then it // is already been removed or does not exist. Hence we return success. if (delete && !found_in_table) { return CY_RSLT_SUCCESS; } _mtb_kvstore_operation_t operation = (delete) ? _MTB_KVSTORE_OPER_DELETE : (found_in_table) ? _MTB_KVSTORE_OPER_UPDATE : _MTB_KVSTORE_OPER_ADD; // We will be adding a new entry if its not found in the table so // check if max keys need to be expanded before we write anything // to flash. if ((operation == _MTB_KVSTORE_OPER_ADD) && (obj->num_entries >= obj->max_entries)) { result = _mtb_kvstore_increment_max_keys(obj); if (result != CY_RSLT_SUCCESS) { return result; } } // Check if space enough for KV record. If not run GC uint32_t record_size = _mtb_kvstore_get_record_size(obj, obj->active_area_addr, strlen( key), size); uint32_t old_record_size = (operation == _MTB_KVSTORE_OPER_ADD) ? 0 : _mtb_kvstore_get_record_size(obj, obj->active_area_addr, strlen(key), old_record_data_size); if (((operation == _MTB_KVSTORE_OPER_UPDATE) || (operation == _MTB_KVSTORE_OPER_ADD)) && ((obj->consumed_size - old_record_size + record_size) > _MTB_KVSTORE_AREA_SIZE(obj))) { return MTB_KVSTORE_STORAGE_FULL_ERROR; } if ((obj->free_space_offset + record_size) > _MTB_KVSTORE_AREA_SIZE(obj)) { // If we need to update or delete a key and we do not enough space left. We can do the // update or // deletion when we run garbage collection by removing the value from the ram table before // GC operation // and injecting new updated entry if it is a update operation. _mtb_kvstore_record_info_t record_info; _mtb_kvstore_update_record_info_t update_rec; if ((operation == _MTB_KVSTORE_OPER_DELETE) || (operation == _MTB_KVSTORE_OPER_UPDATE)) { record_info.ram_tbl_idx = ram_tbl_idx; record_info.consumed_size_info.new_record_size = record_size; record_info.consumed_size_info.old_record_size = old_record_size; } if (operation == _MTB_KVSTORE_OPER_DELETE) { record_info.update_rec_info = NULL; } else if (operation == _MTB_KVSTORE_OPER_UPDATE) { update_rec.key = key; update_rec.data = data; update_rec.data_size = size; update_rec.key_hash = hash; record_info.update_rec_info = &update_rec; } result = _mtb_kvstore_garbage_collection(obj, (operation == _MTB_KVSTORE_OPER_ADD) ? NULL : &record_info); if ((result != CY_RSLT_SUCCESS) || found_in_table) { return result; } } // We check that we have enough space earlier so when we get here we must // have enough space. CY_ASSERT((obj->free_space_offset + record_size) <= _MTB_KVSTORE_AREA_SIZE(obj)); _mtb_kvstore_update_ram_table_info_t ram_tbl_info = { .ram_tbl_idx = ram_tbl_idx, .entry.hash = hash, .entry.offset = obj->free_space_offset }; _mtb_kvstore_update_consumed_size_info_t size_info = { .old_record_size = old_record_size, .new_record_size = record_size }; result = _mtb_kvstore_write_record(obj, obj->active_area_addr, obj->free_space_offset, key, data, size, operation, &ram_tbl_info, &size_info); if (result == CY_RSLT_SUCCESS) { obj->free_space_offset += record_size; } return result; } /**************************************** PUBLIC API ******************************************/ //-------------------------------------------------------------------------------------------------- // mtb_kvstore_init //-------------------------------------------------------------------------------------------------- cy_rslt_t mtb_kvstore_init(mtb_kvstore_t* obj, uint32_t start_addr, uint32_t length, const mtb_kvstore_bd_t* block_device) { if ((NULL == obj) || (NULL == block_device) || (length == 0)) { return MTB_KVSTORE_BAD_PARAM_ERROR; } // Check if start addr and start addr + length align with erase sector size uint32_t erase_size = block_device->erase_size(block_device->context, start_addr); if (!_mtb_kvstore_is_aligned(start_addr, erase_size) || !_mtb_kvstore_is_aligned(start_addr + length, erase_size)) { return MTB_KVSTORE_ALIGNMENT_ERROR; } // Check that the storage does not have 0 sectors and has even number // of erase sectors. uint32_t num_erase_sectors = (length / erase_size); if ((num_erase_sectors == 0) || ((num_erase_sectors > 0) && ((num_erase_sectors & 1) != 0))) { return MTB_KVSTORE_ALIGNMENT_ERROR; } cy_rslt_t result = CY_RSLT_SUCCESS; memset(obj, 0, sizeof(mtb_kvstore_t)); // Init Mutex result = _mtb_kvstore_initlock(obj); if (result != CY_RSLT_SUCCESS) { return result; } // Get Mutex result = _mtb_kvstore_lock(obj); if (result != CY_RSLT_SUCCESS) { return result; } uint32_t prog_size = block_device->program_size(block_device->context, start_addr); uint32_t read_size = block_device->read_size(block_device->context, start_addr); uint32_t buffer_size = (prog_size >= read_size) ? prog_size : read_size; if (buffer_size < _MTB_KVSTORE_MIN_BUFF_SIZE) { buffer_size = _mtb_kvstore_align_up(_MTB_KVSTORE_MIN_BUFF_SIZE, prog_size); } obj->transaction_buffer = (uint8_t*)pvPortMalloc(buffer_size); if (obj->transaction_buffer != NULL) { obj->transaction_buffer_size = buffer_size; obj->bd = block_device; obj->start_addr = start_addr; obj->length = length; if (result == CY_RSLT_SUCCESS) { result = _mtb_kvstore_setup_areas(obj); if (result == CY_RSLT_SUCCESS) { result = _mtb_kvstore_build_ram_table(obj); } } } else { /* Not enough heap available to allocate work buffer */ result = MTB_KVSTORE_MEM_ALLOC_ERROR; } // Release Mutex _mtb_kvstore_unlock(obj); if (result != CY_RSLT_SUCCESS) { mtb_kvstore_deinit(obj); } return result; } //-------------------------------------------------------------------------------------------------- // mtb_kvstore_write //-------------------------------------------------------------------------------------------------- cy_rslt_t mtb_kvstore_write(mtb_kvstore_t* obj, const char* key, const uint8_t* data, uint32_t size) { if (!_mtb_kvstore_is_valid_key(key) || ((data == NULL) && (size != 0))) { return MTB_KVSTORE_BAD_PARAM_ERROR; } cy_rslt_t result = _mtb_kvstore_lock(obj); if (result != CY_RSLT_SUCCESS) { return result; } result = _mtb_kvstore_write_with_flags(obj, key, data, size, 0); _mtb_kvstore_unlock(obj); return result; } //-------------------------------------------------------------------------------------------------- // mtb_kvstore_key_exists //-------------------------------------------------------------------------------------------------- cy_rslt_t mtb_kvstore_key_exists(mtb_kvstore_t* obj, const char* key) { if (!_mtb_kvstore_is_valid_key(key)) { return MTB_KVSTORE_BAD_PARAM_ERROR; } cy_rslt_t result = _mtb_kvstore_lock(obj); if (result != CY_RSLT_SUCCESS) { return result; } uint32_t ram_tbl_idx; uint16_t hash; result = _mtb_kvstore_find_record_in_ram_table(obj, key, &ram_tbl_idx, &hash, NULL); _mtb_kvstore_unlock(obj); return result; } //-------------------------------------------------------------------------------------------------- // mtb_kvstore_value_size //-------------------------------------------------------------------------------------------------- cy_rslt_t mtb_kvstore_value_size(mtb_kvstore_t* obj, const char* key, uint32_t* size) { if (!_mtb_kvstore_is_valid_key(key)) { return MTB_KVSTORE_BAD_PARAM_ERROR; } cy_rslt_t result = _mtb_kvstore_lock(obj); if (result != CY_RSLT_SUCCESS) { return result; } uint32_t ram_tbl_idx; uint16_t hash; result = _mtb_kvstore_find_record_in_ram_table(obj, key, &ram_tbl_idx, &hash, NULL); if (result == CY_RSLT_SUCCESS) { _mtb_kvstore_record_header_t header; result = _mtb_kvstore_read_record(obj, obj->active_area_addr, obj->ram_table[ram_tbl_idx].offset, &header, key, true, NULL, size); } _mtb_kvstore_unlock(obj); return result; } //-------------------------------------------------------------------------------------------------- // mtb_kvstore_read //-------------------------------------------------------------------------------------------------- cy_rslt_t mtb_kvstore_read(mtb_kvstore_t* obj, const char* key, uint8_t* data, uint32_t* size) { if (!_mtb_kvstore_is_valid_key(key)) { return MTB_KVSTORE_BAD_PARAM_ERROR; } // If data buffer is passed but size is NULL or 0 if ((data != NULL) && ((size == NULL) || (*size == 0))) { return MTB_KVSTORE_BAD_PARAM_ERROR; } cy_rslt_t result = _mtb_kvstore_lock(obj); if (result != CY_RSLT_SUCCESS) { return result; } uint32_t ram_tbl_idx; uint16_t hash; result = _mtb_kvstore_find_record_in_ram_table(obj, key, &ram_tbl_idx, &hash, NULL); if (result == CY_RSLT_SUCCESS) { _mtb_kvstore_record_header_t header; uint32_t data_size; if (size == NULL) { data_size = 0UL; } else { data_size = *size; } result = _mtb_kvstore_read_record(obj, obj->active_area_addr, obj->ram_table[ram_tbl_idx].offset, &header, key, true, data, size); // Fill excess buffer space with 0's if ((data != NULL) && (*size < data_size)) { // memset with size 0 is well defined (no effect) (void)memset(&(data[*size]), 0, (data_size - *size)); } } _mtb_kvstore_unlock(obj); return result; } //-------------------------------------------------------------------------------------------------- // mtb_kvstore_read_partial //-------------------------------------------------------------------------------------------------- cy_rslt_t mtb_kvstore_read_partial(mtb_kvstore_t* obj, const char* key, uint8_t* data, uint32_t* size, const uint32_t offset_bytes) { if (!_mtb_kvstore_is_valid_key(key)) { return MTB_KVSTORE_BAD_PARAM_ERROR; } // If data buffer is passed but size is NULL or 0; if ((data != NULL) && ((size == NULL) || (*size == 0U))) { return MTB_KVSTORE_BAD_PARAM_ERROR; } cy_rslt_t result = _mtb_kvstore_lock(obj); if (result != CY_RSLT_SUCCESS) { return result; } uint32_t ram_tbl_idx; uint16_t hash; result = _mtb_kvstore_find_record_in_ram_table(obj, key, &ram_tbl_idx, &hash, NULL); if (result == CY_RSLT_SUCCESS) { uint32_t data_size = *size; _mtb_kvstore_record_header_t header; result = _mtb_kvstore_read_partial_record(obj, obj->active_area_addr, obj->ram_table[ram_tbl_idx].offset, &header, key, true, data, size, offset_bytes); if (result != CY_RSLT_SUCCESS) { _mtb_kvstore_unlock(obj); return result; } // Fill excess buffer space with 0's if ((data != NULL) && ((*size - offset_bytes) < data_size)) { // memset with size 0 is well defined (no effect) (void)memset(&(data[*size - offset_bytes]), 0, (data_size - (*size - offset_bytes))); } // Inform caller if the full value could not fit in buffer & indicate how many bytes were // copied over if (data != NULL) { if ((*size - offset_bytes) > data_size) { result = MTB_KVSTORE_BUFFER_TOO_SMALL; *size = data_size; } else { *size = *size - offset_bytes; } } } _mtb_kvstore_unlock(obj); return result; } //-------------------------------------------------------------------------------------------------- // mtb_kvstore_delete //-------------------------------------------------------------------------------------------------- cy_rslt_t mtb_kvstore_delete(mtb_kvstore_t* obj, const char* key) { cy_rslt_t result = _mtb_kvstore_lock(obj); if (result != CY_RSLT_SUCCESS) { return result; } result = _mtb_kvstore_write_with_flags(obj, key, NULL, 0, true); _mtb_kvstore_unlock(obj); return result; } //-------------------------------------------------------------------------------------------------- // mtb_kvstore_reset //-------------------------------------------------------------------------------------------------- cy_rslt_t mtb_kvstore_reset(mtb_kvstore_t* obj) { cy_rslt_t result = _mtb_kvstore_lock(obj); if (result != CY_RSLT_SUCCESS) { return result; } // Clear the RAM table memset(obj->ram_table, 0, obj->max_entries * sizeof(mtb_kvstore_ram_table_entry_t)); obj->num_entries = 0; // Run GC. result = _mtb_kvstore_garbage_collection(obj, NULL); if (result == CY_RSLT_SUCCESS) { obj->consumed_size = obj->free_space_offset; } _mtb_kvstore_unlock(obj); return result; } //-------------------------------------------------------------------------------------------------- // mtb_kvstore_deinit //-------------------------------------------------------------------------------------------------- void mtb_kvstore_deinit(mtb_kvstore_t* obj) { _mtb_kvstore_lock_wait_forever(obj); if (obj->transaction_buffer != NULL) { free(obj->transaction_buffer); } if (obj->ram_table != NULL) { free(obj->ram_table); } #if defined(CY_RTOS_AWARE) || defined(COMPONENT_RTOS_AWARE) cy_mutex_t local_mutex = obj->mtb_kvstore_mutex; #endif _mtb_kvstore_unlock(obj); #if defined(CY_RTOS_AWARE) || defined(COMPONENT_RTOS_AWARE) cy_rslt_t result = cy_rtos_deinit_mutex(&local_mutex); CY_ASSERT(result == CY_RSLT_SUCCESS); CY_UNUSED_PARAMETER(result); #endif } //-------------------------------------------------------------------------------------------------- // mtb_kvstore_size //-------------------------------------------------------------------------------------------------- uint32_t mtb_kvstore_size(mtb_kvstore_t* obj) { return obj->consumed_size; } //-------------------------------------------------------------------------------------------------- // mtb_kvstore_ensure_capacity //-------------------------------------------------------------------------------------------------- cy_rslt_t mtb_kvstore_ensure_capacity(mtb_kvstore_t* obj, uint32_t size) { uint32_t space_without_gc = _MTB_KVSTORE_AREA_SIZE(obj) - obj->free_space_offset; cy_rslt_t result = CY_RSLT_SUCCESS; if (space_without_gc < size) /* Will always be true if size is MTB_KVSTORE_ENSURE_MAX */ { result = _mtb_kvstore_garbage_collection(obj, NULL); } /* Put this check after the garbage collection operation, even though we have enough * information before garbage collection runs to know whether it will be able to * free up enough space. That way, success or failure, we always make as much space * available as we can. */ if ((CY_RSLT_SUCCESS == result) && (mtb_kvstore_remaining_size(obj) < size)) { /* Don't error on ENSURE_MAX because that is deliberately larger than our storage * is ever likely to be, so as to signify that we should always run garbage collection */ if (MTB_KVSTORE_ENSURE_MAX != size) { result = MTB_KVSTORE_STORAGE_FULL_ERROR; } } return result; } //-------------------------------------------------------------------------------------------------- // mtb_kvstore_remaining_size //-------------------------------------------------------------------------------------------------- uint32_t mtb_kvstore_remaining_size(mtb_kvstore_t* obj) { return _MTB_KVSTORE_AREA_SIZE(obj) - obj->consumed_size; }