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 */