/* interp.c -- Simulator for Motorola 68HC11
   Copyright (C) 1999, 2000 Free Software Foundation, Inc.
   Written by Stephane Carrez (stcarrez@worldnet.fr)

This file is part of GDB, the GNU debugger.

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 2, 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, write to the Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */

#include "sim-main.h"
#include "sim-assert.h"
#include "sim-hw.h"
#include "sim-options.h"
#include "hw-tree.h"
#include "hw-device.h"
#include "hw-ports.h"

#ifndef MONITOR_BASE
# define MONITOR_BASE (0x0C000)
# define MONITOR_SIZE (0x04000)
#endif

static void sim_get_info (SIM_DESC sd, char *cmd);


char *interrupt_names[] = {
  "reset",
  "nmi",
  "int",
  NULL
};

#ifndef INLINE
#if defined(__GNUC__) && defined(__OPTIMIZE__)
#define INLINE __inline__
#else
#define INLINE
#endif
#endif

struct sim_info_list
{
  const char *name;
  const char *device;
};

struct sim_info_list dev_list[] = {
  {"cpu", "/m68hc11"},
  {"timer", "/m68hc11/m68hc11tim"},
  {"sio", "/m68hc11/m68hc11sio"},
  {"spi", "/m68hc11/m68hc11spi"},
  {"eeprom", "/m68hc11/m68hc11eepr"},
  {0, 0}
};

/* Give some information about the simulator.  */
static void
sim_get_info (SIM_DESC sd, char *cmd)
{
  sim_cpu *cpu;

  if (cmd != 0 && (cmd[0] == ' ' || cmd[0] == '-'))
    {
      int i;
      struct hw *hw_dev;
      cmd++;

      for (i = 0; dev_list[i].name; i++)
	if (strcmp (cmd, dev_list[i].name) == 0)
	  break;

      if (dev_list[i].name == 0)
	{
	  sim_io_eprintf (sd, "Device '%s' not found.\n", cmd);
	  sim_io_eprintf (sd, "Valid devices: cpu timer sio eeprom\n");
	  return;
	}
      hw_dev = sim_hw_parse (sd, dev_list[i].device);
      if (hw_dev == 0)
	{
	  sim_io_eprintf (sd, "Device '%s' not found\n", dev_list[i].device);
	  return;
	}
      hw_ioctl (hw_dev, 23, 0);
      return;
    }

  cpu = STATE_CPU (sd, 0);
  cpu_info (sd, cpu);
  interrupts_info (sd, &cpu->cpu_interrupts);
}


void
sim_board_reset (SIM_DESC sd)
{
  struct hw *hw_cpu;
  sim_cpu *cpu;

  cpu = STATE_CPU (sd, 0);
  /*  hw_cpu = sim_hw_parse (sd, "/"); */
  hw_cpu = sim_hw_parse (sd, "/m68hc11");
  if (hw_cpu == 0)
    {
      sim_io_eprintf (sd, "m68hc11 cpu not found in device tree.");
      return;
    }

  cpu_reset (cpu);
  hw_port_event (hw_cpu, 3, 0);
  cpu_restart (cpu);
}

SIM_DESC
sim_open (SIM_OPEN_KIND kind, host_callback *callback,
          struct _bfd *abfd, char **argv)
{
  char **p;
  SIM_DESC sd;
  sim_cpu *cpu;
  struct hw *device_tree;

  sd = sim_state_alloc (kind, callback);
  cpu = STATE_CPU (sd, 0);

  SIM_ASSERT (STATE_MAGIC (sd) == SIM_MAGIC_NUMBER);

  /* for compatibility */
  current_alignment = NONSTRICT_ALIGNMENT;
  current_target_byte_order = BIG_ENDIAN;

  cpu_initialize (sd, cpu);

  cpu->cpu_use_elf_start = 1;
  if (sim_pre_argv_init (sd, argv[0]) != SIM_RC_OK)
    return 0;

  /* getopt will print the error message so we just have to exit if this fails.
     FIXME: Hmmm...  in the case of gdb we need getopt to call
     print_filtered.  */
  if (sim_parse_args (sd, argv) != SIM_RC_OK)
    {
      /* Uninstall the modules to avoid memory leaks,
         file descriptor leaks, etc.  */
      sim_module_uninstall (sd);
      return 0;
    }

  device_tree = sim_hw_parse (sd, "/");
  if (hw_tree_find_property (device_tree, "/m68hc11/reg") == 0)
    {
      /* Allocate core managed memory */

      /* the monitor  */
      sim_do_commandf (sd, "memory region 0x%lx@%d,0x%lx",
		       /* MONITOR_BASE, MONITOR_SIZE */
		       0x8000, M6811_RAM_LEVEL, 0x8000);
      sim_do_commandf (sd, "memory region 0x000@%d,0x8000",
                       M6811_RAM_LEVEL);
      sim_hw_parse (sd, "/m68hc11/reg 0x1000 0x03F");
    }

  if (hw_tree_find_property (device_tree, "/m68hc11/m68hc11sio/reg") == 0)
    {
      sim_hw_parse (sd, "/m68hc11/m68hc11sio/reg 0x2b 0x5");
      sim_hw_parse (sd, "/m68hc11/m68hc11sio/backend stdio");
      sim_hw_parse (sd, "/m68hc11 > cpu-reset reset /m68hc11/m68hc11sio");
    }
  if (hw_tree_find_property (device_tree, "/m68hc11/m68hc11tim/reg") == 0)
    {
      /* M68hc11 Timer configuration. */
      sim_hw_parse (sd, "/m68hc11/m68hc11tim/reg 0x1b 0x5");
      sim_hw_parse (sd, "/m68hc11 > cpu-reset reset /m68hc11/m68hc11tim");
    }

  /* Create the SPI device.  */
  if (hw_tree_find_property (device_tree, "/m68hc11/m68hc11spi/reg") == 0)
    {
      sim_hw_parse (sd, "/m68hc11/m68hc11spi/reg 0x28 0x3");
      sim_hw_parse (sd, "/m68hc11 > cpu-reset reset /m68hc11/m68hc11spi");
    }
  if (hw_tree_find_property (device_tree, "/m68hc11/pram/reg") == 0)
    {
      /* M68hc11 persistent ram configuration. */
      sim_hw_parse (sd, "/m68hc11/nvram/reg 0x0 256");
      sim_hw_parse (sd, "/m68hc11/nvram/file m68hc11.ram");
      sim_hw_parse (sd, "/m68hc11/nvram/mode save-modified");
      /*sim_hw_parse (sd, "/m68hc11 > cpu-reset reset /m68hc11/pram"); */
    }
  if (hw_tree_find_property (device_tree, "/m68hc11/m68hc11eepr/reg") == 0)
    {
      sim_hw_parse (sd, "/m68hc11/m68hc11eepr/reg 0xb000 512");
      /* Connect the CPU reset to all devices. */
      sim_hw_parse (sd, "/m68hc11 > cpu-reset reset /m68hc11/m68hc11eepr");
    }

  /* Check for/establish the a reference program image.  */
  if (sim_analyze_program (sd,
			   (STATE_PROG_ARGV (sd) != NULL
			    ? *STATE_PROG_ARGV (sd)
			    : NULL), abfd) != SIM_RC_OK)
    {
      sim_module_uninstall (sd);
      return 0;
    }

  /* Establish any remaining configuration options.  */
  if (sim_config (sd) != SIM_RC_OK)
    {
      sim_module_uninstall (sd);
      return 0;
    }

  if (sim_post_argv_init (sd) != SIM_RC_OK)
    {
      /* Uninstall the modules to avoid memory leaks,
         file descriptor leaks, etc.  */
      sim_module_uninstall (sd);
      return 0;
    }

  if (abfd != NULL)
    {
      cpu->cpu_elf_start = bfd_get_start_address (abfd);
    }

  sim_board_reset (sd);

  /* Fudge our descriptor.  */
  return sd;
}


void
sim_close (SIM_DESC sd, int quitting)
{
  /* shut down modules */
  sim_module_uninstall (sd);

  /* Ensure that any resources allocated through the callback
     mechanism are released: */
  sim_io_shutdown (sd);

  /* FIXME - free SD */

  return;
}

void
sim_set_profile (int n)
{
}

void
sim_set_profile_size (int n)
{
}

/* Generic implementation of sim_engine_run that works within the
   sim_engine setjmp/longjmp framework. */

void
sim_engine_run (SIM_DESC sd,
                int next_cpu_nr,	/* ignore */
		int nr_cpus,	/* ignore */
		int siggnal)	/* ignore */
{
  sim_cpu *cpu;

  SIM_ASSERT (STATE_MAGIC (sd) == SIM_MAGIC_NUMBER);
  cpu = STATE_CPU (sd, 0);
  while (1)
    {
      cpu_single_step (cpu);

      /* process any events */
      if (sim_events_tickn (sd, cpu->cpu_current_cycle))
	{
	  sim_events_process (sd);
	}
    }
}

int
sim_trace (SIM_DESC sd)
{
  sim_resume (sd, 0, 0);
  return 1;
}

void
sim_info (SIM_DESC sd, int verbose)
{
  sim_io_eprintf (sd, "Simulator info:\n");
  sim_io_eprintf (sd, "  CPU Motorola 68HC11\n");
  sim_get_info (sd, 0);
  sim_module_info (sd, verbose || STATE_VERBOSE_P (sd));
}

SIM_RC
sim_create_inferior (SIM_DESC sd, struct _bfd *abfd,
                     char **argv, char **env)
{
  sim_cpu *cpu;
  int i;

  cpu = STATE_CPU (sd, 0);

  if (abfd != NULL)
    {
      cpu->cpu_elf_start = bfd_get_start_address (abfd);
    }

  /* reset all state information */
  sim_board_reset (sd);

  return SIM_RC_OK;
}


void
sim_set_callbacks (host_callback *p)
{
  /*  m6811_callback = p; */
}


int
sim_fetch_register (SIM_DESC sd, int rn, unsigned char *memory, int length)
{
  sim_cpu *cpu;
  uint16 val;

  cpu = STATE_CPU (sd, 0);
  switch (rn)
    {
    case A_REGNUM:
      val = cpu_get_a (cpu);
      break;

    case B_REGNUM:
      val = cpu_get_b (cpu);
      break;

    case D_REGNUM:
      val = cpu_get_d (cpu);
      break;

    case X_REGNUM:
      val = cpu_get_x (cpu);
      break;

    case Y_REGNUM:
      val = cpu_get_y (cpu);
      break;

    case SP_REGNUM:
      val = cpu_get_sp (cpu);
      break;

    case PC_REGNUM:
      val = cpu_get_pc (cpu);
      break;

    case PSW_REGNUM:
      val = cpu_get_ccr (cpu);
      break;

    default:
      val = 0;
      break;
    }
  memory[0] = val >> 8;
  memory[1] = val & 0x0FF;
  return 2;
}

int
sim_store_register (SIM_DESC sd, int rn, unsigned char *memory, int length)
{
  uint16 val;
  sim_cpu *cpu;

  cpu = STATE_CPU (sd, 0);

  val = *memory++;
  if (length == 2)
    val = (val << 8) | *memory;

  switch (rn)
    {
    case D_REGNUM:
      cpu_set_d (cpu, val);
      break;

    case A_REGNUM:
      cpu_set_a (cpu, val);
      break;

    case B_REGNUM:
      cpu_set_b (cpu, val);
      break;

    case X_REGNUM:
      cpu_set_x (cpu, val);
      break;

    case Y_REGNUM:
      cpu_set_y (cpu, val);
      break;

    case SP_REGNUM:
      cpu_set_sp (cpu, val);
      break;

    case PC_REGNUM:
      cpu_set_pc (cpu, val);
      break;

    case PSW_REGNUM:
      cpu_set_ccr (cpu, val);
      break;

    default:
      break;
    }

  return 2;
}

void
sim_size (int s)
{
  ;
}

void
sim_do_command (SIM_DESC sd, char *cmd)
{
  char *mm_cmd = "memory-map";
  char *int_cmd = "interrupt";

  /* Commands available from GDB:   */
  if (sim_args_command (sd, cmd) != SIM_RC_OK)
    {
      if (strncmp (cmd, "info", sizeof ("info") - 1) == 0)
	sim_get_info (sd, &cmd[4]);
      else if (strncmp (cmd, "frame", sizeof ("frame") - 1) == 0)
	cpu_print_frame (sd, STATE_CPU (sd, 0));
      else if (strncmp (cmd, mm_cmd, strlen (mm_cmd) == 0))
	sim_io_eprintf (sd,
			"`memory-map' command replaced by `sim memory'\n");
      else if (strncmp (cmd, int_cmd, strlen (int_cmd)) == 0)
	sim_io_eprintf (sd, "`interrupt' command replaced by `sim watch'\n");
      else
	sim_io_eprintf (sd, "Unknown command `%s'\n", cmd);
    }
}
