Add ARM64-specific code to initialize the Hyper-V hypervisor when booting as a guest VM. Provide functions and data structures indicating hypervisor status that are needed by VMbus driver. This code is built only when CONFIG_HYPERV is enabled. Signed-off-by: Michael Kelley <mikelley@xxxxxxxxxxxxx> --- arch/arm64/hyperv/mshyperv.c | 152 ++++++++++++++++++++++++++++++++++++++ arch/arm64/include/asm/mshyperv.h | 6 ++ arch/arm64/kernel/setup.c | 4 + 3 files changed, 162 insertions(+) diff --git a/arch/arm64/hyperv/mshyperv.c b/arch/arm64/hyperv/mshyperv.c index d202b4c..95f7e4e 100644 --- a/arch/arm64/hyperv/mshyperv.c +++ b/arch/arm64/hyperv/mshyperv.c @@ -14,6 +14,158 @@ #include <linux/types.h> #include <linux/export.h> #include <linux/ptrace.h> +#include <linux/errno.h> +#include <linux/acpi.h> +#include <linux/version.h> +#include <linux/cpuhotplug.h> +#include <linux/slab.h> +#include <linux/cpumask.h> +#include <asm/mshyperv.h> + +static bool hyperv_initialized; +struct ms_hyperv_info ms_hyperv __ro_after_init; +EXPORT_SYMBOL_GPL(ms_hyperv); + +bool hv_root_partition; +EXPORT_SYMBOL_GPL(hv_root_partition); + +u32 *hv_vp_index; +EXPORT_SYMBOL_GPL(hv_vp_index); + +u32 hv_max_vp_index; +EXPORT_SYMBOL_GPL(hv_max_vp_index); + +void __percpu **hyperv_pcpu_input_arg; +EXPORT_SYMBOL_GPL(hyperv_pcpu_input_arg); + +static int hv_cpu_init(unsigned int cpu) +{ + void **input_arg = (void **)this_cpu_ptr(hyperv_pcpu_input_arg); + + hv_vp_index[cpu] = hv_get_vpreg(HV_REGISTER_VP_INDEX); + + *input_arg = kmalloc(HV_HYP_PAGE_SIZE, GFP_KERNEL); + if (!(*input_arg)) + return -ENOMEM; + + return 0; +} + +void __init hyperv_early_init(void) +{ + struct hv_get_vp_registers_output result; + u32 a, b, c, d; + u64 guest_id; + + /* + * If we're in a VM on Hyper-V, the ACPI hypervisor_id field will + * have the string "MsHyperV". + */ + if (strncmp((char *)&acpi_gbl_FADT.hypervisor_id, "MsHyperV", 8)) + return; + + /* Setup the guest ID */ + guest_id = generate_guest_id(0, LINUX_VERSION_CODE, 0); + hv_set_vpreg(HV_REGISTER_GUEST_OSID, guest_id); + + /* Get the features and hints from Hyper-V */ + hv_get_vpreg_128(HV_REGISTER_FEATURES, &result); + ms_hyperv.features = result.as32.a; + ms_hyperv.priv_high = result.as32.b; + ms_hyperv.misc_features = result.as32.c; + + hv_get_vpreg_128(HV_REGISTER_ENLIGHTENMENTS, &result); + ms_hyperv.hints = result.as32.a; + + pr_info("Hyper-V: privilege flags low 0x%x, high 0x%x, hints 0x%x, misc 0x%x\n", + ms_hyperv.features, ms_hyperv.priv_high, ms_hyperv.hints, + ms_hyperv.misc_features); + + /* + * If Hyper-V has crash notifications, set crash_kexec_post_notifiers + * so that we will report the panic to Hyper-V before running kdump. + */ + if (ms_hyperv.misc_features & HV_FEATURE_GUEST_CRASH_MSR_AVAILABLE) + crash_kexec_post_notifiers = true; + + /* Get information about the Hyper-V host version */ + hv_get_vpreg_128(HV_REGISTER_HYPERVISOR_VERSION, &result); + a = result.as32.a; + b = result.as32.b; + c = result.as32.c; + d = result.as32.d; + pr_info("Hyper-V: Host Build %d.%d.%d.%d-%d-%d\n", + b >> 16, b & 0xFFFF, a, d & 0xFFFFFF, c, d >> 24); + + hyperv_initialized = true; +} + +static int __init hyperv_init(void) +{ + int i, ret; + + hyperv_pcpu_input_arg = alloc_percpu(void *); + if (!hyperv_pcpu_input_arg) + return -ENOMEM; + + /* Allocate and initialize percpu VP index array */ + hv_max_vp_index = num_possible_cpus(); + hv_vp_index = kmalloc_array(hv_max_vp_index, sizeof(*hv_vp_index), + GFP_KERNEL); + if (!hv_vp_index) { + ret = -ENOMEM; + goto free_input_arg; + } + + for (i = 0; i < hv_max_vp_index; i++) + hv_vp_index[i] = VP_INVAL; + + if (cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "arm64/hyperv_init:online", + hv_cpu_init, NULL) < 0) { + ret = -EINVAL; + goto free_vp_index; + } + + return 0; + +free_vp_index: + kfree(hv_vp_index); + hv_vp_index = NULL; + +free_input_arg: + hv_max_vp_index = 0; + free_percpu(hyperv_pcpu_input_arg); + hyperv_pcpu_input_arg = NULL; + return ret; +} + +early_initcall(hyperv_init); + +/* This routine is called before kexec/kdump. It does required cleanup. */ +void hyperv_cleanup(void) +{ + hv_set_vpreg(HV_REGISTER_GUEST_OSID, 0); + +} +EXPORT_SYMBOL_GPL(hyperv_cleanup); + +bool hv_is_hyperv_initialized(void) +{ + return hyperv_initialized; +} +EXPORT_SYMBOL_GPL(hv_is_hyperv_initialized); + +bool hv_is_hibernation_supported(void) +{ + return false; +} +EXPORT_SYMBOL_GPL(hv_is_hibernation_supported); + +bool hv_is_isolation_supported(void) +{ + return false; +} +EXPORT_SYMBOL_GPL(hv_is_isolation_supported); /* * The VMbus handler functions are no-ops on ARM64 because diff --git a/arch/arm64/include/asm/mshyperv.h b/arch/arm64/include/asm/mshyperv.h index b17299c..86ca5c5 100644 --- a/arch/arm64/include/asm/mshyperv.h +++ b/arch/arm64/include/asm/mshyperv.h @@ -23,6 +23,12 @@ #include <asm/hyperv-tlfs.h> #include <clocksource/arm_arch_timer.h> +#if IS_ENABLED(CONFIG_HYPERV) +void __init hyperv_early_init(void); +#else +static inline void hyperv_early_init(void) {}; +#endif + /* * Declare calls to get and set Hyper-V VP register values on ARM64, which * requires a hypercall. diff --git a/arch/arm64/kernel/setup.c b/arch/arm64/kernel/setup.c index 61845c0..7b17d6a 100644 --- a/arch/arm64/kernel/setup.c +++ b/arch/arm64/kernel/setup.c @@ -49,6 +49,7 @@ #include <asm/traps.h> #include <asm/efi.h> #include <asm/xen/hypervisor.h> +#include <asm/mshyperv.h> #include <asm/mmu_context.h> static int num_standard_resources; @@ -355,6 +356,9 @@ void __init __no_sanitize_address setup_arch(char **cmdline_p) if (acpi_disabled) unflatten_device_tree(); + /* Do after acpi_boot_table_init() so local FADT is available */ + hyperv_early_init(); + bootmem_init(); kasan_init(); -- 1.8.3.1