mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-29 12:41:03 +00:00
122 lines
4.6 KiB
C
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()
|