refactor: time -> clock

This commit is contained in:
Loïc Grégoire
2026-04-18 14:07:42 -04:00
parent a1db75746a
commit 44cf08e57c
13 changed files with 95 additions and 83 deletions
+52 -45
View File
@@ -4,9 +4,8 @@ const r = @import("root");
const ke = r.ke; const ke = r.ke;
const ki = ke.private; const ki = ke.private;
/// TimeCounter structure. /// Represents a clock source upon which the kernel can rely.
/// Represents a time-keeping source upon which the kernel can rely. pub const ClockSource = struct {
pub const TimeCounter = struct {
/// Callback that reads the current count. /// Callback that reads the current count.
read_count: *const fn () u64, read_count: *const fn () u64,
/// Frequency of the timer in Hz. /// Frequency of the timer in Hz.
@@ -14,7 +13,7 @@ pub const TimeCounter = struct {
/// Name of the timer. /// Name of the timer.
name: []const u8, name: []const u8,
/// Quality of the timer. /// Quality of the timer.
/// Used to choose between two TimeCounters. /// Used to choose between two ClockSources.
quality: i16, quality: i16,
/// Mask to apply to the counter value. /// Mask to apply to the counter value.
mask: u64, mask: u64,
@@ -26,95 +25,103 @@ pub const TimeCounter = struct {
n: u64, n: u64,
}; };
var best_tc: ?*TimeCounter = null; var best_clock: ?*ClockSource = null;
const TimeState = struct { const State = struct {
// Counter value at the time the current TimeCounter was selected. // Counter value at the time the current ClockSource was selected.
// Used as the baseline for elapsed time calculation. // Used as the baseline for elapsed time calculation.
initial_count: u64, initial_count: u64,
// Accumulated nanosecond offset from previous TimeCounters and overflow events. // Accumulated nanosecond offset from previous ClockSources and overflow events.
// Added to the current counter's contribution in `read_time_nano`. // Added to the current counter's contribution in `read_time`.
offset: u64, offset: u64,
}; };
// SeqLock-protected time state. // SeqLock-protected time state.
// Avoids a race on `initial_count` and `offset` with `update_overflow`. // Avoids a race on `initial_count` and `offset` with `update_overflow`.
var state = rtl.SeqLock(TimeState).init(.{ var state = rtl.SeqLock(State).init(.{
.initial_count = 0, .initial_count = 0,
.offset = 0, .offset = 0,
}); });
// Convert a TimeCounter's count to nanoseconds. // Convert a ClockSource's count to nanoseconds.
// For 64-bit counters (`mask == maxInt(u64)`), uses a fast multiply-shift path // For 64-bit counters (`mask == maxInt(u64)`), uses a fast multiply-shift path
// via precomputed `p` and `n` to avoid 128-bit division at runtime. // via precomputed `p` and `n` to avoid 128-bit division at runtime.
// Falls back to a division-based conversion for narrower counters. // Falls back to a division-based conversion for narrower counters.
fn ticks_to_ns(tc: *TimeCounter, count: u64) u64 { fn ticks_to_ns(cs: *ClockSource, count: u64) u64 {
if (tc.p != 0) { if (cs.p != 0) {
const val: u128 = @as(u128, @intCast(count)) * tc.n; const val: u128 = @as(u128, @intCast(count)) * cs.n;
return @intCast(val >> @intCast(tc.p)); return @intCast(val >> @intCast(cs.p));
} }
return (count * std.time.ns_per_s) / tc.frequency; return (count * std.time.ns_per_s) / cs.frequency;
} }
/// Register a time-keeping source. /// Register a clock source.
/// The TimeCounter will be used if it is the highest quality one available. /// The provided source will be used if it is the highest quality one available.
/// This function cannot be safely called when the hardware timer has been started, as data races on `best_tc` are not avoided. /// This function cannot be safely called when the hardware timer has been started, as data races on `best_tc` are not avoided.
pub fn register_source(tc: *TimeCounter) void { pub fn register_source(cs: *ClockSource) void {
// Precompute scaling factors for the fast multiply-shift path. // Precompute scaling factors for the fast multiply-shift path.
// This is meant to avoid 128-bit division and only use multiplication for 64-bit counters. // This is meant to avoid 128-bit division and only use multiplication for 64-bit counters.
if (tc.mask == std.math.maxInt(u64)) { if (cs.mask == std.math.maxInt(u64)) {
tc.p = std.math.log2_int_ceil(u64, tc.frequency); cs.p = std.math.log2_int_ceil(u64, cs.frequency);
tc.n = (@as(u64, std.time.ns_per_s) << @intCast(tc.p)) / tc.frequency; cs.n = (@as(u64, std.time.ns_per_s) << @intCast(cs.p)) / cs.frequency;
} else { } else {
tc.p = 0; cs.p = 0;
tc.n = 0; cs.n = 0;
} }
// If the best TimeCounter changed, pass the baton: // If the best TimeCounter changed, pass the baton:
// snapshot the old counter's elapsed time into `offset` and // snapshot the old counter's elapsed time into `offset` and
// reset `initial_count` to the new counter's current value. // reset `initial_count` to the new counter's current value.
if (best_tc == null or tc.quality > best_tc.?.quality) { if (best_clock == null or cs.quality > best_clock.?.quality) {
const old_best = best_tc; const old_best = best_clock;
best_tc = tc; best_clock = cs;
if (old_best) |old| { if (old_best) |old| {
const old_count = old.read_count() & old.mask; const old_count = old.read_count() & old.mask;
state.raw.offset = ticks_to_ns(old, old_count); state.raw.offset = ticks_to_ns(old, old_count);
} }
state.raw.initial_count = tc.read_count() & tc.mask; state.raw.initial_count = cs.read_count() & cs.mask;
} }
} }
/// Return the time elapsed since boot in nanoseconds. /// Return the time elapsed since boot in nanoseconds.
pub fn read_time_nano() r.Nanoseconds { pub fn read_time() r.Nanoseconds {
const tc = best_tc orelse return 0; const cs = best_clock orelse return 0;
const s = state.load(); const s = state.load();
const curr_count = tc.read_count() & tc.mask; const curr_count = cs.read_count() & cs.mask;
const elapsed = (curr_count - s.initial_count) & tc.mask; const elapsed = (curr_count - s.initial_count) & cs.mask;
return s.offset + ticks_to_ns(tc, elapsed); return s.offset + ticks_to_ns(cs, elapsed);
} }
/// Return the best TimeCounter. /// Return the best clock source.
pub fn best() ?*TimeCounter { pub fn best() ?*ClockSource {
return best_tc; return best_clock;
} }
/// Do a busy sleep of `ns` nanoseconds using TimeCounter. /// Return whether the current best clock is better than `cs`.
pub fn is_better_than(cs: *ClockSource) bool {
if (best_clock) |b| {
return b.quality > cs.quality;
}
return false;
}
/// Do a busy sleep of `ns` nanoseconds.
pub fn sleep(ns: r.Nanoseconds) void { pub fn sleep(ns: r.Nanoseconds) void {
const start = read_time_nano(); const start = read_time();
while (true) { while (true) {
var cur = read_time_nano(); var cur = read_time();
if (cur < start) continue; if (cur < start) continue;
if (cur - start >= ns) { if (cur - start >= ns) {
cur = read_time_nano(); cur = read_time();
// Check again as we may have overflowed and read a bogus value.. // Check again as we may have overflowed and read a bogus value.
// This is kinda hacky but oh well. // This is kinda hacky but oh well.
if (cur - start >= ns) { if (cur - start >= ns) {
break; break;
@@ -127,17 +134,17 @@ pub fn sleep(ns: r.Nanoseconds) void {
/// Called periodically to handle counter overflows. /// Called periodically to handle counter overflows.
pub fn update_overflow() void { pub fn update_overflow() void {
const tc = best_tc orelse return; const cs = best_clock orelse return;
const s = state.load(); const s = state.load();
const cur_count = tc.read_count() & tc.mask; const cur_count = cs.read_count() & cs.mask;
if (s.initial_count > cur_count) { if (s.initial_count > cur_count) {
// Counter has wrapped, use wrapping arithmetic to figure out by how much, // Counter has wrapped, use wrapping arithmetic to figure out by how much,
// then fold it into the offset and reset `initial_count`. // then fold it into the offset and reset `initial_count`.
const overflowed_by = (cur_count -% s.initial_count) & tc.mask; const overflowed_by = (cur_count -% s.initial_count) & cs.mask;
state.store(.{ state.store(.{
.initial_count = cur_count, .initial_count = cur_count,
.offset = s.offset + ticks_to_ns(tc, overflowed_by), .offset = s.offset + ticks_to_ns(cs, overflowed_by),
}); });
} }
} }
+1 -1
View File
@@ -55,7 +55,7 @@ pub fn log(
// Reserve space in the ring buffer. // Reserve space in the ring buffer.
var res = ringbuffer.reserve(required_len) catch return; var res = ringbuffer.reserve(required_len) catch return;
res.info.timestamp = ke.time.read_time_nano(); res.info.timestamp = ke.clock.read_time();
res.info.length = @truncate(required_len); res.info.length = @truncate(required_len);
// Format the log message into the reserved buffer. // Format the log message into the reserved buffer.
+1 -1
View File
@@ -20,7 +20,7 @@ pub const log = @import("log.zig");
pub const cpu = @import("cpu.zig"); pub const cpu = @import("cpu.zig");
pub const thread = @import("thread.zig"); pub const thread = @import("thread.zig");
pub const dpc = @import("dpc.zig"); pub const dpc = @import("dpc.zig");
pub const time = @import("time.zig"); pub const clock = @import("clock.zig");
pub const sched = @import("sched.zig"); pub const sched = @import("sched.zig");
pub const wait = @import("wait.zig"); pub const wait = @import("wait.zig");
pub const timer = @import("timer.zig"); pub const timer = @import("timer.zig");
+7 -6
View File
@@ -10,7 +10,7 @@ pub const Ipl = p.ipl.Ipl;
pub const SpinLock = p.spinlock.SpinLock; pub const SpinLock = p.spinlock.SpinLock;
pub const Thread = p.thread.Thread; pub const Thread = p.thread.Thread;
pub const Dpc = p.dpc.Dpc; pub const Dpc = p.dpc.Dpc;
pub const TimeCounter = p.time.TimeCounter; pub const ClockSource = p.clock.ClockSource;
pub const Timer = p.timer.Timer; pub const Timer = p.timer.Timer;
pub const Console = p.log.Console; pub const Console = p.log.Console;
pub const CpuLocal = p.cpu.CpuLocal; pub const CpuLocal = p.cpu.CpuLocal;
@@ -35,11 +35,12 @@ pub const dpc = struct {
pub const enqueue = p.dpc.enqueue; pub const enqueue = p.dpc.enqueue;
}; };
pub const time = struct { pub const clock = struct {
pub const register_source = p.time.register_source; pub const register_source = p.clock.register_source;
pub const read_time_nano = p.time.read_time_nano; pub const read_time = p.clock.read_time;
pub const best = p.time.best; pub const best = p.clock.best;
pub const sleep = p.time.sleep; pub const sleep = p.clock.sleep;
pub const is_better_than = p.clock.is_better_than;
}; };
pub const sched = struct { pub const sched = struct {
+2 -2
View File
@@ -246,7 +246,7 @@ pub fn block() void {
td.lock.acquire_no_ipl(); td.lock.acquire_no_ipl();
td.state = .Blocked; td.state = .Blocked;
td.sleep_start = ke.time.read_time_nano(); td.sleep_start = ke.clock.read_time();
td.runq = null; td.runq = null;
yield(ke.cpu.current()); yield(ke.cpu.current());
@@ -259,7 +259,7 @@ pub fn block() void {
pub fn unblock(td: *ke.Thread) void { pub fn unblock(td: *ke.Thread) void {
const ipl = td.lock.acquire(); const ipl = td.lock.acquire();
td.sleep_time = (ke.time.read_time_nano() - td.sleep_start) / std.time.ns_per_ms; td.sleep_time = (ke.clock.read_time() - td.sleep_start) / std.time.ns_per_ms;
if (td.sleep_time >= config.CONFIG_SCHED_TIMESLICE and td.priority_class == .Batch) { if (td.sleep_time >= config.CONFIG_SCHED_TIMESLICE and td.priority_class == .Batch) {
// If we have slept for more than a tick, update interactivity. // If we have slept for more than a tick, update interactivity.
+4 -4
View File
@@ -75,7 +75,7 @@ pub fn set(timer: *Timer, time: r.Nanoseconds, dpc: ?*ke.Dpc) void {
defer cpu.lock.release_no_ipl(); defer cpu.lock.release_no_ipl();
// Initialize the timer. // Initialize the timer.
timer.deadline = ke.time.read_time_nano() + time; timer.deadline = ke.clock.read_time() + time;
timer.cpu = cpu; timer.cpu = cpu;
timer.dpc = dpc; timer.dpc = dpc;
@@ -134,7 +134,7 @@ pub fn clock() void {
ke.dpc.enqueue(&percpu.local().dpc, null); ke.dpc.enqueue(&percpu.local().dpc, null);
// Check for overflows. // Check for overflows.
// This is fine to call a lot, as the function only gets expensive (i.e seqlock store) when an overflow actually happens. // This is fine to call a lot, as the function only gets expensive (i.e seqlock store) when an overflow actually happens.
ki.time.update_overflow(); ki.clock.update_overflow();
} }
// Called in a DPC when a timer has expired. // Called in a DPC when a timer has expired.
@@ -143,7 +143,7 @@ fn handle_expiry(_: ?*anyopaque) void {
const cpu = percpu.local(); const cpu = percpu.local();
while (true) { while (true) {
const curtime = ke.time.read_time_nano(); const curtime = ke.clock.read_time();
cpu.lock.acquire_no_ipl(); cpu.lock.acquire_no_ipl();
@@ -162,7 +162,7 @@ fn handle_expiry(_: ?*anyopaque) void {
cpu.lock.release_no_ipl(); cpu.lock.release_no_ipl();
// Re-check in case the timer expired while we were here. // Re-check in case the timer expired while we were here.
const now = ke.time.read_time_nano(); const now = ke.clock.read_time();
if (next.deadline <= now or next.deadline - now <= std.time.ns_per_ms) { if (next.deadline <= now or next.deadline - now <= std.time.ns_per_ms) {
continue; // expired in the meantime, loop again continue; // expired in the meantime, loop again
} }
+5 -4
View File
@@ -4,7 +4,7 @@ const acpi = b.pl.acpi;
const mm = b.mm; const mm = b.mm;
const ke = b.ke; const ke = b.ke;
var hpet_timer: ke.TimeCounter = .{ var hpet_cs: ke.ClockSource = .{
.name = "HPET", .name = "HPET",
.quality = 50, .quality = 50,
.frequency = 0, // Filled at runtime .frequency = 0, // Filled at runtime
@@ -19,9 +19,10 @@ var hpet_regs: *volatile acpi.HpetRegs = undefined;
const femtos_per_s = 1_000_000_000_000_000; const femtos_per_s = 1_000_000_000_000_000;
pub fn init(hpet: *acpi.HpetTable) linksection(b.init) void { pub fn init(hpet: *acpi.HpetTable) linksection(b.init) void {
hpet_regs = @ptrFromInt(mm.p2v(hpet.base_address.address)); if (ke.clock.is_better_than(&hpet_cs)) return;
hpet_timer.frequency = femtos_per_s / (b.mmio_read(u64, @intFromPtr(&hpet_regs.general_capabilities)) >> 32); hpet_regs = @ptrFromInt(mm.p2v(hpet.base_address.address));
hpet_cs.frequency = femtos_per_s / (b.mmio_read(u64, @intFromPtr(&hpet_regs.general_capabilities)) >> 32);
// Disable timer. // Disable timer.
b.mmio_write(u64, @intFromPtr(&hpet_regs.general_configuration), 0); b.mmio_write(u64, @intFromPtr(&hpet_regs.general_configuration), 0);
@@ -32,7 +33,7 @@ pub fn init(hpet: *acpi.HpetTable) linksection(b.init) void {
// Enable timer. // Enable timer.
b.mmio_write(u64, @intFromPtr(&hpet_regs.general_configuration), 1); b.mmio_write(u64, @intFromPtr(&hpet_regs.general_configuration), 1);
ke.time.register_source(&hpet_timer); ke.clock.register_source(&hpet_cs);
} }
fn read_hpet() u64 { fn read_hpet() u64 {
+4 -2
View File
@@ -4,7 +4,7 @@ const acpi = b.pl.acpi;
const ke = b.ke; const ke = b.ke;
const arch = @import("arch"); const arch = @import("arch");
var pm_timer: ke.TimeCounter = .{ var pm_timer: ke.ClockSource = .{
.name = "ACPI", .name = "ACPI",
.quality = 25, .quality = 25,
.frequency = 3579545, // 3.579545 MHz .frequency = 3579545, // 3.579545 MHz
@@ -27,6 +27,8 @@ pub fn init(fadt: *acpi.Fadt) linksection(b.init) void {
return; return;
} }
if (ke.clock.is_better_than(&pm_timer)) return;
timer_gas.address_space_id = .SystemIo; timer_gas.address_space_id = .SystemIo;
timer_gas.address = fadt.pm_tmr_blk; timer_gas.address = fadt.pm_tmr_blk;
timer_gas.access_size = 3; timer_gas.access_size = 3;
@@ -46,7 +48,7 @@ pub fn init(fadt: *acpi.Fadt) linksection(b.init) void {
pm_timer.mask = std.math.maxInt(u32); pm_timer.mask = std.math.maxInt(u32);
} }
ke.time.register_source(&pm_timer); ke.clock.register_source(&pm_timer);
} }
fn read_timer() u64 { fn read_timer() u64 {
+2 -2
View File
@@ -160,7 +160,7 @@ fn lapic_calibrate(ms: u64) u64 {
write(.LvtTimer, (1 << 16)); write(.LvtTimer, (1 << 16));
write(.TimerInitialCount, std.math.maxInt(u32)); write(.TimerInitialCount, std.math.maxInt(u32));
ke.time.sleep(std.time.ns_per_ms * ms); ke.clock.sleep(std.time.ns_per_ms * ms);
const ticks = std.math.maxInt(u32) - read(.TimerCurrentCount); const ticks = std.math.maxInt(u32) - read(.TimerCurrentCount);
@@ -198,7 +198,7 @@ fn timer_init() linksection(r.init) void {
pub fn arm_timer(ns: r.Nanoseconds) void { pub fn arm_timer(ns: r.Nanoseconds) void {
if (amd64.cpu_features.tsc_deadline) { if (amd64.cpu_features.tsc_deadline) {
const now = amd64.rdtsc(); const now = amd64.rdtsc();
const deadline = now + (ns * tsc.tsc_timer.frequency / std.time.ns_per_s); const deadline = now + (ns * tsc.tsc.frequency / std.time.ns_per_s);
amd64.write_msr(.TscDeadline, deadline); amd64.write_msr(.TscDeadline, deadline);
return; return;
} }
+3 -2
View File
@@ -6,7 +6,7 @@ const mm = r.mm;
const ke = r.ke; const ke = r.ke;
var pvclock_src: kvm.PVClockSource = undefined; var pvclock_src: kvm.PVClockSource = undefined;
var pvclock_tc: ke.TimeCounter = .{ var pvclock: ke.ClockSource = .{
.name = "pvclock", .name = "pvclock",
.quality = 75, .quality = 75,
.frequency = std.time.ns_per_s, .frequency = std.time.ns_per_s,
@@ -17,9 +17,10 @@ var pvclock_tc: ke.TimeCounter = .{
}; };
pub fn init() linksection(r.init) void { pub fn init() linksection(r.init) void {
if (ke.clock.is_better_than(&pvclock)) return;
const page = mm.phys.alloc(); const page = mm.phys.alloc();
pvclock_src = kvm.PVClockSource.init(page, mm.p2v(page)); pvclock_src = kvm.PVClockSource.init(page, mm.p2v(page));
ke.time.register_source(&pvclock_tc); ke.clock.register_source(&pvclock);
} }
fn read_pvclock() u64 { fn read_pvclock() u64 {
+2 -2
View File
@@ -103,9 +103,9 @@ pub fn init() linksection(r.init) void {
// Send the INIT-SIPI-SIPI sequence to start the AP. // Send the INIT-SIPI-SIPI sequence to start the AP.
apic.send_init(apic_id); apic.send_init(apic_id);
ke.time.sleep(init_delay); ke.clock.sleep(init_delay);
apic.send_sipi(apic_id, 0x08); apic.send_sipi(apic_id, 0x08);
ke.time.sleep(sipi_delay); ke.clock.sleep(sipi_delay);
apic.send_sipi(apic_id, 0x08); apic.send_sipi(apic_id, 0x08);
} }
+10 -10
View File
@@ -6,7 +6,7 @@ const ke = r.ke;
const log = std.log.scoped(.@"amd64/tsc"); const log = std.log.scoped(.@"amd64/tsc");
pub var tsc_timer: ke.TimeCounter = .{ pub var tsc: ke.ClockSource = .{
.name = "TSC", .name = "TSC",
.quality = 100, .quality = 100,
.frequency = 0, .frequency = 0,
@@ -21,7 +21,7 @@ pub fn init() linksection(r.init) void {
if (!features.invariant_tsc) { if (!features.invariant_tsc) {
log.info("invariant TSC not supported (get a new PC), using fallback", .{}); log.info("invariant TSC not supported (get a new PC), using fallback", .{});
if (ke.time.best()) |best| { if (ke.clock.best()) |best| {
log.info("using {s} as timecounter source", .{best.name}); log.info("using {s} as timecounter source", .{best.name});
} }
return; return;
@@ -33,14 +33,14 @@ pub fn init() linksection(r.init) void {
if (cpuid_state.ebx != 0 and cpuid_state.ecx != 0 and cpuid_state.eax != 0) { if (cpuid_state.ebx != 0 and cpuid_state.ecx != 0 and cpuid_state.eax != 0) {
const tsc_freq = (cpuid_state.ebx / cpuid_state.eax) * cpuid_state.ecx; const tsc_freq = (cpuid_state.ebx / cpuid_state.eax) * cpuid_state.ecx;
log.info("TSC frequency determined via CPUID: {} Hz", .{tsc_freq}); log.info("TSC frequency determined via CPUID: {} Hz", .{tsc_freq});
tsc_timer.frequency = tsc_freq; tsc.frequency = tsc_freq;
ke.time.register_source(&tsc_timer); ke.clock.register_source(&tsc);
return; return;
} }
// Fallback to the other best time source to calibrate. // Fallback to the other best time source to calibrate.
const best = ke.time.best() orelse return; const best = ke.clock.best() orelse return;
const calib_cost_runs = 5; const calib_cost_runs = 5;
var calib_cost: u64 = 0; var calib_cost: u64 = 0;
@@ -48,7 +48,7 @@ pub fn init() linksection(r.init) void {
// Calculate how many ticks it costs to call tc_sleep. // Calculate how many ticks it costs to call tc_sleep.
for (0..calib_cost_runs) |_| { for (0..calib_cost_runs) |_| {
const start = amd64.rdtsc(); const start = amd64.rdtsc();
ke.time.sleep(0); ke.clock.sleep(0);
const end = amd64.rdtsc(); const end = amd64.rdtsc();
calib_cost += (end - start); calib_cost += (end - start);
} }
@@ -63,7 +63,7 @@ pub fn init() linksection(r.init) void {
for (0..runs) |i| { for (0..runs) |i| {
const start = amd64.rdtsc(); const start = amd64.rdtsc();
ke.time.sleep(calib_time); ke.clock.sleep(calib_time);
const end = amd64.rdtsc(); const end = amd64.rdtsc();
freqs[i] = (end - start - calib_cost) * (std.time.ns_per_s / calib_time); freqs[i] = (end - start - calib_cost) * (std.time.ns_per_s / calib_time);
} }
@@ -73,11 +73,11 @@ pub fn init() linksection(r.init) void {
var sum: u64 = 0; var sum: u64 = 0;
for (freqs[1 .. runs - 1]) |f| sum += f; for (freqs[1 .. runs - 1]) |f| sum += f;
tsc_timer.frequency = sum / (runs - 2); tsc.frequency = sum / (runs - 2);
ke.time.register_source(&tsc_timer); ke.clock.register_source(&tsc);
log.info("frequency calibrated using {s}: {}.{} MHz", .{ best.name, tsc_timer.frequency / 1_000_000, (tsc_timer.frequency % 1_000_000) / 1000 }); log.info("frequency calibrated using {s}: {}.{} MHz", .{ best.name, tsc.frequency / 1_000_000, (tsc.frequency % 1_000_000) / 1000 });
} }
fn read_tsc() u64 { fn read_tsc() u64 {
+2 -2
View File
@@ -39,7 +39,7 @@ fn tc_read() u64 {
return @intCast(ts.sec * std.time.ns_per_s + ts.nsec); return @intCast(ts.sec * std.time.ns_per_s + ts.nsec);
} }
var tc: ke.TimeCounter = .{ var tc: ke.ClockSource = .{
.read_count = tc_read, .read_count = tc_read,
.frequency = std.time.ns_per_s, .frequency = std.time.ns_per_s,
.name = "CLOCK_MONOTONIC", .name = "CLOCK_MONOTONIC",
@@ -59,7 +59,7 @@ pub fn init() void {
posix.sigaction(posix.SIG.ALRM, &act, null); posix.sigaction(posix.SIG.ALRM, &act, null);
ke.time.register_source(&tc); ke.clock.register_source(&tc);
} }
pub fn init_cpu() void { pub fn init_cpu() void {