hachem.wtf
aboutsummaryrefslogtreecommitdiff
path: root/boot
diff options
context:
space:
mode:
Diffstat (limited to 'boot')
-rw-r--r--boot/ata.zig1
-rw-r--r--boot/boot.zig37
-rw-r--r--boot/elf.zig10
-rw-r--r--boot/sched.zig23
-rw-r--r--boot/user.zig69
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"),
}