initial commit

This commit is contained in:
Loïc Grégoire
2026-03-21 22:46:54 -04:00
parent cf7df37498
commit 9e065ee023
45 changed files with 4430 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
const b = @import("base");
pub fn first_init() linksection(b.init) void {}
+16
View File
@@ -0,0 +1,16 @@
const entry = @import("entry.zig");
pub const first_init = entry.first_init;
pub fn devices_init() void {}
pub fn debug_write(c: u8) void {
// Write to debugcon
asm volatile ("outb %[c], %[port]"
:
: [c] "{al}" (c),
[port] "N{dx}" (@as(u16, 0xe9)),
);
}
pub fn arm_timer(_: u64) void {}
+75
View File
@@ -0,0 +1,75 @@
//! Platform interface: each port must implement these functions and variables.
//! To add a new platform, create a new file and implement all required functions.
const config = @import("config");
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");
/// Early platform-specific initialization, called before anything else.
pub inline fn early_init() void {
return impl.early_init();
}
/// Late platform init. Memory allocator is available at this point.
/// SMP bringup must be done after this returns.
pub inline fn late_init() void {
return impl.late_init();
}
/// Write a single character to the debug console.
pub inline fn debug_write(c: u8) void {
return impl.debug_write(c);
}
/// Arm a one-shot timer to fire in `ns` nanoseconds.
/// The timer must call `ki_timer_expiry()` when it fires.
pub inline fn arm_timer(ns: u64) void {
return impl.arm_timer(ns);
}
/// Pretty name for the platform
pub const name = impl.name;
/// A generic struct representing information passed from the bootloader.
pub const BootInfo = struct {
/// Represents the system memory map.
pub const MemMap = struct {
/// A memory map entry.
pub const Entry = struct {
pub const Kind = enum {
Free,
Reserved,
LoaderReclaimable,
AcpiNvs,
AcpiReclaimable,
Kernel,
};
kind: Kind,
base: usize,
size: usize,
};
entry_count: usize,
entries: [256]Entry,
};
pub const KernelAddress = struct { physical_base: usize, virtual_base: usize };
/// RSDP on ACPI machines
rsdp: usize,
/// Kernel command-line arguments
cmdline: ?[]const u8,
/// Memory map
memory_map: MemMap,
/// Kernel address
kernel_address: KernelAddress,
};
+81
View File
@@ -0,0 +1,81 @@
const b = @import("base");
pub const std_options = b.std_options;
const std = @import("std");
const ke = b.ke;
const pl = b.pl;
const default_mem_size = b.gib(1);
fn parse_mem_size(str: []const u8) !usize {
if (str.len == 0) return default_mem_size;
const last = str[str.len - 1];
const mul: usize = switch (std.ascii.toLower(last)) {
'k' => b.kib(1),
'm' => b.mib(1),
'g' => b.gib(1),
else => 1,
};
const digits = if (mul == 1) str else str[0 .. str.len - 1];
const size = try std.fmt.parseInt(usize, digits, 10);
return size * mul;
}
pub fn main(init: std.process.Init) !void {
var args = try init.minimal.args.iterateAllocator(init.gpa);
defer args.deinit();
const exe = args.next().?;
var mem_size: usize = default_mem_size;
var cmdline: ?[]const u8 = null;
var ncpus: usize = 1;
while (args.next()) |arg| {
if (std.mem.eql(u8, arg, "-m") or std.mem.eql(u8, arg, "--mem-size")) {
const val = args.next() orelse fatal("--mem-size requires a value", .{});
mem_size = parse_mem_size(val) catch fatal("invalid mem-size", .{});
} else if (std.mem.eql(u8, arg, "-c") or std.mem.eql(u8, arg, "--cmdline")) {
cmdline = args.next() orelse fatal("--cmdline requires a value", .{});
} else if (std.mem.eql(u8, arg, "-t") or std.mem.eql(u8, arg, "--threads")) {
const val = args.next() orelse fatal("--threads requires a value", .{});
ncpus = std.fmt.parseInt(usize, val, 10) catch fatal("invalid thread count", .{});
} else if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) {
var stderr = std.Io.File.stderr().writerStreaming(init.io, &.{});
var w = &stderr.interface;
try w.print(
\\Usage: {s} [options]
\\ -m, --mem-size SIZE Set the memory size
\\ -c, --cmdline STR Set the kernel cmdline
\\ -t, --threads COUNT Set the number of threads
\\
, .{exe});
std.process.exit(0);
} else {
fatal("unknown argument: {s}", .{arg});
}
}
pl.impl.global_state.params =
.{ .ncpus = ncpus, .cmdline = cmdline, .mem_size = mem_size };
pl.impl.global_state.boot_info.kernel_address.physical_base = 0;
pl.impl.global_state.boot_info.kernel_address.virtual_base = 0;
pl.impl.global_state.boot_info.rsdp = 0;
pl.impl.global_state.boot_info.cmdline = cmdline;
ke.ncpus = ncpus;
ke.private.init(&pl.impl.global_state.boot_info);
}
fn fatal(comptime fmt: []const u8, args: anytype) noreturn {
std.debug.print(fmt ++ "\n", args);
std.process.exit(1);
}
+200
View File
@@ -0,0 +1,200 @@
const std = @import("std");
const linux = std.os.linux;
const b = @import("base");
const pl = b.pl;
const ke = b.ke;
const ki = b.ke.private;
const posix = std.posix;
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 name = "User-Mode";
pub const GlobalState = struct {
const Params = struct {
mem_size: usize,
cmdline: ?[]const u8,
ncpus: usize,
};
params: Params,
boot_info: pl.BootInfo,
phys_memory_memfd: i32,
phys_base: usize,
};
pub var global_state: GlobalState = undefined;
pub threadlocal var my_cpu: *ke.Cpu = undefined;
var cpu_id: u32 = 0;
pub fn devices_init() void {}
pub fn debug_write(char: u8) void {
std.debug.print("{c}", .{char});
}
pub const arm_timer = timer.arm_timer;
fn check_function(fn_name: []const u8, ret: usize) void {
const real_ret: i64 = @intCast(ret);
if (real_ret < 0) {
const errno_val = linux.errno(ret);
std.debug.print("error: {s}: {s}\n", .{ fn_name, @tagName(errno_val) });
std.process.exit(1);
}
}
pub fn early_init() linksection(b.init) void {
// Set current CPU to the bootstrap CPU
my_cpu = &ki.bootstrap_cpu;
my_cpu.impl.pthread = c.pthread_self();
// Allocate our ID
my_cpu.id = 0;
cpu_id += 1;
const memfd: i32 = @intCast(linux.memfd_create("phys_memory", 0));
check_function("ftruncate", linux.ftruncate(memfd, @intCast(global_state.params.mem_size)));
const phys_base = linux.mmap(null, global_state.params.mem_size, .{ .READ = true, .WRITE = true }, .{ .ANONYMOUS = true, .TYPE = .SHARED }, memfd, 0);
check_function("mmap", phys_base);
// Build memory map
global_state.boot_info.memory_map.entry_count = 1;
global_state.boot_info.memory_map.entries[0].base = phys_base;
global_state.boot_info.memory_map.entries[0].size = global_state.params.mem_size;
global_state.boot_info.memory_map.entries[0].kind = .Free;
global_state.phys_memory_memfd = memfd;
global_state.phys_base = phys_base;
const pfndb_base = linux.mmap(null, b.tib(1), .{}, .{ .ANONYMOUS = true, .TYPE = .PRIVATE }, -1, 0);
check_function("mmap", pfndb_base);
const heap_base = linux.mmap(null, b.gib(8), .{}, .{ .ANONYMOUS = true, .TYPE = .PRIVATE }, -1, 0);
check_function("mmap", heap_base);
b.kernel_pfndb_base = pfndb_base;
b.kernel_heap_base = heap_base;
std.log.info("um: kernel heap lives at {X}", .{heap_base});
}
fn sigusr1_handler(_: posix.SIG, _: *const posix.siginfo_t, _: ?*anyopaque) callconv(.c) void {
if (@intFromEnum(ke.curcpu().ipl) < @intFromEnum(ke.Ipl.Dispatch) and ki.ipl.is_softint_pending(ke.curcpu(), .Dispatch)) {
ki.dpc.dispatch(ke.curcpu());
}
}
fn make_thread(entry: *const fn (?*anyopaque) void, td: *ke.Thread) void {
const stack = std.heap.page_allocator.alloc(u8, 16384) catch @panic("wtf");
td.init(@intFromPtr(stack.ptr), 16384, entry, null);
td.priority = 0;
td.priority_class = .Idle;
}
var cpus_up = std.atomic.Value(usize).init(0);
fn other_cpu_entry(arg: ?*anyopaque) callconv(.c) ?*anyopaque {
my_cpu = @ptrCast(@alignCast(arg.?));
my_cpu.id = @atomicRmw(u32, &cpu_id, .Add, 1, .monotonic);
my_cpu.impl.pthread = c.pthread_self();
my_cpu.init(my_cpu.idle_thread.?);
timer.init_cpu();
_ = cpus_up.fetchAdd(1, .monotonic);
while (true) {}
return null;
}
fn idle(_: ?*anyopaque) void {}
pub fn late_init() linksection(b.init) void {
timer.init();
timer.init_cpu();
var sa: posix.Sigaction = std.mem.zeroes(posix.Sigaction);
sa.handler = .{
.sigaction = sigusr1_handler,
};
sa.flags = posix.SA.SIGINFO;
sa.mask = posix.sigemptyset();
_ = posix.sigaction(posix.SIG.USR1, &sa, null);
std.log.info("bum: starting {} thread{s}", .{
ke.ncpus - 1,
if ((ke.ncpus - 1) > 1) "s" else "",
});
const other_count = ke.ncpus - 1;
const allocator = std.heap.page_allocator;
var other_threads =
allocator.alloc(c.pthread_t, other_count) catch @panic("oom");
ke.cpus = allocator.alloc(*ke.Cpu, ke.ncpus) catch @panic("oom");
ke.cpus[0] = &ki.bootstrap_cpu;
for (1..ke.ncpus) |i| {
ke.cpus[i] = allocator.create(ke.Cpu) catch @panic("oom");
ke.cpus[i].idle_thread = allocator.create(ke.Thread) catch @panic("oom");
make_thread(idle, ke.cpus[i].idle_thread.?);
var attr: c.pthread_attr_t = undefined;
_ = c.pthread_attr_init(&attr);
var cpuset: c.cpu_set_t = std.mem.zeroes(c.cpu_set_t);
cpuset.__bits[i / 64] |= @as(c_ulong, 1) << @intCast(i % 64);
_ = c.pthread_attr_setaffinity_np(&attr, @sizeOf(c.cpu_set_t), &cpuset);
const r = c.pthread_create(
&other_threads[i - 1],
&attr,
other_cpu_entry,
ke.cpus[i],
);
if (r != 0) {
@panic("pthread_create() failed");
}
}
if (ke.ncpus > 1) {
while (cpus_up.load(.monotonic) < @as(u32, @intCast(ke.ncpus - 1))) {
std.atomic.spinLoopHint();
}
}
}
+73
View File
@@ -0,0 +1,73 @@
const b = @import("base");
const std = @import("std");
const posix = std.posix;
const linux = std.os.linux;
const c = b.pl.impl.c;
const ke = b.ke;
const ki = ke.private;
threadlocal var timerid: c.timer_t = undefined;
fn timer_handler(_: posix.SIG, _: *const posix.siginfo_t, _: ?*anyopaque) callconv(.c) void {
const old_ipl = ke.ipl.set_hardware(.Device);
ke.dpc.enqueue(&ke.curcpu().timer_dpc, null);
_ = ke.ipl.set_hardware(old_ipl);
if (@intFromEnum(old_ipl) < @intFromEnum(ke.Ipl.Dispatch) and ki.ipl.is_softint_pending(ke.curcpu(), .Dispatch)) {
ki.dpc.dispatch(ke.curcpu());
}
}
pub fn arm_timer(ns: b.Nanoseconds) void {
const value: c.struct_itimerspec = .{ .it_value = .{
.tv_sec = @intCast(ns / std.time.ns_per_s),
.tv_nsec = @intCast(ns % std.time.ns_per_s),
}, .it_interval = .{ .tv_sec = 0, .tv_nsec = 0 } };
if (c.timer_settime(timerid, 0, &value, null) == -1) {
ke.panic("timer_settime failed", .{});
}
}
fn tc_read() u64 {
var ts: std.c.timespec = undefined;
_ = linux.clock_gettime(linux.CLOCK.MONOTONIC, &ts);
return @intCast(ts.sec * std.time.ns_per_s + ts.nsec);
}
var tc: ke.TimeCounter = .{
.read_count = tc_read,
.frequency = std.time.ns_per_s,
.name = "CLOCK_MONOTONIC",
.quality = 1000,
.mask = std.math.maxInt(u64),
.p = 0,
.n = 0,
};
pub fn init() void {
var act: posix.Sigaction = undefined;
act.handler.sigaction = timer_handler;
act.flags = posix.SA.RESTART | posix.SA.SIGINFO;
posix.sigaction(posix.SIG.ALRM, &act, null);
ke.timecounter.register(&tc);
}
pub fn init_cpu() void {
var sev: c.sigevent = std.mem.zeroes(c.sigevent);
sev.sigev_notify = c.SIGEV_THREAD_ID;
sev.sigev_signo = c.SIGALRM;
sev._sigev_un._tid = linux.gettid();
if (c.timer_create(c.CLOCK_MONOTONIC, &sev, &timerid) == -1) {
ke.panic("timer_create failed", .{});
}
}