Skip to main content

ove/
time.rs

1// Copyright (C) 2026 Kamil Lulko <kamil.lulko@gmail.com>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4//
5// This file is part of oveRTOS.
6
7//! Time and delay utilities for oveRTOS.
8//!
9//! Provides a monotonic microsecond timestamp ([`get_us`]) and busy-wait or
10//! scheduler-yielding delay functions ([`delay_ms`], [`delay_us`]).
11
12use crate::bindings;
13use crate::error::{Error, Result};
14use core::time::Duration;
15
16// SAFETY (module-wide contract for the `unsafe { bindings::ove_*(...) }` FFI
17// calls below): any handle passed to the C API is non-null and refers to a
18// live RTOS object — wrapper constructors establish validity via
19// `Error::from_code`, and `Drop` (or an explicit `deinit`) is the only place
20// a handle is released. Pointer and slice arguments reference caller-owned
21// memory valid for the duration of the call; the C side copies whatever it
22// retains and does not alias them past return (verified against the
23// signatures in `include/ove/*.h`). Blocks that deviate — `transmute`, raw
24// pointer casts from user data, slice reconstruction via `from_raw_parts`,
25// or storing a callback across the FFI boundary — carry their own
26// `// SAFETY:` comment.
27
28/// Convert a [`Duration`] to `u64` nanoseconds for the C ABI.
29///
30/// Saturates to `u64::MAX` if the duration overflows `u64` ns
31/// (~584 years). Used internally by every binding wrapper that
32/// passes a timeout to a substrate function taking `uint64_t timeout_ns`.
33#[inline]
34pub(crate) fn dur_to_ns(d: Duration) -> u64 {
35    let n = d.as_nanos();
36    if n > u64::MAX as u128 {
37        u64::MAX
38    } else {
39        n as u64
40    }
41}
42
43/// Get the current monotonic time in microseconds since an arbitrary epoch.
44///
45/// # Errors
46/// Returns an error if the underlying hardware timer is unavailable.
47#[inline]
48pub fn get_us() -> Result<u64> {
49    let mut us: u64 = 0;
50    let rc = unsafe { bindings::ove_time_get_us(&mut us) };
51    Error::from_code(rc)?;
52    Ok(us)
53}
54
55/// Like [`get_us`] but skips the error-mapping branch — the underlying
56/// `ove_time_get_us` is infallible on every supported backend's
57/// hardware timer (the `Result` exists only to keep the API uniform
58/// with other ove fallible operations).
59///
60/// Use this when calling `get_us` in tight hot loops where the
61/// `Result<u64>` plumbing dominates the actual measurement (the bench
62/// suite's `time_get_us_overhead` case is the canonical example).
63#[inline]
64pub fn get_us_unchecked() -> u64 {
65    let mut us: u64 = 0;
66    unsafe { bindings::ove_time_get_us(&mut us) };
67    us
68}
69
70/// Block the current thread for at least `ms` milliseconds.
71///
72/// Yields the CPU to other threads for the duration. Prefer [`crate::Thread::sleep_ms`]
73/// for thread-level sleeping.
74#[inline]
75pub fn delay_ms(ms: u32) {
76    unsafe { bindings::ove_time_delay_ms(ms) }
77}
78
79/// Block the current thread for at least `us` microseconds.
80///
81/// On most platforms this is a busy-wait for short durations; use sparingly.
82#[inline]
83pub fn delay_us(us: u32) {
84    unsafe { bindings::ove_time_delay_us(us) }
85}
86
87/// Zero-sized handle used as the trait target for
88/// `embedded_hal::delay::DelayNs` (behind the `embedded-hal` feature).
89///
90/// Routes `delay_*` calls into the substrate's [`delay_us`] / [`delay_ms`].
91/// Sub-microsecond delays round up to one microsecond — the substrate
92/// lacks nanosecond resolution, and most embedded-hal driver use cases
93/// (protocol setup/hold times) tolerate the round-up.
94#[derive(Debug, Clone, Copy, Default)]
95pub struct Delay;
96
97/// Get the current monotonic time in nanoseconds since an arbitrary epoch.
98///
99/// Prefer [`Instant::now`] for new code — it's the typed counterpart and
100/// the only constructor for [`Instant`] outside of `FOREVER` / `Add<Duration>`
101/// composition.  This bare-`u64` helper is kept as an escape hatch.
102#[inline]
103pub fn now_steady_ns() -> u64 {
104    let mut ns: u64 = 0;
105    unsafe { bindings::ove_time_get_ns(&mut ns) };
106    ns
107}
108
109/// Typed monotonic timestamp for `try_*_until` deadlines.
110///
111/// Wraps a nanosecond count from the substrate's steady clock.  Construct
112/// only via [`Instant::now`] or by adding a [`Duration`] to an existing
113/// `Instant`.  The internal representation is opaque on purpose: this is
114/// what makes a raw `u64` of microseconds or relative duration nanoseconds
115/// fail to compile when fed to a `try_*_until` call.
116///
117/// Matches `std::time::Instant`'s opaque-newtype shape (we can't use the
118/// std type directly because it lives in `std`, not `core` — see also
119/// `parking_lot::Mutex::try_lock_until` which takes std `Instant` on
120/// host but the equivalent type on `no_std` targets).
121///
122/// # Examples
123///
124/// ```ignore
125/// use core::time::Duration;
126/// use ove::time::Instant;
127///
128/// let deadline = Instant::now() + Duration::from_millis(100);
129/// mtx.try_lock_until(deadline)?;
130///
131/// // Or wait indefinitely:
132/// mtx.try_lock_until(Instant::FOREVER)?;
133/// ```
134#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
135pub struct Instant(u64);
136
137impl Instant {
138    /// Returns the current value of the substrate steady clock.
139    #[inline]
140    pub fn now() -> Self {
141        Self(now_steady_ns())
142    }
143
144    /// Sentinel "wait indefinitely" deadline.
145    ///
146    /// Mapped to the substrate's `OVE_WAIT_FOREVER` constant via the
147    /// internal raw-ns representation.  Useful when a function's
148    /// signature only exposes a deadline variant but the caller wants
149    /// to block forever.
150    pub const FOREVER: Instant = Instant(u64::MAX);
151}
152
153impl core::ops::Add<Duration> for Instant {
154    type Output = Instant;
155    #[inline]
156    fn add(self, rhs: Duration) -> Instant {
157        let bumped = self.0.saturating_add(dur_to_ns(rhs));
158        Instant(bumped)
159    }
160}
161
162impl core::ops::AddAssign<Duration> for Instant {
163    #[inline]
164    fn add_assign(&mut self, rhs: Duration) {
165        *self = *self + rhs;
166    }
167}
168
169impl core::ops::Sub<Instant> for Instant {
170    type Output = Duration;
171    #[inline]
172    fn sub(self, rhs: Instant) -> Duration {
173        Duration::from_nanos(self.0.saturating_sub(rhs.0))
174    }
175}
176
177/// Convert an [`Instant`] deadline to the timeout-ns value the substrate
178/// expects (`OVE_WAIT_FOREVER` is preserved; otherwise `deadline - now`,
179/// saturating to 0 if the deadline is in the past).
180#[inline]
181pub(crate) fn deadline_to_timeout_ns(deadline: Instant) -> u64 {
182    if deadline.0 == u64::MAX {
183        return u64::MAX;
184    }
185    let now = now_steady_ns();
186    deadline.0.saturating_sub(now)
187}
188
189// ── fugit interop (G6) ───────────────────────────────────────────────
190//
191// `core::time::Duration` is unit-blind: `from_secs(10)` and
192// `from_millis(10)` produce different durations with the same shape,
193// so the compiler can't catch swaps. `fugit::Duration<u64, NUM, DENOM>`
194// encodes the tick rate as a const-generic ratio, turning the same
195// mix-up into a `type mismatch` at the call site.
196//
197// We don't switch the public API — that would churn every existing
198// app — but offer conversions so users who opt into the `fugit`
199// feature can keep their own time in `fugit` types and only convert
200// at the ove API boundary.
201
202#[cfg(feature = "fugit")]
203pub use fugit_impl::*;
204
205#[cfg(feature = "fugit")]
206mod fugit_impl {
207    use super::Duration;
208
209    /// Microsecond-resolution duration. Same precision as the
210    /// substrate's native `uint64_t timeout_ns / 1000` representation.
211    pub type DurationUs = ::fugit::Duration<u64, 1, 1_000_000>;
212    /// Nanosecond-resolution duration. Use when sub-µs precision
213    /// matters (matches `ove_time_get_ns`).
214    pub type DurationNs = ::fugit::Duration<u64, 1, 1_000_000_000>;
215    /// Millisecond-resolution duration — convenient for the common
216    /// `200ms`-style timeouts.
217    pub type DurationMs = ::fugit::Duration<u64, 1, 1_000>;
218    /// Microsecond-resolution instant; pairs with [`DurationUs`].
219    pub type InstantUs = ::fugit::Instant<u64, 1, 1_000_000>;
220
221    /// Convert a fugit [`DurationUs`] to the std [`Duration`] used by
222    /// the rest of the binding.
223    #[inline]
224    pub fn dur_us_to_std(d: DurationUs) -> Duration {
225        Duration::from_micros(d.ticks())
226    }
227
228    /// Convert a fugit [`DurationNs`] to the std [`Duration`] used by
229    /// the rest of the binding.
230    #[inline]
231    pub fn dur_ns_to_std(d: DurationNs) -> Duration {
232        Duration::from_nanos(d.ticks())
233    }
234
235    /// Convert a fugit [`DurationMs`] to the std [`Duration`] used by
236    /// the rest of the binding.
237    #[inline]
238    pub fn dur_ms_to_std(d: DurationMs) -> Duration {
239        Duration::from_millis(d.ticks())
240    }
241
242    /// Convert a [`Duration`] back to a fugit microsecond-resolution
243    /// duration. Saturates at `u64::MAX` µs (~584 000 years).
244    #[inline]
245    pub fn dur_us_from_std(d: Duration) -> DurationUs {
246        let us = d.as_micros();
247        let ticks = if us > u64::MAX as u128 {
248            u64::MAX
249        } else {
250            us as u64
251        };
252        DurationUs::from_ticks(ticks)
253    }
254}