diff --git a/g2core/board/G2v9/0_hardware.cpp b/g2core/board/G2v9/0_hardware.cpp index c56cec19..09c1fd1b 100644 --- a/g2core/board/G2v9/0_hardware.cpp +++ b/g2core/board/G2v9/0_hardware.cpp @@ -38,6 +38,7 @@ #include "MotateUniqueID.h" #include "MotatePower.h" #include "board_spi.h" +#include "sd_persistence.h" /* * hardware_init() - lowest level hardware init @@ -48,10 +49,12 @@ SPIBus_used_t spiBus; Motate::SPIChipSelectPin sdcs{}; SDCard_used_t sd_card{spiBus, sdcs}; + void hardware_init() { spiBus.init(); sd_card.init(); + setup_sd_persistence(); board_hardware_init(); return; } diff --git a/g2core/device/sd_card/sd_persistence.cpp b/g2core/device/sd_card/sd_persistence.cpp new file mode 100644 index 00000000..6ca623b0 --- /dev/null +++ b/g2core/device/sd_card/sd_persistence.cpp @@ -0,0 +1,403 @@ +/* + * sd_persistence.cpp - persistence functions for SD cards + * This file is part of the g2core project + * + * Copyright (c) 2019 Matt Staniszewski + * Copyright (c) 2019 Robert Giseburt + * + * 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 + * Free Software Foundation. You should have received a copy of the GNU General Public + * License, version 2 along with the software. If not, see . + * + * As a special exception, you may use this file as part of a software library without + * restriction. Specifically, if other files instantiate templates or use macros or + * inline functions from this file, or you compile this file and link it with other + * files to produce an executable, this file does not by itself cause the resulting + * executable to be covered by the GNU General Public License. This exception does not + * however invalidate any other reasons why the executable file might be covered by the + * GNU General Public License. + * + * THE SOFTWARE IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT WITHOUT ANY + * WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT + * SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * 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 "persistence.h" +#include "canonical_machine.h" +#include "report.h" +#include "util.h" +#include "board_spi.h" +#include "ff.h" + +#include "sd_persistence.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 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 +#define MAX_WRITE_CHANGES IO_BUFFER_SIZE // maximum number of write values that change - ms: TODO + +/*********************************************************************************** + **** STRUCTURE ALLOCATIONS ******************************************************** + ***********************************************************************************/ + +//**** persistence singleton **** + +struct nvmSingleton_t { + float tmp_value; + FATFS fat_fs; + FIL file; + uint8_t file_index; + alignas(4) uint8_t io_buffer[IO_BUFFER_SIZE]; + uint16_t changed_nvs; + uint32_t last_write_systick; + uint8_t write_failures; +} 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(); + +// 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 + +/*********************************************************************************** + **** CODE ************************************************************************* + ***********************************************************************************/ + +class SD_Persistence : public Persistence { + public: + void init() override; + stat_t read(nvObj_t *nv) override; + stat_t write(nvObj_t *nv) override; + stat_t periodic() override; +}; + +SD_Persistence sdp {}; + +void setup_sd_persistence() { + persistence = &sdp; +} + +void SD_Persistence::init() +{ + nvm.file_index = 0; + nvm.last_write_systick = Motate::SysTickTimer.getValue(); + nvm.write_failures = 0; + nvm.changed_nvs = 0; + return; +} + +/* + * read_persistent_value() - return value (as float) by index + * + * It's the responsibility of the caller to make sure the index does not exceed range + */ + +stat_t SD_Persistence::read(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); + } + + if (cfgArray[nv->index].flags & TYPE_INTEGER) { + nv->valuetype = TYPE_INTEGER; + nv->value_int = *(int32_t *)nvm.io_buffer; + DEBUG_PRINT("value (i) copied from address %l in file: %l\n", nv->index * NVM_VALUE_LEN, nv->value_int); + } else if (cfgArray[nv->index].flags & TYPE_BOOLEAN) { + nv->valuetype = TYPE_BOOLEAN; + nv->value_int = *(int32_t *)nvm.io_buffer; + DEBUG_PRINT("value (b) copied from address %l in file: %l\n", nv->index * NVM_VALUE_LEN, nv->value_int); + } else { + nv->valuetype = TYPE_FLOAT; + nv->value_flt = *(float *)nvm.io_buffer; + DEBUG_PRINT("value (f) copied from address %l in file: %f\n", nv->index * NVM_VALUE_LEN, nv->value_flt); + } + + return (STAT_OK); +} + +/* + * write_persistent_value() - write to NVM by index, but only if the value has changed + * + * 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 + */ + +stat_t SD_Persistence::write(nvObj_t *nv) +{ + nvm.changed_nvs++; + return (STAT_OK); +} + +/* + * write_persistent_values_callback() + * + * On ARM, write cached values to a file. No-op on AVR. + */ + +stat_t SD_Persistence::periodic() +{ + // 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.changed_nvs > 0) { + if (Motate::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_type != CYCLE_NONE) { + return(STAT_NOOP); // can't write when machine is moving + } + + if(write_persistent_values() == STAT_OK) { + nvm.changed_nvs = 0; + 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.changed_nvs = 0; // 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 = Motate::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 = -1; + 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; + nvObj_t *nv = nv_reset_nv_list(); // sets *nv to the start of the body + cmDistanceMode saved_distance_mode; + + // Save the current units mode + saved_distance_mode = (cmDistanceMode)cm_get_distance_mode(ACTIVE_MODEL); + + // 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((float)IO_BUFFER_SIZE, (float)((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 (; brindex = cnt; nv->index < (cnt + step); nv->index++) { + + // Found an entry that needs to be written + if (nv->index == 0 || cfgArray[nv->index].flags & F_PERSIST) { + nv_get_nvObj(nv); + + // Get the index for the current NVM value + index_t index = (nv->index - cnt) * NVM_VALUE_LEN; + + // Write out based on the value type + if (nv->valuetype == TYPE_INTEGER || nv->valuetype == TYPE_BOOLEAN) { + memcpy(nvm.io_buffer + index, &nv->value_int, NVM_VALUE_LEN); + DEBUG_PRINT("item index: %l , write index: %l (cnt: %l), value: %i\n", nv->index, index, cnt, nv->value_int); + } else if (nv->valuetype == TYPE_FLOAT) { + memcpy(nvm.io_buffer + index, &nv->value_flt, NVM_VALUE_LEN); + DEBUG_PRINT("item index: %l , write index: %l (cnt: %l), value: %f\n", nv->index, index, cnt, nv->value_flt); + } else { + // next - ignore strings and other stuff which shouldn't be set to persist anyway + } + } + } + + // 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; + } + + // Restore units mode + cm_set_distance_mode(saved_distance_mode); + + return (STAT_OK); +} diff --git a/g2core/device/sd_card/sd_persistence.h b/g2core/device/sd_card/sd_persistence.h new file mode 100644 index 00000000..12310b21 --- /dev/null +++ b/g2core/device/sd_card/sd_persistence.h @@ -0,0 +1,34 @@ +/* + * sd_persistence.h - persistence code for SD cards + * This file is part of the g2code project + * + * Copyright (c) 2019 Matt Staniszewski + * Copyright (c) 2019 Robert Giseburt + * + * 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 + * Free Software Foundation. You should have received a copy of the GNU General Public + * License, version 2 along with the software. If not, see . + * + * As a special exception, you may use this file as part of a software library without + * restriction. Specifically, if other files instantiate templates or use macros or + * inline functions from this file, or you compile this file and link it with other + * files to produce an executable, this file does not by itself cause the resulting + * executable to be covered by the GNU General Public License. This exception does not + * however invalidate any other reasons why the executable file might be covered by the + * GNU General Public License. + * + * THE SOFTWARE IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT WITHOUT ANY + * WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT + * SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * 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. + */ + +#ifndef SD_PERSISTENCE_H_ONCE +#define SD_PERSISTENCE_H_ONCE + +void setup_sd_persistence(); + +#endif // End of include guard: SD_PERSISTENCE_H_ONCE diff --git a/g2core/persistence.cpp b/g2core/persistence.cpp index b67b8449..4a8dbf49 100644 --- a/g2core/persistence.cpp +++ b/g2core/persistence.cpp @@ -26,77 +26,19 @@ */ #include "g2core.h" #include "persistence.h" -#include "canonical_machine.h" -#include "report.h" -#include "util.h" -#include "board_spi.h" -#include "ff.h" - -/*********************************************************************************** - **** STRUCTURE ALLOCATIONS ******************************************************** - ***********************************************************************************/ - -//**** persistence singleton **** - -struct nvmSingleton_t { - float tmp_value; - FATFS fat_fs; - FIL file; - uint8_t file_index; - alignas(4) uint8_t io_buffer[IO_BUFFER_SIZE]; - uint16_t changed_nvs; - uint32_t last_write_systick; - uint8_t write_failures; -} 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(); - -// 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 - -/*********************************************************************************** - **** CODE ************************************************************************* - ***********************************************************************************/ +Persistence *persistence = nullptr; void persistence_init() { - nvm.file_index = 0; - nvm.last_write_systick = Motate::SysTickTimer.getValue(); - nvm.write_failures = 0; - nvm.changed_nvs = 0; - return; + if (persistence == nullptr) { + return; + } + persistence->init(); } /* @@ -107,30 +49,11 @@ void persistence_init() 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); + if (persistence == nullptr) { + return (STAT_OK); // it worked! ;-) } - if (cfgArray[nv->index].flags & TYPE_INTEGER) { - nv->valuetype = TYPE_INTEGER; - nv->value_int = *(int32_t *)nvm.io_buffer; - DEBUG_PRINT("value (i) copied from address %l in file: %l\n", nv->index * NVM_VALUE_LEN, nv->value_int); - } else if (cfgArray[nv->index].flags & TYPE_BOOLEAN) { - nv->valuetype = TYPE_BOOLEAN; - nv->value_int = *(int32_t *)nvm.io_buffer; - DEBUG_PRINT("value (b) copied from address %l in file: %l\n", nv->index * NVM_VALUE_LEN, nv->value_int); - } else { - nv->valuetype = TYPE_FLOAT; - nv->value_flt = *(float *)nvm.io_buffer; - DEBUG_PRINT("value (f) copied from address %l in file: %f\n", nv->index * NVM_VALUE_LEN, nv->value_flt); - } - - return (STAT_OK); + return persistence->read(nv); } /* @@ -142,235 +65,23 @@ stat_t read_persistent_value(nvObj_t *nv) stat_t write_persistent_value(nvObj_t *nv) { - nvm.changed_nvs++; - return (STAT_OK); + if (persistence == nullptr) { + return (STAT_OK); // it worked! ;-) + } + + return persistence->write(nv); } /* * 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.changed_nvs > 0) { - if (Motate::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_type != CYCLE_NONE) { - return(STAT_NOOP); // can't write when machine is moving - } + if (persistence == nullptr) { + return (STAT_OK); // it worked! ;-) + } + + return persistence->periodic(); - if(write_persistent_values() == STAT_OK) { - nvm.changed_nvs = 0; - 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.changed_nvs = 0; // 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 = Motate::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 = -1; - 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; - nvObj_t *nv = nv_reset_nv_list(); // sets *nv to the start of the body - cmDistanceMode saved_distance_mode; - - // Save the current units mode - saved_distance_mode = (cmDistanceMode)cm_get_distance_mode(ACTIVE_MODEL); - - // 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((float)IO_BUFFER_SIZE, (float)((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 (; brindex = cnt; nv->index < (cnt + step); nv->index++) { - - // Found an entry that needs to be written - if (nv->index == 0 || cfgArray[nv->index].flags & F_PERSIST) { - nv_get_nvObj(nv); - - // Get the index for the current NVM value - index_t index = (nv->index - cnt) * NVM_VALUE_LEN; - - // Write out based on the value type - if (nv->valuetype == TYPE_INTEGER || nv->valuetype == TYPE_BOOLEAN) { - memcpy(nvm.io_buffer + index, &nv->value_int, NVM_VALUE_LEN); - DEBUG_PRINT("item index: %l , write index: %l (cnt: %l), value: %i\n", nv->index, index, cnt, nv->value_int); - } else if (nv->valuetype == TYPE_FLOAT) { - memcpy(nvm.io_buffer + index, &nv->value_flt, NVM_VALUE_LEN); - DEBUG_PRINT("item index: %l , write index: %l (cnt: %l), value: %f\n", nv->index, index, cnt, nv->value_flt); - } else { - // next - ignore strings and other stuff which shouldn't be set to persist anyway - } - } - } - - // 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; - } - - // Restore units mode - cm_set_distance_mode(saved_distance_mode); - - return (STAT_OK); } diff --git a/g2core/persistence.h b/g2core/persistence.h index 6ac332ad..aa2b79e0 100644 --- a/g2core/persistence.h +++ b/g2core/persistence.h @@ -29,8 +29,6 @@ #define PERSISTENCE_H_ONCE #include "config.h" // needed for nvObj_t definition -// #include "ff.h" -// #include "util.h" // FIXME: this won't compile if included after #define NVM_VALUE_LEN 4 // NVM value length (float, fixed length) #define NVM_BASE_ADDR 0x0000 // base address of usable NVM @@ -42,6 +40,16 @@ //**** persistence function prototypes **** +class Persistence { + public: + virtual void init(); + virtual stat_t read(nvObj_t *nv); + virtual stat_t write(nvObj_t *nv); + virtual stat_t periodic(); +}; + +extern Persistence *persistence; + void persistence_init(void); stat_t read_persistent_value(nvObj_t *nv); stat_t write_persistent_value(nvObj_t *nv);