/*
 * Copyright (C) 2014-2018 MIPS Tech, LLC
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holder nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
*/

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <mips/cpu.h>
#include <mips/fpa.h>
#include <mips/hal.h>
#include <mips/uhi_syscalls.h>

/* Defined in .ld file */
extern char __use_excpt_boot[];
extern char __attribute__((weak)) __flush_to_zero[];

#ifdef VERBOSE_EXCEPTIONS
/*
 * Write a string, a formatted number, then a string.
 */
static void
putsnds (const char *pre, reg_t value, int digits, const char *post)
{
  char buf[digits];
  int shift;
  int idx = 0;

  if (pre != NULL)
    write (1, pre, strlen (pre));

  for (shift = ((digits - 1) * 4) ; shift >= 0 ; shift -= 4)
    buf[idx++] = "0123456789ABCDEF"[(value >> shift) & 0xf];
  write (1, buf, digits);

  if (post != NULL)
    write (1, post, strlen (post));
}

static void
putsns (const char *pre, reg_t value, const char *post)
{
  putsnds (pre, value, sizeof (reg_t) * 2, post);
}

# define WRITE(MSG) write (1, (MSG), strlen (MSG))
# define PUTSNDS(PRE, VALUE, DIGITS, POST) \
    putsnds ((PRE), (VALUE), (DIGITS), (POST))
# define PUTSNS(PRE, VALUE, POST) \
    putsns ((PRE), (VALUE), (POST))

#else

# define WRITE(MSG)
# define PUTSNDS(PRE, VALUE, DIGITS, POST)
# define PUTSNS(PRE, VALUE, POST)

#endif // !VERBOSE_EXCEPTIONS

/* Handle an exception */
#ifdef VERBOSE_EXCEPTIONS
void _MIPS_HAL_NOMIPS16
__exception_handle_verbose (struct gpctx *ctx, int exception)
#else
void _MIPS_HAL_NOMIPS16
__exception_handle_quiet (struct gpctx *ctx, int exception)
#endif
{
  switch (exception)
    {
   case EXC_MOD:
      WRITE ("TLB modification exception\n");
      break;
    case EXC_TLBL:
      PUTSNS ("TLB error on load from 0x", ctx->badvaddr, NULL);
      PUTSNS (" @0x", ctx->epc, "\n");
      break;
    case EXC_TLBS:
      PUTSNS ("TLB error on store to 0x", ctx->badvaddr, NULL);
      PUTSNS (" @0x", ctx->epc, "\n");
      break;
    case EXC_ADEL:
      PUTSNS ("Address error on load from 0x", ctx->badvaddr, NULL);
      PUTSNS (" @0x", ctx->epc, "\n");
      break;
    case EXC_ADES:
      PUTSNS ("Address error on store to 0x", ctx->badvaddr, NULL);
      PUTSNS (" @0x", ctx->epc, "\n");
      break;
    case EXC_IBE:
      WRITE ("Instruction bus error\n");
      break;
    case EXC_DBE:
      WRITE ("Data bus error\n");
      break;
    case EXC_SYS:
      /* Process a UHI SYSCALL, all other SYSCALLs should have been processed
	 by our caller.  __use_excpt_boot has following values:
	 0 = Do not use exception handler present in boot.
	 1 = Use exception handler present in boot if BEV
	     is 0 at startup.
	 2 = Always use exception handler present in boot.   */

      /* Special handling for boot/low level failures.  */
      if (ctx->t2[1] == __MIPS_UHI_BOOTFAIL)
	{
	  switch (ctx->a[0])
	    {
	    case __MIPS_UHI_BF_CACHE:
	      WRITE ("L2 cache configuration error\n");
	      break;
	    default:
	      WRITE ("Unknown boot failure error\n");
	      break;
	    }

	  /* These are unrecoverable.  Abort.  */
	  ctx->epc = (sreg_t)(long)&__exit;
	  /* Exit code of 255 */
	  ctx->a[0] = 0xff;
	  return;
	}

      if (((long) __use_excpt_boot == 2
	   || ((long) __use_excpt_boot == 1
	       && __get_startup_BEV
	       && __get_startup_BEV () == 0))
	  && __chain_uhi_excpt)
	/* This will not return.  */
	__chain_uhi_excpt (ctx);
      else
	__uhi_indirect (ctx);
      return;
    case EXC_BP:
      /* Return from exception handler if breakpoint is handled.  */
      if (__uhi_break && __uhi_break (ctx))
	return;
      PUTSNS ("Breakpoint @0x", ctx->epc, "\n");
      break;
    case EXC_RI:
      PUTSNS ("Illegal instruction @0x", ctx->epc, "\n");
      break;
    case EXC_CPU:
      PUTSNS ("Coprocessor unusable @0x", ctx->epc, "\n");
      break;
    case EXC_OVF:
      WRITE ("Overflow\n");
      break;
    case EXC_TRAP:
      WRITE ("Trap\n");
      break;
    case EXC_MSAFPE:
#if MIPS_MSA_USABLE
      if (__flush_to_zero
	  && (msa_getsr () & FPA_CSR_UNI_X)
	  && (msa_getsr () & FPA_CSR_FS) == 0)
	{
	  unsigned int sr = msa_getsr ();
	  sr &= ~FPA_CSR_UNI_X;
	  sr |= FPA_CSR_FS;
	  msa_setsr (sr);
	  return;
	}
#endif
      WRITE ("MSA Floating point error\n");
      break;
    case EXC_FPE:
      /* Turn on flush to zero the first time we hit an unimplemented
	 operation.  If we hit it again then stop.  */
      if (__flush_to_zero
	  && (fpa_getsr () & FPA_CSR_UNI_X)
	  && (fpa_getsr () & FPA_CSR_FS) == 0)
	{
	  unsigned int sr = fpa_getsr ();
	  sr &= ~FPA_CSR_UNI_X;
	  sr |= FPA_CSR_FS;
	  fpa_setsr (sr);

	  return;
	}
      WRITE ("Floating point error\n");
      break;
    case EXC_IS1:
      WRITE ("Implementation specific exception (16)\n");
      break;
    case EXC_IS2:
      WRITE ("Implementation specific exception (17)\n");
      break;
    case EXC_C2E:
      WRITE ("Precise Coprocessor 2 exception\n");
      break;
    case EXC_TLBRI:
      WRITE ("TLB read inhibit exception\n");
      break;
    case EXC_TLBXI:
      WRITE ("TLB execute inhibit exception\n");
      break;
    case EXC_MSAU:
      PUTSNS ("MSA unusable @0x", ctx->epc, "\n");
      break;
    case EXC_MDMX:
      PUTSNS ("MDMX exception @0x", ctx->epc, "\n");
      break;
    case EXC_WATCH:
      PUTSNS ("Watchpoint @0x", ctx->epc, "\n");
      break;
    case EXC_MCHECK:
      WRITE ("Machine check error\n");
      break;
    case EXC_THREAD:
      WRITE ("Thread exception\n");
      break;
    case EXC_DSPU:
      WRITE ("DSP unusable\n");
      break;
    case EXC_RES30:
      WRITE ("Cache error\n");
      break;
    default:
      PUTSNS ("Unhandled exception ", exception, "\n");
    }

  /* Dump registers */
  PUTSNS (" 0:\t", 0, "\t");
  PUTSNS ("at:\t", ctx->at, "\t");
  PUTSNS ("v0:\t", ctx->v[0], "\t");
  PUTSNS ("v1:\t", ctx->v[1], "\n");

  PUTSNS ("a0:\t", ctx->a[0], "\t");
  PUTSNS ("a1:\t", ctx->a[1], "\t");
  PUTSNS ("a2:\t", ctx->a[2], "\t");
  PUTSNS ("a3:\t", ctx->a[3], "\n");

  PUTSNS ("t0:\t", ctx->t[0], "\t");
  PUTSNS ("t1:\t", ctx->t[1], "\t");
  PUTSNS ("t2:\t", ctx->t[2], "\t");
  PUTSNS ("t3:\t", ctx->t[3], "\n");

  PUTSNS ("t4:\t", ctx->t[4], "\t");
  PUTSNS ("t5:\t", ctx->t[5], "\t");
  PUTSNS ("t6:\t", ctx->t[6], "\t");
  PUTSNS ("t7:\t", ctx->t[7], "\n");

  PUTSNS ("s0:\t", ctx->s[0], "\t");
  PUTSNS ("s1:\t", ctx->s[1], "\t");
  PUTSNS ("s2:\t", ctx->s[2], "\t");
  PUTSNS ("s3:\t", ctx->s[3], "\n");

  PUTSNS ("s4:\t", ctx->s[4], "\t");
  PUTSNS ("s5:\t", ctx->s[5], "\t");
  PUTSNS ("s6:\t", ctx->s[6], "\t");
  PUTSNS ("s7:\t", ctx->s[7], "\n");

  PUTSNS ("t8:\t", ctx->t2[0], "\t");
  PUTSNS ("t9:\t", ctx->t2[1], "\t");
  PUTSNS ("k0:\t", ctx->k[0], "\t");
  PUTSNS ("k1:\t", ctx->k[1], "\n");

  PUTSNS ("gp:\t", ctx->gp, "\t");
  PUTSNS ("sp:\t", ctx->sp, "\t");
  PUTSNS ("fp:\t", ctx->fp, "\t");
  PUTSNS ("ra:\t", ctx->ra, "\n");

#if __mips_isa_rev < 6
  PUTSNS ("hi:\t", ctx->hi, "\t");
  PUTSNS ("lo:\t", ctx->lo, "\n");
#endif

  PUTSNS ("epc:     \t", ctx->epc, "\n");
  PUTSNS ("BadVAddr:\t", ctx->badvaddr, "\n");

  PUTSNDS ("Status:   \t", ctx->status, 8, "\n");
  PUTSNDS ("Cause:    \t", ctx->cause, 8, "\n");
  PUTSNDS ("BadInstr: \t", ctx->badinstr, 8, "\n");
  PUTSNDS ("BadPInstr:\t", ctx->badpinstr, 8, "\n");

  /* Raise UHI exception which may or may not return.  */
  if (__uhi_exception (ctx, UHI_ABI) != 0)
    {
      /* The exception was acknowledged but not handled.  Abort.  */
      ctx->epc = (sreg_t)(long)&__exit;
      /* Exit code of 255 */
      ctx->a[0] = 0xff;
    }
}
