diff options
Diffstat (limited to 'boot')
| -rw-r--r-- | boot/boot.zig | 33 | ||||
| -rw-r--r-- | boot/elf.zig | 2 | ||||
| -rw-r--r-- | boot/serial.zig | 18 | ||||
| -rw-r--r-- | boot/user.zig | 39 |
4 files changed, 71 insertions, 21 deletions
diff --git a/boot/boot.zig b/boot/boot.zig index 4e9d0b4..108afc8 100644 --- a/boot/boot.zig +++ b/boot/boot.zig @@ -8,6 +8,7 @@ const pmm = @import("pmm.zig"); const heap = @import("heap.zig"); const vmm = @import("vmm.zig"); const user = @import("user.zig"); +const elf = @import("elf.zig"); const keyboard = @import("keyboard.zig"); const bridge = @import("bridge"); @@ -48,6 +49,21 @@ export var memmap_request: limine.MemoryMapRequest linksection(".limine_requests // 4 MiB stack export var stack_size_request: limine.StackSizeRequest linksection(".limine_requests") = .{ .stack_size = 4 * 1024 * 1024 }; +// 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 kAllocFrame() u64 { + return pmm.alloc() orelse 0; +} +fn kCreateAddrspace() u64 { + return vmm.createAddressSpace() orelse 0; +} +fn kSerialNext() i64 { + return if (serial.mirrorNext()) |c| @intCast(c) else -1; +} + // ENTRY(_start) export fn _start() callconv(.c) noreturn { serial.init(); @@ -121,16 +137,27 @@ export fn _start() callconv(.c) noreturn { serial.write("vmm test: switched back OK\r\n"); } - user.runTestProgram(); + const elf_pages = (elf.hello_elf.len + 4095) / 4096; + const elf_phys = pmm.allocContig(elf_pages) orelse @panic("no room for the embedded ELF"); + @memcpy(@as([*]u8, @ptrFromInt(pmm.physToVirt(elf_phys)))[0..elf.hello_elf.len], elf.hello_elf); + const kops: bridge.KernelOps = .{ + .phys_to_virt = &kPhysToVirt, + .alloc_frame = &kAllocFrame, + .create_addrspace = &kCreateAddrspace, + .run_process = &user.runProcess, + .serial_next = &kSerialNext, + .elf_phys = elf_phys, + .elf_size = elf.hello_elf.len, + }; - // hand the kernel heap + raw framebuffer to revo and bring up the VM + // 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, } else null; - bridge.boot(heap.allocator(), serial.write, fb); + bridge.boot(heap.allocator(), serial.write, fb, kops); // - sleep until an interrupt // - drain the keyboard diff --git a/boot/elf.zig b/boot/elf.zig new file mode 100644 index 0000000..a9199db --- /dev/null +++ b/boot/elf.zig @@ -0,0 +1,2 @@ +// load c program +pub const hello_elf = @embedFile("user_program"); diff --git a/boot/serial.zig b/boot/serial.zig index fab0e66..ca0bb08 100644 --- a/boot/serial.zig +++ b/boot/serial.zig @@ -38,11 +38,29 @@ pub fn init() void { outb(COM1 + 4, 0x0B); // RTS/DSR set } +// 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 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 it's clear to send fn putc(c: u8) void { while (inb(COM1 + 5) & 0x20 == 0) {} outb(COM1, c); + mirror[m_tail & (mirror.len - 1)] = c; + m_tail +%= 1; +} + +// next un-rendered byte, or null if caught up. if the console fell far enough +// behind to lap the ring, drop the oldest bytes. +pub fn mirrorNext() ?u8 { + if (m_head == m_tail) return null; + if (m_tail -% m_head > mirror.len) m_head = m_tail -% mirror.len; + const c = mirror[m_head & (mirror.len - 1)]; + m_head +%= 1; + return c; } // a raw serial console needs \r\n, but revo (and most sane code) emits bare \n. diff --git a/boot/user.zig b/boot/user.zig index 61345e5..4272269 100644 --- a/boot/user.zig +++ b/boot/user.zig @@ -1,7 +1,7 @@ // ring3 entry + the syscall boundary // drop user mode with iretq, and come back to the kernel // -// see: - https://wiki.osdev.org/System_Calls +// see: - https://wiki.osdev.org/System_Calls // - https://wiki.osdev.org/Getting_to_Ring_3 const serial = @import("serial.zig"); @@ -52,17 +52,7 @@ pub fn enterUser(entry: u64, ustack: u64) void { g_user_stack = ustack; // this is the truest most correct statement ever // get this man a true - asm volatile ("call enterUserAsm" ::: .{ - .memory = true, - .rax = true, - .rcx = true, - .rdx = true, - .rsi = true, - .rdi = true, - .r8 = true, - .r9 = true, - .r10 = true, - .r11 = true }); + asm volatile ("call enterUserAsm" ::: .{ .memory = true, .rax = true, .rcx = true, .rdx = true, .rsi = true, .rdi = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true }); } export fn enterUserAsm() callconv(.naked) void { @@ -134,15 +124,28 @@ export fn syscallStub() callconv(.naked) void { ); } -// returns 0 to resume the program, 1 to return to the kernel (exit). +pub fn runProcess(pml4: u64, entry: u64, ustack: u64) void { + const prev = vmm.activePml4(); + vmm.loadPml4(pml4); + enterUser(entry, ustack); + vmm.loadPml4(prev); +} + export fn syscallHandler(frame: *SyscallFrame) callconv(.c) u64 { switch (frame.rax) { - 0 => { // exit - serial.write("[syscall] exit -> back to kernel\r\n"); - return 1; + 0 => { // exit(code): code in rdi + return 1; // tell the stub to unwind back into the kernel }, - 1 => { // "hello" -- a fixed message, no user pointer to validate yet - serial.write("[syscall] hello from ring 3!\r\n"); + 1 => { // write(ptr, len): rdi = user pointer, rsi = length + const len = frame.rsi; + // the process's address space is active during the syscall, so the + // kernel can read the user pointer directly. cap the length so a + // bogus arg can't run us off into unmapped memory. + if (len > 0 and len <= 4096) { + const bytes: [*]const u8 = @ptrFromInt(frame.rdi); + serial.write(bytes[0..len]); // mirrored -> the revo console renders it + } + frame.rax = len; // syscall return value return 0; }, else => { |
