diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 60cc3e91b11..68f698d1902 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -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 diff --git a/esphome/components/logger/logger_rp2040.cpp b/esphome/components/logger/logger_rp2040.cpp index f76b823a8f7..b7225c2a258 100644 --- a/esphome/components/logger/logger_rp2040.cpp +++ b/esphome/components/logger/logger_rp2040.cpp @@ -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) { diff --git a/esphome/components/rp2040/__init__.py b/esphome/components/rp2040/__init__.py index b15811241ca..276187b273c 100644 --- a/esphome/components/rp2040/__init__.py +++ b/esphome/components/rp2040/__init__.py @@ -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): diff --git a/esphome/components/rp2040/core.cpp b/esphome/components/rp2040/core.cpp index 5e5a96c78b1..7079cbca155 100644 --- a/esphome/components/rp2040/core.cpp +++ b/esphome/components/rp2040/core.cpp @@ -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 diff --git a/esphome/components/rp2040/crash_handler.cpp b/esphome/components/rp2040/crash_handler.cpp index 6ab46da4449..1f579c2d18e 100644 --- a/esphome/components/rp2040/crash_handler.cpp +++ b/esphome/components/rp2040/crash_handler.cpp @@ -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(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(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 diff --git a/esphome/components/rp2040/crash_handler.h b/esphome/components/rp2040/crash_handler.h index f10db47c234..78e8ede08c8 100644 --- a/esphome/components/rp2040/crash_handler.h +++ b/esphome/components/rp2040/crash_handler.h @@ -2,7 +2,9 @@ #ifdef USE_RP2040 -#include +#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 diff --git a/esphome/core/defines.h b/esphome/core/defines.h index a33f10cb9c0..073170aafbf 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -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