blob: 06677be98ffba5fcb0143dac626bf96235fd1e16 [file] [log] [blame]
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -07001/*
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
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +020014/* TODO: Get the correct number from the CONFIG1 system register */
15#define NR_TLB_ENTRIES 32
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -070016
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +020017static void show_dtlb_entry(unsigned int index)
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -070018{
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +020019 u32 tlbehi, tlbehi_save, tlbelo, mmucr, mmucr_save;
Haavard Skinnemoen361f6ed2006-09-27 01:50:14 -070020 unsigned long flags;
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -070021
22 local_irq_save(flags);
23 mmucr_save = sysreg_read(MMUCR);
24 tlbehi_save = sysreg_read(TLBEHI);
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +020025 mmucr = SYSREG_BFINS(DRP, index, mmucr_save);
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -070026 sysreg_write(MMUCR, mmucr);
27
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +020028 __builtin_tlbr();
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -070029 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,
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +020036 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' : ' ');
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -070047
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");
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +020059 for (i = 0; i < NR_TLB_ENTRIES; i++)
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -070060 show_dtlb_entry(i);
61}
62
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +020063static void update_dtlb(unsigned long address, pte_t pte)
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -070064{
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +020065 u32 tlbehi;
66 u32 mmucr;
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -070067
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +020068 /*
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);
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -070077
78 /* Does this mapping already exist? */
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +020079 __builtin_tlbs();
80 mmucr = sysreg_read(MMUCR);
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -070081
82 if (mmucr & SYSREG_BIT(MMUCR_N)) {
83 /* Not found -- pick a not-recently-accessed entry */
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +020084 unsigned int rp;
85 u32 tlbar = sysreg_read(TLBARLO);
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -070086
87 rp = 32 - fls(tlbar);
88 if (rp == 32) {
89 rp = 0;
90 sysreg_write(TLBARLO, -1L);
91 }
92
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +020093 mmucr = SYSREG_BFINS(DRP, rp, mmucr);
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -070094 sysreg_write(MMUCR, mmucr);
95 }
96
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -070097 sysreg_write(TLBELO, pte_val(pte) & _PAGE_FLAGS_HARDWARE_MASK);
98
99 /* Let's go */
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +0200100 __builtin_tlbw();
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700101}
102
103void update_mmu_cache(struct vm_area_struct *vma,
104 unsigned long address, pte_t pte)
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);
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +0200113 update_dtlb(address, pte);
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700114 local_irq_restore(flags);
115}
116
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +0200117static void __flush_tlb_page(unsigned long asid, unsigned long page)
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700118{
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +0200119 u32 mmucr, tlbehi;
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700120
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +0200121 /*
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();
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700130 mmucr = sysreg_read(MMUCR);
131
132 if (!(mmucr & SYSREG_BIT(MMUCR_N))) {
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +0200133 unsigned int entry;
134 u32 tlbarlo;
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700135
136 /* Clear the "valid" bit */
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700137 sysreg_write(TLBEHI, tlbehi);
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700138
139 /* mark the entry as "not accessed" */
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +0200140 entry = SYSREG_BFEXT(DRP, mmucr);
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700141 tlbarlo = sysreg_read(TLBARLO);
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +0200142 tlbarlo |= (0x80000000UL >> entry);
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700143 sysreg_write(TLBARLO, tlbarlo);
144
145 /* update the entry with valid bit clear */
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +0200146 __builtin_tlbw();
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700147 }
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;
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +0200184
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700185 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 {
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +0200190 unsigned long asid;
191 unsigned long saved_asid;
192
193 asid = mm->context & MMU_CONTEXT_ASID_MASK;
194 saved_asid = MMU_NO_ASID;
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700195
196 start &= PAGE_MASK;
197 end += (PAGE_SIZE - 1);
198 end &= PAGE_MASK;
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +0200199
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700200 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/*
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +0200217 * 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.
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700220 */
221void flush_tlb_kernel_range(unsigned long start, unsigned long end)
222{
223 unsigned long flags;
224 int size;
225
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700226 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 {
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +0200230 unsigned long asid;
231
232 local_irq_save(flags);
233 asid = get_asid();
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700234
235 start &= PAGE_MASK;
236 end += (PAGE_SIZE - 1);
237 end &= PAGE_MASK;
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +0200238
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700239 while (start < end) {
240 __flush_tlb_page(asid, start);
241 start += PAGE_SIZE;
242 }
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +0200243 local_irq_restore(flags);
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700244 }
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700245}
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
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +0200280 if (*pos >= NR_TLB_ENTRIES)
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700281 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
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +0200291 if (*index >= NR_TLB_ENTRIES - 1)
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700292 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{
Haavard Skinnemoen361f6ed2006-09-27 01:50:14 -0700306 unsigned int tlbehi, tlbehi_save, tlbelo, mmucr, mmucr_save;
307 unsigned long flags;
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700308 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
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +0200313 BUG_ON(*index >= NR_TLB_ENTRIES);
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700314
315 local_irq_save(flags);
316 mmucr_save = sysreg_read(MMUCR);
317 tlbehi_save = sysreg_read(TLBEHI);
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +0200318 mmucr = SYSREG_BFINS(DRP, *index, mmucr_save);
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700319 sysreg_write(MMUCR, mmucr);
320
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +0200321 /* TLBR might change the ASID */
322 __builtin_tlbr();
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700323 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",
Haavard Skinnemoenb13d6182007-05-15 15:06:41 +0200334 *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');
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700346
347 return 0;
348}
349
Jan Engelhardtf6135d12008-01-22 20:41:37 +0100350static const struct seq_operations tlb_ops = {
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700351 .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
Arjan van de Ven5dfe4c92007-02-12 00:55:31 -0800362static const struct file_operations proc_tlb_operations = {
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700363 .open = tlb_open,
364 .read = seq_read,
365 .llseek = seq_lseek,
366 .release = seq_release,
367};
368
369static int __init proctlb_init(void)
370{
Denis V. Lunev0d9f10f2008-04-29 01:02:22 -0700371 proc_create("tlb", 0, NULL, &proc_tlb_operations);
Haavard Skinnemoen5f97f7f2006-09-25 23:32:13 -0700372 return 0;
373}
374late_initcall(proctlb_init);
375#endif /* CONFIG_PROC_FS */