commit 22bc90e3895b4c87ed47c8d4e2ea839a0c627abd
parent 04326f45a3f35809f042c2fc1971fcf325d23c21
Author: Sylvia Ivory <git@sivory.net>
Date: Thu, 19 Mar 2026 11:05:09 -0700
Use VFS in syscalls
Diffstat:
6 files changed, 388 insertions(+), 51 deletions(-)
diff --git a/pi/fs/fatvfs.zig b/pi/fs/fatvfs.zig
@@ -72,6 +72,13 @@ pub const File = struct {
return file;
}
+ // TODO;
+ pub fn write(self: *File, bytes: []const u8) Error!u32 {
+ _ = self;
+ _ = bytes;
+ return Error.WriteFailed;
+ }
+
pub fn reset(self: *File) Error!void {
self.current_cluster = self.first_cluster;
self.remaining = self.size;
diff --git a/pi/fs/vfs.zig b/pi/fs/vfs.zig
@@ -21,6 +21,8 @@ pub const Error = error{
InitFailed,
ReadFailed,
+ SeekFailed,
+ WriteFailed,
} || std.mem.Allocator.Error;
fn TPtr(T: type, opaque_ptr: *anyopaque) T {
@@ -174,6 +176,7 @@ pub const File = struct {
impl: *anyopaque,
v_close: *const fn (*anyopaque) void,
v_read: *const fn (*anyopaque) Error!?[]u8,
+ v_write: *const fn (*anyopaque, []const u8) Error!u32,
v_reset: *const fn (*anyopaque) Error!void,
pub fn impl_by(impl_obj: anytype) File {
@@ -182,6 +185,7 @@ pub const File = struct {
.impl = impl_obj,
.v_close = delegate.close,
.v_read = delegate.read,
+ .v_write = delegate.write,
.v_reset = delegate.reset,
};
}
@@ -194,6 +198,10 @@ pub const File = struct {
return try self.v_read(self.impl);
}
+ pub fn write(self: File, bytes: []const u8) Error!u32 {
+ return try self.v_write(self.impl, bytes);
+ }
+
pub fn reset(self: File) Error!void {
return try self.v_reset(self.impl);
}
@@ -210,6 +218,10 @@ inline fn FileDelegate(impl_obj: anytype) type {
return try TPtr(ImplType, impl).read();
}
+ fn write(impl: *anyopaque, bytes: []const u8) Error!u32 {
+ return try TPtr(ImplType, impl).write(bytes);
+ }
+
fn reset(impl: *anyopaque) Error!void {
return try TPtr(ImplType, impl).reset();
}
diff --git a/sylveos/files.zig b/sylveos/files.zig
@@ -0,0 +1,236 @@
+const std = @import("std");
+const pi = @import("pi");
+
+const vfs = pi.fs.vfs;
+const uart = pi.devices.mini_uart;
+
+pub const Error = error{NotFound} || vfs.Error;
+
+// stdout is stateless so we don't care about self
+pub const Stdout = struct {
+ pub fn close(self: *Stdout) void {
+ _ = self;
+ }
+ pub fn read(self: *Stdout) vfs.Error!?[]u8 {
+ _ = self;
+ return Error.ReadFailed;
+ }
+ pub fn reset(self: *Stdout) vfs.Error!void {
+ _ = self;
+ return Error.SeekFailed;
+ }
+ pub fn write(self: *Stdout, bytes: []const u8) vfs.Error!u32 {
+ _ = self;
+ // TODO; create some protocol that embeds PID
+ uart.print("{s}", .{bytes});
+
+ return bytes.len;
+ }
+};
+
+// Stderr is an alias for stdout
+pub const Stderr = Stdout;
+
+// stdin needs an allocator to keep track of read bytes
+pub const Stdin = struct {
+ alloc: std.mem.Allocator,
+ pub fn close(self: *Stdin) void {
+ _ = self;
+ }
+ pub fn read(self: *Stdin) vfs.Error!?[]u8 {
+ // read until newline or buffer is full
+ // TODO; should buffer come from user
+ var buffer = try self.alloc.alloc(u8, 1024);
+ var buf_read: u32 = 0;
+ for (0..buffer.len) |i| {
+ const byte = uart.read_byte_sync();
+ buffer[i] = byte;
+ buf_read += 1;
+ if (byte == '\n') break;
+ }
+ return buffer[0..buf_read];
+ }
+ pub fn reset(self: *Stdin) vfs.Error!void {
+ _ = self;
+ return Error.SeekFailed;
+ }
+ pub fn write(self: *Stdin, bytes: []const u8) vfs.Error!u8 {
+ _ = self;
+ _ = bytes;
+ return Error.WriteFailed;
+ }
+};
+
+pub fn resolve_path_relative(fs: vfs.FileSystem, directory: vfs.Directory, path: []const u8) Error!?vfs.DirectoryEntry {
+ // POSIX cannot fail init
+ var it = std.fs.path.componentIterator(path) catch unreachable;
+ var first_cwd = true;
+ var cwd = directory;
+
+ while (it.next()) |component| {
+ if (try cwd.open_entry(component.name)) |entry| {
+ const stat = entry.stat();
+ if (it.peekNext() != null and !stat.directory) {
+ // Still remaining components and yet not a directory
+ entry.close();
+ return Error.ExpectedDirectory;
+ }
+ // Swap in entry and continue
+ if (first_cwd) {
+ // Don't close what was given to us
+ first_cwd = false;
+ } else {
+ cwd.close();
+ }
+
+ cwd = try fs.open_directory(@constCast(&entry));
+ } else {
+ return null;
+ }
+ }
+
+ return try cwd.open_entry(".");
+}
+
+pub fn resolve_path_absolute(fs: vfs.FileSystem, path: []const u8) Error!?vfs.DirectoryEntry {
+ const root = try fs.open_root();
+ defer root.close();
+ return try resolve_path_relative(fs, root, path);
+}
+
+pub fn resolve_path(fs: vfs.FileSystem, cwd: vfs.Directory, path: []const u8) Error!?vfs.DirectoryEntry {
+ if (std.fs.path.isAbsolute(path)) {
+ return try resolve_path_absolute(fs, path);
+ } else {
+ return try resolve_path_relative(fs, cwd, path);
+ }
+}
+
+pub const FileResolver = struct {
+ fd_list: std.ArrayList(vfs.File),
+ read_cache: std.AutoHashMap(u32, []u8),
+
+ alloc: std.mem.Allocator,
+ fs: vfs.FileSystem,
+ cwd: vfs.Directory,
+
+ pub fn init(fs: vfs.FileSystem, cwd: vfs.Directory, alloc: std.mem.Allocator) !FileResolver {
+ uart.print("initializing std(in/err/in)\n", .{});
+ const stdout = try alloc.create(Stdout);
+ const stderr = try alloc.create(Stderr);
+ var stdin = try alloc.create(Stdin);
+ uart.print("created std(out/err)\n", .{});
+ stdin.alloc = alloc;
+
+ var fd_list: std.ArrayList(vfs.File) = try .initCapacity(alloc, 3);
+ fd_list.appendAssumeCapacity(vfs.File.impl_by(stdin));
+ fd_list.appendAssumeCapacity(vfs.File.impl_by(stdout));
+ fd_list.appendAssumeCapacity(vfs.File.impl_by(stderr));
+ uart.print("initialized fd_list\n", .{});
+
+ return .{
+ .fd_list = fd_list,
+ .read_cache = .init(alloc),
+ .alloc = alloc,
+ .fs = fs,
+ .cwd = cwd,
+ };
+ }
+
+ // Would use close but uh, already used unfortunately
+ pub fn deinit(self: *FileResolver) void {
+ var cache_iter = self.read_cache.iterator();
+ while (cache_iter.next()) |entry| {
+ self.alloc.free(entry.value_ptr.*);
+ }
+ self.read_cache.deinit();
+
+ for (self.fd_list.items) |*file| {
+ file.close();
+ }
+ self.fd_list.deinit();
+ }
+
+ pub fn open(self: *FileResolver, path: []const u8) !u32 {
+ var entry = try resolve_path(self.fs, self.cwd, path) orelse return Error.NotFound;
+
+ const stat = entry.stat();
+ if (stat.directory) {
+ entry.close();
+ return Error.ExpectedFile;
+ }
+
+ const file = try self.fs.open_file(&entry);
+
+ try self.fd_list.append(self.alloc, file);
+ return self.fd_list.items.len - 1;
+ }
+
+ pub fn close(self: *FileResolver, fd: u32) !void {
+ if (fd >= self.fd_list.items.len) return Error.NotFound;
+ self.fd_list.items[fd].close();
+ }
+
+ pub fn write(self: *FileResolver, fd: u32, bytes: []const u8) Error!u32 {
+ if (fd >= self.fd_list.items.len) return Error.NotFound;
+
+ return try self.fd_list.items[fd].write(bytes);
+ }
+
+ pub fn read(self: *FileResolver, fd: u32, buffer: []u8) Error!u32 {
+ if (fd > self.fd_list.items.len) return Error.NotFound;
+
+ if (buffer.len == 0) {
+ return 0;
+ }
+
+ var bytes_copied: u32 = 0;
+
+ // Is there data from an earlier call
+ if (self.read_cache.get(fd)) |cached_data| {
+ // If so, grab it
+ const bytes_from_cache = @min(buffer.len, cached_data.len);
+ @memcpy(buffer[0..bytes_from_cache], cached_data[0..bytes_from_cache]);
+ bytes_copied = bytes_from_cache;
+
+ // Update or remove cache
+ if (bytes_from_cache < cached_data.len) {
+ // Still have leftover data, keep the remainder
+ const remaining = cached_data[bytes_from_cache..];
+ const new_cache = try self.alloc.alloc(u8, remaining.len);
+ @memcpy(new_cache, remaining);
+ self.alloc.free(cached_data);
+ try self.read_cache.put(fd, new_cache);
+
+ // Buffer is full, return
+ return @intCast(bytes_copied);
+ } else {
+ // Used all cached data
+ self.alloc.free(cached_data);
+ _ = self.read_cache.remove(fd);
+ }
+ }
+
+ // If buffer still has space, read from file
+ if (bytes_copied < buffer.len) {
+ const file = self.fd_list.items[fd];
+ const data = try file.read() orelse return @intCast(bytes_copied);
+ defer self.alloc.free(data);
+
+ const remaining_buffer = buffer[bytes_copied..];
+ const bytes_from_file = @min(remaining_buffer.len, data.len);
+ @memcpy(remaining_buffer[0..bytes_from_file], data[0..bytes_from_file]);
+ bytes_copied += bytes_from_file;
+
+ // Cache any excess data
+ if (bytes_from_file < data.len) {
+ const excess = data[bytes_from_file..];
+ const cache_data = try self.alloc.alloc(u8, excess.len);
+ @memcpy(cache_data, excess);
+ try self.read_cache.put(fd, cache_data);
+ }
+ }
+
+ return bytes_copied;
+ }
+};
diff --git a/sylveos/loader.zig b/sylveos/loader.zig
@@ -5,10 +5,12 @@ const process = @import("./process.zig");
const memory = @import("./memory.zig");
const kuser = @import("./kuser.zig");
const Pages = @import("./pages.zig");
+const files = @import("./files.zig");
const Page = process.Page;
const mailbox = pi.devices.mailbox;
const uart = pi.devices.mini_uart;
+const vfs = pi.fs.vfs;
const mmu = pi.mmu;
const pt = pi.pt;
@@ -21,10 +23,21 @@ pub const Program = struct {
elf_header: std.elf.Header,
header_location: u32,
heap_start: u32,
+
+ // TODO; these should be in Process
heap_current: u32,
+ files: files.FileResolver,
};
-pub fn init(pt_alloc: std.mem.Allocator, heap_alloc: std.mem.Allocator, root: []mmu.FirstLevelDescriptor, elf: []const u8) !Program {
+pub fn init(
+ fs: vfs.FileSystem,
+ cwd: vfs.Directory,
+ kalloc: std.mem.Allocator,
+ pt_alloc: std.mem.Allocator,
+ heap_alloc: std.mem.Allocator,
+ root: []mmu.FirstLevelDescriptor,
+ elf: []const u8,
+) !Program {
// TODO; proper allocator
// TODO; asid
// TODO; pid
@@ -136,7 +149,14 @@ pub fn init(pt_alloc: std.mem.Allocator, heap_alloc: std.mem.Allocator, root: []
.elf_header = header,
.header_location = header_location orelse unreachable,
.heap_start = heap_start,
+
.heap_current = heap_start,
+ // TODO; use a different allocator
+ // ; aka create an allocator that automatically maps
+ // ; addresses into kernel space
+ // ; probably just uses heap_alloc under the hood and
+ // ; automatically map_4kb into current PT
+ .files = try .init(fs, cwd, kalloc),
};
}
@@ -144,7 +164,6 @@ pub fn init(pt_alloc: std.mem.Allocator, heap_alloc: std.mem.Allocator, root: []
pub var current_program: *Program = undefined;
pub fn execute(self: *Program, pid: u24, args: []const []const u8, env: []const []const u8) !noreturn {
- // TODO; args, env
self.pages.switch_into(pid);
// Temporarily store env/arg pointers for future reference
diff --git a/sylveos/root.zig b/sylveos/root.zig
@@ -57,13 +57,15 @@ fn abort_handler(regs: interrupts.Registers, ev: interrupts.ExceptionVector) voi
if (ev == .PrefetchAbort and ifsr.status == .InstructionDebugEventFault) {
journal.warn("[breakpoint] PC = 0x{X:0>8}\n", .{regs.pc});
+ journal.append_registers(®s);
} else if (ev == .PrefetchAbort) {
journal.err("[prefetch abort] {s}: got fault on 0x{X:0>8} from 0x{X:0>8}\n", .{
@tagName(ifsr.status),
far,
regs.pc,
});
- interrupts.dump_registers(®s);
+ journal.append_registers(®s);
+
pi.reboot();
} else if (ev == .DataAbort) {
const status = if (dfsr.status_kind == 0) @tagName(dfsr.status.@"0") else @tagName(dfsr.status.@"1");
@@ -74,7 +76,8 @@ fn abort_handler(regs: interrupts.Registers, ev: interrupts.ExceptionVector) voi
regs.pc,
@tagName(dfsr.abort_source),
});
- interrupts.dump_registers(®s);
+ journal.append_registers(®s);
+
pi.reboot();
}
}
@@ -130,14 +133,34 @@ fn main() !void {
interrupts.set_exception_handler(.SoftwareInterrupt, syscall.syscall_handler);
interrupts.set_exception_handler(.UndefinedInstruction, undef);
+ var buffer: [1024 * 64]u8 = undefined;
+
var heap_fba = memory.get_allocator();
const heap_alloc = heap_fba.allocator();
var pt_fba = memory.get_pt_allocator();
const pt_alloc = pt_fba.allocator();
+ var kalloc_fba: std.heap.FixedBufferAllocator = .init(&buffer);
+ const kalloc = kalloc_fba.allocator();
+
const pt = memory.get_page_table();
- var lua = try loader.init(pt_alloc, heap_alloc, pt, lua_binary);
+ var sd_card = try pi.devices.sd.init(kalloc);
+ var fat_vfs: pi.fs.FatVFS = .init();
+
+ var vfs_disk = pi.fs.vfs.Disk.impl_by(&sd_card);
+ const vfs_fat = pi.fs.vfs.FileSystem.impl_by(&fat_vfs);
+
+ const mbr: pi.fs.mbr.MasterBootRecord = @bitCast(try vfs_disk.read_sector(0));
+
+ try mbr.debug_print(&uart.writer);
+
+ try vfs_fat.open(kalloc, &vfs_disk, mbr.pte1.first_lba);
+ defer vfs_fat.close();
+
+ const root = try vfs_fat.open_root();
+
+ var lua = try loader.init(vfs_fat, root, kalloc, pt_alloc, heap_alloc, pt, lua_binary);
try loader.execute(&lua, 0, &[_][]const u8{"lua"}, &[_][]u8{});
}
diff --git a/sylveos/syscall.zig b/sylveos/syscall.zig
@@ -89,24 +89,26 @@ pub fn syscall_handler(registers: interrupts.Registers, _: interrupts.ExceptionV
journal.trace("[syscall] read({d}, 0x{X}, {d})", .{ fd, @intFromPtr(buffer), count });
- if (fd == 0) {
- // stdin
- var read: u32 = 0;
- for (0..count) |i| {
- const byte = uart.read_byte_sync();
- buffer[i] = byte;
- read += 1;
- if (byte == '\n') break;
- }
- regs.gp[0] = @bitCast(@as(i32, @bitCast(read)));
- } else if (fd == 3) {
- // temporary buffer
- const code = "print('hello world')";
-
- @memcpy(buffer, code);
-
- regs.gp[0] = @bitCast(@as(i32, @bitCast(code.len)));
- }
+ regs.gp[0] = loader.current_program.files.read(fd, buffer[0..count]) catch @bitCast(@as(i32, -1));
+
+ // if (fd == 0) {
+ // // stdin
+ // var read: u32 = 0;
+ // for (0..count) |i| {
+ // const byte = uart.read_byte_sync();
+ // buffer[i] = byte;
+ // read += 1;
+ // if (byte == '\n') break;
+ // }
+ // regs.gp[0] = @bitCast(@as(i32, @bitCast(read)));
+ // } else if (fd == 3) {
+ // // temporary buffer
+ // const code = "print('hello world')";
+
+ // @memcpy(buffer, code);
+
+ // regs.gp[0] = @bitCast(@as(i32, @bitCast(code.len)));
+ // }
},
// write
4 => {
@@ -116,22 +118,27 @@ pub fn syscall_handler(registers: interrupts.Registers, _: interrupts.ExceptionV
journal.trace("[syscall] write({d}, 0x{X}, {d})", .{ fd, @intFromPtr(buffer), count });
+ regs.gp[0] = loader.current_program.files.write(fd, buffer[0..count]) catch @bitCast(@as(i32, -1));
+
// stdout
- if (fd == 1) {
- uart.print("{s}", .{buffer[0..count]});
- regs.gp[0] = @bitCast(@as(i32, @bitCast(count)));
- } else {
- regs.gp[0] = @bitCast(@as(i32, -9));
- }
+ // if (fd == 1) {
+ // uart.print("{s}", .{buffer[0..count]});
+ // regs.gp[0] = @bitCast(@as(i32, @bitCast(count)));
+ // } else {
+ // regs.gp[0] = @bitCast(@as(i32, -9));
+ // }
},
// open
5 => {
const path: [*:0]u8 = @ptrFromInt(regs.gp[0]);
const flags = regs.gp[1];
+ const len = std.mem.len(path);
journal.trace("[syscall] open({s}, 0x{X})", .{ path, flags });
- regs.gp[0] = @bitCast(@as(i32, 3));
+ regs.gp[0] = loader.current_program.files.open(path[0..len]) catch @bitCast(@as(i32, -1));
+
+ // regs.gp[0] = @bitCast(@as(i32, 3));
},
// close
6 => {
@@ -166,22 +173,39 @@ pub fn syscall_handler(registers: interrupts.Registers, _: interrupts.ExceptionV
journal.trace("[syscall] readv({d}, 0x{X}, {d})", .{ fd, @intFromPtr(iov_ptr), count });
const iov = iov_ptr[0..count];
+ var total: u32 = 0;
+
+ for (iov) |vec| {
+ if (vec.iov_len == 0) continue;
- if (fd == 0) {
- var total: u32 = 0;
- for (iov) |vec| {
- if (vec.iov_len == 0) continue;
+ const bytes_read = loader.current_program.files.read(@intCast(fd), vec.iov_base[0..vec.iov_len]) catch {
+ regs.gp[0] = @bitCast(@as(i32, -1));
+ break;
+ };
+ total += bytes_read;
- uart.print("{s}", .{vec.iov_base[0..vec.iov_len]});
- total += vec.iov_len;
+ if (bytes_read < vec.iov_len) {
+ break;
}
- regs.gp[0] = total;
- } else if (fd == 3) {
- // done by first read
- regs.gp[0] = 0;
} else {
- regs.gp[0] = @bitCast(@as(i32, -1));
+ regs.gp[0] = total;
}
+
+ // if (fd == 0) {
+ // var total: u32 = 0;
+ // for (iov) |vec| {
+ // if (vec.iov_len == 0) continue;
+
+ // uart.print("{s}", .{vec.iov_base[0..vec.iov_len]});
+ // total += vec.iov_len;
+ // }
+ // regs.gp[0] = total;
+ // } else if (fd == 3) {
+ // // done by first read
+ // regs.gp[0] = 0;
+ // } else {
+ // regs.gp[0] = @bitCast(@as(i32, -1));
+ // }
},
// writev
146 => {
@@ -192,19 +216,35 @@ pub fn syscall_handler(registers: interrupts.Registers, _: interrupts.ExceptionV
journal.trace("[syscall] writev({d}, 0x{X}, {d})", .{ fd, @intFromPtr(iov_ptr), count });
const iov = iov_ptr[0..count];
-
- if (fd == 1 or fd == 2) {
- var total: u32 = 0;
- for (iov) |vec| {
- if (vec.iov_len == 0) continue;
-
- uart.print("{s}", .{vec.iov_base[0..vec.iov_len]});
- total += vec.iov_len;
+ var total: u32 = 0;
+ for (iov) |vec| {
+ if (vec.iov_len == 0) continue;
+
+ const bytes_written = loader.current_program.files.write(@intCast(fd), vec.iov_base[0..vec.iov_len]) catch {
+ regs.gp[0] = @bitCast(@as(i32, -1));
+ break;
+ };
+ total += bytes_written;
+
+ if (bytes_written < vec.iov_len) {
+ break;
}
- regs.gp[0] = total;
} else {
- regs.gp[0] = @bitCast(@as(i32, -1));
+ regs.gp[0] = total;
}
+
+ // if (fd == 1 or fd == 2) {
+ // var total: u32 = 0;
+ // for (iov) |vec| {
+ // if (vec.iov_len == 0) continue;
+
+ // uart.print("{s}", .{vec.iov_base[0..vec.iov_len]});
+ // total += vec.iov_len;
+ // }
+ // regs.gp[0] = total;
+ // } else {
+ // regs.gp[0] = @bitCast(@as(i32, -1));
+ // }
},
// mmap2
192 => {