/*
 * Copyright (c) 2007 Juniper Networks, Inc.
 * Copyright (c) 2004 Marcel Moolenaar
 * All rights reserved.
 *
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``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 AUTHORS 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 "defs.h"
#include "frame-unwind.h"
#include "osabi.h"
#include "regcache.h"
#include "solib.h"
#include "trad-frame.h"
#include "mips-tdep.h"

#ifdef __mips__
#include <machine/asm.h>
#include <machine/pcb.h>
#include <machine/frame.h>
#endif

#include "kgdb.h"

/* Size of struct trapframe in registers. */
#define	TRAPFRAME_WORDS	74

/* From sys/mips/include/pcb.h.  Offsets in the pcb_context[] array. */
#define	FBSD_PCB_REG_S0	0
#define	FBSD_PCB_REG_S1	1
#define	FBSD_PCB_REG_S2	2
#define	FBSD_PCB_REG_S3	3
#define	FBSD_PCB_REG_S4	4
#define	FBSD_PCB_REG_S5	5
#define	FBSD_PCB_REG_S6	6
#define	FBSD_PCB_REG_S7	7
#define	FBSD_PCB_REG_SP	8
#define	FBSD_PCB_REG_S8	9
#define	FBSD_PCB_REG_RA	10
#define	FBSD_PCB_REG_SR	11
#define	FBSD_PCB_REG_GP	12
#define	FBSD_PCB_REG_PC	13

#ifdef __mips__
_Static_assert(TRAPFRAME_WORDS * sizeof(register_t) ==
	       sizeof(struct trapframe), "TRAPFRAME_WORDS mismatch");
_Static_assert(FBSD_PCB_REG_S0 == PCB_REG_S0, "PCB_REG_S0 mismatch");
_Static_assert(FBSD_PCB_REG_S1 == PCB_REG_S1, "PCB_REG_S1 mismatch");
_Static_assert(FBSD_PCB_REG_S2 == PCB_REG_S2, "PCB_REG_S2 mismatch");
_Static_assert(FBSD_PCB_REG_S3 == PCB_REG_S3, "PCB_REG_S3 mismatch");
_Static_assert(FBSD_PCB_REG_S4 == PCB_REG_S4, "PCB_REG_S4 mismatch");
_Static_assert(FBSD_PCB_REG_S5 == PCB_REG_S5, "PCB_REG_S5 mismatch");
_Static_assert(FBSD_PCB_REG_S6 == PCB_REG_S6, "PCB_REG_S6 mismatch");
_Static_assert(FBSD_PCB_REG_S7 == PCB_REG_S7, "PCB_REG_S7 mismatch");
_Static_assert(FBSD_PCB_REG_SP == PCB_REG_SP, "PCB_REG_SP mismatch");
_Static_assert(FBSD_PCB_REG_S8 == PCB_REG_S8, "PCB_REG_S8 mismatch");
_Static_assert(FBSD_PCB_REG_RA == PCB_REG_RA, "PCB_REG_RA mismatch");
_Static_assert(FBSD_PCB_REG_SR == PCB_REG_SR, "PCB_REG_SR mismatch");
_Static_assert(FBSD_PCB_REG_GP == PCB_REG_GP, "PCB_REG_GP mismatch");
_Static_assert(FBSD_PCB_REG_PC == PCB_REG_PC, "PCB_REG_PC mismatch");
#endif

static void
mipsfbsd_supply_pcb(struct regcache *regcache, CORE_ADDR pcb_addr)
{
  struct gdbarch *gdbarch = regcache->arch ();
  size_t regsize = mips_isa_regsize (gdbarch);
  gdb_byte buf[regsize * (FBSD_PCB_REG_PC + 1)];

  /* Read the entire pcb_context[] array in one go.  The pcb_context[]
     array is after the pcb_regs member which is a trapframe.  */
  if (target_read_memory (pcb_addr + TRAPFRAME_WORDS * regsize, buf,
			  sizeof(buf)) != 0)
    return;

  regcache->raw_supply_zeroed (MIPS_ZERO_REGNUM);
  regcache->raw_supply (MIPS_S2_REGNUM - 2,
			buf + (regsize * FBSD_PCB_REG_S0));
  regcache->raw_supply (MIPS_S2_REGNUM - 1,
			buf + (regsize * FBSD_PCB_REG_S1));
  regcache->raw_supply (MIPS_S2_REGNUM,
			buf + (regsize * FBSD_PCB_REG_S2));
  regcache->raw_supply (MIPS_S2_REGNUM + 1,
			buf + (regsize * FBSD_PCB_REG_S3));
  regcache->raw_supply (MIPS_S2_REGNUM + 2,
			buf + (regsize * FBSD_PCB_REG_S4));
  regcache->raw_supply (MIPS_S2_REGNUM + 3,
			buf + (regsize * FBSD_PCB_REG_S5));
  regcache->raw_supply (MIPS_S2_REGNUM + 4,
			buf + (regsize * FBSD_PCB_REG_S6));
  regcache->raw_supply (MIPS_S2_REGNUM + 5,
			buf + (regsize * FBSD_PCB_REG_S7));
  regcache->raw_supply (MIPS_SP_REGNUM,
			buf + (regsize * FBSD_PCB_REG_SP));
  regcache->raw_supply (MIPS_S2_REGNUM + 6,
			buf + (regsize * FBSD_PCB_REG_S8));
  regcache->raw_supply (MIPS_RA_REGNUM,
			buf + (regsize * FBSD_PCB_REG_RA));
  regcache->raw_supply (MIPS_PS_REGNUM,
			buf + (regsize * FBSD_PCB_REG_SR));
  regcache->raw_supply (MIPS_GP_REGNUM,
			buf + (regsize * FBSD_PCB_REG_GP));
  regcache->raw_supply (MIPS_EMBED_PC_REGNUM,
			buf + (regsize * FBSD_PCB_REG_PC));
}

static struct trad_frame_cache *
mipsfbsd_trapframe_cache (const frame_info_ptr &this_frame, void **this_cache)
{
  struct gdbarch *gdbarch = get_frame_arch (this_frame);
  size_t regsize = mips_isa_regsize (gdbarch);
  struct trad_frame_cache *cache;
  CORE_ADDR addr, func, sp;
  int regnum;

  if (*this_cache != NULL)
    return ((struct trad_frame_cache *)*this_cache);

  cache = trad_frame_cache_zalloc (this_frame);
  *this_cache = cache;

  func = get_frame_func (this_frame);
  sp = get_frame_register_signed (this_frame,
				  MIPS_SP_REGNUM + gdbarch_num_regs (gdbarch));

  /* Skip over CALLFRAME_SIZ.  */
  addr = sp;
  if (regsize == 8)
    addr += regsize * 4;
  else
    addr += regsize * (4 + 2);

  /* GPRs.  Skip zero.  */
  addr += regsize;
  for (regnum = MIPS_AT_REGNUM; regnum <= MIPS_RA_REGNUM; regnum++)
    {
      trad_frame_set_reg_addr (cache,
			       regnum + gdbarch_num_regs (gdbarch),
			       addr);
      addr += regsize;
    }

  regnum = MIPS_PS_REGNUM;
  trad_frame_set_reg_addr (cache,
			   regnum + gdbarch_num_regs (gdbarch),
			   addr);
  addr += regsize;

  /* HI and LO.  */
  regnum = mips_regnum (gdbarch)->lo;
  trad_frame_set_reg_addr (cache,
			   regnum + gdbarch_num_regs (gdbarch),
			   addr);
  addr += regsize;
  regnum = mips_regnum (gdbarch)->hi;
  trad_frame_set_reg_addr (cache,
			   regnum + gdbarch_num_regs (gdbarch),
			   addr);
  addr += regsize;

  /* BADVADDR.  */
  regnum = mips_regnum (gdbarch)->badvaddr;
  trad_frame_set_reg_addr (cache,
			   regnum + gdbarch_num_regs (gdbarch),
			   addr);
  addr += regsize;

  /* CAUSE.  */
  regnum = mips_regnum (gdbarch)->cause;
  trad_frame_set_reg_addr (cache,
			   regnum + gdbarch_num_regs (gdbarch),
			   addr);
  addr += regsize;

  /* PC.  */
  regnum = mips_regnum (gdbarch)->pc;
  trad_frame_set_reg_addr (cache,
			   regnum + gdbarch_num_regs (gdbarch),
			   addr);
  
  trad_frame_set_id (cache, frame_id_build (sp + TRAPFRAME_WORDS * regsize,
					    func));
  return cache;
}

static void
mipsfbsd_trapframe_this_id (const frame_info_ptr &this_frame,
			    void **this_cache, struct frame_id *this_id)
{
  struct trad_frame_cache *cache =
    mipsfbsd_trapframe_cache (this_frame, this_cache);
  
  trad_frame_get_id (cache, this_id);
}

static struct value *
mipsfbsd_trapframe_prev_register (const frame_info_ptr &this_frame,
				  void **this_cache, int regnum)
{
  struct trad_frame_cache *cache =
    mipsfbsd_trapframe_cache (this_frame, this_cache);

  return trad_frame_get_register (cache, this_frame, regnum);
}

static int
mipsfbsd_trapframe_sniffer (const struct frame_unwind *self,
			    const frame_info_ptr &this_frame,
			    void **this_prologue_cache)
{
  const char *name;

  find_pc_partial_function (get_frame_func (this_frame), &name, NULL, NULL);
  return (name && ((strcmp(name, "MipsKernIntr") == 0) ||
		   (strcmp(name, "MipsKernGenException") == 0) ||
		   (strcmp(name, "MipsTLBInvalidException") == 0)));
}

static const struct frame_unwind mipsfbsd_trapframe_unwind = {
  "mips FreeBSD kernel trap",
  SIGTRAMP_FRAME,
  default_frame_unwind_stop_reason,
  mipsfbsd_trapframe_this_id,
  mipsfbsd_trapframe_prev_register,
  NULL,
  mipsfbsd_trapframe_sniffer
};

static void
mipsfbsd_kernel_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
{
  enum mips_abi abi = mips_abi (gdbarch);

  set_gdbarch_software_single_step (gdbarch, mips_software_single_step);

  switch (abi)
    {
      case MIPS_ABI_O32:
	break;
      case MIPS_ABI_N32:
	set_gdbarch_long_double_bit (gdbarch, 128);
	set_gdbarch_long_double_format (gdbarch, floatformats_ieee_quad);
	break;
      case MIPS_ABI_N64:
	set_gdbarch_long_double_bit (gdbarch, 128);
	set_gdbarch_long_double_format (gdbarch, floatformats_ieee_quad);
	break;
    }

  frame_unwind_prepend_unwinder (gdbarch, &mipsfbsd_trapframe_unwind);

  set_gdbarch_so_ops (gdbarch, &kld_so_ops);

  fbsd_vmcore_set_supply_pcb (gdbarch, mipsfbsd_supply_pcb);
  fbsd_vmcore_set_cpu_pcb_addr (gdbarch, kgdb_trgt_stop_pcb);
}

void _initialize_mips_kgdb_tdep ();
void
_initialize_mips_kgdb_tdep ()
{
  gdbarch_register_osabi_sniffer(bfd_arch_mips,
				 bfd_target_elf_flavour,
				 fbsd_kernel_osabi_sniffer);
  gdbarch_register_osabi (bfd_arch_mips, 0, GDB_OSABI_FREEBSD_KERNEL,
			  mipsfbsd_kernel_init_abi);
}
