diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index 39e02488f..16cc2005b 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -76,6 +76,7 @@ jobs: - libraries/RTC/examples/RTC_NTPSync - libraries/RTC/examples/RTC_Alarm - libraries/SFU + - libraries/KVStore/examples/StartCounter - board: fqbn: "arduino-git:renesas:portenta_c33" additional-sketch-paths: | @@ -91,6 +92,7 @@ jobs: - libraries/RTC/examples/RTC_NTPSync - libraries/RTC/examples/RTC_Alarm - libraries/SFU + - libraries/KVStore/examples/StartCounter - board: fqbn: "arduino:renesas_uno:unor4wifi" additional-sketch-paths: | diff --git a/libraries/KVStore/.portenta_only b/libraries/KVStore/.portenta_only new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/KVStore/examples/StartCounter/StartCounter.ino b/libraries/KVStore/examples/StartCounter/StartCounter.ino new file mode 100644 index 000000000..41dedeba1 --- /dev/null +++ b/libraries/KVStore/examples/StartCounter/StartCounter.ino @@ -0,0 +1,72 @@ +/* + * Microcontroller startup counter example with Portenta c33 kvstore library + * This simple example demonstrates using the KVStore library to store how many times + * the microcontroller has booted. The KVStore library is based on mbed OS KVStore library + * + * This example is based on Martin Sloup (Arcao) StartCounter example for arduino-esp32 + */ + +#include +#include +#include + +auto root = BlockDevice::get_default_instance(); +MBRBlockDevice bd(root, 3); +TDBStore kvstore(&bd); + +void setup() { + Serial.begin(115200); + Serial.println(); + + while(!Serial); + + // Init KVStore + if (kvstore.init() != KVSTORE_SUCCESS) { + Serial.println("Cannot initialize kvstore"); + while(1) {}; + } + + // Remove all values stored in the kvstore + // kvstore.reset(); + + // Or remove the counter key only + // kvstore.remove("counter"); + + // Get the counter value, if it doesn't exist it returns KVSTORE_ERROR_ITEM_NOT_FOUND + unsigned int counter; + auto res = kvstore.get("counter", (void*)&counter, sizeof(counter)); + + if (res == KVSTORE_ERROR_ITEM_NOT_FOUND) { + counter = 0; + } else if (res == KVSTORE_SUCCESS) { + // Increase counter by 1 + counter++; + } else { + Serial.print("Error getting counter from kvstore: "); + Serial.println(res); + } + + // Print the counter to Serial Monitor + Serial.print("Current counter value: "); + Serial.println(counter); + + // Store the updated counter value to the kvstore + if (kvstore.set("counter",(void*)&counter, sizeof(counter), 0) != KVSTORE_SUCCESS) { + Serial.println("Error setting counter from kvstore"); + } + + // Close the kvstore + if (kvstore.deinit() != KVSTORE_SUCCESS) { + Serial.println("Cannot deinitialize kvstore"); + while(1) {}; + } + + // Wait 10 seconds + Serial.println("Restarting in 10 seconds..."); + delay(10000); + + // Reset + NVIC_SystemReset(); +} + +void loop() {} diff --git a/libraries/KVStore/library.properties b/libraries/KVStore/library.properties new file mode 100644 index 000000000..b4320d4b6 --- /dev/null +++ b/libraries/KVStore/library.properties @@ -0,0 +1,9 @@ +name=KVStore +version=1.0.0 +author=Arduino +maintainer=Arduino +sentence=KVStore for arduino core renesas +paragraph= +category=Storage +url=https://github.com/arduino/ArduinoCore-renesas/tree/master/libraries/KVStore +architectures=renesas,renesas_portenta diff --git a/libraries/KVStore/src/KVStore.h b/libraries/KVStore/src/KVStore.h new file mode 100644 index 000000000..08e0bae61 --- /dev/null +++ b/libraries/KVStore/src/KVStore.h @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2018 ARM Limited. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MBED_KVSTORE_H +#define MBED_KVSTORE_H + +#include +#include + +#define KVSTORE_SUCCESS 0 +#define KVSTORE_ERROR_READ_FAILED 283 +#define KVSTORE_ERROR_WRITE_FAILED 284 +#define KVSTORE_ERROR_INVALID_DATA_DETECTED 258 +#define KVSTORE_ERROR_INVALID_SIZE 261 +#define KVSTORE_ERROR_INVALID_ARGUMENT 257 +#define KVSTORE_ERROR_ITEM_NOT_FOUND 263 +#define KVSTORE_ERROR_MEDIA_FULL 267 +#define KVSTORE_ERROR_WRITE_PROTECTED 274 +#define KVSTORE_ERROR_OUT_OF_RESOURCES 288 +#define KVSTORE_ERROR_NOT_READY 270 +#define KVSTORE_ERROR_FAILED_OPERATION 271 + +namespace mbed { + +/** KVStore class + * + * Interface class for Key Value Storage + */ +class KVStore { +public: + enum create_flags { + WRITE_ONCE_FLAG = (1 << 0), + REQUIRE_CONFIDENTIALITY_FLAG = (1 << 1), + RESERVED_FLAG = (1 << 2), + REQUIRE_REPLAY_PROTECTION_FLAG = (1 << 3), + }; + + static const uint32_t MAX_KEY_SIZE = 128; + + typedef struct _opaque_set_handle *set_handle_t; + + typedef struct _opaque_key_iterator *iterator_t; + + /** + * Holds key information + */ + typedef struct info { + /** + * The key size + */ + size_t size; + /* + * The Key flags, possible flags combination: + * WRITE_ONCE_FLAG, + * REQUIRE_CONFIDENTIALITY_FLAG, + * REQUIRE_REPLAY_PROTECTION_FLAG + */ + uint32_t flags; + } info_t; + + virtual ~KVStore() {}; + + /** + * @brief Initialize KVStore + * + * @returns KVSTORE_SUCCESS on success or an error code on failure + */ + virtual int init() = 0; + + /** + * @brief Deinitialize KVStore + * + * @returns KVSTORE_SUCCESS on success or an error code on failure + */ + virtual int deinit() = 0; + + + /** + * @brief Reset KVStore contents (clear all keys) + * + * @returns KVSTORE_SUCCESS on success or an error code on failure + */ + virtual int reset() = 0; + + /** + * @brief Set one KVStore item, given key and value. + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[in] buffer Value data buffer. + * @param[in] size Value data size. + * @param[in] create_flags Flag mask. + * + * @returns KVSTORE_SUCCESS on success or an error code on failure + */ + virtual int set(const char *key, const void *buffer, size_t size, uint32_t create_flags) = 0; + + /** + * @brief Get one KVStore item, given key. + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[in] buffer Value data buffer. + * @param[in] buffer_size Value data buffer size. + * @param[out] actual_size Actual read size (NULL to pass nothing). + * @param[in] offset Offset to read from in data. + * + * @returns KVSTORE_SUCCESS on success or an error code on failure + */ + virtual int get(const char *key, void *buffer, size_t buffer_size, size_t *actual_size = NULL, size_t offset = 0) = 0; + + /** + * @brief Get information of a given key. + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[out] info Returned information structure (NULL to pass nothing). + * + * @returns KVSTORE_SUCCESS on success or an error code on failure + */ + virtual int get_info(const char *key, info_t *info = NULL) = 0; + + /** + * @brief Remove a KVStore item, given key. + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * + * @returns KVSTORE_SUCCESS on success or an error code on failure + */ + virtual int remove(const char *key) = 0; + + + /** + * @brief Start an incremental KVStore set sequence. + * + * @param[out] handle Returned incremental set handle. + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[in] final_data_size Final value data size. + * @param[in] create_flags Flag mask. + * + * @returns KVSTORE_SUCCESS on success or an error code on failure + */ + virtual int set_start(set_handle_t *handle, const char *key, size_t final_data_size, uint32_t create_flags) = 0; + + /** + * @brief Add data to incremental KVStore set sequence. + * + * @param[in] handle Incremental set handle. + * @param[in] value_data Value data to add. + * @param[in] data_size Value data size. + * + * @returns KVSTORE_SUCCESS on success or an error code on failure + */ + virtual int set_add_data(set_handle_t handle, const void *value_data, size_t data_size) = 0; + + /** + * @brief Finalize an incremental KVStore set sequence. + * + * @param[in] handle Incremental set handle. + * + * @returns KVSTORE_SUCCESS on success or an error code on failure + */ + virtual int set_finalize(set_handle_t handle) = 0; + + /** + * @brief Start an iteration over KVStore keys. + * + * @param[out] it Returned iterator handle. + * @param[in] prefix Key prefix (null for all keys). + * + * @returns KVSTORE_SUCCESS on success or an error code on failure + */ + virtual int iterator_open(iterator_t *it, const char *prefix = NULL) = 0; + + /** + * @brief Get next key in iteration. + * + * @param[in] it Iterator handle. + * @param[in] key Buffer for returned key. + * @param[in] key_size Key buffer size. + * + * @returns KVSTORE_SUCCESS on success or an error code on failure + */ + virtual int iterator_next(iterator_t it, char *key, size_t key_size) = 0; + + /** + * @brief Close iteration. + * + * @param[in] it Iterator handle. + * + * @returns KVSTORE_SUCCESS on success or an error code on failure + */ + virtual int iterator_close(iterator_t it) = 0; + + /** Convenience function for checking key validity. + * Key must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * + * @param[in] key Key buffer. + * + * @returns KVSTORE_SUCCESS on success or an error code on failure + */ + bool is_valid_key(const char *key) const + { + if (!key || !strlen(key) || (strlen(key) > MAX_KEY_SIZE)) { + return false; + } + + if (strpbrk(key, " */?:;\"|<>\\")) { + return false; + } + return true; + } + +}; +/** @}*/ + +} // namespace mbed + +#endif diff --git a/libraries/KVStore/src/MbedCRC.h b/libraries/KVStore/src/MbedCRC.h new file mode 100644 index 000000000..ce6b3f530 --- /dev/null +++ b/libraries/KVStore/src/MbedCRC.h @@ -0,0 +1,892 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef MBED_CRC_API_H +#define MBED_CRC_API_H + +// #include "cmsis.h" +// #include "hal/crc_api.h" +#ifdef DEVICE_CRC +#include "device.h" +#endif +// #include "platform/mbed_assert.h" + +#ifdef __cplusplus + +// #include "platform/SingletonPtr.h" +// #include "platform/PlatformMutex.h" + +// #include + +// namespace mbed { +/** \addtogroup drivers-public-api */ +/** @{*/ +/** + * \defgroup drivers_MbedCRC MbedCRC class + * @{ + */ + +// extern SingletonPtr mbed_crc_mutex; + +/** CRC Polynomial value + * + * Different polynomial values supported + */ +typedef enum crc_polynomial { + POLY_7BIT_SD = 0x09, ///< x7+x3+1 + POLY_8BIT_CCITT = 0x07, ///< x8+x2+x+1 + POLY_16BIT_CCITT = 0x1021, ///< x16+x12+x5+1 + POLY_16BIT_IBM = 0x8005, ///< x16+x15+x2+1 + POLY_32BIT_ANSI = 0x04C11DB7 ///< x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1 +} crc_polynomial_t; + +/** CRC mode selection + */ +enum class CrcMode { + HARDWARE, /// Use hardware (if available), else table-based computation + TABLE, /// Use table-based computation (if table available), else bitwise + BITWISE /// Always use bitwise manual computation +}; + +#ifndef DOXYGEN_ONLY +namespace impl { +template +class MbedCRC; + +constexpr bool have_crc_table(uint32_t polynomial, uint8_t width) +{ +#if MBED_CRC_TABLE_SIZE > 0 + return (polynomial == POLY_32BIT_ANSI && width == 32) || + (polynomial == POLY_16BIT_IBM && width == 16) || + (polynomial == POLY_16BIT_CCITT && width == 16) || + (polynomial == POLY_8BIT_CCITT && width == 8) || + (polynomial == POLY_7BIT_SD && width == 7); +#else + return false; +#endif +} + +constexpr CrcMode choose_crc_mode(uint32_t polynomial, uint8_t width, CrcMode mode_limit) +{ + return +#if DEVICE_CRC + mode_limit == CrcMode::HARDWARE && HAL_CRC_IS_SUPPORTED(polynomial, width) ? CrcMode::HARDWARE : +#endif + mode_limit <= CrcMode::TABLE && have_crc_table(polynomial, width) ? CrcMode::TABLE : + CrcMode::BITWISE; +} +#endif // DOXYGEN_ONLY + +} // namespace impl + +/** CRC object provides CRC generation through hardware or software + * + * CRC sums can be generated using three different methods: hardware, software ROM tables + * and bitwise computation. The mode used is normally selected automatically based on required + * polynomial and hardware capabilities. Any polynomial in standard form (`x^3 + x + 1`) + * can be used for computation, but custom ones can affect the performance. + * + * First choice is the hardware mode. The supported polynomials are hardware specific, and + * you need to consult your MCU manual to discover them. Next, ROM polynomial tables + * are tried (you can find list of supported polynomials here ::crc_polynomial). If the selected + * configuration is supported, it will accelerate the software computations. If ROM tables + * are not available for the selected polynomial, then CRC is computed at run time bit by bit + * for all data input. + * + * If desired, the mode can be manually limited for a given instance by specifying the mode_limit + * template parameter. This might be appropriate to ensure a table is not pulled in for a + * non-speed-critical CRC, or to avoid the hardware set-up overhead if you know you will be + * calling `compute` with very small data sizes. + * + * @note Synchronization level: Thread safe + * + * @tparam polynomial CRC polynomial value in hex + * @tparam width CRC polynomial width + * @tparam mode_limit Maximum amount of acceleration to use + * + * Example: Compute CRC data + * @code + * + * #include "mbed.h" + * + * int main() { + * MbedCRC ct; + * + * char test[] = "123456789"; + * uint32_t crc = 0; + * + * printf("\nPolynomial = 0x%lx Width = %d \n", ct.get_polynomial(), ct.get_width()); + * + * ct.compute((void *)test, strlen((const char*)test), &crc); + * + * printf("The CRC of data \"123456789\" is : 0x%lx\n", crc); + * return 0; + * } + * @endcode + * Example: Compute CRC with data available in parts + * @code + * + * #include "mbed.h" + * int main() { + * MbedCRC ct; + * + * char test[] = "123456789"; + * uint32_t crc = 0; + * + * printf("\nPolynomial = 0x%lx Width = %d \n", ct.get_polynomial(), ct.get_width()); + * ct.compute_partial_start(&crc); + * ct.compute_partial((void *)&test, 4, &crc); + * ct.compute_partial((void *)&test[4], 5, &crc); + * ct.compute_partial_stop(&crc); + * printf("The CRC of data \"123456789\" is : 0x%lx\n", crc); + * return 0; + * } + * @endcode + */ +template +class MbedCRC { + impl::MbedCRC crc_impl; + +public: + /* Backwards compatibility */ + enum CrcMode { +#if DEVICE_CRC + HARDWARE = int(::CrcMode::HARDWARE), +#endif + TABLE = int(::CrcMode::TABLE), + BITWISE = int(::CrcMode::BITWISE) + }; + + typedef size_t crc_data_size_t; + + /** Lifetime of CRC object + * + * @param initial_xor Initial value/seed to Xor + * @param final_xor Final Xor value + * @param reflect_data + * @param reflect_remainder + * @note Default constructor without any arguments is valid only for supported CRC polynomials. :: crc_polynomial_t + * MbedCRC ct; --- Valid POLY_7BIT_SD + * MbedCRC <0x1021, 16> ct; --- Valid POLY_16BIT_CCITT + * MbedCRC ct; --- Invalid, compilation error + * MbedCRC ct (i,f,rd,rr) Constructor can be used for not supported polynomials + * MbedCRC sd(0, 0, false, false); Constructor can also be used for supported + * polynomials with different initial/final/reflect values + * + */ + constexpr + MbedCRC(uint32_t initial_xor, uint32_t final_xor, bool reflect_data, bool reflect_remainder) : + crc_impl(initial_xor, final_xor, reflect_data, reflect_remainder) + { + } + + /* Default values for different types of polynomials + */ + // *INDENT-OFF* + template = 0> + constexpr MbedCRC() : MbedCRC(0xFFFFFFFF, 0xFFFFFFFF, true, true) + { + } + + template = 0> + constexpr MbedCRC() : MbedCRC(0, 0, true, true) + { + } + + template = 0> + constexpr MbedCRC() : MbedCRC(0xFFFF, 0, false, false) + { + } + + template = 0> + constexpr MbedCRC() : MbedCRC(0, 0, false, false) + { + } + + template = 0> + constexpr MbedCRC() : MbedCRC(0, 0, false, false) + { + } + // *INDENT-ON* + + /** Compute CRC for the data input + * Compute CRC performs the initialization, computation and collection of + * final CRC. + * + * @param buffer Data bytes + * @param size Size of data + * @param crc CRC is the output value + * @return 0 on success, negative error code on failure + */ + int32_t compute(const void *buffer, crc_data_size_t size, uint32_t *crc) + { + return crc_impl.compute(buffer, size, crc); + } + + /** Compute partial CRC for the data input. + * + * CRC data if not available fully, CRC can be computed in parts with available data. + * + * In case of hardware, intermediate values and states are saved by hardware. Mutex + * locking is used to serialize access to hardware CRC. + * + * In case of software CRC, previous CRC output should be passed as argument to the + * current compute_partial call. Please note the intermediate CRC value is maintained by + * application and not the driver. + * + * @pre: Call `compute_partial_start` to start the partial CRC calculation. + * @post: Call `compute_partial_stop` to get the final CRC value. + * + * @param buffer Data bytes + * @param size Size of data + * @param crc CRC value is intermediate CRC value filled by API. + * @return 0 on success or a negative error code on failure + * @note: CRC as output in compute_partial is not final CRC value, call `compute_partial_stop` + * to get final correct CRC value. + */ + int32_t compute_partial(const void *buffer, crc_data_size_t size, uint32_t *crc) + { + return crc_impl.compute_partial(buffer, size, crc); + } + + /** Compute partial start, indicate start of partial computation. + * + * This API should be called before performing any partial computation + * with compute_partial API. + * + * @param crc Initial CRC value set by the API + * @return 0 on success or a negative in case of failure + * @note: CRC is an out parameter and must be reused with compute_partial + * and `compute_partial_stop` without any modifications in application. + */ + int32_t compute_partial_start(uint32_t *crc) + { + return crc_impl.compute_partial_start(crc); + } + + /** Get the final CRC value of partial computation. + * + * CRC value available in partial computation is not correct CRC, as some + * algorithms require remainder to be reflected and final value to be XORed + * This API is used to perform final computation to get correct CRC value. + * + * @param crc CRC result + * @return 0 on success or a negative in case of failure. + */ + int32_t compute_partial_stop(uint32_t *crc) + { + return crc_impl.compute_partial_stop(crc); + } + + /** Get the current CRC polynomial. + * + * @return Polynomial value + */ + static constexpr uint32_t get_polynomial() + { + return polynomial; + } + + /** Get the current CRC width + * + * @return CRC width + */ + static constexpr uint8_t get_width() + { + return width; + } +}; + +#if !defined(DOXYGEN_ONLY) +/* Internal implementation - basically same as public, but actual mode locked in */ +namespace impl { + +template +class MbedCRC { +public: + typedef size_t crc_data_size_t; + + constexpr + MbedCRC(uint32_t initial_xor, uint32_t final_xor, bool reflect_data, bool reflect_remainder) : + _initial_value(adjust_initial_value(initial_xor, reflect_data)), + _final_xor(final_xor), + _reflect_data(reflect_data), + _reflect_remainder(reflect_remainder) + { + static_assert(width <= 32, "Max 32-bit CRC supported"); + } + + /** Compute CRC for the data input + * Compute CRC performs the initialization, computation and collection of + * final CRC. + * + * @param buffer Data bytes + * @param size Size of data + * @param crc CRC is the output value + * @return 0 on success, negative error code on failure + */ + int32_t compute(const void *buffer, crc_data_size_t size, uint32_t *crc) + { + int32_t status; + + status = compute_partial_start(crc); + if (0 != status) { + return status; + } + + status = compute_partial(buffer, size, crc); + if (0 != status) { + return status; + } + + status = compute_partial_stop(crc); + return status; + } + + /** Compute partial CRC for the data input. + * + * CRC data if not available fully, CRC can be computed in parts with available data. + * + * In case of hardware, intermediate values and states are saved by hardware. Mutex + * locking is used to serialize access to hardware CRC. + * + * In case of software CRC, previous CRC output should be passed as argument to the + * current compute_partial call. Please note the intermediate CRC value is maintained by + * application and not the driver. + * + * @pre: Call `compute_partial_start` to start the partial CRC calculation. + * @post: Call `compute_partial_stop` to get the final CRC value. + * + * @param buffer Data bytes + * @param size Size of data + * @param crc CRC value is intermediate CRC value filled by API. + * @return 0 on success or a negative error code on failure + * @note: CRC as output in compute_partial is not final CRC value, call `compute_partial_stop` + * to get final correct CRC value. + */ + int32_t compute_partial(const void *buffer, crc_data_size_t size, uint32_t *crc) + { + const uint8_t *data = static_cast(buffer); + return do_compute_partial(data, size, crc); + } + + /** Compute partial start, indicate start of partial computation. + * + * This API should be called before performing any partial computation + * with compute_partial API. + * + * @param crc Initial CRC value set by the API + * @return 0 on success or a negative in case of failure + * @note: CRC is an out parameter and must be reused with compute_partial + * and `compute_partial_stop` without any modifications in application. + */ + int32_t compute_partial_start(uint32_t *crc) + { +#if DEVICE_CRC + if (mode == CrcMode::HARDWARE) { + lock(); + crc_mbed_config_t config; + config.polynomial = polynomial; + config.width = width; + config.initial_xor = _initial_value; + config.final_xor = _final_xor; + config.reflect_in = _reflect_data; + config.reflect_out = _reflect_remainder; + + hal_crc_compute_partial_start(&config); + } +#endif + + *crc = _initial_value; + return 0; + } + + /** Get the final CRC value of partial computation. + * + * CRC value available in partial computation is not correct CRC, as some + * algorithms require remainder to be reflected and final value to be XORed + * This API is used to perform final computation to get correct CRC value. + * + * @param crc CRC result + * @return 0 on success or a negative in case of failure. + */ + int32_t compute_partial_stop(uint32_t *crc) + { +#if DEVICE_CRC + if (mode == CrcMode::HARDWARE) { + *crc = hal_crc_get_result(); + unlock(); + return 0; + } +#endif + uint_fast32_t p_crc = *crc; + if (mode == CrcMode::BITWISE) { + if (_reflect_data) { + /* CRC has MSB in bottom bit of register */ + if (!_reflect_remainder) { + p_crc = reflect_crc(p_crc); + } + } else { + /* CRC has MSB in top bit of register */ + p_crc = _reflect_remainder ? reflect(p_crc) : shift_right(p_crc); + } + } else { // TABLE + /* CRC has MSB in bottom bit of register */ + if (!_reflect_remainder) { + p_crc = reflect_crc(p_crc); + } + } + + p_crc ^= _final_xor; + p_crc &= get_crc_mask(); + *crc = p_crc; + + return 0; + } + +private: + /** Guaranteed constexpr reflection (all toolchains) + * + * @note This should never be run-time evaluated - very inefficient + * @param Register value to be reflected (full 32-bit value) + * @return Reflected value (full 32-bit value) + */ + static constexpr uint32_t reflect_constant(uint32_t data) + { + /* Doing this hard way to keep it C++11 constexpr and hence ARM C 5 compatible */ + return ((data & 0x00000001) << 31) | + ((data & 0x00000002) << 29) | + ((data & 0x00000004) << 27) | + ((data & 0x00000008) << 25) | + ((data & 0x00000010) << 23) | + ((data & 0x00000020) << 21) | + ((data & 0x00000040) << 19) | + ((data & 0x00000080) << 17) | + ((data & 0x00000100) << 15) | + ((data & 0x00000200) << 13) | + ((data & 0x00000400) << 11) | + ((data & 0x00000800) << 9) | + ((data & 0x00001000) << 7) | + ((data & 0x00002000) << 5) | + ((data & 0x00004000) << 3) | + ((data & 0x00008000) << 1) | + ((data & 0x00010000) >> 1) | + ((data & 0x00020000) >> 3) | + ((data & 0x00040000) >> 5) | + ((data & 0x00080000) >> 7) | + ((data & 0x00100000) >> 9) | + ((data & 0x00200000) >> 11) | + ((data & 0x00400000) >> 13) | + ((data & 0x00800000) >> 15) | + ((data & 0x01000000) >> 17) | + ((data & 0x02000000) >> 19) | + ((data & 0x04000000) >> 21) | + ((data & 0x08000000) >> 23) | + ((data & 0x10000000) >> 25) | + ((data & 0x20000000) >> 27) | + ((data & 0x40000000) >> 29) | + ((data & 0x80000000) >> 31); + } + + /** General reflection + * + * @note This is used when we may need to perform run-time computation, so + * we need the possibility to produce the optimal run-time RBIT instruction. But + * if the compiler doesn't treat RBIT as a built-in, it's useful to have a C fallback + * for the constant case, avoiding runtime RBIT(0) computations. This is an + * optimization only available for some toolchains; others will always use runtime + * RBIT. If we require a constant expression, use reflect_constant instead. + * + * @param Register value to be reflected (full 32-bit value) + * @return Reflected value (full 32-bit value) + */ +#ifdef MSTD_HAS_IS_CONSTANT_EVALUATED + static constexpr uint32_t reflect(uint32_t data) + { + return mstd::is_constant_evaluated() ? reflect_constant(data) : __RBIT(data); + } +#else + static uint32_t reflect(uint32_t data) + { + return __RBIT(data); + } +#endif + + /** Data bytes may need to be reflected. + * + * @param data value to be reflected (bottom 8 bits) + * @return Reflected value (bottom 8 bits) + */ + static + uint_fast32_t reflect_byte(uint_fast32_t data) + { + return reflect(data) >> 24; + } + + /** Get the current CRC polynomial, reflected at bottom of register. + * + * @return Reflected polynomial value (so x^width term would be at bit -1) + */ + static constexpr uint32_t get_reflected_polynomial() + { + return shift_right(reflect_constant(polynomial)); + } + + /** Get the current CRC polynomial, at top of register. + * + * @return Shifted polynomial value (so x^width term would be at bit 32) + */ + static constexpr uint32_t get_top_polynomial() + { + return shift_left(polynomial); + } + + const uint32_t _initial_value; + const uint32_t _final_xor; + const bool _reflect_data; + const bool _reflect_remainder; + + // *INDENT-OFF* + using crc_table_t = std::conditional_t>; + // *INDENT-ON* + +#if MBED_CRC_TABLE_SIZE > 0 + /* Tables only actually defined for mode == TABLE, and certain polynomials - see below */ + static const crc_table_t _crc_table[MBED_CRC_TABLE_SIZE]; +#endif + + static constexpr uint32_t adjust_initial_value(uint32_t initial_xor, bool reflect_data) + { + if (mode == CrcMode::BITWISE) { + /* For bitwise calculation, CRC register is reflected if data is, to match input. + * (MSB at bottom of register). If not reflected, it is at the top of the register + * (MSB at top of register). + */ + return reflect_data ? reflect_crc(initial_xor) : shift_left(initial_xor); + } else if (mode == CrcMode::TABLE) { + /* For table calculation, CRC value is reflected, to match tables. + * (MSB at bottom of register). */ + return reflect_crc(initial_xor); + } else { // CrcMode::HARDWARE + return initial_xor; + } + } + + /** Acquire exclusive access to CRC hardware/software. + */ + static void lock() + { +// #if DEVICE_CRC +// if (mode == CrcMode::HARDWARE) { +// mbed_crc_mutex->lock(); +// } +// #endif + } + + /** Release exclusive access to CRC hardware/software. + */ + static void unlock() + { +// #if DEVICE_CRC +// if (mode == CrcMode::HARDWARE) { +// mbed_crc_mutex->unlock(); +// } +// #endif + } + + /** Get the CRC data mask. + * + * @return CRC data mask is generated based on current CRC width + */ + static constexpr uint32_t get_crc_mask() + { + return (uint32_t)((uint32_t)2U << (width - 1)) - 1U; + } + + /** CRC values may need to be reflected. + * + * @param CRC value to be reflected (width bits at bottom of 32-bit word) + * @return Reflected value (still at bottom of 32-bit word) + */ + static + uint32_t reflect_crc(uint32_t data) + { + return reflect(data) >> (32 - width); + } + + /** Register values may need to be shifted left. + * + * @param Register value to be shifted up (in bottom width bits) + * @return Shifted value (in top width bits) + */ + static constexpr uint32_t shift_left(uint32_t data) + { + return data << (32 - width); + } + + /** Register values may need to be shifted right. + * + * @param Register value to be shifted right (in top width bits) + * @return Shifted value (in bottom width bits) + */ + static constexpr uint32_t shift_right(uint32_t data) + { + return data >> (32 - width); + } + + /* Check to see if we can do assembler optimizations */ +#if (defined __GNUC__ || defined __clang__) && \ + (defined __arm__ || defined __ARM_ARCH) +#if (__ARM_ARCH_7M__ == 1U) || \ + (__ARM_ARCH_7EM__ == 1U) || \ + (__ARM_ARCH_8M_MAIN__ == 1U) || \ + (__ARM_ARCH_8_1M_MAIN__ == 1U) || \ + (__ARM_ARCH_7A__ == 1U) + /* ARM that has Thumb-2 - same unified assembly is good for either ARM or Thumb state (LSRS; IT CS; EORCS reg/imm) */ +#define MBED_CRC_ARM_THUMB2 1 +#define MBED_CRC_THUMB1 0 +#elif (__ARM_ARCH_6M__ == 1U) || \ + (__ARM_ARCH_8M_BASE__ == 1U) + /* Thumb-1-only ARM-M device - use Thumb-1 compatible assembly with branch (LSRS; BCC; EORS reg) */ +#define MBED_CRC_ARM_THUMB2 0 +#define MBED_CRC_THUMB1 1 +#else // __ARM_ARCH_xxx +#error "Unknown ARM architecture for CRC optimization" +#endif // __ARM_ARCH_xxx +#else // __arm__ || defined __ICC_ARM__ || defined __ARM_ARCH + /* Seem to be compiling for non-ARM, or an unsupported toolchain, so stick with C implementations */ +#define MBED_CRC_ARM_THUMB2 0 +#define MBED_CRC_THUMB1 0 +#endif + + // *INDENT-OFF* + /** Process 1 bit of non-reflected CRC + * + * Shift the p_crc register left 1 bit - if a one is shifted + * out, exclusive-or with the polynomial mask. + * + * Assembler optimizations can be applied here, to make + * use of the CPU's carry output from shifts. + * + * @param p_crc input register value + * @return updated register value + */ + static uint_fast32_t do_1_bit_normal(uint_fast32_t p_crc) + { +#if MBED_CRC_ARM_THUMB2 + __asm(".syntax unified\n\t" + "LSLS" "\t%[p_crc], %[p_crc], #1\n\t" + "IT" "\tCS\n\t" + "EORCS" "\t%[p_crc], %[poly]" + : [p_crc] "+&r" (p_crc) + : [poly] "rI" (get_top_polynomial()) + : "cc"); +#elif MBED_CRC_THUMB1 + __asm(".syntax unified\n\t" + "LSLS" "\t%[p_crc], %[p_crc], #1\n\t" + "BCC" "\t%=f\n\t" + "EORS" "\t%[p_crc], %[poly]\n" + "%=:" + : [p_crc] "+&l" (p_crc) + : [poly] "l" (get_top_polynomial()) + : "cc"); +#else + if (p_crc & 0x80000000) { + p_crc = (p_crc << 1) ^ get_top_polynomial(); + } else { + p_crc = (p_crc << 1); + } +#endif + return p_crc; + } + + /** Process 1 bit of reflected CRC + * + * Shift the p_crc register right 1 bit - if a one is shifted + * out, exclusive-or with the polynomial mask. + * + * Assembler optimizations can be applied here, to make + * use of the CPU's carry output from shifts. + * + * @param p_crc input register value + * @return updated register value + */ + static uint_fast32_t do_1_bit_reflected(uint_fast32_t p_crc) + { +#if MBED_CRC_ARM_THUMB2 + __asm(".syntax unified\n\t" + "LSRS" "\t%[p_crc], %[p_crc], #1\n\t" + "IT" "\tCS\n\t" + "EORCS" "\t%[p_crc], %[poly]" + : [p_crc] "+&r" (p_crc) + : [poly] "rI" (get_reflected_polynomial()) + : "cc"); +#elif MBED_CRC_THUMB1 + __asm(".syntax unified\n\t" + "LSRS" "\t%[p_crc], %[p_crc], #1\n\t" + "BCC" "\t%=f\n\t" + "EORS" "\t%[p_crc], %[poly]\n" + "%=:" + : [p_crc] "+&l" (p_crc) + : [poly] "l" (get_reflected_polynomial()) + : "cc"); +#else + if (p_crc & 1) { + p_crc = (p_crc >> 1) ^ get_reflected_polynomial(); + } else { + p_crc = (p_crc >> 1); + } +#endif + return p_crc; + } + // *INDENT-ON* + + /** Bitwise CRC computation. + * + * @param buffer data buffer + * @param size size of the data + * @param crc CRC value is filled in, but the value is not the final + * @return 0 on success or a negative error code on failure + */ + template + std::enable_if_t + do_compute_partial(const uint8_t *data, crc_data_size_t size, uint32_t *crc) const + { + uint_fast32_t p_crc = *crc; + + if (_reflect_data) { + /* Everything is reflected to match data - MSB of polynomial at bottom of 32-bit register */ + for (crc_data_size_t byte = 0; byte < size; byte++) { + p_crc ^= data[byte]; + + // Perform modulo-2 division, a bit at a time + for (unsigned int bit = 8; bit > 0; --bit) { + p_crc = do_1_bit_reflected(p_crc); + } + } + } else { + /* Polynomial is shifted to put MSB of polynomial at top of 32-bit register */ + for (crc_data_size_t byte = 0; byte < size; byte++) { + p_crc ^= (uint_fast32_t) data[byte] << 24; + + // Perform modulo-2 division, a bit at a time + for (unsigned int bit = 8; bit > 0; --bit) { + p_crc = do_1_bit_normal(p_crc); + } + } + } + + *crc = p_crc; + + return 0; + } + +#if MBED_CRC_TABLE_SIZE > 0 + /** CRC computation using ROM tables. + * + * @param buffer data buffer + * @param size size of the data + * @param crc CRC value is filled in, but the value is not the final + * @return 0 on success or a negative error code on failure + */ + template + std::enable_if_t + do_compute_partial(const uint8_t *data, crc_data_size_t size, uint32_t *crc) const + { + uint_fast32_t p_crc = *crc; + // GCC has been observed to not hoist the load of _reflect_data out of the loop + // Note the inversion because table and CRC are reflected - data must be + bool reflect = !_reflect_data; + + for (crc_data_size_t byte = 0; byte < size; byte++) { + uint_fast32_t data_byte = data[byte]; + if (reflect) { + data_byte = reflect_byte(data_byte); + } +#if MBED_CRC_TABLE_SIZE == 16 + p_crc = _crc_table[(data_byte ^ p_crc) & 0xF] ^ (p_crc >> 4); + data_byte >>= 4; + p_crc = _crc_table[(data_byte ^ p_crc) & 0xF] ^ (p_crc >> 4); +#else + p_crc = _crc_table[(data_byte ^ p_crc) & 0xFF] ^ (p_crc >> 8); +#endif + } + *crc = p_crc; + return 0; + } +#endif + +#ifdef DEVICE_CRC + /** Hardware CRC computation. + * + * @param buffer data buffer + * @param size size of the data + * @return 0 on success or a negative error code on failure + */ + template + std::enable_if_t + do_compute_partial(const uint8_t *data, crc_data_size_t size, uint32_t *) const + { + hal_crc_compute_partial(data, size); + return 0; + } +#endif + +}; + +#if MBED_CRC_TABLE_SIZE > 0 +/* Declarations of the tables we provide. (Not strictly needed, but compilers + * can warn if they see us using the template without a generic definition, so + * let it know we have provided these specialisations.) + */ +template<> +const uint8_t MbedCRC::_crc_table[MBED_CRC_TABLE_SIZE]; + +template<> +const uint8_t MbedCRC::_crc_table[MBED_CRC_TABLE_SIZE]; + +template<> +const uint16_t MbedCRC::_crc_table[MBED_CRC_TABLE_SIZE]; + +template<> +const uint16_t MbedCRC::_crc_table[MBED_CRC_TABLE_SIZE]; + +template<> +const uint32_t MbedCRC::_crc_table[MBED_CRC_TABLE_SIZE]; + +#endif // MBED_CRC_TABLE_SIZE > 0 + +} // namespace impl + +#endif // !defined(DOXYGEN_ONLY) + +/** @}*/ +/** @}*/ + +// } // namespace mbed + +#endif // __cplusplus + +/* Internal helper for mbed_error.c crash recovery */ +#ifdef __cplusplus +extern "C" +#endif +uint32_t mbed_tiny_compute_crc32(const void *data, int datalen); + +#endif diff --git a/libraries/KVStore/src/TDBStore.cpp b/libraries/KVStore/src/TDBStore.cpp new file mode 100644 index 000000000..ab28ec12e --- /dev/null +++ b/libraries/KVStore/src/TDBStore.cpp @@ -0,0 +1,1512 @@ +/* + * Copyright (c) 2018 ARM Limited. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ----------------------------------------------------------- Includes ----------------------------------------------------------- + +#include "TDBStore.h" + +#include +#include +#include +#include "MbedCRC.h" + +using namespace mbed; + +// --------------------------------------------------------- Definitions ---------------------------------------------------------- + +static const uint32_t delete_flag = (1UL << 31); +static const uint32_t internal_flags = delete_flag; +// Only write once flag is supported, other two are kept in storage but ignored +static const uint32_t supported_flags = KVStore::WRITE_ONCE_FLAG | KVStore::REQUIRE_CONFIDENTIALITY_FLAG | KVStore::REQUIRE_REPLAY_PROTECTION_FLAG; + +namespace { + +typedef struct { + uint32_t magic; + uint16_t header_size; + uint16_t revision; + uint32_t flags; + uint16_t key_size; + uint16_t reserved; + uint32_t data_size; + uint32_t crc; +} record_header_t; + +typedef struct { + uint32_t hash; + bd_size_t bd_offset; +} ram_table_entry_t; + +static const char *master_rec_key = "TDBS"; +static const uint32_t tdbstore_magic = 0x54686683; +static const uint32_t tdbstore_revision = 1; + +typedef struct { + uint16_t version; + uint16_t tdbstore_revision; + uint32_t reserved; +} master_record_data_t; + +typedef enum { + TDBSTORE_AREA_STATE_NONE = 0, + TDBSTORE_AREA_STATE_ERASED, + TDBSTORE_AREA_STATE_INVALID, + TDBSTORE_AREA_STATE_VALID, +} area_state_e; + +typedef struct { + uint16_t trailer_size; + uint16_t data_size; + uint32_t crc; +} reserved_trailer_t; + +static const size_t min_work_buf_size = 64; +static const uint32_t initial_crc = 0xFFFFFFFF; +static const uint32_t initial_max_keys = 16; + +// incremental set handle +typedef struct { + record_header_t header; + bd_size_t bd_base_offset; + bd_size_t bd_curr_offset; + uint32_t offset_in_data; + uint32_t ram_table_ind; + uint32_t hash; + bool new_key; +} inc_set_handle_t; + +// iterator handle +typedef struct { + int iterator_num; + uint32_t ram_table_ind; + char *prefix; +} key_iterator_handle_t; + +} // anonymous namespace + + +// -------------------------------------------------- Local Functions Declaration ---------------------------------------------------- + +// -------------------------------------------------- Functions Implementation ---------------------------------------------------- + +static inline uint32_t align_up(uint32_t val, uint32_t size) +{ + return (((val - 1) / size) + 1) * size; +} + +static inline uint32_t align_down(uint64_t val, uint64_t size) +{ + return (((val) / size)) * size; +} + + +static uint32_t calc_crc(uint32_t init_crc, uint32_t data_size, const void *data_buf) +{ + uint32_t crc; + MbedCRC ct(init_crc, 0x0, true, false); + ct.compute(data_buf, data_size, &crc); + return crc; +} + +// Class member functions + +TDBStore::TDBStore(BlockDevice *bd) : _ram_table(0), _max_keys(0), + _num_keys(0), _bd(bd), _buff_bd(0), _free_space_offset(0), _master_record_offset(0), + _master_record_size(0), _is_initialized(false), _active_area(0), _active_area_version(0), _size(0), + _area_params{}, _prog_size(0), _work_buf(0), _work_buf_size(0), _key_buf(0), _inc_set_handle(0) +{ + for (int i = 0; i < _num_areas; i++) { + _area_params[i] = { 0 }; + } + for (int i = 0; i < _max_open_iterators; i++) { + _iterator_table[i] = { 0 }; + } +} + +TDBStore::~TDBStore() +{ + deinit(); +} + +int TDBStore::read_area(uint8_t area, uint32_t offset, uint32_t size, void *buf) +{ + //Check that we are not crossing area boundary + if (offset + size > _size) { + return KVSTORE_ERROR_READ_FAILED; + } + int os_ret = _buff_bd->read(buf, _area_params[area].address + offset, size); + + if (os_ret) { + return KVSTORE_ERROR_READ_FAILED; + } + + return KVSTORE_SUCCESS; +} + +int TDBStore::write_area(uint8_t area, uint32_t offset, uint32_t size, const void *buf) +{ + int os_ret = _buff_bd->program(buf, _area_params[area].address + offset, size); + if (os_ret) { + return KVSTORE_ERROR_WRITE_FAILED; + } + + return KVSTORE_SUCCESS; +} + +int TDBStore::erase_area(uint8_t area, uint32_t offset, uint32_t size) +{ + uint32_t bd_offset = _area_params[area].address + offset; + + int ret = _buff_bd->erase(bd_offset, size); + if (ret) { + return ret; + } + + if (_buff_bd->get_erase_value() == -1) { + // We need to simulate erase to wipe records, as our block device + // may not do it. Program in chunks of _work_buf_size if the minimum + // program size is too small (e.g. one-byte) to avoid performance + // issues. + MBED_ASSERT(_work_buf != nullptr); + MBED_ASSERT(_work_buf_size != 0); + memset(_work_buf, 0xFF, _work_buf_size); + while (size) { + uint32_t chunk = std::min(_work_buf_size, size); + ret = _buff_bd->program(_work_buf, bd_offset, chunk); + if (ret) { + return ret; + } + size -= chunk; + bd_offset += chunk; + } + } + return KVSTORE_SUCCESS; +} + +void TDBStore::calc_area_params() +{ + // TDBStore can't exceed 32 bits + bd_size_t bd_size = std::min(_bd->size(), 0x80000000L); + + memset(_area_params, 0, sizeof(_area_params)); + size_t area_0_size = 0; + size_t area_1_size = 0; + + // The size calculations are a bit complex because we need to make sure we're + // always aligned to an erase block boundary (whose size may not be uniform + // across the address space), and we also need to make sure that the first + // area never goes over half of the total size even if bd_size is an odd + // number of sectors. + while (true) { + bd_size_t erase_unit_size = _bd->get_erase_size(area_0_size); + if (area_0_size + erase_unit_size <= (bd_size / 2)) { + area_0_size += erase_unit_size; + } else { + break; + } + } + + while (true) { + bd_size_t erase_unit_size = _bd->get_erase_size(area_0_size + area_1_size); + if (area_1_size + erase_unit_size <= (bd_size / 2)) { + area_1_size += erase_unit_size; + } else { + break; + } + } + + _area_params[0].address = 0; + _area_params[0].size = area_0_size; + _area_params[1].address = area_0_size; + _area_params[1].size = area_1_size; + + // The areas must be of same size + MBED_ASSERT(_area_params[0].size == _area_params[1].size); +} + + +// This function, reading a record from the BD, is used for multiple purposes: +// - Init (scan all records, no need to return file name and data) +// - Get (return file data) +// - Get first/next file (check whether name matches, return name if so) +int TDBStore::read_record(uint8_t area, uint32_t offset, char *key, + void *data_buf, uint32_t data_buf_size, + uint32_t &actual_data_size, size_t data_offset, bool copy_key, + bool copy_data, bool check_expected_key, bool calc_hash, + uint32_t &hash, uint32_t &flags, uint32_t &next_offset) +{ + int ret; + record_header_t header; + uint32_t total_size, key_size, data_size; + uint32_t curr_data_offset; + char *user_key_ptr; + uint32_t crc = initial_crc; + // Upper layers typically use non zero offsets for reading the records chunk by chunk, + // so only validate entire record at first chunk (otherwise we'll have a serious performance penalty). + bool validate = (data_offset == 0); + + ret = KVSTORE_SUCCESS; + // next offset should only be updated to the end of record if successful + next_offset = offset; + + ret = read_area(area, offset, sizeof(header), &header); + if (ret) { + return ret; + } + + if (header.magic != tdbstore_magic) { + return KVSTORE_ERROR_INVALID_DATA_DETECTED; + } + + offset += align_up(sizeof(header), _prog_size); + + key_size = header.key_size; + data_size = header.data_size; + flags = header.flags; + + if ((!key_size) || (key_size >= MAX_KEY_SIZE)) { + return KVSTORE_ERROR_INVALID_DATA_DETECTED; + } + + total_size = key_size + data_size; + + // Make sure our read sizes didn't cause any wraparounds + if ((total_size < key_size) || (total_size < data_size)) { + return KVSTORE_ERROR_INVALID_DATA_DETECTED; + } + + if (offset + total_size >= _size) { + return KVSTORE_ERROR_INVALID_DATA_DETECTED; + } + + if (data_offset > data_size) { + return KVSTORE_ERROR_INVALID_SIZE; + } + + actual_data_size = std::min(data_buf_size, data_size - data_offset); + + if (copy_data && actual_data_size && !data_buf) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + if (validate) { + // Calculate CRC on header (excluding CRC itself) + crc = calc_crc(crc, sizeof(record_header_t) - sizeof(crc), &header); + curr_data_offset = 0; + } else { + // Non validation case: No need to read the key, nor the parts before data_offset + // or after the actual part requested by the user. + total_size = actual_data_size; + curr_data_offset = data_offset; + offset += data_offset + key_size; + // Mark code that key handling is finished + key_size = 0; + } + + user_key_ptr = key; + hash = initial_crc; + + while (total_size) { + uint8_t *dest_buf; + uint32_t chunk_size; + if (key_size) { + // This means that we're on the key part + if (copy_key) { + dest_buf = reinterpret_cast(user_key_ptr); + chunk_size = key_size; + user_key_ptr[key_size] = '\0'; + } else { + dest_buf = _work_buf; + chunk_size = std::min(key_size, _work_buf_size); + } + } else { + // This means that we're on the data part + // We have four cases that need different handling: + // 1. Before data_offset - read to work buffer + // 2. After data_offset, but before actual part is finished - read to user buffer + // 3. After actual part is finished - read to work buffer + // 4. Copy data flag not set - read to work buffer + if (curr_data_offset < data_offset) { + chunk_size = std::min(_work_buf_size, (data_offset - curr_data_offset)); + dest_buf = _work_buf; + } else if (copy_data && (curr_data_offset < data_offset + actual_data_size)) { + chunk_size = actual_data_size; + dest_buf = static_cast(data_buf); + } else { + chunk_size = std::min(_work_buf_size, total_size); + dest_buf = _work_buf; + } + } + ret = read_area(area, offset, chunk_size, dest_buf); + if (ret) { + goto end; + } + + if (validate) { + // calculate CRC on current read chunk + crc = calc_crc(crc, chunk_size, dest_buf); + } + + if (key_size) { + // We're on key part. May need to calculate hash or check whether key is the expected one + if (check_expected_key) { + if (memcmp(user_key_ptr, dest_buf, chunk_size)) { + ret = KVSTORE_ERROR_ITEM_NOT_FOUND; + } + } + + if (calc_hash) { + hash = calc_crc(hash, chunk_size, dest_buf); +#ifdef KVSTORE_RAM_TABLE_NO_CRC_CHECK + next_offset = align_up(offset + total_size, _prog_size); + return ret; +#endif /* KVSTORE_RAM_TABLE_NO_CRC_CHECK */ + } + + user_key_ptr += chunk_size; + key_size -= chunk_size; + if (!key_size) { + offset += data_offset; + } + } else { + curr_data_offset += chunk_size; + } + + total_size -= chunk_size; + offset += chunk_size; + } + + if (validate && (crc != header.crc)) { + ret = KVSTORE_ERROR_INVALID_DATA_DETECTED; + goto end; + } + + next_offset = align_up(offset, _prog_size); + +end: + return ret; +} + +int TDBStore::find_record(uint8_t area, const char *key, uint32_t &offset, + uint32_t &ram_table_ind, uint32_t &hash) +{ + ram_table_entry_t *ram_table = (ram_table_entry_t *) _ram_table; + ram_table_entry_t *entry; + int ret = KVSTORE_ERROR_ITEM_NOT_FOUND; + uint32_t actual_data_size; + uint32_t flags, dummy_hash, next_offset; + + + hash = calc_crc(initial_crc, strlen(key), key); + + for (ram_table_ind = 0; ram_table_ind < _num_keys; ram_table_ind++) { + entry = &ram_table[ram_table_ind]; + offset = entry->bd_offset; + if (hash < entry->hash) { + continue; + } + if (hash > entry->hash) { + return KVSTORE_ERROR_ITEM_NOT_FOUND; + } + ret = read_record(_active_area, offset, const_cast(key), 0, 0, actual_data_size, 0, + false, false, true, false, dummy_hash, flags, next_offset); + // not found return code here means that hash doesn't belong to name. Continue searching. + if (ret != KVSTORE_ERROR_ITEM_NOT_FOUND) { + break; + } + } + + return ret; +} + +uint32_t TDBStore::record_size(const char *key, uint32_t data_size) +{ + return align_up(sizeof(record_header_t), _prog_size) + + align_up(strlen(key) + data_size, _prog_size); +} + + +int TDBStore::set_start(set_handle_t *handle, const char *key, size_t final_data_size, + uint32_t create_flags) +{ + int ret; + uint32_t offset = 0; + uint32_t hash = 0, ram_table_ind = 0; + inc_set_handle_t *ih; + bool need_gc = false; + + if (!is_valid_key(key)) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + if (create_flags & ~(supported_flags | internal_flags)) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + *handle = reinterpret_cast(_inc_set_handle); + ih = reinterpret_cast(*handle); + + if (!strcmp(key, master_rec_key)) { + // Master record - special case (no need to protect by the mutex, as it is already covered + // in the upper layers). + ih->bd_base_offset = _master_record_offset; + ih->new_key = false; + ram_table_ind = 0; + hash = 0; + } else { + + _mutex.lock(); + + // A valid magic in the header means that this function has been called after an aborted + // incremental set process. This means that our media may be in a bad state - call GC. + if (ih->header.magic == tdbstore_magic) { + ret = garbage_collection(); + if (ret) { + goto fail; + } + } + + // If we have no room for the record, perform garbage collection + uint32_t rec_size = record_size(key, final_data_size); + if (_free_space_offset + rec_size > _size) { + ret = garbage_collection(); + if (ret) { + goto fail; + } + } + + // If even after GC we have no room for the record, return error + if (_free_space_offset + rec_size > _size) { + ret = KVSTORE_ERROR_MEDIA_FULL; + goto fail; + } + + ret = find_record(_active_area, key, offset, ram_table_ind, hash); + + if (ret == KVSTORE_SUCCESS) { + ret = read_area(_active_area, offset, sizeof(ih->header), &ih->header); + if (ret) { + goto fail; + } + if (ih->header.flags & WRITE_ONCE_FLAG) { + ret = KVSTORE_ERROR_WRITE_PROTECTED; + goto fail; + } + ih->new_key = false; + } else if (ret == KVSTORE_ERROR_ITEM_NOT_FOUND) { + if (create_flags & delete_flag) { + goto fail; + } + if (_num_keys >= _max_keys) { + increment_max_keys(); + } + ih->new_key = true; + } else { + goto fail; + } + ih->bd_base_offset = _free_space_offset; + + check_erase_before_write(_active_area, ih->bd_base_offset, rec_size); + } + + ret = KVSTORE_SUCCESS; + + // Fill handle and header fields + // Jump to offset after header (header will be written at finalize phase) + ih->bd_curr_offset = ih->bd_base_offset + align_up(sizeof(record_header_t), _prog_size); + ih->offset_in_data = 0; + ih->hash = hash; + ih->ram_table_ind = ram_table_ind; + ih->header.magic = tdbstore_magic; + ih->header.header_size = sizeof(record_header_t); + ih->header.revision = tdbstore_revision; + ih->header.flags = create_flags; + ih->header.key_size = strlen(key); + ih->header.reserved = 0; + ih->header.data_size = final_data_size; + // Calculate CRC on header and key + ih->header.crc = calc_crc(initial_crc, sizeof(record_header_t) - sizeof(ih->header.crc), &ih->header); + ih->header.crc = calc_crc(ih->header.crc, ih->header.key_size, key); + + // Write key now + ret = write_area(_active_area, ih->bd_curr_offset, ih->header.key_size, key); + if (ret) { + need_gc = true; + goto fail; + } + ih->bd_curr_offset += ih->header.key_size; + goto end; + +fail: + if ((need_gc) && (ih->bd_base_offset != _master_record_offset)) { + garbage_collection(); + } + // mark handle as invalid by clearing magic field in header + ih->header.magic = 0; + + _mutex.unlock(); + +end: + return ret; +} + +int TDBStore::set_add_data(set_handle_t handle, const void *value_data, size_t data_size) +{ + int ret = KVSTORE_SUCCESS; + inc_set_handle_t *ih; + bool need_gc = false; + + if (handle != _inc_set_handle) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + if (!value_data && data_size) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + _inc_set_mutex.lock(); + + ih = reinterpret_cast(handle); + + if (!ih->header.magic) { + ret = KVSTORE_ERROR_INVALID_ARGUMENT; + goto end; + } + + if (ih->offset_in_data + data_size > ih->header.data_size) { + ret = KVSTORE_ERROR_INVALID_SIZE; + goto end; + } + + // Update CRC with data chunk + ih->header.crc = calc_crc(ih->header.crc, data_size, value_data); + + // Write the data chunk + ret = write_area(_active_area, ih->bd_curr_offset, data_size, value_data); + if (ret) { + need_gc = true; + goto end; + } + ih->bd_curr_offset += data_size; + ih->offset_in_data += data_size; + +end: + if ((need_gc) && (ih->bd_base_offset != _master_record_offset)) { + garbage_collection(); + } + + _inc_set_mutex.unlock(); + return ret; +} + +int TDBStore::set_finalize(set_handle_t handle) +{ + int os_ret, ret = KVSTORE_SUCCESS; + inc_set_handle_t *ih; + ram_table_entry_t *ram_table = (ram_table_entry_t *) _ram_table; + ram_table_entry_t *entry; + bool need_gc = false; + uint32_t actual_data_size, hash, flags, next_offset; + + if (handle != _inc_set_handle) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + ih = reinterpret_cast(handle); + + if (!ih->header.magic) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + _inc_set_mutex.lock(); + + if (ih->offset_in_data != ih->header.data_size) { + ret = KVSTORE_ERROR_INVALID_SIZE; + need_gc = true; + goto end; + } + + // Write header + ret = write_area(_active_area, ih->bd_base_offset, sizeof(record_header_t), &ih->header); + if (ret) { + need_gc = true; + goto end; + } + + // Need to flush buffered BD as our record is totally written now + os_ret = _buff_bd->sync(); + if (os_ret) { + ret = KVSTORE_ERROR_WRITE_FAILED; + need_gc = true; + goto end; + } + + // In master record case we don't update RAM table + if (ih->bd_base_offset == _master_record_offset) { + goto end; + } + + // Writes may fail without returning a failure (especially in flash components). Reread the record + // to ensure write success (this won't read the data anywhere - just use the CRC calculation). + ret = read_record(_active_area, ih->bd_base_offset, 0, 0, (uint32_t) -1, + actual_data_size, 0, false, false, false, false, + hash, flags, next_offset); + if (ret) { + need_gc = true; + goto end; + } + + // Update RAM table + if (ih->header.flags & delete_flag) { + _num_keys--; + if (ih->ram_table_ind < _num_keys) { + memmove(&ram_table[ih->ram_table_ind], &ram_table[ih->ram_table_ind + 1], + sizeof(ram_table_entry_t) * (_num_keys - ih->ram_table_ind)); + } + update_all_iterators(false, ih->ram_table_ind); + } else { + if (ih->new_key) { + if (ih->ram_table_ind < _num_keys) { + memmove(&ram_table[ih->ram_table_ind + 1], &ram_table[ih->ram_table_ind], + sizeof(ram_table_entry_t) * (_num_keys - ih->ram_table_ind)); + } + _num_keys++; + update_all_iterators(true, ih->ram_table_ind); + } + entry = &ram_table[ih->ram_table_ind]; + entry->hash = ih->hash; + entry->bd_offset = ih->bd_base_offset; + } + + _free_space_offset = align_up(ih->bd_curr_offset, _prog_size); + + // Safety check: If there seems to be valid keys on the free space + // we should erase one sector more, just to ensure that in case of power failure + // next init() would not extend the scan phase to that section as well. + os_ret = read_record(_active_area, _free_space_offset, 0, 0, 0, actual_data_size, 0, + false, false, false, false, hash, flags, next_offset); + if (os_ret == KVSTORE_SUCCESS) { + check_erase_before_write(_active_area, _free_space_offset, sizeof(record_header_t)); + } + +end: + // mark handle as invalid by clearing magic field in header + ih->header.magic = 0; + + _inc_set_mutex.unlock(); + + if (ih->bd_base_offset != _master_record_offset) { + if (need_gc) { + garbage_collection(); + } + _mutex.unlock(); + } + return ret; +} + +int TDBStore::set(const char *key, const void *buffer, size_t size, uint32_t create_flags) +{ + int ret; + set_handle_t handle; + + // Don't wait till we get to set_add_data to catch this + if (!buffer && size) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + ret = set_start(&handle, key, size, create_flags); + if (ret) { + return ret; + } + + ret = set_add_data(handle, buffer, size); + if (ret) { + return ret; + } + + ret = set_finalize(handle); + return ret; +} + +int TDBStore::remove(const char *key) +{ + return set(key, 0, 0, delete_flag); +} + +int TDBStore::get(const char *key, void *buffer, size_t buffer_size, size_t *actual_size, size_t offset) +{ + int ret; + uint32_t actual_data_size; + uint32_t bd_offset, next_bd_offset; + uint32_t flags, hash, ram_table_ind; + + if (!is_valid_key(key)) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + _mutex.lock(); + + ret = find_record(_active_area, key, bd_offset, ram_table_ind, hash); + + if (ret != KVSTORE_SUCCESS) { + goto end; + } + + ret = read_record(_active_area, bd_offset, const_cast(key), buffer, buffer_size, + actual_data_size, offset, false, true, false, false, hash, flags, next_bd_offset); + + if (actual_size) { + *actual_size = actual_data_size; + } + +end: + _mutex.unlock(); + return ret; +} + +int TDBStore::get_info(const char *key, info_t *info) +{ + int ret; + uint32_t bd_offset, next_bd_offset; + uint32_t flags, hash, ram_table_ind; + uint32_t actual_data_size; + + if (!is_valid_key(key)) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + _mutex.lock(); + + ret = find_record(_active_area, key, bd_offset, ram_table_ind, hash); + + if (ret) { + goto end; + } + + // Give a large dummy buffer size in order to achieve actual data size + // (as copy_data flag is not set, data won't be copied anywhere) + ret = read_record(_active_area, bd_offset, const_cast(key), 0, (uint32_t) -1, + actual_data_size, 0, false, false, false, false, hash, flags, + next_bd_offset); + + if (ret) { + goto end; + } + + if (info) { + info->flags = flags; + info->size = actual_data_size; + } + +end: + _mutex.unlock(); + return ret; +} + +int TDBStore::write_master_record(uint8_t area, uint16_t version, uint32_t &next_offset) +{ + master_record_data_t master_rec; + + master_rec.version = version; + master_rec.tdbstore_revision = tdbstore_revision; + master_rec.reserved = 0; + next_offset = _master_record_offset + _master_record_size; + return set(master_rec_key, &master_rec, sizeof(master_rec), 0); +} + +int TDBStore::copy_record(uint8_t from_area, uint32_t from_offset, uint32_t to_offset, + uint32_t &to_next_offset) +{ + int ret; + record_header_t header; + uint32_t total_size; + uint16_t chunk_size; + + ret = read_area(from_area, from_offset, sizeof(header), &header); + if (ret) { + return ret; + } + + total_size = align_up(sizeof(record_header_t), _prog_size) + + align_up(header.key_size + header.data_size, _prog_size);; + + + if (to_offset + total_size > _size) { + // We are trying to copy more that the are can hold + return KVSTORE_ERROR_MEDIA_FULL; + } + ret = check_erase_before_write(1 - from_area, to_offset, total_size); + if (ret) { + return ret; + } + + chunk_size = align_up(sizeof(record_header_t), _prog_size); + // The record header takes up whole program units + memset(_work_buf, 0, chunk_size); + memcpy(_work_buf, &header, sizeof(record_header_t)); + ret = write_area(1 - from_area, to_offset, chunk_size, _work_buf); + if (ret) { + return ret; + } + + from_offset += chunk_size; + to_offset += chunk_size; + total_size -= chunk_size; + + while (total_size) { + chunk_size = std::min(total_size, _work_buf_size); + ret = read_area(from_area, from_offset, chunk_size, _work_buf); + if (ret) { + return ret; + } + + ret = write_area(1 - from_area, to_offset, chunk_size, _work_buf); + if (ret) { + return ret; + } + + from_offset += chunk_size; + to_offset += chunk_size; + total_size -= chunk_size; + } + + to_next_offset = align_up(to_offset, _prog_size); + return KVSTORE_SUCCESS; +} + +int TDBStore::garbage_collection() +{ + ram_table_entry_t *ram_table = (ram_table_entry_t *) _ram_table; + uint32_t to_offset, to_next_offset; + int ret; + size_t ind; + + // Reset the standby area + ret = reset_area(1 - _active_area); + if (ret) { + return ret; + } + + to_offset = _master_record_offset + _master_record_size; + + // Initialize in case table is empty + to_next_offset = to_offset; + + // Go over ram table and copy all entries to opposite area + for (ind = 0; ind < _num_keys; ind++) { + uint32_t from_offset = ram_table[ind].bd_offset; + ret = copy_record(_active_area, from_offset, to_offset, to_next_offset); + if (ret) { + return ret; + } + // Update RAM table + ram_table[ind].bd_offset = to_offset; + to_offset = to_next_offset; + } + + to_offset = to_next_offset; + _free_space_offset = to_next_offset; + + // Now we can switch to the new active area + _active_area = 1 - _active_area; + + // Now write master record, with version incremented by 1. + _active_area_version++; + ret = write_master_record(_active_area, _active_area_version, to_offset); + if (ret) { + return ret; + } + + return KVSTORE_SUCCESS; +} + + +int TDBStore::build_ram_table() +{ + ram_table_entry_t *ram_table = (ram_table_entry_t *) _ram_table; + uint32_t offset, next_offset = 0, dummy; + int ret = KVSTORE_SUCCESS; + uint32_t hash; + uint32_t flags; + uint32_t actual_data_size; + uint32_t ram_table_ind; + + _num_keys = 0; + offset = _master_record_offset; + + while (offset + sizeof(record_header_t) < _free_space_offset) { + ret = read_record(_active_area, offset, _key_buf, 0, 0, actual_data_size, 0, + true, false, false, true, hash, flags, next_offset); + + if (ret) { + goto end; + } + + ret = find_record(_active_area, _key_buf, dummy, ram_table_ind, hash); + + if ((ret != KVSTORE_SUCCESS) && (ret != KVSTORE_ERROR_ITEM_NOT_FOUND)) { + goto end; + } + + uint32_t save_offset = offset; + offset = next_offset; + + if (ret == KVSTORE_ERROR_ITEM_NOT_FOUND) { + // Key doesn't exist, need to add it to RAM table + ret = KVSTORE_SUCCESS; + + if (flags & delete_flag) { + continue; + } + if (_num_keys >= _max_keys) { + // In order to avoid numerous reallocations of ram table, + // Add a chunk of entries now + increment_max_keys(reinterpret_cast(&ram_table)); + } + memmove(&ram_table[ram_table_ind + 1], &ram_table[ram_table_ind], + sizeof(ram_table_entry_t) * (_num_keys - ram_table_ind)); + + _num_keys++; + } else if (flags & delete_flag) { + _num_keys--; + memmove(&ram_table[ram_table_ind], &ram_table[ram_table_ind + 1], + sizeof(ram_table_entry_t) * (_num_keys - ram_table_ind)); + + continue; + } + + // update record parameters + ram_table[ram_table_ind].hash = hash; + ram_table[ram_table_ind].bd_offset = save_offset; + } + +end: + _free_space_offset = next_offset; + return ret; +} + +int TDBStore::increment_max_keys(void **ram_table) +{ + // Reallocate ram table with new size + ram_table_entry_t *old_ram_table = (ram_table_entry_t *) _ram_table; + ram_table_entry_t *new_ram_table = new ram_table_entry_t[_max_keys + 1]; + memset(new_ram_table, 0, sizeof(ram_table_entry_t) * (_max_keys + 1)); + + // Copy old content to new table + memcpy(new_ram_table, old_ram_table, sizeof(ram_table_entry_t) * _max_keys); + _max_keys++; + + _ram_table = new_ram_table; + delete[] old_ram_table; + + if (ram_table) { + *ram_table = _ram_table; + } + return KVSTORE_SUCCESS; +} + + +int TDBStore::init() +{ + ram_table_entry_t *ram_table; + area_state_e area_state[_num_areas]; + uint32_t next_offset; + uint32_t flags, hash; + uint32_t actual_data_size; + int ret = KVSTORE_SUCCESS; + uint16_t versions[_num_areas]; + + _mutex.lock(); + + if (_is_initialized) { + goto end; + } + + _max_keys = initial_max_keys; + + ram_table = new ram_table_entry_t[_max_keys]; + memset(ram_table, 0, sizeof(ram_table_entry_t) * _max_keys); + _ram_table = ram_table; + _num_keys = 0; + + _size = (size_t) -1; + + _buff_bd = new BufferedBlockDevice(_bd); + ret = _buff_bd->init(); + if (ret) { + goto fail; + } + + _prog_size = _bd->get_program_size(); + _work_buf_size = std::max(_prog_size, min_work_buf_size); + _work_buf = new uint8_t[_work_buf_size]; + _key_buf = new char[MAX_KEY_SIZE]; + _inc_set_handle = new inc_set_handle_t; + memset(_inc_set_handle, 0, sizeof(inc_set_handle_t)); + memset(_iterator_table, 0, sizeof(_iterator_table)); + + _master_record_offset = align_up(RESERVED_AREA_SIZE + sizeof(reserved_trailer_t), _prog_size); + _master_record_size = record_size(master_rec_key, sizeof(master_record_data_t)); + + calc_area_params(); + + /* Minimum space required by Reserved area and master record */ + MBED_ASSERT(_bd->size() + >= (align_up(RESERVED_AREA_SIZE + sizeof(reserved_trailer_t), _prog_size) + + record_size(master_rec_key, sizeof(master_record_data_t)))); + + for (uint8_t area = 0; area < _num_areas; area++) { + area_state[area] = TDBSTORE_AREA_STATE_NONE; + versions[area] = 0; + + _size = std::min(_size, _area_params[area].size); + + // Check validity of master record + master_record_data_t master_rec; + ret = read_record(area, _master_record_offset, const_cast(master_rec_key), + &master_rec, sizeof(master_rec), actual_data_size, 0, false, true, true, false, + hash, flags, next_offset); + if ((ret != KVSTORE_SUCCESS) && (ret != KVSTORE_ERROR_INVALID_DATA_DETECTED)) { + // KVSTORE_ERROR(ret, "TDBSTORE: Unable to read record at init"); // FIXME + } + + // Master record may be either corrupt or erased + if (ret == KVSTORE_ERROR_INVALID_DATA_DETECTED) { + area_state[area] = TDBSTORE_AREA_STATE_INVALID; + continue; + } + + versions[area] = master_rec.version; + + area_state[area] = TDBSTORE_AREA_STATE_VALID; + + // Unless both areas are valid (a case handled later), getting here means + // that we found our active area. + _active_area = area; + _active_area_version = versions[area]; + } + + // In case we have two empty areas, arbitrarily use area 0 as the active one. + if ((area_state[0] == TDBSTORE_AREA_STATE_INVALID) && (area_state[1] == TDBSTORE_AREA_STATE_INVALID)) { + reset_area(0); + _active_area = 0; + _active_area_version = 1; + area_state[0] = TDBSTORE_AREA_STATE_ERASED; + ret = write_master_record(_active_area, _active_area_version, _free_space_offset); + if (ret) { + // KVSTORE_ERROR(ret, "TDBSTORE: Unable to write master record at init"); // FIXME + } + // Nothing more to do here if active area is empty + goto end; + } + + // In case we have two valid areas, choose the one having the higher version (or 0 + // in case of wrap around). + if ((area_state[0] == TDBSTORE_AREA_STATE_VALID) && (area_state[1] == TDBSTORE_AREA_STATE_VALID)) { + if ((versions[0] > versions[1]) || (!versions[0])) { + _active_area = 0; + } else { + _active_area = 1; + } + _active_area_version = versions[_active_area]; + } + + // Currently set free space offset pointer to the end of free space. + // Ram table build process needs it, but will update it. + _free_space_offset = _size; + ret = build_ram_table(); + + // build_ram_table() scans all keys, until invalid data found. + // Therefore INVALID_DATA is not considered error. + if ((ret != KVSTORE_SUCCESS) && (ret != KVSTORE_ERROR_INVALID_DATA_DETECTED)) { + goto fail; + } + +end: + _is_initialized = true; + _mutex.unlock(); + return KVSTORE_SUCCESS; +fail: + delete[] ram_table; + delete _buff_bd; + delete[] _work_buf; + delete[] _key_buf; + delete reinterpret_cast(_inc_set_handle); + _ram_table = nullptr; + _buff_bd = nullptr; + _work_buf = nullptr; + _key_buf = nullptr; + _inc_set_handle = nullptr; + _mutex.unlock(); + return ret; +} + +int TDBStore::deinit() +{ + _mutex.lock(); + if (_is_initialized) { + _buff_bd->deinit(); + delete _buff_bd; + + ram_table_entry_t *ram_table = (ram_table_entry_t *) _ram_table; + delete[] ram_table; + delete[] _work_buf; + delete[] _key_buf; + delete reinterpret_cast(_inc_set_handle); + } + + _is_initialized = false; + _mutex.unlock(); + + return KVSTORE_SUCCESS; +} + +int TDBStore::reset_area(uint8_t area) +{ + uint8_t buf[RESERVED_AREA_SIZE + sizeof(reserved_trailer_t)]; + int ret; + bool copy_reserved_data = do_reserved_data_get(buf, sizeof(buf), 0, buf + RESERVED_AREA_SIZE) == KVSTORE_SUCCESS; + + // Erase reserved area and master record + ret = check_erase_before_write(area, 0, _master_record_offset + _master_record_size + _prog_size, true); + if (ret) { + return ret; + } + if (copy_reserved_data) { + ret = write_area(area, 0, sizeof(buf), buf); + } + return ret; +} + +int TDBStore::reset() +{ + uint8_t area; + int ret; + + if (!_is_initialized) { + return KVSTORE_ERROR_NOT_READY; + } + + _mutex.lock(); + + // Reset both areas + for (area = 0; area < _num_areas; area++) { + ret = check_erase_before_write(area, 0, _master_record_offset + _master_record_size + _prog_size, true); + if (ret) { + goto end; + } + } + + _active_area = 0; + _num_keys = 0; + _free_space_offset = _master_record_offset; + _active_area_version = 1; + memset(_ram_table, 0, sizeof(ram_table_entry_t) * _max_keys); + // Write an initial master record on active area + ret = write_master_record(_active_area, _active_area_version, _free_space_offset); + +end: + _mutex.unlock(); + return ret; +} + +int TDBStore::iterator_open(iterator_t *it, const char *prefix) +{ + key_iterator_handle_t *handle; + int ret = KVSTORE_SUCCESS; + + if (!_is_initialized) { + return KVSTORE_ERROR_NOT_READY; + } + + if (!it) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + _mutex.lock(); + + int it_num; + for (it_num = 0; it_num < _max_open_iterators; it_num++) { + if (!_iterator_table[it_num]) { + break; + } + } + + if (it_num == _max_open_iterators) { + ret = KVSTORE_ERROR_OUT_OF_RESOURCES; + goto end; + } + + handle = new key_iterator_handle_t; + *it = reinterpret_cast(handle); + + if (prefix && strcmp(prefix, "")) { + handle->prefix = new char[strlen(prefix) + 1]; + strcpy(handle->prefix, prefix); + } else { + handle->prefix = 0; + } + handle->ram_table_ind = 0; + handle->iterator_num = it_num; + _iterator_table[it_num] = handle; + +end: + _mutex.unlock(); + return ret; +} + +int TDBStore::iterator_next(iterator_t it, char *key, size_t key_size) +{ + ram_table_entry_t *ram_table = (ram_table_entry_t *) _ram_table; + key_iterator_handle_t *handle; + int ret; + uint32_t actual_data_size, hash, flags, next_offset; + + if (!_is_initialized) { + return KVSTORE_ERROR_NOT_READY; + } + + _mutex.lock(); + + handle = reinterpret_cast(it); + + ret = KVSTORE_ERROR_ITEM_NOT_FOUND; + + while (ret && (handle->ram_table_ind < _num_keys)) { + ret = read_record(_active_area, ram_table[handle->ram_table_ind].bd_offset, _key_buf, + 0, 0, actual_data_size, 0, true, false, false, false, hash, flags, next_offset); + if (ret) { + goto end; + } + if (!handle->prefix || (strstr(_key_buf, handle->prefix) == _key_buf)) { + if (strlen(_key_buf) >= key_size) { + ret = KVSTORE_ERROR_INVALID_SIZE; + goto end; + } + strcpy(key, _key_buf); + } else { + ret = KVSTORE_ERROR_ITEM_NOT_FOUND; + } + handle->ram_table_ind++; + } + +end: + _mutex.unlock(); + return ret; +} + +int TDBStore::iterator_close(iterator_t it) +{ + key_iterator_handle_t *handle; + + if (!_is_initialized) { + return KVSTORE_ERROR_NOT_READY; + } + + _mutex.lock(); + + handle = reinterpret_cast(it); + delete[] handle->prefix; + _iterator_table[handle->iterator_num] = 0; + delete handle; + + _mutex.unlock(); + + return KVSTORE_SUCCESS; +} + +void TDBStore::update_all_iterators(bool added, uint32_t ram_table_ind) +{ + for (int it_num = 0; it_num < _max_open_iterators; it_num++) { + key_iterator_handle_t *handle = static_cast (_iterator_table[it_num]); + if (!handle) { + continue; + } + + if (ram_table_ind >= handle->ram_table_ind) { + continue; + } + + if (added) { + handle->ram_table_ind++; + } else { + handle->ram_table_ind--; + } + } +} + +int TDBStore::reserved_data_set(const void *reserved_data, size_t reserved_data_buf_size) +{ + reserved_trailer_t trailer; + int ret; + + if (reserved_data_buf_size > RESERVED_AREA_SIZE) { + return KVSTORE_ERROR_INVALID_SIZE; + } + + _mutex.lock(); + + ret = do_reserved_data_get(0, 0); + if (ret == KVSTORE_SUCCESS) { + ret = KVSTORE_ERROR_WRITE_FAILED; + goto end; + } + + trailer.trailer_size = sizeof(trailer); + trailer.data_size = reserved_data_buf_size; + trailer.crc = calc_crc(initial_crc, reserved_data_buf_size, reserved_data); + + // Erase the header of non-active area, just to make sure that we can write to it + // In case garbage collection has not yet been run, the area can be un-erased + ret = reset_area(1 - _active_area); + if (ret) { + goto end; + } + + /* + * Write to both areas + * Both must success, as they are required to be erased when TDBStore initializes + * its area + */ + for (int i = 0; i < _num_areas; ++i) { + ret = write_area(i, 0, reserved_data_buf_size, reserved_data); + if (ret) { + goto end; + } + ret = write_area(i, RESERVED_AREA_SIZE, sizeof(trailer), &trailer); + if (ret) { + goto end; + } + ret = _buff_bd->sync(); + if (ret) { + goto end; + } + } + ret = KVSTORE_SUCCESS; +end: + _mutex.unlock(); + return ret; +} + +int TDBStore::do_reserved_data_get(void *reserved_data, size_t reserved_data_buf_size, size_t *actual_data_size, void *copy_trailer) +{ + reserved_trailer_t trailer; + uint8_t buf[RESERVED_AREA_SIZE]; + int ret; + uint32_t crc; + + /* + * Try to keep reserved data identical on both areas, therefore + * we can return any of these data, if the checmsum is correct. + */ + for (int i = 0; i < _num_areas; ++i) { + ret = read_area(i, RESERVED_AREA_SIZE, sizeof(trailer), &trailer); + if (ret) { + return ret; + } + + // First validy check: is the trailer header size correct + if (trailer.trailer_size != sizeof(trailer)) { + continue; + } + // Second validy check: Is the data too big (corrupt header) + if (trailer.data_size > RESERVED_AREA_SIZE) { + continue; + } + + // Next, verify the checksum + ret = read_area(i, 0, trailer.data_size, buf); + if (ret) { + return ret; + } + crc = calc_crc(initial_crc, trailer.data_size, buf); + if (crc == trailer.crc) { + // Correct data, copy it and return to caller + if (reserved_data) { + if (reserved_data_buf_size < trailer.data_size) { + return KVSTORE_ERROR_INVALID_SIZE; + } + memcpy(reserved_data, buf, trailer.data_size); + } + if (actual_data_size) { + *actual_data_size = trailer.data_size; + } + if (copy_trailer) { + memcpy(copy_trailer, &trailer, sizeof(trailer)); + } + return KVSTORE_SUCCESS; + } + } + + return KVSTORE_ERROR_ITEM_NOT_FOUND; +} + +int TDBStore::reserved_data_get(void *reserved_data, size_t reserved_data_buf_size, size_t *actual_data_size) +{ + _mutex.lock(); + int ret = do_reserved_data_get(reserved_data, reserved_data_buf_size, actual_data_size); + _mutex.unlock(); + return ret; +} + +void TDBStore::offset_in_erase_unit(uint8_t area, uint32_t offset, + uint32_t &offset_from_start, uint32_t &dist_to_end) +{ + uint32_t bd_offset = _area_params[area].address + offset; + + // The parameter of `BlockDevice::get_erase_size(bd_addr_t addr)` + // does not need to be aligned. + uint32_t erase_unit = _buff_bd->get_erase_size(bd_offset); + + // Even on a flash device with multiple regions, the start address of + // an erase unit is aligned to the current region's unit size. + offset_from_start = bd_offset % erase_unit; + dist_to_end = erase_unit - offset_from_start; +} + +int TDBStore::check_erase_before_write(uint8_t area, uint32_t offset, uint32_t size, bool force_check) +{ + // In order to save init time, we don't check that the entire area is erased. + // Instead, whenever reaching an erase unit start erase it. + bool erase = false; + uint32_t start_offset; + uint32_t end_offset; + while (size) { + uint32_t dist, offset_from_start; + int ret; + offset_in_erase_unit(area, offset, offset_from_start, dist); + uint32_t chunk = std::min(size, dist); + + if (offset_from_start == 0 || force_check) { + if (!erase) { + erase = true; + start_offset = offset - offset_from_start; + } + end_offset = offset + dist; + } + offset += chunk; + size -= chunk; + } + + if (erase) { + int ret = erase_area(area, start_offset, end_offset - start_offset); + if (ret != KVSTORE_SUCCESS) { + return KVSTORE_ERROR_WRITE_FAILED; + } + } + + return KVSTORE_SUCCESS; +} diff --git a/libraries/KVStore/src/TDBStore.h b/libraries/KVStore/src/TDBStore.h new file mode 100644 index 000000000..ab5fdc441 --- /dev/null +++ b/libraries/KVStore/src/TDBStore.h @@ -0,0 +1,548 @@ +/* + * Copyright (c) 2018 ARM Limited. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MBED_TDBSTORE_H +#define MBED_TDBSTORE_H + +#include +#include "KVStore.h" +#include +#include + +// namespace mbed { + +/** TDBStore class + * + * Lightweight Key Value storage over a block device + */ + +class TDBStore : public mbed::KVStore { +public: + + static const uint32_t RESERVED_AREA_SIZE = 64; + + /** + * @brief Class constructor + * + * @param[in] bd Underlying block device. + * + * @returns none + */ + TDBStore(BlockDevice *bd); + + /** + * @brief Class destructor + * + * @returns none + */ + virtual ~TDBStore(); + + /** + * @brief Initialize TDBStore. If data exists, TDBStore will check the data integrity + * on initialize. If the integrity checks fails, the TDBStore will use GC to collect + * the available data and clean corrupted and erroneous records. + * + * @returns KVSTORE_SUCCESS Success. + * @returns Negative error code on failure. + */ + virtual int init(); + + /** + * @brief Deinitialize TDBStore, release and free resources. + * + * @returns KVSTORE_SUCCESS Success. + */ + virtual int deinit(); + + + /** + * @brief Reset TDBStore contents (clear all keys) and reserved data + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_READ_FAILED Unable to read from media. + * KVSTORE_ERROR_WRITE_FAILED Unable to write to media. + */ + virtual int reset(); + + /** + * @brief Set one TDBStore item, given key and value. + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[in] buffer Value data buffer. + * @param[in] size Value data size. + * @param[in] create_flags Flag mask. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_READ_FAILED Unable to read from media. + * KVSTORE_ERROR_WRITE_FAILED Unable to write to media. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_INVALID_SIZE Invalid size given in function arguments. + * KVSTORE_ERROR_MEDIA_FULL Not enough room on media. + * KVSTORE_ERROR_WRITE_PROTECTED Already stored with "write once" flag. + */ + virtual int set(const char *key, const void *buffer, size_t size, uint32_t create_flags); + + /** + * @brief Get one TDBStore item by given key. + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[in] buffer Value data buffer. + * @param[in] buffer_size Value data buffer size. + * @param[out] actual_size Actual read size. + * @param[in] offset Offset to read from in data. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_READ_FAILED Unable to read from media. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_INVALID_SIZE Invalid size given in function arguments. + * KVSTORE_ERROR_INVALID_DATA_DETECTED Data is corrupt. + * KVSTORE_ERROR_ITEM_NOT_FOUND No such key. + */ + virtual int get(const char *key, void *buffer, size_t buffer_size, size_t *actual_size = NULL, + size_t offset = 0); + + /** + * @brief Get information of a given key. The returned info contains size and flags + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[out] info Returned information structure. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_READ_FAILED Unable to read from media. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_INVALID_DATA_DETECTED Data is corrupt. + * KVSTORE_ERROR_ITEM_NOT_FOUND No such key. + */ + virtual int get_info(const char *key, info_t *info); + + /** + * @brief Remove a TDBStore item by given key. + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_READ_FAILED Unable to read from media. + * KVSTORE_ERROR_WRITE_FAILED Unable to write to media. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_MEDIA_FULL Not enough room on media. + * KVSTORE_ERROR_ITEM_NOT_FOUND No such key. + * KVSTORE_ERROR_WRITE_PROTECTED Already stored with "write once" flag. + */ + virtual int remove(const char *key); + + + /** + * @brief Start an incremental TDBStore set sequence. This operation is blocking other operations. + * Any get/set/remove/iterator operation will be blocked until set_finalize is called. + * + * @param[out] handle Returned incremental set handle. + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[in] final_data_size Final value data size. + * @param[in] create_flags Flag mask. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_READ_FAILED Unable to read from media. + * KVSTORE_ERROR_WRITE_FAILED Unable to write to media. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_INVALID_SIZE Invalid size given in function arguments. + * KVSTORE_ERROR_MEDIA_FULL Not enough room on media. + * KVSTORE_ERROR_WRITE_PROTECTED Already stored with "write once" flag. + */ + virtual int set_start(set_handle_t *handle, const char *key, size_t final_data_size, uint32_t create_flags); + + /** + * @brief Add data to incremental TDBStore set sequence. This operation is blocking other operations. + * Any get/set/remove operation will be blocked until set_finalize will be called. + * + * @param[in] handle Incremental set handle. + * @param[in] value_data Value data to add. + * @param[in] data_size Value data size. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_WRITE_FAILED Unable to write to media. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_INVALID_SIZE Invalid size given in function arguments. + */ + virtual int set_add_data(set_handle_t handle, const void *value_data, size_t data_size); + + /** + * @brief Finalize an incremental KVStore set sequence. + * + * @param[in] handle Incremental set handle. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_WRITE_FAILED Unable to write to media. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + */ + virtual int set_finalize(set_handle_t handle); + + /** + * @brief Start an iteration over KVStore keys. + * There are no issues with any other operations while iterator is open. + * + * @param[out] it Returned iterator handle. + * @param[in] prefix Key prefix (null for all keys). + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + */ + virtual int iterator_open(iterator_t *it, const char *prefix = NULL); + + /** + * @brief Get next key in iteration. + * There are no issues with any other operations while iterator is open. + * + * @param[in] it Iterator handle. + * @param[in] key Buffer for returned key. + * @param[in] key_size Key buffer size. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_READ_FAILED Unable to read from block device. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_INVALID_SIZE Invalid size given in function arguments. + * KVSTORE_ERROR_INVALID_DATA_DETECTED Data is corrupt. + * KVSTORE_ERROR_ITEM_NOT_FOUND No more keys found. + */ + virtual int iterator_next(iterator_t it, char *key, size_t key_size); + + /** + * @brief Close iteration. + * + * @param[in] it Iterator handle. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + */ + virtual int iterator_close(iterator_t it); + + /** + * @brief Set data in reserved area, which is a special location for special data, such as ROT. + * The data written to reserved area can't be overwritten. + * + * @param[in] reserved_data Reserved data buffer. + * @param[in] reserved_data_buf_size + * Reserved data buffer size. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_READ_FAILED Unable to read from media. + * KVSTORE_ERROR_WRITE_FAILED Unable to write to media. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_INVALID_SIZE Invalid size given in function arguments. + */ + virtual int reserved_data_set(const void *reserved_data, size_t reserved_data_buf_size); + + /** + * @brief Get data from reserved area, which is a special location for special data, such as ROT. + * + * @param[in] reserved_data Reserved data buffer. + * @param[in] reserved_data_buf_size + * Reserved data buffer size. + * @param[in] actual_data_size Return data size. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_READ_FAILED Unable to read from media. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_INVALID_DATA_DETECTED Data is corrupt. + * KVSTORE_ERROR_ITEM_NOT_FOUND No reserved data was written. + */ + virtual int reserved_data_get(void *reserved_data, size_t reserved_data_buf_size, + size_t *actual_data_size = 0); + +#if !defined(DOXYGEN_ONLY) +private: + + typedef struct { + uint32_t address; + size_t size; + } tdbstore_area_data_t; + + static const int _num_areas = 2; + static const int _max_open_iterators = 16; + + PlatformMutex _mutex; + PlatformMutex _inc_set_mutex; + void *_ram_table; + size_t _max_keys; + size_t _num_keys; + BlockDevice *_bd; + BufferedBlockDevice *_buff_bd; + uint32_t _free_space_offset; + uint32_t _master_record_offset; + uint32_t _master_record_size; + bool _is_initialized; + int _active_area; + uint16_t _active_area_version; + size_t _size; + tdbstore_area_data_t _area_params[_num_areas]; + uint32_t _prog_size; + uint8_t *_work_buf; + size_t _work_buf_size; + char *_key_buf; + void *_inc_set_handle; + void *_iterator_table[_max_open_iterators]; + + /** + * @brief Read a block from an area. + * + * @param[in] area Area. + * @param[in] offset Offset in area. + * @param[in] size Number of bytes to read. + * @param[in] buf Output buffer. + * + * @returns 0 for success, nonzero for failure. + */ + int read_area(uint8_t area, uint32_t offset, uint32_t size, void *buf); + + /** + * @brief Write a block to an area. + * + * @param[in] area Area. + * @param[in] offset Offset in area. + * @param[in] size Number of bytes to write. + * @param[in] buf Input buffer. + * + * @returns 0 for success, non-zero for failure. + */ + int write_area(uint8_t area, uint32_t offset, uint32_t size, const void *buf); + + /** + * @brief Reset an area (erase its start). + * This erases master record, but preserves the + * reserved area data. + * + * @param[in] area Area. + * + * @returns 0 for success, nonzero for failure. + */ + int reset_area(uint8_t area); + + /** + * @brief Erase an area. + * + * @param[in] area Area. + * @param[in] offset Offset in area. + * @param[in] size Number of bytes to erase. + * + * @returns 0 for success, nonzero for failure. + */ + int erase_area(uint8_t area, uint32_t offset, uint32_t size); + + /** + * @brief Calculate addresses and sizes of areas. + */ + void calc_area_params(); + + /** + * @brief Read a TDBStore record from a given location. + * + * @param[in] area Area. + * @param[in] offset Offset of record in area. + * @param[out] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[out] data_buf Data buffer. + * @param[in] data_buf_size Data buffer size. + * @param[out] actual_data_size Actual data size. + * @param[in] data_offset Offset in data. + * @param[in] copy_key Copy key to user buffer. + * @param[in] copy_data Copy data to user buffer. + * @param[in] check_expected_key Check whether key belongs to this record. + * @param[in] calc_hash Calculate hash (on key). + * @param[out] hash Calculated hash. + * @param[out] flags Record flags. + * @param[out] next_offset Offset of next record. + * + * @returns 0 for success, nonzero for failure. + */ + int read_record(uint8_t area, uint32_t offset, char *key, + void *data_buf, uint32_t data_buf_size, + uint32_t &actual_data_size, size_t data_offset, bool copy_key, + bool copy_data, bool check_expected_key, bool calc_hash, + uint32_t &hash, uint32_t &flags, uint32_t &next_offset); + + /** + * @brief Write a master record of a given area. + * + * @param[in] area Area. + * @param[in] version Area version. + * @param[out] next_offset Offset of next record. + * + * @returns 0 for success, nonzero for failure. + */ + int write_master_record(uint8_t area, uint16_t version, uint32_t &next_offset); + + /** + * @brief Copy a record from one area to the opposite one. + * + * @param[in] from_area Area to copy record from. + * @param[in] from_offset Offset in source area. + * @param[in] to_offset Offset in destination area. + * @param[out] to_next_offset Offset of next record in destination area. + * + * @returns 0 for success, nonzero for failure. + */ + int copy_record(uint8_t from_area, uint32_t from_offset, uint32_t to_offset, + uint32_t &to_next_offset); + + /** + * @brief Garbage collection (compact all records from active area to the standby one). + * + * @returns 0 for success, nonzero for failure. + */ + int garbage_collection(); + + /** + * @brief Return record size given key and data size. + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[in] data_size Data size. + * + * @returns record size. + */ + uint32_t record_size(const char *key, uint32_t data_size); + + /** + * @brief Find a record given key + * + * @param[in] area Area. + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[out] offset Offset of record. + * @param[out] ram_table_ind Index in RAM table (target one if not found). + * @param[out] hash Calculated key hash. + * + * @returns 0 for success, nonzero for failure. + */ + int find_record(uint8_t area, const char *key, uint32_t &offset, + uint32_t &ram_table_ind, uint32_t &hash); + /** + * @brief Actual logics of get API (also covers all other get APIs). + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[in] copy_data Copy data to user buffer. + * @param[in] data_buf Buffer to store data on. + * @param[in] data_buf_size Data buffer size (bytes). + * @param[out] actual_data_size Actual data size (bytes). + * @param[out] flags Flags. + * + * @returns 0 for success, nonzero for failure. + */ + int do_get(const char *key, bool copy_data, + void *data_buf, uint32_t data_buf_size, uint32_t &actual_data_size, + uint32_t &flags); + + /** + * @brief Actual logics of set API (covers also the remove API). + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[in] data_buf Data buffer. + * @param[in] data_buf_size Data buffer size (bytes). + * @param[in] flags Flags. + * + * @returns 0 for success, nonzero for failure. + */ + int do_set(const char *key, const void *data_buf, uint32_t data_buf_size, uint32_t flags); + + /** + * @brief Build RAM table and update _free_space_offset (scanning all the records in the area). + * + * @returns 0 for success, nonzero for failure. + */ + int build_ram_table(); + + /** + * @brief Increment maximum number of keys and reallocate RAM table accordingly. + * + * @param[out] ram_table Updated RAM table. + * + * @returns 0 for success, nonzero for failure. + */ + int increment_max_keys(void **ram_table = 0); + + /** + * @brief Calculate offset from start of erase unit. + * + * @param[in] area Area. + * @param[in] offset Offset in area. + * @param[out] offset_from_start Offset from start of erase unit. + * @param[out] dist_to_end Distance to end of erase unit. + * + * @returns offset in erase unit. + */ + void offset_in_erase_unit(uint8_t area, uint32_t offset, uint32_t &offset_from_start, + uint32_t &dist_to_end); + + /** + * @brief Before writing a record, check whether you are crossing an erase unit. + * If you do, check if it's erased, and erase it if not. + * + * @param[in] area Area. + * @param[in] offset Offset in area. + * @param[in] size Write size. + * @param[in] force_check Force checking. + * + * @returns 0 for success, nonzero for failure. + */ + int check_erase_before_write(uint8_t area, uint32_t offset, uint32_t size, + bool force_check = false); + + /** + * @brief Get data from reserved area - worker function. + * This verifies that reserved data on both areas have + * correct checksums. If given pointer is not NULL, also + * write the reserved data to buffer. If checksums are not + * valid, return error code, and don't write anything to any + * pointers. + * + * @param[out] reserved_data Reserved data buffer (NULL to return nothing). + * @param[in] reserved_data_buf_size + * Reserved data buffer size. + * @param[out] actual_data_size If not NULL, return actual data size. + * @param[out] copy_trailer If not NULL, copy the trailer content to given buffer. + * + * @returns 0 on success or a negative error code on failure + */ + int do_reserved_data_get(void *reserved_data, size_t reserved_data_buf_size, + size_t *actual_data_size = 0, void *copy_trailer = 0); + + /** + * @brief Update all iterators after adding or deleting of keys. + * + * @param[in] added True if added, false if deleted. + * @param[in] ram_table_ind RAM table index. + * + * @returns none + */ + void update_all_iterators(bool added, uint32_t ram_table_ind); + +#endif + +}; +/** @}*/ + +// } // namespace mbed + +#endif diff --git a/libraries/Storage/examples/QSPIFormat/QSPIFormat.ino b/libraries/Storage/examples/QSPIFormat/QSPIFormat.ino index 26af854d4..9af2f8598 100644 --- a/libraries/Storage/examples/QSPIFormat/QSPIFormat.ino +++ b/libraries/Storage/examples/QSPIFormat/QSPIFormat.ino @@ -53,7 +53,8 @@ void setup() { if (true == waitResponse()) { MBRBlockDevice::partition(root, 1, 0x0B, 0, 5 * 1024 * 1024); - MBRBlockDevice::partition(root, 2, 0x0B, 5 * 1024 * 1024, 16 * 1024 * 1024); + MBRBlockDevice::partition(root, 2, 0x0B, 5 * 1024 * 1024, 15 * 1024 * 1024); + MBRBlockDevice::partition(root, 3, 0x0B, 15 * 1024 * 1024, 16 * 1024 * 1024); int err = sys_fs.reformat(&sys_bd); if (err) {