Files
grblHAL/nvs_buffer.c
Terje Io 720f6bd552 Workaround for POS (Power on self-test) failure messages(s) not output on "native" USB connect.
Fixed handling of NVS buffer allocation, size was not increased as expected when physical NVS is capable of holding more than 2 Kbytes.
This could lead to POS failure or plugins not initializing when a large tool table is configured.
2025-10-23 10:40:01 +02:00

467 lines
16 KiB
C

/*
nvs_buffer.c - RAM based non-volatile storage buffer/emulation
Part of grblHAL
Copyright (c) 2017-2025 Terje Io
Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC
Copyright (c) 2009-2011 Simen Svale Skogsrud
grblHAL is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
grblHAL is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License.
along with grblHAL. If not, see <http://www.gnu.org/licenses/>.
*/
//
// Can be used by MCUs with no nonvolatile storage options, be sure to allocate enough heap memory before use
//
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include "hal.h"
#include "nvs_buffer.h"
#include "protocol.h"
#include "settings.h"
#include "gcode.h"
#include "crc.h"
#include "nvs.h"
settings_dirty_t settings_dirty;
static nvs_io_t physical_nvs;
static bool dirty;
static struct {
uint8_t *addr;
uint32_t size;
} nvsbuffer = { .size = NVS_SIZE };
typedef struct {
uint16_t addr;
uint8_t type;
uint8_t offset;
} emap_t;
#define NVS_GROUP_GLOBAL 0
#define NVS_GROUP_TOOLS 1
#define NVS_GROUP_PARAMETERS 2
#define NVS_GROUP_STARTUP 3
#define NVS_GROUP_BUILD 4
#define PARAMETER_ADDR(n) (NVS_ADDR_PARAMETERS + n * (sizeof(coord_data_t) + NVS_CRC_BYTES))
#define STARTLINE_ADDR(n) (NVS_ADDR_STARTUP_BLOCK + n * (sizeof(stored_line_t) + NVS_CRC_BYTES))
#if N_TOOLS
#define TOOL_ADDR(n) (NVS_ADDR_TOOL_TABLE + n * (sizeof(tool_data_t) + NVS_CRC_BYTES))
#endif
static const emap_t target[] = {
{NVS_ADDR_GLOBAL, NVS_GROUP_GLOBAL, 0},
{PARAMETER_ADDR(0), NVS_GROUP_PARAMETERS, 0},
{PARAMETER_ADDR(1), NVS_GROUP_PARAMETERS, 1},
{PARAMETER_ADDR(2), NVS_GROUP_PARAMETERS, 2},
{PARAMETER_ADDR(3), NVS_GROUP_PARAMETERS, 3},
{PARAMETER_ADDR(4), NVS_GROUP_PARAMETERS, 4},
{PARAMETER_ADDR(5), NVS_GROUP_PARAMETERS, 5},
{PARAMETER_ADDR(6), NVS_GROUP_PARAMETERS, 6},
{PARAMETER_ADDR(7), NVS_GROUP_PARAMETERS, 7},
{PARAMETER_ADDR(8), NVS_GROUP_PARAMETERS, 8},
{PARAMETER_ADDR(9), NVS_GROUP_PARAMETERS, 9},
{PARAMETER_ADDR(10), NVS_GROUP_PARAMETERS, 10},
{PARAMETER_ADDR(11), NVS_GROUP_PARAMETERS, 11},
{STARTLINE_ADDR(0), NVS_GROUP_STARTUP, 0},
{STARTLINE_ADDR(1), NVS_GROUP_STARTUP, 1},
#if N_STARTUP_LINE > 2
#error Increase number of startup line entries!
#endif
{NVS_ADDR_BUILD_INFO, NVS_GROUP_BUILD, 0},
#if N_TOOLS
{TOOL_ADDR(0), NVS_GROUP_TOOLS, 0},
{TOOL_ADDR(1), NVS_GROUP_TOOLS, 1},
{TOOL_ADDR(2), NVS_GROUP_TOOLS, 2},
{TOOL_ADDR(3), NVS_GROUP_TOOLS, 3},
{TOOL_ADDR(4), NVS_GROUP_TOOLS, 4},
{TOOL_ADDR(5), NVS_GROUP_TOOLS, 5},
{TOOL_ADDR(6), NVS_GROUP_TOOLS, 6},
{TOOL_ADDR(7), NVS_GROUP_TOOLS, 7},
#if N_TOOLS > 8
{TOOL_ADDR(8), NVS_GROUP_TOOLS, 8},
{TOOL_ADDR(9), NVS_GROUP_TOOLS, 9},
{TOOL_ADDR(10), NVS_GROUP_TOOLS, 10},
{TOOL_ADDR(11), NVS_GROUP_TOOLS, 11},
{TOOL_ADDR(12), NVS_GROUP_TOOLS, 12},
{TOOL_ADDR(13), NVS_GROUP_TOOLS, 13},
{TOOL_ADDR(14), NVS_GROUP_TOOLS, 14},
{TOOL_ADDR(15), NVS_GROUP_TOOLS, 15},
#endif
#if N_TOOLS > 16
{TOOL_ADDR(16), NVS_GROUP_TOOLS, 16},
{TOOL_ADDR(17), NVS_GROUP_TOOLS, 17},
{TOOL_ADDR(18), NVS_GROUP_TOOLS, 18},
{TOOL_ADDR(19), NVS_GROUP_TOOLS, 19},
{TOOL_ADDR(20), NVS_GROUP_TOOLS, 20},
{TOOL_ADDR(21), NVS_GROUP_TOOLS, 21},
{TOOL_ADDR(22), NVS_GROUP_TOOLS, 22},
{TOOL_ADDR(23), NVS_GROUP_TOOLS, 23},
{TOOL_ADDR(24), NVS_GROUP_TOOLS, 24},
{TOOL_ADDR(25), NVS_GROUP_TOOLS, 25},
{TOOL_ADDR(26), NVS_GROUP_TOOLS, 26},
{TOOL_ADDR(27), NVS_GROUP_TOOLS, 27},
{TOOL_ADDR(28), NVS_GROUP_TOOLS, 28},
{TOOL_ADDR(29), NVS_GROUP_TOOLS, 29},
{TOOL_ADDR(30), NVS_GROUP_TOOLS, 30},
{TOOL_ADDR(31), NVS_GROUP_TOOLS, 31},
#endif
#endif
{0, 0, 0} // list termination - do not remove
};
inline static uint8_t ram_get_byte (uint32_t offset)
{
return nvsbuffer.addr[offset];
}
inline static void ram_put_byte (uint32_t offset, uint8_t new_value)
{
if(offset == 0)
settings_dirty.version = true;
dirty = dirty || nvsbuffer.addr[offset] != new_value || offset == 0;
nvsbuffer.addr[offset] = new_value;
}
static nvs_transfer_result_t memcpy_to_ram (uint32_t destination, uint8_t *source, uint32_t size, bool with_checksum)
{
if(hal.nvs.driver_area.address && destination > hal.nvs.driver_area.address + hal.nvs.driver_area.size)
return physical_nvs.memcpy_to_nvs(destination, source, size, with_checksum);
uint32_t dest = destination;
uint16_t checksum = with_checksum ? calc_checksum(source, size) : 0;
dirty = false;
for(; size > 0; size--)
ram_put_byte(dest++, *(source++));
if(with_checksum) {
ram_put_byte(dest, checksum & 0xFF);
#if NVS_CRC_BYTES > 1
ram_put_byte(++dest, checksum >> 8);
#endif
}
if(settings_dirty.version || source == hal.nvs.driver_area.mem_address)
dirty = true;
if(dirty && physical_nvs.type != NVS_None) {
uint8_t idx = 0;
settings_dirty.is_dirty = true;
if(hal.nvs.driver_area.address && destination >= hal.nvs.driver_area.address)
settings_dirty.driver_settings = true;
else {
do {
if(target[idx].addr == destination)
break;
} while(target[++idx].addr);
if(target[idx].addr) switch(target[idx].type) {
case NVS_GROUP_GLOBAL:
settings_dirty.global_settings = true;
break;
#if N_TOOLS
case NVS_GROUP_TOOLS:
settings_dirty.tool_data |= (1 << target[idx].offset);
break;
#endif
case NVS_GROUP_PARAMETERS:
settings_dirty.coord_data |= (1 << target[idx].offset);
break;
case NVS_GROUP_STARTUP:
settings_dirty.startup_lines |= (1 << target[idx].offset);
break;
case NVS_GROUP_BUILD:
settings_dirty.build_info = true;
break;
}
}
}
return NVS_TransferResult_OK;
}
static nvs_transfer_result_t memcpy_from_ram (uint8_t *destination, uint32_t source, uint32_t size, bool with_checksum)
{
if(hal.nvs.driver_area.address && source > hal.nvs.driver_area.address + hal.nvs.driver_area.size)
return physical_nvs.memcpy_from_nvs(destination, source, size, with_checksum);
uint16_t checksum = with_checksum ? calc_checksum(&nvsbuffer.addr[source], size) : 0;
for(; size > 0; size--)
*(destination++) = ram_get_byte(source++);
#if NVS_CRC_BYTES == 1
return !with_checksum || checksum == ram_get_byte(source);
#else
return !with_checksum || checksum == (ram_get_byte(source) | (ram_get_byte(source + 1) << 8));
#endif
}
// Try to allocate RAM from heap for buffer/emulation.
bool nvs_buffer_alloc (void)
{
if(nvsbuffer.addr == NULL) {
if(hal.nvs.size_max > NVS_SIZE)
nvsbuffer.size = min(NVS_SIZE_MAX, hal.nvs.size_max);
if(nvsbuffer.size >= GRBL_NVS_SIZE && (nvsbuffer.addr = malloc(nvsbuffer.size)))
memset(nvsbuffer.addr, 0xFF, nvsbuffer.size);
}
return nvsbuffer.addr != NULL;
}
void nvs_buffer_free (void)
{
if(nvsbuffer.addr) {
nvs_buffer_sync_physical();
free(nvsbuffer.addr);
}
}
//
// Switch over to RAM based copy.
// Changes to RAM based copy will be written to physical storage when grblHAL is in IDLE state.
bool nvs_buffer_init (void)
{
hal.nvs.size = ((hal.nvs.size - 1) | 0x03) + 1; // Ensure NVS area ends on a word boundary
if(nvsbuffer.addr) {
memcpy(&physical_nvs, &hal.nvs, sizeof(nvs_io_t)); // save pointers to physical storage handler functions
// Copy physical storage content to RAM when available
if(physical_nvs.type == NVS_Flash)
physical_nvs.memcpy_from_flash(nvsbuffer.addr);
else if(physical_nvs.type != NVS_None)
physical_nvs.memcpy_from_nvs(nvsbuffer.addr, 0, GRBL_NVS_SIZE + hal.nvs.driver_area.size, false);
// Switch hal to use RAM version of non-volatile storage data
hal.nvs.type = NVS_Emulated;
hal.nvs.get_byte = &ram_get_byte;
hal.nvs.put_byte = &ram_put_byte;
hal.nvs.memcpy_to_nvs = &memcpy_to_ram;
hal.nvs.memcpy_from_nvs = &memcpy_from_ram;
hal.nvs.memcpy_from_flash = NULL;
hal.nvs.memcpy_to_flash = NULL;
// If no physical storage available or if NVS import fails copy default settings to RAM
// and write out to physical storage when available.
if(physical_nvs.type == NVS_None || ram_get_byte(0) != SETTINGS_VERSION) {
settings_restore(settings_all);
if(physical_nvs.type == NVS_Flash)
physical_nvs.memcpy_to_flash(nvsbuffer.addr);
else if(physical_nvs.memcpy_to_nvs)
physical_nvs.memcpy_to_nvs(0, nvsbuffer.addr, GRBL_NVS_SIZE + hal.nvs.driver_area.size, false);
if(physical_nvs.type != NVS_None)
grbl.report.status_message(Status_SettingReadFail);
}
} else
task_run_on_startup(report_warning, "Not enough heap for NVS buffer!");
// Clear settings dirty flags
memset(&settings_dirty, 0, sizeof(settings_dirty_t));
return nvsbuffer.addr != NULL;
}
// Allocate NVS block for driver settings.
// NOTE: allocation has to be done before content is copied from physical storage.
nvs_address_t nvs_alloc (size_t size)
{
static uint8_t *mem_address;
uint8_t *start_address;
nvs_address_t addr = 0;
// Check if already switched to emulation or buffer allocation failed, return NULL if so.
if(hal.nvs.type == NVS_Emulated || (nvsbuffer.addr == NULL && !nvs_buffer_alloc()))
return 0;
if(hal.nvs.driver_area.address == 0) {
hal.nvs.driver_area.address = GRBL_NVS_SIZE;
hal.nvs.driver_area.mem_address = mem_address = nvsbuffer.addr + hal.nvs.driver_area.address;
}
size += NVS_CRC_BYTES; // add room for checksum.
start_address = (uint8_t *)((uintptr_t)(mem_address - 1) | 0x03) + 1; // Align to word boundary
if(start_address + size < nvsbuffer.addr + nvsbuffer.size) {
addr = start_address - nvsbuffer.addr;
mem_address = start_address + size;
hal.nvs.driver_area.size = mem_address - hal.nvs.driver_area.mem_address;
hal.nvs.size = GRBL_NVS_SIZE + hal.nvs.driver_area.size + 1;
}
return addr;
}
// Write RAM changes to physical storage
void nvs_buffer_sync_physical (void)
{
if(!settings_dirty.is_dirty)
return;
if(physical_nvs.memcpy_to_nvs) {
if(settings_dirty.version)
settings_dirty.version = physical_nvs.memcpy_to_nvs(0, nvsbuffer.addr, 1, false) != NVS_TransferResult_OK;
if(settings_dirty.global_settings)
settings_dirty.global_settings = physical_nvs.memcpy_to_nvs(NVS_ADDR_GLOBAL, (uint8_t *)(nvsbuffer.addr + NVS_ADDR_GLOBAL), sizeof(settings_t) + NVS_CRC_BYTES, false) != NVS_TransferResult_OK;
if(settings_dirty.build_info)
settings_dirty.build_info = physical_nvs.memcpy_to_nvs(NVS_ADDR_BUILD_INFO, (uint8_t *)(nvsbuffer.addr + NVS_ADDR_BUILD_INFO), sizeof(stored_line_t) + NVS_CRC_BYTES, false) != NVS_TransferResult_OK;
uint_fast8_t idx = N_STARTUP_LINE, offset;
if(settings_dirty.startup_lines) do {
idx--;
if(bit_istrue(settings_dirty.startup_lines, bit(idx))) {
bit_false(settings_dirty.startup_lines, bit(idx));
offset = NVS_ADDR_STARTUP_BLOCK + idx * (sizeof(stored_line_t) + NVS_CRC_BYTES);
if(physical_nvs.memcpy_to_nvs(offset, (uint8_t *)(nvsbuffer.addr + offset), sizeof(stored_line_t) + NVS_CRC_BYTES, false) == NVS_TransferResult_OK)
bit_false(settings_dirty.startup_lines, bit(idx));
}
} while(idx);
idx = N_CoordinateSystems;
if(settings_dirty.coord_data) do {
if(bit_istrue(settings_dirty.coord_data, bit(idx))) {
offset = NVS_ADDR_PARAMETERS + idx * (sizeof(coord_data_t) + NVS_CRC_BYTES);
if(physical_nvs.memcpy_to_nvs(offset, (uint8_t *)(nvsbuffer.addr + offset), sizeof(coord_data_t) + NVS_CRC_BYTES, false) == NVS_TransferResult_OK)
bit_false(settings_dirty.coord_data, bit(idx));
}
} while(idx--);
if(settings_dirty.driver_settings) {
if(hal.nvs.driver_area.size > 0)
settings_dirty.driver_settings = physical_nvs.memcpy_to_nvs(hal.nvs.driver_area.address, (uint8_t *)(nvsbuffer.addr + hal.nvs.driver_area.address), hal.nvs.driver_area.size, false) != NVS_TransferResult_OK;
else
settings_dirty.driver_settings = false;
}
#if N_TOOLS
idx = N_TOOLS;
if(settings_dirty.tool_data) do {
idx--;
if(bit_istrue(settings_dirty.tool_data, bit(idx))) {
offset = NVS_ADDR_TOOL_TABLE + idx * (sizeof(tool_data_t) + NVS_CRC_BYTES);
if(physical_nvs.memcpy_to_nvs(offset, (uint8_t *)(nvsbuffer.addr + offset), sizeof(tool_data_t) + NVS_CRC_BYTES, false) == NVS_TransferResult_OK)
bit_false(settings_dirty.tool_data, bit(idx));
}
} while(idx);
#endif
settings_dirty.is_dirty = settings_dirty.coord_data ||
settings_dirty.global_settings ||
settings_dirty.driver_settings ||
settings_dirty.startup_lines ||
#if N_TOOLS
settings_dirty.tool_data ||
#endif
settings_dirty.build_info;
} else if(physical_nvs.memcpy_to_flash) {
uint_fast8_t retries = 4;
do {
if(physical_nvs.memcpy_to_flash(nvsbuffer.addr))
retries = 0;
else if(--retries == 0)
report_message("Settings write failed!", Message_Warning);
} while(retries);
memset(&settings_dirty, 0, sizeof(settings_dirty_t));
}
}
nvs_io_t *nvs_buffer_get_physical (void)
{
return hal.nvs.type == NVS_Emulated ? &physical_nvs : &hal.nvs;
}
#ifdef DEBUG
#include "report.h"
void nvs_memmap (void)
{
char buf[30];
report_message("NVS Area: addr size end", Message_Plain);
strcpy(buf, "Global: ");
strcat(buf, uitoa(NVS_ADDR_GLOBAL));
strcat(buf, " ");
strcat(buf, uitoa(sizeof(settings_t) + NVS_CRC_BYTES));
strcat(buf, " ");
strcat(buf, uitoa(NVS_ADDR_GLOBAL + sizeof(settings_t) + NVS_CRC_BYTES));
report_message(buf, Message_Plain);
strcpy(buf, "Parameters: ");
strcat(buf, uitoa(NVS_ADDR_PARAMETERS));
strcat(buf, " ");
strcat(buf, uitoa(NVS_SIZE_PARAMETERS));
strcat(buf, " ");
strcat(buf, uitoa(NVS_ADDR_PARAMETERS + N_CoordinateSystems * (sizeof(coord_data_t) + NVS_CRC_BYTES)));
report_message(buf, Message_Plain);
strcpy(buf, "Startup block: ");
strcat(buf, uitoa(NVS_ADDR_STARTUP_BLOCK));
strcat(buf, " ");
strcat(buf, uitoa(NVS_SIZE_STARTUP_BLOCK));
strcat(buf, " ");
strcat(buf, uitoa(NVS_ADDR_STARTUP_BLOCK + N_STARTUP_LINE * (sizeof(stored_line_t) + NVS_CRC_BYTES)));
report_message(buf, Message_Plain);
strcpy(buf, "Build info: ");
strcat(buf, uitoa(NVS_ADDR_BUILD_INFO));
strcat(buf, " ");
strcat(buf, uitoa(NVS_SIZE_BUILD_INFO));
strcat(buf, " ");
strcat(buf, uitoa(NVS_ADDR_BUILD_INFO + sizeof(stored_line_t) + NVS_CRC_BYTES));
report_message(buf, Message_Plain);
#if N_TOOLS
strcpy(buf, "Tool table: ");
strcat(buf, uitoa(NVS_ADDR_TOOL_TABLE));
strcat(buf, " ");
strcat(buf, uitoa(N_TOOLS * (sizeof(tool_data_t) + NVS_CRC_BYTES)));
strcat(buf, " ");
strcat(buf, uitoa(NVS_ADDR_TOOL_TABLE + N_TOOLS * (sizeof(tool_data_t) + NVS_CRC_BYTES)));
report_message(buf, Message_Plain);
#endif
strcpy(buf, "Driver: ");
strcat(buf, uitoa(hal.nvs.driver_area.address));
strcat(buf, " ");
strcat(buf, uitoa(hal.nvs.driver_area.size));
strcat(buf, " ");
strcat(buf, uitoa(hal.nvs.driver_area.address + hal.nvs.driver_area.size));
report_message(buf, Message_Plain);
}
#endif