Merge branch 'kvm-arm64/vncr-fixes' into next

* kvm-arm64/vncr-fixes:
  : Fixes for handling L1 VNCR mappings, courtesy of Marc Zyngier
  :
  :  - Recompute the VNCR software TLB when the MMU is toggled at stage-1
  :
  :  - Improve TLB invalidation intersection to handle TLBIs affecting the
  :    end of the VA space
  :
  :  - Fix race to invalidate the VNCR fixmap between TLBI emulation and
  :    vcpu_put()
  :
  :  - Add missing sign extension for computing TLBI ranges
  :
  :  - Make VNCR invalidation participate in the MMU notifier seqcount,
  :    preventing a concurrent VNCR TLB fill from consuming a stale
  :    translation
  KVM: arm64: Correctly cap TLBI Range to the architural limit
  KVM: arm64: Add VNCR TLB tracking again
  KVM: arm64: Make VNCR invalidation participate in MMU invalidation retry
  KVM: arm64: Sign-extend VA for range-based TLBI invalidation
  KVM: arm64: Handle VNCR TLB invalidation race with vcpu_put() VNCR unmapping
  KVM: arm64: Correctly handle end of VA space TLBI invalidation
  KVM: arm64: Consider SCTLR_EL2.M when mapping the L1 VNCR page
  KVM: arm64: Handle negative S1 walk levels in VNCR TLB size evaluation
  KVM: arm64: Remove VM-wide VNCR mapping counter

Signed-off-by: Oliver Upton <oupton@kernel.org>
This commit is contained in:
Oliver Upton 2026-08-19 12:28:16 -07:00
commit c75d616c08
6 changed files with 161 additions and 59 deletions

View File

@ -414,8 +414,8 @@ struct kvm_arch {
/* Masks for VNCR-backed and general EL2 sysregs */
struct kvm_sysreg_masks *sysreg_masks;
/* Count the number of VNCR_EL2 currently mapped */
atomic_t vncr_map_count;
/* Count the number of VNCR_EL2 TLBs */
atomic_t vncr_tlb_count;
/*
* For an untrusted host VM, 'pkvm.handle' is used to lookup

View File

@ -291,6 +291,13 @@ static inline u64 decode_range_tlbi(u64 val, u64 *range, u16 *asid)
base = (val & GENMASK(36, 0)) << shift;
/*
* We only deal with at most 48bit VA/IPA, so 48 is where we
* sign-extend from. Should we support FEAT_L{VP}A* at some point,
* this will need to be revisited.
*/
base = (u64)sign_extend64(base, 48);
if (asid)
*asid = FIELD_GET(TLBIR_ASID_MASK, val);
@ -298,6 +305,12 @@ static inline u64 decode_range_tlbi(u64 val, u64 *range, u16 *asid)
num = FIELD_GET(GENMASK(43, 39), val);
*range = __TLBI_RANGE_PAGES(num, scale) << shift;
/* Cap the range to the correct half of the address space */
if (!(base & BIT(48)))
*range = min(*range, (BIT(48) - base));
else
*range = min(*range, ~base + 1);
return base;
}
@ -388,6 +401,8 @@ struct s1_walk_result {
bool failed;
};
#define S1_MMU_DISABLED (-127)
static inline void fail_s1_walk(struct s1_walk_result *wr, u8 fst, bool s1ptw)
{
wr->fst = fst;
@ -396,6 +411,11 @@ static inline void fail_s1_walk(struct s1_walk_result *wr, u8 fst, bool s1ptw)
wr->failed = true;
}
static inline bool s1_walk_translated(struct s1_walk_result *wr)
{
return wr->level != S1_MMU_DISABLED;
}
int __kvm_translate_va(struct kvm_vcpu *vcpu, struct s1_walk_info *wi,
struct s1_walk_result *wr, u64 va);
int __kvm_find_s1_desc_level(struct kvm_vcpu *vcpu, u64 va, u64 ipa,

View File

@ -11,8 +11,6 @@
#include <asm/kvm_mmu.h>
#include <asm/lsui.h>
#define S1_MMU_DISABLED (-127)
static int get_ia_size(struct s1_walk_info *wi)
{
return 64 - wi->txsz;

View File

@ -436,11 +436,15 @@ 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,
* go back to the slow path for further processing.
* If we have to check for any VNCR TLB being invalidated, go back
* to the slow path for further processing.
*
* The synchronisation betweem TLBI and walk is provided by the
* speculative increment of the TLB counter on walk, and the
* invalidation counter. Yes, this is fiddly.
*/
if (vcpu_el2_e2h_is_set(vcpu) && vcpu_el2_tge_is_set(vcpu) &&
atomic_read(&vcpu->kvm->arch.vncr_map_count))
atomic_read(&vcpu->kvm->arch.vncr_tlb_count))
return false;
__kvm_skip_instr(vcpu);

View File

@ -27,7 +27,7 @@ struct vncr_tlb {
bool hpa_writable;
/* -1 when not mapped on a CPU */
int cpu;
atomic_t cpu;
/*
* true if the TLB is valid. Can only be changed with the
@ -48,7 +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_map_count, 0);
atomic_set(&kvm->arch.vncr_tlb_count, 0);
}
static int init_nested_s2_mmu(struct kvm *kvm, struct kvm_s2_mmu *mmu)
@ -506,7 +506,7 @@ int kvm_walk_nested_s2(struct kvm_vcpu *vcpu, phys_addr_t gipa,
return ret;
}
static unsigned int ttl_to_size(u8 ttl)
static unsigned int __ttl_to_size(u8 ttl)
{
int level = ttl & 3;
int gran = (ttl >> 2) & 3;
@ -562,10 +562,22 @@ static unsigned int ttl_to_size(u8 ttl)
return max_size;
}
static u8 pgshift_level_to_ttl(u16 shift, u8 level)
static unsigned int ttl_to_size(u8 ttl)
{
return __ttl_to_size(ttl) ?: SZ_1G;
}
static u8 pgshift_level_to_ttl(u16 shift, s8 level)
{
u8 ttl;
/*
* If we don't have a proper level, fallback to the maximum
* size.
*/
if (level < 0)
return 0;
switch(shift) {
case 12:
ttl = TLBI_TTL_TG_4K;
@ -676,7 +688,11 @@ unsigned long compute_tlb_inval_range(struct kvm_s2_mmu *mmu, u64 val)
ttl = get_guest_mapping_ttl(mmu, addr);
}
max_size = ttl_to_size(ttl);
/*
* Don't use the default 1GB fallback, as we can adapt to the
* max mapping size we allow at S2.
*/
max_size = __ttl_to_size(ttl);
if (!max_size) {
/* Compute the maximum extent of the invalidation */
@ -879,18 +895,41 @@ void kvm_vcpu_load_hw_mmu(struct kvm_vcpu *vcpu)
}
}
/*
* Unmapping an L1 VNCR can happen concurrently without the mmu lock being
* effective (vcpu_put() vs TLBI handling). The atomic_xchg below ensures
* that only one CPU sets it to -1 while getting a valid CPU number back.
*/
static int unmap_l1_vncr(struct vncr_tlb *vt)
{
int cpu = atomic_xchg_relaxed(&vt->cpu, -1);
if (cpu != -1)
clear_fixmap(vncr_fixmap(cpu));
return cpu;
}
static void this_cpu_reset_vncr_fixmap(struct kvm_vcpu *vcpu)
{
if (!host_data_test_flag(L1_VNCR_MAPPED))
return;
BUG_ON(vcpu->arch.vncr_tlb->cpu != smp_processor_id());
BUG_ON(is_hyp_ctxt(vcpu));
clear_fixmap(vncr_fixmap(vcpu->arch.vncr_tlb->cpu));
vcpu->arch.vncr_tlb->cpu = -1;
/*
* Unconditionally unmap the local VNCR if we have lost the race
* against a concurrent TLBI. Otherwise we could end-up running
* another vcpu with VNCR still mapped if the TLBI thread is
* preempted between the exchange and the clear_fixmap().
*
* Note that we do not care about the TLBI nuking the fixmap behind
* the back of an running vcpu. This will only generate a fault and
* possibly a retranslation.
*/
if (unmap_l1_vncr(vcpu->arch.vncr_tlb) == -1)
clear_fixmap(vncr_fixmap(smp_processor_id()));
host_data_clear_flag(L1_VNCR_MAPPED);
atomic_dec(&vcpu->kvm->arch.vncr_map_count);
}
void kvm_vcpu_put_hw_mmu(struct kvm_vcpu *vcpu)
@ -978,11 +1017,26 @@ 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;
if (vt->cpu != -1)
clear_fixmap(vncr_fixmap(vt->cpu));
unmap_l1_vncr(vt);
atomic_dec(&kvm->arch.vncr_tlb_count);
}
static bool vncr_tlb_intersects(struct vncr_tlb *vt, u64 addr,
u64 scope_start, u64 scope_size)
{
u64 tlb_size, tlb_start, tlb_end, scope_end;
tlb_size = ttl_to_size(pgshift_level_to_ttl(vt->wi.pgshift, vt->wr.level));
tlb_start = addr & ~(tlb_size - 1);
tlb_end = tlb_start + tlb_size - 1;
scope_end = scope_start + scope_size - 1;
return !(tlb_end < scope_start || tlb_start > scope_end);
}
/*
@ -1007,19 +1061,15 @@ static void kvm_invalidate_vncr_ipa(struct kvm *kvm, u64 start, u64 end)
if (!kvm_has_feat(kvm, ID_AA64MMFR4_EL1, NV_frac, NV2_ONLY))
return;
kvm_for_each_vncr_tlb(i, vcpu, vt, kvm) {
u64 ipa_start, ipa_end, ipa_size;
ipa_size = ttl_to_size(pgshift_level_to_ttl(vt->wi.pgshift,
vt->wr.level));
ipa_start = vt->wr.pa & ~(ipa_size - 1);
ipa_end = ipa_start + ipa_size;
if (ipa_end <= start || ipa_start >= end)
continue;
invalidate_vncr(vt);
}
/*
* Note that invalidating the VNCR on the back of an MMU notifier
* doesn't require messing with the invalidation counter for a
* parallel walk. The notifier itself will have bumped the counter,
* making sure we rewalk.
*/
kvm_for_each_vncr_tlb(i, vcpu, vt, kvm)
if (vncr_tlb_intersects(vt, vt->wr.pa, start, end - start))
invalidate_vncr(kvm, vt);
}
struct s1e2_tlbi_scope {
@ -1044,29 +1094,29 @@ static void invalidate_vncr_va(struct kvm *kvm,
lockdep_assert_held_write(&kvm->mmu_lock);
/*
* We might be performing a parallel S1 walk, so bump up the
* invalidation counter even in the absence of an actual VNCR TLB
* invalidation, as this could indicate that the guest has gone
* through a BBM sequence.
*/
kvm->mmu_invalidate_seq++;
smp_wmb();
kvm_for_each_vncr_tlb(i, vcpu, vt, kvm) {
u64 va_start, va_end, va_size;
va_size = ttl_to_size(pgshift_level_to_ttl(vt->wi.pgshift,
vt->wr.level));
va_start = vt->gva & ~(va_size - 1);
va_end = va_start + va_size;
switch (scope->type) {
case TLBI_ALL:
break;
case TLBI_VA:
if (va_end <= scope->va ||
va_start >= (scope->va + scope->size))
if (!vncr_tlb_intersects(vt, vt->gva, scope->va, scope->size))
continue;
if (vt->wr.nG && vt->wr.asid != scope->asid)
continue;
break;
case TLBI_VAA:
if (va_end <= scope->va ||
va_start >= (scope->va + scope->size))
if (!vncr_tlb_intersects(vt, vt->gva, scope->va, scope->size))
continue;
break;
@ -1076,7 +1126,7 @@ static void invalidate_vncr_va(struct kvm *kvm,
break;
}
invalidate_vncr(vt);
invalidate_vncr(kvm, vt);
}
}
@ -1126,8 +1176,6 @@ static void compute_s1_tlbi_range(struct kvm_vcpu *vcpu, u32 inst, u64 val,
case OP_TLBI_VALE1OSNXS:
scope->type = TLBI_VA;
scope->size = ttl_to_size(FIELD_GET(TLBI_TTL_MASK, val));
if (!scope->size)
scope->size = SZ_1G;
scope->va = tlbi_va_s1_to_va(val) & ~(scope->size - 1);
scope->asid = FIELD_GET(TLBIR_ASID_MASK, val);
break;
@ -1154,8 +1202,6 @@ static void compute_s1_tlbi_range(struct kvm_vcpu *vcpu, u32 inst, u64 val,
case OP_TLBI_VAALE1OSNXS:
scope->type = TLBI_VAA;
scope->size = ttl_to_size(FIELD_GET(TLBI_TTL_MASK, val));
if (!scope->size)
scope->size = SZ_1G;
scope->va = tlbi_va_s1_to_va(val) & ~(scope->size - 1);
break;
case OP_TLBI_RVAE2:
@ -1316,13 +1362,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)
@ -1377,7 +1430,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,
@ -1391,15 +1445,15 @@ static int kvm_translate_vncr(struct kvm_vcpu *vcpu, bool *is_gmem)
va = read_vncr_el2(vcpu);
mmu_seq = vcpu->kvm->mmu_invalidate_seq;
smp_rmb();
ret = __kvm_translate_va(vcpu, &vt->wi, &vt->wr, va);
if (ret)
return ret;
write_fault = kvm_is_write_fault(vcpu);
mmu_seq = vcpu->kvm->mmu_invalidate_seq;
smp_rmb();
gfn = vt->wr.pa >> PAGE_SHIFT;
memslot = gfn_to_memslot(vcpu->kvm, gfn);
if (!memslot) {
@ -1447,7 +1501,7 @@ static int kvm_translate_vncr(struct kvm_vcpu *vcpu, bool *is_gmem)
vt->hpa = pfn << PAGE_SHIFT;
vt->hpa_writable = writable;
vt->valid = true;
vt->cpu = -1;
atomic_set(&vt->cpu, -1);
kvm_make_request(KVM_REQ_MAP_L1_VNCR_EL2, vcpu);
kvm_release_faultin_page(vcpu->kvm, page, false, vt->wr.pw && vt->hpa_writable);
@ -1502,7 +1556,20 @@ int kvm_handle_vncr_abort(struct kvm_vcpu *vcpu)
return -EIO;
}
/*
* Speculatively increment the TLB count to make sure concurrent
* TLBIs will take the slow path, and will interact with the retry
* mechanism. Drop it again on error.
*/
atomic_inc(&vcpu->kvm->arch.vncr_tlb_count);
smp_mb__after_atomic();
ret = kvm_translate_vncr(vcpu, &is_gmem);
if (ret) {
smp_mb__before_atomic();
atomic_dec(&vcpu->kvm->arch.vncr_tlb_count);
}
switch (ret) {
case -EAGAIN:
/* Let's try again... */
@ -1568,14 +1635,16 @@ static void kvm_map_l1_vncr(struct kvm_vcpu *vcpu)
if (!vt->valid)
return;
/* We cache the MMU state in the TLB. Check that it matches. */
if (!!(vcpu_read_sys_reg(vcpu, SCTLR_EL2) & SCTLR_ELx_M) != s1_walk_translated(&vt->wr))
return;
if (read_vncr_el2(vcpu) != vt->gva)
return;
if (vt->wr.nG && get_asid_by_regime(vcpu, TR_EL20) != vt->wr.asid)
return;
vt->cpu = smp_processor_id();
if (vt->hpa_writable && vt->wr.pw && vt->wr.pr)
prot = PAGE_KERNEL;
else if (vt->wr.pr)
@ -1590,9 +1659,9 @@ static void kvm_map_l1_vncr(struct kvm_vcpu *vcpu)
* FIXME: WO doesn't work at all, need POE support in the kernel.
*/
if (pgprot_val(prot) != pgprot_val(PAGE_NONE)) {
__set_fixmap(vncr_fixmap(vt->cpu), vt->hpa, prot);
atomic_set(&vt->cpu, smp_processor_id());
__set_fixmap(vncr_fixmap(atomic_read(&vt->cpu)), vt->hpa, prot);
host_data_set_flag(L1_VNCR_MAPPED);
atomic_inc(&vcpu->kvm->arch.vncr_map_count);
}
}

View File

@ -4154,6 +4154,7 @@ static bool handle_ripas2e1is(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
u32 sys_encoding = sys_insn(p->Op0, p->Op1, p->CRn, p->CRm, p->Op2);
u64 vttbr = vcpu_read_sys_reg(vcpu, VTTBR_EL2);
u64 base, range;
int pa_bits;
if (!kvm_supported_tlbi_ipas2_op(vcpu, sys_encoding))
return undef_access(vcpu, p, r);
@ -4165,6 +4166,16 @@ static bool handle_ripas2e1is(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
*/
base = decode_range_tlbi(p->regval, &range, NULL);
/*
* Ignore TLBIs that start out of PA_bits range, and cap the
* invalidation to the [base:bit(PA_bits)] interval.
*/
pa_bits = kvm_get_pa_bits(vcpu->kvm);
if (fls64(base) > pa_bits)
return true;
range = min(range, BIT_ULL(pa_bits) - base);
kvm_s2_mmu_iterate_by_vmid(vcpu->kvm, get_vmid(vttbr),
&(union tlbi_info) {
.range = {