Merged in persistence files, starting on compile

This commit is contained in:
Matt Staniszewski
2019-10-31 09:25:31 -04:00
committed by Rob Giseburt
parent a06b341934
commit fe74deae34
4 changed files with 269 additions and 478 deletions

View File

@@ -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);
}