From: Shameer Kolothum <[email protected]> Introduce kvm_arm_pinned_vmid_get() and kvm_arm_pinned_vmid_put(), to pin a VMID associated with a KVM instance. This will guarantee that VMID remains the same after a rollover.
This will allow some devices, such as ones behind an SMMUv3, to have their TLB entries tagged with the KVM VMID. In some systems, TLB maintenance instructions from the CPU to the Inner Shareable domain can invalidate those entries. Signed-off-by: Shameer Kolothum <[email protected]> Signed-off-by: Jean-Philippe Brucker <[email protected]> --- arch/arm64/include/asm/kvm_host.h | 3 ++ arch/arm64/kvm/vmid.c | 77 ++++++++++++++++++++++++++++--- 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h index cd9b9d2462f9..30d0082b15ec 100644 --- a/arch/arm64/include/asm/kvm_host.h +++ b/arch/arm64/include/asm/kvm_host.h @@ -148,6 +148,7 @@ int topup_hyp_memcache(struct kvm_hyp_memcache *mc, unsigned long min_pages); struct kvm_vmid { atomic64_t id; + refcount_t pinned; }; struct kvm_s2_mmu { @@ -1359,6 +1360,8 @@ int __init kvm_arm_vmid_alloc_init(void); void __init kvm_arm_vmid_alloc_free(void); void kvm_arm_vmid_update(struct kvm_vmid *kvm_vmid); void kvm_arm_vmid_clear_active(void); +int kvm_arm_pinned_vmid_get(struct kvm *kvm); +void kvm_arm_pinned_vmid_put(struct kvm *kvm); static inline void kvm_arm_pvtime_vcpu_init(struct kvm_vcpu_arch *vcpu_arch) { diff --git a/arch/arm64/kvm/vmid.c b/arch/arm64/kvm/vmid.c index 7fe8ba1a2851..322279f59e88 100644 --- a/arch/arm64/kvm/vmid.c +++ b/arch/arm64/kvm/vmid.c @@ -25,6 +25,10 @@ static unsigned long *vmid_map; static DEFINE_PER_CPU(atomic64_t, active_vmids); static DEFINE_PER_CPU(u64, reserved_vmids); +static unsigned long max_pinned_vmids; +static unsigned long nr_pinned_vmids; +static unsigned long *pinned_vmid_map; + #define VMID_MASK (~GENMASK(kvm_arm_vmid_bits - 1, 0)) #define VMID_FIRST_VERSION (1UL << kvm_arm_vmid_bits) @@ -47,7 +51,7 @@ static void flush_context(void) int cpu; u64 vmid; - bitmap_zero(vmid_map, NUM_USER_VMIDS); + bitmap_copy(vmid_map, pinned_vmid_map, NUM_USER_VMIDS); for_each_possible_cpu(cpu) { vmid = atomic64_xchg_relaxed(&per_cpu(active_vmids, cpu), 0); @@ -98,12 +102,14 @@ static u64 new_vmid(struct kvm_vmid *kvm_vmid) if (vmid != 0) { u64 newvmid = generation | (vmid & ~VMID_MASK); - if (check_update_reserved_vmid(vmid, newvmid)) { - atomic64_set(&kvm_vmid->id, newvmid); - return newvmid; - } - - if (!__test_and_set_bit(vmid2idx(vmid), vmid_map)) { + /* + * If it is pinned, we can keep using it. Note that reserved + * takes priority, because even if it is also pinned, we need to + * update the generation into the reserved_vmids. + */ + if (check_update_reserved_vmid(vmid, newvmid) || + refcount_read(&kvm_vmid->pinned) || + !__test_and_set_bit(vmid2idx(vmid), vmid_map)) { atomic64_set(&kvm_vmid->id, newvmid); return newvmid; } @@ -169,6 +175,49 @@ void kvm_arm_vmid_update(struct kvm_vmid *kvm_vmid) raw_spin_unlock_irqrestore(&cpu_vmid_lock, flags); } +int kvm_arm_pinned_vmid_get(struct kvm *kvm) +{ + struct kvm_vmid *kvm_vmid; + u64 vmid; + + kvm_vmid = &kvm->arch.mmu.vmid; + + guard(raw_spinlock_irqsave)(&cpu_vmid_lock); + vmid = atomic64_read(&kvm_vmid->id); + + if (refcount_inc_not_zero(&kvm_vmid->pinned)) + return (vmid & ~VMID_MASK); + + if (nr_pinned_vmids >= max_pinned_vmids) + return -EINVAL; + + /* + * If we went through one or more rollover since that VMID was + * used, make sure it is still valid, or generate a new one. + */ + if (!vmid_gen_match(vmid)) + vmid = new_vmid(kvm_vmid); + + nr_pinned_vmids++; + __set_bit(vmid2idx(vmid), pinned_vmid_map); + refcount_set(&kvm_vmid->pinned, 1); + return (vmid & ~VMID_MASK); +} + +void kvm_arm_pinned_vmid_put(struct kvm *kvm) +{ + struct kvm_vmid *kvm_vmid; + u64 vmid; + + kvm_vmid = &kvm->arch.mmu.vmid; + vmid = atomic64_read(&kvm_vmid->id); + guard(raw_spinlock_irqsave)(&cpu_vmid_lock); + if (refcount_dec_and_test(&kvm_vmid->pinned)) { + __clear_bit(vmid2idx(vmid), pinned_vmid_map); + nr_pinned_vmids--; + } +} + /* * Initialize the VMID allocator */ @@ -186,10 +235,24 @@ int __init kvm_arm_vmid_alloc_init(void) if (!vmid_map) return -ENOMEM; + pinned_vmid_map = bitmap_zalloc(NUM_USER_VMIDS, GFP_KERNEL); + if (!pinned_vmid_map) { + bitmap_free(vmid_map); + return -ENOMEM; + } + nr_pinned_vmids = 0; + + /* + * Ensure we have at least one empty slot available after rollover when + * the maximum number of VMIDs are pinned. VMID#0 is reserved. + */ + max_pinned_vmids = NUM_USER_VMIDS - num_possible_cpus() - 2; + return 0; } void __init kvm_arm_vmid_alloc_free(void) { + bitmap_free(pinned_vmid_map); bitmap_free(vmid_map); } -- 2.43.0
