blob: 91345d17dd361c2b543c0368962aa6fe2d07a583 [file] [log] [blame]
// Copyright 2025 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.
//! Safe bindings for driver runtime environment.
#![deny(missing_docs)]
use fdf_sys::*;
use core::ffi;
use core::marker::PhantomData;
use core::ptr::{null_mut, NonNull};
use zx::Status;
use fdf::{Dispatcher, DispatcherBuilder, DispatcherRef, ShutdownObserver};
pub mod test;
/// Create the dispatcher as configured by this object. This must be called from a
/// thread managed by the driver runtime. The dispatcher returned is owned by the caller,
/// and will initiate asynchronous shutdown when the object is dropped unless
/// [`Dispatcher::release`] is called on it to convert it into an unowned [`DispatcherRef`].
///
fn create_with_driver<'a>(
dispatcher: DispatcherBuilder,
driver: DriverRefTypeErased<'a>,
) -> Result<Dispatcher, Status> {
let mut out_dispatcher = null_mut();
let owner = driver.0;
let options = dispatcher.options;
let name = dispatcher.name.as_ptr() as *mut ffi::c_char;
let name_len = dispatcher.name.len();
let scheduler_role = dispatcher.scheduler_role.as_ptr() as *mut ffi::c_char;
let scheduler_role_len = dispatcher.scheduler_role.len();
let observer =
dispatcher.shutdown_observer.unwrap_or_else(|| ShutdownObserver::new(|_| {})).into_ptr();
// SAFETY: all arguments point to memory that will be available for the duration
// of the call, except `observer`, which will be available until it is unallocated
// by the dispatcher exit handler.
Status::ok(unsafe {
fdf_env_dispatcher_create_with_owner(
owner,
options,
name,
name_len,
scheduler_role,
scheduler_role_len,
observer,
&mut out_dispatcher,
)
})?;
// SAFETY: `out_dispatcher` is valid by construction if `fdf_dispatcher_create` returns
// ZX_OK.
Ok(unsafe { Dispatcher::from_raw(NonNull::new_unchecked(out_dispatcher)) })
}
/// As with [`create_with_driver`], this creates a new dispatcher as configured by this object, but
/// instead of returning an owned reference it immediately releases the reference to be
/// managed by the driver runtime.
///
/// # Safety
///
/// |owner| must outlive the dispatcher. You can use the shutdown_observer to find out when it is
/// safe to drop it.
fn create_with_driver_released<'a>(
dispatcher: DispatcherBuilder,
driver: DriverRefTypeErased<'a>,
) -> Result<DispatcherRef<'static>, Status> {
create_with_driver(dispatcher, driver).map(Dispatcher::release)
}
/// A marker trait for a function that can be used as a driver shutdown observer with
/// [`Driver::shutdown`].
pub trait DriverShutdownObserverFn<T: 'static>:
FnOnce(DriverRef<'static, T>) + Send + Sync + 'static
{
}
impl<T, U: 'static> DriverShutdownObserverFn<U> for T where
T: FnOnce(DriverRef<'static, U>) + Send + Sync + 'static
{
}
/// A shutdown observer for [`fdf_dispatcher_create`] that can call any kind of callback instead of
/// just a C-compatible function when a dispatcher is shutdown.
///
/// # Safety
///
/// This object relies on a specific layout to allow it to be cast between a
/// `*mut fdf_dispatcher_shutdown_observer` and a `*mut ShutdownObserver`. To that end,
/// it is important that this struct stay both `#[repr(C)]` and that `observer` be its first member.
#[repr(C)]
struct DriverShutdownObserver<T: 'static> {
observer: fdf_env_driver_shutdown_observer,
shutdown_fn: Box<dyn DriverShutdownObserverFn<T>>,
driver: Driver<T>,
}
impl<T: 'static> DriverShutdownObserver<T> {
/// Creates a new [`ShutdownObserver`] with `f` as the callback to run when a dispatcher
/// finishes shutting down.
fn new<F: DriverShutdownObserverFn<T>>(driver: Driver<T>, f: F) -> Self {
let shutdown_fn = Box::new(f);
Self {
observer: fdf_env_driver_shutdown_observer { handler: Some(Self::handler) },
shutdown_fn,
driver,
}
}
/// Begins the driver shutdown procedure.
/// Turns this object into a stable pointer suitable for passing to
/// [`fdf_env_shutdown_dispatchers_async`] by wrapping it in a [`Box`] and leaking it
/// to be reconstituded by [`Self::handler`] when the dispatcher is shut down.
fn begin(self) -> Result<(), Status> {
let driver = self.driver.inner.as_ptr() as *const _;
// Note: this relies on the assumption that `self.observer` is at the beginning of the
// struct.
let this = Box::into_raw(Box::new(self)) as *mut _;
// SAFTEY: driver is owned by the driver framework and will be kept alive until the handler
// callback is triggered
if let Err(e) = Status::ok(unsafe { fdf_env_shutdown_dispatchers_async(driver, this) }) {
// SAFTEY: The framework didn't actually take ownership of the object if the call
// fails, so we can recover it to avoid leaking.
let _ = unsafe { Box::from_raw(this as *mut DriverShutdownObserver<T>) };
return Err(e);
}
Ok(())
}
/// The callback that is registered with the driver that will be called when the driver
/// is shut down.
///
/// # Safety
///
/// This function should only ever be called by the driver runtime at dispatcher shutdown
/// time, must only ever be called once for any given [`ShutdownObserver`] object, and
/// that [`ShutdownObserver`] object must have previously been made into a pointer by
/// [`Self::into_ptr`].
unsafe extern "C" fn handler(
driver: *const ffi::c_void,
observer: *mut fdf_env_driver_shutdown_observer_t,
) {
// SAFETY: The driver framework promises to only call this function once, so we can
// safely take ownership of the [`Box`] and deallocate it when this function ends.
let observer = unsafe { Box::from_raw(observer as *mut DriverShutdownObserver<T>) };
(observer.shutdown_fn)(DriverRef(driver as *const T, PhantomData));
}
}
/// An owned handle to a Driver instance that can be used to create initial dispatchers.
#[derive(Debug)]
pub struct Driver<T> {
pub(crate) inner: NonNull<T>,
shutdown_triggered: bool,
}
/// An unowned handle to the driver that is returned through certain environment APIs like
/// |get_driver_on_thread_koid|.
pub struct UnownedDriver {
inner: *const ffi::c_void,
}
/// SAFETY: This inner pointer is movable across threads.
unsafe impl<T: Send> Send for Driver<T> {}
impl<T: 'static> Driver<T> {
/// Returns a builder capable of creating a new dispatcher. Note that this dispatcher cannot
/// outlive the driver and is only capable of being stopped by shutting down the driver. It is
/// meant to be created to serve as the initial or default dispatcher for a driver.
pub fn new_dispatcher(
&self,
dispatcher: DispatcherBuilder,
) -> Result<DispatcherRef<'static>, Status> {
create_with_driver_released(dispatcher, self.as_ref_type_erased())
}
/// Run a closure in the context of a driver.
pub fn enter<R>(&mut self, f: impl FnOnce() -> R) -> R {
unsafe { fdf_env_register_driver_entry(self.inner.as_ptr() as *const _) };
let res = f();
unsafe { fdf_env_register_driver_exit() };
res
}
/// Adds an allowed scheduler role to the driver
pub fn add_allowed_scheduler_role(&self, scheduler_role: &str) {
let driver_ptr = self.inner.as_ptr() as *const _;
let scheduler_role_ptr = scheduler_role.as_ptr() as *mut ffi::c_char;
let scheduler_role_len = scheduler_role.len();
unsafe {
fdf_env_add_allowed_scheduler_role_for_driver(
driver_ptr,
scheduler_role_ptr,
scheduler_role_len,
)
};
}
/// Asynchronously shuts down all dispatchers owned by |driver|.
/// |f| will be called once shutdown completes. This is guaranteed to be
/// after all the dispatcher's shutdown observers have been called, and will be running
/// on the thread of the final dispatcher which has been shutdown.
pub fn shutdown<F: DriverShutdownObserverFn<T>>(mut self, f: F) {
self.shutdown_triggered = true;
// It should be impossible for this to fail as we ensure we are the only caller of this
// API, so it cannot be triggered twice nor before the driver has been registered with the
// framework.
DriverShutdownObserver::new(self, f)
.begin()
.expect("Unexpectedly failed start shutdown procedure")
}
/// Create a reference to a driver without ownership. The returned reference lacks the ability
/// to perform most actions available to the owner of the driver, therefore it doesn't need to
/// have it's lifetime tracked closely.
fn as_ref_type_erased<'a>(&'a self) -> DriverRefTypeErased<'a> {
DriverRefTypeErased(self.inner.as_ptr() as *const _, PhantomData)
}
/// Releases ownership of this driver instance, allowing it to be shut down when the runtime
/// shuts down.
pub fn release(self) -> DriverRef<'static, T> {
DriverRef(self.inner.as_ptr() as *const _, PhantomData)
}
}
impl<T> Drop for Driver<T> {
fn drop(&mut self) {
assert!(self.shutdown_triggered, "Cannot drop driver, must call shutdown method instead");
}
}
impl<T> PartialEq<UnownedDriver> for Driver<T> {
fn eq(&self, other: &UnownedDriver) -> bool {
return self.inner.as_ptr() as *const _ == other.inner;
}
}
// Note that inner type is not guaranteed to not be null.
#[derive(Clone, Copy, PartialEq)]
struct DriverRefTypeErased<'a>(*const ffi::c_void, PhantomData<&'a u32>);
impl Default for DriverRefTypeErased<'_> {
fn default() -> Self {
DriverRefTypeErased(std::ptr::null(), PhantomData)
}
}
/// A lifetime-bound reference to a driver handle.
pub struct DriverRef<'a, T>(pub *const T, PhantomData<&'a Driver<T>>);
/// The driver runtime environment
pub struct Environment;
impl Environment {
/// Whether the environment should enforce scheduler roles. Used with [`Self::start`].
pub const ENFORCE_ALLOWED_SCHEDULER_ROLES: u32 = 1;
/// Whether the environment should dynamically spawn threads on-demand for sync call dispatchers.
/// Used with [`Self::start`].
pub const DYNAMIC_THREAD_SPAWNING: u32 = 2;
/// Start the driver runtime. This sets up the initial thread that the dispatchers run on.
pub fn start(options: u32) -> Result<Environment, Status> {
// SAFETY: calling fdf_env_start, which does not have any soundness
// concerns for rust code. It may be called multiple times without any problems.
Status::ok(unsafe { fdf_env_start(options) })?;
Ok(Self)
}
/// Creates a new driver. It is expected that the driver passed in is a leaked pointer which
/// will only be recovered by triggering the shutdown method on the driver.
///
/// # Panics
///
/// This method will panic if |driver| is not null.
pub fn new_driver<T>(&self, driver: *const T) -> Driver<T> {
// We cast to *mut because there is not equivlaent version of NonNull for *const T.
Driver {
inner: NonNull::new(driver as *mut _).expect("driver must not be null"),
shutdown_triggered: false,
}
}
// TODO: Consider tracking all drivers and providing a method to shutdown all outstanding
// drivers and block until they've all finished shutting down.
/// Returns whether the current thread is managed by the driver runtime or not.
fn current_thread_managed_by_driver_runtime() -> bool {
// Safety: Calling fdf_dispatcher_get_current_dispatcher from any thread is safe. Because
// we are not actually using the dispatcher, we don't need to worry about it's lifetime.
!unsafe { fdf_dispatcher_get_current_dispatcher().is_null() }
}
/// Resets the driver runtime to zero threads. This may only be called when there are no
/// existing dispatchers.
///
/// # Panics
///
/// This method should not be called from a thread managed by the driver runtime,
/// such as from tasks or ChannelRead callbacks.
pub fn reset(&self) {
assert!(
Self::current_thread_managed_by_driver_runtime() == false,
"reset must be called from a thread not managed by the driver runtime"
);
// SAFETY: calling fdf_env_reset, which does not have any soundness
// concerns for rust code. It may be called multiple times without any problems.
unsafe { fdf_env_reset() };
}
/// Destroys all dispatchers in the process and blocks the current thread
/// until each runtime dispatcher in the process is observed to have been destroyed.
///
/// This should only be used called after all drivers have been shutdown.
///
/// # Panics
///
/// This method should not be called from a thread managed by the driver runtime,
/// such as from tasks or ChannelRead callbacks.
pub fn destroy_all_dispatchers(&self) {
assert!(Self::current_thread_managed_by_driver_runtime() == false,
"destroy_all_dispatchers must be called from a thread not managed by the driver runtime");
unsafe { fdf_env_destroy_all_dispatchers() };
}
/// Returns whether the dispatcher has any queued tasks.
pub fn dispatcher_has_queued_tasks(&self, dispatcher: DispatcherRef<'_>) -> bool {
unsafe { fdf_env_dispatcher_has_queued_tasks(dispatcher.inner().as_ptr()) }
}
/// Returns the current maximum number of threads which will be spawned for thread pool associated
/// with the given scheduler role.
///
/// |scheduler_role| is the name of the role which is passed when creating dispatchers.
pub fn get_thread_limit(&self, scheduler_role: &str) -> u32 {
let scheduler_role_ptr = scheduler_role.as_ptr() as *mut ffi::c_char;
let scheduler_role_len = scheduler_role.len();
unsafe { fdf_env_get_thread_limit(scheduler_role_ptr, scheduler_role_len) }
}
/// Sets the number of threads which will be spawned for thread pool associated with the given
/// scheduler role. It cannot shrink the limit less to a value lower than the current number of
/// threads in the thread pool.
///
/// |scheduler_role| is the name of the role which is passed when creating dispatchers.
/// |max_threads| is the number of threads to use as new limit.
pub fn set_thread_limit(&self, scheduler_role: &str, max_threads: u32) -> Result<(), Status> {
let scheduler_role_ptr = scheduler_role.as_ptr() as *mut ffi::c_char;
let scheduler_role_len = scheduler_role.len();
Status::ok(unsafe {
fdf_env_set_thread_limit(scheduler_role_ptr, scheduler_role_len, max_threads)
})
}
/// Gets the driver currently running on the thread identified by |thread_koid|, if the thread
/// is running on this driver host with a driver.
pub fn get_driver_on_thread_koid(&self, thread_koid: zx::Koid) -> Option<UnownedDriver> {
let mut driver = std::ptr::null();
unsafe {
Status::ok(fdf_env_get_driver_on_tid(thread_koid.raw_koid(), &mut driver)).ok()?;
}
if driver.is_null() {
None
} else {
Some(UnownedDriver { inner: driver })
}
}
}