mirror of
https://github.com/synthetos/g2.git
synced 2026-02-05 10:39:53 +08:00
Cleanup SD card persistence somewhat
This commit is contained in:
@@ -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<Motate::kSD_ChipSelectPinNumber> sdcs{};
|
||||
SDCard_used_t sd_card{spiBus, sdcs};
|
||||
|
||||
|
||||
void hardware_init()
|
||||
{
|
||||
spiBus.init();
|
||||
sd_card.init();
|
||||
setup_sd_persistence();
|
||||
board_hardware_init();
|
||||
return;
|
||||
}
|
||||
|
||||
403
g2core/device/sd_card/sd_persistence.cpp
Normal file
403
g2core/device/sd_card/sd_persistence.cpp
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 (; 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 changes indexes in the current range
|
||||
for (nv->index = 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);
|
||||
}
|
||||
34
g2core/device/sd_card/sd_persistence.h
Normal file
34
g2core/device/sd_card/sd_persistence.h
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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
|
||||
@@ -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 (; 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 changes indexes in the current range
|
||||
for (nv->index = 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);
|
||||
}
|
||||
|
||||
@@ -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 <map>
|
||||
|
||||
#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);
|
||||
|
||||
Reference in New Issue
Block a user