OpenRTX/platform/drivers/NVM/eeep.c

379 wiersze
11 KiB
C

/***************************************************************************
* Copyright (C) 2024 - 2025 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 <http://www.gnu.org/licenses/> *
***************************************************************************/
#include <nvmem_access.h>
#include <stdint.h>
#include <limits.h>
#include <errno.h>
#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
};