/*
 * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */


#include <xtensa/coreasm.h>
#include <xtensa/corebits.h>
#include <xtensa/config/system.h>
#include "xtensa_context.h"
#include "freertos/xtensa_rtos.h"
#include "esp_private/panic_reason.h"
#include "sdkconfig.h"
#include "soc/soc.h"
#include "soc/soc_caps.h"
#include "soc/dport_reg.h"
#include "soc/timer_group_reg.h"

/*

Interrupt , a high-priority interrupt, is used for several things:
- IPC_ISR handler
- Cache error panic handler
- Interrupt watchdog panic handler

*/

#if CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5

#define LX_INTR_STACK_SIZE  12
#define LX_INTR_A2_OFFSET   0
#define LX_INTR_A3_OFFSET   4
#define LX_INTR_A4_OFFSET   8
#define EPC_X               EPC_5
#define EXCSAVE_X           EXCSAVE_5
#define RFI_X               5
#define xt_highintx         xt_highint5

#elif CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_4

#define LX_INTR_STACK_SIZE  12
#define LX_INTR_A2_OFFSET   0
#define LX_INTR_A3_OFFSET   4
#define LX_INTR_A4_OFFSET   8
#define EPC_X               EPC_4
#define EXCSAVE_X           EXCSAVE_4
#define RFI_X               4
#define xt_highintx         xt_highint4

#endif /* CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 */

/*
--------------------------------------------------------------------------------
  Macro wdt_clr_intr_status - Clear the WDT interrupt status.
  Macro wdt_feed            - Feed the WDT.

  Input  : "dev" - Beginning address of the peripheral registers

  Macro get_int_status_tg1wdt - Get the ETS_TG1_WDT_LEVEL_INTR_SOURCE bit in interrupt status

  output  : "reg" - Store the result into the reg
--------------------------------------------------------------------------------
*/

#define TIMG1_REG_OFFSET(reg)               ((reg) - REG_TIMG_BASE(1))
#define TIMG1_WDTWPROTECT_OFFSET            TIMG1_REG_OFFSET(TIMG_WDTWPROTECT_REG(1))
#define TIMG1_INT_CLR_OFFSET                TIMG1_REG_OFFSET(TIMG_INT_CLR_TIMERS_REG(1))
#define TIMG1_WDT_STG0_HOLD_OFFSET          TIMG1_REG_OFFSET(TIMG_WDTCONFIG2_REG(1))
#define TIMG1_WDT_STG1_HOLD_OFFSET          TIMG1_REG_OFFSET(TIMG_WDTCONFIG3_REG(1))
#define TIMG1_WDT_FEED_OFFSET               TIMG1_REG_OFFSET(TIMG_WDTFEED_REG(1))
#define UART0_DATA_REG                      (0x3FF40078)
#define TIMG_WDT_WKEY_VALUE                 0x50D83AA1
#define ETS_TG1_WDT_LEVEL_INTR_SOURCE       20

    .macro wdt_clr_intr_status  dev
    movi    a2, \dev
    movi    a3, TIMG_WDT_WKEY_VALUE
    s32i    a3, a2, TIMG1_WDTWPROTECT_OFFSET    /* disable write protect */
    memw
    l32i    a4, a2, TIMG1_INT_CLR_OFFSET
    memw
    movi    a3, 4
    or      a3, a4, a3
    s32i    a3, a2, TIMG1_INT_CLR_OFFSET        /* clear 1st stage timeout interrupt */
    memw
    movi    a3, 0
    s32i    a3, a2, TIMG1_WDTWPROTECT_OFFSET    /* enable write protect */
    memw
    .endm

    .macro wdt_feed dev
    movi    a2, \dev
    movi    a3, TIMG_WDT_WKEY_VALUE
    s32i    a3, a2, TIMG1_WDTWPROTECT_OFFSET    /* disable write protect */
    memw
    movi    a4, _lx_intr_livelock_max
    l32i    a4, a4, 0
    memw
    addi    a4, a4, 1
    movi    a3, (CONFIG_ESP_INT_WDT_TIMEOUT_MS<<1)
    quou    a3, a3, a4
    s32i    a3, a2, TIMG1_WDT_STG0_HOLD_OFFSET  /* set timeout before interrupt */
    memw
    movi    a3, (CONFIG_ESP_INT_WDT_TIMEOUT_MS<<2)
    s32i    a3, a2, TIMG1_WDT_STG1_HOLD_OFFSET  /* set timeout before system reset */
    memw
    movi    a3, 1
    s32i    a3, a2, TIMG1_WDT_FEED_OFFSET       /* feed wdt */
    memw
    movi    a3, 0
    s32i    a3, a2, TIMG1_WDTWPROTECT_OFFSET    /* enable write protect */
    memw
    .endm

    .macro get_int_status_tg1wdt reg
    rsr     \reg, INTERRUPT
    extui   \reg, \reg, ETS_T1_WDT_CACHEERR_INUM, 1
    beqz    \reg, 99f        /* not ETS_T1_WDT_INUM or ETS_CACHEERR_INUM */

    getcoreid   \reg
    bnez    \reg, 98f
    /* core 0 */
    movi    \reg, UART0_DATA_REG
    l32i    \reg, \reg, 0     /* Workaround for DPORT read error, for silicon revision 0~2 (ECO V0 ~ ECO V2). */
    movi    \reg, DPORT_PRO_INTR_STATUS_0_REG
    l32i    \reg, \reg, 0
    extui   \reg, \reg, ETS_TG1_WDT_LEVEL_INTR_SOURCE, 1
    j       99f

98: /* core 1 */
    movi    \reg, UART0_DATA_REG
    l32i    \reg, \reg, 0     /* Workaround for DPORT read error, for silicon revision 0~2 (ECO V0 ~ ECO V2). */
    movi    \reg, DPORT_APP_INTR_STATUS_0_REG
    l32i    \reg, \reg, 0
    extui   \reg, \reg, ETS_TG1_WDT_LEVEL_INTR_SOURCE, 1
99:
    .endm

    .data
_lx_intr_stack:
    .space      LX_INTR_STACK_SIZE*CONFIG_FREERTOS_NUMBER_OF_CORES /* This allocates stacks for each individual CPU. */

#if CONFIG_ESP32_ECO3_CACHE_LOCK_FIX && CONFIG_ESP_INT_WDT
    .global _lx_intr_livelock_counter
    .global _lx_intr_livelock_max
    .align  16
_lx_intr_livelock_counter:
    .word   0
_lx_intr_livelock_max:
    .word   0
_lx_intr_livelock_sync:
    .word   0, 0
_lx_intr_livelock_app:
    .word   0
_lx_intr_livelock_pro:
    .word   0
#endif

    .section .iram1,"ax"
    .global     xt_highintx
    .type       xt_highintx,@function
    .align      4
xt_highintx:

#ifndef CONFIG_ESP_SYSTEM_SINGLE_CORE_MODE
    /* See if we're here for the IPC_ISR interrupt */
    rsr     a0, INTERRUPT
    extui   a0, a0, ETS_IPC_ISR_INUM, 1
    beqz    a0, 1f
    /* Jump to `esp_ipc_isr_handler` which is non-returning. We need to use `jx`
     * because on Xtensa, `j` instruction can only refer to a label which
     * is in the range [-131068;+131075]. If the destination is out of scope,
     * linking will fail. So, to make sure we will always be able to jump to
     * that subroutine, retrieve its address and store it in a register. */
    movi    a0, esp_ipc_isr_handler
    jx      a0
1:
#endif /* not CONFIG_ESP_SYSTEM_SINGLE_CORE_MODE */

#if CONFIG_ESP32_ECO3_CACHE_LOCK_FIX && CONFIG_ESP_INT_WDT

#if CONFIG_BTDM_CTRL_HLI
    /* Timer 2 interrupt */
    rsr     a0, INTENABLE
    extui   a0, a0, 16, 1
    beqz    a0, 1f
    rsr     a0, INTERRUPT
    extui   a0, a0, 16, 1
    bnez    a0, .handle_multicore_debug_int
1:
#endif /* CONFIG_BTDM_CTRL_HLI */

    /* ETS_T1_WDT_INUM */
#if CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5
    get_int_status_tg1wdt a0
#elif CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_4
    /* See if we're here for the tg1 watchdog interrupt */
    rsr     a0, INTERRUPT
    extui   a0, a0, ETS_T1_WDT_INUM, 1
#endif /* CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 */
    beqz    a0, 1f

    wsr     a5, depc                        /* use DEPC as temp storage */
    movi    a0, _lx_intr_livelock_counter
    l32i    a0, a0, 0
    movi    a5, _lx_intr_livelock_max
    l32i    a5, a5, 0
    bltu    a0, a5, .handle_livelock_int    /* _lx_intr_livelock_counter < _lx_intr_livelock_max */

    rsr     a5, depc                        /* restore a5 */
#endif

1:  /* ETS_CACHEERR_INUM or ETS_T1_WDT_INUM */
    /* 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_X                       /* save interruptee's PC */
    s32i    a0, sp, XT_STK_PC
    rsr     a0, EXCSAVE_X                   /* save interruptee's a0 */
    s32i    a0, sp, XT_STK_A0
    #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 */
#if CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5
    get_int_status_tg1wdt a0
    bnez    a0, 1f

    /* TODO: Clear the MEMACCESS_ERR interrupt status. */
#elif CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_4
    rsr     a0, INTERRUPT
    extui   a0, a0, ETS_MEMACCESS_ERR_INUM, 1 /* get cacheerr int bit */
    beqz    a0, 1f
#endif /* CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 */

    /* Kill this interrupt; we cannot reset it. */
    rsr     a0, INTENABLE
    movi    a4, ~(1<<ETS_MEMACCESS_ERR_INUM)
    and     a0, a4, a0
    wsr     a0, INTENABLE
    movi    a0, PANIC_RSN_CACHEERR
    j 9f
1:
#if CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5
   /* Clear the WDT interrupt status. */
    wdt_clr_intr_status TIMERG1
#endif /* CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 */
#if CONFIG_ESP_INT_WDT_CHECK_CPU1
    /* Check if the cause is the app cpu failing to tick.*/
    movi    a0, int_wdt_cpu1_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

    /* 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_X
    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_X                   /* restore a0 */
    rfi     RFI_X


#if CONFIG_ESP32_ECO3_CACHE_LOCK_FIX && CONFIG_ESP_INT_WDT
#if CONFIG_BTDM_CTRL_HLI
#define APB_ITCTRL              (0x3f00)
#define APB_DCRSET              (0x200c)

#define ERI_ADDR(APB)           (0x100000 + (APB))

    .align      4
.handle_multicore_debug_int:

    wsr     a2, depc                        /* temp storage */

    rsr.ccount  a2
    addmi   a2, a2, (CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ*50)
    wsr     a2, CCOMPARE2

    /* Enable Integration Mode */
    movi    a2, ERI_ADDR(APB_ITCTRL)
    rer     a0, a2
    addi    a0, a0, 1
    wer     a0, a2

    /* Enable and emit BreakOut signal */
    movi    a2, ERI_ADDR(APB_DCRSET)
    rer     a0, a2
    movi    a2, 0x1020000
    or      a0, a2, a0
    movi    a2, ERI_ADDR(APB_DCRSET)
    wer     a0, a2

    .rept   4
    nop
    .endr

    /* Enable Normal Mode */
    movi    a2, ERI_ADDR(APB_ITCTRL)
    rer     a0, a2
    movi    a2, ~0x1
    and     a0, a2, a0
    movi    a2, ERI_ADDR(APB_ITCTRL)
    wer     a0, a2

    rsr     a2, depc

    rsr     a0, EXCSAVE_5                   /* restore a0 */
    rfi     5
#endif /* CONFIG_BTDM_CTRL_HLI */

/*
--------------------------------------------------------------------------------
  Macro intr_matrix_map - Attach an CPU interrupt to a hardware source.

  Input  : "addr" - Interrupt map configuration base address
  Input  : "src"  - Interrupt source.
  Input  : "inum" - Interrupt number.
--------------------------------------------------------------------------------
*/
    .macro intr_matrix_map  addr src inum
    movi    a2, \src
    slli    a2, a2, 2
    movi    a3, \addr
    add     a3, a3, a2
    movi    a2, \inum
    s32i    a2, a3, 0
    memw
    .endm



    .align      4
.handle_livelock_int:

    getcoreid   a5

    /* Save A2, A3, A4 so we can use those registers */
    movi    a0, LX_INTR_STACK_SIZE
    mull    a5, a5, a0
    movi    a0, _lx_intr_stack
    add     a0, a0, a5
    s32i    a2, a0, LX_INTR_A2_OFFSET
    s32i    a3, a0, LX_INTR_A3_OFFSET
    s32i    a4, a0, LX_INTR_A4_OFFSET

    /* Here, we can use a0, a2, a3, a4, a5 registers */
    getcoreid   a5

    rsil    a0, SOC_DPORT_WORKAROUND_DIS_INTERRUPT_LVL /* disable nested interrupt */

    beqz    a5, 1f
    movi    a2, _lx_intr_livelock_app
    l32i    a3, a2, 0
    addi    a3, a3, 1
    s32i    a3, a2, 0

    /* Dual core synchronization, ensuring that both cores enter interrupts */
1:  movi    a4, 0x1
    movi    a2, _lx_intr_livelock_sync
    addx4   a3, a5, a2
    s32i    a4, a3, 0

1:  movi    a2, _lx_intr_livelock_sync
    movi    a3, 1
    addx4   a3, a3, a2
    l32i    a2, a2, 0
    l32i    a3, a3, 0
    and     a2, a2, a3
    beqz    a2, 1b

    beqz    a5, 1f                          /* Pro cpu (Core 0) jump bypass */

    movi    a2, _lx_intr_livelock_app
    l32i    a2, a2, 0
    bnei    a2, 2, 1f
    movi    a2, _lx_intr_livelock_counter   /* _lx_intr_livelock_counter++ */
    l32i    a3, a2, 0
    addi    a3, a3, 1
    s32i    a3, a2, 0

    /*
    The delay time can be calculated by the following formula:
      T = ceil(0.25 + max(t1, t2)) us

      t1 = 80 / f1, t2 = (1 + 14/N) * 20 / f2

      f1: PSRAM access frequency, unit: MHz.
      f2: Flash access frequency, unit: MHz.

      When flash is slow/fast read, N = 1.
      When flash is DOUT/DIO read, N = 2.
      When flash is QOUT/QIO read, N = 4.
    */
1:  rsr.ccount  a2
#if defined(CONFIG_ESPTOOLPY_FLASHMODE_QIO) || defined(CONFIG_ESPTOOLPY_FLASHMODE_QOUT)
# if defined(CONFIG_ESPTOOLPY_FLASHFREQ_80M) && defined(CONFIG_SPIRAM_SPEED_80M)
    movi    a3, 480
# elif defined(CONFIG_ESPTOOLPY_FLASHFREQ_80M) && defined(CONFIG_SPIRAM_SPEED_40M)
    movi    a3, 720
# elif defined(CONFIG_ESPTOOLPY_FLASHFREQ_40M) && defined(CONFIG_SPIRAM_SPEED_40M)
    movi    a3, 720
# elif defined(CONFIG_ESPTOOLPY_FLASHFREQ_26M) && defined(CONFIG_SPIRAM_SPEED_40M)
    movi    a3, 960
# else
    movi    a3, 1200
# endif
#elif defined(CONFIG_ESPTOOLPY_FLASHMODE_DIO) || defined(CONFIG_ESPTOOLPY_FLASHMODE_DOUT)
# if defined(CONFIG_ESPTOOLPY_FLASHFREQ_80M) && defined(CONFIG_SPIRAM_SPEED_80M)
    movi    a3, 720
# elif defined(CONFIG_ESPTOOLPY_FLASHFREQ_80M) && defined(CONFIG_SPIRAM_SPEED_40M)
    movi    a3, 720
# elif defined(CONFIG_ESPTOOLPY_FLASHFREQ_40M) && defined(CONFIG_SPIRAM_SPEED_40M)
    movi    a3, 1200
# elif defined(CONFIG_ESPTOOLPY_FLASHFREQ_26M) && defined(CONFIG_SPIRAM_SPEED_40M)
    movi    a3, 1680
# else
    movi    a3, 2160
# endif
#endif
2:  rsr.ccount  a4                          /* delay_us(N) */
    sub     a4, a4, a2
    bltu    a4, a3, 2b

    beqz    a5, 2f
    movi    a2, _lx_intr_livelock_app
    l32i    a2, a2, 0
    beqi    a2, 2, 8f
    j       3f

2:  movi    a2, _lx_intr_livelock_pro
    l32i    a4, a2, 0
    addi    a4, a4, 1
    s32i    a4, a2, 0

    movi    a2, _lx_intr_livelock_sync
    movi    a3, 1
    addx4   a3, a3, a2
    l32i    a2, a2, 0
    l32i    a3, a3, 0
    and     a2, a2, a3
    beqz    a2, 5f
    j       1b
5:  bgei    a4, 2, 4f
    j       1b

    /*
    Pro cpu (Core 0) jump bypass, continue waiting, App cpu (Core 1)
    can execute to here, unmap itself tg1 1st stage timeout interrupt
    then restore registers and exit highint5/4.
    */
3:  intr_matrix_map DPORT_APP_MAC_INTR_MAP_REG, ETS_TG1_WDT_LEVEL_INTR_SOURCE, 16
    j       9f

    /*
    Here, App cpu (Core 1) has exited isr, Pro cpu (Core 0) help the
    App cpu map tg1 1st stage timeout interrupt clear tg1 interrupt.
    */
4:  intr_matrix_map DPORT_APP_MAC_INTR_MAP_REG, ETS_TG1_WDT_LEVEL_INTR_SOURCE, ETS_T1_WDT_INUM

1:  movi    a2, _lx_intr_livelock_sync
    movi    a4, 1
    addx4   a3, a4, a2
    l32i    a2, a2, 0
    l32i    a3, a3, 0
    and     a2, a2, a3
    beqz    a2, 1b                          /* Wait for App cpu to enter highint5/4 again */

    wdt_clr_intr_status TIMERG1
    j       9f

    /* Feed watchdog */
8:  wdt_feed    TIMERG1

9:  wsr     a0, PS                          /* restore interrupt level */

    movi    a0, 0
    beqz    a5, 1f
    movi    a2, _lx_intr_livelock_app
    l32i    a3, a2, 0
    bnei    a3, 2, 1f
    s32i    a0, a2, 0

1:  bnez    a5, 2f
    movi    a2, _lx_intr_livelock_pro
    s32i    a0, a2, 0
2:  movi    a2, _lx_intr_livelock_sync
    addx4   a2, a5, a2
    s32i    a0, a2, 0

    /* Done. Restore registers and return. */
    movi    a0, LX_INTR_STACK_SIZE
    mull    a5, a5, a0
    movi    a0, _lx_intr_stack
    add     a0, a0, a5
    l32i    a2, a0, LX_INTR_A2_OFFSET
    l32i    a3, a0, LX_INTR_A3_OFFSET
    l32i    a4, a0, LX_INTR_A4_OFFSET
    rsync                                   /* ensure register restored */

    rsr     a5, depc

    rsr     a0, EXCSAVE_X                   /* restore a0 */
    rfi     RFI_X

#endif

/* 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_highint_hdl
ld_include_highint_hdl:
