711 lines
18 KiB
C
711 lines
18 KiB
C
/*
|
|
* Copyright (c) 2014, Neel Natu (neel@freebsd.org)
|
|
* Copyright (c) 2022 Intel Corporation
|
|
* 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 <asm/guest/vm.h>
|
|
#include <asm/io.h>
|
|
#include <asm/tsc.h>
|
|
#include <vrtc.h>
|
|
#include <logmsg.h>
|
|
|
|
#include "mc146818rtc.h"
|
|
|
|
/* #define DEBUG_RTC */
|
|
#ifdef DEBUG_RTC
|
|
# define RTC_DEBUG pr_info
|
|
#else
|
|
# define RTC_DEBUG(format, ...) do { } while (false)
|
|
#endif
|
|
|
|
static time_t vrtc_get_physical_rtc_time(struct acrn_vrtc *vrtc);
|
|
static void vrtc_update_basetime(time_t physical_time, time_t offset);
|
|
|
|
struct clktime {
|
|
uint32_t year; /* year (4 digit year) */
|
|
uint32_t mon; /* month (1 - 12) */
|
|
uint32_t day; /* day (1 - 31) */
|
|
uint32_t hour; /* hour (0 - 23) */
|
|
uint32_t min; /* minute (0 - 59) */
|
|
uint32_t sec; /* second (0 - 59) */
|
|
uint32_t dow; /* day of week (0 - 6; 0 = Sunday) */
|
|
};
|
|
|
|
static spinlock_t vrtc_rebase_lock = { .head = 0U, .tail = 0U };
|
|
|
|
#define POSIX_BASE_YEAR 1970
|
|
#define SECDAY (24 * 60 * 60)
|
|
#define SECYR (SECDAY * 365)
|
|
#define VRTC_BROKEN_TIME ((time_t)-1)
|
|
|
|
#define FEBRUARY 2U
|
|
|
|
static const uint32_t month_days[12] = {
|
|
31U, 28U, 31U, 30U, 31U, 30U, 31U, 31U, 30U, 31U, 30U, 31U
|
|
};
|
|
|
|
/*
|
|
* 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 inline uint32_t leapyear(uint32_t year)
|
|
{
|
|
uint32_t rv = 0U;
|
|
|
|
if ((year & 3U) == 0) {
|
|
rv = 1U;
|
|
if ((year % 100U) == 0) {
|
|
rv = 0U;
|
|
if ((year % 400U) == 0) {
|
|
rv = 1U;
|
|
}
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
static inline uint32_t days_in_year(uint32_t year)
|
|
{
|
|
return leapyear(year) ? 366U : 365U;
|
|
}
|
|
|
|
static inline uint32_t days_in_month(uint32_t year, uint32_t month)
|
|
{
|
|
return month_days[(month) - 1U] + ((month == FEBRUARY) ? leapyear(year) : 0U);
|
|
}
|
|
|
|
/*
|
|
* Day of week. Days are counted from 1/1/1970, which was a Thursday.
|
|
*/
|
|
static inline uint32_t day_of_week(uint32_t days)
|
|
{
|
|
return ((days) + 4U) % 7U;
|
|
}
|
|
|
|
uint8_t const bin2bcd_data[] = {
|
|
0x00U, 0x01U, 0x02U, 0x03U, 0x04U, 0x05U, 0x06U, 0x07U, 0x08U, 0x09U,
|
|
0x10U, 0x11U, 0x12U, 0x13U, 0x14U, 0x15U, 0x16U, 0x17U, 0x18U, 0x19U,
|
|
0x20U, 0x21U, 0x22U, 0x23U, 0x24U, 0x25U, 0x26U, 0x27U, 0x28U, 0x29U,
|
|
0x30U, 0x31U, 0x32U, 0x33U, 0x34U, 0x35U, 0x36U, 0x37U, 0x38U, 0x39U,
|
|
0x40U, 0x41U, 0x42U, 0x43U, 0x44U, 0x45U, 0x46U, 0x47U, 0x48U, 0x49U,
|
|
0x50U, 0x51U, 0x52U, 0x53U, 0x54U, 0x55U, 0x56U, 0x57U, 0x58U, 0x59U,
|
|
0x60U, 0x61U, 0x62U, 0x63U, 0x64U, 0x65U, 0x66U, 0x67U, 0x68U, 0x69U,
|
|
0x70U, 0x71U, 0x72U, 0x73U, 0x74U, 0x75U, 0x76U, 0x77U, 0x78U, 0x79U,
|
|
0x80U, 0x81U, 0x82U, 0x83U, 0x84U, 0x85U, 0x86U, 0x87U, 0x88U, 0x89U,
|
|
0x90U, 0x91U, 0x92U, 0x93U, 0x94U, 0x95U, 0x96U, 0x97U, 0x98U, 0x99U
|
|
};
|
|
|
|
/*
|
|
* @pre val < 100
|
|
*/
|
|
static inline uint8_t rtcset(struct rtcdev *rtc, uint32_t val)
|
|
{
|
|
return ((rtc->reg_b & RTCSB_BCD) ? val : bin2bcd_data[val]);
|
|
}
|
|
|
|
/*
|
|
* Get rtc time register binary value.
|
|
* If BCD data mode is enabled, translate BCD to binary.
|
|
*/
|
|
static int32_t rtcget(const struct rtcdev *rtc, uint8_t val, uint32_t *retval)
|
|
{
|
|
uint8_t upper, lower;
|
|
int32_t errno = 0;
|
|
|
|
if (rtc->reg_b & RTCSB_BCD) {
|
|
*retval = val;
|
|
} else {
|
|
lower = val & 0xfU;
|
|
upper = (val >> 4) & 0xfU;
|
|
|
|
if ((lower > 9U) || (upper > 9U)) {
|
|
errno = -EINVAL;
|
|
} else {
|
|
*retval = upper * 10U + lower;
|
|
}
|
|
}
|
|
return errno;
|
|
}
|
|
|
|
/*
|
|
* Translate clktime (such as year, month, day) to time_t.
|
|
*/
|
|
static int32_t clk_ct_to_ts(struct clktime *ct, time_t *sec)
|
|
{
|
|
uint32_t i, year, days;
|
|
int32_t err = 0;
|
|
|
|
year = ct->year;
|
|
|
|
/* Sanity checks. */
|
|
if ((ct->mon < 1U) || (ct->mon > 12U) || (ct->day < 1U) ||
|
|
(ct->day > days_in_month(year, ct->mon)) ||
|
|
(ct->hour > 23U) || (ct->min > 59U) || (ct->sec > 59U) ||
|
|
(year < POSIX_BASE_YEAR) || (year > 2037U)) {
|
|
/* time_t overflow */
|
|
err = -EINVAL;
|
|
} else {
|
|
/*
|
|
* Compute days since start of time
|
|
* First from years, then from months.
|
|
*/
|
|
days = 0U;
|
|
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);
|
|
|
|
*sec = (((time_t)days * 24 + ct->hour) * 60 + ct->min) * 60 + ct->sec;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Translate time_t to clktime (such as year, month, day)
|
|
*/
|
|
static int32_t clk_ts_to_ct(time_t secs, struct clktime *ct)
|
|
{
|
|
uint32_t i, year, days;
|
|
time_t rsec; /* remainder seconds */
|
|
int32_t err = 0;
|
|
|
|
days = secs / SECDAY;
|
|
rsec = secs % SECDAY;
|
|
|
|
ct->dow = day_of_week(days);
|
|
|
|
/* Substract 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;
|
|
|
|
/* Substract 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 / 3600U;
|
|
rsec = rsec % 3600U;
|
|
ct->min = rsec / 60U;
|
|
rsec = rsec % 60U;
|
|
ct->sec = rsec;
|
|
|
|
/* time_t is defined as int32_t, so year should not be more than 2037. */
|
|
if ((ct->mon > 12U) || (ct->year > 2037) || (ct->day > days_in_month(ct->year, ct->mon))) {
|
|
pr_err("Invalid vRTC param mon %d, year %d, day %d\n", ct->mon, ct->year, ct->day);
|
|
err = -EINVAL;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Calculate second value from rtcdev register info which save in vrtc.
|
|
*/
|
|
static time_t rtc_to_secs(const struct acrn_vrtc *vrtc)
|
|
{
|
|
struct clktime ct;
|
|
time_t second = VRTC_BROKEN_TIME;
|
|
const struct rtcdev *rtc= &vrtc->rtcdev;
|
|
uint32_t hour = 0, pm = 0;
|
|
uint32_t century = 0, year = 0;
|
|
|
|
do {
|
|
if ((rtcget(rtc, rtc->sec, &ct.sec) < 0) || (rtcget(rtc, rtc->min, &ct.min) < 0) ||
|
|
(rtcget(rtc, rtc->day_of_month, &ct.day) < 0) ||
|
|
(rtcget(rtc, rtc->month, &ct.mon) < 0) || (rtcget(rtc, rtc->year, &year) < 0) ||
|
|
(rtcget(rtc, rtc->century, ¢ury) < 0)) {
|
|
pr_err("Invalid RTC sec %#x hour %#x day %#x mon %#x year %#x century %#x\n",
|
|
rtc->sec, rtc->min, rtc->day_of_month, rtc->month,
|
|
rtc->year, rtc->century);
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* If 12 hour format is inuse, translate it to 24 hour format here.
|
|
*/
|
|
pm = 0;
|
|
hour = rtc->hour;
|
|
if ((rtc->reg_b & RTCSB_24HR) == 0) {
|
|
if (hour & 0x80U) {
|
|
hour &= ~0x80U;
|
|
pm = 1;
|
|
}
|
|
}
|
|
if (rtcget(rtc, hour, &ct.hour) != 0) {
|
|
pr_err("Invalid RTC hour %#x\n", rtc->hour);
|
|
break;
|
|
}
|
|
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 {
|
|
pr_err("Invalid RTC 12-hour format %#x/%d\n",
|
|
rtc->hour, ct.hour);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
ct.year = century * 100 + year;
|
|
if (ct.year < POSIX_BASE_YEAR) {
|
|
pr_err("Invalid RTC century %x/%d\n", rtc->century,
|
|
ct.year);
|
|
break;
|
|
}
|
|
|
|
if (clk_ct_to_ts(&ct, &second) != 0) {
|
|
pr_err("Invalid RTC clocktime.date %04d-%02d-%02d\n",
|
|
ct.year, ct.mon, ct.day);
|
|
pr_err("Invalid RTC clocktime.time %02d:%02d:%02d\n",
|
|
ct.hour, ct.min, ct.sec);
|
|
break;
|
|
}
|
|
} while (false);
|
|
|
|
return second;
|
|
}
|
|
|
|
/*
|
|
* Translate second value to rtcdev register info and save it in vrtc.
|
|
*/
|
|
static void secs_to_rtc(time_t rtctime, struct acrn_vrtc *vrtc)
|
|
{
|
|
struct clktime ct;
|
|
struct rtcdev *rtc;
|
|
uint32_t hour;
|
|
|
|
if ((rtctime > 0) && (clk_ts_to_ct(rtctime, &ct) == 0)) {
|
|
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);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If the base_rtctime is valid, calculate current time by add tsc offset and offset_rtctime.
|
|
*/
|
|
static time_t vrtc_get_current_time(struct acrn_vrtc *vrtc)
|
|
{
|
|
uint64_t offset;
|
|
time_t second = VRTC_BROKEN_TIME;
|
|
|
|
spinlock_obtain(&vrtc_rebase_lock);
|
|
if (vrtc->base_rtctime > 0) {
|
|
offset = (cpu_ticks() - vrtc->base_tsc) / (get_tsc_khz() * 1000U);
|
|
second = vrtc->base_rtctime + vrtc->offset_rtctime + (time_t)offset;
|
|
if (second < vrtc->last_rtctime) {
|
|
second = vrtc->last_rtctime;
|
|
} else {
|
|
vrtc->last_rtctime = second;
|
|
}
|
|
}
|
|
spinlock_release(&vrtc_rebase_lock);
|
|
return second;
|
|
}
|
|
|
|
#define CMOS_ADDR_PORT 0x70U
|
|
#define CMOS_DATA_PORT 0x71U
|
|
|
|
static spinlock_t cmos_lock = { .head = 0U, .tail = 0U };
|
|
|
|
static uint8_t cmos_read(uint8_t addr)
|
|
{
|
|
pio_write8(addr, CMOS_ADDR_PORT);
|
|
return pio_read8(CMOS_DATA_PORT);
|
|
}
|
|
|
|
static void cmos_write(uint8_t addr, uint8_t value)
|
|
{
|
|
pio_write8(addr, CMOS_ADDR_PORT);
|
|
pio_write8(value, CMOS_DATA_PORT);
|
|
}
|
|
|
|
static bool cmos_update_in_progress(void)
|
|
{
|
|
return (cmos_read(RTC_STATUSA) & RTCSA_TUP) ? 1 : 0;
|
|
}
|
|
|
|
static uint8_t cmos_get_reg_val(uint8_t addr)
|
|
{
|
|
uint8_t reg;
|
|
int32_t tries = 2000;
|
|
|
|
spinlock_obtain(&cmos_lock);
|
|
|
|
/* Make sure an update isn't in progress */
|
|
while (cmos_update_in_progress() && (tries != 0)) {
|
|
tries -= 1;
|
|
}
|
|
|
|
reg = cmos_read(addr);
|
|
|
|
spinlock_release(&cmos_lock);
|
|
return reg;
|
|
}
|
|
|
|
static void cmos_set_reg_val(uint8_t addr, uint8_t value)
|
|
{
|
|
int32_t tries = 2000;
|
|
|
|
spinlock_obtain(&cmos_lock);
|
|
|
|
/* Make sure an update isn't in progress */
|
|
while (cmos_update_in_progress() && (tries != 0)) {
|
|
tries -= 1;
|
|
}
|
|
|
|
cmos_write(addr, value);
|
|
|
|
spinlock_release(&cmos_lock);
|
|
}
|
|
|
|
#define TRIGGER_ALARM (RTCIR_ALARM | RTCIR_INT)
|
|
#define RTC_DELTA 1 /* For RTC and system time may out of sync for no more than 1s */
|
|
static inline bool rtc_halted(struct acrn_vrtc *rtc)
|
|
{
|
|
return ((rtc->rtcdev.reg_b & RTCSB_HALT) != 0U);
|
|
}
|
|
|
|
static uint8_t vrtc_get_reg_c(struct acrn_vrtc *vrtc)
|
|
{
|
|
uint8_t ret = vrtc->rtcdev.reg_c;
|
|
struct rtcdev *rtc = &vrtc->rtcdev;
|
|
time_t current, alarm;
|
|
|
|
if ((rtc->reg_b & RTCSB_AINTR) != 0U) {
|
|
current = rtc->hour * 3600 + rtc->min * 60 + rtc->sec;
|
|
alarm = rtc->alarm_hour * 3600 + rtc->alarm_min * 60 + rtc->alarm_sec;
|
|
|
|
if ((current >= (alarm - RTC_DELTA)) && (current <= (alarm + RTC_DELTA))) {
|
|
/*
|
|
* Linux RTC driver will trigger alarm interrupt when getting
|
|
* RTC time, and then read the interrupt flag register. If the value was not
|
|
* correct, read failure will occurs. So if alarm interrupt is enabled
|
|
* and rtc time is in alarm time scale, set the interrupt flag. The
|
|
* interrupt is not acturally triggered for driver will read the register
|
|
* proactively.
|
|
*/
|
|
ret |= TRIGGER_ALARM;
|
|
}
|
|
}
|
|
|
|
vrtc->rtcdev.reg_c = 0;
|
|
return ret;
|
|
}
|
|
|
|
static void vrtc_set_reg_b(struct acrn_vrtc *vrtc, uint8_t newval)
|
|
{
|
|
vrtc->rtcdev.reg_b = newval;
|
|
}
|
|
|
|
/**
|
|
* @pre vcpu != NULL
|
|
* @pre vcpu->vm != NULL
|
|
*/
|
|
static bool vrtc_read(struct acrn_vcpu *vcpu, uint16_t addr, __unused size_t width)
|
|
{
|
|
uint8_t offset;
|
|
time_t current;
|
|
struct acrn_vrtc *vrtc = &vcpu->vm->vrtc;
|
|
struct acrn_pio_request *pio_req = &vcpu->req.reqs.pio_request;
|
|
struct acrn_vm *vm = vcpu->vm;
|
|
bool ret = true;
|
|
|
|
offset = vrtc->addr;
|
|
|
|
if (addr == CMOS_ADDR_PORT) {
|
|
pio_req->value = offset;
|
|
} else {
|
|
if (is_service_vm(vm)) {
|
|
pio_req->value = cmos_get_reg_val(offset);
|
|
} else {
|
|
if (offset <= RTC_CENTURY) {
|
|
current = vrtc_get_current_time(vrtc);
|
|
secs_to_rtc(current, vrtc);
|
|
|
|
if(offset == 0xCU) {
|
|
pio_req->value = vrtc_get_reg_c(vrtc);
|
|
} else {
|
|
pio_req->value = *((uint8_t *)&vrtc->rtcdev + offset);
|
|
}
|
|
RTC_DEBUG("read 0x%x, 0x%x", offset, pio_req->value);
|
|
} else {
|
|
pr_err("vrtc read invalid addr 0x%x", offset);
|
|
ret = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
/**
|
|
* @pre vcpu != NULL
|
|
* @pre vcpu->vm != NULL
|
|
*/
|
|
static bool vrtc_write(struct acrn_vcpu *vcpu, uint16_t addr, size_t width,
|
|
uint32_t value)
|
|
{
|
|
time_t current, after;
|
|
struct acrn_vrtc *vrtc = &vcpu->vm->vrtc;
|
|
struct acrn_vrtc temp_vrtc;
|
|
uint8_t mask = 0xFFU;
|
|
|
|
if ((width == 1U) && (addr == CMOS_ADDR_PORT)) {
|
|
vrtc->addr = (uint8_t)(value & 0x7FU);
|
|
} else {
|
|
if (is_service_vm(vcpu->vm)) {
|
|
if (vrtc_is_time_register(vrtc->addr)) {
|
|
current = vrtc_get_physical_rtc_time(&temp_vrtc);
|
|
cmos_set_reg_val(vcpu->vm->vrtc.addr, (uint8_t)(value & 0xFFU));
|
|
after = vrtc_get_physical_rtc_time(&temp_vrtc);
|
|
vrtc_update_basetime(after, current - after);
|
|
} else {
|
|
cmos_set_reg_val(vcpu->vm->vrtc.addr, (uint8_t)(value & 0xFFU));
|
|
}
|
|
} else {
|
|
switch (vrtc->addr) {
|
|
case RTC_STATUSA:
|
|
case RTC_INTR:
|
|
case RTC_STATUSD:
|
|
RTC_DEBUG("RTC reg_%x set to %#x (ignored)\n", vrtc->addr, value);
|
|
break;
|
|
case RTC_STATUSB:
|
|
vrtc_set_reg_b(vrtc, value);
|
|
RTC_DEBUG("RTC reg_b set to %#x\n", value);
|
|
break;
|
|
case RTC_SECALRM:
|
|
case RTC_MINALRM:
|
|
/* FALLTHRU */
|
|
case RTC_HRSALRM:
|
|
*((uint8_t *)&vrtc->rtcdev + vrtc->addr) = (uint8_t)(value & 0x7FU);
|
|
RTC_DEBUG("RTC alarm reg(%d) set to %#x (ignored)\n", vrtc->addr, value);
|
|
break;
|
|
case RTC_SEC:
|
|
/*
|
|
* High order bit of 'seconds' is readonly.
|
|
*/
|
|
mask = 0x7FU;
|
|
/* FALLTHRU */
|
|
default:
|
|
RTC_DEBUG("RTC offset %#x set to %#x\n", vrtc->addr, value);
|
|
*((uint8_t *)&vrtc->rtcdev + vrtc->addr) = (uint8_t)(value & mask);
|
|
current = vrtc_get_current_time(vrtc);
|
|
after = rtc_to_secs(vrtc);
|
|
spinlock_obtain(&vrtc_rebase_lock);
|
|
vrtc->offset_rtctime += after - current;
|
|
vrtc->last_rtctime = VRTC_BROKEN_TIME;
|
|
spinlock_release(&vrtc_rebase_lock);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#define CALIBRATE_PERIOD (3 * 3600 * 1000) /* By ms, totally 3 hours. */
|
|
static struct hv_timer calibrate_timer;
|
|
|
|
static time_t vrtc_get_physical_rtc_time(struct acrn_vrtc *vrtc)
|
|
{
|
|
struct rtcdev *vrtcdev = &vrtc->rtcdev;
|
|
|
|
vrtcdev->sec = cmos_get_reg_val(RTC_SEC);
|
|
vrtcdev->min = cmos_get_reg_val(RTC_MIN);
|
|
vrtcdev->hour = cmos_get_reg_val(RTC_HRS);
|
|
vrtcdev->day_of_month = cmos_get_reg_val(RTC_DAY);
|
|
vrtcdev->month = cmos_get_reg_val(RTC_MONTH);
|
|
vrtcdev->year = cmos_get_reg_val(RTC_YEAR);
|
|
vrtcdev->century = cmos_get_reg_val(RTC_CENTURY);
|
|
vrtcdev->reg_b = cmos_get_reg_val(RTC_STATUSB);
|
|
|
|
return rtc_to_secs(vrtc);
|
|
}
|
|
|
|
static void vrtc_update_basetime(time_t physical_time, time_t offset)
|
|
{
|
|
struct acrn_vm *vm;
|
|
uint32_t vm_id;
|
|
|
|
for (vm_id = 0U; vm_id < CONFIG_MAX_VM_NUM; vm_id++) {
|
|
vm = get_vm_from_vmid(vm_id);
|
|
if (is_rt_vm(vm) || is_prelaunched_vm(vm)) {
|
|
spinlock_obtain(&vrtc_rebase_lock);
|
|
vm->vrtc.base_tsc = cpu_ticks();
|
|
vm->vrtc.base_rtctime = physical_time;
|
|
vm->vrtc.offset_rtctime += offset;
|
|
spinlock_release(&vrtc_rebase_lock);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void calibrate_timer_callback(__unused void *data)
|
|
{
|
|
struct acrn_vrtc temp_vrtc;
|
|
time_t physical_time = vrtc_get_physical_rtc_time(&temp_vrtc);
|
|
|
|
vrtc_update_basetime(physical_time, 0);
|
|
}
|
|
|
|
static void calibrate_setup_timer(void)
|
|
{
|
|
uint64_t period_in_cycle, fire_tsc;
|
|
|
|
period_in_cycle = TICKS_PER_MS * CALIBRATE_PERIOD;
|
|
fire_tsc = cpu_ticks() + period_in_cycle;
|
|
initialize_timer(&calibrate_timer,
|
|
calibrate_timer_callback, NULL,
|
|
fire_tsc, period_in_cycle);
|
|
|
|
/* Start an periodic timer */
|
|
if (add_timer(&calibrate_timer) != 0) {
|
|
pr_err("Failed to add calibrate timer");
|
|
}
|
|
}
|
|
|
|
static void vrtc_set_basetime(struct acrn_vrtc *vrtc)
|
|
{
|
|
struct rtcdev *vrtcdev = &vrtc->rtcdev;
|
|
time_t current;
|
|
|
|
/*
|
|
* Read base time from physical rtc.
|
|
*/
|
|
vrtcdev->sec = cmos_get_reg_val(RTC_SEC);
|
|
vrtcdev->min = cmos_get_reg_val(RTC_MIN);
|
|
vrtcdev->hour = cmos_get_reg_val(RTC_HRS);
|
|
vrtcdev->day_of_month = cmos_get_reg_val(RTC_DAY);
|
|
vrtcdev->month = cmos_get_reg_val(RTC_MONTH);
|
|
vrtcdev->year = cmos_get_reg_val(RTC_YEAR);
|
|
vrtcdev->century = cmos_get_reg_val(RTC_CENTURY);
|
|
vrtcdev->reg_a = cmos_get_reg_val(RTC_STATUSA) & (~RTCSA_TUP);
|
|
vrtcdev->reg_b = cmos_get_reg_val(RTC_STATUSB);
|
|
vrtcdev->reg_c = cmos_get_reg_val(RTC_INTR);
|
|
vrtcdev->reg_d = cmos_get_reg_val(RTC_STATUSD);
|
|
|
|
current = rtc_to_secs(vrtc);
|
|
spinlock_obtain(&vrtc_rebase_lock);
|
|
vrtc->base_rtctime = current;
|
|
vrtc->last_rtctime = VRTC_BROKEN_TIME;
|
|
spinlock_release(&vrtc_rebase_lock);
|
|
}
|
|
|
|
void vrtc_init(struct acrn_vm *vm)
|
|
{
|
|
struct vm_io_range range = {
|
|
.base = CMOS_ADDR_PORT, .len = 2U};
|
|
|
|
/* Initializing the CMOS RAM offset to 0U */
|
|
vm->vrtc.addr = 0U;
|
|
|
|
vm->vrtc.vm = vm;
|
|
register_pio_emulation_handler(vm, RTC_PIO_IDX, &range, vrtc_read, vrtc_write);
|
|
|
|
if (is_service_vm(vm)) {
|
|
calibrate_setup_timer();
|
|
} else {
|
|
vrtc_set_basetime(&vm->vrtc);
|
|
vm->vrtc.base_tsc = cpu_ticks();
|
|
}
|
|
}
|