mirror of
https://github.com/esphome/esphome.git
synced 2026-09-16 09:38:42 +00:00
[rp2040] Fix crash handler design flaws before release
Version the crash data by encoding the version in the magic value (upper 16 bits = 0xDEAD sentinel, lower 16 bits = version). This allows safely changing the scratch register layout in future firmware without misinterpreting old crash data, and costs zero scratch registers (we only have 8). Add USE_RP2040_CRASH_HANDLER define and guard all call sites so the crash handler can be conditionally compiled, matching the ESP32 crash handler pattern. Add crash_handler_has_data() so callers can check for crash data without triggering log output. Log crash data when the API client subscribes to logs so dashboard and HA users see crash reports even when connecting after boot, matching the ESP32 crash handler behavior.
This commit is contained in:
@@ -17,6 +17,9 @@
|
||||
#ifdef USE_ESP32_CRASH_HANDLER
|
||||
#include "esphome/components/esp32/crash_handler.h"
|
||||
#endif
|
||||
#ifdef USE_RP2040_CRASH_HANDLER
|
||||
#include "esphome/components/rp2040/crash_handler.h"
|
||||
#endif
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
|
||||
@@ -240,6 +243,9 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
App.schedule_dump_config();
|
||||
#ifdef USE_ESP32_CRASH_HANDLER
|
||||
esp32::crash_handler_log();
|
||||
#endif
|
||||
#ifdef USE_RP2040_CRASH_HANDLER
|
||||
rp2040::crash_handler_log();
|
||||
#endif
|
||||
}
|
||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#ifdef USE_RP2040
|
||||
#include "logger.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_RP2040_CRASH_HANDLER
|
||||
#include "esphome/components/rp2040/crash_handler.h"
|
||||
#endif
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::logger {
|
||||
@@ -26,7 +29,9 @@ void Logger::pre_setup() {
|
||||
}
|
||||
global_logger = this;
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
#ifdef USE_RP2040_CRASH_HANDLER
|
||||
rp2040::crash_handler_log();
|
||||
#endif
|
||||
}
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg, uint16_t len) {
|
||||
|
||||
@@ -212,6 +212,7 @@ async def to_code(config):
|
||||
)
|
||||
|
||||
cg.add_define("USE_RP2040_WATCHDOG_TIMEOUT", config[CONF_WATCHDOG_TIMEOUT])
|
||||
cg.add_define("USE_RP2040_CRASH_HANDLER")
|
||||
|
||||
|
||||
def add_pio_file(component: str, key: str, data: str):
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#ifdef USE_RP2040
|
||||
|
||||
#include "core.h"
|
||||
#include "crash_handler.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_RP2040_CRASH_HANDLER
|
||||
#include "crash_handler.h"
|
||||
#endif
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
@@ -25,7 +27,9 @@ void arch_restart() {
|
||||
}
|
||||
|
||||
void arch_init() {
|
||||
#ifdef USE_RP2040_CRASH_HANDLER
|
||||
rp2040::crash_handler_read_and_clear();
|
||||
#endif
|
||||
#if USE_RP2040_WATCHDOG_TIMEOUT > 0
|
||||
watchdog_enable(USE_RP2040_WATCHDOG_TIMEOUT, false);
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#ifdef USE_RP2040
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_RP2040_CRASH_HANDLER
|
||||
|
||||
#include "crash_handler.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
@@ -13,13 +16,19 @@
|
||||
static constexpr uint32_t EF_LR = 5;
|
||||
static constexpr uint32_t EF_PC = 6;
|
||||
|
||||
static constexpr uint32_t CRASH_MAGIC = 0xDEADBEEF;
|
||||
// Version encoded in the magic value: upper 16 bits are sentinel (0xDEAD),
|
||||
// lower 16 bits are the version number. This avoids using a separate scratch
|
||||
// register for versioning (we only have 8 total). Future firmware reads the
|
||||
// sentinel to confirm it's crash data, then the version to know the layout.
|
||||
static constexpr uint32_t CRASH_MAGIC_SENTINEL = 0xDEAD0000;
|
||||
static constexpr uint32_t CRASH_DATA_VERSION = 1;
|
||||
static constexpr uint32_t CRASH_MAGIC_V1 = CRASH_MAGIC_SENTINEL | CRASH_DATA_VERSION;
|
||||
|
||||
// We only have 8 scratch registers (32 bytes) that survive watchdog reboot.
|
||||
// Use them for the most important data, then scan the stack for code addresses.
|
||||
//
|
||||
// Scratch register layout:
|
||||
// [0] = magic (CRASH_MAGIC)
|
||||
// [0] = versioned magic (upper 16 bits = 0xDEAD sentinel, lower 16 bits = version)
|
||||
// [1] = PC (program counter at fault)
|
||||
// [2] = LR (link register from exception frame)
|
||||
// [3] = SP (stack pointer at fault)
|
||||
@@ -57,9 +66,12 @@ static struct {
|
||||
uint8_t backtrace_count;
|
||||
} __attribute__((section(".noinit"))) s_crash_data;
|
||||
|
||||
bool crash_handler_has_data() { return s_crash_data.valid; }
|
||||
|
||||
void crash_handler_read_and_clear() {
|
||||
s_crash_data.valid = false;
|
||||
if (watchdog_hw->scratch[0] == CRASH_MAGIC) {
|
||||
uint32_t magic = watchdog_hw->scratch[0];
|
||||
if ((magic & 0xFFFF0000) == CRASH_MAGIC_SENTINEL && (magic & 0xFFFF) == CRASH_DATA_VERSION) {
|
||||
s_crash_data.valid = true;
|
||||
s_crash_data.pc = watchdog_hw->scratch[1];
|
||||
s_crash_data.lr = watchdog_hw->scratch[2];
|
||||
@@ -135,7 +147,7 @@ static void __attribute__((used, noreturn)) hard_fault_handler_c(uint32_t *frame
|
||||
// by a stacking error or corrupted SP, frame may be invalid. Write a minimal
|
||||
// crash marker so we at least know a crash occurred.
|
||||
if (!is_valid_sram_ptr(frame)) {
|
||||
watchdog_hw->scratch[0] = CRASH_MAGIC;
|
||||
watchdog_hw->scratch[0] = CRASH_MAGIC_V1;
|
||||
watchdog_hw->scratch[1] = 0; // PC unknown
|
||||
watchdog_hw->scratch[2] = 0; // LR unknown
|
||||
watchdog_hw->scratch[3] = reinterpret_cast<uintptr_t>(frame); // Record the bad SP for diagnosis
|
||||
@@ -157,7 +169,7 @@ static void __attribute__((used, noreturn)) hard_fault_handler_c(uint32_t *frame
|
||||
uint32_t pre_fault_sp = reinterpret_cast<uintptr_t>(post_frame);
|
||||
|
||||
// Write key registers
|
||||
watchdog_hw->scratch[0] = CRASH_MAGIC;
|
||||
watchdog_hw->scratch[0] = CRASH_MAGIC_V1;
|
||||
watchdog_hw->scratch[1] = frame[EF_PC];
|
||||
watchdog_hw->scratch[2] = frame[EF_LR];
|
||||
watchdog_hw->scratch[3] = pre_fault_sp;
|
||||
@@ -224,4 +236,5 @@ extern "C" void __attribute__((naked, used)) isr_hardfault() {
|
||||
: "i"(hard_fault_handler_c));
|
||||
}
|
||||
|
||||
#endif // USE_RP2040_CRASH_HANDLER
|
||||
#endif // USE_RP2040
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
#ifdef USE_RP2040
|
||||
|
||||
#include <cstdint>
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_RP2040_CRASH_HANDLER
|
||||
|
||||
namespace esphome::rp2040 {
|
||||
|
||||
@@ -12,6 +14,10 @@ void crash_handler_read_and_clear();
|
||||
/// Log crash data if a crash was detected on previous boot.
|
||||
void crash_handler_log();
|
||||
|
||||
/// Returns true if crash data was found this boot.
|
||||
bool crash_handler_has_data();
|
||||
|
||||
} // namespace esphome::rp2040
|
||||
|
||||
#endif // USE_RP2040_CRASH_HANDLER
|
||||
#endif // USE_RP2040
|
||||
|
||||
@@ -338,6 +338,7 @@
|
||||
#ifdef USE_RP2040
|
||||
#define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 3, 0)
|
||||
#define USE_LOOP_PRIORITY
|
||||
#define USE_RP2040_CRASH_HANDLER
|
||||
#define USE_HTTP_REQUEST_RESPONSE
|
||||
#define USE_I2C
|
||||
#define USE_LOGGER_USB_CDC
|
||||
|
||||
Reference in New Issue
Block a user