summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Qin <richard.qin.x@gmail.com>2026-05-13 16:29:29 +0800
committerRichard Qin <richard.qin.x@gmail.com>2026-05-13 16:29:29 +0800
commitfb4512afbec76e91cfaf8725d2d39a11c3969943 (patch)
tree1b9a72a864ea3c3b3544026eb86f2b8e66973ed5
parent25fa7f331d6772f5b2cfee50c581db1c5aa71a1e (diff)
downloadlume-fb4512afbec76e91cfaf8725d2d39a11c3969943.tar.gz
lume-fb4512afbec76e91cfaf8725d2d39a11c3969943.tar.xz
feat: update PMM self-test to use page terminology and improve clarity
-rw-r--r--na/test/test_pmm.cc248
-rw-r--r--na/test/test_slab.cc12
-rw-r--r--na/test/test_vma.cc150
3 files changed, 267 insertions, 143 deletions
diff --git a/na/test/test_pmm.cc b/na/test/test_pmm.cc
index 948e444..53b2b2a 100644
--- a/na/test/test_pmm.cc
+++ b/na/test/test_pmm.cc
@@ -5,14 +5,14 @@
* Boot Self-Test: Physical Memory Manager (PMM)
*
* Tests:
- * 1. Single frame alloc/free round-trip
- * 2. Frame address alignment (4KB)
- * 3. No duplicate frames from consecutive allocs
- * 4. Free then re-alloc returns the same frame (PCP cache)
- * 5. Multi-frame (order > 0) alloc/free
+ * 1. Single page alloc/free round-trip
+ * 2. Page address alignment (4KB)
+ * 3. No duplicate pages from consecutive allocs
+ * 4. Free then re-alloc returns the same page (PCP cache)
+ * 5. Multi-page (order > 0) alloc/free
* 6. Bulk alloc stress test (alloc many, free all)
* 7. Reference counting (incref/decref)
- * 8. frame_to_pa / pa_to_frame round-trip
+ * 8. page_to_pa / pa_to_page round-trip
*/
#include <lume/selftest.h>
@@ -21,236 +21,236 @@
void selftest_pmm()
{
- st_begin("pmm: single frame alloc/free");
+ st_begin("pmm: single page alloc/free");
{
- Frame* f = pmm_alloc_frame();
- ST_ASSERT(f != nullptr);
- ST_ASSERT(f->state == FrameState::Allocated);
- ST_ASSERT(f->order == 0);
+ Page* page = pmm_alloc_page();
+ ST_ASSERT(page != nullptr);
+ ST_ASSERT(page->state == PageState::Allocated);
+ ST_ASSERT(page->order == 0);
- uint64 pa = frame_to_pa(f).raw;
+ uint64 pa = page_to_pa(page).raw;
ST_ASSERT_EQ(pa % kPageSize, 0ULL); // 4KB aligned
- uint64 va = frame_to_va(f).raw;
+ uint64 va = page_to_va(page).raw;
ST_ASSERT_EQ(va, pa_to_va(phys_addr(pa)).raw);
- pmm_free_frame(f);
+ pmm_free_page(page);
}
st_pass();
- st_begin("pmm: no duplicate frames");
+ st_begin("pmm: no duplicate pages");
{
- Frame* f1 = pmm_alloc_frame();
- Frame* f2 = pmm_alloc_frame();
- ST_ASSERT(f1 != nullptr);
- ST_ASSERT(f2 != nullptr);
- ST_ASSERT_NE(f1, f2);
- ST_ASSERT_NE(frame_to_pa(f1).raw, frame_to_pa(f2).raw);
- pmm_free_frame(f2);
- pmm_free_frame(f1);
+ Page* p1 = pmm_alloc_page();
+ Page* p2 = pmm_alloc_page();
+ ST_ASSERT(p1 != nullptr);
+ ST_ASSERT(p2 != nullptr);
+ ST_ASSERT_NE(p1, p2);
+ ST_ASSERT_NE(page_to_pa(p1).raw, page_to_pa(p2).raw);
+ pmm_free_page(p2);
+ pmm_free_page(p1);
}
st_pass();
st_begin("pmm: free then re-alloc (PCP)");
{
- Frame* f1 = pmm_alloc_frame();
- uint64 pa1 = frame_to_pa(f1).raw;
- pmm_free_frame(f1);
+ Page* p1 = pmm_alloc_page();
+ uint64 pa1 = page_to_pa(p1).raw;
+ pmm_free_page(p1);
- // PCP should return the most recently freed frame
- Frame* f2 = pmm_alloc_frame();
- uint64 pa2 = frame_to_pa(f2).raw;
+ // PCP should return the most recently freed page
+ Page* p2 = pmm_alloc_page();
+ uint64 pa2 = page_to_pa(p2).raw;
ST_ASSERT_EQ(pa1, pa2);
- pmm_free_frame(f2);
+ pmm_free_page(p2);
}
st_pass();
- st_begin("pmm: multi-frame alloc (order 2 = 4 pages)");
+ st_begin("pmm: multi-page alloc (order 2 = 4 pages)");
{
- Frame* f = pmm_alloc_frames(2);
- ST_ASSERT(f != nullptr);
- ST_ASSERT(f->order == 2);
+ Page* page = pmm_alloc_pages(2);
+ ST_ASSERT(page != nullptr);
+ ST_ASSERT(page->order == 2);
- uint64 pa = frame_to_pa(f).raw;
+ uint64 pa = page_to_pa(page).raw;
ST_ASSERT_EQ(pa % (kPageSize * 4), 0ULL); // Naturally aligned
- pmm_free_frames(f, 2);
+ pmm_free_pages(page, 2);
}
st_pass();
- st_begin("pmm: bulk alloc/free stress (256 frames)");
+ st_begin("pmm: bulk alloc/free stress (256 pages)");
{
constexpr int N = 256;
- Frame* frames[N];
+ Page* pages[N];
// Allocate all
for (int i = 0; i < N; i++) {
- frames[i] = pmm_alloc_frame();
- ST_ASSERT(frames[i] != nullptr);
+ pages[i] = pmm_alloc_page();
+ ST_ASSERT(pages[i] != nullptr);
}
// Verify no duplicates (spot check: consecutive pairs)
for (int i = 1; i < N; i++) {
- ST_ASSERT_NE(frames[i], frames[i - 1]);
+ ST_ASSERT_NE(pages[i], pages[i - 1]);
}
// Free all
for (int i = 0; i < N; i++) {
- pmm_free_frame(frames[i]);
+ pmm_free_page(pages[i]);
}
}
st_pass();
- st_begin("pmm: frame_to_pa / pa_to_frame round-trip");
+ st_begin("pmm: page_to_pa / pa_to_page round-trip");
{
- Frame* f = pmm_alloc_frame();
- ST_ASSERT(f != nullptr);
- uint64 pa = frame_to_pa(f).raw;
- Frame* f2 = pa_to_frame(phys_addr(pa));
- ST_ASSERT_EQ(f, f2);
- pmm_free_frame(f);
+ Page* page = pmm_alloc_page();
+ ST_ASSERT(page != nullptr);
+ uint64 pa = page_to_pa(page).raw;
+ Page* page2 = pa_to_page(phys_addr(pa));
+ ST_ASSERT_EQ(page, page2);
+ pmm_free_page(page);
}
st_pass();
st_begin("pmm: refcount incref/decref");
{
- Frame* f = pmm_alloc_frame();
- ST_ASSERT(f != nullptr);
- ST_ASSERT(f->refcount.load() == 1);
+ Page* page = pmm_alloc_page();
+ ST_ASSERT(page != nullptr);
+ ST_ASSERT(page->refcount.load() == 1);
- frame_incref(f);
- ST_ASSERT(f->refcount.load() == 2);
+ page_incref(page);
+ ST_ASSERT(page->refcount.load() == 2);
- frame_decref(f); // 2 -> 1, should NOT free
- ST_ASSERT(f->refcount.load() == 1);
- ST_ASSERT(f->state == FrameState::Allocated);
+ page_decref(page); // 2 -> 1, should NOT free
+ ST_ASSERT(page->refcount.load() == 1);
+ ST_ASSERT(page->state == PageState::Allocated);
- frame_decref(f); // 1 -> 0, should free to pool
- // After decref to 0, frame is returned to pool.
- // We can't safely check f->state because it might be reused.
+ page_decref(page); // 1 -> 0, should free to pool
+ // After decref to 0, page is returned to pool.
+ // We can't safely check page->state because it might be reused.
}
st_pass();
- st_begin("pmm: pmm_alloc_frames natural alignment (order-3)");
+ st_begin("pmm: pmm_alloc_pages natural alignment (order-3)");
{
- Frame* f = pmm_alloc_frames(3);
- ST_ASSERT(f != nullptr);
- uint64 pa = frame_to_pa(f).raw;
+ Page* page = pmm_alloc_pages(3);
+ ST_ASSERT(page != nullptr);
+ uint64 pa = page_to_pa(page).raw;
ST_ASSERT_EQ(pa % (kPageSize * 8), 0ULL);
- pmm_free_frames(f, 3);
+ pmm_free_pages(page, 3);
}
st_pass();
- st_begin("pmm: frame_decref to 0 and reuse (order-0)");
+ st_begin("pmm: page_decref to 0 and reuse (order-0)");
{
- Frame* f = pmm_alloc_frame();
- uint64 pa = frame_to_pa(f).raw;
- frame_decref(f); // drops 1 -> 0, freeing it
+ Page* page = pmm_alloc_page();
+ uint64 pa = page_to_pa(page).raw;
+ page_decref(page); // drops 1 -> 0, freeing it
- // Next alloc from PCP should give back the same frame
- Frame* f2 = pmm_alloc_frame();
- ST_ASSERT_EQ(frame_to_pa(f2).raw, pa);
- pmm_free_frame(f2);
+ // Next alloc from PCP should give back the same page
+ Page* page2 = pmm_alloc_page();
+ ST_ASSERT_EQ(page_to_pa(page2).raw, pa);
+ pmm_free_page(page2);
}
st_pass();
- st_begin("pmm: frame_decref to 0 on multi-page blocks (order-3)");
+ st_begin("pmm: page_decref to 0 on multi-page blocks (order-3)");
{
- Frame* f = pmm_alloc_frames(3);
- uint64 pa = frame_to_pa(f).raw;
- frame_decref(f); // Should internally route to pmm_free_frames(f, 3)
+ Page* page = pmm_alloc_pages(3);
+ uint64 pa = page_to_pa(page).raw;
+ page_decref(page); // Should internally route to pmm_free_pages(page, 3)
// Re-allocate order-3 and see if we get it back (buddy coalescing was respected)
- Frame* f2 = pmm_alloc_frames(3);
- ST_ASSERT_EQ(frame_to_pa(f2).raw, pa);
- pmm_free_frames(f2, 3);
+ Page* page2 = pmm_alloc_pages(3);
+ ST_ASSERT_EQ(page_to_pa(page2).raw, pa);
+ pmm_free_pages(page2, 3);
}
st_pass();
st_begin("pmm: buddy split and block coalescing correctness");
{
// Force split: allocate order-1, free it, allocate two order-0
- Frame* f_ord1 = pmm_alloc_frames(1);
- uint64 pa1 = frame_to_pa(f_ord1).raw;
- pmm_free_frames(f_ord1, 1);
+ Page* page_ord1 = pmm_alloc_pages(1);
+ uint64 pa1 = page_to_pa(page_ord1).raw;
+ pmm_free_pages(page_ord1, 1);
- Frame* f0_a = pmm_alloc_frame();
- Frame* f0_b = pmm_alloc_frame();
+ Page* p0_a = pmm_alloc_page();
+ Page* p0_b = pmm_alloc_page();
// Free them to trigger coalesce
- pmm_free_frame(f0_a);
- pmm_free_frame(f0_b);
+ pmm_free_page(p0_a);
+ pmm_free_page(p0_b);
// Allocate order-1 again to verify successful coalescing
- Frame* f_ord1_again = pmm_alloc_frames(1);
- ST_ASSERT_EQ(frame_to_pa(f_ord1_again).raw, pa1);
- pmm_free_frames(f_ord1_again, 1);
+ Page* page_ord1_again = pmm_alloc_pages(1);
+ ST_ASSERT_EQ(page_to_pa(page_ord1_again).raw, pa1);
+ pmm_free_pages(page_ord1_again, 1);
}
st_pass();
st_begin("pmm: mixed order cross allocation");
{
- Frame* f0 = pmm_alloc_frame();
- Frame* f2 = pmm_alloc_frames(2);
- Frame* f0b = pmm_alloc_frame();
+ Page* p0 = pmm_alloc_page();
+ Page* p2 = pmm_alloc_pages(2);
+ Page* p0b = pmm_alloc_page();
- ST_ASSERT(f0 != nullptr);
- ST_ASSERT(f2 != nullptr);
- ST_ASSERT(f0b != nullptr);
- ST_ASSERT_NE(frame_to_pa(f0).raw, frame_to_pa(f2).raw);
+ ST_ASSERT(p0 != nullptr);
+ ST_ASSERT(p2 != nullptr);
+ ST_ASSERT(p0b != nullptr);
+ ST_ASSERT_NE(page_to_pa(p0).raw, page_to_pa(p2).raw);
- pmm_free_frame(f0);
- pmm_free_frames(f2, 2);
- pmm_free_frame(f0b);
+ pmm_free_page(p0);
+ pmm_free_pages(p2, 2);
+ pmm_free_page(p0b);
}
st_pass();
st_begin("pmm: PCP drain/refill boundaries");
{
// kPcpHighWatermark = 64. Allocating and freeing 65 blocks triggers drain.
- Frame* frames[65];
- for (int i=0; i<65; i++) frames[i] = pmm_alloc_frame();
- for (int i=0; i<65; i++) pmm_free_frame(frames[i]);
+ Page* pages[65];
+ for (int i=0; i<65; i++) pages[i] = pmm_alloc_page();
+ for (int i=0; i<65; i++) pmm_free_page(pages[i]);
// The system shouldn't crash and PCP logic should handle this.
- Frame* verify_f = pmm_alloc_frame();
- ST_ASSERT(verify_f != nullptr);
- pmm_free_frame(verify_f);
+ Page* verify_page = pmm_alloc_page();
+ ST_ASSERT(verify_page != nullptr);
+ pmm_free_page(verify_page);
}
st_pass();
st_begin("pmm: OOM exhaustion stability");
{
- Frame* head = nullptr;
+ Page* head = nullptr;
// First allocate large chunks to be fast
while (true) {
- Frame* f = pmm_alloc_frames(5);
- if (!f) break;
- uint64* ptr = reinterpret_cast<uint64*>(frame_to_va(f).raw);
+ Page* page = pmm_alloc_pages(5);
+ if (!page) break;
+ uint64* ptr = reinterpret_cast<uint64*>(page_to_va(page).raw);
*ptr = reinterpret_cast<uint64>(head);
// encode order in the second word
*(ptr + 1) = 5;
- head = f;
+ head = page;
}
// Then allocate single frames until absolute zero
while (true) {
- Frame* f = pmm_alloc_frame();
- if (!f) break;
- uint64* ptr = reinterpret_cast<uint64*>(frame_to_va(f).raw);
+ Page* page = pmm_alloc_page();
+ if (!page) break;
+ uint64* ptr = reinterpret_cast<uint64*>(page_to_va(page).raw);
*ptr = reinterpret_cast<uint64>(head);
*(ptr + 1) = 0;
- head = f;
+ head = page;
}
// We hit nullptr. System is completely exhausted.
- ST_ASSERT(pmm_alloc_frame() == nullptr);
+ ST_ASSERT(pmm_alloc_page() == nullptr);
// Free everything
while (head) {
- uint64* ptr = reinterpret_cast<uint64*>(frame_to_va(head).raw);
- Frame* nxt = reinterpret_cast<Frame*>(*ptr);
+ uint64* ptr = reinterpret_cast<uint64*>(page_to_va(head).raw);
+ Page* nxt = reinterpret_cast<Page*>(*ptr);
uint64 order = *(ptr + 1);
- if (order == 5) pmm_free_frames(head, 5);
- else pmm_free_frame(head);
+ if (order == 5) pmm_free_pages(head, 5);
+ else pmm_free_page(head);
head = nxt;
}
}
@@ -259,10 +259,10 @@ void selftest_pmm()
st_begin("pmm: out of bounds order allocation gracefully fails");
{
// kMaxOrder is 11, so passing 11 or higher should safely return nullptr.
- Frame* f = pmm_alloc_frames(11);
- ST_ASSERT(f == nullptr);
- f = pmm_alloc_frames(255);
- ST_ASSERT(f == nullptr);
+ Page* page = pmm_alloc_pages(11);
+ ST_ASSERT(page == nullptr);
+ page = pmm_alloc_pages(255);
+ ST_ASSERT(page == nullptr);
}
st_pass();
diff --git a/na/test/test_slab.cc b/na/test/test_slab.cc
index e3ac72d..2ba1a5d 100644
--- a/na/test/test_slab.cc
+++ b/na/test/test_slab.cc
@@ -174,7 +174,7 @@ void selftest_slab()
}
st_pass();
- st_begin("slab: empty page reclamation (verifiable by frame availability)");
+ st_begin("slab: empty page reclamation (verifiable by page availability)");
{
// Allocate 128-byte objects until we hit the start of a completely new page.
void* base_ptr = nullptr;
@@ -201,18 +201,18 @@ void selftest_slab()
// At this point, we completely own all 32 objects of this slab page.
uint64 page_pa = va_to_pa(virt_addr(reinterpret_cast<uint64>(base_ptr))).raw;
- Frame* f = pa_to_frame(phys_addr(page_pa));
+ Page* page = pa_to_page(phys_addr(page_pa));
- ST_ASSERT(f->state == FrameState::Slab);
- ST_ASSERT(f->slab.obj_count == 32);
+ ST_ASSERT(page->state == PageState::Slab);
+ ST_ASSERT(page->slab.obj_count == 32);
// Free all items in this isolated slab page
for (int i = 0; i < 32; i++) {
kfree(ptrs[i]);
}
- // The frame MUST have been returned to the PMM (either global Free or Per-CPU Cached).
- ST_ASSERT(f->state == FrameState::Free || f->state == FrameState::PcpCached);
+ // The page MUST have been returned to the PMM (either global Free or Per-CPU Cached).
+ ST_ASSERT(page->state == PageState::Free || page->state == PageState::PcpCached);
// Clean up the drain array
for (int i = 0; i < drain_count; i++) {
diff --git a/na/test/test_vma.cc b/na/test/test_vma.cc
index d47fbd0..5278914 100644
--- a/na/test/test_vma.cc
+++ b/na/test/test_vma.cc
@@ -31,7 +31,7 @@ void selftest_vma() {
ST_ASSERT_EQ(ret, 0);
// Fault in the first page
- ret = vmm_handle_page_fault(space, 0x400500, 0);
+ ret = vmm_handle_page_fault(space, 0x400500, VmFaultCause::Read);
ST_ASSERT_EQ(ret, 0);
// Verify mapping in hardware page table
@@ -41,11 +41,11 @@ void selftest_vma() {
ST_ASSERT_NE(pa, 0ULL);
// Fault in the second page
- ret = vmm_handle_page_fault(space, 0x401FFF, 0);
+ ret = vmm_handle_page_fault(space, 0x401FFF, VmFaultCause::Read);
ST_ASSERT_EQ(ret, 0);
// Should reject fault outside VMA with -EFAULT
- ret = vmm_handle_page_fault(space, 0x402000, 0);
+ ret = vmm_handle_page_fault(space, 0x402000, VmFaultCause::Read);
ST_ASSERT_EQ(ret, -EFAULT);
space->destroy();
@@ -58,13 +58,13 @@ void selftest_vma() {
vmm_map_user(space, 0x10000, 0x2000, VM_READ);
// Inside the region — should succeed
- ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x10500, 0), 0);
+ ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x10500, VmFaultCause::Read), 0);
// Unmap exactly
vmm_unmap_user(space, 0x10000, 0x2000);
// Should fail now with -EFAULT
- ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x10500, 0), -EFAULT);
+ ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x10500, VmFaultCause::Read), -EFAULT);
space->destroy();
}
@@ -75,15 +75,15 @@ void selftest_vma() {
VmSpace* space = VmSpace::create();
vmm_map_user(space, 0x200000, 0x1000, VM_READ | VM_WRITE);
- // First fault allocates the physical frame
- int ret = vmm_handle_page_fault(space, 0x200000, 0);
+ // First fault allocates the physical page
+ int ret = vmm_handle_page_fault(space, 0x200000, VmFaultCause::Read);
ST_ASSERT_EQ(ret, 0);
// Second fault on the same page should hit "already mapped" path
- ret = vmm_handle_page_fault(space, 0x200100, 0);
+ ret = vmm_handle_page_fault(space, 0x200100, VmFaultCause::Read);
ST_ASSERT_EQ(ret, 0);
- // Verify both land on the same physical frame
+ // Verify both land on the same physical page
uint64 pa1 = 0, pa2 = 0;
pmap::lookup(space->root_pa, 0x200000, &pa1);
pmap::lookup(space->root_pa, 0x200100, &pa2);
@@ -138,11 +138,11 @@ void selftest_vma() {
vmm_unmap_user(space, 0x60000, 0x1000);
// First and third should still be reachable
- ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x50000, 0), 0);
- ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x70000, 0), 0);
+ ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x50000, VmFaultCause::Read), 0);
+ ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x70000, VmFaultCause::Read), 0);
// Middle should fail
- ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x60000, 0), -EFAULT);
+ ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x60000, VmFaultCause::Read), -EFAULT);
space->destroy();
}
@@ -152,7 +152,131 @@ void selftest_vma() {
{
ST_ASSERT_EQ(vmm_map_user(nullptr, 0x1000, 0x1000, VM_READ), -EINVAL);
ST_ASSERT_EQ(vmm_unmap_user(nullptr, 0x1000, 0x1000), -EINVAL);
- ST_ASSERT_EQ(vmm_handle_page_fault(nullptr, 0x1000, 0), -EINVAL);
+ ST_ASSERT_EQ(vmm_handle_page_fault(nullptr, 0x1000, VmFaultCause::Read), -EINVAL);
+ }
+ st_pass();
+
+ st_begin("vma: clone and copy-on-write");
+ {
+ VmSpace* parent = VmSpace::create();
+ vmm_map_user(parent, 0x80000, 0x1000, VM_READ | VM_WRITE);
+
+ /* Trigger read fault in parent to allocate physical page */
+ ST_ASSERT_EQ(vmm_handle_page_fault(parent, 0x80000, VmFaultCause::Read), 0);
+ uint64 parent_pa_initial;
+ ST_ASSERT(pmap::lookup(parent->root_pa, 0x80000, &parent_pa_initial));
+
+ /* Clone the address space */
+ VmSpace* child = vmm_clone(parent);
+ ST_ASSERT(child != nullptr);
+
+ /* Verify child has the same physical page mapped but COW */
+ uint64 child_pa;
+ ST_ASSERT(pmap::lookup(child->root_pa, 0x80000, &child_pa));
+ ST_ASSERT_EQ(child_pa, parent_pa_initial);
+ ST_ASSERT(pmap::is_cow(child->root_pa, 0x80000));
+ ST_ASSERT(pmap::is_cow(parent->root_pa, 0x80000)); /* Parent also marked COW */
+
+ /* Trigger Store Page Fault (cause=15) in child */
+ ST_ASSERT_EQ(vmm_handle_page_fault(child, 0x80000, VmFaultCause::Write), 0);
+
+ /* Child should now have a new physical page, no longer COW */
+ uint64 child_pa_new;
+ ST_ASSERT(pmap::lookup(child->root_pa, 0x80000, &child_pa_new));
+ ST_ASSERT(child_pa_new != parent_pa_initial);
+ ST_ASSERT(!pmap::is_cow(child->root_pa, 0x80000));
+
+ /* Parent is still mapped to the old page, still COW because it hasn't written */
+ uint64 parent_pa_current;
+ ST_ASSERT(pmap::lookup(parent->root_pa, 0x80000, &parent_pa_current));
+ ST_ASSERT_EQ(parent_pa_current, parent_pa_initial);
+ ST_ASSERT(pmap::is_cow(parent->root_pa, 0x80000));
+
+ /* Trigger Store Page Fault in parent */
+ ST_ASSERT_EQ(vmm_handle_page_fault(parent, 0x80000, VmFaultCause::Write), 0);
+ ST_ASSERT(!pmap::is_cow(parent->root_pa, 0x80000));
+
+ child->destroy();
+ parent->destroy();
+ }
+ st_pass();
+
+ st_begin("vma: gap search finds next free range");
+ {
+ VmSpace* space = VmSpace::create();
+ vmm_map_user(space, 0x1000, 0x3000, VM_READ);
+
+ uint64 va = mm_find_free_va(space, 0x1000, kPageSize);
+ ST_ASSERT_EQ(va, 0x4000ULL);
+
+ space->destroy();
+ }
+ st_pass();
+
+ st_begin("vma: reject overlapping mapping");
+ {
+ VmSpace* space = VmSpace::create();
+ ST_ASSERT_EQ(vmm_map_user(space, 0x20000, 0x2000, VM_READ), 0);
+ ST_ASSERT_EQ(vmm_map_user(space, 0x21000, 0x1000, VM_READ), -EINVAL);
+ space->destroy();
+ }
+ st_pass();
+
+ st_begin("vma: partial unmap splits a single VMA");
+ {
+ VmSpace* space = VmSpace::create();
+ ST_ASSERT_EQ(vmm_map_user(space, 0x80000, 0x3000, VM_READ | VM_WRITE), 0);
+
+ ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x80000, VmFaultCause::Read), 0);
+ ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x81000, VmFaultCause::Read), 0);
+ ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x82000, VmFaultCause::Read), 0);
+
+ ST_ASSERT_EQ(vmm_unmap_user(space, 0x81000, 0x1000), 0);
+
+ ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x80000, VmFaultCause::Read), 0);
+ ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x82000, VmFaultCause::Read), 0);
+ ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x81000, VmFaultCause::Read), -EFAULT);
+
+ space->destroy();
+ }
+ st_pass();
+
+ st_begin("vma: mprotect read-only blocks write faults");
+ {
+ VmSpace* space = VmSpace::create();
+ ST_ASSERT_EQ(vmm_map_user(space, 0x90000, 0x2000, VM_READ | VM_WRITE), 0);
+ ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x90000, VmFaultCause::Read), 0);
+
+ ST_ASSERT_EQ(vmm_protect_user(space, 0x90000, 0x1000, VM_READ), 0);
+ ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x90000, VmFaultCause::Write), -EFAULT);
+ ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x91000, VmFaultCause::Write), 0);
+
+ space->destroy();
+ }
+ st_pass();
+
+ st_begin("vma: PROT_NONE unmaps existing pages");
+ {
+ VmSpace* space = VmSpace::create();
+ ST_ASSERT_EQ(vmm_map_user(space, 0xA0000, 0x1000, VM_READ | VM_WRITE), 0);
+ ST_ASSERT_EQ(vmm_handle_page_fault(space, 0xA0000, VmFaultCause::Read), 0);
+
+ ST_ASSERT_EQ(vmm_protect_user(space, 0xA0000, 0x1000, 0), 0);
+ ST_ASSERT_EQ(vmm_handle_page_fault(space, 0xA0000, VmFaultCause::Read), -EFAULT);
+
+ space->destroy();
+ }
+ st_pass();
+
+ st_begin("vma: range covered check");
+ {
+ VmSpace* space = VmSpace::create();
+ ST_ASSERT_EQ(vmm_map_user(space, 0xB0000, 0x2000, VM_READ), 0);
+
+ ST_ASSERT(vma_range_covered(space, 0xB0000, 0xB1000));
+ ST_ASSERT(!vma_range_covered(space, 0xB0000, 0xB3000));
+
+ space->destroy();
}
st_pass();
}