|
@@ -6,13 +6,97 @@
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
|
* published by the Free Software Foundation.
|
|
|
+ *
|
|
|
+ * vineetg: Aug 2011
|
|
|
+ * -Reintroduce duplicate PD fixup - some customer chips still have the issue
|
|
|
+ *
|
|
|
+ * vineetg: May 2011
|
|
|
+ * -No need to flush_cache_page( ) for each call to update_mmu_cache()
|
|
|
+ * some of the LMBench tests improved amazingly
|
|
|
+ * = page-fault thrice as fast (75 usec to 28 usec)
|
|
|
+ * = mmap twice as fast (9.6 msec to 4.6 msec),
|
|
|
+ * = fork (5.3 msec to 3.7 msec)
|
|
|
+ *
|
|
|
+ * vineetg: April 2011 :
|
|
|
+ * -MMU v3: PD{0,1} bits layout changed: They don't overlap anymore,
|
|
|
+ * helps avoid a shift when preparing PD0 from PTE
|
|
|
+ *
|
|
|
+ * vineetg: April 2011 : Preparing for MMU V3
|
|
|
+ * -MMU v2/v3 BCRs decoded differently
|
|
|
+ * -Remove TLB_SIZE hardcoding as it's variable now: 256 or 512
|
|
|
+ * -tlb_entry_erase( ) can be void
|
|
|
+ * -local_flush_tlb_range( ):
|
|
|
+ * = need not "ceil" @end
|
|
|
+ * = walks MMU only if range spans < 32 entries, as opposed to 256
|
|
|
+ *
|
|
|
+ * Vineetg: Sept 10th 2008
|
|
|
+ * -Changes related to MMU v2 (Rel 4.8)
|
|
|
+ *
|
|
|
+ * Vineetg: Aug 29th 2008
|
|
|
+ * -In TLB Flush operations (Metal Fix MMU) there is a explict command to
|
|
|
+ * flush Micro-TLBS. If TLB Index Reg is invalid prior to TLBIVUTLB cmd,
|
|
|
+ * it fails. Thus need to load it with ANY valid value before invoking
|
|
|
+ * TLBIVUTLB cmd
|
|
|
+ *
|
|
|
+ * Vineetg: Aug 21th 2008:
|
|
|
+ * -Reduced the duration of IRQ lockouts in TLB Flush routines
|
|
|
+ * -Multiple copies of TLB erase code seperated into a "single" function
|
|
|
+ * -In TLB Flush routines, interrupt disabling moved UP to retrieve ASID
|
|
|
+ * in interrupt-safe region.
|
|
|
+ *
|
|
|
+ * Vineetg: April 23rd Bug #93131
|
|
|
+ * Problem: tlb_flush_kernel_range() doesnt do anything if the range to
|
|
|
+ * flush is more than the size of TLB itself.
|
|
|
+ *
|
|
|
+ * Rahul Trivedi : Codito Technologies 2004
|
|
|
*/
|
|
|
|
|
|
#include <linux/module.h>
|
|
|
#include <asm/arcregs.h>
|
|
|
+#include <asm/setup.h>
|
|
|
#include <asm/mmu_context.h>
|
|
|
#include <asm/tlb.h>
|
|
|
|
|
|
+/* Need for ARC MMU v2
|
|
|
+ *
|
|
|
+ * ARC700 MMU-v1 had a Joint-TLB for Code and Data and is 2 way set-assoc.
|
|
|
+ * For a memcpy operation with 3 players (src/dst/code) such that all 3 pages
|
|
|
+ * map into same set, there would be contention for the 2 ways causing severe
|
|
|
+ * Thrashing.
|
|
|
+ *
|
|
|
+ * Although J-TLB is 2 way set assoc, ARC700 caches J-TLB into uTLBS which has
|
|
|
+ * much higher associativity. u-D-TLB is 8 ways, u-I-TLB is 4 ways.
|
|
|
+ * Given this, the thrasing problem should never happen because once the 3
|
|
|
+ * J-TLB entries are created (even though 3rd will knock out one of the prev
|
|
|
+ * two), the u-D-TLB and u-I-TLB will have what is required to accomplish memcpy
|
|
|
+ *
|
|
|
+ * Yet we still see the Thrashing because a J-TLB Write cause flush of u-TLBs.
|
|
|
+ * This is a simple design for keeping them in sync. So what do we do?
|
|
|
+ * The solution which James came up was pretty neat. It utilised the assoc
|
|
|
+ * of uTLBs by not invalidating always but only when absolutely necessary.
|
|
|
+ *
|
|
|
+ * - Existing TLB commands work as before
|
|
|
+ * - New command (TLBWriteNI) for TLB write without clearing uTLBs
|
|
|
+ * - New command (TLBIVUTLB) to invalidate uTLBs.
|
|
|
+ *
|
|
|
+ * The uTLBs need only be invalidated when pages are being removed from the
|
|
|
+ * OS page table. If a 'victim' TLB entry is being overwritten in the main TLB
|
|
|
+ * as a result of a miss, the removed entry is still allowed to exist in the
|
|
|
+ * uTLBs as it is still valid and present in the OS page table. This allows the
|
|
|
+ * full associativity of the uTLBs to hide the limited associativity of the main
|
|
|
+ * TLB.
|
|
|
+ *
|
|
|
+ * During a miss handler, the new "TLBWriteNI" command is used to load
|
|
|
+ * entries without clearing the uTLBs.
|
|
|
+ *
|
|
|
+ * When the OS page table is updated, TLB entries that may be associated with a
|
|
|
+ * removed page are removed (flushed) from the TLB using TLBWrite. In this
|
|
|
+ * circumstance, the uTLBs must also be cleared. This is done by using the
|
|
|
+ * existing TLBWrite command. An explicit IVUTLB is also required for those
|
|
|
+ * corner cases when TLBWrite was not executed at all because the corresp
|
|
|
+ * J-TLB entry got evicted/replaced.
|
|
|
+ */
|
|
|
+
|
|
|
/* A copy of the ASID from the PID reg is kept in asid_cache */
|
|
|
int asid_cache = FIRST_ASID;
|
|
|
|
|
@@ -22,6 +106,233 @@ int asid_cache = FIRST_ASID;
|
|
|
*/
|
|
|
struct mm_struct *asid_mm_map[NUM_ASID + 1];
|
|
|
|
|
|
+/*
|
|
|
+ * Utility Routine to erase a J-TLB entry
|
|
|
+ * The procedure is to look it up in the MMU. If found, ERASE it by
|
|
|
+ * issuing a TlbWrite CMD with PD0 = PD1 = 0
|
|
|
+ */
|
|
|
+
|
|
|
+static void __tlb_entry_erase(void)
|
|
|
+{
|
|
|
+ write_aux_reg(ARC_REG_TLBPD1, 0);
|
|
|
+ write_aux_reg(ARC_REG_TLBPD0, 0);
|
|
|
+ write_aux_reg(ARC_REG_TLBCOMMAND, TLBWrite);
|
|
|
+}
|
|
|
+
|
|
|
+static void tlb_entry_erase(unsigned int vaddr_n_asid)
|
|
|
+{
|
|
|
+ unsigned int idx;
|
|
|
+
|
|
|
+ /* Locate the TLB entry for this vaddr + ASID */
|
|
|
+ write_aux_reg(ARC_REG_TLBPD0, vaddr_n_asid);
|
|
|
+ write_aux_reg(ARC_REG_TLBCOMMAND, TLBProbe);
|
|
|
+ idx = read_aux_reg(ARC_REG_TLBINDEX);
|
|
|
+
|
|
|
+ /* No error means entry found, zero it out */
|
|
|
+ if (likely(!(idx & TLB_LKUP_ERR))) {
|
|
|
+ __tlb_entry_erase();
|
|
|
+ } else { /* Some sort of Error */
|
|
|
+
|
|
|
+ /* Duplicate entry error */
|
|
|
+ if (idx & 0x1) {
|
|
|
+ /* TODO we need to handle this case too */
|
|
|
+ pr_emerg("unhandled Duplicate flush for %x\n",
|
|
|
+ vaddr_n_asid);
|
|
|
+ }
|
|
|
+ /* else entry not found so nothing to do */
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/****************************************************************************
|
|
|
+ * ARC700 MMU caches recently used J-TLB entries (RAM) as uTLBs (FLOPs)
|
|
|
+ *
|
|
|
+ * New IVUTLB cmd in MMU v2 explictly invalidates the uTLB
|
|
|
+ *
|
|
|
+ * utlb_invalidate ( )
|
|
|
+ * -For v2 MMU calls Flush uTLB Cmd
|
|
|
+ * -For v1 MMU does nothing (except for Metal Fix v1 MMU)
|
|
|
+ * This is because in v1 TLBWrite itself invalidate uTLBs
|
|
|
+ ***************************************************************************/
|
|
|
+
|
|
|
+static void utlb_invalidate(void)
|
|
|
+{
|
|
|
+#if (CONFIG_ARC_MMU_VER >= 2)
|
|
|
+
|
|
|
+#if (CONFIG_ARC_MMU_VER < 3)
|
|
|
+ /* MMU v2 introduced the uTLB Flush command.
|
|
|
+ * There was however an obscure hardware bug, where uTLB flush would
|
|
|
+ * fail when a prior probe for J-TLB (both totally unrelated) would
|
|
|
+ * return lkup err - because the entry didnt exist in MMU.
|
|
|
+ * The Workround was to set Index reg with some valid value, prior to
|
|
|
+ * flush. This was fixed in MMU v3 hence not needed any more
|
|
|
+ */
|
|
|
+ unsigned int idx;
|
|
|
+
|
|
|
+ /* make sure INDEX Reg is valid */
|
|
|
+ idx = read_aux_reg(ARC_REG_TLBINDEX);
|
|
|
+
|
|
|
+ /* If not write some dummy val */
|
|
|
+ if (unlikely(idx & TLB_LKUP_ERR))
|
|
|
+ write_aux_reg(ARC_REG_TLBINDEX, 0xa);
|
|
|
+#endif
|
|
|
+
|
|
|
+ write_aux_reg(ARC_REG_TLBCOMMAND, TLBIVUTLB);
|
|
|
+#endif
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Un-conditionally (without lookup) erase the entire MMU contents
|
|
|
+ */
|
|
|
+
|
|
|
+noinline void local_flush_tlb_all(void)
|
|
|
+{
|
|
|
+ unsigned long flags;
|
|
|
+ unsigned int entry;
|
|
|
+ struct cpuinfo_arc_mmu *mmu = &cpuinfo_arc700[smp_processor_id()].mmu;
|
|
|
+
|
|
|
+ local_irq_save(flags);
|
|
|
+
|
|
|
+ /* Load PD0 and PD1 with template for a Blank Entry */
|
|
|
+ write_aux_reg(ARC_REG_TLBPD1, 0);
|
|
|
+ write_aux_reg(ARC_REG_TLBPD0, 0);
|
|
|
+
|
|
|
+ for (entry = 0; entry < mmu->num_tlb; entry++) {
|
|
|
+ /* write this entry to the TLB */
|
|
|
+ write_aux_reg(ARC_REG_TLBINDEX, entry);
|
|
|
+ write_aux_reg(ARC_REG_TLBCOMMAND, TLBWrite);
|
|
|
+ }
|
|
|
+
|
|
|
+ utlb_invalidate();
|
|
|
+
|
|
|
+ local_irq_restore(flags);
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Flush the entrie MM for userland. The fastest way is to move to Next ASID
|
|
|
+ */
|
|
|
+noinline void local_flush_tlb_mm(struct mm_struct *mm)
|
|
|
+{
|
|
|
+ /*
|
|
|
+ * Small optimisation courtesy IA64
|
|
|
+ * flush_mm called during fork,exit,munmap etc, multiple times as well.
|
|
|
+ * Only for fork( ) do we need to move parent to a new MMU ctxt,
|
|
|
+ * all other cases are NOPs, hence this check.
|
|
|
+ */
|
|
|
+ if (atomic_read(&mm->mm_users) == 0)
|
|
|
+ return;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Workaround for Android weirdism:
|
|
|
+ * A binder VMA could end up in a task such that vma->mm != tsk->mm
|
|
|
+ * old code would cause h/w - s/w ASID to get out of sync
|
|
|
+ */
|
|
|
+ if (current->mm != mm)
|
|
|
+ destroy_context(mm);
|
|
|
+ else
|
|
|
+ get_new_mmu_context(mm);
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Flush a Range of TLB entries for userland.
|
|
|
+ * @start is inclusive, while @end is exclusive
|
|
|
+ * Difference between this and Kernel Range Flush is
|
|
|
+ * -Here the fastest way (if range is too large) is to move to next ASID
|
|
|
+ * without doing any explicit Shootdown
|
|
|
+ * -In case of kernel Flush, entry has to be shot down explictly
|
|
|
+ */
|
|
|
+void local_flush_tlb_range(struct vm_area_struct *vma, unsigned long start,
|
|
|
+ unsigned long end)
|
|
|
+{
|
|
|
+ unsigned long flags;
|
|
|
+ unsigned int asid;
|
|
|
+
|
|
|
+ /* If range @start to @end is more than 32 TLB entries deep,
|
|
|
+ * its better to move to a new ASID rather than searching for
|
|
|
+ * individual entries and then shooting them down
|
|
|
+ *
|
|
|
+ * The calc above is rough, doesn't account for unaligned parts,
|
|
|
+ * since this is heuristics based anyways
|
|
|
+ */
|
|
|
+ if (unlikely((end - start) >= PAGE_SIZE * 32)) {
|
|
|
+ local_flush_tlb_mm(vma->vm_mm);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * @start moved to page start: this alone suffices for checking
|
|
|
+ * loop end condition below, w/o need for aligning @end to end
|
|
|
+ * e.g. 2000 to 4001 will anyhow loop twice
|
|
|
+ */
|
|
|
+ start &= PAGE_MASK;
|
|
|
+
|
|
|
+ local_irq_save(flags);
|
|
|
+ asid = vma->vm_mm->context.asid;
|
|
|
+
|
|
|
+ if (asid != NO_ASID) {
|
|
|
+ while (start < end) {
|
|
|
+ tlb_entry_erase(start | (asid & 0xff));
|
|
|
+ start += PAGE_SIZE;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ utlb_invalidate();
|
|
|
+
|
|
|
+ local_irq_restore(flags);
|
|
|
+}
|
|
|
+
|
|
|
+/* Flush the kernel TLB entries - vmalloc/modules (Global from MMU perspective)
|
|
|
+ * @start, @end interpreted as kvaddr
|
|
|
+ * Interestingly, shared TLB entries can also be flushed using just
|
|
|
+ * @start,@end alone (interpreted as user vaddr), although technically SASID
|
|
|
+ * is also needed. However our smart TLbProbe lookup takes care of that.
|
|
|
+ */
|
|
|
+void local_flush_tlb_kernel_range(unsigned long start, unsigned long end)
|
|
|
+{
|
|
|
+ unsigned long flags;
|
|
|
+
|
|
|
+ /* exactly same as above, except for TLB entry not taking ASID */
|
|
|
+
|
|
|
+ if (unlikely((end - start) >= PAGE_SIZE * 32)) {
|
|
|
+ local_flush_tlb_all();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ start &= PAGE_MASK;
|
|
|
+
|
|
|
+ local_irq_save(flags);
|
|
|
+ while (start < end) {
|
|
|
+ tlb_entry_erase(start);
|
|
|
+ start += PAGE_SIZE;
|
|
|
+ }
|
|
|
+
|
|
|
+ utlb_invalidate();
|
|
|
+
|
|
|
+ local_irq_restore(flags);
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Delete TLB entry in MMU for a given page (??? address)
|
|
|
+ * NOTE One TLB entry contains translation for single PAGE
|
|
|
+ */
|
|
|
+
|
|
|
+void local_flush_tlb_page(struct vm_area_struct *vma, unsigned long page)
|
|
|
+{
|
|
|
+ unsigned long flags;
|
|
|
+
|
|
|
+ /* Note that it is critical that interrupts are DISABLED between
|
|
|
+ * checking the ASID and using it flush the TLB entry
|
|
|
+ */
|
|
|
+ local_irq_save(flags);
|
|
|
+
|
|
|
+ if (vma->vm_mm->context.asid != NO_ASID) {
|
|
|
+ tlb_entry_erase((page & PAGE_MASK) |
|
|
|
+ (vma->vm_mm->context.asid & 0xff));
|
|
|
+ utlb_invalidate();
|
|
|
+ }
|
|
|
+
|
|
|
+ local_irq_restore(flags);
|
|
|
+}
|
|
|
|
|
|
/*
|
|
|
* Routine to create a TLB entry
|