hachem.wtf
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-09-25 18:08:17 +0200
committerhachem <im@hachem.wtf>2026-09-25 18:08:17 +0200
commitfd3f51eecaecc12806a573c38263df1de43ee8c2 (patch)
tree4bd43519ce87af6b791c7f86d8a273829a56786d
parent6593ddc56fc96df0a566cc3b1a3e9396e4f3b2c6 (diff)
feat: pit timer
-rw-r--r--boot/boot.zig6
-rw-r--r--boot/idt.zig6
-rw-r--r--boot/limine.zig6
-rw-r--r--boot/pmm.zig10
-rw-r--r--boot/timer.zig17
-rw-r--r--bridge/bridge.zig7
-rw-r--r--kernel/console.rv16
-rw-r--r--kernel/input.rv9
-rw-r--r--kernel/main.rv6
9 files changed, 72 insertions, 11 deletions
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()