292 lines
7.7 KiB
C
292 lines
7.7 KiB
C
// 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_clear(mmid_map, 0, 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 = kcalloc(BITS_TO_LONGS(num_mmids), sizeof(*mmid_map),
|
|
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);
|