acrn-hypervisor/devicemodel/hw/platform/rtc.c
Peter Fang 3fe4c3f2a8 dm: provide timer callback handlers the number of expirations
It is possible for multiple timeouts to occur in one mevent epoll
iteration. Providing the number of timer expirations to the timer
callback handlers can be useful. E.g., this could improve emulation of
timing-sensitive hardware components.

Tracked-On: #2319
Signed-off-by: Peter Fang <peter.fang@intel.com>
Acked-by: Anthony Xu <anthony.xu@intel.com>
2019-01-23 10:33:28 +08:00

1208 lines
28 KiB
C

/*-
* 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 <assert.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.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"
/* #define DEBUG_RTC */
#ifdef DEBUG_RTC
# define RTC_DEBUG(format, ...) printf(format, ## __VA_ARGS__)
#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;
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;
}
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 = time(NULL);
delta = now - vrtc->base_uptime;
assert(delta >= 0);
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;
assert(ct->year >= 0 && ct->year < 10000);
assert(ct->mon >= 1 && ct->mon <= 12);
assert(ct->day >= 1 && ct->day <= 31);
assert(ct->hour >= 0 && ct->hour <= 23);
assert(ct->min >= 0 && ct->min <= 59);
/* Not sure if this interface needs to handle leapseconds or not. */
assert(ct->sec >= 0 && ct->sec <= 60);
}
static inline uint8_t
rtcset(struct rtcdev *rtc, int val)
{
assert(val >= 0 && val < 100);
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) {
assert(rtctime == VRTC_BROKEN_TIME);
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);
assert(ct.sec >= 0 && ct.sec <= 59);
assert(ct.min >= 0 && ct.min <= 59);
assert(ct.hour >= 0 && ct.hour <= 23);
assert(ct.dow >= 0 && ct.dow <= 6);
assert(ct.day >= 1 && ct.day <= 31);
assert(ct.mon >= 1 && ct.mon <= 12);
assert(ct.year >= POSIX_BASE_YEAR);
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", 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", 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",
rtc->hour, ct.hour);
goto fail;
}
}
if (error || ct.hour < 0 || ct.hour > 23) {
RTC_DEBUG("Invalid RTC hour %#x/%d", 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", 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", 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", 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", 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",
ct.year, ct.mon, ct.day);
RTC_DEBUG("Invalid RTC clocktime.time %02d:%02d:%02d",
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");
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;
assert(acrn_timer_settime(timer, &ts) == 0);
}
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 int
vrtc_set_reg_b(struct vrtc *vrtc, uint8_t newval)
{
struct rtcdev *rtc;
time_t oldfreq, newfreq, basetime;
time_t curtime, rtctime;
int error;
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 = time(NULL);
if (rtctime == VRTC_BROKEN_TIME) {
if (rtc_flag_broken_time)
return -1;
}
} else {
curtime = vrtc_curtime(vrtc, &basetime);
assert(curtime == vrtc->base_rtctime);
/*
* 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;
}
error = vrtc_time_update(vrtc, rtctime, basetime);
assert(error == 0);
}
/*
* 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",
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 = time(NULL);
RTC_DEBUG("RTC divider out of reset at %#lx/%#lx",
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",
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_read(struct vrtc *vrtc, int offset, uint8_t *retval)
{
time_t basetime;
time_t curtime;
uint8_t *ptr;
/*
* Allow all offsets in the RTC to be read.
*/
if (offset < 0 || offset >= sizeof(struct rtcdev))
return -1;
pthread_mutex_lock(&vrtc->mtx);
/*
* Update RTC date/time fields if necessary.
*/
if (offset < 10 || offset == RTC_CENTURY) {
curtime = vrtc_curtime(vrtc, &basetime);
secs_to_rtc(curtime, vrtc, 0);
}
ptr = (uint8_t *)(&vrtc->rtcdev);
*retval = ptr[offset];
pthread_mutex_unlock(&vrtc->mtx);
return 0;
}
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", 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", 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;
}
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;
}
/*
* XXX some guests (e.g. OpenBSD) write the century byte
* outside of RTCSB_HALT so re-calculate the RTC date/time.
*/
if (offset == RTC_CENTURY && !rtc_halted(vrtc)) {
curtime = rtc_to_secs(vrtc);
error = vrtc_time_update(vrtc, curtime, time(NULL));
assert(!error);
if (curtime == VRTC_BROKEN_TIME && rtc_flag_broken_time)
error = -1;
}
}
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, time(NULL));
pthread_mutex_unlock(&vrtc->mtx);
if (error)
RTC_DEBUG("Error %d setting RTC time to %#lx", error, secs);
else
RTC_DEBUG("RTC time set to %#lx", secs);
return error;
}
time_t
vrtc_get_time(struct vrtc *vrtc)
{
time_t basetime;
time_t t;
pthread_mutex_lock(&vrtc->mtx);
t = vrtc_curtime(vrtc, &basetime);
pthread_mutex_unlock(&vrtc->mtx);
return t;
}
void
vrtc_reset(struct vrtc *vrtc)
{
struct rtcdev *rtc;
pthread_mutex_lock(&vrtc->mtx);
rtc = &vrtc->rtcdev;
vrtc_set_reg_b(vrtc, rtc->reg_b & ~(RTCSB_ALL_INTRS | RTCSB_SQWE));
vrtc_set_reg_c(vrtc, 0);
pthread_mutex_unlock(&vrtc->mtx);
}
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));
assert(vrtc != NULL);
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);
assert(lomem >= 16 * MB);
lomem = (lomem - 16 * MB) / (64 * KB);
err = vrtc_nvram_write(vrtc, RTC_LMEM_LSB, lomem);
assert(err == 0);
err = vrtc_nvram_write(vrtc, RTC_LMEM_MSB, lomem >> 8);
assert(err == 0);
himem = vm_get_highmem_size(ctx) / (64 * KB);
err = vrtc_nvram_write(vrtc, RTC_HMEM_LSB, himem);
assert(err == 0);
err = vrtc_nvram_write(vrtc, RTC_HMEM_SB, himem >> 8);
assert(err == 0);
err = vrtc_nvram_write(vrtc, RTC_HMEM_MSB, himem >> 16);
assert(err == 0);
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;
assert(register_inout(&rtc_addr) == 0);
/*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;
assert(register_inout(&rtc_data) == 0);
/* 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;
/*
* 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, time(NULL));
secs_to_rtc(curtime, vrtc, 0);
pthread_mutex_unlock(&vrtc->mtx);
/* init periodic interrupt timer */
vrtc->periodic_timer.clockid = CLOCK_REALTIME;
acrn_timer_init(&vrtc->periodic_timer, vrtc_periodic_timer, vrtc);
/* init update interrupt timer(1s)*/
vrtc->update_timer.clockid = CLOCK_REALTIME;
acrn_timer_init(&vrtc->update_timer, vrtc_update_timer, vrtc);
vrtc_start_timer(&vrtc->update_timer, 1, 0);
return 0;
}
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);