Files
ZephCore/zephcore/helpers/fatal_reboot.c
T
liquidraver d5bd805710 peripherals & power: GPS fixes, poweroff/pwrmgt, upstream SensorManager telemetry
- gps: servers honour gps_enabled at boot (was lost on reboot; zc.gps_set
  upgrades older prefs to on once); sleep lock only while acquiring;
  gps off works on boards without a power line (UART sleep); UI toggles
  and fixes handled on the main thread; configure only after a good init;
  observer parks its GPS; one interval clamp (clampGpsInterval); one
  nav-mode knob (room servers on Air530Z now stationary); GNSS UARTE from DT
- gps: split into ZephyrGPSManager / gps_power / gps_module_cfg
  (+gps_internal.h); dead driver branches, gps_get_diag, sats_* removed
- clock: ZephyrRTCClock writes the hardware RTC itself (clkreboot now
  resets it); MESH_EVENT_RTC_SAVE gone
- power: one power-off path (zephcore_power_off, ZephyrBoard::powerOff),
  poweroff/shutdown CLI, get pwrmgt.*, crash record kept across reboot,
  battery reading cached 10 s, fuel gauge on the current API
- telemetry: upstream SensorManager + CayenneLPP (rounded to nearest),
  one builder for all roles; per-sensor channels, MCU temp always,
  guest gating and inverse mask on servers; sensor list/get/set work;
  BME280/BMP280 also probed at 0x77
- CLI: gps in upstream's form, get gps detail, gps sync
2026-09-25 17:56:34 +02:00

128 lines
3.7 KiB
C

/*
* SPDX-License-Identifier: MIT
*
* Production reboot-on-fatal-error handler.
*
* Zephyr has no built-in Kconfig to auto-reboot on a fatal error — the
* default k_sys_fatal_error_handler() (weak, in kernel/fatal.c) flushes the
* log and calls arch_system_halt(), spinning forever. For unattended mesh
* nodes that is the wrong behaviour: a transient stack overflow / CPU
* exception / k_panic should recover the device rather than wedge it until a
* manual power-cycle.
*
* When CONFIG_ZEPHCORE_RESET_ON_FATAL_ERROR=y (production default; debug.conf
* forces it n so a debugger can inspect the halted core) we override the weak
* handler to cold-reboot after flushing the panic log. When it is n the weak
* default applies and there is no crash record.
*
* The handler also leaves {reason, pc, lr, thread} in RAM the boot does not
* clear, so the next boot can say what crashed (get pwrmgt.bootreason, the
* boot log) without a debugger: resolve pc with addr2line on the same build.
*/
#include <zephyr/kernel.h>
#include <string.h>
#include "boot_info.h"
#if IS_ENABLED(CONFIG_ZEPHCORE_RESET_ON_FATAL_ERROR)
#include <zephyr/init.h>
#include <zephyr/fatal.h>
#include <zephyr/sys/reboot.h>
#include <zephyr/logging/log.h>
#include <zephyr/logging/log_ctrl.h>
LOG_MODULE_REGISTER(zephcore_fatal, CONFIG_ZEPHCORE_BOARD_LOG_LEVEL);
#define CRASH_MAGIC 0x7A43F417u
/* Survives the warm reboot on nRF; on ESP32 the bootloader in between may
* reuse the RAM, which the magic and check reject. */
static __noinit struct {
uint32_t magic;
struct zephcore_crash crash;
uint32_t check;
} s_record;
static struct zephcore_crash s_last;
static bool s_last_valid;
static uint32_t record_check(void)
{
const uint32_t *w = (const uint32_t *)&s_record.crash;
uint32_t sum = CRASH_MAGIC;
for (size_t i = 0; i < sizeof(s_record.crash) / sizeof(uint32_t); i++) {
sum = (sum << 5 | sum >> 27) ^ w[i];
}
return sum;
}
void k_sys_fatal_error_handler(unsigned int reason, const struct arch_esf *esf)
{
memset(&s_record.crash, 0, sizeof(s_record.crash));
s_record.crash.reason = reason;
if (esf != NULL) {
#if defined(CONFIG_ARM)
s_record.crash.pc = esf->basic.pc;
s_record.crash.lr = esf->basic.lr;
#elif defined(CONFIG_RISCV)
s_record.crash.pc = (uint32_t)esf->mepc;
s_record.crash.lr = (uint32_t)esf->ra;
#endif
}
const char *name = k_thread_name_get(k_current_get());
if (name != NULL) {
strncpy(s_record.crash.thread, name, sizeof(s_record.crash.thread) - 1);
}
s_record.magic = CRASH_MAGIC;
s_record.check = record_check();
/* Put logging into panic (synchronous) mode and flush so the fault
* reason actually reaches the console/RTT before we reset. */
LOG_PANIC();
LOG_ERR("Fatal error (reason %u) in %s pc 0x%08x lr 0x%08x — cold rebooting",
reason, s_record.crash.thread, s_record.crash.pc, s_record.crash.lr);
sys_reboot(SYS_REBOOT_COLD);
CODE_UNREACHABLE;
}
/* POST_KERNEL, like the reset cause: take the record once, so it is reported
* for the boot it caused and not again. */
static int crash_record_init(void)
{
if (s_record.magic == CRASH_MAGIC && s_record.check == record_check()) {
s_last = s_record.crash;
s_last.thread[sizeof(s_last.thread) - 1] = '\0';
s_last_valid = true;
LOG_WRN("Last run crashed: reason %u in %s pc 0x%08x lr 0x%08x",
s_last.reason, s_last.thread, s_last.pc, s_last.lr);
}
s_record.magic = 0;
return 0;
}
SYS_INIT(crash_record_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
bool zephcore_boot_crash(struct zephcore_crash *out)
{
if (!s_last_valid) {
return false;
}
*out = s_last;
return true;
}
#else
bool zephcore_boot_crash(struct zephcore_crash *out)
{
ARG_UNUSED(out);
return false;
}
#endif /* CONFIG_ZEPHCORE_RESET_ON_FATAL_ERROR */