From cc8768d33a6ff6cc64306d01679a10c3a1847736 Mon Sep 17 00:00:00 2001 From: hachem Date: Sat, 10 Oct 2026 10:31:01 +0200 Subject: chore: small refactor --- boot/ata.zig | 34 ++++++++++++-------- boot/boot.zig | 64 +++++++++++++++++++++---------------- boot/gdt.zig | 23 ++++++-------- boot/heap.zig | 64 ++++++++++++++++++++----------------- boot/idt.zig | 22 ++++++++----- boot/limine.zig | 6 ++-- boot/pic.zig | 2 +- boot/pmm.zig | 54 +++++++++++++++---------------- boot/sched.zig | 76 ++++++++++++++++++++++---------------------- boot/serial.zig | 37 ++++++++++++++++------ boot/user.zig | 98 +++++++++++++++++++++++++++++++++++---------------------- boot/vmm.zig | 15 +++++---- 12 files changed, 284 insertions(+), 211 deletions(-) (limited to 'boot') 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 } -- cgit v1.3