hachem.wtf
aboutsummaryrefslogtreecommitdiff
path: root/boot/user.zig
diff options
context:
space:
mode:
Diffstat (limited to 'boot/user.zig')
-rw-r--r--boot/user.zig181
1 files changed, 51 insertions, 130 deletions
diff --git a/boot/user.zig b/boot/user.zig
index 4272269..1d0839f 100644
--- a/boot/user.zig
+++ b/boot/user.zig
@@ -1,22 +1,18 @@
-// ring3 entry + the syscall boundary
-// drop user mode with iretq, and come back to the kernel
+// the syscall boundary (int 0x80). ring 3 entry itself now lives in the
+// scheduler (boot/sched.zig), which iretqs into tasks, this file is just the
+// syscall gate + handlers
//
-// see: - https://wiki.osdev.org/System_Calls
-// - https://wiki.osdev.org/Getting_to_Ring_3
+// see: https://wiki.osdev.org/System_Calls
const serial = @import("serial.zig");
const idt = @import("idt.zig");
-const vmm = @import("vmm.zig");
-const pmm = @import("pmm.zig");
+const sched = @import("sched.zig");
+const regs = @import("regs.zig");
+const Frame = regs.Frame;
-// 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
+// what the syscall stub leaves on the stack: the gp regs it pushed, then the
+// frame the cpu pushed on int 0x80. rax holds the syscall number in, and the
+// handler writes the return value back into it (restored by the stubs pop)
pub const SyscallFrame = extern struct {
r15: u64,
r14: u64,
@@ -40,140 +36,65 @@ pub const SyscallFrame = extern struct {
ss: u64,
};
-// install the int 0x80 gate at DPL 3 so ring3 code is allowed to invoke it.
+// 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
- );
+ idt.setGate(0x80, @intFromPtr(&syscallStub), 3, 1); // ist1: big stack for vm calls
}
+// save gp regs, hand the frame to the zig handler, restore, iretq. the handler
+// returns via syscallframe.rax (restored by the pop below), exit() never returns
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
+ asm volatile (regs.PUSH_GPRS ++
+ \\
+ \\ mov %rsp, %rdi
+ \\ call syscallHandler
+ \\
+ ++ regs.POP_GPRS ++
+ \\
+ \\ iretq
);
}
-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 {
+export fn syscallHandler(frame: *SyscallFrame) callconv(.c) void {
switch (frame.rax) {
0 => { // exit(code): code in rdi
- return 1; // tell the stub to unwind back into the kernel
+ sched.exitCurrent(); // mark dead, the next tick reclaims + switches away
+ asm volatile ("sti");
+ while (true) asm volatile ("hlt"); // never returns to this task
},
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
+ // the processs 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.
+ // bogus arg cant 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
+ serial.write(bytes[0..len]); // mirrored -> kernel tasks on_tick renders it
}
frame.rax = len; // syscall return value
- return 0;
},
- else => {
- serial.write("[syscall] unknown\r\n");
- return 0;
+ 2 => { // read(): return a buffered byte, else block for one
+ // become the foreground reader, if a byte is already queued, take it
+ if (sched.readReady()) |byte| {
+ frame.rax = byte;
+ return;
+ }
+ // otherwise save a context that resumes right after this int 0x80
+ // (with rax = the delivered byte), mark the task blocked, and yield
+ // the kernel task decodes keys and calls sched.deliverinput to wake us
+ const resume_frame: Frame = .{
+ .r15 = frame.r15, .r14 = frame.r14, .r13 = frame.r13, .r12 = frame.r12,
+ .r11 = frame.r11, .r10 = frame.r10, .r9 = frame.r9, .r8 = frame.r8,
+ .rbp = frame.rbp, .rdi = frame.rdi, .rsi = frame.rsi, .rdx = frame.rdx,
+ .rcx = frame.rcx, .rbx = frame.rbx, .rax = frame.rax,
+ .vector = 0, .error_code = 0,
+ .rip = frame.rip, .cs = frame.cs, .rflags = frame.rflags,
+ .rsp = frame.rsp, .ss = frame.ss,
+ };
+ sched.blockCurrentOnRead(resume_frame);
+ asm volatile ("sti");
+ while (true) asm volatile ("hlt"); // yield, resumed via rf when input arrives
},
+ else => serial.write("[syscall] unknown\r\n"),
}
}
-
-// 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");
-}