1 /* 2 * Nios2 TLB handling 3 * 4 * Copyright (C) 2009, Wind River Systems Inc 5 * Implemented by fredrik.markstrom@gmail.com and ivarholmqvist@gmail.com 6 * 7 * This file is subject to the terms and conditions of the GNU General Public 8 * License. See the file "COPYING" in the main directory of this archive 9 * for more details. 10 */ 11 12 #include <linux/init.h> 13 #include <linux/sched.h> 14 #include <linux/mm.h> 15 #include <linux/pagemap.h> 16 17 #include <asm/tlb.h> 18 #include <asm/mmu_context.h> 19 #include <asm/pgtable.h> 20 #include <asm/cpuinfo.h> 21 22 #define TLB_INDEX_MASK \ 23 ((((1UL << (cpuinfo.tlb_ptr_sz - cpuinfo.tlb_num_ways_log2))) - 1) \ 24 << PAGE_SHIFT) 25 26 static void get_misc_and_pid(unsigned long *misc, unsigned long *pid) 27 { 28 *misc = RDCTL(CTL_TLBMISC); 29 *misc &= (TLBMISC_PID | TLBMISC_WAY); 30 *pid = *misc & TLBMISC_PID; 31 } 32 33 /* 34 * This provides a PTEADDR value for addr that will cause a TLB miss 35 * (fast TLB miss). TLB invalidation replaces entries with this value. 36 */ 37 static unsigned long pteaddr_invalid(unsigned long addr) 38 { 39 return ((addr | 0xC0000000UL) >> PAGE_SHIFT) << 2; 40 } 41 42 /* 43 * This one is only used for pages with the global bit set so we don't care 44 * much about the ASID. 45 */ 46 static void replace_tlb_one_pid(unsigned long addr, unsigned long mmu_pid, unsigned long tlbacc) 47 { 48 unsigned int way; 49 unsigned long org_misc, pid_misc; 50 51 /* remember pid/way until we return. */ 52 get_misc_and_pid(&org_misc, &pid_misc); 53 54 WRCTL(CTL_PTEADDR, (addr >> PAGE_SHIFT) << 2); 55 56 for (way = 0; way < cpuinfo.tlb_num_ways; way++) { 57 unsigned long pteaddr; 58 unsigned long tlbmisc; 59 unsigned long pid; 60 61 tlbmisc = TLBMISC_RD | (way << TLBMISC_WAY_SHIFT); 62 WRCTL(CTL_TLBMISC, tlbmisc); 63 64 pteaddr = RDCTL(CTL_PTEADDR); 65 if (((pteaddr >> 2) & 0xfffff) != (addr >> PAGE_SHIFT)) 66 continue; 67 68 tlbmisc = RDCTL(CTL_TLBMISC); 69 pid = (tlbmisc >> TLBMISC_PID_SHIFT) & TLBMISC_PID_MASK; 70 if (pid != mmu_pid) 71 continue; 72 73 tlbmisc = (mmu_pid << TLBMISC_PID_SHIFT) | TLBMISC_WE | 74 (way << TLBMISC_WAY_SHIFT); 75 WRCTL(CTL_TLBMISC, tlbmisc); 76 if (tlbacc == 0) 77 WRCTL(CTL_PTEADDR, pteaddr_invalid(addr)); 78 WRCTL(CTL_TLBACC, tlbacc); 79 /* 80 * There should be only a single entry that maps a 81 * particular {address,pid} so break after a match. 82 */ 83 break; 84 } 85 86 WRCTL(CTL_TLBMISC, org_misc); 87 } 88 89 static void flush_tlb_one_pid(unsigned long addr, unsigned long mmu_pid) 90 { 91 pr_debug("Flush tlb-entry for vaddr=%#lx\n", addr); 92 93 replace_tlb_one_pid(addr, mmu_pid, 0); 94 } 95 96 static void reload_tlb_one_pid(unsigned long addr, unsigned long mmu_pid, pte_t pte) 97 { 98 pr_debug("Reload tlb-entry for vaddr=%#lx\n", addr); 99 100 replace_tlb_one_pid(addr, mmu_pid, pte_val(pte)); 101 } 102 103 void flush_tlb_range(struct vm_area_struct *vma, unsigned long start, 104 unsigned long end) 105 { 106 unsigned long mmu_pid = get_pid_from_context(&vma->vm_mm->context); 107 108 while (start < end) { 109 flush_tlb_one_pid(start, mmu_pid); 110 start += PAGE_SIZE; 111 } 112 } 113 114 void reload_tlb_page(struct vm_area_struct *vma, unsigned long addr, pte_t pte) 115 { 116 unsigned long mmu_pid = get_pid_from_context(&vma->vm_mm->context); 117 118 reload_tlb_one_pid(addr, mmu_pid, pte); 119 } 120 121 /* 122 * This one is only used for pages with the global bit set so we don't care 123 * much about the ASID. 124 */ 125 static void flush_tlb_one(unsigned long addr) 126 { 127 unsigned int way; 128 unsigned long org_misc, pid_misc; 129 130 pr_debug("Flush tlb-entry for vaddr=%#lx\n", addr); 131 132 /* remember pid/way until we return. */ 133 get_misc_and_pid(&org_misc, &pid_misc); 134 135 WRCTL(CTL_PTEADDR, (addr >> PAGE_SHIFT) << 2); 136 137 for (way = 0; way < cpuinfo.tlb_num_ways; way++) { 138 unsigned long pteaddr; 139 unsigned long tlbmisc; 140 141 tlbmisc = TLBMISC_RD | (way << TLBMISC_WAY_SHIFT); 142 WRCTL(CTL_TLBMISC, tlbmisc); 143 144 pteaddr = RDCTL(CTL_PTEADDR); 145 if (((pteaddr >> 2) & 0xfffff) != (addr >> PAGE_SHIFT)) 146 continue; 147 148 pr_debug("Flush entry by writing way=%dl pid=%ld\n", 149 way, (pid_misc >> TLBMISC_PID_SHIFT)); 150 151 tlbmisc = TLBMISC_WE | (way << TLBMISC_WAY_SHIFT); 152 WRCTL(CTL_TLBMISC, tlbmisc); 153 WRCTL(CTL_PTEADDR, pteaddr_invalid(addr)); 154 WRCTL(CTL_TLBACC, 0); 155 } 156 157 WRCTL(CTL_TLBMISC, org_misc); 158 } 159 160 void flush_tlb_kernel_range(unsigned long start, unsigned long end) 161 { 162 while (start < end) { 163 flush_tlb_one(start); 164 start += PAGE_SIZE; 165 } 166 } 167 168 void dump_tlb_line(unsigned long line) 169 { 170 unsigned int way; 171 unsigned long org_misc; 172 173 pr_debug("dump tlb-entries for line=%#lx (addr %08lx)\n", line, 174 line << (PAGE_SHIFT + cpuinfo.tlb_num_ways_log2)); 175 176 /* remember pid/way until we return */ 177 org_misc = (RDCTL(CTL_TLBMISC) & (TLBMISC_PID | TLBMISC_WAY)); 178 179 WRCTL(CTL_PTEADDR, line << 2); 180 181 for (way = 0; way < cpuinfo.tlb_num_ways; way++) { 182 unsigned long pteaddr; 183 unsigned long tlbmisc; 184 unsigned long tlbacc; 185 186 WRCTL(CTL_TLBMISC, TLBMISC_RD | (way << TLBMISC_WAY_SHIFT)); 187 pteaddr = RDCTL(CTL_PTEADDR); 188 tlbmisc = RDCTL(CTL_TLBMISC); 189 tlbacc = RDCTL(CTL_TLBACC); 190 191 if ((tlbacc << PAGE_SHIFT) != 0) { 192 pr_debug("-- way:%02x vpn:0x%08lx phys:0x%08lx pid:0x%02lx flags:%c%c%c%c%c\n", 193 way, 194 (pteaddr << (PAGE_SHIFT-2)), 195 (tlbacc << PAGE_SHIFT), 196 ((tlbmisc >> TLBMISC_PID_SHIFT) & 197 TLBMISC_PID_MASK), 198 (tlbacc & _PAGE_READ ? 'r' : '-'), 199 (tlbacc & _PAGE_WRITE ? 'w' : '-'), 200 (tlbacc & _PAGE_EXEC ? 'x' : '-'), 201 (tlbacc & _PAGE_GLOBAL ? 'g' : '-'), 202 (tlbacc & _PAGE_CACHED ? 'c' : '-')); 203 } 204 } 205 206 WRCTL(CTL_TLBMISC, org_misc); 207 } 208 209 void dump_tlb(void) 210 { 211 unsigned int i; 212 213 for (i = 0; i < cpuinfo.tlb_num_lines; i++) 214 dump_tlb_line(i); 215 } 216 217 void flush_tlb_pid(unsigned long mmu_pid) 218 { 219 unsigned long addr = 0; 220 unsigned int line; 221 unsigned int way; 222 unsigned long org_misc, pid_misc; 223 224 /* remember pid/way until we return */ 225 get_misc_and_pid(&org_misc, &pid_misc); 226 227 for (line = 0; line < cpuinfo.tlb_num_lines; line++) { 228 WRCTL(CTL_PTEADDR, pteaddr_invalid(addr)); 229 230 for (way = 0; way < cpuinfo.tlb_num_ways; way++) { 231 unsigned long tlbmisc; 232 unsigned long pid; 233 234 tlbmisc = TLBMISC_RD | (way << TLBMISC_WAY_SHIFT); 235 WRCTL(CTL_TLBMISC, tlbmisc); 236 tlbmisc = RDCTL(CTL_TLBMISC); 237 pid = (tlbmisc >> TLBMISC_PID_SHIFT) & TLBMISC_PID_MASK; 238 if (pid != mmu_pid) 239 continue; 240 241 tlbmisc = TLBMISC_WE | (way << TLBMISC_WAY_SHIFT); 242 WRCTL(CTL_TLBMISC, tlbmisc); 243 WRCTL(CTL_TLBACC, 0); 244 } 245 246 addr += PAGE_SIZE; 247 } 248 249 WRCTL(CTL_TLBMISC, org_misc); 250 } 251 252 /* 253 * All entries common to a mm share an asid. To effectively flush these 254 * entries, we just bump the asid. 255 */ 256 void flush_tlb_mm(struct mm_struct *mm) 257 { 258 if (current->mm == mm) { 259 unsigned long mmu_pid = get_pid_from_context(&mm->context); 260 flush_tlb_pid(mmu_pid); 261 } else { 262 memset(&mm->context, 0, sizeof(mm_context_t)); 263 } 264 } 265 266 void flush_tlb_all(void) 267 { 268 unsigned long addr = 0; 269 unsigned int line; 270 unsigned int way; 271 unsigned long org_misc, pid_misc; 272 273 /* remember pid/way until we return */ 274 get_misc_and_pid(&org_misc, &pid_misc); 275 276 /* Start at way 0, way is auto-incremented after each TLBACC write */ 277 WRCTL(CTL_TLBMISC, TLBMISC_WE); 278 279 /* Map each TLB entry to physcal address 0 with no-access and a 280 bad ptbase */ 281 for (line = 0; line < cpuinfo.tlb_num_lines; line++) { 282 WRCTL(CTL_PTEADDR, pteaddr_invalid(addr)); 283 for (way = 0; way < cpuinfo.tlb_num_ways; way++) 284 WRCTL(CTL_TLBACC, 0); 285 286 addr += PAGE_SIZE; 287 } 288 289 /* restore pid/way */ 290 WRCTL(CTL_TLBMISC, org_misc); 291 } 292 293 void set_mmu_pid(unsigned long pid) 294 { 295 unsigned long tlbmisc; 296 297 tlbmisc = RDCTL(CTL_TLBMISC); 298 tlbmisc = (tlbmisc & TLBMISC_WAY); 299 tlbmisc |= (pid & TLBMISC_PID_MASK) << TLBMISC_PID_SHIFT; 300 WRCTL(CTL_TLBMISC, tlbmisc); 301 } 302