Re: [PATCH v3 26/27] KVM: nSVM: Use different ASIDs for L1 and L2
From: Sean Christopherson
Date: Thu Nov 05 2026 - 18:32:07 EST
On Tue, Oct 27, 2026, Sean Christopherson wrote:
> On Mon, Oct 26, 2026, Yosry Ahmed wrote:
> > On Mon, Oct 26, 2026 at 10:33 AM Sean Christopherson <seanjc@xxxxxxxxxx> wrote:
> > >
> > > On Thu, Oct 22, 2026, Yosry Ahmed wrote:
> > > > On Thu, Oct 22, 2026 at 3:44 PM Sean Christopherson <seanjc@xxxxxxxxxx> wrote:
> > > > >
> > > > > On Fri, Oct 16, 2026, Jim Mattson wrote:
> > > > > > On Mon, Oct 12, 2026 at 4:37 PM Yosry Ahmed <yosry@xxxxxxxxxx> wrote:
> > > > > > Moreover, does this change the semantics of PV TLB flush? In
> > > > > > record_steal_time(), we have:
> > > > > >
> > > > > > if (st_preempted & KVM_VCPU_FLUSH_TLB)
> > > > > > kvm_vcpu_flush_tlb_guest(vcpu);
> > > > > >
> > > > > > IIUC, an idle vCPU that wakes into L2 will flush only L2's ASID. In
> > > > > > the past, with a shared ASID, L1's ASID was always flushed on emulated
> > > > > > VM-exit anyway. Now, L1's ASID will be unaffected. Though I can't find
> > > > > > any real documentation on PV TLB flush, it *seems* like the remote
> > > > > > vCPU would want L1's ASID to be flushed. OTOH, this change makes nSVM
> > > > > > behave just like nVMX.
> > > > >
> > > > > That's a bug that absolutely needs to be fixed, probably relatively urgently. We
> > > > > should also document that passing through the steal_time shared information to L2
> > > > > to utilize PV TLB flushes for L2 is explicitly not supported.
> > > > >
> > > > > I hate to re-introduce deferred actions for L2 => L1 things, but that seems like
> > > > > the only sane way to handle this, because handling the !tdp_enabled case, where
> > > > > KVM needs to sync the current root, requires knowing what MMU is going to be in
> > > > > use when L1 regains control. Yuck.
> > > >
> > > > Are you implying that *you* will be re-introducing deferred actions? :P
> > >
> > > Heh, probably not this calendar year.
> >
> > Are you opposed to this series landing before that is fixed, keeping
> > in mind that it will expose nSVM to the pre-existing bug?
>
> Nah, though I should just fix the damn bug. It's not hard, I just don't want to
> do it because I don't want to reintroduce a deferred request. Untested, but I
> think this is all that's needed.
Early warning, so that you don't post a new version of the series and then have
to respin again. I'm planning on posting a fix that plumbs an @l1_pv_flush flag
around instead of deferring the fix. I realized that KVM synchronizes *all* MMUs
for the !tdp_enabled case, which means that all flushing immediately is overall
less risky than deferring the flush (and obviously I was looking for any excuse
to not re-introduce deferred actions).
I peeked at the rest of this series, and AFAICT it won't throw too big of a wrench
in things.
---
KVM: x86: Always target L1 when handling steal-time PV TLB flushes
Explicitly target L1 when handling steal-time based PV TLB flushes, as
KVM's undocumented ABI for its PV features is that they are scoped to L1,
e.g. KVM hypercalls from L2 are routed to L1 or rejected with a #UD.
Route a flag through all the flush_tlb_guest() paths to basically ignore
is_guest_mode() as appropriate. Note! KVM synchronizes all MMUs, past
and present, when servicing a guest TLB flush, i.e. there is no need to
switch from targeting L2 to targeting L1 for that specific flow.
Alternatively, KVM could defer PV TLB flushes until L1 is active, but the
correctness of waiting to synchronize MMUs when TDP is disabled is dubious,
as KVM has historically synchronized *all* MMUs for the vCPU. And from the
guest's perspective (other vCPUs), KVM has communicated the the flush has
already occurred. And in general, KVM has moved away from deferred L2=>L1
actions, precisely because of similar edge cases.
Fixes: 2b4a5a5d5688 ("KVM: nVMX: Flush current VPID (L1 vs. L2) for KVM_REQ_TLB_FLUSH_GUEST")
Cc: stable@xxxxxxxxxxxxxxx
Reported-by: Jim Mattson <jmattson@xxxxxxxxxx>
Closes: https://lore.kernel.org/all/CALMp9eS8KDozNGT9J_uN69W32QEvTbg2JrtRqN0ESi00b_uq=A@xxxxxxxxxxxxxx
Cc: Yosry Ahmed <yosry@xxxxxxxxxx>
Signed-off-by: Sean Christopherson <seanjc@xxxxxxxxxx>
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 0378577d9d8b..51bb66d1618a 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -1598,7 +1598,7 @@ struct kvm_x86_ops {
* Flush any TLB entries created by the guest. Like tlb_flush_gva(),
* does not need to flush GPA->HPA mappings.
*/
- void (*flush_tlb_guest)(struct kvm_vcpu *vcpu);
+ void (*flush_tlb_guest)(struct kvm_vcpu *vcpu, bool l1_pv_flush);
bool (*vcpu_needs_initialization)(struct kvm_vcpu *vcpu);
enum exit_fastpath_completion (*vcpu_run)(struct kvm_vcpu *vcpu,
diff --git a/arch/x86/kvm/hyperv.h b/arch/x86/kvm/hyperv.h
index 622a6553e9ac..3ece27d34398 100644
--- a/arch/x86/kvm/hyperv.h
+++ b/arch/x86/kvm/hyperv.h
@@ -312,14 +312,16 @@ static inline struct kvm_vcpu_hv_tlb_flush_fifo *kvm_hv_get_tlb_flush_fifo(struc
return &hv_vcpu->tlb_flush_fifo[i];
}
-static inline void kvm_hv_vcpu_purge_flush_tlb(struct kvm_vcpu *vcpu)
+static inline void kvm_hv_vcpu_purge_flush_tlb(struct kvm_vcpu *vcpu,
+ bool l1_pv_flush)
{
struct kvm_vcpu_hv_tlb_flush_fifo *tlb_flush_fifo;
if (!kvm_check_request(KVM_REQ_HV_TLB_FLUSH, vcpu))
return;
- tlb_flush_fifo = kvm_hv_get_tlb_flush_fifo(vcpu, is_guest_mode(vcpu));
+ tlb_flush_fifo = kvm_hv_get_tlb_flush_fifo(vcpu, !l1_pv_flush &&
+ is_guest_mode(vcpu));
if (!tlb_flush_fifo)
return;
@@ -396,7 +398,7 @@ static inline int kvm_hv_hypercall(struct kvm_vcpu *vcpu)
{
return HV_STATUS_ACCESS_DENIED;
}
-static inline void kvm_hv_vcpu_purge_flush_tlb(struct kvm_vcpu *vcpu) {}
+static inline void kvm_hv_vcpu_purge_flush_tlb(struct kvm_vcpu *vcpu, bool l1_pv_flush) {}
static inline bool kvm_hv_synic_has_vector(struct kvm_vcpu *vcpu, int vector)
{
return false;
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index 581bc3a23c95..f045dc010027 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -4304,7 +4304,7 @@ static void svm_enable_nmi_window(struct kvm_vcpu *vcpu)
svm->vmcb->save.rflags |= (X86_EFLAGS_TF | X86_EFLAGS_RF);
}
-static void svm_flush_tlb_asid(struct kvm_vcpu *vcpu)
+static void svm_flush_tlb_asid(struct kvm_vcpu *vcpu, bool l1_pv_flush)
{
struct vcpu_svm *svm = to_svm(vcpu);
@@ -4313,14 +4313,14 @@ static void svm_flush_tlb_asid(struct kvm_vcpu *vcpu)
* A TLB flush for the current ASID flushes both "host" and "guest" TLB
* entries, and thus is a superset of Hyper-V's fine grained flushing.
*/
- kvm_hv_vcpu_purge_flush_tlb(vcpu);
+ kvm_hv_vcpu_purge_flush_tlb(vcpu, l1_pv_flush);
/*
* Flush only the current ASID even if the TLB flush was invoked via
- * kvm_flush_remote_tlbs(). Although flushing remote TLBs requires all
- * ASIDs to be flushed, KVM uses a single ASID for L1 and L2, and
- * unconditionally does a TLB flush on both nested VM-Enter and nested
- * VM-Exit (via kvm_mmu_reset_context()).
+ * kvm_flush_remote_tlbs() or is a PV TLB flush targeting L1. Although
+ * flushing remote TLBs requires all ASIDs to be flushed, KVM uses a
+ * single ASID for L1 and L2, and unconditionally does a TLB flush on
+ * both nested VM-Enter and nested VM-Exit (via kvm_mmu_reset_context()).
*/
if (cpu_feature_enabled(X86_FEATURE_FLUSHBYASID))
svm->vmcb->control.tlb_ctl = TLB_CONTROL_FLUSH_ASID;
@@ -4341,7 +4341,7 @@ static void svm_flush_tlb_current(struct kvm_vcpu *vcpu)
if (svm_hv_is_enlightened_tlb_enabled(vcpu) && VALID_PAGE(root_tdp))
hyperv_flush_guest_mapping(root_tdp);
- svm_flush_tlb_asid(vcpu);
+ svm_flush_tlb_asid(vcpu, false);
}
static void svm_flush_tlb_all(struct kvm_vcpu *vcpu)
@@ -4356,14 +4356,14 @@ static void svm_flush_tlb_all(struct kvm_vcpu *vcpu)
if (WARN_ON_ONCE(svm_hv_is_enlightened_tlb_enabled(vcpu)))
hv_flush_remote_tlbs(vcpu->kvm);
- svm_flush_tlb_asid(vcpu);
+ svm_flush_tlb_asid(vcpu, false);
}
-static void svm_flush_tlb_guest(struct kvm_vcpu *vcpu)
+static void svm_flush_tlb_guest(struct kvm_vcpu *vcpu, bool l1_pv_flush)
{
kvm_register_mark_dirty(vcpu, VCPU_REG_ERAPS);
- svm_flush_tlb_asid(vcpu);
+ svm_flush_tlb_asid(vcpu, l1_pv_flush);
}
static void svm_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t gva)
@@ -4381,7 +4381,7 @@ static void svm_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t gva)
return;
}
- svm_flush_tlb_guest(vcpu);
+ svm_flush_tlb_guest(vcpu, false);
}
static inline void sync_cr8_to_lapic(struct kvm_vcpu *vcpu)
diff --git a/arch/x86/kvm/vmx/main.c b/arch/x86/kvm/vmx/main.c
index d75170a6199e..c2b5c04cd2b8 100644
--- a/arch/x86/kvm/vmx/main.c
+++ b/arch/x86/kvm/vmx/main.c
@@ -547,12 +547,12 @@ static void vt_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t addr)
vmx_flush_tlb_gva(vcpu, addr);
}
-static void vt_flush_tlb_guest(struct kvm_vcpu *vcpu)
+static void vt_flush_tlb_guest(struct kvm_vcpu *vcpu, bool l1_pv_flush)
{
if (is_td_vcpu(vcpu))
return;
- vmx_flush_tlb_guest(vcpu);
+ vmx_flush_tlb_guest(vcpu, l1_pv_flush);
}
static void vt_inject_nmi(struct kvm_vcpu *vcpu)
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index b341af4c6057..e6a61514e797 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -3398,7 +3398,7 @@ void vmx_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t addr)
vpid_sync_vcpu_addr(vmx_get_current_vpid(vcpu), addr);
}
-void vmx_flush_tlb_guest(struct kvm_vcpu *vcpu)
+void vmx_flush_tlb_guest(struct kvm_vcpu *vcpu, bool l1_pv_flush)
{
/*
* vpid_sync_context() is a nop if vpid==0, e.g. if enable_vpid==0 or a
@@ -3407,7 +3407,10 @@ void vmx_flush_tlb_guest(struct kvm_vcpu *vcpu)
* disabled (VM-Enter with vpid enabled and vpid==0 is disallowed),
* i.e. no explicit INVVPID is necessary.
*/
- vpid_sync_context(vmx_get_current_vpid(vcpu));
+ if (l1_pv_flush)
+ vpid_sync_context(to_vmx(vcpu)->vpid);
+ else
+ vpid_sync_context(vmx_get_current_vpid(vcpu));
}
void vmx_ept_load_pdptrs(struct kvm_vcpu *vcpu)
diff --git a/arch/x86/kvm/vmx/x86_ops.h b/arch/x86/kvm/vmx/x86_ops.h
index cd72684d075d..67e5dd728267 100644
--- a/arch/x86/kvm/vmx/x86_ops.h
+++ b/arch/x86/kvm/vmx/x86_ops.h
@@ -82,7 +82,7 @@ bool vmx_get_if_flag(struct kvm_vcpu *vcpu);
void vmx_flush_tlb_all(struct kvm_vcpu *vcpu);
void vmx_flush_tlb_current(struct kvm_vcpu *vcpu);
void vmx_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t addr);
-void vmx_flush_tlb_guest(struct kvm_vcpu *vcpu);
+void vmx_flush_tlb_guest(struct kvm_vcpu *vcpu, bool l1_pv_flush);
void vmx_set_interrupt_shadow(struct kvm_vcpu *vcpu, int mask);
u32 vmx_get_interrupt_shadow(struct kvm_vcpu *vcpu);
void vmx_patch_hypercall(struct kvm_vcpu *vcpu, unsigned char *hypercall);
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 26e7beaff8a0..3bbd15b64e99 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -2009,7 +2009,7 @@ static void kvm_vcpu_flush_tlb_all(struct kvm_vcpu *vcpu)
kvm_clear_request(KVM_REQ_TLB_FLUSH_CURRENT, vcpu);
}
-static void kvm_vcpu_flush_tlb_guest(struct kvm_vcpu *vcpu)
+static void kvm_vcpu_flush_tlb_guest(struct kvm_vcpu *vcpu, bool l1_pv_flush)
{
++vcpu->stat.tlb_flush;
@@ -2024,13 +2024,13 @@ static void kvm_vcpu_flush_tlb_guest(struct kvm_vcpu *vcpu)
kvm_mmu_sync_prev_roots(vcpu);
}
- kvm_x86_call(flush_tlb_guest)(vcpu);
+ kvm_x86_call(flush_tlb_guest)(vcpu, l1_pv_flush);
/*
* Flushing all "guest" TLB is always a superset of Hyper-V's fine
* grained flushing.
*/
- kvm_hv_vcpu_purge_flush_tlb(vcpu);
+ kvm_hv_vcpu_purge_flush_tlb(vcpu, l1_pv_flush);
}
@@ -2052,7 +2052,7 @@ void kvm_service_local_tlb_flush_requests(struct kvm_vcpu *vcpu)
kvm_vcpu_flush_tlb_current(vcpu);
if (kvm_check_request(KVM_REQ_TLB_FLUSH_GUEST, vcpu))
- kvm_vcpu_flush_tlb_guest(vcpu);
+ kvm_vcpu_flush_tlb_guest(vcpu, false);
}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_service_local_tlb_flush_requests);
@@ -2118,7 +2118,7 @@ static void record_steal_time(struct kvm_vcpu *vcpu)
trace_kvm_pv_tlb_flush(vcpu->vcpu_id,
st_preempted & KVM_VCPU_FLUSH_TLB);
if (st_preempted & KVM_VCPU_FLUSH_TLB)
- kvm_vcpu_flush_tlb_guest(vcpu);
+ kvm_vcpu_flush_tlb_guest(vcpu, true);
if (!user_access_begin(st, sizeof(*st)))
goto dirty;
@@ -8193,7 +8193,7 @@ static int vcpu_enter_guest(struct kvm_vcpu *vcpu)
#ifdef CONFIG_KVM_HYPERV
if (kvm_check_request(KVM_REQ_HV_TLB_FLUSH, vcpu) &&
kvm_hv_vcpu_flush_tlb(vcpu))
- kvm_vcpu_flush_tlb_guest(vcpu);
+ kvm_vcpu_flush_tlb_guest(vcpu, false);
#endif
if (kvm_check_request(KVM_REQ_REPORT_TPR_ACCESS, vcpu)) {