acrn-hypervisor/hypervisor/dm/vrtc.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, &century) < 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();
}
}