xref: /optee_os/core/mm/pgt_cache.c (revision 021fee0affe5db6f1f713fea1119350a7433baea)
1 // SPDX-License-Identifier: BSD-2-Clause
2 /*
3  * Copyright (c) 2016, 2022 Linaro Limited
4  */
5 
6 #include <assert.h>
7 #include <kernel/mutex.h>
8 #include <kernel/spinlock.h>
9 #include <kernel/tee_misc.h>
10 #include <kernel/user_mode_ctx.h>
11 #include <mm/core_memprot.h>
12 #include <mm/core_mmu.h>
13 #include <mm/pgt_cache.h>
14 #include <mm/tee_pager.h>
15 #include <stdlib.h>
16 #include <trace.h>
17 #include <util.h>
18 
19 /*
20  * With pager enabled we allocate page table from the pager.
21  *
22  * For LPAE each page table is a complete page which is allocated and freed
23  * using the interface provided by the pager.
24  *
25  * For compat v7 page tables there's room for four page table in one page
26  * so we need to keep track of how much of an allocated page is used. When
27  * a page is completely unused it's returned to the pager.
28  *
29  * With pager disabled we have a static allocation of page tables instead.
30  *
31  * In all cases we limit the number of active page tables to
32  * PGT_CACHE_SIZE.  This pool of page tables are shared between all
33  * threads. In case a thread can't allocate the needed number of pager
34  * tables it will release all its current tables and wait for some more to
35  * be freed. A threads allocated tables are freed each time a TA is
36  * unmapped so each thread should be able to allocate the needed tables in
37  * turn if needed.
38  */
39 
40 #if defined(CFG_CORE_PREALLOC_EL0_TBLS) || \
41 	(defined(CFG_WITH_PAGER) && !defined(CFG_WITH_LPAE))
42 struct pgt_parent {
43 	size_t num_used;
44 	struct pgt_cache pgt_cache;
45 #if defined(CFG_CORE_PREALLOC_EL0_TBLS)
46 	tee_mm_entry_t *mm;
47 	SLIST_ENTRY(pgt_parent) link;
48 #endif
49 };
50 #endif
51 
52 #if defined(CFG_CORE_PREALLOC_EL0_TBLS)
53 
54 /*
55  * Pick something large enough that tee_mm_alloc() doesn't have to be
56  * called for each needed translation table.
57  */
58 #define PGT_PARENT_SIZE		(4 * SMALL_PAGE_SIZE)
59 #define PGT_PARENT_TBL_COUNT	(PGT_PARENT_SIZE / PGT_SIZE)
60 
61 SLIST_HEAD(pgt_parent_list, pgt_parent);
62 static struct pgt_parent_list parent_list = SLIST_HEAD_INITIALIZER(parent_list);
63 static unsigned int parent_spinlock = SPINLOCK_UNLOCK;
64 
65 static void free_pgt(struct pgt *pgt)
66 {
67 	struct pgt_parent *parent = NULL;
68 	uint32_t exceptions = 0;
69 
70 	exceptions = cpu_spin_lock_xsave(&parent_spinlock);
71 
72 	assert(pgt && pgt->parent);
73 	parent = pgt->parent;
74 	assert(parent->num_used <= PGT_PARENT_TBL_COUNT &&
75 	       parent->num_used > 0);
76 	if (parent->num_used == PGT_PARENT_TBL_COUNT)
77 		SLIST_INSERT_HEAD(&parent_list, parent, link);
78 	parent->num_used--;
79 
80 	if (!parent->num_used && SLIST_NEXT(SLIST_FIRST(&parent_list), link)) {
81 		/*
82 		 * If this isn't the last pgt_parent with free entries we
83 		 * can free this.
84 		 */
85 		SLIST_REMOVE(&parent_list, parent, pgt_parent, link);
86 		tee_mm_free(parent->mm);
87 		free(parent);
88 	} else {
89 		SLIST_INSERT_HEAD(&parent->pgt_cache, pgt, link);
90 		pgt->vabase = 0;
91 		pgt->populated = false;
92 	}
93 
94 	cpu_spin_unlock_xrestore(&parent_spinlock, exceptions);
95 }
96 
97 static struct pgt_parent *alloc_pgt_parent(void)
98 {
99 	struct pgt_parent *parent = NULL;
100 	struct pgt *pgt = NULL;
101 	uint8_t *tbl = NULL;
102 	size_t sz = 0;
103 	size_t n = 0;
104 
105 	sz = sizeof(*parent) + sizeof(*pgt) * PGT_PARENT_TBL_COUNT;
106 	parent = calloc(1, sz);
107 	if (!parent)
108 		return NULL;
109 	parent->mm = tee_mm_alloc(&tee_mm_sec_ddr, PGT_PARENT_SIZE);
110 	if (!parent->mm) {
111 		free(parent);
112 		return NULL;
113 	}
114 	tbl = phys_to_virt(tee_mm_get_smem(parent->mm), MEM_AREA_TA_RAM,
115 			   PGT_PARENT_SIZE);
116 	assert(tbl); /* "can't fail" */
117 
118 	SLIST_INIT(&parent->pgt_cache);
119 	pgt = (struct pgt *)(parent + 1);
120 	for (n = 0; n < PGT_PARENT_TBL_COUNT; n++) {
121 		pgt[n].parent = parent;
122 		pgt[n].tbl = tbl + n * PGT_SIZE;
123 		SLIST_INSERT_HEAD(&parent->pgt_cache, pgt + n, link);
124 	}
125 
126 	return parent;
127 }
128 
129 static struct pgt *alloc_pgt(vaddr_t vabase)
130 {
131 	struct pgt_parent *parent = NULL;
132 	uint32_t exceptions = 0;
133 	struct pgt *pgt = NULL;
134 
135 	exceptions = cpu_spin_lock_xsave(&parent_spinlock);
136 
137 	parent = SLIST_FIRST(&parent_list);
138 	if (!parent) {
139 		parent = alloc_pgt_parent();
140 		if (!parent)
141 			goto out;
142 
143 		SLIST_INSERT_HEAD(&parent_list, parent, link);
144 	}
145 
146 	pgt = SLIST_FIRST(&parent->pgt_cache);
147 	SLIST_REMOVE_HEAD(&parent->pgt_cache, link);
148 	parent->num_used++;
149 	assert(pgt && parent->num_used <= PGT_PARENT_TBL_COUNT);
150 	if (parent->num_used == PGT_PARENT_TBL_COUNT)
151 		SLIST_REMOVE_HEAD(&parent_list, link);
152 
153 	pgt->vabase = vabase;
154 out:
155 	cpu_spin_unlock_xrestore(&parent_spinlock, exceptions);
156 	return pgt;
157 }
158 
159 static bool pgt_entry_matches(struct pgt *p, vaddr_t begin, vaddr_t last)
160 {
161 	if (!p)
162 		return false;
163 	if (last <= begin)
164 		return false;
165 	return core_is_buffer_inside(p->vabase, CORE_MMU_PGDIR_SIZE, begin,
166 				     last - begin);
167 }
168 
169 void pgt_flush_range(struct user_mode_ctx *uctx, vaddr_t begin, vaddr_t last)
170 {
171 	struct pgt_cache *pgt_cache = &uctx->pgt_cache;
172 	struct pgt *next_p = NULL;
173 	struct pgt *p = NULL;
174 
175 	/*
176 	 * Do the special case where the first element in the list is
177 	 * removed first.
178 	 */
179 	p = SLIST_FIRST(pgt_cache);
180 	while (pgt_entry_matches(p, begin, last)) {
181 		SLIST_REMOVE_HEAD(pgt_cache, link);
182 		free_pgt(p);
183 		p = SLIST_FIRST(pgt_cache);
184 	}
185 
186 	/*
187 	 * p either points to the first element in the list or it's NULL,
188 	 * if NULL the list is empty and we're done.
189 	 */
190 	if (!p)
191 		return;
192 
193 	/*
194 	 * Do the common case where the next element in the list is
195 	 * removed.
196 	 */
197 	while (true) {
198 		next_p = SLIST_NEXT(p, link);
199 		if (!next_p)
200 			break;
201 		if (pgt_entry_matches(next_p, begin, last)) {
202 			SLIST_REMOVE_AFTER(p, link);
203 			free_pgt(next_p);
204 			continue;
205 		}
206 
207 		p = SLIST_NEXT(p, link);
208 	}
209 }
210 
211 void pgt_flush(struct user_mode_ctx *uctx)
212 {
213 	struct pgt_cache *pgt_cache = &uctx->pgt_cache;
214 	struct pgt *p = NULL;
215 
216 	while (true) {
217 		p = SLIST_FIRST(pgt_cache);
218 		if (!p)
219 			break;
220 		SLIST_REMOVE_HEAD(pgt_cache, link);
221 		free_pgt(p);
222 	}
223 }
224 
225 void pgt_clear_range(struct user_mode_ctx *uctx, vaddr_t begin, vaddr_t end)
226 {
227 	struct pgt_cache *pgt_cache = &uctx->pgt_cache;
228 	struct pgt *p = NULL;
229 #ifdef CFG_WITH_LPAE
230 	uint64_t *tbl = NULL;
231 #else
232 	uint32_t *tbl = NULL;
233 #endif
234 	unsigned int idx = 0;
235 	unsigned int n = 0;
236 
237 	SLIST_FOREACH(p, pgt_cache, link) {
238 		vaddr_t b = MAX(p->vabase, begin);
239 		vaddr_t e = MIN(p->vabase + CORE_MMU_PGDIR_SIZE, end);
240 
241 		if (b >= e)
242 			continue;
243 
244 		tbl = p->tbl;
245 		idx = (b - p->vabase) / SMALL_PAGE_SIZE;
246 		n = (e - b) / SMALL_PAGE_SIZE;
247 		memset(tbl + idx, 0, n * sizeof(*tbl));
248 	}
249 }
250 
251 static struct pgt *prune_before_va(struct pgt_cache *pgt_cache, struct pgt *p,
252 				   struct pgt *pp, vaddr_t va)
253 {
254 	while (p && p->vabase < va) {
255 		if (pp) {
256 			assert(p == SLIST_NEXT(pp, link));
257 			SLIST_REMOVE_AFTER(pp, link);
258 			free_pgt(p);
259 			p = SLIST_NEXT(pp, link);
260 		} else {
261 			assert(p == SLIST_FIRST(pgt_cache));
262 			SLIST_REMOVE_HEAD(pgt_cache, link);
263 			free_pgt(p);
264 			p = SLIST_FIRST(pgt_cache);
265 		}
266 	}
267 
268 	return p;
269 }
270 
271 bool pgt_check_avail(struct user_mode_ctx *uctx)
272 {
273 	struct pgt_cache *pgt_cache = &uctx->pgt_cache;
274 	struct vm_info *vm_info = &uctx->vm_info;
275 	struct pgt *p = SLIST_FIRST(pgt_cache);
276 	struct vm_region *r = NULL;
277 	struct pgt *pp = NULL;
278 	vaddr_t va = 0;
279 	bool p_used = false;
280 
281 	/*
282 	 * Prune unused tables. This is normally not needed since
283 	 * pgt_flush_range() does this too, but in the error path of for
284 	 * instance vm_remap() such calls may not be done. So for increased
285 	 * robustness remove all unused translation tables before we may
286 	 * allocate new ones.
287 	 */
288 	TAILQ_FOREACH(r, &vm_info->regions, link) {
289 		for (va = ROUNDDOWN(r->va, CORE_MMU_PGDIR_SIZE);
290 		     va < r->va + r->size; va += CORE_MMU_PGDIR_SIZE) {
291 			if (!p_used)
292 				p = prune_before_va(pgt_cache, p, pp, va);
293 			if (!p)
294 				goto prune_done;
295 
296 			if (p->vabase < va) {
297 				pp = p;
298 				p = SLIST_NEXT(pp, link);
299 				if (!p)
300 					goto prune_done;
301 				p_used = false;
302 			}
303 
304 			if (p->vabase == va)
305 				p_used = true;
306 		}
307 	}
308 prune_done:
309 
310 	p = SLIST_FIRST(pgt_cache);
311 	pp = NULL;
312 	TAILQ_FOREACH(r, &vm_info->regions, link) {
313 		for (va = ROUNDDOWN(r->va, CORE_MMU_PGDIR_SIZE);
314 		     va < r->va + r->size; va += CORE_MMU_PGDIR_SIZE) {
315 			if (p && p->vabase < va) {
316 				pp = p;
317 				p = SLIST_NEXT(pp, link);
318 			}
319 
320 			if (p) {
321 				if (p->vabase == va)
322 					continue;
323 				assert(p->vabase > va);
324 			}
325 
326 			p = alloc_pgt(va);
327 			if (!p)
328 				return false;
329 
330 			if (pp)
331 				SLIST_INSERT_AFTER(pp, p, link);
332 			else
333 				SLIST_INSERT_HEAD(pgt_cache, p, link);
334 		}
335 	}
336 
337 	return true;
338 }
339 #else /* !CFG_CORE_PREALLOC_EL0_TBLS */
340 
341 #define PGT_CACHE_SIZE	ROUNDUP(CFG_PGT_CACHE_ENTRIES, PGT_NUM_PGT_PER_PAGE)
342 
343 #if defined(CFG_WITH_PAGER) && !defined(CFG_WITH_LPAE)
344 static struct pgt_parent pgt_parents[PGT_CACHE_SIZE / PGT_NUM_PGT_PER_PAGE];
345 #else
346 
347 static struct pgt_cache pgt_free_list = SLIST_HEAD_INITIALIZER(pgt_free_list);
348 #endif
349 
350 /*
351  * When a user TA context is temporarily unmapped the used struct pgt's of
352  * the context (page tables holding valid physical pages) are saved in this
353  * cache in the hope that it will remain in the cache when the context is
354  * mapped again.
355  */
356 static struct pgt_cache pgt_cache_list = SLIST_HEAD_INITIALIZER(pgt_cache_list);
357 
358 static struct pgt pgt_entries[PGT_CACHE_SIZE];
359 
360 static struct mutex pgt_mu = MUTEX_INITIALIZER;
361 static struct condvar pgt_cv = CONDVAR_INITIALIZER;
362 
363 #if defined(CFG_WITH_PAGER) && defined(CFG_WITH_LPAE)
364 /*
365  * Simple allocation of translation tables from pager, one translation
366  * table is one page.
367  */
368 void pgt_init(void)
369 {
370 	size_t n;
371 
372 	for (n = 0; n < PGT_CACHE_SIZE; n++) {
373 		struct pgt *p = pgt_entries + n;
374 
375 		p->tbl = tee_pager_alloc(PGT_SIZE);
376 		SLIST_INSERT_HEAD(&pgt_free_list, p, link);
377 	}
378 }
379 #elif defined(CFG_WITH_PAGER) && !defined(CFG_WITH_LPAE)
380 /*
381  * Four translation tables per page -> need to keep track of the page
382  * allocated from the pager.
383  */
384 void pgt_init(void)
385 {
386 	size_t n;
387 	size_t m;
388 
389 	COMPILE_TIME_ASSERT(PGT_CACHE_SIZE % PGT_NUM_PGT_PER_PAGE == 0);
390 	COMPILE_TIME_ASSERT(PGT_SIZE * PGT_NUM_PGT_PER_PAGE == SMALL_PAGE_SIZE);
391 
392 	for (n = 0; n < ARRAY_SIZE(pgt_parents); n++) {
393 		uint8_t *tbl = tee_pager_alloc(SMALL_PAGE_SIZE);
394 
395 		SLIST_INIT(&pgt_parents[n].pgt_cache);
396 		for (m = 0; m < PGT_NUM_PGT_PER_PAGE; m++) {
397 			struct pgt *p = pgt_entries +
398 					n * PGT_NUM_PGT_PER_PAGE + m;
399 
400 			p->tbl = tbl + m * PGT_SIZE;
401 			p->parent = &pgt_parents[n];
402 			SLIST_INSERT_HEAD(&pgt_parents[n].pgt_cache, p, link);
403 		}
404 	}
405 }
406 #else
407 /* Static allocation of translation tables */
408 void pgt_init(void)
409 {
410 	/*
411 	 * We're putting this in .nozi.* instead of .bss because .nozi.* already
412 	 * has a large alignment, while .bss has a small alignment. The current
413 	 * link script is optimized for small alignment in .bss
414 	 */
415 	static uint8_t pgt_tables[PGT_CACHE_SIZE][PGT_SIZE]
416 			__aligned(PGT_SIZE) __section(".nozi.pgt_cache");
417 	size_t n;
418 
419 	for (n = 0; n < ARRAY_SIZE(pgt_tables); n++) {
420 		struct pgt *p = pgt_entries + n;
421 
422 		p->tbl = pgt_tables[n];
423 		SLIST_INSERT_HEAD(&pgt_free_list, p, link);
424 	}
425 }
426 #endif
427 
428 #if defined(CFG_WITH_LPAE) || !defined(CFG_WITH_PAGER)
429 /* Simple allocation of translation tables from pager or static allocation */
430 static struct pgt *pop_from_free_list(void)
431 {
432 	struct pgt *p = SLIST_FIRST(&pgt_free_list);
433 
434 	if (p) {
435 		SLIST_REMOVE_HEAD(&pgt_free_list, link);
436 		memset(p->tbl, 0, PGT_SIZE);
437 		p->populated = false;
438 	}
439 	return p;
440 }
441 
442 static void push_to_free_list(struct pgt *p)
443 {
444 	SLIST_INSERT_HEAD(&pgt_free_list, p, link);
445 #if defined(CFG_WITH_PAGER)
446 	tee_pager_release_phys(p->tbl, PGT_SIZE);
447 #endif
448 }
449 #else
450 /*
451  * Four translation tables per page -> need to keep track of the page
452  * allocated from the pager.
453  */
454 static struct pgt *pop_from_free_list(void)
455 {
456 	size_t n;
457 
458 	for (n = 0; n < ARRAY_SIZE(pgt_parents); n++) {
459 		struct pgt *p = SLIST_FIRST(&pgt_parents[n].pgt_cache);
460 
461 		if (p) {
462 			SLIST_REMOVE_HEAD(&pgt_parents[n].pgt_cache, link);
463 			pgt_parents[n].num_used++;
464 			memset(p->tbl, 0, PGT_SIZE);
465 			p->populated = false;
466 			return p;
467 		}
468 	}
469 	return NULL;
470 }
471 
472 static void push_to_free_list(struct pgt *p)
473 {
474 	SLIST_INSERT_HEAD(&p->parent->pgt_cache, p, link);
475 	assert(p->parent->num_used > 0);
476 	p->parent->num_used--;
477 	if (!p->parent->num_used) {
478 		vaddr_t va = (vaddr_t)p->tbl & ~SMALL_PAGE_MASK;
479 
480 		tee_pager_release_phys((void *)va, SMALL_PAGE_SIZE);
481 	}
482 }
483 #endif
484 
485 static void push_to_cache_list(struct pgt *pgt)
486 {
487 	SLIST_INSERT_HEAD(&pgt_cache_list, pgt, link);
488 }
489 
490 static bool match_pgt(struct pgt *pgt, vaddr_t vabase, void *ctx)
491 {
492 	return pgt->ctx == ctx && pgt->vabase == vabase;
493 }
494 
495 static struct pgt *pop_from_cache_list(vaddr_t vabase, void *ctx)
496 {
497 	struct pgt *pgt;
498 	struct pgt *p;
499 
500 	pgt = SLIST_FIRST(&pgt_cache_list);
501 	if (!pgt)
502 		return NULL;
503 	if (match_pgt(pgt, vabase, ctx)) {
504 		SLIST_REMOVE_HEAD(&pgt_cache_list, link);
505 		return pgt;
506 	}
507 
508 	while (true) {
509 		p = SLIST_NEXT(pgt, link);
510 		if (!p)
511 			break;
512 		if (match_pgt(p, vabase, ctx)) {
513 			SLIST_REMOVE_AFTER(pgt, link);
514 			break;
515 		}
516 		pgt = p;
517 	}
518 	return p;
519 }
520 
521 static uint16_t get_num_used_entries(struct pgt *pgt __maybe_unused)
522 {
523 #ifdef CFG_PAGED_USER_TA
524 	return pgt->num_used_entries;
525 #else
526 	return 0;
527 #endif
528 }
529 
530 static struct pgt *pop_least_used_from_cache_list(void)
531 {
532 	struct pgt *pgt = NULL;
533 	struct pgt *p_prev = NULL;
534 	size_t least_used = 0;
535 	size_t next_used = 0;
536 
537 	pgt = SLIST_FIRST(&pgt_cache_list);
538 	if (!pgt)
539 		return NULL;
540 	least_used = get_num_used_entries(pgt);
541 
542 	while (true) {
543 		if (!SLIST_NEXT(pgt, link))
544 			break;
545 		next_used = get_num_used_entries(SLIST_NEXT(pgt, link));
546 		if (next_used <= least_used) {
547 			p_prev = pgt;
548 			least_used = next_used;
549 		}
550 		pgt = SLIST_NEXT(pgt, link);
551 	}
552 
553 	if (p_prev) {
554 		pgt = SLIST_NEXT(p_prev, link);
555 		SLIST_REMOVE_AFTER(p_prev, link);
556 	} else {
557 		pgt = SLIST_FIRST(&pgt_cache_list);
558 		SLIST_REMOVE_HEAD(&pgt_cache_list, link);
559 	}
560 	return pgt;
561 }
562 
563 static void pgt_free_unlocked(struct pgt_cache *pgt_cache)
564 {
565 	while (!SLIST_EMPTY(pgt_cache)) {
566 		struct pgt *p = SLIST_FIRST(pgt_cache);
567 
568 		SLIST_REMOVE_HEAD(pgt_cache, link);
569 
570 		/*
571 		 * With paging enabled we free all tables which doesn't
572 		 * refer to any paged pages any longer. This reduces the
573 		 * pressure the pool of physical pages.
574 		 */
575 		if (IS_ENABLED(CFG_PAGED_USER_TA) && !get_num_used_entries(p)) {
576 			tee_pager_pgt_save_and_release_entries(p);
577 			p->ctx = NULL;
578 			p->vabase = 0;
579 
580 			push_to_free_list(p);
581 			continue;
582 		}
583 
584 		push_to_cache_list(p);
585 	}
586 }
587 
588 static struct pgt *pop_from_some_list(vaddr_t vabase, void *ctx)
589 {
590 	struct pgt *p = pop_from_cache_list(vabase, ctx);
591 
592 	if (p)
593 		return p;
594 	p = pop_from_free_list();
595 	if (!p) {
596 		p = pop_least_used_from_cache_list();
597 		if (!p)
598 			return NULL;
599 		tee_pager_pgt_save_and_release_entries(p);
600 		memset(p->tbl, 0, PGT_SIZE);
601 		p->populated = false;
602 	}
603 	p->ctx = ctx;
604 	p->vabase = vabase;
605 	return p;
606 }
607 
608 void pgt_flush(struct user_mode_ctx *uctx)
609 {
610 	struct ts_ctx *ctx = uctx->ts_ctx;
611 	struct pgt *pp = NULL;
612 	struct pgt *p = NULL;
613 
614 	mutex_lock(&pgt_mu);
615 
616 	while (true) {
617 		p = SLIST_FIRST(&pgt_cache_list);
618 		if (!p)
619 			goto out;
620 		if (p->ctx != ctx)
621 			break;
622 		SLIST_REMOVE_HEAD(&pgt_cache_list, link);
623 		tee_pager_pgt_save_and_release_entries(p);
624 		p->ctx = NULL;
625 		p->vabase = 0;
626 		push_to_free_list(p);
627 	}
628 
629 	pp = p;
630 	while (true) {
631 		p = SLIST_NEXT(pp, link);
632 		if (!p)
633 			break;
634 		if (p->ctx == ctx) {
635 			SLIST_REMOVE_AFTER(pp, link);
636 			tee_pager_pgt_save_and_release_entries(p);
637 			p->ctx = NULL;
638 			p->vabase = 0;
639 			push_to_free_list(p);
640 		} else {
641 			pp = p;
642 		}
643 	}
644 
645 out:
646 	mutex_unlock(&pgt_mu);
647 }
648 
649 static void flush_pgt_entry(struct pgt *p)
650 {
651 	tee_pager_pgt_save_and_release_entries(p);
652 	p->ctx = NULL;
653 	p->vabase = 0;
654 }
655 
656 static bool pgt_entry_matches(struct pgt *p, void *ctx, vaddr_t begin,
657 			      vaddr_t last)
658 {
659 	if (!p)
660 		return false;
661 	if (p->ctx != ctx)
662 		return false;
663 	if (last <= begin)
664 		return false;
665 	if (!core_is_buffer_inside(p->vabase, CORE_MMU_PGDIR_SIZE, begin,
666 				   last - begin))
667 		return false;
668 
669 	return true;
670 }
671 
672 static void flush_ctx_range_from_list(struct pgt_cache *pgt_cache, void *ctx,
673 				      vaddr_t begin, vaddr_t last)
674 {
675 	struct pgt *p;
676 	struct pgt *next_p;
677 
678 	/*
679 	 * Do the special case where the first element in the list is
680 	 * removed first.
681 	 */
682 	p = SLIST_FIRST(pgt_cache);
683 	while (pgt_entry_matches(p, ctx, begin, last)) {
684 		flush_pgt_entry(p);
685 		SLIST_REMOVE_HEAD(pgt_cache, link);
686 		push_to_free_list(p);
687 		p = SLIST_FIRST(pgt_cache);
688 	}
689 
690 	/*
691 	 * p either points to the first element in the list or it's NULL,
692 	 * if NULL the list is empty and we're done.
693 	 */
694 	if (!p)
695 		return;
696 
697 	/*
698 	 * Do the common case where the next element in the list is
699 	 * removed.
700 	 */
701 	while (true) {
702 		next_p = SLIST_NEXT(p, link);
703 		if (!next_p)
704 			break;
705 		if (pgt_entry_matches(next_p, ctx, begin, last)) {
706 			flush_pgt_entry(next_p);
707 			SLIST_REMOVE_AFTER(p, link);
708 			push_to_free_list(next_p);
709 			continue;
710 		}
711 
712 		p = SLIST_NEXT(p, link);
713 	}
714 }
715 
716 void pgt_flush_range(struct user_mode_ctx *uctx, vaddr_t begin, vaddr_t last)
717 {
718 	struct pgt_cache *pgt_cache = &uctx->pgt_cache;
719 	struct ts_ctx *ctx = uctx->ts_ctx;
720 
721 	mutex_lock(&pgt_mu);
722 
723 	flush_ctx_range_from_list(pgt_cache, ctx, begin, last);
724 	flush_ctx_range_from_list(&pgt_cache_list, ctx, begin, last);
725 
726 	condvar_broadcast(&pgt_cv);
727 	mutex_unlock(&pgt_mu);
728 }
729 
730 static void clear_ctx_range_from_list(struct pgt_cache *pgt_cache,
731 				      void *ctx, vaddr_t begin, vaddr_t end)
732 {
733 	struct pgt *p = NULL;
734 #ifdef CFG_WITH_LPAE
735 	uint64_t *tbl = NULL;
736 #else
737 	uint32_t *tbl = NULL;
738 #endif
739 	unsigned int idx = 0;
740 	unsigned int n = 0;
741 
742 	SLIST_FOREACH(p, pgt_cache, link) {
743 		vaddr_t b = MAX(p->vabase, begin);
744 		vaddr_t e = MIN(p->vabase + CORE_MMU_PGDIR_SIZE, end);
745 
746 		if (p->ctx != ctx)
747 			continue;
748 		if (b >= e)
749 			continue;
750 
751 		tbl = p->tbl;
752 		idx = (b - p->vabase) / SMALL_PAGE_SIZE;
753 		n = (e - b) / SMALL_PAGE_SIZE;
754 		memset(tbl + idx, 0, n * sizeof(*tbl));
755 	}
756 }
757 
758 void pgt_clear_range(struct user_mode_ctx *uctx, vaddr_t begin, vaddr_t end)
759 {
760 	struct pgt_cache *pgt_cache = &uctx->pgt_cache;
761 	struct ts_ctx *ctx = uctx->ts_ctx;
762 
763 	mutex_lock(&pgt_mu);
764 
765 	clear_ctx_range_from_list(pgt_cache, ctx, begin, end);
766 	clear_ctx_range_from_list(&pgt_cache_list, ctx, begin, end);
767 
768 	mutex_unlock(&pgt_mu);
769 }
770 
771 static bool pgt_alloc_unlocked(struct pgt_cache *pgt_cache, struct ts_ctx *ctx,
772 			       struct vm_info *vm_info)
773 {
774 	struct vm_region *r = NULL;
775 	struct pgt *pp = NULL;
776 	struct pgt *p = NULL;
777 	vaddr_t va = 0;
778 
779 	TAILQ_FOREACH(r, &vm_info->regions, link) {
780 		for (va = ROUNDDOWN(r->va, CORE_MMU_PGDIR_SIZE);
781 		     va < r->va + r->size; va += CORE_MMU_PGDIR_SIZE) {
782 			if (p && p->vabase == va)
783 				continue;
784 			p = pop_from_some_list(va, ctx);
785 			if (!p) {
786 				pgt_free_unlocked(pgt_cache);
787 				return false;
788 			}
789 			if (pp)
790 				SLIST_INSERT_AFTER(pp, p, link);
791 			else
792 				SLIST_INSERT_HEAD(pgt_cache, p, link);
793 			pp = p;
794 		}
795 	}
796 
797 	return true;
798 }
799 
800 bool pgt_check_avail(struct user_mode_ctx *uctx)
801 {
802 	struct vm_info *vm_info = &uctx->vm_info;
803 	struct vm_region *r = NULL;
804 	size_t tbl_count = 0;
805 	vaddr_t last_va = 0;
806 	vaddr_t va = 0;
807 
808 	TAILQ_FOREACH(r, &vm_info->regions, link) {
809 		for (va = ROUNDDOWN(r->va, CORE_MMU_PGDIR_SIZE);
810 		     va < r->va + r->size; va += CORE_MMU_PGDIR_SIZE) {
811 			if (va == last_va)
812 				continue;
813 			tbl_count++;
814 			last_va = va;
815 		}
816 	}
817 
818 	return tbl_count <= PGT_CACHE_SIZE;
819 }
820 
821 void pgt_get_all(struct user_mode_ctx *uctx)
822 {
823 	struct pgt_cache *pgt_cache = &uctx->pgt_cache;
824 	struct vm_info *vm_info = &uctx->vm_info;
825 
826 	if (TAILQ_EMPTY(&vm_info->regions))
827 		return;
828 
829 	mutex_lock(&pgt_mu);
830 
831 	pgt_free_unlocked(pgt_cache);
832 	while (!pgt_alloc_unlocked(pgt_cache, uctx->ts_ctx, vm_info)) {
833 		assert(pgt_check_avail(uctx));
834 		DMSG("Waiting for page tables");
835 		condvar_broadcast(&pgt_cv);
836 		condvar_wait(&pgt_cv, &pgt_mu);
837 	}
838 
839 	mutex_unlock(&pgt_mu);
840 }
841 
842 void pgt_put_all(struct user_mode_ctx *uctx)
843 {
844 	struct pgt_cache *pgt_cache = &uctx->pgt_cache;
845 
846 	if (SLIST_EMPTY(pgt_cache))
847 		return;
848 
849 	mutex_lock(&pgt_mu);
850 
851 	pgt_free_unlocked(pgt_cache);
852 
853 	condvar_broadcast(&pgt_cv);
854 	mutex_unlock(&pgt_mu);
855 }
856 
857 struct pgt *pgt_pop_from_cache_list(vaddr_t vabase, struct ts_ctx *ctx)
858 {
859 	struct pgt *pgt = NULL;
860 
861 	mutex_lock(&pgt_mu);
862 	pgt = pop_from_cache_list(vabase, ctx);
863 	mutex_unlock(&pgt_mu);
864 
865 	return pgt;
866 }
867 
868 void pgt_push_to_cache_list(struct pgt *pgt)
869 {
870 	mutex_lock(&pgt_mu);
871 	push_to_cache_list(pgt);
872 	mutex_unlock(&pgt_mu);
873 }
874 
875 #endif /* !CFG_CORE_PREALLOC_EL0_TBLS */
876