mirror of
https://github.com/synthetos/g2.git
synced 2026-02-06 02:51:54 +08:00
Merged in persistence files, starting on compile
This commit is contained in:
committed by
Rob Giseburt
parent
a06b341934
commit
fe74deae34
@@ -40,6 +40,30 @@ nvmSingleton_t nvm;
|
||||
**** GENERIC STATIC FUNCTIONS AND VARIABLES ***************************************
|
||||
***********************************************************************************/
|
||||
|
||||
stat_t prepare_persistence_file();
|
||||
stat_t write_persistent_values();
|
||||
stat_t validate_persistence_file();
|
||||
uint8_t active_file_index();
|
||||
|
||||
// Analogous to ritorno(a), but for the FatFS return codes and with optional debug output
|
||||
#define fs_ritorno(a, msg) if((status_code=a) != FR_OK) \
|
||||
{ DEBUG_PRINT("%s res: %i\n", msg, status_code); return(STAT_PERSISTENCE_ERROR); }
|
||||
|
||||
/*
|
||||
We cycle between three different files, indexed by a suffix. Each time we need to write
|
||||
new values, we copy data from the current file to a new file with NEXT_FILE_INDEX, then
|
||||
delete the current file once the write is complete. This ensures that at least one recent
|
||||
copy of the file will be preserved if power is lost in the middle of a write.
|
||||
*/
|
||||
#define PERSISTENCE_DIR "persist"
|
||||
#define PERSISTENCE_FILENAME(num) PERSISTENCE_DIR"/persist"#num".bin"
|
||||
#define PERSISTENCE_FILENAME_CNT 3
|
||||
#define NEXT_FILE_INDEX (nvm.file_index+1) % PERSISTENCE_FILENAME_CNT
|
||||
#define PREV_FILE_INDEX (nvm.file_index+PERSISTENCE_FILENAME_CNT-1) % PERSISTENCE_FILENAME_CNT
|
||||
const char* filenames[PERSISTENCE_FILENAME_CNT] = {
|
||||
PERSISTENCE_FILENAME(0), PERSISTENCE_FILENAME(1), PERSISTENCE_FILENAME(2) };
|
||||
|
||||
#define CRC_LEN 4
|
||||
|
||||
/***********************************************************************************
|
||||
**** CODE *************************************************************************
|
||||
@@ -47,6 +71,9 @@ nvmSingleton_t nvm;
|
||||
|
||||
void persistence_init()
|
||||
{
|
||||
nvm.file_index = 0;
|
||||
nvm.last_write_systick = SysTickTimer_getValue();
|
||||
nvm.write_failures = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -59,6 +86,16 @@ void persistence_init()
|
||||
stat_t read_persistent_value(nvObj_t *nv)
|
||||
{
|
||||
nv->value_flt = 0;
|
||||
ritorno(prepare_persistence_file());
|
||||
DEBUG_PRINT("file opened for reading\n");
|
||||
fs_ritorno(f_lseek(&nvm.file, nv->index * NVM_VALUE_LEN), "f_lseek during read");
|
||||
UINT br;
|
||||
fs_ritorno(f_read(&nvm.file, &nvm.io_buffer, NVM_VALUE_LEN, &br), "read value");
|
||||
if (br != NVM_VALUE_LEN) {
|
||||
return (STAT_PERSISTENCE_ERROR);
|
||||
}
|
||||
memcpy(&nv->value, &nvm.io_buffer, NVM_VALUE_LEN);
|
||||
DEBUG_PRINT("value copied from address %i in file: %f\n", nv->index * NVM_VALUE_LEN, nv->value);
|
||||
return (STAT_OK);
|
||||
}
|
||||
|
||||
@@ -71,8 +108,216 @@ stat_t read_persistent_value(nvObj_t *nv)
|
||||
|
||||
stat_t write_persistent_value(nvObj_t *nv)
|
||||
{
|
||||
// if (cm->cycle_state != CYCLE_OFF) { // can't write when machine is moving
|
||||
// return(rpt_exception(STAT_FILE_NOT_OPEN, "write_persistent_value() can't write when machine is in cycle"));
|
||||
// }
|
||||
nvm.tmp_value = nv->value;
|
||||
if (read_persistent_value(nv) != STAT_OK ||
|
||||
(isnan((double)nv->value)) ||
|
||||
(isinf((double)nv->value)) ||
|
||||
(nv->value != nvm.tmp_value)) { // use a bitwise equality check rather than fp_EQ
|
||||
// since underlying value might not really be a float
|
||||
nvm.write_cache[nv->index] = nvm.tmp_value;
|
||||
}
|
||||
nv->value =nvm.tmp_value; // always restore value
|
||||
return (STAT_OK);
|
||||
}
|
||||
|
||||
/*
|
||||
* write_persistent_values_callback()
|
||||
*
|
||||
* On ARM, write cached values to a file. No-op on AVR.
|
||||
*/
|
||||
|
||||
stat_t write_persistent_values_callback()
|
||||
{
|
||||
// Check the disk status to ensure we catch card-detect pin changes.
|
||||
// FIXME: it would be much better to do this with an interrupt!
|
||||
f_polldisk();
|
||||
if (nvm.write_cache.size()) {
|
||||
if (SysTickTimer_getValue() - nvm.last_write_systick < MIN_WRITE_INTERVAL) return (STAT_NOOP);
|
||||
// this check may not be necessary on ARM, but just in case...
|
||||
if (cm.cycle_state != CYCLE_OFF) return(STAT_NOOP); // can't write when machine is moving
|
||||
|
||||
if(write_persistent_values() == STAT_OK) {
|
||||
nvm.write_cache.clear();
|
||||
nvm.write_failures = 0;
|
||||
} else {
|
||||
// if the write failed, make sure no half-written output file exists
|
||||
f_unlink(filenames[NEXT_FILE_INDEX]);
|
||||
if (++nvm.write_failures >= MAX_WRITE_FAILURES) {
|
||||
nvm.write_cache.clear(); // give up on these values
|
||||
nvm.write_failures = 0; // but try again if we get more values later
|
||||
return(rpt_exception(STAT_PERSISTENCE_ERROR, NULL));
|
||||
}
|
||||
}
|
||||
nvm.last_write_systick = SysTickTimer_getValue();
|
||||
return STAT_OK;
|
||||
}
|
||||
return STAT_NOOP;
|
||||
}
|
||||
|
||||
/*
|
||||
* active_file_index()
|
||||
*
|
||||
* Determines which of the existing files is most current and should be used for
|
||||
* value reads. If no files exist, return 0. This assumes that no more than two
|
||||
* files exist at any one time, which should always be the case under our updating
|
||||
* scheme (described in a comment near the PERSISTENCE_FILENAME define).
|
||||
*/
|
||||
uint8_t active_file_index()
|
||||
{
|
||||
uint8_t i = 0;
|
||||
for (; i < PERSISTENCE_FILENAME_CNT; ++i) {
|
||||
if (f_stat(filenames[i], nullptr) == FR_OK) {
|
||||
uint8_t next = (i+1)%PERSISTENCE_FILENAME_CNT;
|
||||
if (next > i && f_stat(filenames[next], nullptr) == FR_OK) {
|
||||
i = next;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
/*
|
||||
* prepare_persistence_file()
|
||||
*
|
||||
* ARM only. Ensures that the file is open and has a valid CRC. This should be called
|
||||
* prior to using the file in any other function.
|
||||
*/
|
||||
stat_t prepare_persistence_file()
|
||||
{
|
||||
// if the file is already open and valid, no further prep is necessary.
|
||||
// NOTE: we don't close the file after every use because the higher latency
|
||||
// would slow down consecutive reads. However, we still need to re-validate before
|
||||
// every use to ensure that the card status hasn't changed.
|
||||
if (f_is_open(&nvm.file) && validate(&nvm.file) == FR_OK) return STAT_OK;
|
||||
|
||||
// mount volume if necessary
|
||||
if (!nvm.fat_fs.fs_type) {
|
||||
fs_ritorno(f_mount(&nvm.fat_fs, "", 1), "mount"); /* Give a work area to the default drive */
|
||||
}
|
||||
f_mkdir(PERSISTENCE_DIR);
|
||||
uint8_t index = active_file_index();
|
||||
fs_ritorno(f_open(&nvm.file, filenames[index], FA_READ | FA_OPEN_EXISTING), "open input");
|
||||
nvm.file_index = index;
|
||||
|
||||
// if CRC doesn't match, delete file and return error
|
||||
if (validate_persistence_file() != STAT_OK) {
|
||||
f_close(&nvm.file);
|
||||
f_unlink(filenames[nvm.file_index]);
|
||||
nvm.file_index = 0;
|
||||
return STAT_PERSISTENCE_ERROR;
|
||||
}
|
||||
// OK to delete old file now (if it still exists), since we know the current one is good
|
||||
f_unlink(filenames[PREV_FILE_INDEX]);
|
||||
return STAT_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* validate_persistence_file()
|
||||
*
|
||||
* ARM only. Helper function that checks the CRC and byte count of the
|
||||
* persistence file. Assumes the file is already open.
|
||||
*/
|
||||
stat_t validate_persistence_file()
|
||||
{
|
||||
uint32_t crc = 0;
|
||||
uint32_t filecrc = NAN;
|
||||
UINT br, br_sum = 0;
|
||||
fs_ritorno(f_lseek(&nvm.file, 0), "crc check seek");
|
||||
while (!f_eof(&nvm.file)) {
|
||||
fs_ritorno(f_read(&nvm.file, &nvm.io_buffer, IO_BUFFER_SIZE, &br), "file read during CRC check");
|
||||
|
||||
if (f_eof(&nvm.file)) {
|
||||
br -= std::min((UINT)CRC_LEN, br); // don't include old CRC in current CRC calculation
|
||||
memcpy(&filecrc, nvm.io_buffer+br, CRC_LEN); // copy old CRC out of read buffer
|
||||
}
|
||||
// update calculated CRC
|
||||
crc = crc32(crc, nvm.io_buffer, br);
|
||||
br_sum += br;
|
||||
}
|
||||
|
||||
// how did we do?
|
||||
if (br_sum != nv_index_max() * NVM_VALUE_LEN) {
|
||||
DEBUG_PRINT("bad byte count in file: %i\n", br_sum);
|
||||
return STAT_PERSISTENCE_ERROR;
|
||||
}
|
||||
DEBUG_PRINT("crc: %lu from file, %lu calculated\n", filecrc, crc);
|
||||
return crc == filecrc ? STAT_OK : STAT_PERSISTENCE_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* write_persistent_values()
|
||||
*
|
||||
* ARM only. Writes all the values from the write cache to the SD card. Since we can't
|
||||
* rewrite individual pieces of data in the middle of an existing file, this requires
|
||||
* rewriting all the data into a new file.
|
||||
*/
|
||||
stat_t write_persistent_values()
|
||||
{
|
||||
DEBUG_PRINT("writing new version\n");
|
||||
|
||||
FIL f_out;
|
||||
UINT bw;
|
||||
|
||||
// attempt to open file with previously persisted values
|
||||
if (prepare_persistence_file() == STAT_OK) {
|
||||
fs_ritorno(f_lseek(&nvm.file, 0), "f_lseek to input file start");
|
||||
}
|
||||
|
||||
// open new file for writing updated values
|
||||
fs_ritorno(f_open(&f_out, filenames[NEXT_FILE_INDEX], FA_WRITE | FA_OPEN_ALWAYS), "open output");
|
||||
fs_ritorno(f_sync(&f_out), "sync output file");
|
||||
DEBUG_PRINT("opened %s for writing\n", filenames[NEXT_FILE_INDEX]);
|
||||
|
||||
uint32_t crc = 0;
|
||||
uint16_t step = IO_BUFFER_SIZE/NVM_VALUE_LEN;
|
||||
for (index_t cnt = 0; cnt < nv_index_max(); cnt += step) {
|
||||
// try to read old values from existing file
|
||||
uint16_t io_byte_count = std::min(IO_BUFFER_SIZE, (nv_index_max()-cnt) * NVM_VALUE_LEN);
|
||||
UINT br = 0;
|
||||
f_read(&nvm.file, &nvm.io_buffer, io_byte_count, &br);
|
||||
DEBUG_PRINT("read %i bytes from old file\n", br);
|
||||
|
||||
// if we didn't get enough bytes from the old file, pad the buffer with defaults
|
||||
// to keep the length correct
|
||||
// FIXME: integrate this with default-setting code in config.cpp
|
||||
for (; br<io_byte_count; br += NVM_VALUE_LEN) {
|
||||
index_t index = cnt + br/NVM_VALUE_LEN;
|
||||
memcpy(nvm.io_buffer+br, &cfgArray[index].def_value, NVM_VALUE_LEN);
|
||||
}
|
||||
DEBUG_PRINT("io_buffer populated with %i bytes total\n", br);
|
||||
|
||||
// update the values in the buffer from the write cache
|
||||
for (auto i = nvm.write_cache.lower_bound(cnt);
|
||||
i != nvm.write_cache.lower_bound(cnt+step);
|
||||
++i) {
|
||||
index_t index = (i->first - cnt) * NVM_VALUE_LEN;
|
||||
memcpy(nvm.io_buffer+index, &i->second, NVM_VALUE_LEN);
|
||||
DEBUG_PRINT("item index: %i, write index: %i (cnt: %i), value: %f\n", i->first, index, cnt, i->second);
|
||||
}
|
||||
|
||||
// write updated values to output file and sync
|
||||
fs_ritorno(f_write(&f_out, &nvm.io_buffer, io_byte_count, &bw), "new file write");
|
||||
if (bw != io_byte_count) return (STAT_PERSISTENCE_ERROR);
|
||||
fs_ritorno(f_sync(&f_out), "out sync");
|
||||
|
||||
// update CRC
|
||||
crc = crc32(crc, nvm.io_buffer, io_byte_count);
|
||||
}
|
||||
|
||||
// write CRC in final 4 bytes
|
||||
fs_ritorno(f_write(&f_out, &crc, CRC_LEN, &bw), "write crc");
|
||||
DEBUG_PRINT("wrote crc: %lu\n", crc);
|
||||
|
||||
// close both old and new files
|
||||
fs_ritorno(f_close(&f_out), "close output");
|
||||
if (f_is_open(&nvm.file)) {
|
||||
fs_ritorno(f_close(&nvm.file), "close input");
|
||||
// if we made it here, it's now safe to delete the older file
|
||||
fs_ritorno(f_unlink(filenames[nvm.file_index]), "old file delete");
|
||||
DEBUG_PRINT("deleted obsolete file %s\n", filenames[nvm.file_index]);
|
||||
nvm.file_index = 0;
|
||||
}
|
||||
|
||||
return (STAT_OK);
|
||||
}
|
||||
Reference in New Issue
Block a user