I tracked this problem down as I was hitting the same issue and it turns out it's related to the hypercall mechanism.
Hyper-V allocates an executable page doing:
virtaddr = __vmalloc(PAGE_SIZE, GFP_KERNEL, PAGE_KERNEL_RX);
and passes the physical address to the hypervisor doing:
hypercall_msr.guest_physical_address = vmalloc_to_pfn(virtaddr);
wrmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
then the do_hypercall function just calls into this virtaddr for doing the communcation (it uses an SGDT [ecx] instruction for triggering the VMM).
The problem is that it hangs while trying to execute that instruction from that vmalloc'd memory.
I tried to change PAGE_KERNEL_RX to PAGE_KERNEL_EXEC, didn't work...
My fix, which I'd like to know if it is kosher enough, was to change this code to do what Xen does: reserve some memory on the .text section for the hypercall (see ENTRY(hypercall_page) on xen-head.S).
So I just reserved a PAGE_SIZE buffer same way and adapted the code to use it.
This is the patch for 3.14.17:
- Code: Select all
diff --git a/arch/x86/include/asm/mshyperv.h b/arch/x86/include/asm/mshyperv.h
index cd9c419..28935a7 100644
--- a/arch/x86/include/asm/mshyperv.h
+++ b/arch/x86/include/asm/mshyperv.h
@@ -10,6 +10,7 @@ struct ms_hyperv_info {
};
extern struct ms_hyperv_info ms_hyperv;
+extern struct { char _entry[PAGE_SIZE]; } hv_hypercall_page;
void hyperv_callback_vector(void);
#ifdef CONFIG_TRACING
diff --git a/arch/x86/kernel/cpu/mshyperv.c b/arch/x86/kernel/cpu/mshyperv.c
index 832d05a..6215fcb 100644
--- a/arch/x86/kernel/cpu/mshyperv.c
+++ b/arch/x86/kernel/cpu/mshyperv.c
@@ -30,6 +30,7 @@
struct ms_hyperv_info ms_hyperv;
EXPORT_SYMBOL_GPL(ms_hyperv);
+EXPORT_SYMBOL_GPL(hv_hypercall_page);
static uint32_t __init ms_hyperv_platform(void)
{
diff --git a/arch/x86/kernel/head_64.S b/arch/x86/kernel/head_64.S
index c7dec74..2aec589 100644
--- a/arch/x86/kernel/head_64.S
+++ b/arch/x86/kernel/head_64.S
@@ -608,6 +608,12 @@ ENTRY(phys_base)
/* This must match the first entry in level2_kernel_pgt */
.quad 0x0000000000000000
+.pushsection .text
+ .balign PAGE_SIZE
+ENTRY(hv_hypercall_page)
+ .skip PAGE_SIZE
+.popsection
+
#include "../../x86/xen/xen-head.S"
.section .rodata,"a",@progbits
diff --git a/drivers/hv/hv.c b/drivers/hv/hv.c
index 61dba6c..764971e 100644
--- a/drivers/hv/hv.c
+++ b/drivers/hv/hv.c
@@ -29,6 +29,7 @@
#include <linux/version.h>
#include <linux/interrupt.h>
#include <asm/hyperv.h>
+#include <asm/mshyperv.h>
#include "hyperv_vmbus.h"
/* The one and only */
@@ -133,7 +134,6 @@ int hv_init(void)
{
int max_leaf;
union hv_x64_msr_hypercall_contents hypercall_msr;
- void *virtaddr = NULL;
memset(hv_context.synic_event_page, 0, sizeof(void *) * NR_CPUS);
memset(hv_context.synic_message_page, 0,
@@ -154,14 +154,9 @@ int hv_init(void)
/* See if the hypercall page is already set */
rdmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
- virtaddr = __vmalloc(PAGE_SIZE, GFP_KERNEL, PAGE_KERNEL_RX);
-
- if (!virtaddr)
- goto cleanup;
-
+ hypercall_msr.as_uint64 = __pa(&hv_hypercall_page);
hypercall_msr.enable = 1;
-
- hypercall_msr.guest_physical_address = vmalloc_to_pfn(virtaddr);
+ hypercall_msr.reserved = 0;
wrmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
/* Confirm that hypercall page did get setup. */
@@ -171,20 +166,16 @@ int hv_init(void)
if (!hypercall_msr.enable)
goto cleanup;
- hv_context.hypercall_page = virtaddr;
+ hv_context.hypercall_page = &hv_hypercall_page;
return 0;
cleanup:
- if (virtaddr) {
if (hypercall_msr.enable) {
hypercall_msr.as_uint64 = 0;
wrmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
}
- vfree(virtaddr);
- }
-
return -ENOTSUPP;
}
@@ -203,7 +194,6 @@ void hv_cleanup(void)
if (hv_context.hypercall_page) {
hypercall_msr.as_uint64 = 0;
wrmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
- vfree(hv_context.hypercall_page);
hv_context.hypercall_page = NULL;
}
}