Phase 2d (i): a kernel wall-clock from the CMOS RTC

Adds real (calendar) time, the foundation for filesystem timestamps. Monotonic
time (`clock`) says how long since boot; this says what time it actually is.

- cpu.zig (x86_64): readRtcUnixSeconds() reads the CMOS real-time clock (ports
  0x70/0x71) — waits out an update-in-progress, reads twice until stable, handles
  BCD-vs-binary and 12-vs-24-hour per status register B — and converts to Unix
  epoch seconds (UTC).
- kernel/wall-clock.zig: reads the RTC once at boot and anchors it to the monotonic
  clock, so a query is a cheap arithmetic offset — no per-call CMOS poll, no lock,
  no SMP hazard on the shared ports. kmain calls init() once the monotonic clock is
  final and logs the epoch.
- wall_clock() syscall (33) -> Unix epoch seconds, wrapped by runtime.system
  .wallClock(). Wall-clock *seconds* are mechanism the kernel owns like the
  monotonic clock; calendars/timezones are user-space policy (the stale comment on
  systemClock that called wall-clock a "user-space service" is updated in spirit by
  the new handler's doc).
- A `wall-clock` kernel test asserts the boot RTC read is a plausible current epoch.

Verified against the host: the guest read epoch 1783971244 while `date -u +%s` gave
1783971245 (one second of boot lag) — the CMOS read + epoch conversion are correct to
the second. zig build, zig build test, and smoke/clock/init are green.
This commit is contained in:
Daniel Samson
2026-07-13 20:35:03 +01:00
parent 54635eecf5
commit 8a38540312
8 changed files with 157 additions and 0 deletions
+9
View File
@@ -34,6 +34,7 @@ const devices_broker = @import("devices-broker.zig");
const irq = @import("irq.zig");
const initial_ramdisk = @import("initial-ramdisk");
const log = @import("log.zig");
const wall_clock = @import("wall-clock.zig");
const page_size = abi.page_size;
const SystemCall = abi.SystemCall;
@@ -207,6 +208,7 @@ fn system_call(state: *architecture.CpuState) void {
.process_signal => systemProcessSignal(state),
.timer_bind => systemTimerBind(state),
.klog_read => systemKlogRead(state),
.wall_clock => systemWallClock(state),
_ => fail(state),
}
}
@@ -1336,3 +1338,10 @@ pub fn spawnProcessSupervised(image: []const u8, priority: u3, argv: []const []c
fn systemClock(state: *architecture.CpuState) void {
architecture.setSystemCallResult(state, architecture.nanos());
}
/// wall_clock() -> Unix epoch seconds (UTC). The RTC value, read at boot and offset
/// by the monotonic clock (wall-clock.zig) — mechanism, not policy: calendars and
/// timezones layer on top in user space. Needed for filesystem timestamps (mtime).
fn systemWallClock(state: *architecture.CpuState) void {
architecture.setSystemCallResult(state, wall_clock.nowSeconds());
}