acrn-hypervisor/hypervisor/dm/vrtc.c
Yuanyuan Zhao 145a56d448 hv: calibrate vrtc when modify physical time
For Service VM modify physical rtc time and vrtc will calibrate
time by read physical rtc. So when Service VM modify physical
time, calibrate all vrtc.

Tracked-On: #7440
Signed-off-by: Yuanyuan Zhao <yuanyuan.zhao@linux.intel.com>
Reviewed-by: Junjie Mao <junjie.mao@intel.com>
2022-05-12 13:20:21 +08:00

706 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;
}
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;
bool is_time_register;
uint8_t mask = 0xFFU;
if ((width == 1U) && (addr == CMOS_ADDR_PORT)) {
vrtc->addr = (uint8_t)(value & 0x7FU);
} else {
if (is_service_vm(vcpu->vm)) {
is_time_register = vrtc_is_time_register(vrtc->addr);
if (is_time_register) {
current = vrtc_get_physical_rtc_time(&temp_vrtc);
}
cmos_set_reg_val(vcpu->vm->vrtc.addr, (uint8_t)(value & 0xFFU));
if (is_time_register) {
after = vrtc_get_physical_rtc_time(&temp_vrtc);
vrtc_update_basetime(after, current - after);
}
} 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;
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;
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();
}
}