Skip to content

Commit 2b445a7

Browse files
kelleymhtyhicks
authored andcommitted
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> (cherry picked from commit ec866be) Signed-off-by: Tyler Hicks <tyhicks@linux.microsoft.com>
1 parent e0cd2fc commit 2b445a7

6 files changed

Lines changed: 120 additions & 72 deletions

File tree

arch/x86/hyperv/hv_init.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ static void __init hv_stimer_setup_percpu_clockev(void)
301301
* Ignore any errors in setting up stimer clockevents
302302
* as we can run with the LAPIC timer as a fallback.
303303
*/
304-
(void)hv_stimer_alloc();
304+
(void)hv_stimer_alloc(false);
305305

306306
/*
307307
* Still register the LAPIC timer, because the direct-mode STIMER is

arch/x86/include/asm/mshyperv.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,6 @@ static inline u64 hv_get_register(unsigned int reg)
3131

3232
void hyperv_vector_handler(struct pt_regs *regs);
3333

34-
static inline void hv_enable_stimer0_percpu_irq(int irq) {}
35-
static inline void hv_disable_stimer0_percpu_irq(int irq) {}
36-
37-
3834
#if IS_ENABLED(CONFIG_HYPERV)
3935
extern int hyperv_init_cpuhp;
4036

arch/x86/kernel/cpu/mshyperv.c

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,21 +85,17 @@ DEFINE_IDTENTRY_SYSVEC(sysvec_hyperv_stimer0)
8585
set_irq_regs(old_regs);
8686
}
8787

88-
int hv_setup_stimer0_irq(int *irq, int *vector, void (*handler)(void))
88+
/* For x86/x64, override weak placeholders in hyperv_timer.c */
89+
void hv_setup_stimer0_handler(void (*handler)(void))
8990
{
90-
*vector = HYPERV_STIMER0_VECTOR;
91-
*irq = -1; /* Unused on x86/x64 */
9291
hv_stimer0_handler = handler;
93-
return 0;
9492
}
95-
EXPORT_SYMBOL_GPL(hv_setup_stimer0_irq);
9693

97-
void hv_remove_stimer0_irq(int irq)
94+
void hv_remove_stimer0_handler(void)
9895
{
9996
/* We have no way to deallocate the interrupt gate */
10097
hv_stimer0_handler = NULL;
10198
}
102-
EXPORT_SYMBOL_GPL(hv_remove_stimer0_irq);
10399

104100
void hv_setup_kexec_handler(void (*handler)(void))
105101
{

drivers/clocksource/hyperv_timer.c

Lines changed: 115 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
#include <linux/sched_clock.h>
1919
#include <linux/mm.h>
2020
#include <linux/cpuhotplug.h>
21+
#include <linux/interrupt.h>
22+
#include <linux/irq.h>
23+
#include <linux/acpi.h>
2124
#include <clocksource/hyperv_timer.h>
2225
#include <asm/hyperv-tlfs.h>
2326
#include <asm/mshyperv.h>
@@ -43,14 +46,13 @@ static u64 hv_sched_clock_offset __ro_after_init;
4346
*/
4447
static bool direct_mode_enabled;
4548

46-
static int stimer0_irq;
47-
static int stimer0_vector;
49+
static int stimer0_irq = -1;
4850
static int stimer0_message_sint;
51+
static DEFINE_PER_CPU(long, stimer0_evt);
4952

5053
/*
51-
* ISR for when stimer0 is operating in Direct Mode. Direct Mode
52-
* does not use VMbus or any VMbus messages, so process here and not
53-
* in the VMbus driver code.
54+
* Common code for stimer0 interrupts coming via Direct Mode or
55+
* as a VMbus message.
5456
*/
5557
void hv_stimer0_isr(void)
5658
{
@@ -61,6 +63,16 @@ void hv_stimer0_isr(void)
6163
}
6264
EXPORT_SYMBOL_GPL(hv_stimer0_isr);
6365

66+
/*
67+
* stimer0 interrupt handler for architectures that support
68+
* per-cpu interrupts, which also implies Direct Mode.
69+
*/
70+
static irqreturn_t hv_stimer0_percpu_isr(int irq, void *dev_id)
71+
{
72+
hv_stimer0_isr();
73+
return IRQ_HANDLED;
74+
}
75+
6476
static int hv_ce_set_next_event(unsigned long delta,
6577
struct clock_event_device *evt)
6678
{
@@ -76,8 +88,8 @@ static int hv_ce_shutdown(struct clock_event_device *evt)
7688
{
7789
hv_set_register(HV_REGISTER_STIMER0_COUNT, 0);
7890
hv_set_register(HV_REGISTER_STIMER0_CONFIG, 0);
79-
if (direct_mode_enabled)
80-
hv_disable_stimer0_percpu_irq(stimer0_irq);
91+
if (direct_mode_enabled && stimer0_irq >= 0)
92+
disable_percpu_irq(stimer0_irq);
8193

8294
return 0;
8395
}
@@ -95,8 +107,9 @@ static int hv_ce_set_oneshot(struct clock_event_device *evt)
95107
* on the specified hardware vector/IRQ.
96108
*/
97109
timer_cfg.direct_mode = 1;
98-
timer_cfg.apic_vector = stimer0_vector;
99-
hv_enable_stimer0_percpu_irq(stimer0_irq);
110+
timer_cfg.apic_vector = HYPERV_STIMER0_VECTOR;
111+
if (stimer0_irq >= 0)
112+
enable_percpu_irq(stimer0_irq, IRQ_TYPE_NONE);
100113
} else {
101114
/*
102115
* When it expires, the timer will generate a VMbus message,
@@ -169,10 +182,58 @@ int hv_stimer_cleanup(unsigned int cpu)
169182
}
170183
EXPORT_SYMBOL_GPL(hv_stimer_cleanup);
171184

185+
/*
186+
* These placeholders are overridden by arch specific code on
187+
* architectures that need special setup of the stimer0 IRQ because
188+
* they don't support per-cpu IRQs (such as x86/x64).
189+
*/
190+
void __weak hv_setup_stimer0_handler(void (*handler)(void))
191+
{
192+
};
193+
194+
void __weak hv_remove_stimer0_handler(void)
195+
{
196+
};
197+
198+
/* Called only on architectures with per-cpu IRQs (i.e., not x86/x64) */
199+
static int hv_setup_stimer0_irq(void)
200+
{
201+
int ret;
202+
203+
ret = acpi_register_gsi(NULL, HYPERV_STIMER0_VECTOR,
204+
ACPI_EDGE_SENSITIVE, ACPI_ACTIVE_HIGH);
205+
if (ret < 0) {
206+
pr_err("Can't register Hyper-V stimer0 GSI. Error %d", ret);
207+
return ret;
208+
}
209+
stimer0_irq = ret;
210+
211+
ret = request_percpu_irq(stimer0_irq, hv_stimer0_percpu_isr,
212+
"Hyper-V stimer0", &stimer0_evt);
213+
if (ret) {
214+
pr_err("Can't request Hyper-V stimer0 IRQ %d. Error %d",
215+
stimer0_irq, ret);
216+
acpi_unregister_gsi(stimer0_irq);
217+
stimer0_irq = -1;
218+
}
219+
return ret;
220+
}
221+
222+
static void hv_remove_stimer0_irq(void)
223+
{
224+
if (stimer0_irq == -1) {
225+
hv_remove_stimer0_handler();
226+
} else {
227+
free_percpu_irq(stimer0_irq, &stimer0_evt);
228+
acpi_unregister_gsi(stimer0_irq);
229+
stimer0_irq = -1;
230+
}
231+
}
232+
172233
/* hv_stimer_alloc - Global initialization of the clockevent and stimer0 */
173-
int hv_stimer_alloc(void)
234+
int hv_stimer_alloc(bool have_percpu_irqs)
174235
{
175-
int ret = 0;
236+
int ret;
176237

177238
/*
178239
* Synthetic timers are always available except on old versions of
@@ -188,29 +249,37 @@ int hv_stimer_alloc(void)
188249

189250
direct_mode_enabled = ms_hyperv.misc_features &
190251
HV_STIMER_DIRECT_MODE_AVAILABLE;
191-
if (direct_mode_enabled) {
192-
ret = hv_setup_stimer0_irq(&stimer0_irq, &stimer0_vector,
193-
hv_stimer0_isr);
252+
253+
/*
254+
* If Direct Mode isn't enabled, the remainder of the initialization
255+
* is done later by hv_stimer_legacy_init()
256+
*/
257+
if (!direct_mode_enabled)
258+
return 0;
259+
260+
if (have_percpu_irqs) {
261+
ret = hv_setup_stimer0_irq();
194262
if (ret)
195-
goto free_percpu;
263+
goto free_clock_event;
264+
} else {
265+
hv_setup_stimer0_handler(hv_stimer0_isr);
266+
}
196267

197-
/*
198-
* Since we are in Direct Mode, stimer initialization
199-
* can be done now with a CPUHP value in the same range
200-
* as other clockevent devices.
201-
*/
202-
ret = cpuhp_setup_state(CPUHP_AP_HYPERV_TIMER_STARTING,
203-
"clockevents/hyperv/stimer:starting",
204-
hv_stimer_init, hv_stimer_cleanup);
205-
if (ret < 0)
206-
goto free_stimer0_irq;
268+
/*
269+
* Since we are in Direct Mode, stimer initialization
270+
* can be done now with a CPUHP value in the same range
271+
* as other clockevent devices.
272+
*/
273+
ret = cpuhp_setup_state(CPUHP_AP_HYPERV_TIMER_STARTING,
274+
"clockevents/hyperv/stimer:starting",
275+
hv_stimer_init, hv_stimer_cleanup);
276+
if (ret < 0) {
277+
hv_remove_stimer0_irq();
278+
goto free_clock_event;
207279
}
208280
return ret;
209281

210-
free_stimer0_irq:
211-
hv_remove_stimer0_irq(stimer0_irq);
212-
stimer0_irq = 0;
213-
free_percpu:
282+
free_clock_event:
214283
free_percpu(hv_clock_event);
215284
hv_clock_event = NULL;
216285
return ret;
@@ -254,23 +323,6 @@ void hv_stimer_legacy_cleanup(unsigned int cpu)
254323
}
255324
EXPORT_SYMBOL_GPL(hv_stimer_legacy_cleanup);
256325

257-
258-
/* hv_stimer_free - Free global resources allocated by hv_stimer_alloc() */
259-
void hv_stimer_free(void)
260-
{
261-
if (!hv_clock_event)
262-
return;
263-
264-
if (direct_mode_enabled) {
265-
cpuhp_remove_state(CPUHP_AP_HYPERV_TIMER_STARTING);
266-
hv_remove_stimer0_irq(stimer0_irq);
267-
stimer0_irq = 0;
268-
}
269-
free_percpu(hv_clock_event);
270-
hv_clock_event = NULL;
271-
}
272-
EXPORT_SYMBOL_GPL(hv_stimer_free);
273-
274326
/*
275327
* Do a global cleanup of clockevents for the cases of kexec and
276328
* vmbus exit
@@ -287,12 +339,17 @@ void hv_stimer_global_cleanup(void)
287339
hv_stimer_legacy_cleanup(cpu);
288340
}
289341

290-
/*
291-
* If Direct Mode is enabled, the cpuhp teardown callback
292-
* (hv_stimer_cleanup) will be run on all CPUs to stop the
293-
* stimers.
294-
*/
295-
hv_stimer_free();
342+
if (!hv_clock_event)
343+
return;
344+
345+
if (direct_mode_enabled) {
346+
cpuhp_remove_state(CPUHP_AP_HYPERV_TIMER_STARTING);
347+
hv_remove_stimer0_irq();
348+
stimer0_irq = -1;
349+
}
350+
free_percpu(hv_clock_event);
351+
hv_clock_event = NULL;
352+
296353
}
297354
EXPORT_SYMBOL_GPL(hv_stimer_global_cleanup);
298355

@@ -454,9 +511,14 @@ static bool __init hv_init_tsc_clocksource(void)
454511
* Hyper-V Reference TSC rating, causing the generic TSC to be used.
455512
* TSC_INVARIANT is not offered on ARM64, so the Hyper-V Reference
456513
* TSC will be preferred over the virtualized ARM64 arch counter.
514+
* While the Hyper-V MSR clocksource won't be used since the
515+
* Reference TSC clocksource is present, change its rating as
516+
* well for consistency.
457517
*/
458-
if (ms_hyperv.features & HV_ACCESS_TSC_INVARIANT)
518+
if (ms_hyperv.features & HV_ACCESS_TSC_INVARIANT) {
459519
hyperv_cs_tsc.rating = 250;
520+
hyperv_cs_msr.rating = 250;
521+
}
460522

461523
hv_read_reference_counter = read_hv_clock_tsc;
462524
phys_addr = virt_to_phys(hv_get_tsc_page());

include/asm-generic/mshyperv.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,9 +178,4 @@ static inline bool hv_is_hibernation_supported(void) { return false; }
178178
static inline void hyperv_cleanup(void) {}
179179
#endif /* CONFIG_HYPERV */
180180

181-
#if IS_ENABLED(CONFIG_HYPERV)
182-
extern int hv_setup_stimer0_irq(int *irq, int *vector, void (*handler)(void));
183-
extern void hv_remove_stimer0_irq(int irq);
184-
#endif
185-
186181
#endif

include/clocksource/hyperv_timer.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@
2121
#define HV_MIN_DELTA_TICKS 1
2222

2323
/* Routines called by the VMbus driver */
24-
extern int hv_stimer_alloc(void);
25-
extern void hv_stimer_free(void);
24+
extern int hv_stimer_alloc(bool have_percpu_irqs);
2625
extern int hv_stimer_cleanup(unsigned int cpu);
2726
extern void hv_stimer_legacy_init(unsigned int cpu, int sint);
2827
extern void hv_stimer_legacy_cleanup(unsigned int cpu);

0 commit comments

Comments
 (0)