diff --git a/ostd/src/mm/test.rs b/ostd/src/mm/test.rs index c7d54ead..5aea472c 100644 --- a/ostd/src/mm/test.rs +++ b/ostd/src/mm/test.rs @@ -8,7 +8,10 @@ use ostd_pod::Pod; use crate::{ mm::{ io::{VmIo, VmReader, VmWriter}, - FallibleVmRead, FallibleVmWrite, FrameAllocOptions, + tlb::TlbFlushOp, + vm_space::{get_activated_vm_space, VmItem, VmSpaceClearError}, + CachePolicy, FallibleVmRead, FallibleVmWrite, FrameAllocOptions, PageFlags, PageProperty, + UFrame, VmSpace, }, prelude::*, Error, @@ -489,3 +492,439 @@ mod io { assert_eq!(read_buffer, [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0]); } } + +mod vmspace { + use super::*; + + /// Helper function to create a dummy `UFrame`. + fn create_dummy_frame() -> UFrame { + let frame = FrameAllocOptions::new().alloc_frame().unwrap(); + let uframe: UFrame = frame.into(); + uframe + } + + /// Creates a new `VmSpace` and verifies its initial state. + #[ktest] + fn vmspace_creation() { + let vmspace = VmSpace::new(); + let range = 0x0..0x1000; + let mut cursor = vmspace.cursor(&range).expect("Failed to create cursor"); + assert_eq!( + cursor.next(), + Some(VmItem::NotMapped { va: 0, len: 0x1000 }) + ); + } + + /// Maps and unmaps a single page using `CursorMut`. + #[ktest] + fn vmspace_map_unmap() { + let vmspace = VmSpace::default(); + let range = 0x1000..0x2000; + let frame = create_dummy_frame(); + let prop = PageProperty::new(PageFlags::R, CachePolicy::Writeback); + + { + let mut cursor_mut = vmspace + .cursor_mut(&range) + .expect("Failed to create mutable cursor"); + // Initially, the page should not be mapped. + assert_eq!( + cursor_mut.query().unwrap(), + VmItem::NotMapped { + va: range.start, + len: range.start + 0x1000 + } + ); + // Maps a frame. + cursor_mut.map(frame.clone(), prop); + } + + // Queries the mapping. + { + let mut cursor = vmspace.cursor(&range).expect("Failed to create cursor"); + assert_eq!(cursor.virt_addr(), range.start); + assert_eq!( + cursor.query().unwrap(), + VmItem::Mapped { + va: range.start, + frame, + prop + } + ); + } + + { + let mut cursor_mut = vmspace + .cursor_mut(&range) + .expect("Failed to create mutable cursor"); + // Unmaps the frame. + cursor_mut.unmap(range.start); + } + + // Queries to ensure it's unmapped. + let mut cursor = vmspace.cursor(&range).expect("Failed to create cursor"); + assert_eq!( + cursor.query().unwrap(), + VmItem::NotMapped { + va: range.start, + len: range.start + 0x1000 + } + ); + } + + /// Maps a page twice and unmaps twice using `CursorMut`. + #[ktest] + fn vmspace_map_twice() { + let vmspace = VmSpace::default(); + let range = 0x1000..0x2000; + let frame = create_dummy_frame(); + let prop = PageProperty::new(PageFlags::R, CachePolicy::Writeback); + + { + let mut cursor_mut = vmspace + .cursor_mut(&range) + .expect("Failed to create mutable cursor"); + cursor_mut.map(frame.clone(), prop); + } + + { + let mut cursor = vmspace.cursor(&range).expect("Failed to create cursor"); + assert_eq!( + cursor.query().unwrap(), + VmItem::Mapped { + va: range.start, + frame: frame.clone(), + prop + } + ); + } + + { + let mut cursor_mut = vmspace + .cursor_mut(&range) + .expect("Failed to create mutable cursor"); + cursor_mut.map(frame.clone(), prop); + } + + { + let mut cursor = vmspace.cursor(&range).expect("Failed to create cursor"); + assert_eq!( + cursor.query().unwrap(), + VmItem::Mapped { + va: range.start, + frame, + prop + } + ); + } + + { + let mut cursor_mut = vmspace + .cursor_mut(&range) + .expect("Failed to create mutable cursor"); + cursor_mut.unmap(range.start); + } + + let mut cursor = vmspace.cursor(&range).expect("Failed to create cursor"); + assert_eq!( + cursor.query().unwrap(), + VmItem::NotMapped { + va: range.start, + len: range.start + 0x1000 + } + ); + } + + /// Unmaps twice using `CursorMut`. + #[ktest] + fn vmspace_unmap_twice() { + let vmspace = VmSpace::default(); + let range = 0x1000..0x2000; + let frame = create_dummy_frame(); + let prop = PageProperty::new(PageFlags::R, CachePolicy::Writeback); + + { + let mut cursor_mut = vmspace + .cursor_mut(&range) + .expect("Failed to create mutable cursor"); + cursor_mut.map(frame.clone(), prop); + } + + { + let mut cursor_mut = vmspace + .cursor_mut(&range) + .expect("Failed to create mutable cursor"); + cursor_mut.unmap(range.start); + } + + { + let mut cursor_mut = vmspace + .cursor_mut(&range) + .expect("Failed to create mutable cursor"); + cursor_mut.unmap(range.start); + } + + let mut cursor = vmspace.cursor(&range).expect("Failed to create cursor"); + assert_eq!( + cursor.query().unwrap(), + VmItem::NotMapped { + va: range.start, + len: range.start + 0x1000 + } + ); + } + + /// Clears the `VmSpace`. + #[ktest] + fn vmspace_clear() { + let vmspace = VmSpace::new(); + let range = 0x2000..0x3000; + { + let mut cursor_mut = vmspace + .cursor_mut(&range) + .expect("Failed to create mutable cursor"); + let frame = create_dummy_frame(); + let prop = PageProperty::new(PageFlags::R, CachePolicy::Writeback); + cursor_mut.map(frame, prop); + } + + // Clears the VmSpace. + assert!(vmspace.clear().is_ok()); + + // Verifies that the mapping is cleared. + let mut cursor = vmspace.cursor(&range).expect("Failed to create cursor"); + assert_eq!( + cursor.next(), + Some(VmItem::NotMapped { + va: range.start, + len: range.start + 0x1000 + }) + ); + } + + /// Verifies that `VmSpace::clear` returns an error when cursors are active. + #[ktest] + fn vmspace_clear_with_alive_cursors() { + let vmspace = VmSpace::new(); + let range = 0x3000..0x4000; + let _cursor_mut = vmspace + .cursor_mut(&range) + .expect("Failed to create mutable cursor"); + + // Attempts to clear the VmSpace while a cursor is active. + let result = vmspace.clear(); + assert!(matches!(result, Err(VmSpaceClearError::CursorsAlive))); + } + + /// Activates and deactivates the `VmSpace` in single-CPU scenarios. + #[ktest] + fn vmspace_activate() { + let vmspace = Arc::new(VmSpace::new()); + + // Activates the VmSpace. + vmspace.activate(); + assert_eq!(get_activated_vm_space().unwrap(), Arc::as_ptr(&vmspace)); + + // Deactivates the VmSpace. + let vmspace2 = Arc::new(VmSpace::new()); + vmspace2.activate(); + assert_eq!(get_activated_vm_space().unwrap(), Arc::as_ptr(&vmspace2)); + } + + /// Tests the `flusher` method of `CursorMut`. + #[ktest] + fn cursor_mut_flusher() { + let vmspace = VmSpace::new(); + let range = 0x4000..0x5000; + let frame = create_dummy_frame(); + let prop = PageProperty::new(PageFlags::R, CachePolicy::Writeback); + + { + let mut cursor_mut = vmspace + .cursor_mut(&range) + .expect("Failed to create mutable cursor"); + cursor_mut.map(frame.clone(), prop); + } + + { + // Verifies that the mapping exists. + let mut cursor = vmspace.cursor(&range).expect("Failed to create cursor"); + assert_eq!( + cursor.next(), + Some(VmItem::Mapped { + va: 0x4000, + frame: frame.clone(), + prop: PageProperty::new(PageFlags::R, CachePolicy::Writeback), + }) + ); + } + + { + // Flushes the TLB using a mutable cursor. + let mut cursor_mut = vmspace + .cursor_mut(&range) + .expect("Failed to create mutable cursor"); + cursor_mut.flusher().issue_tlb_flush(TlbFlushOp::All); + cursor_mut.flusher().dispatch_tlb_flush(); + } + + { + // Verifies that the mapping still exists. + let mut cursor = vmspace.cursor(&range).expect("Failed to create cursor"); + assert_eq!( + cursor.next(), + Some(VmItem::Mapped { + va: 0x4000, + frame, + prop: PageProperty::new(PageFlags::R, CachePolicy::Writeback), + }) + ); + } + } + + /// Verifies the `VmReader` and `VmWriter` interfaces. + #[ktest] + fn vmspace_reader_writer() { + let vmspace = Arc::new(VmSpace::new()); + let range = 0x4000..0x5000; + { + let mut cursor_mut = vmspace + .cursor_mut(&range) + .expect("Failed to create mutable cursor"); + let frame = create_dummy_frame(); + let prop = PageProperty::new(PageFlags::R, CachePolicy::Writeback); + cursor_mut.map(frame, prop); + } + + // Mocks the current page table paddr to match the VmSpace's root paddr. + // Fails if the VmSpace is not the current task's user space. + + // Attempts to create a reader. + let reader_result = vmspace.reader(0x4000, 0x1000); + // Expects failure in a test environment. + assert!(reader_result.is_err()); + + // Attempts to create a writer. + let writer_result = vmspace.writer(0x4000, 0x1000); + assert!(writer_result.is_err()); + + // Activates the VmSpace. + vmspace.activate(); + + // Attempts to create a reader. + let reader_result = vmspace.reader(0x4000, 0x1000); + assert!(reader_result.is_ok()); + // Attempts to create a writer. + let writer_result = vmspace.writer(0x4000, 0x1000); + assert!(writer_result.is_ok()); + + // Attempts to create a reader with an out-of-range address. + let reader_result = vmspace.reader(0x4000, usize::MAX); + assert!(reader_result.is_err()); + // Attempts to create a writer with an out-of-range address. + let writer_result = vmspace.writer(0x4000, usize::MAX); + assert!(writer_result.is_err()); + } + + /// Creates overlapping cursors and verifies handling. + #[ktest] + fn overlapping_cursors() { + let vmspace = VmSpace::new(); + let range1 = 0x5000..0x6000; + let range2 = 0x5800..0x6800; // Overlaps with range1. + + // Creates the first cursor. + let _cursor1 = vmspace + .cursor(&range1) + .expect("Failed to create first cursor"); + + // Attempts to create the second overlapping cursor. + let cursor2_result = vmspace.cursor(&range2); + assert!(cursor2_result.is_err()); + } + + /// Iterates over the `Cursor` using the `Iterator` trait. + #[ktest] + fn cursor_iterator() { + let vmspace = VmSpace::new(); + let range = 0x6000..0x7000; + let frame = create_dummy_frame(); + { + let mut cursor_mut = vmspace + .cursor_mut(&range) + .expect("Failed to create mutable cursor"); + let prop = PageProperty::new(PageFlags::R, CachePolicy::Writeback); + cursor_mut.map(frame.clone(), prop); + } + + let mut cursor = vmspace.cursor(&range).expect("Failed to create cursor"); + assert!(cursor.jump(range.start).is_ok()); + let item = cursor.next(); + assert_eq!( + item, + Some(VmItem::Mapped { + va: 0x6000, + frame, + prop: PageProperty::new(PageFlags::R, CachePolicy::Writeback), + }) + ); + + // Confirms no additional items. + assert!(cursor.next().is_none()); + } + + /// Protects a range of pages. + #[ktest] + fn protect_next() { + let vmspace = VmSpace::new(); + let range = 0x7000..0x8000; + let frame = create_dummy_frame(); + { + let mut cursor_mut = vmspace + .cursor_mut(&range) + .expect("Failed to create mutable cursor"); + let prop = PageProperty::new(PageFlags::RW, CachePolicy::Writeback); + cursor_mut.map(frame.clone(), prop); + cursor_mut.jump(range.start).expect("Failed to jump cursor"); + let protected_range = cursor_mut.protect_next(0x1000, |prop| { + prop.flags = PageFlags::R; + }); + + assert_eq!(protected_range, Some(0x7000..0x8000)); + } + // Confirms that the property was updated. + let mut cursor = vmspace.cursor(&range).expect("Failed to create cursor"); + assert_eq!( + cursor.next(), + Some(VmItem::Mapped { + va: 0x7000, + frame, + prop: PageProperty::new(PageFlags::R, CachePolicy::Writeback), + }) + ); + } + + /// Attempts to map unaligned lengths and expects a panic. + #[ktest] + #[should_panic(expected = "assertion failed: len % super::PAGE_SIZE == 0")] + fn unaligned_unmap_panics() { + let vmspace = VmSpace::new(); + let range = 0xA000..0xB000; + let mut cursor_mut = vmspace + .cursor_mut(&range) + .expect("Failed to create mutable cursor"); + cursor_mut.unmap(0x800); // Not page-aligned. + } + + /// Attempts to protect a partial page and expects a panic. + #[ktest] + #[should_panic] + fn protect_out_range_page() { + let vmspace = VmSpace::new(); + let range = 0xB000..0xC000; + let mut cursor_mut = vmspace + .cursor_mut(&range) + .expect("Failed to create mutable cursor"); + cursor_mut.protect_next(0x2000, |_| {}); // Not page-aligned. + } +} diff --git a/ostd/src/mm/vm_space.rs b/ostd/src/mm/vm_space.rs index 8229cbc2..2c819667 100644 --- a/ostd/src/mm/vm_space.rs +++ b/ostd/src/mm/vm_space.rs @@ -462,6 +462,17 @@ cpu_local_cell! { static ACTIVATED_VM_SPACE: *const VmSpace = core::ptr::null(); } +#[cfg(ktest)] +pub(crate) fn get_activated_vm_space() -> Option<*const VmSpace> { + let ptr = ACTIVATED_VM_SPACE.load(); + if ptr.is_null() { + None + } else { + // SAFETY: The pointer is only set to a valid `Arc` pointer. + Some(ptr) + } +} + /// The result of a query over the VM space. #[derive(Debug)] pub enum VmItem { @@ -483,6 +494,30 @@ pub enum VmItem { }, } +impl PartialEq for VmItem { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + // The `len` varies, so we only compare `va`. + (VmItem::NotMapped { va: va1, len: _ }, VmItem::NotMapped { va: va2, len: _ }) => { + va1 == va2 + } + ( + VmItem::Mapped { + va: va1, + frame: frame1, + prop: prop1, + }, + VmItem::Mapped { + va: va2, + frame: frame2, + prop: prop2, + }, + ) => va1 == va2 && frame1.start_paddr() == frame2.start_paddr() && prop1 == prop2, + _ => false, + } + } +} + impl TryFrom for VmItem { type Error = &'static str;