#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;
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();
}
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();
if (WARN_ON(IS_ENABLED(CONFIG_DEBUG_VM) && cpu_has_mmid))
return;
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;
bitmap_zero(mmid_map, num_mmids);
__set_bit(MMID_KERNEL_WIRED, mmid_map);
for_each_possible_cpu(cpu) {
mmid = xchg_relaxed(&cpu_data[cpu].asid_cache, 0);
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;
}
cpumask_setall(&tlb_flush_pending);
}
static bool check_update_reserved_mmid(u64 mmid, u64 newmmid)
{
bool hit;
int cpu;
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 (check_update_reserved_mmid(mmid, newmmid)) {
mmid = newmmid;
goto set_context;
}
if (!__test_and_set_bit(mmid & mmid_mask, mmid_map)) {
mmid = newmmid;
goto set_context;
}
}
mmid = find_next_zero_bit(mmid_map, num_mmids, cur_idx);
if (mmid != num_mmids)
goto reserve_mmid;
version = atomic64_add_return_relaxed(asid_first_version(0),
&mmid_version);
flush_context();
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;
}
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);
}
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));
#ifdef CONFIG_SMP
if (cpu_has_shared_ftlb_entries &&
cpumask_intersects(&tlb_flush_pending, &cpu_sibling_map[cpu])) {
mtc0_tlbw_hazard();
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;
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);
__set_bit(MMID_KERNEL_WIRED, mmid_map);
pr_info("MMID allocator initialised with %u entries\n", num_mmids);
return 0;
}
early_initcall(mmid_init);