/* * arch/score/mm/tlb-score.c * * Score Processor version. * * Copyright (C) 2009 Sunplus Core Technology Co., Ltd. * Lennox Wu <lennox.wu@sunplusct.com> * Chen Liqin <liqin.chen@sunplusct.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see the file COPYING, or write * to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include <linux/highmem.h> #include <linux/module.h> #include <asm/irq.h> #include <asm/mmu_context.h> #include <asm/tlb.h> #define TLBSIZE 32 unsigned long asid_cache = ASID_FIRST_VERSION; EXPORT_SYMBOL(asid_cache); void local_flush_tlb_all(void) { unsigned long flags; unsigned long old_ASID; int entry; local_irq_save(flags); old_ASID = pevn_get() & ASID_MASK; pectx_set(0); /* invalid */ entry = tlblock_get(); /* skip locked entries*/ for (; entry < TLBSIZE; entry++) { tlbpt_set(entry); pevn_set(KSEG1); barrier(); tlb_write_indexed(); } pevn_set(old_ASID); local_irq_restore(flags); } /* * If mm is currently active_mm, we can't really drop it. Instead, * we will get a new one for it. */ static inline void drop_mmu_context(struct mm_struct *mm) { unsigned long flags; local_irq_save(flags); get_new_mmu_context(mm); pevn_set(mm->context & ASID_MASK); local_irq_restore(flags); } void local_flush_tlb_mm(struct mm_struct *mm) { if (mm->context != 0) drop_mmu_context(mm); } void local_flush_tlb_range(struct vm_area_struct *vma, unsigned long start, unsigned long end) { struct mm_struct *mm = vma->vm_mm; unsigned long vma_mm_context = mm->context; if (mm->context != 0) { unsigned long flags; int size; local_irq_save(flags); size = (end - start + (PAGE_SIZE - 1)) >> PAGE_SHIFT; if (size <= TLBSIZE) { int oldpid = pevn_get() & ASID_MASK; int newpid = vma_mm_context & ASID_MASK; start &= PAGE_MASK; end += (PAGE_SIZE - 1); end &= PAGE_MASK; while (start < end) { int idx; pevn_set(start | newpid); start += PAGE_SIZE; barrier(); tlb_probe(); idx = tlbpt_get(); pectx_set(0); pevn_set(KSEG1); if (idx < 0) continue; tlb_write_indexed(); } pevn_set(oldpid); } else { /* Bigger than TLBSIZE, get new ASID directly */ get_new_mmu_context(mm); if (mm == current->active_mm) pevn_set(vma_mm_context & ASID_MASK); } local_irq_restore(flags); } } void local_flush_tlb_kernel_range(unsigned long start, unsigned long end) { unsigned long flags; int size; local_irq_save(flags); size = (end - start + (PAGE_SIZE - 1)) >> PAGE_SHIFT; if (size <= TLBSIZE) { int pid = pevn_get(); start &= PAGE_MASK; end += PAGE_SIZE - 1; end &= PAGE_MASK; while (start < end) { long idx; pevn_set(start); start += PAGE_SIZE; tlb_probe(); idx = tlbpt_get(); if (idx < 0) continue; pectx_set(0); pevn_set(KSEG1); barrier(); tlb_write_indexed(); } pevn_set(pid); } else { local_flush_tlb_all(); } local_irq_restore(flags); } void local_flush_tlb_page(struct vm_area_struct *vma, unsigned long page) { if (vma && vma->vm_mm->context != 0) { unsigned long flags; int oldpid, newpid, idx; unsigned long vma_ASID = vma->vm_mm->context; newpid = vma_ASID & ASID_MASK; page &= PAGE_MASK; local_irq_save(flags); oldpid = pevn_get() & ASID_MASK; pevn_set(page | newpid); barrier(); tlb_probe(); idx = tlbpt_get(); pectx_set(0); pevn_set(KSEG1); if (idx < 0) /* p_bit(31) - 1: miss, 0: hit*/ goto finish; barrier(); tlb_write_indexed(); finish: pevn_set(oldpid); local_irq_restore(flags); } } /* * This one is only used for pages with the global bit set so we don't care * much about the ASID. */ void local_flush_tlb_one(unsigned long page) { unsigned long flags; int oldpid, idx; local_irq_save(flags); oldpid = pevn_get(); page &= (PAGE_MASK << 1); pevn_set(page); barrier(); tlb_probe(); idx = tlbpt_get(); pectx_set(0); if (idx >= 0) { /* Make sure all entries differ. */ pevn_set(KSEG1); barrier(); tlb_write_indexed(); } pevn_set(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 = pevn_get() & ASID_MASK; local_irq_save(flags); address &= PAGE_MASK; pevn_set(address | pid); barrier(); tlb_probe(); idx = tlbpt_get(); pectx_set(pte_val(pte)); pevn_set(address | pid); if (idx < 0) tlb_write_random(); else tlb_write_indexed(); pevn_set(pid); local_irq_restore(flags); } void __cpuinit tlb_init(void) { tlblock_set(0); local_flush_tlb_all(); memcpy((void *)(EXCEPTION_VECTOR_BASE_ADDR + 0x100), &score7_FTLB_refill_Handler, 0xFC); flush_icache_range(EXCEPTION_VECTOR_BASE_ADDR + 0x100, EXCEPTION_VECTOR_BASE_ADDR + 0x1FC); }