/*
 * Copyright (c) 2010-2014 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 the 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.
 */

/*
 * IOAPIC emulator.
 *   Supports injecting a edge-triggered interrupt.
 *   Not-supports injecting a level-triggered interrupt.
 */

#include <core/assert.h>
#include <core/extint.h>
#include <core/initfunc.h>
#include <core/mmio.h>
#include <core/printf.h>
#include <core/spinlock.h>
#include <core/string.h>
#include <core/types.h>
#include <core/vm.h>
#include <core/vmmerr.h>
#include <io/ioapic_emu.h>
#include "hpet.h"
#include "ioapic.h"
#include "ioapic_regs.h"

#define IOAPIC_REDIR_TBL_NUM		24
#define IOAPIC_REDIR_TBLmax_HIGH_INDEX \
	(IOAPIC_REDIR_TBL0_LOW_INDEX \
	 + IOAPIC_REDIR_TBL_SIZE * IOAPIC_REDIR_TBL_NUM \
	 - 1)

#define IOAPIC_REDIR_HIGH(ioapic, pin) \
	(ioapic->redir[pin * 2 + 1])
#define IOAPIC_REDIR_LOW(ioapic, pin) \
	(ioapic->redir[pin * 2])
#define IOAPIC_GSI(index) \
	((index - IOAPIC_REDIR_TBL0_LOW_INDEX) / 2)

#define IOAPIC_VER_REG_VAL			0x00170011

/* #define IOAPIC_DEBUG */
#ifdef IOAPIC_DEBUG
#define IOAPIC_DBG(...)						\
	do {							\
		printf(__VA_ARGS__);				\
	} while (0)
#else
#define IOAPIC_DBG(...)
#endif

struct ioapic_emu {
	spinlock_t	lock;
	u8		index;
	u32		id;
	u32		redir[IOAPIC_REDIR_TBL_NUM * 2];
};

static drvdata_hdl_t ioapic_handle;

vmmerr_t
ioapic_get_vector(gsi_t gsi, vector_t *vector, bool *mask)
{
	struct ioapic_emu	*ioapic;
	u32			redir_low;

	ASSERT(vm_get_id() != 0);

	if (gsi >= IOAPIC_REDIR_TBL_NUM) {
		return VMMERR_RANGE;
	}

	ioapic = vm_get_driver_data(ioapic_handle);
	spinlock_lock(&ioapic->lock);

	redir_low = IOAPIC_REDIR_LOW(ioapic, gsi);
	*mask = (redir_low & IOAPIC_REDIR_LOW_MASK_BIT) ? true : false;
	*vector = redir_low & IOAPIC_REDIR_LOW_VECTOR_MASK;

	spinlock_unlock(&ioapic->lock);
	return VMMERR_SUCCESS;
}

void
ioapic_trigger_int(gsi_t gsi)
{
	struct ioapic_emu	*ioapic;
	vector_t		vector;
	u32			redir_low, redir_high;
	apic_id_t		dest;
	vmmerr_t		err;

	if (vm_get_id() == 0) {
		err = ioapic_read_redir(gsi, &redir_high, &redir_low);
		if (err != VMMERR_SUCCESS) {
			return;
		}
	} else {
		if (gsi >= IOAPIC_REDIR_TBL_NUM) {
			return;
		}
		ioapic = vm_get_driver_data(ioapic_handle);
		spinlock_lock(&ioapic->lock);
		redir_low = IOAPIC_REDIR_LOW(ioapic, gsi);
		redir_high = IOAPIC_REDIR_HIGH(ioapic, gsi);
		spinlock_unlock(&ioapic->lock);
	}

	if (redir_low & (IOAPIC_REDIR_LOW_MASK_BIT |
			 IOAPIC_REDIR_LOW_LEVEL_TRIGGER_BIT |
			 IOAPIC_REDIR_LOW_LOGICAL_DEST_BIT)) {
		return;
	}
	if ((redir_low & IOAPIC_REDIR_LOW_DELIVERY_MODE_MASK) !=
	    IOAPIC_REDIR_LOW_DELIVERY_MODE_FIXED) {
		return;
	}
	vector = (redir_low & IOAPIC_REDIR_LOW_VECTOR_MASK)
		>> IOAPIC_REDIR_LOW_VECTOR_SHIFT;
	dest = (redir_high & IOAPIC_REDIR_HIGH_DEST_MASK)
		>> IOAPIC_REDIR_HIGH_DEST_SHIFT;

	IOAPIC_DBG("IOAPIC: trigger vector 0x%x dest 0x%x\n", vector, dest);
	extint_send_apic_int(vector, dest);
}

static gsi_t
ioapic_write_data_reg(struct ioapic_emu *ioapic, u32 val)
{
	int updated_gsi = INVALID_GSI;

	switch(ioapic->index) {
	case IOAPIC_ID_REG_INDEX:
		ioapic->id = val;
		IOAPIC_DBG("IOAPIC: Write ID, val 0x%x\n",
			   val);
		break;
	default:
		if (ioapic->index >= IOAPIC_REDIR_TBL0_LOW_INDEX &&
		    ioapic->index <= IOAPIC_REDIR_TBLmax_HIGH_INDEX) {
			ioapic->redir[ioapic->index
				      - IOAPIC_REDIR_TBL0_LOW_INDEX]
				= val;

			IOAPIC_DBG("IOAPIC: Write REDIR %d %s. val 0x%x\n",
				   (ioapic->index - IOAPIC_REDIR_TBL0_LOW_INDEX) / 2,
				   ioapic->index % 2 ? "high" : "low",
				   val);
			updated_gsi = IOAPIC_GSI(ioapic->index);
		} else {
			IOAPIC_DBG("IOAPIC: Write unknown reg. index %d. val 0x%x\n",
				   ioapic->index, val);
		}
		break;
	}

	return updated_gsi;
}

static u32
ioapic_read_data_reg(struct ioapic_emu* ioapic)
{
	u32 val = 0xffffffff;

	switch(ioapic->index) {
	case IOAPIC_ID_REG_INDEX:
		val = ioapic->id;
		IOAPIC_DBG("IOAPIC: Read ID, val 0x%x\n",
			   val);
		break;
	case IOAPIC_VER_REG_INDEX:
		val = IOAPIC_VER_REG_VAL;
		IOAPIC_DBG("IOAPIC: Read VER, val 0x%x\n",
			   val);
		break;
	default:
		if (ioapic->index >= IOAPIC_REDIR_TBL0_LOW_INDEX &&
		    ioapic->index <= IOAPIC_REDIR_TBLmax_HIGH_INDEX) {
			val = ioapic->redir[ioapic->index
					    - IOAPIC_REDIR_TBL0_LOW_INDEX];
			IOAPIC_DBG("IOAPIC: Read REDIR %d %s. val 0x%x\n",
				   (ioapic->index - IOAPIC_REDIR_TBL0_LOW_INDEX) / 2,
				   ioapic->index % 2 ? "high" : "low",
				   val);
		} else {
			IOAPIC_DBG("IOAPIC: Read unknown reg. index %d\n",
				   ioapic->index);
		}
		break;
	}

	return val;
}

static int
ioapic_mem_reg_handler(void *data, phys_t gphys, bool wr, void *buf,
		       uint len, u32 flags)
{
	struct ioapic_emu *ioapic = (struct ioapic_emu *)data;
	gsi_t updated_gsi = INVALID_GSI;;
	phys_t offset = gphys - IOAPIC_MEM_START;
	int done = 0;
	u32 *buf32 = (u32 *)buf;
	u8 *buf8 = (u8 *)buf;

	spinlock_lock(&ioapic->lock);

	if (wr) {
		switch (offset) {
		case IOAPIC_INDEX_REG_OFFSET:
			ioapic->index = *buf8;
			done = 1;
			break;
		case IOAPIC_DATA_REG_OFFSET:
			if (len != 4) {
				break;
			}
			updated_gsi = ioapic_write_data_reg(ioapic, *buf32);
			done = 1;
			break;
		default:
			IOAPIC_DBG("IOAPIC: Write unknown reg. offset 0x%llx, val 0x%x\n",
				   offset, *(u32 *)buf);
		}
	} else {
		switch (offset) {
		case IOAPIC_INDEX_REG_OFFSET:
			memset(buf, 0, len);
			*buf8 = ioapic->index;
			done = 1;
			break;
		case IOAPIC_DATA_REG_OFFSET:
			if (len != 4) {
				break;
			}
			*buf32 = ioapic_read_data_reg(ioapic);
			done = 1;
			break;
		default:
			IOAPIC_DBG("IOAPIC: Read unknown reg. offset 0x%llx\n",
				   offset);
		}
	}

	if (! done) {
		mmio_do_nothing(data, gphys, wr, buf, len, flags);
	}

	spinlock_unlock(&ioapic->lock);

	if (updated_gsi != INVALID_GSI) {
		hpet_ioapic_updated(updated_gsi);
	}

	return 1; /* emulated */
}


static void
ioapic_emu_setup (void)
{
	struct ioapic_emu	*ioapic;
	int			i;

	ioapic = vm_get_driver_data(ioapic_handle);

	spinlock_init(&ioapic->lock);
	for (i = 0; i < IOAPIC_REDIR_TBL_NUM; i++) {
		IOAPIC_REDIR_LOW(ioapic, i) = IOAPIC_REDIR_LOW_MASK_BIT;
	}

	if (vm_get_id() == 0) {
		/*
		 * vm0 is allowed to access ioapic directory.
		 */
		return;
	}

	mmio_register(IOAPIC_MEM_START, IOAPIC_MEM_SIZE,
		      ioapic_mem_reg_handler, ioapic);
}

static void
ioapic_emu_init(void)
{
	ioapic_handle = vm_alloc_driver_data(sizeof(struct ioapic_emu));
}

DRIVER_SETUPVM(ioapic_emu_setup);
DRIVER_INIT(ioapic_emu_init);
