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}