hachem.wtf
aboutsummaryrefslogtreecommitdiff
path: root/boot/user.zig
blob: 1d0839f3845d704f87c58e1ad4d9e83f44edbdb1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// 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

const serial = @import("serial.zig");
const idt = @import("idt.zig");
const sched = @import("sched.zig");
const regs = @import("regs.zig");
const Frame = regs.Frame;

// 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,
    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, 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 (regs.PUSH_GPRS ++
            \\
            \\ mov %rsp, %rdi
            \\ call syscallHandler
            \\
        ++ regs.POP_GPRS ++
            \\
            \\ iretq
    );
}

export fn syscallHandler(frame: *SyscallFrame) callconv(.c) void {
    switch (frame.rax) {
        0 => { // exit(code): code in rdi
            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 processs address space is active during the syscall, so the
            // kernel can read the user pointer directly. cap the length so a
            // 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 -> kernel tasks on_tick renders it
            }
            frame.rax = len; // syscall return value
        },
        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"),
    }
}