/*
    TMHal.c, Copyright (c) by Silvio Dragone & David Reist,
    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.
*/

#include "TMHal.h"
//#include "IOHal.h"
#include "Support.h"
#include "SupportSA1100.h"
#include "TMScheduler.h"
#include "Assert.h"
#include "TMClock.h"
#include "cpu.h"
#include "vm.h"
#include "tt.h"
#include "vm_pmap.h"
#include "vm_pmap_hal.h"
#include "MemoryLayout.h"
#include "TMProtectionDomain.h"
#include "TMIPC.h"
#include "IXPUtil.h"

void intDispatcher(Register causeReg);
void exceptionDispatcher(
  ExceptionId cause,			/* casue of the exception */
  Register cause1, 				/* additional cause 1 */
  Register cause2, 				/* additional cause 2 */
  Register cause3,        /* additional cause 3 */
  ProcContext context);		/* the context of the interrupted thread */

/* Hardware interrupt service branch table (interrupt handlers).
 * - 8 hw interrupts
 */
InterruptHandler interruptHandlerTable[MAXNBOFINTERRUPTS];
void* interruptHandlerArgTable[MAXNBOFINTERRUPTS];

/* Exception service branch table (exception handlers).
 * - 1 reset handlers,
 * - 1 undefined instruction handler
 * - 2 Abort handler 
 */
ExceptionHandler exceptionHandlerTable[MAXNBOFEXCEPTIONS];

void tmInstallExceptionCode(void)
{
  int *ptr_mem = NULL, *ptr_code = NULL;

  /* Copies the exception vector table at location EXCSEG_PHYS_BASE
   * and all the jump addresses which are added at the end of the
   * of the exception vector table by the linker!!
   * Note: after ExceptionAddressTable follows the data of this code,
   * so they needed to be copied too!!
   */
  ptr_code = (Address)ExceptionVectorTable;

  for(ptr_mem=(address_t)EXCSEG_PHYS_BASE; (address_t)ptr_mem <
      (address_t)(EXCSEG_PHYS_BASE + 0x80); ptr_mem++)
  {
    *ptr_mem = *ptr_code;
    ptr_code++;
  }
}


InterruptHandler tmSetInterruptHandler( InterruptId intId,
                                        InterruptHandler intHdler, void* arg)
{
  /* As you can see IRQ and FIQ have the same interrupt table and the same
   * position in the table.
   */

  InterruptHandler oldIntHdler = interruptHandlerTable[intId];

  interruptHandlerTable[intId] = intHdler;
  interruptHandlerArgTable[intId] = arg;

  return oldIntHdler;
}


ExceptionHandler tmSetExceptionHandler( ExceptionId excId,
                                        ExceptionHandler excHdler)
{
  /* Up to now there is no exception handler in the table,
   * but all exception have a dummy handler and it will branch 
   * directly to the specific procedure defined in Exception.h
   */

  ExceptionHandler oldExcHdler = exceptionHandlerTable[excId];

  exceptionHandlerTable[excId] = excHdler;
  return oldExcHdler;
}

void exceptionDispatcher(
  ExceptionId cause,			/* casue of the exception */
  Register cause1, 				/* additional cause 1 */
  Register cause2, 				/* additional cause 2 */
  Register cause3,        /* additional cause 3 */
  ProcContext context)		/* the context of the interrupted thread */
{
  ThreadPtr		currentThread;
  extern int 	exceptionContextFlag;
  address_t		phyPage,
  adjMsgPtr;
  ThreadId		pdId;

  //-CJ	printf("Exception: cause %i, %#lx %#lx %#lx [PC %p]\n",
  //-CJ			cause, cause1, cause2, cause3, (address_t)context.programCounter);
  //-BL dumpMemory(&context, 0, sizeof(context));
  if ( exceptionContextFlag == 1 )
  {
    // switching to kernel protection domain
    setTTBase(vm->kernel->vm_pmap.tt1base_p);
    //-CJ		printf("Switched to kernel Protection Domain\n");
  }

  currentThread = schedulerRunning();
  ASSERT( currentThread != NULL );

  /* store context only if this is the first execption */
  if ( exceptionContextFlag == 1 )
    byteCopy(currentThread->contextPtr, &context, sizeof(ProcContext));

  if ( cause == IRQ_INTR || cause == FIQ_INTR )
  {
    printf("calling interrupt dispatcher\n");
    intDispatcher(cause1);
    return;
  }

  ASSERT( cause >= 0 && cause <= ABORT_DATA );

  if (cause != SOFT_INTR)
  {
    //-CJ		printf("calling exception handler\n");
    //-BL dumpMemory(exceptionHandlerTable, 0, MAXNBOFINTERRUPTS * 4);
    (exceptionHandlerTable[cause])(schedulerRunning()->id, cause1, cause2, cause3);
  }
  else
  {
    // 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 PMap and map to
    	 MAP_PAGE_POS4). XXX will fail if the message crosses a page boundary
     */
    // cause1 = msgPtr   cause2 = timeout   cause3 = type (send/receive)

    // get the physical address of the message structure
    phyPage = vm_pmap_lookup_paddr(&currentThread->pdPtr->vm_space.vm_pmap,
                                   (address_t) cause1 /*msgPtr*/ );
    if (phyPage == NULL)
      PANIC("could not find pointer to Message!");
    // map it to kernel PD @ MAP_PAGE_POS4
    vm_pmap_enter(&vm->kernel->vm_pmap, MAP_PAGE_POS4, phyPage,
                  PMAP_READ | PMAP_WRITE);
    // adjust pointer to message
    adjMsgPtr = (Message*)( (uint32)PAGEREMAINDER(cause1 /*msgPtr*/ ) +
                            MAP_PAGE_POS4 );
    // do the job :-)
    //-CJ		printf("calling msgDispatcher\n");
    msgDispatcher(currentThread->id, adjMsgPtr, cause2 /* timeout*/,
                  cause3 /*type*/ );
    /* XXX 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 ( cause3 == SYSCALL_RECV_OP && schedulerRunning() != currentThread )
    {
      //-CJ printf("ExcDispatcher: forcing msgPendingPtr form %p to %p\n",
      //-CJ 				currentThread->msgQueue.msgPendingPtr, cause1);
      currentThread->msgQueue.msgPendingPtr = (address_t)cause1; /*msgPtr*/
    }
    // remove temprary mapping again
    //-CJ		printf("removing temp mapping\n");
    vm_pmap_page_remove(&vm->kernel->vm_pmap, MAP_PAGE_POS4);
  }

  /* restore the context of the next running thread if we leave the
  	 exception mode */
  if ( exceptionContextFlag == 1 )
  {
    currentThread = schedulerRunning();
    ASSERT(currentThread != NULL);
    byteCopy(&context, currentThread->contextPtr, sizeof(ProcContext));
    //-BL
    pdId = currentThread->pdPtr->pdId;
    //-CJ		if (currentThread->pdPtr->pdId != 0 ) {
    //-CJ			printf("switching to user pd %li\n", currentThread->pdPtr->pdId);
    //-CJ			dumpMemory(&context, 0, sizeof(context));
    //-CJ		}
    //-CJ printf("EXC: new running %li PD %li TT1base %p\n",
    //-CJ				currentThread->id, currentThread->pdPtr->pdId,
    //-CJ				currentThread->pdPtr->vm_space.vm_pmap.tt1base_p);
    setTTBase(currentThread->pdPtr->vm_space.vm_pmap.tt1base_p);
    led(pdId);
  }

  return;
}

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

  if ( (long)causeReg < 0 )
  {
    PANIC("Must be Zero");
    /* I have no idea what must be zero is so let's panic */
    return;
  }
  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
   */
  printf("calling interrupt handler at offset %i causeReg %#lx [%p]\n",
         offset, causeReg, interruptHandlerTable[offset]);
  (interruptHandlerTable[offset])(interruptHandlerArgTable[offset]);

}


void moveMessageToPD(Thread* threadPtr, Message* msgPtr, Message* destPtr)
{
  /* we are in the kernel PD and have to copy the message to another PD.
   * do this by temporaly mapping the destPtr to address 0x0 in the kernel
   */
  address_t		phyPage;


  // get the physical address of the destPtr
  phyPage = vm_pmap_lookup_paddr(&threadPtr->pdPtr->vm_space.vm_pmap,
                                 destPtr);
  //-CJ	printf("moveMessageToPD: phyPage = %p\n", phyPage);
  if (phyPage == NULL)
    PANIC("could not find pointer to Message!");
  // map it to kernel PD @ 0x0
  vm_pmap_enter(&vm->kernel->vm_pmap, MAP_PAGE_POS5, phyPage,
                PMAP_READ | PMAP_WRITE);
  // adjust pointer to message
  destPtr = (Message*)( (uint32)PAGEREMAINDER(destPtr) + MAP_PAGE_POS5);
  // copy from kernel to destPtr
  byteCopy(destPtr, msgPtr, sizeof(Message));
  //-CJ	printf("moveMessageToPD: byteCopy %p -> %p done\n", msgPtr, destPtr);
  // remove mapping
  vm_pmap_page_remove(&vm->kernel->vm_pmap, MAP_PAGE_POS5);
}


void tmSetReturnValue(ProcContextPtr contextPtr, Register value)
{
  /* The argument registers are also used for the results! */
  contextPtr->argument[0] = value;
}

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

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

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(ProcContextPtr contextPtr, AddressSpace space)
{
  /* not needed on SA1100 Architecture, or? */
}

void enableInterruptInContext( InterruptId intId, ProcContextPtr contextPtr)
{
  /* With this function the IRQ- and FIQ-bit will be cleared at the PSR
   * of the contextPtr, because for specific enabling of an interrupt there
   * must be much more information. */

  contextPtr->statusRegister &= ~((unsigned int)CPSR_I); // enables IRQ general
  contextPtr->statusRegister &= ~((unsigned int)CPSR_F); // enables FIQ general
}

void disableInterruptInContext( InterruptId intId, ProcContextPtr contextPtr)
{
  /* With this function the IRQ- and FIQ-bit will be set at the PSR
   * of the contextPtr, because for specific disabling of an interrupt there
   * must be much more information. */

  contextPtr->statusRegister |= (unsigned int)CPSR_I; // disables IRQ general
  contextPtr->statusRegister |= (unsigned int)CPSR_F; // disables FIQ general
}

void enableAllInterruptsInContext( ProcContextPtr contextPtr)
{
  /* This procedure enables all interrupts in the IRQ and FIQ mode. You have to be
   * sure about what you are doing, since some interruptes may have only an exception
   * handler for FIQ mode. */

  contextPtr->statusRegister &= ~((unsigned int)CPSR_I); // enables IRQ general
  contextPtr->statusRegister &= ~((unsigned int)CPSR_F); // enables FIQ general
}

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

void powerSaver(void)
{
  static unsigned int idlecounter = 0;
  int i = 0;


  if ( idlecounter % 1000 == 0 )
  {
    led(idlecounter/1000);
  }
  for ( i = 0; i < 1000; i++)
    ;
  ++idlecounter;
  // not needed
}

void tmAdjustMachineDependentRegisters(ProcContextPtr contextPtr, AddressSpace space)
{
  // not needed
}

