From 1e92797775bb865fc9c53b5cf05fb00dd387f140 Mon Sep 17 00:00:00 2001 From: Erin Date: Tue, 8 Aug 2023 02:48:47 +0200 Subject: [PATCH] Abstraction of memory --- hbvm/src/lib.rs | 29 +- hbvm/src/main.rs | 15 +- hbvm/src/mem/bmc.rs | 20 +- hbvm/src/mem/mod.rs | 460 +----------------------- hbvm/src/mem/pfhandler.rs | 20 -- hbvm/src/mem/softpaged/mod.rs | 475 +++++++++++++++++++++++++ hbvm/src/mem/{ => softpaged}/paging.rs | 0 7 files changed, 508 insertions(+), 511 deletions(-) delete mode 100644 hbvm/src/mem/pfhandler.rs create mode 100644 hbvm/src/mem/softpaged/mod.rs rename hbvm/src/mem/{ => softpaged}/paging.rs (100%) diff --git a/hbvm/src/lib.rs b/hbvm/src/lib.rs index f71aeb9..ed35230 100644 --- a/hbvm/src/lib.rs +++ b/hbvm/src/lib.rs @@ -26,12 +26,12 @@ use { hbbytecode::{ valider, OpParam, ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBBW, ParamBD, }, - mem::{bmc::BlockCopier, HandlePageFault, Memory}, + mem::bmc::BlockCopier, value::{Value, ValueVariant}, }; /// HoleyBytes Virtual Machine -pub struct Vm<'a, PfHandler, const TIMER_QUOTIENT: usize> { +pub struct Vm<'a, Memory, const TIMER_QUOTIENT: usize> { /// Holds 256 registers /// /// Writing to register 0 is considered undefined behaviour @@ -41,9 +41,6 @@ pub struct Vm<'a, PfHandler, const TIMER_QUOTIENT: usize> { /// Memory implementation pub memory: Memory, - /// Trap handler - pub pfhandler: PfHandler, - /// Program counter pub pc: usize, @@ -63,18 +60,18 @@ pub struct Vm<'a, PfHandler, const TIMER_QUOTIENT: usize> { copier: Option, } -impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> - Vm<'a, PfHandler, TIMER_QUOTIENT> +impl<'a, Memory, const TIMER_QUOTIENT: usize> Vm<'a, Memory, TIMER_QUOTIENT> +where + Memory: mem::Memory, { /// Create a new VM with program and trap handler /// /// # Safety /// Program code has to be validated - pub unsafe fn new_unchecked(program: &'a [u8], traph: PfHandler, memory: Memory) -> Self { + pub unsafe fn new_unchecked(program: &'a [u8], memory: Memory) -> Self { Self { registers: [Value::from(0_u64); 256], memory, - pfhandler: traph, pc: 0, program_len: program.len() - 12, program: program[4..].as_ptr(), @@ -85,13 +82,9 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> } /// Create a new VM with program and trap handler only if it passes validation - pub fn new_validated( - program: &'a [u8], - traph: PfHandler, - memory: Memory, - ) -> Result { + pub fn new_validated(program: &'a [u8], memory: Memory) -> Result { valider::validate(program)?; - Ok(unsafe { Self::new_unchecked(program, traph, memory) }) + Ok(unsafe { Self::new_unchecked(program, memory) }) } /// Execute program @@ -250,7 +243,6 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> .add(usize::from(dst) + usize::from(n)) .cast(), usize::from(count).saturating_sub(n.into()), - &mut self.pfhandler, )?; } ST => { @@ -260,14 +252,13 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> self.ldst_addr_uber(dst, base, off, count, 0)?, self.registers.as_ptr().add(usize::from(dst)).cast(), count.into(), - &mut self.pfhandler, )?; } BMC => { // Block memory copy match if let Some(copier) = &mut self.copier { // There is some copier, poll. - copier.poll(&mut self.memory, &mut self.pfhandler) + copier.poll(&mut self.memory) } else { // There is none, make one! let ParamBBD(src, dst, count) = self.decode(); @@ -284,7 +275,7 @@ impl<'a, PfHandler: HandlePageFault, const TIMER_QUOTIENT: usize> self.copier .as_mut() .unwrap_unchecked() // SAFETY: We just assigned there - .poll(&mut self.memory, &mut self.pfhandler) + .poll(&mut self.memory) } { // We are done, shift program counter core::task::Poll::Ready(Ok(())) => { diff --git a/hbvm/src/main.rs b/hbvm/src/main.rs index f3cc2d0..fd46a3e 100644 --- a/hbvm/src/main.rs +++ b/hbvm/src/main.rs @@ -1,9 +1,8 @@ +use hbvm::mem::softpaged::{HandlePageFault, PageSize, SoftPagedMem}; + use { hbbytecode::valider::validate, - hbvm::{ - mem::{HandlePageFault, Memory, MemoryAccessReason, PageSize}, - Vm, - }, + hbvm::{mem::MemoryAccessReason, Vm}, std::io::{stdin, Read}, }; @@ -16,7 +15,8 @@ fn main() -> Result<(), Box> { return Ok(()); } else { unsafe { - let mut vm = Vm::<_, 0>::new_unchecked(&prog, TestTrapHandler, Default::default()); + let mut vm = + Vm::<_, 0>::new_unchecked(&prog, SoftPagedMem::::default()); let data = { let ptr = std::alloc::alloc_zeroed(std::alloc::Layout::from_size_align_unchecked( 4096, 4096, @@ -31,7 +31,7 @@ fn main() -> Result<(), Box> { .map( data, 0, - hbvm::mem::paging::Permission::Write, + hbvm::mem::softpaged::paging::Permission::Write, PageSize::Size4K, ) .unwrap(); @@ -54,12 +54,13 @@ pub fn time() -> u32 { 9 } +#[derive(Default)] struct TestTrapHandler; impl HandlePageFault for TestTrapHandler { fn page_fault( &mut self, _: MemoryAccessReason, - _: &mut Memory, + //_: &mut Memory, _: u64, _: PageSize, _: *mut u8, diff --git a/hbvm/src/mem/bmc.rs b/hbvm/src/mem/bmc.rs index 0d47566..4155ea1 100644 --- a/hbvm/src/mem/bmc.rs +++ b/hbvm/src/mem/bmc.rs @@ -1,9 +1,6 @@ use { super::MemoryAccessReason, - crate::{ - mem::{HandlePageFault, Memory}, - VmRunError, - }, + crate::{mem::Memory, VmRunError}, core::{mem::MaybeUninit, task::Poll}, }; @@ -40,11 +37,7 @@ impl BlockCopier { /// /// # Safety /// - Same as for [`Memory::load`] and [`Memory::store`] - pub unsafe fn poll( - &mut self, - memory: &mut Memory, - traph: &mut impl HandlePageFault, - ) -> Poll> { + pub unsafe fn poll(&mut self, memory: &mut impl Memory) -> Poll> { // Safety: Assuming uninit of array of MaybeUninit is sound let mut buf = AlignedBuf(MaybeUninit::uninit().assume_init()); @@ -56,7 +49,6 @@ impl BlockCopier { self.dst, buf.0.as_mut_ptr().cast(), BUF_SIZE, - traph, ) { return Poll::Ready(Err(e)); } @@ -92,7 +84,6 @@ impl BlockCopier { self.dst, buf.0.as_mut_ptr().cast(), self.rem, - traph, ) { return Poll::Ready(Err(e)); } @@ -104,16 +95,15 @@ impl BlockCopier { #[inline] unsafe fn act( - memory: &mut Memory, + memory: &mut impl Memory, src: u64, dst: u64, buf: *mut u8, count: usize, - traph: &mut impl HandlePageFault, ) -> Result<(), BlkCopyError> { // Load to buffer memory - .load(src, buf, count, traph) + .load(src, buf, count) .map_err(|super::LoadError(addr)| BlkCopyError::Access { access_reason: MemoryAccessReason::Load, addr, @@ -121,7 +111,7 @@ unsafe fn act( // Store from buffer memory - .store(dst, buf, count, traph) + .store(dst, buf, count) .map_err(|super::StoreError(addr)| BlkCopyError::Access { access_reason: MemoryAccessReason::Store, addr, diff --git a/hbvm/src/mem/mod.rs b/hbvm/src/mem/mod.rs index 38b9901..2391a3b 100644 --- a/hbvm/src/mem/mod.rs +++ b/hbvm/src/mem/mod.rs @@ -1,426 +1,27 @@ //! Program memory implementation pub mod bmc; -pub mod paging; +pub mod softpaged; -mod pfhandler; +use {super::VmRunError, derive_more::Display}; -pub use pfhandler::HandlePageFault; - -use { - super::VmRunError, - derive_more::Display, - paging::{PageTable, Permission}, -}; - -#[cfg(feature = "alloc")] -use {alloc::boxed::Box, paging::PtEntry}; - -/// HoleyBytes virtual memory -#[derive(Clone, Debug)] -pub struct Memory { - /// Root page table - pub root_pt: *mut PageTable, -} - -#[cfg(feature = "alloc")] -impl Default for Memory { - fn default() -> Self { - Self { - root_pt: Box::into_raw(Default::default()), - } - } -} - -#[cfg(feature = "alloc")] -impl Drop for Memory { - fn drop(&mut self) { - let _ = unsafe { Box::from_raw(self.root_pt) }; - } -} - -impl Memory { - /// Maps host's memory into VM's memory +pub trait Memory { + /// Load data from memory on address /// /// # Safety - /// - Your faith in the gods of UB - /// - Addr-san claims it's fine but who knows is she isn't lying :ferrisSus: - /// - Alright, Miri-sama is also fine with this, who knows why - #[cfg(feature = "alloc")] - pub unsafe fn map( - &mut self, - host: *mut u8, - target: u64, - perm: Permission, - pagesize: PageSize, - ) -> Result<(), MapError> { - let mut current_pt = self.root_pt; + /// - Shall not overrun the buffer + unsafe fn load(&mut self, addr: u64, target: *mut u8, count: usize) -> Result<(), LoadError>; - // Decide on what level depth are we going - let lookup_depth = match pagesize { - PageSize::Size4K => 0, - PageSize::Size2M => 1, - PageSize::Size1G => 2, - }; - - // Walk pagetable levels - for lvl in (lookup_depth + 1..5).rev() { - let entry = (*current_pt) - .table - .get_unchecked_mut(addr_extract_index(target, lvl)); - - let ptr = entry.ptr(); - match entry.permission() { - // Still not on target and already seeing empty entry? - // No worries! Let's create one (allocates). - Permission::Empty => { - // Increase children count - (*current_pt).childen += 1; - - let table = Box::into_raw(Box::new(paging::PtPointedData { - pt: PageTable::default(), - })); - - core::ptr::write(entry, PtEntry::new(table, Permission::Node)); - current_pt = table as _; - } - // Continue walking - Permission::Node => current_pt = ptr as _, - - // There is some entry on place of node - _ => return Err(MapError::PageOnNode), - } - } - - let node = (*current_pt) - .table - .get_unchecked_mut(addr_extract_index(target, lookup_depth)); - - // Check if node is not mapped - if node.permission() != Permission::Empty { - return Err(MapError::AlreadyMapped); - } - - // Write entry - (*current_pt).childen += 1; - core::ptr::write(node, PtEntry::new(host.cast(), perm)); - - Ok(()) - } - - /// Unmaps pages from VM's memory - /// - /// If errors, it only means there is no entry to unmap and in most cases - /// just should be ignored. - #[cfg(feature = "alloc")] - pub fn unmap(&mut self, addr: u64) -> Result<(), NothingToUnmap> { - let mut current_pt = self.root_pt; - let mut page_tables = [core::ptr::null_mut(); 5]; - - // Walk page table in reverse - for lvl in (0..5).rev() { - let entry = unsafe { - (*current_pt) - .table - .get_unchecked_mut(addr_extract_index(addr, lvl)) - }; - - let ptr = entry.ptr(); - match entry.permission() { - // Nothing is there, throw an error, not critical! - Permission::Empty => return Err(NothingToUnmap), - // Node – Save to visited pagetables and continue walking - Permission::Node => { - page_tables[lvl as usize] = entry; - current_pt = ptr as _ - } - // Page entry – zero it out! - // Zero page entry is completely valid entry with - // empty permission - no UB here! - _ => unsafe { - core::ptr::write_bytes(entry, 0, 1); - break; - }, - } - } - - // Now walk in order visited page tables - for entry in page_tables.into_iter() { - // Level not visited, skip. - if entry.is_null() { - continue; - } - - unsafe { - let children = &mut (*(*entry).ptr()).pt.childen; - *children -= 1; // Decrease children count - - // If there are no children, deallocate. - if *children == 0 { - let _ = Box::from_raw((*entry).ptr() as *mut PageTable); - - // Zero visited entry - core::ptr::write_bytes(entry, 0, 1); - } else { - break; - } - } - } - - Ok(()) - } - - /// Load value from an address + /// Store data to memory on address /// /// # Safety - /// Applies same conditions as for [`core::ptr::copy_nonoverlapping`] - pub unsafe fn load( - &mut self, - addr: u64, - target: *mut u8, - count: usize, - traph: &mut impl HandlePageFault, - ) -> Result<(), LoadError> { - self.memory_access( - MemoryAccessReason::Load, - addr, - target, - count, - perm_check::readable, - |src, dst, count| core::ptr::copy_nonoverlapping(src, dst, count), - traph, - ) - .map_err(LoadError) - } - - /// Store value to an address - /// - /// # Safety - /// Applies same conditions as for [`core::ptr::copy_nonoverlapping`] - pub unsafe fn store( + /// - Shall not overrun the buffer + unsafe fn store( &mut self, addr: u64, source: *const u8, count: usize, - traph: &mut impl HandlePageFault, - ) -> Result<(), StoreError> { - self.memory_access( - MemoryAccessReason::Store, - addr, - source.cast_mut(), - count, - perm_check::writable, - |dst, src, count| core::ptr::copy_nonoverlapping(src, dst, count), - traph, - ) - .map_err(StoreError) - } - - // Everyone behold, the holy function, the god of HBVM memory accesses! - - /// Split address to pages, check their permissions and feed pointers with offset - /// to a specified function. - /// - /// If page is not found, execute page fault trap handler. - #[allow(clippy::too_many_arguments)] // Silence peasant - fn memory_access( - &mut self, - reason: MemoryAccessReason, - src: u64, - mut dst: *mut u8, - len: usize, - permission_check: fn(Permission) -> bool, - action: fn(*mut u8, *mut u8, usize), - traph: &mut impl HandlePageFault, - ) -> Result<(), u64> { - // Create new splitter - let mut pspl = AddrPageLookuper::new(src, len, self.root_pt); - loop { - match pspl.next() { - // Page is found - Some(Ok(AddrPageLookupOk { - vaddr, - ptr, - size, - perm, - })) => { - if !permission_check(perm) { - return Err(vaddr); - } - - // Perform specified memory action and bump destination pointer - action(ptr, dst, size); - dst = unsafe { dst.add(size) }; - } - // No page found - Some(Err(AddrPageLookupError { addr, size })) => { - // Attempt to execute page fault handler - if traph.page_fault(reason, self, addr, size, dst) { - // Shift the splitter address - pspl.bump(size); - - // Bump dst pointer - dst = unsafe { dst.add(size as _) }; - } else { - return Err(addr); // Unhandleable, VM will yield. - } - } - // No remaining pages, we are done! - None => return Ok(()), - } - } - } -} - -/// Good result from address split -struct AddrPageLookupOk { - /// Virtual address - vaddr: u64, - - /// Pointer to the start for perform operation - ptr: *mut u8, - - /// Size to the end of page / end of desired size - size: usize, - - /// Page permission - perm: Permission, -} - -/// Errornous address split result -struct AddrPageLookupError { - /// Address of failure - addr: u64, - - /// Requested page size - size: PageSize, -} - -/// Address splitter into pages -struct AddrPageLookuper { - /// Current address - addr: u64, - - /// Size left - size: usize, - - /// Page table - pagetable: *const PageTable, -} - -impl AddrPageLookuper { - /// Create a new page lookuper - #[inline] - pub const fn new(addr: u64, size: usize, pagetable: *const PageTable) -> Self { - Self { - addr, - size, - pagetable, - } - } - - /// Bump address by size X - fn bump(&mut self, page_size: PageSize) { - self.addr += page_size as u64; - self.size = self.size.saturating_sub(page_size as _); - } -} - -impl Iterator for AddrPageLookuper { - type Item = Result; - - fn next(&mut self) -> Option { - // The end, everything is fine - if self.size == 0 { - return None; - } - - let (base, perm, size, offset) = 'a: { - let mut current_pt = self.pagetable; - - // Walk the page table - for lvl in (0..5).rev() { - // Get an entry - unsafe { - let entry = (*current_pt) - .table - .get_unchecked(addr_extract_index(self.addr, lvl)); - - let ptr = entry.ptr(); - match entry.permission() { - // No page → page fault - Permission::Empty => { - return Some(Err(AddrPageLookupError { - addr: self.addr, - size: PageSize::from_lvl(lvl)?, - })) - } - - // Node → proceed waking - Permission::Node => current_pt = ptr as _, - - // Leaf → return relevant data - perm => { - break 'a ( - // Pointer in host memory - ptr as *mut u8, - perm, - PageSize::from_lvl(lvl)?, - // In-page offset - addr_extract_index(self.addr, lvl), - ); - } - } - } - } - return None; // Reached the end (should not happen) - }; - - // Get available byte count in the selected page with offset - let avail = (size as usize - offset).clamp(0, self.size); - self.bump(size); - - Some(Ok(AddrPageLookupOk { - vaddr: self.addr, - ptr: unsafe { base.add(offset) }, // Return pointer to the start of region - size: avail, - perm, - })) - } -} - -/// Extract index in page table on specified level -/// -/// The level shall not be larger than 4, otherwise -/// the output of the function is unspecified (yes, it can also panic :) -pub fn addr_extract_index(addr: u64, lvl: u8) -> usize { - debug_assert!(lvl <= 4); - usize::try_from((addr >> (lvl * 8 + 12)) & ((1 << 8) - 1)).expect("?conradluget a better CPU") -} - -/// Page size -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum PageSize { - /// 4 KiB page (on level 0) - Size4K = 4096, - - /// 2 MiB page (on level 1) - Size2M = 1024 * 1024 * 2, - - /// 1 GiB page (on level 2) - Size1G = 1024 * 1024 * 1024, -} - -impl PageSize { - /// Convert page table level to size of page - const fn from_lvl(lvl: u8) -> Option { - match lvl { - 0 => Some(PageSize::Size4K), - 1 => Some(PageSize::Size2M), - 2 => Some(PageSize::Size1G), - _ => None, - } - } + ) -> Result<(), StoreError>; } /// Unhandled load access trap @@ -433,15 +34,6 @@ pub struct LoadError(pub u64); #[display(fmt = "Store access error at address {_0:#x}")] pub struct StoreError(pub u64); -/// There was no entry in page table to unmap -/// -/// No worry, don't panic, nothing bad has happened, -/// but if you are 120% sure there should be something, -/// double-check your addresses. -#[derive(Clone, Copy, Display, Debug)] -#[display(fmt = "There was no entry to unmap")] -pub struct NothingToUnmap; - /// Reason to access memory #[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] pub enum MemoryAccessReason { @@ -460,35 +52,3 @@ impl From for VmRunError { Self::StoreAccessEx(value.0) } } - -/// Error mapping -#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] -pub enum MapError { - /// Entry was already mapped - #[display(fmt = "There is already a page mapped on specified address")] - AlreadyMapped, - /// When walking a page entry was - /// encounterd. - #[display(fmt = "There was a page mapped on the way instead of node")] - PageOnNode, -} - -/// Permisison checks -pub mod perm_check { - use super::paging::Permission; - - /// Page is readable - #[inline(always)] - pub const fn readable(perm: Permission) -> bool { - matches!( - perm, - Permission::Readonly | Permission::Write | Permission::Exec - ) - } - - /// Page is writable - #[inline(always)] - pub const fn writable(perm: Permission) -> bool { - matches!(perm, Permission::Write) - } -} diff --git a/hbvm/src/mem/pfhandler.rs b/hbvm/src/mem/pfhandler.rs deleted file mode 100644 index fb0cb22..0000000 --- a/hbvm/src/mem/pfhandler.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! Program trap handling interfaces - -use super::{Memory, MemoryAccessReason, PageSize}; - -/// Handle VM traps -pub trait HandlePageFault { - /// Handle page fault - /// - /// Return true if handling was sucessful, - /// otherwise the program will be interrupted and will - /// yield an error. - fn page_fault( - &mut self, - reason: MemoryAccessReason, - memory: &mut Memory, - vaddr: u64, - size: PageSize, - dataptr: *mut u8, - ) -> bool; -} diff --git a/hbvm/src/mem/softpaged/mod.rs b/hbvm/src/mem/softpaged/mod.rs new file mode 100644 index 0000000..2ec72e5 --- /dev/null +++ b/hbvm/src/mem/softpaged/mod.rs @@ -0,0 +1,475 @@ +//! Platform independent, software paged memory implementation + +pub mod paging; + +use { + super::{LoadError, Memory, MemoryAccessReason, StoreError}, + derive_more::Display, + paging::{PageTable, Permission}, +}; + +#[cfg(feature = "alloc")] +use {alloc::boxed::Box, paging::PtEntry}; + +/// HoleyBytes software paged memory +#[derive(Clone, Debug)] +pub struct SoftPagedMem { + /// Root page table + pub root_pt: *mut PageTable, + /// Page fault handler + pub pf_handler: PfHandler, +} + +impl Memory for SoftPagedMem { + /// Load value from an address + /// + /// # Safety + /// Applies same conditions as for [`core::ptr::copy_nonoverlapping`] + unsafe fn load(&mut self, addr: u64, target: *mut u8, count: usize) -> Result<(), LoadError> { + self.memory_access( + MemoryAccessReason::Load, + addr, + target, + count, + perm_check::readable, + |src, dst, count| core::ptr::copy_nonoverlapping(src, dst, count), + ) + .map_err(LoadError) + } + + /// Store value to an address + /// + /// # Safety + /// Applies same conditions as for [`core::ptr::copy_nonoverlapping`] + unsafe fn store( + &mut self, + addr: u64, + source: *const u8, + count: usize, + ) -> Result<(), StoreError> { + self.memory_access( + MemoryAccessReason::Store, + addr, + source.cast_mut(), + count, + perm_check::writable, + |dst, src, count| core::ptr::copy_nonoverlapping(src, dst, count), + ) + .map_err(StoreError) + } +} + +impl SoftPagedMem { + // Everyone behold, the holy function, the god of HBVM memory accesses! + + /// Split address to pages, check their permissions and feed pointers with offset + /// to a specified function. + /// + /// If page is not found, execute page fault trap handler. + #[allow(clippy::too_many_arguments)] // Silence peasant + fn memory_access( + &mut self, + reason: MemoryAccessReason, + src: u64, + mut dst: *mut u8, + len: usize, + permission_check: fn(Permission) -> bool, + action: fn(*mut u8, *mut u8, usize), + ) -> Result<(), u64> { + // Create new splitter + let mut pspl = AddrPageLookuper::new(src, len, self.root_pt); + loop { + match pspl.next() { + // Page is found + Some(Ok(AddrPageLookupOk { + vaddr, + ptr, + size, + perm, + })) => { + if !permission_check(perm) { + return Err(vaddr); + } + + // Perform specified memory action and bump destination pointer + action(ptr, dst, size); + dst = unsafe { dst.add(size) }; + } + // No page found + Some(Err(AddrPageLookupError { addr, size })) => { + // Attempt to execute page fault handler + if self.pf_handler.page_fault(reason, addr, size, dst) { + // Shift the splitter address + pspl.bump(size); + + // Bump dst pointer + dst = unsafe { dst.add(size as _) }; + } else { + return Err(addr); // Unhandleable, VM will yield. + } + } + // No remaining pages, we are done! + None => return Ok(()), + } + } + } +} + +/// Good result from address split +struct AddrPageLookupOk { + /// Virtual address + vaddr: u64, + + /// Pointer to the start for perform operation + ptr: *mut u8, + + /// Size to the end of page / end of desired size + size: usize, + + /// Page permission + perm: Permission, +} + +/// Errornous address split result +struct AddrPageLookupError { + /// Address of failure + addr: u64, + + /// Requested page size + size: PageSize, +} + +/// Address splitter into pages +struct AddrPageLookuper { + /// Current address + addr: u64, + + /// Size left + size: usize, + + /// Page table + pagetable: *const PageTable, +} + +impl AddrPageLookuper { + /// Create a new page lookuper + #[inline] + pub const fn new(addr: u64, size: usize, pagetable: *const PageTable) -> Self { + Self { + addr, + size, + pagetable, + } + } + + /// Bump address by size X + fn bump(&mut self, page_size: PageSize) { + self.addr += page_size as u64; + self.size = self.size.saturating_sub(page_size as _); + } +} + +impl Iterator for AddrPageLookuper { + type Item = Result; + + fn next(&mut self) -> Option { + // The end, everything is fine + if self.size == 0 { + return None; + } + + let (base, perm, size, offset) = 'a: { + let mut current_pt = self.pagetable; + + // Walk the page table + for lvl in (0..5).rev() { + // Get an entry + unsafe { + let entry = (*current_pt) + .table + .get_unchecked(addr_extract_index(self.addr, lvl)); + + let ptr = entry.ptr(); + match entry.permission() { + // No page → page fault + Permission::Empty => { + return Some(Err(AddrPageLookupError { + addr: self.addr, + size: PageSize::from_lvl(lvl)?, + })) + } + + // Node → proceed waking + Permission::Node => current_pt = ptr as _, + + // Leaf → return relevant data + perm => { + break 'a ( + // Pointer in host memory + ptr as *mut u8, + perm, + PageSize::from_lvl(lvl)?, + // In-page offset + addr_extract_index(self.addr, lvl), + ); + } + } + } + } + return None; // Reached the end (should not happen) + }; + + // Get available byte count in the selected page with offset + let avail = (size as usize - offset).clamp(0, self.size); + self.bump(size); + + Some(Ok(AddrPageLookupOk { + vaddr: self.addr, + ptr: unsafe { base.add(offset) }, // Return pointer to the start of region + size: avail, + perm, + })) + } +} + +#[cfg(feature = "alloc")] +impl Default for SoftPagedMem { + fn default() -> Self { + Self { + root_pt: Box::into_raw(Default::default()), + pf_handler: Default::default(), + } + } +} + +#[cfg(feature = "alloc")] +impl Drop for SoftPagedMem { + fn drop(&mut self) { + let _ = unsafe { Box::from_raw(self.root_pt) }; + } +} + +#[cfg(feature = "alloc")] +impl SoftPagedMem { + /// Maps host's memory into VM's memory + /// + /// # Safety + /// - Your faith in the gods of UB + /// - Addr-san claims it's fine but who knows is she isn't lying :ferrisSus: + /// - Alright, Miri-sama is also fine with this, who knows why + pub unsafe fn map( + &mut self, + host: *mut u8, + target: u64, + perm: Permission, + pagesize: PageSize, + ) -> Result<(), MapError> { + let mut current_pt = self.root_pt; + + // Decide on what level depth are we going + let lookup_depth = match pagesize { + PageSize::Size4K => 0, + PageSize::Size2M => 1, + PageSize::Size1G => 2, + }; + + // Walk pagetable levels + for lvl in (lookup_depth + 1..5).rev() { + let entry = (*current_pt) + .table + .get_unchecked_mut(addr_extract_index(target, lvl)); + + let ptr = entry.ptr(); + match entry.permission() { + // Still not on target and already seeing empty entry? + // No worries! Let's create one (allocates). + Permission::Empty => { + // Increase children count + (*current_pt).childen += 1; + + let table = Box::into_raw(Box::new(paging::PtPointedData { + pt: PageTable::default(), + })); + + core::ptr::write(entry, PtEntry::new(table, Permission::Node)); + current_pt = table as _; + } + // Continue walking + Permission::Node => current_pt = ptr as _, + + // There is some entry on place of node + _ => return Err(MapError::PageOnNode), + } + } + + let node = (*current_pt) + .table + .get_unchecked_mut(addr_extract_index(target, lookup_depth)); + + // Check if node is not mapped + if node.permission() != Permission::Empty { + return Err(MapError::AlreadyMapped); + } + + // Write entry + (*current_pt).childen += 1; + core::ptr::write(node, PtEntry::new(host.cast(), perm)); + + Ok(()) + } + + /// Unmaps pages from VM's memory + /// + /// If errors, it only means there is no entry to unmap and in most cases + /// just should be ignored. + pub fn unmap(&mut self, addr: u64) -> Result<(), NothingToUnmap> { + let mut current_pt = self.root_pt; + let mut page_tables = [core::ptr::null_mut(); 5]; + + // Walk page table in reverse + for lvl in (0..5).rev() { + let entry = unsafe { + (*current_pt) + .table + .get_unchecked_mut(addr_extract_index(addr, lvl)) + }; + + let ptr = entry.ptr(); + match entry.permission() { + // Nothing is there, throw an error, not critical! + Permission::Empty => return Err(NothingToUnmap), + // Node – Save to visited pagetables and continue walking + Permission::Node => { + page_tables[lvl as usize] = entry; + current_pt = ptr as _ + } + // Page entry – zero it out! + // Zero page entry is completely valid entry with + // empty permission - no UB here! + _ => unsafe { + core::ptr::write_bytes(entry, 0, 1); + break; + }, + } + } + + // Now walk in order visited page tables + for entry in page_tables.into_iter() { + // Level not visited, skip. + if entry.is_null() { + continue; + } + + unsafe { + let children = &mut (*(*entry).ptr()).pt.childen; + *children -= 1; // Decrease children count + + // If there are no children, deallocate. + if *children == 0 { + let _ = Box::from_raw((*entry).ptr() as *mut PageTable); + + // Zero visited entry + core::ptr::write_bytes(entry, 0, 1); + } else { + break; + } + } + } + + Ok(()) + } +} + +/// Extract index in page table on specified level +/// +/// The level shall not be larger than 4, otherwise +/// the output of the function is unspecified (yes, it can also panic :) +pub fn addr_extract_index(addr: u64, lvl: u8) -> usize { + debug_assert!(lvl <= 4); + usize::try_from((addr >> (lvl * 8 + 12)) & ((1 << 8) - 1)).expect("?conradluget a better CPU") +} + +/// Page size +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum PageSize { + /// 4 KiB page (on level 0) + Size4K = 4096, + + /// 2 MiB page (on level 1) + Size2M = 1024 * 1024 * 2, + + /// 1 GiB page (on level 2) + Size1G = 1024 * 1024 * 1024, +} + +impl PageSize { + /// Convert page table level to size of page + const fn from_lvl(lvl: u8) -> Option { + match lvl { + 0 => Some(PageSize::Size4K), + 1 => Some(PageSize::Size2M), + 2 => Some(PageSize::Size1G), + _ => None, + } + } +} + +/// Error mapping +#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)] +pub enum MapError { + /// Entry was already mapped + #[display(fmt = "There is already a page mapped on specified address")] + AlreadyMapped, + /// When walking a page entry was + /// encounterd. + #[display(fmt = "There was a page mapped on the way instead of node")] + PageOnNode, +} + +/// There was no entry in page table to unmap +/// +/// No worry, don't panic, nothing bad has happened, +/// but if you are 120% sure there should be something, +/// double-check your addresses. +#[derive(Clone, Copy, Display, Debug)] +#[display(fmt = "There was no entry to unmap")] +pub struct NothingToUnmap; + +/// Permisison checks +pub mod perm_check { + use super::paging::Permission; + + /// Page is readable + #[inline(always)] + pub const fn readable(perm: Permission) -> bool { + matches!( + perm, + Permission::Readonly | Permission::Write | Permission::Exec + ) + } + + /// Page is writable + #[inline(always)] + pub const fn writable(perm: Permission) -> bool { + matches!(perm, Permission::Write) + } +} + +/// Handle VM traps +pub trait HandlePageFault { + /// Handle page fault + /// + /// Return true if handling was sucessful, + /// otherwise the program will be interrupted and will + /// yield an error. + fn page_fault( + &mut self, + reason: MemoryAccessReason, + // memory: &mut SoftPagedMem, TODO: Make work + vaddr: u64, + size: PageSize, + dataptr: *mut u8, + ) -> bool + where + Self: Sized; +} diff --git a/hbvm/src/mem/paging.rs b/hbvm/src/mem/softpaged/paging.rs similarity index 100% rename from hbvm/src/mem/paging.rs rename to hbvm/src/mem/softpaged/paging.rs