/*
 * Copyright (c) 2010-2012 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/acpi.h>
#include <core/assert.h>
#include <core/spinlock.h>
#include <core/string.h>
#include <core/initfunc.h>
#include <core/int.h>
#include <core/io.h>
#include <core/cpu.h>
#include <core/mm.h>
#include <core/mmio.h>
#include <core/panic.h>
#include <core/printf.h>
#include <core/types.h>
#include <core/vm.h>
#include <io/io.h>
#include <io/ioapic_emu.h>

/* #define HPET_DEBUG 1 */
#ifdef HPET_DEBUG
#define HPET_DBG(level, ...) \
	do {							\
		if (level <= HPET_DEBUG) {			\
			printf(__VA_ARGS__);			\
		}						\
	} while (0)
#else
#define HPET_DBG(...)
#endif

#define ACPI_HPET_SIGNATURE		"HPET"

#define ACPI_NUM_OF_TIMER_MASK		0x00001f00
#define ACPI_NUM_OF_TIMER_SHIFT		8

#define HPET_EMU_BASE			0xfed00000
#define HPET_MEM_SIZE			0x400

#define HPET_GCAP_ID			0x0
#define HPET_GCAP_ID_SIZE		8
#define  HPET_NUM_OF_TIMER_MASK		0x00001f00
#define  HPET_NUM_OF_TIMER_SHIFT	8

#define HPET_GEN_CONF			0x10
#define HPET_GEN_CONF_SIZE		8
#define  HPET_LEG_RT_CNF		0x2
#define  HPET_ENABLE_CNF		0x1

#define HPET_ENABLED(gen_conf) \
	(((gen_conf) & HPET_ENABLE_CNF) != 0)

#define HPET_LEG_RT_ENABLED(gen_conf) \
	(((gen_conf) & (HPET_LEG_RT_CNF | HPET_ENABLE_CNF))	\
	 == (HPET_LEG_RT_CNF | HPET_ENABLE_CNF))
#define HPET_NON_LEG_RT_ENABLED(gen_conf) \
	(((gen_conf) & (HPET_LEG_RT_CNF | HPET_ENABLE_CNF))	\
	 == HPET_ENABLE_CNF)

#define HPET_GINTR_STA			0x20
#define HPET_GINTR_STA_SIZE		8

#define HPET_MAIN_CNT			0xf0
#define HPET_MAIN_CNT_SIZE		8

#define HPET_TIM_CONF(timer)		((timer) * 0x20 + 0x100)
#define HPET_TIM_CONF_SIZE		8
#define HPET_TIM_CONF_TIMER(reg)	(((reg) - 0x100) / 0x20)
#define  HPET_TN_FSB_INT_DEL_CAP	0x8000
#define  HPET_TN_FSB_EN_CNF		0x4000
#define  HPET_TN_INT_ROUTE_CNF_MASK	0x3e00
#define  HPET_TN_VAL_SET_CNF		0x0040
#define  HPET_TN_INT_ENB_CNF		0x0004
#define  HPET_TN_INT_TYPE_CNF		0x0002

#define HPET_PIN_BASED_ENABLED(conf) \
	(((conf) & (HPET_TN_INT_ENB_CNF | HPET_TN_FSB_EN_CNF)) == \
	 HPET_TN_INT_ENB_CNF)

#define HPET_TIM_COMP(timer)		((timer) * 0x20 + 0x108)
#define HPET_TIM_COMP_SIZE		8
#define HPET_TIM_COMP_TIMER(reg)	(((reg) - 0x108) / 0x20)

#define HPET_FSB_INT_ROUTE(timer)	((timer) * 0x20 + 0x110)
#define HPET_FSB_INT_ROUTE_SIZE		8
#define HPET_FSB_INT_ROUTE_TIMER(reg)	(((reg) - 0x110) / 0x20)

#define TIMER0_GSI		2
#define TIMER1_GSI		8

/*
 * Physial destination mode.
 * No redirection.
 * Edge trigger.
 * FIxed interrupt.
 */
#define HPET_FSB_INT_ROUTE_VAL		0xfee0000000000000LL
#define HPET_FSB_DEST_ID_SHIFT		44
#define HPET_FSB_VECTOR_SHIFT		0

#define hpet_reg(emu, gphys)				\
	((gphys) - (emu)->gphys_base)

#define hpet_g2v(dev, emu, gphys)			\
	(void *)((char *)(dev)->virt_base + hpet_reg((emu), (gphys)))

/* #define HPET_PASS_TIMER_NUM		4 */

#define HPET_TIMER_MAX_NUM		32

#define hpet_read32(dev, reg)				\
	read64((char *)((dev)->virt_base) + (reg))
#define hpet_read64(dev, reg)				\
	read32((char *)((dev)->virt_base) + (reg))
#define hpet_write32(dev, reg, val)			\
	write32((char *)((dev)->virt_base) + (reg), (val))
#define hpet_write64(dev, reg, val)			\
	write64((char *)((dev)->virt_base) + (reg), (val))

struct acpi_hpet {
	struct acpi_desc_header		header;
	u32				hardware_id;
	struct acpi_generic_address	base_address;
	u8				number;
	u16				min_clock_tick;
	u8				attribute;
} __attribute__ ((packed));

struct hpet_emu_timer {
	u64		conf;
	vector_t	vector;
	gsi_t		gsi;
};

struct hpet_emu {
	spinlock_t		lock;
	phys_t			gphys_base;
	u64			gen_conf;
	struct hpet_emu_timer	timer[HPET_TIMER_MAX_NUM];
};

struct hpet_dev {
	spinlock_t	lock;
	phys_t		phys_base;
	void		*virt_base;
	uint		num_of_timer;
	bool		initialized;
};

static struct hpet_dev	hpet_dev = {.lock = SPINLOCK_INITIALIZER,
				    .initialized = false};
static drvdata_hdl_t	hpet_handle;

static void
hpet_modify_reg_val64(u64 *reg, phys_t off, char *buf, uint len)
{
	memcpy((char *)reg + (off & 0x7), buf, len);
}

static void
hpet_modify_buf_val64(char *buf, uint len, u64 *reg, phys_t off)
{
	memcpy(buf, (char *)reg + (off & 0x7), len);
}

static void
hpet_setup_non_legacy_int(struct hpet_emu *hpet_emu, uint timer_no)
{
	struct hpet_emu_timer *timer = hpet_emu->timer + timer_no;
	u64 conf = timer->conf;
#ifdef HPET_DEBUG
	u64 old_conf;
#endif

	if (HPET_PIN_BASED_ENABLED(conf)) {
		printf("HPET: Pin-based interrupt is not supported. timer %d\n",
		       timer_no);
		conf &= ~HPET_TN_INT_ENB_CNF;
	}
	conf |= HPET_TN_FSB_EN_CNF;

	spinlock_lock(&hpet_dev.lock);
	if (timer_no >= hpet_dev.num_of_timer) {
		spinlock_unlock(&hpet_dev.lock);
		return;
	}
#ifdef HPET_DEBUG
	old_conf = hpet_read64(&hpet_dev, HPET_TIM_CONF(timer_no));
	if (!(old_conf & HPET_TN_INT_ENB_CNF) &&
	    (conf & HPET_TN_INT_ENB_CNF)) {
		HPET_DBG(1, "Enable non-legacy int of timer %d. "
			 "HPET_TIM_CONF(%d) 0x%llx\n",
			 timer_no, timer_no, conf);
	} else {
		HPET_DBG(2, "Write host HPET_TIM_CONF(%d) 0x%llx\n",
			 timer_no, conf);
	}
#endif
	hpet_write64(&hpet_dev, HPET_TIM_CONF(timer_no), conf);
	spinlock_unlock(&hpet_dev.lock);
}

#ifdef HPET_HOOK_INTR
static int
hpet_int(vector_t vector, void *data)
{
	HPET_DBG(2, "hpet_int 0x%x\n", vector);
	return INT_UNHANDLED;
}
#endif

static void
hpet_setup_legacy_int(struct hpet_emu *hpet_emu, uint timer_no)
{
	struct hpet_emu_timer *timer = hpet_emu->timer + timer_no;
	u64 conf = timer->conf;
	u64 fsb;
	vector_t vector = 0;
	apic_id_t dest_id;
	bool mask;
#ifdef HPET_DEBUG
	u64 old_conf;
#endif

#ifdef HPET_HOOK_INTR
	if (timer->vector) {
		clear_int_hook(timer->vector);
	}
#endif

	if (conf & HPET_TN_INT_ENB_CNF) {
		ioapic_get_vector(timer->gsi, &vector, &mask);
		if (mask) {
			vector = 0;
		}
	}

	spinlock_lock(&hpet_dev.lock);
	if (timer_no >= hpet_dev.num_of_timer) {
		spinlock_unlock(&hpet_dev.lock);
		return;
	}

	if (vector) {
#ifdef HPET_HOOK_INTR
		/*
		 * Setup a interrupt.
		 */
		set_int_hook(vector, hpet_int,
			     (void *)(long)timer->gsi);
#endif
		dest_id = vm_vbsp_apic_id();
		fsb = HPET_FSB_INT_ROUTE_VAL |
			((u64)dest_id << HPET_FSB_DEST_ID_SHIFT) |
			(vector << HPET_FSB_VECTOR_SHIFT);
		HPET_DBG(2, "Write host HPET_FSB(%d) 0x%llx\n",
			 timer_no, fsb);
		hpet_write64(&hpet_dev, HPET_FSB_INT_ROUTE(timer_no), fsb);
		conf &= ~HPET_TN_INT_TYPE_CNF; /* Use edge-triggered */
		conf |= HPET_TN_FSB_EN_CNF; /* Enable MSI */
	} else {
		conf &= ~HPET_TN_INT_ENB_CNF;
	}

#ifdef HPET_DEBUG
	old_conf = hpet_read64(&hpet_dev, HPET_TIM_CONF(timer_no));
	if (!(old_conf & HPET_TN_INT_ENB_CNF) &&
	    (conf & HPET_TN_INT_ENB_CNF)) {
		HPET_DBG(1, "Enable legacy int of timer %d. "
			 "HPET_TIM_CONF(%d) 0x%llx\n",
			 timer_no, timer_no, conf);
	} else {
		HPET_DBG(2, "Write host HPET_TIM_CONF(%d) 0x%llx\n",
			 timer_no, conf);
	}
#endif
	hpet_write64(&hpet_dev, HPET_TIM_CONF(timer_no), conf);
	spinlock_unlock(&hpet_dev.lock);

	timer->vector = vector;
}

static void
hpet_setup_all_int(struct hpet_emu *hpet_emu)
{
	struct hpet_emu_timer *timer;
	int i;

	for (i = 0; i < HPET_TIMER_MAX_NUM; i++) {
		timer = hpet_emu->timer + i;
		if (HPET_LEG_RT_ENABLED(hpet_emu->gen_conf) &&
		    timer->gsi != INVALID_GSI) {
			hpet_setup_legacy_int(hpet_emu, i);
		} else {
			hpet_setup_non_legacy_int(hpet_emu, i);
		}
	}
}

static int
hpet_gen_conf_handler(void *data, phys_t gphys, bool wr, void *buf,
		      uint len, u32 flags)
{
	struct hpet_emu *hpet_emu = (struct hpet_emu *)data;
	u64 reg, old_conf;

	spinlock_lock(&hpet_emu->lock);
	reg = hpet_emu->gen_conf;
	if (wr) {
		old_conf = reg;
		hpet_modify_reg_val64(&reg, gphys, buf, len);
		hpet_emu->gen_conf = reg;
		HPET_DBG(2, "Write guest HPET_GEN_CONF 0x%llx\n", reg);

		if (!HPET_ENABLED(old_conf) &&
		    HPET_ENABLED(hpet_emu->gen_conf)) {
			hpet_setup_all_int(hpet_emu);
		}
		/*
		 * Disable legacy interrupt. We use MSI (FSB).
		 */
		reg &= ~HPET_LEG_RT_CNF;

		spinlock_lock(&hpet_dev.lock);
		HPET_DBG(2, "Write host HPET_GEN_CONF 0x%llx\n", reg);
		hpet_write64(&hpet_dev, HPET_GEN_CONF, reg);
		spinlock_unlock(&hpet_dev.lock);
	} else {
		hpet_modify_buf_val64(buf, len, &reg, gphys);
	}
	spinlock_unlock(&hpet_emu->lock);
	return 1; /* emulated */
}

void
hpet_ioapic_updated(gsi_t gsi)
{
	struct hpet_emu *hpet_emu = vm_get_driver_data(hpet_handle);
	spinlock_lock(&hpet_emu->lock);

	if (!HPET_LEG_RT_ENABLED(hpet_emu->gen_conf)) {
		spinlock_unlock(&hpet_emu->lock);
		return;
	}

	switch (gsi) {
	case TIMER0_GSI:
		hpet_setup_legacy_int(hpet_emu, 0);
		break;
	case TIMER1_GSI:
		hpet_setup_legacy_int(hpet_emu, 1);
		break;
	}

	spinlock_unlock(&hpet_emu->lock);
}

static mmio_handler_t hpet_tim_conf_handler;
static int
hpet_tim_conf_handler(void *data, phys_t gphys, bool wr, void *buf,
		      uint len, u32 flags)
{
	struct hpet_emu *hpet_emu = (struct hpet_emu *)data;
	struct hpet_emu_timer *timer;
	u64 conf;
	uint timer_no;

	spinlock_lock(&hpet_emu->lock);

	timer_no = HPET_TIM_CONF_TIMER(hpet_reg(hpet_emu, gphys));
	timer = hpet_emu->timer + timer_no;
	conf = timer->conf;

	if (!wr) {
		hpet_modify_buf_val64(buf, len, &conf, gphys);
		spinlock_unlock(&hpet_emu->lock);
		return 1; /* emulated */
	}

	hpet_modify_reg_val64(&conf, gphys, buf, len);
	timer->conf = conf;

	HPET_DBG(2, "Write guest HPET_TIM_CONF(%d) 0x%llx\n", timer_no, conf);

	if (HPET_LEG_RT_ENABLED(hpet_emu->gen_conf) &&
	    timer->gsi != INVALID_GSI) {
		hpet_setup_legacy_int(hpet_emu, timer_no);
	} else if (HPET_NON_LEG_RT_ENABLED(hpet_emu->gen_conf) ||
		   (HPET_LEG_RT_ENABLED(hpet_emu->gen_conf) &&
		    timer->gsi == INVALID_GSI)) {
		hpet_setup_non_legacy_int(hpet_emu, timer_no);
	} else {
		spinlock_lock(&hpet_dev.lock);
		if (timer_no < hpet_dev.num_of_timer) {
			HPET_DBG(2, "Write host HPET_TIM_CONF(%d) 0x%llx\n",
				 timer_no, conf);
		}
		hpet_write64(&hpet_dev, HPET_TIM_CONF(timer_no), conf);
		spinlock_unlock(&hpet_dev.lock);
	}
	/*
	 * HPET_TN_VAL_SET_CNF should be cleared automatically for
	 * guest software.
	 */
	timer->conf &= ~HPET_TN_VAL_SET_CNF;

	spinlock_unlock(&hpet_emu->lock);
	return 1; /* emulated */
}

static int
hpet_pass(void *data, phys_t gphys, bool wr, void *buf,
		uint len, u32 flags)
{
	struct hpet_emu *hpet_emu = (struct hpet_emu *)data;
#ifdef HPET_DEBUG
	u64 tmp = 0;
#endif
	spinlock_lock(&hpet_emu->lock);
	spinlock_lock(&hpet_dev.lock);
	if (wr) {
		mmio_memcpy(hpet_g2v(&hpet_dev, hpet_emu, gphys), buf, len);
	} else {
		mmio_memcpy(buf, hpet_g2v(&hpet_dev, hpet_emu, gphys), len);
	}

#ifdef HPET_DEBUG
	memcpy(&tmp, buf, len <= sizeof(tmp) ? len : sizeof(tmp));
	HPET_DBG(2, "%s HPET off 0x%llx val 0x%llx\n",
		 wr ? "Write" : "Read", hpet_reg(hpet_emu, gphys), tmp);
#endif

	spinlock_unlock(&hpet_dev.lock);
	spinlock_unlock(&hpet_emu->lock);
	return 1; /* emulated */
}

static void
hpet_setup_vm(void)
{
	struct hpet_emu	*hpet_emu;
	int i;

	spinlock_lock(&hpet_dev.lock);
	if (hpet_dev.initialized == false) {
		spinlock_unlock(&hpet_dev.lock);
		return;
	}

	if (vm_get_id() != 1) {
		/*
		 * Hide HPET from vm0 and vmn (n > 1).
		 */
		mmio_register(hpet_dev.phys_base, HPET_MEM_SIZE,
			      mmio_do_nothing, NULL);
		spinlock_unlock(&hpet_dev.lock);
		return;
	}

	hpet_emu = vm_get_driver_data(hpet_handle);

	spinlock_init(&hpet_emu->lock);
	hpet_emu->gphys_base = HPET_EMU_BASE;

	mmio_register(hpet_emu->gphys_base + HPET_GCAP_ID,
		      HPET_GCAP_ID_SIZE, hpet_pass,
		      hpet_emu);
	mmio_register(hpet_emu->gphys_base + HPET_GEN_CONF,
		      HPET_GEN_CONF_SIZE, hpet_gen_conf_handler,
		      hpet_emu);
	mmio_register(hpet_emu->gphys_base + HPET_GINTR_STA,
		      HPET_GINTR_STA_SIZE, hpet_pass,
		      hpet_emu);
	mmio_register(hpet_emu->gphys_base + HPET_MAIN_CNT,
		      HPET_MAIN_CNT_SIZE, hpet_pass,
		      hpet_emu);

	for (i = 0; i < hpet_dev.num_of_timer; i++) {
		mmio_register(hpet_emu->gphys_base + HPET_TIM_CONF(i),
			      HPET_TIM_CONF_SIZE, hpet_tim_conf_handler,
			      hpet_emu);
		mmio_register(hpet_emu->gphys_base + HPET_TIM_COMP(i),
			      HPET_TIM_COMP_SIZE + HPET_FSB_INT_ROUTE_SIZE,
			      hpet_pass, hpet_emu);
		hpet_emu->timer[i].conf
			= hpet_read64(&hpet_dev, HPET_TIM_CONF(i));
	}

	hpet_emu->gen_conf = hpet_read64(&hpet_dev, HPET_GEN_CONF);

	hpet_emu->timer[0].gsi = TIMER0_GSI;
	hpet_emu->timer[1].gsi = TIMER1_GSI;
	for (i = 2; i < HPET_TIMER_MAX_NUM; i++) {
		hpet_emu->timer[i].gsi = INVALID_GSI;
	}
	spinlock_unlock(&hpet_dev.lock);

}

static void
hpet_init_dev(phys_t base)
{
	u32 cap_reg;

	spinlock_lock(&hpet_dev.lock);
	hpet_dev.virt_base = mapmem(MAPMEM_HPHYS | MAPMEM_WRITE | MAPMEM_UC,
				    base, HPET_MEM_SIZE);
	if (hpet_dev.virt_base == NULL) {
		printf("Failed to map HPET registers.\n");
		return;
	}
	hpet_dev.phys_base = base;
	cap_reg = hpet_read32(&hpet_dev, HPET_GCAP_ID);
	hpet_dev.num_of_timer = ((cap_reg & HPET_NUM_OF_TIMER_MASK)
		>> HPET_NUM_OF_TIMER_SHIFT) + 1;
	printf("HPET has %d timers.\n", hpet_dev.num_of_timer);

	hpet_dev.initialized = true;
	spinlock_unlock(&hpet_dev.lock);
}

static acpi_parser_t hpet_parse_acpi_table;
static void
hpet_parse_acpi_table(void *table, u32 length)
{
	struct acpi_hpet *acpi_hpet = (struct acpi_hpet *)table;

	if (acpi_hpet->base_address.space_id != ACPI_SYSTEM_MEMORY) {
		printf("A space id of base address of HPET is invalid. %d\n",
		       acpi_hpet->base_address.space_id);
		return;
	}
	printf("HPET found. base 0x%llx\n",
		acpi_hpet->base_address.address);

	if (acpi_hpet->number > 0) {
		/*
		 * We only handle the first HPET.
		 * not-first HPETs are exposed to a vm0.
		 */
		return;
	}
	
	printf("Hide HPET from vm0\n");
	acpi_hide_table(table);

	hpet_init_dev(acpi_hpet->base_address.address);
}

static void
hpet_init(void)
{
	hpet_handle = vm_alloc_driver_data(sizeof(struct hpet_emu));
	acpi_register_parser(ACPI_HPET_SIGNATURE, hpet_parse_acpi_table);
}

DRIVER_SETUPVM(hpet_setup_vm);
DRIVER_INIT(hpet_init);
