Skip to content

Commit 421f090

Browse files
dcuiliuw
authored andcommitted
x86/hyperv: Suspend/resume the VP assist page for hibernation
Unlike the other CPUs, CPU0 is never offlined during hibernation, so in the resume path, the "new" kernel's VP assist page is not suspended (i.e. not disabled), and later when we jump to the "old" kernel, the page is not properly re-enabled for CPU0 with the allocated page from the old kernel. So far, the VP assist page is used by hv_apic_eoi_write(), and is also used in the case of nested virtualization (running KVM atop Hyper-V). For hv_apic_eoi_write(), when the page is not properly re-enabled, hvp->apic_assist is always 0, so the HV_X64_MSR_EOI MSR is always written. This is not ideal with respect to performance, but Hyper-V can still correctly handle this according to the Hyper-V spec; nevertheless, Linux still must update the Hyper-V hypervisor with the correct VP assist page to prevent Hyper-V from writing to the stale page, which causes guest memory corruption and consequently may have caused the hangs and triple faults seen during non-boot CPUs resume. Fix the issue by calling hv_cpu_die()/hv_cpu_init() in the syscore ops. Without the fix, hibernation can fail at a rate of 1/300 ~ 1/500. With the fix, hibernation can pass a long-haul test of 2000 runs. In the case of nested virtualization, disabling/reenabling the assist page upon hibernation may be unsafe if there are active L2 guests. It looks KVM should be enhanced to abort the hibernation request if there is any active L2 guest. Fixes: 05bd330 ("x86/hyperv: Suspend/resume the hypercall page for hibernation") Cc: [email protected] Signed-off-by: Dexuan Cui <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Wei Liu <[email protected]>
1 parent 2ddddd0 commit 421f090

File tree

1 file changed

+10
-2
lines changed

1 file changed

+10
-2
lines changed

arch/x86/hyperv/hv_init.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ static int hv_cpu_init(unsigned int cpu)
7373
struct page *pg;
7474

7575
input_arg = (void **)this_cpu_ptr(hyperv_pcpu_input_arg);
76-
pg = alloc_page(GFP_KERNEL);
76+
/* hv_cpu_init() can be called with IRQs disabled from hv_resume() */
77+
pg = alloc_page(irqs_disabled() ? GFP_ATOMIC : GFP_KERNEL);
7778
if (unlikely(!pg))
7879
return -ENOMEM;
7980
*input_arg = page_address(pg);
@@ -254,6 +255,7 @@ static int __init hv_pci_init(void)
254255
static int hv_suspend(void)
255256
{
256257
union hv_x64_msr_hypercall_contents hypercall_msr;
258+
int ret;
257259

258260
/*
259261
* Reset the hypercall page as it is going to be invalidated
@@ -270,12 +272,17 @@ static int hv_suspend(void)
270272
hypercall_msr.enable = 0;
271273
wrmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
272274

273-
return 0;
275+
ret = hv_cpu_die(0);
276+
return ret;
274277
}
275278

276279
static void hv_resume(void)
277280
{
278281
union hv_x64_msr_hypercall_contents hypercall_msr;
282+
int ret;
283+
284+
ret = hv_cpu_init(0);
285+
WARN_ON(ret);
279286

280287
/* Re-enable the hypercall page */
281288
rdmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
@@ -288,6 +295,7 @@ static void hv_resume(void)
288295
hv_hypercall_pg_saved = NULL;
289296
}
290297

298+
/* Note: when the ops are called, only CPU0 is online and IRQs are disabled. */
291299
static struct syscore_ops hv_syscore_ops = {
292300
.suspend = hv_suspend,
293301
.resume = hv_resume,

0 commit comments

Comments
 (0)