/*-
 * Copyright (c) 2014, 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 unmodified, 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 THE AUTHOR ``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 AUTHOR 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 <pthread.h>
#include <string.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <errno.h>

#include "vmmapi.h"
#include "inout.h"
#include "mc146818rtc.h"
#include "rtc.h"
#include "mevent.h"
#include "timer.h"
#include "acpi.h"
#include "lpc.h"
#include "vm_event.h"

#include "log.h"

/* #define DEBUG_RTC */
#ifdef DEBUG_RTC
# define RTC_DEBUG   pr_dbg
#else
# define RTC_DEBUG(format, ...)      do { } while (0)
#endif

/* Register layout of the RTC */
struct rtcdev {
	uint8_t	sec;
	uint8_t	alarm_sec;
	uint8_t	min;
	uint8_t	alarm_min;
	uint8_t	hour;
	uint8_t	alarm_hour;
	uint8_t	day_of_week;
	uint8_t	day_of_month;
	uint8_t	month;
	uint8_t	year;
	uint8_t	reg_a;
	uint8_t	reg_b;
	uint8_t	reg_c;
	uint8_t	reg_d;
	uint8_t	nvram[36];
	uint8_t	century;
	uint8_t	nvram2[128 - 51];
} __packed;

struct vrtc {
	struct vmctx *vm;
	pthread_mutex_t	mtx;
	struct acrn_timer update_timer;     /* timer for update interrupt */
	struct acrn_timer periodic_timer;   /* timer for periodic interrupt */
	u_int		addr;               /* RTC register to read or write */
	time_t		base_uptime;
	time_t		base_rtctime;
	time_t		halted_rtctime;
	struct rtcdev	rtcdev;
};

/*
 * Structure to hold the values typically reported by time-of-day clocks.
 * This can be passed to the generic conversion functions to be converted
 * to a struct timespec.
 */
struct clktime {
	int	year;			/* year (4 digit year) */
	int	mon;			/* month (1 - 12) */
	int	day;			/* day (1 - 31) */
	int	hour;			/* hour (0 - 23) */
	int	min;			/* minute (0 - 59) */
	int	sec;			/* second (0 - 59) */
	int	dow;			/* day of week (0 - 6; 0 = Sunday) */
	long nsec;			/* nano seconds */
};

/* Some handy constants. */
#define SECDAY		(24 * 60 * 60)
#define SECYR		(SECDAY * 365)

/* Traditional POSIX base year */
#define	POSIX_BASE_YEAR	1970

#define	SBT_1S	((time_t)1000000000LL)
#define	SBT_1M	(SBT_1S * 60)
#define	SBT_1MS	(SBT_1S / 1000)
#define	SBT_1US	(SBT_1S / 1000000)
#define	SBT_1NS	(SBT_1S / 1000000000)
#define	SBT_MAX	0x7fffffffffffffffLL

/*
 * RTC time is considered "broken" if:
 * - RTC updates are halted by the guest
 * - RTC date/time fields have invalid values
 */
#define	VRTC_BROKEN_TIME	((time_t)-1)

/* signo of timers created by vrtc */
#define	VRTC_TIMER_SIGNO	SIGALRM

#define	RTC_IRQ			8
#define	RTCSB_BIN		0x04
#define	RTCSB_ALL_INTRS		(RTCSB_UINTR | RTCSB_AINTR | RTCSB_PINTR)
#define	rtc_halted(rtc)		((rtc->rtcdev.reg_b & RTCSB_HALT) != 0)
#define	aintr_enabled(rtc)	(((rtc)->rtcdev.reg_b & RTCSB_AINTR) != 0)
#define	pintr_enabled(rtc)	(((rtc)->rtcdev.reg_b & RTCSB_PINTR) != 0)
#define	uintr_enabled(rtc)	(((rtc)->rtcdev.reg_b & RTCSB_UINTR) != 0)

/*--------------------------------------------------------------------*
 * Generic routines to convert between a POSIX date
 * (seconds since 1/1/1970) and yr/mo/day/hr/min/sec
 * Derived from NetBSD arch/hp300/hp300/clock.c
 */

#define	FEBRUARY	2
#define	days_in_year(y)	(leapyear(y) ? 366 : 365)
#define	days_in_month(y, m) \
	(month_days[(m) - 1] + (m == FEBRUARY ? leapyear(y) : 0))
/* Day of week. Days are counted from 1/1/1970, which was a Thursday */
#define	day_of_week(days)	(((days) + 4) % 7)

static const int month_days[12] = {
	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};


/*
 * This inline avoids some unnecessary modulo operations
 * as compared with the usual macro:
 *   ( ((year % 4) == 0 &&
 *      (year % 100) != 0) ||
 *     ((year % 400) == 0) )
 * It is otherwise equivalent.
 */
static int
leapyear(int year)
{
	int rv = 0;

	if ((year & 3) == 0) {
		rv = 1;
		if ((year % 100) == 0) {
			rv = 0;
			if ((year % 400) == 0)
				rv = 1;
		}
	}
	return rv;
}

u_char const bin2bcd_data[] = {
	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
	0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
	0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
	0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
	0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
	0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
	0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
	0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
	0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99
};

static void vrtc_set_reg_c(struct vrtc *vrtc, uint8_t newval);

static int rtc_flag_broken_time = 1;

static inline int
divider_enabled(int reg_a)
{
	/*
	 * The RTC is counting only when dividers are not held in reset.
	 */
	return ((reg_a & 0x70) == 0x20);
}

static inline int
update_enabled(struct vrtc *vrtc)
{
	/*
	 * RTC date/time can be updated only if:
	 * - divider is not held in reset
	 * - guest has not disabled updates
	 * - the date/time fields have valid contents
	 */
	if (!divider_enabled(vrtc->rtcdev.reg_a))
		return false;

	if (rtc_halted(vrtc))
		return false;

	if (vrtc->base_rtctime == VRTC_BROKEN_TIME)
		return false;

	return true;
}

/* monotonic time is number of seconds that the system has been running
 * since it was booted. It is none setable. It is more suitable to be used
 * as base time.
 */
static time_t monotonic_time(void)
{
	struct timespec ts;
	clock_gettime(CLOCK_MONOTONIC, &ts);
	return ts.tv_sec;
}

static time_t
vrtc_curtime(struct vrtc *vrtc, time_t *basetime)
{
	time_t now, delta;
	time_t t, secs;

	t = vrtc->base_rtctime;
	*basetime = vrtc->base_uptime;
	if (update_enabled(vrtc)) {
		now = monotonic_time();
		delta = now - vrtc->base_uptime;
		secs = delta;
		t += secs;
		*basetime += secs;
	}
	return t;
}

static int
clk_ct_to_ts(struct clktime *ct, struct timespec *ts)
{
	int i, year, days;

	year = ct->year;

	/* Sanity checks. */
	if (ct->mon < 1 || ct->mon > 12 || ct->day < 1 ||
			ct->day > days_in_month(year, ct->mon) ||
			ct->hour > 23 ||  ct->min > 59 || ct->sec > 59 ||
			(sizeof(time_t) == 4 && year > 2037)) {
		/* time_t overflow */
		return -1;
	}

	/*
	 * Compute days since start of time
	 * First from years, then from months.
	 */
	days = 0;
	for (i = POSIX_BASE_YEAR; i < year; i++)
		days += days_in_year(i);

	/* Months */
	for (i = 1; i < ct->mon; i++)
		days += days_in_month(year, i);
	days += (ct->day - 1);

	ts->tv_sec = (((time_t)days * 24 + ct->hour) * 60 + ct->min) * 60 +
		ct->sec;
	ts->tv_nsec = ct->nsec;
	return 0;
}

static void
clk_ts_to_ct(struct timespec *ts, struct clktime *ct)
{
	time_t i, year, days;
	time_t rsec;	/* remainder seconds */
	time_t secs;

	secs = ts->tv_sec;
	days = secs / SECDAY;
	rsec = secs % SECDAY;

	ct->dow = day_of_week(days);

	/* Subtract out whole years, counting them in i. */
	for (year = POSIX_BASE_YEAR; days >= days_in_year(year); year++)
		days -= days_in_year(year);
	ct->year = year;

	/* Subtract out whole months, counting them in i. */
	for (i = 1; days >= days_in_month(year, i); i++)
		days -= days_in_month(year, i);
	ct->mon = i;

	/* Days are what is left over (+1) from all that. */
	ct->day = days + 1;

	/* Hours, minutes, seconds are easy */
	ct->hour = rsec / 3600;
	rsec = rsec % 3600;
	ct->min  = rsec / 60;
	rsec = rsec % 60;
	ct->sec  = rsec;
	ct->nsec = ts->tv_nsec;
}

static inline uint8_t
rtcset(struct rtcdev *rtc, int val)
{
	return ((rtc->reg_b & RTCSB_BIN) ? val : bin2bcd_data[val]);
}

static time_t
vrtc_freq(struct vrtc *vrtc)
{
	int ratesel;

	static time_t pf[16] = {
		0,
		SBT_1S / 256,
		SBT_1S / 128,
		SBT_1S / 8192,
		SBT_1S / 4096,
		SBT_1S / 2048,
		SBT_1S / 1024,
		SBT_1S / 512,
		SBT_1S / 256,
		SBT_1S / 128,
		SBT_1S / 64,
		SBT_1S / 32,
		SBT_1S / 16,
		SBT_1S / 8,
		SBT_1S / 4,
		SBT_1S / 2,
	};

	/*
	 * If both periodic and alarm interrupts are enabled then use the
	 * periodic frequency to drive the callout. The minimum periodic
	 * frequency (2 Hz) is higher than the alarm frequency (1 Hz) so
	 * piggyback the alarm on top of it. The same argument applies to
	 * the update interrupt.
	 */
	if (pintr_enabled(vrtc) && divider_enabled(vrtc->rtcdev.reg_a)) {
		ratesel = vrtc->rtcdev.reg_a & 0xf;
		return pf[ratesel];
	} else if (aintr_enabled(vrtc) && update_enabled(vrtc))
		return SBT_1S;
	else if (uintr_enabled(vrtc) && update_enabled(vrtc))
		return SBT_1S;
	else
		return 0;
}

static int
rtcget(struct rtcdev *rtc, int val, int *retval)
{
	uint8_t upper, lower;

	if (rtc->reg_b & RTCSB_BIN) {
		*retval = val;
		return 0;
	}

	lower = val & 0xf;
	upper = (val >> 4) & 0xf;

	if (lower > 9 || upper > 9)
		return -1;

	*retval = upper * 10 + lower;
	return 0;
}

static void
secs_to_rtc(time_t rtctime, struct vrtc *vrtc, int force_update)
{
	struct clktime ct;
	struct timespec ts;
	struct rtcdev *rtc;
	int hour;

	if (rtctime < 0)
		/*VRTC_BROKEN_TIME case*/
		return;

	/*
	 * If the RTC is halted then the guest has "ownership" of the
	 * date/time fields. Don't update the RTC date/time fields in
	 * this case (unless forced).
	 */
	if (rtc_halted(vrtc) && !force_update)
		return;

	ts.tv_sec = rtctime;
	ts.tv_nsec = 0;
	clk_ts_to_ct(&ts, &ct);

	if ((ct.sec < 0 || ct.sec > 59) || (ct.min < 0 || ct.min > 59)
			|| (ct.hour < 0 || ct.hour > 23) || (ct.dow < 0 || ct.dow > 6)
			|| (ct.day < 1 || ct.day > 31) || (ct.mon < 1 || ct.mon > 12)
			|| (ct.year < POSIX_BASE_YEAR))
		return;

	rtc = &vrtc->rtcdev;
	rtc->sec = rtcset(rtc, ct.sec);
	rtc->min = rtcset(rtc, ct.min);

	if (rtc->reg_b & RTCSB_24HR) {
		hour = ct.hour;
	} else {
		/*
		 * Convert to the 12-hour format.
		 */
		switch (ct.hour) {
		case 0:			/* 12 AM */
		case 12:		/* 12 PM */
			hour = 12;
			break;
		default:
			/*
			 * The remaining 'ct.hour' values are interpreted as:
			 * [1  - 11] ->  1 - 11 AM
			 * [13 - 23] ->  1 - 11 PM
			 */
			hour = ct.hour % 12;
			break;
		}
	}

	rtc->hour = rtcset(rtc, hour);

	if ((rtc->reg_b & RTCSB_24HR) == 0 && ct.hour >= 12)
		rtc->hour |= 0x80;	    /* set MSB to indicate PM */

	rtc->day_of_week = rtcset(rtc, ct.dow + 1);
	rtc->day_of_month = rtcset(rtc, ct.day);
	rtc->month = rtcset(rtc, ct.mon);
	rtc->year = rtcset(rtc, ct.year % 100);
	rtc->century = rtcset(rtc, ct.year / 100);
}

static time_t
rtc_to_secs(struct vrtc *vrtc)
{
	struct clktime ct;
	struct timespec ts;
	struct rtcdev *rtc;
	int century, error, hour, pm, year;

	rtc = &vrtc->rtcdev;
	bzero(&ct, sizeof(struct clktime));
	error = rtcget(rtc, rtc->sec, &ct.sec);
	if (error || ct.sec < 0 || ct.sec > 59) {
		RTC_DEBUG("Invalid RTC sec %#x/%d\n", rtc->sec, ct.sec);
		goto fail;
	}

	error = rtcget(rtc, rtc->min, &ct.min);
	if (error || ct.min < 0 || ct.min > 59) {
		RTC_DEBUG("Invalid RTC min %#x/%d\n", rtc->min, ct.min);
		goto fail;
	}

	pm = 0;
	hour = rtc->hour;
	if ((rtc->reg_b & RTCSB_24HR) == 0) {
		if (hour & 0x80) {
			hour &= ~0x80;
			pm = 1;
		}
	}
	error = rtcget(rtc, hour, &ct.hour);
	if ((rtc->reg_b & RTCSB_24HR) == 0) {
		if (ct.hour >= 1 && ct.hour <= 12) {
			/*
			 * Convert from 12-hour format to internal 24-hour
			 * representation as follows:
			 *
			 *    12-hour format		ct.hour
			 *	12	AM		0
			 *	1 - 11	AM		1 - 11
			 *	12	PM		12
			 *	1 - 11	PM		13 - 23
			 */
			if (ct.hour == 12)
				ct.hour = 0;
			if (pm)
				ct.hour += 12;
		} else {
			RTC_DEBUG("Invalid RTC 12-hour format %#x/%d\n",
					rtc->hour, ct.hour);
			goto fail;
		}
	}

	if (error || ct.hour < 0 || ct.hour > 23) {
		RTC_DEBUG("Invalid RTC hour %#x/%d\n", rtc->hour, ct.hour);
		goto fail;
	}

	/*
	 * Ignore 'rtc->dow' because some guests like Linux don't bother
	 * setting it at all while others like OpenBSD/i386 set it incorrectly.
	 *
	 * clock_ct_to_ts() does not depend on 'ct.dow' anyways so ignore it.
	 */
	ct.dow = -1;

	error = rtcget(rtc, rtc->day_of_month, &ct.day);
	if (error || ct.day < 1 || ct.day > 31) {
		RTC_DEBUG("Invalid RTC mday %#x/%d\n", rtc->day_of_month,
				ct.day);
		goto fail;
	}

	error = rtcget(rtc, rtc->month, &ct.mon);
	if (error || ct.mon < 1 || ct.mon > 12) {
		RTC_DEBUG("Invalid RTC month %#x/%d\n", rtc->month, ct.mon);
		goto fail;
	}

	error = rtcget(rtc, rtc->year, &year);
	if (error || year < 0 || year > 99) {
		RTC_DEBUG("Invalid RTC year %#x/%d\n", rtc->year, year);
		goto fail;
	}

	error = rtcget(rtc, rtc->century, &century);
	ct.year = century * 100 + year;
	if (error || ct.year < POSIX_BASE_YEAR) {
		RTC_DEBUG("Invalid RTC century %#x/%d\n", rtc->century,
				ct.year);
		goto fail;
	}

	error = clk_ct_to_ts(&ct, &ts);
	if (error || ts.tv_sec < 0) {
		RTC_DEBUG("Invalid RTC clocktime.date %04d-%02d-%02d\n",
				ct.year, ct.mon, ct.day);
		RTC_DEBUG("Invalid RTC clocktime.time %02d:%02d:%02d\n",
				ct.hour, ct.min, ct.sec);
		goto fail;
	}
	return ts.tv_sec;		/* success */
fail:
	/*
	 * Stop updating the RTC if the date/time fields programmed by
	 * the guest are invalid.
	 */
	RTC_DEBUG("Invalid RTC date/time programming detected\n");
	return VRTC_BROKEN_TIME;
}

static void
vrtc_start_timer(struct acrn_timer *timer, time_t sec, time_t nsec)
{
	struct itimerspec ts;

	/*setting the interval time*/
	ts.it_interval.tv_sec = sec;
	ts.it_interval.tv_nsec = nsec;
	/*set the delay time it will be started when timer_setting*/
	ts.it_value.tv_sec = sec;
	ts.it_value.tv_nsec = nsec;
	acrn_timer_settime(timer, &ts);
}

static int
vrtc_time_update(struct vrtc *vrtc, time_t newtime, time_t newbase)
{
	struct rtcdev *rtc;
	time_t oldtime;
	uint8_t alarm_sec, alarm_min, alarm_hour;

	rtc = &vrtc->rtcdev;
	alarm_sec = rtc->alarm_sec;
	alarm_min = rtc->alarm_min;
	alarm_hour = rtc->alarm_hour;

	oldtime = vrtc->base_rtctime;
	RTC_DEBUG("Updating RTC secs from %#lx to %#lx\n",
			oldtime, newtime);

	RTC_DEBUG("Updating RTC base uptime from %#lx to %#lx\n",
			vrtc->base_uptime, newbase);
	vrtc->base_uptime = newbase;

	if (newtime == oldtime)
		return 0;

	/*
	 * If 'newtime' indicates that RTC updates are disabled then just
	 * record that and return. There is no need to do alarm interrupt
	 * processing in this case.
	 */
	if (newtime == VRTC_BROKEN_TIME) {
		vrtc->base_rtctime = VRTC_BROKEN_TIME;
		return 0;
	}

	/*
	 * Return an error if RTC updates are halted by the guest.
	 */
	if (rtc_halted(vrtc)) {
		RTC_DEBUG("RTC update halted by guest\n");
		return -1;
	}

	do {
		/*
		 * If the alarm interrupt is enabled and 'oldtime' is valid
		 * then visit all the seconds between 'oldtime' and 'newtime'
		 * to check for the alarm condition.
		 *
		 * Otherwise move the RTC time forward directly to 'newtime'.
		 */
		if (aintr_enabled(vrtc) && oldtime != VRTC_BROKEN_TIME)
			vrtc->base_rtctime++;
		else
			vrtc->base_rtctime = newtime;

		if (aintr_enabled(vrtc)) {
			/*
			 * Update the RTC date/time fields before checking
			 * if the alarm conditions are satisfied.
			 */
			secs_to_rtc(vrtc->base_rtctime, vrtc, 0);

			if ((alarm_sec >= 0xC0 || alarm_sec == rtc->sec) &&
				(alarm_min >= 0xC0 || alarm_min == rtc->min) &&
					(alarm_hour >= 0xC0 ||
					 alarm_hour == rtc->hour)) {
				vrtc_set_reg_c(vrtc, rtc->reg_c | RTCIR_ALARM);
			}
		}
	} while (vrtc->base_rtctime != newtime);

	if (uintr_enabled(vrtc))
		vrtc_set_reg_c(vrtc, rtc->reg_c | RTCIR_UPDATE);

	return 0;
}

static void
vrtc_periodic_timer(void *arg, uint64_t nexp)
{
	struct vrtc *vrtc = arg;

	pthread_mutex_lock(&vrtc->mtx);

	if (pintr_enabled(vrtc))
		vrtc_set_reg_c(vrtc, vrtc->rtcdev.reg_c | RTCIR_PERIOD);

	pthread_mutex_unlock(&vrtc->mtx);
}

static void
vrtc_update_timer(void *arg, uint64_t nexp)
{
	struct vrtc *vrtc = arg;
	time_t basetime;
	time_t curtime;

	pthread_mutex_lock(&vrtc->mtx);

	if (aintr_enabled(vrtc) || uintr_enabled(vrtc)) {
		curtime = vrtc_curtime(vrtc, &basetime);
		vrtc_time_update(vrtc, curtime, basetime);
	}

	pthread_mutex_unlock(&vrtc->mtx);
}

static void
vrtc_set_reg_c(struct vrtc *vrtc, uint8_t newval)
{
	struct rtcdev *rtc;
	int oldirqf, newirqf;
	uint8_t oldval, changed;

	rtc = &vrtc->rtcdev;
	newval &= RTCIR_ALARM | RTCIR_PERIOD | RTCIR_UPDATE;

	oldirqf = rtc->reg_c & RTCIR_INT;

	if ((aintr_enabled(vrtc) && (newval & RTCIR_ALARM) != 0) ||
			(pintr_enabled(vrtc) && (newval & RTCIR_PERIOD) != 0) ||
			(uintr_enabled(vrtc) && (newval & RTCIR_UPDATE) != 0)) {
		newirqf = RTCIR_INT;
	} else {
		newirqf = 0;
	}

	oldval = rtc->reg_c;
	rtc->reg_c = newirqf | newval;
	changed = oldval ^ rtc->reg_c;

	if (changed) {
		RTC_DEBUG("RTC reg_c changed from %#x to %#x\n",
				oldval, rtc->reg_c);
	}

	if (!oldirqf && newirqf) {
		vm_set_gsi_irq(vrtc->vm, RTC_IRQ, GSI_SET_HIGH);
		RTC_DEBUG("RTC irq %d asserted\n", RTC_IRQ);
	} else if (oldirqf && !newirqf) {
		vm_set_gsi_irq(vrtc->vm, RTC_IRQ, GSI_SET_LOW);
		RTC_DEBUG("RTC irq %d deasserted\n", RTC_IRQ);
	}
}

static void
send_rtc_chg_event(time_t newtime, time_t lasttime)
{
	struct vm_event event;
	struct rtc_change_event_data *data = (struct rtc_change_event_data *)event.event_data;

	event.type = VM_EVENT_RTC_CHG;
	data->delta_time = newtime - lasttime;
	data->last_time = lasttime;
	dm_send_vm_event(&event);
}

static int
vrtc_set_reg_b(struct vrtc *vrtc, uint8_t newval)
{
	struct rtcdev *rtc;
	time_t oldfreq, newfreq, basetime;
	time_t curtime, rtctime;
	uint8_t oldval, changed;

	rtc = &vrtc->rtcdev;
	oldval = rtc->reg_b;
	oldfreq = vrtc_freq(vrtc);

	rtc->reg_b = newval;
	changed = oldval ^ newval;
	if (changed) {
		RTC_DEBUG("RTC reg_b changed from %#x to %#x\n",
				oldval, newval);
	}

	if (changed & RTCSB_HALT) {
		if ((newval & RTCSB_HALT) == 0) {
			rtctime = rtc_to_secs(vrtc);
			basetime = monotonic_time();
			if (rtctime == VRTC_BROKEN_TIME) {
				if (rtc_flag_broken_time)
					return -1;
			} else {
				/* send rtc change event if rtc time changed during halt */
				if (vrtc->halted_rtctime != VRTC_BROKEN_TIME &&
					rtctime != vrtc->halted_rtctime) {
					send_rtc_chg_event(rtctime, vrtc->halted_rtctime);
					vrtc->halted_rtctime = VRTC_BROKEN_TIME;
				}
			}
		} else {
			curtime = vrtc_curtime(vrtc, &basetime);
			if (curtime != vrtc->base_rtctime)
				return -1;
			vrtc->halted_rtctime = curtime;
			/*
			 * Force a refresh of the RTC date/time fields so
			 * they reflect the time right before the guest set
			 * the HALT bit.
			 */
			secs_to_rtc(curtime, vrtc, 1);

			/*
			 * Updates are halted so mark 'base_rtctime' to denote
			 * that the RTC date/time is in flux.
			 */
			rtctime = VRTC_BROKEN_TIME;
			rtc->reg_b &= ~RTCSB_UINTR;
		}
		if (vrtc_time_update(vrtc, rtctime, basetime) != 0)
			return -1;
	}

	/*
	 * Side effect of changes to the interrupt enable bits.
	 */
	if (changed & RTCSB_ALL_INTRS)
		vrtc_set_reg_c(vrtc, vrtc->rtcdev.reg_c);

	/*
	 * Change the callout frequency if it has changed.
	 */
	newfreq = vrtc_freq(vrtc);

	if (pintr_enabled(vrtc) && newfreq != oldfreq) {
		/*start the new periodic timer*/
		vrtc_start_timer(&vrtc->periodic_timer, 0, newfreq);

	} else {
		/*Nothing to do*/
	}

	/*
	 * The side effect of bits that control the RTC date/time format
	 * is handled lazily when those fields are actually read.
	 */
	return 0;
}

static void
vrtc_set_reg_a(struct vrtc *vrtc, uint8_t newval)
{
	time_t oldfreq, newfreq;
	uint8_t oldval, changed;

	newval &= ~RTCSA_TUP;
	oldval = vrtc->rtcdev.reg_a;
	oldfreq = vrtc_freq(vrtc);

	if (divider_enabled(oldval) && !divider_enabled(newval)) {
		RTC_DEBUG("RTC divider held in reset at %#lx/%#lx\n",
				vrtc->base_rtctime, vrtc->base_uptime);
	} else if (!divider_enabled(oldval) && divider_enabled(newval)) {
		/*
		 * If the dividers are coming out of reset then update
		 * 'base_uptime' before this happens. This is done to
		 * maintain the illusion that the RTC date/time was frozen
		 * while the dividers were disabled.
		 */
		vrtc->base_uptime = monotonic_time();
		RTC_DEBUG("RTC divider out of reset at %#lx/%#lx\n",
				vrtc->base_rtctime, vrtc->base_uptime);
	} else {
		/* NOTHING */
	}

	vrtc->rtcdev.reg_a = newval;
	changed = oldval ^ newval;
	if (changed) {
		RTC_DEBUG("RTC reg_a changed from %#x to %#x\n",
				oldval, newval);
	}

	/*
	 * Side effect of changes to rate select and divider enable bits.
	 */
	newfreq = vrtc_freq(vrtc);

	if (pintr_enabled(vrtc) && newfreq != oldfreq) {
		/*start the new periodic timer*/
		vrtc_start_timer(&vrtc->periodic_timer, 0, newfreq);
	} else {
		/*Nothing to do*/
	}
}

int
vrtc_nvram_write(struct vrtc *vrtc, int offset, uint8_t value)
{
	uint8_t *ptr;

	/*
	 * Don't allow writes to RTC control registers or the date/time fields.
	 */
	if (offset < offsetof(struct rtcdev, nvram[0]) ||
			offset == RTC_CENTURY ||
			offset >= sizeof(struct rtcdev)) {
		RTC_DEBUG("RTC nvram write to invalid offset %d\n", offset);
		return -1;
	}

	pthread_mutex_lock(&vrtc->mtx);
	ptr = (uint8_t *)(&vrtc->rtcdev);
	ptr[offset] = value;
	RTC_DEBUG("RTC nvram write %#x to offset %#x\n", value, offset);
	pthread_mutex_unlock(&vrtc->mtx);

	return 0;
}

int
vrtc_addr_handler(struct vmctx *ctx, int vcpu, int in, int port,
		  int bytes, uint32_t *eax, void *arg)
{
	struct vrtc *vrtc = arg;

	if (bytes != 1)
		return -1;

	if (in) {
		*eax = 0xff;
		return 0;
	}

	pthread_mutex_lock(&vrtc->mtx);
	vrtc->addr = *eax & 0x7f;
	pthread_mutex_unlock(&vrtc->mtx);

	return 0;
}

static inline bool vrtc_is_time_register(uint32_t offset)
{
	return ((offset == RTC_SEC) || (offset == RTC_MIN) || (offset == RTC_HRS) || (offset == RTC_DAY)
			|| (offset == RTC_MONTH) || (offset == RTC_YEAR) || (offset == RTC_CENTURY));
}

int
vrtc_data_handler(struct vmctx *ctx, int vcpu, int in, int port,
		  int bytes, uint32_t *eax, void *arg)
{
	struct vrtc *vrtc = arg;
	struct rtcdev *rtc;
	time_t basetime;
	time_t curtime;
	int error, offset;

	rtc = &vrtc->rtcdev;

	if (bytes != 1)
		return -1;

	pthread_mutex_lock(&vrtc->mtx);
	offset = vrtc->addr;
	if (offset >= sizeof(struct rtcdev)) {
		pthread_mutex_unlock(&vrtc->mtx);
		return -1;
	}

	error = 0;
	curtime = vrtc_curtime(vrtc, &basetime);
	vrtc_time_update(vrtc, curtime, basetime);

	/*
	 * Update RTC date/time fields if necessary.
	 *
	 * This is not just for reads of the RTC. The side-effect of writing
	 * the century byte requires other RTC date/time fields (e.g. sec)
	 * to be updated here.
	 */
	if (offset < 10 || offset == RTC_CENTURY)
		secs_to_rtc(curtime, vrtc, 0);

	if (in) {
		if (offset == 12) {
			/*
			 * XXX
			 * reg_c interrupt flags are updated only if the
			 * corresponding interrupt enable bit in reg_b is set.
			 */
			*eax = vrtc->rtcdev.reg_c;
			vrtc_set_reg_c(vrtc, 0);
		} else {
			*eax = *((uint8_t *)rtc + offset);
		}
		RTC_DEBUG("Read value %#x from RTC offset %#x\n", *eax, offset);
	} else {
		switch (offset) {
		case 10:
			RTC_DEBUG("RTC reg_a set to %#x\n", *eax);
			vrtc_set_reg_a(vrtc, *eax);
			break;
		case 11:
			RTC_DEBUG("RTC reg_b set to %#x\n", *eax);
			error = vrtc_set_reg_b(vrtc, *eax);
			break;
		case 12:
			RTC_DEBUG("RTC reg_c set to %#x (ignored)\n", *eax);
			break;
		case 13:
			RTC_DEBUG("RTC reg_d set to %#x (ignored)\n", *eax);
			break;
		case 0:
			/*
			 * High order bit of 'seconds' is readonly.
			 */
			*eax &= 0x7f;
			/* FALLTHRU */
		default:
			RTC_DEBUG("RTC offset %#x set to %#x\n", offset, *eax);
			*((uint8_t *)rtc + offset) = *eax;
			break;
		}

		/*
		 * Some guests (e.g. OpenBSD) write the century byte outside of RTCSB_HALT,
		 * and some guests (e.g. WaaG) write all date/time outside of RTCSB_HALT,
		 * so re-calculate the RTC date/time.
		 */
		if (vrtc_is_time_register(offset) && !rtc_halted(vrtc)) {
			time_t last_time = curtime;
			curtime = rtc_to_secs(vrtc);
			error = vrtc_time_update(vrtc, curtime, monotonic_time());
			if ((error != 0) || (curtime == VRTC_BROKEN_TIME && rtc_flag_broken_time)) {
				error = -1;
			} else {
				/* We don't know when the Guest has finished the RTC change action.
				 * So send an event each time the date/time regs has been updated.
				 * The event handler will process those events.
				 */
				send_rtc_chg_event(rtc_to_secs(vrtc), last_time);
			}
		}
	}

	pthread_mutex_unlock(&vrtc->mtx);

	return error;
}

int
vrtc_set_time(struct vrtc *vrtc, time_t secs)
{
	int error;

	pthread_mutex_lock(&vrtc->mtx);
	error = vrtc_time_update(vrtc, secs, monotonic_time());
	pthread_mutex_unlock(&vrtc->mtx);

	if (error)
		RTC_DEBUG("Error %d setting RTC time to %#lx\n", error, secs);
	else
		RTC_DEBUG("RTC time set to %#lx\n", secs);

	return error;
}

/* set CMOS shutdown status register (index 0xF) as S3_resume(0xFE)
 * BIOS will read it and start S3 resume at POST Entry
 */
void vrtc_suspend(struct vmctx *ctx)
{
	struct vrtc *vrtc = ctx->vrtc;
	struct rtcdev *rtc = &vrtc->rtcdev;

	*((uint8_t *)rtc + 0xF) = 0xFE;
}

int
vrtc_init(struct vmctx *ctx)
{
	struct vrtc *vrtc;
	size_t lomem, himem;
	int err;
	struct rtcdev *rtc;
	time_t curtime;
	struct inout_port rtc_addr, rtc_data;

	vrtc = calloc(1, sizeof(struct vrtc));
	if (vrtc == NULL)
		return -ENOMEM;

	vrtc->vm = ctx;
	ctx->vrtc = vrtc;

	pthread_mutex_init(&vrtc->mtx, NULL);

	/*
	 * Report guest memory size in nvram cells as required by UEFI.
	 * Little-endian encoding.
	 * 0x34/0x35 - 64KB chunks above 16MB, below 4GB
	 * 0x5b/0x5c/0x5d - 64KB chunks above 4GB
	 */
	lomem = vm_get_lowmem_size(ctx);
	if (lomem < 16 * MB) {
		err = -EINVAL;
		goto fail;
	}

	lomem = (lomem - 16 * MB) / (64 * KB);
	if (vrtc_nvram_write(vrtc, RTC_LMEM_LSB, lomem) != 0) {
		err = -EIO;
		goto fail;
	}

	if (vrtc_nvram_write(vrtc, RTC_LMEM_MSB, lomem >> 8) != 0) {
		err = -EIO;
		goto fail;
	}

	himem = vm_get_highmem_size(ctx) / (64 * KB);
	if (vrtc_nvram_write(vrtc, RTC_HMEM_LSB, himem) != 0) {
		err = -EIO;
		goto fail;
	}

	if (vrtc_nvram_write(vrtc, RTC_HMEM_SB, himem >> 8) != 0) {
		err = -EIO;
		goto fail;
	}

	if (vrtc_nvram_write(vrtc, RTC_HMEM_MSB, himem >> 16) != 0) {
		err = -EIO;
		goto fail;
	}

	memset(&rtc_addr, 0, sizeof(struct inout_port));
	memset(&rtc_data, 0, sizeof(struct inout_port));
	/*register io port handler for rtc addr*/
	rtc_addr.name = "rtc";
	rtc_addr.port = IO_RTC;
	rtc_addr.size = 1;
	rtc_addr.flags = IOPORT_F_INOUT;
	rtc_addr.handler = vrtc_addr_handler;
	rtc_addr.arg = vrtc;
	if (register_inout(&rtc_addr) != 0) {
		err = -EINVAL;
		goto fail;
	}

	/*register io port handler for rtc data*/
	rtc_data.name = "rtc";
	rtc_data.port = IO_RTC + 1;
	rtc_data.size = 1;
	rtc_data.flags = IOPORT_F_INOUT;
	rtc_data.handler = vrtc_data_handler;
	rtc_data.arg = vrtc;
	if (register_inout(&rtc_data) != 0) {
		err = -EINVAL;
		goto fail;
	}

	/* Allow dividers o keep time but disable everything else */
	rtc = &vrtc->rtcdev;
	rtc->reg_a = 0x20;
	rtc->reg_b = RTCSB_24HR;
	rtc->reg_c = 0;
	rtc->reg_d = RTCSD_PWR;

	/* Reset the index register to a safe value. */
	vrtc->addr = RTC_STATUSD;

	vrtc->halted_rtctime = VRTC_BROKEN_TIME;
	/*
	 * Initialize RTC time to 00:00:00 Jan 1, 1970 if curtime = 0
	 */
	/*curtime = 0;*/
	curtime = time(NULL);

	pthread_mutex_lock(&vrtc->mtx);
	vrtc->base_rtctime = VRTC_BROKEN_TIME;
	vrtc_time_update(vrtc, curtime, monotonic_time());
	secs_to_rtc(curtime, vrtc, 0);
	pthread_mutex_unlock(&vrtc->mtx);

	/* init periodic interrupt timer */
	vrtc->periodic_timer.clockid = CLOCK_MONOTONIC;
	acrn_timer_init(&vrtc->periodic_timer, vrtc_periodic_timer, vrtc);

	/* init update interrupt timer(1s)*/
	vrtc->update_timer.clockid = CLOCK_MONOTONIC;
	acrn_timer_init(&vrtc->update_timer, vrtc_update_timer, vrtc);
	vrtc_start_timer(&vrtc->update_timer, 1, 0);

	return 0;

fail:
	free(vrtc);
	return err;
}

void
vrtc_deinit(struct vmctx *ctx)
{
	struct vrtc *vrtc = ctx->vrtc;
	struct inout_port iop;

	/*deinit acrn_timer*/
	acrn_timer_deinit(&vrtc->periodic_timer);
	acrn_timer_deinit(&vrtc->update_timer);

	memset(&iop, 0, sizeof(struct inout_port));
	iop.name = "rtc";
	iop.port = IO_RTC;
	iop.size = 1;
	unregister_inout(&iop);

	memset(&iop, 0, sizeof(struct inout_port));
	iop.name = "rtc";
	iop.port = IO_RTC + 1;
	iop.size = 1;
	unregister_inout(&iop);

	free(vrtc);
	ctx->vrtc = NULL;
}

static void
rtc_dsdt(void)
{
	dsdt_line("");
	dsdt_line("Device (RTC)");
	dsdt_line("{");
	dsdt_line("  Name (_HID, EisaId (\"PNP0B00\"))");
	dsdt_line("  Name (_CRS, ResourceTemplate ()");
	dsdt_line("  {");
	dsdt_indent(2);
	dsdt_fixed_ioport(IO_RTC, 2);
	dsdt_fixed_irq(8);
	dsdt_unindent(2);
	dsdt_line("  })");
	dsdt_line("}");
}
LPC_DSDT(rtc_dsdt);

/*
 * Reserve the extended RTC I/O ports although they are not emulated at this
 * time.
 */
SYSRES_IO(0x72, 6);
