Use reference sentinel to efficiently reset dirty flags on rollover
This commit is contained in:
@@ -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) {
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user