Repository navigation
Remove VP assist page for SNP VMs - #165
Jenna Goddard (jennagoddard) wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved dispatch issues can return stale exit data, lose intercept messages, and mishandle VTL1 exits.
3 open issues
What changed in this PR
Removes the SNP dispatch loop’s dependency on the hypervisor-provided VP assist page, using VMSA state and SynIC messages instead.
Changes:
- Stops allocating VP assist pages for SNP guests and makes APIC EOI access null-safe.
- Uses VMSA exit codes and tracks whether the lower VTL ran.
- Routes SNP intercept messages through SynIC and disables assist-page-dependent capabilities.
| File | Description |
|---|---|
drivers/hv/mshv_vtl_main.c |
Reworks SNP exit handling around VMSA state and SynIC messages. |
drivers/hv/hv_common.c |
Disables VP assist page allocation for SNP guests. |
arch/x86/hyperv/hv_apic.c |
Uses the null-safe VP assist page accessor. |
Review effort: Balanced
There was a problem hiding this comment.
🟡 Changes recommended
With interrupt offloading disabled, the halt-wake path overwrites userspace-managed VMSA state without guest execution, while interrupt-only exits introduce unnecessary userspace round trips.
2 open issues
1 resolved since last review
Review effort: Balanced
There was a problem hiding this comment.
🔵 Needs a closer look
Ignoring the intercept handler’s return value causes interrupt-only exits to return to userspace with HVMSG_NONE instead of re-entering the guest, adding a round trip for every host interrupt when injection offload is disabled.
No open issues 🎉
2 resolved since last review
Review effort: Balanced
d6c3c87 to
2b346bd
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
With interrupt-injection offload disabled, interrupt-only wakeups without an intercept now return to userspace with HVMSG_NONE instead of retrying in kernel, adding unnecessary round trips and bypassing the configured reentry policy.
No open issues 🎉
Review effort: Balanced
2b346bd to
135d584
Compare
a4362c9 to
012bddd
Compare
There was a problem hiding this comment.
🟡 Changes recommended
A SynIC-message race can leave a halted guest with queued messages unprocessed indefinitely, and interrupt-only wakeups now cause unnecessary userspace round trips when interrupt-injection offload is disabled.
1 open issue
Review effort: Balanced
|
From the previous test, the kernel change fails to go through SNP CI test pipeline and there is new oneline change from Open VMM. We may try SNP Ci test pipeline again. |
Dexuan Cui (dcui)
left a comment
There was a problem hiding this comment.
For TDX, the PR looks good to me since I don't think it really changes the TDX related code, and my TDX VM works fine with the PR.
The mshv SNP VTL return path uses vtl_entry_reason and intercept_message from the VP assist page to classify lower-VTL exits. The page is shared with the host in an SNP VM, so its contents are not trustworthy for exit dispatch. Do not allocate VP assist pages for SNP guests. Classify architectural exits from protected VMSA state and obtain synthetic intercept messages from the SynIC message page. Track whether the lower VTL ran so a local halt wakeup is not mistaken for a new VMSA exit, and keep the existing kernel exit offloads and timer state synchronized. Use a NULL-safe assist-page lookup in the Hyper-V APIC EOI path, and stop advertising assist-page-backed intercept and return-action capabilities for SNP and TDX guests. This does not change the userspace ABI. Signed-off-by: Jenna Goddard <jennagoddard@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
012bddd to
1314bb8
Compare
There was a problem hiding this comment.
🟡 Changes recommended
With interrupt-injection offload disabled, interrupt-only SNP returns unnecessarily exit to userspace instead of reentering in-kernel, adding kernel/userspace round trips.
1 open issue
1 resolved since last review
Review effort: Balanced
tiala
left a comment
There was a problem hiding this comment.
Try these changes on the local machine.
| @@ -890,6 +895,9 @@ static void mshv_vtl_scan_proxy_interrupts(struct hv_per_cpu_context *per_cpu) | |||
| break; | |||
|
|
|||
| if (message_type != HVMSG_X64_PROXY_INTERRUPT_INTERCEPT) { | |||
| /* SNP exit dispatch consumes non-proxy messages. */ | |||
| if (hv_isolation_type_snp()) | |||
| break; | |||
| WARN_ONCE(1, "Unexpected message type: %d\n", message_type); | |||
| vmbus_signal_eom(msg, message_type); | |||
| continue; | |||
| @@ -1871,9 +1879,13 @@ static bool mshv_vtl_process_intercept(void) | |||
| { | |||
| struct hv_per_cpu_context *mshv_cpu; | |||
| void *synic_message_page; | |||
| struct hv_message *exit_msg; | |||
| struct hv_message *msg; | |||
| u32 message_type; | |||
|
|
|||
| exit_msg = (struct hv_message *)mshv_vtl_this_run()->exit_message; | |||
| WRITE_ONCE(exit_msg->header.message_type, HVMSG_NONE); | |||
|
|
|||
| mshv_cpu = this_cpu_ptr(hv_context.cpu_context); | |||
| synic_message_page = mshv_cpu->synic_message_page; | |||
| if (unlikely(!synic_message_page)) | |||
| @@ -1884,7 +1896,7 @@ static bool mshv_vtl_process_intercept(void) | |||
| if (message_type == HVMSG_NONE) | |||
| return true; | |||
|
|
|||
| memcpy(mshv_vtl_this_run()->exit_message, msg, sizeof(*msg)); | |||
| memcpy(exit_msg, msg, sizeof(*msg)); | |||
| vmbus_signal_eom(msg, message_type); | |||
|
|
|||
| return false; | |||
| @@ -2041,7 +2053,7 @@ static void mshv_vtl_switch_to_vtl0_irqoff(void) | |||
|
|
|||
| armed = mshv_tdx_halt_timer_pre(flags & MSHV_VTL_RUN_FLAG_HALTED); | |||
|
|
|||
| /* A VTL2 TDX kernel doesn't allocate hv_vp_assist_page at the moment */ | |||
| /* Hardware-isolated guests don't allocate hv_vp_assist_page. */ | |||
| hvp = hv_vp_assist_page ? hv_vp_assist_page[smp_processor_id()] : NULL; | |||
|
|
|||
| /* | |||
| @@ -2095,10 +2107,13 @@ static void mshv_vtl_switch_to_vtl0_irqoff(void) | |||
| #endif | |||
| } if (hv_isolation_type_snp()) { | |||
| #if defined(CONFIG_SEV_GUEST) | |||
| if (unlikely(flags & MSHV_VTL_RUN_FLAG_HALTED)) | |||
| if (unlikely(flags & MSHV_VTL_RUN_FLAG_HALTED)) { | |||
| native_safe_halt(); | |||
| else | |||
| } else { | |||
| this_cpu_write(mshv_vtl_lower_vtl_ran, true); | |||
| snp_mshv_vtl_return(target_vtl.use_target_vtl ? target_vtl.target_vtl : 0); | |||
| this_cpu_inc(num_vtl0_transitions); | |||
| } | |||
| return; | |||
| #endif | |||
| } else { | |||
| @@ -2450,25 +2465,6 @@ static void mshv_snp_clear_exit_code(struct sev_es_save_area *vmsa, bool int_sha | |||
| vmsa->vintr_ctrl &= ~V_GUEST_BUSY_MASK; | |||
| } | |||
|
|
|||
| /* | |||
| * Determine if this interrupt was handled completely in the kernel. | |||
| * | |||
| * Returns true if the exit was handled entirely in kernel, and the VMPL should be re-entered. | |||
| * Returns false if the exit must be handled by user-space. | |||
| */ | |||
| static bool mshv_snp_try_handle_interrupt_entry(struct mshv_vtl_run *run) | |||
| { | |||
| struct hv_vp_assist_page *hvp = hv_vp_assist_page[smp_processor_id()]; | |||
|
|
|||
| if (!(run->offload_flags & MSHV_VTL_OFFLOAD_FLAG_INTR_INJECT) || | |||
| READ_ONCE(hvp->vtl_entry_reason) != MSHV_ENTRY_REASON_INTERRUPT) | |||
| return false; | |||
|
|
|||
| mshv_snp_clear_exit_code(snp_this_vmsa(), false); | |||
| return true; | |||
| } | |||
|
|
|||
|
|
|||
| /* | |||
| * Try to handle the incomplete IPI SEV-SNP exit. | |||
| * | |||
| @@ -2501,16 +2497,27 @@ static bool mshv_snp_try_handle_incomplete_ipi(struct mshv_vtl_run *run, | |||
| * Returns true if the exit was handled entirely in kernel, and the VMPL should be re-entered. | |||
| * Returns false if the exit must be handled by user-space. | |||
| */ | |||
| static bool mshv_snp_try_handle_exit(struct mshv_vtl_run *run) | |||
| static bool mshv_snp_try_handle_exit(struct mshv_vtl_run *run, bool lower_vtl_ran) | |||
| { | |||
| const bool intr_inject = MSHV_VTL_OFFLOAD_FLAG_INTR_INJECT & run->offload_flags; | |||
| const bool x2apic = MSHV_VTL_OFFLOAD_FLAG_X2APIC & run->offload_flags; | |||
| union hv_input_vtl target_vtl = READ_ONCE(run->target_vtl); | |||
| struct sev_es_save_area *vmsa; | |||
|
|
|||
| if (!intr_inject) | |||
| /* Kernel exit offloads are supported only for VTL 0. */ | |||
| if (target_vtl.use_target_vtl && target_vtl.target_vtl > 0) | |||
| return false; | |||
|
|
|||
| vmsa = snp_this_vmsa(); | |||
| /* | |||
| * native_safe_halt() may wake without entering the lower VTL. In that | |||
| * case the VMSA still contains the benign SVM_EXIT_INTR sentinel left | |||
| * by the previous handled exit. | |||
| */ | |||
| if (!intr_inject) | |||
| return false; | |||
| if (!lower_vtl_ran) | |||
| goto handled; | |||
|
|
|||
| switch (vmsa->guest_exit_code) { | |||
| case SVM_EXIT_AVIC_INCOMPLETE_IPI: | |||
| @@ -2528,19 +2535,10 @@ static bool mshv_snp_try_handle_exit(struct mshv_vtl_run *run) | |||
| run->offload_flags |= MSHV_VTL_OFFLOAD_FLAG_HALT_IDLE; | |||
| goto handled; | |||
| case SVM_EXIT_INTR: | |||
| if (message_type != HVMSG_X64_PROXY_INTERRUPT_INTERCEPT) { | ||
| /* SNP exit dispatch consumes non-proxy messages. */ | ||
| if (hv_isolation_type_snp()) | ||
| break; |
There was a problem hiding this comment.
Keep “continue” here or just ignore warning for snp guest.
| * case the VMSA still contains the benign SVM_EXIT_INTR sentinel left | ||
| * by the previous handled exit. |
There was a problem hiding this comment.
This is fast path code for halt operation while this version now only enables slow path for halt. So this will introduce conflict.


Remove mshv driver dependency on the hypervisor provided VP assist page in the main dispatch loop for SNP VMs. There is no user-visible impact. For SNP VMs the VP assist page is no longer registered, and the dispatch loop relies on VMSA state instead of hypervisor provided entry reason. Synic messsages are used instead of the intercept page.