diff options
| author | hachem <im@hachem.wtf> | 2026-09-25 21:21:09 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2026-09-25 21:21:09 +0200 |
| commit | 09982d80c45805715984cc1cae96c37e4fc148c3 (patch) | |
| tree | f711d6e7f4d4cb2d4dcb555d6a139aae343d7334 /bridge/bridge.zig | |
| parent | fd3f51eecaecc12806a573c38263df1de43ee8c2 (diff) | |
feat: scheduler + cleanup
Diffstat (limited to 'bridge/bridge.zig')
| -rw-r--r-- | bridge/bridge.zig | 290 |
1 files changed, 186 insertions, 104 deletions
diff --git a/bridge/bridge.zig b/bridge/bridge.zig index a8a2243..2c4da6a 100644 --- a/bridge/bridge.zig +++ b/bridge/bridge.zig @@ -1,22 +1,22 @@ -// so the thing is right, revo isn't really that low level and we need to have a -// thin bridge to translate shit over. Its just to setup the VM, serial ports, -// heap shit like that. +// so the thing is right, revo isnt really that low level and we need to have a +// thin bridge to translate shit over. its just to setup the vm, serial ports, +// heap shit like that const std = @import("std"); const revo = @import("revo"); -// all the VM's output goes here, boot injects this at runtime so the -// bridge doesn't need to pull a serial port from its ass +// 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.Io's 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. +// 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, @@ -39,7 +39,7 @@ fn writeToSink( } // every file write (stdout, stderr, whatever) routes to the serial port but we -// don't have a filesystem or multiple sinks yet, so there's nowhere else to go +// 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| .{ @@ -53,7 +53,7 @@ fn ioOperate(_: ?*anyopaque, operation: std.Io.Operation) std.Io.Cancelable!std. // 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 +// todo: fine until we have the pit/tsc fn ioNow(_: ?*anyopaque, _: std.Io.Clock) std.Io.Timestamp { return .{ .nanoseconds = 0 }; } @@ -68,40 +68,40 @@ 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 can't provide +// 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 std's all-failing vtable and override only what the VM touches. +// start from stds all failing vtable and override only what the vm touches const io_vtable: std.Io.VTable = blk: { - var v = std.Io.failing.vtable.*; - v.operate = ioOperate; - v.now = ioNow; - v.clockResolution = ioClockResolution; - v.sleep = ioSleep; - v.crashHandler = ioCrashHandler; - v.lockStderr = ioLockStderr; - v.unlockStderr = ioUnlockStderr; - break :blk v; + 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 std's debug IO (for std.debug.print, which revo hits -// on a few error paths). std's default debug IO is a threaded stderr writer that -// doesn't exist on freestandin, and std.Io.failing's 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. +// 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 doesn't exist on - // freestanding so we build the file by hand instead. ioOperate ignores + // 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); @@ -115,22 +115,22 @@ fn dbgSwapCancel(_: ?*anyopaque, new: std.Io.CancelProtection) std.Io.CancelProt } const debug_io_vtable: std.Io.VTable = blk: { - var v = std.Io.failing.vtable.*; - v.operate = ioOperate; - v.now = ioNow; - v.clockResolution = ioClockResolution; - v.sleep = ioSleep; - v.crashHandler = ioCrashHandler; - v.lockStderr = dbgLockStderr; - v.unlockStderr = dbgUnlockStderr; - v.swapCancelProtection = dbgSwapCancel; - break :blk v; + 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 -// revo's own error reports (which write to a std.Io.Writer) onto the console +// 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, @@ -141,9 +141,9 @@ const SinkWriter = struct { 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]) |s| { - if (s.len > 0) emit(s); - total += s.len; + 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; @@ -160,11 +160,11 @@ const SinkWriter = struct { const HostResult = revo.baselib.host.HostResult; const Data = revo.Value; -// revo's only number type is f64 which is really fucking annoying +// 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) isn't representable - // as an f64 literal, so comparing against the comptime_int directly won't + // 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)); @@ -172,10 +172,17 @@ fn f64ToInt(comptime T: type, n: f64) ?T { 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 = f64ToInt(u16, args[0].asNumOpt().?) orelse return HostResult.other("outb: port out of range"); - const val = f64ToInt(u8, args[1].asNumOpt().?) orelse return HostResult.other("outb: value out of range"); + 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), @@ -186,7 +193,7 @@ fn hostOutb(args: []const revo.Value, _: *revo.VM) anyerror!HostResult { // pass shit to revo fn hostInb(args: []const revo.Value, _: *revo.VM) anyerror!HostResult { - const port = f64ToInt(u16, args[0].asNumOpt().?) orelse return HostResult.other("inb: port out of range"); + 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), @@ -196,8 +203,8 @@ fn hostInb(args: []const revo.Value, _: *revo.VM) anyerror!HostResult { // pass shit to revo fn hostPuts(args: []const revo.Value, vm: *revo.VM) anyerror!HostResult { - const s = args[0].asString() orelse return HostResult.other("puts: not a string"); - emit(vm.stringValue(s)); + 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()); } @@ -218,14 +225,14 @@ 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 +// 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 = f64ToInt(usize, args[0].asNumOpt().?) orelse return HostResult.other("fill_rect: bad x"); - const y = f64ToInt(usize, args[1].asNumOpt().?) orelse return HostResult.other("fill_rect: bad y"); - const w = f64ToInt(usize, args[2].asNumOpt().?) orelse return HostResult.other("fill_rect: bad w"); - const h = f64ToInt(usize, args[3].asNumOpt().?) orelse return HostResult.other("fill_rect: bad h"); - const color = f64ToInt(u32, args[4].asNumOpt().?) orelse return HostResult.other("fill_rect: bad color"); + 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); @@ -249,8 +256,8 @@ fn emit(bytes: []const u8) void { // 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 = f64ToInt(usize, args[0].asNumOpt().?) orelse return HostResult.other("fb_scroll: bad dy"); - const color = f64ToInt(u32, args[1].asNumOpt().?) orelse return HostResult.other("fb_scroll: bad color"); + 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; @@ -270,9 +277,20 @@ fn hostFbScroll(args: []const revo.Value, _: *revo.VM) anyerror!HostResult { 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) - run_process: *const fn (u64, u64, u64) void, // pml4, entry, ustack + 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, elf_phys: u64, elf_size: u64, }; @@ -281,14 +299,14 @@ var g_kops: ?KernelOps = null; // 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 ops = g_kops orelse return HostResult.other("no kernel ops"); - const phys = f64ToInt(u64, args[0].asNumOpt().?) orelse return HostResult.other("mem_read: bad addr"); - const size = f64ToInt(u64, args[1].asNumOpt().?) orelse return HostResult.other("mem_read: bad size"); - const v = ops.phys_to_virt(phys); + 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 = ops.phys_to_virt(phys); const val: u64 = switch (size) { - 1 => @as(*const u8, @ptrFromInt(v)).*, - 2 => @as(*align(1) const u16, @ptrFromInt(v)).*, - 4 => @as(*align(1) const u32, @ptrFromInt(v)).*, - 8 => @as(*align(1) const u64, @ptrFromInt(v)).*, + 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)); @@ -297,9 +315,9 @@ fn hostMemRead(args: []const revo.Value, _: *revo.VM) anyerror!HostResult { // mem_copy(dst_phys, src_phys, len): raw copy between physical regions fn hostMemCopy(args: []const revo.Value, _: *revo.VM) anyerror!HostResult { const ops = g_kops orelse return HostResult.other("no kernel ops"); - const dst = ops.phys_to_virt(f64ToInt(u64, args[0].asNumOpt().?) orelse return HostResult.other("mem_copy: bad dst")); - const src = ops.phys_to_virt(f64ToInt(u64, args[1].asNumOpt().?) orelse return HostResult.other("mem_copy: bad src")); - const len = f64ToInt(usize, args[2].asNumOpt().?) orelse return HostResult.other("mem_copy: bad len"); + const dst = ops.phys_to_virt(argInt(u64, args, 0) orelse return HostResult.other("mem_copy: bad dst")); + const src = ops.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()); } @@ -307,8 +325,8 @@ fn hostMemCopy(args: []const revo.Value, _: *revo.VM) anyerror!HostResult { // mem_zero(phys, len) fn hostMemZero(args: []const revo.Value, _: *revo.VM) anyerror!HostResult { const ops = g_kops orelse return HostResult.other("no kernel ops"); - const dst = ops.phys_to_virt(f64ToInt(u64, args[0].asNumOpt().?) orelse return HostResult.other("mem_zero: bad addr")); - const len = f64ToInt(usize, args[1].asNumOpt().?) orelse return HostResult.other("mem_zero: bad len"); + const dst = ops.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()); } @@ -316,23 +334,23 @@ fn hostMemZero(args: []const revo.Value, _: *revo.VM) anyerror!HostResult { // 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 ops = g_kops orelse return HostResult.other("no kernel ops"); - const phys = f64ToInt(u64, args[0].asNumOpt().?) orelse return HostResult.other("mem_write: bad addr"); - const val = f64ToInt(u64, args[1].asNumOpt().?) orelse return HostResult.other("mem_write: bad val"); - const size = f64ToInt(u64, args[2].asNumOpt().?) orelse return HostResult.other("mem_write: bad size"); - const v = ops.phys_to_virt(phys); + 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 = ops.phys_to_virt(phys); switch (size) { - 1 => @as(*u8, @ptrFromInt(v)).* = @truncate(val), - 2 => @as(*align(1) u16, @ptrFromInt(v)).* = @truncate(val), - 4 => @as(*align(1) u32, @ptrFromInt(v)).* = @truncate(val), - 8 => @as(*align(1) u64, @ptrFromInt(v)).* = val, + 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 +// invlpg(virt): flush one page from the tlb after remapping it fn hostInvlpg(args: []const revo.Value, _: *revo.VM) anyerror!HostResult { - const virt = f64ToInt(u64, args[0].asNumOpt().?) orelse return HostResult.other("invlpg: bad addr"); + const virt = argInt(u64, args, 0) orelse return HostResult.other("invlpg: bad addr"); asm volatile ("invlpg (%[v])" : : [v] "r" (virt), @@ -352,13 +370,28 @@ fn hostAsCreate(_: []const revo.Value, _: *revo.VM) anyerror!HostResult { fn hostProcRun(args: []const revo.Value, _: *revo.VM) anyerror!HostResult { const ops = g_kops orelse return HostResult.other("no kernel ops"); - const as = f64ToInt(u64, args[0].asNumOpt().?) orelse return HostResult.other("proc_run: bad as"); - const entry = f64ToInt(u64, args[1].asNumOpt().?) orelse return HostResult.other("proc_run: bad entry"); - const ustack = f64ToInt(u64, args[2].asNumOpt().?) orelse return HostResult.other("proc_run: bad ustack"); - ops.run_process(as, entry, ustack); + 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"); + ops.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 ops = g_kops orelse return HostResult.other("no kernel ops"); + const ch = argInt(u8, args, 0) orelse return HostResult.other("stdin_deliver: bad char"); + return HostResult.data(Data.new.num(if (ops.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 { + const ops = g_kops orelse return HostResult.other("no kernel ops"); + return HostResult.data(Data.new.num(if (ops.proc_exited()) @as(f64, 1) else 0)); +} + // serial_next() -> byte or -1 fn hostSerialNext(_: []const revo.Value, _: *revo.VM) anyerror!HostResult { const ops = g_kops orelse return HostResult.other("no kernel ops"); @@ -375,6 +408,43 @@ fn hostElfSize(_: []const revo.Value, _: *revo.VM) anyerror!HostResult { return HostResult.data(Data.new.num(ops.elf_size)); } +// read only state getters revo uses for mem/uptime/ps and dump_memmap +fn hostMemmapCount(_: []const revo.Value, _: *revo.VM) anyerror!HostResult { + const ops = g_kops orelse return HostResult.other("no kernel ops"); + return HostResult.data(Data.new.num(ops.memmap_count())); +} +fn hostMemmapBase(args: []const revo.Value, _: *revo.VM) anyerror!HostResult { + const ops = g_kops orelse return HostResult.other("no kernel ops"); + const index = argInt(u64, args, 0) orelse return HostResult.other("memmap_base: bad index"); + return HostResult.data(Data.new.num(ops.memmap_base(index))); +} +fn hostMemmapLen(args: []const revo.Value, _: *revo.VM) anyerror!HostResult { + const ops = g_kops orelse return HostResult.other("no kernel ops"); + const index = argInt(u64, args, 0) orelse return HostResult.other("memmap_len: bad index"); + return HostResult.data(Data.new.num(ops.memmap_len(index))); +} +fn hostMemmapKind(args: []const revo.Value, _: *revo.VM) anyerror!HostResult { + const ops = g_kops orelse return HostResult.other("no kernel ops"); + const index = argInt(u64, args, 0) orelse return HostResult.other("memmap_kind: bad index"); + return HostResult.data(Data.new.num(ops.memmap_kind(index))); +} +fn hostMemFree(_: []const revo.Value, _: *revo.VM) anyerror!HostResult { + const ops = g_kops orelse return HostResult.other("no kernel ops"); + return HostResult.data(Data.new.num(ops.mem_free())); +} +fn hostMemTotal(_: []const revo.Value, _: *revo.VM) anyerror!HostResult { + const ops = g_kops orelse return HostResult.other("no kernel ops"); + return HostResult.data(Data.new.num(ops.mem_total())); +} +fn hostUptime(_: []const revo.Value, _: *revo.VM) anyerror!HostResult { + const ops = g_kops orelse return HostResult.other("no kernel ops"); + return HostResult.data(Data.new.num(ops.uptime())); +} +fn hostProcCount(_: []const revo.Value, _: *revo.VM) anyerror!HostResult { + const ops = g_kops orelse return HostResult.other("no kernel ops"); + return HostResult.data(Data.new.num(ops.proc_count())); +} + fn registerPrimitives(vm: *revo.VM) !void { const define = revo.baselib.host.define; const T = revo.baselib.host.ParamType; @@ -385,7 +455,7 @@ fn registerPrimitives(vm: *revo.VM) !void { 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))); - // low-level: memory + address spaces + process launch (revo's ELF loader) + // low level: memory + address spaces + process launch (revos elf loader) 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))); @@ -394,12 +464,22 @@ fn registerPrimitives(vm: *revo.VM) !void { 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("elf_phys", try vm.installHost("elf_phys", define(&[_]T{}, hostElfPhys))); try vm.registerGlobal("elf_size", try vm.installHost("elf_size", define(&[_]T{}, hostElfSize))); + 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))); } -// the VM outlives boot() now, so we juts make this hoe static +// the vm outlives boot() now, so we juts make this hoe static var g_vm: ?*revo.VM = null; const init_program = @embedFile("k_font") ++ "\n" ++ @@ -415,9 +495,9 @@ pub fn boot(alloc: std.mem.Allocator, out: Sink, fb: ?Fb, ops: ?KernelOps) void g_fb = fb; g_kops = 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 - // trips when analyzed. same low-level path revo's own wasm entry uses. + // 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 + // trips when analyzed. same low level path revos own wasm entry uses const VM = revo.VM; const vm = alloc.create(VM) catch { out("bridge: OOM creating VM\r\n"); @@ -454,7 +534,7 @@ pub fn boot(alloc: std.mem.Allocator, out: Sink, fb: ?Fb, ops: ?KernelOps) void }, }; - // NOTE: on purpose we don't free artifact.instructions/spans cause on_key's + // note: on purpose we dont free artifact.instructions/spans cause on_keys // bytecode lives in there and we call it for the life of the kernel vm.setProgramDebugInfo(artifact.spans, "", "kernel") catch { out("bridge: setProgramDebugInfo failed\r\n"); @@ -466,7 +546,7 @@ pub fn boot(alloc: std.mem.Allocator, out: Sink, fb: ?Fb, ops: ?KernelOps) void return; }; switch (eval_result) { - .ok => g_vm = vm, // handler is defined so hand the VM to the event loop + .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(); @@ -477,8 +557,8 @@ pub fn boot(alloc: std.mem.Allocator, out: Sink, fb: ?Fb, ops: ?KernelOps) void } // called from the boot event loop for each raw scancode -// hand it to revo's on_key -// runs in normal context (not the ISR), so allocation is fine here +// hand it to revos on_key +// runs in normal context (not the isr), so allocation is fine here // i think pub fn onKey(scancode: u8) void { const vm = g_vm orelse return; @@ -492,11 +572,13 @@ pub fn onKey(scancode: u8) void { // render any serial output produced while handling the key fn flushConsole(vm: *revo.VM) void { - const f = vm.getGlobal("flush_console") orelse return; - if (f.asFunction() == null) return; - _ = vm.callFunctionParts(f, null, &.{}, null) catch {}; + 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; |
