// Copyright 2023 Linaro Ltd. All Rights Reserved. // Viresh Kumar // // Xen specific memory mapping implementations // // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause //! Helper structure for working with mmap'ed memory regions on Xen. use bitflags::bitflags; use libc::{c_int, c_void, MAP_SHARED, _SC_PAGESIZE}; use std::{io, mem::size_of, os::raw::c_ulong, os::unix::io::AsRawFd, ptr::null_mut, result}; use vmm_sys_util::{ fam::{Error as FamError, FamStruct, FamStructWrapper}, generate_fam_struct_impl, ioctl::{ioctl_expr, _IOC_NONE}, }; // Use a dummy ioctl implementation for tests instead. #[cfg(not(test))] use vmm_sys_util::ioctl::ioctl_with_ref; #[cfg(test)] use tests::ioctl_with_ref; use crate::bitmap::{Bitmap, BS}; use crate::guest_memory::{FileOffset, GuestAddress}; use crate::mmap::{check_file_offset, NewBitmap}; use crate::volatile_memory::{self, VolatileMemory, VolatileSlice}; /// Error conditions that may arise when creating a new `MmapRegion` object. #[derive(Debug, thiserror::Error)] pub enum Error { /// The specified file offset and length cause overflow when added. #[error("The specified file offset and length cause overflow when added")] InvalidOffsetLength, /// The forbidden `MAP_FIXED` flag was specified. #[error("The forbidden `MAP_FIXED` flag was specified")] MapFixed, /// A mapping with offset + length > EOF was attempted. #[error("The specified file offset and length is greater then file length")] MappingPastEof, /// The `mmap` call returned an error. #[error("{0}")] Mmap(io::Error), /// Seeking the end of the file returned an error. #[error("Error seeking the end of the file: {0}")] SeekEnd(io::Error), /// Seeking the start of the file returned an error. #[error("Error seeking the start of the file: {0}")] SeekStart(io::Error), /// Invalid file offset. #[error("Invalid file offset")] InvalidFileOffset, /// Memory mapped in advance. #[error("Memory mapped in advance")] MappedInAdvance, /// Invalid Xen mmap flags. #[error("Invalid Xen Mmap flags: {0:x}")] MmapFlags(u32), /// Fam error. #[error("Fam error: {0}")] Fam(FamError), /// Unexpected error. #[error("Unexpected error")] UnexpectedError, } type Result = result::Result; /// `MmapRange` represents a range of arguments required to create Mmap regions. #[derive(Clone, Debug)] pub struct MmapRange { size: usize, file_offset: Option, prot: Option, flags: Option, hugetlbfs: Option, addr: GuestAddress, mmap_flags: u32, mmap_data: u32, } impl MmapRange { /// Creates instance of the range with multiple arguments. pub fn new( size: usize, file_offset: Option, addr: GuestAddress, mmap_flags: u32, mmap_data: u32, ) -> Self { Self { size, file_offset, prot: None, flags: None, hugetlbfs: None, addr, mmap_flags, mmap_data, } } /// Creates instance of the range for `MmapXenFlags::UNIX` type mapping. pub fn new_unix(size: usize, file_offset: Option, addr: GuestAddress) -> Self { let flags = Some(match file_offset { Some(_) => libc::MAP_NORESERVE | libc::MAP_SHARED, None => libc::MAP_ANONYMOUS | libc::MAP_PRIVATE, }); Self { size, file_offset, prot: None, flags, hugetlbfs: None, addr, mmap_flags: MmapXenFlags::UNIX.bits(), mmap_data: 0, } } /// Set the prot of the range. pub fn set_prot(&mut self, prot: i32) { self.prot = Some(prot) } /// Set the flags of the range. pub fn set_flags(&mut self, flags: i32) { self.flags = Some(flags) } /// Set the hugetlbfs of the range. pub fn set_hugetlbfs(&mut self, hugetlbfs: bool) { self.hugetlbfs = Some(hugetlbfs) } } /// Helper structure for working with mmaped memory regions with Xen. /// /// The structure is used for accessing the guest's physical memory by mmapping it into /// the current process. /// /// # Limitations /// When running a 64-bit virtual machine on a 32-bit hypervisor, only part of the guest's /// physical memory may be mapped into the current process due to the limited virtual address /// space size of the process. #[derive(Debug)] pub struct MmapRegion { bitmap: B, size: usize, prot: i32, flags: i32, file_offset: Option, hugetlbfs: Option, mmap: MmapXen, } // SAFETY: Send and Sync aren't automatically inherited for the raw address pointer. // Accessing that pointer is only done through the stateless interface which // allows the object to be shared by multiple threads without a decrease in // safety. unsafe impl Send for MmapRegion {} // SAFETY: See comment above. unsafe impl Sync for MmapRegion {} impl MmapRegion { /// Creates a shared anonymous mapping of `size` bytes. /// /// # Arguments /// * `range` - An instance of type `MmapRange`. /// /// # Examples /// * Write a slice at guest address 0x1200 with Xen's Grant mapping. /// /// ``` /// use std::fs::File; /// use std::path::Path; /// use vm_memory::{ /// Bytes, FileOffset, GuestAddress, GuestMemoryMmap, GuestRegionMmap, MmapRange, MmapRegion, /// MmapXenFlags, /// }; /// # use vmm_sys_util::tempfile::TempFile; /// /// let addr = GuestAddress(0x1000); /// # if false { /// let file = Some(FileOffset::new( /// File::open(Path::new("/dev/xen/gntdev")).expect("Could not open file"), /// 0, /// )); /// /// let range = MmapRange::new(0x400, file, addr, MmapXenFlags::GRANT.bits(), 0); /// # } /// # // We need a UNIX mapping for tests to succeed. /// # let range = MmapRange::new_unix(0x400, None, addr); /// /// let r = GuestRegionMmap::new( /// MmapRegion::<()>::from_range(range).expect("Could not create mmap region"), /// addr, /// ) /// .expect("Could not create guest region"); /// /// let mut gm = GuestMemoryMmap::from_regions(vec![r]).expect("Could not create guest memory"); /// let res = gm /// .write(&[1, 2, 3, 4, 5], GuestAddress(0x1200)) /// .expect("Could not write to guest memory"); /// assert_eq!(5, res); /// ``` /// /// * Write a slice at guest address 0x1200 with Xen's Foreign mapping. /// /// ``` /// use std::fs::File; /// use std::path::Path; /// use vm_memory::{ /// Bytes, FileOffset, GuestAddress, GuestMemoryMmap, GuestRegionMmap, MmapRange, MmapRegion, /// MmapXenFlags, /// }; /// # use vmm_sys_util::tempfile::TempFile; /// /// let addr = GuestAddress(0x1000); /// # if false { /// let file = Some(FileOffset::new( /// File::open(Path::new("/dev/xen/privcmd")).expect("Could not open file"), /// 0, /// )); /// /// let range = MmapRange::new(0x400, file, addr, MmapXenFlags::FOREIGN.bits(), 0); /// # } /// # // We need a UNIX mapping for tests to succeed. /// # let range = MmapRange::new_unix(0x400, None, addr); /// /// let r = GuestRegionMmap::new( /// MmapRegion::<()>::from_range(range).expect("Could not create mmap region"), /// addr, /// ) /// .expect("Could not create guest region"); /// /// let mut gm = GuestMemoryMmap::from_regions(vec![r]).expect("Could not create guest memory"); /// let res = gm /// .write(&[1, 2, 3, 4, 5], GuestAddress(0x1200)) /// .expect("Could not write to guest memory"); /// assert_eq!(5, res); /// ``` pub fn from_range(mut range: MmapRange) -> Result { if range.prot.is_none() { range.prot = Some(libc::PROT_READ | libc::PROT_WRITE); } match range.flags { Some(flags) => { if flags & libc::MAP_FIXED != 0 { // Forbid MAP_FIXED, as it doesn't make sense in this context, and is pretty dangerous // in general. return Err(Error::MapFixed); } } None => range.flags = Some(libc::MAP_NORESERVE | libc::MAP_SHARED), } let mmap = MmapXen::new(&range)?; Ok(MmapRegion { bitmap: B::with_len(range.size), size: range.size, prot: range.prot.ok_or(Error::UnexpectedError)?, flags: range.flags.ok_or(Error::UnexpectedError)?, file_offset: range.file_offset, hugetlbfs: range.hugetlbfs, mmap, }) } } impl MmapRegion { /// Returns a pointer to the beginning of the memory region. Mutable accesses performed /// using the resulting pointer are not automatically accounted for by the dirty bitmap /// tracking functionality. /// /// Should only be used for passing this region to ioctls for setting guest memory. pub fn as_ptr(&self) -> *mut u8 { self.mmap.addr() } /// Returns the size of this region. pub fn size(&self) -> usize { self.size } /// Returns information regarding the offset into the file backing this region (if any). pub fn file_offset(&self) -> Option<&FileOffset> { self.file_offset.as_ref() } /// Returns the value of the `prot` parameter passed to `mmap` when mapping this region. pub fn prot(&self) -> i32 { self.prot } /// Returns the value of the `flags` parameter passed to `mmap` when mapping this region. pub fn flags(&self) -> i32 { self.flags } /// Checks whether this region and `other` are backed by overlapping /// [`FileOffset`](struct.FileOffset.html) objects. /// /// This is mostly a sanity check available for convenience, as different file descriptors /// can alias the same file. pub fn fds_overlap(&self, other: &MmapRegion) -> bool { if let Some(f_off1) = self.file_offset() { if let Some(f_off2) = other.file_offset() { if f_off1.file().as_raw_fd() == f_off2.file().as_raw_fd() { let s1 = f_off1.start(); let s2 = f_off2.start(); let l1 = self.len() as u64; let l2 = other.len() as u64; if s1 < s2 { return s1 + l1 > s2; } else { return s2 + l2 > s1; } } } } false } /// Set the hugetlbfs of the region pub fn set_hugetlbfs(&mut self, hugetlbfs: bool) { self.hugetlbfs = Some(hugetlbfs) } /// Returns `true` if the region is hugetlbfs pub fn is_hugetlbfs(&self) -> Option { self.hugetlbfs } /// Returns a reference to the inner bitmap object. pub fn bitmap(&self) -> &B { &self.bitmap } /// Returns xen mmap flags. pub fn xen_mmap_flags(&self) -> u32 { self.mmap.flags() } /// Returns xen mmap data. pub fn xen_mmap_data(&self) -> u32 { self.mmap.data() } } impl VolatileMemory for MmapRegion { type B = B; fn len(&self) -> usize { self.size } fn get_slice( &self, offset: usize, count: usize, ) -> volatile_memory::Result>> { let _ = self.compute_end_offset(offset, count)?; let mmap_info = if self.mmap.mmap_in_advance() { None } else { Some(&self.mmap) }; Ok( // SAFETY: Safe because we checked that offset + count was within our range and we only // ever hand out volatile accessors. unsafe { VolatileSlice::with_bitmap( self.as_ptr().add(offset), count, self.bitmap.slice_at(offset), mmap_info, ) }, ) } } #[derive(Clone, Debug, PartialEq)] struct MmapUnix { addr: *mut u8, size: usize, } impl MmapUnix { fn new(size: usize, prot: i32, flags: i32, fd: i32, f_offset: u64) -> Result { let addr = // SAFETY: This is safe because we're not allowing MAP_FIXED, and invalid parameters // cannot break Rust safety guarantees (things may change if we're mapping /dev/mem or // some wacky file). unsafe { libc::mmap(null_mut(), size, prot, flags, fd, f_offset as libc::off_t) }; if addr == libc::MAP_FAILED { return Err(Error::Mmap(io::Error::last_os_error())); } Ok(Self { addr: addr as *mut u8, size, }) } fn addr(&self) -> *mut u8 { self.addr } } impl Drop for MmapUnix { fn drop(&mut self) { // SAFETY: This is safe because we mmap the area at addr ourselves, and nobody // else is holding a reference to it. unsafe { libc::munmap(self.addr as *mut libc::c_void, self.size); } } } // Bit mask for the vhost-user xen mmap message. bitflags! { /// Flags for the Xen mmap message. pub struct MmapXenFlags: u32 { /// Standard Unix memory mapping. const UNIX = 0x0; /// Xen foreign memory (accessed via /dev/privcmd). const FOREIGN = 0x1; /// Xen grant memory (accessed via /dev/gntdev). const GRANT = 0x2; /// Xen no advance mapping. const NO_ADVANCE_MAP = 0x8; /// All valid mappings. const ALL = Self::FOREIGN.bits() | Self::GRANT.bits(); } } impl MmapXenFlags { /// Mmap flags are valid. pub fn is_valid(&self) -> bool { // only one of unix, foreign or grant should be set and mmap_in_advance() should be true // with foreign and unix. if self.is_grant() { !self.is_foreign() } else if self.is_foreign() || self.is_unix() { self.mmap_in_advance() } else { false } } /// Is standard Unix memory. pub fn is_unix(&self) -> bool { self.bits() == Self::UNIX.bits() } /// Is xen foreign memory. pub fn is_foreign(&self) -> bool { self.contains(Self::FOREIGN) } /// Is xen grant memory. pub fn is_grant(&self) -> bool { self.contains(Self::GRANT) } /// Can mmap entire region in advance. pub fn mmap_in_advance(&self) -> bool { !self.contains(Self::NO_ADVANCE_MAP) } } fn page_size() -> u64 { // SAFETY: Safe because this call just returns the page size and doesn't have any side effects. unsafe { libc::sysconf(_SC_PAGESIZE) as u64 } } fn pages(size: usize) -> (usize, usize) { let page_size = page_size() as usize; let num = (size + page_size - 1) / page_size; (num, page_size * num) } fn validate_file(file_offset: &Option) -> Result<(i32, u64)> { let file_offset = match file_offset { Some(f) => f, None => return Err(Error::InvalidFileOffset), }; let fd = file_offset.file().as_raw_fd(); let f_offset = file_offset.start(); // We don't allow file offsets with Xen foreign mappings. if f_offset != 0 { return Err(Error::InvalidOffsetLength); } Ok((fd, f_offset)) } // Xen Foreign memory mapping interface. trait MmapXenTrait: std::fmt::Debug { fn mmap_slice(&self, addr: *const u8, prot: i32, len: usize) -> Result; fn addr(&self) -> *mut u8; } // Standard Unix memory mapping for testing other crates. #[derive(Clone, Debug, PartialEq)] struct MmapXenUnix(MmapUnix); impl MmapXenUnix { fn new(range: &MmapRange) -> Result { let (fd, offset) = if let Some(ref f_off) = range.file_offset { check_file_offset(f_off, range.size)?; (f_off.file().as_raw_fd(), f_off.start()) } else { (-1, 0) }; Ok(Self(MmapUnix::new( range.size, range.prot.ok_or(Error::UnexpectedError)?, range.flags.ok_or(Error::UnexpectedError)?, fd, offset, )?)) } } impl MmapXenTrait for MmapXenUnix { #[allow(unused_variables)] fn mmap_slice(&self, addr: *const u8, prot: i32, len: usize) -> Result { Err(Error::MappedInAdvance) } fn addr(&self) -> *mut u8 { self.0.addr() } } // Privcmd mmap batch v2 command // // include/uapi/xen/privcmd.h: `privcmd_mmapbatch_v2` #[repr(C)] #[derive(Debug, Copy, Clone)] struct PrivCmdMmapBatchV2 { // number of pages to populate num: u32, // target domain domid: u16, // virtual address addr: *mut c_void, // array of mfns arr: *const u64, // array of error codes err: *mut c_int, } const XEN_PRIVCMD_TYPE: u32 = 'P' as u32; // #define IOCTL_PRIVCMD_MMAPBATCH_V2 _IOC(_IOC_NONE, 'P', 4, sizeof(privcmd_mmapbatch_v2_t)) fn ioctl_privcmd_mmapbatch_v2() -> c_ulong { ioctl_expr( _IOC_NONE, XEN_PRIVCMD_TYPE, 4, size_of::() as u32, ) } // Xen foreign memory specific implementation. #[derive(Clone, Debug, PartialEq)] struct MmapXenForeign { domid: u32, guest_base: GuestAddress, unix_mmap: MmapUnix, fd: i32, } impl AsRawFd for MmapXenForeign { fn as_raw_fd(&self) -> i32 { self.fd } } impl MmapXenForeign { fn new(range: &MmapRange) -> Result { let (fd, f_offset) = validate_file(&range.file_offset)?; let (count, size) = pages(range.size); let unix_mmap = MmapUnix::new( size, range.prot.ok_or(Error::UnexpectedError)?, range.flags.ok_or(Error::UnexpectedError)? | MAP_SHARED, fd, f_offset, )?; let foreign = Self { domid: range.mmap_data, guest_base: range.addr, unix_mmap, fd, }; foreign.mmap_ioctl(count)?; Ok(foreign) } // Ioctl to pass additional information to mmap infrastructure of privcmd driver. fn mmap_ioctl(&self, count: usize) -> Result<()> { let base = self.guest_base.0 / page_size(); let mut pfn = Vec::with_capacity(count); for i in 0..count { pfn.push(base + i as u64); } let mut err: Vec = vec![0; count]; let map = PrivCmdMmapBatchV2 { num: count as u32, domid: self.domid as u16, addr: self.addr() as *mut c_void, arr: pfn.as_ptr(), err: err.as_mut_ptr(), }; // SAFETY: This is safe because the ioctl guarantees to not access memory beyond `map`. let ret = unsafe { ioctl_with_ref(self, ioctl_privcmd_mmapbatch_v2(), &map) }; if ret == 0 { Ok(()) } else { Err(Error::Mmap(io::Error::last_os_error())) } } } impl MmapXenTrait for MmapXenForeign { #[allow(unused_variables)] fn mmap_slice(&self, addr: *const u8, prot: i32, len: usize) -> Result { Err(Error::MappedInAdvance) } fn addr(&self) -> *mut u8 { self.unix_mmap.addr() } } // Xen Grant memory mapping interface. const XEN_GRANT_ADDR_OFF: u64 = 1 << 63; // Grant reference // // include/uapi/xen/gntdev.h: `ioctl_gntdev_grant_ref` #[repr(C)] #[derive(Copy, Clone, Debug, Default, PartialEq)] struct GntDevGrantRef { // The domain ID of the grant to be mapped. domid: u32, // The grant reference of the grant to be mapped. reference: u32, } #[repr(C)] #[derive(Debug, Default, PartialEq, Eq)] struct __IncompleteArrayField(::std::marker::PhantomData, [T; 0]); impl __IncompleteArrayField { #[inline] unsafe fn as_ptr(&self) -> *const T { self as *const __IncompleteArrayField as *const T } #[inline] unsafe fn as_mut_ptr(&mut self) -> *mut T { self as *mut __IncompleteArrayField as *mut T } #[inline] unsafe fn as_slice(&self, len: usize) -> &[T] { ::std::slice::from_raw_parts(self.as_ptr(), len) } #[inline] unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] { ::std::slice::from_raw_parts_mut(self.as_mut_ptr(), len) } } // Grant dev mapping reference // // include/uapi/xen/gntdev.h: `ioctl_gntdev_map_grant_ref` #[repr(C)] #[derive(Debug, Default)] struct GntDevMapGrantRef { // The number of grants to be mapped. count: u32, // Unused padding pad: u32, // The offset to be used on a subsequent call to mmap(). index: u64, // Array of grant references, of size @count. refs: __IncompleteArrayField, } generate_fam_struct_impl!( GntDevMapGrantRef, GntDevGrantRef, refs, u32, count, usize::MAX ); type GntDevMapGrantRefWrapper = FamStructWrapper; impl GntDevMapGrantRef { fn new(domid: u32, base: u32, count: usize) -> Result { let mut wrapper = GntDevMapGrantRefWrapper::new(count).map_err(Error::Fam)?; let refs = wrapper.as_mut_slice(); // GntDevMapGrantRef's pad and index are initialized to 0 by Fam layer. for (i, r) in refs.iter_mut().enumerate().take(count) { r.domid = domid; r.reference = base + i as u32; } Ok(wrapper) } } // Grant dev un-mapping reference // // include/uapi/xen/gntdev.h: `ioctl_gntdev_unmap_grant_ref` #[repr(C)] #[derive(Debug, Copy, Clone)] struct GntDevUnmapGrantRef { // The offset returned by the map operation. index: u64, // The number of grants to be unmapped. count: u32, // Unused padding pad: u32, } impl GntDevUnmapGrantRef { fn new(index: u64, count: u32) -> Self { Self { index, count, pad: 0, } } } const XEN_GNTDEV_TYPE: u32 = 'G' as u32; // #define IOCTL_GNTDEV_MAP_GRANT_REF _IOC(_IOC_NONE, 'G', 0, sizeof(ioctl_gntdev_map_grant_ref)) fn ioctl_gntdev_map_grant_ref() -> c_ulong { ioctl_expr( _IOC_NONE, XEN_GNTDEV_TYPE, 0, (size_of::() + size_of::()) as u32, ) } // #define IOCTL_GNTDEV_UNMAP_GRANT_REF _IOC(_IOC_NONE, 'G', 1, sizeof(struct ioctl_gntdev_unmap_grant_ref)) fn ioctl_gntdev_unmap_grant_ref() -> c_ulong { ioctl_expr( _IOC_NONE, XEN_GNTDEV_TYPE, 1, size_of::() as u32, ) } // Xen grant memory specific implementation. #[derive(Clone, Debug)] struct MmapXenGrant { guest_base: GuestAddress, unix_mmap: Option, file_offset: FileOffset, flags: i32, size: usize, index: u64, domid: u32, } impl AsRawFd for MmapXenGrant { fn as_raw_fd(&self) -> i32 { self.file_offset.file().as_raw_fd() } } impl MmapXenGrant { fn new(range: &MmapRange, mmap_flags: MmapXenFlags) -> Result { validate_file(&range.file_offset)?; let mut grant = Self { guest_base: range.addr, unix_mmap: None, file_offset: range.file_offset.as_ref().unwrap().clone(), flags: range.flags.ok_or(Error::UnexpectedError)?, size: 0, index: 0, domid: range.mmap_data, }; // Region can't be mapped in advance, partial mapping will be done later via // `MmapXenSlice`. if mmap_flags.mmap_in_advance() { let (unix_mmap, index) = grant.mmap_range( range.addr, range.size, range.prot.ok_or(Error::UnexpectedError)?, )?; grant.unix_mmap = Some(unix_mmap); grant.index = index; grant.size = range.size; } Ok(grant) } fn mmap_range(&self, addr: GuestAddress, size: usize, prot: i32) -> Result<(MmapUnix, u64)> { let (count, size) = pages(size); let index = self.mmap_ioctl(addr, count)?; let unix_mmap = MmapUnix::new(size, prot, self.flags, self.as_raw_fd(), index)?; Ok((unix_mmap, index)) } fn unmap_range(&self, unix_mmap: MmapUnix, size: usize, index: u64) { let (count, _) = pages(size); // Unmap the address first. drop(unix_mmap); self.unmap_ioctl(count as u32, index).unwrap(); } fn mmap_ioctl(&self, addr: GuestAddress, count: usize) -> Result { let base = ((addr.0 & !XEN_GRANT_ADDR_OFF) / page_size()) as u32; let wrapper = GntDevMapGrantRef::new(self.domid, base, count)?; let reference = wrapper.as_fam_struct_ref(); // SAFETY: This is safe because the ioctl guarantees to not access memory beyond reference. let ret = unsafe { ioctl_with_ref(self, ioctl_gntdev_map_grant_ref(), reference) }; if ret == 0 { Ok(reference.index) } else { Err(Error::Mmap(io::Error::last_os_error())) } } fn unmap_ioctl(&self, count: u32, index: u64) -> Result<()> { let unmap = GntDevUnmapGrantRef::new(index, count); // SAFETY: This is safe because the ioctl guarantees to not access memory beyond unmap. let ret = unsafe { ioctl_with_ref(self, ioctl_gntdev_unmap_grant_ref(), &unmap) }; if ret == 0 { Ok(()) } else { Err(Error::Mmap(io::Error::last_os_error())) } } } impl MmapXenTrait for MmapXenGrant { // Maps a slice out of the entire region. fn mmap_slice(&self, addr: *const u8, prot: i32, len: usize) -> Result { MmapXenSlice::new_with(self.clone(), addr as usize, prot, len) } fn addr(&self) -> *mut u8 { if let Some(ref unix_mmap) = self.unix_mmap { unix_mmap.addr() } else { null_mut() } } } impl Drop for MmapXenGrant { fn drop(&mut self) { if let Some(unix_mmap) = self.unix_mmap.take() { self.unmap_range(unix_mmap, self.size, self.index); } } } #[derive(Debug)] pub(crate) struct MmapXenSlice { grant: Option, unix_mmap: Option, addr: *mut u8, size: usize, index: u64, } impl MmapXenSlice { fn raw(addr: *mut u8) -> Self { Self { grant: None, unix_mmap: None, addr, size: 0, index: 0, } } fn new_with(grant: MmapXenGrant, offset: usize, prot: i32, size: usize) -> Result { let page_size = page_size() as usize; let page_base: usize = (offset / page_size) * page_size; let offset = offset - page_base; let size = offset + size; let addr = grant.guest_base.0 + page_base as u64; let (unix_mmap, index) = grant.mmap_range(GuestAddress(addr), size, prot)?; // SAFETY: We have already mapped the range including offset. let addr = unsafe { unix_mmap.addr().add(offset) }; Ok(Self { grant: Some(grant), unix_mmap: Some(unix_mmap), addr, size, index, }) } // Mapped address for the region. pub(crate) fn addr(&self) -> *mut u8 { self.addr } } impl Drop for MmapXenSlice { fn drop(&mut self) { // Unmaps memory automatically once this instance goes out of scope. if let Some(unix_mmap) = self.unix_mmap.take() { self.grant .as_ref() .unwrap() .unmap_range(unix_mmap, self.size, self.index); } } } #[derive(Debug)] pub struct MmapXen { xen_flags: MmapXenFlags, domid: u32, mmap: Box, } impl MmapXen { fn new(range: &MmapRange) -> Result { let xen_flags = match MmapXenFlags::from_bits(range.mmap_flags) { Some(flags) => flags, None => return Err(Error::MmapFlags(range.mmap_flags)), }; if !xen_flags.is_valid() { return Err(Error::MmapFlags(xen_flags.bits())); } Ok(Self { xen_flags, domid: range.mmap_data, mmap: if xen_flags.is_foreign() { Box::new(MmapXenForeign::new(range)?) } else if xen_flags.is_grant() { Box::new(MmapXenGrant::new(range, xen_flags)?) } else { Box::new(MmapXenUnix::new(range)?) }, }) } fn addr(&self) -> *mut u8 { self.mmap.addr() } fn flags(&self) -> u32 { self.xen_flags.bits() } fn data(&self) -> u32 { self.domid } fn mmap_in_advance(&self) -> bool { self.xen_flags.mmap_in_advance() } pub(crate) fn mmap( mmap_xen: Option<&Self>, addr: *mut u8, prot: i32, len: usize, ) -> MmapXenSlice { match mmap_xen { Some(mmap_xen) => mmap_xen.mmap.mmap_slice(addr, prot, len).unwrap(), None => MmapXenSlice::raw(addr), } } } #[cfg(test)] mod tests { #![allow(clippy::undocumented_unsafe_blocks)] use super::*; use vmm_sys_util::tempfile::TempFile; // Adding a helper method to extract the errno within an Error::Mmap(e), or return a // distinctive value when the error is represented by another variant. impl Error { fn raw_os_error(&self) -> i32 { match self { Error::Mmap(e) => e.raw_os_error().unwrap(), _ => std::i32::MIN, } } } #[allow(unused_variables)] pub unsafe fn ioctl_with_ref(fd: &F, req: c_ulong, arg: &T) -> c_int { 0 } impl MmapRange { fn initialized(is_file: bool) -> Self { let file_offset = if is_file { Some(FileOffset::new(TempFile::new().unwrap().into_file(), 0)) } else { None }; let mut range = MmapRange::new_unix(0x1000, file_offset, GuestAddress(0x1000)); range.prot = Some(libc::PROT_READ | libc::PROT_WRITE); range.mmap_data = 1; range } } impl MmapRegion { pub fn new(size: usize) -> Result { let range = MmapRange::new_unix(size, None, GuestAddress(0)); Self::from_range(range) } } #[test] fn test_mmap_xen_failures() { let mut range = MmapRange::initialized(true); // Invalid flags range.mmap_flags = 16; let r = MmapXen::new(&range); assert_eq!( format!("{:?}", r.unwrap_err()), format!("MmapFlags({})", range.mmap_flags), ); range.mmap_flags = MmapXenFlags::FOREIGN.bits() | MmapXenFlags::GRANT.bits(); let r = MmapXen::new(&range); assert_eq!( format!("{:?}", r.unwrap_err()), format!("MmapFlags({:x})", MmapXenFlags::ALL.bits()), ); range.mmap_flags = MmapXenFlags::FOREIGN.bits() | MmapXenFlags::NO_ADVANCE_MAP.bits(); let r = MmapXen::new(&range); assert_eq!( format!("{:?}", r.unwrap_err()), format!( "MmapFlags({:x})", MmapXenFlags::NO_ADVANCE_MAP.bits() | MmapXenFlags::FOREIGN.bits(), ), ); } #[test] fn test_mmap_xen_success() { let mut range = MmapRange::initialized(true); range.mmap_flags = MmapXenFlags::FOREIGN.bits(); let r = MmapXen::new(&range).unwrap(); assert_eq!(r.flags(), range.mmap_flags); assert_eq!(r.data(), range.mmap_data); assert_ne!(r.addr(), null_mut()); assert!(r.mmap_in_advance()); range.mmap_flags = MmapXenFlags::GRANT.bits(); let r = MmapXen::new(&range).unwrap(); assert_eq!(r.flags(), range.mmap_flags); assert_eq!(r.data(), range.mmap_data); assert_ne!(r.addr(), null_mut()); assert!(r.mmap_in_advance()); range.mmap_flags = MmapXenFlags::GRANT.bits() | MmapXenFlags::NO_ADVANCE_MAP.bits(); let r = MmapXen::new(&range).unwrap(); assert_eq!(r.flags(), range.mmap_flags); assert_eq!(r.data(), range.mmap_data); assert_eq!(r.addr(), null_mut()); assert!(!r.mmap_in_advance()); } #[test] fn test_foreign_map_failure() { let mut range = MmapRange::initialized(true); range.file_offset = Some(FileOffset::new(TempFile::new().unwrap().into_file(), 0)); range.prot = None; let r = MmapXenForeign::new(&range); assert_eq!(format!("{:?}", r.unwrap_err()), "UnexpectedError"); let mut range = MmapRange::initialized(true); range.flags = None; let r = MmapXenForeign::new(&range); assert_eq!(format!("{:?}", r.unwrap_err()), "UnexpectedError"); let mut range = MmapRange::initialized(true); range.file_offset = Some(FileOffset::new(TempFile::new().unwrap().into_file(), 1)); let r = MmapXenForeign::new(&range); assert_eq!(format!("{:?}", r.unwrap_err()), "InvalidOffsetLength"); let mut range = MmapRange::initialized(true); range.size = 0; let r = MmapXenForeign::new(&range); assert_eq!(r.unwrap_err().raw_os_error(), libc::EINVAL); } #[test] fn test_foreign_map_success() { let range = MmapRange::initialized(true); let r = MmapXenForeign::new(&range).unwrap(); assert_ne!(r.addr(), null_mut()); assert_eq!(r.domid, range.mmap_data); assert_eq!(r.guest_base, range.addr); } #[test] fn test_grant_map_failure() { let mut range = MmapRange::initialized(true); range.prot = None; let r = MmapXenGrant::new(&range, MmapXenFlags::empty()); assert_eq!(format!("{:?}", r.unwrap_err()), "UnexpectedError"); let mut range = MmapRange::initialized(true); range.prot = None; // Protection isn't used for no-advance mappings MmapXenGrant::new(&range, MmapXenFlags::NO_ADVANCE_MAP).unwrap(); let mut range = MmapRange::initialized(true); range.flags = None; let r = MmapXenGrant::new(&range, MmapXenFlags::NO_ADVANCE_MAP); assert_eq!(format!("{:?}", r.unwrap_err()), "UnexpectedError"); let mut range = MmapRange::initialized(true); range.file_offset = Some(FileOffset::new(TempFile::new().unwrap().into_file(), 1)); let r = MmapXenGrant::new(&range, MmapXenFlags::NO_ADVANCE_MAP); assert_eq!(format!("{:?}", r.unwrap_err()), "InvalidOffsetLength"); let mut range = MmapRange::initialized(true); range.size = 0; let r = MmapXenGrant::new(&range, MmapXenFlags::empty()); assert_eq!(r.unwrap_err().raw_os_error(), libc::EINVAL); } #[test] fn test_grant_map_success() { let range = MmapRange::initialized(true); let r = MmapXenGrant::new(&range, MmapXenFlags::NO_ADVANCE_MAP).unwrap(); assert_eq!(r.addr(), null_mut()); assert_eq!(r.domid, range.mmap_data); assert_eq!(r.guest_base, range.addr); let mut range = MmapRange::initialized(true); // Size isn't used with no-advance mapping. range.size = 0; MmapXenGrant::new(&range, MmapXenFlags::NO_ADVANCE_MAP).unwrap(); let range = MmapRange::initialized(true); let r = MmapXenGrant::new(&range, MmapXenFlags::empty()).unwrap(); assert_ne!(r.addr(), null_mut()); assert_eq!(r.domid, range.mmap_data); assert_eq!(r.guest_base, range.addr); } #[test] fn test_grant_ref_alloc() { let wrapper = GntDevMapGrantRef::new(0, 0x1000, 0x100).unwrap(); let r = wrapper.as_fam_struct_ref(); assert_eq!(r.count, 0x100); assert_eq!(r.pad, 0); assert_eq!(r.index, 0); } }