/**
 * @file int.c
 * @brief Interrupt parsing and handling
 * 
 */

#include "int.h"
#include "sys.h"
#include "sched.h"
#include "thread.h"
#include "io.h"

/** Timer interrupt constant */
#define INT_TIMER		cp0_cause_ip7_mask
/** Keyboard interrupt constant */
#define INT_KEYBOARD	cp0_cause_ip3_mask

/**
 * @brief Interupt handling function
 * 
 * interrupt() is called from the exception handler - see exc.c. Right now
 * we are sure that the processor is in the exception level so all other
 * interrupts are disabled.
 *
 * A bit mask which device have to be handled can be found in the Cause
 * register, the IP (interrupt pending) field. Note that more than one
 * bit can be set.
 *
 * It is up to the device to clear an interrupt request, so the device which
 * asserted the external interrupt signal have to be satisfied. Otherwise
 * the exception interrupt is generated immediately after the interrupts are
 * enabled.
 * 
 */
void
interrupt (void)
{
	int cause = read_cp0_cause ();

	/*
	 * keyboard interrupt handling
	 * info:works fine, tested
	 */
	
	if (cause & INT_KEYBOARD) {
		int key;
		key = def_getc();
		
		kbd_buffer_putc(key);
		cond_signal(&keyboard_cnd);
	}
	
	/*
	 * timer interrupt handling
	 *
	 * It is a good idea to handle the timer interrupt as the last one.
	 * We want to switch to another thread/context and we are not
	 * sure that all devices will be handled immediately.
	 */
	if (cause & INT_TIMER) {
		schedule ();
	}
}

/**
 * @brief Globally enables interrupts
 *
 * Globally enables interrupts on the processor by setting the
 * Interrupt Enable field of the CP0 Status Register to 1.
 * 
 */
inline void enable_interrupts (void) {
	write_cp0_status (read_cp0_status () | cp0_status_ie_mask);
}

/**
 * @brief Globally disables interrupts
 *
 * Globally disables interrupts on the processor by setting the
 * Interrupt Enable field of the CP0 Status Register to 0.
 */
inline void disable_interrupts (void) {
		write_cp0_status (read_cp0_status () & ~cp0_status_ie_mask);
}

/**
 * @brief Thread save disable interrupts
 *
 * Raise interrupt counter of current thread
 * 
 */
void save_and_disable_interrupts () {
	if (current_thread->interrupt_counter==0) { 
		current_thread->interrupt_previous_state = (read_cp0_status() & cp0_status_ie_mask);
		disable_interrupts();	
	}
	current_thread->interrupt_counter++;
} 

/**
 * @brief Thread save enable interrupts
 *
 * Lower interrupt counter of current thread and if it is 0 globally enable interrupts
 * 
 */
void restore_interrupts () {
	if (current_thread->interrupt_counter) {	
			  current_thread->interrupt_counter--;
	}		
	if ((current_thread->interrupt_counter == 0) && (current_thread->interrupt_previous_state)) 
			enable_interrupts(); 
}



