hachem.wtf
aboutsummaryrefslogtreecommitdiff
path: root/bridge/bridge.zig
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-09-25 11:31:59 +0200
committerhachem <im@hachem.wtf>2026-09-25 11:31:59 +0200
commited285c9fc665e262ed21c7bf5734acb2b7a532db (patch)
treee51434e8069bc7219221cc1136515828d0293aca /bridge/bridge.zig
parentd18d1da05a4d065d54787434d6bc46f3354626a5 (diff)
feat: load + run native ELF binaries
Diffstat (limited to 'bridge/bridge.zig')
-rw-r--r--bridge/bridge.zig143
1 files changed, 138 insertions, 5 deletions
diff --git a/bridge/bridge.zig b/bridge/bridge.zig
index 0c934eb..8b5a30f 100644
--- a/bridge/bridge.zig
+++ b/bridge/bridge.zig
@@ -267,8 +267,114 @@ fn hostFbScroll(args: []const revo.Data, _: *revo.VM) anyerror!HostResult {
return HostResult.data(Data.new.nil());
}
-// install the primitives as globals the revo program can call by name
-// idk how else we should be doing this, lung said he'll do stdlib stuff
+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
+ serial_next: *const fn () i64, // next mirrored serial byte, or -1 if empty
+ elf_phys: u64,
+ elf_size: u64,
+};
+var g_kops: ?KernelOps = null;
+
+// mem_read(phys, size) -> value: read 1/2/4/8 bytes of physical memory
+fn hostMemRead(args: []const revo.Data, _: *revo.VM) anyerror!HostResult {
+ const ops = g_kops orelse return HostResult.other("no kernel ops");
+ const phys = f64ToInt(u64, args[0].asNum().?) orelse return HostResult.other("mem_read: bad addr");
+ const size = f64ToInt(u64, args[1].asNum().?) orelse return HostResult.other("mem_read: bad size");
+ const v = 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)).*,
+ 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.Data, _: *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].asNum().?) orelse return HostResult.other("mem_copy: bad dst"));
+ const src = ops.phys_to_virt(f64ToInt(u64, args[1].asNum().?) orelse return HostResult.other("mem_copy: bad src"));
+ const len = f64ToInt(usize, args[2].asNum().?) 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.Data, _: *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].asNum().?) orelse return HostResult.other("mem_zero: bad addr"));
+ const len = f64ToInt(usize, args[1].asNum().?) 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.Data, _: *revo.VM) anyerror!HostResult {
+ const ops = g_kops orelse return HostResult.other("no kernel ops");
+ const phys = f64ToInt(u64, args[0].asNum().?) orelse return HostResult.other("mem_write: bad addr");
+ const val = f64ToInt(u64, args[1].asNum().?) orelse return HostResult.other("mem_write: bad val");
+ const size = f64ToInt(u64, args[2].asNum().?) orelse return HostResult.other("mem_write: bad size");
+ const v = 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,
+ 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.Data, _: *revo.VM) anyerror!HostResult {
+ const virt = f64ToInt(u64, args[0].asNum().?) 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.Data, _: *revo.VM) anyerror!HostResult {
+ const ops = g_kops orelse return HostResult.other("no kernel ops");
+ return HostResult.data(Data.new.num(ops.alloc_frame()));
+}
+
+fn hostAsCreate(_: []const revo.Data, _: *revo.VM) anyerror!HostResult {
+ const ops = g_kops orelse return HostResult.other("no kernel ops");
+ return HostResult.data(Data.new.num(ops.create_addrspace()));
+}
+
+fn hostProcRun(args: []const revo.Data, _: *revo.VM) anyerror!HostResult {
+ const ops = g_kops orelse return HostResult.other("no kernel ops");
+ const as = f64ToInt(u64, args[0].asNum().?) orelse return HostResult.other("proc_run: bad as");
+ const entry = f64ToInt(u64, args[1].asNum().?) orelse return HostResult.other("proc_run: bad entry");
+ const ustack = f64ToInt(u64, args[2].asNum().?) orelse return HostResult.other("proc_run: bad ustack");
+ ops.run_process(as, entry, ustack);
+ return HostResult.data(Data.new.nil());
+}
+
+// serial_next() -> byte or -1
+fn hostSerialNext(_: []const revo.Data, _: *revo.VM) anyerror!HostResult {
+ const ops = g_kops orelse return HostResult.other("no kernel ops");
+ return HostResult.data(Data.new.num(ops.serial_next()));
+}
+
+fn hostElfPhys(_: []const revo.Data, _: *revo.VM) anyerror!HostResult {
+ const ops = g_kops orelse return HostResult.other("no kernel ops");
+ return HostResult.data(Data.new.num(ops.elf_phys));
+}
+
+fn hostElfSize(_: []const revo.Data, _: *revo.VM) anyerror!HostResult {
+ const ops = g_kops orelse return HostResult.other("no kernel ops");
+ return HostResult.data(Data.new.num(ops.elf_size));
+}
+
fn registerPrimitives(vm: *revo.VM) !void {
const define = revo.std_lib.define;
const T = revo.std_lib.TypeSpec;
@@ -279,16 +385,35 @@ 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)
+ 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("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)));
}
// the VM outlives boot() now, so we juts make this hoe static
var g_vm: ?*revo.VM = null;
-const init_program = @embedFile("kernel_main");
+const init_program = @embedFile("k_font") ++ "\n" ++
+ @embedFile("k_console") ++ "\n" ++
+ @embedFile("k_vmm") ++ "\n" ++
+ @embedFile("k_proc") ++ "\n" ++
+ @embedFile("k_shell") ++ "\n" ++
+ @embedFile("k_input") ++ "\n" ++
+ @embedFile("k_main");
-pub fn boot(alloc: std.mem.Allocator, out: Sink, fb: ?Fb) void {
+pub fn boot(alloc: std.mem.Allocator, out: Sink, fb: ?Fb, ops: ?KernelOps) void {
sink = out;
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
@@ -328,9 +453,9 @@ pub fn boot(alloc: std.mem.Allocator, out: Sink, fb: ?Fb) void {
return;
},
};
+
// NOTE: on purpose we don't free artifact.instructions/spans cause on_key's
// 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");
return;
@@ -362,4 +487,12 @@ pub fn onKey(scancode: u8) void {
_ = vm.callFunctionParts(cb, null, &[_]revo.Data{revo.Data.new.num(scancode)}, null) catch {
sink("bridge: on_key threw\r\n");
};
+ flushConsole(vm);
+}
+
+// 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 {};
}