| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * HyperV Detection code. |
|---|
| 3 | 4 | * |
|---|
| 4 | 5 | * Copyright (C) 2010, Novell, Inc. |
|---|
| 5 | 6 | * Author : K. Y. Srinivasan <ksrinivasan@novell.com> |
|---|
| 6 | | - * |
|---|
| 7 | | - * This program is free software; you can redistribute it and/or modify |
|---|
| 8 | | - * it under the terms of the GNU General Public License as published by |
|---|
| 9 | | - * the Free Software Foundation; version 2 of the License. |
|---|
| 10 | | - * |
|---|
| 11 | 7 | */ |
|---|
| 12 | 8 | |
|---|
| 13 | 9 | #include <linux/types.h> |
|---|
| .. | .. |
|---|
| 21 | 17 | #include <linux/irq.h> |
|---|
| 22 | 18 | #include <linux/kexec.h> |
|---|
| 23 | 19 | #include <linux/i8253.h> |
|---|
| 20 | +#include <linux/random.h> |
|---|
| 24 | 21 | #include <asm/processor.h> |
|---|
| 25 | 22 | #include <asm/hypervisor.h> |
|---|
| 26 | 23 | #include <asm/hyperv-tlfs.h> |
|---|
| 27 | 24 | #include <asm/mshyperv.h> |
|---|
| 28 | 25 | #include <asm/desc.h> |
|---|
| 26 | +#include <asm/idtentry.h> |
|---|
| 29 | 27 | #include <asm/irq_regs.h> |
|---|
| 30 | 28 | #include <asm/i8259.h> |
|---|
| 31 | 29 | #include <asm/apic.h> |
|---|
| 32 | 30 | #include <asm/timer.h> |
|---|
| 33 | 31 | #include <asm/reboot.h> |
|---|
| 34 | 32 | #include <asm/nmi.h> |
|---|
| 33 | +#include <clocksource/hyperv_timer.h> |
|---|
| 35 | 34 | |
|---|
| 36 | 35 | struct ms_hyperv_info ms_hyperv; |
|---|
| 37 | 36 | EXPORT_SYMBOL_GPL(ms_hyperv); |
|---|
| .. | .. |
|---|
| 42 | 41 | static void (*hv_kexec_handler)(void); |
|---|
| 43 | 42 | static void (*hv_crash_handler)(struct pt_regs *regs); |
|---|
| 44 | 43 | |
|---|
| 45 | | -__visible void __irq_entry hyperv_vector_handler(struct pt_regs *regs) |
|---|
| 44 | +DEFINE_IDTENTRY_SYSVEC(sysvec_hyperv_callback) |
|---|
| 46 | 45 | { |
|---|
| 47 | 46 | struct pt_regs *old_regs = set_irq_regs(regs); |
|---|
| 48 | 47 | |
|---|
| 49 | | - entering_irq(); |
|---|
| 50 | 48 | inc_irq_stat(irq_hv_callback_count); |
|---|
| 51 | 49 | if (vmbus_handler) |
|---|
| 52 | 50 | vmbus_handler(); |
|---|
| .. | .. |
|---|
| 54 | 52 | if (ms_hyperv.hints & HV_DEPRECATING_AEOI_RECOMMENDED) |
|---|
| 55 | 53 | ack_APIC_irq(); |
|---|
| 56 | 54 | |
|---|
| 57 | | - exiting_irq(); |
|---|
| 58 | 55 | set_irq_regs(old_regs); |
|---|
| 59 | 56 | } |
|---|
| 60 | 57 | |
|---|
| 61 | | -void hv_setup_vmbus_irq(void (*handler)(void)) |
|---|
| 58 | +int hv_setup_vmbus_irq(int irq, void (*handler)(void)) |
|---|
| 62 | 59 | { |
|---|
| 60 | + /* |
|---|
| 61 | + * The 'irq' argument is ignored on x86/x64 because a hard-coded |
|---|
| 62 | + * interrupt vector is used for Hyper-V interrupts. |
|---|
| 63 | + */ |
|---|
| 63 | 64 | vmbus_handler = handler; |
|---|
| 65 | + return 0; |
|---|
| 64 | 66 | } |
|---|
| 65 | 67 | |
|---|
| 66 | 68 | void hv_remove_vmbus_irq(void) |
|---|
| .. | .. |
|---|
| 75 | 77 | * Routines to do per-architecture handling of stimer0 |
|---|
| 76 | 78 | * interrupts when in Direct Mode |
|---|
| 77 | 79 | */ |
|---|
| 78 | | - |
|---|
| 79 | | -__visible void __irq_entry hv_stimer0_vector_handler(struct pt_regs *regs) |
|---|
| 80 | +DEFINE_IDTENTRY_SYSVEC(sysvec_hyperv_stimer0) |
|---|
| 80 | 81 | { |
|---|
| 81 | 82 | struct pt_regs *old_regs = set_irq_regs(regs); |
|---|
| 82 | 83 | |
|---|
| 83 | | - entering_irq(); |
|---|
| 84 | 84 | inc_irq_stat(hyperv_stimer0_count); |
|---|
| 85 | 85 | if (hv_stimer0_handler) |
|---|
| 86 | 86 | hv_stimer0_handler(); |
|---|
| 87 | + add_interrupt_randomness(HYPERV_STIMER0_VECTOR); |
|---|
| 87 | 88 | ack_APIC_irq(); |
|---|
| 88 | 89 | |
|---|
| 89 | | - exiting_irq(); |
|---|
| 90 | 90 | set_irq_regs(old_regs); |
|---|
| 91 | 91 | } |
|---|
| 92 | 92 | |
|---|
| 93 | 93 | int hv_setup_stimer0_irq(int *irq, int *vector, void (*handler)(void)) |
|---|
| 94 | 94 | { |
|---|
| 95 | 95 | *vector = HYPERV_STIMER0_VECTOR; |
|---|
| 96 | | - *irq = 0; /* Unused on x86/x64 */ |
|---|
| 96 | + *irq = -1; /* Unused on x86/x64 */ |
|---|
| 97 | 97 | hv_stimer0_handler = handler; |
|---|
| 98 | 98 | return 0; |
|---|
| 99 | 99 | } |
|---|
| .. | .. |
|---|
| 135 | 135 | { |
|---|
| 136 | 136 | if (kexec_in_progress && hv_kexec_handler) |
|---|
| 137 | 137 | hv_kexec_handler(); |
|---|
| 138 | + |
|---|
| 139 | + /* |
|---|
| 140 | + * Call hv_cpu_die() on all the CPUs, otherwise later the hypervisor |
|---|
| 141 | + * corrupts the old VP Assist Pages and can crash the kexec kernel. |
|---|
| 142 | + */ |
|---|
| 143 | + if (kexec_in_progress && hyperv_init_cpuhp > 0) |
|---|
| 144 | + cpuhp_remove_state(hyperv_init_cpuhp); |
|---|
| 145 | + |
|---|
| 146 | + /* The function calls stop_other_cpus(). */ |
|---|
| 138 | 147 | native_machine_shutdown(); |
|---|
| 148 | + |
|---|
| 149 | + /* Disable the hypercall page when there is only 1 active CPU. */ |
|---|
| 150 | + if (kexec_in_progress) |
|---|
| 151 | + hyperv_cleanup(); |
|---|
| 139 | 152 | } |
|---|
| 140 | 153 | |
|---|
| 141 | 154 | static void hv_machine_crash_shutdown(struct pt_regs *regs) |
|---|
| 142 | 155 | { |
|---|
| 143 | 156 | if (hv_crash_handler) |
|---|
| 144 | 157 | hv_crash_handler(regs); |
|---|
| 158 | + |
|---|
| 159 | + /* The function calls crash_smp_send_stop(). */ |
|---|
| 145 | 160 | native_machine_crash_shutdown(regs); |
|---|
| 161 | + |
|---|
| 162 | + /* Disable the hypercall page when there is only 1 active CPU. */ |
|---|
| 163 | + hyperv_cleanup(); |
|---|
| 146 | 164 | } |
|---|
| 147 | 165 | #endif /* CONFIG_KEXEC_CORE */ |
|---|
| 148 | 166 | #endif /* CONFIG_HYPERV */ |
|---|
| .. | .. |
|---|
| 200 | 218 | return freq / 1000; |
|---|
| 201 | 219 | } |
|---|
| 202 | 220 | |
|---|
| 221 | +#if defined(CONFIG_SMP) && IS_ENABLED(CONFIG_HYPERV) |
|---|
| 222 | +static void __init hv_smp_prepare_boot_cpu(void) |
|---|
| 223 | +{ |
|---|
| 224 | + native_smp_prepare_boot_cpu(); |
|---|
| 225 | +#if defined(CONFIG_X86_64) && defined(CONFIG_PARAVIRT_SPINLOCKS) |
|---|
| 226 | + hv_init_spinlocks(); |
|---|
| 227 | +#endif |
|---|
| 228 | +} |
|---|
| 229 | +#endif |
|---|
| 230 | + |
|---|
| 203 | 231 | static void __init ms_hyperv_init_platform(void) |
|---|
| 204 | 232 | { |
|---|
| 205 | 233 | int hv_host_info_eax; |
|---|
| 206 | 234 | int hv_host_info_ebx; |
|---|
| 207 | 235 | int hv_host_info_ecx; |
|---|
| 208 | 236 | int hv_host_info_edx; |
|---|
| 237 | + |
|---|
| 238 | +#ifdef CONFIG_PARAVIRT |
|---|
| 239 | + pv_info.name = "Hyper-V"; |
|---|
| 240 | +#endif |
|---|
| 209 | 241 | |
|---|
| 210 | 242 | /* |
|---|
| 211 | 243 | * Extract the features and hints |
|---|
| .. | .. |
|---|
| 239 | 271 | hv_host_info_edx >> 24, hv_host_info_edx & 0xFFFFFF); |
|---|
| 240 | 272 | } |
|---|
| 241 | 273 | |
|---|
| 242 | | - if (ms_hyperv.features & HV_X64_ACCESS_FREQUENCY_MSRS && |
|---|
| 274 | + if (ms_hyperv.features & HV_ACCESS_FREQUENCY_MSRS && |
|---|
| 243 | 275 | ms_hyperv.misc_features & HV_FEATURE_FREQUENCY_MSRS_AVAILABLE) { |
|---|
| 244 | 276 | x86_platform.calibrate_tsc = hv_get_tsc_khz; |
|---|
| 245 | 277 | x86_platform.calibrate_cpu = hv_get_tsc_khz; |
|---|
| .. | .. |
|---|
| 261 | 293 | crash_kexec_post_notifiers = true; |
|---|
| 262 | 294 | |
|---|
| 263 | 295 | #ifdef CONFIG_X86_LOCAL_APIC |
|---|
| 264 | | - if (ms_hyperv.features & HV_X64_ACCESS_FREQUENCY_MSRS && |
|---|
| 296 | + if (ms_hyperv.features & HV_ACCESS_FREQUENCY_MSRS && |
|---|
| 265 | 297 | ms_hyperv.misc_features & HV_FEATURE_FREQUENCY_MSRS_AVAILABLE) { |
|---|
| 266 | 298 | /* |
|---|
| 267 | 299 | * Get the APIC frequency. |
|---|
| .. | .. |
|---|
| 270 | 302 | |
|---|
| 271 | 303 | rdmsrl(HV_X64_MSR_APIC_FREQUENCY, hv_lapic_frequency); |
|---|
| 272 | 304 | hv_lapic_frequency = div_u64(hv_lapic_frequency, HZ); |
|---|
| 273 | | - lapic_timer_frequency = hv_lapic_frequency; |
|---|
| 305 | + lapic_timer_period = hv_lapic_frequency; |
|---|
| 274 | 306 | pr_info("Hyper-V: LAPIC Timer Frequency: %#x\n", |
|---|
| 275 | | - lapic_timer_frequency); |
|---|
| 307 | + lapic_timer_period); |
|---|
| 276 | 308 | } |
|---|
| 277 | 309 | |
|---|
| 278 | 310 | register_nmi_handler(NMI_UNKNOWN, hv_nmi_unknown, NMI_FLAG_FIRST, |
|---|
| .. | .. |
|---|
| 287 | 319 | machine_ops.shutdown = hv_machine_shutdown; |
|---|
| 288 | 320 | machine_ops.crash_shutdown = hv_machine_crash_shutdown; |
|---|
| 289 | 321 | #endif |
|---|
| 290 | | - mark_tsc_unstable("running on Hyper-V"); |
|---|
| 322 | + if (ms_hyperv.features & HV_ACCESS_TSC_INVARIANT) { |
|---|
| 323 | + wrmsrl(HV_X64_MSR_TSC_INVARIANT_CONTROL, 0x1); |
|---|
| 324 | + setup_force_cpu_cap(X86_FEATURE_TSC_RELIABLE); |
|---|
| 325 | + } |
|---|
| 291 | 326 | |
|---|
| 292 | 327 | /* |
|---|
| 293 | 328 | * Generation 2 instances don't support reading the NMI status from |
|---|
| .. | .. |
|---|
| 313 | 348 | x86_platform.apic_post_init = hyperv_init; |
|---|
| 314 | 349 | hyperv_setup_mmu_ops(); |
|---|
| 315 | 350 | /* Setup the IDT for hypervisor callback */ |
|---|
| 316 | | - alloc_intr_gate(HYPERVISOR_CALLBACK_VECTOR, hyperv_callback_vector); |
|---|
| 351 | + alloc_intr_gate(HYPERVISOR_CALLBACK_VECTOR, asm_sysvec_hyperv_callback); |
|---|
| 317 | 352 | |
|---|
| 318 | 353 | /* Setup the IDT for reenlightenment notifications */ |
|---|
| 319 | | - if (ms_hyperv.features & HV_X64_ACCESS_REENLIGHTENMENT) |
|---|
| 354 | + if (ms_hyperv.features & HV_ACCESS_REENLIGHTENMENT) { |
|---|
| 320 | 355 | alloc_intr_gate(HYPERV_REENLIGHTENMENT_VECTOR, |
|---|
| 321 | | - hyperv_reenlightenment_vector); |
|---|
| 356 | + asm_sysvec_hyperv_reenlightenment); |
|---|
| 357 | + } |
|---|
| 322 | 358 | |
|---|
| 323 | 359 | /* Setup the IDT for stimer0 */ |
|---|
| 324 | | - if (ms_hyperv.misc_features & HV_STIMER_DIRECT_MODE_AVAILABLE) |
|---|
| 360 | + if (ms_hyperv.misc_features & HV_STIMER_DIRECT_MODE_AVAILABLE) { |
|---|
| 325 | 361 | alloc_intr_gate(HYPERV_STIMER0_VECTOR, |
|---|
| 326 | | - hv_stimer0_callback_vector); |
|---|
| 362 | + asm_sysvec_hyperv_stimer0); |
|---|
| 363 | + } |
|---|
| 364 | + |
|---|
| 365 | +# ifdef CONFIG_SMP |
|---|
| 366 | + smp_ops.smp_prepare_boot_cpu = hv_smp_prepare_boot_cpu; |
|---|
| 367 | +# endif |
|---|
| 368 | + |
|---|
| 369 | + /* |
|---|
| 370 | + * Hyper-V doesn't provide irq remapping for IO-APIC. To enable x2apic, |
|---|
| 371 | + * set x2apic destination mode to physcial mode when x2apic is available |
|---|
| 372 | + * and Hyper-V IOMMU driver makes sure cpus assigned with IO-APIC irqs |
|---|
| 373 | + * have 8-bit APIC id. |
|---|
| 374 | + */ |
|---|
| 375 | +# ifdef CONFIG_X86_X2APIC |
|---|
| 376 | + if (x2apic_supported()) |
|---|
| 377 | + x2apic_phys = 1; |
|---|
| 378 | +# endif |
|---|
| 379 | + |
|---|
| 380 | + /* Register Hyper-V specific clocksource */ |
|---|
| 381 | + hv_init_clocksource(); |
|---|
| 327 | 382 | #endif |
|---|
| 383 | + /* |
|---|
| 384 | + * TSC should be marked as unstable only after Hyper-V |
|---|
| 385 | + * clocksource has been initialized. This ensures that the |
|---|
| 386 | + * stability of the sched_clock is not altered. |
|---|
| 387 | + */ |
|---|
| 388 | + if (!(ms_hyperv.features & HV_ACCESS_TSC_INVARIANT)) |
|---|
| 389 | + mark_tsc_unstable("running on Hyper-V"); |
|---|
| 328 | 390 | } |
|---|
| 329 | 391 | |
|---|
| 330 | 392 | const __initconst struct hypervisor_x86 x86_hyper_ms_hyperv = { |
|---|