diff --git a/src/core.h b/src/core.h index fcbf58293..9bc5b2784 100644 --- a/src/core.h +++ b/src/core.h @@ -13,12 +13,6 @@ struct Param; struct Pkg; -#define Ram_ADDR 0 - -#define Ram_SIZE (((1024 * 1024) * 1024) * 8) - -#define Ram_VM_PAGE_SIZE 16384 - /** * Error object managed by Rust side. */ @@ -146,6 +140,10 @@ extern int kvm_get_sregs(int vcpu, kvm_sregs *regs); extern int kvm_set_sregs(int vcpu, const kvm_sregs *regs); #endif +#if defined(__linux__) +extern int kvm_translate(int vcpu, kvm_translation *arg); +#endif + #ifdef __cplusplus } // extern "C" #endif // __cplusplus diff --git a/src/core/build.rs b/src/core/build.rs index dbd1b80eb..00f4098a7 100644 --- a/src/core/build.rs +++ b/src/core/build.rs @@ -30,12 +30,16 @@ fn main() { conf.usize_is_size_t = true; conf.export.exclude.push("KvmRegs".into()); conf.export.exclude.push("KvmSpecialRegs".into()); + conf.export.exclude.push("KvmTranslation".into()); conf.export .rename .insert("KvmRegs".into(), "kvm_regs".into()); conf.export .rename .insert("KvmSpecialRegs".into(), "kvm_sregs".into()); + conf.export + .rename + .insert("KvmTranslation".into(), "kvm_translation".into()); conf.defines .insert("target_os = linux".into(), "__linux__".into()); conf.defines diff --git a/src/core/src/vmm/hv/linux/cpu.rs b/src/core/src/vmm/hv/linux/cpu.rs index b325b3d3c..89bf0bdef 100644 --- a/src/core/src/vmm/hv/linux/cpu.rs +++ b/src/core/src/vmm/hv/linux/cpu.rs @@ -1,8 +1,12 @@ -use super::ffi::{kvm_get_regs, kvm_get_sregs, kvm_run, kvm_set_regs, kvm_set_sregs}; +use super::ffi::{ + kvm_get_regs, kvm_get_sregs, kvm_run, kvm_set_regs, kvm_set_sregs, kvm_translate, + KvmTranslation, +}; use super::regs::{KvmRegs, KvmSpecialRegs}; use super::run::KvmRun; -use crate::vmm::hv::{Cpu, CpuExit, CpuIo, CpuStates}; +use crate::vmm::hv::{Cpu, CpuExit, CpuIo, CpuStates, IoBuf}; use libc::munmap; +use std::error::Error; use std::marker::PhantomData; use std::mem::MaybeUninit; use std::os::fd::{AsRawFd, OwnedFd}; @@ -43,7 +47,7 @@ impl<'a> Drop for KvmCpu<'a> { impl<'a> Cpu for KvmCpu<'a> { type States<'b> = KvmStates<'b> where Self: 'b; type GetStatesErr = StatesError; - type Exit<'b> = KvmExit<'b> where Self: 'b; + type Exit<'b> = KvmExit<'b, 'a> where Self: 'b; type RunErr = std::io::Error; fn states(&mut self) -> Result, Self::GetStatesErr> { @@ -74,9 +78,7 @@ impl<'a> Cpu for KvmCpu<'a> { fn run(&mut self) -> Result, Self::RunErr> { match unsafe { kvm_run(self.fd.as_raw_fd()) } { - 0 => Ok(KvmExit { - cx: unsafe { &*self.cx.0 }, - }), + 0 => Ok(KvmExit(self)), _ => Err(std::io::Error::last_os_error()), } } @@ -204,34 +206,62 @@ impl<'a> CpuStates for KvmStates<'a> { } /// Implementation of [`Cpu::Exit`] for KVM. -pub struct KvmExit<'a> { - cx: &'a KvmRun, -} +pub struct KvmExit<'a, 'b>(&'a mut KvmCpu<'b>); + +impl<'a, 'b> CpuExit for KvmExit<'a, 'b> { + type Io = KvmIo<'a, 'b>; -impl<'a> CpuExit for KvmExit<'a> { #[cfg(target_arch = "x86_64")] - fn is_hlt(&self) -> bool { - self.cx.exit_reason == 5 + fn into_hlt(self) -> Result<(), Self> { + if unsafe { (*self.0.cx.0).exit_reason == 5 } { + Ok(()) + } else { + Err(self) + } } - #[cfg(target_arch = "x86_64")] - fn is_io(&mut self) -> Option { - // Check if I/O. - if self.cx.exit_reason != 2 { - return None; + fn into_io(self) -> Result { + if unsafe { (*self.0.cx.0).exit_reason } == 6 { + Ok(KvmIo(self.0)) + } else { + Err(self) } + } +} - // Check direction. - let io = unsafe { &self.cx.exit.io }; - let port = io.port; - let data = unsafe { (self.cx as *const KvmRun as *const u8).add(io.data_offset) }; - let len: usize = io.size.into(); +/// Implementation of [`CpuIo`] for KVM. +pub struct KvmIo<'a, 'b>(&'a mut KvmCpu<'b>); - Some(match io.direction { - 0 => todo!(), // KVM_EXIT_IO_IN - 1 => CpuIo::Out(port, unsafe { std::slice::from_raw_parts(data, len) }), - _ => unreachable!(), - }) +impl<'a, 'b> CpuIo for KvmIo<'a, 'b> { + fn addr(&self) -> usize { + unsafe { (*self.0.cx.0).exit.mmio.phys_addr } + } + + fn buffer(&mut self) -> IoBuf { + let io = unsafe { &mut (*self.0.cx.0).exit.mmio }; + let len: usize = io.len.try_into().unwrap(); + let buf = &mut io.data[..len]; + + match io.is_write { + 0 => IoBuf::Read(buf), + _ => IoBuf::Write(buf), + } + } + + fn translate(&self, vaddr: usize) -> Result> { + let mut data = KvmTranslation { + linear_address: vaddr, + physical_address: 0, + valid: 0, + writeable: 0, + usermode: 0, + pad: [0; 5], + }; + + match unsafe { kvm_translate(self.0.fd.as_raw_fd(), &mut data) } { + 0 => Ok(data.physical_address), + _ => return Err(Box::new(std::io::Error::last_os_error())), + } } } diff --git a/src/core/src/vmm/hv/linux/ffi.rs b/src/core/src/vmm/hv/linux/ffi.rs index 745756056..9887f406d 100644 --- a/src/core/src/vmm/hv/linux/ffi.rs +++ b/src/core/src/vmm/hv/linux/ffi.rs @@ -21,4 +21,15 @@ extern "C" { pub fn kvm_set_regs(vcpu: c_int, regs: *const KvmRegs) -> c_int; pub fn kvm_get_sregs(vcpu: c_int, regs: *mut KvmSpecialRegs) -> c_int; pub fn kvm_set_sregs(vcpu: c_int, regs: *const KvmSpecialRegs) -> c_int; + pub fn kvm_translate(vcpu: c_int, arg: *mut KvmTranslation) -> c_int; +} + +#[repr(C)] +pub struct KvmTranslation { + pub linear_address: usize, + pub physical_address: usize, + pub valid: u8, + pub writeable: u8, + pub usermode: u8, + pub pad: [u8; 5], } diff --git a/src/core/src/vmm/hv/linux/mod.rs b/src/core/src/vmm/hv/linux/mod.rs index f410ba75b..d4f4c36eb 100644 --- a/src/core/src/vmm/hv/linux/mod.rs +++ b/src/core/src/vmm/hv/linux/mod.rs @@ -5,7 +5,7 @@ use self::ffi::{ }; use super::Hypervisor; use crate::vmm::hw::Ram; -use crate::vmm::{MemoryAddr, VmmError}; +use crate::vmm::VmmError; use libc::{mmap, open, MAP_FAILED, MAP_PRIVATE, O_RDWR, PROT_READ, PROT_WRITE}; use std::os::fd::{AsRawFd, FromRawFd, OwnedFd}; use std::ptr::null_mut; @@ -83,7 +83,7 @@ impl Kvm { // Set RAM. let vm = unsafe { OwnedFd::from_raw_fd(vm) }; let slot = 0; - let addr = ram.vm_addr().try_into().unwrap(); + let addr = ram.addr().try_into().unwrap(); let len = ram.len().try_into().unwrap(); let mem = ram.host_addr().cast_mut().cast(); diff --git a/src/core/src/vmm/hv/linux/run.rs b/src/core/src/vmm/hv/linux/run.rs index 4de1061aa..be730b75b 100644 --- a/src/core/src/vmm/hv/linux/run.rs +++ b/src/core/src/vmm/hv/linux/run.rs @@ -23,7 +23,7 @@ pub union Exit { ex: ManuallyDrop, pub io: Io, debug: ManuallyDrop, - mmio: ManuallyDrop, + pub mmio: Mmio, iocsr_io: ManuallyDrop, hypercall: ManuallyDrop, tpr_access: ManuallyDrop, @@ -92,11 +92,12 @@ struct KvmDebugExitArch { } #[repr(C)] -struct Mmio { - phys_addr: u64, - data: [u8; 8], - len: u32, - is_write: u8, +#[derive(Clone, Copy)] +pub struct Mmio { + pub phys_addr: usize, + pub data: [u8; 8], + pub len: u32, + pub is_write: u8, } #[repr(C)] diff --git a/src/core/src/vmm/hv/macos/cpu.rs b/src/core/src/vmm/hv/macos/cpu.rs index 36b85a338..b7ab96789 100644 --- a/src/core/src/vmm/hv/macos/cpu.rs +++ b/src/core/src/vmm/hv/macos/cpu.rs @@ -1,5 +1,6 @@ -use crate::vmm::hv::{Cpu, CpuExit, CpuStates}; +use crate::vmm::hv::{Cpu, CpuExit, CpuIo, CpuStates, IoBuf}; use hv_sys::hv_vcpu_destroy; +use std::error::Error; use std::marker::PhantomData; use std::mem::MaybeUninit; use std::num::NonZero; @@ -352,22 +353,38 @@ pub struct HfExit<'a> { } impl<'a> CpuExit for HfExit<'a> { + type Io = HfIo; + #[cfg(target_arch = "x86_64")] - fn is_hlt(&self) -> bool { + fn into_hlt(self) -> Result<(), Self> { match self.exit_reason.try_into() { - Ok(hv_sys::VMX_REASON_HLT) => true, - _ => false, + Ok(hv_sys::VMX_REASON_HLT) => Ok(()), + _ => Err(self), } } - #[cfg(target_arch = "x86_64")] - fn is_io(&mut self) -> Option { - match self.exit_reason.try_into() { - Ok(hv_sys::VMX_REASON_IO) => todo!(), - _ => None, - } + fn into_io(self) -> Result { + todo!(); } } + +/// Implementation of [`CpuIo`] for Hypervisor Framework. +pub struct HfIo {} + +impl CpuIo for HfIo { + fn addr(&self) -> usize { + todo!(); + } + + fn buffer(&mut self) -> IoBuf { + todo!(); + } + + fn translate(&self, vaddr: usize) -> Result> { + todo!(); + } +} + /// Implementation of [`Cpu::RunErr`]. #[derive(Debug, Error)] pub enum RunError { diff --git a/src/core/src/vmm/hv/macos/mod.rs b/src/core/src/vmm/hv/macos/mod.rs index fb75ec579..97d70859f 100644 --- a/src/core/src/vmm/hv/macos/mod.rs +++ b/src/core/src/vmm/hv/macos/mod.rs @@ -2,7 +2,7 @@ use self::cpu::HfCpu; use self::vm::Vm; use super::Hypervisor; use crate::vmm::hw::Ram; -use crate::vmm::{MemoryAddr, VmmError}; +use crate::vmm::VmmError; use hv_sys::hv_vcpu_create; use std::ffi::c_int; use std::num::NonZero; @@ -28,7 +28,7 @@ impl Hf { // Map memory. vm.vm_map( ram.host_addr().cast_mut().cast(), - ram.vm_addr().try_into().unwrap(), + ram.addr().try_into().unwrap(), ram.len(), ) .map_err(VmmError::MapRamFailed)?; diff --git a/src/core/src/vmm/hv/mod.rs b/src/core/src/vmm/hv/mod.rs index fe3748db9..c3718d818 100644 --- a/src/core/src/vmm/hv/mod.rs +++ b/src/core/src/vmm/hv/mod.rs @@ -97,16 +97,25 @@ pub trait CpuStates { } /// Contains information when VM exited. -pub trait CpuExit { - #[cfg(target_arch = "x86_64")] - fn is_hlt(&self) -> bool; +pub trait CpuExit: Sized { + type Io: CpuIo; #[cfg(target_arch = "x86_64")] - fn is_io(&mut self) -> Option; + fn into_hlt(self) -> Result<(), Self>; + + fn into_io(self) -> Result; +} + +/// Contains information when a VM exited because of memory-mapped I/O. +pub trait CpuIo { + /// Returns physical address where the VM attempting to be accessed. + fn addr(&self) -> usize; + fn buffer(&mut self) -> IoBuf; + fn translate(&self, vaddr: usize) -> Result>; } -/// Contains information when a VM exited because of I/O instructions. -#[cfg(target_arch = "x86_64")] -pub enum CpuIo<'a> { - Out(u16, &'a [u8]), +/// Encapsulates a buffer for memory-mapped I/O. +pub enum IoBuf<'a> { + Write(&'a [u8]), + Read(&'a mut [u8]), } diff --git a/src/core/src/vmm/hv/windows/cpu.rs b/src/core/src/vmm/hv/windows/cpu.rs index af4933e65..c7e335fb2 100644 --- a/src/core/src/vmm/hv/windows/cpu.rs +++ b/src/core/src/vmm/hv/windows/cpu.rs @@ -1,4 +1,5 @@ -use crate::vmm::hv::{Cpu, CpuExit, CpuIo, CpuStates}; +use crate::vmm::hv::{Cpu, CpuExit, CpuIo, CpuStates, IoBuf}; +use std::error::Error; use std::marker::PhantomData; use std::mem::{size_of, zeroed, MaybeUninit}; use thiserror::Error; @@ -273,13 +274,37 @@ pub struct WhpExit<'a, 'b> { } impl<'a, 'b> CpuExit for WhpExit<'a, 'b> { + type Io = WhpIo<'a, 'b>; + #[cfg(target_arch = "x86_64")] - fn is_hlt(&self) -> bool { - self.cx.ExitReason == WHvRunVpExitReasonX64Halt + fn into_hlt(self) -> Result<(), Self> { + if self.cx.ExitReason == WHvRunVpExitReasonX64Halt { + Ok(()) + } else { + Err(self) + } } - #[cfg(target_arch = "x86_64")] - fn is_io(&mut self) -> Option { + fn into_io(self) -> Result { + todo!(); + } +} + +/// Implementation of [`CpuIo`] for Windows Hypervisor Platform. +pub struct WhpIo<'a, 'b> { + cpu: PhantomData<&'a mut WhpCpu<'b>>, +} + +impl<'a, 'b> CpuIo for WhpIo<'a, 'b> { + fn addr(&self) -> usize { + todo!(); + } + + fn buffer(&mut self) -> IoBuf { + todo!(); + } + + fn translate(&self, vaddr: usize) -> Result> { todo!() } } diff --git a/src/core/src/vmm/hv/windows/mod.rs b/src/core/src/vmm/hv/windows/mod.rs index ec213c5f2..54e3e2934 100644 --- a/src/core/src/vmm/hv/windows/mod.rs +++ b/src/core/src/vmm/hv/windows/mod.rs @@ -2,7 +2,7 @@ use self::cpu::WhpCpu; use self::partition::Partition; use super::Hypervisor; use crate::vmm::hw::Ram; -use crate::vmm::{MemoryAddr, VmmError}; +use crate::vmm::VmmError; use std::sync::Arc; use thiserror::Error; use windows_sys::core::HRESULT; @@ -30,7 +30,7 @@ impl Whp { // Map memory. part.map_gpa( ram.host_addr().cast(), - ram.vm_addr().try_into().unwrap(), + ram.addr().try_into().unwrap(), ram.len().try_into().unwrap(), ) .map_err(VmmError::MapRamFailed)?; diff --git a/src/core/src/vmm/hw/console/context.rs b/src/core/src/vmm/hw/console/context.rs new file mode 100644 index 000000000..2486908d1 --- /dev/null +++ b/src/core/src/vmm/hw/console/context.rs @@ -0,0 +1,145 @@ +use super::{Console, Log}; +use crate::vmm::hv::{CpuIo, IoBuf}; +use crate::vmm::hw::{DeviceContext, Ram}; +use obvirt::console::{Commit, Memory}; +use std::error::Error; +use std::mem::offset_of; +use thiserror::Error; + +/// Implementation of [`DeviceContext`]. +pub struct Context<'a> { + dev: &'a Console, + ram: &'a Ram, + file_len: usize, + file: String, + msg_len: usize, + msg: String, +} + +impl<'a> Context<'a> { + pub fn new(dev: &'a Console, ram: &'a Ram) -> Self { + Self { + dev, + ram, + file_len: 0, + file: String::new(), + msg_len: 0, + msg: String::new(), + } + } + + fn read_u32(&self, off: usize, exit: &mut dyn CpuIo) -> Result { + // Get data. + let data = match exit.buffer() { + IoBuf::Write(v) => v, + IoBuf::Read(_) => return Err(ExecError::ReadNotSupported(off)), + }; + + // Parse data. + data.try_into() + .map(|v| u32::from_ne_bytes(v)) + .map_err(|_| ExecError::InvalidData(off)) + } + + fn read_usize(&self, off: usize, exit: &mut dyn CpuIo) -> Result { + // Get data. + let data = match exit.buffer() { + IoBuf::Write(v) => v, + IoBuf::Read(_) => return Err(ExecError::ReadNotSupported(off)), + }; + + // Parse data. + data.try_into() + .map(|v| usize::from_ne_bytes(v)) + .map_err(|_| ExecError::InvalidData(off)) + } + + fn read_str<'b>( + &self, + off: usize, + exit: &'b mut dyn CpuIo, + len: usize, + ) -> Result<&'b str, ExecError> { + // Get data. + let buf = match exit.buffer() { + IoBuf::Write(v) => v, + IoBuf::Read(_) => return Err(ExecError::ReadNotSupported(off)), + }; + + // Get address. + let vaddr = buf + .try_into() + .map(|v| usize::from_ne_bytes(v)) + .map_err(|_| ExecError::InvalidData(off))?; + let paddr = exit + .translate(vaddr) + .map_err(|e| ExecError::TranslateVaddrFailed(vaddr, e))?; + + // Read data. + let data = unsafe { self.ram.host_addr().add(paddr) }; + let data = unsafe { std::slice::from_raw_parts(data, len) }; + + Ok(std::str::from_utf8(data).unwrap()) + } +} + +impl<'a> DeviceContext for Context<'a> { + fn exec(&mut self, exit: &mut dyn CpuIo) -> Result<(), Box> { + // Check field. + let off = exit.addr() - self.dev.addr; + + if off == offset_of!(Memory, msg_len) { + self.msg_len = self.read_usize(off, exit)?; + } else if off == offset_of!(Memory, msg_addr) { + self.msg.push_str(self.read_str(off, exit, self.msg_len)?); + } else if off == offset_of!(Memory, file_len) { + self.file_len = self.read_usize(off, exit)?; + } else if off == offset_of!(Memory, file_addr) { + self.file = self.read_str(off, exit, self.file_len)?.to_owned(); + } else if off == offset_of!(Memory, commit) { + // Parse data. + let commit = self.read_u32(off, exit)?; + let (ty, line) = match Commit::parse(commit) { + Some(v) => v, + None => return Err(Box::new(ExecError::InvalidCommit(commit))), + }; + + // Push log. + let mut logs = self.dev.logs.lock().unwrap(); + + logs.push_back(Log { + ty, + file: std::mem::take(&mut self.file), + line, + msg: std::mem::take(&mut self.msg), + }); + + while logs.len() > 10000 { + logs.pop_front(); + } + } else { + return Err(Box::new(ExecError::UnknownField(off))); + } + + Ok(()) + } +} + +/// Represents an error when [`Context::exec()`] fails. +#[derive(Debug, Error)] +enum ExecError { + #[error("unknown field at offset {0:#}")] + UnknownField(usize), + + #[error("read at offset {0:#} is not supported")] + ReadNotSupported(usize), + + #[error("invalid data for offset {0:#}")] + InvalidData(usize), + + #[error("couldn't translate {0:#x} to physical address")] + TranslateVaddrFailed(usize, #[source] Box), + + #[error("{0:#} is not a valid commit")] + InvalidCommit(u32), +} diff --git a/src/core/src/vmm/hw/console/mod.rs b/src/core/src/vmm/hw/console/mod.rs new file mode 100644 index 000000000..4bd9ed934 --- /dev/null +++ b/src/core/src/vmm/hw/console/mod.rs @@ -0,0 +1,47 @@ +use self::context::Context; +use super::{Device, DeviceContext, Ram, PAGE_SIZE}; +use obvirt::console::MsgType; +use std::collections::VecDeque; +use std::num::NonZero; +use std::sync::Mutex; + +mod context; + +/// Virtual console for the VM. +pub struct Console { + addr: usize, + logs: Mutex>, +} + +impl Console { + pub(crate) const SIZE: NonZero = PAGE_SIZE; + + pub fn new(addr: usize) -> Self { + Self { + addr, + logs: Mutex::default(), + } + } +} + +impl Device for Console { + fn addr(&self) -> usize { + self.addr + } + + fn len(&self) -> NonZero { + Self::SIZE + } + + fn create_context<'a>(&'a self, ram: &'a Ram) -> Box { + Box::new(Context::new(self, ram)) + } +} + +/// Contains data for each logging entry. +struct Log { + ty: MsgType, + file: String, + line: u32, + msg: String, +} diff --git a/src/core/src/vmm/hw/mod.rs b/src/core/src/vmm/hw/mod.rs index 93a9cf835..a7a18bc88 100644 --- a/src/core/src/vmm/hw/mod.rs +++ b/src/core/src/vmm/hw/mod.rs @@ -1,3 +1,66 @@ +use crate::vmm::hv::CpuIo; +use std::collections::BTreeMap; +use std::error::Error; +use std::num::NonZero; +use std::sync::Arc; + +pub use self::console::*; 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 { + let mut map = BTreeMap::>::new(); + + // Console. + let addr = start_addr; + let console = Arc::new(Console::new(addr)); + + assert!(map.insert(console.addr(), console.clone()).is_none()); + + // Make sure nothing are overlapped. + let mut end = start_addr; + + for (addr, dev) in &map { + assert!(*addr >= end); + end = addr.checked_add(dev.len().get()).unwrap(); + } + + DeviceTree { console, map } +} + +/// Contains all virtual devices, except RAM; for the VM. +pub struct DeviceTree { + console: Arc, + map: BTreeMap>, +} + +impl DeviceTree { + pub fn console(&self) -> &Console { + &self.console + } + + /// Returns iterator ordered by physical address. + pub fn map(&self) -> impl Iterator + '_ { + self.map.iter().map(|(addr, dev)| (*addr, dev.as_ref())) + } +} + +/// Virtual device that has a physical address in the virtual machine. +pub trait Device: Send + Sync { + /// Physical address in the virtual machine. + fn addr(&self) -> usize; + + /// Total size of device memory, in bytes. + fn len(&self) -> NonZero; + + fn create_context<'a>(&'a self, ram: &'a Ram) -> Box; +} + +/// Context to execute memory-mapped I/O operations on a virtual device. +pub trait DeviceContext { + fn exec(&mut self, exit: &mut dyn CpuIo) -> Result<(), Box>; +} diff --git a/src/core/src/vmm/hw/ram/builder.rs b/src/core/src/vmm/hw/ram/builder.rs index 293d7ea7e..3577e69fd 100644 --- a/src/core/src/vmm/hw/ram/builder.rs +++ b/src/core/src/vmm/hw/ram/builder.rs @@ -1,4 +1,5 @@ use super::{Ram, RamError}; +use crate::vmm::hw::{DeviceTree, PAGE_SIZE}; use crate::vmm::VmmError; use obconf::BootEnv; use std::ops::Range; @@ -21,7 +22,7 @@ impl RamBuilder { // we don't need to keep track allocations here. let page_size = Self::get_page_size().map_err(VmmError::GetPageSizeFailed)?; - if page_size > Ram::VM_PAGE_SIZE { + if page_size > PAGE_SIZE.get() { return Err(VmmError::UnsupportedPageSize); } @@ -103,10 +104,10 @@ 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::() <= Ram::VM_PAGE_SIZE); + assert!(align_of::() <= PAGE_SIZE.get()); // Allocate RAM for all arguments. - let len = size_of::().next_multiple_of(Ram::VM_PAGE_SIZE); + let len = size_of::().next_multiple_of(PAGE_SIZE.get()); let args = unsafe { self.ram.alloc(self.next, len)?.as_mut_ptr() }; // Write env. @@ -127,6 +128,7 @@ impl RamBuilder { #[cfg(target_arch = "x86_64")] pub fn build( mut self, + devices: &DeviceTree, dynamic: Option<(usize, usize)>, ) -> Result<(Ram, RamMap), RamBuilderError> { // For x86-64 we require the kernel to be a Position-Independent Executable so we can map it @@ -144,6 +146,15 @@ impl RamBuilder { .map_err(RamBuilderError::AllocPml4TableFailed)?; let pml4t = unsafe { &mut *pml4t }; + // Setup page tables to map virtual devices. We use identity mapping for virtual devices. + let mut dev_end = 0; + + for (addr, dev) in devices.map() { + let len = dev.len().get(); + self.setup_page_tables(pml4t, addr, addr, len)?; + dev_end = addr + len; + } + // Setup page tables to map virtual address 0xffffffff82200000 to the kernel. // TODO: Implement ASLR. let mut vaddr = 0xffffffff82200000; @@ -154,6 +165,8 @@ impl RamBuilder { .map(|v| (v.start, v.end - v.start)) .unwrap(); + assert!(vaddr >= dev_end); + self.setup_page_tables(pml4t, vaddr, kern_paddr, kern_len)?; vaddr += kern_len; @@ -228,7 +241,11 @@ impl RamBuilder { } #[cfg(target_arch = "aarch64")] - pub fn build(self, dynamic: Option<(usize, usize)>) -> Result<(Ram, RamMap), RamBuilderError> { + pub fn build( + self, + devices: &DeviceTree, + dynamic: Option<(usize, usize)>, + ) -> Result<(Ram, RamMap), RamBuilderError> { todo!() } @@ -278,7 +295,7 @@ impl RamBuilder { paddr: usize, len: usize, ) -> Result<(), RamBuilderError> { - assert_eq!(len % Ram::VM_PAGE_SIZE, 0); + assert_eq!(len % PAGE_SIZE, 0); fn set_page_entry(entry: &mut usize, addr: usize) { assert_eq!(addr & 0x7FF0000000000000, 0); @@ -351,7 +368,7 @@ 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(Ram::VM_PAGE_SIZE); + 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; diff --git a/src/core/src/vmm/hw/ram/mod.rs b/src/core/src/vmm/hw/ram/mod.rs index 0cf11cc96..49102795c 100644 --- a/src/core/src/vmm/hw/ram/mod.rs +++ b/src/core/src/vmm/hw/ram/mod.rs @@ -1,4 +1,4 @@ -use crate::vmm::MemoryAddr; +use super::PAGE_SIZE; use std::io::{Error, ErrorKind}; use thiserror::Error; @@ -15,19 +15,30 @@ mod builder; pub struct Ram(*mut u8); impl Ram { - pub const ADDR: usize = 0; // It seems like RAM on all system always at address 0. - pub const SIZE: usize = 1024 * 1024 * 1024 * 8; // 8GB - pub const VM_PAGE_SIZE: usize = 0x4000; + 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 + } + + pub fn len(&self) -> usize { + Self::SIZE + } /// # Panics - /// If `off` or `len` is not multiply by [`Self::VM_PAGE_SIZE`]. + /// If `off` or `len` is not multiply by [`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 % Self::VM_PAGE_SIZE, 0); - assert_eq!(len % Self::VM_PAGE_SIZE, 0); + assert_eq!(off % PAGE_SIZE, 0); + assert_eq!(len % PAGE_SIZE, 0); if !off.checked_add(len).is_some_and(|v| v <= Self::SIZE) { return Err(RamError::InvalidAddr); @@ -39,13 +50,13 @@ impl Ram { } /// # Panics - /// If `off` or `len` is not multiply by [`Self::VM_PAGE_SIZE`]. + /// If `off` or `len` is not multiply by [`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 % Self::VM_PAGE_SIZE, 0); - assert_eq!(len % Self::VM_PAGE_SIZE, 0); + assert_eq!(off % PAGE_SIZE, 0); + assert_eq!(len % PAGE_SIZE, 0); if off.checked_add(len).unwrap() > Self::SIZE { return Err(Error::from(ErrorKind::InvalidInput)); @@ -140,20 +151,6 @@ impl Drop for Ram { } } -impl MemoryAddr for Ram { - fn vm_addr(&self) -> usize { - Self::ADDR - } - - fn host_addr(&self) -> *const u8 { - self.0 - } - - fn len(&self) -> usize { - Self::SIZE - } -} - unsafe impl Send for Ram {} unsafe impl Sync for Ram {} diff --git a/src/core/src/vmm/mod.rs b/src/core/src/vmm/mod.rs index 3b211ce44..bc37f0951 100644 --- a/src/core/src/vmm/mod.rs +++ b/src/core/src/vmm/mod.rs @@ -1,10 +1,12 @@ -use self::hv::{Cpu, CpuExit, CpuStates, Hypervisor}; -use self::hw::{Ram, RamBuilder, RamMap}; +use self::hv::{Cpu, CpuExit, CpuIo, CpuStates, Hypervisor}; +use self::hw::{ + setup_devices, Device, DeviceContext, DeviceTree, Ram, RamBuilder, RamMap, PAGE_SIZE, +}; use self::screen::Screen; use crate::error::RustError; use obconf::{BootEnv, Vm}; use obvirt::console::MsgType; -use std::collections::VecDeque; +use std::collections::{BTreeMap, VecDeque}; use std::error::Error; use std::ffi::{c_char, c_void, CStr}; use std::fs::File; @@ -142,7 +144,7 @@ pub unsafe extern "C" fn vmm_run( return null_mut(); } - if p_align != Ram::VM_PAGE_SIZE { + if p_align != PAGE_SIZE.get() { *err = RustError::new(format!("unsupported p_align on PT_LOAD {index}")); return null_mut(); } @@ -194,7 +196,7 @@ pub unsafe extern "C" fn vmm_run( len = match p_vaddr .checked_add(p_memsz) - .and_then(|end| end.checked_next_multiple_of(Ram::VM_PAGE_SIZE)) + .and_then(|end| end.checked_next_multiple_of(PAGE_SIZE.get())) { Some(v) => v, None => { @@ -252,8 +254,13 @@ pub unsafe extern "C" fn vmm_run( return null_mut(); } + // Setup virtual devices. + let devices = Arc::new(setup_devices(Ram::SIZE)); + // Allocate arguments. - let env = BootEnv::Vm(Vm {}); + let env = BootEnv::Vm(Vm { + console: devices.console().addr(), + }); if let Err(e) = ram.alloc_args(env) { *err = RustError::with_source("couldn't allocate RAM for arguments", e); @@ -261,7 +268,7 @@ pub unsafe extern "C" fn vmm_run( } // Build RAM. - let (ram, map) = match ram.build(dynamic) { + let (ram, map) = match ram.build(&devices, dynamic) { Ok(v) => v, Err(e) => { *err = RustError::with_source("couldn't build RAM", e); @@ -292,9 +299,10 @@ pub unsafe extern "C" fn vmm_run( let logs = Arc::new(Mutex::new(VecDeque::new())); let shutdown = Arc::new(AtomicBool::new(false)); let args = CpuArgs { - hv: hv.clone(), + hv, + ram, screen: screen.buffer().clone(), - logs: logs.clone(), + devices, shutdown: shutdown.clone(), }; @@ -327,8 +335,6 @@ pub unsafe extern "C" fn vmm_run( // Create VMM. let vmm = Vmm { - hv, - ram, cpus: vec![main], screen, logs, @@ -452,71 +458,51 @@ fn setup_main_cpu(cpu: &mut impl Cpu, entry: usize, map: RamMap) -> Result<(), M .map_err(|e| MainCpuError::CommitCpuStatesFailed(Box::new(e))) } -#[cfg(target_arch = "x86_64")] fn run_cpu(mut cpu: impl Cpu, args: &CpuArgs) { - use self::hv::CpuIo; - - let mut logs = Vec::new(); + let mut devices = args + .devices + .map() + .map(|(addr, dev)| (addr, dev.create_context(&args.ram))) + .collect::>>(); while !args.shutdown.load(Ordering::Relaxed) { - // Run the vCPU and check why VM exit. - let mut exit = cpu.run().unwrap(); - - if let Some(io) = exit.is_io() { - match io { - CpuIo::Out(0, data) => { - logs.extend_from_slice(data); - parse_logs(&args.logs, &mut logs); - } - CpuIo::Out(_, _) => todo!(), - } - } else if !exit.is_hlt() { - todo!() - } - } -} - -#[cfg(target_arch = "aarch64")] -fn run_cpu(mut cpu: impl Cpu, args: &CpuArgs) { - todo!() -} - -#[cfg(target_arch = "x86_64")] -fn parse_logs(logs: &Mutex>, data: &mut Vec) { - // Check minimum size. - let (hdr, msg) = match data.split_at_checked(9) { - Some(v) => v, - None => return, - }; - - // Check if message completed. - let len = usize::from_ne_bytes(hdr[1..].try_into().unwrap()); - let msg = match msg.get(..len) { - Some(v) => v, - None => return, - }; - - // Push to list. - let ty = MsgType::from_u8(hdr[0]).unwrap(); - let msg = std::str::from_utf8(msg).unwrap().to_owned(); - let mut logs = logs.lock().unwrap(); + // Run the vCPU. + let exit = match cpu.run() { + Ok(v) => v, + Err(_) => todo!(), + }; - logs.push_back((ty, msg)); + // Check if HLT. + #[cfg(target_arch = "x86_64")] + let exit = match exit.into_hlt() { + Ok(_) => continue, + Err(v) => v, + }; - while logs.len() > 10000 { - logs.pop_front(); + // Check if I/O. + match exit.into_io() { + Ok(io) => match exec_io(&mut devices, io) { + Ok(_) => continue, + Err(_) => todo!(), + }, + Err(_) => todo!(), + } } +} - drop(logs); +fn exec_io<'a>( + devices: &mut BTreeMap>, + mut io: impl CpuIo, +) -> Result<(), Box> { + // Get target device. + let addr = io.addr(); + let (_, dev) = devices.range_mut(..=addr).last().unwrap(); - // Remove parsed data. - data.drain(..(hdr.len() + len)); + dev.exec(&mut io) } /// Manage a virtual machine that run the kernel. pub struct Vmm { - hv: Arc, - ram: Arc, cpus: Vec>, screen: self::screen::Default, logs: Arc>>, @@ -545,23 +531,12 @@ pub struct VmmScreen { pub view: usize, } -/// Object that has a physical address in the virtual machine. -trait MemoryAddr { - /// Physical address in the virtual machine. - fn vm_addr(&self) -> usize; - - /// Address in our process. - fn host_addr(&self) -> *const u8; - - /// Total size of the object, in bytes. - fn len(&self) -> usize; -} - /// Encapsulates arguments for a function to run a CPU. struct CpuArgs { hv: Arc, + ram: Arc, screen: Arc<::Buffer>, - logs: Arc>>, + devices: Arc, shutdown: Arc, } diff --git a/src/kvm.cpp b/src/kvm.cpp index 8817bb675..c838c7afa 100644 --- a/src/kvm.cpp +++ b/src/kvm.cpp @@ -110,3 +110,7 @@ extern "C" int kvm_set_sregs(int vcpu, const kvm_sregs *regs) { return ioctl(vcpu, KVM_SET_SREGS, regs); } + +extern "C" int kvm_translate(int vcpu, kvm_translation *arg) { + return ioctl(vcpu, KVM_TRANSLATE, arg); +} diff --git a/src/obconf/src/env/vm.rs b/src/obconf/src/env/vm.rs index a85966e08..0f2d1f959 100644 --- a/src/obconf/src/env/vm.rs +++ b/src/obconf/src/env/vm.rs @@ -1,3 +1,6 @@ /// Provides boot information when booting on a Virtual Machine. #[repr(C)] -pub struct Vm {} +pub struct Vm { + /// Physical address of one page for console memory. + pub console: usize, +} diff --git a/src/obkrnl/src/console/mod.rs b/src/obkrnl/src/console/mod.rs index 251210f18..1f9b7c7a8 100644 --- a/src/obkrnl/src/console/mod.rs +++ b/src/obkrnl/src/console/mod.rs @@ -1,40 +1,26 @@ use crate::config::boot_env; +use core::fmt::Arguments; use obconf::BootEnv; use obvirt::console::MsgType; -/// When running inside a VM each call will cause a VM to exit so don't do this in a performance -/// critical path. -pub fn info(msg: impl AsRef) { - match boot_env() { - BootEnv::Vm(_) => print_vm(MsgType::Info, msg), - } -} +mod vm; -#[cfg(target_arch = "x86_64")] -fn print_vm(ty: MsgType, msg: impl AsRef) { - let msg = msg.as_ref(); - let len = msg.len(); - - unsafe { - core::arch::asm!( - "outsb", // ty - "mov rsi, rcx", - "outsd", // len+0 - "outsd", // len+4 - "mov rsi, rax", - "mov rcx, [rcx]", - "rep outsb", // msg - in("dx") 0, // port - in("rsi") &ty, - lateout("rsi") _, - in("rcx") &len, - lateout("rcx") _, - in("rax") msg.as_ptr() - ) +/// Write single line of information log. +/// +/// When running inside a VM each call will cause a VM to exit multiple times so don't do this in a +/// performance critical path. +/// +/// The line should not contains LF character. +#[macro_export] +macro_rules! info { + ($($args:tt)*) => { + $crate::console::info(file!(), line!(), format_args!($($args)*)) }; } -#[cfg(target_arch = "aarch64")] -fn print_vm(_: MsgType, _: impl AsRef) { - todo!() +#[doc(hidden)] +pub fn info(file: &str, line: u32, msg: Arguments) { + match boot_env() { + BootEnv::Vm(env) => self::vm::print(env, MsgType::Info, file, line, msg), + } } diff --git a/src/obkrnl/src/console/vm.rs b/src/obkrnl/src/console/vm.rs new file mode 100644 index 000000000..a528098aa --- /dev/null +++ b/src/obkrnl/src/console/vm.rs @@ -0,0 +1,25 @@ +use core::fmt::{Arguments, Write}; +use core::ptr::{addr_of_mut, write_volatile}; +use obconf::Vm; +use obvirt::console::{Commit, Memory, MsgType}; + +pub fn print(env: &Vm, ty: MsgType, file: &str, line: u32, msg: Arguments) { + let c = env.console as *mut Memory; + + unsafe { write_volatile(addr_of_mut!((*c).file_len), file.len()) }; + unsafe { write_volatile(addr_of_mut!((*c).file_addr), file.as_ptr() as usize) }; + + Writer(c).write_fmt(msg).unwrap(); + + unsafe { write_volatile(addr_of_mut!((*c).commit), Commit::new(ty, line)) }; +} + +struct Writer(*mut Memory); + +impl Write for Writer { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + unsafe { write_volatile(addr_of_mut!((*self.0).msg_len), s.len()) }; + unsafe { write_volatile(addr_of_mut!((*self.0).msg_addr), s.as_ptr() as usize) }; + Ok(()) + } +} diff --git a/src/obkrnl/src/main.rs b/src/obkrnl/src/main.rs index d68826905..2981f96f9 100644 --- a/src/obkrnl/src/main.rs +++ b/src/obkrnl/src/main.rs @@ -2,7 +2,6 @@ #![cfg_attr(not(test), no_main)] use crate::config::set_boot_env; -use crate::console::info; use core::arch::asm; use core::panic::PanicInfo; use obconf::BootEnv; @@ -25,7 +24,7 @@ extern "C" fn _start(env: &'static BootEnv) -> ! { // SAFETY: This is safe because we called it as the first thing here. unsafe { set_boot_env(env) }; - info("Starting Obliteration Kernel."); + info!("Starting Obliteration Kernel."); loop { #[cfg(target_arch = "x86_64")] diff --git a/src/obvirt/src/console/mod.rs b/src/obvirt/src/console/mod.rs index 0fddf203d..2459831b8 100644 --- a/src/obvirt/src/console/mod.rs +++ b/src/obvirt/src/console/mod.rs @@ -1,15 +1,47 @@ +/// Layout of console memory. +/// +/// The sequence of operations on a console memory is per-cpu. The kernel will start each log by: +/// +/// 1. Write [`Self::file_len`] then [`Self::file_addr`]. +/// 2. Write [`Self::msg_len`] then [`Self::msg_addr`]. +/// 3. Repeat step 2 until the whole message has been written. +/// 4. Write [`Self::commit`]. +#[repr(C)] +pub struct Memory { + pub file_len: usize, + pub file_addr: usize, + pub msg_len: usize, + pub msg_addr: usize, + pub commit: Commit, +} + +/// Struct to commit a log. +#[repr(transparent)] +pub struct Commit(u32); + +impl Commit { + /// # Panics + /// If `line` greater than 0xffffff. + pub fn new(ty: MsgType, line: u32) -> Self { + assert!(line <= 0xffffff); + + Self((ty as u32) << 24 | line) + } + + pub fn parse(raw: u32) -> Option<(MsgType, u32)> { + let line = raw & 0xffffff; + let ty = match (raw >> 24) as u8 { + v if v == MsgType::Info as u8 => MsgType::Info, + _ => return None, + }; + + Some((ty, line)) + } +} + /// Type of console message. #[repr(u8)] #[derive(Clone, Copy)] pub enum MsgType { Info, } - -impl MsgType { - pub fn from_u8(v: u8) -> Option { - Some(match v { - v if v == Self::Info as u8 => Self::Info, - _ => return None, - }) - } -}