Nordic stack and drivers for the mbed BLE API
Dependents: Sensen-classic-v8-IAQ_copy_4
Fork of nRF51822 by
TARGET_MCU_NRF51822/sdk/source/libraries/fstorage/fstorage.c
- Committer:
- amithy
- Date:
- 2017-11-10
- Revision:
- 639:7f459d371b75
- Parent:
- 638:c90ae1400bf2
File content as of revision 639:7f459d371b75:
/* * 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"); }