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:
@@ -469,6 +469,95 @@ pub fn clockHz() u64 {
|
||||
return apic.tscHz();
|
||||
}
|
||||
|
||||
// --- real-time clock (CMOS) --------------------------------------------------
|
||||
//
|
||||
// The battery-backed CMOS clock, read once at boot and thereafter anchored to the
|
||||
// monotonic clock (see kernel/wall-clock.zig) — so this is never on a hot path and
|
||||
// needs no lock. Wall-clock *seconds* are mechanism the kernel owns (the hardware's
|
||||
// value), like the monotonic clock; calendars/timezones are policy layered on top.
|
||||
|
||||
fn cmosRead(register: u8) u8 {
|
||||
io.outb(0x70, register);
|
||||
return io.inb(0x71);
|
||||
}
|
||||
|
||||
const RtcFields = struct { second: u8, minute: u8, hour: u8, day: u8, month: u8, year: u8 };
|
||||
|
||||
fn rtcRaw() RtcFields {
|
||||
while (cmosRead(0x0A) & 0x80 != 0) {} // wait out any update in progress (status A bit 7)
|
||||
return .{
|
||||
.second = cmosRead(0x00),
|
||||
.minute = cmosRead(0x02),
|
||||
.hour = cmosRead(0x04),
|
||||
.day = cmosRead(0x07),
|
||||
.month = cmosRead(0x08),
|
||||
.year = cmosRead(0x09),
|
||||
};
|
||||
}
|
||||
|
||||
fn bcdToBinary(v: u8) u8 {
|
||||
return (v & 0x0F) + ((v >> 4) * 10);
|
||||
}
|
||||
|
||||
fn isLeapYear(y: u32) bool {
|
||||
return (y % 4 == 0 and y % 100 != 0) or (y % 400 == 0);
|
||||
}
|
||||
|
||||
/// Read the CMOS real-time clock and convert it to Unix epoch seconds (UTC).
|
||||
pub fn readRtcUnixSeconds() u64 {
|
||||
// Read until two consecutive reads agree, so we never latch a half-updated time.
|
||||
var a = rtcRaw();
|
||||
while (true) {
|
||||
const b = rtcRaw();
|
||||
if (a.second == b.second and a.minute == b.minute and a.hour == b.hour and
|
||||
a.day == b.day and a.month == b.month and a.year == b.year) break;
|
||||
a = b;
|
||||
}
|
||||
|
||||
const status_b = cmosRead(0x0B);
|
||||
const binary_mode = status_b & 0x04 != 0; // else BCD
|
||||
const hour_24 = status_b & 0x02 != 0; // else 12-hour with a PM bit
|
||||
|
||||
var second = a.second;
|
||||
var minute = a.minute;
|
||||
var hour_field = a.hour;
|
||||
var day = a.day;
|
||||
var month = a.month;
|
||||
var year = a.year;
|
||||
if (!binary_mode) {
|
||||
second = bcdToBinary(second);
|
||||
minute = bcdToBinary(minute);
|
||||
hour_field = bcdToBinary(hour_field & 0x7F) | (hour_field & 0x80); // preserve the PM bit
|
||||
day = bcdToBinary(day);
|
||||
month = bcdToBinary(month);
|
||||
year = bcdToBinary(year);
|
||||
}
|
||||
|
||||
var hour: u32 = hour_field & 0x7F;
|
||||
if (!hour_24) {
|
||||
const pm = hour_field & 0x80 != 0;
|
||||
hour %= 12; // 12 AM/PM -> 0
|
||||
if (pm) hour += 12;
|
||||
}
|
||||
|
||||
// The CMOS year is 0..99; QEMU and modern hardware mean 20xx (there is no
|
||||
// reliable century register on QEMU). Treat < 70 as 20xx, else 19xx.
|
||||
const full_year: u32 = if (year < 70) 2000 + @as(u32, year) else 1900 + @as(u32, year);
|
||||
|
||||
var days: u64 = 0;
|
||||
var y: u32 = 1970;
|
||||
while (y < full_year) : (y += 1) days += if (isLeapYear(y)) 366 else 365;
|
||||
const month_lengths = [_]u8{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
|
||||
var m: u8 = 1;
|
||||
while (m < month) : (m += 1) {
|
||||
days += month_lengths[m - 1];
|
||||
if (m == 2 and isLeapYear(full_year)) days += 1;
|
||||
}
|
||||
days += @as(u64, day) - 1;
|
||||
|
||||
return ((days * 24 + hour) * 60 + minute) * 60 + second;
|
||||
}
|
||||
|
||||
/// Whether the CPU guarantees an **invariant** TSC (CPUID 0x80000007 EDX[8] on
|
||||
/// x86; the analogous architectural guarantee elsewhere). When false the TSC is not
|
||||
/// used as the clocksource.
|
||||
|
||||
Reference in New Issue
Block a user