/*
    TMHal.c, Copyright  (c) by George Fankhauser,
    Swiss Federal Institute of Technology,
    Computer Engineering and Networks Laboratory.
 
    TOPSY -- A Teachable Operating System.
             Implementation of a tiny and simple
             micro kernel for teaching purposes.
 
    For further information, please visit http://www.tik.ee.ethz.ch/~topsy
 
    This software is provided under the terms of the GNU General Public Licence.
 
    A full copy of the GNU GPL is provided in the file COPYING found in the 
    development root of Topsy.
*/
/*
	Revision 1.11  1999/10/23 19:07:16  jeker
	added Makefile.SimOS.mips and did some code cleanup
	
	Revision 1.10  1999/10/22 19:22:34  gfa
	*** empty log message ***
 
	Revision 1.7  1999/06/06 20:55:23  jeker
	putting everything together for Topsy 2.0
 
	Revision 1.6  1999/05/29 14:32:08  gfa
	*** empty log message ***
 
	Revision 1.5  1999/05/19 14:30:46  gfa
	ome linux tweakin' done...
 
	Revision 1.4  1999/05/19 11:43:20  gfa
	new link script for linux
 
	Revision 1.3  1999/05/13 17:05:55  jeker
	Initial revision
 
*/

#include "TMHal.h"
#include "IOHal.h"
#include "Support.h"
#include "TMClock.h"
#include "TMScheduler.h"
#include "TMIPC.h"

#include "Unix.h"

ExceptionHandler exceptionHandlerTable[MAXNBOFEXCEPTIONS+1];

InterruptHandler interruptHandlerTable[MAXNBOFINTERRUPTS];
void* interruptHandlerArgTable[MAXNBOFINTERRUPTS];

static struct ucontext* signal_context = NULL;
static int signal_no;
#ifdef __sparc__
static char* gwin_save_area[SPARC_MAXREGWINDOW * sizeof(struct rwindow)];
#endif

// memory protection stuff
void setKernelProtected(Boolean mode)
{
  int prot = 0;
  //int size = sysconf(_SC_PAGESIZE);

  if (mode)
  {
    prot = PROT_WRITE | PROT_EXEC; // since we need to restore after
    // protecting we can't use read
  }
  else
  {
    prot = PROT_NONE;
  }
  mprotect(0x00000000, USERBRKAT, prot);  // where's kernel space?
  // mprot from 0 to user start
}


void saveContext(ProcContextPtr contextPtr)
{
  // copy context from signal stack to thread's processor context

#ifdef __sparc__
  if (signal_context->uc_mcontext.gwins != NULL)
  {
    PANIC("save gwin...");
    byteCopy(gwin_save_area, (char*)signal_context->uc_mcontext.gwins,
             sizeof(gwin_save_area));
  }
#endif

  byteCopy(&(contextPtr->uc), signal_context, sizeof(struct ucontext));

  setKernelProtected(FALSE);
}


void restoreContext(ProcContextPtr contextPtr)
{
#ifdef __SOLARIS__ // really is: has memProtect and setContext...
  if (THREAD_MODE(schedulerRunning()->id) == USER)
  {
    setKernelProtected(TRUE);
  }
#ifdef __sparc__
  contextPtr->uc.uc_mcontext.gregs[REG_PSR] |= 1;
  if (contextPtr->uc.uc_mcontext.gwins != NULL)
  {
    PANIC("sparc gwin restore needed...");
    byteCopy((char*)contextPtr->uc.uc_mcontext.gwins, gwin_save_area,
             sizeof(gwin_save_area));
  }
#endif
  // solaris can restore a user/sigcontext explicitly
  setcontext( &(contextPtr->uc));
#endif

#ifdef __linux__

  struct __jmp_buf_tag buf;

  if (signal_context != NULL)
  {
    // replace sigcontext on stack by context to be restored by linux
    //byteCopy(signal_context, &(contextPtr->uc), sizeof(struct ucontext));

    {
      signal_context->uc_mcontext.gregs[GS]=contextPtr->uc.uc_mcontext.gregs[GS];
      signal_context->uc_mcontext.gregs[FS]=contextPtr->uc.uc_mcontext.gregs[FS];
      signal_context->uc_mcontext.gregs[ES]=contextPtr->uc.uc_mcontext.gregs[ES];
      signal_context->uc_mcontext.gregs[DS]=contextPtr->uc.uc_mcontext.gregs[DS];

      signal_context->uc_mcontext.gregs[EDI]=contextPtr->uc.uc_mcontext.gregs[EDI];
      signal_context->uc_mcontext.gregs[ESI]=contextPtr->uc.uc_mcontext.gregs[ESI];

      signal_context->uc_mcontext.gregs[EBP]=contextPtr->uc.uc_mcontext.gregs[EBP];
      signal_context->uc_mcontext.gregs[ESP]=contextPtr->uc.uc_mcontext.gregs[ESP];

      signal_context->uc_mcontext.gregs[EBX]=contextPtr->uc.uc_mcontext.gregs[EBX];
      signal_context->uc_mcontext.gregs[EDX]=contextPtr->uc.uc_mcontext.gregs[EDX];
      signal_context->uc_mcontext.gregs[ECX]=contextPtr->uc.uc_mcontext.gregs[ECX];
      signal_context->uc_mcontext.gregs[EAX]=contextPtr->uc.uc_mcontext.gregs[EAX];

      signal_context->uc_mcontext.gregs[TRAPNO]=contextPtr->uc.uc_mcontext.gregs[TRAPNO];
      signal_context->uc_mcontext.gregs[ERR]=contextPtr->uc.uc_mcontext.gregs[ERR];
      signal_context->uc_mcontext.gregs[EIP]=contextPtr->uc.uc_mcontext.gregs[EIP];
      signal_context->uc_mcontext.gregs[CS]=contextPtr->uc.uc_mcontext.gregs[CS];
      signal_context->uc_mcontext.gregs[EFL]=contextPtr->uc.uc_mcontext.gregs[EFL];
      signal_context->uc_mcontext.gregs[UESP]=contextPtr->uc.uc_mcontext.gregs[UESP];
      signal_context->uc_mcontext.gregs[SS]=contextPtr->uc.uc_mcontext.gregs[SS];
    }

    // sigreturn(dummy); // implicit syscall by stack frame signal code
  }
  else
  {  // the very first context is not on a signal stack and needs
    // to jump explicitely
    buf.__jmpbuf[JB_BX] = (int)contextPtr->uc.uc_mcontext.gregs[EBX];
    buf.__jmpbuf[JB_SI] = (int)contextPtr->uc.uc_mcontext.gregs[ESI];
    buf.__jmpbuf[JB_DI] = (int)contextPtr->uc.uc_mcontext.gregs[EDI];
    buf.__jmpbuf[JB_BP] = (int)contextPtr->uc.uc_mcontext.gregs[EBP];
    buf.__jmpbuf[JB_SP] = (int)contextPtr->uc.uc_mcontext.gregs[ESP];
    buf.__jmpbuf[JB_PC] = (int)contextPtr->uc.uc_mcontext.gregs[EIP];
    longjmp(&buf, 0);
  }
#endif
  // does not return...
}

/* general signal/exception handler */
void generalExceptionHandler(int signo, siginfo_t* info, struct ucontext* uc)
{
  signal_context = uc;
  signal_no = signo;

  saveContext(schedulerRunning()->contextPtr);
  {
    // dispatch signal (exception)
    exceptionHandlerTable[signo](schedulerRunning()->id);
  }
  restoreContext(schedulerRunning()->contextPtr);
}


void syscallExceptionHandler(ThreadId from)
{
  // grabbing parameter of tmMsgSend/Recv from calling thread's stack

  Message* msg;

#ifdef i386

  greg_t* g = signal_context->uc_mcontext.gregs;

  if (signal_no == SEND)
  {
    // for tmMsgSend the user stack is shorter (2 args)
    msg = (Message*)(*(int*)(g[ESP]+40));   // msgptr (arg1)
    msg->from = *(ThreadId*)(g[ESP]+36);    // to (arg0)
    msgDispatcher(  from,	                // sending thread
                    msg,
                    0,                      // timeout (not used for send)
                    SYSCALL_SEND_OP);       // send op id
  }
  else if (signal_no == RECV)
  {
    // for tmMsgRecv the user stack is a little longer (4 args)
    msg = (Message*)(*(int*)(g[ESP]+60));   // msgptr (arg2)
    msgDispatcher(  from,                   // recv threadid
                    msg,
                    *(int*)(g[ESP]+64),     // timeout (arg3)
                    SYSCALL_RECV_OP);       // recv op
  }
  // on return of msgDispatcher (TMIPC.c), the syscall return value
  // is written into the contexts mc.g[EAX] register with tmSetReturnRegister
#endif

#ifdef sparc
  greg_t* g = signal_context->uc_mcontext.gregs;

  if (signal_no == SEND)
  {
    msg = (Message*)(*(int*)(g[REG_SP]+36));      // msgptr (arg1)
    msg->from=(ThreadId)(*(int*)(g[REG_SP]+32));  // to (arg0)
    msgDispatcher(  from,	      		      // sending thread
                    msg,
                    0, 			      // timeout (not for send)
                    SYSCALL_SEND_OP);	      // send op id
  }
  else if (signal_no == RECV)
  {
    msg = (Message*)(*(int*)(g[REG_SP]+40)); 	// msgptr (arg2)
    msgDispatcher(  from,				// receiving threadid
                    msg,
                    *(int*)(g[REG_SP]+44), 		// timeout (arg3)
                    SYSCALL_RECV_OP);		// recv op
  }
  // on return of msgDispatcher (TMIPC.c), the syscall return value
  // is written into the context's return register with tmSetReturnRegister
#endif
}


/*
 * This code should run in kernel and in user space. Thus no relative
 * calls (across address spaces) must be used. A simple, however non-portable
 * way to do this is a small piece of assembly that traps twice to unix.
 * A C version would be kill(getpid(), SIGEMT);
 */
void automaticThreadExit()
{
#ifdef i386
  asm("movl  $0x14,%eax");   // getpid (unix syscall)
  asm("int   $0x80    ");    // pid in eax
  asm("movl  %eax,0x8(%esp,1)");
  asm("movl  $31,0x4(%esp,1)");  // SIGEMT (or UNUSED) as exit msg
  asm("movl  $0x25,%eax");   // SIGEMT in eax
  asm("int   $0x80    ");    // kill (unix syscall)
#endif

#ifdef sparc

  asm(" mov  0x14, %g1");   // getpid (solaris syscall)
  asm(" ta  8");            // pid in i0
  asm(" mov 0x7, %o1");     // SIGEMT is used as exit msg replacement
  asm(" mov  %o1, %i1");    // SIGEMT in i1
  asm(" mov  0x25, %g1");   // kill (solaris syscall)
  asm(" ta  8");
#endif

#ifdef mips

  asm("mov  a0, 0x14");     // getpid (irix syscall)
  asm("syscall");           // pid in v0 returned
  asm("mov  a1, v0");
  asm("mov  a2, 0x7");      // SIGEMT is used as exit msg replacement
  asm("mov  a0, 0x25");     // SIGEMT in a0
  asm("syscall");           // kill (irix syscall)
#endif
  // as of here we should be dead
}
void endAutomaticThreadExit()
{} // is there another way to do labels in C?!


InterruptHandler tmSetInterruptHandler( InterruptId intId,
                                        InterruptHandler intHandler,void* arg)
{
  // no interrupts in unix processes :-)
  PANIC("no interrupts in unix processes");
  return intHandler;
}


ExceptionHandler tmSetExceptionHandler( ExceptionId excId,
                                        ExceptionHandler excHandler)
{
  ExceptionHandler prev;
#if defined( __svr4__ ) || defined ( linux )

  struct sigaction act;
  sigset_t set;

  sigfillset(&set);
  sigdelset(&set, SIGILL);
  sigdelset(&set, SIGTRAP);
#ifndef linux

  sigdelset(&set, SIGEMT);
#endif

  sigdelset(&set, SIGFPE);
  sigdelset(&set, SIGBUS);
  sigdelset(&set, SIGSEGV);

  // all signals handled by the same exception dispatcher
  // this is similar to the MIPS exception model

  act.sa_sigaction = generalExceptionHandler;
#ifdef linux

  act.sa_flags = SA_SIGINFO;
#else

  act.sa_flags = SA_ONSTACK|SA_SIGINFO;
#endif

  act.sa_mask = set;
  sigaction(excId, &act, NULL);

#else

  struct sigvec sv;

  sv.sv_handler = generalExceptionHandler;
  sv.sv_flags = SV_ONSTACK;
  sv.sv_mask = SIGALRM;
  sigvec(excId, &sv, NULL);   // all signals caught by same function

#endif

  prev = exceptionHandlerTable[excId];
  exceptionHandlerTable[excId] = excHandler;  // signal dispatcher
  // uses this table
  return prev;
}


void tmInstallExceptionCode()
{
  /* not needed in unix */
}

void tmSetReturnValue(ProcContextPtr contextPtr, Register value)
{
  SETRETURNVALUE(contextPtr, value);
}

void tmSetStackPointer(ProcContextPtr contextPtr, Register value)
{
  SETSTACKPOINTER(contextPtr, value);
}

void tmSetProgramCounter(ProcContextPtr contextPtr, Register value)
{
  SETPROGRAMCOUNTER(contextPtr, value);
}

void tmSetStatusRegister(ProcContextPtr contextPtr, Register value)
{
  SETSTATUSREGISTER(contextPtr, value);
}

void tmSetReturnAddress(ProcContextPtr contextPtr, Register value)
{
  SETRETADDR(contextPtr, value);
}

void tmSetFramePointer(ProcContextPtr contextPtr, Register value)
{
  SETFRAMEPOINTER(contextPtr, value);
}

void tmSetArgument0(ProcContextPtr contextPtr, Register value)
{
  SETARGUMENT0(contextPtr, value);
}

void tmSetArgument1(ProcContextPtr contextPtr, Register value)
{
  SETARGUMENT1(contextPtr, value);
}

void tmSetMachineDependentRegisters(ProcContextPtr contextPtr, AddressSpace space)
{
#ifdef i386
  // find out which segments are used in this user process
  // we will reuse it for all topsy threads
  // no idea how to get these index register values
  // (besides inline asm)...
  contextPtr->uc.uc_mcontext.gregs[SS] = 43;
  contextPtr->uc.uc_mcontext.gregs[CS] = 35;
  contextPtr->uc.uc_mcontext.gregs[DS] = 43;
  contextPtr->uc.uc_mcontext.gregs[ES] = 43;
#endif
#ifdef sparc
  // get a full sample
  getcontext(&(contextPtr->uc));
#endif
}

void enableInterruptInContext( InterruptId intId, ProcContextPtr contextPtr)
{
  // not needed, unix version runs always in user mode, even the kernel :-)
}

void disableInterruptInContext( InterruptId intId, ProcContextPtr contextPtr)
{
  // not needed, unix version runs always in user mode, even the kernel :-)
}

void enableAllInterruptsInContext( ProcContextPtr contextPtr)
{
  // not needed, unix version runs always in user mode, even the kernel :-)
}

Register tmGetProgramCounter(ProcContextPtr contextPtr)
{
  GETPROGRAMCOUNTER(contextPtr);
}

Register tmGetFaultingAddress(ProcContextPtr contextPtr)
{
  GETFAULTINGADDRESS(contextPtr);
}

void msgAdjust(ThreadPtr pFromOrToThread, Message *msg, Boolean ToKernel)
{
  // not needed in unix port
}

void powerSaver(void)
{
  // not needed, unix version runs always :-)
}

/* LR 20010213: adjust Machine Dependencies at the end  */
void tmAdjustMachineDependentRegisters(ProcContextPtr contextPtr, AddressSpace space)
{

  // not really needed here
}
