|
|
@ -26,35 +26,967 @@ |
|
|
|
|
|
|
|
#if ENABLED(FLASH_EEPROM_EMULATION) |
|
|
|
|
|
|
|
#include "../../inc/MarlinConfig.h" |
|
|
|
#ifndef E2END |
|
|
|
#define E2END 0xFFF // Default to Flash emulated EEPROM size (eeprom_emul.cpp)
|
|
|
|
#endif |
|
|
|
|
|
|
|
#include "../shared/eeprom_if.h" |
|
|
|
#include "../shared/eeprom_api.h" |
|
|
|
/* EEPROM emulation over flash with reduced wear
|
|
|
|
* |
|
|
|
* We will use 2 contiguous groups of pages as main and alternate. |
|
|
|
* We want an structure that allows to read as fast as possible, |
|
|
|
* without the need of scanning the whole FLASH memory. |
|
|
|
* |
|
|
|
* FLASH bits default erased state is 1, and can be set to 0 |
|
|
|
* on a per bit basis. To reset them to 1, a full page erase |
|
|
|
* is needed. |
|
|
|
* |
|
|
|
* Values are stored as differences that should be applied to a |
|
|
|
* completely erased EEPROM (filled with 0xFFs). We just encode |
|
|
|
* the starting address of the values to change, the length of |
|
|
|
* the block of new values, and the values themselves. All diffs |
|
|
|
* are accumulated into a RAM buffer, compacted into the least |
|
|
|
* amount of non overlapping diffs possible and sorted by starting |
|
|
|
* address before being saved into the next available page of FLASH |
|
|
|
* of the current group. |
|
|
|
* Once the current group is completely full, we compact it and save |
|
|
|
* it into the other group, then erase the current group and switch |
|
|
|
* to that new group and set it as current. |
|
|
|
* |
|
|
|
* The FLASH endurance is about 1/10 ... 1/100 of an EEPROM |
|
|
|
* endurance, but EEPROM endurance is specified per byte, not |
|
|
|
* per page. We can't emulate EE endurance with FLASH for all |
|
|
|
* bytes, but we can emulate endurance for a given percent of |
|
|
|
* bytes. |
|
|
|
* |
|
|
|
*/ |
|
|
|
|
|
|
|
//#define EE_EMU_DEBUG
|
|
|
|
|
|
|
|
#define EEPROMSize 4096 |
|
|
|
#define PagesPerGroup 128 |
|
|
|
#define GroupCount 2 |
|
|
|
#define PageSize 256u |
|
|
|
|
|
|
|
/* Flash storage */ |
|
|
|
typedef struct FLASH_SECTOR { |
|
|
|
uint8_t page[PageSize]; |
|
|
|
} FLASH_SECTOR_T; |
|
|
|
|
|
|
|
#define PAGE_FILL \ |
|
|
|
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ |
|
|
|
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ |
|
|
|
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ |
|
|
|
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ |
|
|
|
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ |
|
|
|
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ |
|
|
|
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ |
|
|
|
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ |
|
|
|
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ |
|
|
|
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ |
|
|
|
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ |
|
|
|
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ |
|
|
|
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ |
|
|
|
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ |
|
|
|
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ |
|
|
|
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF |
|
|
|
|
|
|
|
#define FLASH_INIT_FILL \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ |
|
|
|
PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL |
|
|
|
|
|
|
|
/* This is the FLASH area used to emulate a 2Kbyte EEPROM -- We need this buffer aligned
|
|
|
|
to a 256 byte boundary. */ |
|
|
|
static const uint8_t flashStorage[PagesPerGroup * GroupCount * PageSize] __attribute__ ((aligned (PageSize))) = { FLASH_INIT_FILL }; |
|
|
|
|
|
|
|
/* Get the address of an specific page */ |
|
|
|
static const FLASH_SECTOR_T* getFlashStorage(int page) { |
|
|
|
return (const FLASH_SECTOR_T*)&flashStorage[page*PageSize]; |
|
|
|
} |
|
|
|
|
|
|
|
static uint8_t buffer[256] = {0}, // The RAM buffer to accumulate writes
|
|
|
|
curPage = 0, // Current FLASH page inside the group
|
|
|
|
curGroup = 0xFF; // Current FLASH group
|
|
|
|
|
|
|
|
#define DEBUG_OUT ENABLED(EE_EMU_DEBUG) |
|
|
|
#include "../../core/debug_out.h" |
|
|
|
|
|
|
|
static void ee_Dump(const int page, const void* data) { |
|
|
|
|
|
|
|
#ifdef EE_EMU_DEBUG |
|
|
|
|
|
|
|
const uint8_t* c = (const uint8_t*) data; |
|
|
|
char buffer[80]; |
|
|
|
|
|
|
|
sprintf_P(buffer, PSTR("Page: %d (0x%04x)\n"), page, page); |
|
|
|
DEBUG_ECHO(buffer); |
|
|
|
|
|
|
|
char* p = &buffer[0]; |
|
|
|
for (int i = 0; i< PageSize; ++i) { |
|
|
|
if ((i & 0xF) == 0) p += sprintf_P(p, PSTR("%04x] "), i); |
|
|
|
|
|
|
|
#if !defined(E2END) |
|
|
|
#define E2END 0xFFF // Default to Flash emulated EEPROM size (EepromEmulation_Due.cpp)
|
|
|
|
p += sprintf_P(p, PSTR(" %02x"), c[i]); |
|
|
|
if ((i & 0xF) == 0xF) { |
|
|
|
*p++ = '\n'; |
|
|
|
*p = 0; |
|
|
|
DEBUG_ECHO(buffer); |
|
|
|
p = &buffer[0]; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
#else |
|
|
|
UNUSED(page); |
|
|
|
UNUSED(data); |
|
|
|
#endif |
|
|
|
} |
|
|
|
|
|
|
|
/* Flash Writing Protection Key */ |
|
|
|
#define FWP_KEY 0x5Au |
|
|
|
|
|
|
|
#if SAM4S_SERIES |
|
|
|
#define EEFC_FCR_FCMD(value) \ |
|
|
|
((EEFC_FCR_FCMD_Msk & ((value) << EEFC_FCR_FCMD_Pos))) |
|
|
|
#define EEFC_ERROR_FLAGS (EEFC_FSR_FLOCKE | EEFC_FSR_FCMDE | EEFC_FSR_FLERR) |
|
|
|
#else |
|
|
|
#define EEFC_ERROR_FLAGS (EEFC_FSR_FLOCKE | EEFC_FSR_FCMDE) |
|
|
|
#endif |
|
|
|
|
|
|
|
extern void eeprom_flush(); |
|
|
|
/**
|
|
|
|
* Writes the contents of the specified page (no previous erase) |
|
|
|
* @param page (page #) |
|
|
|
* @param data (pointer to the data buffer) |
|
|
|
*/ |
|
|
|
__attribute__ ((long_call, section (".ramfunc"))) |
|
|
|
static bool ee_PageWrite(uint16_t page, const void* data) { |
|
|
|
|
|
|
|
uint16_t i; |
|
|
|
uint32_t addrflash = uint32_t(getFlashStorage(page)); |
|
|
|
|
|
|
|
size_t PersistentStore::capacity() { return E2END + 1; } |
|
|
|
bool PersistentStore::access_start() { return true; } |
|
|
|
// Read the flash contents
|
|
|
|
uint32_t pageContents[PageSize>>2]; |
|
|
|
memcpy(pageContents, (void*)addrflash, PageSize); |
|
|
|
|
|
|
|
// We ONLY want to toggle bits that have changed, and that have changed to 0.
|
|
|
|
// SAM3X8E tends to destroy contiguous bits if reprogrammed without erasing, so
|
|
|
|
// we try by all means to avoid this. That is why it says: "The Partial
|
|
|
|
// Programming mode works only with 128-bit (or higher) boundaries. It cannot
|
|
|
|
// be used with boundaries lower than 128 bits (8, 16 or 32-bit for example)."
|
|
|
|
// All bits that did not change, set them to 1.
|
|
|
|
for (i = 0; i <PageSize >> 2; i++) |
|
|
|
pageContents[i] = (((uint32_t*)data)[i]) | (~(pageContents[i] ^ ((uint32_t*)data)[i])); |
|
|
|
|
|
|
|
DEBUG_ECHO_START(); |
|
|
|
DEBUG_ECHOLNPAIR("EEPROM PageWrite ", page); |
|
|
|
DEBUG_ECHOLNPAIR(" in FLASH address ", (uint32_t)addrflash); |
|
|
|
DEBUG_ECHOLNPAIR(" base address ", (uint32_t)getFlashStorage(0)); |
|
|
|
DEBUG_FLUSH(); |
|
|
|
|
|
|
|
// Get the page relative to the start of the EFC controller, and the EFC controller to use
|
|
|
|
Efc *efc; |
|
|
|
uint16_t fpage; |
|
|
|
if (addrflash >= IFLASH1_ADDR) { |
|
|
|
efc = EFC1; |
|
|
|
fpage = (addrflash - IFLASH1_ADDR) / IFLASH1_PAGE_SIZE; |
|
|
|
} |
|
|
|
else { |
|
|
|
efc = EFC0; |
|
|
|
fpage = (addrflash - IFLASH0_ADDR) / IFLASH0_PAGE_SIZE; |
|
|
|
} |
|
|
|
|
|
|
|
// Get the page that must be unlocked, then locked
|
|
|
|
uint16_t lpage = fpage & (~((IFLASH0_LOCK_REGION_SIZE / IFLASH0_PAGE_SIZE) - 1)); |
|
|
|
|
|
|
|
// Disable all interrupts
|
|
|
|
__disable_irq(); |
|
|
|
|
|
|
|
// Get the FLASH wait states
|
|
|
|
uint32_t orgWS = (efc->EEFC_FMR & EEFC_FMR_FWS_Msk) >> EEFC_FMR_FWS_Pos; |
|
|
|
|
|
|
|
// Set wait states to 6 (SAM errata)
|
|
|
|
efc->EEFC_FMR = (efc->EEFC_FMR & (~EEFC_FMR_FWS_Msk)) | EEFC_FMR_FWS(6); |
|
|
|
|
|
|
|
// Unlock the flash page
|
|
|
|
uint32_t status; |
|
|
|
efc->EEFC_FCR = EEFC_FCR_FKEY(FWP_KEY) | EEFC_FCR_FARG(lpage) | EEFC_FCR_FCMD(EFC_FCMD_CLB); |
|
|
|
while (((status = efc->EEFC_FSR) & EEFC_FSR_FRDY) != EEFC_FSR_FRDY) { |
|
|
|
// force compiler to not optimize this -- NOPs don't work!
|
|
|
|
__asm__ __volatile__(""); |
|
|
|
}; |
|
|
|
|
|
|
|
if ((status & EEFC_ERROR_FLAGS) != 0) { |
|
|
|
|
|
|
|
// Restore original wait states
|
|
|
|
efc->EEFC_FMR = (efc->EEFC_FMR & (~EEFC_FMR_FWS_Msk)) | EEFC_FMR_FWS(orgWS); |
|
|
|
|
|
|
|
// Reenable interrupts
|
|
|
|
__enable_irq(); |
|
|
|
|
|
|
|
DEBUG_ECHO_START(); |
|
|
|
DEBUG_ECHOLNPAIR("EEPROM Unlock failure for page ", page); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
// Write page and lock: Writing 8-bit and 16-bit data is not allowed and may lead to unpredictable data corruption.
|
|
|
|
const uint32_t * aligned_src = (const uint32_t *) &pageContents[0]; /*data;*/ |
|
|
|
uint32_t * p_aligned_dest = (uint32_t *) addrflash; |
|
|
|
for (i = 0; i < (IFLASH0_PAGE_SIZE / sizeof(uint32_t)); ++i) { |
|
|
|
*p_aligned_dest++ = *aligned_src++; |
|
|
|
} |
|
|
|
efc->EEFC_FCR = EEFC_FCR_FKEY(FWP_KEY) | EEFC_FCR_FARG(fpage) | EEFC_FCR_FCMD(EFC_FCMD_WPL); |
|
|
|
while (((status = efc->EEFC_FSR) & EEFC_FSR_FRDY) != EEFC_FSR_FRDY) { |
|
|
|
// force compiler to not optimize this -- NOPs don't work!
|
|
|
|
__asm__ __volatile__(""); |
|
|
|
}; |
|
|
|
|
|
|
|
if ((status & EEFC_ERROR_FLAGS) != 0) { |
|
|
|
|
|
|
|
// Restore original wait states
|
|
|
|
efc->EEFC_FMR = (efc->EEFC_FMR & (~EEFC_FMR_FWS_Msk)) | EEFC_FMR_FWS(orgWS); |
|
|
|
|
|
|
|
// Reenable interrupts
|
|
|
|
__enable_irq(); |
|
|
|
|
|
|
|
DEBUG_ECHO_START(); |
|
|
|
DEBUG_ECHOLNPAIR("EEPROM Write failure for page ", page); |
|
|
|
|
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
// Restore original wait states
|
|
|
|
efc->EEFC_FMR = (efc->EEFC_FMR & (~EEFC_FMR_FWS_Msk)) | EEFC_FMR_FWS(orgWS); |
|
|
|
|
|
|
|
// Reenable interrupts
|
|
|
|
__enable_irq(); |
|
|
|
|
|
|
|
// Compare contents
|
|
|
|
if (memcmp(getFlashStorage(page),data,PageSize)) { |
|
|
|
|
|
|
|
#ifdef EE_EMU_DEBUG |
|
|
|
DEBUG_ECHO_START(); |
|
|
|
DEBUG_ECHOLNPAIR("EEPROM Verify Write failure for page ", page); |
|
|
|
|
|
|
|
ee_Dump( page, (uint32_t *)addrflash); |
|
|
|
ee_Dump(-page, data); |
|
|
|
|
|
|
|
// Calculate count of changed bits
|
|
|
|
uint32_t* p1 = (uint32_t*)addrflash; |
|
|
|
uint32_t* p2 = (uint32_t*)data; |
|
|
|
int count = 0; |
|
|
|
for (i =0; i<PageSize >> 2; i++) { |
|
|
|
if (p1[i] != p2[i]) { |
|
|
|
uint32_t delta = p1[i] ^ p2[i]; |
|
|
|
while (delta) { |
|
|
|
if ((delta&1) != 0) |
|
|
|
count++; |
|
|
|
delta >>= 1; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
DEBUG_ECHOLNPAIR("--> Differing bits: ", count); |
|
|
|
#endif |
|
|
|
|
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
/**
|
|
|
|
* Erases the contents of the specified page |
|
|
|
* @param page (page #) |
|
|
|
*/ |
|
|
|
__attribute__ ((long_call, section (".ramfunc"))) |
|
|
|
static bool ee_PageErase(uint16_t page) { |
|
|
|
|
|
|
|
uint16_t i; |
|
|
|
uint32_t addrflash = uint32_t(getFlashStorage(page)); |
|
|
|
|
|
|
|
DEBUG_ECHO_START(); |
|
|
|
DEBUG_ECHOLNPAIR("EEPROM PageErase ", page); |
|
|
|
DEBUG_ECHOLNPAIR(" in FLASH address ", (uint32_t)addrflash); |
|
|
|
DEBUG_ECHOLNPAIR(" base address ", (uint32_t)getFlashStorage(0)); |
|
|
|
DEBUG_FLUSH(); |
|
|
|
|
|
|
|
// Get the page relative to the start of the EFC controller, and the EFC controller to use
|
|
|
|
Efc *efc; |
|
|
|
uint16_t fpage; |
|
|
|
if (addrflash >= IFLASH1_ADDR) { |
|
|
|
efc = EFC1; |
|
|
|
fpage = (addrflash - IFLASH1_ADDR) / IFLASH1_PAGE_SIZE; |
|
|
|
} |
|
|
|
else { |
|
|
|
efc = EFC0; |
|
|
|
fpage = (addrflash - IFLASH0_ADDR) / IFLASH0_PAGE_SIZE; |
|
|
|
} |
|
|
|
|
|
|
|
// Get the page that must be unlocked, then locked
|
|
|
|
uint16_t lpage = fpage & (~((IFLASH0_LOCK_REGION_SIZE / IFLASH0_PAGE_SIZE) - 1)); |
|
|
|
|
|
|
|
// Disable all interrupts
|
|
|
|
__disable_irq(); |
|
|
|
|
|
|
|
// Get the FLASH wait states
|
|
|
|
uint32_t orgWS = (efc->EEFC_FMR & EEFC_FMR_FWS_Msk) >> EEFC_FMR_FWS_Pos; |
|
|
|
|
|
|
|
// Set wait states to 6 (SAM errata)
|
|
|
|
efc->EEFC_FMR = (efc->EEFC_FMR & (~EEFC_FMR_FWS_Msk)) | EEFC_FMR_FWS(6); |
|
|
|
|
|
|
|
// Unlock the flash page
|
|
|
|
uint32_t status; |
|
|
|
efc->EEFC_FCR = EEFC_FCR_FKEY(FWP_KEY) | EEFC_FCR_FARG(lpage) | EEFC_FCR_FCMD(EFC_FCMD_CLB); |
|
|
|
while (((status = efc->EEFC_FSR) & EEFC_FSR_FRDY) != EEFC_FSR_FRDY) { |
|
|
|
// force compiler to not optimize this -- NOPs don't work!
|
|
|
|
__asm__ __volatile__(""); |
|
|
|
}; |
|
|
|
if ((status & EEFC_ERROR_FLAGS) != 0) { |
|
|
|
|
|
|
|
// Restore original wait states
|
|
|
|
efc->EEFC_FMR = (efc->EEFC_FMR & (~EEFC_FMR_FWS_Msk)) | EEFC_FMR_FWS(orgWS); |
|
|
|
|
|
|
|
// Reenable interrupts
|
|
|
|
__enable_irq(); |
|
|
|
|
|
|
|
DEBUG_ECHO_START(); |
|
|
|
DEBUG_ECHOLNPAIR("EEPROM Unlock failure for page ",page); |
|
|
|
|
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
// Erase Write page and lock: Writing 8-bit and 16-bit data is not allowed and may lead to unpredictable data corruption.
|
|
|
|
uint32_t * p_aligned_dest = (uint32_t *) addrflash; |
|
|
|
for (i = 0; i < (IFLASH0_PAGE_SIZE / sizeof(uint32_t)); ++i) { |
|
|
|
*p_aligned_dest++ = 0xFFFFFFFF; |
|
|
|
} |
|
|
|
efc->EEFC_FCR = EEFC_FCR_FKEY(FWP_KEY) | EEFC_FCR_FARG(fpage) | EEFC_FCR_FCMD(EFC_FCMD_EWPL); |
|
|
|
while (((status = efc->EEFC_FSR) & EEFC_FSR_FRDY) != EEFC_FSR_FRDY) { |
|
|
|
// force compiler to not optimize this -- NOPs don't work!
|
|
|
|
__asm__ __volatile__(""); |
|
|
|
}; |
|
|
|
if ((status & EEFC_ERROR_FLAGS) != 0) { |
|
|
|
|
|
|
|
// Restore original wait states
|
|
|
|
efc->EEFC_FMR = (efc->EEFC_FMR & (~EEFC_FMR_FWS_Msk)) | EEFC_FMR_FWS(orgWS); |
|
|
|
|
|
|
|
// Reenable interrupts
|
|
|
|
__enable_irq(); |
|
|
|
|
|
|
|
DEBUG_ECHO_START(); |
|
|
|
DEBUG_ECHOLNPAIR("EEPROM Erase failure for page ",page); |
|
|
|
|
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
// Restore original wait states
|
|
|
|
efc->EEFC_FMR = (efc->EEFC_FMR & (~EEFC_FMR_FWS_Msk)) | EEFC_FMR_FWS(orgWS); |
|
|
|
|
|
|
|
// Reenable interrupts
|
|
|
|
__enable_irq(); |
|
|
|
|
|
|
|
// Check erase
|
|
|
|
uint32_t * aligned_src = (uint32_t *) addrflash; |
|
|
|
for (i = 0; i < PageSize >> 2; i++) { |
|
|
|
if (*aligned_src++ != 0xFFFFFFFF) { |
|
|
|
DEBUG_ECHO_START(); |
|
|
|
DEBUG_ECHOLNPAIR("EEPROM Verify Erase failure for page ",page); |
|
|
|
ee_Dump(page, (uint32_t *)addrflash); |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
bool PersistentStore::access_finish() { |
|
|
|
eeprom_flush(); |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
static uint8_t ee_Read(uint32_t address, bool excludeRAMBuffer=false) { |
|
|
|
|
|
|
|
uint32_t baddr; |
|
|
|
uint32_t blen; |
|
|
|
|
|
|
|
// If we were requested an address outside of the emulated range, fail now
|
|
|
|
if (address >= EEPROMSize) |
|
|
|
return false; |
|
|
|
|
|
|
|
// Check that the value is not contained in the RAM buffer
|
|
|
|
if (!excludeRAMBuffer) { |
|
|
|
uint16_t i = 0; |
|
|
|
while (i <= (PageSize - 4)) { /* (PageSize - 4) because otherwise, there is not enough room for data and headers */ |
|
|
|
|
|
|
|
// Get the address of the block
|
|
|
|
baddr = buffer[i] | (buffer[i + 1] << 8); |
|
|
|
|
|
|
|
// Get the length of the block
|
|
|
|
blen = buffer[i + 2]; |
|
|
|
|
|
|
|
// If we reach the end of the list, break loop
|
|
|
|
if (blen == 0xFF) |
|
|
|
break; |
|
|
|
|
|
|
|
// Check if data is contained in this block
|
|
|
|
if (address >= baddr && |
|
|
|
address < (baddr + blen)) { |
|
|
|
|
|
|
|
// Yes, it is contained. Return it!
|
|
|
|
return buffer[i + 3 + address - baddr]; |
|
|
|
} |
|
|
|
|
|
|
|
// As blocks are always sorted, if the starting address of this block is higher
|
|
|
|
// than the address we are looking for, break loop now - We wont find the value
|
|
|
|
// associated to the address
|
|
|
|
if (baddr > address) |
|
|
|
break; |
|
|
|
|
|
|
|
// Jump to the next block
|
|
|
|
i += 3 + blen; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// It is NOT on the RAM buffer. It could be stored in FLASH. We are
|
|
|
|
// ensured on a given FLASH page, address contents are never repeated
|
|
|
|
// but on different pages, there is no such warranty, so we must go
|
|
|
|
// backwards from the last written FLASH page to the first one.
|
|
|
|
for (int page = curPage - 1; page >= 0; --page) { |
|
|
|
|
|
|
|
// Get a pointer to the flash page
|
|
|
|
uint8_t* pflash = (uint8_t*)getFlashStorage(page + curGroup * PagesPerGroup); |
|
|
|
|
|
|
|
uint16_t i = 0; |
|
|
|
while (i <= (PageSize - 4)) { /* (PageSize - 4) because otherwise, there is not enough room for data and headers */ |
|
|
|
|
|
|
|
// Get the address of the block
|
|
|
|
baddr = pflash[i] | (pflash[i + 1] << 8); |
|
|
|
|
|
|
|
// Get the length of the block
|
|
|
|
blen = pflash[i + 2]; |
|
|
|
|
|
|
|
// If we reach the end of the list, break loop
|
|
|
|
if (blen == 0xFF) |
|
|
|
break; |
|
|
|
|
|
|
|
// Check if data is contained in this block
|
|
|
|
if (address >= baddr && address < (baddr + blen)) |
|
|
|
return pflash[i + 3 + address - baddr]; // Yes, it is contained. Return it!
|
|
|
|
|
|
|
|
// As blocks are always sorted, if the starting address of this block is higher
|
|
|
|
// than the address we are looking for, break loop now - We wont find the value
|
|
|
|
// associated to the address
|
|
|
|
if (baddr > address) break; |
|
|
|
|
|
|
|
// Jump to the next block
|
|
|
|
i += 3 + blen; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// If reached here, value is not stored, so return its default value
|
|
|
|
return 0xFF; |
|
|
|
} |
|
|
|
|
|
|
|
static uint32_t ee_GetAddrRange(uint32_t address, bool excludeRAMBuffer=false) { |
|
|
|
uint32_t baddr, |
|
|
|
blen, |
|
|
|
nextAddr = 0xFFFF, |
|
|
|
nextRange = 0; |
|
|
|
|
|
|
|
// Check that the value is not contained in the RAM buffer
|
|
|
|
if (!excludeRAMBuffer) { |
|
|
|
uint16_t i = 0; |
|
|
|
while (i <= (PageSize - 4)) { /* (PageSize - 4) because otherwise, there is not enough room for data and headers */ |
|
|
|
|
|
|
|
// Get the address of the block
|
|
|
|
baddr = buffer[i] | (buffer[i + 1] << 8); |
|
|
|
|
|
|
|
// Get the length of the block
|
|
|
|
blen = buffer[i + 2]; |
|
|
|
|
|
|
|
// If we reach the end of the list, break loop
|
|
|
|
if (blen == 0xFF) break; |
|
|
|
|
|
|
|
// Check if address and address + 1 is contained in this block
|
|
|
|
if (address >= baddr && address < (baddr + blen)) |
|
|
|
return address | ((blen - address + baddr) << 16); // Yes, it is contained. Return it!
|
|
|
|
|
|
|
|
// Otherwise, check if we can use it as a limit
|
|
|
|
if (baddr > address && baddr < nextAddr) { |
|
|
|
nextAddr = baddr; |
|
|
|
nextRange = blen; |
|
|
|
} |
|
|
|
|
|
|
|
// As blocks are always sorted, if the starting address of this block is higher
|
|
|
|
// than the address we are looking for, break loop now - We wont find the value
|
|
|
|
// associated to the address
|
|
|
|
if (baddr > address) break; |
|
|
|
|
|
|
|
// Jump to the next block
|
|
|
|
i += 3 + blen; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// It is NOT on the RAM buffer. It could be stored in FLASH. We are
|
|
|
|
// ensured on a given FLASH page, address contents are never repeated
|
|
|
|
// but on different pages, there is no such warranty, so we must go
|
|
|
|
// backwards from the last written FLASH page to the first one.
|
|
|
|
for (int page = curPage - 1; page >= 0; --page) { |
|
|
|
|
|
|
|
// Get a pointer to the flash page
|
|
|
|
uint8_t* pflash = (uint8_t*)getFlashStorage(page + curGroup * PagesPerGroup); |
|
|
|
|
|
|
|
uint16_t i = 0; |
|
|
|
while (i <= (PageSize - 4)) { /* (PageSize - 4) because otherwise, there is not enough room for data and headers */ |
|
|
|
|
|
|
|
// Get the address of the block
|
|
|
|
baddr = pflash[i] | (pflash[i + 1] << 8); |
|
|
|
|
|
|
|
// Get the length of the block
|
|
|
|
blen = pflash[i + 2]; |
|
|
|
|
|
|
|
// If we reach the end of the list, break loop
|
|
|
|
if (blen == 0xFF) break; |
|
|
|
|
|
|
|
// Check if data is contained in this block
|
|
|
|
if (address >= baddr && address < (baddr + blen)) |
|
|
|
return address | ((blen - address + baddr) << 16); // Yes, it is contained. Return it!
|
|
|
|
|
|
|
|
// Otherwise, check if we can use it as a limit
|
|
|
|
if (baddr > address && baddr < nextAddr) { |
|
|
|
nextAddr = baddr; |
|
|
|
nextRange = blen; |
|
|
|
} |
|
|
|
|
|
|
|
// As blocks are always sorted, if the starting address of this block is higher
|
|
|
|
// than the address we are looking for, break loop now - We wont find the value
|
|
|
|
// associated to the address
|
|
|
|
if (baddr > address) break; |
|
|
|
|
|
|
|
// Jump to the next block
|
|
|
|
i += 3 + blen; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// If reached here, we will return the next valid address
|
|
|
|
return nextAddr | (nextRange << 16); |
|
|
|
} |
|
|
|
|
|
|
|
static bool ee_IsPageClean(int page) { |
|
|
|
uint32_t* pflash = (uint32_t*) getFlashStorage(page); |
|
|
|
for (uint16_t i = 0; i < (PageSize >> 2); ++i) |
|
|
|
if (*pflash++ != 0xFFFFFFFF) return false; |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
static bool ee_Flush(uint32_t overrideAddress = 0xFFFFFFFF, uint8_t overrideData=0xFF) { |
|
|
|
|
|
|
|
// Check if RAM buffer has something to be written
|
|
|
|
bool isEmpty = true; |
|
|
|
uint32_t* p = (uint32_t*) &buffer[0]; |
|
|
|
for (uint16_t j = 0; j < (PageSize >> 2); j++) { |
|
|
|
if (*p++ != 0xFFFFFFFF) { |
|
|
|
isEmpty = false; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// If something has to be written, do so!
|
|
|
|
if (!isEmpty) { |
|
|
|
|
|
|
|
// Write the current ram buffer into FLASH
|
|
|
|
ee_PageWrite(curPage + curGroup * PagesPerGroup, buffer); |
|
|
|
|
|
|
|
// Clear the RAM buffer
|
|
|
|
memset(buffer, 0xFF, sizeof(buffer)); |
|
|
|
|
|
|
|
// Increment the page to use the next time
|
|
|
|
++curPage; |
|
|
|
} |
|
|
|
|
|
|
|
// Did we reach the maximum count of available pages per group for storage ?
|
|
|
|
if (curPage < PagesPerGroup) { |
|
|
|
|
|
|
|
// Do we have an override address ?
|
|
|
|
if (overrideAddress < EEPROMSize) { |
|
|
|
|
|
|
|
// Yes, just store the value into the RAM buffer
|
|
|
|
buffer[0] = overrideAddress & 0xFF; |
|
|
|
buffer[0 + 1] = (overrideAddress >> 8) & 0xFF; |
|
|
|
buffer[0 + 2] = 1; |
|
|
|
buffer[0 + 3] = overrideData; |
|
|
|
} |
|
|
|
|
|
|
|
// Done!
|
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
// We have no space left on the current group - We must compact the values
|
|
|
|
uint16_t i = 0; |
|
|
|
|
|
|
|
// Compute the next group to use
|
|
|
|
int curwPage = 0, curwGroup = curGroup + 1; |
|
|
|
if (curwGroup >= GroupCount) curwGroup = 0; |
|
|
|
|
|
|
|
uint32_t rdAddr = 0; |
|
|
|
do { |
|
|
|
|
|
|
|
// Get the next valid range
|
|
|
|
uint32_t addrRange = ee_GetAddrRange(rdAddr, true); |
|
|
|
|
|
|
|
// Make sure not to skip the override address, if specified
|
|
|
|
int rdRange; |
|
|
|
if (overrideAddress < EEPROMSize && |
|
|
|
rdAddr <= overrideAddress && |
|
|
|
(addrRange & 0xFFFF) > overrideAddress) { |
|
|
|
|
|
|
|
rdAddr = overrideAddress; |
|
|
|
rdRange = 1; |
|
|
|
} |
|
|
|
else { |
|
|
|
rdAddr = addrRange & 0xFFFF; |
|
|
|
rdRange = addrRange >> 16; |
|
|
|
} |
|
|
|
|
|
|
|
// If no range, break loop
|
|
|
|
if (rdRange == 0) |
|
|
|
break; |
|
|
|
|
|
|
|
do { |
|
|
|
|
|
|
|
// Get the value
|
|
|
|
uint8_t rdValue = overrideAddress == rdAddr ? overrideData : ee_Read(rdAddr, true); |
|
|
|
|
|
|
|
// Do not bother storing default values
|
|
|
|
if (rdValue != 0xFF) { |
|
|
|
|
|
|
|
// If we have room, add it to the buffer
|
|
|
|
if (buffer[i + 2] == 0xFF) { |
|
|
|
|
|
|
|
// Uninitialized buffer, just add it!
|
|
|
|
buffer[i] = rdAddr & 0xFF; |
|
|
|
buffer[i + 1] = (rdAddr >> 8) & 0xFF; |
|
|
|
buffer[i + 2] = 1; |
|
|
|
buffer[i + 3] = rdValue; |
|
|
|
|
|
|
|
} |
|
|
|
else { |
|
|
|
// Buffer already has contents. Check if we can extend it
|
|
|
|
|
|
|
|
// Get the address of the block
|
|
|
|
uint32_t baddr = buffer[i] | (buffer[i + 1] << 8); |
|
|
|
|
|
|
|
// Get the length of the block
|
|
|
|
uint32_t blen = buffer[i + 2]; |
|
|
|
|
|
|
|
// Can we expand it ?
|
|
|
|
if (rdAddr == (baddr + blen) && |
|
|
|
i < (PageSize - 4) && /* This block has a chance to contain data AND */ |
|
|
|
buffer[i + 2] < (PageSize - i - 3)) {/* There is room for this block to be expanded */ |
|
|
|
|
|
|
|
// Yes, do it
|
|
|
|
++buffer[i + 2]; |
|
|
|
|
|
|
|
// And store the value
|
|
|
|
buffer[i + 3 + rdAddr - baddr] = rdValue; |
|
|
|
|
|
|
|
} |
|
|
|
else { |
|
|
|
|
|
|
|
// No, we can't expand it - Skip the existing block
|
|
|
|
i += 3 + blen; |
|
|
|
|
|
|
|
// Can we create a new slot ?
|
|
|
|
if (i > (PageSize - 4)) { |
|
|
|
|
|
|
|
// Not enough space - Write the current buffer to FLASH
|
|
|
|
ee_PageWrite(curwPage + curwGroup * PagesPerGroup, buffer); |
|
|
|
|
|
|
|
// Advance write page (as we are compacting, should never overflow!)
|
|
|
|
++curwPage; |
|
|
|
|
|
|
|
// Clear RAM buffer
|
|
|
|
memset(buffer, 0xFF, sizeof(buffer)); |
|
|
|
|
|
|
|
// Start fresh */
|
|
|
|
i = 0; |
|
|
|
} |
|
|
|
|
|
|
|
// Enough space, add the new block
|
|
|
|
buffer[i] = rdAddr & 0xFF; |
|
|
|
buffer[i + 1] = (rdAddr >> 8) & 0xFF; |
|
|
|
buffer[i + 2] = 1; |
|
|
|
buffer[i + 3] = rdValue; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Go to the next address
|
|
|
|
++rdAddr; |
|
|
|
|
|
|
|
// Repeat for bytes of this range
|
|
|
|
} while (--rdRange); |
|
|
|
|
|
|
|
// Repeat until we run out of ranges
|
|
|
|
} while (rdAddr < EEPROMSize); |
|
|
|
|
|
|
|
// We must erase the previous group, in preparation for the next swap
|
|
|
|
for (int page = 0; page < curPage; page++) { |
|
|
|
ee_PageErase(page + curGroup * PagesPerGroup); |
|
|
|
} |
|
|
|
|
|
|
|
// Finally, Now the active group is the created new group
|
|
|
|
curGroup = curwGroup; |
|
|
|
curPage = curwPage; |
|
|
|
|
|
|
|
// Done!
|
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
static bool ee_Write(uint32_t address, uint8_t data) { |
|
|
|
|
|
|
|
// If we were requested an address outside of the emulated range, fail now
|
|
|
|
if (address >= EEPROMSize) return false; |
|
|
|
|
|
|
|
// Lets check if we have a block with that data previously defined. Block
|
|
|
|
// start addresses are always sorted in ascending order
|
|
|
|
uint16_t i = 0; |
|
|
|
while (i <= (PageSize - 4)) { /* (PageSize - 4) because otherwise, there is not enough room for data and headers */ |
|
|
|
|
|
|
|
// Get the address of the block
|
|
|
|
uint32_t baddr = buffer[i] | (buffer[i + 1] << 8); |
|
|
|
|
|
|
|
// Get the length of the block
|
|
|
|
uint32_t blen = buffer[i + 2]; |
|
|
|
|
|
|
|
// If we reach the end of the list, break loop
|
|
|
|
if (blen == 0xFF) |
|
|
|
break; |
|
|
|
|
|
|
|
// Check if data is contained in this block
|
|
|
|
if (address >= baddr && |
|
|
|
address < (baddr + blen)) { |
|
|
|
|
|
|
|
// Yes, it is contained. Just modify it
|
|
|
|
buffer[i + 3 + address - baddr] = data; |
|
|
|
|
|
|
|
// Done!
|
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
// Maybe we could add it to the front or to the back
|
|
|
|
// of this block ?
|
|
|
|
if ((address + 1) == baddr || address == (baddr + blen)) { |
|
|
|
|
|
|
|
// Potentially, it could be done. But we must ensure there is room
|
|
|
|
// so we can expand the block. Lets find how much free space remains
|
|
|
|
uint32_t iend = i; |
|
|
|
do { |
|
|
|
uint32_t ln = buffer[iend + 2]; |
|
|
|
if (ln == 0xFF) break; |
|
|
|
iend += 3 + ln; |
|
|
|
} while (iend <= (PageSize - 4)); /* (PageSize - 4) because otherwise, there is not enough room for data and headers */ |
|
|
|
|
|
|
|
// Here, inxt points to the first free address in the buffer. Do we have room ?
|
|
|
|
if (iend < PageSize) { |
|
|
|
// Yes, at least a byte is free - We can expand the block
|
|
|
|
|
|
|
|
// Do we have to insert at the beginning ?
|
|
|
|
if ((address + 1) == baddr) { |
|
|
|
|
|
|
|
// Insert at the beginning
|
|
|
|
|
|
|
|
// Make room at the beginning for our byte
|
|
|
|
memmove(&buffer[i + 3 + 1], &buffer[i + 3], iend - i - 3); |
|
|
|
|
|
|
|
// Adjust the header and store the data
|
|
|
|
buffer[i] = address & 0xFF; |
|
|
|
buffer[i + 1] = (address >> 8) & 0xFF; |
|
|
|
buffer[i + 2]++; |
|
|
|
buffer[i + 3] = data; |
|
|
|
|
|
|
|
} |
|
|
|
else { |
|
|
|
|
|
|
|
// Insert at the end - There is a very interesting thing that could happen here:
|
|
|
|
// Maybe we could coalesce the next block with this block. Let's try to do it!
|
|
|
|
uint16_t inext = i + 3 + blen; |
|
|
|
if (inext <= (PageSize - 4) && |
|
|
|
(buffer[inext] | uint16_t(buffer[inext + 1] << 8)) == (baddr + blen + 1)) { |
|
|
|
// YES! ... we can coalesce blocks! . Do it!
|
|
|
|
|
|
|
|
// Adjust this block header to include the next one
|
|
|
|
buffer[i + 2] += buffer[inext + 2] + 1; |
|
|
|
|
|
|
|
// Store data at the right place
|
|
|
|
buffer[i + 3 + blen] = data; |
|
|
|
|
|
|
|
// Remove the next block header and append its data
|
|
|
|
memmove(&buffer[inext + 1], &buffer[inext + 3], iend - inext - 3); |
|
|
|
|
|
|
|
// Finally, as we have saved 2 bytes at the end, make sure to clean them
|
|
|
|
buffer[iend - 2] = 0xFF; |
|
|
|
buffer[iend - 1] = 0xFF; |
|
|
|
|
|
|
|
} |
|
|
|
else { |
|
|
|
// NO ... No coalescing possible yet
|
|
|
|
|
|
|
|
// Make room at the end for our byte
|
|
|
|
memmove(&buffer[i + 3 + blen + 1], &buffer[i + 3 + blen], iend - i - 3 - blen); |
|
|
|
|
|
|
|
// And add the data to the block
|
|
|
|
buffer[i + 2]++; |
|
|
|
buffer[i + 3 + blen] = data; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Done!
|
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// As blocks are always sorted, if the starting address of this block is higher
|
|
|
|
// than the address we are looking for, break loop now - We wont find the value
|
|
|
|
// associated to the address
|
|
|
|
if (baddr > address) break; |
|
|
|
|
|
|
|
// Jump to the next block
|
|
|
|
i += 3 + blen; |
|
|
|
} |
|
|
|
|
|
|
|
// Value is not stored AND we can't expand previous block to contain it. We must create a new block
|
|
|
|
|
|
|
|
// First, lets find how much free space remains
|
|
|
|
uint32_t iend = i; |
|
|
|
while (iend <= (PageSize - 4)) { /* (PageSize - 4) because otherwise, there is not enough room for data and headers */ |
|
|
|
uint32_t ln = buffer[iend + 2]; |
|
|
|
if (ln == 0xFF) break; |
|
|
|
iend += 3 + ln; |
|
|
|
} |
|
|
|
|
|
|
|
// If there is room for a new block, insert it at the proper place
|
|
|
|
if (iend <= (PageSize - 4)) { |
|
|
|
|
|
|
|
// We have room to create a new block. Do so --- But add
|
|
|
|
// the block at the proper position, sorted by starting
|
|
|
|
// address, so it will be possible to compact it with other blocks.
|
|
|
|
|
|
|
|
// Make space
|
|
|
|
memmove(&buffer[i + 4], &buffer[i], iend - i); |
|
|
|
|
|
|
|
// And add the block
|
|
|
|
buffer[i] = address & 0xFF; |
|
|
|
buffer[i + 1] = (address >> 8) & 0xFF; |
|
|
|
buffer[i + 2] = 1; |
|
|
|
buffer[i + 3] = data; |
|
|
|
|
|
|
|
// Done!
|
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
// Not enough room to store this information on this FLASH page - Perform a
|
|
|
|
// flush and override the address with the specified contents
|
|
|
|
return ee_Flush(address, data); |
|
|
|
} |
|
|
|
|
|
|
|
static void ee_Init() { |
|
|
|
|
|
|
|
// Just init once!
|
|
|
|
if (curGroup != 0xFF) return; |
|
|
|
|
|
|
|
// Clean up the SRAM buffer
|
|
|
|
memset(buffer, 0xFF, sizeof(buffer)); |
|
|
|
|
|
|
|
// Now, we must find out the group where settings are stored
|
|
|
|
for (curGroup = 0; curGroup < GroupCount; curGroup++) |
|
|
|
if (!ee_IsPageClean(curGroup * PagesPerGroup)) break; |
|
|
|
|
|
|
|
// If all groups seem to be used, default to first group
|
|
|
|
if (curGroup >= GroupCount) curGroup = 0; |
|
|
|
|
|
|
|
DEBUG_ECHO_START(); |
|
|
|
DEBUG_ECHOLNPAIR("EEPROM Current Group: ",curGroup); |
|
|
|
DEBUG_FLUSH(); |
|
|
|
|
|
|
|
// Now, validate that all the other group pages are empty
|
|
|
|
for (int grp = 0; grp < GroupCount; grp++) { |
|
|
|
if (grp == curGroup) continue; |
|
|
|
|
|
|
|
for (int page = 0; page < PagesPerGroup; page++) { |
|
|
|
if (!ee_IsPageClean(grp * PagesPerGroup + page)) { |
|
|
|
DEBUG_ECHO_START(); |
|
|
|
DEBUG_ECHOLNPAIR("EEPROM Page ", page, " not clean on group ", grp); |
|
|
|
DEBUG_FLUSH(); |
|
|
|
ee_PageErase(grp * PagesPerGroup + page); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Finally, for the active group, determine the first unused page
|
|
|
|
// and also validate that all the other ones are clean
|
|
|
|
for (curPage = 0; curPage < PagesPerGroup; curPage++) { |
|
|
|
if (ee_IsPageClean(curGroup * PagesPerGroup + curPage)) { |
|
|
|
ee_Dump(curGroup * PagesPerGroup + curPage, getFlashStorage(curGroup * PagesPerGroup + curPage)); |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
DEBUG_ECHO_START(); |
|
|
|
DEBUG_ECHOLNPAIR("EEPROM Active page: ", curPage); |
|
|
|
DEBUG_FLUSH(); |
|
|
|
|
|
|
|
// Make sure the pages following the first clean one are also clean
|
|
|
|
for (int page = curPage + 1; page < PagesPerGroup; page++) { |
|
|
|
if (!ee_IsPageClean(curGroup * PagesPerGroup + page)) { |
|
|
|
DEBUG_ECHO_START(); |
|
|
|
DEBUG_ECHOLNPAIR("EEPROM Page ", page, " not clean on active group ", curGroup); |
|
|
|
DEBUG_FLUSH(); |
|
|
|
ee_Dump(curGroup * PagesPerGroup + page, getFlashStorage(curGroup * PagesPerGroup + page)); |
|
|
|
ee_PageErase(curGroup * PagesPerGroup + page); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/* PersistentStore -----------------------------------------------------------*/ |
|
|
|
|
|
|
|
#include "../shared/eeprom_api.h" |
|
|
|
|
|
|
|
size_t PersistentStore::capacity() { return E2END + 1; } |
|
|
|
bool PersistentStore::access_start() { ee_Init(); return true; } |
|
|
|
bool PersistentStore::access_finish() { ee_Flush(); return true; } |
|
|
|
|
|
|
|
bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, uint16_t *crc) { |
|
|
|
while (size--) { |
|
|
|
uint8_t * const p = (uint8_t * const)pos; |
|
|
|
uint8_t v = *value; |
|
|
|
// EEPROM has only ~100,000 write cycles,
|
|
|
|
// so only write bytes that have changed!
|
|
|
|
if (v != eeprom_read_byte(p)) { |
|
|
|
eeprom_write_byte(p, v); |
|
|
|
if (v != ee_Read(uint32_t(p))) { |
|
|
|
ee_Write(uint32_t(p), v); |
|
|
|
delay(2); |
|
|
|
if (eeprom_read_byte(p) != v) { |
|
|
|
if (ee_Read(uint32_t(p)) != v) { |
|
|
|
SERIAL_ECHO_MSG(STR_ERR_EEPROM_WRITE); |
|
|
|
return true; |
|
|
|
} |
|
|
@ -68,7 +1000,7 @@ bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, ui |
|
|
|
|
|
|
|
bool PersistentStore::read_data(int &pos, uint8_t* value, size_t size, uint16_t *crc, const bool writing/*=true*/) { |
|
|
|
do { |
|
|
|
uint8_t c = eeprom_read_byte((uint8_t*)pos); |
|
|
|
uint8_t c = ee_Read(uint32_t(pos)); |
|
|
|
if (writing) *value = c; |
|
|
|
crc16(crc, &c, 1); |
|
|
|
pos++; |
|
|
@ -77,5 +1009,5 @@ bool PersistentStore::read_data(int &pos, uint8_t* value, size_t size, uint16_t |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
#endif // EEPROM_SETTINGS
|
|
|
|
#endif // FLASH_EEPROM_EMULATION
|
|
|
|
#endif // ARDUINO_ARCH_SAM
|
|
|
|