diff --git a/build/linker-scripts/x86_64.lds b/build/linker-scripts/x86_64.lds index a9cad22..9303a5f 100644 --- a/build/linker-scripts/x86_64.lds +++ b/build/linker-scripts/x86_64.lds @@ -52,12 +52,6 @@ SECTIONS } :rodata - .percpu_init_array : { - __init_array_percpu_start = .; - KEEP(*(.percpu_init_array)) - __init_array_percpu_end = .; - } :rodata - /* Add a .note.gnu.build-id output section in case a build ID flag is added to the */ /* linker command. */ .note.gnu.build-id : { diff --git a/src/ex/init.zig b/src/ex/init.zig index c252756..5246fe0 100644 --- a/src/ex/init.zig +++ b/src/ex/init.zig @@ -6,6 +6,7 @@ const mm = r.mm; const ex = r.ex; const ps = r.ps; const exp = ex.private; +const rtl = @import("rtl"); // XXX const fbconsole = @import("../dev/fbconsole.zig"); diff --git a/src/kern/cpu.zig b/src/kern/cpu.zig index 0cbca1d..fbf9042 100644 --- a/src/kern/cpu.zig +++ b/src/kern/cpu.zig @@ -5,22 +5,16 @@ const r = @import("root"); const ke = r.ke; const ki = r.ke.private; -extern var __init_array_percpu_start: u8; -extern var __init_array_percpu_end: u8; - const id = CpuLocal(u32, 0); /// Initialize a CPU. Must be called on all CPUs. pub fn init_cpu(cpu_id: u32) void { - const start = @intFromPtr(&__init_array_percpu_start); - const end = @intFromPtr(&__init_array_percpu_end); - const count = (end - start) / @sizeOf(*const fn () void); - const funcs: [*]const *const fn () callconv(.c) void = @ptrFromInt(start); + const elems = r.percpu_init_set.elems(); id.local().* = cpu_id; - for (0..count) |i| { - funcs[i](); + for (elems) |func| { + func(); } } diff --git a/src/kern/dpc.zig b/src/kern/dpc.zig index 4aa2c7a..8a39869 100644 --- a/src/kern/dpc.zig +++ b/src/kern/dpc.zig @@ -40,12 +40,14 @@ const pcpu = ke.CpuLocal(PerCpu, .{ .queue = undefined, }); -export const dpc_percpu_init linksection(r.percpu_init) = &pcpu_init; - -fn pcpu_init() linksection(r.init) callconv(.c) void { +fn pcpu_init() linksection(r.init) void { pcpu.local().queue.init(); } +comptime { + _ = r.percpu_init_set.insert(&pcpu_init); +} + /// Enqueue a DPC. /// If the DPC is already enqueued, this function is a no-op. pub fn enqueue(dpc: *Dpc, arg: ?*anyopaque) void { diff --git a/src/kern/sched.zig b/src/kern/sched.zig index 005a7d8..8745a92 100644 --- a/src/kern/sched.zig +++ b/src/kern/sched.zig @@ -384,10 +384,8 @@ pub fn update_priority_locked(td: *ke.Thread, new_prio: u8) void { } -export const sched_percpu_init linksection(r.percpu_init) = &init_cpu; - /// Initialize a CPU for use by the scheduler. -fn init_cpu() linksection(r.init) callconv(.c) void { +fn init_cpu() linksection(r.init) void { var cpu = percpu.local(); cpu.* = .{ @@ -421,6 +419,10 @@ fn init_cpu() linksection(r.init) callconv(.c) void { } } +comptime { + _ = r.percpu_init_set.insert(&init_cpu); +} + /// Called on CPU 0 to initialize load balancing mechanisms. pub fn late_init() linksection(r.init) void { balance_timer.init(); diff --git a/src/kern/shootdown.zig b/src/kern/shootdown.zig index 479620b..bde9708 100644 --- a/src/kern/shootdown.zig +++ b/src/kern/shootdown.zig @@ -49,9 +49,7 @@ var sync_addr: r.VAddr = 0; var sync_count: usize = 0; var shootdown_lock: ke.SpinLock = .init(); -export const tlb_percpu_init linksection(r.percpu_init) = &pcpu_init; - -fn pcpu_init() linksection(r.init) callconv(.c) void { +fn pcpu_init() linksection(r.init) void { const local = percpu.local(); local.valid_states = @splat(.init(0)); @@ -59,6 +57,10 @@ fn pcpu_init() linksection(r.init) callconv(.c) void { local.states = @splat(.{ .base = 0, .npages = 0, .state = .init(slot_free), .payload = undefined, .link = undefined }); } +comptime { + _ = r.percpu_init_set.insert(&pcpu_init); +} + /// Allocate an invalidation state slot on this CPU. /// O(N) algorithm but N is tiny. fn allocate_slot(cpu: *PerCpu) ?usize { diff --git a/src/kern/spinlock.zig b/src/kern/spinlock.zig index b5cf7bf..0b715ad 100644 --- a/src/kern/spinlock.zig +++ b/src/kern/spinlock.zig @@ -124,10 +124,8 @@ const PerCpu = struct { const pcpu = ke.CpuLocal(PerCpu, undefined); -export const qspinlock_percpu_init linksection(r.percpu_init) = &init_cpu; - /// Initialize a CPU's MCS nodes. -fn init_cpu() linksection(r.init) callconv(.c) void { +fn init_cpu() linksection(r.init) void { var cpu = pcpu.local(); cpu.curr_idx = 0; @@ -137,6 +135,10 @@ fn init_cpu() linksection(r.init) callconv(.c) void { } } +comptime { + _ = r.percpu_init_set.insert(&init_cpu); +} + /// Queued spin lock implementation. /// See https://rdmsr.github.io/writing/qspinlocks pub const QSpinLock = LockTemplate(struct { diff --git a/src/kern/tunable.zig b/src/kern/tunable.zig index 398d744..bb3bccb 100644 --- a/src/kern/tunable.zig +++ b/src/kern/tunable.zig @@ -5,6 +5,8 @@ const rtl = @import("rtl"); const r = @import("root"); const pl = r.pl; +const set = rtl.LinkerSet("tunables", *const Entry); + const Entry = struct { name: []const u8, address: *anyopaque, @@ -18,7 +20,7 @@ pub fn Tunable(comptime T: type, comptime default: T, comptime name: []const u8) return struct { var storage: T = default; - const metadata: Entry linksection(".data.tunable") = + const metadata: Entry = .{ .name = name, .address = &storage, @@ -30,33 +32,32 @@ pub fn Tunable(comptime T: type, comptime default: T, comptime name: []const u8) }, }; + comptime { + _ = set.insert(&metadata); + } + pub fn load() T { - _ = metadata; return storage; } }; } -extern var __tunables_start: u8; -extern var __tunables_end: u8; - pub fn init(boot_info: *pl.BootInfo) void { const cmdline = boot_info.cmdline orelse return; - const start = @intFromPtr(&__tunables_start); - const end = @intFromPtr(&__tunables_end); - const count = (end - start) / @sizeOf(Entry); - const entries: [*]Entry = @ptrFromInt(start); + const elems = set.elems(); - for (0..count) |i| { - switch (entries[i].type) { + for (elems) |entry| { + switch (entry.type) { .num => |info| { - const a = entries[i].address; + const a = entry.address; switch (info.signedness) { .unsigned => { - const value = rtl.cmdline.get_number(u64, cmdline, entries[i].name) orelse { - std.log.warn("Invalid number value for '{s}', falling back.", .{entries[i].name}); + 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; }; @@ -70,8 +71,11 @@ pub fn init(boot_info: *pl.BootInfo) void { }, .signed => { - const value = rtl.cmdline.get_number(i64, cmdline, entries[i].name) orelse { - std.log.warn("Invalid number value for '{s}', falling back.", .{entries[i].name}); + 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; }; @@ -87,15 +91,15 @@ pub fn init(boot_info: *pl.BootInfo) void { }, .bool => { - const value = rtl.cmdline.get_string(cmdline, entries[i].name) orelse continue; - const ptr: *bool = @ptrCast(@alignCast(entries[i].address)); + 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}'", .{entries[i].name}); + std.log.warn("Invalid cmdline boolean value for tunable '{s}'", .{entry.name}); } }, } diff --git a/src/root.zig b/src/root.zig index 39fe227..c562503 100644 --- a/src/root.zig +++ b/src/root.zig @@ -11,7 +11,8 @@ pub const ps = @import("proc/root.zig"); const config = @import("config"); pub const init = ".text.init"; -pub const percpu_init = ".percpu_init_array"; + +pub const percpu_init_set = rtl.LinkerSet("percpu_init", *const fn () void); pub const percpu = ".data.percpu"; pub const VAddr = usize; diff --git a/src/rtl/cmdline.zig b/src/rtl/cmdline.zig index 2c00afd..5a573ee 100644 --- a/src/rtl/cmdline.zig +++ b/src/rtl/cmdline.zig @@ -17,13 +17,13 @@ pub fn get_string(cmdline: []const u8, key: []const u8) ?[]const u8 { } /// Get the number value of `key` in `cmdline`. -pub fn get_number(comptime T: type, cmdline: []const u8, key: []const u8) ?T { +pub fn get_number(comptime T: type, cmdline: []const u8, key: []const u8) !T { const str = get_string(cmdline, key); if (str) |s| { - const res = std.fmt.parseInt(T, s, 10) catch return null; + const res = std.fmt.parseInt(T, s, 10) catch return error.Format; return res; } - return null; + return error.NotPresent; } test "get_string() should return the value of a key" { diff --git a/src/rtl/linker_set.zig b/src/rtl/linker_set.zig new file mode 100644 index 0000000..29b5830 --- /dev/null +++ b/src/rtl/linker_set.zig @@ -0,0 +1,41 @@ +const std = @import("std"); + +/// Used for declaring global objects that get collected by the linker +/// into a `LinkerSet`. This works by declaring each set its own section +/// and abusing linker-generated `__start_` and `__stop_` symbols. +/// Each set stores pointers to objects, so `T` must be a pointer type. +pub fn LinkerSet(name: []const u8, comptime T: type) type { + if (@typeInfo(T) != .pointer) { + @compileError("LinkerSet type must be a pointer type"); + } + + return struct { + const start = @extern(*u8, .{ .name = "__start_set_" ++ name }); + const end = @extern(*u8, .{ .name = "__stop_set_" ++ name }); + + const Self = @This(); + + pub fn count() usize { + return (@intFromPtr(end) - @intFromPtr(start)) / @sizeOf(T); + } + + pub fn elems() []T { + const n = count(); + const arr: [*]T = @ptrCast(@alignCast(start)); + + return arr[0..n]; + } + + pub fn insert(elem: T) type { + const U = struct { + var storage: T linksection("set_" ++ name) = elem; + + comptime { + _ = &storage; + } + }; + + return U; + } + }; +} diff --git a/src/rtl/root.zig b/src/rtl/root.zig index 13ea806..3404729 100644 --- a/src/rtl/root.zig +++ b/src/rtl/root.zig @@ -14,6 +14,7 @@ pub const AVLTree = @import("avl.zig").AVLTree; pub const BitMap = @import("bitmap.zig").BitMap; pub const AtomicBitMap = @import("bitmap.zig").AtomicBitMap; pub const HandoffList = @import("handoff.zig").HandoffList; +pub const LinkerSet = @import("linker_set.zig").LinkerSet; /// Asserts that a given type `T` matches the schema declared by `I`. /// This includes public methods and fields.