Re: [PATCH 6/6] KVM: arm64: Add VNCR TLB tracking again
Yao Yuan <[email protected]> Tue, 4 Aug 2026 11:09:25 +0800
| Newsgroups | dev.linux.lists.kvmarm,org.infradead.lists.linux-arm-kernel,org.kernel.vger.kvm |
|---|---|
| Message-ID | <jtw5wkairjo6fxsv7lclgjkaixn4dwjtdufp6vtu3mmoidqp3l@cr5bmrvzz2ng> |
On Sat, Aug 01, 2026 at 01:48:18PM +0800, Marc Zyngier wrote: > Having established that our VNCR TLB tracking was flawed and dropped > it from KVM, it is time to replace it with something that works. > > The goal of that tracking is to hit the TLBI slow path if there > are any VNCR TLBs in the guest, irrespective of their mapping state. > > For this purpose, we introduce an VM wide counter (vncr_tlb_count) > that tracks how many valid VNCR TLB are present. This means that > creating such TLB must increment the counter, and invalidation > decrement it, and both these operations must be done with the MMU > lock held for write. > > On TLBI handling affecting EL2 S1, a non-zero counter forces the > handling to take the slow path to consider the VNCR TLBs. > > Not exactly rocket science. Hopefully I got it right this time. Reviewed-by: Yuan Yao <[email protected]> > > Signed-off-by: Marc Zyngier <[email protected]> > --- > arch/arm64/include/asm/kvm_host.h | 3 +++ > arch/arm64/kvm/hyp/vhe/switch.c | 5 +++-- > arch/arm64/kvm/nested.c | 25 +++++++++++++++++++------ > 3 files changed, 25 insertions(+), 8 deletions(-) > > diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h > index ac16f96c878d6..108966a9db12b 100644 > --- a/arch/arm64/include/asm/kvm_host.h > +++ b/arch/arm64/include/asm/kvm_host.h > @@ -411,6 +411,9 @@ struct kvm_arch { > /* Masks for VNCR-backed and general EL2 sysregs */ > struct kvm_sysreg_masks *sysreg_masks; > > + /* Count the number of VNCR_EL2 TLBs */ > + atomic_t vncr_tlb_count; > + > /* > * For an untrusted host VM, 'pkvm.handle' is used to lookup > * the associated pKVM instance in the hypervisor. > diff --git a/arch/arm64/kvm/hyp/vhe/switch.c b/arch/arm64/kvm/hyp/vhe/switch.c > index c09b1d411c584..eb59549ec2172 100644 > --- a/arch/arm64/kvm/hyp/vhe/switch.c > +++ b/arch/arm64/kvm/hyp/vhe/switch.c > @@ -424,10 +424,11 @@ static bool kvm_hyp_handle_tlbi_el2(struct kvm_vcpu *vcpu, u64 *exit_code) > return false; > > /* > - * If we have to check for any VNCR mapping being invalidated, > + * If we have to check for any VNCR TLB being invalidated, > * go back to the slow path for further processing. > */ > - if (vcpu_el2_e2h_is_set(vcpu) && vcpu_el2_tge_is_set(vcpu)) > + if (vcpu_el2_e2h_is_set(vcpu) && vcpu_el2_tge_is_set(vcpu) && > + atomic_read(&vcpu->kvm->arch.vncr_tlb_count)) > return false; > > __kvm_skip_instr(vcpu); > diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c > index 84915e2cff604..1e3fd98f6589b 100644 > --- a/arch/arm64/kvm/nested.c > +++ b/arch/arm64/kvm/nested.c > @@ -48,6 +48,7 @@ void kvm_init_nested(struct kvm *kvm) > { > kvm->arch.nested_mmus = NULL; > kvm->arch.nested_mmus_size = 0; > + atomic_set(&kvm->arch.vncr_tlb_count, 0); > } > > static int init_nested_s2_mmu(struct kvm *kvm, struct kvm_s2_mmu *mmu) > @@ -997,9 +998,11 @@ u16 get_asid_by_regime(struct kvm_vcpu *vcpu, enum trans_regime regime) > return asid; > } > > -static void invalidate_vncr(struct vncr_tlb *vt) > +static void invalidate_vncr(struct kvm *kvm, struct vncr_tlb *vt) > { > + BUG_ON(!vt->valid); > vt->valid = false; > + atomic_dec(&kvm->arch.vncr_tlb_count); > if (vt->cpu != -1) > unmap_l1_vncr(vt); > } > @@ -1042,7 +1045,7 @@ static void kvm_invalidate_vncr_ipa(struct kvm *kvm, u64 start, u64 end) > > kvm_for_each_vncr_tlb(i, vcpu, vt, kvm) > if (vncr_tlb_intersects(vt, vt->wr.pa, start, end - start)) > - invalidate_vncr(vt); > + invalidate_vncr(kvm, vt); > } > > struct s1e2_tlbi_scope { > @@ -1090,7 +1093,7 @@ static void invalidate_vncr_va(struct kvm *kvm, > break; > } > > - invalidate_vncr(vt); > + invalidate_vncr(kvm, vt); > } > } > > @@ -1326,13 +1329,20 @@ void kvm_arch_flush_shadow_all(struct kvm *kvm) > * intersects with the TLBI request, invalidate it, and unmap the page > * from the fixmap. Because we need to look at all the vcpu-private TLBs, > * this requires some wide-ranging locking to ensure that nothing races > - * against it. This may require some refcounting to avoid the search when > - * no such TLB is present. > + * against it. This requires some refcounting to avoid the search when > + * no such TLB is present (see below). > * > * - On MMU notifiers, we must invalidate our TLB in a similar way, but > * looking at the IPA instead. The funny part is that there may not be a > * stage-2 mapping for this page if L1 hasn't accessed it using LD/ST > * instructions. > + * > + * - vncr_tlb_count tracks the number of valid VNCR TLBs VM-wide. This isn't > + * the number of *mapped* L1 VNCR pages, which is likely be a subset (and > + * by definition, a TLBI handled from L1 runs with the canonical VNCR > + * page, not the L1's). The innermost trap handling code checks this to > + * find out whether to return to the guest ASAP (no L1 TLBs) or to visit > + * this part of the world for some extra invalidation work. > */ > > int kvm_vcpu_allocate_vncr_tlb(struct kvm_vcpu *vcpu) > @@ -1387,7 +1397,8 @@ static int kvm_translate_vncr(struct kvm_vcpu *vcpu, bool *is_gmem) > */ > scoped_guard(write_lock, &vcpu->kvm->mmu_lock) { > this_cpu_reset_vncr_fixmap(vcpu); > - vt->valid = false; > + if (vt->valid) > + invalidate_vncr(vcpu->kvm, vt); > > vt->wi = (struct s1_walk_info) { > .regime = TR_EL20, > @@ -1459,6 +1470,8 @@ static int kvm_translate_vncr(struct kvm_vcpu *vcpu, bool *is_gmem) > vt->valid = true; > vt->cpu = -1; > > + atomic_inc(&vcpu->kvm->arch.vncr_tlb_count); > + > kvm_make_request(KVM_REQ_MAP_L1_VNCR_EL2, vcpu); > kvm_release_faultin_page(vcpu->kvm, page, false, vt->wr.pw && vt->hpa_writable); > } > -- > 2.47.3 >