/* Target-dependent code for PowerPC systems running FreeBSD.

   Copyright (C) 2013-2019 Free Software Foundation, Inc.

   This file is part of GDB.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

#include "defs.h"
#include "arch-utils.h"
#include "frame.h"
#include "gdbcore.h"
#include "frame-unwind.h"
#include "gdbtypes.h"
#include "osabi.h"
#include "regcache.h"
#include "regset.h"
#include "symtab.h"
#include "target.h"
#include "trad-frame.h"
#include "rs6000-tdep.h"
#include "ppc-tdep.h"

#include "ppc-tdep.h"
#include "ppc64-tdep.h"
#include "ppc-fbsd-tdep.h"
#include "morphos-tdep.h"


/* Calculate the destination of a branch/jump.  Return -1 if not a branch.  */

static CORE_ADDR
branch_dest (struct regcache *regcache, int opcode, int instr,
	     CORE_ADDR pc, CORE_ADDR safety)
{
  struct gdbarch *gdbarch = regcache->arch ();
  struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch);
  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
  CORE_ADDR dest;
  int immediate;
  int absolute;
  int ext_op;

  absolute = (int) ((instr >> 1) & 1);

  switch (opcode)
    {
    case 18:
      immediate = ((instr & ~3) << 6) >> 6;	/* br unconditional */
      if (absolute)
	dest = immediate;
      else
	dest = pc + immediate;
      break;

    case 16:
      immediate = ((instr & ~3) << 16) >> 16;	/* br conditional */
      if (absolute)
	dest = immediate;
      else
	dest = pc + immediate;
      break;

    case 19:
#warning MorphOS: fix the calculation of destination of a branch/jump
      ext_op = (instr >> 1) & 0x3ff;

      if (ext_op == 16)		/* br conditional register */
	{
          dest = regcache_raw_get_unsigned (regcache, tdep->ppc_lr_regnum) & ~3;

		/*What if dest is out of the text segment? E.g. library call?
	 if (dest < MORPHOS_TEXT_SEGMENT_BASE)
	    {
			dest =
	    }
		*/
	}

      else if (ext_op == 528)	/* br cond to count reg */
	{
          dest = regcache_raw_get_unsigned (regcache,
					    tdep->ppc_ctr_regnum) & ~3;

	/*What if dest is out of the text segment? E.g. library call?
	  if (dest < MORPHOS_TEXT_SEGMENT_BASE)
	  {
            dest = 
	  }*/
	}
      else
	return -1;
      break;

    default:
      return -1;
    }
  return /*(dest < MORPHOS_TEXT_SEGMENT_BASE) ? safety :*/ dest;
}

static std::vector<CORE_ADDR>
rs6000_software_single_step (struct regcache *regcache)
{
  struct gdbarch *gdbarch = regcache->arch ();
  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
  int ii, insn;
  CORE_ADDR loc;
  CORE_ADDR breaks[2];
  int opcode;

  loc = regcache_read_pc (regcache);

  insn = read_memory_integer (loc, 4, byte_order);

  std::vector<CORE_ADDR> next_pcs = ppc_deal_with_atomic_sequence (regcache);
  if (!next_pcs.empty ())
    return next_pcs;
  
  breaks[0] = loc + PPC_INSN_SIZE;
  opcode = insn >> 26;
  breaks[1] = branch_dest (regcache, opcode, insn, loc, breaks[0]);

  /* Don't put two breakpoints on the same address.  */
  if (breaks[1] == breaks[0])
    breaks[1] = -1;

  for (ii = 0; ii < 2; ++ii)
    {
      /* ignore invalid breakpoint.  */
      if (breaks[ii] == -1)
	continue;

      next_pcs.push_back (breaks[ii]);
    }

  errno = 0;			/* FIXME, don't ignore errors!  */
  /* What errors?  {read,write}_memory call error().  */
  return next_pcs;
}

static void
morphos_init_osabi (struct gdbarch_info info, struct gdbarch *gdbarch)
{
  struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch);

  /* MorphOS does not support single stepping.  Has to be simulated.  */
  set_gdbarch_software_single_step (gdbarch, rs6000_software_single_step);

  /* Displaced stepping is currently not supported in combination with
     software single-stepping.  */
  set_gdbarch_displaced_step_copy_insn (gdbarch, NULL);
  set_gdbarch_displaced_step_fixup (gdbarch, NULL);
  set_gdbarch_displaced_step_location (gdbarch, NULL);

/*
  set_gdbarch_push_dummy_call (gdbarch, rs6000_push_dummy_call);
  set_gdbarch_return_value (gdbarch, rs6000_return_value);
  set_gdbarch_long_double_bit (gdbarch, 8 * TARGET_CHAR_BIT);
*/
}

void
_initialize_morphos_tdep (void)
{
  gdbarch_register_osabi (bfd_arch_rs6000, 0, GDB_OSABI_MORPHOS,
                          morphos_init_osabi);
  gdbarch_register_osabi (bfd_arch_powerpc, 0, GDB_OSABI_MORPHOS,
                          morphos_init_osabi);
}
