|
14 | 14 | #include <linux/percpu.h> |
15 | 15 | #include <linux/cpumask.h> |
16 | 16 | #include <linux/clockchips.h> |
| 17 | +#include <linux/clocksource.h> |
| 18 | +#include <linux/sched_clock.h> |
17 | 19 | #include <linux/mm.h> |
18 | 20 | #include <clocksource/hyperv_timer.h> |
19 | 21 | #include <asm/hyperv-tlfs.h> |
@@ -198,3 +200,140 @@ void hv_stimer_global_cleanup(void) |
198 | 200 | hv_stimer_free(); |
199 | 201 | } |
200 | 202 | EXPORT_SYMBOL_GPL(hv_stimer_global_cleanup); |
| 203 | + |
| 204 | +/* |
| 205 | + * Code and definitions for the Hyper-V clocksources. Two |
| 206 | + * clocksources are defined: one that reads the Hyper-V defined MSR, and |
| 207 | + * the other that uses the TSC reference page feature as defined in the |
| 208 | + * TLFS. The MSR version is for compatibility with old versions of |
| 209 | + * Hyper-V and 32-bit x86. The TSC reference page version is preferred. |
| 210 | + */ |
| 211 | + |
| 212 | +struct clocksource *hyperv_cs; |
| 213 | +EXPORT_SYMBOL_GPL(hyperv_cs); |
| 214 | + |
| 215 | +#ifdef CONFIG_HYPERV_TSCPAGE |
| 216 | + |
| 217 | +static struct ms_hyperv_tsc_page *tsc_pg; |
| 218 | + |
| 219 | +struct ms_hyperv_tsc_page *hv_get_tsc_page(void) |
| 220 | +{ |
| 221 | + return tsc_pg; |
| 222 | +} |
| 223 | +EXPORT_SYMBOL_GPL(hv_get_tsc_page); |
| 224 | + |
| 225 | +static u64 notrace read_hv_sched_clock_tsc(void) |
| 226 | +{ |
| 227 | + u64 current_tick = hv_read_tsc_page(tsc_pg); |
| 228 | + |
| 229 | + if (current_tick == U64_MAX) |
| 230 | + hv_get_time_ref_count(current_tick); |
| 231 | + |
| 232 | + return current_tick; |
| 233 | +} |
| 234 | + |
| 235 | +static u64 read_hv_clock_tsc(struct clocksource *arg) |
| 236 | +{ |
| 237 | + return read_hv_sched_clock_tsc(); |
| 238 | +} |
| 239 | + |
| 240 | +static struct clocksource hyperv_cs_tsc = { |
| 241 | + .name = "hyperv_clocksource_tsc_page", |
| 242 | + .rating = 400, |
| 243 | + .read = read_hv_clock_tsc, |
| 244 | + .mask = CLOCKSOURCE_MASK(64), |
| 245 | + .flags = CLOCK_SOURCE_IS_CONTINUOUS, |
| 246 | +}; |
| 247 | +#endif |
| 248 | + |
| 249 | +static u64 notrace read_hv_sched_clock_msr(void) |
| 250 | +{ |
| 251 | + u64 current_tick; |
| 252 | + /* |
| 253 | + * Read the partition counter to get the current tick count. This count |
| 254 | + * is set to 0 when the partition is created and is incremented in |
| 255 | + * 100 nanosecond units. |
| 256 | + */ |
| 257 | + hv_get_time_ref_count(current_tick); |
| 258 | + return current_tick; |
| 259 | +} |
| 260 | + |
| 261 | +static u64 read_hv_clock_msr(struct clocksource *arg) |
| 262 | +{ |
| 263 | + return read_hv_sched_clock_msr(); |
| 264 | +} |
| 265 | + |
| 266 | +static struct clocksource hyperv_cs_msr = { |
| 267 | + .name = "hyperv_clocksource_msr", |
| 268 | + .rating = 400, |
| 269 | + .read = read_hv_clock_msr, |
| 270 | + .mask = CLOCKSOURCE_MASK(64), |
| 271 | + .flags = CLOCK_SOURCE_IS_CONTINUOUS, |
| 272 | +}; |
| 273 | + |
| 274 | +#ifdef CONFIG_HYPERV_TSCPAGE |
| 275 | +static bool __init hv_init_tsc_clocksource(void) |
| 276 | +{ |
| 277 | + u64 tsc_msr; |
| 278 | + phys_addr_t phys_addr; |
| 279 | + |
| 280 | + if (!(ms_hyperv.features & HV_MSR_REFERENCE_TSC_AVAILABLE)) |
| 281 | + return false; |
| 282 | + |
| 283 | + tsc_pg = vmalloc(PAGE_SIZE); |
| 284 | + if (!tsc_pg) |
| 285 | + return false; |
| 286 | + |
| 287 | + hyperv_cs = &hyperv_cs_tsc; |
| 288 | + phys_addr = page_to_phys(vmalloc_to_page(tsc_pg)); |
| 289 | + |
| 290 | + /* |
| 291 | + * The Hyper-V TLFS specifies to preserve the value of reserved |
| 292 | + * bits in registers. So read the existing value, preserve the |
| 293 | + * low order 12 bits, and add in the guest physical address |
| 294 | + * (which already has at least the low 12 bits set to zero since |
| 295 | + * it is page aligned). Also set the "enable" bit, which is bit 0. |
| 296 | + */ |
| 297 | + hv_get_reference_tsc(tsc_msr); |
| 298 | + tsc_msr &= GENMASK_ULL(11, 0); |
| 299 | + tsc_msr = tsc_msr | 0x1 | (u64)phys_addr; |
| 300 | + hv_set_reference_tsc(tsc_msr); |
| 301 | + |
| 302 | + hv_set_clocksource_vdso(hyperv_cs_tsc); |
| 303 | + clocksource_register_hz(&hyperv_cs_tsc, NSEC_PER_SEC/100); |
| 304 | + |
| 305 | + /* sched_clock_register is needed on ARM64 but is a no-op on x86 */ |
| 306 | + sched_clock_register(read_hv_sched_clock_tsc, 64, HV_CLOCK_HZ); |
| 307 | + return true; |
| 308 | +} |
| 309 | +#else |
| 310 | +static bool __init hv_init_tsc_clocksource(void) |
| 311 | +{ |
| 312 | + return false; |
| 313 | +} |
| 314 | +#endif |
| 315 | + |
| 316 | + |
| 317 | +void __init hv_init_clocksource(void) |
| 318 | +{ |
| 319 | + /* |
| 320 | + * Try to set up the TSC page clocksource. If it succeeds, we're |
| 321 | + * done. Otherwise, set up the MSR clocksoruce. At least one of |
| 322 | + * these will always be available except on very old versions of |
| 323 | + * Hyper-V on x86. In that case we won't have a Hyper-V |
| 324 | + * clocksource, but Linux will still run with a clocksource based |
| 325 | + * on the emulated PIT or LAPIC timer. |
| 326 | + */ |
| 327 | + if (hv_init_tsc_clocksource()) |
| 328 | + return; |
| 329 | + |
| 330 | + if (!(ms_hyperv.features & HV_MSR_TIME_REF_COUNT_AVAILABLE)) |
| 331 | + return; |
| 332 | + |
| 333 | + hyperv_cs = &hyperv_cs_msr; |
| 334 | + clocksource_register_hz(&hyperv_cs_msr, NSEC_PER_SEC/100); |
| 335 | + |
| 336 | + /* sched_clock_register is needed on ARM64 but is a no-op on x86 */ |
| 337 | + sched_clock_register(read_hv_sched_clock_msr, 64, HV_CLOCK_HZ); |
| 338 | +} |
| 339 | +EXPORT_SYMBOL_GPL(hv_init_clocksource); |
0 commit comments