mirror of
https://github.com/crosspoint-reader/crosspoint-reader.git
synced 2026-08-13 14:21:57 -07:00
## Summary
* **What is the goal of this PR?**
* Prevent reset-only watchdog events from opening the crash-report flow
with an empty panic reason.
* **What changes are included?**
* Record a marker in RTC memory only when panic diagnostics are
captured.
* Continue reporting explicit panics and CPU lockups.
* Require that marker before treating watchdog resets as crashes, and
clear it with the existing panic state.
## Scope Check
CrossPoint is intentionally narrow. See
[SCOPE.md](../blob/master/SCOPE.md) and
[ROADMAP.md](../blob/master/ROADMAP.md).
Please confirm:
- [x] I have read SCOPE.md and ROADMAP.md.
- [x] This PR is **not** a new built-in theme (themes are temporarily
closed pending the move to SD-loaded themes).
- [x] This PR is **not** a new external network connector (sync engine,
cloud storage, remote file access, etc.).
- [x] This PR is **not** an interactive app, writing tool,
RSS/news/browser, media playback, or PDF feature.
- [x] The stock firmware does not already handle this well, **and** no
other popular CrossPoint fork already does
(or, if one does, I explain why CrossPoint still needs it below).
- [x] If this PR touches `freeink-sdk/`, `lib/hal/`, the bootloader,
OTA, or recovery code, I have coordinated with
the relevant maintainer.
**If this PR was opened against the previous (broader) scope and was
already in flight under Phase 0, link the
relevant Discussion or issue so reviewers can see the history.**
## Additional Context
* This is a focused port of CrossInk commit `c96d4275`.
* The change adds 4 bytes of RTC no-init state; it does not affect
storage formats, normal boot routing, or real panic reporting.
* Hardware follow-up: on an X3/X4, confirm a reset-only watchdog boots
normally without an empty crash report, while a genuine panic still
reaches the crash-report screen.
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**<YES>**_
174 lines
5.0 KiB
C++
174 lines
5.0 KiB
C++
#include "HalSystem.h"
|
|
|
|
#include <string>
|
|
|
|
#include "Arduino.h"
|
|
#include "HalStorage.h"
|
|
#include "Logging.h"
|
|
#include "esp_debug_helpers.h"
|
|
#include "esp_private/esp_cpu_internal.h"
|
|
#include "esp_private/esp_system_attr.h"
|
|
#include "esp_private/panic_internal.h"
|
|
|
|
#define MAX_PANIC_STACK_DEPTH 32
|
|
#define PANIC_CAPTURE_MAGIC 0x50414E49u
|
|
|
|
RTC_NOINIT_ATTR char panicMessage[256];
|
|
RTC_NOINIT_ATTR HalSystem::StackFrame panicStack[MAX_PANIC_STACK_DEPTH];
|
|
// RTC_NOINIT is uninitialized on cold boot, so only this exact marker proves a
|
|
// panic diagnostic was captured before the reset.
|
|
RTC_NOINIT_ATTR volatile uint32_t panicCaptureMarker;
|
|
|
|
extern "C" {
|
|
|
|
void __real_panic_abort(const char* message);
|
|
void __real_panic_print_backtrace(const void* frame, int core);
|
|
|
|
static DRAM_ATTR const char PANIC_REASON_UNKNOWN[] = "(unknown panic reason)";
|
|
void IRAM_ATTR __wrap_panic_abort(const char* message) {
|
|
if (!message) message = PANIC_REASON_UNKNOWN;
|
|
// IRAM-safe bounded copy (strncpy is not IRAM-safe in panic context)
|
|
int i = 0;
|
|
for (; i < (int)sizeof(panicMessage) - 1 && message[i]; i++) {
|
|
panicMessage[i] = message[i];
|
|
}
|
|
panicMessage[i] = '\0';
|
|
panicCaptureMarker = PANIC_CAPTURE_MAGIC;
|
|
|
|
__real_panic_abort(message);
|
|
}
|
|
|
|
void IRAM_ATTR __wrap_panic_print_backtrace(const void* frame, int core) {
|
|
if (!frame) {
|
|
__real_panic_print_backtrace(frame, core);
|
|
return;
|
|
}
|
|
|
|
#if !__riscv
|
|
__real_panic_print_backtrace(frame, core);
|
|
return;
|
|
#else
|
|
for (size_t i = 0; i < MAX_PANIC_STACK_DEPTH; i++) {
|
|
panicStack[i].sp = 0;
|
|
}
|
|
|
|
// Copied from components/esp_system/port/arch/riscv/panic_arch.c
|
|
uint32_t sp = (uint32_t)((RvExcFrame*)frame)->sp;
|
|
const int per_line = 8;
|
|
int depth = 0;
|
|
for (int x = 0; x < 1024; x += per_line * sizeof(uint32_t)) {
|
|
uint32_t* spp = (uint32_t*)(sp + x);
|
|
// panic_print_hex(sp + x);
|
|
// panic_print_str(": ");
|
|
panicStack[depth].sp = sp + x;
|
|
for (int y = 0; y < per_line; y++) {
|
|
// panic_print_str("0x");
|
|
// panic_print_hex(spp[y]);
|
|
// panic_print_str(y == per_line - 1 ? "\r\n" : " ");
|
|
panicStack[depth].spp[y] = spp[y];
|
|
}
|
|
|
|
depth++;
|
|
if (depth >= MAX_PANIC_STACK_DEPTH) {
|
|
break;
|
|
}
|
|
}
|
|
panicCaptureMarker = PANIC_CAPTURE_MAGIC;
|
|
|
|
__real_panic_print_backtrace(frame, core);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
namespace HalSystem {
|
|
|
|
void begin() {
|
|
// On a panic reboot, preserve diagnostics until checkPanic() has tried to write them to the SD card.
|
|
// Ordinary boots clear any stale retained diagnostics.
|
|
if (!isRebootFromPanic()) {
|
|
clearPanic();
|
|
} else {
|
|
// Panic reboot: preserve logs and panic info, but clamp logHead in case the
|
|
// panic occurred before begin() ever ran (e.g. in a static constructor).
|
|
// If logHead was out of range, logMessages is also garbage — clear it so
|
|
// getLastLogs() does not dump corrupt data into the crash report.
|
|
if (sanitizeLogHead()) {
|
|
clearLastLogs();
|
|
}
|
|
}
|
|
}
|
|
|
|
void checkPanic() {
|
|
if (isRebootFromPanic()) {
|
|
auto panicInfo = getPanicInfo(true);
|
|
auto file = Storage.open("/crash_report.txt", O_WRITE | O_CREAT | O_TRUNC);
|
|
if (file) {
|
|
const size_t written = file.write(panicInfo.c_str(), panicInfo.size());
|
|
file.close();
|
|
if (written == panicInfo.size()) {
|
|
// Keep the crash data for CrashActivity, but mark it consumed so a
|
|
// later watchdog reset cannot be mistaken for this panic.
|
|
panicCaptureMarker = 0;
|
|
LOG_INF("SYS", "Dumped panic info to SD card");
|
|
} else {
|
|
LOG_ERR("SYS", "Failed to write complete crash report (%zu of %zu bytes)", written, panicInfo.size());
|
|
}
|
|
} else {
|
|
LOG_ERR("SYS", "Failed to open crash_report.txt for writing");
|
|
}
|
|
}
|
|
}
|
|
|
|
void clearPanic() {
|
|
panicCaptureMarker = 0;
|
|
panicMessage[0] = '\0';
|
|
for (size_t i = 0; i < MAX_PANIC_STACK_DEPTH; i++) {
|
|
panicStack[i].sp = 0;
|
|
}
|
|
clearLastLogs();
|
|
}
|
|
|
|
std::string getPanicInfo(bool full) {
|
|
if (!full) {
|
|
return panicMessage;
|
|
} else {
|
|
std::string info;
|
|
|
|
info += "CrossPoint version: " CROSSPOINT_VERSION;
|
|
info += "\n\nPanic reason: " + std::string(panicMessage);
|
|
info += "\n\nLast logs:\n" + getLastLogs();
|
|
info += "\n\nStack memory:\n";
|
|
|
|
auto toHex = [](uint32_t value) {
|
|
char buffer[9];
|
|
snprintf(buffer, sizeof(buffer), "%08X", value);
|
|
return std::string(buffer);
|
|
};
|
|
for (size_t i = 0; i < MAX_PANIC_STACK_DEPTH; i++) {
|
|
if (panicStack[i].sp == 0) {
|
|
break;
|
|
}
|
|
info += "0x" + toHex(panicStack[i].sp) + ": ";
|
|
for (size_t j = 0; j < 8; j++) {
|
|
info += "0x" + toHex(panicStack[i].spp[j]) + " ";
|
|
}
|
|
info += "\n";
|
|
}
|
|
|
|
return info;
|
|
}
|
|
}
|
|
|
|
bool isRebootFromPanic() {
|
|
const auto resetReason = esp_reset_reason();
|
|
if (resetReason == ESP_RST_PANIC || resetReason == ESP_RST_CPU_LOCKUP) {
|
|
return true;
|
|
}
|
|
|
|
const bool watchdogReset =
|
|
resetReason == ESP_RST_INT_WDT || resetReason == ESP_RST_TASK_WDT || resetReason == ESP_RST_WDT;
|
|
return watchdogReset && panicCaptureMarker == PANIC_CAPTURE_MAGIC;
|
|
}
|
|
|
|
} // namespace HalSystem
|