18c2ecf20Sopenharmony_ci/* 28c2ecf20Sopenharmony_ci * Nios2 TLB handling 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * Copyright (C) 2009, Wind River Systems Inc 58c2ecf20Sopenharmony_ci * Implemented by fredrik.markstrom@gmail.com and ivarholmqvist@gmail.com 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public 88c2ecf20Sopenharmony_ci * License. See the file "COPYING" in the main directory of this archive 98c2ecf20Sopenharmony_ci * for more details. 108c2ecf20Sopenharmony_ci */ 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include <linux/init.h> 138c2ecf20Sopenharmony_ci#include <linux/sched.h> 148c2ecf20Sopenharmony_ci#include <linux/mm.h> 158c2ecf20Sopenharmony_ci#include <linux/pagemap.h> 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci#include <asm/tlb.h> 188c2ecf20Sopenharmony_ci#include <asm/mmu_context.h> 198c2ecf20Sopenharmony_ci#include <asm/cpuinfo.h> 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#define TLB_INDEX_MASK \ 228c2ecf20Sopenharmony_ci ((((1UL << (cpuinfo.tlb_ptr_sz - cpuinfo.tlb_num_ways_log2))) - 1) \ 238c2ecf20Sopenharmony_ci << PAGE_SHIFT) 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_cistatic void get_misc_and_pid(unsigned long *misc, unsigned long *pid) 268c2ecf20Sopenharmony_ci{ 278c2ecf20Sopenharmony_ci *misc = RDCTL(CTL_TLBMISC); 288c2ecf20Sopenharmony_ci *misc &= (TLBMISC_PID | TLBMISC_WAY); 298c2ecf20Sopenharmony_ci *pid = *misc & TLBMISC_PID; 308c2ecf20Sopenharmony_ci} 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci/* 338c2ecf20Sopenharmony_ci * This provides a PTEADDR value for addr that will cause a TLB miss 348c2ecf20Sopenharmony_ci * (fast TLB miss). TLB invalidation replaces entries with this value. 358c2ecf20Sopenharmony_ci */ 368c2ecf20Sopenharmony_cistatic unsigned long pteaddr_invalid(unsigned long addr) 378c2ecf20Sopenharmony_ci{ 388c2ecf20Sopenharmony_ci return ((addr | 0xC0000000UL) >> PAGE_SHIFT) << 2; 398c2ecf20Sopenharmony_ci} 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci/* 428c2ecf20Sopenharmony_ci * This one is only used for pages with the global bit set so we don't care 438c2ecf20Sopenharmony_ci * much about the ASID. 448c2ecf20Sopenharmony_ci */ 458c2ecf20Sopenharmony_cistatic void replace_tlb_one_pid(unsigned long addr, unsigned long mmu_pid, unsigned long tlbacc) 468c2ecf20Sopenharmony_ci{ 478c2ecf20Sopenharmony_ci unsigned int way; 488c2ecf20Sopenharmony_ci unsigned long org_misc, pid_misc; 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci /* remember pid/way until we return. */ 518c2ecf20Sopenharmony_ci get_misc_and_pid(&org_misc, &pid_misc); 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci WRCTL(CTL_PTEADDR, (addr >> PAGE_SHIFT) << 2); 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci for (way = 0; way < cpuinfo.tlb_num_ways; way++) { 568c2ecf20Sopenharmony_ci unsigned long pteaddr; 578c2ecf20Sopenharmony_ci unsigned long tlbmisc; 588c2ecf20Sopenharmony_ci unsigned long pid; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci tlbmisc = TLBMISC_RD | (way << TLBMISC_WAY_SHIFT); 618c2ecf20Sopenharmony_ci WRCTL(CTL_TLBMISC, tlbmisc); 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci pteaddr = RDCTL(CTL_PTEADDR); 648c2ecf20Sopenharmony_ci if (((pteaddr >> 2) & 0xfffff) != (addr >> PAGE_SHIFT)) 658c2ecf20Sopenharmony_ci continue; 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci tlbmisc = RDCTL(CTL_TLBMISC); 688c2ecf20Sopenharmony_ci pid = (tlbmisc >> TLBMISC_PID_SHIFT) & TLBMISC_PID_MASK; 698c2ecf20Sopenharmony_ci if (pid != mmu_pid) 708c2ecf20Sopenharmony_ci continue; 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci tlbmisc = (mmu_pid << TLBMISC_PID_SHIFT) | TLBMISC_WE | 738c2ecf20Sopenharmony_ci (way << TLBMISC_WAY_SHIFT); 748c2ecf20Sopenharmony_ci WRCTL(CTL_TLBMISC, tlbmisc); 758c2ecf20Sopenharmony_ci if (tlbacc == 0) 768c2ecf20Sopenharmony_ci WRCTL(CTL_PTEADDR, pteaddr_invalid(addr)); 778c2ecf20Sopenharmony_ci WRCTL(CTL_TLBACC, tlbacc); 788c2ecf20Sopenharmony_ci /* 798c2ecf20Sopenharmony_ci * There should be only a single entry that maps a 808c2ecf20Sopenharmony_ci * particular {address,pid} so break after a match. 818c2ecf20Sopenharmony_ci */ 828c2ecf20Sopenharmony_ci break; 838c2ecf20Sopenharmony_ci } 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci WRCTL(CTL_TLBMISC, org_misc); 868c2ecf20Sopenharmony_ci} 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_cistatic void flush_tlb_one_pid(unsigned long addr, unsigned long mmu_pid) 898c2ecf20Sopenharmony_ci{ 908c2ecf20Sopenharmony_ci pr_debug("Flush tlb-entry for vaddr=%#lx\n", addr); 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci replace_tlb_one_pid(addr, mmu_pid, 0); 938c2ecf20Sopenharmony_ci} 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_cistatic void reload_tlb_one_pid(unsigned long addr, unsigned long mmu_pid, pte_t pte) 968c2ecf20Sopenharmony_ci{ 978c2ecf20Sopenharmony_ci pr_debug("Reload tlb-entry for vaddr=%#lx\n", addr); 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci replace_tlb_one_pid(addr, mmu_pid, pte_val(pte)); 1008c2ecf20Sopenharmony_ci} 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_civoid flush_tlb_range(struct vm_area_struct *vma, unsigned long start, 1038c2ecf20Sopenharmony_ci unsigned long end) 1048c2ecf20Sopenharmony_ci{ 1058c2ecf20Sopenharmony_ci unsigned long mmu_pid = get_pid_from_context(&vma->vm_mm->context); 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci while (start < end) { 1088c2ecf20Sopenharmony_ci flush_tlb_one_pid(start, mmu_pid); 1098c2ecf20Sopenharmony_ci start += PAGE_SIZE; 1108c2ecf20Sopenharmony_ci } 1118c2ecf20Sopenharmony_ci} 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_civoid reload_tlb_page(struct vm_area_struct *vma, unsigned long addr, pte_t pte) 1148c2ecf20Sopenharmony_ci{ 1158c2ecf20Sopenharmony_ci unsigned long mmu_pid = get_pid_from_context(&vma->vm_mm->context); 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci reload_tlb_one_pid(addr, mmu_pid, pte); 1188c2ecf20Sopenharmony_ci} 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci/* 1218c2ecf20Sopenharmony_ci * This one is only used for pages with the global bit set so we don't care 1228c2ecf20Sopenharmony_ci * much about the ASID. 1238c2ecf20Sopenharmony_ci */ 1248c2ecf20Sopenharmony_cistatic void flush_tlb_one(unsigned long addr) 1258c2ecf20Sopenharmony_ci{ 1268c2ecf20Sopenharmony_ci unsigned int way; 1278c2ecf20Sopenharmony_ci unsigned long org_misc, pid_misc; 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci pr_debug("Flush tlb-entry for vaddr=%#lx\n", addr); 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci /* remember pid/way until we return. */ 1328c2ecf20Sopenharmony_ci get_misc_and_pid(&org_misc, &pid_misc); 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci WRCTL(CTL_PTEADDR, (addr >> PAGE_SHIFT) << 2); 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci for (way = 0; way < cpuinfo.tlb_num_ways; way++) { 1378c2ecf20Sopenharmony_ci unsigned long pteaddr; 1388c2ecf20Sopenharmony_ci unsigned long tlbmisc; 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci tlbmisc = TLBMISC_RD | (way << TLBMISC_WAY_SHIFT); 1418c2ecf20Sopenharmony_ci WRCTL(CTL_TLBMISC, tlbmisc); 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci pteaddr = RDCTL(CTL_PTEADDR); 1448c2ecf20Sopenharmony_ci if (((pteaddr >> 2) & 0xfffff) != (addr >> PAGE_SHIFT)) 1458c2ecf20Sopenharmony_ci continue; 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci pr_debug("Flush entry by writing way=%dl pid=%ld\n", 1488c2ecf20Sopenharmony_ci way, (pid_misc >> TLBMISC_PID_SHIFT)); 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci tlbmisc = TLBMISC_WE | (way << TLBMISC_WAY_SHIFT); 1518c2ecf20Sopenharmony_ci WRCTL(CTL_TLBMISC, tlbmisc); 1528c2ecf20Sopenharmony_ci WRCTL(CTL_PTEADDR, pteaddr_invalid(addr)); 1538c2ecf20Sopenharmony_ci WRCTL(CTL_TLBACC, 0); 1548c2ecf20Sopenharmony_ci } 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci WRCTL(CTL_TLBMISC, org_misc); 1578c2ecf20Sopenharmony_ci} 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_civoid flush_tlb_kernel_range(unsigned long start, unsigned long end) 1608c2ecf20Sopenharmony_ci{ 1618c2ecf20Sopenharmony_ci while (start < end) { 1628c2ecf20Sopenharmony_ci flush_tlb_one(start); 1638c2ecf20Sopenharmony_ci start += PAGE_SIZE; 1648c2ecf20Sopenharmony_ci } 1658c2ecf20Sopenharmony_ci} 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_civoid dump_tlb_line(unsigned long line) 1688c2ecf20Sopenharmony_ci{ 1698c2ecf20Sopenharmony_ci unsigned int way; 1708c2ecf20Sopenharmony_ci unsigned long org_misc; 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci pr_debug("dump tlb-entries for line=%#lx (addr %08lx)\n", line, 1738c2ecf20Sopenharmony_ci line << (PAGE_SHIFT + cpuinfo.tlb_num_ways_log2)); 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci /* remember pid/way until we return */ 1768c2ecf20Sopenharmony_ci org_misc = (RDCTL(CTL_TLBMISC) & (TLBMISC_PID | TLBMISC_WAY)); 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci WRCTL(CTL_PTEADDR, line << 2); 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci for (way = 0; way < cpuinfo.tlb_num_ways; way++) { 1818c2ecf20Sopenharmony_ci unsigned long pteaddr; 1828c2ecf20Sopenharmony_ci unsigned long tlbmisc; 1838c2ecf20Sopenharmony_ci unsigned long tlbacc; 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci WRCTL(CTL_TLBMISC, TLBMISC_RD | (way << TLBMISC_WAY_SHIFT)); 1868c2ecf20Sopenharmony_ci pteaddr = RDCTL(CTL_PTEADDR); 1878c2ecf20Sopenharmony_ci tlbmisc = RDCTL(CTL_TLBMISC); 1888c2ecf20Sopenharmony_ci tlbacc = RDCTL(CTL_TLBACC); 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci if ((tlbacc << PAGE_SHIFT) != 0) { 1918c2ecf20Sopenharmony_ci pr_debug("-- way:%02x vpn:0x%08lx phys:0x%08lx pid:0x%02lx flags:%c%c%c%c%c\n", 1928c2ecf20Sopenharmony_ci way, 1938c2ecf20Sopenharmony_ci (pteaddr << (PAGE_SHIFT-2)), 1948c2ecf20Sopenharmony_ci (tlbacc << PAGE_SHIFT), 1958c2ecf20Sopenharmony_ci ((tlbmisc >> TLBMISC_PID_SHIFT) & 1968c2ecf20Sopenharmony_ci TLBMISC_PID_MASK), 1978c2ecf20Sopenharmony_ci (tlbacc & _PAGE_READ ? 'r' : '-'), 1988c2ecf20Sopenharmony_ci (tlbacc & _PAGE_WRITE ? 'w' : '-'), 1998c2ecf20Sopenharmony_ci (tlbacc & _PAGE_EXEC ? 'x' : '-'), 2008c2ecf20Sopenharmony_ci (tlbacc & _PAGE_GLOBAL ? 'g' : '-'), 2018c2ecf20Sopenharmony_ci (tlbacc & _PAGE_CACHED ? 'c' : '-')); 2028c2ecf20Sopenharmony_ci } 2038c2ecf20Sopenharmony_ci } 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci WRCTL(CTL_TLBMISC, org_misc); 2068c2ecf20Sopenharmony_ci} 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_civoid dump_tlb(void) 2098c2ecf20Sopenharmony_ci{ 2108c2ecf20Sopenharmony_ci unsigned int i; 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci for (i = 0; i < cpuinfo.tlb_num_lines; i++) 2138c2ecf20Sopenharmony_ci dump_tlb_line(i); 2148c2ecf20Sopenharmony_ci} 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_civoid flush_tlb_pid(unsigned long mmu_pid) 2178c2ecf20Sopenharmony_ci{ 2188c2ecf20Sopenharmony_ci unsigned long addr = 0; 2198c2ecf20Sopenharmony_ci unsigned int line; 2208c2ecf20Sopenharmony_ci unsigned int way; 2218c2ecf20Sopenharmony_ci unsigned long org_misc, pid_misc; 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci /* remember pid/way until we return */ 2248c2ecf20Sopenharmony_ci get_misc_and_pid(&org_misc, &pid_misc); 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci for (line = 0; line < cpuinfo.tlb_num_lines; line++) { 2278c2ecf20Sopenharmony_ci WRCTL(CTL_PTEADDR, pteaddr_invalid(addr)); 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci for (way = 0; way < cpuinfo.tlb_num_ways; way++) { 2308c2ecf20Sopenharmony_ci unsigned long tlbmisc; 2318c2ecf20Sopenharmony_ci unsigned long pid; 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ci tlbmisc = TLBMISC_RD | (way << TLBMISC_WAY_SHIFT); 2348c2ecf20Sopenharmony_ci WRCTL(CTL_TLBMISC, tlbmisc); 2358c2ecf20Sopenharmony_ci tlbmisc = RDCTL(CTL_TLBMISC); 2368c2ecf20Sopenharmony_ci pid = (tlbmisc >> TLBMISC_PID_SHIFT) & TLBMISC_PID_MASK; 2378c2ecf20Sopenharmony_ci if (pid != mmu_pid) 2388c2ecf20Sopenharmony_ci continue; 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_ci tlbmisc = TLBMISC_WE | (way << TLBMISC_WAY_SHIFT); 2418c2ecf20Sopenharmony_ci WRCTL(CTL_TLBMISC, tlbmisc); 2428c2ecf20Sopenharmony_ci WRCTL(CTL_TLBACC, 0); 2438c2ecf20Sopenharmony_ci } 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci addr += PAGE_SIZE; 2468c2ecf20Sopenharmony_ci } 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_ci WRCTL(CTL_TLBMISC, org_misc); 2498c2ecf20Sopenharmony_ci} 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci/* 2528c2ecf20Sopenharmony_ci * All entries common to a mm share an asid. To effectively flush these 2538c2ecf20Sopenharmony_ci * entries, we just bump the asid. 2548c2ecf20Sopenharmony_ci */ 2558c2ecf20Sopenharmony_civoid flush_tlb_mm(struct mm_struct *mm) 2568c2ecf20Sopenharmony_ci{ 2578c2ecf20Sopenharmony_ci if (current->mm == mm) { 2588c2ecf20Sopenharmony_ci unsigned long mmu_pid = get_pid_from_context(&mm->context); 2598c2ecf20Sopenharmony_ci flush_tlb_pid(mmu_pid); 2608c2ecf20Sopenharmony_ci } else { 2618c2ecf20Sopenharmony_ci memset(&mm->context, 0, sizeof(mm_context_t)); 2628c2ecf20Sopenharmony_ci } 2638c2ecf20Sopenharmony_ci} 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_civoid flush_tlb_all(void) 2668c2ecf20Sopenharmony_ci{ 2678c2ecf20Sopenharmony_ci unsigned long addr = 0; 2688c2ecf20Sopenharmony_ci unsigned int line; 2698c2ecf20Sopenharmony_ci unsigned int way; 2708c2ecf20Sopenharmony_ci unsigned long org_misc, pid_misc; 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_ci /* remember pid/way until we return */ 2738c2ecf20Sopenharmony_ci get_misc_and_pid(&org_misc, &pid_misc); 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci /* Start at way 0, way is auto-incremented after each TLBACC write */ 2768c2ecf20Sopenharmony_ci WRCTL(CTL_TLBMISC, TLBMISC_WE); 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci /* Map each TLB entry to physcal address 0 with no-access and a 2798c2ecf20Sopenharmony_ci bad ptbase */ 2808c2ecf20Sopenharmony_ci for (line = 0; line < cpuinfo.tlb_num_lines; line++) { 2818c2ecf20Sopenharmony_ci WRCTL(CTL_PTEADDR, pteaddr_invalid(addr)); 2828c2ecf20Sopenharmony_ci for (way = 0; way < cpuinfo.tlb_num_ways; way++) 2838c2ecf20Sopenharmony_ci WRCTL(CTL_TLBACC, 0); 2848c2ecf20Sopenharmony_ci 2858c2ecf20Sopenharmony_ci addr += PAGE_SIZE; 2868c2ecf20Sopenharmony_ci } 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ci /* restore pid/way */ 2898c2ecf20Sopenharmony_ci WRCTL(CTL_TLBMISC, org_misc); 2908c2ecf20Sopenharmony_ci} 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_civoid set_mmu_pid(unsigned long pid) 2938c2ecf20Sopenharmony_ci{ 2948c2ecf20Sopenharmony_ci unsigned long tlbmisc; 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_ci tlbmisc = RDCTL(CTL_TLBMISC); 2978c2ecf20Sopenharmony_ci tlbmisc = (tlbmisc & TLBMISC_WAY); 2988c2ecf20Sopenharmony_ci tlbmisc |= (pid & TLBMISC_PID_MASK) << TLBMISC_PID_SHIFT; 2998c2ecf20Sopenharmony_ci WRCTL(CTL_TLBMISC, tlbmisc); 3008c2ecf20Sopenharmony_ci} 301