hachem.wtf
aboutsummaryrefslogtreecommitdiff
path: root/boot
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-10-08 22:23:55 +0200
committerhachem <im@hachem.wtf>2026-10-08 22:23:55 +0200
commit852a5b237116e8b53ae18ea8ea00505e44b40dfe (patch)
tree1ed51afcf6471d2a0031d31b67b63f4ec404b9ed /boot
parentef104148057ab98f80b1560405b5c02768457096 (diff)
feat: file descriptor and proper stdio
Diffstat (limited to 'boot')
-rw-r--r--boot/elf.zig1
-rw-r--r--boot/sched.zig59
-rw-r--r--boot/user.zig172
3 files changed, 188 insertions, 44 deletions
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);