summary refs log tree commit diff
path: root/vm_control
diff options
context:
space:
mode:
authorDylan Reid <dgreid@chromium.org>2017-10-06 15:30:34 -0700
committerchrome-bot <chrome-bot@chromium.org>2017-10-09 17:39:04 -0700
commit94bf1bf6b4a7791757937e2ffb2a81e797b82922 (patch)
tree386430a70c5fe176d8ae981aa1ca9b5bfc2d346f /vm_control
parentd0c9adc642fc968cb347952eed1d7fd0d0a8e80e (diff)
downloadcrosvm-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.toml10
-rw-r--r--vm_control/src/lib.rs458
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));
+    }
+}