Commit | Line | Data |
---|---|---|
5f97f7f9 HS |
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 | ||
b13d618b HS |
14 | /* TODO: Get the correct number from the CONFIG1 system register */ |
15 | #define NR_TLB_ENTRIES 32 | |
5f97f7f9 | 16 | |
b13d618b | 17 | static void show_dtlb_entry(unsigned int index) |
5f97f7f9 | 18 | { |
b13d618b | 19 | u32 tlbehi, tlbehi_save, tlbelo, mmucr, mmucr_save; |
361f6ed1 | 20 | unsigned long flags; |
5f97f7f9 HS |
21 | |
22 | local_irq_save(flags); | |
23 | mmucr_save = sysreg_read(MMUCR); | |
24 | tlbehi_save = sysreg_read(TLBEHI); | |
b13d618b | 25 | mmucr = SYSREG_BFINS(DRP, index, mmucr_save); |
5f97f7f9 HS |
26 | sysreg_write(MMUCR, mmucr); |
27 | ||
b13d618b | 28 | __builtin_tlbr(); |
5f97f7f9 HS |
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, | |
b13d618b HS |
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' : ' '); | |
5f97f7f9 HS |
47 | |
48 | sysreg_write(MMUCR, mmucr_save); | |
49 | sysreg_write(TLBEHI, tlbehi_save); | |
50 | cpu_sync_pipeline(); | |
51 | local_irq_restore(flags); | |
52 | } | |
53 | ||
54 | void dump_dtlb(void) | |
55 | { | |
56 | unsigned int i; | |
57 | ||
58 | printk("ID V G ASID VPN PFN AP SZ C B W D\n"); | |
b13d618b | 59 | for (i = 0; i < NR_TLB_ENTRIES; i++) |
5f97f7f9 HS |
60 | show_dtlb_entry(i); |
61 | } | |
62 | ||
b13d618b | 63 | static void update_dtlb(unsigned long address, pte_t pte) |
5f97f7f9 | 64 | { |
b13d618b HS |
65 | u32 tlbehi; |
66 | u32 mmucr; | |
5f97f7f9 | 67 | |
b13d618b HS |
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); | |
5f97f7f9 HS |
77 | |
78 | /* Does this mapping already exist? */ | |
b13d618b HS |
79 | __builtin_tlbs(); |
80 | mmucr = sysreg_read(MMUCR); | |
5f97f7f9 HS |
81 | |
82 | if (mmucr & SYSREG_BIT(MMUCR_N)) { | |
83 | /* Not found -- pick a not-recently-accessed entry */ | |
b13d618b HS |
84 | unsigned int rp; |
85 | u32 tlbar = sysreg_read(TLBARLO); | |
5f97f7f9 HS |
86 | |
87 | rp = 32 - fls(tlbar); | |
88 | if (rp == 32) { | |
89 | rp = 0; | |
90 | sysreg_write(TLBARLO, -1L); | |
91 | } | |
92 | ||
b13d618b | 93 | mmucr = SYSREG_BFINS(DRP, rp, mmucr); |
5f97f7f9 HS |
94 | sysreg_write(MMUCR, mmucr); |
95 | } | |
96 | ||
5f97f7f9 HS |
97 | sysreg_write(TLBELO, pte_val(pte) & _PAGE_FLAGS_HARDWARE_MASK); |
98 | ||
99 | /* Let's go */ | |
b13d618b | 100 | __builtin_tlbw(); |
5f97f7f9 HS |
101 | } |
102 | ||
103 | void update_mmu_cache(struct vm_area_struct *vma, | |
4b3073e1 | 104 | unsigned long address, pte_t *ptep) |
5f97f7f9 HS |
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); | |
4b3073e1 | 113 | update_dtlb(address, *ptep); |
5f97f7f9 HS |
114 | local_irq_restore(flags); |
115 | } | |
116 | ||
b13d618b | 117 | static void __flush_tlb_page(unsigned long asid, unsigned long page) |
5f97f7f9 | 118 | { |
b13d618b | 119 | u32 mmucr, tlbehi; |
5f97f7f9 | 120 | |
b13d618b HS |
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(); | |
5f97f7f9 HS |
130 | mmucr = sysreg_read(MMUCR); |
131 | ||
132 | if (!(mmucr & SYSREG_BIT(MMUCR_N))) { | |
b13d618b HS |
133 | unsigned int entry; |
134 | u32 tlbarlo; | |
5f97f7f9 HS |
135 | |
136 | /* Clear the "valid" bit */ | |
5f97f7f9 | 137 | sysreg_write(TLBEHI, tlbehi); |
5f97f7f9 HS |
138 | |
139 | /* mark the entry as "not accessed" */ | |
b13d618b | 140 | entry = SYSREG_BFEXT(DRP, mmucr); |
5f97f7f9 | 141 | tlbarlo = sysreg_read(TLBARLO); |
b13d618b | 142 | tlbarlo |= (0x80000000UL >> entry); |
5f97f7f9 HS |
143 | sysreg_write(TLBARLO, tlbarlo); |
144 | ||
145 | /* update the entry with valid bit clear */ | |
b13d618b | 146 | __builtin_tlbw(); |
5f97f7f9 HS |
147 | } |
148 | } | |
149 | ||
150 | void 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 | ||
173 | void 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; | |
b13d618b | 184 | |
5f97f7f9 HS |
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 { | |
b13d618b HS |
190 | unsigned long asid; |
191 | unsigned long saved_asid; | |
192 | ||
193 | asid = mm->context & MMU_CONTEXT_ASID_MASK; | |
194 | saved_asid = MMU_NO_ASID; | |
5f97f7f9 HS |
195 | |
196 | start &= PAGE_MASK; | |
197 | end += (PAGE_SIZE - 1); | |
198 | end &= PAGE_MASK; | |
b13d618b | 199 | |
5f97f7f9 HS |
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 | /* | |
b13d618b HS |
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. | |
5f97f7f9 HS |
220 | */ |
221 | void flush_tlb_kernel_range(unsigned long start, unsigned long end) | |
222 | { | |
223 | unsigned long flags; | |
224 | int size; | |
225 | ||
5f97f7f9 HS |
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 { | |
b13d618b HS |
230 | unsigned long asid; |
231 | ||
232 | local_irq_save(flags); | |
233 | asid = get_asid(); | |
5f97f7f9 HS |
234 | |
235 | start &= PAGE_MASK; | |
236 | end += (PAGE_SIZE - 1); | |
237 | end &= PAGE_MASK; | |
b13d618b | 238 | |
5f97f7f9 HS |
239 | while (start < end) { |
240 | __flush_tlb_page(asid, start); | |
241 | start += PAGE_SIZE; | |
242 | } | |
b13d618b | 243 | local_irq_restore(flags); |
5f97f7f9 | 244 | } |
5f97f7f9 HS |
245 | } |
246 | ||
247 | void 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 | ||
261 | void 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 | ||
276 | static void *tlb_start(struct seq_file *tlb, loff_t *pos) | |
277 | { | |
278 | static unsigned long tlb_index; | |
279 | ||
b13d618b | 280 | if (*pos >= NR_TLB_ENTRIES) |
5f97f7f9 HS |
281 | return NULL; |
282 | ||
283 | tlb_index = 0; | |
284 | return &tlb_index; | |
285 | } | |
286 | ||
287 | static void *tlb_next(struct seq_file *tlb, void *v, loff_t *pos) | |
288 | { | |
289 | unsigned long *index = v; | |
290 | ||
b13d618b | 291 | if (*index >= NR_TLB_ENTRIES - 1) |
5f97f7f9 HS |
292 | return NULL; |
293 | ||
294 | ++*pos; | |
295 | ++*index; | |
296 | return index; | |
297 | } | |
298 | ||
299 | static void tlb_stop(struct seq_file *tlb, void *v) | |
300 | { | |
301 | ||
302 | } | |
303 | ||
304 | static int tlb_show(struct seq_file *tlb, void *v) | |
305 | { | |
361f6ed1 HS |
306 | unsigned int tlbehi, tlbehi_save, tlbelo, mmucr, mmucr_save; |
307 | unsigned long flags; | |
5f97f7f9 HS |
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 | ||
b13d618b | 313 | BUG_ON(*index >= NR_TLB_ENTRIES); |
5f97f7f9 HS |
314 | |
315 | local_irq_save(flags); | |
316 | mmucr_save = sysreg_read(MMUCR); | |
317 | tlbehi_save = sysreg_read(TLBEHI); | |
b13d618b | 318 | mmucr = SYSREG_BFINS(DRP, *index, mmucr_save); |
5f97f7f9 HS |
319 | sysreg_write(MMUCR, mmucr); |
320 | ||
b13d618b HS |
321 | /* TLBR might change the ASID */ |
322 | __builtin_tlbr(); | |
5f97f7f9 HS |
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", | |
b13d618b HS |
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'); | |
5f97f7f9 HS |
346 | |
347 | return 0; | |
348 | } | |
349 | ||
f6135d12 | 350 | static const struct seq_operations tlb_ops = { |
5f97f7f9 HS |
351 | .start = tlb_start, |
352 | .next = tlb_next, | |
353 | .stop = tlb_stop, | |
354 | .show = tlb_show, | |
355 | }; | |
356 | ||
357 | static int tlb_open(struct inode *inode, struct file *file) | |
358 | { | |
359 | return seq_open(file, &tlb_ops); | |
360 | } | |
361 | ||
5dfe4c96 | 362 | static const struct file_operations proc_tlb_operations = { |
5f97f7f9 HS |
363 | .open = tlb_open, |
364 | .read = seq_read, | |
365 | .llseek = seq_lseek, | |
366 | .release = seq_release, | |
367 | }; | |
368 | ||
369 | static int __init proctlb_init(void) | |
370 | { | |
0d9f10f4 | 371 | proc_create("tlb", 0, NULL, &proc_tlb_operations); |
5f97f7f9 HS |
372 | return 0; |
373 | } | |
374 | late_initcall(proctlb_init); | |
375 | #endif /* CONFIG_PROC_FS */ |