diff --git a/devicemodel/Makefile b/devicemodel/Makefile index 42c981b07..317a333aa 100644 --- a/devicemodel/Makefile +++ b/devicemodel/Makefile @@ -73,6 +73,7 @@ SRCS += hw/platform/atkbdc.c SRCS += hw/platform/ps2mouse.c SRCS += hw/platform/rtc.c SRCS += hw/platform/pit.c +SRCS += hw/platform/hpet.c SRCS += hw/platform/ps2kbd.c SRCS += hw/platform/ioapic.c SRCS += hw/platform/cmos_io.c diff --git a/devicemodel/core/main.c b/devicemodel/core/main.c index bcbd59bfc..74bae85b8 100644 --- a/devicemodel/core/main.c +++ b/devicemodel/core/main.c @@ -56,6 +56,7 @@ #include "smbiostbl.h" #include "rtc.h" #include "pit.h" +#include "hpet.h" #include "version.h" #include "sw_load.h" #include "monitor.h" @@ -458,6 +459,10 @@ vm_init_vdevs(struct vmctx *ctx) if (ret < 0) goto vpit_fail; + ret = vhpet_init(ctx); + if (ret < 0) + goto vhpet_fail; + sci_init(ctx); if (debugexit_enabled) @@ -481,6 +486,8 @@ monitor_fail: if (debugexit_enabled) deinit_debugexit(); + vhpet_deinit(ctx); +vhpet_fail: vpit_deinit(ctx); vpit_fail: vrtc_deinit(ctx); @@ -501,6 +508,7 @@ vm_deinit_vdevs(struct vmctx *ctx) if (debugexit_enabled) deinit_debugexit(); + vhpet_deinit(ctx); vpit_deinit(ctx); vrtc_deinit(ctx); ioc_deinit(ctx); @@ -529,6 +537,7 @@ vm_reset_vdevs(struct vmctx *ctx) if (debugexit_enabled) deinit_debugexit(); + vhpet_deinit(ctx); vpit_deinit(ctx); vrtc_deinit(ctx); @@ -540,6 +549,7 @@ vm_reset_vdevs(struct vmctx *ctx) atkbdc_init(ctx); vrtc_init(ctx); vpit_init(ctx); + vhpet_init(ctx); if (debugexit_enabled) init_debugexit(); diff --git a/devicemodel/core/mem.c b/devicemodel/core/mem.c index a67667d57..c02ac25c0 100644 --- a/devicemodel/core/mem.c +++ b/devicemodel/core/mem.c @@ -93,7 +93,6 @@ mmio_rb_lookup(struct mmio_rb_tree *rbt, uint64_t addr, return -1; } -__attribute__((unused)) static int mmio_rb_add(struct mmio_rb_tree *rbt, struct mmio_rb_range *new) { @@ -131,7 +130,6 @@ mmio_rb_dump(struct mmio_rb_tree *rbt) RB_GENERATE(mmio_rb_tree, mmio_rb_range, mr_link, mmio_rb_range_compare); -__attribute__((unused)) static int mem_read(void *ctx, int vcpu, uint64_t gpa, uint64_t *rval, int size, void *arg) { @@ -143,7 +141,6 @@ mem_read(void *ctx, int vcpu, uint64_t gpa, uint64_t *rval, int size, void *arg) return error; } -__attribute__((unused)) static int mem_write(void *ctx, int vcpu, uint64_t gpa, uint64_t wval, int size, void *arg) { diff --git a/devicemodel/hw/platform/acpi/acpi.c b/devicemodel/hw/platform/acpi/acpi.c index 1332408db..8abf44b44 100644 --- a/devicemodel/hw/platform/acpi/acpi.c +++ b/devicemodel/hw/platform/acpi/acpi.c @@ -556,7 +556,8 @@ basl_fwrite_hpet(FILE *fp, struct vmctx *ctx) EFPRINTF(fp, "[0004]\t\tAsl Compiler Revision : 00000000\n"); EFPRINTF(fp, "\n"); - EFPRINTF(fp, "[0004]\t\tTimer Block ID : 00000000\n"); + EFPRINTF(fp, "[0004]\t\tTimer Block ID : %08X\n", + (uint32_t)vhpet_capabilities()); EFPRINTF(fp, "[0012]\t\tTimer Block Register : [Generic Address Structure]\n"); EFPRINTF(fp, "[0001]\t\tSpace ID : 00 [SystemMemory]\n"); diff --git a/devicemodel/hw/platform/hpet.c b/devicemodel/hw/platform/hpet.c new file mode 100644 index 000000000..1ef5897f3 --- /dev/null +++ b/devicemodel/hw/platform/hpet.c @@ -0,0 +1,997 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 Intel Corporation + * Copyright (c) 2013 Tycho Nightingale + * Copyright (c) 2013 Neel Natu + * 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 +#include +#include +#include +#include +#include +#include + +#include "vmmapi.h" +#include "mem.h" +#include "timer.h" +#include "hpet.h" +#include "acpi_hpet.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); \ + assert(err == 0); \ + } while (0) + +#define VHPET_UNLOCK() \ + do { \ + int err; \ + err = pthread_mutex_unlock(&vhpet_mtx); \ + assert(err == 0); \ + } 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) printf params; } while (0) +#define WPRINTF(params) (printf params) + + +struct vhpet_timer_arg { + struct vhpet *vhpet; + int timer_num; + bool running; +}; + +static 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]; +} __vhpet; /* one vHPET per VM */ + + +/* one vHPET per VM */ +static pthread_mutex_t vhpet_mtx = PTHREAD_MUTEX_INITIALIZER; + +static const struct itimerspec zero_ts = { 0 }; + + +static inline struct vhpet * +vhpet_instance(void) +{ + 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_REALTIME, &now)) { + perror("clock_gettime failed"); + assert(0); + } + + /* delta = now - countbase_ts */ + assert(timespeccmp(&now, &vhpet->countbase_ts, >=)); + 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. Make sure + * that the caller doesn't want to use it. + */ + assert(nowptr == NULL); + } + + 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); + assert(pin != 0); + vm_set_gsi_irq(vhpet->vm, pin, GSI_SET_LOW); + 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) +{ + assert(!vhpet_timer_msi_enabled(vhpet, n)); + return ((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)) != 0) { + assert(!vhpet_timer_edge_trig(vhpet, n)); + DPRINTF(("hpet t%d intr is already asserted\n", n)); + return; + } + + 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)); + assert(ts_is_zero(&vhpet->timer[n].expts)); + goto done; + } else + assert(arg == vhpet_tmrarg(vhpet, n)); + + vhpet_timer_interrupt(vhpet, n); + + if (clock_gettime(CLOCK_REALTIME, &now)) { + perror("clock_gettime failed"); + assert(0); + } + + if (acrn_timer_gettime(vhpet_tmr(vhpet, n), &tmrts)) + assert(0); + + /* One-shot mode has a periodicity of 2^32 ticks */ + assert(!ts_is_zero(&tmrts.it_interval)); + + /* + * 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; + + assert(comprate != 0); + assert(timespeccmp(&vhpet->timer[n].expts, now, <)); + + /* 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; + + assert(vhpet_timer_running(vhpet, n)); + assert(!ts_is_zero(&vhpet->timer[n].expts)); + + 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)) + assert(0); + + if (++vhpet->timer[n].tmridx == nitems(vhpet->timer[n].tmrlst)) + vhpet->timer[n].tmridx = 0; + + assert(!vhpet_timer_running(vhpet, n)); + + /* + * 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 (timespeccmp(&vhpet->timer[n].expts, now, <)) { + DPRINTF(("hpet t%d interrupt triggered after " + "stopping timer\n", n)); + if (adj_compval && vhpet->timer[n].comprate != 0) + 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; + + if (vhpet_timer_running(vhpet, n)) + 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); + assert(!arg->running); + arg->running = true; + + /* Arm the new timer */ + if (acrn_timer_settime_abs(vhpet_tmr(vhpet, n), &ts)) + assert(0); + + assert(ts_is_zero(&vhpet->timer[n].expts)); + 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_REALTIME, &vhpet->countbase_ts)) { + perror("clock_gettime failed"); + assert(0); + } + + /* 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 + assert(!vhpet_timer_running(vhpet, i)); + } +} + +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 + assert(!vhpet_timer_running(vhpet, i)); + } +} + +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)) + assert(!(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_REALTIME, &now)) { + perror("clock_gettime failed"); + assert(0); + } + 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)) { + assert(old_pin != 0); + 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 { + assert(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; + + 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)); + data = 0; + 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)); + data = 0; + } + +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; + struct mem_range mr; + + 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_REALTIME; + error = acrn_timer_init(tmr, vhpet_timer_handler, arg); + + if (error) { + vhpet_deinit_timers(vhpet); + goto done; + } + } + } + + mr.name = "vhpet"; + mr.base = VHPET_BASE; + mr.size = VHPET_SIZE; + mr.flags = MEM_F_RW; + mr.handler = vhpet_handler; + mr.arg1 = vhpet; + mr.arg2 = 0; + + error = register_mem(&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); + vhpet->inited = false; + +done: + VHPET_UNLOCK(); +} diff --git a/devicemodel/hw/platform/pit.c b/devicemodel/hw/platform/pit.c index 0e9512362..39c838434 100644 --- a/devicemodel/hw/platform/pit.c +++ b/devicemodel/hw/platform/pit.c @@ -32,10 +32,9 @@ #include #include #include -#include -#include #include "vmmapi.h" +#include "timer.h" #include "inout.h" #include "pit.h" @@ -43,7 +42,6 @@ #define PIT_8254_FREQ (1193182) #define PIT_HZ_TO_TICKS(hz) ((PIT_8254_FREQ + (hz) / 2) / (hz)) -#define NS_PER_SEC (1000000000ULL) #define PERIODIC_MODE(mode) \ ((mode) == TIMER_RATEGEN || (mode) == TIMER_SQWAVE) @@ -62,6 +60,10 @@ assert(err == 0); \ } while (0) +#define vpit_ts_to_ticks(ts) ts_to_ticks(PIT_8254_FREQ, ts) +/* won't overflow since the max value of ticks is 65536 */ +#define vpit_ticks_to_ts(tk, ts) ticks_to_ts(PIT_8254_FREQ, tk, ts) + struct vpit_timer_arg { struct vpit *vpit; @@ -92,32 +94,9 @@ struct vpit { /* one vPIT per VM */ -pthread_mutex_t vpit_mtx = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t vpit_mtx = PTHREAD_MUTEX_INITIALIZER; -static inline uint64_t -ts_to_ticks(const struct timespec *ts) -{ - uint64_t tv_sec_ticks, tv_nsec_ticks; - - tv_sec_ticks = ts->tv_sec * PIT_8254_FREQ; - tv_nsec_ticks = howmany(ts->tv_nsec * PIT_8254_FREQ, NS_PER_SEC); - - return tv_sec_ticks + tv_nsec_ticks; -} - -static inline void -ticks_to_ts(uint64_t ticks, struct timespec *ts) -{ - uint64_t ns; - - /* won't overflow since the max value of ticks is 65536 */ - ns = howmany(ticks * NS_PER_SEC, PIT_8254_FREQ); - - ts->tv_sec = ns / NS_PER_SEC; - ts->tv_nsec = ns % NS_PER_SEC; -} - static uint64_t ticks_elapsed_since(const struct timespec *since) { @@ -132,7 +111,7 @@ ticks_elapsed_since(const struct timespec *since) } timespecsub(&ts, since); - return ts_to_ticks(&ts); + return vpit_ts_to_ticks(&ts); } static bool @@ -313,7 +292,7 @@ pit_timer_start_cntr0(struct vpit *vpit) assert(timespecisset(&ts.it_interval)); /* ts.it_value contains the remaining time until expiration */ - ticks_to_ts(pit_cr_val(c->cr), &ts.it_interval); + vpit_ticks_to_ts(pit_cr_val(c->cr), &ts.it_interval); } else { /* * Aperiodic mode or no running periodic counter. @@ -325,7 +304,7 @@ pit_timer_start_cntr0(struct vpit *vpit) timer_ticks = (c->mode == TIMER_SWSTROBE) ? c->initial + 1 : c->initial; - ticks_to_ts(timer_ticks, &ts.it_value); + vpit_ticks_to_ts(timer_ticks, &ts.it_value); /* make it periodic if required */ if (PERIODIC_MODE(c->mode)) { diff --git a/devicemodel/include/timer.h b/devicemodel/include/timer.h index 7631e944f..344129e33 100644 --- a/devicemodel/include/timer.h +++ b/devicemodel/include/timer.h @@ -6,6 +6,8 @@ #ifndef _TIMER_H_ #define _TIMER_H_ +#include + struct acrn_timer { int32_t fd; int32_t clockid; @@ -26,4 +28,28 @@ acrn_timer_settime_abs(struct acrn_timer *timer, int32_t acrn_timer_gettime(struct acrn_timer *timer, struct itimerspec *cur_value); +#define NS_PER_SEC (1000000000ULL) + +static inline uint64_t +ts_to_ticks(const uint32_t freq, const struct timespec *const ts) +{ + uint64_t tv_sec_ticks, tv_nsec_ticks; + + tv_sec_ticks = ts->tv_sec * freq; + tv_nsec_ticks = (ts->tv_nsec * freq) / NS_PER_SEC; + + return tv_sec_ticks + tv_nsec_ticks; +} + +static inline void +ticks_to_ts(const uint32_t freq, const uint64_t ticks, + struct timespec *const ts) +{ + uint64_t ns; + + ns = howmany(ticks * NS_PER_SEC, freq); + + ts->tv_sec = ns / NS_PER_SEC; + ts->tv_nsec = ns % NS_PER_SEC; +} #endif /* _VTIMER_ */