Add ktest cases for vmspace

This commit is contained in:
Fabing Li 2025-03-31 11:16:57 +00:00 committed by Tate, Hongliang Tian
parent 52e0776591
commit 6c0827b681
2 changed files with 475 additions and 1 deletions

View File

@ -8,7 +8,10 @@ use ostd_pod::Pod;
use crate::{ use crate::{
mm::{ mm::{
io::{VmIo, VmReader, VmWriter}, 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::*, prelude::*,
Error, Error,
@ -489,3 +492,439 @@ mod io {
assert_eq!(read_buffer, [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0]); 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.
}
}

View File

@ -462,6 +462,17 @@ cpu_local_cell! {
static ACTIVATED_VM_SPACE: *const VmSpace = core::ptr::null(); 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. /// The result of a query over the VM space.
#[derive(Debug)] #[derive(Debug)]
pub enum VmItem { 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<PageTableItem> for VmItem { impl TryFrom<PageTableItem> for VmItem {
type Error = &'static str; type Error = &'static str;