/** @file ArchExceptionHandler.c

  LoongArch64 CPU Exception Handler.

  Copyright (c) 2024, Loongson Technology Corporation Limited. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent
**/

#include <Library/BaseLib.h>
#include <Register/LoongArch64/Csr.h>
#include "ExceptionCommon.h"

/**
  Get Exception Type

  @param[in] SystemContext  Pointer to EFI_SYSTEM_CONTEXT.

  @return    LoongArch64 exception type.

**/
EFI_EXCEPTION_TYPE
EFIAPI
GetExceptionType (
  IN EFI_SYSTEM_CONTEXT  SystemContext
  )
{
  EFI_EXCEPTION_TYPE  ExceptionType;

  ExceptionType = (SystemContext.SystemContextLoongArch64->ESTAT & CSR_ESTAT_EXC) >> CSR_ESTAT_EXC_SHIFT;
  return ExceptionType;
}

/**
  Get Interrupt Type

  @param[in] SystemContext  Pointer to EFI_SYSTEM_CONTEXT.

  @return    LoongArch64 intrrupt type.

**/
EFI_EXCEPTION_TYPE
EFIAPI
GetInterruptType (
  IN EFI_SYSTEM_CONTEXT  SystemContext
  )
{
  EFI_EXCEPTION_TYPE  InterruptType;

  for (InterruptType = 0; InterruptType <= EXCEPT_LOONGARCH_INT_IPI; InterruptType++) {
    if (SystemContext.SystemContextLoongArch64->ESTAT & (1 << InterruptType)) {
      //
      // 0  - EXCEPT_LOONGARCH_INT_SIP0
      // 1  - EXCEPT_LOONGARCH_INT_SIP1
      // 2  - EXCEPT_LOONGARCH_INT_IP0
      // 3  - EXCEPT_LOONGARCH_INT_IP1
      // 4  - EXCEPT_LOONGARCH_INT_IP2
      // 5  - EXCEPT_LOONGARCH_INT_IP3
      // 6  - EXCEPT_LOONGARCH_INT_IP4
      // 7  - EXCEPT_LOONGARCH_INT_IP5
      // 8  - EXCEPT_LOONGARCH_INT_IP6
      // 9  - EXCEPT_LOONGARCH_INT_IP7
      // 10 - EXCEPT_LOONGARCH_INT_PMC
      // 11 - EXCEPT_LOONGARCH_INT_TIMER
      // 12 - EXCEPT_LOONGARCH_INT_IPI
      // Greater than EXCEPT_LOONGARCH_INI_IPI is currently invalid.
      //
      return InterruptType;
    }
  }

  //
  // Invalid IRQ
  //
  return 0xFF;
}

/**
  Display CPU information.

  @param ExceptionType  Exception type.
  @param SystemContext  Pointer to EFI_SYSTEM_CONTEXT.

**/
VOID
EFIAPI
DumpCpuContext (
  IN EFI_EXCEPTION_TYPE  ExceptionType,
  IN EFI_SYSTEM_CONTEXT  SystemContext
  )
{
  InternalPrintMessage (
    "\n!!!! LoongArch64 Exception Type - %02x(%a) !!!!\n",
    ExceptionType,
    GetExceptionNameStr (ExceptionType)
    );

  //
  // Dump interrupt type if the exception type is INT.
  //
  if (ExceptionType == EXCEPT_LOONGARCH_INT) {
    InternalPrintMessage (
      "\n!!!! Unhandled interrupt Type - %02x(%a) !!!!\n",
      GetInterruptType (SystemContext),
      GetInterruptNameStr (GetInterruptType (SystemContext))
      );
  }

  //
  // Dump TLB refill ERA and BADV
  //
  if (ExceptionType == (mExceptionKnownNameNum - 1)) {
    InternalPrintMessage ("TLB refill ERA  0x%llx\n", (CsrRead (LOONGARCH_CSR_TLBRERA) & (~0x3ULL)));
    InternalPrintMessage ("TLB refill BADV  0x%llx\n", CsrRead (LOONGARCH_CSR_TLBRBADV));
  }

  //
  // Dump the general registers
  //
  InternalPrintMessage (
    "Zero  - 0x%016lx, RA  - 0x%016lx, TP - 0x%016lx, SP - 0x%016lx\n",
    SystemContext.SystemContextLoongArch64->R0,
    SystemContext.SystemContextLoongArch64->R1,
    SystemContext.SystemContextLoongArch64->R2,
    SystemContext.SystemContextLoongArch64->R3
    );
  InternalPrintMessage (
    "  A0  - 0x%016lx, A1  - 0x%016lx, A2 - 0x%016lx, A3 - 0x%016lx\n",
    SystemContext.SystemContextLoongArch64->R4,
    SystemContext.SystemContextLoongArch64->R5,
    SystemContext.SystemContextLoongArch64->R6,
    SystemContext.SystemContextLoongArch64->R7
    );
  InternalPrintMessage (
    "  A4  - 0x%016lx, A5  - 0x%016lx, A6 - 0x%016lx, A7 - 0x%016lx\n",
    SystemContext.SystemContextLoongArch64->R8,
    SystemContext.SystemContextLoongArch64->R9,
    SystemContext.SystemContextLoongArch64->R10,
    SystemContext.SystemContextLoongArch64->R11
    );
  InternalPrintMessage (
    "  T0  - 0x%016lx, T1  - 0x%016lx, T2 - 0x%016lx, T3 - 0x%016lx\n",
    SystemContext.SystemContextLoongArch64->R12,
    SystemContext.SystemContextLoongArch64->R13,
    SystemContext.SystemContextLoongArch64->R14,
    SystemContext.SystemContextLoongArch64->R15
    );
  InternalPrintMessage (
    "  T4  - 0x%016lx, T5  - 0x%016lx, T6 - 0x%016lx, T7 - 0x%016lx\n",
    SystemContext.SystemContextLoongArch64->R16,
    SystemContext.SystemContextLoongArch64->R17,
    SystemContext.SystemContextLoongArch64->R18,
    SystemContext.SystemContextLoongArch64->R19
    );
  InternalPrintMessage (
    "  T8  - 0x%016lx, R21 - 0x%016lx, FP - 0x%016lx, S0 - 0x%016lx\n",
    SystemContext.SystemContextLoongArch64->R20,
    SystemContext.SystemContextLoongArch64->R21,
    SystemContext.SystemContextLoongArch64->R22,
    SystemContext.SystemContextLoongArch64->R23
    );
  InternalPrintMessage (
    "  S1  - 0x%016lx, S2  - 0x%016lx, S3 - 0x%016lx, S4 - 0x%016lx\n",
    SystemContext.SystemContextLoongArch64->R24,
    SystemContext.SystemContextLoongArch64->R25,
    SystemContext.SystemContextLoongArch64->R26,
    SystemContext.SystemContextLoongArch64->R27
    );
  InternalPrintMessage (
    "  S5  - 0x%016lx, S6  - 0x%016lx, S7 - 0x%016lx, S8 - 0x%016lx\n",
    SystemContext.SystemContextLoongArch64->R28,
    SystemContext.SystemContextLoongArch64->R29,
    SystemContext.SystemContextLoongArch64->R30,
    SystemContext.SystemContextLoongArch64->R31
    );
  InternalPrintMessage ("\n");

  //
  // Dump the CSR registers
  //
  InternalPrintMessage (
    "CRMD  - 0x%016lx, PRMD  - 0x%016lx, EUEN - 0x%016lx, MISC - 0x%016lx\n",
    SystemContext.SystemContextLoongArch64->CRMD,
    SystemContext.SystemContextLoongArch64->PRMD,
    SystemContext.SystemContextLoongArch64->EUEN,
    SystemContext.SystemContextLoongArch64->MISC
    );
  InternalPrintMessage (
    "ECFG  - 0x%016lx, ESTAT - 0x%016lx, ERA  - 0x%016lx, BADV - 0x%016lx\n",
    SystemContext.SystemContextLoongArch64->ECFG,
    SystemContext.SystemContextLoongArch64->ESTAT,
    SystemContext.SystemContextLoongArch64->ERA,
    SystemContext.SystemContextLoongArch64->BADV
    );
  InternalPrintMessage (
    "BADI  - 0x%016lx\n",
    SystemContext.SystemContextLoongArch64->BADI
    );
}

/**
  Display CPU information.

  @param ExceptionType  Exception type.
  @param SystemContext  Pointer to EFI_SYSTEM_CONTEXT.

**/
VOID
DumpImageAndCpuContent (
  IN EFI_EXCEPTION_TYPE  ExceptionType,
  IN EFI_SYSTEM_CONTEXT  SystemContext
  )
{
  DumpCpuContext (ExceptionType, SystemContext);

  if (ExceptionType == (mExceptionKnownNameNum - 1)) {
    //
    // Dump TLB refill image info
    //
    DumpModuleImageInfo ((CsrRead (LOONGARCH_CSR_TLBRERA) & (~0x3ULL)));
  } else {
    DumpModuleImageInfo (SystemContext.SystemContextLoongArch64->ERA);
  }
}

/**
  IPI Interrupt Handler.

  Generally, the IPI interrupt uses three vector:
  SMP_BOOT_CPU       BSP boots AP. The BSP may reside in OS or other non-UEFI environment.
  SMP_RESCHEDULE     BSP calls AP via UEFI MpInitLib.
  SMP_CALL_FUNCTION  BSP calls AP to jump specified pointer, which allows with one parameter.

  @param InterruptType    The type of interrupt that occurred
  @param SystemContext    A pointer to the system context when the interrupt occurred
**/
VOID
EFIAPI
IpiInterruptHandler (
  IN EFI_EXCEPTION_TYPE  InterruptType,
  IN EFI_SYSTEM_CONTEXT  SystemContext
  )
{
  UINTN  ResumeVector = 0;
  UINTN  Parameter    = 0;
  UINTN  IpiStatus    = 0;

  IpiStatus = IoCsrRead32 (LOONGARCH_IOCSR_IPI_STATUS);

  //
  // Clear interrupt.
  //
  IoCsrWrite32 (LOONGARCH_IOCSR_IPI_CLEAR, IpiStatus);

  if ((IpiStatus & SMP_RESCHEDULE) != 0) {
    MemoryFence ();
    return;
  } else {
    if (((IpiStatus & SMP_BOOT_CPU) != 0) || ((IpiStatus & SMP_CALL_FUNCTION) != 0)) {
      //
      // Confirm that the mail box message has arrived.
      //
      do {
        ResumeVector = IoCsrRead32 (LOONGARCH_IOCSR_MBUF0);
      } while (!ResumeVector);

      //
      // Get the resume vector if populated.
      //
      ResumeVector = IoCsrRead64 (LOONGARCH_IOCSR_MBUF0);

      if ((IpiStatus & SMP_BOOT_CPU) != 0) {
        SystemContext.SystemContextLoongArch64->PRMD &= ~((UINT64)BIT2); // Clean PIE
      } else if ((IpiStatus & SMP_CALL_FUNCTION) != 0) {
        //
        // Confirm that the mail box message has arrived.
        //
        do {
          Parameter = IoCsrRead32 (LOONGARCH_IOCSR_MBUF3);
        } while (!Parameter);

        //
        // Get the parameter if populated.
        //
        Parameter = IoCsrRead64 (LOONGARCH_IOCSR_MBUF3);

        //
        // Set $a0 as APIC ID and $a1 as parameter value.
        //
        SystemContext.SystemContextLoongArch64->R4 = CsrRead (LOONGARCH_CSR_CPUID);
        SystemContext.SystemContextLoongArch64->R5 = Parameter;
      }
    } else {
      InternalPrintMessage (
        "Core %d: Should never be here, IPI Status = %d.\n",
        CsrRead (LOONGARCH_CSR_CPUID),
        IpiStatus
        );
      DefaultExceptionHandler (EXCEPT_LOONGARCH_INT, SystemContext);
    }

    //
    // Clean up current processor mailbox 0 and mailbox 3.
    //
    IoCsrWrite64 (LOONGARCH_IOCSR_MBUF0, 0x0);
    IoCsrWrite64 (LOONGARCH_IOCSR_MBUF3, 0x0);

    //
    // Set the ERA to the resume vector sent by caller.
    //
    SystemContext.SystemContextLoongArch64->ERA = ResumeVector;
  }

  MemoryFence ();
}
