reflow-oven-control-sw/stm-firmware/safety/safety-memory.c

698 lines
16 KiB
C

/* Reflow Oven Controller
*
* Copyright (C) 2020 Mario Hüttel <mario.huettel@gmx.net>
*
* This file is part of the Reflow Oven Controller Project.
*
* The reflow oven controller 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.
*
* The Reflow Oven Control Firmware 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 the reflow oven controller project.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <reflow-controller/safety/safety-memory.h>
#include <helper-macros/helper-macros.h>
#include <stm-periph/crc-unit.h>
#include <stm-periph/backup-ram.h>
#include <string.h>
#ifndef SAFETY_MEMORY_STRIPOUT_DUMP
#include <base64-lib/base64-lib.h>
#endif /* SAFETY_MEMORY_STRIPOUT_DUMP */
static int word_to_error_memory_entry(uint32_t entry_data, struct error_memory_entry *out)
{
int ret = 0;
if (!out)
return -1002;
if (entry_data == SAFETY_MEMORY_NOP_ENTRY_WORD) {
out->flag_num = 0U;
out->type = SAFETY_MEMORY_ERR_ENTRY_NOP;
out->counter = 0U;
} else if ((entry_data & 0xFFU) == SAFETY_MEMORY_ERROR_ENTRY_MARKER) {
out->flag_num = (uint8_t)((entry_data >> 8U) & 0xFFU);
out->type = SAFETY_MEMORY_ERR_ENTRY_FLAG;
out->counter = (uint16_t)((entry_data >> 16U) & 0xFFFF);
} else {
/* Invalid entry */
ret = -1;
}
return ret;
}
static uint32_t error_memory_entry_to_word(const struct error_memory_entry *entry)
{
uint32_t word = 0;
switch (entry->type) {
case SAFETY_MEMORY_ERR_ENTRY_NOP:
word = SAFETY_MEMORY_NOP_ENTRY_WORD;
break;
case SAFETY_MEMORY_ERR_ENTRY_FLAG:
word = (uint32_t)SAFETY_MEMORY_ERROR_ENTRY_MARKER | ((uint32_t)entry->flag_num << 8U) |
((uint32_t)entry->counter << 16U);
break;
}
return word;
}
static enum safety_memory_state safety_memory_get_header(struct safety_memory_header *header)
{
int res;
enum safety_memory_state ret;
if (!header)
return SAFETY_MEMORY_INIT_CORRUPTED;
res = backup_ram_get_data(0UL, (uint32_t *)header, wordsize_of(struct safety_memory_header));
if (res)
return SAFETY_MEMORY_INIT_CORRUPTED;
/* Check magic */
if (header->magic != SAFETY_MEMORY_MAGIC) {
/* Magic invalid */
ret = SAFETY_MEMORY_INIT_FRESH;
goto return_val;
}
/* Check the header crc */
crc_unit_reset();
crc_unit_input_array((uint32_t *)header, wordsize_of(struct safety_memory_header));
if (crc_unit_get_crc() != 0UL) {
ret = SAFETY_MEMORY_INIT_CORRUPTED;
goto return_val;
}
res = 0;
if (header->boot_status_offset < wordsize_of(struct safety_memory_header))
res++;
if (header->config_overrides_offset < header->boot_status_offset +
wordsize_of(struct safety_memory_boot_status))
res++;
if (header->config_overrides_len > SAFETY_MEMORY_CONFIG_OVERRIDE_COUNT)
res++;
if (header->firmware_update_filename < header->config_overrides_offset + header->config_overrides_len)
res++;
if (header->err_memory_offset < header->firmware_update_filename + (SAFETY_MEMORY_UPDATE_FILENAME_MAXSIZE / 4))
res++;
if (header->err_memory_end >= backup_ram_get_size_in_words() ||
header->err_memory_end < header->err_memory_offset)
res++;
if (res) {
/* Error detected: Write new header */
ret = SAFETY_MEMORY_INIT_CORRUPTED;
} else {
ret = SAFETY_MEMORY_INIT_VALID_MEMORY;
}
return_val:
return ret;
}
static void safety_memory_write_and_patch_header(struct safety_memory_header *header)
{
/* Patch the CRC */
crc_unit_reset();
crc_unit_input_array((uint32_t *)header, wordsize_of(struct safety_memory_header) - 1U);
header->crc = crc_unit_get_crc();
/* Write to memory */
backup_ram_write_data(0UL, (uint32_t *)header, wordsize_of(*header));
}
static void safety_memory_write_new_header(void)
{
struct safety_memory_header header;
header.boot_status_offset = wordsize_of(struct safety_memory_header);
header.config_overrides_len = SAFETY_MEMORY_CONFIG_OVERRIDE_COUNT;
header.config_overrides_offset = header.boot_status_offset + wordsize_of(struct safety_memory_boot_status);
header.firmware_update_filename = header.config_overrides_offset + SAFETY_MEMORY_CONFIG_OVERRIDE_COUNT;
header.err_memory_offset = header.firmware_update_filename + (SAFETY_MEMORY_UPDATE_FILENAME_MAXSIZE / 4);
header.err_memory_end = header.err_memory_offset;
header.magic = SAFETY_MEMORY_MAGIC;
backup_ram_wipe();
safety_memory_write_and_patch_header(&header);
}
static int safety_memory_check_crc(void)
{
struct safety_memory_header header;
enum safety_memory_state state = safety_memory_get_header(&header);
uint32_t crc_offset;
uint32_t data;
uint32_t addr;
int res;
if (state != SAFETY_MEMORY_INIT_VALID_MEMORY)
return -1;
crc_offset = header.err_memory_end;
crc_unit_reset();
for (addr = 0; addr < crc_offset; addr++) {
res = backup_ram_get_data(addr, &data, 1UL);
if (res)
return -2000;
crc_unit_input(data);
}
res = backup_ram_get_data(crc_offset, &data, 1UL);
if (res)
return -2001;
if (crc_unit_get_crc() != data)
return -3000;
else
return 0;
}
static int safety_memory_gen_crc(void)
{
struct safety_memory_header header;
uint32_t word_addr;
uint32_t data;
int res;
if (safety_memory_get_header(&header) != SAFETY_MEMORY_INIT_VALID_MEMORY)
return -1;
crc_unit_reset();
for (word_addr = 0; word_addr < header.err_memory_end; word_addr++) {
res = backup_ram_get_data(word_addr, &data, 1);
if (res)
return -2;
crc_unit_input(data);
}
/* Write CRC */
data = crc_unit_get_crc();
res = backup_ram_write_data(header.err_memory_end, &data, 1UL);
if (res)
return -3;
return 0;
}
int safety_memory_reinit(enum safety_memory_state *found_state)
{
struct safety_memory_header header;
int res;
int ret = -1;
if (!found_state)
return -1001;
*found_state = safety_memory_get_header(&header);
switch (*found_state) {
case SAFETY_MEMORY_INIT_VALID_MEMORY:
/* Valid memory detected. Check CRC and error entries */
res = safety_memory_check();
if (res)
*found_state = SAFETY_MEMORY_INIT_CORRUPTED;
break;
case SAFETY_MEMORY_INIT_FRESH:
break;
case SAFETY_MEMORY_INIT_CORRUPTED:
break;
default:
*found_state = SAFETY_MEMORY_INIT_CORRUPTED;
break;
}
/* Check if memory header has to be written */
if (*found_state != SAFETY_MEMORY_INIT_VALID_MEMORY) {
safety_memory_write_new_header();
/* If yes, generate new CRC checksum */
res = safety_memory_gen_crc();
if (res)
ret = -100;
else
ret = 0;
} else {
ret = 0;
}
return ret;
}
int safety_memory_init(enum safety_memory_state *found_state)
{
crc_unit_init();
backup_ram_init(true);
return safety_memory_reinit(found_state);
}
int safety_memory_get_boot_status(struct safety_memory_boot_status *status)
{
struct safety_memory_header header;
int res;
if (!status)
return -1001;
if (safety_memory_get_header(&header) != SAFETY_MEMORY_INIT_VALID_MEMORY)
return -2000;
if (safety_memory_check_crc())
return -2001;
res = backup_ram_get_data(header.boot_status_offset, (uint32_t *)status, wordsize_of(*status));
if (res)
return -3000;
return 0;
}
int safety_memory_set_boot_status(const struct safety_memory_boot_status *status)
{
struct safety_memory_header header;
int res;
if (!status)
return -1001;
if (safety_memory_get_header(&header) != SAFETY_MEMORY_INIT_VALID_MEMORY)
return -2000;
if (safety_memory_check_crc())
return -2001;
res = backup_ram_write_data(header.boot_status_offset, (uint32_t *)status, wordsize_of(*status));
res |= safety_memory_gen_crc();
if (res)
return -3000;
return 0;
}
static int safety_memory_check_error_entries(void)
{
struct safety_memory_header header;
uint32_t addr;
uint32_t data;
int ret = 0;
int res;
if (safety_memory_get_header(&header) != SAFETY_MEMORY_INIT_VALID_MEMORY)
return -2000;
for (addr = header.err_memory_offset; addr < header.err_memory_end; addr++) {
res = backup_ram_get_data(addr, &data, 1UL);
if (res)
return -100;
/* Valid flag entry */
if ((data & 0xFF) == SAFETY_MEMORY_ERROR_ENTRY_MARKER)
continue;
if (data == SAFETY_MEMORY_NOP_ENTRY_WORD)
continue;
ret--;
}
return ret;
}
int safety_memory_get_error_entry_count(uint32_t *count)
{
struct safety_memory_header header;
if (!count)
return -1001;
if (safety_memory_get_header(&header) != SAFETY_MEMORY_INIT_VALID_MEMORY)
return -2000;
*count = header.err_memory_end - header.err_memory_offset;
return 0;
}
int safety_memory_check(void)
{
int res;
res = safety_memory_check_crc();
if (!res)
res |= safety_memory_check_error_entries();
return -!!res;
}
int safety_memory_get_error_entry(uint32_t idx, struct error_memory_entry *entry)
{
struct safety_memory_header header;
uint32_t err_mem_count;
int ret = -1;
int res;
uint32_t data;
if (!entry)
return -1001;
if (safety_memory_get_header(&header) != SAFETY_MEMORY_INIT_VALID_MEMORY)
return -2000;
err_mem_count = header.err_memory_end - header.err_memory_offset;
if (idx < err_mem_count && err_mem_count > 0) {
res = backup_ram_get_data(header.err_memory_offset + idx, &data, 1UL);
if (res)
goto return_value;
res = word_to_error_memory_entry(data, entry);
if (res)
goto return_value;
ret = 0;
} else {
/* out of range */
ret = -1001;
}
return_value:
return ret;
}
int safety_memory_insert_error_entry(struct error_memory_entry *entry)
{
int res;
int ret = -0xFFFF;
uint32_t addr;
uint32_t data;
bool found;
uint32_t input_data;
struct error_memory_entry current_entry;
struct safety_memory_header header;
input_data = error_memory_entry_to_word(entry);
if (safety_memory_get_header(&header) != SAFETY_MEMORY_INIT_VALID_MEMORY)
return -2000;
if (entry->type == SAFETY_MEMORY_ERR_ENTRY_NOP) {
/* Append to end */
if ((header.err_memory_end + 1U) < backup_ram_get_size_in_words()) {
/* Still fits in memory */
backup_ram_write_data(header.err_memory_end, &input_data, 1UL);
header.err_memory_end++;
safety_memory_write_and_patch_header(&header);
safety_memory_gen_crc();
ret = 0;
}
} else if (entry->type == SAFETY_MEMORY_ERR_ENTRY_FLAG) {
found = false;
for (addr = header.err_memory_offset; addr < header.err_memory_end; addr++) {
res = backup_ram_get_data(addr, &data, 1UL);
if (res) {
ret = -1;
goto return_value;
}
res = word_to_error_memory_entry(data, &current_entry);
if (res) {
ret = -2;
goto return_value;
}
if (current_entry.type == SAFETY_MEMORY_ERR_ENTRY_FLAG &&
current_entry.flag_num == entry->flag_num) {
found = true;
break;
}
if (current_entry.type == SAFETY_MEMORY_ERR_ENTRY_NOP) {
found = true;
break;
}
}
if (!found) {
/* No suitable place found in memory. Append */
if ((addr + 1) < backup_ram_get_size_in_words()) {
backup_ram_write_data(addr, &input_data, 1UL);
header.err_memory_end++;
safety_memory_write_and_patch_header(&header);
} else {
ret = -3;
goto return_value;
}
} else {
if (current_entry.type == SAFETY_MEMORY_ERR_ENTRY_NOP) {
backup_ram_write_data(addr, &input_data, 1UL);
} else {
current_entry.counter += entry->counter;
if (current_entry.counter < entry->counter)
current_entry.counter = 0xFFFF;
data = error_memory_entry_to_word(&current_entry);
backup_ram_write_data(addr, &data, 1UL);
}
}
safety_memory_gen_crc();
ret = 0;
} else {
ret = -1001;
}
return_value:
return ret;
}
static uint32_t convert_config_override_to_word(const struct config_override *conf_override)
{
uint32_t data = 0;
if (conf_override->type == SAFETY_MEMORY_CONFIG_OVERRIDE_WEIGHT) {
data |= 0xAA0000A2UL;
data |= ((uint32_t)conf_override->entry.weight_override.flag) << 16;
data |= ((uint32_t)conf_override->entry.weight_override.weight) << 8;
} else if (conf_override->type == SAFETY_MEMORY_CONFIG_OVERRIDE_PERSISTENCE) {
data |= 0xBB00008EUL;
data |= ((uint32_t)conf_override->entry.persistence_override.flag) << 16;
data |= ((uint32_t)(conf_override->entry.persistence_override.persistence ? 1UL : 0UL)) << 8;
}
return data;
}
int safety_memory_insert_config_override(struct config_override *config_override)
{
struct safety_memory_header header;
uint32_t idx;
uint32_t data;
int res;
int ret = -3;
if (safety_memory_get_header(&header) != SAFETY_MEMORY_INIT_VALID_MEMORY)
return -2000;
if (header.config_overrides_len == 0)
return -1;
for (idx = 0; idx < header.config_overrides_len; idx++) {
res = backup_ram_get_data(header.config_overrides_offset + idx, &data, 1UL);
if (res)
return -2;
if (data == 0UL) {
data = convert_config_override_to_word(config_override);
res = backup_ram_write_data(header.config_overrides_offset + idx, &data, 1UL);
if (res)
return -4;
res = safety_memory_gen_crc();
if (res)
return -5;
ret = 0;
break;
}
}
return ret;
}
int safety_memory_get_config_override_count(uint32_t *count)
{
struct safety_memory_header header;
uint32_t iter;
uint32_t valid_count;
uint32_t data;
int res;
if (!count)
return -1001;
*count = 0UL;
if (safety_memory_get_header(&header) != SAFETY_MEMORY_INIT_VALID_MEMORY)
return -2000;
if (header.config_overrides_len == 0)
return 0;
valid_count = 0;
for (iter = 0; iter < header.config_overrides_len; iter++) {
res = backup_ram_get_data(header.config_overrides_offset + iter, &data, 1UL);
if (res)
return -2;
if (data != 0)
valid_count++;
else
break;
}
*count = valid_count;
return 0;
}
int safety_memory_get_config_override(uint32_t idx, struct config_override *config_override)
{
struct safety_memory_header header;
uint32_t data;
int res;
if (!config_override)
return -1002;
if (safety_memory_get_header(&header) != SAFETY_MEMORY_INIT_VALID_MEMORY)
return -2000;
if (idx >= header.config_overrides_len)
return -1001;
res = backup_ram_get_data(header.config_overrides_offset + idx, &data, 1UL);
if (res)
return -1;
switch (data & 0xFF) {
case 0xA2:
/* Weight override */
config_override->type = SAFETY_MEMORY_CONFIG_OVERRIDE_WEIGHT;
config_override->entry.weight_override.flag = (data & 0xFF0000UL) >> 16;
config_override->entry.weight_override.weight = (data & 0xFF00UL) >> 8;
break;
case 0x8E:
/* persistence override */
config_override->type = SAFETY_MEMORY_CONFIG_OVERRIDE_PERSISTENCE;
config_override->entry.persistence_override.flag = (data & 0xFF0000UL) >> 16;
config_override->entry.persistence_override.persistence = ((data & 0xFF00UL) >> 8) ? true : false;
break;
default:
return -2;
}
return 0;
}
#ifndef SAFETY_MEMORY_STRIPOUT_DUMP
int safety_memory_dump_base64(char *buffer, size_t buffsize, size_t *used_size)
{
uint32_t safety_mem_size;
size_t output_size;
const char *backup_mem_ptr;
int res;
if (!buffer)
return -1000;
safety_mem_size = backup_ram_get_size_in_words() * 4U;
output_size = base64_calculate_encoded_size(safety_mem_size);
if (output_size + 1 > buffsize)
return -1001;
backup_mem_ptr = (const char *)backup_ram_get_base_ptr();
res = base64_encode(backup_mem_ptr, buffer, safety_mem_size, buffsize, &output_size);
if (res)
return -1;
buffer[output_size] = '\0';
if (used_size)
*used_size = output_size + 1u;
return 0;
}
#endif /* SAFETY_MEMORY_STRIPOUT_DUMP */
int safety_memory_get_update_filename(char *filename, size_t *outlen)
{
struct safety_memory_header header;
unsigned int i;
size_t len = 0u;
volatile char *ptr;
/* If filename and outlen are both NULL, we don't do anything */
if (!filename && !outlen)
return -1;
if (safety_memory_get_header(&header) != SAFETY_MEMORY_INIT_VALID_MEMORY)
return -2000;
/* Get the filename */
ptr = (volatile char *)backup_ram_get_base_ptr();
ptr += (unsigned int)(header.firmware_update_filename * 4);
for (i = 0; i < SAFETY_MEMORY_UPDATE_FILENAME_MAXSIZE; i++) {
if (filename)
filename[i] = *ptr;
if (*ptr)
len++;
else
break;
ptr++;
}
if (outlen)
*outlen = len;
return 0;
}
int safety_memory_set_update_filename(const char *filename)
{
int ret = 0;
size_t len;
unsigned int i;
struct safety_memory_header header;
volatile char *ram_ptr;
if (safety_memory_get_header(&header) != SAFETY_MEMORY_INIT_VALID_MEMORY)
return -2000;
if (!filename)
return -1001;
len = strnlen(filename, SAFETY_MEMORY_UPDATE_FILENAME_MAXSIZE - 1);
ram_ptr = backup_ram_get_base_ptr();
ram_ptr += header.firmware_update_filename * 4;
for (i = 0u; i < len; i++)
ram_ptr[i] = filename[i];
ram_ptr[i] = 0;
ret = safety_memory_gen_crc();
return ret;
}