diff --git a/mongoose.c b/mongoose.c index 23551cca..a9054354 100644 --- a/mongoose.c +++ b/mongoose.c @@ -3855,87 +3855,117 @@ struct mg_fs mg_fs_posix = {p_stat, p_list, p_open, p_close, p_read, #endif -#include + #if MG_ENABLE_HEALTH #if !MG_ENABLE_CUSTOM_USAGE +#if MG_ARCH == MG_ARCH_ESP32 +#include +#include +#include +#elif MG_ENABLE_FREERTOS && defined(__GNUC__) && defined(__ELF__) +// Some FreeRTOS allocators provide current free bytes but no low watermark. +extern size_t xPortGetMinimumEverFreeHeapSize(void) __attribute__((weak)); +#endif + +#if MG_ENABLE_FREERTOS && MG_ARCH == MG_ARCH_CUBE && \ + configUSE_TRACE_FACILITY == 1 && configRECORD_STACK_HIGH_ADDRESS != 1 +#warning \ + "Set configRECORD_STACK_HIGH_ADDRESS to 1 in FreeRTOSConfig.h to report stack total and peak" +#endif + size_t mg_usage(int task, int metric) { - // FreeRTOS: call NUM_TASKS first to snapshot tasks and copy their names - // Indices and names stay valid until the next refresh, preventing duplicate - // or mismatched entries. STACK_FREE is minimum-ever free; STACK_TOTAL is 0 -#if MG_ENABLE_FREERTOS - if (metric == MG_USAGE_HEAP_FREE) return xPortGetFreeHeapSize(); -#ifdef configTOTAL_HEAP_SIZE - if (metric == MG_USAGE_HEAP_TOTAL) return configTOTAL_HEAP_SIZE; +#if MG_ARCH == MG_ARCH_ESP32 || \ + (MG_ENABLE_FREERTOS && defined(configTOTAL_HEAP_SIZE)) + if (metric == MG_USAGE_HEAP_TOTAL || metric == MG_USAGE_HEAP_USED || + metric == MG_USAGE_HEAP_PEAK) { + size_t total, available; +#if MG_ARCH == MG_ARCH_ESP32 + total = heap_caps_get_total_size(MALLOC_CAP_DEFAULT); + if (metric == MG_USAGE_HEAP_TOTAL) return total; + available = metric == MG_USAGE_HEAP_USED + ? heap_caps_get_free_size(MALLOC_CAP_DEFAULT) + : heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT); +#else + total = configTOTAL_HEAP_SIZE; + if (metric == MG_USAGE_HEAP_TOTAL) return total; + if (metric == MG_USAGE_HEAP_USED) { + available = xPortGetFreeHeapSize(); + } else { +#if defined(__GNUC__) && defined(__ELF__) + if (xPortGetMinimumEverFreeHeapSize == NULL) return 0; + available = xPortGetMinimumEverFreeHeapSize(); +#else + return 0; #endif + } +#endif + return available <= total ? total - available : 0; + } +#endif + +#if MG_ENABLE_FREERTOS || MG_ARCH == MG_ARCH_ESP32 #if configUSE_TRACE_FACILITY == 1 - static TaskStatus_t *tasks; - static UBaseType_t count; - if (metric == MG_USAGE_NUM_TASKS) { - size_t size = sizeof(*tasks) + configMAX_TASK_NAME_LEN; - vTaskSuspendAll(); - if (tasks != NULL) { - mg_free(tasks); - tasks = NULL; + UBaseType_t count = uxTaskGetNumberOfTasks(); + if (metric == MG_USAGE_NUM_TASKS) return (size_t) count; + if (task >= 0 && (size_t) task < count) { + TaskStatus_t tasks[count]; + if ((size_t) task >= uxTaskGetSystemState(tasks, count, NULL)) return 0; + TaskStatus_t *t = &tasks[task]; + if (metric == MG_USAGE_TASK_NAME) return (size_t) t->pcTaskName; +#if (MG_ARCH == MG_ARCH_CUBE || MG_ARCH == MG_ARCH_ESP32) && \ + configRECORD_STACK_HIGH_ADDRESS == 1 + size_t total = 0; + uintptr_t tcb = (uintptr_t) t->xHandle; + uintptr_t base = (uintptr_t) t->pxStackBase; + if (tcb != 0 && + t->pcTaskName == (const char *) (tcb + offsetof(StaticTask_t, ucDummy7))) { + StackType_t *end; + uintptr_t field = tcb + offsetof(StaticTask_t, pxDummy8); + memcpy(&end, (const void *) field, sizeof(end)); + if (base != 0 && (uintptr_t) end >= base && + (uintptr_t) end - base <= ((size_t) -1) - sizeof(*end)) + total = (size_t) ((uintptr_t) end - base) + sizeof(*end); } - count = 0; - UBaseType_t n = uxTaskGetNumberOfTasks(); - if (n > 0 && (size_t) n <= INT_MAX && (size_t) n <= SIZE_MAX / size) { - tasks = (TaskStatus_t *) mg_calloc((size_t) n, size); - if (tasks != NULL) { - count = uxTaskGetSystemState(tasks, n, NULL); - if (count == n) { - for (UBaseType_t i = 0; i < count; i++) { - char *name = (char *) (tasks + n) + (size_t) i * configMAX_TASK_NAME_LEN; - mg_snprintf(name, configMAX_TASK_NAME_LEN, "%s", tasks[i].pcTaskName); - tasks[i].pcTaskName = name; - } - } else { - mg_free(tasks); - tasks = NULL; - count = 0; - } - } + if (metric == MG_USAGE_STACK_TOTAL) return total; + if (metric == MG_USAGE_STACK_PEAK) { + size_t available = + (size_t) t->usStackHighWaterMark * sizeof(StackType_t); + return available <= total ? total - available : 0; } - (void) xTaskResumeAll(); - return (size_t) count; - } - if (task >= 0 && (size_t) task < (size_t) count) { - if (metric == MG_USAGE_TASK_NAME) return (size_t) tasks[task].pcTaskName; - if (metric == MG_USAGE_STACK_FREE) - return (size_t) tasks[task].usStackHighWaterMark * sizeof(StackType_t); +#endif } #endif #endif - (void) task; - (void) metric; + (void) task, (void) metric; return 0; } #endif -static size_t print_stacks(mg_pfn_t fn, void *arg, va_list *ap) { - int i; - int count = (int) mg_usage(-1, MG_USAGE_NUM_TASKS); - size_t len = mg_xprintf(fn, arg, "["); - for (i = 0; i < count; i++) { - const char *name = (const char *) mg_usage(i, MG_USAGE_TASK_NAME); - len += mg_xprintf(fn, arg, "%s{%m:%m,%m:%zu,%m:%zu}", - i == 0 ? "" : ",", MG_ESC("name"), - MG_ESC(name == NULL ? "" : name), MG_ESC("free"), - (size_t) mg_usage(i, MG_USAGE_STACK_FREE), MG_ESC("total"), - (size_t) mg_usage(i, MG_USAGE_STACK_TOTAL)); - } - (void) ap; - return len + mg_xprintf(fn, arg, "]"); +static size_t print_mem_stat(mg_pfn_t fn, void *arg, const char *name, + size_t total, size_t used, size_t peak) { + return mg_xprintf(fn, arg, "{%m:%m,%m:%zu,%m:%zu,%m:%zu}", MG_ESC("name"), + MG_ESC(name == NULL ? "" : name), MG_ESC("total"), total, + MG_ESC("used"), used, MG_ESC("peak"), peak); } size_t mg_print_mem_stats(mg_pfn_t fn, void *arg, va_list *ap) { - size_t heap_total = (size_t) mg_usage(0, MG_USAGE_HEAP_TOTAL); - size_t heap_free = (size_t) mg_usage(0, MG_USAGE_HEAP_FREE); + int i, count; + size_t heap_total = mg_usage(0, MG_USAGE_HEAP_TOTAL); + size_t heap_used = mg_usage(0, MG_USAGE_HEAP_USED); + size_t heap_peak = mg_usage(0, MG_USAGE_HEAP_PEAK); + size_t len = mg_xprintf(fn, arg, "["); + len += print_mem_stat(fn, arg, "heap", heap_total, heap_used, heap_peak); + count = (int) mg_usage(-1, MG_USAGE_NUM_TASKS); + for (i = 0; i < count; i++) { + const char *name = (const char *) mg_usage(i, MG_USAGE_TASK_NAME); + len += mg_xprintf(fn, arg, ","); + len += print_mem_stat(fn, arg, name, mg_usage(i, MG_USAGE_STACK_TOTAL), + mg_usage(i, MG_USAGE_STACK_USED), + mg_usage(i, MG_USAGE_STACK_PEAK)); + } (void) ap; - return mg_xprintf(fn, arg, "{%m:%zu,%m:%zu,%m:%M}", - MG_ESC("heap_total"), heap_total, MG_ESC("heap_free"), - heap_free, MG_ESC("stacks"), print_stacks); + return len + mg_xprintf(fn, arg, "]"); } #if MG_ARCH == MG_ARCH_ESP32 @@ -3971,10 +4001,10 @@ bool mg_health_coredump_begin(size_t *size) { bool mg_health_coredump_read(size_t offset, void *buf, size_t *len) { #if MG_ARCH == MG_ARCH_ESP32 && CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH const esp_partition_t *p = (const esp_partition_t *) s_coredump.ptr; - if (p == NULL || offset > s_coredump.size) - return false; + if (p == NULL || offset > s_coredump.size) return false; if (*len > s_coredump.size - offset) *len = s_coredump.size - offset; - return esp_partition_read(p, s_coredump.poffset + offset, buf, *len) == ESP_OK; + return esp_partition_read(p, s_coredump.poffset + offset, buf, *len) == + ESP_OK; #else (void) offset, (void) buf, (void) len; return false; @@ -4016,6 +4046,7 @@ __attribute__((used, noinline)) static void fault_c(uint32_t *frame, r[3 + i] = stack + i < &_estack ? stack[i] : 0; } mg_health_record.cpuid = SCB->CPUID; + mg_health_record.arch = MG_HEALTH_ARCH_ARM; mg_health_record.reset_reason = MG_HEALTH_RESET_FAULT; NVIC_SystemReset(); } @@ -4047,46 +4078,43 @@ void mg_health_init(void) { } #if MG_ARCH == MG_ARCH_ESP32 #if CONFIG_IDF_TARGET_ARCH_XTENSA - mg_health_record.arch = (uint16_t) MG_HEALTH_ARCH_XTENSA; + mg_health_record.arch = MG_HEALTH_ARCH_XTENSA; #elif CONFIG_IDF_TARGET_ARCH_RISCV - mg_health_record.arch = (uint16_t) MG_HEALTH_ARCH_RISCV; + mg_health_record.arch = MG_HEALTH_ARCH_RISCV; #endif esp_reset_reason_t rr = esp_reset_reason(); mg_health_record.reset_reason = esp2mg(rr); #if CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH == 0 MG_ERROR(("MG_HEALTH is enabled but CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=n")); #else - // After a crash, copy the backtrace from the core dump in flash: the same - // addresses the panic handler prints as "Backtrace: PC:SP PC:SP ...". - // The core dump stays in flash, so check the reset reason, otherwise an - // old crash is reported on every boot - esp_core_dump_summary_t *s = calloc(1, sizeof(*s)); - uint32_t *out = mg_health_record.data; - size_t n = 0; - memset(out, 0, sizeof(mg_health_record.data)); - if (s == NULL) return; - if ((rr != ESP_RST_PANIC && rr != ESP_RST_INT_WDT && - rr != ESP_RST_TASK_WDT) // Not a crash caught by the panic handler - || esp_core_dump_get_summary(s) != ESP_OK) { - free(s); - return; - } - out[n++] = s->exc_pc; - memcpy(&out[n], s->exc_task, 16), n += 4; + // After a crash, copy the backtrace from the core dump in flash: the same + // addresses the panic handler prints as "Backtrace: PC:SP PC:SP ...". + // The core dump stays in flash, so check the reset reason, otherwise an + // old crash is reported on every boot + esp_core_dump_summary_t s; + uint32_t *out = mg_health_record.data; + size_t n = 0; + memset(out, 0, sizeof(mg_health_record.data)); + if ((rr != ESP_RST_PANIC && rr != ESP_RST_INT_WDT && + rr != ESP_RST_TASK_WDT) || + esp_core_dump_get_summary(&s) != ESP_OK) { + return; // If not panic, and if cannot get crash info, give up + } + out[n++] = s.exc_pc; // Crash PC + memcpy(&out[n], s.exc_task, 16), n += 4; // Crashed task name #if CONFIG_IDF_TARGET_ARCH_XTENSA - out[n++] = s->ex_info.exc_a[0]; - out[n++] = s->ex_info.exc_a[1]; - size_t len = s->exc_bt_info.depth; - if (len > MG_HEALTH_DATA_SIZE - n) len = (MG_HEALTH_DATA_SIZE - n); - memcpy(&out[n], s->exc_bt_info.bt, 4 * len); // sizeof(uint32_t) + out[n++] = s.ex_info.exc_a[0]; + out[n++] = s.ex_info.exc_a[1]; + size_t len = s.exc_bt_info.depth; + if (len > MG_HEALTH_DATA_SIZE - n) len = MG_HEALTH_DATA_SIZE - n; + memcpy(&out[n], s.exc_bt_info.bt, 4 * len); #elif CONFIG_IDF_TARGET_ARCH_RISCV - out[n++] = s->ex_info.exc_ra; - out[n++] = s->ex_info.exc_sp; - size_t len = s->exc_bt_info.dump_size / 4 ; // sizeof(uint32_t) - if (len > MG_HEALTH_DATA_SIZE - n) len = (MG_HEALTH_DATA_SIZE - n); - memcpy(&out[n], s->exc_bt_info.stackdump, 4 * len); + out[n++] = s.ex_info.ra; + out[n++] = s.ex_info.sp; + size_t len = s.exc_bt_info.dump_size / 4; + if (len > MG_HEALTH_DATA_SIZE - n) len = MG_HEALTH_DATA_SIZE - n; + memcpy(&out[n], s.exc_bt_info.stackdump, 4 * len); #endif - free(s); #endif // CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH #endif // MG_ARCH_ESP32 } @@ -4102,13 +4130,10 @@ static size_t print_uint32(void (*fn)(char, void *), void *arg, va_list *ap) { size_t mg_print_crash_record(void (*fn)(char, void *), void *arg, va_list *ap) { size_t len = 0; if (mg_health_valid() && - (mg_health_record.reset_reason == (uint16_t) MG_HEALTH_RESET_FAULT -#if MG_ARCH == MG_ARCH_ESP32 - || mg_health_record.reset_reason == (uint16_t) MG_HEALTH_RESET_WATCHDOG -#endif - )) { + (mg_health_record.reset_reason == MG_HEALTH_RESET_FAULT || + mg_health_record.reset_reason == MG_HEALTH_RESET_WATCHDOG)) { size_t n = sizeof(mg_health_record.data) / sizeof(mg_health_record.data[0]); - len += mg_xprintf(fn, arg, "{%m:%hhu,%m:%hu,%m:%hu,%m:%u,%m:[%M]}", // + len += mg_xprintf(fn, arg, "{%m:%hhu,%m:%lu,%m:%lu,%m:%u,%m:[%M]}", // MG_ESC("version"), mg_health_record.magic[3], // MG_ESC("arch"), mg_health_record.arch, // MG_ESC("reason"), mg_health_record.reset_reason, // diff --git a/mongoose.h b/mongoose.h index 480ec5f3..27770736 100644 --- a/mongoose.h +++ b/mongoose.h @@ -265,10 +265,6 @@ extern "C" { #define MG_ENABLE_POSIX_FS 1 #endif -#ifndef MG_HEALTH_DATA_SIZE -#define MG_HEALTH_DATA_SIZE 20 -#endif - #endif @@ -4395,8 +4391,10 @@ void mg_ota_poll(struct mg_mgr *); #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 +#elif MG_ARCH == MG_ARCH_ESP32 && CONFIG_IDF_TARGET_ARCH_XTENSA +#define MG_HEALTH_DATA_SIZE 23 // 7 header words + 16 backtrace PCs +#elif MG_ARCH == MG_ARCH_ESP32 && CONFIG_IDF_TARGET_ARCH_RISCV +#define MG_HEALTH_DATA_SIZE 7 // PC, task name, RA, SP #else #define MG_HEALTH_DATA_SIZE 10 #endif @@ -4429,10 +4427,10 @@ enum mg_health_reason { MG_HEALTH_RESET_FAULT // Fault handler ran, see regs[] and saved_stack }; -struct mg_health { +struct mg_health { // NOTE: use 32-bit fields to avoid write issues 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 reset_reason; // enum mg_health_reason that started this boot + uint32_t arch; // enum mg_health_arch, core architecture (ARM, etc) uint32_t cpuid; // ARM CPU ID uint32_t data[MG_HEALTH_DATA_SIZE]; }; @@ -4501,24 +4499,26 @@ enum mg_usage_metric { MG_USAGE_NUM_TASKS, MG_USAGE_TASK_NAME, MG_USAGE_STACK_TOTAL, - MG_USAGE_STACK_FREE, + MG_USAGE_STACK_USED, + MG_USAGE_STACK_PEAK, MG_USAGE_HEAP_TOTAL, - MG_USAGE_HEAP_FREE + MG_USAGE_HEAP_USED, + MG_USAGE_HEAP_PEAK }; // Returns a task or heap metric. task is a zero-based index; NUM_TASKS and heap // metrics ignore it. Sizes are in bytes, TASK_NAME returns const char * cast to // size_t -// Unsupported metrics and unknown totals return 0 +// Unsupported metrics return 0. PEAK is the maximum used since boot // Set MG_ENABLE_CUSTOM_USAGE=1 and define mg_usage() in the application to // provide custom measurements size_t mg_usage(int task, int metric); #define MG_HEALTH_INIT() mg_health_init() extern size_t mg_print_crash_record(mg_pfn_t, void *, va_list *); -// JSON memory statistics via mg_usage(), suitable for %M: heap_total, heap_free, -// and a stacks array of {name, free, total} -// A zero total means unknown +// JSON memory statistics via mg_usage(), suitable for %M: an array of +// {name, total, used, peak} entries, starting with "heap", then task stacks +// Unavailable metrics are reported as 0 extern size_t mg_print_mem_stats(mg_pfn_t, void *, va_list *); extern void mg_health_init(void); extern bool mg_health_coredump_begin(size_t *size); @@ -4540,7 +4540,7 @@ static inline size_t mg_print_crash_record(mg_pfn_t fn, void *arg, } static inline size_t mg_print_mem_stats(mg_pfn_t fn, void *arg, va_list *ap) { (void) ap; - return mg_xprintf(fn, arg, "{}"); + return mg_xprintf(fn, arg, "[]"); } static inline bool mg_health_coredump_begin(size_t *size) { (void) size; diff --git a/src/arch_esp32.h b/src/arch_esp32.h index 0eeb0ed9..f96b6871 100644 --- a/src/arch_esp32.h +++ b/src/arch_esp32.h @@ -34,8 +34,4 @@ #define MG_ENABLE_POSIX_FS 1 #endif -#ifndef MG_HEALTH_DATA_SIZE -#define MG_HEALTH_DATA_SIZE 20 -#endif - #endif diff --git a/src/health.c b/src/health.c index 2be84cfd..a4914790 100644 --- a/src/health.c +++ b/src/health.c @@ -1,86 +1,116 @@ +#include "arch.h" #include "config.h" #include "health.h" -#include #if MG_ENABLE_HEALTH #if !MG_ENABLE_CUSTOM_USAGE +#if MG_ARCH == MG_ARCH_ESP32 +#include +#include +#include +#elif MG_ENABLE_FREERTOS && defined(__GNUC__) && defined(__ELF__) +// Some FreeRTOS allocators provide current free bytes but no low watermark. +extern size_t xPortGetMinimumEverFreeHeapSize(void) __attribute__((weak)); +#endif + +#if MG_ENABLE_FREERTOS && MG_ARCH == MG_ARCH_CUBE && \ + configUSE_TRACE_FACILITY == 1 && configRECORD_STACK_HIGH_ADDRESS != 1 +#warning \ + "Set configRECORD_STACK_HIGH_ADDRESS to 1 in FreeRTOSConfig.h to report stack total and peak" +#endif + size_t mg_usage(int task, int metric) { - // FreeRTOS: call NUM_TASKS first to snapshot tasks and copy their names - // Indices and names stay valid until the next refresh, preventing duplicate - // or mismatched entries. STACK_FREE is minimum-ever free; STACK_TOTAL is 0 -#if MG_ENABLE_FREERTOS - if (metric == MG_USAGE_HEAP_FREE) return xPortGetFreeHeapSize(); -#ifdef configTOTAL_HEAP_SIZE - if (metric == MG_USAGE_HEAP_TOTAL) return configTOTAL_HEAP_SIZE; +#if MG_ARCH == MG_ARCH_ESP32 || \ + (MG_ENABLE_FREERTOS && defined(configTOTAL_HEAP_SIZE)) + if (metric == MG_USAGE_HEAP_TOTAL || metric == MG_USAGE_HEAP_USED || + metric == MG_USAGE_HEAP_PEAK) { + size_t total, available; +#if MG_ARCH == MG_ARCH_ESP32 + total = heap_caps_get_total_size(MALLOC_CAP_DEFAULT); + if (metric == MG_USAGE_HEAP_TOTAL) return total; + available = metric == MG_USAGE_HEAP_USED + ? heap_caps_get_free_size(MALLOC_CAP_DEFAULT) + : heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT); +#else + total = configTOTAL_HEAP_SIZE; + if (metric == MG_USAGE_HEAP_TOTAL) return total; + if (metric == MG_USAGE_HEAP_USED) { + available = xPortGetFreeHeapSize(); + } else { +#if defined(__GNUC__) && defined(__ELF__) + if (xPortGetMinimumEverFreeHeapSize == NULL) return 0; + available = xPortGetMinimumEverFreeHeapSize(); +#else + return 0; #endif + } +#endif + return available <= total ? total - available : 0; + } +#endif + +#if MG_ENABLE_FREERTOS || MG_ARCH == MG_ARCH_ESP32 #if configUSE_TRACE_FACILITY == 1 - static TaskStatus_t *tasks; - static UBaseType_t count; - if (metric == MG_USAGE_NUM_TASKS) { - size_t size = sizeof(*tasks) + configMAX_TASK_NAME_LEN; - vTaskSuspendAll(); - if (tasks != NULL) { - mg_free(tasks); - tasks = NULL; + UBaseType_t count = uxTaskGetNumberOfTasks(); + if (metric == MG_USAGE_NUM_TASKS) return (size_t) count; + if (task >= 0 && (size_t) task < count) { + TaskStatus_t tasks[count]; + if ((size_t) task >= uxTaskGetSystemState(tasks, count, NULL)) return 0; + TaskStatus_t *t = &tasks[task]; + if (metric == MG_USAGE_TASK_NAME) return (size_t) t->pcTaskName; +#if (MG_ARCH == MG_ARCH_CUBE || MG_ARCH == MG_ARCH_ESP32) && \ + configRECORD_STACK_HIGH_ADDRESS == 1 + size_t total = 0; + uintptr_t tcb = (uintptr_t) t->xHandle; + uintptr_t base = (uintptr_t) t->pxStackBase; + if (tcb != 0 && + t->pcTaskName == (const char *) (tcb + offsetof(StaticTask_t, ucDummy7))) { + StackType_t *end; + uintptr_t field = tcb + offsetof(StaticTask_t, pxDummy8); + memcpy(&end, (const void *) field, sizeof(end)); + if (base != 0 && (uintptr_t) end >= base && + (uintptr_t) end - base <= ((size_t) -1) - sizeof(*end)) + total = (size_t) ((uintptr_t) end - base) + sizeof(*end); } - count = 0; - UBaseType_t n = uxTaskGetNumberOfTasks(); - if (n > 0 && (size_t) n <= INT_MAX && (size_t) n <= SIZE_MAX / size) { - tasks = (TaskStatus_t *) mg_calloc((size_t) n, size); - if (tasks != NULL) { - count = uxTaskGetSystemState(tasks, n, NULL); - if (count == n) { - for (UBaseType_t i = 0; i < count; i++) { - char *name = (char *) (tasks + n) + (size_t) i * configMAX_TASK_NAME_LEN; - mg_snprintf(name, configMAX_TASK_NAME_LEN, "%s", tasks[i].pcTaskName); - tasks[i].pcTaskName = name; - } - } else { - mg_free(tasks); - tasks = NULL; - count = 0; - } - } + if (metric == MG_USAGE_STACK_TOTAL) return total; + if (metric == MG_USAGE_STACK_PEAK) { + size_t available = + (size_t) t->usStackHighWaterMark * sizeof(StackType_t); + return available <= total ? total - available : 0; } - (void) xTaskResumeAll(); - return (size_t) count; - } - if (task >= 0 && (size_t) task < (size_t) count) { - if (metric == MG_USAGE_TASK_NAME) return (size_t) tasks[task].pcTaskName; - if (metric == MG_USAGE_STACK_FREE) - return (size_t) tasks[task].usStackHighWaterMark * sizeof(StackType_t); +#endif } #endif #endif - (void) task; - (void) metric; + (void) task, (void) metric; return 0; } #endif -static size_t print_stacks(mg_pfn_t fn, void *arg, va_list *ap) { - int i; - int count = (int) mg_usage(-1, MG_USAGE_NUM_TASKS); - size_t len = mg_xprintf(fn, arg, "["); - for (i = 0; i < count; i++) { - const char *name = (const char *) mg_usage(i, MG_USAGE_TASK_NAME); - len += mg_xprintf(fn, arg, "%s{%m:%m,%m:%zu,%m:%zu}", - i == 0 ? "" : ",", MG_ESC("name"), - MG_ESC(name == NULL ? "" : name), MG_ESC("free"), - (size_t) mg_usage(i, MG_USAGE_STACK_FREE), MG_ESC("total"), - (size_t) mg_usage(i, MG_USAGE_STACK_TOTAL)); - } - (void) ap; - return len + mg_xprintf(fn, arg, "]"); +static size_t print_mem_stat(mg_pfn_t fn, void *arg, const char *name, + size_t total, size_t used, size_t peak) { + return mg_xprintf(fn, arg, "{%m:%m,%m:%zu,%m:%zu,%m:%zu}", MG_ESC("name"), + MG_ESC(name == NULL ? "" : name), MG_ESC("total"), total, + MG_ESC("used"), used, MG_ESC("peak"), peak); } size_t mg_print_mem_stats(mg_pfn_t fn, void *arg, va_list *ap) { - size_t heap_total = (size_t) mg_usage(0, MG_USAGE_HEAP_TOTAL); - size_t heap_free = (size_t) mg_usage(0, MG_USAGE_HEAP_FREE); + int i, count; + size_t heap_total = mg_usage(0, MG_USAGE_HEAP_TOTAL); + size_t heap_used = mg_usage(0, MG_USAGE_HEAP_USED); + size_t heap_peak = mg_usage(0, MG_USAGE_HEAP_PEAK); + size_t len = mg_xprintf(fn, arg, "["); + len += print_mem_stat(fn, arg, "heap", heap_total, heap_used, heap_peak); + count = (int) mg_usage(-1, MG_USAGE_NUM_TASKS); + for (i = 0; i < count; i++) { + const char *name = (const char *) mg_usage(i, MG_USAGE_TASK_NAME); + len += mg_xprintf(fn, arg, ","); + len += print_mem_stat(fn, arg, name, mg_usage(i, MG_USAGE_STACK_TOTAL), + mg_usage(i, MG_USAGE_STACK_USED), + mg_usage(i, MG_USAGE_STACK_PEAK)); + } (void) ap; - return mg_xprintf(fn, arg, "{%m:%zu,%m:%zu,%m:%M}", - MG_ESC("heap_total"), heap_total, MG_ESC("heap_free"), - heap_free, MG_ESC("stacks"), print_stacks); + return len + mg_xprintf(fn, arg, "]"); } #if MG_ARCH == MG_ARCH_ESP32 @@ -116,10 +146,10 @@ bool mg_health_coredump_begin(size_t *size) { bool mg_health_coredump_read(size_t offset, void *buf, size_t *len) { #if MG_ARCH == MG_ARCH_ESP32 && CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH const esp_partition_t *p = (const esp_partition_t *) s_coredump.ptr; - if (p == NULL || offset > s_coredump.size) - return false; + if (p == NULL || offset > s_coredump.size) return false; if (*len > s_coredump.size - offset) *len = s_coredump.size - offset; - return esp_partition_read(p, s_coredump.poffset + offset, buf, *len) == ESP_OK; + return esp_partition_read(p, s_coredump.poffset + offset, buf, *len) == + ESP_OK; #else (void) offset, (void) buf, (void) len; return false; @@ -161,6 +191,7 @@ __attribute__((used, noinline)) static void fault_c(uint32_t *frame, r[3 + i] = stack + i < &_estack ? stack[i] : 0; } mg_health_record.cpuid = SCB->CPUID; + mg_health_record.arch = MG_HEALTH_ARCH_ARM; mg_health_record.reset_reason = MG_HEALTH_RESET_FAULT; NVIC_SystemReset(); } @@ -192,46 +223,43 @@ void mg_health_init(void) { } #if MG_ARCH == MG_ARCH_ESP32 #if CONFIG_IDF_TARGET_ARCH_XTENSA - mg_health_record.arch = (uint16_t) MG_HEALTH_ARCH_XTENSA; + mg_health_record.arch = MG_HEALTH_ARCH_XTENSA; #elif CONFIG_IDF_TARGET_ARCH_RISCV - mg_health_record.arch = (uint16_t) MG_HEALTH_ARCH_RISCV; + mg_health_record.arch = MG_HEALTH_ARCH_RISCV; #endif esp_reset_reason_t rr = esp_reset_reason(); mg_health_record.reset_reason = esp2mg(rr); #if CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH == 0 MG_ERROR(("MG_HEALTH is enabled but CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=n")); #else - // After a crash, copy the backtrace from the core dump in flash: the same - // addresses the panic handler prints as "Backtrace: PC:SP PC:SP ...". - // The core dump stays in flash, so check the reset reason, otherwise an - // old crash is reported on every boot - esp_core_dump_summary_t *s = calloc(1, sizeof(*s)); - uint32_t *out = mg_health_record.data; - size_t n = 0; - memset(out, 0, sizeof(mg_health_record.data)); - if (s == NULL) return; - if ((rr != ESP_RST_PANIC && rr != ESP_RST_INT_WDT && - rr != ESP_RST_TASK_WDT) // Not a crash caught by the panic handler - || esp_core_dump_get_summary(s) != ESP_OK) { - free(s); - return; - } - out[n++] = s->exc_pc; - memcpy(&out[n], s->exc_task, 16), n += 4; + // After a crash, copy the backtrace from the core dump in flash: the same + // addresses the panic handler prints as "Backtrace: PC:SP PC:SP ...". + // The core dump stays in flash, so check the reset reason, otherwise an + // old crash is reported on every boot + esp_core_dump_summary_t s; + uint32_t *out = mg_health_record.data; + size_t n = 0; + memset(out, 0, sizeof(mg_health_record.data)); + if ((rr != ESP_RST_PANIC && rr != ESP_RST_INT_WDT && + rr != ESP_RST_TASK_WDT) || + esp_core_dump_get_summary(&s) != ESP_OK) { + return; // If not panic, and if cannot get crash info, give up + } + out[n++] = s.exc_pc; // Crash PC + memcpy(&out[n], s.exc_task, 16), n += 4; // Crashed task name #if CONFIG_IDF_TARGET_ARCH_XTENSA - out[n++] = s->ex_info.exc_a[0]; - out[n++] = s->ex_info.exc_a[1]; - size_t len = s->exc_bt_info.depth; - if (len > MG_HEALTH_DATA_SIZE - n) len = (MG_HEALTH_DATA_SIZE - n); - memcpy(&out[n], s->exc_bt_info.bt, 4 * len); // sizeof(uint32_t) + out[n++] = s.ex_info.exc_a[0]; + out[n++] = s.ex_info.exc_a[1]; + size_t len = s.exc_bt_info.depth; + if (len > MG_HEALTH_DATA_SIZE - n) len = MG_HEALTH_DATA_SIZE - n; + memcpy(&out[n], s.exc_bt_info.bt, 4 * len); #elif CONFIG_IDF_TARGET_ARCH_RISCV - out[n++] = s->ex_info.exc_ra; - out[n++] = s->ex_info.exc_sp; - size_t len = s->exc_bt_info.dump_size / 4 ; // sizeof(uint32_t) - if (len > MG_HEALTH_DATA_SIZE - n) len = (MG_HEALTH_DATA_SIZE - n); - memcpy(&out[n], s->exc_bt_info.stackdump, 4 * len); + out[n++] = s.ex_info.ra; + out[n++] = s.ex_info.sp; + size_t len = s.exc_bt_info.dump_size / 4; + if (len > MG_HEALTH_DATA_SIZE - n) len = MG_HEALTH_DATA_SIZE - n; + memcpy(&out[n], s.exc_bt_info.stackdump, 4 * len); #endif - free(s); #endif // CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH #endif // MG_ARCH_ESP32 } @@ -247,13 +275,10 @@ static size_t print_uint32(void (*fn)(char, void *), void *arg, va_list *ap) { size_t mg_print_crash_record(void (*fn)(char, void *), void *arg, va_list *ap) { size_t len = 0; if (mg_health_valid() && - (mg_health_record.reset_reason == (uint16_t) MG_HEALTH_RESET_FAULT -#if MG_ARCH == MG_ARCH_ESP32 - || mg_health_record.reset_reason == (uint16_t) MG_HEALTH_RESET_WATCHDOG -#endif - )) { + (mg_health_record.reset_reason == MG_HEALTH_RESET_FAULT || + mg_health_record.reset_reason == MG_HEALTH_RESET_WATCHDOG)) { size_t n = sizeof(mg_health_record.data) / sizeof(mg_health_record.data[0]); - len += mg_xprintf(fn, arg, "{%m:%hhu,%m:%hu,%m:%hu,%m:%u,%m:[%M]}", // + len += mg_xprintf(fn, arg, "{%m:%hhu,%m:%lu,%m:%lu,%m:%u,%m:[%M]}", // MG_ESC("version"), mg_health_record.magic[3], // MG_ESC("arch"), mg_health_record.arch, // MG_ESC("reason"), mg_health_record.reset_reason, // diff --git a/src/health.h b/src/health.h index 112510de..621c4f7e 100644 --- a/src/health.h +++ b/src/health.h @@ -18,8 +18,10 @@ #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 +#elif MG_ARCH == MG_ARCH_ESP32 && CONFIG_IDF_TARGET_ARCH_XTENSA +#define MG_HEALTH_DATA_SIZE 23 // 7 header words + 16 backtrace PCs +#elif MG_ARCH == MG_ARCH_ESP32 && CONFIG_IDF_TARGET_ARCH_RISCV +#define MG_HEALTH_DATA_SIZE 7 // PC, task name, RA, SP #else #define MG_HEALTH_DATA_SIZE 10 #endif @@ -52,10 +54,10 @@ enum mg_health_reason { MG_HEALTH_RESET_FAULT // Fault handler ran, see regs[] and saved_stack }; -struct mg_health { +struct mg_health { // NOTE: use 32-bit fields to avoid write issues 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 reset_reason; // enum mg_health_reason that started this boot + uint32_t arch; // enum mg_health_arch, core architecture (ARM, etc) uint32_t cpuid; // ARM CPU ID uint32_t data[MG_HEALTH_DATA_SIZE]; }; @@ -124,24 +126,26 @@ enum mg_usage_metric { MG_USAGE_NUM_TASKS, MG_USAGE_TASK_NAME, MG_USAGE_STACK_TOTAL, - MG_USAGE_STACK_FREE, + MG_USAGE_STACK_USED, + MG_USAGE_STACK_PEAK, MG_USAGE_HEAP_TOTAL, - MG_USAGE_HEAP_FREE + MG_USAGE_HEAP_USED, + MG_USAGE_HEAP_PEAK }; // Returns a task or heap metric. task is a zero-based index; NUM_TASKS and heap // metrics ignore it. Sizes are in bytes, TASK_NAME returns const char * cast to // size_t -// Unsupported metrics and unknown totals return 0 +// Unsupported metrics return 0. PEAK is the maximum used since boot // Set MG_ENABLE_CUSTOM_USAGE=1 and define mg_usage() in the application to // provide custom measurements size_t mg_usage(int task, int metric); #define MG_HEALTH_INIT() mg_health_init() extern size_t mg_print_crash_record(mg_pfn_t, void *, va_list *); -// JSON memory statistics via mg_usage(), suitable for %M: heap_total, heap_free, -// and a stacks array of {name, free, total} -// A zero total means unknown +// JSON memory statistics via mg_usage(), suitable for %M: an array of +// {name, total, used, peak} entries, starting with "heap", then task stacks +// Unavailable metrics are reported as 0 extern size_t mg_print_mem_stats(mg_pfn_t, void *, va_list *); extern void mg_health_init(void); extern bool mg_health_coredump_begin(size_t *size); @@ -163,7 +167,7 @@ static inline size_t mg_print_crash_record(mg_pfn_t fn, void *arg, } static inline size_t mg_print_mem_stats(mg_pfn_t fn, void *arg, va_list *ap) { (void) ap; - return mg_xprintf(fn, arg, "{}"); + return mg_xprintf(fn, arg, "[]"); } static inline bool mg_health_coredump_begin(size_t *size) { (void) size; diff --git a/tutorials/esp32/minimal/Makefile b/tutorials/esp32/minimal/Makefile index fb044734..0f753ce8 100644 --- a/tutorials/esp32/minimal/Makefile +++ b/tutorials/esp32/minimal/Makefile @@ -1,25 +1,22 @@ THISDIR = $(realpath $(CURDIR)) ROOTDIR = $(realpath $(CURDIR)/../../..) DOCKER ?= docker run --rm $(DA) -v $(ROOTDIR):$(ROOTDIR) -w $(THISDIR) espressif/idf -CMD ?= idf.py -DCFLAGS_EXTRA="$(CFLAGS_EXTRA)" build +ARCH ?= esp32 PORT ?= /dev/ttyUSB0 -CFLAGS_EXTRA ?= -DMG_TLS=MG_TLS_BUILTIN - all: example example: true build: Makefile $(wildcard main/*) - $(DOCKER) $(CMD) + test -d build || $(DOCKER) idf.py set-target $(ARCH) + $(DOCKER) idf.py build flash: -flash: CMD = idf.py flash -flash: DA = --device $(PORT) -flash: build + cd build && esptool --chip $(ARCH) --baud 460800 --port $(PORT) write-flash @flash_args clean: - test -d build && $(DOCKER) rm -rf build sdkconfig || true + test -d build && (rm -rf build sdkconfig || true) .PHONY: build diff --git a/tutorials/esp32/minimal/main/mongoose_config.h b/tutorials/esp32/minimal/main/mongoose_config.h index bbc492ff..528919c0 100644 --- a/tutorials/esp32/minimal/main/mongoose_config.h +++ b/tutorials/esp32/minimal/main/mongoose_config.h @@ -1,3 +1,16 @@ #define MG_ARCH MG_ARCH_ESP32 +// Crash reports and mem usage stats +// Make sure to add to sdkconfig.defaults the following: +// CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=y +// CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF=y +// CONFIG_ESP_COREDUMP_MAX_TASKS_NUM=16 +// CONFIG_ESP_COREDUMP_STACK_SIZE=2048 +// CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192 #define MG_ENABLE_HEALTH 1 + +// mdash.net device management support, https://mongoose.ws/docs/guides/mdash/ +#define MG_ENABLE_MDASH 0 +#define MG_MDASH_KEY "..." +#define MG_FIRMWARE_VERSION "esp32_min.1.0.0" +#define MG_TLS MG_TLS_BUILTIN diff --git a/tutorials/esp32/minimal/sdkconfig.defaults b/tutorials/esp32/minimal/sdkconfig.defaults index 89d7695f..63bae2a8 100644 --- a/tutorials/esp32/minimal/sdkconfig.defaults +++ b/tutorials/esp32/minimal/sdkconfig.defaults @@ -5,3 +5,5 @@ CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=y CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF=y CONFIG_ESP_COREDUMP_MAX_TASKS_NUM=16 CONFIG_ESP_COREDUMP_STACK_SIZE=2048 +CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192 +CONFIG_FREERTOS_USE_TRACE_FACILITY=y diff --git a/tutorials/stm32/nucleo-h563zi/cubemx/mongoose/mongoose_config.h b/tutorials/stm32/nucleo-h563zi/cubemx/mongoose/mongoose_config.h index ce027c46..1371b6b5 100644 --- a/tutorials/stm32/nucleo-h563zi/cubemx/mongoose/mongoose_config.h +++ b/tutorials/stm32/nucleo-h563zi/cubemx/mongoose/mongoose_config.h @@ -1,4 +1,5 @@ #pragma once +#include #define MG_ARCH MG_ARCH_CUBE #define MG_ENABLE_LFS 1 #define MG_LFS_SIZE (128 * 1024) diff --git a/tutorials/stm32/nucleo-h723zg/cubemx-freertos/Core/Inc/FreeRTOSConfig.h b/tutorials/stm32/nucleo-h723zg/cubemx-freertos/Core/Inc/FreeRTOSConfig.h index 6d592ec1..c8324441 100644 --- a/tutorials/stm32/nucleo-h723zg/cubemx-freertos/Core/Inc/FreeRTOSConfig.h +++ b/tutorials/stm32/nucleo-h723zg/cubemx-freertos/Core/Inc/FreeRTOSConfig.h @@ -73,6 +73,7 @@ extern uint32_t SystemCoreClock; #define configMAX_TASK_NAME_LEN ( 16 ) #define configHEAP_CLEAR_MEMORY_ON_FREE 0 #define configUSE_TRACE_FACILITY 1 +#define configRECORD_STACK_HIGH_ADDRESS 1 #define configUSE_16_BIT_TICKS 0 #define configUSE_MUTEXES 1 #define configQUEUE_REGISTRY_SIZE 8 diff --git a/tutorials/stm32/nucleo-h723zg/cubemx-freertos/Core/Src/main.c b/tutorials/stm32/nucleo-h723zg/cubemx-freertos/Core/Src/main.c index 5164d294..71b02504 100644 --- a/tutorials/stm32/nucleo-h723zg/cubemx-freertos/Core/Src/main.c +++ b/tutorials/stm32/nucleo-h723zg/cubemx-freertos/Core/Src/main.c @@ -104,8 +104,7 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) { struct mg_http_message *hm = (struct mg_http_message *) ev_data; if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) { mg_http_reply(c, 200, "", "{%m:%lu}\n", MG_ESC("tick"), HAL_GetTick()); - } else if (mg_match(hm->uri, mg_str("/api/kill"), NULL)) { - SCB->SHCSR &= ~SCB_SHCSR_USGFAULTENA_Msk; + } else if (mg_match(hm->uri, mg_str("/api/crash"), NULL)) { __asm volatile("udf #0"); } else { mg_http_reply(c, 200, "Content-Type: text/plain\r\n", @@ -446,6 +445,11 @@ void StartDefaultTask(void *argument) mg_mgr_init(&mgr); mg_http_listen(&mgr, "http://0.0.0.0", http_ev_handler, NULL); + MG_INFO(("Initialised. CPU clock: %lu MHz", SystemCoreClock / 1000000)); + MG_INFO(("Memory: %M", mg_print_mem_stats)); + MG_INFO(("Crash record: %s, %M", mg_health_valid() ? "enabled" : "disabled", + mg_print_crash_record)); + /* Infinite loop */ for(;;) { diff --git a/tutorials/stm32/nucleo-h723zg/cubemx-freertos/Middlewares/Third_Party/FreeRTOS/Source/tasks.c b/tutorials/stm32/nucleo-h723zg/cubemx-freertos/Middlewares/Third_Party/FreeRTOS/Source/tasks.c index afb70092..a8b21e5c 100644 --- a/tutorials/stm32/nucleo-h723zg/cubemx-freertos/Middlewares/Third_Party/FreeRTOS/Source/tasks.c +++ b/tutorials/stm32/nucleo-h723zg/cubemx-freertos/Middlewares/Third_Party/FreeRTOS/Source/tasks.c @@ -328,6 +328,13 @@ typedef struct tskTaskControlBlock /* The old naming convention is used to * below to enable the use of older kernel aware debuggers. */ typedef tskTCB TCB_t; +#if ( configRECORD_STACK_HIGH_ADDRESS == 1 ) + _Static_assert( offsetof( TCB_t, pcTaskName ) == offsetof( StaticTask_t, ucDummy7 ), + "StaticTask_t task-name offset must match TCB_t" ); + _Static_assert( offsetof( TCB_t, pxEndOfStack ) == offsetof( StaticTask_t, pxDummy8 ), + "StaticTask_t stack-end offset must match TCB_t" ); +#endif + /*lint -save -e956 A manual analysis and inspection has been used to determine * which static variables must be declared volatile. */ portDONT_DISCARD PRIVILEGED_DATA TCB_t * volatile pxCurrentTCB = NULL; diff --git a/tutorials/stm32/nucleo-h723zg/cubemx-freertos/STM32H723XG_FLASH.ld b/tutorials/stm32/nucleo-h723zg/cubemx-freertos/STM32H723XG_FLASH.ld index fd8575e2..ec28538b 100644 --- a/tutorials/stm32/nucleo-h723zg/cubemx-freertos/STM32H723XG_FLASH.ld +++ b/tutorials/stm32/nucleo-h723zg/cubemx-freertos/STM32H723XG_FLASH.ld @@ -254,4 +254,6 @@ SECTIONS libgcc.a:* ( * ) } + /* Retained health record. Startup code must neither copy nor clear it. */ + .mg_health (NOLOAD) : { KEEP(*(.mg_health .mg_health*)) } > RAM_D3 } diff --git a/tutorials/stm32/nucleo-h723zg/cubemx-freertos/mongoose/mongoose_config.h b/tutorials/stm32/nucleo-h723zg/cubemx-freertos/mongoose/mongoose_config.h index a9414f66..26be0232 100644 --- a/tutorials/stm32/nucleo-h723zg/cubemx-freertos/mongoose/mongoose_config.h +++ b/tutorials/stm32/nucleo-h723zg/cubemx-freertos/mongoose/mongoose_config.h @@ -1,7 +1,11 @@ #pragma once #define MG_ARCH MG_ARCH_CUBE -#define MG_ENABLE_HEALTH 1 -#define MG_ENABLE_FREERTOS 1 +#define MG_ENABLE_HEALTH 1 // Mem stats and crash reports +#define MG_ENABLE_FREERTOS 1 // Set configRECORD_STACK_HIGH_ADDRESS + +#define MG_ENABLE_MDASH 0 // mdash.net device management service support +#define MG_MDASH_KEY "..." // See https://mongoose.ws/docs/guides/mdash/ +#define MG_FIRMWARE_VERSION "h723_cube.1.0.0" // Ethernet driver tunables: // #define MG_DRIVER_MDC_CR 4 // RMII MDC clock divider, from 0 to 5 diff --git a/tutorials/stm32/nucleo-h723zg/cubemx/Core/Src/main.c b/tutorials/stm32/nucleo-h723zg/cubemx/Core/Src/main.c index 47d7a875..9c7c765d 100644 --- a/tutorials/stm32/nucleo-h723zg/cubemx/Core/Src/main.c +++ b/tutorials/stm32/nucleo-h723zg/cubemx/Core/Src/main.c @@ -86,22 +86,24 @@ static void MX_USART3_UART_Init(void); /* USER CODE BEGIN 0 */ size_t mg_usage(int task, int metric) { extern void *_sbrk(ptrdiff_t incr); - extern unsigned char _end, _sstack; + extern unsigned char _end, _sstack, _estack; extern uint32_t _Min_Stack_Size; switch (metric) { case MG_USAGE_NUM_TASKS: return 1; case MG_USAGE_TASK_NAME: return task == 0 ? (size_t) "main" : 0; case MG_USAGE_STACK_TOTAL: return task == 0 ? (size_t) &_Min_Stack_Size : 0; - case MG_USAGE_STACK_FREE: { - size_t sp = (size_t) __get_MSP(), base = (size_t) &_sstack; - return task == 0 && sp > base ? sp - base : 0; + case MG_USAGE_STACK_USED: { + size_t sp = (size_t) __get_MSP(); + size_t base = (size_t) &_sstack, top = (size_t) &_estack; + if (task != 0 || sp > top) return 0; + return top - (sp < base ? base : sp); } case MG_USAGE_HEAP_TOTAL: return (size_t) &_sstack - (size_t) &_end; - case MG_USAGE_HEAP_FREE: { - size_t heap = (size_t) _sbrk(0), limit = (size_t) &_sstack; - return limit > heap ? limit - heap : 0; + case MG_USAGE_HEAP_USED: { + size_t heap = (size_t) _sbrk(0), start = (size_t) &_end; + return heap >= start && heap <= (size_t) &_sstack ? heap - start : 0; } default: return 0; } @@ -121,8 +123,7 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) { struct mg_http_message *hm = (struct mg_http_message *) ev_data; if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) { mg_http_reply(c, 200, "", "{%m:%lu}\n", MG_ESC("tick"), HAL_GetTick()); - } else if (mg_match(hm->uri, mg_str("/api/kill"), NULL)) { - SCB->SHCSR &= ~SCB_SHCSR_USGFAULTENA_Msk; + } else if (mg_match(hm->uri, mg_str("/api/crash"), NULL)) { __asm volatile("udf #0"); } else { mg_http_reply(c, 200, "", "Hi from Mongoose, tick %lu\n%M\n", diff --git a/tutorials/stm32/nucleo-h723zg/cubemx/mongoose/mongoose_config.h b/tutorials/stm32/nucleo-h723zg/cubemx/mongoose/mongoose_config.h index b4a69c76..4145e619 100644 --- a/tutorials/stm32/nucleo-h723zg/cubemx/mongoose/mongoose_config.h +++ b/tutorials/stm32/nucleo-h723zg/cubemx/mongoose/mongoose_config.h @@ -1,4 +1,5 @@ #pragma once +#include #define MG_ARCH MG_ARCH_CUBE #define MG_ENABLE_HEALTH 1 #define MG_ENABLE_CUSTOM_USAGE 1 diff --git a/tutorials/stm32/nucleo-h723zg/minimal/main.c b/tutorials/stm32/nucleo-h723zg/minimal/main.c index 66e0f726..9b731f58 100644 --- a/tutorials/stm32/nucleo-h723zg/minimal/main.c +++ b/tutorials/stm32/nucleo-h723zg/minimal/main.c @@ -6,13 +6,17 @@ size_t mg_usage(int task, int metric) { extern void *_sbrk(int incr); + extern unsigned char _end[], _estack[]; if (metric == MG_USAGE_NUM_TASKS) return 1; if (metric == MG_USAGE_TASK_NAME) return task == 0 ? (size_t) "main" : 0; - // Heap and stack share the free gap, neither has a fixed total - if (metric == MG_USAGE_HEAP_FREE || - (metric == MG_USAGE_STACK_FREE && task == 0)) { - size_t sp = (size_t) __get_MSP(), heap = (size_t) _sbrk(0); - return sp > heap ? sp - heap : 0; + // Heap and stack share RAM, so neither has a fixed total. + if (metric == MG_USAGE_HEAP_USED) { + size_t heap = (size_t) _sbrk(0), start = (size_t) _end; + return heap >= start && heap <= (size_t) _estack ? heap - start : 0; + } + if (metric == MG_USAGE_STACK_USED && task == 0) { + size_t sp = (size_t) __get_MSP(), top = (size_t) _estack; + return sp <= top ? top - sp : 0; } return 0; }