sylveos

Toy Operating System
Log | Files | Refs

commit 8d9fc0af14640de4e4aeed81401ae7904b154874
parent e2ea44793d4e394e5fcee53b5692b4cee3296fea
Author: Sylvia Ivory <git@sivory.net>
Date:   Thu, 22 Jan 2026 19:52:08 -0800

Add interrupts

Diffstat:
Mbuild.zig | 5+++--
Api/cpsr.zig | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Api/interrupts.zig | 151++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpi/mem.zig | 8++++----
Mpi/root.zig | 39+++++++++++++++++++++++++++++++++++++++
Csrc/devices/timer.zig -> src/devices/clock.zig | 0
Msrc/devices/mini-uart.zig | 15++++++++++-----
Msrc/devices/timer.zig | 124+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Msrc/labs/4-measure.zig | 6+++---
Msrc/main.zig | 41++++++++++++++++++++++++++++++++++++++++-
Msrc/root.zig | 63++++++++++++++++++++++++++++++++++++++++++++++++++-------------
11 files changed, 455 insertions(+), 50 deletions(-)

diff --git a/build.zig b/build.zig @@ -60,7 +60,7 @@ fn add_exe_real(exe_name: []const u8, path: std.Build.LazyPath, b: *std.Build) ! const pi = b.createModule(.{ .root_source_file = b.path("pi/root.zig"), .target = b.graph.host, - .optimize = .ReleaseSafe, + .optimize = .ReleaseFast, }); // BCM2835 is **very specifically** the ARM1176JZF-S @@ -68,7 +68,7 @@ fn add_exe_real(exe_name: []const u8, path: std.Build.LazyPath, b: *std.Build) ! const boot = b.createModule(.{ .root_source_file = path, .target = b.resolveTargetQuery(target), - .optimize = .ReleaseSafe, + .optimize = .ReleaseFast, .unwind_tables = .none, .single_threaded = true, .error_tracing = false, @@ -76,6 +76,7 @@ fn add_exe_real(exe_name: []const u8, path: std.Build.LazyPath, b: *std.Build) ! .no_builtin = true, }); boot.addImport("pi", pi); + pi.addImport("boot", boot); const exe = b.addExecutable(.{ .name = exe_name, diff --git a/pi/cpsr.zig b/pi/cpsr.zig @@ -0,0 +1,53 @@ +pub const Mode = enum(u5) { + User = 0b10000, + FIQ = 0b10001, + IRQ = 0b10010, + Supervisor = 0b10011, + Abort = 0b10111, + Undefined = 0b11011, + System = 0b11111, +}; + +pub const CPSR = packed struct(u32) { + mode: Mode, + t: bool, + f: bool, + i: bool, + a: bool, + e: bool, + _reserved_10_15: u6, + ge: u4, + _reserved_20_23: u4, + j: bool, + res: u2, + q: bool, + v: bool, + c: bool, + z: bool, + n: bool, +}; + +pub inline fn get() CPSR { + return asm volatile ("MRS %[result], cpsr" + : [result] "=r" (-> CPSR), + ); +} + +pub inline fn set(cpsr: CPSR) void { + return asm volatile ("MSR cpsr_c, %[value]" + : + : [value] "r" (@as(u32, @bitCast(cpsr))), + : .{ .cpsr = true }); +} + +pub inline fn enable_interrupts() void { + var cpsr = get(); + cpsr.i = true; + set(cpsr); +} + +pub inline fn disable_interrupts() void { + var cpsr = get(); + cpsr.i = false; + set(cpsr); +} diff --git a/pi/interrupts.zig b/pi/interrupts.zig @@ -0,0 +1,151 @@ +const cpsr = @import("./cpsr.zig"); +const mem = @import("./mem.zig"); + +const BASE_ADDRESS: usize = mem.BASE_ADDRESS + 0xB200; + +const IRQ_BASIC_PENDING: usize = BASE_ADDRESS + 0x00; +const IRQ_PENDING_1: usize = BASE_ADDRESS + 0x04; +const IRQ_PENDING_2: usize = BASE_ADDRESS + 0x08; + +const IRQ_FIQ_CONTROL: usize = BASE_ADDRESS + 0x0C; + +const IRQ_ENABLE_1: usize = BASE_ADDRESS + 0x10; +const IRQ_ENABLE_2: usize = BASE_ADDRESS + 0x14; +const IRQ_ENABLE_BASIC: usize = BASE_ADDRESS + 0x18; + +const IRQ_DISABLE_1: usize = BASE_ADDRESS + 0x1C; +const IRQ_DISABLE_2: usize = BASE_ADDRESS + 0x20; +const IRQ_DISABLE_BASIC: usize = BASE_ADDRESS + 0x24; + +pub inline fn enable_interrupts() void { + asm volatile ("cpsie i\n"); +} + +pub inline fn disable_interrupts() void { + asm volatile ("cpsid i\n"); +} + +pub inline fn clear_interrupt_flags() void { + mem.put_u32(@ptrFromInt(IRQ_DISABLE_1), ~@as(u32, 0)); + mem.put_u32(@ptrFromInt(IRQ_DISABLE_2), ~@as(u32, 0)); + mem.barrier(.Write); +} + +const BasicInterrupt = enum(u8) { + Timer = 1 << 0, + Mailbox = 1 << 1, + Doorbell0 = 1 << 2, + Doorbell1 = 1 << 3, + GPU0Halted = 1 << 4, + GPU1Halted = 1 << 5, + AccessError1 = 1 << 6, + AccessError0 = 1 << 7, +}; + +pub inline fn enable_basic_interrupt(i: BasicInterrupt) void { + mem.put_u32_barrier(@ptrFromInt(IRQ_ENABLE_BASIC), @intFromEnum(i)); +} +pub inline fn disable_basic_interrupt(i: BasicInterrupt) void { + mem.put_u32_barrier(@ptrFromInt(IRQ_DISABLE_BASIC), @intFromEnum(i)); +} +pub inline fn pending_basic(i: BasicInterrupt) bool { + return (mem.get_u32_barrier(@ptrFromInt(IRQ_BASIC_PENDING)) & @intFromEnum(i)) != 0; +} + +fn empty(lr: u32) void { + @import("boot").uart.write_slice("default handler called\n"); + _ = lr; +} + +var reset_handler: *const fn (u32) void = empty; +var undefined_instruction_handler: *const fn (u32) void = empty; +var software_interrupt_handler: *const fn (u32) void = empty; +var prefetch_abort_handler: *const fn (u32) void = empty; +var data_abort_handler: *const fn (u32) void = empty; +var interrupt_handler: *const fn (u32) void = empty; +var fast_interrupt_handler: *const fn (u32) void = empty; + +fn get_lr() u32 { + return asm volatile ("mov %[result], lr" + : [result] "=r" (-> u32), + ); +} + +export fn reset() callconv(.{ .arm_interrupt = .{ .type = .generic } }) void { + reset_handler(0); +} +export fn undefined_instruction() callconv(.{ .arm_interrupt = .{ .type = .undef } }) void { + undefined_instruction_handler(get_lr() - 4); +} +export fn software_interrupt() callconv(.{ .arm_interrupt = .{ .type = .swi } }) void { + software_interrupt_handler(get_lr() - 4); +} +export fn prefetch_abort() callconv(.{ .arm_interrupt = .{ .type = .abort } }) void { + prefetch_abort_handler(get_lr() - 8); +} +export fn data_abort() callconv(.{ .arm_interrupt = .{ .type = .abort } }) void { + data_abort_handler(get_lr() - 4); +} +export fn interrupt() callconv(.{ .arm_interrupt = .{ .type = .irq } }) void { + interrupt_handler(get_lr() - 4); +} +export fn fast_interrupt() callconv(.{ .arm_interrupt = .{ .type = .fiq } }) void { + fast_interrupt_handler(get_lr() - 4); +} + +// https://leiradel.github.io/2019/02/09/Initialization.html +pub fn setup_exception_vector() void { + var exception_vector: usize = 0; + + for (0..8) |_| { + mem.put_u32(@ptrFromInt(exception_vector), 0xE59FF018); // ldr pc, [pc, #24] + exception_vector += @sizeOf(u32); + } + + mem.put_u32(@ptrFromInt(exception_vector), @intFromPtr(&reset)); + exception_vector += @sizeOf(u32); + + mem.put_u32(@ptrFromInt(exception_vector), @intFromPtr(&undefined_instruction)); + exception_vector += @sizeOf(u32); + + mem.put_u32(@ptrFromInt(exception_vector), @intFromPtr(&software_interrupt)); + exception_vector += @sizeOf(u32); + + mem.put_u32(@ptrFromInt(exception_vector), @intFromPtr(&prefetch_abort)); + exception_vector += @sizeOf(u32); + + mem.put_u32(@ptrFromInt(exception_vector), @intFromPtr(&data_abort)); + exception_vector += @sizeOf(u32); + + mem.put_u32(@ptrFromInt(exception_vector), 0); + exception_vector += @sizeOf(u32); + + mem.put_u32(@ptrFromInt(exception_vector), @intFromPtr(&interrupt)); + exception_vector += @sizeOf(u32); + + mem.put_u32(@ptrFromInt(exception_vector), @intFromPtr(&fast_interrupt)); + + mem.barrier(.Write); +} + +const ExceptionVector = enum { + Reset, + UndefinedInstruction, + SoftwareInterrupt, + PrefetchAbort, + DataAbort, + IRQ, + FIQ, +}; + +pub fn set_exception_handler(vector: ExceptionVector, handler: *const fn (u32) void) void { + switch (vector) { + .Reset => reset_handler = handler, + .UndefinedInstruction => undefined_instruction_handler = handler, + .SoftwareInterrupt => software_interrupt_handler = handler, + .PrefetchAbort => prefetch_abort_handler = handler, + .DataAbort => data_abort_handler = handler, + .IRQ => interrupt_handler = handler, + .FIQ => fast_interrupt_handler = handler, + } +} diff --git a/pi/mem.zig b/pi/mem.zig @@ -34,7 +34,7 @@ pub inline fn barrier(op: Operation) void { } } -pub inline fn put_u32(address: *u32, value: u32) void { +pub inline fn put_u32(address: *allowzero u32, value: u32) void { asm volatile ("str %[value], [%[address]]" : : [value] "r" (value), @@ -43,21 +43,21 @@ pub inline fn put_u32(address: *u32, value: u32) void { } pub inline fn put_u32_barrier( - address: *u32, + address: *allowzero u32, value: u32, ) void { put_u32(address, value); barrier(.Write); } -pub inline fn get_u32(address: *u32) u32 { +pub inline fn get_u32(address: *allowzero u32) u32 { return asm volatile ("ldr %[result], [%[address]]" : [result] "=r" (-> u32), : [address] "r" (address), ); } -pub inline fn get_u32_barrier(address: *u32) u32 { +pub inline fn get_u32_barrier(address: *allowzero u32) u32 { barrier(.Read); return get_u32(address); } diff --git a/pi/root.zig b/pi/root.zig @@ -1,3 +1,5 @@ +pub const interrupts = @import("./interrupts.zig"); +pub const cpsr = @import("./cpsr.zig"); pub const mem = @import("./mem.zig"); pub inline fn cycle_counter_init() void { @@ -13,3 +15,40 @@ pub inline fn cycle_counter_read() u32 { : [result] "=r" (-> u32), ); } + +pub inline fn set_sp(sp: *usize) void { + asm volatile ("mov sp, %[value]" + : + : [value] "r" (sp), + : .{ .r13 = true }); // sp +} + +// TODO; should do something better +var svc_stack: [2024]usize align(8) = .{0} ** 2024; +var abort_stack: [32]usize align(8) = .{0} ** 32; +var irq_stack: [32]usize align(8) = .{0} ** 32; +var fiq_stack: [32]usize align(8) = .{0} ** 32; + +pub export fn setup_stacks() void { + const original_cpsr = cpsr.get(); + var new_cpsr = original_cpsr; + + new_cpsr.mode = .Undefined; + cpsr.set(new_cpsr); + set_sp(@ptrFromInt(@intFromPtr(&abort_stack) + (abort_stack.len * @sizeOf(usize)))); + + new_cpsr.mode = .Abort; + cpsr.set(new_cpsr); + set_sp(@ptrFromInt(@intFromPtr(&abort_stack) + (abort_stack.len * @sizeOf(usize)))); + + new_cpsr.mode = .IRQ; + cpsr.set(new_cpsr); + set_sp(@ptrFromInt(@intFromPtr(&irq_stack) + (irq_stack.len * @sizeOf(usize)))); + + new_cpsr.mode = .FIQ; + cpsr.set(new_cpsr); + set_sp(@ptrFromInt(@intFromPtr(&fiq_stack) + (fiq_stack.len * @sizeOf(usize)))); + + // Restore + cpsr.set(original_cpsr); +} diff --git a/src/devices/timer.zig b/src/devices/clock.zig diff --git a/src/devices/mini-uart.zig b/src/devices/mini-uart.zig @@ -2,7 +2,6 @@ const std = @import("std"); const mem = @import("pi").mem; const gpio = @import("./gpio.zig"); -const timer = @import("./timer.zig"); pub const Error = error{ AlreadyInitialized, NotInitialized, InvalidReadLimit } || gpio.Error; @@ -110,21 +109,21 @@ pub fn is_initialized() bool { return initialized; } -fn can_write() bool { +pub fn can_write() bool { // Check if FIFO can accept data // Page 15: AUX_MU_LSR_REG Register // Bit 5 is set when FIFO can accept at least 1 byte return (mem.get_u32(@ptrFromInt(AUX_MU_LSR_REG)) & 0x20) != 0; } -fn can_read() bool { +pub fn can_read() bool { // Check if FIFO has data // Page 15: AUX_MU_LSR_REG Register // Bit 1 is set when FIFO holds at least 1 byte return (mem.get_u32(@ptrFromInt(AUX_MU_LSR_REG)) & 1) == 1; } -fn write_byte(byte: u8) void { +pub fn write_byte(byte: u8) void { // TODO; support timeout while (!can_write()) {} @@ -134,7 +133,13 @@ fn write_byte(byte: u8) void { mem.put_u32(@ptrFromInt(AUX_MU_IO_REG), @as(u32, byte) & 0xFF); } -fn read_byte() u8 { +pub fn write_slice(bytes: []const u8) void { + for (bytes) |b| { + write_byte(b); + } +} + +pub fn read_byte() u8 { // TODO; support timeout while (!can_read()) {} diff --git a/src/devices/timer.zig b/src/devices/timer.zig @@ -1,33 +1,113 @@ const std = @import("std"); -const mem = @import("pi").mem; +const pi = @import("pi"); -const BASE_ADDRESS: usize = mem.BASE_ADDRESS + 0x0000_3000; -const COUNTER_LOWER: usize = BASE_ADDRESS + 0x0004; -const COUNTER_UPPER: usize = BASE_ADDRESS + 0x0008; +const clock = @import("./clock.zig"); -pub fn current_count() u64 { - const upper = mem.get_u32_barrier(@ptrFromInt(COUNTER_UPPER)); - const lower = mem.get_u32_barrier(@ptrFromInt(COUNTER_LOWER)); - const extended: u64 = @intCast(upper); +const interrupts = pi.interrupts; +const mem = pi.mem; - return (extended << 32) | lower; +const BASE_ADDRESS: usize = mem.BASE_ADDRESS + 0xB400; + +const TIMER_LOAD: usize = BASE_ADDRESS + 0x00; +const TIMER_VALUE: usize = BASE_ADDRESS + 0x04; +const TIMER_CONTROL: usize = BASE_ADDRESS + 0x08; + +const TIMER_IRQ_CLEAR: usize = BASE_ADDRESS + 0x0C; +const TIMER_IRQ_RAW: usize = BASE_ADDRESS + 0x10; +const TIMER_IRQ_MASKED: usize = BASE_ADDRESS + 0x14; + +const TIMER_RELOAD: usize = BASE_ADDRESS + 0x18; +const TIMER_PREDIV: usize = BASE_ADDRESS + 0x1C; +const TIMER_COUNTER: usize = BASE_ADDRESS + 0x20; + +const PreScale = enum(u2) { + None = 0b00, + Sixteen = 0b01, + TwoFiftySix = 0b10, + One = 0b11, +}; + +const Counter = enum(u1) { + Bit16 = 0, + Bit32 = 1, +}; + +const Control = packed struct(u32) { + _unused_1: u1, // 0 + counter: Counter, // 1 + pre_scale: PreScale, // 2-3 + _unused_4: u1, // 4 + interrupt_enabled: bool, // 5 + _unused_6: u1, // 6 + timer_enabled: bool, // 7 + run_in_debug_halted: bool, // 8 + free_running_counter_enable: bool, // 9 + _unused_15_10: u5, // 10-15 + free_running_counter_pre_scaler: u7, // 16-23 + _unused_31_24: u10, +}; + +fn default_control() Control { + return @bitCast(@as(u32, 0)); +} + +fn empty(pc: usize) void { + _ = pc; +} + +var tick_fn: *const fn (usize) void = empty; + +pub fn initialize(pre_scale: PreScale, cycles: u32) void { + mem.barrier(.Write); + + interrupts.set_exception_handler(.IRQ, timer_handler); + interrupts.enable_basic_interrupt(.Timer); + + mem.put_u32(@ptrFromInt(TIMER_LOAD), cycles); + + var control = default_control(); + control.counter = .Bit32; + control.timer_enabled = true; + control.interrupt_enabled = true; + control.pre_scale = pre_scale; + + mem.put_u32_barrier(@ptrFromInt(TIMER_CONTROL), @bitCast(control)); +} + +var count: u32 = 0; +var previous_clock: u64 = 0; +var period: u64 = 0; +var period_sum: u64 = 0; + +noinline fn timer_handler(pc: usize) void { + mem.barrier(.Write); // Sync + + if (!interrupts.pending_basic(.Timer)) return; + + // Clear interrupt + mem.put_u32_barrier(@ptrFromInt(TIMER_IRQ_CLEAR), 1); + + // TODO; check if we need volatile + count += 1; + + const current_clock = clock.current_count(); + period = if (previous_clock == 0) 0 else current_clock - previous_clock; + period_sum += period; + previous_clock = current_clock; + + tick_fn(pc); + + mem.barrier(.Write); // Sync before restoring } -// Busy delay -// Not the best way to delay *but* a proper delay -// requires *a lot* more code -pub fn delay(us: u64) void { - const start = current_count(); - while (true) { - const current = current_count(); - if ((current - start) >= us) return; - } +pub inline fn get_count() u32 { + return count; } -pub fn delay_ms(ms: u64) void { - delay(ms * 1000); +pub inline fn get_period() u64 { + return period; } -pub fn delay_s(s: u64) void { - delay_ms(s * 1000); +pub inline fn get_period_sum() u64 { + return period_sum; } diff --git a/src/labs/4-measure.zig b/src/labs/4-measure.zig @@ -2,7 +2,7 @@ const std = @import("std"); const pi = @import("pi"); const uart = @import("../devices/mini-uart.zig"); -const timer = @import("../devices/timer.zig"); +const clock = @import("../devices/clock.zig"); pub fn main() !void { try uart.initialize(115200, .Gpio14, .Gpio15); @@ -10,8 +10,8 @@ pub fn main() !void { const start = pi.cycle_counter_read(); - const s = timer.current_count(); - while ((timer.current_count() - s) < 1000 * 1000) {} + const s = clock.current_count(); + while ((clock.current_count() - s) < 1000 * 1000) {} const end = pi.cycle_counter_read(); const total = end - start; diff --git a/src/main.zig b/src/main.zig @@ -1,5 +1,44 @@ +const interrupts = @import("pi").interrupts; + const uart = @import("devices/mini-uart.zig"); +const clock = @import("devices/clock.zig"); +const timer = @import("devices/timer.zig"); pub fn main() !void { - try uart.writer.print("Hello {s}", .{"World!"}); + try uart.writer.print("entered main safely...\n", .{}); + + timer.initialize(.Sixteen, 0x100); + + try uart.writer.print("initialized timer...\n", .{}); + + interrupts.enable_interrupts(); + + try uart.writer.print("enabled interrupts...\n", .{}); + + const start = clock.current_count(); + var iter: u32 = 0; + const N = 20; + + while (timer.get_count() < N) { + try uart.writer.print("iter={d}: count={d}, time between interrupts = {d} usec (0x{x})\n", .{ + iter, + timer.get_count(), + timer.get_period(), + timer.get_period(), + }); + iter += 1; + } + + const total = clock.current_count() - start; + const total_sec = total / (1000 * 1000); + const total_ms = (total / 1000) % 10000; + const total_us = (total % 1000); + + try uart.writer.print( + \\ total iterations: {} + \\ total interrupts: {} + \\ iterations / interrupt: {} + \\ average period: {} + \\ total execution time: {}s.{}ms.{}us + , .{ iter, N, iter / N, timer.get_period_sum() / (N - 1), total_sec, total_ms, total_us }); } diff --git a/src/root.zig b/src/root.zig @@ -1,24 +1,61 @@ const std = @import("std"); +const pi = @import("pi"); -const uart = @import("devices/mini-uart.zig"); +pub const uart = @import("devices/mini-uart.zig"); const user_main = @import("main.zig").main; extern const __bss_start__: usize; extern const __bss_end__: usize; fn zero_bss() void { - for (__bss_start__..__bss_end__) |b| { + // No bss data allocated + if (__bss_start__ >= __bss_end__) return; + + const n: usize = @divFloor(__bss_end__ - __bss_start__, @sizeOf(u32)); + + for (0..n) |b| { // Force a write - const ptr: *volatile u8 = @ptrFromInt(b); - ptr.* = 0; + const ptr: *allowzero u32 = @ptrFromInt(__bss_start__ + b * @sizeOf(u32)); + pi.mem.put_u32(ptr, 0); } } -noinline fn kmain() void { - zero_bss(); +fn initialize_interrupts() void { + uart.write_slice(" Disabling interrupts\n"); + pi.interrupts.disable_interrupts(); + pi.mem.barrier(.Write); + + uart.write_slice(" Clearing interrupt flags\n"); + pi.interrupts.clear_interrupt_flags(); + pi.mem.barrier(.Write); + uart.write_slice(" Setting exception vector\n"); + pi.interrupts.setup_exception_vector(); + pi.mem.barrier(.Write); +} + +export fn kmain() void { uart.initialize(115200, .Gpio14, .Gpio15) catch {}; + uart.write_slice("Clearing bss\n"); + uart.writer.print(" __bss_start__ = 0x{X}\n", .{__bss_start__}) catch {}; + uart.writer.print(" __bss_end__ = 0x{X}\n", .{__bss_end__}) catch {}; + zero_bss(); + + uart.write_slice("Initializing interrupts\n"); + initialize_interrupts(); + + uart.write_slice("Setting up stack\n"); + pi.setup_stacks(); + + uart.write_slice("Exception Vector:\n"); + for (0..16) |i| { + const dst: *allowzero u32 = @ptrFromInt(i * @sizeOf(*u32)); + uart.writer.print(" 0x{X} = 0x{X}\n", .{ @intFromPtr(dst), dst.* }) catch {}; + } + + uart.write_slice("Calling user main\n"); + user_main() catch |e| { uart.writer.print("main returned error: {t}\n", .{e}) catch {}; }; @@ -27,15 +64,12 @@ noinline fn kmain() void { export fn _start() linksection(".kmain") callconv(.naked) noreturn { asm volatile ( \\ mov sp, 0x800000 - \\ bl %[kmain:P] - \\ bl %[abort:P] - : - : [kmain] "X" (&kmain), - [abort] "X" (&abort), + \\ bl kmain + \\ bl abort ); } -noinline fn abort() noreturn { +export fn abort() noreturn { @branchHint(.cold); while (true) {} } @@ -44,12 +78,15 @@ fn panic_handler(msg: []const u8, trace_addr: ?usize) noreturn { @branchHint(.cold); if (uart.is_initialized()) { - uart.writer.print("kernel panic: {s}\n", .{msg}) catch {}; + uart.write_slice("kernel panic: "); + uart.write_slice(msg); + uart.write_byte('\n'); var it = std.debug.StackIterator.init(trace_addr, null); var ix: usize = 0; while (it.next()) |frame| : (ix += 1) { + // TODO; remove uart.writer.print("| #{d:0>2}: 0x{X:0>16}\n", .{ ix, frame }) catch {}; } }