diff options
author | Dylan Reid <dgreid@chromium.org> | 2017-10-06 15:30:34 -0700 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2017-10-09 17:39:04 -0700 |
commit | 94bf1bf6b4a7791757937e2ffb2a81e797b82922 (patch) | |
tree | 386430a70c5fe176d8ae981aa1ca9b5bfc2d346f /vm_control | |
parent | d0c9adc642fc968cb347952eed1d7fd0d0a8e80e (diff) | |
download | crosvm-94bf1bf6b4a7791757937e2ffb2a81e797b82922.tar crosvm-94bf1bf6b4a7791757937e2ffb2a81e797b82922.tar.gz crosvm-94bf1bf6b4a7791757937e2ffb2a81e797b82922.tar.bz2 crosvm-94bf1bf6b4a7791757937e2ffb2a81e797b82922.tar.lz crosvm-94bf1bf6b4a7791757937e2ffb2a81e797b82922.tar.xz crosvm-94bf1bf6b4a7791757937e2ffb2a81e797b82922.tar.zst crosvm-94bf1bf6b4a7791757937e2ffb2a81e797b82922.zip |
Move vm_control to a top-level crate
Break out vm_control to a crate that will be able to used by more modules. Having vm_control usable from outside crosvm makes it possible to move the devices out of crosvm in a later commit. Change-Id: I1f060700ed49b5d77519d55efa2430490d521256 Signed-off-by: Dylan Reid <dgreid@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/706558 Reviewed-by: Zach Reizner <zachr@chromium.org>
Diffstat (limited to 'vm_control')
-rw-r--r-- | vm_control/Cargo.toml | 10 | ||||
-rw-r--r-- | vm_control/src/lib.rs | 458 |
2 files changed, 468 insertions, 0 deletions
diff --git a/vm_control/Cargo.toml b/vm_control/Cargo.toml new file mode 100644 index 0000000..06b3695 --- /dev/null +++ b/vm_control/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "vm_control" +version = "0.1.0" +authors = ["The Chromium OS Authors"] + +[dependencies] +data_model = { path = "../data_model" } +kvm = { path = "../kvm" } +libc = "*" +sys_util = { path = "../sys_util" } diff --git a/vm_control/src/lib.rs b/vm_control/src/lib.rs new file mode 100644 index 0000000..7c84b0f --- /dev/null +++ b/vm_control/src/lib.rs @@ -0,0 +1,458 @@ +// Copyright 2017 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +//! Handles IPC for controlling the main VM process. +//! +//! The VM Control IPC protocol is synchronous, meaning that each `VmRequest` sent over a connection +//! will receive a `VmResponse` for that request next time data is received over that connection. +//! +//! The wire message format is a little-endian C-struct of fixed size, along with a file descriptor +//! if the request type expects one. + +extern crate data_model; +extern crate kvm; +extern crate libc; +extern crate sys_util; + +use std::fs::File; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::os::unix::net::UnixDatagram; +use std::result; + +use libc::{ERANGE, EINVAL}; + +use data_model::{DataInit, Le32, Le64, VolatileMemory}; +use sys_util::{EventFd, Error as SysError, MmapError, MemoryMapping, Scm, GuestAddress}; +use kvm::{IoeventAddress, Vm}; + +#[derive(Debug, PartialEq)] +/// An error during a request or response transaction. +pub enum VmControlError { + /// Error while sending a request or response. + Send(SysError), + /// Error while receiving a request or response. + Recv(SysError), + /// The type of a received request or response is unknown. + InvalidType, + /// There was not the expected amount of data when receiving a request or response. The inner + /// value is how much data was read. + BadSize(usize), + /// There was no associated file descriptor received for a request that expected it. + ExpectFd, +} + +pub type VmControlResult<T> = result::Result<T, VmControlError>; + +/// A file descriptor either borrowed or owned by this. +pub enum MaybeOwnedFd { + /// Owned by this enum variant, and will be destructed automatically if not moved out. + Owned(File), + /// A file descriptor borrwed by this enum. + Borrowed(RawFd), +} + +impl AsRawFd for MaybeOwnedFd { + fn as_raw_fd(&self) -> RawFd { + match self { + &MaybeOwnedFd::Owned(ref f) => f.as_raw_fd(), + &MaybeOwnedFd::Borrowed(fd) => fd, + } + } +} + +/// A request to the main process to perform some operation on the VM. +/// +/// Unless otherwise noted, each request should expect a `VmResponse::Ok` to be received on success. +pub enum VmRequest { + /// Break the VM's run loop and exit. + Exit, + /// Register the given ioevent address along with given datamatch to trigger the `EventFd`. + RegisterIoevent(EventFd, IoeventAddress, u32), + /// Register the given IRQ number to be triggered when the `EventFd` is triggered. + RegisterIrqfd(EventFd, u32), + /// Register shared memory represented by the given fd into guest address space. The response + /// variant is `VmResponse::RegisterMemory`. + RegisterMemory(MaybeOwnedFd, usize), + /// Unregister the given memory slot that was previously registereed with `RegisterMemory`. + UnregisterMemory(u32), +} + +const VM_REQUEST_TYPE_EXIT: u32 = 1; +const VM_REQUEST_TYPE_REGISTER_MEMORY: u32 = 2; +const VM_REQUEST_TYPE_UNREGISTER_MEMORY: u32 = 3; +const VM_REQUEST_SIZE: usize = 16; + +#[repr(C)] +#[derive(Clone, Copy, Default)] +struct VmRequestStruct { + type_: Le32, + slot: Le32, + size: Le64, +} + +// Safe because it only has data and has no implicit padding. +unsafe impl DataInit for VmRequestStruct {} + +impl VmRequest { + /// Receive a `VmRequest` from the given socket. + /// + /// A `VmResponse` should be sent out over the given socket before another request is received. + pub fn recv(scm: &mut Scm, s: &UnixDatagram) -> VmControlResult<VmRequest> { + let mut buf = [0; VM_REQUEST_SIZE]; + let mut fds = Vec::new(); + let read = scm.recv(s, &mut [&mut buf], &mut fds) + .map_err(|e| VmControlError::Recv(e))?; + if read != VM_REQUEST_SIZE { + return Err(VmControlError::BadSize(read)); + } + // The unwrap() will never fail because it's referencing a buf statically sized to be large + // enough for a VmRequestStruct. + let req: VmRequestStruct = buf.as_mut().get_ref(0).unwrap().load(); + + match req.type_.into() { + VM_REQUEST_TYPE_EXIT => Ok(VmRequest::Exit), + VM_REQUEST_TYPE_REGISTER_MEMORY => { + let fd = fds.pop().ok_or(VmControlError::ExpectFd)?; + Ok(VmRequest::RegisterMemory(MaybeOwnedFd::Owned(fd), + req.size.to_native() as usize)) + } + VM_REQUEST_TYPE_UNREGISTER_MEMORY => Ok(VmRequest::UnregisterMemory(req.slot.into())), + _ => Err(VmControlError::InvalidType), + } + } + + /// Send a `VmRequest` over the given socket. + /// + /// After this request is a sent, a `VmResponse` should be received before sending another + /// request. + pub fn send(&self, scm: &mut Scm, s: &UnixDatagram) -> VmControlResult<()> { + let mut req = VmRequestStruct::default(); + let mut fd_buf = [0; 1]; + let mut fd_len = 0; + match self { + &VmRequest::Exit => req.type_ = Le32::from(VM_REQUEST_TYPE_EXIT), + &VmRequest::RegisterMemory(ref fd, size) => { + req.type_ = Le32::from(VM_REQUEST_TYPE_REGISTER_MEMORY); + req.size = Le64::from(size as u64); + fd_buf[0] = fd.as_raw_fd(); + fd_len = 1; + } + &VmRequest::UnregisterMemory(slot) => { + req.type_ = Le32::from(VM_REQUEST_TYPE_UNREGISTER_MEMORY); + req.slot = Le32::from(slot); + } + _ => return Err(VmControlError::InvalidType), + } + let mut buf = [0; VM_REQUEST_SIZE]; + buf.as_mut().get_ref(0).unwrap().store(req); + scm.send(s, &[buf.as_ref()], &fd_buf[..fd_len]) + .map_err(|e| VmControlError::Send(e))?; + Ok(()) + } + + /// Executes this request on the given Vm and other mutable state. + /// + /// # Arguments + /// * `vm` - The `Vm` to perform the request on. + /// * `next_mem_pfn` - In/out argument for the page frame number to put the next chunk of device + /// memory into. + /// * `running` - Out argument that is set to false if the request was to stop running the VM. + /// + /// This does not return a result, instead encapsulating the success or failure in a + /// `VmResponse` with the intended purpose of sending the response back over the socket that + /// received this `VmRequest`. + pub fn execute(&self, vm: &mut Vm, next_mem_pfn: &mut u64, running: &mut bool) -> VmResponse { + *running = true; + match self { + &VmRequest::Exit => { + *running = false; + VmResponse::Ok + } + &VmRequest::RegisterIoevent(ref evt, addr, datamatch) => { + match vm.register_ioevent(evt, addr, datamatch) { + Ok(_) => VmResponse::Ok, + Err(e) => VmResponse::Err(e), + } + } + &VmRequest::RegisterIrqfd(ref evt, irq) => { + match vm.register_irqfd(evt, irq) { + Ok(_) => VmResponse::Ok, + Err(e) => return VmResponse::Err(e), + } + } + &VmRequest::RegisterMemory(ref fd, size) => { + let mmap = match MemoryMapping::from_fd(fd, size) { + Ok(v) => v, + Err(MmapError::SystemCallFailed(e)) => return VmResponse::Err(e), + _ => return VmResponse::Err(SysError::new(-EINVAL)), + }; + let pfn = *next_mem_pfn; + let slot = match vm.add_device_memory(GuestAddress((pfn << 12) as usize), mmap) { + Ok(slot) => slot, + Err(e) => return VmResponse::Err(e), + }; + // TODO(zachr): Use a smarter allocation strategy. The current strategy is just + // bumping this pointer, meaning the remove operation does not free any address + // space. Given enough allocations, device memory may run out of address space and + // collide with guest memory or MMIO address space. There is currently nothing in + // place to limit the amount of address space used by device memory. + *next_mem_pfn += (((size + 0x7ff) >> 12) + 1) as u64; + VmResponse::RegisterMemory { + pfn: pfn, + slot: slot, + } + } + &VmRequest::UnregisterMemory(slot) => { + match vm.remove_device_memory(slot) { + Ok(_) => VmResponse::Ok, + Err(e) => VmResponse::Err(e), + } + } + } + } +} + +/// Indication of success or failure of a `VmRequest`. +/// +/// Success is usually indicated `VmResponse::Ok` unless there is data associated with the response. +#[derive(Debug, PartialEq)] +pub enum VmResponse { + /// Indicates the request was executed successfully. + Ok, + /// Indicates the request encountered some error during execution. + Err(SysError), + /// The request to register memory into guest address space was successfully done at page frame + /// number `pfn` and memory slot number `slot`. + RegisterMemory { pfn: u64, slot: u32 }, +} + +const VM_RESPONSE_TYPE_OK: u32 = 1; +const VM_RESPONSE_TYPE_ERR: u32 = 2; +const VM_RESPONSE_TYPE_REGISTER_MEMORY: u32 = 3; +const VM_RESPONSE_SIZE: usize = 24; + +#[repr(C)] +#[derive(Clone, Copy, Default)] +struct VmResponseStruct { + type_: Le32, + errno: Le32, + pfn: Le64, + slot: Le32, + padding: Le32, +} + +// Safe because it only has data and has no implicit padding. +unsafe impl DataInit for VmResponseStruct {} + +impl VmResponse { + /// Receive a `VmResponse` from the given socket. + /// + /// This should be called after the sending a `VmRequest` before sending another request. + pub fn recv(scm: &mut Scm, s: &UnixDatagram) -> VmControlResult<VmResponse> { + let mut buf = [0; VM_RESPONSE_SIZE]; + let mut fds = Vec::new(); + let read = scm.recv(s, &mut [&mut buf], &mut fds) + .map_err(|e| VmControlError::Recv(e))?; + if read != VM_RESPONSE_SIZE { + return Err(VmControlError::BadSize(read)); + } + let resp: VmResponseStruct = buf.as_mut().get_ref(0).unwrap().load(); + + match resp.type_.into() { + VM_RESPONSE_TYPE_OK => Ok(VmResponse::Ok), + VM_RESPONSE_TYPE_ERR => { + Ok(VmResponse::Err(SysError::new(-(resp.errno.to_native() as i32)))) + } + VM_RESPONSE_TYPE_REGISTER_MEMORY => { + Ok(VmResponse::RegisterMemory { + pfn: resp.pfn.into(), + slot: resp.slot.into(), + }) + } + _ => Err(VmControlError::InvalidType), + } + } + + /// Send a `VmResponse` over the given socket. + /// + /// This must be called after receiving a `VmRequest` to indicate the outcome of that request's + /// execution. + pub fn send(&self, scm: &mut Scm, s: &UnixDatagram) -> VmControlResult<()> { + let mut resp = VmResponseStruct::default(); + match self { + &VmResponse::Ok => resp.type_ = Le32::from(VM_RESPONSE_TYPE_OK), + &VmResponse::Err(e) => { + resp.type_ = Le32::from(VM_RESPONSE_TYPE_ERR); + resp.errno = Le32::from(e.errno().checked_abs().unwrap_or(ERANGE) as u32); + } + &VmResponse::RegisterMemory { pfn, slot } => { + resp.type_ = Le32::from(VM_RESPONSE_TYPE_REGISTER_MEMORY); + resp.pfn = Le64::from(pfn); + resp.slot = Le32::from(slot); + } + } + let mut buf = [0; VM_RESPONSE_SIZE]; + buf.as_mut().get_ref(0).unwrap().store(resp); + scm.send(s, &[buf.as_ref()], &[]) + .map_err(|e| VmControlError::Send(e))?; + Ok(()) + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + use std::net::Shutdown; + + use sys_util::SharedMemory; + + #[test] + fn request_exit() { + let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair"); + let mut scm = Scm::new(1); + VmRequest::Exit.send(&mut scm, &s1).unwrap(); + match VmRequest::recv(&mut scm, &s2).unwrap() { + VmRequest::Exit => {} + _ => panic!("recv wrong request variant"), + } + } + + #[test] + fn request_register_memory() { + let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair"); + let mut scm = Scm::new(1); + let shm_size: usize = 4096; + let mut shm = SharedMemory::new(None).unwrap(); + shm.set_size(shm_size as u64).unwrap(); + VmRequest::RegisterMemory(MaybeOwnedFd::Borrowed(shm.as_raw_fd()), shm_size) + .send(&mut scm, &s1) + .unwrap(); + match VmRequest::recv(&mut scm, &s2).unwrap() { + VmRequest::RegisterMemory(MaybeOwnedFd::Owned(fd), size) => { + assert!(fd.as_raw_fd() >= 0); + assert_eq!(size, shm_size); + } + _ => panic!("recv wrong request variant"), + } + } + + #[test] + fn request_unregister_memory() { + let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair"); + let mut scm = Scm::new(1); + VmRequest::UnregisterMemory(77) + .send(&mut scm, &s1) + .unwrap(); + match VmRequest::recv(&mut scm, &s2).unwrap() { + VmRequest::UnregisterMemory(slot) => assert_eq!(slot, 77), + _ => panic!("recv wrong request variant"), + } + } + + #[test] + fn request_expect_fd() { + let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair"); + let mut scm = Scm::new(1); + let mut bad_request = [0; VM_REQUEST_SIZE]; + bad_request[0] = VM_REQUEST_TYPE_REGISTER_MEMORY as u8; + scm.send(&s2, &[bad_request.as_ref()], &[]).unwrap(); + match VmRequest::recv(&mut scm, &s1) { + Err(VmControlError::ExpectFd) => {} + _ => panic!("recv wrong error variant"), + } + } + + #[test] + fn request_no_data() { + let (s1, _) = UnixDatagram::pair().expect("failed to create socket pair"); + let mut scm = Scm::new(1); + s1.shutdown(Shutdown::Both).unwrap(); + match VmRequest::recv(&mut scm, &s1) { + Err(VmControlError::BadSize(s)) => assert_eq!(s, 0), + _ => panic!("recv wrong error variant"), + } + } + + #[test] + fn request_bad_size() { + let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair"); + let mut scm = Scm::new(1); + scm.send(&s2, &[[12; 7].as_ref()], &[]).unwrap(); + match VmRequest::recv(&mut scm, &s1) { + Err(VmControlError::BadSize(_)) => {} + _ => panic!("recv wrong error variant"), + } + } + + #[test] + fn request_invalid_type() { + let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair"); + let mut scm = Scm::new(1); + scm.send(&s2, &[[12; VM_RESPONSE_SIZE].as_ref()], &[]) + .unwrap(); + match VmRequest::recv(&mut scm, &s1) { + Err(VmControlError::InvalidType) => {} + _ => panic!("recv wrong error variant"), + } + } + + #[test] + fn resp_ok() { + let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair"); + let mut scm = Scm::new(1); + VmResponse::Ok.send(&mut scm, &s1).unwrap(); + let r = VmResponse::recv(&mut scm, &s2).unwrap(); + assert_eq!(r, VmResponse::Ok); + } + + #[test] + fn resp_err() { + let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair"); + let mut scm = Scm::new(1); + let r1 = VmResponse::Err(SysError::new(-89)); + r1.send(&mut scm, &s1).unwrap(); + let r2 = VmResponse::recv(&mut scm, &s2).unwrap(); + assert_eq!(r1, r2); + } + + #[test] + fn resp_memory() { + let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair"); + let mut scm = Scm::new(1); + let r1 = VmResponse::RegisterMemory { pfn: 55, slot: 66 }; + r1.send(&mut scm, &s1).unwrap(); + let r2 = VmResponse::recv(&mut scm, &s2).unwrap(); + assert_eq!(r1, r2); + } + + #[test] + fn resp_no_data() { + let (s1, _) = UnixDatagram::pair().expect("failed to create socket pair"); + let mut scm = Scm::new(1); + s1.shutdown(Shutdown::Both).unwrap(); + let r = VmResponse::recv(&mut scm, &s1); + assert_eq!(r, Err(VmControlError::BadSize(0))); + } + + #[test] + fn resp_bad_size() { + let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair"); + let mut scm = Scm::new(1); + scm.send(&s2, &[[12; 7].as_ref()], &[]).unwrap(); + let r = VmResponse::recv(&mut scm, &s1); + assert_eq!(r, Err(VmControlError::BadSize(7))); + } + + #[test] + fn resp_invalid_type() { + let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair"); + let mut scm = Scm::new(1); + scm.send(&s2, &[[12; VM_RESPONSE_SIZE].as_ref()], &[]) + .unwrap(); + let r = VmResponse::recv(&mut scm, &s1); + assert_eq!(r, Err(VmControlError::InvalidType)); + } +} |