/*
 *  This file is part of ixemul.library for the Amiga.
 *  Copyright (C) 1991, 1992  Markus M. Wild
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#define KERNEL
#include "ixemul.h"

#include <exec/execbase.h>

#ifdef DEBUG
#define DP(a) kprintf a
#else
#define DP(a)
#endif

/* jump to pc in supervisor mode, usp is set to USP before */
extern void volatile supervisor (u_int pc, u_int usp);

/* context restore functions for 68000 and 68020 rsp */
extern void volatile restore_00 (), restore_20 ();

/* takes the sigcontext * from the usp and restores it 
 * Assumes it's called by Supervisor(), ie. with an exception frame
 */
extern void volatile do_sigreturn (void);

/*
 * These two are callable with jsr from supervisor mode, and then
 * set up a fake exception frame and call do_sigreturn().
 */
extern void volatile sup00_do_sigreturn_ssp (u_int ssp);
extern void volatile sup20_do_sigreturn_ssp (u_int ssp);
/*
 * These two work the same, but they don't touch the ssp
 */
extern void volatile sup00_do_sigreturn (void);
extern void volatile sup20_do_sigreturn (void);

/*
 * Either one of sup{00,20}_do_sigreturn, set by configure_context_switch ();
 */
static void volatile (*sup_do_sigreturn) (void);
static void volatile (*sup_do_sigreturn_ssp) (u_int ssp);

void setrun (struct Task *t);
void sendsig(struct user *p, sig_t catcher, int sig, int mask, unsigned code, void *addr);
void sig_exit ();

/*
 * NOTE: this variable belongs to ixemulbase, not to the current task. But
 *       due to the nature of launch/switch, we can access them here without
 *       locking. It quite a natural thing to do actually ;-)
 */
struct user *curproc;

struct sigframe {
  int			sf_signum;	/* signo for handler */
  int			sf_code;	/* additional info for handler */
  void			*sf_addr;	/* yet another info for handler ;-)) */
  sig_t			sf_handler;	/* handler addr for u_sigc */
  struct sigcontext 	sf_sc;		/* actual context */
};


void
configure_context_switch ()
{
  struct ExecBase *SysBase = *(struct ExecBase **)4;

  if (SysBase->AttnFlags & AFF_68020)
    {
      sup_do_sigreturn = sup20_do_sigreturn;
      sup_do_sigreturn_ssp = sup20_do_sigreturn_ssp;
    }
  else
    {
      sup_do_sigreturn = sup00_do_sigreturn;
      sup_do_sigreturn_ssp = sup00_do_sigreturn_ssp;
    }
}

void volatile
sigreturn (struct sigcontext *sc)
{
DP(("sigreturn: sc = $%lx\n", sc));

  supervisor ((u_int) do_sigreturn, (u_int) sc);
}

void volatile
sig_trampoline (struct sigframe sf)
{
DP(("sig_trmp: sf @$%lx, sig = %ld, code = %ld, addr = %ld, handler = $%lx, sigex=$%lx\n", &sf, sf.sf_signum, sf.sf_code, sf.sf_addr, sf.sf_handler, sig_exit));

  sf.sf_handler (sf.sf_signum, sf.sf_code, sf.sf_addr, & sf.sf_sc);

DP(("sig_trampoline: handler returned.\n"));

  sigreturn (& sf.sf_sc);
}

/*
 * This one is executed in Supervisor mode, just before dispatching this
 * task, so be as quick as possible here !
 */
void
sig_launch () 
{
  struct ExecBase	*SysBase 	= *(struct ExecBase **) 4;
  struct Task 		*me 		= SysBase->ThisTask;
  /* precalculate struct user, so we don't have to go thru SysBase all the time */
  struct user 		*p 		= (struct user *) me->tc_TrapData;
  sigset_t 		sigmsg 		= sigmask (SIGMSG);
  sigset_t		sigint 		= sigmask (SIGINT);
  sigset_t 		newsigs;
  int 			i;
  u_int			usp, orig_usp;
  struct sigcontext 	*sc;
  u_int			ret_pc, ret_ssp;

  usp = orig_usp = get_usp () + 8; 	/* set up by our glue_launch() stub */
  
  /* remember who we are */
  curproc = p;

  /* the glue passes us the values of the pc and ssp to restore, if we should
   * decide to sup_do_sigreturn_ssp() out of here, instead of leaving harmlessly..
   */
  ret_pc  = ((u_int *)usp)[-2];
  ret_ssp = ((u_int *)usp)[-1];
  
#if 0
/* not enabled by default, generates way too much information for my poor vt100... */

DP(("sig_launch: usp = $%lx, ret_pc = $%lx, ret_ssp = $%lx\n", usp, ret_pc, ret_ssp));
#endif

  /* push a sigcontext that will get us back if no other signals
   * were produced */
  usp -= sizeof (struct sigcontext);
  sc = (struct sigcontext *) usp;
  set_usp (usp);
  
  sc->sc_onstack = p->u_onstack;
  sc->sc_mask	 = p->p_sigmask;
  sc->sc_sp	 = orig_usp;
  /* the OS context restore function expects a5 to contain the usp, so
   * we have to obey.. */
  sc->sc_fp	 = orig_usp;
  sc->sc_ap	 = *(u_int *)&me->tc_Flags;
  sc->sc_pc	 = ret_pc;
  sc->sc_ps	 = get_sr ();
  
  /*
   * first check amigados signals. If SIGMSG is set to SIG_IGN or SIG_DFL, 
   * we do our default mapping of SIGBREAKF_CTRL_C into SIGINT.
   */
  newsigs	  = me->tc_SigRecvd &~ p->u_lastrcvsig;
  p->u_lastrcvsig = me->tc_SigRecvd;

  if (((p->p_sigignore & sigmsg) || !(p->p_sigcatch & sigmsg)) 
      && (newsigs & SIGBREAKF_CTRL_C))
    {
DP(("  found SIGBREAKF_CTRL_C, sending us SIGINT\n"));
      /* in that case send us a SIGINT, if it's not ignored */
      if (!(p->p_sigignore & sigint))
        _psignal (me, SIGINT);
        
      /* in this mode we fully handle and use SIGBREAKF_CTRL_C, so remove it
       * from the Exec signal mask */
      me->tc_SigRecvd &= ~SIGBREAKF_CTRL_C;
      p->u_lastrcvsig &= ~SIGBREAKF_CTRL_C;
    }
  else if (newsigs && (p->p_sigcatch & sigmsg))
    {
      /* if possible, deliver the signal directly to get a code argument */
      if (!(p->p_flag & STRC) && !(p->p_sigmask & sigmsg))
        {
DP(("  generating SIGMSG\n"));
          p->u_ru.ru_nsignals++;
          sendsig(p, p->u_signal[SIGMSG], SIGMSG, p->p_sigmask, newsigs, 0);
          p->p_sigmask |= p->u_sigmask[SIGMSG] | sigmsg;
          setrun (me);
        }
      else
        _psignal (me, SIGMSG);
    }

  /* stack watch.. NEVER do this when vforked, the sp is out of bounds then.. */
  if (ix.ix_watch_stack && p->u_red_zone && ! p->p_vfork_msg && ! p->u_onstack)
    {
      if (usp < p->u_red_zone)
        _psignal (me, SIGSEGV);
    }

  /* now deal with `real' signals */
  if (i = CURSIG(p))
    {
DP(("  processing signal %ld.\n", i));
      psig (p, i);
    }

  /* now try to optimize. We could always call sup_do_sigreturn here, but if no
   * signals generated frames, we can just as well simply return, after having
   * restored our usp */
  if (usp == get_usp ())
    {
      /* this is probably not even necessary, since after processing sig_launch
       * the OS reinstalls the usp as me->tc_SPReg, but I guess it's cleaner to
       * do it explicitly here, to show that we reset usp to what it was before
       */
      set_usp (orig_usp);
      return;
    }

  sup_do_sigreturn_ssp (ret_ssp);
}


/*
 * nothing magic about this... when leaving, we note that no process is currently
 * executing (at least none under our control ;-))
 */

void
switch_glue ()
{
  curproc = 0;
}


/*
 * Send an interrupt to process.
 * Called from psig() which is called from sig_launch, thus we are in
 * SUPERVISOR .
 */
void
sendsig (struct user *p, sig_t catcher, int sig, int mask, unsigned code, void *addr)
{
  struct ExecBase	*SysBase 	= *(struct ExecBase **) 4;
  struct Task		*me		= SysBase->ThisTask;
  u_int 		usp, orig_usp;
  struct sigframe 	*sf;
  struct sigcontext	*sc;
  int			oonstack;

  orig_usp = get_usp();	/* get value to restore later */

  oonstack = p->u_onstack;

  if (!p->u_onstack && (p->u_sigonstack & sigmask(sig)))
    {
      p->u_onstack = 1;
      usp = (u_int) p->u_sigsp;
    }
  else
    usp = orig_usp;
  
  /* push signal frame */
  usp -= sizeof (struct sigframe);
  sf = (struct sigframe *) usp;
  
  /* fill out the frame */
  sf->sf_signum        = sig;
  sf->sf_code          = code;
  sf->sf_addr	       = addr;
  sf->sf_handler       = catcher;
  sf->sf_sc.sc_onstack = oonstack;
  sf->sf_sc.sc_mask    = mask;
  sf->sf_sc.sc_sp      = (int) orig_usp;	/* previous sigcontext */
  sf->sf_sc.sc_fp      = 0;
  sf->sf_sc.sc_ap      = *(u_int *)&me->tc_Flags;
  sf->sf_sc.sc_ps      = get_sr ();		/* we're in supervisor then */
  sf->sf_sc.sc_pc      = (int) sup_do_sigreturn;/* this pc will restore it */

  /* push a signal context to call sig_trampoline */
  usp -= sizeof (struct sigcontext);
  sc = (struct sigcontext *) usp;

  /*
   * NOTE: we set the default of a handler to Permit(), Enable(). I guess this
   *       makes sense, since if either Forbid() or Disable() is active, it
   *	   shouldn't be possible to invoke a signal anyway, EXCEPT if the
   *	   task is Wait()ing, then the OS calls Switch() directly while
   *	   either Disable() or Forbid() is active (depends on OS version).
   */

  sc->sc_onstack = p->u_onstack;
  sc->sc_mask    = p->p_sigmask;
  sc->sc_sp	 = ((int) sf) - 4; /* so that sp@(4) is the argument */
  sc->sc_fp	 = 0;
  sc->sc_ap	 = (me->tc_Flags << 24) | (me->tc_State << 16) |
  		   ((u_char)(-1) << 8) | (u_char)(-1);
  sc->sc_ps	 = 0;	/* thus we switch into user-mode now! */
  sc->sc_pc	 = (int) sig_trampoline;
  
  set_usp (usp);
}


/*
 * called as the default action of a signal that terminates the process
 */
void
sig_exit (unsigned int code)
{
#ifndef QUIET_SIGEXIT
  /* the whole purpose of this code inside `ifndef QUIET_SIGEXIT' is to
   * prettyprint and identify the job that just terminates
   * This stuff should be handled by a shell, but since there's (yet) no
   * shell that knows how to interpret a signal-exit code (ored with 0x80)
   * I have to do it here myself...
   */

  extern char *sys_siglist[NSIG];
  struct Process *me = (struct Process *)((*(struct ExecBase **)4)->ThisTask);
  char err_buf[255];
  struct CommandLineInterface *cli;
  char process_name[255];
  int is_fg;

  /* output differs depending on
   *  o  whether we're a CLI or a WB process (stderr or requester)
   *  o  whether this is a foreground or background process
   *  o  whether this is SIGINT or not
   */

DP(("SIG_EXIT:"));

  /* make sure we're not interrupted in this last step.. */
  syscall (SYS_sigsetmask, ~0);
  
  if (cli = BTOCPTR (me->pr_CLI))
    {
      char *tmp = BTOCPTR (cli->cli_CommandName);
      int   len = *tmp++;
      
      if (len > sizeof (process_name) - 1)
        len = sizeof (process_name) - 1;
      
      bcopy (tmp, process_name, len);
      process_name[len] = 0;
      
      is_fg = cli->cli_Interactive && !cli->cli_Background;

DP(("  cli-proc, is_fg = %ld, name = %s.\n", is_fg, process_name));
    }
  else
    {
      process_name[0] = 0;
      if (me->pr_Task.tc_Node.ln_Name)
        strncpy (process_name, me->pr_Task.tc_Node.ln_Name, sizeof (process_name) - 1);
        
      /* no WB process is ever considered fg */
      is_fg = 0;

DP(("  wb-proc, name = %s\n", process_name));
    }

  /* if is_fg and SIGINT, simulate tty-driver and display ^C */
  if (!(is_fg && (code == SIGINT)))
    {
      strcpy (err_buf, (code < NSIG) ? sys_siglist[code] : "Unknown signal");

DP(("  err_buf = %s\n", err_buf));

      /* if is_fg, don't display the job */
      if (! is_fg)
        {
          strcat (err_buf, " - ");
	  strcat (err_buf, process_name);
          /* if we're a CLI we have an argument line saved, that we can print
           * as well */
	  if (cli)
      	    {
	      int line_len;
	      char *cp;
	      
	      /* we can display upto column 77, this should be save on all normal
	       * amiga CLI windows */
	      line_len = 77 - strlen (err_buf) - 1;
	      if (line_len > u.u_arglinelen)
	        line_len = u.u_arglinelen;

	      if (line_len > 0 && u.u_argline)
	        {
	          strcat (err_buf, " ");
		  strncat (err_buf, u.u_argline, line_len);
		}

	      /* now get rid of possible terminating line feeds/cr's */
	      for (cp = err_buf; *cp && *cp != '\n' && *cp != '\r'; cp++) ;
	      *cp = 0;
	    }
	}

      if (cli)
        {
          /* uniformly append ONE line feed */
	  strcat (err_buf, "\n");
DP(("  2err_buf = %s\n", err_buf));
          syscall (SYS_write, 2, err_buf, strlen (err_buf));
        }
      else
        ix_panic (err_buf);
    }
  else
    syscall (SYS_write, 2, "^C\n", 3);
#endif  

  syscall (SYS_exit, code | 0x80);
  /* not reached */
}

/*
 * This is used to awaken a possibly sleeping sigsuspend()
 * and to force a context switch, if we send a signal to ourselves
 */
void
setrun (struct Task *t)
{
  struct user *p = (struct user *) t->tc_TrapData;
  struct ExecBase *SysBase = *(struct ExecBase **)4;
  u_int curr_disp;
  u_int	sr;
DP(("setrun ()\n"));

  if (t->tc_SigWait & (1 << p->u_sleep_sig))
    Signal (t, (1 << p->u_sleep_sig));

  if (SysBase->ThisTask != t) return;
  
  /* NOTE: the context switch is done to make sure sig_launch() is called as
   *       soon as possible in the respective task. It's not nice if you can
   *       return from a kill() to yourself, before the signal handler had a
   *       chance to react accordingly to the signal..
   */
  asm volatile (" 
    movel a5,a0
    lea	  pc@(Lget_sr-.+2),a5
    movel 4:w,a6
    jsr	  a6@(-0x1e)
    movel a1,%0
    bra	  Lskip
Lget_sr:
    movew sp@,a1	| get sr register from the calling function
    rte
Lskip:
    movel a0,a5
	" : "=g" (sr) : : "a0", "a1", "a6");

  if (sr & 0x2000) return; /* if in Supervisor, don't do this... */

  for (curr_disp = SysBase->DispCount; curr_disp == SysBase->DispCount; ) ;
  /* this quite brute-force method, but the only thing I could think off that
   * really guarantees that there was a context switch.. */
}

/*
 * Mapping from vector numbers into signals
 */
const static int hwtraptable[256] = {
  SIGILL, /* Reset initial stack pointer */
  SIGILL, /* Reset initial program counter */
  SIGBUS, /* Bus Error */
  SIGBUS, /* Address Error */
  SIGILL, /* Illegal Instruction */
  SIGFPE, /* Zero Divide */
  SIGFPE, /* CHK, CHK2 Instruction */
  SIGFPE, /* cpTRAPcc, TRAPcc, TRAPV Instruction */
  SIGILL, /* Privilege Violation */
  SIGTRAP,/* Trace */
  SIGEMT, /* Line 1010 Emulator */
  SIGEMT, /* Line 1111 Emulator */
  SIGILL,
  SIGILL, /* Coprocessor Protocol Violation */
  SIGILL, /* Format Error */
  SIGILL, /* Uninitialized Interrupt */
  SIGILL, /* 16 */
  SIGILL, /* 17 */
  SIGILL, /* 18 */
  SIGILL, /* 19 */		/* unimplemented, reserved */
  SIGILL, /* 20 */
  SIGILL, /* 21 */
  SIGILL, /* 22 */
  SIGILL, /* 23 */
  SIGILL, /* spurious Interrupt */
  SIGILL, /* Level 1 Interrupt Autovector */
  SIGILL, /* Level 2 Interrupt Autovector */
  SIGILL, /* Level 3 Interrupt Autovector */
  SIGILL, /* Level 4 Interrupt Autovector */
  SIGILL, /* Level 5 Interrupt Autovector */
  SIGILL, /* Level 6 Interrupt Autovector */
  SIGILL, /* Level 7 Interrupt Autovector */
  SIGILL, /* Trap #0 (not available on Unix) */
  SIGILL, /* Trap #1 */
  SIGILL, /* Trap #2 */
  SIGILL, /* Trap #3 */
  SIGILL, /* Trap #4 */
  SIGILL, /* Trap #5 */
  SIGILL, /* Trap #6 */
  SIGILL, /* Trap #7 */
  SIGILL, /* Trap #8 */
  SIGILL, /* Trap #9 */
  SIGILL, /* Trap #10 */
  SIGILL, /* Trap #11 */
  SIGILL, /* Trap #12 */
  SIGILL, /* Trap #13 */
  SIGILL, /* Trap #14 */
  SIGILL, /* Trap #15 (not available on Unix) */
  SIGFPE, /* FPCP Branch or Set on Unordererd Condition */
  SIGFPE, /* FPCP Inexact Result */
  SIGFPE, /* FPCP Divide by Zero */
  SIGFPE, /* FPCP Underflow */
  SIGFPE, /* FPCP Operand Error */
  SIGFPE, /* FPCP Overflow */
  SIGFPE, /* FPCP Signaling NAN */
  SIGILL,
  SIGBUS, /* MMU Configuration Error */
  SIGILL, /* MMU Illegal Operation (only 68851) */
  SIGILL, /* MMU Privilege Violation (only 68851) */
  /* rest undefined or free user-settable.. */
};

/*
 * handle traps handled over from the lowlevel trap handlers
 */
void
trap (u_int format, void *addr)
{
  struct ExecBase	*SysBase 	= *(struct ExecBase **) 4;
  struct Task 		*me 		= SysBase->ThisTask;
  /* precalculate struct user, so we don't have to go thru SysBase all the time */
  struct user 		*p 		= (struct user *) me->tc_TrapData;
  int 			sig;
  u_int			usp, orig_usp;
  struct sigcontext 	*sc;
#if __GNUC__ != 2 || defined(BROKEN_GCC20)
  int volatile 		been_here;
#endif
  u_int			ret_pc, ret_ssp;

extern void restore_20();

  usp = orig_usp = get_usp () + 8;	/* skip argument parameters */
  ret_pc  = ((u_int *)usp)[-2];
  ret_ssp = ((u_int *)usp)[-1];
  
DP(("trap: format = $%lx, addr = $%lx, ret_pc = $%lx, ret_ssp = $%lx, rst20 = $%lx\n", format, addr, ret_pc, ret_ssp, restore_20));

  /* push a sigcontext that will get us back here if no other signals
   * were produced */
  usp -= sizeof (struct sigcontext);
  sc = (struct sigcontext *) usp;
  set_usp (usp);

  sc->sc_onstack = p->u_onstack;
  sc->sc_mask	 = p->p_sigmask;
  sc->sc_sp	 = orig_usp;
  sc->sc_fp	 = get_fp ();
  sc->sc_ap	 = *(u_int *)&me->tc_Flags;
  sc->sc_pc	 = ret_pc;
  sc->sc_ps	 = get_sr ();

  /* format contains the vector * 4, in the lower 12 bits */
  sig = *(int *)((u_char *)hwtraptable + (format & 0x0fff));

  trapsignal (me, sig, format, addr);

  if (sig = CURSIG(p))
    psig (p, sig);

  /* now try to optimize. We could always call sup_do_sigreturn here, but if no
   * signals generated frames, we can just as well simply return, after having
   * restored our usp */
  if (usp == get_usp ())
    {
      set_usp (orig_usp);
      return;
    }

  sup_do_sigreturn_ssp (ret_ssp);
}
