From 71d3a2c31973c4276969558d14ba51d15fe8f197 Mon Sep 17 00:00:00 2001 From: Silvano Seva Date: Mon, 12 Aug 2024 08:18:41 +0200 Subject: [PATCH] Driver for emulated EEPROM storage --- openrtx/include/interfaces/nvmem.h | 3 +- platform/drivers/NVM/eeep.c | 378 +++++++++++++++++++++++++++++ platform/drivers/NVM/eeep.h | 85 +++++++ 3 files changed, 465 insertions(+), 1 deletion(-) create mode 100644 platform/drivers/NVM/eeep.c create mode 100644 platform/drivers/NVM/eeep.h diff --git a/openrtx/include/interfaces/nvmem.h b/openrtx/include/interfaces/nvmem.h index 3ac23a9e..ea8e79fe 100644 --- a/openrtx/include/interfaces/nvmem.h +++ b/openrtx/include/interfaces/nvmem.h @@ -37,7 +37,8 @@ enum nvmType { NVM_FLASH = 0, ///< FLASH type non volatile memory NVM_EEPROM, ///< EEPROM type non volatile memory - NVM_FILE ///< File type non volatile memory + NVM_FILE, ///< File type non volatile memory + NVM_EEEPROM ///< Emulated EEPROM type non volatile memory }; /** diff --git a/platform/drivers/NVM/eeep.c b/platform/drivers/NVM/eeep.c new file mode 100644 index 00000000..3fa44fe6 --- /dev/null +++ b/platform/drivers/NVM/eeep.c @@ -0,0 +1,378 @@ +/*************************************************************************** + * Copyright (C) 2024 by Federico Amedeo Izzo IU2NUO, * + * Niccolò Izzo IU2KIN * + * Frederik Saraci IU2NRO * + * Silvano Seva IU2KWO * + * Morgan Diepart ON4MOD * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include +#include +#include +#include +#include "eeep.h" + +// NOTE: cannot use an enum for these because enum type is "int" +#define EEEP_PAGE_ERASED (0xFFFFFFFF) +#define EEEP_PAGE_VERIFIED (0x00FFFFFF) +#define EEEP_PAGE_COPYING (0x0000FFFF) +#define EEEP_PAGE_ACTIVE (0x000000FF) +#define EEEP_PAGE_INACTIVE (0x00000000) +#define EEEP_PAGE_HDR_SIZE sizeof(uint32_t) + +enum RecordStatus +{ + EEEP_RECORD_EMPTY = 0xFF, + EEEP_RECORD_INVALID = 0x55, + EEEP_RECORD_VALID = 0x05, + EEEP_RECORD_ERASED = 0x00 +}; + +struct eeepRecord +{ + uint8_t status; + uint8_t size; + uint16_t virtAddr; +}; + +struct eeepEntry +{ + uint32_t physAddr; + uint16_t virtAddr; +}; + +static uint32_t nextRecordAddress(const uint32_t addr, const struct eeepRecord *rec) +{ + uint32_t nextAddr = addr; + switch(rec->status) + { + case EEEP_RECORD_EMPTY: + break; + + case EEEP_RECORD_INVALID: + if(rec->size != 0xFF) + nextAddr += rec->size; + break; + + case EEEP_RECORD_VALID: + case EEEP_RECORD_ERASED: + nextAddr += rec->size; + break; + } + + // Next record is at least one record header ahead of the current address. + return nextAddr + sizeof(struct eeepRecord); +} + +static int findRecord(struct eeepData *priv, uint32_t *memAddr, const uint16_t virtAddr) +{ + struct eeepRecord rec; + uint32_t addr = priv->readAddr; + *memAddr = 0xFFFFFFFF; + + while(addr < priv->writeAddr) + { + int ret = nvm_devRead(priv->nvm, addr, &rec, sizeof(struct eeepRecord)); + if(ret < 0) + return ret; + + if((rec.status == EEEP_RECORD_VALID) && (rec.virtAddr == virtAddr)) + *memAddr = addr; + + addr = nextRecordAddress(addr, &rec); + } + + if(*memAddr != 0xFFFFFFFF) + return 0; + + return -1; +} + +static int writeRecord(struct eeepData *priv, uint16_t virtAddr, const void *data, + size_t len) +{ + uint32_t dataAddr = priv->writeAddr + sizeof(struct eeepRecord); + uint32_t headAddr = priv->writeAddr; + struct eeepRecord rec = + { + .status = EEEP_RECORD_INVALID, + .size = len, + .virtAddr = virtAddr + }; + + // Write record header + int ret = nvm_devWrite(priv->nvm, headAddr, &rec, sizeof(struct eeepRecord)); + if(ret < 0) + return ret; + + // Write data. If the operation fails, it is assumed anyways that the entire + // data block has been written. This to be sure that following writes do not + // end up in a partially-written space. + priv->writeAddr += sizeof(struct eeepRecord) + len; + ret = nvm_devWrite(priv->nvm, dataAddr, data, len); + if(ret < 0) + return ret; + + // Finally, update the record header changing the state to "valid". + rec.status = EEEP_RECORD_VALID; + ret = nvm_devWrite(priv->nvm, headAddr, &rec, sizeof(struct eeepRecord)); + + return ret; +} + +static int swapBlock(struct eeepData *priv) +{ + // Round-robin page swap. + // Note: when computing the next block address we have to take into account + // that readAddr points at the first record after the page header. + uint32_t currBlock = priv->readAddr - EEEP_PAGE_HDR_SIZE; + uint32_t nextBlock = currBlock + priv->nvm->info->erase_size; + if(nextBlock >= (priv->part->offset + priv->part->size)) + nextBlock = priv->part->offset; + + // Erase new page + int ret = nvm_devErase(priv->nvm, nextBlock, priv->nvm->info->erase_size); + if(ret < 0) + return ret; + + // Search for all the active entries + // TODO: use dynamic memory allocation + struct eeepEntry entryList[8] = {0}; + uint32_t address = priv->readAddr; + uint8_t numEntries = 0; + + while(address < priv->writeAddr) + { + struct eeepRecord rec; + + ret = nvm_devRead(priv->nvm, address, &rec, sizeof(struct eeepRecord)); + if(ret < 0) + return ret; + + if(rec.status == EEEP_RECORD_VALID) + { + uint8_t pos; + for(pos = 0; pos < numEntries; pos++) + { + // Found record in list, update its physical address + if(entryList[pos].virtAddr == rec.virtAddr) + { + entryList[pos].physAddr = address; + break; + } + } + + // Record not found, append it to the list + if(pos == numEntries) + { + entryList[pos].virtAddr = rec.virtAddr; + entryList[pos].physAddr = address; + numEntries += 1; + } + } + + address = nextRecordAddress(address, &rec); + } + + // Set new write address, mark the page as a page with an ogoing copy + priv->writeAddr = nextBlock + sizeof(uint32_t); + uint32_t tmp = EEEP_PAGE_COPYING; + ret = nvm_devWrite(priv->nvm, nextBlock, &tmp, sizeof(uint32_t)); + if(ret < 0) + return ret; + + // Copy over the records to the new page, + for(uint8_t i = 0; i < numEntries; i++) + { + struct eeepRecord rec; + uint8_t data[256]; + uint32_t address = entryList[i].physAddr; + + ret = nvm_devRead(priv->nvm, address, &rec, sizeof(struct eeepRecord)); + if(ret < 0) + return ret; + + address += sizeof(struct eeepRecord); + ret = nvm_devRead(priv->nvm, address, data, rec.size); + if(ret < 0) + return ret; + + ret = writeRecord(priv, rec.virtAddr, data, rec.size); + if(ret < 0) + return ret; + } + + // Finally, set the page as the new active page, invalidate the previous one + // and update the reading address + tmp = EEEP_PAGE_ACTIVE; + ret = nvm_devWrite(priv->nvm, nextBlock, &tmp, sizeof(uint32_t)); + if(ret < 0) + return ret; + + tmp = EEEP_PAGE_INACTIVE; + ret = nvm_devWrite(priv->nvm, currBlock, &tmp, sizeof(uint32_t)); + if(ret < 0) + return ret; + + priv->readAddr = nextBlock + EEEP_PAGE_HDR_SIZE; + + return 0; +} + +static int eeep_read(const struct nvmDevice *dev, uint32_t offset, void *data, + size_t len) +{ + struct eeepData *priv = (struct eeepData *) dev->priv; + struct eeepRecord rec; + uint32_t memAddr; + + if((offset >= 0xFFFF) || (len >= 255)) + return -EINVAL; + + int ret = findRecord(priv, &memAddr, offset); + if(ret < 0) + return ret; + + // Found, read infoblock + ret = nvm_devRead(priv->nvm, memAddr, &rec, sizeof(struct eeepRecord)); + if(ret < 0) + return ret; + + // Adjust size and read data + if(rec.size < len) + len = rec.size; + + memAddr += sizeof(struct eeepRecord); + ret = nvm_devRead(priv->nvm, memAddr, data, rec.size); + + return ret; +} + +static int eeep_write(const struct nvmDevice *dev, uint32_t offset, + const void *data, size_t len) +{ + struct eeepData *priv = (struct eeepData *) dev->priv; + int ret; + + if((offset >= 0xFFFF) || (len >= 255)) + return -EINVAL; + + uint32_t usedSpace = (priv->writeAddr - priv->readAddr) + EEEP_PAGE_HDR_SIZE; + uint32_t freeSpace = priv->nvm->info->erase_size - usedSpace; + uint32_t entrySize = sizeof(struct eeepRecord) + len; + + if(entrySize > freeSpace) + { + ret = swapBlock(priv); + if(ret < 0) + return ret; + } + + return writeRecord(priv, offset, data, len); +} + +int eeep_init(const struct nvmDevice *dev, const uint32_t nvm, + const uint32_t part) +{ + const struct nvmDescriptor *desc = nvm_getDesc(nvm); + if(desc == NULL) + return -EINVAL; + + if(part >= desc->partNum) + return -EINVAL; + + uint32_t partAddr = desc->partitions[part].offset; + uint32_t partSize = desc->partitions[part].size; + uint32_t pageSize = desc->dev->info->erase_size; + + struct eeepData *priv = (struct eeepData *) dev->priv; + priv->nvm = desc->dev; + priv->part = &desc->partitions[part]; + priv->readAddr = 0xFFFFFFFF; + + // Search for an active page, set the read address to the first record + // immediately after the page header + for(uint32_t i = 0; i < partSize; i += pageSize) + { + uint32_t pageAddr = partAddr + i; + uint32_t tmp; + + nvm_devRead(priv->nvm, pageAddr, &tmp, sizeof(uint32_t)); + if(tmp == EEEP_PAGE_ACTIVE) + { + priv->readAddr = pageAddr + EEEP_PAGE_HDR_SIZE; + break; + } + } + + // If no active page found, erase all the memory and set the first page as + // active page. + if(priv->readAddr == 0xFFFFFFFF) + { + uint32_t tmp = EEEP_PAGE_ACTIVE; + + nvm_devErase(priv->nvm, partAddr, partSize); + nvm_devWrite(priv->nvm, partAddr, &tmp, sizeof(uint32_t)); + priv->readAddr = partAddr + EEEP_PAGE_HDR_SIZE; + priv->writeAddr = priv->readAddr; + } + else + { + uint32_t addr = priv->readAddr; + uint32_t end = priv->readAddr + pageSize - EEEP_PAGE_HDR_SIZE; + priv->writeAddr = end; + + while(addr < end) + { + struct eeepRecord rec; + uint32_t *tmp = (uint32_t *) &rec; + + nvm_devRead(desc->dev, addr, &rec, sizeof(struct eeepRecord)); + if(*tmp == 0xFFFFFFFF) + { + priv->writeAddr = addr; + break; + } + + addr = nextRecordAddress(addr, &rec); + } + } + + return 0; +} + +int eeep_terminate(const struct nvmDevice* dev) +{ + (void) dev; + + return 0; +} + +const struct nvmOps eeep_ops = +{ + .read = eeep_read, + .write = eeep_write, + .erase = NULL, + .sync = NULL +}; + +const struct nvmInfo eeep_info = +{ + .write_size = 1, + .erase_size = 1, + .erase_cycles = INT_MAX, + .device_info = NVM_EEEPROM | NVM_WRITE +}; diff --git a/platform/drivers/NVM/eeep.h b/platform/drivers/NVM/eeep.h new file mode 100644 index 00000000..78318d0f --- /dev/null +++ b/platform/drivers/NVM/eeep.h @@ -0,0 +1,85 @@ +/*************************************************************************** + * Copyright (C) 2024 by Federico Amedeo Izzo IU2NUO, * + * Niccolò Izzo IU2KIN * + * Frederik Saraci IU2NRO * + * Silvano Seva IU2KWO * + * Morgan Diepart ON4MOD * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef EEEP_H +#define EEEP_H + +#include + +/** + * Driver for Emulated EEPROM, providing a means to store recurrent data without + * stressing too much the same sector of a NOR or NAND flash memory. + */ + +/** + * Device driver and information block for EEEPROM memory. + */ +extern const struct nvmOps eeep_ops; +extern const struct nvmInfo eeep_info; + +/** + * Driver private data. + */ +struct eeepData +{ + const struct nvmDevice *nvm; ///< Underlying NVM device + const struct nvmPartition *part; ///< Memory partition used for EEPROM emulation + uint32_t readAddr; ///< Physical start address for EEEPROM reads + uint32_t writeAddr; ///< Physical start address for EEEPROM writes +}; + +/** + * Instantiate a EEEPROM NVM device. + * + * @param name: device name. + * @param path: full path of the file used for data storage. + * @param size: size of the storage file, in bytes. + */ +#define EEEP_DEVICE_DEFINE(name) \ +static struct eeepData eeepData_##name; \ +struct nvmDevice name = \ +{ \ + .priv = &eeepData_##name, \ + .ops = &eeep_ops, \ + .info = &eeep_info, \ + .size = 65536 \ +}; + +/** + * Initialize an EEEP driver instance. + * + * @param dev: pointer to device descriptor. + * @param nvm: index of the underlying NVM device used for data storage. + * @param part: NVM partition used for data storage. + * @return zero on success, a negative error code otherwise. + */ +int eeep_init(const struct nvmDevice *dev, const uint32_t nvm, + const uint32_t part); + +/** + * Shut down an EEEP driver instance. + * + * @param dev: pointer to device descriptor. + * @return zero on success, a negative error code otherwise. + */ +int eeep_terminate(const struct nvmDevice *dev); + +#endif /* EEEP_H */