tlb.c revision 5dfe4c964a0dd7bb3a1d64a4166835a153146207
1/* 2 * AVR32 TLB operations 3 * 4 * Copyright (C) 2004-2006 Atmel Corporation 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 2 as 8 * published by the Free Software Foundation. 9 */ 10#include <linux/mm.h> 11 12#include <asm/mmu_context.h> 13 14#define _TLBEHI_I 0x100 15 16void show_dtlb_entry(unsigned int index) 17{ 18 unsigned int tlbehi, tlbehi_save, tlbelo, mmucr, mmucr_save; 19 unsigned long flags; 20 21 local_irq_save(flags); 22 mmucr_save = sysreg_read(MMUCR); 23 tlbehi_save = sysreg_read(TLBEHI); 24 mmucr = mmucr_save & 0x13; 25 mmucr |= index << 14; 26 sysreg_write(MMUCR, mmucr); 27 28 asm volatile("tlbr" : : : "memory"); 29 cpu_sync_pipeline(); 30 31 tlbehi = sysreg_read(TLBEHI); 32 tlbelo = sysreg_read(TLBELO); 33 34 printk("%2u: %c %c %02x %05x %05x %o %o %c %c %c %c\n", 35 index, 36 (tlbehi & 0x200)?'1':'0', 37 (tlbelo & 0x100)?'1':'0', 38 (tlbehi & 0xff), 39 (tlbehi >> 12), (tlbelo >> 12), 40 (tlbelo >> 4) & 7, (tlbelo >> 2) & 3, 41 (tlbelo & 0x200)?'1':'0', 42 (tlbelo & 0x080)?'1':'0', 43 (tlbelo & 0x001)?'1':'0', 44 (tlbelo & 0x002)?'1':'0'); 45 46 sysreg_write(MMUCR, mmucr_save); 47 sysreg_write(TLBEHI, tlbehi_save); 48 cpu_sync_pipeline(); 49 local_irq_restore(flags); 50} 51 52void dump_dtlb(void) 53{ 54 unsigned int i; 55 56 printk("ID V G ASID VPN PFN AP SZ C B W D\n"); 57 for (i = 0; i < 32; i++) 58 show_dtlb_entry(i); 59} 60 61static unsigned long last_mmucr; 62 63static inline void set_replacement_pointer(unsigned shift) 64{ 65 unsigned long mmucr, mmucr_save; 66 67 mmucr = mmucr_save = sysreg_read(MMUCR); 68 69 /* Does this mapping already exist? */ 70 __asm__ __volatile__( 71 " tlbs\n" 72 " mfsr %0, %1" 73 : "=r"(mmucr) 74 : "i"(SYSREG_MMUCR)); 75 76 if (mmucr & SYSREG_BIT(MMUCR_N)) { 77 /* Not found -- pick a not-recently-accessed entry */ 78 unsigned long rp; 79 unsigned long tlbar = sysreg_read(TLBARLO); 80 81 rp = 32 - fls(tlbar); 82 if (rp == 32) { 83 rp = 0; 84 sysreg_write(TLBARLO, -1L); 85 } 86 87 mmucr &= 0x13; 88 mmucr |= (rp << shift); 89 90 sysreg_write(MMUCR, mmucr); 91 } 92 93 last_mmucr = mmucr; 94} 95 96static void update_dtlb(unsigned long address, pte_t pte, unsigned long asid) 97{ 98 unsigned long vpn; 99 100 vpn = (address & MMU_VPN_MASK) | _TLBEHI_VALID | asid; 101 sysreg_write(TLBEHI, vpn); 102 cpu_sync_pipeline(); 103 104 set_replacement_pointer(14); 105 106 sysreg_write(TLBELO, pte_val(pte) & _PAGE_FLAGS_HARDWARE_MASK); 107 108 /* Let's go */ 109 asm volatile("nop\n\ttlbw" : : : "memory"); 110 cpu_sync_pipeline(); 111} 112 113void update_mmu_cache(struct vm_area_struct *vma, 114 unsigned long address, pte_t pte) 115{ 116 unsigned long flags; 117 118 /* ptrace may call this routine */ 119 if (vma && current->active_mm != vma->vm_mm) 120 return; 121 122 local_irq_save(flags); 123 update_dtlb(address, pte, get_asid()); 124 local_irq_restore(flags); 125} 126 127void __flush_tlb_page(unsigned long asid, unsigned long page) 128{ 129 unsigned long mmucr, tlbehi; 130 131 page |= asid; 132 sysreg_write(TLBEHI, page); 133 cpu_sync_pipeline(); 134 asm volatile("tlbs"); 135 mmucr = sysreg_read(MMUCR); 136 137 if (!(mmucr & SYSREG_BIT(MMUCR_N))) { 138 unsigned long tlbarlo; 139 unsigned long entry; 140 141 /* Clear the "valid" bit */ 142 tlbehi = sysreg_read(TLBEHI); 143 tlbehi &= ~_TLBEHI_VALID; 144 sysreg_write(TLBEHI, tlbehi); 145 cpu_sync_pipeline(); 146 147 /* mark the entry as "not accessed" */ 148 entry = (mmucr >> 14) & 0x3f; 149 tlbarlo = sysreg_read(TLBARLO); 150 tlbarlo |= (0x80000000 >> entry); 151 sysreg_write(TLBARLO, tlbarlo); 152 153 /* update the entry with valid bit clear */ 154 asm volatile("tlbw"); 155 cpu_sync_pipeline(); 156 } 157} 158 159void flush_tlb_page(struct vm_area_struct *vma, unsigned long page) 160{ 161 if (vma->vm_mm && vma->vm_mm->context != NO_CONTEXT) { 162 unsigned long flags, asid; 163 unsigned long saved_asid = MMU_NO_ASID; 164 165 asid = vma->vm_mm->context & MMU_CONTEXT_ASID_MASK; 166 page &= PAGE_MASK; 167 168 local_irq_save(flags); 169 if (vma->vm_mm != current->mm) { 170 saved_asid = get_asid(); 171 set_asid(asid); 172 } 173 174 __flush_tlb_page(asid, page); 175 176 if (saved_asid != MMU_NO_ASID) 177 set_asid(saved_asid); 178 local_irq_restore(flags); 179 } 180} 181 182void flush_tlb_range(struct vm_area_struct *vma, unsigned long start, 183 unsigned long end) 184{ 185 struct mm_struct *mm = vma->vm_mm; 186 187 if (mm->context != NO_CONTEXT) { 188 unsigned long flags; 189 int size; 190 191 local_irq_save(flags); 192 size = (end - start + (PAGE_SIZE - 1)) >> PAGE_SHIFT; 193 if (size > (MMU_DTLB_ENTRIES / 4)) { /* Too many entries to flush */ 194 mm->context = NO_CONTEXT; 195 if (mm == current->mm) 196 activate_context(mm); 197 } else { 198 unsigned long asid = mm->context & MMU_CONTEXT_ASID_MASK; 199 unsigned long saved_asid = MMU_NO_ASID; 200 201 start &= PAGE_MASK; 202 end += (PAGE_SIZE - 1); 203 end &= PAGE_MASK; 204 if (mm != current->mm) { 205 saved_asid = get_asid(); 206 set_asid(asid); 207 } 208 209 while (start < end) { 210 __flush_tlb_page(asid, start); 211 start += PAGE_SIZE; 212 } 213 if (saved_asid != MMU_NO_ASID) 214 set_asid(saved_asid); 215 } 216 local_irq_restore(flags); 217 } 218} 219 220/* 221 * TODO: If this is only called for addresses > TASK_SIZE, we can probably 222 * skip the ASID stuff and just use the Global bit... 223 */ 224void flush_tlb_kernel_range(unsigned long start, unsigned long end) 225{ 226 unsigned long flags; 227 int size; 228 229 local_irq_save(flags); 230 size = (end - start + (PAGE_SIZE - 1)) >> PAGE_SHIFT; 231 if (size > (MMU_DTLB_ENTRIES / 4)) { /* Too many entries to flush */ 232 flush_tlb_all(); 233 } else { 234 unsigned long asid = init_mm.context & MMU_CONTEXT_ASID_MASK; 235 unsigned long saved_asid = get_asid(); 236 237 start &= PAGE_MASK; 238 end += (PAGE_SIZE - 1); 239 end &= PAGE_MASK; 240 set_asid(asid); 241 while (start < end) { 242 __flush_tlb_page(asid, start); 243 start += PAGE_SIZE; 244 } 245 set_asid(saved_asid); 246 } 247 local_irq_restore(flags); 248} 249 250void flush_tlb_mm(struct mm_struct *mm) 251{ 252 /* Invalidate all TLB entries of this process by getting a new ASID */ 253 if (mm->context != NO_CONTEXT) { 254 unsigned long flags; 255 256 local_irq_save(flags); 257 mm->context = NO_CONTEXT; 258 if (mm == current->mm) 259 activate_context(mm); 260 local_irq_restore(flags); 261 } 262} 263 264void flush_tlb_all(void) 265{ 266 unsigned long flags; 267 268 local_irq_save(flags); 269 sysreg_write(MMUCR, sysreg_read(MMUCR) | SYSREG_BIT(MMUCR_I)); 270 local_irq_restore(flags); 271} 272 273#ifdef CONFIG_PROC_FS 274 275#include <linux/seq_file.h> 276#include <linux/proc_fs.h> 277#include <linux/init.h> 278 279static void *tlb_start(struct seq_file *tlb, loff_t *pos) 280{ 281 static unsigned long tlb_index; 282 283 if (*pos >= 32) 284 return NULL; 285 286 tlb_index = 0; 287 return &tlb_index; 288} 289 290static void *tlb_next(struct seq_file *tlb, void *v, loff_t *pos) 291{ 292 unsigned long *index = v; 293 294 if (*index >= 31) 295 return NULL; 296 297 ++*pos; 298 ++*index; 299 return index; 300} 301 302static void tlb_stop(struct seq_file *tlb, void *v) 303{ 304 305} 306 307static int tlb_show(struct seq_file *tlb, void *v) 308{ 309 unsigned int tlbehi, tlbehi_save, tlbelo, mmucr, mmucr_save; 310 unsigned long flags; 311 unsigned long *index = v; 312 313 if (*index == 0) 314 seq_puts(tlb, "ID V G ASID VPN PFN AP SZ C B W D\n"); 315 316 BUG_ON(*index >= 32); 317 318 local_irq_save(flags); 319 mmucr_save = sysreg_read(MMUCR); 320 tlbehi_save = sysreg_read(TLBEHI); 321 mmucr = mmucr_save & 0x13; 322 mmucr |= *index << 14; 323 sysreg_write(MMUCR, mmucr); 324 325 asm volatile("tlbr" : : : "memory"); 326 cpu_sync_pipeline(); 327 328 tlbehi = sysreg_read(TLBEHI); 329 tlbelo = sysreg_read(TLBELO); 330 331 sysreg_write(MMUCR, mmucr_save); 332 sysreg_write(TLBEHI, tlbehi_save); 333 cpu_sync_pipeline(); 334 local_irq_restore(flags); 335 336 seq_printf(tlb, "%2lu: %c %c %02x %05x %05x %o %o %c %c %c %c\n", 337 *index, 338 (tlbehi & 0x200)?'1':'0', 339 (tlbelo & 0x100)?'1':'0', 340 (tlbehi & 0xff), 341 (tlbehi >> 12), (tlbelo >> 12), 342 (tlbelo >> 4) & 7, (tlbelo >> 2) & 3, 343 (tlbelo & 0x200)?'1':'0', 344 (tlbelo & 0x080)?'1':'0', 345 (tlbelo & 0x001)?'1':'0', 346 (tlbelo & 0x002)?'1':'0'); 347 348 return 0; 349} 350 351static struct seq_operations tlb_ops = { 352 .start = tlb_start, 353 .next = tlb_next, 354 .stop = tlb_stop, 355 .show = tlb_show, 356}; 357 358static int tlb_open(struct inode *inode, struct file *file) 359{ 360 return seq_open(file, &tlb_ops); 361} 362 363static const struct file_operations proc_tlb_operations = { 364 .open = tlb_open, 365 .read = seq_read, 366 .llseek = seq_lseek, 367 .release = seq_release, 368}; 369 370static int __init proctlb_init(void) 371{ 372 struct proc_dir_entry *entry; 373 374 entry = create_proc_entry("tlb", 0, NULL); 375 if (entry) 376 entry->proc_fops = &proc_tlb_operations; 377 return 0; 378} 379late_initcall(proctlb_init); 380#endif /* CONFIG_PROC_FS */ 381