/*
 * Copyright (c) 2010-2013 Yuichi Watanabe
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of Yuichi Watanabe nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <core/assert.h>
#include <core/cpu.h>
#include <core/extint.h>
#include "apic.h"
#include "bitmap.h"
#include "cpu_emul.h"
#include "constants.h"
#include "current.h"
#include "panic.h"

/* #define EXTINT_DEBUG 2 */
#ifdef EXTINT_DEBUG
#include <core/printf.h>
#define EXTINT_DBG(level, ...) \
	do {							\
		if (level <= EXTINT_DEBUG) {			\
			printf(__VA_ARGS__);			\
		}						\
	} while (0)
#else
#define EXTINT_DBG(...)
#endif

void
extint_pend_8259a_int(vector_t vector)
{
	EXTINT_DBG(2, "P%d 8259a %d\n", get_cpu_id(), vector);

	if (BITMAP_SET(&current->extint.pending_8259a_int, vector)) {
		EXTINT_DBG(2, "A external interrupt %d (8259a) received while the same interrupt is pending.\n",
			   vector);
	}
	current->halt = false;
}

void
extint_request_check(void)
{
	current->extint.check = true;
}

/*
 * The caller should disable interrupts (CLI) before calling this
 * function until vmlaunch/vmresume to avoid losing interrupts.
 */
void
extint_check_interrupt(void)
{
	long vector = -1;
	ulong cr0;
	vmmerr_t err;

	/*
	 * Get the highest priority interrupt. We assume 8259a
	 * interrupts are higher than apic interrupts.
	 */
	if (current->vbsp) {
		vector = BITMAP_BSR(&current->extint.pending_8259a_int);
#ifdef EXTINT_DEBUG
		if (vector >= 0) {
			EXTINT_DBG(2, "E%d 8259a %ld\n",
				   get_cpu_id(), vector);
		}
#endif
	}

	if (current->extint.check == false && vector < 0) {
		return;
	}

	current->extint.check = false;

	if (current->vmctl.extint_is_blocked ()) {
		/*
		 * A external interrupt is blocked. Make VM-exit occur
		 * at the beginning of next interrupt window.
		 */
		current->vmctl.extint_pending(true);
		return;
	}

	current->vmctl.extint_pending(false);

	if (current->vmctl.event_injecting()) {
		/*
		 * We are injecting another event.
		 * VM-exit will occur, after injecting it.
		 */
		EXTINT_DBG(2, "O%d\n", get_cpu_id());
		return;
	}

	if (vector >= 0) {
		BITMAP_CLEAR(&current->extint.pending_8259a_int, vector);
	} else {
		/*
		 * Receive a interrupt.
		 */
		vector = int_receive();
#ifdef EXTINT_DEBUG
		if (vector >= 0) {
			if (apic_check_apic_interrupt(vector)) {
				EXTINT_DBG(2, "R%d %ld\n",
					   get_cpu_id(), vector);
			} else {
				EXTINT_DBG(2, "R%d 8259a %ld\n",
					   get_cpu_id(), vector);
			}
		}
#endif
	}

	if (vector < 0) {
		/*
		 * There is no external interrupt to inject as soon as
		 * possible.
		 */
		return;
	}

	/*
	 * Inject a interrupt.
	 */
	current->vmctl.read_control_reg(CONTROL_REG_CR0,
					&cr0);
	if (cr0 & CR0_PE_BIT) {
		EXTINT_DBG(2, "IP%d %ld\n", get_cpu_id(), vector);
		current->vmctl.generate_external_int(vector);
	} else {
		EXTINT_DBG(2, "IR%d %ld\n", get_cpu_id(), vector);
		err = cpu_emul_realmode_int(vector);
		if (err != VMMERR_SUCCESS) {
			panic("vt_check_extint: "
			      "cpu_emul_realmode_int retured error %d",
			      err);
		}
	}
}

void
extint_send_apic_int(vector_t vector, apic_id_t dest)
{
	apic_send_ipi(vector, dest);
}

void
extint_reset_8259a(void)
{
	ASSERT(current->vbsp);

	EXTINT_DBG(2, "Reset 8259a\n");
	BITMAP_INIT(&current->extint.pending_8259a_int);
}

void
extint_reset_apic(void)
{
	EXTINT_DBG(2, "Reset APIC\n");

	long vector;
	int i;

	ASSERT(currentcpu->disable_interrupt == 1);

	for (i = 0; i< 255; i++) {
		vector = int_receive();
		if (vector < 0) {
			break;
		}
		apic_write_eoi();
	}
}
