162306a36Sopenharmony_ci/* 262306a36Sopenharmony_ci * arch/xtensa/mm/tlb.c 362306a36Sopenharmony_ci * 462306a36Sopenharmony_ci * Logic that manipulates the Xtensa MMU. Derived from MIPS. 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public 762306a36Sopenharmony_ci * License. See the file "COPYING" in the main directory of this archive 862306a36Sopenharmony_ci * for more details. 962306a36Sopenharmony_ci * 1062306a36Sopenharmony_ci * Copyright (C) 2001 - 2003 Tensilica Inc. 1162306a36Sopenharmony_ci * 1262306a36Sopenharmony_ci * Joe Taylor 1362306a36Sopenharmony_ci * Chris Zankel <chris@zankel.net> 1462306a36Sopenharmony_ci * Marc Gauthier 1562306a36Sopenharmony_ci */ 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#include <linux/mm.h> 1862306a36Sopenharmony_ci#include <asm/processor.h> 1962306a36Sopenharmony_ci#include <asm/mmu_context.h> 2062306a36Sopenharmony_ci#include <asm/tlb.h> 2162306a36Sopenharmony_ci#include <asm/tlbflush.h> 2262306a36Sopenharmony_ci#include <asm/cacheflush.h> 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_cistatic inline void __flush_itlb_all (void) 2662306a36Sopenharmony_ci{ 2762306a36Sopenharmony_ci int w, i; 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci for (w = 0; w < ITLB_ARF_WAYS; w++) { 3062306a36Sopenharmony_ci for (i = 0; i < (1 << XCHAL_ITLB_ARF_ENTRIES_LOG2); i++) { 3162306a36Sopenharmony_ci int e = w + (i << PAGE_SHIFT); 3262306a36Sopenharmony_ci invalidate_itlb_entry_no_isync(e); 3362306a36Sopenharmony_ci } 3462306a36Sopenharmony_ci } 3562306a36Sopenharmony_ci asm volatile ("isync\n"); 3662306a36Sopenharmony_ci} 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_cistatic inline void __flush_dtlb_all (void) 3962306a36Sopenharmony_ci{ 4062306a36Sopenharmony_ci int w, i; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci for (w = 0; w < DTLB_ARF_WAYS; w++) { 4362306a36Sopenharmony_ci for (i = 0; i < (1 << XCHAL_DTLB_ARF_ENTRIES_LOG2); i++) { 4462306a36Sopenharmony_ci int e = w + (i << PAGE_SHIFT); 4562306a36Sopenharmony_ci invalidate_dtlb_entry_no_isync(e); 4662306a36Sopenharmony_ci } 4762306a36Sopenharmony_ci } 4862306a36Sopenharmony_ci asm volatile ("isync\n"); 4962306a36Sopenharmony_ci} 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_civoid local_flush_tlb_all(void) 5362306a36Sopenharmony_ci{ 5462306a36Sopenharmony_ci __flush_itlb_all(); 5562306a36Sopenharmony_ci __flush_dtlb_all(); 5662306a36Sopenharmony_ci} 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci/* If mm is current, we simply assign the current task a new ASID, thus, 5962306a36Sopenharmony_ci * invalidating all previous tlb entries. If mm is someone else's user mapping, 6062306a36Sopenharmony_ci * wie invalidate the context, thus, when that user mapping is swapped in, 6162306a36Sopenharmony_ci * a new context will be assigned to it. 6262306a36Sopenharmony_ci */ 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_civoid local_flush_tlb_mm(struct mm_struct *mm) 6562306a36Sopenharmony_ci{ 6662306a36Sopenharmony_ci int cpu = smp_processor_id(); 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci if (mm == current->active_mm) { 6962306a36Sopenharmony_ci unsigned long flags; 7062306a36Sopenharmony_ci local_irq_save(flags); 7162306a36Sopenharmony_ci mm->context.asid[cpu] = NO_CONTEXT; 7262306a36Sopenharmony_ci activate_context(mm, cpu); 7362306a36Sopenharmony_ci local_irq_restore(flags); 7462306a36Sopenharmony_ci } else { 7562306a36Sopenharmony_ci mm->context.asid[cpu] = NO_CONTEXT; 7662306a36Sopenharmony_ci mm->context.cpu = -1; 7762306a36Sopenharmony_ci } 7862306a36Sopenharmony_ci} 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci#define _ITLB_ENTRIES (ITLB_ARF_WAYS << XCHAL_ITLB_ARF_ENTRIES_LOG2) 8262306a36Sopenharmony_ci#define _DTLB_ENTRIES (DTLB_ARF_WAYS << XCHAL_DTLB_ARF_ENTRIES_LOG2) 8362306a36Sopenharmony_ci#if _ITLB_ENTRIES > _DTLB_ENTRIES 8462306a36Sopenharmony_ci# define _TLB_ENTRIES _ITLB_ENTRIES 8562306a36Sopenharmony_ci#else 8662306a36Sopenharmony_ci# define _TLB_ENTRIES _DTLB_ENTRIES 8762306a36Sopenharmony_ci#endif 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_civoid local_flush_tlb_range(struct vm_area_struct *vma, 9062306a36Sopenharmony_ci unsigned long start, unsigned long end) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci int cpu = smp_processor_id(); 9362306a36Sopenharmony_ci struct mm_struct *mm = vma->vm_mm; 9462306a36Sopenharmony_ci unsigned long flags; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci if (mm->context.asid[cpu] == NO_CONTEXT) 9762306a36Sopenharmony_ci return; 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci pr_debug("[tlbrange<%02lx,%08lx,%08lx>]\n", 10062306a36Sopenharmony_ci (unsigned long)mm->context.asid[cpu], start, end); 10162306a36Sopenharmony_ci local_irq_save(flags); 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci if (end-start + (PAGE_SIZE-1) <= _TLB_ENTRIES << PAGE_SHIFT) { 10462306a36Sopenharmony_ci int oldpid = get_rasid_register(); 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci set_rasid_register(ASID_INSERT(mm->context.asid[cpu])); 10762306a36Sopenharmony_ci start &= PAGE_MASK; 10862306a36Sopenharmony_ci if (vma->vm_flags & VM_EXEC) 10962306a36Sopenharmony_ci while(start < end) { 11062306a36Sopenharmony_ci invalidate_itlb_mapping(start); 11162306a36Sopenharmony_ci invalidate_dtlb_mapping(start); 11262306a36Sopenharmony_ci start += PAGE_SIZE; 11362306a36Sopenharmony_ci } 11462306a36Sopenharmony_ci else 11562306a36Sopenharmony_ci while(start < end) { 11662306a36Sopenharmony_ci invalidate_dtlb_mapping(start); 11762306a36Sopenharmony_ci start += PAGE_SIZE; 11862306a36Sopenharmony_ci } 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci set_rasid_register(oldpid); 12162306a36Sopenharmony_ci } else { 12262306a36Sopenharmony_ci local_flush_tlb_mm(mm); 12362306a36Sopenharmony_ci } 12462306a36Sopenharmony_ci local_irq_restore(flags); 12562306a36Sopenharmony_ci} 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_civoid local_flush_tlb_page(struct vm_area_struct *vma, unsigned long page) 12862306a36Sopenharmony_ci{ 12962306a36Sopenharmony_ci int cpu = smp_processor_id(); 13062306a36Sopenharmony_ci struct mm_struct* mm = vma->vm_mm; 13162306a36Sopenharmony_ci unsigned long flags; 13262306a36Sopenharmony_ci int oldpid; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci if (mm->context.asid[cpu] == NO_CONTEXT) 13562306a36Sopenharmony_ci return; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci local_irq_save(flags); 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci oldpid = get_rasid_register(); 14062306a36Sopenharmony_ci set_rasid_register(ASID_INSERT(mm->context.asid[cpu])); 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci if (vma->vm_flags & VM_EXEC) 14362306a36Sopenharmony_ci invalidate_itlb_mapping(page); 14462306a36Sopenharmony_ci invalidate_dtlb_mapping(page); 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci set_rasid_register(oldpid); 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci local_irq_restore(flags); 14962306a36Sopenharmony_ci} 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_civoid local_flush_tlb_kernel_range(unsigned long start, unsigned long end) 15262306a36Sopenharmony_ci{ 15362306a36Sopenharmony_ci if (end > start && start >= TASK_SIZE && end <= PAGE_OFFSET && 15462306a36Sopenharmony_ci end - start < _TLB_ENTRIES << PAGE_SHIFT) { 15562306a36Sopenharmony_ci start &= PAGE_MASK; 15662306a36Sopenharmony_ci while (start < end) { 15762306a36Sopenharmony_ci invalidate_itlb_mapping(start); 15862306a36Sopenharmony_ci invalidate_dtlb_mapping(start); 15962306a36Sopenharmony_ci start += PAGE_SIZE; 16062306a36Sopenharmony_ci } 16162306a36Sopenharmony_ci } else { 16262306a36Sopenharmony_ci local_flush_tlb_all(); 16362306a36Sopenharmony_ci } 16462306a36Sopenharmony_ci} 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_civoid update_mmu_tlb(struct vm_area_struct *vma, 16762306a36Sopenharmony_ci unsigned long address, pte_t *ptep) 16862306a36Sopenharmony_ci{ 16962306a36Sopenharmony_ci local_flush_tlb_page(vma, address); 17062306a36Sopenharmony_ci} 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci#ifdef CONFIG_DEBUG_TLB_SANITY 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_cistatic unsigned get_pte_for_vaddr(unsigned vaddr) 17562306a36Sopenharmony_ci{ 17662306a36Sopenharmony_ci struct task_struct *task = get_current(); 17762306a36Sopenharmony_ci struct mm_struct *mm = task->mm; 17862306a36Sopenharmony_ci pgd_t *pgd; 17962306a36Sopenharmony_ci p4d_t *p4d; 18062306a36Sopenharmony_ci pud_t *pud; 18162306a36Sopenharmony_ci pmd_t *pmd; 18262306a36Sopenharmony_ci pte_t *pte; 18362306a36Sopenharmony_ci unsigned int pteval; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci if (!mm) 18662306a36Sopenharmony_ci mm = task->active_mm; 18762306a36Sopenharmony_ci pgd = pgd_offset(mm, vaddr); 18862306a36Sopenharmony_ci if (pgd_none_or_clear_bad(pgd)) 18962306a36Sopenharmony_ci return 0; 19062306a36Sopenharmony_ci p4d = p4d_offset(pgd, vaddr); 19162306a36Sopenharmony_ci if (p4d_none_or_clear_bad(p4d)) 19262306a36Sopenharmony_ci return 0; 19362306a36Sopenharmony_ci pud = pud_offset(p4d, vaddr); 19462306a36Sopenharmony_ci if (pud_none_or_clear_bad(pud)) 19562306a36Sopenharmony_ci return 0; 19662306a36Sopenharmony_ci pmd = pmd_offset(pud, vaddr); 19762306a36Sopenharmony_ci if (pmd_none_or_clear_bad(pmd)) 19862306a36Sopenharmony_ci return 0; 19962306a36Sopenharmony_ci pte = pte_offset_map(pmd, vaddr); 20062306a36Sopenharmony_ci if (!pte) 20162306a36Sopenharmony_ci return 0; 20262306a36Sopenharmony_ci pteval = pte_val(*pte); 20362306a36Sopenharmony_ci pte_unmap(pte); 20462306a36Sopenharmony_ci return pteval; 20562306a36Sopenharmony_ci} 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_cienum { 20862306a36Sopenharmony_ci TLB_SUSPICIOUS = 1, 20962306a36Sopenharmony_ci TLB_INSANE = 2, 21062306a36Sopenharmony_ci}; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_cistatic void tlb_insane(void) 21362306a36Sopenharmony_ci{ 21462306a36Sopenharmony_ci BUG_ON(1); 21562306a36Sopenharmony_ci} 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_cistatic void tlb_suspicious(void) 21862306a36Sopenharmony_ci{ 21962306a36Sopenharmony_ci WARN_ON(1); 22062306a36Sopenharmony_ci} 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci/* 22362306a36Sopenharmony_ci * Check that TLB entries with kernel ASID (1) have kernel VMA (>= TASK_SIZE), 22462306a36Sopenharmony_ci * and TLB entries with user ASID (>=4) have VMA < TASK_SIZE. 22562306a36Sopenharmony_ci * 22662306a36Sopenharmony_ci * Check that valid TLB entries either have the same PA as the PTE, or PTE is 22762306a36Sopenharmony_ci * marked as non-present. Non-present PTE and the page with non-zero refcount 22862306a36Sopenharmony_ci * and zero mapcount is normal for batched TLB flush operation. Zero refcount 22962306a36Sopenharmony_ci * means that the page was freed prematurely. Non-zero mapcount is unusual, 23062306a36Sopenharmony_ci * but does not necessary means an error, thus marked as suspicious. 23162306a36Sopenharmony_ci */ 23262306a36Sopenharmony_cistatic int check_tlb_entry(unsigned w, unsigned e, bool dtlb) 23362306a36Sopenharmony_ci{ 23462306a36Sopenharmony_ci unsigned tlbidx = w | (e << PAGE_SHIFT); 23562306a36Sopenharmony_ci unsigned r0 = dtlb ? 23662306a36Sopenharmony_ci read_dtlb_virtual(tlbidx) : read_itlb_virtual(tlbidx); 23762306a36Sopenharmony_ci unsigned r1 = dtlb ? 23862306a36Sopenharmony_ci read_dtlb_translation(tlbidx) : read_itlb_translation(tlbidx); 23962306a36Sopenharmony_ci unsigned vpn = (r0 & PAGE_MASK) | (e << PAGE_SHIFT); 24062306a36Sopenharmony_ci unsigned pte = get_pte_for_vaddr(vpn); 24162306a36Sopenharmony_ci unsigned mm_asid = (get_rasid_register() >> 8) & ASID_MASK; 24262306a36Sopenharmony_ci unsigned tlb_asid = r0 & ASID_MASK; 24362306a36Sopenharmony_ci bool kernel = tlb_asid == 1; 24462306a36Sopenharmony_ci int rc = 0; 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci if (tlb_asid > 0 && ((vpn < TASK_SIZE) == kernel)) { 24762306a36Sopenharmony_ci pr_err("%cTLB: way: %u, entry: %u, VPN %08x in %s PTE\n", 24862306a36Sopenharmony_ci dtlb ? 'D' : 'I', w, e, vpn, 24962306a36Sopenharmony_ci kernel ? "kernel" : "user"); 25062306a36Sopenharmony_ci rc |= TLB_INSANE; 25162306a36Sopenharmony_ci } 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci if (tlb_asid == mm_asid) { 25462306a36Sopenharmony_ci if ((pte ^ r1) & PAGE_MASK) { 25562306a36Sopenharmony_ci pr_err("%cTLB: way: %u, entry: %u, mapping: %08x->%08x, PTE: %08x\n", 25662306a36Sopenharmony_ci dtlb ? 'D' : 'I', w, e, r0, r1, pte); 25762306a36Sopenharmony_ci if (pte == 0 || !pte_present(__pte(pte))) { 25862306a36Sopenharmony_ci struct page *p = pfn_to_page(r1 >> PAGE_SHIFT); 25962306a36Sopenharmony_ci pr_err("page refcount: %d, mapcount: %d\n", 26062306a36Sopenharmony_ci page_count(p), 26162306a36Sopenharmony_ci page_mapcount(p)); 26262306a36Sopenharmony_ci if (!page_count(p)) 26362306a36Sopenharmony_ci rc |= TLB_INSANE; 26462306a36Sopenharmony_ci else if (page_mapcount(p)) 26562306a36Sopenharmony_ci rc |= TLB_SUSPICIOUS; 26662306a36Sopenharmony_ci } else { 26762306a36Sopenharmony_ci rc |= TLB_INSANE; 26862306a36Sopenharmony_ci } 26962306a36Sopenharmony_ci } 27062306a36Sopenharmony_ci } 27162306a36Sopenharmony_ci return rc; 27262306a36Sopenharmony_ci} 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_civoid check_tlb_sanity(void) 27562306a36Sopenharmony_ci{ 27662306a36Sopenharmony_ci unsigned long flags; 27762306a36Sopenharmony_ci unsigned w, e; 27862306a36Sopenharmony_ci int bug = 0; 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci local_irq_save(flags); 28162306a36Sopenharmony_ci for (w = 0; w < DTLB_ARF_WAYS; ++w) 28262306a36Sopenharmony_ci for (e = 0; e < (1 << XCHAL_DTLB_ARF_ENTRIES_LOG2); ++e) 28362306a36Sopenharmony_ci bug |= check_tlb_entry(w, e, true); 28462306a36Sopenharmony_ci for (w = 0; w < ITLB_ARF_WAYS; ++w) 28562306a36Sopenharmony_ci for (e = 0; e < (1 << XCHAL_ITLB_ARF_ENTRIES_LOG2); ++e) 28662306a36Sopenharmony_ci bug |= check_tlb_entry(w, e, false); 28762306a36Sopenharmony_ci if (bug & TLB_INSANE) 28862306a36Sopenharmony_ci tlb_insane(); 28962306a36Sopenharmony_ci if (bug & TLB_SUSPICIOUS) 29062306a36Sopenharmony_ci tlb_suspicious(); 29162306a36Sopenharmony_ci local_irq_restore(flags); 29262306a36Sopenharmony_ci} 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci#endif /* CONFIG_DEBUG_TLB_SANITY */ 295