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