aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNeel Natu <neel@FreeBSD.org>2014-12-30 22:19:34 +0000
committerNeel Natu <neel@FreeBSD.org>2014-12-30 22:19:34 +0000
commit0dafa5cd4baeba5cd83594af440a3e250e5ddc22 (patch)
tree0751604dc4d91153ec1742651bba58911187d665
parent6db8a9f3a5ed5f35a00f88a2bdcef46bd66f15ea (diff)
Notes
-rw-r--r--lib/libvmmapi/vmmapi.c52
-rw-r--r--lib/libvmmapi/vmmapi.h6
-rw-r--r--sys/amd64/include/vmm.h1
-rw-r--r--sys/amd64/include/vmm_dev.h23
-rw-r--r--sys/amd64/vmm/io/vhpet.c70
-rw-r--r--sys/amd64/vmm/io/vrtc.c952
-rw-r--r--sys/amd64/vmm/io/vrtc.h50
-rw-r--r--sys/amd64/vmm/vmm.c15
-rw-r--r--sys/amd64/vmm/vmm_dev.c22
-rw-r--r--sys/amd64/vmm/vmm_ioport.c3
-rw-r--r--sys/modules/vmm/Makefile3
-rw-r--r--usr.sbin/bhyve/rtc.c305
-rw-r--r--usr.sbin/bhyvectl/bhyvectl.c84
13 files changed, 1245 insertions, 341 deletions
diff --git a/lib/libvmmapi/vmmapi.c b/lib/libvmmapi/vmmapi.c
index 93955c7c233e..bca117436cc8 100644
--- a/lib/libvmmapi/vmmapi.c
+++ b/lib/libvmmapi/vmmapi.c
@@ -1146,3 +1146,55 @@ vm_set_intinfo(struct vmctx *ctx, int vcpu, uint64_t info1)
error = ioctl(ctx->fd, VM_SET_INTINFO, &vmii);
return (error);
}
+
+int
+vm_rtc_write(struct vmctx *ctx, int offset, uint8_t value)
+{
+ struct vm_rtc_data rtcdata;
+ int error;
+
+ bzero(&rtcdata, sizeof(struct vm_rtc_data));
+ rtcdata.offset = offset;
+ rtcdata.value = value;
+ error = ioctl(ctx->fd, VM_RTC_WRITE, &rtcdata);
+ return (error);
+}
+
+int
+vm_rtc_read(struct vmctx *ctx, int offset, uint8_t *retval)
+{
+ struct vm_rtc_data rtcdata;
+ int error;
+
+ bzero(&rtcdata, sizeof(struct vm_rtc_data));
+ rtcdata.offset = offset;
+ error = ioctl(ctx->fd, VM_RTC_READ, &rtcdata);
+ if (error == 0)
+ *retval = rtcdata.value;
+ return (error);
+}
+
+int
+vm_rtc_settime(struct vmctx *ctx, time_t secs)
+{
+ struct vm_rtc_time rtctime;
+ int error;
+
+ bzero(&rtctime, sizeof(struct vm_rtc_time));
+ rtctime.secs = secs;
+ error = ioctl(ctx->fd, VM_RTC_SETTIME, &rtctime);
+ return (error);
+}
+
+int
+vm_rtc_gettime(struct vmctx *ctx, time_t *secs)
+{
+ struct vm_rtc_time rtctime;
+ int error;
+
+ bzero(&rtctime, sizeof(struct vm_rtc_time));
+ error = ioctl(ctx->fd, VM_RTC_GETTIME, &rtctime);
+ if (error == 0)
+ *secs = rtctime.secs;
+ return (error);
+}
diff --git a/lib/libvmmapi/vmmapi.h b/lib/libvmmapi/vmmapi.h
index fbb6ddd3acfb..b13443125a05 100644
--- a/lib/libvmmapi/vmmapi.h
+++ b/lib/libvmmapi/vmmapi.h
@@ -133,6 +133,12 @@ void vm_copyin(struct vmctx *ctx, int vcpu, struct iovec *guest_iov,
void vm_copyout(struct vmctx *ctx, int vcpu, const void *host_src,
struct iovec *guest_iov, size_t len);
+/* RTC */
+int vm_rtc_write(struct vmctx *ctx, int offset, uint8_t value);
+int vm_rtc_read(struct vmctx *ctx, int offset, uint8_t *retval);
+int vm_rtc_settime(struct vmctx *ctx, time_t secs);
+int vm_rtc_gettime(struct vmctx *ctx, time_t *secs);
+
/* Reset vcpu register state */
int vcpu_reset(struct vmctx *ctx, int vcpu);
diff --git a/sys/amd64/include/vmm.h b/sys/amd64/include/vmm.h
index 8a8c3f471831..eef5efa11feb 100644
--- a/sys/amd64/include/vmm.h
+++ b/sys/amd64/include/vmm.h
@@ -286,6 +286,7 @@ int vm_unassign_pptdev(struct vm *vm, int bus, int slot, int func);
struct vatpic *vm_atpic(struct vm *vm);
struct vatpit *vm_atpit(struct vm *vm);
struct vpmtmr *vm_pmtmr(struct vm *vm);
+struct vrtc *vm_rtc(struct vm *vm);
/*
* Inject exception 'vme' into the guest vcpu. This function returns 0 on
diff --git a/sys/amd64/include/vmm_dev.h b/sys/amd64/include/vmm_dev.h
index e4d839ef6549..3097386db581 100644
--- a/sys/amd64/include/vmm_dev.h
+++ b/sys/amd64/include/vmm_dev.h
@@ -195,6 +195,15 @@ struct vm_intinfo {
uint64_t info2;
};
+struct vm_rtc_time {
+ time_t secs;
+};
+
+struct vm_rtc_data {
+ int offset;
+ uint8_t value;
+};
+
enum {
/* general routines */
IOCNUM_ABIVERS = 0,
@@ -254,6 +263,12 @@ enum {
/* vm_cpuset */
IOCNUM_ACTIVATE_CPU = 90,
IOCNUM_GET_CPUSET = 91,
+
+ /* RTC */
+ IOCNUM_RTC_READ = 100,
+ IOCNUM_RTC_WRITE = 101,
+ IOCNUM_RTC_SETTIME = 102,
+ IOCNUM_RTC_GETTIME = 103,
};
#define VM_RUN \
@@ -336,4 +351,12 @@ enum {
_IOW('v', IOCNUM_SET_INTINFO, struct vm_intinfo)
#define VM_GET_INTINFO \
_IOWR('v', IOCNUM_GET_INTINFO, struct vm_intinfo)
+#define VM_RTC_WRITE \
+ _IOW('v', IOCNUM_RTC_WRITE, struct vm_rtc_data)
+#define VM_RTC_READ \
+ _IOWR('v', IOCNUM_RTC_READ, struct vm_rtc_data)
+#define VM_RTC_SETTIME \
+ _IOW('v', IOCNUM_RTC_SETTIME, struct vm_rtc_time)
+#define VM_RTC_GETTIME \
+ _IOR('v', IOCNUM_RTC_GETTIME, struct vm_rtc_time)
#endif
diff --git a/sys/amd64/vmm/io/vhpet.c b/sys/amd64/vmm/io/vhpet.c
index 46e5ca723927..a4c96cd19b31 100644
--- a/sys/amd64/vmm/io/vhpet.c
+++ b/sys/amd64/vmm/io/vhpet.c
@@ -104,7 +104,6 @@ vhpet_capabilities(void)
uint64_t cap = 0;
cap |= 0x8086 << 16; /* vendor id */
- cap |= HPET_CAP_LEG_RT; /* legacy routing capable */
cap |= (VHPET_NUM_TIMERS - 1) << 8; /* number of timers */
cap |= 1; /* revision */
cap &= ~HPET_CAP_COUNT_SIZE; /* 32-bit timer */
@@ -127,15 +126,6 @@ vhpet_timer_msi_enabled(struct vhpet *vhpet, int n)
{
const uint64_t msi_enable = HPET_TCAP_FSB_INT_DEL | HPET_TCNF_FSB_EN;
- /*
- * LegacyReplacement Route configuration takes precedence over MSI
- * for timers 0 and 1.
- */
- if (n == 0 || n == 1) {
- if (vhpet->config & HPET_CNF_LEG_RT)
- return (false);
- }
-
if ((vhpet->timer[n].cap_config & msi_enable) == msi_enable)
return (true);
else
@@ -152,41 +142,9 @@ vhpet_timer_ioapic_pin(struct vhpet *vhpet, int n)
if (vhpet_timer_msi_enabled(vhpet, n))
return (0);
- if (vhpet->config & HPET_CNF_LEG_RT) {
- /*
- * In "legacy routing" timers 0 and 1 are connected to
- * ioapic pins 2 and 8 respectively.
- */
- switch (n) {
- case 0:
- return (2);
- case 1:
- return (8);
- }
- }
-
return ((vhpet->timer[n].cap_config & HPET_TCNF_INT_ROUTE) >> 9);
}
-static __inline int
-vhpet_timer_atpic_pin(struct vhpet *vhpet, int n)
-{
- if (vhpet->config & HPET_CNF_LEG_RT) {
- /*
- * In "legacy routing" timers 0 and 1 are connected to
- * 8259 master pin 0 and slave pin 0 respectively.
- */
- switch (n) {
- case 0:
- return (0);
- case 1:
- return (8);
- }
- }
-
- return (-1);
-}
-
static uint32_t
vhpet_counter(struct vhpet *vhpet, sbintime_t *nowptr)
{
@@ -216,17 +174,12 @@ vhpet_counter(struct vhpet *vhpet, sbintime_t *nowptr)
static void
vhpet_timer_clear_isr(struct vhpet *vhpet, int n)
{
- int pin, legacy_pin;
+ int pin;
if (vhpet->isr & (1 << n)) {
pin = vhpet_timer_ioapic_pin(vhpet, n);
KASSERT(pin != 0, ("vhpet timer %d irq incorrectly routed", n));
vioapic_deassert_irq(vhpet->vm, pin);
-
- legacy_pin = vhpet_timer_atpic_pin(vhpet, n);
- if (legacy_pin != -1)
- vatpic_deassert_irq(vhpet->vm, legacy_pin);
-
vhpet->isr &= ~(1 << n);
}
}
@@ -252,12 +205,6 @@ vhpet_timer_edge_trig(struct vhpet *vhpet, int n)
KASSERT(!vhpet_timer_msi_enabled(vhpet, n), ("vhpet_timer_edge_trig: "
"timer %d is using MSI", n));
- /* The legacy replacement interrupts are always edge triggered */
- if (vhpet->config & HPET_CNF_LEG_RT) {
- if (n == 0 || n == 1)
- return (true);
- }
-
if ((vhpet->timer[n].cap_config & HPET_TCNF_INT_TYPE) == 0)
return (true);
else
@@ -267,7 +214,7 @@ vhpet_timer_edge_trig(struct vhpet *vhpet, int n)
static void
vhpet_timer_interrupt(struct vhpet *vhpet, int n)
{
- int pin, legacy_pin;
+ int pin;
/* If interrupts are not enabled for this timer then just return. */
if (!vhpet_timer_interrupt_enabled(vhpet, n))
@@ -293,17 +240,11 @@ vhpet_timer_interrupt(struct vhpet *vhpet, int n)
return;
}
- legacy_pin = vhpet_timer_atpic_pin(vhpet, n);
-
if (vhpet_timer_edge_trig(vhpet, n)) {
vioapic_pulse_irq(vhpet->vm, pin);
- if (legacy_pin != -1)
- vatpic_pulse_irq(vhpet->vm, legacy_pin);
} else {
vhpet->isr |= 1 << n;
vioapic_assert_irq(vhpet->vm, pin);
- if (legacy_pin != -1)
- vatpic_assert_irq(vhpet->vm, legacy_pin);
}
}
@@ -579,6 +520,13 @@ vhpet_mmio_write(void *vm, int vcpuid, uint64_t gpa, uint64_t val, int size,
counter = vhpet_counter(vhpet, nowptr);
oldval = vhpet->config;
update_register(&vhpet->config, data, mask);
+
+ /*
+ * LegacyReplacement Routing is not supported so clear the
+ * bit explicitly.
+ */
+ vhpet->config &= ~HPET_CNF_LEG_RT;
+
if ((oldval ^ vhpet->config) & HPET_CNF_ENABLE) {
if (vhpet_counter_enabled(vhpet)) {
vhpet_start_counting(vhpet);
diff --git a/sys/amd64/vmm/io/vrtc.c b/sys/amd64/vmm/io/vrtc.c
new file mode 100644
index 000000000000..d5e93dc07381
--- /dev/null
+++ b/sys/amd64/vmm/io/vrtc.c
@@ -0,0 +1,952 @@
+/*-
+ * 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/queue.h>
+#include <sys/cpuset.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/clock.h>
+#include <sys/sysctl.h>
+
+#include <machine/vmm.h>
+
+#include <isa/rtc.h>
+
+#include "vmm_ktr.h"
+#include "vatpic.h"
+#include "vioapic.h"
+#include "vrtc.h"
+
+/* 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[128 - 14];
+} __packed;
+CTASSERT(sizeof(struct rtcdev) == 128);
+
+struct vrtc {
+ struct vm *vm;
+ struct mtx mtx;
+ struct callout callout;
+ u_int addr; /* RTC register to read or write */
+ sbintime_t base_uptime;
+ time_t base_rtctime;
+ struct rtcdev rtcdev;
+};
+
+#define VRTC_LOCK(vrtc) mtx_lock(&((vrtc)->mtx))
+#define VRTC_UNLOCK(vrtc) mtx_unlock(&((vrtc)->mtx))
+#define VRTC_LOCKED(vrtc) mtx_owned(&((vrtc)->mtx))
+
+/*
+ * 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)
+
+#define RTC_IRQ 8
+#define RTCSB_BIN 0x04
+#define RTCSB_ALL_INTRS (RTCSB_UINTR | RTCSB_AINTR | RTCSB_PINTR)
+#define rtc_halted(vrtc) ((vrtc->rtcdev.reg_b & RTCSB_HALT) != 0)
+#define aintr_enabled(vrtc) (((vrtc)->rtcdev.reg_b & RTCSB_AINTR) != 0)
+#define pintr_enabled(vrtc) (((vrtc)->rtcdev.reg_b & RTCSB_PINTR) != 0)
+#define uintr_enabled(vrtc) (((vrtc)->rtcdev.reg_b & RTCSB_UINTR) != 0)
+
+static void vrtc_callout_handler(void *arg);
+static void vrtc_set_reg_c(struct vrtc *vrtc, uint8_t newval);
+
+static MALLOC_DEFINE(M_VRTC, "vrtc", "bhyve virtual rtc");
+
+SYSCTL_DECL(_hw_vmm);
+SYSCTL_NODE(_hw_vmm, OID_AUTO, vrtc, CTLFLAG_RW, NULL, NULL);
+
+static int rtc_flag_broken_time = 1;
+SYSCTL_INT(_hw_vmm_vrtc, OID_AUTO, flag_broken_time, CTLFLAG_RDTUN,
+ &rtc_flag_broken_time, 0, "Stop guest when invalid RTC time is detected");
+
+static __inline bool
+divider_enabled(int reg_a)
+{
+ /*
+ * The RTC is counting only when dividers are not held in reset.
+ */
+ return ((reg_a & 0x70) == 0x20);
+}
+
+static __inline bool
+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)
+{
+ sbintime_t now, delta;
+ time_t t;
+
+ KASSERT(VRTC_LOCKED(vrtc), ("%s: vrtc not locked", __func__));
+
+ t = vrtc->base_rtctime;
+ if (update_enabled(vrtc)) {
+ now = sbinuptime();
+ delta = now - vrtc->base_uptime;
+ KASSERT(delta >= 0, ("vrtc_curtime: uptime went backwards: "
+ "%#lx to %#lx", vrtc->base_uptime, now));
+ t += delta / SBT_1S;
+ }
+ return (t);
+}
+
+static __inline uint8_t
+rtcset(struct rtcdev *rtc, int val)
+{
+
+ KASSERT(val >= 0 && val < 100, ("%s: invalid bin2bcd index %d",
+ __func__, val));
+
+ return ((rtc->reg_b & RTCSB_BIN) ? val : bin2bcd_data[val]);
+}
+
+static void
+secs_to_rtc(time_t rtctime, struct vrtc *vrtc, int force_update)
+{
+ struct clocktime ct;
+ struct timespec ts;
+ struct rtcdev *rtc;
+ int hour;
+
+ KASSERT(VRTC_LOCKED(vrtc), ("%s: vrtc not locked", __func__));
+
+ if (rtctime < 0) {
+ KASSERT(rtctime == VRTC_BROKEN_TIME,
+ ("%s: invalid vrtc time %#lx", __func__, rtctime));
+ 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;
+ clock_ts_to_ct(&ts, &ct);
+
+ KASSERT(ct.sec >= 0 && ct.sec <= 59, ("invalid clocktime sec %d",
+ ct.sec));
+ KASSERT(ct.min >= 0 && ct.min <= 59, ("invalid clocktime min %d",
+ ct.min));
+ KASSERT(ct.hour >= 0 && ct.hour <= 23, ("invalid clocktime hour %d",
+ ct.hour));
+ KASSERT(ct.dow >= 0 && ct.dow <= 6, ("invalid clocktime wday %d",
+ ct.dow));
+ KASSERT(ct.day >= 1 && ct.day <= 31, ("invalid clocktime mday %d",
+ ct.day));
+ KASSERT(ct.mon >= 1 && ct.mon <= 12, ("invalid clocktime month %d",
+ ct.mon));
+ KASSERT(ct.year >= POSIX_BASE_YEAR, ("invalid clocktime year %d",
+ ct.year));
+
+ rtc = &vrtc->rtcdev;
+ rtc->sec = rtcset(rtc, ct.sec);
+ rtc->min = rtcset(rtc, ct.min);
+
+ hour = ct.hour;
+ if ((rtc->reg_b & RTCSB_24HR) == 0)
+ hour = (hour % 12) + 1; /* convert to a 12-hour format */
+
+ 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);
+}
+
+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 time_t
+rtc_to_secs(struct vrtc *vrtc)
+{
+ struct clocktime ct;
+ struct timespec ts;
+ struct rtcdev *rtc;
+ struct vm *vm;
+ int error, hour, pm, year;
+
+ KASSERT(VRTC_LOCKED(vrtc), ("%s: vrtc not locked", __func__));
+
+ vm = vrtc->vm;
+ rtc = &vrtc->rtcdev;
+
+ bzero(&ct, sizeof(struct clocktime));
+
+ error = rtcget(rtc, rtc->sec, &ct.sec);
+ if (error || ct.sec < 0 || ct.sec > 59) {
+ VM_CTR2(vm, "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) {
+ VM_CTR2(vm, "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) {
+ ct.hour -= 1;
+ if (pm)
+ ct.hour += 12;
+ }
+
+ if (error || ct.hour < 0 || ct.hour > 23) {
+ VM_CTR2(vm, "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) {
+ VM_CTR2(vm, "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) {
+ VM_CTR2(vm, "Invalid RTC month %#x/%d", rtc->month, ct.mon);
+ goto fail;
+ }
+
+ error = rtcget(rtc, rtc->year, &year);
+ if (error || year < 0 || year > 99) {
+ VM_CTR2(vm, "Invalid RTC year %#x/%d", rtc->year, year);
+ goto fail;
+ }
+ if (year >= 70)
+ ct.year = 1900 + year;
+ else
+ ct.year = 2000 + year;
+
+ error = clock_ct_to_ts(&ct, &ts);
+ if (error || ts.tv_sec < 0) {
+ VM_CTR3(vm, "Invalid RTC clocktime.date %04d-%02d-%02d",
+ ct.year, ct.mon, ct.day);
+ VM_CTR3(vm, "Invalid RTC clocktime.time %02d:%02d:%02d",
+ ct.hour, ct.min, ct.sec);
+ goto fail;
+ }
+ return (ts.tv_sec); /* success */
+fail:
+ return (VRTC_BROKEN_TIME); /* failure */
+}
+
+static int
+vrtc_time_update(struct vrtc *vrtc, time_t newtime)
+{
+ struct rtcdev *rtc;
+ time_t oldtime;
+ uint8_t alarm_sec, alarm_min, alarm_hour;
+
+ KASSERT(VRTC_LOCKED(vrtc), ("%s: vrtc not locked", __func__));
+
+ rtc = &vrtc->rtcdev;
+ alarm_sec = rtc->alarm_sec;
+ alarm_min = rtc->alarm_min;
+ alarm_hour = rtc->alarm_hour;
+
+ oldtime = vrtc->base_rtctime;
+ VM_CTR2(vrtc->vm, "Updating RTC time from %#lx to %#lx",
+ oldtime, newtime);
+
+ 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 or update 'base_uptime' 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)) {
+ VM_CTR0(vrtc->vm, "RTC update halted by guest");
+ return (EBUSY);
+ }
+
+ 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);
+
+ vrtc->base_uptime = sbinuptime();
+
+ return (0);
+}
+
+static sbintime_t
+vrtc_freq(struct vrtc *vrtc)
+{
+ int ratesel;
+
+ static sbintime_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,
+ };
+
+ KASSERT(VRTC_LOCKED(vrtc), ("%s: vrtc not locked", __func__));
+
+ /*
+ * 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 void
+vrtc_callout_reset(struct vrtc *vrtc, sbintime_t freqsbt)
+{
+
+ KASSERT(VRTC_LOCKED(vrtc), ("%s: vrtc not locked", __func__));
+
+ if (freqsbt == 0) {
+ if (callout_active(&vrtc->callout)) {
+ VM_CTR0(vrtc->vm, "RTC callout stopped");
+ callout_stop(&vrtc->callout);
+ }
+ return;
+ }
+ VM_CTR1(vrtc->vm, "RTC callout frequency %d hz", SBT_1S / freqsbt);
+ callout_reset_sbt(&vrtc->callout, freqsbt, 0, vrtc_callout_handler,
+ vrtc, 0);
+}
+
+static void
+vrtc_callout_handler(void *arg)
+{
+ struct vrtc *vrtc = arg;
+ sbintime_t freqsbt;
+ time_t rtctime;
+ int error;
+
+ VM_CTR0(vrtc->vm, "vrtc callout fired");
+
+ VRTC_LOCK(vrtc);
+ if (callout_pending(&vrtc->callout)) /* callout was reset */
+ goto done;
+
+ if (!callout_active(&vrtc->callout)) /* callout was stopped */
+ goto done;
+
+ callout_deactivate(&vrtc->callout);
+
+ KASSERT((vrtc->rtcdev.reg_b & RTCSB_ALL_INTRS) != 0,
+ ("gratuitous vrtc callout"));
+
+ if (pintr_enabled(vrtc))
+ vrtc_set_reg_c(vrtc, vrtc->rtcdev.reg_c | RTCIR_PERIOD);
+
+ if (aintr_enabled(vrtc) || uintr_enabled(vrtc)) {
+ rtctime = vrtc_curtime(vrtc);
+ error = vrtc_time_update(vrtc, rtctime);
+ KASSERT(error == 0, ("%s: vrtc_time_update error %d",
+ __func__, error));
+ }
+
+ freqsbt = vrtc_freq(vrtc);
+ KASSERT(freqsbt != 0, ("%s: vrtc frequency cannot be zero", __func__));
+ vrtc_callout_reset(vrtc, freqsbt);
+done:
+ VRTC_UNLOCK(vrtc);
+}
+
+static __inline void
+vrtc_callout_check(struct vrtc *vrtc, sbintime_t freq)
+{
+ int active;
+
+ active = callout_active(&vrtc->callout) ? 1 : 0;
+ KASSERT((freq == 0 && !active) || (freq != 0 && active),
+ ("vrtc callout %s with frequency %#lx",
+ active ? "active" : "inactive", freq));
+}
+
+static void
+vrtc_set_reg_c(struct vrtc *vrtc, uint8_t newval)
+{
+ struct rtcdev *rtc;
+ int oldirqf, newirqf;
+ uint8_t oldval, changed;
+
+ KASSERT(VRTC_LOCKED(vrtc), ("%s: vrtc not locked", __func__));
+
+ 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) {
+ VM_CTR2(vrtc->vm, "RTC reg_c changed from %#x to %#x",
+ oldval, rtc->reg_c);
+ }
+
+ if (!oldirqf && newirqf) {
+ VM_CTR1(vrtc->vm, "RTC irq %d asserted", RTC_IRQ);
+ vatpic_pulse_irq(vrtc->vm, RTC_IRQ);
+ vioapic_pulse_irq(vrtc->vm, RTC_IRQ);
+ } else if (oldirqf && !newirqf) {
+ VM_CTR1(vrtc->vm, "RTC irq %d deasserted", RTC_IRQ);
+ }
+}
+
+static int
+vrtc_set_reg_b(struct vrtc *vrtc, uint8_t newval)
+{
+ struct rtcdev *rtc;
+ sbintime_t oldfreq, newfreq;
+ time_t curtime, rtctime;
+ int error;
+ uint8_t oldval, changed;
+
+ KASSERT(VRTC_LOCKED(vrtc), ("%s: vrtc not locked", __func__));
+
+ rtc = &vrtc->rtcdev;
+ oldval = rtc->reg_b;
+ oldfreq = vrtc_freq(vrtc);
+
+ rtc->reg_b = newval;
+ changed = oldval ^ newval;
+ if (changed) {
+ VM_CTR2(vrtc->vm, "RTC reg_b changed from %#x to %#x",
+ oldval, newval);
+ }
+
+ if (changed & RTCSB_HALT) {
+ if ((newval & RTCSB_HALT) == 0) {
+ rtctime = rtc_to_secs(vrtc);
+ if (rtctime == VRTC_BROKEN_TIME) {
+ /*
+ * Stop updating the RTC if the date/time
+ * programmed by the guest is not correct.
+ */
+ VM_CTR0(vrtc->vm, "Invalid RTC date/time "
+ "programming detected");
+
+ if (rtc_flag_broken_time)
+ return (-1);
+ }
+ } else {
+ curtime = vrtc_curtime(vrtc);
+ KASSERT(curtime == vrtc->base_rtctime, ("%s: mismatch "
+ "between vrtc basetime (%#lx) and curtime (%#lx)",
+ __func__, vrtc->base_rtctime, curtime));
+
+ /*
+ * 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);
+ KASSERT(error == 0, ("vrtc_time_update error %d", error));
+ }
+
+ /*
+ * 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 (newfreq != oldfreq)
+ vrtc_callout_reset(vrtc, newfreq);
+ else
+ vrtc_callout_check(vrtc, newfreq);
+
+ /*
+ * 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)
+{
+ sbintime_t oldfreq, newfreq;
+ uint8_t oldval, changed;
+
+ KASSERT(VRTC_LOCKED(vrtc), ("%s: vrtc not locked", __func__));
+
+ newval &= ~RTCSA_TUP;
+ oldval = vrtc->rtcdev.reg_a;
+ oldfreq = vrtc_freq(vrtc);
+
+ if (divider_enabled(oldval) && !divider_enabled(newval)) {
+ VM_CTR2(vrtc->vm, "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 = sbinuptime();
+ VM_CTR2(vrtc->vm, "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) {
+ VM_CTR2(vrtc->vm, "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 (newfreq != oldfreq)
+ vrtc_callout_reset(vrtc, newfreq);
+ else
+ vrtc_callout_check(vrtc, newfreq);
+}
+
+int
+vrtc_set_time(struct vm *vm, time_t secs)
+{
+ struct vrtc *vrtc;
+ int error;
+
+ vrtc = vm_rtc(vm);
+ VRTC_LOCK(vrtc);
+ error = vrtc_time_update(vrtc, secs);
+ VRTC_UNLOCK(vrtc);
+
+ if (error) {
+ VM_CTR2(vrtc->vm, "Error %d setting RTC time to %#lx", error,
+ secs);
+ } else {
+ VM_CTR1(vrtc->vm, "RTC time set to %#lx", secs);
+ }
+
+ return (error);
+}
+
+time_t
+vrtc_get_time(struct vm *vm)
+{
+ struct vrtc *vrtc;
+ time_t t;
+
+ vrtc = vm_rtc(vm);
+ VRTC_LOCK(vrtc);
+ t = vrtc_curtime(vrtc);
+ VRTC_UNLOCK(vrtc);
+
+ return (t);
+}
+
+int
+vrtc_nvram_write(struct vm *vm, int offset, uint8_t value)
+{
+ struct vrtc *vrtc;
+ uint8_t *ptr;
+
+ vrtc = vm_rtc(vm);
+
+ /*
+ * Don't allow writes to RTC control registers or the date/time fields.
+ */
+ if (offset < offsetof(struct rtcdev, nvram[0]) ||
+ offset >= sizeof(struct rtcdev)) {
+ VM_CTR1(vrtc->vm, "RTC nvram write to invalid offset %d",
+ offset);
+ return (EINVAL);
+ }
+
+ VRTC_LOCK(vrtc);
+ ptr = (uint8_t *)(&vrtc->rtcdev);
+ ptr[offset] = value;
+ VM_CTR2(vrtc->vm, "RTC nvram write %#x to offset %#x", value, offset);
+ VRTC_UNLOCK(vrtc);
+
+ return (0);
+}
+
+int
+vrtc_nvram_read(struct vm *vm, int offset, uint8_t *retval)
+{
+ struct vrtc *vrtc;
+ time_t curtime;
+ uint8_t *ptr;
+
+ /*
+ * Allow all offsets in the RTC to be read.
+ */
+ if (offset < 0 || offset >= sizeof(struct rtcdev))
+ return (EINVAL);
+
+ vrtc = vm_rtc(vm);
+ VRTC_LOCK(vrtc);
+
+ /*
+ * Update RTC date/time fields if necessary.
+ */
+ if (offset < 10) {
+ curtime = vrtc_curtime(vrtc);
+ secs_to_rtc(curtime, vrtc, 0);
+ }
+
+ ptr = (uint8_t *)(&vrtc->rtcdev);
+ *retval = ptr[offset];
+
+ VRTC_UNLOCK(vrtc);
+ return (0);
+}
+
+int
+vrtc_addr_handler(struct vm *vm, int vcpuid, bool in, int port, int bytes,
+ uint32_t *val)
+{
+ struct vrtc *vrtc;
+
+ vrtc = vm_rtc(vm);
+
+ if (bytes != 1)
+ return (-1);
+
+ if (in) {
+ *val = 0xff;
+ return (0);
+ }
+
+ VRTC_LOCK(vrtc);
+ vrtc->addr = *val & 0x7f;
+ VRTC_UNLOCK(vrtc);
+
+ return (0);
+}
+
+int
+vrtc_data_handler(struct vm *vm, int vcpuid, bool in, int port, int bytes,
+ uint32_t *val)
+{
+ struct vrtc *vrtc;
+ struct rtcdev *rtc;
+ time_t curtime;
+ int error, offset;
+
+ vrtc = vm_rtc(vm);
+ rtc = &vrtc->rtcdev;
+
+ if (bytes != 1)
+ return (-1);
+
+ VRTC_LOCK(vrtc);
+ offset = vrtc->addr;
+ if (offset >= sizeof(struct rtcdev)) {
+ VRTC_UNLOCK(vrtc);
+ return (-1);
+ }
+
+ error = 0;
+ curtime = vrtc_curtime(vrtc);
+ vrtc_time_update(vrtc, curtime);
+
+ if (in) {
+ /*
+ * Update RTC date/time fields if necessary.
+ */
+ if (offset < 10)
+ secs_to_rtc(curtime, vrtc, 0);
+
+ if (offset == 12) {
+ /*
+ * XXX
+ * reg_c interrupt flags are updated only if the
+ * corresponding interrupt enable bit in reg_b is set.
+ */
+ *val = vrtc->rtcdev.reg_c;
+ vrtc_set_reg_c(vrtc, 0);
+ } else {
+ *val = *((uint8_t *)rtc + offset);
+ }
+ VCPU_CTR2(vm, vcpuid, "Read value %#x from RTC offset %#x",
+ *val, offset);
+ } else {
+ switch (offset) {
+ case 10:
+ VCPU_CTR1(vm, vcpuid, "RTC reg_a set to %#x", *val);
+ vrtc_set_reg_a(vrtc, *val);
+ break;
+ case 11:
+ VCPU_CTR1(vm, vcpuid, "RTC reg_b set to %#x", *val);
+ error = vrtc_set_reg_b(vrtc, *val);
+ break;
+ case 12:
+ VCPU_CTR1(vm, vcpuid, "RTC reg_c set to %#x (ignored)",
+ *val);
+ break;
+ case 13:
+ VCPU_CTR1(vm, vcpuid, "RTC reg_d set to %#x (ignored)",
+ *val);
+ break;
+ case 0:
+ /*
+ * High order bit of 'seconds' is readonly.
+ */
+ *val &= 0x7f;
+ /* FALLTHRU */
+ default:
+ VCPU_CTR2(vm, vcpuid, "RTC offset %#x set to %#x",
+ offset, *val);
+ *((uint8_t *)rtc + offset) = *val;
+ break;
+ }
+ }
+ VRTC_UNLOCK(vrtc);
+ return (error);
+}
+
+void
+vrtc_reset(struct vrtc *vrtc)
+{
+ struct rtcdev *rtc;
+
+ VRTC_LOCK(vrtc);
+
+ rtc = &vrtc->rtcdev;
+ vrtc_set_reg_b(vrtc, rtc->reg_b & ~(RTCSB_ALL_INTRS | RTCSB_SQWE));
+ vrtc_set_reg_c(vrtc, 0);
+ KASSERT(!callout_active(&vrtc->callout), ("rtc callout still active"));
+
+ VRTC_UNLOCK(vrtc);
+}
+
+struct vrtc *
+vrtc_init(struct vm *vm)
+{
+ struct vrtc *vrtc;
+ struct rtcdev *rtc;
+ time_t curtime;
+
+ vrtc = malloc(sizeof(struct vrtc), M_VRTC, M_WAITOK | M_ZERO);
+ vrtc->vm = vm;
+ mtx_init(&vrtc->mtx, "vrtc lock", NULL, MTX_DEF);
+ callout_init(&vrtc->callout, 1);
+
+ /* Allow dividers to 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.
+ */
+ curtime = 0;
+
+ VRTC_LOCK(vrtc);
+ vrtc->base_rtctime = VRTC_BROKEN_TIME;
+ vrtc_time_update(vrtc, curtime);
+ secs_to_rtc(curtime, vrtc, 0);
+ VRTC_UNLOCK(vrtc);
+
+ return (vrtc);
+}
+
+void
+vrtc_cleanup(struct vrtc *vrtc)
+{
+
+ callout_drain(&vrtc->callout);
+ free(vrtc, M_VRTC);
+}
diff --git a/sys/amd64/vmm/io/vrtc.h b/sys/amd64/vmm/io/vrtc.h
new file mode 100644
index 000000000000..6fbbc9c810ac
--- /dev/null
+++ b/sys/amd64/vmm/io/vrtc.h
@@ -0,0 +1,50 @@
+/*-
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _VRTC_H_
+#define _VRTC_H_
+
+#include <isa/isareg.h>
+
+struct vrtc;
+
+struct vrtc *vrtc_init(struct vm *vm);
+void vrtc_cleanup(struct vrtc *vrtc);
+void vrtc_reset(struct vrtc *vrtc);
+
+time_t vrtc_get_time(struct vm *vm);
+int vrtc_set_time(struct vm *vm, time_t secs);
+int vrtc_nvram_write(struct vm *vm, int offset, uint8_t value);
+int vrtc_nvram_read(struct vm *vm, int offset, uint8_t *retval);
+
+int vrtc_addr_handler(struct vm *vm, int vcpuid, bool in, int port, int bytes,
+ uint32_t *val);
+int vrtc_data_handler(struct vm *vm, int vcpuid, bool in, int port, int bytes,
+ uint32_t *val);
+
+#endif
diff --git a/sys/amd64/vmm/vmm.c b/sys/amd64/vmm/vmm.c
index d9cb6f3c1254..6c55271f4269 100644
--- a/sys/amd64/vmm/vmm.c
+++ b/sys/amd64/vmm/vmm.c
@@ -75,6 +75,7 @@ __FBSDID("$FreeBSD$");
#include "vioapic.h"
#include "vlapic.h"
#include "vpmtmr.h"
+#include "vrtc.h"
#include "vmm_ipi.h"
#include "vmm_stat.h"
#include "vmm_lapic.h"
@@ -136,6 +137,7 @@ struct vm {
struct vatpic *vatpic; /* (i) virtual atpic */
struct vatpit *vatpit; /* (i) virtual atpit */
struct vpmtmr *vpmtmr; /* (i) virtual ACPI PM timer */
+ struct vrtc *vrtc; /* (o) virtual RTC */
volatile cpuset_t active_cpus; /* (i) active vcpus */
int suspend; /* (i) stop VM execution */
volatile cpuset_t suspended_cpus; /* (i) suspended vcpus */
@@ -375,6 +377,8 @@ vm_init(struct vm *vm, bool create)
vm->vatpic = vatpic_init(vm);
vm->vatpit = vatpit_init(vm);
vm->vpmtmr = vpmtmr_init(vm);
+ if (create)
+ vm->vrtc = vrtc_init(vm);
CPU_ZERO(&vm->active_cpus);
@@ -437,6 +441,10 @@ vm_cleanup(struct vm *vm, bool destroy)
if (vm->iommu != NULL)
iommu_destroy_domain(vm->iommu);
+ if (destroy)
+ vrtc_cleanup(vm->vrtc);
+ else
+ vrtc_reset(vm->vrtc);
vpmtmr_cleanup(vm->vpmtmr);
vatpit_cleanup(vm->vatpit);
vhpet_cleanup(vm->vhpet);
@@ -2222,6 +2230,13 @@ vm_pmtmr(struct vm *vm)
return (vm->vpmtmr);
}
+struct vrtc *
+vm_rtc(struct vm *vm)
+{
+
+ return (vm->vrtc);
+}
+
enum vm_reg_name
vm_segment_name(int seg)
{
diff --git a/sys/amd64/vmm/vmm_dev.c b/sys/amd64/vmm/vmm_dev.c
index a85109edaa1d..a6491d65ea8b 100644
--- a/sys/amd64/vmm/vmm_dev.c
+++ b/sys/amd64/vmm/vmm_dev.c
@@ -58,6 +58,7 @@ __FBSDID("$FreeBSD$");
#include "io/vatpic.h"
#include "io/vioapic.h"
#include "io/vhpet.h"
+#include "io/vrtc.h"
struct vmmdev_softc {
struct vm *vm; /* vm instance cookie */
@@ -174,6 +175,8 @@ vmmdev_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag,
struct vm_activate_cpu *vac;
struct vm_cpuset *vm_cpuset;
struct vm_intinfo *vmii;
+ struct vm_rtc_time *rtctime;
+ struct vm_rtc_data *rtcdata;
sc = vmmdev_lookup2(cdev);
if (sc == NULL)
@@ -482,6 +485,25 @@ vmmdev_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag,
error = vm_get_intinfo(sc->vm, vmii->vcpuid, &vmii->info1,
&vmii->info2);
break;
+ case VM_RTC_WRITE:
+ rtcdata = (struct vm_rtc_data *)data;
+ error = vrtc_nvram_write(sc->vm, rtcdata->offset,
+ rtcdata->value);
+ break;
+ case VM_RTC_READ:
+ rtcdata = (struct vm_rtc_data *)data;
+ error = vrtc_nvram_read(sc->vm, rtcdata->offset,
+ &rtcdata->value);
+ break;
+ case VM_RTC_SETTIME:
+ rtctime = (struct vm_rtc_time *)data;
+ error = vrtc_set_time(sc->vm, rtctime->secs);
+ break;
+ case VM_RTC_GETTIME:
+ error = 0;
+ rtctime = (struct vm_rtc_time *)data;
+ rtctime->secs = vrtc_get_time(sc->vm);
+ break;
default:
error = ENOTTY;
break;
diff --git a/sys/amd64/vmm/vmm_ioport.c b/sys/amd64/vmm/vmm_ioport.c
index e5535997c9f3..3466b8170417 100644
--- a/sys/amd64/vmm/vmm_ioport.c
+++ b/sys/amd64/vmm/vmm_ioport.c
@@ -42,6 +42,7 @@ __FBSDID("$FreeBSD$");
#include "vatpic.h"
#include "vatpit.h"
#include "vpmtmr.h"
+#include "vrtc.h"
#include "vmm_ioport.h"
#include "vmm_ktr.h"
@@ -60,6 +61,8 @@ ioport_handler_func_t ioport_handler[MAX_IOPORTS] = {
[IO_ELCR1] = vatpic_elc_handler,
[IO_ELCR2] = vatpic_elc_handler,
[IO_PMTMR] = vpmtmr_handler,
+ [IO_RTC] = vrtc_addr_handler,
+ [IO_RTC + 1] = vrtc_data_handler,
};
#ifdef KTR
diff --git a/sys/modules/vmm/Makefile b/sys/modules/vmm/Makefile
index 330ea037f57a..e81c2438b357 100644
--- a/sys/modules/vmm/Makefile
+++ b/sys/modules/vmm/Makefile
@@ -35,7 +35,8 @@ SRCS+= iommu.c \
vhpet.c \
vioapic.c \
vlapic.c \
- vpmtmr.c
+ vpmtmr.c \
+ vrtc.c
# intel-specific files
.PATH: ${.CURDIR}/../../amd64/vmm/intel
diff --git a/usr.sbin/bhyve/rtc.c b/usr.sbin/bhyve/rtc.c
index 459c9000687a..055af50f5372 100644
--- a/usr.sbin/bhyve/rtc.c
+++ b/usr.sbin/bhyve/rtc.c
@@ -30,10 +30,7 @@
__FBSDID("$FreeBSD$");
#include <sys/types.h>
-#include <sys/time.h>
-#include <stdio.h>
-#include <string.h>
#include <time.h>
#include <assert.h>
@@ -41,47 +38,11 @@ __FBSDID("$FreeBSD$");
#include <vmmapi.h>
#include "acpi.h"
-#include "inout.h"
#include "pci_lpc.h"
#include "rtc.h"
-#define IO_RTC 0x70
+#define IO_RTC 0x70
-#define RTC_SEC 0x00 /* seconds */
-#define RTC_SEC_ALARM 0x01
-#define RTC_MIN 0x02
-#define RTC_MIN_ALARM 0x03
-#define RTC_HRS 0x04
-#define RTC_HRS_ALARM 0x05
-#define RTC_WDAY 0x06
-#define RTC_DAY 0x07
-#define RTC_MONTH 0x08
-#define RTC_YEAR 0x09
-#define RTC_CENTURY 0x32 /* current century */
-
-#define RTC_STATUSA 0xA
-#define RTCSA_TUP 0x80 /* time update, don't look now */
-
-#define RTC_STATUSB 0xB
-#define RTCSB_DST 0x01
-#define RTCSB_24HR 0x02
-#define RTCSB_BIN 0x04 /* 0 = BCD, 1 = Binary */
-#define RTCSB_PINTR 0x40 /* 1 = enable periodic clock interrupt */
-#define RTCSB_HALT 0x80 /* stop clock updates */
-
-#define RTC_INTR 0x0c /* status register C (R) interrupt source */
-
-#define RTC_STATUSD 0x0d /* status register D (R) Lost Power */
-#define RTCSD_PWR 0x80 /* clock power OK */
-
-#define RTC_NVRAM_START 0x0e
-#define RTC_NVRAM_END 0x7f
-#define RTC_NVRAM_SZ (128 - RTC_NVRAM_START)
-#define nvoff(x) ((x) - RTC_NVRAM_START)
-
-#define RTC_DIAG 0x0e
-#define RTC_RSTCODE 0x0f
-#define RTC_EQUIPMENT 0x14
#define RTC_LMEM_LSB 0x34
#define RTC_LMEM_MSB 0x35
#define RTC_HMEM_LSB 0x5b
@@ -92,249 +53,30 @@ __FBSDID("$FreeBSD$");
#define m_16MB (16*1024*1024)
#define m_4GB (4ULL*1024*1024*1024)
-static int addr;
-
-static uint8_t rtc_nvram[RTC_NVRAM_SZ];
-
-/* XXX initialize these to default values as they would be from BIOS */
-static uint8_t status_a, status_b;
-
-static struct {
- uint8_t hours;
- uint8_t mins;
- uint8_t secs;
-} rtc_alarm;
-
-static 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
-};
-#define bin2bcd(bin) (bin2bcd_data[bin])
-
-#define rtcout(val) ((status_b & RTCSB_BIN) ? (val) : bin2bcd((val)))
-
-static void
-timevalfix(struct timeval *t1)
-{
-
- if (t1->tv_usec < 0) {
- t1->tv_sec--;
- t1->tv_usec += 1000000;
- }
- if (t1->tv_usec >= 1000000) {
- t1->tv_sec++;
- t1->tv_usec -= 1000000;
- }
-}
-
-static void
-timevalsub(struct timeval *t1, const struct timeval *t2)
-{
-
- t1->tv_sec -= t2->tv_sec;
- t1->tv_usec -= t2->tv_usec;
- timevalfix(t1);
-}
-
-static int
-rtc_addr_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
- uint32_t *eax, void *arg)
-{
- if (bytes != 1)
- return (-1);
-
- if (in) {
- /* straight read of this register will return 0xFF */
- *eax = 0xff;
- return (0);
- }
-
- switch (*eax & 0x7f) {
- case RTC_SEC:
- case RTC_SEC_ALARM:
- case RTC_MIN:
- case RTC_MIN_ALARM:
- case RTC_HRS:
- case RTC_HRS_ALARM:
- case RTC_WDAY:
- case RTC_DAY:
- case RTC_MONTH:
- case RTC_YEAR:
- case RTC_STATUSA:
- case RTC_STATUSB:
- case RTC_INTR:
- case RTC_STATUSD:
- case RTC_NVRAM_START ... RTC_NVRAM_END:
- break;
- default:
- return (-1);
- }
-
- addr = *eax & 0x7f;
- return (0);
-}
-
-static int
-rtc_data_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
- uint32_t *eax, void *arg)
+/*
+ * Returns the current RTC time as number of seconds since 00:00:00 Jan 1, 1970
+ *
+ * XXX this always returns localtime to maintain compatibility with the
+ * original device model.
+ */
+static time_t
+rtc_time(struct vmctx *ctx)
{
- int hour;
+ struct tm tm;
time_t t;
- struct timeval cur, delta;
-
- static struct timeval last;
- static struct tm tm;
-
- if (bytes != 1)
- return (-1);
-
- gettimeofday(&cur, NULL);
-
- /*
- * Increment the cached time only once per second so we can guarantee
- * that the guest has at least one second to read the hour:min:sec
- * separately and still get a coherent view of the time.
- */
- delta = cur;
- timevalsub(&delta, &last);
- if (delta.tv_sec >= 1 && (status_b & RTCSB_HALT) == 0) {
- t = cur.tv_sec;
- localtime_r(&t, &tm);
- last = cur;
- }
-
- if (in) {
- switch (addr) {
- case RTC_SEC_ALARM:
- *eax = rtc_alarm.secs;
- break;
- case RTC_MIN_ALARM:
- *eax = rtc_alarm.mins;
- break;
- case RTC_HRS_ALARM:
- *eax = rtc_alarm.hours;
- break;
- case RTC_SEC:
- *eax = rtcout(tm.tm_sec);
- return (0);
- case RTC_MIN:
- *eax = rtcout(tm.tm_min);
- return (0);
- case RTC_HRS:
- if (status_b & RTCSB_24HR)
- hour = tm.tm_hour;
- else
- hour = (tm.tm_hour % 12) + 1;
-
- *eax = rtcout(hour);
-
- /*
- * If we are representing time in the 12-hour format
- * then set the MSB to indicate PM.
- */
- if ((status_b & RTCSB_24HR) == 0 && tm.tm_hour >= 12)
- *eax |= 0x80;
-
- return (0);
- case RTC_WDAY:
- *eax = rtcout(tm.tm_wday + 1);
- return (0);
- case RTC_DAY:
- *eax = rtcout(tm.tm_mday);
- return (0);
- case RTC_MONTH:
- *eax = rtcout(tm.tm_mon + 1);
- return (0);
- case RTC_YEAR:
- *eax = rtcout(tm.tm_year % 100);
- return (0);
- case RTC_STATUSA:
- *eax = status_a;
- return (0);
- case RTC_STATUSB:
- *eax = status_b;
- return (0);
- case RTC_INTR:
- *eax = 0;
- return (0);
- case RTC_STATUSD:
- *eax = RTCSD_PWR;
- return (0);
- case RTC_NVRAM_START ... RTC_NVRAM_END:
- *eax = rtc_nvram[addr - RTC_NVRAM_START];
- return (0);
- default:
- return (-1);
- }
- }
- switch (addr) {
- case RTC_STATUSA:
- status_a = *eax & ~RTCSA_TUP;
- break;
- case RTC_STATUSB:
- /* XXX not implemented yet XXX */
- if (*eax & RTCSB_PINTR)
- return (-1);
- status_b = *eax;
- break;
- case RTC_STATUSD:
- /* ignore write */
- break;
- case RTC_SEC_ALARM:
- rtc_alarm.secs = *eax;
- break;
- case RTC_MIN_ALARM:
- rtc_alarm.mins = *eax;
- break;
- case RTC_HRS_ALARM:
- rtc_alarm.hours = *eax;
- break;
- case RTC_SEC:
- case RTC_MIN:
- case RTC_HRS:
- case RTC_WDAY:
- case RTC_DAY:
- case RTC_MONTH:
- case RTC_YEAR:
- /*
- * Ignore writes to the time of day registers
- */
- break;
- case RTC_NVRAM_START ... RTC_NVRAM_END:
- rtc_nvram[addr - RTC_NVRAM_START] = *eax;
- break;
- default:
- return (-1);
- }
- return (0);
+ time(&t);
+ localtime_r(&t, &tm);
+ return (timegm(&tm));
}
void
rtc_init(struct vmctx *ctx)
{
- struct timeval cur;
- struct tm tm;
size_t himem;
size_t lomem;
int err;
- err = gettimeofday(&cur, NULL);
- assert(err == 0);
- (void) localtime_r(&cur.tv_sec, &tm);
-
- memset(rtc_nvram, 0, sizeof(rtc_nvram));
-
- rtc_nvram[nvoff(RTC_CENTURY)] = bin2bcd((tm.tm_year + 1900) / 100);
-
/* XXX init diag/reset code/equipment/checksum ? */
/*
@@ -344,17 +86,22 @@ rtc_init(struct vmctx *ctx)
* 0x5b/0x5c/0x5d - 64KB chunks above 4GB
*/
lomem = (vm_get_lowmem_size(ctx) - m_16MB) / m_64KB;
- rtc_nvram[nvoff(RTC_LMEM_LSB)] = lomem;
- rtc_nvram[nvoff(RTC_LMEM_MSB)] = lomem >> 8;
+ err = vm_rtc_write(ctx, RTC_LMEM_LSB, lomem);
+ assert(err == 0);
+ err = vm_rtc_write(ctx, RTC_LMEM_MSB, lomem >> 8);
+ assert(err == 0);
himem = vm_get_highmem_size(ctx) / m_64KB;
- rtc_nvram[nvoff(RTC_HMEM_LSB)] = himem;
- rtc_nvram[nvoff(RTC_HMEM_SB)] = himem >> 8;
- rtc_nvram[nvoff(RTC_HMEM_MSB)] = himem >> 16;
-}
+ err = vm_rtc_write(ctx, RTC_HMEM_LSB, himem);
+ assert(err == 0);
+ err = vm_rtc_write(ctx, RTC_HMEM_SB, himem >> 8);
+ assert(err == 0);
+ err = vm_rtc_write(ctx, RTC_HMEM_MSB, himem >> 16);
+ assert(err == 0);
-INOUT_PORT(rtc, IO_RTC, IOPORT_F_INOUT, rtc_addr_handler);
-INOUT_PORT(rtc, IO_RTC + 1, IOPORT_F_INOUT, rtc_data_handler);
+ err = vm_rtc_settime(ctx, rtc_time(ctx));
+ assert(err == 0);
+}
static void
rtc_dsdt(void)
diff --git a/usr.sbin/bhyvectl/bhyvectl.c b/usr.sbin/bhyvectl/bhyvectl.c
index 0c4457e6ee87..170ca2114a0b 100644
--- a/usr.sbin/bhyvectl/bhyvectl.c
+++ b/usr.sbin/bhyvectl/bhyvectl.c
@@ -45,6 +45,7 @@ __FBSDID("$FreeBSD$");
#include <fcntl.h>
#include <string.h>
#include <getopt.h>
+#include <time.h>
#include <assert.h>
#include <machine/cpufunc.h>
@@ -157,6 +158,11 @@ usage(bool cpu_intel)
" [--inject-nmi]\n"
" [--force-reset]\n"
" [--force-poweroff]\n"
+ " [--get-rtc-time]\n"
+ " [--set-rtc-time=<secs>]\n"
+ " [--get-rtc-nvram]\n"
+ " [--set-rtc-nvram=<val>]\n"
+ " [--rtc-nvram-offset=<offset>]\n"
" [--get-active-cpus]\n"
" [--get-suspended-cpus]\n"
" [--get-intinfo]\n"
@@ -220,6 +226,12 @@ usage(bool cpu_intel)
exit(1);
}
+static int get_rtc_time, set_rtc_time;
+static int get_rtc_nvram, set_rtc_nvram;
+static int rtc_nvram_offset;
+static uint8_t rtc_nvram_value;
+static time_t rtc_secs;
+
static int get_stats, getcap, setcap, capval, get_gpa_pmap;
static int inject_nmi, assert_lapic_lvt;
static int force_reset, force_poweroff;
@@ -545,6 +557,9 @@ enum {
UNASSIGN_PPTDEV,
GET_GPA_PMAP,
ASSERT_LAPIC_LVT,
+ SET_RTC_TIME,
+ SET_RTC_NVRAM,
+ RTC_NVRAM_OFFSET,
};
static void
@@ -1269,6 +1284,11 @@ setup_options(bool cpu_intel)
{ "setcap", REQ_ARG, 0, SET_CAP },
{ "get-gpa-pmap", REQ_ARG, 0, GET_GPA_PMAP },
{ "assert-lapic-lvt", REQ_ARG, 0, ASSERT_LAPIC_LVT },
+ { "get-rtc-time", NO_ARG, &get_rtc_time, 1 },
+ { "set-rtc-time", REQ_ARG, 0, SET_RTC_TIME },
+ { "rtc-nvram-offset", REQ_ARG, 0, RTC_NVRAM_OFFSET },
+ { "get-rtc-nvram", NO_ARG, &get_rtc_nvram, 1 },
+ { "set-rtc-nvram", REQ_ARG, 0, SET_RTC_NVRAM },
{ "getcap", NO_ARG, &getcap, 1 },
{ "get-stats", NO_ARG, &get_stats, 1 },
{ "get-desc-ds",NO_ARG, &get_desc_ds, 1 },
@@ -1462,6 +1482,33 @@ setup_options(bool cpu_intel)
return (all_opts);
}
+static const char *
+wday_str(int idx)
+{
+ static const char *weekdays[] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+ };
+
+ if (idx >= 0 && idx < 7)
+ return (weekdays[idx]);
+ else
+ return ("UNK");
+}
+
+static const char *
+mon_str(int idx)
+{
+ static const char *months[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+ };
+
+ if (idx >= 0 && idx < 12)
+ return (months[idx]);
+ else
+ return ("UNK");
+}
+
int
main(int argc, char *argv[])
{
@@ -1477,6 +1524,7 @@ main(int argc, char *argv[])
cpuset_t cpus;
bool cpu_intel;
uint64_t cs, ds, es, fs, gs, ss, tr, ldtr;
+ struct tm tm;
struct option *opts;
cpu_intel = cpu_vendor_intel();
@@ -1594,6 +1642,17 @@ main(int argc, char *argv[])
capval = strtoul(optarg, NULL, 0);
setcap = 1;
break;
+ case SET_RTC_TIME:
+ rtc_secs = strtoul(optarg, NULL, 0);
+ set_rtc_time = 1;
+ break;
+ case SET_RTC_NVRAM:
+ rtc_nvram_value = (uint8_t)strtoul(optarg, NULL, 0);
+ set_rtc_nvram = 1;
+ break;
+ case RTC_NVRAM_OFFSET:
+ rtc_nvram_offset = strtoul(optarg, NULL, 0);
+ break;
case GET_GPA_PMAP:
gpa_pmap = strtoul(optarg, NULL, 0);
get_gpa_pmap = 1;
@@ -1971,6 +2030,31 @@ main(int argc, char *argv[])
}
}
+ if (!error && set_rtc_nvram)
+ error = vm_rtc_write(ctx, rtc_nvram_offset, rtc_nvram_value);
+
+ if (!error && (get_rtc_nvram || get_all)) {
+ error = vm_rtc_read(ctx, rtc_nvram_offset, &rtc_nvram_value);
+ if (error == 0) {
+ printf("rtc nvram[%03d]: 0x%02x\n", rtc_nvram_offset,
+ rtc_nvram_value);
+ }
+ }
+
+ if (!error && set_rtc_time)
+ error = vm_rtc_settime(ctx, rtc_secs);
+
+ if (!error && (get_rtc_time || get_all)) {
+ error = vm_rtc_gettime(ctx, &rtc_secs);
+ if (error == 0) {
+ gmtime_r(&rtc_secs, &tm);
+ printf("rtc time %#lx: %s %s %02d %02d:%02d:%02d %d\n",
+ rtc_secs, wday_str(tm.tm_wday), mon_str(tm.tm_mon),
+ tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
+ 1900 + tm.tm_year);
+ }
+ }
+
if (!error && (getcap || get_all)) {
int captype, val, getcaptype;