#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: qemu.c,v 1.6 2023/08/01 20:09:12 andvar Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/time.h>
#include <sys/timetc.h>
#include <sys/kernel.h>
#include <sys/cpu.h>
#include <machine/autoconf.h>
#include <machine/cpuconf.h>
#include <machine/rpb.h>
#include <machine/alpha.h>
#include <alpha/alpha/clockvar.h>
extern struct cfdriver qemu_cd;
struct qemu_softc {
device_t sc_dev;
struct timecounter sc_tc;
};
static unsigned long qemu_nsec_per_tick __read_mostly = (unsigned long)-1;
static inline unsigned long
qemu_get_vmtime(void)
{
register unsigned long v0 __asm("$0");
register unsigned long a0 __asm("$16") = 7;
__asm volatile ("call_pal %2"
: "=r"(v0), "+r"(a0)
: "i"(PAL_cserve)
: "$17", "$18", "$19", "$20", "$21");
return v0;
}
static void
qemu_delay(unsigned long usec)
{
const unsigned long base = qemu_get_vmtime();
const unsigned long nsec = usec * 1000;
KASSERT(nsec > usec);
const unsigned long finished = base + nsec;
KASSERT(finished > base);
unsigned long now;
while ((now = qemu_get_vmtime()) < finished) {
if (finished - now > qemu_nsec_per_tick) {
alpha_pal_wtint(0);
}
}
}
static u_int
qemu_get_timecount(struct timecounter * const tc __unused)
{
return (u_int)qemu_get_vmtime();
}
static inline void
qemu_set_alarm_relative(unsigned long nsec)
{
register unsigned long a0 __asm("$16") = 5;
register unsigned long a1 __asm("$17") = nsec;
__asm volatile ("call_pal %2"
: "+r"(a0), "+r"(a1)
: "i"(PAL_cserve)
: "$0", "$18", "$19", "$20", "$21");
}
static void
qemu_hardclock(struct clockframe * const framep)
{
if (__predict_false(qemu_nsec_per_tick == (unsigned long)-1)) {
return;
}
qemu_set_alarm_relative(qemu_nsec_per_tick);
hardclock(framep);
}
static void
qemu_clock_init(void * const v __unused)
{
if (qemu_nsec_per_tick == (unsigned long)-1) {
KASSERT(CPU_IS_PRIMARY(curcpu()));
platform.clockintr = qemu_hardclock;
hz = 50;
schedhz = 0;
qemu_nsec_per_tick = 1000000000UL / hz;
printf("Using the Qemu CPU alarm for %d Hz hardclock.\n", hz);
}
qemu_set_alarm_relative(qemu_nsec_per_tick);
}
static int
qemu_match(device_t parent, cfdata_t cfdata, void *aux)
{
struct mainbus_attach_args *ma = aux;
if (strcmp(ma->ma_name, qemu_cd.cd_name) != 0)
return (0);
return (1);
}
static void
qemu_attach(device_t parent, device_t self, void *aux)
{
struct qemu_softc * const sc = device_private(self);
struct timecounter * const tc = &sc->sc_tc;
sc->sc_dev = self;
aprint_normal(": Qemu virtual machine services\n");
aprint_naive("\n");
tc->tc_name = "Qemu";
tc->tc_get_timecount = qemu_get_timecount;
tc->tc_quality = 3000;
tc->tc_counter_mask = __BITS(0,31);
tc->tc_frequency = 1000000000UL;
tc->tc_priv = sc;
tc_init(tc);
clockattach(qemu_clock_init, sc);
cpu_idle_fn = cpu_idle_wtint;
alpha_delay_fn = qemu_delay;
}
CFATTACH_DECL_NEW(qemu, sizeof(struct qemu_softc),
qemu_match, qemu_attach, NULL, NULL);