blob: 00a41ae331a263a423848ef553c17db5ba5d149a [file]
// 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 crate::kernel::types::PAddr;
use core::ops::Deref;
use core::ptr::NonNull;
use fbl::{IsOpaqueRefCounted, OpaqueRefCountedFacade, RefPtr};
use ksync::{KMutex, RawCriticalMutex, guarded};
use zr::ToMutPtr;
use zx_status::Status;
use super::arch_vm_aspace::ArchMmuFlags;
use super::vm_object::VmObject;
use vm_constants_rs as constants;
use vm_object_bindings as bindings;
use ::page as kernel_page;
// Assert size and alignment of VmObjectPhysicalState matches the generated C++ constants.
::zr::static_assert_size_and_align!(
VmObjectPhysicalState,
constants::kVmObjectPhysicalStateSize,
constants::kVmObjectPhysicalStateAlign,
);
#[guarded]
#[repr(C)]
pub struct VmObjectPhysicalState {
#[mutex]
lock: KMutex<RawCriticalMutex>,
size: u64,
base: PAddr,
is_slice: bool,
parent_user_id: u64,
// parent is guarded by ChildListLock on the C++ side.
parent: Option<RefPtr<VmObjectPhysical>>,
}
impl VmObjectPhysicalState {
pub fn init(
base: PAddr,
size: u64,
is_slice: bool,
parent_user_id: u64,
) -> impl pin_init::PinInit<Self, core::convert::Infallible> {
pin_init::pin_init!(Self {
lock <- KMutex::init(),
size,
base,
is_slice,
parent_user_id,
parent: None,
})
}
}
const PAGE_SIZE: u64 = kernel_page::SIZE as u64;
const ZX_CACHE_POLICY_MASK: ArchMmuFlags = constants::kVmCachePolicyMask as ArchMmuFlags;
const fn in_range(offset: u64, len: u64, size: u64) -> bool {
if let Some(end) = offset.checked_add(len) { end <= size } else { false }
}
unsafe extern "C" {
fn cpp_vm_object_lookup_fn_invoke(
lookup_fn: *const VmObjectLookupFunction,
offset: u64,
pa: PAddr,
) -> Status;
fn cpp_vm_object_get_mapping_cache_policy_locked(vmo: *const VmObjectPhysical) -> ArchMmuFlags;
fn cpp_vm_object_num_mappings_locked(vmo: *const VmObjectPhysical) -> usize;
fn cpp_child_list_lock_acquire() -> bool;
fn cpp_child_list_lock_release(should_clear: bool);
fn cpp_vm_object_has_children_locked(vmo: *const VmObjectPhysical) -> bool;
fn cpp_vm_object_set_cache_policy_locked(
vmo: *mut VmObjectPhysical,
cache_policy: ArchMmuFlags,
);
}
#[repr(C)]
/// Opaque type representing the C++ `VmObject::LookupFunction` callback.
pub struct VmObjectLookupFunction {
_private: [u8; 0],
}
#[repr(C)]
/// FFI-safe result structure returned by `rust_vm_object_physical_state_lookup_contiguous_locked`.
pub struct LookupContiguousResult {
/// The status of the lookup operation.
pub status: Status,
/// The physical address retrieved, valid only if `status` is `Status::OK`.
pub paddr: PAddr,
}
#[repr(C)]
/// FFI-safe result structure returned by `rust_vm_object_physical_validate_child_slice_args`.
pub struct ValidateChildSliceResult {
/// The status of the validation operation.
pub status: Status,
/// The physical address retrieved, valid only if `status` is `Status::OK`.
pub base: PAddr,
}
/// RAII guard that manages the acquisition and release of the child list lock.
pub struct ChildListLockGuard {
should_clear: bool,
}
impl ChildListLockGuard {
#[inline(always)]
pub fn new() -> Self {
// TODO(https://fxbug.dev/539633525): Figure out lockdep validation for
// ChildListLock instead of bypassing it via C++ FFI wrappers.
// SAFETY: Calling the stateless helper `cpp_child_list_lock_acquire` has no
// preconditions and cannot cause undefined behavior or invalid memory access.
let should_clear = unsafe { cpp_child_list_lock_acquire() };
Self { should_clear }
}
}
impl Drop for ChildListLockGuard {
#[inline(always)]
fn drop(&mut self) {
// SAFETY: Calling the stateless helper `cpp_child_list_lock_release` has no
// preconditions and cannot cause undefined behavior or invalid memory access.
unsafe {
cpp_child_list_lock_release(self.should_clear);
}
}
}
#[repr(C)]
/// VMO representing a physical range of memory
pub struct VmObjectPhysical {
_facade: OpaqueRefCountedFacade<VmObject>,
}
unsafe impl IsOpaqueRefCounted for VmObjectPhysical {
type TargetBase = VmObject;
}
impl Deref for VmObjectPhysical {
type Target = VmObject;
fn deref(&self) -> &Self::Target {
// SAFETY: `raw` is derived from the valid reference `self`. The FFI helper performs
// a safe `static_cast` to the base `VmObject`, returning a valid pointer that is safe
// to dereference for the lifetime of `self`.
unsafe {
let raw = self as *const Self as *mut Self;
&*cpp_vm_object_physical_as_vm_object(raw)
}
}
}
unsafe extern "C" {
fn cpp_vm_object_physical_create(
base: PAddr,
size: usize,
out_status: *mut Status,
) -> *mut VmObjectPhysical;
fn cpp_vm_object_physical_as_vm_object(vmo: *mut VmObjectPhysical) -> *mut VmObject;
}
impl VmObjectPhysical {
/// Create a new physical VMO for the given physical region.
pub fn create(base: PAddr, size: usize) -> Result<RefPtr<VmObjectPhysical>, Status> {
let mut status = Status::OK;
// SAFETY: The pointer derived from `&mut status` is valid.
let raw = unsafe { cpp_vm_object_physical_create(base, size, &mut status) };
if status != Status::OK {
return Err(status);
}
// SAFETY: The raw pointer returned by C++ is refcounted and ownership is transferred
// to Rust via RefPtr.
unsafe { RefPtr::try_from_raw(raw).ok_or(Status::NO_MEMORY) }
}
/// Converts a `RefPtr<VmObjectPhysical>` into a base `RefPtr<VmObject>`.
pub fn into_vm_object(this: RefPtr<Self>) -> RefPtr<VmObject> {
let this: *const VmObjectPhysical = RefPtr::into_raw(this);
let this: *mut VmObjectPhysical = this.cast_mut();
// Since `this` is non-null, `cpp_vm_object_physical_as_vm_object` guarantees its output is
// non-null.
// SAFETY: `this` points to a live `VmObjectPhysical` with an active refcount.
let this: *mut VmObject = unsafe { cpp_vm_object_physical_as_vm_object(this) };
let this: *mut bindings::VmObject = this.cast();
// SAFETY: `this` points to a live `VmObject` with an active refcount.
let this = unsafe { VmObject::from_raw(this) };
this.expect("RefPtr guarantees this is non-null")
}
/// Cast a pointer to a VmObjectPhysical to its base VmObject.
pub fn cast(vmo: NonNull<VmObjectPhysical>) -> NonNull<VmObject> {
// SAFETY: Calls C++ helper to cast VmObjectPhysical pointer to base class VmObject.
// The return value is guaranteed to be non-null and valid.
unsafe { NonNull::new_unchecked(cpp_vm_object_physical_as_vm_object(vmo.as_ptr())) }
}
pub fn get_mapping_cache_policy_locked(&self) -> ArchMmuFlags {
// SAFETY: Safe to call FFI wrapper with valid reference to self.
unsafe { cpp_vm_object_get_mapping_cache_policy_locked(self) }
}
pub fn num_mappings_locked(&self) -> usize {
// SAFETY: Safe to call FFI wrapper with valid reference to self.
unsafe { cpp_vm_object_num_mappings_locked(self) }
}
pub fn has_children_locked(&self) -> bool {
// SAFETY: Safe to call FFI wrapper with valid reference to self.
unsafe { cpp_vm_object_has_children_locked(self) }
}
pub fn set_cache_policy_locked(&self, cache_policy: ArchMmuFlags) {
// SAFETY: Safe to call FFI wrapper with valid reference to self.
unsafe {
cpp_vm_object_set_cache_policy_locked(
core::ptr::from_ref(self).cast_mut(),
cache_policy,
)
}
}
}
// FFI trampolines for C++ calling into Rust VmObjectPhysicalState
/// # Safety
///
/// The caller must ensure `ptr` points to uninitialized memory of at least
/// `size_of::<VmObjectPhysicalState>()` bytes with proper alignment.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_vm_object_physical_state_init(
ptr: *mut VmObjectPhysicalState,
base: PAddr,
size: u64,
is_slice: bool,
parent_user_id: u64,
) {
// SAFETY: The caller guarantees `ptr` points to uninitialized memory of at least
// `size_of::<VmObjectPhysicalState>()` bytes with proper alignment.
unsafe {
let _ = pin_init::PinInit::__pinned_init(
VmObjectPhysicalState::init(base, size, is_slice, parent_user_id),
ptr,
);
}
}
/// # Safety
///
/// The caller must ensure `ptr` points to an initialized `VmObjectPhysicalState`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_vm_object_physical_state_destroy(ptr: *mut VmObjectPhysicalState) {
// SAFETY: The caller guarantees `ptr` points to an initialized `VmObjectPhysicalState`.
unsafe {
core::ptr::drop_in_place(ptr);
}
}
/// # Safety
///
/// The caller must ensure `ptr` points to an initialized `VmObjectPhysicalState`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_vm_object_physical_state_get_lock(
ptr: *const VmObjectPhysicalState,
) -> *mut KMutex<VmObjectPhysicalStateLockClass, RawCriticalMutex> {
// SAFETY: The caller guarantees `ptr` points to an initialized `VmObjectPhysicalState`.
unsafe {
let lock_ref = &(*ptr).lock;
lock_ref.to_mut_ptr()
}
}
/// # Safety
///
/// The caller must ensure `ptr` points to an initialized `VmObjectPhysicalState`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_vm_object_physical_state_get_size(
ptr: *const VmObjectPhysicalState,
) -> u64 {
// SAFETY: The caller guarantees `ptr` points to an initialized `VmObjectPhysicalState`.
unsafe { (*ptr).size }
}
/// # Safety
///
/// The caller must ensure `ptr` points to an initialized `VmObjectPhysicalState`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_vm_object_physical_state_get_base(
ptr: *const VmObjectPhysicalState,
) -> PAddr {
// SAFETY: The caller guarantees `ptr` points to an initialized `VmObjectPhysicalState`.
unsafe { (*ptr).base }
}
/// # Safety
///
/// The caller must ensure `ptr` points to an initialized `VmObjectPhysicalState`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_vm_object_physical_state_get_is_slice(
ptr: *const VmObjectPhysicalState,
) -> bool {
// SAFETY: The caller guarantees `ptr` points to an initialized `VmObjectPhysicalState`.
unsafe { (*ptr).is_slice }
}
/// # Safety
///
/// The caller must ensure `ptr` points to an initialized `VmObjectPhysicalState`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_vm_object_physical_state_get_parent_user_id(
ptr: *const VmObjectPhysicalState,
) -> u64 {
// SAFETY: The caller guarantees `ptr` points to an initialized `VmObjectPhysicalState`.
unsafe { (*ptr).parent_user_id }
}
/// # Safety
///
/// The caller must ensure `ptr` points to an initialized `VmObjectPhysicalState`.
/// The caller must ensure that the `ChildListLock` is held.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_vm_object_physical_state_get_parent_locked(
ptr: *const VmObjectPhysicalState,
) -> *mut VmObjectPhysical {
// SAFETY: The caller guarantees `ptr` points to an initialized `VmObjectPhysicalState`
// and that the ChildListLock is held.
unsafe {
match &(*ptr).parent {
Some(ref_ptr) => RefPtr::into_raw(ref_ptr.clone()).cast_mut(),
None => core::ptr::null_mut(),
}
}
}
/// # Safety
///
/// The caller must ensure `ptr` points to an initialized `VmObjectPhysicalState`.
/// The caller must ensure that the `ChildListLock` is held.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_vm_object_physical_state_set_parent_locked(
ptr: *mut VmObjectPhysicalState,
parent: *mut VmObjectPhysical,
) {
// SAFETY: The caller guarantees `ptr` points to an initialized `VmObjectPhysicalState`
// and that the ChildListLock is held.
unsafe {
let parent_ref = if parent.is_null() { None } else { Some(RefPtr::from_raw(parent)) };
(*ptr).parent = parent_ref;
}
}
/// # Safety
///
/// The caller must ensure `ptr` points to an initialized `VmObjectPhysicalState`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_vm_object_physical_state_lookup_contiguous_locked(
ptr: *const VmObjectPhysicalState,
offset: u64,
len: u64,
) -> LookupContiguousResult {
// SAFETY: The caller guarantees `ptr` points to an initialized `VmObjectPhysicalState`.
let state = unsafe { &*ptr };
if len == 0 || !kernel_page::is_aligned(offset as usize) {
return LookupContiguousResult { status: Status::INVALID_ARGS, paddr: PAddr(0) };
}
if !in_range(offset, len, state.size) {
return LookupContiguousResult { status: Status::OUT_OF_RANGE, paddr: PAddr(0) };
}
LookupContiguousResult { status: Status::OK, paddr: PAddr(state.base.0 + (offset as usize)) }
}
/// # Safety
///
/// The caller must ensure `ptr` points to an initialized `VmObjectPhysicalState`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_vm_object_physical_state_commit_range_pinned(
ptr: *const VmObjectPhysicalState,
offset: u64,
len: u64,
) -> Status {
// SAFETY: The caller guarantees `ptr` points to an initialized `VmObjectPhysicalState`.
let state = unsafe { &*ptr };
if len == 0 || !kernel_page::is_aligned(offset as usize) {
return Status::INVALID_ARGS;
}
ksync::lock!(let _guard = state.lock_lock());
if !in_range(offset, len, state.size) {
return Status::OUT_OF_RANGE;
}
// Physical VMOs are always committed and so are always pinned.
Status::OK
}
/// # Safety
///
/// The caller must ensure `ptr` points to an initialized `VmObjectPhysicalState`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_vm_object_physical_state_prefetch_range(
ptr: *const VmObjectPhysicalState,
offset: u64,
len: u64,
) -> Status {
// SAFETY: The caller guarantees `ptr` points to an initialized `VmObjectPhysicalState`.
let state = unsafe { &*ptr };
if !in_range(offset, len, state.size) {
return Status::OUT_OF_RANGE;
}
Status::OK
}
/// # Safety
///
/// The caller must ensure `ptr` points to an initialized `VmObjectPhysicalState`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_vm_object_physical_state_lookup(
ptr: *const VmObjectPhysicalState,
offset: u64,
len: u64,
lookup_fn: *const VmObjectLookupFunction,
) -> Status {
// SAFETY: The caller guarantees `ptr` points to an initialized `VmObjectPhysicalState`.
let state = unsafe { &*ptr };
if len == 0 {
return Status::INVALID_ARGS;
}
ksync::lock!(let _guard = state.lock_lock());
if !in_range(offset, len, state.size) {
return Status::OUT_OF_RANGE;
}
let mut cur_offset = kernel_page::round_down(offset as usize) as u64;
let end = offset + len;
let end_page_offset = kernel_page::round_up(end as usize) as u64;
let base_addr = state.base;
while cur_offset < end_page_offset {
let pa = PAddr(base_addr.0 + (cur_offset as usize));
// SAFETY: The caller guarantees `lookup_fn` is a valid pointer to VmObjectLookupFunction.
let status = unsafe { cpp_vm_object_lookup_fn_invoke(lookup_fn, cur_offset, pa) };
if status != Status::NEXT {
if status == Status::STOP {
return Status::OK;
}
return status;
}
cur_offset += PAGE_SIZE;
}
Status::OK
}
/// # Safety
///
/// The caller must ensure `state_ptr` points to an initialized `VmObjectPhysicalState`.
/// The caller must ensure `vmo` points to a valid `VmObjectPhysical`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_vm_object_physical_set_mapping_cache_policy(
vmo: *mut VmObjectPhysical,
state_ptr: *const VmObjectPhysicalState,
cache_policy: ArchMmuFlags,
) -> Status {
// SAFETY: The caller guarantees `state_ptr` points to an initialized `VmObjectPhysicalState`.
let state = unsafe { &*state_ptr };
if (cache_policy & !ZX_CACHE_POLICY_MASK) != 0 {
return Status::INVALID_ARGS;
}
ksync::lock!(let _guard = state.lock_lock());
// SAFETY: The caller guarantees `vmo` points to a valid VmObjectPhysical.
let vmo = unsafe { &*vmo };
// If the cache policy is already configured on this VMO and matches
// the requested policy then this is a no-op. This is a common practice
// in the serialio and magma drivers, but may change.
// TODO: revisit this when we shake out more of the future DDK protocol.
if cache_policy == vmo.get_mapping_cache_policy_locked() {
return Status::OK;
}
let _guard = ChildListLockGuard::new();
// If this VMO is mapped already it is not safe to allow its caching policy to change.
if vmo.num_mappings_locked() != 0 || vmo.has_children_locked() || state.parent.is_some() {
return Status::BAD_STATE;
}
vmo.set_cache_policy_locked(cache_policy);
Status::OK
}
/// # Safety
///
/// The caller must ensure `state_ptr` points to an initialized `VmObjectPhysicalState`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_vm_object_physical_validate_child_slice_args(
state_ptr: *const VmObjectPhysicalState,
offset: u64,
size: u64,
) -> ValidateChildSliceResult {
// SAFETY: The caller guarantees `state_ptr` is valid.
let state = unsafe { &*state_ptr };
ksync::lock!(let _guard = state.lock_lock());
// Slice must be wholly contained.
// state.size is not an atomic variable and although it should not be changing, as we are not
// allowing this operation on resizable VMOs, we should still be holding the lock to
// correctly read state.size. We drop the lock when returning from this function before
// performing the child VMO allocation on the C++ side.
if !in_range(offset, size, state.size) {
return ValidateChildSliceResult { status: Status::INVALID_ARGS, base: PAddr(0) };
}
ValidateChildSliceResult { status: Status::OK, base: PAddr(state.base.0 + offset as usize) }
}
/// # Safety
///
/// The caller must ensure `state_ptr` points to an initialized `VmObjectPhysicalState`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_vm_object_physical_dump(
state_ptr: *const VmObjectPhysicalState,
depth: u32,
cpp_vmo_addr: usize,
ref_count: i32,
) {
// SAFETY: The caller guarantees `state_ptr` is valid.
let state = unsafe { &*state_ptr };
ksync::lock!(let _guard = state.lock_lock());
for _ in 0..depth {
kprint::kprint!(" ");
}
kprint::kprintln!(
"object {:p} base {:#x} size {:#x} ref {}",
cpp_vmo_addr,
state.base.0,
state.size,
ref_count,
);
}