/* * Copyright (c) 2019-2025 Beijing Hanwei Innovation Technology Ltd. Co. and * its subsidiaries and affiliates (collectly called MKSEMI). * * 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, except as embedded into an MKSEMI * integrated circuit in a product or a software update for such product, * 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 MKSEMI nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * 4. This software, with or without modification, must only be used with a * MKSEMI integrated circuit. * * 5. Any software provided in binary form under this license must not be * reverse engineered, decompiled, modified and/or disassembled. * * THIS SOFTWARE IS PROVIDED BY MKSEMI "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL MKSEMI 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 "mk_nvm.h" #include "mk_trace.h" #include "crc.h" /************************************************************************************************** Macros **************************************************************************************************/ /*! The size of flash space for NVM is 8K */ #define PAL_NVM_SIZE (0x2000) /*! NVM start address is 0x0407E000, 126th sector */ #define PAL_NVM_ADDR (0x0407E000) /*! defragmentation area size. */ #define NVM_DEFRAGMENT_SIZE (0x400) /*! defragmentation area start address */ #define NVM_DEFRAGMENT_START_ADDR (PAL_NVM_SIZE - NVM_DEFRAGMENT_SIZE) /*! NVM data offset. */ #define NVM_DATA_OFFSET 0x0000 /*! Reserved filecode. */ #define NVM_OBSOLETED_REC_TYPE ((uint16_t)0) /* Unused (erased) filecode. */ #define NVM_UNUSED_REC_TYPE ((uint16_t)0xFFFF) /*! Flash word size. */ #define NVM_FLASH_WORD_SIZE 1U /*! Align value to word boundary. */ #define NVM_WORD_ALIGN(x) (((x) + (NVM_FLASH_WORD_SIZE - 1)) & ~(NVM_FLASH_WORD_SIZE - 1)) #define pal_flash_read(pBuf, size, srcAddr) flash_read(FLASH_ID0, PAL_NVM_ADDR + srcAddr, (uint8_t *)pBuf, size) #if FLASH_WRITE_EN #define pal_flash_write(pBuf, size, dstAddr) flash_write_nbytes(FLASH_ID0, PAL_NVM_ADDR + dstAddr, pBuf, size) #else #define pal_flash_write(pBuf, size, dstAddr) (void)0 #endif /************************************************************************************************** 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. */ } rec_hdr_t; /*************************************************************************************************/ /*! * \brief Erase sector. * * \param[in] numOfSectors Number of sectors to be erased. * \param[in] startAddr Sector aligned start address. */ /*************************************************************************************************/ static void pal_flash_erase_sectors(uint32_t numOfSectors, uint32_t startAddr) { #if FLASH_WRITE_EN uint32_t start_idx = (PAL_NVM_ADDR + startAddr - FLASH_BASE) / FLASH_SECTOR_SIZE; ASSERT((PAL_NVM_ADDR + startAddr) % FLASH_SECTOR_SIZE == 0, "Address is not aligned to sector size: %d", PAL_NVM_ADDR + startAddr); ASSERT(numOfSectors <= (PAL_NVM_SIZE / FLASH_SECTOR_SIZE), "Number of sectors exceeds limit: %d", numOfSectors); ASSERT(start_idx + numOfSectors <= (FLASH_SIZE / FLASH_SECTOR_SIZE), "End index exceeds limit: %d", start_idx + numOfSectors); for (uint32_t i = 0; i < numOfSectors; i++) { flash_sector_erase(FLASH_ID0, i + start_idx); } #endif } #define NVM_REC_FREE (1U) #define NVM_REC_RUIN (2U) #define NVM_REC_HITS (3U) #define NVM_REC_ENDS (4U) static uint16_t _seek_record(rec_hdr_t *header, uint32_t *storageAddr, uint16_t id) { do { pal_flash_read(header, sizeof(rec_hdr_t), *storageAddr); if (header->id == NVM_UNUSED_REC_TYPE) { return NVM_REC_FREE; } else if (header->id == NVM_OBSOLETED_REC_TYPE) { if ((header->headerCrc != 0) || (header->dataCrc != 0)) { return NVM_REC_RUIN; } } else { uint16_t headerCrc = crc16((uint8_t *)header, sizeof(header->id) + sizeof(header->len)); if (headerCrc != header->headerCrc) { return NVM_REC_RUIN; } else if (header->id == id) { return NVM_REC_HITS; } } *storageAddr += NVM_WORD_ALIGN(header->len) + sizeof(rec_hdr_t); } while ((*storageAddr - NVM_DATA_OFFSET) < PAL_NVM_SIZE); return NVM_REC_ENDS; } void mk_nvm_init(void) { flash_open(FLASH_ID0, NULL); rec_hdr_t header; uint32_t storageAddr = NVM_DATA_OFFSET; uint16_t ret = _seek_record(&header, &storageAddr, NVM_UNUSED_REC_TYPE); if (ret == NVM_REC_RUIN) { mk_nvm_erase_all(); } } bool mk_nvm_read(uint16_t id, uint8_t *pData, uint16_t len) { rec_hdr_t header; uint16_t dataCrc; uint32_t storageAddr = NVM_DATA_OFFSET; ASSERT((id != NVM_UNUSED_REC_TYPE) && (id != NVM_OBSOLETED_REC_TYPE), "Invalid ID: %d", id); uint16_t ret = _seek_record(&header, &storageAddr, id); if (ret == NVM_REC_HITS) { if (header.len == len) { pal_flash_read(pData, header.len, storageAddr + sizeof(rec_hdr_t)); dataCrc = crc16(pData, header.len); if (dataCrc == header.dataCrc) { return true; } } } return false; } static void _obsolete_record(rec_hdr_t *header, uint32_t storageAddr) { header->id = NVM_OBSOLETED_REC_TYPE; header->headerCrc = 0; header->dataCrc = 0; pal_flash_write((const uint8_t *)header, sizeof(rec_hdr_t), storageAddr); } static void __copy_record(uint32_t srcAddr, uint32_t dstAddr, uint16_t len) { uint8_t buf[8]; uint16_t remain = len; while (remain > 0) { uint16_t copyLen = (remain > sizeof(buf)) ? sizeof(buf) : remain; pal_flash_read(buf, copyLen, srcAddr); pal_flash_write(buf, copyLen, dstAddr); srcAddr += copyLen; dstAddr += copyLen; remain -= copyLen; } } static void __copy_valid_records(uint32_t srcAddr, uint32_t dstAddr, uint32_t endAddr) { rec_hdr_t header; while (srcAddr < endAddr) { pal_flash_read(&header, sizeof(rec_hdr_t), srcAddr); if (header.id == NVM_UNUSED_REC_TYPE) { break; } else if (header.id == NVM_OBSOLETED_REC_TYPE) { } else { __copy_record(srcAddr, dstAddr, NVM_WORD_ALIGN(header.len) + sizeof(rec_hdr_t)); dstAddr += NVM_WORD_ALIGN(header.len) + sizeof(rec_hdr_t); } srcAddr += NVM_WORD_ALIGN(header.len) + sizeof(rec_hdr_t); } } static void _defragment(void) { /* copy valid records to the defragmentation area */ __copy_valid_records(NVM_DATA_OFFSET, NVM_DEFRAGMENT_START_ADDR, NVM_DEFRAGMENT_START_ADDR); /* erase the fist sector of the NVM */ pal_flash_erase_sectors(1, NVM_DATA_OFFSET); /* copy valid records back to the fisrt sector of the NVM */ __copy_valid_records(NVM_DEFRAGMENT_START_ADDR, NVM_DATA_OFFSET, PAL_NVM_SIZE); /* erase all sectors of the NVM except the first one */ pal_flash_erase_sectors(PAL_NVM_SIZE / FLASH_SECTOR_SIZE - 1, NVM_DATA_OFFSET + FLASH_SECTOR_SIZE); } bool mk_nvm_write(uint16_t id, const uint8_t *pData, uint16_t len) { rec_hdr_t header; uint16_t dataCrc; uint32_t storageAddr = NVM_DATA_OFFSET; ASSERT((id != NVM_OBSOLETED_REC_TYPE) && (id != NVM_UNUSED_REC_TYPE), "Invalid ID: %d", id); uint16_t ret = _seek_record(&header, &storageAddr, id); if (ret == NVM_REC_HITS) { dataCrc = crc16(pData, len); if (dataCrc == header.dataCrc) { return true; } else { _obsolete_record(&header, storageAddr); } } ret = _seek_record(&header, &storageAddr, NVM_UNUSED_REC_TYPE); if (storageAddr + NVM_DEFRAGMENT_SIZE + NVM_WORD_ALIGN(len) >= PAL_NVM_SIZE) { _defragment(); storageAddr = NVM_DATA_OFFSET; ret = _seek_record(&header, &storageAddr, NVM_UNUSED_REC_TYPE); } if (ret == NVM_REC_FREE) { header.id = id; header.len = len; header.headerCrc = crc16((uint8_t *)&header, sizeof(header.id) + sizeof(header.len)); header.dataCrc = crc16(pData, len); pal_flash_write((const uint8_t *)&header, sizeof(rec_hdr_t), storageAddr); pal_flash_write(pData, len, storageAddr + sizeof(rec_hdr_t)); return true; } return false; } bool mk_nvm_erase(uint16_t id) { rec_hdr_t header; uint32_t storageAddr = NVM_DATA_OFFSET; ASSERT((id != NVM_OBSOLETED_REC_TYPE) && (id != NVM_UNUSED_REC_TYPE), "Invalid ID: %d", id); uint16_t ret = _seek_record(&header, &storageAddr, id); if (ret == NVM_REC_HITS) { _obsolete_record(&header, storageAddr); return true; } return false; } void mk_nvm_erase_all(void) { pal_flash_erase_sectors(PAL_NVM_SIZE / FLASH_SECTOR_SIZE, NVM_DATA_OFFSET); }