/*
    TMHal.c, Copyright  (c) by Christian Conrad,
    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.
*/
/*
	
	File:                  $Source: /home/ruf/cvs_Topsy/cvs/Topsy/Threads/mips/TMHal.c,v $
 	Author(s):             Christian Conrad
 	Affiliation:           ETH Zuerich, TIK
 	Version:               $Revision: 2936 $
 	Creation Date:         
 	Last Date of Change:   $Date: 2007-03-21 15:23:42 +0100 (Mit, 21 Mär 2007) $      by: $Author: baderma $
	
*/

#include "Assert.h"
#include "TMHal.h"
#include "TMIPC.h"
#include "IOHal.h"
#include "Support.h"
#include "SupportMIPS.h"
#include "TMClock.h"
#include "cpu.h"
#include "tlb.h"
#include "TMProtectionDomain.h"
#include "MemoryLayout.h"
#include "TMScheduler.h"

#include "vm.h"
#include "vm_pmap.h"

/* until now these functions are only used in here */
Register tmGetProgramCounter(ProcContextPtr contextPtr);
void intDispatcher( Register causeReg);

/* this on gets calld form an assembler function */
void exceptionDispatcher(
  Thread* currentThread, 		/* thread that caused exception */
  Message* msgPtr,					/* message reference only used on syscalls */
  unsigned long int timeout,/* timeout for receiving only used by tmMsgRecv */
  MsgOpCode code,						/* SYSCALL_SEND_OP/SYSCALL_RECV_OP */
  Register cause,						/* unmasked cause register */
  Register badVA,						/* badVA (only valid for TLB exceptions */
  Scheduler *pseudo);				/* pseudo schduler struct, to access context */


/* Exception service branch table (exception handlers).
 * - 16 exception handlers,
 * - 16 reserved (not set)
 */
ExceptionHandler exceptionHandlerTable[MAXNBOFEXCEPTIONS];

/* Hardware interrupt service branch table (interrupt handlers) and
 * corresponding arguments that may be passed to the interrupt handler.
 * - 6 hw interrupts
 * - 2 sw interrupts 
 */
InterruptHandler interruptHandlerTable[MAXNBOFINTERRUPTS];
void* interruptHandlerArgTable[MAXNBOFINTERRUPTS];

void tmInstallExceptionCode()
{
  /* Installation of default basic exception handlers */
  /* INSTALL generalExceptionHandler AT LOCATION 0x8000_0080 */
  if ( getPRID() == PRID_R3K )
  {
    byteCopy( (Address)VEC_EXCEPTION_R3k, generalExceptionHandler,
              endGeneralExceptionHandler-generalExceptionHandler);
  }
  else
  {
    byteCopy( (Address)VEC_EXCEPTION_R4k, generalExceptionHandler,
              endGeneralExceptionHandler-generalExceptionHandler);
  }

  /* INSTALL UTLBMissHandler AT LOCATION 0x8000_0000 */
  printf("utlbMissHandler size = %#x bytes\n", endGeneralExceptionHandler-generalExceptionHandler);
  if (endGeneralExceptionHandler-generalExceptionHandler > 0x00000080)
  {
    PANIC("utlbMissHandler larger than 0x80 bytes");
  }
  byteCopy( (Address)VEC_TLB_UMISS, generalExceptionHandler,
            endGeneralExceptionHandler-generalExceptionHandler);

  /* RESETHandler AT LOCATION 0xbfc0_0000 is in EPROMs */
}


InterruptHandler tmSetInterruptHandler( InterruptId intId,
                                        InterruptHandler intHdler, void* arg)
{
  InterruptHandler oldIntHdler = interruptHandlerTable[intId];

  if (intId == SWINT_0 || intId == SWINT_1)
  {
    interruptHandlerTable[intId-6] = intHdler;
    interruptHandlerArgTable[intId-6] = arg;
  }
  else
  {
    interruptHandlerTable[intId+2] = intHdler;
    interruptHandlerArgTable[intId+2] = arg;
  }
  return oldIntHdler;
}


ExceptionHandler tmSetExceptionHandler( ExceptionId excId,
                                        ExceptionHandler excHdler)
{
  ExceptionHandler oldExcHdler = exceptionHandlerTable[excId];

  exceptionHandlerTable[excId] = excHdler;
  return oldExcHdler;
}

static int forceFault(address_t faultaddr)
{
  PANIC("forceFault: does not work yet...");
  return *((int*)faultaddr);
}

void exceptionDispatcher(
  Thread* currentThread,		/* thread that caused exception */
  Message* msgPtr,					/* message reference only used on syscalls */
  unsigned long int timeout,/* timeout for receiving only used by tmMsgRecv */
  MsgOpCode code,						/* SYSCALL_SEND_OP/SYSCALL_RECV_OP */
  Register cause,						/* unmasked cause register */
  Register badVA,						/* badVA (only valid for TLB exceptions */
  Scheduler *pseudo)				/* pseudo schduler struct, to access context */
{
  ExceptionHandler handler;
  ProcContextPtr ctxt;
  Message* adjMsgPtr;
  unsigned int exception = 0;
  int index;
  Register tlblo;
#if 0

  extern int exceptionContextFlag;

  //-CJ	dumpMemory(&ctxt, 32, 256);
  ctxt = pseudo->running->contextPtr;
  printf("Exception[%i]: cause %#.8lx thread# % li @ [%p] badVAddr %p\n",
         exceptionContextFlag, cause,
         exceptionContextFlag==1?currentThread->id:0, /* can cause a miss */
         (address_t)tmGetProgramCounter(ctxt), (address_t) badVA);
#endif

  /* move into kernel PD -> TLB pid = kernel pseudopid */

  ASSERT( &tlblo > (address_t)EXCEPTIONSTACKBOTTOM + 0x200 );

  exception = cause & CAUSE_EXC_MASK;

  if ( exception == EXC_INT )
  {
    intDispatcher( cause );
  }
  else if ( exception != EXC_SYS )
  {
    handler = *(ExceptionHandler*)((address_t)exceptionHandlerTable + exception);
    handler(currentThread);
  }
  else
  {
    if ( cause & CAUSE_BD )
    {
      /* syscall in branch delay */
      branchDelayHandler( currentThread->id, msgPtr, timeout, code);
    }
    else
    {
      /* A SYSCALL exception occurred, this means that the return address
       * as saved in the exceptionContext must be incremented with 4
       * (if not, the SYSCALL will be generated ad aeternam !).
       */

      ctxt = pseudo->running->contextPtr;

      /* XXX XXX we are in the kernel PD therefor it is neccesary to move the
      	 page into the kernel PD (we do this directly with the TLB and map to
      	 address 0x0).
      	 XXX this will fail if the message crosses a page boundary.
       */
      if (!ISA_DIRECTMAPPED(msgPtr))
      {
        index = probeR3kTLBEntry(
                  PAGEROUNDDOWN(msgPtr) |
                  (ctxt->TLBhi&TLB_HI_PID_MASK_R3k));
        if ( index < 0 )
        {
          /* page not in TLB, start a pmap lookup */
          if (vm_pmap_lookup(&currentThread->pdPtr->vm_space.vm_pmap,
                             msgPtr, PMAP_READ) != VM_OK )
          {
            /* page not in pmap now this would need an high level lookup */
            PANIC("Message not in pmap layer");
            /* we need to switch back to old PD to force a miss,
            	 don't know if it works... -CJ does not. */
            setR3kHITLBEntry(ctxt->TLBhi&TLB_HI_PID_MASK_R3k);
            index = forceFault(&msgPtr->from); /* force tlbMiss */
            setR3kHITLBEntry(0);
          }

          index = probeR3kTLBEntry(
                    PAGEROUNDDOWN(msgPtr) |
                    (ctxt->TLBhi&TLB_HI_PID_MASK_R3k));
          if ( index < 0 )
            PANIC("destPtr still not in TLB.");
        }
        tlblo = getR3kLOTLBEntry();
      }
      else
      {
        tlblo = K0SEG_TO_PHYS(msgPtr)&TLB_LO_PFN_MASK_R3k;
        tlblo |= TLB_VALID_R3k|TLB_DIRTY_R3k;
        index = 0;
      }
      setR3kTLBEntry(tlblo, 0, 0); /* 0-7 are safe */
      /* XXX NOTE: the page containing is at address 0x0 but msgPtr points
      	 still to the old position, this is needed because kRecv needs to
      	 remember where to store the page (queue->msgPendingPtr)
       */
      //-CJ printf("SYSCALL: thread % li msgPtr %p (%p) [%i], tlblo %#lx index %i\n",
      //-CJ currentThread->id, msgPtr, (address_t)PAGEREMAINDER(msgPtr), code,
      //-CJ tlblo, index>>TLB_IND_INX_SHIFT_R3k);

      adjMsgPtr = (Message*)PAGEREMAINDER(msgPtr);

#if 0
      //-CJ used to debug loanout
      if ( adjMsgPtr->enveloppe != NULL &&
           (ctxt->TLBhi&TLB_HI_PID_MASK_R3k) != 0)
      {

        dumpMemory(adjMsgPtr, 0, sizeof(Message));

        setR3kHITLBEntry(ctxt->TLBhi&TLB_HI_PID_MASK_R3k);
        /* we need to switch back to lod PD to force a miss,
        	 don't know if it works... */
        index = forceFault(msgPtr->enveloppe); /* force tlbMiss */
        setR3kHITLBEntry(0);

        index = probeR3kTLBEntry(
                  PAGEROUNDDOWN(adjMsgPtr->enveloppe) |
                  (ctxt->TLBhi&TLB_HI_PID_MASK_R3k));
        tlblo = getR3kLOTLBEntry();
        printf("SYSCALL: msgPtr %p enveloppe %p (%p), tlblo %#lx index %i\n",
               adjMsgPtr, adjMsgPtr->enveloppe,
               (address_t)PAGEREMAINDER(adjMsgPtr->enveloppe),
               tlblo,  index>>TLB_IND_INX_SHIFT_R3k);
        setR3kTLBEntry(tlblo, 0x1000, 1); /* 0-7 are safe */
        dumpMemory(0x1000, 0, 0x1000);
        PANIC("STOP");
      }
#endif

      tmSetProgramCounter(ctxt,	tmGetProgramCounter(ctxt) + 4);

      msgDispatcher(currentThread->id, adjMsgPtr, timeout, code);

      /* XXX XXX UGLY HACK TO MAKE INTER PD IPC POSSIBLE XXX */
      /* we need to adjust the msgPendingPtr again because the page at
      	 0x0 was only temporary and will be removed now
       */
      if ( code == SYSCALL_RECV_OP && schedulerRunning() != currentThread )
      {
        //-CJ				printf("ExcDispatcher: forcing msgPendingPtr form %p to %p\n",
        //-CJ						currentThread->msgQueue.msgPendingPtr, msgPtr);
        currentThread->msgQueue.msgPendingPtr = msgPtr;
      }
      /* invalidate the entry again */
      setR3kTLBEntry(0, 0, 0); /* 0-7 are safe */
    }
  }
  /* return from exception, does not return */
  //-CJ	printf("exceptionDispatcher[%i]: about to return to %p tid %li\n",
  //-CJ		exceptionContextFlag, pseudo->running->contextPtr,
  //-CJ		exceptionContextFlag == 1?pseudo->running->id:666); /* the devil himself */
  restoreContext(pseudo->running->contextPtr);
}

void intDispatcher( Register causeReg)
{
  int offset=-1;

  causeReg &= CAUSE_IP_MASK;
  causeReg >>= CAUSE_IP_SHIFT;

  /* The invariant here is causeReg>0, otherwise an hardware
     error would have occurred */
  while (causeReg != 0)
  {
    causeReg >>= 1;
    offset += 1;
  }

  /* we call here the handler for interrupt offset with
   * its (optionally) defined argument
   */
  (interruptHandlerTable[offset])(interruptHandlerArgTable[offset]);

}

int branchDelayHandler( ThreadId fromId,     /* thread that caused exception */
                        Message* msgPtr,          /* message reference */
                        unsigned long int timeout,/* timeout for receiving */
                        MsgOpCode code)           /* SYSCALL_SEND_OP/SYSCALL_RECV_OP */
{
  Message killMsg;
  /* since we write no silly code we can assume that we never get caught
   * here. if we would allow syscalls in BD slots we would have to interpret
   * the faulting branch in software (which is not funny).
   */
  /* if (branchDelayBit == CAUSE_BD) */
  ERROR("Syscall in branch delay slot");
  killMsg.id = TM_KILL;
  killMsg.from = TMTHREADID;
  killMsg.msg.tmKill.id = fromId;
  kSend(TMTHREADID, &killMsg);
  return TM_OK;
}


void moveMessageToPD(Thread* threadPtr, Message* msgPtr, Message* destPtr)
{
  Message tmp;
  int err;
  int pseudopid;
  int index;
  Register tlblo;

  byteCopy(&tmp, msgPtr, sizeof(Message));

  if (!ISA_DIRECTMAPPED(destPtr))
  {
    pseudopid = threadPtr->pdPtr->vm_space.vm_pmap.pseudopid;

    /* XXX XXX we are in the kernel PD therefor it is neccesary to move the
    	 page into the thread PD (we do this directly with the TLB and map to
    	 address 0x0)
     */
    index = probeR3kTLBEntry( PAGEROUNDDOWN(destPtr) |
                              ((pseudopid<<TLB_HI_PID_SHIFT_R3k)&TLB_HI_PID_MASK_R3k));
    //-CJ		printf("MOVEMSG: probing %p ppid %i -> index %#x %i\n",
    //-CJ				PAGEROUNDDOWN(destPtr), pseudopid,
    //-CJ				index, index>>TLB_IND_INX_SHIFT_R3k);
    if ( index < 0 )
    {
      /* page not in TLB, start a pmap lookup */
      err = vm_pmap_lookup(&threadPtr->pdPtr->vm_space.vm_pmap,
                           destPtr, PMAP_WRITE);

      if ( err != VM_OK )
      {
        /* page not in pmap now this would need an high level lookup */
        PANIC("destination Message not in pmap");
        setR3kHITLBEntry(pseudopid&TLB_HI_PID_MASK_R3k);
        index = forceFault(&destPtr->from); /* force tlbMiss */
        setR3kHITLBEntry(0);
      }

      index = probeR3kTLBEntry( PAGEROUNDDOWN(destPtr) |
                                ((pseudopid<<TLB_HI_PID_SHIFT_R3k)&TLB_HI_PID_MASK_R3k));
      //-CJ			printf("MOVEMSG: probing %p ppid %i -> index %#x %i\n",
      //-CJ					PAGEROUNDDOWN(destPtr), pseudopid, index,
      //-CJ					index>>TLB_IND_INX_SHIFT_R3k);
      if ( index < 0 )
        PANIC("destPtr still not in TLB.");
    }
    tlblo = getR3kLOTLBEntry();

    setR3kTLBEntry(tlblo, 0, 0); /* 0-7 are safe */

    //-CJ printf("MOVEMSG: msgPtr %p destPtr %p (%p), tlblo %#lx @ %i [%i]\n",
    //-CJ msgPtr, destPtr, (address_t)PAGEREMAINDER(destPtr),
    //-CJ				tlblo, index>>TLB_IND_INX_SHIFT_R3k, pseudopid);

    destPtr = (Message*)PAGEREMAINDER(destPtr);
  }

  //-CJ	dumpMemory(destPtr, 0, sizeof(Message));
  byteCopy(destPtr, &tmp, sizeof(Message));
  //-CJ	dumpMemory(destPtr, 0, sizeof(Message));

  setR3kTLBEntry(0, 0, 0); /* 0-7 are safe */
}


void tmSetReturnValue(ProcContextPtr contextPtr, Register value)
{
  contextPtr->returnValue[0] = value;
}

void tmSetStackPointer(ProcContextPtr contextPtr, Register value)
{
  contextPtr->stackPointer = value;
}

void tmSetReturnAddress(ProcContextPtr contextPtr, Register value)
{
  contextPtr->returnAddress = value;
}

Register tmGetProgramCounter(ProcContextPtr contextPtr)
{
  return contextPtr->programCounter;
}

void tmSetProgramCounter(ProcContextPtr contextPtr, Register value)
{
  contextPtr->programCounter = value;
}

void tmSetStatusRegister(ProcContextPtr contextPtr, Register value)
{
  contextPtr->statusRegister = value;
}

void tmSetFramePointer(ProcContextPtr contextPtr, Register value)
{
  contextPtr->framePointer = value;
}

void tmSetArgument0(ProcContextPtr contextPtr, Register value)
{
  contextPtr->argument[0] = value;
}

void tmSetArgument1(ProcContextPtr contextPtr, Register value)
{
  contextPtr->argument[1] = value;
}

void tmSetMachineDependentRegisters(ThreadPtr thread, AddressSpace space)
{
  ProcContextPtr contextPtr;

  contextPtr = thread->contextPtr;

  contextPtr->TLBhi =
    thread->pdPtr->vm_space.vm_pmap.pseudopid << TLB_HI_PID_SHIFT_R3k;
}

void enableInterruptInContext( InterruptId intId, ProcContextPtr contextPtr)
{
  unsigned long int bitToSet = 1;

  if (intId < SWINT_0)
  {  /* either CLOCKINT, CENTRONICS, or DUART */
    bitToSet <<= (INTMASK_SHIFT+2+intId);
  }
  else
  {
    bitToSet <<= (INTMASK_SHIFT+intId-SWINT_0);
  }
  contextPtr->statusRegister |= bitToSet;
}

void disableInterruptInContext( InterruptId intId, ProcContextPtr contextPtr)
{
  unsigned long int bitToClear = 1;

  if (intId < SWINT_0)
  {  /* either CLOCKINT, CENTRONICS, or DUART */
    bitToClear <<= (INTMASK_SHIFT+2+intId);
  }
  else
  {
    bitToClear <<= (INTMASK_SHIFT+intId-SWINT_0);
  }
  contextPtr->statusRegister &= ~bitToClear;
}

void enableAllInterruptsInContext( ProcContextPtr contextPtr)
{
  contextPtr->statusRegister |= SR_INT_MASK;
}

void msgAdjust(ThreadPtr pFromOrToThread, Message *msg, Boolean ToKernel)
{
  // not needed on mips
}

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

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

  // not really needed here
}
