#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/queue.h>
#include <sys/kernel.h>
#include <sys/kmem.h>
#include <sys/mutex.h>
#include <sys/clock.h>
#include <sys/sysctl.h>
#include <machine/vmm.h>
#include <isa/rtc.h>
#include "vatpic.h"
#include "vioapic.h"
#include "vrtc.h"
struct rtcdev {
uint8_t sec;
uint8_t alarm_sec;
uint8_t min;
uint8_t alarm_min;
uint8_t hour;
uint8_t alarm_hour;
uint8_t day_of_week;
uint8_t day_of_month;
uint8_t month;
uint8_t year;
uint8_t reg_a;
uint8_t reg_b;
uint8_t reg_c;
uint8_t reg_d;
uint8_t nvram[36];
uint8_t century;
uint8_t nvram2[128 - 51];
} __packed;
CTASSERT(sizeof (struct rtcdev) == 128);
CTASSERT(offsetof(struct rtcdev, century) == RTC_CENTURY);
struct vrtc {
struct vm *vm;
kmutex_t lock;
struct callout callout;
uint8_t addr;
hrtime_t base_clock;
hrtime_t last_period;
time_t base_rtctime;
struct rtcdev rtcdev;
};
#define VRTC_LOCK(vrtc) mutex_enter(&((vrtc)->lock))
#define VRTC_UNLOCK(vrtc) mutex_exit(&((vrtc)->lock))
#define VRTC_LOCKED(vrtc) MUTEX_HELD(&((vrtc)->lock))
#define VRTC_BROKEN_TIME ((time_t)-1)
#define RTC_IRQ 8
#define RTCSA_DIVIDER_MASK 0x70
#define RTCSA_DIVIDER_32K 0x20
#define RTCSA_PERIOD_MASK 0x0f
#define RTCSB_BIN 0x04
#define RTCSB_INTR_MASK (RTCSB_UINTR | RTCSB_AINTR | RTCSB_PINTR)
#define RTCSC_MASK (RTCIR_UPDATE | RTCIR_ALARM | RTCIR_PERIOD | RTCIR_INT)
#define ALARM_DONT_CARE(x) (((x) & 0xc0) == 0xc0)
#define HOUR_IS_PM 0x80
#define SEC_PER_DAY (24 * 60 * 60)
#define ROUNDDOWN(x, y) (((x)/(y))*(y))
static void vrtc_regc_update(struct vrtc *, uint8_t);
static void vrtc_callout_reschedule(struct vrtc *);
static __inline bool
rtc_field_datetime(uint8_t off)
{
switch (off) {
case RTC_SEC:
case RTC_MIN:
case RTC_HRS:
case RTC_WDAY:
case RTC_DAY:
case RTC_MONTH:
case RTC_YEAR:
case RTC_CENTURY:
return (true);
default:
return (false);
}
}
static __inline bool
rtc_field_ondemand(uint8_t off)
{
switch (off) {
case RTC_STATUSA:
case RTC_STATUSB:
case RTC_INTR:
case RTC_STATUSD:
return (true);
default:
return (rtc_field_datetime(off));
}
}
static __inline bool
rtc_halted(const struct vrtc *vrtc)
{
return ((vrtc->rtcdev.reg_b & RTCSB_HALT) != 0);
}
static __inline bool
rega_divider_en(uint8_t rega)
{
return ((rega & RTCSA_DIVIDER_MASK) == RTCSA_DIVIDER_32K);
}
static __inline hrtime_t
rega_period(uint8_t rega)
{
const uint_t sel = rega & RTCSA_PERIOD_MASK;
const hrtime_t rate_period[16] = {
0,
NANOSEC / 256,
NANOSEC / 128,
NANOSEC / 8192,
NANOSEC / 4096,
NANOSEC / 2048,
NANOSEC / 1024,
NANOSEC / 512,
NANOSEC / 256,
NANOSEC / 128,
NANOSEC / 64,
NANOSEC / 32,
NANOSEC / 16,
NANOSEC / 8,
NANOSEC / 4,
NANOSEC / 2,
};
return (rate_period[sel]);
}
static __inline bool
vrtc_update_enabled(const struct vrtc *vrtc)
{
if (!rega_divider_en(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, hrtime_t *basep, hrtime_t *phasep)
{
time_t t = vrtc->base_rtctime;
hrtime_t base = vrtc->base_clock;
hrtime_t phase = 0;
ASSERT(VRTC_LOCKED(vrtc));
if (vrtc_update_enabled(vrtc)) {
const hrtime_t delta = gethrtime() - vrtc->base_clock;
const time_t sec = delta / NANOSEC;
ASSERT3S(delta, >=, 0);
t += sec;
base += sec * NANOSEC;
phase = delta % NANOSEC;
}
if (basep != NULL) {
*basep = base;
}
if (phasep != NULL) {
*phasep = phase;
}
return (t);
}
static __inline uint8_t
rtc_enc(const struct rtcdev *rtc, uint8_t val)
{
const uint8_t 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
};
ASSERT3U(val, <, 100);
return ((rtc->reg_b & RTCSB_BIN) ? val : bin2bcd_data[val]);
}
static void
vrtc_time_to_cmos(struct vrtc *vrtc, bool force_update)
{
struct rtcdev *rtc = &vrtc->rtcdev;
struct timespec ts = {
.tv_sec = vrtc->base_rtctime,
.tv_nsec = 0,
};
ASSERT(VRTC_LOCKED(vrtc));
if (vrtc->base_rtctime < 0) {
ASSERT3S(vrtc->base_rtctime, ==, VRTC_BROKEN_TIME);
return;
}
if (rtc_halted(vrtc) && !force_update) {
return;
}
struct clocktime ct;
clock_ts_to_ct(&ts, &ct);
ASSERT(ct.sec >= 0 && ct.sec <= 59);
ASSERT(ct.min >= 0 && ct.min <= 59);
ASSERT(ct.hour >= 0 && ct.hour <= 23);
ASSERT(ct.dow >= 0 && ct.dow <= 6);
ASSERT(ct.day >= 1 && ct.day <= 31);
ASSERT(ct.mon >= 1 && ct.mon <= 12);
ASSERT(ct.year >= POSIX_BASE_YEAR);
rtc->sec = rtc_enc(rtc, ct.sec);
rtc->min = rtc_enc(rtc, ct.min);
int hour;
if (rtc->reg_b & RTCSB_24HR) {
hour = ct.hour;
} else {
switch (ct.hour) {
case 0:
case 12:
hour = 12;
break;
default:
hour = ct.hour % 12;
break;
}
}
rtc->hour = rtc_enc(rtc, hour);
if ((rtc->reg_b & RTCSB_24HR) == 0 && ct.hour >= 12) {
rtc->hour |= HOUR_IS_PM;
}
rtc->day_of_week = rtc_enc(rtc, ct.dow + 1);
rtc->day_of_month = rtc_enc(rtc, ct.day);
rtc->month = rtc_enc(rtc, ct.mon);
rtc->year = rtc_enc(rtc, ct.year % 100);
rtc->century = rtc_enc(rtc, ct.year / 100);
}
static uint8_t
rtc_dec(const struct rtcdev *rtc, uint8_t val, bool *errp)
{
if ((rtc->reg_b & RTCSB_BIN) == 0) {
const uint8_t lower = val & 0xf;
const uint8_t upper = val >> 4;
*errp = (lower > 9 || upper > 9);
return ((upper * 10) + lower);
} else {
*errp = false;
return (val);
}
}
static uint8_t
rtc_parse_hour(const struct rtcdev *rtc, uint8_t hour, bool *errp)
{
bool pm = false;
if ((rtc->reg_b & RTCSB_24HR) == 0) {
if ((hour & HOUR_IS_PM) != 0) {
hour &= ~HOUR_IS_PM;
pm = true;
}
}
hour = rtc_dec(rtc, hour, errp);
if ((rtc->reg_b & RTCSB_24HR) == 0) {
if (hour >= 1 && hour <= 12) {
if (hour == 12) {
hour = 0;
}
if (pm) {
hour += 12;
}
} else {
*errp = true;
}
}
if (hour > 23) {
*errp = true;
}
return (hour);
}
static bool
vrtc_alarm_valid(const struct vrtc *vrtc)
{
const struct rtcdev *rtc = &vrtc->rtcdev;
bool err;
uint8_t val;
ASSERT(VRTC_LOCKED(vrtc));
val = rtc->sec;
if (!ALARM_DONT_CARE(val)) {
val = rtc_dec(rtc, val, &err);
if (err || val > 59) {
return (false);
}
}
val = rtc->min;
if (!ALARM_DONT_CARE(val)) {
val = rtc_dec(rtc, val, &err);
if (err || val > 59) {
return (false);
}
}
val = rtc->hour;
if (!ALARM_DONT_CARE(val)) {
(void) rtc_parse_hour(rtc, val, &err);
if (err) {
return (false);
}
}
return (true);
}
static time_t
vrtc_cmos_to_secs(struct vrtc *vrtc)
{
struct rtcdev *rtc = &vrtc->rtcdev;
struct clocktime ct = { 0 };
bool err;
ASSERT(VRTC_LOCKED(vrtc));
ct.sec = rtc_dec(rtc, rtc->sec, &err);
if (err || ct.sec > 59) {
goto fail;
}
ct.min = rtc_dec(rtc, rtc->min, &err);
if (err || ct.min > 59) {
goto fail;
}
ct.hour = rtc_parse_hour(rtc, rtc->hour, &err);
if (err) {
goto fail;
}
ct.dow = -1;
ct.day = rtc_dec(rtc, rtc->day_of_month, &err);
if (err || ct.day < 1 || ct.day > 31) {
goto fail;
}
ct.mon = rtc_dec(rtc, rtc->month, &err);
if (err || ct.mon < 1 || ct.mon > 12) {
goto fail;
}
const uint_t year = rtc_dec(rtc, rtc->year, &err);
if (err || year > 99) {
goto fail;
}
const uint_t century = rtc_dec(rtc, rtc->century, &err);
ct.year = century * 100 + year;
if (err || ct.year < POSIX_BASE_YEAR) {
goto fail;
}
struct timespec ts;
if (clock_ct_to_ts(&ct, &ts) != 0 || ts.tv_sec < 0) {
goto fail;
}
return (ts.tv_sec);
fail:
return (VRTC_BROKEN_TIME);
}
static void
vrtc_periodic_update(struct vrtc *vrtc)
{
struct rtcdev *rtc = &vrtc->rtcdev;
ASSERT(VRTC_LOCKED(vrtc));
const hrtime_t period = rega_period(rtc->reg_a);
if (!rega_divider_en(rtc->reg_a) || period == 0) {
return;
}
hrtime_t since_last = gethrtime() - vrtc->last_period;
if (since_last > period) {
vrtc_regc_update(vrtc, RTCIR_PERIOD);
vrtc->last_period += ROUNDDOWN(since_last, period);
}
}
static void
vrtc_update(struct vrtc *vrtc, uint8_t off)
{
struct rtcdev *rtc = &vrtc->rtcdev;
ASSERT(VRTC_LOCKED(vrtc));
if (!rtc_field_ondemand(off)) {
return;
}
if (!rega_divider_en(rtc->reg_a)) {
return;
}
vrtc_periodic_update(vrtc);
if (vrtc->base_rtctime == VRTC_BROKEN_TIME) {
return;
}
hrtime_t base_clock;
const time_t newtime = vrtc_curtime(vrtc, &base_clock, NULL);
if (vrtc->base_rtctime >= newtime) {
return;
}
vrtc->base_clock = base_clock;
if (!vrtc_alarm_valid(vrtc) || (rtc->reg_c & RTCIR_ALARM) != 0) {
vrtc->base_rtctime = newtime;
} else if ((newtime - vrtc->base_rtctime) >= SEC_PER_DAY) {
vrtc_regc_update(vrtc, RTCIR_ALARM);
vrtc->base_rtctime = newtime;
} else {
const uint8_t a_sec = rtc->alarm_sec;
const uint8_t a_min = rtc->alarm_min;
const uint8_t a_hour = rtc->alarm_hour;
do {
vrtc->base_rtctime++;
vrtc_time_to_cmos(vrtc, false);
if ((ALARM_DONT_CARE(a_sec) || a_sec == rtc->sec) &&
(ALARM_DONT_CARE(a_min) || a_min == rtc->min) &&
(ALARM_DONT_CARE(a_hour) || a_hour == rtc->hour)) {
vrtc_regc_update(vrtc, RTCIR_ALARM);
vrtc->base_rtctime = newtime;
}
} while (vrtc->base_rtctime != newtime);
}
vrtc_regc_update(vrtc, RTCIR_UPDATE);
}
static void
vrtc_callout_handler(void *arg)
{
struct vrtc *vrtc = arg;
VRTC_LOCK(vrtc);
if (callout_pending(&vrtc->callout)) {
} else if (!callout_active(&vrtc->callout)) {
} else {
callout_deactivate(&vrtc->callout);
vrtc_update(vrtc, RTC_INTR);
vrtc_callout_reschedule(vrtc);
}
VRTC_UNLOCK(vrtc);
}
static void
vrtc_callout_reschedule(struct vrtc *vrtc)
{
struct rtcdev *rtc = &vrtc->rtcdev;
ASSERT(VRTC_LOCKED(vrtc));
hrtime_t period = 0;
if ((rtc->reg_b & RTCSB_PINTR) != 0) {
period = rega_period(rtc->reg_a);
}
if (period == 0 && vrtc_update_enabled(vrtc)) {
period = NANOSEC;
}
const bool intr_enabled = (rtc->reg_b & RTCSB_INTR_MASK) != 0;
const bool intr_asserted = (rtc->reg_c & RTCIR_INT) != 0;
if (period != 0 && intr_enabled && !intr_asserted) {
const hrtime_t delta = gethrtime() + period - vrtc->base_clock;
const hrtime_t next =
ROUNDDOWN(delta, period) + vrtc->base_clock;
callout_reset_hrtime(&vrtc->callout, next, vrtc_callout_handler,
vrtc, C_ABSOLUTE);
} else {
if (callout_active(&vrtc->callout)) {
callout_stop(&vrtc->callout);
}
}
}
CTASSERT(RTCIR_UPDATE == RTCSB_UINTR);
CTASSERT(RTCIR_ALARM == RTCSB_AINTR);
CTASSERT(RTCIR_PERIOD == RTCSB_PINTR);
static void
vrtc_regc_update(struct vrtc *vrtc, uint8_t events)
{
struct rtcdev *rtc = &vrtc->rtcdev;
ASSERT(VRTC_LOCKED(vrtc));
ASSERT0(events & ~(RTCSB_INTR_MASK));
rtc->reg_c |= events;
const bool oldirq = (rtc->reg_c & RTCIR_INT) != 0;
if ((rtc->reg_b & RTCSB_INTR_MASK & rtc->reg_c) != 0) {
rtc->reg_c |= RTCIR_INT;
}
const bool newirq = (rtc->reg_c & RTCIR_INT) != 0;
if (!oldirq && newirq) {
(void) vatpic_pulse_irq(vrtc->vm, RTC_IRQ);
(void) vioapic_pulse_irq(vrtc->vm, RTC_IRQ);
} else if (oldirq && !newirq) {
}
}
static uint8_t
vrtc_regc_read(struct vrtc *vrtc)
{
struct rtcdev *rtc = &vrtc->rtcdev;
ASSERT(VRTC_LOCKED(vrtc));
const uint8_t val = rtc->reg_c;
rtc->reg_c = 0;
if ((val & RTCIR_INT) != 0) {
vrtc_callout_reschedule(vrtc);
}
return (val);
}
static void
vrtc_regb_write(struct vrtc *vrtc, uint8_t newval)
{
struct rtcdev *rtc = &vrtc->rtcdev;
ASSERT(VRTC_LOCKED(vrtc));
uint8_t changed = rtc->reg_b ^ newval;
rtc->reg_b = newval;
if (changed & RTCSB_HALT) {
if ((newval & RTCSB_HALT) == 0) {
vrtc->base_rtctime = vrtc_cmos_to_secs(vrtc);
if (rega_divider_en(vrtc->rtcdev.reg_a)) {
const hrtime_t delta =
gethrtime() - vrtc->base_clock;
if (delta > NANOSEC) {
vrtc->base_clock += delta / NANOSEC;
}
} else {
}
} else {
vrtc_update(vrtc, RTC_STATUSB);
vrtc_time_to_cmos(vrtc, true);
vrtc->base_rtctime = VRTC_BROKEN_TIME;
if ((rtc->reg_b & RTCSB_UINTR) != 0) {
rtc->reg_b &= ~RTCSB_UINTR;
changed |= RTCSB_UINTR;
}
}
}
if (changed & RTCSB_INTR_MASK) {
vrtc_regc_update(vrtc, 0);
}
vrtc_callout_reschedule(vrtc);
}
static void
vrtc_rega_write(struct vrtc *vrtc, uint8_t newval)
{
ASSERT(VRTC_LOCKED(vrtc));
const hrtime_t now = gethrtime();
const uint8_t oldval = vrtc->rtcdev.reg_a;
bool divider_restarted = false;
if (rega_divider_en(oldval) && !rega_divider_en(newval)) {
} else if (!rega_divider_en(oldval) && rega_divider_en(newval)) {
vrtc->base_rtctime = vrtc_cmos_to_secs(vrtc);
vrtc->base_clock = now - (NANOSEC / 2);
divider_restarted = true;
}
const hrtime_t period_old = rega_period(oldval);
const hrtime_t period_new = rega_period(newval);
if (period_old != period_new || divider_restarted) {
if (period_new != 0) {
const hrtime_t since_last = now - vrtc->base_clock;
vrtc->last_period = vrtc->base_clock;
vrtc->last_period += ROUNDDOWN(since_last, period_new);
} else {
vrtc->last_period = now;
}
}
vrtc->rtcdev.reg_a = newval & ~RTCSA_TUP;
vrtc_callout_reschedule(vrtc);
}
int
vrtc_set_time(struct vm *vm, const timespec_t *ts)
{
struct vrtc *vrtc = vm_rtc(vm);
if (ts->tv_sec < 0 || ts->tv_nsec >= NANOSEC) {
return (EINVAL);
}
VRTC_LOCK(vrtc);
vrtc->base_rtctime = ts->tv_sec;
vrtc->base_clock = gethrtime() - ts->tv_nsec;
vrtc->last_period = vrtc->base_clock;
if (!vm_is_paused(vrtc->vm)) {
vrtc_callout_reschedule(vrtc);
}
VRTC_UNLOCK(vrtc);
return (0);
}
void
vrtc_get_time(struct vm *vm, timespec_t *ts)
{
struct vrtc *vrtc = vm_rtc(vm);
hrtime_t phase;
VRTC_LOCK(vrtc);
ts->tv_sec = vrtc_curtime(vrtc, NULL, &phase);
ts->tv_nsec = phase;
VRTC_UNLOCK(vrtc);
}
int
vrtc_nvram_write(struct vm *vm, int offset, uint8_t value)
{
struct vrtc *vrtc = vm_rtc(vm);
uint8_t *rtc_raw = (uint8_t *)&vrtc->rtcdev;
if (offset < 0 || offset >= sizeof (struct rtcdev)) {
return (EINVAL);
}
if (rtc_field_ondemand(offset)) {
return (EINVAL);
}
VRTC_LOCK(vrtc);
rtc_raw[offset] = value;
VRTC_UNLOCK(vrtc);
return (0);
}
int
vrtc_nvram_read(struct vm *vm, int offset, uint8_t *retval)
{
struct vrtc *vrtc = vm_rtc(vm);
const uint8_t *rtc_raw = (uint8_t *)&vrtc->rtcdev;
if (offset < 0 || offset >= sizeof (struct rtcdev)) {
return (EINVAL);
}
VRTC_LOCK(vrtc);
vrtc_update(vrtc, offset);
if (rtc_field_datetime(offset)) {
vrtc_time_to_cmos(vrtc, false);
}
*retval = rtc_raw[offset];
VRTC_UNLOCK(vrtc);
return (0);
}
int
vrtc_addr_handler(void *arg, bool in, uint16_t port, uint8_t bytes,
uint32_t *val)
{
struct vrtc *vrtc = arg;
if (bytes != 1) {
return (-1);
}
if (in) {
*val = 0xff;
return (0);
}
VRTC_LOCK(vrtc);
vrtc->addr = *val & 0x7f;
VRTC_UNLOCK(vrtc);
return (0);
}
static uint8_t
vrtc_read(struct vrtc *vrtc, uint8_t offset)
{
const uint8_t *rtc_raw = (uint8_t *)&vrtc->rtcdev;
ASSERT(VRTC_LOCKED(vrtc));
ASSERT(offset < sizeof (struct rtcdev));
switch (offset) {
case RTC_INTR:
return (vrtc_regc_read(vrtc));
default:
return (rtc_raw[offset]);
}
}
static int
vrtc_write(struct vrtc *vrtc, uint8_t offset, uint8_t val)
{
uint8_t *rtc_raw = (uint8_t *)&vrtc->rtcdev;
ASSERT(VRTC_LOCKED(vrtc));
if (offset >= sizeof (struct rtcdev))
return (-1);
switch (offset) {
case RTC_STATUSA:
vrtc_rega_write(vrtc, val);
break;
case RTC_STATUSB:
vrtc_regb_write(vrtc, val);
break;
case RTC_INTR:
break;
case RTC_STATUSD:
break;
case RTC_SEC:
rtc_raw[offset] = val & 0x7f;
break;
default:
rtc_raw[offset] = val;
break;
}
if (rtc_field_datetime(offset) && !rtc_halted(vrtc)) {
vrtc->base_rtctime = vrtc_cmos_to_secs(vrtc);
}
return (0);
}
int
vrtc_data_handler(void *arg, bool in, uint16_t port, uint8_t bytes,
uint32_t *val)
{
struct vrtc *vrtc = arg;
int ret = -1;
if (bytes != 1) {
return (ret);
}
VRTC_LOCK(vrtc);
const uint8_t offset = vrtc->addr;
if (offset < sizeof (struct rtcdev)) {
ret = 0;
vrtc_update(vrtc, offset);
if (rtc_field_datetime(offset)) {
vrtc_time_to_cmos(vrtc, false);
}
if (in) {
*val = vrtc_read(vrtc, offset);
} else {
ret = vrtc_write(vrtc, offset, *val);
}
}
VRTC_UNLOCK(vrtc);
return (ret);
}
void
vrtc_reset(struct vrtc *vrtc)
{
struct rtcdev *rtc = &vrtc->rtcdev;
VRTC_LOCK(vrtc);
vrtc_regb_write(vrtc, rtc->reg_b & ~(RTCSB_INTR_MASK | RTCSB_SQWE));
rtc->reg_c = 0;
ASSERT(!callout_active(&vrtc->callout));
VRTC_UNLOCK(vrtc);
}
struct vrtc *
vrtc_init(struct vm *vm)
{
struct vrtc *vrtc;
struct rtcdev *rtc;
vrtc = kmem_zalloc(sizeof (struct vrtc), KM_SLEEP);
vrtc->vm = vm;
mutex_init(&vrtc->lock, NULL, MUTEX_ADAPTIVE, NULL);
callout_init(&vrtc->callout, 1);
rtc = &vrtc->rtcdev;
rtc->reg_a = RTCSA_DIVIDER_32K;
rtc->reg_b = RTCSB_24HR;
rtc->reg_c = 0;
rtc->reg_d = RTCSD_PWR;
vrtc->addr = RTC_STATUSD;
VRTC_LOCK(vrtc);
vrtc->base_rtctime = 0;
vrtc->base_clock = gethrtime();
vrtc->last_period = vrtc->base_clock;
vrtc_time_to_cmos(vrtc, false);
VRTC_UNLOCK(vrtc);
return (vrtc);
}
void
vrtc_cleanup(struct vrtc *vrtc)
{
callout_drain(&vrtc->callout);
mutex_destroy(&vrtc->lock);
kmem_free(vrtc, sizeof (*vrtc));
}
void
vrtc_localize_resources(struct vrtc *vrtc)
{
vmm_glue_callout_localize(&vrtc->callout);
}
void
vrtc_pause(struct vrtc *vrtc)
{
VRTC_LOCK(vrtc);
callout_stop(&vrtc->callout);
VRTC_UNLOCK(vrtc);
}
void
vrtc_resume(struct vrtc *vrtc)
{
VRTC_LOCK(vrtc);
ASSERT(!callout_active(&vrtc->callout));
vrtc_callout_reschedule(vrtc);
VRTC_UNLOCK(vrtc);
}
static int
vrtc_data_read(void *datap, const vmm_data_req_t *req)
{
VERIFY3U(req->vdr_class, ==, VDC_RTC);
VERIFY3U(req->vdr_version, ==, 2);
VERIFY3U(req->vdr_len, >=, sizeof (struct vdi_rtc_v2));
struct vrtc *vrtc = datap;
struct vdi_rtc_v2 *out = req->vdr_data;
VRTC_LOCK(vrtc);
out->vr_addr = vrtc->addr;
out->vr_base_clock = vm_normalize_hrtime(vrtc->vm, vrtc->base_clock);
out->vr_last_period = vm_normalize_hrtime(vrtc->vm, vrtc->last_period);
bcopy(&vrtc->rtcdev, out->vr_content, sizeof (out->vr_content));
VRTC_UNLOCK(vrtc);
return (0);
}
static int
vrtc_data_write(void *datap, const vmm_data_req_t *req)
{
VERIFY3U(req->vdr_class, ==, VDC_RTC);
VERIFY3U(req->vdr_version, ==, 2);
VERIFY3U(req->vdr_len, >=, sizeof (struct vdi_rtc_v2));
struct vrtc *vrtc = datap;
const struct vdi_rtc_v2 *src = req->vdr_data;
const hrtime_t base_clock =
vm_denormalize_hrtime(vrtc->vm, src->vr_base_clock);
const hrtime_t last_period =
vm_denormalize_hrtime(vrtc->vm, src->vr_last_period);
const hrtime_t now = gethrtime();
if (base_clock > now || last_period > now) {
return (EINVAL);
}
VRTC_LOCK(vrtc);
vrtc->base_clock = base_clock;
bcopy(src->vr_content, &vrtc->rtcdev, sizeof (vrtc->rtcdev));
vrtc->addr = src->vr_addr;
vrtc->rtcdev.reg_a &= ~RTCSA_TUP;
vrtc->rtcdev.reg_c &= RTCSC_MASK;
vrtc->rtcdev.reg_d = RTCSD_PWR;
vrtc->base_rtctime = vrtc_cmos_to_secs(vrtc);
vrtc->base_clock = base_clock;
vrtc->last_period = last_period;
if (!vm_is_paused(vrtc->vm)) {
vrtc_callout_reschedule(vrtc);
}
VRTC_UNLOCK(vrtc);
return (0);
}
static const vmm_data_version_entry_t rtc_v2 = {
.vdve_class = VDC_RTC,
.vdve_version = 2,
.vdve_len_expect = sizeof (struct vdi_rtc_v2),
.vdve_readf = vrtc_data_read,
.vdve_writef = vrtc_data_write,
};
VMM_DATA_VERSION(rtc_v2);