hachem.wtf
aboutsummaryrefslogtreecommitdiff
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
parentd18d1da05a4d065d54787434d6bc46f3354626a5 (diff)
feat: load + run native ELF binaries
-rw-r--r--boot/boot.zig33
-rw-r--r--boot/elf.zig2
-rw-r--r--boot/serial.zig18
-rw-r--r--boot/user.zig39
-rw-r--r--bridge/bridge.zig143
-rw-r--r--build.zig27
-rw-r--r--kernel/console.rv64
-rw-r--r--kernel/font.rv102
-rw-r--r--kernel/input.rv47
-rw-r--r--kernel/main.rv236
-rw-r--r--kernel/proc.rv40
-rw-r--r--kernel/shell.rv55
-rw-r--r--kernel/vmm.rv24
-rw-r--r--user/link.ld8
-rw-r--r--user/program.c22
15 files changed, 596 insertions, 264 deletions
diff --git a/boot/boot.zig b/boot/boot.zig
index 4e9d0b4..108afc8 100644
--- a/boot/boot.zig
+++ b/boot/boot.zig
@@ -8,6 +8,7 @@ const pmm = @import("pmm.zig");
const heap = @import("heap.zig");
const vmm = @import("vmm.zig");
const user = @import("user.zig");
+const elf = @import("elf.zig");
const keyboard = @import("keyboard.zig");
const bridge = @import("bridge");
@@ -48,6 +49,21 @@ export var memmap_request: limine.MemoryMapRequest linksection(".limine_requests
// 4 MiB stack
export var stack_size_request: limine.StackSizeRequest linksection(".limine_requests") = .{ .stack_size = 4 * 1024 * 1024 };
+// wrappers matching bridge.KernelOps (pmm/vmm return optionals; the revo-facing
+// primitives use 0 as the failure sentinel).
+fn kPhysToVirt(p: u64) u64 {
+ return pmm.physToVirt(p);
+}
+fn kAllocFrame() u64 {
+ return pmm.alloc() orelse 0;
+}
+fn kCreateAddrspace() u64 {
+ return vmm.createAddressSpace() orelse 0;
+}
+fn kSerialNext() i64 {
+ return if (serial.mirrorNext()) |c| @intCast(c) else -1;
+}
+
// ENTRY(_start)
export fn _start() callconv(.c) noreturn {
serial.init();
@@ -121,16 +137,27 @@ export fn _start() callconv(.c) noreturn {
serial.write("vmm test: switched back OK\r\n");
}
- user.runTestProgram();
+ const elf_pages = (elf.hello_elf.len + 4095) / 4096;
+ const elf_phys = pmm.allocContig(elf_pages) orelse @panic("no room for the embedded ELF");
+ @memcpy(@as([*]u8, @ptrFromInt(pmm.physToVirt(elf_phys)))[0..elf.hello_elf.len], elf.hello_elf);
+ const kops: bridge.KernelOps = .{
+ .phys_to_virt = &kPhysToVirt,
+ .alloc_frame = &kAllocFrame,
+ .create_addrspace = &kCreateAddrspace,
+ .run_process = &user.runProcess,
+ .serial_next = &kSerialNext,
+ .elf_phys = elf_phys,
+ .elf_size = elf.hello_elf.len,
+ };
- // hand the kernel heap + raw framebuffer to revo and bring up the VM
+ // hand the kernel heap + framebuffer + low-level ops to revo and bring up the VM
const fb: ?bridge.Fb = if (framebuffer.get()) |s| .{
.ptr = s.address,
.width = s.width,
.height = s.height,
.pitch = s.pitch,
} else null;
- bridge.boot(heap.allocator(), serial.write, fb);
+ bridge.boot(heap.allocator(), serial.write, fb, kops);
// - sleep until an interrupt
// - drain the keyboard
diff --git a/boot/elf.zig b/boot/elf.zig
new file mode 100644
index 0000000..a9199db
--- /dev/null
+++ b/boot/elf.zig
@@ -0,0 +1,2 @@
+// load c program
+pub const hello_elf = @embedFile("user_program");
diff --git a/boot/serial.zig b/boot/serial.zig
index fab0e66..ca0bb08 100644
--- a/boot/serial.zig
+++ b/boot/serial.zig
@@ -38,11 +38,29 @@ pub fn init() void {
outb(COM1 + 4, 0x0B); // RTS/DSR set
}
+// every byte we send to the port is also captured here so the revo
+// console can render the same stream
+var mirror: [1 << 15]u8 = undefined; // 32 KiB, power of two
+var m_head: usize = 0; // next byte the console will render
+var m_tail: usize = 0; // next slot putc will write
+
// COM1+5 (bit 5) = transmit holding register and
// spin until it's clear to send
fn putc(c: u8) void {
while (inb(COM1 + 5) & 0x20 == 0) {}
outb(COM1, c);
+ mirror[m_tail & (mirror.len - 1)] = c;
+ m_tail +%= 1;
+}
+
+// next un-rendered byte, or null if caught up. if the console fell far enough
+// behind to lap the ring, drop the oldest bytes.
+pub fn mirrorNext() ?u8 {
+ if (m_head == m_tail) return null;
+ if (m_tail -% m_head > mirror.len) m_head = m_tail -% mirror.len;
+ const c = mirror[m_head & (mirror.len - 1)];
+ m_head +%= 1;
+ return c;
}
// a raw serial console needs \r\n, but revo (and most sane code) emits bare \n.
diff --git a/boot/user.zig b/boot/user.zig
index 61345e5..4272269 100644
--- a/boot/user.zig
+++ b/boot/user.zig
@@ -1,7 +1,7 @@
// ring3 entry + the syscall boundary
// drop user mode with iretq, and come back to the kernel
//
-// see: - https://wiki.osdev.org/System_Calls
+// see: - https://wiki.osdev.org/System_Calls
// - https://wiki.osdev.org/Getting_to_Ring_3
const serial = @import("serial.zig");
@@ -52,17 +52,7 @@ pub fn enterUser(entry: u64, ustack: u64) void {
g_user_stack = ustack;
// this is the truest most correct statement ever
// get this man a true
- asm volatile ("call enterUserAsm" ::: .{
- .memory = true,
- .rax = true,
- .rcx = true,
- .rdx = true,
- .rsi = true,
- .rdi = true,
- .r8 = true,
- .r9 = true,
- .r10 = true,
- .r11 = true });
+ asm volatile ("call enterUserAsm" ::: .{ .memory = true, .rax = true, .rcx = true, .rdx = true, .rsi = true, .rdi = true, .r8 = true, .r9 = true, .r10 = true, .r11 = true });
}
export fn enterUserAsm() callconv(.naked) void {
@@ -134,15 +124,28 @@ export fn syscallStub() callconv(.naked) void {
);
}
-// returns 0 to resume the program, 1 to return to the kernel (exit).
+pub fn runProcess(pml4: u64, entry: u64, ustack: u64) void {
+ const prev = vmm.activePml4();
+ vmm.loadPml4(pml4);
+ enterUser(entry, ustack);
+ vmm.loadPml4(prev);
+}
+
export fn syscallHandler(frame: *SyscallFrame) callconv(.c) u64 {
switch (frame.rax) {
- 0 => { // exit
- serial.write("[syscall] exit -> back to kernel\r\n");
- return 1;
+ 0 => { // exit(code): code in rdi
+ return 1; // tell the stub to unwind back into the kernel
},
- 1 => { // "hello" -- a fixed message, no user pointer to validate yet
- serial.write("[syscall] hello from ring 3!\r\n");
+ 1 => { // write(ptr, len): rdi = user pointer, rsi = length
+ const len = frame.rsi;
+ // the process's address space is active during the syscall, so the
+ // kernel can read the user pointer directly. cap the length so a
+ // bogus arg can't run us off into unmapped memory.
+ if (len > 0 and len <= 4096) {
+ const bytes: [*]const u8 = @ptrFromInt(frame.rdi);
+ serial.write(bytes[0..len]); // mirrored -> the revo console renders it
+ }
+ frame.rax = len; // syscall return value
return 0;
},
else => {
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 {};
}
diff --git a/build.zig b/build.zig
index 46322a7..6d33ab7 100644
--- a/build.zig
+++ b/build.zig
@@ -63,12 +63,33 @@ pub fn build(b: *std.Build) void {
.link_libc = false,
});
bridge_mod.addImport("revo", revo_mod);
- // the OS source lives in kernel/*.rv; @embedFile("kernel_main") in the bridge
- // pulls it in at build time (an anonymous import lets it cross the module dir)
- bridge_mod.addAnonymousImport("kernel_main", .{ .root_source_file = b.path("kernel/main.rv") });
+ inline for (.{
+ .{ "k_font", "kernel/font.rv" },
+ .{ "k_console", "kernel/console.rv" },
+ .{ "k_vmm", "kernel/vmm.rv" },
+ .{ "k_proc", "kernel/proc.rv" },
+ .{ "k_shell", "kernel/shell.rv" },
+ .{ "k_input", "kernel/input.rv" },
+ .{ "k_main", "kernel/main.rv" },
+ }) |e| {
+ bridge_mod.addAnonymousImport(e[0], .{ .root_source_file = b.path(e[1]) });
+ }
kernel.root_module.addImport("bridge", bridge_mod);
+ const cc = b.addSystemCommand(&.{
+ "zig", "cc",
+ "-target", "x86_64-freestanding",
+ "-ffreestanding", "-nostdlib",
+ "-static", "-no-pie",
+ "-fno-stack-protector", "-fno-sanitize=all",
+ "-Wl,-T,user/link.ld", "-Wl,--build-id=none",
+ "-o",
+ });
+ const program_elf = cc.addOutputFileArg("user_program.elf");
+ cc.addFileArg(b.path("user/program.c"));
+ kernel.root_module.addAnonymousImport("user_program", .{ .root_source_file = program_elf });
+
b.installArtifact(kernel);
}
diff --git a/kernel/console.rv b/kernel/console.rv
new file mode 100644
index 0000000..a448ef9
--- /dev/null
+++ b/kernel/console.rv
@@ -0,0 +1,64 @@
+const FW = 8 # glyph width in pixels
+const FH = 16 # glyph height in pixels (8x16 VGA bitmap font)
+const FG = 0xd0d0d0
+const CON_BG = 0x101018
+
+global con = { col = 0, row = 0, scale = 1 }
+
+# cell size in pixels at the current zoom. named cell_* (not cw/ch) so they don't
+# collide with draw_glyph's `ch` character parameter.
+fn cell_w() FW * con.scale
+fn cell_h() FH * con.scale
+fn con_cols() fb_width() / cell_w()
+fn con_rows() fb_height() / cell_h()
+
+fn draw_glyph(col, row, ch) do
+ const x0 = col * cell_w()
+ const y0 = row * cell_h()
+ fill_rect(x0, y0, cell_w(), cell_h(), CON_BG) # clear the cell
+ const code = ch:ascii()
+ if code < 32 return :nil
+ if code > 127 return :nil
+ const g = FONT[code - 32]
+ for fy in 0..FH do
+ const bits = g[fy]
+ for fx in 0..FW do
+ if (bits shr fx) band 1 == 1 fill_rect(x0 + fx * con.scale, y0 + fy * con.scale, con.scale, con.scale, FG)
+ end
+ end
+end
+
+fn con_newline() do
+ con.col = 0
+ con.row = con.row + 1
+ if con.row >= con_rows() do
+ fb_scroll(cell_h(), CON_BG)
+ con.row = con_rows() - 1
+ end
+end
+
+# we dont do Shift/Cntrl
+# thats a job for future me lmao
+fn con_putc(ch) do
+ const code = ch:ascii()
+ if code == 10 do con_newline() return :nil end # \n
+ if code == 13 do con.col = 0 return :nil end # \r
+ if code == 8 do # backspace: move left
+ if con.col > 0 con.col = con.col - 1
+ return :nil
+ end
+ if code < 32 return :nil
+ draw_glyph(con.col, con.row, ch)
+ con.col = con.col + 1
+ if con.col >= con_cols() con_newline()
+end
+
+fn puts(s) serial_puts(s)
+
+global flush_console = fn() do
+ let c = serial_next()
+ while c >= 0 do
+ con_putc(string.of_ascii(c))
+ c = serial_next()
+ end
+end
diff --git a/kernel/font.rv b/kernel/font.rv
new file mode 100644
index 0000000..7bd7c6a
--- /dev/null
+++ b/kernel/font.rv
@@ -0,0 +1,102 @@
+# IBM VGA 8x16 font (from Linux lib/fonts/font_8x16.c)
+# ASCII 0x20..0x7f, indexed by (code - 32).
+# one byte per row, 16 rows.
+# bit 0 (LSB) of a byte is the leftmost pixel (VGA data is MSB-first, reversed here).
+global FONT = {
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, # space
+ { 0x00, 0x00, 0x18, 0x3C, 0x3C, 0x3C, 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00 }, # !
+ { 0x00, 0x66, 0x66, 0x66, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, # "
+ { 0x00, 0x00, 0x00, 0x36, 0x36, 0x7F, 0x36, 0x36, 0x36, 0x7F, 0x36, 0x36, 0x00, 0x00, 0x00, 0x00 }, # #
+ { 0x18, 0x18, 0x3E, 0x63, 0x43, 0x03, 0x3E, 0x60, 0x60, 0x61, 0x63, 0x3E, 0x18, 0x18, 0x00, 0x00 }, # $
+ { 0x00, 0x00, 0x00, 0x00, 0x43, 0x63, 0x30, 0x18, 0x0C, 0x06, 0x63, 0x61, 0x00, 0x00, 0x00, 0x00 }, # %
+ { 0x00, 0x00, 0x1C, 0x36, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x33, 0x33, 0x6E, 0x00, 0x00, 0x00, 0x00 }, # &
+ { 0x00, 0x0C, 0x0C, 0x0C, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, # '
+ { 0x00, 0x00, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00 }, # (
+ { 0x00, 0x00, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x18, 0x0C, 0x00, 0x00, 0x00, 0x00 }, # )
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, # *
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, # +
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x0C, 0x00, 0x00, 0x00 }, # ,
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, # -
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00 }, # .
+ { 0x00, 0x00, 0x00, 0x00, 0x40, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00 }, # /
+ { 0x00, 0x00, 0x1C, 0x36, 0x63, 0x63, 0x6B, 0x6B, 0x63, 0x63, 0x36, 0x1C, 0x00, 0x00, 0x00, 0x00 }, # 0
+ { 0x00, 0x00, 0x18, 0x1C, 0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00, 0x00, 0x00, 0x00 }, # 1
+ { 0x00, 0x00, 0x3E, 0x63, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x63, 0x7F, 0x00, 0x00, 0x00, 0x00 }, # 2
+ { 0x00, 0x00, 0x3E, 0x63, 0x60, 0x60, 0x3C, 0x60, 0x60, 0x60, 0x63, 0x3E, 0x00, 0x00, 0x00, 0x00 }, # 3
+ { 0x00, 0x00, 0x30, 0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x30, 0x30, 0x78, 0x00, 0x00, 0x00, 0x00 }, # 4
+ { 0x00, 0x00, 0x7F, 0x03, 0x03, 0x03, 0x3F, 0x60, 0x60, 0x60, 0x63, 0x3E, 0x00, 0x00, 0x00, 0x00 }, # 5
+ { 0x00, 0x00, 0x1C, 0x06, 0x03, 0x03, 0x3F, 0x63, 0x63, 0x63, 0x63, 0x3E, 0x00, 0x00, 0x00, 0x00 }, # 6
+ { 0x00, 0x00, 0x7F, 0x63, 0x60, 0x60, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x0C, 0x00, 0x00, 0x00, 0x00 }, # 7
+ { 0x00, 0x00, 0x3E, 0x63, 0x63, 0x63, 0x3E, 0x63, 0x63, 0x63, 0x63, 0x3E, 0x00, 0x00, 0x00, 0x00 }, # 8
+ { 0x00, 0x00, 0x3E, 0x63, 0x63, 0x63, 0x7E, 0x60, 0x60, 0x60, 0x30, 0x1E, 0x00, 0x00, 0x00, 0x00 }, # 9
+ { 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00 }, # :
+ { 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x0C, 0x00, 0x00, 0x00, 0x00 }, # ;
+ { 0x00, 0x00, 0x00, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00 }, # <
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, # =
+ { 0x00, 0x00, 0x00, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x00, 0x00, 0x00, 0x00 }, # >
+ { 0x00, 0x00, 0x3E, 0x63, 0x63, 0x30, 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00 }, # ?
+ { 0x00, 0x00, 0x00, 0x3E, 0x63, 0x63, 0x7B, 0x7B, 0x7B, 0x3B, 0x03, 0x3E, 0x00, 0x00, 0x00, 0x00 }, # @
+ { 0x00, 0x00, 0x08, 0x1C, 0x36, 0x63, 0x63, 0x7F, 0x63, 0x63, 0x63, 0x63, 0x00, 0x00, 0x00, 0x00 }, # A
+ { 0x00, 0x00, 0x3F, 0x66, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x66, 0x66, 0x3F, 0x00, 0x00, 0x00, 0x00 }, # B
+ { 0x00, 0x00, 0x3C, 0x66, 0x43, 0x03, 0x03, 0x03, 0x03, 0x43, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00 }, # C
+ { 0x00, 0x00, 0x1F, 0x36, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00, 0x00, 0x00, 0x00 }, # D
+ { 0x00, 0x00, 0x7F, 0x66, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x46, 0x66, 0x7F, 0x00, 0x00, 0x00, 0x00 }, # E
+ { 0x00, 0x00, 0x7F, 0x66, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x06, 0x06, 0x0F, 0x00, 0x00, 0x00, 0x00 }, # F
+ { 0x00, 0x00, 0x3C, 0x66, 0x43, 0x03, 0x03, 0x7B, 0x63, 0x63, 0x66, 0x5C, 0x00, 0x00, 0x00, 0x00 }, # G
+ { 0x00, 0x00, 0x63, 0x63, 0x63, 0x63, 0x7F, 0x63, 0x63, 0x63, 0x63, 0x63, 0x00, 0x00, 0x00, 0x00 }, # H
+ { 0x00, 0x00, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00 }, # I
+ { 0x00, 0x00, 0x78, 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x33, 0x33, 0x1E, 0x00, 0x00, 0x00, 0x00 }, # J
+ { 0x00, 0x00, 0x67, 0x66, 0x66, 0x36, 0x1E, 0x1E, 0x36, 0x66, 0x66, 0x67, 0x00, 0x00, 0x00, 0x00 }, # K
+ { 0x00, 0x00, 0x0F, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00, 0x00, 0x00, 0x00 }, # L
+ { 0x00, 0x00, 0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x63, 0x63, 0x63, 0x00, 0x00, 0x00, 0x00 }, # M
+ { 0x00, 0x00, 0x63, 0x67, 0x6F, 0x7F, 0x7B, 0x73, 0x63, 0x63, 0x63, 0x63, 0x00, 0x00, 0x00, 0x00 }, # N
+ { 0x00, 0x00, 0x3E, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x3E, 0x00, 0x00, 0x00, 0x00 }, # O
+ { 0x00, 0x00, 0x3F, 0x66, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x06, 0x06, 0x0F, 0x00, 0x00, 0x00, 0x00 }, # P
+ { 0x00, 0x00, 0x3E, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x6B, 0x7B, 0x3E, 0x30, 0x70, 0x00, 0x00 }, # Q
+ { 0x00, 0x00, 0x3F, 0x66, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x66, 0x66, 0x67, 0x00, 0x00, 0x00, 0x00 }, # R
+ { 0x00, 0x00, 0x3E, 0x63, 0x63, 0x06, 0x1C, 0x30, 0x60, 0x63, 0x63, 0x3E, 0x00, 0x00, 0x00, 0x00 }, # S
+ { 0x00, 0x00, 0x7E, 0x7E, 0x5A, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00 }, # T
+ { 0x00, 0x00, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x3E, 0x00, 0x00, 0x00, 0x00 }, # U
+ { 0x00, 0x00, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x08, 0x00, 0x00, 0x00, 0x00 }, # V
+ { 0x00, 0x00, 0x63, 0x63, 0x63, 0x63, 0x6B, 0x6B, 0x6B, 0x7F, 0x77, 0x36, 0x00, 0x00, 0x00, 0x00 }, # W
+ { 0x00, 0x00, 0x63, 0x63, 0x36, 0x3E, 0x1C, 0x1C, 0x3E, 0x36, 0x63, 0x63, 0x00, 0x00, 0x00, 0x00 }, # X
+ { 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00 }, # Y
+ { 0x00, 0x00, 0x7F, 0x63, 0x61, 0x30, 0x18, 0x0C, 0x06, 0x43, 0x63, 0x7F, 0x00, 0x00, 0x00, 0x00 }, # Z
+ { 0x00, 0x00, 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3C, 0x00, 0x00, 0x00, 0x00 }, # [
+ { 0x00, 0x00, 0x00, 0x01, 0x03, 0x07, 0x0E, 0x1C, 0x38, 0x70, 0x60, 0x40, 0x00, 0x00, 0x00, 0x00 }, # \
+ { 0x00, 0x00, 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x00, 0x00, 0x00, 0x00 }, # ]
+ { 0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, # ^
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00 }, # _
+ { 0x00, 0x0C, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, # `
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x33, 0x33, 0x6E, 0x00, 0x00, 0x00, 0x00 }, # a
+ { 0x00, 0x00, 0x07, 0x06, 0x06, 0x1E, 0x36, 0x66, 0x66, 0x66, 0x66, 0x3E, 0x00, 0x00, 0x00, 0x00 }, # b
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x63, 0x03, 0x03, 0x03, 0x63, 0x3E, 0x00, 0x00, 0x00, 0x00 }, # c
+ { 0x00, 0x00, 0x38, 0x30, 0x30, 0x3C, 0x36, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00, 0x00, 0x00, 0x00 }, # d
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x63, 0x7F, 0x03, 0x03, 0x63, 0x3E, 0x00, 0x00, 0x00, 0x00 }, # e
+ { 0x00, 0x00, 0x38, 0x6C, 0x4C, 0x0C, 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00, 0x00, 0x00, 0x00 }, # f
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x6E, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x33, 0x1E, 0x00 }, # g
+ { 0x00, 0x00, 0x07, 0x06, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x66, 0x66, 0x67, 0x00, 0x00, 0x00, 0x00 }, # h
+ { 0x00, 0x00, 0x18, 0x18, 0x00, 0x1C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00 }, # i
+ { 0x00, 0x00, 0x60, 0x60, 0x00, 0x70, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x66, 0x66, 0x3C, 0x00 }, # j
+ { 0x00, 0x00, 0x07, 0x06, 0x06, 0x66, 0x36, 0x1E, 0x1E, 0x36, 0x66, 0x67, 0x00, 0x00, 0x00, 0x00 }, # k
+ { 0x00, 0x00, 0x1C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00 }, # l
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x7F, 0x6B, 0x6B, 0x6B, 0x6B, 0x63, 0x00, 0x00, 0x00, 0x00 }, # m
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x3B, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00 }, # n
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x63, 0x63, 0x63, 0x63, 0x63, 0x3E, 0x00, 0x00, 0x00, 0x00 }, # o
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x3B, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00 }, # p
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x6E, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x30, 0x78, 0x00 }, # q
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x06, 0x06, 0x0F, 0x00, 0x00, 0x00, 0x00 }, # r
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x63, 0x06, 0x1C, 0x30, 0x63, 0x3E, 0x00, 0x00, 0x00, 0x00 }, # s
+ { 0x00, 0x00, 0x08, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x0C, 0x0C, 0x6C, 0x38, 0x00, 0x00, 0x00, 0x00 }, # t
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00, 0x00, 0x00, 0x00 }, # u
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x63, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00, 0x00, 0x00, 0x00 }, # v
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x63, 0x6B, 0x6B, 0x6B, 0x7F, 0x36, 0x00, 0x00, 0x00, 0x00 }, # w
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x36, 0x1C, 0x1C, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00 }, # x
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x7E, 0x60, 0x30, 0x1F, 0x00 }, # y
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x33, 0x18, 0x0C, 0x06, 0x63, 0x7F, 0x00, 0x00, 0x00, 0x00 }, # z
+ { 0x00, 0x00, 0x70, 0x18, 0x18, 0x18, 0x0E, 0x18, 0x18, 0x18, 0x18, 0x70, 0x00, 0x00, 0x00, 0x00 }, # {
+ { 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00 }, # |
+ { 0x00, 0x00, 0x0E, 0x18, 0x18, 0x18, 0x70, 0x18, 0x18, 0x18, 0x18, 0x0E, 0x00, 0x00, 0x00, 0x00 }, # }
+ { 0x00, 0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, # ~
+ { 0x00, 0x00, 0x00, 0x00, 0x08, 0x1C, 0x36, 0x63, 0x63, 0x63, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00 }, # 
+}
diff --git a/kernel/input.rv b/kernel/input.rv
new file mode 100644
index 0000000..33cf65d
--- /dev/null
+++ b/kernel/input.rv
@@ -0,0 +1,47 @@
+# keyboard scancode table
+# ts is NOT a good way to write a driver LMAO
+const KBD = " 1234567890-= qwertyuiop[] asdfghjkl;'` \\zxcvbnm,./ "
+# same layout, shifted. index 42 (left shift) / 54 (right shift) stay blank.
+const KBD_SHIFT = " !@#$%^&*()_+ QWERTYUIOP{} ASDFGHJKL:\"~ |ZXCVBNM<>? "
+
+# 8259 PIC
+fn pic_remap() do
+ outb(0x20, 0x11) outb(0xa0, 0x11)
+ outb(0x21, 0x20) outb(0xa1, 0x28)
+ outb(0x21, 0x04) outb(0xa1, 0x02)
+ outb(0x21, 0x01) outb(0xa1, 0x01)
+ outb(0x21, 0xff) outb(0xa1, 0xff)
+end
+fn pic_unmask(line) outb(0x21, inb(0x21) band (255 bxor (1 shl line)))
+
+# she stroke my keys till i handle
+# no fr this is just a key stroke handler
+global sh = { line = "", shift = :false }
+global on_key = fn(code) do
+ # shift: 0x2a/0x36 press,
+ # 0xaa/0xb6 release
+ # index a blank cell and type a stray space.
+ if code == 0x2a do sh.shift = :true return :nil end
+ if code == 0x36 do sh.shift = :true return :nil end
+ if code == 0xaa do sh.shift = :false return :nil end
+ if code == 0xb6 do sh.shift = :false return :nil end
+ if code >= 128 return :nil # other key releases
+ if code == 28 do # Enter
+ puts("\n")
+ run(sh.line)
+ sh.line = ""
+ return :nil
+ end
+ if code == 14 do # Backspace
+ if sh.line:len() > 0 do
+ sh.line = sh.line:sub(0, sh.line:len() - 1)
+ puts("\x08 \x08")
+ end
+ return :nil
+ end
+ if code < 2 return :nil
+ if code >= KBD:len() return :nil
+ const ch = if sh.shift KBD_SHIFT[code] else KBD[code]
+ sh.line = sh.line ~ ch
+ puts(ch)
+end
diff --git a/kernel/main.rv b/kernel/main.rv
index 8fe3b81..d3f1b23 100644
--- a/kernel/main.rv
+++ b/kernel/main.rv
@@ -1,240 +1,6 @@
-# font8x8_basic (printable ASCII 0x20..0x7f), one row of 8 glyphs per line
-# each glyph is 8 bytes; bit 0 (LSB) of a byte is the leftmost pixel
-global FONT = {
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
- { 0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00 },
- { 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
- { 0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00 },
- { 0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00 },
- { 0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00 },
- { 0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00 },
- { 0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 },
- { 0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00 },
- { 0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00 },
- { 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00 },
- { 0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00 },
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x06 },
- { 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00 },
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00 },
- { 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00 },
- { 0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00 },
- { 0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00 },
- { 0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00 },
- { 0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00 },
- { 0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00 },
- { 0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00 },
- { 0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00 },
- { 0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00 },
- { 0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00 },
- { 0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00 },
- { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00 },
- { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x06 },
- { 0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00 },
- { 0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00 },
- { 0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00 },
- { 0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00 },
- { 0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00 },
- { 0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00 },
- { 0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00 },
- { 0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00 },
- { 0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00 },
- { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00 },
- { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00 },
- { 0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00 },
- { 0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00 },
- { 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00 },
- { 0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00 },
- { 0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00 },
- { 0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00 },
- { 0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00 },
- { 0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00 },
- { 0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00 },
- { 0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00 },
- { 0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00 },
- { 0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00 },
- { 0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00 },
- { 0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00 },
- { 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00 },
- { 0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00 },
- { 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00 },
- { 0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00 },
- { 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00 },
- { 0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00 },
- { 0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x00 },
- { 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00 },
- { 0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x00 },
- { 0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00 },
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF },
- { 0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00 },
- { 0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00 },
- { 0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00 },
- { 0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00 },
- { 0x38, 0x30, 0x30, 0x3E, 0x33, 0x33, 0x6E, 0x00 },
- { 0x00, 0x00, 0x1E, 0x33, 0x3F, 0x03, 0x1E, 0x00 },
- { 0x1C, 0x36, 0x06, 0x0F, 0x06, 0x06, 0x0F, 0x00 },
- { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F },
- { 0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00 },
- { 0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00 },
- { 0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E },
- { 0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00 },
- { 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00 },
- { 0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00 },
- { 0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00 },
- { 0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00 },
- { 0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F },
- { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78 },
- { 0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00 },
- { 0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00 },
- { 0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00 },
- { 0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00 },
- { 0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00 },
- { 0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00 },
- { 0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00 },
- { 0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F },
- { 0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00 },
- { 0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00 },
- { 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00 },
- { 0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00 },
- { 0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
-}
-
-const SCALE = 2
-const CW = 8 * SCALE
-const CH = 8 * SCALE
-const FG = 0xd0d0d0
-const CON_BG = 0x101018
-global con = { col = 0, row = 0 }
-
-fn con_cols() fb_width() / CW
-fn con_rows() fb_height() / CH
-
-fn draw_glyph(col, row, ch) do
- const x0 = col * CW
- const y0 = row * CH
- fill_rect(x0, y0, CW, CH, CON_BG) # clear the cell
- const code = ch:ascii()
- if code < 32 return :nil
- if code > 127 return :nil
- const g = FONT[code - 32]
- for fy in 0..8 do
- const bits = g[fy]
- for fx in 0..8 do
- if (bits shr fx) band 1 == 1 fill_rect(x0 + fx * SCALE, y0 + fy * SCALE, SCALE, SCALE, FG)
- end
- end
-end
-
-fn con_newline() do
- con.col = 0
- con.row = con.row + 1
- if con.row >= con_rows() do
- fb_scroll(CH, CON_BG)
- con.row = con_rows() - 1
- end
-end
-
-# we dont do Shift/Cntrl
-# thats a job for future me lmao
-fn con_putc(ch) do
- const code = ch:ascii()
- if code == 10 do con_newline() return :nil end # \n
- if code == 13 do con.col = 0 return :nil end # \r
- if code == 8 do # backspace: move left
- if con.col > 0 con.col = con.col - 1
- return :nil
- end
- if code < 32 return :nil
- draw_glyph(con.col, con.row, ch)
- con.col = con.col + 1
- if con.col >= con_cols() con_newline()
-end
-
-# render a string to the screen and mirror to serial
-fn puts(s) do
- serial_puts(s)
- for i in 0..s:len() do con_putc(s[i]) end
-end
-
-# keyboard scancode table
-# ts is NOT a good way to write a driver LMAO
-const KBD = " 1234567890-= qwertyuiop[] asdfghjkl;'` \\zxcvbnm,./ "
-
-# 8259 PIC
-fn pic_remap() do
- outb(0x20, 0x11) outb(0xa0, 0x11)
- outb(0x21, 0x20) outb(0xa1, 0x28)
- outb(0x21, 0x04) outb(0xa1, 0x02)
- outb(0x21, 0x01) outb(0xa1, 0x01)
- outb(0x21, 0xff) outb(0xa1, 0xff)
-end
-fn pic_unmask(line) outb(0x21, inb(0x21) band (255 bxor (1 shl line)))
-
-# this is just a simple prompt to test that shit is working
-fn cmd_help() puts("commands: help, clear, bars, echo <text>, time\n")
-
-fn cmd_clear() do
- fill_rect(0, 0, fb_width(), fb_height(), CON_BG)
- con.col = 0
- con.row = 0
-end
-
-fn cmd_bars() do
- const bars = { 0xff5555, 0xffaa00, 0xffff55, 0x55ff55, 0x55ffff, 0x5599ff, 0xaa55ff, 0xffffff }
- const bw = fb_width() / 8
- for i in 0..8 do fill_rect(i * bw, 0, bw, 40, bars[i]) end
- puts("painted 8 bars from revo\n")
-end
-
-fn bcd(b) (b shr 4) * 10 + (b band 15)
-fn rtc(reg) do
- outb(0x70, reg)
- inb(0x71)
-end
-fn cmd_time() puts("utc " ~ bcd(rtc(4)) ~ ":" ~ bcd(rtc(2)) ~ ":" ~ bcd(rtc(0)) ~ "\n")
-
-fn run(line) do
- const words = line:split(" ")
- const cmd = words[0]
- if cmd == "help" do cmd_help() end
- else if cmd == "clear" do cmd_clear() end
- else if cmd == "bars" do cmd_bars() end
- else if cmd == "time" do cmd_time() end
- else if cmd == "echo" do
- if line:len() > 5 puts(line:sub(5, line:len() - 5) ~ "\n") else puts("\n")
- end
- else if line:len() == 0 do :nil end
- else do puts("unknown command: " ~ cmd ~ "\n") end
- puts("> ")
-end
-
-# she stroke my keys till i handle
-# no fr this is just a key stroke handler
-global sh = { line = "" }
-global on_key = fn(code) do
- if code >= 128 return :nil # key release
- if code == 28 do # Enter
- puts("\n")
- run(sh.line)
- sh.line = ""
- return :nil
- end
- if code == 14 do # Backspace
- if sh.line:len() > 0 do
- sh.line = sh.line:sub(0, sh.line:len() - 1)
- puts("\x08 \x08")
- end
- return :nil
- end
- if code < 2 return :nil
- if code >= KBD:len() return :nil
- const ch = KBD[code]
- sh.line = sh.line ~ ch
- puts(ch)
-end
-
# kmain
pic_remap()
pic_unmask(1)
cmd_clear()
puts("revel shell, live in revo. type `help`\n> ")
+flush_console()
diff --git a/kernel/proc.rv b/kernel/proc.rv
new file mode 100644
index 0000000..af6e8f7
--- /dev/null
+++ b/kernel/proc.rv
@@ -0,0 +1,40 @@
+# parse the embedded ELF (physical address from elf_phys()) and run it in ring 3
+
+const PT_LOAD = 1
+const PAGE_FLAGS = 6 # WRITE | USER
+
+fn cmd_run() do
+ const base = elf_phys()
+ if mem_read(base, 4) != 0x464c457f do
+ puts("run: not an ELF\n")
+ return :nil
+ end
+ const entry = mem_read(base + 24, 8)
+ const phoff = mem_read(base + 32, 8)
+ const phnum = mem_read(base + 56, 2)
+ const phentsize = mem_read(base + 54, 2)
+ const as = as_create()
+ for i in 0..phnum do
+ const ph = base + phoff + i * phentsize
+ if mem_read(ph, 4) == PT_LOAD do
+ const offset = mem_read(ph + 8, 8)
+ const vaddr = mem_read(ph + 16, 8)
+ const filesz = mem_read(ph + 32, 8)
+ const memsz = mem_read(ph + 40, 8)
+ let off = 0
+ while off < memsz do
+ const frame = frame_alloc()
+ mem_zero(frame, 4096)
+ if off < filesz do
+ const left = filesz - off
+ mem_copy(frame, base + offset + off, if left > 4096 4096 else left)
+ end
+ as_map(as, vaddr + off, frame, PAGE_FLAGS)
+ off = off + 4096
+ end
+ end
+ end
+ const stack_top = 0x7fff0000
+ as_map(as, stack_top - 4096, frame_alloc(), PAGE_FLAGS)
+ proc_run(as, entry, stack_top)
+end
diff --git a/kernel/shell.rv b/kernel/shell.rv
new file mode 100644
index 0000000..562f3c4
--- /dev/null
+++ b/kernel/shell.rv
@@ -0,0 +1,55 @@
+# this is just a simple prompt to test that shit is working
+fn cmd_help() puts("commands: help, clear, bars, echo <text>, time, run, zoom in|out\n")
+
+fn zoom_by(d) do
+ let s = con.scale + d
+ if s < 1 do s = 1 end
+ if s > 6 do s = 6 end
+ con.scale = s
+ cmd_clear()
+ puts("zoom " ~ s ~ "x\n")
+end
+
+fn cmd_zoom(words) do
+ const dir = if words:len() > 1 words[1] else ""
+ if dir == "in" do zoom_by(1) end
+ else if dir == "out" do zoom_by(-1) end
+ else do puts("usage: zoom in | zoom out\n") end
+end
+
+fn cmd_clear() do
+ fill_rect(0, 0, fb_width(), fb_height(), CON_BG)
+ con.col = 0
+ con.row = 0
+end
+
+fn cmd_bars() do
+ const bars = { 0xff5555, 0xffaa00, 0xffff55, 0x55ff55, 0x55ffff, 0x5599ff, 0xaa55ff, 0xffffff }
+ const bw = fb_width() / 8
+ for i in 0..8 do fill_rect(i * bw, 0, bw, 40, bars[i]) end
+ puts("painted 8 bars from revo\n")
+end
+
+fn bcd(b) (b shr 4) * 10 + (b band 15)
+fn rtc(reg) do
+ outb(0x70, reg)
+ inb(0x71)
+end
+fn cmd_time() puts("utc " ~ bcd(rtc(4)) ~ ":" ~ bcd(rtc(2)) ~ ":" ~ bcd(rtc(0)) ~ "\n")
+
+fn run(line) do
+ const words = line:split(" ")
+ const cmd = words[0]
+ if cmd == "help" do cmd_help() end
+ else if cmd == "clear" do cmd_clear() end
+ else if cmd == "bars" do cmd_bars() end
+ else if cmd == "time" do cmd_time() end
+ else if cmd == "run" do cmd_run() end
+ else if cmd == "zoom" do cmd_zoom(words) end
+ else if cmd == "echo" do
+ if line:len() > 5 puts(line:sub(5, line:len() - 5) ~ "\n") else puts("\n")
+ end
+ else if line:len() == 0 do :nil end
+ else do puts("unknown command: " ~ cmd ~ "\n") end
+ puts("> ")
+end
diff --git a/kernel/vmm.rv b/kernel/vmm.rv
new file mode 100644
index 0000000..d6673b3
--- /dev/null
+++ b/kernel/vmm.rv
@@ -0,0 +1,24 @@
+# build the 4-level page tables for one user page
+# everything here only writes small entries we construct, so it's f64-safe
+const PTE_ADDR = 0x000FFFFFFFFFF000
+
+fn pte_idx(virt, level) (virt shr (12 + 9 * level)) band 0x1ff
+
+# next-level table phys under entry `i` of table `tbl`, creating it if absent
+fn next_table(tbl, i) do
+ const slot = tbl + i * 8
+ const entry = mem_read(slot, 8)
+ if (entry band 1) != 0 return entry band PTE_ADDR
+ const frame = frame_alloc()
+ mem_zero(frame, 4096)
+ mem_write(slot, frame bor 7, 8) # present | write | user
+ frame
+end
+
+fn as_map(pml4, virt, phys, flags) do
+ const pdpt = next_table(pml4, pte_idx(virt, 3))
+ const pd = next_table(pdpt, pte_idx(virt, 2))
+ const pt = next_table(pd, pte_idx(virt, 1))
+ mem_write(pt + pte_idx(virt, 0) * 8, (phys band PTE_ADDR) bor flags bor 1, 8)
+ invlpg(virt)
+end
diff --git a/user/link.ld b/user/link.ld
new file mode 100644
index 0000000..9dc8402
--- /dev/null
+++ b/user/link.ld
@@ -0,0 +1,8 @@
+ENTRY(_start)
+SECTIONS {
+ . = 0x400000;
+ .text : ALIGN(0x1000) { *(.text*) }
+ .rodata : ALIGN(0x1000) { *(.rodata*) }
+ .data : ALIGN(0x1000) { *(.data*) }
+ .bss : ALIGN(0x1000) { *(.bss*) *(COMMON) }
+}
diff --git a/user/program.c b/user/program.c
new file mode 100644
index 0000000..8e26128
--- /dev/null
+++ b/user/program.c
@@ -0,0 +1,22 @@
+static long sys_write(const char* buffer, unsigned long buffer_size)
+{
+ long ret;
+ __asm__ volatile("int $0x80" : "=a"(ret) : "a"(1L), "D"(buffer), "S"(buffer_size) : "memory");
+ return ret;
+}
+static void sys_exit(long code)
+{
+ __asm__ volatile("int $0x80" : : "a"(0L), "D"(code) : "memory");
+ for (;;);
+}
+
+void _start(void)
+{
+ const char* message = "wassup shawty\n";
+ unsigned long message_length = 0;
+ while (message[message_length])
+ message_length++;
+
+ sys_write(message, message_length);
+ sys_exit(0);
+}