use core::sync::atomic::{AtomicI64, Ordering};
pub use core::time::Duration;
pub type TimeValue = Duration;
static WALL_TIME_ADJUSTMENT_NANOS: AtomicI64 = AtomicI64::new(0);
pub const MILLIS_PER_SEC: u64 = 1_000;
pub const MICROS_PER_SEC: u64 = 1_000_000;
pub const NANOS_PER_SEC: u64 = 1_000_000_000;
pub const NANOS_PER_MILLIS: u64 = 1_000_000;
pub const NANOS_PER_MICROS: u64 = 1_000;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SchedulerClockStability {
Stable,
Unstable,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
pub enum SchedulerClockError {
#[error("logical CPU {cpu_id} is outside the installed per-CPU layout")]
InvalidCpu { cpu_id: usize },
#[error("the calling CPU has no validated CPU-local area")]
CurrentCpuUnavailable,
#[error("scheduler clock CPU mismatch: expected {expected_cpu_id}, current {actual_cpu_id}")]
WrongCurrentCpu {
expected_cpu_id: usize,
actual_cpu_id: usize,
},
#[error("the scheduler clock CPU is already online")]
CpuAlreadyOnline,
#[error("the scheduler clock CPU is offline")]
CpuOffline,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
pub enum WallTimeError {
#[error("wall time cannot precede the current monotonic time")]
BeforeMonotonic,
#[error("wall-time adjustment is outside the supported range")]
AdjustmentOutOfRange,
}
#[def_plat_interface]
pub trait TimeIf {
fn current_ticks() -> u64;
fn ticks_to_nanos(ticks: u64) -> u64;
fn scheduler_clock_raw_nanos() -> u64;
fn nanos_to_ticks(nanos: u64) -> u64;
fn scheduler_clock_stability() -> SchedulerClockStability;
fn epochoffset_nanos() -> u64;
fn irq_num() -> irq_framework::IrqId;
fn set_oneshot_timer(deadline_ns: u64);
fn oneshot_timer_requires_irq_quiesce() -> bool;
fn resume_oneshot_timer(deadline_ns: u64);
fn cancel_oneshot_timer();
}
pub unsafe fn init_scheduler_clock(cpu_id: usize) -> Result<(), SchedulerClockError> {
let stability = scheduler_clock_stability();
let raw_clock = scheduler_clock_raw_nanos();
unsafe { crate::scheduler_clock::online_current_cpu(cpu_id, raw_clock, stability) }
}
pub unsafe fn shutdown_scheduler_clock(cpu_id: usize) -> Result<(), SchedulerClockError> {
unsafe { crate::scheduler_clock::offline_current_cpu(cpu_id) }
}
#[inline]
pub unsafe fn scheduler_clock_source() -> Result<u64, SchedulerClockError> {
let raw_clock = scheduler_clock_raw_nanos();
unsafe { crate::scheduler_clock::source_current(raw_clock) }
}
#[inline]
pub unsafe fn scheduler_clock_hardirq_sample() -> Result<u64, SchedulerClockError> {
let stability = scheduler_clock_stability();
let raw_clock = scheduler_clock_raw_nanos();
unsafe { crate::scheduler_clock::hardirq_sample(raw_clock, stability) }
}
#[inline]
pub unsafe fn scheduler_clock_tick() -> Result<u64, SchedulerClockError> {
let raw_clock = scheduler_clock_raw_nanos();
unsafe { crate::scheduler_clock::tick(raw_clock) }
}
pub fn monotonic_time_nanos() -> u64 {
ticks_to_nanos(current_ticks())
}
pub fn monotonic_time() -> TimeValue {
TimeValue::from_nanos(monotonic_time_nanos())
}
pub fn wall_time_nanos() -> u64 {
adjusted_wall_time_nanos(
base_wall_time_nanos(),
WALL_TIME_ADJUSTMENT_NANOS.load(Ordering::Acquire),
)
}
pub fn wall_time() -> TimeValue {
TimeValue::from_nanos(wall_time_nanos())
}
pub fn set_wall_time(new_time: TimeValue) -> Result<(), WallTimeError> {
let monotonic_nanos = monotonic_time_nanos();
let requested_nanos =
u64::try_from(new_time.as_nanos()).map_err(|_| WallTimeError::AdjustmentOutOfRange)?;
if requested_nanos < monotonic_nanos {
return Err(WallTimeError::BeforeMonotonic);
}
let base_nanos = monotonic_nanos.saturating_add(epochoffset_nanos());
let adjustment = i128::from(requested_nanos) - i128::from(base_nanos);
let adjustment = i64::try_from(adjustment).map_err(|_| WallTimeError::AdjustmentOutOfRange)?;
WALL_TIME_ADJUSTMENT_NANOS.store(adjustment, Ordering::Release);
Ok(())
}
fn base_wall_time_nanos() -> u64 {
monotonic_time_nanos().saturating_add(epochoffset_nanos())
}
fn adjusted_wall_time_nanos(base_nanos: u64, adjustment_nanos: i64) -> u64 {
if adjustment_nanos >= 0 {
base_nanos.saturating_add(adjustment_nanos as u64)
} else {
base_nanos.saturating_sub(adjustment_nanos.unsigned_abs())
}
}
pub fn busy_wait(dur: Duration) {
busy_wait_until(monotonic_time() + dur);
}
pub fn busy_wait_until(deadline: TimeValue) {
while monotonic_time() < deadline {
core::hint::spin_loop();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wall_time_adjustment_moves_forward_and_backward() {
assert_eq!(adjusted_wall_time_nanos(20, 5), 25);
assert_eq!(adjusted_wall_time_nanos(20, -5), 15);
}
#[test]
fn wall_time_adjustment_saturates_at_clock_bounds() {
assert_eq!(adjusted_wall_time_nanos(u64::MAX - 1, 5), u64::MAX);
assert_eq!(adjusted_wall_time_nanos(1, -5), 0);
}
}