123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290 |
- // 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);
|