/*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2019-2022 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 #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_REALTIME, &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_REALTIME, 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_REALTIME, &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_REALTIME, &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_REALTIME, &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_REALTIME; 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(); }