diff options
| author | hachem <im@hachem.wtf> | 2026-10-04 02:02:15 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2026-10-04 02:02:15 +0200 |
| commit | 7c1bd03a71cccfcf48e79d241787021b0de98e73 (patch) | |
| tree | b7131a1f4eee60d24781d3f549d191e17793c282 | |
| parent | 627f05098785327744e666c9149375125499d2ff (diff) | |
feat: add more user programs
| -rw-r--r-- | Makefile | 4 | ||||
| -rw-r--r-- | boot/ata.zig | 1 | ||||
| -rw-r--r-- | boot/boot.zig | 37 | ||||
| -rw-r--r-- | boot/elf.zig | 10 | ||||
| -rw-r--r-- | boot/sched.zig | 23 | ||||
| -rw-r--r-- | boot/user.zig | 69 | ||||
| -rw-r--r-- | bridge/bridge.zig | 149 | ||||
| -rw-r--r-- | build.zig | 29 | ||||
| -rw-r--r-- | kernel/fs.rv | 83 | ||||
| -rw-r--r-- | kernel/main.rv | 9 | ||||
| -rw-r--r-- | kernel/proc.rv | 22 | ||||
| -rw-r--r-- | kernel/seed.rv | 5 | ||||
| -rw-r--r-- | kernel/shell.rv | 56 | ||||
| -rw-r--r-- | user/calc.c | 78 | ||||
| -rw-r--r-- | user/hexview.c | 91 | ||||
| -rw-r--r-- | user/primes.c | 42 | ||||
| -rw-r--r-- | user/program.c | 63 | ||||
| -rw-r--r-- | user/user.h | 97 |
18 files changed, 640 insertions, 228 deletions
@@ -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; +} @@ -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); +} |
