#include <sys/cdefs.h>
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/rman.h>
#include <sys/sysctl.h>
#include <sys/time.h>
#include <sys/timeet.h>
#include <sys/timetc.h>
#include <machine/bus.h>
#include <machine/machdep.h>
#include <machine/vmm.h>
#include <machine/armreg.h>
#include <arm64/vmm/arm64.h>
#include "vgic.h"
#include "vtimer.h"
#define RES1 0xffffffffffffffffUL
#define timer_enabled(ctl) \
(!((ctl) & CNTP_CTL_IMASK) && ((ctl) & CNTP_CTL_ENABLE))
static uint32_t tmr_frq;
#define timer_condition_met(ctl) ((ctl) & CNTP_CTL_ISTATUS)
SYSCTL_DECL(_hw_vmm);
SYSCTL_NODE(_hw_vmm, OID_AUTO, vtimer, CTLFLAG_RW, NULL, NULL);
static bool allow_ecv_phys = false;
SYSCTL_BOOL(_hw_vmm_vtimer, OID_AUTO, allow_ecv_phys, CTLFLAG_RW,
&allow_ecv_phys, 0,
"Enable hardware access to the physical timer if FEAT_ECV_POFF is supported");
static void vtimer_schedule_irq(struct hypctx *hypctx, bool phys);
static int
vtimer_virtual_timer_intr(void *arg)
{
struct hypctx *hypctx;
uint64_t cntpct_el0;
uint32_t cntv_ctl;
hypctx = arm64_get_active_vcpu();
cntv_ctl = READ_SPECIALREG(cntv_ctl_el0);
if (!hypctx) {
eprintf("No active vcpu\n");
cntv_ctl = READ_SPECIALREG(cntv_ctl_el0);
goto out;
}
if (!timer_enabled(cntv_ctl)) {
eprintf("Timer not enabled\n");
goto out;
}
if (!timer_condition_met(cntv_ctl)) {
eprintf("Timer condition not met\n");
goto out;
}
cntpct_el0 = READ_SPECIALREG(cntpct_el0) -
hypctx->hyp->vtimer.cntvoff_el2;
if (hypctx->vtimer_cpu.virt_timer.cntx_cval_el0 < cntpct_el0)
vgic_inject_irq(hypctx->hyp, vcpu_vcpuid(hypctx->vcpu),
GT_VIRT_IRQ, true);
cntv_ctl = hypctx->vtimer_cpu.virt_timer.cntx_ctl_el0;
out:
cntv_ctl &= ~CNTP_CTL_ENABLE;
WRITE_SPECIALREG(cntv_ctl_el0, cntv_ctl);
return (FILTER_HANDLED);
}
int
vtimer_init(void)
{
tmr_frq = READ_SPECIALREG(cntfrq_el0);
return (0);
}
void
vtimer_vminit(struct hyp *hyp)
{
uint64_t now;
bool ecv_poff;
ecv_poff = false;
if (allow_ecv_phys && (hyp->feats & HYP_FEAT_ECV_POFF) != 0)
ecv_poff = true;
if (in_vhe()) {
hyp->vtimer.cnthctl_el2 =
CNTHCTL_E2H_EL0VCTEN_NOTRAP |
CNTHCTL_E2H_EL0VTEN_NOTRAP;
if (ecv_poff) {
hyp->vtimer.cnthctl_el2 |=
CNTHCTL_E2H_EL0PCTEN_NOTRAP |
CNTHCTL_E2H_EL0PTEN_NOTRAP |
CNTHCTL_E2H_EL1PCTEN_NOTRAP |
CNTHCTL_E2H_EL1PTEN_NOTRAP;
} else {
hyp->vtimer.cnthctl_el2 |=
CNTHCTL_E2H_EL0PCTEN_TRAP |
CNTHCTL_E2H_EL0PTEN_TRAP |
CNTHCTL_E2H_EL1PCTEN_TRAP |
CNTHCTL_E2H_EL1PTEN_TRAP;
}
} else {
if (ecv_poff) {
hyp->vtimer.cnthctl_el2 =
CNTHCTL_EL1PCTEN_NOTRAP |
CNTHCTL_EL1PCEN_NOTRAP;
} else {
hyp->vtimer.cnthctl_el2 =
CNTHCTL_EL1PCTEN_TRAP |
CNTHCTL_EL1PCEN_TRAP;
}
}
if (ecv_poff)
hyp->vtimer.cnthctl_el2 |= CNTHCTL_ECV_EN;
now = READ_SPECIALREG(cntpct_el0);
hyp->vtimer.cntvoff_el2 = now;
return;
}
void
vtimer_cpuinit(struct hypctx *hypctx)
{
struct vtimer_cpu *vtimer_cpu;
vtimer_cpu = &hypctx->vtimer_cpu;
vtimer_cpu->phys_timer.cntx_ctl_el0 = CNTP_CTL_IMASK & ~CNTP_CTL_ENABLE;
mtx_init(&vtimer_cpu->phys_timer.mtx, "vtimer phys callout mutex", NULL,
MTX_DEF);
callout_init_mtx(&vtimer_cpu->phys_timer.callout,
&vtimer_cpu->phys_timer.mtx, 0);
vtimer_cpu->phys_timer.irqid = GT_PHYS_NS_IRQ;
mtx_init(&vtimer_cpu->virt_timer.mtx, "vtimer virt callout mutex", NULL,
MTX_DEF);
callout_init_mtx(&vtimer_cpu->virt_timer.callout,
&vtimer_cpu->virt_timer.mtx, 0);
vtimer_cpu->virt_timer.irqid = GT_VIRT_IRQ;
}
void
vtimer_cpucleanup(struct hypctx *hypctx)
{
struct vtimer_cpu *vtimer_cpu;
vtimer_cpu = &hypctx->vtimer_cpu;
callout_drain(&vtimer_cpu->phys_timer.callout);
callout_drain(&vtimer_cpu->virt_timer.callout);
mtx_destroy(&vtimer_cpu->phys_timer.mtx);
mtx_destroy(&vtimer_cpu->virt_timer.mtx);
}
void
vtimer_vmcleanup(struct hyp *hyp)
{
struct hypctx *hypctx;
uint32_t cntv_ctl;
hypctx = arm64_get_active_vcpu();
if (!hypctx) {
cntv_ctl = READ_SPECIALREG(cntv_ctl_el0);
cntv_ctl &= ~CNTP_CTL_ENABLE;
WRITE_SPECIALREG(cntv_ctl_el0, cntv_ctl);
}
}
void
vtimer_cleanup(void)
{
}
static void
vtime_sync_timer(struct hypctx *hypctx, struct vtimer_timer *timer,
uint64_t cntpct_el0)
{
if (!timer_enabled(timer->cntx_ctl_el0)) {
vgic_inject_irq(hypctx->hyp, vcpu_vcpuid(hypctx->vcpu),
timer->irqid, false);
} else if (timer->cntx_cval_el0 < cntpct_el0) {
vgic_inject_irq(hypctx->hyp, vcpu_vcpuid(hypctx->vcpu),
timer->irqid, true);
} else {
vgic_inject_irq(hypctx->hyp, vcpu_vcpuid(hypctx->vcpu),
timer->irqid, false);
vtimer_schedule_irq(hypctx, false);
}
}
void
vtimer_sync_hwstate(struct hypctx *hypctx)
{
uint64_t cntpct_el0;
cntpct_el0 = READ_SPECIALREG(cntpct_el0) -
hypctx->hyp->vtimer.cntvoff_el2;
vtime_sync_timer(hypctx, &hypctx->vtimer_cpu.virt_timer, cntpct_el0);
if ((hypctx->hyp->vtimer.cnthctl_el2 & CNTHCTL_ECV_EN) != 0) {
vtime_sync_timer(hypctx, &hypctx->vtimer_cpu.phys_timer,
cntpct_el0);
}
}
static void
vtimer_inject_irq_callout_phys(void *context)
{
struct hypctx *hypctx;
hypctx = context;
vgic_inject_irq(hypctx->hyp, vcpu_vcpuid(hypctx->vcpu),
hypctx->vtimer_cpu.phys_timer.irqid, true);
}
static void
vtimer_inject_irq_callout_virt(void *context)
{
struct hypctx *hypctx;
hypctx = context;
vgic_inject_irq(hypctx->hyp, vcpu_vcpuid(hypctx->vcpu),
hypctx->vtimer_cpu.virt_timer.irqid, true);
}
static void
vtimer_schedule_irq(struct hypctx *hypctx, bool phys)
{
sbintime_t time;
struct vtimer_timer *timer;
uint64_t cntpct_el0;
uint64_t diff;
if (phys)
timer = &hypctx->vtimer_cpu.phys_timer;
else
timer = &hypctx->vtimer_cpu.virt_timer;
cntpct_el0 = READ_SPECIALREG(cntpct_el0) -
hypctx->hyp->vtimer.cntvoff_el2;
if (timer->cntx_cval_el0 < cntpct_el0) {
vgic_inject_irq(hypctx->hyp, vcpu_vcpuid(hypctx->vcpu),
timer->irqid, true);
} else {
diff = timer->cntx_cval_el0 - cntpct_el0;
time = diff * SBT_1S / tmr_frq;
if (phys)
callout_reset_sbt(&timer->callout, time, 0,
vtimer_inject_irq_callout_phys, hypctx, 0);
else
callout_reset_sbt(&timer->callout, time, 0,
vtimer_inject_irq_callout_virt, hypctx, 0);
}
}
static void
vtimer_remove_irq(struct hypctx *hypctx, struct vcpu *vcpu)
{
struct vtimer_cpu *vtimer_cpu;
struct vtimer_timer *timer;
vtimer_cpu = &hypctx->vtimer_cpu;
timer = &vtimer_cpu->phys_timer;
callout_drain(&timer->callout);
vgic_inject_irq(hypctx->hyp, vcpu_vcpuid(vcpu), timer->irqid, false);
}
int
vtimer_phys_ctl_read(struct vcpu *vcpu, uint64_t *rval, void *arg)
{
struct hyp *hyp;
struct hypctx *hypctx;
struct vtimer_cpu *vtimer_cpu;
uint64_t cntpct_el0;
hypctx = vcpu_get_cookie(vcpu);
hyp = hypctx->hyp;
vtimer_cpu = &hypctx->vtimer_cpu;
cntpct_el0 = READ_SPECIALREG(cntpct_el0) - hyp->vtimer.cntvoff_el2;
if (vtimer_cpu->phys_timer.cntx_cval_el0 < cntpct_el0)
*rval = vtimer_cpu->phys_timer.cntx_ctl_el0 | CNTP_CTL_ISTATUS;
else
*rval = vtimer_cpu->phys_timer.cntx_ctl_el0 & ~CNTP_CTL_ISTATUS;
return (0);
}
int
vtimer_phys_ctl_write(struct vcpu *vcpu, uint64_t wval, void *arg)
{
struct hypctx *hypctx;
struct vtimer_cpu *vtimer_cpu;
uint64_t ctl_el0;
bool timer_toggled_on;
hypctx = vcpu_get_cookie(vcpu);
vtimer_cpu = &hypctx->vtimer_cpu;
timer_toggled_on = false;
ctl_el0 = vtimer_cpu->phys_timer.cntx_ctl_el0;
if (!timer_enabled(ctl_el0) && timer_enabled(wval))
timer_toggled_on = true;
else if (timer_enabled(ctl_el0) && !timer_enabled(wval))
vtimer_remove_irq(hypctx, vcpu);
vtimer_cpu->phys_timer.cntx_ctl_el0 = wval;
if (timer_toggled_on)
vtimer_schedule_irq(hypctx, true);
return (0);
}
int
vtimer_phys_cnt_read(struct vcpu *vcpu, uint64_t *rval, void *arg)
{
struct vm *vm;
struct hyp *hyp;
vm = vcpu_vm(vcpu);
hyp = vm_get_cookie(vm);
*rval = READ_SPECIALREG(cntpct_el0) - hyp->vtimer.cntvoff_el2;
return (0);
}
int
vtimer_phys_cnt_write(struct vcpu *vcpu, uint64_t wval, void *arg)
{
return (0);
}
int
vtimer_phys_cval_read(struct vcpu *vcpu, uint64_t *rval, void *arg)
{
struct hypctx *hypctx;
struct vtimer_cpu *vtimer_cpu;
hypctx = vcpu_get_cookie(vcpu);
vtimer_cpu = &hypctx->vtimer_cpu;
*rval = vtimer_cpu->phys_timer.cntx_cval_el0;
return (0);
}
int
vtimer_phys_cval_write(struct vcpu *vcpu, uint64_t wval, void *arg)
{
struct hypctx *hypctx;
struct vtimer_cpu *vtimer_cpu;
hypctx = vcpu_get_cookie(vcpu);
vtimer_cpu = &hypctx->vtimer_cpu;
vtimer_cpu->phys_timer.cntx_cval_el0 = wval;
vtimer_remove_irq(hypctx, vcpu);
if (timer_enabled(vtimer_cpu->phys_timer.cntx_ctl_el0)) {
vtimer_schedule_irq(hypctx, true);
}
return (0);
}
int
vtimer_phys_tval_read(struct vcpu *vcpu, uint64_t *rval, void *arg)
{
struct hyp *hyp;
struct hypctx *hypctx;
struct vtimer_cpu *vtimer_cpu;
uint32_t cntpct_el0;
hypctx = vcpu_get_cookie(vcpu);
hyp = hypctx->hyp;
vtimer_cpu = &hypctx->vtimer_cpu;
if (!(vtimer_cpu->phys_timer.cntx_ctl_el0 & CNTP_CTL_ENABLE)) {
*rval = (uint32_t)RES1;
} else {
cntpct_el0 = READ_SPECIALREG(cntpct_el0) -
hyp->vtimer.cntvoff_el2;
*rval = vtimer_cpu->phys_timer.cntx_cval_el0 - cntpct_el0;
}
return (0);
}
int
vtimer_phys_tval_write(struct vcpu *vcpu, uint64_t wval, void *arg)
{
struct hyp *hyp;
struct hypctx *hypctx;
struct vtimer_cpu *vtimer_cpu;
uint64_t cntpct_el0;
hypctx = vcpu_get_cookie(vcpu);
hyp = hypctx->hyp;
vtimer_cpu = &hypctx->vtimer_cpu;
cntpct_el0 = READ_SPECIALREG(cntpct_el0) - hyp->vtimer.cntvoff_el2;
vtimer_cpu->phys_timer.cntx_cval_el0 = (int32_t)wval + cntpct_el0;
vtimer_remove_irq(hypctx, vcpu);
if (timer_enabled(vtimer_cpu->phys_timer.cntx_ctl_el0)) {
vtimer_schedule_irq(hypctx, true);
}
return (0);
}
struct vtimer_softc {
struct resource *res;
void *ihl;
int rid;
};
static int
vtimer_probe(device_t dev)
{
device_set_desc(dev, "Virtual timer");
return (BUS_PROBE_DEFAULT);
}
static int
vtimer_attach(device_t dev)
{
struct vtimer_softc *sc;
sc = device_get_softc(dev);
sc->rid = 0;
sc->res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->rid, RF_ACTIVE);
if (sc->res == NULL)
return (ENXIO);
bus_setup_intr(dev, sc->res, INTR_TYPE_CLK, vtimer_virtual_timer_intr,
NULL, NULL, &sc->ihl);
return (0);
}
static device_method_t vtimer_methods[] = {
DEVMETHOD(device_probe, vtimer_probe),
DEVMETHOD(device_attach, vtimer_attach),
DEVMETHOD_END
};
DEFINE_CLASS_0(vtimer, vtimer_driver, vtimer_methods,
sizeof(struct vtimer_softc));
DRIVER_MODULE(vtimer, generic_timer, vtimer_driver, 0, 0);