diff options
| author | Michael Kelley <mikelley@microsoft.com> | 2019-07-01 04:25:56 +0000 | 
|---|---|---|
| committer | Thomas Gleixner <tglx@linutronix.de> | 2019-07-03 11:00:59 +0200 | 
| commit | fd1fea6834d0f9f93062ae6685862908a9baed39 (patch) | |
| tree | 5f361b02b874c0517d363a4538d426a6830d4d40 /drivers/clocksource/hyperv_timer.c | |
| parent | 341924049558e5f7c1a148a2c461a417933d35d9 (diff) | |
| download | blackbird-op-linux-fd1fea6834d0f9f93062ae6685862908a9baed39.tar.gz blackbird-op-linux-fd1fea6834d0f9f93062ae6685862908a9baed39.zip | |
clocksource/drivers: Make Hyper-V clocksource ISA agnostic
Hyper-V clock/timer code and data structures are currently mixed
in with other code in the ISA independent drivers/hv directory as
well as the ISA dependent Hyper-V code under arch/x86.
Consolidate this code and data structures into a Hyper-V clocksource driver
to better follow the Linux model. In doing so, separate out the ISA
dependent portions so the new clocksource driver works for x86 and for the
in-process Hyper-V on ARM64 code.
To start, move the existing clockevents code to create the new clocksource
driver. Update the VMbus driver to call initialization and cleanup routines
since the Hyper-V synthetic timers are not independently enumerated in
ACPI.
No behavior is changed and no new functionality is added.
Suggested-by: Marc Zyngier <marc.zyngier@arm.com>
Signed-off-by: Michael Kelley <mikelley@microsoft.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Reviewed-by: Vitaly Kuznetsov <vkuznets@redhat.com>
Cc: "bp@alien8.de" <bp@alien8.de>
Cc: "will.deacon@arm.com" <will.deacon@arm.com>
Cc: "catalin.marinas@arm.com" <catalin.marinas@arm.com>
Cc: "mark.rutland@arm.com" <mark.rutland@arm.com>
Cc: "linux-arm-kernel@lists.infradead.org" <linux-arm-kernel@lists.infradead.org>
Cc: "gregkh@linuxfoundation.org" <gregkh@linuxfoundation.org>
Cc: "linux-hyperv@vger.kernel.org" <linux-hyperv@vger.kernel.org>
Cc: "olaf@aepfle.de" <olaf@aepfle.de>
Cc: "apw@canonical.com" <apw@canonical.com>
Cc: "jasowang@redhat.com" <jasowang@redhat.com>
Cc: "marcelo.cerri@canonical.com" <marcelo.cerri@canonical.com>
Cc: Sunil Muthuswamy <sunilmut@microsoft.com>
Cc: KY Srinivasan <kys@microsoft.com>
Cc: "sashal@kernel.org" <sashal@kernel.org>
Cc: "vincenzo.frascino@arm.com" <vincenzo.frascino@arm.com>
Cc: "linux-arch@vger.kernel.org" <linux-arch@vger.kernel.org>
Cc: "linux-mips@vger.kernel.org" <linux-mips@vger.kernel.org>
Cc: "linux-kselftest@vger.kernel.org" <linux-kselftest@vger.kernel.org>
Cc: "arnd@arndb.de" <arnd@arndb.de>
Cc: "linux@armlinux.org.uk" <linux@armlinux.org.uk>
Cc: "ralf@linux-mips.org" <ralf@linux-mips.org>
Cc: "paul.burton@mips.com" <paul.burton@mips.com>
Cc: "daniel.lezcano@linaro.org" <daniel.lezcano@linaro.org>
Cc: "salyzyn@android.com" <salyzyn@android.com>
Cc: "pcc@google.com" <pcc@google.com>
Cc: "shuah@kernel.org" <shuah@kernel.org>
Cc: "0x7f454c46@gmail.com" <0x7f454c46@gmail.com>
Cc: "linux@rasmusvillemoes.dk" <linux@rasmusvillemoes.dk>
Cc: "huw@codeweavers.com" <huw@codeweavers.com>
Cc: "sfr@canb.auug.org.au" <sfr@canb.auug.org.au>
Cc: "pbonzini@redhat.com" <pbonzini@redhat.com>
Cc: "rkrcmar@redhat.com" <rkrcmar@redhat.com>
Cc: "kvm@vger.kernel.org" <kvm@vger.kernel.org>
Link: https://lkml.kernel.org/r/1561955054-1838-2-git-send-email-mikelley@microsoft.com
Diffstat (limited to 'drivers/clocksource/hyperv_timer.c')
| -rw-r--r-- | drivers/clocksource/hyperv_timer.c | 200 | 
1 files changed, 200 insertions, 0 deletions
| diff --git a/drivers/clocksource/hyperv_timer.c b/drivers/clocksource/hyperv_timer.c new file mode 100644 index 000000000000..68a28af31561 --- /dev/null +++ b/drivers/clocksource/hyperv_timer.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * Clocksource driver for the synthetic counter and timers + * provided by the Hyper-V hypervisor to guest VMs, as described + * in the Hyper-V Top Level Functional Spec (TLFS). This driver + * is instruction set architecture independent. + * + * Copyright (C) 2019, Microsoft, Inc. + * + * Author:  Michael Kelley <mikelley@microsoft.com> + */ + +#include <linux/percpu.h> +#include <linux/cpumask.h> +#include <linux/clockchips.h> +#include <linux/mm.h> +#include <clocksource/hyperv_timer.h> +#include <asm/hyperv-tlfs.h> +#include <asm/mshyperv.h> + +static struct clock_event_device __percpu *hv_clock_event; + +/* + * If false, we're using the old mechanism for stimer0 interrupts + * where it sends a VMbus message when it expires. The old + * mechanism is used when running on older versions of Hyper-V + * that don't support Direct Mode. While Hyper-V provides + * four stimer's per CPU, Linux uses only stimer0. + */ +static bool direct_mode_enabled; + +static int stimer0_irq; +static int stimer0_vector; +static int stimer0_message_sint; + +/* + * ISR for when stimer0 is operating in Direct Mode.  Direct Mode + * does not use VMbus or any VMbus messages, so process here and not + * in the VMbus driver code. + */ +void hv_stimer0_isr(void) +{ +	struct clock_event_device *ce; + +	ce = this_cpu_ptr(hv_clock_event); +	ce->event_handler(ce); +} +EXPORT_SYMBOL_GPL(hv_stimer0_isr); + +static int hv_ce_set_next_event(unsigned long delta, +				struct clock_event_device *evt) +{ +	u64 current_tick; + +	current_tick = hyperv_cs->read(NULL); +	current_tick += delta; +	hv_init_timer(0, current_tick); +	return 0; +} + +static int hv_ce_shutdown(struct clock_event_device *evt) +{ +	hv_init_timer(0, 0); +	hv_init_timer_config(0, 0); +	if (direct_mode_enabled) +		hv_disable_stimer0_percpu_irq(stimer0_irq); + +	return 0; +} + +static int hv_ce_set_oneshot(struct clock_event_device *evt) +{ +	union hv_stimer_config timer_cfg; + +	timer_cfg.as_uint64 = 0; +	timer_cfg.enable = 1; +	timer_cfg.auto_enable = 1; +	if (direct_mode_enabled) { +		/* +		 * When it expires, the timer will directly interrupt +		 * on the specified hardware vector/IRQ. +		 */ +		timer_cfg.direct_mode = 1; +		timer_cfg.apic_vector = stimer0_vector; +		hv_enable_stimer0_percpu_irq(stimer0_irq); +	} else { +		/* +		 * When it expires, the timer will generate a VMbus message, +		 * to be handled by the normal VMbus interrupt handler. +		 */ +		timer_cfg.direct_mode = 0; +		timer_cfg.sintx = stimer0_message_sint; +	} +	hv_init_timer_config(0, timer_cfg.as_uint64); +	return 0; +} + +/* + * hv_stimer_init - Per-cpu initialization of the clockevent + */ +void hv_stimer_init(unsigned int cpu) +{ +	struct clock_event_device *ce; + +	/* +	 * Synthetic timers are always available except on old versions of +	 * Hyper-V on x86.  In that case, just return as Linux will use a +	 * clocksource based on emulated PIT or LAPIC timer hardware. +	 */ +	if (!(ms_hyperv.features & HV_MSR_SYNTIMER_AVAILABLE)) +		return; + +	ce = per_cpu_ptr(hv_clock_event, cpu); +	ce->name = "Hyper-V clockevent"; +	ce->features = CLOCK_EVT_FEAT_ONESHOT; +	ce->cpumask = cpumask_of(cpu); +	ce->rating = 1000; +	ce->set_state_shutdown = hv_ce_shutdown; +	ce->set_state_oneshot = hv_ce_set_oneshot; +	ce->set_next_event = hv_ce_set_next_event; + +	clockevents_config_and_register(ce, +					HV_CLOCK_HZ, +					HV_MIN_DELTA_TICKS, +					HV_MAX_MAX_DELTA_TICKS); +} +EXPORT_SYMBOL_GPL(hv_stimer_init); + +/* + * hv_stimer_cleanup - Per-cpu cleanup of the clockevent + */ +void hv_stimer_cleanup(unsigned int cpu) +{ +	struct clock_event_device *ce; + +	/* Turn off clockevent device */ +	if (ms_hyperv.features & HV_MSR_SYNTIMER_AVAILABLE) { +		ce = per_cpu_ptr(hv_clock_event, cpu); +		hv_ce_shutdown(ce); +	} +} +EXPORT_SYMBOL_GPL(hv_stimer_cleanup); + +/* hv_stimer_alloc - Global initialization of the clockevent and stimer0 */ +int hv_stimer_alloc(int sint) +{ +	int ret; + +	hv_clock_event = alloc_percpu(struct clock_event_device); +	if (!hv_clock_event) +		return -ENOMEM; + +	direct_mode_enabled = ms_hyperv.misc_features & +			HV_STIMER_DIRECT_MODE_AVAILABLE; +	if (direct_mode_enabled) { +		ret = hv_setup_stimer0_irq(&stimer0_irq, &stimer0_vector, +				hv_stimer0_isr); +		if (ret) { +			free_percpu(hv_clock_event); +			hv_clock_event = NULL; +			return ret; +		} +	} + +	stimer0_message_sint = sint; +	return 0; +} +EXPORT_SYMBOL_GPL(hv_stimer_alloc); + +/* hv_stimer_free - Free global resources allocated by hv_stimer_alloc() */ +void hv_stimer_free(void) +{ +	if (direct_mode_enabled && (stimer0_irq != 0)) { +		hv_remove_stimer0_irq(stimer0_irq); +		stimer0_irq = 0; +	} +	free_percpu(hv_clock_event); +	hv_clock_event = NULL; +} +EXPORT_SYMBOL_GPL(hv_stimer_free); + +/* + * Do a global cleanup of clockevents for the cases of kexec and + * vmbus exit + */ +void hv_stimer_global_cleanup(void) +{ +	int	cpu; +	struct clock_event_device *ce; + +	if (ms_hyperv.features & HV_MSR_SYNTIMER_AVAILABLE) { +		for_each_present_cpu(cpu) { +			ce = per_cpu_ptr(hv_clock_event, cpu); +			clockevents_unbind_device(ce, cpu); +		} +	} +	hv_stimer_free(); +} +EXPORT_SYMBOL_GPL(hv_stimer_global_cleanup); | 

