diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3dfd6e705..7a6e2f8f1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
- Berry class ``webclient`` for HTTP/HTTPS requests
- Support for ESP32S2 GPIOs
- ESP32 add GPIO 6/7/8/11 to template and remove GPIO 28-31 (remapping so backwards compatible)
+- Crash recorder ``Status 12`` for ESP32/ESP32S2/ESP32C3, supporting Esp-idf 3.3/4.4
### Fixed
- OpenTherm invalid JSON (#13028)
diff --git a/platformio_tasmota32.ini b/platformio_tasmota32.ini
index ad2a50d08..62dfd7739 100644
--- a/platformio_tasmota32.ini
+++ b/platformio_tasmota32.ini
@@ -23,6 +23,8 @@ build_flags = ${esp_defaults.build_flags}
-I$PROJECT_DIR/include
-include "sdkconfig.h"
-include "esp32x_fixes.h"
+ ; wrappers for the crash-recorder
+ -Wl,--wrap=panicHandler -Wl,--wrap=xt_unhandled_exception
[core32]
diff --git a/tasmota/support_crash_recorder.ino b/tasmota/support_crash_recorder.ino
index b9d81b53f..9981a9d7f 100644
--- a/tasmota/support_crash_recorder.ino
+++ b/tasmota/support_crash_recorder.ino
@@ -17,6 +17,31 @@
along with this program. If not, see .
*/
+// Generate a crash to test the crash recorder
+void CmndCrash(void)
+{
+ volatile uint32_t dummy;
+ dummy = *((uint32_t*) 0x00000000);
+ (void)dummy;
+}
+
+// Do an infinite loop to trigger WDT watchdog
+void CmndWDT(void)
+{
+ volatile uint32_t dummy = 0;
+ while (1) {
+ dummy++;
+ }
+}
+
+// This will trigger the os watch after OSWATCH_RESET_TIME (=120) seconds
+void CmndBlockedLoop(void)
+{
+ while (1) {
+ delay(1000);
+ }
+}
+
#ifdef ESP8266
const uint32_t crash_magic = 0x53415400; // Stack trace magic number (TASx)
@@ -45,31 +70,6 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
ESP.rtcUserMemoryWrite(crash_rtc_offset + crash_dump_max_len, (uint32_t*)&value, sizeof(value));
}
-// Generate a crash to test the crash recorder
-void CmndCrash(void)
-{
- volatile uint32_t dummy;
- dummy = *((uint32_t*) 0x00000000);
- (void)dummy;
-}
-
-// Do an infinite loop to trigger WDT watchdog
-void CmndWDT(void)
-{
- volatile uint32_t dummy = 0;
- while (1) {
- dummy++;
- }
-}
-
-// This will trigger the os watch after OSWATCH_RESET_TIME (=120) seconds
-void CmndBlockedLoop(void)
-{
- while (1) {
- delay(1000);
- }
-}
-
// Clear the RTC dump counter when we do a normal reboot, this avoids garbage data to stay in RTC
void CrashDumpClear(void)
{
@@ -113,4 +113,244 @@ void CrashDump(void)
ResponseJsonEnd();
}
-#endif // ESP8266
\ No newline at end of file
+#endif // ESP8266
+
+#ifdef ESP32
+
+const uint32_t crash_magic = 0x53415400; // Stack trace magic number (TASx)
+const uint32_t crash_dump_max_len = 48; // Dump only 48 call addresses
+
+// RTC_NOINIT_ATTR: store in RTC slow memory that survices a boot/crash
+// needs to be volatile and have no intializer
+RTC_NOINIT_ATTR volatile struct {
+ uint32_t magic;
+ uint32_t stack[crash_dump_max_len];
+ uint32_t pc;
+ uint32_t exccause;
+ uint32_t excvaddr;
+} crash_recorder;
+
+bool CrashFlag(void)
+{
+ return crash_recorder.magic == crash_magic;
+}
+
+// Clear the RTC dump counter when we do a normal reboot, this avoids garbage data to stay in RTC
+void CrashDumpClear(void)
+{
+ crash_recorder.magic = 0;
+ for (uint32_t i=0; ipc;
+ crash_recorder.exccause = exc_frame->exccause;
+ crash_recorder.excvaddr = exc_frame->excvaddr;
+ for (uint32_t i=0; ipc, .sp = exc_frame->a1, .next_pc = exc_frame->a0};
+ esp_backtrace_frame_t stk_frame;
+ stk_frame.pc = exc_frame->pc;
+ stk_frame.sp = exc_frame->a1;
+ stk_frame.next_pc = exc_frame->a0;
+ crash_recorder.stack[idx++] = esp_cpu_process_stack_pc(stk_frame.pc);
+
+ static const uint32_t depth = 100;
+ //Check if first frame is valid
+ bool corrupted = (esp_stack_ptr_is_sane(stk_frame.sp) &&
+ esp_ptr_executable((void*)esp_cpu_process_stack_pc(stk_frame.pc))) ?
+ false : true;
+ uint32_t i = ((depth <= 0) ? INT32_MAX : depth) - 1; //Account for stack frame that's already printed
+ while (i-- > 0 && stk_frame.next_pc != 0 && !corrupted) {
+ if (!esp_backtrace_get_next_frame(&stk_frame)) { //Get next stack frame
+ corrupted = true;
+ }
+ crash_recorder.stack[idx++] = esp_cpu_process_stack_pc(stk_frame.pc);
+ if (idx >= crash_dump_max_len) break; // no more slots
+ }
+}
+
+extern "C" IRAM_ATTR void __wrap_panicHandler(XtExcFrame *frame) {
+ custom_crash_recorder(frame);
+ __real_panicHandler(frame); // call the actual panic handler
+}
+
+extern "C" IRAM_ATTR void __wrap_xt_unhandled_exception(XtExcFrame *frame) {
+ crash_recorder.magic = crash_magic; // crash occured
+ custom_crash_recorder(frame);
+ __real_xt_unhandled_exception(frame); // call the actual panic handler
+}
+
+// taken in panic.c, but orginal array is 'static' so can't be called
+static const char *edesc[] = {
+ "IllegalInstruction", "Syscall", "InstructionFetchError", "LoadStoreError",
+ "Level1Interrupt", "Alloca", "IntegerDivideByZero", "PCValue",
+ "Privileged", "LoadStoreAlignment", "res", "res",
+ "InstrPDAddrError", "LoadStorePIFDataError", "InstrPIFAddrError", "LoadStorePIFAddrError",
+ "InstTLBMiss", "InstTLBMultiHit", "InstFetchPrivilege", "res",
+ "InstrFetchProhibited", "res", "res", "res",
+ "LoadStoreTLBMiss", "LoadStoreTLBMultihit", "LoadStorePrivilege", "res",
+ "LoadProhibited", "StoreProhibited", "res", "res",
+ "Cp0Dis", "Cp1Dis", "Cp2Dis", "Cp3Dis",
+ "Cp4Dis", "Cp5Dis", "Cp6Dis", "Cp7Dis"
+};
+#define NUM_EDESCS (sizeof(edesc) / sizeof(char *))
+
+void CrashDump(void)
+{
+ if (crash_recorder.magic == crash_magic) {
+ ResponseAppend_P("{\"Exception\":%d,\"Reason\":\"%s\",\"EPC\":\"%08x\",\"EXCVADDR\":\"%08x\"",
+ crash_recorder.exccause, // Exception Cause
+ crash_recorder.exccause < NUM_EDESCS ? edesc[crash_recorder.exccause] : "Unknown",
+ crash_recorder.pc, // Exception Progam Counter
+ crash_recorder.excvaddr // Exception Virtual Address Register - Virtual address that caused last fetch, load, or store exception
+ );
+ // crash dump present
+ ResponseAppend_P(PSTR(",\"CallChain\":["));
+ for (uint32_t i = 0; i < crash_dump_max_len; i++) {
+ uint32_t return_addr = crash_recorder.stack[i];
+ if (!return_addr) { break; }
+ if (i > 0) { ResponseAppend_P(PSTR(",")); }
+ ResponseAppend_P(PSTR("\"%08x\""), return_addr);
+ }
+ ResponseAppend_P(PSTR("]"));
+ }
+ ResponseJsonEnd();
+}
+#elif CONFIG_IDF_TARGET_ESP32C3
+
+extern "C" {
+ // esp-idf 3.x
+ void __real_panicHandler(void *frame);
+ void __real_xt_unhandled_exception(void *frame);
+}
+
+// from panic_arch.c
+const char *esp32c3_crash_reason[] = {
+ "Instruction address misaligned",
+ "Instruction access fault",
+ "Illegal instruction",
+ "Breakpoint",
+ "Load address misaligned",
+ "Load access fault",
+ "Store address misaligned",
+ "Store access fault",
+ "Environment call from U-mode",
+ "Environment call from S-mode",
+ "",
+ "Environment call from M-mode",
+ "Instruction page fault",
+ "Load page fault",
+ "",
+ "Store page fault",
+};
+#define NUM_C3_REASONS (sizeof(esp32c3_crash_reason) / sizeof(char *))
+
+#include
+extern "C" IRAM_ATTR void custom_crash_recorder(void *exc_frame) {
+ RvExcFrame *regs = (RvExcFrame *)exc_frame;
+
+ crash_recorder.magic = crash_magic; // crash occured
+ crash_recorder.pc = regs->mepc;
+ crash_recorder.exccause = regs->mcause;
+ crash_recorder.excvaddr = regs->mtval;
+ for (uint32_t i=0; ira; // push return address as first value
+
+ // // code copied from panic_print_basic_backtrace()
+ uint32_t * sp = (uint32_t*) regs->sp;
+ uint32_t i = 0;
+ for (uint32_t i = 0; ((uint32_t) sp) < 0x3FCDFFF0 && i < 320 && idx < crash_dump_max_len; i++, sp++) {
+ uint32_t value = *sp;
+ if ((value >= 0x40000000) && (value < 0x42800000)) { // keep only addresses in code area
+ crash_recorder.stack[idx++] = value;
+ }
+ }
+}
+
+extern "C" IRAM_ATTR void __wrap_panicHandler(void *frame) {
+ custom_crash_recorder(frame);
+ __real_panicHandler(frame); // call the actual panic handler
+}
+
+extern "C" IRAM_ATTR void __wrap_xt_unhandled_exception(void *frame) {
+ crash_recorder.magic = crash_magic; // crash occured
+ custom_crash_recorder(frame);
+ __real_xt_unhandled_exception(frame); // call the actual panic handler
+}
+
+void CrashDump(void)
+{
+ if (crash_recorder.magic == crash_magic) {
+ ResponseAppend_P("{\"Exception\":%d,\"Reason\":\"%s\",\"EPC\":\"%08x\",\"EXCVADDR\":\"%08x\"",
+ crash_recorder.exccause, // Exception Cause
+ crash_recorder.exccause < NUM_C3_REASONS ? esp32c3_crash_reason[crash_recorder.exccause] : "Unknown",
+ crash_recorder.pc, // Exception Progam Counter
+ crash_recorder.excvaddr // Exception Virtual Address Register - Virtual address that caused last fetch, load, or store exception
+ );
+ // crash dump present
+ ResponseAppend_P(PSTR(",\"CallChain\":["));
+ for (uint32_t i = 0; i < crash_dump_max_len; i++) {
+ uint32_t return_addr = crash_recorder.stack[i];
+ if (!return_addr) { break; }
+ if (i > 0) { ResponseAppend_P(PSTR(",")); }
+ ResponseAppend_P(PSTR("\"%08x\""), return_addr);
+ }
+ ResponseAppend_P(PSTR("]"));
+ }
+ ResponseJsonEnd();
+}
+#else // unknown
+
+bool CrashFlag(void)
+{
+ return false;
+}
+
+// Clear the RTC dump counter when we do a normal reboot, this avoids garbage data to stay in RTC
+void CrashDumpClear(void)
+{
+}
+
+void CrashDump(void)
+{
+}
+
+#endif
+#endif
\ No newline at end of file
diff --git a/tasmota/support_esp.ino b/tasmota/support_esp.ino
index 5feba48e7..6f94bf3f7 100644
--- a/tasmota/support_esp.ino
+++ b/tasmota/support_esp.ino
@@ -311,44 +311,6 @@ int32_t EspPartitionMmap(uint32_t action) {
}
*/
-//
-// Crash stuff
-//
-
-void CrashDump(void) {
-}
-
-bool CrashFlag(void) {
- return false;
-}
-
-void CrashDumpClear(void) {
-}
-
-void CmndCrash(void) {
- /*
- volatile uint32_t dummy;
- dummy = *((uint32_t*) 0x00000000);
-*/
-}
-
-// Do an infinite loop to trigger WDT watchdog
-void CmndWDT(void) {
- /*
- volatile uint32_t dummy = 0;
- while (1) {
- dummy++;
- }
-*/
-}
-// This will trigger the os watch after OSWATCH_RESET_TIME (=120) seconds
-void CmndBlockedLoop(void) {
- /*
- while (1) {
- delay(1000);
- }
-*/
-}
//
// ESP32 specific