commit ad00709e43bdbe9bac83c192a96839aa47eaadfc
parent ad5c9a1189bfda3f02bf087702be75a31deef32c
Author: Sylvia Ivory <git@sivory.net>
Date: Thu, 19 Feb 2026 19:27:57 -0800
No exiting works
Diffstat:
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(¤t.saved_sp, self.get_current().saved_sp);
+ coroutine_context_switch(¤t.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(®s);
+}
+
+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) {}
+}