From fe74deae34147e0e835af613883fbb130d6478e0 Mon Sep 17 00:00:00 2001 From: Matt Staniszewski Date: Thu, 31 Oct 2019 09:25:31 -0400 Subject: [PATCH] Merged in persistence files, starting on compile --- g2core/persistence.cpp | 251 +++++++++++++++++++++- g2core/persistence.h | 28 ++- g2core/xio.cpp | 5 +- persistence_diff.patch | 463 ----------------------------------------- 4 files changed, 269 insertions(+), 478 deletions(-) delete mode 100644 persistence_diff.patch diff --git a/g2core/persistence.cpp b/g2core/persistence.cpp index cce395c0..cbffa83a 100644 --- a/g2core/persistence.cpp +++ b/g2core/persistence.cpp @@ -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 (; brfirst - 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); +} \ No newline at end of file diff --git a/g2core/persistence.h b/g2core/persistence.h index 3a64d27e..d49c9648 100644 --- a/g2core/persistence.h +++ b/g2core/persistence.h @@ -29,24 +29,34 @@ #define PERSISTENCE_H_ONCE #include "config.h" // needed for nvObj_t definition +#include "fatfs/ff.h" +#include "util.h" // FIXME: this won't compile if included after +#include -#define NVM_VALUE_LEN 4 // NVM value length (float, fixed length) -#define NVM_BASE_ADDR 0x0000 // base address of usable NVM +#define NVM_VALUE_LEN 4 // NVM value length (float, fixed length) +#define NVM_BASE_ADDR 0x0000 // base address of usable NVM + +#define IO_BUFFER_SIZE 512 // this should be evenly divisible by NVM_VALUE_LEN, and <=512 until multi-block reads are fixed (right now they are hanging...) +#define MIN_WRITE_INTERVAL 1000 // minimum interval between persistence file writes +#define MAX_WRITE_FAILURES 3 //**** persistence singleton **** typedef struct nvmSingleton { - uint16_t base_addr; // NVM base address - uint16_t profile_base; // NVM base address of current profile] - uint16_t address; - float tmp_value; - int8_t byte_array[NVM_VALUE_LEN]; + FATFS fat_fs; + FIL file; + uint8_t file_index; + uint8_t io_buffer[IO_BUFFER_SIZE]; + std::map write_cache; + uint32_t last_write_systick; + uint8_t write_failures; } nvmSingleton_t; //**** persistence function prototypes **** void persistence_init(void); -stat_t read_persistent_value(nvObj_t* nv); -stat_t write_persistent_value(nvObj_t* nv); +stat_t read_persistent_value(nvObj_t *nv); +stat_t write_persistent_value(nvObj_t *nv); +stat_t write_persistent_values_callback(); #endif // End of include guard: PERSISTENCE_H_ONCE diff --git a/g2core/xio.cpp b/g2core/xio.cpp index e133bf3d..e75b39b1 100644 --- a/g2core/xio.cpp +++ b/g2core/xio.cpp @@ -403,16 +403,15 @@ struct xio_t { uint16_t magic_end; }; -//ms: fix void xio_flush_device(devflags_t flags) { - /*or( uint8_t dev=0; dev < DEV_MAX; dev++) { + or( uint8_t dev=0; dev < DEV_MAX; dev++) { if(!xio.d[dev]->isActive()) continue; if(!(xio.d[dev]->flags & flags)) continue; DeviceWrappers[dev]->flushRead(); - }*/ + } } // Declare (but don't define) the xio singleton object now, define it later diff --git a/persistence_diff.patch b/persistence_diff.patch deleted file mode 100644 index d6ef59fe..00000000 --- a/persistence_diff.patch +++ /dev/null @@ -1,463 +0,0 @@ -diff --git a/./g2core/persistence.cpp b/../g2/TinyG2/persistence.cpp -index cce395c0..8ff38397 100644 ---- a/./g2core/persistence.cpp -+++ b/../g2/TinyG2/persistence.cpp -@@ -1,8 +1,8 @@ - /* - * persistence.cpp - persistence functions -- * This file is part of the g2core project -+ * This file is part of the TinyG2 project - * -- * Copyright (c) 2013 - 2018 Alden S. Hart Jr. -+ * Copyright (c) 2013 - 2014 Alden S. Hart Jr. - * - * This file ("the software") is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2 as published by the -@@ -24,11 +24,14 @@ - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF - * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ --#include "g2core.h" -+#include "tinyg2.h" - #include "persistence.h" - #include "canonical_machine.h" - #include "report.h" --//#include "util.h" -+ -+#ifdef __AVR -+#include "xmega/xmega_eeprom.h" -+#endif - - /*********************************************************************************** - **** STRUCTURE ALLOCATIONS ******************************************************** -@@ -40,6 +43,42 @@ nvmSingleton_t nvm; - **** GENERIC STATIC FUNCTIONS AND VARIABLES *************************************** - ***********************************************************************************/ - -+#ifdef __ARM -+ -+stat_t prepare_persistence_file(); -+stat_t write_persistent_values(); -+stat_t validate_persistence_file(); -+uint8_t active_file_index(); -+ -+// Leaving this in for now in case bugs come up; we can remove it when we're confident -+// it's stable -+#if 0 -+# define DEBUG_PRINT(...) printf(__VA_ARGS__); -+#else -+# define DEBUG_PRINT(...) do {} while (0) -+#endif -+ -+// 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 -+ -+#endif - - /*********************************************************************************** - **** CODE ************************************************************************* -@@ -47,7 +86,16 @@ nvmSingleton_t nvm; - - void persistence_init() - { -- return; -+#ifdef __AVR -+ nvm.base_addr = NVM_BASE_ADDR; -+ nvm.profile_base = 0; -+#endif -+#ifdef __ARM -+ nvm.file_index = 0; -+ nvm.last_write_systick = SysTickTimer_getValue(); -+ nvm.write_failures = 0; -+#endif -+ return; - } - - /* -@@ -56,23 +104,287 @@ void persistence_init() - * It's the responsibility of the caller to make sure the index does not exceed range - */ - -+#ifdef __AVR - stat_t read_persistent_value(nvObj_t *nv) - { -- nv->value_flt = 0; -- return (STAT_OK); -+ int8_t nvm_byte_array[NVM_VALUE_LEN]; -+ uint16_t nvm_address = nvm.profile_base + (nv->index * NVM_VALUE_LEN); -+ (void)EEPROM_ReadBytes(nvm_address, nvm_byte_array, NVM_VALUE_LEN); -+ memcpy(&nv->value, &nvm_byte_array, NVM_VALUE_LEN); -+ return (STAT_OK); - } -+#endif // __AVR -+ -+#ifdef __ARM -+stat_t read_persistent_value(nvObj_t *nv) -+{ -+ 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); -+} -+#endif // __ARM - - /* -- * write_persistent_value() - write to NVM by index, but only if the value has changed -+ * write_persistent_value() - write to NVM by index, but only if the value has changed. -+ * -+ * On AVR, the write is performed immediately. On ARM, the new value is cached, and -+ * later written from the write_persistent_values_callback (batching writes should -+ * extend the lifetime of the SD card). - * - * It's the responsibility of the caller to make sure the index does not exceed range - * Note: Removed NAN and INF checks on floats - not needed - */ - -+#ifdef __AVR - 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")); --// } -- return (STAT_OK); -+ if (cm.cycle_state != CYCLE_OFF) return(rpt_exception(STAT_FILE_NOT_OPEN, NULL)); // can't write when machine is moving -+ nvm.tmp_value = nv->value; -+ ritorno(read_persistent_value(nv)); -+ if ((isnan((double)nv->value)) || (isinf((double)nv->value)) || (fp_NE(nv->value, nvm.tmp_value))) { -+ memcpy(&nvm.byte_array, &nvm.tmp_value, NVM_VALUE_LEN); -+ nvm.address = nvm.profile_base + (nv->index * NVM_VALUE_LEN); -+ (void)EEPROM_WriteBytes(nvm.address, nvm.byte_array, NVM_VALUE_LEN); -+ } -+ nv->value =nvm.tmp_value; // always restore value -+ return (STAT_OK); - } -+#endif // __AVR -+ -+#ifdef __ARM -+stat_t write_persistent_value(nvObj_t *nv) -+{ -+ 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); -+} -+#endif // __ARM -+ -+/* -+ * write_persistent_values_callback() -+ * -+ * On ARM, write cached values to a file. No-op on AVR. -+ */ -+ -+stat_t write_persistent_values_callback() -+{ -+#ifdef __ARM -+ -+ // 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; -+ } -+ -+#endif -+ return STAT_NOOP; -+} -+ -+#ifdef __ARM -+ -+/* -+ * 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 (; brfirst - 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); -+} -+#endif // __ARM -+ -+ -+ -+ -+ -diff --git a/./g2core/persistence.h b/../g2/TinyG2/persistence.h -index 3a64d27e..22941d15 100644 ---- a/./g2core/persistence.h -+++ b/../g2/TinyG2/persistence.h -@@ -1,8 +1,8 @@ - /* - * persistence.h - persistence code -- * This file is part of the g2code project -+ * This file is part of the TinyG project - * -- * Copyright (c) 2013 - 2018 Alden S. Hart Jr. -+ * Copyright (c) 2013 - 2014 Alden S. Hart Jr. - * - * This file ("the software") is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2 as published by the -@@ -28,25 +28,48 @@ - #ifndef PERSISTENCE_H_ONCE - #define PERSISTENCE_H_ONCE - --#include "config.h" // needed for nvObj_t definition -+#include "config.h" // needed for nvObj_t definition - --#define NVM_VALUE_LEN 4 // NVM value length (float, fixed length) --#define NVM_BASE_ADDR 0x0000 // base address of usable NVM -+#define NVM_VALUE_LEN 4 // NVM value length (float, fixed length) -+#define NVM_BASE_ADDR 0x0000 // base address of usable NVM -+ -+#ifdef __ARM -+#include "fatfs/ff.h" -+#include "util.h" // FIXME: this won't compile if included after -+#include -+ -+#define IO_BUFFER_SIZE 512 // this should be evenly divisible by NVM_VALUE_LEN, and <=512 until multi-block reads are fixed (right now they are hanging...) -+#define MIN_WRITE_INTERVAL 1000 // minimum interval between persistence file writes -+#define MAX_WRITE_FAILURES 3 -+ -+#endif - - //**** persistence singleton **** - - typedef struct nvmSingleton { -- uint16_t base_addr; // NVM base address -- uint16_t profile_base; // NVM base address of current profile] -- uint16_t address; -- float tmp_value; -- int8_t byte_array[NVM_VALUE_LEN]; -+ float tmp_value; -+#ifdef __AVR -+ uint16_t base_addr; // NVM base address -+ uint16_t profile_base; // NVM base address of current profile] -+ uint16_t address; -+ int8_t byte_array[NVM_VALUE_LEN]; -+#endif -+#ifdef __ARM -+ FATFS fat_fs; -+ FIL file; -+ uint8_t file_index; -+ uint8_t io_buffer[IO_BUFFER_SIZE]; -+ std::map write_cache; -+ uint32_t last_write_systick; -+ uint8_t write_failures; -+#endif - } nvmSingleton_t; - - //**** persistence function prototypes **** - - void persistence_init(void); --stat_t read_persistent_value(nvObj_t* nv); --stat_t write_persistent_value(nvObj_t* nv); -+stat_t read_persistent_value(nvObj_t *nv); -+stat_t write_persistent_value(nvObj_t *nv); -+stat_t write_persistent_values_callback(); - --#endif // End of include guard: PERSISTENCE_H_ONCE -+#endif // End of include guard: PERSISTENCE_H_ONCE