[BACK]Return to mc146818.c CVS log [TXT][DIR] Up to [local] / src / usr.sbin / vmd

File: [local] / src / usr.sbin / vmd / mc146818.c (download)

Revision 1.27, Wed Oct 25 12:44:28 2023 UTC (7 months, 1 week ago) by dv
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, HEAD
Changes since 1.26: +1 -2 lines

vmd(8): stop toggling irq deassert for edge triggered devs.

For edge-triggered devices, there's no need to deassert an irq given
how vmd(8) emulates a pic.  Deassertion grabs a lock and can trigger
the ioctl for toggling pending interrupts causing a race condition.
This results in a storm of vm-exits and guest vcpu becoming
unresponsive.

The original sign of this issue is guest "pauses" when pasting text
into a serial console connection in something like xterm(1).

Tested by mbuhl@, cheloha@, sashan@, kn@, and mlarkin@.

"go for it", mlarkin@

/* $OpenBSD: mc146818.c,v 1.27 2023/10/25 12:44:28 dv 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);

	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(void)
{
	evtimer_del(&rtc.per);
	evtimer_del(&rtc.sec);
	event_del(&dev_pipe.read_ev);
}

void
mc146818_start(void)
{
	evtimer_add(&rtc.sec, &rtc.sec_tv);
	event_add(&dev_pipe.read_ev, NULL);
	rtc_reschedule_per();
}