/*-
 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
 *
 * Copyright (c) 2019-2022 Intel Corporation.
 * Copyright (c) 2013 Tycho Nightingale <tycho.nightingale@pluribusnetworks.com>
 * Copyright (c) 2013 Neel Natu <neel@freebsd.org>
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``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 NETAPP, INC 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.
 *
 * $FreeBSD$
 */

#include <pthread.h>
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <err.h>
#include <sysexits.h>
#include <string.h>
#include <unistd.h>

#include "vmmapi.h"
#include "mem.h"
#include "timer.h"
#include "hpet.h"
#include "acpi_hpet.h"
#include "log.h"

#define	HPET_FREQ	(16777216)		/* 16.7 (2^24) Mhz */
#define	FS_PER_S	(1000000000000000UL)

/* Timer N Configuration and Capabilities Register */
#define	HPET_TCAP_RO_MASK	(HPET_TCAP_INT_ROUTE 	|		\
				 HPET_TCAP_FSB_INT_DEL	|		\
				 HPET_TCAP_SIZE		|		\
				 HPET_TCAP_PER_INT)

/*
 * HPET requires at least 3 timers and up to 32 timers per block.
 */
#define	VHPET_NUM_TIMERS	(8)

#define	VHPET_LOCK()												\
	do {															\
		int err;													\
		err = pthread_mutex_lock(&vhpet_mtx);						\
		if (err)													\
			errx(EX_SOFTWARE, "pthread_mutex_lock returned %s",		\
					strerror(err));									\
	} while (0)

#define	VHPET_UNLOCK()												\
	do {															\
		int err;													\
		err = pthread_mutex_unlock(&vhpet_mtx);						\
		if (err)													\
			errx(EX_SOFTWARE, "pthread_mutex_unlock returned %s",	\
					strerror(err));									\
	} while (0)

#define vhpet_ts_to_ticks(ts)	ts_to_ticks(HPET_FREQ, ts)
/* won't overflow since the max value of ticks is 2^32 */
#define vhpet_ticks_to_ts(tk, ts)	ticks_to_ts(HPET_FREQ, tk, ts)

#define vhpet_tmr(v, n)	(&(v)->timer[n].tmrlst[(v)->timer[n].tmridx].t)
#define vhpet_tmrarg(v, n)	(&(v)->timer[n].tmrlst[(v)->timer[n].tmridx].a)

#define ts_is_zero(ts)	timespeccmp(ts, &zero_ts.it_value, ==)
#define ts_set_zero(ts)	do { *(ts) = zero_ts.it_value; } while (0)

/*
 * Debug printf
 */
static int hpet_debug;
#define DPRINTF(params)	do { if (hpet_debug) pr_dbg params; } while (0)
#define WPRINTF(params)	(pr_err params)


static struct mem_range vhpet_mr = {
	.name = "vhpet",
	.base = VHPET_BASE,
	.size = VHPET_SIZE,
	.flags = MEM_F_RW
};

struct vhpet_timer_arg {
	struct vhpet	*vhpet;
	int	timer_num;
	bool	running;
};

struct vhpet {
	struct vmctx	*vm;
	bool	inited;

	uint64_t	config;		/* Configuration */
	uint64_t	isr;		/* Interrupt Status */
	uint32_t	countbase;	/* HPET counter base value */
	struct timespec	countbase_ts;	/* uptime corresponding to base value */

	struct {
		uint64_t	cap_config;	/* Configuration */
		uint64_t	msireg;		/* FSB interrupt routing */
		uint32_t	compval;	/* Comparator */
		uint32_t	comprate;
		struct timespec	expts;	/* time when counter==compval */
		struct {
			struct acrn_timer	t;
			struct vhpet_timer_arg	a;
		} tmrlst[3];
		int	tmridx;
	} timer[VHPET_NUM_TIMERS];
};


/* one vHPET per VM */
static pthread_mutex_t vhpet_mtx = PTHREAD_MUTEX_INITIALIZER;

static const struct itimerspec zero_ts = { 0 };


/* one vHPET per VM */
static struct vhpet *
vhpet_instance(void)
{
	static struct vhpet __vhpet;

	return &__vhpet;
}

const uint64_t
vhpet_capabilities(void)
{
	static uint64_t cap = 0;

	if (cap == 0) {
		cap |= 0x8086 << 16;			/* vendor id */
		cap |= (VHPET_NUM_TIMERS - 1) << 8;	/* number of timers */
		cap |= 1;				/* revision */
		cap &= ~HPET_CAP_COUNT_SIZE;		/* 32-bit timer */

		cap &= 0xffffffff;
		cap |= (FS_PER_S / HPET_FREQ) << 32;	/* tick period in fs */
	}

	return cap;
}

static inline bool
vhpet_counter_enabled(struct vhpet *vhpet)
{
	return ((vhpet->config & HPET_CNF_ENABLE) != 0);
}

static inline bool
vhpet_timer_msi_enabled(struct vhpet *vhpet, int n)
{
	const uint64_t msi_enable = HPET_TCAP_FSB_INT_DEL | HPET_TCNF_FSB_EN;

	return ((vhpet->timer[n].cap_config & msi_enable) == msi_enable);
}

static inline int
vhpet_timer_ioapic_pin(struct vhpet *vhpet, int n)
{
	/*
	 * If the timer is configured to use MSI then treat it as if the
	 * timer is not connected to the ioapic.
	 */
	if (vhpet_timer_msi_enabled(vhpet, n))
		return 0;

	return ((vhpet->timer[n].cap_config & HPET_TCNF_INT_ROUTE) >> 9);
}

static uint32_t
vhpet_counter(struct vhpet *vhpet, struct timespec *nowptr)
{
	uint32_t val;
	struct timespec now, delta;

	val = vhpet->countbase;

	if (vhpet_counter_enabled(vhpet)) {
		if (clock_gettime(CLOCK_MONOTONIC, &now))
			pr_dbg("clock_gettime returned: %s", strerror(errno));

		/* delta = now - countbase_ts */
		if (timespeccmp(&now, &vhpet->countbase_ts, <)) {
			pr_dbg("vhpet counter going backwards");
			vhpet->countbase_ts = now;
		}

		delta = now;
		timespecsub(&delta, &vhpet->countbase_ts);
		val += vhpet_ts_to_ticks(&delta);

		if (nowptr != NULL)
			*nowptr = now;
	} else {
		/*
		 * The timespec corresponding to the 'countbase' is
		 * meaningless when the counter is disabled. Warn if
		 * the caller wants to use it.
		 */
		if (nowptr) {
			pr_warn("vhpet unexpected nowptr");
			if (clock_gettime(CLOCK_MONOTONIC, nowptr))
				pr_dbg("clock_gettime returned: %s", strerror(errno));
		}
	}

	return val;
}

static void
vhpet_timer_clear_isr(struct vhpet *vhpet, int n)
{
	int pin;

	if (vhpet->isr & (1 << n)) {
		pin = vhpet_timer_ioapic_pin(vhpet, n);

		if (pin)
			vm_set_gsi_irq(vhpet->vm, pin, GSI_SET_LOW);
		else
			pr_dbg("vhpet t%d intr asserted without a valid intr route", n);

		vhpet->isr &= ~(1 << n);
	}
}

static inline bool
vhpet_periodic_timer(struct vhpet *vhpet, int n)
{
	return ((vhpet->timer[n].cap_config & HPET_TCNF_TYPE) != 0);
}

static inline bool
vhpet_timer_interrupt_enabled(struct vhpet *vhpet, int n)
{
	return ((vhpet->timer[n].cap_config & HPET_TCNF_INT_ENB) != 0);
}

static inline bool
vhpet_timer_enabled(struct vhpet *vhpet, int n)
{
	/* The timer is enabled when at least one of the two bits is set */
	return vhpet_timer_interrupt_enabled(vhpet, n) ||
	       vhpet_periodic_timer(vhpet, n);
}

static inline bool
vhpet_timer_running(struct vhpet *vhpet, int n)
{
	return vhpet_tmrarg(vhpet, n)->running;
}

static inline bool
vhpet_timer_edge_trig(struct vhpet *vhpet, int n)
{
	return (!vhpet_timer_msi_enabled(vhpet, n) &&
	        (vhpet->timer[n].cap_config & HPET_TCNF_INT_TYPE) == 0);
}

static void
vhpet_timer_interrupt(struct vhpet *vhpet, int n)
{
	int pin;

	/* If interrupts are not enabled for this timer then just return. */
	if (!vhpet_timer_interrupt_enabled(vhpet, n))
		return;

	/*
	 * If a level triggered interrupt is already asserted then just return.
	 */
	if (vhpet->isr & (1 << n)) {
		if (!vhpet_timer_msi_enabled(vhpet, n) &&
		    !vhpet_timer_edge_trig(vhpet, n)) {
			DPRINTF(("hpet t%d intr is already asserted\n", n));
			return;
		} else {
			pr_dbg("vhpet t%d intr asserted in %s mode", n,
					vhpet_timer_msi_enabled(vhpet, n) ?
					"msi" : "edge-triggered");
			vhpet->isr &= ~(1 << n);
		}
	}

	if (vhpet_timer_msi_enabled(vhpet, n)) {
		vm_lapic_msi(vhpet->vm, vhpet->timer[n].msireg >> 32,
				vhpet->timer[n].msireg & 0xffffffff);
		return;
	}

	pin = vhpet_timer_ioapic_pin(vhpet, n);

	if (pin == 0) {
		DPRINTF(("hpet t%d intr is not routed to ioapic\n", n));
		return;
	}

	if (vhpet_timer_edge_trig(vhpet, n)) {
		vm_set_gsi_irq(vhpet->vm, pin, GSI_RAISING_PULSE);
	} else {
		vhpet->isr |= 1 << n;
		vm_set_gsi_irq(vhpet->vm, pin, GSI_SET_HIGH);
	}
}

static void
vhpet_timer_handler(void *a, uint64_t nexp)
{
	int n;
	struct vhpet *vhpet;
	struct vhpet_timer_arg *arg;
	struct timespec now;
	struct itimerspec tmrts;
	uint64_t newexp;

	arg = a;
	vhpet = arg->vhpet;
	n = arg->timer_num;

	DPRINTF(("hpet t%d(%p) fired\n", n, arg));

	VHPET_LOCK();

	/* Bail if timer was destroyed */
	if (!vhpet->inited)
		goto done;

	/* Bail if timer was stopped */
	if (!arg->running) {
		DPRINTF(("hpet t%d(%p) already stopped\n", n, arg));
		if (!ts_is_zero(&vhpet->timer[n].expts)) {
			pr_warn("vhpet t%d stopped with an expiration time", n);
			ts_set_zero(&vhpet->timer[n].expts);
		}
		goto done;
	} else if (arg != vhpet_tmrarg(vhpet, n)) {
		pr_warn("vhpet t%d observes a stale timer arg", n);
		goto done;
	}

	vhpet_timer_interrupt(vhpet, n);

	if (clock_gettime(CLOCK_MONOTONIC, &now))
		pr_dbg("clock_gettime returned: %s", strerror(errno));

	if (acrn_timer_gettime(vhpet_tmr(vhpet, n), &tmrts))
		pr_dbg("acrn_timer_gettime returned: %s", strerror(errno));

	/* One-shot mode has a periodicity of 2^32 ticks */
	if (ts_is_zero(&tmrts.it_interval))
		pr_dbg("vhpet t%d has no periodicity", n);

	/*
	 * The actual expiration time will be slightly later than expts.
	 *
	 * This may cause spurious interrupts when stopping a timer but
	 * at least interrupts won't be completely lost.
	 */
	timespecadd(&tmrts.it_value, &now);
	vhpet->timer[n].expts = tmrts.it_value;

	/*
	 * Catch any remaining expirations that happened after being
	 * last consumed by the mevent_dispatch thread.
	 */
	if (read(vhpet_tmr(vhpet, n)->fd, &newexp, sizeof(newexp)) > 0)
		nexp += newexp;

	/*
	 * Periodic timer updates 'compval' upon expiration.
	 * Try to keep 'compval' as up-to-date as possible.
	 */
	vhpet->timer[n].compval += nexp * vhpet->timer[n].comprate;

done:
	VHPET_UNLOCK();
	return;
}

static void
vhpet_adjust_compval(struct vhpet *vhpet, int n, const struct timespec *now)
{
	uint32_t compval, comprate, compnext;
	struct timespec delta;
	uint64_t delta_ticks;

	compval = vhpet->timer[n].compval;
	comprate = vhpet->timer[n].comprate;

	if (!comprate || timespeccmp(&vhpet->timer[n].expts, now, >=))
		return;

	/* delta = now - expts */
	delta = *now;
	timespecsub(&delta, &vhpet->timer[n].expts);
	delta_ticks = vhpet_ts_to_ticks(&delta);

	/*
	 * Calculate the comparator value to be used for the next periodic
	 * interrupt.
	 *
	 * In this scenario 'counter' is ahead of 'compval' by at least
	 * 'comprate'. To find the next value to program into the
	 * accumulator we divide 'delta_ticks', the number space between
	 * 'compval + comprate' and 'counter', into 'comprate' sized units.
	 * The 'compval' is rounded up such that it stays "ahead" of
	 * 'counter'.
	 *
	 * There's a slight chance read() in vhpet_timer_handler() had
	 * already accomplished some of this just prior to calling this
	 * function.
	 */
	compnext = compval + (delta_ticks / comprate + 1) * comprate;

	vhpet->timer[n].compval = compnext;
}

static void
vhpet_stop_timer(struct vhpet *vhpet, int n, const struct timespec *now,
		bool adj_compval)
{
	struct vhpet_timer_arg *arg;

	if (!vhpet_timer_running(vhpet, n))
		return;

	if (ts_is_zero(&vhpet->timer[n].expts))
		pr_dbg("vhpet t%d is running without an expiration time", n);

	DPRINTF(("hpet t%d stopped\n", n));

	arg = vhpet_tmrarg(vhpet, n);
	arg->running = false;

	/* Cancel the existing timer */
	if (acrn_timer_settime(vhpet_tmr(vhpet, n), &zero_ts))
		pr_dbg("acrn_timer_settime returned: %s", strerror(errno));

	if (++vhpet->timer[n].tmridx == nitems(vhpet->timer[n].tmrlst))
		vhpet->timer[n].tmridx = 0;

	if (vhpet_timer_running(vhpet, n)) {
		pr_dbg("vhpet t%d timer %d is still running",
				n, vhpet->timer[n].tmridx);
		vhpet_stop_timer(vhpet, n, &zero_ts.it_value, false);
	}

	/*
	 * If the timer was scheduled to expire in the past but hasn't
	 * had a chance to execute yet then trigger the timer interrupt
	 * here. Failing to do so will result in a missed timer interrupt
	 * in the guest. This is especially bad in one-shot mode because
	 * the next interrupt has to wait for the counter to wrap around.
	 */
	if (!ts_is_zero(&vhpet->timer[n].expts)) {
		if (timespeccmp(&vhpet->timer[n].expts, now, <)) {
			DPRINTF(("hpet t%d interrupt triggered after "
					 "stopping timer\n", n));
			if (adj_compval)
				vhpet_adjust_compval(vhpet, n, now);
			vhpet_timer_interrupt(vhpet, n);
		}

		ts_set_zero(&vhpet->timer[n].expts);
	}
}

static void
vhpet_start_timer(struct vhpet *vhpet, int n, uint32_t counter,
		const struct timespec *now, bool adj_compval)
{
	struct itimerspec ts;
	uint32_t delta;
	struct vhpet_timer_arg *arg;

	vhpet_stop_timer(vhpet, n, now, adj_compval);

	DPRINTF(("hpet t%d started\n", n));

	/*
	 * It is the guest's responsibility to make sure that the
	 * comparator value is not in the "past". The hardware
	 * doesn't have any belt-and-suspenders to deal with this
	 * so we don't either.
	 */
	delta = vhpet->timer[n].compval - counter;
	vhpet_ticks_to_ts(delta, &ts.it_value);
	timespecadd(&ts.it_value, now);

	if (vhpet->timer[n].comprate != 0)
		vhpet_ticks_to_ts(vhpet->timer[n].comprate, &ts.it_interval);
	else	/* It takes 2^32 ticks to wrap around */
		vhpet_ticks_to_ts(1ULL << 32, &ts.it_interval);

	arg = vhpet_tmrarg(vhpet, n);
	arg->running = true;

	/* Arm the new timer */
	if (acrn_timer_settime_abs(vhpet_tmr(vhpet, n), &ts))
		pr_dbg("acrn_timer_settime_abs returned: %s",
				strerror(errno));

	vhpet->timer[n].expts = ts.it_value;
}

static void
vhpet_restart_timer(struct vhpet *vhpet, int n, bool adj_compval)
{
	uint32_t counter;
	struct timespec now;

	/*
	 * Restart the specified timer based on the current value of
	 * the main counter.
	 */
	counter = vhpet_counter(vhpet, &now);
	vhpet_start_timer(vhpet, n, counter, &now, adj_compval);
}

static void
vhpet_start_counting(struct vhpet *vhpet)
{
	int i;

	if (clock_gettime(CLOCK_MONOTONIC, &vhpet->countbase_ts))
		pr_dbg("clock_gettime returned: %s", strerror(errno));

	/* Restart the timers based on the main counter base value */
	for (i = 0; i < VHPET_NUM_TIMERS; i++) {
		if (vhpet_timer_enabled(vhpet, i))
			vhpet_start_timer(vhpet, i, vhpet->countbase,
					&vhpet->countbase_ts, true);
		else if (vhpet_timer_running(vhpet, i)) {
			pr_dbg("vhpet t%d's timer is disabled but running", i);
			vhpet_stop_timer(vhpet, i, &zero_ts.it_value, false);
		}
	}
}

static void
vhpet_stop_counting(struct vhpet *vhpet, uint32_t counter,
		const struct timespec *now)
{
	int i;

	/* Update the main counter base value */
	vhpet->countbase = counter;

	for (i = 0; i < VHPET_NUM_TIMERS; i++) {
		if (vhpet_timer_enabled(vhpet, i))
			vhpet_stop_timer(vhpet, i, now, true);
		else if (vhpet_timer_running(vhpet, i)) {
			pr_dbg("vhpet t%d's timer is disabled but running", i);
			vhpet_stop_timer(vhpet, i, &zero_ts.it_value, false);
		}
	}
}

static inline void
update_register(uint64_t *const regptr, const uint64_t data,
		const uint64_t mask)
{
	*regptr &= ~mask;
	*regptr |= (data & mask);
}

static void
vhpet_timer_update_config(struct vhpet *vhpet, int n, uint64_t data,
		uint64_t mask)
{
	int old_pin, new_pin;
	uint32_t allowed_irqs;
	uint64_t oldval, newval;
	struct timespec now;

	if (vhpet_timer_msi_enabled(vhpet, n) ||
	    vhpet_timer_edge_trig(vhpet, n)) {
		if (vhpet->isr & (1 << n)) {
			pr_dbg("vhpet t%d intr asserted in %s mode", n,
					vhpet_timer_msi_enabled(vhpet, n) ?
					"msi" : "edge-triggered");
			vhpet->isr &= ~(1 << n);
		}
	}

	old_pin = vhpet_timer_ioapic_pin(vhpet, n);
	oldval = vhpet->timer[n].cap_config;

	newval = oldval;
	update_register(&newval, data, mask);
	newval &= ~(HPET_TCAP_RO_MASK | HPET_TCNF_32MODE);
	newval |= oldval & HPET_TCAP_RO_MASK;

	if (newval == oldval)
		return;

	vhpet->timer[n].cap_config = newval;
	DPRINTF(("hpet t%d cap_config set to 0x%016lx\n", n, newval));

	if ((oldval ^ newval) & (HPET_TCNF_TYPE | HPET_TCNF_INT_ENB)) {
		if (!vhpet_periodic_timer(vhpet, n))
			vhpet->timer[n].comprate = 0;

		if (vhpet_counter_enabled(vhpet)) {
			/*
			 * Stop the timer if both bits are now cleared
			 *
			 * Else, restart the timer if:
			 *   - The timer was stopped, or
			 *   - HPET_TCNF_TYPE is being toggled
			 *
			 * Else, no-op
			 *   - Timer remains in periodic mode
			 */
			if (!vhpet_timer_enabled(vhpet, n)) {
				if (clock_gettime(CLOCK_MONOTONIC, &now))
					pr_dbg("clock_gettime returned: %s", strerror(errno));
				vhpet_stop_timer(vhpet, n, &now, true);
			} else if (!(oldval & (HPET_TCNF_TYPE | HPET_TCNF_INT_ENB)) ||
			           ((oldval ^ newval) & HPET_TCNF_TYPE))
				vhpet_restart_timer(vhpet, n, true);
		}
	}

	/*
	 * Validate the interrupt routing in the HPET_TCNF_INT_ROUTE field.
	 * If it does not match the bits set in HPET_TCAP_INT_ROUTE then set
	 * it to the default value of 0.
	 */
	allowed_irqs = vhpet->timer[n].cap_config >> 32;
	new_pin = vhpet_timer_ioapic_pin(vhpet, n);

	if (new_pin != 0 && (allowed_irqs & (1 << new_pin)) == 0) {
		WPRINTF(("hpet t%d configured invalid irq %d, "
		         "allowed_irqs 0x%08x\n", n, new_pin, allowed_irqs));
		new_pin = 0;
		vhpet->timer[n].cap_config &= ~HPET_TCNF_INT_ROUTE;
	}

	/*
	 * If the timer's ISR bit is set then clear it in the following cases:
	 * - interrupt is disabled
	 * - interrupt type is changed from level to edge or fsb.
	 * - interrupt routing is changed
	 *
	 * This is to ensure that this timer's level triggered interrupt does
	 * not remain asserted forever.
	 */
	if (vhpet->isr & (1 << n)) {
		if (!old_pin) {
			pr_dbg("vhpet t%d intr asserted without a valid intr route", n);
			vhpet->isr &= ~(1 << n);
		} else if (!vhpet_timer_interrupt_enabled(vhpet, n) ||
		    vhpet_timer_msi_enabled(vhpet, n) ||
		    vhpet_timer_edge_trig(vhpet, n) ||
		    new_pin != old_pin) {
			DPRINTF(("hpet t%d isr cleared due to "
			         "configuration change\n", n));
			vm_set_gsi_irq(vhpet->vm, old_pin, GSI_SET_LOW);
			vhpet->isr &= ~(1 << n);
		}
	}
}

static int
vhpet_mmio_write(struct vhpet *vhpet, int vcpuid, uint64_t gpa, uint64_t *wval,
		int size)
{
	uint64_t data, mask, oldval, val64;
	uint32_t isr_clear_mask, old_compval, old_comprate, counter;
	struct timespec now, *nowptr;
	int offset, i;

	offset = gpa - VHPET_BASE;

	/* Accesses to the HPET should be 4 or 8 bytes wide */
	switch (size) {
	case 8:
		mask = 0xffffffffffffffff;
		data = *wval;
		break;
	case 4:
		mask = 0xffffffff;
		data = *wval;
		if ((offset & 0x4) != 0) {
			mask <<= 32;
			data <<= 32;
		}
		break;
	default:
		WPRINTF(("hpet invalid mmio write: "
		         "offset 0x%08x, size %d\n", offset, size));
		goto done;
	}

	/* Access to the HPET should be naturally aligned to its width */
	if (offset & (size - 1)) {
		WPRINTF(("hpet invalid mmio write: "
		         "offset 0x%08x, size %d\n", offset, size));
		goto done;
	}

	if (offset == HPET_CONFIG || offset == HPET_CONFIG + 4) {
		/*
		 * Get the most recent value of the counter before updating
		 * the 'config' register. If the HPET is going to be disabled
		 * then we need to update 'countbase' with the value right
		 * before it is disabled.
		 */
		nowptr = vhpet_counter_enabled(vhpet) ? &now : NULL;
		counter = vhpet_counter(vhpet, nowptr);
		oldval = vhpet->config;
		update_register(&vhpet->config, data, mask);

		/*
		 * LegacyReplacement Routing is not supported so clear the
		 * bit along with the reserved bits explicitly.
		 */
		vhpet->config &= HPET_CNF_ENABLE;

		if ((oldval ^ vhpet->config) & HPET_CNF_ENABLE) {
			if (vhpet_counter_enabled(vhpet)) {
				vhpet_start_counting(vhpet);
				DPRINTF(("hpet enabled\n"));
			} else {
				vhpet_stop_counting(vhpet, counter, &now);
				DPRINTF(("hpet disabled\n"));
			}
		}
		goto done;
	}

	if (offset == HPET_ISR || offset == HPET_ISR + 4) {
		/* Top 32 bits are reserved */
		isr_clear_mask = vhpet->isr & data;
		for (i = 0; i < VHPET_NUM_TIMERS; i++) {
			if ((isr_clear_mask & (1 << i)) != 0) {
				DPRINTF(("hpet t%d isr cleared\n", i));
				vhpet_timer_clear_isr(vhpet, i);
			}
		}
		goto done;
	}

	if (offset == HPET_MAIN_COUNTER || offset == HPET_MAIN_COUNTER + 4) {
		/* Zero-extend the counter to 64-bits before updating it */
		val64 = vhpet_counter(vhpet, NULL);
		update_register(&val64, data, mask);
		vhpet->countbase = val64;
		if (vhpet_counter_enabled(vhpet))
			vhpet_start_counting(vhpet);
		goto done;
	}

	for (i = 0; i < VHPET_NUM_TIMERS; i++) {
		if (offset == HPET_TIMER_CAP_CNF(i) ||
		    offset == HPET_TIMER_CAP_CNF(i) + 4) {
			vhpet_timer_update_config(vhpet, i, data, mask);
			break;
		}

		if (offset == HPET_TIMER_COMPARATOR(i) ||
		    offset == HPET_TIMER_COMPARATOR(i) + 4) {
			old_compval = vhpet->timer[i].compval;
			old_comprate = vhpet->timer[i].comprate;

			if (vhpet_periodic_timer(vhpet, i)) {
				/*
				 * In periodic mode, writes to the comparator
				 * change the 'compval' register only if the
				 * HPET_TCNF_VAL_SET bit is set in the config
				 * register.
				 */
				val64 = vhpet->timer[i].comprate;
				update_register(&val64, data, mask);
				vhpet->timer[i].comprate = val64;

				if ((vhpet->timer[i].cap_config &
				     HPET_TCNF_VAL_SET) != 0)
					vhpet->timer[i].compval = val64;
			} else {
				if (vhpet->timer[i].comprate) {
					pr_warn("vhpet t%d's comprate is %u in non-periodic mode"
							" - should be 0", i, vhpet->timer[i].comprate);
					vhpet->timer[i].comprate = 0;
				}
				val64 = vhpet->timer[i].compval;
				update_register(&val64, data, mask);
				vhpet->timer[i].compval = val64;
			}

			vhpet->timer[i].cap_config &= ~HPET_TCNF_VAL_SET;

			if (vhpet->timer[i].compval != old_compval ||
			    vhpet->timer[i].comprate != old_comprate) {
				if (vhpet_counter_enabled(vhpet) &&
				    vhpet_timer_enabled(vhpet, i))
					vhpet_restart_timer(vhpet, i, false);
			}
			break;
		}

		if (offset == HPET_TIMER_FSB_VAL(i) ||
		    offset == HPET_TIMER_FSB_ADDR(i)) {
			update_register(&vhpet->timer[i].msireg, data, mask);
			break;
		}
	}

	if (i >= VHPET_NUM_TIMERS)
		WPRINTF(("hpet invalid mmio write: "
		         "offset 0x%08x, size %d\n", offset, size));

done:
	return 0;
}

static int
vhpet_mmio_read(struct vhpet *vhpet, int vcpuid, uint64_t gpa, uint64_t *rval,
		int size)
{
	int offset, i;
	uint64_t data = 0;

	offset = gpa - VHPET_BASE;

	/*
	 * Accesses to the HPET should be:
	 *   - 4 or 8 bytes wide
	 *   - naturally aligned to its width
	 */
	if ((size != 4 && size != 8) || (offset & (size - 1))) {
		WPRINTF(("hpet invalid mmio read: "
		         "offset 0x%08x, size %d\n", offset, size));
		goto done;
	}

	if (offset == HPET_CAPABILITIES || offset == HPET_CAPABILITIES + 4) {
		data = vhpet_capabilities();
		goto done;
	}

	if (offset == HPET_CONFIG || offset == HPET_CONFIG + 4) {
		data = vhpet->config;
		goto done;
	}

	if (offset == HPET_ISR || offset == HPET_ISR + 4) {
		data = vhpet->isr;
		goto done;
	}

	if (offset == HPET_MAIN_COUNTER || offset == HPET_MAIN_COUNTER + 4) {
		data = vhpet_counter(vhpet, NULL);
		goto done;
	}

	for (i = 0; i < VHPET_NUM_TIMERS; i++) {
		if (offset == HPET_TIMER_CAP_CNF(i) ||
		    offset == HPET_TIMER_CAP_CNF(i) + 4) {
			data = vhpet->timer[i].cap_config;
			break;
		}

		if (offset == HPET_TIMER_COMPARATOR(i) ||
		    offset == HPET_TIMER_COMPARATOR(i) + 4) {
			data = vhpet->timer[i].compval;
			break;
		}

		if (offset == HPET_TIMER_FSB_VAL(i) ||
		    offset == HPET_TIMER_FSB_ADDR(i)) {
			data = vhpet->timer[i].msireg;
			break;
		}
	}

	if (i >= VHPET_NUM_TIMERS) {
		WPRINTF(("hpet invalid mmio read: "
		         "offset 0x%08x, size %d\n", offset, size));
	}

done:
	if (size == 4) {
		if (offset & 0x4)
			data >>= 32;
	}

	*rval = data;
	return 0;
}

static int
vhpet_handler(struct vmctx *ctx, int vcpu, int dir, uint64_t addr,
		int size, uint64_t *val, void *arg1, long arg2)
{
	struct vhpet *vhpet = arg1;
	int error;

	VHPET_LOCK();

	if (!vhpet->inited) {
		error = -EINVAL;
		goto done;
	}

	error = ((dir == MEM_F_READ) ? vhpet_mmio_read : vhpet_mmio_write)(
				vhpet, vcpu, addr, val, size);

done:
	VHPET_UNLOCK();
	return error;
}

static void
vhpet_deinit_timers(struct vhpet *vhpet)
{
	int i, j;
	struct acrn_timer *tmr;

	for (i = 0; i < VHPET_NUM_TIMERS; i++) {
		for (j = 0; j < nitems(vhpet->timer[i].tmrlst); j++) {
			tmr = &vhpet->timer[i].tmrlst[j].t;
			acrn_timer_deinit(tmr);
		}
	}
}

int
vhpet_init(struct vmctx *ctx)
{
	int error = 0, pincount, i, j;
	struct vhpet *vhpet;
	uint64_t allowed_irqs;
	struct vhpet_timer_arg *arg;
	struct acrn_timer *tmr;

	vhpet = vhpet_instance();

	VHPET_LOCK();

	if (vhpet->inited) {
		WPRINTF(("hpet already initialized!\n"));
		error = -EINVAL;
		goto done;
	}

	memset(vhpet, 0, sizeof(*vhpet));
	vhpet->vm = ctx;

	pincount = VIOAPIC_RTE_NUM;

	if (pincount >= 32)
		allowed_irqs = 0xff000000;	/* irqs 24-31 */
	else if (pincount >= 20)
		allowed_irqs = 0xf << (pincount - 4);	/* 4 upper irqs */
	else
		allowed_irqs = 0;

	/*
	 * Initialize HPET timer hardware state.
	 */
	for (i = 0; i < VHPET_NUM_TIMERS; i++) {
		vhpet->timer[i].cap_config = allowed_irqs << 32;
		vhpet->timer[i].cap_config |= HPET_TCAP_PER_INT;
		vhpet->timer[i].cap_config |= HPET_TCAP_FSB_INT_DEL;
		vhpet->timer[i].compval = 0xffffffff;

		for (j = 0; j < nitems(vhpet->timer[i].tmrlst); j++) {
			arg = &vhpet->timer[i].tmrlst[j].a;
			arg->vhpet = vhpet;
			arg->timer_num = i;

			tmr = &vhpet->timer[i].tmrlst[j].t;
			tmr->clockid = CLOCK_MONOTONIC;
			error = acrn_timer_init(tmr, vhpet_timer_handler, arg);

			if (error) {
				vhpet_deinit_timers(vhpet);
				goto done;
			}
		}
	}

	vhpet_mr.handler = vhpet_handler;
	vhpet_mr.arg1 = vhpet;
	vhpet_mr.arg2 = 0;

	error = register_mem(&vhpet_mr);

	if (error) {
		vhpet_deinit_timers(vhpet);
		goto done;
	}

	vhpet->inited = true;

done:
	VHPET_UNLOCK();
	return error;
}

void
vhpet_deinit(struct vmctx *ctx)
{
	struct vhpet *vhpet;

	vhpet = vhpet_instance();

	VHPET_LOCK();

	if (!vhpet->inited)
		goto done;

	vhpet_deinit_timers(vhpet);
	unregister_mem(&vhpet_mr);

	vhpet->inited = false;

done:
	VHPET_UNLOCK();
}
