diff options
Diffstat (limited to 'boot')
| -rw-r--r-- | boot/ata.zig | 1 | ||||
| -rw-r--r-- | boot/boot.zig | 37 | ||||
| -rw-r--r-- | boot/elf.zig | 10 | ||||
| -rw-r--r-- | boot/sched.zig | 23 | ||||
| -rw-r--r-- | boot/user.zig | 69 |
5 files changed, 91 insertions, 49 deletions
diff --git a/boot/ata.zig b/boot/ata.zig index ec63c1c..4c5afe9 100644 --- a/boot/ata.zig +++ b/boot/ata.zig @@ -125,7 +125,6 @@ pub fn init() void { serial.write("\r\n"); } -// read one 512 byte sector at lba into buf pub fn read(lba: u28, buf: *[SECTOR]u8) bool { if (!g_present) return false; if (!waitNotBusy()) return false; diff --git a/boot/boot.zig b/boot/boot.zig index 28fb8c1..5a3c429 100644 --- a/boot/boot.zig +++ b/boot/boot.zig @@ -101,10 +101,6 @@ fn kProcCount() u64 { return sched.procCount(); } -// block device, revo builds the filesystem on top of these -fn kDiskPresent() u64 { - return if (ata.present()) 1 else 0; -} fn kDiskSectors() u64 { return ata.sectorCount(); } @@ -117,6 +113,29 @@ fn kDiskWrite(lba: u64, phys: u64) u64 { return if (ata.write(@truncate(lba), buf)) 1 else 0; } +// the embedded programs, copied to frames at boot so revo can load them by name +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; + 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 }; + } +} + +fn kProgCount() u64 { + return g_progs.len; +} +fn kProgPhys(i: u64) u64 { + return g_progs[i].phys; +} +fn kProgName(i: u64) []const u8 { + return g_progs[i].name; +} + // entry(_start) export fn _start() callconv(.c) noreturn { serial.init(); @@ -143,9 +162,7 @@ export fn _start() callconv(.c) noreturn { vmmSelfTest(); ata.init(); - 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); + loadPrograms(); // the event loop below is scheduler task 0 (the kernel), running in the // current cr3. user tasks spawned by `run` get time fucked against it sched.init(vmm.activePml4()); @@ -166,9 +183,9 @@ export fn _start() callconv(.c) noreturn { .mem_total = &kMemTotal, .uptime = &kUptime, .proc_count = &kProcCount, - .elf_phys = elf_phys, - .elf_size = elf.hello_elf.len, - .disk_present = &kDiskPresent, + .prog_count = &kProgCount, + .prog_phys = &kProgPhys, + .prog_name = &kProgName, .disk_sectors = &kDiskSectors, .disk_read = &kDiskRead, .disk_write = &kDiskWrite, diff --git a/boot/elf.zig b/boot/elf.zig index a9199db..906e380 100644 --- a/boot/elf.zig +++ b/boot/elf.zig @@ -1,2 +1,8 @@ -// load c program -pub const hello_elf = @embedFile("user_program"); +// the embedded ring 3 programs, boot copies each to frames at startup +pub const Program = struct { name: []const u8, bytes: []const u8 }; + +pub const programs = [_]Program{ + .{ .name = "hexview", .bytes = @embedFile("user_hexview") }, + .{ .name = "calc", .bytes = @embedFile("user_calc") }, + .{ .name = "primes", .bytes = @embedFile("user_primes") }, +}; diff --git a/boot/sched.zig b/boot/sched.zig index 95b94e0..60a8fa0 100644 --- a/boot/sched.zig +++ b/boot/sched.zig @@ -1,22 +1,14 @@ -// tiny preemptive round robin scheduler -// -// every schedulable thing is a task holding a saved register frame + its cr3 -// task 0 is the kernel itself (the event loop / shell), always ready. the pit -// tick (idt.zig -> tick) is where we preempt: save the interrupted tasks frame, -// pick the next ready task, copy its frame back over the one on the stack, and -// the irq return path iretqs straight into it. iretq handles ring 0 <-> ring 3 -// the same way (long mode always restores ss:rsp), so one swap covers leaving -// either the kernel or a process -// -// each user task gets its own kernel stack (for its ring3->ring0 syscall/irq -// entries) and we point tss.rsp0 at it on switch. thats what keeps preempting a -// task mid syscall safe (e.g. read()s sti+hlt). a shared rsp0 would get -// clobbered the next time any task trapped into the kernel +// tiny preemptive round robin scheduler. a task is a saved frame + cr3 + kernel +// stack, task 0 is the kernel event loop. the pit tick swaps the interrupted +// frame for the next ready tasks and iretqs in, which covers ring 0 and ring 3 +// alike. each task has its own kernel stack so preempting one mid syscall (e.g. a +// read() parked on sti/hlt) doesnt clobber anothers rsp0 const std = @import("std"); const Frame = @import("regs.zig").Frame; const vmm = @import("vmm.zig"); const gdt = @import("gdt.zig"); +const bridge = @import("bridge"); const MAX_TASKS = 8; const KSTACK_SIZE = 16 * 1024; @@ -156,6 +148,9 @@ pub fn deliverInput(byte: u8) bool { // the preemption point, called from the pit irq with the interrupted frame pub fn tick(frame: *Frame) void { + // dont preempt task 0 mid vm call, the shared fiber cant be re-entered + if (bridge.vmBusy()) return; + if (tasks[current].state == .dead) { tasks[current].state = .free; // reclaim, dont bother saving its context } else if (tasks[current].state == .blocked) { diff --git a/boot/user.zig b/boot/user.zig index 1d0839f..5ba6604 100644 --- a/boot/user.zig +++ b/boot/user.zig @@ -1,15 +1,23 @@ -// 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 -// +// int 0x80 syscall gate + handlers. ring 3 entry itself lives in sched.zig // 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 bridge = @import("bridge"); const Frame = regs.Frame; +// scratch for a filename copied out of the caller. one static buffer is fine, +// syscalls run one at a time (if=0) +var name_buf: [64]u8 = undefined; +fn userName(ptr: u64) []const u8 { + const p: [*]const u8 = @ptrFromInt(ptr); + var i: usize = 0; + while (i < name_buf.len - 1 and p[i] != 0) : (i += 1) name_buf[i] = p[i]; + return name_buf[0..i]; +} + // 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) @@ -58,42 +66,59 @@ export fn syscallStub() callconv(.naked) void { 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 + sched.exitCurrent(); asm volatile ("sti"); - while (true) asm volatile ("hlt"); // never returns to this task + while (true) asm volatile ("hlt"); }, 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 + serial.write(bytes[0..len]); } 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, + .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 + while (true) asm volatile ("hlt"); + }, + 4 => { // fs_size(name_ptr): returns size (like obv), or -1 + frame.rax = @bitCast(bridge.fsSize(userName(frame.rdi))); + }, + 5 => { // fs_read(name_ptr, offset, buf_ptr, len): returns bytes read + const len = @min(frame.rcx, 4096); + const name = userName(frame.rdi); + const dst: [*]u8 = @ptrFromInt(frame.rdx); + frame.rax = bridge.fsReadInto(name, frame.rsi, dst[0..len]); }, else => serial.write("[syscall] unknown\r\n"), } |
