sylveos

Toy Operating System
Log | Files | Refs

commit ad00709e43bdbe9bac83c192a96839aa47eaadfc
parent ad5c9a1189bfda3f02bf087702be75a31deef32c
Author: Sylvia Ivory <git@sivory.net>
Date:   Thu, 19 Feb 2026 19:27:57 -0800

No exiting works

Diffstat:
MJustfile | 2+-
Mboot/root.zig | 22+++++++++++-----------
Mbuild.zig | 6+++---
Mpi/coroutine.zig | 33+++++++++++++++++----------------
Mpi/devices/gpio.zig | 3+--
Mpi/devices/timer.zig | 25-------------------------
Mpi/interrupts.zig | 51++++++++++++++++++++++++++++++++++++---------------
Mpi/root.zig | 30+-----------------------------
Api/thread.zig | 132+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mprograms/syscall.zig | 14++++++--------
Aprograms/threads.zig | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
11 files changed, 266 insertions(+), 110 deletions(-)

diff --git a/Justfile b/Justfile @@ -16,7 +16,7 @@ install program sd-card="/dev/mmcblk0p1": udisksctl unmount -b {{ sd-card }} tools *args: - cargo run --manifest-path tools/Cargo.toml -- {{ args }} + cargo run --release --manifest-path tools/Cargo.toml -- {{ args }} run program *args: just build {{ program }} Bootable diff --git a/boot/root.zig b/boot/root.zig @@ -27,8 +27,8 @@ export fn kmain() void { uart.write_slice("Initializing interrupts\n"); initialize_interrupts(); - uart.write_slice("Setting up stack\n"); - pi.setup_stacks(); + // uart.write_slice("Setting up stack\n"); + // pi.setup_stacks(); uart.write_slice("Setting Cycle Counter\n"); pi.cycle_counter_init(); @@ -40,8 +40,8 @@ export fn kmain() void { } uart.write_slice("Enabling Interrupts\n"); - uart.set_tx_interrupts(true); - uart.set_rx_interrupts(true) catch {}; + // uart.set_tx_interrupts(true); + // uart.set_rx_interrupts(true) catch {}; pi.interrupts.enable_interrupts(); uart.write_slice("Entering program\n"); @@ -64,16 +64,16 @@ fn panic_handler(msg: []const u8, trace_addr: ?usize) noreturn { @branchHint(.cold); if (uart.is_initialized()) { - uart.print("kernel panic: {s}\n", .{msg}); + uart.print("kernel panic: {s} @ 0x{X}\n", .{ msg, trace_addr orelse 0 }); uart.flush(); - var it = std.debug.StackIterator.init(trace_addr, null); - var ix: usize = 0; + // var it = std.debug.StackIterator.init(trace_addr, null); + // var ix: usize = 0; - while (it.next()) |frame| : (ix += 1) { - uart.print("| #{d:0>2}: 0x{X:0>16}\n", .{ ix, frame }); - uart.flush(); - } + // while (it.next()) |frame| : (ix += 1) { + // uart.print("| #{d:0>2}: 0x{X:0>16}\n", .{ ix, frame }); + // uart.flush(); + // } } abort(); diff --git a/build.zig b/build.zig @@ -115,11 +115,11 @@ fn build_program(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. const path = try b.path("programs").getPath3(b, null).toString(b.allocator); defer b.allocator.free(path); - var dir = try std.fs.openDirAbsolute(path, .{ .access_sub_paths = false, .iterate = true }); - defer dir.close(); + var dir = try std.Io.Dir.openDirAbsolute(b.graph.io, path, .{ .access_sub_paths = false, .iterate = true }); + defer dir.close(b.graph.io); var iter = dir.iterate(); - while (try iter.next()) |entry| { + while (try iter.next(b.graph.io)) |entry| { if (entry.kind != .file) continue; if (!std.mem.endsWith(u8, entry.name, ".zig")) continue; const name = entry.name[0..(entry.name.len - 4)]; diff --git a/pi/coroutine.zig b/pi/coroutine.zig @@ -61,8 +61,8 @@ pub fn fork(self: *Self, f: *const fn (*Self, ?*anyopaque) callconv(.c) void, ar var sp: *usize = @ptrFromInt(stack_top); std.debug.assertAligned(sp, .@"8"); - // tr - sp = push_stack(sp, @intFromPtr(&trampoline)); + // lr + sp = push_stack(sp, @intFromPtr(&coroutine_trampoline)); // r4-r11 for (4..12) |_| { @@ -90,7 +90,7 @@ pub fn join(self: *Self) !void { self.scheduler_task.tid = 0; self.scheduler_task.saved_sp = @ptrFromInt(@frameAddress()); - context_switch(&self.scheduler_task.saved_sp, self.get_current().saved_sp); + coroutine_context_switch(&self.scheduler_task.saved_sp, self.get_current().saved_sp); } pub fn yield(self: *Self) void { @@ -103,14 +103,14 @@ pub fn yield(self: *Self) void { self.tasks.append(node_task); const current: *Task = @alignCast(@fieldParentPtr("node", node_task)); - context_switch(&current.saved_sp, self.get_current().saved_sp); + coroutine_context_switch(&current.saved_sp, self.get_current().saved_sp); } pub fn get_tid(self: *Self) usize { return self.get_current().tid; } -export fn trampoline_inner(self: *Self) callconv(.c) void { +export fn coroutine_trampoline_inner(self: *Self) callconv(.c) void { { const current: *Task = self.get_current(); current.func(self, current.arg); @@ -124,22 +124,22 @@ export fn trampoline_inner(self: *Self) callconv(.c) void { if (self.tasks.first) |node_task| { const next: *Task = @alignCast(@fieldParentPtr("node", node_task)); - context_switch(&saved_sp, next.saved_sp); + coroutine_context_switch(&saved_sp, next.saved_sp); } else { - context_switch(&saved_sp, self.scheduler_task.saved_sp); + coroutine_context_switch(&saved_sp, self.scheduler_task.saved_sp); } } comptime { asm ( - \\ .global trampoline - \\ .type trampoline, %function; - \\ trampoline: + \\ .global coroutine_trampoline + \\ .type coroutine_trampoline, %function; + \\ coroutine_trampoline: \\ mov r0, r3 - \\ bl trampoline_inner - \\ .global context_switch; - \\ .type context_switch, %function; - \\ context_switch: + \\ bl coroutine_trampoline_inner + \\ .global coroutine_context_switch; + \\ .type coroutine_context_switch, %function; + \\ coroutine_context_switch: \\ push {r3-r11,lr} \\ str sp, [r0] \\ mov sp, r1 @@ -148,5 +148,6 @@ comptime { ); } -extern fn trampoline() void; -extern fn context_switch(current_task_sp: **usize, next_task_sp: *usize) void; +// TODO; maybe should be naked functions? +extern fn coroutine_trampoline() void; +extern fn coroutine_context_switch(current_task_sp: **usize, next_task_sp: *usize) void; diff --git a/pi/devices/gpio.zig b/pi/devices/gpio.zig @@ -227,8 +227,7 @@ pub fn enable_interrupts() !void { mem.barrier(.Write); } -noinline fn gpio_handler(pc: usize, registers: *interrupts.Registers) void { - _ = pc; +noinline fn gpio_handler(registers: interrupts.Registers) void { _ = registers; mem.barrier(.Write); defer mem.barrier(.Write); diff --git a/pi/devices/timer.zig b/pi/devices/timer.zig @@ -73,11 +73,6 @@ pub fn initialize(pre_scale: PreScale, cycles: u32) void { 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(reg: interrupts.Registers) void { mem.barrier(.Write); // Sync @@ -86,14 +81,6 @@ noinline fn timer_handler(reg: interrupts.Registers) void { // 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(reg); mem.barrier(.Write); // Sync before restoring @@ -102,15 +89,3 @@ noinline fn timer_handler(reg: interrupts.Registers) void { pub inline fn set_tick(tick: *const fn (interrupts.Registers) void) void { tick_fn = tick; } - -pub inline fn get_count() u32 { - return count; -} - -pub inline fn get_period() u64 { - return period; -} - -pub inline fn get_period_sum() u64 { - return period_sum; -} diff --git a/pi/interrupts.zig b/pi/interrupts.zig @@ -1,5 +1,5 @@ const mem = @import("./mem.zig"); -const pi = @import("./root.zig"); +const PSR = @import("./psr.zig").PSR; const switching = @import("./switching.zig"); const BASE_ADDRESS: usize = mem.BASE_ADDRESS + 0xB200; @@ -112,9 +112,21 @@ pub const Registers = struct { sp: usize, lr: usize, pc: usize, - psr: pi.PSR, + psr: PSR, }; +// TODO; remove +pub fn dump_registers(regs: *const Registers) void { + const uart = @import("devices/mini-uart.zig"); + uart.print("Registers\n", .{}); + for (regs.gp, 0..) |r, i| { + uart.print(" r{d} = 0x{X}\n", .{ i, r }); + } + uart.print(" sp = 0x{X}\n lr = 0x{X}\n pc = 0x{X}\n", .{ regs.sp, regs.lr, regs.pc }); + uart.print(" psr = {s}\n", .{@tagName(regs.psr.mode)}); + uart.flush(); +} + // 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 ( @@ -127,7 +139,7 @@ noinline fn user_get_sp_lr(sp: *u32, lr: *u32) void { } noinline fn privileged_get_sp_lr(sp: *u32, lr: *u32) void { - const cpsr = pi.PSR.switch_current_mode(pi.PSR.get_s().mode); + const cpsr = PSR.switch_current_mode(PSR.get_s().mode); mem.barrier(.Instruction); asm volatile ( @@ -145,7 +157,7 @@ noinline fn privileged_get_sp_lr(sp: *u32, lr: *u32) void { 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() }; + var view: Registers = .{ .gp = undefined, .sp = undefined, .lr = undefined, .pc = pc, .psr = PSR.get_s() }; @memcpy(&view.gp, gp); @@ -158,18 +170,26 @@ inline fn setup_reg(pc: u32, gp: *const [13]u32) Registers { return view; } +// I still hate this +const stack_size = 256; // 1kb stacks + +// Things were already cooked +pub export var reset_stack: [8]usize align(8) = undefined; export fn reset_stub(pc: u32, gp: *const [13]u32) void { reset_handler(setup_reg(pc, gp)); } +pub export var undef_stack: [stack_size]usize align(8) = undefined; export fn undefined_instruction_stub(pc: u32, gp: *const [13]u32) void { undefined_instruction_handler(setup_reg(pc, gp)); } +pub export var svc_stack: [stack_size]usize align(8) = undefined; export fn software_interrupt_stub(pc: u32, gp: *const [13]u32) void { software_interrupt_handler(setup_reg(pc, gp)); } +pub export var abort_stack: [stack_size]usize align(8) = undefined; export fn prefetch_abort_stub(pc: u32, gp: *const [13]u32) void { prefetch_abort_handler(setup_reg(pc, gp)); } @@ -178,39 +198,40 @@ 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 { +pub export var irq_stack: [stack_size]usize align(8) = undefined; export fn interrupt_stub(pc: u32, gp: *const [13]u32) void { interrupt_handler(setup_reg(pc, gp)); } // TODO; +pub export var fiq_stack: [stack_size]usize align(8) = undefined; export fn fast_interrupt_stub(pc: u32, reg: *Registers) void { _ = pc; _ = reg; } -fn create_trampoline(name: []const u8, offset: []const u8, ret: []const u8) []const u8 { +fn create_trampoline(name: []const u8, offset: []const u8, ret: []const u8, stack: []const u8) []const u8 { return "" ++ ".global " ++ name ++ "\n" ++ ".type " ++ name ++ ", %function\n" ++ name ++ ":\n" ++ + " ldr sp, =" ++ stack ++ "\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-r12, lr}\n" ++ ret; + " pop {r0-r12, lr}\n" ++ ret ++ "\n"; } comptime { - asm (create_trampoline("reset", "4", "")); // doesn't matter - asm (create_trampoline("undefined_instruction", "4", "movs pc, lr")); - 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("fast_interrupt", "4", "subs pc, lr, #4")); + asm (create_trampoline("reset", "4", "", "reset_stack")); // doesn't matter + asm (create_trampoline("undefined_instruction", "4", "movs pc, lr", "undef_stack")); + asm (create_trampoline("software_interrupt", "4", "movs pc, lr", "svc_stack")); + asm (create_trampoline("prefetch_abort", "4", "subs pc, lr, #4", "abort_stack")); + asm (create_trampoline("data_abort", "8", "subs pc, lr, #8", "abort_stack")); + asm (create_trampoline("interrupt", "4", "subs pc, lr, #4", "irq_stack")); + asm (create_trampoline("fast_interrupt", "4", "subs pc, lr, #4", "fiq_stack")); } extern fn reset() void; extern fn undefined_instruction() void; diff --git a/pi/root.zig b/pi/root.zig @@ -8,6 +8,7 @@ pub const switching = @import("./switching.zig"); pub const register = @import("./register.zig"); pub const journal = @import("./journal.zig"); pub const faults = @import("./faults.zig"); +pub const thread = @import("./thread.zig"); pub const debug = @import("./debug.zig"); pub const mem = @import("./mem.zig"); @@ -52,35 +53,6 @@ pub inline fn get_sp() usize { ); } -// TODO; should do something better -const stack_size = 256; // 1kb stacks -pub export var svc_stack: [stack_size]usize align(8) = undefined; -pub export var abort_stack: [stack_size]usize align(8) = undefined; -pub export var irq_stack: [stack_size]usize align(8) = undefined; -pub export var fiq_stack: [stack_size]usize align(8) = undefined; - -pub fn setup_stacks() void { - const original_cpsr = PSR.get_c(); - - _ = PSR.switch_current_mode(.Supervisor); - set_sp(@ptrFromInt(@intFromPtr(&svc_stack) + (svc_stack.len * @sizeOf(usize)))); - - _ = PSR.switch_current_mode(.Undefined); - set_sp(@ptrFromInt(@intFromPtr(&abort_stack) + (abort_stack.len * @sizeOf(usize)))); - - _ = PSR.switch_current_mode(.Abort); - set_sp(@ptrFromInt(@intFromPtr(&abort_stack) + (abort_stack.len * @sizeOf(usize)))); - - _ = PSR.switch_current_mode(.IRQ); - set_sp(@ptrFromInt(@intFromPtr(&irq_stack) + (irq_stack.len * @sizeOf(usize)))); - - _ = PSR.switch_current_mode(.FIQ); - set_sp(@ptrFromInt(@intFromPtr(&fiq_stack) + (fiq_stack.len * @sizeOf(usize)))); - - // Restore - original_cpsr.set_c(); -} - pub fn reboot() noreturn { // Prevent any more interrupts _ = mem.enter_critical_section(); diff --git a/pi/thread.zig b/pi/thread.zig @@ -0,0 +1,132 @@ +const std = @import("std"); + +const interrupts = @import("./interrupts.zig"); +const switching = @import("./switching.zig"); +const PSR = @import("./psr.zig").PSR; +const mem = @import("./mem.zig"); + +pub var tid_counter: usize = 1; +var ready: bool = false; +var threads: std.DoublyLinkedList = .{}; +var scheduler_thread: *Thread = undefined; +var allocator: std.mem.Allocator = undefined; + +pub const Thread = struct { + tid: usize, + node: std.DoublyLinkedList.Node, + + // Store them all + registers: interrupts.Registers, + stack: [1024]usize align(8), +}; + +pub fn init(alloc: std.mem.Allocator) !void { + allocator = alloc; +} + +pub fn destroy() void { + var thread_node = threads.first; + + while (thread_node) |n| { + thread_node = n.next; + allocator.destroy(get_current()); + } +} + +pub fn fork(f: *const fn (?*anyopaque) callconv(.c) void, arg: ?*anyopaque) !void { + var thread = try allocator.create(Thread); + + thread.tid = tid_counter; + tid_counter += 1; + + const stack_top = @intFromPtr(&thread.stack) + thread.stack.len; + var registers: interrupts.Registers = .{ + .gp = .{0} ** 13, + .sp = stack_top, + // trampoline will handle exit (hopefully) + .lr = 0, + .pc = @intFromPtr(&thread_trampoline), + .psr = PSR.get_c(), + }; + + registers.gp[0] = @intFromPtr(f); + if (arg) |a| { + registers.gp[1] = @intFromPtr(a); + } + + thread.registers = registers; + + threads.append(&thread.node); +} + +fn get_current() *Thread { + return @alignCast(@fieldParentPtr("node", threads.first.?)); +} + +pub fn join() !void { + if (threads.first == null) { + // Nothing to do + return; + } + + var thread = try allocator.create(Thread); + thread.tid = 0; + + scheduler_thread = thread; + + @import("devices/mini-uart.zig").print("switching to PC=0x{X}\n", .{get_current().registers.pc}); + + switching.switch_state(&thread.registers, &get_current().registers); + + ready = false; +} + +// Comes in from exception +pub fn preempt(regs: *const interrupts.Registers) void { + // Haven't joined yet + if (!ready) return; + + // Single task + if (threads.first == threads.last) { + return; + } + + if (threads.first == null) { + ready = false; + + return; + } + + const node_task = threads.popFirst().?; + threads.append(node_task); + + const current: *Thread = @alignCast(@fieldParentPtr("node", node_task)); + current.registers = regs.*; + + // Switch to new thread + switching.restore_state(&get_current().registers); +} + +pub fn get_tid() usize { + return get_current().tid; +} + +fn thread_trampoline(func: *const fn (?*anyopaque) callconv(.c) void, arg: ?*anyopaque) callconv(.c) noreturn { + // Won't release as next's CPSR will already reenable + // _ = mem.enter_critical_section(); + @import("devices/mini-uart.zig").print("in trampoline, running PC=0x{X}\n", .{@intFromPtr(func)}); + + ready = true; + + func(arg); + + const current: *Thread = @alignCast(@fieldParentPtr("node", threads.popFirst().?)); + allocator.destroy(current); + + if (threads.first) |node_task| { + const next: *Thread = @alignCast(@fieldParentPtr("node", node_task)); + switching.restore_state(&next.registers); + } else { + switching.restore_state(&scheduler_thread.registers); + } +} diff --git a/programs/syscall.zig b/programs/syscall.zig @@ -28,16 +28,14 @@ comptime { } extern fn syscall_test() void; -noinline fn software_interrupt_handler(pc: u32, registers: *interrupts.Registers) void { - const dup = registers.*; - - uart.writer.print("instruction=0x{X}\n", .{pc}) catch {}; - for (dup.gp, 0..dup.gp.len) |r, i| { +noinline fn software_interrupt_handler(regs: interrupts.Registers) void { + uart.writer.print("instruction=0x{X}\n", .{regs.pc}) catch {}; + for (regs.gp, 0..regs.gp.len) |r, i| { uart.writer.print("r{d} = {d}\n", .{ i, r }) catch {}; } - uart.writer.print("sp = 0x{X}\n", .{dup.sp}) catch {}; - uart.writer.print("lr = 0x{X}\n", .{dup.lr}) catch {}; - uart.writer.print("pc = 0x{X}\n", .{dup.pc}) catch {}; + uart.writer.print("sp = 0x{X}\n", .{regs.sp}) catch {}; + uart.writer.print("lr = 0x{X}\n", .{regs.lr}) catch {}; + uart.writer.print("pc = 0x{X}\n", .{regs.pc}) catch {}; } pub fn main() !void { diff --git a/programs/threads.zig b/programs/threads.zig @@ -0,0 +1,58 @@ +const std = @import("std"); +const pi = @import("pi"); + +const interrupts = pi.interrupts; +const uart = pi.devices.mini_uart; +const clock = pi.devices.clock; +const timer = pi.devices.timer; +const thread = pi.thread; +const mem = pi.mem; + +fn thread_test(arg: ?*anyopaque) callconv(.c) void { + _ = arg; + + while (true) { + // Don't use format for simpler function + const cs = mem.enter_critical_section(); + + uart.write_slice_sync("tid = "); + uart.write_byte_sync(@truncate(thread.get_tid() + 48)); + uart.write_byte_sync('\n'); + + cs.exit(); + } +} + +noinline fn tick(regs: interrupts.Registers) void { + uart.print("preempting PC=0x{X}\n", .{regs.pc}); + thread.preempt(&regs); +} + +pub fn main() !void { + var buffer: [1024 * 1024]u8 = undefined; + var fba: std.heap.FixedBufferAllocator = .init(&buffer); + + try thread.init(fba.allocator()); + + try thread.fork(thread_test, null); + try thread.fork(thread_test, null); + try thread.fork(thread_test, null); + try thread.fork(thread_test, null); + + uart.print("Forked threads tid={}\n", .{thread.tid_counter}); + + timer.set_tick(tick); + uart.print("Setting tick\n", .{}); + + timer.initialize(.Sixteen, 0x32); + + uart.print("Timer Initialized\n", .{}); + + uart.print("Joining\n", .{}); + + try thread.join(); + + uart.print("This should be unreachable\n", .{}); + + while (true) {} +}