/*************************************************************************************************/ /*! * \file wsf_nvm.c * * \brief NVM service. * * Copyright (c) 2019 Arm Ltd. All Rights Reserved. * * Copyright (c) 2019-2020 Packetcraft, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /*************************************************************************************************/ #include "wsf_types.h" #include "wsf_nvm.h" #include "wsf_assert.h" #include "pal_flash.h" #include "crc.h" /************************************************************************************************** Macros **************************************************************************************************/ /*! NVM data start address. */ #define WSF_NVM_START_ADDR 0x0000 /*! Reserved filecode. */ #define WSF_NVM_RESERVED_FILECODE ((uint16_t)0) /* Unused (erased) filecode. */ /* TODO: May depend on flash type */ #define WSF_NVM_UNUSED_FILECODE ((uint16_t)0xFFFF) /*! Flash word size. */ #define WSF_FLASH_WORD_SIZE 1U /*! Align value to word boundary. */ #define WSF_NVM_WORD_ALIGN(x) (((x) + (WSF_FLASH_WORD_SIZE - 1)) & ~(WSF_FLASH_WORD_SIZE - 1)) #define WSF_NVM_CRC_INIT_VALUE 0xFEDCBA98 /************************************************************************************************** Data Types **************************************************************************************************/ /*! \brief Header. */ typedef struct { uint16_t id; /*!< Stored data ID. */ uint16_t len; /*!< Stored data length. */ uint16_t headerCrc; /*!< CRC of this header. */ uint16_t dataCrc; /*!< CRC of subsequent data. */ } WsfNvmHeader_t; static struct { uint32_t availAddr; /*!< Next available address for NVM write. */ uint32_t sectorSize; /*!< Size of erase sector. */ uint32_t totalSize; /*!< Total size of NVM storage. */ } wsfNvmCb; /************************************************************************************************** Global Functions **************************************************************************************************/ /*************************************************************************************************/ /*! * \brief Initialize the WSF NVM. */ /*************************************************************************************************/ void WsfNvmInit(void) { PalFlashInit(NULL); wsfNvmCb.totalSize = PalNvmGetTotalSize(); wsfNvmCb.sectorSize = PalNvmGetSectorSize(); WsfNvmHeader_t header; uint32_t storageAddr = WSF_NVM_START_ADDR; uint16_t headerCrc; bool_t corruptData = FALSE; do { /* Read header. */ PalFlashRead(&header, sizeof(header), storageAddr); if (header.id == WSF_NVM_UNUSED_FILECODE) { /* Found unused entry at end of used storage. */ break; } /* Iterate through stored data headers, looking for existing matching stored data header. */ if (header.id != WSF_NVM_RESERVED_FILECODE) { /* Calculate CRC of header itself. */ headerCrc = crc16((uint8_t *)&header, sizeof(header.id) + sizeof(header.len)); if (headerCrc != header.headerCrc) { /* Corrupt header. */ corruptData = TRUE; break; } } else { if ((header.headerCrc != 0) || (header.dataCrc != 0)) { /* Corrupt header. */ corruptData = TRUE; break; } } /* Move to next stored data block and read header. */ storageAddr += WSF_NVM_WORD_ALIGN(header.len) + sizeof(header); WSF_ASSERT((storageAddr - WSF_NVM_START_ADDR) < wsfNvmCb.totalSize); } while ((storageAddr - WSF_NVM_START_ADDR) < wsfNvmCb.totalSize); wsfNvmCb.availAddr = storageAddr; /* Check for corrupt data. */ if (corruptData == TRUE) { /* Search for the first available location */ while ((storageAddr - WSF_NVM_START_ADDR) < wsfNvmCb.totalSize) { PalFlashRead(&header.id, sizeof(header.id), storageAddr); if (header.id == WSF_NVM_UNUSED_FILECODE) { break; } storageAddr += sizeof(header.id); } /* Update the address of the first available location. align to sector boundary. */ wsfNvmCb.availAddr = (storageAddr + wsfNvmCb.sectorSize - 1) & ~(wsfNvmCb.sectorSize - 1); /* Erase all data. */ WsfNvmEraseDataAll(NULL); } } /*************************************************************************************************/ /*! * \brief Read data. * * \param id Stored data ID. * \param pData Buffer to read to. * \param len Data length to read. * \param compCback Read callback. * * \return TRUE if NVM operation is successful, FALSE otherwise. */ /*************************************************************************************************/ bool_t WsfNvmReadData(uint16_t id, uint8_t *pData, uint16_t len, WsfNvmCompEvent_t compCback) { WsfNvmHeader_t header; uint16_t headerCrc, dataCrc; uint32_t storageAddr = WSF_NVM_START_ADDR; bool_t findId = FALSE; WSF_ASSERT(!((id == WSF_NVM_RESERVED_FILECODE) || (id == WSF_NVM_UNUSED_FILECODE))); /* Read first header. */ PalFlashRead(&header, sizeof(header), storageAddr); do { if (header.id == WSF_NVM_UNUSED_FILECODE) { /* Found unused entry at end of used storage. */ break; } /* Iterate through stored data headers, looking for existing matching stored data header. */ if (header.id != WSF_NVM_RESERVED_FILECODE) { /* Calculate CRC of header itself. */ headerCrc = crc16((uint8_t *)&header, sizeof(header.id) + sizeof(header.len)); if (headerCrc != header.headerCrc) { /* Corrupt header. */ /* TODO: Catastrophic failure? */ break; } else if ((header.id == id) && (header.len == len)) { /* Valid header and matching ID - read data after header. */ storageAddr += sizeof(header); PalFlashRead(pData, header.len, storageAddr); dataCrc = crc16(pData, header.len); if (dataCrc == header.dataCrc) { findId = TRUE; } break; } } /* Move to next stored data block and read header. */ storageAddr += WSF_NVM_WORD_ALIGN(header.len) + sizeof(header); PalFlashRead(&header, sizeof(header), storageAddr); } while (1); if (compCback) { compCback(findId); } return findId; } /*************************************************************************************************/ /*! * \brief Write data. * * \param id Stored data ID. * \param pData Buffer to write. * \param len Data length to write. * \param compCback Write callback. * * \return TRUE if NVM operation is successful, FALSE otherwise. */ /*************************************************************************************************/ bool_t WsfNvmWriteData(uint16_t id, const uint8_t *pData, uint16_t len, WsfNvmCompEvent_t compCback) { WsfNvmHeader_t header; uint16_t headerCrc, dataCrc; uint32_t storageAddr = WSF_NVM_START_ADDR; WSF_ASSERT(!((id == WSF_NVM_RESERVED_FILECODE) || (id == WSF_NVM_UNUSED_FILECODE))); WSF_ASSERT((wsfNvmCb.availAddr - WSF_NVM_START_ADDR) <= wsfNvmCb.totalSize); /* Read first header. */ PalFlashRead(&header, sizeof(header), storageAddr); do { if (header.id == WSF_NVM_UNUSED_FILECODE) { /* Found unused entry at end of used storage. */ break; } /* Iterate through stored data headers, looking for existing matching stored data header. */ if (header.id != WSF_NVM_RESERVED_FILECODE) { /* Calculate CRC of header itself. */ headerCrc = crc16((uint8_t *)&header, sizeof(header.id) + sizeof(header.len)); if (headerCrc != header.headerCrc) { /* Corrupt header. */ /* TODO: Catastrophic failure? */ break; } else if (header.id == id) { dataCrc = crc16(pData, len); if (dataCrc == header.dataCrc) { if (compCback) { compCback(TRUE); } return TRUE; } else { /* Valid header and matching ID - scratch header out. */ header.id = WSF_NVM_RESERVED_FILECODE; header.headerCrc = 0; header.dataCrc = 0; PalFlashWrite((const uint8_t *)&header, sizeof(header), storageAddr); } } } /* Move to next stored data block and read header. */ storageAddr += WSF_NVM_WORD_ALIGN(header.len) + sizeof(header); PalFlashRead(&header, sizeof(header), storageAddr); } while (1); /* After cycling through all headers, create a new stored data header and store data */ header.id = id; header.len = len; header.headerCrc = crc16((uint8_t *)&header, sizeof(header.id) + sizeof(header.len)); header.dataCrc = crc16(pData, len); PalFlashWrite((const uint8_t *)&header, sizeof(header), storageAddr); PalFlashWrite(pData, len, storageAddr + sizeof(header)); /* Move to next empty flash. */ storageAddr += WSF_NVM_WORD_ALIGN(header.len) + sizeof(header); wsfNvmCb.availAddr = storageAddr; if (compCback) { compCback((wsfNvmCb.availAddr - WSF_NVM_START_ADDR) <= wsfNvmCb.totalSize); } return TRUE; } /*************************************************************************************************/ /*! * \brief Erase data. * * \param id Erase ID. * \param compCback Write callback. * * \return TRUE if NVM operation is successful, FALSE otherwise. */ /*************************************************************************************************/ bool_t WsfNvmEraseData(uint64_t id, WsfNvmCompEvent_t compCback) { WsfNvmHeader_t header; uint32_t headerCrc; uint32_t storageAddr = WSF_NVM_START_ADDR; bool_t erased = FALSE; WSF_ASSERT(!((id == WSF_NVM_RESERVED_FILECODE) || (id == WSF_NVM_UNUSED_FILECODE))); /* Read first header. */ PalFlashRead(&header, sizeof(header), storageAddr); do { if (header.id == WSF_NVM_UNUSED_FILECODE) { /* Found unused entry at end of used storage. */ break; } /* Iterate through stored data headers, looking for existing matching stored data header. */ if (header.id != WSF_NVM_RESERVED_FILECODE) { headerCrc = crc16((uint8_t *)&header, sizeof(header.id) + sizeof(header.len)); if (headerCrc != header.headerCrc) { /* Corrupt header. */ /* TODO: Catastrophic failure? */ break; } else if (header.id == id) { header.id = WSF_NVM_RESERVED_FILECODE; header.headerCrc = 0; header.dataCrc = 0; PalFlashWrite((const uint8_t *)&header, sizeof(header), storageAddr); erased = TRUE; } } /* Move to next stored data block and read header. */ storageAddr += WSF_NVM_WORD_ALIGN(header.len) + sizeof(header); PalFlashRead(&header, sizeof(header), storageAddr); } while (1); if (compCback) { compCback(erased); } return erased; } /*************************************************************************************************/ /*! * \brief Erase all data located in NVM storage. * * \param compCback Erase callback. * * \note Security Risk Warning. NVM storage could be shared by multiple Apps. */ /*************************************************************************************************/ void WsfNvmEraseDataAll(WsfNvmCompEvent_t compCback) { for (uint32_t eraseAddr = WSF_NVM_START_ADDR; eraseAddr < wsfNvmCb.availAddr; eraseAddr += wsfNvmCb.sectorSize) { PalFlashEraseSector(1, eraseAddr); } wsfNvmCb.availAddr = WSF_NVM_START_ADDR; if (compCback) { compCback(TRUE); } }