Files
drybox-core/src/common/memory.c
2026-02-02 19:31:28 +00:00

277 lines
5.9 KiB
C

#include "common.h"
#include <util/atomic.h>
#include <string.h>
#include <stdio.h>
#define MEM_SIZE 4096
#define MEM_START 0x00
#define MEM_BLOCK_SIZE ((int) sizeof(mem_block_t))
#define MEM_MAX_BLOCKS (MEM_SIZE / MEM_BLOCK_SIZE)
static byte GetSentinel(void);
static int GetFreeBlock(void);
static int GetUsedBlock(void);
static void WriteBlock(int n, mem_block_t *in);
static void ReadBlock(int n, mem_block_t *out);
static void WriteRaw(int addr, byte data);
static byte ReadRaw(int addr);
// The memory manager divides the memory space into blocks
// with each block starting with a sentinel byte. This byte
// indicates if the block was already used in this cycle.
// /--- used
// 80 50 DD EE FF 60 AA BB CC
// 00 00 00 00 00 00 00 00 00
// \--- free
// The first block is always considered used and therefore
// its sentinel value can act as a reference for the "used"
// flag. This means a block is considered free, only if its
// sentinel value differs from the first block.
void MEM_Write(mem_block_t *in)
{
int head;
byte flag;
// As we add entries we check the flags for each
// block in sequence until we find a free one and
// go back one step.
head = GetFreeBlock();
flag = GetSentinel();
// When we have to write new data but there are no
// free entries, we change the sentinel bit value
// for the reference block and all other blocks are
// considered free again without erasing any flags.
// /--- used
// 00 50 DD EE FF 60 AA BB CC
// 80 20 FF CC 25 00 EE FF CC
// \--- free
if (head == 0) {
// Clear all "used" flags
flag = flag ? 0x00 : 0x80;
}
in->flag = flag;
WriteBlock(head, in);
}
bool MEM_Read(mem_block_t *out)
{
int head;
head = GetUsedBlock();
if (head < 0) { // Empty?
return false;
}
ReadBlock(head, out);
return true;
}
void MEM_Free(void)
{
int head;
for (head = 0; head < MEM_MAX_BLOCKS; head++) {
// Set sentinel values to unitialized
WriteRaw(head * MEM_BLOCK_SIZE, 0xFF);
if ((head % 64) == 0) {
WDT_Reset();
}
}
}
void MEM_Dump(void)
{
int n, m;
char buf[MEM_BLOCK_SIZE*3+1];
char *ptr = buf;
// Used for debugging purposes
for (n = 0, m = 0; n < MEM_SIZE; n++) {
ptr += sprintf(ptr, "%02X ", ReadRaw(n));
if (++m == MEM_BLOCK_SIZE || n == (MEM_SIZE - 1)) {
Print("%s\r\n", buf);
WDT_Reset();
ptr = buf;
m = 0;
}
}
}
static byte GetSentinel(void)
{
return ReadRaw(MEM_START);
}
static int GetFreeBlock(void)
{
int head;
byte ref, flag;
if ((ref = GetSentinel()) == 0xFF) {
return 0; // Fresh memory
}
for (head = 0; head < MEM_MAX_BLOCKS; head++) {
flag = ReadRaw(head * MEM_BLOCK_SIZE);
if (flag != ref) {
break;
}
}
if (head == MEM_MAX_BLOCKS) {
return 0; // Rollover
}
return head;
}
static int GetUsedBlock(void)
{
int next, last;
byte ref;
if ((ref = GetSentinel()) == 0xFF) {
return -1; // Fresh memory
}
if ((next = GetFreeBlock()) == 0) {
// Important edge case: Rollover or empty?
last = (MEM_MAX_BLOCKS - 1) * MEM_BLOCK_SIZE;
if (ReadRaw(last) == GetSentinel()) {
return MEM_MAX_BLOCKS - 1;
} else {
return -1; // None
}
}
return next - 1;
}
static void WriteBlock(int n, mem_block_t *in)
{
int addr;
byte *raw;
raw = (byte *) in;
addr = n * MEM_BLOCK_SIZE;
for (int i = 0; i < MEM_BLOCK_SIZE; i++) {
WriteRaw(addr + i, raw[i]);
}
}
static void ReadBlock(int n, mem_block_t *out)
{
int addr;
byte block[MEM_BLOCK_SIZE];
addr = n * MEM_BLOCK_SIZE;
for (int i = 0; i < MEM_BLOCK_SIZE; i++) {
block[i] = ReadRaw(addr + i);
}
memcpy(out, block, sizeof(mem_block_t));
}
static void WriteRaw(int addr, byte data)
{
// The EEMPE bit determines whether setting EEPE to
// one causes the EEPROM to be written. When EEMPE
// is set, setting EEPE within four clock cycles
// will write data to the EEPROM at the selected
// address.
// If EEMPE is zero, setting EEPE will have no
// effect. When EEMPE has been written to one by
// software, hardware clears the bit to zero after
// four clock cycles.
// Caution: An interrupt between the last two steps
// will make the write cycle fail, since the EEPROM
// Master Write Enable will time-out.
// If an interrupt routine accessing the EEPROM is
// interrupting another EEPROM Access, the EEAR or
// EEDR register will be modified, causing the
// interrupted EEPROM Access to fail.
// It is recommended to have the Global Interrupt
// Flag cleared during all the steps to avoid these
// problems.
// When the write access time has elapsed, the EEPE
// bit is cleared by hardware. The user software can
// poll this bit and wait for a zero before writing
// the next byte. When EEPE has been set, the CPU is
// halted for two cycles before the next instruction
// is executed.
// No interrupts during EEPROM write
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
// Wait until ready
while (EECR & BIT(EEPE));
// The EEPROM Address Registers - EEARH and
// EEARL specify the EEPROM address in the
// 512/1K/2K/4Kbytes EEPROM space. The EEPROM
// data bytes are addressed linearly between 0
// and 511/1023/2047/4096. The initial value
// of EEAR is undefined. A proper value must be
// written before the EEPROM may be accessed.
EEAR = addr;
EEDR = data;
// Write to address
EECR |= BIT(EEMPE);
EECR |= BIT(EEPE);
}
}
static byte ReadRaw(int addr)
{
byte data;
// The EEPROM Read Enable Signal EERE is the read
// strobe to the EEPROM. When the correct address
// is set up in the EEAR Register, the EERE bit
// must be written to a logic one to trigger the
// EEPROM read.
// The EEPROM read access takes one instruction,
// and the requested data is available immediately.
// When the EEPROM is read, the CPU is halted for
// four cycles before the next instruction is
// executed.
// No interrupts during EEPROM read
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
// Wait until ready
while (EECR & BIT(EEPE));
EEAR = addr;
// Read from address
EECR |= BIT(EERE);
data = EEDR;
}
return data;
}