Files
mongoose/src/health.h
T
2026-09-11 14:09:55 +01:00

122 lines
4.6 KiB
C

// Device health monitoring
//
// Keeps a small record in a RAM region that survives a warm reset, so a device
// can tell why it restarted. The record holds a boot counter, the uptime this
// boot has reached, and the reset reason reported by the hardware. A crash
// loop, a hang loop and a marginal power supply each leave a different mark.
//
// Put MG_HEALTH_INIT() in main(), right after clock and memory init:
//
// ```c
// int main(void) {
// hal_clock_init();
// MG_HEALTH_INIT();
// ...
// }
// ```
//
// The record must live in RAM that startup code neither copies nor zeroes.
// See tutorials/stm32/nucleo-h723zg/minimal/link.ld for the .mg_health region,
// and tutorials/stm32/nucleo-h723zg/minimal/mongoose_config.h for the
// reset-reason hooks.
//
// On Cortex-M, a fault handler can record the crash: it sets reset_reason to
// MG_HEALTH_RESET_FAULT and walks the stack into backtrace[]. See
// tutorials/stm32/nucleo-h723zg/minimal/main.c for a sample HardFault_Handler.
#pragma once
#include "arch.h"
#ifndef MG_HEALTH_MAGIC
#define MG_HEALTH_MAGIC {'M', 'G', 'H', '3'} // '3' is the layout version
#endif
// Places the record in RAM that survives a reset. Define in mongoose_config.h
// to match the linker script, e.g. __attribute__((section(".mg_health")))
#ifndef MG_HEALTH_RAM
#define MG_HEALTH_RAM
#endif
// Why the device restarted. Hardware usually reports several flags at once,
// so these are ordered by how much they tell you: the most specific cause a
// device reports wins
enum mg_health_reason {
MG_HEALTH_RESET_UNKNOWN = 0, // Nothing reported, or no masks configured
MG_HEALTH_RESET_POWER, // Power-on reset, RAM contents are lost
MG_HEALTH_RESET_BROWNOUT, // Supply dipped below the brown-out level
MG_HEALTH_RESET_PIN, // External reset pin, e.g. the reset button
MG_HEALTH_RESET_SOFTWARE, // Firmware asked for it, e.g. NVIC_SystemReset
MG_HEALTH_RESET_LOWPOWER, // Illegal or failed low-power transition
MG_HEALTH_RESET_WATCHDOG, // Watchdog fired: the application stopped
MG_HEALTH_RESET_FAULT // Fault handler ran, see regs[] and saved_stack
};
#define MG_HEALTH_BACKTRACE 20 // Return addresses in mg_health::backtrace
struct mg_health {
char magic[4]; // MG_HEALTH_MAGIC when the record holds valid data
uint32_t reset_reason; // enum mg_health_reason that started this boot
uint32_t backtrace[MG_HEALTH_BACKTRACE]; // Crash backtrace, frame 0 first
};
extern struct mg_health mg_health_record; // Defined in health.c
// Return true if the record holds valid data, i.e. it survived the reset and
// was written by this firmware layout
static inline bool mg_health_valid(void) {
static const char magic[] = MG_HEALTH_MAGIC;
return memcmp(mg_health_record.magic, magic,
sizeof(mg_health_record.magic)) == 0;
}
static inline int mg_health_reason(void) {
return mg_health_valid() ? (int) mg_health_record.reset_reason
: MG_HEALTH_RESET_UNKNOWN;
}
static inline const char *mg_health_reason_str(int reason) {
switch (reason) {
case MG_HEALTH_RESET_POWER: return "power";
case MG_HEALTH_RESET_BROWNOUT: return "brownout";
case MG_HEALTH_RESET_PIN: return "pin";
case MG_HEALTH_RESET_SOFTWARE: return "software";
case MG_HEALTH_RESET_LOWPOWER: return "lowpower";
case MG_HEALTH_RESET_WATCHDOG: return "watchdog";
case MG_HEALTH_RESET_FAULT: return "fault";
default: return "unknown";
}
}
// Start a new boot: validate the record, count the boot, store the reset
// reason. The current boot moves to prev_* slots first, so the previous
// boot's uptime and reason stay readable. Called by MG_HEALTH_INIT()
static inline void mg_health_init(void) {
char magic[] = MG_HEALTH_MAGIC;
// struct mg_health *h = mg_health_get();
if (!mg_health_valid()) { // First boot, or RAM lost its contents
memset(&mg_health_record, 0, sizeof(mg_health_record));
memcpy(mg_health_record.magic, magic, sizeof(mg_health_record.magic));
}
// h->prev_uptime = h->uptime;
// h->prev_reason = h->reason;
// h->counter++;
// h->uptime = 0;
// h->reason = mg_health_decode_reason();
// MG_HEALTH_RESET_CLEAR();
}
// // Record that the main loop is still alive. Called from mg_mgr_poll(), so a
// // hung application leaves uptime frozen at the hang
// extern uint64_t mg_health_next_ms; // Defined in health.c
// static inline void mg_health_uptime(uint64_t now_ms) {
// struct mg_health *h = mg_health_get();
// if (now_ms >= mg_health_next_ms && mg_health_valid()) {
// mg_health_next_ms = now_ms + 1000;
// h->uptime++;
// }
// }
#define MG_HEALTH_INIT() mg_health_init()