xref: /linux/arch/nios2/mm/tlb.c (revision 737a3fa20f2e195d94d9501ab5d76c29194d8176)
1c983e92fSLey Foon Tan /*
2c983e92fSLey Foon Tan  * Nios2 TLB handling
3c983e92fSLey Foon Tan  *
4c983e92fSLey Foon Tan  * Copyright (C) 2009, Wind River Systems Inc
5c983e92fSLey Foon Tan  *   Implemented by fredrik.markstrom@gmail.com and ivarholmqvist@gmail.com
6c983e92fSLey Foon Tan  *
7c983e92fSLey Foon Tan  * This file is subject to the terms and conditions of the GNU General Public
8c983e92fSLey Foon Tan  * License.  See the file "COPYING" in the main directory of this archive
9c983e92fSLey Foon Tan  * for more details.
10c983e92fSLey Foon Tan  */
11c983e92fSLey Foon Tan 
12c983e92fSLey Foon Tan #include <linux/init.h>
13c983e92fSLey Foon Tan #include <linux/sched.h>
14c983e92fSLey Foon Tan #include <linux/mm.h>
15c983e92fSLey Foon Tan #include <linux/pagemap.h>
16c983e92fSLey Foon Tan 
17c983e92fSLey Foon Tan #include <asm/tlb.h>
18c983e92fSLey Foon Tan #include <asm/mmu_context.h>
19c983e92fSLey Foon Tan #include <asm/pgtable.h>
20c983e92fSLey Foon Tan #include <asm/cpuinfo.h>
21c983e92fSLey Foon Tan 
22c983e92fSLey Foon Tan #define TLB_INDEX_MASK		\
23c983e92fSLey Foon Tan 	((((1UL << (cpuinfo.tlb_ptr_sz - cpuinfo.tlb_num_ways_log2))) - 1) \
24c983e92fSLey Foon Tan 		<< PAGE_SHIFT)
25c983e92fSLey Foon Tan 
26c983e92fSLey Foon Tan static void get_misc_and_pid(unsigned long *misc, unsigned long *pid)
27c983e92fSLey Foon Tan {
28c983e92fSLey Foon Tan 	*misc  = RDCTL(CTL_TLBMISC);
29c983e92fSLey Foon Tan 	*misc &= (TLBMISC_PID | TLBMISC_WAY);
30c983e92fSLey Foon Tan 	*pid  = *misc & TLBMISC_PID;
31c983e92fSLey Foon Tan }
32c983e92fSLey Foon Tan 
33c983e92fSLey Foon Tan /*
343437d3c8SNicholas Piggin  * This provides a PTEADDR value for addr that will cause a TLB miss
353437d3c8SNicholas Piggin  * (fast TLB miss). TLB invalidation replaces entries with this value.
363437d3c8SNicholas Piggin  */
373437d3c8SNicholas Piggin static unsigned long pteaddr_invalid(unsigned long addr)
383437d3c8SNicholas Piggin {
393437d3c8SNicholas Piggin 	return ((addr | 0xC0000000UL) >> PAGE_SHIFT) << 2;
403437d3c8SNicholas Piggin }
413437d3c8SNicholas Piggin 
423437d3c8SNicholas Piggin /*
43c983e92fSLey Foon Tan  * This one is only used for pages with the global bit set so we don't care
44c983e92fSLey Foon Tan  * much about the ASID.
45c983e92fSLey Foon Tan  */
46c983e92fSLey Foon Tan void flush_tlb_one_pid(unsigned long addr, unsigned long mmu_pid)
47c983e92fSLey Foon Tan {
48c983e92fSLey Foon Tan 	unsigned int way;
49c983e92fSLey Foon Tan 	unsigned long org_misc, pid_misc;
50c983e92fSLey Foon Tan 
51c983e92fSLey Foon Tan 	pr_debug("Flush tlb-entry for vaddr=%#lx\n", addr);
52c983e92fSLey Foon Tan 
53c983e92fSLey Foon Tan 	/* remember pid/way until we return. */
54c983e92fSLey Foon Tan 	get_misc_and_pid(&org_misc, &pid_misc);
55c983e92fSLey Foon Tan 
56c983e92fSLey Foon Tan 	WRCTL(CTL_PTEADDR, (addr >> PAGE_SHIFT) << 2);
57c983e92fSLey Foon Tan 
58c983e92fSLey Foon Tan 	for (way = 0; way < cpuinfo.tlb_num_ways; way++) {
59c983e92fSLey Foon Tan 		unsigned long pteaddr;
60c983e92fSLey Foon Tan 		unsigned long tlbmisc;
61c983e92fSLey Foon Tan 		unsigned long pid;
62c983e92fSLey Foon Tan 
637d173070SNicholas Piggin 		tlbmisc = TLBMISC_RD | (way << TLBMISC_WAY_SHIFT);
64c983e92fSLey Foon Tan 		WRCTL(CTL_TLBMISC, tlbmisc);
65c6b1d363SNicholas Piggin 
66c983e92fSLey Foon Tan 		pteaddr = RDCTL(CTL_PTEADDR);
67c6b1d363SNicholas Piggin 		if (((pteaddr >> 2) & 0xfffff) != (addr >> PAGE_SHIFT))
68c6b1d363SNicholas Piggin 			continue;
69c6b1d363SNicholas Piggin 
70c983e92fSLey Foon Tan 		tlbmisc = RDCTL(CTL_TLBMISC);
71c983e92fSLey Foon Tan 		pid = (tlbmisc >> TLBMISC_PID_SHIFT) & TLBMISC_PID_MASK;
72c6b1d363SNicholas Piggin 		if (pid != mmu_pid)
73c6b1d363SNicholas Piggin 			continue;
74c983e92fSLey Foon Tan 
757d173070SNicholas Piggin 		tlbmisc = TLBMISC_WE | (way << TLBMISC_WAY_SHIFT);
76c983e92fSLey Foon Tan 		WRCTL(CTL_TLBMISC, tlbmisc);
77c6b1d363SNicholas Piggin 		WRCTL(CTL_PTEADDR, pteaddr_invalid(addr));
783437d3c8SNicholas Piggin 		WRCTL(CTL_TLBACC, 0);
79c983e92fSLey Foon Tan 	}
80c983e92fSLey Foon Tan 
81c983e92fSLey Foon Tan 	WRCTL(CTL_TLBMISC, org_misc);
82c983e92fSLey Foon Tan }
83c983e92fSLey Foon Tan 
84c983e92fSLey Foon Tan void flush_tlb_range(struct vm_area_struct *vma, unsigned long start,
85c983e92fSLey Foon Tan 			unsigned long end)
86c983e92fSLey Foon Tan {
87c983e92fSLey Foon Tan 	unsigned long mmu_pid = get_pid_from_context(&vma->vm_mm->context);
88c983e92fSLey Foon Tan 
89c983e92fSLey Foon Tan 	while (start < end) {
90c983e92fSLey Foon Tan 		flush_tlb_one_pid(start, mmu_pid);
91c983e92fSLey Foon Tan 		start += PAGE_SIZE;
92c983e92fSLey Foon Tan 	}
93c983e92fSLey Foon Tan }
94c983e92fSLey Foon Tan 
95c983e92fSLey Foon Tan /*
96c983e92fSLey Foon Tan  * This one is only used for pages with the global bit set so we don't care
97c983e92fSLey Foon Tan  * much about the ASID.
98c983e92fSLey Foon Tan  */
99195568a1SNicholas Piggin static void flush_tlb_one(unsigned long addr)
100c983e92fSLey Foon Tan {
101c983e92fSLey Foon Tan 	unsigned int way;
102c983e92fSLey Foon Tan 	unsigned long org_misc, pid_misc;
103c983e92fSLey Foon Tan 
104c983e92fSLey Foon Tan 	pr_debug("Flush tlb-entry for vaddr=%#lx\n", addr);
105c983e92fSLey Foon Tan 
106c983e92fSLey Foon Tan 	/* remember pid/way until we return. */
107c983e92fSLey Foon Tan 	get_misc_and_pid(&org_misc, &pid_misc);
108c983e92fSLey Foon Tan 
109c983e92fSLey Foon Tan 	WRCTL(CTL_PTEADDR, (addr >> PAGE_SHIFT) << 2);
110c983e92fSLey Foon Tan 
111c983e92fSLey Foon Tan 	for (way = 0; way < cpuinfo.tlb_num_ways; way++) {
112c983e92fSLey Foon Tan 		unsigned long pteaddr;
113c983e92fSLey Foon Tan 		unsigned long tlbmisc;
114c983e92fSLey Foon Tan 
1157d173070SNicholas Piggin 		tlbmisc = TLBMISC_RD | (way << TLBMISC_WAY_SHIFT);
116c983e92fSLey Foon Tan 		WRCTL(CTL_TLBMISC, tlbmisc);
117c983e92fSLey Foon Tan 
118c6b1d363SNicholas Piggin 		pteaddr = RDCTL(CTL_PTEADDR);
119c6b1d363SNicholas Piggin 		if (((pteaddr >> 2) & 0xfffff) != (addr >> PAGE_SHIFT))
120c6b1d363SNicholas Piggin 			continue;
121c6b1d363SNicholas Piggin 
1223437d3c8SNicholas Piggin 		pr_debug("Flush entry by writing way=%dl pid=%ld\n",
1233437d3c8SNicholas Piggin 			 way, (pid_misc >> TLBMISC_PID_SHIFT));
124c983e92fSLey Foon Tan 
1257d173070SNicholas Piggin 		tlbmisc = TLBMISC_WE | (way << TLBMISC_WAY_SHIFT);
126c983e92fSLey Foon Tan 		WRCTL(CTL_TLBMISC, tlbmisc);
127c6b1d363SNicholas Piggin 		WRCTL(CTL_PTEADDR, pteaddr_invalid(addr));
1283437d3c8SNicholas Piggin 		WRCTL(CTL_TLBACC, 0);
129c983e92fSLey Foon Tan 	}
130c983e92fSLey Foon Tan 
131c983e92fSLey Foon Tan 	WRCTL(CTL_TLBMISC, org_misc);
132c983e92fSLey Foon Tan }
133c983e92fSLey Foon Tan 
134195568a1SNicholas Piggin void flush_tlb_kernel_range(unsigned long start, unsigned long end)
135195568a1SNicholas Piggin {
136195568a1SNicholas Piggin 	while (start < end) {
137195568a1SNicholas Piggin 		flush_tlb_one(start);
138195568a1SNicholas Piggin 		start += PAGE_SIZE;
139195568a1SNicholas Piggin 	}
140195568a1SNicholas Piggin }
141195568a1SNicholas Piggin 
142c983e92fSLey Foon Tan void dump_tlb_line(unsigned long line)
143c983e92fSLey Foon Tan {
144c983e92fSLey Foon Tan 	unsigned int way;
145c983e92fSLey Foon Tan 	unsigned long org_misc;
146c983e92fSLey Foon Tan 
147c983e92fSLey Foon Tan 	pr_debug("dump tlb-entries for line=%#lx (addr %08lx)\n", line,
148c983e92fSLey Foon Tan 		line << (PAGE_SHIFT + cpuinfo.tlb_num_ways_log2));
149c983e92fSLey Foon Tan 
150c983e92fSLey Foon Tan 	/* remember pid/way until we return */
151c983e92fSLey Foon Tan 	org_misc = (RDCTL(CTL_TLBMISC) & (TLBMISC_PID | TLBMISC_WAY));
152c983e92fSLey Foon Tan 
153c983e92fSLey Foon Tan 	WRCTL(CTL_PTEADDR, line << 2);
154c983e92fSLey Foon Tan 
155c983e92fSLey Foon Tan 	for (way = 0; way < cpuinfo.tlb_num_ways; way++) {
156c983e92fSLey Foon Tan 		unsigned long pteaddr;
157c983e92fSLey Foon Tan 		unsigned long tlbmisc;
158c983e92fSLey Foon Tan 		unsigned long tlbacc;
159c983e92fSLey Foon Tan 
160c983e92fSLey Foon Tan 		WRCTL(CTL_TLBMISC, TLBMISC_RD | (way << TLBMISC_WAY_SHIFT));
161c983e92fSLey Foon Tan 		pteaddr = RDCTL(CTL_PTEADDR);
162c983e92fSLey Foon Tan 		tlbmisc = RDCTL(CTL_TLBMISC);
163c983e92fSLey Foon Tan 		tlbacc = RDCTL(CTL_TLBACC);
164c983e92fSLey Foon Tan 
1653437d3c8SNicholas Piggin 		if ((tlbacc << PAGE_SHIFT) != 0) {
166c983e92fSLey Foon Tan 			pr_debug("-- way:%02x vpn:0x%08lx phys:0x%08lx pid:0x%02lx flags:%c%c%c%c%c\n",
167c983e92fSLey Foon Tan 				way,
168c983e92fSLey Foon Tan 				(pteaddr << (PAGE_SHIFT-2)),
169c983e92fSLey Foon Tan 				(tlbacc << PAGE_SHIFT),
170c983e92fSLey Foon Tan 				((tlbmisc >> TLBMISC_PID_SHIFT) &
171c983e92fSLey Foon Tan 				TLBMISC_PID_MASK),
172c983e92fSLey Foon Tan 				(tlbacc & _PAGE_READ ? 'r' : '-'),
173c983e92fSLey Foon Tan 				(tlbacc & _PAGE_WRITE ? 'w' : '-'),
174c983e92fSLey Foon Tan 				(tlbacc & _PAGE_EXEC ? 'x' : '-'),
175c983e92fSLey Foon Tan 				(tlbacc & _PAGE_GLOBAL ? 'g' : '-'),
176c983e92fSLey Foon Tan 				(tlbacc & _PAGE_CACHED ? 'c' : '-'));
177c983e92fSLey Foon Tan 		}
178c983e92fSLey Foon Tan 	}
179c983e92fSLey Foon Tan 
180c983e92fSLey Foon Tan 	WRCTL(CTL_TLBMISC, org_misc);
181c983e92fSLey Foon Tan }
182c983e92fSLey Foon Tan 
183c983e92fSLey Foon Tan void dump_tlb(void)
184c983e92fSLey Foon Tan {
185c983e92fSLey Foon Tan 	unsigned int i;
186c983e92fSLey Foon Tan 
187c983e92fSLey Foon Tan 	for (i = 0; i < cpuinfo.tlb_num_lines; i++)
188c983e92fSLey Foon Tan 		dump_tlb_line(i);
189c983e92fSLey Foon Tan }
190c983e92fSLey Foon Tan 
191c6b1d363SNicholas Piggin void flush_tlb_pid(unsigned long mmu_pid)
192c983e92fSLey Foon Tan {
1933437d3c8SNicholas Piggin 	unsigned long addr = 0;
194c983e92fSLey Foon Tan 	unsigned int line;
195c983e92fSLey Foon Tan 	unsigned int way;
196c983e92fSLey Foon Tan 	unsigned long org_misc, pid_misc;
197c983e92fSLey Foon Tan 
198c983e92fSLey Foon Tan 	/* remember pid/way until we return */
199c983e92fSLey Foon Tan 	get_misc_and_pid(&org_misc, &pid_misc);
200c983e92fSLey Foon Tan 
201c983e92fSLey Foon Tan 	for (line = 0; line < cpuinfo.tlb_num_lines; line++) {
2023437d3c8SNicholas Piggin 		WRCTL(CTL_PTEADDR, pteaddr_invalid(addr));
203c983e92fSLey Foon Tan 
204c983e92fSLey Foon Tan 		for (way = 0; way < cpuinfo.tlb_num_ways; way++) {
205c983e92fSLey Foon Tan 			unsigned long tlbmisc;
206c6b1d363SNicholas Piggin 			unsigned long pid;
207c983e92fSLey Foon Tan 
2087d173070SNicholas Piggin 			tlbmisc = TLBMISC_RD | (way << TLBMISC_WAY_SHIFT);
209c983e92fSLey Foon Tan 			WRCTL(CTL_TLBMISC, tlbmisc);
210c983e92fSLey Foon Tan 			tlbmisc = RDCTL(CTL_TLBMISC);
211c6b1d363SNicholas Piggin 			pid = (tlbmisc >> TLBMISC_PID_SHIFT) & TLBMISC_PID_MASK;
212c6b1d363SNicholas Piggin 			if (pid != mmu_pid)
213c6b1d363SNicholas Piggin 				continue;
214c983e92fSLey Foon Tan 
2157d173070SNicholas Piggin 			tlbmisc = TLBMISC_WE | (way << TLBMISC_WAY_SHIFT);
216c983e92fSLey Foon Tan 			WRCTL(CTL_TLBMISC, tlbmisc);
2173437d3c8SNicholas Piggin 			WRCTL(CTL_TLBACC, 0);
218c983e92fSLey Foon Tan 		}
219c983e92fSLey Foon Tan 
2203437d3c8SNicholas Piggin 		addr += PAGE_SIZE;
22158fd4766SNicholas Piggin 	}
2223437d3c8SNicholas Piggin 
223c983e92fSLey Foon Tan 	WRCTL(CTL_TLBMISC, org_misc);
224c983e92fSLey Foon Tan }
225c983e92fSLey Foon Tan 
226e71c99feSNicholas Piggin /*
227e71c99feSNicholas Piggin  * All entries common to a mm share an asid.  To effectively flush these
228e71c99feSNicholas Piggin  * entries, we just bump the asid.
229e71c99feSNicholas Piggin  */
230e71c99feSNicholas Piggin void flush_tlb_mm(struct mm_struct *mm)
231e71c99feSNicholas Piggin {
232e71c99feSNicholas Piggin 	if (current->mm == mm) {
233e71c99feSNicholas Piggin 		unsigned long mmu_pid = get_pid_from_context(&mm->context);
234e71c99feSNicholas Piggin 		flush_tlb_pid(mmu_pid);
235e71c99feSNicholas Piggin 	} else {
236e71c99feSNicholas Piggin 		memset(&mm->context, 0, sizeof(mm_context_t));
237e71c99feSNicholas Piggin 	}
238e71c99feSNicholas Piggin }
239e71c99feSNicholas Piggin 
240c983e92fSLey Foon Tan void flush_tlb_all(void)
241c983e92fSLey Foon Tan {
2423437d3c8SNicholas Piggin 	unsigned long addr = 0;
2433437d3c8SNicholas Piggin 	unsigned int line;
244c983e92fSLey Foon Tan 	unsigned int way;
245*737a3fa2SNicholas Piggin 	unsigned long org_misc, pid_misc;
246c983e92fSLey Foon Tan 
247c983e92fSLey Foon Tan 	/* remember pid/way until we return */
248c983e92fSLey Foon Tan 	get_misc_and_pid(&org_misc, &pid_misc);
249c983e92fSLey Foon Tan 
250*737a3fa2SNicholas Piggin 	/* Start at way 0, way is auto-incremented after each TLBACC write */
251*737a3fa2SNicholas Piggin 	WRCTL(CTL_TLBMISC, TLBMISC_WE);
252*737a3fa2SNicholas Piggin 
253c983e92fSLey Foon Tan 	/* Map each TLB entry to physcal address 0 with no-access and a
254c983e92fSLey Foon Tan 	   bad ptbase */
2553437d3c8SNicholas Piggin 	for (line = 0; line < cpuinfo.tlb_num_lines; line++) {
2563437d3c8SNicholas Piggin 		WRCTL(CTL_PTEADDR, pteaddr_invalid(addr));
257*737a3fa2SNicholas Piggin 		for (way = 0; way < cpuinfo.tlb_num_ways; way++)
2583437d3c8SNicholas Piggin 			WRCTL(CTL_TLBACC, 0);
2593437d3c8SNicholas Piggin 
2603437d3c8SNicholas Piggin 		addr += PAGE_SIZE;
261c983e92fSLey Foon Tan 	}
262c983e92fSLey Foon Tan 
263c983e92fSLey Foon Tan 	/* restore pid/way */
264c983e92fSLey Foon Tan 	WRCTL(CTL_TLBMISC, org_misc);
265c983e92fSLey Foon Tan }
266c983e92fSLey Foon Tan 
267c983e92fSLey Foon Tan void set_mmu_pid(unsigned long pid)
268c983e92fSLey Foon Tan {
269c6b1d363SNicholas Piggin 	unsigned long tlbmisc;
270c6b1d363SNicholas Piggin 
271c6b1d363SNicholas Piggin 	tlbmisc = RDCTL(CTL_TLBMISC);
272c6b1d363SNicholas Piggin 	tlbmisc = (tlbmisc & TLBMISC_WAY);
273c6b1d363SNicholas Piggin 	tlbmisc |= (pid & TLBMISC_PID_MASK) << TLBMISC_PID_SHIFT;
274c6b1d363SNicholas Piggin 	WRCTL(CTL_TLBMISC, tlbmisc);
275c983e92fSLey Foon Tan }
276