Commit | Line | Data |
---|---|---|
fd1fea68 MK |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | ||
3 | /* | |
4 | * Clocksource driver for the synthetic counter and timers | |
5 | * provided by the Hyper-V hypervisor to guest VMs, as described | |
6 | * in the Hyper-V Top Level Functional Spec (TLFS). This driver | |
7 | * is instruction set architecture independent. | |
8 | * | |
9 | * Copyright (C) 2019, Microsoft, Inc. | |
10 | * | |
11 | * Author: Michael Kelley <mikelley@microsoft.com> | |
12 | */ | |
13 | ||
14 | #include <linux/percpu.h> | |
15 | #include <linux/cpumask.h> | |
16 | #include <linux/clockchips.h> | |
17 | #include <linux/mm.h> | |
18 | #include <clocksource/hyperv_timer.h> | |
19 | #include <asm/hyperv-tlfs.h> | |
20 | #include <asm/mshyperv.h> | |
21 | ||
22 | static struct clock_event_device __percpu *hv_clock_event; | |
23 | ||
24 | /* | |
25 | * If false, we're using the old mechanism for stimer0 interrupts | |
26 | * where it sends a VMbus message when it expires. The old | |
27 | * mechanism is used when running on older versions of Hyper-V | |
28 | * that don't support Direct Mode. While Hyper-V provides | |
29 | * four stimer's per CPU, Linux uses only stimer0. | |
30 | */ | |
31 | static bool direct_mode_enabled; | |
32 | ||
33 | static int stimer0_irq; | |
34 | static int stimer0_vector; | |
35 | static int stimer0_message_sint; | |
36 | ||
37 | /* | |
38 | * ISR for when stimer0 is operating in Direct Mode. Direct Mode | |
39 | * does not use VMbus or any VMbus messages, so process here and not | |
40 | * in the VMbus driver code. | |
41 | */ | |
42 | void hv_stimer0_isr(void) | |
43 | { | |
44 | struct clock_event_device *ce; | |
45 | ||
46 | ce = this_cpu_ptr(hv_clock_event); | |
47 | ce->event_handler(ce); | |
48 | } | |
49 | EXPORT_SYMBOL_GPL(hv_stimer0_isr); | |
50 | ||
51 | static int hv_ce_set_next_event(unsigned long delta, | |
52 | struct clock_event_device *evt) | |
53 | { | |
54 | u64 current_tick; | |
55 | ||
56 | current_tick = hyperv_cs->read(NULL); | |
57 | current_tick += delta; | |
58 | hv_init_timer(0, current_tick); | |
59 | return 0; | |
60 | } | |
61 | ||
62 | static int hv_ce_shutdown(struct clock_event_device *evt) | |
63 | { | |
64 | hv_init_timer(0, 0); | |
65 | hv_init_timer_config(0, 0); | |
66 | if (direct_mode_enabled) | |
67 | hv_disable_stimer0_percpu_irq(stimer0_irq); | |
68 | ||
69 | return 0; | |
70 | } | |
71 | ||
72 | static int hv_ce_set_oneshot(struct clock_event_device *evt) | |
73 | { | |
74 | union hv_stimer_config timer_cfg; | |
75 | ||
76 | timer_cfg.as_uint64 = 0; | |
77 | timer_cfg.enable = 1; | |
78 | timer_cfg.auto_enable = 1; | |
79 | if (direct_mode_enabled) { | |
80 | /* | |
81 | * When it expires, the timer will directly interrupt | |
82 | * on the specified hardware vector/IRQ. | |
83 | */ | |
84 | timer_cfg.direct_mode = 1; | |
85 | timer_cfg.apic_vector = stimer0_vector; | |
86 | hv_enable_stimer0_percpu_irq(stimer0_irq); | |
87 | } else { | |
88 | /* | |
89 | * When it expires, the timer will generate a VMbus message, | |
90 | * to be handled by the normal VMbus interrupt handler. | |
91 | */ | |
92 | timer_cfg.direct_mode = 0; | |
93 | timer_cfg.sintx = stimer0_message_sint; | |
94 | } | |
95 | hv_init_timer_config(0, timer_cfg.as_uint64); | |
96 | return 0; | |
97 | } | |
98 | ||
99 | /* | |
100 | * hv_stimer_init - Per-cpu initialization of the clockevent | |
101 | */ | |
102 | void hv_stimer_init(unsigned int cpu) | |
103 | { | |
104 | struct clock_event_device *ce; | |
105 | ||
106 | /* | |
107 | * Synthetic timers are always available except on old versions of | |
108 | * Hyper-V on x86. In that case, just return as Linux will use a | |
109 | * clocksource based on emulated PIT or LAPIC timer hardware. | |
110 | */ | |
111 | if (!(ms_hyperv.features & HV_MSR_SYNTIMER_AVAILABLE)) | |
112 | return; | |
113 | ||
114 | ce = per_cpu_ptr(hv_clock_event, cpu); | |
115 | ce->name = "Hyper-V clockevent"; | |
116 | ce->features = CLOCK_EVT_FEAT_ONESHOT; | |
117 | ce->cpumask = cpumask_of(cpu); | |
118 | ce->rating = 1000; | |
119 | ce->set_state_shutdown = hv_ce_shutdown; | |
120 | ce->set_state_oneshot = hv_ce_set_oneshot; | |
121 | ce->set_next_event = hv_ce_set_next_event; | |
122 | ||
123 | clockevents_config_and_register(ce, | |
124 | HV_CLOCK_HZ, | |
125 | HV_MIN_DELTA_TICKS, | |
126 | HV_MAX_MAX_DELTA_TICKS); | |
127 | } | |
128 | EXPORT_SYMBOL_GPL(hv_stimer_init); | |
129 | ||
130 | /* | |
131 | * hv_stimer_cleanup - Per-cpu cleanup of the clockevent | |
132 | */ | |
133 | void hv_stimer_cleanup(unsigned int cpu) | |
134 | { | |
135 | struct clock_event_device *ce; | |
136 | ||
137 | /* Turn off clockevent device */ | |
138 | if (ms_hyperv.features & HV_MSR_SYNTIMER_AVAILABLE) { | |
139 | ce = per_cpu_ptr(hv_clock_event, cpu); | |
140 | hv_ce_shutdown(ce); | |
141 | } | |
142 | } | |
143 | EXPORT_SYMBOL_GPL(hv_stimer_cleanup); | |
144 | ||
145 | /* hv_stimer_alloc - Global initialization of the clockevent and stimer0 */ | |
146 | int hv_stimer_alloc(int sint) | |
147 | { | |
148 | int ret; | |
149 | ||
150 | hv_clock_event = alloc_percpu(struct clock_event_device); | |
151 | if (!hv_clock_event) | |
152 | return -ENOMEM; | |
153 | ||
154 | direct_mode_enabled = ms_hyperv.misc_features & | |
155 | HV_STIMER_DIRECT_MODE_AVAILABLE; | |
156 | if (direct_mode_enabled) { | |
157 | ret = hv_setup_stimer0_irq(&stimer0_irq, &stimer0_vector, | |
158 | hv_stimer0_isr); | |
159 | if (ret) { | |
160 | free_percpu(hv_clock_event); | |
161 | hv_clock_event = NULL; | |
162 | return ret; | |
163 | } | |
164 | } | |
165 | ||
166 | stimer0_message_sint = sint; | |
167 | return 0; | |
168 | } | |
169 | EXPORT_SYMBOL_GPL(hv_stimer_alloc); | |
170 | ||
171 | /* hv_stimer_free - Free global resources allocated by hv_stimer_alloc() */ | |
172 | void hv_stimer_free(void) | |
173 | { | |
174 | if (direct_mode_enabled && (stimer0_irq != 0)) { | |
175 | hv_remove_stimer0_irq(stimer0_irq); | |
176 | stimer0_irq = 0; | |
177 | } | |
178 | free_percpu(hv_clock_event); | |
179 | hv_clock_event = NULL; | |
180 | } | |
181 | EXPORT_SYMBOL_GPL(hv_stimer_free); | |
182 | ||
183 | /* | |
184 | * Do a global cleanup of clockevents for the cases of kexec and | |
185 | * vmbus exit | |
186 | */ | |
187 | void hv_stimer_global_cleanup(void) | |
188 | { | |
189 | int cpu; | |
190 | struct clock_event_device *ce; | |
191 | ||
192 | if (ms_hyperv.features & HV_MSR_SYNTIMER_AVAILABLE) { | |
193 | for_each_present_cpu(cpu) { | |
194 | ce = per_cpu_ptr(hv_clock_event, cpu); | |
195 | clockevents_unbind_device(ce, cpu); | |
196 | } | |
197 | } | |
198 | hv_stimer_free(); | |
199 | } | |
200 | EXPORT_SYMBOL_GPL(hv_stimer_global_cleanup); |