// Device health monitoring #pragma once #include "arch.h" #include "printf.h" #if MG_ENABLE_HEALTH #ifndef MG_HEALTH_MAGIC #define MG_HEALTH_MAGIC {'M', 'G', 'H', '3'} // '3' is the layout version #endif #if !defined(MG_HEALTH_RAM) && (defined(STM32H7_DEV_ID) || defined(STM32H5) || defined(STM32C5)) #define MG_HEALTH_RAM __attribute__((section(".mg_health"))) #endif #ifndef MG_HEALTH_DATA_SIZE #if (defined(STM32H7_DEV_ID) || defined(STM32H5)) || defined(STM32C5) #define MG_HEALTH_DATA_SIZE 250 #elif MG_ARCH == MG_ARCH_ESP32 #define MG_HEALTH_DATA_SIZE 23 #else #define MG_HEALTH_DATA_SIZE 10 #endif #endif // MG_HEALTH_DATA_SIZE // 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 // CPU core architecture enum mg_health_arch { MG_HEALTH_ARCH_ARM = 0, MG_HEALTH_ARCH_XTENSA, MG_HEALTH_ARCH_RISCV }; // 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 }; struct mg_health { char magic[4]; // MG_HEALTH_MAGIC when the record holds valid data uint16_t reset_reason; // enum mg_health_reason that started this boot uint16_t arch; // enum mg_health_arch, core architecture (ARM, etc) uint32_t cpuid; // ARM CPU ID uint32_t data[MG_HEALTH_DATA_SIZE]; }; 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"; } } #if MG_ARCH == MG_ARCH_ESP32 #ifndef CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH #define CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH 0 #endif #ifndef ESP_PARTITION_SUBTYPE_DATA_COREDUMP #define ESP_PARTITION_SUBTYPE_DATA_COREDUMP 0x03 #endif // clang-format off static inline uint16_t esp2mg(esp_reset_reason_t rr) { switch(rr) { case ESP_RST_POWERON: return (uint16_t) MG_HEALTH_RESET_POWER; #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 2) case ESP_RST_PWR_GLITCH: #endif case ESP_RST_BROWNOUT: return (uint16_t) MG_HEALTH_RESET_BROWNOUT; case ESP_RST_EXT: return (uint16_t) MG_HEALTH_RESET_PIN; case ESP_RST_SW: return (uint16_t) MG_HEALTH_RESET_SOFTWARE; case ESP_RST_DEEPSLEEP: return (uint16_t) MG_HEALTH_RESET_LOWPOWER; case ESP_RST_INT_WDT: case ESP_RST_TASK_WDT: case ESP_RST_WDT: return (uint16_t) MG_HEALTH_RESET_WATCHDOG; #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 2) case ESP_RST_CPU_LOCKUP: #endif case ESP_RST_PANIC: return (uint16_t) MG_HEALTH_RESET_FAULT; default: break; } return (uint16_t) MG_HEALTH_RESET_UNKNOWN; } // clang-format on #endif #define MG_HEALTH_INIT() mg_health_init() extern size_t mg_print_crash_record(mg_pfn_t, void *, va_list *); extern void mg_health_init(void); extern bool mg_health_coredump_begin(size_t *size); extern bool mg_health_coredump_read(size_t offset, void *buf, size_t *len); extern bool mg_health_coredump_end(void); #else #define MG_HEALTH_INIT() static inline int mg_health_reason(void) { return 0; } static inline const char *mg_health_reason_str(int reason) { (void) reason; return "unknown"; } static inline size_t mg_print_crash_record(mg_pfn_t fn, void *arg, va_list *ap) { (void) ap; return mg_xprintf(fn, arg, "{}"); } static inline bool mg_health_coredump_begin(size_t *size) { (void) size; return false; } static inline bool mg_health_coredump_read(size_t offset, void *buf, size_t *len) { (void) offset, (void) buf, (void) len; return false; } static inline bool mg_health_coredump_end(void) { return false; } #endif