mirror of
https://github.com/rdmsr/zag.git
synced 2026-10-31 07:18:22 +00:00
all: refactor coding style
This commit is contained in:
+22
-4
@@ -25,7 +25,12 @@ const ThreadFrame = extern struct {
|
||||
|
||||
extern fn asm_thread_entry() void;
|
||||
|
||||
extern fn do_context_switch(old: *ThreadContext, new: *ThreadContext, lock: *u8, switching: *bool) callconv(.c) void;
|
||||
extern fn do_context_switch(
|
||||
old: *ThreadContext,
|
||||
new: *ThreadContext,
|
||||
lock: *u8,
|
||||
switching: *bool,
|
||||
) callconv(.c) void;
|
||||
extern fn do_context_load(td: *ThreadContext) callconv(.c) void;
|
||||
|
||||
pub const ThreadContext = extern struct {
|
||||
@@ -38,7 +43,12 @@ pub const ThreadContext = extern struct {
|
||||
entry_fn(@ptrFromInt(arg));
|
||||
}
|
||||
|
||||
pub fn init(stack: r.VAddr, stack_size: usize, entry: *const fn (?*anyopaque) void, arg: ?*anyopaque) @This() {
|
||||
pub fn init(
|
||||
stack: r.VAddr,
|
||||
stack_size: usize,
|
||||
entry: *const fn (?*anyopaque) void,
|
||||
arg: ?*anyopaque,
|
||||
) @This() {
|
||||
var ctx: @This() = undefined;
|
||||
var sp: usize = stack + stack_size;
|
||||
|
||||
@@ -58,9 +68,17 @@ pub const ThreadContext = extern struct {
|
||||
return ctx;
|
||||
}
|
||||
|
||||
pub fn switch_to(self: *ThreadContext, new: *ThreadContext) callconv(.c) void {
|
||||
pub fn switch_to(
|
||||
self: *ThreadContext,
|
||||
new: *ThreadContext,
|
||||
) callconv(.c) void {
|
||||
const thread: *ke.Thread = @alignCast(@fieldParentPtr("context", self));
|
||||
do_context_switch(self, new, &thread.lock.inner.locked.raw, &thread.switching.raw);
|
||||
do_context_switch(
|
||||
self,
|
||||
new,
|
||||
&thread.lock.inner.locked.raw,
|
||||
&thread.switching.raw,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn load(self: *ThreadContext) callconv(.c) void {
|
||||
|
||||
+5
-1
@@ -40,7 +40,11 @@ pub fn CpuLocal(comptime T: type, comptime init: T) type {
|
||||
}
|
||||
|
||||
/// Wraps around CPU-local data with a designated symbol name.
|
||||
pub fn ExportedCpuLocal(comptime T: type, comptime init: T, comptime name: []const u8) type {
|
||||
pub fn ExportedCpuLocal(
|
||||
comptime T: type,
|
||||
comptime init: T,
|
||||
comptime name: []const u8,
|
||||
) type {
|
||||
const S = struct {
|
||||
var storage: T linksection(".data.percpu") = init;
|
||||
|
||||
|
||||
+8
-6
@@ -6,7 +6,7 @@ const ke = r.ke;
|
||||
const ki = ke.private;
|
||||
|
||||
/// Deferred Procedure Call (DPC) structure.
|
||||
/// Used for scheduling work to be done when IPL is lowered below `ke.Ipl.Dispatch`.
|
||||
/// Used for scheduling work to be done when IPL is lowered below `.Dispatch`.
|
||||
pub const Dpc = struct {
|
||||
/// Entry into per-CPU DPC queue
|
||||
link: rtl.List.Entry,
|
||||
@@ -78,7 +78,8 @@ fn dispatch_queue(cpu: u32) void {
|
||||
|
||||
ipl_cpu.ipl = .Dispatch;
|
||||
|
||||
// Mark as handled, this must be done before enabling interrupts or we will race.
|
||||
// Mark as handled, this must be done before enabling interrupts or we will
|
||||
// race.
|
||||
ki.ipl.clear_softint_pending(cpu, .Dispatch);
|
||||
|
||||
_ = ki.impl.enable_interrupts();
|
||||
@@ -100,8 +101,9 @@ fn dispatch_queue(cpu: u32) void {
|
||||
dpc = @fieldParentPtr("link", first_elem);
|
||||
dpc.inserted.store(false, .release);
|
||||
|
||||
// An interrupt which would enqueue this DPC could occur between loading the argument and calling the routine,
|
||||
// which is why we capture it here to ensure that we get the intended context.
|
||||
// An interrupt which would enqueue this DPC could occur between
|
||||
// loading the argument and calling the routine, which is why we capture
|
||||
// it here to ensure that we get the intended context.
|
||||
const arg = dpc.arg;
|
||||
|
||||
dpc_cpu.lock.release(ipl);
|
||||
@@ -148,8 +150,8 @@ fn dispatch_queue(cpu: u32) void {
|
||||
|
||||
/// Dispatch the DPC queue on `cpu`.
|
||||
pub fn dispatch(cpu: u32) void {
|
||||
// DPC processing is done at Ipl.Dispatch
|
||||
// We can't call lower/raise here because they might call us again recursively.
|
||||
// DPC processing is done at IPL Dispatch.
|
||||
// Don't call lower/raise here because they might call us again recursively.
|
||||
var mycpu = cpu;
|
||||
const old_ipl = ki.ipl.percpu.remote(mycpu).ipl;
|
||||
const int_state = ki.impl.disable_interrupts();
|
||||
|
||||
+5
-1
@@ -37,7 +37,11 @@ pub fn init(_: ?*anyopaque) linksection(r.init) void {
|
||||
ki.sched.percpu.local().current_thread = &thread0;
|
||||
ki.sched.percpu.local().idle_thread = &thread0;
|
||||
|
||||
std.log.info("Zag for {s} ({s}), cmdline is \"{?s}\"", .{ pl.name, arch.name, r.boot_info.cmdline });
|
||||
std.log.info("Zag for {s} ({s}), cmdline is \"{?s}\"", .{
|
||||
pl.name,
|
||||
arch.name,
|
||||
r.boot_info.cmdline,
|
||||
});
|
||||
|
||||
ex.init();
|
||||
ki.sched.idle(null);
|
||||
|
||||
+7
-3
@@ -35,7 +35,11 @@ const percpu = ke.CpuLocal(PerCpu, undefined);
|
||||
/// The function will be executed synchronously, i.e this will block until
|
||||
/// the function finishes running. The callback is called with the sender cpu
|
||||
/// and provided argument as arguments.
|
||||
pub fn unicast(cpu: u32, func: *const fn (u32, ?*anyopaque) void, arg: ?*anyopaque) void {
|
||||
pub fn unicast(
|
||||
cpu: u32,
|
||||
func: *const fn (u32, ?*anyopaque) void,
|
||||
arg: ?*anyopaque,
|
||||
) void {
|
||||
const ipl = ke.ipl.raise(.Dispatch);
|
||||
const curcpu = percpu.local();
|
||||
const remote = percpu.remote(cpu);
|
||||
@@ -93,8 +97,8 @@ pub fn ipi_handler() void {
|
||||
|
||||
while (true) {
|
||||
// Mark the current CPU as processing IPIs, if the CAS fails here,
|
||||
// then it's probably some kind of IPI that was sent to force us to check
|
||||
// software interrupts (like a rescheduling IPI).
|
||||
// then it's probably some kind of IPI that was sent to force us to
|
||||
// check software interrupts (like a rescheduling IPI).
|
||||
const val = curcpu.state.cmpxchgStrong(
|
||||
.Pending,
|
||||
.Running,
|
||||
|
||||
+15
-4
@@ -21,7 +21,11 @@ const DebugWriter = struct {
|
||||
};
|
||||
}
|
||||
|
||||
fn drain(_: *std.Io.Writer, data: []const []const u8, _: usize) std.Io.Writer.Error!usize {
|
||||
fn drain(
|
||||
_: *std.Io.Writer,
|
||||
data: []const []const u8,
|
||||
_: usize,
|
||||
) std.Io.Writer.Error!usize {
|
||||
var total_written: usize = 0;
|
||||
for (data) |slice| {
|
||||
for (slice) |byte| {
|
||||
@@ -37,7 +41,10 @@ const DebugWriter = struct {
|
||||
// Messages are on average 2^5 = 32 bytes.
|
||||
const avg_msg_size_bits = 5;
|
||||
|
||||
pub var ringbuffer = ki.log_ring.RingBuffer(config.log_buffer_shift, avg_msg_size_bits).init();
|
||||
pub var ringbuffer = ki.log_ring.RingBuffer(
|
||||
config.log_buffer_shift,
|
||||
avg_msg_size_bits,
|
||||
).init();
|
||||
|
||||
// Initialize manually because we need this ASAP.
|
||||
pub var event: ke.Event = .{
|
||||
@@ -46,7 +53,10 @@ pub var event: ke.Event = .{
|
||||
.signaled = 0,
|
||||
.type = .Notification,
|
||||
.waitblocks = .{
|
||||
.head = .{ .next = &event.hdr.waitblocks.head, .prev = &event.hdr.waitblocks.head },
|
||||
.head = .{
|
||||
.next = &event.hdr.waitblocks.head,
|
||||
.prev = &event.hdr.waitblocks.head,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -75,7 +85,8 @@ pub fn log(
|
||||
res.info.length = @truncate(required_len);
|
||||
|
||||
// Format the log message into the reserved buffer.
|
||||
res.buf = std.fmt.bufPrint(res.buf, scope_str ++ fmt ++ "\n", args) catch return;
|
||||
res.buf = std.fmt.bufPrint(res.buf, scope_str ++ fmt ++ "\n", args) catch
|
||||
return;
|
||||
|
||||
ringbuffer.publish(res);
|
||||
|
||||
|
||||
+17
-3
@@ -53,7 +53,13 @@ fn pcpu_init() linksection(r.init) void {
|
||||
|
||||
local.valid_states = @splat(.init(0));
|
||||
local.senders = .init(false);
|
||||
local.states = @splat(.{ .base = 0, .npages = 0, .state = .init(slot_free), .payload = undefined, .link = undefined });
|
||||
local.states = @splat(.{
|
||||
.base = 0,
|
||||
.npages = 0,
|
||||
.state = .init(slot_free),
|
||||
.payload = undefined,
|
||||
.link = undefined,
|
||||
});
|
||||
}
|
||||
|
||||
comptime {
|
||||
@@ -66,7 +72,12 @@ fn allocate_slot(cpu: *PerCpu) ?usize {
|
||||
for (0.., &cpu.states) |slot, *state| {
|
||||
if (state.state.load(.monotonic) != slot_free) continue;
|
||||
|
||||
if (state.state.cmpxchgStrong(slot_free, slot_reserved, .acquire, .monotonic) == null) {
|
||||
if (state.state.cmpxchgStrong(
|
||||
slot_free,
|
||||
slot_reserved,
|
||||
.acquire,
|
||||
.monotonic,
|
||||
) == null) {
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
@@ -167,7 +178,10 @@ pub fn submit(state: ShootdownState, target_mask: ke.CpuMask) !void {
|
||||
while (iter.next()) |bit| {
|
||||
if (bit == curcpu) continue;
|
||||
|
||||
_ = cpu.valid_states[bit].fetchOr(@as(u64, 1) << @truncate(slot), .release);
|
||||
_ = cpu.valid_states[bit].fetchOr(
|
||||
@as(u64, 1) << @truncate(slot),
|
||||
.release,
|
||||
);
|
||||
|
||||
ki.ipl.set_softint_pending(@truncate(bit), .Dispatch);
|
||||
|
||||
|
||||
+32
-17
@@ -77,7 +77,8 @@ pub const Tracker = struct {
|
||||
pub var full_scans: std.atomic.Value(usize) = .init(0);
|
||||
|
||||
/// Scan all CPUs and return the minimum observed value.
|
||||
/// If `should_wait` is true, this will spinloop (or block) until all CPUs have reached the given goal.
|
||||
/// If `should_wait` is true, this will spinloop (or block)
|
||||
/// until all CPUs have reached the given goal.
|
||||
fn scan(dom: *Domain, goal: Sequence, clock: Clock, should_wait: bool) Sequence {
|
||||
rtl.barrier.fence(.seq_cst);
|
||||
|
||||
@@ -162,7 +163,12 @@ fn scan(dom: *Domain, goal: Sequence, clock: Clock, should_wait: bool) Sequence
|
||||
break;
|
||||
}
|
||||
|
||||
ki.turnstile.block(ts, cpu, .{ .shared = &cpu.stalled }, .Exclusive);
|
||||
ki.turnstile.block(
|
||||
ts,
|
||||
cpu,
|
||||
.{ .shared = &cpu.stalled },
|
||||
.Exclusive,
|
||||
);
|
||||
ke.ipl.lower(ipl);
|
||||
|
||||
seq = cpu.stall_seq.load(.monotonic);
|
||||
@@ -256,8 +262,9 @@ pub fn advance(dom: *Domain) Sequence {
|
||||
|
||||
/// Pretend-advance the write sequence and return the value for use as a wait goal.
|
||||
/// It is guaranteed that all previous memory writes made by
|
||||
/// the calling thread are visible. The global clock isn't advanced unlike `advance`,
|
||||
/// it is only when commit() is called.
|
||||
/// the calling thread are visible.
|
||||
/// The global clock isn't advanced unlike `advance`, it is only when commit()
|
||||
/// is called.
|
||||
pub fn deferred_advance(dom: *Domain) Sequence {
|
||||
rtl.barrier.fence(.seq_cst);
|
||||
|
||||
@@ -267,7 +274,12 @@ pub fn deferred_advance(dom: *Domain) Sequence {
|
||||
/// Actually advance the global clock to a value previously returned by
|
||||
/// deferred_advance().
|
||||
pub fn deferred_advance_commit(dom: *Domain, seq: Sequence) void {
|
||||
_ = dom.clock.write_seq.cmpxchgStrong(seq - seq_incr, seq, .monotonic, .monotonic);
|
||||
_ = dom.clock.write_seq.cmpxchgStrong(
|
||||
seq - seq_incr,
|
||||
seq,
|
||||
.monotonic,
|
||||
.monotonic,
|
||||
);
|
||||
}
|
||||
|
||||
/// Enter a read section.
|
||||
@@ -276,18 +288,20 @@ fn enter_internal(dom: *Domain) Sequence {
|
||||
|
||||
// Store the currently observed write sequence into our CPU state.
|
||||
// Subsequent loads must not be re-ordered w.r.t to the store here.
|
||||
// On AMD64, we can simply use XADD as this is faster and provides the same guarantees,
|
||||
// on other architectures we have to rely on a full (seq_cst) fence.
|
||||
// The add operation works because cpu.current_seq is always only 0 (seq_invalid)
|
||||
// when we write to it.
|
||||
// On AMD64, we can simply use XADD as this is faster and provides the
|
||||
// same guarantees, on other architectures we have to rely on a full fence.
|
||||
// The add operation works because cpu.current_seq is always only 0
|
||||
// (seq_invalid) when we write to it.
|
||||
//
|
||||
// There can be a large pause between the load from the domain write sequence
|
||||
// and the store to the per-cpu state because of e.g an interrupt. This could lead
|
||||
// to a state where another CPU advances the global write sequence and current_seq
|
||||
// becomes lower than the global write sequence. This is not an issue with the ordering
|
||||
// in scan(), which also relies on a full memory barrier, as it is guaranteed that the
|
||||
// CPU was treated as inactive and cannot possibly hold a reference to anything the
|
||||
// poll declared reclaimable. See scan().
|
||||
// There can be a large pause between the load from the domain write
|
||||
// sequence and the store to the per-cpu state because of e.g an interrupt.
|
||||
// This could lead to a state where another CPU advances
|
||||
// the global write sequence and current_seq ends up lower
|
||||
// than the global write sequence.
|
||||
// This is not an issue with the ordering in scan(), which also relies
|
||||
// on a full memory barrier, as it is guaranteed that the CPU was treated as
|
||||
// inactive and cannot possibly hold a reference to anything the poll
|
||||
// declared reclaimable. See scan().
|
||||
std.debug.assert(cpu.current_seq.load(.monotonic) == seq_invalid);
|
||||
|
||||
const wr_seq = dom.clock.write_seq.load(.monotonic);
|
||||
@@ -434,7 +448,8 @@ pub fn exit_preempt(dom: *Domain, tracker: *Tracker) void {
|
||||
}
|
||||
|
||||
if (wake) {
|
||||
if (turnstile) |ts| ki.turnstile.wakeup(ts, .Exclusive, ts.waiters, null);
|
||||
if (turnstile) |ts|
|
||||
ki.turnstile.wakeup(ts, .Exclusive, ts.waiters, null);
|
||||
}
|
||||
|
||||
ki.turnstile.exit(cpu);
|
||||
|
||||
+28
-5
@@ -176,7 +176,12 @@ pub const QSpinLock = LockTemplate(struct {
|
||||
|
||||
pub fn acquire_no_ipl(self: *Self) void {
|
||||
// Fast path: try to acquire the lock.
|
||||
var v = self.data.val.cmpxchgStrong(0, locked_val, .acquire, .monotonic) orelse return;
|
||||
var v = self.data.val.cmpxchgStrong(
|
||||
0,
|
||||
locked_val,
|
||||
.acquire,
|
||||
.monotonic,
|
||||
) orelse return;
|
||||
|
||||
// Medium path: if only pending is set, the pending waiter is
|
||||
// in the process of clearing pending and setting `locked` to take
|
||||
@@ -229,7 +234,12 @@ pub const QSpinLock = LockTemplate(struct {
|
||||
}
|
||||
|
||||
pub fn try_acquire_no_ipl(self: *Self) bool {
|
||||
return self.data.val.cmpxchgStrong(0, locked_val, .acquire, .monotonic) == null;
|
||||
return self.data.val.cmpxchgStrong(
|
||||
0,
|
||||
locked_val,
|
||||
.acquire,
|
||||
.monotonic,
|
||||
) == null;
|
||||
}
|
||||
|
||||
pub fn is_locked(self: *Self) bool {
|
||||
@@ -247,7 +257,12 @@ pub const QSpinLock = LockTemplate(struct {
|
||||
if (idx >= 4) {
|
||||
// Exhausted all indices, spin.
|
||||
while (true) {
|
||||
_ = self.data.val.cmpxchgWeak(0, locked_val, .acquire, .monotonic) orelse break;
|
||||
_ = self.data.val.cmpxchgWeak(
|
||||
0,
|
||||
locked_val,
|
||||
.acquire,
|
||||
.monotonic,
|
||||
) orelse break;
|
||||
std.atomic.spinLoopHint();
|
||||
}
|
||||
|
||||
@@ -266,7 +281,10 @@ pub const QSpinLock = LockTemplate(struct {
|
||||
// Store cpu + 1 so that we can distinguish from an empty tail field.
|
||||
rtl.barrier.fence(.release);
|
||||
|
||||
const tail: u16 = @bitCast(Tail{ .cpu = @intCast(ke.cpu.current() + 1), .idx = @intCast(idx) });
|
||||
const tail: u16 = @bitCast(Tail{
|
||||
.cpu = @intCast(ke.cpu.current() + 1),
|
||||
.idx = @intCast(idx),
|
||||
});
|
||||
const old: Tail = @bitCast(self.data.split.tail.swap(tail, .acq_rel));
|
||||
|
||||
if (old.cpu != 0) {
|
||||
@@ -293,7 +311,12 @@ pub const QSpinLock = LockTemplate(struct {
|
||||
if ((v & tail_mask) == @as(u32, tail) << 16) {
|
||||
// We are the only waiter in the queue.
|
||||
// Try to claim the lock and clear the tail atomically.
|
||||
if (self.data.val.cmpxchgStrong(v, locked_val, .acquire, .monotonic) == null) {
|
||||
if (self.data.val.cmpxchgStrong(
|
||||
v,
|
||||
locked_val,
|
||||
.acquire,
|
||||
.monotonic,
|
||||
) == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
+9
-2
@@ -123,12 +123,19 @@ pub const Thread = struct {
|
||||
smr_sections: rtl.List,
|
||||
|
||||
/// Initialize a thread.
|
||||
/// - `stack`: Address of the **base** of the stack on which the initial context for the thread is built
|
||||
/// - `stack`: Address of the base of the stack used by the thread.
|
||||
/// - `stack_size`: Size of the stack
|
||||
/// - `prio`: Base priority of the thread
|
||||
/// - `entry`: Entry point of the thread
|
||||
/// - `arg`: Extraneous argument to be passed to `entry`
|
||||
pub fn init(thread: *Thread, stack: r.VAddr, stack_size: usize, prio: u8, entry: *const fn (?*anyopaque) void, arg: ?*anyopaque) void {
|
||||
pub fn init(
|
||||
thread: *Thread,
|
||||
stack: r.VAddr,
|
||||
stack_size: usize,
|
||||
prio: u8,
|
||||
entry: *const fn (?*anyopaque) void,
|
||||
arg: ?*anyopaque,
|
||||
) void {
|
||||
thread.* = .{
|
||||
.context = .init(stack, stack_size, entry, arg),
|
||||
.lock = .init(),
|
||||
|
||||
+6
-6
@@ -18,11 +18,9 @@ pub const TimeCounter = struct {
|
||||
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`.
|
||||
/// Binary scaling exponent for fast ns conversion. See `ticks_to_ns`.
|
||||
p: u64,
|
||||
/// Scaled nanoseconds-per-tick multiplier for fast ns conversion (0 if unused).
|
||||
/// See `ticks_to_ns`.
|
||||
/// Scaled for fast ns conversion (0 if unused). See `ticks_to_ns`.
|
||||
n: u64,
|
||||
};
|
||||
|
||||
@@ -59,10 +57,12 @@ fn ticks_to_ns(tc: *TimeCounter, count: u64) u64 {
|
||||
|
||||
/// Register a time-keeping source.
|
||||
/// The TimeCounter 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 {
|
||||
// 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)) {
|
||||
tc.p = std.math.log2_int_ceil(u64, tc.frequency);
|
||||
tc.n = (@as(u64, std.time.ns_per_s) << @intCast(tc.p)) / tc.frequency;
|
||||
|
||||
+28
-7
@@ -1,5 +1,6 @@
|
||||
//! Timer object implementation.
|
||||
//! Timer objects are useful when one wants to wait a fixed amount of time for an event to occur.
|
||||
//! Timer objects are useful when one wants to wait a given amount of time for
|
||||
//! an event to occur.
|
||||
const std = @import("std");
|
||||
const rtl = @import("rtl");
|
||||
const r = @import("root");
|
||||
@@ -70,7 +71,12 @@ pub fn set(timer: *Timer, time: r.Nanoseconds, opts: Options) void {
|
||||
|
||||
const cpu = percpu.local();
|
||||
|
||||
if (timer.state.cmpxchgStrong(.Stopped, .Pending, .acquire, .monotonic) != null) {
|
||||
if (timer.state.cmpxchgStrong(
|
||||
.Stopped,
|
||||
.Pending,
|
||||
.acquire,
|
||||
.monotonic,
|
||||
) != null) {
|
||||
// Could not set the timer.
|
||||
return;
|
||||
}
|
||||
@@ -107,7 +113,12 @@ pub fn cancel(timer: *Timer) void {
|
||||
const ipl = timer.hdr.lock.acquire();
|
||||
defer timer.hdr.lock.release(ipl);
|
||||
|
||||
if (timer.state.cmpxchgStrong(.Pending, .Stopped, .acquire, .monotonic) != null) {
|
||||
if (timer.state.cmpxchgStrong(
|
||||
.Pending,
|
||||
.Stopped,
|
||||
.acquire,
|
||||
.monotonic,
|
||||
) != null) {
|
||||
// Something changed, it probably expired.
|
||||
return;
|
||||
}
|
||||
@@ -137,7 +148,8 @@ pub fn cmp_timer(a: *rtl.pairing_heap.Node, b_: *rtl.pairing_heap.Node) std.math
|
||||
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.
|
||||
// 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();
|
||||
}
|
||||
|
||||
@@ -161,13 +173,17 @@ fn handle_expiry(_: *ke.Dpc, _: ?*anyopaque) void {
|
||||
|
||||
// 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.value > curtime.value and timer.deadline.value - curtime.value > std.time.ns_per_ms) {
|
||||
if (timer.deadline.value > curtime.value and
|
||||
timer.deadline.value - curtime.value > 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();
|
||||
if (next.deadline.value <= now.value or next.deadline.value - now.value <= std.time.ns_per_ms) {
|
||||
if (next.deadline.value <= now.value or
|
||||
next.deadline.value - now.value <= std.time.ns_per_ms)
|
||||
{
|
||||
continue; // expired in the meantime, loop again
|
||||
}
|
||||
|
||||
@@ -176,7 +192,12 @@ fn handle_expiry(_: *ke.Dpc, _: ?*anyopaque) void {
|
||||
return;
|
||||
}
|
||||
|
||||
if (timer.state.cmpxchgStrong(.Pending, .Running, .acquire, .monotonic) != null) {
|
||||
if (timer.state.cmpxchgStrong(
|
||||
.Pending,
|
||||
.Running,
|
||||
.acquire,
|
||||
.monotonic,
|
||||
) != null) {
|
||||
// Timer must've been canceled.
|
||||
cpu.lock.release_no_ipl();
|
||||
continue;
|
||||
|
||||
+59
-56
@@ -16,7 +16,11 @@ const Entry = struct {
|
||||
},
|
||||
};
|
||||
|
||||
pub fn Tunable(comptime T: type, comptime default: T, comptime name: []const u8) type {
|
||||
pub fn Tunable(
|
||||
comptime T: type,
|
||||
comptime default: T,
|
||||
comptime name: []const u8,
|
||||
) type {
|
||||
return struct {
|
||||
var storage: T = default;
|
||||
|
||||
@@ -42,66 +46,65 @@ pub fn Tunable(comptime T: type, comptime default: T, comptime name: []const u8)
|
||||
};
|
||||
}
|
||||
|
||||
fn store_int(comptime T: type, addr: *anyopaque, value: anytype) bool {
|
||||
const ptr: *T = @ptrCast(@alignCast(addr));
|
||||
ptr.* = std.math.cast(T, value) orelse return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
fn apply_num(
|
||||
comptime ValueT: type,
|
||||
cmdline: []const u8,
|
||||
entry: *const Entry,
|
||||
info: std.builtin.Type.Int,
|
||||
) void {
|
||||
const value = rtl.cmdline.get_number(ValueT, cmdline, entry.name) catch |e| {
|
||||
if (e == error.Format) {
|
||||
std.log.warn(
|
||||
"Invalid number value for '{s}', falling back.",
|
||||
.{entry.name},
|
||||
);
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
const sign = @typeInfo(ValueT).int.signedness;
|
||||
|
||||
_ = switch (info.bits) {
|
||||
8 => store_int(std.meta.Int(sign, 8), entry.address, value),
|
||||
16 => store_int(std.meta.Int(sign, 16), entry.address, value),
|
||||
32 => store_int(std.meta.Int(sign, 32), entry.address, value),
|
||||
64 => store_int(std.meta.Int(sign, 64), entry.address, value),
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
fn apply_bool(cmdline: []const u8, entry: *const Entry) void {
|
||||
const value = rtl.cmdline.get_string(cmdline, entry.name) orelse return;
|
||||
const ptr: *bool = @ptrCast(@alignCast(entry.address));
|
||||
|
||||
if (std.mem.eql(u8, value, "true")) {
|
||||
ptr.* = true;
|
||||
} else if (std.mem.eql(u8, value, "false")) {
|
||||
ptr.* = false;
|
||||
} else {
|
||||
std.log.warn(
|
||||
"Invalid cmdline boolean value for tunable '{s}'",
|
||||
.{entry.name},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init() void {
|
||||
const cmdline = r.boot_info.cmdline orelse return;
|
||||
|
||||
const elems = set.elems();
|
||||
|
||||
for (elems) |entry| {
|
||||
for (set.elems()) |entry| {
|
||||
switch (entry.type) {
|
||||
.num => |info| {
|
||||
const a = entry.address;
|
||||
|
||||
switch (info.signedness) {
|
||||
.unsigned => {
|
||||
const value = rtl.cmdline.get_number(u64, cmdline, entry.name) catch |e| {
|
||||
if (e == error.Format) {
|
||||
std.log.warn("Invalid number value for '{s}', falling back.", .{entry.name});
|
||||
}
|
||||
continue;
|
||||
};
|
||||
|
||||
switch (info.bits) {
|
||||
8 => (@as(*u8, @ptrCast(@alignCast(a)))).* = std.math.cast(u8, value) orelse continue,
|
||||
16 => (@as(*u16, @ptrCast(@alignCast(a)))).* = std.math.cast(u16, value) orelse continue,
|
||||
32 => (@as(*u32, @ptrCast(@alignCast(a)))).* = std.math.cast(u32, value) orelse continue,
|
||||
64 => (@as(*u64, @ptrCast(@alignCast(a)))).* = value,
|
||||
else => unreachable,
|
||||
}
|
||||
},
|
||||
|
||||
.signed => {
|
||||
const value = rtl.cmdline.get_number(i64, cmdline, entry.name) catch |e| {
|
||||
if (e == error.Format) {
|
||||
std.log.warn("Invalid number value for '{s}', falling back.", .{entry.name});
|
||||
}
|
||||
|
||||
continue;
|
||||
};
|
||||
|
||||
switch (info.bits) {
|
||||
8 => (@as(*i8, @ptrCast(@alignCast(a)))).* = std.math.cast(i8, value) orelse continue,
|
||||
16 => (@as(*i16, @ptrCast(@alignCast(a)))).* = std.math.cast(i16, value) orelse continue,
|
||||
32 => (@as(*i32, @ptrCast(@alignCast(a)))).* = std.math.cast(i32, value) orelse continue,
|
||||
64 => (@as(*i64, @ptrCast(@alignCast(a)))).* = value,
|
||||
else => unreachable,
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
.bool => {
|
||||
const value = rtl.cmdline.get_string(cmdline, entry.name) orelse continue;
|
||||
const ptr: *bool = @ptrCast(@alignCast(entry.address));
|
||||
|
||||
if (std.mem.eql(u8, value, "true")) {
|
||||
ptr.* = true;
|
||||
} else if (std.mem.eql(u8, value, "false")) {
|
||||
ptr.* = false;
|
||||
} else {
|
||||
std.log.warn("Invalid cmdline boolean value for tunable '{s}'", .{entry.name});
|
||||
}
|
||||
.num => |info| switch (info.signedness) {
|
||||
.unsigned => apply_num(u64, cmdline, entry, info),
|
||||
.signed => apply_num(i64, cmdline, entry, info),
|
||||
},
|
||||
.bool => apply_bool(cmdline, entry),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
-5
@@ -314,7 +314,8 @@ fn propagate(curtd: *ke.Thread, root: *Chain) void {
|
||||
// and they are forbidden to explicitly block.
|
||||
var it = targets(ts);
|
||||
while (it.next()) |t| {
|
||||
if (t.thread == curtd) @panic("turnstile: cycle in blocking chain");
|
||||
if (t.thread == curtd)
|
||||
@panic("turnstile: cycle in blocking chain");
|
||||
t.thread.lock.acquire_no_ipl();
|
||||
_ = donate_to(t.boost, t.thread, donate);
|
||||
t.thread.lock.release_no_ipl();
|
||||
@@ -384,7 +385,12 @@ pub fn exit(obj: *const anyopaque) void {
|
||||
/// This must be called with the appropriate turnstile chain lock held, and
|
||||
/// returns with the chain lock released.
|
||||
/// IPL is kept as before on return.
|
||||
pub fn block(turnstile: ?*Turnstile, obj: *anyopaque, own: Ownership, queue: Queue) void {
|
||||
pub fn block(
|
||||
turnstile: ?*Turnstile,
|
||||
obj: *anyopaque,
|
||||
own: Ownership,
|
||||
queue: Queue,
|
||||
) void {
|
||||
const chain = chain_for(obj);
|
||||
const curtd = ki.sched.percpu.local().current_thread.?;
|
||||
const ipl = ke.ipl.current();
|
||||
@@ -441,7 +447,8 @@ pub fn block(turnstile: ?*Turnstile, obj: *anyopaque, own: Ownership, queue: Que
|
||||
|
||||
// Now block on the event.
|
||||
_ = ke.ipl.lower(.Passive);
|
||||
_ = ke.wait.wait_one(&waiter.event.hdr, "turnstile:waiter", .{}) catch unreachable;
|
||||
_ = ke.wait.wait_one(&waiter.event.hdr, "turnstile:waiter", .{}) catch
|
||||
unreachable;
|
||||
_ = ke.ipl.raise(ipl);
|
||||
}
|
||||
|
||||
@@ -464,7 +471,10 @@ pub fn wakeup(ts: *Turnstile, queue: Queue, count: usize, new_owner: ?*ke.Thread
|
||||
} else {
|
||||
for (0..count) |_| {
|
||||
if (ts.queues[queue_idx].is_empty()) break;
|
||||
const waiter: *Waiter = @fieldParentPtr("link", ts.queues[queue_idx].first());
|
||||
const waiter: *Waiter = @fieldParentPtr(
|
||||
"link",
|
||||
ts.queues[queue_idx].first(),
|
||||
);
|
||||
dequeue(ts, waiter.thread);
|
||||
}
|
||||
if (ts.waiters > 0) ts.detach();
|
||||
@@ -487,7 +497,8 @@ fn dequeue(ts: *Turnstile, td: *ke.Thread) void {
|
||||
ts.next_free = free.next_free;
|
||||
free.next_free = null;
|
||||
} else {
|
||||
// Last waiter, pull the turnstile off the chain and keep it for ourselves.
|
||||
// Last waiter, pull the turnstile off the chain
|
||||
// and keep it for ourselves.
|
||||
ts.link.remove();
|
||||
ts.reset();
|
||||
}
|
||||
|
||||
+48
-19
@@ -13,7 +13,8 @@ pub const DispatchHeader = struct {
|
||||
pub const Type = enum {
|
||||
/// When signaled, `signaled` is kept high and all waiters are woken up.
|
||||
Notification,
|
||||
/// When signaled, `signaled` is decreased until 0, and a single waiter is woken up.
|
||||
/// When signaled, `signaled` is decreased until 0, and a single waiter
|
||||
/// is woken up.
|
||||
Synchronization,
|
||||
/// Special type for queue objects.
|
||||
Queue,
|
||||
@@ -70,11 +71,11 @@ pub const DispatchHeader = struct {
|
||||
|
||||
pub const WaitBlock = struct {
|
||||
pub const Status = enum(u8) {
|
||||
/// WaitBlock is linked to an object as part of a thread that is waiting.
|
||||
/// Block is linked to an object as part of a thread that is waiting.
|
||||
Active,
|
||||
/// The wait associated with this WaitBlock has been satisfied (or timed out).
|
||||
/// The wait associated has been satisfied (or timed out).
|
||||
Inactive,
|
||||
/// A signal was delivered to the wait (this could be before it was committed).
|
||||
/// A signal was delivered to the wait.
|
||||
Signaled,
|
||||
};
|
||||
/// List linkage.
|
||||
@@ -114,8 +115,9 @@ pub fn wait_one(object: *DispatchHeader, reason: []const u8, opts: Options) !usi
|
||||
/// Returns the index of the object that was signaled, or an error
|
||||
/// in the case of timeout.
|
||||
/// If `timeout` is not provided, it will wait indefinitely.
|
||||
/// If `waitblocks` is specified, then the wait will use those waitblocks for the operation.
|
||||
/// Note that if `timeout` is provided, then one additional waitblock must be allocated.
|
||||
/// If `waitblocks` is specified, then the wait will use those waitblocks
|
||||
/// for the operation. Note that if `timeout` is provided, then one additional
|
||||
/// waitblock must be allocated.
|
||||
pub fn wait_any(objects: []*DispatchHeader, reason: []const u8, opts: Options) !usize {
|
||||
const ipl = ke.ipl.raise(.Dispatch);
|
||||
defer ke.ipl.lower(ipl);
|
||||
@@ -123,7 +125,9 @@ pub fn wait_any(objects: []*DispatchHeader, reason: []const u8, opts: Options) !
|
||||
const obj_count = objects.len;
|
||||
const has_timeout = opts.timeout != null;
|
||||
const blocks = opts.waitblocks orelse blk: {
|
||||
std.debug.assert(obj_count <= curtd.waitblocks.len - @intFromBool(has_timeout));
|
||||
std.debug.assert(
|
||||
obj_count <= curtd.waitblocks.len - @intFromBool(has_timeout),
|
||||
);
|
||||
break :blk &curtd.waitblocks;
|
||||
};
|
||||
|
||||
@@ -146,7 +150,12 @@ pub fn wait_any(objects: []*DispatchHeader, reason: []const u8, opts: Options) !
|
||||
|
||||
if (obj.can_satisfy()) {
|
||||
// Object was already signaled. Try consuming it.
|
||||
if (curtd.wait_status.cmpxchgStrong(.InProgress, .Satisfied, .acq_rel, .monotonic) == null) {
|
||||
if (curtd.wait_status.cmpxchgStrong(
|
||||
.InProgress,
|
||||
.Satisfied,
|
||||
.acq_rel,
|
||||
.monotonic,
|
||||
) == null) {
|
||||
obj.consume(curtd);
|
||||
satisfier = i;
|
||||
}
|
||||
@@ -161,7 +170,7 @@ pub fn wait_any(objects: []*DispatchHeader, reason: []const u8, opts: Options) !
|
||||
wb.object = obj;
|
||||
wb.thread = curtd;
|
||||
wb.status = .Active;
|
||||
// We are not satisfied yet, so add a waitblock to the object's waitblocks
|
||||
// We are not satisfied yet, so add a waitblock to the object.
|
||||
obj.waitblocks.insert_tail(&wb.link);
|
||||
}
|
||||
|
||||
@@ -197,9 +206,15 @@ pub fn wait_any(objects: []*DispatchHeader, reason: []const u8, opts: Options) !
|
||||
curtd.lock.acquire_no_ipl();
|
||||
|
||||
// Now try committing the wait.
|
||||
// While we're trying to commit the wait, the object locks have been released, and the state could therefore change.
|
||||
// While we're trying to commit the wait, the object locks
|
||||
// have been released, and the state could therefore change.
|
||||
// We need to re-check the state of the wait before actually blocking.
|
||||
if (curtd.wait_status.cmpxchgStrong(.InProgress, .Committed, .acq_rel, .monotonic) == null) {
|
||||
if (curtd.wait_status.cmpxchgStrong(
|
||||
.InProgress,
|
||||
.Committed,
|
||||
.acq_rel,
|
||||
.monotonic,
|
||||
) == null) {
|
||||
if (queue == null) {
|
||||
if (curtd.queue) |q| {
|
||||
ki.queue.signal_wait(q);
|
||||
@@ -236,7 +251,8 @@ pub fn wait_any(objects: []*DispatchHeader, reason: []const u8, opts: Options) !
|
||||
wb.link.remove();
|
||||
}
|
||||
if (wb.status == .Signaled) {
|
||||
// This waitblock was signaled, it must be the one that satisfied the wait.
|
||||
// This waitblock was signaled, it must be the one that satisfied
|
||||
// the wait.
|
||||
std.debug.assert(satisfier == null);
|
||||
satisfier = i;
|
||||
}
|
||||
@@ -245,7 +261,10 @@ pub fn wait_any(objects: []*DispatchHeader, reason: []const u8, opts: Options) !
|
||||
}
|
||||
|
||||
const final_sat = satisfier orelse return error.Timeout;
|
||||
return if (has_timeout and final_sat == timer_i) error.Timeout else final_sat;
|
||||
return if (has_timeout and final_sat == timer_i)
|
||||
error.Timeout
|
||||
else
|
||||
final_sat;
|
||||
}
|
||||
|
||||
/// Satisfy a wait on an object.
|
||||
@@ -267,19 +286,29 @@ pub fn satisfy_wait(obj: *DispatchHeader) void {
|
||||
wb.link.remove();
|
||||
|
||||
// Three cases may occur here:
|
||||
// 1. The wait was still preparing (status == .InProgress) and we interrupted it.
|
||||
// 2. The wait was already committed (status == .Committed) and we satisfied it.
|
||||
// 3. The wait was already satisfied by another object (status == .Satisfied).
|
||||
|
||||
// 1. The wait was still preparing (.InProgress) and we interrupted it.
|
||||
// 2. The wait was already committed (.Committed) and we satisfied it.
|
||||
// 3. The wait was already satisfied by another object (.Satisfied).
|
||||
//
|
||||
// 1.
|
||||
if (td.wait_status.cmpxchgStrong(.InProgress, .Satisfied, .acq_rel, .monotonic) == null) {
|
||||
if (td.wait_status.cmpxchgStrong(
|
||||
.InProgress,
|
||||
.Satisfied,
|
||||
.acq_rel,
|
||||
.monotonic,
|
||||
) == null) {
|
||||
// We interrupted the wait while it was being prepared.
|
||||
wb.status = .Signaled;
|
||||
obj.consume(td);
|
||||
}
|
||||
|
||||
// 2.
|
||||
else if (td.wait_status.cmpxchgStrong(.Committed, .Satisfied, .acq_rel, .monotonic) == null) {
|
||||
else if (td.wait_status.cmpxchgStrong(
|
||||
.Committed,
|
||||
.Satisfied,
|
||||
.acq_rel,
|
||||
.monotonic,
|
||||
) == null) {
|
||||
// We interrupted the wait while it was committed.
|
||||
// Wake the thread.
|
||||
wb.status = .Signaled;
|
||||
|
||||
Reference in New Issue
Block a user