diff --git a/hypervisor/dm/vrtc.c b/hypervisor/dm/vrtc.c index 23fb978c2..91cdd38df 100644 --- a/hypervisor/dm/vrtc.c +++ b/hypervisor/dm/vrtc.c @@ -1,18 +1,272 @@ /* - * Copyright (C) 2018 Intel Corporation. + * Copyright (c) 2014, Neel Natu (neel@freebsd.org) + * 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 #include +#include +#include +#include + +#include "mc146818rtc.h" + +/* #define DEBUG_RTC */ +#ifdef DEBUG_RTC +# define RTC_DEBUG pr_info +#else +# define RTC_DEBUG(format, ...) do { } while (false) +#endif + +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) */ +}; + +#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; +} + +/* + * 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; + + do { + ct.sec = rtc->sec; + ct.min = rtc->min; + ct.hour = rtc->hour; + ct.day = rtc->day_of_month; + ct.mon = rtc->month; + + /* + * 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 = rtc->century * 100 + rtc->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; + + if ((rtctime > 0) && (clk_ts_to_ct(rtctime, &ct) == 0)) { + rtc = &vrtc->rtcdev; + rtc->sec = ct.sec; + rtc->min = ct.min; + rtc->hour = ct.hour; + rtc->day_of_week = ct.dow + 1; + rtc->day_of_month = ct.day; + rtc->month = ct.mon; + rtc->year = ct.year % 100; + rtc->century = 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; + + if (vrtc->base_rtctime > 0) { + offset = (cpu_ticks() - vrtc->base_tsc) / (get_tsc_khz() * 1000U); + second = vrtc->base_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) @@ -23,7 +277,7 @@ static uint8_t cmos_read(uint8_t addr) static bool cmos_update_in_progress(void) { - return (cmos_read(RTC_STATUSA) & RTCSA_TUP)?1:0; + return (cmos_read(RTC_STATUSA) & RTCSA_TUP) ? 1 : 0; } static uint8_t cmos_get_reg_val(uint8_t addr) @@ -51,18 +305,34 @@ 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; + bool ret = true; - 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); + 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); + + pio_req->value = *((uint8_t *)&vm->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 true; + return ret; } /** @@ -73,19 +343,45 @@ 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) +{ + struct rtcdev *vrtcdev = &vrtc->rtcdev; + + /* + * 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); + + 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_tsc = 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 #include #include +#include #include #include #include @@ -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/mc146818rtc.h b/hypervisor/include/dm/mc146818rtc.h new file mode 100644 index 000000000..694ef3dbf --- /dev/null +++ b/hypervisor/include/dm/mc146818rtc.h @@ -0,0 +1,64 @@ +/*- + * Copyright (c) 1990 The Regents of the University of California. + * All rights reserved. + * + * Copyright (C) 2022 Intel Corporation. + * + * This code is derived from software contributed to Berkeley by + * William Jolitz. + * + * 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, 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. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + * + * from: @(#)rtc.h 7.1 (Berkeley) 5/12/91 + * $FreeBSD$ + */ + +#ifndef _MC146818_RTC_H_ +#define _MC146818_RTC_H_ + +/* + * MC146818 RTC Register locations + */ + +#define RTC_SEC 0x00 /* seconds */ +#define RTC_SECALRM 0x01 /* seconds alarm */ +#define RTC_MIN 0x02 /* minutes */ +#define RTC_MINALRM 0x03 /* minutes alarm */ +#define RTC_HRS 0x04 /* hours */ +#define RTC_HRSALRM 0x05 /* hours alarm */ +#define RTC_WDAY 0x06 /* week day */ +#define RTC_DAY 0x07 /* day of month */ +#define RTC_MONTH 0x08 /* month of year */ +#define RTC_YEAR 0x09 /* year in century*/ +#define RTC_CENTURY 0x32 /* century */ + +#define RTC_STATUSA 0x0a /* status register A */ +#define RTCSA_TUP 0x80U /* time update, don't look now */ + +#define RTC_STATUSB 0x0b /* status register B */ +#define RTC_INTR 0x0c /* status register C (R) interrupt source */ +#define RTC_STATUSD 0x0d /* status register D (R) Lost Power */ + +#endif /* _MC146818_RTC_H_ */ diff --git a/hypervisor/include/dm/vrtc.h b/hypervisor/include/dm/vrtc.h new file mode 100644 index 000000000..fff2559d2 --- /dev/null +++ b/hypervisor/include/dm/vrtc.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 Intel Corporation. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef VRTC_H +#define VRTC_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 res[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. */ + uint64_t base_tsc; /* Base tsc value */ + + struct rtcdev rtcdev; /* RTC register */ +}; + +#endif /* VRTC_H */