phaser-ags / zig /kernel /trap.zig
SNAPKITTYWEST's picture
October 2026 main drop: mirror from GitHub
829b19c verified
Raw History Blame Contribute Delete
12.5 kB
//! SPDX-License-Identifier: AGPL-3.0-or-later
//! Copyright 2025 Ahmad Ali Parr / SnapKitty οΏ½ https://github.com/SNAPKITTYWEST/phaser-ags
//! ═══════════════════════════════════════════════════════════════════
//! PHASER AGS β€” Trap framework (complete, no stubs)
//!
//! Full context save/restore dispatch. Integrates with process.zig
//! for preemptive context switching on timer tick.
//! ═══════════════════════════════════════════════════════════════════
const std = @import("std");
const driver = @import("driver.zig");
// ─── Trap frame layout (matches start.S offsets exactly) ─────────
pub const TrapFrame = extern struct {
prev_ctx: u64, // 0(sp) β€” old sp before csrrw swap
ra: u64, // 8 x1
t0: u64, // 16 x5
t1: u64, // 24 x6
t2: u64, // 32 x7
a0: u64, // 40 x10
a1: u64, // 48 x11
a2: u64, // 56 x12
a3: u64, // 64 x13
a4: u64, // 72 x14
a5: u64, // 80 x15
a6: u64, // 88 x16
a7: u64, // 96 x17
s0: u64, // 104 x8
s1: u64, // 112 x9
s2: u64, // 120 x18
s3: u64, // 128 x19
s4: u64, // 136 x20
s5: u64, // 144 x21
s6: u64, // 152 x22
s7: u64, // 160 x23
s8: u64, // 168 x24
s9: u64, // 176 x25
s10: u64, // 184 x26
s11: u64, // 192 x27
t3: u64, // 200 x28
t4: u64, // 208 x29
t5: u64, // 216 x30
t6: u64, // 224 x31
mepc: u64, // 232
mstatus: u64, // 240
mcause: u64, // 248
mtval: u64, // 256
};
comptime {
std.debug.assert(@sizeOf(TrapFrame) == 264);
}
// ─── Exception and interrupt codes ───────────────────────────────
pub const Exception = enum(u64) {
instr_misaligned = 0,
instr_access = 1,
illegal_instr = 2,
breakpoint = 3,
load_misaligned = 4,
load_access = 5,
store_misaligned = 6,
store_access = 7,
ecall_u = 8,
ecall_s = 9,
ecall_m = 11,
instr_page_fault = 12,
load_page_fault = 13,
store_page_fault = 15,
_,
};
pub const Interrupt = enum(u64) {
s_software = 1,
m_software = 3,
s_timer = 5,
m_timer = 7,
s_external = 9,
m_external = 11,
_,
};
// ─── Handler registration ────────────────────────────────────────
const ExceptionHandler = *const fn (*TrapFrame, u64) void;
const InterruptHandler = *const fn (*TrapFrame, u64) void;
var exc_handlers: [16]?ExceptionHandler = .{null} ** 16;
var irq_handlers: [12]?InterruptHandler = .{null} ** 12;
pub fn registerException(code: u64, handler: ExceptionHandler) void {
if (code < 16) exc_handlers[code] = handler;
}
pub fn registerInterrupt(code: u64, handler: InterruptHandler) void {
if (code > 0 and code < 12) irq_handlers[code] = handler;
}
// ─── Reschedule flag (set by timer, checked by dispatch) ─────────
var need_reschedule: bool = false;
pub fn requestReschedule() void {
need_reschedule = true;
}
// ─── Main dispatch β€” called from start.S ─────────────────────────
export fn trapDispatch(frame: *TrapFrame) *TrapFrame {
const cause = frame.mcause;
const is_irq = (cause >> 63) != 0;
const code = cause & 0x7FFF_FFFF_FFFF_FFFF;
if (is_irq) {
dispatchInterrupt(frame, code);
} else {
dispatchException(frame, code);
}
// ── Preemptive context switch ──
if (need_reschedule) {
need_reschedule = false;
return doContextSwitch(frame);
}
return frame;
}
// ─── Interrupt dispatch ──────────────────────────────────────────
fn dispatchInterrupt(frame: *TrapFrame, code: u64) void {
if (code < 12 and irq_handlers[code] != null) {
irq_handlers[code].?(frame, code);
return;
}
switch (@as(Interrupt, @enumFromInt(code))) {
.m_timer => defaultTimerHandler(frame),
.m_software => defaultSoftwareHandler(frame),
.m_external => defaultExternalHandler(frame),
else => {
driver.Uart.puts("UNHANDLED IRQ ");
driver.Uart.putHex(@intCast(code));
driver.Uart.putc('\n');
},
}
}
// ─── Exception dispatch ──────────────────────────────────────────
fn dispatchException(frame: *TrapFrame, code: u64) void {
if (code < 16 and exc_handlers[code] != null) {
exc_handlers[code].?(frame, code);
return;
}
switch (@as(Exception, @enumFromInt(code))) {
.ecall_m => handleEcall(frame),
.ecall_u => handleEcall(frame),
.illegal_instr => handleIllegal(frame),
.instr_page_fault,
.load_page_fault,
.store_page_fault => handlePageFault(frame),
.instr_misaligned,
.load_misaligned,
.store_misaligned => handleMisalign(frame),
.instr_access,
.load_access,
.store_access => handleAccessFault(frame),
.breakpoint => handleBreakpoint(frame),
else => {
driver.Uart.puts("UNHANDLED EXC ");
driver.Uart.putHex(@intCast(code));
driver.Uart.puts(" at ");
driver.Uart.putHex(@intCast(frame.mepc));
driver.Uart.putc('\n');
frame.mepc += 4;
},
}
}
// ─── Default interrupt handlers ──────────────────────────────────
fn defaultTimerHandler(frame: *TrapFrame) void {
// Acknowledge: CLINT mtimecmp already set by timer.zig
// Deactivate in MIP by clearing MTIP (done by writing mtimecmp)
_ = frame;
// Increment tick count
const timer_mod = @import("timer.zig");
timer_mod.ackTick();
}
fn defaultSoftwareHandler(frame: *TrapFrame) void {
_ = frame;
// Clear MSIP in CLINT
driver.regWrite(u32, 0x02000000, 0);
}
fn defaultExternalHandler(frame: *TrapFrame) void {
_ = frame;
// Claim interrupt from PLIC
const plic = @import("driver.zig").Plic;
const irq_id = plic.claim(0);
if (irq_id != 0) {
// Dispatch to registered external IRQ handlers
driver.dispatchExternalIrq(irq_id);
plic.complete(0, irq_id);
}
}
// ─── Exception handlers ──────────────────────────────────────────
fn handleEcall(frame: *TrapFrame) void {
const syscall_num: u64 = frame.a7;
const result = @import("syscall.zig").dispatch(syscall_num, frame);
frame.a0 = result;
frame.mepc += 4; // Skip the ecall instruction
}
fn handleIllegal(frame: *TrapFrame) void {
driver.Uart.puts("ILLEGAL INSTR at ");
driver.Uart.putHex(@intCast(frame.mepc));
driver.Uart.puts(" mtval=");
driver.Uart.putHex(@intCast(frame.mtval));
driver.Uart.putc('\n');
frame.mepc += 4;
}
fn handlePageFault(frame: *TrapFrame) void {
driver.Uart.puts("PAGE FAULT at ");
driver.Uart.putHex(@intCast(frame.mepc));
driver.Uart.puts(" addr=");
driver.Uart.putHex(@intCast(frame.mtval));
driver.Uart.putc('\n');
// Kill the offending process if one exists
const proc = @import("process.zig");
if (proc.getCurrent()) |p| {
driver.Uart.puts("Killing proc ");
driver.Uart.putHex(p.pid);
driver.Uart.putc('\n');
proc.kill(p.pid);
requestReschedule();
} else {
frame.mepc += 4;
}
}
fn handleMisalign(frame: *TrapFrame) void {
driver.Uart.puts("MISALIGNED at ");
driver.Uart.putHex(@intCast(frame.mepc));
driver.Uart.puts(" addr=");
driver.Uart.putHex(@intCast(frame.mtval));
driver.Uart.putc('\n');
frame.mepc += 4;
}
fn handleAccessFault(frame: *TrapFrame) void {
driver.Uart.puts("ACCESS FAULT at ");
driver.Uart.putHex(@intCast(frame.mepc));
driver.Uart.puts(" addr=");
driver.Uart.putHex(@intCast(frame.mtval));
driver.Uart.putc('\n');
const proc = @import("process.zig");
if (proc.getCurrent()) |p| {
proc.kill(p.pid);
requestReschedule();
} else {
frame.mepc += 4;
}
}
fn handleBreakpoint(frame: *TrapFrame) void {
driver.Uart.puts("BREAKPOINT at ");
driver.Uart.putHex(@intCast(frame.mepc));
driver.Uart.putc('\n');
frame.mepc += 4;
}
// ─── Context switch ──────────────────────────────────────────────
fn doContextSwitch(frame: *TrapFrame) *TrapFrame {
const proc = @import("process.zig");
// Save current process state
if (proc.getCurrent()) |cur| {
cur.ksp = @intFromPtr(frame);
if (cur.state == .running) {
cur.state = .runnable;
}
}
// Pick next process
const next = proc.schedule() orelse {
// Nothing to run β€” return current frame (idle)
return frame;
};
// Switch address space if page tables differ
const cur = proc.getCurrent();
if (cur == null or cur.?.page_root != next.page_root) {
@import("memory.zig").switchToPageTable(next.page_root);
}
next.state = .running;
proc.setCurrentPid(next.pid);
// Return the new process's saved trap frame
return @ptrFromInt(next.ksp);
}
// ─── Install default handlers at boot ────────────────────────────
pub fn initDefaults() void {
// Register all exception handlers
registerException(@intFromEnum(Exception.ecall_m), handleEcall);
registerException(@intFromEnum(Exception.ecall_u), handleEcall);
registerException(@intFromEnum(Exception.illegal_instr), handleIllegal);
registerException(@intFromEnum(Exception.instr_page_fault), handlePageFault);
registerException(@intFromEnum(Exception.load_page_fault), handlePageFault);
registerException(@intFromEnum(Exception.store_page_fault), handlePageFault);
registerException(@intFromEnum(Exception.instr_misaligned), handleMisalign);
registerException(@intFromEnum(Exception.load_misaligned), handleMisalign);
registerException(@intFromEnum(Exception.store_misaligned), handleMisalign);
registerException(@intFromEnum(Exception.instr_access), handleAccessFault);
registerException(@intFromEnum(Exception.load_access), handleAccessFault);
registerException(@intFromEnum(Exception.store_access), handleAccessFault);
registerException(@intFromEnum(Exception.breakpoint), handleBreakpoint);
// Timer interrupt handler
registerInterrupt(@intFromEnum(Interrupt.m_timer), handleTimerIrq);
registerInterrupt(@intFromEnum(Interrupt.m_software), handleSoftwareIrq);
registerInterrupt(@intFromEnum(Interrupt.m_external), handleExternalIrq);
}
fn handleTimerIrq(frame: *TrapFrame, code: u64) void {
_ = code;
const timer_mod = @import("timer.zig");
timer_mod.ackTick();
timer_mod.advanceCompare();
// Request reschedule on every tick
requestReschedule();
}
fn handleSoftwareIrq(frame: *TrapFrame, code: u64) void {
_ = frame;
_ = code;
// Clear MSIP
driver.regWrite(u32, 0x02000000, 0);
}
fn handleExternalIrq(frame: *TrapFrame, code: u64) void {
_ = frame;
_ = code;
const plic = driver.Plic;
const irq_id = plic.claim(0);
if (irq_id != 0) {
driver.dispatchExternalIrq(irq_id);
plic.complete(0, irq_id);
}
}