mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-13 10:15:00 +00:00
Add generic mg_health report for mdash
This commit is contained in:
+376
-8
@@ -3604,9 +3604,373 @@ struct mg_fs mg_fs_posix = {p_stat, p_list, p_open, p_close, p_read,
|
||||
#endif
|
||||
|
||||
|
||||
// The one health record. Lives in RAM that survives a warm reset, see
|
||||
// MG_HEALTH_RAM and the .mg_health region in the linker script
|
||||
struct mg_health mg_health_record MG_HEALTH_RAM;
|
||||
|
||||
struct mg_health mg_health_record;
|
||||
|
||||
void mg_health_init(void) {
|
||||
static const char magic[] = MG_HEALTH_MAGIC;
|
||||
memset(&mg_health_record, 0, sizeof(mg_health_record));
|
||||
memcpy(mg_health_record.magic, magic, sizeof(mg_health_record.magic));
|
||||
#if MG_HEALTH == MG_HEALTH_CORTEX
|
||||
mg_health_cortex_init(&mg_health_cortex);
|
||||
mg_health_record.get_blob = mg_health_cortex_get_blob;
|
||||
mg_health_record.fn_data = &mg_health_cortex;
|
||||
#endif
|
||||
}
|
||||
|
||||
struct mg_str mg_health_get_blob(void) {
|
||||
return !mg_health_valid() || mg_health_record.get_blob == NULL
|
||||
? mg_str("")
|
||||
: mg_health_record.get_blob(mg_health_record.fn_data);
|
||||
}
|
||||
|
||||
#ifdef MG_ENABLE_LINES
|
||||
#line 1 "src/health_cortex.c"
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#if MG_HEALTH == MG_HEALTH_CORTEX
|
||||
|
||||
#define MG_SCB_CPUID (*(volatile uint32_t *) 0xe000ed00)
|
||||
#define MG_SCB_CFSR (*(volatile uint32_t *) 0xe000ed28)
|
||||
#define MG_SCB_HFSR (*(volatile uint32_t *) 0xe000ed2c)
|
||||
#define MG_SCB_DFSR (*(volatile uint32_t *) 0xe000ed30)
|
||||
#define MG_SCB_MMFAR (*(volatile uint32_t *) 0xe000ed34)
|
||||
#define MG_SCB_BFAR (*(volatile uint32_t *) 0xe000ed38)
|
||||
#define MG_SCB_AFSR (*(volatile uint32_t *) 0xe000ed3c)
|
||||
#define MG_SCB_ABFSR (*(volatile uint32_t *) 0xe000efa8)
|
||||
|
||||
#define MG_CPU_CORTEX_M0 0xc20
|
||||
#define MG_CPU_CORTEX_M23 0xd20
|
||||
#define MG_CPU_CORTEX_M3 0xc23
|
||||
#define MG_CPU_CORTEX_M4 0xc24
|
||||
#define MG_CPU_CORTEX_M7 0xc27
|
||||
#define MG_CPU_CORTEX_M0P 0xc60
|
||||
#define MG_CPU_CORTEX_M33 0xd21
|
||||
#define MG_CPU_CORTEX_M35P 0xd31
|
||||
#define MG_CPU_CORTEX_M52 0xd24
|
||||
#define MG_CPU_CORTEX_M55 0xd22
|
||||
#define MG_CPU_CORTEX_M85 0xd23
|
||||
|
||||
extern uint32_t __StackLimit __attribute__((weak));
|
||||
extern uint32_t __StackTop __attribute__((weak));
|
||||
extern uint32_t _sstack __attribute__((weak));
|
||||
extern uint32_t _estack __attribute__((weak));
|
||||
extern bool mg_health_cortex_stack_range(const void *, uintptr_t *, uintptr_t *)
|
||||
__attribute__((weak));
|
||||
|
||||
static struct mg_health_cortex_record s_record;
|
||||
static char s_report[MG_HEALTH_CORTEX_REPORT_SIZE];
|
||||
|
||||
static bool storage_init(void *storage, size_t size) {
|
||||
// Note: actual init is handled in main()
|
||||
(void) storage;
|
||||
(void) size;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool storage_read(const void *storage, size_t offset, void *buf,
|
||||
size_t len) {
|
||||
volatile const uint8_t *src = (volatile const uint8_t *) storage + offset;
|
||||
uint8_t *dst = (uint8_t *) buf;
|
||||
size_t i;
|
||||
for (i = 0; i < len; i++) dst[i] = src[i];
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool storage_write(void *storage, size_t offset, const void *buf,
|
||||
size_t len) {
|
||||
volatile uint8_t *dst = (volatile uint8_t *) storage + offset;
|
||||
const uint8_t *src = (const uint8_t *) buf;
|
||||
size_t i;
|
||||
for (i = 0; i < len; i++) dst[i] = src[i];
|
||||
return true;
|
||||
}
|
||||
|
||||
struct mg_health_cortex mg_health_cortex = {
|
||||
MG_HEALTH_CORTEX_STORAGE,
|
||||
MG_HEALTH_CORTEX_STORAGE_SIZE,
|
||||
storage_init,
|
||||
storage_read,
|
||||
storage_write,
|
||||
};
|
||||
|
||||
static bool storage_bounds(const struct mg_health_cortex *ctx, size_t offset,
|
||||
size_t len) {
|
||||
return ctx != NULL && ctx->storage != NULL && offset <= ctx->storage_size &&
|
||||
len <= ctx->storage_size - offset;
|
||||
}
|
||||
|
||||
static bool storage_configured(const struct mg_health_cortex *ctx) {
|
||||
return ctx != NULL && ctx->storage != NULL && ctx->storage_size != 0 &&
|
||||
ctx->storage_size >= sizeof(struct mg_health_cortex_record) &&
|
||||
ctx->init != NULL && ctx->read != NULL && ctx->write != NULL;
|
||||
}
|
||||
|
||||
static bool record_read(struct mg_health_cortex *ctx,
|
||||
struct mg_health_cortex_record *record) {
|
||||
static const char magic[] = MG_HEALTH_MAGIC;
|
||||
if (ctx == NULL || ctx->read == NULL ||
|
||||
!storage_bounds(ctx, 0, sizeof(*record)) ||
|
||||
!ctx->read(ctx->storage, 0, record, sizeof(*record))) {
|
||||
return false;
|
||||
}
|
||||
return memcmp(record->magic, magic, sizeof(record->magic)) == 0 &&
|
||||
record->version == MG_HEALTH_CORTEX_VERSION &&
|
||||
record->stack_word_count <= MG_HEALTH_CORTEX_STACK_WORDS;
|
||||
}
|
||||
|
||||
static bool record_write(struct mg_health_cortex *ctx,
|
||||
const struct mg_health_cortex_record *record) {
|
||||
char invalid[sizeof(record->magic)] = {0};
|
||||
size_t offset = sizeof(record->magic);
|
||||
if (ctx == NULL || ctx->write == NULL ||
|
||||
!storage_bounds(ctx, 0, sizeof(*record)) ||
|
||||
!ctx->write(ctx->storage, 0, &invalid, sizeof(invalid))) {
|
||||
return false;
|
||||
}
|
||||
if (!ctx->write(ctx->storage, offset, (const uint8_t *) record + offset,
|
||||
sizeof(*record) - offset)) {
|
||||
return false;
|
||||
}
|
||||
if (!ctx->write(ctx->storage, 0, record->magic, sizeof(record->magic)))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool stack_range(const void *ptr, uintptr_t *lo, uintptr_t *hi) {
|
||||
if (mg_health_cortex_stack_range != NULL &&
|
||||
mg_health_cortex_stack_range(ptr, lo, hi)) {
|
||||
return *lo < *hi;
|
||||
} else if (&__StackLimit != NULL && &__StackTop != NULL) {
|
||||
*lo = (uintptr_t) &__StackLimit;
|
||||
*hi = (uintptr_t) &__StackTop;
|
||||
} else if (&_sstack != NULL && &_estack != NULL) {
|
||||
*lo = (uintptr_t) &_sstack;
|
||||
*hi = (uintptr_t) &_estack;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return *lo < *hi;
|
||||
}
|
||||
|
||||
static int stack_valid(const void *ptr, size_t words) {
|
||||
uintptr_t lo, hi, p = (uintptr_t) ptr;
|
||||
if ((p & 3U) != 0) return 0;
|
||||
if (!stack_range(ptr, &lo, &hi)) return -1;
|
||||
return p >= lo && p <= hi && words <= (hi - p) / sizeof(uint32_t);
|
||||
}
|
||||
|
||||
static bool has_fault_status(uint32_t part) {
|
||||
return part == MG_CPU_CORTEX_M3 || part == MG_CPU_CORTEX_M4 ||
|
||||
part == MG_CPU_CORTEX_M7 || part == MG_CPU_CORTEX_M33 ||
|
||||
part == MG_CPU_CORTEX_M35P || part == MG_CPU_CORTEX_M52 ||
|
||||
part == MG_CPU_CORTEX_M55 || part == MG_CPU_CORTEX_M85;
|
||||
}
|
||||
|
||||
static const char *core_name(uint32_t part) {
|
||||
switch (part) {
|
||||
case MG_CPU_CORTEX_M0:
|
||||
return "cortex-m0";
|
||||
case MG_CPU_CORTEX_M0P:
|
||||
return "cortex-m0+";
|
||||
case MG_CPU_CORTEX_M23:
|
||||
return "cortex-m23";
|
||||
case MG_CPU_CORTEX_M3:
|
||||
return "cortex-m3";
|
||||
case MG_CPU_CORTEX_M4:
|
||||
return "cortex-m4";
|
||||
case MG_CPU_CORTEX_M7:
|
||||
return "cortex-m7";
|
||||
case MG_CPU_CORTEX_M33:
|
||||
return "cortex-m33";
|
||||
case MG_CPU_CORTEX_M35P:
|
||||
return "cortex-m35p";
|
||||
case MG_CPU_CORTEX_M52:
|
||||
return "cortex-m52";
|
||||
case MG_CPU_CORTEX_M55:
|
||||
return "cortex-m55";
|
||||
case MG_CPU_CORTEX_M85:
|
||||
return "cortex-m85";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *arch_name(uint32_t part) {
|
||||
if (part == MG_CPU_CORTEX_M0 || part == MG_CPU_CORTEX_M0P) return "armv6-m";
|
||||
if (part == MG_CPU_CORTEX_M3) return "armv7-m";
|
||||
if (part == MG_CPU_CORTEX_M4 || part == MG_CPU_CORTEX_M7) return "armv7e-m";
|
||||
return "armv8-m";
|
||||
}
|
||||
|
||||
static bool json_append(char *buf, size_t size, size_t *offset, const char *fmt,
|
||||
...) {
|
||||
va_list ap;
|
||||
size_t available, n;
|
||||
if (*offset >= size) return false;
|
||||
available = size - *offset;
|
||||
va_start(ap, fmt);
|
||||
n = mg_vsnprintf(buf + *offset, available, fmt, &ap);
|
||||
va_end(ap);
|
||||
if (n >= available) {
|
||||
*offset = size;
|
||||
return false;
|
||||
}
|
||||
*offset += n;
|
||||
return true;
|
||||
}
|
||||
|
||||
void mg_health_cortex_init(struct mg_health_cortex *ctx) {
|
||||
if (storage_configured(ctx)) {
|
||||
(void) ctx->init(ctx->storage, ctx->storage_size);
|
||||
}
|
||||
}
|
||||
|
||||
struct mg_str mg_health_cortex_get_blob(void *data) {
|
||||
struct mg_health_cortex *ctx = (struct mg_health_cortex *) data;
|
||||
struct mg_health_cortex_record *r = &s_record;
|
||||
uint32_t part;
|
||||
size_t n = 0, i;
|
||||
bool ok;
|
||||
|
||||
if (!storage_configured(ctx)) {
|
||||
return mg_str(
|
||||
"{\"valid\":false,\"error\":\"health storage is not configured\"}");
|
||||
}
|
||||
if (!record_read(ctx, r)) return mg_str("{\"valid\":false}");
|
||||
part = (r->cpuid >> 4) & 0xfffU;
|
||||
ok = json_append(
|
||||
s_report, sizeof(s_report), &n,
|
||||
"{\"valid\":true,\"version\":%lu,\"arch\":\"%s\","
|
||||
"\"core\":\"%s\",\"image\":\"firmware.elf\","
|
||||
"\"map\":\"firmware.elf.map\",\"binary\":\"firmware.bin\","
|
||||
"\"cpuid\":\"0x%08lx\",\"exception_sp\":\"0x%08lx\","
|
||||
"\"exc_return\":\"0x%08lx\",\"exception_valid\":%s,"
|
||||
"\"stack_valid\":%s,\"regs\":{"
|
||||
"\"r0\":\"0x%08lx\",\"r1\":\"0x%08lx\","
|
||||
"\"r2\":\"0x%08lx\",\"r3\":\"0x%08lx\","
|
||||
"\"r4\":\"0x%08lx\",\"r5\":\"0x%08lx\","
|
||||
"\"r6\":\"0x%08lx\",\"r7\":\"0x%08lx\","
|
||||
"\"r8\":\"0x%08lx\",\"r9\":\"0x%08lx\","
|
||||
"\"r10\":\"0x%08lx\",\"r11\":\"0x%08lx\","
|
||||
"\"r12\":\"0x%08lx\",\"sp\":\"0x%08lx\","
|
||||
"\"lr\":\"0x%08lx\",\"pc\":\"0x%08lx\","
|
||||
"\"xpsr\":\"0x%08lx\"},\"fault\":{"
|
||||
"\"cfsr\":\"0x%08lx\",\"hfsr\":\"0x%08lx\","
|
||||
"\"dfsr\":\"0x%08lx\",\"afsr\":\"0x%08lx\","
|
||||
"\"mmfar\":\"0x%08lx\",\"bfar\":\"0x%08lx\","
|
||||
"\"abfsr\":\"0x%08lx\"},\"stack\":{"
|
||||
"\"addr\":\"0x%08lx\",\"word_size\":4,\"words\":[",
|
||||
(unsigned long) r->version, arch_name(part), core_name(part),
|
||||
(unsigned long) r->cpuid, (unsigned long) r->exception_sp,
|
||||
(unsigned long) r->exc_return, r->exception_valid > 0 ? "true" : "false",
|
||||
r->stack_valid > 0 ? "true" : "false", (unsigned long) r->r0,
|
||||
(unsigned long) r->r1, (unsigned long) r->r2, (unsigned long) r->r3,
|
||||
(unsigned long) r->r4, (unsigned long) r->r5, (unsigned long) r->r6,
|
||||
(unsigned long) r->r7, (unsigned long) r->r8, (unsigned long) r->r9,
|
||||
(unsigned long) r->r10, (unsigned long) r->r11, (unsigned long) r->r12,
|
||||
(unsigned long) r->sp, (unsigned long) r->lr, (unsigned long) r->pc,
|
||||
(unsigned long) r->psr, (unsigned long) r->cfsr, (unsigned long) r->hfsr,
|
||||
(unsigned long) r->dfsr, (unsigned long) r->afsr,
|
||||
(unsigned long) r->mmfar, (unsigned long) r->bfar,
|
||||
(unsigned long) r->abfsr, (unsigned long) r->sp);
|
||||
for (i = 0; ok && i < r->stack_word_count; i++) {
|
||||
ok = json_append(s_report, sizeof(s_report), &n, "%s\"0x%08lx\"",
|
||||
i == 0 ? "" : ",", (unsigned long) r->stack_words[i]);
|
||||
}
|
||||
if (ok) ok = json_append(s_report, sizeof(s_report), &n, "]}}");
|
||||
return ok ? mg_str_n(s_report, n)
|
||||
: mg_str("{\"valid\":false,\"error\":\"JSON overflow\"}");
|
||||
}
|
||||
|
||||
static void __attribute__((used)) mg_health_cortex_handler(
|
||||
uint32_t *exception_stack, uint32_t lr,
|
||||
const uint32_t *callee_saved) {
|
||||
static const char magic[] = MG_HEALTH_MAGIC;
|
||||
uint32_t cpuid = MG_SCB_CPUID;
|
||||
uint32_t part = (cpuid >> 4) & 0xfffU;
|
||||
size_t fp_words = (lr & (1U << 4)) == 0 ? 18U : 0U;
|
||||
int valid = stack_valid(exception_stack, fp_words + 8U);
|
||||
struct mg_health_cortex_record *r = &s_record;
|
||||
volatile uint32_t *words = (volatile uint32_t *) r;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < sizeof(*r) / sizeof(*words); i++) words[i] = 0;
|
||||
r->version = MG_HEALTH_CORTEX_VERSION;
|
||||
r->cpuid = cpuid;
|
||||
r->exception_sp = (uint32_t) (uintptr_t) exception_stack;
|
||||
r->exc_return = lr;
|
||||
r->exception_valid = valid;
|
||||
r->r4 = callee_saved[0];
|
||||
r->r5 = callee_saved[1];
|
||||
r->r6 = callee_saved[2];
|
||||
r->r7 = callee_saved[3];
|
||||
r->r8 = callee_saved[4];
|
||||
r->r9 = callee_saved[5];
|
||||
r->r10 = callee_saved[6];
|
||||
r->r11 = callee_saved[7];
|
||||
if (valid > 0) {
|
||||
uint32_t *core_frame = exception_stack + fp_words;
|
||||
uint32_t *fault_sp;
|
||||
uintptr_t lo, hi, p;
|
||||
size_t count;
|
||||
r->r0 = core_frame[0];
|
||||
r->r1 = core_frame[1];
|
||||
r->r2 = core_frame[2];
|
||||
r->r3 = core_frame[3];
|
||||
r->r12 = core_frame[4];
|
||||
r->lr = core_frame[5];
|
||||
r->pc = core_frame[6];
|
||||
r->psr = core_frame[7];
|
||||
fault_sp = core_frame + 8;
|
||||
if (r->psr & (1U << 9)) fault_sp++;
|
||||
r->sp = (uint32_t) (uintptr_t) fault_sp;
|
||||
r->stack_valid = stack_valid(fault_sp, 0);
|
||||
p = (uintptr_t) fault_sp;
|
||||
if (r->stack_valid > 0 && stack_range(fault_sp, &lo, &hi)) {
|
||||
(void) lo;
|
||||
count = (hi - p) / sizeof(uint32_t);
|
||||
if (count > MG_HEALTH_CORTEX_STACK_WORDS)
|
||||
count = MG_HEALTH_CORTEX_STACK_WORDS;
|
||||
r->stack_word_count = (uint32_t) count;
|
||||
for (i = 0; i < count; i++) r->stack_words[i] = fault_sp[i];
|
||||
}
|
||||
}
|
||||
if (has_fault_status(part)) {
|
||||
r->cfsr = MG_SCB_CFSR;
|
||||
r->hfsr = MG_SCB_HFSR;
|
||||
r->dfsr = MG_SCB_DFSR;
|
||||
r->afsr = MG_SCB_AFSR;
|
||||
r->bfar = MG_SCB_BFAR;
|
||||
r->mmfar = MG_SCB_MMFAR;
|
||||
if (part == MG_CPU_CORTEX_M7) r->abfsr = MG_SCB_ABFSR;
|
||||
}
|
||||
memcpy(r->magic, magic, sizeof(r->magic));
|
||||
(void) record_write(&mg_health_cortex, r);
|
||||
for (;;) (void) 0;
|
||||
}
|
||||
|
||||
void HardFault_Handler(void);
|
||||
__attribute__((naked)) void HardFault_Handler(void) {
|
||||
__asm volatile(
|
||||
"mov r0, lr \n"
|
||||
"movs r1, #4 \n"
|
||||
"tst r0, r1 \n"
|
||||
"beq 1f \n"
|
||||
"mrs r0, psp \n"
|
||||
"b 2f \n"
|
||||
"1: \n"
|
||||
"mrs r0, msp \n"
|
||||
"2: \n"
|
||||
"mov r1, lr \n"
|
||||
"stmdb sp!, {r4-r11} \n"
|
||||
"mov r2, sp \n"
|
||||
"b mg_health_cortex_handler \n");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef MG_ENABLE_LINES
|
||||
#line 1 "src/http.c"
|
||||
@@ -7192,11 +7556,15 @@ static void mg_mdash_fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||
}
|
||||
|
||||
static void mg_mdash_rpc_get_info(struct mg_rpc_req *r) {
|
||||
mg_rpc_ok(r, "{%m:%m,%m:%llu,%m:%m,%m:\"mws.%d\"}", MG_ESC("fw_version"),
|
||||
MG_ESC(MG_MDASH_FIRMWARE_VERSION), MG_ESC("uptime"),
|
||||
(uint64_t) (mg_millis() / 1000), MG_ESC("reboot_reason"),
|
||||
MG_ESC(mg_health_reason_str(mg_health_reason())), MG_ESC("arch"),
|
||||
MG_ARCH);
|
||||
struct mg_str blob = mg_health_get_blob();
|
||||
if (blob.buf == NULL) blob.len = 0;
|
||||
mg_rpc_ok(r, "{%m:%m,%m:%llu,%m:%m,%m:\"mws.%d\",%m:{%m:%m,%m:%llu,%m:%m}}",
|
||||
MG_ESC("fw_version"), MG_ESC(MG_MDASH_FIRMWARE_VERSION), MG_ESC("uptime"),
|
||||
(uint64_t) (mg_millis() / 1000), MG_ESC("reboot_reason"),
|
||||
MG_ESC(mg_health_reason_str(mg_health_reason())), MG_ESC("arch"), MG_ARCH,
|
||||
MG_ESC("health"), MG_ESC("encoding"), MG_ESC("base64"), MG_ESC("size"),
|
||||
(uint64_t) blob.len, MG_ESC("data"), mg_print_base64, (int) blob.len,
|
||||
(uint8_t *) (blob.buf == NULL ? "" : blob.buf));
|
||||
}
|
||||
|
||||
static void mg_mdash_rpc_ota_begin(struct mg_rpc_req *r) {
|
||||
|
||||
+117
-72
@@ -4945,44 +4945,25 @@ void mg_ota_device_id(char *buf, size_t len);
|
||||
void mg_ota_poll(struct mg_mgr *);
|
||||
// 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.
|
||||
// A health provider owns the report format and returns it as an opaque blob.
|
||||
// Applications and transports use mg_health_get_blob() without knowing how the
|
||||
// report was collected or encoded.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#define MG_HEALTH_NONE 0
|
||||
#define MG_HEALTH_CORTEX 1
|
||||
|
||||
#ifndef MG_HEALTH
|
||||
#define MG_HEALTH MG_HEALTH_NONE
|
||||
#endif
|
||||
|
||||
#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
|
||||
@@ -4997,15 +4978,16 @@ enum mg_health_reason {
|
||||
MG_HEALTH_RESET_FAULT // Fault handler ran, see regs[] and saved_stack
|
||||
};
|
||||
|
||||
#define MG_HEALTH_BACKTRACE 20 // Return addresses in mg_health::backtrace
|
||||
typedef struct mg_str mg_health_blob;
|
||||
|
||||
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
|
||||
char magic[4];
|
||||
mg_health_blob (*get_blob)(void *);
|
||||
void *fn_data;
|
||||
uint32_t reset_reason;
|
||||
};
|
||||
|
||||
extern struct mg_health mg_health_record; // Defined in health.c
|
||||
extern struct mg_health mg_health_record;
|
||||
|
||||
// Return true if the record holds valid data, i.e. it survived the reset and
|
||||
// was written by this firmware layout
|
||||
@@ -5015,6 +4997,14 @@ static inline bool mg_health_valid(void) {
|
||||
sizeof(mg_health_record.magic)) == 0;
|
||||
}
|
||||
|
||||
// Select and initialise the configured health provider. Call after
|
||||
// initializing the board-specific persistent-storage
|
||||
void mg_health_init(void);
|
||||
|
||||
// Return the provider's opaque report. The returned memory is provider-owned
|
||||
// and remains valid until the next call to this function
|
||||
struct mg_str mg_health_get_blob(void);
|
||||
|
||||
static inline int mg_health_reason(void) {
|
||||
return mg_health_valid() ? (int) mg_health_record.reset_reason
|
||||
: MG_HEALTH_RESET_UNKNOWN;
|
||||
@@ -5022,48 +5012,103 @@ static inline int mg_health_reason(void) {
|
||||
|
||||
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";
|
||||
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()
|
||||
|
||||
|
||||
|
||||
|
||||
#define MG_HEALTH_CORTEX_VERSION 1U
|
||||
|
||||
#ifndef MG_HEALTH_CORTEX_STORAGE
|
||||
#define MG_HEALTH_CORTEX_STORAGE NULL
|
||||
#endif
|
||||
|
||||
#ifndef MG_HEALTH_CORTEX_STORAGE_SIZE
|
||||
#define MG_HEALTH_CORTEX_STORAGE_SIZE 0
|
||||
#endif
|
||||
|
||||
#ifndef MG_HEALTH_CORTEX_STACK_WORDS
|
||||
#define MG_HEALTH_CORTEX_STACK_WORDS 980U
|
||||
#endif
|
||||
|
||||
#ifndef MG_HEALTH_CORTEX_REPORT_SIZE
|
||||
#define MG_HEALTH_CORTEX_REPORT_SIZE 16384U
|
||||
#endif
|
||||
|
||||
// Storage operations are deliberately format-agnostic. H723 uses the default
|
||||
// direct-memory implementation; boards with different retained storage can
|
||||
// replace these callbacks before calling MG_HEALTH_INIT()
|
||||
struct mg_health_cortex {
|
||||
void *storage;
|
||||
size_t storage_size;
|
||||
bool (*init)(void *, size_t);
|
||||
bool (*read)(const void *, size_t, void *, size_t);
|
||||
bool (*write)(void *, size_t, const void *, size_t);
|
||||
};
|
||||
|
||||
struct mg_health_cortex_record {
|
||||
char magic[4];
|
||||
uint32_t version;
|
||||
|
||||
uint32_t cpuid;
|
||||
uint32_t exception_sp;
|
||||
uint32_t sp;
|
||||
uint32_t exc_return;
|
||||
int32_t exception_valid;
|
||||
int32_t stack_valid;
|
||||
|
||||
uint32_t r0;
|
||||
uint32_t r1;
|
||||
uint32_t r2;
|
||||
uint32_t r3;
|
||||
uint32_t r4;
|
||||
uint32_t r5;
|
||||
uint32_t r6;
|
||||
uint32_t r7;
|
||||
uint32_t r8;
|
||||
uint32_t r9;
|
||||
uint32_t r10;
|
||||
uint32_t r11;
|
||||
uint32_t r12;
|
||||
uint32_t lr;
|
||||
uint32_t pc;
|
||||
uint32_t psr;
|
||||
|
||||
uint32_t cfsr;
|
||||
uint32_t hfsr;
|
||||
uint32_t dfsr;
|
||||
uint32_t afsr;
|
||||
uint32_t bfar;
|
||||
uint32_t mmfar;
|
||||
uint32_t abfsr;
|
||||
|
||||
uint32_t stack_word_count;
|
||||
uint32_t stack_words[MG_HEALTH_CORTEX_STACK_WORDS];
|
||||
};
|
||||
|
||||
extern struct mg_health_cortex mg_health_cortex;
|
||||
|
||||
void mg_health_cortex_init(struct mg_health_cortex *);
|
||||
struct mg_str mg_health_cortex_get_blob(void *);
|
||||
// Cloud connector API to https://mdash.net - device management cloud
|
||||
|
||||
|
||||
|
||||
+19
-3
@@ -1,5 +1,21 @@
|
||||
#include "health.h"
|
||||
#include "health_cortex.h"
|
||||
|
||||
// The one health record. Lives in RAM that survives a warm reset, see
|
||||
// MG_HEALTH_RAM and the .mg_health region in the linker script
|
||||
struct mg_health mg_health_record MG_HEALTH_RAM;
|
||||
struct mg_health mg_health_record;
|
||||
|
||||
void mg_health_init(void) {
|
||||
static const char magic[] = MG_HEALTH_MAGIC;
|
||||
memset(&mg_health_record, 0, sizeof(mg_health_record));
|
||||
memcpy(mg_health_record.magic, magic, sizeof(mg_health_record.magic));
|
||||
#if MG_HEALTH == MG_HEALTH_CORTEX
|
||||
mg_health_cortex_init(&mg_health_cortex);
|
||||
mg_health_record.get_blob = mg_health_cortex_get_blob;
|
||||
mg_health_record.fn_data = &mg_health_cortex;
|
||||
#endif
|
||||
}
|
||||
|
||||
struct mg_str mg_health_get_blob(void) {
|
||||
return !mg_health_valid() || mg_health_record.get_blob == NULL
|
||||
? mg_str("")
|
||||
: mg_health_record.get_blob(mg_health_record.fn_data);
|
||||
}
|
||||
|
||||
+41
-73
@@ -1,43 +1,24 @@
|
||||
// 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.
|
||||
// A health provider owns the report format and returns it as an opaque blob.
|
||||
// Applications and transports use mg_health_get_blob() without knowing how the
|
||||
// report was collected or encoded.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "arch.h"
|
||||
#include "str.h"
|
||||
|
||||
#define MG_HEALTH_NONE 0
|
||||
#define MG_HEALTH_CORTEX 1
|
||||
|
||||
#ifndef MG_HEALTH
|
||||
#define MG_HEALTH MG_HEALTH_NONE
|
||||
#endif
|
||||
|
||||
#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
|
||||
@@ -52,15 +33,16 @@ enum mg_health_reason {
|
||||
MG_HEALTH_RESET_FAULT // Fault handler ran, see regs[] and saved_stack
|
||||
};
|
||||
|
||||
#define MG_HEALTH_BACKTRACE 20 // Return addresses in mg_health::backtrace
|
||||
typedef struct mg_str mg_health_blob;
|
||||
|
||||
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
|
||||
char magic[4];
|
||||
mg_health_blob (*get_blob)(void *);
|
||||
void *fn_data;
|
||||
uint32_t reset_reason;
|
||||
};
|
||||
|
||||
extern struct mg_health mg_health_record; // Defined in health.c
|
||||
extern struct mg_health mg_health_record;
|
||||
|
||||
// Return true if the record holds valid data, i.e. it survived the reset and
|
||||
// was written by this firmware layout
|
||||
@@ -70,6 +52,14 @@ static inline bool mg_health_valid(void) {
|
||||
sizeof(mg_health_record.magic)) == 0;
|
||||
}
|
||||
|
||||
// Select and initialise the configured health provider. Call after
|
||||
// initializing the board-specific persistent-storage
|
||||
void mg_health_init(void);
|
||||
|
||||
// Return the provider's opaque report. The returned memory is provider-owned
|
||||
// and remains valid until the next call to this function
|
||||
struct mg_str mg_health_get_blob(void);
|
||||
|
||||
static inline int mg_health_reason(void) {
|
||||
return mg_health_valid() ? (int) mg_health_record.reset_reason
|
||||
: MG_HEALTH_RESET_UNKNOWN;
|
||||
@@ -77,45 +67,23 @@ static inline int mg_health_reason(void) {
|
||||
|
||||
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";
|
||||
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()
|
||||
|
||||
@@ -0,0 +1,344 @@
|
||||
#include "health_cortex.h"
|
||||
#include "printf.h"
|
||||
|
||||
#if MG_HEALTH == MG_HEALTH_CORTEX
|
||||
|
||||
#define MG_SCB_CPUID (*(volatile uint32_t *) 0xe000ed00)
|
||||
#define MG_SCB_CFSR (*(volatile uint32_t *) 0xe000ed28)
|
||||
#define MG_SCB_HFSR (*(volatile uint32_t *) 0xe000ed2c)
|
||||
#define MG_SCB_DFSR (*(volatile uint32_t *) 0xe000ed30)
|
||||
#define MG_SCB_MMFAR (*(volatile uint32_t *) 0xe000ed34)
|
||||
#define MG_SCB_BFAR (*(volatile uint32_t *) 0xe000ed38)
|
||||
#define MG_SCB_AFSR (*(volatile uint32_t *) 0xe000ed3c)
|
||||
#define MG_SCB_ABFSR (*(volatile uint32_t *) 0xe000efa8)
|
||||
|
||||
#define MG_CPU_CORTEX_M0 0xc20
|
||||
#define MG_CPU_CORTEX_M23 0xd20
|
||||
#define MG_CPU_CORTEX_M3 0xc23
|
||||
#define MG_CPU_CORTEX_M4 0xc24
|
||||
#define MG_CPU_CORTEX_M7 0xc27
|
||||
#define MG_CPU_CORTEX_M0P 0xc60
|
||||
#define MG_CPU_CORTEX_M33 0xd21
|
||||
#define MG_CPU_CORTEX_M35P 0xd31
|
||||
#define MG_CPU_CORTEX_M52 0xd24
|
||||
#define MG_CPU_CORTEX_M55 0xd22
|
||||
#define MG_CPU_CORTEX_M85 0xd23
|
||||
|
||||
extern uint32_t __StackLimit __attribute__((weak));
|
||||
extern uint32_t __StackTop __attribute__((weak));
|
||||
extern uint32_t _sstack __attribute__((weak));
|
||||
extern uint32_t _estack __attribute__((weak));
|
||||
extern bool mg_health_cortex_stack_range(const void *, uintptr_t *, uintptr_t *)
|
||||
__attribute__((weak));
|
||||
|
||||
static struct mg_health_cortex_record s_record;
|
||||
static char s_report[MG_HEALTH_CORTEX_REPORT_SIZE];
|
||||
|
||||
static bool storage_init(void *storage, size_t size) {
|
||||
// Note: actual init is handled in main()
|
||||
(void) storage;
|
||||
(void) size;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool storage_read(const void *storage, size_t offset, void *buf,
|
||||
size_t len) {
|
||||
volatile const uint8_t *src = (volatile const uint8_t *) storage + offset;
|
||||
uint8_t *dst = (uint8_t *) buf;
|
||||
size_t i;
|
||||
for (i = 0; i < len; i++) dst[i] = src[i];
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool storage_write(void *storage, size_t offset, const void *buf,
|
||||
size_t len) {
|
||||
volatile uint8_t *dst = (volatile uint8_t *) storage + offset;
|
||||
const uint8_t *src = (const uint8_t *) buf;
|
||||
size_t i;
|
||||
for (i = 0; i < len; i++) dst[i] = src[i];
|
||||
return true;
|
||||
}
|
||||
|
||||
struct mg_health_cortex mg_health_cortex = {
|
||||
MG_HEALTH_CORTEX_STORAGE,
|
||||
MG_HEALTH_CORTEX_STORAGE_SIZE,
|
||||
storage_init,
|
||||
storage_read,
|
||||
storage_write,
|
||||
};
|
||||
|
||||
static bool storage_bounds(const struct mg_health_cortex *ctx, size_t offset,
|
||||
size_t len) {
|
||||
return ctx != NULL && ctx->storage != NULL && offset <= ctx->storage_size &&
|
||||
len <= ctx->storage_size - offset;
|
||||
}
|
||||
|
||||
static bool storage_configured(const struct mg_health_cortex *ctx) {
|
||||
return ctx != NULL && ctx->storage != NULL && ctx->storage_size != 0 &&
|
||||
ctx->storage_size >= sizeof(struct mg_health_cortex_record) &&
|
||||
ctx->init != NULL && ctx->read != NULL && ctx->write != NULL;
|
||||
}
|
||||
|
||||
static bool record_read(struct mg_health_cortex *ctx,
|
||||
struct mg_health_cortex_record *record) {
|
||||
static const char magic[] = MG_HEALTH_MAGIC;
|
||||
if (ctx == NULL || ctx->read == NULL ||
|
||||
!storage_bounds(ctx, 0, sizeof(*record)) ||
|
||||
!ctx->read(ctx->storage, 0, record, sizeof(*record))) {
|
||||
return false;
|
||||
}
|
||||
return memcmp(record->magic, magic, sizeof(record->magic)) == 0 &&
|
||||
record->version == MG_HEALTH_CORTEX_VERSION &&
|
||||
record->stack_word_count <= MG_HEALTH_CORTEX_STACK_WORDS;
|
||||
}
|
||||
|
||||
static bool record_write(struct mg_health_cortex *ctx,
|
||||
const struct mg_health_cortex_record *record) {
|
||||
char invalid[sizeof(record->magic)] = {0};
|
||||
size_t offset = sizeof(record->magic);
|
||||
if (ctx == NULL || ctx->write == NULL ||
|
||||
!storage_bounds(ctx, 0, sizeof(*record)) ||
|
||||
!ctx->write(ctx->storage, 0, &invalid, sizeof(invalid))) {
|
||||
return false;
|
||||
}
|
||||
if (!ctx->write(ctx->storage, offset, (const uint8_t *) record + offset,
|
||||
sizeof(*record) - offset)) {
|
||||
return false;
|
||||
}
|
||||
if (!ctx->write(ctx->storage, 0, record->magic, sizeof(record->magic)))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool stack_range(const void *ptr, uintptr_t *lo, uintptr_t *hi) {
|
||||
if (mg_health_cortex_stack_range != NULL &&
|
||||
mg_health_cortex_stack_range(ptr, lo, hi)) {
|
||||
return *lo < *hi;
|
||||
} else if (&__StackLimit != NULL && &__StackTop != NULL) {
|
||||
*lo = (uintptr_t) &__StackLimit;
|
||||
*hi = (uintptr_t) &__StackTop;
|
||||
} else if (&_sstack != NULL && &_estack != NULL) {
|
||||
*lo = (uintptr_t) &_sstack;
|
||||
*hi = (uintptr_t) &_estack;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return *lo < *hi;
|
||||
}
|
||||
|
||||
static int stack_valid(const void *ptr, size_t words) {
|
||||
uintptr_t lo, hi, p = (uintptr_t) ptr;
|
||||
if ((p & 3U) != 0) return 0;
|
||||
if (!stack_range(ptr, &lo, &hi)) return -1;
|
||||
return p >= lo && p <= hi && words <= (hi - p) / sizeof(uint32_t);
|
||||
}
|
||||
|
||||
static bool has_fault_status(uint32_t part) {
|
||||
return part == MG_CPU_CORTEX_M3 || part == MG_CPU_CORTEX_M4 ||
|
||||
part == MG_CPU_CORTEX_M7 || part == MG_CPU_CORTEX_M33 ||
|
||||
part == MG_CPU_CORTEX_M35P || part == MG_CPU_CORTEX_M52 ||
|
||||
part == MG_CPU_CORTEX_M55 || part == MG_CPU_CORTEX_M85;
|
||||
}
|
||||
|
||||
static const char *core_name(uint32_t part) {
|
||||
switch (part) {
|
||||
case MG_CPU_CORTEX_M0:
|
||||
return "cortex-m0";
|
||||
case MG_CPU_CORTEX_M0P:
|
||||
return "cortex-m0+";
|
||||
case MG_CPU_CORTEX_M23:
|
||||
return "cortex-m23";
|
||||
case MG_CPU_CORTEX_M3:
|
||||
return "cortex-m3";
|
||||
case MG_CPU_CORTEX_M4:
|
||||
return "cortex-m4";
|
||||
case MG_CPU_CORTEX_M7:
|
||||
return "cortex-m7";
|
||||
case MG_CPU_CORTEX_M33:
|
||||
return "cortex-m33";
|
||||
case MG_CPU_CORTEX_M35P:
|
||||
return "cortex-m35p";
|
||||
case MG_CPU_CORTEX_M52:
|
||||
return "cortex-m52";
|
||||
case MG_CPU_CORTEX_M55:
|
||||
return "cortex-m55";
|
||||
case MG_CPU_CORTEX_M85:
|
||||
return "cortex-m85";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *arch_name(uint32_t part) {
|
||||
if (part == MG_CPU_CORTEX_M0 || part == MG_CPU_CORTEX_M0P) return "armv6-m";
|
||||
if (part == MG_CPU_CORTEX_M3) return "armv7-m";
|
||||
if (part == MG_CPU_CORTEX_M4 || part == MG_CPU_CORTEX_M7) return "armv7e-m";
|
||||
return "armv8-m";
|
||||
}
|
||||
|
||||
static bool json_append(char *buf, size_t size, size_t *offset, const char *fmt,
|
||||
...) {
|
||||
va_list ap;
|
||||
size_t available, n;
|
||||
if (*offset >= size) return false;
|
||||
available = size - *offset;
|
||||
va_start(ap, fmt);
|
||||
n = mg_vsnprintf(buf + *offset, available, fmt, &ap);
|
||||
va_end(ap);
|
||||
if (n >= available) {
|
||||
*offset = size;
|
||||
return false;
|
||||
}
|
||||
*offset += n;
|
||||
return true;
|
||||
}
|
||||
|
||||
void mg_health_cortex_init(struct mg_health_cortex *ctx) {
|
||||
if (storage_configured(ctx)) {
|
||||
(void) ctx->init(ctx->storage, ctx->storage_size);
|
||||
}
|
||||
}
|
||||
|
||||
struct mg_str mg_health_cortex_get_blob(void *data) {
|
||||
struct mg_health_cortex *ctx = (struct mg_health_cortex *) data;
|
||||
struct mg_health_cortex_record *r = &s_record;
|
||||
uint32_t part;
|
||||
size_t n = 0, i;
|
||||
bool ok;
|
||||
|
||||
if (!storage_configured(ctx)) {
|
||||
return mg_str(
|
||||
"{\"valid\":false,\"error\":\"health storage is not configured\"}");
|
||||
}
|
||||
if (!record_read(ctx, r)) return mg_str("{\"valid\":false}");
|
||||
part = (r->cpuid >> 4) & 0xfffU;
|
||||
ok = json_append(
|
||||
s_report, sizeof(s_report), &n,
|
||||
"{\"valid\":true,\"version\":%lu,\"arch\":\"%s\","
|
||||
"\"core\":\"%s\",\"image\":\"firmware.elf\","
|
||||
"\"map\":\"firmware.elf.map\",\"binary\":\"firmware.bin\","
|
||||
"\"cpuid\":\"0x%08lx\",\"exception_sp\":\"0x%08lx\","
|
||||
"\"exc_return\":\"0x%08lx\",\"exception_valid\":%s,"
|
||||
"\"stack_valid\":%s,\"regs\":{"
|
||||
"\"r0\":\"0x%08lx\",\"r1\":\"0x%08lx\","
|
||||
"\"r2\":\"0x%08lx\",\"r3\":\"0x%08lx\","
|
||||
"\"r4\":\"0x%08lx\",\"r5\":\"0x%08lx\","
|
||||
"\"r6\":\"0x%08lx\",\"r7\":\"0x%08lx\","
|
||||
"\"r8\":\"0x%08lx\",\"r9\":\"0x%08lx\","
|
||||
"\"r10\":\"0x%08lx\",\"r11\":\"0x%08lx\","
|
||||
"\"r12\":\"0x%08lx\",\"sp\":\"0x%08lx\","
|
||||
"\"lr\":\"0x%08lx\",\"pc\":\"0x%08lx\","
|
||||
"\"xpsr\":\"0x%08lx\"},\"fault\":{"
|
||||
"\"cfsr\":\"0x%08lx\",\"hfsr\":\"0x%08lx\","
|
||||
"\"dfsr\":\"0x%08lx\",\"afsr\":\"0x%08lx\","
|
||||
"\"mmfar\":\"0x%08lx\",\"bfar\":\"0x%08lx\","
|
||||
"\"abfsr\":\"0x%08lx\"},\"stack\":{"
|
||||
"\"addr\":\"0x%08lx\",\"word_size\":4,\"words\":[",
|
||||
(unsigned long) r->version, arch_name(part), core_name(part),
|
||||
(unsigned long) r->cpuid, (unsigned long) r->exception_sp,
|
||||
(unsigned long) r->exc_return, r->exception_valid > 0 ? "true" : "false",
|
||||
r->stack_valid > 0 ? "true" : "false", (unsigned long) r->r0,
|
||||
(unsigned long) r->r1, (unsigned long) r->r2, (unsigned long) r->r3,
|
||||
(unsigned long) r->r4, (unsigned long) r->r5, (unsigned long) r->r6,
|
||||
(unsigned long) r->r7, (unsigned long) r->r8, (unsigned long) r->r9,
|
||||
(unsigned long) r->r10, (unsigned long) r->r11, (unsigned long) r->r12,
|
||||
(unsigned long) r->sp, (unsigned long) r->lr, (unsigned long) r->pc,
|
||||
(unsigned long) r->psr, (unsigned long) r->cfsr, (unsigned long) r->hfsr,
|
||||
(unsigned long) r->dfsr, (unsigned long) r->afsr,
|
||||
(unsigned long) r->mmfar, (unsigned long) r->bfar,
|
||||
(unsigned long) r->abfsr, (unsigned long) r->sp);
|
||||
for (i = 0; ok && i < r->stack_word_count; i++) {
|
||||
ok = json_append(s_report, sizeof(s_report), &n, "%s\"0x%08lx\"",
|
||||
i == 0 ? "" : ",", (unsigned long) r->stack_words[i]);
|
||||
}
|
||||
if (ok) ok = json_append(s_report, sizeof(s_report), &n, "]}}");
|
||||
return ok ? mg_str_n(s_report, n)
|
||||
: mg_str("{\"valid\":false,\"error\":\"JSON overflow\"}");
|
||||
}
|
||||
|
||||
static void __attribute__((used)) mg_health_cortex_handler(
|
||||
uint32_t *exception_stack, uint32_t lr,
|
||||
const uint32_t *callee_saved) {
|
||||
static const char magic[] = MG_HEALTH_MAGIC;
|
||||
uint32_t cpuid = MG_SCB_CPUID;
|
||||
uint32_t part = (cpuid >> 4) & 0xfffU;
|
||||
size_t fp_words = (lr & (1U << 4)) == 0 ? 18U : 0U;
|
||||
int valid = stack_valid(exception_stack, fp_words + 8U);
|
||||
struct mg_health_cortex_record *r = &s_record;
|
||||
volatile uint32_t *words = (volatile uint32_t *) r;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < sizeof(*r) / sizeof(*words); i++) words[i] = 0;
|
||||
r->version = MG_HEALTH_CORTEX_VERSION;
|
||||
r->cpuid = cpuid;
|
||||
r->exception_sp = (uint32_t) (uintptr_t) exception_stack;
|
||||
r->exc_return = lr;
|
||||
r->exception_valid = valid;
|
||||
r->r4 = callee_saved[0];
|
||||
r->r5 = callee_saved[1];
|
||||
r->r6 = callee_saved[2];
|
||||
r->r7 = callee_saved[3];
|
||||
r->r8 = callee_saved[4];
|
||||
r->r9 = callee_saved[5];
|
||||
r->r10 = callee_saved[6];
|
||||
r->r11 = callee_saved[7];
|
||||
if (valid > 0) {
|
||||
uint32_t *core_frame = exception_stack + fp_words;
|
||||
uint32_t *fault_sp;
|
||||
uintptr_t lo, hi, p;
|
||||
size_t count;
|
||||
r->r0 = core_frame[0];
|
||||
r->r1 = core_frame[1];
|
||||
r->r2 = core_frame[2];
|
||||
r->r3 = core_frame[3];
|
||||
r->r12 = core_frame[4];
|
||||
r->lr = core_frame[5];
|
||||
r->pc = core_frame[6];
|
||||
r->psr = core_frame[7];
|
||||
fault_sp = core_frame + 8;
|
||||
if (r->psr & (1U << 9)) fault_sp++;
|
||||
r->sp = (uint32_t) (uintptr_t) fault_sp;
|
||||
r->stack_valid = stack_valid(fault_sp, 0);
|
||||
p = (uintptr_t) fault_sp;
|
||||
if (r->stack_valid > 0 && stack_range(fault_sp, &lo, &hi)) {
|
||||
(void) lo;
|
||||
count = (hi - p) / sizeof(uint32_t);
|
||||
if (count > MG_HEALTH_CORTEX_STACK_WORDS)
|
||||
count = MG_HEALTH_CORTEX_STACK_WORDS;
|
||||
r->stack_word_count = (uint32_t) count;
|
||||
for (i = 0; i < count; i++) r->stack_words[i] = fault_sp[i];
|
||||
}
|
||||
}
|
||||
if (has_fault_status(part)) {
|
||||
r->cfsr = MG_SCB_CFSR;
|
||||
r->hfsr = MG_SCB_HFSR;
|
||||
r->dfsr = MG_SCB_DFSR;
|
||||
r->afsr = MG_SCB_AFSR;
|
||||
r->bfar = MG_SCB_BFAR;
|
||||
r->mmfar = MG_SCB_MMFAR;
|
||||
if (part == MG_CPU_CORTEX_M7) r->abfsr = MG_SCB_ABFSR;
|
||||
}
|
||||
memcpy(r->magic, magic, sizeof(r->magic));
|
||||
(void) record_write(&mg_health_cortex, r);
|
||||
for (;;) (void) 0;
|
||||
}
|
||||
|
||||
void HardFault_Handler(void);
|
||||
__attribute__((naked)) void HardFault_Handler(void) {
|
||||
__asm volatile(
|
||||
"mov r0, lr \n"
|
||||
"movs r1, #4 \n"
|
||||
"tst r0, r1 \n"
|
||||
"beq 1f \n"
|
||||
"mrs r0, psp \n"
|
||||
"b 2f \n"
|
||||
"1: \n"
|
||||
"mrs r0, msp \n"
|
||||
"2: \n"
|
||||
"mov r1, lr \n"
|
||||
"stmdb sp!, {r4-r11} \n"
|
||||
"mov r2, sp \n"
|
||||
"b mg_health_cortex_handler \n");
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include "health.h"
|
||||
|
||||
#define MG_HEALTH_CORTEX_VERSION 1U
|
||||
|
||||
#ifndef MG_HEALTH_CORTEX_STORAGE
|
||||
#define MG_HEALTH_CORTEX_STORAGE NULL
|
||||
#endif
|
||||
|
||||
#ifndef MG_HEALTH_CORTEX_STORAGE_SIZE
|
||||
#define MG_HEALTH_CORTEX_STORAGE_SIZE 0
|
||||
#endif
|
||||
|
||||
#ifndef MG_HEALTH_CORTEX_STACK_WORDS
|
||||
#define MG_HEALTH_CORTEX_STACK_WORDS 980U
|
||||
#endif
|
||||
|
||||
#ifndef MG_HEALTH_CORTEX_REPORT_SIZE
|
||||
#define MG_HEALTH_CORTEX_REPORT_SIZE 16384U
|
||||
#endif
|
||||
|
||||
// Storage operations are deliberately format-agnostic. H723 uses the default
|
||||
// direct-memory implementation; boards with different retained storage can
|
||||
// replace these callbacks before calling MG_HEALTH_INIT()
|
||||
struct mg_health_cortex {
|
||||
void *storage;
|
||||
size_t storage_size;
|
||||
bool (*init)(void *, size_t);
|
||||
bool (*read)(const void *, size_t, void *, size_t);
|
||||
bool (*write)(void *, size_t, const void *, size_t);
|
||||
};
|
||||
|
||||
struct mg_health_cortex_record {
|
||||
char magic[4];
|
||||
uint32_t version;
|
||||
|
||||
uint32_t cpuid;
|
||||
uint32_t exception_sp;
|
||||
uint32_t sp;
|
||||
uint32_t exc_return;
|
||||
int32_t exception_valid;
|
||||
int32_t stack_valid;
|
||||
|
||||
uint32_t r0;
|
||||
uint32_t r1;
|
||||
uint32_t r2;
|
||||
uint32_t r3;
|
||||
uint32_t r4;
|
||||
uint32_t r5;
|
||||
uint32_t r6;
|
||||
uint32_t r7;
|
||||
uint32_t r8;
|
||||
uint32_t r9;
|
||||
uint32_t r10;
|
||||
uint32_t r11;
|
||||
uint32_t r12;
|
||||
uint32_t lr;
|
||||
uint32_t pc;
|
||||
uint32_t psr;
|
||||
|
||||
uint32_t cfsr;
|
||||
uint32_t hfsr;
|
||||
uint32_t dfsr;
|
||||
uint32_t afsr;
|
||||
uint32_t bfar;
|
||||
uint32_t mmfar;
|
||||
uint32_t abfsr;
|
||||
|
||||
uint32_t stack_word_count;
|
||||
uint32_t stack_words[MG_HEALTH_CORTEX_STACK_WORDS];
|
||||
};
|
||||
|
||||
extern struct mg_health_cortex mg_health_cortex;
|
||||
|
||||
void mg_health_cortex_init(struct mg_health_cortex *);
|
||||
struct mg_str mg_health_cortex_get_blob(void *);
|
||||
+9
-5
@@ -91,11 +91,15 @@ static void mg_mdash_fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||
}
|
||||
|
||||
static void mg_mdash_rpc_get_info(struct mg_rpc_req *r) {
|
||||
mg_rpc_ok(r, "{%m:%m,%m:%llu,%m:%m,%m:\"mws.%d\"}", MG_ESC("fw_version"),
|
||||
MG_ESC(MG_MDASH_FIRMWARE_VERSION), MG_ESC("uptime"),
|
||||
(uint64_t) (mg_millis() / 1000), MG_ESC("reboot_reason"),
|
||||
MG_ESC(mg_health_reason_str(mg_health_reason())), MG_ESC("arch"),
|
||||
MG_ARCH);
|
||||
struct mg_str blob = mg_health_get_blob();
|
||||
if (blob.buf == NULL) blob.len = 0;
|
||||
mg_rpc_ok(r, "{%m:%m,%m:%llu,%m:%m,%m:\"mws.%d\",%m:{%m:%m,%m:%llu,%m:%m}}",
|
||||
MG_ESC("fw_version"), MG_ESC(MG_MDASH_FIRMWARE_VERSION), MG_ESC("uptime"),
|
||||
(uint64_t) (mg_millis() / 1000), MG_ESC("reboot_reason"),
|
||||
MG_ESC(mg_health_reason_str(mg_health_reason())), MG_ESC("arch"), MG_ARCH,
|
||||
MG_ESC("health"), MG_ESC("encoding"), MG_ESC("base64"), MG_ESC("size"),
|
||||
(uint64_t) blob.len, MG_ESC("data"), mg_print_base64, (int) blob.len,
|
||||
(uint8_t *) (blob.buf == NULL ? "" : blob.buf));
|
||||
}
|
||||
|
||||
static void mg_mdash_rpc_ota_begin(struct mg_rpc_req *r) {
|
||||
|
||||
+1
-1
@@ -106,7 +106,7 @@ mongoose.c: Makefile $(wildcard ../src/*.c) $(wildcard ../src/drivers/*.c)
|
||||
cd .. && (export LC_ALL=C ; cat src/license.h; echo; echo '#include "mongoose.h"' ; (for F in src/*.c src/drivers/*.c ; do echo; echo '#ifdef MG_ENABLE_LINES'; echo "#line 1 \"$$F\""; echo '#endif'; cat $$F | sed -e 's,#include ".*,,'; done))> $@
|
||||
|
||||
mongoose.h: $(HDRS) Makefile
|
||||
cd .. && (export LC_ALL=C ; cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/os_*.h src/net_ft.h src/net_lwip.h src/net_rl.h src/config.h src/profile.h src/str.h src/queue.h src/fmt.h src/printf.h src/log.h src/timer.h src/fs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/sha256.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_x25519.h src/tls_aes128.h src/tls_uecc.h src/tls_chacha20.h src/tls_rsa.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/modbus.h src/json.h src/jwt.h src/rpc.h src/dash.h src/ota.h src/health.h src/mdash.h src/flash.h src/wifi.h src/l2.h src/net_builtin.h src/bsd.h src/drivers/*.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@
|
||||
cd .. && (export LC_ALL=C ; cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/os_*.h src/net_ft.h src/net_lwip.h src/net_rl.h src/config.h src/profile.h src/str.h src/queue.h src/fmt.h src/printf.h src/log.h src/timer.h src/fs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/sha256.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_x25519.h src/tls_aes128.h src/tls_uecc.h src/tls_chacha20.h src/tls_rsa.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/modbus.h src/json.h src/jwt.h src/rpc.h src/dash.h src/ota.h src/health.h src/health_cortex.h src/mdash.h src/flash.h src/wifi.h src/l2.h src/net_builtin.h src/bsd.h src/drivers/*.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@
|
||||
|
||||
# Check that all external (exported) symbols have "mg_" prefix
|
||||
mg_prefix: mongoose.c mongoose.h
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
# Copyright (c) 2026 Cesanta Software Limited
|
||||
# Environment setup: https://mongoose.ws/docs/getting-started/build-environment/
|
||||
|
||||
MGDIR = ../../../..
|
||||
MGDIR = mongoose
|
||||
|
||||
CFLAGS = -W -Wall -Wextra -Wundef -Wshadow -Wdouble-promotion
|
||||
CFLAGS += -Wformat-truncation -fno-common -Wconversion -Wno-sign-conversion
|
||||
CFLAGS += -g3 -Os -ffunction-sections -fdata-sections
|
||||
CFLAGS += -I. -I$(MGDIR) -Icmsis_core/CMSIS/Core/Include -Icmsis_h7/Include
|
||||
CFLAGS += -fno-omit-frame-pointer -funwind-tables
|
||||
CFLAGS += -I. -Icmsis_core/CMSIS/Core/Include -Icmsis_h7/Include -Imongoose
|
||||
CFLAGS += -mcpu=cortex-m7 -mthumb -mfloat-abi=hard -mfpu=fpv5-d16 $(CFLAGS_EXTRA)
|
||||
LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs nosys.specs -lc -lgcc -Wl,--gc-sections -Wl,-Map=$@.map
|
||||
|
||||
@@ -16,6 +17,10 @@ SOURCES += $(MGDIR)/mongoose.c
|
||||
|
||||
all build example: firmware.bin
|
||||
|
||||
mongoose/mongoose.c mongoose/mongoose.h:
|
||||
mkdir -p mongoose/
|
||||
cp ../../../../mongoose.[ch] mongoose/
|
||||
|
||||
firmware.elf: cmsis_core cmsis_h7 hal.h link.ld Makefile $(SOURCES) $(MGDIR)/mongoose.h mongoose_config.h
|
||||
arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(CFLAGS_EXTRA) $(LDFLAGS) -o $@
|
||||
|
||||
@@ -24,6 +29,15 @@ firmware.bin: firmware.elf
|
||||
@echo
|
||||
@echo "To flash, run 'make flash', or use STM32CubeProgrammer"
|
||||
|
||||
crash.json:
|
||||
@echo "To get crash.json, curl http://DEVICE_IP/api/report after recovering from \
|
||||
a hardfault and store the result in crash.json"
|
||||
|
||||
stacktrace.txt: firmware.bin firmware.elf.map crash.json fake_target.py stacktrace.gdb
|
||||
python3 -u fake_target.py 3334 > fake_target.log 2>&1 &
|
||||
sleep 0.2
|
||||
gdb-multiarch -q firmware.elf -batch -x stacktrace.gdb > $@
|
||||
|
||||
flash: firmware.bin
|
||||
STM32_Programmer_CLI -c port=swd -w firmware.elf -hardRst
|
||||
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
|
||||
|
||||
FLASH_BASE = 0x08000000
|
||||
FAULT_REGS = {
|
||||
"cfsr": 0xE000ED28,
|
||||
"hfsr": 0xE000ED2C,
|
||||
"dfsr": 0xE000ED30,
|
||||
"afsr": 0xE000ED3C,
|
||||
"mmfar": 0xE000ED34,
|
||||
"bfar": 0xE000ED38,
|
||||
"abfsr": 0xE000EFA8,
|
||||
}
|
||||
|
||||
|
||||
def checksum(s):
|
||||
return sum(s.encode("ascii")) & 0xff
|
||||
|
||||
|
||||
def packet(s):
|
||||
return "$%s#%02x" % (s, checksum(s))
|
||||
|
||||
|
||||
def parse_packet(conn):
|
||||
while True:
|
||||
c = conn.recv(1)
|
||||
if c == b"$":
|
||||
break
|
||||
if c == b"\x03":
|
||||
return "\x03"
|
||||
data = bytearray()
|
||||
while True:
|
||||
c = conn.recv(1)
|
||||
if c == b"#":
|
||||
conn.recv(2)
|
||||
conn.sendall(b"+")
|
||||
return data.decode("ascii")
|
||||
data += c
|
||||
|
||||
|
||||
def symbols(path):
|
||||
syms = {}
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
lines = f.readlines()
|
||||
for line in lines:
|
||||
fields = line.split()
|
||||
if len(fields) >= 2 and fields[0].startswith("0x"):
|
||||
syms[fields[-1]] = int(fields[0], 16)
|
||||
return syms
|
||||
|
||||
|
||||
def value(v, syms):
|
||||
if isinstance(v, int):
|
||||
return v & 0xffffffff
|
||||
s = str(v).replace(" ", "")
|
||||
for op in ("+", "-"):
|
||||
if op in s:
|
||||
name, off = s.split(op, 1)
|
||||
n = int(off, 0)
|
||||
return (syms[name] + n if op == "+" else syms[name] - n) & 0xffffffff
|
||||
if s.startswith("0x"):
|
||||
return int(s, 16) & 0xffffffff
|
||||
return syms[s] & 0xffffffff
|
||||
|
||||
|
||||
def readmem(mem, addr, size):
|
||||
data = bytearray()
|
||||
for i in range(size):
|
||||
b = mem.get(addr + i)
|
||||
if b is None:
|
||||
return None
|
||||
data.append(b)
|
||||
return data.hex()
|
||||
|
||||
|
||||
def hex_decode(s):
|
||||
return bytes.fromhex(s).decode("ascii")
|
||||
|
||||
|
||||
def main():
|
||||
port = int(sys.argv[1]) if len(sys.argv) > 1 else 3333
|
||||
with open("crash.json", "r", encoding="utf-8") as f:
|
||||
crash = json.load(f)
|
||||
image = crash.get("image", "crash.elf")
|
||||
syms = symbols(crash.get("map", image + ".map"))
|
||||
with open(crash.get("binary", image.rsplit(".", 1)[0] + ".bin"), "rb") as f:
|
||||
flash = f.read()
|
||||
|
||||
regs = crash["regs"]
|
||||
stack = crash["stack"]
|
||||
mem = {}
|
||||
for i, b in enumerate(flash):
|
||||
mem[FLASH_BASE + i] = b
|
||||
addr = value(stack["addr"], syms)
|
||||
for i, word in enumerate(stack["words"]):
|
||||
w = value(word, syms)
|
||||
for j, b in enumerate(struct.pack("<I", w)):
|
||||
mem[addr + i * 4 + j] = b
|
||||
for name, addr in FAULT_REGS.items():
|
||||
if name in crash.get("fault", {}):
|
||||
w = value(crash["fault"][name], syms)
|
||||
for j, b in enumerate(struct.pack("<I", w)):
|
||||
mem[addr + j] = b
|
||||
|
||||
order = ["r%d" % n for n in range(13)] + ["sp", "lr", "pc", "xpsr"]
|
||||
regvals = [value(regs[name], syms) for name in order]
|
||||
regpkt = b"".join(struct.pack("<I", r) for r in regvals).hex()
|
||||
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
s.bind(("127.0.0.1", port))
|
||||
s.listen(1)
|
||||
print("fake target listening on 127.0.0.1:%d" % port, flush=True)
|
||||
conn, _ = s.accept()
|
||||
with conn:
|
||||
while True:
|
||||
req = parse_packet(conn)
|
||||
if req.startswith("qSupported"):
|
||||
rep = "PacketSize=4000"
|
||||
elif req.startswith("qRcmd,"):
|
||||
print("monitor %s" % hex_decode(req[6:]), flush=True)
|
||||
rep = "OK"
|
||||
elif req in ("?", "vStopped", "\x03", "c", "s") or \
|
||||
req.startswith(("C", "S", "vCont;c", "vCont;s")):
|
||||
rep = "T05thread:1;"
|
||||
elif req == "vCont?":
|
||||
rep = "vCont;c;s"
|
||||
elif req == "qC":
|
||||
rep = "QC1"
|
||||
elif req == "qfThreadInfo":
|
||||
rep = "m1"
|
||||
elif req == "qsThreadInfo":
|
||||
rep = "l"
|
||||
elif req == "qAttached":
|
||||
rep = "1"
|
||||
elif req.startswith("T"):
|
||||
rep = "OK"
|
||||
elif req.startswith(("Hg", "Hc", "QStartNoAckMode")):
|
||||
rep = "OK"
|
||||
elif req == "g":
|
||||
rep = regpkt
|
||||
elif req.startswith("p"):
|
||||
n = int(req[1:], 16)
|
||||
rep = struct.pack("<I", regvals[n]).hex() if n < len(regvals) else "00000000"
|
||||
elif req.startswith("m"):
|
||||
a, n = req[1:].split(",", 1)
|
||||
data = readmem(mem, int(a, 16), int(n, 16))
|
||||
rep = data if data is not None else "E01"
|
||||
elif req.startswith(("q", "v", "Z", "z")):
|
||||
rep = ""
|
||||
elif req in ("D", "k"):
|
||||
conn.sendall(packet("OK").encode("ascii"))
|
||||
break
|
||||
else:
|
||||
rep = ""
|
||||
conn.sendall(packet(rep).encode("ascii"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -93,6 +93,7 @@ __attribute__((weak)) int _getpid(void) {
|
||||
|
||||
__attribute__((weak)) int _write(int fd, char *ptr, int len) {
|
||||
(void) fd, (void) ptr, (void) len;
|
||||
hal_uart_write_buf(USART3, ptr, len);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,12 +9,23 @@ MEMORY {
|
||||
itcmram (rwx) : ORIGIN = 0x00000000, LENGTH = 64K
|
||||
}
|
||||
_estack = ORIGIN(ram_d1) + LENGTH(ram_d1);
|
||||
_sstack = ORIGIN(ram_d1);
|
||||
|
||||
SECTIONS {
|
||||
.vectors : { KEEP(*(.isr_vector)) } > flash
|
||||
.text : { *(.text* .text.*) } > flash
|
||||
.rodata : { *(.rodata*) } > flash
|
||||
|
||||
.ARM.extab : {
|
||||
*(.ARM.extab* .gnu.linkonce.armextab.*)
|
||||
} > flash
|
||||
|
||||
.ARM.exidx : {
|
||||
__exidx_start = .;
|
||||
KEEP(*(.ARM.exidx* .gnu.linkonce.armexidx.*))
|
||||
__exidx_end = .;
|
||||
} > flash
|
||||
|
||||
/* Note the .iram section, for functions copied to RAM. Required for MG_IRAM OTA support */
|
||||
.data : { _sdata = .; *(.first_data) *(.iram .iram* .iram.*) *(.data SORT(.data.*)) _edata = .; } > ram_d1 AT > flash
|
||||
_sidata = LOADADDR(.data);
|
||||
@@ -23,10 +34,6 @@ SECTIONS {
|
||||
|
||||
.eth_ram : { *(.eth_ram .eth_ram*) } > ram_d1 AT > flash
|
||||
|
||||
/* Health record. NOLOAD and in unused DTCM, so startup code neither copies
|
||||
nor zeroes it and the contents survive a warm reset */
|
||||
.mg_health (NOLOAD) : { KEEP(*(.mg_health .mg_health*)) } > dtcmram
|
||||
|
||||
. = ALIGN(8);
|
||||
_end = .;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,14 @@
|
||||
#define LED2 PIN('E', 1)
|
||||
#define LED3 PIN('B', 14)
|
||||
|
||||
void hal_storage_init(void) {
|
||||
hal_backup_domain_init();
|
||||
RCC->AHB4ENR |= RCC_AHB4ENR_BKPRAMEN;
|
||||
(void) RCC->AHB4ENR;
|
||||
PWR->CR2 |= PWR_CR2_BREN;
|
||||
while ((PWR->CR2 & PWR_CR2_BRRDY) == 0) (void) 0;
|
||||
}
|
||||
|
||||
static void log_fn(char ch, void *param) {
|
||||
hal_uart_write_buf(param, &ch, 1);
|
||||
}
|
||||
@@ -28,50 +36,6 @@ static void blink_task(void) {
|
||||
}
|
||||
}
|
||||
|
||||
// Fault handler body. Runs in exception context: no printf, no malloc, no
|
||||
// blocking calls. Records the crash reason and a backtrace into the health
|
||||
// record, then resets.
|
||||
// "used" keeps the linker from garbage-collecting this section: the only
|
||||
// reference is the "b fault_c" branch in the naked handler below
|
||||
__attribute__((used, noinline)) static void fault_c(uint32_t *sp) {
|
||||
extern uint8_t _estack; // End of the main RAM region, defined in link.ld
|
||||
size_t n = 0;
|
||||
mg_health_record.reset_reason = MG_HEALTH_RESET_FAULT;
|
||||
// Frame 0 is the faulting PC, frame 1 the caller's LR. Deeper frames come
|
||||
// from a heuristic stack walk: BL pushes an odd return address that lives
|
||||
// in flash. A stack word that merely looks like an address shows up as a
|
||||
// bogus frame when symbolised, which is easy to filter by eye
|
||||
mg_health_record.backtrace[n++] = sp[6] & ~1U; // Stacked PC
|
||||
mg_health_record.backtrace[n++] = sp[5] & ~1U; // Stacked LR
|
||||
for (uint32_t *p = sp + 8;
|
||||
n < MG_HEALTH_BACKTRACE &&
|
||||
(uintptr_t) p < (uintptr_t) sp + 4096U && // Bound the scan
|
||||
(uintptr_t) p < (uintptr_t) &_estack; // Stay in RAM
|
||||
p++) {
|
||||
uint32_t v = *p;
|
||||
if ((v & 1U) && v >= 0x08000000U && v < 0x08000000U + 1024U * 1024U) {
|
||||
mg_health_record.backtrace[n++] = v & ~1U;
|
||||
}
|
||||
}
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
// Common fault entry. EXC_RETURN bit 2 tells which stack was in use:
|
||||
// 0 = MSP, 1 = PSP. Load the faulting SP into r0 and hand it to fault_c()
|
||||
__attribute__((naked)) void HardFault_Handler(void) {
|
||||
__asm volatile(
|
||||
"tst lr, #4\n\t" // Test EXC_RETURN bit 2
|
||||
"ite eq\n\t" // If zero, use MSP; else PSP
|
||||
"mrseq r0, msp\n\t"
|
||||
"mrsne r0, psp\n\t"
|
||||
"b fault_c\n\t");
|
||||
}
|
||||
|
||||
// Route the other fault types through the same entry point
|
||||
void MemManage_Handler(void) __attribute__((alias("HardFault_Handler")));
|
||||
void BusFault_Handler(void) __attribute__((alias("HardFault_Handler")));
|
||||
void UsageFault_Handler(void) __attribute__((alias("HardFault_Handler")));
|
||||
|
||||
uint64_t mg_millis(void) {
|
||||
return hal_get_tick();
|
||||
}
|
||||
@@ -92,6 +56,10 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
} else if (mg_match(hm->uri, mg_str("/api/kill"), NULL)) {
|
||||
SCB->SHCSR &= ~SCB_SHCSR_USGFAULTENA_Msk;
|
||||
__asm volatile("udf #0");
|
||||
} else if (mg_match(hm->uri, mg_str("/api/report"), NULL)) {
|
||||
struct mg_str report = mg_health_get_blob();
|
||||
mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%.*s\n",
|
||||
(int) report.len, report.buf);
|
||||
} else {
|
||||
mg_http_reply(c, 200, "", "Hi from Mongoose, tick %llu\n",
|
||||
hal_get_tick());
|
||||
@@ -101,9 +69,8 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
|
||||
int main(void) {
|
||||
hal_clock_init();
|
||||
|
||||
MG_HEALTH_INIT(); // Must be called after clock init
|
||||
|
||||
hal_storage_init();
|
||||
MG_HEALTH_INIT();
|
||||
hal_uart_init(UART_DEBUG, UART_DEBUG_TX_PIN, UART_DEBUG_RX_PIN, 115200);
|
||||
mg_log_set_fn(log_fn, UART_DEBUG);
|
||||
hal_rng_init();
|
||||
@@ -114,19 +81,7 @@ int main(void) {
|
||||
|
||||
MG_INFO(("Initialised. CPU clock: %lu MHz", SystemCoreClock / 1000000));
|
||||
|
||||
// Report the previous boot's crash backtrace, if any
|
||||
if (mg_health_reason() == MG_HEALTH_RESET_FAULT) {
|
||||
char buf[MG_HEALTH_BACKTRACE * 10 + 100];
|
||||
mg_snprintf(buf, sizeof(buf), "%s",
|
||||
"arm-none-eabi-addr2line -pfiaC -e firmware.elf");
|
||||
for (int i = 0; i < MG_HEALTH_BACKTRACE; i++) {
|
||||
if (mg_health_record.backtrace[i] == 0) break;
|
||||
mg_snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), " 0x%08lx",
|
||||
mg_health_record.backtrace[i]);
|
||||
}
|
||||
// mg_snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "\n");
|
||||
MG_INFO(("Previous boot crashed! Analyse with: %s", buf));
|
||||
}
|
||||
// MG_OTA_BOOT_CHECK(); // Must be called after clock init
|
||||
|
||||
struct mg_mgr mgr;
|
||||
mg_mgr_init(&mgr);
|
||||
|
||||
@@ -26,8 +26,11 @@
|
||||
mac[5] = MGUID[2] & 255; \
|
||||
} while (0)
|
||||
|
||||
// Crash report support. Health record lives in the .mg_health region, see link.ld
|
||||
#define MG_HEALTH_RAM __attribute__((section(".mg_health")))
|
||||
// Persist Cortex-M crash reports in the 4 KB backup SRAM. hal_storage_init()
|
||||
// enables this memory before MG_HEALTH_INIT() is called.
|
||||
#define MG_HEALTH MG_HEALTH_CORTEX
|
||||
#define MG_HEALTH_CORTEX_STORAGE ((void *) D3_BKPSRAM_BASE)
|
||||
#define MG_HEALTH_CORTEX_STORAGE_SIZE (4U * 1024U)
|
||||
|
||||
// mdash.net device management service support
|
||||
#define MG_ENABLE_MDASH 1
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
set architecture armv7e-m
|
||||
target remote 127.0.0.1:3334
|
||||
set $cfsr = *(unsigned int *) 0xe000ed28
|
||||
set $hfsr = *(unsigned int *) 0xe000ed2c
|
||||
set $bfar = *(unsigned int *) 0xe000ed38
|
||||
set $abfsr = *(unsigned int *) 0xe000efa8
|
||||
printf "Fault registers: CFSR=0x%08x HFSR=0x%08x BFAR=0x%08x ABFSR=0x%08x\n", $cfsr, $hfsr, $bfar, $abfsr
|
||||
if $cfsr & 0x00000400
|
||||
printf "Imprecise BusFault: exception PC is asynchronous; offending instruction may be earlier.\n"
|
||||
end
|
||||
if $cfsr & 0x00008000
|
||||
printf "BFAR valid: 0x%08x\n", $bfar
|
||||
end
|
||||
bt
|
||||
@@ -0,0 +1,15 @@
|
||||
The target architecture is set to "armv7e-m".
|
||||
http_ev_handler (c=0x2400da68, ev=<optimized out>, ev_data=<optimized out>) at main.c:174
|
||||
174 __asm volatile ("udf #0");
|
||||
Fault registers: CFSR=0x00010000 HFSR=0x40000000 BFAR=0x00000000 ABFSR=0x00000000
|
||||
#0 http_ev_handler (c=0x2400da68, ev=<optimized out>, ev_data=<optimized out>) at main.c:174
|
||||
#1 0x080107aa in http_cb (c=0x2400da68, ev=<optimized out>, ev_data=<optimized out>) at mongoose/mongoose.c:4842
|
||||
#2 0x08007812 in mg_call (c=0x2400da68, ev=7, ev_data=0x2404fdd4) at mongoose/mongoose.c:2619
|
||||
#3 0x080194b8 in read_conn (c=<optimized out>, pkt=<optimized out>) at mongoose/mongoose.c:9907
|
||||
#4 rx_tcp (ifp=<optimized out>, pkt=<optimized out>) at mongoose/mongoose.c:10022
|
||||
#5 rx_ip (ifp=0x2400da68, pkt=0x2404fdc8) at mongoose/mongoose.c:10133
|
||||
#6 mg_tcpip_rx (ifp=ifp@entry=0x24007e58 <mif_>, buf=<optimized out>, len=len@entry=139) at mongoose/mongoose.c:10260
|
||||
#7 0x0801a082 in mg_tcpip_poll (ifp=0x24007e58 <mif_>, now=32453) at mongoose/mongoose.c:10413
|
||||
#8 mg_mgr_poll (mgr=mgr@entry=0x2404ffac, ms=ms@entry=0) at mongoose/mongoose.c:10642
|
||||
#9 0x08000a64 in main () at main.c:212
|
||||
[Inferior 1 (Remote target) detached]
|
||||
Reference in New Issue
Block a user