From a46782adcabe4b46fe57c9fb2667c25f23c97b9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Gr=C3=A9goire?= Date: Fri, 24 Apr 2026 10:35:53 -0400 Subject: [PATCH] build: switch to zonfig --- Kconfig | 81 ----------------------------- build.zig | 37 ++++++-------- build.zig.zon | 5 +- build/config.zig | 104 ++++++++++---------------------------- build/menuconfig | 23 --------- build/run.zig | 3 +- config.zig.zon | 97 +++++++++++++++++++++++++++++++++++ src/kern/cpu.zig | 2 +- src/kern/dpc.zig | 4 +- src/kern/log.zig | 2 +- src/kern/private.zig | 11 ++-- src/kern/sched.zig | 6 +-- src/mm/private.zig | 11 ++-- src/platform/pc/smp.zig | 2 +- src/platform/root.zig | 11 ++-- src/platform/um/c.h | 9 ++++ src/platform/um/entry.zig | 7 +-- src/platform/um/impl.zig | 11 +--- 18 files changed, 179 insertions(+), 247 deletions(-) delete mode 100644 Kconfig delete mode 100755 build/menuconfig create mode 100644 config.zig.zon create mode 100644 src/platform/um/c.h diff --git a/Kconfig b/Kconfig deleted file mode 100644 index e3ee526..0000000 --- a/Kconfig +++ /dev/null @@ -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 diff --git a/build.zig b/build.zig index 61337f5..851b779 100644 --- a/build.zig +++ b/build.zig @@ -3,30 +3,19 @@ const builtin = @import("builtin"); const config = @import("build/config.zig"); const image = @import("build/image.zig"); const run = @import("build/run.zig"); +const zonfig = @import("zonfig"); pub fn build(b: *std.Build) void { - const config_step = b.step("config", "Show the config menu"); - config_step.dependOn(config.configStep(b)); + const zonfig_dep = b.dependency("zonfig", .{}); - // 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| { - 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.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; }; @@ -40,10 +29,7 @@ pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); - const config_module = b.createModule(.{ - .root_source_file = config_wf.getDirectory().path(b, "config.gen.zig"), - .optimize = optimize, - }); + const config_module = zonfig.createConfigModule(b, zonfig_dep, "config.zig.zon", ".config.zig.zon"); const ksyms_module = b.createModule(.{ .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)})); } 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.root_module.link_libc = true; kernel.linker_allow_shlib_undefined = true; diff --git a/build.zig.zon b/build.zig.zon index 07fabb9..c3efaea 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -8,11 +8,14 @@ .url = "git+https://codeberg.org/Limine/Limine#v10.x-binary", .hash = "N-V-__8AAL1qTADWWlnz4JFHM8Pg-h0E3DlZ-Fr5aMZ-gatM", }, - .ovmf = .{ .url = "https://github.com/osdev0/edk2-ovmf-nightly/releases/download/nightly-20260318T020202Z/edk2-ovmf.tar.gz", .hash = "N-V-__8AAAAAjAwsopZIlbp4U6NN1Eh573uVdLTwl1mkFUug", }, + .zonfig = .{ + .url = "git+https://github.com/rdmsr/zonfig?ref=master#baabe5559e41cadffc064ea2ff41ca91a40f4fcb", + .hash = "zonfig-0.1.0-V0D8-VtHAQC7JI19yaLkjXcdO20hg2oezkDrhxpH-nwo", + }, }, .paths = .{ diff --git a/build/config.zig b/build/config.zig index 21040bb..98dce4a 100644 --- a/build/config.zig +++ b/build/config.zig @@ -1,7 +1,6 @@ const std = @import("std"); const builtin = @import("builtin"); - -pub const Config = std.StringHashMap([]const u8); +const zonfig = @import("zonfig"); pub const Bootloader = enum { Limine, @@ -14,88 +13,39 @@ pub const Platform = struct { 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 { - const file = try std.Io.Dir.cwd().readFileAlloc(b.graph.io, ".config", b.allocator, std.Io.Limit.unlimited); - - 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; + 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 }); } pub fn getPlatform(config: Config) !Platform { - const keys = .{ - .{ "CONFIG_ARCH_AMD64", Platform{ .arch = .x86_64, .os = .freestanding } }, - .{ "CONFIG_ARCH_AARCH64", Platform{ .arch = .aarch64, .os = .freestanding } }, - .{ "CONFIG_ARCH_RISCV64", Platform{ .arch = .riscv64, .os = .freestanding } }, - .{ "CONFIG_ARCH_UM", Platform{ .arch = builtin.cpu.arch, .os = .linux } }, - }; - - 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; + var ret = Platform{ + .arch = switch (config.arch) { + .amd64 => .x86_64, + .aarch64 => .aarch64, + .um => builtin.cpu.arch, + }, + .os = if (config.arch == .um) .linux else .freestanding, }; if (ret.os == .freestanding and (ret.arch == .x86_64 or ret.arch == .aarch64)) { - ret.bootloader = blk: { - if (config.get("CONFIG_BOOT_LIMINE")) |val| { - if (std.mem.eql(u8, val, "y")) break :blk .Limine; - } - - return error.NoBootloaderConfigured; + ret.bootloader = switch (config.bootloader) { + .limine => .Limine, }; } diff --git a/build/menuconfig b/build/menuconfig deleted file mode 100755 index db7ea8a..0000000 --- a/build/menuconfig +++ /dev/null @@ -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 diff --git a/build/run.zig b/build/run.zig index 12a17cf..ca9cef3 100644 --- a/build/run.zig +++ b/build/run.zig @@ -1,6 +1,7 @@ const std = @import("std"); const config = @import("config.zig"); const image = @import("image.zig"); +const builtin = @import("builtin"); const QemuArgs = struct { suffix: []u8, @@ -57,7 +58,7 @@ pub fn addRun(b: *std.Build, kernel: *std.Build.Step.Compile, plat: config.Platf .x86_64 => { 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" }), .riscv64 => qemu.addArgs(&.{ "-machine", "virt" }), diff --git a/config.zig.zon b/config.zig.zon new file mode 100644 index 0000000..162a4ac --- /dev/null +++ b/config.zig.zon @@ -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" }, + }, + }, + }, + }, + }, + }, + }, + }, +} diff --git a/src/kern/cpu.zig b/src/kern/cpu.zig index 280cfb3..cb4889f 100644 --- a/src/kern/cpu.zig +++ b/src/kern/cpu.zig @@ -69,7 +69,7 @@ pub fn ExportedCpuLocal(comptime T: type, comptime init: T, comptime name: []con /// Bitmask of CPUs. pub const CpuMask = struct { 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, diff --git a/src/kern/dpc.zig b/src/kern/dpc.zig index 50c1f01..a83bf24 100644 --- a/src/kern/dpc.zig +++ b/src/kern/dpc.zig @@ -110,13 +110,13 @@ fn dispatch_queue(cpu: u32) void { } 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) { // Reload the quantum for the new thread. 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; diff --git a/src/kern/log.zig b/src/kern/log.zig index 7adce33..a8747b5 100644 --- a/src/kern/log.zig +++ b/src/kern/log.zig @@ -37,7 +37,7 @@ 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.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( comptime level: std.log.Level, diff --git a/src/kern/private.zig b/src/kern/private.zig index b72ce74..40a5b94 100644 --- a/src/kern/private.zig +++ b/src/kern/private.zig @@ -3,12 +3,11 @@ const config = @import("config"); const init_mod = @import("init.zig"); -pub const impl = if (@hasDecl(config, "CONFIG_ARCH_AMD64")) - @import("amd64/impl.zig") -else if (@hasDecl(config, "CONFIG_ARCH_UM")) - @import("um/impl.zig") -else - @compileError("unsupported architecture"); +pub const impl = switch (config.arch) { + .amd64 => @import("amd64/impl.zig"), + .um => @import("um/impl.zig"), + else => @compileError("unsupported architecture"), +}; pub const init = init_mod.init; diff --git a/src/kern/sched.zig b/src/kern/sched.zig index 93f7b87..adfefde 100644 --- a/src/kern/sched.zig +++ b/src/kern/sched.zig @@ -96,7 +96,7 @@ const runqueues_n = 64; const interactivity_threshold = 30; const scaling_factor = 50; 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; var balance_timer: ke.Timer = undefined; @@ -211,7 +211,7 @@ pub fn clock(_: ?*anyopaque) void { if (curtd.priority_class == .Batch) { // Update interactivity metrics. - curtd.run_time += config.CONFIG_SCHED_TIMESLICE; + curtd.run_time += config.sched_timeslice; clamp_time(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; - 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. clamp_time(td); recompute_priority(td); diff --git a/src/mm/private.zig b/src/mm/private.zig index a11741d..376292f 100644 --- a/src/mm/private.zig +++ b/src/mm/private.zig @@ -8,11 +8,10 @@ pub const heap = @import("heap.zig"); pub const zone = @import("zone.zig"); pub const PMap = pmap.PMap; -pub const impl = if (@hasDecl(config, "CONFIG_ARCH_AMD64")) - @import("amd64/impl.zig") -else if (@hasDecl(config, "CONFIG_ARCH_UM")) - @import("um/impl.zig") -else - @compileError("unsupported architecture"); +pub const impl = switch (config.arch) { + .amd64 => @import("amd64/impl.zig"), + .um => @import("um/impl.zig"), + else => @compileError("unsupported architecture"), +}; pub var kernel_pmap: PMap = undefined; diff --git a/src/platform/pc/smp.zig b/src/platform/pc/smp.zig index 20e29de..ab949cd 100644 --- a/src/platform/pc/smp.zig +++ b/src/platform/pc/smp.zig @@ -17,7 +17,7 @@ extern var AP_TRAMPOLINE_DATA: u8; extern var __percpu_start: 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"); diff --git a/src/platform/root.zig b/src/platform/root.zig index 0c13db9..bdc5947 100644 --- a/src/platform/root.zig +++ b/src/platform/root.zig @@ -3,12 +3,11 @@ const config = @import("config"); -pub const impl = if (@hasDecl(config, "CONFIG_ARCH_AMD64")) - @import("pc/impl.zig") -else if (@hasDecl(config, "CONFIG_ARCH_UM")) - @import("um/impl.zig") -else - @compileError("unsupported architecture"); +pub const impl = switch (config.arch) { + .amd64 => @import("pc/impl.zig"), + .um => @import("um/impl.zig"), + else => @compileError("unsupported architecture"), +}; pub const acpi = @import("acpi/root.zig"); diff --git a/src/platform/um/c.h b/src/platform/um/c.h new file mode 100644 index 0000000..1915bee --- /dev/null +++ b/src/platform/um/c.h @@ -0,0 +1,9 @@ +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include +#include \ No newline at end of file diff --git a/src/platform/um/entry.zig b/src/platform/um/entry.zig index a3ab818..cf07693 100644 --- a/src/platform/um/entry.zig +++ b/src/platform/um/entry.zig @@ -3,12 +3,7 @@ const r = @import("root"); pub const std_options = r.std_options; pub const panic = r.panic; -const c = @cImport({ - @cInclude("SDL.h"); - @cInclude("signal.h"); - @cInclude("pthread.h"); -}); - +const c = @import("c"); const std = @import("std"); const ke = r.ke; const pl = r.pl; diff --git a/src/platform/um/impl.zig b/src/platform/um/impl.zig index 54c21e7..a7e6e18 100644 --- a/src/platform/um/impl.zig +++ b/src/platform/um/impl.zig @@ -10,16 +10,7 @@ pub const entry = @import("entry.zig"); const timer = @import("timer.zig"); -pub const c = @cImport({ - @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 c = @import("c"); pub const name = "User-Mode";