build: switch to zonfig

This commit is contained in:
Loïc Grégoire
2026-04-24 10:35:53 -04:00
parent 36690a8149
commit a46782adca
18 changed files with 179 additions and 247 deletions
-81
View File
@@ -1,81 +0,0 @@
mainmenu "Zag Configuration"
choice
prompt "CPU Architecture"
default ARCH_AMD64
config ARCH_AMD64
bool "amd64"
help
Enable this option to select the AMD64 (x86_64) architecture.
config ARCH_AARCH64
bool "aarch64"
help
Enable this option to select the AARCH64 architecture.
config ARCH_UM
bool "User-Mode"
help
Enable this option to select the User-Mode (UM) architecture.
endchoice
choice
prompt "Bootloader support"
depends on (!ARCH_UM)
default BOOT_LIMINE
config BOOT_LIMINE
depends on (ARCH_AMD64 || ARCH_AARCH64)
bool "Limine"
help
Enable this option to select support for the Limine bootloader.
endchoice
menu "Kernel Options"
config SCHED_TIMESLICE
int "Scheduler time slice"
default 10
help
This option sets the default time slice (in milliseconds) used by the scheduler.
config BALANCE_INTERVAL
int "Load balancing interval"
default 1000
help
This option sets the rate (in milliseconds) at which thread load balancing is perfomed.
config NCPUS
int "Maximal number of CPUs"
default 64
help
This option sets the maximum number of CPUs the kernel can support.
config LOG_BUFFER_SHIFT
int "Log buffer size shift"
default 14
help
This option sets the size of the kernel log buffer as a power of two. The actual size is calculated as 2 raised to the power of this value (i.e., 2^LOG_BUFFER_SHIFT bytes).
menu "Memory Management"
menu "Kernel Allocator"
config SLAB_POISON
bool "Poison slab objects on allocation"
default y
config SLAB_POISON_CHECK
bool "Verify poison state on allocation"
default y
endmenu
endmenu
endmenu
+15 -22
View File
@@ -3,30 +3,19 @@ const builtin = @import("builtin");
const config = @import("build/config.zig"); const config = @import("build/config.zig");
const image = @import("build/image.zig"); const image = @import("build/image.zig");
const run = @import("build/run.zig"); const run = @import("build/run.zig");
const zonfig = @import("zonfig");
pub fn build(b: *std.Build) void { pub fn build(b: *std.Build) void {
const config_step = b.step("config", "Show the config menu"); const zonfig_dep = b.dependency("zonfig", .{});
config_step.dependOn(config.configStep(b));
// Parse the config zonfig.addConfigStep(b, zonfig_dep, "config.zig.zon", ".config.zig.zon");
// Parse the config or fail if file is not present
const cfg = config.parseConfig(b) catch |err| { const cfg = config.parseConfig(b) catch |err| {
if (err == error.FileNotFound) {
b.default_step.dependOn(
&b.addFail("No .config found, run `zig build config` first").step,
);
} else {
b.default_step.dependOn(
&b.addFail("Failed to parse .config").step,
);
}
return;
};
// Generate the zig config file we can import in the code
const config_wf = config.generateConfig(b, cfg) catch {
b.default_step.dependOn( b.default_step.dependOn(
&b.addFail("Failed to generate config.gen.zig").step, &b.addFail(b.fmt("Failed to parse config, have you run `zig build config`? (Error: {any})", .{err})).step,
); );
return; return;
}; };
@@ -40,10 +29,7 @@ pub fn build(b: *std.Build) void {
const optimize = b.standardOptimizeOption(.{}); const optimize = b.standardOptimizeOption(.{});
const config_module = b.createModule(.{ const config_module = zonfig.createConfigModule(b, zonfig_dep, "config.zig.zon", ".config.zig.zon");
.root_source_file = config_wf.getDirectory().path(b, "config.gen.zig"),
.optimize = optimize,
});
const ksyms_module = b.createModule(.{ const ksyms_module = b.createModule(.{
.root_source_file = b.path("src/ksyms.zig"), // Use the static wrapper .root_source_file = b.path("src/ksyms.zig"), // Use the static wrapper
@@ -233,6 +219,13 @@ fn addKernel(b: *std.Build, plat: config.Platform, optimize: std.builtin.Optimiz
kernel.linker_script = b.path(b.fmt("build/linker-scripts/{s}.lds", .{@tagName(plat.arch)})); kernel.linker_script = b.path(b.fmt("build/linker-scripts/{s}.lds", .{@tagName(plat.arch)}));
} else { } else {
const translate_c = b.addTranslateC(.{
.root_source_file = b.path("src/platform/um/c.h"),
.target = target,
.optimize = optimize,
});
kernel.root_module.addImport("c", translate_c.createModule());
kernel.linker_script = b.path("build/linker-scripts/uml.lds"); kernel.linker_script = b.path("build/linker-scripts/uml.lds");
kernel.root_module.link_libc = true; kernel.root_module.link_libc = true;
kernel.linker_allow_shlib_undefined = true; kernel.linker_allow_shlib_undefined = true;
+4 -1
View File
@@ -8,11 +8,14 @@
.url = "git+https://codeberg.org/Limine/Limine#v10.x-binary", .url = "git+https://codeberg.org/Limine/Limine#v10.x-binary",
.hash = "N-V-__8AAL1qTADWWlnz4JFHM8Pg-h0E3DlZ-Fr5aMZ-gatM", .hash = "N-V-__8AAL1qTADWWlnz4JFHM8Pg-h0E3DlZ-Fr5aMZ-gatM",
}, },
.ovmf = .{ .ovmf = .{
.url = "https://github.com/osdev0/edk2-ovmf-nightly/releases/download/nightly-20260318T020202Z/edk2-ovmf.tar.gz", .url = "https://github.com/osdev0/edk2-ovmf-nightly/releases/download/nightly-20260318T020202Z/edk2-ovmf.tar.gz",
.hash = "N-V-__8AAAAAjAwsopZIlbp4U6NN1Eh573uVdLTwl1mkFUug", .hash = "N-V-__8AAAAAjAwsopZIlbp4U6NN1Eh573uVdLTwl1mkFUug",
}, },
.zonfig = .{
.url = "git+https://github.com/rdmsr/zonfig?ref=master#baabe5559e41cadffc064ea2ff41ca91a40f4fcb",
.hash = "zonfig-0.1.0-V0D8-VtHAQC7JI19yaLkjXcdO20hg2oezkDrhxpH-nwo",
},
}, },
.paths = .{ .paths = .{
+27 -77
View File
@@ -1,7 +1,6 @@
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin"); const builtin = @import("builtin");
const zonfig = @import("zonfig");
pub const Config = std.StringHashMap([]const u8);
pub const Bootloader = enum { pub const Bootloader = enum {
Limine, Limine,
@@ -14,88 +13,39 @@ pub const Platform = struct {
bootloader: ?Bootloader = null, bootloader: ?Bootloader = null,
}; };
const ArchEnum = enum {
amd64,
aarch64,
um,
};
const BootloaderEnum = enum {
limine,
};
pub const Config = struct {
arch: ArchEnum,
bootloader: BootloaderEnum,
};
pub fn parseConfig(b: *std.Build) !Config { pub fn parseConfig(b: *std.Build) !Config {
const file = try std.Io.Dir.cwd().readFileAlloc(b.graph.io, ".config", b.allocator, std.Io.Limit.unlimited); const file = try std.Io.Dir.cwd().readFileAllocOptions(b.graph.io, ".config.zig.zon", b.allocator, std.Io.Limit.unlimited, .@"1", 0);
return std.zon.parse.fromSlice(Config, b.allocator, file, null, .{ .ignore_unknown_fields = true });
var lines = std.mem.splitScalar(u8, file, '\n');
var map = std.StringHashMap([]const u8).init(b.allocator);
while (lines.next()) |line| {
const trimmed = std.mem.trim(u8, line, " \r\t");
if (trimmed.len == 0 or std.mem.startsWith(u8, trimmed, "#")) {
continue;
}
var kv = std.mem.splitScalar(u8, trimmed, '=');
const key = kv.first();
const value = kv.rest();
try map.put(key, value);
}
return map;
}
pub fn configStep(b: *std.Build) *std.Build.Step {
return &b.addSystemCommand(&.{ "bash", "build/menuconfig" }).step;
}
fn isInteger(str: []const u8) bool {
_ = std.fmt.parseInt(i64, str, 10) catch return false;
return true;
}
pub fn generateConfig(b: *std.Build, config: Config) !*std.Build.Step.WriteFile {
var out = std.Io.Writer.Allocating.init(b.allocator);
var it = config.iterator();
while (it.next()) |entry| {
const key = entry.key_ptr.*;
const value = entry.value_ptr.*;
if (std.mem.eql(u8, value, "y")) {
try out.writer.print("pub const {s}: bool = true;\n", .{key});
} else if (isInteger(value)) {
try out.writer.print("pub const {s}: i64 = {s};\n", .{ key, value });
} else {
try out.writer.print("pub const {s} = \"{s}\";\n", .{ key, value });
}
}
const wf = b.addWriteFiles();
_ = wf.add("config.gen.zig", out.written());
_ = wf.addCopyFile(b.path(".config"), ".config");
return wf;
} }
pub fn getPlatform(config: Config) !Platform { pub fn getPlatform(config: Config) !Platform {
const keys = .{ var ret = Platform{
.{ "CONFIG_ARCH_AMD64", Platform{ .arch = .x86_64, .os = .freestanding } }, .arch = switch (config.arch) {
.{ "CONFIG_ARCH_AARCH64", Platform{ .arch = .aarch64, .os = .freestanding } }, .amd64 => .x86_64,
.{ "CONFIG_ARCH_RISCV64", Platform{ .arch = .riscv64, .os = .freestanding } }, .aarch64 => .aarch64,
.{ "CONFIG_ARCH_UM", Platform{ .arch = builtin.cpu.arch, .os = .linux } }, .um => builtin.cpu.arch,
}; },
.os = if (config.arch == .um) .linux else .freestanding,
var ret = blk: {
inline for (keys) |entry| {
if (config.get(entry[0])) |val| {
if (std.mem.eql(u8, val, "y")) break :blk entry[1];
}
}
return error.NoArchConfigured;
}; };
if (ret.os == .freestanding and (ret.arch == .x86_64 or ret.arch == .aarch64)) { if (ret.os == .freestanding and (ret.arch == .x86_64 or ret.arch == .aarch64)) {
ret.bootloader = blk: { ret.bootloader = switch (config.bootloader) {
if (config.get("CONFIG_BOOT_LIMINE")) |val| { .limine => .Limine,
if (std.mem.eql(u8, val, "y")) break :blk .Limine;
}
return error.NoBootloaderConfigured;
}; };
} }
-23
View File
@@ -1,23 +0,0 @@
#!/bin/bash
frontend="${FRONTEND:-kconfig-nconf}"
has_kconfig_frontends=true
if ! command -v kconfig-mconf &> /dev/null
then
has_kconfig_frontends=false
fi
if [ "$has_kconfig_frontends" = false ]; then
if ! command -v python3 -m menuconfig -h &> /dev/null
then
echo "No Kconfig frontend found. Please install kconfig-frontends or python3-kconfiglib"
exit 1
fi
python3 -m menuconfig
else
$frontend Kconfig
fi
+2 -1
View File
@@ -1,6 +1,7 @@
const std = @import("std"); const std = @import("std");
const config = @import("config.zig"); const config = @import("config.zig");
const image = @import("image.zig"); const image = @import("image.zig");
const builtin = @import("builtin");
const QemuArgs = struct { const QemuArgs = struct {
suffix: []u8, suffix: []u8,
@@ -57,7 +58,7 @@ pub fn addRun(b: *std.Build, kernel: *std.Build.Step.Compile, plat: config.Platf
.x86_64 => { .x86_64 => {
qemu.addArgs(&.{ "-serial", "stdio", "-m", "1G", "-smp", "4" }); qemu.addArgs(&.{ "-serial", "stdio", "-m", "1G", "-smp", "4" });
if (!debug) qemu.addArgs(&.{ "-enable-kvm", "-cpu", "host,+invtsc" }); if (!debug and builtin.target.os.tag == .linux and builtin.cpu.arch == .x86_64) qemu.addArgs(&.{ "-enable-kvm", "-cpu", "host,+invtsc" });
}, },
.aarch64 => qemu.addArgs(&.{ "-machine", "virt", "-cpu", "cortex-a57" }), .aarch64 => qemu.addArgs(&.{ "-machine", "virt", "-cpu", "cortex-a57" }),
.riscv64 => qemu.addArgs(&.{ "-machine", "virt" }), .riscv64 => qemu.addArgs(&.{ "-machine", "virt" }),
+97
View File
@@ -0,0 +1,97 @@
.{
.title = "Zag configuration",
.entries = .{
.{
.kind = .choice,
.key = "arch",
.label = "CPU Architecture",
.default = .{ .choice = "amd64" },
.options = .{
.{ .value = "amd64" },
.{ .value = "aarch64" },
.{ .value = "um", .label = "Usermode (Linux)" },
},
},
.{
.kind = .choice,
.key = "bootloader",
.label = "Bootloader",
.default = .{ .choice = "limine" },
.options = .{
.{ .value = "limine", .label = "Limine" },
},
.depends_on = .{
.not = .{
.key_eq = .{ .key = "arch", .value = "um" },
},
},
},
.{
.kind = .menu,
.label = "Kernel Options",
.entries = .{
.{
.kind = .int,
.key = "sched_timeslice",
.label = "Scheduler time slice",
.default = .{ .int = 10 },
.help = "This option sets the default time slice (in milliseconds) used by the scheduler.",
},
.{
.kind = .int,
.key = "sched_balance_interval",
.label = "Load balancing interval",
.default = .{ .int = 1000 },
.help = "This option sets the rate (in milliseconds) at which thread load balancing is performed.",
},
.{
.kind = .int,
.key = "ncpus",
.label = "Maximal number of CPUs",
.default = .{ .int = 64 },
.range = .{ .min = 1, .max = 16384 },
.help = "This option sets the maximum number of CPUs the kernel can support.",
},
.{
.kind = .int,
.key = "log_buffer_shift",
.label = "Log buffer size shift",
.default = .{ .int = 14 },
.help = "This option sets the size of the kernel log buffer as a power of two. The actual size is calculated as 2 raised to the power of this value (i.e., 2^LOG_BUFFER_SHIFT bytes).",
},
.{
.kind = .menu,
.label = "Memory Management Options",
.entries = .{
.{
.kind = .menu,
.label = "Kernel memory allocator",
.entries = .{
.{
.kind = .bool,
.key = "slab_poison",
.label = "Poison slab objects",
.default = .{ .bool = false },
.help = "This option enables poisoning of slab allocator objects. When enabled, freed objects are filled with a specific pattern to help detect use-after-free bugs. This can be useful for debugging memory corruption issues, but may have a performance impact.",
},
.{
.kind = .bool,
.key = "slab_check_poison",
.label = "Check poison on allocation",
.default = .{ .bool = false },
.help = "This option enables checking of the poison pattern when allocating objects from the slab allocator. When enabled, the allocator will verify that the poison pattern is intact before reusing a freed object. This can help detect use-after-free bugs where an object is accessed after being freed, but may have a performance impact.",
.depends_on = .{ .key = "slab_poison" },
},
},
},
},
},
},
},
},
}
+1 -1
View File
@@ -69,7 +69,7 @@ pub fn ExportedCpuLocal(comptime T: type, comptime init: T, comptime name: []con
/// Bitmask of CPUs. /// Bitmask of CPUs.
pub const CpuMask = struct { pub const CpuMask = struct {
const bits_per_word = @bitSizeOf(usize); const bits_per_word = @bitSizeOf(usize);
const num_words = (config.CONFIG_NCPUS + bits_per_word - 1) / bits_per_word; const num_words = (config.ncpus + bits_per_word - 1) / bits_per_word;
bits: [num_words]usize, bits: [num_words]usize,
+2 -2
View File
@@ -110,13 +110,13 @@ fn dispatch_queue(cpu: u32) void {
} }
if (sched_cpu.start_timer) { if (sched_cpu.start_timer) {
ke.timer.set(&sched_cpu.resched_timer, std.time.ns_per_ms * config.CONFIG_SCHED_TIMESLICE, &sched_cpu.resched_dpc); ke.timer.set(&sched_cpu.resched_timer, std.time.ns_per_ms * config.sched_timeslice, &sched_cpu.resched_dpc);
} }
if (sched_cpu.preemption_reason == .HigherPriority) { if (sched_cpu.preemption_reason == .HigherPriority) {
// Reload the quantum for the new thread. // Reload the quantum for the new thread.
ke.timer.cancel(&sched_cpu.resched_timer); ke.timer.cancel(&sched_cpu.resched_timer);
ke.timer.set(&sched_cpu.resched_timer, std.time.ns_per_ms * config.CONFIG_SCHED_TIMESLICE, &sched_cpu.resched_dpc); ke.timer.set(&sched_cpu.resched_timer, std.time.ns_per_ms * config.sched_timeslice, &sched_cpu.resched_dpc);
} }
sched_cpu.start_timer = false; sched_cpu.start_timer = false;
+1 -1
View File
@@ -37,7 +37,7 @@ const DebugWriter = struct {
// Messages are on average 2^5 = 32 bytes. // Messages are on average 2^5 = 32 bytes.
const avg_msg_size_bits = 5; const avg_msg_size_bits = 5;
pub var ringbuffer = ki.log_ring.RingBuffer(config.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();
pub fn log( pub fn log(
comptime level: std.log.Level, comptime level: std.log.Level,
+5 -6
View File
@@ -3,12 +3,11 @@ const config = @import("config");
const init_mod = @import("init.zig"); const init_mod = @import("init.zig");
pub const impl = if (@hasDecl(config, "CONFIG_ARCH_AMD64")) pub const impl = switch (config.arch) {
@import("amd64/impl.zig") .amd64 => @import("amd64/impl.zig"),
else if (@hasDecl(config, "CONFIG_ARCH_UM")) .um => @import("um/impl.zig"),
@import("um/impl.zig") else => @compileError("unsupported architecture"),
else };
@compileError("unsupported architecture");
pub const init = init_mod.init; pub const init = init_mod.init;
+3 -3
View File
@@ -96,7 +96,7 @@ const runqueues_n = 64;
const interactivity_threshold = 30; const interactivity_threshold = 30;
const scaling_factor = 50; const scaling_factor = 50;
const preempt_threshold = ke.Thread.Priority.realtime; const preempt_threshold = ke.Thread.Priority.realtime;
const balance_interval = @as(u64, config.CONFIG_BALANCE_INTERVAL); const balance_interval = @as(u64, config.sched_balance_interval);
const steal_threshold = 1; const steal_threshold = 1;
var balance_timer: ke.Timer = undefined; var balance_timer: ke.Timer = undefined;
@@ -211,7 +211,7 @@ pub fn clock(_: ?*anyopaque) void {
if (curtd.priority_class == .Batch) { if (curtd.priority_class == .Batch) {
// Update interactivity metrics. // Update interactivity metrics.
curtd.run_time += config.CONFIG_SCHED_TIMESLICE; curtd.run_time += config.sched_timeslice;
clamp_time(curtd); clamp_time(curtd);
recompute_priority(curtd); recompute_priority(curtd);
@@ -261,7 +261,7 @@ pub fn unblock(td: *ke.Thread) void {
td.sleep_time = (ke.time.read_time() - td.sleep_start) / std.time.ns_per_ms; td.sleep_time = (ke.time.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.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.
clamp_time(td); clamp_time(td);
recompute_priority(td); recompute_priority(td);
+5 -6
View File
@@ -8,11 +8,10 @@ pub const heap = @import("heap.zig");
pub const zone = @import("zone.zig"); pub const zone = @import("zone.zig");
pub const PMap = pmap.PMap; pub const PMap = pmap.PMap;
pub const impl = if (@hasDecl(config, "CONFIG_ARCH_AMD64")) pub const impl = switch (config.arch) {
@import("amd64/impl.zig") .amd64 => @import("amd64/impl.zig"),
else if (@hasDecl(config, "CONFIG_ARCH_UM")) .um => @import("um/impl.zig"),
@import("um/impl.zig") else => @compileError("unsupported architecture"),
else };
@compileError("unsupported architecture");
pub var kernel_pmap: PMap = undefined; pub var kernel_pmap: PMap = undefined;
+1 -1
View File
@@ -17,7 +17,7 @@ extern var AP_TRAMPOLINE_DATA: u8;
extern var __percpu_start: u8; extern var __percpu_start: u8;
extern var __percpu_end: u8; extern var __percpu_end: u8;
pub export var cpu_id_to_apic_id: [config.CONFIG_NCPUS]u32 = undefined; pub export var cpu_id_to_apic_id: [config.ncpus]u32 = undefined;
const start_stack = ke.ExportedCpuLocal(usize, 0, "ap_start_stack"); const start_stack = ke.ExportedCpuLocal(usize, 0, "ap_start_stack");
+5 -6
View File
@@ -3,12 +3,11 @@
const config = @import("config"); const config = @import("config");
pub const impl = if (@hasDecl(config, "CONFIG_ARCH_AMD64")) pub const impl = switch (config.arch) {
@import("pc/impl.zig") .amd64 => @import("pc/impl.zig"),
else if (@hasDecl(config, "CONFIG_ARCH_UM")) .um => @import("um/impl.zig"),
@import("um/impl.zig") else => @compileError("unsupported architecture"),
else };
@compileError("unsupported architecture");
pub const acpi = @import("acpi/root.zig"); pub const acpi = @import("acpi/root.zig");
+9
View File
@@ -0,0 +1,9 @@
#define _GNU_SOURCE 1
#include <SDL.h>
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <ucontext.h>
#include <unistd.h>
+1 -6
View File
@@ -3,12 +3,7 @@ const r = @import("root");
pub const std_options = r.std_options; pub const std_options = r.std_options;
pub const panic = r.panic; pub const panic = r.panic;
const c = @cImport({ const c = @import("c");
@cInclude("SDL.h");
@cInclude("signal.h");
@cInclude("pthread.h");
});
const std = @import("std"); const std = @import("std");
const ke = r.ke; const ke = r.ke;
const pl = r.pl; const pl = r.pl;
+1 -10
View File
@@ -10,16 +10,7 @@ pub const entry = @import("entry.zig");
const timer = @import("timer.zig"); const timer = @import("timer.zig");
pub const c = @cImport({ pub const c = @import("c");
@cDefine("_GNU_SOURCE", "1");
@cInclude("pthread.h");
@cInclude("sys/mman.h");
@cInclude("ucontext.h");
@cInclude("signal.h");
@cInclude("unistd.h");
@cInclude("sys/time.h");
@cInclude("sched.h");
});
pub const name = "User-Mode"; pub const name = "User-Mode";