blob: fa2e7c90171496e45fa7937015a4b86c7df2e77f [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 core::mem::MaybeUninit;
use counters_rs::define_kcounter;
use fbl::{Canary, HasRefCount};
use ksync::{KMutex, RawCriticalMutex, guarded};
use pin_init::{PinInit, pin_data, pin_init, pinned_drop};
use zx_status::Status;
use zx_types::{
ZX_CLOCK_MONOTONIC, ZX_OBJ_TYPE_TIMER, ZX_RIGHT_DUPLICATE, ZX_RIGHT_INSPECT, ZX_RIGHT_SIGNAL,
ZX_RIGHT_TRANSFER, ZX_RIGHT_WAIT, ZX_RIGHT_WRITE, ZX_TIMER_SIGNALED, zx_clock_t, zx_duration_t,
zx_rights_t, zx_time_t,
};
use crate::kernel::timer::{Deadline, SlackMode, Timer, TimerSlack};
use crate::platform_rs::timer::{current_boot_time, current_mono_time};
use super::timer_dispatcher_ffi::{
cpp_timer_dispatcher_create, cpp_timer_dispatcher_init_dpc, timer_irq_callback,
};
use super::{DispatcherOps, KernelHandle};
use object_constants_rs as object_constants;
pub const DEFAULT_RIGHTS: zx_rights_t = ZX_RIGHT_TRANSFER
| ZX_RIGHT_DUPLICATE
| ZX_RIGHT_WAIT
| ZX_RIGHT_INSPECT
| ZX_RIGHT_WRITE
| ZX_RIGHT_SIGNAL;
zr::static_assert_size_and_align!(
TimerDispatcherState,
object_constants::kTimerDispatcherStateSize,
object_constants::kTimerDispatcherStateAlign,
);
zr::define_opaque_storage_ffi! {
struct DpcStorage(
object_constants::kDpcStorageSize,
object_constants::kDpcStorageAlign,
8, // Matches object_constants::kDpcStorageAlign.
cpp_timer_dispatcher_init_dpc,
dispatcher: *const TimerDispatcher,
);
}
define_kcounter!(DISPATCHER_TIMER_CREATE_COUNT, "dispatcher.timer.create", Sum);
define_kcounter!(DISPATCHER_TIMER_DESTROY_COUNT, "dispatcher.timer.destroy", Sum);
#[guarded]
#[pin_data(PinnedDrop)]
#[repr(C)]
pub struct TimerDispatcherState {
canary: Canary<{ fbl::magic(b"TIMR") }>,
options: u32,
clock_id: zx_clock_t,
#[pin]
dpc: DpcStorage,
#[guarded_by(lock)]
deadline: zx_time_t,
#[guarded_by(lock)]
slack_amount: zx_duration_t,
#[guarded_by(lock)]
cancel_pending: bool,
#[pin]
#[guarded_by(lock)]
timer: Timer,
#[mutex]
lock: KMutex<RawCriticalMutex>,
}
impl TimerDispatcherState {
pub fn init(
dispatcher: *const TimerDispatcher,
options: u32,
clock_id: zx_clock_t,
) -> impl PinInit<Self, core::convert::Infallible> {
DISPATCHER_TIMER_CREATE_COUNT.add(1);
pin_init!(Self {
canary: Canary::new(),
options,
clock_id,
dpc <- unsafe { DpcStorage::init(dispatcher) },
deadline: 0.into(),
slack_amount: 0.into(),
cancel_pending: false.into(),
timer <- ksync::kcell_init(Timer::init(*clock_id)),
lock <- KMutex::init(),
})
}
}
#[pinned_drop]
impl PinnedDrop for TimerDispatcherState {
fn drop(self: core::pin::Pin<&mut Self>) {
let this = self.project();
debug_assert_eq!(*this.deadline.get_inner_mut(), 0);
debug_assert_eq!(*this.slack_amount.get_inner_mut(), 0);
DISPATCHER_TIMER_DESTROY_COUNT.add(1);
}
}
crate::object::dispatcher::impl_dispatcher_facade_with_state!(
pub struct TimerDispatcher,
TimerDispatcherState,
ZX_OBJ_TYPE_TIMER,
object_constants::kTimerDispatcherStateOffset
);
#[must_use = "The return value indicates whether the reference should be released or retained"]
#[derive(Debug, PartialEq, Eq)]
pub enum OnTimerFiredAction {
/// Release the reference count (the timer callback is done).
ReleaseRef,
/// Retain the reference count (the timer was restarted and reused the reference).
RetainRef,
}
impl TimerDispatcher {
pub fn default_rights() -> zx_rights_t {
DEFAULT_RIGHTS
}
/// Creates a new TimerDispatcher via C++ and returns its kernel handle and rights.
pub fn create(
options: u32,
clock_id: zx_clock_t,
) -> Result<(KernelHandle<Self>, zx_rights_t), Status> {
if options > zx_types::ZX_TIMER_SLACK_LATE {
return Err(Status::INVALID_ARGS);
}
match options {
zx_types::ZX_TIMER_SLACK_CENTER
| zx_types::ZX_TIMER_SLACK_EARLY
| zx_types::ZX_TIMER_SLACK_LATE => {}
_ => return Err(Status::INVALID_ARGS),
}
let mut handle_out = MaybeUninit::<KernelHandle<Self>>::uninit();
// SAFETY: handle_out points to valid uninitialized memory for KernelHandle<Self>.
let status = unsafe { cpp_timer_dispatcher_create(options, clock_id, &raw mut handle_out) };
Status::ok(status)?;
// SAFETY: cpp_timer_dispatcher_create initialized the handle.
unsafe { Ok((handle_out.assume_init(), DEFAULT_RIGHTS)) }
}
pub fn on_zero_handles(&self) {
// The timers can be kept alive indefinitely by the callbacks, so
// we need to cancel when there are no more user-mode clients.
ksync::lock!(let mut guard = self.state().lock_lock());
if !self.cancel_timer_locked(guard.as_mut()) {
let fields = guard.as_mut().fields_mut();
fields.timer.cancel();
}
}
pub fn set(&self, deadline: zx_time_t, slack_amount: zx_duration_t) -> Result<(), Status> {
self.state().canary.assert();
ksync::lock!(let mut guard = self.state().lock_lock());
let did_cancel = self.cancel_timer_locked(guard.as_mut());
// If the timer is already due, then we can set the signal immediately without
// starting the timer.
if deadline == 0 || deadline <= self.current_time() {
self.update_state_locked(guard.token(), 0, ZX_TIMER_SIGNALED);
return Ok(());
}
let fields = guard.as_mut().fields_mut();
*fields.deadline = deadline;
*fields.slack_amount = slack_amount;
// If we're imminently awaiting a timer callback due to a prior cancellation request,
// let the callback take care of restarting the timer too so everything happens in the
// right sequence.
if *fields.cancel_pending {
return Ok(());
}
// We need to ref-up because the timer and the dpc don't understand
// refcounted objects. The reference is released either when the RefPtr
// in rust_timer_dispatcher_on_timer_fired drops or in the complicated
// cancellation path above.
fbl::RefPtr::add_ref(self);
self.set_timer_locked(guard.as_mut(), !did_cancel);
Ok(())
}
pub fn cancel(&self) -> Result<(), Status> {
self.state().canary.assert();
ksync::lock!(let mut guard = self.state().lock_lock());
self.cancel_timer_locked(guard.as_mut());
Ok(())
}
pub fn on_timer_fired(&self) -> OnTimerFiredAction {
self.state().canary.assert();
ksync::lock!(let mut guard = self.state().lock_lock());
let cancel_pending = *guard.as_mut().fields_mut().cancel_pending;
if cancel_pending {
// We previously attempted to cancel the timer but the dpc had already
// been queued. Suppress handling of this callback but take care to
// restart the timer if its deadline was set in the meantime.
let fields = guard.as_mut().fields_mut();
*fields.cancel_pending = false;
if *fields.deadline != 0 {
self.set_timer_locked(guard.as_mut(), true);
OnTimerFiredAction::RetainRef
} else {
OnTimerFiredAction::ReleaseRef
}
} else {
// The timer is firing.
self.update_state_locked(guard.token(), 0, ZX_TIMER_SIGNALED);
let fields = guard.as_mut().fields_mut();
*fields.deadline = 0;
*fields.slack_amount = 0;
OnTimerFiredAction::ReleaseRef
}
}
pub fn get_info(&self) -> zx_types::zx_info_timer_t {
self.state().canary.assert();
ksync::lock!(let guard = self.state().lock_lock());
let fields = guard.fields();
zx_types::zx_info_timer_t {
options: self.state().options,
clock_id: self.state().clock_id,
deadline: *fields.deadline,
slack: *fields.slack_amount,
}
}
fn current_time(&self) -> zx_time_t {
if self.state().clock_id == ZX_CLOCK_MONOTONIC {
current_mono_time().0
} else {
current_boot_time().0
}
}
fn set_timer_locked(
&self,
mut guard: core::pin::Pin<&mut TimerDispatcherStateLockGuard<'_>>,
cancel_first: bool,
) {
let mut fields = guard.as_mut().fields_mut();
if cancel_first {
fields.timer.as_mut().cancel();
}
let slack_mode = match self.state().options {
zx_types::ZX_TIMER_SLACK_CENTER => SlackMode::Center,
zx_types::ZX_TIMER_SLACK_EARLY => SlackMode::Early,
zx_types::ZX_TIMER_SLACK_LATE => SlackMode::Late,
other => panic!("Unknown options: {other:#x}"),
};
let slack = TimerSlack { amount: *fields.slack_amount, mode: slack_mode };
let slack_deadline = Deadline { when: *fields.deadline, slack };
let dpc_ptr = self.state().dpc.as_void_ptr();
// SAFETY: `timer_irq_callback` is a valid callback that queues the DPC using `dpc_ptr`.
// The `TimerDispatcher` (`self`) is guaranteed to remain alive for the duration of the
// timer callback and DPC execution because we manually incremented its reference count in
// `set()` using `fbl::RefPtr::add_ref(self)`. That reference is released either when the
// callback completes or in `cancel_timer_locked()`.
unsafe {
fields.timer.set_deadline(&slack_deadline, timer_irq_callback, dpc_ptr);
}
}
fn cancel_timer_locked(
&self,
mut guard: core::pin::Pin<&mut TimerDispatcherStateLockGuard<'_>>,
) -> bool {
// Always clear the signal bit.
self.update_state_locked(guard.token(), ZX_TIMER_SIGNALED, 0);
let fields = guard.as_mut().fields_mut();
// If the timer isn't pending then we're done.
if *fields.deadline == 0 {
return false; // didn't call timer_cancel
}
*fields.deadline = 0;
*fields.slack_amount = 0;
// If we're already waiting for the timer to be canceled, then we don't need
// to cancel it again.
if *fields.cancel_pending {
return false; // didn't call timer_cancel
}
// The timer is active and needs to be canceled.
// Refcount is at least 2 because there is a pending timer that we need to cancel.
let timer_canceled = fields.timer.cancel();
if timer_canceled {
// Managed to cancel before OnTimerFired() ran. So we need to decrement the
// ref count here. The caller must be holding a reference as well, so
// the refcount must be at least 2 before dropping.
debug_assert!(self.ref_count().ref_count_debug() >= 2);
let _ = unsafe { fbl::RefPtr::from_raw(self as *const _) };
} else {
// The DPC thread is about to run the callback! Yet we are holding the lock.
// We'll let the timer callback take care of cleanup.
*fields.cancel_pending = true;
}
true // did call timer_cancel
}
}