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, ¢ury); + 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
|