/*****************************************************************************

			       XCopilot

This code is part of XCopilot, a port of copilot

     Portions of this code are Copyright (C) 1997 Ivan A. Curtis
		       icurtis@radlogic.com.au

The original MS-Windows95 copilot emulator was written by Greg Hewgill.
The following copyright notice appeared on the original copilot sources:

		  Copyright (c) 1996 Greg Hewgill

 MC68000 Emulation code is from Bernd Schmidt's Unix Amiga Emulator.
       The following copyright notice appeared in those files:

	  Original UAE code Copyright (c) 1995 Bernd Schmidt

This code must not be distributed without these copyright notices intact.

*******************************************************************************
*******************************************************************************

Filename:	newcpu.c

Description:	MC68000 emulation

Update History:   (most recent first)
   Ian Goldberg 10-Apr-97 16:53 -- added support for logging flow traces
   I. Curtis    26-Feb-97 15:00 -- modified from win95 version

******************************************************************************/
#include <assert.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

#include "sysdeps.h"
#include "shared.h"
#include "memory.h"
#include "custom.h"
#include "newcpu.h"
#include "dragonball.h"

int areg_byteinc[] = { 1,1,1,1,1,1,1,2 };
int imm8_table[] = { 8,1,2,3,4,5,6,7 };

UWORD *InstructionStart_p;
shared_img *Shptr;

#ifdef COUNT_INSTRS
static unsigned long int instrcount[65536];
static UWORD opcodenums[65536];

int compfn(const void *el1, const void *el2)
{
  return instrcount[*(const UWORD *)el1] < instrcount[*(const UWORD *)el2];
}

void dump_counts(void)
{
  FILE *f = fopen("insncount", "w");
  unsigned long int total = 0;
  int i;
  
  for(i=0; i < 65536; i++) {
    opcodenums[i] = i;
    total += instrcount[i];
  }
  qsort(opcodenums, 65536, sizeof(UWORD), compfn);
  
  fprintf(f, "Total: %ld\n", total);
  for(i=0; i < 65536; i++) {
    unsigned long int cnt = instrcount[opcodenums[i]];
    if (!cnt)
      break;
    fprintf(f, "%04x: %ld\n", opcodenums[i], cnt);
  }
  fclose(f);
}
#endif

#ifdef INTEL_FLAG_OPT
union flagu intel_flag_lookup[256];
#endif

void fatal(void)
{
    Shptr->CpuReq = cpuExit;
    abort();
}

UWORD nextiword(void)
{
  UWORD r = *(Shptr->regs).pc_p++;
  return r;
}

ULONG nextilong(void)
{
  ULONG r = *(Shptr->regs).pc_p++;
  r = (r << 16) + *(Shptr->regs).pc_p++;
  return r;
}

void MC68000_setpc(CPTR newpc)
{
    if (!get_real_address(newpc)) {
	fprintf(stderr, "FATAL: weird setpc(%08lx) at %08lx!\n",
	    (unsigned long)newpc, MC68000_getpc());
	abort();
    }
    if (Shptr->logF) {
	fprintf(Shptr->logF, "setpc %08lx -> %08lx\n",
	    MC68000_getpc(), (unsigned long)newpc);
    }
    (Shptr->regs).pc = newpc;
    (Shptr->regs).pc_p = (Shptr->regs).pc_oldp = get_real_address(newpc);
}

CPTR MC68000_getpc(void)
{
    return (Shptr->regs).pc +
      ((char *)(Shptr->regs).pc_p - (char *)(Shptr->regs).pc_oldp);
}

void MC68000_setstopped(int stop)
{
    (Shptr->regs).stopped = stop;
    if (stop)
      specialflags |= SPCFLAG_STOP;
}

int cctrue(const int cc)
{
  switch(cc) {
  case 0: return 1;                       /* T */
  case 1: return 0;                       /* F */
  case 2: return !CFLG && !ZFLG;          /* HI */
  case 3: return CFLG || ZFLG;            /* LS */
  case 4: return !CFLG;                   /* CC */
  case 5: return CFLG;                    /* CS */
  case 6: return !ZFLG;                   /* NE */
  case 7: return ZFLG;                    /* EQ */
  case 8: return !VFLG;                   /* VC */
  case 9: return VFLG;                    /* VS */
  case 10:return !NFLG;                   /* PL */
  case 11:return NFLG;                    /* MI */
  case 12:return NFLG == VFLG;            /* GE */
  case 13:return NFLG != VFLG;            /* LT */
  case 14:return !ZFLG && (NFLG == VFLG); /* GT */
  case 15:return ZFLG || (NFLG != VFLG);  /* LE */
  }
  fatal();
  return 0;
}

extern ULONG get_disp_ea (ULONG base, UWORD dp)
{
    int reg = (dp >> 12) & 7;
    LONG regd;
    
    if (dp & 0x8000)
	regd = (Shptr->regs).a[reg];
    else
	regd = (Shptr->regs).d[reg];
    if (!(dp & 0x800))
	regd = (LONG)(WORD)regd;
    return base + (BYTE)(dp) + regd;
}

void MC68000_setshare(shared_img *shptr)
{
  Shptr = shptr;
}

void MC68000_init(shared_img *shptr)
{
  Shptr = shptr;
#ifdef COUNT_INSTRS
  memset(instrcount, 0, sizeof instrcount);
#endif
#ifdef INTEL_FLAG_OPT
  for (i = 0; i < 256; i++) {
    intel_flag_lookup[i].flags.c = !!(i & 1);
    intel_flag_lookup[i].flags.z = !!(i & 64);
    intel_flag_lookup[i].flags.n = !!(i & 128);
    intel_flag_lookup[i].flags.v = 0;
  }
#endif
  /*    printf("Building CPU table...\n");
	read_table68k ();
	do_merges ();
	for (opcode = 0; opcode < 65536; opcode++)
	cpufunctbl[opcode] = op_illg;
	for (i = 0; smallcputbl[i].handler != NULL; i++) {
	if (!smallcputbl[i].specific)
	cpufunctbl[smallcputbl[i].opcode] = smallcputbl[i].handler;
	}
	for (opcode = 0; opcode < 65536; opcode++) {
	cpuop_func *f;
	
	if (table68k[opcode].mnemo == i_ILLG)
	continue;
	
	if (table68k[opcode].handler != -1) {
	f = cpufunctbl[table68k[opcode].handler];
	if (f == op_illg)
	fatal();
	cpufunctbl[opcode] = f;
	}
	}	
	for (i = 0; smallcputbl[i].handler != NULL; i++) {
	if (smallcputbl[i].specific)
	cpufunctbl[smallcputbl[i].opcode] = smallcputbl[i].handler;
	}*/
}

/************ these two moved into shared_img
struct regstruct regs;
union flagu regflags;
********************************************/

#if CPU_LEVEL > 1
ULONG get_disp_ea (ULONG base, UWORD dp)
{
  int reg = (dp >> 12) & 7;
  LONG regd;
  if (dp & 0x8000)
    regd = (Shptr->regs).a[reg];
  else
    regd = (Shptr->regs).d[reg];
  if (!(dp & 0x800))
    regd = (LONG)(WORD)regd;
  if (dp & 0x100) {
    LONG extraind = 0;
    printf("020\n");
    regd <<= (dp >> 9) & 3;
    if (dp & 0x80)
      base = 0;
    if (dp & 0x40)
      regd = 0;
    if ((dp & 0x30) == 0x20)
      base += (LONG)(WORD)nextiword();
    if ((dp & 0x30) == 0x30)
      base += nextilong();
    
    if ((dp & 0x3) == 0x2)
      extraind = (LONG)(WORD)nextiword();
    if ((dp & 0x3) == 0x3)
      extraind = nextilong();
    
    if (!(dp & 4))
      base += regd;
    if (dp & 3)
      base = get_long (base);
    if (dp & 4)
      base += regd;
    
    return base + extraind;
    /* Yikes, that's complicated */
  } else {
    return base + (BYTE)(dp) + regd;
  }
}
#endif

void MakeSR(void)
{
#if 0
  assert((NFLG & 1) == NFLG);
  assert(((Shptr->regs).s & 1) == (Shptr->regs).s);
  assert(((Shptr->regs).x & 1) == (Shptr->regs).x);
  assert((CFLG & 1) == CFLG);
  assert((VFLG & 1) == VFLG);
  assert((ZFLG & 1) == ZFLG);
#endif
  (Shptr->regs).sr = (((Shptr->regs).t << 15) | ((Shptr->regs).s << 13) |
		      ((Shptr->regs).intmask << 8) | ((Shptr->regs).x << 4) |
		      (NFLG << 3) | (ZFLG << 2) | (VFLG << 1) |  CFLG);
}

void MakeFromSR(void)
{
  int olds = (Shptr->regs).s;
  
  (Shptr->regs).t = ((Shptr->regs).sr >> 15) & 1;
  (Shptr->regs).s = ((Shptr->regs).sr >> 13) & 1;
  (Shptr->regs).intmask = ((Shptr->regs).sr >> 8) & 7;
  (Shptr->regs).x = ((Shptr->regs).sr >> 4) & 1;
  NFLG = ((Shptr->regs).sr >> 3) & 1;
  ZFLG = ((Shptr->regs).sr >> 2) & 1;
  VFLG = ((Shptr->regs).sr >> 1) & 1;
  CFLG = (Shptr->regs).sr & 1;
  if (olds != (Shptr->regs).s) {
    CPTR temp = (Shptr->regs).usp;
    (Shptr->regs).usp = (Shptr->regs).a[7];
    (Shptr->regs).a[7] = temp;
  }
  specialflags |= SPCFLAG_INT;
  if ((Shptr->regs).t)
    specialflags |= SPCFLAG_TRACE;
  else
    specialflags &= ~(SPCFLAG_TRACE | SPCFLAG_DOTRACE);
}

void MC68000_exception(int nr)
{
  if (nr < 48 && Shptr->ExceptionFlags[nr] && InstructionStart_p) {
    (Shptr->regs).pc_p = InstructionStart_p;
    Shptr->CpuState = cpuException + nr;
    return;
  }

  if (nr == 32 + 0xF && do_api(*(Shptr->regs).pc_p)) {
    nextiword();
    return;
  }

  MakeSR();
  if (!(Shptr->regs).s) {
    CPTR temp = (Shptr->regs).usp;
    (Shptr->regs).usp = (Shptr->regs).a[7];
    (Shptr->regs).a[7] = temp;
    (Shptr->regs).s = 1;
  }
  if (CPU_LEVEL > 0) {
    (Shptr->regs).a[7] -= 2;
    put_word ((Shptr->regs).a[7], nr * 4);
  }
  (Shptr->regs).a[7] -= 4;
  put_long ((Shptr->regs).a[7], MC68000_getpc ());
  (Shptr->regs).a[7] -= 2;
  put_word ((Shptr->regs).a[7], (Shptr->regs).sr);
  MC68000_setpc(get_long(4*nr));
  (Shptr->regs).t = 0;
  specialflags &= ~(SPCFLAG_TRACE | SPCFLAG_DOTRACE);
}

static void MC68000_interrupt(int nr)
{
  assert(nr < 8 && nr >= 0);
  MC68000_exception(nr + intbase());
    
  (Shptr->regs).intmask = nr;
  specialflags |= SPCFLAG_INT;
}

void MC68000_reset(void)
{
  (Shptr->regs).a[7] = get_long(0x10c00000);
  MC68000_setpc(get_long(0x10c00004));
  (Shptr->regs).s = 1;
  (Shptr->regs).stopped = 0;
  (Shptr->regs).t = 0;
  specialflags = 0;
  Shptr->CpuState = cpuStopped;
  (Shptr->regs).intmask = 7;
  customreset();
}

void op_illg(ULONG opcode)
{
  (Shptr->regs).pc_p--;
  if ((opcode & 0xF000) == 0xF000) {
    if ((opcode & 0xE00) == 0x200)
      MC68000_exception(0xB);
    else 
      switch (opcode & 0x1FF) {
      case 0x17:
	(Shptr->regs).pc_p+=2;
	break;
      default:
	(Shptr->regs).pc_p++;
      }
    return;
  }
  if ((opcode & 0xF000) == 0xA000) {
    MC68000_exception(0xA);
    return;
  }
  if (opcode == 0x4AFC) { /* breakpoint */
    Shptr->CpuState = cpuBreakpoint;
    /*
     *     fprintf(stderr, "I - CpuState = cpuBreakpoint\n");
     *     fflush(stderr);
     */
    return;
  }
  fprintf(stderr, "Illegal instruction: %04x\n", (unsigned int)opcode);
  MC68000_exception(4);
}

void updateisr();
void maybe_updateisr();

#ifndef DEFPILOTLOG
#define DEFPILOTLOG "xcopilot.log"
#endif

void MC68000_run(void)
{
  UWORD opcode;
  Shptr->CpuState = cpuRunning;       /* start running */
  for(;;) {
    if (Shptr->CpuReq != cpuNone) {   /* check for a request */
      return;			      /* bail out if requested */
    }
    if (Shptr->dolog == !Shptr->logF) {
	/* We need to turn logging on or off */
	if (Shptr->dolog) {
	    char *pilotdir = getenv("XCOPILOTDIR");
	    char *fname = getenv("XCOPILOTLOG");
	    char *fbuf = NULL;
	    if (!fname) fname = DEFPILOTLOG;
	    if (fname[0] != '/' && pilotdir) {
		fbuf = malloc(strlen(pilotdir)+strlen(fname)+2);
		if (fbuf) {
		    sprintf("%s/%s", pilotdir, fname);
		    fname = fbuf;
		}
	    }
	    Shptr->logF = fopen(fname, "a");
	    if (fbuf) {
		free(fbuf);
		fbuf = NULL;
	    }
	} else {
	    fclose(Shptr->logF);
	    Shptr->logF = NULL;
	}
    }
    InstructionStart_p = (Shptr->regs).pc_p;
    opcode = nextiword();
    (*cpufunctbl[opcode])(opcode);
    maybe_updateisr();
#ifndef NO_EXCEPTION_3
    if (buserr) {
      MC68000_exception(3);
      buserr = 0;
    }
#endif
    InstructionStart_p = NULL;
    do_cycles(0);	
    if (specialflags) {
      if (specialflags & SPCFLAG_DOTRACE) {
	MC68000_exception(9);
      }
      while (specialflags & SPCFLAG_STOP) {
	usleep(100);
	maybe_updateisr();
	do_cycles(1);
	if (specialflags & (SPCFLAG_INT | SPCFLAG_DOINT)){
	  int intr = intlev();
	  specialflags &= ~(SPCFLAG_INT | SPCFLAG_DOINT);
	  if (intr != -1 && intr > (Shptr->regs).intmask) {
	    MC68000_interrupt(intr);
	    (Shptr->regs).stopped = 0;
	    specialflags &= ~SPCFLAG_STOP;
	  }	    
	}
	if (Shptr->CpuState > cpuRunning || Shptr->CpuReq != cpuNone) {
	  return;
	}
      }
      if (specialflags & SPCFLAG_TRACE) {
	specialflags &= ~SPCFLAG_TRACE;
	specialflags |= SPCFLAG_DOTRACE;
      }
      
      if (specialflags & SPCFLAG_DOINT) {
	int intr = intlev();
	specialflags &= ~(SPCFLAG_INT | SPCFLAG_DOINT);
	if (intr != -1 && intr > (Shptr->regs).intmask) {
	  MC68000_interrupt(intr);
	  (Shptr->regs).stopped = 0;
	}	    
      }
      if (specialflags & SPCFLAG_INT) {
	specialflags &= ~SPCFLAG_INT;
	specialflags |= SPCFLAG_DOINT;
      }
      if (specialflags & SPCFLAG_BRK) {
	specialflags &= ~SPCFLAG_BRK;
	return;		
      }
    }
    if (Shptr->CpuState > cpuRunning) {
      return;
    }
  }
}

void MC68000_step(void)
{
    specialflags |= SPCFLAG_BRK;
    MC68000_run();
}

void MC68000_skip(CPTR nextpc)
{
    specialflags |= SPCFLAG_BRK;
    do {
      MC68000_step();
    } while (nextpc != MC68000_getpc());
    Shptr->CpuState = cpuStopped;
}
