Skip to content

Commit

Permalink
ARC: TLB flush Handling
Browse files Browse the repository at this point in the history
Signed-off-by: Vineet Gupta <[email protected]>
  • Loading branch information
vineetgarc committed Feb 15, 2013
1 parent cc562d2 commit d79e678
Show file tree
Hide file tree
Showing 3 changed files with 356 additions and 0 deletions.
17 changes: 17 additions & 0 deletions arch/arc/include/asm/tlb.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,23 @@

#ifndef __ASSEMBLY__

#define tlb_flush(tlb) local_flush_tlb_mm((tlb)->mm)

/*
* This pair is called at time of munmap/exit to flush cache and TLB entries
* for mappings being torn down.
* 1) cache-flush part -implemented via tlb_start_vma( ) can be NOP (for now)
* as we don't support aliasing configs in our VIPT D$.
* 2) tlb-flush part - implemted via tlb_end_vma( ) can be NOP as well-
* albiet for difft reasons - its better handled by moving to new ASID
*
* Note, read http://lkml.org/lkml/2004/1/15/6
*/
#define tlb_start_vma(tlb, vma)
#define tlb_end_vma(tlb, vma)

#define __tlb_remove_tlb_entry(tlb, ptep, address)

#include <linux/pagemap.h>
#include <asm-generic/tlb.h>

Expand Down
28 changes: 28 additions & 0 deletions arch/arc/include/asm/tlbflush.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (C) 2004, 2007-2010, 2011-2012 Synopsys, Inc. (www.synopsys.com)
*
* 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.
*/

#ifndef __ASM_ARC_TLBFLUSH__
#define __ASM_ARC_TLBFLUSH__

#include <linux/mm.h>

void local_flush_tlb_all(void);
void local_flush_tlb_mm(struct mm_struct *mm);
void local_flush_tlb_page(struct vm_area_struct *vma, unsigned long page);
void local_flush_tlb_kernel_range(unsigned long start, unsigned long end);
void local_flush_tlb_range(struct vm_area_struct *vma,
unsigned long start, unsigned long end);

/* XXX: Revisit for SMP */
#define flush_tlb_range(vma, s, e) local_flush_tlb_range(vma, s, e)
#define flush_tlb_page(vma, page) local_flush_tlb_page(vma, page)
#define flush_tlb_kernel_range(s, e) local_flush_tlb_kernel_range(s, e)
#define flush_tlb_all() local_flush_tlb_all()
#define flush_tlb_mm(mm) local_flush_tlb_mm(mm)

#endif
311 changes: 311 additions & 0 deletions arch/arc/mm/tlb.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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
Expand Down

0 comments on commit d79e678

Please sign in to comment.