/*
 *   Trap management routines
 */

// Includes 
#include "h/nucconst.h"
#include "h/const.h"
#include "h/interrupts.h"
#include "h/utils.h"
#include "e/trap.e"
#include "e/threadq.e"
#include "e/kernel.e"
#include "e/scheduler.e"
#include "e/timer.e"
#include "e/devices.e"
#include "e/syscall.e"
#include "e/libmps.e"
#include "e/printk.e"
#include "h/debug.h"

// Prototypes
static void InitHandler( int type, void *func );
static void TrapHandler( int type );
static void PassUp( Thread *current, int type, cpu_t cause );

typedef unsigned int ( *RestartRoutine )( void );

// Data
static RestartRoutine	RestartVect[ 4 ];

// stack size for trap handlers, they'll share the same stack
#define SIZE    PAGESIZE * 1

void InitTrapHandlers( void )
{
    DBG( printk( "InitTrapHandlers\n" ));

    INITSTACK( Trap, SIZE );

    InitHandler( INTERRUPT, TrapHandler );
    InitHandler( TLBTRAP,   TrapHandler );
    InitHandler( PRGTRAP,   TrapHandler );
    InitHandler( SYSTRAP,   TrapHandler );

    RestartVect[ INTERRUPT ] = INTRESTART;
    RestartVect[ TLBTRAP   ] = TLBRESTART;
    RestartVect[ PRGTRAP   ] = PRGRESTART;
    RestartVect[ SYSTRAP   ] = SYSRESTART;
}

// Initialise the NEW area of the BIOS related to the requested
// trap vector
static void InitHandler( int type, void *func )
{
    state_t  *st;

    st          = GETVECTADDRESS( type, NEW );

    STST( st );

    st->sp      = (memaddr)ENDSTACK( Trap, SIZE );
    st->pc      = st->t9 = (memaddr)func;
    st->a0      = type;
    st->status &= ~( STATUS_IEP | CAUSEINTMASK ); // disable interrupts for trap handlers
}

#undef SIZE

// Dispatcher for trap handling routines
static void TrapHandler( int type )
{
    cpu_t       cause;
    cpu_t       timer;
    state_t    *area;
    Thread     *current;

    timer = *((cpu_t *) TIMERADDR);

    cause   = getCAUSE();
    area    = GETVECTADDRESS( type, OLD );
    current = KernData.CurrentThread;

    if( current ) { // update the current thread status
        current->updateCPUTime();
        current->setStartTime();
        current->setState( area );
    }

    switch( type ) {
    
    	case SYSTRAP: {
            int handled = FALSE;

            // MsgRecv/Send require kernel mode
            if(!( area->status & STATUS_KUP ))
                switch( area->a0 ) {

                    case SEND:
                        current->proc_s.pc += WORDLEN; // we'll restart from the next instruction
                        area->pc += WORDLEN;
                        HandleMsgSend((Thread *)area->a1, (int)area->a2 );
                        handled = TRUE;
                        break;

                    case RECV:
                        current->proc_s.pc += WORDLEN; // we'll restart from the next instruction
                        area->pc += WORDLEN;
                        if( HandleMsgRecv((Thread *)area->a1, (Message *)area->a2 ))
                            area->v0 = current->proc_s.v0; // fix the return value
                        handled = TRUE;
                        break;
                }

            if( !handled ) {
                DBG( printk( "Passing up SYSTRAP, thread = " ); printhex( current, TRUE ));
                PassUp( current, type, cause );
            }
        }   break;

    	case TLBTRAP:
    	case PRGTRAP:
            DBG(
                printk( type == TLBTRAP ? "TLBTRAP\n" : "PRGTRAP\n" );
                printk( "PC:       " ); printhex( (void *)area->pc, TRUE );
                printk( "BADVADDR: " ); printhex( (void *)getBADVADDR(), TRUE );
            );
            PassUp( current, type, cause );
    	    break;
    	
    	case INTERRUPT:
            if( cause & ( CAUSEINTMASK & ~STATUS_MAKE_IM( TIMERINT )))
                DeviceHandler( cause );
    	    break;
    }

    // check for pending timer interrupts. Don't do this in the
    // switch() as the interrupt could arrive together with
    // other traps
    if( cause & STATUS_MAKE_IM( TIMERINT )) {

        TimerIntHandler( type, current );
        timer = QUANTUM * ( *((cpu_t *)TIMESCALEADDR ));

    } else if( !current || ( current != KernData.CurrentThread )) {
        // still idle or
        // new thread is running, reset timer
        timer = QUANTUM * ( *((cpu_t *)TIMESCALEADDR ));
    }

    if( KernData.CurrentThread )
        KernData.CurrentThread->setStartTime();

    *((cpu_t *) TIMERADDR) = timer;

    // if all went fine it's time to restart
    ( *RestartVect[ type ] )();
}

// inform the thread trap manager of the exception
// kill the thread if it has no manager
static void PassUp( Thread *current, int type, cpu_t cause )
{
    if( current ) {

        if( current->HandleException( type, cause )) {

            // now wait for the manager answer
            current->setWaitFor( WF_TRAP );
            current->setTrap( type );

            KernData.ThreadWait->insertThread( current );

            NextThread( type );

        } else
            KillCurrentThread( type );
    }
}
