Use reference sentinel to efficiently reset dirty flags on rollover

This commit is contained in:
2024-09-21 20:54:25 +02:00
parent 70781c6628
commit cb8c738ca8
3 changed files with 120 additions and 106 deletions

View File

@@ -2,140 +2,163 @@
#include <util/atomic.h>
#include <string.h>
#include <stdio.h>
#define MEM_SIZE 1024
#define MEM_START 0x00
#define MEM_BLOCK_SIZE ((int) sizeof(mem_block_t))
#define MEM_MAX_BLOCKS (MEM_SIZE / MEM_BLOCK_SIZE)
#define MEM_IS_DIRTY 0x80 // Dirty block marker
static int GetUsedBlock(void);
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);
// TODO: Update and clean up documentation.
// TODO: Invert sentinel value when memory is full
// and always set first block as "used". This allows
// us to reset the flags without erasing any memory.
// 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.
void MEM_Init(void)
{
byte flag;
// /--- used
// 80 50 DD EE FF 60 AA BB CC
// 00 00 00 00 00 00 00 00 00
// \--- free
flag = ReadRaw(0);
// We divide the memory space into blocks and
// provide each block with a "dirty" flag to
// indicate if the block is in use.
// /--- used
// 80 50 DD EE FF 60 AA BB CC
// 00 00 00 00 00 00 00 00 00
// \--- free
// We initialize the memory first if we don't find
// a valid flag at the first sentinel position. This
// value can be either a 0x00 or 0x80. Empty memory
// on Atmel chips is set to 0xFF so we must not use
// that as a flag.
if (flag != 0x00 && flag != MEM_IS_DIRTY) {
Info("Initializing fresh memory...");
MEM_Free(); // Reset all dirty flags
}
}
// 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 n;
int head;
byte flag;
// As we add entries it's trivial to keep track of
// the last one: We check the flags for each block in
// sequence until we find a "free" one and go back
// one step.
// 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 start writing from the first
// entry and only reset the flags (for now).
// free entries, we change the sentinel bit value
// for the reference block and all other blocks are
// considered free again without erasing any flags.
// EEPROM cells are written by the hardware in a two
// step operation: First, the cell is set to all ones,
// then the bits to be written (effectively only those
// that are 0) are actually written.
// /--- used
// 00 50 DD EE FF 60 AA BB CC
// 80 20 FF CC 25 00 EE FF CC
// \--- free
n = GetFreeBlock();
if (n < 0) { // Full?
MEM_Free();
n = 0;
if (head == 0) {
// Clear all "used" flags
flag = flag ? 0x00 : 0x80;
}
in->flag = MEM_IS_DIRTY;
WriteBlock(n, in);
in->flag = flag;
WriteBlock(head, in);
}
int MEM_Read(mem_block_t *out)
{
int n;
int head;
// To get the current block we search for the next
// free one and go back by one. If we reach the end
// we return a negative value to indicate empty
// memory.
head = GetUsedBlock();
n = GetUsedBlock();
if (n < 0) { // Empty?
if (head < 0) { // Empty?
return -1;
}
ReadBlock(n, out);
ReadBlock(head, out);
return 0;
}
void MEM_Free(void)
{
int n;
int head;
// For now we clear all dirty block markers but it
// would be better to invert the sentinel value when
// we've reached the end (and set the first block to
// always be "used"). This means we don't have to
// erase any flags below.
for (head = 0; head < MEM_MAX_BLOCKS; head++) {
// Set sentinel values to unitialized
WriteRaw(head * MEM_BLOCK_SIZE, 0xFF);
for (n = 0; n < MEM_MAX_BLOCKS; n++) {
WriteRaw(n * MEM_BLOCK_SIZE, 0x0);
if ((n % 64) == 0) WDT_Reset();
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 bl;
int next, last;
byte ref;
bl = GetFreeBlock();
if ((ref = GetSentinel()) == 0xFF) {
return -1; // Fresh memory
}
if (bl == 0) {
return -1;
} else if (bl < 0) {
return MEM_MAX_BLOCKS;
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 bl - 1;
return -1; // None
}
}
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));
return next - 1;
}
static void WriteBlock(int n, mem_block_t *in)
@@ -151,24 +174,17 @@ static void WriteBlock(int n, mem_block_t *in)
}
}
static int GetFreeBlock(void)
static void ReadBlock(int n, mem_block_t *out)
{
int n;
byte flag;
bool is_free;
int addr;
byte block[MEM_BLOCK_SIZE];
for (n = 0; n < MEM_MAX_BLOCKS; n++) {
// Only read first member of struct
flag = ReadRaw(n * MEM_BLOCK_SIZE);
if (flag != MEM_IS_DIRTY) {
is_free = true;
break;
}
addr = n * MEM_BLOCK_SIZE;
for (int i = 0; i < MEM_BLOCK_SIZE; i++) {
block[i] = ReadRaw(addr + i);
}
// Is there any space left?
return (is_free) ? n : -1;
memcpy(out, block, sizeof(mem_block_t));
}
static void WriteRaw(int addr, byte data)
@@ -235,7 +251,6 @@ static byte ReadRaw(int addr)
// 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) {

View File

@@ -12,9 +12,9 @@ struct mem_block_s {
float temp, dewp;
} __attribute__((packed));
void MEM_Init(void);
void MEM_Write(mem_block_t *in);
int MEM_Read(mem_block_t *out);
void MEM_Free(void);
void MEM_Dump(void);
#endif // MAD_CORE_COMMON_MEMORY_H

View File

@@ -74,11 +74,10 @@ static int Init(void)
// be some sanity checking before these values are
// used.
MEM_Init();
// mem.temp = 30.50f;
// mem.dewp = 15.25f;
// mem.temp = 20.50f;
// mem.dewp = 10.25f;
// MEM_Write(&mem);
// MEM_Dump();
// MEM_Free();
if (MEM_Read(&mem) == 0) {