sylveos

Toy Operating System
Log | Files | Refs

commit 56d9a1f8783e77f251af61567b15326f31fa6eb3
parent 37fc93e452e0bc8c87a9b7f71e8bef3ce1e0df60
Author: Sylvia Ivory <git@sivory.net>
Date:   Tue, 17 Feb 2026 21:03:13 -0800

Update exception code

Diffstat:
Mpi/devices/mini-uart.zig | 5++---
Mpi/devices/timer.zig | 12++++++------
Mpi/interrupts.zig | 114+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Mpi/psr.zig | 6+++---
Mpi/root.zig | 1+
Api/switching.zig | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mprograms/debug.zig | 90++++++++++++++++++-------------------------------------------------------------
Aprograms/timer.zig | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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(&regs); 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(&regs); } 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", .{}); +}