dissertation/usr.sbin/vmd/mc146818.c

386 lines
8.2 KiB
C

/* $OpenBSD: mc146818.c,v 1.25 2022/01/15 23:39:11 mlarkin Exp $ */
/*
* Copyright (c) 2016 Mike Larkin <mlarkin@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <dev/ic/mc146818reg.h>
#include <dev/isa/isareg.h>
#include <machine/vmmvar.h>
#include <event.h>
#include <stddef.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "atomicio.h"
#include "mc146818.h"
#include "virtio.h"
#include "vmd.h"
#include "vmm.h"
#define MC_RATE_MASK 0xf
#define NVRAM_CENTURY 0x32
#define NVRAM_MEMSIZE_LO 0x34
#define NVRAM_MEMSIZE_HI 0x35
#define NVRAM_HIMEMSIZE_LO 0x5B
#define NVRAM_HIMEMSIZE_MID 0x5C
#define NVRAM_HIMEMSIZE_HI 0x5D
#define NVRAM_SMP_COUNT 0x5F
#define NVRAM_SIZE 0x60
#define TOBCD(x) (((x) / 10 * 16) + ((x) % 10))
struct mc146818 {
time_t now;
uint8_t idx;
uint8_t regs[NVRAM_SIZE];
uint32_t vm_id;
struct event sec;
struct timeval sec_tv;
struct event per;
struct timeval per_tv;
};
struct mc146818 rtc;
static struct vm_dev_pipe dev_pipe;
static void rtc_reschedule_per(void);
/*
* mc146818_pipe_dispatch
*
* Reads a message off the pipe, expecting a request to reschedule periodic
* interrupt rate.
*/
static void
mc146818_pipe_dispatch(int fd, short event, void *arg)
{
enum pipe_msg_type msg;
msg = vm_pipe_recv(&dev_pipe);
if (msg == MC146818_RESCHEDULE_PER) {
rtc_reschedule_per();
} else {
fatalx("%s: unexpected pipe message %d", __func__, msg);
}
}
/*
* rtc_updateregs
*
* Updates the RTC TOD bytes, reflecting 'now'.
*/
static void
rtc_updateregs(void)
{
struct tm *gnow;
rtc.regs[MC_REGD] &= ~MC_REGD_VRT;
gnow = gmtime(&rtc.now);
rtc.regs[MC_SEC] = TOBCD(gnow->tm_sec);
rtc.regs[MC_MIN] = TOBCD(gnow->tm_min);
rtc.regs[MC_HOUR] = TOBCD(gnow->tm_hour);
rtc.regs[MC_DOW] = TOBCD(gnow->tm_wday + 1);
rtc.regs[MC_DOM] = TOBCD(gnow->tm_mday);
rtc.regs[MC_MONTH] = TOBCD(gnow->tm_mon + 1);
rtc.regs[MC_YEAR] = TOBCD((gnow->tm_year + 1900) % 100);
rtc.regs[NVRAM_CENTURY] = TOBCD((gnow->tm_year + 1900) / 100);
rtc.regs[MC_REGD] |= MC_REGD_VRT;
}
/*
* rtc_fire1
*
* Callback for the 1s periodic TOD refresh timer
*
* Parameters:
* fd: unused
* type: unused
* arg: unused
*/
static void
rtc_fire1(int fd, short type, void *arg)
{
time_t old = rtc.now;
time(&rtc.now);
rtc_updateregs();
if (rtc.now - old > 5) {
log_debug("%s: RTC clock drift (%llds), requesting guest "
"resync", __func__, (rtc.now - old));
vmmci_ctl(VMMCI_SYNCRTC);
}
evtimer_add(&rtc.sec, &rtc.sec_tv);
}
/*
* rtc_fireper
*
* Callback for the periodic interrupt timer
*
* Parameters:
* fd: unused
* type: unused
* arg: (as uint32_t), VM ID to which this RTC belongs
*/
static void
rtc_fireper(int fd, short type, void *arg)
{
rtc.regs[MC_REGC] |= MC_REGC_PF;
vcpu_assert_pic_irq((ptrdiff_t)arg, 0, 8);
vcpu_deassert_pic_irq((ptrdiff_t)arg, 0, 8);
evtimer_add(&rtc.per, &rtc.per_tv);
}
/*
* mc146818_init
*
* Initializes the emulated RTC/NVRAM
*
* Parameters:
* vm_id: VM ID to which this RTC belongs
* memlo: size of memory in bytes between 16MB .. 4GB
* memhi: size of memory in bytes after 4GB
*/
void
mc146818_init(uint32_t vm_id, uint64_t memlo, uint64_t memhi)
{
memset(&rtc, 0, sizeof(rtc));
time(&rtc.now);
rtc.regs[MC_REGB] = MC_REGB_24HR;
memlo /= 65536;
memhi /= 65536;
rtc.regs[NVRAM_MEMSIZE_HI] = (memlo >> 8) & 0xFF;
rtc.regs[NVRAM_MEMSIZE_LO] = memlo & 0xFF;
rtc.regs[NVRAM_HIMEMSIZE_HI] = (memhi >> 16) & 0xFF;
rtc.regs[NVRAM_HIMEMSIZE_MID] = (memhi >> 8) & 0xFF;
rtc.regs[NVRAM_HIMEMSIZE_LO] = memhi & 0xFF;
rtc.regs[NVRAM_SMP_COUNT] = 0;
rtc_updateregs();
rtc.vm_id = vm_id;
timerclear(&rtc.sec_tv);
rtc.sec_tv.tv_sec = 1;
timerclear(&rtc.per_tv);
evtimer_set(&rtc.sec, rtc_fire1, NULL);
evtimer_add(&rtc.sec, &rtc.sec_tv);
evtimer_set(&rtc.per, rtc_fireper, (void *)(intptr_t)rtc.vm_id);
vm_pipe_init(&dev_pipe, mc146818_pipe_dispatch);
event_add(&dev_pipe.read_ev, NULL);
}
/*
* rtc_reschedule_per
*
* Reschedule the periodic interrupt firing rate, based on the currently
* selected REGB values.
*/
static void
rtc_reschedule_per(void)
{
uint16_t rate;
uint64_t us;
if (rtc.regs[MC_REGB] & MC_REGB_PIE) {
rate = 32768 >> ((rtc.regs[MC_REGA] & MC_RATE_MASK) - 1);
us = (1.0 / rate) * 1000000;
rtc.per_tv.tv_usec = us;
if (evtimer_pending(&rtc.per, NULL))
evtimer_del(&rtc.per);
evtimer_add(&rtc.per, &rtc.per_tv);
}
}
/*
* rtc_update_rega
*
* Updates the RTC's REGA register
*
* Parameters:
* data: REGA register data
*/
static void
rtc_update_rega(uint32_t data)
{
rtc.regs[MC_REGA] = data;
if ((rtc.regs[MC_REGA] ^ data) & 0x0f)
vm_pipe_send(&dev_pipe, MC146818_RESCHEDULE_PER);
}
/*
* rtc_update_regb
*
* Updates the RTC's REGB register
*
* Parameters:
* data: REGB register data
*/
static void
rtc_update_regb(uint32_t data)
{
if (data & MC_REGB_DSE)
log_warnx("%s: DSE mode not supported", __func__);
if (!(data & MC_REGB_24HR))
log_warnx("%s: 12 hour mode not supported", __func__);
rtc.regs[MC_REGB] = data;
if (data & MC_REGB_PIE)
vm_pipe_send(&dev_pipe, MC146818_RESCHEDULE_PER);
}
/*
* vcpu_exit_mc146818
*
* Handles emulated MC146818 RTC access (in/out instruction to RTC ports).
*
* Parameters:
* vrp: vm run parameters containing exit information for the I/O
* instruction being performed
*
* Return value:
* Interrupt to inject to the guest VM, or 0xFF if no interrupt should
* be injected.
*/
uint8_t
vcpu_exit_mc146818(struct vm_run_params *vrp)
{
struct vm_exit *vei = vrp->vrp_exit;
uint16_t port = vei->vei.vei_port;
uint8_t dir = vei->vei.vei_dir;
uint32_t data = 0;
get_input_data(vei, &data);
if (port == IO_RTC) {
/* Discard NMI bit */
if (data & 0x80)
data &= ~0x80;
if (dir == 0) {
if (data < (NVRAM_SIZE))
rtc.idx = data;
else
rtc.idx = MC_REGD;
} else
set_return_data(vei, rtc.idx);
} else if (port == IO_RTC + 1) {
if (dir == 0) {
switch (rtc.idx) {
case MC_SEC ... MC_YEAR:
case MC_NVRAM_START ... MC_NVRAM_START + MC_NVRAM_SIZE:
rtc.regs[rtc.idx] = data;
break;
case MC_REGA:
rtc_update_rega(data);
break;
case MC_REGB:
rtc_update_regb(data);
break;
case MC_REGC:
case MC_REGD:
log_warnx("%s: mc146818 illegal write "
"of reg 0x%x", __func__, rtc.idx);
break;
default:
log_warnx("%s: mc146818 illegal reg %x\n",
__func__, rtc.idx);
}
} else {
data = rtc.regs[rtc.idx];
set_return_data(vei, data);
if (rtc.idx == MC_REGC) {
/* Reset IRQ state */
rtc.regs[MC_REGC] &= ~MC_REGC_PF;
}
}
} else {
log_warnx("%s: mc146818 unknown port 0x%x",
__func__, vei->vei.vei_port);
}
return 0xFF;
}
int
mc146818_dump(int fd)
{
log_debug("%s: sending RTC", __func__);
if (atomicio(vwrite, fd, &rtc, sizeof(rtc)) != sizeof(rtc)) {
log_warnx("%s: error writing RTC to fd", __func__);
return (-1);
}
return (0);
}
int
mc146818_restore(int fd, uint32_t vm_id)
{
log_debug("%s: restoring RTC", __func__);
if (atomicio(read, fd, &rtc, sizeof(rtc)) != sizeof(rtc)) {
log_warnx("%s: error reading RTC from fd", __func__);
return (-1);
}
rtc.vm_id = vm_id;
memset(&rtc.sec, 0, sizeof(struct event));
memset(&rtc.per, 0, sizeof(struct event));
evtimer_set(&rtc.sec, rtc_fire1, NULL);
evtimer_set(&rtc.per, rtc_fireper, (void *)(intptr_t)rtc.vm_id);
vm_pipe_init(&dev_pipe, mc146818_pipe_dispatch);
return (0);
}
void
mc146818_stop()
{
evtimer_del(&rtc.per);
evtimer_del(&rtc.sec);
event_del(&dev_pipe.read_ev);
}
void
mc146818_start()
{
evtimer_add(&rtc.sec, &rtc.sec_tv);
event_add(&dev_pipe.read_ev, NULL);
rtc_reschedule_per();
}