hachem.wtf
aboutsummaryrefslogtreecommitdiff
path: root/bridge
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-10-04 16:03:42 +0200
committerhachem <im@hachem.wtf>2026-10-04 16:03:42 +0200
commit52dab7fc85d877e80e3032f0afabae906adebad8 (patch)
treeb6d56299b839413bb77b1b8c8fa9f7a682124a05 /bridge
parenta8a35a00a3c3c57cfbef2311d0348376c2e83208 (diff)
feat: fs tree and free list alloc
Diffstat (limited to 'bridge')
-rw-r--r--bridge/bridge.zig539
-rw-r--r--bridge/convert.zig18
-rw-r--r--bridge/hosts.zig380
-rw-r--r--bridge/serial.zig141
4 files changed, 568 insertions, 510 deletions
diff --git a/bridge/bridge.zig b/bridge/bridge.zig
index 7d64528..71f7023 100644
--- a/bridge/bridge.zig
+++ b/bridge/bridge.zig
@@ -4,505 +4,13 @@
const std = @import("std");
const revo = @import("revo");
+const serial = @import("serial.zig");
+const hosts = @import("hosts.zig");
-// all the vms output goes here, boot injects this at runtime so the
-// bridge doesnt need to pull a serial port from its ass
-pub const Sink = *const fn ([]const u8) void;
-fn noopSink(_: []const u8) void {}
-var sink: Sink = noopSink;
-
-// fan a multi slice write payload out to the sink
-// mirrors the shape of std.ios file_write_streaming request so
-// an optional header, then the data whose last slice is repeated
-// splat times
-// idk why zig chose these names, they are funny af
-// note: returns the total byte count written
-fn writeToSink(
- header: []const u8,
- data: []const []const u8,
- splat: usize,
-) std.Io.Operation.FileWriteStreaming.Result {
- if (header.len > 0) emit(header);
- for (data[0..data.len -| 1]) |slice| {
- if (slice.len > 0) emit(slice);
- }
- const last = data[data.len -| 1];
- var i: usize = 0;
- while (i < splat) : (i += 1) {
- if (last.len > 0) emit(last);
- }
-
- var total: usize = header.len;
- for (data[0..data.len -| 1]) |slice| total += slice.len;
- total += last.len * splat;
- return total;
-}
-
-// every file write (stdout, stderr, whatever) routes to the serial port but we
-// dont have a filesystem or multiple sinks yet, so theres nowhere else to go
-fn ioOperate(_: ?*anyopaque, operation: std.Io.Operation) std.Io.Cancelable!std.Io.Operation.Result {
- return switch (operation) {
- .file_write_streaming => |req| .{
- .file_write_streaming = writeToSink(req.header, req.data, req.splat),
- },
- .file_read_streaming => .{ .file_read_streaming = error.InputOutput },
- .device_io_control => .{ .device_io_control = -1 },
- .net_receive => .{ .net_receive = .{ error.NetworkDown, 0 } },
- };
-}
-
-// no timer wired up yet, so time stands still,
-// call me the time bender the way i fuck shit up
-// todo: fine until we have the pit/tsc
-fn ioNow(_: ?*anyopaque, _: std.Io.Clock) std.Io.Timestamp {
- return .{ .nanoseconds = 0 };
-}
-
-fn ioClockResolution(_: ?*anyopaque, _: std.Io.Clock) std.Io.Clock.ResolutionError!std.Io.Duration {
- return .{ .nanoseconds = 1_000_000 };
-}
-
-// the scheduler parks fibers itself, it never needs us to actually block
-fn ioSleep(_: ?*anyopaque, _: std.Io.Timeout) std.Io.Cancelable!void {}
-
-fn ioCrashHandler(_: ?*anyopaque) void {}
-
-// eval captures its own errors into a buffer, so this is never reached lol
-// return canceled rather than recursing into a stderr lock we cant provide
-fn ioLockStderr(_: ?*anyopaque, _: ?std.Io.Terminal.Mode) std.Io.Cancelable!std.Io.LockedStderr {
- return error.Canceled;
-}
-
-fn ioUnlockStderr(_: ?*anyopaque) void {}
-
-// start from stds all failing vtable and override only what the vm touches
-const io_vtable: std.Io.VTable = blk: {
- var vtable = std.Io.failing.vtable.*;
- vtable.operate = ioOperate;
- vtable.now = ioNow;
- vtable.clockResolution = ioClockResolution;
- vtable.sleep = ioSleep;
- vtable.crashHandler = ioCrashHandler;
- vtable.lockStderr = ioLockStderr;
- vtable.unlockStderr = ioUnlockStderr;
- break :blk vtable;
-};
-
-const serial_io: std.Io = .{ .userdata = null, .vtable = &io_vtable };
-
-// a second io used only as stds debug io (for std.debug.print, which revo hits
-// on a few error paths). stds default debug io is a threaded stderr writer that
-// doesnt exist on freestandin, and std.io.failings stderr path is literally
-// unreachable, so we give std a real one that drains to the same sink. unlike
-// serial_io above, its lockstderr must return an actual file.writer (std turns a
-// canceled lock into a panic), and swapcancelprotection must not be unreachable
-var dbg_fw: std.Io.File.Writer = undefined;
-var dbg_fw_buf: [256]u8 = undefined;
-
-fn dbgLockStderr(_: ?*anyopaque, _: ?std.Io.Terminal.Mode) std.Io.Cancelable!std.Io.LockedStderr {
- // file.stderr() reads posix.stderr_fileno, which doesnt exist on
- // freestanding so we build the file by hand instead. iooperate ignores
- // the handle and routes every write to the sink regardless
- const stderr_file: std.Io.File = .{ .handle = undefined, .flags = .{ .nonblocking = false } };
- dbg_fw = stderr_file.writerStreaming(debug_io, &dbg_fw_buf);
- return .{ .file_writer = &dbg_fw, .terminal_mode = .no_color };
-}
-fn dbgUnlockStderr(_: ?*anyopaque) void {
- dbg_fw.interface.flush() catch {};
-}
-fn dbgSwapCancel(_: ?*anyopaque, new: std.Io.CancelProtection) std.Io.CancelProtection {
- return new;
-}
-
-const debug_io_vtable: std.Io.VTable = blk: {
- var vtable = std.Io.failing.vtable.*;
- vtable.operate = ioOperate;
- vtable.now = ioNow;
- vtable.clockResolution = ioClockResolution;
- vtable.sleep = ioSleep;
- vtable.crashHandler = ioCrashHandler;
- vtable.lockStderr = dbgLockStderr;
- vtable.unlockStderr = dbgUnlockStderr;
- vtable.swapCancelProtection = dbgSwapCancel;
- break :blk vtable;
-};
-
-pub const debug_io: std.Io = .{ .userdata = null, .vtable = &debug_io_vtable };
-
-// an unbuffered std.io.writer that drains straight to the sink, so we can render
-// revos own error reports (which write to a std.io.writer) onto the console
-const SinkWriter = struct {
- interface: std.Io.Writer,
-
- fn init() SinkWriter {
- return .{ .interface = .{ .buffer = &.{}, .vtable = &.{ .drain = drain, .flush = flush }, .end = 0 } };
- }
-
- fn drain(w: *std.Io.Writer, data: []const []const u8, splat: usize) std.Io.Writer.Error!usize {
- _ = w;
- var total: usize = 0;
- for (data[0..data.len -| 1]) |slice| {
- if (slice.len > 0) emit(slice);
- total += slice.len;
- }
- const last = data[data.len -| 1];
- var i: usize = 0;
- while (i < splat) : (i += 1) {
- if (last.len > 0) emit(last);
- }
- total += last.len * splat;
- return total;
- }
-
- fn flush(_: *std.Io.Writer) std.Io.Writer.Error!void {}
-};
-
-const HostResult = revo.baselib.host.HostResult;
-const Data = revo.Value;
-
-// revos only number type is f64 which is really fucking annoying
-fn f64ToInt(comptime T: type, n: f64) ?T {
- if (!std.math.isFinite(n)) return null;
- // go through @floatfromint for the bounds: maxint(u64) isnt representable
- // as an f64 literal, so comparing against the comptime_int directly wont
- // even compile bruh
- const min_f: f64 = @floatFromInt(std.math.minInt(T));
- const max_f: f64 = @floatFromInt(std.math.maxInt(T));
- if (n < min_f or n > max_f) return null;
- return @intFromFloat(n);
-}
-
-// pull argument i as an integer of type t. args are already type checked to be
-// numbers by define(&.{.number,...}), so asnumopt cant be null, the result is
-// null only when the value is out of ts range (the host fn maps that to an error)
-fn argInt(comptime T: type, args: []const revo.Value, i: usize) ?T {
- return f64ToInt(T, args[i].asNumOpt().?);
-}
-
-// pass shit to revo
-fn hostOutb(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- const port = argInt(u16, args, 0) orelse return HostResult.other("outb: port out of range");
- const val = argInt(u8, args, 1) orelse return HostResult.other("outb: value out of range");
- asm volatile ("outb %[v], %[p]"
- :
- : [v] "{al}" (val),
- [p] "N{dx}" (port),
- );
- return HostResult.data(Data.new.nil());
-}
-
-// pass shit to revo
-fn hostInb(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- const port = argInt(u16, args, 0) orelse return HostResult.other("inb: port out of range");
- const val = asm volatile ("inb %[p], %[r]"
- : [r] "={al}" (-> u8),
- : [p] "N{dx}" (port),
- );
- return HostResult.data(Data.new.num(val));
-}
-
-// pass shit to revo
-fn hostPuts(args: []const revo.Value, vm: *revo.VM) anyerror!HostResult {
- const string_id = args[0].asString() orelse return HostResult.other("puts: not a string");
- emit(vm.stringValue(string_id));
- return HostResult.data(Data.new.nil());
-}
-
-// the actual framebuffer, the data layout feels pretty self explanatory
-pub const Fb = struct {
- ptr: [*]u8,
- width: usize,
- height: usize,
- pitch: usize,
-};
-var g_fb: ?Fb = null;
-
-fn hostFbWidth(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- return HostResult.data(Data.new.num(if (g_fb) |fb| fb.width else 0));
-}
-
-fn hostFbHeight(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- return HostResult.data(Data.new.num(if (g_fb) |fb| fb.height else 0));
-}
-
-// color is 0xrrggbb in the usual limine 32 bpp layout
-fn hostFillRect(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- const fb = g_fb orelse return HostResult.data(Data.new.nil()); // no screen, no op
- const x = argInt(usize, args, 0) orelse return HostResult.other("fill_rect: bad x");
- const y = argInt(usize, args, 1) orelse return HostResult.other("fill_rect: bad y");
- const w = argInt(usize, args, 2) orelse return HostResult.other("fill_rect: bad w");
- const h = argInt(usize, args, 3) orelse return HostResult.other("fill_rect: bad h");
- const color = argInt(u32, args, 4) orelse return HostResult.other("fill_rect: bad color");
-
- var yy = y;
- const y_end = @min(y + h, fb.height);
- const x_end = @min(x + w, fb.width);
- while (yy < y_end) : (yy += 1) {
- const row = fb.ptr + yy * fb.pitch;
- var xx = x;
- while (xx < x_end) : (xx += 1) {
- const px: *align(1) u32 = @ptrCast(row + xx * 4);
- px.* = color;
- }
- }
- return HostResult.data(Data.new.nil());
-}
-
-// mirror everything to serial
-fn emit(bytes: []const u8) void {
- sink(bytes);
-}
-
-// shift the whole framebuffer up dy pixels and clear the freed rows
-fn hostFbScroll(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- const fb = g_fb orelse return HostResult.data(Data.new.nil());
- const dy = argInt(usize, args, 0) orelse return HostResult.other("fb_scroll: bad dy");
- const color = argInt(u32, args, 1) orelse return HostResult.other("fb_scroll: bad color");
- if (dy == 0 or dy >= fb.height) return HostResult.data(Data.new.nil());
- const move_bytes = dy * fb.pitch;
- const total = fb.height * fb.pitch;
- std.mem.copyForwards(u8, fb.ptr[0 .. total - move_bytes], fb.ptr[move_bytes..total]);
- var y: usize = fb.height - dy;
- while (y < fb.height) : (y += 1) {
- const line = fb.ptr + y * fb.pitch;
- var x: usize = 0;
- while (x < fb.width) : (x += 1) {
- const px: *align(1) u32 = @ptrCast(line + x * 4);
- px.* = color;
- }
- }
- return HostResult.data(Data.new.nil());
-}
-
-pub const KernelOps = struct {
- phys_to_virt: *const fn (u64) u64,
- alloc_frame: *const fn () u64, // 0 on failure
- create_addrspace: *const fn () u64, // pml4 phys, 0 on failure (raw 64 bit entry copy incl. possible nx bit -> not f64 representable)
- spawn: *const fn (u64, u64, u64) void, // pml4, entry, ustack -> queue a ring 3 task
- deliver_input: *const fn (u8) bool, // hand a byte to a read()-blocked task, false if none
- proc_exited: *const fn () bool, // true once after a process exits (consumed)
- serial_next: *const fn () i64, // next mirrored serial byte, or -1 if empty
-
- // read only state for revo to present (mem/uptime/ps/dump_memmap):
- memmap_count: *const fn () u64,
- memmap_base: *const fn (u64) u64,
- memmap_len: *const fn (u64) u64,
- memmap_kind: *const fn (u64) u64,
- mem_free: *const fn () u64,
- mem_total: *const fn () u64,
-
- uptime: *const fn () u64,
- proc_count: *const fn () u64,
- prog_count: *const fn () u64,
- prog_phys: *const fn (u64) u64,
- prog_name: *const fn (u64) []const u8,
-
- disk_sectors: *const fn () u64,
- disk_read: *const fn (u64, u64) u64, // lba, phys buf -> 1 ok / 0 fail
- disk_write: *const fn (u64, u64) u64, // lba, phys buf -> 1 ok / 0 fail
-};
-var g_kops: ?KernelOps = null;
-
-fn kops() KernelOps {
- return g_kops.?;
-}
-
-// mem_read(phys, size) -> value: read 1/2/4/8 bytes of physical memory
-fn hostMemRead(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- const phys = argInt(u64, args, 0) orelse return HostResult.other("mem_read: bad addr");
- const size = argInt(u64, args, 1) orelse return HostResult.other("mem_read: bad size");
- const virt = kops().phys_to_virt(phys);
- const val: u64 = switch (size) {
- 1 => @as(*const u8, @ptrFromInt(virt)).*,
- 2 => @as(*align(1) const u16, @ptrFromInt(virt)).*,
- 4 => @as(*align(1) const u32, @ptrFromInt(virt)).*,
- 8 => @as(*align(1) const u64, @ptrFromInt(virt)).*,
- else => return HostResult.other("mem_read: size must be 1/2/4/8"),
- };
- return HostResult.data(Data.new.num(val));
-}
-
-// mem_copy(dst_phys, src_phys, len): raw copy between physical regions
-fn hostMemCopy(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- const dst = kops().phys_to_virt(argInt(u64, args, 0) orelse return HostResult.other("mem_copy: bad dst"));
- const src = kops().phys_to_virt(argInt(u64, args, 1) orelse return HostResult.other("mem_copy: bad src"));
- const len = argInt(usize, args, 2) orelse return HostResult.other("mem_copy: bad len");
- @memcpy(@as([*]u8, @ptrFromInt(dst))[0..len], @as([*]const u8, @ptrFromInt(src))[0..len]);
- return HostResult.data(Data.new.nil());
-}
-
-// mem_zero(phys, len)
-fn hostMemZero(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- const dst = kops().phys_to_virt(argInt(u64, args, 0) orelse return HostResult.other("mem_zero: bad addr"));
- const len = argInt(usize, args, 1) orelse return HostResult.other("mem_zero: bad len");
- @memset(@as([*]u8, @ptrFromInt(dst))[0..len], 0);
- return HostResult.data(Data.new.nil());
-}
-
-// mem_write(phys, val, size): write 1/2/4/8 bytes of physical memory
-fn hostMemWrite(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- const phys = argInt(u64, args, 0) orelse return HostResult.other("mem_write: bad addr");
- const val = argInt(u64, args, 1) orelse return HostResult.other("mem_write: bad val");
- const size = argInt(u64, args, 2) orelse return HostResult.other("mem_write: bad size");
- const virt = kops().phys_to_virt(phys);
- switch (size) {
- 1 => @as(*u8, @ptrFromInt(virt)).* = @truncate(val),
- 2 => @as(*align(1) u16, @ptrFromInt(virt)).* = @truncate(val),
- 4 => @as(*align(1) u32, @ptrFromInt(virt)).* = @truncate(val),
- 8 => @as(*align(1) u64, @ptrFromInt(virt)).* = val,
- else => return HostResult.other("mem_write: size must be 1/2/4/8"),
- }
- return HostResult.data(Data.new.nil());
-}
-
-// invlpg(virt): flush one page from the tlb after remapping it
-fn hostInvlpg(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- const virt = argInt(u64, args, 0) orelse return HostResult.other("invlpg: bad addr");
- asm volatile ("invlpg (%[v])"
- :
- : [v] "r" (virt),
- : .{ .memory = true });
- return HostResult.data(Data.new.nil());
-}
-
-fn hostFrameAlloc(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- return HostResult.data(Data.new.num(kops().alloc_frame()));
-}
-
-fn hostAsCreate(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- return HostResult.data(Data.new.num(kops().create_addrspace()));
-}
-
-fn hostProcRun(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- const as = argInt(u64, args, 0) orelse return HostResult.other("proc_run: bad as");
- const entry = argInt(u64, args, 1) orelse return HostResult.other("proc_run: bad entry");
- const ustack = argInt(u64, args, 2) orelse return HostResult.other("proc_run: bad ustack");
- kops().spawn(as, entry, ustack); // queues the task, the scheduler runs it
- return HostResult.data(Data.new.nil());
-}
-
-// stdin_deliver(charcode) -> 1 if a read()-blocked process took it, else 0. the
-// revo key handler calls this before feeding its own line editor
-fn hostStdinDeliver(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- const ch = argInt(u8, args, 0) orelse return HostResult.other("stdin_deliver: bad char");
- return HostResult.data(Data.new.num(if (kops().deliver_input(ch)) @as(f64, 1) else 0));
-}
-
-// proc_exited() -> 1 once after a process exits, so the shell can reprint its
-// prompt below the programs output
-fn hostProcExited(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- return HostResult.data(Data.new.num(if (kops().proc_exited()) @as(f64, 1) else 0));
-}
-
-// serial_next() -> byte or -1
-fn hostSerialNext(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- return HostResult.data(Data.new.num(kops().serial_next()));
-}
-
-fn hostProgCount(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- return HostResult.data(Data.new.num(kops().prog_count()));
-}
-
-fn hostProgPhys(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- const i = argInt(u64, args, 0) orelse return HostResult.other("prog_phys: bad index");
- return HostResult.data(Data.new.num(kops().prog_phys(i)));
-}
-
-fn hostProgName(args: []const revo.Value, vm: *revo.VM) anyerror!HostResult {
- const i = argInt(u64, args, 0) orelse return HostResult.other("prog_name: bad index");
- return HostResult.data(try vm.ownValueString(kops().prog_name(i)));
-}
-
-// read only state getters revo uses for mem/uptime/ps and dump_memmap
-fn hostMemmapCount(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- return HostResult.data(Data.new.num(kops().memmap_count()));
-}
-
-fn hostMemmapBase(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- const index = argInt(u64, args, 0) orelse return HostResult.other("memmap_base: bad index");
- return HostResult.data(Data.new.num(kops().memmap_base(index)));
-}
-
-fn hostMemmapLen(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- const index = argInt(u64, args, 0) orelse return HostResult.other("memmap_len: bad index");
- return HostResult.data(Data.new.num(kops().memmap_len(index)));
-}
-
-fn hostMemmapKind(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- const index = argInt(u64, args, 0) orelse return HostResult.other("memmap_kind: bad index");
- return HostResult.data(Data.new.num(kops().memmap_kind(index)));
-}
-
-fn hostMemFree(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- return HostResult.data(Data.new.num(kops().mem_free()));
-}
-
-fn hostMemTotal(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- return HostResult.data(Data.new.num(kops().mem_total()));
-}
-
-fn hostUptime(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- return HostResult.data(Data.new.num(kops().uptime()));
-}
-
-fn hostProcCount(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- return HostResult.data(Data.new.num(kops().proc_count()));
-}
-
-fn hostDiskSectors(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- return HostResult.data(Data.new.num(kops().disk_sectors()));
-}
-
-fn hostDiskRead(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- const lba = argInt(u64, args, 0) orelse return HostResult.other("disk_read: bad lba");
- const phys = argInt(u64, args, 1) orelse return HostResult.other("disk_read: bad phys");
- return HostResult.data(Data.new.num(kops().disk_read(lba, phys)));
-}
-
-fn hostDiskWrite(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- const lba = argInt(u64, args, 0) orelse return HostResult.other("disk_write: bad lba");
- const phys = argInt(u64, args, 1) orelse return HostResult.other("disk_write: bad phys");
- return HostResult.data(Data.new.num(kops().disk_write(lba, phys)));
-}
-
-fn registerPrimitives(vm: *revo.VM) !void {
- const define = revo.baselib.host.define;
- const T = revo.baselib.host.ParamType;
- try vm.registerGlobal("outb", try vm.installHost("outb", define(&[_]T{ .number, .number }, hostOutb)));
- try vm.registerGlobal("inb", try vm.installHost("inb", define(&[_]T{.number}, hostInb)));
- try vm.registerGlobal("serial_puts", try vm.installHost("serial_puts", define(&[_]T{.string}, hostPuts)));
- try vm.registerGlobal("fb_width", try vm.installHost("fb_width", define(&[_]T{}, hostFbWidth)));
- try vm.registerGlobal("fb_height", try vm.installHost("fb_height", define(&[_]T{}, hostFbHeight)));
- try vm.registerGlobal("fill_rect", try vm.installHost("fill_rect", define(&[_]T{ .number, .number, .number, .number, .number }, hostFillRect)));
- try vm.registerGlobal("fb_scroll", try vm.installHost("fb_scroll", define(&[_]T{ .number, .number }, hostFbScroll)));
-
- try vm.registerGlobal("mem_read", try vm.installHost("mem_read", define(&[_]T{ .number, .number }, hostMemRead)));
- try vm.registerGlobal("mem_copy", try vm.installHost("mem_copy", define(&[_]T{ .number, .number, .number }, hostMemCopy)));
- try vm.registerGlobal("mem_zero", try vm.installHost("mem_zero", define(&[_]T{ .number, .number }, hostMemZero)));
- try vm.registerGlobal("mem_write", try vm.installHost("mem_write", define(&[_]T{ .number, .number, .number }, hostMemWrite)));
- try vm.registerGlobal("invlpg", try vm.installHost("invlpg", define(&[_]T{.number}, hostInvlpg)));
- try vm.registerGlobal("frame_alloc", try vm.installHost("frame_alloc", define(&[_]T{}, hostFrameAlloc)));
- try vm.registerGlobal("as_create", try vm.installHost("as_create", define(&[_]T{}, hostAsCreate)));
- try vm.registerGlobal("proc_run", try vm.installHost("proc_run", define(&[_]T{ .number, .number, .number }, hostProcRun)));
- try vm.registerGlobal("stdin_deliver", try vm.installHost("stdin_deliver", define(&[_]T{.number}, hostStdinDeliver)));
- try vm.registerGlobal("proc_exited", try vm.installHost("proc_exited", define(&[_]T{}, hostProcExited)));
- try vm.registerGlobal("serial_next", try vm.installHost("serial_next", define(&[_]T{}, hostSerialNext)));
- try vm.registerGlobal("prog_count", try vm.installHost("prog_count", define(&[_]T{}, hostProgCount)));
- try vm.registerGlobal("prog_phys", try vm.installHost("prog_phys", define(&[_]T{.number}, hostProgPhys)));
- try vm.registerGlobal("prog_name", try vm.installHost("prog_name", define(&[_]T{.number}, hostProgName)));
- try vm.registerGlobal("memmap_count", try vm.installHost("memmap_count", define(&[_]T{}, hostMemmapCount)));
- try vm.registerGlobal("memmap_base", try vm.installHost("memmap_base", define(&[_]T{.number}, hostMemmapBase)));
- try vm.registerGlobal("memmap_len", try vm.installHost("memmap_len", define(&[_]T{.number}, hostMemmapLen)));
- try vm.registerGlobal("memmap_kind", try vm.installHost("memmap_kind", define(&[_]T{.number}, hostMemmapKind)));
- try vm.registerGlobal("mem_free", try vm.installHost("mem_free", define(&[_]T{}, hostMemFree)));
- try vm.registerGlobal("mem_total", try vm.installHost("mem_total", define(&[_]T{}, hostMemTotal)));
- try vm.registerGlobal("uptime", try vm.installHost("uptime", define(&[_]T{}, hostUptime)));
- try vm.registerGlobal("proc_count", try vm.installHost("proc_count", define(&[_]T{}, hostProcCount)));
-
- try vm.registerGlobal("disk_sectors", try vm.installHost("disk_sectors", define(&[_]T{}, hostDiskSectors)));
- try vm.registerGlobal("disk_read", try vm.installHost("disk_read", define(&[_]T{ .number, .number }, hostDiskRead)));
- try vm.registerGlobal("disk_write", try vm.installHost("disk_write", define(&[_]T{ .number, .number }, hostDiskWrite)));
-}
+pub const Sink = serial.Sink;
+pub const debug_io = serial.debug_io;
+pub const Fb = hosts.Fb;
+pub const KernelOps = hosts.KernelOps;
// the vm outlives boot() now, so we juts make this hoe static
var g_vm: ?*revo.VM = null;
@@ -511,6 +19,7 @@ const init_program = @embedFile("k_font") ++ "\n" ++
@embedFile("k_console") ++ "\n" ++
@embedFile("k_vmm") ++ "\n" ++
@embedFile("k_fs") ++ "\n" ++
+ @embedFile("k_vfs") ++ "\n" ++
@embedFile("k_seed") ++ "\n" ++
@embedFile("k_proc") ++ "\n" ++
@embedFile("k_shell") ++ "\n" ++
@@ -518,9 +27,9 @@ const init_program = @embedFile("k_font") ++ "\n" ++
@embedFile("k_main");
pub fn boot(alloc: std.mem.Allocator, out: Sink, fb: ?Fb, ops: ?KernelOps) void {
- sink = out;
- g_fb = fb;
- g_kops = ops;
+ serial.setSink(out);
+ hosts.setFb(fb);
+ hosts.setKops(ops);
// note: we drive the vm directly (compile -> run) instead of the high level
// runtime.eval wrapper, which has a latent type error in revo that only
@@ -532,7 +41,7 @@ pub fn boot(alloc: std.mem.Allocator, out: Sink, fb: ?Fb, ops: ?KernelOps) void
};
vm.* = VM.init(.{
.alloc = alloc,
- .io = serial_io,
+ .io = serial.serial_io,
.argv = &.{},
.diag_alloc = alloc,
}) catch {
@@ -541,7 +50,7 @@ pub fn boot(alloc: std.mem.Allocator, out: Sink, fb: ?Fb, ops: ?KernelOps) void
};
out("bridge: revo VM up\r\n");
- registerPrimitives(vm) catch {
+ hosts.registerPrimitives(vm) catch {
out("bridge: registering primitives failed\r\n");
return;
};
@@ -554,7 +63,7 @@ pub fn boot(alloc: std.mem.Allocator, out: Sink, fb: ?Fb, ops: ?KernelOps) void
.ok => |art| art,
.err => |failure| {
out("bridge: compile error: ");
- var sw = SinkWriter.init();
+ var sw = serial.SinkWriter.init();
revo.lang.renderError(alloc, &sw.interface, .{ .name = "kernel", .text = init_program }, failure) catch {};
out("\r\n");
return;
@@ -576,7 +85,7 @@ pub fn boot(alloc: std.mem.Allocator, out: Sink, fb: ?Fb, ops: ?KernelOps) void
.ok => g_vm = vm, // handler is defined so hand the vm to the event loop
.err => |failure| {
out("\r\nbridge: runtime error: ");
- var sw = SinkWriter.init();
+ var sw = serial.SinkWriter.init();
failure.render(alloc, &sw.interface, init_program) catch {};
out("\r\n");
},
@@ -593,21 +102,20 @@ pub fn onKey(scancode: u8) void {
if (cb.asFunction() == null) return;
g_vm_running = true;
defer g_vm_running = false;
- _ = vm.callFunctionParts(cb, null, &[_]revo.Value{revo.Value.new.num(scancode)}, null) catch {
- sink("bridge: on_key threw\r\n");
+ _ = vm.callFunctionParts(cb, null, &[_]revo.Value{revo.Value.new.num(scancode)}, null) catch |e| {
+ serial.emit("bridge: on_key threw: ");
+ serial.emit(@errorName(e));
+ serial.emit("\r\n");
};
flushConsole(vm);
}
-// render any serial output produced while handling the key
fn flushConsole(vm: *revo.VM) void {
const func = vm.getGlobal("flush_console") orelse return;
if (func.asFunction() == null) return;
_ = vm.callFunctionParts(func, null, &.{}, null) catch {};
}
-// called each pit tick from the event loop, hands it to revos on_tick, which
-// renders any process output and blinks the cursor
pub fn onTick(ticks: u64) void {
const vm = g_vm orelse return;
const cb = vm.getGlobal("on_tick") orelse return;
@@ -649,3 +157,14 @@ pub fn fsReadInto(name: []const u8, offset: u64, dst: []u8) usize {
@memcpy(dst[0..n], bytes[0..n]);
return n;
}
+
+pub fn fsStore(name: []const u8, data: []const u8) bool {
+ const vm = g_vm orelse return false;
+ const func = vm.getGlobal("fs_store") orelse return false;
+ const name_val = vm.ownValueString(name) catch return false;
+ const data_val = vm.ownValueString(data) catch return false;
+ g_vm_running = true;
+ defer g_vm_running = false;
+ const r = vm.callFunctionParts(func, null, &[_]revo.Value{ name_val, data_val }, null) catch return false;
+ return (r.asNumOpt() orelse 0) == 1;
+}
diff --git a/bridge/convert.zig b/bridge/convert.zig
new file mode 100644
index 0000000..5718428
--- /dev/null
+++ b/bridge/convert.zig
@@ -0,0 +1,18 @@
+const std = @import("std");
+const revo = @import("revo");
+
+pub const HostResult = revo.baselib.host.HostResult;
+pub const Data = revo.Value;
+
+// revos only number type is f64 which is really fucking annoying
+pub fn f64ToInt(comptime T: type, n: f64) ?T {
+ if (!std.math.isFinite(n)) return null;
+ const min_f: f64 = @floatFromInt(std.math.minInt(T));
+ const max_f: f64 = @floatFromInt(std.math.maxInt(T));
+ if (n < min_f or n > max_f) return null;
+ return @intFromFloat(n);
+}
+
+pub fn argInt(comptime T: type, args: []const revo.Value, i: usize) ?T {
+ return f64ToInt(T, args[i].asNumOpt().?);
+}
diff --git a/bridge/hosts.zig b/bridge/hosts.zig
new file mode 100644
index 0000000..e329316
--- /dev/null
+++ b/bridge/hosts.zig
@@ -0,0 +1,380 @@
+const std = @import("std");
+const revo = @import("revo");
+const serial = @import("serial.zig");
+const convert = @import("convert.zig");
+
+const HostResult = convert.HostResult;
+const Data = convert.Data;
+const argInt = convert.argInt;
+const emit = serial.emit;
+
+// the actual framebuffer, the data layout feels pretty self explanatory
+pub const Fb = struct {
+ ptr: [*]u8,
+ width: usize,
+ height: usize,
+ pitch: usize,
+};
+var g_fb: ?Fb = null;
+
+pub fn setFb(fb: ?Fb) void {
+ g_fb = fb;
+}
+
+pub const KernelOps = struct {
+ phys_to_virt: *const fn (u64) u64,
+ alloc_frame: *const fn () u64, // 0 on failure
+ create_addrspace: *const fn () u64, // pml4 phys, 0 on failure (raw 64 bit entry copy incl. possible nx bit -> not f64 representable)
+ spawn: *const fn (u64, u64, u64, u64, u64) void, // pml4, entry, ustack, arg_ptr, arg_len -> queue a ring 3 task
+ deliver_input: *const fn (u8) bool, // hand a byte to a read()-blocked task, false if none
+ proc_exited: *const fn () bool, // true once after a process exits (consumed)
+ serial_next: *const fn () i64, // next mirrored serial byte, or -1 if empty
+
+ // read only state for revo to present (mem/uptime/ps/dump_memmap):
+ memmap_count: *const fn () u64,
+ memmap_base: *const fn (u64) u64,
+ memmap_len: *const fn (u64) u64,
+ memmap_kind: *const fn (u64) u64,
+ mem_free: *const fn () u64,
+ mem_total: *const fn () u64,
+
+ uptime: *const fn () u64,
+ proc_count: *const fn () u64,
+ prog_count: *const fn () u64,
+ prog_phys: *const fn (u64) u64,
+ prog_name: *const fn (u64) []const u8,
+ prog_size: *const fn (u64) u64,
+ task_max: *const fn () u64,
+ task_state: *const fn (u64) u64,
+
+ disk_sectors: *const fn () u64,
+ disk_read: *const fn (u64, u64) u64, // lba, phys buf -> 1 ok / 0 fail
+ disk_write: *const fn (u64, u64) u64, // lba, phys buf -> 1 ok / 0 fail
+};
+var g_kops: ?KernelOps = null;
+
+pub fn setKops(ops: ?KernelOps) void {
+ g_kops = ops;
+}
+
+fn kops() KernelOps {
+ return g_kops.?;
+}
+
+// pass shit to revo
+fn hostOutb(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ const port = argInt(u16, args, 0) orelse return HostResult.other("outb: port out of range");
+ const val = argInt(u8, args, 1) orelse return HostResult.other("outb: value out of range");
+ asm volatile ("outb %[v], %[p]"
+ :
+ : [v] "{al}" (val),
+ [p] "N{dx}" (port),
+ );
+ return HostResult.data(Data.new.nil());
+}
+
+// pass shit to revo
+fn hostInb(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ const port = argInt(u16, args, 0) orelse return HostResult.other("inb: port out of range");
+ const val = asm volatile ("inb %[p], %[r]"
+ : [r] "={al}" (-> u8),
+ : [p] "N{dx}" (port),
+ );
+ return HostResult.data(Data.new.num(val));
+}
+
+// pass shit to revo
+fn hostPuts(args: []const revo.Value, vm: *revo.VM) anyerror!HostResult {
+ const string_id = args[0].asString() orelse return HostResult.other("puts: not a string");
+ emit(vm.stringValue(string_id));
+ return HostResult.data(Data.new.nil());
+}
+
+// pass shit to revo
+fn hostFbWidth(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ return HostResult.data(Data.new.num(if (g_fb) |fb| fb.width else 0));
+}
+
+// pass shit to revo
+fn hostFbHeight(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ return HostResult.data(Data.new.num(if (g_fb) |fb| fb.height else 0));
+}
+
+// color is 0xrrggbb in the usual limine 32 bpp layout
+// pass shit to revo
+fn hostFillRect(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ const fb = g_fb orelse return HostResult.data(Data.new.nil()); // no screen, no op
+ const x = argInt(usize, args, 0) orelse return HostResult.other("fill_rect: bad x");
+ const y = argInt(usize, args, 1) orelse return HostResult.other("fill_rect: bad y");
+ const w = argInt(usize, args, 2) orelse return HostResult.other("fill_rect: bad w");
+ const h = argInt(usize, args, 3) orelse return HostResult.other("fill_rect: bad h");
+ const color = argInt(u32, args, 4) orelse return HostResult.other("fill_rect: bad color");
+
+ var yy = y;
+ const y_end = @min(y + h, fb.height);
+ const x_end = @min(x + w, fb.width);
+ while (yy < y_end) : (yy += 1) {
+ const row = fb.ptr + yy * fb.pitch;
+ var xx = x;
+ while (xx < x_end) : (xx += 1) {
+ const px: *align(1) u32 = @ptrCast(row + xx * 4);
+ px.* = color;
+ }
+ }
+ return HostResult.data(Data.new.nil());
+}
+
+// pass shit to revo
+fn hostFbScroll(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ const fb = g_fb orelse return HostResult.data(Data.new.nil());
+ const dy = argInt(usize, args, 0) orelse return HostResult.other("fb_scroll: bad dy");
+ const color = argInt(u32, args, 1) orelse return HostResult.other("fb_scroll: bad color");
+ if (dy == 0 or dy >= fb.height) return HostResult.data(Data.new.nil());
+ const move_bytes = dy * fb.pitch;
+ const total = fb.height * fb.pitch;
+ std.mem.copyForwards(u8, fb.ptr[0 .. total - move_bytes], fb.ptr[move_bytes..total]);
+ var y: usize = fb.height - dy;
+ while (y < fb.height) : (y += 1) {
+ const line = fb.ptr + y * fb.pitch;
+ var x: usize = 0;
+ while (x < fb.width) : (x += 1) {
+ const px: *align(1) u32 = @ptrCast(line + x * 4);
+ px.* = color;
+ }
+ }
+ return HostResult.data(Data.new.nil());
+}
+
+// pass shit to revo
+fn hostMemRead(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ const phys = argInt(u64, args, 0) orelse return HostResult.other("mem_read: bad addr");
+ const size = argInt(u64, args, 1) orelse return HostResult.other("mem_read: bad size");
+ const virt = kops().phys_to_virt(phys);
+ const val: u64 = switch (size) {
+ 1 => @as(*const u8, @ptrFromInt(virt)).*,
+ 2 => @as(*align(1) const u16, @ptrFromInt(virt)).*,
+ 4 => @as(*align(1) const u32, @ptrFromInt(virt)).*,
+ 8 => @as(*align(1) const u64, @ptrFromInt(virt)).*,
+ else => return HostResult.other("mem_read: size must be 1/2/4/8"),
+ };
+ return HostResult.data(Data.new.num(val));
+}
+
+// pass shit to revo
+fn hostMemCopy(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ const dst = kops().phys_to_virt(argInt(u64, args, 0) orelse return HostResult.other("mem_copy: bad dst"));
+ const src = kops().phys_to_virt(argInt(u64, args, 1) orelse return HostResult.other("mem_copy: bad src"));
+ const len = argInt(usize, args, 2) orelse return HostResult.other("mem_copy: bad len");
+ @memcpy(@as([*]u8, @ptrFromInt(dst))[0..len], @as([*]const u8, @ptrFromInt(src))[0..len]);
+ return HostResult.data(Data.new.nil());
+}
+
+// pass shit to revo
+fn hostMemZero(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ const dst = kops().phys_to_virt(argInt(u64, args, 0) orelse return HostResult.other("mem_zero: bad addr"));
+ const len = argInt(usize, args, 1) orelse return HostResult.other("mem_zero: bad len");
+ @memset(@as([*]u8, @ptrFromInt(dst))[0..len], 0);
+ return HostResult.data(Data.new.nil());
+}
+
+// pass shit to revo
+fn hostMemWrite(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ const phys = argInt(u64, args, 0) orelse return HostResult.other("mem_write: bad addr");
+ const val = argInt(u64, args, 1) orelse return HostResult.other("mem_write: bad val");
+ const size = argInt(u64, args, 2) orelse return HostResult.other("mem_write: bad size");
+ const virt = kops().phys_to_virt(phys);
+ switch (size) {
+ 1 => @as(*u8, @ptrFromInt(virt)).* = @truncate(val),
+ 2 => @as(*align(1) u16, @ptrFromInt(virt)).* = @truncate(val),
+ 4 => @as(*align(1) u32, @ptrFromInt(virt)).* = @truncate(val),
+ 8 => @as(*align(1) u64, @ptrFromInt(virt)).* = val,
+ else => return HostResult.other("mem_write: size must be 1/2/4/8"),
+ }
+ return HostResult.data(Data.new.nil());
+}
+
+// pass shit to revo
+fn hostInvlpg(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ const virt = argInt(u64, args, 0) orelse return HostResult.other("invlpg: bad addr");
+ asm volatile ("invlpg (%[v])"
+ :
+ : [v] "r" (virt),
+ : .{ .memory = true });
+ return HostResult.data(Data.new.nil());
+}
+
+// pass shit to revo
+fn hostFrameAlloc(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ return HostResult.data(Data.new.num(kops().alloc_frame()));
+}
+
+// pass shit to revo
+fn hostAsCreate(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ return HostResult.data(Data.new.num(kops().create_addrspace()));
+}
+
+// pass shit to revo
+fn hostProcRun(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ const as = argInt(u64, args, 0) orelse return HostResult.other("proc_run: bad as");
+ const entry = argInt(u64, args, 1) orelse return HostResult.other("proc_run: bad entry");
+ const ustack = argInt(u64, args, 2) orelse return HostResult.other("proc_run: bad ustack");
+ const arg = argInt(u64, args, 3) orelse return HostResult.other("proc_run: bad arg");
+ const arglen = argInt(u64, args, 4) orelse return HostResult.other("proc_run: bad arglen");
+ kops().spawn(as, entry, ustack, arg, arglen);
+ return HostResult.data(Data.new.nil());
+}
+
+// pass shit to revo
+fn hostStdinDeliver(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ const ch = argInt(u8, args, 0) orelse return HostResult.other("stdin_deliver: bad char");
+ return HostResult.data(Data.new.num(if (kops().deliver_input(ch)) @as(f64, 1) else 0));
+}
+
+// pass shit to revo
+fn hostProcExited(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ return HostResult.data(Data.new.num(if (kops().proc_exited()) @as(f64, 1) else 0));
+}
+
+// pass shit to revo
+fn hostSerialNext(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ return HostResult.data(Data.new.num(kops().serial_next()));
+}
+
+// pass shit to revo
+fn hostProgCount(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ return HostResult.data(Data.new.num(kops().prog_count()));
+}
+
+// pass shit to revo
+fn hostProgPhys(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ const i = argInt(u64, args, 0) orelse return HostResult.other("prog_phys: bad index");
+ return HostResult.data(Data.new.num(kops().prog_phys(i)));
+}
+
+// pass shit to revo
+fn hostProgName(args: []const revo.Value, vm: *revo.VM) anyerror!HostResult {
+ const i = argInt(u64, args, 0) orelse return HostResult.other("prog_name: bad index");
+ return HostResult.data(try vm.ownValueString(kops().prog_name(i)));
+}
+
+// pass shit to revo
+fn hostProgSize(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ const i = argInt(u64, args, 0) orelse return HostResult.other("prog_size: bad index");
+ return HostResult.data(Data.new.num(kops().prog_size(i)));
+}
+
+// pass shit to revo
+fn hostTaskMax(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ return HostResult.data(Data.new.num(kops().task_max()));
+}
+
+// pass shit to revo
+fn hostTaskState(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ const i = argInt(u64, args, 0) orelse return HostResult.other("task_state: bad index");
+ return HostResult.data(Data.new.num(kops().task_state(i)));
+}
+
+// pass shit to revo
+fn hostMemmapCount(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ return HostResult.data(Data.new.num(kops().memmap_count()));
+}
+
+// pass shit to revo
+fn hostMemmapBase(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ const index = argInt(u64, args, 0) orelse return HostResult.other("memmap_base: bad index");
+ return HostResult.data(Data.new.num(kops().memmap_base(index)));
+}
+
+// pass shit to revo
+fn hostMemmapLen(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ const index = argInt(u64, args, 0) orelse return HostResult.other("memmap_len: bad index");
+ return HostResult.data(Data.new.num(kops().memmap_len(index)));
+}
+
+// pass shit to revo
+fn hostMemmapKind(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ const index = argInt(u64, args, 0) orelse return HostResult.other("memmap_kind: bad index");
+ return HostResult.data(Data.new.num(kops().memmap_kind(index)));
+}
+
+// pass shit to revo
+fn hostMemFree(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ return HostResult.data(Data.new.num(kops().mem_free()));
+}
+
+// pass shit to revo
+fn hostMemTotal(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ return HostResult.data(Data.new.num(kops().mem_total()));
+}
+
+// pass shit to revo
+fn hostUptime(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ return HostResult.data(Data.new.num(kops().uptime()));
+}
+
+// pass shit to revo
+fn hostProcCount(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ return HostResult.data(Data.new.num(kops().proc_count()));
+}
+
+// pass shit to revo
+fn hostDiskSectors(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ return HostResult.data(Data.new.num(kops().disk_sectors()));
+}
+
+// pass shit to revo
+fn hostDiskRead(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ const lba = argInt(u64, args, 0) orelse return HostResult.other("disk_read: bad lba");
+ const phys = argInt(u64, args, 1) orelse return HostResult.other("disk_read: bad phys");
+ return HostResult.data(Data.new.num(kops().disk_read(lba, phys)));
+}
+
+// pass shit to revo
+fn hostDiskWrite(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ const lba = argInt(u64, args, 0) orelse return HostResult.other("disk_write: bad lba");
+ const phys = argInt(u64, args, 1) orelse return HostResult.other("disk_write: bad phys");
+ return HostResult.data(Data.new.num(kops().disk_write(lba, phys)));
+}
+
+// pass shit to revo
+pub fn registerPrimitives(vm: *revo.VM) !void {
+ const define = revo.baselib.host.define;
+ const T = revo.baselib.host.ParamType;
+ try vm.registerGlobal("outb", try vm.installHost("outb", define(&[_]T{ .number, .number }, hostOutb)));
+ try vm.registerGlobal("inb", try vm.installHost("inb", define(&[_]T{.number}, hostInb)));
+ try vm.registerGlobal("serial_puts", try vm.installHost("serial_puts", define(&[_]T{.string}, hostPuts)));
+ try vm.registerGlobal("fb_width", try vm.installHost("fb_width", define(&[_]T{}, hostFbWidth)));
+ try vm.registerGlobal("fb_height", try vm.installHost("fb_height", define(&[_]T{}, hostFbHeight)));
+ try vm.registerGlobal("fill_rect", try vm.installHost("fill_rect", define(&[_]T{ .number, .number, .number, .number, .number }, hostFillRect)));
+ try vm.registerGlobal("fb_scroll", try vm.installHost("fb_scroll", define(&[_]T{ .number, .number }, hostFbScroll)));
+
+ try vm.registerGlobal("mem_read", try vm.installHost("mem_read", define(&[_]T{ .number, .number }, hostMemRead)));
+ try vm.registerGlobal("mem_copy", try vm.installHost("mem_copy", define(&[_]T{ .number, .number, .number }, hostMemCopy)));
+ try vm.registerGlobal("mem_zero", try vm.installHost("mem_zero", define(&[_]T{ .number, .number }, hostMemZero)));
+ try vm.registerGlobal("mem_write", try vm.installHost("mem_write", define(&[_]T{ .number, .number, .number }, hostMemWrite)));
+ try vm.registerGlobal("invlpg", try vm.installHost("invlpg", define(&[_]T{.number}, hostInvlpg)));
+ try vm.registerGlobal("frame_alloc", try vm.installHost("frame_alloc", define(&[_]T{}, hostFrameAlloc)));
+ try vm.registerGlobal("as_create", try vm.installHost("as_create", define(&[_]T{}, hostAsCreate)));
+ try vm.registerGlobal("proc_run", try vm.installHost("proc_run", define(&[_]T{ .number, .number, .number, .number, .number }, hostProcRun)));
+ try vm.registerGlobal("stdin_deliver", try vm.installHost("stdin_deliver", define(&[_]T{.number}, hostStdinDeliver)));
+ try vm.registerGlobal("proc_exited", try vm.installHost("proc_exited", define(&[_]T{}, hostProcExited)));
+ try vm.registerGlobal("serial_next", try vm.installHost("serial_next", define(&[_]T{}, hostSerialNext)));
+ try vm.registerGlobal("prog_count", try vm.installHost("prog_count", define(&[_]T{}, hostProgCount)));
+ try vm.registerGlobal("prog_phys", try vm.installHost("prog_phys", define(&[_]T{.number}, hostProgPhys)));
+ try vm.registerGlobal("prog_name", try vm.installHost("prog_name", define(&[_]T{.number}, hostProgName)));
+ try vm.registerGlobal("prog_size", try vm.installHost("prog_size", define(&[_]T{.number}, hostProgSize)));
+ try vm.registerGlobal("task_max", try vm.installHost("task_max", define(&[_]T{}, hostTaskMax)));
+ try vm.registerGlobal("task_state", try vm.installHost("task_state", define(&[_]T{.number}, hostTaskState)));
+ try vm.registerGlobal("memmap_count", try vm.installHost("memmap_count", define(&[_]T{}, hostMemmapCount)));
+ try vm.registerGlobal("memmap_base", try vm.installHost("memmap_base", define(&[_]T{.number}, hostMemmapBase)));
+ try vm.registerGlobal("memmap_len", try vm.installHost("memmap_len", define(&[_]T{.number}, hostMemmapLen)));
+ try vm.registerGlobal("memmap_kind", try vm.installHost("memmap_kind", define(&[_]T{.number}, hostMemmapKind)));
+ try vm.registerGlobal("mem_free", try vm.installHost("mem_free", define(&[_]T{}, hostMemFree)));
+ try vm.registerGlobal("mem_total", try vm.installHost("mem_total", define(&[_]T{}, hostMemTotal)));
+ try vm.registerGlobal("uptime", try vm.installHost("uptime", define(&[_]T{}, hostUptime)));
+ try vm.registerGlobal("proc_count", try vm.installHost("proc_count", define(&[_]T{}, hostProcCount)));
+
+ try vm.registerGlobal("disk_sectors", try vm.installHost("disk_sectors", define(&[_]T{}, hostDiskSectors)));
+ try vm.registerGlobal("disk_read", try vm.installHost("disk_read", define(&[_]T{ .number, .number }, hostDiskRead)));
+ try vm.registerGlobal("disk_write", try vm.installHost("disk_write", define(&[_]T{ .number, .number }, hostDiskWrite)));
+}
diff --git a/bridge/serial.zig b/bridge/serial.zig
new file mode 100644
index 0000000..ec0bdb9
--- /dev/null
+++ b/bridge/serial.zig
@@ -0,0 +1,141 @@
+const std = @import("std");
+
+pub const Sink = *const fn ([]const u8) void;
+fn noopSink(_: []const u8) void {}
+var sink: Sink = noopSink;
+
+pub fn setSink(s: Sink) void {
+ sink = s;
+}
+
+// mirror everything to serial
+pub fn emit(bytes: []const u8) void {
+ sink(bytes);
+}
+
+// note: returns the total byte count written
+fn writeToSink(
+ header: []const u8,
+ data: []const []const u8,
+ splat: usize,
+) std.Io.Operation.FileWriteStreaming.Result {
+ if (header.len > 0) emit(header);
+ for (data[0..data.len -| 1]) |slice| {
+ if (slice.len > 0) emit(slice);
+ }
+ const last = data[data.len -| 1];
+ var i: usize = 0;
+ while (i < splat) : (i += 1) {
+ if (last.len > 0) emit(last);
+ }
+
+ var total: usize = header.len;
+ for (data[0..data.len -| 1]) |slice| total += slice.len;
+ total += last.len * splat;
+ return total;
+}
+
+fn ioOperate(_: ?*anyopaque, operation: std.Io.Operation) std.Io.Cancelable!std.Io.Operation.Result {
+ return switch (operation) {
+ .file_write_streaming => |req| .{
+ .file_write_streaming = writeToSink(req.header, req.data, req.splat),
+ },
+ .file_read_streaming => .{ .file_read_streaming = error.InputOutput },
+ .device_io_control => .{ .device_io_control = -1 },
+ .net_receive => .{ .net_receive = .{ error.NetworkDown, 0 } },
+ };
+}
+
+// no timer wired up yet
+// #call me the time bender the way i fuck shit up
+// todo: fine until we have the pit/tsc
+fn ioNow(_: ?*anyopaque, _: std.Io.Clock) std.Io.Timestamp {
+ return .{ .nanoseconds = 0 };
+}
+
+fn ioClockResolution(_: ?*anyopaque, _: std.Io.Clock) std.Io.Clock.ResolutionError!std.Io.Duration {
+ return .{ .nanoseconds = 1_000_000 };
+}
+
+fn ioSleep(_: ?*anyopaque, _: std.Io.Timeout) std.Io.Cancelable!void {}
+
+fn ioCrashHandler(_: ?*anyopaque) void {}
+
+// eval captures its own errors into a buffer, so this is never reached lol
+fn ioLockStderr(_: ?*anyopaque, _: ?std.Io.Terminal.Mode) std.Io.Cancelable!std.Io.LockedStderr {
+ return error.Canceled;
+}
+
+fn ioUnlockStderr(_: ?*anyopaque) void {}
+
+const io_vtable: std.Io.VTable = blk: {
+ var vtable = std.Io.failing.vtable.*;
+ vtable.operate = ioOperate;
+ vtable.now = ioNow;
+ vtable.clockResolution = ioClockResolution;
+ vtable.sleep = ioSleep;
+ vtable.crashHandler = ioCrashHandler;
+ vtable.lockStderr = ioLockStderr;
+ vtable.unlockStderr = ioUnlockStderr;
+ break :blk vtable;
+};
+
+pub const serial_io: std.Io = .{ .userdata = null, .vtable = &io_vtable };
+
+var dbg_fw: std.Io.File.Writer = undefined;
+var dbg_fw_buf: [256]u8 = undefined;
+
+fn dbgLockStderr(_: ?*anyopaque, _: ?std.Io.Terminal.Mode) std.Io.Cancelable!std.Io.LockedStderr {
+ const stderr_file: std.Io.File = .{ .handle = undefined, .flags = .{ .nonblocking = false } };
+ dbg_fw = stderr_file.writerStreaming(debug_io, &dbg_fw_buf);
+ return .{ .file_writer = &dbg_fw, .terminal_mode = .no_color };
+}
+fn dbgUnlockStderr(_: ?*anyopaque) void {
+ dbg_fw.interface.flush() catch {};
+}
+fn dbgSwapCancel(_: ?*anyopaque, new: std.Io.CancelProtection) std.Io.CancelProtection {
+ return new;
+}
+
+const debug_io_vtable: std.Io.VTable = blk: {
+ var vtable = std.Io.failing.vtable.*;
+ vtable.operate = ioOperate;
+ vtable.now = ioNow;
+ vtable.clockResolution = ioClockResolution;
+ vtable.sleep = ioSleep;
+ vtable.crashHandler = ioCrashHandler;
+ vtable.lockStderr = dbgLockStderr;
+ vtable.unlockStderr = dbgUnlockStderr;
+ vtable.swapCancelProtection = dbgSwapCancel;
+ break :blk vtable;
+};
+
+pub const debug_io: std.Io = .{ .userdata = null, .vtable = &debug_io_vtable };
+
+// an unbuffered std.io.writer that drains straight to the sink, so we can render
+// revos own error reports (which write to a std.io.writer) onto the console
+pub const SinkWriter = struct {
+ interface: std.Io.Writer,
+
+ pub fn init() SinkWriter {
+ return .{ .interface = .{ .buffer = &.{}, .vtable = &.{ .drain = drain, .flush = flush }, .end = 0 } };
+ }
+
+ fn drain(w: *std.Io.Writer, data: []const []const u8, splat: usize) std.Io.Writer.Error!usize {
+ _ = w;
+ var total: usize = 0;
+ for (data[0..data.len -| 1]) |slice| {
+ if (slice.len > 0) emit(slice);
+ total += slice.len;
+ }
+ const last = data[data.len -| 1];
+ var i: usize = 0;
+ while (i < splat) : (i += 1) {
+ if (last.len > 0) emit(last);
+ }
+ total += last.len * splat;
+ return total;
+ }
+
+ fn flush(_: *std.Io.Writer) std.Io.Writer.Error!void {}
+};