18c2ecf20Sopenharmony_ci/* 28c2ecf20Sopenharmony_ci * arch/xtensa/mm/tlb.c 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * Logic that manipulates the Xtensa MMU. Derived from MIPS. 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public 78c2ecf20Sopenharmony_ci * License. See the file "COPYING" in the main directory of this archive 88c2ecf20Sopenharmony_ci * for more details. 98c2ecf20Sopenharmony_ci * 108c2ecf20Sopenharmony_ci * Copyright (C) 2001 - 2003 Tensilica Inc. 118c2ecf20Sopenharmony_ci * 128c2ecf20Sopenharmony_ci * Joe Taylor 138c2ecf20Sopenharmony_ci * Chris Zankel <chris@zankel.net> 148c2ecf20Sopenharmony_ci * Marc Gauthier 158c2ecf20Sopenharmony_ci */ 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci#include <linux/mm.h> 188c2ecf20Sopenharmony_ci#include <asm/processor.h> 198c2ecf20Sopenharmony_ci#include <asm/mmu_context.h> 208c2ecf20Sopenharmony_ci#include <asm/tlbflush.h> 218c2ecf20Sopenharmony_ci#include <asm/cacheflush.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_cistatic inline void __flush_itlb_all (void) 258c2ecf20Sopenharmony_ci{ 268c2ecf20Sopenharmony_ci int w, i; 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci for (w = 0; w < ITLB_ARF_WAYS; w++) { 298c2ecf20Sopenharmony_ci for (i = 0; i < (1 << XCHAL_ITLB_ARF_ENTRIES_LOG2); i++) { 308c2ecf20Sopenharmony_ci int e = w + (i << PAGE_SHIFT); 318c2ecf20Sopenharmony_ci invalidate_itlb_entry_no_isync(e); 328c2ecf20Sopenharmony_ci } 338c2ecf20Sopenharmony_ci } 348c2ecf20Sopenharmony_ci asm volatile ("isync\n"); 358c2ecf20Sopenharmony_ci} 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_cistatic inline void __flush_dtlb_all (void) 388c2ecf20Sopenharmony_ci{ 398c2ecf20Sopenharmony_ci int w, i; 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci for (w = 0; w < DTLB_ARF_WAYS; w++) { 428c2ecf20Sopenharmony_ci for (i = 0; i < (1 << XCHAL_DTLB_ARF_ENTRIES_LOG2); i++) { 438c2ecf20Sopenharmony_ci int e = w + (i << PAGE_SHIFT); 448c2ecf20Sopenharmony_ci invalidate_dtlb_entry_no_isync(e); 458c2ecf20Sopenharmony_ci } 468c2ecf20Sopenharmony_ci } 478c2ecf20Sopenharmony_ci asm volatile ("isync\n"); 488c2ecf20Sopenharmony_ci} 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_civoid local_flush_tlb_all(void) 528c2ecf20Sopenharmony_ci{ 538c2ecf20Sopenharmony_ci __flush_itlb_all(); 548c2ecf20Sopenharmony_ci __flush_dtlb_all(); 558c2ecf20Sopenharmony_ci} 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci/* If mm is current, we simply assign the current task a new ASID, thus, 588c2ecf20Sopenharmony_ci * invalidating all previous tlb entries. If mm is someone else's user mapping, 598c2ecf20Sopenharmony_ci * wie invalidate the context, thus, when that user mapping is swapped in, 608c2ecf20Sopenharmony_ci * a new context will be assigned to it. 618c2ecf20Sopenharmony_ci */ 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_civoid local_flush_tlb_mm(struct mm_struct *mm) 648c2ecf20Sopenharmony_ci{ 658c2ecf20Sopenharmony_ci int cpu = smp_processor_id(); 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci if (mm == current->active_mm) { 688c2ecf20Sopenharmony_ci unsigned long flags; 698c2ecf20Sopenharmony_ci local_irq_save(flags); 708c2ecf20Sopenharmony_ci mm->context.asid[cpu] = NO_CONTEXT; 718c2ecf20Sopenharmony_ci activate_context(mm, cpu); 728c2ecf20Sopenharmony_ci local_irq_restore(flags); 738c2ecf20Sopenharmony_ci } else { 748c2ecf20Sopenharmony_ci mm->context.asid[cpu] = NO_CONTEXT; 758c2ecf20Sopenharmony_ci mm->context.cpu = -1; 768c2ecf20Sopenharmony_ci } 778c2ecf20Sopenharmony_ci} 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci#define _ITLB_ENTRIES (ITLB_ARF_WAYS << XCHAL_ITLB_ARF_ENTRIES_LOG2) 818c2ecf20Sopenharmony_ci#define _DTLB_ENTRIES (DTLB_ARF_WAYS << XCHAL_DTLB_ARF_ENTRIES_LOG2) 828c2ecf20Sopenharmony_ci#if _ITLB_ENTRIES > _DTLB_ENTRIES 838c2ecf20Sopenharmony_ci# define _TLB_ENTRIES _ITLB_ENTRIES 848c2ecf20Sopenharmony_ci#else 858c2ecf20Sopenharmony_ci# define _TLB_ENTRIES _DTLB_ENTRIES 868c2ecf20Sopenharmony_ci#endif 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_civoid local_flush_tlb_range(struct vm_area_struct *vma, 898c2ecf20Sopenharmony_ci unsigned long start, unsigned long end) 908c2ecf20Sopenharmony_ci{ 918c2ecf20Sopenharmony_ci int cpu = smp_processor_id(); 928c2ecf20Sopenharmony_ci struct mm_struct *mm = vma->vm_mm; 938c2ecf20Sopenharmony_ci unsigned long flags; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci if (mm->context.asid[cpu] == NO_CONTEXT) 968c2ecf20Sopenharmony_ci return; 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci pr_debug("[tlbrange<%02lx,%08lx,%08lx>]\n", 998c2ecf20Sopenharmony_ci (unsigned long)mm->context.asid[cpu], start, end); 1008c2ecf20Sopenharmony_ci local_irq_save(flags); 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci if (end-start + (PAGE_SIZE-1) <= _TLB_ENTRIES << PAGE_SHIFT) { 1038c2ecf20Sopenharmony_ci int oldpid = get_rasid_register(); 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci set_rasid_register(ASID_INSERT(mm->context.asid[cpu])); 1068c2ecf20Sopenharmony_ci start &= PAGE_MASK; 1078c2ecf20Sopenharmony_ci if (vma->vm_flags & VM_EXEC) 1088c2ecf20Sopenharmony_ci while(start < end) { 1098c2ecf20Sopenharmony_ci invalidate_itlb_mapping(start); 1108c2ecf20Sopenharmony_ci invalidate_dtlb_mapping(start); 1118c2ecf20Sopenharmony_ci start += PAGE_SIZE; 1128c2ecf20Sopenharmony_ci } 1138c2ecf20Sopenharmony_ci else 1148c2ecf20Sopenharmony_ci while(start < end) { 1158c2ecf20Sopenharmony_ci invalidate_dtlb_mapping(start); 1168c2ecf20Sopenharmony_ci start += PAGE_SIZE; 1178c2ecf20Sopenharmony_ci } 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci set_rasid_register(oldpid); 1208c2ecf20Sopenharmony_ci } else { 1218c2ecf20Sopenharmony_ci local_flush_tlb_mm(mm); 1228c2ecf20Sopenharmony_ci } 1238c2ecf20Sopenharmony_ci local_irq_restore(flags); 1248c2ecf20Sopenharmony_ci} 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_civoid local_flush_tlb_page(struct vm_area_struct *vma, unsigned long page) 1278c2ecf20Sopenharmony_ci{ 1288c2ecf20Sopenharmony_ci int cpu = smp_processor_id(); 1298c2ecf20Sopenharmony_ci struct mm_struct* mm = vma->vm_mm; 1308c2ecf20Sopenharmony_ci unsigned long flags; 1318c2ecf20Sopenharmony_ci int oldpid; 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci if (mm->context.asid[cpu] == NO_CONTEXT) 1348c2ecf20Sopenharmony_ci return; 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci local_irq_save(flags); 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci oldpid = get_rasid_register(); 1398c2ecf20Sopenharmony_ci set_rasid_register(ASID_INSERT(mm->context.asid[cpu])); 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci if (vma->vm_flags & VM_EXEC) 1428c2ecf20Sopenharmony_ci invalidate_itlb_mapping(page); 1438c2ecf20Sopenharmony_ci invalidate_dtlb_mapping(page); 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci set_rasid_register(oldpid); 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci local_irq_restore(flags); 1488c2ecf20Sopenharmony_ci} 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_civoid local_flush_tlb_kernel_range(unsigned long start, unsigned long end) 1518c2ecf20Sopenharmony_ci{ 1528c2ecf20Sopenharmony_ci if (end > start && start >= TASK_SIZE && end <= PAGE_OFFSET && 1538c2ecf20Sopenharmony_ci end - start < _TLB_ENTRIES << PAGE_SHIFT) { 1548c2ecf20Sopenharmony_ci start &= PAGE_MASK; 1558c2ecf20Sopenharmony_ci while (start < end) { 1568c2ecf20Sopenharmony_ci invalidate_itlb_mapping(start); 1578c2ecf20Sopenharmony_ci invalidate_dtlb_mapping(start); 1588c2ecf20Sopenharmony_ci start += PAGE_SIZE; 1598c2ecf20Sopenharmony_ci } 1608c2ecf20Sopenharmony_ci } else { 1618c2ecf20Sopenharmony_ci local_flush_tlb_all(); 1628c2ecf20Sopenharmony_ci } 1638c2ecf20Sopenharmony_ci} 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci#ifdef CONFIG_DEBUG_TLB_SANITY 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_cistatic unsigned get_pte_for_vaddr(unsigned vaddr) 1688c2ecf20Sopenharmony_ci{ 1698c2ecf20Sopenharmony_ci struct task_struct *task = get_current(); 1708c2ecf20Sopenharmony_ci struct mm_struct *mm = task->mm; 1718c2ecf20Sopenharmony_ci pgd_t *pgd; 1728c2ecf20Sopenharmony_ci p4d_t *p4d; 1738c2ecf20Sopenharmony_ci pud_t *pud; 1748c2ecf20Sopenharmony_ci pmd_t *pmd; 1758c2ecf20Sopenharmony_ci pte_t *pte; 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci if (!mm) 1788c2ecf20Sopenharmony_ci mm = task->active_mm; 1798c2ecf20Sopenharmony_ci pgd = pgd_offset(mm, vaddr); 1808c2ecf20Sopenharmony_ci if (pgd_none_or_clear_bad(pgd)) 1818c2ecf20Sopenharmony_ci return 0; 1828c2ecf20Sopenharmony_ci p4d = p4d_offset(pgd, vaddr); 1838c2ecf20Sopenharmony_ci if (p4d_none_or_clear_bad(p4d)) 1848c2ecf20Sopenharmony_ci return 0; 1858c2ecf20Sopenharmony_ci pud = pud_offset(p4d, vaddr); 1868c2ecf20Sopenharmony_ci if (pud_none_or_clear_bad(pud)) 1878c2ecf20Sopenharmony_ci return 0; 1888c2ecf20Sopenharmony_ci pmd = pmd_offset(pud, vaddr); 1898c2ecf20Sopenharmony_ci if (pmd_none_or_clear_bad(pmd)) 1908c2ecf20Sopenharmony_ci return 0; 1918c2ecf20Sopenharmony_ci pte = pte_offset_map(pmd, vaddr); 1928c2ecf20Sopenharmony_ci if (!pte) 1938c2ecf20Sopenharmony_ci return 0; 1948c2ecf20Sopenharmony_ci return pte_val(*pte); 1958c2ecf20Sopenharmony_ci} 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_cienum { 1988c2ecf20Sopenharmony_ci TLB_SUSPICIOUS = 1, 1998c2ecf20Sopenharmony_ci TLB_INSANE = 2, 2008c2ecf20Sopenharmony_ci}; 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_cistatic void tlb_insane(void) 2038c2ecf20Sopenharmony_ci{ 2048c2ecf20Sopenharmony_ci BUG_ON(1); 2058c2ecf20Sopenharmony_ci} 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_cistatic void tlb_suspicious(void) 2088c2ecf20Sopenharmony_ci{ 2098c2ecf20Sopenharmony_ci WARN_ON(1); 2108c2ecf20Sopenharmony_ci} 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci/* 2138c2ecf20Sopenharmony_ci * Check that TLB entries with kernel ASID (1) have kernel VMA (>= TASK_SIZE), 2148c2ecf20Sopenharmony_ci * and TLB entries with user ASID (>=4) have VMA < TASK_SIZE. 2158c2ecf20Sopenharmony_ci * 2168c2ecf20Sopenharmony_ci * Check that valid TLB entries either have the same PA as the PTE, or PTE is 2178c2ecf20Sopenharmony_ci * marked as non-present. Non-present PTE and the page with non-zero refcount 2188c2ecf20Sopenharmony_ci * and zero mapcount is normal for batched TLB flush operation. Zero refcount 2198c2ecf20Sopenharmony_ci * means that the page was freed prematurely. Non-zero mapcount is unusual, 2208c2ecf20Sopenharmony_ci * but does not necessary means an error, thus marked as suspicious. 2218c2ecf20Sopenharmony_ci */ 2228c2ecf20Sopenharmony_cistatic int check_tlb_entry(unsigned w, unsigned e, bool dtlb) 2238c2ecf20Sopenharmony_ci{ 2248c2ecf20Sopenharmony_ci unsigned tlbidx = w | (e << PAGE_SHIFT); 2258c2ecf20Sopenharmony_ci unsigned r0 = dtlb ? 2268c2ecf20Sopenharmony_ci read_dtlb_virtual(tlbidx) : read_itlb_virtual(tlbidx); 2278c2ecf20Sopenharmony_ci unsigned r1 = dtlb ? 2288c2ecf20Sopenharmony_ci read_dtlb_translation(tlbidx) : read_itlb_translation(tlbidx); 2298c2ecf20Sopenharmony_ci unsigned vpn = (r0 & PAGE_MASK) | (e << PAGE_SHIFT); 2308c2ecf20Sopenharmony_ci unsigned pte = get_pte_for_vaddr(vpn); 2318c2ecf20Sopenharmony_ci unsigned mm_asid = (get_rasid_register() >> 8) & ASID_MASK; 2328c2ecf20Sopenharmony_ci unsigned tlb_asid = r0 & ASID_MASK; 2338c2ecf20Sopenharmony_ci bool kernel = tlb_asid == 1; 2348c2ecf20Sopenharmony_ci int rc = 0; 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci if (tlb_asid > 0 && ((vpn < TASK_SIZE) == kernel)) { 2378c2ecf20Sopenharmony_ci pr_err("%cTLB: way: %u, entry: %u, VPN %08x in %s PTE\n", 2388c2ecf20Sopenharmony_ci dtlb ? 'D' : 'I', w, e, vpn, 2398c2ecf20Sopenharmony_ci kernel ? "kernel" : "user"); 2408c2ecf20Sopenharmony_ci rc |= TLB_INSANE; 2418c2ecf20Sopenharmony_ci } 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci if (tlb_asid == mm_asid) { 2448c2ecf20Sopenharmony_ci if ((pte ^ r1) & PAGE_MASK) { 2458c2ecf20Sopenharmony_ci pr_err("%cTLB: way: %u, entry: %u, mapping: %08x->%08x, PTE: %08x\n", 2468c2ecf20Sopenharmony_ci dtlb ? 'D' : 'I', w, e, r0, r1, pte); 2478c2ecf20Sopenharmony_ci if (pte == 0 || !pte_present(__pte(pte))) { 2488c2ecf20Sopenharmony_ci struct page *p = pfn_to_page(r1 >> PAGE_SHIFT); 2498c2ecf20Sopenharmony_ci pr_err("page refcount: %d, mapcount: %d\n", 2508c2ecf20Sopenharmony_ci page_count(p), 2518c2ecf20Sopenharmony_ci page_mapcount(p)); 2528c2ecf20Sopenharmony_ci if (!page_count(p)) 2538c2ecf20Sopenharmony_ci rc |= TLB_INSANE; 2548c2ecf20Sopenharmony_ci else if (page_mapcount(p)) 2558c2ecf20Sopenharmony_ci rc |= TLB_SUSPICIOUS; 2568c2ecf20Sopenharmony_ci } else { 2578c2ecf20Sopenharmony_ci rc |= TLB_INSANE; 2588c2ecf20Sopenharmony_ci } 2598c2ecf20Sopenharmony_ci } 2608c2ecf20Sopenharmony_ci } 2618c2ecf20Sopenharmony_ci return rc; 2628c2ecf20Sopenharmony_ci} 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_civoid check_tlb_sanity(void) 2658c2ecf20Sopenharmony_ci{ 2668c2ecf20Sopenharmony_ci unsigned long flags; 2678c2ecf20Sopenharmony_ci unsigned w, e; 2688c2ecf20Sopenharmony_ci int bug = 0; 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci local_irq_save(flags); 2718c2ecf20Sopenharmony_ci for (w = 0; w < DTLB_ARF_WAYS; ++w) 2728c2ecf20Sopenharmony_ci for (e = 0; e < (1 << XCHAL_DTLB_ARF_ENTRIES_LOG2); ++e) 2738c2ecf20Sopenharmony_ci bug |= check_tlb_entry(w, e, true); 2748c2ecf20Sopenharmony_ci for (w = 0; w < ITLB_ARF_WAYS; ++w) 2758c2ecf20Sopenharmony_ci for (e = 0; e < (1 << XCHAL_ITLB_ARF_ENTRIES_LOG2); ++e) 2768c2ecf20Sopenharmony_ci bug |= check_tlb_entry(w, e, false); 2778c2ecf20Sopenharmony_ci if (bug & TLB_INSANE) 2788c2ecf20Sopenharmony_ci tlb_insane(); 2798c2ecf20Sopenharmony_ci if (bug & TLB_SUSPICIOUS) 2808c2ecf20Sopenharmony_ci tlb_suspicious(); 2818c2ecf20Sopenharmony_ci local_irq_restore(flags); 2828c2ecf20Sopenharmony_ci} 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_ci#endif /* CONFIG_DEBUG_TLB_SANITY */ 285