/*- * Copyright (c) 2014, Neel Natu (neel@freebsd.org) * 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 #include #include #include #include #include #include #include #include "vmmapi.h" #include "inout.h" #include "mc146818rtc.h" #include "rtc.h" #include "mevent.h" #include "timer.h" #include "acpi.h" #include "lpc.h" /* #define DEBUG_RTC */ #ifdef DEBUG_RTC # define RTC_DEBUG(format, ...) printf(format, ## __VA_ARGS__) #else # define RTC_DEBUG(format, ...) do { } while (0) #endif /* 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; uint8_t nvram2[128 - 51]; } __packed; struct vrtc { struct vmctx *vm; pthread_mutex_t mtx; struct acrn_timer update_timer; /* timer for update interrupt */ struct acrn_timer periodic_timer; /* timer for periodic interrupt */ u_int addr; /* RTC register to read or write */ time_t base_uptime; time_t base_rtctime; struct rtcdev rtcdev; }; /* * Structure to hold the values typically reported by time-of-day clocks. * This can be passed to the generic conversion functions to be converted * to a struct timespec. */ struct clktime { int year; /* year (4 digit year) */ int mon; /* month (1 - 12) */ int day; /* day (1 - 31) */ int hour; /* hour (0 - 23) */ int min; /* minute (0 - 59) */ int sec; /* second (0 - 59) */ int dow; /* day of week (0 - 6; 0 = Sunday) */ long nsec; /* nano seconds */ }; /* Some handy constants. */ #define SECDAY (24 * 60 * 60) #define SECYR (SECDAY * 365) /* Traditional POSIX base year */ #define POSIX_BASE_YEAR 1970 #define SBT_1S ((time_t)1000000000LL) #define SBT_1M (SBT_1S * 60) #define SBT_1MS (SBT_1S / 1000) #define SBT_1US (SBT_1S / 1000000) #define SBT_1NS (SBT_1S / 1000000000) #define SBT_MAX 0x7fffffffffffffffLL /* * RTC time is considered "broken" if: * - RTC updates are halted by the guest * - RTC date/time fields have invalid values */ #define VRTC_BROKEN_TIME ((time_t)-1) /* signo of timers created by vrtc */ #define VRTC_TIMER_SIGNO SIGALRM #define RTC_IRQ 8 #define RTCSB_BIN 0x04 #define RTCSB_ALL_INTRS (RTCSB_UINTR | RTCSB_AINTR | RTCSB_PINTR) #define rtc_halted(rtc) ((rtc->rtcdev.reg_b & RTCSB_HALT) != 0) #define aintr_enabled(rtc) (((rtc)->rtcdev.reg_b & RTCSB_AINTR) != 0) #define pintr_enabled(rtc) (((rtc)->rtcdev.reg_b & RTCSB_PINTR) != 0) #define uintr_enabled(rtc) (((rtc)->rtcdev.reg_b & RTCSB_UINTR) != 0) /*--------------------------------------------------------------------* * Generic routines to convert between a POSIX date * (seconds since 1/1/1970) and yr/mo/day/hr/min/sec * Derived from NetBSD arch/hp300/hp300/clock.c */ #define FEBRUARY 2 #define days_in_year(y) (leapyear(y) ? 366 : 365) #define days_in_month(y, m) \ (month_days[(m) - 1] + (m == FEBRUARY ? leapyear(y) : 0)) /* Day of week. Days are counted from 1/1/1970, which was a Thursday */ #define day_of_week(days) (((days) + 4) % 7) static const int 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 int leapyear(int year) { int rv = 0; if ((year & 3) == 0) { rv = 1; if ((year % 100) == 0) { rv = 0; if ((year % 400) == 0) rv = 1; } } return rv; } u_char const bin2bcd_data[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99 }; static void vrtc_set_reg_c(struct vrtc *vrtc, uint8_t newval); static int rtc_flag_broken_time = 1; static inline int divider_enabled(int reg_a) { /* * The RTC is counting only when dividers are not held in reset. */ return ((reg_a & 0x70) == 0x20); } static inline int update_enabled(struct vrtc *vrtc) { /* * RTC date/time can be updated only if: * - divider is not held in reset * - guest has not disabled updates * - the date/time fields have valid contents */ if (!divider_enabled(vrtc->rtcdev.reg_a)) return false; if (rtc_halted(vrtc)) return false; if (vrtc->base_rtctime == VRTC_BROKEN_TIME) return false; return true; } static time_t vrtc_curtime(struct vrtc *vrtc, time_t *basetime) { time_t now, delta; time_t t, secs; t = vrtc->base_rtctime; *basetime = vrtc->base_uptime; if (update_enabled(vrtc)) { now = time(NULL); delta = now - vrtc->base_uptime; assert(delta >= 0); secs = delta; t += secs; *basetime += secs; } return t; } static int clk_ct_to_ts(struct clktime *ct, struct timespec *ts) { int i, year, days; 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 */ return -1; } /* * 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); ts->tv_sec = (((time_t)days * 24 + ct->hour) * 60 + ct->min) * 60 + ct->sec; ts->tv_nsec = ct->nsec; return 0; } static void clk_ts_to_ct(struct timespec *ts, struct clktime *ct) { time_t i, year, days; time_t rsec; /* remainder seconds */ time_t secs; secs = ts->tv_sec; 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; ct->nsec = ts->tv_nsec; assert(ct->year >= 0 && ct->year < 10000); assert(ct->mon >= 1 && ct->mon <= 12); assert(ct->day >= 1 && ct->day <= 31); assert(ct->hour >= 0 && ct->hour <= 23); assert(ct->min >= 0 && ct->min <= 59); /* Not sure if this interface needs to handle leapseconds or not. */ assert(ct->sec >= 0 && ct->sec <= 60); } static inline uint8_t rtcset(struct rtcdev *rtc, int val) { assert(val >= 0 && val < 100); return ((rtc->reg_b & RTCSB_BIN) ? val : bin2bcd_data[val]); } static time_t vrtc_freq(struct vrtc *vrtc) { int ratesel; static time_t pf[16] = { 0, SBT_1S / 256, SBT_1S / 128, SBT_1S / 8192, SBT_1S / 4096, SBT_1S / 2048, SBT_1S / 1024, SBT_1S / 512, SBT_1S / 256, SBT_1S / 128, SBT_1S / 64, SBT_1S / 32, SBT_1S / 16, SBT_1S / 8, SBT_1S / 4, SBT_1S / 2, }; /* * If both periodic and alarm interrupts are enabled then use the * periodic frequency to drive the callout. The minimum periodic * frequency (2 Hz) is higher than the alarm frequency (1 Hz) so * piggyback the alarm on top of it. The same argument applies to * the update interrupt. */ if (pintr_enabled(vrtc) && divider_enabled(vrtc->rtcdev.reg_a)) { ratesel = vrtc->rtcdev.reg_a & 0xf; return pf[ratesel]; } else if (aintr_enabled(vrtc) && update_enabled(vrtc)) return SBT_1S; else if (uintr_enabled(vrtc) && update_enabled(vrtc)) return SBT_1S; else return 0; } static int rtcget(struct rtcdev *rtc, int val, int *retval) { uint8_t upper, lower; if (rtc->reg_b & RTCSB_BIN) { *retval = val; return 0; } lower = val & 0xf; upper = (val >> 4) & 0xf; if (lower > 9 || upper > 9) return -1; *retval = upper * 10 + lower; return 0; } static void secs_to_rtc(time_t rtctime, struct vrtc *vrtc, int force_update) { struct clktime ct; struct timespec ts; struct rtcdev *rtc; int hour; if (rtctime < 0) { assert(rtctime == VRTC_BROKEN_TIME); return; } /* * 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) return; ts.tv_sec = rtctime; ts.tv_nsec = 0; clk_ts_to_ct(&ts, &ct); assert(ct.sec >= 0 && ct.sec <= 59); assert(ct.min >= 0 && ct.min <= 59); assert(ct.hour >= 0 && ct.hour <= 23); assert(ct.dow >= 0 && ct.dow <= 6); assert(ct.day >= 1 && ct.day <= 31); assert(ct.mon >= 1 && ct.mon <= 12); assert(ct.year >= POSIX_BASE_YEAR); 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); } static time_t rtc_to_secs(struct vrtc *vrtc) { struct clktime ct; struct timespec ts; struct rtcdev *rtc; int century, error, hour, pm, year; rtc = &vrtc->rtcdev; bzero(&ct, 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", rtc->sec, ct.sec); goto fail; } error = rtcget(rtc, rtc->min, &ct.min); if (error || ct.min < 0 || ct.min > 59) { RTC_DEBUG("Invalid RTC min %#x/%d", rtc->min, ct.min); goto fail; } pm = 0; hour = rtc->hour; if ((rtc->reg_b & RTCSB_24HR) == 0) { if (hour & 0x80) { hour &= ~0x80; 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", rtc->hour, ct.hour); goto fail; } } if (error || ct.hour < 0 || ct.hour > 23) { RTC_DEBUG("Invalid RTC hour %#x/%d", rtc->hour, ct.hour); goto fail; } /* * 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", rtc->day_of_month, ct.day); goto fail; } error = rtcget(rtc, rtc->month, &ct.mon); if (error || ct.mon < 1 || ct.mon > 12) { RTC_DEBUG("Invalid RTC month %#x/%d", rtc->month, ct.mon); goto fail; } error = rtcget(rtc, rtc->year, &year); if (error || year < 0 || year > 99) { RTC_DEBUG("Invalid RTC year %#x/%d", rtc->year, year); goto fail; } 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", rtc->century, ct.year); goto fail; } error = clk_ct_to_ts(&ct, &ts); if (error || ts.tv_sec < 0) { RTC_DEBUG("Invalid RTC clocktime.date %04d-%02d-%02d", ct.year, ct.mon, ct.day); RTC_DEBUG("Invalid RTC clocktime.time %02d:%02d:%02d", ct.hour, ct.min, ct.sec); goto fail; } return ts.tv_sec; /* success */ fail: /* * Stop updating the RTC if the date/time fields programmed by * the guest are invalid. */ RTC_DEBUG("Invalid RTC date/time programming detected"); return VRTC_BROKEN_TIME; } static void vrtc_start_timer(struct acrn_timer *timer, time_t sec, time_t nsec) { struct itimerspec ts; /*setting the interval time*/ ts.it_interval.tv_sec = sec; ts.it_interval.tv_nsec = nsec; /*set the delay time it will be started when timer_setting*/ ts.it_value.tv_sec = sec; ts.it_value.tv_nsec = nsec; assert(acrn_timer_settime(timer, &ts) == 0); } static int vrtc_time_update(struct vrtc *vrtc, time_t newtime, time_t newbase) { struct rtcdev *rtc; time_t oldtime; uint8_t alarm_sec, alarm_min, alarm_hour; rtc = &vrtc->rtcdev; alarm_sec = rtc->alarm_sec; alarm_min = rtc->alarm_min; alarm_hour = rtc->alarm_hour; oldtime = vrtc->base_rtctime; RTC_DEBUG("Updating RTC secs from %#lx to %#lx\n", oldtime, newtime); RTC_DEBUG("Updating RTC base uptime from %#lx to %#lx\n", vrtc->base_uptime, newbase); vrtc->base_uptime = newbase; if (newtime == oldtime) return 0; /* * If 'newtime' indicates that RTC updates are disabled then just * record that and return. There is no need to do alarm interrupt * processing in this case. */ if (newtime == VRTC_BROKEN_TIME) { vrtc->base_rtctime = VRTC_BROKEN_TIME; return 0; } /* * Return an error if RTC updates are halted by the guest. */ if (rtc_halted(vrtc)) { RTC_DEBUG("RTC update halted by guest\n"); return -1; } do { /* * If the alarm interrupt is enabled and 'oldtime' is valid * then visit all the seconds between 'oldtime' and 'newtime' * to check for the alarm condition. * * Otherwise move the RTC time forward directly to 'newtime'. */ if (aintr_enabled(vrtc) && oldtime != VRTC_BROKEN_TIME) vrtc->base_rtctime++; else vrtc->base_rtctime = newtime; if (aintr_enabled(vrtc)) { /* * Update the RTC date/time fields before checking * if the alarm conditions are satisfied. */ secs_to_rtc(vrtc->base_rtctime, vrtc, 0); if ((alarm_sec >= 0xC0 || alarm_sec == rtc->sec) && (alarm_min >= 0xC0 || alarm_min == rtc->min) && (alarm_hour >= 0xC0 || alarm_hour == rtc->hour)) { vrtc_set_reg_c(vrtc, rtc->reg_c | RTCIR_ALARM); } } } while (vrtc->base_rtctime != newtime); if (uintr_enabled(vrtc)) vrtc_set_reg_c(vrtc, rtc->reg_c | RTCIR_UPDATE); return 0; } static void vrtc_periodic_timer(void *arg, uint64_t nexp) { struct vrtc *vrtc = arg; pthread_mutex_lock(&vrtc->mtx); if (pintr_enabled(vrtc)) vrtc_set_reg_c(vrtc, vrtc->rtcdev.reg_c | RTCIR_PERIOD); pthread_mutex_unlock(&vrtc->mtx); } static void vrtc_update_timer(void *arg, uint64_t nexp) { struct vrtc *vrtc = arg; time_t basetime; time_t curtime; pthread_mutex_lock(&vrtc->mtx); if (aintr_enabled(vrtc) || uintr_enabled(vrtc)) { curtime = vrtc_curtime(vrtc, &basetime); vrtc_time_update(vrtc, curtime, basetime); } pthread_mutex_unlock(&vrtc->mtx); } static void vrtc_set_reg_c(struct vrtc *vrtc, uint8_t newval) { struct rtcdev *rtc; int oldirqf, newirqf; uint8_t oldval, changed; rtc = &vrtc->rtcdev; newval &= RTCIR_ALARM | RTCIR_PERIOD | RTCIR_UPDATE; oldirqf = rtc->reg_c & RTCIR_INT; if ((aintr_enabled(vrtc) && (newval & RTCIR_ALARM) != 0) || (pintr_enabled(vrtc) && (newval & RTCIR_PERIOD) != 0) || (uintr_enabled(vrtc) && (newval & RTCIR_UPDATE) != 0)) { newirqf = RTCIR_INT; } else { newirqf = 0; } oldval = rtc->reg_c; rtc->reg_c = newirqf | newval; changed = oldval ^ rtc->reg_c; if (changed) { RTC_DEBUG("RTC reg_c changed from %#x to %#x\n", oldval, rtc->reg_c); } if (!oldirqf && newirqf) { vm_set_gsi_irq(vrtc->vm, RTC_IRQ, GSI_SET_HIGH); RTC_DEBUG("RTC irq %d asserted\n", RTC_IRQ); } else if (oldirqf && !newirqf) { vm_set_gsi_irq(vrtc->vm, RTC_IRQ, GSI_SET_LOW); RTC_DEBUG("RTC irq %d deasserted\n", RTC_IRQ); } } static int vrtc_set_reg_b(struct vrtc *vrtc, uint8_t newval) { struct rtcdev *rtc; time_t oldfreq, newfreq, basetime; time_t curtime, rtctime; int error; uint8_t oldval, changed; rtc = &vrtc->rtcdev; oldval = rtc->reg_b; oldfreq = vrtc_freq(vrtc); rtc->reg_b = newval; changed = oldval ^ newval; if (changed) { RTC_DEBUG("RTC reg_b changed from %#x to %#x\n", oldval, newval); } if (changed & RTCSB_HALT) { if ((newval & RTCSB_HALT) == 0) { rtctime = rtc_to_secs(vrtc); basetime = time(NULL); if (rtctime == VRTC_BROKEN_TIME) { if (rtc_flag_broken_time) return -1; } } else { curtime = vrtc_curtime(vrtc, &basetime); assert(curtime == vrtc->base_rtctime); /* * Force a refresh of the RTC date/time fields so * they reflect the time right before the guest set * the HALT bit. */ secs_to_rtc(curtime, vrtc, 1); /* * Updates are halted so mark 'base_rtctime' to denote * that the RTC date/time is in flux. */ rtctime = VRTC_BROKEN_TIME; rtc->reg_b &= ~RTCSB_UINTR; } error = vrtc_time_update(vrtc, rtctime, basetime); assert(error == 0); } /* * Side effect of changes to the interrupt enable bits. */ if (changed & RTCSB_ALL_INTRS) vrtc_set_reg_c(vrtc, vrtc->rtcdev.reg_c); /* * Change the callout frequency if it has changed. */ newfreq = vrtc_freq(vrtc); if (pintr_enabled(vrtc) && newfreq != oldfreq) { /*start the new periodic timer*/ vrtc_start_timer(&vrtc->periodic_timer, 0, newfreq); } else { /*Nothing to do*/ } /* * The side effect of bits that control the RTC date/time format * is handled lazily when those fields are actually read. */ return 0; } static void vrtc_set_reg_a(struct vrtc *vrtc, uint8_t newval) { time_t oldfreq, newfreq; uint8_t oldval, changed; newval &= ~RTCSA_TUP; oldval = vrtc->rtcdev.reg_a; oldfreq = vrtc_freq(vrtc); if (divider_enabled(oldval) && !divider_enabled(newval)) { RTC_DEBUG("RTC divider held in reset at %#lx/%#lx", vrtc->base_rtctime, vrtc->base_uptime); } else if (!divider_enabled(oldval) && divider_enabled(newval)) { /* * If the dividers are coming out of reset then update * 'base_uptime' before this happens. This is done to * maintain the illusion that the RTC date/time was frozen * while the dividers were disabled. */ vrtc->base_uptime = time(NULL); RTC_DEBUG("RTC divider out of reset at %#lx/%#lx", vrtc->base_rtctime, vrtc->base_uptime); } else { /* NOTHING */ } vrtc->rtcdev.reg_a = newval; changed = oldval ^ newval; if (changed) { RTC_DEBUG("RTC reg_a changed from %#x to %#x", oldval, newval); } /* * Side effect of changes to rate select and divider enable bits. */ newfreq = vrtc_freq(vrtc); if (pintr_enabled(vrtc) && newfreq != oldfreq) { /*start the new periodic timer*/ vrtc_start_timer(&vrtc->periodic_timer, 0, newfreq); } else { /*Nothing to do*/ } } int vrtc_nvram_read(struct vrtc *vrtc, int offset, uint8_t *retval) { time_t basetime; time_t curtime; uint8_t *ptr; /* * Allow all offsets in the RTC to be read. */ if (offset < 0 || offset >= sizeof(struct rtcdev)) return -1; pthread_mutex_lock(&vrtc->mtx); /* * Update RTC date/time fields if necessary. */ if (offset < 10 || offset == RTC_CENTURY) { curtime = vrtc_curtime(vrtc, &basetime); secs_to_rtc(curtime, vrtc, 0); } ptr = (uint8_t *)(&vrtc->rtcdev); *retval = ptr[offset]; pthread_mutex_unlock(&vrtc->mtx); return 0; } int vrtc_nvram_write(struct vrtc *vrtc, int offset, uint8_t value) { uint8_t *ptr; /* * Don't allow writes to RTC control registers or the date/time fields. */ if (offset < offsetof(struct rtcdev, nvram[0]) || offset == RTC_CENTURY || offset >= sizeof(struct rtcdev)) { RTC_DEBUG("RTC nvram write to invalid offset %d", offset); return -1; } pthread_mutex_lock(&vrtc->mtx); ptr = (uint8_t *)(&vrtc->rtcdev); ptr[offset] = value; RTC_DEBUG("RTC nvram write %#x to offset %#x", value, offset); pthread_mutex_unlock(&vrtc->mtx); return 0; } int vrtc_addr_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes, uint32_t *eax, void *arg) { struct vrtc *vrtc = arg; if (bytes != 1) return -1; if (in) { *eax = 0xff; return 0; } pthread_mutex_lock(&vrtc->mtx); vrtc->addr = *eax & 0x7f; pthread_mutex_unlock(&vrtc->mtx); return 0; } int vrtc_data_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes, uint32_t *eax, void *arg) { struct vrtc *vrtc = arg; struct rtcdev *rtc; time_t basetime; time_t curtime; int error, offset; rtc = &vrtc->rtcdev; if (bytes != 1) return -1; pthread_mutex_lock(&vrtc->mtx); offset = vrtc->addr; if (offset >= sizeof(struct rtcdev)) { pthread_mutex_unlock(&vrtc->mtx); return -1; } error = 0; curtime = vrtc_curtime(vrtc, &basetime); vrtc_time_update(vrtc, curtime, basetime); /* * Update RTC date/time fields if necessary. * * This is not just for reads of the RTC. The side-effect of writing * the century byte requires other RTC date/time fields (e.g. sec) * to be updated here. */ if (offset < 10 || offset == RTC_CENTURY) secs_to_rtc(curtime, vrtc, 0); if (in) { if (offset == 12) { /* * XXX * reg_c interrupt flags are updated only if the * corresponding interrupt enable bit in reg_b is set. */ *eax = vrtc->rtcdev.reg_c; vrtc_set_reg_c(vrtc, 0); } else { *eax = *((uint8_t *)rtc + offset); } RTC_DEBUG("Read value %#x from RTC offset %#x\n", *eax, offset); } else { switch (offset) { case 10: RTC_DEBUG("RTC reg_a set to %#x\n", *eax); vrtc_set_reg_a(vrtc, *eax); break; case 11: RTC_DEBUG("RTC reg_b set to %#x\n", *eax); error = vrtc_set_reg_b(vrtc, *eax); break; case 12: RTC_DEBUG("RTC reg_c set to %#x (ignored)\n", *eax); break; case 13: RTC_DEBUG("RTC reg_d set to %#x (ignored)\n", *eax); break; case 0: /* * High order bit of 'seconds' is readonly. */ *eax &= 0x7f; /* FALLTHRU */ default: RTC_DEBUG("RTC offset %#x set to %#x\n", offset, *eax); *((uint8_t *)rtc + offset) = *eax; break; } /* * XXX some guests (e.g. OpenBSD) write the century byte * outside of RTCSB_HALT so re-calculate the RTC date/time. */ if (offset == RTC_CENTURY && !rtc_halted(vrtc)) { curtime = rtc_to_secs(vrtc); error = vrtc_time_update(vrtc, curtime, time(NULL)); assert(!error); if (curtime == VRTC_BROKEN_TIME && rtc_flag_broken_time) error = -1; } } pthread_mutex_unlock(&vrtc->mtx); return error; } int vrtc_set_time(struct vrtc *vrtc, time_t secs) { int error; pthread_mutex_lock(&vrtc->mtx); error = vrtc_time_update(vrtc, secs, time(NULL)); pthread_mutex_unlock(&vrtc->mtx); if (error) RTC_DEBUG("Error %d setting RTC time to %#lx", error, secs); else RTC_DEBUG("RTC time set to %#lx", secs); return error; } time_t vrtc_get_time(struct vrtc *vrtc) { time_t basetime; time_t t; pthread_mutex_lock(&vrtc->mtx); t = vrtc_curtime(vrtc, &basetime); pthread_mutex_unlock(&vrtc->mtx); return t; } void vrtc_reset(struct vrtc *vrtc) { struct rtcdev *rtc; pthread_mutex_lock(&vrtc->mtx); rtc = &vrtc->rtcdev; vrtc_set_reg_b(vrtc, rtc->reg_b & ~(RTCSB_ALL_INTRS | RTCSB_SQWE)); vrtc_set_reg_c(vrtc, 0); pthread_mutex_unlock(&vrtc->mtx); } int vrtc_init(struct vmctx *ctx) { struct vrtc *vrtc; size_t lomem, himem; int err; struct rtcdev *rtc; time_t curtime; struct inout_port rtc_addr, rtc_data; vrtc = calloc(1, sizeof(struct vrtc)); assert(vrtc != NULL); vrtc->vm = ctx; ctx->vrtc = vrtc; pthread_mutex_init(&vrtc->mtx, NULL); /* * Report guest memory size in nvram cells as required by UEFI. * Little-endian encoding. * 0x34/0x35 - 64KB chunks above 16MB, below 4GB * 0x5b/0x5c/0x5d - 64KB chunks above 4GB */ lomem = vm_get_lowmem_size(ctx); assert(lomem >= 16 * MB); lomem = (lomem - 16 * MB) / (64 * KB); err = vrtc_nvram_write(vrtc, RTC_LMEM_LSB, lomem); assert(err == 0); err = vrtc_nvram_write(vrtc, RTC_LMEM_MSB, lomem >> 8); assert(err == 0); himem = vm_get_highmem_size(ctx) / (64 * KB); err = vrtc_nvram_write(vrtc, RTC_HMEM_LSB, himem); assert(err == 0); err = vrtc_nvram_write(vrtc, RTC_HMEM_SB, himem >> 8); assert(err == 0); err = vrtc_nvram_write(vrtc, RTC_HMEM_MSB, himem >> 16); assert(err == 0); memset(&rtc_addr, 0, sizeof(struct inout_port)); memset(&rtc_data, 0, sizeof(struct inout_port)); /*register io port handler for rtc addr*/ rtc_addr.name = "rtc"; rtc_addr.port = IO_RTC; rtc_addr.size = 1; rtc_addr.flags = IOPORT_F_INOUT; rtc_addr.handler = vrtc_addr_handler; rtc_addr.arg = vrtc; assert(register_inout(&rtc_addr) == 0); /*register io port handler for rtc data*/ rtc_data.name = "rtc"; rtc_data.port = IO_RTC + 1; rtc_data.size = 1; rtc_data.flags = IOPORT_F_INOUT; rtc_data.handler = vrtc_data_handler; rtc_data.arg = vrtc; assert(register_inout(&rtc_data) == 0); /* Allow dividers o keep time but disable everything else */ rtc = &vrtc->rtcdev; rtc->reg_a = 0x20; rtc->reg_b = RTCSB_24HR; rtc->reg_c = 0; rtc->reg_d = RTCSD_PWR; /* Reset the index register to a safe value. */ vrtc->addr = RTC_STATUSD; /* * Initialize RTC time to 00:00:00 Jan 1, 1970 if curtime = 0 */ /*curtime = 0;*/ curtime = time(NULL); pthread_mutex_lock(&vrtc->mtx); vrtc->base_rtctime = VRTC_BROKEN_TIME; vrtc_time_update(vrtc, curtime, time(NULL)); secs_to_rtc(curtime, vrtc, 0); pthread_mutex_unlock(&vrtc->mtx); /* init periodic interrupt timer */ vrtc->periodic_timer.clockid = CLOCK_REALTIME; acrn_timer_init(&vrtc->periodic_timer, vrtc_periodic_timer, vrtc); /* init update interrupt timer(1s)*/ vrtc->update_timer.clockid = CLOCK_REALTIME; acrn_timer_init(&vrtc->update_timer, vrtc_update_timer, vrtc); vrtc_start_timer(&vrtc->update_timer, 1, 0); return 0; } void vrtc_deinit(struct vmctx *ctx) { struct vrtc *vrtc = ctx->vrtc; struct inout_port iop; /*deinit acrn_timer*/ acrn_timer_deinit(&vrtc->periodic_timer); acrn_timer_deinit(&vrtc->update_timer); memset(&iop, 0, sizeof(struct inout_port)); iop.name = "rtc"; iop.port = IO_RTC; iop.size = 1; unregister_inout(&iop); memset(&iop, 0, sizeof(struct inout_port)); iop.name = "rtc"; iop.port = IO_RTC + 1; iop.size = 1; unregister_inout(&iop); free(vrtc); ctx->vrtc = NULL; } static void rtc_dsdt(void) { dsdt_line(""); dsdt_line("Device (RTC)"); dsdt_line("{"); dsdt_line(" Name (_HID, EisaId (\"PNP0B00\"))"); dsdt_line(" Name (_CRS, ResourceTemplate ()"); dsdt_line(" {"); dsdt_indent(2); dsdt_fixed_ioport(IO_RTC, 2); dsdt_fixed_irq(8); dsdt_unindent(2); dsdt_line(" })"); dsdt_line("}"); } LPC_DSDT(rtc_dsdt); /* * Reserve the extended RTC I/O ports although they are not emulated at this * time. */ SYSRES_IO(0x72, 6);