diff --git a/src/core/src/vmm/hv/linux/mod.rs b/src/core/src/vmm/hv/linux/mod.rs index d4f4c36eb..8194fa5dc 100644 --- a/src/core/src/vmm/hv/linux/mod.rs +++ b/src/core/src/vmm/hv/linux/mod.rs @@ -83,11 +83,10 @@ impl Kvm { // Set RAM. let vm = unsafe { OwnedFd::from_raw_fd(vm) }; let slot = 0; - let addr = ram.addr().try_into().unwrap(); let len = ram.len().try_into().unwrap(); let mem = ram.host_addr().cast_mut().cast(); - match unsafe { kvm_set_user_memory_region(vm.as_raw_fd(), slot, addr, len, mem) } { + match unsafe { kvm_set_user_memory_region(vm.as_raw_fd(), slot, 0, len, mem) } { 0 => {} v => return Err(VmmError::MapRamFailed(Error::from_raw_os_error(v))), } diff --git a/src/core/src/vmm/hv/macos/mod.rs b/src/core/src/vmm/hv/macos/mod.rs index 97d70859f..9001b6d2e 100644 --- a/src/core/src/vmm/hv/macos/mod.rs +++ b/src/core/src/vmm/hv/macos/mod.rs @@ -26,12 +26,8 @@ impl Hf { let vm = Vm::new().map_err(VmmError::CreateVmFailed)?; // Map memory. - vm.vm_map( - ram.host_addr().cast_mut().cast(), - ram.addr().try_into().unwrap(), - ram.len(), - ) - .map_err(VmmError::MapRamFailed)?; + vm.vm_map(ram.host_addr().cast_mut().cast(), 0, ram.len()) + .map_err(VmmError::MapRamFailed)?; Ok(Self { vm, ram }) } diff --git a/src/core/src/vmm/hv/windows/mod.rs b/src/core/src/vmm/hv/windows/mod.rs index 54e3e2934..e75e0c175 100644 --- a/src/core/src/vmm/hv/windows/mod.rs +++ b/src/core/src/vmm/hv/windows/mod.rs @@ -28,12 +28,8 @@ impl Whp { part.setup().map_err(VmmError::SetupPartitionFailed)?; // Map memory. - part.map_gpa( - ram.host_addr().cast(), - ram.addr().try_into().unwrap(), - ram.len().try_into().unwrap(), - ) - .map_err(VmmError::MapRamFailed)?; + part.map_gpa(ram.host_addr().cast(), 0, ram.len().try_into().unwrap()) + .map_err(VmmError::MapRamFailed)?; Ok(Self { part, ram }) } diff --git a/src/core/src/vmm/hw/console/mod.rs b/src/core/src/vmm/hw/console/mod.rs index 4bd9ed934..da0ad3429 100644 --- a/src/core/src/vmm/hw/console/mod.rs +++ b/src/core/src/vmm/hw/console/mod.rs @@ -1,6 +1,6 @@ use self::context::Context; -use super::{Device, DeviceContext, Ram, PAGE_SIZE}; -use obvirt::console::MsgType; +use super::{Device, DeviceContext, Ram}; +use obvirt::console::{Memory, MsgType}; use std::collections::VecDeque; use std::num::NonZero; use std::sync::Mutex; @@ -10,15 +10,22 @@ mod context; /// Virtual console for the VM. pub struct Console { addr: usize, + len: NonZero, logs: Mutex>, } impl Console { - pub(crate) const SIZE: NonZero = PAGE_SIZE; + pub fn new(addr: usize, vm_page_size: NonZero) -> Self { + let len = size_of::() + .checked_next_multiple_of(vm_page_size.get()) + .and_then(NonZero::new) + .unwrap(); + + addr.checked_add(len.get()).unwrap(); - pub fn new(addr: usize) -> Self { Self { addr, + len, logs: Mutex::default(), } } @@ -30,7 +37,7 @@ impl Device for Console { } fn len(&self) -> NonZero { - Self::SIZE + self.len } fn create_context<'a>(&'a self, ram: &'a Ram) -> Box { diff --git a/src/core/src/vmm/hw/mod.rs b/src/core/src/vmm/hw/mod.rs index a7a18bc88..9dece26f4 100644 --- a/src/core/src/vmm/hw/mod.rs +++ b/src/core/src/vmm/hw/mod.rs @@ -10,14 +10,12 @@ pub use self::ram::*; mod console; mod ram; -pub(crate) const PAGE_SIZE: NonZero = unsafe { NonZero::new_unchecked(0x4000) }; - -pub fn setup_devices(start_addr: usize) -> DeviceTree { +pub fn setup_devices(start_addr: usize, vm_page_size: NonZero) -> DeviceTree { let mut map = BTreeMap::>::new(); // Console. let addr = start_addr; - let console = Arc::new(Console::new(addr)); + let console = Arc::new(Console::new(addr, vm_page_size)); assert!(map.insert(console.addr(), console.clone()).is_none()); diff --git a/src/core/src/vmm/hw/ram/builder.rs b/src/core/src/vmm/hw/ram/builder.rs index 3577e69fd..661929a14 100644 --- a/src/core/src/vmm/hw/ram/builder.rs +++ b/src/core/src/vmm/hw/ram/builder.rs @@ -1,7 +1,8 @@ use super::{Ram, RamError}; -use crate::vmm::hw::{DeviceTree, PAGE_SIZE}; +use crate::vmm::hw::DeviceTree; use crate::vmm::VmmError; use obconf::BootEnv; +use std::num::NonZero; use std::ops::Range; use thiserror::Error; @@ -15,16 +16,10 @@ pub struct RamBuilder { } impl RamBuilder { - pub fn new() -> Result { - // In theory we can support any page size on the host. The problem is it required a lot of - // work. It is also unlikely for someone to need this feature because AFAIK the maximum page - // size on a consumer computer is the same as PS4. With page size the same as PS4 or lower - // we don't need to keep track allocations here. - let page_size = Self::get_page_size().map_err(VmmError::GetPageSizeFailed)?; - - if page_size > PAGE_SIZE.get() { - return Err(VmmError::UnsupportedPageSize); - } + /// # Safety + /// `host_page_size` must be valid. + pub unsafe fn new(host_page_size: NonZero) -> Result { + use std::io::Error; // Reserve memory range. #[cfg(unix)] @@ -32,19 +27,17 @@ impl RamBuilder { use libc::{mmap, MAP_ANON, MAP_FAILED, MAP_PRIVATE, PROT_NONE}; use std::ptr::null_mut; - let mem = unsafe { - mmap( - null_mut(), - Ram::SIZE, - PROT_NONE, - MAP_PRIVATE | MAP_ANON, - -1, - 0, - ) - }; + let mem = mmap( + null_mut(), + Ram::SIZE, + PROT_NONE, + MAP_PRIVATE | MAP_ANON, + -1, + 0, + ); if mem == MAP_FAILED { - return Err(VmmError::CreateRamFailed(std::io::Error::last_os_error())); + return Err(VmmError::CreateRamFailed(Error::last_os_error())); } mem.cast() @@ -55,17 +48,20 @@ impl RamBuilder { use std::ptr::null; use windows_sys::Win32::System::Memory::{VirtualAlloc, MEM_RESERVE, PAGE_NOACCESS}; - let mem = unsafe { VirtualAlloc(null(), Ram::SIZE, MEM_RESERVE, PAGE_NOACCESS) }; + let mem = VirtualAlloc(null(), Ram::SIZE, MEM_RESERVE, PAGE_NOACCESS); if mem.is_null() { - return Err(VmmError::CreateRamFailed(std::io::Error::last_os_error())); + return Err(VmmError::CreateRamFailed(Error::last_os_error())); } mem.cast() }; Ok(Self { - ram: Ram(mem), + ram: Ram { + mem, + host_page_size, + }, next: 0, kern: None, stack: None, @@ -74,28 +70,32 @@ impl RamBuilder { } /// # Panics - /// If called a second time. - pub fn alloc_kernel(&mut self, len: usize) -> Result<&mut [u8], RamError> { + /// - If `len` is not multiplied by host page size. + /// - If called a second time. + pub fn alloc_kernel(&mut self, len: NonZero) -> Result<&mut [u8], RamError> { assert!(self.kern.is_none()); - let mem = unsafe { self.ram.alloc(self.next, len)? }; + let addr = self.next; + let mem = unsafe { self.ram.alloc(addr, len)? }; - self.kern = Some(self.next..(self.next + len)); - self.next += len; + self.kern = Some(addr..(addr + len.get())); + self.next += len.get(); Ok(mem) } /// # Panics - /// - If `len` is not multiplied by [`Ram::VM_PAGE_SIZE`]. + /// - If `len` is not multiplied by host page size. /// - If called a second time. - pub fn alloc_stack(&mut self, len: usize) -> Result<(), RamError> { + pub fn alloc_stack(&mut self, len: NonZero) -> Result<(), RamError> { assert!(self.stack.is_none()); - unsafe { self.ram.alloc(self.next, len) }?; + let addr = self.next; - self.stack = Some(self.next..(self.next + len)); - self.next += len; + unsafe { self.ram.alloc(addr, len) }?; + + self.stack = Some(addr..(addr + len.get())); + self.next += len.get(); Ok(()) } @@ -104,23 +104,27 @@ impl RamBuilder { /// If called a second time. pub fn alloc_args(&mut self, env: BootEnv) -> Result<(), RamError> { assert!(self.args.is_none()); - assert!(align_of::() <= PAGE_SIZE.get()); + assert!(align_of::() <= self.ram.host_page_size.get()); // Allocate RAM for all arguments. - let len = size_of::().next_multiple_of(PAGE_SIZE.get()); - let args = unsafe { self.ram.alloc(self.next, len)?.as_mut_ptr() }; + let addr = self.next; + let len = size_of::() + .checked_next_multiple_of(self.ram.host_page_size.get()) + .and_then(NonZero::new) + .unwrap(); + let args = unsafe { self.ram.alloc(addr, len)?.as_mut_ptr() }; // Write env. let off = 0; - unsafe { std::ptr::write(args.cast(), env) }; + unsafe { std::ptr::write(args.add(off).cast(), env) }; self.args = Some(KernelArgs { - ram: self.next..(self.next + len), + ram: addr..(addr + len.get()), env: off, }); - self.next += len; + self.next += len.get(); Ok(()) } @@ -151,7 +155,7 @@ impl RamBuilder { for (addr, dev) in devices.map() { let len = dev.len().get(); - self.setup_page_tables(pml4t, addr, addr, len)?; + self.setup_4k_page_tables(pml4t, addr, addr, len)?; dev_end = addr + len; } @@ -167,7 +171,7 @@ impl RamBuilder { assert!(vaddr >= dev_end); - self.setup_page_tables(pml4t, vaddr, kern_paddr, kern_len)?; + self.setup_4k_page_tables(pml4t, vaddr, kern_paddr, kern_len)?; vaddr += kern_len; @@ -179,7 +183,7 @@ impl RamBuilder { .map(|v| (v.start, v.end - v.start)) .unwrap(); - self.setup_page_tables(pml4t, vaddr, paddr, stack_len)?; + self.setup_4k_page_tables(pml4t, vaddr, paddr, stack_len)?; vaddr += stack_len; @@ -188,7 +192,7 @@ impl RamBuilder { let ram = args.ram; let env_vaddr = vaddr + args.env; - self.setup_page_tables(pml4t, vaddr, ram.start, ram.end - ram.start)?; + self.setup_4k_page_tables(pml4t, vaddr, ram.start, ram.end - ram.start)?; // Check if PT_DYNAMIC valid. let (p_vaddr, p_memsz) = dynamic; @@ -198,7 +202,8 @@ impl RamBuilder { } // Get PT_DYNAMIC. - let kern = unsafe { std::slice::from_raw_parts_mut(self.ram.0.add(kern_paddr), kern_len) }; + let kern = + unsafe { std::slice::from_raw_parts_mut(self.ram.mem.add(kern_paddr), kern_len) }; let dynamic = p_vaddr .checked_add(p_memsz) .and_then(|end| kern.get(p_vaddr..end)) @@ -288,14 +293,14 @@ impl RamBuilder { } #[cfg(target_arch = "x86_64")] - fn setup_page_tables( + fn setup_4k_page_tables( &mut self, pml4t: &mut [usize; 512], vaddr: usize, paddr: usize, len: usize, ) -> Result<(), RamBuilderError> { - assert_eq!(len % PAGE_SIZE, 0); + assert_eq!(len % 4096, 0); fn set_page_entry(entry: &mut usize, addr: usize) { assert_eq!(addr & 0x7FF0000000000000, 0); @@ -320,7 +325,7 @@ impl RamBuilder { unsafe { &mut *pdpt } } - v => unsafe { &mut *self.ram.0.add(v & 0xFFFFFFFFFF000).cast() }, + v => unsafe { &mut *self.ram.mem.add(v & 0xFFFFFFFFFF000).cast() }, }; // Get page-directory table. @@ -335,7 +340,7 @@ impl RamBuilder { unsafe { &mut *pdt } } - v => unsafe { &mut *self.ram.0.add(v & 0xFFFFFFFFFF000).cast() }, + v => unsafe { &mut *self.ram.mem.add(v & 0xFFFFFFFFFF000).cast() }, }; // Get page table. @@ -350,7 +355,7 @@ impl RamBuilder { unsafe { &mut *pt } } - v => unsafe { &mut *self.ram.0.add(v & 0xFFFFFFFFFF000).cast() }, + v => unsafe { &mut *self.ram.mem.add(v & 0xFFFFFFFFFF000).cast() }, }; // Set page table entry. @@ -367,35 +372,22 @@ impl RamBuilder { #[cfg(target_arch = "x86_64")] fn alloc_page_table(&mut self) -> Result<(*mut [usize; 512], usize), RamError> { - let off = self.next; - let len = (512usize * 8).next_multiple_of(PAGE_SIZE.get()); - let tab = unsafe { self.ram.alloc(off, len).map(|v| v.as_mut_ptr().cast())? }; - - self.next += len; - - Ok((tab, off)) - } - - #[cfg(unix)] - fn get_page_size() -> Result { - let v = unsafe { libc::sysconf(libc::_SC_PAGE_SIZE) }; + // Get address and length. + let addr = self.next; + let len = (512usize * 8) + .checked_next_multiple_of(self.ram.host_page_size.get()) + .and_then(NonZero::new) + .unwrap(); - if v < 0 { - Err(std::io::Error::last_os_error()) - } else { - Ok(v.try_into().unwrap()) - } - } + // Page table on x86-64 always 4k aligned regardless page size being used. + assert_eq!(addr % 4096, 0); - #[cfg(windows)] - fn get_page_size() -> Result { - use std::mem::zeroed; - use windows_sys::Win32::System::SystemInformation::GetSystemInfo; - let mut i = unsafe { zeroed() }; + // Allocate. + let tab = unsafe { self.ram.alloc(addr, len).map(|v| v.as_mut_ptr().cast())? }; - unsafe { GetSystemInfo(&mut i) }; + self.next += len.get(); - Ok(i.dwPageSize.try_into().unwrap()) + Ok((tab, addr)) } } diff --git a/src/core/src/vmm/hw/ram/mod.rs b/src/core/src/vmm/hw/ram/mod.rs index 49102795c..704d4fcfe 100644 --- a/src/core/src/vmm/hw/ram/mod.rs +++ b/src/core/src/vmm/hw/ram/mod.rs @@ -1,5 +1,5 @@ -use super::PAGE_SIZE; -use std::io::{Error, ErrorKind}; +use std::io::Error; +use std::num::NonZero; use thiserror::Error; pub use self::builder::*; @@ -10,20 +10,20 @@ mod builder; /// /// This struct will allocate a 8GB of memory immediately but not commit any parts of it until there /// is an allocation request. That mean the actual memory usage is not fixed at 8GB but will be -/// dependent on what PS4 applications currently running. If it is a simple game the memory usage might be -/// just a hundred of megabytes. -pub struct Ram(*mut u8); +/// dependent on what PS4 applications currently running. If it is a simple game the memory usage +/// might be just a hundred of megabytes. +/// +/// RAM always started at address 0. +pub struct Ram { + mem: *mut u8, + host_page_size: NonZero, +} impl Ram { - pub(crate) const ADDR: usize = 0; // It seems like RAM on all system always at address 0. pub(crate) const SIZE: usize = 1024 * 1024 * 1024 * 8; // 8GB - pub fn addr(&self) -> usize { - Self::ADDR - } - pub fn host_addr(&self) -> *const u8 { - self.0 + self.mem } pub fn len(&self) -> usize { @@ -31,54 +31,52 @@ impl Ram { } /// # Panics - /// If `off` or `len` is not multiply by [`PAGE_SIZE`]. + /// If `addr` or `len` is not multiply by host page size. /// /// # Safety - /// This method does not check if `off` is already allocated. It is undefined behavior if - /// `off` + `len` is overlapped with the previous allocation. - pub unsafe fn alloc(&self, off: usize, len: usize) -> Result<&mut [u8], RamError> { - assert_eq!(off % PAGE_SIZE, 0); - assert_eq!(len % PAGE_SIZE, 0); + /// This method does not check if `addr` is already allocated. It is undefined behavior if + /// `addr` + `len` is overlapped with the previous allocation. + pub unsafe fn alloc(&self, addr: usize, len: NonZero) -> Result<&mut [u8], RamError> { + assert_eq!(addr % self.host_page_size, 0); + assert_eq!(len.get() % self.host_page_size, 0); - if !off.checked_add(len).is_some_and(|v| v <= Self::SIZE) { + if !addr.checked_add(len.get()).is_some_and(|v| v <= Self::SIZE) { return Err(RamError::InvalidAddr); } - Self::commit(self.0.add(off), len) - .map(|v| std::slice::from_raw_parts_mut(v, len)) + Self::commit(self.mem.add(addr), len.get()) + .map(|v| std::slice::from_raw_parts_mut(v, len.get())) .map_err(RamError::HostFailed) } /// # Panics - /// If `off` or `len` is not multiply by [`PAGE_SIZE`]. + /// If `addr` or `len` is not multiply by host page size. /// /// # Safety - /// Accessing the deallocated memory on the host will be undefined behavior. - pub unsafe fn dealloc(&self, off: usize, len: usize) -> Result<(), Error> { - assert_eq!(off % PAGE_SIZE, 0); - assert_eq!(len % PAGE_SIZE, 0); + /// Accessing the deallocated memory on the host after this will be undefined behavior. + pub unsafe fn dealloc(&self, addr: usize, len: NonZero) -> Result<(), RamError> { + assert_eq!(addr % self.host_page_size, 0); + assert_eq!(len.get() % self.host_page_size, 0); - if off.checked_add(len).unwrap() > Self::SIZE { - return Err(Error::from(ErrorKind::InvalidInput)); + if !addr.checked_add(len.get()).is_some_and(|v| v <= Self::SIZE) { + return Err(RamError::InvalidAddr); } - Self::decommit(self.0.add(off), len) + Self::decommit(self.mem.add(addr), len.get()).map_err(RamError::HostFailed) } #[cfg(unix)] - fn commit(addr: *const u8, len: usize) -> Result<*mut u8, Error> { + unsafe fn commit(addr: *const u8, len: usize) -> Result<*mut u8, Error> { use libc::{mmap, MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_READ, PROT_WRITE}; - let ptr = unsafe { - mmap( - addr.cast_mut().cast(), - len, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANON | MAP_FIXED, - -1, - 0, - ) - }; + let ptr = mmap( + addr.cast_mut().cast(), + len, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, + -1, + 0, + ); if ptr == MAP_FAILED { Err(Error::last_os_error()) @@ -88,10 +86,10 @@ impl Ram { } #[cfg(windows)] - fn commit(addr: *const u8, len: usize) -> Result<*mut u8, Error> { + unsafe fn commit(addr: *const u8, len: usize) -> Result<*mut u8, Error> { use windows_sys::Win32::System::Memory::{VirtualAlloc, MEM_COMMIT, PAGE_READWRITE}; - let ptr = unsafe { VirtualAlloc(addr.cast(), len, MEM_COMMIT, PAGE_READWRITE) }; + let ptr = VirtualAlloc(addr.cast(), len, MEM_COMMIT, PAGE_READWRITE); if ptr.is_null() { Err(Error::last_os_error()) @@ -101,10 +99,10 @@ impl Ram { } #[cfg(unix)] - fn decommit(addr: *mut u8, len: usize) -> Result<(), Error> { + unsafe fn decommit(addr: *mut u8, len: usize) -> Result<(), Error> { use libc::{mprotect, PROT_NONE}; - if unsafe { mprotect(addr.cast(), len, PROT_NONE) } < 0 { + if mprotect(addr.cast(), len, PROT_NONE) < 0 { Err(Error::last_os_error()) } else { Ok(()) @@ -112,10 +110,10 @@ impl Ram { } #[cfg(windows)] - fn decommit(addr: *mut u8, len: usize) -> Result<(), Error> { + unsafe fn decommit(addr: *mut u8, len: usize) -> Result<(), Error> { use windows_sys::Win32::System::Memory::{VirtualFree, MEM_DECOMMIT}; - if unsafe { VirtualFree(addr.cast(), len, MEM_DECOMMIT) } == 0 { + if VirtualFree(addr.cast(), len, MEM_DECOMMIT) == 0 { Err(Error::last_os_error()) } else { Ok(()) @@ -128,10 +126,10 @@ impl Drop for Ram { fn drop(&mut self) { use libc::munmap; - if unsafe { munmap(self.0.cast(), Self::SIZE) } < 0 { + if unsafe { munmap(self.mem.cast(), Self::SIZE) } < 0 { panic!( "failed to unmap RAM at {:p}: {}", - self.0, + self.mem, Error::last_os_error() ); } @@ -141,10 +139,10 @@ impl Drop for Ram { fn drop(&mut self) { use windows_sys::Win32::System::Memory::{VirtualFree, MEM_RELEASE}; - if unsafe { VirtualFree(self.0.cast(), 0, MEM_RELEASE) } == 0 { + if unsafe { VirtualFree(self.mem.cast(), 0, MEM_RELEASE) } == 0 { panic!( "failed to free RAM at {:p}: {}", - self.0, + self.mem, Error::last_os_error() ); } diff --git a/src/core/src/vmm/mod.rs b/src/core/src/vmm/mod.rs index bc37f0951..5d3809ba5 100644 --- a/src/core/src/vmm/mod.rs +++ b/src/core/src/vmm/mod.rs @@ -1,7 +1,5 @@ use self::hv::{Cpu, CpuExit, CpuIo, CpuStates, Hypervisor}; -use self::hw::{ - setup_devices, Device, DeviceContext, DeviceTree, Ram, RamBuilder, RamMap, PAGE_SIZE, -}; +use self::hw::{setup_devices, Device, DeviceContext, DeviceTree, Ram, RamBuilder, RamMap}; use self::screen::Screen; use crate::error::RustError; use obconf::{BootEnv, Vm}; @@ -11,6 +9,7 @@ use std::error::Error; use std::ffi::{c_char, c_void, CStr}; use std::fs::File; use std::io::{Read, Seek, SeekFrom}; +use std::num::NonZero; use std::ops::Deref; use std::ptr::null_mut; use std::sync::atomic::{AtomicBool, Ordering}; @@ -135,7 +134,6 @@ pub unsafe extern "C" fn vmm_run( let p_vaddr = usize::from_ne_bytes(data[16..24].try_into().unwrap()); let p_filesz = usize::from_ne_bytes(data[32..40].try_into().unwrap()); let p_memsz = usize::from_ne_bytes(data[40..48].try_into().unwrap()); - let p_align = usize::from_ne_bytes(data[48..56].try_into().unwrap()); match p_type { 1 => { @@ -144,11 +142,6 @@ pub unsafe extern "C" fn vmm_run( return null_mut(); } - if p_align != PAGE_SIZE.get() { - *err = RustError::new(format!("unsupported p_align on PT_LOAD {index}")); - return null_mut(); - } - segments.push((p_offset, p_filesz, p_vaddr, p_memsz)); } 2 => { @@ -194,10 +187,7 @@ pub unsafe extern "C" fn vmm_run( return null_mut(); } - len = match p_vaddr - .checked_add(p_memsz) - .and_then(|end| end.checked_next_multiple_of(PAGE_SIZE.get())) - { + len = match p_vaddr.checked_add(p_memsz) { Some(v) => v, None => { *err = RustError::new(format!("invalid p_memsz on PT_LOAD at {p_vaddr:#x}")); @@ -206,8 +196,32 @@ pub unsafe extern "C" fn vmm_run( }; } + // Get host page size. + let host_page_size = match get_page_size() { + Ok(v) => v, + Err(e) => { + *err = RustError::with_source("couldn't get host page size", e); + return null_mut(); + } + }; + + // Round kernel memory size. + let len = match len { + 0 => { + *err = RustError::new("the kernel has PT_LOAD with zero length"); + return null_mut(); + } + v => match v.checked_next_multiple_of(host_page_size.get()) { + Some(v) => NonZero::new_unchecked(v), + None => { + *err = RustError::new("total size of PT_LOAD is too large"); + return null_mut(); + } + }, + }; + // Setup RAM builder. - let mut ram = match RamBuilder::new() { + let mut ram = match RamBuilder::new(host_page_size) { Ok(v) => v, Err(e) => { *err = RustError::wrap(e); @@ -249,13 +263,14 @@ pub unsafe extern "C" fn vmm_run( } // Allocate stack. - if let Err(e) = ram.alloc_stack(1024 * 1024 * 2) { + if let Err(e) = ram.alloc_stack(NonZero::new(1024 * 1024 * 2).unwrap()) { *err = RustError::with_source("couldn't allocate RAM for stack", e); return null_mut(); } // Setup virtual devices. - let devices = Arc::new(setup_devices(Ram::SIZE)); + let vm_page_size = NonZero::new(0x4000).unwrap(); + let devices = Arc::new(setup_devices(Ram::SIZE, vm_page_size)); // Allocate arguments. let env = BootEnv::Vm(Vm { @@ -462,8 +477,12 @@ fn run_cpu(mut cpu: impl Cpu, args: &CpuArgs) { let mut devices = args .devices .map() - .map(|(addr, dev)| (addr, dev.create_context(&args.ram))) - .collect::>>(); + .map(|(addr, dev)| { + let end = dev.len().checked_add(addr).unwrap(); + + (addr, (dev.create_context(&args.ram), end)) + }) + .collect::, NonZero)>>(); while !args.shutdown.load(Ordering::Relaxed) { // Run the vCPU. @@ -491,16 +510,40 @@ fn run_cpu(mut cpu: impl Cpu, args: &CpuArgs) { } fn exec_io<'a>( - devices: &mut BTreeMap>, + devices: &mut BTreeMap, NonZero)>, mut io: impl CpuIo, ) -> Result<(), Box> { // Get target device. let addr = io.addr(); - let (_, dev) = devices.range_mut(..=addr).last().unwrap(); + let (_, (dev, end)) = devices.range_mut(..=addr).last().unwrap(); + + assert!(addr < end.get()); dev.exec(&mut io) } +#[cfg(unix)] +fn get_page_size() -> Result, std::io::Error> { + let v = unsafe { libc::sysconf(libc::_SC_PAGE_SIZE) }; + + if v < 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(v.try_into().ok().and_then(NonZero::new).unwrap()) + } +} + +#[cfg(windows)] +fn get_page_size() -> Result, std::io::Error> { + use std::mem::zeroed; + use windows_sys::Win32::System::SystemInformation::GetSystemInfo; + let mut i = unsafe { zeroed() }; + + unsafe { GetSystemInfo(&mut i) }; + + Ok(i.dwPageSize.try_into().ok().and_then(NonZero::new).unwrap()) +} + /// Manage a virtual machine that run the kernel. pub struct Vmm { cpus: Vec>, @@ -543,12 +586,6 @@ struct CpuArgs { /// Represents an error when [`vmm_new()`] fails. #[derive(Debug, Error)] enum VmmError { - #[error("couldn't get page size of the host")] - GetPageSizeFailed(#[source] std::io::Error), - - #[error("host system is using an unsupported page size")] - UnsupportedPageSize, - #[error("couldn't create a RAM")] CreateRamFailed(#[source] std::io::Error),