acrn-hypervisor/devicemodel/hw/platform/hpet.c
Wu Zhou 1407dd3738 dm: change timers to MONOTONIC mode
Some DM's virtual timer devices use CLOCK_REALTIME as either clock
counter source or period timer source. Including:
  - virtual RTC
  - virtual PIT
  - virtual HPET

According to Linux Manual, CLOCK_REALTIME is the 'wall clock' which is
affected by discontinuous jumps in the system time.

The issue is that service VM system time could be changed, either by
root user manually or by NTP automatically calibration.
When that happens, DM's virtual timer devices which relays on
CLOCK_REALTIME will experience discontinuous time jump, and become
inaccurate. It would affect both time stamp read value and period timer.
Especially when service VM system time is moved backwards, WaaG's system
software will lost response and be stalled for quite a long time.

To solve this issue, we need to switch CLOCK_REALTIME to
CLOCK_MONOTONIC. As it represents:
'A nonsettable monotonically increasing clock that measures time from
some unspecified point in the past that does not change after system
startup'

Tracked-On: #8547
Signed-off-by: Wu Zhou <wu.zhou@intel.com>
Reviewed-by: Jian Jun Chen <jian.jun.chen@intel.com>
2024-02-01 17:01:31 +08:00

1049 lines
26 KiB
C

/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2019-2022 Intel Corporation.
* Copyright (c) 2013 Tycho Nightingale <tycho.nightingale@pluribusnetworks.com>
* Copyright (c) 2013 Neel Natu <neel@freebsd.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <pthread.h>
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <err.h>
#include <sysexits.h>
#include <string.h>
#include <unistd.h>
#include "vmmapi.h"
#include "mem.h"
#include "timer.h"
#include "hpet.h"
#include "acpi_hpet.h"
#include "log.h"
#define HPET_FREQ (16777216) /* 16.7 (2^24) Mhz */
#define FS_PER_S (1000000000000000UL)
/* Timer N Configuration and Capabilities Register */
#define HPET_TCAP_RO_MASK (HPET_TCAP_INT_ROUTE | \
HPET_TCAP_FSB_INT_DEL | \
HPET_TCAP_SIZE | \
HPET_TCAP_PER_INT)
/*
* HPET requires at least 3 timers and up to 32 timers per block.
*/
#define VHPET_NUM_TIMERS (8)
#define VHPET_LOCK() \
do { \
int err; \
err = pthread_mutex_lock(&vhpet_mtx); \
if (err) \
errx(EX_SOFTWARE, "pthread_mutex_lock returned %s", \
strerror(err)); \
} while (0)
#define VHPET_UNLOCK() \
do { \
int err; \
err = pthread_mutex_unlock(&vhpet_mtx); \
if (err) \
errx(EX_SOFTWARE, "pthread_mutex_unlock returned %s", \
strerror(err)); \
} while (0)
#define vhpet_ts_to_ticks(ts) ts_to_ticks(HPET_FREQ, ts)
/* won't overflow since the max value of ticks is 2^32 */
#define vhpet_ticks_to_ts(tk, ts) ticks_to_ts(HPET_FREQ, tk, ts)
#define vhpet_tmr(v, n) (&(v)->timer[n].tmrlst[(v)->timer[n].tmridx].t)
#define vhpet_tmrarg(v, n) (&(v)->timer[n].tmrlst[(v)->timer[n].tmridx].a)
#define ts_is_zero(ts) timespeccmp(ts, &zero_ts.it_value, ==)
#define ts_set_zero(ts) do { *(ts) = zero_ts.it_value; } while (0)
/*
* Debug printf
*/
static int hpet_debug;
#define DPRINTF(params) do { if (hpet_debug) pr_dbg params; } while (0)
#define WPRINTF(params) (pr_err params)
static struct mem_range vhpet_mr = {
.name = "vhpet",
.base = VHPET_BASE,
.size = VHPET_SIZE,
.flags = MEM_F_RW
};
struct vhpet_timer_arg {
struct vhpet *vhpet;
int timer_num;
bool running;
};
struct vhpet {
struct vmctx *vm;
bool inited;
uint64_t config; /* Configuration */
uint64_t isr; /* Interrupt Status */
uint32_t countbase; /* HPET counter base value */
struct timespec countbase_ts; /* uptime corresponding to base value */
struct {
uint64_t cap_config; /* Configuration */
uint64_t msireg; /* FSB interrupt routing */
uint32_t compval; /* Comparator */
uint32_t comprate;
struct timespec expts; /* time when counter==compval */
struct {
struct acrn_timer t;
struct vhpet_timer_arg a;
} tmrlst[3];
int tmridx;
} timer[VHPET_NUM_TIMERS];
};
/* one vHPET per VM */
static pthread_mutex_t vhpet_mtx = PTHREAD_MUTEX_INITIALIZER;
static const struct itimerspec zero_ts = { 0 };
/* one vHPET per VM */
static struct vhpet *
vhpet_instance(void)
{
static struct vhpet __vhpet;
return &__vhpet;
}
const uint64_t
vhpet_capabilities(void)
{
static uint64_t cap = 0;
if (cap == 0) {
cap |= 0x8086 << 16; /* vendor id */
cap |= (VHPET_NUM_TIMERS - 1) << 8; /* number of timers */
cap |= 1; /* revision */
cap &= ~HPET_CAP_COUNT_SIZE; /* 32-bit timer */
cap &= 0xffffffff;
cap |= (FS_PER_S / HPET_FREQ) << 32; /* tick period in fs */
}
return cap;
}
static inline bool
vhpet_counter_enabled(struct vhpet *vhpet)
{
return ((vhpet->config & HPET_CNF_ENABLE) != 0);
}
static inline bool
vhpet_timer_msi_enabled(struct vhpet *vhpet, int n)
{
const uint64_t msi_enable = HPET_TCAP_FSB_INT_DEL | HPET_TCNF_FSB_EN;
return ((vhpet->timer[n].cap_config & msi_enable) == msi_enable);
}
static inline int
vhpet_timer_ioapic_pin(struct vhpet *vhpet, int n)
{
/*
* If the timer is configured to use MSI then treat it as if the
* timer is not connected to the ioapic.
*/
if (vhpet_timer_msi_enabled(vhpet, n))
return 0;
return ((vhpet->timer[n].cap_config & HPET_TCNF_INT_ROUTE) >> 9);
}
static uint32_t
vhpet_counter(struct vhpet *vhpet, struct timespec *nowptr)
{
uint32_t val;
struct timespec now, delta;
val = vhpet->countbase;
if (vhpet_counter_enabled(vhpet)) {
if (clock_gettime(CLOCK_MONOTONIC, &now))
pr_dbg("clock_gettime returned: %s", strerror(errno));
/* delta = now - countbase_ts */
if (timespeccmp(&now, &vhpet->countbase_ts, <)) {
pr_dbg("vhpet counter going backwards");
vhpet->countbase_ts = now;
}
delta = now;
timespecsub(&delta, &vhpet->countbase_ts);
val += vhpet_ts_to_ticks(&delta);
if (nowptr != NULL)
*nowptr = now;
} else {
/*
* The timespec corresponding to the 'countbase' is
* meaningless when the counter is disabled. Warn if
* the caller wants to use it.
*/
if (nowptr) {
pr_warn("vhpet unexpected nowptr");
if (clock_gettime(CLOCK_MONOTONIC, nowptr))
pr_dbg("clock_gettime returned: %s", strerror(errno));
}
}
return val;
}
static void
vhpet_timer_clear_isr(struct vhpet *vhpet, int n)
{
int pin;
if (vhpet->isr & (1 << n)) {
pin = vhpet_timer_ioapic_pin(vhpet, n);
if (pin)
vm_set_gsi_irq(vhpet->vm, pin, GSI_SET_LOW);
else
pr_dbg("vhpet t%d intr asserted without a valid intr route", n);
vhpet->isr &= ~(1 << n);
}
}
static inline bool
vhpet_periodic_timer(struct vhpet *vhpet, int n)
{
return ((vhpet->timer[n].cap_config & HPET_TCNF_TYPE) != 0);
}
static inline bool
vhpet_timer_interrupt_enabled(struct vhpet *vhpet, int n)
{
return ((vhpet->timer[n].cap_config & HPET_TCNF_INT_ENB) != 0);
}
static inline bool
vhpet_timer_enabled(struct vhpet *vhpet, int n)
{
/* The timer is enabled when at least one of the two bits is set */
return vhpet_timer_interrupt_enabled(vhpet, n) ||
vhpet_periodic_timer(vhpet, n);
}
static inline bool
vhpet_timer_running(struct vhpet *vhpet, int n)
{
return vhpet_tmrarg(vhpet, n)->running;
}
static inline bool
vhpet_timer_edge_trig(struct vhpet *vhpet, int n)
{
return (!vhpet_timer_msi_enabled(vhpet, n) &&
(vhpet->timer[n].cap_config & HPET_TCNF_INT_TYPE) == 0);
}
static void
vhpet_timer_interrupt(struct vhpet *vhpet, int n)
{
int pin;
/* If interrupts are not enabled for this timer then just return. */
if (!vhpet_timer_interrupt_enabled(vhpet, n))
return;
/*
* If a level triggered interrupt is already asserted then just return.
*/
if (vhpet->isr & (1 << n)) {
if (!vhpet_timer_msi_enabled(vhpet, n) &&
!vhpet_timer_edge_trig(vhpet, n)) {
DPRINTF(("hpet t%d intr is already asserted\n", n));
return;
} else {
pr_dbg("vhpet t%d intr asserted in %s mode", n,
vhpet_timer_msi_enabled(vhpet, n) ?
"msi" : "edge-triggered");
vhpet->isr &= ~(1 << n);
}
}
if (vhpet_timer_msi_enabled(vhpet, n)) {
vm_lapic_msi(vhpet->vm, vhpet->timer[n].msireg >> 32,
vhpet->timer[n].msireg & 0xffffffff);
return;
}
pin = vhpet_timer_ioapic_pin(vhpet, n);
if (pin == 0) {
DPRINTF(("hpet t%d intr is not routed to ioapic\n", n));
return;
}
if (vhpet_timer_edge_trig(vhpet, n)) {
vm_set_gsi_irq(vhpet->vm, pin, GSI_RAISING_PULSE);
} else {
vhpet->isr |= 1 << n;
vm_set_gsi_irq(vhpet->vm, pin, GSI_SET_HIGH);
}
}
static void
vhpet_timer_handler(void *a, uint64_t nexp)
{
int n;
struct vhpet *vhpet;
struct vhpet_timer_arg *arg;
struct timespec now;
struct itimerspec tmrts;
uint64_t newexp;
arg = a;
vhpet = arg->vhpet;
n = arg->timer_num;
DPRINTF(("hpet t%d(%p) fired\n", n, arg));
VHPET_LOCK();
/* Bail if timer was destroyed */
if (!vhpet->inited)
goto done;
/* Bail if timer was stopped */
if (!arg->running) {
DPRINTF(("hpet t%d(%p) already stopped\n", n, arg));
if (!ts_is_zero(&vhpet->timer[n].expts)) {
pr_warn("vhpet t%d stopped with an expiration time", n);
ts_set_zero(&vhpet->timer[n].expts);
}
goto done;
} else if (arg != vhpet_tmrarg(vhpet, n)) {
pr_warn("vhpet t%d observes a stale timer arg", n);
goto done;
}
vhpet_timer_interrupt(vhpet, n);
if (clock_gettime(CLOCK_MONOTONIC, &now))
pr_dbg("clock_gettime returned: %s", strerror(errno));
if (acrn_timer_gettime(vhpet_tmr(vhpet, n), &tmrts))
pr_dbg("acrn_timer_gettime returned: %s", strerror(errno));
/* One-shot mode has a periodicity of 2^32 ticks */
if (ts_is_zero(&tmrts.it_interval))
pr_dbg("vhpet t%d has no periodicity", n);
/*
* The actual expiration time will be slightly later than expts.
*
* This may cause spurious interrupts when stopping a timer but
* at least interrupts won't be completely lost.
*/
timespecadd(&tmrts.it_value, &now);
vhpet->timer[n].expts = tmrts.it_value;
/*
* Catch any remaining expirations that happened after being
* last consumed by the mevent_dispatch thread.
*/
if (read(vhpet_tmr(vhpet, n)->fd, &newexp, sizeof(newexp)) > 0)
nexp += newexp;
/*
* Periodic timer updates 'compval' upon expiration.
* Try to keep 'compval' as up-to-date as possible.
*/
vhpet->timer[n].compval += nexp * vhpet->timer[n].comprate;
done:
VHPET_UNLOCK();
return;
}
static void
vhpet_adjust_compval(struct vhpet *vhpet, int n, const struct timespec *now)
{
uint32_t compval, comprate, compnext;
struct timespec delta;
uint64_t delta_ticks;
compval = vhpet->timer[n].compval;
comprate = vhpet->timer[n].comprate;
if (!comprate || timespeccmp(&vhpet->timer[n].expts, now, >=))
return;
/* delta = now - expts */
delta = *now;
timespecsub(&delta, &vhpet->timer[n].expts);
delta_ticks = vhpet_ts_to_ticks(&delta);
/*
* Calculate the comparator value to be used for the next periodic
* interrupt.
*
* In this scenario 'counter' is ahead of 'compval' by at least
* 'comprate'. To find the next value to program into the
* accumulator we divide 'delta_ticks', the number space between
* 'compval + comprate' and 'counter', into 'comprate' sized units.
* The 'compval' is rounded up such that it stays "ahead" of
* 'counter'.
*
* There's a slight chance read() in vhpet_timer_handler() had
* already accomplished some of this just prior to calling this
* function.
*/
compnext = compval + (delta_ticks / comprate + 1) * comprate;
vhpet->timer[n].compval = compnext;
}
static void
vhpet_stop_timer(struct vhpet *vhpet, int n, const struct timespec *now,
bool adj_compval)
{
struct vhpet_timer_arg *arg;
if (!vhpet_timer_running(vhpet, n))
return;
if (ts_is_zero(&vhpet->timer[n].expts))
pr_dbg("vhpet t%d is running without an expiration time", n);
DPRINTF(("hpet t%d stopped\n", n));
arg = vhpet_tmrarg(vhpet, n);
arg->running = false;
/* Cancel the existing timer */
if (acrn_timer_settime(vhpet_tmr(vhpet, n), &zero_ts))
pr_dbg("acrn_timer_settime returned: %s", strerror(errno));
if (++vhpet->timer[n].tmridx == nitems(vhpet->timer[n].tmrlst))
vhpet->timer[n].tmridx = 0;
if (vhpet_timer_running(vhpet, n)) {
pr_dbg("vhpet t%d timer %d is still running",
n, vhpet->timer[n].tmridx);
vhpet_stop_timer(vhpet, n, &zero_ts.it_value, false);
}
/*
* If the timer was scheduled to expire in the past but hasn't
* had a chance to execute yet then trigger the timer interrupt
* here. Failing to do so will result in a missed timer interrupt
* in the guest. This is especially bad in one-shot mode because
* the next interrupt has to wait for the counter to wrap around.
*/
if (!ts_is_zero(&vhpet->timer[n].expts)) {
if (timespeccmp(&vhpet->timer[n].expts, now, <)) {
DPRINTF(("hpet t%d interrupt triggered after "
"stopping timer\n", n));
if (adj_compval)
vhpet_adjust_compval(vhpet, n, now);
vhpet_timer_interrupt(vhpet, n);
}
ts_set_zero(&vhpet->timer[n].expts);
}
}
static void
vhpet_start_timer(struct vhpet *vhpet, int n, uint32_t counter,
const struct timespec *now, bool adj_compval)
{
struct itimerspec ts;
uint32_t delta;
struct vhpet_timer_arg *arg;
vhpet_stop_timer(vhpet, n, now, adj_compval);
DPRINTF(("hpet t%d started\n", n));
/*
* It is the guest's responsibility to make sure that the
* comparator value is not in the "past". The hardware
* doesn't have any belt-and-suspenders to deal with this
* so we don't either.
*/
delta = vhpet->timer[n].compval - counter;
vhpet_ticks_to_ts(delta, &ts.it_value);
timespecadd(&ts.it_value, now);
if (vhpet->timer[n].comprate != 0)
vhpet_ticks_to_ts(vhpet->timer[n].comprate, &ts.it_interval);
else /* It takes 2^32 ticks to wrap around */
vhpet_ticks_to_ts(1ULL << 32, &ts.it_interval);
arg = vhpet_tmrarg(vhpet, n);
arg->running = true;
/* Arm the new timer */
if (acrn_timer_settime_abs(vhpet_tmr(vhpet, n), &ts))
pr_dbg("acrn_timer_settime_abs returned: %s",
strerror(errno));
vhpet->timer[n].expts = ts.it_value;
}
static void
vhpet_restart_timer(struct vhpet *vhpet, int n, bool adj_compval)
{
uint32_t counter;
struct timespec now;
/*
* Restart the specified timer based on the current value of
* the main counter.
*/
counter = vhpet_counter(vhpet, &now);
vhpet_start_timer(vhpet, n, counter, &now, adj_compval);
}
static void
vhpet_start_counting(struct vhpet *vhpet)
{
int i;
if (clock_gettime(CLOCK_MONOTONIC, &vhpet->countbase_ts))
pr_dbg("clock_gettime returned: %s", strerror(errno));
/* Restart the timers based on the main counter base value */
for (i = 0; i < VHPET_NUM_TIMERS; i++) {
if (vhpet_timer_enabled(vhpet, i))
vhpet_start_timer(vhpet, i, vhpet->countbase,
&vhpet->countbase_ts, true);
else if (vhpet_timer_running(vhpet, i)) {
pr_dbg("vhpet t%d's timer is disabled but running", i);
vhpet_stop_timer(vhpet, i, &zero_ts.it_value, false);
}
}
}
static void
vhpet_stop_counting(struct vhpet *vhpet, uint32_t counter,
const struct timespec *now)
{
int i;
/* Update the main counter base value */
vhpet->countbase = counter;
for (i = 0; i < VHPET_NUM_TIMERS; i++) {
if (vhpet_timer_enabled(vhpet, i))
vhpet_stop_timer(vhpet, i, now, true);
else if (vhpet_timer_running(vhpet, i)) {
pr_dbg("vhpet t%d's timer is disabled but running", i);
vhpet_stop_timer(vhpet, i, &zero_ts.it_value, false);
}
}
}
static inline void
update_register(uint64_t *const regptr, const uint64_t data,
const uint64_t mask)
{
*regptr &= ~mask;
*regptr |= (data & mask);
}
static void
vhpet_timer_update_config(struct vhpet *vhpet, int n, uint64_t data,
uint64_t mask)
{
int old_pin, new_pin;
uint32_t allowed_irqs;
uint64_t oldval, newval;
struct timespec now;
if (vhpet_timer_msi_enabled(vhpet, n) ||
vhpet_timer_edge_trig(vhpet, n)) {
if (vhpet->isr & (1 << n)) {
pr_dbg("vhpet t%d intr asserted in %s mode", n,
vhpet_timer_msi_enabled(vhpet, n) ?
"msi" : "edge-triggered");
vhpet->isr &= ~(1 << n);
}
}
old_pin = vhpet_timer_ioapic_pin(vhpet, n);
oldval = vhpet->timer[n].cap_config;
newval = oldval;
update_register(&newval, data, mask);
newval &= ~(HPET_TCAP_RO_MASK | HPET_TCNF_32MODE);
newval |= oldval & HPET_TCAP_RO_MASK;
if (newval == oldval)
return;
vhpet->timer[n].cap_config = newval;
DPRINTF(("hpet t%d cap_config set to 0x%016lx\n", n, newval));
if ((oldval ^ newval) & (HPET_TCNF_TYPE | HPET_TCNF_INT_ENB)) {
if (!vhpet_periodic_timer(vhpet, n))
vhpet->timer[n].comprate = 0;
if (vhpet_counter_enabled(vhpet)) {
/*
* Stop the timer if both bits are now cleared
*
* Else, restart the timer if:
* - The timer was stopped, or
* - HPET_TCNF_TYPE is being toggled
*
* Else, no-op
* - Timer remains in periodic mode
*/
if (!vhpet_timer_enabled(vhpet, n)) {
if (clock_gettime(CLOCK_MONOTONIC, &now))
pr_dbg("clock_gettime returned: %s", strerror(errno));
vhpet_stop_timer(vhpet, n, &now, true);
} else if (!(oldval & (HPET_TCNF_TYPE | HPET_TCNF_INT_ENB)) ||
((oldval ^ newval) & HPET_TCNF_TYPE))
vhpet_restart_timer(vhpet, n, true);
}
}
/*
* Validate the interrupt routing in the HPET_TCNF_INT_ROUTE field.
* If it does not match the bits set in HPET_TCAP_INT_ROUTE then set
* it to the default value of 0.
*/
allowed_irqs = vhpet->timer[n].cap_config >> 32;
new_pin = vhpet_timer_ioapic_pin(vhpet, n);
if (new_pin != 0 && (allowed_irqs & (1 << new_pin)) == 0) {
WPRINTF(("hpet t%d configured invalid irq %d, "
"allowed_irqs 0x%08x\n", n, new_pin, allowed_irqs));
new_pin = 0;
vhpet->timer[n].cap_config &= ~HPET_TCNF_INT_ROUTE;
}
/*
* If the timer's ISR bit is set then clear it in the following cases:
* - interrupt is disabled
* - interrupt type is changed from level to edge or fsb.
* - interrupt routing is changed
*
* This is to ensure that this timer's level triggered interrupt does
* not remain asserted forever.
*/
if (vhpet->isr & (1 << n)) {
if (!old_pin) {
pr_dbg("vhpet t%d intr asserted without a valid intr route", n);
vhpet->isr &= ~(1 << n);
} else if (!vhpet_timer_interrupt_enabled(vhpet, n) ||
vhpet_timer_msi_enabled(vhpet, n) ||
vhpet_timer_edge_trig(vhpet, n) ||
new_pin != old_pin) {
DPRINTF(("hpet t%d isr cleared due to "
"configuration change\n", n));
vm_set_gsi_irq(vhpet->vm, old_pin, GSI_SET_LOW);
vhpet->isr &= ~(1 << n);
}
}
}
static int
vhpet_mmio_write(struct vhpet *vhpet, int vcpuid, uint64_t gpa, uint64_t *wval,
int size)
{
uint64_t data, mask, oldval, val64;
uint32_t isr_clear_mask, old_compval, old_comprate, counter;
struct timespec now, *nowptr;
int offset, i;
offset = gpa - VHPET_BASE;
/* Accesses to the HPET should be 4 or 8 bytes wide */
switch (size) {
case 8:
mask = 0xffffffffffffffff;
data = *wval;
break;
case 4:
mask = 0xffffffff;
data = *wval;
if ((offset & 0x4) != 0) {
mask <<= 32;
data <<= 32;
}
break;
default:
WPRINTF(("hpet invalid mmio write: "
"offset 0x%08x, size %d\n", offset, size));
goto done;
}
/* Access to the HPET should be naturally aligned to its width */
if (offset & (size - 1)) {
WPRINTF(("hpet invalid mmio write: "
"offset 0x%08x, size %d\n", offset, size));
goto done;
}
if (offset == HPET_CONFIG || offset == HPET_CONFIG + 4) {
/*
* Get the most recent value of the counter before updating
* the 'config' register. If the HPET is going to be disabled
* then we need to update 'countbase' with the value right
* before it is disabled.
*/
nowptr = vhpet_counter_enabled(vhpet) ? &now : NULL;
counter = vhpet_counter(vhpet, nowptr);
oldval = vhpet->config;
update_register(&vhpet->config, data, mask);
/*
* LegacyReplacement Routing is not supported so clear the
* bit along with the reserved bits explicitly.
*/
vhpet->config &= HPET_CNF_ENABLE;
if ((oldval ^ vhpet->config) & HPET_CNF_ENABLE) {
if (vhpet_counter_enabled(vhpet)) {
vhpet_start_counting(vhpet);
DPRINTF(("hpet enabled\n"));
} else {
vhpet_stop_counting(vhpet, counter, &now);
DPRINTF(("hpet disabled\n"));
}
}
goto done;
}
if (offset == HPET_ISR || offset == HPET_ISR + 4) {
/* Top 32 bits are reserved */
isr_clear_mask = vhpet->isr & data;
for (i = 0; i < VHPET_NUM_TIMERS; i++) {
if ((isr_clear_mask & (1 << i)) != 0) {
DPRINTF(("hpet t%d isr cleared\n", i));
vhpet_timer_clear_isr(vhpet, i);
}
}
goto done;
}
if (offset == HPET_MAIN_COUNTER || offset == HPET_MAIN_COUNTER + 4) {
/* Zero-extend the counter to 64-bits before updating it */
val64 = vhpet_counter(vhpet, NULL);
update_register(&val64, data, mask);
vhpet->countbase = val64;
if (vhpet_counter_enabled(vhpet))
vhpet_start_counting(vhpet);
goto done;
}
for (i = 0; i < VHPET_NUM_TIMERS; i++) {
if (offset == HPET_TIMER_CAP_CNF(i) ||
offset == HPET_TIMER_CAP_CNF(i) + 4) {
vhpet_timer_update_config(vhpet, i, data, mask);
break;
}
if (offset == HPET_TIMER_COMPARATOR(i) ||
offset == HPET_TIMER_COMPARATOR(i) + 4) {
old_compval = vhpet->timer[i].compval;
old_comprate = vhpet->timer[i].comprate;
if (vhpet_periodic_timer(vhpet, i)) {
/*
* In periodic mode, writes to the comparator
* change the 'compval' register only if the
* HPET_TCNF_VAL_SET bit is set in the config
* register.
*/
val64 = vhpet->timer[i].comprate;
update_register(&val64, data, mask);
vhpet->timer[i].comprate = val64;
if ((vhpet->timer[i].cap_config &
HPET_TCNF_VAL_SET) != 0)
vhpet->timer[i].compval = val64;
} else {
if (vhpet->timer[i].comprate) {
pr_warn("vhpet t%d's comprate is %u in non-periodic mode"
" - should be 0", i, vhpet->timer[i].comprate);
vhpet->timer[i].comprate = 0;
}
val64 = vhpet->timer[i].compval;
update_register(&val64, data, mask);
vhpet->timer[i].compval = val64;
}
vhpet->timer[i].cap_config &= ~HPET_TCNF_VAL_SET;
if (vhpet->timer[i].compval != old_compval ||
vhpet->timer[i].comprate != old_comprate) {
if (vhpet_counter_enabled(vhpet) &&
vhpet_timer_enabled(vhpet, i))
vhpet_restart_timer(vhpet, i, false);
}
break;
}
if (offset == HPET_TIMER_FSB_VAL(i) ||
offset == HPET_TIMER_FSB_ADDR(i)) {
update_register(&vhpet->timer[i].msireg, data, mask);
break;
}
}
if (i >= VHPET_NUM_TIMERS)
WPRINTF(("hpet invalid mmio write: "
"offset 0x%08x, size %d\n", offset, size));
done:
return 0;
}
static int
vhpet_mmio_read(struct vhpet *vhpet, int vcpuid, uint64_t gpa, uint64_t *rval,
int size)
{
int offset, i;
uint64_t data = 0;
offset = gpa - VHPET_BASE;
/*
* Accesses to the HPET should be:
* - 4 or 8 bytes wide
* - naturally aligned to its width
*/
if ((size != 4 && size != 8) || (offset & (size - 1))) {
WPRINTF(("hpet invalid mmio read: "
"offset 0x%08x, size %d\n", offset, size));
goto done;
}
if (offset == HPET_CAPABILITIES || offset == HPET_CAPABILITIES + 4) {
data = vhpet_capabilities();
goto done;
}
if (offset == HPET_CONFIG || offset == HPET_CONFIG + 4) {
data = vhpet->config;
goto done;
}
if (offset == HPET_ISR || offset == HPET_ISR + 4) {
data = vhpet->isr;
goto done;
}
if (offset == HPET_MAIN_COUNTER || offset == HPET_MAIN_COUNTER + 4) {
data = vhpet_counter(vhpet, NULL);
goto done;
}
for (i = 0; i < VHPET_NUM_TIMERS; i++) {
if (offset == HPET_TIMER_CAP_CNF(i) ||
offset == HPET_TIMER_CAP_CNF(i) + 4) {
data = vhpet->timer[i].cap_config;
break;
}
if (offset == HPET_TIMER_COMPARATOR(i) ||
offset == HPET_TIMER_COMPARATOR(i) + 4) {
data = vhpet->timer[i].compval;
break;
}
if (offset == HPET_TIMER_FSB_VAL(i) ||
offset == HPET_TIMER_FSB_ADDR(i)) {
data = vhpet->timer[i].msireg;
break;
}
}
if (i >= VHPET_NUM_TIMERS) {
WPRINTF(("hpet invalid mmio read: "
"offset 0x%08x, size %d\n", offset, size));
}
done:
if (size == 4) {
if (offset & 0x4)
data >>= 32;
}
*rval = data;
return 0;
}
static int
vhpet_handler(struct vmctx *ctx, int vcpu, int dir, uint64_t addr,
int size, uint64_t *val, void *arg1, long arg2)
{
struct vhpet *vhpet = arg1;
int error;
VHPET_LOCK();
if (!vhpet->inited) {
error = -EINVAL;
goto done;
}
error = ((dir == MEM_F_READ) ? vhpet_mmio_read : vhpet_mmio_write)(
vhpet, vcpu, addr, val, size);
done:
VHPET_UNLOCK();
return error;
}
static void
vhpet_deinit_timers(struct vhpet *vhpet)
{
int i, j;
struct acrn_timer *tmr;
for (i = 0; i < VHPET_NUM_TIMERS; i++) {
for (j = 0; j < nitems(vhpet->timer[i].tmrlst); j++) {
tmr = &vhpet->timer[i].tmrlst[j].t;
acrn_timer_deinit(tmr);
}
}
}
int
vhpet_init(struct vmctx *ctx)
{
int error = 0, pincount, i, j;
struct vhpet *vhpet;
uint64_t allowed_irqs;
struct vhpet_timer_arg *arg;
struct acrn_timer *tmr;
vhpet = vhpet_instance();
VHPET_LOCK();
if (vhpet->inited) {
WPRINTF(("hpet already initialized!\n"));
error = -EINVAL;
goto done;
}
memset(vhpet, 0, sizeof(*vhpet));
vhpet->vm = ctx;
pincount = VIOAPIC_RTE_NUM;
if (pincount >= 32)
allowed_irqs = 0xff000000; /* irqs 24-31 */
else if (pincount >= 20)
allowed_irqs = 0xf << (pincount - 4); /* 4 upper irqs */
else
allowed_irqs = 0;
/*
* Initialize HPET timer hardware state.
*/
for (i = 0; i < VHPET_NUM_TIMERS; i++) {
vhpet->timer[i].cap_config = allowed_irqs << 32;
vhpet->timer[i].cap_config |= HPET_TCAP_PER_INT;
vhpet->timer[i].cap_config |= HPET_TCAP_FSB_INT_DEL;
vhpet->timer[i].compval = 0xffffffff;
for (j = 0; j < nitems(vhpet->timer[i].tmrlst); j++) {
arg = &vhpet->timer[i].tmrlst[j].a;
arg->vhpet = vhpet;
arg->timer_num = i;
tmr = &vhpet->timer[i].tmrlst[j].t;
tmr->clockid = CLOCK_MONOTONIC;
error = acrn_timer_init(tmr, vhpet_timer_handler, arg);
if (error) {
vhpet_deinit_timers(vhpet);
goto done;
}
}
}
vhpet_mr.handler = vhpet_handler;
vhpet_mr.arg1 = vhpet;
vhpet_mr.arg2 = 0;
error = register_mem(&vhpet_mr);
if (error) {
vhpet_deinit_timers(vhpet);
goto done;
}
vhpet->inited = true;
done:
VHPET_UNLOCK();
return error;
}
void
vhpet_deinit(struct vmctx *ctx)
{
struct vhpet *vhpet;
vhpet = vhpet_instance();
VHPET_LOCK();
if (!vhpet->inited)
goto done;
vhpet_deinit_timers(vhpet);
unregister_mem(&vhpet_mr);
vhpet->inited = false;
done:
VHPET_UNLOCK();
}