| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * VMID allocator. | |
| * | |
| * Based on Arm64 ASID allocator algorithm. | |
| * Please refer arch/arm64/mm/context.c for detailed | |
| * comments on algorithm. | |
| * | |
| * Copyright (C) 2002-2003 Deep Blue Solutions Ltd, all rights reserved. | |
| * Copyright (C) 2012 ARM Ltd. | |
| */ | |
| #include <linux/bitfield.h> | |
| #include <linux/bitops.h> | |
| #include <asm/kvm_asm.h> | |
| #include <asm/kvm_mmu.h> | |
| unsigned int __ro_after_init kvm_arm_vmid_bits; | |
| static DEFINE_RAW_SPINLOCK(cpu_vmid_lock); | |
| static atomic64_t vmid_generation; | |
| static unsigned long *vmid_map; | |
| static DEFINE_PER_CPU(atomic64_t, active_vmids); | |
| static DEFINE_PER_CPU(u64, reserved_vmids); | |
| #define VMID_MASK (~GENMASK(kvm_arm_vmid_bits - 1, 0)) | |
| #define VMID_FIRST_VERSION (1UL << kvm_arm_vmid_bits) | |
| #define NUM_USER_VMIDS VMID_FIRST_VERSION | |
| #define vmid2idx(vmid) ((vmid) & ~VMID_MASK) | |
| #define idx2vmid(idx) vmid2idx(idx) | |
| /* | |
| * As vmid #0 is always reserved, we will never allocate one | |
| * as below and can be treated as invalid. This is used to | |
| * set the active_vmids on vCPU schedule out. | |
| */ | |
| #define VMID_ACTIVE_INVALID VMID_FIRST_VERSION | |
| #define vmid_gen_match(vmid) \ | |
| (!(((vmid) ^ atomic64_read(&vmid_generation)) >> kvm_arm_vmid_bits)) | |
| static void flush_context(void) | |
| { | |
| int cpu; | |
| u64 vmid; | |
| bitmap_zero(vmid_map, NUM_USER_VMIDS); | |
| for_each_possible_cpu(cpu) { | |
| vmid = atomic64_xchg_relaxed(&per_cpu(active_vmids, cpu), 0); | |
| /* Preserve reserved VMID */ | |
| if (vmid == 0) | |
| vmid = per_cpu(reserved_vmids, cpu); | |
| __set_bit(vmid2idx(vmid), vmid_map); | |
| per_cpu(reserved_vmids, cpu) = vmid; | |
| } | |
| /* | |
| * Unlike ASID allocator, we expect less frequent rollover in | |
| * case of VMIDs. Hence, instead of marking the CPU as | |
| * flush_pending and issuing a local context invalidation on | |
| * the next context-switch, we broadcast TLB flush + I-cache | |
| * invalidation over the inner shareable domain on rollover. | |
| */ | |
| kvm_call_hyp(__kvm_flush_vm_context); | |
| } | |
| static bool check_update_reserved_vmid(u64 vmid, u64 newvmid) | |
| { | |
| int cpu; | |
| bool hit = false; | |
| /* | |
| * Iterate over the set of reserved VMIDs looking for a match | |
| * and update to use newvmid (i.e. the same VMID in the current | |
| * generation). | |
| */ | |
| for_each_possible_cpu(cpu) { | |
| if (per_cpu(reserved_vmids, cpu) == vmid) { | |
| hit = true; | |
| per_cpu(reserved_vmids, cpu) = newvmid; | |
| } | |
| } | |
| return hit; | |
| } | |
| static u64 new_vmid(struct kvm_vmid *kvm_vmid) | |
| { | |
| static u32 cur_idx = 1; | |
| u64 vmid = atomic64_read(&kvm_vmid->id); | |
| u64 generation = atomic64_read(&vmid_generation); | |
| 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)) { | |
| atomic64_set(&kvm_vmid->id, newvmid); | |
| return newvmid; | |
| } | |
| } | |
| vmid = find_next_zero_bit(vmid_map, NUM_USER_VMIDS, cur_idx); | |
| if (vmid != NUM_USER_VMIDS) | |
| goto set_vmid; | |
| /* We're out of VMIDs, so increment the global generation count */ | |
| generation = atomic64_add_return_relaxed(VMID_FIRST_VERSION, | |
| &vmid_generation); | |
| flush_context(); | |
| /* We have more VMIDs than CPUs, so this will always succeed */ | |
| vmid = find_next_zero_bit(vmid_map, NUM_USER_VMIDS, 1); | |
| set_vmid: | |
| __set_bit(vmid, vmid_map); | |
| cur_idx = vmid; | |
| vmid = idx2vmid(vmid) | generation; | |
| atomic64_set(&kvm_vmid->id, vmid); | |
| return vmid; | |
| } | |
| /* Called from vCPU sched out with preemption disabled */ | |
| void kvm_arm_vmid_clear_active(void) | |
| { | |
| atomic64_set(this_cpu_ptr(&active_vmids), VMID_ACTIVE_INVALID); | |
| } | |
| void kvm_arm_vmid_update(struct kvm_vmid *kvm_vmid) | |
| { | |
| unsigned long flags; | |
| u64 vmid, old_active_vmid; | |
| vmid = atomic64_read(&kvm_vmid->id); | |
| /* | |
| * Please refer comments in check_and_switch_context() in | |
| * arch/arm64/mm/context.c. | |
| * | |
| * Unlike ASID allocator, we set the active_vmids to | |
| * VMID_ACTIVE_INVALID on vCPU schedule out to avoid | |
| * reserving the VMID space needlessly on rollover. | |
| * Hence explicitly check here for a "!= 0" to | |
| * handle the sync with a concurrent rollover. | |
| */ | |
| old_active_vmid = atomic64_read(this_cpu_ptr(&active_vmids)); | |
| if (old_active_vmid != 0 && vmid_gen_match(vmid) && | |
| 0 != atomic64_cmpxchg_relaxed(this_cpu_ptr(&active_vmids), | |
| old_active_vmid, vmid)) | |
| return; | |
| raw_spin_lock_irqsave(&cpu_vmid_lock, flags); | |
| /* Check that our VMID belongs to the current generation. */ | |
| vmid = atomic64_read(&kvm_vmid->id); | |
| if (!vmid_gen_match(vmid)) | |
| vmid = new_vmid(kvm_vmid); | |
| atomic64_set(this_cpu_ptr(&active_vmids), vmid); | |
| raw_spin_unlock_irqrestore(&cpu_vmid_lock, flags); | |
| } | |
| /* | |
| * Initialize the VMID allocator | |
| */ | |
| int __init kvm_arm_vmid_alloc_init(void) | |
| { | |
| kvm_arm_vmid_bits = kvm_get_vmid_bits(); | |
| /* | |
| * Expect allocation after rollover to fail if we don't have | |
| * at least one more VMID than CPUs. VMID #0 is always reserved. | |
| */ | |
| WARN_ON(NUM_USER_VMIDS - 1 <= num_possible_cpus()); | |
| atomic64_set(&vmid_generation, VMID_FIRST_VERSION); | |
| vmid_map = bitmap_zalloc(NUM_USER_VMIDS, GFP_KERNEL); | |
| if (!vmid_map) | |
| return -ENOMEM; | |
| return 0; | |
| } | |
| void __init kvm_arm_vmid_alloc_free(void) | |
| { | |
| bitmap_free(vmid_map); | |
| } |