ax_plat/time.rs
1//! Time-related operations.
2
3use core::sync::atomic::{AtomicI64, Ordering};
4pub use core::time::Duration;
5
6/// A measurement of the system clock.
7///
8/// Currently, it reuses the [`core::time::Duration`] type. But it does not
9/// represent a duration, but a clock time.
10pub type TimeValue = Duration;
11
12static WALL_TIME_ADJUSTMENT_NANOS: AtomicI64 = AtomicI64::new(0);
13
14/// Number of milliseconds in a second.
15pub const MILLIS_PER_SEC: u64 = 1_000;
16/// Number of microseconds in a second.
17pub const MICROS_PER_SEC: u64 = 1_000_000;
18/// Number of nanoseconds in a second.
19pub const NANOS_PER_SEC: u64 = 1_000_000_000;
20/// Number of nanoseconds in a millisecond.
21pub const NANOS_PER_MILLIS: u64 = 1_000_000;
22/// Number of nanoseconds in a microsecond.
23pub const NANOS_PER_MICROS: u64 = 1_000;
24
25/// Platform assessment of the raw counter used by the scheduler clock.
26#[derive(Clone, Copy, Debug, Eq, PartialEq)]
27pub enum SchedulerClockStability {
28 /// Every CPU observes one synchronized system counter.
29 Stable,
30 /// The raw counter is CPU-local and requires per-CPU correction.
31 Unstable,
32}
33
34/// Failure to access the platform scheduler clock lifecycle.
35#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
36pub enum SchedulerClockError {
37 /// The logical CPU index is outside the installed per-CPU layout.
38 #[error("logical CPU {cpu_id} is outside the installed per-CPU layout")]
39 InvalidCpu { cpu_id: usize },
40 /// The calling CPU has no validated CPU-local area yet.
41 #[error("the calling CPU has no validated CPU-local area")]
42 CurrentCpuUnavailable,
43 /// An owner-only lifecycle operation was invoked from another CPU.
44 #[error("scheduler clock CPU mismatch: expected {expected_cpu_id}, current {actual_cpu_id}")]
45 WrongCurrentCpu {
46 expected_cpu_id: usize,
47 actual_cpu_id: usize,
48 },
49 /// The CPU scheduler clock is already online or being initialized.
50 #[error("the scheduler clock CPU is already online")]
51 CpuAlreadyOnline,
52 /// The CPU scheduler clock is offline.
53 #[error("the scheduler clock CPU is offline")]
54 CpuOffline,
55}
56
57/// Failure to install a new wall-clock value.
58#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
59pub enum WallTimeError {
60 /// The requested wall time precedes the current monotonic time.
61 #[error("wall time cannot precede the current monotonic time")]
62 BeforeMonotonic,
63 /// The requested adjustment cannot be represented by the wall-clock state.
64 #[error("wall-time adjustment is outside the supported range")]
65 AdjustmentOutOfRange,
66}
67
68/// Time-related interfaces.
69#[def_plat_interface]
70pub trait TimeIf {
71 /// Returns the current clock time in hardware ticks.
72 fn current_ticks() -> u64;
73
74 /// Converts hardware ticks to nanoseconds.
75 fn ticks_to_nanos(ticks: u64) -> u64;
76
77 /// Samples the raw scheduler clock directly in nanoseconds.
78 ///
79 /// The platform must read and convert one counter sample within this
80 /// operation. Scheduler clock correction is applied by `ax-plat` after
81 /// this raw sample crosses the platform boundary.
82 fn scheduler_clock_raw_nanos() -> u64;
83
84 /// Converts nanoseconds to hardware ticks.
85 fn nanos_to_ticks(nanos: u64) -> u64;
86
87 /// Reports whether the current architecture counter is synchronized
88 /// across every runtime CPU.
89 fn scheduler_clock_stability() -> SchedulerClockStability;
90
91 /// Return epoch offset in nanoseconds (wall time offset to monotonic
92 /// clock start).
93 fn epochoffset_nanos() -> u64;
94
95 /// Returns the IRQ number for the timer interrupt.
96 fn irq_num() -> irq_framework::IrqId;
97
98 /// Set a one-shot timer.
99 ///
100 /// A timer interrupt will be triggered at the specified monotonic time
101 /// deadline (in nanoseconds). This capability is infallible: an already
102 /// elapsed or sub-resolution deadline must be clamped to the device's
103 /// minimum non-zero delta before the method returns. Implementations must
104 /// not silently leave the previous event armed.
105 fn set_oneshot_timer(deadline_ns: u64);
106
107 /// Returns whether a claimed timer IRQ must physically quiesce the
108 /// one-shot source before the interrupt controller completes the edge.
109 ///
110 /// Edge-triggered or rearm-cleared devices return `false`; level-triggered
111 /// devices whose expired comparator remains observable return `true`.
112 fn oneshot_timer_requires_irq_quiesce() -> bool;
113
114 /// Returns a stopped one-shot timer to its active state and programs it.
115 ///
116 /// The implementation owns the architecture-specific activation order.
117 /// Edge devices may need to unmask before programming a minimum delta;
118 /// level devices may need to replace an expired comparator before unmask
119 /// so controller EOI cannot latch the old level again.
120 fn resume_oneshot_timer(deadline_ns: u64);
121
122 /// Stops the current CPU's one-shot timer until it is programmed again.
123 ///
124 /// The interrupt source must become unobservable and its comparator must
125 /// be discarded so a later resume cannot inherit a stale event.
126 fn cancel_oneshot_timer();
127}
128
129/// Initializes the current CPU's scheduler-clock anchor before scheduler use.
130///
131/// # Errors
132///
133/// Returns an error if `cpu_id` does not identify the current installed CPU
134/// area or if that CPU clock is already online.
135///
136/// # Safety
137///
138/// The current CPU must be offline, non-migrating and unable to take an
139/// interrupt that can access scheduler-clock state.
140pub unsafe fn init_scheduler_clock(cpu_id: usize) -> Result<(), SchedulerClockError> {
141 let stability = scheduler_clock_stability();
142 let raw_clock = scheduler_clock_raw_nanos();
143 // SAFETY: forwarded from this function's offline-CPU contract.
144 unsafe { crate::scheduler_clock::online_current_cpu(cpu_id, raw_clock, stability) }
145}
146
147/// Stops the current CPU's scheduler-clock publication.
148///
149/// # Errors
150///
151/// Returns an error if `cpu_id` is not current or its clock is already offline.
152///
153/// # Safety
154///
155/// The scheduler must have closed remote admission to this CPU and the caller
156/// must exclude migration, local IRQs and scheduler-clock re-entry.
157pub unsafe fn shutdown_scheduler_clock(cpu_id: usize) -> Result<(), SchedulerClockError> {
158 // SAFETY: forwarded from this function's scheduler lifecycle contract.
159 unsafe { crate::scheduler_clock::offline_current_cpu(cpu_id) }
160}
161
162/// Samples the current CPU's comparable wrapping scheduler clock in nanoseconds.
163///
164/// Stable platforms use the synchronized system counter. Unstable platforms
165/// update the current CPU's corrected local publication.
166///
167/// # Errors
168///
169/// Returns an error when an unstable current CPU clock has no available
170/// CPU-local state.
171///
172/// # Safety
173///
174/// The caller must own an initialized scheduler CPU and prevent migration for
175/// the complete operation. Scheduler callers satisfy this through the owner
176/// runqueue IRQ-save lock.
177#[inline]
178pub unsafe fn scheduler_clock_source() -> Result<u64, SchedulerClockError> {
179 let raw_clock = scheduler_clock_raw_nanos();
180 // SAFETY: forwarded from this function's migration-exclusion contract.
181 unsafe { crate::scheduler_clock::source_current(raw_clock) }
182}
183
184/// Samples the current CPU's scheduler clock before an outer hard interrupt.
185///
186/// This is the only runtime boundary allowed to move a scheduler clock from
187/// the stable fast path to corrected per-CPU clocks. The transition therefore
188/// cannot split one hard-interrupt accounting interval across two clock
189/// epochs.
190///
191/// # Errors
192///
193/// Returns an error if the current CPU clock has not been initialized.
194///
195/// # Safety
196///
197/// The caller must exclude migration and local IRQ re-entry, and must invoke
198/// this function before starting the outer hard-interrupt time interval.
199#[inline]
200pub unsafe fn scheduler_clock_hardirq_sample() -> Result<u64, SchedulerClockError> {
201 let stability = scheduler_clock_stability();
202 let raw_clock = scheduler_clock_raw_nanos();
203 // SAFETY: forwarded from this function's outer hard-IRQ entry contract.
204 unsafe { crate::scheduler_clock::hardirq_sample(raw_clock, stability) }
205}
206
207/// Stamps the current CPU's scheduler clock from a local timer interrupt.
208///
209/// Clock stability transitions are deliberately excluded from this API. They
210/// are committed before outer hard-interrupt accounting begins.
211///
212/// # Errors
213///
214/// Returns an error if the current CPU clock has not been initialized.
215///
216/// # Safety
217///
218/// The caller must exclude migration and local scheduler-clock re-entry. The
219/// local timer interrupt path naturally satisfies both conditions.
220#[inline]
221pub unsafe fn scheduler_clock_tick() -> Result<u64, SchedulerClockError> {
222 let raw_clock = scheduler_clock_raw_nanos();
223 // SAFETY: forwarded from this function's local tick contract.
224 unsafe { crate::scheduler_clock::tick(raw_clock) }
225}
226
227/// Returns nanoseconds elapsed since system boot.
228pub fn monotonic_time_nanos() -> u64 {
229 ticks_to_nanos(current_ticks())
230}
231
232/// Returns the time elapsed since system boot in [`TimeValue`].
233pub fn monotonic_time() -> TimeValue {
234 TimeValue::from_nanos(monotonic_time_nanos())
235}
236
237/// Returns nanoseconds elapsed since epoch (also known as realtime).
238pub fn wall_time_nanos() -> u64 {
239 adjusted_wall_time_nanos(
240 base_wall_time_nanos(),
241 WALL_TIME_ADJUSTMENT_NANOS.load(Ordering::Acquire),
242 )
243}
244
245/// Returns the time elapsed since epoch (also known as realtime) in [`TimeValue`].
246pub fn wall_time() -> TimeValue {
247 TimeValue::from_nanos(wall_time_nanos())
248}
249
250/// Sets the system-wide wall clock without changing the monotonic clock.
251///
252/// The platform epoch remains the boot-time reference. This function stores a
253/// signed adjustment relative to that reference so every wall-clock consumer
254/// observes the same value while scheduler and relative-time accounting remain
255/// tied to the monotonic counter.
256///
257/// # Errors
258///
259/// Returns [`WallTimeError::BeforeMonotonic`] if `new_time` is earlier than
260/// the current monotonic time. Returns
261/// [`WallTimeError::AdjustmentOutOfRange`] if either the timestamp or its
262/// adjustment cannot be represented by the shared clock state.
263pub fn set_wall_time(new_time: TimeValue) -> Result<(), WallTimeError> {
264 let monotonic_nanos = monotonic_time_nanos();
265 let requested_nanos =
266 u64::try_from(new_time.as_nanos()).map_err(|_| WallTimeError::AdjustmentOutOfRange)?;
267 // Match Linux do_settimeofday64 after its timespec validation:
268 // wall_to_monotonic = monotonic - old_realtime, so rejecting
269 // wall_to_monotonic > new_realtime - old_realtime rejects exactly
270 // new_realtime < monotonic. clock_settime(2) documents this since Linux 4.3.
271 if requested_nanos < monotonic_nanos {
272 return Err(WallTimeError::BeforeMonotonic);
273 }
274
275 let base_nanos = monotonic_nanos.saturating_add(epochoffset_nanos());
276 let adjustment = i128::from(requested_nanos) - i128::from(base_nanos);
277 let adjustment = i64::try_from(adjustment).map_err(|_| WallTimeError::AdjustmentOutOfRange)?;
278 WALL_TIME_ADJUSTMENT_NANOS.store(adjustment, Ordering::Release);
279 Ok(())
280}
281
282fn base_wall_time_nanos() -> u64 {
283 monotonic_time_nanos().saturating_add(epochoffset_nanos())
284}
285
286fn adjusted_wall_time_nanos(base_nanos: u64, adjustment_nanos: i64) -> u64 {
287 if adjustment_nanos >= 0 {
288 base_nanos.saturating_add(adjustment_nanos as u64)
289 } else {
290 base_nanos.saturating_sub(adjustment_nanos.unsigned_abs())
291 }
292}
293
294/// Busy waiting for the given duration.
295pub fn busy_wait(dur: Duration) {
296 busy_wait_until(monotonic_time() + dur);
297}
298
299/// Busy waiting until reaching the given monotonic deadline.
300pub fn busy_wait_until(deadline: TimeValue) {
301 while monotonic_time() < deadline {
302 core::hint::spin_loop();
303 }
304}
305
306#[cfg(test)]
307mod tests {
308 use super::*;
309
310 #[test]
311 fn wall_time_adjustment_moves_forward_and_backward() {
312 assert_eq!(adjusted_wall_time_nanos(20, 5), 25);
313 assert_eq!(adjusted_wall_time_nanos(20, -5), 15);
314 }
315
316 #[test]
317 fn wall_time_adjustment_saturates_at_clock_bounds() {
318 assert_eq!(adjusted_wall_time_nanos(u64::MAX - 1, 5), u64::MAX);
319 assert_eq!(adjusted_wall_time_nanos(1, -5), 0);
320 }
321}