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