diff options
| -rw-r--r-- | boot/elf.zig | 2 | ||||
| -rw-r--r-- | boot/regs.zig | 6 | ||||
| -rw-r--r-- | boot/sched.zig | 33 | ||||
| -rw-r--r-- | bridge/bridge.zig | 12 | ||||
| -rw-r--r-- | kernel/console.rv | 162 | ||||
| -rw-r--r-- | kernel/fs.rv | 436 | ||||
| -rw-r--r-- | kernel/main.rv | 11 | ||||
| -rw-r--r-- | kernel/proc.rv | 4 | ||||
| -rw-r--r-- | kernel/seed.rv | 4 | ||||
| -rw-r--r-- | kernel/shell.rv | 110 |
10 files changed, 398 insertions, 382 deletions
diff --git a/boot/elf.zig b/boot/elf.zig index 906e380..51dc107 100644 --- a/boot/elf.zig +++ b/boot/elf.zig @@ -1,6 +1,4 @@ -// 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") }, diff --git a/boot/regs.zig b/boot/regs.zig index aedae99..7b2bb88 100644 --- a/boot/regs.zig +++ b/boot/regs.zig @@ -24,10 +24,8 @@ pub const Frame = extern struct { ss: u64, }; -// push/pop the 15 gprs in frames exact order (rax pushed first -> highest field, -// r15 last -> at rsp = &frame). shared by every save/restore stub so they cant -// drift from the frame layout above. `mov %rsp, %rdi` after push_gprs hands the -// handler a *frame +// push/pop the 15 gprs in frames exact order +// (rax pushed first -> highest field, r15 last -> at rsp = &frame) pub const PUSH_GPRS = \\push %rax \\push %rbx diff --git a/boot/sched.zig b/boot/sched.zig index 60a8fa0..0857abe 100644 --- a/boot/sched.zig +++ b/boot/sched.zig @@ -26,18 +26,15 @@ var tasks: [MAX_TASKS]Task = @splat(.{}); var kstacks: [MAX_TASKS][KSTACK_SIZE]u8 align(16) = undefined; var current: usize = 0; var spawn_count: u64 = 0; -// the one task currently blocked in read(), if any var blocked_reader: ?usize = null; -// the "foreground" task that owns keyboard input (set when a task first reads, -// cleared when it exits). keystrokes route to it, into stdin_buf when it isnt -// currently blocked, so nothing is lost between its reads. else the shell + var foreground: ?usize = null; var stdin_buf: [64]u8 = undefined; var stdin_head: usize = 0; var stdin_tail: usize = 0; fn stdinPush(byte: u8) void { - if (stdin_tail -% stdin_head >= stdin_buf.len) return; // full, drop + if (stdin_tail -% stdin_head >= stdin_buf.len) return; stdin_buf[stdin_tail % stdin_buf.len] = byte; stdin_tail +%= 1; } @@ -54,9 +51,6 @@ pub fn init(kernel_cr3: u64) void { current = 0; } -// queue a ring 3 process. it starts running the next time the scheduler picks -// it (the tick copies its initial frame onto the stack and iretqs in). rdi is -// seeded with a small per spawn id so demo programs can tell instances apart pub fn spawn(cr3: u64, entry: u64, ustack: u64) void { var i: usize = 1; while (i < MAX_TASKS) : (i += 1) { @@ -79,7 +73,6 @@ pub fn spawn(cr3: u64, entry: u64, ustack: u64) void { } } -// number of live user processes (ready or blocked), excluding the kernel task pub fn procCount() u64 { var count: u64 = 0; var i: usize = 1; @@ -91,8 +84,7 @@ pub fn procCount() u64 { var proc_exited_flag: bool = false; -// mark the running process dead. it then sti+hlts, the next tick reclaims the -// slot and switches away, never returning here +// mark the running process dead. it then sti+hlts pub fn exitCurrent() void { if (current != 0) { tasks[current].state = .dead; @@ -105,23 +97,17 @@ pub fn exitCurrent() void { } } -// did a process exit since last checked? (consumed once.) pub fn takeProcExited() bool { const e = proc_exited_flag; proc_exited_flag = false; return e; } -// called at the top of read(): the caller becomes the foreground reader. returns -// a buffered byte if one is waiting (so read() neednt block), else null pub fn readReady() ?u8 { foreground = current; return stdinPop(); } -// block the running task in read(). `resume_frame` is a context that iretqs -// straight back to just after the processs int 0x80, so when input arrives we -// set its rax and mark it ready, and the scheduler resumes it there pub fn blockCurrentOnRead(resume_frame: Frame) void { if (current == 0) return; // the kernel task never blocks like this tasks[current].frame = resume_frame; @@ -129,9 +115,6 @@ pub fn blockCurrentOnRead(resume_frame: Frame) void { blocked_reader = current; } -// route one input byte. if a task is blocked in read(), wake it with the byte -// else if a foreground reader exists, buffer it for its next read(). returns -// false only when nothing wants it (so the caller feeds the shell instead) pub fn deliverInput(byte: u8) bool { if (blocked_reader) |task| { tasks[task].frame.rax = byte; // read()s return value @@ -146,20 +129,17 @@ pub fn deliverInput(byte: u8) bool { return false; } -// 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) { - // keep the resume frame saved by blockcurrentonread, dont clobber it + // keep the resume frame saved by blockcurrentonread, dont clobber it you bitch } else { tasks[current].frame = frame.*; // freeze the current task } - // round robin to the next ready task var next = current; var i = current; var n: usize = 0; @@ -170,10 +150,9 @@ pub fn tick(frame: *Frame) void { break; } } - // if the current task cant continue (dead/blocked) and nothing else is - // ready, fall back to the kernel task (0), which is always ready + if (tasks[current].state != .ready and next == current) next = 0; - if (next == current) return; // nothing else to run, keep going + if (next == current) return; current = next; frame.* = tasks[current].frame; diff --git a/bridge/bridge.zig b/bridge/bridge.zig index 2fdd1e2..7d64528 100644 --- a/bridge/bridge.zig +++ b/bridge/bridge.zig @@ -303,7 +303,6 @@ pub const KernelOps = struct { }; 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.?; } @@ -418,27 +417,34 @@ fn hostProgName(args: []const revo.Value, vm: *revo.VM) anyerror!HostResult { fn hostMemmapCount(_: []const revo.Value, _: *revo.VM) anyerror!HostResult { return HostResult.data(Data.new.num(kops().memmap_count())); } + fn hostMemmapBase(args: []const revo.Value, _: *revo.VM) anyerror!HostResult { const index = argInt(u64, args, 0) orelse return HostResult.other("memmap_base: bad index"); return HostResult.data(Data.new.num(kops().memmap_base(index))); } + fn hostMemmapLen(args: []const revo.Value, _: *revo.VM) anyerror!HostResult { const index = argInt(u64, args, 0) orelse return HostResult.other("memmap_len: bad index"); return HostResult.data(Data.new.num(kops().memmap_len(index))); } + fn hostMemmapKind(args: []const revo.Value, _: *revo.VM) anyerror!HostResult { const index = argInt(u64, args, 0) orelse return HostResult.other("memmap_kind: bad index"); return HostResult.data(Data.new.num(kops().memmap_kind(index))); } + fn hostMemFree(_: []const revo.Value, _: *revo.VM) anyerror!HostResult { return HostResult.data(Data.new.num(kops().mem_free())); } + fn hostMemTotal(_: []const revo.Value, _: *revo.VM) anyerror!HostResult { return HostResult.data(Data.new.num(kops().mem_total())); } + fn hostUptime(_: []const revo.Value, _: *revo.VM) anyerror!HostResult { return HostResult.data(Data.new.num(kops().uptime())); } + fn hostProcCount(_: []const revo.Value, _: *revo.VM) anyerror!HostResult { return HostResult.data(Data.new.num(kops().proc_count())); } @@ -611,15 +617,11 @@ pub fn onTick(ticks: u64) void { _ = 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; diff --git a/kernel/console.rv b/kernel/console.rv index 2083817..65fe03c 100644 --- a/kernel/console.rv +++ b/kernel/console.rv @@ -2,110 +2,118 @@ 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 +const CUR = 0x50fa7b -global con = { col = 0, row = 0, scale = 1, shown = :false, cur_x = 0, cur_y = 0, blink = 0 } +global con = { + col = 0, row = 0, scale = 1, shown = :false, cur_x = 0, cur_y = 0, blink = 0, -# cell size in pixels at the current zoom. named cell_* (not cw/ch) so they dont -# collide with draw_glyphs `ch` character parameter -fn cell_w() FW * con.scale -fn cell_h() FH * con.scale -fn con_cols() fb_width() / cell_w() -fn con_rows() fb_height() / cell_h() + # cell size in pixels at the current zoom + cell_w = fn(self) FW * self.scale, + cell_h = fn(self) FH * self.scale, + cols = fn(self) fb_width() / self:cell_w(), + rows = fn(self) fb_height() / self:cell_h(), -fn draw_glyph(col, row, ch) do - const x0 = col * cell_w() - const y0 = row * cell_h() - fill_rect(x0, y0, cell_w(), cell_h(), CON_BG) # clear the cell - const code = ch:ascii() - if code < 32 return :nil - if code > 127 return :nil - const glyph = FONT[code - 32] - for fy in 0..FH do - const bits = glyph[fy] - for fx in 0..FW do - if (bits shr fx) band 1 == 1 fill_rect(x0 + fx * con.scale, y0 + fy * con.scale, con.scale, con.scale, FG) + draw_glyph = fn(self, col, row, ch) do + const x0 = col * self:cell_w() + const y0 = row * self:cell_h() + fill_rect(x0, y0, self:cell_w(), self:cell_h(), CON_BG) # clear the cell + const code = ch:ascii() + if code < 32 return :nil + if code > 127 return :nil + const glyph = FONT[code - 32] + for fy in 0..FH do + const bits = glyph[fy] + for fx in 0..FW do + if (bits shr fx) band 1 == 1 fill_rect(x0 + fx * self.scale, y0 + fy * self.scale, self.scale, self.scale, FG) + end end - end -end + end, -fn con_newline() do - con.col = 0 - con.row += 1 - if con.row >= con_rows() do - fb_scroll(cell_h(), CON_BG) - con.row = con_rows() - 1 - end -end + newline = fn(self) do + self.col = 0 + self.row += 1 + if self.row >= self:rows() do + fb_scroll(self:cell_h(), CON_BG) + self.row = self:rows() - 1 + end + 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 - if code == 13 do con.col = 0 return :nil end # \r - if code == 8 do # backspace: move left - if con.col > 0 con.col -= 1 - return :nil - end - if code < 32 return :nil - draw_glyph(con.col, con.row, ch) - con.col += 1 - if con.col >= con_cols() con_newline() -end + # we dont do shift/cntrl, thats a job for future me lmao + # NOTE: future me kinda delt with it, i was mad drunk + putc = fn(self, ch) do + const code = ch:ascii() + if code == 10 do self:newline() return :nil end # \n + if code == 13 do self.col = 0 return :nil end # \r + if code == 8 do # backspace: move left + if self.col > 0 self.col -= 1 + return :nil + end + if code < 32 return :nil + self:draw_glyph(self.col, self.row, ch) + self.col += 1 + if self.col >= self:cols() self:newline() + end, -fn puts(str) serial_puts(str) + clear = fn(self) do + fill_rect(0, 0, fb_width(), fb_height(), CON_BG) + self.col = 0 + self.row = 0 + end, -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 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) - con.shown = :false - end -end -fn cursor_show() do - con.cur_x = con.col - con.cur_y = con.row - fill_rect(con.cur_x * cell_w(), con.cur_y * cell_h(), cell_w(), cell_h(), CUR) - con.shown = :true -end + # 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 and shit) + cursor_clear = fn(self) do + if self.shown do + fill_rect(self.cur_x * self:cell_w(), self.cur_y * self:cell_h(), self:cell_w(), self:cell_h(), CON_BG) + self.shown = :false + end + end, -fn drain() do - let byte = serial_next() - while byte >= 0 do con_putc(string.of_ascii(byte)) byte = serial_next() end -end + cursor_show = fn(self) do + self.cur_x = self.col + self.cur_y = self.row + fill_rect(self.cur_x * self:cell_w(), self.cur_y * self:cell_h(), self:cell_w(), self:cell_h(), CUR) + self.shown = :true + end, + + # render every byte the serial mirror has buffered + drain = fn(self) do + let byte = serial_next() + while byte >= 0 do self:putc(string.of_ascii(byte)) byte = serial_next() end + end, +} + +fn puts(str) serial_puts(str) global flush_console = fn() do - cursor_clear() - drain() + con:cursor_clear() + con:drain() con.blink = 0 - cursor_show() + con:cursor_show() end global on_tick = fn(ticks) do let byte = serial_next() if byte >= 0 do - cursor_clear() - con_putc(string.of_ascii(byte)) - drain() + con:cursor_clear() + con:putc(string.of_ascii(byte)) + con:drain() con.blink = 0 - cursor_show() + con:cursor_show() return :nil end if proc_exited() do - cursor_clear() + con:cursor_clear() puts("% ") - drain() + con:drain() con.blink = 0 - cursor_show() + con:cursor_show() return :nil end con.blink += 1 if con.blink >= 50 do con.blink = 0 - if con.shown cursor_clear() else cursor_show() + if con.shown con:cursor_clear() else con:cursor_show() end end diff --git a/kernel/fs.rv b/kernel/fs.rv index 5d72bc1..0c98c36 100644 --- a/kernel/fs.rv +++ b/kernel/fs.rv @@ -1,4 +1,4 @@ -# revelfs, a tiny fat style filesystem +# revelfs, a tiny fat style filesystem, now an object you talk to as fs:method() # # on disk layout for the 16 mib test disk (32768 sectors): # sector 0 superblock (magic + total sectors) @@ -19,242 +19,252 @@ const NAME_MAX = 24 const MAGIC = 0x53465652 # "RVFS" little endian const END = 0xffff # end of chain marker in the fat +# pure index math, no state, so theyre plain helpers not methods +fn dir_lba(i) DIR_LBA + (i shr 4) +fn dir_off(i) (i band 15) * 32 + # 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 } +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 + fat_get = fn(self, block) do + disk_read(FAT_LBA + (block shr 8), self.fat) + mem_read(self.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 + fat_set = fn(self, block, val) do + const lba = FAT_LBA + (block shr 8) + disk_read(lba, self.fat) + mem_write(self.fat + (block band 255) * 2, val, 2) + disk_write(lba, self.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 + # first free data block, or 0 if full. scans a whole fat sector per read so this + # isnt snails pace + fat_alloc = fn(self) do + let block = DATA_LBA + while block < self.total do + disk_read(FAT_LBA + (block shr 8), self.fat) + let off = block band 255 + while (off < 256 and block < self.total) do + if mem_read(self.fat + off * 2, 2) == 0 return block + off += 1 + block += 1 + end 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 + 0 + end, -fn dir_lba(i) DIR_LBA + (i shr 4) -fn dir_off(i) (i band 15) * 32 + free_chain = fn(self, first) do + let block = first + while (block != 0 and block != END) do + const nxt = self:fat_get(block) + self:fat_set(block, 0) + block = nxt + end + end, -# 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 - out -end + # dir entry is 32 bytes: + # +0 used + # +1 name len + # +2 first block + # +4 size + # +8 name + name_at = fn(self, 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 + name_matches = fn(self, base, target) do + const len = mem_read(base + 1, 1) + if len != target:len() return :false + return self:name_at(base, len) == target + 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 + find = fn(self, name) do + for i in 0..DIR_ENTRIES do + disk_read(dir_lba(i), self.buf) + const base = self.buf + dir_off(i) + if mem_read(base, 1) == 1 and self: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 + dir_alloc = fn(self) do + for i in 0..DIR_ENTRIES do + disk_read(dir_lba(i), self.buf) + if mem_read(self.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 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 + mount = fn(self) do + self.buf = frame_alloc() + self.fat = frame_alloc() + self.total = disk_sectors() + if self.total == 0 do self.ready = :false return :nil end + disk_read(SB_LBA, self.buf) + self.ready = if mem_read(self.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 + # ready is a field so the gate cant be called `ready`, this complains if theres no fs + ensure = fn(self) do + if self.ready return :true + puts("no filesystem, run mkfs first\n") + :false + end, -fn fs_format() do - 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) - 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 + format = fn(self) do + if disk_sectors() == 0 do puts("mkfs: no disk\n") return :nil end + mem_zero(self.buf, BLOCK) + mem_write(self.buf, MAGIC, 4) + mem_write(self.buf + 4, self.total, 4) + disk_write(SB_LBA, self.buf) + mem_zero(self.buf, BLOCK) + for s in 0..DIR_SECTORS do disk_write(DIR_LBA + s, self.buf) end + for s in 0..FAT_SECTORS do disk_write(FAT_LBA + s, self.buf) end + self.ready = :true + puts("formatted #{self.total} sectors, #{(self.total shr 1)} KiB\n") + 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) - 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 0 end - end + # silent core of write, returns 1 ok / 0 fail + store = fn(self, name, text) do + if self:ensure() == :false return 0 + if name:len() > NAME_MAX do puts("name too long (max #{NAME_MAX})\n") return 0 end + let idx = self:find(name) + if idx >= 0 do + disk_read(dir_lba(idx), self.buf) + self:free_chain(mem_read(self.buf + dir_off(idx) + 2, 2)) + end + else do + idx = self:dir_alloc() + if idx < 0 do puts("directory full\n") return 0 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 0 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 + const size = text:len() + let first = 0 + let prev = 0 + let written = 0 + while written < size do + const block = self:fat_alloc() + if block == 0 do puts("disk full\n") return 0 end + self:fat_set(block, END) # reserve + if first == 0 do first = block end + else do self:fat_set(prev, block) end + mem_zero(self.buf, BLOCK) + let k = 0 + while (k < BLOCK and written < size) do + mem_write(self.buf + k, text[written]:ascii(), 1) + k += 1 + written += 1 + end + disk_write(block, self.buf) + prev = block 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) - return 1 -end + disk_read(dir_lba(idx), self.buf) + const base = self.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), self.buf) + 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 + # write a file, gee i wonder what a function called `write` does + write = fn(self, name, text) do + if self: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 + size = fn(self, name) do + const idx = self:find(name) + if idx < 0 return -1 + disk_read(dir_lba(idx), self.buf) + return mem_read(self.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 + # read up to len bytes from offset, returned as a string (shorter at eof) + read_bytes = fn(self, name, offset, len) do + const idx = self:find(name) + if idx < 0 return "" + disk_read(dir_lba(idx), self.buf) + const base = self.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 = self:fat_get(block) + if (pos + BLOCK > offset and pos < stop) do + disk_read(block, self.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(self.buf + k, 1)) end + k += 1 + end end + pos += BLOCK + block = nxt end - pos += BLOCK - block = nxt - end - return out -end + return out + end, -fn fs_cat(name) do - 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) - 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 + cat = fn(self, name) do + if self:ensure() == :false return :nil + const idx = self:find(name) + if idx < 0 do puts("cat: #{name}: no such file\n") return :nil end + disk_read(dir_lba(idx), self.buf) + const base = self.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 = self:fat_get(block) # grab next before disk_read reuses self.buf + disk_read(block, self.buf) + const chunk = if remaining > BLOCK BLOCK else remaining + for k in 0..chunk do out = out ~ string.of_ascii(mem_read(self.buf + k, 1)) end + remaining -= chunk + block = nxt + end + puts(out) + puts("\n") + end, -fn fs_ls() do - if fs_ready() == :false return :nil - 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 + ls = fn(self) do + if self:ensure() == :false return :nil + let count = 0 + for i in 0..DIR_ENTRIES do + disk_read(dir_lba(i), self.buf) + const base = self.buf + dir_off(i) + if mem_read(base, 1) == 1 do + puts(" #{self:name_at(base, mem_read(base + 1, 1))} #{mem_read(base + 4, 4)} bytes\n") + count += 1 + end end - end - if count == 0 puts(" (empty)\n") -end + if count == 0 puts(" (empty)\n") + end, + + rm = fn(self, name) do + if self:ensure() == :false return :nil + const idx = self:find(name) + if idx < 0 do puts("rm: #{name}: no such file\n") return :nil end + disk_read(dir_lba(idx), self.buf) + const base = self.buf + dir_off(idx) + self:free_chain(mem_read(base + 2, 2)) + disk_read(dir_lba(idx), self.buf) + mem_write(self.buf + dir_off(idx), 0, 1) + disk_write(dir_lba(idx), self.buf) + puts("removed #{name}\n") + end, +} -fn fs_rm(name) do - 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) - 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 +global fs_store = fn(name, text) fs:store(name, text) +global fs_size = fn(name) fs:size(name) +global fs_read_bytes = fn(name, offset, len) fs:read_bytes(name, offset, len) diff --git a/kernel/main.rv b/kernel/main.rv index 24852fe..4a6fb58 100644 --- a/kernel/main.rv +++ b/kernel/main.rv @@ -8,15 +8,12 @@ pic_unmask(1) # keyboard (irq1) cmd_clear() dump_memmap() -fs_init() -if disk_sectors() == 0 puts("no disk attached\n") -else if fs.ready puts("mounted revelfs on disk\n") -else do - # blank disk, first boot after a clone. format it and drop the default files on +fs:mount() +if (disk_sectors() != 0 and fs.ready == :false) do puts("blank disk, formatting + seeding default files\n") - fs_format() + fs:format() seed_fs() end -puts("revel shell, live in revo. type `help`\n% ") +welcome() flush_console() diff --git a/kernel/proc.rv b/kernel/proc.rv index 3303bc1..f76c511 100644 --- a/kernel/proc.rv +++ b/kernel/proc.rv @@ -43,12 +43,12 @@ 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") + 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 + 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 index 6c0a73f..72f0efd 100644 --- a/kernel/seed.rv +++ b/kernel/seed.rv @@ -1,5 +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") + 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 35eac37..f72387a 100644 --- a/kernel/shell.rv +++ b/kernel/shell.rv @@ -1,42 +1,80 @@ # this is just a simple prompt to test that shit is working + +fn pad(s, width) do + let out = s + while out:len() < width do out = out ~ " " end + return out +end + +fn help_row(cmd, desc) puts(" #{pad(cmd, 20)}#{desc}\n") + fn cmd_help() do - 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") + puts("\n shell\n") + help_row("help", "show this") + help_row("clear", "wipe the screen") + help_row("time", "utc clock") + help_row("mem", "free / total ram") + help_row("uptime", "seconds since boot") + help_row("ps", "running processes") + help_row("zoom in|out", "scale the font") + help_row("echo <text>", "print it back") + puts("\n files\n") + help_row("ls", "list files") + help_row("cat <name>", "print a file") + help_row("write <name> <text>", "create or overwrite") + help_row("rm <name>", "delete a file") + help_row("mkfs", "format the disk") + puts("\n programs\n") + help_row("run", "list programs") + help_row("run <name>", "") + puts("\n") +end + +fn welcome() do + let fsline = "no filesystem" + if fs.ready do fsline = "revelfs mounted" end + puts("\n") + puts(" a tiny os whose brain lives in revo, zig only touches the metal\n") + puts(" #{(mem_total() shr 20)} MiB ram | #{fsline} | #{prog_count()} programs\n") + puts(" type help to see what it does\n\n% ") end 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 + let quoted = :false + let begin = start + if (begin < line:len() and line[begin] == "\"") do begin += 1 quoted = :true end + # scan to the end of the name, a closing quote or a space depending on how it opened + let i = begin + if quoted do + while (i < line:len() and line[i] != "\"") do i += 1 end end else do - while (i < line:len() and line[i]:ascii() != 32) do name = name ~ line[i] i += 1 end + while (i < line:len() and line[i] != " ") do i += 1 end end - if (i < line:len() and line[i]:ascii() == 32) do i += 1 end - { name = name, rest = i } + const name = line[begin..i] + # step past the closing quote and the separating space so rest points at the text + let rest = i + if (quoted and rest < line:len()) do rest += 1 end + if (rest < line:len() and line[rest] == " ") do rest += 1 end + { name = name, rest = rest } 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) + fs:write(a.name, line[a.rest..]) 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) + 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) + fs:rm(a.name) end fn zoom_by(delta) do @@ -45,7 +83,7 @@ fn zoom_by(delta) do if scale > 6 do scale = 6 end con.scale = scale cmd_clear() - puts("zoom " ~ scale ~ "x\n") + puts("zoom #{scale}x\n") end fn cmd_zoom(words) do @@ -55,32 +93,21 @@ fn cmd_zoom(words) do else do puts("usage: zoom in | zoom out\n") end end -fn cmd_clear() do - fill_rect(0, 0, fb_width(), fb_height(), CON_BG) - con.col = 0 - con.row = 0 -end - -fn cmd_bars() do - const bars = { 0xff5555, 0xffaa00, 0xffff55, 0x55ff55, 0x55ffff, 0x5599ff, 0xaa55ff, 0xffffff } - const bw = fb_width() / 8 - for i in 0..8 do fill_rect(i * bw, 0, bw, 40, bars[i]) end - puts("painted 8 bars from revo\n") -end +fn cmd_clear() con:clear() fn bcd(byte) (byte shr 4) * 10 + (byte band 15) fn rtc(reg) do outb(0x70, reg) inb(0x71) end -fn cmd_time() puts("utc " ~ bcd(rtc(4)) ~ ":" ~ bcd(rtc(2)) ~ ":" ~ bcd(rtc(0)) ~ "\n") +fn cmd_time() puts("utc #{bcd(rtc(4))}:#{bcd(rtc(2))}:#{bcd(rtc(0))}\n") # 0x prefixed 16 digit hex fn hex(n) do const digits = "0123456789abcdef" let out = "" for i in 0..16 do out = out ~ digits[(n shr ((15 - i) * 4)) band 15] end - "0x" ~ out + "0x#{out}" end fn mem_type_name(kind) do @@ -99,21 +126,19 @@ end fn dump_memmap() do puts("memory map:\n") for i in 0..memmap_count() do - puts(" " ~ hex(memmap_base(i)) ~ " len=" ~ hex(memmap_len(i)) ~ " type=" ~ mem_type_name(memmap_kind(i)) ~ "\n") + puts(" #{hex(memmap_base(i))} len=#{hex(memmap_len(i))} type=#{mem_type_name(memmap_kind(i))}\n") end - puts("usable: " ~ (mem_total() shr 20) ~ " MiB, free: " ~ (mem_free() shr 20) ~ " MiB\n") + puts("usable: #{(mem_total() shr 20)} MiB, free: #{(mem_free() shr 20)} MiB\n") end -fn cmd_mem() puts((mem_free() shr 20) ~ " MiB free / " ~ (mem_total() shr 20) ~ " MiB usable\n") -fn cmd_uptime() puts(math.floor(uptime() / 100) ~ "s (" ~ uptime() ~ " ticks)\n") -fn cmd_ps() puts(proc_count() ~ " process(es) running\n") +fn cmd_mem() puts("#{(mem_free() shr 20)} MiB free / #{(mem_total() shr 20)} MiB usable\n") +fn cmd_uptime() puts("#{math.floor(uptime() / 100)}s (#{uptime()} ticks)\n") +fn cmd_ps() puts("#{proc_count()} process(es) running\n") 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 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 @@ -123,18 +148,17 @@ fn run(line) do match cmd | "help" => cmd_help() | "clear" => cmd_clear() - | "bars" => cmd_bars() | "time" => cmd_time() | "mem" => cmd_mem() | "uptime" => cmd_uptime() | "ps" => cmd_ps() | "zoom" => cmd_zoom(words) - | "mkfs" => fs_format() - | "ls" => fs_ls() + | "mkfs" => fs:format() + | "ls" => fs:ls() | "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") + | "echo" => if line:len() > 5 puts("#{line[5..]}\n") else puts("\n") + | _ => puts("unknown command: #{cmd}\n") puts("% ") end |
