Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

MIPS: MemoryMapID (MMID) Support

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

+509 -31
+13
arch/mips/include/asm/cpu-features.h
··· 591 591 #endif /* CONFIG_MIPS_MT_SMP */ 592 592 593 593 /* 594 + * We only enable MMID support for configurations which natively support 64 bit 595 + * atomics because getting good performance from the allocator relies upon 596 + * efficient atomic64_*() functions. 597 + */ 598 + #ifndef cpu_has_mmid 599 + # ifdef CONFIG_GENERIC_ATOMIC64 600 + # define cpu_has_mmid 0 601 + # else 602 + # define cpu_has_mmid __isa_ge_and_opt(6, MIPS_CPU_MMID) 603 + # endif 604 + #endif 605 + 606 + /* 594 607 * Guest capabilities 595 608 */ 596 609 #ifndef cpu_guest_has_conf1
+1
arch/mips/include/asm/cpu.h
··· 422 422 MBIT_ULL(55) /* CPU shares FTLB entries with another */ 423 423 #define MIPS_CPU_MT_PER_TC_PERF_COUNTERS \ 424 424 MBIT_ULL(56) /* CPU has perf counters implemented per TC (MIPSMT ASE) */ 425 + #define MIPS_CPU_MMID MBIT_ULL(57) /* CPU supports MemoryMapIDs */ 425 426 426 427 /* 427 428 * CPU ASE encodings
+4
arch/mips/include/asm/mipsregs.h
··· 667 667 #define MIPS_CONF5_FRE (_ULCAST_(1) << 8) 668 668 #define MIPS_CONF5_UFE (_ULCAST_(1) << 9) 669 669 #define MIPS_CONF5_CA2 (_ULCAST_(1) << 14) 670 + #define MIPS_CONF5_MI (_ULCAST_(1) << 17) 670 671 #define MIPS_CONF5_CRCP (_ULCAST_(1) << 18) 671 672 #define MIPS_CONF5_MSAEN (_ULCAST_(1) << 27) 672 673 #define MIPS_CONF5_EVA (_ULCAST_(1) << 28) ··· 1610 1609 1611 1610 #define read_c0_xcontextconfig() __read_ulong_c0_register($4, 3) 1612 1611 #define write_c0_xcontextconfig(val) __write_ulong_c0_register($4, 3, val) 1612 + 1613 + #define read_c0_memorymapid() __read_32bit_c0_register($4, 5) 1614 + #define write_c0_memorymapid(val) __write_32bit_c0_register($4, 5, val) 1613 1615 1614 1616 #define read_c0_pagemask() __read_32bit_c0_register($5, 0) 1615 1617 #define write_c0_pagemask(val) __write_32bit_c0_register($5, 0, val)
+5 -1
arch/mips/include/asm/mmu.h
··· 7 7 #include <linux/wait.h> 8 8 9 9 typedef struct { 10 - u64 asid[NR_CPUS]; 10 + union { 11 + u64 asid[NR_CPUS]; 12 + atomic64_t mmid; 13 + }; 14 + 11 15 void *vdso; 12 16 13 17 /* lock to be held whilst modifying fp_bd_emupage_allocmap */
+50 -4
arch/mips/include/asm/mmu_context.h
··· 17 17 #include <linux/smp.h> 18 18 #include <linux/slab.h> 19 19 20 + #include <asm/barrier.h> 20 21 #include <asm/cacheflush.h> 21 22 #include <asm/dsemul.h> 23 + #include <asm/ginvt.h> 22 24 #include <asm/hazards.h> 23 25 #include <asm/tlbflush.h> 24 26 #include <asm-generic/mm_hooks.h> ··· 75 73 #endif /* CONFIG_MIPS_PGD_C0_CONTEXT*/ 76 74 77 75 /* 76 + * The ginvt instruction will invalidate wired entries when its type field 77 + * targets anything other than the entire TLB. That means that if we were to 78 + * allow the kernel to create wired entries with the MMID of current->active_mm 79 + * then those wired entries could be invalidated when we later use ginvt to 80 + * invalidate TLB entries with that MMID. 81 + * 82 + * In order to prevent ginvt from trashing wired entries, we reserve one MMID 83 + * for use by the kernel when creating wired entries. This MMID will never be 84 + * assigned to a struct mm, and we'll never target it with a ginvt instruction. 85 + */ 86 + #define MMID_KERNEL_WIRED 0 87 + 88 + /* 78 89 * All unused by hardware upper bits will be considered 79 90 * as a software asid extension. 80 91 */ ··· 105 90 106 91 static inline u64 cpu_context(unsigned int cpu, const struct mm_struct *mm) 107 92 { 93 + if (cpu_has_mmid) 94 + return atomic64_read(&mm->context.mmid); 95 + 108 96 return mm->context.asid[cpu]; 109 97 } 110 98 111 99 static inline void set_cpu_context(unsigned int cpu, 112 100 struct mm_struct *mm, u64 ctx) 113 101 { 114 - mm->context.asid[cpu] = ctx; 102 + if (cpu_has_mmid) 103 + atomic64_set(&mm->context.mmid, ctx); 104 + else 105 + mm->context.asid[cpu] = ctx; 115 106 } 116 107 117 108 #define asid_cache(cpu) (cpu_data[cpu].asid_cache) ··· 141 120 { 142 121 int i; 143 122 144 - for_each_possible_cpu(i) 145 - set_cpu_context(i, mm, 0); 123 + if (cpu_has_mmid) { 124 + set_cpu_context(0, mm, 0); 125 + } else { 126 + for_each_possible_cpu(i) 127 + set_cpu_context(i, mm, 0); 128 + } 146 129 147 130 mm->context.bd_emupage_allocmap = NULL; 148 131 spin_lock_init(&mm->context.bd_emupage_lock); ··· 193 168 { 194 169 unsigned long flags; 195 170 unsigned int cpu; 171 + u32 old_mmid; 172 + u64 ctx; 196 173 197 174 local_irq_save(flags); 198 175 199 176 cpu = smp_processor_id(); 200 - if (!cpu_context(cpu, mm)) { 177 + ctx = cpu_context(cpu, mm); 178 + 179 + if (!ctx) { 201 180 /* no-op */ 181 + } else if (cpu_has_mmid) { 182 + /* 183 + * Globally invalidating TLB entries associated with the MMID 184 + * is pretty cheap using the GINVT instruction, so we'll do 185 + * that rather than incur the overhead of allocating a new 186 + * MMID. The latter would be especially difficult since MMIDs 187 + * are global & other CPUs may be actively using ctx. 188 + */ 189 + htw_stop(); 190 + old_mmid = read_c0_memorymapid(); 191 + write_c0_memorymapid(ctx & cpu_asid_mask(&cpu_data[cpu])); 192 + mtc0_tlbw_hazard(); 193 + ginvt_mmid(); 194 + sync_ginv(); 195 + write_c0_memorymapid(old_mmid); 196 + instruction_hazard(); 197 + htw_start(); 202 198 } else if (cpumask_test_cpu(cpu, mm_cpumask(mm))) { 203 199 /* 204 200 * mm is currently active, so we can't really drop it.
+54 -1
arch/mips/kernel/cpu-probe.c
··· 872 872 873 873 static inline unsigned int decode_config5(struct cpuinfo_mips *c) 874 874 { 875 - unsigned int config5; 875 + unsigned int config5, max_mmid_width; 876 + unsigned long asid_mask; 876 877 877 878 config5 = read_c0_config5(); 878 879 config5 &= ~(MIPS_CONF5_UFR | MIPS_CONF5_UFE); 880 + 881 + if (cpu_has_mips_r6) { 882 + if (!__builtin_constant_p(cpu_has_mmid) || cpu_has_mmid) 883 + config5 |= MIPS_CONF5_MI; 884 + else 885 + config5 &= ~MIPS_CONF5_MI; 886 + } 887 + 879 888 write_c0_config5(config5); 880 889 881 890 if (config5 & MIPS_CONF5_EVA) ··· 902 893 903 894 if (config5 & MIPS_CONF5_CRCP) 904 895 elf_hwcap |= HWCAP_MIPS_CRC32; 896 + 897 + if (cpu_has_mips_r6) { 898 + /* Ensure the write to config5 above takes effect */ 899 + back_to_back_c0_hazard(); 900 + 901 + /* Check whether we successfully enabled MMID support */ 902 + config5 = read_c0_config5(); 903 + if (config5 & MIPS_CONF5_MI) 904 + c->options |= MIPS_CPU_MMID; 905 + 906 + /* 907 + * Warn if we've hardcoded cpu_has_mmid to a value unsuitable 908 + * for the CPU we're running on, or if CPUs in an SMP system 909 + * have inconsistent MMID support. 910 + */ 911 + WARN_ON(!!cpu_has_mmid != !!(config5 & MIPS_CONF5_MI)); 912 + 913 + if (cpu_has_mmid) { 914 + write_c0_memorymapid(~0ul); 915 + back_to_back_c0_hazard(); 916 + asid_mask = read_c0_memorymapid(); 917 + 918 + /* 919 + * We maintain a bitmap to track MMID allocation, and 920 + * need a sensible upper bound on the size of that 921 + * bitmap. The initial CPU with MMID support (I6500) 922 + * supports 16 bit MMIDs, which gives us an 8KiB 923 + * bitmap. The architecture recommends that hardware 924 + * support 32 bit MMIDs, which would give us a 512MiB 925 + * bitmap - that's too big in most cases. 926 + * 927 + * Cap MMID width at 16 bits for now & we can revisit 928 + * this if & when hardware supports anything wider. 929 + */ 930 + max_mmid_width = 16; 931 + if (asid_mask > GENMASK(max_mmid_width - 1, 0)) { 932 + pr_info("Capping MMID width at %d bits", 933 + max_mmid_width); 934 + asid_mask = GENMASK(max_mmid_width - 1, 0); 935 + } 936 + 937 + set_cpu_asid_mask(c, asid_mask); 938 + } 939 + } 905 940 906 941 return config5 & MIPS_CONF_M; 907 942 }
+52 -5
arch/mips/kernel/smp.c
··· 39 39 40 40 #include <linux/atomic.h> 41 41 #include <asm/cpu.h> 42 + #include <asm/ginvt.h> 42 43 #include <asm/processor.h> 43 44 #include <asm/idle.h> 44 45 #include <asm/r4k-timer.h> ··· 483 482 484 483 void flush_tlb_all(void) 485 484 { 485 + if (cpu_has_mmid) { 486 + htw_stop(); 487 + ginvt_full(); 488 + sync_ginv(); 489 + instruction_hazard(); 490 + htw_start(); 491 + return; 492 + } 493 + 486 494 on_each_cpu(flush_tlb_all_ipi, NULL, 1); 487 495 } 488 496 ··· 540 530 { 541 531 preempt_disable(); 542 532 543 - if ((atomic_read(&mm->mm_users) != 1) || (current->mm != mm)) { 533 + if (cpu_has_mmid) { 534 + /* 535 + * No need to worry about other CPUs - the ginvt in 536 + * drop_mmu_context() will be globalized. 537 + */ 538 + } else if ((atomic_read(&mm->mm_users) != 1) || (current->mm != mm)) { 544 539 smp_on_other_tlbs(flush_tlb_mm_ipi, mm); 545 540 } else { 546 541 unsigned int cpu; ··· 576 561 void flush_tlb_range(struct vm_area_struct *vma, unsigned long start, unsigned long end) 577 562 { 578 563 struct mm_struct *mm = vma->vm_mm; 564 + unsigned long addr; 565 + u32 old_mmid; 579 566 580 567 preempt_disable(); 581 - if ((atomic_read(&mm->mm_users) != 1) || (current->mm != mm)) { 568 + if (cpu_has_mmid) { 569 + htw_stop(); 570 + old_mmid = read_c0_memorymapid(); 571 + write_c0_memorymapid(cpu_asid(0, mm)); 572 + mtc0_tlbw_hazard(); 573 + addr = round_down(start, PAGE_SIZE * 2); 574 + end = round_up(end, PAGE_SIZE * 2); 575 + do { 576 + ginvt_va_mmid(addr); 577 + sync_ginv(); 578 + addr += PAGE_SIZE * 2; 579 + } while (addr < end); 580 + write_c0_memorymapid(old_mmid); 581 + instruction_hazard(); 582 + htw_start(); 583 + } else if ((atomic_read(&mm->mm_users) != 1) || (current->mm != mm)) { 582 584 struct flush_tlb_data fd = { 583 585 .vma = vma, 584 586 .addr1 = start, ··· 603 571 }; 604 572 605 573 smp_on_other_tlbs(flush_tlb_range_ipi, &fd); 574 + local_flush_tlb_range(vma, start, end); 606 575 } else { 607 576 unsigned int cpu; 608 577 int exec = vma->vm_flags & VM_EXEC; ··· 618 585 if (cpu != smp_processor_id() && cpu_context(cpu, mm)) 619 586 set_cpu_context(cpu, mm, !exec); 620 587 } 588 + local_flush_tlb_range(vma, start, end); 621 589 } 622 - local_flush_tlb_range(vma, start, end); 623 590 preempt_enable(); 624 591 } 625 592 ··· 649 616 650 617 void flush_tlb_page(struct vm_area_struct *vma, unsigned long page) 651 618 { 619 + u32 old_mmid; 620 + 652 621 preempt_disable(); 653 - if ((atomic_read(&vma->vm_mm->mm_users) != 1) || (current->mm != vma->vm_mm)) { 622 + if (cpu_has_mmid) { 623 + htw_stop(); 624 + old_mmid = read_c0_memorymapid(); 625 + write_c0_memorymapid(cpu_asid(0, vma->vm_mm)); 626 + mtc0_tlbw_hazard(); 627 + ginvt_va_mmid(page); 628 + sync_ginv(); 629 + write_c0_memorymapid(old_mmid); 630 + instruction_hazard(); 631 + htw_start(); 632 + } else if ((atomic_read(&vma->vm_mm->mm_users) != 1) || 633 + (current->mm != vma->vm_mm)) { 654 634 struct flush_tlb_data fd = { 655 635 .vma = vma, 656 636 .addr1 = page, 657 637 }; 658 638 659 639 smp_on_other_tlbs(flush_tlb_page_ipi, &fd); 640 + local_flush_tlb_page(vma, page); 660 641 } else { 661 642 unsigned int cpu; 662 643 ··· 684 637 if (cpu != smp_processor_id() && cpu_context(cpu, vma->vm_mm)) 685 638 set_cpu_context(cpu, vma->vm_mm, 1); 686 639 } 640 + local_flush_tlb_page(vma, page); 687 641 } 688 - local_flush_tlb_page(vma, page); 689 642 preempt_enable(); 690 643 } 691 644
+3 -1
arch/mips/kernel/traps.c
··· 2223 2223 cp0_fdc_irq = -1; 2224 2224 } 2225 2225 2226 - if (!cpu_data[cpu].asid_cache) 2226 + if (cpu_has_mmid) 2227 + cpu_data[cpu].asid_cache = 0; 2228 + else if (!cpu_data[cpu].asid_cache) 2227 2229 cpu_data[cpu].asid_cache = asid_first_version(cpu); 2228 2230 2229 2231 mmgrab(&init_mm);
+1
arch/mips/kernel/unaligned.c
··· 89 89 #include <asm/fpu.h> 90 90 #include <asm/fpu_emulator.h> 91 91 #include <asm/inst.h> 92 + #include <asm/mmu_context.h> 92 93 #include <linux/uaccess.h> 93 94 94 95 #define STR(x) __STR(x)
+5
arch/mips/kvm/mips.c
··· 1723 1723 { 1724 1724 int ret; 1725 1725 1726 + if (cpu_has_mmid) { 1727 + pr_warn("KVM does not yet support MMIDs. KVM Disabled\n"); 1728 + return -EOPNOTSUPP; 1729 + } 1730 + 1726 1731 ret = kvm_mips_entry_setup(); 1727 1732 if (ret) 1728 1733 return ret;
+17 -5
arch/mips/lib/dump_tlb.c
··· 10 10 11 11 #include <asm/hazards.h> 12 12 #include <asm/mipsregs.h> 13 + #include <asm/mmu_context.h> 13 14 #include <asm/page.h> 14 15 #include <asm/pgtable.h> 15 16 #include <asm/tlbdebug.h> ··· 74 73 75 74 static void dump_tlb(int first, int last) 76 75 { 77 - unsigned long s_entryhi, entryhi, asid; 76 + unsigned long s_entryhi, entryhi, asid, mmid; 78 77 unsigned long long entrylo0, entrylo1, pa; 79 78 unsigned int s_index, s_pagemask, s_guestctl1 = 0; 80 79 unsigned int pagemask, guestctl1 = 0, c0, c1, i; 81 80 unsigned long asidmask = cpu_asid_mask(&current_cpu_data); 82 81 int asidwidth = DIV_ROUND_UP(ilog2(asidmask) + 1, 4); 82 + unsigned long uninitialized_var(s_mmid); 83 83 #ifdef CONFIG_32BIT 84 84 bool xpa = cpu_has_xpa && (read_c0_pagegrain() & PG_ELPA); 85 85 int pwidth = xpa ? 11 : 8; ··· 94 92 s_pagemask = read_c0_pagemask(); 95 93 s_entryhi = read_c0_entryhi(); 96 94 s_index = read_c0_index(); 97 - asid = s_entryhi & asidmask; 95 + 96 + if (cpu_has_mmid) 97 + asid = s_mmid = read_c0_memorymapid(); 98 + else 99 + asid = s_entryhi & asidmask; 100 + 98 101 if (cpu_has_guestid) 99 102 s_guestctl1 = read_c0_guestctl1(); 100 103 ··· 112 105 entryhi = read_c0_entryhi(); 113 106 entrylo0 = read_c0_entrylo0(); 114 107 entrylo1 = read_c0_entrylo1(); 108 + 109 + if (cpu_has_mmid) 110 + mmid = read_c0_memorymapid(); 111 + else 112 + mmid = entryhi & asidmask; 113 + 115 114 if (cpu_has_guestid) 116 115 guestctl1 = read_c0_guestctl1(); 117 116 ··· 137 124 * leave only a single G bit set after a machine check exception 138 125 * due to duplicate TLB entry. 139 126 */ 140 - if (!((entrylo0 | entrylo1) & ENTRYLO_G) && 141 - (entryhi & asidmask) != asid) 127 + if (!((entrylo0 | entrylo1) & ENTRYLO_G) && (mmid != asid)) 142 128 continue; 143 129 144 130 /* ··· 150 138 151 139 pr_cont("va=%0*lx asid=%0*lx", 152 140 vwidth, (entryhi & ~0x1fffUL), 153 - asidwidth, entryhi & asidmask); 141 + asidwidth, mmid); 154 142 if (cpu_has_guestid) 155 143 pr_cont(" gid=%02lx", 156 144 (guestctl1 & MIPS_GCTL1_RID)
+3
arch/mips/mm/c-r4k.c
··· 540 540 unsigned int i; 541 541 const cpumask_t *mask = cpu_present_mask; 542 542 543 + if (cpu_has_mmid) 544 + return cpu_context(0, mm) != 0; 545 + 543 546 /* cpu_sibling_map[] undeclared when !CONFIG_SMP */ 544 547 #ifdef CONFIG_SMP 545 548 /*
+253 -3
arch/mips/mm/context.c
··· 1 1 // SPDX-License-Identifier: GPL-2.0 2 + #include <linux/atomic.h> 2 3 #include <linux/mmu_context.h> 4 + #include <linux/percpu.h> 5 + #include <linux/spinlock.h> 6 + 7 + static DEFINE_RAW_SPINLOCK(cpu_mmid_lock); 8 + 9 + static atomic64_t mmid_version; 10 + static unsigned int num_mmids; 11 + static unsigned long *mmid_map; 12 + 13 + static DEFINE_PER_CPU(u64, reserved_mmids); 14 + static cpumask_t tlb_flush_pending; 15 + 16 + static bool asid_versions_eq(int cpu, u64 a, u64 b) 17 + { 18 + return ((a ^ b) & asid_version_mask(cpu)) == 0; 19 + } 3 20 4 21 void get_new_mmu_context(struct mm_struct *mm) 5 22 { 6 23 unsigned int cpu; 7 24 u64 asid; 25 + 26 + /* 27 + * This function is specific to ASIDs, and should not be called when 28 + * MMIDs are in use. 29 + */ 30 + if (WARN_ON(IS_ENABLED(CONFIG_DEBUG_VM) && cpu_has_mmid)) 31 + return; 8 32 9 33 cpu = smp_processor_id(); 10 34 asid = asid_cache(cpu); ··· 47 23 { 48 24 unsigned int cpu = smp_processor_id(); 49 25 26 + /* 27 + * This function is specific to ASIDs, and should not be called when 28 + * MMIDs are in use. 29 + */ 30 + if (WARN_ON(IS_ENABLED(CONFIG_DEBUG_VM) && cpu_has_mmid)) 31 + return; 32 + 50 33 /* Check if our ASID is of an older version and thus invalid */ 51 - if ((cpu_context(cpu, mm) ^ asid_cache(cpu)) & asid_version_mask(cpu)) 34 + if (!asid_versions_eq(cpu, cpu_context(cpu, mm), asid_cache(cpu))) 52 35 get_new_mmu_context(mm); 36 + } 37 + 38 + static void flush_context(void) 39 + { 40 + u64 mmid; 41 + int cpu; 42 + 43 + /* Update the list of reserved MMIDs and the MMID bitmap */ 44 + bitmap_clear(mmid_map, 0, num_mmids); 45 + 46 + /* Reserve an MMID for kmap/wired entries */ 47 + __set_bit(MMID_KERNEL_WIRED, mmid_map); 48 + 49 + for_each_possible_cpu(cpu) { 50 + mmid = xchg_relaxed(&cpu_data[cpu].asid_cache, 0); 51 + 52 + /* 53 + * If this CPU has already been through a 54 + * rollover, but hasn't run another task in 55 + * the meantime, we must preserve its reserved 56 + * MMID, as this is the only trace we have of 57 + * the process it is still running. 58 + */ 59 + if (mmid == 0) 60 + mmid = per_cpu(reserved_mmids, cpu); 61 + 62 + __set_bit(mmid & cpu_asid_mask(&cpu_data[cpu]), mmid_map); 63 + per_cpu(reserved_mmids, cpu) = mmid; 64 + } 65 + 66 + /* 67 + * Queue a TLB invalidation for each CPU to perform on next 68 + * context-switch 69 + */ 70 + cpumask_setall(&tlb_flush_pending); 71 + } 72 + 73 + static bool check_update_reserved_mmid(u64 mmid, u64 newmmid) 74 + { 75 + bool hit; 76 + int cpu; 77 + 78 + /* 79 + * Iterate over the set of reserved MMIDs looking for a match. 80 + * If we find one, then we can update our mm to use newmmid 81 + * (i.e. the same MMID in the current generation) but we can't 82 + * exit the loop early, since we need to ensure that all copies 83 + * of the old MMID are updated to reflect the mm. Failure to do 84 + * so could result in us missing the reserved MMID in a future 85 + * generation. 86 + */ 87 + hit = false; 88 + for_each_possible_cpu(cpu) { 89 + if (per_cpu(reserved_mmids, cpu) == mmid) { 90 + hit = true; 91 + per_cpu(reserved_mmids, cpu) = newmmid; 92 + } 93 + } 94 + 95 + return hit; 96 + } 97 + 98 + static u64 get_new_mmid(struct mm_struct *mm) 99 + { 100 + static u32 cur_idx = MMID_KERNEL_WIRED + 1; 101 + u64 mmid, version, mmid_mask; 102 + 103 + mmid = cpu_context(0, mm); 104 + version = atomic64_read(&mmid_version); 105 + mmid_mask = cpu_asid_mask(&boot_cpu_data); 106 + 107 + if (!asid_versions_eq(0, mmid, 0)) { 108 + u64 newmmid = version | (mmid & mmid_mask); 109 + 110 + /* 111 + * If our current MMID was active during a rollover, we 112 + * can continue to use it and this was just a false alarm. 113 + */ 114 + if (check_update_reserved_mmid(mmid, newmmid)) { 115 + mmid = newmmid; 116 + goto set_context; 117 + } 118 + 119 + /* 120 + * We had a valid MMID in a previous life, so try to re-use 121 + * it if possible. 122 + */ 123 + if (!__test_and_set_bit(mmid & mmid_mask, mmid_map)) { 124 + mmid = newmmid; 125 + goto set_context; 126 + } 127 + } 128 + 129 + /* Allocate a free MMID */ 130 + mmid = find_next_zero_bit(mmid_map, num_mmids, cur_idx); 131 + if (mmid != num_mmids) 132 + goto reserve_mmid; 133 + 134 + /* We're out of MMIDs, so increment the global version */ 135 + version = atomic64_add_return_relaxed(asid_first_version(0), 136 + &mmid_version); 137 + 138 + /* Note currently active MMIDs & mark TLBs as requiring flushes */ 139 + flush_context(); 140 + 141 + /* We have more MMIDs than CPUs, so this will always succeed */ 142 + mmid = find_first_zero_bit(mmid_map, num_mmids); 143 + 144 + reserve_mmid: 145 + __set_bit(mmid, mmid_map); 146 + cur_idx = mmid; 147 + mmid |= version; 148 + set_context: 149 + set_cpu_context(0, mm, mmid); 150 + return mmid; 53 151 } 54 152 55 153 void check_switch_mmu_context(struct mm_struct *mm) 56 154 { 57 155 unsigned int cpu = smp_processor_id(); 156 + u64 ctx, old_active_mmid; 157 + unsigned long flags; 58 158 59 - check_mmu_context(mm); 60 - write_c0_entryhi(cpu_asid(cpu, mm)); 159 + if (!cpu_has_mmid) { 160 + check_mmu_context(mm); 161 + write_c0_entryhi(cpu_asid(cpu, mm)); 162 + goto setup_pgd; 163 + } 164 + 165 + /* 166 + * MMID switch fast-path, to avoid acquiring cpu_mmid_lock when it's 167 + * unnecessary. 168 + * 169 + * The memory ordering here is subtle. If our active_mmids is non-zero 170 + * and the MMID matches the current version, then we update the CPU's 171 + * asid_cache with a relaxed cmpxchg. Racing with a concurrent rollover 172 + * means that either: 173 + * 174 + * - We get a zero back from the cmpxchg and end up waiting on 175 + * cpu_mmid_lock in check_mmu_context(). Taking the lock synchronises 176 + * with the rollover and so we are forced to see the updated 177 + * generation. 178 + * 179 + * - We get a valid MMID back from the cmpxchg, which means the 180 + * relaxed xchg in flush_context will treat us as reserved 181 + * because atomic RmWs are totally ordered for a given location. 182 + */ 183 + ctx = cpu_context(cpu, mm); 184 + old_active_mmid = READ_ONCE(cpu_data[cpu].asid_cache); 185 + if (!old_active_mmid || 186 + !asid_versions_eq(cpu, ctx, atomic64_read(&mmid_version)) || 187 + !cmpxchg_relaxed(&cpu_data[cpu].asid_cache, old_active_mmid, ctx)) { 188 + raw_spin_lock_irqsave(&cpu_mmid_lock, flags); 189 + 190 + ctx = cpu_context(cpu, mm); 191 + if (!asid_versions_eq(cpu, ctx, atomic64_read(&mmid_version))) 192 + ctx = get_new_mmid(mm); 193 + 194 + WRITE_ONCE(cpu_data[cpu].asid_cache, ctx); 195 + raw_spin_unlock_irqrestore(&cpu_mmid_lock, flags); 196 + } 197 + 198 + /* 199 + * Invalidate the local TLB if needed. Note that we must only clear our 200 + * bit in tlb_flush_pending after this is complete, so that the 201 + * cpu_has_shared_ftlb_entries case below isn't misled. 202 + */ 203 + if (cpumask_test_cpu(cpu, &tlb_flush_pending)) { 204 + if (cpu_has_vtag_icache) 205 + flush_icache_all(); 206 + local_flush_tlb_all(); 207 + cpumask_clear_cpu(cpu, &tlb_flush_pending); 208 + } 209 + 210 + write_c0_memorymapid(ctx & cpu_asid_mask(&boot_cpu_data)); 211 + 212 + /* 213 + * If this CPU shares FTLB entries with its siblings and one or more of 214 + * those siblings hasn't yet invalidated its TLB following a version 215 + * increase then we need to invalidate any TLB entries for our MMID 216 + * that we might otherwise pick up from a sibling. 217 + * 218 + * We ifdef on CONFIG_SMP because cpu_sibling_map isn't defined in 219 + * CONFIG_SMP=n kernels. 220 + */ 221 + #ifdef CONFIG_SMP 222 + if (cpu_has_shared_ftlb_entries && 223 + cpumask_intersects(&tlb_flush_pending, &cpu_sibling_map[cpu])) { 224 + /* Ensure we operate on the new MMID */ 225 + mtc0_tlbw_hazard(); 226 + 227 + /* 228 + * Invalidate all TLB entries associated with the new 229 + * MMID, and wait for the invalidation to complete. 230 + */ 231 + ginvt_mmid(); 232 + sync_ginv(); 233 + } 234 + #endif 235 + 236 + setup_pgd: 61 237 TLBMISS_HANDLER_SETUP_PGD(mm->pgd); 62 238 } 239 + 240 + static int mmid_init(void) 241 + { 242 + if (!cpu_has_mmid) 243 + return 0; 244 + 245 + /* 246 + * Expect allocation after rollover to fail if we don't have at least 247 + * one more MMID than CPUs. 248 + */ 249 + num_mmids = asid_first_version(0); 250 + WARN_ON(num_mmids <= num_possible_cpus()); 251 + 252 + atomic64_set(&mmid_version, asid_first_version(0)); 253 + mmid_map = kcalloc(BITS_TO_LONGS(num_mmids), sizeof(*mmid_map), 254 + GFP_KERNEL); 255 + if (!mmid_map) 256 + panic("Failed to allocate bitmap for %u MMIDs\n", num_mmids); 257 + 258 + /* Reserve an MMID for kmap/wired entries */ 259 + __set_bit(MMID_KERNEL_WIRED, mmid_map); 260 + 261 + pr_info("MMID allocator initialised with %u entries\n", num_mmids); 262 + return 0; 263 + } 264 + early_initcall(mmid_init);
+7
arch/mips/mm/init.c
··· 84 84 static void *__kmap_pgprot(struct page *page, unsigned long addr, pgprot_t prot) 85 85 { 86 86 enum fixed_addresses idx; 87 + unsigned int uninitialized_var(old_mmid); 87 88 unsigned long vaddr, flags, entrylo; 88 89 unsigned long old_ctx; 89 90 pte_t pte; ··· 111 110 write_c0_entryhi(vaddr & (PAGE_MASK << 1)); 112 111 write_c0_entrylo0(entrylo); 113 112 write_c0_entrylo1(entrylo); 113 + if (cpu_has_mmid) { 114 + old_mmid = read_c0_memorymapid(); 115 + write_c0_memorymapid(MMID_KERNEL_WIRED); 116 + } 114 117 #ifdef CONFIG_XPA 115 118 if (cpu_has_xpa) { 116 119 entrylo = (pte.pte_low & _PFNX_MASK); ··· 129 124 tlb_write_indexed(); 130 125 tlbw_use_hazard(); 131 126 write_c0_entryhi(old_ctx); 127 + if (cpu_has_mmid) 128 + write_c0_memorymapid(old_mmid); 132 129 local_irq_restore(flags); 133 130 134 131 return (void*) vaddr;
+41 -11
arch/mips/mm/tlb-r4k.c
··· 120 120 if (size <= (current_cpu_data.tlbsizeftlbsets ? 121 121 current_cpu_data.tlbsize / 8 : 122 122 current_cpu_data.tlbsize / 2)) { 123 - int oldpid = read_c0_entryhi(); 123 + unsigned long old_entryhi, uninitialized_var(old_mmid); 124 124 int newpid = cpu_asid(cpu, mm); 125 + 126 + old_entryhi = read_c0_entryhi(); 127 + if (cpu_has_mmid) { 128 + old_mmid = read_c0_memorymapid(); 129 + write_c0_memorymapid(newpid); 130 + } 125 131 126 132 htw_stop(); 127 133 while (start < end) { 128 134 int idx; 129 135 130 - write_c0_entryhi(start | newpid); 136 + if (cpu_has_mmid) 137 + write_c0_entryhi(start); 138 + else 139 + write_c0_entryhi(start | newpid); 131 140 start += (PAGE_SIZE << 1); 132 141 mtc0_tlbw_hazard(); 133 142 tlb_probe(); ··· 152 143 tlb_write_indexed(); 153 144 } 154 145 tlbw_use_hazard(); 155 - write_c0_entryhi(oldpid); 146 + write_c0_entryhi(old_entryhi); 147 + if (cpu_has_mmid) 148 + write_c0_memorymapid(old_mmid); 156 149 htw_start(); 157 150 } else { 158 151 drop_mmu_context(mm); ··· 214 203 int cpu = smp_processor_id(); 215 204 216 205 if (cpu_context(cpu, vma->vm_mm) != 0) { 217 - unsigned long flags; 218 - int oldpid, newpid, idx; 206 + unsigned long uninitialized_var(old_mmid); 207 + unsigned long flags, old_entryhi; 208 + int idx; 219 209 220 - newpid = cpu_asid(cpu, vma->vm_mm); 221 210 page &= (PAGE_MASK << 1); 222 211 local_irq_save(flags); 223 - oldpid = read_c0_entryhi(); 212 + old_entryhi = read_c0_entryhi(); 224 213 htw_stop(); 225 - write_c0_entryhi(page | newpid); 214 + if (cpu_has_mmid) { 215 + old_mmid = read_c0_memorymapid(); 216 + write_c0_entryhi(page); 217 + write_c0_memorymapid(cpu_asid(cpu, vma->vm_mm)); 218 + } else { 219 + write_c0_entryhi(page | cpu_asid(cpu, vma->vm_mm)); 220 + } 226 221 mtc0_tlbw_hazard(); 227 222 tlb_probe(); 228 223 tlb_probe_hazard(); ··· 244 227 tlbw_use_hazard(); 245 228 246 229 finish: 247 - write_c0_entryhi(oldpid); 230 + write_c0_entryhi(old_entryhi); 231 + if (cpu_has_mmid) 232 + write_c0_memorymapid(old_mmid); 248 233 htw_start(); 249 234 flush_micro_tlb_vm(vma); 250 235 local_irq_restore(flags); ··· 309 290 local_irq_save(flags); 310 291 311 292 htw_stop(); 312 - pid = read_c0_entryhi() & cpu_asid_mask(&current_cpu_data); 313 293 address &= (PAGE_MASK << 1); 314 - write_c0_entryhi(address | pid); 294 + if (cpu_has_mmid) { 295 + write_c0_entryhi(address); 296 + } else { 297 + pid = read_c0_entryhi() & cpu_asid_mask(&current_cpu_data); 298 + write_c0_entryhi(address | pid); 299 + } 315 300 pgdp = pgd_offset(vma->vm_mm, address); 316 301 mtc0_tlbw_hazard(); 317 302 tlb_probe(); ··· 381 358 #ifdef CONFIG_XPA 382 359 panic("Broken for XPA kernels"); 383 360 #else 361 + unsigned int uninitialized_var(old_mmid); 384 362 unsigned long flags; 385 363 unsigned long wired; 386 364 unsigned long old_pagemask; 387 365 unsigned long old_ctx; 388 366 389 367 local_irq_save(flags); 368 + if (cpu_has_mmid) { 369 + old_mmid = read_c0_memorymapid(); 370 + write_c0_memorymapid(MMID_KERNEL_WIRED); 371 + } 390 372 /* Save old context and create impossible VPN2 value */ 391 373 old_ctx = read_c0_entryhi(); 392 374 htw_stop(); ··· 409 381 tlbw_use_hazard(); 410 382 411 383 write_c0_entryhi(old_ctx); 384 + if (cpu_has_mmid) 385 + write_c0_memorymapid(old_mmid); 412 386 tlbw_use_hazard(); /* What is the hazard here? */ 413 387 htw_start(); 414 388 write_c0_pagemask(old_pagemask);