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/* TODO: Get the correct number from the CONFIG1 system register */ 15#define NR_TLB_ENTRIES 32 16 17static void show_dtlb_entry(unsigned int index) 18{ 19 u32 tlbehi, tlbehi_save, tlbelo, mmucr, mmucr_save; 20 unsigned long flags; 21 22 local_irq_save(flags); 23 mmucr_save = sysreg_read(MMUCR); 24 tlbehi_save = sysreg_read(TLBEHI); 25 mmucr = SYSREG_BFINS(DRP, index, mmucr_save); 26 sysreg_write(MMUCR, mmucr); 27 28 __builtin_tlbr(); 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 SYSREG_BFEXT(TLBEHI_V, tlbehi) ? '1' : '0', 37 SYSREG_BFEXT(G, tlbelo) ? '1' : '0', 38 SYSREG_BFEXT(ASID, tlbehi), 39 SYSREG_BFEXT(VPN, tlbehi) >> 2, 40 SYSREG_BFEXT(PFN, tlbelo) >> 2, 41 SYSREG_BFEXT(AP, tlbelo), 42 SYSREG_BFEXT(SZ, tlbelo), 43 SYSREG_BFEXT(TLBELO_C, tlbelo) ? 'C' : ' ', 44 SYSREG_BFEXT(B, tlbelo) ? 'B' : ' ', 45 SYSREG_BFEXT(W, tlbelo) ? 'W' : ' ', 46 SYSREG_BFEXT(TLBELO_D, tlbelo) ? 'D' : ' '); 47 48 sysreg_write(MMUCR, mmucr_save); 49 sysreg_write(TLBEHI, tlbehi_save); 50 cpu_sync_pipeline(); 51 local_irq_restore(flags); 52} 53 54void dump_dtlb(void) 55{ 56 unsigned int i; 57 58 printk("ID V G ASID VPN PFN AP SZ C B W D\n"); 59 for (i = 0; i < NR_TLB_ENTRIES; i++) 60 show_dtlb_entry(i); 61} 62 63static void update_dtlb(unsigned long address, pte_t pte) 64{ 65 u32 tlbehi; 66 u32 mmucr; 67 68 /* 69 * We're not changing the ASID here, so no need to flush the 70 * pipeline. 71 */ 72 tlbehi = sysreg_read(TLBEHI); 73 tlbehi = SYSREG_BF(ASID, SYSREG_BFEXT(ASID, tlbehi)); 74 tlbehi |= address & MMU_VPN_MASK; 75 tlbehi |= SYSREG_BIT(TLBEHI_V); 76 sysreg_write(TLBEHI, tlbehi); 77 78 /* Does this mapping already exist? */ 79 __builtin_tlbs(); 80 mmucr = sysreg_read(MMUCR); 81 82 if (mmucr & SYSREG_BIT(MMUCR_N)) { 83 /* Not found -- pick a not-recently-accessed entry */ 84 unsigned int rp; 85 u32 tlbar = sysreg_read(TLBARLO); 86 87 rp = 32 - fls(tlbar); 88 if (rp == 32) { 89 rp = 0; 90 sysreg_write(TLBARLO, -1L); 91 } 92 93 mmucr = SYSREG_BFINS(DRP, rp, mmucr); 94 sysreg_write(MMUCR, mmucr); 95 } 96 97 sysreg_write(TLBELO, pte_val(pte) & _PAGE_FLAGS_HARDWARE_MASK); 98 99 /* Let's go */ 100 __builtin_tlbw(); 101} 102 103void update_mmu_cache(struct vm_area_struct *vma, 104 unsigned long address, pte_t *ptep) 105{ 106 unsigned long flags; 107 108 /* ptrace may call this routine */ 109 if (vma && current->active_mm != vma->vm_mm) 110 return; 111 112 local_irq_save(flags); 113 update_dtlb(address, *ptep); 114 local_irq_restore(flags); 115} 116 117static void __flush_tlb_page(unsigned long asid, unsigned long page) 118{ 119 u32 mmucr, tlbehi; 120 121 /* 122 * Caller is responsible for masking out non-PFN bits in page 123 * and changing the current ASID if necessary. This means that 124 * we don't need to flush the pipeline after writing TLBEHI. 125 */ 126 tlbehi = page | asid; 127 sysreg_write(TLBEHI, tlbehi); 128 129 __builtin_tlbs(); 130 mmucr = sysreg_read(MMUCR); 131 132 if (!(mmucr & SYSREG_BIT(MMUCR_N))) { 133 unsigned int entry; 134 u32 tlbarlo; 135 136 /* Clear the "valid" bit */ 137 sysreg_write(TLBEHI, tlbehi); 138 139 /* mark the entry as "not accessed" */ 140 entry = SYSREG_BFEXT(DRP, mmucr); 141 tlbarlo = sysreg_read(TLBARLO); 142 tlbarlo |= (0x80000000UL >> entry); 143 sysreg_write(TLBARLO, tlbarlo); 144 145 /* update the entry with valid bit clear */ 146 __builtin_tlbw(); 147 } 148} 149 150void flush_tlb_page(struct vm_area_struct *vma, unsigned long page) 151{ 152 if (vma->vm_mm && vma->vm_mm->context != NO_CONTEXT) { 153 unsigned long flags, asid; 154 unsigned long saved_asid = MMU_NO_ASID; 155 156 asid = vma->vm_mm->context & MMU_CONTEXT_ASID_MASK; 157 page &= PAGE_MASK; 158 159 local_irq_save(flags); 160 if (vma->vm_mm != current->mm) { 161 saved_asid = get_asid(); 162 set_asid(asid); 163 } 164 165 __flush_tlb_page(asid, page); 166 167 if (saved_asid != MMU_NO_ASID) 168 set_asid(saved_asid); 169 local_irq_restore(flags); 170 } 171} 172 173void flush_tlb_range(struct vm_area_struct *vma, unsigned long start, 174 unsigned long end) 175{ 176 struct mm_struct *mm = vma->vm_mm; 177 178 if (mm->context != NO_CONTEXT) { 179 unsigned long flags; 180 int size; 181 182 local_irq_save(flags); 183 size = (end - start + (PAGE_SIZE - 1)) >> PAGE_SHIFT; 184 185 if (size > (MMU_DTLB_ENTRIES / 4)) { /* Too many entries to flush */ 186 mm->context = NO_CONTEXT; 187 if (mm == current->mm) 188 activate_context(mm); 189 } else { 190 unsigned long asid; 191 unsigned long saved_asid; 192 193 asid = mm->context & MMU_CONTEXT_ASID_MASK; 194 saved_asid = MMU_NO_ASID; 195 196 start &= PAGE_MASK; 197 end += (PAGE_SIZE - 1); 198 end &= PAGE_MASK; 199 200 if (mm != current->mm) { 201 saved_asid = get_asid(); 202 set_asid(asid); 203 } 204 205 while (start < end) { 206 __flush_tlb_page(asid, start); 207 start += PAGE_SIZE; 208 } 209 if (saved_asid != MMU_NO_ASID) 210 set_asid(saved_asid); 211 } 212 local_irq_restore(flags); 213 } 214} 215 216/* 217 * This function depends on the pages to be flushed having the G 218 * (global) bit set in their pte. This is true for all 219 * PAGE_KERNEL(_RO) pages. 220 */ 221void flush_tlb_kernel_range(unsigned long start, unsigned long end) 222{ 223 unsigned long flags; 224 int size; 225 226 size = (end - start + (PAGE_SIZE - 1)) >> PAGE_SHIFT; 227 if (size > (MMU_DTLB_ENTRIES / 4)) { /* Too many entries to flush */ 228 flush_tlb_all(); 229 } else { 230 unsigned long asid; 231 232 local_irq_save(flags); 233 asid = get_asid(); 234 235 start &= PAGE_MASK; 236 end += (PAGE_SIZE - 1); 237 end &= PAGE_MASK; 238 239 while (start < end) { 240 __flush_tlb_page(asid, start); 241 start += PAGE_SIZE; 242 } 243 local_irq_restore(flags); 244 } 245} 246 247void flush_tlb_mm(struct mm_struct *mm) 248{ 249 /* Invalidate all TLB entries of this process by getting a new ASID */ 250 if (mm->context != NO_CONTEXT) { 251 unsigned long flags; 252 253 local_irq_save(flags); 254 mm->context = NO_CONTEXT; 255 if (mm == current->mm) 256 activate_context(mm); 257 local_irq_restore(flags); 258 } 259} 260 261void flush_tlb_all(void) 262{ 263 unsigned long flags; 264 265 local_irq_save(flags); 266 sysreg_write(MMUCR, sysreg_read(MMUCR) | SYSREG_BIT(MMUCR_I)); 267 local_irq_restore(flags); 268} 269 270#ifdef CONFIG_PROC_FS 271 272#include <linux/seq_file.h> 273#include <linux/proc_fs.h> 274#include <linux/init.h> 275 276static void *tlb_start(struct seq_file *tlb, loff_t *pos) 277{ 278 static unsigned long tlb_index; 279 280 if (*pos >= NR_TLB_ENTRIES) 281 return NULL; 282 283 tlb_index = 0; 284 return &tlb_index; 285} 286 287static void *tlb_next(struct seq_file *tlb, void *v, loff_t *pos) 288{ 289 unsigned long *index = v; 290 291 if (*index >= NR_TLB_ENTRIES - 1) 292 return NULL; 293 294 ++*pos; 295 ++*index; 296 return index; 297} 298 299static void tlb_stop(struct seq_file *tlb, void *v) 300{ 301 302} 303 304static int tlb_show(struct seq_file *tlb, void *v) 305{ 306 unsigned int tlbehi, tlbehi_save, tlbelo, mmucr, mmucr_save; 307 unsigned long flags; 308 unsigned long *index = v; 309 310 if (*index == 0) 311 seq_puts(tlb, "ID V G ASID VPN PFN AP SZ C B W D\n"); 312 313 BUG_ON(*index >= NR_TLB_ENTRIES); 314 315 local_irq_save(flags); 316 mmucr_save = sysreg_read(MMUCR); 317 tlbehi_save = sysreg_read(TLBEHI); 318 mmucr = SYSREG_BFINS(DRP, *index, mmucr_save); 319 sysreg_write(MMUCR, mmucr); 320 321 /* TLBR might change the ASID */ 322 __builtin_tlbr(); 323 cpu_sync_pipeline(); 324 325 tlbehi = sysreg_read(TLBEHI); 326 tlbelo = sysreg_read(TLBELO); 327 328 sysreg_write(MMUCR, mmucr_save); 329 sysreg_write(TLBEHI, tlbehi_save); 330 cpu_sync_pipeline(); 331 local_irq_restore(flags); 332 333 seq_printf(tlb, "%2lu: %c %c %02x %05x %05x %o %o %c %c %c %c\n", 334 *index, 335 SYSREG_BFEXT(TLBEHI_V, tlbehi) ? '1' : '0', 336 SYSREG_BFEXT(G, tlbelo) ? '1' : '0', 337 SYSREG_BFEXT(ASID, tlbehi), 338 SYSREG_BFEXT(VPN, tlbehi) >> 2, 339 SYSREG_BFEXT(PFN, tlbelo) >> 2, 340 SYSREG_BFEXT(AP, tlbelo), 341 SYSREG_BFEXT(SZ, tlbelo), 342 SYSREG_BFEXT(TLBELO_C, tlbelo) ? '1' : '0', 343 SYSREG_BFEXT(B, tlbelo) ? '1' : '0', 344 SYSREG_BFEXT(W, tlbelo) ? '1' : '0', 345 SYSREG_BFEXT(TLBELO_D, tlbelo) ? '1' : '0'); 346 347 return 0; 348} 349 350static const struct seq_operations tlb_ops = { 351 .start = tlb_start, 352 .next = tlb_next, 353 .stop = tlb_stop, 354 .show = tlb_show, 355}; 356 357static int tlb_open(struct inode *inode, struct file *file) 358{ 359 return seq_open(file, &tlb_ops); 360} 361 362static const struct file_operations proc_tlb_operations = { 363 .open = tlb_open, 364 .read = seq_read, 365 .llseek = seq_lseek, 366 .release = seq_release, 367}; 368 369static int __init proctlb_init(void) 370{ 371 proc_create("tlb", 0, NULL, &proc_tlb_operations); 372 return 0; 373} 374late_initcall(proctlb_init); 375#endif /* CONFIG_PROC_FS */ 376