blob: 8e60a03596ef59c62fcec61ba39626d7acda3fa0 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Type-safe bindings for Zircon object information.
use crate::{ok, sys, HandleRef, Status};
use std::mem::MaybeUninit;
use std::ops::Deref;
use zerocopy::{FromBytes, Immutable};
// Tuning constants for get_info_vec(). pub(crate) to support unit tests.
pub(crate) const INFO_VEC_SIZE_INITIAL: usize = 16;
const INFO_VEC_SIZE_PAD: usize = 2;
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
#[repr(transparent)]
pub struct Topic(sys::zx_object_info_topic_t);
impl Deref for Topic {
type Target = sys::zx_object_info_topic_t;
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// A query to get info about a zircon object.
///
/// # Safety
///
/// `InfoTy` must be the same size as what the kernel expects to return for the provided topic.
pub(crate) unsafe trait ObjectQuery {
/// A `Topic` identifying this query.
const TOPIC: Topic;
/// The datatype returned by querying for Self::TOPIC.
type InfoTy: FromBytes + Immutable;
}
assoc_values!(Topic, [
NONE = sys::ZX_INFO_NONE;
HANDLE_VALID = sys::ZX_INFO_HANDLE_VALID;
HANDLE_BASIC = sys::ZX_INFO_HANDLE_BASIC;
PROCESS = sys::ZX_INFO_PROCESS;
PROCESS_THREADS = sys::ZX_INFO_PROCESS_THREADS;
VMAR = sys::ZX_INFO_VMAR;
VMAR_MAPS = sys::ZX_INFO_VMAR_MAPS;
JOB_CHILDREN = sys::ZX_INFO_JOB_CHILDREN;
JOB_PROCESSES = sys::ZX_INFO_JOB_PROCESSES;
THREAD = sys::ZX_INFO_THREAD;
THREAD_EXCEPTION_REPORT = sys::ZX_INFO_THREAD_EXCEPTION_REPORT;
TASK_STATS = sys::ZX_INFO_TASK_STATS;
TASK_RUNTIME = sys::ZX_INFO_TASK_RUNTIME;
PROCESS_MAPS = sys::ZX_INFO_PROCESS_MAPS;
PROCESS_VMOS = sys::ZX_INFO_PROCESS_VMOS;
THREAD_STATS = sys::ZX_INFO_THREAD_STATS;
CPU_STATS = sys::ZX_INFO_CPU_STATS;
KMEM_STATS = sys::ZX_INFO_KMEM_STATS;
KMEM_STATS_EXTENDED = sys::ZX_INFO_KMEM_STATS_EXTENDED;
KMEM_STATS_COMPRESSION = sys::ZX_INFO_KMEM_STATS_COMPRESSION;
RESOURCE = sys::ZX_INFO_RESOURCE;
HANDLE_COUNT = sys::ZX_INFO_HANDLE_COUNT;
BTI = sys::ZX_INFO_BTI;
PROCESS_HANDLE_STATS = sys::ZX_INFO_PROCESS_HANDLE_STATS;
SOCKET = sys::ZX_INFO_SOCKET;
TIMER = sys::ZX_INFO_TIMER;
VMO = sys::ZX_INFO_VMO;
JOB = sys::ZX_INFO_JOB;
IOB = sys::ZX_INFO_IOB;
IOB_REGIONS = sys::ZX_INFO_IOB_REGIONS;
MEMORY_STALL = sys::ZX_INFO_MEMORY_STALL;
CLOCK_MAPPED_SIZE = sys::ZX_INFO_CLOCK_MAPPED_SIZE;
]);
/// Query information about a zircon object. Returns a valid slice and any remaining capacity on
/// success, along with a count of how many infos the kernel had available.
pub(crate) fn object_get_info<'a, Q: ObjectQuery>(
handle: HandleRef<'_>,
out: &'a mut [MaybeUninit<Q::InfoTy>],
) -> Result<(&'a mut [Q::InfoTy], &'a mut [MaybeUninit<Q::InfoTy>], usize), Status>
where
Q::InfoTy: FromBytes + Immutable,
{
let mut actual = 0;
let mut avail = 0;
// SAFETY: The slice pointer is known valid to write to for `size_of_val` because it came from
// a mutable reference.
let status = unsafe {
sys::zx_object_get_info(
handle.raw_handle(),
*Q::TOPIC,
out.as_mut_ptr().cast::<u8>(),
std::mem::size_of_val(out),
&mut actual,
&mut avail,
)
};
ok(status)?;
let (initialized, uninit) = out.split_at_mut(actual);
// TODO(https://fxbug.dev/352398385) switch to MaybeUninit::slice_assume_init_mut
// SAFETY: these values have been initialized by the kernel and implement the right zerocopy
// traits to be instantiated from arbitrary bytes.
let initialized: &mut [Q::InfoTy] = unsafe {
std::slice::from_raw_parts_mut(
initialized.as_mut_ptr().cast::<Q::InfoTy>(),
initialized.len(),
)
};
Ok((initialized, uninit, avail))
}
/// Query information about a zircon object, expecting only a single info in the return.
pub(crate) fn object_get_info_single<Q: ObjectQuery>(
handle: HandleRef<'_>,
) -> Result<Q::InfoTy, Status>
where
Q::InfoTy: Copy + FromBytes + Immutable,
{
let mut info = MaybeUninit::<Q::InfoTy>::uninit();
let (info, _, _) = object_get_info::<Q>(handle, std::slice::from_mut(&mut info))?;
Ok(info[0])
}
/// Query multiple records of information about a zircon object.
/// Returns a vec of Q::InfoTy on success.
/// Intended for calls that return multiple small objects.
pub(crate) fn object_get_info_vec<Q: ObjectQuery>(
handle: HandleRef<'_>,
) -> Result<Vec<Q::InfoTy>, Status> {
// Start with a few slots
let mut out = Vec::<Q::InfoTy>::with_capacity(INFO_VEC_SIZE_INITIAL);
loop {
let (init, _uninit, avail) =
object_get_info::<Q>(handle.clone(), out.spare_capacity_mut())?;
let num_initialized = init.len();
if num_initialized == avail {
// SAFETY: the kernel has initialized all of these values.
unsafe { out.set_len(num_initialized) };
return Ok(out);
} else {
// The number of records may increase between retries; reserve space for that.
// TODO(https://fxbug.dev/384531846) grow more conservatively
let needed_space = avail * INFO_VEC_SIZE_PAD;
if let Some(to_grow) = needed_space.checked_sub(out.capacity()) {
out.reserve_exact(to_grow);
}
// We may ask the kernel to copy more than a page in the next iteration of this loop, so
// prefault each of the pages in the region.
//
// Currently Zircon has a very slow path when performing large usercopies into freshly
// allocated buffers. In practice our large heap allocated buffers are typically newly
// mapped and do not yet have any pages faulted in. Unlike a copy performed by
// userspace, Zircon itself has to handle any page faults and in order to avoid
// deadlocking with user pagers (among other issues), it will drop locks it holds,
// service the page fault, and then reacquire the locks (see linked bug below).
//
// In order to avoid hitting this slow path, we want to ensure that all of the pages of
// our Vec have already been faulted in before we hand off to the kernel to write data
// into them. Ideally the kernel would handle this for us but in the meantime we can
// shave a lot of time from the heaviest zx_object_get_info calls by faulting the pages
// ourselves.
//
// We only want to do this when the bindings themselves are responsible for creating the
// vector, because callers who manage their own buffer for the syscall may be doing
// their own page management for the buffer and we don't want to penalize them.
// TODO(https://fxbug.dev/383401884) remove once zircon prefaults get_info usercopies
// TODO(https://fxbug.dev/384941113) consider zx_vmar_op_range on root vmar instead
let maybe_unfaulted = out.spare_capacity_mut();
// SAFETY: zerocopy::FromBytes means it's OK to write arbitrary bytes to the slice.
// TODO(https://github.com/rust-lang/rust/issues/93092) use MaybeUninit::slice_as_bytes_mut
let maybe_unfaulted_bytes: &mut [MaybeUninit<u8>] = unsafe {
std::slice::from_raw_parts_mut(
maybe_unfaulted.as_mut_ptr().cast::<MaybeUninit<u8>>(),
std::mem::size_of_val(maybe_unfaulted),
)
};
// chunks_mut doesn't give us page alignment but that doesn't matter for pre-faulting.
for page in maybe_unfaulted_bytes.chunks_mut(crate::system_get_page_size() as usize) {
// This writes a single byte to each page to avoid unnecessary memory traffic. A
// non-zero byte is written so that these pages won't get picked up by Zircon's
// zero page scanner.
page[0] = MaybeUninit::new(1u8);
}
}
}
}