 /*
  * E-UAE - The portable Amiga Emulator
  *
  * Support for traps
  *
  * Copyright Richard Drummond 2005
  *
  * Inspired by code from UAE:
  * Copyright 1995, 1996 Bernd Schmidt
  * Copyright 1996 Ed Hanway
  */

#include "sysconfig.h"
#include "sysdeps.h"

#include "options.h"
#include "memory.h"
#include "custom.h"
#include "newcpu.h"
#include "td-sdl/thread.h"
#include "autoconf.h"
#include "traps.h"

/*
 * Traps are the mechanism via which 68k code can call emulator code
 * (and for that emulator code in turn to call 68k code). They are
 * thus the basis for much of the cool stuff that E-UAE can do.
 *
 * Emulator traps take advantage of the illegal 68k opwords 0xA000 to
 * 0xAFFF. Normally these would generate an A-line exception. However,
 * when encountered in the RTAREA section of memory, these opwords
 * instead invoke a corresponding emulator trap, allowing a host
 * function to be called.
 *
 * Two types of emulator trap are available - a simple trap and an
 * extended trap. A simple trap may not call 68k code; an extended
 * trap can.
 *
 * Extended traps are rather complex beasts (to implement, not
 * necessarily to use). This is because for the trap handler function
 * to be able to call 68k code, we must somehow allow the emulator's
 * 68k interpreter to resume execution of 68k code in the middle of
 * the trap handler function.
 *
 * In UAE of old this used to be implemented via a stack-swap mechanism.
 * While this worked, it was definitely in the realm of black magic and
 * horribly non-portable, requiring assembly language glue specific to
 * the host ABI and compiler to actually perform the swap.
 *
 * In this implementation, in essence we do something similar - but the
 * new stack is provided by a new thread. No voodoo required, just a
 * working thread layer.
 *
 * The complexity in this approach arises in synchronizing the trap
 * threads with the emulator thread. This implementation errs on the side
 * of paranoia when it comes to thread synchronization. Once all the
 * bugs are knocked out of the bsdsocket emulation, a simpler scheme may
 * suffice.
 */

/*
 * Record of a defined trap (that is, a trap allocated to a host function)
 */
struct Trap
{
  TrapHandler handler;	/* Handler function to be invoked for this trap. */
  int         flags;		/* Trap attributes. */
  const TCHAR *name;		/* For debugging purposes. */
  uaecptr addr;
};

#define MAX_TRAPS 4096

/* Defined traps */
static struct Trap  traps[MAX_TRAPS];
static unsigned int trap_count = 1;


static const int trace_traps = 0;

static void trap_HandleExtendedTrap (TrapHandler, int has_retval);

uaecptr find_trap (const TCHAR *name)
{
  int i;

  for (i = 0; i < trap_count; i++) {
  	struct Trap *trap = &traps[i];
  	if ((trap->flags & TRAPFLAG_UAERES) && trap->name && !_tcscmp (trap->name, name))
	    return trap->addr;
  }
  return 0;
}


/*
 * Define an emulator trap
 *
 * handler_func = host function that will be invoked to handle this trap
 * flags        = trap attributes
 * name         = name for debugging purposes
 *
 * returns trap number of defined trap
 */
unsigned int define_trap (TrapHandler handler_func, int flags, const TCHAR *name)
{
  if (trap_count == MAX_TRAPS) {
		write_log (_T("Ran out of emulator traps\n"));
  	abort ();
  	return -1;
  } else {
  	int i;
  	unsigned int trap_num;
  	struct Trap *trap;
  	uaecptr addr = here ();

  	for (i = 0; i < trap_count; i++) {
	    if (addr == traps[i].addr)
    		return i;
  	}

  	trap_num = trap_count++;
  	trap = &traps[trap_num];

  	trap->handler = handler_func;
  	trap->flags   = flags;
  	trap->name    = name;
  	trap->addr    = addr;

  	return trap_num;
  }
}


/*
 * This function is called by the 68k interpreter to handle an emulator trap.
 *
 * trap_num = number of trap to invoke
 * regs     = current 68k state
 */
void REGPARAM2 m68k_handle_trap (unsigned int trap_num)
{
  struct Trap *trap = &traps[trap_num];
  uae_u32 retval = 0;

  int has_retval   = (trap->flags & TRAPFLAG_NO_RETVAL) == 0;
  int implicit_rts = (trap->flags & TRAPFLAG_DORET) != 0;

  if (trap->name && trap->name[0] != 0 && trace_traps)
		write_log (_T("TRAP: %s\n"), trap->name);

  if (trap_num < trap_count) {
  	if (trap->flags & TRAPFLAG_EXTRA_STACK) {
	    /* Handle an extended trap.
	     * Note: the return value of this trap is passed back to 68k
	     * space via a separate, dedicated simple trap which the trap
	     * handler causes to be invoked when it is done.
	     */
	    trap_HandleExtendedTrap (trap->handler, has_retval);
  	} else {
	    /* Handle simple trap */
	    retval = (trap->handler) (NULL);

	    if (has_retval)
    		m68k_dreg (regs, 0) = retval;

	    if (implicit_rts) {
    		m68k_do_rts ();
    		fill_prefetch ();
	    }
    }
  } else
		write_log (_T("Illegal emulator trap\n"));
}



/*
 * Implementation of extended traps
 */

struct TrapCPUContext
{
	uae_u32 regs[16];
	uae_u32 pc;
	int intmask;
};

struct TrapContext
{
  /* Trap's working copy of 68k state. This is what the trap handler should
  *  access to get arguments from 68k space. */
	//struct regstruct regs;

  /* Trap handler function that gets called on the trap context */
  TrapHandler       trap_handler;
  /* Should the handler return a value to 68k space in D0? */
  int               trap_has_retval;
  /* Return value from trap handler */
  uae_u32           trap_retval;

  /* Copy of 68k state at trap entry. */
	//struct regstruct saved_regs;
	struct TrapCPUContext saved_regs;

  /* Thread which effects the trap context. */
  uae_thread_id     thread;
  /* For IPC between the main emulator. */
  uae_sem_t         switch_to_emu_sem;
  /* context and the trap context. */
  uae_sem_t         switch_to_trap_sem;

  /* When calling a 68k function from a trap handler, this is set to the
  *  address of the function to call. */
  uaecptr           call68k_func_addr;
  /* And this gets set to the return value of the 68k call. */
  uae_u32           call68k_retval;
};

static void copytocpucontext(struct TrapCPUContext *cpu)
{
	memcpy (cpu->regs, regs.regs, sizeof (regs.regs));
	cpu->intmask = regs.intmask;
	cpu->pc = m68k_getpc ();
}
static void copyfromcpucontext(struct TrapCPUContext *cpu, uae_u32 pc)
{
	memcpy (regs.regs, cpu->regs, sizeof (regs.regs));
	regs.intmask = cpu->intmask;
	m68k_setpc (pc);
}


/* 68k addresses which invoke the corresponding traps. */
static uaecptr m68k_call_trapaddr;
static uaecptr m68k_return_trapaddr;
static uaecptr exit_trap_trapaddr;

/* For IPC between main thread and trap context */
static uae_sem_t trap_mutex = 0;
static TrapContext *current_context;


/*
 * Thread body for trap context
 */
static void *trap_thread (void *arg)
{
  TrapContext *context = (TrapContext *) arg;

  /* Wait until main thread is ready to switch to the
   * this trap context. */
  uae_sem_wait (&context->switch_to_trap_sem);

  /* Execute trap handler function. */
  context->trap_retval = context->trap_handler (context);

  /* Trap handler is done - we still need to tidy up
   * and make sure the handler's return value is propagated
   * to the calling 68k thread.
   *
   * We do this by causing our exit handler to be executed on the 68k context.
   */

  /* Enter critical section - only one trap at a time, please! */
  uae_sem_wait (&trap_mutex);

	//regs = context->saved_regs;
	/* Set PC to address of the exit handler, so that it will be called
	* when the 68k context resumes. */
	copyfromcpucontext (&context->saved_regs, exit_trap_trapaddr);
  /* Don't allow an interrupt and thus potentially another
   * trap to be invoked while we hold the above mutex.
   * This is probably just being paranoid. */
  regs.intmask = 7;

	//m68k_setpc (exit_trap_trapaddr);
  current_context = context;

  /* Switch back to 68k context */
  uae_sem_post (&context->switch_to_emu_sem);

  /* Good bye, cruel world... */

  /* dummy return value */
  write_log("trap_thread: exit (arg=0x%08X)\n", arg);
  return 0;
}

/*
 * Set up extended trap context and call handler function
 */
static void trap_HandleExtendedTrap (TrapHandler handler_func, int has_retval)
{
  struct TrapContext *context = xcalloc (TrapContext, 1);

  if (context) {
	  uae_sem_init (&context->switch_to_trap_sem, 0, 0);
	  uae_sem_init (&context->switch_to_emu_sem, 0, 0);

	  context->trap_handler    = handler_func;
	  context->trap_has_retval = has_retval;

		//context->saved_regs = regs;
		copytocpucontext (&context->saved_regs);

	  /* Start thread to handle new trap context. */
	  uae_start_thread_fast (trap_thread, (void *)context, &context->thread);

	  /* Switch to trap context to begin execution of
	   * trap handler function.
	   */
	  uae_sem_post (&context->switch_to_trap_sem);

	  /* Wait for trap context to switch back to us.
	   *
	   * It'll do this when the trap handler is done - or when
	   * the handler wants to call 68k code. */
	  uae_sem_wait (&context->switch_to_emu_sem);
  }
}

/*
 * Call m68k function from an extended trap handler
 *
 * This function is to be called from the trap context.
 */
static uae_u32 trap_Call68k (TrapContext *context, uaecptr func_addr)
{
  /* Enter critical section - only one trap at a time, please! */
  uae_sem_wait (&trap_mutex);
  current_context = context;

  /* Don't allow an interrupt and thus potentially another
   * trap to be invoked while we hold the above mutex.
   * This is probably just being paranoid. */
  regs.intmask = 7;

  /* Set up function call address. */
  context->call68k_func_addr = func_addr;

  /* Set PC to address of 68k call trap, so that it will be
   * executed when emulator context resumes. */
  m68k_setpc (m68k_call_trapaddr);
  fill_prefetch ();

  /* Switch to emulator context. */
  uae_sem_post (&context->switch_to_emu_sem);

  /* Wait for 68k call return handler to switch back to us. */
  uae_sem_wait (&context->switch_to_trap_sem);

  /* End critical section. */
  uae_sem_post (&trap_mutex);

  /* Get return value from 68k function called. */
  return context->call68k_retval;
}

/*
 * Handles the emulator's side of a 68k call (from an extended trap)
 */
static uae_u32 REGPARAM2 m68k_call_handler (TrapContext *dummy_ctx)
{
  TrapContext *context = current_context;

  uae_u32 sp;

  sp = m68k_areg (regs, 7);

  /* Push address of trap context on 68k stack. This is
   * so the return trap can find this context. */
  sp -= sizeof (void *);
  put_pointer (sp, context);

  /* Push addr to return handler trap on 68k stack.
   * When the called m68k function does an RTS, the CPU will pull this
   * address off the stack and so call the return handler. */
  sp -= 4;
  put_long (sp, m68k_return_trapaddr);

  m68k_areg (regs, 7) = sp;

  /* Set PC to address of 68k function to call. */
  m68k_setpc (context->call68k_func_addr);
  fill_prefetch ();

  /* End critical section: allow other traps run. */
  uae_sem_post (&trap_mutex);

  /* Restore interrupts. */
  regs.intmask = context->saved_regs.intmask;

  /* Dummy return value. */
  return 0;
}

/*
 * Handles the return from a 68k call at the emulator's side.
 */
static uae_u32 REGPARAM2 m68k_return_handler (TrapContext *dummy_ctx)
{
  TrapContext *context;
  uae_u32 sp;

  /* One trap returning at a time, please! */
  uae_sem_wait (&trap_mutex);

  /* Get trap context from 68k stack. */
  sp = m68k_areg (regs, 7);
  context = (TrapContext *) get_pointer(sp);
  sp += sizeof (void *);
  m68k_areg (regs, 7) = sp;

  /* Get return value from the 68k call. */
  context->call68k_retval = m68k_dreg (regs, 0);

  /* Switch back to trap context. */
  uae_sem_post (&context->switch_to_trap_sem);

  /* Wait for trap context to switch back to us.
   *
   * It'll do this when the trap handler is done - or when
   * the handler wants to call another 68k function. */
  uae_sem_wait (&context->switch_to_emu_sem);

  /* Dummy return value. */
  return 0;
}

/*
 * Handles completion of an extended trap and passes
 * return value from trap function to 68k space.
 */
static uae_u32 REGPARAM2 exit_trap_handler (TrapContext *dummy_ctx)
{
  TrapContext *context = current_context;

  /* Wait for trap context thread to exit. */
  uae_wait_thread (context->thread);

  /* Restore 68k state saved at trap entry. */
	//regs = context->saved_regs;
	copyfromcpucontext (&context->saved_regs, context->saved_regs.pc);
  
  /* If trap is supposed to return a value, then store
   * return value in D0. */
  if (context->trap_has_retval)
  	m68k_dreg (regs, 0) = context->trap_retval;

  uae_sem_destroy (&context->switch_to_trap_sem);
  uae_sem_destroy (&context->switch_to_emu_sem);

  xfree (context);

  /* End critical section */
  uae_sem_post (&trap_mutex);

  /* Dummy return value. */
  return 0;
}



/*
 * Call a 68k library function from extended trap.
 */
uae_u32 CallLib (TrapContext *context, uaecptr base, uae_s16 offset)
{
  uae_u32 retval;
  uaecptr olda6 = m68k_areg (regs, 6);

  m68k_areg (regs, 6) = base;
  retval = trap_Call68k (context, base + offset);
  m68k_areg (regs, 6) = olda6;

  return retval;
}

/*
 * Call 68k function from extended trap.
 */
uae_u32 CallFunc (TrapContext *context, uaecptr func)
{
  return trap_Call68k (context, func);
}


/*
 * Initialize trap mechanism.
 */
void init_traps (void)
{
  trap_count = 0;
}

/*
 * Initialize the extended trap mechanism.
 */
void init_extended_traps (void)
{
  m68k_call_trapaddr = here ();
  calltrap (deftrap2 (m68k_call_handler, TRAPFLAG_NO_RETVAL, _T("m68k_call")));

  m68k_return_trapaddr = here();
  calltrap (deftrap2 (m68k_return_handler, TRAPFLAG_NO_RETVAL, _T("m68k_return")));

  exit_trap_trapaddr = here();
  calltrap (deftrap2 (exit_trap_handler, TRAPFLAG_NO_RETVAL, _T("exit_trap")));

  if(trap_mutex != 0)
    uae_sem_destroy(&trap_mutex);
  trap_mutex = 0;
  uae_sem_init (&trap_mutex, 0, 1);
}
