c8790d657b
Introduce support for using MemoryMapIDs (MMIDs) as an alternative to Address Space IDs (ASIDs). The major difference between the two is that MMIDs are global - ie. an MMID uniquely identifies an address space across all coherent CPUs. In contrast ASIDs are non-global per-CPU IDs, wherein each address space is allocated a separate ASID for each CPU upon which it is used. This global namespace allows a new GINVT instruction be used to globally invalidate TLB entries associated with a particular MMID across all coherent CPUs in the system, removing the need for IPIs to invalidate entries with separate ASIDs on each CPU. The allocation scheme used here is largely borrowed from arm64 (see arch/arm64/mm/context.c). In essence we maintain a bitmap to track available MMIDs, and MMIDs in active use at the time of a rollover to a new MMID version are preserved in the new version. The allocation scheme requires efficient 64 bit atomics in order to perform reasonably, so this support depends upon CONFIG_GENERIC_ATOMIC64=n (ie. currently it will only be included in MIPS64 kernels). The first, and currently only, available CPU with support for MMIDs is the MIPS I6500. This CPU supports 16 bit MMIDs, and so for now we cap our MMIDs to 16 bits wide in order to prevent the bitmap growing to absurd sizes if any future CPU does implement 32 bit MMIDs as the architecture manuals suggest is recommended. When MMIDs are in use we also make use of GINVT instruction which is available due to the global nature of MMIDs. By executing a sequence of GINVT & SYNC 0x14 instructions we can avoid the overhead of an IPI to each remote CPU in many cases. One complication is that GINVT will invalidate wired entries (in all cases apart from type 0, which targets the entire TLB). In order to avoid GINVT invalidating any wired TLB entries we set up, we make sure to create those entries using a reserved MMID (0) that we never associate with any address space. Also of note is that KVM will require further work in order to support MMIDs & GINVT, since KVM is involved in allocating IDs for guests & in configuring the MMU. That work is not part of this patch, so for now when MMIDs are in use KVM is disabled. Signed-off-by: Paul Burton <paul.burton@mips.com> Cc: linux-mips@vger.kernel.org
241 lines
6.1 KiB
C
241 lines
6.1 KiB
C
/*
|
|
* Switch a MMU context.
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU General Public
|
|
* License. See the file "COPYING" in the main directory of this archive
|
|
* for more details.
|
|
*
|
|
* Copyright (C) 1996, 1997, 1998, 1999 by Ralf Baechle
|
|
* Copyright (C) 1999 Silicon Graphics, Inc.
|
|
*/
|
|
#ifndef _ASM_MMU_CONTEXT_H
|
|
#define _ASM_MMU_CONTEXT_H
|
|
|
|
#include <linux/errno.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/mm_types.h>
|
|
#include <linux/smp.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include <asm/barrier.h>
|
|
#include <asm/cacheflush.h>
|
|
#include <asm/dsemul.h>
|
|
#include <asm/ginvt.h>
|
|
#include <asm/hazards.h>
|
|
#include <asm/tlbflush.h>
|
|
#include <asm-generic/mm_hooks.h>
|
|
|
|
#define htw_set_pwbase(pgd) \
|
|
do { \
|
|
if (cpu_has_htw) { \
|
|
write_c0_pwbase(pgd); \
|
|
back_to_back_c0_hazard(); \
|
|
} \
|
|
} while (0)
|
|
|
|
extern void tlbmiss_handler_setup_pgd(unsigned long);
|
|
extern char tlbmiss_handler_setup_pgd_end[];
|
|
|
|
/* Note: This is also implemented with uasm in arch/mips/kvm/entry.c */
|
|
#define TLBMISS_HANDLER_SETUP_PGD(pgd) \
|
|
do { \
|
|
tlbmiss_handler_setup_pgd((unsigned long)(pgd)); \
|
|
htw_set_pwbase((unsigned long)pgd); \
|
|
} while (0)
|
|
|
|
#ifdef CONFIG_MIPS_PGD_C0_CONTEXT
|
|
|
|
#define TLBMISS_HANDLER_RESTORE() \
|
|
write_c0_xcontext((unsigned long) smp_processor_id() << \
|
|
SMP_CPUID_REGSHIFT)
|
|
|
|
#define TLBMISS_HANDLER_SETUP() \
|
|
do { \
|
|
TLBMISS_HANDLER_SETUP_PGD(swapper_pg_dir); \
|
|
TLBMISS_HANDLER_RESTORE(); \
|
|
} while (0)
|
|
|
|
#else /* !CONFIG_MIPS_PGD_C0_CONTEXT: using pgd_current*/
|
|
|
|
/*
|
|
* For the fast tlb miss handlers, we keep a per cpu array of pointers
|
|
* to the current pgd for each processor. Also, the proc. id is stuffed
|
|
* into the context register.
|
|
*/
|
|
extern unsigned long pgd_current[];
|
|
|
|
#define TLBMISS_HANDLER_RESTORE() \
|
|
write_c0_context((unsigned long) smp_processor_id() << \
|
|
SMP_CPUID_REGSHIFT)
|
|
|
|
#define TLBMISS_HANDLER_SETUP() \
|
|
TLBMISS_HANDLER_RESTORE(); \
|
|
back_to_back_c0_hazard(); \
|
|
TLBMISS_HANDLER_SETUP_PGD(swapper_pg_dir)
|
|
#endif /* CONFIG_MIPS_PGD_C0_CONTEXT*/
|
|
|
|
/*
|
|
* The ginvt instruction will invalidate wired entries when its type field
|
|
* targets anything other than the entire TLB. That means that if we were to
|
|
* allow the kernel to create wired entries with the MMID of current->active_mm
|
|
* then those wired entries could be invalidated when we later use ginvt to
|
|
* invalidate TLB entries with that MMID.
|
|
*
|
|
* In order to prevent ginvt from trashing wired entries, we reserve one MMID
|
|
* for use by the kernel when creating wired entries. This MMID will never be
|
|
* assigned to a struct mm, and we'll never target it with a ginvt instruction.
|
|
*/
|
|
#define MMID_KERNEL_WIRED 0
|
|
|
|
/*
|
|
* All unused by hardware upper bits will be considered
|
|
* as a software asid extension.
|
|
*/
|
|
static inline u64 asid_version_mask(unsigned int cpu)
|
|
{
|
|
unsigned long asid_mask = cpu_asid_mask(&cpu_data[cpu]);
|
|
|
|
return ~(u64)(asid_mask | (asid_mask - 1));
|
|
}
|
|
|
|
static inline u64 asid_first_version(unsigned int cpu)
|
|
{
|
|
return ~asid_version_mask(cpu) + 1;
|
|
}
|
|
|
|
static inline u64 cpu_context(unsigned int cpu, const struct mm_struct *mm)
|
|
{
|
|
if (cpu_has_mmid)
|
|
return atomic64_read(&mm->context.mmid);
|
|
|
|
return mm->context.asid[cpu];
|
|
}
|
|
|
|
static inline void set_cpu_context(unsigned int cpu,
|
|
struct mm_struct *mm, u64 ctx)
|
|
{
|
|
if (cpu_has_mmid)
|
|
atomic64_set(&mm->context.mmid, ctx);
|
|
else
|
|
mm->context.asid[cpu] = ctx;
|
|
}
|
|
|
|
#define asid_cache(cpu) (cpu_data[cpu].asid_cache)
|
|
#define cpu_asid(cpu, mm) \
|
|
(cpu_context((cpu), (mm)) & cpu_asid_mask(&cpu_data[cpu]))
|
|
|
|
static inline void enter_lazy_tlb(struct mm_struct *mm, struct task_struct *tsk)
|
|
{
|
|
}
|
|
|
|
extern void get_new_mmu_context(struct mm_struct *mm);
|
|
extern void check_mmu_context(struct mm_struct *mm);
|
|
extern void check_switch_mmu_context(struct mm_struct *mm);
|
|
|
|
/*
|
|
* Initialize the context related info for a new mm_struct
|
|
* instance.
|
|
*/
|
|
static inline int
|
|
init_new_context(struct task_struct *tsk, struct mm_struct *mm)
|
|
{
|
|
int i;
|
|
|
|
if (cpu_has_mmid) {
|
|
set_cpu_context(0, mm, 0);
|
|
} else {
|
|
for_each_possible_cpu(i)
|
|
set_cpu_context(i, mm, 0);
|
|
}
|
|
|
|
mm->context.bd_emupage_allocmap = NULL;
|
|
spin_lock_init(&mm->context.bd_emupage_lock);
|
|
init_waitqueue_head(&mm->context.bd_emupage_queue);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next,
|
|
struct task_struct *tsk)
|
|
{
|
|
unsigned int cpu = smp_processor_id();
|
|
unsigned long flags;
|
|
local_irq_save(flags);
|
|
|
|
htw_stop();
|
|
check_switch_mmu_context(next);
|
|
|
|
/*
|
|
* Mark current->active_mm as not "active" anymore.
|
|
* We don't want to mislead possible IPI tlb flush routines.
|
|
*/
|
|
cpumask_clear_cpu(cpu, mm_cpumask(prev));
|
|
cpumask_set_cpu(cpu, mm_cpumask(next));
|
|
htw_start();
|
|
|
|
local_irq_restore(flags);
|
|
}
|
|
|
|
/*
|
|
* Destroy context related info for an mm_struct that is about
|
|
* to be put to rest.
|
|
*/
|
|
static inline void destroy_context(struct mm_struct *mm)
|
|
{
|
|
dsemul_mm_cleanup(mm);
|
|
}
|
|
|
|
#define activate_mm(prev, next) switch_mm(prev, next, current)
|
|
#define deactivate_mm(tsk, mm) do { } while (0)
|
|
|
|
static inline void
|
|
drop_mmu_context(struct mm_struct *mm)
|
|
{
|
|
unsigned long flags;
|
|
unsigned int cpu;
|
|
u32 old_mmid;
|
|
u64 ctx;
|
|
|
|
local_irq_save(flags);
|
|
|
|
cpu = smp_processor_id();
|
|
ctx = cpu_context(cpu, mm);
|
|
|
|
if (!ctx) {
|
|
/* no-op */
|
|
} else if (cpu_has_mmid) {
|
|
/*
|
|
* Globally invalidating TLB entries associated with the MMID
|
|
* is pretty cheap using the GINVT instruction, so we'll do
|
|
* that rather than incur the overhead of allocating a new
|
|
* MMID. The latter would be especially difficult since MMIDs
|
|
* are global & other CPUs may be actively using ctx.
|
|
*/
|
|
htw_stop();
|
|
old_mmid = read_c0_memorymapid();
|
|
write_c0_memorymapid(ctx & cpu_asid_mask(&cpu_data[cpu]));
|
|
mtc0_tlbw_hazard();
|
|
ginvt_mmid();
|
|
sync_ginv();
|
|
write_c0_memorymapid(old_mmid);
|
|
instruction_hazard();
|
|
htw_start();
|
|
} else if (cpumask_test_cpu(cpu, mm_cpumask(mm))) {
|
|
/*
|
|
* mm is currently active, so we can't really drop it.
|
|
* Instead we bump the ASID.
|
|
*/
|
|
htw_stop();
|
|
get_new_mmu_context(mm);
|
|
write_c0_entryhi(cpu_asid(cpu, mm));
|
|
htw_start();
|
|
} else {
|
|
/* will get a new context next time */
|
|
set_cpu_context(cpu, mm, 0);
|
|
}
|
|
|
|
local_irq_restore(flags);
|
|
}
|
|
|
|
#endif /* _ASM_MMU_CONTEXT_H */
|