blob: 07262fa53c14f6e60f97944b8bdad72c0f610893 [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::power::OnWakeOps;
use crate::signals::{SignalDetail, SignalEvent, SignalEventNotify, SignalInfo, send_signal};
use crate::task::{CurrentTask, Kernel, ThreadGroup};
use crate::time::utc::{estimate_boot_deadline_from_utc, utc_now};
use crate::time::{
GenericDuration, HrTimer, HrTimerHandle, TargetTime, Timeline, TimerId, TimerWakeup,
};
use crate::vfs::timer::TimerOps;
use assert_matches::assert_matches;
use fuchsia_runtime::UtcInstant;
use futures::channel::mpsc;
use futures::stream::AbortHandle;
use futures::{FutureExt, StreamExt, select};
use starnix_logging::{log_debug, log_error, log_trace, log_warn, track_stub};
use starnix_sync::Mutex;
use starnix_types::ownership::TempRef;
use starnix_types::time::{duration_from_timespec, timespec_from_duration};
use starnix_uapi::errors::Errno;
use starnix_uapi::{SI_TIMER, itimerspec};
use std::fmt::Debug;
use std::ops::DerefMut;
use std::pin::pin;
use std::sync::{Arc, Weak};
#[derive(Default)]
pub struct TimerRemaining {
/// Remaining time until the next expiration.
pub remainder: zx::SyntheticDuration,
/// Interval for periodic timer.
pub interval: zx::SyntheticDuration,
}
impl From<TimerRemaining> for itimerspec {
fn from(value: TimerRemaining) -> Self {
Self {
it_interval: timespec_from_duration(value.interval),
it_value: timespec_from_duration(value.remainder),
}
}
}
#[derive(Debug)]
pub struct IntervalTimer {
pub timer_id: TimerId,
/// HrTimer to trigger wakeup
hr_timer: Option<HrTimerHandle>,
timeline: Timeline,
pub signal_event: SignalEvent,
state: Mutex<IntervalTimerMutableState>,
}
pub type IntervalTimerHandle = Arc<IntervalTimer>;
/// Emulates waiting on the UTC timeline for the interval timer.
///
/// Combines two functionalities offered by the Fuchsia runtime's timer
/// and the wake alarm:
/// * Fuchsia's runtime timer can wake process after a wait,
/// * Wake alarm can wake the system after a period expires.
///
/// The UtcWaiter combines the two to ensure that once [UtcWaiter.wait()]
/// returns, the correct amount of UTC (wall clock) time has expired.
#[derive(Debug)]
struct UtcWaiter {
// Used in `on_wake` below.
send: mpsc::UnboundedSender<()>,
// Call to obtain the current UtcInstant. Injected in tests.
utc_now_fn: fn() -> UtcInstant,
}
impl OnWakeOps for UtcWaiter {
// This fn is called when a wake alarm expires. [UtcWaiter] must be
// submitted to HrTimerManager for that to happen.
fn on_wake(&self, _: &CurrentTask, _: &zx::NullableHandle) {
self.on_wake_internal()
}
}
impl UtcWaiter {
/// Creates a new UtcWaiter.
///
/// Await on `UtcWaiter::wait` to pass the time.
///
/// # Returns
/// A pair of:
/// * `Self`: the waiter itself. Call `UtcWaiter::wait` on it.
/// * `UnboundedSender<_>`: a channel used for async notification from the
/// wake alarm subsystem. Feed it into `UtcWaiter::wait`.
pub fn new() -> (Self, mpsc::UnboundedReceiver<()>) {
Self::new_internal(utc_now)
}
fn on_wake_internal(&self) {
self.send
.unbounded_send(())
// This is a common occurrence if the timer has been destroyed
// before we get to `unbounded_send` for it.
.inspect_err(|err| log_warn!("UtcWaiter::on_wake: {err:?}"))
.unwrap_or(());
}
fn new_internal(clock_fn: fn() -> UtcInstant) -> (Self, mpsc::UnboundedReceiver<()>) {
let (send, recv) = mpsc::unbounded();
// Returning recv instead of adding to Self avoids the need to take a Mutex.
(Self { send, utc_now_fn: clock_fn }, recv)
}
/// Awaits until `deadline` expires. Get `utc_signal` from a call to `UtcWaiter::new()`.
pub async fn wait(&self, deadline: UtcInstant, mut utc_signal: mpsc::UnboundedReceiver<()>) {
loop {
let mut utc_wait_fut = utc_signal.next().fuse();
let (deadline_boot, _) = estimate_boot_deadline_from_utc(deadline);
let mut boot_wait_fut = pin!(fuchsia_async::Timer::new(deadline_boot));
log_debug!(
"UtcWaiter::wait: waiting for: deadline_utc={:?}, deadline_boot={:?}",
deadline,
deadline_boot
);
// The UTC deadline can move. Initially, the UTC and the boot deadline
// are in sync. But if after starting the wait the UTC timeline changes,
// the actual UTC deadline may move to be sooner or later than the boot
// deadline, meaning that we can wake either earlier or later than
// the user requested.
//
// If we woke up correctly, we return. If we woke up too early, we
// recompute and continue waiting.
select! {
// While nominally the waits on the boot and UTC timelines will trigger at about
// the same wall clock instant absent timeline changes and wakes, the boot timer
// has a much finer resolution than the UTC timer. For example, some devices
// support at most 1 second resolution for UTC wakes, vs effectively millisecond
// resolution on boot timers or even finer.
//
// So if we have an opportunity to be more accurate by waking on boot timer, we
// should probably take it.
_ = boot_wait_fut => {
log_debug!("UtcWaiter::wait: woken by boot deadline.");
},
// The UTC timer has an additional property that it is able to wake the device
// from sleep. As a tradeoff, it usually has orders of magnitude coarser resolution
// than the boot timer. So, we also wait on UTC to get the wake functionality.
_ = utc_wait_fut => {
log_debug!("UtcWaiter::wait: woken by UTC deadline.");
},
}
let utc_now = (self.utc_now_fn)();
if deadline <= utc_now {
log_debug!(
"UtcWaiter::wait: UTC deadline reached: now={:?}, deadline={:?}",
utc_now,
deadline
);
break;
} else {
log_debug!(
"UtcWaiter::wait: UTC deadline NOT reached: now={:?}, deadline={:?}",
utc_now,
deadline
);
}
}
}
}
#[derive(Debug)]
struct IntervalTimerMutableState {
/// Handle to abort the running timer task.
abort_handle: Option<AbortHandle>,
/// If the timer is armed (started).
armed: bool,
/// Time of the next expiration on the requested timeline.
target_time: TargetTime,
/// Interval for periodic timer.
interval: zx::SyntheticDuration,
/// Number of timer expirations that have occurred since the last time a signal was sent.
///
/// Timer expiration is not counted as overrun under `SignalEventNotify::None`.
overrun_cur: i32,
/// Number of timer expirations that was on last delivered signal.
overrun_last: i32,
}
impl IntervalTimerMutableState {
fn disarm(&mut self) {
self.armed = false;
if let Some(abort_handle) = &self.abort_handle {
abort_handle.abort();
}
self.abort_handle = None;
}
fn on_setting_changed(&mut self) {
self.overrun_cur = 0;
self.overrun_last = 0;
}
}
impl IntervalTimer {
pub fn new(
timer_id: TimerId,
timeline: Timeline,
wakeup_type: TimerWakeup,
signal_event: SignalEvent,
) -> Result<IntervalTimerHandle, Errno> {
// TODO(b/470129973): We may also need to add hr_timer for regular wakeups on the real time
// timeline, to track UTC timeline changes.
let hr_timer = match wakeup_type {
TimerWakeup::Regular => None,
TimerWakeup::Alarm => Some(HrTimer::new()),
};
Ok(Arc::new(Self {
timer_id,
hr_timer,
timeline,
signal_event,
state: Mutex::new(IntervalTimerMutableState {
target_time: timeline.zero_time(),
abort_handle: Default::default(),
armed: Default::default(),
interval: Default::default(),
overrun_cur: Default::default(),
overrun_last: Default::default(),
}),
}))
}
fn signal_info(self: &IntervalTimerHandle) -> Option<SignalInfo> {
let signal_detail = SignalDetail::Timer { timer: self.clone() };
Some(SignalInfo::new(self.signal_event.signo?, SI_TIMER, signal_detail))
}
async fn start_timer_loop(
self: &IntervalTimerHandle,
kernel: &Kernel,
timer_thread_group: Weak<ThreadGroup>,
) {
loop {
let overtime = loop {
// We may have to issue multiple sleeps if the target time in the timer is
// updated while we are sleeping or if our estimation of the target time
// relative to the monotonic clock is off. Drop the guard before blocking so
// that the target time can be updated.
let target_time = { self.state.lock().target_time };
let now = self.timeline.now();
if now >= target_time {
break now
.delta(&target_time)
.expect("timer timeline and target time are comparable");
}
let (utc_waiter, utc_signal) = UtcWaiter::new();
let utc_waiter = Arc::new(utc_waiter);
if let Some(hr_timer) = &self.hr_timer {
assert_matches!(
target_time,
TargetTime::BootInstant(_) | TargetTime::RealTime(_),
"monotonic times can't be alarm deadlines",
);
let weak_utc_waiter = Arc::downgrade(&utc_waiter);
if let Err(e) = hr_timer.start(
kernel.kthreads.system_task(),
Some(weak_utc_waiter),
target_time,
) {
log_error!("Failed to start the HrTimer to trigger wakeup: {e}");
}
}
match target_time {
TargetTime::Monotonic(t) => fuchsia_async::Timer::new(t).await,
TargetTime::BootInstant(t) => fuchsia_async::Timer::new(t).await,
TargetTime::RealTime(t) => utc_waiter.wait(t, utc_signal).await,
}
};
if !self.state.lock().armed {
return;
}
// Timer expirations are counted as overruns except SIGEV_NONE.
if self.signal_event.notify != SignalEventNotify::None {
let mut guard = self.state.lock();
// If the `interval` is zero, the timer expires just once, at the time
// specified by `target_time`.
if guard.interval == zx::SyntheticDuration::ZERO {
guard.overrun_cur = 1;
} else {
let exp =
i32::try_from(overtime.into_nanos() / guard.interval.into_nanos() + 1)
.unwrap_or(i32::MAX);
guard.overrun_cur = guard.overrun_cur.saturating_add(exp);
};
}
// Check on notify enum to determine the signal target.
if let Some(timer_thread_group) = timer_thread_group.upgrade() {
match self.signal_event.notify {
SignalEventNotify::Signal => {
if let Some(signal_info) = self.signal_info() {
log_trace!(
signal = signal_info.signal.number(),
pid = timer_thread_group.leader;
"sending signal for timer"
);
timer_thread_group.write().send_signal(signal_info);
}
}
SignalEventNotify::None => {}
SignalEventNotify::Thread { .. } => {
track_stub!(TODO("https://fxbug.dev/322875029"), "SIGEV_THREAD timer");
}
SignalEventNotify::ThreadId(tid) => {
// Check if the target thread exists in the thread group.
timer_thread_group.read().get_task(tid).map(TempRef::into_static).map(
|target| {
if let Some(signal_info) = self.signal_info() {
log_trace!(
signal = signal_info.signal.number(),
tid;
"sending signal for timer"
);
send_signal(
kernel.kthreads.unlocked_for_async().deref_mut(),
&target,
signal_info,
)
.unwrap_or_else(|e| {
log_warn!("Failed to queue timer signal: {}", e)
});
}
},
);
}
}
}
// If the `interval` is zero, the timer expires just once, at the time
// specified by `target_time`.
let mut guard = self.state.lock();
if guard.interval != zx::SyntheticDuration::default() {
guard.target_time = self.timeline.now() + GenericDuration::from(guard.interval);
} else {
guard.disarm();
return;
}
}
}
pub fn on_signal_delivered(self: &IntervalTimerHandle) {
let mut guard = self.state.lock();
guard.overrun_last = guard.overrun_cur;
guard.overrun_cur = 0;
}
pub fn arm(
self: &IntervalTimerHandle,
current_task: &CurrentTask,
new_value: itimerspec,
is_absolute: bool,
) -> Result<(), Errno> {
let mut guard = self.state.lock();
let target_time = if is_absolute {
self.timeline.target_from_timespec(new_value.it_value)?
} else {
self.timeline.now()
+ GenericDuration::from(duration_from_timespec::<zx::SyntheticTimeline>(
new_value.it_value,
)?)
};
// Stop the current running task.
guard.disarm();
let interval = duration_from_timespec(new_value.it_interval)?;
guard.interval = interval;
if let Some(hr_timer) = &self.hr_timer {
// It is important for power management that the hrtimer is marked as interval, as
// interval timers may prohibit container suspension. Note that marking `is_interval`
// changes the hrtimer ID, which is only allowed if the hrtimer is not running.
*hr_timer.is_interval.lock() = guard.interval != zx::SyntheticDuration::default();
}
if target_time.is_zero() {
return Ok(());
}
guard.armed = true;
guard.target_time = target_time;
guard.on_setting_changed();
let kernel_ref = current_task.kernel().clone();
let self_ref = self.clone();
let thread_group = current_task.thread_group().weak_self.clone();
current_task.kernel().kthreads.spawn_future(async move || {
let _ = {
// 1. Lock the state to update `abort_handle` when the timer is still armed.
// 2. MutexGuard needs to be dropped before calling await on the future task.
// Unfortuately, std::mem::drop is not working correctly on this:
// (https://github.com/rust-lang/rust/issues/57478).
let mut guard = self_ref.state.lock();
if !guard.armed {
return;
}
let (abortable_future, abort_handle) = futures::future::abortable(
self_ref.start_timer_loop(&kernel_ref, thread_group),
);
guard.abort_handle = Some(abort_handle);
abortable_future
}
.await;
});
Ok(())
}
pub fn disarm(&self, current_task: &CurrentTask) -> Result<(), Errno> {
let mut guard = self.state.lock();
guard.disarm();
guard.on_setting_changed();
if let Some(hr_timer) = &self.hr_timer {
hr_timer.stop(current_task.kernel())?;
}
Ok(())
}
pub fn time_remaining(&self) -> TimerRemaining {
let guard = self.state.lock();
if !guard.armed {
return TimerRemaining::default();
}
TimerRemaining {
remainder: std::cmp::max(
zx::SyntheticDuration::ZERO,
*guard.target_time.delta(&self.timeline.now()).expect("timelines must match"),
),
interval: guard.interval,
}
}
pub fn overrun_cur(&self) -> i32 {
self.state.lock().overrun_cur
}
pub fn overrun_last(&self) -> i32 {
self.state.lock().overrun_last
}
}
impl PartialEq for IntervalTimer {
fn eq(&self, other: &Self) -> bool {
std::ptr::addr_of!(self) == std::ptr::addr_of!(other)
}
}
impl Eq for IntervalTimer {}
#[cfg(test)]
mod tests {
use super::*;
use crate::time::utc::UtcClockOverrideGuard;
use assert_matches::assert_matches;
use std::task::Poll;
use zx::HandleBased;
use {fuchsia_async as fasync, fuchsia_runtime as fxr};
struct TestContext {
_initial_time_mono: zx::MonotonicInstant,
initial_time_utc: UtcInstant,
_utc_clock: fxr::UtcClock,
_guard: UtcClockOverrideGuard,
}
impl TestContext {
async fn new() -> Self {
// Make them the same initially.
let _initial_time_mono = zx::MonotonicInstant::from_nanos(1000);
let initial_time_utc = UtcInstant::from_nanos(_initial_time_mono.into_nanos());
fasync::TestExecutor::advance_to(_initial_time_mono.into()).await;
// Create and start the UTC clock.
let utc_clock =
fxr::UtcClock::create(zx::ClockOpts::empty(), Some(initial_time_utc)).unwrap();
let utc_clock_clone = utc_clock.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
let initial_time_boot = zx::BootInstant::from_nanos(_initial_time_mono.into_nanos());
utc_clock
.update(
fxr::UtcClockUpdate::builder()
.absolute_value(initial_time_boot, initial_time_utc)
.build(),
)
.unwrap();
// Inject the clock into Starnix infra.
let _guard = UtcClockOverrideGuard::new(utc_clock_clone);
Self { _initial_time_mono, initial_time_utc, _utc_clock: utc_clock, _guard }
}
}
// If the UTC signal is received and the wait expired, we are done.
#[fuchsia::test(allow_stalls = false)]
async fn test_utc_waiter_on_utc_expired() {
let _context = TestContext::new().await;
let (waiter, utc_signal) = UtcWaiter::new();
// Expired deadline, and notification.
waiter.on_wake_internal();
let deadline_utc = _context.initial_time_utc - fxr::UtcDuration::from_nanos(10);
let wait_fut = pin!(waiter.wait(deadline_utc, utc_signal));
assert_matches!(
fasync::TestExecutor::poll_until_stalled(wait_fut).await,
Poll::Ready(_),
"UTC deadline should have expired"
);
}
// If the UTC signal is received, but the deadline is not reached, we
// must still pend.
#[fuchsia::test(allow_stalls = false)]
async fn test_utc_waiter_on_utc_still_pending() {
let context = TestContext::new().await;
let (waiter, utc_signal) =
UtcWaiter::new_internal(|| -> fxr::UtcInstant { fxr::UtcInstant::from_nanos(2000) });
// Notified, but not expired yet.
waiter.on_wake_internal();
let deadline_utc = context.initial_time_utc + fxr::UtcDuration::INFINITE;
let wait_fut = pin!(waiter.wait(deadline_utc, utc_signal));
assert_matches!(
fasync::TestExecutor::poll_until_stalled(wait_fut).await,
Poll::Pending,
"UTC deadline should not have expired"
);
}
// If we are woken by the timer, and UTC deadline has passed, we are done.
#[fuchsia::test(allow_stalls = false)]
async fn test_utc_waiter_on_boot_expires() {
let context = TestContext::new().await;
let (waiter, utc_signal) =
UtcWaiter::new_internal(|| -> fxr::UtcInstant { fxr::UtcInstant::from_nanos(5000) });
let deadline_utc = context.initial_time_utc + fxr::UtcDuration::from_nanos(4000);
let wait_fut = pin!(waiter.wait(deadline_utc, utc_signal));
fasync::TestExecutor::advance_to(zx::MonotonicInstant::from_nanos(10000).into()).await;
assert_matches!(
fasync::TestExecutor::poll_until_stalled(wait_fut).await,
Poll::Ready(_),
"UTC deadline should have expired, and we got notified via the timer wait"
);
}
}