]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/mips/mips/tlb.c
Upgrade to 1.6.16
[FreeBSD/FreeBSD.git] / sys / mips / mips / tlb.c
1 /*-
2  * Copyright (c) 2004-2010 Juli Mallett <jmallett@FreeBSD.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  * $FreeBSD$
27  */
28
29 #include "opt_ddb.h"
30
31 #include <sys/param.h>
32 #include <sys/kernel.h>
33 #include <sys/systm.h>
34 #include <sys/pcpu.h>
35 #include <sys/smp.h>
36
37 #include <vm/vm.h>
38 #include <vm/pmap.h>
39
40 #include <machine/pte.h>
41 #include <machine/tlb.h>
42
43 struct tlb_state {
44         unsigned wired;
45         struct tlb_entry {
46                 register_t entryhi;
47                 register_t entrylo0;
48                 register_t entrylo1;
49         } entry[MIPS_MAX_TLB_ENTRIES];
50 };
51
52 static struct tlb_state tlb_state[MAXCPU];
53
54 #if 0
55 /*
56  * PageMask must increment in steps of 2 bits.
57  */
58 COMPILE_TIME_ASSERT(POPCNT(TLBMASK_MASK) % 2 == 0);
59 #endif
60
61 static inline void
62 tlb_probe(void)
63 {
64         __asm __volatile ("tlbp" : : : "memory");
65         mips_cp0_sync();
66 }
67
68 static inline void
69 tlb_read(void)
70 {
71         __asm __volatile ("tlbr" : : : "memory");
72         mips_cp0_sync();
73 }
74
75 static inline void
76 tlb_write_indexed(void)
77 {
78         __asm __volatile ("tlbwi" : : : "memory");
79         mips_cp0_sync();
80 }
81
82 static inline void
83 tlb_write_random(void)
84 {
85         __asm __volatile ("tlbwr" : : : "memory");
86         mips_cp0_sync();
87 }
88
89 static void tlb_invalidate_one(unsigned);
90
91 void
92 tlb_insert_wired(unsigned i, vm_offset_t va, pt_entry_t pte0, pt_entry_t pte1)
93 {
94         register_t asid;
95         register_t s;
96
97         va &= ~PAGE_MASK;
98
99         s = intr_disable();
100         asid = mips_rd_entryhi() & TLBHI_ASID_MASK;
101
102         mips_wr_index(i);
103         mips_wr_pagemask(0);
104         mips_wr_entryhi(TLBHI_ENTRY(va, 0));
105         mips_wr_entrylo0(pte0);
106         mips_wr_entrylo1(pte1);
107         tlb_write_indexed();
108
109         mips_wr_entryhi(asid);
110         intr_restore(s);
111 }
112
113 void
114 tlb_invalidate_address(struct pmap *pmap, vm_offset_t va)
115 {
116         register_t asid;
117         register_t s;
118         int i;
119
120         va &= ~PAGE_MASK;
121
122         s = intr_disable();
123         asid = mips_rd_entryhi() & TLBHI_ASID_MASK;
124
125         mips_wr_pagemask(0);
126         mips_wr_entryhi(TLBHI_ENTRY(va, pmap_asid(pmap)));
127         tlb_probe();
128         i = mips_rd_index();
129         if (i >= 0)
130                 tlb_invalidate_one(i);
131
132         mips_wr_entryhi(asid);
133         intr_restore(s);
134 }
135
136 void
137 tlb_invalidate_all(void)
138 {
139         register_t asid;
140         register_t s;
141         unsigned i;
142
143         s = intr_disable();
144         asid = mips_rd_entryhi() & TLBHI_ASID_MASK;
145
146         for (i = mips_rd_wired(); i < num_tlbentries; i++)
147                 tlb_invalidate_one(i);
148
149         mips_wr_entryhi(asid);
150         intr_restore(s);
151 }
152
153 void
154 tlb_invalidate_all_user(struct pmap *pmap)
155 {
156         register_t asid;
157         register_t s;
158         unsigned i;
159
160         s = intr_disable();
161         asid = mips_rd_entryhi() & TLBHI_ASID_MASK;
162
163         for (i = mips_rd_wired(); i < num_tlbentries; i++) {
164                 register_t uasid;
165
166                 mips_wr_index(i);
167                 tlb_read();
168
169                 uasid = mips_rd_entryhi() & TLBHI_ASID_MASK;
170                 if (pmap == NULL) {
171                         /*
172                          * Invalidate all non-kernel entries.
173                          */
174                         if (uasid == 0)
175                                 continue;
176                 } else {
177                         /*
178                          * Invalidate this pmap's entries.
179                          */
180                         if (uasid != pmap_asid(pmap))
181                                 continue;
182                 }
183                 tlb_invalidate_one(i);
184         }
185
186         mips_wr_entryhi(asid);
187         intr_restore(s);
188 }
189
190 /*
191  * Invalidates any TLB entries that map a virtual page from the specified
192  * address range.  If "end" is zero, then every virtual page is considered to
193  * be within the address range's upper bound.
194  */
195 void
196 tlb_invalidate_range(pmap_t pmap, vm_offset_t start, vm_offset_t end)
197 {
198         register_t asid, end_hi, hi, hi_pagemask, s, save_asid, start_hi;
199         int i;
200
201         KASSERT(start < end || (end == 0 && start > 0),
202             ("tlb_invalidate_range: invalid range"));
203
204         /*
205          * Truncate the virtual address "start" to an even page frame number,
206          * and round the virtual address "end" to an even page frame number.
207          */
208         start &= ~((1 << TLBMASK_SHIFT) - 1);
209         end = (end + (1 << TLBMASK_SHIFT) - 1) & ~((1 << TLBMASK_SHIFT) - 1);
210
211         s = intr_disable();
212         save_asid = mips_rd_entryhi() & TLBHI_ASID_MASK;
213
214         asid = pmap_asid(pmap);
215         start_hi = TLBHI_ENTRY(start, asid);
216         end_hi = TLBHI_ENTRY(end, asid);
217
218         /*
219          * Select the fastest method for invalidating the TLB entries.
220          */
221         if (end - start < num_tlbentries << TLBMASK_SHIFT || (end == 0 &&
222             start >= -(num_tlbentries << TLBMASK_SHIFT))) {
223                 /*
224                  * The virtual address range is small compared to the size of
225                  * the TLB.  Probe the TLB for each even numbered page frame
226                  * within the virtual address range.
227                  */
228                 for (hi = start_hi; hi != end_hi; hi += 1 << TLBMASK_SHIFT) {
229                         mips_wr_pagemask(0);
230                         mips_wr_entryhi(hi);
231                         tlb_probe();
232                         i = mips_rd_index();
233                         if (i >= 0)
234                                 tlb_invalidate_one(i);
235                 }
236         } else {
237                 /*
238                  * The virtual address range is large compared to the size of
239                  * the TLB.  Test every non-wired TLB entry.
240                  */
241                 for (i = mips_rd_wired(); i < num_tlbentries; i++) {
242                         mips_wr_index(i);
243                         tlb_read();
244                         hi = mips_rd_entryhi();
245                         if ((hi & TLBHI_ASID_MASK) == asid && (hi < end_hi ||
246                             end == 0)) {
247                                 /*
248                                  * If "hi" is a large page that spans
249                                  * "start_hi", then it must be invalidated.
250                                  */
251                                 hi_pagemask = mips_rd_pagemask();
252                                 if (hi >= (start_hi & ~(hi_pagemask <<
253                                     TLBMASK_SHIFT)))
254                                         tlb_invalidate_one(i);
255                         }
256                 }
257         }
258
259         mips_wr_entryhi(save_asid);
260         intr_restore(s);
261 }
262
263 /* XXX Only if DDB?  */
264 void
265 tlb_save(void)
266 {
267         unsigned i, cpu;
268
269         cpu = PCPU_GET(cpuid);
270
271         tlb_state[cpu].wired = mips_rd_wired();
272         for (i = 0; i < num_tlbentries; i++) {
273                 mips_wr_index(i);
274                 tlb_read();
275
276                 tlb_state[cpu].entry[i].entryhi = mips_rd_entryhi();
277                 tlb_state[cpu].entry[i].entrylo0 = mips_rd_entrylo0();
278                 tlb_state[cpu].entry[i].entrylo1 = mips_rd_entrylo1();
279         }
280 }
281
282 void
283 tlb_update(struct pmap *pmap, vm_offset_t va, pt_entry_t pte)
284 {
285         register_t asid;
286         register_t s;
287         int i;
288
289         va &= ~PAGE_MASK;
290         pte &= ~TLBLO_SWBITS_MASK;
291
292         s = intr_disable();
293         asid = mips_rd_entryhi() & TLBHI_ASID_MASK;
294
295         mips_wr_pagemask(0);
296         mips_wr_entryhi(TLBHI_ENTRY(va, pmap_asid(pmap)));
297         tlb_probe();
298         i = mips_rd_index();
299         if (i >= 0) {
300                 tlb_read();
301
302                 if ((va & PAGE_SIZE) == 0) {
303                         mips_wr_entrylo0(pte);
304                 } else {
305                         mips_wr_entrylo1(pte);
306                 }
307                 tlb_write_indexed();
308         }
309
310         mips_wr_entryhi(asid);
311         intr_restore(s);
312 }
313
314 static void
315 tlb_invalidate_one(unsigned i)
316 {
317         /* XXX an invalid ASID? */
318         mips_wr_entryhi(TLBHI_ENTRY(MIPS_KSEG0_START + (2 * i * PAGE_SIZE), 0));
319         mips_wr_entrylo0(0);
320         mips_wr_entrylo1(0);
321         mips_wr_pagemask(0);
322         mips_wr_index(i);
323         tlb_write_indexed();
324 }
325
326 #ifdef DDB
327 #include <ddb/ddb.h>
328
329 DB_SHOW_COMMAND(tlb, ddb_dump_tlb)
330 {
331         register_t ehi, elo0, elo1;
332         unsigned i, cpu;
333
334         /*
335          * XXX
336          * The worst conversion from hex to decimal ever.
337          */
338         if (have_addr)
339                 cpu = ((addr >> 4) % 16) * 10 + (addr % 16);
340         else
341                 cpu = PCPU_GET(cpuid);
342
343         if (cpu < 0 || cpu >= mp_ncpus) {
344                 db_printf("Invalid CPU %u\n", cpu);
345                 return;
346         }
347
348         if (cpu == PCPU_GET(cpuid))
349                 tlb_save();
350
351         db_printf("Beginning TLB dump for CPU %u...\n", cpu);
352         for (i = 0; i < num_tlbentries; i++) {
353                 if (i == tlb_state[cpu].wired) {
354                         if (i != 0)
355                                 db_printf("^^^ WIRED ENTRIES ^^^\n");
356                         else
357                                 db_printf("(No wired entries.)\n");
358                 }
359
360                 /* XXX PageMask.  */
361                 ehi = tlb_state[cpu].entry[i].entryhi;
362                 elo0 = tlb_state[cpu].entry[i].entrylo0;
363                 elo1 = tlb_state[cpu].entry[i].entrylo1;
364
365                 if (elo0 == 0 && elo1 == 0)
366                         continue;
367
368                 db_printf("#%u\t=> %jx\n", i, (intmax_t)ehi);
369                 db_printf(" Lo0\t%jx\t(%#jx)\n", (intmax_t)elo0, (intmax_t)TLBLO_PTE_TO_PA(elo0));
370                 db_printf(" Lo1\t%jx\t(%#jx)\n", (intmax_t)elo1, (intmax_t)TLBLO_PTE_TO_PA(elo1));
371         }
372         db_printf("Finished.\n");
373 }
374 #endif