xref: /xnu-11417.140.69/osfmk/tests/pmap_tests.c (revision 43a90889846e00bfb5cf1d255cdc0a701a1e05a4)
1 /*
2  * Copyright (c) 2016 Apple Inc. All rights reserved.
3  *
4  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5  *
6  * This file contains Original Code and/or Modifications of Original Code
7  * as defined in and that are subject to the Apple Public Source License
8  * Version 2.0 (the 'License'). You may not use this file except in
9  * compliance with the License. The rights granted to you under the License
10  * may not be used to create, or enable the creation or redistribution of,
11  * unlawful or unlicensed copies of an Apple operating system, or to
12  * circumvent, violate, or enable the circumvention or violation of, any
13  * terms of an Apple operating system software license agreement.
14  *
15  * Please obtain a copy of the License at
16  * http://www.opensource.apple.com/apsl/ and read it before using this file.
17  *
18  * The Original Code and all software distributed under the License are
19  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23  * Please see the License for the specific language governing rights and
24  * limitations under the License.
25  *
26  * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27  */
28 
29 #include <vm/vm_page_internal.h>
30 #include <vm/pmap.h>
31 #include <kern/ledger.h>
32 #include <kern/thread.h>
33 #if defined(__arm64__)
34 #include <pexpert/arm64/board_config.h>
35 #if CONFIG_SPTM
36 #include <arm64/sptm/pmap/pmap_pt_geometry.h>
37 #else /* CONFIG_SPTM */
38 #include <arm/pmap/pmap_pt_geometry.h>
39 #endif /* CONFIG_SPTM */
40 #endif /* defined(__arm64__) */
41 #include <vm/vm_map_xnu.h>
42 
43 extern void read_random(void* buffer, u_int numBytes);
44 
45 extern ledger_template_t task_ledger_template;
46 
47 extern boolean_t arm_force_fast_fault(ppnum_t, vm_prot_t, int, void*);
48 extern kern_return_t arm_fast_fault(pmap_t, vm_map_address_t, vm_prot_t, bool, bool);
49 
50 kern_return_t test_pmap_enter_disconnect(unsigned int num_loops);
51 kern_return_t test_pmap_compress_remove(unsigned int num_loops);
52 kern_return_t test_pmap_exec_remove(unsigned int num_loops);
53 kern_return_t test_pmap_nesting(unsigned int num_loops);
54 kern_return_t test_pmap_iommu_disconnect(void);
55 kern_return_t test_pmap_extended(void);
56 void test_pmap_call_overhead(unsigned int num_loops);
57 uint64_t test_pmap_page_protect_overhead(unsigned int num_loops, unsigned int num_aliases);
58 #if CONFIG_SPTM
59 kern_return_t test_pmap_huge_pv_list(unsigned int num_loops, unsigned int num_mappings);
60 kern_return_t test_pmap_reentrance(unsigned int num_loops);
61 #endif
62 
63 #define PMAP_TEST_VA (0xDEADULL << PAGE_SHIFT)
64 
65 typedef struct {
66 	pmap_t pmap;
67 	vm_map_address_t va;
68 	processor_t proc;
69 	ppnum_t pn;
70 	volatile boolean_t stop;
71 } pmap_test_thread_args;
72 
73 
74 /**
75  * Helper for creating a new pmap to be used for testing.
76  *
77  * @param flags Flags to pass to pmap_create_options()
78  *
79  * @return The newly-allocated pmap, or NULL if allocation fails.
80  */
81 static pmap_t
pmap_create_wrapper(unsigned int flags)82 pmap_create_wrapper(unsigned int flags)
83 {
84 	pmap_t new_pmap = NULL;
85 	ledger_t ledger;
86 	assert(task_ledger_template != NULL);
87 	if ((ledger = ledger_instantiate(task_ledger_template, LEDGER_CREATE_ACTIVE_ENTRIES)) == NULL) {
88 		return NULL;
89 	}
90 	new_pmap = pmap_create_options(ledger, 0, flags);
91 	ledger_dereference(ledger);
92 	return new_pmap;
93 }
94 
95 /**
96  * Helper for allocating a wired VM page to be used for testing.
97  *
98  * @note The allocated page will be wired with the VM_KERN_MEMORY_PTE tag,
99  *       which will attribute the page to the pmap module.
100  *
101  * @return the newly-allocated vm_page_t, or NULL if allocation fails.
102  */
103 static vm_page_t
pmap_test_alloc_vm_page(void)104 pmap_test_alloc_vm_page(void)
105 {
106 	vm_page_t m = vm_page_grab();
107 	if (m != VM_PAGE_NULL) {
108 		vm_page_lock_queues();
109 		vm_page_wire(m, VM_KERN_MEMORY_PTE, TRUE);
110 		vm_page_unlock_queues();
111 	}
112 	return m;
113 }
114 
115 /**
116  * Helper for freeing a VM page previously allocated by pmap_test_alloc_vm_page().
117  *
118  * @param m The page to free.  This may be NULL, in which case this function will
119  *          do nothing.
120  */
121 static void
pmap_test_free_vm_page(vm_page_t m)122 pmap_test_free_vm_page(vm_page_t m)
123 {
124 	if (m != VM_PAGE_NULL) {
125 		vm_page_lock_queues();
126 		vm_page_free(m);
127 		vm_page_unlock_queues();
128 	}
129 }
130 
131 static void
pmap_disconnect_thread(void * arg,wait_result_t __unused wres)132 pmap_disconnect_thread(void *arg, wait_result_t __unused wres)
133 {
134 	pmap_test_thread_args *args = arg;
135 	do {
136 		pmap_disconnect(args->pn);
137 	} while (!args->stop);
138 	thread_wakeup((event_t)args);
139 }
140 
141 kern_return_t
test_pmap_enter_disconnect(unsigned int num_loops)142 test_pmap_enter_disconnect(unsigned int num_loops)
143 {
144 	kern_return_t kr = KERN_SUCCESS;
145 	thread_t disconnect_thread;
146 	pmap_t new_pmap = pmap_create_wrapper(0);
147 	if (new_pmap == NULL) {
148 		return KERN_FAILURE;
149 	}
150 	vm_page_t m = pmap_test_alloc_vm_page();
151 	if (m == VM_PAGE_NULL) {
152 		pmap_destroy(new_pmap);
153 		return KERN_FAILURE;
154 	}
155 	ppnum_t phys_page = VM_PAGE_GET_PHYS_PAGE(m);
156 	pmap_test_thread_args args = {.pmap = new_pmap, .stop = FALSE, .pn = phys_page};
157 	kern_return_t res = kernel_thread_start_priority(pmap_disconnect_thread,
158 	    &args, thread_kern_get_pri(current_thread()), &disconnect_thread);
159 	if (res) {
160 		pmap_destroy(new_pmap);
161 		pmap_test_free_vm_page(m);
162 		return res;
163 	}
164 	thread_deallocate(disconnect_thread);
165 
166 	while (num_loops-- != 0) {
167 		kr = pmap_enter(new_pmap, PMAP_TEST_VA, phys_page,
168 		    VM_PROT_READ | VM_PROT_WRITE, VM_PROT_NONE, VM_WIMG_USE_DEFAULT, FALSE, PMAP_MAPPING_TYPE_INFER);
169 		assert(kr == KERN_SUCCESS);
170 	}
171 
172 	assert_wait((event_t)&args, THREAD_UNINT);
173 	args.stop = TRUE;
174 	thread_block(THREAD_CONTINUE_NULL);
175 
176 	pmap_remove(new_pmap, PMAP_TEST_VA, PMAP_TEST_VA + PAGE_SIZE);
177 	pmap_test_free_vm_page(m);
178 	pmap_destroy(new_pmap);
179 	return KERN_SUCCESS;
180 }
181 
182 static void
pmap_remove_thread(void * arg,wait_result_t __unused wres)183 pmap_remove_thread(void *arg, wait_result_t __unused wres)
184 {
185 	pmap_test_thread_args *args = arg;
186 	do {
187 		__assert_only kern_return_t kr = pmap_enter_options(args->pmap, args->va, args->pn,
188 		    VM_PROT_READ, VM_PROT_NONE, VM_WIMG_USE_DEFAULT, FALSE, PMAP_OPTIONS_INTERNAL, NULL, PMAP_MAPPING_TYPE_INFER);
189 		assert(kr == KERN_SUCCESS);
190 		pmap_remove(args->pmap, args->va, args->va + PAGE_SIZE);
191 	} while (!args->stop);
192 	thread_wakeup((event_t)args);
193 }
194 
195 /**
196  * Test that a mapping to a physical page can be concurrently removed while
197  * the page is being compressed, without triggering accounting panics.
198  *
199  * @param num_loops The number of test loops to run
200  *
201  * @return KERN_SUCCESS if the test runs to completion, otherwise an
202  *         appropriate error code.
203  */
204 kern_return_t
test_pmap_compress_remove(unsigned int num_loops)205 test_pmap_compress_remove(unsigned int num_loops)
206 {
207 	thread_t remove_thread;
208 	pmap_t new_pmap = pmap_create_wrapper(0);
209 	if (new_pmap == NULL) {
210 		return KERN_FAILURE;
211 	}
212 	vm_page_t m = pmap_test_alloc_vm_page();
213 	if (m == VM_PAGE_NULL) {
214 		pmap_destroy(new_pmap);
215 		return KERN_FAILURE;
216 	}
217 	ppnum_t phys_page = VM_PAGE_GET_PHYS_PAGE(m);
218 	pmap_test_thread_args args = {.pmap = new_pmap, .stop = FALSE, .va = PMAP_TEST_VA, .pn = phys_page};
219 	kern_return_t res = kernel_thread_start_priority(pmap_remove_thread,
220 	    &args, thread_kern_get_pri(current_thread()), &remove_thread);
221 	if (res) {
222 		pmap_destroy(new_pmap);
223 		pmap_test_free_vm_page(m);
224 		return res;
225 	}
226 	thread_deallocate(remove_thread);
227 
228 	while (num_loops-- != 0) {
229 		pmap_disconnect_options(phys_page, PMAP_OPTIONS_COMPRESSOR, NULL);
230 	}
231 
232 	assert_wait((event_t)&args, THREAD_UNINT);
233 	args.stop = TRUE;
234 	thread_block(THREAD_CONTINUE_NULL);
235 
236 	pmap_remove(new_pmap, PMAP_TEST_VA, PMAP_TEST_VA + PAGE_SIZE);
237 	pmap_destroy(new_pmap);
238 	pmap_test_free_vm_page(m);
239 	return KERN_SUCCESS;
240 }
241 
242 
243 kern_return_t
test_pmap_exec_remove(unsigned int num_loops __unused)244 test_pmap_exec_remove(unsigned int num_loops __unused)
245 {
246 	return KERN_NOT_SUPPORTED;
247 }
248 
249 
250 #if defined(__arm64__)
251 
252 static const vm_map_address_t nesting_start = SHARED_REGION_BASE;
253 static const vm_map_address_t nesting_size = 16 * ARM_16K_TT_L2_SIZE;
254 
255 static void
pmap_nest_thread(void * arg,wait_result_t __unused wres)256 pmap_nest_thread(void *arg, wait_result_t __unused wres)
257 {
258 	const pmap_test_thread_args *args = arg;
259 	pmap_t main_pmap = pmap_create_wrapper(0);
260 	kern_return_t kr;
261 
262 	thread_bind(args->proc);
263 	thread_block(THREAD_CONTINUE_NULL);
264 
265 	/**
266 	 * Exercise nesting and unnesting while bound to the specified CPU (if non-NULL).
267 	 * The unnesting size here should match the unnesting size used in the first
268 	 * unnesting step of the main thread, in order to avoid concurrently unnesting
269 	 * beyond that region and violating the checks against over-unnesting performed
270 	 * in the main thread.
271 	 */
272 	if (main_pmap != NULL) {
273 		kr = pmap_nest(main_pmap, args->pmap, nesting_start, nesting_size);
274 		assert(kr == KERN_SUCCESS);
275 
276 		kr = pmap_unnest(main_pmap, nesting_start, nesting_size - ARM_16K_TT_L2_SIZE);
277 		assert(kr == KERN_SUCCESS);
278 	}
279 
280 	thread_bind(PROCESSOR_NULL);
281 	thread_block(THREAD_CONTINUE_NULL);
282 
283 	assert_wait((event_t)(uintptr_t)&(args->stop), THREAD_UNINT);
284 	if (!args->stop) {
285 		thread_block(THREAD_CONTINUE_NULL);
286 	} else {
287 		clear_wait(current_thread(), THREAD_AWAKENED);
288 	}
289 
290 	/* Unnest all remaining mappings so that we can safely destroy our pmap. */
291 	if (main_pmap != NULL) {
292 		kr = pmap_unnest(main_pmap, nesting_start + nesting_size - ARM_16K_TT_L2_SIZE, ARM_16K_TT_L2_SIZE);
293 		assert(kr == KERN_SUCCESS);
294 		pmap_destroy(main_pmap);
295 	}
296 
297 	thread_wakeup((event_t)arg);
298 }
299 
300 /**
301  * Test that pmap_nest() and pmap_unnest() work correctly when executed concurrently from
302  * multiple threads.  Spawn some worker threads at elevated priority and bound to the
303  * same CPU in order to provoke preemption of the nest/unnest operation.
304  *
305  * @param num_loops The number of nest/unnest loops to perform.  This should be kept to
306  *        a small number because each cycle is expensive and may consume a global shared
307  *        region ID.
308  *
309  * @return KERN_SUCCESS if all tests succeed, an appropriate error code otherwise.
310  */
311 kern_return_t
test_pmap_nesting(unsigned int num_loops)312 test_pmap_nesting(unsigned int num_loops)
313 {
314 	kern_return_t kr = KERN_SUCCESS;
315 
316 	vm_page_t m1 = VM_PAGE_NULL, m2 = VM_PAGE_NULL;
317 
318 	m1 = pmap_test_alloc_vm_page();
319 	m2 = pmap_test_alloc_vm_page();
320 	if ((m1 == VM_PAGE_NULL) || (m2 == VM_PAGE_NULL)) {
321 		kr = KERN_FAILURE;
322 		goto test_nesting_cleanup;
323 	}
324 	const ppnum_t pp1 = VM_PAGE_GET_PHYS_PAGE(m1);
325 	const ppnum_t pp2 = VM_PAGE_GET_PHYS_PAGE(m2);
326 	for (unsigned int i = 0; (i < num_loops) && (kr == KERN_SUCCESS); i++) {
327 		pmap_t nested_pmap = pmap_create_wrapper(0);
328 		pmap_t main_pmap = pmap_create_wrapper(0);
329 		if ((nested_pmap == NULL) || (main_pmap == NULL)) {
330 			pmap_destroy(main_pmap);
331 			pmap_destroy(nested_pmap);
332 			kr = KERN_FAILURE;
333 			break;
334 		}
335 		pmap_set_nested(nested_pmap);
336 		for (vm_map_address_t va = nesting_start; va < (nesting_start + nesting_size); va += PAGE_SIZE) {
337 			uint8_t rand;
338 			read_random(&rand, sizeof(rand));
339 			uint8_t rand_mod = rand % 3;
340 			if (rand_mod == 0) {
341 				continue;
342 			}
343 			kr = pmap_enter(nested_pmap, va, (rand_mod == 1) ? pp1 : pp2, VM_PROT_READ,
344 			    VM_PROT_NONE, VM_WIMG_USE_DEFAULT, FALSE, PMAP_MAPPING_TYPE_INFER);
345 			assert(kr == KERN_SUCCESS);
346 		}
347 		kr = pmap_nest(main_pmap, nested_pmap, nesting_start, nesting_size);
348 		assert(kr == KERN_SUCCESS);
349 
350 		/* Validate the initial nest operation produced global mappings within the nested pmap. */
351 		for (vm_map_address_t va = nesting_start; va < (nesting_start + nesting_size); va += PAGE_SIZE) {
352 			pt_entry_t *nested_pte = pmap_pte(nested_pmap, va);
353 			pt_entry_t *main_pte = pmap_pte(main_pmap, va);
354 			if (nested_pte != main_pte) {
355 				panic("%s: nested_pte (%p) is not identical to main_pte (%p) for va 0x%llx",
356 				    __func__, nested_pte, main_pte, (unsigned long long)va);
357 			}
358 			if ((nested_pte != NULL) && (*nested_pte != ARM_PTE_EMPTY) && (*nested_pte & ARM_PTE_NG)) {
359 				panic("%s: nested_pte (%p) is not global for va 0x%llx",
360 				    __func__, nested_pte, (unsigned long long)va);
361 			}
362 		}
363 
364 		/* Now kick off various worker threads to concurrently nest and unnest. */
365 		const processor_t nest_proc = current_processor();
366 		thread_bind(nest_proc);
367 		thread_block(THREAD_CONTINUE_NULL);
368 
369 		/**
370 		 * Avoid clogging the CPUs with high-priority kernel threads on older devices.
371 		 * Testing has shown this may provoke a userspace watchdog timeout.
372 		 */
373 		#define TEST_NEST_THREADS 4
374 		#if TEST_NEST_THREADS >= MAX_CPUS
375 		#undef TEST_NEST_THREADS
376 		#define TEST_NEST_THREADS MAX_CPUS - 1
377 		#endif
378 		thread_t nest_threads[TEST_NEST_THREADS];
379 		kern_return_t thread_krs[TEST_NEST_THREADS];
380 		pmap_test_thread_args args[TEST_NEST_THREADS];
381 		for (unsigned int j = 0; j < (sizeof(nest_threads) / sizeof(nest_threads[0])); j++) {
382 			args[j].pmap = nested_pmap;
383 			args[j].stop = FALSE;
384 			/**
385 			 * Spawn the worker threads at various priorities at the high end of the kernel range,
386 			 * and bind every other thread to the same CPU as this thread to provoke preemption,
387 			 * while also allowing some threads to run concurrently on other CPUs.
388 			 */
389 			args[j].proc = ((j % 2) ? PROCESSOR_NULL : nest_proc);
390 			thread_krs[j] = kernel_thread_start_priority(pmap_nest_thread, &args[j], MAXPRI_KERNEL - (j % 4), &nest_threads[j]);
391 			if (thread_krs[j] == KERN_SUCCESS) {
392 				thread_set_thread_name(nest_threads[j], "pmap_nest_thread");
393 			}
394 		}
395 
396 		/* Unnest the bulk of the nested region and validate that it produced the expected PTE contents. */
397 		kr = pmap_unnest(main_pmap, nesting_start, nesting_size - ARM_16K_TT_L2_SIZE);
398 		assert(kr == KERN_SUCCESS);
399 
400 		for (vm_map_address_t va = nesting_start; va < (nesting_start + nesting_size - ARM_16K_TT_L2_SIZE); va += PAGE_SIZE) {
401 			pt_entry_t *nested_pte = pmap_pte(nested_pmap, va);
402 			pt_entry_t *main_pte = pmap_pte(main_pmap, va);
403 
404 			if (main_pte != NULL) {
405 				panic("%s: main_pte (%p) is not NULL for unnested VA 0x%llx",
406 				    __func__, main_pte, (unsigned long long)va);
407 			}
408 			if ((nested_pte != NULL) && (*nested_pte != ARM_PTE_EMPTY) && !(*nested_pte & ARM_PTE_NG)) {
409 				panic("%s: nested_pte (%p) is global for va 0x%llx following unnest",
410 				    __func__, nested_pte, (unsigned long long)va);
411 			}
412 		}
413 
414 		/* Validate that the prior unnest did not unnest too much. */
415 		for (vm_map_address_t va = nesting_start + nesting_size - ARM_16K_TT_L2_SIZE; va < (nesting_start + nesting_size); va += PAGE_SIZE) {
416 			pt_entry_t *nested_pte = pmap_pte(nested_pmap, va);
417 			pt_entry_t *main_pte = pmap_pte(main_pmap, va);
418 			if (nested_pte != main_pte) {
419 				panic("%s: nested_pte (%p) is not identical to main_pte (%p) for va 0x%llx following adjacent unnest",
420 				    __func__, nested_pte, main_pte, (unsigned long long)va);
421 			}
422 			if ((nested_pte != NULL) && (*nested_pte != ARM_PTE_EMPTY) && (*nested_pte & ARM_PTE_NG)) {
423 				panic("%s: nested_pte (%p) is not global for va 0x%llx following adjacent unnest",
424 				    __func__, nested_pte, (unsigned long long)va);
425 			}
426 		}
427 
428 		/* Now unnest the remainder. */
429 		kr = pmap_unnest(main_pmap, nesting_start + nesting_size - ARM_16K_TT_L2_SIZE, ARM_16K_TT_L2_SIZE);
430 		assert(kr == KERN_SUCCESS);
431 
432 		thread_bind(PROCESSOR_NULL);
433 		thread_block(THREAD_CONTINUE_NULL);
434 
435 		for (vm_map_address_t va = nesting_start + nesting_size - ARM_16K_TT_L2_SIZE; va < (nesting_start + nesting_size); va += PAGE_SIZE) {
436 			pt_entry_t *nested_pte = pmap_pte(nested_pmap, va);
437 			pt_entry_t *main_pte = pmap_pte(main_pmap, va);
438 
439 			if (main_pte != NULL) {
440 				panic("%s: main_pte (%p) is not NULL for unnested VA 0x%llx",
441 				    __func__, main_pte, (unsigned long long)va);
442 			}
443 			if ((nested_pte != NULL) && (*nested_pte != ARM_PTE_EMPTY) && !(*nested_pte & ARM_PTE_NG)) {
444 				panic("%s: nested_pte (%p) is global for va 0x%llx following unnest",
445 				    __func__, nested_pte, (unsigned long long)va);
446 			}
447 		}
448 
449 		for (unsigned int j = 0; j < (sizeof(nest_threads) / sizeof(nest_threads[0])); j++) {
450 			if (thread_krs[j] == KERN_SUCCESS) {
451 				assert_wait((event_t)&args[j], THREAD_UNINT);
452 				args[j].stop = TRUE;
453 				thread_wakeup((event_t)(uintptr_t)&(args[j].stop));
454 				thread_block(THREAD_CONTINUE_NULL);
455 			} else {
456 				kr = thread_krs[j];
457 			}
458 		}
459 		pmap_remove(nested_pmap, nesting_start, nesting_start + nesting_size);
460 		pmap_destroy(main_pmap);
461 		pmap_destroy(nested_pmap);
462 	}
463 
464 test_nesting_cleanup:
465 	pmap_test_free_vm_page(m1);
466 	pmap_test_free_vm_page(m2);
467 
468 	return kr;
469 }
470 
471 #else /* defined(__arm64__) */
472 
473 kern_return_t
test_pmap_nesting(unsigned int num_loops __unused)474 test_pmap_nesting(unsigned int num_loops __unused)
475 {
476 	return KERN_NOT_SUPPORTED;
477 }
478 
479 #endif /* defined(__arm64__) */
480 
481 kern_return_t
test_pmap_iommu_disconnect(void)482 test_pmap_iommu_disconnect(void)
483 {
484 	return KERN_SUCCESS;
485 }
486 
487 
488 kern_return_t
test_pmap_extended(void)489 test_pmap_extended(void)
490 {
491 #if !CONFIG_SPTM /* SPTM TODO: remove this condition once the SPTM supports 4K and stage-2 mappings */
492 #endif /* !CONFIG_SPTM */
493 	return KERN_SUCCESS;
494 }
495 
496 void
test_pmap_call_overhead(unsigned int num_loops __unused)497 test_pmap_call_overhead(unsigned int num_loops __unused)
498 {
499 #if defined(__arm64__)
500 	pmap_t pmap = current_thread()->map->pmap;
501 	for (unsigned int i = 0; i < num_loops; ++i) {
502 		pmap_nop(pmap);
503 	}
504 #endif
505 }
506 
507 uint64_t
test_pmap_page_protect_overhead(unsigned int num_loops __unused,unsigned int num_aliases __unused)508 test_pmap_page_protect_overhead(unsigned int num_loops __unused, unsigned int num_aliases __unused)
509 {
510 	uint64_t duration = 0;
511 #if defined(__arm64__)
512 	pmap_t new_pmap = pmap_create_wrapper(0);
513 	vm_page_t m = pmap_test_alloc_vm_page();
514 	kern_return_t kr = KERN_SUCCESS;
515 
516 	if ((new_pmap == NULL) || (m == VM_PAGE_NULL)) {
517 		goto ppo_cleanup;
518 	}
519 
520 	ppnum_t phys_page = VM_PAGE_GET_PHYS_PAGE(m);
521 
522 	for (unsigned int loop = 0; loop < num_loops; ++loop) {
523 		for (unsigned int alias = 0; alias < num_aliases; ++alias) {
524 			kr = pmap_enter(new_pmap, PMAP_TEST_VA + (PAGE_SIZE * alias), phys_page,
525 			    VM_PROT_READ | VM_PROT_WRITE, VM_PROT_NONE, VM_WIMG_USE_DEFAULT, FALSE, PMAP_MAPPING_TYPE_INFER);
526 			assert(kr == KERN_SUCCESS);
527 		}
528 
529 		uint64_t start_time = mach_absolute_time();
530 
531 		pmap_page_protect_options(phys_page, VM_PROT_READ, 0, NULL);
532 
533 		duration += (mach_absolute_time() - start_time);
534 
535 		pmap_remove(new_pmap, PMAP_TEST_VA, PMAP_TEST_VA + (num_aliases * PAGE_SIZE));
536 	}
537 
538 ppo_cleanup:
539 	pmap_test_free_vm_page(m);
540 	if (new_pmap != NULL) {
541 		pmap_destroy(new_pmap);
542 	}
543 #endif
544 	return duration;
545 }
546 
547 #if CONFIG_SPTM
548 
549 typedef struct {
550 	pmap_test_thread_args args;
551 	unsigned int num_mappings;
552 	volatile unsigned int nthreads;
553 	thread_call_t panic_callout;
554 } pmap_hugepv_test_thread_args;
555 
556 /**
557  * Worker thread that exercises pmap_remove() and pmap_enter() with a huge PV list.
558  * This thread relies on the fact that PV lists are structured with newer PTEs at
559  * the beginning of the list, so it maximizes PV list traversal time by removing
560  * mappings sequentially starting with the beginning VA of the mapping region
561  * (thus the oldest mapping), and then re-entering that removed mapping at the
562  * beginning of the list.
563  *
564  * @param arg Thread argument parameter, actually of type pmap_hugepv_test_thread_args*
565  * @param wres Wait result, currently unused.
566  */
567 static void
hugepv_remove_enter_thread(void * arg,wait_result_t __unused wres)568 hugepv_remove_enter_thread(void *arg, wait_result_t __unused wres)
569 {
570 	unsigned int mapping = 0;
571 	pmap_hugepv_test_thread_args *args = arg;
572 	do {
573 		vm_map_address_t va = args->args.va + ((vm_offset_t)mapping << PAGE_SHIFT);
574 		pmap_remove(args->args.pmap, va, va + PAGE_SIZE);
575 		kern_return_t kr = pmap_enter_options(args->args.pmap, va, args->args.pn,
576 		    VM_PROT_READ | VM_PROT_WRITE, VM_PROT_NONE, VM_WIMG_USE_DEFAULT, FALSE, PMAP_OPTIONS_INTERNAL,
577 		    NULL, PMAP_MAPPING_TYPE_INFER);
578 		assert(kr == KERN_SUCCESS);
579 		if (++mapping == args->num_mappings) {
580 			mapping = 0;
581 		}
582 	} while (!args->args.stop);
583 	/* Ensure the update of nthreads is not speculated ahead of checking the stop flag. */
584 	os_atomic_thread_fence(acquire);
585 	if (os_atomic_dec(&args->nthreads, relaxed) == 0) {
586 		thread_wakeup((event_t)args);
587 	}
588 }
589 
590 /**
591  * Worker thread to exercise fast-fault behavior with a huge PV list.
592  * This thread first removes permissions from all mappings for the page, which
593  * does not actually remove the mappings but rather clears their AF bit.
594  * It then simulates a fast fault on one random mapping in the list, which
595  * also clears the fast-fault state for the first 64  mappings in the list.
596  *
597  * @param arg Thread argument parameter, actually of type pmap_hugepv_test_thread_args*
598  * @param wres Wait result, currently unused.
599  */
600 static void
hugepv_fast_fault_thread(void * arg,wait_result_t __unused wres)601 hugepv_fast_fault_thread(void *arg, wait_result_t __unused wres)
602 {
603 	pmap_hugepv_test_thread_args *args = arg;
604 	do {
605 		boolean_t success = arm_force_fast_fault(args->args.pn, VM_PROT_NONE, 0, NULL);
606 		assert(success);
607 		unsigned int rand;
608 		read_random(&rand, sizeof(rand));
609 		unsigned int mapping = rand % args->num_mappings;
610 		arm_fast_fault(args->args.pmap, args->args.va + ((vm_offset_t)mapping << PAGE_SHIFT), VM_PROT_READ, false, FALSE);
611 	} while (!args->args.stop);
612 	/* Ensure the update of nthreads is not speculated ahead of checking the stop flag. */
613 	os_atomic_thread_fence(acquire);
614 	if (os_atomic_dec(&args->nthreads, relaxed) == 0) {
615 		thread_wakeup((event_t)args);
616 	}
617 }
618 
619 /**
620  * Worker thread for updating cacheability of a physical page with a huge PV list.
621  * This thread simply twiddles all mappings between write-combined and normal (write-back)
622  * cacheability.
623  *
624  * @param arg Thread argument parameter, actually of type pmap_hugepv_test_thread_args*
625  * @param wres Wait result, currently unused.
626  */
627 static void
hugepv_cache_attr_thread(void * arg,wait_result_t __unused wres)628 hugepv_cache_attr_thread(void *arg, wait_result_t __unused wres)
629 {
630 	pmap_hugepv_test_thread_args *args = arg;
631 	do {
632 		pmap_set_cache_attributes(args->args.pn, VM_WIMG_WCOMB);
633 		pmap_set_cache_attributes(args->args.pn, VM_WIMG_DEFAULT);
634 	} while (!args->args.stop);
635 	/* Ensure the update of nthreads is not speculated ahead of checking the stop flag. */
636 	os_atomic_thread_fence(acquire);
637 	if (os_atomic_dec(&args->nthreads, relaxed) == 0) {
638 		thread_wakeup((event_t)args);
639 	}
640 }
641 
642 /**
643  * Helper function for starting the 2.5-minute panic timer to ensure that we
644  * don't get stuck during test teardown.
645  *
646  * @param panic_callout The timer call to use for the panic callout.
647  */
648 static inline void
huge_pv_start_panic_timer(thread_call_t panic_callout)649 huge_pv_start_panic_timer(thread_call_t panic_callout)
650 {
651 	uint64_t deadline;
652 	clock_interval_to_deadline(150, NSEC_PER_SEC, &deadline);
653 	thread_call_enter_delayed(panic_callout, deadline);
654 }
655 
656 /**
657  * Timer callout that executes in case the huge PV test incurs excessive (>= 5min)
658  * runtime, which can happen due to unlucky scheduling of the main thread.  In this
659  * case we simply set the "stop" flag and expect the worker threads to exit gracefully.
660  *
661  * @param param0 The pmap_hugepv_test_thread_args used to control the test, cast
662  *               as thread_call_param_t.
663  * @param param1 Unused argument.
664  */
665 static void
huge_pv_test_timeout(thread_call_param_t param0,__unused thread_call_param_t param1)666 huge_pv_test_timeout(thread_call_param_t param0, __unused thread_call_param_t param1)
667 {
668 	pmap_hugepv_test_thread_args *args = (pmap_hugepv_test_thread_args*)param0;
669 	args->args.stop = TRUE;
670 	huge_pv_start_panic_timer(args->panic_callout);
671 }
672 
673 /**
674  * Timer callout that executes in case the huge PV test was canceled by
675  * huge_pv_test_timeout above, but failed to terminate within 2.5 minutes.
676  * This callout simply panics to allow inspection of the resultant coredump,
677  * as it should never be reached under correct operation.
678  *
679  * @param param0 Unused argument.
680  * @param param1 Unused argument.
681  */
682 static void __attribute__((noreturn))
huge_pv_test_panic(__unused thread_call_param_t param0,__unused thread_call_param_t param1)683 huge_pv_test_panic(__unused thread_call_param_t param0, __unused thread_call_param_t param1)
684 {
685 	panic("%s: test timed out", __func__);
686 }
687 
688 /**
689  * Main test thread for exercising contention on a massive physical-to-virtual
690  * mapping list in the pmap.  This thread creates a large number of mappings
691  * (as requested by the caller) to the same physical page, spawns the above
692  * worker threads to do different operations on that physical page, then while
693  * that is going on it repeatedly calls pmap_page_protect_options() on the page,
694  * for the number of loops specified by the caller.
695  *
696  * @param num_loops Number of iterations to execute in the main thread before
697  *                  stopping the workers.
698  * @param num_mappings The number of alias mappings to create for the same
699  *                     physical page.
700  *
701  * @return KERN_SUCCESS if the test succeeds, KERN_FAILURE if it encounters
702  *         an unexpected setup failure.  Any failed integrity check during
703  *         the actual execution of the worker threads will panic.
704  */
705 kern_return_t
test_pmap_huge_pv_list(unsigned int num_loops,unsigned int num_mappings)706 test_pmap_huge_pv_list(unsigned int num_loops, unsigned int num_mappings)
707 {
708 	kern_return_t kr = KERN_SUCCESS;
709 	thread_t remove_enter_thread, fast_fault_thread, cache_attr_thread;
710 	if ((num_loops == 0) || (num_mappings == 0)) {
711 		/**
712 		 * If num_mappings is 0, we'll get into a case in which the
713 		 * remove_enter_thread leaves a single dangling mapping, triggering
714 		 * a panic when we free the page.  This isn't a valid test
715 		 * configuration anyway.
716 		 */
717 		return KERN_SUCCESS;
718 	}
719 	pmap_t new_pmap = pmap_create_wrapper(0);
720 	if (new_pmap == NULL) {
721 		return KERN_FAILURE;
722 	}
723 	vm_page_t m = pmap_test_alloc_vm_page();
724 	if (m == VM_PAGE_NULL) {
725 		pmap_destroy(new_pmap);
726 		return KERN_FAILURE;
727 	}
728 
729 	ppnum_t phys_page = VM_PAGE_GET_PHYS_PAGE(m);
730 
731 	for (unsigned int mapping = 0; mapping < num_mappings; ++mapping) {
732 		kr = pmap_enter(new_pmap, PMAP_TEST_VA + ((vm_offset_t)mapping << PAGE_SHIFT), phys_page,
733 		    VM_PROT_READ | VM_PROT_WRITE, VM_PROT_NONE, VM_WIMG_USE_DEFAULT, FALSE, PMAP_MAPPING_TYPE_INFER);
734 		assert(kr == KERN_SUCCESS);
735 	}
736 
737 	thread_call_t huge_pv_panic_call = thread_call_allocate(huge_pv_test_panic, NULL);
738 
739 	pmap_hugepv_test_thread_args args = {
740 		.args = {.pmap = new_pmap, .stop = FALSE, .va = PMAP_TEST_VA, .pn = phys_page},
741 		.nthreads = 0, .num_mappings = num_mappings, .panic_callout = huge_pv_panic_call
742 	};
743 
744 	thread_call_t huge_pv_timer_call = thread_call_allocate(huge_pv_test_timeout, &args);
745 
746 	kr = kernel_thread_start_priority(hugepv_remove_enter_thread,
747 	    &args, thread_kern_get_pri(current_thread()), &remove_enter_thread);
748 	if (kr != KERN_SUCCESS) {
749 		goto hugepv_cleanup;
750 	}
751 	++args.nthreads;
752 	thread_deallocate(remove_enter_thread);
753 
754 	kr = kernel_thread_start_priority(hugepv_fast_fault_thread, &args,
755 	    thread_kern_get_pri(current_thread()), &fast_fault_thread);
756 	if (kr != KERN_SUCCESS) {
757 		goto hugepv_cleanup;
758 	}
759 	++args.nthreads;
760 	thread_deallocate(fast_fault_thread);
761 
762 	kr = kernel_thread_start_priority(hugepv_cache_attr_thread, &args,
763 	    thread_kern_get_pri(current_thread()), &cache_attr_thread);
764 	if (kr != KERN_SUCCESS) {
765 		goto hugepv_cleanup;
766 	}
767 	++args.nthreads;
768 	thread_deallocate(cache_attr_thread);
769 
770 	/**
771 	 * Set up a 5 minute timer to gracefully halt the test upon expiry.
772 	 * Ordinarily the test should complete in well less than 5 minutes,
773 	 * but it can run longer and hit the 10 minute BATS timeout if this
774 	 * thread is really unlucky w.r.t. scheduling (which can happen if
775 	 * it is repeatedly preempted and starved by the other threads
776 	 * contending on the PVH lock).
777 	 */
778 	uint64_t deadline;
779 	clock_interval_to_deadline(300, NSEC_PER_SEC, &deadline);
780 	thread_call_enter_delayed(huge_pv_timer_call, deadline);
781 
782 	for (unsigned int i = 0; (i < num_loops) && !args.args.stop; i++) {
783 		pmap_page_protect_options(phys_page, VM_PROT_READ, 0, NULL);
784 		/**
785 		 * Yield briefly to give the other workers a chance to get through
786 		 * more iterations.
787 		 */
788 		__builtin_arm_wfe();
789 	}
790 
791 	pmap_disconnect_options(phys_page, PMAP_OPTIONS_COMPRESSOR, NULL);
792 
793 hugepv_cleanup:
794 	thread_call_cancel_wait(huge_pv_timer_call);
795 	thread_call_free(huge_pv_timer_call);
796 
797 	if (__improbable(args.args.stop)) {
798 		/**
799 		 * If stop is already set, we hit the timeout, so we can't safely block waiting for
800 		 * the workers to terminate as they may already be doing so.  Spin in a WFE loop
801 		 * instead.
802 		 */
803 		while (os_atomic_load_exclusive(&args.nthreads, relaxed) != 0) {
804 			__builtin_arm_wfe();
805 		}
806 		os_atomic_clear_exclusive();
807 	} else if (args.nthreads > 0) {
808 		/* Ensure prior stores to nthreads are visible before the update to args.args.stop. */
809 		os_atomic_thread_fence(release);
810 		huge_pv_start_panic_timer(huge_pv_panic_call);
811 		assert_wait((event_t)&args, THREAD_UNINT);
812 		args.args.stop = TRUE;
813 		thread_block(THREAD_CONTINUE_NULL);
814 		assert(args.nthreads == 0);
815 	}
816 
817 	thread_call_cancel_wait(huge_pv_panic_call);
818 	thread_call_free(huge_pv_panic_call);
819 
820 	if (new_pmap != NULL) {
821 		pmap_remove(new_pmap, PMAP_TEST_VA, PMAP_TEST_VA + ((vm_offset_t)num_mappings << PAGE_SHIFT));
822 	}
823 
824 	pmap_test_free_vm_page(m);
825 	if (new_pmap != NULL) {
826 		pmap_destroy(new_pmap);
827 	}
828 
829 	return kr;
830 }
831 
832 
833 kern_return_t
test_pmap_reentrance(unsigned int num_loops __unused)834 test_pmap_reentrance(unsigned int num_loops __unused)
835 {
836 	return KERN_NOT_SUPPORTED;
837 }
838 
839 
840 #endif /* CONFIG_SPTM */
841