81cf6e2355974f99899692c86da1a3af7f80c2fe
[dragonfly.git] / sys / platform / vkernel64 / platform / pmap_inval.c
1 /*
2  * Copyright (c) 2003,2004 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Matthew Dillon <dillon@backplane.com>
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  * 3. Neither the name of The DragonFly Project nor the names of its
18  *    contributors may be used to endorse or promote products derived
19  *    from this software without specific, prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
25  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  *
34  * $DragonFly: src/sys/platform/vkernel/platform/pmap_inval.c,v 1.4 2007/07/02 02:22:58 dillon Exp $
35  */
36
37 /*
38  * pmap invalidation support code.  Certain hardware requirements must
39  * be dealt with when manipulating page table entries and page directory
40  * entries within a pmap.  In particular, we cannot safely manipulate
41  * page tables which are in active use by another cpu (even if it is
42  * running in userland) for two reasons: First, TLB writebacks will
43  * race against our own modifications and tests.  Second, even if we
44  * were to use bus-locked instruction we can still screw up the
45  * target cpu's instruction pipeline due to Intel cpu errata.
46  *
47  * For our virtual page tables, the real kernel will handle SMP interactions
48  * with pmaps that may be active on other cpus.  Even so, we have to be
49  * careful about bit setting races particularly when we are trying to clean
50  * a page and test the modified bit to avoid races where the modified bit
51  * might get set after our poll but before we clear the field.
52  */
53 #include <sys/param.h>
54 #include <sys/systm.h>
55 #include <sys/kernel.h>
56 #include <sys/proc.h>
57 #include <sys/vmmeter.h>
58 #include <sys/thread2.h>
59 #include <sys/cdefs.h>
60 #include <sys/mman.h>
61 #include <sys/vmspace.h>
62 #include <sys/vmm.h>
63
64 #include <vm/vm.h>
65 #include <vm/pmap.h>
66 #include <vm/vm_object.h>
67
68 #include <machine/cputypes.h>
69 #include <machine/md_var.h>
70 #include <machine/specialreg.h>
71 #include <machine/smp.h>
72 #include <machine/globaldata.h>
73 #include <machine/pmap.h>
74 #include <machine/pmap_inval.h>
75
76 #include <unistd.h>
77 #include <pthread.h>
78
79 extern int vmm_enabled;
80
81 static __inline
82 void
83 vmm_cpu_invltlb(void)
84 {
85         /* For VMM mode forces vmmexit/resume */
86         uint64_t rax = -1;
87         __asm __volatile("syscall;"
88                         :
89                         : "a" (rax)
90                         :);
91 }
92
93 /*
94  * Invalidate va in the TLB on the current cpu
95  */
96 static __inline
97 void
98 pmap_inval_cpu(struct pmap *pmap, vm_offset_t va, size_t bytes)
99 {
100         if (pmap == &kernel_pmap) {
101                 madvise((void *)va, bytes, MADV_INVAL);
102         } else {
103                 vmspace_mcontrol(pmap, (void *)va, bytes, MADV_INVAL, 0);
104         }
105 }
106
107 /*
108  * This is a bit of a mess because we don't know what virtual cpus are
109  * mapped to real cpus.  Basically try to optimize the degenerate cases
110  * (primarily related to user processes with only one thread or only one
111  * running thread), and shunt all the rest to the host cpu.  The host cpu
112  * will invalidate all real cpu's the vkernel is running on.
113  *
114  * This can't optimize situations where a pmap is only mapped to some of
115  * the virtual cpus, though shunting to the real host will still be faster
116  * if the virtual kernel processes are running on fewer real-host cpus.
117  * (And probably will be faster anyway since there's no round-trip signaling
118  * overhead).
119  *
120  * NOTE: The critical section protects against preemption while the pmap
121  *       is locked, which could otherwise result in a deadlock.
122  */
123 static __inline
124 void
125 guest_sync_addr(struct pmap *pmap,
126                 volatile vpte_t *dst_ptep, volatile vpte_t *src_ptep)
127 {
128         globaldata_t gd = mycpu;
129         cpulock_t olock;
130         cpulock_t nlock;
131
132         /*
133          * Lock the pmap
134          */
135         crit_enter();
136         for (;;) {
137                 olock = pmap->pm_active_lock;
138                 cpu_ccfence();
139                 if ((olock & CPULOCK_EXCL) == 0) {
140                         nlock = olock | CPULOCK_EXCL;
141                         if (atomic_cmpset_int(&pmap->pm_active_lock,
142                                               olock, nlock)) {
143                                 break;
144                         }
145                 }
146                 cpu_pause();
147                 lwkt_process_ipiq();
148                 pthread_yield();
149         }
150
151         /*
152          * Update the pte and synchronize with other cpus.  If we can update
153          * it trivially, do so.
154          */
155         if (pmap->pm_active == 0 ||
156             pmap->pm_active == gd->gd_cpumask) {
157                 *dst_ptep = *src_ptep;
158                 vmm_cpu_invltlb();
159         } else {
160                 vmm_guest_sync_addr(__DEVOLATILE(void *, dst_ptep),
161                                     __DEVOLATILE(void *, src_ptep));
162         }
163
164         /*
165          * Unlock the pmap
166          */
167         atomic_clear_int(&pmap->pm_active_lock, CPULOCK_EXCL);
168         crit_exit();
169 }
170
171 /*
172  * Invalidate a pte in a pmap and synchronize with target cpus
173  * as required.  Throw away the modified and access bits.  Use
174  * pmap_clean_pte() to do the same thing but also get an interlocked
175  * modified/access status.
176  *
177  * Clearing the field first (basically clearing VPTE_V) prevents any
178  * new races from occuring while we invalidate the TLB (i.e. the pmap
179  * on the real cpu), then clear it again to clean out any race that
180  * might have occured before the invalidation completed.
181  */
182 void
183 pmap_inval_pte(volatile vpte_t *ptep, struct pmap *pmap, vm_offset_t va)
184 {
185         vpte_t pte;
186
187         if (vmm_enabled == 0) {
188                 *ptep = 0;
189                 pmap_inval_cpu(pmap, va, PAGE_SIZE);
190         } else {
191                 pte = 0;
192                 guest_sync_addr(pmap, ptep, &pte);
193         }
194 }
195
196 /*
197  * Same as pmap_inval_pte() but only synchronize with the current
198  * cpu.  For the moment its the same as the non-quick version.
199  */
200 void
201 pmap_inval_pte_quick(volatile vpte_t *ptep, struct pmap *pmap, vm_offset_t va)
202 {
203         *ptep = 0;
204         if (vmm_enabled)
205                 vmm_cpu_invltlb();
206         else
207                 pmap_inval_cpu(pmap, va, PAGE_SIZE);
208 }
209
210 /*
211  * Invalidating page directory entries requires some additional
212  * sophistication.  The cachemask must be cleared so the kernel
213  * resynchronizes its temporary page table mappings cache.
214  */
215 void
216 pmap_inval_pde(volatile vpte_t *ptep, struct pmap *pmap, vm_offset_t va)
217 {
218         vpte_t pte;
219
220         if (vmm_enabled == 0) {
221                 *ptep = 0;
222                 pmap_inval_cpu(pmap, va, SEG_SIZE);
223         } else if ((pmap->pm_active & mycpu->gd_other_cpus) == 0) {
224                 *ptep = 0;
225                 vmm_cpu_invltlb();
226         } else {
227                 pte = 0;
228                 guest_sync_addr(pmap, ptep, &pte);
229         }
230 }
231
232 void
233 pmap_inval_pde_quick(volatile vpte_t *ptep, struct pmap *pmap, vm_offset_t va)
234 {
235         pmap_inval_pde(ptep, pmap, va);
236 }
237
238 /*
239  * These carefully handle interactions with other cpus and return
240  * the original vpte.  Clearing VPTE_RW prevents us from racing the
241  * setting of VPTE_M, allowing us to invalidate the tlb (the real cpu's
242  * pmap) and get good status for VPTE_M.
243  *
244  * When messing with page directory entries we have to clear the cpu
245  * mask to force a reload of the kernel's page table mapping cache.
246  *
247  * clean: clear VPTE_M and VPTE_RW
248  * setro: clear VPTE_RW
249  * load&clear: clear entire field
250  */
251 #include<stdio.h>
252 vpte_t
253 pmap_clean_pte(volatile vpte_t *ptep, struct pmap *pmap, vm_offset_t va)
254 {
255         vpte_t pte;
256
257         pte = *ptep;
258         if (pte & VPTE_V) {
259                 atomic_clear_long(ptep, VPTE_RW);  /* XXX */
260                 if (vmm_enabled == 0) {
261                         pmap_inval_cpu(pmap, va, PAGE_SIZE);
262                         pte = *ptep;
263                 } else {
264                         guest_sync_addr(pmap, &pte, ptep);
265                 }
266                 atomic_clear_long(ptep, VPTE_RW|VPTE_M);
267         }
268         return(pte);
269 }
270
271 vpte_t
272 pmap_clean_pde(volatile vpte_t *ptep, struct pmap *pmap, vm_offset_t va)
273 {
274         vpte_t pte;
275
276         pte = *ptep;
277         if (pte & VPTE_V) {
278                 atomic_clear_long(ptep, VPTE_RW);
279                 if (vmm_enabled == 0) {
280                         pmap_inval_cpu(pmap, va, SEG_SIZE);
281                         pte = *ptep;
282                 } else {
283                         guest_sync_addr(pmap, &pte, ptep);
284                 }
285                 atomic_clear_long(ptep, VPTE_RW|VPTE_M);
286         }
287         return(pte);
288 }
289
290 /*
291  * This is an odd case and I'm not sure whether it even occurs in normal
292  * operation.  Turn off write access to the page, clean out the tlb
293  * (the real cpu's pmap), and deal with any VPTE_M race that may have
294  * occured.  VPTE_M is not cleared.
295  */
296 vpte_t
297 pmap_setro_pte(volatile vpte_t *ptep, struct pmap *pmap, vm_offset_t va)
298 {
299         vpte_t pte;
300         vpte_t npte;
301
302         pte = *ptep;
303         if (pte & VPTE_V) {
304                 atomic_clear_long(ptep, VPTE_RW);
305                 if (vmm_enabled == 0) {
306                         pmap_inval_cpu(pmap, va, PAGE_SIZE);
307                         pte |= *ptep & VPTE_M;
308                 } else {
309                         guest_sync_addr(pmap, &npte, ptep);
310                         pte |= npte & VPTE_M;
311                 }
312         }
313         return(pte);
314 }
315
316 /*
317  * This is a combination of pmap_inval_pte() and pmap_clean_pte().
318  * Firts prevent races with the 'A' and 'M' bits, then clean out
319  * the tlb (the real cpu's pmap), then incorporate any races that
320  * may have occured in the mean time, and finally zero out the pte.
321  */
322 vpte_t
323 pmap_inval_loadandclear(volatile vpte_t *ptep, struct pmap *pmap,
324                         vm_offset_t va)
325 {
326         vpte_t pte;
327         vpte_t npte;
328
329         pte = *ptep;
330         if (pte & VPTE_V) {
331                 pte = *ptep;
332                 atomic_clear_long(ptep, VPTE_RW);
333                 if (vmm_enabled == 0) {
334                         pmap_inval_cpu(pmap, va, PAGE_SIZE);
335                         pte |= *ptep & (VPTE_A | VPTE_M);
336                 } else {
337                         guest_sync_addr(pmap, &npte, ptep);
338                         pte |= npte & (VPTE_A | VPTE_M);
339                 }
340         }
341         *ptep = 0;
342         return(pte);
343 }
344
345 /*
346  * Synchronize a kvm mapping originally made for the private use on
347  * some other cpu so it can be used on all cpus.
348  *
349  * XXX add MADV_RESYNC to improve performance.
350  *
351  * We don't need to do anything because our pmap_inval_pte_quick()
352  * synchronizes it immediately.
353  */
354 void
355 pmap_kenter_sync(vm_offset_t va __unused)
356 {
357 }
358
359 void
360 cpu_invlpg(void *addr)
361 {
362         if (vmm_enabled)
363                 vmm_cpu_invltlb(); /* For VMM mode forces vmmexit/resume */
364         else
365                 madvise(addr, PAGE_SIZE, MADV_INVAL);
366 }
367
368 void
369 cpu_invltlb(void)
370 {
371         if (vmm_enabled)
372                 vmm_cpu_invltlb(); /* For VMM mode forces vmmexit/resume */
373         else
374                 madvise((void *)KvaStart, KvaEnd - KvaStart, MADV_INVAL);
375 }
376
377 void
378 smp_invltlb(void)
379 {
380         /* XXX must invalidate the tlb on all cpus */
381         /* at the moment pmap_inval_pte_quick */
382         /* do nothing */
383 }