/** @file
  RISC-V backtrace implementation.

  Copyright (c) 2016 - 2022, Hewlett Packard Enterprise Development LP. All rights reserved.<BR>
  Copyright (c) 2011 - 2014, ARM Ltd. All rights reserved.<BR>
  Copyright (c) 2023, Intel Corporation. All rights reserved.<BR>
  Copyright (c) 2025, Ventana Micro Systems Inc. All rights reserved.<BR>

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

**/

#include "Backtrace.h"

#define MAX_STACK_FRAME_SIZE  SIZE_16KB

STATIC
INTN
CheckFpValid (
  IN UINTN  Fp,
  IN UINTN  Sp
  )
{
  UINTN  Low, High;

  Low  = Sp + 2 * sizeof (UINTN);
  High = ALIGN_VALUE (Sp, MAX_STACK_FRAME_SIZE);

  return !(Fp < Low || Fp > High || Fp & 0x07);
}

STATIC
CONST CHAR8 *
BaseName (
  IN  CONST CHAR8  *FullName
  )
{
  CONST CHAR8  *Str;

  Str = FullName + AsciiStrLen (FullName);

  while (--Str > FullName) {
    if ((*Str == '/') || (*Str == '\\')) {
      return Str + 1;
    }
  }

  return Str;
}

/**
  Helper for displaying a backtrace.

  @param Regs       Pointer to SMODE_TRAP_REGISTERS.
  @param FirstPdb   Pointer to the first symbol file used.
  @param ListImage  If true, only show the full path to symbol file, else
                    show the PC value and its decoded components.
**/
STATIC
VOID
DumpCpuBacktraceHelper (
  IN  SMODE_TRAP_REGISTERS  *Regs,
  IN  CHAR8                 *FirstPdb,
  IN  BOOLEAN               ListImage
  )
{
  UINTN    ImageBase;
  UINTN    PeCoffSizeOfHeader;
  BOOLEAN  IsLeaf;
  UINTN    RootFp;
  UINTN    RootRa;
  UINTN    Sp;
  UINTN    Fp;
  UINTN    Ra;
  UINTN    Idx;
  CHAR8    *Pdb;
  CHAR8    *PrevPdb;

  RootRa = Regs->ra;
  RootFp = Regs->s0;

  Idx     = 0;
  IsLeaf  = TRUE;
  Fp      = RootFp;
  Ra      = RootRa;
  PrevPdb = FirstPdb;
  while (Fp != 0) {
    Pdb = GetImageName (Ra, &ImageBase, &PeCoffSizeOfHeader);
    if (Pdb != NULL) {
      if (Pdb != PrevPdb) {
        Idx++;
        if (ListImage) {
          DEBUG ((DEBUG_ERROR, "[% 2d] %a\n", Idx, Pdb));
        }

        PrevPdb = Pdb;
      }

      if (!ListImage) {
        DEBUG ((
          DEBUG_ERROR,
          "PC 0x%012lx (0x%012lx+0x%08x) [% 2d] %a\n",
          Ra,
          ImageBase,
          Ra - ImageBase,
          Idx,
          BaseName (Pdb)
          ));
      }
    } else if (!ListImage) {
      DEBUG ((DEBUG_ERROR, "PC 0x%012lx\n", Ra));
    }

    /*
     * After the prologue, the frame pointer register s0 will point
     * to the Canonical Frame Address or CFA, which is the stack
     * pointer value on entry to the current procedure. The previous
     * frame pointer and return address pair will reside just prior
     * to the current stack address held in s0. This puts the return
     * address at s0 - XLEN/8, and the previous frame pointer at
     * s0 - 2 * XLEN/8.
     */
    Sp  = Fp;
    Fp -= sizeof (UINTN) * 2;
    Ra  = *(UINTN *)(Fp + sizeof (UINTN));
    Fp  = *(UINTN *)(Fp);
    if (IsLeaf && CheckFpValid (Ra, Sp)) {
      /* We hit function where ra is not saved on the stack */
      Fp = Ra;
      Ra = RootRa;
    }

    IsLeaf = FALSE;
  }
}

/**
  Display a backtrace.

  @param SystemContext  Pointer to EFI_SYSTEM_CONTEXT.
**/
VOID
EFIAPI
DumpCpuBacktrace (
  IN EFI_SYSTEM_CONTEXT  SystemContext
  )
{
  SMODE_TRAP_REGISTERS  *Regs;
  CHAR8                 *Pdb;
  UINTN                 ImageBase;
  UINTN                 PeCoffSizeOfHeader;

  Regs = (SMODE_TRAP_REGISTERS *)SystemContext.SystemContextRiscV64;
  Pdb  = GetImageName (Regs->sepc, &ImageBase, &PeCoffSizeOfHeader);
  if (Pdb != NULL) {
    DEBUG ((
      DEBUG_ERROR,
      "PC 0x%012lx (0x%012lx+0x%08x) [ 0] %a\n",
      Regs->sepc,
      ImageBase,
      Regs->sepc - ImageBase,
      BaseName (Pdb)
      ));
  } else {
    DEBUG ((DEBUG_ERROR, "PC 0x%012lx\n", Regs->sepc));
  }

  DumpCpuBacktraceHelper (Regs, Pdb, FALSE);

  if (Pdb != NULL) {
    DEBUG ((DEBUG_ERROR, "\n[ 0] %a\n", Pdb));
  }

  DumpCpuBacktraceHelper (Regs, Pdb, TRUE);
}
