|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [PATCH v2 3/7] x86/nestedvmx: Reflect EPT holes hit during L2 emulation
From: Chunjie Zhu <chunjie.zhu@xxxxxxxxxx>
When L0 emulates an L2 instruction, e.g. a `movs` from MMIO, it
translates the other operand with paging_gva_to_gfn(). For an n2 vCPU
this walks L2's pagetables and then L1's EPT. If L1's EPT has no
mapping for the resulting L2 GPA, the walk returns
NESTEDHVM_PAGEFAULT_INJECT and paging_gva_to_gfn() returns INVALID_GFN.
The emulator then injects #PF into L2, although L2's own pagetables map
the address. L2 is blamed for a fault that is L1's to handle.
Reflect the hole to L1 as an EPT violation instead:
* paging_gva_to_gfn() calls nvmx_reflect_emul_ept_violation() on
INJECT. If the current L2 exit is an EPT violation, it records the
missing GPA and sets nv_vmexit_pending.
* sync_vvmcs_ro() reports that GPA to L1 as the guest-physical address
of the violation, together with the exit qualification computed by
the L1 EPT walk.
* hvm_inject_event() drops events for L2 while a virtual vmexit to L1
is pending, so the emulator's #PF does not stay in vmcs02.
L1 can then fix its EPT and resume L2, which re-executes the
instruction.
Signed-off-by: Chunjie Zhu <chunjie.zhu@xxxxxxxxxx>
Assisted-by: Claude:claude-sonnet-5
---
xen/arch/x86/hvm/hvm.c | 6 +++++
xen/arch/x86/hvm/vmx/vvmx.c | 30 +++++++++++++++++++++++++
xen/arch/x86/include/asm/hvm/vmx/vvmx.h | 4 ++++
xen/arch/x86/mm/p2m.c | 8 ++++++-
4 files changed, 47 insertions(+), 1 deletion(-)
diff --git a/xen/arch/x86/hvm/hvm.c b/xen/arch/x86/hvm/hvm.c
index ef9c7cd05777..b38f8dd68733 100644
--- a/xen/arch/x86/hvm/hvm.c
+++ b/xen/arch/x86/hvm/hvm.c
@@ -1801,6 +1801,12 @@ void hvm_inject_event(const struct x86_event *event)
}
}
+ /* L2 is about to exit to L1; an event for L2 would only be lost. */
+ if ( nestedhvm_enabled(curr->domain) &&
+ nestedhvm_vcpu_in_guestmode(curr) &&
+ vcpu_nestedhvm(curr).nv_vmexit_pending )
+ return;
+
alternative_vcall(hvm_funcs.inject_event, event);
}
diff --git a/xen/arch/x86/hvm/vmx/vvmx.c b/xen/arch/x86/hvm/vmx/vvmx.c
index 29e48387a110..0ec9eb6be450 100644
--- a/xen/arch/x86/hvm/vmx/vvmx.c
+++ b/xen/arch/x86/hvm/vmx/vvmx.c
@@ -1290,7 +1290,12 @@ static void sync_vvmcs_ro(struct vcpu *v)
{
set_vvmcs(v, EXIT_QUALIFICATION, nvmx->ept.exit_qual);
set_vvmcs(v, VM_EXIT_REASON, nvmx->ept.exit_reason);
+
+ if ( nvmx->ept.gpa_valid )
+ set_vvmcs(v, GUEST_PHYSICAL_ADDRESS, nvmx->ept.gpa);
}
+
+ nvmx->ept.gpa_valid = false;
}
static void load_vvmcs_host_state(struct vcpu *v)
@@ -2422,6 +2427,31 @@ void nvmx_idtv_handling(void)
}
}
+/*
+ * L0 is emulating an L2 instruction triggered by an EPT violation, and L1's
+ * EPT has a hole for another address the instruction accesses. Report that
+ * hole to L1 as the EPT violation, instead of blaming L2 with a #PF. Called
+ * after nvmx_hap_walk_L1_p2m() returned NESTEDHVM_PAGEFAULT_INJECT.
+ */
+bool nvmx_reflect_emul_ept_violation(struct vcpu *v, paddr_t L2_gpa)
+{
+ struct nestedvmx *nvmx = &vcpu_2_nvmx(v);
+ unsigned long reason;
+
+ if ( v != current || !nestedhvm_vcpu_in_guestmode(v) )
+ return false;
+
+ __vmread(VM_EXIT_REASON, &reason);
+ if ( (uint16_t)reason != EXIT_REASON_EPT_VIOLATION )
+ return false;
+
+ nvmx->ept.gpa = L2_gpa;
+ nvmx->ept.gpa_valid = true;
+ vcpu_nestedhvm(v).nv_vmexit_pending = 1;
+
+ return true;
+}
+
/*
* L2 VMExit handling
* return 1: Done or skip the normal layer 0 hypervisor process.
diff --git a/xen/arch/x86/include/asm/hvm/vmx/vvmx.h
b/xen/arch/x86/include/asm/hvm/vmx/vvmx.h
index da10d3fa9617..d5bc0ab40198 100644
--- a/xen/arch/x86/include/asm/hvm/vmx/vvmx.h
+++ b/xen/arch/x86/include/asm/hvm/vmx/vvmx.h
@@ -38,6 +38,9 @@ struct nestedvmx {
bool enabled;
uint32_t exit_reason;
uint32_t exit_qual;
+ /* Violation raised by L0's emulation, not by the L2 access itself */
+ bool gpa_valid;
+ paddr_t gpa;
} ept;
uint32_t guest_vpid;
struct list_head launched_list;
@@ -92,6 +95,7 @@ bool cf_check nvmx_ept_enabled(struct vcpu *v);
int cf_check nvmx_hap_walk_L1_p2m(
struct vcpu *v, paddr_t L2_gpa, paddr_t *L1_gpa, unsigned int *page_order,
uint8_t *p2m_acc, struct npfec npfec);
+bool nvmx_reflect_emul_ept_violation(struct vcpu *v, paddr_t L2_gpa);
/*
* Virtual VMCS layout
diff --git a/xen/arch/x86/mm/p2m.c b/xen/arch/x86/mm/p2m.c
index 52cadd9b25c6..7660ea48fc64 100644
--- a/xen/arch/x86/mm/p2m.c
+++ b/xen/arch/x86/mm/p2m.c
@@ -25,6 +25,8 @@
#include <asm/p2m.h>
#include <asm/mem_sharing.h>
#include <asm/hvm/nestedhvm.h>
+#include <asm/hvm/vmx/vmcs.h>
+#include <asm/hvm/vmx/vvmx.h>
#include <asm/altp2m.h>
#include <asm/vm_event.h>
#include <xsm/xsm.h>
@@ -1653,7 +1655,7 @@ unsigned long paging_gva_to_gfn(struct vcpu *v,
.write_access = *pfec & PFEC_write_access,
.insn_fetch = *pfec & PFEC_insn_fetch,
};
- int rv;
+ int rv = NESTEDHVM_PAGEFAULT_DONE;
/* translate l2 guest va into l2 guest gfn */
p2m = p2m_get_nestedp2m(v);
@@ -1667,7 +1669,11 @@ unsigned long paging_gva_to_gfn(struct vcpu *v,
v, pfn_to_paddr(l2_gfn), &l1_gpa, &l1_page_order, &l1_p2ma, npfec);
if ( rv != NESTEDHVM_PAGEFAULT_DONE )
+ {
+ if ( rv == NESTEDHVM_PAGEFAULT_INJECT && cpu_has_vmx )
+ nvmx_reflect_emul_ept_violation(v, pfn_to_paddr(l2_gfn));
return gfn_x(INVALID_GFN);
+ }
l1_gfn = paddr_to_pfn(l1_gpa);
--
2.34.1
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |