| // Copyright 2026 The Fuchsia Authors |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| use super::arch_vm_aspace::ArchMmuFlags; |
| use super::vm_object_paged::VmObjectPaged; |
| use crate::kernel::types::PAddr; |
| use core::marker::{PhantomData, PhantomPinned}; |
| use core::mem::ManuallyDrop; |
| use core::ptr::NonNull; |
| use fbl::{HasRefCount, Recyclable, RefPtr}; |
| use kalloc::AllocError; |
| use vm_object_bindings as bindings; |
| use zx_status::Status; |
| use zx_types::zx_status_t; |
| |
| pub use bindings::{Resizability, SnapshotType, VmObject_EvictionHint as EvictionHint}; |
| |
| /// The base vm object that holds a range of bytes of data |
| /// |
| /// Can be created without mapping and used as a container of data, or mappable |
| /// into an address space via VmAddressRegion::CreateVmMapping |
| #[repr(C)] |
| pub struct VmObject { |
| raw: bindings::VmObject, |
| phantom: PhantomData<PhantomPinned>, |
| } |
| |
| impl VmObject { |
| pub const MAX_SIZE: u64 = bindings::VmObject_MAX_SIZE; |
| |
| /// Domain-specific conversion: returns raw pointer for `VmObject`. |
| pub fn as_raw(&self) -> *mut bindings::VmObject { |
| core::ptr::from_ref(&self.raw).cast_mut() |
| } |
| |
| /// Domain-specific conversion: constructs a `RefPtr` from an exported pointer. |
| /// |
| /// # Safety |
| /// |
| /// `ptr` must be a valid raw `VmObject` pointer exported from C++. |
| pub unsafe fn from_raw(ptr: *mut bindings::VmObject) -> Option<RefPtr<Self>> { |
| unsafe { RefPtr::try_from_raw(ptr.cast::<Self>()) } |
| } |
| |
| /// Returns a raw `VmObject` pointer from an underlying bindings pointer. |
| /// |
| /// Provides additional type safety when used instead of a `.cast()`. |
| pub fn ptr_from_raw(raw: *mut bindings::VmObject) -> *mut VmObject { |
| raw.cast() |
| } |
| |
| /// Returns a pointer to the underlying `VmObject` structure. |
| /// |
| /// This method is helpful when you don't have a reference to the `VmObject`. If you do, then |
| /// use `VmObject::as_raw` instead. |
| pub fn cast_raw(ptr: *mut VmObject) -> *mut bindings::VmObject { |
| ptr.cast() |
| } |
| |
| /// Returns the size of the VMO in bytes. |
| pub fn size(&self) -> u64 { |
| // SAFETY: `self.as_raw()` returns a valid `VmObject` pointer. |
| unsafe { bindings::cpp_vm_object_size(self.as_raw()) } |
| } |
| |
| /// Returns whether the VMO is resizable. |
| pub fn is_resizable(&self) -> bool { |
| // SAFETY: `self.as_raw()` returns a valid `VmObject` pointer. |
| unsafe { bindings::cpp_vm_object_is_resizable(self.as_raw()) } |
| } |
| |
| /// Returns whether the VMO is contiguous. |
| pub fn is_contiguous(&self) -> bool { |
| // SAFETY: `self.as_raw()` returns a valid `VmObject` pointer. |
| unsafe { bindings::cpp_vm_object_is_contiguous(self.as_raw()) } |
| } |
| |
| /// Resizes the VMO to the given size. |
| pub fn resize(&self, size: u64) -> Result<(), Status> { |
| // SAFETY: `self.as_raw()` returns a valid `VmObject` pointer. |
| let status = unsafe { bindings::cpp_vm_object_resize(self.as_raw(), size) }; |
| Status::ok(status) |
| } |
| |
| /// Writes data from `data` slice into the VMO at `offset`. |
| pub fn write(&self, offset: u64, data: &[u8]) -> Result<(), Status> { |
| // SAFETY: `self.as_raw()` returns a valid `VmObject` pointer and `data` points to |
| // `data.len()` bytes of valid memory. |
| let status = unsafe { |
| bindings::cpp_vm_object_write(self.as_raw(), data.as_ptr().cast(), offset, data.len()) |
| }; |
| Status::ok(status) |
| } |
| |
| /// Sets the name of the VMO. |
| pub fn set_name(&self, name: &[u8]) -> Result<(), Status> { |
| // SAFETY: `self.as_raw()` returns a valid `VmObject` pointer and `name` points to |
| // `name.len()` bytes of valid memory. |
| let status = unsafe { |
| bindings::cpp_vm_object_set_name(self.as_raw(), name.as_ptr().cast(), name.len()) |
| }; |
| Status::ok(status) |
| } |
| |
| /// Decommit a range of pages from the VMO. |
| pub fn decommit_range(&self, offset: u64, len: u64) -> Result<(), Status> { |
| // SAFETY: `self.as_raw()` returns a valid `VmObject` pointer. |
| let status = unsafe { bindings::cpp_vm_object_decommit_range(self.as_raw(), offset, len) }; |
| Status::ok(status) |
| } |
| |
| /// Commits the specified range of pages in the VMO. |
| pub fn commit_range(&self, offset: u64, len: u64) -> Result<(), Status> { |
| // SAFETY: `self.as_raw()` returns a valid `VmObject` pointer. |
| let status = unsafe { bindings::cpp_vm_object_commit_range(self.as_raw(), offset, len) }; |
| Status::ok(status) |
| } |
| |
| /// Commits and pins the specified range of pages in the VMO. |
| pub fn commit_range_pinned(&self, offset: u64, len: u64, write: bool) -> Result<(), Status> { |
| // SAFETY: `self.as_raw()` returns a valid `VmObject` pointer. |
| let status = unsafe { |
| bindings::cpp_vm_object_commit_range_pinned(self.as_raw(), offset, len, write) |
| }; |
| Status::ok(status) |
| } |
| |
| /// Unpins the specified range of pages in the VMO. |
| pub fn unpin(&self, offset: u64, len: u64) { |
| // SAFETY: `self.as_raw()` returns a valid `VmObject` pointer. |
| unsafe { |
| bindings::cpp_vm_object_unpin(self.as_raw(), offset, len); |
| } |
| } |
| |
| /// Provide an eviction hint for a range of pages. |
| pub fn hint_range(&self, offset: u64, len: u64, hint: EvictionHint) -> Result<(), Status> { |
| let status = |
| unsafe { bindings::cpp_vm_object_hint_range(self.as_raw(), offset, len, hint) }; |
| Status::ok(status) |
| } |
| |
| /// Returns the mapping cache policy of the VMO. |
| pub fn get_mapping_cache_policy(&self) -> ArchMmuFlags { |
| // SAFETY: `self.as_raw()` returns a valid `VmObject` pointer. |
| unsafe { bindings::cpp_vm_object_get_mapping_cache_policy(self.as_raw()) } |
| } |
| |
| /// Create a copy-on-write clone VMO at the page-aligned offset and length. |
| pub fn create_clone( |
| &self, |
| resizable: Resizability, |
| snapshot_type: SnapshotType, |
| offset: u64, |
| size: u64, |
| copy_name: bool, |
| ) -> Result<RefPtr<VmObject>, Status> { |
| let mut status = 0; |
| // SAFETY: `self.as_raw()` returns a valid `VmObject` pointer. |
| let raw = unsafe { |
| bindings::cpp_vm_object_create_clone( |
| self.as_raw(), |
| resizable, |
| snapshot_type, |
| offset, |
| size, |
| copy_name, |
| &mut status, |
| ) |
| }; |
| Status::ok(status)?; |
| // SAFETY: cpp_vm_object_create_clone returns valid VmObject pointers, or null. |
| let clone = unsafe { VmObject::from_raw(raw) }; |
| Ok(clone.expect("clone returned ZX_OK; must be non-null")) |
| } |
| |
| /// Helper variant of get_page that will retry the operation after waiting on a PageRequest if required. |
| pub fn get_page_blocking(&self, offset: u64, pf_flags: u32) -> Result<(), Status> { |
| // SAFETY: `self.as_raw()` returns a valid `VmObject` pointer. |
| let status = |
| unsafe { bindings::cpp_vm_object_get_page_blocking(self.as_raw(), offset, pf_flags) }; |
| Status::ok(status) |
| } |
| |
| /// Downcasts a `RefPtr<VmObject>` by value into a `RefPtr<VmObjectPaged>` if it is a paged VMO. |
| pub fn downcast_paged(this: RefPtr<Self>) -> Option<RefPtr<VmObjectPaged>> { |
| let this = ManuallyDrop::new(this); |
| // SAFETY: `this.as_raw()` returns a valid `VmObject` pointer. |
| let raw = |
| unsafe { vm_object_paged_bindings::cpp_vm_object_as_vm_object_paged(this.as_raw()) }; |
| if raw.is_null() { |
| drop(ManuallyDrop::into_inner(this)); |
| None |
| } else { |
| // SAFETY: `raw` points to a valid `VmObjectPaged` whose reference count is owned by |
| // `this`. |
| unsafe { VmObjectPaged::from_raw(raw) } |
| } |
| } |
| |
| /// Sets the user ID of the VMO. |
| pub fn set_user_id(&self, user_id: u64) { |
| // SAFETY: `self.as_raw()` returns a valid `VmObject` pointer. |
| unsafe { bindings::cpp_vm_object_set_user_id(self.as_raw(), user_id) } |
| } |
| |
| /// Returns the user ID of the VMO. |
| pub fn user_id(&self) -> u64 { |
| // SAFETY: `self.as_raw()` returns a valid `VmObject` pointer. |
| unsafe { bindings::cpp_vm_object_user_id(self.as_raw()) } |
| } |
| |
| /// Returns the user ID of the parent VMO, if any. |
| pub fn parent_user_id(&self) -> u64 { |
| // SAFETY: `self.as_raw()` returns a valid `VmObject` pointer. |
| unsafe { bindings::cpp_vm_object_parent_user_id(self.as_raw()) } |
| } |
| |
| /// execute lookup_fn on a given range of physical addresses within the vmo. Only pages that are |
| /// present and writable in this VMO will be enumerated. Any copy-on-write pages in our parent |
| /// will not be enumerated. The physical addresses given to the lookup_fn should not be retained |
| /// in any way unless the range has also been pinned by the caller. Offsets provided will be in |
| /// relation to the object being queried, even if pages are actually from a parent object where |
| /// this is a slice. |
| /// Ranges of length zero are considered invalid and will return |
| /// ZX_ERR_INVALID_ARGS. The lookup_fn can terminate iteration early by returning ZX_ERR_STOP. |
| pub fn lookup<T: Sized>( |
| &self, |
| offset: u64, |
| len: u64, |
| ctx: &mut T, |
| lookup_fn: fn(u64, PAddr, &mut T) -> Result<(), Status>, |
| ) -> Result<(), Status> { |
| struct LookupState<'a, T> { |
| ctx: &'a mut T, |
| lookup_fn: fn(u64, PAddr, &mut T) -> Result<(), Status>, |
| } |
| |
| /// # Safety |
| /// |
| /// `ctx` must point to a valid `LookupState<'_, T>` created on the stack in `lookup` |
| /// that remains valid for the duration of the C++ FFI lookup callback. |
| unsafe extern "C" fn lookup_callback_shim<T>( |
| ctx: *mut core::ffi::c_void, |
| offset: u64, |
| paddr: u64, |
| ) -> zx_status_t { |
| // SAFETY: `ctx` is guaranteed by `cpp_vm_object_lookup` to be the non-null `ctx_ptr` |
| // passed from `lookup`, which points to a live `LookupState<'_, T>` on the caller's |
| // stack. |
| let state = unsafe { ctx.cast::<LookupState<'_, T>>().as_mut_unchecked() }; |
| Status::result_into_raw((state.lookup_fn)(offset, paddr.into(), state.ctx)) |
| } |
| |
| let mut state = LookupState { ctx, lookup_fn }; |
| let state_ptr: *mut LookupState<'_, T> = &mut state; |
| // Erase the Rust type so we can pass our context pointer through C++'s void* argument. |
| let ctx_ptr: *mut core::ffi::c_void = state_ptr.cast(); |
| let status = unsafe { |
| bindings::cpp_vm_object_lookup( |
| self.as_raw(), |
| offset, |
| len, |
| ctx_ptr, |
| Some(lookup_callback_shim::<T>), |
| ) |
| }; |
| Status::ok(status) |
| } |
| } |
| |
| impl HasRefCount for VmObject { |
| fn ref_count(&self) -> &fbl::RefCounted { |
| let raw = unsafe { bindings::cpp_vm_object_get_ref_counted(self.as_raw()) }; |
| unsafe { &*(raw.cast::<fbl::RefCounted>()) } |
| } |
| } |
| |
| unsafe impl Recyclable for VmObject { |
| unsafe fn recycle(ptr: NonNull<Self>) { |
| unsafe { |
| bindings::cpp_vm_object_free(VmObject::cast_raw(ptr.as_ptr())); |
| } |
| } |
| |
| fn allocate(_value: Self) -> Result<NonNull<Self>, AllocError> { |
| Err(AllocError) |
| } |
| } |