android_kernel_motorola_sm6225/arch/xtensa/mm/tlb.c
Max Filippov 87962c4db7 xtensa: flush TLB entries for pages of non-current mm correctly
Sometimes under high memory pressure one process gets a page of another
process, which manifests itself with an invalid instruction exception.

This happens because flush_tlb_page fails to clear TLB entries when
called with vma that does not belong to current mm, because it does not
set RASID appropriately. When page reclaiming mechanism swaps physical
pages out replacing their PTEs with none or swap PTEs, it calls
flush_tlb_page. Later physical page may be reused elsewhere, but the
stale TLB mapping still refers to it, allowing process that owned the
mapping to see the new state of that physical page.

Put ASID of the mm that owns vma to the RASID to fix that issue.
Also replace otherwise meaningless local_save_flags with local_irq_save.

Signed-off-by: Max Filippov <jcmvbkbc@gmail.com>
Signed-off-by: Chris Zankel <chris@zankel.net>
2013-06-05 10:14:19 -07:00

143 lines
3.1 KiB
C

/*
* arch/xtensa/mm/tlb.c
*
* Logic that manipulates the Xtensa MMU. Derived from MIPS.
*
* 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) 2001 - 2003 Tensilica Inc.
*
* Joe Taylor
* Chris Zankel <chris@zankel.net>
* Marc Gauthier
*/
#include <linux/mm.h>
#include <asm/processor.h>
#include <asm/mmu_context.h>
#include <asm/tlbflush.h>
#include <asm/cacheflush.h>
static inline void __flush_itlb_all (void)
{
int w, i;
for (w = 0; w < ITLB_ARF_WAYS; w++) {
for (i = 0; i < (1 << XCHAL_ITLB_ARF_ENTRIES_LOG2); i++) {
int e = w + (i << PAGE_SHIFT);
invalidate_itlb_entry_no_isync(e);
}
}
asm volatile ("isync\n");
}
static inline void __flush_dtlb_all (void)
{
int w, i;
for (w = 0; w < DTLB_ARF_WAYS; w++) {
for (i = 0; i < (1 << XCHAL_DTLB_ARF_ENTRIES_LOG2); i++) {
int e = w + (i << PAGE_SHIFT);
invalidate_dtlb_entry_no_isync(e);
}
}
asm volatile ("isync\n");
}
void flush_tlb_all (void)
{
__flush_itlb_all();
__flush_dtlb_all();
}
/* If mm is current, we simply assign the current task a new ASID, thus,
* invalidating all previous tlb entries. If mm is someone else's user mapping,
* wie invalidate the context, thus, when that user mapping is swapped in,
* a new context will be assigned to it.
*/
void flush_tlb_mm(struct mm_struct *mm)
{
if (mm == current->active_mm) {
unsigned long flags;
local_irq_save(flags);
__get_new_mmu_context(mm);
__load_mmu_context(mm);
local_irq_restore(flags);
}
else
mm->context = 0;
}
#define _ITLB_ENTRIES (ITLB_ARF_WAYS << XCHAL_ITLB_ARF_ENTRIES_LOG2)
#define _DTLB_ENTRIES (DTLB_ARF_WAYS << XCHAL_DTLB_ARF_ENTRIES_LOG2)
#if _ITLB_ENTRIES > _DTLB_ENTRIES
# define _TLB_ENTRIES _ITLB_ENTRIES
#else
# define _TLB_ENTRIES _DTLB_ENTRIES
#endif
void flush_tlb_range (struct vm_area_struct *vma,
unsigned long start, unsigned long end)
{
struct mm_struct *mm = vma->vm_mm;
unsigned long flags;
if (mm->context == NO_CONTEXT)
return;
#if 0
printk("[tlbrange<%02lx,%08lx,%08lx>]\n",
(unsigned long)mm->context, start, end);
#endif
local_irq_save(flags);
if (end-start + (PAGE_SIZE-1) <= _TLB_ENTRIES << PAGE_SHIFT) {
int oldpid = get_rasid_register();
set_rasid_register (ASID_INSERT(mm->context));
start &= PAGE_MASK;
if (vma->vm_flags & VM_EXEC)
while(start < end) {
invalidate_itlb_mapping(start);
invalidate_dtlb_mapping(start);
start += PAGE_SIZE;
}
else
while(start < end) {
invalidate_dtlb_mapping(start);
start += PAGE_SIZE;
}
set_rasid_register(oldpid);
} else {
flush_tlb_mm(mm);
}
local_irq_restore(flags);
}
void flush_tlb_page (struct vm_area_struct *vma, unsigned long page)
{
struct mm_struct* mm = vma->vm_mm;
unsigned long flags;
int oldpid;
if(mm->context == NO_CONTEXT)
return;
local_irq_save(flags);
oldpid = get_rasid_register();
set_rasid_register(ASID_INSERT(mm->context));
if (vma->vm_flags & VM_EXEC)
invalidate_itlb_mapping(page);
invalidate_dtlb_mapping(page);
set_rasid_register(oldpid);
local_irq_restore(flags);
}