summary refs log tree commit diff
path: root/kvm/src/lib.rs
diff options
context:
space:
mode:
authorDaniel Prilik <prilik@google.com>2019-03-29 10:48:57 -0700
committerchrome-bot <chrome-bot@chromium.org>2019-04-12 14:50:03 -0700
commitd49adc9005a300dbae60bd8ecb12ea620fc0fd31 (patch)
tree21bd0290bccf00dd7ba09e4ee359e3ade86b0140 /kvm/src/lib.rs
parentc211a6ccc69dbf090002e58822846c2b4a69519c (diff)
downloadcrosvm-d49adc9005a300dbae60bd8ecb12ea620fc0fd31.tar
crosvm-d49adc9005a300dbae60bd8ecb12ea620fc0fd31.tar.gz
crosvm-d49adc9005a300dbae60bd8ecb12ea620fc0fd31.tar.bz2
crosvm-d49adc9005a300dbae60bd8ecb12ea620fc0fd31.tar.lz
crosvm-d49adc9005a300dbae60bd8ecb12ea620fc0fd31.tar.xz
crosvm-d49adc9005a300dbae60bd8ecb12ea620fc0fd31.tar.zst
crosvm-d49adc9005a300dbae60bd8ecb12ea620fc0fd31.zip
sys_util: add MemoryMappingArena
There is a hard-limit to the number of MemoryMaps that can be added to a
KVM VM, a arch-dependent number defined as KVM_USER_MEM_SLOTS. e.g: on
x86 this is 509 (512 - 3 internal slots).

For most purposes, this isn't too much of an issue, but there are some
cases where one might want to share a lot of mmaps with a Guest. e.g:
virtio-fs uses a large cache region for mapping in slices of file fds
directly into guest memory. If one tries to add a new KVM memory region
for each mmap, the number of available slots is quickly exhausted.

MemoryMappingArena is a way to work around this limitation by allocating
a single KVM memory region for a large slice of memory, and then using
mmap with MAP_FIXED to override slices of this "arena" hostside, thereby
achieving the same effect without quickly exhausting the number of KVM
memory region slots.

BUG=chromium:936567
TEST=cargo test -p sys_util

Change-Id: I89cc3b22cdba6756b2d76689176d7147cf238f07
Reviewed-on: https://chromium-review.googlesource.com/1546600
Commit-Ready: ChromeOS CL Exonerator Bot <chromiumos-cl-exonerator@appspot.gserviceaccount.com>
Tested-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Zach Reizner <zachr@chromium.org>
Diffstat (limited to 'kvm/src/lib.rs')
-rw-r--r--kvm/src/lib.rs155
1 files changed, 126 insertions, 29 deletions
diff --git a/kvm/src/lib.rs b/kvm/src/lib.rs
index 42ed400..cae377d 100644
--- a/kvm/src/lib.rs
+++ b/kvm/src/lib.rs
@@ -13,7 +13,6 @@ extern crate msg_socket;
 mod cap;
 
 use std::cmp::{min, Ordering};
-use std::collections::hash_map::Entry;
 use std::collections::{BinaryHeap, HashMap};
 use std::fs::File;
 use std::mem::size_of;
@@ -32,7 +31,8 @@ use sys_util::{
     ioctl, ioctl_with_mut_ptr, ioctl_with_mut_ref, ioctl_with_ptr, ioctl_with_ref, ioctl_with_val,
 };
 use sys_util::{
-    pagesize, signal, Error, EventFd, GuestAddress, GuestMemory, MemoryMapping, Result,
+    pagesize, signal, Error, EventFd, GuestAddress, GuestMemory, MemoryMapping, MemoryMappingArena,
+    Result,
 };
 
 pub use crate::cap::*;
@@ -79,7 +79,7 @@ unsafe fn set_user_memory_region<F: AsRawFd>(
     log_dirty_pages: bool,
     guest_addr: u64,
     memory_size: u64,
-    userspace_addr: u64,
+    userspace_addr: *mut u8,
 ) -> Result<()> {
     let mut flags = if read_only { KVM_MEM_READONLY } else { 0 };
     if log_dirty_pages {
@@ -90,7 +90,7 @@ unsafe fn set_user_memory_region<F: AsRawFd>(
         flags,
         guest_phys_addr: guest_addr,
         memory_size,
-        userspace_addr,
+        userspace_addr: userspace_addr as u64,
     };
 
     let ret = ioctl_with_ref(fd, KVM_SET_USER_MEMORY_REGION(), &region);
@@ -300,6 +300,7 @@ pub struct Vm {
     vm: File,
     guest_mem: GuestMemory,
     device_memory: HashMap<u32, MemoryMapping>,
+    mmap_arenas: HashMap<u32, MemoryMappingArena>,
     mem_slot_gaps: BinaryHeap<MemSlot>,
 }
 
@@ -322,7 +323,7 @@ impl Vm {
                         false,
                         guest_addr.offset() as u64,
                         size as u64,
-                        host_addr as u64,
+                        host_addr as *mut u8,
                     )
                 }
             })?;
@@ -331,6 +332,7 @@ impl Vm {
                 vm: vm_file,
                 guest_mem,
                 device_memory: HashMap::new(),
+                mmap_arenas: HashMap::new(),
                 mem_slot_gaps: BinaryHeap::new(),
             })
         } else {
@@ -338,6 +340,49 @@ impl Vm {
         }
     }
 
+    // Helper method for `set_user_memory_region` that tracks available slots.
+    unsafe fn set_user_memory_region(
+        &mut self,
+        read_only: bool,
+        log_dirty_pages: bool,
+        guest_addr: u64,
+        memory_size: u64,
+        userspace_addr: *mut u8,
+    ) -> Result<u32> {
+        let slot = match self.mem_slot_gaps.pop() {
+            Some(gap) => gap.0,
+            None => {
+                (self.device_memory.len()
+                    + self.guest_mem.num_regions() as usize
+                    + self.mmap_arenas.len()) as u32
+            }
+        };
+
+        let res = set_user_memory_region(
+            &self.vm,
+            slot,
+            read_only,
+            log_dirty_pages,
+            guest_addr,
+            memory_size,
+            userspace_addr,
+        );
+        match res {
+            Ok(_) => Ok(slot),
+            Err(e) => {
+                self.mem_slot_gaps.push(MemSlot(slot));
+                Err(e)
+            }
+        }
+    }
+
+    // Helper method for `set_user_memory_region` that tracks available slots.
+    unsafe fn remove_user_memory_region(&mut self, slot: u32) -> Result<()> {
+        set_user_memory_region(&self.vm, slot, false, false, 0, 0, std::ptr::null_mut())?;
+        self.mem_slot_gaps.push(MemSlot(slot));
+        Ok(())
+    }
+
     /// Checks if a particular `Cap` is available.
     ///
     /// This is distinct from the `Kvm` version of this method because the some extensions depend on
@@ -374,29 +419,18 @@ impl Vm {
             return Err(Error::new(ENOSPC));
         }
 
-        // If there are no gaps, the lowest slot number is equal to the number of slots we are
-        // currently using between guest memory and device memory. For example, if 2 slots are used
-        // by guest memory, 3 slots are used for device memory, and there are no gaps, it follows
-        // that the lowest unused slot is 2+3=5.
-        let slot = match self.mem_slot_gaps.pop() {
-            Some(gap) => gap.0,
-            None => (self.device_memory.len() + (self.guest_mem.num_regions() as usize)) as u32,
-        };
-
         // Safe because we check that the given guest address is valid and has no overlaps. We also
         // know that the pointer and size are correct because the MemoryMapping interface ensures
         // this. We take ownership of the memory mapping so that it won't be unmapped until the slot
         // is removed.
-        unsafe {
-            set_user_memory_region(
-                &self.vm,
-                slot,
+        let slot = unsafe {
+            self.set_user_memory_region(
                 read_only,
                 log_dirty_pages,
                 guest_addr.offset() as u64,
                 mem.size() as u64,
-                mem.as_ptr() as u64,
-            )?;
+                mem.as_ptr(),
+            )?
         };
         self.device_memory.insert(slot, mem);
 
@@ -407,19 +441,82 @@ impl Vm {
     ///
     /// Ownership of the host memory mapping associated with the given slot is returned on success.
     pub fn remove_device_memory(&mut self, slot: u32) -> Result<MemoryMapping> {
-        match self.device_memory.entry(slot) {
-            Entry::Occupied(entry) => {
-                // Safe because the slot is checked against the list of device memory slots.
-                unsafe {
-                    set_user_memory_region(&self.vm, slot, false, false, 0, 0, 0)?;
-                }
-                self.mem_slot_gaps.push(MemSlot(slot));
-                Ok(entry.remove())
+        if self.device_memory.contains_key(&slot) {
+            // Safe because the slot is checked against the list of device memory slots.
+            unsafe {
+                self.remove_user_memory_region(slot)?;
             }
-            _ => Err(Error::new(ENOENT)),
+            // Safe to unwrap since map is checked to contain key
+            Ok(self.device_memory.remove(&slot).unwrap())
+        } else {
+            Err(Error::new(ENOENT))
+        }
+    }
+
+    /// Inserts the given `MemoryMappingArena` into the VM's address space at `guest_addr`.
+    ///
+    /// The slot that was assigned the device memory mapping is returned on success. The slot can be
+    /// given to `Vm::remove_mmap_arena` to remove the memory from the VM's address space and
+    /// take back ownership of `mmap_arena`.
+    ///
+    /// Note that memory inserted into the VM's address space must not overlap with any other memory
+    /// slot's region.
+    ///
+    /// If `read_only` is true, the guest will be able to read the memory as normal, but attempts to
+    /// write will trigger a mmio VM exit, leaving the memory untouched.
+    ///
+    /// If `log_dirty_pages` is true, the slot number can be used to retrieve the pages written to
+    /// by the guest with `get_dirty_log`.
+    pub fn add_mmap_arena(
+        &mut self,
+        guest_addr: GuestAddress,
+        mmap_arena: MemoryMappingArena,
+        read_only: bool,
+        log_dirty_pages: bool,
+    ) -> Result<u32> {
+        if guest_addr < self.guest_mem.end_addr() {
+            return Err(Error::new(ENOSPC));
+        }
+
+        // Safe because we check that the given guest address is valid and has no overlaps. We also
+        // know that the pointer and size are correct because the MemoryMapping interface ensures
+        // this. We take ownership of the memory mapping so that it won't be unmapped until the slot
+        // is removed.
+        let slot = unsafe {
+            self.set_user_memory_region(
+                read_only,
+                log_dirty_pages,
+                guest_addr.offset() as u64,
+                mmap_arena.size() as u64,
+                mmap_arena.as_ptr(),
+            )?
+        };
+        self.mmap_arenas.insert(slot, mmap_arena);
+
+        Ok(slot)
+    }
+
+    /// Removes memory map arena that was previously added at the given slot.
+    ///
+    /// Ownership of the host memory mapping associated with the given slot is returned on success.
+    pub fn remove_mmap_arena(&mut self, slot: u32) -> Result<MemoryMappingArena> {
+        if self.mmap_arenas.contains_key(&slot) {
+            // Safe because the slot is checked against the list of device memory slots.
+            unsafe {
+                self.remove_user_memory_region(slot)?;
+            }
+            // Safe to unwrap since map is checked to contain key
+            Ok(self.mmap_arenas.remove(&slot).unwrap())
+        } else {
+            Err(Error::new(ENOENT))
         }
     }
 
+    /// Get a mutable reference to the memory map arena added at the given slot.
+    pub fn get_mmap_arena(&mut self, slot: u32) -> Option<&mut MemoryMappingArena> {
+        self.mmap_arenas.get_mut(&slot)
+    }
+
     /// Gets the bitmap of dirty pages since the last call to `get_dirty_log` for the memory at
     /// `slot`.
     ///