// Copyright 2020 The ChromiumOS Authors1// Use of this source code is governed by a BSD-style license that can be2// found in the LICENSE file.34use std::cmp::min;5use std::fs::File;6use std::io;7use std::mem::size_of;8use std::ptr::copy_nonoverlapping;9use std::ptr::read_unaligned;10use std::ptr::read_volatile;11use std::ptr::write_unaligned;12use std::ptr::write_volatile;13use std::sync::atomic::fence;14use std::sync::atomic::Ordering;15use std::sync::OnceLock;1617use remain::sorted;18use serde::Deserialize;19use serde::Serialize;20use zerocopy::FromBytes;21use zerocopy::Immutable;22use zerocopy::IntoBytes;2324use crate::descriptor::AsRawDescriptor;25use crate::descriptor::SafeDescriptor;26use crate::platform::MemoryMapping as PlatformMmap;27use crate::SharedMemory;28use crate::VolatileMemory;29use crate::VolatileMemoryError;30use crate::VolatileMemoryResult;31use crate::VolatileSlice;3233static CACHELINE_SIZE: OnceLock<usize> = OnceLock::new();3435#[allow(unused_assignments)]36fn get_cacheline_size_once() -> usize {37let mut assume_reason: &str = "unknown";38cfg_if::cfg_if! {39if #[cfg(all(any(target_os = "android", target_os = "linux"), not(target_env = "musl")))] {40// TODO: Remove once available in libc bindings41#[cfg(target_os = "android")]42const _SC_LEVEL1_DCACHE_LINESIZE: i32 = 0x0094;43#[cfg(target_os = "linux")]44use libc::_SC_LEVEL1_DCACHE_LINESIZE;4546// SAFETY:47// Safe because we check the return value for errors or unsupported requests48let linesize = unsafe { libc::sysconf(_SC_LEVEL1_DCACHE_LINESIZE) };49if linesize > 0 {50return linesize as usize;51} else {52assume_reason = "sysconf cacheline size query failed";53}54} else {55assume_reason = "cacheline size query not implemented for platform/arch";56}57}5859let assumed_size = 64;60log::debug!(61"assuming cacheline_size={}; reason: {}.",62assumed_size,63assume_reason64);65assumed_size66}6768/// Returns the system's effective cacheline size (e.g. the granularity at which arch-specific69/// cacheline management, such as with the clflush instruction, is expected to occur).70#[inline(always)]71fn get_cacheline_size() -> usize {72let size = *CACHELINE_SIZE.get_or_init(get_cacheline_size_once);73assert!(size > 0);74size75}7677#[sorted]78#[derive(Debug, thiserror::Error)]79pub enum Error {80#[error("`add_fd_mapping` is unsupported")]81AddFdMappingIsUnsupported,82#[error("requested memory out of range")]83InvalidAddress,84#[error("requested alignment is incompatible")]85InvalidAlignment,86#[error("invalid argument provided when creating mapping")]87InvalidArgument,88#[error("requested offset is out of range of off_t")]89InvalidOffset,90#[error("requested memory range spans past the end of the region: offset={0} count={1} region_size={2}")]91InvalidRange(usize, usize, usize),92#[error("operation is not implemented on platform/architecture: {0}")]93NotImplemented(&'static str),94#[error("requested memory is not page aligned")]95NotPageAligned,96#[error("failed to read from file to memory: {0}")]97ReadToMemory(#[source] io::Error),98#[error("`remove_mapping` is unsupported")]99RemoveMappingIsUnsupported,100#[error("system call failed while creating the mapping: {0}")]101StdSyscallFailed(io::Error),102#[error("mmap related system call failed: {0}")]103SystemCallFailed(#[source] crate::Error),104#[error("failed to write from memory to file: {0}")]105WriteFromMemory(#[source] io::Error),106}107pub type Result<T> = std::result::Result<T, Error>;108109/// Memory access type for anonymous shared memory mapping.110#[derive(Copy, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Debug)]111pub struct Protection {112pub(crate) read: bool,113pub(crate) write: bool,114}115116impl Protection {117/// Returns Protection allowing read/write access.118#[inline(always)]119pub fn read_write() -> Protection {120Protection {121read: true,122write: true,123}124}125126/// Returns Protection allowing read access.127#[inline(always)]128pub fn read() -> Protection {129Protection {130read: true,131..Default::default()132}133}134135/// Returns Protection allowing write access.136#[inline(always)]137pub fn write() -> Protection {138Protection {139write: true,140..Default::default()141}142}143144/// Set read events.145#[inline(always)]146pub fn set_read(self) -> Protection {147Protection { read: true, ..self }148}149150/// Set write events.151#[inline(always)]152pub fn set_write(self) -> Protection {153Protection {154write: true,155..self156}157}158159/// Returns true if all access allowed by |other| is also allowed by |self|.160#[inline(always)]161pub fn allows(&self, other: &Protection) -> bool {162self.read >= other.read && self.write >= other.write163}164}165166/// See [MemoryMapping](crate::platform::MemoryMapping) for struct- and method-level167/// documentation.168#[derive(Debug)]169pub struct MemoryMapping {170pub(crate) mapping: PlatformMmap,171172// File backed mappings on Windows need to keep the underlying file open while the mapping is173// open.174// This will be a None in non-windows case. The variable will not be read so the '^_'.175//176// TODO(b:230902713) There was a concern about relying on the kernel's refcounting to keep the177// file object's locks (e.g. exclusive read/write) in place. We need to revisit/validate that178// concern.179pub(crate) _file_descriptor: Option<SafeDescriptor>,180}181182#[inline(always)]183unsafe fn flush_one(_addr: *const u8) -> Result<()> {184cfg_if::cfg_if! {185if #[cfg(target_arch = "x86_64")] {186// As per table 11-7 of the SDM, processors are not required to187// snoop UC mappings, so flush the target to memory.188// SAFETY: assumes that the caller has supplied a valid address.189unsafe { core::arch::x86_64::_mm_clflush(_addr) };190Ok(())191} else if #[cfg(target_arch = "aarch64")] {192// Data cache clean by VA to PoC.193std::arch::asm!("DC CVAC, {x}", x = in(reg) _addr);194Ok(())195} else {196Err(Error::NotImplemented("Cache flush not implemented"))197}198}199}200201impl MemoryMapping {202pub fn write_slice(&self, buf: &[u8], offset: usize) -> Result<usize> {203match self.mapping.size().checked_sub(offset) {204Some(size_past_offset) => {205let bytes_copied = min(size_past_offset, buf.len());206// SAFETY:207// The bytes_copied equation above ensures we don't copy bytes out of range of208// either buf or this slice. We also know that the buffers do not overlap because209// slices can never occupy the same memory as a volatile slice.210unsafe {211copy_nonoverlapping(buf.as_ptr(), self.as_ptr().add(offset), bytes_copied);212}213Ok(bytes_copied)214}215None => Err(Error::InvalidAddress),216}217}218219pub fn read_slice(&self, buf: &mut [u8], offset: usize) -> Result<usize> {220match self.size().checked_sub(offset) {221Some(size_past_offset) => {222let bytes_copied = min(size_past_offset, buf.len());223// SAFETY:224// The bytes_copied equation above ensures we don't copy bytes out of range of225// either buf or this slice. We also know that the buffers do not overlap because226// slices can never occupy the same memory as a volatile slice.227unsafe {228copy_nonoverlapping(self.as_ptr().add(offset), buf.as_mut_ptr(), bytes_copied);229}230Ok(bytes_copied)231}232None => Err(Error::InvalidAddress),233}234}235236/// Writes an object to the memory region at the specified offset.237/// Returns Ok(()) if the object fits, or Err if it extends past the end.238///239/// This method is for writing to regular memory. If writing to a mapped240/// I/O region, use [`MemoryMapping::write_obj_volatile`].241///242/// # Examples243/// * Write a u64 at offset 16.244///245/// ```246/// # use base::MemoryMappingBuilder;247/// # use base::SharedMemory;248/// # let shm = SharedMemory::new("test", 1024).unwrap();249/// # let mut mem_map = MemoryMappingBuilder::new(1024).from_shared_memory(&shm).build().unwrap();250/// let res = mem_map.write_obj(55u64, 16);251/// assert!(res.is_ok());252/// ```253pub fn write_obj<T: IntoBytes + Immutable>(&self, val: T, offset: usize) -> Result<()> {254self.mapping.range_end(offset, size_of::<T>())?;255// SAFETY:256// This is safe because we checked the bounds above.257unsafe {258write_unaligned(self.as_ptr().add(offset) as *mut T, val);259}260Ok(())261}262263/// Reads on object from the memory region at the given offset.264/// Reading from a volatile area isn't strictly safe as it could change265/// mid-read. However, as long as the type T is plain old data and can266/// handle random initialization, everything will be OK.267///268/// This method is for reading from regular memory. If reading from a269/// mapped I/O region, use [`MemoryMapping::read_obj_volatile`].270///271/// # Examples272/// * Read a u64 written to offset 32.273///274/// ```275/// # use base::MemoryMappingBuilder;276/// # let mut mem_map = MemoryMappingBuilder::new(1024).build().unwrap();277/// let res = mem_map.write_obj(55u64, 32);278/// assert!(res.is_ok());279/// let num: u64 = mem_map.read_obj(32).unwrap();280/// assert_eq!(55, num);281/// ```282pub fn read_obj<T: FromBytes>(&self, offset: usize) -> Result<T> {283self.mapping.range_end(offset, size_of::<T>())?;284// SAFETY:285// This is safe because by definition Copy types can have their bits set arbitrarily and286// still be valid.287unsafe {288Ok(read_unaligned(289self.as_ptr().add(offset) as *const u8 as *const T290))291}292}293294/// Writes an object to the memory region at the specified offset.295/// Returns Ok(()) if the object fits, or Err if it extends past the end.296///297/// The write operation will be volatile, i.e. it will not be reordered by298/// the compiler and is suitable for I/O, but must be aligned. When writing299/// to regular memory, prefer [`MemoryMapping::write_obj`].300///301/// # Examples302/// * Write a u32 at offset 16.303///304/// ```305/// # use base::MemoryMappingBuilder;306/// # use base::SharedMemory;307/// # let shm = SharedMemory::new("test", 1024).unwrap();308/// # let mut mem_map = MemoryMappingBuilder::new(1024).from_shared_memory(&shm).build().unwrap();309/// let res = mem_map.write_obj_volatile(0xf00u32, 16);310/// assert!(res.is_ok());311/// ```312pub fn write_obj_volatile<T: IntoBytes + Immutable>(313&self,314val: T,315offset: usize,316) -> Result<()> {317self.mapping.range_end(offset, size_of::<T>())?;318// Make sure writes to memory have been committed before performing I/O that could319// potentially depend on them.320fence(Ordering::SeqCst);321// SAFETY:322// This is safe because we checked the bounds above.323unsafe {324write_volatile(self.as_ptr().add(offset) as *mut T, val);325}326Ok(())327}328329/// Reads on object from the memory region at the given offset.330/// Reading from a volatile area isn't strictly safe as it could change331/// mid-read. However, as long as the type T is plain old data and can332/// handle random initialization, everything will be OK.333///334/// The read operation will be volatile, i.e. it will not be reordered by335/// the compiler and is suitable for I/O, but must be aligned. When reading336/// from regular memory, prefer [`MemoryMapping::read_obj`].337///338/// # Examples339/// * Read a u32 written to offset 16.340///341/// ```342/// # use base::MemoryMappingBuilder;343/// # use base::SharedMemory;344/// # let shm = SharedMemory::new("test", 1024).unwrap();345/// # let mut mem_map = MemoryMappingBuilder::new(1024).from_shared_memory(&shm).build().unwrap();346/// let res = mem_map.write_obj(0xf00u32, 16);347/// assert!(res.is_ok());348/// let num: u32 = mem_map.read_obj_volatile(16).unwrap();349/// assert_eq!(0xf00, num);350/// ```351pub fn read_obj_volatile<T: FromBytes>(&self, offset: usize) -> Result<T> {352self.mapping.range_end(offset, size_of::<T>())?;353// SAFETY:354// This is safe because by definition Copy types can have their bits set arbitrarily and355// still be valid.356unsafe {357Ok(read_volatile(358self.as_ptr().add(offset) as *const u8 as *const T359))360}361}362363pub fn msync(&self) -> Result<()> {364self.mapping.msync()365}366367/// Flush a region of the MemoryMapping from the system's caching hierarchy.368/// There are several uses for flushing:369///370/// * Cached memory which the guest may be reading through an uncached mapping:371///372/// Guest reads via an uncached mapping can bypass the cache and directly access main373/// memory. This is outside the memory model of Rust, which means that even with proper374/// synchronization, guest reads via an uncached mapping might not see updates from the375/// host. As such, it is necessary to perform architectural cache maintainance to flush the376/// host writes to main memory.377///378/// Note that this does not support writable uncached guest mappings, as doing so379/// requires invalidating the cache, not flushing the cache.380///381/// * Uncached memory which the guest may be writing through a cached mapping:382///383/// Guest writes via a cached mapping of a host's uncached memory may never make it to384/// system/device memory prior to being read. In such cases, explicit flushing of the cached385/// writes is necessary, since other managers of the host's uncached mapping (e.g. DRM) see386/// no need to flush, as they believe all writes would explicitly bypass the caches.387///388/// Currently only supported on x86_64 and aarch64. Cannot be supported on 32-bit arm.389pub fn flush_region(&self, offset: usize, len: usize) -> Result<()> {390let addr: *const u8 = self.as_ptr();391let size = self.size();392393// disallow overflow/wrapping ranges and subregion extending beyond mapped range394if usize::MAX - size < addr as usize || offset >= size || size - offset < len {395return Err(Error::InvalidRange(offset, len, size));396}397398// SAFETY:399// Safe because already validated that `next` will be an address in the mapping:400// * mapped region is non-wrapping401// * subregion is bounded within the mapped region402let mut next: *const u8 = unsafe { addr.add(offset) };403404let cacheline_size = get_cacheline_size();405let cacheline_count = len.div_ceil(cacheline_size);406407for _ in 0..cacheline_count {408// SAFETY:409// Safe because `next` is guaranteed to be within the mapped region (see earlier410// validations), and flushing the cache doesn't affect any rust safety properties.411unsafe { flush_one(next)? };412413// SAFETY:414// Safe because we never use next if it goes out of the mapped region or overflows its415// storage type (based on earlier validations and the loop bounds).416next = unsafe { next.add(cacheline_size) };417}418Ok(())419}420421/// Flush all backing memory for a mapping in an arch-specific manner (see `flush_region()`).422pub fn flush_all(&self) -> Result<()> {423self.flush_region(0, self.size())424}425}426427pub struct MemoryMappingBuilder<'a> {428pub(crate) descriptor: Option<&'a dyn AsRawDescriptor>,429pub(crate) is_file_descriptor: bool,430#[cfg_attr(target_os = "macos", allow(unused))]431pub(crate) size: usize,432pub(crate) offset: Option<u64>,433pub(crate) align: Option<u64>,434pub(crate) protection: Option<Protection>,435#[cfg_attr(target_os = "macos", allow(unused))]436#[cfg_attr(windows, allow(unused))]437pub(crate) populate: bool,438}439440/// Builds a MemoryMapping object from the specified arguments.441impl<'a> MemoryMappingBuilder<'a> {442/// Creates a new builder specifying size of the memory region in bytes.443pub fn new(size: usize) -> MemoryMappingBuilder<'a> {444MemoryMappingBuilder {445descriptor: None,446size,447is_file_descriptor: false,448offset: None,449align: None,450protection: None,451populate: false,452}453}454455/// Build the memory mapping given the specified File to mapped memory456///457/// Default: Create a new memory mapping.458///459/// Note: this is a forward looking interface to accomodate platforms that460/// require special handling for file backed mappings.461#[allow(clippy::wrong_self_convention, unused_mut)]462pub fn from_file(mut self, file: &'a File) -> MemoryMappingBuilder<'a> {463// On Windows, files require special handling (next day shipping if possible).464self.is_file_descriptor = true;465466self.descriptor = Some(file as &dyn AsRawDescriptor);467self468}469470/// Build the memory mapping given the specified SharedMemory to mapped memory471///472/// Default: Create a new memory mapping.473pub fn from_shared_memory(mut self, shm: &'a SharedMemory) -> MemoryMappingBuilder<'a> {474self.descriptor = Some(shm as &dyn AsRawDescriptor);475self476}477478/// Offset in bytes from the beginning of the mapping to start the mmap.479///480/// Default: No offset481pub fn offset(mut self, offset: u64) -> MemoryMappingBuilder<'a> {482self.offset = Some(offset);483self484}485486/// Protection (e.g. readable/writable) of the memory region.487///488/// Default: Read/write489pub fn protection(mut self, protection: Protection) -> MemoryMappingBuilder<'a> {490self.protection = Some(protection);491self492}493494/// Alignment of the memory region mapping in bytes.495///496/// Default: No alignment497pub fn align(mut self, alignment: u64) -> MemoryMappingBuilder<'a> {498self.align = Some(alignment);499self500}501}502503impl VolatileMemory for MemoryMapping {504fn get_slice(&self, offset: usize, count: usize) -> VolatileMemoryResult<VolatileSlice> {505let mem_end = offset506.checked_add(count)507.ok_or(VolatileMemoryError::Overflow {508base: offset,509offset: count,510})?;511512if mem_end > self.size() {513return Err(VolatileMemoryError::OutOfBounds { addr: mem_end });514}515516let new_addr =517(self.as_ptr() as usize)518.checked_add(offset)519.ok_or(VolatileMemoryError::Overflow {520base: self.as_ptr() as usize,521offset,522})?;523524// SAFETY:525// Safe because we checked that offset + count was within our range and we only ever hand526// out volatile accessors.527Ok(unsafe { VolatileSlice::from_raw_parts(new_addr as *mut u8, count) })528}529}530531/// A range of memory that can be msynced, for abstracting over different types of memory mappings.532///533/// # Safety534/// Safe when implementers guarantee `ptr`..`ptr+size` is an mmaped region owned by this object that535/// can't be unmapped during the `MappedRegion`'s lifetime.536pub unsafe trait MappedRegion: Send + Sync {537// SAFETY:538/// Returns a pointer to the beginning of the memory region. Should only be539/// used for passing this region to ioctls for setting guest memory.540fn as_ptr(&self) -> *mut u8;541542/// Returns the size of the memory region in bytes.543fn size(&self) -> usize;544545/// Maps `size` bytes starting at `fd_offset` bytes from within the given `fd`546/// at `offset` bytes from the start of the region with `prot` protections.547/// `offset` must be page aligned.548///549/// # Arguments550/// * `offset` - Page aligned offset into the arena in bytes.551/// * `size` - Size of memory region in bytes.552/// * `fd` - File descriptor to mmap from.553/// * `fd_offset` - Offset in bytes from the beginning of `fd` to start the mmap.554/// * `prot` - Protection (e.g. readable/writable) of the memory region.555fn add_fd_mapping(556&mut self,557_offset: usize,558_size: usize,559_fd: &dyn AsRawDescriptor,560_fd_offset: u64,561_prot: Protection,562) -> Result<()> {563Err(Error::AddFdMappingIsUnsupported)564}565566/// Remove `size`-byte mapping starting at `offset`.567fn remove_mapping(&mut self, _offset: usize, _size: usize) -> Result<()> {568Err(Error::RemoveMappingIsUnsupported)569}570}571572// SAFETY:573// Safe because it exclusively forwards calls to a safe implementation.574unsafe impl MappedRegion for MemoryMapping {575fn as_ptr(&self) -> *mut u8 {576self.mapping.as_ptr()577}578579fn size(&self) -> usize {580self.mapping.size()581}582}583584#[derive(Debug, PartialEq, Eq)]585pub struct ExternalMapping {586pub ptr: u64,587pub size: usize,588}589590// SAFETY:591// `ptr`..`ptr+size` is an mmaped region and is owned by this object. Caller592// needs to ensure that the region is not unmapped during the `MappedRegion`'s593// lifetime.594unsafe impl MappedRegion for ExternalMapping {595/// used for passing this region to ioctls for setting guest memory.596fn as_ptr(&self) -> *mut u8 {597self.ptr as *mut u8598}599600/// Returns the size of the memory region in bytes.601fn size(&self) -> usize {602self.size603}604}605606607