commit 546350ad4560109757ac57b589c307631de8b6ff
parent 3fa4913f626e710efdffdc96c1ef24f99f86a32e
Author: Sylvia Ivory <git@sivory.net>
Date: Fri, 13 Feb 2026 09:19:48 -0800
It works
Diffstat:
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) {}
+}