/**
 * TrueReality - n64/interrupts.c
 * Copyright (C) 1998 Niki W. Waibel
 *
 * This program is free software; you can redistribute it and/
 * or modify it under the terms of the GNU General Public Li-
 * cence as published by the Free Software Foundation; either
 * version 2 of the Licence, or any later version.
 *
 * This program is distributed in the hope that it will be use-
 * ful, but WITHOUT ANY WARRANTY; without even the implied war-
 * ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public Licence for more details.
 *
 * You should have received a copy of the GNU General Public
 * Licence along with this program; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
 * USA.
 *
 * Information about me (the author):
 *   Niki W. Waibel, Reichenau 20, 6890 Lustenau, Austria - EUROPE
 *   niki.waibel@gmx.net
**/










#include <stdio.h>
#include <stdlib.h>
#include "../config.h"
#include "type_sizes.h"
#include "cpu.h"
#include "memory_extern.h"
#include "../parser_extern.h"
#include "registers.h"
#include "exceptions.h"
#include "interrupts.h"

#ifdef DEBUG
   #include "../debug.h"
#endif










rs4300i_Exception       rs4300i_interrupts[8];





void rs4300i_InitInterrupts()
{
#ifdef PUR03A_COMPATIBLE
        rs4300i_interrupts[2].time    = 0x96b55;
#else
        rs4300i_interrupts[2].time    = 0;
#endif
        rs4300i_interrupts[2].handler = rs4300i_MI_Intr_VI;
   
        rs4300i_interrupts[7].time    = 0;
        rs4300i_interrupts[7].handler = rs4300i_CompareInterrupt;
   
} /* void rs4300i_InitInterrupts() */





/* IP2 */
  
void rs4300i_MI_Intr_VI()
{  
        rs4300i_interrupts[2].time += VI_INTR_TIME;


   
        /* add MI_INTR_VI (0x08) in MI INTR reg */
        ((WORD *)mem.mi_reg)[2] |= MI_INTR_VI;


   
        /* if MI_INTR_VI (0x08) in MI INTR MASK reg is disabled: return */
        if(!(((WORD *)mem.mi_reg)[3] & MI_INTR_VI))
                return;



        /* set IP2 in CAUSE reg */
        reg.cpr[0][13] |= 0x0400;
   

   
        /* if IP2 or IE bits in STATUS reg are clr: return */
        if((reg.cpr[0][12] & 0x0401) != 0x0401)
                return;

      
   
        /* if EXL or ERL bits in STATUS reg are set: return */
        if(reg.cpr[0][12] & 0x0006)
                return;



#ifdef DEBUG_INTERRUPT_VI
        if(prefs.debug & DBG_INTR_VI)
                printf("Interrupt (VI) @ %16llx\n", reg.cpr[0][COUNT]); fflush(stdout);
#endif


   
        /* set ExcCode to Int (0) */
        reg.cpr[0][CAUSE] &= ~EXC_CODE__MASK;
        


        jump_to_common_exception_vector();

} /* void rs4300i_MI_Intr_VI() */





void rs4300i_MI_Intr_SI()
{  
        /* add bit # 12 in SI_STATUS_REG */
        ((WORD *)mem.si_reg)[6] = 0x00001000;


   
        /* add MI_INTR_SI (0x02) in MI INTR reg */
        ((WORD *)mem.mi_reg)[2] |= MI_INTR_SI;


   
        /* if MI_INTR_SI (0x02) in MI INTR MASK reg is disabled: return */
        if(!(((WORD *)mem.mi_reg)[3] & MI_INTR_SI))
                return;



        /* set IP2 in CAUSE reg */
        reg.cpr[0][CAUSE] |= 0x0400;
   

   
        /* if IP2 or IE bits in STATUS reg are clr: return */
        if((reg.cpr[0][STATUS] & 0x0401) != 0x0401)
                return;

      
   
        /* if EXL or ERL bits in STATUS reg are set: return */
        if(reg.cpr[0][STATUS] & 0x0006)
                return;



#ifdef DEBUG_INTERRUPT_SI
        if(prefs.debug & DBG_INTR_SI)
                printf("Interrupt (SI) @ %16llx\n", reg.cpr[0][COUNT]); fflush(stdout);
#endif


   
        /* set ExcCode to Int (0) */
        reg.cpr[0][CAUSE] &= ~EXC_CODE__MASK;
        


        jump_to_common_exception_vector();

} /* void rs4300i_MI_Intr_SI() */





void rs4300i_MI_Intr_SP()
{  
        /* add MI_INTR_SP (0x01) in MI INTR reg */
        ((WORD *)mem.mi_reg)[2] |= MI_INTR_SP;


   
        /* if MI_INTR_SP (0x01) in MI INTR MASK reg is disabled: return */
        if(!(((WORD *)mem.mi_reg)[3] & MI_INTR_SP))
                return;



        /* set IP2 in CAUSE reg */
        reg.cpr[0][CAUSE] |= 0x0400;
   

   
        /* if IP2 or IE bits in STATUS reg are clr: return */
        if((reg.cpr[0][STATUS] & 0x0401) != 0x0401)
                return;

      
   
        /* if EXL or ERL bits in STATUS reg are set: return */
        if(reg.cpr[0][STATUS] & 0x0006)
                return;



#ifdef DEBUG_INTERRUPT_SP
        if(prefs.debug & DBG_INTR_SP)
                printf("Interrupt (SP) @ %16llx\n", reg.cpr[0][COUNT]); fflush(stdout);
#endif


   
        /* set ExcCode to Int (0) */
        reg.cpr[0][CAUSE] &= ~EXC_CODE__MASK;
        


        jump_to_common_exception_vector();

} /* void rs4300i_MI_Intr_SP() */





void rs4300i_MI_Intr_PI()
{  
        /* add MI_INTR_PI (0x10) in MI INTR reg */
        ((WORD *)mem.mi_reg)[2] |= MI_INTR_PI;


   
        /* if MI_INTR_SI (0x10) in MI INTR MASK reg is disabled: return */
        if(!(((WORD *)mem.mi_reg)[3] & MI_INTR_PI))
                return;



        /* set IP2 in CAUSE reg */
        reg.cpr[0][CAUSE] |= 0x0400;
   

   
        /* if IP2 or IE bits in STATUS reg are clr: return */
        if((reg.cpr[0][STATUS] & 0x0401) != 0x0401)
                return;

      
   
        /* if EXL or ERL bits in STATUS reg are set: return */
        if(reg.cpr[0][STATUS] & 0x0006)
                return;



#ifdef DEBUG_INTERRUPT_PI
        if(prefs.debug & DBG_INTR_PI)
                printf("Interrupt (PI) @ %16llx\n", reg.cpr[0][COUNT]); fflush(stdout);
#endif


   
        /* set ExcCode to Int (0) */
        reg.cpr[0][CAUSE] &= ~EXC_CODE__MASK;
        


        jump_to_common_exception_vector();

} /* void rs4300i_MI_Intr_PI() */





/* IP7 */

void rs4300i_CompareInterrupt()
{  
        /* set IP7 in CAUSE reg */
        reg.cpr[0][CAUSE] |= 0x8000;
   

   
        /* if IP7 or IE bits in STATUS reg are clr: return */
        if((reg.cpr[0][STATUS] & 0x8001) != 0x8001)
                return;

      
   
        /* if EXL or ERL bits in status reg are set: return */
        if(reg.cpr[0][12] & 0x0006)
                return;



#ifdef DEBUG_INTERRUPT_COMPARE
        if(prefs.debug & DBG_INTR_COMP)
                printf("Interrupt (compare) @ %16llx\n", reg.cpr[0][9]); fflush(stdout);
#endif


   
        /* set ExcCode to Int (0) */
        reg.cpr[0][CAUSE] &= ~EXC_CODE__MASK;
        


        jump_to_common_exception_vector();
   
} /* void rs4300i_CompareInterrupt() */





void rs4300i_Check_MI_Interrupts()
{
        static WORD     mi_old_intr_mask_reg = MI_INTR_NO;



        if(mi_old_intr_mask_reg != ((WORD *)mem.mi_reg)[3])
        {
                mi_old_intr_mask_reg = ((WORD *)mem.mi_reg)[3];

                
                
                if( ((WORD *)mem.mi_reg)[3] & ((WORD *)mem.mi_reg)[2] & MI_INTR_VI )
                {
                        rs4300i_MI_Intr_VI();
                        return;
                }

                if( ((WORD *)mem.mi_reg)[3] & ((WORD *)mem.mi_reg)[2] & MI_INTR_SI )
                {
                        rs4300i_MI_Intr_SI();
                        return;
                }

                if( ((WORD *)mem.mi_reg)[3] & ((WORD *)mem.mi_reg)[2] & MI_INTR_SP )
                {
                        rs4300i_MI_Intr_SP();
                        return;
                }

                if( ((WORD *)mem.mi_reg)[3] & ((WORD *)mem.mi_reg)[2] & MI_INTR_PI )
                {
                        rs4300i_MI_Intr_PI();
                        return;
                }

        } /* if(mi_old_intr_mask_reg != ((WORD *)mem.mi_reg)[3]) */

} /* void rs4300i_Check_MI_Interrupts() */





void rs4300i_Check_CPU_Interrupts()
{
        if(reg.cpr[0][STATUS] & reg.cpr[0][CAUSE] & 0xff00)   /* if any interrupt without mask pending */
        {
                /* if IE bit in STATUS reg is set */
                if(reg.cpr[0][STATUS] & 0x0001)
                {
                        /* if EXL or ERL bits in STATUS reg are clr */
                        if((reg.cpr[0][STATUS] & 0x0006) == 0x0000)
                        {                                      
#ifdef DEBUG_INTERRUPT_VI
                                if(prefs.debug & DBG_INTR_VI)
                                        if((reg.cpr[0][CAUSE] & 0x0400) && (((WORD *)mem.mi_reg)[2] & MI_INTR_VI))
                                                printf("Interrupt (VI) @ %16llx\n", reg.cpr[0][COUNT]); fflush(stdout);
#endif
#ifdef DEBUG_INTERRUPT_SI
                                if(prefs.debug & DBG_INTR_SI)
                                        if((reg.cpr[0][CAUSE] & 0x0400) && (((WORD *)mem.mi_reg)[2] & MI_INTR_SI))
                                                printf("Interrupt (SI) @ %16llx\n", reg.cpr[0][COUNT]); fflush(stdout);
#endif
#ifdef DEBUG_INTERRUPT_SP
                                if(prefs.debug & DBG_INTR_SP)
                                        if((reg.cpr[0][CAUSE] & 0x0400) && (((WORD *)mem.mi_reg)[2] & MI_INTR_SP))
                                                printf("Interrupt (SP) @ %16llx\n", reg.cpr[0][COUNT]); fflush(stdout);
#endif
#ifdef DEBUG_INTERRUPT_COMPARE
// maybe the compare interrupt should check COMPARE reg
// it's possible that this is a BUG
                                if(prefs.debug & DBG_INTR_COMP)
                                        if((reg.cpr[0][CAUSE] & 0x8000))
                                                printf("CompareInterrupt @ %16llx\n", reg.cpr[0][COUNT]); fflush(stdout);
#endif
 
                                if( 0xff00 & reg.cpr[0][STATUS] & reg.cpr[0][CAUSE] )
                                        jump_to_common_exception_vector();
                  
                        } /* if((reg.cpr[0][STATUS] & 0x0006) == 0x0000) */

                } /* if(reg.cpr[0][STATUS] & 0x0001) */

        } /* if(reg.cpr[0][STATUS] & reg.cpr[0][CAUSE] & 0xff00) */

} /* void rs4300i_Check_CPU_Interrupts() */







