mirror of
https://github.com/rdmsr/zag.git
synced 2026-10-27 13:58:20 +00:00
198 lines
5.4 KiB
Zig
198 lines
5.4 KiB
Zig
//! Timer object implementation.
|
|
//! Timer objects are useful when one wants to wait a fixed amount of time for an event to occur.
|
|
const std = @import("std");
|
|
const rtl = @import("rtl");
|
|
const r = @import("root");
|
|
const ke = r.ke;
|
|
const ki = ke.private;
|
|
const pl = r.pl;
|
|
|
|
const PerCpu = struct {
|
|
/// Heap of pending timers on this CPU.
|
|
timers: rtl.PairingHeap(.min, ki.timer.cmp_timer),
|
|
/// Lock over the timer heap.
|
|
lock: ke.SpinLock,
|
|
dpc: ke.Dpc,
|
|
};
|
|
|
|
pub const Timer = struct {
|
|
pub const State = enum(u8) {
|
|
/// The timer is currently being handled.
|
|
Running,
|
|
/// The timer is currently enqueued.
|
|
Pending,
|
|
/// The timer was stopped.
|
|
Stopped,
|
|
};
|
|
|
|
hdr: ki.wait.DispatchHeader,
|
|
|
|
/// Timer state.
|
|
state: std.atomic.Value(State),
|
|
/// When the timer is bound to expire.
|
|
deadline: r.Nanoseconds,
|
|
/// Attached DPC.
|
|
dpc: ?*ke.Dpc,
|
|
/// Intrusive pairing heap node.
|
|
node: rtl.pairing_heap.Node,
|
|
/// CPU this timer is enqueued on.
|
|
cpu: ?*PerCpu,
|
|
|
|
pub fn init(self: *Timer) void {
|
|
self.* = .{
|
|
.hdr = undefined,
|
|
.state = .init(.Stopped),
|
|
.deadline = 0,
|
|
.dpc = null,
|
|
.node = .{},
|
|
.cpu = null,
|
|
};
|
|
|
|
self.hdr.init(.Notification);
|
|
}
|
|
};
|
|
|
|
const percpu = ke.CpuLocal(PerCpu, .{
|
|
.timers = .init(),
|
|
.lock = .init(),
|
|
.dpc = .init(handle_expiry),
|
|
});
|
|
|
|
/// Start a timer with an expiration time.
|
|
/// A DPC that will be enqueued upon expiration can be passed.
|
|
pub fn set(timer: *Timer, time: r.Nanoseconds, dpc: ?*ke.Dpc) void {
|
|
const ipl = timer.hdr.lock.acquire();
|
|
defer timer.hdr.lock.release(ipl);
|
|
|
|
const cpu = percpu.local();
|
|
|
|
if (timer.state.cmpxchgStrong(.Stopped, .Pending, .acquire, .monotonic) != null) {
|
|
// Could not set the timer.
|
|
return;
|
|
}
|
|
|
|
cpu.lock.acquire_no_ipl();
|
|
defer cpu.lock.release_no_ipl();
|
|
|
|
// Initialize the timer.
|
|
timer.deadline = ke.time.read_time_nano() + time;
|
|
|
|
timer.cpu = cpu;
|
|
timer.dpc = dpc;
|
|
timer.hdr.signaled = 0;
|
|
|
|
cpu.timers.insert(&timer.node);
|
|
|
|
if (cpu.timers.root == &timer.node) {
|
|
// This is the earliest timer to expire, arm the hardware timer.
|
|
pl.arm_timer(time);
|
|
}
|
|
|
|
// Locks dropped
|
|
}
|
|
|
|
/// Cancel a timer.
|
|
pub fn cancel(timer: *Timer) void {
|
|
const old_state = timer.state.load(.monotonic);
|
|
|
|
if (old_state == .Stopped or old_state == .Running) {
|
|
// Already stopped or currently executing.
|
|
return;
|
|
}
|
|
|
|
const ipl = timer.hdr.lock.acquire();
|
|
defer timer.hdr.lock.release(ipl);
|
|
|
|
if (timer.state.cmpxchgStrong(.Pending, .Stopped, .acquire, .monotonic) != null) {
|
|
// Something changed, it probably expired.
|
|
return;
|
|
}
|
|
|
|
if (timer.cpu) |cpu| {
|
|
// Dequeue the timer.
|
|
cpu.lock.acquire_no_ipl();
|
|
|
|
cpu.timers.remove(&timer.node);
|
|
timer.cpu = null;
|
|
|
|
cpu.lock.release_no_ipl();
|
|
}
|
|
|
|
// Lock dropped
|
|
}
|
|
|
|
/// Compare two timers.
|
|
pub fn cmp_timer(a: *rtl.pairing_heap.Node, b_: *rtl.pairing_heap.Node) std.math.Order {
|
|
const timer_a: *Timer = @fieldParentPtr("node", a);
|
|
const timer_b: *Timer = @fieldParentPtr("node", b_);
|
|
|
|
return std.math.order(timer_a.deadline, timer_b.deadline);
|
|
}
|
|
|
|
/// Called by the platform on a clock interrupt.
|
|
pub fn clock() void {
|
|
ke.dpc.enqueue(&percpu.local().dpc, null);
|
|
// 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.
|
|
ki.time.update_overflow();
|
|
}
|
|
|
|
// Called in a DPC when a timer has expired.
|
|
fn handle_expiry(_: ?*anyopaque) void {
|
|
std.debug.assert(ki.ipl.current() == .Dispatch);
|
|
const cpu = percpu.local();
|
|
|
|
while (true) {
|
|
const curtime = ke.time.read_time_nano();
|
|
|
|
cpu.lock.acquire_no_ipl();
|
|
|
|
// Get the timer that expires the soonest.
|
|
const timer_node = cpu.timers.root orelse {
|
|
cpu.lock.release_no_ipl();
|
|
return;
|
|
};
|
|
|
|
const timer: *Timer = @fieldParentPtr("node", timer_node);
|
|
|
|
// If the timer expires more than 1ms in the future, consider it not yet due.
|
|
// Sub-millisecond differences are close enough to expire immediately.
|
|
if (timer.deadline > curtime and timer.deadline - curtime > std.time.ns_per_ms) {
|
|
const next = timer;
|
|
cpu.lock.release_no_ipl();
|
|
|
|
// Re-check in case the timer expired while we were here.
|
|
const now = ke.time.read_time_nano();
|
|
if (next.deadline <= now or next.deadline - now <= std.time.ns_per_ms) {
|
|
continue; // expired in the meantime, loop again
|
|
}
|
|
|
|
std.debug.assert(next.deadline > now);
|
|
pl.arm_timer(next.deadline - now);
|
|
return;
|
|
}
|
|
|
|
_ = cpu.timers.pop();
|
|
cpu.lock.release_no_ipl();
|
|
|
|
if (timer.state.cmpxchgStrong(.Pending, .Running, .acquire, .monotonic) != null) {
|
|
// Timer must've been canceled.
|
|
continue;
|
|
}
|
|
|
|
timer.hdr.lock.acquire_no_ipl();
|
|
|
|
// Set signaled.
|
|
timer.hdr.signaled = 1;
|
|
timer.cpu = null;
|
|
|
|
// Wake whomever was waiting on the timer.
|
|
ki.wait.satisfy_wait(&timer.hdr);
|
|
|
|
timer.state.store(.Stopped, .release);
|
|
|
|
if (timer.dpc) |dpc| ke.dpc.enqueue(dpc, null);
|
|
timer.hdr.lock.release_no_ipl();
|
|
}
|
|
}
|