const std = @import("std"); const danos = @import("danos"); const arch = @import("arch"); const console = @import("console.zig"); const pmm = @import("pmm.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; 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: framebuffer console online\n"); con.write("danos: cpu tables online (GDT, IDT, TSS)\n"); con.print(" resolution : {d}x{d}\n", .{ fb.width, fb.height }); con.print(" pitch : {d} bytes\n", .{fb.pitch}); con.print(" format : {s}\n", .{@tagName(fb.format)}); con.print(" framebuffer: 0x{x:0>16}\n", .{fb.base}); // 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 reclaim_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, .reclaimable => reclaim_pages += r.pages, .reserved, .acpi_tables, .acpi_nvs => reserved_pages += r.pages, .mmio => {}, } } const total_pages = usable_pages + reclaim_pages + reserved_pages; const total_bytes = total_pages * danos.page_size; const gib = 1 << 30; con.write("\ndanos: physical memory\n"); con.print(" 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) }); con.print(" usable : {d} MiB - free now; owned by the frame allocator\n", .{mib(usable_pages)}); con.print(" reclaimable: {d} MiB - UEFI boot-services memory, free after exit\n", .{mib(reclaim_pages)}); con.print(" reserved : {d} MiB - kernel image, ACPI, runtime services\n", .{mib(reserved_pages)}); con.print(" 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 s = pmm.stats(); con.print("\ndanos: frame allocator online\n", .{}); con.print(" free frames: {d} ({d} MiB)\n", .{ s.free_frames, mib(s.free_frames) }); const f0 = pmm.alloc(); const f1 = pmm.alloc(); const f2 = pmm.alloc(); con.print(" 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); con.print(" 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); con.print("\ndanos: paging enabled\n", .{}); con.print(" page tables: CR3 = 0x{x:0>16}\n", .{arch.readCr3()}); con.print(" kernel segs: {d} (mapped with W^X permissions)\n", .{boot_info.kernel_segment_count}); // Start the timer and unmask interrupts — the kernel now has a heartbeat. arch.startTimer(); arch.enableInterrupts(); con.write("\ndanos: timer interrupts enabled\n"); // In a test build (`zig build -Dtest-case=`), 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("\nkernel initialised; nothing left to do, halting.\n"); arch.halt(); } /// Frames (4 KiB pages) to whole MiB. fn mib(pages: u64) u64 { return pages * danos.page_size / (1024 * 1024); } /// Report a CPU exception in red and halt. There's no fault recovery yet, so any /// exception is terminal — but now it prints 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. Print 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);