holey-bytes/hbvm/src/vm/mem/mod.rs
Erin 06b1184772 HoleyBytes, almost adhering the spec
- Changed instruction encoding to be faster to match on
- Implemented all instructions defined in spec
- Bytecode validation
- Assembler
- Implemented 5 level paging (based on SV57)
- Implemented some degree of interrupts (though not fully adhering the spec yet)
2023-06-21 02:07:48 +02:00

216 lines
6.2 KiB
Rust

mod paging;
use self::paging::{PageTable, Permission, PtEntry};
use alloc::boxed::Box;
#[derive(Clone, Debug)]
pub struct Memory {
root_pt: *mut PageTable,
}
impl Default for Memory {
fn default() -> Self {
Self {
root_pt: Box::into_raw(Box::default()),
}
}
}
impl Drop for Memory {
fn drop(&mut self) {
let _ = unsafe { Box::from_raw(self.root_pt) };
}
}
impl Memory {
// HACK: Just for allocation testing, will be removed when proper memory interfaces
// implemented.
pub fn insert_test_page(&mut self) {
unsafe {
let mut entry = PtEntry::new(
{
let layout = alloc::alloc::Layout::from_size_align_unchecked(4096, 4096);
let ptr = alloc::alloc::alloc_zeroed(layout);
if ptr.is_null() {
alloc::alloc::handle_alloc_error(layout);
}
core::ptr::write_bytes(ptr, 69, 10);
ptr.cast()
},
Permission::Write,
);
for _ in 0..4 {
let mut pt = Box::<PageTable>::default();
pt[0] = entry;
entry = PtEntry::new(Box::into_raw(pt) as _, Permission::Node);
}
self.root_pt_mut()[0] = entry;
}
}
/// Load value from an address
pub unsafe fn load(&self, addr: u64, target: *mut u8, count: usize) -> Result<(), ()> {
self.memory_access(
addr,
target,
count,
|perm| {
matches!(
perm,
Permission::Readonly | Permission::Write | Permission::Exec
)
},
|src, dst, count| core::ptr::copy_nonoverlapping(src, dst, count),
)
}
/// Store value to an address
pub unsafe fn store(&mut self, addr: u64, source: *const u8, count: usize) -> Result<(), ()> {
self.memory_access(
addr,
source.cast_mut(),
count,
|perm| perm == Permission::Write,
|dst, src, count| core::ptr::copy_nonoverlapping(src, dst, count),
)
}
/// Copy a block of memory
pub unsafe fn block_copy(&mut self, src: u64, dst: u64, count: u64) -> Result<(), ()> {
let count = usize::try_from(count).expect("?conradluget a better CPU");
let mut srcs = PageSplitter::new(src, count, self.root_pt);
let mut dsts = PageSplitter::new(dst, count, self.root_pt);
let mut c_src = srcs.next().ok_or(())?;
let mut c_dst = dsts.next().ok_or(())?;
loop {
let min_size = c_src.size.min(c_dst.size);
unsafe {
core::ptr::copy(c_src.ptr, c_dst.ptr, min_size);
}
match (
match c_src.size.saturating_sub(min_size) {
0 => srcs.next(),
size => Some(PageSplitResult { size, ..c_src }),
},
match c_dst.size.saturating_sub(min_size) {
0 => dsts.next(),
size => Some(PageSplitResult { size, ..c_dst }),
},
) {
(None, None) => return Ok(()),
(Some(src), Some(dst)) => (c_src, c_dst) = (src, dst),
_ => return Err(()),
}
}
}
#[inline]
pub fn root_pt(&self) -> &PageTable {
unsafe { &*self.root_pt }
}
#[inline]
pub fn root_pt_mut(&mut self) -> &mut PageTable {
unsafe { &mut *self.root_pt }
}
fn memory_access(
&self,
src: u64,
mut dst: *mut u8,
len: usize,
permission_check: impl Fn(Permission) -> bool,
action: impl Fn(*mut u8, *mut u8, usize),
) -> Result<(), ()> {
for PageSplitResult { ptr, size, perm } in PageSplitter::new(src, len, self.root_pt) {
if !permission_check(perm) {
return Err(());
}
action(ptr, dst, size);
dst = unsafe { dst.add(size) };
}
Ok(())
}
}
struct PageSplitResult {
ptr: *mut u8,
size: usize,
perm: Permission,
}
struct PageSplitter {
addr: u64,
size: usize,
pagetable: *const PageTable,
}
impl PageSplitter {
pub const fn new(addr: u64, size: usize, pagetable: *const PageTable) -> Self {
Self {
addr,
size,
pagetable,
}
}
}
impl Iterator for PageSplitter {
type Item = PageSplitResult;
fn next(&mut self) -> Option<Self::Item> {
if self.size == 0 {
return None;
}
let (base, perm, size, offset) = 'a: {
let mut current_pt = self.pagetable;
for lvl in (0..5).rev() {
unsafe {
let entry = (*current_pt).get_unchecked(
usize::try_from((self.addr >> (lvl * 9 + 12)) & ((1 << 9) - 1))
.expect("?conradluget a better CPU"),
);
let ptr = entry.ptr();
match entry.permission() {
Permission::Empty => return None,
Permission::Node => current_pt = ptr as _,
perm => {
break 'a (
ptr as *mut u8,
perm,
match lvl {
0 => 4096,
1 => 1024_usize.pow(2) * 2,
2 => 1024_usize.pow(3),
_ => return None,
},
self.addr as usize & ((1 << (lvl * 9 + 12)) - 1),
)
}
}
}
}
return None;
};
let avail = (size - offset).clamp(0, self.size);
self.addr += size as u64;
self.size = self.size.saturating_sub(size);
Some(PageSplitResult {
ptr: unsafe { base.add(offset) },
size: avail,
perm,
})
}
}