From fd3f51eecaecc12806a573c38263df1de43ee8c2 Mon Sep 17 00:00:00 2001 From: hachem Date: Fri, 25 Sep 2026 18:08:17 +0200 Subject: feat: pit timer --- boot/boot.zig | 6 ++++++ boot/idt.zig | 6 +++++- boot/limine.zig | 6 +++--- boot/pmm.zig | 10 +++++----- boot/timer.zig | 17 +++++++++++++++++ bridge/bridge.zig | 7 +++++++ kernel/console.rv | 16 +++++++++++++++- kernel/input.rv | 9 +++++++++ kernel/main.rv | 6 +++++- 9 files changed, 72 insertions(+), 11 deletions(-) create mode 100644 boot/timer.zig diff --git a/boot/boot.zig b/boot/boot.zig index 108afc8..8f8c7fe 100644 --- a/boot/boot.zig +++ b/boot/boot.zig @@ -10,6 +10,7 @@ const vmm = @import("vmm.zig"); const user = @import("user.zig"); const elf = @import("elf.zig"); const keyboard = @import("keyboard.zig"); +const timer = @import("timer.zig"); const bridge = @import("bridge"); // We need a panic handler otherwise zig won't be happy, @@ -166,11 +167,16 @@ export fn _start() callconv(.c) noreturn { // the cli/pop/sti-hlt bullshit closes the lost-wakeup race (a key // arriving between "queue empty" and hlt would otherwise sit until // the next keypress) + var last_ticks: u64 = 0; while (true) { asm volatile ("cli"); if (keyboard.pop()) |sc| { asm volatile ("sti"); bridge.onKey(sc); + } else if (timer.now() != last_ticks) { + last_ticks = timer.now(); + asm volatile ("sti"); + bridge.onTick(last_ticks); } else { asm volatile ("sti; hlt"); } diff --git a/boot/idt.zig b/boot/idt.zig index fda5b9d..19afa6b 100644 --- a/boot/idt.zig +++ b/boot/idt.zig @@ -8,6 +8,7 @@ const gdt = @import("gdt.zig"); const serial = @import("serial.zig"); const pic = @import("pic.zig"); const keyboard = @import("keyboard.zig"); +const timer = @import("timer.zig"); // 64bit interrupt gate const Gate = packed struct { @@ -219,6 +220,7 @@ export fn irqCommon() callconv(.naked) void { export fn irqDispatch(frame: *Frame) callconv(.c) void { switch (frame.vector) { + pic.MASTER_OFFSET + 0 => timer.onIrq(), // IRQ0: PIT pic.MASTER_OFFSET + 1 => keyboard.onIrq(), // IRQ1: keyboard else => {}, } @@ -242,8 +244,10 @@ pub fn setGate(vector: u8, handler: u64, dpl: u2) void { // fill the exception vectors and load the IDT pub fn init() void { inline for (0..32) |v| setGate(v, @intFromPtr(&stub(v)), 0); + // IRQ0 (PIT) -> vector 0x20 // IRQ1 (keyboard) -> vector 0x21 - // the PIC is remapped separately in boot. + // the PIC is remapped separately in boot + setGate(pic.MASTER_OFFSET + 0, @intFromPtr(&irqStub(pic.MASTER_OFFSET + 0)), 0); setGate(pic.MASTER_OFFSET + 1, @intFromPtr(&irqStub(pic.MASTER_OFFSET + 1)), 0); idtr = .{ .limit = @sizeOf(@TypeOf(idt)) - 1, .base = @intFromPtr(&idt) }; asm volatile ("lidt (%[idtr])" diff --git a/boot/limine.zig b/boot/limine.zig index f1ed158..3d18b31 100644 --- a/boot/limine.zig +++ b/boot/limine.zig @@ -1,5 +1,5 @@ -// Limine scans the loaded image for these magic numbers, BaseRevision is -// a handshake, essentially we ask limine for rev 3, and if it supports it, +// Limine scans the loaded image for these magic numbers, BaseRevision is +// a handshake, essentially we ask limine for rev 3, and if it supports it, // limine zeros it out pub const BaseRevision = extern struct { magic0: u64 = 0xf9562b2d5c95a6c8, @@ -11,7 +11,7 @@ pub const BaseRevision = extern struct { } }; -// bracketing markers, they are pretty optional but when we add proper +// bracketing markers, they are pretty optional but when we add proper // limine requests, having these here comes handy pub const RequestsStartMarker = extern struct { m0: u64 = 0xf6b8f4b39de7d1ae, diff --git a/boot/pmm.zig b/boot/pmm.zig index 71d132e..0c4bdda 100644 --- a/boot/pmm.zig +++ b/boot/pmm.zig @@ -20,10 +20,10 @@ var hhdm_offset: u64 = 0; // reached through the HHDM, so it's just a normal slice we write through. var bitmap: []u8 = &[_]u8{}; -var total_frames: usize = 0; // frames the bitmap can index (up to highest usable end) +var total_frames: usize = 0; // frames the bitmap can index (up to highest usable end) var usable_frames: usize = 0; // frames that started free -var free_frames: usize = 0; // frames currently free -var next_hint: usize = 0; // where to start the next alloc scan +var free_frames: usize = 0; // frames currently free +var next_hint: usize = 0; // where to start the next alloc scan pub fn physToVirt(phys: u64) u64 { return phys + hhdm_offset; @@ -95,7 +95,7 @@ pub fn init(hhdm: *const limine.HhdmResponse, memmap: *const limine.MemoryMapRes // // WARNING: never hand out a physical-null address // - // unlike marking a region free, here we round the END UP so a partial + // unlike marking a region free, here we round the END UP so a partial // last frame the bitmap spills into still gets reserved { const start = storage_base / PAGE_SIZE; @@ -136,7 +136,7 @@ pub fn alloc() ?u64 { // hand out n contiguous physical frames cause the heap needs one flat buffer, and a // single usable region is contiguous in physical space, so this just finds a -// run of n free bits. +// run of n free bits. // NOTE: returns the base physical address of the run pub fn allocContig(n: usize) ?u64 { if (n == 0) return null; diff --git a/boot/timer.zig b/boot/timer.zig new file mode 100644 index 0000000..4569d6a --- /dev/null +++ b/boot/timer.zig @@ -0,0 +1,17 @@ +// PIT channel 0 tick counter +// configuring the PIT itself (outb to 0x43/0x40) is done in revo (kernel +// `pit_init`), since it's just port writes the VM can drive + +var ticks: u64 = 0; + +// called from the IRQ0 handler +pub fn onIrq() void { + const t: *volatile u64 = &ticks; + t.* +%= 1; +} + +// called from the event loop +pub fn now() u64 { + const t: *volatile u64 = &ticks; + return t.*; +} diff --git a/bridge/bridge.zig b/bridge/bridge.zig index 8786f20..a8a2243 100644 --- a/bridge/bridge.zig +++ b/bridge/bridge.zig @@ -496,3 +496,10 @@ fn flushConsole(vm: *revo.VM) void { if (f.asFunction() == null) return; _ = vm.callFunctionParts(f, null, &.{}, null) catch {}; } + +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; + _ = vm.callFunctionParts(cb, null, &[_]revo.Value{revo.Value.new.num(ticks)}, null) catch {}; +} diff --git a/kernel/console.rv b/kernel/console.rv index a894141..a949cd2 100644 --- a/kernel/console.rv +++ b/kernel/console.rv @@ -3,7 +3,7 @@ const FH = 16 # glyph height in pixels (8x16 VGA bitmap font) const FG = 0xd0d0d0 const CON_BG = 0x101018 -global con = { col = 0, row = 0, scale = 1 } +global con = { col = 0, row = 0, scale = 1, cur_on = :true, blink = 0 } # cell size in pixels at the current zoom. named cell_* (not cw/ch) so they don't # collide with draw_glyph's `ch` character parameter. @@ -65,5 +65,19 @@ global flush_console = fn() do con_putc(string.of_ascii(c)) c = serial_next() end + # after any output the cursor is solid at the new head restart the blink phase + con.cur_on = :true + con.blink = 0 cursor_paint(CUR) end + +# the PIT fires ~100x/sec +# blink the cursor every 50 ticks (~2 Hz) +global on_tick = fn(t) do + con.blink += 1 + if con.blink >= 50 do + con.blink = 0 + con.cur_on = not con.cur_on + cursor_paint(if con.cur_on CUR else CON_BG) + end +end diff --git a/kernel/input.rv b/kernel/input.rv index fde962d..d069521 100644 --- a/kernel/input.rv +++ b/kernel/input.rv @@ -14,6 +14,15 @@ fn pic_remap() do end fn pic_unmask(line) outb(0x21, inb(0x21) band (255 bxor (1 shl line))) +# 8253/8254 PIT channel 0, mode 3 (square wave) +# divisor 11931 -> ~100 Hz (1193182 / 11931) +# the ISR just counts ticks +fn pit_init() do + outb(0x43, 0x36) + outb(0x40, 11931 band 0xff) + outb(0x40, (11931 shr 8) band 0xff) +end + # she stroke my keys till i handle # no fr this is just a key stroke handler global sh = { line = "", shift = :false } diff --git a/kernel/main.rv b/kernel/main.rv index d3f1b23..35413ef 100644 --- a/kernel/main.rv +++ b/kernel/main.rv @@ -1,6 +1,10 @@ # kmain pic_remap() -pic_unmask(1) +pit_init() + +pic_unmask(0) # PIT (IRQ0) -> cursor blink / uptime +pic_unmask(1) # keyboard (IRQ1) + cmd_clear() puts("revel shell, live in revo. type `help`\n> ") flush_console() -- cgit v1.3