cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

vmid.c (5116B)


      1// SPDX-License-Identifier: GPL-2.0
      2/*
      3 * VMID allocator.
      4 *
      5 * Based on Arm64 ASID allocator algorithm.
      6 * Please refer arch/arm64/mm/context.c for detailed
      7 * comments on algorithm.
      8 *
      9 * Copyright (C) 2002-2003 Deep Blue Solutions Ltd, all rights reserved.
     10 * Copyright (C) 2012 ARM Ltd.
     11 */
     12
     13#include <linux/bitfield.h>
     14#include <linux/bitops.h>
     15
     16#include <asm/kvm_asm.h>
     17#include <asm/kvm_mmu.h>
     18
     19unsigned int kvm_arm_vmid_bits;
     20static DEFINE_RAW_SPINLOCK(cpu_vmid_lock);
     21
     22static atomic64_t vmid_generation;
     23static unsigned long *vmid_map;
     24
     25static DEFINE_PER_CPU(atomic64_t, active_vmids);
     26static DEFINE_PER_CPU(u64, reserved_vmids);
     27
     28#define VMID_MASK		(~GENMASK(kvm_arm_vmid_bits - 1, 0))
     29#define VMID_FIRST_VERSION	(1UL << kvm_arm_vmid_bits)
     30
     31#define NUM_USER_VMIDS		VMID_FIRST_VERSION
     32#define vmid2idx(vmid)		((vmid) & ~VMID_MASK)
     33#define idx2vmid(idx)		vmid2idx(idx)
     34
     35/*
     36 * As vmid #0 is always reserved, we will never allocate one
     37 * as below and can be treated as invalid. This is used to
     38 * set the active_vmids on vCPU schedule out.
     39 */
     40#define VMID_ACTIVE_INVALID		VMID_FIRST_VERSION
     41
     42#define vmid_gen_match(vmid) \
     43	(!(((vmid) ^ atomic64_read(&vmid_generation)) >> kvm_arm_vmid_bits))
     44
     45static void flush_context(void)
     46{
     47	int cpu;
     48	u64 vmid;
     49
     50	bitmap_clear(vmid_map, 0, NUM_USER_VMIDS);
     51
     52	for_each_possible_cpu(cpu) {
     53		vmid = atomic64_xchg_relaxed(&per_cpu(active_vmids, cpu), 0);
     54
     55		/* Preserve reserved VMID */
     56		if (vmid == 0)
     57			vmid = per_cpu(reserved_vmids, cpu);
     58		__set_bit(vmid2idx(vmid), vmid_map);
     59		per_cpu(reserved_vmids, cpu) = vmid;
     60	}
     61
     62	/*
     63	 * Unlike ASID allocator, we expect less frequent rollover in
     64	 * case of VMIDs. Hence, instead of marking the CPU as
     65	 * flush_pending and issuing a local context invalidation on
     66	 * the next context-switch, we broadcast TLB flush + I-cache
     67	 * invalidation over the inner shareable domain on rollover.
     68	 */
     69	kvm_call_hyp(__kvm_flush_vm_context);
     70}
     71
     72static bool check_update_reserved_vmid(u64 vmid, u64 newvmid)
     73{
     74	int cpu;
     75	bool hit = false;
     76
     77	/*
     78	 * Iterate over the set of reserved VMIDs looking for a match
     79	 * and update to use newvmid (i.e. the same VMID in the current
     80	 * generation).
     81	 */
     82	for_each_possible_cpu(cpu) {
     83		if (per_cpu(reserved_vmids, cpu) == vmid) {
     84			hit = true;
     85			per_cpu(reserved_vmids, cpu) = newvmid;
     86		}
     87	}
     88
     89	return hit;
     90}
     91
     92static u64 new_vmid(struct kvm_vmid *kvm_vmid)
     93{
     94	static u32 cur_idx = 1;
     95	u64 vmid = atomic64_read(&kvm_vmid->id);
     96	u64 generation = atomic64_read(&vmid_generation);
     97
     98	if (vmid != 0) {
     99		u64 newvmid = generation | (vmid & ~VMID_MASK);
    100
    101		if (check_update_reserved_vmid(vmid, newvmid)) {
    102			atomic64_set(&kvm_vmid->id, newvmid);
    103			return newvmid;
    104		}
    105
    106		if (!__test_and_set_bit(vmid2idx(vmid), vmid_map)) {
    107			atomic64_set(&kvm_vmid->id, newvmid);
    108			return newvmid;
    109		}
    110	}
    111
    112	vmid = find_next_zero_bit(vmid_map, NUM_USER_VMIDS, cur_idx);
    113	if (vmid != NUM_USER_VMIDS)
    114		goto set_vmid;
    115
    116	/* We're out of VMIDs, so increment the global generation count */
    117	generation = atomic64_add_return_relaxed(VMID_FIRST_VERSION,
    118						 &vmid_generation);
    119	flush_context();
    120
    121	/* We have more VMIDs than CPUs, so this will always succeed */
    122	vmid = find_next_zero_bit(vmid_map, NUM_USER_VMIDS, 1);
    123
    124set_vmid:
    125	__set_bit(vmid, vmid_map);
    126	cur_idx = vmid;
    127	vmid = idx2vmid(vmid) | generation;
    128	atomic64_set(&kvm_vmid->id, vmid);
    129	return vmid;
    130}
    131
    132/* Called from vCPU sched out with preemption disabled */
    133void kvm_arm_vmid_clear_active(void)
    134{
    135	atomic64_set(this_cpu_ptr(&active_vmids), VMID_ACTIVE_INVALID);
    136}
    137
    138void kvm_arm_vmid_update(struct kvm_vmid *kvm_vmid)
    139{
    140	unsigned long flags;
    141	u64 vmid, old_active_vmid;
    142
    143	vmid = atomic64_read(&kvm_vmid->id);
    144
    145	/*
    146	 * Please refer comments in check_and_switch_context() in
    147	 * arch/arm64/mm/context.c.
    148	 *
    149	 * Unlike ASID allocator, we set the active_vmids to
    150	 * VMID_ACTIVE_INVALID on vCPU schedule out to avoid
    151	 * reserving the VMID space needlessly on rollover.
    152	 * Hence explicitly check here for a "!= 0" to
    153	 * handle the sync with a concurrent rollover.
    154	 */
    155	old_active_vmid = atomic64_read(this_cpu_ptr(&active_vmids));
    156	if (old_active_vmid != 0 && vmid_gen_match(vmid) &&
    157	    0 != atomic64_cmpxchg_relaxed(this_cpu_ptr(&active_vmids),
    158					  old_active_vmid, vmid))
    159		return;
    160
    161	raw_spin_lock_irqsave(&cpu_vmid_lock, flags);
    162
    163	/* Check that our VMID belongs to the current generation. */
    164	vmid = atomic64_read(&kvm_vmid->id);
    165	if (!vmid_gen_match(vmid))
    166		vmid = new_vmid(kvm_vmid);
    167
    168	atomic64_set(this_cpu_ptr(&active_vmids), vmid);
    169	raw_spin_unlock_irqrestore(&cpu_vmid_lock, flags);
    170}
    171
    172/*
    173 * Initialize the VMID allocator
    174 */
    175int kvm_arm_vmid_alloc_init(void)
    176{
    177	kvm_arm_vmid_bits = kvm_get_vmid_bits();
    178
    179	/*
    180	 * Expect allocation after rollover to fail if we don't have
    181	 * at least one more VMID than CPUs. VMID #0 is always reserved.
    182	 */
    183	WARN_ON(NUM_USER_VMIDS - 1 <= num_possible_cpus());
    184	atomic64_set(&vmid_generation, VMID_FIRST_VERSION);
    185	vmid_map = kcalloc(BITS_TO_LONGS(NUM_USER_VMIDS),
    186			   sizeof(*vmid_map), GFP_KERNEL);
    187	if (!vmid_map)
    188		return -ENOMEM;
    189
    190	return 0;
    191}
    192
    193void kvm_arm_vmid_alloc_free(void)
    194{
    195	kfree(vmid_map);
    196}