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/cpuinfo.h> 20 21 #define TLB_INDEX_MASK \ 22 ((((1UL << (cpuinfo.tlb_ptr_sz - cpuinfo.tlb_num_ways_log2))) - 1) \ 23 << PAGE_SHIFT) 24 25 static void get_misc_and_pid(unsigned long *misc, unsigned long *pid) 26 { 27 *misc = RDCTL(CTL_TLBMISC); 28 *misc &= (TLBMISC_PID | TLBMISC_WAY); 29 *pid = *misc & TLBMISC_PID; 30 } 31 32 /* 33 * This provides a PTEADDR value for addr that will cause a TLB miss 34 * (fast TLB miss). TLB invalidation replaces entries with this value. 35 */ 36 static unsigned long pteaddr_invalid(unsigned long addr) 37 { 38 return ((addr | 0xC0000000UL) >> PAGE_SHIFT) << 2; 39 } 40 41 /* 42 * This one is only used for pages with the global bit set so we don't care 43 * much about the ASID. 44 */ 45 static void replace_tlb_one_pid(unsigned long addr, unsigned long mmu_pid, unsigned long tlbacc) 46 { 47 unsigned int way; 48 unsigned long org_misc, pid_misc; 49 50 /* remember pid/way until we return. */ 51 get_misc_and_pid(&org_misc, &pid_misc); 52 53 WRCTL(CTL_PTEADDR, (addr >> PAGE_SHIFT) << 2); 54 55 for (way = 0; way < cpuinfo.tlb_num_ways; way++) { 56 unsigned long pteaddr; 57 unsigned long tlbmisc; 58 unsigned long pid; 59 60 tlbmisc = TLBMISC_RD | (way << TLBMISC_WAY_SHIFT); 61 WRCTL(CTL_TLBMISC, tlbmisc); 62 63 pteaddr = RDCTL(CTL_PTEADDR); 64 if (((pteaddr >> 2) & 0xfffff) != (addr >> PAGE_SHIFT)) 65 continue; 66 67 tlbmisc = RDCTL(CTL_TLBMISC); 68 pid = (tlbmisc >> TLBMISC_PID_SHIFT) & TLBMISC_PID_MASK; 69 if (pid != mmu_pid) 70 continue; 71 72 tlbmisc = (mmu_pid << TLBMISC_PID_SHIFT) | TLBMISC_WE | 73 (way << TLBMISC_WAY_SHIFT); 74 WRCTL(CTL_TLBMISC, tlbmisc); 75 if (tlbacc == 0) 76 WRCTL(CTL_PTEADDR, pteaddr_invalid(addr)); 77 WRCTL(CTL_TLBACC, tlbacc); 78 /* 79 * There should be only a single entry that maps a 80 * particular {address,pid} so break after a match. 81 */ 82 break; 83 } 84 85 WRCTL(CTL_TLBMISC, org_misc); 86 } 87 88 static void flush_tlb_one_pid(unsigned long addr, unsigned long mmu_pid) 89 { 90 pr_debug("Flush tlb-entry for vaddr=%#lx\n", addr); 91 92 replace_tlb_one_pid(addr, mmu_pid, 0); 93 } 94 95 static void reload_tlb_one_pid(unsigned long addr, unsigned long mmu_pid, pte_t pte) 96 { 97 pr_debug("Reload tlb-entry for vaddr=%#lx\n", addr); 98 99 replace_tlb_one_pid(addr, mmu_pid, pte_val(pte)); 100 } 101 102 void flush_tlb_range(struct vm_area_struct *vma, unsigned long start, 103 unsigned long end) 104 { 105 unsigned long mmu_pid = get_pid_from_context(&vma->vm_mm->context); 106 107 while (start < end) { 108 flush_tlb_one_pid(start, mmu_pid); 109 start += PAGE_SIZE; 110 } 111 } 112 113 void reload_tlb_page(struct vm_area_struct *vma, unsigned long addr, pte_t pte) 114 { 115 unsigned long mmu_pid = get_pid_from_context(&vma->vm_mm->context); 116 117 reload_tlb_one_pid(addr, mmu_pid, pte); 118 } 119 120 /* 121 * This one is only used for pages with the global bit set so we don't care 122 * much about the ASID. 123 */ 124 static void flush_tlb_one(unsigned long addr) 125 { 126 unsigned int way; 127 unsigned long org_misc, pid_misc; 128 129 pr_debug("Flush tlb-entry for vaddr=%#lx\n", addr); 130 131 /* remember pid/way until we return. */ 132 get_misc_and_pid(&org_misc, &pid_misc); 133 134 WRCTL(CTL_PTEADDR, (addr >> PAGE_SHIFT) << 2); 135 136 for (way = 0; way < cpuinfo.tlb_num_ways; way++) { 137 unsigned long pteaddr; 138 unsigned long tlbmisc; 139 140 tlbmisc = TLBMISC_RD | (way << TLBMISC_WAY_SHIFT); 141 WRCTL(CTL_TLBMISC, tlbmisc); 142 143 pteaddr = RDCTL(CTL_PTEADDR); 144 if (((pteaddr >> 2) & 0xfffff) != (addr >> PAGE_SHIFT)) 145 continue; 146 147 tlbmisc = RDCTL(CTL_TLBMISC); 148 pr_debug("Flush entry by writing way=%dl pid=%ld\n", 149 way, ((tlbmisc >> TLBMISC_PID_SHIFT) & TLBMISC_PID_MASK)); 150 151 tlbmisc = TLBMISC_WE | (way << TLBMISC_WAY_SHIFT) | (tlbmisc & TLBMISC_PID); 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 (pid << TLBMISC_PID_SHIFT); 243 WRCTL(CTL_TLBMISC, tlbmisc); 244 WRCTL(CTL_TLBACC, 0); 245 } 246 247 addr += PAGE_SIZE; 248 } 249 250 WRCTL(CTL_TLBMISC, org_misc); 251 } 252 253 /* 254 * All entries common to a mm share an asid. To effectively flush these 255 * entries, we just bump the asid. 256 */ 257 void flush_tlb_mm(struct mm_struct *mm) 258 { 259 if (current->mm == mm) { 260 unsigned long mmu_pid = get_pid_from_context(&mm->context); 261 flush_tlb_pid(mmu_pid); 262 } else { 263 memset(&mm->context, 0, sizeof(mm_context_t)); 264 } 265 } 266 267 void flush_tlb_all(void) 268 { 269 unsigned long addr = 0; 270 unsigned int line; 271 unsigned int way; 272 unsigned long org_misc, pid_misc; 273 274 /* remember pid/way until we return */ 275 get_misc_and_pid(&org_misc, &pid_misc); 276 277 /* Map each TLB entry to physcal address 0 with no-access and a 278 bad ptbase */ 279 for (line = 0; line < cpuinfo.tlb_num_lines; line++) { 280 WRCTL(CTL_PTEADDR, pteaddr_invalid(addr)); 281 for (way = 0; way < cpuinfo.tlb_num_ways; way++) { 282 // Code such as replace_tlb_one_pid assumes that no duplicate entries exist 283 // for a single address across ways, so also use way as a dummy PID 284 WRCTL(CTL_TLBMISC, TLBMISC_WE | (way << TLBMISC_WAY_SHIFT) | 285 (way << TLBMISC_PID_SHIFT)); 286 WRCTL(CTL_TLBACC, 0); 287 } 288 289 addr += PAGE_SIZE; 290 } 291 292 /* restore pid/way */ 293 WRCTL(CTL_TLBMISC, org_misc); 294 } 295 296 void set_mmu_pid(unsigned long pid) 297 { 298 unsigned long tlbmisc; 299 300 tlbmisc = RDCTL(CTL_TLBMISC); 301 tlbmisc = (tlbmisc & TLBMISC_WAY); 302 tlbmisc |= (pid & TLBMISC_PID_MASK) << TLBMISC_PID_SHIFT; 303 WRCTL(CTL_TLBMISC, tlbmisc); 304 } 305