/*
 *  This file is part of x48, an emulator of the HP-48sx Calculator.
 *  Copyright (C) 1994  Eddie C. Dost  (ecd@dressler.de)
 *
 *  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 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, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* $Log: debugger.c,v $
 * Revision 1.8  1995/01/11  18:20:01  ecd
 * major update to support HP48 G/GX
 *
 * Revision 1.7  1994/12/07  20:20:50  ecd
 * more functions
 *
 * Revision 1.7  1994/12/07  20:20:50  ecd
 * more functions
 *
 * Revision 1.6  1994/11/28  02:00:51  ecd
 * new functions: do_ram, do_stack
 *
 * Revision 1.5  1994/11/02  14:40:38  ecd
 * more functions
 *
 * Revision 1.4  1994/10/09  20:29:47  ecd
 * start of disassembler implementation.
 *
 * Revision 1.3  1994/10/06  16:30:05  ecd
 * added refresh_display()
 *
 * Revision 1.2  1994/10/05  08:36:44  ecd
 * more functions
 *
 * Revision 1.1  1994/10/04  15:12:21  ecd
 * Initial revision
 *
 *
 * $Id: debugger.c,v 1.8 1995/01/11 18:20:01 ecd Exp ecd $
 */

#include "global.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#ifdef SUNOS
#include <memory.h>
#endif
#include <X11/Xlib.h>

#include "hp48.h"
#include "device.h"
#include "timer.h"
#include "x48_x11.h"
#include "debugger.h"
#include "disasm.h"
#include "rpl.h"
#include "romio.h"
#include "resources.h"

extern char *readline __ProtoType__ ((const char *));
extern void add_history __ProtoType__ ((char *));

#define MAX_ARGS 16

int enter_debugger = 0;
int in_debugger = 0;
int exec_flags = 0;

static int continue_flag;
static char instr[100];

/*
 * Pointers in the HP48sx ROM
 */
#define DSKTOP_SX	0x70579
#define DSKBOT_SX	0x7057e
#define DSKTOP_GX	0x806f8
#define DSKBOT_GX	0x806fd

/*
 * Breakpoint related stuff
 */
#define BP_EXEC		1
#define BP_READ		2
#define BP_WRITE	4
#define BP_RANGE	8

#define MAX_BREAKPOINTS	32
int num_bkpts;

struct breakpoint
  {
    word_20 addr;
    word_20 end_addr;
    int flags;
  }

bkpt_tbl[MAX_BREAKPOINTS + 1];

/*
 * command functions
 */
static void do_break __ProtoType__ ((int, char **));
static void do_continue __ProtoType__ ((int, char **));
static void do_delete __ProtoType__ ((int, char **));
static void do_exit __ProtoType__ ((int, char **));
static void do_go __ProtoType__ ((int, char **));
static void do_help __ProtoType__ ((int, char **));
static void do_load __ProtoType__ ((int, char **));
static void do_mode __ProtoType__ ((int, char **));
static void do_quit __ProtoType__ ((int, char **));
static void do_regs __ProtoType__ ((int, char **));
static void do_save __ProtoType__ ((int, char **));
static void do_stack __ProtoType__ ((int, char **));
static void do_stat __ProtoType__ ((int, char **));
static void do_step __ProtoType__ ((int, char **));
static void do_ram __ProtoType__ ((int, char **));
static void do_reset __ProtoType__ ((int, char **));
static void do_rstk __ProtoType__ ((int, char **));

struct cmd
  {
    char *name;
    void (*func) __ProtoType__ ((int, char **));
    char *help;
  }

cmd_tbl[] =
{
  {
    "break", do_break,
    "break [address]            Set breakpoint at `address\' or show breakpoints"
  }
  ,
  {
    "b", do_break, 0
  }
  ,

  {
    "cont", do_continue,
    "cont                       Continue execution"
  }
  ,
  {
    "c", do_continue, 0
  }
  ,

  {
    "delete", do_delete,
    "delete [all | n]           Delete breakpoint or watchpoint number `n\',\n                           all breakpoints, or current breakpoint"
  }
  ,
  {
    "d", do_delete, 0
  }
  ,

  {
    "exit", do_exit,
    "exit                       Exit the emulator without saving"
  }
  ,

  {
    "go", do_go,
    "go address                 Set PC to `address\'"
  }
  ,

  {
    "help", do_help,
    "help                       Display this information"
  }
  ,
  {
    "h", do_help, 0
  }
  ,
  {
    "?", do_help, 0
  }
  ,

  {
    "load", do_load,
    "load                       Load emulator-state from files"
  }
  ,

  {
    "mode", do_mode,
    "mode [hp | class]          Show or set disassembler mode"
  }
  ,

  {
    "quit", do_quit,
    "quit                       Exit the emulator after saving its state"
  }
  ,
  {
    "q", do_quit, 0
  }
  ,

  {
    "ram", do_ram,
    "ram                        Show RAM layout"
  }
  ,

  {
    "reg", do_regs,
    "reg [register [hexvalue]]  Display or set register value"
  }
  ,
  {
    "r", do_regs, 0
  }
  ,

  {
    "reset", do_reset,
    "reset                      Set the HP48\'s PC to ZERO"
  }
  ,

  {
    "save", do_save,
    "save                       Save emulator-state to files"
  }
  ,

  {
    "stack", do_stack,
    "stack                      Display RPL stack"
  }
  ,

  {
    "stat", do_stat,
    "stat                       Display statistics for the emulator"
  }
  ,

  {
    "step", do_step,
    "step [n]                   Step one or n Instruction(s)"
  }
  ,
  {
    "s", do_step, 0
  }
  ,

  {
    "where", do_rstk,
    "where                      Show ML return stack"
  }
  ,

  {
    0, 0, 0
  }
};

void
#ifdef __FunctionProto__
init_debugger (void)
#else
init_debugger ()
#endif
{
  int i;

  num_bkpts = 0;
  for (i = 0; i < MAX_BREAKPOINTS; i++)
    bkpt_tbl[i].flags = 0;
  exec_flags = 0;
}

int
#ifdef __FunctionProto__
check_breakpoint (int type, word_20 addr)
#else
check_breakpoint (type, addr)
     int type;
     word_20 addr;
#endif
{
  struct breakpoint *bp;
  int i, n;

  bp = bkpt_tbl;
  n = num_bkpts;
  i = 0;
  for (; n > 0; bp++)
    {
      i++;
      if (bp->flags == 0)
	continue;
      n--;
      if (bp->flags & BP_RANGE && addr >= bp->addr && addr <= bp->end_addr)
	{
	  goto hit_it;
	}
      if (bp->flags & type && addr == bp->addr)
	{
	hit_it:
	  if (type == BP_READ)
	    {
	      printf ("%.5lX: Read watchpoint %d hit at %.5lX\n", saturn.PC,
		      i, addr);
	    }
	  else if (type == BP_WRITE)
	    {
	      printf ("%.5lX: Write watchpoint %d hit at %.5lX\n", saturn.PC,
		      i, addr);
	    }
	  else
	    {
	      printf ("Breakpoint %d hit at %.5lX\n", i, addr);
	    }
	  return 1;
	}
    }
  return 0;
}


char *
#ifdef __FunctionProto__
read_str(char *str, int n, int fp)
#else
read_str(str, n, fp)
	char *str;
	int n;
	int fp;
#endif
{
  int cc;
  int flags;

  while (1)
    {
      cc = read(fp, str, n);
      if (cc > 0)
        return str;
      if (cc == 0)
        return NULL;

      if (errno == EINTR)
        continue;

      if (errno == EAGAIN)
        {
          flags = fcntl(fp, F_GETFL, 0);
          flags &= ~ O_NONBLOCK;
          fcntl(fp, F_SETFL, flags);
          continue;
        }
 
      return NULL;
    }
}


static inline void
#ifdef __FunctionProto__
str_to_upper (char *arg)
#else
str_to_upper (arg)
     char *arg;
#endif
{
  int i;

  for (i = 0; i < strlen (arg); i++)
    {
      if ('a' <= arg[i] && arg[i] <= 'z')
	{
	  arg[i] = (char) ((int) arg[i] - (int) 'a' + (int) 'A');
	}
    }
}

static int
#ifdef __FunctionProto__
decode_dec (int *num, char *arg)
#else
decode_dec (num, arg)
     int *num;
     char *arg;
#endif
{
  int i;

  if (arg == (char *) 0)
    {
      printf ("Command requires an argument.\n");
      return 0;
    }

  *num = 0;
  for (i = 0; i < strlen (arg); i++)
    {
      *num *= 10;
      if ('0' <= arg[i] && arg[i] <= '9')
	{
	  *num += ((int) arg[i] - (int) '0');
	}
      else
	{
	  *num = 0;
	  printf ("Not a number: %s.\n", arg);
	  return 0;
	}
    }
  return 1;
}

static int
#ifdef __FunctionProto__
decode_20 (word_20 * addr, char *arg)
#else
decode_20 (addr, arg)
     word_20 *addr;
     char *arg;
#endif
{
  int i;

  if (arg == (char *) 0)
    {
      printf ("Command requires an argument.\n");
      return 0;
    }

  *addr = 0;
  for (i = 0; i < strlen (arg); i++)
    {
      *addr <<= 4;
      if ('0' <= arg[i] && arg[i] <= '9')
	{
	  *addr |= ((int) arg[i] - (int) '0');
	}
      else if ('A' <= arg[i] && arg[i] <= 'F')
	{
	  *addr |= ((int) arg[i] - (int) 'A' + 10);
	}
      else
	{
	  *addr = 0;
	  printf ("Not a number: %s.\n", arg);
	  return 0;
	}
      *addr &= 0xfffff;
    }
  return 1;
}

static int
#ifdef __FunctionProto__
decode_32 (word_32 * addr, char *arg)
#else
decode_32 (addr, arg)
     word_32 *addr;
     char *arg;
#endif
{
  int i;

  if (arg == (char *) 0)
    {
      printf ("Command requires an argument.\n");
      return 0;
    }

  *addr = 0;
  for (i = 0; i < strlen (arg); i++)
    {
      *addr <<= 4;
      if ('0' <= arg[i] && arg[i] <= '9')
	{
	  *addr |= ((int) arg[i] - (int) '0');
	}
      else if ('A' <= arg[i] && arg[i] <= 'F')
	{
	  *addr |= ((int) arg[i] - (int) 'A' + 10);
	}
      else
	{
	  *addr = 0;
	  printf ("Not a number: %s.\n", arg);
	  return 0;
	}
    }
  return 1;
}

static int
#ifdef __FunctionProto__
decode_64 (word_64 * addr, char *arg)
#else
decode_64 (addr, arg)
     word_64 *addr;
     char *arg;
#endif
{
  int i;

  if (arg == (char *) 0)
    {
      printf ("Command requires an argument.\n");
      return 0;
    }

  addr->lo = addr->hi = 0;
  for (i = 0; i < strlen (arg); i++)
    {
      addr->hi <<= 4;
      addr->hi |= ((addr->lo >> 28) & 0x0f);
      addr->lo <<= 4;
      if ('0' <= arg[i] && arg[i] <= '9')
	{
	  addr->lo |= ((int) arg[i] - (int) '0');
	}
      else if ('A' <= arg[i] && arg[i] <= 'F')
	{
	  addr->lo |= ((int) arg[i] - (int) 'A' + 10);
	}
      else
	{
	  addr->hi = addr->lo = 0;
	  printf ("Not a number: %s.\n", arg);
	  return 0;
	}
    }
  return 1;
}

char *
#ifdef __FunctionProto__
str_nibbles (word_20 addr, int n)
#else
str_nibbles (addr, n)
     word_20 addr;
     int n;
#endif
{
  static char str[1025];
  char *cp;
  int i;

  if (n > 1024)
    {
      str[0] = '\0';
      return str;
    }

  for (cp = str, i = 0; i < n; i++)
    {
      sprintf (cp, "%.1X", read_nibble (addr + i));
      cp++;
    }
  *cp = '\0';

  return str;
}

static int
#ifdef __FunctionProto__
confirm (const char *prompt)
#else
confirm (prompt)
     const char *prompt;
#endif
{
  char ans[80];

  printf ("%s (y or n) ", prompt);
  fflush (stdout);
  read_str (ans, sizeof (ans), 0);
  while (ans[0] != 'y' && ans[0] != 'Y' && ans[0] != 'n' && ans[0] != 'N')
    {
      printf ("Please answer y or n.\n");
      printf ("%s (y or n) ", prompt);
      fflush (stdout);
      read_str (ans, sizeof (ans), 0);
    }
  if (ans[0] == 'y' || ans[0] == 'Y')
    {
      return 1;
    }
  else
    {
      printf ("Not confirmed.\n");
      return 0;
    }
}

static void
#ifdef __FunctionProto__
do_break (int argc, char **argv)
#else
do_break (argc, argv)
     int argc;
     char *argv;
#endif
{
  int i;
  word_20 addr;

  if (argc == 1)
    {
      for (i = 0; i < MAX_BREAKPOINTS; i++)
	{
	  if (bkpt_tbl[i].flags == 0)
	    continue;
	  if (bkpt_tbl[i].flags == BP_EXEC)
	    {
	      printf ("Breakpoint %d at 0x%.5lX\n", i + 1, bkpt_tbl[i].addr);
	    }
	  else if (bkpt_tbl[i].flags == BP_RANGE)
	    {
	      printf ("Range watchpoint %d at 0x%.5lX - 0x%.5lX\n", i + 1,
		      bkpt_tbl[i].addr, bkpt_tbl[i].end_addr);
	    }
	  else
	    {
	      printf ("Watchpoint %d at 0x%.5lX\n", i + 1, bkpt_tbl[i].addr);
	    }
	}
    }
  else
    {
      str_to_upper (argv[1]);
      if (!decode_20 (&addr, argv[1]))
	{
	  return;
	}
      for (i = 0; i < MAX_BREAKPOINTS; i++)
	{
	  if (bkpt_tbl[i].flags == 0)
	    {
	      bkpt_tbl[i].flags = BP_EXEC;
	      bkpt_tbl[i].addr = addr;
	      printf ("Breakpoint %d at 0x%.5lX\n", i + 1, bkpt_tbl[i].addr);
	      num_bkpts++;
	      return;
	    }
	}
      printf ("Breakpoint table full\n");
    }
}

static void
#ifdef __FunctionProto__
do_continue (int argc, char **argv)
#else
do_continue (argc, argv)
     int argc;
     char *argv;
#endif
{
  continue_flag = 1;
}

static void
#ifdef __FunctionProto__
do_delete (int argc, char **argv)
#else
do_delete (argc, argv)
     int argc;
     char *argv;
#endif
{
  int num;

  if (argc == 1)
    {
      for (num = 0; num < MAX_BREAKPOINTS; num++)
	{
	  if (bkpt_tbl[num].addr == saturn.PC)
            {
	      if (bkpt_tbl[num].flags == BP_EXEC)
		{
		  printf ("Breakpoint %d at 0x%.5lX deleted.\n",
			  num + 1, bkpt_tbl[num].addr);
		}
	      else if (bkpt_tbl[num].flags == BP_RANGE)
		{
		  printf ("Range watchpoint %d at 0x%.5lX - 0x%.5lX deleted.\n",
		       num + 1, bkpt_tbl[num].addr, bkpt_tbl[num].end_addr);
		}
	      else if (bkpt_tbl[num].flags)
		{
		  printf ("Watchpoint %d at 0x%.5lX deleted.\n",
			  num + 1, bkpt_tbl[num].addr);
		}
	      num_bkpts--;
	      bkpt_tbl[num].addr = 0;
	      bkpt_tbl[num].flags = 0;
            }
	}
    }
  else
    {
      str_to_upper (argv[1]);
      if (!strcmp ("ALL", argv[1]))
	{
	  for (num = 0; num < MAX_BREAKPOINTS; num++)
	    {
	      bkpt_tbl[num].addr = 0;
	      bkpt_tbl[num].flags = 0;
	    }
	  num_bkpts = 0;
	  printf ("All breakpoints deleted.\n");
	}
      else
	{
	  if (decode_dec (&num, argv[1]))
	    {
	      if (num < 1 || num > MAX_BREAKPOINTS)
		{
		  printf ("Breakpoint %d out of range.\n", num);
		  return;
		}
	      num -= 1;
	      if (bkpt_tbl[num].flags == BP_EXEC)
		{
		  printf ("Breakpoint %d at 0x%.5lX deleted.\n",
			  num + 1, bkpt_tbl[num].addr);
		}
	      else if (bkpt_tbl[num].flags == BP_RANGE)
		{
		  printf ("Range watchpoint %d at 0x%.5lX - 0x%.5lX deleted.\n",
		       num + 1, bkpt_tbl[num].addr, bkpt_tbl[num].end_addr);
		}
	      else if (bkpt_tbl[num].flags)
		{
		  printf ("Watchpoint %d at 0x%.5lX deleted.\n",
			  num + 1, bkpt_tbl[num].addr);
		}
	      num_bkpts--;
	      bkpt_tbl[num].addr = 0;
	      bkpt_tbl[num].flags = 0;
	    }
	}
    }
}

static void
#ifdef __FunctionProto__
do_exit (int argc, char **argv)
#else
do_exit (argc, argv)
     int argc;
     char *argv;
#endif
{
  if (confirm ("Exit the emulator WITHOUT saving its state?"))
    {
      printf ("Exit.\n");
      XCloseDisplay(dpy);
      exit (0);
    }
}

static void
#ifdef __FunctionProto__
do_go (int argc, char **argv)
#else
do_go (argc, argv)
     int argc;
     char *argv;
#endif
{
  word_20 addr;

  str_to_upper (argv[1]);
  if (decode_20 (&addr, argv[1]))
    {
      saturn.PC = addr;
      enter_debugger &= ~ILLEGAL_INSTRUCTION;
    }
}

static void
#ifdef __FunctionProto__
do_help (int argc, char **argv)
#else
do_help (argc, argv)
     int argc;
     char *argv;
#endif
{
  int i;

  for (i = 0; cmd_tbl[i].name; i++)
    {
      if (cmd_tbl[i].help)
	{
	  printf ("%s.\n", cmd_tbl[i].help);
	}
    }
}

static void
#ifdef __FunctionProto__
do_load (int argc, char **argv)
#else
do_load (argc, argv)
     int argc;
     char *argv;
#endif
{
  saturn_t tmp_saturn;
  device_t tmp_device;

  if (confirm ("Load emulator-state from files?"))
    {
      memcpy (&tmp_saturn, &saturn, sizeof (saturn));
      memcpy (&tmp_device, &device, sizeof (device));
      memset (&saturn, 0, sizeof (saturn));
      if (read_files ())
	{
	  printf ("Loading done.\n");
	  enter_debugger &= ~ILLEGAL_INSTRUCTION;
	  if (tmp_saturn.rom)
	    {
	      free (tmp_saturn.rom);
	    }
	  if (tmp_saturn.ram)
	    {
	      free (tmp_saturn.ram);
	    }
	  if (tmp_saturn.port1)
	    {
	      free (tmp_saturn.port1);
	    }
	  if (tmp_saturn.port2)
	    {
	      free (tmp_saturn.port2);
	    }
	  init_display ();
	  update_display ();
#ifdef HAVE_XSHM
	  if (disp.display_update)
	    refresh_display ();
#endif
	}
      else
	{
	  printf ("Loading emulator-state from files failed.\n");
	  if (saturn.rom)
	    {
	      free (saturn.rom);
	    }
	  if (saturn.ram)
	    {
	      free (saturn.ram);
	    }
	  if (saturn.port1)
	    {
	      free (saturn.port1);
	    }
	  if (saturn.port2)
	    {
	      free (saturn.port2);
	    }
	  memcpy (&saturn, &tmp_saturn, sizeof (saturn));
	  memcpy (&device, &tmp_device, sizeof (device));
	}
    }
}

static void
#ifdef __FunctionProto__
do_mode (int argc, char **argv)
#else
do_mode (argc, argv)
     int argc;
     char *argv;
#endif
{
  if (argc < 2)
    {
      printf ("Disassembler uses %s mnemonics.\n", mode_name[disassembler_mode]);
    }
  else
    {
      str_to_upper (argv[1]);
      if (!strcmp ("HP", argv[1]))
	{
	  disassembler_mode = HP_MNEMONICS;
	}
      else if (!strcmp ("CLASS", argv[1]))
	{
	  disassembler_mode = CLASS_MNEMONICS;
	}
      else
	{
	  printf ("Unknown disassembler mode %s. Try \"help\".\n", argv[1]);
	}
    }
}

static void
#ifdef __FunctionProto__
do_quit (int argc, char **argv)
#else
do_quit (argc, argv)
     int argc;
     char *argv;
#endif
{
  if (confirm ("Quit the emulator and save its state?"))
    {
      printf ("Exit.\n");
      exit_emulator ();
      XCloseDisplay(dpy);
      exit (0);
    }
}

static void
#ifdef __FunctionProto__
set_reg (word_64 val, int n, unsigned char *r)
#else
set_reg (val, n, r)
     word_64 val;
     int n;
     unsigned char *r;
#endif
{
  int i;

  for (i = 0; i < n; i++)
    {
      if (i < 8)
	r[i] = (unsigned char) ((val.lo & (0xf << (4 * i))) >> (4 * i));
      else
	r[i] = (unsigned char) ((val.hi & (0xf << (4 * (i - 8)))) >> (4 * (i - 8)));
    }
}

static void
#ifdef __FunctionProto__
dump_reg (const char *reg, int n, unsigned char *r)
#else
dump_reg (reg, n, r)
     const char *reg;
     int n;
     unsigned char *r;
#endif
{
  int i;

  printf ("%s:\t", reg);
  for (i = n - 1; i >= 0; i--)
    {
      printf ("%.1X", r[i] & 0xf);
    }
  printf ("\n");
}


static void
#ifdef __FunctionProto__
set_st (word_64 val)
#else
set_st (val)
     word_64 val;
#endif
{
  int i;

  for (i = 0; i < 16; i++)
    saturn.PSTAT[i] = (val.lo & (1 << i)) ? 1 : 0;
}

static void
#ifdef __FunctionProto__
dump_st (void)
#else
dump_st ()
#endif
{
  int i;
  int val;

  val = 0;
  for (i = NR_PSTAT - 1; i >= 0; i--)
    {
      val <<= 1;
      val |= saturn.PSTAT[i] ? 1 : 0;
    }
  printf ("    ST:\t%.4X (", val);
  for (i = NR_PSTAT - 1; i > 0; i--)
    {
      if (saturn.PSTAT[i])
	{
	  printf ("%.1X ", i);
	}
      else
	{
	  printf ("- ");
	}
    }
  if (saturn.PSTAT[0])
    {
      printf ("%.1X)\n", 0);
    }
  else
    {
      printf ("-)\n");
    }
}

static void
#ifdef __FunctionProto__
set_hst (word_64 val)
#else
set_hst (val)
     word_64 val;
#endif
{
  saturn.XM = 0;
  saturn.SB = 0;
  saturn.SR = 0;
  saturn.MP = 0;
  if (val.lo & 1)
    saturn.XM = 1;
  if (val.lo & 2)
    saturn.SB = 1;
  if (val.lo & 4)
    saturn.SR = 1;
  if (val.lo & 8)
    saturn.MP = 1;
}

static void
#ifdef __FunctionProto__
dump_hst (void)
#else
dump_hst ()
#endif
{
  short hst = 0;
  if (saturn.XM != 0)
    hst |= 1;
  if (saturn.SB != 0)
    hst |= 2;
  if (saturn.SR != 0)
    hst |= 3;
  if (saturn.MP != 0)
    hst |= 4;
  printf ("   HST:\t%.1X    (%s%s%s%s)\n", hst,
          saturn.MP ? "MP " : "-- ", saturn.SR ? "SR " : "-- ",
          saturn.SB ? "SB " : "-- ", saturn.XM ? "XM" : "--");
}

static char *mctl_str_gx[] = {
  "MMIO       ", 
  "SysRAM     ",
  "Bank Switch",
  "Port 1     ",
  "Port 2     ",
  "SysROM     "
};

static char *mctl_str_sx[] = {
  "MMIO  ", 
  "SysRAM",
  "Port 1",
  "Port 2",
  "Extra ",
  "SysROM"
};

static void
#ifdef __FunctionProto__
do_ram (int argc, char **argv)
#else
do_ram (argc, argv)
     int argc;
     char *argv;
#endif
{
  int i;

  for (i = 0; i < 5; i++)
    {
      printf("%s ", opt_gx ? mctl_str_gx[i] : mctl_str_sx[i]);
      if (saturn.mem_cntl[i].unconfigured)
        printf("unconfigured\n");
      else
        if (i == 0)
          printf("configured to 0x%.5lx\n", saturn.mem_cntl[i].config[0]);
        else
          printf("configured to 0x%.5lX - 0x%.5lX\n",
                 saturn.mem_cntl[i].config[0],
                 (saturn.mem_cntl[i].config[0] | ~saturn.mem_cntl[i].config[1])
                 & 0xfffff);
    }
  if (opt_gx)
    printf("Port 2      switched to bank %d\n", saturn.bank_switch);
}

static void
#ifdef __FunctionProto__
do_regs (int argc, char **argv)
#else
do_regs (argc, argv)
     int argc;
     char *argv;
#endif
{
  int i;
  word_64 val;

  if (argc < 2)
    {
      /*
     * dump all registers
     */
      printf ("CPU is in %s mode. Registers:\n",
	      saturn.hexmode == HEX ? "HEX" : "DEC");
      dump_reg ("     A", 16, saturn.A);
      dump_reg ("     B", 16, saturn.B);
      dump_reg ("     C", 16, saturn.C);
      dump_reg ("     D", 16, saturn.D);
      printf ("    D0:\t%.5lX ->", saturn.D0);
      for (i = 0; i < 20; i += 5)
	{
	  printf (" %s", str_nibbles (saturn.D0 + i, 5));
	}
      printf ("\n");
      printf ("    D1:\t%.5lX ->", saturn.D1);
      for (i = 0; i < 20; i += 5)
	{
	  printf (" %s", str_nibbles (saturn.D1 + i, 5));
	}
      printf ("\n");
      printf ("     P:\t%.1X\n", saturn.P);
      disassemble (saturn.PC, instr);
      printf ("    PC:\t%.5lX -> %s\n", saturn.PC, instr);
      dump_reg ("    R0", 16, saturn.R0);
      dump_reg ("    R1", 16, saturn.R1);
      dump_reg ("    R2", 16, saturn.R2);
      dump_reg ("    R3", 16, saturn.R3);
      dump_reg ("    R4", 16, saturn.R4);
      dump_reg ("    IN", 4, saturn.IN);
      dump_reg ("   OUT", 3, saturn.OUT);
      printf (" CARRY:\t%.1d\n", saturn.CARRY);
      dump_st ();
      dump_hst ();
    }
  else if (argc == 2)
    {
      /*
     * dump specified register
     */
      str_to_upper (argv[1]);
      if (!strcmp ("A", argv[1]))
	{
          dump_reg ("     A", 16, saturn.A);
        }
      else if (!strcmp ("B", argv[1]))
        {
          dump_reg ("     B", 16, saturn.B);
        }
      else if (!strcmp ("C", argv[1]))
        {
          dump_reg ("     C", 16, saturn.C);
        }
      else if (!strcmp ("D", argv[1]))
        {
          dump_reg ("     D", 16, saturn.D);
	}
      else if (!strcmp ("D0", argv[1]))
	{
	  printf ("    D0:\t%.5lX ->", saturn.D0);
	  for (i = 0; i < 20; i += 5)
	    {
	      printf (" %s", str_nibbles (saturn.D0 + i, 5));
	    }
	  printf ("\n");
	}
      else if (!strcmp ("D1", argv[1]))
	{
	  printf ("    D1:\t%.5lX ->", saturn.D1);
	  for (i = 0; i < 20; i += 5)
	    {
	      printf (" %s", str_nibbles (saturn.D1 + i, 5));
	    }
	  printf ("\n");
	}
      else if (!strcmp ("P", argv[1]))
	{
	  printf ("     P:\t%.1X\n", saturn.P);
	}
      else if (!strcmp ("PC", argv[1]))
	{
	  disassemble (saturn.PC, instr);
	  printf ("    PC:\t%.5lX -> %s\n", saturn.PC, instr);
	}
      else if (!strcmp ("R0", argv[1]))
	{
          dump_reg ("    R0", 16, saturn.R0);
        }
      else if (!strcmp ("R1", argv[1]))
        {
          dump_reg ("    R1", 16, saturn.R1);
        }
      else if (!strcmp ("R2", argv[1]))
        {
          dump_reg ("    R2", 16, saturn.R2);
        }
      else if (!strcmp ("R3", argv[1]))
        {
          dump_reg ("    R3", 16, saturn.R3);
        }
      else if (!strcmp ("R4", argv[1]))
        {
          dump_reg ("    R4", 16, saturn.R4);
        }
      else if (!strcmp ("IN", argv[1]))
        {
          dump_reg ("    IN", 4, saturn.IN);
        }
      else if (!strcmp ("OUT", argv[1]))
        {
          dump_reg ("   OUT", 3, saturn.OUT);
	}
      else if (!strcmp ("CARRY", argv[1]))
	{
	  printf (" CARRY:\t%.1d\n", saturn.CARRY);
	}
      else if (!strcmp ("CY", argv[1]))
	{
	  printf (" CARRY:\t%.1d\n", saturn.CARRY);
	}
      else if (!strcmp ("ST", argv[1]))
	{
	  dump_st ();
	}
      else if (!strcmp ("HST", argv[1]))
	{
	  dump_hst ();
	}
      else
	{
	  printf ("No Register %s in CPU.\n", argv[1]);
	}
    }
  else
    {
      /*
     * set specified register
     */
      str_to_upper (argv[1]);
      str_to_upper (argv[2]);
      if (decode_64 (&val, argv[2]))
	{
	  if (!strcmp ("A", argv[1]))
	    {
              set_reg (val, 16, saturn.A);
              dump_reg ("     A", 16, saturn.A);
            }
          else if (!strcmp ("B", argv[1]))
            {
              set_reg (val, 16, saturn.B);
              dump_reg ("     B", 16, saturn.B);
            }
          else if (!strcmp ("C", argv[1]))
            {
              set_reg (val, 16, saturn.C);
              dump_reg ("     C", 16, saturn.C);
            }
          else if (!strcmp ("D", argv[1]))
            {
              set_reg (val, 16, saturn.D);
              dump_reg ("     D", 16, saturn.D);
	    }
	  else if (!strcmp ("D0", argv[1]))
	    {
	      saturn.D0 = (word_20)(val.lo & 0xfffff);
	      printf ("    D0:\t%.5lX ->", saturn.D0);
	      for (i = 0; i < 20; i += 5)
		{
		  printf (" %s", str_nibbles (saturn.D0 + i, 5));
		}
	      printf ("\n");
	    }
	  else if (!strcmp ("D1", argv[1]))
	    {
	      saturn.D1 = (word_20)(val.lo & 0xfffff);
	      printf ("    D1:\t%.5lX ->", saturn.D1);
	      for (i = 0; i < 20; i += 5)
		{
		  printf (" %s", str_nibbles (saturn.D1 + i, 5));
		}
	      printf ("\n");
	    }
	  else if (!strcmp ("P", argv[1]))
	    {
	      saturn.P = (word_4)(val.lo & 0xf);
	      printf ("     P:\t%.1X\n", saturn.P);
	    }
	  else if (!strcmp ("PC", argv[1]))
	    {
	      saturn.PC = (word_20)(val.lo & 0xfffff);
	      disassemble (saturn.PC, instr);
	      printf ("    PC:\t%.5lX -> %s\n", saturn.PC, instr);
	    }
	  else if (!strcmp ("R0", argv[1]))
	    {
              set_reg (val, 16, saturn.R0);
              dump_reg ("    R0", 16, saturn.R0);
            }
          else if (!strcmp ("R1", argv[1]))
            {
              set_reg (val, 16, saturn.R1);
              dump_reg ("    R1", 16, saturn.R1);
            }
          else if (!strcmp ("R2", argv[1]))
            {
              set_reg (val, 16, saturn.R2);
              dump_reg ("    R2", 16, saturn.R2);
            }
          else if (!strcmp ("R3", argv[1]))
            {
              set_reg (val, 16, saturn.R3);
              dump_reg ("    R3", 16, saturn.R3);
            }
          else if (!strcmp ("R4", argv[1]))
            {
              set_reg (val, 16, saturn.R4);
              dump_reg ("    R4", 16, saturn.R4);
            }
          else if (!strcmp ("IN", argv[1]))
            {
              set_reg (val, 4, saturn.IN);
              dump_reg ("    IN", 4, saturn.IN);
            }
          else if (!strcmp ("OUT", argv[1]))
            {
              set_reg (val, 3, saturn.OUT);
              dump_reg ("   OUT", 3, saturn.OUT);
	    }
	  else if (!strcmp ("CARRY", argv[1]))
	    {
	      saturn.CARRY = (word_1)(val.lo & 0x1);
	      printf (" CARRY:\t%.1d\n", saturn.CARRY);
	    }
	  else if (!strcmp ("CY", argv[1]))
	    {
	      saturn.CARRY = (word_1)(val.lo & 0x1);
	      printf (" CARRY:\t%.1d\n", saturn.CARRY);
	    }
	  else if (!strcmp ("ST", argv[1]))
	    {
	      set_st (val);
	      dump_st ();
	    }
	  else if (!strcmp ("HST", argv[1]))
	    {
	      set_hst (val);
	      dump_hst ();
	    }
	  else
	    {
	      printf ("No Register %s in CPU.\n", argv[1]);
	    }
	}
    }
}

static void
#ifdef __FunctionProto__
do_save (int argc, char **argv)
#else
do_save (argc, argv)
     int argc;
     char *argv;
#endif
{
  if (write_files ())
    {
      printf ("Saving done.\n");
    }
  else
    {
      printf ("Saving emulator-state failed.\n");
    }
}

struct se {
  int        se_n;
  word_20    se_p;
  struct se *se_next;
};

static void
#ifdef __FunctionProto__
do_stack (int argc, char **argv)
#else
do_stack (argc, argv)
     int argc;
     char *argv;
#endif
{
  word_20 dsktop, dskbot;
  word_20 sp = 0, end = 0, ent = 0;
  word_20 ram_base, ram_mask;
  char    buf[65536];
  struct se *stack, *se;
  int n;

  ram_base = saturn.mem_cntl[1].config[0];
  ram_mask = saturn.mem_cntl[1].config[1];
  if (opt_gx)
    {
      saturn.mem_cntl[1].config[0] = 0x80000;
      saturn.mem_cntl[1].config[1] = 0xc0000;
      dsktop = DSKTOP_GX;
      dskbot = DSKBOT_GX;
    }
  else
    {
      saturn.mem_cntl[1].config[0] = 0x70000;
      saturn.mem_cntl[1].config[1] = 0xf0000;
      dsktop = DSKTOP_SX;
      dskbot = DSKBOT_SX;
    }

  load_addr(&sp, dsktop, 5);
  load_addr(&end, dskbot, 5);

  stack = (struct se *)0;
  n = 0;
  do
    {
      load_addr(&ent, sp, 5);
      if (ent == 0)
        break;
      n++;
      sp += 5;
      se = (struct se *)malloc(sizeof(struct se));
      if (se == 0)
        {
          fprintf(stderr, "Out off memory.\n");
          break;
        }
      se->se_n = n;
      se->se_p = ent;
      se->se_next = stack;
      stack = se;
    }
  while (sp <= end);

  if (n == 0)
    printf("Empty stack.\n");

  se = stack;
  while (se)
    {
      decode_rpl_obj(se->se_p, buf);
      if (se->se_n != 1)
        if (strlen(buf) > 63)
          {
            sprintf(&buf[60], "...");
            buf[63] = '\0';
          }
      printf("%5d: %.5lX -> %s\n", se->se_n, se->se_p, buf);
      se = se->se_next;
    } 

  se = stack;
  while (se)
    {
      stack = se;
      se = se->se_next;
      free(stack);
    }

  saturn.mem_cntl[1].config[0] = ram_base;
  saturn.mem_cntl[1].config[1] = ram_mask;
}

static void
#ifdef __FunctionProto__
do_stat (int argc, char **argv)
#else
do_stat (argc, argv)
     int argc;
     char *argv;
#endif
{
  printf ("Instructions/s: %ld\n", saturn.i_per_s);
  printf ("Timer 1 I/TICK: %d\n", saturn.t1_tick);
  printf ("Timer 2 I/TICK: %d\n", saturn.t2_tick);
}

static void
#ifdef __FunctionProto__
do_step (int argc, char **argv)
#else
do_step (argc, argv)
     int argc;
     char *argv;
#endif
{
  word_20 next_instr;
  word_32 n;
  int leave;

  if (enter_debugger & ILLEGAL_INSTRUCTION)
    {
      printf ("Can\'t step into an illegal instruction.");
      return;
    }

  n = 1;
  if (argc > 1)
    if (!decode_32 (&n, argv[1]))
      return;

  if (n <= 0)
    return;

  in_debugger = 1;
  step_instruction ();

  if (exec_flags & EXEC_BKPT)
    {
      if (check_breakpoint (BP_EXEC, saturn.PC))
	{
	  enter_debugger |= BREAKPOINT_HIT;
	  return;
	}
    }

  next_instr = saturn.PC;

  sched_adjtime = 0;
  schedule ();

  enter_debugger = 0;
  while (1)
    {
      if (enter_debugger)
	break;

      leave = 0;

      if (saturn.PC == next_instr)
	{
	  n--;
	  leave = 1;
	  if (n == 0)
	    break;
	}

      step_instruction ();

      if (exec_flags & EXEC_BKPT)
	{
	  if (check_breakpoint (BP_EXEC, saturn.PC))
	    {
	      enter_debugger |= BREAKPOINT_HIT;
	      break;
	    }
	}

      if (leave)
	next_instr = saturn.PC;

      schedule ();
    }
}

static void
#ifdef __FunctionProto__
do_reset (int argc, char **argv)
#else
do_reset (argc, argv)
     int argc;
     char *argv;
#endif
{
  if (confirm ("Do a RESET (PC = 00000)?"))
    {
      saturn.PC = 0;
      enter_debugger &= ~ILLEGAL_INSTRUCTION;
    }
}

static void
#ifdef __FunctionProto__
do_rstk (int argc, char **argv)
#else
do_rstk (argc, argv)
     int argc;
     char *argv;
#endif
{
  int i, j;

  disassemble (saturn.PC, instr);
  printf ("PC: %.5lX: %s\n", saturn.PC, instr);
  if (saturn.rstkp < 0)
    {
      printf ("Empty return stack.\n");
    }
  else
    {
      j = 0;
      for (i = saturn.rstkp; i >= 0; i--)
	{
	  disassemble (saturn.rstk[i], instr);
	  printf ("%2d: %.5lX: %s\n", j, saturn.rstk[i], instr);
	  j++;
	}
    }
}

int
#ifdef __FunctionProto__
debug (void)
#else
debug ()
#endif
{
  t1_t2_ticks ticks;
  struct cmd *cmdp;
  char *cp;
  int argc;
  char *argv[MAX_ARGS];
  char *rl = NULL;
  static char *cl = (char *) 0;
  static char *old_line = (char *) 0;
  int i;

  /*
   * do we want to debug ???
   */
  if (!useDebugger)
    {
      if (enter_debugger & ILLEGAL_INSTRUCTION)
        {
          if (!quiet)
            fprintf (stderr, "%s: reset (illegal instruction at 0x%.5lX)\n",
                     progname, saturn.PC);
          saturn.PC = 0;
        }
      if (enter_debugger & USER_INTERRUPT)
        if (verbose)
          printf ("%s: user interrupt (SIGINT) ignored\n", progname);
      if (enter_debugger & BREAKPOINT_HIT)
        if (verbose)
          printf ("%s: breakpoint hit at 0x%.5lX ignored\n",
                  progname, saturn.PC);
      if (enter_debugger & TRAP_INSTRUCTION)
        if (verbose)
          printf ("%s: trap instruction at 0x%.5lX ignored\n",
                  progname, saturn.PC);
      enter_debugger = 0;
      return 0;
    }

  /*
   * update the lcd if necessary
   */
  if (device.display_touched)
    {
      device.display_touched = 0;
      update_display ();
#ifdef HAVE_XSHM
      if (disp.display_update)
	refresh_display ();
#endif
    }

  /*
   * debugging is counted as idle time
   */
  stop_timer (RUN_TIMER);
  start_timer (IDLE_TIMER);

  continue_flag = 0;

  if (enter_debugger & ILLEGAL_INSTRUCTION)
    {
      printf ("ILLEGAL INSTRUCTION at %.5lX : %s\n",
	      saturn.PC, str_nibbles (saturn.PC, 16));
    }

  if (enter_debugger & TRAP_INSTRUCTION)
    {
      printf ("TRAP at %.5lX : %s\n",
	      saturn.PC - 5, str_nibbles (saturn.PC - 5, 16));
      enter_debugger &= ~TRAP_INSTRUCTION;
    }

  do
    {

      /*
       * print current instruction
       */
      disassemble (saturn.PC, instr);
      printf ("%.5lX: %s\n", saturn.PC, instr);

      /*
       * read a command
       */
#ifdef HAVE_READLINE
      rl = readline ("x48-debug> ");
#else
      if (rl == (char *) 0)
        rl = (char *)malloc((size_t)80);
      printf("x48-debug> ");
      fflush(stdout);
      rl = read_str(rl, 80, 0);
#endif

      if (rl == (char *) 0)
	{
	  continue_flag = 1;
	  continue;
	}
      if (*rl == '\0')
	{
	  free (rl);
          rl = (char *) 0;
	  if (cl)
            {
	      free (cl);
              cl = (char *) 0;
            }
	  if (old_line)
	    cl = strcpy ((char *) malloc (strlen (old_line)), old_line);
	  else
	    cl = strcpy ((char *) malloc (strlen ("(null)")), "(null)");
	}
      else
	{
#ifndef HAVE_READLINE
          if (rl[strlen(rl) - 1] == '\n')
            rl[strlen(rl) - 1] = '\0';
#endif
	  if (cl)
	    {
	      free (cl);
              cl = (char *) 0;
	    }
	  if (old_line)
	    {
	      free (old_line);
              old_line = (char *) 0;
	    }
	  cl = strcpy ((char *) malloc (strlen (rl)), rl);
	  old_line = strcpy ((char *) malloc (strlen (rl)), rl);
#ifdef HAVE_READLINE
	  add_history (rl);
#endif
	  free (rl);
          rl = (char *) 0;
	}

      /*
       * decode the commandline
       */
      cp = strtok (cl, " \t");
      for (cmdp = cmd_tbl; cmdp->name; cmdp++)
	{
	  if (strcmp (cp, cmdp->name) == 0)
	    {
	      break;
	    }
	}

      argc = 0;
      argv[argc++] = cp;
      while ((cp = strtok ((char *) 0, " \t")) != (char *) 0)
	{
	  argv[argc++] = cp;
	  if (argc == MAX_ARGS)
	    break;
	}
      for (i = argc; i < MAX_ARGS; i++)
	argv[i] = (char *) NULL;

      /*
       * execute the command, if valid
       */
      if (cmdp->func)
	{
	  (*cmdp->func) (argc, argv);
	}
      else
	{
	  printf ("Undefined command \"%s\". Try \"help\".\n", argv[0]);
	}
      in_debugger = 0;

    }
  while (!continue_flag);

  /*
   * adjust the hp48's timers
   */
  in_debugger = 1;
  ticks = get_t1_t2 ();
  in_debugger = 0;
  
  if (saturn.t2_ctrl & 0x01)
    {
      saturn.timer2 = ticks.t2_ticks;
    }

  saturn.timer1 = (set_t1 - ticks.t1_ticks) & 0xf;

  sched_adjtime = 0;

  /*
   * restart timers
   */
  stop_timer (IDLE_TIMER);
  start_timer (RUN_TIMER);

  set_accesstime();

  if (enter_debugger & ILLEGAL_INSTRUCTION)
    {
      printf ("Reset (ILLEGAL INSTRUCTION)\n");
      saturn.PC = 0;
    }
  else
    {
      printf ("Continue.\n");
    }

  enter_debugger = 0;

  /*
   * Set exec_flags according to breakpoints, etc.
   */
  exec_flags = 0;
  if (num_bkpts)
    exec_flags |= EXEC_BKPT;

  return 0;
}

int
#ifdef __FunctionProto__
emulate_debug (void)
#else
emulate_debug ()
#endif
{
  do
    {

      step_instruction ();

      if (exec_flags & EXEC_BKPT)
	{
	  if (check_breakpoint (BP_EXEC, saturn.PC))
	    {
	      enter_debugger |= BREAKPOINT_HIT;
	      break;
	    }
	}

      if (schedule_event-- == 0)
	{
	  schedule ();
	}

    }
  while (!enter_debugger);

  return 0;
}
