diff options
-rw-r--r-- | hypervisor/Cargo.toml | 3 | ||||
-rw-r--r-- | hypervisor/src/kvm/aarch64.rs | 2 | ||||
-rw-r--r-- | hypervisor/src/kvm/mod.rs | 167 | ||||
-rw-r--r-- | hypervisor/src/lib.rs | 29 | ||||
-rw-r--r-- | sys_util/src/descriptor.rs | 12 |
5 files changed, 194 insertions, 19 deletions
diff --git a/hypervisor/Cargo.toml b/hypervisor/Cargo.toml index a68f315..8f07d5d 100644 --- a/hypervisor/Cargo.toml +++ b/hypervisor/Cargo.toml @@ -8,4 +8,5 @@ edition = "2018" libc = "*" kvm = { path = "../kvm" } kvm_sys = { path = "../kvm_sys" } -sys_util = { path = "../sys_util" } \ No newline at end of file +sync = { path = "../sync" } +sys_util = { path = "../sys_util" } diff --git a/hypervisor/src/kvm/aarch64.rs b/hypervisor/src/kvm/aarch64.rs index 6e9f9f7..4f0398f 100644 --- a/hypervisor/src/kvm/aarch64.rs +++ b/hypervisor/src/kvm/aarch64.rs @@ -16,7 +16,7 @@ impl VmAArch64 for KvmVm { } impl VcpuAArch64 for KvmVcpu { - fn set_one_reg(&self, reg_id: u64, data: u64) -> Result<()> { + fn set_one_reg(&self, _reg_id: u64, _data: u64) -> Result<()> { Ok(()) } } diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index ede1391..0550792 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -7,18 +7,57 @@ mod aarch64; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] mod x86_64; -use kvm_sys::*; -use libc::{open, O_CLOEXEC, O_RDWR}; +use std::cmp::Ordering; +use std::collections::{BinaryHeap, HashMap}; use std::convert::TryFrom; use std::ops::{Deref, DerefMut}; use std::os::raw::{c_char, c_ulong}; use std::os::unix::io::{AsRawFd, RawFd}; +use std::sync::Arc; + +use libc::{open, O_CLOEXEC, O_RDWR}; + +use kvm_sys::*; +use sync::Mutex; use sys_util::{ - errno_result, ioctl_with_val, AsRawDescriptor, Error, FromRawDescriptor, GuestMemory, - RawDescriptor, Result, SafeDescriptor, + errno_result, ioctl, ioctl_with_ref, ioctl_with_val, AsRawDescriptor, Error, FromRawDescriptor, + GuestMemory, RawDescriptor, Result, SafeDescriptor, }; -use crate::{Hypervisor, HypervisorCap, RunnableVcpu, Vcpu, VcpuExit, Vm}; +use crate::{Hypervisor, HypervisorCap, MappedRegion, RunnableVcpu, Vcpu, VcpuExit, Vm}; + +// Wrapper around KVM_SET_USER_MEMORY_REGION ioctl, which creates, modifies, or deletes a mapping +// from guest physical to host user pages. +// +// Safe when the guest regions are guaranteed not to overlap. +unsafe fn set_user_memory_region( + descriptor: &SafeDescriptor, + slot: u32, + read_only: bool, + log_dirty_pages: bool, + guest_addr: u64, + memory_size: u64, + userspace_addr: *mut u8, +) -> Result<()> { + let mut flags = if read_only { KVM_MEM_READONLY } else { 0 }; + if log_dirty_pages { + flags |= KVM_MEM_LOG_DIRTY_PAGES; + } + let region = kvm_userspace_memory_region { + slot, + flags, + guest_phys_addr: guest_addr, + memory_size, + userspace_addr: userspace_addr as u64, + }; + + let ret = ioctl_with_ref(descriptor, KVM_SET_USER_MEMORY_REGION(), ®ion); + if ret == 0 { + Ok(()) + } else { + errno_result() + } +} pub struct Kvm { kvm: SafeDescriptor, @@ -67,15 +106,64 @@ impl Hypervisor for Kvm { } } +// Used to invert the order when stored in a max-heap. +#[derive(Copy, Clone, Eq, PartialEq)] +struct MemSlot(u32); + +impl Ord for MemSlot { + fn cmp(&self, other: &MemSlot) -> Ordering { + // Notice the order is inverted so the lowest magnitude slot has the highest priority in a + // max-heap. + other.0.cmp(&self.0) + } +} + +impl PartialOrd for MemSlot { + fn partial_cmp(&self, other: &MemSlot) -> Option<Ordering> { + Some(self.cmp(other)) + } +} + /// A wrapper around creating and using a KVM VM. pub struct KvmVm { + vm: SafeDescriptor, guest_mem: GuestMemory, + mem_regions: Arc<Mutex<HashMap<u32, Box<dyn MappedRegion>>>>, + mem_slot_gaps: Arc<Mutex<BinaryHeap<MemSlot>>>, } impl KvmVm { /// Constructs a new `KvmVm` using the given `Kvm` instance. - pub fn new(_kvm: &Kvm, guest_mem: GuestMemory) -> Result<KvmVm> { - Ok(KvmVm { guest_mem }) + pub fn new(kvm: &Kvm, guest_mem: GuestMemory) -> Result<KvmVm> { + // Safe because we know kvm is a real kvm fd as this module is the only one that can make + // Kvm objects. + let ret = unsafe { ioctl(kvm, KVM_CREATE_VM()) }; + if ret < 0 { + return errno_result(); + } + // Safe because we verify that ret is valid and we own the fd. + let vm_descriptor = unsafe { SafeDescriptor::from_raw_descriptor(ret) }; + guest_mem.with_regions(|index, guest_addr, size, host_addr, _| { + unsafe { + // Safe because the guest regions are guaranteed not to overlap. + set_user_memory_region( + &vm_descriptor, + index as u32, + false, + false, + guest_addr.offset() as u64, + size as u64, + host_addr as *mut u8, + ) + } + })?; + // TODO(colindr/srichman): add default IRQ routes in IrqChip constructor or configure_vm + Ok(KvmVm { + vm: vm_descriptor, + guest_mem, + mem_regions: Arc::new(Mutex::new(HashMap::new())), + mem_slot_gaps: Arc::new(Mutex::new(BinaryHeap::new())), + }) } fn create_kvm_vcpu(&self, _id: usize) -> Result<KvmVcpu> { @@ -84,11 +172,32 @@ impl KvmVm { } impl Vm for KvmVm { - fn get_guest_mem(&self) -> &GuestMemory { + fn try_clone(&self) -> Result<Self> { + Ok(KvmVm { + vm: self.vm.try_clone()?, + guest_mem: self.guest_mem.clone(), + mem_regions: self.mem_regions.clone(), + mem_slot_gaps: self.mem_slot_gaps.clone(), + }) + } + + fn get_memory(&self) -> &GuestMemory { &self.guest_mem } } +impl AsRawDescriptor for KvmVm { + fn as_raw_descriptor(&self) -> RawDescriptor { + self.vm.as_raw_descriptor() + } +} + +impl AsRawFd for KvmVm { + fn as_raw_fd(&self) -> RawFd { + self.vm.as_raw_descriptor() + } +} + /// A wrapper around creating and using a KVM Vcpu. pub struct KvmVcpu {} @@ -154,7 +263,9 @@ impl<'a> TryFrom<&'a HypervisorCap> for KvmCap { #[cfg(test)] mod tests { - use super::{Hypervisor, HypervisorCap, Kvm}; + use super::*; + use std::thread; + use sys_util::GuestAddress; #[test] fn new() { @@ -167,4 +278,42 @@ mod tests { assert!(kvm.check_capability(&HypervisorCap::UserMemory)); assert!(!kvm.check_capability(&HypervisorCap::S390UserSigp)); } + + #[test] + fn create_vm() { + let kvm = Kvm::new().unwrap(); + let gm = GuestMemory::new(&[(GuestAddress(0), 0x1000)]).unwrap(); + KvmVm::new(&kvm, gm).unwrap(); + } + + #[test] + fn clone_vm() { + let kvm = Kvm::new().unwrap(); + let gm = GuestMemory::new(&[(GuestAddress(0), 0x1000)]).unwrap(); + let vm = KvmVm::new(&kvm, gm).unwrap(); + vm.try_clone().unwrap(); + } + + #[test] + fn send_vm() { + let kvm = Kvm::new().unwrap(); + let gm = GuestMemory::new(&[(GuestAddress(0), 0x1000)]).unwrap(); + let vm = KvmVm::new(&kvm, gm).unwrap(); + thread::spawn(move || { + let _vm = vm; + }) + .join() + .unwrap(); + } + + #[test] + fn get_memory() { + let kvm = Kvm::new().unwrap(); + let gm = GuestMemory::new(&[(GuestAddress(0), 0x1000)]).unwrap(); + let vm = KvmVm::new(&kvm, gm).unwrap(); + let obj_addr = GuestAddress(0xf0); + vm.get_memory().write_obj_at_addr(67u8, obj_addr).unwrap(); + let read_val: u8 = vm.get_memory().read_obj_from_addr(obj_addr).unwrap(); + assert_eq!(read_val, 67u8); + } } diff --git a/hypervisor/src/lib.rs b/hypervisor/src/lib.rs index f17528b..784af8c 100644 --- a/hypervisor/src/lib.rs +++ b/hypervisor/src/lib.rs @@ -27,15 +27,18 @@ pub trait Hypervisor { } /// A wrapper for using a VM and getting/setting its state. -pub trait Vm { - // Gets the guest-mapped memory for the Vm - fn get_guest_mem(&self) -> &GuestMemory; +pub trait Vm: Send + Sized { + /// Makes a shallow clone this `Vm`. + fn try_clone(&self) -> Result<Self>; + + /// Gets the guest-mapped memory for the Vm. + fn get_memory(&self) -> &GuestMemory; } -/// A wrapper around creating and using a VCPU. +/// A wrapper around using a VCPU. /// `Vcpu` provides all functionality except for running. To run, `to_runnable` must be called to /// lock the vcpu to a thread. Then the returned `RunnableVcpu` can be used for running. -pub trait Vcpu { +pub trait Vcpu: Send + Sized { type Runnable: RunnableVcpu; /// Consumes `self` and returns a `RunnableVcpu`. A `RunnableVcpu` is required to run the guest. @@ -58,6 +61,22 @@ pub trait RunnableVcpu: Deref<Target = <Self as RunnableVcpu>::Vcpu> + DerefMut fn run(&self) -> Result<VcpuExit>; } +/// A memory region in the current process that can be mapped into the guest's memory. +/// +/// Safe when implementers guarantee `ptr`..`ptr+size` is an mmaped region owned by this object that +/// can't be unmapped during the `MappedRegion`'s lifetime. +pub unsafe trait MappedRegion: Send + Sync { + /// Returns a pointer to the beginning of the memory region. Should only be + /// used for passing this region to ioctls for setting guest memory. + fn as_ptr(&self) -> *mut u8; + + /// Returns the size of the memory region in bytes. + fn size(&self) -> usize; + + /// Flushes changes to this memory region to the backing file. + fn msync(&self) -> Result<()>; +} + /// A reason why a VCPU exited. One of these returns every time `Vcpu::run` is called. #[derive(Debug)] pub enum VcpuExit { diff --git a/sys_util/src/descriptor.rs b/sys_util/src/descriptor.rs index 1af72fc..9082394 100644 --- a/sys_util/src/descriptor.rs +++ b/sys_util/src/descriptor.rs @@ -2,11 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -use std::os::unix::io::RawFd; - -use crate::{errno_result, Result}; use std::mem; use std::ops::Drop; +use std::os::unix::io::{AsRawFd, RawFd}; + +use crate::{errno_result, Result}; pub type RawDescriptor = RawFd; @@ -60,6 +60,12 @@ impl FromRawDescriptor for SafeDescriptor { } } +impl AsRawFd for SafeDescriptor { + fn as_raw_fd(&self) -> RawFd { + self.as_raw_descriptor() + } +} + impl SafeDescriptor { /// Clones this descriptor, internally creating a new descriptor. The new SafeDescriptor will /// share the same underlying count within the kernel. |