clocksource/drivers/hyper-v: Move handling of STIMER0 interrupts
STIMER0 interrupts are most naturally modeled as per-cpu IRQs. But because x86/x64 doesn't have per-cpu IRQs, the core STIMER0 interrupt handling machinery is done in code under arch/x86 and Linux IRQs are not used. Adding support for ARM64 means adding equivalent code using per-cpu IRQs under arch/arm64. A better model is to treat per-cpu IRQs as the normal path (which it is for modern architectures), and the x86/x64 path as the exception. Do this by incorporating standard Linux per-cpu IRQ allocation into the main SITMER0 driver code, and bypass it in the x86/x64 exception case. For x86/x64, special case code is retained under arch/x86, but no STIMER0 interrupt handling code is needed under arch/arm64. No functional change. Signed-off-by: Michael Kelley <mikelley@microsoft.com> Acked-by: Daniel Lezcano <daniel.lezcano@linaro.org> Link: https://lore.kernel.org/r/1614721102-2241-11-git-send-email-mikelley@microsoft.com Signed-off-by: Wei Liu <wei.liu@kernel.org>
This commit is contained in:
@@ -327,7 +327,7 @@ static void __init hv_stimer_setup_percpu_clockev(void)
|
|||||||
* Ignore any errors in setting up stimer clockevents
|
* Ignore any errors in setting up stimer clockevents
|
||||||
* as we can run with the LAPIC timer as a fallback.
|
* as we can run with the LAPIC timer as a fallback.
|
||||||
*/
|
*/
|
||||||
(void)hv_stimer_alloc();
|
(void)hv_stimer_alloc(false);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Still register the LAPIC timer, because the direct-mode STIMER is
|
* Still register the LAPIC timer, because the direct-mode STIMER is
|
||||||
|
|||||||
@@ -31,10 +31,6 @@ static inline u64 hv_get_register(unsigned int reg)
|
|||||||
|
|
||||||
void hyperv_vector_handler(struct pt_regs *regs);
|
void hyperv_vector_handler(struct pt_regs *regs);
|
||||||
|
|
||||||
static inline void hv_enable_stimer0_percpu_irq(int irq) {}
|
|
||||||
static inline void hv_disable_stimer0_percpu_irq(int irq) {}
|
|
||||||
|
|
||||||
|
|
||||||
#if IS_ENABLED(CONFIG_HYPERV)
|
#if IS_ENABLED(CONFIG_HYPERV)
|
||||||
extern int hyperv_init_cpuhp;
|
extern int hyperv_init_cpuhp;
|
||||||
|
|
||||||
|
|||||||
@@ -90,21 +90,17 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_hyperv_stimer0)
|
|||||||
set_irq_regs(old_regs);
|
set_irq_regs(old_regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
int hv_setup_stimer0_irq(int *irq, int *vector, void (*handler)(void))
|
/* For x86/x64, override weak placeholders in hyperv_timer.c */
|
||||||
|
void hv_setup_stimer0_handler(void (*handler)(void))
|
||||||
{
|
{
|
||||||
*vector = HYPERV_STIMER0_VECTOR;
|
|
||||||
*irq = -1; /* Unused on x86/x64 */
|
|
||||||
hv_stimer0_handler = handler;
|
hv_stimer0_handler = handler;
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(hv_setup_stimer0_irq);
|
|
||||||
|
|
||||||
void hv_remove_stimer0_irq(int irq)
|
void hv_remove_stimer0_handler(void)
|
||||||
{
|
{
|
||||||
/* We have no way to deallocate the interrupt gate */
|
/* We have no way to deallocate the interrupt gate */
|
||||||
hv_stimer0_handler = NULL;
|
hv_stimer0_handler = NULL;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(hv_remove_stimer0_irq);
|
|
||||||
|
|
||||||
void hv_setup_kexec_handler(void (*handler)(void))
|
void hv_setup_kexec_handler(void (*handler)(void))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,6 +18,9 @@
|
|||||||
#include <linux/sched_clock.h>
|
#include <linux/sched_clock.h>
|
||||||
#include <linux/mm.h>
|
#include <linux/mm.h>
|
||||||
#include <linux/cpuhotplug.h>
|
#include <linux/cpuhotplug.h>
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
#include <linux/irq.h>
|
||||||
|
#include <linux/acpi.h>
|
||||||
#include <clocksource/hyperv_timer.h>
|
#include <clocksource/hyperv_timer.h>
|
||||||
#include <asm/hyperv-tlfs.h>
|
#include <asm/hyperv-tlfs.h>
|
||||||
#include <asm/mshyperv.h>
|
#include <asm/mshyperv.h>
|
||||||
@@ -43,14 +46,13 @@ static u64 hv_sched_clock_offset __ro_after_init;
|
|||||||
*/
|
*/
|
||||||
static bool direct_mode_enabled;
|
static bool direct_mode_enabled;
|
||||||
|
|
||||||
static int stimer0_irq;
|
static int stimer0_irq = -1;
|
||||||
static int stimer0_vector;
|
|
||||||
static int stimer0_message_sint;
|
static int stimer0_message_sint;
|
||||||
|
static DEFINE_PER_CPU(long, stimer0_evt);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ISR for when stimer0 is operating in Direct Mode. Direct Mode
|
* Common code for stimer0 interrupts coming via Direct Mode or
|
||||||
* does not use VMbus or any VMbus messages, so process here and not
|
* as a VMbus message.
|
||||||
* in the VMbus driver code.
|
|
||||||
*/
|
*/
|
||||||
void hv_stimer0_isr(void)
|
void hv_stimer0_isr(void)
|
||||||
{
|
{
|
||||||
@@ -61,6 +63,16 @@ void hv_stimer0_isr(void)
|
|||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(hv_stimer0_isr);
|
EXPORT_SYMBOL_GPL(hv_stimer0_isr);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* stimer0 interrupt handler for architectures that support
|
||||||
|
* per-cpu interrupts, which also implies Direct Mode.
|
||||||
|
*/
|
||||||
|
static irqreturn_t hv_stimer0_percpu_isr(int irq, void *dev_id)
|
||||||
|
{
|
||||||
|
hv_stimer0_isr();
|
||||||
|
return IRQ_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
static int hv_ce_set_next_event(unsigned long delta,
|
static int hv_ce_set_next_event(unsigned long delta,
|
||||||
struct clock_event_device *evt)
|
struct clock_event_device *evt)
|
||||||
{
|
{
|
||||||
@@ -76,8 +88,8 @@ static int hv_ce_shutdown(struct clock_event_device *evt)
|
|||||||
{
|
{
|
||||||
hv_set_register(HV_REGISTER_STIMER0_COUNT, 0);
|
hv_set_register(HV_REGISTER_STIMER0_COUNT, 0);
|
||||||
hv_set_register(HV_REGISTER_STIMER0_CONFIG, 0);
|
hv_set_register(HV_REGISTER_STIMER0_CONFIG, 0);
|
||||||
if (direct_mode_enabled)
|
if (direct_mode_enabled && stimer0_irq >= 0)
|
||||||
hv_disable_stimer0_percpu_irq(stimer0_irq);
|
disable_percpu_irq(stimer0_irq);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -95,8 +107,9 @@ static int hv_ce_set_oneshot(struct clock_event_device *evt)
|
|||||||
* on the specified hardware vector/IRQ.
|
* on the specified hardware vector/IRQ.
|
||||||
*/
|
*/
|
||||||
timer_cfg.direct_mode = 1;
|
timer_cfg.direct_mode = 1;
|
||||||
timer_cfg.apic_vector = stimer0_vector;
|
timer_cfg.apic_vector = HYPERV_STIMER0_VECTOR;
|
||||||
hv_enable_stimer0_percpu_irq(stimer0_irq);
|
if (stimer0_irq >= 0)
|
||||||
|
enable_percpu_irq(stimer0_irq, IRQ_TYPE_NONE);
|
||||||
} else {
|
} else {
|
||||||
/*
|
/*
|
||||||
* When it expires, the timer will generate a VMbus message,
|
* When it expires, the timer will generate a VMbus message,
|
||||||
@@ -169,10 +182,58 @@ int hv_stimer_cleanup(unsigned int cpu)
|
|||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(hv_stimer_cleanup);
|
EXPORT_SYMBOL_GPL(hv_stimer_cleanup);
|
||||||
|
|
||||||
/* hv_stimer_alloc - Global initialization of the clockevent and stimer0 */
|
/*
|
||||||
int hv_stimer_alloc(void)
|
* These placeholders are overridden by arch specific code on
|
||||||
|
* architectures that need special setup of the stimer0 IRQ because
|
||||||
|
* they don't support per-cpu IRQs (such as x86/x64).
|
||||||
|
*/
|
||||||
|
void __weak hv_setup_stimer0_handler(void (*handler)(void))
|
||||||
{
|
{
|
||||||
int ret = 0;
|
};
|
||||||
|
|
||||||
|
void __weak hv_remove_stimer0_handler(void)
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Called only on architectures with per-cpu IRQs (i.e., not x86/x64) */
|
||||||
|
static int hv_setup_stimer0_irq(void)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = acpi_register_gsi(NULL, HYPERV_STIMER0_VECTOR,
|
||||||
|
ACPI_EDGE_SENSITIVE, ACPI_ACTIVE_HIGH);
|
||||||
|
if (ret < 0) {
|
||||||
|
pr_err("Can't register Hyper-V stimer0 GSI. Error %d", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
stimer0_irq = ret;
|
||||||
|
|
||||||
|
ret = request_percpu_irq(stimer0_irq, hv_stimer0_percpu_isr,
|
||||||
|
"Hyper-V stimer0", &stimer0_evt);
|
||||||
|
if (ret) {
|
||||||
|
pr_err("Can't request Hyper-V stimer0 IRQ %d. Error %d",
|
||||||
|
stimer0_irq, ret);
|
||||||
|
acpi_unregister_gsi(stimer0_irq);
|
||||||
|
stimer0_irq = -1;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hv_remove_stimer0_irq(void)
|
||||||
|
{
|
||||||
|
if (stimer0_irq == -1) {
|
||||||
|
hv_remove_stimer0_handler();
|
||||||
|
} else {
|
||||||
|
free_percpu_irq(stimer0_irq, &stimer0_evt);
|
||||||
|
acpi_unregister_gsi(stimer0_irq);
|
||||||
|
stimer0_irq = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* hv_stimer_alloc - Global initialization of the clockevent and stimer0 */
|
||||||
|
int hv_stimer_alloc(bool have_percpu_irqs)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Synthetic timers are always available except on old versions of
|
* Synthetic timers are always available except on old versions of
|
||||||
@@ -188,11 +249,21 @@ int hv_stimer_alloc(void)
|
|||||||
|
|
||||||
direct_mode_enabled = ms_hyperv.misc_features &
|
direct_mode_enabled = ms_hyperv.misc_features &
|
||||||
HV_STIMER_DIRECT_MODE_AVAILABLE;
|
HV_STIMER_DIRECT_MODE_AVAILABLE;
|
||||||
if (direct_mode_enabled) {
|
|
||||||
ret = hv_setup_stimer0_irq(&stimer0_irq, &stimer0_vector,
|
/*
|
||||||
hv_stimer0_isr);
|
* If Direct Mode isn't enabled, the remainder of the initialization
|
||||||
|
* is done later by hv_stimer_legacy_init()
|
||||||
|
*/
|
||||||
|
if (!direct_mode_enabled)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (have_percpu_irqs) {
|
||||||
|
ret = hv_setup_stimer0_irq();
|
||||||
if (ret)
|
if (ret)
|
||||||
goto free_percpu;
|
goto free_clock_event;
|
||||||
|
} else {
|
||||||
|
hv_setup_stimer0_handler(hv_stimer0_isr);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Since we are in Direct Mode, stimer initialization
|
* Since we are in Direct Mode, stimer initialization
|
||||||
@@ -202,15 +273,13 @@ int hv_stimer_alloc(void)
|
|||||||
ret = cpuhp_setup_state(CPUHP_AP_HYPERV_TIMER_STARTING,
|
ret = cpuhp_setup_state(CPUHP_AP_HYPERV_TIMER_STARTING,
|
||||||
"clockevents/hyperv/stimer:starting",
|
"clockevents/hyperv/stimer:starting",
|
||||||
hv_stimer_init, hv_stimer_cleanup);
|
hv_stimer_init, hv_stimer_cleanup);
|
||||||
if (ret < 0)
|
if (ret < 0) {
|
||||||
goto free_stimer0_irq;
|
hv_remove_stimer0_irq();
|
||||||
|
goto free_clock_event;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
free_stimer0_irq:
|
free_clock_event:
|
||||||
hv_remove_stimer0_irq(stimer0_irq);
|
|
||||||
stimer0_irq = 0;
|
|
||||||
free_percpu:
|
|
||||||
free_percpu(hv_clock_event);
|
free_percpu(hv_clock_event);
|
||||||
hv_clock_event = NULL;
|
hv_clock_event = NULL;
|
||||||
return ret;
|
return ret;
|
||||||
@@ -254,23 +323,6 @@ void hv_stimer_legacy_cleanup(unsigned int cpu)
|
|||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(hv_stimer_legacy_cleanup);
|
EXPORT_SYMBOL_GPL(hv_stimer_legacy_cleanup);
|
||||||
|
|
||||||
|
|
||||||
/* hv_stimer_free - Free global resources allocated by hv_stimer_alloc() */
|
|
||||||
void hv_stimer_free(void)
|
|
||||||
{
|
|
||||||
if (!hv_clock_event)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (direct_mode_enabled) {
|
|
||||||
cpuhp_remove_state(CPUHP_AP_HYPERV_TIMER_STARTING);
|
|
||||||
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
|
* Do a global cleanup of clockevents for the cases of kexec and
|
||||||
* vmbus exit
|
* vmbus exit
|
||||||
@@ -287,12 +339,17 @@ void hv_stimer_global_cleanup(void)
|
|||||||
hv_stimer_legacy_cleanup(cpu);
|
hv_stimer_legacy_cleanup(cpu);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
if (!hv_clock_event)
|
||||||
* If Direct Mode is enabled, the cpuhp teardown callback
|
return;
|
||||||
* (hv_stimer_cleanup) will be run on all CPUs to stop the
|
|
||||||
* stimers.
|
if (direct_mode_enabled) {
|
||||||
*/
|
cpuhp_remove_state(CPUHP_AP_HYPERV_TIMER_STARTING);
|
||||||
hv_stimer_free();
|
hv_remove_stimer0_irq();
|
||||||
|
stimer0_irq = -1;
|
||||||
|
}
|
||||||
|
free_percpu(hv_clock_event);
|
||||||
|
hv_clock_event = NULL;
|
||||||
|
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(hv_stimer_global_cleanup);
|
EXPORT_SYMBOL_GPL(hv_stimer_global_cleanup);
|
||||||
|
|
||||||
@@ -457,9 +514,14 @@ static bool __init hv_init_tsc_clocksource(void)
|
|||||||
* Hyper-V Reference TSC rating, causing the generic TSC to be used.
|
* Hyper-V Reference TSC rating, causing the generic TSC to be used.
|
||||||
* TSC_INVARIANT is not offered on ARM64, so the Hyper-V Reference
|
* TSC_INVARIANT is not offered on ARM64, so the Hyper-V Reference
|
||||||
* TSC will be preferred over the virtualized ARM64 arch counter.
|
* TSC will be preferred over the virtualized ARM64 arch counter.
|
||||||
|
* While the Hyper-V MSR clocksource won't be used since the
|
||||||
|
* Reference TSC clocksource is present, change its rating as
|
||||||
|
* well for consistency.
|
||||||
*/
|
*/
|
||||||
if (ms_hyperv.features & HV_ACCESS_TSC_INVARIANT)
|
if (ms_hyperv.features & HV_ACCESS_TSC_INVARIANT) {
|
||||||
hyperv_cs_tsc.rating = 250;
|
hyperv_cs_tsc.rating = 250;
|
||||||
|
hyperv_cs_msr.rating = 250;
|
||||||
|
}
|
||||||
|
|
||||||
hv_read_reference_counter = read_hv_clock_tsc;
|
hv_read_reference_counter = read_hv_clock_tsc;
|
||||||
phys_addr = virt_to_phys(hv_get_tsc_page());
|
phys_addr = virt_to_phys(hv_get_tsc_page());
|
||||||
|
|||||||
@@ -183,9 +183,4 @@ static inline bool hv_is_hibernation_supported(void) { return false; }
|
|||||||
static inline void hyperv_cleanup(void) {}
|
static inline void hyperv_cleanup(void) {}
|
||||||
#endif /* CONFIG_HYPERV */
|
#endif /* CONFIG_HYPERV */
|
||||||
|
|
||||||
#if IS_ENABLED(CONFIG_HYPERV)
|
|
||||||
extern int hv_setup_stimer0_irq(int *irq, int *vector, void (*handler)(void));
|
|
||||||
extern void hv_remove_stimer0_irq(int irq);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -21,8 +21,7 @@
|
|||||||
#define HV_MIN_DELTA_TICKS 1
|
#define HV_MIN_DELTA_TICKS 1
|
||||||
|
|
||||||
/* Routines called by the VMbus driver */
|
/* Routines called by the VMbus driver */
|
||||||
extern int hv_stimer_alloc(void);
|
extern int hv_stimer_alloc(bool have_percpu_irqs);
|
||||||
extern void hv_stimer_free(void);
|
|
||||||
extern int hv_stimer_cleanup(unsigned int cpu);
|
extern int hv_stimer_cleanup(unsigned int cpu);
|
||||||
extern void hv_stimer_legacy_init(unsigned int cpu, int sint);
|
extern void hv_stimer_legacy_init(unsigned int cpu, int sint);
|
||||||
extern void hv_stimer_legacy_cleanup(unsigned int cpu);
|
extern void hv_stimer_legacy_cleanup(unsigned int cpu);
|
||||||
|
|||||||
Reference in New Issue
Block a user