// SPDX-License-Identifier: GPL-2.0 #include <linux/atomic.h> #include <linux/mmu_context.h> #include <linux/percpu.h> #include <linux/spinlock.h> static DEFINE_RAW_SPINLOCK(cpu_mmid_lock); static atomic64_t mmid_version; static unsigned int num_mmids; static unsigned long *mmid_map; static DEFINE_PER_CPU(u64, reserved_mmids); static cpumask_t tlb_flush_pending; static bool asid_versions_eq(int cpu, u64 a, u64 b) { return ((a ^ b) & asid_version_mask(cpu)) == 0; } void get_new_mmu_context(struct mm_struct *mm) { unsigned int cpu; u64 asid; /* * This function is specific to ASIDs, and should not be called when * MMIDs are in use. */ if (WARN_ON(IS_ENABLED(CONFIG_DEBUG_VM) && cpu_has_mmid)) return; cpu = smp_processor_id(); asid = asid_cache(cpu); if (!((asid += cpu_asid_inc()) & cpu_asid_mask(&cpu_data[cpu]))) { if (cpu_has_vtag_icache) flush_icache_all(); local_flush_tlb_all(); /* start new asid cycle */ } set_cpu_context(cpu, mm, asid); asid_cache(cpu) = asid; } EXPORT_SYMBOL_GPL(get_new_mmu_context); void check_mmu_context(struct mm_struct *mm) { unsigned int cpu = smp_processor_id(); /* * This function is specific to ASIDs, and should not be called when * MMIDs are in use. */ if (WARN_ON(IS_ENABLED(CONFIG_DEBUG_VM) && cpu_has_mmid)) return; /* Check if our ASID is of an older version and thus invalid */ if (!asid_versions_eq(cpu, cpu_context(cpu, mm), asid_cache(cpu))) get_new_mmu_context(mm); } EXPORT_SYMBOL_GPL(check_mmu_context); static void flush_context(void) { u64 mmid; int cpu; /* Update the list of reserved MMIDs and the MMID bitmap */ bitmap_zero(mmid_map, num_mmids); /* Reserve an MMID for kmap/wired entries */ __set_bit(MMID_KERNEL_WIRED, mmid_map); for_each_possible_cpu(cpu) { mmid = xchg_relaxed(&cpu_data[cpu].asid_cache, 0); /* * If this CPU has already been through a * rollover, but hasn't run another task in * the meantime, we must preserve its reserved * MMID, as this is the only trace we have of * the process it is still running. */ if (mmid == 0) mmid = per_cpu(reserved_mmids, cpu); __set_bit(mmid & cpu_asid_mask(&cpu_data[cpu]), mmid_map); per_cpu(reserved_mmids, cpu) = mmid; } /* * Queue a TLB invalidation for each CPU to perform on next * context-switch */ cpumask_setall(&tlb_flush_pending); } static bool check_update_reserved_mmid(u64 mmid, u64 newmmid) { bool hit; int cpu; /* * Iterate over the set of reserved MMIDs looking for a match. * If we find one, then we can update our mm to use newmmid * (i.e. the same MMID in the current generation) but we can't * exit the loop early, since we need to ensure that all copies * of the old MMID are updated to reflect the mm. Failure to do * so could result in us missing the reserved MMID in a future * generation. */ hit = false; for_each_possible_cpu(cpu) { if (per_cpu(reserved_mmids, cpu) == mmid) { hit = true; per_cpu(reserved_mmids, cpu) = newmmid; } } return hit; } static u64 get_new_mmid(struct mm_struct *mm) { static u32 cur_idx = MMID_KERNEL_WIRED + 1; u64 mmid, version, mmid_mask; mmid = cpu_context(0, mm); version = atomic64_read(&mmid_version); mmid_mask = cpu_asid_mask(&boot_cpu_data); if (!asid_versions_eq(0, mmid, 0)) { u64 newmmid = version | (mmid & mmid_mask); /* * If our current MMID was active during a rollover, we * can continue to use it and this was just a false alarm. */ if (check_update_reserved_mmid(mmid, newmmid)) { mmid = newmmid; goto set_context; } /* * We had a valid MMID in a previous life, so try to re-use * it if possible. */ if (!__test_and_set_bit(mmid & mmid_mask, mmid_map)) { mmid = newmmid; goto set_context; } } /* Allocate a free MMID */ mmid = find_next_zero_bit(mmid_map, num_mmids, cur_idx); if (mmid != num_mmids) goto reserve_mmid; /* We're out of MMIDs, so increment the global version */ version = atomic64_add_return_relaxed(asid_first_version(0), &mmid_version); /* Note currently active MMIDs & mark TLBs as requiring flushes */ flush_context(); /* We have more MMIDs than CPUs, so this will always succeed */ mmid = find_first_zero_bit(mmid_map, num_mmids); reserve_mmid: __set_bit(mmid, mmid_map); cur_idx = mmid; mmid |= version; set_context: set_cpu_context(0, mm, mmid); return mmid; } void check_switch_mmu_context(struct mm_struct *mm) { unsigned int cpu = smp_processor_id(); u64 ctx, old_active_mmid; unsigned long flags; if (!cpu_has_mmid) { check_mmu_context(mm); write_c0_entryhi(cpu_asid(cpu, mm)); goto setup_pgd; } /* * MMID switch fast-path, to avoid acquiring cpu_mmid_lock when it's * unnecessary. * * The memory ordering here is subtle. If our active_mmids is non-zero * and the MMID matches the current version, then we update the CPU's * asid_cache with a relaxed cmpxchg. Racing with a concurrent rollover * means that either: * * - We get a zero back from the cmpxchg and end up waiting on * cpu_mmid_lock in check_mmu_context(). Taking the lock synchronises * with the rollover and so we are forced to see the updated * generation. * * - We get a valid MMID back from the cmpxchg, which means the * relaxed xchg in flush_context will treat us as reserved * because atomic RmWs are totally ordered for a given location. */ ctx = cpu_context(cpu, mm); old_active_mmid = READ_ONCE(cpu_data[cpu].asid_cache); if (!old_active_mmid || !asid_versions_eq(cpu, ctx, atomic64_read(&mmid_version)) || !cmpxchg_relaxed(&cpu_data[cpu].asid_cache, old_active_mmid, ctx)) { raw_spin_lock_irqsave(&cpu_mmid_lock, flags); ctx = cpu_context(cpu, mm); if (!asid_versions_eq(cpu, ctx, atomic64_read(&mmid_version))) ctx = get_new_mmid(mm); WRITE_ONCE(cpu_data[cpu].asid_cache, ctx); raw_spin_unlock_irqrestore(&cpu_mmid_lock, flags); } /* * Invalidate the local TLB if needed. Note that we must only clear our * bit in tlb_flush_pending after this is complete, so that the * cpu_has_shared_ftlb_entries case below isn't misled. */ if (cpumask_test_cpu(cpu, &tlb_flush_pending)) { if (cpu_has_vtag_icache) flush_icache_all(); local_flush_tlb_all(); cpumask_clear_cpu(cpu, &tlb_flush_pending); } write_c0_memorymapid(ctx & cpu_asid_mask(&boot_cpu_data)); /* * If this CPU shares FTLB entries with its siblings and one or more of * those siblings hasn't yet invalidated its TLB following a version * increase then we need to invalidate any TLB entries for our MMID * that we might otherwise pick up from a sibling. * * We ifdef on CONFIG_SMP because cpu_sibling_map isn't defined in * CONFIG_SMP=n kernels. */ #ifdef CONFIG_SMP if (cpu_has_shared_ftlb_entries && cpumask_intersects(&tlb_flush_pending, &cpu_sibling_map[cpu])) { /* Ensure we operate on the new MMID */ mtc0_tlbw_hazard(); /* * Invalidate all TLB entries associated with the new * MMID, and wait for the invalidation to complete. */ ginvt_mmid(); sync_ginv(); } #endif setup_pgd: TLBMISS_HANDLER_SETUP_PGD(mm->pgd); } EXPORT_SYMBOL_GPL(check_switch_mmu_context); static int mmid_init(void) { if (!cpu_has_mmid) return 0; /* * Expect allocation after rollover to fail if we don't have at least * one more MMID than CPUs. */ num_mmids = asid_first_version(0); WARN_ON(num_mmids <= num_possible_cpus()); atomic64_set(&mmid_version, asid_first_version(0)); mmid_map = bitmap_zalloc(num_mmids, GFP_KERNEL); if (!mmid_map) panic("Failed to allocate bitmap for %u MMIDs\n", num_mmids); /* Reserve an MMID for kmap/wired entries */ __set_bit(MMID_KERNEL_WIRED, mmid_map); pr_info("MMID allocator initialised with %u entries\n", num_mmids); return 0; } early_initcall(mmid_init);