mirror of
https://github.com/espressif/esp-idf.git
synced 2025-08-10 04:43:33 +00:00
Merge branch 'refactor/common_code_panic_handler' into 'master'
Panic handling common code refactor See merge request espressif/esp-idf!7489
This commit is contained in:
@@ -16,14 +16,11 @@ else()
|
||||
"cpu_start.c"
|
||||
"crosscore_int.c"
|
||||
"dport_access.c"
|
||||
"dport_panic_highint_hdl.S"
|
||||
"hw_random.c"
|
||||
"int_wdt.c"
|
||||
"intr_alloc.c"
|
||||
"panic.c"
|
||||
"pm_esp32s2.c"
|
||||
"pm_trace.c"
|
||||
"reset_reason.c"
|
||||
"sleep_modes.c"
|
||||
"spiram.c"
|
||||
"spiram_psram.c"
|
||||
|
@@ -296,38 +296,6 @@ menu "ESP32S2-specific"
|
||||
|
||||
Data is reserved at the beginning of RTC slow memory.
|
||||
|
||||
choice ESP32S2_PANIC
|
||||
prompt "Panic handler behaviour"
|
||||
default ESP32S2_PANIC_PRINT_REBOOT
|
||||
help
|
||||
If FreeRTOS detects unexpected behaviour or an unhandled exception, the panic handler is
|
||||
invoked. Configure the panic handlers action here.
|
||||
|
||||
config ESP32S2_PANIC_PRINT_HALT
|
||||
bool "Print registers and halt"
|
||||
help
|
||||
Outputs the relevant registers over the serial port and halt the
|
||||
processor. Needs a manual reset to restart.
|
||||
|
||||
config ESP32S2_PANIC_PRINT_REBOOT
|
||||
bool "Print registers and reboot"
|
||||
help
|
||||
Outputs the relevant registers over the serial port and immediately
|
||||
reset the processor.
|
||||
|
||||
config ESP32S2_PANIC_SILENT_REBOOT
|
||||
bool "Silent reboot"
|
||||
help
|
||||
Just resets the processor without outputting anything
|
||||
|
||||
config ESP32S2_PANIC_GDBSTUB
|
||||
bool "Invoke GDBStub"
|
||||
select ESP_GDBSTUB_ENABLED
|
||||
help
|
||||
Invoke gdbstub on the serial port, allowing for gdb to attach to it to do a postmortem
|
||||
of the crash.
|
||||
endchoice
|
||||
|
||||
config ESP32S2_DEBUG_OCDAWARE
|
||||
bool "Make exception and panic handlers JTAG/OCD aware"
|
||||
default y
|
||||
|
@@ -78,5 +78,7 @@ void esp_cache_err_int_init(void)
|
||||
|
||||
int IRAM_ATTR esp_cache_err_get_cpuid(void)
|
||||
{
|
||||
// TODO: The description for this seem to indicate that when cache is not in error
|
||||
// state, return -1.
|
||||
return PRO_CPU_NUM;
|
||||
}
|
||||
|
@@ -1,120 +0,0 @@
|
||||
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
#include <xtensa/coreasm.h>
|
||||
#include <xtensa/corebits.h>
|
||||
#include <xtensa/config/system.h>
|
||||
#include "freertos/xtensa_context.h"
|
||||
#include "esp_private/panic_reason.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "soc/soc.h"
|
||||
#include "soc/dport_reg.h"
|
||||
|
||||
/*
|
||||
|
||||
Interrupt , a high-priority interrupt, is used for several things:
|
||||
- Cache error panic handler
|
||||
- Interrupt watchdog panic handler
|
||||
|
||||
*/
|
||||
|
||||
.section .iram1,"ax"
|
||||
.global xt_highint4
|
||||
.type xt_highint4,@function
|
||||
.align 4
|
||||
xt_highint4:
|
||||
|
||||
/* Allocate exception frame and save minimal context. */
|
||||
mov a0, sp
|
||||
addi sp, sp, -XT_STK_FRMSZ
|
||||
s32i a0, sp, XT_STK_A1
|
||||
#if XCHAL_HAVE_WINDOWED
|
||||
s32e a0, sp, -12 /* for debug backtrace */
|
||||
#endif
|
||||
rsr a0, PS /* save interruptee's PS */
|
||||
s32i a0, sp, XT_STK_PS
|
||||
rsr a0, EPC_4 /* save interruptee's PC */
|
||||
s32i a0, sp, XT_STK_PC
|
||||
#if XCHAL_HAVE_WINDOWED
|
||||
s32e a0, sp, -16 /* for debug backtrace */
|
||||
#endif
|
||||
s32i a12, sp, XT_STK_A12 /* _xt_context_save requires A12- */
|
||||
s32i a13, sp, XT_STK_A13 /* A13 to have already been saved */
|
||||
call0 _xt_context_save
|
||||
|
||||
/* Save vaddr into exception frame */
|
||||
rsr a0, EXCVADDR
|
||||
s32i a0, sp, XT_STK_EXCVADDR
|
||||
|
||||
/* Figure out reason, save into EXCCAUSE reg */
|
||||
|
||||
rsr a0, INTERRUPT
|
||||
extui a0, a0, ETS_CACHEERR_INUM, 1 /* get cacheerr int bit */
|
||||
beqz a0, 1f
|
||||
/* Kill this interrupt; we cannot reset it. */
|
||||
rsr a0, INTENABLE
|
||||
movi a4, ~(1<<ETS_CACHEERR_INUM)
|
||||
and a0, a4, a0
|
||||
wsr a0, INTENABLE
|
||||
movi a0, PANIC_RSN_CACHEERR
|
||||
j 9f
|
||||
1:
|
||||
#if CONFIG_INT_WDT_CHECK_CPU1
|
||||
/* Check if the cause is the app cpu failing to tick.*/
|
||||
movi a0, int_wdt_app_cpu_ticked
|
||||
l32i a0, a0, 0
|
||||
bnez a0, 2f
|
||||
/* It is. Modify cause. */
|
||||
movi a0,PANIC_RSN_INTWDT_CPU1
|
||||
j 9f
|
||||
2:
|
||||
#endif
|
||||
/* Set EXCCAUSE to reflect cause of the wdt int trigger */
|
||||
movi a0,PANIC_RSN_INTWDT_CPU0
|
||||
9:
|
||||
/* Found the reason, now save it. */
|
||||
s32i a0, sp, XT_STK_EXCCAUSE
|
||||
|
||||
/* _xt_context_save seems to save the current a0, but we need the interuptees a0. Fix this. */
|
||||
rsr a0, EXCSAVE_4 /* save interruptee's a0 */
|
||||
|
||||
s32i a0, sp, XT_STK_A0
|
||||
|
||||
/* Set up PS for C, disable all interrupts except NMI and debug, and clear EXCM. */
|
||||
movi a0, PS_INTLEVEL(5) | PS_UM | PS_WOE
|
||||
wsr a0, PS
|
||||
|
||||
//Call panic handler
|
||||
mov a6,sp
|
||||
call4 panicHandler
|
||||
|
||||
call0 _xt_context_restore
|
||||
l32i a0, sp, XT_STK_PS /* retrieve interruptee's PS */
|
||||
wsr a0, PS
|
||||
l32i a0, sp, XT_STK_PC /* retrieve interruptee's PC */
|
||||
wsr a0, EPC_4
|
||||
l32i a0, sp, XT_STK_A0 /* retrieve interruptee's A0 */
|
||||
l32i sp, sp, XT_STK_A1 /* remove exception frame */
|
||||
rsync /* ensure PS and EPC written */
|
||||
|
||||
rsr a0, EXCSAVE_4 /* restore a0 */
|
||||
rfi 4
|
||||
|
||||
/* The linker has no reason to link in this file; all symbols it exports are already defined
|
||||
(weakly!) in the default int handler. Define a symbol here so we can use it to have the
|
||||
linker inspect this anyway. */
|
||||
|
||||
.global ld_include_panic_highint_hdl
|
||||
ld_include_panic_highint_hdl:
|
@@ -1,8 +1,3 @@
|
||||
[mapping:esp32s2]
|
||||
archive: libesp32s2.a
|
||||
entries:
|
||||
panic (noflash)
|
||||
|
||||
[mapping:gcc]
|
||||
archive: libgcc.a
|
||||
entries:
|
||||
|
@@ -1,678 +0,0 @@
|
||||
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <xtensa/config/core.h>
|
||||
|
||||
#include "esp32s2/rom/rtc.h"
|
||||
#include "esp32s2/rom/uart.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/xtensa_api.h"
|
||||
|
||||
#include "soc/uart_reg.h"
|
||||
#include "soc/io_mux_reg.h"
|
||||
#include "soc/dport_reg.h"
|
||||
#include "soc/extmem_reg.h"
|
||||
#include "soc/cache_memory.h"
|
||||
#include "soc/rtc_cntl_reg.h"
|
||||
#include "soc/timer_group_struct.h"
|
||||
#include "soc/timer_group_reg.h"
|
||||
#include "soc/cpu.h"
|
||||
#include "soc/soc_memory_layout.h"
|
||||
#include "soc/rtc.h"
|
||||
#include "soc/rtc_wdt.h"
|
||||
|
||||
#include "esp_private/gdbstub.h"
|
||||
#include "esp_debug_helpers.h"
|
||||
#include "esp_private/panic_reason.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_core_dump.h"
|
||||
#include "esp_spi_flash.h"
|
||||
#include "esp32s2/cache_err_int.h"
|
||||
#include "esp_app_trace.h"
|
||||
#include "esp_private/system_internal.h"
|
||||
#include "sdkconfig.h"
|
||||
#if CONFIG_SYSVIEW_ENABLE
|
||||
#include "SEGGER_RTT.h"
|
||||
#endif
|
||||
|
||||
#if CONFIG_APPTRACE_ONPANIC_HOST_FLUSH_TMO == -1
|
||||
#define APPTRACE_ONPANIC_HOST_FLUSH_TMO ESP_APPTRACE_TMO_INFINITE
|
||||
#else
|
||||
#define APPTRACE_ONPANIC_HOST_FLUSH_TMO (1000*CONFIG_APPTRACE_ONPANIC_HOST_FLUSH_TMO)
|
||||
#endif
|
||||
/*
|
||||
Panic handlers; these get called when an unhandled exception occurs or the assembly-level
|
||||
task switching / interrupt code runs into an unrecoverable error. The default task stack
|
||||
overflow handler and abort handler are also in here.
|
||||
*/
|
||||
|
||||
/*
|
||||
Note: The linker script will put everything in this file in IRAM/DRAM, so it also works with flash cache disabled.
|
||||
*/
|
||||
|
||||
#if !CONFIG_ESP32S2_PANIC_SILENT_REBOOT
|
||||
//printf may be broken, so we fix our own printing fns...
|
||||
static void panicPutChar(char c)
|
||||
{
|
||||
while (((READ_PERI_REG(UART_STATUS_REG(CONFIG_ESP_CONSOLE_UART_NUM)) >> UART_TXFIFO_CNT_S)&UART_TXFIFO_CNT) >= 126) ;
|
||||
WRITE_PERI_REG(UART_FIFO_AHB_REG(CONFIG_ESP_CONSOLE_UART_NUM), c);
|
||||
}
|
||||
|
||||
static void panicPutStr(const char *c)
|
||||
{
|
||||
int x = 0;
|
||||
while (c[x] != 0) {
|
||||
panicPutChar(c[x]);
|
||||
x++;
|
||||
}
|
||||
}
|
||||
|
||||
static void panicPutHex(int a)
|
||||
{
|
||||
int x;
|
||||
int c;
|
||||
for (x = 0; x < 8; x++) {
|
||||
c = (a >> 28) & 0xf;
|
||||
if (c < 10) {
|
||||
panicPutChar('0' + c);
|
||||
} else {
|
||||
panicPutChar('a' + c - 10);
|
||||
}
|
||||
a <<= 4;
|
||||
}
|
||||
}
|
||||
|
||||
static void panicPutDec(int a)
|
||||
{
|
||||
int n1, n2;
|
||||
n1 = a % 10;
|
||||
n2 = a / 10;
|
||||
if (n2 == 0) {
|
||||
panicPutChar(' ');
|
||||
} else {
|
||||
panicPutChar(n2 + '0');
|
||||
}
|
||||
panicPutChar(n1 + '0');
|
||||
}
|
||||
#else
|
||||
//No printing wanted. Stub out these functions.
|
||||
static void panicPutChar(char c) { }
|
||||
static void panicPutStr(const char *c) { }
|
||||
static void panicPutHex(int a) { }
|
||||
static void panicPutDec(int a) { }
|
||||
#endif
|
||||
|
||||
void __attribute__((weak)) vApplicationStackOverflowHook( TaskHandle_t xTask, signed char *pcTaskName )
|
||||
{
|
||||
panicPutStr("***ERROR*** A stack overflow in task ");
|
||||
panicPutStr((char *)pcTaskName);
|
||||
panicPutStr(" has been detected.\r\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
/* These two weak stubs for esp_reset_reason_{get,set}_hint are used when
|
||||
* the application does not call esp_reset_reason() function, and
|
||||
* reset_reason.c is not linked into the output file.
|
||||
*/
|
||||
void __attribute__((weak)) esp_reset_reason_set_hint(esp_reset_reason_t hint)
|
||||
{
|
||||
}
|
||||
|
||||
esp_reset_reason_t __attribute__((weak)) esp_reset_reason_get_hint(void)
|
||||
{
|
||||
return ESP_RST_UNKNOWN;
|
||||
}
|
||||
|
||||
|
||||
static bool abort_called;
|
||||
|
||||
static __attribute__((noreturn)) inline void invoke_abort(void)
|
||||
{
|
||||
abort_called = true;
|
||||
#if CONFIG_APPTRACE_ENABLE
|
||||
#if CONFIG_SYSVIEW_ENABLE
|
||||
SEGGER_RTT_ESP32_FlushNoLock(CONFIG_APPTRACE_POSTMORTEM_FLUSH_THRESH, APPTRACE_ONPANIC_HOST_FLUSH_TMO);
|
||||
#else
|
||||
esp_apptrace_flush_nolock(ESP_APPTRACE_DEST_TRAX, CONFIG_APPTRACE_POSTMORTEM_FLUSH_THRESH,
|
||||
APPTRACE_ONPANIC_HOST_FLUSH_TMO);
|
||||
#endif
|
||||
#endif
|
||||
while (1) {
|
||||
if (esp_cpu_in_ocd_debug_mode()) {
|
||||
__asm__ ("break 0,0");
|
||||
}
|
||||
*((int *) 0) = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void abort(void)
|
||||
{
|
||||
#if !CONFIG_ESP32S2_PANIC_SILENT_REBOOT
|
||||
ets_printf("abort() was called at PC 0x%08x on core %d\r\n", (intptr_t)__builtin_return_address(0) - 3, xPortGetCoreID());
|
||||
#endif
|
||||
/* Calling code might have set other reset reason hint (such as Task WDT),
|
||||
* don't overwrite that.
|
||||
*/
|
||||
if (esp_reset_reason_get_hint() == ESP_RST_UNKNOWN) {
|
||||
esp_reset_reason_set_hint(ESP_RST_PANIC);
|
||||
}
|
||||
invoke_abort();
|
||||
}
|
||||
|
||||
|
||||
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 *))
|
||||
|
||||
static void commonErrorHandler(XtExcFrame *frame);
|
||||
static inline void disableAllWdts(void);
|
||||
|
||||
//The fact that we've panic'ed probably means the other CPU is now running wild, possibly
|
||||
//messing up the serial output, so we stall it here.
|
||||
|
||||
|
||||
static void setFirstBreakpoint(uint32_t pc)
|
||||
{
|
||||
asm(
|
||||
"wsr.ibreaka0 %0\n" \
|
||||
"rsr.ibreakenable a3\n" \
|
||||
"movi a4,1\n" \
|
||||
"or a4, a4, a3\n" \
|
||||
"wsr.ibreakenable a4\n" \
|
||||
::"r"(pc):"a3", "a4");
|
||||
}
|
||||
|
||||
static inline void printCacheError(void)
|
||||
{
|
||||
uint32_t vaddr = 0, size = 0;
|
||||
uint32_t status[2];
|
||||
status[0] = REG_READ(EXTMEM_CACHE_DBG_STATUS0_REG);
|
||||
status[1] = REG_READ(EXTMEM_CACHE_DBG_STATUS1_REG);
|
||||
for (int i = 0; i < 32; i++) {
|
||||
switch (status[0] & BIT(i)) {
|
||||
case EXTMEM_IC_SYNC_SIZE_FAULT_ST:
|
||||
vaddr = REG_READ(EXTMEM_PRO_ICACHE_MEM_SYNC0_REG);
|
||||
size = REG_READ(EXTMEM_PRO_ICACHE_MEM_SYNC1_REG);
|
||||
panicPutStr("Icache sync parameter configuration error, the error address and size is 0x");
|
||||
panicPutHex(vaddr);
|
||||
panicPutStr("(0x");
|
||||
panicPutHex(size);
|
||||
panicPutStr(")\r\n");
|
||||
break;
|
||||
case EXTMEM_IC_PRELOAD_SIZE_FAULT_ST:
|
||||
vaddr = REG_READ(EXTMEM_PRO_ICACHE_PRELOAD_ADDR_REG);
|
||||
size = REG_READ(EXTMEM_PRO_ICACHE_PRELOAD_SIZE_REG);
|
||||
panicPutStr("Icache preload parameter configuration error, the error address and size is 0x");
|
||||
panicPutHex(vaddr);
|
||||
panicPutStr("(0x");
|
||||
panicPutHex(size);
|
||||
panicPutStr(")\r\n");
|
||||
break;
|
||||
case EXTMEM_ICACHE_REJECT_ST:
|
||||
vaddr = REG_READ(EXTMEM_PRO_ICACHE_REJECT_VADDR_REG);
|
||||
panicPutStr("Icache reject error occurred while accessing the address 0x");
|
||||
panicPutHex(vaddr);
|
||||
|
||||
if (REG_READ(EXTMEM_PRO_CACHE_MMU_FAULT_CONTENT_REG) & MMU_INVALID) {
|
||||
panicPutStr(" (invalid mmu entry)");
|
||||
}
|
||||
panicPutStr("\r\n");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
switch (status[1] & BIT(i)) {
|
||||
case EXTMEM_DC_SYNC_SIZE_FAULT_ST:
|
||||
vaddr = REG_READ(EXTMEM_PRO_DCACHE_MEM_SYNC0_REG);
|
||||
size = REG_READ(EXTMEM_PRO_DCACHE_MEM_SYNC1_REG);
|
||||
panicPutStr("Dcache sync parameter configuration error, the error address and size is 0x");
|
||||
panicPutHex(vaddr);
|
||||
panicPutStr("(0x");
|
||||
panicPutHex(size);
|
||||
panicPutStr(")\r\n");
|
||||
break;
|
||||
case EXTMEM_DC_PRELOAD_SIZE_FAULT_ST:
|
||||
vaddr = REG_READ(EXTMEM_PRO_DCACHE_PRELOAD_ADDR_REG);
|
||||
size = REG_READ(EXTMEM_PRO_DCACHE_PRELOAD_SIZE_REG);
|
||||
panicPutStr("Dcache preload parameter configuration error, the error address and size is 0x");
|
||||
panicPutHex(vaddr);
|
||||
panicPutStr("(0x");
|
||||
panicPutHex(size);
|
||||
panicPutStr(")\r\n");
|
||||
break;
|
||||
case EXTMEM_DCACHE_WRITE_FLASH_ST:
|
||||
panicPutStr("Write back error occurred while dcache tries to write back to flash\r\n");
|
||||
break;
|
||||
case EXTMEM_DCACHE_REJECT_ST:
|
||||
vaddr = REG_READ(EXTMEM_PRO_DCACHE_REJECT_VADDR_REG);
|
||||
panicPutStr("Dcache reject error occurred while accessing the address 0x");
|
||||
panicPutHex(vaddr);
|
||||
|
||||
if (REG_READ(EXTMEM_PRO_CACHE_MMU_FAULT_CONTENT_REG) & MMU_INVALID) {
|
||||
panicPutStr(" (invalid mmu entry)");
|
||||
}
|
||||
panicPutStr("\r\n");
|
||||
break;
|
||||
case EXTMEM_MMU_ENTRY_FAULT_ST:
|
||||
vaddr = REG_READ(EXTMEM_PRO_CACHE_MMU_FAULT_VADDR_REG);
|
||||
panicPutStr("MMU entry fault error occurred while accessing the address 0x");
|
||||
panicPutHex(vaddr);
|
||||
|
||||
if (REG_READ(EXTMEM_PRO_CACHE_MMU_FAULT_CONTENT_REG) & MMU_INVALID) {
|
||||
panicPutStr(" (invalid mmu entry)");
|
||||
}
|
||||
panicPutStr("\r\n");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
panicPutStr("\r\n");
|
||||
}
|
||||
|
||||
void panicHandler(XtExcFrame *frame)
|
||||
{
|
||||
int core_id = xPortGetCoreID();
|
||||
//Please keep in sync with PANIC_RSN_* defines
|
||||
const char *reasons[] = {
|
||||
"Unknown reason",
|
||||
"Unhandled debug exception",
|
||||
"Double exception",
|
||||
"Unhandled kernel exception",
|
||||
"Coprocessor exception",
|
||||
"Interrupt wdt timeout on CPU0",
|
||||
"Interrupt wdt timeout on CPU1",
|
||||
"Cache exception",
|
||||
};
|
||||
const char *reason = reasons[0];
|
||||
//The panic reason is stored in the EXCCAUSE register.
|
||||
if (frame->exccause <= PANIC_RSN_MAX) {
|
||||
reason = reasons[frame->exccause];
|
||||
}
|
||||
|
||||
if (frame->exccause == PANIC_RSN_INTWDT_CPU0) {
|
||||
esp_reset_reason_set_hint(ESP_RST_INT_WDT);
|
||||
}
|
||||
|
||||
panicPutStr("Guru Meditation Error: Core ");
|
||||
panicPutDec(core_id);
|
||||
panicPutStr(" panic'ed (");
|
||||
panicPutStr(reason);
|
||||
panicPutStr(")\r\n");
|
||||
if (frame->exccause == PANIC_RSN_DEBUGEXCEPTION) {
|
||||
int debugRsn;
|
||||
asm("rsr.debugcause %0":"=r"(debugRsn));
|
||||
panicPutStr("Debug exception reason: ");
|
||||
if (debugRsn & XCHAL_DEBUGCAUSE_ICOUNT_MASK) {
|
||||
panicPutStr("SingleStep ");
|
||||
}
|
||||
if (debugRsn & XCHAL_DEBUGCAUSE_IBREAK_MASK) {
|
||||
panicPutStr("HwBreakpoint ");
|
||||
}
|
||||
if (debugRsn & XCHAL_DEBUGCAUSE_DBREAK_MASK) {
|
||||
//Unlike what the ISA manual says, this core seemingly distinguishes from a DBREAK
|
||||
//reason caused by watchdog 0 and one caused by watchdog 1 by setting bit 8 of the
|
||||
//debugcause if the cause is watchdog 1 and clearing it if it's watchdog 0.
|
||||
if (debugRsn & (1 << 8)) {
|
||||
#if CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK
|
||||
const char *name = pcTaskGetTaskName(xTaskGetCurrentTaskHandleForCPU(core_id));
|
||||
panicPutStr("Stack canary watchpoint triggered (");
|
||||
panicPutStr(name);
|
||||
panicPutStr(") ");
|
||||
#else
|
||||
panicPutStr("Watchpoint 1 triggered ");
|
||||
#endif
|
||||
} else {
|
||||
panicPutStr("Watchpoint 0 triggered ");
|
||||
}
|
||||
}
|
||||
if (debugRsn & XCHAL_DEBUGCAUSE_BREAK_MASK) {
|
||||
panicPutStr("BREAK instr ");
|
||||
}
|
||||
if (debugRsn & XCHAL_DEBUGCAUSE_BREAKN_MASK) {
|
||||
panicPutStr("BREAKN instr ");
|
||||
}
|
||||
if (debugRsn & XCHAL_DEBUGCAUSE_DEBUGINT_MASK) {
|
||||
panicPutStr("DebugIntr ");
|
||||
}
|
||||
panicPutStr("\r\n");
|
||||
} else if (frame->exccause == PANIC_RSN_CACHEERR) {
|
||||
panicPutStr(" ^~~~~~~~~~~~~~~\r\n");
|
||||
printCacheError();
|
||||
}
|
||||
|
||||
if (esp_cpu_in_ocd_debug_mode()) {
|
||||
disableAllWdts();
|
||||
if (frame->exccause == PANIC_RSN_INTWDT_CPU0 ||
|
||||
frame->exccause == PANIC_RSN_INTWDT_CPU1) {
|
||||
TIMERG1.int_clr.wdt = 1;
|
||||
}
|
||||
#if CONFIG_APPTRACE_ENABLE
|
||||
#if CONFIG_SYSVIEW_ENABLE
|
||||
SEGGER_RTT_ESP32_FlushNoLock(CONFIG_APPTRACE_POSTMORTEM_FLUSH_THRESH, APPTRACE_ONPANIC_HOST_FLUSH_TMO);
|
||||
#else
|
||||
esp_apptrace_flush_nolock(ESP_APPTRACE_DEST_TRAX, CONFIG_APPTRACE_POSTMORTEM_FLUSH_THRESH,
|
||||
APPTRACE_ONPANIC_HOST_FLUSH_TMO);
|
||||
#endif
|
||||
#endif
|
||||
setFirstBreakpoint(frame->pc);
|
||||
return;
|
||||
}
|
||||
commonErrorHandler(frame);
|
||||
}
|
||||
|
||||
void xt_unhandled_exception(XtExcFrame *frame)
|
||||
{
|
||||
if (!abort_called) {
|
||||
panicPutStr("Guru Meditation Error: Core ");
|
||||
panicPutDec(xPortGetCoreID());
|
||||
panicPutStr(" panic'ed (");
|
||||
int exccause = frame->exccause;
|
||||
if (exccause < NUM_EDESCS) {
|
||||
panicPutStr(edesc[exccause]);
|
||||
} else {
|
||||
panicPutStr("Unknown");
|
||||
}
|
||||
panicPutStr(")");
|
||||
if (esp_cpu_in_ocd_debug_mode()) {
|
||||
panicPutStr(" at pc=");
|
||||
panicPutHex(frame->pc);
|
||||
panicPutStr(". Setting bp and returning..\r\n");
|
||||
#if CONFIG_APPTRACE_ENABLE
|
||||
#if CONFIG_SYSVIEW_ENABLE
|
||||
SEGGER_RTT_ESP32_FlushNoLock(CONFIG_APPTRACE_POSTMORTEM_FLUSH_THRESH, APPTRACE_ONPANIC_HOST_FLUSH_TMO);
|
||||
#else
|
||||
esp_apptrace_flush_nolock(ESP_APPTRACE_DEST_TRAX, CONFIG_APPTRACE_POSTMORTEM_FLUSH_THRESH,
|
||||
APPTRACE_ONPANIC_HOST_FLUSH_TMO);
|
||||
#endif
|
||||
#endif
|
||||
//Stick a hardware breakpoint on the address the handler returns to. This way, the OCD debugger
|
||||
//will kick in exactly at the context the error happened.
|
||||
setFirstBreakpoint(frame->pc);
|
||||
return;
|
||||
}
|
||||
panicPutStr(". Exception was unhandled.\r\n");
|
||||
esp_reset_reason_set_hint(ESP_RST_PANIC);
|
||||
}
|
||||
commonErrorHandler(frame);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
If watchdogs are enabled, the panic handler runs the risk of getting aborted pre-emptively because
|
||||
an overzealous watchdog decides to reset it. On the other hand, if we disable all watchdogs, we run
|
||||
the risk of somehow halting in the panic handler and not resetting. That is why this routine kills
|
||||
all watchdogs except the timer group 0 watchdog, and it reconfigures that to reset the chip after
|
||||
one second.
|
||||
*/
|
||||
static void reconfigureAllWdts(void)
|
||||
{
|
||||
TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE;
|
||||
TIMERG0.wdt_feed = 1;
|
||||
TIMERG0.wdt_config0.sys_reset_length = 7; //3.2uS
|
||||
TIMERG0.wdt_config0.cpu_reset_length = 7; //3.2uS
|
||||
TIMERG0.wdt_config0.stg0 = TIMG_WDT_STG_SEL_RESET_SYSTEM; //1st stage timeout: reset system
|
||||
TIMERG0.wdt_config1.clk_prescale = 80 * 500; //Prescaler: wdt counts in ticks of 0.5mS
|
||||
TIMERG0.wdt_config2 = 2000; //1 second before reset
|
||||
TIMERG0.wdt_config0.en = 1;
|
||||
TIMERG0.wdt_wprotect = 0;
|
||||
//Disable wdt 1
|
||||
TIMERG1.wdt_wprotect = TIMG_WDT_WKEY_VALUE;
|
||||
TIMERG1.wdt_config0.en = 0;
|
||||
TIMERG1.wdt_wprotect = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
This disables all the watchdogs for when we call the gdbstub.
|
||||
*/
|
||||
static inline void disableAllWdts(void)
|
||||
{
|
||||
TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE;
|
||||
TIMERG0.wdt_config0.en = 0;
|
||||
TIMERG0.wdt_wprotect = 0;
|
||||
TIMERG1.wdt_wprotect = TIMG_WDT_WKEY_VALUE;
|
||||
TIMERG1.wdt_config0.en = 0;
|
||||
TIMERG1.wdt_wprotect = 0;
|
||||
}
|
||||
|
||||
#if CONFIG_ESP32S2_PANIC_PRINT_REBOOT || CONFIG_ESP32S2_PANIC_SILENT_REBOOT
|
||||
|
||||
static void esp_panic_dig_reset(void) __attribute__((noreturn));
|
||||
|
||||
static void esp_panic_dig_reset(void)
|
||||
{
|
||||
// make sure all the panic handler output is sent from UART FIFO
|
||||
uart_tx_wait_idle(CONFIG_ESP_CONSOLE_UART_NUM);
|
||||
// switch to XTAL (otherwise we will keep running from the PLL)
|
||||
rtc_clk_cpu_freq_set_xtal();
|
||||
SET_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG, RTC_CNTL_SW_SYS_RST);
|
||||
while (true) {
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static void putEntry(uint32_t pc, uint32_t sp)
|
||||
{
|
||||
if (pc & 0x80000000) {
|
||||
pc = (pc & 0x3fffffff) | 0x40000000;
|
||||
}
|
||||
panicPutStr(" 0x");
|
||||
panicPutHex(pc);
|
||||
panicPutStr(":0x");
|
||||
panicPutHex(sp);
|
||||
}
|
||||
|
||||
static void doBacktrace(XtExcFrame *frame)
|
||||
{
|
||||
uint32_t i = 0, pc = frame->pc, sp = frame->a1;
|
||||
panicPutStr("\r\nBacktrace:");
|
||||
/* Do not check sanity on first entry, PC could be smashed. */
|
||||
putEntry(pc, sp);
|
||||
pc = frame->a0;
|
||||
while (i++ < 100) {
|
||||
uint32_t psp = sp;
|
||||
if (!esp_stack_ptr_is_sane(sp) || i++ > 100) {
|
||||
break;
|
||||
}
|
||||
sp = *((uint32_t *) (sp - 0x10 + 4));
|
||||
putEntry(pc - 3, sp); // stack frame addresses are return addresses, so subtract 3 to get the CALL address
|
||||
pc = *((uint32_t *) (psp - 0x10));
|
||||
if (pc < 0x40000000) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
panicPutStr("\r\n\r\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Dump registers and do backtrace.
|
||||
*/
|
||||
static void commonErrorHandler_dump(XtExcFrame *frame, int core_id)
|
||||
{
|
||||
int *regs = (int *)frame;
|
||||
int x, y;
|
||||
const char *sdesc[] = {
|
||||
"PC ", "PS ", "A0 ", "A1 ", "A2 ", "A3 ", "A4 ", "A5 ",
|
||||
"A6 ", "A7 ", "A8 ", "A9 ", "A10 ", "A11 ", "A12 ", "A13 ",
|
||||
"A14 ", "A15 ", "SAR ", "EXCCAUSE", "EXCVADDR", "LBEG ", "LEND ", "LCOUNT "
|
||||
};
|
||||
|
||||
/* only dump registers for 'real' crashes, if crashing via abort()
|
||||
the register window is no longer useful.
|
||||
*/
|
||||
if (!abort_called) {
|
||||
panicPutStr("Core");
|
||||
panicPutDec(core_id);
|
||||
panicPutStr(" register dump:\r\n");
|
||||
|
||||
for (x = 0; x < 24; x += 4) {
|
||||
for (y = 0; y < 4; y++) {
|
||||
if (sdesc[x + y][0] != 0) {
|
||||
panicPutStr(sdesc[x + y]);
|
||||
panicPutStr(": 0x");
|
||||
panicPutHex(regs[x + y + 1]);
|
||||
panicPutStr(" ");
|
||||
}
|
||||
}
|
||||
panicPutStr("\r\n");
|
||||
}
|
||||
|
||||
if (xPortInterruptedFromISRContext()) {
|
||||
//If the core which triggers the interrupt watchdog was in ISR context, dump the epc registers.
|
||||
uint32_t __value;
|
||||
panicPutStr("Core");
|
||||
panicPutDec(core_id);
|
||||
panicPutStr(" was running in ISR context:\r\n");
|
||||
|
||||
__asm__("rsr.epc1 %0" : "=a"(__value));
|
||||
panicPutStr("EPC1 : 0x");
|
||||
panicPutHex(__value);
|
||||
|
||||
__asm__("rsr.epc2 %0" : "=a"(__value));
|
||||
panicPutStr(" EPC2 : 0x");
|
||||
panicPutHex(__value);
|
||||
|
||||
__asm__("rsr.epc3 %0" : "=a"(__value));
|
||||
panicPutStr(" EPC3 : 0x");
|
||||
panicPutHex(__value);
|
||||
|
||||
__asm__("rsr.epc4 %0" : "=a"(__value));
|
||||
panicPutStr(" EPC4 : 0x");
|
||||
panicPutHex(__value);
|
||||
|
||||
panicPutStr("\r\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* With windowed ABI backtracing is easy, let's do it. */
|
||||
doBacktrace(frame);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
We arrive here after a panic or unhandled exception, when no OCD is detected. Dump the registers to the
|
||||
serial port and either jump to the gdb stub, halt the CPU or reboot.
|
||||
*/
|
||||
static __attribute__((noreturn)) void commonErrorHandler(XtExcFrame *frame)
|
||||
{
|
||||
|
||||
int core_id = xPortGetCoreID();
|
||||
// start panic WDT to restart system if we hang in this handler
|
||||
if (!rtc_wdt_is_on()) {
|
||||
rtc_wdt_protect_off();
|
||||
rtc_wdt_disable();
|
||||
rtc_wdt_set_length_of_reset_signal(RTC_WDT_SYS_RESET_SIG, RTC_WDT_LENGTH_3_2us);
|
||||
rtc_wdt_set_length_of_reset_signal(RTC_WDT_CPU_RESET_SIG, RTC_WDT_LENGTH_3_2us);
|
||||
rtc_wdt_set_stage(RTC_WDT_STAGE0, RTC_WDT_STAGE_ACTION_RESET_SYSTEM);
|
||||
// 64KB of core dump data (stacks of about 30 tasks) will produce ~85KB base64 data.
|
||||
// @ 115200 UART speed it will take more than 6 sec to print them out.
|
||||
rtc_wdt_set_time(RTC_WDT_STAGE0, 7000);
|
||||
rtc_wdt_enable();
|
||||
rtc_wdt_protect_on();
|
||||
}
|
||||
|
||||
//Feed the watchdogs, so they will give us time to print out debug info
|
||||
reconfigureAllWdts();
|
||||
|
||||
commonErrorHandler_dump(frame, core_id);
|
||||
|
||||
#if CONFIG_APPTRACE_ENABLE
|
||||
disableAllWdts();
|
||||
#if CONFIG_SYSVIEW_ENABLE
|
||||
SEGGER_RTT_ESP32_FlushNoLock(CONFIG_APPTRACE_POSTMORTEM_FLUSH_THRESH, APPTRACE_ONPANIC_HOST_FLUSH_TMO);
|
||||
#else
|
||||
esp_apptrace_flush_nolock(ESP_APPTRACE_DEST_TRAX, CONFIG_APPTRACE_POSTMORTEM_FLUSH_THRESH,
|
||||
APPTRACE_ONPANIC_HOST_FLUSH_TMO);
|
||||
#endif
|
||||
reconfigureAllWdts();
|
||||
#endif
|
||||
|
||||
#if CONFIG_ESP32S2_PANIC_GDBSTUB
|
||||
disableAllWdts();
|
||||
rtc_wdt_disable();
|
||||
panicPutStr("Entering gdb stub now.\r\n");
|
||||
esp_gdbstub_panic_handler(frame);
|
||||
#else
|
||||
#if CONFIG_ESP32_ENABLE_COREDUMP
|
||||
static bool s_dumping_core;
|
||||
if (s_dumping_core) {
|
||||
panicPutStr("Re-entered core dump! Exception happened during core dump!\r\n");
|
||||
} else {
|
||||
disableAllWdts();
|
||||
s_dumping_core = true;
|
||||
#if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH
|
||||
esp_core_dump_to_flash(frame);
|
||||
#endif
|
||||
#if CONFIG_ESP32_ENABLE_COREDUMP_TO_UART && !CONFIG_ESP32S2_PANIC_SILENT_REBOOT
|
||||
esp_core_dump_to_uart(frame);
|
||||
#endif
|
||||
s_dumping_core = false;
|
||||
reconfigureAllWdts();
|
||||
}
|
||||
#endif /* CONFIG_ESP32_ENABLE_COREDUMP */
|
||||
rtc_wdt_disable();
|
||||
#if CONFIG_ESP32S2_PANIC_PRINT_REBOOT || CONFIG_ESP32S2_PANIC_SILENT_REBOOT
|
||||
panicPutStr("Rebooting...\r\n");
|
||||
if (frame->exccause != PANIC_RSN_CACHEERR) {
|
||||
esp_restart_noos();
|
||||
} else {
|
||||
// The only way to clear invalid cache access interrupt is to reset the digital part
|
||||
esp_panic_dig_reset();
|
||||
}
|
||||
#else
|
||||
disableAllWdts();
|
||||
panicPutStr("CPU halted.\r\n");
|
||||
while (1);
|
||||
#endif /* CONFIG_ESP32S2_PANIC_PRINT_REBOOT || CONFIG_ESP32S2_PANIC_SILENT_REBOOT */
|
||||
#endif /* CONFIG_ESP32S2_PANIC_GDBSTUB */
|
||||
}
|
||||
|
||||
|
||||
void esp_set_breakpoint_if_jtag(void *fn)
|
||||
{
|
||||
if (esp_cpu_in_ocd_debug_mode()) {
|
||||
setFirstBreakpoint((uint32_t)fn);
|
||||
}
|
||||
}
|
||||
|
||||
void _esp_error_check_failed(esp_err_t rc, const char *file, int line, const char *function, const char *expression)
|
||||
{
|
||||
ets_printf("ESP_ERROR_CHECK failed: esp_err_t 0x%x", rc);
|
||||
#ifdef CONFIG_ESP_ERR_TO_NAME_LOOKUP
|
||||
ets_printf(" (%s)", esp_err_to_name(rc));
|
||||
#endif //CONFIG_ESP_ERR_TO_NAME_LOOKUP
|
||||
ets_printf(" at 0x%08x\n", (intptr_t)__builtin_return_address(0) - 3);
|
||||
if (spi_flash_cache_enabled()) { // strings may be in flash cache
|
||||
ets_printf("file: \"%s\" line %d\nfunc: %s\nexpression: %s\n", file, line, function, expression);
|
||||
}
|
||||
invoke_abort();
|
||||
}
|
@@ -1,120 +0,0 @@
|
||||
// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "esp_system.h"
|
||||
#include "esp32s2/rom/rtc.h"
|
||||
#include "esp_private/system_internal.h"
|
||||
#include "soc/rtc_periph.h"
|
||||
|
||||
static void esp_reset_reason_clear_hint(void);
|
||||
|
||||
static esp_reset_reason_t s_reset_reason;
|
||||
|
||||
static esp_reset_reason_t get_reset_reason(RESET_REASON rtc_reset_reason, esp_reset_reason_t reset_reason_hint)
|
||||
{
|
||||
switch (rtc_reset_reason) {
|
||||
case POWERON_RESET:
|
||||
return ESP_RST_POWERON;
|
||||
|
||||
case RTC_SW_CPU_RESET:
|
||||
case RTC_SW_SYS_RESET:
|
||||
if (reset_reason_hint == ESP_RST_PANIC ||
|
||||
reset_reason_hint == ESP_RST_BROWNOUT ||
|
||||
reset_reason_hint == ESP_RST_TASK_WDT ||
|
||||
reset_reason_hint == ESP_RST_INT_WDT) {
|
||||
return reset_reason_hint;
|
||||
}
|
||||
return ESP_RST_SW;
|
||||
|
||||
case DEEPSLEEP_RESET:
|
||||
return ESP_RST_DEEPSLEEP;
|
||||
|
||||
case TG0WDT_SYS_RESET:
|
||||
return ESP_RST_TASK_WDT;
|
||||
|
||||
case TG1WDT_SYS_RESET:
|
||||
return ESP_RST_INT_WDT;
|
||||
|
||||
case RTCWDT_SYS_RESET:
|
||||
case RTCWDT_RTC_RESET:
|
||||
case SUPER_WDT_RESET:
|
||||
case RTCWDT_CPU_RESET: /* unused */
|
||||
case TG0WDT_CPU_RESET: /* unused */
|
||||
case TG1WDT_CPU_RESET: /* unused */
|
||||
return ESP_RST_WDT;
|
||||
|
||||
case RTCWDT_BROWN_OUT_RESET:
|
||||
return ESP_RST_BROWNOUT;
|
||||
|
||||
case INTRUSION_RESET: /* unused */
|
||||
default:
|
||||
return ESP_RST_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static void __attribute__((constructor)) esp_reset_reason_init(void)
|
||||
{
|
||||
esp_reset_reason_t hint = esp_reset_reason_get_hint();
|
||||
s_reset_reason = get_reset_reason(rtc_get_reset_reason(PRO_CPU_NUM),
|
||||
hint);
|
||||
if (hint != ESP_RST_UNKNOWN) {
|
||||
esp_reset_reason_clear_hint();
|
||||
}
|
||||
}
|
||||
|
||||
esp_reset_reason_t esp_reset_reason(void)
|
||||
{
|
||||
return s_reset_reason;
|
||||
}
|
||||
|
||||
/* Reset reason hint is stored in RTC_RESET_CAUSE_REG, a.k.a. RTC_CNTL_STORE6_REG,
|
||||
* a.k.a. RTC_ENTRY_ADDR_REG. It is safe to use this register both for the
|
||||
* deep sleep wake stub entry address and for reset reason hint, since wake stub
|
||||
* is only used for deep sleep reset, and in this case the reason provided by
|
||||
* rtc_get_reset_reason is unambiguous.
|
||||
*
|
||||
* Same layout is used as for RTC_APB_FREQ_REG (a.k.a. RTC_CNTL_STORE5_REG):
|
||||
* the value is replicated in low and high half-words. In addition to that,
|
||||
* MSB is set to 1, which doesn't happen when RTC_CNTL_STORE6_REG contains
|
||||
* deep sleep wake stub address.
|
||||
*/
|
||||
|
||||
#define RST_REASON_BIT 0x80000000
|
||||
#define RST_REASON_MASK 0x7FFF
|
||||
#define RST_REASON_SHIFT 16
|
||||
|
||||
/* in IRAM, can be called from panic handler */
|
||||
void IRAM_ATTR esp_reset_reason_set_hint(esp_reset_reason_t hint)
|
||||
{
|
||||
assert((hint & (~RST_REASON_MASK)) == 0);
|
||||
uint32_t val = hint | (hint << RST_REASON_SHIFT) | RST_REASON_BIT;
|
||||
REG_WRITE(RTC_RESET_CAUSE_REG, val);
|
||||
}
|
||||
|
||||
/* in IRAM, can be called from panic handler */
|
||||
esp_reset_reason_t IRAM_ATTR esp_reset_reason_get_hint(void)
|
||||
{
|
||||
uint32_t reset_reason_hint = REG_READ(RTC_RESET_CAUSE_REG);
|
||||
uint32_t high = (reset_reason_hint >> RST_REASON_SHIFT) & RST_REASON_MASK;
|
||||
uint32_t low = reset_reason_hint & RST_REASON_MASK;
|
||||
if ((reset_reason_hint & RST_REASON_BIT) == 0 || high != low) {
|
||||
return ESP_RST_UNKNOWN;
|
||||
}
|
||||
return (esp_reset_reason_t) low;
|
||||
}
|
||||
static void esp_reset_reason_clear_hint(void)
|
||||
{
|
||||
REG_WRITE(RTC_RESET_CAUSE_REG, 0);
|
||||
}
|
||||
|
Reference in New Issue
Block a user