From 852a5b237116e8b53ae18ea8ea00505e44b40dfe Mon Sep 17 00:00:00 2001 From: hachem Date: Thu, 8 Oct 2026 22:23:55 +0200 Subject: feat: file descriptor and proper stdio --- boot/elf.zig | 1 + boot/sched.zig | 59 +++++++++++++++++++- boot/user.zig | 172 +++++++++++++++++++++++++++++++++++++++++++-------------- 3 files changed, 188 insertions(+), 44 deletions(-) (limited to 'boot') diff --git a/boot/elf.zig b/boot/elf.zig index 2070db6..a6e621c 100644 --- a/boot/elf.zig +++ b/boot/elf.zig @@ -4,4 +4,5 @@ pub const programs = [_]Program{ .{ .name = "calc", .bytes = @embedFile("user_calc") }, .{ .name = "primes", .bytes = @embedFile("user_primes") }, .{ .name = "save", .bytes = @embedFile("user_save") }, + .{ .name = "chat", .bytes = @embedFile("user_chat") }, }; diff --git a/boot/sched.zig b/boot/sched.zig index 383b40d..30d5698 100644 --- a/boot/sched.zig +++ b/boot/sched.zig @@ -8,9 +8,20 @@ const bridge = @import("bridge"); const MAX_TASKS = 8; const KSTACK_SIZE = 16 * 1024; +const MAX_FDS = 8; +const MAX_PATH = 128; const State = enum { free, ready, dead, blocked }; +// file descriptor not "foid decimator" +pub const FdKind = enum { closed, keyboard, console, file }; +pub const Fd = struct { + kind: FdKind = .closed, + offset: u64 = 0, + path_len: usize = 0, + path: [MAX_PATH]u8 = @splat(0), +}; + const Task = struct { frame: Frame = std.mem.zeroes(Frame), cr3: u64 = 0, @@ -20,6 +31,7 @@ const Task = struct { var tasks: [MAX_TASKS]Task = @splat(.{}); var kstacks: [MAX_TASKS][KSTACK_SIZE]u8 align(16) = undefined; +var fdtabs: [MAX_TASKS][MAX_FDS]Fd = @splat(@splat(.{})); var current: usize = 0; var blocked_reader: ?usize = null; @@ -53,10 +65,10 @@ pub fn spawn(cr3: u64, entry: u64, ustack: u64, arg: u64, arglen: u64) void { var f = std.mem.zeroes(Frame); f.rip = entry; f.cs = 0x1b; // user code (0x18) | rpl 3 - f.rflags = 0x202; // reserved bit + if=1, so its preemptible + f.rflags = 0x202; // reserved bit + if=1 so its preemptible f.rsp = ustack; f.ss = 0x23; // user data (0x20) | rpl 3 - f.rdi = arg; // _start(arg_ptr, arg_len), the sysv first two args + f.rdi = arg; // _start(arg_ptr, arg_len) the sysv first two args f.rsi = arglen; tasks[i] = .{ .frame = f, @@ -64,10 +76,51 @@ pub fn spawn(cr3: u64, entry: u64, ustack: u64, arg: u64, arglen: u64) void { .kstack_top = @intFromPtr(&kstacks[i]) + KSTACK_SIZE, .state = .ready, }; + initFds(i); return; } } +// stdin at keyboard +fn initFds(i: usize) void { + for (&fdtabs[i]) |*f| f.* = .{}; + fdtabs[i][0].kind = .keyboard; + fdtabs[i][1].kind = .console; + fdtabs[i][2].kind = .console; +} + +// foid decimator open +pub fn fdOpen(path: []const u8) i64 { + var i: usize = 3; + while (i < MAX_FDS) : (i += 1) { + const f = &fdtabs[current][i]; + if (f.kind == .closed) { + f.kind = .file; + f.offset = 0; + const n = @min(path.len, MAX_PATH); + @memcpy(f.path[0..n], path[0..n]); + f.path_len = n; + return @intCast(i); + } + } + return -1; +} + +// foid decimator get +pub fn fdGet(fd: i64) ?*Fd { + if (fd < 0 or fd >= MAX_FDS) return null; + const f = &fdtabs[current][@intCast(fd)]; + if (f.kind == .closed) return null; + return f; +} + +// foid decimator close +pub fn fdClose(fd: i64) i64 { + const f = fdGet(fd) orelse return -1; + f.kind = .closed; + return 0; +} + pub fn procCount() u64 { var count: u64 = 0; var i: usize = 1; @@ -122,7 +175,7 @@ pub fn blockCurrentOnRead(resume_frame: Frame) void { pub fn deliverInput(byte: u8) bool { if (blocked_reader) |task| { - tasks[task].frame.rax = byte; + stdinPush(byte); tasks[task].state = .ready; blocked_reader = null; return true; diff --git a/boot/user.zig b/boot/user.zig index 9bce9e9..78d20ff 100644 --- a/boot/user.zig +++ b/boot/user.zig @@ -8,8 +8,17 @@ const regs = @import("regs.zig"); const bridge = @import("bridge"); const Frame = regs.Frame; -// scratch for a filename copied out of the caller legit one static buffer is fine -var name_buf: [64]u8 = undefined; +// what a syscall returns in rax on failure +const SYS_ERR: u64 = @bitCast(@as(i64, -1)); + +// open() flags (linux values lmao) +const O_CREAT: u64 = 0x40; +const O_TRUNC: u64 = 0x200; +const O_APPEND: u64 = 0x400; + +// scratch for a filename copied out of the caller +// legit one static buffer is fine +var name_buf: [128]u8 = undefined; fn userName(ptr: u64) []const u8 { const p: [*]const u8 = @ptrFromInt(ptr); var i: usize = 0; @@ -62,63 +71,144 @@ export fn syscallStub() callconv(.naked) void { } export fn syscallHandler(frame: *SyscallFrame) callconv(.c) void { + // i js reorganized half of these so if anyone wrote code in asm + // for this (me), your syscalled are fucked switch (frame.rax) { 0 => { // exit(code): code in rdi sched.exitCurrent(); asm volatile ("sti"); while (true) asm volatile ("hlt"); }, - 1 => { // write(ptr, len): rdi = user pointer, rsi = length - const len = frame.rsi; - if (len > 0 and len <= 4096) { - const bytes: [*]const u8 = @ptrFromInt(frame.rdi); - serial.write(bytes[0..len]); + 1 => { // write(fd, ptr, len) + const len = frame.rdx; + if (sched.fdGet(@bitCast(frame.rdi))) |f| { + if (f.kind == .console) { + if (len > 0 and len <= 4096) { + const bytes: [*]const u8 = @ptrFromInt(frame.rsi); + serial.write(bytes[0..len]); + } + frame.rax = len; + } else if (f.kind == .file) { + const n = @min(len, 4096); + const src: [*]const u8 = @ptrFromInt(frame.rsi); + if (bridge.fsWriteAt(f.path[0..f.path_len], f.offset, src[0..n])) { + f.offset += n; + frame.rax = n; + } else frame.rax = SYS_ERR; + } else { + frame.rax = SYS_ERR; // keyboard isnt writable + } + } else frame.rax = SYS_ERR; + }, + 2 => { // read(fd, buf, count) + const count = frame.rdx; + const f = sched.fdGet(@bitCast(frame.rdi)) orelse { + frame.rax = SYS_ERR; + return; + }; + switch (f.kind) { + .keyboard => { + if (count == 0) { + frame.rax = 0; + return; + } + if (sched.readReady()) |byte| { + const dst: [*]u8 = @ptrFromInt(frame.rsi); + dst[0] = byte; + frame.rax = 1; + return; + } + // nothn buffered + 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 - 2, + .cs = frame.cs, + .rflags = frame.rflags, + .rsp = frame.rsp, + .ss = frame.ss, + }; + sched.blockCurrentOnRead(resume_frame); + asm volatile ("sti"); + while (true) asm volatile ("hlt"); + }, + .file => { + const n = @min(count, 4096); + const dst: [*]u8 = @ptrFromInt(frame.rsi); + const got = bridge.fsReadInto(f.path[0..f.path_len], f.offset, dst[0..n]); + f.offset += got; + frame.rax = got; + }, + else => frame.rax = SYS_ERR, } - frame.rax = len; // syscall return value }, - 2 => { // read(): return a buffered byte, else block for one - if (sched.readReady()) |byte| { - frame.rax = byte; + 7 => { // open(path_ptr, flags) + const name = userName(frame.rdi); + const flags = frame.rsi; + var size = bridge.fsSize(name); + if (size < 0) { + if (flags & O_CREAT != 0 and bridge.fsStore(name, "")) { + size = 0; + } else { + frame.rax = SYS_ERR; + return; + } + } else if (flags & O_TRUNC != 0) { + _ = bridge.fsStore(name, ""); + size = 0; + } + const fd = sched.fdOpen(name); + if (fd < 0) { + frame.rax = SYS_ERR; return; } - 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"); + if (flags & O_APPEND != 0) { + if (sched.fdGet(fd)) |f| f.offset = @intCast(size); + } + frame.rax = @bitCast(fd); + }, + 8 => { // close(fd) + frame.rax = @bitCast(sched.fdClose(@bitCast(frame.rdi))); + }, + 9 => { // lseek(fd, offset, whence) + if (sched.fdGet(@bitCast(frame.rdi))) |f| { + switch (frame.rdx) { + 0 => f.offset = frame.rsi, // SEEK_SET + 1 => f.offset += frame.rsi, // SEEK_CUR + 2 => { // SEEK_END + const sz = bridge.fsSize(f.path[0..f.path_len]); + if (sz >= 0) f.offset = @as(u64, @intCast(sz)) +% frame.rsi; + }, + else => {}, + } + frame.rax = f.offset; + } else frame.rax = SYS_ERR; }, - 4 => { // fs_size(name_ptr): returns size (like obv), or -1 + 4 => { // fs_size(name_ptr) frame.rax = @bitCast(bridge.fsSize(userName(frame.rdi))); }, - 5 => { // fs_read(name_ptr, offset, buf_ptr, len): returns bytes read + 5 => { // fs_read(name_ptr, offset, buf_ptr, len) 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]); }, - 6 => { // fs_write(name_ptr, buf_ptr, len): returns 1 ok / 0 fail + 6 => { // fs_write(name_ptr, buf_ptr, len) const len = @min(frame.rdx, 64 * 1024); const name = userName(frame.rdi); const src: [*]const u8 = @ptrFromInt(frame.rsi); -- cgit v1.3