diff options
Diffstat (limited to 'boot')
| -rw-r--r-- | boot/boot.zig | 4 | ||||
| -rw-r--r-- | boot/idt.zig | 12 | ||||
| -rw-r--r-- | boot/user.zig | 176 |
3 files changed, 187 insertions, 5 deletions
diff --git a/boot/boot.zig b/boot/boot.zig index 77e7bb6..4e9d0b4 100644 --- a/boot/boot.zig +++ b/boot/boot.zig @@ -7,6 +7,7 @@ const idt = @import("idt.zig"); const pmm = @import("pmm.zig"); const heap = @import("heap.zig"); const vmm = @import("vmm.zig"); +const user = @import("user.zig"); const keyboard = @import("keyboard.zig"); const bridge = @import("bridge"); @@ -54,6 +55,7 @@ export fn _start() callconv(.c) noreturn { gdt.load(); idt.init(); + user.installSyscall(); // int 0x80 gate at DPL 3 // NOTE: CPU interrupts stay off until the event loop's sti // which is after the VM has run that\ serial.write("gdt + idt loaded\r\n"); @@ -119,6 +121,8 @@ export fn _start() callconv(.c) noreturn { serial.write("vmm test: switched back OK\r\n"); } + user.runTestProgram(); + // hand the kernel heap + raw framebuffer to revo and bring up the VM const fb: ?bridge.Fb = if (framebuffer.get()) |s| .{ .ptr = s.address, diff --git a/boot/idt.zig b/boot/idt.zig index a9898d6..fda5b9d 100644 --- a/boot/idt.zig +++ b/boot/idt.zig @@ -160,7 +160,7 @@ export fn exceptionHandler(frame: *Frame) callconv(.c) noreturn { while (true) asm volatile ("hlt"); } -// hardware IRQ. +// hardware IRQ // unlike exceptions these must return so we acknowledge the PIC and iretq // back to whatever we interrupted. the stub mirrors the exception one (dummy // error code + vector for a uniform Frame) but jumps to the returning path. @@ -225,12 +225,14 @@ export fn irqDispatch(frame: *Frame) callconv(.c) void { pic.eoi(@intCast(frame.vector - pic.MASTER_OFFSET)); } -fn setGate(vector: u8, handler: u64) void { +// dpl 0 = only ring 0 can invoke via `int` +// dpl 3 = ring 3 may (for syscalls) +pub fn setGate(vector: u8, handler: u64, dpl: u2) void { idt[vector] = .{ .offset_low = @truncate(handler), .selector = gdt.KERNEL_CODE, .ist = 0, - .type_attr = 0x8E, + .type_attr = 0x8E | (@as(u8, dpl) << 5), .offset_mid = @truncate(handler >> 16), .offset_high = @truncate(handler >> 32), .reserved = 0, @@ -239,10 +241,10 @@ fn setGate(vector: u8, handler: u64) void { // fill the exception vectors and load the IDT pub fn init() void { - inline for (0..32) |v| setGate(v, @intFromPtr(&stub(v))); + inline for (0..32) |v| setGate(v, @intFromPtr(&stub(v)), 0); // IRQ1 (keyboard) -> vector 0x21 // the PIC is remapped separately in boot. - setGate(pic.MASTER_OFFSET + 1, @intFromPtr(&irqStub(pic.MASTER_OFFSET + 1))); + setGate(pic.MASTER_OFFSET + 1, @intFromPtr(&irqStub(pic.MASTER_OFFSET + 1)), 0); idtr = .{ .limit = @sizeOf(@TypeOf(idt)) - 1, .base = @intFromPtr(&idt) }; asm volatile ("lidt (%[idtr])" : diff --git a/boot/user.zig b/boot/user.zig new file mode 100644 index 0000000..61345e5 --- /dev/null +++ b/boot/user.zig @@ -0,0 +1,176 @@ +// ring3 entry + the syscall boundary +// drop user mode with iretq, and come back to the kernel +// +// see: - https://wiki.osdev.org/System_Calls +// - https://wiki.osdev.org/Getting_to_Ring_3 + +const serial = @import("serial.zig"); +const idt = @import("idt.zig"); +const vmm = @import("vmm.zig"); +const pmm = @import("pmm.zig"); + +// where enterUserAsm parked the kernel stack +export var kernel_return_rsp: u64 = 0; +// args to enterUserAsm, passed via globals so it can stay a clean naked fn :flushed: +export var g_user_entry: u64 = 0; +export var g_user_stack: u64 = 0; + +// what the syscall stub leaves on the stack (GP regs it pushed, then the frame the CPU pushed on the int 0x80) +// rax holds the syscall number +pub const SyscallFrame = extern struct { + r15: u64, + r14: u64, + r13: u64, + r12: u64, + r11: u64, + r10: u64, + r9: u64, + r8: u64, + rbp: u64, + rdi: u64, + rsi: u64, + rdx: u64, + rcx: u64, + rbx: u64, + rax: u64, + rip: u64, + cs: u64, + rflags: u64, + rsp: u64, + ss: u64, +}; + +// 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); +} + +// drop to ring 3 at g_user_entry with stack g_user_stack +// returns (via the exit syscall s longjmp) once the program calls exit +pub fn enterUser(entry: u64, ustack: u64) void { + g_user_entry = entry; + 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 }); +} + +export fn enterUserAsm() callconv(.naked) void { + asm volatile ( + \\ push %rbx + \\ push %rbp + \\ push %r12 + \\ push %r13 + \\ push %r14 + \\ push %r15 + \\ mov %rsp, kernel_return_rsp(%rip) + \\ pushq $0x23 // SS = user data (0x20) | RPL 3 + \\ mov g_user_stack(%rip), %rax + \\ push %rax // user RSP + \\ pushq $0x2 // RFLAGS: reserved bit only (IF=0 for the demo) + \\ pushq $0x1b // CS = user code (0x18) | RPL 3 + \\ mov g_user_entry(%rip), %rax + \\ push %rax // RIP = entry + \\ iretq + ); +} + +export fn syscallStub() callconv(.naked) void { + asm volatile ( + \\ push %rax + \\ push %rbx + \\ push %rcx + \\ push %rdx + \\ push %rsi + \\ push %rdi + \\ push %rbp + \\ push %r8 + \\ push %r9 + \\ push %r10 + \\ push %r11 + \\ push %r12 + \\ push %r13 + \\ push %r14 + \\ push %r15 + \\ mov %rsp, %rdi + \\ call syscallHandler // rax = 0 continue, 1 exit + \\ test %rax, %rax + \\ jnz 1f + \\ pop %r15 + \\ pop %r14 + \\ pop %r13 + \\ pop %r12 + \\ pop %r11 + \\ pop %r10 + \\ pop %r9 + \\ pop %r8 + \\ pop %rbp + \\ pop %rdi + \\ pop %rsi + \\ pop %rdx + \\ pop %rcx + \\ pop %rbx + \\ pop %rax + \\ iretq + \\ 1: + \\ mov kernel_return_rsp(%rip), %rsp + \\ pop %r15 + \\ pop %r14 + \\ pop %r13 + \\ pop %r12 + \\ pop %rbp + \\ pop %rbx + \\ ret + ); +} + +// returns 0 to resume the program, 1 to return to the kernel (exit). +export fn syscallHandler(frame: *SyscallFrame) callconv(.c) u64 { + switch (frame.rax) { + 0 => { // exit + serial.write("[syscall] exit -> back to kernel\r\n"); + return 1; + }, + 1 => { // "hello" -- a fixed message, no user pointer to validate yet + serial.write("[syscall] hello from ring 3!\r\n"); + return 0; + }, + else => { + serial.write("[syscall] unknown\r\n"); + return 0; + }, + } +} + +// mov $1,%eax ; int $0x80 (hello) +// xor %eax,%eax ; int $0x80 (exit) +pub fn runTestProgram() void { + const as = vmm.createAddressSpace() orelse return; + const code_frame = pmm.alloc() orelse return; + const stack_frame = pmm.alloc() orelse return; + const code_virt: u64 = 0x400000; // 4 MiB + const stack_virt: u64 = 0x800000; // 8 MiB + _ = vmm.map(as, code_virt, code_frame, vmm.WRITE | vmm.USER); + _ = vmm.map(as, stack_virt, stack_frame, vmm.WRITE | vmm.USER); + + const code = [_]u8{ 0xB8, 0x01, 0x00, 0x00, 0x00, 0xCD, 0x80, 0x31, 0xC0, 0xCD, 0x80 }; + const dst: [*]u8 = @ptrFromInt(pmm.physToVirt(code_frame)); // write via HHDM + @memcpy(dst[0..code.len], &code); + + const prev = vmm.activePml4(); + vmm.loadPml4(as); + serial.write("[user] entering ring 3...\r\n"); + enterUser(code_virt, stack_virt + pmm.PAGE_SIZE); + vmm.loadPml4(prev); + serial.write("[user] back in the kernel, continuing\r\n"); +} |
