Skip to content

Commit

Permalink
MIPS: MemoryMapID (MMID) Support
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Paul Burton committed Feb 4, 2019
1 parent 5351138 commit c8790d6
Show file tree
Hide file tree
Showing 15 changed files with 509 additions and 31 deletions.
13 changes: 13 additions & 0 deletions arch/mips/include/asm/cpu-features.h
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,19 @@
# define cpu_has_mipsmt_pertccounters 0
#endif /* CONFIG_MIPS_MT_SMP */

/*
* We only enable MMID support for configurations which natively support 64 bit
* atomics because getting good performance from the allocator relies upon
* efficient atomic64_*() functions.
*/
#ifndef cpu_has_mmid
# ifdef CONFIG_GENERIC_ATOMIC64
# define cpu_has_mmid 0
# else
# define cpu_has_mmid __isa_ge_and_opt(6, MIPS_CPU_MMID)
# endif
#endif

/*
* Guest capabilities
*/
Expand Down
1 change: 1 addition & 0 deletions arch/mips/include/asm/cpu.h
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,7 @@ enum cpu_type_enum {
MBIT_ULL(55) /* CPU shares FTLB entries with another */
#define MIPS_CPU_MT_PER_TC_PERF_COUNTERS \
MBIT_ULL(56) /* CPU has perf counters implemented per TC (MIPSMT ASE) */
#define MIPS_CPU_MMID MBIT_ULL(57) /* CPU supports MemoryMapIDs */

/*
* CPU ASE encodings
Expand Down
4 changes: 4 additions & 0 deletions arch/mips/include/asm/mipsregs.h
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,7 @@
#define MIPS_CONF5_FRE (_ULCAST_(1) << 8)
#define MIPS_CONF5_UFE (_ULCAST_(1) << 9)
#define MIPS_CONF5_CA2 (_ULCAST_(1) << 14)
#define MIPS_CONF5_MI (_ULCAST_(1) << 17)
#define MIPS_CONF5_CRCP (_ULCAST_(1) << 18)
#define MIPS_CONF5_MSAEN (_ULCAST_(1) << 27)
#define MIPS_CONF5_EVA (_ULCAST_(1) << 28)
Expand Down Expand Up @@ -1610,6 +1611,9 @@ do { \
#define read_c0_xcontextconfig() __read_ulong_c0_register($4, 3)
#define write_c0_xcontextconfig(val) __write_ulong_c0_register($4, 3, val)

#define read_c0_memorymapid() __read_32bit_c0_register($4, 5)
#define write_c0_memorymapid(val) __write_32bit_c0_register($4, 5, val)

#define read_c0_pagemask() __read_32bit_c0_register($5, 0)
#define write_c0_pagemask(val) __write_32bit_c0_register($5, 0, val)

Expand Down
6 changes: 5 additions & 1 deletion arch/mips/include/asm/mmu.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
#include <linux/wait.h>

typedef struct {
u64 asid[NR_CPUS];
union {
u64 asid[NR_CPUS];
atomic64_t mmid;
};

void *vdso;

/* lock to be held whilst modifying fp_bd_emupage_allocmap */
Expand Down
54 changes: 50 additions & 4 deletions arch/mips/include/asm/mmu_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
#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>
Expand Down Expand Up @@ -72,6 +74,19 @@ extern unsigned long pgd_current[];
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.
Expand All @@ -90,13 +105,19 @@ static inline u64 asid_first_version(unsigned int cpu)

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)
{
mm->context.asid[cpu] = 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)
Expand All @@ -120,8 +141,12 @@ init_new_context(struct task_struct *tsk, struct mm_struct *mm)
{
int i;

for_each_possible_cpu(i)
set_cpu_context(i, mm, 0);
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);
Expand Down Expand Up @@ -168,12 +193,33 @@ 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();
if (!cpu_context(cpu, mm)) {
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.
Expand Down
55 changes: 54 additions & 1 deletion arch/mips/kernel/cpu-probe.c
Original file line number Diff line number Diff line change
Expand Up @@ -872,10 +872,19 @@ static inline unsigned int decode_config4(struct cpuinfo_mips *c)

static inline unsigned int decode_config5(struct cpuinfo_mips *c)
{
unsigned int config5;
unsigned int config5, max_mmid_width;
unsigned long asid_mask;

config5 = read_c0_config5();
config5 &= ~(MIPS_CONF5_UFR | MIPS_CONF5_UFE);

if (cpu_has_mips_r6) {
if (!__builtin_constant_p(cpu_has_mmid) || cpu_has_mmid)
config5 |= MIPS_CONF5_MI;
else
config5 &= ~MIPS_CONF5_MI;
}

write_c0_config5(config5);

if (config5 & MIPS_CONF5_EVA)
Expand All @@ -894,6 +903,50 @@ static inline unsigned int decode_config5(struct cpuinfo_mips *c)
if (config5 & MIPS_CONF5_CRCP)
elf_hwcap |= HWCAP_MIPS_CRC32;

if (cpu_has_mips_r6) {
/* Ensure the write to config5 above takes effect */
back_to_back_c0_hazard();

/* Check whether we successfully enabled MMID support */
config5 = read_c0_config5();
if (config5 & MIPS_CONF5_MI)
c->options |= MIPS_CPU_MMID;

/*
* Warn if we've hardcoded cpu_has_mmid to a value unsuitable
* for the CPU we're running on, or if CPUs in an SMP system
* have inconsistent MMID support.
*/
WARN_ON(!!cpu_has_mmid != !!(config5 & MIPS_CONF5_MI));

if (cpu_has_mmid) {
write_c0_memorymapid(~0ul);
back_to_back_c0_hazard();
asid_mask = read_c0_memorymapid();

/*
* We maintain a bitmap to track MMID allocation, and
* need a sensible upper bound on the size of that
* bitmap. The initial CPU with MMID support (I6500)
* supports 16 bit MMIDs, which gives us an 8KiB
* bitmap. The architecture recommends that hardware
* support 32 bit MMIDs, which would give us a 512MiB
* bitmap - that's too big in most cases.
*
* Cap MMID width at 16 bits for now & we can revisit
* this if & when hardware supports anything wider.
*/
max_mmid_width = 16;
if (asid_mask > GENMASK(max_mmid_width - 1, 0)) {
pr_info("Capping MMID width at %d bits",
max_mmid_width);
asid_mask = GENMASK(max_mmid_width - 1, 0);
}

set_cpu_asid_mask(c, asid_mask);
}
}

return config5 & MIPS_CONF_M;
}

Expand Down
57 changes: 52 additions & 5 deletions arch/mips/kernel/smp.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

#include <linux/atomic.h>
#include <asm/cpu.h>
#include <asm/ginvt.h>
#include <asm/processor.h>
#include <asm/idle.h>
#include <asm/r4k-timer.h>
Expand Down Expand Up @@ -482,6 +483,15 @@ static void flush_tlb_all_ipi(void *info)

void flush_tlb_all(void)
{
if (cpu_has_mmid) {
htw_stop();
ginvt_full();
sync_ginv();
instruction_hazard();
htw_start();
return;
}

on_each_cpu(flush_tlb_all_ipi, NULL, 1);
}

Expand Down Expand Up @@ -530,7 +540,12 @@ void flush_tlb_mm(struct mm_struct *mm)
{
preempt_disable();

if ((atomic_read(&mm->mm_users) != 1) || (current->mm != mm)) {
if (cpu_has_mmid) {
/*
* No need to worry about other CPUs - the ginvt in
* drop_mmu_context() will be globalized.
*/
} else if ((atomic_read(&mm->mm_users) != 1) || (current->mm != mm)) {
smp_on_other_tlbs(flush_tlb_mm_ipi, mm);
} else {
unsigned int cpu;
Expand Down Expand Up @@ -561,16 +576,34 @@ static void flush_tlb_range_ipi(void *info)
void flush_tlb_range(struct vm_area_struct *vma, unsigned long start, unsigned long end)
{
struct mm_struct *mm = vma->vm_mm;
unsigned long addr;
u32 old_mmid;

preempt_disable();
if ((atomic_read(&mm->mm_users) != 1) || (current->mm != mm)) {
if (cpu_has_mmid) {
htw_stop();
old_mmid = read_c0_memorymapid();
write_c0_memorymapid(cpu_asid(0, mm));
mtc0_tlbw_hazard();
addr = round_down(start, PAGE_SIZE * 2);
end = round_up(end, PAGE_SIZE * 2);
do {
ginvt_va_mmid(addr);
sync_ginv();
addr += PAGE_SIZE * 2;
} while (addr < end);
write_c0_memorymapid(old_mmid);
instruction_hazard();
htw_start();
} else if ((atomic_read(&mm->mm_users) != 1) || (current->mm != mm)) {
struct flush_tlb_data fd = {
.vma = vma,
.addr1 = start,
.addr2 = end,
};

smp_on_other_tlbs(flush_tlb_range_ipi, &fd);
local_flush_tlb_range(vma, start, end);
} else {
unsigned int cpu;
int exec = vma->vm_flags & VM_EXEC;
Expand All @@ -585,8 +618,8 @@ void flush_tlb_range(struct vm_area_struct *vma, unsigned long start, unsigned l
if (cpu != smp_processor_id() && cpu_context(cpu, mm))
set_cpu_context(cpu, mm, !exec);
}
local_flush_tlb_range(vma, start, end);
}
local_flush_tlb_range(vma, start, end);
preempt_enable();
}

Expand Down Expand Up @@ -616,14 +649,28 @@ static void flush_tlb_page_ipi(void *info)

void flush_tlb_page(struct vm_area_struct *vma, unsigned long page)
{
u32 old_mmid;

preempt_disable();
if ((atomic_read(&vma->vm_mm->mm_users) != 1) || (current->mm != vma->vm_mm)) {
if (cpu_has_mmid) {
htw_stop();
old_mmid = read_c0_memorymapid();
write_c0_memorymapid(cpu_asid(0, vma->vm_mm));
mtc0_tlbw_hazard();
ginvt_va_mmid(page);
sync_ginv();
write_c0_memorymapid(old_mmid);
instruction_hazard();
htw_start();
} else if ((atomic_read(&vma->vm_mm->mm_users) != 1) ||
(current->mm != vma->vm_mm)) {
struct flush_tlb_data fd = {
.vma = vma,
.addr1 = page,
};

smp_on_other_tlbs(flush_tlb_page_ipi, &fd);
local_flush_tlb_page(vma, page);
} else {
unsigned int cpu;

Expand All @@ -637,8 +684,8 @@ void flush_tlb_page(struct vm_area_struct *vma, unsigned long page)
if (cpu != smp_processor_id() && cpu_context(cpu, vma->vm_mm))
set_cpu_context(cpu, vma->vm_mm, 1);
}
local_flush_tlb_page(vma, page);
}
local_flush_tlb_page(vma, page);
preempt_enable();
}

Expand Down
4 changes: 3 additions & 1 deletion arch/mips/kernel/traps.c
Original file line number Diff line number Diff line change
Expand Up @@ -2223,7 +2223,9 @@ void per_cpu_trap_init(bool is_boot_cpu)
cp0_fdc_irq = -1;
}

if (!cpu_data[cpu].asid_cache)
if (cpu_has_mmid)
cpu_data[cpu].asid_cache = 0;
else if (!cpu_data[cpu].asid_cache)
cpu_data[cpu].asid_cache = asid_first_version(cpu);

mmgrab(&init_mm);
Expand Down
1 change: 1 addition & 0 deletions arch/mips/kernel/unaligned.c
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
#include <asm/fpu.h>
#include <asm/fpu_emulator.h>
#include <asm/inst.h>
#include <asm/mmu_context.h>
#include <linux/uaccess.h>

#define STR(x) __STR(x)
Expand Down
5 changes: 5 additions & 0 deletions arch/mips/kvm/mips.c
Original file line number Diff line number Diff line change
Expand Up @@ -1723,6 +1723,11 @@ static int __init kvm_mips_init(void)
{
int ret;

if (cpu_has_mmid) {
pr_warn("KVM does not yet support MMIDs. KVM Disabled\n");
return -EOPNOTSUPP;
}

ret = kvm_mips_entry_setup();
if (ret)
return ret;
Expand Down
Loading

0 comments on commit c8790d6

Please sign in to comment.