hachem.wtf
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile4
-rw-r--r--boot/ata.zig1
-rw-r--r--boot/boot.zig37
-rw-r--r--boot/elf.zig10
-rw-r--r--boot/sched.zig23
-rw-r--r--boot/user.zig69
-rw-r--r--bridge/bridge.zig149
-rw-r--r--build.zig29
-rw-r--r--kernel/fs.rv83
-rw-r--r--kernel/main.rv9
-rw-r--r--kernel/proc.rv22
-rw-r--r--kernel/seed.rv5
-rw-r--r--kernel/shell.rv56
-rw-r--r--user/calc.c78
-rw-r--r--user/hexview.c91
-rw-r--r--user/primes.c42
-rw-r--r--user/program.c63
-rw-r--r--user/user.h97
18 files changed, 640 insertions, 228 deletions
diff --git a/Makefile b/Makefile
index ea96df8..57a6554 100644
--- a/Makefile
+++ b/Makefile
@@ -44,16 +44,12 @@ iso: kernel limine/limine
iso_root -o $(ISO)
./limine/limine bios-install $(ISO)
-# persistent scratch disk for the filesystem. survives across boots and across
-# `make clean`, blow it away by hand or with `make wipedisk` if you want a fresh one
$(DISK):
qemu-img create -f raw $(DISK) $(DISK_MB)M
wipedisk:
rm -f $(DISK)
-# -M pc (i440fx) not q35, we want the piix3 legacy ide controller at the classic
-# 0x1F0 ports so the ata pio driver can find the disk. q35 only gives you ahci
run: iso $(DISK)
$(QEMU) -M pc -m 256M -cdrom $(ISO) -boot d \
-drive file=$(DISK),format=raw,if=ide \
diff --git a/boot/ata.zig b/boot/ata.zig
index ec63c1c..4c5afe9 100644
--- a/boot/ata.zig
+++ b/boot/ata.zig
@@ -125,7 +125,6 @@ pub fn init() void {
serial.write("\r\n");
}
-// read one 512 byte sector at lba into buf
pub fn read(lba: u28, buf: *[SECTOR]u8) bool {
if (!g_present) return false;
if (!waitNotBusy()) return false;
diff --git a/boot/boot.zig b/boot/boot.zig
index 28fb8c1..5a3c429 100644
--- a/boot/boot.zig
+++ b/boot/boot.zig
@@ -101,10 +101,6 @@ fn kProcCount() u64 {
return sched.procCount();
}
-// block device, revo builds the filesystem on top of these
-fn kDiskPresent() u64 {
- return if (ata.present()) 1 else 0;
-}
fn kDiskSectors() u64 {
return ata.sectorCount();
}
@@ -117,6 +113,29 @@ fn kDiskWrite(lba: u64, phys: u64) u64 {
return if (ata.write(@truncate(lba), buf)) 1 else 0;
}
+// the embedded programs, copied to frames at boot so revo can load them by name
+const Prog = struct { name: []const u8, phys: u64, size: u64 };
+var g_progs: [elf.programs.len]Prog = undefined;
+
+fn loadPrograms() void {
+ for (elf.programs, 0..) |p, i| {
+ const pages = (p.bytes.len + 4095) / 4096;
+ const phys = pmm.allocContig(pages) orelse @panic("no room for an embedded program");
+ @memcpy(@as([*]u8, @ptrFromInt(pmm.physToVirt(phys)))[0..p.bytes.len], p.bytes);
+ g_progs[i] = .{ .name = p.name, .phys = phys, .size = p.bytes.len };
+ }
+}
+
+fn kProgCount() u64 {
+ return g_progs.len;
+}
+fn kProgPhys(i: u64) u64 {
+ return g_progs[i].phys;
+}
+fn kProgName(i: u64) []const u8 {
+ return g_progs[i].name;
+}
+
// entry(_start)
export fn _start() callconv(.c) noreturn {
serial.init();
@@ -143,9 +162,7 @@ export fn _start() callconv(.c) noreturn {
vmmSelfTest();
ata.init();
- 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);
+ loadPrograms();
// the event loop below is scheduler task 0 (the kernel), running in the
// current cr3. user tasks spawned by `run` get time fucked against it
sched.init(vmm.activePml4());
@@ -166,9 +183,9 @@ export fn _start() callconv(.c) noreturn {
.mem_total = &kMemTotal,
.uptime = &kUptime,
.proc_count = &kProcCount,
- .elf_phys = elf_phys,
- .elf_size = elf.hello_elf.len,
- .disk_present = &kDiskPresent,
+ .prog_count = &kProgCount,
+ .prog_phys = &kProgPhys,
+ .prog_name = &kProgName,
.disk_sectors = &kDiskSectors,
.disk_read = &kDiskRead,
.disk_write = &kDiskWrite,
diff --git a/boot/elf.zig b/boot/elf.zig
index a9199db..906e380 100644
--- a/boot/elf.zig
+++ b/boot/elf.zig
@@ -1,2 +1,8 @@
-// load c program
-pub const hello_elf = @embedFile("user_program");
+// the embedded ring 3 programs, boot copies each to frames at startup
+pub const Program = struct { name: []const u8, bytes: []const u8 };
+
+pub const programs = [_]Program{
+ .{ .name = "hexview", .bytes = @embedFile("user_hexview") },
+ .{ .name = "calc", .bytes = @embedFile("user_calc") },
+ .{ .name = "primes", .bytes = @embedFile("user_primes") },
+};
diff --git a/boot/sched.zig b/boot/sched.zig
index 95b94e0..60a8fa0 100644
--- a/boot/sched.zig
+++ b/boot/sched.zig
@@ -1,22 +1,14 @@
-// tiny preemptive round robin scheduler
-//
-// every schedulable thing is a task holding a saved register frame + its cr3
-// task 0 is the kernel itself (the event loop / shell), always ready. the pit
-// tick (idt.zig -> tick) is where we preempt: save the interrupted tasks frame,
-// pick the next ready task, copy its frame back over the one on the stack, and
-// the irq return path iretqs straight into it. iretq handles ring 0 <-> ring 3
-// the same way (long mode always restores ss:rsp), so one swap covers leaving
-// either the kernel or a process
-//
-// each user task gets its own kernel stack (for its ring3->ring0 syscall/irq
-// entries) and we point tss.rsp0 at it on switch. thats what keeps preempting a
-// task mid syscall safe (e.g. read()s sti+hlt). a shared rsp0 would get
-// clobbered the next time any task trapped into the kernel
+// tiny preemptive round robin scheduler. a task is a saved frame + cr3 + kernel
+// stack, task 0 is the kernel event loop. the pit tick swaps the interrupted
+// frame for the next ready tasks and iretqs in, which covers ring 0 and ring 3
+// alike. each task has its own kernel stack so preempting one mid syscall (e.g. a
+// read() parked on sti/hlt) doesnt clobber anothers rsp0
const std = @import("std");
const Frame = @import("regs.zig").Frame;
const vmm = @import("vmm.zig");
const gdt = @import("gdt.zig");
+const bridge = @import("bridge");
const MAX_TASKS = 8;
const KSTACK_SIZE = 16 * 1024;
@@ -156,6 +148,9 @@ pub fn deliverInput(byte: u8) bool {
// the preemption point, called from the pit irq with the interrupted frame
pub fn tick(frame: *Frame) void {
+ // dont preempt task 0 mid vm call, the shared fiber cant be re-entered
+ if (bridge.vmBusy()) return;
+
if (tasks[current].state == .dead) {
tasks[current].state = .free; // reclaim, dont bother saving its context
} else if (tasks[current].state == .blocked) {
diff --git a/boot/user.zig b/boot/user.zig
index 1d0839f..5ba6604 100644
--- a/boot/user.zig
+++ b/boot/user.zig
@@ -1,15 +1,23 @@
-// the syscall boundary (int 0x80). ring 3 entry itself now lives in the
-// scheduler (boot/sched.zig), which iretqs into tasks, this file is just the
-// syscall gate + handlers
-//
+// int 0x80 syscall gate + handlers. ring 3 entry itself lives in sched.zig
// see: https://wiki.osdev.org/System_Calls
const serial = @import("serial.zig");
const idt = @import("idt.zig");
const sched = @import("sched.zig");
const regs = @import("regs.zig");
+const bridge = @import("bridge");
const Frame = regs.Frame;
+// scratch for a filename copied out of the caller. one static buffer is fine,
+// syscalls run one at a time (if=0)
+var name_buf: [64]u8 = undefined;
+fn userName(ptr: u64) []const u8 {
+ const p: [*]const u8 = @ptrFromInt(ptr);
+ var i: usize = 0;
+ while (i < name_buf.len - 1 and p[i] != 0) : (i += 1) name_buf[i] = p[i];
+ return name_buf[0..i];
+}
+
// what the syscall stub leaves on the stack: the gp regs it pushed, then the
// frame the cpu pushed on int 0x80. rax holds the syscall number in, and the
// handler writes the return value back into it (restored by the stubs pop)
@@ -58,42 +66,59 @@ export fn syscallStub() callconv(.naked) void {
export fn syscallHandler(frame: *SyscallFrame) callconv(.c) void {
switch (frame.rax) {
0 => { // exit(code): code in rdi
- sched.exitCurrent(); // mark dead, the next tick reclaims + switches away
+ sched.exitCurrent();
asm volatile ("sti");
- while (true) asm volatile ("hlt"); // never returns to this task
+ while (true) asm volatile ("hlt");
},
1 => { // write(ptr, len): rdi = user pointer, rsi = length
const len = frame.rsi;
- // the processs address space is active during the syscall, so the
- // kernel can read the user pointer directly. cap the length so a
- // bogus arg cant 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 -> kernel tasks on_tick renders it
+ serial.write(bytes[0..len]);
}
frame.rax = len; // syscall return value
},
2 => { // read(): return a buffered byte, else block for one
- // become the foreground reader, if a byte is already queued, take it
if (sched.readReady()) |byte| {
frame.rax = byte;
return;
}
- // otherwise save a context that resumes right after this int 0x80
- // (with rax = the delivered byte), mark the task blocked, and yield
- // the kernel task decodes keys and calls sched.deliverinput to wake us
const resume_frame: Frame = .{
- .r15 = frame.r15, .r14 = frame.r14, .r13 = frame.r13, .r12 = frame.r12,
- .r11 = frame.r11, .r10 = frame.r10, .r9 = frame.r9, .r8 = frame.r8,
- .rbp = frame.rbp, .rdi = frame.rdi, .rsi = frame.rsi, .rdx = frame.rdx,
- .rcx = frame.rcx, .rbx = frame.rbx, .rax = frame.rax,
- .vector = 0, .error_code = 0,
- .rip = frame.rip, .cs = frame.cs, .rflags = frame.rflags,
- .rsp = frame.rsp, .ss = frame.ss,
+ .r15 = frame.r15,
+ .r14 = frame.r14,
+ .r13 = frame.r13,
+ .r12 = frame.r12,
+ .r11 = frame.r11,
+ .r10 = frame.r10,
+ .r9 = frame.r9,
+ .r8 = frame.r8,
+ .rbp = frame.rbp,
+ .rdi = frame.rdi,
+ .rsi = frame.rsi,
+ .rdx = frame.rdx,
+ .rcx = frame.rcx,
+ .rbx = frame.rbx,
+ .rax = frame.rax,
+ .vector = 0,
+ .error_code = 0,
+ .rip = frame.rip,
+ .cs = frame.cs,
+ .rflags = frame.rflags,
+ .rsp = frame.rsp,
+ .ss = frame.ss,
};
sched.blockCurrentOnRead(resume_frame);
asm volatile ("sti");
- while (true) asm volatile ("hlt"); // yield, resumed via rf when input arrives
+ while (true) asm volatile ("hlt");
+ },
+ 4 => { // fs_size(name_ptr): returns size (like obv), or -1
+ frame.rax = @bitCast(bridge.fsSize(userName(frame.rdi)));
+ },
+ 5 => { // fs_read(name_ptr, offset, buf_ptr, len): returns bytes read
+ const len = @min(frame.rcx, 4096);
+ const name = userName(frame.rdi);
+ const dst: [*]u8 = @ptrFromInt(frame.rdx);
+ frame.rax = bridge.fsReadInto(name, frame.rsi, dst[0..len]);
},
else => serial.write("[syscall] unknown\r\n"),
}
diff --git a/bridge/bridge.zig b/bridge/bridge.zig
index c4918b0..2fdd1e2 100644
--- a/bridge/bridge.zig
+++ b/bridge/bridge.zig
@@ -282,6 +282,7 @@ pub const KernelOps = struct {
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,
@@ -289,24 +290,29 @@ pub const KernelOps = struct {
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,
- // block device, the filesystem in revo drives these (512 byte sectors)
- disk_present: *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;
+// every host fn needs the kernel ops, boot wires them before the vm ever runs
+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 ops = g_kops orelse return HostResult.other("no kernel ops");
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 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)).*,
@@ -319,9 +325,8 @@ 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(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 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());
@@ -329,8 +334,7 @@ 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(argInt(u64, args, 0) orelse return HostResult.other("mem_zero: bad addr"));
+ 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());
@@ -338,11 +342,10 @@ 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 = 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);
+ 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),
@@ -364,114 +367,96 @@ fn hostInvlpg(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
}
fn hostFrameAlloc(_: []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.alloc_frame()));
+ return HostResult.data(Data.new.num(kops().alloc_frame()));
}
fn hostAsCreate(_: []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.create_addrspace()));
+ return HostResult.data(Data.new.num(kops().create_addrspace()));
}
fn hostProcRun(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- const ops = g_kops orelse return HostResult.other("no kernel ops");
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
+ 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 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));
+ 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 {
- 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));
+ 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 {
- const ops = g_kops orelse return HostResult.other("no kernel ops");
- return HostResult.data(Data.new.num(ops.serial_next()));
+ 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 hostElfPhys(_: []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.elf_phys));
+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 hostElfSize(_: []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.elf_size));
+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 {
- const ops = g_kops orelse return HostResult.other("no kernel ops");
- return HostResult.data(Data.new.num(ops.memmap_count()));
+ return HostResult.data(Data.new.num(kops().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)));
+ return HostResult.data(Data.new.num(kops().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)));
+ return HostResult.data(Data.new.num(kops().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)));
+ return HostResult.data(Data.new.num(kops().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()));
+ return HostResult.data(Data.new.num(kops().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()));
+ return HostResult.data(Data.new.num(kops().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()));
+ return HostResult.data(Data.new.num(kops().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 hostDiskPresent(_: []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.disk_present()));
+ return HostResult.data(Data.new.num(kops().proc_count()));
}
fn hostDiskSectors(_: []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.disk_sectors()));
+ return HostResult.data(Data.new.num(kops().disk_sectors()));
}
fn hostDiskRead(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- const ops = g_kops orelse return HostResult.other("no kernel ops");
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(ops.disk_read(lba, phys)));
+ return HostResult.data(Data.new.num(kops().disk_read(lba, phys)));
}
fn hostDiskWrite(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
- const ops = g_kops orelse return HostResult.other("no kernel ops");
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(ops.disk_write(lba, phys)));
+ return HostResult.data(Data.new.num(kops().disk_write(lba, phys)));
}
fn registerPrimitives(vm: *revo.VM) !void {
@@ -496,8 +481,9 @@ fn registerPrimitives(vm: *revo.VM) !void {
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("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)));
@@ -507,7 +493,6 @@ fn registerPrimitives(vm: *revo.VM) !void {
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_present", try vm.installHost("disk_present", define(&[_]T{}, hostDiskPresent)));
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)));
@@ -520,6 +505,7 @@ const init_program = @embedFile("k_font") ++ "\n" ++
@embedFile("k_console") ++ "\n" ++
@embedFile("k_vmm") ++ "\n" ++
@embedFile("k_fs") ++ "\n" ++
+ @embedFile("k_seed") ++ "\n" ++
@embedFile("k_proc") ++ "\n" ++
@embedFile("k_shell") ++ "\n" ++
@embedFile("k_input") ++ "\n" ++
@@ -599,6 +585,8 @@ pub fn onKey(scancode: u8) void {
const vm = g_vm orelse return;
const cb = vm.getGlobal("on_key") orelse return;
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");
};
@@ -618,5 +606,44 @@ pub fn onTick(ticks: u64) void {
const vm = g_vm orelse return;
const cb = vm.getGlobal("on_tick") orelse return;
if (cb.asFunction() == null) return;
+ g_vm_running = true;
+ defer g_vm_running = false;
_ = vm.callFunctionParts(cb, null, &[_]revo.Value{revo.Value.new.num(ticks)}, null) catch {};
}
+
+// true while revo bytecode runs, sched.tick wont preempt task 0 mid call so the
+// shared fiber cant be re-entered
+var g_vm_running: bool = false;
+pub fn vmBusy() bool {
+ return g_vm_running;
+}
+
+// fs access for ring 3 programs, called from the int 0x80 handler (interrupts
+// off) straight into the revo fs
+pub fn fsSize(name: []const u8) i64 {
+ const vm = g_vm orelse return -1;
+ const func = vm.getGlobal("fs_size") orelse return -1;
+ const name_val = vm.ownValueString(name) catch return -1;
+ g_vm_running = true;
+ defer g_vm_running = false;
+ const r = vm.callFunctionParts(func, null, &[_]revo.Value{name_val}, null) catch return -1;
+ return @intFromFloat(r.asNumOpt() orelse return -1);
+}
+
+pub fn fsReadInto(name: []const u8, offset: u64, dst: []u8) usize {
+ const vm = g_vm orelse return 0;
+ const func = vm.getGlobal("fs_read_bytes") orelse return 0;
+ const name_val = vm.ownValueString(name) catch return 0;
+ g_vm_running = true;
+ defer g_vm_running = false;
+ const r = vm.callFunctionParts(func, null, &[_]revo.Value{
+ name_val,
+ revo.Value.new.num(offset),
+ revo.Value.new.num(dst.len),
+ }, null) catch return 0;
+ const sid = r.asString() orelse return 0;
+ const bytes = vm.stringValue(sid);
+ const n = @min(bytes.len, dst.len);
+ @memcpy(dst[0..n], bytes[0..n]);
+ return n;
+}
diff --git a/build.zig b/build.zig
index 9877192..0c38e9f 100644
--- a/build.zig
+++ b/build.zig
@@ -68,6 +68,7 @@ pub fn build(b: *std.Build) void {
.{ "k_console", "kernel/console.rv" },
.{ "k_vmm", "kernel/vmm.rv" },
.{ "k_fs", "kernel/fs.rv" },
+ .{ "k_seed", "kernel/seed.rv" },
.{ "k_proc", "kernel/proc.rv" },
.{ "k_shell", "kernel/shell.rv" },
.{ "k_input", "kernel/input.rv" },
@@ -78,18 +79,22 @@ pub fn build(b: *std.Build) void {
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 });
+ // each ring 3 program compiles to its own freestanding elf, embedded under
+ // user_<name>. boot copies them to frames and the shells `run <name>` loads one
+ inline for (.{ "hexview", "calc", "primes" }) |prog| {
+ 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 prog_elf = cc.addOutputFileArg(prog ++ ".elf");
+ cc.addFileArg(b.path("user/" ++ prog ++ ".c"));
+ kernel.root_module.addAnonymousImport("user_" ++ prog, .{ .root_source_file = prog_elf });
+ }
b.installArtifact(kernel);
}
diff --git a/kernel/fs.rv b/kernel/fs.rv
index adb317c..5d72bc1 100644
--- a/kernel/fs.rv
+++ b/kernel/fs.rv
@@ -5,10 +5,7 @@
# sectors 1..8 directory, 128 entries of 32 bytes (16 per sector)
# sectors 9..136 fat, one 2 byte entry per sector on the whole disk
# sectors 137.. file data, one block == one sector
-#
-# a fat entry is the next block in a files chain, 0 means free, END means last
-# block of a chain. a file just stores its first block + byte size in its dir
-# entry and we walk the chain from there
+# a fat entry points at the next block in a files chain, 0 is free, END is the last
const SB_LBA = 0
const DIR_LBA = 1
@@ -66,12 +63,7 @@ end
fn dir_lba(i) DIR_LBA + (i shr 4)
fn dir_off(i) (i band 15) * 32
-# entry byte layout: base is fs.buf + dir_off(i) after loading the sector
-# +0 used (0/1)
-# +1 name length
-# +2 first block (2)
-# +4 size (4)
-# +8 name bytes (up to NAME_MAX)
+# dir entry, 32 bytes: +0 used, +1 name len, +2 first block, +4 size, +8 name bytes
fn name_at(base, len) do
let out = ""
for k in 0..len do out = out ~ string.of_ascii(mem_read(base + 8 + k, 1)) end
@@ -108,13 +100,19 @@ fn fs_init() do
fs.buf = frame_alloc()
fs.fat = frame_alloc()
fs.total = disk_sectors()
- if disk_present() == 0 do fs.ready = :false return :nil end
+ if fs.total == 0 do fs.ready = :false return :nil end
disk_read(SB_LBA, fs.buf)
fs.ready = if mem_read(fs.buf, 4) == MAGIC :true else :false
end
+fn fs_ready() do
+ if fs.ready return :true
+ puts("no filesystem, run mkfs first\n")
+ :false
+end
+
fn fs_format() do
- if disk_present() == 0 do puts("mkfs: no disk\n") return :nil end
+ if disk_sectors() == 0 do puts("mkfs: no disk\n") return :nil end
mem_zero(fs.buf, BLOCK)
mem_write(fs.buf, MAGIC, 4)
mem_write(fs.buf + 4, fs.total, 4)
@@ -126,10 +124,10 @@ fn fs_format() do
puts("formatted " ~ fs.total ~ " sectors, " ~ (fs.total shr 1) ~ " KiB\n")
end
-# write a file, gee i wonder what a function called `fs_write` does
-fn fs_write(name, text) do
- if fs.ready == :false do puts("no filesystem, run mkfs first\n") return :nil end
- if name:len() > NAME_MAX do puts("name too long (max " ~ NAME_MAX ~ ")\n") return :nil end
+# silent core of fs_write, global so the bridge can reach it via getGlobal
+global fs_store = fn(name, text) do
+ if fs_ready() == :false return 0
+ if name:len() > NAME_MAX do puts("name too long (max " ~ NAME_MAX ~ ")\n") return 0 end
let idx = fs_find(name)
if idx >= 0 do
disk_read(dir_lba(idx), fs.buf)
@@ -137,7 +135,7 @@ fn fs_write(name, text) do
end
else do
idx = dir_alloc()
- if idx < 0 do puts("directory full\n") return :nil end
+ if idx < 0 do puts("directory full\n") return 0 end
end
const size = text:len()
@@ -146,7 +144,7 @@ fn fs_write(name, text) do
let written = 0
while written < size do
const block = fat_alloc()
- if block == 0 do puts("disk full\n") return :nil end
+ if block == 0 do puts("disk full\n") return 0 end
fat_set(block, END) # reserve
if first == 0 do first = block end
else do fat_set(prev, block) end
@@ -169,11 +167,52 @@ fn fs_write(name, text) do
mem_write(base + 4, size, 4)
for k in 0..name:len() do mem_write(base + 8 + k, name[k]:ascii(), 1) end
disk_write(dir_lba(idx), fs.buf)
- puts("wrote " ~ name ~ " (" ~ size ~ " bytes)\n")
+ return 1
+end
+
+# write a file, gee i wonder what a function called `fs_write` does
+fn fs_write(name, text) do
+ if fs_store(name, text) == 1 puts("wrote " ~ name ~ " (" ~ text:len() ~ " bytes)\n")
+end
+
+global fs_size = fn(name) do
+ const idx = fs_find(name)
+ if idx < 0 return -1
+ disk_read(dir_lba(idx), fs.buf)
+ return mem_read(fs.buf + dir_off(idx) + 4, 4)
+end
+
+# read up to len bytes from offset, returned as a string (shorter at eof)
+global fs_read_bytes = fn(name, offset, len) do
+ const idx = fs_find(name)
+ if idx < 0 return ""
+ disk_read(dir_lba(idx), fs.buf)
+ const base = fs.buf + dir_off(idx)
+ let block = mem_read(base + 2, 2)
+ const size = mem_read(base + 4, 4)
+ let stop = offset + len
+ if stop > size do stop = size end
+ let pos = 0
+ let out = ""
+ while (block != 0 and block != END and pos < stop) do
+ const nxt = fat_get(block)
+ if (pos + BLOCK > offset and pos < stop) do
+ disk_read(block, fs.buf)
+ let k = 0
+ while (k < BLOCK) do
+ const abs = pos + k
+ if (abs >= offset and abs < stop) do out = out ~ string.of_ascii(mem_read(fs.buf + k, 1)) end
+ k += 1
+ end
+ end
+ pos += BLOCK
+ block = nxt
+ end
+ return out
end
fn fs_cat(name) do
- if fs.ready == :false do puts("no filesystem, run mkfs first\n") return :nil end
+ if fs_ready() == :false return :nil
const idx = fs_find(name)
if idx < 0 do puts("cat: " ~ name ~ ": no such file\n") return :nil end
disk_read(dir_lba(idx), fs.buf)
@@ -194,7 +233,7 @@ fn fs_cat(name) do
end
fn fs_ls() do
- if fs.ready == :false do puts("no filesystem, run mkfs first\n") return :nil end
+ if fs_ready() == :false return :nil
let count = 0
for i in 0..DIR_ENTRIES do
disk_read(dir_lba(i), fs.buf)
@@ -208,7 +247,7 @@ fn fs_ls() do
end
fn fs_rm(name) do
- if fs.ready == :false do puts("no filesystem, run mkfs first\n") return :nil end
+ if fs_ready() == :false return :nil
const idx = fs_find(name)
if idx < 0 do puts("rm: " ~ name ~ ": no such file\n") return :nil end
disk_read(dir_lba(idx), fs.buf)
diff --git a/kernel/main.rv b/kernel/main.rv
index 896bb54..24852fe 100644
--- a/kernel/main.rv
+++ b/kernel/main.rv
@@ -9,9 +9,14 @@ cmd_clear()
dump_memmap()
fs_init()
-if disk_present() == 0 puts("no disk attached\n")
+if disk_sectors() == 0 puts("no disk attached\n")
else if fs.ready puts("mounted revelfs on disk\n")
-else puts("disk has no filesystem, run `mkfs`\n")
+else do
+ # blank disk, first boot after a clone. format it and drop the default files on
+ puts("blank disk, formatting + seeding default files\n")
+ fs_format()
+ seed_fs()
+end
puts("revel shell, live in revo. type `help`\n% ")
flush_console()
diff --git a/kernel/proc.rv b/kernel/proc.rv
index e2f451b..3303bc1 100644
--- a/kernel/proc.rv
+++ b/kernel/proc.rv
@@ -1,12 +1,11 @@
-# parse the embedded elf (physical address from elf_phys()) and run it in ring 3
+# load an embedded elf into a fresh address space and run it in ring 3
const PT_LOAD = 1
const PAGE_FLAGS = 6 # write | user
-fn cmd_run() do
- const base = elf_phys()
+fn load_elf(base) do
if mem_read(base, 4) != 0x464c457f do
- puts("run: not an ELF\n")
+ puts("not an ELF\n")
return :nil
end
const entry = mem_read(base + 24, 8)
@@ -38,3 +37,18 @@ fn cmd_run() do
as_map(space, stack_top - 4096, frame_alloc(), PAGE_FLAGS)
proc_run(space, entry, stack_top)
end
+
+# launch a program by name, :true if one matched
+fn run_prog(name) do
+ for i in 0..prog_count() do
+ if prog_name(i) == name do load_elf(prog_phys(i)) return :true end
+ end
+ puts("no such program: " ~ name ~ "\n")
+ return :false
+end
+
+fn list_progs() do
+ puts("programs:")
+ for i in 0..prog_count() do puts(" " ~ prog_name(i)) end
+ puts("\n")
+end
diff --git a/kernel/seed.rv b/kernel/seed.rv
new file mode 100644
index 0000000..6c0a73f
--- /dev/null
+++ b/kernel/seed.rv
@@ -0,0 +1,5 @@
+# js dw about this ok?
+fn seed_fs() do
+ fs_write("i have big balls.txt", "dude my balls are actually massive")
+ fs_write("balls.txt", "its lowk insane how big my balls are")
+end
diff --git a/kernel/shell.rv b/kernel/shell.rv
index 6e3c2e4..35eac37 100644
--- a/kernel/shell.rv
+++ b/kernel/shell.rv
@@ -1,15 +1,42 @@
# this is just a simple prompt to test that shit is working
fn cmd_help() do
- puts("commands: help, clear, bars, echo <text>, time, mem, uptime, ps, run, zoom in|out\n")
+ puts("commands: help, clear, bars, echo <text>, time, mem, uptime, ps, run <prog>, zoom in|out\n")
puts("fs: mkfs, ls, cat <name>, write <name> <text>, rm <name>\n")
+ puts(" names with spaces go in \"quotes\"\n")
end
-fn cmd_write(line, words) do
- if words:len() < 2 do puts("usage: write <name> <text>\n") return :nil end
- const name = words[1]
- const start = 6 + name:len() + 1
- const text = if line:len() > start line:sub(start, line:len() - start) else ""
- fs_write(name, text)
+fn arg_name(line, start) do
+ let i = start
+ let name = ""
+ if (i < line:len() and line[i]:ascii() == 34) do
+ i += 1
+ while (i < line:len() and line[i]:ascii() != 34) do name = name ~ line[i] i += 1 end
+ if i < line:len() do i += 1 end
+ end
+ else do
+ while (i < line:len() and line[i]:ascii() != 32) do name = name ~ line[i] i += 1 end
+ end
+ if (i < line:len() and line[i]:ascii() == 32) do i += 1 end
+ { name = name, rest = i }
+end
+
+fn cmd_write(line) do
+ const a = arg_name(line, 6)
+ if a.name:len() == 0 do puts("usage: write <name> <text>\n") return :nil end
+ const text = if line:len() > a.rest line:sub(a.rest, line:len() - a.rest) else ""
+ fs_write(a.name, text)
+end
+
+fn cmd_cat(line) do
+ const a = arg_name(line, 4)
+ if a.name:len() == 0 do puts("usage: cat <name>\n") return :nil end
+ fs_cat(a.name)
+end
+
+fn cmd_rm(line) do
+ const a = arg_name(line, 3)
+ if a.name:len() == 0 do puts("usage: rm <name>\n") return :nil end
+ fs_rm(a.name)
end
fn zoom_by(delta) do
@@ -85,7 +112,14 @@ fn run(line) do
if line:trim():len() == 0 do puts("% ") return :nil end
const words = line:split(" ")
const cmd = words[0]
- if cmd == "run" do cmd_run() return :nil end
+ # run launches a process, its prompt gets reprinted by on_tick when it exits so
+ # it lands below the programs output. no arg just lists whats available
+ if cmd == "run" do
+ if words:len() <= 1 do list_progs() puts("% ") return :nil end
+ if run_prog(words[1]) == :true return :nil
+ puts("% ")
+ return :nil
+ end
match cmd
| "help" => cmd_help()
| "clear" => cmd_clear()
@@ -97,9 +131,9 @@ fn run(line) do
| "zoom" => cmd_zoom(words)
| "mkfs" => fs_format()
| "ls" => fs_ls()
- | "cat" => if words:len() > 1 fs_cat(words[1]) else puts("usage: cat <name>\n")
- | "write" => cmd_write(line, words)
- | "rm" => if words:len() > 1 fs_rm(words[1]) else puts("usage: rm <name>\n")
+ | "cat" => cmd_cat(line)
+ | "write" => cmd_write(line)
+ | "rm" => cmd_rm(line)
| "echo" => if line:len() > 5 puts(line:sub(5, line:len() - 5) ~ "\n") else puts("\n")
| _ => puts("unknown command: " ~ cmd ~ "\n")
puts("% ")
diff --git a/user/calc.c b/user/calc.c
new file mode 100644
index 0000000..2d0e3f8
--- /dev/null
+++ b/user/calc.c
@@ -0,0 +1,78 @@
+#include "user.h"
+
+static int is_digit(char character)
+{
+ return character >= '0' && character <= '9';
+}
+
+void _start(void)
+{
+ put("calc, e.g. 3 + 4 * 2 (left to right), empty line quits\n");
+
+ char line[128];
+ for (;;)
+ {
+ put("> ");
+ int length = read_line(line, 128);
+ put("\n");
+ if (length == 0)
+ break;
+
+ long accumulator = 0;
+ char operator = '+';
+ int valid = 1;
+ int saw_number = 0;
+ int position = 0;
+ while (position < length)
+ {
+ char character = line[position];
+ if (character == ' ')
+ {
+ position++;
+ continue;
+ }
+ if (is_digit(character))
+ {
+ long value = 0;
+ while (position < length && is_digit(line[position]))
+ value = value * 10 + (line[position++] - '0');
+ if (operator == '+')
+ accumulator += value;
+ else if (operator == '-')
+ accumulator -= value;
+ else if (operator == '*')
+ accumulator *= value;
+ else if (operator == '/' || operator == '%')
+ {
+ if (value == 0)
+ {
+ put("divide by zero\n");
+ valid = 0;
+ break;
+ }
+ accumulator = (operator == '/') ? accumulator / value : accumulator % value;
+ }
+ saw_number = 1;
+ }
+ else if (character == '+' || character == '-' || character == '*'
+ || character == '/' || character == '%')
+ {
+ operator = character;
+ position++;
+ }
+ else
+ {
+ put("bad character\n");
+ valid = 0;
+ break;
+ }
+ }
+ if (valid && saw_number)
+ {
+ put("= ");
+ put_int(accumulator);
+ put("\n");
+ }
+ }
+ sys_exit(0);
+}
diff --git a/user/hexview.c b/user/hexview.c
new file mode 100644
index 0000000..f2425e1
--- /dev/null
+++ b/user/hexview.c
@@ -0,0 +1,91 @@
+// hexview, a ring 3 program that dumps a file from the revel fs. nav is n/p/q
+// since the keyboard only gives us decoded ascii, no arrow keys yet
+
+#include "user.h"
+
+static const char HEX[] = "0123456789abcdef";
+
+static void put_hex8(unsigned char byte)
+{
+ char digits[2] = { HEX[byte >> 4], HEX[byte & 15] };
+ sys_write(digits, 2);
+}
+
+static void put_hex32(unsigned long value)
+{
+ char digits[8];
+ for (int nibble = 0; nibble < 8; nibble++)
+ digits[nibble] = HEX[(value >> ((7 - nibble) * 4)) & 15];
+ sys_write(digits, 8);
+}
+
+#define PAGE 256
+#define COLS 16
+
+void _start(void)
+{
+ put("hexview, type a filename\nfile: ");
+ char name[64];
+ read_line(name, 64);
+ put("\n");
+
+ long size = sys_fs_size(name);
+ if (size < 0)
+ {
+ put("no such file\n");
+ sys_exit(1);
+ }
+
+ unsigned long offset = 0;
+ char buffer[PAGE];
+ for (;;)
+ {
+ long count = sys_fs_read(name, offset, buffer, PAGE);
+
+ put("--- ");
+ put(name);
+ put(" @ 0x");
+ put_hex32(offset);
+ put(" / 0x");
+ put_hex32((unsigned long)size);
+ put(" ---\n");
+
+ for (long row = 0; row < count; row += COLS)
+ {
+ put_hex32(offset + (unsigned long)row);
+ put(": ");
+ for (int column = 0; column < COLS; column++)
+ {
+ if (row + column < count)
+ {
+ put_hex8((unsigned char)buffer[row + column]);
+ sys_write(" ", 1);
+ }
+ else
+ put(" ");
+ }
+ put(" ");
+ for (int column = 0; column < COLS && row + column < count; column++)
+ {
+ char character = buffer[row + column];
+ if (character >= 32 && character < 127)
+ sys_write(&character, 1);
+ else
+ sys_write(".", 1);
+ }
+ put("\n");
+ }
+
+ put("[n]ext [p]rev [q]uit > ");
+ char key = (char)sys_read();
+ put("\n");
+ if (key == 'q')
+ break;
+ if (key == 'n' && offset + PAGE < (unsigned long)size)
+ offset += PAGE;
+ if (key == 'p' && offset >= PAGE)
+ offset -= PAGE;
+ }
+
+ sys_exit(0);
+}
diff --git a/user/primes.c b/user/primes.c
new file mode 100644
index 0000000..49c545a
--- /dev/null
+++ b/user/primes.c
@@ -0,0 +1,42 @@
+#include "user.h"
+
+void _start(void)
+{
+ put("primes up to N, enter N (empty line quits)\n");
+ char line[32];
+ for (;;)
+ {
+ put("> ");
+ int length = read_line(line, 32);
+ put("\n");
+ if (length == 0)
+ break;
+
+ long limit = 0;
+ for (int position = 0; position < length; position++)
+ if (line[position] >= '0' && line[position] <= '9')
+ limit = limit * 10 + (line[position] - '0');
+
+ int count = 0;
+ for (long candidate = 2; candidate <= limit; candidate++)
+ {
+ int prime = 1;
+ for (long divisor = 2; divisor * divisor <= candidate; divisor++)
+ if (candidate % divisor == 0)
+ {
+ prime = 0;
+ break;
+ }
+ if (prime)
+ {
+ put_int(candidate);
+ put(" ");
+ count++;
+ }
+ }
+ put("\n");
+ put_int(count);
+ put(" primes\n");
+ }
+ sys_exit(0);
+}
diff --git a/user/program.c b/user/program.c
deleted file mode 100644
index 65621f5..0000000
--- a/user/program.c
+++ /dev/null
@@ -1,63 +0,0 @@
-static long sys_write(const char* buffer, unsigned long buffer_length)
-{
- long ret;
- __asm__ volatile("int $0x80" : "=a"(ret) : "a"(1L), "D"(buffer), "S"(buffer_length) : "memory");
- return ret;
-}
-
-static long sys_read(void)
-{
- long ret;
- __asm__ volatile("int $0x80" : "=a"(ret) : "a"(2L) : "memory");
- return ret;
-}
-
-static void sys_exit(long code)
-{
- __asm__ volatile("int $0x80" : : "a"(0L), "D"(code) : "memory");
- for (;;);
-}
-
-static void put(const char* string)
-{
- unsigned long n = 0;
- while (string[n])
- n++;
- sys_write(string, n);
-}
-
-void _start(void)
-{
- put("what's your name? ");
- char name[64];
- int name_length = 0;
-
- for (;;)
- {
- char character = (char)sys_read();
- if (character == '\n')
- break;
-
- if (character == '\b')
- {
- if (name_length > 0)
- {
- name_length--;
- sys_write("\b \b", 3);
- }
-
- continue;
- }
-
- if (name_length < 63)
- {
- name[name_length++] = character;
- sys_write(&character, 1);
- }
- }
-
- put("\noue va te faire foutre ");
- sys_write(name, name_length);
- put("\n");
- sys_exit(0);
-}
diff --git a/user/user.h b/user/user.h
new file mode 100644
index 0000000..676326b
--- /dev/null
+++ b/user/user.h
@@ -0,0 +1,97 @@
+// shared syscall stubs + tiny helpers for revel ring 3 programs
+
+static long sys_write(const char* buffer, unsigned long length)
+{
+ long result;
+ __asm__ volatile("int $0x80" : "=a"(result) : "a"(1L), "D"(buffer), "S"(length) : "memory");
+ return result;
+}
+
+static long sys_read(void)
+{
+ long result;
+ __asm__ volatile("int $0x80" : "=a"(result) : "a"(2L) : "memory");
+ return result;
+}
+
+static void sys_exit(long code)
+{
+ __asm__ volatile("int $0x80" : : "a"(0L), "D"(code) : "memory");
+ for (;;);
+}
+
+static long sys_fs_size(const char* name)
+{
+ long result;
+ __asm__ volatile("int $0x80" : "=a"(result) : "a"(4L), "D"(name) : "memory");
+ return result;
+}
+
+static long sys_fs_read(const char* name, unsigned long offset, char* buffer, unsigned long length)
+{
+ long result;
+ __asm__ volatile("int $0x80"
+ : "=a"(result)
+ : "a"(5L), "D"(name), "S"(offset), "d"(buffer), "c"(length)
+ : "memory");
+ return result;
+}
+
+static void put(const char* string)
+{
+ unsigned long length = 0;
+ while (string[length])
+ length++;
+ sys_write(string, length);
+}
+
+static int read_line(char* buffer, int max_length)
+{
+ int length = 0;
+ for (;;)
+ {
+ char character = (char)sys_read();
+ if (character == '\n')
+ break;
+ if (character == '\b')
+ {
+ if (length > 0)
+ {
+ length--;
+ sys_write("\b \b", 3);
+ }
+ continue;
+ }
+ if (length < max_length - 1)
+ {
+ buffer[length++] = character;
+ sys_write(&character, 1);
+ }
+ }
+ buffer[length] = 0;
+ return length;
+}
+
+static void put_int(long value)
+{
+ char buffer[21];
+ int length = 0;
+ int negative = value < 0;
+ unsigned long magnitude = negative ? (unsigned long)(-value) : (unsigned long)value;
+ if (magnitude == 0)
+ buffer[length++] = '0';
+ while (magnitude > 0)
+ {
+ buffer[length++] = (char)('0' + magnitude % 10);
+ magnitude /= 10;
+ }
+ if (negative)
+ buffer[length++] = '-';
+ for (int left = 0, right = length - 1; left < right; left++, right--)
+ {
+ char swap = buffer[left];
+ buffer[left] = buffer[right];
+ buffer[right] = swap;
+ }
+ sys_write(buffer, length);
+}