SchOS/kernel/core/time.c
Nexoniarz 617a4aec0a boot: one line when it works, all of it when it does not
A boot printed about thirty lines before reaching a prompt. Every one was
useful once, and printed on every boot for ever they train the person in front
of the machine to stop reading their own console -- which is expensive on the
day one of them changes.

    SchOS UEFI loader
    Press F8 for boot options... booting

    Welcome to SchOS!  (F8 at boot for the full log, or dmesg once you are in)

    root@schos / »

**Nothing was deleted, it was routed.** consputc() already wrote to the klog
ring before anywhere else, so a line can be kept in full while being held back
from the two places a person looks. kinfo() is cprintf() with that routing, and
`dmesg` still has the whole boot with timestamps afterwards. The rule for which
is which is not how interesting a line is -- everything was interesting to
somebody once -- but whether you would want to see it on a machine that is
*working*. `pci: 7 devices` is yes the first time and no every time after;
`e1000: no DMA memory for the rings` is yes for ever and stays a cprintf.

The verbose boot the F8 menu already offered now means something much bigger,
which is the other half of this: it is the only way to see any of it live.

Userspace follows through /proc/bootmode, one word. schinit, schinitd and vgpud
hold back their progress lines and never their failures -- a supervisor that
goes quiet when a job dies is worse than one that chatters.

Two things found by testing rather than by reading:

  - schinit read /proc/bootmode *before* bootstrap() mounted /proc, so it got
    "quiet" every time and the verbose boot was quiet in the one process that
    runs first. It reads after the mount now.
  - resolvconf announced "already exists; -f to replace it" on every boot after
    the first, which is a line about having done nothing. It is quiet when the
    file already says what it would have written, and speaks only when the two
    disagree -- which means the nameserver changed and the machine is using the
    old one.

Verified both ways: quiet boot is six lines, a forced-verbose build prints all
thirty plus the `verbose boot:` steps, and /proc/kmsg holds every line in both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
2026-09-14 21:19:50 +02:00

163 lines
6.7 KiB
C

// Wall-clock time.
//
// The kernel has always known how long it had been running -- `ticks`, bumped
// 100 times a second by the timer interrupt -- and has never known what time it
// was. sys_gettimeofday() returned `ticks / 100` as though it were seconds
// since the epoch, so every SchOS machine believed it was the 1st of January
// 1970 and that the date advanced at the rate of its own uptime. `date` printed
// a number that barely moved, which is what it looks like when a clock is
// counting from the wrong place rather than not counting at all.
//
// cmostime() in kernel/arch/x86_64/lapic.c has been able to read the hardware
// clock the whole time. Nothing called it.
//
// The arrangement here is the usual one: read the RTC once at boot, convert it
// to a Unix timestamp, and add uptime to it thereafter. The RTC is slow to read
// (it is polled over two I/O ports and has to be sampled twice to catch it
// mid-update) so reading it on every gettimeofday would be absurd; and `ticks`
// is a perfectly good interval clock once it has something to count from.
#include "types.h"
#include "defs.h"
#include "x86.h"
#include "date.h"
#include "param.h"
#include "spinlock.h"
extern uint ticks;
// Seconds between the Unix epoch and the moment this machine booted. Zero until
// time_init() runs, which makes the behaviour before that point exactly what it
// used to be -- uptime presented as absolute time -- rather than something new
// and differently wrong.
uint64 boot_epoch;
// Days from 1970-01-01 to the given civil date, proleptic Gregorian.
//
// Howard Hinnant's days_from_civil. It is worth using rather than a loop over
// years because it is branchless, exact for any year, and has no table to get
// wrong; the alternative -- accumulating 365 or 366 days per year since 1970 --
// gets leap centuries wrong unless you remember that 1900 was not a leap year
// and 2000 was, which is precisely the sort of thing that is discovered on a
// February morning years later.
//
// The trick is shifting the year so that March is month 1, which puts the leap
// day at the *end* of the year and makes the leap rule depend only on the year
// number rather than on where in the year you are.
static int64
days_from_civil(int64 y, unsigned m, unsigned d)
{
y -= m <= 2;
const int64 era = (y >= 0 ? y : y - 399) / 400;
const unsigned yoe = (unsigned)(y - era * 400); // [0, 399]
const unsigned doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
const unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096]
return era * 146097 + (int64)doe - 719468;
}
// Read the hardware clock and work out when we booted.
//
// The RTC is read in UTC. That is not universally true on a PC -- a machine
// that has had Windows installed on it usually keeps the RTC in local time --
// but there is nowhere to store a timezone yet and no way to ask, and UTC is
// the honest default for a system with no configuration. When there is a
// settings store, this is where the offset goes.
void
time_init(void)
{
struct rtcdate r;
cmostime(&r);
// cmostime() returns a two-digit year on hardware that only stores one.
// 2000-2099 is the right guess now and stays right for a lifetime; a machine
// reporting 70 means the battery is flat, and the epoch is as good an answer
// as any for that.
int64 year = r.year;
if (year < 100)
year += 2000;
// A wildly implausible reading means the CMOS is not telling us anything.
// Leaving boot_epoch at zero keeps the old behaviour, which is wrong but
// predictable, rather than putting the machine somewhere in the Bronze Age.
if (r.month < 1 || r.month > 12 || r.day < 1 || r.day > 31 ||
r.hour > 23 || r.minute > 59 || r.second > 60 || year < 1970) {
cprintf("time: CMOS clock is not plausible, staying at the epoch\n");
return;
}
int64 days = days_from_civil(year, r.month, r.day);
uint64 now = (uint64)(days * 86400) + r.hour * 3600 + r.minute * 60 + r.second;
// Subtract the uptime so far, so boot_epoch is the moment of boot rather than
// the moment this function ran. It is only a fraction of a second, but it
// keeps boot_epoch meaning exactly what its name says.
uint64 up, dummy;
monotonic(&up, &dummy);
boot_epoch = now > up ? now - up : now;
kinfo("time: %d-%d-%d %d:%d:%d UTC from the hardware clock\n",
(int)year, r.month, r.day, r.hour, r.minute, r.second);
}
// How long the machine has been running, from the TSC.
//
// **Not from `ticks`.** This is the important line in this file.
//
// `ticks` counts timer interrupts, and a timer interrupt that arrives while
// interrupts are disabled is not delayed, it is *lost* -- the LAPIC in periodic
// mode holds one pending interrupt and drops the rest. So anything the kernel
// does with interrupts off for longer than 10 ms makes the clock run slow, and
// it never catches up. A long console write, a framebuffer scroll on a display
// that is expensive to draw to, a driver spinning on a register: all of them
// silently steal time from every program that asks what time it is.
//
// That has now happened twice. The first was an interrupt storm taking 83% of
// the CPU (docs/history/kernel.md §13) and the clock ran seven times slow; the
// second was heavy console output, and it presented the same way. Both times
// the tick was the messenger.
//
// The TSC cannot be starved. It is a counter in the silicon that advances at a
// fixed rate whether or not this kernel is paying attention, and it is already
// measured against the PIT at boot. Reading it is one instruction and no lock.
//
// `ticks` is still what the scheduler preempts on and what sleep() counts,
// which is the right job for it: losing a tick there means a slightly longer
// sleep, not a wrong answer about what time it is.
void
monotonic(uint64 *sec, uint64 *usec)
{
// Before calibration there is no TSC frequency to divide by. Fall back to
// the tick count, which is what this used to do always.
if (tsc_hz == 0) {
uint xticks;
acquire(&tickslock);
xticks = ticks;
release(&tickslock);
if (sec) *sec = xticks / HZ;
if (usec) *usec = (uint64)(xticks % HZ) * (1000000 / HZ);
return;
}
uint64 d = rdtsc() - boot_tsc;
uint64 s = d / tsc_hz;
// The remainder first, then scale. `d * 1000000` would overflow 64 bits
// after about 80 minutes at 3.8 GHz; the remainder is always below tsc_hz,
// so this cannot.
uint64 rem = d - s * tsc_hz;
if (sec) *sec = s;
if (usec) *usec = (rem * 1000000) / tsc_hz;
}
// Seconds and microseconds since the epoch. One place, so gettimeofday,
// clock_gettime and anything added later cannot disagree about what time it is.
void
wallclock(uint64 *sec, uint64 *usec)
{
uint64 s, us;
monotonic(&s, &us);
if (sec) *sec = boot_epoch + s;
if (usec) *usec = us;
}