moving kernel code to kernel/

This commit is contained in:
2026-07-05 10:19:11 +01:00
parent 7c3cffb337
commit 6f6ccc8bc9
37 changed files with 63 additions and 61 deletions
+168
View File
@@ -0,0 +1,168 @@
const std = @import("std");
const danos = @import("danos");
const arch = @import("arch");
const console = @import("console.zig");
const pmm = @import("pmm.zig");
const heap = @import("heap.zig");
const sched = @import("sched.zig");
const tests = @import("tests.zig");
const build_options = @import("build_options");
const BootInfo = danos.BootInfo;
/// The calling convention used to enter the kernel. Pinned to SysV explicitly:
/// the bootloader is built for the UEFI target, whose C convention is Microsoft
/// x64 (first argument in RCX), while the kernel is SysV (first argument in
/// RDI). Both sides reference this so the `boot_info` pointer lands in the
/// register the other expects. `danos.kernel_abi` re-exports it to the loader.
pub const kernel_abi = danos.kernel_abi;
/// The system console, valid once `kmain` has initialised it. Global so the
/// panic handler can reach it too.
var con: console.Console = undefined;
var con_ready = false;
/// Kernel entry point. The bootloader jumps here after `ExitBootServices` with a
/// pointer to the handoff data. There is no runtime, no stack unwinding, and no
/// caller to return to, so this never returns.
export fn _start(boot_info: *const BootInfo) callconv(kernel_abi) noreturn {
kmain(boot_info);
}
fn kmain(boot_info: *const BootInfo) noreturn {
arch.serialInit(); // machine-readable log; console mirrors to it
const fb = boot_info.framebuffer;
const serial0 = console.SerialConsole;
con = console.Console.init(fb);
con.clear();
con_ready = true;
// Catch CPU exceptions before doing anything that might fault: install our
// reporter, then bring up the GDT + IDT.
arch.setFaultHandler(onException);
arch.init();
con.write("danos: initalizing kernel...");
serial0.debugWrite("danos: framebuffer console online\n");
serial0.debugWrite("danos: cpu tables online (GDT, IDT, TSS)\n");
serial0.debugPrint(" resolution : {d}x{d}\n", .{ fb.width, fb.height });
serial0.debugPrint(" pitch : {d} bytes\n", .{fb.pitch});
serial0.debugPrint(" format : {s}\n", .{@tagName(fb.format)});
serial0.debugPrint(" framebuffer: 0x{x:0>16}\n", .{fb.base});
serial0.debugPrint (" footdebugPrint : {d} MiB\n", .{(fb.pitch * fb.height) / (1024 * 1024)});
// Summarise the physical memory the loader handed us. The array is danos's
// own MemoryRegion, so this is a plain slice — no firmware layout in sight.
const regions = @as([*]const danos.MemoryRegion, @ptrFromInt(boot_info.memory_map.regions))[0..boot_info.memory_map.len];
var usable_pages: u64 = 0;
var reserved_pages: u64 = 0; // reserved RAM only — MMIO is device space, not RAM
for (regions) |r| {
switch (r.kind) {
.usable => usable_pages += r.pages,
.reserved, .acpi_tables, .acpi_nvs => reserved_pages += r.pages,
.mmio => {},
}
}
const total_pages = usable_pages + reserved_pages;
const total_bytes = total_pages * danos.page_size;
const gib = 1 << 30;
serial0.debugWrite("\ndanos: physical memory\n");
serial0.debugPrint(" total RAM : {d}.{d:0>2} GiB ({d} MiB) - RAM the firmware reported\n", .{ total_bytes / gib, (total_bytes % gib) * 100 / gib, mib(total_pages) });
serial0.debugPrint(" usable : {d} MiB - free RAM (incl. reclaimed boot-services memory)\n", .{mib(usable_pages)});
serial0.debugPrint(" reserved : {d} MiB - kernel image, boot stack, ACPI, runtime services\n", .{mib(reserved_pages)});
serial0.debugPrint(" regions : {d} - entries in the firmware memory map\n", .{regions.len});
// Bring up the physical frame allocator over that map, and prove it works:
// allocate three frames, then hand them back.
pmm.init(boot_info.memory_map);
const s1 = pmm.stats();
serial0.debugPrint("\ndanos: frame allocator online\n", .{});
serial0.debugPrint(" free frames: {d} ({d} MiB)\n", .{ s1.free_frames, mib(s1.free_frames) });
const f0 = pmm.alloc();
const f1 = pmm.alloc();
const f2 = pmm.alloc();
serial0.debugPrint(" alloc x3 : 0x{x} 0x{x} 0x{x}\n", .{ f0 orelse 0, f1 orelse 0, f2 orelse 0 });
if (f0) |p| pmm.free(p);
if (f1) |p| pmm.free(p);
if (f2) |p| pmm.free(p);
serial0.debugPrint(" after free : {d} frames free\n", .{pmm.stats().free_frames});
// Switch off the firmware's page tables onto our own (with real permissions).
arch.enablePaging(pmm.alloc, boot_info);
serial0.debugPrint("\ndanos: paging enabled\n", .{});
serial0.debugPrint(" page tables: CR3 = 0x{x:0>16}\n", .{arch.readCr3()});
serial0.debugPrint(" kernel segs: {d} (mapped with W^X permissions)\n", .{boot_info.kernel_segment_count});
// Bring up the kernel heap (dynamic allocation), built on the VMM.
heap.init();
serial0.debugWrite("\ndanos: kernel heap online\n");
// Measure the amount of resources the kernel is actually using
const s2 = pmm.stats();
serial0.debugPrint(" Kernel FootdebugPrint: {d} KiB\n", .{kib(s1.free_frames - s2.free_frames)});
// Register the current context as the first task before enabling preemption.
sched.init(4);
serial0.debugWrite("\ndanos: scheduler online\n");
// Start the timer and unmask interrupts — the kernel now has a heartbeat, and
// the timer preempts among tasks.
arch.startTimer();
arch.enableInterrupts();
serial0.debugPrint("danos: timer online ({d} Hz tick; LAPIC {d} MHz, TSC {d} MHz measured)\n", .{ arch.timer_hz, arch.lapicHz() / 1_000_000, arch.tscHz() / 1_000_000 });
// In a test build (`zig build -Dtest-case=<name>`), run that case and stop.
// Normal builds fall through to the idle halt.
if (build_options.test_case) |case| {
tests.run(case, boot_info);
arch.halt();
}
con.write("kernel initialised.\n");
// TODO: init process
con.write("\nnothing left to do; halting CPU.\n");
arch.halt();
}
/// Frames (4 KiB pages) to whole MiB.
fn mib(pages: u64) u64 {
return pages * danos.page_size / (1024 * 1024);
}
fn kib(frames: u64) u64 {
return frames * danos.page_size / (1024);
}
/// Report a CPU exception in red and halt. There's no fault recovery yet, so any
/// exception is terminal — but now it debugPrints what and where instead of silently
/// resetting the machine.
fn onException(state: *const arch.CpuState) noreturn {
if (con_ready) {
con.fg = 0x00ff_5555;
con.print("\nCPU EXCEPTION: {s} (vector {d})\n", .{ arch.vectorName(state.vector), state.vector });
con.print(" error code : 0x{x}\n", .{state.error_code});
con.print(" RIP : 0x{x:0>16}\n", .{state.rip});
con.print(" RSP : 0x{x:0>16}\n", .{state.rsp});
if (state.vector == 14) con.print(" CR2 (addr) : 0x{x:0>16}\n", .{arch.readCr2()});
}
arch.halt();
}
/// Freestanding has no OS to receive a panic. debugPrint it to the console (if it is
/// up yet) in red, then halt.
pub const panic = std.debug.FullPanic(struct {
fn panic(msg: []const u8, first_trace_addr: ?usize) noreturn {
_ = first_trace_addr;
if (con_ready) {
con.fg = 0x00ff_5555;
con.write("\nKERNEL PANIC: ");
con.write(msg);
con.write("\n");
}
arch.halt();
}
}.panic);