You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
256 lines
8.0 KiB
256 lines
8.0 KiB
//! Mock Page Table
|
|
//!
|
|
//! An mock implementation for the PageTable.
|
|
//! Used to test page table operation.
|
|
|
|
use alloc::boxed::Box;
|
|
use super::*;
|
|
|
|
const PAGE_COUNT: usize = 16;
|
|
const PAGE_SIZE: usize = 4096;
|
|
|
|
// a mock page table for test purpose
|
|
pub struct MockPageTable {
|
|
entries: [MockEntry; PAGE_COUNT],
|
|
data: [u8; PAGE_SIZE * PAGE_COUNT],
|
|
page_fault_handler: Option<PageFaultHandler>,
|
|
}
|
|
|
|
// the entry of the mock page table
|
|
#[derive(Default, Copy, Clone)]
|
|
pub struct MockEntry {
|
|
target: PhysAddr,
|
|
present: bool,
|
|
writable: bool,
|
|
accessed: bool,
|
|
dirty: bool,
|
|
writable_shared: bool,
|
|
readonly_shared: bool,
|
|
swapped: bool,
|
|
}
|
|
|
|
impl Entry for MockEntry {
|
|
fn update(&mut self) {}
|
|
fn accessed(&self) -> bool { self.accessed }
|
|
fn dirty(&self) -> bool { self.dirty }
|
|
fn writable(&self) -> bool { self.writable }
|
|
fn present(&self) -> bool { self.present }
|
|
fn clear_accessed(&mut self) { self.accessed = false; }
|
|
fn clear_dirty(&mut self) { self.dirty = false; }
|
|
fn set_writable(&mut self, value: bool) { self.writable = value; }
|
|
fn set_present(&mut self, value: bool) { self.present = value; }
|
|
fn target(&self) -> usize { self.target }
|
|
fn set_target(&mut self, target: usize) { self.target = target; }
|
|
fn writable_shared(&self) -> bool { self.writable_shared }
|
|
fn readonly_shared(&self) -> bool { self.readonly_shared }
|
|
fn set_shared(&mut self, writable: bool) {
|
|
self.writable_shared = writable;
|
|
self.readonly_shared = !writable;
|
|
}
|
|
fn clear_shared(&mut self) {
|
|
self.writable_shared = false;
|
|
self.readonly_shared = false;
|
|
}
|
|
fn swapped(&self) -> bool { self.swapped }
|
|
fn set_swapped(&mut self, value: bool) { self.swapped = value; }
|
|
fn user(&self) -> bool { unimplemented!() }
|
|
fn set_user(&mut self, value: bool) { unimplemented!() }
|
|
fn execute(&self) -> bool { unimplemented!() }
|
|
fn set_execute(&mut self, value: bool) { unimplemented!() }
|
|
}
|
|
|
|
type PageFaultHandler = Box<FnMut(&mut MockPageTable, VirtAddr)>;
|
|
|
|
impl PageTable for MockPageTable {
|
|
type Entry = MockEntry;
|
|
|
|
fn map(&mut self, addr: VirtAddr, target: PhysAddr) -> &mut Self::Entry {
|
|
let entry = &mut self.entries[addr / PAGE_SIZE];
|
|
assert!(!entry.present);
|
|
entry.present = true;
|
|
entry.writable = true;
|
|
entry.target = target & !(PAGE_SIZE - 1);
|
|
entry
|
|
}
|
|
fn unmap(&mut self, addr: VirtAddr) {
|
|
let entry = &mut self.entries[addr / PAGE_SIZE];
|
|
assert!(entry.present);
|
|
entry.present = false;
|
|
}
|
|
fn get_entry(&mut self, addr: VirtAddr) -> Option<&mut Self::Entry> {
|
|
Some(&mut self.entries[addr / PAGE_SIZE])
|
|
}
|
|
fn get_page_slice_mut<'a,'b>(&'a mut self, addr: VirtAddr) -> &'b mut [u8] {
|
|
self._read(addr);
|
|
let pa = self.translate(addr) & !(PAGE_SIZE - 1);
|
|
let data = unsafe{ &mut *(&mut self.data as *mut [u8; PAGE_SIZE * PAGE_COUNT])};
|
|
&mut data[pa..pa + PAGE_SIZE]
|
|
}
|
|
fn read(&mut self, addr: usize) -> u8 {
|
|
self._read(addr);
|
|
self.data[self.translate(addr)]
|
|
}
|
|
fn write(&mut self, addr: usize, data: u8) {
|
|
self._write(addr);
|
|
self.data[self.translate(addr)] = data;
|
|
}
|
|
}
|
|
|
|
impl MockPageTable {
|
|
/*
|
|
** @brief create a new MockPageTable
|
|
** @retval MockPageTable the mock page table created
|
|
*/
|
|
pub fn new() -> Self {
|
|
use core::mem::uninitialized;
|
|
MockPageTable {
|
|
entries: [MockEntry::default(); PAGE_COUNT],
|
|
data: unsafe { uninitialized() },
|
|
page_fault_handler: None,
|
|
}
|
|
}
|
|
/*
|
|
** @brief set the page fault handler
|
|
** used for mock the page fault feature
|
|
** @param page_fault_handler: PageFaultHandler
|
|
** the page fault handler
|
|
** @retval none
|
|
*/
|
|
pub fn set_handler(&mut self, page_fault_handler: PageFaultHandler) {
|
|
self.page_fault_handler = Some(page_fault_handler);
|
|
}
|
|
/*
|
|
** @brief trigger page fault
|
|
** used for mock the page fault feature
|
|
** @param addr: VirtAddr the virtual address used to trigger the page fault
|
|
** @retval none
|
|
*/
|
|
fn trigger_page_fault(&mut self, addr: VirtAddr) {
|
|
// In order to call the handler with &mut self as an argument
|
|
// We have to first take the handler out of self, finally put it back
|
|
let mut handler = self.page_fault_handler.take().unwrap();
|
|
handler(self, addr);
|
|
self.page_fault_handler = Some(handler);
|
|
}
|
|
/*
|
|
** @brief translate virtual address to physics address
|
|
** used for mock address translation feature
|
|
** @param addr: VirtAddr the virtual address to translation
|
|
** @retval PhysAddr the translation result
|
|
*/
|
|
fn translate(&self, addr: VirtAddr) -> PhysAddr {
|
|
let entry = &self.entries[addr / PAGE_SIZE];
|
|
assert!(entry.present);
|
|
let pa = (entry.target & !(PAGE_SIZE - 1)) | (addr & (PAGE_SIZE - 1));
|
|
assert!(pa < self.data.len(), "Physical memory access out of range");
|
|
pa
|
|
}
|
|
/*
|
|
** @brief attempt to read the virtual address
|
|
** trigger page fault when failed
|
|
** @param addr: VirtAddr the virual address of data to read
|
|
** @retval none
|
|
*/
|
|
fn _read(&mut self, addr: VirtAddr) {
|
|
while !self.entries[addr / PAGE_SIZE].present {
|
|
self.trigger_page_fault(addr);
|
|
}
|
|
self.entries[addr / PAGE_SIZE].accessed = true;
|
|
}
|
|
/*
|
|
** @brief attempt to write the virtual address
|
|
** trigger page fault when failed
|
|
** @param addr: VirtAddr the virual address of data to write
|
|
** @retval none
|
|
*/
|
|
fn _write(&mut self, addr: VirtAddr) {
|
|
while !(self.entries[addr / PAGE_SIZE].present && self.entries[addr / PAGE_SIZE].writable) {
|
|
self.trigger_page_fault(addr);
|
|
}
|
|
self.entries[addr / PAGE_SIZE].accessed = true;
|
|
self.entries[addr / PAGE_SIZE].dirty = true;
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
use alloc::sync::Arc;
|
|
use core::cell::RefCell;
|
|
|
|
#[test]
|
|
fn read_write() {
|
|
let mut pt = MockPageTable::new();
|
|
pt.map(0x0, 0x0);
|
|
pt.map(0x1000, 0x1000);
|
|
pt.map(0x2000, 0x1000);
|
|
|
|
pt.write(0x0, 1);
|
|
pt.write(0x1, 2);
|
|
pt.write(0x1000, 3);
|
|
assert_eq!(pt.read(0x0), 1);
|
|
assert_eq!(pt.read(0x1), 2);
|
|
assert_eq!(pt.read(0x1000), 3);
|
|
assert_eq!(pt.read(0x2000), 3);
|
|
}
|
|
|
|
#[test]
|
|
fn entry() {
|
|
let mut pt = MockPageTable::new();
|
|
pt.map(0x0, 0x1000);
|
|
|
|
let entry = pt.get_entry(0).unwrap();
|
|
assert!(entry.present());
|
|
assert!(entry.writable());
|
|
assert!(!entry.accessed());
|
|
assert!(!entry.dirty());
|
|
assert_eq!(entry.target(), 0x1000);
|
|
|
|
pt.read(0x0);
|
|
let entry = pt.get_entry(0).unwrap();
|
|
assert!(entry.accessed());
|
|
assert!(!entry.dirty());
|
|
|
|
entry.clear_accessed();
|
|
assert!(!entry.accessed());
|
|
|
|
pt.write(0x1, 1);
|
|
let entry = pt.get_entry(0).unwrap();
|
|
assert!(entry.accessed());
|
|
assert!(entry.dirty());
|
|
|
|
entry.clear_dirty();
|
|
assert!(!entry.dirty());
|
|
|
|
entry.set_writable(false);
|
|
assert!(!entry.writable());
|
|
|
|
entry.set_present(false);
|
|
assert!(!entry.present());
|
|
}
|
|
|
|
#[test]
|
|
fn page_fault() {
|
|
let page_fault_count = Arc::new(RefCell::new(0usize));
|
|
|
|
let mut pt = MockPageTable::new();
|
|
pt.set_handler(Box::new({
|
|
let page_fault_count1 = page_fault_count.clone();
|
|
move |pt: &mut MockPageTable, addr: VirtAddr| {
|
|
*page_fault_count1.borrow_mut() += 1;
|
|
pt.map(addr, addr);
|
|
}
|
|
}));
|
|
|
|
pt.map(0, 0);
|
|
pt.read(0);
|
|
assert_eq!(*page_fault_count.borrow(), 0);
|
|
|
|
pt.write(0x1000, 0xff);
|
|
assert_eq!(*page_fault_count.borrow(), 1);
|
|
|
|
pt.unmap(0);
|
|
pt.read(0);
|
|
assert_eq!(*page_fault_count.borrow(), 2);
|
|
}
|
|
} |