Nordic stack and drivers for the mbed BLE API
Dependents: Sensen-classic-v8-IAQ_copy_4
Fork of nRF51822 by
Diff: TARGET_MCU_NRF51822/sdk/source/libraries/fstorage/fstorage.c
- Revision:
- 638:c90ae1400bf2
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/TARGET_MCU_NRF51822/sdk/source/libraries/fstorage/fstorage.c Wed Sep 14 14:39:43 2016 +0100 @@ -0,0 +1,569 @@ +/* + * Copyright (c) Nordic Semiconductor ASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of other + * contributors to this software may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "fstorage.h" +#include <stdio.h> +#include <string.h> +#include <stdbool.h> +#include "fstorage_config.h" +#include "nrf_error.h" +#include "nrf_soc.h" + + +#define FS_FLAG_INIT (1 << 0) /**< fstorage has been initialized. */ +#define FS_FLAG_PROCESSING (1 << 1) /**< fstorage is executing queued flash operations. */ +#define FS_FLAG_FLASH_REQ_PENDING (1 << 2) /**< fstorage is waiting for a flash operation initiated by another module to complete. */ + + +/**@brief Macro invocation that registers section fs_data. + * + * @details Required for compilation. + */ +NRF_SECTION_VARS_REGISTER_SECTION(fs_data); + + +/**@brief Macro invocation that declares symbols used to find the beginning and end of the section fs_data. + * + * @details Required for compilation. + */ +NRF_SECTION_VARS_REGISTER_SYMBOLS(fs_config_t, fs_data); + + +/**@defgroup Section vars helper macros. + * + * @details Macros used to manipulate registered section variables. + */ + /**@brief Get section variable with fstorage configuration by index. */ +#define FS_SECTION_VARS_GET(i) NRF_SECTION_VARS_GET(i, fs_config_t, fs_data) + /**@brief Get the number of registered section variables. */ +#define FS_SECTION_VARS_COUNT NRF_SECTION_VARS_COUNT(fs_config_t, fs_data) + /**@brief Get the start address of the registered section variables. */ +#define FS_SECTION_VARS_START_ADDR NRF_SECTION_VARS_START_ADDR(fs_data) + /**@brief Get the end address of the registered section variables. */ +#define FS_SECTION_VARS_END_ADDR NRF_SECTION_VARS_END_ADDR(fs_data) + +/** @} */ + + +/**@brief The command queue element. + * + * @details Encapsulate details of a command requested to this module. + */ +typedef struct +{ + fs_config_t const * p_config; /**< The configuration of the user who requested the operation. */ + uint8_t op_code; /**< Operation code. */ + uint32_t const * p_src; /**< Pointer to the data to be written to flash. The data must be kept in memory until the operation has finished. */ + uint32_t const * p_addr; /**< Destination of the data in flash. */ + fs_length_t length_words; /**< Length of the operation */ + fs_length_t offset; /**< Offset of the operation if operation is done in chunks */ +} fs_cmd_t; + + +/**@brief Structure that defines the command queue + * + * @details This queue holds flash operations requested to the module. + * The data to be written must be kept in memory until the write operation is completed, + * i.e., a callback indicating completion is received by the application. + */ +typedef struct +{ + uint8_t rp; /**< The current element being processed. */ + uint8_t count; /**< Number of elements in the queue. */ + fs_cmd_t cmd[FS_CMD_QUEUE_SIZE]; /**< Array to maintain flash access operation details. */ +} fs_cmd_queue_t; + + +static uint8_t m_flags; /**< FStorage status flags. */ +static fs_cmd_queue_t m_cmd_queue; /**< Flash operation request queue. */ +static uint16_t m_retry_count = 0; /**< Number of times a single flash operation was retried. */ + + +// Function prototypes +static ret_code_t queue_process(void); +static ret_code_t queue_process_impl(void); +static void app_notify(uint32_t result, fs_cmd_t const * p_cmd); + + +/**@brief Macro to check that the configuration is non-NULL and within +* valid section variable memory bounds. + * + * @param[in] config Configuration to check. + */ +#define FS_CHECK_CONFIG(config) \ + ((FS_SECTION_VARS_START_ADDR < config) && (config < FS_SECTION_VARS_END_ADDR)) + + +/**@brief Function to check that the configuration is non-NULL and within +* valid section variable memory bounds. + * + * @param[in] config Configuration to check. + */ +static bool check_config(fs_config_t const * const config) +{ + if (config == NULL) + { + return false; + } + + if ((FS_SECTION_VARS_START_ADDR <= (uint32_t)config) && ((uint32_t)config < FS_SECTION_VARS_END_ADDR)) + { + return true; + } + else + { + return false; + } +} + + +/**@brief Function to initialize the queue. */ +static void queue_init(void) +{ + memset(&m_cmd_queue, 0, sizeof(fs_cmd_queue_t)); +} + + +/**@brief Function to reset a queue item to its default values. + * + * @param index Index of the queue element. + */ +static void cmd_reset(uint32_t index) +{ + memset(&m_cmd_queue.cmd[index], 0, sizeof(fs_cmd_t)); +} + + +/**@brief Function to enqueue flash access command + * + * @param[in] config Registered configuration. + * @param[in] op_code Operation code. + * @param[in] address Destination of the data. + * @param[in] p_src Source of data or NULL if n/a. + * @param[in] length Length of the data, in 4 byte words. + * + * @retval NRF_SUCCESS Success. Command enqueued. + * @retval NRF_ERROR_NO_MEM Error. Queue is full. + * @retval Any error returned by the SoftDevice flash API. + */ +static ret_code_t cmd_enqueue(fs_config_t const * p_config, + uint8_t op_code, + uint32_t const * p_addr, + uint32_t const * p_src, + fs_length_t length_words) +{ + fs_cmd_t * p_cmd; + uint8_t write_pos; + + if (m_cmd_queue.count == FS_CMD_QUEUE_SIZE - 1) + { + return NRF_ERROR_NO_MEM; + } + + write_pos = (m_cmd_queue.rp + m_cmd_queue.count) % FS_CMD_QUEUE_SIZE; + + p_cmd = &m_cmd_queue.cmd[write_pos]; + + p_cmd->p_config = p_config; + p_cmd->op_code = op_code; + p_cmd->p_src = p_src; + p_cmd->p_addr = p_addr; + p_cmd->length_words = length_words; + + m_cmd_queue.count++; + + return queue_process(); +} + + +/**@brief Function to consume queue item and notify the return value of the operation. + * + * @details This function will report the result and remove the command from the queue after + * notification. + */ +static void cmd_consume(uint32_t result, const fs_cmd_t * p_cmd) +{ + // Consume the current item on the queue. + uint8_t rp = m_cmd_queue.rp; + + m_cmd_queue.count--; + if (m_cmd_queue.count == 0) + { + // There are no elements left. Stop processing the queue. + m_flags &= ~FS_FLAG_PROCESSING; + } + + if (++(m_cmd_queue.rp) == FS_CMD_QUEUE_SIZE) + { + m_cmd_queue.rp = 0; + } + + // Notify upon successful operation. + app_notify(result, p_cmd); + + // Reset the queue element. + cmd_reset(rp); +} + + +/**@brief Function to store data to flash. + * + * @param[in] p_cmd The queue element associated with the operation. + * + * @retval NRF_SUCCESS Success. The request was sent to the SoftDevice. + * @retval Any error returned by the SoftDevice flash API. + */ +static __INLINE uint32_t store_execute(fs_cmd_t const * const p_cmd) +{ + // Write in chunks if write-size is larger than FS_MAX_WRITE_SIZE. + fs_length_t const length = ((p_cmd->length_words - p_cmd->offset) < FS_MAX_WRITE_SIZE_WORDS) ? + (p_cmd->length_words - p_cmd->offset) : FS_MAX_WRITE_SIZE_WORDS; + + return sd_flash_write((uint32_t*)p_cmd->p_addr + p_cmd->offset /* destination */, + (uint32_t*)p_cmd->p_src + p_cmd->offset /* source */, + length); +} + + +/**@brief Function to erase a page. + * + * @param[in] p_cmd The queue element associated with the operation. + * + * @retval NRF_SUCCESS Success. The request was sent to the SoftDevice. + * @retval Any error returned by the SoftDevice flash API. + */ +static __INLINE uint32_t erase_execute(fs_cmd_t const * const p_cmd) +{ + // Erase the page. + return sd_flash_page_erase((uint32_t)(p_cmd->p_addr + p_cmd->offset) / FS_PAGE_SIZE); +} + + +/**@brief Function to process the current element in the queue and return the result. + * + * @retval NRF_SUCCESS Success. + * @retval NRF_ERROR_FORBIDDEN Error. Undefined command. + * @retval Any error returned by the SoftDevice flash API. + */ +static uint32_t queue_process_impl(void) +{ + uint32_t ret; + + fs_cmd_t const * const p_cmd = &m_cmd_queue.cmd[m_cmd_queue.rp]; + + switch (p_cmd->op_code) + { + case FS_OP_STORE: + ret = store_execute(p_cmd); + break; + + case FS_OP_ERASE: + ret = erase_execute(p_cmd); + break; + + case FS_OP_NONE: + ret = NRF_SUCCESS; + break; + + default: + ret = NRF_ERROR_FORBIDDEN; + break; + } + + return ret; +} + + +/**@brief Starts processing the queue if there are no pending flash operations + * for which we are awaiting a callback. + */ +static ret_code_t queue_process(void) +{ + ret_code_t ret = NRF_SUCCESS; + + /** If the queue is not being processed, and there are still + * some elements in it, then start processing. */ + if ( !(m_flags & FS_FLAG_PROCESSING) && + (m_cmd_queue.count > 0)) + { + m_flags |= FS_FLAG_PROCESSING; + + ret = queue_process_impl(); + + /** There is ongoing flash-operation which was not + * initiated by fstorage. */ + if (ret == NRF_ERROR_BUSY) + { + // Wait for a system callback. + m_flags |= FS_FLAG_FLASH_REQ_PENDING; + + // Stop processing the queue. + m_flags &= ~FS_FLAG_PROCESSING; + + ret = NRF_SUCCESS; + } + else if (ret != NRF_SUCCESS) + { + // Another error has occurred. + app_notify(ret, &m_cmd_queue.cmd[m_cmd_queue.rp]); + } + } + + // If we are already processing the queue, return immediately. + return ret; +} + + +/**@brief Flash operation success callback handler. + * + * @details This function updates read/write pointers. + * This function resets retry count. + */ +static __INLINE void on_operation_success(void) +{ + fs_cmd_t * const p_cmd = &m_cmd_queue.cmd[m_cmd_queue.rp]; + + m_retry_count = 0; + + switch (p_cmd->op_code) + { + case FS_OP_STORE: + // Update the offset on successful write. + p_cmd->offset += FS_MAX_WRITE_SIZE_WORDS; + break; + + case FS_OP_ERASE: + // Update the offset to correspond to the page that has been erased. + p_cmd->offset += FS_PAGE_SIZE_WORDS; + break; + } + + // If offset is equal to or larger than length, then the operation has finished. + if (p_cmd->offset >= p_cmd->length_words) + { + cmd_consume(NRF_SUCCESS, p_cmd); + } + + queue_process(); +} + + +/**@brief Flash operation failure callback handler. + * + * @details Function to keep track of retries and notify failures. + */ +static __INLINE void on_operation_failure(uint32_t sys_evt) +{ + const fs_cmd_t * p_cmd; + + if (++m_retry_count > FS_CMD_MAX_RETRIES) + { + p_cmd = &m_cmd_queue.cmd[m_cmd_queue.rp]; + cmd_consume(NRF_ERROR_TIMEOUT, p_cmd); + } + + queue_process(); +} + + +/**@brief Function to notify users. + * + * @param[in] result Result of the flash operation. + * @param[in] p_cmd The command associated with the callback. + */ +static void app_notify(uint32_t result, fs_cmd_t const * const p_cmd) +{ + p_cmd->p_config->cb(p_cmd->op_code, result, p_cmd->p_addr, p_cmd->length_words); +} + + +ret_code_t fs_init(void) +{ + uint16_t lowest_index = 0; + uint16_t lowest_order = 0xFFFF; + uint32_t * current_end = (uint32_t*)FS_PAGE_END_ADDR; + uint32_t num_left = FS_SECTION_VARS_COUNT; + + queue_init(); + + /** Assign pages to registered users, beginning with the ones with the lowest + * order, which will be assigned pages with the lowest memory address. */ + do + { + fs_config_t * p_config; + for (uint16_t i = 0; i < FS_SECTION_VARS_COUNT; i++) + { + p_config = FS_SECTION_VARS_GET(i); + + // Skip the ones which have the end-address already set. + if (p_config->p_end_addr != NULL) + continue; + + if (p_config->page_order < lowest_order) + { + lowest_order = p_config->page_order; + lowest_index = i; + } + } + + p_config = FS_SECTION_VARS_GET(lowest_index); + + p_config->p_end_addr = current_end; + p_config->p_start_addr = p_config->p_end_addr - (p_config->num_pages * FS_PAGE_SIZE_WORDS); + + current_end = p_config->p_start_addr; + lowest_order = 0xFFFF; + + } while ( --num_left > 0 ); + + m_flags |= FS_FLAG_INIT; + + return NRF_SUCCESS; +} + + +ret_code_t fs_store(fs_config_t const * p_config, + uint32_t const * p_addr, + uint32_t const * const p_data, + fs_length_t length_words) +{ + if ((m_flags & FS_FLAG_INIT) == 0) + { + return NRF_ERROR_INVALID_STATE; + } + + if (!check_config(p_config)) + { + return NRF_ERROR_FORBIDDEN; + } + + if (!is_word_aligned(p_addr)) + { + return NRF_ERROR_INVALID_ADDR; + } + + // Check that the erase operation is on pages owned by this user (configuration). + if ((p_addr < p_config->p_start_addr) || ((p_addr + length_words) > p_config->p_end_addr)) + { + return NRF_ERROR_INVALID_ADDR; + } + + return cmd_enqueue(p_config, FS_OP_STORE, p_addr, p_data, length_words); +} + + +ret_code_t fs_erase(fs_config_t const * p_config, + uint32_t * const p_addr, + fs_length_t const length_words) +{ + if ((m_flags & FS_FLAG_INIT) == 0) + { + return NRF_ERROR_INVALID_STATE; + } + + if (!check_config(p_config)) + { + return NRF_ERROR_FORBIDDEN; + } + + /** Check that the address is aligned on a page boundary and the length to erase + * is a multiple of the page size. */ + if (((uint32_t)p_addr & (FS_PAGE_SIZE - 1)) || + (length_words & (FS_PAGE_SIZE_WORDS - 1))) + { + return NRF_ERROR_INVALID_ADDR; + } + + // Check that the erase operation is on pages owned by this user (configuration). + if ((p_addr < p_config->p_start_addr) || ((p_addr + length_words) > p_config->p_end_addr)) + { + return NRF_ERROR_INVALID_ADDR; + } + + return cmd_enqueue(p_config, FS_OP_ERASE, p_addr, NULL, length_words); +} + + +/**@brief Function to handle system events from the SoftDevice. + * + * @details This function should be dispatched system events if any of the modules used by + * the application rely on FStorage. Examples include @ref Peer Manager and + * @ref Flash Data Storage. + * + * @param[in] sys_evt System Event received. + */ +void fs_sys_event_handler(uint32_t sys_evt) +{ + if (m_flags & FS_FLAG_PROCESSING) + { + /** A flash operation was initiated by this module. + * Handle its result. */ + switch (sys_evt) + { + case NRF_EVT_FLASH_OPERATION_SUCCESS: + on_operation_success(); + break; + + case NRF_EVT_FLASH_OPERATION_ERROR: + on_operation_failure(sys_evt); + break; + } + } + else if ((m_flags & FS_FLAG_FLASH_REQ_PENDING)) + { + /** A flash operation was initiated outside this module. + * We have now receveid a callback which indicates it has + * finished. Clear the FS_FLAG_FLASH_REQ_PENDING flag. */ + m_flags &= ~FS_FLAG_FLASH_REQ_PENDING; + + // Resume processing the queue, if necessary. + queue_process(); + } +} + + +// Just for testing out section vars (across many compilers). +void fs_debug_print() +{ + printf("fs start address: 0x%08lx\r\n", (unsigned long)FS_SECTION_VARS_START_ADDR); + printf("fs end address: 0x%08lx\r\n", (unsigned long)FS_SECTION_VARS_END_ADDR); + printf("Num items: 0x%08lx\r\n", (unsigned long)FS_SECTION_VARS_COUNT); + printf("===== ITEMS %lu =====\r\n", (unsigned long)FS_SECTION_VARS_COUNT); + + for(int i = 0; i < FS_SECTION_VARS_COUNT; i++) + { + fs_config_t* config = FS_SECTION_VARS_GET(i); + printf( "Address: 0x%08lx, CB: 0x%08lx\r\n", + (unsigned long)config, (unsigned long)config->cb ); + } + printf("\r\n"); +}