sylveos

Toy Operating System
Log | Files | Refs

commit 546350ad4560109757ac57b589c307631de8b6ff
parent 3fa4913f626e710efdffdc96c1ef24f99f86a32e
Author: Sylvia Ivory <git@sivory.net>
Date:   Fri, 13 Feb 2026 09:19:48 -0800

It works

Diffstat:
Mboot/root.zig | 4+---
Api/debug.zig | 324+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpi/devices/mini-uart.zig | 9+++++++++
Api/faults.zig | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpi/interrupts.zig | 46+++++++++++++++++++++++++++++++++++++++++++++-
Mpi/mem.zig | 13+++++++++++--
Mpi/psr.zig | 2+-
Mpi/root.zig | 2++
Aprograms/debug.zig | 222+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 722 insertions(+), 7 deletions(-)

diff --git a/boot/root.zig b/boot/root.zig @@ -63,9 +63,7 @@ fn panic_handler(msg: []const u8, trace_addr: ?usize) noreturn { @branchHint(.cold); if (uart.is_initialized()) { - uart.write_slice("kernel panic: "); - uart.write_slice(msg); - uart.write_slice("\n"); + uart.writer.print("kernel panic: {s} @ 0x{X}\n", .{ msg, trace_addr orelse 0 }) catch {}; uart.flush(); var it = std.debug.StackIterator.init(trace_addr, null); diff --git a/pi/debug.zig b/pi/debug.zig @@ -0,0 +1,324 @@ +// CP14 +const mem = @import("./mem.zig"); + +// # Debug ID Register +// Reference: ARM1176JZF-S Technical Reference Manual - 13.3.2 +pub const DIDR = packed struct(u32) { + revision: u4, + variant: u4, + _reserved_8_11: u4, + debug_revision: u4, + debug_version: u4, + context: u4, + breakpoint_pairs: u4, + watchpoint_pairs: u4, + + pub inline fn get() @This() { + return asm volatile ("MRC p14, 0, %[result], c0, c0, 0" + : [result] "=r" (-> @This()), + ); + } +}; + +// # Debug Status and Control Register +// Reference: ARM1176JZF-S Technical Reference Manual - 13.3.3 +pub const DSCR = packed struct(u32) { + pub const EntryMethod = enum(u4) { + Halt = 0b0000, + Breakpoint = 0b0001, + Watchpoint = 0b0010, + BKPT = 0b0011, + EDBGRQ = 0b0100, + VectorCatch = 0b0101, + }; + pub const ModeSelect = enum(u1) { + Monitor = 0, + Halting = 1, + }; + + core_halted: bool, + core_restarted: bool, + entry_method: EntryMethod, + sticky_precise: bool, + sticky_imprecise: bool, + sticky_undefined: bool, + power_down_disable: bool, + dbg_ack: bool, + interrupts: bool, + user_comms_access: bool, + execute_arm: bool, + mode_select: ModeSelect, + monitor_debug: bool, + invasive_debug: bool, + non_invasive_debug: bool, + world_status: u1, + imprecise_aborts_ignore: bool, + _reverse_20_28: u9, + w_dtr_full: bool, + r_dtr_full: bool, + _reserved_31: u1, + + pub inline fn get() @This() { + return asm volatile ("MRC p14, 0, %[result], c0, c1, 0" + : [result] "=r" (-> @This()), + ); + } + + pub inline fn set(dscr: *const @This()) void { + return asm volatile ("MCR p14, 0, %[value], c0, c1, 0" + : + : [value] "r" (@as(u32, @bitCast(dscr.*))), + ); + } +}; + +pub const SupervisorAccess = enum(u2) { + Priviledged = 0b01, + User = 0b10, + Either = 0b11, +}; + +pub const ByteAddressSelect = packed struct(u4) { + pub const All: @This() = .{ + .@"0" = true, + .@"1" = true, + .@"2" = true, + .@"3" = true, + }; + + @"0": bool, + @"1": bool, + @"2": bool, + @"3": bool, +}; + +pub const Matches = enum(u2) { + Both = 0b00, + NonSecure = 0b01, + Secure = 0b10, +}; + +inline fn register(comptime n: u4) []const u8 { + return switch (n) { + 0 => "c0", + 1 => "c1", + 2 => "c2", + 3 => "c3", + 4 => "c4", + 5 => "c5", + 6 => "c6", + 7 => "c7", + 8 => "c8", + 9 => "c9", + 10 => "c10", + 11 => "c11", + 12 => "c12", + 13 => "c13", + 14 => "c14", + 15 => "c15", + }; +} + +// # Breakpoint Control Register +// Reference: ARM1176JZF-S Technical Reference Manual - 13.3.8 +pub const BCR = packed struct(u32) { + pub const BVRMeaning = enum(u2) { + IMVAMatch = 0b00, + ContextIdMatch = 0b01, + IMVAMismatch = 0b10, + }; + + enabled: bool, + supervisor_access: SupervisorAccess, + _reserved_3_4: u2, + byte_address_select: ByteAddressSelect, + _reserved_9_13: u5, + matches: Matches, + linked_brp: u4, + linking: bool, + bvr_meaning: BVRMeaning, + _reserved_23_31: u9, + + pub inline fn get(comptime n: u4) @This() { + const Crn = register(n); + + return asm volatile ("MRC p14, 0, %[result], c0, " ++ Crn ++ ", 5" + : [result] "=r" (-> @This()), + ); + } + pub inline fn get_pc(comptime n: u4) u32 { + const Crn = register(n); + + return asm volatile ("MRC p14, 0, %[result], c0, " ++ Crn ++ ", 4" + : [result] "=r" (-> u32), + ); + } + + pub inline fn set(self: *const @This(), comptime n: u4) void { + const Crn = register(n); + + asm volatile ("MCR p14, 0, %[value], c0, " ++ Crn ++ ", 5" + : + : [value] "r" (@as(u32, @bitCast(self.*))), + ); + } + pub inline fn set_pc(comptime n: u4, pc: u32) void { + const Crn = register(n); + + asm volatile ("MCR p14, 0, %[value], c0, " ++ Crn ++ ", 4" + : + : [value] "r" (pc), + ); + } +}; + +// # Watchpoint Control Register +// Reference: ARM1176JZF-S Technical Reference Manual - 13.3.10 +pub const WCR = packed struct(u32) { + pub const LoadStoreAccess = enum(u2) { + Load = 0b01, + Store = 0b10, + Either = 0b11, + }; + + enabled: bool, + supervisor_access: SupervisorAccess, + load_store_access: LoadStoreAccess, + byte_address_select: ByteAddressSelect, + _reserved_9_13: u5, + matches: Matches, + linked_brp: u4, + linking: bool, + _reserved_21_31: u11, + + pub inline fn get(comptime n: u4) @This() { + const Crn = register(n); + + return asm volatile ("MRC p14, 0, %[result], c0, " ++ Crn ++ ", 7" + : [result] "=r" (-> @This()), + ); + } + pub inline fn get_address(comptime n: u4) u32 { + const Crn = register(n); + + return asm volatile ("MRC p14, 0, %[result], c0, " ++ Crn ++ ", 6" + : [result] "=r" (-> u32), + ); + } + + pub inline fn set(self: *const @This(), comptime n: u4) void { + const Crn = register(n); + + asm volatile ("MCR p14, 0, %[value], c0, " ++ Crn ++ ", 7" + : + : [value] "r" (@as(u32, @bitCast(self.*))), + ); + } + pub inline fn set_address(comptime n: u4, address: u32) void { + const Crn = register(n); + + asm volatile ("MCR p14, 0, %[value], c0, " ++ Crn ++ ", 6" + : + : [value] "r" (address), + ); + } +}; + +// # Watchpoint Fault Address Register +// Reference: ARM1176JZF-S Technical Reference Manual - 13.3.5 +pub const WFAR = struct { + pub inline fn get() u32 { + return asm volatile ("MRC p14, 0, %[result], c0, c6, 0" + : [result] "=r" (-> u32), + ); + } + // I don't know why you'd want to write + // but it isn't disallowed + pub inline fn set(address: u32) void { + return asm volatile ("MCR p14, 0, %[value], c0, c6, 0" + : + : [value] "r" (address), + ); + } +}; + +pub inline fn enable_monitor_mode() void { + var dscr = DSCR.get(); + + dscr.monitor_debug = true; + dscr.mode_select = .Monitor; + dscr.set(); + mem.barrier(.Instruction); +} + +pub inline fn disable_monitor_mode() void { + var dscr = DSCR.get(); + dscr.monitor_debug = false; + dscr.set(); + mem.barrier(.Instruction); +} + +// 13.14.2 +pub inline fn set_breakpoint(comptime n: u4, pc: usize, kind: BCR.BVRMeaning) void { + // "Read the BCR" + var bcr = BCR.get(n); + + // "Clear the BCR[0] enable breakpoint bit... write it back" + bcr.enabled = false; + bcr.set(n); + + // Write the IMVA to the BVR + BCR.set_pc(n, pc); + + // Write to the BCR + bcr.bvr_meaning = kind; + bcr.linking = false; + bcr.matches = .Both; + bcr.byte_address_select = ByteAddressSelect.All; + bcr.supervisor_access = .Either; + bcr.enabled = true; + bcr.set(n); + + // Sync + mem.barrier(.Instruction); +} + +pub inline fn clear_breakpoint(comptime n: u4) void { + var bcr = BCR.get(n); + bcr.enabled = false; + bcr.set(n); + + // Sync + mem.barrier(.Instruction); +} + +// 13.14.2 +pub inline fn set_watchpoint(comptime n: u4, address: u32, kind: WCR.LoadStoreAccess) void { + // "Read the WCR" + var wcr = WCR.get(n); + + // "Clear the WCR[0] enable watchpoint bit... write it back" + wcr.enabled = false; + wcr.set(n); + + // Write the DMVA to the WVR + WCR.set_address(n, address); + + // Write to the WCR + wcr.linking = false; + wcr.load_store_access = kind; + wcr.enabled = true; + wcr.set(n); + + // Sync + mem.barrier(.Instruction); +} + +pub inline fn clear_watchpoint(comptime n: u4) void { + var wcr = WCR.get(n); + wcr.enabled = false; + wcr.set(n); + + // Sync + mem.barrier(.Instruction); +} diff --git a/pi/devices/mini-uart.zig b/pi/devices/mini-uart.zig @@ -138,6 +138,12 @@ pub fn enable_interrupts() Error!void { // Temporarily disables TX interrupting pub fn toggle_tx_interrupts() void { interrupts_enabled = !interrupts_enabled; + if (interrupts_enabled) return; + + // Flush queue + while (tx_list.pop()) |b| { + write_byte(b) catch {}; + } } fn enable_tx_interrupt() void { @@ -254,6 +260,9 @@ pub fn read_queue_length() usize { } pub fn write_queue_length() usize { + // Queuing disabled + if (!interrupts_enabled) return 0; + const cs = mem.enter_critical_section(); defer cs.exit(); diff --git a/pi/faults.zig b/pi/faults.zig @@ -0,0 +1,107 @@ +pub const FaultKindZero = enum(u4) { + AlignmentFault = 0b0001, + InstructionDebugEventFault = 0b0010, + AccessBitFaultOnSection = 0b0011, + InstructionCacheMaintenanceOperationFault = 0b0100, + TranslationSectionFault = 0b0101, + AccessBitFault = 0b0110, + TranslationPageFault = 0b0111, + PreciseExternalAbort = 0b1000, + DomainSectionFault = 0b1001, + DomainPageFault = 0b1011, + ExternalAbortOnTranslationFirstLevel = 0b1100, + PermissionSectionFault = 0b1101, + ExternalAbortOnTranslationSecondLevel = 0b1110, + PermissionPageFault = 0b1111, +}; + +pub const AxiSource = enum(u1) { + Decode = 0, + Slave = 1, +}; + +pub const DFSR = packed struct(u32) { + pub const FaultKindOne = enum(u4) { + ImpreciseExternalAbort = 0b0110, + }; + pub const AbortSource = enum(u1) { + Read = 0, + Write = 1, + }; + + status: packed union { + @"0": FaultKindZero, + @"1": FaultKindOne, + }, + domain: u4, + _reserved_8_9: u2, + status_kind: u1, + abort_source: AbortSource, + axi_source: AxiSource, + _reserved_13_31: u19, + + pub fn get() @This() { + return asm volatile ("MRC p15, 0, %[result], c5, c0, 0" + : [result] "=r" (-> @This()), + ); + } + + pub fn set(self: *@This()) void { + asm volatile ("MCR p15, 0, %[value], c5, c0, 0" + : + : [value] "r" (@as(u32, @bitCast(self.*))), + ); + } +}; + +pub const IFSR = packed struct(u32) { + status: FaultKindZero, + _reserved_4_9: u8, + status_kind: u1, + _reserved_11: u1, + axi_source: AxiSource, + _reserved_13_31: u19, + + pub fn get() @This() { + return asm volatile ("MRC p15, 0, %[result], c5, c0, 1" + : [result] "=r" (-> @This()), + ); + } + + pub fn set(self: *@This()) void { + asm volatile ("MCR p15, 0, %[value], c5, c0, 1" + : + : [value] "r" (@as(u32, @bitCast(self.*))), + ); + } +}; + +pub const FAR = struct { + pub fn get() u32 { + return asm volatile ("MRC p15, 0, %[result], c6, c0, 0" + : [result] "=r" (-> u32), + ); + } + + pub fn set(address: u32) void { + asm volatile ("MCR p15, 0, %[result], c6, c0, 0" + : + : [result] "r" (address), + ); + } +}; + +pub const IFAR = struct { + pub fn get() u32 { + return asm volatile ("MRC p15, 0, %[result], c6, c0, 2" + : [result] "=r" (-> u32), + ); + } + + pub fn set(address: u32) void { + asm volatile ("MCR p15, 0, %[result], c6, c0, 2" + : + : [result] "r" (address), + ); + } +}; diff --git a/pi/interrupts.zig b/pi/interrupts.zig @@ -112,27 +112,71 @@ pub const Registers = struct { sp: usize, lr: usize, pc: usize, + psr: pi.PSR, }; +inline fn user_get_sp() u32 { + var sp: u32 = undefined; + + asm volatile ("stm %[ptr], {sp}^" + : + : [ptr] "r" (&sp), + : .{ .memory = true }); + + return sp; +} + +inline fn user_get_lr() u32 { + var lr: u32 = undefined; + + asm volatile ("stm %[ptr], {lr}^" + : + : [ptr] "r" (&lr), + : .{ .memory = true }); + + return lr; +} + +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(); +} + export fn reset_stub(pc: u32, reg: *Registers) void { + setup_reg(pc, reg); reset_handler(pc, reg); } + export fn undefined_instruction_stub(pc: u32, reg: *Registers) void { + setup_reg(pc, reg); undefined_instruction_stub(pc, reg); } + export fn software_interrupt_stub(pc: u32, reg: *Registers) void { + setup_reg(pc, reg); software_interrupt_handler(pc, reg); } + export fn prefetch_abort_stub(pc: u32, reg: *Registers) void { + setup_reg(pc, reg); prefetch_abort_handler(pc, reg); } + export fn data_abort_stub(pc: u32, reg: *Registers) void { + setup_reg(pc, reg); data_abort_handler(pc, reg); } + +// This interrupt is a bastard export fn interrupt_stub(pc: u32, reg: *Registers) void { + // setup_reg(pc, reg); interrupt_handler(pc, reg); } + export fn fast_interrupt_stub(pc: u32, reg: *Registers) void { + // setup_reg(pc, reg); fast_interrupt_handler(pc, reg); } @@ -141,7 +185,7 @@ fn create_trampoline(name: []const u8, offset: []const u8, ret: []const u8) []co ".global " ++ name ++ "\n" ++ ".type " ++ name ++ ", %function\n" ++ name ++ ":\n" ++ - " push {r0-r15}\n" ++ + " push {r0-r14}\n" ++ " sub lr, lr, #" ++ offset ++ "\n" ++ " mov r0, lr\n" ++ " mov r1, sp\n" ++ diff --git a/pi/mem.zig b/pi/mem.zig @@ -3,7 +3,7 @@ pub const BASE_ADDRESS: usize = 0x2000_0000; // Forces all outstanding explicit memory transactions to complete before executing // another **instruction**. Other instructions can execute out of order inline fn dsb() void { - asm volatile ("mcr p15, 0, %[reg], c7, c10, 5" + asm volatile ("mcr p15, 0, %[reg], c7, c10, 4" : : [reg] "r" (0), ); @@ -11,7 +11,14 @@ inline fn dsb() void { // Forces all outstanding explicit memory transactions to complete before executing // another **memory transaction**. No instruction can execute until this is complete inline fn dmb() void { - asm volatile ("mcr p15, 0, %[reg], c7, c10, 4" + asm volatile ("mcr p15, 0, %[reg], c7, c10, 5" + : + : [reg] "r" (0), + ); +} +// Flush Prefetch Buffer +inline fn imb() void { + asm volatile ("mcr p15, 0, %[reg], c7, c5, 4" : : [reg] "r" (0), ); @@ -30,6 +37,7 @@ const BarrierKind = enum { const Operation = enum { Read, Write, + Instruction, Compiler, }; @@ -37,6 +45,7 @@ pub inline fn barrier(comptime op: Operation) void { switch (op) { .Write => dsb(), .Read => dmb(), + .Instruction => imb(), .Compiler => compiler_barrier(), } } diff --git a/pi/psr.zig b/pi/psr.zig @@ -94,7 +94,7 @@ pub const PSR = packed struct(u32) { /// Set the SPSR pub inline fn set_s(spsr: *const @This()) PSR { - return asm volatile ("MRS spsr_cxsf, %[value]" + return asm volatile ("MSR spsr_cxsf, %[value]" : : [value] "r" (@as(u32, @bitCast(spsr.*))), : .{ .cpsr = true }); diff --git a/pi/root.zig b/pi/root.zig @@ -6,6 +6,8 @@ pub const PSR = @import("./psr.zig").PSR; pub const interrupts = @import("./interrupts.zig"); pub const register = @import("./register.zig"); pub const journal = @import("./journal.zig"); +pub const faults = @import("./faults.zig"); +pub const debug = @import("./debug.zig"); pub const mem = @import("./mem.zig"); pub const devices = struct { diff --git a/programs/debug.zig b/programs/debug.zig @@ -0,0 +1,222 @@ +const pi = @import("pi"); + +const interrupts = pi.interrupts; +const debug = pi.debug; +const uart = pi.devices.mini_uart; +const faults = pi.faults; + +var scheduler_registers: interrupts.Registers = undefined; +var scheduler_psr: pi.PSR = undefined; +var count: usize = 0; + +fn dump_registers(regs: *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 {}; + } + uart.writer.print(" sp = 0x{X}\n lr = 0x{X}\n pc = 0x{X}\n", .{ regs.sp, regs.lr, regs.pc }) catch {}; + uart.writer.print(" psr = {s}\n", .{@tagName(regs.psr.mode)}) catch {}; + uart.flush(); +} + +fn single_step_handler(pc: u32, 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.flush(); + + debug.set_breakpoint(0, regs.pc, .IMVAMismatch); + uart.flush(); + + restore_state_user(regs); +} + +fn watchpoint_handler(pc: u32, 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(" -> accessed address 0x{X}\n", .{debug.WCR.get_address(0)}) catch {}; + uart.flush(); + + restore_state_user(regs); +} + +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 {}; + uart.writer.print(" -> restoring to 0x{X}\n", .{regs.pc}) catch {}; + + const sys = regs.gp[0]; + const arg0 = regs.gp[1]; + + switch (sys) { + 1 => { + uart.write_slice("exiting process\n"); + uart.flush(); + + restore_state_privileged(&scheduler_registers); + }, + 2 => { + uart.write_byte(@truncate(arg0)) catch {}; + }, + else => { + uart.write_slice("illegal syscall\n"); + }, + } + + restore_state_user(regs); +} + +fn count_registers() callconv(.naked) void { + asm volatile ( + \\ 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 + \\ mov r0, 1 + \\ bx lr + ); +} + +// do null pointer bs +fn watchpoint_test() callconv(.naked) void { + asm volatile ( + \\ mov r0, #8 + \\ ldr r0, [r0] + \\ str r0, [r0] + \\ bx lr + ); +} + +fn exit_trampoline() callconv(.naked) void { + asm volatile ( + \\ mov r1, r0 + \\ mov r0, 1 + \\ swi 1 + ); +} + +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.toggle_tx_interrupts(); + + const didr = debug.DIDR.get(); + try uart.writer.print("Debug ID\n", .{}); + try uart.writer.print(" revision = {d}\n variant = {d}\n debug revision = {d}\n debug version = {d}\n", .{ + didr.revision, + didr.variant, + didr.debug_revision, + didr.debug_version, + }); + try uart.writer.print(" breakpoints = {d}\n watchpoints = {d}\n", .{ didr.breakpoint_pairs + 1, didr.watchpoint_pairs + 1 }); + + interrupts.set_exception_handler(.PrefetchAbort, single_step_handler); + interrupts.set_exception_handler(.DataAbort, watchpoint_handler); + interrupts.set_exception_handler(.SoftwareInterrupt, syscall_handler); + try uart.writer.print("Set exception handlers\n", .{}); + + try uart.writer.print("Running as {s}\n", .{@tagName(pi.PSR.get_c().mode)}); + debug.enable_monitor_mode(); + try uart.writer.print("Enabled monitor mode\n", .{}); + + debug.set_breakpoint(0, 0, .IMVAMismatch); + try uart.writer.print("Set breakpoint\n", .{}); + + var psr = pi.PSR.get_c(); + psr.mode = .User; + + var new_registers: interrupts.Registers = .{ + .gp = .{0} ** 13, + .pc = @intFromPtr(&count_registers), + .sp = 0, + .lr = @intFromPtr(&exit_trampoline), + .psr = psr, + }; + dump_registers(&new_registers); + + try uart.writer.print("Testing stepping\n", .{}); + switch_state(&scheduler_registers, &new_registers, restore_state_user); + try uart.writer.print("Stepping success\n", .{}); + + try uart.writer.print("Testing watchpoint\n", .{}); + new_registers = .{ + .gp = .{0} ** 13, + .pc = @intFromPtr(&watchpoint_test), + .sp = 0, + .lr = @intFromPtr(&exit_trampoline), + .psr = psr, + }; + debug.clear_breakpoint(0); + debug.set_watchpoint(0, 0x8, .Either); + count = 0; + switch_state(&scheduler_registers, &new_registers, restore_state_user); + try uart.writer.print("Watchpoint success\n", .{}); + + debug.disable_monitor_mode(); + + while (true) {} +}