hachem.wtf
aboutsummaryrefslogtreecommitdiff
path: root/boot
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-10-10 10:31:01 +0200
committerhachem <im@hachem.wtf>2026-10-10 10:31:01 +0200
commitcc8768d33a6ff6cc64306d01679a10c3a1847736 (patch)
tree144b2f68c6e6ed482b99e45e981854bfd5db59e6 /boot
parent89abe49aad1ac7416844f3c5e95655ad2614392b (diff)
chore: small refactor
Diffstat (limited to 'boot')
-rw-r--r--boot/ata.zig34
-rw-r--r--boot/boot.zig64
-rw-r--r--boot/gdt.zig23
-rw-r--r--boot/heap.zig64
-rw-r--r--boot/idt.zig22
-rw-r--r--boot/limine.zig6
-rw-r--r--boot/pic.zig2
-rw-r--r--boot/pmm.zig54
-rw-r--r--boot/sched.zig76
-rw-r--r--boot/serial.zig37
-rw-r--r--boot/user.zig98
-rw-r--r--boot/vmm.zig15
12 files changed, 284 insertions, 211 deletions
diff --git a/boot/ata.zig b/boot/ata.zig
index 4c5afe9..da1c4cb 100644
--- a/boot/ata.zig
+++ b/boot/ata.zig
@@ -35,6 +35,14 @@ const CMD_WRITE = 0x30;
const CMD_FLUSH = 0xE7;
const CMD_IDENTIFY = 0xEC;
+const DRIVE_MASTER = 0xA0; // chs mode (for identify)
+const DRIVE_LBA = 0xE0; // lba mode (top nibble holds lba 24..27)
+const FLOAT_BUS = 0xFF; // status read on a bus with no drive
+const SPIN_LIMIT = 1_000_000; // busy poll cap before we call the bus dead
+const IDENTIFY_WORDS = 256; // identify returns one sector of 16 bit words
+const ID_SECTORS_LO = 60; // identify words 60/61 hold the 28 bit sector count
+const ID_SECTORS_HI = 61;
+
var g_present = false;
var g_sectors: u64 = 0;
@@ -59,7 +67,7 @@ fn delay400() void {
// returns false if it never does (dead bus)
fn waitNotBusy() bool {
var spins: u32 = 0;
- while (spins < 1_000_000) : (spins += 1) {
+ while (spins < SPIN_LIMIT) : (spins += 1) {
if (port.inb(CMD) & ST_BSY == 0) return true;
}
return false;
@@ -69,7 +77,7 @@ fn waitNotBusy() bool {
// false on error/timeout
fn waitDrq() bool {
var spins: u32 = 0;
- while (spins < 1_000_000) : (spins += 1) {
+ while (spins < SPIN_LIMIT) : (spins += 1) {
const st = port.inb(CMD);
if (st & ST_ERR != 0) return false;
if (st & ST_BSY == 0 and st & ST_DRQ != 0) return true;
@@ -79,7 +87,7 @@ fn waitDrq() bool {
// select master and load the lba + a one sector count
fn selectSector(lba: u28) void {
- port.outb(DRIVE, 0xE0 | @as(u8, @truncate((lba >> 24) & 0x0F)));
+ port.outb(DRIVE, DRIVE_LBA | @as(u8, @truncate((lba >> 24) & 0x0F)));
port.outb(FEATURES, 0);
port.outb(SECCOUNT, 1);
port.outb(LBA_LO, @truncate(lba & 0xFF));
@@ -92,12 +100,12 @@ fn selectSector(lba: u28) void {
// words 60/61 hold the 28 bit sector count
pub fn init() void {
// a nonexistent bus floats high, bail before we wait a million spins on it
- if (port.inb(CMD) == 0xFF) {
+ if (port.inb(CMD) == FLOAT_BUS) {
serial.write("ata: no drive on primary bus\r\n");
return;
}
- port.outb(DRIVE, 0xA0); // master
+ port.outb(DRIVE, DRIVE_MASTER);
delay400();
port.outb(SECCOUNT, 0);
port.outb(LBA_LO, 0);
@@ -115,10 +123,10 @@ pub fn init() void {
return;
}
- var id: [256]u16 = undefined;
- for (&id) |*w| w.* = port.inw(DATA);
+ var id: [IDENTIFY_WORDS]u16 = undefined;
+ for (&id) |*word| word.* = port.inw(DATA);
- g_sectors = (@as(u64, id[61]) << 16) | id[60];
+ g_sectors = (@as(u64, id[ID_SECTORS_HI]) << 16) | id[ID_SECTORS_LO];
g_present = true;
serial.write("ata: primary master, sectors=");
serial.writeDec(g_sectors);
@@ -133,9 +141,9 @@ pub fn read(lba: u28, buf: *[SECTOR]u8) bool {
if (!waitDrq()) return false;
var i: usize = 0;
while (i < SECTOR) : (i += 2) {
- const w = port.inw(DATA);
- buf[i] = @truncate(w & 0xFF);
- buf[i + 1] = @truncate(w >> 8);
+ const word = port.inw(DATA);
+ buf[i] = @truncate(word & 0xFF);
+ buf[i + 1] = @truncate(word >> 8);
}
return true;
}
@@ -150,8 +158,8 @@ pub fn write(lba: u28, buf: *const [SECTOR]u8) bool {
if (!waitDrq()) return false;
var i: usize = 0;
while (i < SECTOR) : (i += 2) {
- const w = @as(u16, buf[i]) | (@as(u16, buf[i + 1]) << 8);
- port.outw(DATA, w);
+ const word = @as(u16, buf[i]) | (@as(u16, buf[i + 1]) << 8);
+ port.outw(DATA, word);
}
port.outb(CMD, CMD_FLUSH);
_ = waitNotBusy();
diff --git a/boot/boot.zig b/boot/boot.zig
index 267fc3f..6ead044 100644
--- a/boot/boot.zig
+++ b/boot/boot.zig
@@ -55,18 +55,22 @@ export var stack_size_request: limine.StackSizeRequest linksection(".limine_requ
// wrappers matching bridge.kernelops (pmm/vmm return optionals, the revo facing
// primitives use 0 as the failure sentinel)
-fn kPhysToVirt(p: u64) u64 {
- return pmm.physToVirt(p);
+fn kPhysToVirt(phys: u64) u64 {
+ return pmm.physToVirt(phys);
}
+
fn kAllocFrame() u64 {
return pmm.alloc() orelse 0;
}
-fn kAllocContig(n: u64) u64 {
- return pmm.allocContig(@intCast(n)) orelse 0;
+
+fn kAllocContig(count: u64) u64 {
+ return pmm.allocContig(@intCast(count)) orelse 0;
}
+
fn kCreateAddrspace() u64 {
return vmm.createAddressSpace() orelse 0;
}
+
fn kSerialNext() i64 {
return if (serial.mirrorNext()) |byte| @intCast(byte) else -1;
}
@@ -87,15 +91,15 @@ fn memmapEntry(i: u64) ?*const limine.MemoryMapEntry {
}
fn kMemmapBase(i: u64) u64 {
- return if (memmapEntry(i)) |e| e.base else 0;
+ return if (memmapEntry(i)) |entry| entry.base else 0;
}
fn kMemmapLen(i: u64) u64 {
- return if (memmapEntry(i)) |e| e.length else 0;
+ return if (memmapEntry(i)) |entry| entry.length else 0;
}
fn kMemmapKind(i: u64) u64 {
- return if (memmapEntry(i)) |e| @backingInt(e.type) else 0;
+ return if (memmapEntry(i)) |entry| @backingInt(entry.type) else 0;
}
fn kMemFree() u64 {
@@ -133,11 +137,11 @@ const Prog = struct { name: []const u8, phys: u64, size: u64 };
var g_progs: [elf.programs.len]Prog = undefined;
fn loadPrograms() void {
- for (elf.programs, 0..) |p, i| {
- const pages = (p.bytes.len + 4095) / 4096;
+ for (elf.programs, 0..) |program, i| {
+ const pages = (program.bytes.len + pmm.PAGE_SIZE - 1) / pmm.PAGE_SIZE;
const phys = pmm.allocContig(pages) orelse @panic("no room for an embedded program");
- @memcpy(@as([*]u8, @ptrFromInt(pmm.physToVirt(phys)))[0..p.bytes.len], p.bytes);
- g_progs[i] = .{ .name = p.name, .phys = phys, .size = p.bytes.len };
+ @memcpy(@as([*]u8, @ptrFromInt(pmm.physToVirt(phys)))[0..program.bytes.len], program.bytes);
+ g_progs[i] = .{ .name = program.name, .phys = phys, .size = program.bytes.len };
}
}
@@ -169,11 +173,11 @@ const FontBlob = struct { name: []const u8, phys: u64, size: u64 };
var g_fonts: [fontdata.fonts.len]FontBlob = undefined;
fn loadFonts() void {
- for (fontdata.fonts, 0..) |f, i| {
- const pages = (f.bytes.len + 4095) / 4096;
+ for (fontdata.fonts, 0..) |font, i| {
+ const pages = (font.bytes.len + pmm.PAGE_SIZE - 1) / pmm.PAGE_SIZE;
const phys = pmm.allocContig(pages) orelse @panic("no room for an embedded font");
- @memcpy(@as([*]u8, @ptrFromInt(pmm.physToVirt(phys)))[0..f.bytes.len], f.bytes);
- g_fonts[i] = .{ .name = f.name, .phys = phys, .size = f.bytes.len };
+ @memcpy(@as([*]u8, @ptrFromInt(pmm.physToVirt(phys)))[0..font.bytes.len], font.bytes);
+ g_fonts[i] = .{ .name = font.name, .phys = phys, .size = font.bytes.len };
}
}
@@ -277,11 +281,11 @@ export fn _start() callconv(.c) noreturn {
};
// hand the kernel heap + framebuffer + low level ops to revo and bring up the vm
- const fb: ?bridge.Fb = if (framebuffer.get()) |s| .{
- .ptr = s.address,
- .width = s.width,
- .height = s.height,
- .pitch = s.pitch,
+ const fb: ?bridge.Fb = if (framebuffer.get()) |info| .{
+ .ptr = info.address,
+ .width = info.width,
+ .height = info.height,
+ .pitch = info.pitch,
} else null;
bridge.boot(heap.allocator(), serial.write, fb, kops);
@@ -307,18 +311,24 @@ fn pmmSelfTest() void {
pmm.free(refreed);
}
-// map a fresh frame at an unused higher half address, write a pattern, read it
-// back through the mapping, then create a separate address space, switch into it
-// (a bad cr3 triple faults), map + rw a lower half user page, and switch back
+// map a fresh frame at an unused higher half address
+// write a pattern
+// read it back through the mapping
+// create a separate address space
+// switch into it (a bad cr3 triple faults)
+// map + rw a lower half user page
+// switch back
fn vmmSelfTest() void {
+ const kernel_pattern: u64 = 0xDEAD_BEEF_CAFE_BABE;
+ const user_pattern: u64 = 0x1234_5678_9ABC_DEF0;
const pml4 = vmm.activePml4();
const frame = pmm.alloc().?;
const test_virt: u64 = 0xffff_c000_0000_0000;
_ = vmm.map(pml4, test_virt, frame, vmm.WRITE);
const cell: *volatile u64 = @ptrFromInt(test_virt);
- cell.* = 0xDEADBEEFCAFEBABE;
+ cell.* = kernel_pattern;
serial.write("vmm test: map+rw ");
- serial.write(if (cell.* == 0xDEADBEEFCAFEBABE) "OK" else "FAIL");
+ serial.write(if (cell.* == kernel_pattern) "OK" else "FAIL");
serial.write(", translate ");
const back = vmm.translate(pml4, test_virt) orelse 0;
serial.write(if (back == frame) "OK\r\n" else "FAIL\r\n");
@@ -330,9 +340,9 @@ fn vmmSelfTest() void {
vmm.loadPml4(as);
serial.write("vmm test: switched CR3 OK\r\n"); // reached => kernel still mapped
const ucell: *volatile u64 = @ptrFromInt(user_virt);
- ucell.* = 0x1234_5678_9ABC_DEF0;
+ ucell.* = user_pattern;
serial.write("vmm test: user page rw ");
- serial.write(if (ucell.* == 0x123456789ABCDEF0) "OK\r\n" else "FAIL\r\n");
+ serial.write(if (ucell.* == user_pattern) "OK\r\n" else "FAIL\r\n");
vmm.loadPml4(pml4); // back to the original address space
serial.write("vmm test: switched back OK\r\n");
}
diff --git a/boot/gdt.zig b/boot/gdt.zig
index 42dbec9..fd56468 100644
--- a/boot/gdt.zig
+++ b/boot/gdt.zig
@@ -46,16 +46,11 @@ const Tss = packed struct {
var tss: Tss = .{};
-// the stack the cpu lands on for a ring0 entry from ring3 (16 kib). the scheduler
+// the stack the cpu lands on for a ring0 entry from ring3 (16 kib) so the scheduler
// overrides rsp0 per task, this is the boot default. the plain irq handlers run
-// here and are tiny, so 16 kib is fine
+// here and are tiny so 16 kib is fine ¯\_(ツ)_/¯
var kernel_stack: [16 * 1024]u8 align(16) = undefined;
-// int 0x80 uses ist1: syscalls may call into the revo vm, whose computed goto
-// dispatcher frame is huge (~330 kib observed for a single trivial call), so it
-// needs a big dedicated stack, since the small per task rsp0 kstacks overflow it
-// (that was the old "vm hangs in a syscall"). 2 mib gives room for a few nested
-// vm entries, like the 4 mib boot stack the event loop uses
var trap_stack: [2 * 1024 * 1024]u8 align(16) = undefined;
// #df/#pf use ist2 so a stack overflow lands on a known good stack and produces a
// loud exception dump instead of a silent triple fault
@@ -97,20 +92,20 @@ pub fn load() void {
tss.rsp0 = @intFromPtr(&kernel_stack) + kernel_stack.len; // stack grows down
tss.ist1 = ist1Top(); // big stack for int 0x80 (vm calls)
tss.ist2 = ist2Top(); // fault stack for #df/#pf
- tss.iomap_base = @sizeOf(Tss); // no i/o bitmap
- const d = tssDescriptor(@intFromPtr(&tss), @sizeOf(Tss) - 1);
- gdt[5] = d.low;
- gdt[6] = d.high;
+ tss.iomap_base = @sizeOf(Tss); // no io bitmap
+ const descriptor = tssDescriptor(@intFromPtr(&tss), @sizeOf(Tss) - 1);
+ gdt[5] = descriptor.low; // tss descriptor spans slots 5 and 6
+ gdt[6] = descriptor.high;
gdtr = .{ .limit = @sizeOf(@TypeOf(gdt)) - 1, .base = @intFromPtr(&gdt) };
asm volatile (
\\ lgdt (%[gdtr])
- \\ pushq $0x08
+ \\ pushq %[kcode]
\\ leaq 1f(%rip), %rax
\\ pushq %rax
\\ lretq
\\ 1:
- \\ movw $0x10, %ax
+ \\ movw %[kdata], %ax
\\ movw %ax, %ds
\\ movw %ax, %es
\\ movw %ax, %fs
@@ -120,6 +115,8 @@ pub fn load() void {
\\ ltr %ax
:
: [gdtr] "r" (&gdtr),
+ [kcode] "i" (@as(u16, KERNEL_CODE)),
+ [kdata] "i" (@as(u16, KERNEL_DATA)),
[tss] "i" (@as(u16, TSS_SEL)),
: .{ .rax = true, .memory = true });
}
diff --git a/boot/heap.zig b/boot/heap.zig
index ff8bfad..38127c4 100644
--- a/boot/heap.zig
+++ b/boot/heap.zig
@@ -7,7 +7,8 @@ const serial = @import("serial.zig");
const HEAP_PAGES: usize = 16384; // 64 mib
const HEADER: usize = 16; // keeps payloads 16 aligned
const ALIGN: usize = 16;
-const MIN_BLOCK: usize = 32; // header + a usable scrap, dont split below this
+const MIN_BLOCK: usize = 32; // header + alignment usable scrap, note: dont split below this
+const USED_BIT: usize = 1; // low header bit, 1 = allocated
var heap_start: usize = 0;
var heap_end: usize = 0;
@@ -15,20 +16,25 @@ var heap_end: usize = 0;
// alloc landed instead of heap_start everytime. it was O(n) before
var rover: usize = 0;
-inline fn word(b: usize) *usize {
- return @ptrFromInt(b);
+// hot take: zig is very ugly
+inline fn word(block: usize) *usize {
+ return @ptrFromInt(block);
}
-inline fn blkSize(b: usize) usize {
- return word(b).* & ~@as(usize, 0xf);
+
+inline fn blkSize(block: usize) usize {
+ return word(block).* & ~@as(usize, ALIGN - 1);
}
-inline fn blkUsed(b: usize) bool {
- return (word(b).* & 1) != 0;
+
+inline fn blkUsed(block: usize) bool {
+ return (word(block).* & USED_BIT) != 0;
}
-inline fn setBlk(b: usize, size: usize, used: bool) void {
- word(b).* = size | @as(usize, if (used) 1 else 0);
+
+inline fn setBlk(block: usize, size: usize, used: bool) void {
+ word(block).* = size | (if (used) USED_BIT else 0);
}
-inline fn alignUp(x: usize, a: usize) usize {
- return (x + a - 1) & ~(a - 1);
+
+inline fn alignUp(value: usize, alignment: usize) usize {
+ return (value + alignment - 1) & ~(alignment - 1);
}
pub fn init() void {
@@ -54,47 +60,47 @@ fn allocImpl(_: *anyopaque, len: usize, alignment: std.mem.Alignment, _: usize)
if (rover < heap_start or rover >= heap_end) rover = heap_start;
// next fit bull shit
- var b = rover;
+ var block = rover;
var limit = heap_end;
var pass: u8 = 0;
while (pass < 2) : (pass += 1) {
- while (b < limit) {
- if (!blkUsed(b)) {
+ while (block < limit) {
+ if (!blkUsed(block)) {
// lazy coalesce
- var size = blkSize(b);
- while (b + size < heap_end and !blkUsed(b + size)) size += blkSize(b + size);
- setBlk(b, size, false);
+ var size = blkSize(block);
+ while (block + size < heap_end and !blkUsed(block + size)) size += blkSize(block + size);
+ setBlk(block, size, false);
if (size >= need) {
if (size >= need + MIN_BLOCK) {
- setBlk(b, need, true);
- setBlk(b + need, size - need, false); // the leftover tail
- rover = b + need;
+ setBlk(block, need, true);
+ setBlk(block + need, size - need, false); // the leftover tail
+ rover = block + need;
} else {
- setBlk(b, size, true);
- rover = b + size;
+ setBlk(block, size, true);
+ rover = block + size;
}
if (rover >= heap_end) rover = heap_start;
- return @ptrFromInt(b + HEADER);
+ return @ptrFromInt(block + HEADER);
}
}
- b += blkSize(b);
+ block += blkSize(block);
}
// wrap
- b = heap_start;
+ block = heap_start;
limit = rover;
}
return null;
}
fn freeImpl(_: *anyopaque, buf: []u8, _: std.mem.Alignment, _: usize) void {
- const b = @intFromPtr(buf.ptr) - HEADER;
- setBlk(b, blkSize(b), false);
+ const block = @intFromPtr(buf.ptr) - HEADER;
+ setBlk(block, blkSize(block), false);
}
fn resizeImpl(_: *anyopaque, buf: []u8, _: std.mem.Alignment, new_len: usize, _: usize) bool {
- const b = @intFromPtr(buf.ptr) - HEADER;
- return new_len <= blkSize(b) - HEADER;
+ const block = @intFromPtr(buf.ptr) - HEADER;
+ return new_len <= blkSize(block) - HEADER;
}
fn remapImpl(ctx: *anyopaque, buf: []u8, alignment: std.mem.Alignment, new_len: usize, ra: usize) ?[*]u8 {
diff --git a/boot/idt.zig b/boot/idt.zig
index 15e6588..50b5c6f 100644
--- a/boot/idt.zig
+++ b/boot/idt.zig
@@ -29,6 +29,10 @@ const Idtr = packed struct {
base: u64,
};
+// the two faults that get their own ist stack, referenced from init + the handler
+const VEC_DOUBLE_FAULT: u8 = 8;
+const VEC_PAGE_FAULT: u8 = 14;
+
var idt: [256]Gate = @splat(@bitCast(@as(u128, 0)));
var idtr: Idtr = undefined;
@@ -98,7 +102,7 @@ export fn exceptionHandler(frame: *Frame) callconv(.c) noreturn {
serial.write(" rsp=");
serial.writeHex(frame.rsp);
// page faults stash the offending address in cr2
- if (frame.vector == 14) {
+ if (frame.vector == VEC_PAGE_FAULT) {
const cr2 = asm volatile ("mov %%cr2, %[out]"
: [out] "=r" (-> u64),
);
@@ -152,15 +156,16 @@ export fn irqDispatch(frame: *Frame) callconv(.c) void {
if (frame.vector == pic.MASTER_OFFSET + 0) sched.tick(frame);
}
-// dpl 0 = only ring 0 can invoke via `int`, dpl 3 = ring 3 may (for syscalls)
-// ist 0 = use the stack the cpu would pick (rsp0 on a ring switch), ist 1..7 =
-// force tss.istn, for handlers that need a known good/big stack
+// dpl 0 = only ring 0 can invoke via int
+// dpl 3 = ring 3 may also invoke (for syscalls)
+// ist 0 = use the stack the cpu would pick (rsp0 on a ring switch)
+// ist 1..7 = force tss.istn
pub fn setGate(vector: u8, handler: u64, dpl: u2, ist: u3) void {
idt[vector] = .{
.offset_low = @truncate(handler),
.selector = gdt.KERNEL_CODE,
.ist = ist,
- .type_attr = 0x8E | (@as(u8, dpl) << 5),
+ .type_attr = 0x8E | (@as(u8, dpl) << 5), // 0x8e = present ring 0 64 bit interrupt gate, dpl in bits 5..6
.offset_mid = @truncate(handler >> 16),
.offset_high = @truncate(handler >> 32),
.reserved = 0,
@@ -169,11 +174,12 @@ pub fn setGate(vector: u8, handler: u64, dpl: u2, ist: u3) void {
// fill the exception vectors and load the idt
pub fn init() void {
- inline for (0..32) |v| setGate(v, @intFromPtr(&stub(v)), 0, 0);
+ inline for (0..32) |vector| setGate(vector, @intFromPtr(&stub(vector)), 0, 0); // vectors 0..31 are exceptions
// #df (8) and #pf (14) land on ist2s fault stack, so a kernel stack overflow
// produces a real exception dump instead of a silent triple fault
- setGate(8, @intFromPtr(&stub(8)), 0, 2);
- setGate(14, @intFromPtr(&stub(14)), 0, 2);
+ const ist_fault = 2;
+ setGate(VEC_DOUBLE_FAULT, @intFromPtr(&stub(VEC_DOUBLE_FAULT)), 0, ist_fault);
+ setGate(VEC_PAGE_FAULT, @intFromPtr(&stub(VEC_PAGE_FAULT)), 0, ist_fault);
// irq0 (pit) -> vector 0x20, irq1 (keyboard) -> vector 0x21
// the pic is remapped separately in boot
setGate(pic.MASTER_OFFSET + 0, @intFromPtr(&irqStub(pic.MASTER_OFFSET + 0)), 0, 0);
diff --git a/boot/limine.zig b/boot/limine.zig
index 947a5be..df5f6d1 100644
--- a/boot/limine.zig
+++ b/boot/limine.zig
@@ -1,5 +1,5 @@
// limine scans the loaded image for these magic numbers, baserevision is
-// a handshake, essentially we ask limine for rev 3, and if it supports it,
+// id0 handshake, essentially we ask limine for rev 3, and if it supports it,
// limine zeros it out
pub const BaseRevision = extern struct {
magic0: u64 = 0xf9562b2d5c95a6c8,
@@ -27,8 +27,8 @@ pub const RequestsEndMarker = extern struct {
// every request id starts with this common magic, then two request specific
// words, bootloader scans .limine_requests for these and fills in `response`
-fn requestId(a: u64, b: u64) [4]u64 {
- return .{ 0xc7b1dd30df4c8b88, 0x0a82e883a194f07b, a, b };
+fn requestId(id0: u64, id1: u64) [4]u64 {
+ return .{ 0xc7b1dd30df4c8b88, 0x0a82e883a194f07b, id0, id1 };
}
// this comes from the limine spec
diff --git a/boot/pic.zig b/boot/pic.zig
index 365fa2f..b2326e4 100644
--- a/boot/pic.zig
+++ b/boot/pic.zig
@@ -17,6 +17,6 @@ pub const SLAVE_OFFSET: u8 = 0x28; // irq8..15 -> vectors 0x28..0x2f
// tell the pic were done. the slaves interrupts also need the master eoid
// since they arrive via the cascade
pub fn eoi(irq: u4) void {
- if (irq >= 8) port.outb(SLAVE_CMD, EOI);
+ if (irq >= 8) port.outb(SLAVE_CMD, EOI); // irq 8..15 live on the slave pic
port.outb(MASTER_CMD, EOI);
}
diff --git a/boot/pmm.zig b/boot/pmm.zig
index 51ce60c..1112134 100644
--- a/boot/pmm.zig
+++ b/boot/pmm.zig
@@ -59,9 +59,9 @@ pub fn init(hhdm: *const limine.HhdmResponse, memmap: *const limine.MemoryMapRes
// highest end of any usable region bounds the bitmap. we only ever
// alloc/free usable frames, so theres no point indexing past that
var highest: u64 = 0;
- for (entries) |e| {
- if (e.type == .usable) {
- const top = e.base + e.length;
+ for (entries) |entry| {
+ if (entry.type == .usable) {
+ const top = entry.base + entry.length;
if (top > highest) highest = top;
}
}
@@ -70,9 +70,9 @@ pub fn init(hhdm: *const limine.HhdmResponse, memmap: *const limine.MemoryMapRes
// park the bitmap in the first usable region big enough to hold it
var storage_base: u64 = 0;
- for (entries) |e| {
- if (e.type == .usable and e.length >= bitmap_bytes) {
- storage_base = e.base;
+ for (entries) |entry| {
+ if (entry.type == .usable and entry.length >= bitmap_bytes) {
+ storage_base = entry.base;
break;
}
}
@@ -82,10 +82,10 @@ pub fn init(hhdm: *const limine.HhdmResponse, memmap: *const limine.MemoryMapRes
// start with everything used, then punch out the usable regions
@memset(bitmap, 0xFF);
- for (entries) |e| {
- if (e.type == .usable) {
- forEachFrame(e.base, e.length, markFree);
- usable_frames += e.length / PAGE_SIZE;
+ for (entries) |entry| {
+ if (entry.type == .usable) {
+ forEachFrame(entry.base, entry.length, markFree);
+ usable_frames += entry.length / PAGE_SIZE;
}
}
free_frames = usable_frames;
@@ -134,19 +134,18 @@ pub fn alloc() ?u64 {
return null;
}
-// hand out n contiguous physical frames cause the heap needs one flat buffer, and a
-// single usable region is contiguous in physical space, so this just finds a
-// run of n free bits
+// hand out count contiguous physical frames cause the heap needs one flat buffer and a
+// single usable region is contiguous in physical space, so this just finds a run of count free bits
// note: returns the base physical address of the run
-pub fn allocContig(n: usize) ?u64 {
- if (n == 0) return null;
+pub fn allocContig(count: usize) ?u64 {
+ if (count == 0) return null;
var i: usize = 0;
- while (i + n <= total_frames) {
+ while (i + count <= total_frames) {
var run: usize = 0;
- while (run < n and !frameUsed(i + run)) : (run += 1) {}
- if (run == n) {
+ while (run < count and !frameUsed(i + run)) : (run += 1) {}
+ if (run == count) {
var j: usize = 0;
- while (j < n) : (j += 1) {
+ while (j < count) : (j += 1) {
markUsed(i + j);
free_frames -= 1;
}
@@ -179,24 +178,25 @@ pub fn usableBytes() u64 {
pub fn dump(memmap: *const limine.MemoryMapResponse) void {
const entries = memmap.entries.?[0..memmap.entry_count];
serial.write("memory map:\r\n");
- for (entries) |e| {
+ for (entries) |entry| {
serial.write(" ");
- serial.writeHex(e.base);
+ serial.writeHex(entry.base);
serial.write(" len=");
- serial.writeHex(e.length);
+ serial.writeHex(entry.length);
serial.write(" type=");
- serial.write(typeName(e.type));
+ serial.write(typeName(entry.type));
serial.write("\r\n");
}
+ const mib = 1024 * 1024;
serial.write("usable: ");
- serial.writeDec(usableBytes() / (1024 * 1024));
+ serial.writeDec(usableBytes() / mib);
serial.write(" MiB, free: ");
- serial.writeDec(freeBytes() / (1024 * 1024));
+ serial.writeDec(freeBytes() / mib);
serial.write(" MiB\r\n");
}
-fn typeName(t: limine.MemoryType) []const u8 {
- return switch (t) {
+fn typeName(kind: limine.MemoryType) []const u8 {
+ return switch (kind) {
.usable => "usable",
.reserved => "reserved",
.acpi_reclaimable => "acpi-reclaimable",
diff --git a/boot/sched.zig b/boot/sched.zig
index 4367fc0..e267edd 100644
--- a/boot/sched.zig
+++ b/boot/sched.zig
@@ -68,16 +68,18 @@ pub fn spawn(cr3: u64, entry: u64, ustack: u64, arg: u64, arglen: u64) void {
var i: usize = 1;
while (i < MAX_TASKS) : (i += 1) {
if (tasks[i].state != .free) continue;
- var f = std.mem.zeroes(Frame);
- f.rip = entry;
- f.cs = 0x1b; // user code (0x18) | rpl 3
- f.rflags = 0x202; // reserved bit + if=1 so its preemptible
- f.rsp = ustack;
- f.ss = 0x23; // user data (0x20) | rpl 3
- f.rdi = arg; // _start(arg_ptr, arg_len) the sysv first two args
- f.rsi = arglen;
+ const rpl_user = 3; // low two selector bits request ring 3
+ const rflags_init = (1 << 1) | (1 << 9); // reserved bit + interrupt enable (preemptible)
+ var frame = std.mem.zeroes(Frame);
+ frame.rip = entry;
+ frame.cs = @as(u64, gdt.USER_CODE) | rpl_user;
+ frame.rflags = rflags_init;
+ frame.rsp = ustack;
+ frame.ss = @as(u64, gdt.USER_DATA) | rpl_user;
+ frame.rdi = arg; // _start(arg_ptr, arg_len) the sysv first two args
+ frame.rsi = arglen;
tasks[i] = .{
- .frame = f,
+ .frame = frame,
.cr3 = cr3,
.kstack_top = @intFromPtr(&kstacks[i]) + KSTACK_SIZE,
.state = .ready,
@@ -93,17 +95,17 @@ const USER_PAGE: u64 = vmm.WRITE | vmm.USER;
// map heap pages until everything below target is present
// false on oom
-fn growHeap(t: *Task, target: u64) bool {
- while (t.brk_top < target) {
+fn growHeap(task: *Task, target: u64) bool {
+ while (task.brk_top < target) {
const phys = pmm.alloc() orelse return false;
// zero it so we never hand ring 3 whatever the fuck was in that frame before
const page: [*]u8 = @ptrFromInt(pmm.physToVirt(phys));
@memset(page[0..pmm.PAGE_SIZE], 0);
- if (!vmm.map(t.cr3, t.brk_top, phys, USER_PAGE)) {
+ if (!vmm.map(task.cr3, task.brk_top, phys, USER_PAGE)) {
pmm.free(phys);
return false;
}
- t.brk_top += pmm.PAGE_SIZE;
+ task.brk_top += pmm.PAGE_SIZE;
}
return true;
}
@@ -111,28 +113,28 @@ fn growHeap(t: *Task, target: u64) bool {
// move the break to addr
pub fn brk(addr: u64) i64 {
if (current == 0 or addr < HEAP_BASE) return -1;
- const t = &tasks[current];
- if (addr > t.brk_cur and !growHeap(t, addr)) return -1;
- t.brk_cur = addr;
+ const task = &tasks[current];
+ if (addr > task.brk_cur and !growHeap(task, addr)) return -1;
+ task.brk_cur = addr;
return @intCast(addr);
}
// move the break by increment
pub fn sbrk(increment: i64) i64 {
if (current == 0) return -1;
- const t = &tasks[current];
- const old = t.brk_cur;
+ const task = &tasks[current];
+ const old = task.brk_cur;
const want: i64 = @as(i64, @intCast(old)) + increment;
if (want < @as(i64, @intCast(HEAP_BASE))) return -1;
const new_brk: u64 = @intCast(want);
- if (new_brk > old and !growHeap(t, new_brk)) return -1;
- t.brk_cur = new_brk;
+ if (new_brk > old and !growHeap(task, new_brk)) return -1;
+ task.brk_cur = new_brk;
return @intCast(old);
}
// stdin at keyboard
fn initFds(i: usize) void {
- for (&fdtabs[i]) |*f| f.* = .{};
+ for (&fdtabs[i]) |*slot| slot.* = .{};
fdtabs[i][0].kind = .keyboard;
fdtabs[i][1].kind = .console;
fdtabs[i][2].kind = .console;
@@ -142,13 +144,13 @@ fn initFds(i: usize) void {
pub fn fdOpen(path: []const u8) i64 {
var i: usize = 3;
while (i < MAX_FDS) : (i += 1) {
- const f = &fdtabs[current][i];
- if (f.kind == .closed) {
- f.kind = .file;
- f.offset = 0;
- const n = @min(path.len, MAX_PATH);
- @memcpy(f.path[0..n], path[0..n]);
- f.path_len = n;
+ const slot = &fdtabs[current][i];
+ if (slot.kind == .closed) {
+ slot.kind = .file;
+ slot.offset = 0;
+ const count = @min(path.len, MAX_PATH);
+ @memcpy(slot.path[0..count], path[0..count]);
+ slot.path_len = count;
return @intCast(i);
}
}
@@ -158,15 +160,15 @@ pub fn fdOpen(path: []const u8) i64 {
// foid decimator get
pub fn fdGet(fd: i64) ?*Fd {
if (fd < 0 or fd >= MAX_FDS) return null;
- const f = &fdtabs[current][@intCast(fd)];
- if (f.kind == .closed) return null;
- return f;
+ const slot = &fdtabs[current][@intCast(fd)];
+ if (slot.kind == .closed) return null;
+ return slot;
}
// foid decimator close
pub fn fdClose(fd: i64) i64 {
- const f = fdGet(fd) orelse return -1;
- f.kind = .closed;
+ const slot = fdGet(fd) orelse return -1;
+ slot.kind = .closed;
return 0;
}
@@ -205,9 +207,9 @@ pub fn exitCurrent() void {
}
pub fn takeProcExited() bool {
- const e = proc_exited_flag;
+ const exited = proc_exited_flag;
proc_exited_flag = false;
- return e;
+ return exited;
}
pub fn readReady() ?u8 {
@@ -249,8 +251,8 @@ pub fn tick(frame: *Frame) void {
var next = current;
var i = current;
- var n: usize = 0;
- while (n < MAX_TASKS) : (n += 1) {
+ var count: usize = 0;
+ while (count < MAX_TASKS) : (count += 1) {
i = (i + 1) % MAX_TASKS;
if (tasks[i].state == .ready) {
next = i;
diff --git a/boot/serial.zig b/boot/serial.zig
index c51b981..ca9fe2a 100644
--- a/boot/serial.zig
+++ b/boot/serial.zig
@@ -6,6 +6,23 @@
const COM1: u16 = 0x3F8;
+// 16550 register offsets from the base port
+const REG_DATA: u16 = 0; // rx/tx
+const REG_IER: u16 = 1; // interrupt enable
+const REG_FCR: u16 = 2; // fifo control (write)
+const REG_LCR: u16 = 3; // line control
+const REG_MCR: u16 = 4; // modem control
+const REG_LSR: u16 = 5; // line status
+
+const LCR_DLAB: u8 = 0x80; // divisor latch access bit
+const LCR_8N1: u8 = 0x03; // 8 data bits
+const LSR_THR_EMPTY: u8 = 0x20; // transmit holding register empty
+const FCR_ENABLE: u8 = 0xC7; // enable + clear fifos
+const MCR_READY: u8 = 0x0B; // dtr + rts + out2
+const BAUD_38400_LOW: u8 = 0x03; // divisor 3 low byte
+
+const MIRROR_SIZE = 1 << 15; // 32 kib, power of two
+
inline fn outb(port: u16, val: u8) void {
asm volatile ("outb %[val], %[port]"
:
@@ -29,26 +46,26 @@ inline fn inb(port: u16) u8 {
// idempotent so it should be relatively safe to call
// from the panic handler
pub fn init() void {
- outb(COM1 + 1, 0x00); // no interrupts
- outb(COM1 + 3, 0x80); // dlab on: the next two writes set the baud divisor
- outb(COM1 + 0, 0x03); // divisor 3 -> 38400 baud (low byte)
- outb(COM1 + 1, 0x00); // divisor high byte
- outb(COM1 + 3, 0x03); // dlab off, 8n1
- outb(COM1 + 2, 0xC7); // enable + clear fifos, 14 byte trigger
- outb(COM1 + 4, 0x0B); // rts/dsr set
+ outb(COM1 + REG_IER, 0x00); // no interrupts
+ outb(COM1 + REG_LCR, LCR_DLAB); // the next two writes set the baud divisor
+ outb(COM1 + REG_DATA, BAUD_38400_LOW); // divisor low byte
+ outb(COM1 + REG_IER, 0x00); // divisor high byte
+ outb(COM1 + REG_LCR, LCR_8N1); // dlab off
+ outb(COM1 + REG_FCR, FCR_ENABLE);
+ outb(COM1 + REG_MCR, MCR_READY);
}
// every byte we send to the port is also captured here so the revo
// console can render the same stream
-var mirror: [1 << 15]u8 = undefined; // 32 kib, power of two
+var mirror: [MIRROR_SIZE]u8 = undefined;
var m_head: usize = 0; // next byte the console will render
var m_tail: usize = 0; // next slot putc will write
// com1+5 (bit 5) = transmit holding register and
// spin until its clear to send
fn putc(byte: u8) void {
- while (inb(COM1 + 5) & 0x20 == 0) {}
- outb(COM1, byte);
+ while (inb(COM1 + REG_LSR) & LSR_THR_EMPTY == 0) {}
+ outb(COM1 + REG_DATA, byte);
mirror[m_tail & (mirror.len - 1)] = byte;
m_tail +%= 1;
}
diff --git a/boot/user.zig b/boot/user.zig
index 0cda97d..cff7b16 100644
--- a/boot/user.zig
+++ b/boot/user.zig
@@ -16,9 +16,33 @@ const O_CREAT: u64 = 0x40;
const O_TRUNC: u64 = 0x200;
const O_APPEND: u64 = 0x400;
+// syscall numbers
+const SYS_EXIT: u64 = 0;
+const SYS_WRITE: u64 = 1;
+const SYS_READ: u64 = 2;
+const SYS_FS_SIZE: u64 = 4;
+const SYS_FS_READ: u64 = 5;
+const SYS_FS_WRITE: u64 = 6;
+const SYS_OPEN: u64 = 7;
+const SYS_CLOSE: u64 = 8;
+const SYS_LSEEK: u64 = 9;
+const SYS_BRK: u64 = 10;
+const SYS_SBRK: u64 = 11;
+
+// lseek whence
+const SEEK_SET: u64 = 0;
+const SEEK_CUR: u64 = 1;
+const SEEK_END: u64 = 2;
+
+const SYSCALL_VECTOR: u8 = 0x80; // the int vector ring 3 traps through
+const INT80_LEN: u64 = 2; // length of the int 0x80 instruction, i use this to rewind rip
+const MAX_IO: u64 = 4096; // clamp on a single read/write
+const MAX_FS_WRITE: u64 = 64 * 1024; // clamp on a whole file write
+const MAX_NAME = 128;
+
// scratch for a filename copied out of the caller
// legit one static buffer is fine
-var name_buf: [128]u8 = undefined;
+var name_buf: [MAX_NAME]u8 = undefined;
fn userName(ptr: u64) []const u8 {
const p: [*]const u8 = @ptrFromInt(ptr);
var i: usize = 0;
@@ -51,7 +75,7 @@ pub const SyscallFrame = extern struct {
// install the int 0x80 gate at dpl 3 so ring3 code is allowed to invoke it
pub fn installSyscall() void {
- idt.setGate(0x80, @intFromPtr(&syscallStub), 3, 1); // ist1: big stack for vm calls
+ idt.setGate(SYSCALL_VECTOR, @intFromPtr(&syscallStub), 3, 1); // ist1: big stack for vm calls
}
// 1- save gp regs
@@ -74,39 +98,39 @@ export fn syscallHandler(frame: *SyscallFrame) callconv(.c) void {
// i js reorganized half of these so if anyone wrote code in asm
// for this (me), your syscalled are fucked
switch (frame.rax) {
- 0 => { // exit(code): code in rdi
+ SYS_EXIT => { // exit(code): code in rdi
sched.exitCurrent();
asm volatile ("sti");
while (true) asm volatile ("hlt");
},
- 1 => { // write(fd, ptr, len)
+ SYS_WRITE => { // write(fd, ptr, len)
const len = frame.rdx;
- if (sched.fdGet(@bitCast(frame.rdi))) |f| {
- if (f.kind == .console) {
- if (len > 0 and len <= 4096) {
+ if (sched.fdGet(@bitCast(frame.rdi))) |descriptor| {
+ if (descriptor.kind == .console) {
+ if (len > 0 and len <= MAX_IO) {
const bytes: [*]const u8 = @ptrFromInt(frame.rsi);
serial.write(bytes[0..len]);
}
frame.rax = len;
- } else if (f.kind == .file) {
- const n = @min(len, 4096);
+ } else if (descriptor.kind == .file) {
+ const count = @min(len, MAX_IO);
const src: [*]const u8 = @ptrFromInt(frame.rsi);
- if (bridge.fsWriteAt(f.path[0..f.path_len], f.offset, src[0..n])) {
- f.offset += n;
- frame.rax = n;
+ if (bridge.fsWriteAt(descriptor.path[0..descriptor.path_len], descriptor.offset, src[0..count])) {
+ descriptor.offset += count;
+ frame.rax = count;
} else frame.rax = SYS_ERR;
} else {
frame.rax = SYS_ERR; // keyboard isnt writable
}
} else frame.rax = SYS_ERR;
},
- 2 => { // read(fd, buf, count)
+ SYS_READ => { // read(fd, buf, count)
const count = frame.rdx;
- const f = sched.fdGet(@bitCast(frame.rdi)) orelse {
+ const descriptor = sched.fdGet(@bitCast(frame.rdi)) orelse {
frame.rax = SYS_ERR;
return;
};
- switch (f.kind) {
+ switch (descriptor.kind) {
.keyboard => {
if (count == 0) {
frame.rax = 0;
@@ -137,7 +161,7 @@ export fn syscallHandler(frame: *SyscallFrame) callconv(.c) void {
.rax = frame.rax,
.vector = 0,
.error_code = 0,
- .rip = frame.rip - 2,
+ .rip = frame.rip - INT80_LEN,
.cs = frame.cs,
.rflags = frame.rflags,
.rsp = frame.rsp,
@@ -148,16 +172,16 @@ export fn syscallHandler(frame: *SyscallFrame) callconv(.c) void {
while (true) asm volatile ("hlt");
},
.file => {
- const n = @min(count, 4096);
+ const clamped = @min(count, MAX_IO);
const dst: [*]u8 = @ptrFromInt(frame.rsi);
- const got = bridge.fsReadInto(f.path[0..f.path_len], f.offset, dst[0..n]);
- f.offset += got;
+ const got = bridge.fsReadInto(descriptor.path[0..descriptor.path_len], descriptor.offset, dst[0..clamped]);
+ descriptor.offset += got;
frame.rax = got;
},
else => frame.rax = SYS_ERR,
}
},
- 7 => { // open(path_ptr, flags)
+ SYS_OPEN => { // open(path_ptr, flags)
const name = userName(frame.rdi);
const flags = frame.rsi;
var size = bridge.fsSize(name);
@@ -178,46 +202,46 @@ export fn syscallHandler(frame: *SyscallFrame) callconv(.c) void {
return;
}
if (flags & O_APPEND != 0) {
- if (sched.fdGet(fd)) |f| f.offset = @intCast(size);
+ if (sched.fdGet(fd)) |descriptor| descriptor.offset = @intCast(size);
}
frame.rax = @bitCast(fd);
},
- 8 => { // close(fd)
+ SYS_CLOSE => { // close(fd)
frame.rax = @bitCast(sched.fdClose(@bitCast(frame.rdi)));
},
- 9 => { // lseek(fd, offset, whence)
- if (sched.fdGet(@bitCast(frame.rdi))) |f| {
+ SYS_LSEEK => { // lseek(fd, offset, whence)
+ if (sched.fdGet(@bitCast(frame.rdi))) |descriptor| {
switch (frame.rdx) {
- 0 => f.offset = frame.rsi, // SEEK_SET
- 1 => f.offset += frame.rsi, // SEEK_CUR
- 2 => { // SEEK_END
- const sz = bridge.fsSize(f.path[0..f.path_len]);
- if (sz >= 0) f.offset = @as(u64, @intCast(sz)) +% frame.rsi;
+ SEEK_SET => descriptor.offset = frame.rsi, // SEEK_SET
+ SEEK_CUR => descriptor.offset += frame.rsi, // SEEK_CUR
+ SEEK_END => {
+ const sz = bridge.fsSize(descriptor.path[0..descriptor.path_len]);
+ if (sz >= 0) descriptor.offset = @as(u64, @intCast(sz)) +% frame.rsi;
},
else => {},
}
- frame.rax = f.offset;
+ frame.rax = descriptor.offset;
} else frame.rax = SYS_ERR;
},
- 4 => { // fs_size(name_ptr)
+ SYS_FS_SIZE => { // fs_size(name_ptr)
frame.rax = @bitCast(bridge.fsSize(userName(frame.rdi)));
},
- 5 => { // fs_read(name_ptr, offset, buf_ptr, len)
- const len = @min(frame.rcx, 4096);
+ SYS_FS_READ => { // fs_read(name_ptr, offset, buf_ptr, len)
+ const len = @min(frame.rcx, MAX_IO);
const name = userName(frame.rdi);
const dst: [*]u8 = @ptrFromInt(frame.rdx);
frame.rax = bridge.fsReadInto(name, frame.rsi, dst[0..len]);
},
- 6 => { // fs_write(name_ptr, buf_ptr, len)
- const len = @min(frame.rdx, 64 * 1024);
+ SYS_FS_WRITE => { // fs_write(name_ptr, buf_ptr, len)
+ const len = @min(frame.rdx, MAX_FS_WRITE);
const name = userName(frame.rdi);
const src: [*]const u8 = @ptrFromInt(frame.rsi);
frame.rax = if (bridge.fsStore(name, src[0..len])) 1 else 0;
},
- 10 => { // brk(addr)
+ SYS_BRK => { // brk(addr)
frame.rax = @bitCast(sched.brk(frame.rdi));
},
- 11 => { // sbrk(increment)
+ SYS_SBRK => { // sbrk(increment)
frame.rax = @bitCast(sched.sbrk(@bitCast(frame.rdi)));
},
else => serial.write("[syscall] unknown\r\n"),
diff --git a/boot/vmm.zig b/boot/vmm.zig
index 8a20305..03d89ef 100644
--- a/boot/vmm.zig
+++ b/boot/vmm.zig
@@ -13,7 +13,7 @@ const pmm = @import("pmm.zig");
pub const PRESENT: u64 = 1 << 0;
pub const WRITE: u64 = 1 << 1;
pub const USER: u64 = 1 << 2; // 1 = ring 3 accessible
-pub const NX: u64 = 1 << 63; // warning: needs efer.nxe, dont set unless enabled
+pub const NX: u64 = 1 << 63; // WARNING: NEEDS EFER.NXE DO NOT SET UNLESS ENABLED
// bits 12..51 of an entry hold the physical frame address
const ADDR_MASK: u64 = 0x000F_FFFF_FFFF_F000;
@@ -52,19 +52,21 @@ fn zeroFrame(phys: u64) void {
// which 9 bit slice of the virtual address indexes level (3=pml4 .. 0=pt)
fn index(virt: u64, level: u6) usize {
- return @intCast((virt >> (12 + 9 * level)) & 0x1FF);
+ const page_shift = 12; // low 12 bits are the page offset
+ const index_bits = 9; // each level consumes 9 bits of the address
+ return @intCast((virt >> (page_shift + index_bits * level)) & 0x1FF);
}
// return the physical base of the next level table under entry i
// if missing, create one
fn nextTable(parent_phys: u64, i: usize, create: bool) ?u64 {
- const t = table(parent_phys);
- const entry = t[i];
+ const entries = table(parent_phys);
+ const entry = entries[i];
if (entry & PRESENT != 0) return entry & ADDR_MASK;
if (!create) return null;
const frame = pmm.alloc() orelse return null;
zeroFrame(frame);
- t[i] = frame | PRESENT | WRITE | USER;
+ entries[i] = frame | PRESENT | WRITE | USER;
return frame;
}
@@ -92,6 +94,7 @@ pub fn createAddressSpace() ?u64 {
zeroFrame(pml4);
const src = table(activePml4());
const dst = table(pml4);
+ // entries 256..511 are the shared kernel higher half
var i: usize = 256;
while (i < 512) : (i += 1) dst[i] = src[i];
return pml4;
@@ -106,5 +109,5 @@ pub fn translate(pml4_phys: u64, virt: u64) ?u64 {
const pt = nextTable(pd, index(virt, 1), false) orelse return null;
const entry = table(pt)[index(virt, 0)];
if (entry & PRESENT == 0) return null;
- return (entry & ADDR_MASK) | (virt & 0xFFF);
+ return (entry & ADDR_MASK) | (virt & 0xFFF); // frame base | page offset
}