forked from jappel/VersaMCU
Harden firmware state and transfer handling
This commit is contained in:
parent
50dbf8fbee
commit
ce5db617a1
27 changed files with 558 additions and 231 deletions
|
|
@ -6,15 +6,15 @@ enum class ActionType : uint8_t
|
|||
NONE, // Keine Aktion
|
||||
HID_KEY, // Standard-Keyboard-Keycode (direkt in Firmware gesendet)
|
||||
HID_CONSUMER, // Consumer-Control-Keycode (Volume, Media, …)
|
||||
HOST_COMMAND, // Host-Event; data wird vom aktuellen Controller nicht übertragen
|
||||
HOST_COMMAND, // Host-Event; data = Command-ID für die Desktop-App
|
||||
MACRO, // Makro-Slot (data = Slot-Index 0–31) → bis zu 8 HID-Keys sequenziell
|
||||
PROFILE_SWITCH, // Profil 0–2 oder 0xFF = nächstes Profil; speichert in NVM
|
||||
PROFILE_SWITCH, // Profil 0–2 oder 0x00FF/0xFFFF = nächstes Profil; speichert in NVM
|
||||
};
|
||||
|
||||
struct __attribute__((packed)) SAction
|
||||
{
|
||||
ActionType type;
|
||||
uint16_t data; // Typabhängige Nutzdaten; für HOST_COMMAND aktuell ungenutzt
|
||||
uint16_t data; // Typabhängige Nutzdaten; HOST_COMMAND = Command-ID
|
||||
// packed: 1B type + 2B data = 3B (kein Alignment-Padding)
|
||||
// Muss packed sein, damit sizeof(SDeviceConfig)==740 und die
|
||||
// hostseitige Serialisierung bytegenau übereinstimmen.
|
||||
|
|
|
|||
|
|
@ -42,6 +42,18 @@ static bool nvm_write_page(uint32_t addr, const uint8_t* data)
|
|||
return nvm_exec(NVMCTRL_CTRLA_CMD_WP);
|
||||
}
|
||||
|
||||
bool macro_config_validate(const SMacroTable& tbl)
|
||||
{
|
||||
for (uint8_t slot = 0; slot < MACRO_SLOTS; slot++) {
|
||||
for (uint8_t step = 0; step < MACRO_MAX_STEPS; step++) {
|
||||
uint8_t keycode = tbl.steps[slot][step].keycode;
|
||||
if (keycode > 0x65)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool macro_config_load(SMacroTable& tbl)
|
||||
{
|
||||
memcpy(&tbl, reinterpret_cast<const void*>(k_macro_addr), sizeof(tbl));
|
||||
|
|
@ -56,11 +68,17 @@ bool macro_config_load(SMacroTable& tbl)
|
|||
memset(&tbl, 0, sizeof(tbl)); // Leere Tabelle als Default
|
||||
return false;
|
||||
}
|
||||
if (!macro_config_validate(tbl)) {
|
||||
memset(&tbl, 0, sizeof(tbl));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool macro_config_save(const SMacroTable& tbl)
|
||||
{
|
||||
if (!macro_config_validate(tbl)) return false;
|
||||
|
||||
// Auf 4-Byte-ausgerichteten Puffer kopieren bevor nvm_write_page ihn als uint32_t* liest.
|
||||
// SMacroTable ist __attribute__((packed)) und könnte unaligned liegen →
|
||||
// direkter uint32_t*-Cast würde auf Cortex-M0+ einen HardFault auslösen.
|
||||
|
|
|
|||
|
|
@ -28,6 +28,13 @@ struct __attribute__((packed)) SMacroTable
|
|||
SMacroStep steps[MACRO_SLOTS][MACRO_MAX_STEPS];
|
||||
};
|
||||
|
||||
static_assert(sizeof(SMacroStep) == 2, "SMacroStep binary layout changed");
|
||||
static_assert(sizeof(SMacroTable) == 512, "SMacroTable binary layout changed");
|
||||
|
||||
// Prüft, dass alle belegten Steps in den vom HID-Descriptor unterstützten
|
||||
// Keyboard-Usage-Bereich fallen.
|
||||
bool macro_config_validate(const SMacroTable& tbl);
|
||||
|
||||
// Makro-Tabelle aus NVM lesen (Row 0+1: 0x1FB00).
|
||||
// Gibt false zurück wenn der Flash-Bereich noch gelöscht (0xFF) war → leere Tabelle geladen.
|
||||
bool macro_config_load(SMacroTable& tbl);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// NVM-Zugriff für SDeviceConfig (3 Rows ab 0x1FD00, 768B gesamt, 740B genutzt).
|
||||
|
||||
#include "nvm_config.h"
|
||||
#include "macro_config.h"
|
||||
#include <Arduino.h>
|
||||
#include <string.h>
|
||||
|
||||
|
|
@ -65,6 +66,63 @@ uint16_t nvm_config_crc(const SDeviceConfig& cfg)
|
|||
return crc;
|
||||
}
|
||||
|
||||
static bool action_valid(const SAction& action)
|
||||
{
|
||||
switch (action.type) {
|
||||
case ActionType::NONE:
|
||||
return true;
|
||||
|
||||
case ActionType::HID_KEY:
|
||||
return static_cast<uint8_t>(action.data & 0xFF) <= 0x65;
|
||||
|
||||
case ActionType::HID_CONSUMER:
|
||||
return action.data <= 0x03FF;
|
||||
|
||||
case ActionType::HOST_COMMAND:
|
||||
return true;
|
||||
|
||||
case ActionType::MACRO:
|
||||
return action.data < MACRO_SLOTS;
|
||||
|
||||
case ActionType::PROFILE_SWITCH:
|
||||
return action.data <= 2 ||
|
||||
action.data == 0x00FF ||
|
||||
action.data == 0xFFFF;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool nvm_config_validate(const SDeviceConfig& cfg)
|
||||
{
|
||||
if (cfg.magic != NVM_CONFIG_MAGIC) return false;
|
||||
if (cfg.version != NVM_CONFIG_VERSION) return false;
|
||||
if (cfg.crc != nvm_config_crc(cfg)) return false;
|
||||
if (cfg.active_profile >= 3) return false;
|
||||
|
||||
for (uint8_t p = 0; p < 3; p++) {
|
||||
const SDeviceProfile& prof = cfg.profiles[p];
|
||||
|
||||
for (uint8_t i = 0; i < 20; i++) {
|
||||
if (!action_valid(prof.mx_actions[i])) return false;
|
||||
|
||||
uint8_t anim = prof.led_anim[i];
|
||||
if (anim > 6) return false; // LEDAnim::COLOR_FADE
|
||||
if (anim == 2 && prof.led_period_ms[i] < 2) return false;
|
||||
}
|
||||
|
||||
for (uint8_t enc = 0; enc < 4; enc++) {
|
||||
for (uint8_t action = 0; action < 3; action++) {
|
||||
if (!action_valid(prof.enc_actions[enc][action]))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── Defaults ─────────────────────────────────────────────────────────────────
|
||||
|
||||
void nvm_config_defaults(SDeviceConfig& cfg)
|
||||
|
|
@ -112,12 +170,10 @@ bool nvm_config_load(SDeviceConfig& cfg)
|
|||
{
|
||||
memcpy(&cfg, reinterpret_cast<const void*>(k_config_addr), sizeof(cfg));
|
||||
|
||||
if (cfg.magic != NVM_CONFIG_MAGIC) { nvm_config_defaults(cfg); return false; }
|
||||
if (cfg.version != NVM_CONFIG_VERSION) { nvm_config_defaults(cfg); return false; }
|
||||
if (cfg.crc != nvm_config_crc(cfg)) { nvm_config_defaults(cfg); return false; }
|
||||
|
||||
// Profil-Index absichern
|
||||
if (cfg.active_profile >= 3) cfg.active_profile = 0;
|
||||
if (!nvm_config_validate(cfg)) {
|
||||
nvm_config_defaults(cfg);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -126,11 +182,15 @@ bool nvm_config_load(SDeviceConfig& cfg)
|
|||
|
||||
bool nvm_config_save(const SDeviceConfig& cfg)
|
||||
{
|
||||
SDeviceConfig stored = cfg;
|
||||
stored.crc = nvm_config_crc(stored);
|
||||
if (!nvm_config_validate(stored)) return false;
|
||||
|
||||
// Config (740B) in 768B-Puffer kopieren (3 Rows), Rest mit 0xFF füllen.
|
||||
// __attribute__((aligned(4))) ist zwingend: nvm_write_page castet zu uint32_t*.
|
||||
uint8_t row[768] __attribute__((aligned(4)));
|
||||
memset(row, 0xFF, sizeof(row));
|
||||
memcpy(row, &cfg, sizeof(cfg));
|
||||
memcpy(row, &stored, sizeof(stored));
|
||||
|
||||
NVMCTRL->CTRLB.bit.MANW = 1;
|
||||
|
||||
|
|
|
|||
|
|
@ -61,13 +61,22 @@ struct __attribute__((packed)) SDeviceConfig
|
|||
// Gesamt: 32 + 708 = 740B
|
||||
};
|
||||
|
||||
static_assert(sizeof(SAction) == 3, "SAction binary layout changed");
|
||||
static_assert(sizeof(SDeviceProfile) == 236, "SDeviceProfile binary layout changed");
|
||||
static_assert(sizeof(SDeviceConfig) == 740, "SDeviceConfig binary layout changed");
|
||||
|
||||
// Standardwerte wenn keine gültige Config im NVM
|
||||
void nvm_config_defaults(SDeviceConfig& cfg);
|
||||
|
||||
// Vollständige Prüfung des persistenten/seriellen Binärvertrags inklusive CRC,
|
||||
// Enum-Bereichen, Action-Nutzdaten und animationsspezifischen Mindestwerten.
|
||||
bool nvm_config_validate(const SDeviceConfig& cfg);
|
||||
|
||||
// Config aus NVM lesen. Gibt false zurück wenn Magic/CRC/Version ungültig → Defaults geladen.
|
||||
bool nvm_config_load(SDeviceConfig& cfg);
|
||||
|
||||
// Config in NVM schreiben (löscht 3 Rows, schreibt 12 Pages).
|
||||
// Config in NVM schreiben (CRC wird intern neu berechnet; löscht 3 Rows,
|
||||
// schreibt 12 Pages).
|
||||
// Gibt false zurück wenn eine NVM-Operation nicht rechtzeitig fertig wird (Board hängt nicht).
|
||||
bool nvm_config_save(const SDeviceConfig& cfg);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue