diff options
| author | hachem <im@hachem.wtf> | 2026-10-04 00:52:49 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2026-10-04 00:52:49 +0200 |
| commit | 627f05098785327744e666c9149375125499d2ff (patch) | |
| tree | a72f3949e0729f1e73ad39ccb1809698a5ed8298 | |
| parent | 09982d80c45805715984cc1cae96c37e4fc148c3 (diff) | |
feat: revelfs type shi
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | Makefile | 20 | ||||
| -rw-r--r-- | boot/ata.zig | 160 | ||||
| -rw-r--r-- | boot/boot.zig | 22 | ||||
| -rw-r--r-- | boot/port.zig | 15 | ||||
| -rw-r--r-- | bridge/bridge.zig | 37 | ||||
| -rw-r--r-- | build.zig | 1 | ||||
| -rw-r--r-- | kernel/console.rv | 10 | ||||
| -rw-r--r-- | kernel/fs.rv | 221 | ||||
| -rw-r--r-- | kernel/main.rv | 10 | ||||
| -rw-r--r-- | kernel/proc.rv | 2 | ||||
| -rw-r--r-- | kernel/shell.rv | 25 |
12 files changed, 507 insertions, 19 deletions
@@ -8,6 +8,9 @@ /iso_root/ /*.iso +# persistent fs disk image +/*.img + # local boot scratch (screendumps, qemu logs) /*.ppm /*.png @@ -9,8 +9,10 @@ QEMU ?= qemu-system-x86_64 KERNEL := zig-out/bin/revel ISO := revel.iso +DISK := revel-disk.img +DISK_MB := 16 -.PHONY: all kernel iso run clean distclean +.PHONY: all kernel iso run clean distclean wipedisk all: iso @@ -42,8 +44,20 @@ iso: kernel limine/limine iso_root -o $(ISO) ./limine/limine bios-install $(ISO) -run: iso - $(QEMU) -M q35 -m 256M -cdrom $(ISO) -boot d -no-reboot -no-shutdown +# 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 \ + -no-reboot -no-shutdown clean: rm -rf iso_root zig-out .zig-cache $(ISO) diff --git a/boot/ata.zig b/boot/ata.zig new file mode 100644 index 0000000..ec63c1c --- /dev/null +++ b/boot/ata.zig @@ -0,0 +1,160 @@ +// ata pio driver, primary bus master drive, 28 bit lba +// +// this is the dumbest most simple most retartded possible block device driver +// it busy polls the status port and shovels one sector at a time through the 16 +// bit data port. no dma no interrupts no second drive. its slow but the whole point +// is that revo builds a real filesystem on top of these two calls (read/write a 512 byte sector) so +// i need to rewrite this when im not drunk (never gonna happen) +// +// see: https://wiki.osdev.org/ATA_PIO_Mode + +const port = @import("port.zig"); +const serial = @import("serial.zig"); + +pub const SECTOR = 512; + +// primary bus io ports +const DATA = 0x1F0; +const FEATURES = 0x1F1; // error on read +const SECCOUNT = 0x1F2; +const LBA_LO = 0x1F3; +const LBA_MID = 0x1F4; +const LBA_HI = 0x1F5; +const DRIVE = 0x1F6; +const CMD = 0x1F7; // status on read +const CTRL = 0x3F6; // alt status / device control + +// status bits +const ST_ERR = 0x01; +const ST_DRQ = 0x08; // data request, drive wants a word moved +const ST_DRDY = 0x40; +const ST_BSY = 0x80; + +const CMD_READ = 0x20; +const CMD_WRITE = 0x30; +const CMD_FLUSH = 0xE7; +const CMD_IDENTIFY = 0xEC; + +var g_present = false; +var g_sectors: u64 = 0; + +pub fn present() bool { + return g_present; +} + +pub fn sectorCount() u64 { + return g_sectors; +} + +// reading the alt status 4 times burns ~400ns, the spec wants that gap after a +// command before the status bits mean anything +fn delay400() void { + _ = port.inb(CTRL); + _ = port.inb(CTRL); + _ = port.inb(CTRL); + _ = port.inb(CTRL); +} + +// spin until bsy drops +// returns false if it never does (dead bus) +fn waitNotBusy() bool { + var spins: u32 = 0; + while (spins < 1_000_000) : (spins += 1) { + if (port.inb(CMD) & ST_BSY == 0) return true; + } + return false; +} + +// spin until the drive is ready to move a word (drq set) +// false on error/timeout +fn waitDrq() bool { + var spins: u32 = 0; + while (spins < 1_000_000) : (spins += 1) { + const st = port.inb(CMD); + if (st & ST_ERR != 0) return false; + if (st & ST_BSY == 0 and st & ST_DRQ != 0) return true; + } + return false; +} + +// select master and load the lba + a one sector count +fn selectSector(lba: u28) void { + port.outb(DRIVE, 0xE0 | @as(u8, @truncate((lba >> 24) & 0x0F))); + port.outb(FEATURES, 0); + port.outb(SECCOUNT, 1); + port.outb(LBA_LO, @truncate(lba & 0xFF)); + port.outb(LBA_MID, @truncate((lba >> 8) & 0xFF)); + port.outb(LBA_HI, @truncate((lba >> 16) & 0xFF)); +} + +// probe the primary master with identify, the floating bus reads status +// 0xFF, a present drive clears bsy and coughs up 256 words of identify data, +// words 60/61 hold the 28 bit sector count +pub fn init() void { + // a nonexistent bus floats high, bail before we wait a million spins on it + if (port.inb(CMD) == 0xFF) { + serial.write("ata: no drive on primary bus\r\n"); + return; + } + + port.outb(DRIVE, 0xA0); // master + delay400(); + port.outb(SECCOUNT, 0); + port.outb(LBA_LO, 0); + port.outb(LBA_MID, 0); + port.outb(LBA_HI, 0); + port.outb(CMD, CMD_IDENTIFY); + delay400(); + + if (port.inb(CMD) == 0) { + serial.write("ata: primary master absent\r\n"); + return; + } + if (!waitDrq()) { + serial.write("ata: identify failed\r\n"); + return; + } + + var id: [256]u16 = undefined; + for (&id) |*w| w.* = port.inw(DATA); + + g_sectors = (@as(u64, id[61]) << 16) | id[60]; + g_present = true; + serial.write("ata: primary master, sectors="); + serial.writeDec(g_sectors); + 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; + selectSector(lba); + port.outb(CMD, CMD_READ); + if (!waitDrq()) return false; + var i: usize = 0; + while (i < SECTOR) : (i += 2) { + const w = port.inw(DATA); + buf[i] = @truncate(w & 0xFF); + buf[i + 1] = @truncate(w >> 8); + } + return true; +} + +// write one 512 byte sector from buf to lba, then flush the drive cache so it +// actually hits the image and survives a reboot, yk, like an actual file system +pub fn write(lba: u28, buf: *const [SECTOR]u8) bool { + if (!g_present) return false; + if (!waitNotBusy()) return false; + selectSector(lba); + port.outb(CMD, CMD_WRITE); + if (!waitDrq()) return false; + var i: usize = 0; + while (i < SECTOR) : (i += 2) { + const w = @as(u16, buf[i]) | (@as(u16, buf[i + 1]) << 8); + port.outw(DATA, w); + } + port.outb(CMD, CMD_FLUSH); + _ = waitNotBusy(); + return true; +} diff --git a/boot/boot.zig b/boot/boot.zig index 9f384fa..28fb8c1 100644 --- a/boot/boot.zig +++ b/boot/boot.zig @@ -12,6 +12,7 @@ const elf = @import("elf.zig"); const keyboard = @import("keyboard.zig"); const timer = @import("timer.zig"); const sched = @import("sched.zig"); +const ata = @import("ata.zig"); const bridge = @import("bridge"); // we need a panic handler otherwise zig wont be happy, @@ -100,6 +101,22 @@ 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(); +} +fn kDiskRead(lba: u64, phys: u64) u64 { + const buf: *[ata.SECTOR]u8 = @ptrFromInt(pmm.physToVirt(phys)); + return if (ata.read(@truncate(lba), buf)) 1 else 0; +} +fn kDiskWrite(lba: u64, phys: u64) u64 { + const buf: *const [ata.SECTOR]u8 = @ptrFromInt(pmm.physToVirt(phys)); + return if (ata.write(@truncate(lba), buf)) 1 else 0; +} + // entry(_start) export fn _start() callconv(.c) noreturn { serial.init(); @@ -124,6 +141,7 @@ export fn _start() callconv(.c) noreturn { pmmSelfTest(); 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"); @@ -150,6 +168,10 @@ export fn _start() callconv(.c) noreturn { .proc_count = &kProcCount, .elf_phys = elf_phys, .elf_size = elf.hello_elf.len, + .disk_present = &kDiskPresent, + .disk_sectors = &kDiskSectors, + .disk_read = &kDiskRead, + .disk_write = &kDiskWrite, }; // hand the kernel heap + framebuffer + low level ops to revo and bring up the vm diff --git a/boot/port.zig b/boot/port.zig index be760b1..d38f829 100644 --- a/boot/port.zig +++ b/boot/port.zig @@ -17,3 +17,18 @@ pub inline fn inb(port: u16) u8 { : [port] "N{dx}" (port), ); } + +pub inline fn outw(port: u16, val: u16) void { + asm volatile ("outw %[val], %[port]" + : + : [val] "{ax}" (val), + [port] "N{dx}" (port), + ); +} + +pub inline fn inw(port: u16) u16 { + return asm volatile ("inw %[port], %[result]" + : [result] "={ax}" (-> u16), + : [port] "N{dx}" (port), + ); +} diff --git a/bridge/bridge.zig b/bridge/bridge.zig index 2c4da6a..c4918b0 100644 --- a/bridge/bridge.zig +++ b/bridge/bridge.zig @@ -293,6 +293,11 @@ pub const KernelOps = struct { 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, + 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; @@ -445,6 +450,30 @@ fn hostProcCount(_: []const revo.Value, _: *revo.VM) anyerror!HostResult { 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())); +} + +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())); +} + +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))); +} + +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))); +} + fn registerPrimitives(vm: *revo.VM) !void { const define = revo.baselib.host.define; const T = revo.baselib.host.ParamType; @@ -455,7 +484,7 @@ fn registerPrimitives(vm: *revo.VM) !void { try vm.registerGlobal("fb_height", try vm.installHost("fb_height", define(&[_]T{}, hostFbHeight))); try vm.registerGlobal("fill_rect", try vm.installHost("fill_rect", define(&[_]T{ .number, .number, .number, .number, .number }, hostFillRect))); try vm.registerGlobal("fb_scroll", try vm.installHost("fb_scroll", define(&[_]T{ .number, .number }, hostFbScroll))); - // low level: memory + address spaces + process launch (revos elf loader) + try vm.registerGlobal("mem_read", try vm.installHost("mem_read", define(&[_]T{ .number, .number }, hostMemRead))); try vm.registerGlobal("mem_copy", try vm.installHost("mem_copy", define(&[_]T{ .number, .number, .number }, hostMemCopy))); try vm.registerGlobal("mem_zero", try vm.installHost("mem_zero", define(&[_]T{ .number, .number }, hostMemZero))); @@ -477,6 +506,11 @@ fn registerPrimitives(vm: *revo.VM) !void { try vm.registerGlobal("mem_total", try vm.installHost("mem_total", define(&[_]T{}, hostMemTotal))); try vm.registerGlobal("uptime", try vm.installHost("uptime", define(&[_]T{}, hostUptime))); try vm.registerGlobal("proc_count", try vm.installHost("proc_count", define(&[_]T{}, hostProcCount))); + + 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))); } // the vm outlives boot() now, so we juts make this hoe static @@ -485,6 +519,7 @@ var g_vm: ?*revo.VM = null; const init_program = @embedFile("k_font") ++ "\n" ++ @embedFile("k_console") ++ "\n" ++ @embedFile("k_vmm") ++ "\n" ++ + @embedFile("k_fs") ++ "\n" ++ @embedFile("k_proc") ++ "\n" ++ @embedFile("k_shell") ++ "\n" ++ @embedFile("k_input") ++ "\n" ++ @@ -67,6 +67,7 @@ pub fn build(b: *std.Build) void { .{ "k_font", "kernel/font.rv" }, .{ "k_console", "kernel/console.rv" }, .{ "k_vmm", "kernel/vmm.rv" }, + .{ "k_fs", "kernel/fs.rv" }, .{ "k_proc", "kernel/proc.rv" }, .{ "k_shell", "kernel/shell.rv" }, .{ "k_input", "kernel/input.rv" }, diff --git a/kernel/console.rv b/kernel/console.rv index d0ee43c..2083817 100644 --- a/kernel/console.rv +++ b/kernel/console.rv @@ -1,5 +1,5 @@ -const FW = 8 # glyph width in pixels -const FH = 16 # glyph height in pixels (8x16 vga bitmap font) +const FW = 8 # glyph width in pixels +const FH = 16 # glyph height in pixels (8x16 vga bitmap font) const FG = 0xd0d0d0 const CON_BG = 0x101018 @@ -39,6 +39,7 @@ end # we dont do shift/cntrl # thats a job for future me lmao +# NOTE: future me kinda delt with it, i was mad drunk fn con_putc(ch) do const code = ch:ascii() if code == 10 do con_newline() return :nil end # \n @@ -58,7 +59,7 @@ fn puts(str) serial_puts(str) const CUR = 0x50fa7b # the cursor is an overlay block. its cleared before any drawing and shown after, # so it never sits under a glyph, cur_x/cur_y remember where it was actually drawn -# so we always clear the right cell even after the head moved (backspace etc) +# so we always clear the right cell even after the head moved (backspace and shit) fn cursor_clear() do if con.shown do fill_rect(con.cur_x * cell_w(), con.cur_y * cell_h(), cell_w(), cell_h(), CON_BG) @@ -84,9 +85,6 @@ global flush_console = fn() do cursor_show() end -# called each pit tick from the event loop (scheduler task 0). render any output a -# running process left in the serial mirror (this is how its writes reach the -# screen while time sliced), reprint the prompt when one exits, else blink global on_tick = fn(ticks) do let byte = serial_next() if byte >= 0 do diff --git a/kernel/fs.rv b/kernel/fs.rv new file mode 100644 index 0000000..adb317c --- /dev/null +++ b/kernel/fs.rv @@ -0,0 +1,221 @@ +# revelfs, a tiny fat style filesystem +# +# on disk layout for the 16 mib test disk (32768 sectors): +# sector 0 superblock (magic + total sectors) +# 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 + +const SB_LBA = 0 +const DIR_LBA = 1 +const DIR_SECTORS = 8 +const DIR_ENTRIES = 128 +const FAT_LBA = 9 +const FAT_SECTORS = 128 +const DATA_LBA = 137 +const BLOCK = 512 +const NAME_MAX = 24 +const MAGIC = 0x53465652 # "RVFS" little endian +const END = 0xffff # end of chain marker in the fat + +# buf is a scratch sector for the superblock/directory/data, fat is a separate +# scratch sector so fat lookups dont trample a directory sector were mid edit +global fs = { buf = 0, fat = 0, ready = :false, total = 0 } + +fn fat_get(block) do + disk_read(FAT_LBA + (block shr 8), fs.fat) + mem_read(fs.fat + (block band 255) * 2, 2) +end + +fn fat_set(block, val) do + const lba = FAT_LBA + (block shr 8) + disk_read(lba, fs.fat) + mem_write(fs.fat + (block band 255) * 2, val, 2) + disk_write(lba, fs.fat) +end + +# first free data block, or 0 if the disk is full. scans a whole fat sector per +# read instead of one entry at a time so this isnt snails pace +fn fat_alloc() do + let block = DATA_LBA + while block < fs.total do + disk_read(FAT_LBA + (block shr 8), fs.fat) + let off = block band 255 + while (off < 256 and block < fs.total) do + if mem_read(fs.fat + off * 2, 2) == 0 return block + off += 1 + block += 1 + end + end + 0 +end + +fn free_chain(first) do + let block = first + while (block != 0 and block != END) do + const nxt = fat_get(block) + fat_set(block, 0) + block = nxt + end +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) +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 + out +end + +fn name_matches(base, target) do + const len = mem_read(base + 1, 1) + if len != target:len() return :false + for k in 0..len do + if mem_read(base + 8 + k, 1) != target[k]:ascii() return :false + end + :true +end + +fn fs_find(name) do + for i in 0..DIR_ENTRIES do + disk_read(dir_lba(i), fs.buf) + const base = fs.buf + dir_off(i) + if mem_read(base, 1) == 1 and name_matches(base, name) return i + end + return -1 +end + +fn dir_alloc() do + for i in 0..DIR_ENTRIES do + disk_read(dir_lba(i), fs.buf) + if mem_read(fs.buf + dir_off(i), 1) == 0 return i + end + return -1 +end + +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 + disk_read(SB_LBA, fs.buf) + fs.ready = if mem_read(fs.buf, 4) == MAGIC :true else :false +end + +fn fs_format() do + if disk_present() == 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) + disk_write(SB_LBA, fs.buf) + mem_zero(fs.buf, BLOCK) + for s in 0..DIR_SECTORS do disk_write(DIR_LBA + s, fs.buf) end + for s in 0..FAT_SECTORS do disk_write(FAT_LBA + s, fs.buf) end + fs.ready = :true + 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 + let idx = fs_find(name) + if idx >= 0 do + disk_read(dir_lba(idx), fs.buf) + free_chain(mem_read(fs.buf + dir_off(idx) + 2, 2)) + end + else do + idx = dir_alloc() + if idx < 0 do puts("directory full\n") return :nil end + end + + const size = text:len() + let first = 0 + let prev = 0 + let written = 0 + while written < size do + const block = fat_alloc() + if block == 0 do puts("disk full\n") return :nil end + fat_set(block, END) # reserve + if first == 0 do first = block end + else do fat_set(prev, block) end + mem_zero(fs.buf, BLOCK) + let k = 0 + while (k < BLOCK and written < size) do + mem_write(fs.buf + k, text[written]:ascii(), 1) + k += 1 + written += 1 + end + disk_write(block, fs.buf) + prev = block + end + + disk_read(dir_lba(idx), fs.buf) + const base = fs.buf + dir_off(idx) + mem_write(base, 1, 1) + mem_write(base + 1, name:len(), 1) + mem_write(base + 2, first, 2) + 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") +end + +fn fs_cat(name) do + if fs.ready == :false do puts("no filesystem, run mkfs first\n") return :nil end + 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) + const base = fs.buf + dir_off(idx) + let block = mem_read(base + 2, 2) + let remaining = mem_read(base + 4, 4) + let out = "" + while (block != 0 and block != END and remaining > 0) do + const nxt = fat_get(block) # grab next before disk_read reuses fs.buf + disk_read(block, fs.buf) + const chunk = if remaining > BLOCK BLOCK else remaining + for k in 0..chunk do out = out ~ string.of_ascii(mem_read(fs.buf + k, 1)) end + remaining -= chunk + block = nxt + end + puts(out) + puts("\n") +end + +fn fs_ls() do + if fs.ready == :false do puts("no filesystem, run mkfs first\n") return :nil end + let count = 0 + for i in 0..DIR_ENTRIES do + disk_read(dir_lba(i), fs.buf) + const base = fs.buf + dir_off(i) + if mem_read(base, 1) == 1 do + puts(" " ~ name_at(base, mem_read(base + 1, 1)) ~ " " ~ mem_read(base + 4, 4) ~ " bytes\n") + count += 1 + end + end + if count == 0 puts(" (empty)\n") +end + +fn fs_rm(name) do + if fs.ready == :false do puts("no filesystem, run mkfs first\n") return :nil end + 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) + const base = fs.buf + dir_off(idx) + free_chain(mem_read(base + 2, 2)) + disk_read(dir_lba(idx), fs.buf) + mem_write(fs.buf + dir_off(idx), 0, 1) + disk_write(dir_lba(idx), fs.buf) + puts("removed " ~ name ~ "\n") +end diff --git a/kernel/main.rv b/kernel/main.rv index 4a0db06..896bb54 100644 --- a/kernel/main.rv +++ b/kernel/main.rv @@ -2,10 +2,16 @@ pic_remap() pit_init() -pic_unmask(0) # pit (irq0) -> cursor blink / uptime -pic_unmask(1) # keyboard (irq1) +pic_unmask(0) # pit (irq0) -> cursor blink / uptime +pic_unmask(1) # keyboard (irq1) cmd_clear() dump_memmap() + +fs_init() +if disk_present() == 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") + puts("revel shell, live in revo. type `help`\n% ") flush_console() diff --git a/kernel/proc.rv b/kernel/proc.rv index 162d24b..e2f451b 100644 --- a/kernel/proc.rv +++ b/kernel/proc.rv @@ -1,7 +1,7 @@ # parse the embedded elf (physical address from elf_phys()) and run it in ring 3 const PT_LOAD = 1 -const PAGE_FLAGS = 6 # write | user +const PAGE_FLAGS = 6 # write | user fn cmd_run() do const base = elf_phys() diff --git a/kernel/shell.rv b/kernel/shell.rv index 8d9019f..6e3c2e4 100644 --- a/kernel/shell.rv +++ b/kernel/shell.rv @@ -1,5 +1,16 @@ # this is just a simple prompt to test that shit is working -fn cmd_help() puts("commands: help, clear, bars, echo <text>, time, mem, uptime, ps, run, zoom in|out\n") +fn cmd_help() do + puts("commands: help, clear, bars, echo <text>, time, mem, uptime, ps, run, zoom in|out\n") + puts("fs: mkfs, ls, cat <name>, write <name> <text>, rm <name>\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) +end fn zoom_by(delta) do let scale = con.scale + delta @@ -37,7 +48,7 @@ fn rtc(reg) do end fn cmd_time() puts("utc " ~ bcd(rtc(4)) ~ ":" ~ bcd(rtc(2)) ~ ":" ~ bcd(rtc(0)) ~ "\n") -# 0x prefixed 16 digit hex, matching the kernels serial writehex +# 0x prefixed 16 digit hex fn hex(n) do const digits = "0123456789abcdef" let out = "" @@ -57,7 +68,7 @@ fn mem_type_name(kind) do "unknown" end -# the boot memory map dump, in revo now (called from kmain once the vm is up) +# the boot memory map dump fn dump_memmap() do puts("memory map:\n") for i in 0..memmap_count() do @@ -74,9 +85,6 @@ fn run(line) do if line:trim():len() == 0 do puts("% ") return :nil end const words = line:split(" ") const cmd = words[0] - # `run` launches a process, its prompt is reprinted by on_tick when the process - # exits (so it lands below the programs output, not before it). return early to - # skip the trailing prompt here if cmd == "run" do cmd_run() return :nil end match cmd | "help" => cmd_help() @@ -87,6 +95,11 @@ fn run(line) do | "uptime" => cmd_uptime() | "ps" => cmd_ps() | "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") | "echo" => if line:len() > 5 puts(line:sub(5, line:len() - 5) ~ "\n") else puts("\n") | _ => puts("unknown command: " ~ cmd ~ "\n") puts("% ") |
