Replace bhyve's minimal RTC emulation with a fully featured one in vmm.ko.

The new RTC emulation supports all interrupt modes: periodic, update ended
and alarm. It is also capable of maintaining the date/time and NVRAM contents
across virtual machine reset. Also, the date/time fields can now be modified
by the guest.

Since bhyve now emulates both the PIT and the RTC there is no need for
"Legacy Replacement Routing" in the HPET so get rid of it.

The RTC device state can be inspected via bhyvectl as follows:
bhyvectl --vm=vm --get-rtc-time
bhyvectl --vm=vm --set-rtc-time=<unix_time_secs>
bhyvectl --vm=vm --rtc-nvram-offset=<offset> --get-rtc-nvram
bhyvectl --vm=vm --rtc-nvram-offset=<offset> --set-rtc-nvram=<value>

Reviewed by:	tychon
Discussed with:	grehan
Differential Revision:	https://reviews.freebsd.org/D1385
MFC after:	2 weeks
This commit is contained in:
Neel Natu 2014-12-30 22:19:34 +00:00
parent 6db8a9f3a5
commit 0dafa5cd4b
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=276428
13 changed files with 1245 additions and 341 deletions

View File

@ -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);
}

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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);

952
sys/amd64/vmm/io/vrtc.c Normal file
View File

@ -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);
}

50
sys/amd64/vmm/io/vrtc.h Normal file
View File

@ -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

View File

@ -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)
{

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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)
/*
* 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)
{
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)
{
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)

View File

@ -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;