/* * Copyright (c) 2016, 2018, Arm Limited and affiliates. * 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. */ /* * NVM adaptation to Configuration Store that is storing Key-Value pairs to SRAM or flash. */ /* * Application needs to enable MBED_CONF_NANOSTACK_HAL_NVM_CFSTORE in its configuration in * order to use configuration-store. Application also needs to define storage area start * address in flash (CONFIG_HARDWARE_MTD_START_ADDR) and storage size (CONFIG_HARDWARE_MTD_SIZE). */ #if MBED_CONF_NANOSTACK_HAL_NVM_CFSTORE #include <stdio.h> #include <string.h> #include <ns_types.h> #include "configuration-store/configuration_store.h" // #define HAVE_DEBUG #include "ns_trace.h" #include "nsdynmemLIB.h" #include "eventOS_event_timer.h" #include "platform/arm_hal_nvm.h" #define TRACE_GROUP "pnvm" // Timeout for polling response from configuration store #define NVM_CB_POLLING_TIMEOUT 50 // Check if synchronous mode is enabled #define IS_SYNC_MODE(cs_ctx) ((cs_ctx)->capabilities.asynchronous_ops == 0) // NVM internal states typedef enum cfstore_state_t { NVM_STATE_NONE, NVM_STATE_INIT_DONE, NVM_STATE_CREATING, NVM_STATE_CREATE_DONE, NVM_STATE_OPENING, NVM_STATE_OPEN_DONE, NVM_STATE_WRITING, NVM_STATE_WRITE_DONE, NVM_STATE_CLOSING, NVM_STATE_CLOSE_DONE, NVM_STATE_FLUSHING, NVM_STATE_FLUSH_DONE, NVM_STATE_READING, NVM_STATE_READ_DONE, NVM_STATE_DELETING, NVM_STATE_DELETE_DONE, NVM_STATE_UNINITIALIZING, NVM_STATE_UNINIT_DONE } nvm_state_e; // NVM context typedef struct cfstore_context_s { ARM_CFSTORE_CAPABILITIES capabilities; //TODO: Fix hkey length once CFSTORE_HANDLE_BUFSIZE becomes visible uint8_t hkey[/*CFSTORE_HANDLE_BUFSIZE*/40]; // Handle to the key in process ARM_CFSTORE_SIZE data_len; // Data length cfstore is using nvm_state_e state; // current nvm_state_e timeout_t *callback_timer; // timer handle for informing client nvm_callback *client_cb; // callback provided by client void *client_context; // context provided by client platform_nvm_status client_status; // status to be returned to client uint8_t *client_buf; // buffer provided by client uint16_t *client_buf_len; // client buffer length } cs_context_t; ARM_CFSTORE_DRIVER *drv = &cfstore_driver; static cs_context_t *cs_context_ptr = NULL; // forward declarations static bool nvm_write_internal(cs_context_t *cf_context); static bool nvm_read_internal(cs_context_t *cf_context); static bool nvm_delete_internal(cs_context_t *cf_context); static bool nvm_close_internal(cs_context_t *cf_context); static bool nvm_status_check(cs_context_t *cf_context); static platform_nvm_status nvm_error_map(int32_t cs_error); static void nvm_fsm_timer_start(void); static void nvm_fsm_timer_cb(void *arg); /** * Configuration store callback */ static void configuration_store_cb(int32_t status, ARM_CFSTORE_OPCODE cmd_code, void *ctx, ARM_CFSTORE_HANDLE handle) { tr_debug("configuration_store_cb status=%d, cmd_code=%d, ctx=%x, hndl=%x", (int)status, (int)cmd_code, (unsigned int)ctx, (unsigned int)handle); cs_context_t *cf_context = (cs_context_t *)ctx; switch (cmd_code) { case CFSTORE_OPCODE_INITIALIZE: tr_debug("CFSTORE_OPCODE_INITIALIZE %d", (int)status); cf_context->state = NVM_STATE_INIT_DONE; cf_context->client_status = nvm_error_map(status); break; case CFSTORE_OPCODE_POWER_CONTROL: tr_debug("CFSTORE_OPCODE_POWER_CONTROL %d", (int)status); // do nothing for power control break; case CFSTORE_OPCODE_CREATE: tr_debug("CFSTORE_OPCODE_CREATE %d", (int)status); cf_context->client_status = nvm_error_map(status); cf_context->state = NVM_STATE_CREATE_DONE; if (status >= ARM_DRIVER_OK) { // key created successfully, close the key if (nvm_close_internal(cf_context) == false) { // closing failed // Ignore errors in close as closing recreated keys returns error // cf_context->state = NVM_STATE_CLOSE_DONE; // cf_context->client_status = PLATFORM_NVM_ERROR; } else { // closing OK, wait for CFSTORE_OPCODE_CLOSE callback } } break; case CFSTORE_OPCODE_OPEN: tr_debug("CFSTORE_OPCODE_OPEN %d", (int)status); if (status < ARM_DRIVER_OK) { // opening failed, do not continue any further cf_context->client_status = nvm_error_map(status); cf_context->state = NVM_STATE_OPEN_DONE; break; } // proceed to client action read/write/delete if (cf_context->state == NVM_STATE_WRITING) { if (nvm_write_internal(cf_context) == false) { /* reading failed set client_status */ cf_context->client_status = PLATFORM_NVM_ERROR; } else { // writing OK, wait for CFSTORE_OPCODE_WRITE callback } } else if (cf_context->state == NVM_STATE_READING) { if (nvm_read_internal(cf_context) == false) { /* reading failed set client_status */ cf_context->client_status = PLATFORM_NVM_ERROR; } else { // reading in progress, wait for CFSTORE_OPCODE_READ callback } } else if (cf_context->state == NVM_STATE_DELETING) { if (nvm_delete_internal(cf_context) == false) { /* reading failed set client_status */ cf_context->client_status = PLATFORM_NVM_ERROR; } else { // deleting in progress, wait for CFSTORE_OPCODE_DELETE callback } } if (cf_context->client_status == PLATFORM_NVM_ERROR) { // read/write/delete operation failed, close the handle if (nvm_close_internal(cf_context) == false) { cf_context->state = NVM_STATE_CLOSE_DONE; cf_context->client_status = PLATFORM_NVM_ERROR; } } break; case CFSTORE_OPCODE_WRITE: tr_debug("CFSTORE_OPCODE_WRITE %d", (int)status); cf_context->state = NVM_STATE_WRITE_DONE; *cf_context->client_buf_len = cf_context->data_len; if (nvm_close_internal(cf_context) == false) { /* writing failed set status and start callback timer */ cf_context->state = NVM_STATE_CLOSE_DONE; cf_context->client_status = PLATFORM_NVM_ERROR; } else { // closing OK, wait for CFSTORE_OPCODE_CLOSE callback } break; case CFSTORE_OPCODE_READ: tr_debug("CFSTORE_OPCODE_READ %d", (int)status); cf_context->state = NVM_STATE_READ_DONE; if (nvm_close_internal(cf_context) == false) { cf_context->state = NVM_STATE_CLOSE_DONE; cf_context->client_status = PLATFORM_NVM_ERROR; } else { // closing OK, wait for CFSTORE_OPCODE_CLOSE callback *cf_context->client_buf_len = (uint16_t)status; // save the bytes read } break; case CFSTORE_OPCODE_DELETE: tr_debug("CFSTORE_OPCODE_DELETE %d", (int)status); if (nvm_close_internal(cf_context) == false) { /* closing failed set client_status */ cf_context->state = NVM_STATE_CLOSE_DONE; cf_context->client_status = PLATFORM_NVM_ERROR; } else { // closing OK, wait for CFSTORE_OPCODE_CLOSE callback } break; case CFSTORE_OPCODE_CLOSE: tr_debug("CFSTORE_OPCODE_CLOSE %d", (int)status); cf_context->state = NVM_STATE_CLOSE_DONE; // client_status is already set by read/write/delete operation, do not override it break; case CFSTORE_OPCODE_UNINITIALIZE: tr_debug("CFSTORE_OPCODE_UNINITIALIZE %d", (int)status); cf_context->state = NVM_STATE_UNINIT_DONE; cf_context->client_status = nvm_error_map(status); break; case CFSTORE_OPCODE_FLUSH: tr_debug("CFSTORE_OPCODE_FLUSH %d", (int)status); cf_context->state = NVM_STATE_FLUSH_DONE; cf_context->client_status = nvm_error_map(status); break; default: tr_debug("unhandled cmd_code %d", cmd_code); break; } return; } static int nvm_fsm_update(cs_context_t *cs_context) { int ret_val = 0; tr_debug("nvm_fsm_update() state=%d", (int)cs_context->state); switch (cs_context->state) { case NVM_STATE_UNINIT_DONE: cs_context->client_cb(cs_context->client_status, cs_context->client_context); cs_context->state = NVM_STATE_NONE; if (cs_context->client_status == PLATFORM_NVM_OK) { ns_dyn_mem_free(cs_context_ptr); cs_context_ptr = NULL; } ret_val = 1; break; case NVM_STATE_INIT_DONE: case NVM_STATE_CREATE_DONE: case NVM_STATE_OPEN_DONE: case NVM_STATE_WRITE_DONE: case NVM_STATE_READ_DONE: case NVM_STATE_DELETE_DONE: case NVM_STATE_CLOSE_DONE: case NVM_STATE_FLUSH_DONE: cs_context->state = NVM_STATE_NONE; cs_context->client_cb(cs_context->client_status, cs_context->client_context); ret_val = 1; break; default: tr_error("unknown state %d", cs_context->state); break; } return ret_val; } /** * Initialize NVM */ platform_nvm_status platform_nvm_init(nvm_callback *callback, void *context) { int32_t ret; tr_debug("platform_nvm_init()"); if (callback == NULL || cs_context_ptr) { return PLATFORM_NVM_ERROR; } if (cs_context_ptr == NULL) { cs_context_ptr = ns_dyn_mem_alloc(sizeof(cs_context_t)); } if (cs_context_ptr == NULL) { return PLATFORM_NVM_ERROR; } memset(cs_context_ptr, 0, sizeof(cs_context_t)); cs_context_ptr->client_cb = callback; cs_context_ptr->client_context = context; cs_context_ptr->capabilities = drv->GetCapabilities(); tr_debug("mode: %s", IS_SYNC_MODE(cs_context_ptr) ? "sync" : "async"); ret = drv->Initialize(configuration_store_cb, cs_context_ptr); if (ret < ARM_DRIVER_OK) { tr_error("initialisation failed %d", (int)ret); ns_dyn_mem_free(cs_context_ptr); cs_context_ptr = NULL; return PLATFORM_NVM_ERROR; } drv->PowerControl(ARM_POWER_FULL); // start timer to report initialization status back to client nvm_fsm_timer_start(); return PLATFORM_NVM_OK; } /* * Deinitialize NVM. */ platform_nvm_status platform_nvm_finalize(nvm_callback *callback, void *context) { int32_t ret; tr_debug("platform_nvm_deinit()"); if (!nvm_status_check(cs_context_ptr)) { return PLATFORM_NVM_ERROR; } if (callback == NULL) { return PLATFORM_NVM_ERROR; } cs_context_ptr->client_cb = callback; cs_context_ptr->client_context = context; cs_context_ptr->state = NVM_STATE_UNINITIALIZING; cs_context_ptr->client_status = PLATFORM_NVM_OK; drv->PowerControl(ARM_POWER_OFF); ret = drv->Uninitialize(); if (ret < ARM_DRIVER_OK) { tr_error("deinit failed %d", (int)ret); cs_context_ptr->state = NVM_STATE_UNINIT_DONE; cs_context_ptr->client_status = nvm_error_map(ret); } nvm_fsm_timer_start(); return PLATFORM_NVM_OK; } /* * Create key to NVM */ platform_nvm_status platform_nvm_key_create(nvm_callback *callback, const char *key_name, uint16_t value_len, uint32_t flags, void *context) { int32_t ret; ARM_CFSTORE_KEYDESC keydesc; (void)flags; tr_debug("platform_nvm_key_create() %s len=%d", key_name, value_len); if (callback == NULL || key_name == NULL) { return PLATFORM_NVM_ERROR; } if (!nvm_status_check(cs_context_ptr)) { return PLATFORM_NVM_ERROR; } cs_context_ptr->client_cb = callback; cs_context_ptr->client_context = context; cs_context_ptr->state = NVM_STATE_CREATING; cs_context_ptr->client_status = PLATFORM_NVM_OK; memset(&keydesc, 0, sizeof(ARM_CFSTORE_KEYDESC)); keydesc.drl = ARM_RETENTION_NVM; ret = drv->Create(key_name, value_len, &keydesc, cs_context_ptr->hkey); if (ret < ARM_DRIVER_OK) { if (ret == ARM_CFSTORE_DRIVER_ERROR_PREEXISTING_KEY) { tr_debug("adjust value len to %d", value_len); ret = drv->Create(key_name, value_len, NULL, cs_context_ptr->hkey); } } if (ret < ARM_DRIVER_OK) { tr_error("Key creation failed %d", (int)ret); cs_context_ptr->state = NVM_STATE_CREATE_DONE; cs_context_ptr->client_status = nvm_error_map(ret); } nvm_fsm_timer_start(); return PLATFORM_NVM_OK; } /** * Delete key from NVM */ platform_nvm_status platform_nvm_key_delete(nvm_callback *callback, const char *key_name, void *context) { int32_t ret; ARM_CFSTORE_FMODE flags; tr_debug("platform_nvm_key_delete() %s", key_name); if (callback == NULL || key_name == NULL) { return PLATFORM_NVM_ERROR; } if (!nvm_status_check(cs_context_ptr)) { return PLATFORM_NVM_ERROR; } cs_context_ptr->client_cb = callback; cs_context_ptr->client_context = context; cs_context_ptr->client_status = PLATFORM_NVM_OK; cs_context_ptr->state = NVM_STATE_DELETING; memset(&flags, 0, sizeof(ARM_CFSTORE_FMODE)); flags.read = 1; flags.write = 1; ret = drv->Open(key_name, flags, cs_context_ptr->hkey); if (ret < ARM_DRIVER_OK) { tr_error("Key delete, open failed %d", (int)ret); cs_context_ptr->state = NVM_STATE_DELETE_DONE; cs_context_ptr->client_status = nvm_error_map(ret); } // start callback timer in both asynch and synch mode nvm_fsm_timer_start(); return PLATFORM_NVM_OK; } /** * Reading from NVM */ platform_nvm_status platform_nvm_read(nvm_callback *callback, const char *key_name, void *buf, uint16_t *buf_len, void *context) { int32_t ret; ARM_CFSTORE_FMODE flags; tr_debug("platform_nvm_read()"); if (callback == NULL || key_name == NULL || buf == NULL || buf_len == NULL) { return PLATFORM_NVM_ERROR; } if (!nvm_status_check(cs_context_ptr)) { return PLATFORM_NVM_ERROR; } cs_context_ptr->client_cb = callback; cs_context_ptr->client_context = context; cs_context_ptr->client_buf = buf; cs_context_ptr->client_buf_len = buf_len; cs_context_ptr->data_len = *buf_len; cs_context_ptr->client_status = PLATFORM_NVM_OK; cs_context_ptr->state = NVM_STATE_READING; // Open handle for reading memset(&flags, 0, sizeof(ARM_CFSTORE_FMODE)); flags.read = 1; ret = drv->Open(key_name, flags, cs_context_ptr->hkey); if (ret < ARM_DRIVER_OK) { tr_error("Read failed to open handle %d", (int)ret); cs_context_ptr->state = NVM_STATE_READ_DONE; cs_context_ptr->client_status = nvm_error_map(ret); } // start callback timer in both async and synch mode nvm_fsm_timer_start(); return PLATFORM_NVM_OK; } /** * Write to NVM. */ platform_nvm_status platform_nvm_write(nvm_callback *callback, const char *key_name, const void *data, uint16_t *data_len, void *context) { int32_t ret; ARM_CFSTORE_FMODE flags; tr_debug("platform_nvm_write()"); if (callback == NULL || key_name == NULL || data == NULL || data_len == NULL) { return PLATFORM_NVM_ERROR; } if (!nvm_status_check(cs_context_ptr)) { return PLATFORM_NVM_ERROR; } cs_context_ptr->client_cb = callback; cs_context_ptr->client_context = context; cs_context_ptr->client_buf = (void *)data; cs_context_ptr->client_buf_len = data_len; cs_context_ptr->data_len = *data_len; cs_context_ptr->client_status = PLATFORM_NVM_OK; cs_context_ptr->state = NVM_STATE_WRITING; // Open handle for writing, execution continues in callback memset(&flags, 0, sizeof(ARM_CFSTORE_FMODE)); flags.write = 1; ret = drv->Open(key_name, flags, cs_context_ptr->hkey); if (ret < ARM_DRIVER_OK) { tr_error("Write failed %d", (int)ret); cs_context_ptr->state = NVM_STATE_WRITE_DONE; cs_context_ptr->client_status = nvm_error_map(ret); } // start callback timer in both asynch and synch mode nvm_fsm_timer_start(); return PLATFORM_NVM_OK; } /** * Flush the NVM */ platform_nvm_status platform_nvm_flush(nvm_callback *callback, void *context) { tr_debug("platform_nvm_flush()"); int32_t ret; if (callback == NULL) { return PLATFORM_NVM_ERROR; } if (!nvm_status_check(cs_context_ptr)) { return PLATFORM_NVM_ERROR; } cs_context_ptr->client_cb = callback; cs_context_ptr->client_context = context; cs_context_ptr->client_status = PLATFORM_NVM_OK; cs_context_ptr->state = NVM_STATE_FLUSHING; ret = drv->Flush(); if (ret < ARM_DRIVER_OK) { cs_context_ptr->state = NVM_STATE_FLUSH_DONE; cs_context_ptr->client_status = nvm_error_map(ret); } // start callback timer in both asynch and synch mode nvm_fsm_timer_start(); return PLATFORM_NVM_OK; } static bool nvm_write_internal(cs_context_t *cf_context) { int32_t ret; cf_context->state = NVM_STATE_WRITING; ret = drv->Write(cf_context->hkey, (const char *)cf_context->client_buf, &cf_context->data_len); if (ret >= ARM_DRIVER_OK) { return true; } else { tr_error("Write failed %d", (int)ret); return false; } } static bool nvm_read_internal(cs_context_t *cf_context) { int32_t ret; cf_context->state = NVM_STATE_READING; ret = drv->Read(cf_context->hkey, (void *)cf_context->client_buf, &cf_context->data_len); if (ret >= ARM_DRIVER_OK) { return true; } else { tr_error("Read failed %d", (int)ret); return false; } } static bool nvm_delete_internal(cs_context_t *cf_context) { int32_t ret; cf_context->state = NVM_STATE_DELETING; ret = drv->Delete(cf_context->hkey); if (ret >= ARM_DRIVER_OK) { return true; } else { tr_error("Delete failed %d", (int)ret); return false; } } static bool nvm_close_internal(cs_context_t *cf_context) { int32_t ret; cf_context->state = NVM_STATE_CLOSING; ret = drv->Close(cf_context->hkey); if (ret >= ARM_DRIVER_OK) { return true; } else { tr_error("Close failed %d", (int)ret); return false; } } /* * Check NVM state before executing client action */ static bool nvm_status_check(cs_context_t *cf_context) { if (!cs_context_ptr) { // not initialized tr_error("NVM not initialized"); return false; } if (cf_context->state != NVM_STATE_NONE) { tr_error("NVM busy, operation in progress %d", cf_context->state); return false; } return true; } static platform_nvm_status nvm_error_map(int32_t cs_error) { platform_nvm_status client_error; if (cs_error >= ARM_DRIVER_OK) { return PLATFORM_NVM_OK; } switch (cs_error) { case ARM_CFSTORE_DRIVER_ERROR_KEY_NOT_FOUND: client_error = PLATFORM_NVM_KEY_NOT_FOUND; break; default: client_error = PLATFORM_NVM_ERROR; break; } return client_error; } static void nvm_fsm_timer_cb(void *args) { (void) args; switch (nvm_fsm_update(cs_context_ptr)) { case 0: // Nothing processed, restart timer tr_debug("nvm_fsm_timer_cb not handled event in () %d", (int)cs_context_ptr->state); nvm_fsm_timer_start(); break; default: break; } } /* * Start timer for polling callback from */ static void nvm_fsm_timer_start(void) { cs_context_ptr->callback_timer = eventOS_timeout_ms(nvm_fsm_timer_cb, NVM_CB_POLLING_TIMEOUT, NULL); } #endif /* MBED_CONF_NANOSTACK_HAL_NVM_CFSTORE */