commit 56d9a1f8783e77f251af61567b15326f31fa6eb3
parent 37fc93e452e0bc8c87a9b7f71e8bef3ce1e0df60
Author: Sylvia Ivory <git@sivory.net>
Date: Tue, 17 Feb 2026 21:03:13 -0800
Update exception code
Diffstat:
8 files changed, 245 insertions(+), 134 deletions(-)
diff --git a/pi/devices/mini-uart.zig b/pi/devices/mini-uart.zig
@@ -232,9 +232,8 @@ pub fn get_rx_written() usize {
return rx_writer_written;
}
-noinline fn uart_handler(pc: usize, registers: *interrupts.Registers) void {
- _ = pc;
- _ = registers;
+noinline fn uart_handler(regs: interrupts.Registers) void {
+ _ = regs;
mem.barrier(.Write);
defer mem.barrier(.Write);
diff --git a/pi/devices/timer.zig b/pi/devices/timer.zig
@@ -50,11 +50,11 @@ fn default_control() Control {
return @bitCast(@as(u32, 0));
}
-fn empty(pc: usize) void {
- _ = pc;
+fn empty(regs: interrupts.Registers) void {
+ _ = regs;
}
-var tick_fn: *const fn (usize) void = empty;
+var tick_fn: *const fn (interrupts.Registers) void = empty;
pub fn initialize(pre_scale: PreScale, cycles: u32) void {
mem.barrier(.Write);
@@ -78,7 +78,7 @@ var previous_clock: u64 = 0;
var period: u64 = 0;
var period_sum: u64 = 0;
-noinline fn timer_handler(pc: usize) void {
+noinline fn timer_handler(reg: interrupts.Registers) void {
mem.barrier(.Write); // Sync
if (!interrupts.pending_basic(.Timer)) return;
@@ -94,12 +94,12 @@ noinline fn timer_handler(pc: usize) void {
period_sum += period;
previous_clock = current_clock;
- tick_fn(pc);
+ tick_fn(reg);
mem.barrier(.Write); // Sync before restoring
}
-pub inline fn set_tick(tick: *const fn (usize) void) void {
+pub inline fn set_tick(tick: *const fn (interrupts.Registers) void) void {
tick_fn = tick;
}
diff --git a/pi/interrupts.zig b/pi/interrupts.zig
@@ -1,5 +1,6 @@
const mem = @import("./mem.zig");
const pi = @import("./root.zig");
+const switching = @import("./switching.zig");
const BASE_ADDRESS: usize = mem.BASE_ADDRESS + 0xB200;
@@ -94,18 +95,17 @@ pub inline fn pending_peripheral_interrupt(i: PeripheralsInterrupt) bool {
}
}
-fn empty(pc: u32, registers: *Registers) void {
- _ = pc;
- _ = registers;
+fn empty(regs: Registers) void {
+ _ = regs;
}
-var reset_handler: *const fn (u32, *Registers) void = empty;
-var undefined_instruction_handler: *const fn (u32, *Registers) void = empty;
-var software_interrupt_handler: *const fn (u32, *Registers) void = empty;
-var prefetch_abort_handler: *const fn (u32, *Registers) void = empty;
-var data_abort_handler: *const fn (u32, *Registers) void = empty;
-var interrupt_handler: *const fn (u32, *Registers) void = empty;
-var fast_interrupt_handler: *const fn (u32, *Registers) void = empty;
+var reset_handler: *const fn (Registers) void = empty;
+var undefined_instruction_handler: *const fn (Registers) void = empty;
+var software_interrupt_handler: *const fn (Registers) void = empty;
+var prefetch_abort_handler: *const fn (Registers) void = empty;
+var data_abort_handler: *const fn (Registers) void = empty;
+var interrupt_handler: *const fn (Registers) void = empty;
+var fast_interrupt_handler: *const fn (Registers) void = empty;
pub const Registers = struct {
gp: [13]usize,
@@ -115,69 +115,79 @@ pub const Registers = struct {
psr: pi.PSR,
};
-inline fn user_get_sp() u32 {
- var sp: u32 = undefined;
-
- asm volatile ("stm %[ptr], {sp}^"
+// For whatever godforsaken reason, inlining this function causes bad things to happen
+noinline fn user_get_sp_lr(sp: *u32, lr: *u32) void {
+ asm volatile (
+ \\ stm %[sp], {sp}^
+ \\ stm %[lr], {lr}^
:
- : [ptr] "r" (&sp),
+ : [sp] "r" (sp),
+ [lr] "r" (lr),
: .{ .memory = true });
-
- return sp;
}
-inline fn user_get_lr() u32 {
- var lr: u32 = undefined;
+noinline fn privileged_get_sp_lr(sp: *u32, lr: *u32) void {
+ const cpsr = pi.PSR.switch_current_mode(pi.PSR.get_s().mode);
+ mem.barrier(.Instruction);
- asm volatile ("stm %[ptr], {lr}^"
+ asm volatile (
+ \\ str sp, [%[sp]]
+ \\ str lr, [%[lr]]
:
- : [ptr] "r" (&lr),
+ : [sp] "r" (sp),
+ [lr] "r" (lr),
: .{ .memory = true });
- return lr;
+ cpsr.set_c();
+ mem.barrier(.Instruction);
}
-inline fn setup_reg(pc: u32, reg: *Registers) void {
- reg.pc = pc;
- reg.psr = pi.PSR.get_s();
- reg.lr = user_get_lr();
- reg.sp = user_get_sp();
+inline fn setup_reg(pc: u32, gp: *const [13]u32) Registers {
+ // We use a copy as if editing the registers was
+ // desired, using a restore_state would be better
+ var view: Registers = .{ .gp = undefined, .sp = undefined, .lr = undefined, .pc = pc, .psr = pi.PSR.get_s() };
+
+ @memcpy(&view.gp, gp);
+
+ if (view.psr.mode == .User or view.psr.mode == .System) {
+ user_get_sp_lr(&view.sp, &view.lr);
+ } else {
+ privileged_get_sp_lr(&view.sp, &view.lr);
+ }
+
+ return view;
}
-export fn reset_stub(pc: u32, reg: *Registers) void {
- setup_reg(pc, reg);
- reset_handler(pc, reg);
+export fn reset_stub(pc: u32, gp: *const [13]u32) void {
+ reset_handler(setup_reg(pc, gp));
}
-export fn undefined_instruction_stub(pc: u32, reg: *Registers) void {
- setup_reg(pc, reg);
- undefined_instruction_stub(pc, reg);
+export fn undefined_instruction_stub(pc: u32, gp: *const [13]u32) void {
+ undefined_instruction_handler(setup_reg(pc, gp));
}
-export fn software_interrupt_stub(pc: u32, reg: *Registers) void {
- setup_reg(pc, reg);
- software_interrupt_handler(pc, reg);
+export fn software_interrupt_stub(pc: u32, gp: *const [13]u32) void {
+ software_interrupt_handler(setup_reg(pc, gp));
}
-export fn prefetch_abort_stub(pc: u32, reg: *Registers) void {
- setup_reg(pc, reg);
- prefetch_abort_handler(pc, reg);
+export fn prefetch_abort_stub(pc: u32, gp: *const [13]u32) void {
+ prefetch_abort_handler(setup_reg(pc, gp));
}
-export fn data_abort_stub(pc: u32, reg: *Registers) void {
- setup_reg(pc, reg);
- data_abort_handler(pc, reg);
+export fn data_abort_stub(pc: u32, gp: *const [13]u32) void {
+ data_abort_handler(setup_reg(pc, gp));
}
// This interrupt is a bastard
-export fn interrupt() callconv(.{ .arm_interrupt = .{ .type = .irq } }) void {
- // setup_reg(pc, reg);
- interrupt_handler(0, @ptrFromInt(0x8));
+// export fn interrupt() callconv(.{ .arm_interrupt = .{ .type = .irq } }) void {
+export fn interrupt_stub(pc: u32, gp: *const [13]u32) void {
+ interrupt_handler(setup_reg(pc, gp));
}
+// TODO;
export fn fast_interrupt_stub(pc: u32, reg: *Registers) void {
- // setup_reg(pc, reg);
- fast_interrupt_handler(pc, reg);
+ _ = pc;
+ _ = reg;
}
fn create_trampoline(name: []const u8, offset: []const u8, ret: []const u8) []const u8 {
@@ -185,12 +195,12 @@ fn create_trampoline(name: []const u8, offset: []const u8, ret: []const u8) []co
".global " ++ name ++ "\n" ++
".type " ++ name ++ ", %function\n" ++
name ++ ":\n" ++
- " push {r0-r14}\n" ++
+ " push {r0-r12, lr}\n" ++
" sub lr, lr, #" ++ offset ++ "\n" ++
" mov r0, lr\n" ++
" mov r1, sp\n" ++
" blx " ++ name ++ "_stub\n" ++
- " pop {r0-r14}\n" ++ ret;
+ " pop {r0-r12, lr}\n" ++ ret;
}
comptime {
@@ -199,7 +209,7 @@ comptime {
asm (create_trampoline("software_interrupt", "4", "movs pc, lr"));
asm (create_trampoline("prefetch_abort", "4", "subs pc, lr, #4"));
asm (create_trampoline("data_abort", "8", "subs pc, lr, #8"));
- // asm (create_trampoline("interrupt", "4", "subs pc, lr, #4"));
+ asm (create_trampoline("interrupt", "4", "subs pc, lr, #4"));
asm (create_trampoline("fast_interrupt", "4", "subs pc, lr, #4"));
}
extern fn reset() void;
@@ -207,7 +217,7 @@ extern fn undefined_instruction() void;
extern fn software_interrupt() void;
extern fn prefetch_abort() void;
extern fn data_abort() void;
-// extern fn interrupt() void;
+extern fn interrupt() void;
extern fn fast_interrupt() void;
// https://leiradel.github.io/2019/02/09/Initialization.html
@@ -255,7 +265,7 @@ const ExceptionVector = enum {
FIQ,
};
-pub fn set_exception_handler(vector: ExceptionVector, handler: *const fn (u32, *Registers) void) void {
+pub fn set_exception_handler(vector: ExceptionVector, handler: *const fn (Registers) void) void {
switch (vector) {
.Reset => reset_handler = handler,
.UndefinedInstruction => undefined_instruction_handler = handler,
diff --git a/pi/psr.zig b/pi/psr.zig
@@ -86,15 +86,15 @@ pub const PSR = packed struct(u32) {
/// Set the CPSR
pub inline fn set_c(cpsr: *const @This()) void {
- return asm volatile ("MSR cpsr_cxsf, %[value]"
+ asm volatile ("MSR cpsr_cxsf, %[value]"
:
: [value] "r" (@as(u32, @bitCast(cpsr.*))),
: .{ .cpsr = true });
}
/// Set the SPSR
- pub inline fn set_s(spsr: *const @This()) PSR {
- return asm volatile ("MSR spsr_cxsf, %[value]"
+ pub inline fn set_s(spsr: *const @This()) void {
+ asm volatile ("MSR spsr_cxsf, %[value]"
:
: [value] "r" (@as(u32, @bitCast(spsr.*))),
: .{ .cpsr = true });
diff --git a/pi/root.zig b/pi/root.zig
@@ -4,6 +4,7 @@ pub const Scheduler = @import("scheduler.zig");
pub const PSR = @import("./psr.zig").PSR;
pub const interrupts = @import("./interrupts.zig");
+pub const switching = @import("./switching.zig");
pub const register = @import("./register.zig");
pub const journal = @import("./journal.zig");
pub const faults = @import("./faults.zig");
diff --git a/pi/switching.zig b/pi/switching.zig
@@ -0,0 +1,88 @@
+// Heavenly Guide,
+//
+// We come before you with a prayer for luck and success in our switching journey.
+// Grant us the opportunities and experiences that foster our spiritual and
+// emotional development.
+//
+// May we embrace challenges as opportunities for growth and self-improvement.
+// Lead us on a path of self-discovery and fulfillment, knowing that true success
+// lies in becoming the best versions of ourselves.
+//
+// In your guidance, we find switching.
+//
+// Amen.
+// TODO; write this in Holy C
+const interrupts = @import("./interrupts.zig");
+
+// TODO; maybe this should also generate the extern?
+pub fn mk_fn(name: []const u8, body: []const u8) []const u8 {
+ return "" ++
+ ".global " ++ name ++ "\n" ++
+ ".type " ++ name ++ ", %function\n" ++
+ name ++ ":\n" ++ body;
+}
+
+comptime {
+ // r0 - current registers
+ // r1 - new registers
+ // r2 - function
+ //
+ // Store current registers/PSR
+ // Call function
+ // Find God and Pray
+ // We modify PC to the return statement to
+ // allow returning back as if nothing happened
+ asm (mk_fn("switch_state_inner",
+ \\ stm r0, {r0-r14}
+ \\ str lr, [r0, #60]
+ \\ mrs r3, cpsr
+ \\ str r3, [r0, #64]
+ \\ // ldr r3, [r1, #64]
+ \\ // msr cpsr_cxsf, r3
+ \\ mov r0, r1
+ \\ blx r2
+ ));
+
+ // Just restore state, don't preserve current
+ // Treat the new registers as the stack
+ // Copy "stack" into registers (user)
+ // Collapse stack
+ // Treat as exception return
+ asm (mk_fn("restore_state_user",
+ \\ mov sp, r0
+ \\ ldm sp, {r0-r14}^
+ \\ add sp, sp, #60
+ \\ rfeia sp
+ ));
+
+ // Restore the PSR
+ // Flush prefetch
+ // Load all registers (including PC)
+ asm (mk_fn("restore_state_privileged",
+ \\ ldr r1, [r0, #64]
+ \\ msr cpsr_cxsf, r1
+ \\ mov r2, #0
+ \\ mcr p15, 0, r2, c7, c5, 4
+ \\ ldm r0, {r0-r15}
+ ));
+}
+
+extern fn switch_state_inner(old: *interrupts.Registers, new: *const interrupts.Registers, *const fn (*interrupts.Registers) callconv(.c) void) void;
+pub extern fn restore_state_user(state: *const interrupts.Registers) noreturn;
+pub extern fn restore_state_privileged(state: *const interrupts.Registers) noreturn;
+
+pub inline fn switch_state(old: *interrupts.Registers, new: *const interrupts.Registers) void {
+ if (new.psr.mode == .User) {
+ switch_state_inner(old, new, restore_state_user);
+ } else {
+ switch_state_inner(old, new, restore_state_privileged);
+ }
+}
+
+pub inline fn restore_state(state: *const interrupts.Registers) void {
+ if (state.psr.mode == .User) {
+ restore_state_user(state);
+ } else {
+ restore_state_privileged(state);
+ }
+}
diff --git a/programs/debug.zig b/programs/debug.zig
@@ -4,12 +4,13 @@ const interrupts = pi.interrupts;
const debug = pi.debug;
const uart = pi.devices.mini_uart;
const faults = pi.faults;
+const switching = pi.switching;
var scheduler_registers: interrupts.Registers = undefined;
-var scheduler_psr: pi.PSR = undefined;
+var last_registers: interrupts.Registers = undefined;
var count: usize = 0;
-fn dump_registers(regs: *interrupts.Registers) void {
+fn dump_registers(regs: *const interrupts.Registers) void {
uart.writer.print("Registers\n", .{}) catch {};
for (regs.gp, 0..) |r, i| {
uart.writer.print(" r{d} = 0x{X}\n", .{ i, r }) catch {};
@@ -19,30 +20,35 @@ fn dump_registers(regs: *interrupts.Registers) void {
uart.flush();
}
-fn single_step_handler(pc: u32, regs: *interrupts.Registers) void {
+fn single_step_handler(regs: interrupts.Registers) void {
count += 1;
- uart.writer.print("fault: 0x{X} @ 0x{X}: {d}\n", .{ pi.mem.get_u32(@ptrFromInt(pc)), pc, count }) catch {};
+ uart.writer.print("fault: 0x{X} @ 0x{X}: {d}\n", .{ pi.mem.get_u32(@ptrFromInt(regs.pc)), regs.pc, count }) catch {};
+ dump_registers(®s);
uart.flush();
debug.set_breakpoint(0, regs.pc, .IMVAMismatch);
uart.flush();
- restore_state_user(regs);
+ // My guess why this is necessary is since regs lives in the stack
+ // That will cause an explosion when the stack is switched
+ last_registers = regs;
+ switching.restore_state_user(&last_registers);
}
-fn watchpoint_handler(pc: u32, regs: *interrupts.Registers) void {
+fn watchpoint_handler(regs: interrupts.Registers) void {
count += 1;
- uart.writer.print("fault: 0x{X} @ 0x{X}: {d}\n", .{ pi.mem.get_u32(@ptrFromInt(pc)), pc, count }) catch {};
+ uart.writer.print("fault: 0x{X} @ 0x{X}: {d}\n", .{ pi.mem.get_u32(@ptrFromInt(regs.pc)), regs.pc, count }) catch {};
uart.writer.print(" -> accessed address 0x{X}\n", .{debug.WCR.get_address(0)}) catch {};
uart.flush();
- restore_state_user(regs);
+ last_registers = regs;
+ switching.restore_state(&last_registers);
}
-fn syscall_handler(pc: u32, regs: *interrupts.Registers) void {
- uart.writer.print("got syscall at level {s} @ 0x{X}!\n", .{ @tagName(pi.PSR.get_s().mode), pc }) catch {};
+fn syscall_handler(regs: interrupts.Registers) void {
+ uart.writer.print("got syscall at level {s} @ 0x{X}!\n", .{ @tagName(regs.psr.mode), regs.pc }) catch {};
uart.writer.print(" -> restoring to 0x{X}\n", .{regs.pc}) catch {};
const sys = regs.gp[0];
@@ -53,7 +59,7 @@ fn syscall_handler(pc: u32, regs: *interrupts.Registers) void {
uart.write_slice("exiting process\n");
uart.flush();
- restore_state_privileged(&scheduler_registers);
+ switching.restore_state(&scheduler_registers);
},
2 => {
uart.write_byte(@truncate(arg0));
@@ -63,7 +69,7 @@ fn syscall_handler(pc: u32, regs: *interrupts.Registers) void {
},
}
- restore_state_user(regs);
+ switching.restore_state(®s);
}
fn count_registers() callconv(.naked) void {
@@ -104,62 +110,6 @@ fn exit_trampoline() callconv(.naked) void {
);
}
-comptime {
- // r0 - current registers
- // r1 - new registers
- // r2 - function
- //
- // Store current registers/PSR
- // Call function
- // Find God and Pray
- // We modify PC to the return statement to
- // allow returning back as if nothing happened
- asm (
- \\ .global switch_state
- \\ .type switch_state, %function
- \\ switch_state:
- \\ stm r0, {r0-r14}
- \\ str lr, [r0, #60]
- \\ mrs r3, cpsr
- \\ str r3, [r0, #64]
- \\ msr cpsr_cxsf, r3
- \\ mov r0, r1
- \\ blx r2
- );
-
- // Just restore state, don't preserve current
- // Treat the new registers as the stack
- // Copy "stack" into registers (user)
- // Collapse stack
- // Use "return from exception"
- asm (
- \\ .global restore_state_user
- \\ .type restore_state_user, %function
- \\ restore_state_user:
- \\ mov sp, r0
- \\ ldm sp, {r0-r14}^
- \\ add sp, sp, #60
- \\ rfeia sp
- );
-
- // Restore the PSR
- // Flush prefetch
- // Load all registers (including PC)
- asm (
- \\ .global restore_state_privileged
- \\ .type restore_state_privileged, %function
- \\ restore_state_privileged:
- \\ ldr r1, [r0, #64]
- \\ msr cpsr_cxsf, r1
- \\ mov r1, #0
- \\ mcr p15, 0, r1, c7, c5, 4
- \\ ldm r0, {r0-r15}
- );
-}
-extern fn switch_state(*interrupts.Registers, *interrupts.Registers, *const fn (*interrupts.Registers) callconv(.c) void) void;
-extern fn restore_state_user(*interrupts.Registers) void;
-extern fn restore_state_privileged(*interrupts.Registers) void;
-
pub fn main() !void {
interrupts.disable_interrupts();
uart.set_tx_interrupts(false);
@@ -199,7 +149,7 @@ pub fn main() !void {
dump_registers(&new_registers);
try uart.writer.print("Testing stepping\n", .{});
- switch_state(&scheduler_registers, &new_registers, restore_state_user);
+ switching.switch_state(&scheduler_registers, &new_registers);
try uart.writer.print("Stepping success\n", .{});
try uart.writer.print("Testing watchpoint\n", .{});
@@ -213,7 +163,7 @@ pub fn main() !void {
debug.clear_breakpoint(0);
debug.set_watchpoint(0, 0x8, .Either);
count = 0;
- switch_state(&scheduler_registers, &new_registers, restore_state_user);
+ switching.switch_state(&scheduler_registers, &new_registers);
try uart.writer.print("Watchpoint success\n", .{});
debug.disable_monitor_mode();
diff --git a/programs/timer.zig b/programs/timer.zig
@@ -0,0 +1,63 @@
+// Interrupt sanity check (by spamming interrupts)
+const std = @import("std");
+const pi = @import("pi");
+
+const uart = pi.devices.mini_uart;
+const timer = pi.devices.timer;
+const interrupts = pi.interrupts;
+
+comptime {
+ asm (
+ \\ .global infinite_loop;
+ \\ .type infinite_loop, %function;
+ \\ infinite_loop:
+ \\ mov r0, 0
+ \\ mov r1, 1
+ \\ mov r2, 2
+ \\ mov r3, 3
+ \\ mov r4, 4
+ \\ mov r5, 5
+ \\ mov r6, 6
+ \\ mov r7, 7
+ \\ mov r8, 8
+ \\ mov r9, 9
+ \\ mov r10, 10
+ \\ mov r11, 11
+ \\ mov r12, 12
+ \\ cmp r0, r1
+ \\ bne infinite_loop
+ \\ bx lr
+ );
+}
+extern fn infinite_loop() void;
+
+noinline fn tick(regs: interrupts.Registers) void {
+ for (regs.gp, 0..regs.gp.len) |r, i| {
+ uart.print("r{d} = 0x{X}\n", .{ i, r });
+ }
+ uart.print("sp = 0x{X}\n", .{regs.sp});
+ uart.print("lr = 0x{X}\n", .{regs.lr});
+ uart.print("pc = 0x{X}\n", .{regs.pc});
+ uart.print("psr = {s}\n", .{@tagName(regs.psr.mode)});
+ uart.flush();
+}
+
+pub fn main() !void {
+ uart.print("testing timer\n", .{});
+ uart.print("sp = 0x{X}\n", .{pi.get_sp()});
+ uart.print("mode = {s}\n", .{@tagName(pi.PSR.get_c().mode)});
+ uart.flush();
+
+ timer.set_tick(tick);
+ uart.print("set tick\n", .{});
+
+ timer.initialize(.Sixteen, 0x256);
+ uart.print("timer initialized, entering loop\n", .{});
+ const sp = pi.get_sp();
+ pi.mem.barrier(.Compiler);
+ uart.print("initial sp = 0x{X}\n", .{sp});
+
+ infinite_loop();
+
+ uart.print("this is unreachable", .{});
+}