Re: [PATCH 3/5] hv: add `rtcdev` emulate vrtc


Zhao, Yuanyuan
 

On 4/15/2022 8:25 AM, Eddie Dong wrote:
This patch doesn't have a cover letter. I am unable to know the overall goal of the work. Please make it clear.
OK, i will add it in v2.
BTW, I am not sure if we need this kind of full emulation. We already of a simplified vrtc model. Can not we use it with minimal change?
Most modification is used to transformation between rtc register and time_t. In v2 i will refine some detail to reduce codes.


-----Original Message-----
From: acrn-dev@... <acrn-dev@...> On
Behalf Of Zhao, Yuanyuan
Sent: Thursday, April 14, 2022 4:57 AM
To: Li, Fei1 <fei1.li@...>; acrn-dev@...
Cc: yuanyuan.zhao@...
Subject: [acrn-dev] [PATCH 3/5] hv: add `rtcdev` emulate vrtc

For RTVM & pre-launch VM, acrn used to read from physical RTC.
This patch add `struct rtcdev` to emulate the vrtc.

Read physical RTC and save a base time when initialize vrtc.

After initialization, the time is calculated by base time and tsc offset.
Translate time to `struct rtcdev` and return the required register value.

Signed-off-by: Yuanyuan Zhao <yuanyuan.zhao@...>
---
hypervisor/dm/vrtc.c | 470 ++++++++++++++++++++-
hypervisor/include/arch/x86/asm/guest/vm.h | 3 +-
hypervisor/include/dm/vrtc.h | 45 ++
3 files changed, 507 insertions(+), 11 deletions(-) create mode 100644
hypervisor/include/dm/vrtc.h

diff --git a/hypervisor/dm/vrtc.c b/hypervisor/dm/vrtc.c index
23fb978c2..dbff7dfb8 100644
--- a/hypervisor/dm/vrtc.c
+++ b/hypervisor/dm/vrtc.c
@@ -1,18 +1,436 @@
/*
- * Copyright (C) 2018 Intel Corporation.
+ * Copyright (c) 2014, Neel Natu (neel@...)
+ * Copyright (c) 2022 Intel Corporation
+ * All rights reserved.
*
- * SPDX-License-Identifier: BSD-3-Clause
+ * 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 "mc146818rtc.h"
+
+/* #define DEBUG_RTC */
+#ifdef DEBUG_RTC
+# define RTC_DEBUG pr_info
+#else
+# define RTC_DEBUG(format, ...) do { } while (false)
+#endif
+
+static inline bool rtc_halted(struct acrn_vrtc *rtc);
+
+struct clktime {
+ int32_t year; /* year (4 digit year) */
+ int32_t mon; /* month (1 - 12) */
+ int32_t day; /* day (1 - 31) */
+ int32_t hour; /* hour (0 - 23) */
+ int32_t min; /* minute (0 - 59) */
+ int32_t sec; /* second (0 - 59) */
+ int32_t dow; /* day of week (0 - 6; 0 =
Sunday) */
+};
+
+#define POSIX_BASE_YEAR 1970
+#define SECDAY (24 * 60 * 60)
+#define SECYR (SECDAY * 365)
+#define VRTC_BROKEN_TIME ((time_t)-1)
+
+#define FEBRUARY 2
+
+static const int32_t 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 inline int32_t leapyear(int32_t year) {
+ int32_t rv = 0;
+
+ if ((year & 3) == 0) {
+ rv = 1;
+ if ((year % 100) == 0) {
+ rv = 0;
+ if ((year % 400) == 0) {
+ rv = 1;
+ }
+ }
+ }
+ return rv;
+}
+
+static inline int32_t days_in_year(int32_t year) {
+ return leapyear(year) ? 366 : 365; }
+
+static inline int32_t days_in_month(int32_t year, int32_t month) {
+ return month_days[(month) - 1] + (month == FEBRUARY ?
+leapyear(year) : 0); }
+
+/*
+ * Day of week. Days are counted from 1/1/1970, which was a Thursday.
+ */
+static inline int32_t day_of_week(int32_t days) {
+ return ((days) + 4) % 7;
+}
+
+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, int 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, int32_t val, int32_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 > 9 || upper > 9) {
+ errno = -1;
+ } else {
+ *retval = upper * 10 + lower;
+ }
+ }
+ return errno;
+}
+
+/*
+ * Tranlate clktime(such as year, month, day) to time_t.
+ */
+static int32_t clk_ct_to_ts(struct clktime *ct, time_t *sec) {
+ int32_t i, year, days;
+ int32_t errno = 0;
+
+ 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 */
+ errno = -1;
+ } else {
+ /*
+ * 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);
+
+ *sec = (((time_t)days * 24 + ct->hour) * 60 + ct->min) * 60 +
+ ct->sec;
+ }
+ return errno;
+}
+
+/*
+ * Tranlate time_t to clktime(such as year, month, day) */ static
+int32_t clk_ts_to_ct(time_t secs, struct clktime *ct) {
+ time_t i, year, days;
+ time_t rsec; /* remainder seconds */
+ int32_t errno = 0;
+
+ 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;
+
+ 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)) {
+ errno = -1;
+ }
+ return errno;
+}
+
+/*
+ * Calculate second value from rtcdev regsiter 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;
+ int32_t century = 0, error = 0, hour = 0, pm = 0, year = 0;
+
+ do {
+ (void)memset(&ct, 0U, 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);
+ break;
+ }
+
+ 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);
+ break;
+ }
+
+ pm = 0;
+ hour = rtc->hour;
+ if ((rtc->reg_b & RTCSB_24HR) == 0) {
+ if (hour & 0x80U) {
+ hour &= ~0x80U;
+ 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);
+ break;
+ }
+ }
+
+ if (error || ct.hour < 0 || ct.hour > 23) {
+ RTC_DEBUG("Invalid RTC hour %#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;
+
+ 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);
+ break;
+ }
+
+ 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);
+ break;
+ }
+
+ error = rtcget(rtc, rtc->year, &year);
+ if (error || year < 0 || year > 99) {
+ RTC_DEBUG("Invalid RTC year %#x/%d\n", rtc-
year, year);
+ break;
+ }
+
+ 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);
+ break;
+ }
+
+ error = clk_ct_to_ts(&ct, &second);
+ if (error || second < 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);
+ break;
+ }
+ } while (false);
+
+ return second;
+}
+
+/*
+ * Translate second value to rtcdev regsiter info and save it in vrtc.
+ */
+static void secs_to_rtc(time_t rtctime, struct acrn_vrtc *vrtc, int32_t
+force_update) {
+ struct clktime ct;
+ struct rtcdev *rtc;
+ int32_t hour;
+
+ do {
+ if (rtctime < 0) {
+ /*VRTC_BROKEN_TIME case*/
+ break;
+ }
+
+ /*
+ * 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) {
+ break;
+ }
+
+ if (clk_ts_to_ct(rtctime, &ct) < 0) {
+ break;
+ }
+
+ 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);
+ } while (false);
+}
+
+/*
+ * If the base_rtctime is valid, calculate current time by add tsc offset and
offset_rtctime..
+ * The offset_rtctime saves the offset for time calibrate.
+ */
+static time_t vrtc_get_current_time(struct acrn_vrtc *vrtc) {
+ uint64_t offset;
+ time_t second = VRTC_BROKEN_TIME;
+
+ if (vrtc->base_rtctime > 0) {
+ offset = (cpu_ticks() - vrtc->base_tsctime) / (get_tsc_khz() *
1000U);
+ second = vrtc->base_rtctime + vrtc->offset_rtctime +
(time_t)offset;
+ }
+ return second;
+}

#define CMOS_ADDR_PORT 0x70U
#define CMOS_DATA_PORT 0x71U

-#define RTC_STATUSA 0x0AU /* status register A */
-#define RTCSA_TUP 0x80U /* time update, don't look now */
-
static spinlock_t cmos_lock = { .head = 0U, .tail = 0U };

static uint8_t cmos_read(uint8_t addr)
@@ -44,6 +462,11 @@ static uint8_t cmos_get_reg_val(uint8_t addr)
return reg;
}

+static inline bool rtc_halted(struct acrn_vrtc *rtc) {
+ return ((rtc->rtcdev.reg_b & RTCSB_HALT) != 0U); }
+
/**
* @pre vcpu != NULL
* @pre vcpu->vm != NULL
@@ -51,15 +474,21 @@ static uint8_t cmos_get_reg_val(uint8_t addr)
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;

- offset = vm->vrtc_offset;
+ offset = vm->vrtc.addr;

if (addr == CMOS_ADDR_PORT) {
- pio_req->value = vm->vrtc_offset;
+ pio_req->value = offset;
} else {
- pio_req->value = cmos_get_reg_val(offset);
+ current = vrtc_get_current_time(vrtc);
+ secs_to_rtc(current, vrtc, 0);
+
+ pio_req->value = *((uint8_t *)&vm->vrtc.rtcdev + offset);
+ RTC_DEBUG("read 0x%x, 0x%x", offset, pio_req->value);
}

return true;
@@ -73,19 +502,40 @@ static bool vrtc_write(struct acrn_vcpu *vcpu,
uint16_t addr, size_t width,
uint32_t value)
{
if ((width == 1U) && (addr == CMOS_ADDR_PORT)) {
- vcpu->vm->vrtc_offset = (uint8_t)value & 0x7FU;
+ vcpu->vm->vrtc.addr = (uint8_t)value & 0x7FU;
}

return true;
}

+static void vrtc_set_basetime(struct acrn_vrtc *vrtc) {
+ uint32_t offset;
+ uint8_t *rtc = (uint8_t *)&vrtc->rtcdev;
+
+ /*
+ * Read base time from physicall rtc.
+ */
+ for(offset = 0; offset < 14; offset++) {
+ *(rtc + offset) = cmos_get_reg_val(offset);
+ RTC_DEBUG("rtc init %x, %d", offset, *(rtc + offset));
+ }
+
+ vrtc->rtcdev.century = cmos_get_reg_val(&vrtc->rtcdev.century -
&vrtc->rtcdev.sec);
+ vrtc->base_rtctime = rtc_to_secs(vrtc); }
+
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_offset = 0U;
+ vm->vrtc.addr = 0U;

+ vm->vrtc.vm = vm;
register_pio_emulation_handler(vm, RTC_PIO_IDX, &range,
vrtc_read, vrtc_write);
+
+ vrtc_set_basetime(&vm->vrtc);
+ vm->vrtc.base_tsctime = cpu_ticks();
}
diff --git a/hypervisor/include/arch/x86/asm/guest/vm.h
b/hypervisor/include/arch/x86/asm/guest/vm.h
index 589d0a913..c05b9b212 100644
--- a/hypervisor/include/arch/x86/asm/guest/vm.h
+++ b/hypervisor/include/arch/x86/asm/guest/vm.h
@@ -21,6 +21,7 @@
#include <vpic.h>
#include <asm/guest/vmx_io.h>
#include <vuart.h>
+#include <vrtc.h>
#include <asm/guest/trusty.h>
#include <asm/guest/vcpuid.h>
#include <vpci.h>
@@ -169,7 +170,7 @@ struct acrn_vm {
uint32_t vcpuid_entry_nr, vcpuid_level, vcpuid_xlevel;
struct vcpuid_entry vcpuid_entries[MAX_VM_VCPUID_ENTRIES];
struct acrn_vpci vpci;
- uint8_t vrtc_offset;
+ struct acrn_vrtc vrtc;

uint64_t intr_inject_delay_delta; /* delay of intr injection */ }
__aligned(PAGE_SIZE); diff --git a/hypervisor/include/dm/vrtc.h
b/hypervisor/include/dm/vrtc.h new file mode 100644 index
000000000..3966e8f76
--- /dev/null
+++ b/hypervisor/include/dm/vrtc.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 Intel Corporation. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause */
+
+#ifndef VRTC_H
+#define VRTC_H
+
+#include <asm/lib/spinlock.h>
+
+typedef int32_t time_t;
+
+/* 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;
+};
+
+struct acrn_vrtc {
+ struct acrn_vm *vm;
+ uint32_t addr; /* RTC register to read or write */
+
+ time_t base_rtctime; /* Base time calulated from
physical rtc register. */
+ time_t offset_rtctime; /* Time calibrate value */
+ uint64_t base_tsctime; /* Base tsc value */
+
+ struct rtcdev rtcdev; /* RTC register */
+};
+
+#endif /* VRTC_H */
--
2.25.1




Join {acrn-dev@lists.projectacrn.org to automatically receive all group messages.