PatchworkOS  19e446b
A non-POSIX operating system.
Loading...
Searching...
No Matches
paging.h
Go to the documentation of this file.
1#pragma once
2
3#include <kernel/cpu/regs.h>
5
6#include <_libstd/PAGE_SIZE.h>
7#include <assert.h>
8#include <stdbool.h>
9#include <stdlib.h>
10#include <string.h>
11
12/**
13 * @addtogroup kernel_mem_paging
14 * @{
15 */
16
17/**
18 * @brief Invalidates a region of pages in the TLB.
19 *
20 * Even if a page table entry is modified, the CPU might still use a cached version of the entry in the TLB. To ensure
21 * our changes are detected we must invalidate this cache using `invlpg` or if many pages are changed, a full TLB flush
22 * by reloading CR3.
23 *
24 * @param addr The starting virtual address of the region.
25 * @param amount The number of pages to invalidate.
26 */
27static inline void tlb_invalidate(void* addr, size_t amount)
28{
29 if (amount == 0)
30 {
31 return;
32 }
33
34 if (amount > 16)
35 {
37 }
38 else
39 {
40 for (uint64_t i = 0; i < amount; i++)
41 {
42 ASM("invlpg (%0)" ::"r"(addr + (i * PAGE_SIZE)) : "memory");
43 }
44 }
45}
46
47/**
48 * @brief Checks if a page table level is empty (all entries are 0).
49 *
50 * Used as a helper for `page_table_clear()`.
51 *
52 * @param pml The page table level to check.
53 * @return true if all entries are raw 0, false otherwise.
54 */
55static inline bool pml_is_empty(pml_t* pml)
56{
57 for (pml_index_t i = 0; i < PML_INDEX_AMOUNT; i++)
58 {
59 if (pml->entries[i].raw != 0)
60 {
61 return false;
62 }
63 }
64 return true;
65}
66
67/**
68 * @brief Allocates and initializes a new page table level.
69 *
70 * @param table The page table.
71 * @param outPml Will be filled with the newly allocated page table level.
72 * @return On success, `0`. On failure, `ERR`.
73 */
74static inline uint64_t pml_new(page_table_t* table, pml_t** outPml)
75{
76 pfn_t pfn;
77 if (table->allocPages(&pfn, 1) == ERR)
78 {
79 return ERR;
80 }
81 pml_t* pml = PFN_TO_VIRT(pfn);
82 memset(pml, 0, PAGE_SIZE);
83 *outPml = pml;
84 return 0;
85}
86
87/**
88 * @brief Recursively frees a page table level, all its children and any owned pages.
89 *
90 * @param table The page table.
91 * @param pml The current page table level to free.
92 * @param level The current level of the page table.
93 */
94static inline void pml_free(page_table_t* table, pml_t* pml, pml_level_t level)
95{
96 if (level < 0)
97 {
98 return;
99 }
100
101 for (pml_index_t i = 0; i < PML_INDEX_AMOUNT; i++)
102 {
103 pml_entry_t* entry = &pml->entries[i];
104 if (!entry->present)
105 {
106 continue;
107 }
108
109 if (level > PML1)
110 {
111 pml_free(table, PFN_TO_VIRT(entry->pfn), level - 1);
112 }
113 else if (entry->owned)
114 {
115 pfn_t pfn = entry->pfn;
116 table->freePages(&pfn, 1);
117 }
118 }
119
120 pfn_t pfn = VIRT_TO_PFN(pml);
121 table->freePages(&pfn, 1);
122}
123
124/**
125 * @brief Initializes a page table.
126 *
127 * @param table The page table to initialize.
128 * @param allocPages The function to use for allocating pages.
129 * @param freePages The function to use for freeing pages.
130 * @return On success, `0`. On failure, `ERR`.
131 */
132static inline uint64_t page_table_init(page_table_t* table, pml_alloc_pages_t allocPages, pml_free_pages_t freePages)
133{
134 table->allocPages = allocPages;
135 table->freePages = freePages;
136 if (pml_new(table, &table->pml4) == ERR)
137 {
138 return ERR;
139 }
140 return 0;
141}
142
143/**
144 * @brief Deinitializes a page table, freeing all allocated pages.
145 *
146 * @param table The page table to deinitialize.
147 */
148static inline void page_table_deinit(page_table_t* table)
149{
150 pml_free(table, table->pml4, PML4);
151}
152
153/**
154 * @brief Loads the page table into the CR3 register if it is not already loaded.
155 *
156 * @param table The page table to load.
157 */
158static inline void page_table_load(page_table_t* table)
159{
160 uint64_t cr3 = PML_ENSURE_LOWER_HALF(table->pml4);
161 if (cr3 != cr3_read())
162 {
163 cr3_write(cr3);
164 }
165}
166
167/**
168 * @brief Retrieves or allocates the next level page table.
169 *
170 * If the entry at the specified index is present, it retrieves the corresponding page table level.
171 * If the entry is not present and the `PML_PRESENT` flag is set in `flags`, it allocates a new page table level, and
172 * initializes it with the provided flags and callback ID. If the entry is not present and the `PML_PRESENT` flag is not
173 * set, it returns `ERR`.
174 *
175 * @param table The page table.
176 * @param current The current page table level.
177 * @param index The index within the current page table level.
178 * @param flags The flags to assign to a newly allocated page table level, if applicable.
179 * @param out Will be filled with the retrieved or newly allocated page table level.
180 * @return On success, `0`. On failure, `ERR`.
181 */
183 pml_t** out)
184{
185 pml_entry_t* entry = &current->entries[index];
186 if (entry->present)
187 {
188 *out = PFN_TO_VIRT(entry->pfn);
189 return 0;
190 }
191
192 if (flags & PML_PRESENT)
193 {
194 pml_t* next;
195 if (pml_new(table, &next) == ERR)
196 {
197 return ERR;
198 }
199 current->entries[index].raw = flags & PML_FLAGS_MASK;
200 current->entries[index].pfn = VIRT_TO_PFN(next);
201 *out = next;
202 return 0;
203 }
204
205 return ERR;
206}
207
208/**
209 * @brief Helper structure for fast traversal of the page table.
210 * @struct page_table_traverse_t
211 */
225
226/**
227 * @brief Create a `page_table_traverse_t` initializer.
228 *
229 * @return A `page_table_traverse_t` initializer.
230 */
231#define PAGE_TABLE_TRAVERSE_CREATE \
232 { \
233 .pml3Valid = false, \
234 .pml2Valid = false, \
235 .pml1Valid = false, \
236 }
237
238/**
239 * @brief Allows for fast traversal of the page table by caching previously accessed layers.
240 *
241 * If the present flag is not set in `flags` then no new levels will be allocated and if non present pages are
242 * encountered the function will return `false`.
243 *
244 * Note that higher level flags are or'd with `PML_WRITE | PML_USER` since only the permissions of a higher level will
245 * apply to lower levels, meaning that the lowest level should be the one with the actual desired permissions.
246 * Additionally, the `PML_GLOBAL` flag is not allowed on the PML3 level.
247 *
248 * @param table The page table.
249 * @param traverse The helper structure used to cache each layer.
250 * @param addr The target virtual address.
251 * @param flags The flags to assigned to newly allocated levels, if the present flag is not set then dont allocate new
252 * levels.
253 * @return On success, `0`. On failure, `ERR`.
254 */
255static inline uint64_t page_table_traverse(page_table_t* table, page_table_traverse_t* traverse, const void* addr,
257{
258 pml_index_t newIdx3 = PML_ADDR_TO_INDEX(addr, PML4);
259 if (!traverse->pml3Valid || traverse->oldIdx3 != newIdx3)
260 {
261 if (page_table_get_pml(table, table->pml4, newIdx3, (flags | PML_WRITE | PML_USER) & ~PML_GLOBAL,
262 &traverse->pml3) == ERR)
263 {
264 return ERR;
265 }
266 traverse->oldIdx3 = newIdx3;
267 traverse->pml2Valid = false; // Invalidate cache for lower levels
268 }
269
270 pml_index_t newIdx2 = PML_ADDR_TO_INDEX(addr, PML3);
271 if (!traverse->pml2Valid || traverse->oldIdx2 != newIdx2)
272 {
273 if (page_table_get_pml(table, traverse->pml3, newIdx2, flags | PML_WRITE | PML_USER, &traverse->pml2) == ERR)
274 {
275 return ERR;
276 }
277 traverse->oldIdx2 = newIdx2;
278 traverse->pml1Valid = false; // Invalidate cache for lower levels
279 }
280
281 pml_index_t newIdx1 = PML_ADDR_TO_INDEX(addr, PML2);
282 if (!traverse->pml1Valid || traverse->oldIdx1 != newIdx1)
283 {
284 if (page_table_get_pml(table, traverse->pml2, newIdx1, flags | PML_WRITE | PML_USER, &traverse->pml1) == ERR)
285 {
286 return ERR;
287 }
288 traverse->oldIdx1 = newIdx1;
289 }
290
291 traverse->entry = &traverse->pml1->entries[PML_ADDR_TO_INDEX(addr, PML1)];
292 return 0;
293}
294
295/**
296 * @brief Retrieves the physical address mapped to a given virtual address.
297 *
298 * @param table The page table.
299 * @param addr The virtual address to look up.
300 * @param out Will be filled with the corresponding physical address on success.
301 * @return On success, `0`. On failure, `ERR`.
302 */
303static inline uint64_t page_table_get_phys_addr(page_table_t* table, void* addr, phys_addr_t* out)
304{
305 size_t offset = ((uintptr_t)addr) % PAGE_SIZE;
306 addr = (void*)ROUND_DOWN(addr, PAGE_SIZE);
307
309
310 if (page_table_traverse(table, &traverse, addr, PML_NONE) == ERR)
311 {
312 return ERR;
313 }
314
315 if (!traverse.entry->present)
316 {
317 return ERR;
318 }
319
320 *out = PFN_TO_PHYS(traverse.entry->pfn) + offset;
321 return 0;
322}
323
324/**
325 * @brief Checks if a range of virtual addresses is completely mapped.
326 *
327 * If any page in the range is not mapped, the function returns `false`.
328 *
329 * @param table The page table.
330 * @param addr The starting virtual address.
331 * @param amount The number of pages to check.
332 * @return `true` if the entire range is mapped, `false` otherwise.
333 */
334static inline bool page_table_is_mapped(page_table_t* table, const void* addr, size_t amount)
335{
337 for (uint64_t i = 0; i < amount; i++)
338 {
339 if (page_table_traverse(table, &traverse, addr + i * PAGE_SIZE, PML_NONE) == ERR)
340 {
341 return false;
342 }
343
344 if (!traverse.entry->present)
345 {
346 return false;
347 }
348 }
349
350 return true;
351}
352
353/**
354 * @brief Checks if a range of virtual addresses is completely unmapped.
355 *
356 * If any page in the range is mapped, the function returns `false`.
357 *
358 * @param table The page table.
359 * @param addr The starting virtual address.
360 * @param amount The number of pages to check.
361 * @return `true` if the entire range is unmapped, `false` otherwise.
362 */
363static inline bool page_table_is_unmapped(page_table_t* table, void* addr, size_t amount)
364{
366
367 for (uint64_t i = 0; i < amount; i++)
368 {
369 if (page_table_traverse(table, &traverse, addr + i * PAGE_SIZE, PML_NONE) == ERR)
370 {
371 continue;
372 }
373
374 if (traverse.entry->present)
375 {
376 return false;
377 }
378 }
379
380 return true;
381}
382
383/**
384 * @brief Maps a range of virtual addresses to physical addresses in the page table.
385 *
386 * If any page in the range is already mapped, the function will fail and return `ERR`.
387 *
388 * @param table The page table.
389 * @param addr The starting virtual address.
390 * @param phys The starting physical address.
391 * @param amount The number of pages to map.
392 * @param flags The flags to set for the mapped pages. Must include `PML_PRESENT`.
393 * @param callbackId The callback ID to associate with the mapped pages or `PML_CALLBACK_NONE`.
394 * @return On success, `0`. On failure, `ERR`.
395 */
396static inline uint64_t page_table_map(page_table_t* table, void* addr, phys_addr_t phys, size_t amount,
398{
399 if (!(flags & PML_PRESENT))
400 {
401 return ERR;
402 }
403
405
406 for (uint64_t i = 0; i < amount; i++)
407 {
408 if (page_table_traverse(table, &traverse, addr, flags) == ERR)
409 {
410 return ERR;
411 }
412
413 if (traverse.entry->present)
414 {
415 return ERR;
416 }
417
418 traverse.entry->raw = flags;
419 traverse.entry->pfn = PHYS_TO_PFN(phys);
420 traverse.entry->lowCallbackId = callbackId & 1;
421 traverse.entry->highCallbackId = callbackId >> 1;
422
423 phys += PAGE_SIZE;
424 addr += PAGE_SIZE;
425 }
426
427 return 0;
428}
429
430/**
431 * @brief Maps an array of physical pages to contiguous virtual addresses in the page table.
432 *
433 * If any page in the range is already mapped, the function will fail and return `ERR`.
434 *
435 * @param table The page table.
436 * @param addr The starting virtual address.
437 * @param pfns Array of page frame numbers to map.
438 * @param amount The number of pages in the array to map.
439 * @param flags The flags to set for the mapped pages. Must include `PML_PRESENT`.
440 * @param callbackId The callback ID to associate with the mapped pages or `PML_CALLBACK_NONE`.
441 * @return On success, `0`. On failure, `ERR`.
442 */
443static inline uint64_t page_table_map_pages(page_table_t* table, void* addr, const pfn_t* pfns, size_t amount,
445{
446 if (!(flags & PML_PRESENT))
447 {
448 return ERR;
449 }
450
452
453 for (uint64_t i = 0; i < amount; i++)
454 {
455 if (page_table_traverse(table, &traverse, addr, flags) == ERR)
456 {
457 return ERR;
458 }
459
460 if (traverse.entry->present)
461 {
462 return ERR;
463 }
464
465 traverse.entry->raw = flags;
466 traverse.entry->pfn = pfns[i];
467 traverse.entry->lowCallbackId = callbackId & 1;
468 traverse.entry->highCallbackId = callbackId >> 1;
469
470 addr = (void*)((uintptr_t)addr + PAGE_SIZE);
471 }
472
473 return 0;
474}
475
476/**
477 * @brief Unmaps a range of virtual addresses from the page table.
478 *
479 * If a page is not currently mapped, it is skipped.
480 *
481 * Will NOT free owned pages, instead it only sets the present flag to 0. This is to help with TLB shootdowns where we
482 * must unmap, wait for all CPUs to acknowledge the unmap, and only then free the pages. Use `page_table_clear()` to
483 * free owned pages separately.
484 *
485 * @param table The page table.
486 * @param addr The starting virtual address.
487 * @param amount The number of pages to unmap.
488 */
489static inline void page_table_unmap(page_table_t* table, void* addr, size_t amount)
490{
492
493 for (uint64_t i = 0; i < amount; i++)
494 {
495 if (page_table_traverse(table, &traverse, addr + i * PAGE_SIZE, PML_NONE) == ERR)
496 {
497 continue;
498 }
499
500 if (!traverse.entry->present)
501 {
502 continue;
503 }
504
505 traverse.entry->present = 0;
506 }
507
508 tlb_invalidate(addr, amount);
509}
510
511/**
512 * @brief Buffer of pages used to batch page frees.
513 * @struct page_table_page_buffer_t
514 */
520
521/**
522 * @brief Pushes a page table level onto the page buffer, freeing the buffer if full.
523 *
524 * Used as a helper for `page_table_clear()`.
525 *
526 * @param table The page table.
527 * @param buffer The page buffer.
528 * @param address The address to push.
529 */
531{
532 buffer->pfns[buffer->amount] = VIRT_TO_PFN(address);
533 buffer->amount++;
534
535 if (buffer->amount >= PML_PAGE_BUFFER_SIZE)
536 {
537 table->freePages(buffer->pfns, buffer->amount);
538 buffer->amount = 0;
539 }
540}
541
542/**
543 * @brief Flushes the page buffer, freeing any remaining pages.
544 *
545 * Used as a helper for `page_table_clear()`.
546 *
547 * @param table The page table.
548 * @param buffer The page buffer.
549 */
551{
552 if (buffer->amount > 0)
553 {
554 table->freePages(buffer->pfns, buffer->amount);
555 buffer->amount = 0;
556 }
557}
558
559/**
560 * @brief Clears any empty page table levels any time a pml1, pml2 or pml3 boundry is crossed.
561 *
562 * Used as a helper for `page_table_clear()`.
563 *
564 * @param table The page table.
565 * @param prevTraverse The previous traverse state.
566 * @param traverse The current traverse state.
567 * @param pageBuffer The page buffer.
568 */
570 page_table_traverse_t* traverse, page_table_page_buffer_t* pageBuffer)
571{
572 if (prevTraverse->pml1Valid && prevTraverse->pml1 != traverse->pml1 && pml_is_empty(prevTraverse->pml1))
573 {
574 page_table_page_buffer_push(table, pageBuffer, prevTraverse->pml1);
575 prevTraverse->pml2->entries[prevTraverse->oldIdx1].raw = 0;
576 if (prevTraverse->pml2Valid && prevTraverse->pml2 != traverse->pml2 && pml_is_empty(prevTraverse->pml2))
577 {
578 page_table_page_buffer_push(table, pageBuffer, prevTraverse->pml2);
579 prevTraverse->pml3->entries[prevTraverse->oldIdx2].raw = 0;
580 if (prevTraverse->pml3Valid && prevTraverse->pml3 != traverse->pml3 && pml_is_empty(prevTraverse->pml3))
581 {
582 page_table_page_buffer_push(table, pageBuffer, prevTraverse->pml3);
583 table->pml4->entries[prevTraverse->oldIdx3].raw = 0;
584 }
585 }
586 }
587}
588
589/**
590 * @brief Clears page table entries in the specified range and frees any owned pages.
591 *
592 * Intended to be used in conjunction with `page_table_unmap()` to first unmap pages and then free any owned pages after
593 * TLB shootdown is complete.
594 *
595 * Any still present or pinned entries will be skipped.
596 *
597 * All unskipped entries will be fully cleared (set to 0).
598 *
599 * @param table The page table.
600 * @param addr The starting virtual address.
601 * @param amount The number of pages to clear.
602 */
603static inline void page_table_clear(page_table_t* table, void* addr, size_t amount)
604{
605 page_table_page_buffer_t pageBuffer = {0};
606
609 for (uint64_t i = 0; i < amount; i++)
610 {
611 page_table_clear_pml1_pml2_pml3(table, &prevTraverse, &traverse, &pageBuffer);
612
613 if (page_table_traverse(table, &traverse, addr + i * PAGE_SIZE, PML_NONE) == ERR)
614 {
615 prevTraverse.pml1Valid = false;
616 prevTraverse.pml2Valid = false;
617 prevTraverse.pml3Valid = false;
618 continue;
619 }
620 prevTraverse = traverse;
621
622 if (traverse.entry->present)
623 {
624 continue;
625 }
626
627 if (traverse.entry->owned)
628 {
629 page_table_page_buffer_push(table, &pageBuffer, PFN_TO_VIRT(traverse.entry->pfn));
630 }
631
632 traverse.entry->raw = 0;
633 }
634
635 page_table_clear_pml1_pml2_pml3(table, &prevTraverse, &traverse, &pageBuffer);
636 page_table_page_buffer_flush(table, &pageBuffer);
637}
638
639/**
640 * @brief Collects the number of pages associated with each callback ID in the specified range.
641 *
642 * @param table The page table.
643 * @param addr The starting virtual address.
644 * @param amount The number of pages to check.
645 * @param callbacks An array of size `PML_MAX_CALLBACK` that will be filled with the occurrences of each callback ID.
646 */
647static inline void page_table_collect_callbacks(page_table_t* table, void* addr, size_t amount, uint64_t* callbacks)
648{
650
651 for (uint64_t i = 0; i < amount; i++)
652 {
653 if (page_table_traverse(table, &traverse, addr + i * PAGE_SIZE, PML_NONE) == ERR)
654 {
655 continue;
656 }
657
658 if (!traverse.entry->present)
659 {
660 continue;
661 }
662
663 pml_callback_id_t callbackId = traverse.entry->lowCallbackId | (traverse.entry->highCallbackId << 1);
664 if (callbackId != PML_CALLBACK_NONE)
665 {
666 callbacks[callbackId]++;
667 }
668 }
669}
670
671/**
672 * @brief Sets the flags for a range of pages in the page table.
673 *
674 * If a page is not currently mapped, it is skipped.
675 *
676 * @param table The page table.
677 * @param addr The starting virtual address.
678 * @param amount The number of pages to update.
679 * @param flags The new flags to set. The `PML_OWNED` flag is preserved.
680 * @return On success, `0`. On failure, `ERR`.
681 */
682static inline uint64_t page_table_set_flags(page_table_t* table, void* addr, size_t amount, pml_flags_t flags)
683{
685
686 for (uint64_t i = 0; i < amount; i++)
687 {
688 if (page_table_traverse(table, &traverse, addr + i * PAGE_SIZE, PML_NONE) == ERR)
689 {
690 continue;
691 }
692
693 if (!traverse.entry->present)
694 {
695 return ERR;
696 }
697
698 if (traverse.entry->owned)
699 {
700 flags |= PML_OWNED;
701 }
702
703 // Bit magic to only update the flags while preserving the address and callback ID.
704 traverse.entry->raw = (traverse.entry->raw & ~PML_FLAGS_MASK) | (flags & PML_FLAGS_MASK);
705 }
706
707 tlb_invalidate(addr, amount);
708 return 0;
709}
710
711/**
712 * @brief Finds the first contiguous unmapped region with the given number of pages within the specified address range.
713 *
714 * Good luck with this function, im like 99% sure it works.
715 *
716 * This function should be `O(r)` in the worse case where `r` is the amount of pages in the address range, note how the
717 * number of pages needed does not affect the complexity. This has the fun affect that the more memory is allocated the
718 * faster this function will run on average.
719 *
720 * @param table The page table.
721 * @param startAddr The start address to begin searching (inclusive).
722 * @param endAddr The end address of the search range (exclusive).
723 * @param amount The number of consecutive unmapped pages needed.
724 * @param alignment The required alignment for the region in bytes.
725 * @param outAddr Will be filled with the start address of the unmapped region if found.
726 * @return On success, `0`. If no suitable region is found, `ERR`.
727 */
728static inline uint64_t page_table_find_unmapped_region(page_table_t* table, void* startAddr, void* endAddr,
729 size_t amount, size_t alignment, void** outAddr)
730{
731 uintptr_t currentAddr = ROUND_DOWN((uintptr_t)startAddr, PAGE_SIZE);
732 uintptr_t end = (uintptr_t)endAddr;
733
734 if (alignment < PAGE_SIZE)
735 {
736 alignment = PAGE_SIZE;
737 }
738
739 if (amount >= (PML3_SIZE / PAGE_SIZE))
740 {
741 while (currentAddr < end)
742 {
743 pml_index_t idx4 = PML_ADDR_TO_INDEX(currentAddr, PML4);
744 pml_index_t idx3 = PML_ADDR_TO_INDEX(currentAddr, PML3);
745
746 pml_entry_t* entry4 = &table->pml4->entries[idx4];
747 if (!entry4->present)
748 {
749 uintptr_t alignedAddr = ROUND_UP(currentAddr, alignment);
750 uintptr_t nextPml4 = ROUND_UP(currentAddr + 1, PML4_SIZE);
751 if (alignedAddr < nextPml4 && alignedAddr < end)
752 {
753 *outAddr = (void*)alignedAddr;
754 return 0;
755 }
756 currentAddr = nextPml4;
757 continue;
758 }
759
760 pml_t* pml3 = PFN_TO_VIRT(entry4->pfn);
761 pml_entry_t* entry3 = &pml3->entries[idx3];
762
763 if (!entry3->present)
764 {
765 uintptr_t alignedAddr = ROUND_UP(currentAddr, alignment);
766 uintptr_t nextPml3 = ROUND_UP(currentAddr + 1, PML3_SIZE);
767 if (alignedAddr < nextPml3 && alignedAddr < end)
768 {
769 *outAddr = (void*)alignedAddr;
770 return 0;
771 }
772 }
773
774 currentAddr = ROUND_UP(currentAddr + 1, PML3_SIZE);
775 }
776 return ERR;
777 }
778
779 if (amount >= (PML2_SIZE / PAGE_SIZE))
780 {
781 while (currentAddr < end)
782 {
783 pml_index_t idx4 = PML_ADDR_TO_INDEX(currentAddr, PML4);
784 pml_entry_t* entry4 = &table->pml4->entries[idx4];
785
786 if (!entry4->present)
787 {
788 uintptr_t alignedAddr = ROUND_UP(currentAddr, alignment);
789 uintptr_t nextPml4 = ROUND_UP(currentAddr + 1, PML4_SIZE);
790 if (alignedAddr < nextPml4 && alignedAddr < end)
791 {
792 *outAddr = (void*)alignedAddr;
793 return 0;
794 }
795 currentAddr = nextPml4;
796 continue;
797 }
798
799 pml_t* pml3 = PFN_TO_VIRT(entry4->pfn);
800 pml_index_t idx3 = PML_ADDR_TO_INDEX(currentAddr, PML3);
801 pml_entry_t* entry3 = &pml3->entries[idx3];
802
803 if (!entry3->present)
804 {
805 uintptr_t alignedAddr = ROUND_UP(currentAddr, alignment);
806 uintptr_t nextPml3 = ROUND_UP(currentAddr + 1, PML3_SIZE);
807 if (alignedAddr < nextPml3 && alignedAddr < end)
808 {
809 *outAddr = (void*)alignedAddr;
810 return 0;
811 }
812 currentAddr = nextPml3;
813 continue;
814 }
815
816 pml_t* pml2 = PFN_TO_VIRT(entry3->pfn);
817 pml_index_t idx2 = PML_ADDR_TO_INDEX(currentAddr, PML2);
818 pml_entry_t* entry2 = &pml2->entries[idx2];
819
820 if (!entry2->present)
821 {
822 uintptr_t alignedAddr = ROUND_UP(currentAddr, alignment);
823 uintptr_t nextPml2 = ROUND_UP(currentAddr + 1, PML2_SIZE);
824 if (alignedAddr < nextPml2 && alignedAddr < end)
825 {
826 *outAddr = (void*)alignedAddr;
827 return 0;
828 }
829 }
830
831 currentAddr = ROUND_UP(currentAddr + 1, PML2_SIZE);
832 }
833 return ERR;
834 }
835
836 uintptr_t regionStart = 0;
837 uint64_t consecutiveUnmapped = 0;
838
839 while (currentAddr < end)
840 {
841 pml_index_t idx4 = PML_ADDR_TO_INDEX(currentAddr, PML4);
842 pml_entry_t* entry4 = &table->pml4->entries[idx4];
843
844 if (!entry4->present)
845 {
846 if (consecutiveUnmapped == 0)
847 {
848 regionStart = currentAddr;
849 }
850
851 uintptr_t skipTo = PML_INDEX_TO_ADDR(idx4 + 1, PML4);
852 uint64_t skippedPages = (MIN(skipTo, end) - currentAddr) / PAGE_SIZE;
853 consecutiveUnmapped += skippedPages;
854
855 if (consecutiveUnmapped >= amount)
856 {
857 *outAddr = (void*)regionStart;
858 return 0;
859 }
860
861 currentAddr = skipTo;
862 continue;
863 }
864
865 pml_t* pml3 = PFN_TO_VIRT(entry4->pfn);
866 pml_index_t idx3 = PML_ADDR_TO_INDEX(currentAddr, PML3);
867 pml_entry_t* entry3 = &pml3->entries[idx3];
868
869 if (!entry3->present)
870 {
871 uintptr_t skipTo = ROUND_UP(currentAddr + 1, PML3_SIZE);
872
873 if (consecutiveUnmapped == 0)
874 {
875 uintptr_t alignedAddr = ROUND_UP(currentAddr, alignment);
876 if (alignedAddr < skipTo && alignedAddr < end)
877 {
878 regionStart = alignedAddr;
879 consecutiveUnmapped = (MIN(skipTo, end) - alignedAddr) / PAGE_SIZE;
880 }
881 }
882 else
883 {
884 uint64_t skippedPages = (MIN(skipTo, end) - currentAddr) / PAGE_SIZE;
885 consecutiveUnmapped += skippedPages;
886 }
887
888 if (consecutiveUnmapped >= amount)
889 {
890 *outAddr = (void*)regionStart;
891 return 0;
892 }
893
894 currentAddr = skipTo;
895 continue;
896 }
897
898 pml_t* pml2 = PFN_TO_VIRT(entry3->pfn);
899 pml_index_t idx2 = PML_ADDR_TO_INDEX(currentAddr, PML2);
900 pml_entry_t* entry2 = &pml2->entries[idx2];
901
902 if (!entry2->present)
903 {
904 uintptr_t skipTo = ROUND_UP(currentAddr + 1, PML2_SIZE);
905
906 if (consecutiveUnmapped == 0)
907 {
908 uintptr_t alignedAddr = ROUND_UP(currentAddr, alignment);
909 if (alignedAddr < skipTo && alignedAddr < end)
910 {
911 regionStart = alignedAddr;
912 consecutiveUnmapped = (MIN(skipTo, end) - alignedAddr) / PAGE_SIZE;
913 }
914 }
915 else
916 {
917 uint64_t skippedPages = (MIN(skipTo, end) - currentAddr) / PAGE_SIZE;
918 consecutiveUnmapped += skippedPages;
919 }
920
921 if (consecutiveUnmapped >= amount)
922 {
923 *outAddr = (void*)regionStart;
924 return 0;
925 }
926
927 currentAddr = skipTo;
928 continue;
929 }
930
931 pml_t* pml1 = PFN_TO_VIRT(entry2->pfn);
932 pml_index_t idx1 = PML_ADDR_TO_INDEX(currentAddr, PML1);
933
934 for (; idx1 < PML_INDEX_AMOUNT && currentAddr < end; idx1++, currentAddr += PAGE_SIZE)
935 {
936 if (!pml1->entries[idx1].present)
937 {
938 if (consecutiveUnmapped == 0)
939 {
940 uintptr_t alignedAddr = ROUND_UP(currentAddr, alignment);
941 if (alignedAddr == currentAddr)
942 {
943 regionStart = currentAddr;
944 consecutiveUnmapped++;
945 }
946 }
947 else
948 {
949 consecutiveUnmapped++;
950 }
951
952 if (consecutiveUnmapped >= amount)
953 {
954 *outAddr = (void*)regionStart;
955 return 0;
956 }
957 }
958 else
959 {
960 consecutiveUnmapped = 0;
961 }
962 }
963 }
964
965 return ERR;
966}
967
968/**
969 * @brief Checks if any page in a range is pinned.
970 *
971 * @param table The page table.
972 * @param addr The starting virtual address.
973 * @param amount The number of pages to check.
974 * @return `true` if any page in the range us pinned, `false` otherwise.
975 */
976static inline bool page_table_is_pinned(page_table_t* table, void* addr, size_t amount)
977{
979 for (uint64_t i = 0; i < amount; i++)
980 {
981 if (page_table_traverse(table, &traverse, addr + i * PAGE_SIZE, PML_NONE) == ERR)
982 {
983 continue;
984 }
985
986 if (!traverse.entry->present)
987 {
988 continue;
989 }
990
991 if (traverse.entry->pinned)
992 {
993 return true;
994 }
995 }
996
997 return false;
998}
999
1000/**
1001 * @brief Counts the number of pages in a range that have all the specified flags set.
1002 *
1003 * Can be used to, for example, check the total amount of pages allocated to a process by counting the pages with the
1004 * `PML_PRESENT | PML_USER | PML_OWNED` flags set.
1005 *
1006 * @param table The page table.
1007 * @param addr The starting virtual address.
1008 * @param amount The number of pages to check.
1009 * @param flags The flags to check for.
1010 * @return The number of pages with the specified flags set.
1011 */
1012static inline uint64_t page_table_count_pages_with_flags(page_table_t* table, void* addr, size_t amount,
1014{
1015 uint64_t count = 0;
1016 while (amount > 0)
1017 {
1019 pml_entry_t* entry4 = &table->pml4->entries[idx4];
1020
1021 if (!entry4->present)
1022 {
1023 uint64_t skipPages = MIN(amount, (PML_INDEX_TO_ADDR(idx4 + 1, PML4) - (uintptr_t)addr) / PAGE_SIZE);
1024 addr = (void*)((uintptr_t)addr + skipPages * PAGE_SIZE);
1025 amount -= skipPages;
1026 continue;
1027 }
1028
1029 pml_t* pml3 = PFN_TO_VIRT(entry4->pfn);
1031 pml_entry_t* entry3 = &pml3->entries[idx3];
1032
1033 if (!entry3->present)
1034 {
1035 uint64_t skipPages = MIN(amount, (PML_INDEX_TO_ADDR(idx3 + 1, PML3) - (uintptr_t)addr) / PAGE_SIZE);
1036 addr = (void*)((uintptr_t)addr + skipPages * PAGE_SIZE);
1037 amount -= skipPages;
1038 continue;
1039 }
1040
1041 pml_t* pml2 = PFN_TO_VIRT(entry3->pfn);
1043 pml_entry_t* entry2 = &pml2->entries[idx2];
1044
1045 if (!entry2->present)
1046 {
1047 uint64_t skipPages = MIN(amount, (PML_INDEX_TO_ADDR(idx2 + 1, PML2) - (uintptr_t)addr) / PAGE_SIZE);
1048 addr = (void*)((uintptr_t)addr + skipPages * PAGE_SIZE);
1049 amount -= skipPages;
1050 continue;
1051 }
1052
1053 pml_t* pml1 = PFN_TO_VIRT(entry2->pfn);
1055
1056 for (; idx1 < PML_INDEX_AMOUNT && amount > 0; idx1++, addr = (void*)((uintptr_t)addr + PAGE_SIZE), amount--)
1057 {
1058 pml_entry_t* entry1 = &pml1->entries[idx1];
1059 if (!entry1->present)
1060 {
1061 continue;
1062 }
1063 if ((entry1->raw & flags) == flags)
1064 {
1065 count++;
1066 }
1067 }
1068 }
1069
1070 return count;
1071}
1072
1073/** @} */
EFI_PHYSICAL_ADDRESS buffer
Definition main.c:237
static uintptr_t address
Mapped virtual address of the HPET registers.
Definition hpet.c:96
#define PFN_TO_PHYS(_pfn)
Convert a PFN to its physical address.
static void page_table_clear_pml1_pml2_pml3(page_table_t *table, page_table_traverse_t *prevTraverse, page_table_traverse_t *traverse, page_table_page_buffer_t *pageBuffer)
Clears any empty page table levels any time a pml1, pml2 or pml3 boundry is crossed.
Definition paging.h:569
uintptr_t phys_addr_t
Physical address type.
pml_level_t
Enums for the different page table levels.
#define VIRT_TO_PFN(_addr)
Convert a identity mapped higher half virtual address to its PFN.
#define PML_FLAGS_MASK
Mask for all pml flags.
static uint64_t page_table_count_pages_with_flags(page_table_t *table, void *addr, size_t amount, pml_flags_t flags)
Counts the number of pages in a range that have all the specified flags set.
Definition paging.h:1012
static void page_table_clear(page_table_t *table, void *addr, size_t amount)
Clears page table entries in the specified range and frees any owned pages.
Definition paging.h:603
static void page_table_collect_callbacks(page_table_t *table, void *addr, size_t amount, uint64_t *callbacks)
Collects the number of pages associated with each callback ID in the specified range.
Definition paging.h:647
static uint64_t page_table_traverse(page_table_t *table, page_table_traverse_t *traverse, const void *addr, pml_flags_t flags)
Allows for fast traversal of the page table by caching previously accessed layers.
Definition paging.h:255
#define PML_INDEX_TO_ADDR(index, level)
Calculates the lowest virtual address that maps to a given index at a specified page table level.
pml_index_t
Indexes into a pml level.
static void page_table_unmap(page_table_t *table, void *addr, size_t amount)
Unmaps a range of virtual addresses from the page table.
Definition paging.h:489
#define PML4_SIZE
Size of the region mapped by a single PML4 entry.
static uint64_t page_table_get_phys_addr(page_table_t *table, void *addr, phys_addr_t *out)
Retrieves the physical address mapped to a given virtual address.
Definition paging.h:303
#define PML_CALLBACK_NONE
Special callback ID that indicates no callback is associated with the page.
static bool page_table_is_unmapped(page_table_t *table, void *addr, size_t amount)
Checks if a range of virtual addresses is completely unmapped.
Definition paging.h:363
#define PML_ADDR_TO_INDEX(addr, level)
Calculates the index into a page table level for a given virtual address.
#define PML_PAGE_BUFFER_SIZE
Size of the page buffer used to batch page allocations and frees.
static void pml_free(page_table_t *table, pml_t *pml, pml_level_t level)
Recursively frees a page table level, all its children and any owned pages.
Definition paging.h:94
static void page_table_page_buffer_push(page_table_t *table, page_table_page_buffer_t *buffer, void *address)
Pushes a page table level onto the page buffer, freeing the buffer if full.
Definition paging.h:530
static uint64_t page_table_map(page_table_t *table, void *addr, phys_addr_t phys, size_t amount, pml_flags_t flags, pml_callback_id_t callbackId)
Maps a range of virtual addresses to physical addresses in the page table.
Definition paging.h:396
static bool pml_is_empty(pml_t *pml)
Checks if a page table level is empty (all entries are 0).
Definition paging.h:55
uint8_t pml_callback_id_t
Callback ID type.
void(* pml_free_pages_t)(pfn_t *, size_t)
Generic page free function type.
static uint64_t page_table_get_pml(page_table_t *table, pml_t *current, pml_index_t index, pml_flags_t flags, pml_t **out)
Retrieves or allocates the next level page table.
Definition paging.h:182
static void page_table_load(page_table_t *table)
Loads the page table into the CR3 register if it is not already loaded.
Definition paging.h:158
#define PML2_SIZE
Size of the region mapped by a single PML2 entry.
#define PAGE_TABLE_TRAVERSE_CREATE
Create a page_table_traverse_t initializer.
Definition paging.h:231
static void tlb_invalidate(void *addr, size_t amount)
Invalidates a region of pages in the TLB.
Definition paging.h:27
static bool page_table_is_pinned(page_table_t *table, void *addr, size_t amount)
Checks if any page in a range is pinned.
Definition paging.h:976
static uint64_t page_table_map_pages(page_table_t *table, void *addr, const pfn_t *pfns, size_t amount, pml_flags_t flags, pml_callback_id_t callbackId)
Maps an array of physical pages to contiguous virtual addresses in the page table.
Definition paging.h:443
static uint64_t page_table_set_flags(page_table_t *table, void *addr, size_t amount, pml_flags_t flags)
Sets the flags for a range of pages in the page table.
Definition paging.h:682
static void page_table_deinit(page_table_t *table)
Deinitializes a page table, freeing all allocated pages.
Definition paging.h:148
#define PHYS_TO_PFN(_addr)
Convert a physical address to its PFN.
static uint64_t pml_new(page_table_t *table, pml_t **outPml)
Allocates and initializes a new page table level.
Definition paging.h:74
uint64_t(* pml_alloc_pages_t)(pfn_t *, size_t)
Generic page allocation function type.
#define PFN_TO_VIRT(_pfn)
Convert a PFN to its identity mapped higher half virtual address.
size_t pfn_t
Page Frame Number type.
static uint64_t page_table_init(page_table_t *table, pml_alloc_pages_t allocPages, pml_free_pages_t freePages)
Initializes a page table.
Definition paging.h:132
static bool page_table_is_mapped(page_table_t *table, const void *addr, size_t amount)
Checks if a range of virtual addresses is completely mapped.
Definition paging.h:334
static uint64_t page_table_find_unmapped_region(page_table_t *table, void *startAddr, void *endAddr, size_t amount, size_t alignment, void **outAddr)
Finds the first contiguous unmapped region with the given number of pages within the specified addres...
Definition paging.h:728
#define PML3_SIZE
Size of the region mapped by a single PML3 entry.
static void page_table_page_buffer_flush(page_table_t *table, page_table_page_buffer_t *buffer)
Flushes the page buffer, freeing any remaining pages.
Definition paging.h:550
#define PML_ENSURE_LOWER_HALF(addr)
Ensures that the given address is in the lower half of the address space.
@ PML3
@ PML1
@ PML4
@ PML2
@ PML_INDEX_AMOUNT
@ PML_USER
@ PML_PRESENT
@ PML_WRITE
@ PML_NONE
@ PML_GLOBAL
@ PML_OWNED
#define ASM(...)
Inline assembly macro.
Definition defs.h:160
#define MIN(x, y)
Definition math.h:18
#define ROUND_DOWN(number, multiple)
Definition math.h:23
#define ROUND_UP(number, multiple)
Definition math.h:21
#define ERR
Integer error value.
Definition ERR.h:17
#define PAGE_SIZE
The size of a memory page in bytes.
Definition PAGE_SIZE.h:8
static uint64_t offset
Definition screen.c:19
static const path_flag_t flags[]
Definition path.c:47
static atomic_long next
Definition main.c:12
static atomic_long count
Definition main.c:11
static void cr3_write(uint64_t value)
Definition regs.h:111
static uint64_t cr3_read(void)
Definition regs.h:104
__UINT64_TYPE__ uint64_t
Definition stdint.h:17
__UINTPTR_TYPE__ uintptr_t
Definition stdint.h:43
_PUBLIC void * memset(void *s, int c, size_t n)
Definition memset.c:4
Buffer of pages used to batch page frees.
Definition paging.h:516
pfn_t pfns[PML_PAGE_BUFFER_SIZE]
Definition paging.h:517
A page table structure.
pml_alloc_pages_t allocPages
pml_free_pages_t freePages
Helper structure for fast traversal of the page table.
Definition paging.h:213
pml_index_t oldIdx3
Definition paging.h:220
pml_entry_t * entry
Definition paging.h:223
pml_index_t oldIdx1
Definition paging.h:222
pml_index_t oldIdx2
Definition paging.h:221
uint64_t pinned
uint64_t highCallbackId
uint64_t owned
pfn_t pfn
The physical frame number (physical address >> 12).
uint64_t raw
uint64_t present
If set the page is present in memory and readable.
uint64_t lowCallbackId
A entry in a page table without a specified address or callback ID.
A page table level.
pml_entry_t entries[PML_INDEX_AMOUNT]