#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/init.h>
#include <linux/spinlock.h>
#include <linux/memblock.h>
#include <linux/notifier.h>
#include <linux/cpu.h>
#include <linux/slab.h>
#include <asm/mmu_context.h>
#include <asm/tlbflush.h>
#include <asm/smp.h>
#include <asm/kup.h>
#include <mm/mmu_decl.h>
void *abatron_pteptrs[2];
#define FIRST_CONTEXT 1
#if defined(CONFIG_PPC_8xx)
#define LAST_CONTEXT 16
#elif defined(CONFIG_PPC_47x)
#define LAST_CONTEXT 65535
#else
#define LAST_CONTEXT 255
#endif
static unsigned int next_context, nr_free_contexts;
static unsigned long *context_map;
static unsigned long *stale_map[NR_CPUS];
static struct mm_struct **context_mm;
static DEFINE_RAW_SPINLOCK(context_lock);
#define CTX_MAP_SIZE \
(sizeof(unsigned long) * (LAST_CONTEXT / BITS_PER_LONG + 1))
static unsigned int steal_context_smp(unsigned int id)
{
struct mm_struct *mm;
unsigned int cpu, max, i;
max = LAST_CONTEXT - FIRST_CONTEXT;
while (max--) {
mm = context_mm[id];
if (mm->context.active) {
id++;
if (id > LAST_CONTEXT)
id = FIRST_CONTEXT;
continue;
}
mm->context.id = MMU_NO_CONTEXT;
for_each_cpu(cpu, mm_cpumask(mm)) {
for (i = cpu_first_thread_sibling(cpu);
i <= cpu_last_thread_sibling(cpu); i++) {
if (stale_map[i])
__set_bit(id, stale_map[i]);
}
cpu = i - 1;
}
return id;
}
raw_spin_unlock(&context_lock);
cpu_relax();
raw_spin_lock(&context_lock);
return MMU_NO_CONTEXT;
}
static unsigned int steal_all_contexts(void)
{
struct mm_struct *mm;
int cpu = smp_processor_id();
unsigned int id;
for (id = FIRST_CONTEXT; id <= LAST_CONTEXT; id++) {
mm = context_mm[id];
mm->context.id = MMU_NO_CONTEXT;
if (id != FIRST_CONTEXT) {
context_mm[id] = NULL;
__clear_bit(id, context_map);
}
if (IS_ENABLED(CONFIG_SMP))
__clear_bit(id, stale_map[cpu]);
}
_tlbil_all();
nr_free_contexts = LAST_CONTEXT - FIRST_CONTEXT;
return FIRST_CONTEXT;
}
static unsigned int steal_context_up(unsigned int id)
{
struct mm_struct *mm;
int cpu = smp_processor_id();
mm = context_mm[id];
local_flush_tlb_mm(mm);
mm->context.id = MMU_NO_CONTEXT;
if (IS_ENABLED(CONFIG_SMP))
__clear_bit(id, stale_map[cpu]);
return id;
}
static void set_context(unsigned long id, pgd_t *pgd)
{
if (IS_ENABLED(CONFIG_PPC_8xx)) {
mtspr(SPRN_M_TWB, __pa(pgd));
mtspr(SPRN_M_CASID, id - 1);
mb();
} else if (kuap_is_disabled()) {
mtspr(SPRN_PID, id);
isync();
}
}
void switch_mmu_context(struct mm_struct *prev, struct mm_struct *next,
struct task_struct *tsk)
{
unsigned int id;
unsigned int i, cpu = smp_processor_id();
unsigned long *map;
raw_spin_lock(&context_lock);
if (IS_ENABLED(CONFIG_SMP)) {
next->context.active++;
if (prev) {
WARN_ON(prev->context.active < 1);
prev->context.active--;
}
}
again:
id = next->context.id;
if (likely(id != MMU_NO_CONTEXT))
goto ctxt_ok;
id = next_context;
if (id > LAST_CONTEXT)
id = FIRST_CONTEXT;
map = context_map;
if (nr_free_contexts == 0) {
if (num_online_cpus() > 1) {
id = steal_context_smp(id);
if (id == MMU_NO_CONTEXT)
goto again;
goto stolen;
}
if (IS_ENABLED(CONFIG_PPC_8xx))
id = steal_all_contexts();
else
id = steal_context_up(id);
goto stolen;
}
nr_free_contexts--;
while (__test_and_set_bit(id, map)) {
id = find_next_zero_bit(map, LAST_CONTEXT+1, id);
if (id > LAST_CONTEXT)
id = FIRST_CONTEXT;
}
stolen:
next_context = id + 1;
context_mm[id] = next;
next->context.id = id;
ctxt_ok:
if (IS_ENABLED(CONFIG_SMP) && test_bit(id, stale_map[cpu])) {
local_flush_tlb_mm(next);
for (i = cpu_first_thread_sibling(cpu);
i <= cpu_last_thread_sibling(cpu); i++) {
if (stale_map[i])
__clear_bit(id, stale_map[i]);
}
}
if (IS_ENABLED(CONFIG_BDI_SWITCH))
abatron_pteptrs[1] = next->pgd;
set_context(id, next->pgd);
#if defined(CONFIG_BOOKE) && defined(CONFIG_PPC_KUAP)
tsk->thread.pid = id;
#endif
raw_spin_unlock(&context_lock);
}
int init_new_context(struct task_struct *t, struct mm_struct *mm)
{
mm->context.id = MMU_NO_CONTEXT;
mm->context.active = 0;
pte_frag_set(&mm->context, NULL);
return 0;
}
void destroy_context(struct mm_struct *mm)
{
unsigned long flags;
unsigned int id;
if (mm->context.id == MMU_NO_CONTEXT)
return;
WARN_ON(mm->context.active != 0);
raw_spin_lock_irqsave(&context_lock, flags);
id = mm->context.id;
if (id != MMU_NO_CONTEXT) {
__clear_bit(id, context_map);
mm->context.id = MMU_NO_CONTEXT;
context_mm[id] = NULL;
nr_free_contexts++;
}
raw_spin_unlock_irqrestore(&context_lock, flags);
}
static int mmu_ctx_cpu_prepare(unsigned int cpu)
{
if (cpu == boot_cpuid)
return 0;
stale_map[cpu] = kzalloc(CTX_MAP_SIZE, GFP_KERNEL);
return 0;
}
static int mmu_ctx_cpu_dead(unsigned int cpu)
{
#ifdef CONFIG_HOTPLUG_CPU
if (cpu == boot_cpuid)
return 0;
kfree(stale_map[cpu]);
stale_map[cpu] = NULL;
clear_tasks_mm_cpumask(cpu);
#endif
return 0;
}
void __init mmu_context_init(void)
{
init_mm.context.active = NR_CPUS;
context_map = memblock_alloc_or_panic(CTX_MAP_SIZE, SMP_CACHE_BYTES);
context_mm = memblock_alloc_or_panic(sizeof(void *) * (LAST_CONTEXT + 1),
SMP_CACHE_BYTES);
if (IS_ENABLED(CONFIG_SMP)) {
stale_map[boot_cpuid] = memblock_alloc_or_panic(CTX_MAP_SIZE, SMP_CACHE_BYTES);
cpuhp_setup_state_nocalls(CPUHP_POWERPC_MMU_CTX_PREPARE,
"powerpc/mmu/ctx:prepare",
mmu_ctx_cpu_prepare, mmu_ctx_cpu_dead);
}
printk(KERN_INFO
"MMU: Allocated %zu bytes of context maps for %d contexts\n",
2 * CTX_MAP_SIZE + (sizeof(void *) * (LAST_CONTEXT + 1)),
LAST_CONTEXT - FIRST_CONTEXT + 1);
context_map[0] = (1 << FIRST_CONTEXT) - 1;
next_context = FIRST_CONTEXT;
nr_free_contexts = LAST_CONTEXT - FIRST_CONTEXT + 1;
}