linux/arch/mips/mm/tlb-r3k.c
Maciej W. Rozycki dbfbf60f4a MIPS: tlb-r3k: Also invalidate wired TLB entries on boot
Most R3k processor implementations have their 8 first TLB entries fixed
as wired, so we always skip them in TLB invalidation.  That however
means any leftover entries present there at boot will stay throughout
the life of the kernel, unless replaced with new ones.

So rename `local_flush_tlb_all' to `local_flush_tlb_from' and make it
accept the TLB entry to start from.  Then use 0 initially at bootstrap,
and the first regular entry later on, bypassing any wired entries.
Wrap the latter arrangement into a new `local_flush_tlb_all' entry
point.

There is no need to disable interrupts in the call made from `tlb_init'
because it's made before the interrupt subsystem has been initialised;
this is also true for secondary processors, should we ever support R3k
SMP.  So move this piece of code to new `local_flush_tlb_all'.

Signed-off-by: Maciej W. Rozycki <macro@linux-mips.org>
Cc: James Hogan <james.hogan@imgtec.com>
Cc: linux-mips@linux-mips.org
Patchwork: https://patchwork.linux-mips.org/patch/10194/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
2015-06-21 21:52:39 +02:00

286 lines
6.4 KiB
C

/*
* r2300.c: R2000 and R3000 specific mmu/cache code.
*
* Copyright (C) 1996 David S. Miller (davem@davemloft.net)
*
* with a lot of changes to make this thing work for R3000s
* Tx39XX R4k style caches added. HK
* Copyright (C) 1998, 1999, 2000 Harald Koerfgen
* Copyright (C) 1998 Gleb Raiko & Vladimir Roganov
* Copyright (C) 2002 Ralf Baechle
* Copyright (C) 2002 Maciej W. Rozycki
*/
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/smp.h>
#include <linux/mm.h>
#include <asm/page.h>
#include <asm/pgtable.h>
#include <asm/mmu_context.h>
#include <asm/tlbmisc.h>
#include <asm/isadep.h>
#include <asm/io.h>
#include <asm/bootinfo.h>
#include <asm/cpu.h>
#undef DEBUG_TLB
extern void build_tlb_refill_handler(void);
/* CP0 hazard avoidance. */
#define BARRIER \
__asm__ __volatile__( \
".set push\n\t" \
".set noreorder\n\t" \
"nop\n\t" \
".set pop\n\t")
int r3k_have_wired_reg; /* should be in cpu_data? */
/* TLB operations. */
static void local_flush_tlb_from(int entry)
{
unsigned long old_ctx;
old_ctx = read_c0_entryhi() & ASID_MASK;
write_c0_entrylo0(0);
for (; entry < current_cpu_data.tlbsize; entry++) {
write_c0_index(entry << 8);
write_c0_entryhi((entry | 0x80000) << 12);
BARRIER;
tlb_write_indexed();
}
write_c0_entryhi(old_ctx);
}
void local_flush_tlb_all(void)
{
unsigned long flags;
#ifdef DEBUG_TLB
printk("[tlball]");
#endif
local_irq_save(flags);
local_flush_tlb_from(r3k_have_wired_reg ? read_c0_wired() : 8);
local_irq_restore(flags);
}
void local_flush_tlb_mm(struct mm_struct *mm)
{
int cpu = smp_processor_id();
if (cpu_context(cpu, mm) != 0) {
#ifdef DEBUG_TLB
printk("[tlbmm<%lu>]", (unsigned long)cpu_context(cpu, mm));
#endif
drop_mmu_context(mm, cpu);
}
}
void local_flush_tlb_range(struct vm_area_struct *vma, unsigned long start,
unsigned long end)
{
struct mm_struct *mm = vma->vm_mm;
int cpu = smp_processor_id();
if (cpu_context(cpu, mm) != 0) {
unsigned long size, flags;
#ifdef DEBUG_TLB
printk("[tlbrange<%lu,0x%08lx,0x%08lx>]",
cpu_context(cpu, mm) & ASID_MASK, start, end);
#endif
local_irq_save(flags);
size = (end - start + (PAGE_SIZE - 1)) >> PAGE_SHIFT;
if (size <= current_cpu_data.tlbsize) {
int oldpid = read_c0_entryhi() & ASID_MASK;
int newpid = cpu_context(cpu, mm) & ASID_MASK;
start &= PAGE_MASK;
end += PAGE_SIZE - 1;
end &= PAGE_MASK;
while (start < end) {
int idx;
write_c0_entryhi(start | newpid);
start += PAGE_SIZE; /* BARRIER */
tlb_probe();
idx = read_c0_index();
write_c0_entrylo0(0);
write_c0_entryhi(KSEG0);
if (idx < 0) /* BARRIER */
continue;
tlb_write_indexed();
}
write_c0_entryhi(oldpid);
} else {
drop_mmu_context(mm, cpu);
}
local_irq_restore(flags);
}
}
void local_flush_tlb_kernel_range(unsigned long start, unsigned long end)
{
unsigned long size, flags;
#ifdef DEBUG_TLB
printk("[tlbrange<%lu,0x%08lx,0x%08lx>]", start, end);
#endif
local_irq_save(flags);
size = (end - start + (PAGE_SIZE - 1)) >> PAGE_SHIFT;
if (size <= current_cpu_data.tlbsize) {
int pid = read_c0_entryhi();
start &= PAGE_MASK;
end += PAGE_SIZE - 1;
end &= PAGE_MASK;
while (start < end) {
int idx;
write_c0_entryhi(start);
start += PAGE_SIZE; /* BARRIER */
tlb_probe();
idx = read_c0_index();
write_c0_entrylo0(0);
write_c0_entryhi(KSEG0);
if (idx < 0) /* BARRIER */
continue;
tlb_write_indexed();
}
write_c0_entryhi(pid);
} else {
local_flush_tlb_all();
}
local_irq_restore(flags);
}
void local_flush_tlb_page(struct vm_area_struct *vma, unsigned long page)
{
int cpu = smp_processor_id();
if (cpu_context(cpu, vma->vm_mm) != 0) {
unsigned long flags;
int oldpid, newpid, idx;
#ifdef DEBUG_TLB
printk("[tlbpage<%lu,0x%08lx>]", cpu_context(cpu, vma->vm_mm), page);
#endif
newpid = cpu_context(cpu, vma->vm_mm) & ASID_MASK;
page &= PAGE_MASK;
local_irq_save(flags);
oldpid = read_c0_entryhi() & ASID_MASK;
write_c0_entryhi(page | newpid);
BARRIER;
tlb_probe();
idx = read_c0_index();
write_c0_entrylo0(0);
write_c0_entryhi(KSEG0);
if (idx < 0) /* BARRIER */
goto finish;
tlb_write_indexed();
finish:
write_c0_entryhi(oldpid);
local_irq_restore(flags);
}
}
void __update_tlb(struct vm_area_struct *vma, unsigned long address, pte_t pte)
{
unsigned long flags;
int idx, pid;
/*
* Handle debugger faulting in for debugee.
*/
if (current->active_mm != vma->vm_mm)
return;
pid = read_c0_entryhi() & ASID_MASK;
#ifdef DEBUG_TLB
if ((pid != (cpu_context(cpu, vma->vm_mm) & ASID_MASK)) || (cpu_context(cpu, vma->vm_mm) == 0)) {
printk("update_mmu_cache: Wheee, bogus tlbpid mmpid=%lu tlbpid=%d\n",
(cpu_context(cpu, vma->vm_mm)), pid);
}
#endif
local_irq_save(flags);
address &= PAGE_MASK;
write_c0_entryhi(address | pid);
BARRIER;
tlb_probe();
idx = read_c0_index();
write_c0_entrylo0(pte_val(pte));
write_c0_entryhi(address | pid);
if (idx < 0) { /* BARRIER */
tlb_write_random();
} else {
tlb_write_indexed();
}
write_c0_entryhi(pid);
local_irq_restore(flags);
}
void add_wired_entry(unsigned long entrylo0, unsigned long entrylo1,
unsigned long entryhi, unsigned long pagemask)
{
unsigned long flags;
unsigned long old_ctx;
static unsigned long wired = 0;
if (r3k_have_wired_reg) { /* TX39XX */
unsigned long old_pagemask;
unsigned long w;
#ifdef DEBUG_TLB
printk("[tlbwired<entry lo0 %8x, hi %8x\n, pagemask %8x>]\n",
entrylo0, entryhi, pagemask);
#endif
local_irq_save(flags);
/* Save old context and create impossible VPN2 value */
old_ctx = read_c0_entryhi() & ASID_MASK;
old_pagemask = read_c0_pagemask();
w = read_c0_wired();
write_c0_wired(w + 1);
write_c0_index(w << 8);
write_c0_pagemask(pagemask);
write_c0_entryhi(entryhi);
write_c0_entrylo0(entrylo0);
BARRIER;
tlb_write_indexed();
write_c0_entryhi(old_ctx);
write_c0_pagemask(old_pagemask);
local_flush_tlb_all();
local_irq_restore(flags);
} else if (wired < 8) {
#ifdef DEBUG_TLB
printk("[tlbwired<entry lo0 %8x, hi %8x\n>]\n",
entrylo0, entryhi);
#endif
local_irq_save(flags);
old_ctx = read_c0_entryhi() & ASID_MASK;
write_c0_entrylo0(entrylo0);
write_c0_entryhi(entryhi);
write_c0_index(wired);
wired++; /* BARRIER */
tlb_write_indexed();
write_c0_entryhi(old_ctx);
local_flush_tlb_all();
local_irq_restore(flags);
}
}
void tlb_init(void)
{
local_flush_tlb_from(0);
build_tlb_refill_handler();
}