| /* |
| * 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); |
| } |