| // Copyright 2020 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. |
| |
| use remain::sorted; |
| use thiserror::Error; |
| |
| #[sorted] |
| #[derive(Error, Debug, Eq, PartialEq)] |
| pub enum Error { |
| // For external mappings that have weird sizes |
| #[error("invalid size returned")] |
| InvalidSize, |
| // External library failed to map |
| #[error("library failed to map with {0}")] |
| LibraryError(i32), |
| // A null address is typically bad. mmap allows it, but not external libraries |
| #[error("null address returned")] |
| NullAddress, |
| // If external mapping is unsupported. |
| #[error("external mapping unsupported")] |
| Unsupported, |
| } |
| |
| pub type Result<T> = std::result::Result<T, Error>; |
| |
| // Maps a external library resource given an id, returning address and size upon success |
| pub type Map = fn(u32) -> Result<(u64, usize)>; |
| // Unmaps the resource given a resource id. |
| pub type Unmap = fn(u32); |
| |
| /// ExternalMapping wraps an external library mapping. This is useful in cases where where the |
| /// device memory is not compatible with the mmap interface, such as Vulkan VkDeviceMemory in the |
| /// non-exportable case or when exported as an opaque fd. |
| #[derive(Debug, PartialEq)] |
| pub struct ExternalMapping { |
| resource_id: u32, |
| ptr: u64, |
| size: usize, |
| unmap: Unmap, |
| } |
| |
| unsafe impl Send for ExternalMapping {} |
| unsafe impl Sync for ExternalMapping {} |
| impl ExternalMapping { |
| /// Creates an ExternalMapping given a library-specific resource id and map/unmap functions. |
| /// |
| /// # Safety |
| /// |
| /// The map function must return a valid host memory region. In addition, callers of the |
| /// function must guarantee that the map and unmap functions are thread-safe, never return a |
| /// region overlapping already Rust referenced-data, and the backing store of the resource |
| /// doesn't disappear before the unmap function is called. |
| pub unsafe fn new(resource_id: u32, map: Map, unmap: Unmap) -> Result<ExternalMapping> { |
| let (ptr, size) = map(resource_id)?; |
| |
| if (ptr as *mut u8).is_null() { |
| return Err(Error::NullAddress); |
| } |
| if size == 0 { |
| return Err(Error::InvalidSize); |
| } |
| |
| Ok(ExternalMapping { |
| resource_id, |
| ptr, |
| size, |
| unmap, |
| }) |
| } |
| |
| /// used for passing this region to ioctls for setting guest memory. |
| pub fn as_ptr(&self) -> *mut u8 { |
| self.ptr as *mut u8 |
| } |
| |
| /// Returns the size of the memory region in bytes. |
| pub fn size(&self) -> usize { |
| self.size |
| } |
| } |
| |
| impl Drop for ExternalMapping { |
| fn drop(&mut self) { |
| // This is safe because we own this memory range, and nobody else is holding a reference to |
| // it. |
| (self.unmap)(self.resource_id) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| #[test] |
| fn check_valid_external_map() { |
| let map1: Map = |_resource_id| Ok((0xAAAABBBB, 500)); |
| let map2: Map = |_resource_id| Ok((0xBBBBAAAA, 1000)); |
| let unmap: Unmap = |_resource_id| {}; |
| let external_map1 = unsafe { ExternalMapping::new(0, map1, unmap).unwrap() }; |
| let external_map2 = unsafe { ExternalMapping::new(0, map2, unmap).unwrap() }; |
| |
| assert_eq!(external_map1.as_ptr(), 0xAAAABBBB as *mut u8); |
| assert_eq!(external_map1.size(), 500); |
| assert_eq!(external_map2.as_ptr(), 0xBBBBAAAA as *mut u8); |
| assert_eq!(external_map2.size(), 1000); |
| } |
| |
| #[test] |
| fn check_invalid_external_map() { |
| let map1: Map = |_resource_id| Ok((0xAAAABBBB, 0)); |
| let map2: Map = |_resource_id| Ok((0, 500)); |
| let unmap: Unmap = |_resource_id| {}; |
| |
| assert_eq!( |
| unsafe { ExternalMapping::new(0, map1, unmap) }, |
| Err(Error::InvalidSize) |
| ); |
| |
| assert_eq!( |
| unsafe { ExternalMapping::new(0, map2, unmap) }, |
| Err(Error::NullAddress) |
| ); |
| } |
| |
| #[test] |
| #[should_panic] |
| fn check_external_map_drop() { |
| let map = |_resource_id| Ok((0xAAAABBBB, 500)); |
| let unmap = |_resource_id| panic!(); |
| let _external_map = unsafe { ExternalMapping::new(0, map, unmap) }; |
| } |
| } |