//! The danos time interface — monotonic time, delays, and deadlines for user space. //! //! There is no time *service*: the kernel already owns the scheduling timer and //! surfaces it directly, so reading the clock is one system call (an `rdtsc` and a //! scale), never an IPC round trip (docs/timers.md explains why). This module is a //! thin, generic layer over the `clock`/`sleep`/`timer_bind` wrappers in `system.zig` //! — an ergonomic `Instant`/`Duration` front door, not new mechanism. //! //! It is **monotonic** time only: nanoseconds since boot, moving forward, no date or //! timezone. Wall-clock/calendar time is a separate user-space service (an RTC-backed //! CLOCK_REALTIME) layered on top later. const std = @import("std"); const sc = @import("system-call"); // --- raw syscall wrappers, formerly in the system.zig dumping ground --- /// Monotonic nanoseconds since boot — the raw reading; `now()` wraps it in an `Instant`. /// Never runs backward. Not wall-clock time (see `wallClock`). pub fn clock() u64 { return @intCast(sc.systemCall0(.clock)); } /// Wall-clock time in Unix epoch seconds (UTC), from the RTC — the real date/time, what a /// filesystem stamps as an mtime. Unlike `clock` (monotonic since boot), this is calendar time. pub fn wallClock() u64 { return @intCast(sc.systemCall0(.wall_clock)); } /// Block the caller for `ms` milliseconds — the raw, coarse, allocation-free form. pub fn sleepMillis(ms: u64) void { _ = sc.systemCall1(.sleep, ms); } /// Arm a one-shot timer: after `ms` the kernel posts a timer notification /// (`ipc.Received.isTimer`) to `endpoint` (a handle from `ipc.createIpcEndpoint`). Unlike /// `sleep`, does not block — a service keeps serving IPC while the deadline is pending. pub fn timerOnce(endpoint: usize, ms: u64) bool { return sc.systemCall2(.timer_bind, endpoint, ms) == 0; } const nanos_per_micro: u64 = 1_000; const nanos_per_milli: u64 = 1_000_000; const nanos_per_second: u64 = 1_000_000_000; /// A span of time, held as nanoseconds. Constructors name their unit; accessors /// truncate toward zero. `ceilMillis` rounds *up*, since `sleep`/`after` land on the /// kernel's millisecond granularity and rounding down could return early. pub const Duration = struct { ns: u64, pub fn fromNanos(n: u64) Duration { return .{ .ns = n }; } pub fn fromMicros(n: u64) Duration { return .{ .ns = n *| nanos_per_micro }; } pub fn fromMillis(n: u64) Duration { return .{ .ns = n *| nanos_per_milli }; } pub fn fromSeconds(n: u64) Duration { return .{ .ns = n *| nanos_per_second }; } pub fn asNanos(d: Duration) u64 { return d.ns; } pub fn asMicros(d: Duration) u64 { return d.ns / nanos_per_micro; } pub fn asMillis(d: Duration) u64 { return d.ns / nanos_per_milli; } pub fn asSeconds(d: Duration) u64 { return d.ns / nanos_per_second; } /// Whole milliseconds, rounded up — the argument `sleep`/`after` pass the kernel. /// A non-zero sub-millisecond duration becomes 1 ms rather than 0. pub fn ceilMillis(d: Duration) u64 { return (d.ns +| (nanos_per_milli - 1)) / nanos_per_milli; } pub fn plus(a: Duration, b: Duration) Duration { return .{ .ns = a.ns +| b.ns }; } }; /// A point on the monotonic clock — nanoseconds since boot. Compare and subtract /// instants to measure elapsed time; it never runs backward, so `since` is safe to /// saturate at zero rather than wrap. pub const Instant = struct { ns: u64, /// The span from `earlier` to `self`, saturating at zero if `earlier` is later /// (which the monotonic clock should never produce, but callers may pass any pair). pub fn since(self: Instant, earlier: Instant) Duration { return .{ .ns = self.ns -| earlier.ns }; } /// How long since this instant, sampled now. pub fn elapsed(self: Instant) Duration { return now().since(self); } /// This instant advanced by `d` (a deadline, `d` from here). pub fn plus(self: Instant, d: Duration) Instant { return .{ .ns = self.ns +| d.ns }; } /// Whether the monotonic clock has reached this instant (used as a deadline). pub fn reached(deadline: Instant) bool { return now().ns >= deadline.ns; } }; /// The current monotonic time. pub fn now() Instant { return .{ .ns = clock() }; } /// Monotonic nanoseconds since boot — the raw `clock()` reading, for callers that /// want a plain integer instead of an `Instant`. pub fn monotonicNanos() u64 { return clock(); } /// Whether the monotonic clock is usable. The kernel returns 0 until the TSC is /// calibrated (`tsc_hz == 0`); a caller that needs real time can treat that as /// "unavailable" instead of assuming the clock advances. pub fn available() bool { return clock() != 0; } /// Block the caller for at least `d`, rounded up to the kernel's millisecond /// granularity. For sub-millisecond precision the scheduler cannot express, use /// `spin`. (The raw millisecond form is `sleepMillis`.) pub fn sleep(d: Duration) void { sleepMillis(d.ceilMillis()); } /// Busy-wait until `d` has elapsed, polling the monotonic clock. This burns the CPU /// on purpose, to hit sub-millisecond delays the scheduler's millisecond tick cannot. /// Prefer `sleep` for anything at or above a millisecond. pub fn spin(d: Duration) void { const deadline = now().plus(d); while (!deadline.reached()) {} } /// The ergonomic `Duration` form of `timerOnce`: arm a one-shot timer against `endpoint` /// for `d` (rounded up to milliseconds). Returns false if the timer could not be armed. pub fn after(endpoint: usize, d: Duration) bool { return timerOnce(endpoint, d.ceilMillis()); } test "Duration unit conversions round toward zero" { try std.testing.expectEqual(@as(u64, 1_000_000_000), Duration.fromSeconds(1).asNanos()); try std.testing.expectEqual(@as(u64, 1_500), Duration.fromNanos(1_500).asNanos()); try std.testing.expectEqual(@as(u64, 2), Duration.fromMillis(2).asMillis()); try std.testing.expectEqual(@as(u64, 1), Duration.fromNanos(1_999_999).asMillis()); try std.testing.expectEqual(@as(u64, 250), Duration.fromMicros(250).asMicros()); } test "ceilMillis rounds up, and never turns a nonzero span into zero" { try std.testing.expectEqual(@as(u64, 0), Duration.fromNanos(0).ceilMillis()); try std.testing.expectEqual(@as(u64, 1), Duration.fromNanos(1).ceilMillis()); try std.testing.expectEqual(@as(u64, 1), Duration.fromMillis(1).ceilMillis()); try std.testing.expectEqual(@as(u64, 2), Duration.fromNanos(nanos_per_milli + 1).ceilMillis()); try std.testing.expectEqual(@as(u64, 5), Duration.fromMillis(5).ceilMillis()); } test "Instant arithmetic: since saturates, plus/reached form deadlines" { const t0 = Instant{ .ns = 1_000 }; const t1 = Instant{ .ns = 4_000 }; try std.testing.expectEqual(@as(u64, 3_000), t1.since(t0).asNanos()); // earlier-than-self can't happen on a monotonic clock; saturate rather than wrap. try std.testing.expectEqual(@as(u64, 0), t0.since(t1).asNanos()); const deadline = t0.plus(Duration.fromNanos(2_500)); try std.testing.expectEqual(@as(u64, 3_500), deadline.ns); } test "saturating arithmetic does not overflow at the u64 ceiling" { const big = Duration.fromSeconds(std.math.maxInt(u64)); try std.testing.expectEqual(@as(u64, std.math.maxInt(u64)), big.asNanos()); const late = Instant{ .ns = std.math.maxInt(u64) }; try std.testing.expectEqual(@as(u64, std.math.maxInt(u64)), late.plus(Duration.fromSeconds(10)).ns); }