[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [Minios-devel] [UNIKRAFT PATCH v2 8/8] plat/kvm: Add KVM (x86_64) timer support
From: Costin Lupu <costin.lupu@xxxxxxxxx> We are using TSC clock as main timer on KVM. Signed-off-by: Costin Lupu <costin.lupu@xxxxxxxxx> --- plat/kvm/Config.uk | 1 + plat/kvm/Makefile.uk | 2 + plat/kvm/include/kvm/tscclock.h | 42 +++++ plat/kvm/irq.c | 10 ++ plat/kvm/time.c | 65 ++++++++ plat/kvm/tscclock.c | 359 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 479 insertions(+) create mode 100644 plat/kvm/include/kvm/tscclock.h create mode 100644 plat/kvm/time.c create mode 100644 plat/kvm/tscclock.c diff --git a/plat/kvm/Config.uk b/plat/kvm/Config.uk index 449c381..622c4eb 100644 --- a/plat/kvm/Config.uk +++ b/plat/kvm/Config.uk @@ -4,6 +4,7 @@ menuconfig PLAT_KVM depends on (ARCH_X86_64) select LIBUKDEBUG select LIBUKALLOC + select LIBUKTIMECONV select LIBNOLIBC if !HAVE_LIBC help Create a Unikraft image that runs as a KVM guest diff --git a/plat/kvm/Makefile.uk b/plat/kvm/Makefile.uk index 46258ff..dcfa517 100644 --- a/plat/kvm/Makefile.uk +++ b/plat/kvm/Makefile.uk @@ -34,4 +34,6 @@ LIBKVMPLAT_SRCS-$(ARCH_X86_64) += $(LIBKVMPLAT_BASE)/x86/intctrl.c LIBKVMPLAT_SRCS-y += $(LIBKVMPLAT_BASE)/shutdown.c LIBKVMPLAT_SRCS-y += $(LIBKVMPLAT_BASE)/memory.c LIBKVMPLAT_SRCS-y += $(LIBKVMPLAT_BASE)/irq.c +LIBKVMPLAT_SRCS-y += $(LIBKVMPLAT_BASE)/time.c +LIBKVMPLAT_SRCS-y += $(LIBKVMPLAT_BASE)/tscclock.c LIBKVMPLAT_SRCS-y += $(UK_PLAT_COMMON_BASE)/lcpu.c|common diff --git a/plat/kvm/include/kvm/tscclock.h b/plat/kvm/include/kvm/tscclock.h new file mode 100644 index 0000000..27d0e02 --- /dev/null +++ b/plat/kvm/include/kvm/tscclock.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Authors: Costin Lupu <costin.lupu@xxxxxxxxx> + * + * Copyright (c) 2018, NEC Europe Ltd., NEC Corporation. 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. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT HOLDER 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. + * + * THIS HEADER MAY NOT BE EXTRACTED OR MODIFIED IN ANY WAY. + */ + +#ifndef __KVM_TSCCLOCK_H__ +#define __KVM_TSCCLOCK_H__ + +int tscclock_init(void); +__u64 tscclock_monotonic(void); +__u64 tscclock_epochoffset(void); + +#endif /* __KVM_TSCCLOCK_H__ */ diff --git a/plat/kvm/irq.c b/plat/kvm/irq.c index b3d8aaa..ab8d0e5 100644 --- a/plat/kvm/irq.c +++ b/plat/kvm/irq.c @@ -71,12 +71,22 @@ int ukplat_irq_register(unsigned long irq, irq_handler_func_t func, void *arg) return 0; } +/* + * TODO: This is a temporary solution used to identify non TSC clock + * interrupts in order to stop waiting for interrupts with deadline. + */ +extern long nontsc_interrupt_assert; + void _ukplat_irq_handle(unsigned long irq) { struct irq_handler *h; int handled = 0; UK_SLIST_FOREACH(h, &irq_handlers[irq], entries) { + /* TODO define platform wise macro for timer IRQ number */ + if (irq != 0) + nontsc_interrupt_assert = 1; + if (h->func(h->arg) == 1) { handled = 1; break; diff --git a/plat/kvm/time.c b/plat/kvm/time.c new file mode 100644 index 0000000..1fb48bf --- /dev/null +++ b/plat/kvm/time.c @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: ISC */ +/* + * Authors: Dan Williams + * Martin Lucina + * Ricardo Koller + * Costin Lupu <costin.lupu@xxxxxxxxx> + * + * Copyright (c) 2015-2017 IBM + * Copyright (c) 2016-2017 Docker, Inc. + * Copyright (c) 2018, NEC Europe Ltd., NEC Corporation + * + * Permission to use, copy, modify, and/or distribute this software + * for any purpose with or without fee is hereby granted, provided + * that the above copyright notice and this permission notice appear + * in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +/* Taken from solo5 time.c */ + +#include <stdlib.h> +#include <uk/plat/time.h> +#include <uk/plat/irq.h> +#include <kvm/tscclock.h> +#include <uk/assert.h> + + +/* return ns since time_init() */ +__nsec ukplat_monotonic_clock(void) +{ + return tscclock_monotonic(); +} + +/* return wall time in nsecs */ +__nsec ukplat_clock_wall(void) +{ + return tscclock_monotonic() + tscclock_epochoffset(); +} + +static int timer_handler(void *arg __unused) +{ + /* Yes, we handled the irq. */ + return 1; +} + +/* must be called before interrupts are enabled */ +void ukplat_time_init(void) +{ + int rc; + + rc = ukplat_irq_register(0, timer_handler, NULL); + if (rc < 0) + UK_CRASH("Failed to register timer interrupt handler\n"); + + rc = tscclock_init(); + if (rc < 0) + UK_CRASH("Failed to initialize TSCCLOCK\n"); +} diff --git a/plat/kvm/tscclock.c b/plat/kvm/tscclock.c new file mode 100644 index 0000000..9ca5b95 --- /dev/null +++ b/plat/kvm/tscclock.c @@ -0,0 +1,359 @@ +/* SPDX-License-Identifier: ISC */ +/* + * Authors: Dan Williams + * Martin Lucina + * Ricardo Koller + * Costin Lupu <costin.lupu@xxxxxxxxx> + * + * Copyright (c) 2015-2017 IBM + * Copyright (c) 2016-2017 Docker, Inc. + * Copyright (c) 2018, NEC Europe Ltd., NEC Corporation + * + * Permission to use, copy, modify, and/or distribute this software + * for any purpose with or without fee is hereby granted, provided + * that the above copyright notice and this permission notice appear + * in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +/* Taken from solo5 tscclock.c */ + +/*- + * Copyright (c) 2014, 2015 Antti Kantee. All Rights Reserved. + * Copyright (c) 2015 Martin Lucina. All Rights Reserved. + * Modified for solo5 by Ricardo Koller <kollerr@xxxxxxxxxx> + * + * 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 THE AUTHOR ``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 THE AUTHOR 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. + */ + +#include <uk/plat/lcpu.h> +#include <uk/plat/time.h> +#include <x86/cpu.h> +#include <uk/timeconv.h> +#include <uk/print.h> +#include <uk/assert.h> + +#define NSEC_PER_SEC 1000000000ULL + +#define TIMER_CNTR 0x40 +#define TIMER_MODE 0x43 +#define TIMER_SEL0 0x00 +#define TIMER_LATCH 0x00 +#define TIMER_RATEGEN 0x04 +#define TIMER_ONESHOT 0x08 +#define TIMER_16BIT 0x30 +#define TIMER_HZ 1193182 + +#define RTC_COMMAND 0x70 +#define RTC_DATA 0x71 +#define RTC_NMI_DISABLE (1<<8) +#define RTC_NMI_ENABLE 0 +#define RTC_SEC 0x00 +#define RTC_MIN 0x02 +#define RTC_HOUR 0x04 +#define RTC_DAY 0x07 +#define RTC_MONTH 0x08 +#define RTC_YEAR 0x09 +#define RTC_STATUS_A 0x0a +#define RTC_UIP (1<<7) + +/* RTC wall time offset at monotonic time base. */ +static __u64 rtc_epochoffset; + +/* + * TSC clock specific. + */ + +/* Base time values at the last call to tscclock_monotonic(). */ +static __u64 time_base; +static __u64 tsc_base; + +/* Multiplier for converting TSC ticks to nsecs. (0.32) fixed point. */ +static __u32 tsc_mult; + +/* + * Multiplier for converting nsecs to PIT ticks. (1.32) fixed point. + * + * Calculated as: + * + * f = NSEC_PER_SEC / TIMER_HZ (0.31) fixed point. + * pit_mult = 1 / f (1.32) fixed point. + */ +static const __u32 pit_mult = + (1ULL << 63) / ((NSEC_PER_SEC << 31) / TIMER_HZ); + + +/* + * Read the current i8254 channel 0 tick count. + */ +static unsigned int i8254_gettick(void) +{ + __u16 rdval; + + outb(TIMER_MODE, TIMER_SEL0 | TIMER_LATCH); + rdval = inb(TIMER_CNTR); + rdval |= (inb(TIMER_CNTR) << 8); + return rdval; +} + +/* + * Delay for approximately n microseconds using the i8254 channel 0 counter. + * Timer must be programmed appropriately before calling this function. + */ +static void i8254_delay(unsigned int n) +{ + unsigned int cur_tick, initial_tick; + int remaining; + const unsigned long timer_rval = TIMER_HZ / 100; + + initial_tick = i8254_gettick(); + + remaining = (unsigned long long) n * TIMER_HZ / 1000000; + + while (remaining > 1) { + cur_tick = i8254_gettick(); + if (cur_tick > initial_tick) + remaining -= timer_rval - (cur_tick - initial_tick); + else + remaining -= initial_tick - cur_tick; + initial_tick = cur_tick; + } +} + +/* + * Read a RTC register. Due to PC platform braindead-ness also disables NMI. + */ +static inline __u8 rtc_read(__u8 reg) +{ + outb(RTC_COMMAND, reg | RTC_NMI_DISABLE); + return inb(RTC_DATA); +} + +/* + * Return current RTC time. Note that due to waiting for the update cycle to + * complete, this call may take some time. + */ +static __u64 rtc_gettimeofday(void) +{ + struct uktimeconv_bmkclock dt; + unsigned long flags; + + flags = ukplat_lcpu_save_irqf(); + + /* + * If RTC_UIP is down, we have at least 244us to obtain a + * consistent reading before an update can occur. + */ + while (rtc_read(RTC_STATUS_A) & RTC_UIP) + continue; + + dt.dt_sec = uktimeconv_bcdtobin(rtc_read(RTC_SEC)); + dt.dt_min = uktimeconv_bcdtobin(rtc_read(RTC_MIN)); + dt.dt_hour = uktimeconv_bcdtobin(rtc_read(RTC_HOUR)); + dt.dt_day = uktimeconv_bcdtobin(rtc_read(RTC_DAY)); + dt.dt_mon = uktimeconv_bcdtobin(rtc_read(RTC_MONTH)); + dt.dt_year = uktimeconv_bcdtobin(rtc_read(RTC_YEAR)) + 2000; + + ukplat_lcpu_restore_irqf(flags); + + return uktimeconv_bmkclock_to_nsec(&dt); +} + +/* + * Beturn monotonic time using TSC clock. + */ +__u64 tscclock_monotonic(void) +{ + __u64 tsc_now, tsc_delta; + + /* + * Update time_base (monotonic time) and tsc_base (TSC time). + */ + tsc_now = rdtsc(); + tsc_delta = tsc_now - tsc_base; + time_base += mul64_32(tsc_delta, tsc_mult); + tsc_base = tsc_now; + + return time_base; +} + +/* + * Calibrate TSC and initialise TSC clock. + */ +int tscclock_init(void) +{ + __u64 tsc_freq, rtc_boot; + + /* Initialise i8254 timer channel 0 to mode 2 at 100 Hz */ + outb(TIMER_MODE, TIMER_SEL0 | TIMER_RATEGEN | TIMER_16BIT); + outb(TIMER_CNTR, (TIMER_HZ / 100) & 0xff); + outb(TIMER_CNTR, (TIMER_HZ / 100) >> 8); + + /* + * Read RTC "time at boot". This must be done just before tsc_base is + * initialised in order to get a correct offset below. + */ + rtc_boot = rtc_gettimeofday(); + + /* + * Calculate TSC frequency by calibrating against an 0.1s delay + * using the i8254 timer. + * TODO: Find a more elegant solution that does not require us to + * to delay the boot for 100ms. Does KVM provides us a pre-calculated + * TSC value? + */ + tsc_base = rdtsc(); + i8254_delay(100000); + tsc_freq = (rdtsc() - tsc_base) * 10; + uk_printd(DLVL_INFO, + "Clock source: TSC, frequency estimate is %llu Hz\n", + (unsigned long long) tsc_freq); + + /* + * Calculate TSC scaling multiplier. + * + * (0.32) tsc_mult = NSEC_PER_SEC (32.32) / tsc_freq (32.0) + */ + tsc_mult = (NSEC_PER_SEC << 32) / tsc_freq; + + /* + * Monotonic time begins at tsc_base (first read of TSC before + * calibration). + */ + time_base = mul64_32(tsc_base, tsc_mult); + + /* + * Compute RTC epoch offset by subtracting monotonic time_base from RTC + * time at boot. + */ + rtc_epochoffset = rtc_boot - time_base; + + /* + * Initialise i8254 timer channel 0 to mode 4 (one shot). + */ + outb(TIMER_MODE, TIMER_SEL0 | TIMER_ONESHOT | TIMER_16BIT); + + return 0; +} + +/* + * Return epoch offset (wall time offset to monotonic clock start). + */ +__u64 tscclock_epochoffset(void) +{ + return rtc_epochoffset; +} + +/* + * Minimum delta to sleep using PIT. Programming seems to have an overhead of + * 3-4us, but play it safe here. + */ +#define PIT_MIN_DELTA 16 + +/* + * Returns early if any interrupts are serviced, or if the requested delay is + * too short. Must be called with interrupts disabled, will enable interrupts + * "atomically" during idle loop. + */ +static void tscclock_cpu_block(__u64 until) +{ + __u64 now, delta_ns; + __u64 delta_ticks; + unsigned int ticks; + + UK_ASSERT(ukplat_lcpu_irqs_disabled()); + + now = ukplat_monotonic_clock(); + + /* + * Compute delta in PIT ticks. Return if it is less than minimum safe + * amount of ticks. Essentially this will cause us to spin until + * the timeout. + */ + delta_ns = until - now; + delta_ticks = mul64_32(delta_ns, pit_mult); + if (delta_ticks < PIT_MIN_DELTA) { + /* + * Since we are "spinning", quickly enable interrupts in + * the hopes that we might get new work and can do something + * else than spin. + */ + ukplat_lcpu_enable_irq(); + nop(); /* ints are enabled 1 instr after sti */ + ukplat_lcpu_disable_irq(); + return; + } + + /* + * Program the timer to interrupt the CPU after the delay has expired. + * Maximum timer delay is 65535 ticks. + */ + if (delta_ticks > 65535) + ticks = 65535; + else + ticks = delta_ticks; + + /* + * Note that according to the Intel 82C54 datasheet, p12 the + * interrupt is actually delivered in N + 1 ticks. + */ + ticks -= 1; + outb(TIMER_CNTR, ticks & 0xff); + outb(TIMER_CNTR, ticks >> 8); + + /* + * Wait for any interrupt. If we got an interrupt then + * just return into the scheduler which will check if there is + * work to do and send us back here if not. + * + * TODO: It would be more efficient for longer sleeps to be + * able to distinguish if the interrupt was the PIT interrupt + * and no other, but this will do for now. + */ + ukplat_lcpu_halt_irq(); +} + +long nontsc_interrupt_assert; + +void time_block_until(__snsec until) +{ + volatile long *pnontsc_interrupt_assert = &nontsc_interrupt_assert; + + while ((__snsec) ukplat_monotonic_clock() < until) { + tscclock_cpu_block(until); + + /* who triggered the interrupt? */ + if (*pnontsc_interrupt_assert) { + /* it was another device, stop blocking */ + nontsc_interrupt_assert = 0; + break; + } + /* it was us */ + } +} -- 2.7.4 _______________________________________________ Minios-devel mailing list Minios-devel@xxxxxxxxxxxxxxxxxxxx https://lists.xenproject.org/mailman/listinfo/minios-devel
|
Lists.xenproject.org is hosted with RackSpace, monitoring our |