mirror of
https://github.com/rdmsr/zag.git
synced 2026-10-28 14:08:22 +00:00
151 lines
4.8 KiB
Zig
151 lines
4.8 KiB
Zig
const std = @import("std");
|
|
const rtl = @import("rtl");
|
|
const r = @import("root");
|
|
const ke = r.ke;
|
|
const ki = ke.private;
|
|
|
|
/// Represents a clock source upon which the kernel can rely.
|
|
pub const ClockSource = struct {
|
|
/// Callback that reads the current count.
|
|
read_count: *const fn () u64,
|
|
/// Frequency of the timer in Hz.
|
|
frequency: u64,
|
|
/// Name of the timer.
|
|
name: []const u8,
|
|
/// Quality of the timer.
|
|
/// Used to choose between two ClockSources.
|
|
quality: i16,
|
|
/// Mask to apply to the counter value.
|
|
mask: u64,
|
|
/// Binary scaling exponent for fast ns conversion (0 if unused).
|
|
/// See `ticks_to_ns`.
|
|
p: u64,
|
|
/// Scaled nanoseconds-per-tick multiplier for fast ns conversion (0 if unused).
|
|
/// See `ticks_to_ns`.
|
|
n: u64,
|
|
};
|
|
|
|
var best_clock: ?*ClockSource = null;
|
|
|
|
const State = struct {
|
|
// Counter value at the time the current ClockSource was selected.
|
|
// Used as the baseline for elapsed time calculation.
|
|
initial_count: u64,
|
|
// Accumulated nanosecond offset from previous ClockSources and overflow events.
|
|
// Added to the current counter's contribution in `read_time`.
|
|
offset: u64,
|
|
};
|
|
|
|
// SeqLock-protected time state.
|
|
// Avoids a race on `initial_count` and `offset` with `update_overflow`.
|
|
var state = rtl.SeqLock(State).init(.{
|
|
.initial_count = 0,
|
|
.offset = 0,
|
|
});
|
|
|
|
// Convert a ClockSource's count to nanoseconds.
|
|
// 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.
|
|
// Falls back to a division-based conversion for narrower counters.
|
|
fn ticks_to_ns(cs: *ClockSource, count: u64) u64 {
|
|
if (cs.p != 0) {
|
|
const val: u128 = @as(u128, @intCast(count)) * cs.n;
|
|
return @intCast(val >> @intCast(cs.p));
|
|
}
|
|
|
|
return (count * std.time.ns_per_s) / cs.frequency;
|
|
}
|
|
|
|
/// Register a clock source.
|
|
/// 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.
|
|
pub fn register_source(cs: *ClockSource) void {
|
|
// 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.
|
|
if (cs.mask == std.math.maxInt(u64)) {
|
|
cs.p = std.math.log2_int_ceil(u64, cs.frequency);
|
|
cs.n = (@as(u64, std.time.ns_per_s) << @intCast(cs.p)) / cs.frequency;
|
|
} else {
|
|
cs.p = 0;
|
|
cs.n = 0;
|
|
}
|
|
|
|
// If the best TimeCounter changed, pass the baton:
|
|
// snapshot the old counter's elapsed time into `offset` and
|
|
// reset `initial_count` to the new counter's current value.
|
|
if (best_clock == null or cs.quality > best_clock.?.quality) {
|
|
const old_best = best_clock;
|
|
best_clock = cs;
|
|
|
|
if (old_best) |old| {
|
|
const old_count = old.read_count() & old.mask;
|
|
state.raw.offset = ticks_to_ns(old, old_count);
|
|
}
|
|
|
|
state.raw.initial_count = cs.read_count() & cs.mask;
|
|
}
|
|
}
|
|
|
|
/// Return the time elapsed since boot in nanoseconds.
|
|
pub fn read_time() r.Nanoseconds {
|
|
const cs = best_clock orelse return 0;
|
|
|
|
const s = state.load();
|
|
const curr_count = cs.read_count() & cs.mask;
|
|
const elapsed = (curr_count - s.initial_count) & cs.mask;
|
|
return s.offset + ticks_to_ns(cs, elapsed);
|
|
}
|
|
|
|
/// Return the best clock source.
|
|
pub fn best() ?*ClockSource {
|
|
return best_clock;
|
|
}
|
|
|
|
/// 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 {
|
|
const start = read_time();
|
|
|
|
while (true) {
|
|
var cur = read_time();
|
|
|
|
if (cur < start) continue;
|
|
|
|
if (cur - start >= ns) {
|
|
cur = read_time();
|
|
|
|
// Check again as we may have overflowed and read a bogus value.
|
|
// This is kinda hacky but oh well.
|
|
if (cur - start >= ns) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
std.atomic.spinLoopHint();
|
|
}
|
|
}
|
|
|
|
/// Called periodically to handle counter overflows.
|
|
pub fn update_overflow() void {
|
|
const cs = best_clock orelse return;
|
|
const s = state.load();
|
|
const cur_count = cs.read_count() & cs.mask;
|
|
|
|
if (s.initial_count > cur_count) {
|
|
// Counter has wrapped, use wrapping arithmetic to figure out by how much,
|
|
// then fold it into the offset and reset `initial_count`.
|
|
const overflowed_by = (cur_count -% s.initial_count) & cs.mask;
|
|
state.store(.{
|
|
.initial_count = cur_count,
|
|
.offset = s.offset + ticks_to_ns(cs, overflowed_by),
|
|
});
|
|
}
|
|
}
|