commit bdaf157b915565ce2802382e16bf2dfdfcfbcdf4
parent 84ee98bde8cbb1ae93be63a2559ce4cc0f4c056b
Author: Sylvia Ivory <git@sivory.net>
Date: Sat, 31 Jan 2026 00:40:06 -0800
Reorganize code
Diffstat:
28 files changed, 1135 insertions(+), 1194 deletions(-)
diff --git a/Justfile b/Justfile
@@ -1,9 +1,23 @@
-build:
- zig build
+build program mode="Bootable":
+ if [ "{{ program }}" = "bootloader" ]; then \
+ zig build -Dmode=Bootloader -Dprogram=bootloader; \
+ else \
+ zig build -Dmode={{ mode }} -Dprogram={{ program }}; \
+ fi
+ arm-none-eabi-objdump -m armv6 -D zig-out/bin/{{ program }}.elf > zig-out/bin/{{ program }}.s
+ du -h zig-out/bin/{{ program }}.elf
-lab name:
- zig build -Dlab={{ name }}
- rm src/*.runner.zig
+install program sd-card="/dev/mmcblk0p1":
+ just build {{ program }} Standalone
+ udisksctl mount -b {{ sd-card }}
+ cp zig-out/bin/{{ program }}.bin $(grep {{ sd-card }} /etc/mtab | cut "-d " -f2)/kernel.img
+ sync $(grep {{ sd-card }} /etc/mtab | cut "-d " -f2)
+ sleep 3 # pray
+ udisksctl unmount -b {{ sd-card }}
-list name="SylveOS": build
- arm-none-eabi-objdump -m armv6 -d zig-out/bin/{{ name }}.elf
+run program:
+ just build {{ program }} Bootable
+ ./zig-out/bin/pi-install zig-out/bin/{{ program }}.bin
+
+clean:
+ rm -rfv .zig-cache zig-out
diff --git a/boot/bootable.zig b/boot/bootable.zig
@@ -0,0 +1,20 @@
+// Boot stub for bootable programs (ran by bootloader)
+
+pub const bootable_asm =
+ \\ mov sp, 0x8000000
+ \\ mov fp, 0x0
+ \\ bl %[kmain_fn:P]
+ \\ bl %[abort_fn:P]
+;
+
+pub fn make(comptime kmain: *const fn () callconv(.c) void, comptime abort: *const fn () callconv(.c) void) type {
+ return struct {
+ pub fn _start() linksection(".kmain") callconv(.naked) noreturn {
+ asm volatile (bootable_asm
+ :
+ : [kmain_fn] "X" (kmain),
+ [abort_fn] "X" (abort),
+ );
+ }
+ };
+}
diff --git a/boot/bootloader.zig b/boot/bootloader.zig
@@ -0,0 +1,23 @@
+const standalone = @import("./standalone.zig");
+
+pub const bootloader_asm =
+ \\ b skip
+ \\ .space 0x200000-0x8004,0
+ \\ skip:
+ \\
+;
+
+pub fn make(comptime kmain: *const fn () callconv(.c) void, comptime abort: *const fn () callconv(.c) void) type {
+ return struct {
+ pub fn _start() linksection(".kmain") callconv(.naked) noreturn {
+ asm volatile (bootloader_asm ++ standalone.standalone_asm
+ :
+ : [CLEAR_MODE_MASK] "i" (standalone.CLEAR_MODE_MASK),
+ [SUPER_MODE] "i" (standalone.SUPER_MODE),
+ [CLEAR_MODE_IRQ_FIQ] "i" (standalone.CLEAR_MODE_IRQ_FIQ),
+ [kmain_fn] "X" (kmain),
+ [abort_fn] "X" (abort),
+ );
+ }
+ };
+}
diff --git a/boot/root.zig b/boot/root.zig
@@ -0,0 +1,77 @@
+const std = @import("std");
+const pi = @import("pi");
+
+const make = @import("boot").make;
+
+const uart = pi.devices.mini_uart;
+
+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 const _start = make(kmain, abort)._start;
+
+export fn kmain() void {
+ uart.initialize(115200, .Gpio14, .Gpio15) catch {};
+
+ 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("Enabling Interrupts\n");
+ uart.enable_interrupts() catch {};
+ pi.interrupts.enable_interrupts();
+
+ uart.write_slice("Entering program\n");
+ uart.flush();
+
+ @import("program").main() catch |e| {
+ uart.writer.print("program returned error: {t}\n", .{e}) catch {};
+ };
+}
+
+export fn abort() noreturn {
+ @branchHint(.cold);
+ while (true) {}
+}
+
+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_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 {};
+ }
+ }
+
+ // TODO; proper abort
+ abort();
+}
+
+pub const panic = std.debug.FullPanic(panic_handler);
diff --git a/boot/standalone.zig b/boot/standalone.zig
@@ -0,0 +1,55 @@
+// Boot stub for standalone programs (directly on pi)
+
+pub const standalone_asm =
+ \\ // Switch to Super mode and disable FIQ/IRQ
+ \\ mrs r0, cpsr
+ \\ and r0, r0, %[CLEAR_MODE_MASK]
+ \\ orr r0, r0, %[SUPER_MODE]
+ \\ orr r0, r0, %[CLEAR_MODE_IRQ_FIQ]
+ \\ msr cpsr, r0
+ \\ // Prefetch flush
+ \\ mov r0, #0
+ \\ mcr p15, 0, r0, c7, c5, 4
+ \\ // Clear BSS
+ \\ mov r0, #0
+ \\ ldr r1, bss_start
+ \\ ldr r2, bss_end
+ \\ subs r2, r2, r1
+ \\ beq L3
+ \\ L2:
+ \\ strb r0, [r1], #1
+ \\ subs r2, r2, #1
+ \\ bne L2
+ \\ L3:
+ \\ // Initialize SP
+ \\ ldr sp, stack_init
+ \\ and sp, sp, -16
+ \\ // Clear the frame pointer
+ \\ mov fp, #0
+ \\ // Boot
+ \\ bl %[kmain_fn:P]
+ \\ bl %[abort_fn:P]
+ \\ bss_start: .word __bss_start__
+ \\ bss_end: .word __bss_end__
+ \\ stack_init: .word 0x08000000
+ \\
+;
+
+pub const CLEAR_MODE_MASK: u32 = 0b11111;
+pub const SUPER_MODE: u32 = 0b10011;
+pub const CLEAR_MODE_IRQ_FIQ: u32 = (@as(u32, 1) << 7) | (@as(u32, 1) << 6);
+
+pub fn make(comptime kmain: *const fn () callconv(.c) void, comptime abort: *const fn () callconv(.c) void) type {
+ return struct {
+ pub fn _start() linksection(".kmain") callconv(.naked) noreturn {
+ asm volatile (standalone_asm
+ :
+ : [CLEAR_MODE_MASK] "i" (CLEAR_MODE_MASK),
+ [SUPER_MODE] "i" (SUPER_MODE),
+ [CLEAR_MODE_IRQ_FIQ] "i" (CLEAR_MODE_IRQ_FIQ),
+ [kmain_fn] "X" (kmain),
+ [abort_fn] "X" (abort),
+ );
+ }
+ };
+}
diff --git a/build.zig b/build.zig
@@ -1,57 +1,12 @@
const std = @import("std");
-const config = @import("build.zig.zon");
+const builtin = @import("builtin");
-const add_exe_ty = *const fn (exe_name: []const u8, path: std.Build.LazyPath, b: *std.Build) anyerror!*std.Build.Step.Compile;
-
-fn add_exe_fake_pi(exe_name: []const u8, path: std.Build.LazyPath, b: *std.Build) !*std.Build.Step.Compile {
- const pi = b.createModule(.{
- .root_source_file = b.path("fake-pi/pi.zig"),
- .target = b.graph.host,
- .optimize = .ReleaseSafe,
- });
-
- const boot = b.createModule(.{
- .root_source_file = path,
- .target = b.graph.host,
- .optimize = .ReleaseSafe,
- });
- boot.addImport("pi", pi);
-
- const fake_pi = b.createModule(.{
- .root_source_file = b.path("fake-pi/main.zig"),
- .target = b.graph.host,
- .optimize = .ReleaseFast,
- .link_libc = true,
- });
- fake_pi.addImport("boot", boot);
-
- // TODO; C should get its own module so we can have fake-pi be in debug for faster comp
- const CFlags = &.{"-O2"};
- fake_pi.addCSourceFile(.{ .file = b.path("include/fake-random.c"), .flags = CFlags });
- fake_pi.addCSourceFile(.{ .file = b.path("include/pi-random.c"), .flags = CFlags });
- fake_pi.addIncludePath(b.path("include/"));
-
- const exe_name_fake = try std.fmt.allocPrint(b.allocator, "{s}-fake-pi", .{exe_name});
- defer b.allocator.free(exe_name_fake);
-
- const exe = b.addExecutable(.{
- .name = exe_name_fake,
- .linkage = .dynamic,
- .root_module = fake_pi,
- });
-
- b.installArtifact(exe);
-
- return exe;
-}
-
-fn add_exe_real(exe_name: []const u8, path: std.Build.LazyPath, b: *std.Build) !*std.Build.Step.Compile {
- // BCM2835 is **very specifically** the ARM1176JZF-S
- // https://www.raspberrypi.com/documentation/computers/processors.html
+fn pi_zero_target(b: *std.Build) std.Build.ResolvedTarget {
var cpu_features = std.Target.arm.cpu.arm1176jzf_s.features;
cpu_features.addFeatureSet(std.Target.arm.featureSet(&[_]std.Target.arm.Feature{.strict_align}));
+ cpu_features.removeFeatureSet(std.Target.arm.featureSet(&[_]std.Target.arm.Feature{.vfp2}));
- const target: std.Target.Query = .{
+ const query: std.Target.Query = .{
.cpu_arch = .arm,
.cpu_model = .{ .explicit = &std.Target.arm.cpu.arm1176jzf_s },
.cpu_features_add = cpu_features,
@@ -59,150 +14,135 @@ fn add_exe_real(exe_name: []const u8, path: std.Build.LazyPath, b: *std.Build) !
.abi = .eabi,
};
- const pi = b.createModule(.{
- .root_source_file = b.path("pi/root.zig"),
- .target = b.graph.host,
- .optimize = .ReleaseSafe,
- });
-
- const net = b.createModule(.{
- .root_source_file = b.path("installer/net.zig"),
- .target = b.graph.host,
- .optimize = .ReleaseSafe,
- });
+ return b.resolveTargetQuery(query);
+}
- const boot = b.createModule(.{
+fn pi_module_opts(target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, path: ?std.Build.LazyPath) std.Build.Module.CreateOptions {
+ return .{
.root_source_file = path,
- .target = b.resolveTargetQuery(target),
- .optimize = .ReleaseSafe,
- .unwind_tables = .none,
- .error_tracing = false,
+ .target = target,
+ .optimize = optimize,
.link_libc = false,
+ .omit_frame_pointer = true,
+ .single_threaded = true,
.no_builtin = true,
- });
- boot.addImport("pi", pi);
- boot.addImport("net", net);
- pi.addImport("boot", boot);
-
- const exe = b.addExecutable(.{
- .name = exe_name,
- .linkage = .static,
- .root_module = boot,
- });
- exe.setLinkerScript(b.path("linker.ld"));
-
- const asm_name = try std.fmt.allocPrint(b.allocator, "{s}.s", .{exe_name});
- defer b.allocator.free(asm_name);
-
- const install_asm = b.addInstallBinFile(exe.getEmittedAsm(), asm_name);
- b.getInstallStep().dependOn(&install_asm.step);
-
- b.installArtifact(exe);
-
- const bin_name = try std.fmt.allocPrint(b.allocator, "{s}.bin", .{exe_name});
- defer b.allocator.free(bin_name);
+ .code_model = .small,
+ };
+}
- const copy = b.addObjCopy(exe.getEmittedBin(), .{
- .format = .bin,
- });
- copy.step.dependOn(&exe.step);
+fn build_pi(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, name: []const u8, program_path: std.Build.LazyPath) !void {
+ const BinaryMode = enum {
+ Bootloader,
+ Standalone,
+ Bootable,
+ };
+ const mode = b.option(BinaryMode, "mode", "specify binary output mode") orelse .Bootable;
- const install_bin = b.addInstallBinFile(copy.getOutput(), bin_name);
- b.getInstallStep().dependOn(&install_bin.step);
+ const shared = b.createModule(pi_module_opts(target, optimize, b.path("shared/root.zig")));
- return exe;
-}
+ const pi = b.createModule(pi_module_opts(target, optimize, b.path("pi/root.zig")));
+ pi.addImport("shared", shared);
-fn lab(name: []const u8, units: []const []const u8, b: *std.Build, add_exe: add_exe_ty) !void {
- for (units) |unit| {
- const exe_name = try std.fmt.allocPrint(b.allocator, "{s}_{s}.elf", .{ name, unit });
- defer b.allocator.free(exe_name);
+ const program = b.createModule(pi_module_opts(target, optimize, program_path));
+ program.addImport("shared", shared);
+ program.addImport("pi", pi);
- const lab_path = try std.fmt.allocPrint(b.allocator, "labs/{s}.zig", .{name});
- defer b.allocator.free(lab_path);
+ // TODO; boot/root.zig could probably just read some config flag we pass down
+ const boot_stub_path = switch (mode) {
+ .Bootable => b.path("boot/bootable.zig"),
+ .Bootloader => b.path("boot/bootloader.zig"),
+ .Standalone => b.path("boot/standalone.zig"),
+ };
+ const boot_stub = b.createModule(pi_module_opts(target, optimize, boot_stub_path));
+ boot_stub.addImport("shared", shared);
- const template =
- \\ const lab = @import("{s}");
- \\ export fn abort() void {{ while(true) {{}} }}
- \\ pub export fn kmain() void {{
- \\ _ = lab.{s}() catch {{}};
- \\ }}
- ;
+ const root = b.createModule(pi_module_opts(target, optimize, b.path("boot/root.zig")));
+ root.addImport("shared", shared);
+ root.addImport("boot", boot_stub);
+ root.addImport("pi", pi);
- const root_src = try std.fmt.allocPrint(b.allocator, template, .{ lab_path, unit });
- defer b.allocator.free(root_src);
+ root.addImport("program", program);
- // Ensure correct imports
- const lab_runner_path = try std.fmt.allocPrint(b.allocator, "src/{s}-{s}.runner.zig", .{ name, unit });
- defer b.allocator.free(lab_runner_path);
+ const exe_name = try std.fmt.allocPrint(b.allocator, "{s}.elf", .{name});
+ defer b.allocator.free(exe_name);
- const relative_path = b.path(lab_runner_path);
- const path = try relative_path.getPath3(b, null).toString(b.allocator);
- defer b.allocator.free(path);
+ const exe = b.addExecutable(.{
+ .name = exe_name,
+ .linkage = .static,
+ .root_module = root,
+ .use_lld = true,
+ .use_llvm = true,
+ });
+ exe.link_function_sections = true;
+ exe.link_data_sections = true;
+ exe.link_gc_sections = true;
+ exe.lto = .full;
- const file = try std.fs.createFileAbsolute(path, .{});
- _ = try file.write(root_src);
- file.close();
+ exe.setLinkerScript(b.path("pi/linker.ld"));
+ b.installArtifact(exe);
- _ = try add_exe(exe_name, relative_path, b);
- }
-}
+ const obj_copy = b.addObjCopy(exe.getEmittedBin(), .{
+ .format = .bin,
+ });
+ obj_copy.step.dependOn(&exe.step);
-fn handle_lab(name: []const u8, b: *std.Build, add_exe: add_exe_ty) !void {
- if (std.mem.eql(u8, name, "2-gpio")) {
- try lab(name, &.{ "blink_1", "blink_2", "loopback", "act_blink", "all" }, b, add_exe);
- } else if (std.mem.eql(u8, name, "4-measure")) {
- try lab(name, &.{"main"}, b, add_exe);
- } else {
- @panic("invalid lab name");
- }
-}
+ const bin_name = try std.fmt.allocPrint(b.allocator, "{s}.bin", .{name});
+ defer b.allocator.free(bin_name);
-fn handle_test(name: []const u8, b: *std.Build) !void {
- if (std.mem.eql(u8, name, "gpio")) {
- try @import("tests/gpio.zig").build(b);
- } else {
- @panic("invalid test name");
- }
+ const install_bin = b.addInstallBinFile(obj_copy.getOutput(), bin_name);
+ b.getInstallStep().dependOn(&install_bin.step);
}
-pub fn build(b: *std.Build) !void {
- const fake_pi = b.option(bool, "fake-pi", "build with fake-pi");
- const add_exe = if (fake_pi orelse false) &add_exe_fake_pi else &add_exe_real;
-
- const lab_opt = b.option([]const u8, "lab", "compile specific lab");
- if (lab_opt) |lab_name| {
- try handle_lab(lab_name, b, add_exe);
-
+fn build_program(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) !void {
+ const program = b.option([]const u8, "program", "specify pi program") orelse {
+ // Only building pi-install
return;
- }
+ };
- const test_opt = b.option([]const u8, "test", "compile specific test suite");
- if (test_opt) |test_name| {
- try handle_test(test_name, b);
+ const program_name = try std.fmt.allocPrint(b.allocator, "programs/{s}.zig", .{program});
+ defer b.allocator.free(program_name);
- return;
- }
+ try build_pi(b, target, optimize, program, b.path(program_name));
+}
- _ = try add_exe(@tagName(config.name), b.path("src/root.zig"), b);
+fn build_installer(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) !void {
+ const shared = b.addModule("shared", .{
+ .root_source_file = b.path("shared/root.zig"),
+ .optimize = optimize,
+ .target = target,
+ });
- const installer = b.createModule(.{
+ const installer = b.addModule("pi-installer", .{
.root_source_file = b.path("installer/root.zig"),
- .target = b.graph.host,
- .optimize = .ReleaseSafe,
.link_libc = true,
+ .optimize = optimize,
+ .target = target,
});
+
installer.addCSourceFile(.{ .file = b.path("include/setup-tty.c") });
installer.addIncludePath(b.path("include/"));
+ installer.addImport("shared", shared);
const clap = b.dependency("clap", .{});
installer.addImport("clap", clap.module("clap"));
const exe = b.addExecutable(.{
.name = "pi-install",
- .linkage = .dynamic,
.root_module = installer,
});
b.installArtifact(exe);
+
+ const run_exe = b.addRunArtifact(exe);
+ const run_step = b.step("pi-install", "run the pi installer");
+ run_step.dependOn(&run_exe.step);
+}
+
+pub fn build(b: *std.Build) !void {
+ const pi_target = pi_zero_target(b);
+ const local_target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast });
+
+ try build_installer(b, local_target, optimize);
+ try build_program(b, pi_target, optimize);
}
diff --git a/installer/bootloader.zig b/installer/bootloader.zig
@@ -1,6 +1,6 @@
const std = @import("std");
-const net = @import("./net.zig");
+const net = @import("shared").bootloader_protocol;
const Message = net.Message;
const Error = net.Error;
diff --git a/installer/net.zig b/installer/net.zig
@@ -1,97 +0,0 @@
-const std = @import("std");
-
-pub const Message = enum(u32) {
- GET_PROG_INFO = 0x11112222,
- PUT_PROG_INFO = 0x33334444,
-
- GET_CODE = 0x55556666,
- PUT_CODE = 0x77778888,
-
- BOOT_SUCCESS = 0x9999AAAA,
- BAD_CODE_ADDR = 0xDEADBEEF,
- BAD_CODE_CKSUM = 0xFEEDFACE,
-};
-
-pub const Error = error{
- BadAddress,
- BadChecksum,
- Timeout,
-};
-
-pub const Header = struct {
- arm_base: u32,
- n_bytes: u32,
- checksum: u32,
-};
-
-pub fn get_u32(r: *std.Io.Reader) !u32 {
- var buffer: [@sizeOf(u32)]u8 = undefined;
- try r.readSliceAll(&buffer);
- const v = std.mem.readInt(u32, &buffer, .little);
-
- return v;
-}
-
-pub fn get_u8(r: *std.Io.Reader) !u8 {
- var buffer: [1]u8 = undefined;
- try r.readSliceAll(&buffer);
- return buffer[0];
-}
-
-pub fn put_u32(w: *std.Io.Writer, v: u32) !void {
- var buffer: [@sizeOf(u32)]u8 = undefined;
- std.mem.writeInt(u32, &buffer, v, .little);
- try w.writeAll(&buffer);
- try w.flush();
-}
-
-pub fn put_u8(w: *std.Io.Writer, v: u8) !void {
- try w.writeByte(v);
- try w.flush();
-}
-
-pub fn put_message(w: *std.Io.Writer, message: Message) !void {
- try put_u32(w, @intFromEnum(message));
-}
-
-pub fn transmit_header(w: *std.Io.Writer, header: Header) !void {
- try put_message(w, .PUT_PROG_INFO);
- try put_u32(w, header.arm_base);
- try put_u32(w, header.n_bytes);
- try put_u32(w, header.checksum);
-}
-
-pub fn receive_header(r: *std.Io.Reader, max_size: u32) !Header {
- const arm_base = try get_u32(r);
- const n_bytes = try get_u32(r);
- const checksum = try get_u32(r);
-
- // TODO; better check
- if (arm_base + n_bytes >= max_size) {
- return Error.BadAddress;
- }
-
- return .{ .arm_base = arm_base, .checksum = checksum, .n_bytes = n_bytes };
-}
-
-pub fn transmit_code(w: *std.Io.Writer, progress: std.Progress.Node, code: []const u8) !void {
- defer progress.end();
- try put_message(w, .PUT_CODE);
- for (code) |byte| {
- try put_u8(w, byte);
- progress.completeOne();
- }
-}
-
-pub fn receive_code(r: *std.Io.Reader, buffer: []u8, header: Header) !void {
- try r.readSliceAll(buffer);
-
- if (std.hash.Crc32.hash(buffer) != header.checksum) {
- return Error.BadChecksum;
- }
-}
-
-pub fn request_code(w: *std.Io.Writer, crc: u32) !void {
- try put_message(w, .GET_CODE);
- try put_u32(w, crc);
-}
diff --git a/pi/devices/clock.zig b/pi/devices/clock.zig
@@ -0,0 +1,34 @@
+const std = @import("std");
+
+const mem = @import("../mem.zig");
+
+const BASE_ADDRESS: usize = mem.BASE_ADDRESS + 0x0000_3000;
+const COUNTER_LOWER: usize = BASE_ADDRESS + 0x0004;
+const COUNTER_UPPER: usize = BASE_ADDRESS + 0x0008;
+
+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);
+
+ return (extended << 32) | lower;
+}
+
+// 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 fn delay_ms(ms: u64) void {
+ delay(ms * 1000);
+}
+
+pub fn delay_s(s: u64) void {
+ delay_ms(s * 1000);
+}
diff --git a/pi/devices/gpio.zig b/pi/devices/gpio.zig
@@ -0,0 +1,146 @@
+const std = @import("std");
+
+const mem = @import("../mem.zig");
+
+pub const Error = error{
+ PinOutOfRange,
+};
+
+// Page 90, Table 6-1: GPIO Register Assignment
+const BASE_ADDRESS: usize = mem.BASE_ADDRESS + 0x0020_0000;
+const GPFSEL0: usize = BASE_ADDRESS + 0x0000;
+const GPSET0: usize = BASE_ADDRESS + 0x001C;
+const GPCLR0: usize = BASE_ADDRESS + 0x0028;
+const GPLEV0: usize = BASE_ADDRESS + 0x0034;
+const GPPUD: usize = BASE_ADDRESS + 0x0094;
+const GPPUDCLK0: usize = BASE_ADDRESS + 0x0098;
+
+pub const MAX_GPIO: u8 = 53;
+const GPIO_PER_FSEL: u8 = 10;
+
+fn gpio_check(pin: u8) Error!void {
+ if (pin > MAX_GPIO) return Error.PinOutOfRange;
+}
+
+fn get_address(pin: u8, offset: usize, chunk: ?u32) Error!*u32 {
+ try gpio_check(pin);
+
+ // One might say "@bitSizeOf(u32)" is redundant but I do not care
+ const index = @divFloor(pin, chunk orelse @bitSizeOf(u32)) * @sizeOf(u32);
+ const address: *u32 = @ptrFromInt(offset + index);
+
+ return address;
+}
+
+// Page 92, Table 6-2: GPIO Alternate function select register 0
+// All function select registers reflect this bit layout
+pub const FunctionSelect = enum(u3) {
+ input = 0b000,
+ output = 0b001,
+
+ alt_fn_0 = 0b100,
+ alt_fn_1 = 0b101,
+ alt_fn_2 = 0b110,
+ alt_fn_3 = 0b111,
+ alt_fn_4 = 0b011,
+ alt_fn_5 = 0b010,
+};
+
+pub fn fn_sel(pin: u8, sel: FunctionSelect) Error!void {
+ const address = try get_address(pin, GPFSEL0, GPIO_PER_FSEL);
+
+ const offset: u5 = @truncate((pin % GPIO_PER_FSEL) * 3);
+ const mask = ~(@as(u32, 0b111) << offset);
+
+ var state = mem.get_u32(address);
+ state &= mask;
+ state |= (@as(u32, @intCast(@intFromEnum(sel))) << offset);
+ mem.put_u32(address, state);
+}
+
+fn addr_bitset(pin: u8, address: *u32) void {
+ const offset: u5 = @truncate(pin % 32);
+ const mask = (@as(u32, 1) << offset);
+
+ // We don't want to preserve the old value
+ // SET and CLR are separated for this purpose
+ mem.put_u32(address, mask);
+}
+
+// Turn on
+fn output_set(pin: u8) Error!void {
+ addr_bitset(pin, try get_address(pin, GPSET0, null));
+}
+
+// Turn off
+fn output_clear(pin: u8) Error!void {
+ addr_bitset(pin, try get_address(pin, GPCLR0, null));
+}
+
+// Public API
+pub fn set_output(pin: u8) Error!void {
+ try fn_sel(pin, .output);
+}
+
+pub fn set_input(pin: u8) Error!void {
+ try fn_sel(pin, .input);
+}
+
+pub fn set_on(pin: u8) Error!void {
+ try output_set(pin);
+}
+
+pub fn set_off(pin: u8) Error!void {
+ try output_clear(pin);
+}
+
+pub fn read(pin: u8) Error!bool {
+ const address = try get_address(pin, GPLEV0, null);
+
+ const offset: u5 = @truncate(pin % 32);
+ const mask = (@as(u32, 1) << offset);
+
+ return (mem.get_u32(address) & mask) == mask;
+}
+
+pub fn write(pin: u8, state: bool) Error!void {
+ if (state) {
+ try set_on(pin);
+ } else {
+ try set_off(pin);
+ }
+}
+
+pub const PullMode = enum(u2) {
+ Off = 0b00,
+ Down = 0b01,
+ Up = 0b10,
+};
+
+fn wait(count: usize) void {
+ var c = count;
+ while (c != 0) {
+ c -= 1;
+ }
+}
+
+pub fn set_pull(pin: u8, pull: PullMode) Error!void {
+ try gpio_check(pin);
+
+ // Set GPPUD
+ mem.put_u32(@ptrFromInt(GPPUD), @intFromEnum(pull));
+
+ // Wait 150 cycles
+ wait(150);
+
+ // Set GPPUDCLK
+ const GPPUDCLK = try get_address(pin, GPPUDCLK0, null);
+ addr_bitset(pin, GPPUDCLK);
+
+ // Wait 150 cycles
+ wait(150);
+
+ // Clear GPPUD & GPPUDCLK
+ mem.put_u32(GPPUDCLK, 0);
+ mem.put_u32(@ptrFromInt(GPPUDCLK0), 0);
+}
diff --git a/pi/devices/mini-uart.zig b/pi/devices/mini-uart.zig
@@ -0,0 +1,324 @@
+const std = @import("std");
+
+const StackRingBuffer = @import("shared").StackRingBuffer;
+
+const interrupts = @import("../interrupts.zig");
+const mem = @import("../mem.zig");
+
+const clock = @import("./clock.zig");
+const gpio = @import("./gpio.zig");
+
+pub const Error = error{ AlreadyInitialized, NotInitialized, InvalidReadLimit } || gpio.Error;
+
+// Page 8: Auxiliary peripherals Register Map
+const BASE_ADDRESS: usize = mem.BASE_ADDRESS + 0x0021_5000;
+const AUX_ENABLES: usize = BASE_ADDRESS + 0x0004;
+const AUX_MU_IO_REG: usize = BASE_ADDRESS + 0x0040;
+const AUX_MU_IER_REG: usize = BASE_ADDRESS + 0x0044;
+const AUX_MU_IIR_REG: usize = BASE_ADDRESS + 0x0048;
+const AUX_MU_LCR_REG: usize = BASE_ADDRESS + 0x004C;
+const AUX_MU_MCR_REG: usize = BASE_ADDRESS + 0x0050;
+const AUX_MU_LSR_REG: usize = BASE_ADDRESS + 0x0054;
+const AUX_MU_MSR_REG: usize = BASE_ADDRESS + 0x0058;
+const AUX_MU_SCRATCH: usize = BASE_ADDRESS + 0x005C;
+const AUX_MU_CNTL_REG: usize = BASE_ADDRESS + 0x0060;
+const AUX_MU_STAT_REG: usize = BASE_ADDRESS + 0x0064;
+const AUX_MU_BAUD_REG: usize = BASE_ADDRESS + 0x0068;
+
+const CLOCK_FREQ = 250_000_000;
+
+const TxPin = enum(u8) {
+ Gpio14 = 14,
+ Gpio32 = 32,
+ Gpio36 = 36,
+};
+const RxPin = enum(u8) {
+ Gpio15 = 15,
+ Gpio33 = 33,
+ Gpio37 = 37,
+};
+
+var initialized = false;
+var interrupts_enabled = false;
+
+pub fn initialize(baud: u32, tx_pin: TxPin, rx_pin: RxPin) Error!void {
+ if (initialized) return Error.AlreadyInitialized;
+
+ // Set GPIO pins first (as specified by manual)
+ // Page 102 specifies which alt mode
+ const tx_fn: gpio.FunctionSelect = switch (tx_pin) {
+ .Gpio14 => .alt_fn_5,
+ .Gpio32 => .alt_fn_3,
+ .Gpio36 => .alt_fn_2,
+ };
+ const rx_fn: gpio.FunctionSelect = switch (rx_pin) {
+ .Gpio15 => .alt_fn_5,
+ .Gpio33 => .alt_fn_3,
+ .Gpio37 => .alt_fn_2,
+ };
+
+ // The error case is technically unreachable due to hardcoding gpio pin values
+ // But at the same time this function body may change with time so it's important
+ // to keep the Error
+ try gpio.fn_sel(@intFromEnum(tx_pin), tx_fn);
+ try gpio.fn_sel(@intFromEnum(rx_pin), rx_fn);
+
+ try gpio.set_pull(@intFromEnum(tx_pin), .Off);
+ try gpio.set_pull(@intFromEnum(rx_pin), .Off);
+
+ // AUX_ENABLES needs bit 0 set to 1
+ // Page 9: AUXENB Register
+ mem.put_u32(@ptrFromInt(AUX_ENABLES), 1);
+
+ // Disable interrupts
+ // Page 12: AUX_MU_IER_REG Register
+ // When bit 0/1 is 0, interrupts are disabled
+ mem.put_u32(@ptrFromInt(AUX_MU_IER_REG), 0);
+
+ // Disable RX/TX during configuration
+ // Page 17: AUX_MU_CNTL_REG Register
+ // When bit 0/1 is 0, UART receiver/transmitter is disabled
+ mem.put_u32(@ptrFromInt(AUX_MU_CNTL_REG), 0);
+
+ // Set data size to 8 bits
+ // Page 14: AUX_MU_LCR_REG Register
+ // When bit 0 is 1, UART is in 8-bit mode, else 7-bit
+ // Errata: "bit 1 must be set for 8 bit mode"
+ mem.put_u32(@ptrFromInt(AUX_MU_LCR_REG), 0b11);
+
+ // Put RTS high (indicate request to send)
+ // Page 14: AUX_MU_MCR_REG Register
+ // When bit 1 is 0, RTS line is high, else low
+ mem.put_u32(@ptrFromInt(AUX_MU_MCR_REG), 0);
+
+ // Clear FIFO
+ // Page 13: AUX_MU_IER_REG Register
+ // When bit 1/2 is 1, receive/transmit will be cleared
+ mem.put_u32(@ptrFromInt(AUX_MU_IIR_REG), 0b110);
+
+ // Set baud rate
+ // Page 11: 2.2.1 Mini UART Implementation Details
+ // The baudrate formula is given as (system_clock_freq)/(8 * (baudrate_reg + 1))
+ mem.put_u32(@ptrFromInt(AUX_MU_BAUD_REG), CLOCK_FREQ / (8 * baud) - 1);
+
+ // Enable RX/TX again
+ // Page 17: AUX_MU_CNTL_REG Register
+ // When bit 0/1 is 1, UART receiver/transmitter is enabled
+ mem.put_u32(@ptrFromInt(AUX_MU_CNTL_REG), 0b11);
+
+ mem.barrier(.Write);
+
+ initialized = true;
+}
+
+// Separate function to allow TX used during early boot
+pub fn enable_interrupts() Error!void {
+ if (!initialized) return Error.NotInitialized;
+ if (interrupts_enabled) return Error.AlreadyInitialized;
+
+ mem.barrier(.Write);
+
+ interrupts.set_exception_handler(.IRQ, uart_handler);
+ interrupts.enable_peripheral_interrupt(.AuxInterrupt);
+
+ // Enable RX/TX Interrupts
+ // Page 12: AUX_MU_IIR_REG Register
+ // Bit 0 - TX Interrupt
+ // Bit 1 - RX Interrupt
+ // Errata: "Bits 3:2 are marked as don't care, but
+ // are actually required in order to receive interrupts."
+
+ // TODO; figure out why TX interrupts aren't working
+ // mem.put_u32_barrier(@ptrFromInt(AUX_MU_IER_REG), 0b0111);
+ mem.put_u32_barrier(@ptrFromInt(AUX_MU_IER_REG), 0b0101);
+
+ interrupts_enabled = true;
+}
+
+// var tx_buffer: [1024]u8 = undefined;
+// var tx_list: std.ArrayList(u8) = .initBuffer(&tx_buffer);
+
+var rx_list: StackRingBuffer(u8, 4096) = .init();
+
+noinline fn uart_handler(pc: usize) void {
+ _ = pc;
+ mem.barrier(.Write);
+ defer mem.barrier(.Write);
+
+ if (!initialized) return;
+ if (!interrupts.pending_peripheral_interrupt(.AuxInterrupt)) return;
+
+ while (true) {
+ const IIR = mem.get_u32(@ptrFromInt(AUX_MU_IIR_REG));
+
+ // UART interrupt pending
+ if ((IIR & 0b1) == 1) return;
+
+ if ((IIR & 0b100) != 0) {
+ // can't do anything with error
+ rx_list.push(read_byte_raw()) catch {};
+ }
+
+ // TODO; figure out why TX interrupts aren't working
+ // if ((IIR & 0b010) != 0) {
+ // // TX empty
+ // // while (can_write()) {
+ // if (tx_list.items.len >= 1) {
+ // const byte = tx_list.orderedRemove(0);
+ // write_byte(byte, true);
+ // } else {
+ // // break;
+ // }
+ // // }
+ // }
+ }
+}
+
+pub fn is_initialized() bool {
+ return initialized;
+}
+
+const Status = packed struct(u32) {
+ rx_has_symbol: bool,
+ tx_has_space: bool,
+ rx_is_idle: bool,
+ tx_is_idle: bool,
+ rx_overrun: bool,
+ tx_full: bool,
+ rts_status: bool,
+ ctx_status: bool,
+ tx_is_empty: bool,
+ tx_done: bool,
+ _reserved_10_15: u6,
+ rx_fill_level: u4,
+ _reserved_20_23: u4,
+ tx_fill_level: u4,
+ _reserved_28_31: u4,
+};
+
+pub fn status() Status {
+ return @bitCast(mem.get_u32_barrier(@ptrFromInt(AUX_MU_STAT_REG)));
+}
+
+pub fn read_queue_length() usize {
+ const cs = mem.enter_critical_section();
+ defer cs.exit();
+
+ return rx_list.length();
+}
+
+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 rx_list.length() > 0; // or can_read_raw();
+}
+
+// // TODO; is this necessary
+// inline fn can_read_raw() bool {
+// return (mem.get_u32_barrier(@ptrFromInt(AUX_MU_LSR_REG)) & 0x01) == 1;
+// }
+
+pub fn read_byte() ?u8 {
+ const cs = mem.enter_critical_section();
+ defer cs.exit();
+
+ return rx_list.pop();
+}
+
+pub fn read_byte_blocking(timeout: ?u64) ?u8 {
+ var wait_until: u64 = std.math.maxInt(u64);
+ if (timeout) |t| {
+ wait_until = clock.current_count() + t;
+ }
+
+ while (true) {
+ if (clock.current_count() > wait_until) {
+ return null;
+ }
+
+ if (read_byte()) |b| {
+ return b;
+ }
+ }
+}
+
+inline fn read_byte_raw() u8 {
+ return @truncate(mem.get_u32_barrier(@ptrFromInt(AUX_MU_IO_REG)));
+}
+
+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_barrier(@ptrFromInt(AUX_MU_LSR_REG)) & 0x20) != 0;
+}
+
+// pub fn write_queued() usize {
+// return tx_list.items.len;
+// }
+
+pub fn write_byte(byte: u8) void {
+ while (!can_write()) {}
+
+ mem.put_u32_barrier(@ptrFromInt(AUX_MU_IO_REG), @as(u32, byte) & 0xFF);
+}
+
+pub fn write_slice(bytes: []const u8) void {
+ for (bytes) |b| {
+ write_byte(b);
+ }
+}
+
+pub fn flush() void {
+ // Loop until there's nothing left in TX queue
+ while (true) {
+ const s = status();
+
+ if (s.tx_is_empty and s.tx_is_idle) break;
+ }
+}
+
+fn writer_drain(io_w: *std.Io.Writer, data: []const []const u8, splat: usize) !usize {
+ if (!initialized) return std.Io.Writer.Error.WriteFailed;
+
+ // We don't care about getting the "parent"
+ _ = io_w;
+ _ = splat;
+
+ for (data[0]) |b| {
+ write_byte(b);
+ }
+
+ return data[0].len;
+}
+
+fn writer_flush(io_w: *std.Io.Writer) !void {
+ if (!initialized) return std.Io.Writer.Error.WriteFailed;
+
+ // We don't care about getting the "parent"
+ _ = io_w;
+
+ flush();
+}
+
+const writer_vtable: std.Io.Writer.VTable = .{ .drain = writer_drain, .flush = writer_flush };
+pub var writer = std.Io.Writer{ .buffer = undefined, .vtable = &writer_vtable };
+
+fn stream(io_r: *std.Io.Reader, io_w: *std.Io.Writer, limit: std.Io.Limit) !usize {
+ // We don't care about getting the "parent"
+ _ = io_r;
+
+ if (limit.toInt()) |max| {
+ for (0..max) |_| {
+ try io_w.writeByte(read_byte_blocking(null).?);
+ }
+
+ return max;
+ } else {
+ return std.Io.Reader.StreamError.ReadFailed;
+ }
+}
+
+const reader_vtable: std.Io.Reader.VTable = .{ .stream = stream };
+pub var reader = std.Io.Reader{ .buffer = undefined, .seek = 0, .end = 0, .vtable = &reader_vtable };
diff --git a/pi/devices/timer.zig b/pi/devices/timer.zig
@@ -0,0 +1,116 @@
+const std = @import("std");
+
+const clock = @import("./clock.zig");
+
+const interrupts = @import("../interrupts.zig");
+const mem = @import("../mem.zig");
+
+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
+}
+
+pub inline fn set_tick(tick: *const fn (usize) 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/linker.ld b/pi/linker.ld
diff --git a/pi/root.zig b/pi/root.zig
@@ -1,7 +1,16 @@
-pub const interrupts = @import("./interrupts.zig");
+pub const Scheduler = @import("scheduler.zig");
pub const PSR = @import("./psr.zig").PSR;
+
+pub const interrupts = @import("./interrupts.zig");
pub const mem = @import("./mem.zig");
+pub const devices = struct {
+ pub const clock = @import("./devices/clock.zig");
+ pub const gpio = @import("./devices/gpio.zig");
+ pub const mini_uart = @import("./devices/mini-uart.zig");
+ pub const timer = @import("./devices/timer.zig");
+};
+
pub inline fn cycle_counter_init() void {
const in: u32 = 1;
asm volatile ("MCR p15, 0, %[in], c15, c12, 0"
@@ -30,12 +39,13 @@ pub inline fn get_sp() usize {
}
// 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;
+const stack_size = 256; // 1kb stacks
+export var svc_stack: [stack_size]usize align(8) = undefined;
+export var abort_stack: [stack_size]usize align(8) = undefined;
+export var irq_stack: [stack_size]usize align(8) = undefined;
+export var fiq_stack: [stack_size]usize align(8) = undefined;
-pub export fn setup_stacks() void {
+pub fn setup_stacks() void {
const original_cpsr = PSR.get_c();
_ = PSR.switch_current_mode(.Undefined);
diff --git a/src/scheduler.zig b/pi/scheduler.zig
diff --git a/programs/bootloader.zig b/programs/bootloader.zig
@@ -0,0 +1,77 @@
+const std = @import("std");
+const pi = @import("pi");
+
+const net = @import("shared").bootloader_protocol;
+
+const uart = pi.devices.mini_uart;
+const clock = pi.devices.clock;
+
+const Message = net.Message;
+const Error = net.Error;
+
+const MAX_ADDR = 0x200000;
+
+pub fn main() !void {
+ // w - UART (safe)
+ // r - UART (safe)
+ const w = &uart.writer;
+ const r = &uart.reader;
+
+ while (true) {
+ // UART cannot fail
+ try net.put_message(w, .GET_PROG_INFO);
+
+ if (uart.read_queue_length() >= 4) {
+ const response = try net.get_u32(r);
+
+ if (response == @intFromEnum(Message.PUT_PROG_INFO)) break;
+ }
+
+ clock.delay_ms(300);
+ }
+
+ const header = net.receive_header(r, MAX_ADDR) catch |e| switch (e) {
+ Error.BadAddress => {
+ try net.put_message(w, .BAD_CODE_ADDR);
+
+ // TODO; reboot
+ return;
+ },
+ else => unreachable,
+ };
+
+ try net.request_code(w, header.checksum);
+
+ while (true) {
+ if (uart.read_queue_length() >= 4) {
+ const response = try net.get_u32(r);
+
+ if (response == @intFromEnum(Message.PUT_CODE)) break;
+ }
+ }
+
+ var start_ptr: [*]u8 = @ptrFromInt(header.arm_base);
+ net.receive_code(r, start_ptr[0..(header.n_bytes)], header) catch |e| switch (e) {
+ Error.BadChecksum => {
+ try net.put_message(w, .BAD_CODE_CKSUM);
+
+ // TODO; reboot
+ return;
+ },
+ else => unreachable,
+ };
+
+ try net.put_message(w, .BOOT_SUCCESS);
+ uart.flush();
+
+ branch_to(@ptrCast(start_ptr));
+}
+
+comptime {
+ asm (
+ \\ .globl branch_to
+ \\ branch_to:
+ \\ bx r0
+ );
+}
+extern fn branch_to(to: *u8) noreturn;
diff --git a/programs/echo.zig b/programs/echo.zig
@@ -0,0 +1,28 @@
+const std = @import("std");
+const pi = @import("pi");
+
+const uart = pi.devices.mini_uart;
+const clock = pi.devices.clock;
+
+pub fn main() !void {
+ while (true) {
+ const status = uart.status();
+
+ uart.writer.print("rx queue: {d}, rx hw: {d}, rx ready: {any}, tx queue: {d}, tx hw: {d}, tx ready: {any}\n", .{
+ uart.read_queue_length(),
+ status.rx_fill_level,
+ uart.can_read(),
+ 0,
+ status.tx_fill_level,
+ uart.can_write(),
+ }) catch {};
+
+ while (uart.can_read()) {
+ var buffer: [1]u8 = .{0};
+ uart.reader.readSliceAll(&buffer) catch {};
+ uart.writer.print(" read: {s}\n", .{buffer}) catch {};
+ }
+
+ clock.delay_s(1);
+ }
+}
diff --git a/src/lists.zig b/shared/lists.zig
diff --git a/shared/net.zig b/shared/net.zig
@@ -0,0 +1,97 @@
+const std = @import("std");
+
+pub const Message = enum(u32) {
+ GET_PROG_INFO = 0x11112222,
+ PUT_PROG_INFO = 0x33334444,
+
+ GET_CODE = 0x55556666,
+ PUT_CODE = 0x77778888,
+
+ BOOT_SUCCESS = 0x9999AAAA,
+ BAD_CODE_ADDR = 0xDEADBEEF,
+ BAD_CODE_CKSUM = 0xFEEDFACE,
+};
+
+pub const Error = error{
+ BadAddress,
+ BadChecksum,
+ Timeout,
+};
+
+pub const Header = struct {
+ arm_base: u32,
+ n_bytes: u32,
+ checksum: u32,
+};
+
+pub fn get_u32(r: *std.Io.Reader) !u32 {
+ var buffer: [@sizeOf(u32)]u8 = undefined;
+ try r.readSliceAll(&buffer);
+ const v = std.mem.readInt(u32, &buffer, .little);
+
+ return v;
+}
+
+pub fn get_u8(r: *std.Io.Reader) !u8 {
+ var buffer: [1]u8 = undefined;
+ try r.readSliceAll(&buffer);
+ return buffer[0];
+}
+
+pub fn put_u32(w: *std.Io.Writer, v: u32) !void {
+ var buffer: [@sizeOf(u32)]u8 = undefined;
+ std.mem.writeInt(u32, &buffer, v, .little);
+ try w.writeAll(&buffer);
+ try w.flush();
+}
+
+pub fn put_u8(w: *std.Io.Writer, v: u8) !void {
+ try w.writeByte(v);
+ try w.flush();
+}
+
+pub fn put_message(w: *std.Io.Writer, message: Message) !void {
+ try put_u32(w, @intFromEnum(message));
+}
+
+pub fn transmit_header(w: *std.Io.Writer, header: Header) !void {
+ try put_message(w, .PUT_PROG_INFO);
+ try put_u32(w, header.arm_base);
+ try put_u32(w, header.n_bytes);
+ try put_u32(w, header.checksum);
+}
+
+pub fn receive_header(r: *std.Io.Reader, max_addr: u32) !Header {
+ const arm_base = try get_u32(r);
+ const n_bytes = try get_u32(r);
+ const checksum = try get_u32(r);
+
+ // TODO; better check
+ if (arm_base + n_bytes >= max_addr) {
+ return Error.BadAddress;
+ }
+
+ return .{ .arm_base = arm_base, .checksum = checksum, .n_bytes = n_bytes };
+}
+
+pub fn transmit_code(w: *std.Io.Writer, progress: std.Progress.Node, code: []const u8) !void {
+ defer progress.end();
+ try put_message(w, .PUT_CODE);
+ for (code) |byte| {
+ try put_u8(w, byte);
+ progress.completeOne();
+ }
+}
+
+pub fn receive_code(r: *std.Io.Reader, buffer: []u8, header: Header) !void {
+ try r.readSliceAll(buffer);
+
+ if (std.hash.Crc32.hash(buffer) != header.checksum) {
+ return Error.BadChecksum;
+ }
+}
+
+pub fn request_code(w: *std.Io.Writer, crc: u32) !void {
+ try put_message(w, .GET_CODE);
+ try put_u32(w, crc);
+}
diff --git a/shared/root.zig b/shared/root.zig
@@ -0,0 +1,4 @@
+pub const bootloader_protocol = @import("./net.zig");
+pub const lists = @import("./lists.zig");
+
+pub const StackRingBuffer = lists.StackRingBuffer;
diff --git a/src/bootloader.zig b/src/bootloader.zig
@@ -1,90 +0,0 @@
-const std = @import("std");
-
-const net = @import("net");
-const pi = @import("pi");
-
-const uart = @import("devices/mini-uart.zig");
-const clock = @import("devices/clock.zig");
-
-const Message = net.Message;
-const Error = net.Error;
-
-const MAX_SIZE = 0x200000 - 0x8004;
-
-// w - UART (safe)
-// r - UART (safe)
-pub fn boot(w: *std.Io.Writer, r: *std.Io.Reader) void {
- while (true) {
- // UART cannot fail
- net.put_message(w, .GET_PROG_INFO) catch @panic("get_prog_info");
-
- if (uart.read_queue_length() >= 4) {
- const response = net.get_u32(r) catch @panic("put_prog_info");
-
- if (response == @intFromEnum(Message.PUT_PROG_INFO)) break;
- }
-
- clock.delay_ms(300);
- }
-
- const header = net.receive_header(r, MAX_SIZE) catch |e| {
- if (e == Error.BadAddress) {
- net.put_message(w, .BAD_CODE_ADDR) catch @panic("bad_code_addr");
-
- // TODO; reboot
- return;
- }
-
- @panic("header");
- };
-
- net.request_code(w, header.checksum) catch @panic("checksum");
-
- while (true) {
- if (uart.read_queue_length() >= 4) {
- const response = net.get_u32(r) catch @panic("put_code");
-
- if (response == @intFromEnum(Message.PUT_CODE)) break;
- }
- }
-
- var start_ptr: [*]u8 = @ptrFromInt(header.arm_base);
- // var start_ptr: [4096]u8 = .{0} ** 4096;
-
- // var hasher = std.hash.Crc32.init();
- // var n: usize = 0;
- // while (n < header.n_bytes) {
- // const read = uart.read_byte_blocking(null).?;
-
- // start_ptr[n] = read;
- // uart.writer.print("TRACE:PUT8:{X}\n", .{read}) catch @panic("read");
- // n += 1;
- // }
- // start_ptr[header.n_bytes - 1] = 0;
- // uart.writer.print("read {d} bytes\nhash: 0x{X}, hash2: 0x{X}, expected 0x{X}\nb64: {b64}\n", .{ n, hasher.final(), std.hash.Crc32.hash(start_ptr[0..header.n_bytes]), header.checksum, start_ptr[0..n] }) catch @panic("debug");
-
- net.receive_code(r, start_ptr[0..(header.n_bytes)], header) catch |e| {
- if (e == Error.BadChecksum) {
- net.put_message(w, .BAD_CODE_CKSUM) catch @panic("bad_code_cksum");
-
- // TODO; reboot
- return;
- }
-
- @panic("code");
- };
-
- net.put_message(w, .BOOT_SUCCESS) catch @panic("boot_success");
- uart.flush();
-
- branch_to(@ptrCast(start_ptr));
-}
-
-comptime {
- asm (
- \\ .globl branch_to
- \\ branch_to:
- \\ bx r0
- );
-}
-extern fn branch_to(to: *u8) noreturn;
diff --git a/src/devices/clock.zig b/src/devices/clock.zig
@@ -1,33 +0,0 @@
-const std = @import("std");
-const mem = @import("pi").mem;
-
-const BASE_ADDRESS: usize = mem.BASE_ADDRESS + 0x0000_3000;
-const COUNTER_LOWER: usize = BASE_ADDRESS + 0x0004;
-const COUNTER_UPPER: usize = BASE_ADDRESS + 0x0008;
-
-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);
-
- return (extended << 32) | lower;
-}
-
-// 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 fn delay_ms(ms: u64) void {
- delay(ms * 1000);
-}
-
-pub fn delay_s(s: u64) void {
- delay_ms(s * 1000);
-}
diff --git a/src/devices/gpio.zig b/src/devices/gpio.zig
@@ -1,145 +0,0 @@
-const std = @import("std");
-const mem = @import("pi").mem;
-
-pub const Error = error{
- PinOutOfRange,
-};
-
-// Page 90, Table 6-1: GPIO Register Assignment
-const BASE_ADDRESS: usize = mem.BASE_ADDRESS + 0x0020_0000;
-const GPFSEL0: usize = BASE_ADDRESS + 0x0000;
-const GPSET0: usize = BASE_ADDRESS + 0x001C;
-const GPCLR0: usize = BASE_ADDRESS + 0x0028;
-const GPLEV0: usize = BASE_ADDRESS + 0x0034;
-const GPPUD: usize = BASE_ADDRESS + 0x0094;
-const GPPUDCLK0: usize = BASE_ADDRESS + 0x0098;
-
-pub const MAX_GPIO: u8 = 53;
-const GPIO_PER_FSEL: u8 = 10;
-
-fn gpio_check(pin: u8) Error!void {
- if (pin > MAX_GPIO) return Error.PinOutOfRange;
-}
-
-fn get_address(pin: u8, offset: usize, chunk: ?u32) Error!*u32 {
- try gpio_check(pin);
-
- // One might say "@bitSizeOf(u32)" is redundant but I do not care
- const index = @divFloor(pin, chunk orelse @bitSizeOf(u32)) * @sizeOf(u32);
- const address: *u32 = @ptrFromInt(offset + index);
-
- return address;
-}
-
-// Page 92, Table 6-2: GPIO Alternate function select register 0
-// All function select registers reflect this bit layout
-pub const FunctionSelect = enum(u3) {
- input = 0b000,
- output = 0b001,
-
- alt_fn_0 = 0b100,
- alt_fn_1 = 0b101,
- alt_fn_2 = 0b110,
- alt_fn_3 = 0b111,
- alt_fn_4 = 0b011,
- alt_fn_5 = 0b010,
-};
-
-pub fn fn_sel(pin: u8, sel: FunctionSelect) Error!void {
- const address = try get_address(pin, GPFSEL0, GPIO_PER_FSEL);
-
- const offset: u5 = @truncate((pin % GPIO_PER_FSEL) * 3);
- const mask = ~(@as(u32, 0b111) << offset);
-
- var state = mem.get_u32(address);
- state &= mask;
- state |= (@as(u32, @intCast(@intFromEnum(sel))) << offset);
- mem.put_u32(address, state);
-}
-
-fn addr_bitset(pin: u8, address: *u32) void {
- const offset: u5 = @truncate(pin % 32);
- const mask = (@as(u32, 1) << offset);
-
- // We don't want to preserve the old value
- // SET and CLR are separated for this purpose
- mem.put_u32(address, mask);
-}
-
-// Turn on
-fn output_set(pin: u8) Error!void {
- addr_bitset(pin, try get_address(pin, GPSET0, null));
-}
-
-// Turn off
-fn output_clear(pin: u8) Error!void {
- addr_bitset(pin, try get_address(pin, GPCLR0, null));
-}
-
-// Public API
-pub fn set_output(pin: u8) Error!void {
- try fn_sel(pin, .output);
-}
-
-pub fn set_input(pin: u8) Error!void {
- try fn_sel(pin, .input);
-}
-
-pub fn set_on(pin: u8) Error!void {
- try output_set(pin);
-}
-
-pub fn set_off(pin: u8) Error!void {
- try output_clear(pin);
-}
-
-pub fn read(pin: u8) Error!bool {
- const address = try get_address(pin, GPLEV0, null);
-
- const offset: u5 = @truncate(pin % 32);
- const mask = (@as(u32, 1) << offset);
-
- return (mem.get_u32(address) & mask) == mask;
-}
-
-pub fn write(pin: u8, state: bool) Error!void {
- if (state) {
- try set_on(pin);
- } else {
- try set_off(pin);
- }
-}
-
-pub const PullMode = enum(u2) {
- Off = 0b00,
- Down = 0b01,
- Up = 0b10,
-};
-
-fn wait(count: usize) void {
- var c = count;
- while (c != 0) {
- c -= 1;
- }
-}
-
-pub fn set_pull(pin: u8, pull: PullMode) Error!void {
- try gpio_check(pin);
-
- // Set GPPUD
- mem.put_u32(@ptrFromInt(GPPUD), @intFromEnum(pull));
-
- // Wait 150 cycles
- wait(150);
-
- // Set GPPUDCLK
- const GPPUDCLK = try get_address(pin, GPPUDCLK0, null);
- addr_bitset(pin, GPPUDCLK);
-
- // Wait 150 cycles
- wait(150);
-
- // Clear GPPUD & GPPUDCLK
- mem.put_u32(GPPUDCLK, 0);
- mem.put_u32(@ptrFromInt(GPPUDCLK0), 0);
-}
diff --git a/src/devices/mini-uart.zig b/src/devices/mini-uart.zig
@@ -1,324 +0,0 @@
-const std = @import("std");
-const pi = @import("pi");
-
-const lists = @import("../lists.zig");
-const clock = @import("clock.zig");
-const gpio = @import("./gpio.zig");
-
-const interrupts = pi.interrupts;
-const mem = pi.mem;
-
-pub const Error = error{ AlreadyInitialized, NotInitialized, InvalidReadLimit } || gpio.Error;
-
-// Page 8: Auxiliary peripherals Register Map
-const BASE_ADDRESS: usize = mem.BASE_ADDRESS + 0x0021_5000;
-const AUX_ENABLES: usize = BASE_ADDRESS + 0x0004;
-const AUX_MU_IO_REG: usize = BASE_ADDRESS + 0x0040;
-const AUX_MU_IER_REG: usize = BASE_ADDRESS + 0x0044;
-const AUX_MU_IIR_REG: usize = BASE_ADDRESS + 0x0048;
-const AUX_MU_LCR_REG: usize = BASE_ADDRESS + 0x004C;
-const AUX_MU_MCR_REG: usize = BASE_ADDRESS + 0x0050;
-const AUX_MU_LSR_REG: usize = BASE_ADDRESS + 0x0054;
-const AUX_MU_MSR_REG: usize = BASE_ADDRESS + 0x0058;
-const AUX_MU_SCRATCH: usize = BASE_ADDRESS + 0x005C;
-const AUX_MU_CNTL_REG: usize = BASE_ADDRESS + 0x0060;
-const AUX_MU_STAT_REG: usize = BASE_ADDRESS + 0x0064;
-const AUX_MU_BAUD_REG: usize = BASE_ADDRESS + 0x0068;
-
-const CLOCK_FREQ = 250_000_000;
-
-const TxPin = enum(u8) {
- Gpio14 = 14,
- Gpio32 = 32,
- Gpio36 = 36,
-};
-const RxPin = enum(u8) {
- Gpio15 = 15,
- Gpio33 = 33,
- Gpio37 = 37,
-};
-
-var initialized = false;
-var interrupts_enabled = false;
-
-pub fn initialize(baud: u32, tx_pin: TxPin, rx_pin: RxPin) Error!void {
- if (initialized) return Error.AlreadyInitialized;
-
- // Set GPIO pins first (as specified by manual)
- // Page 102 specifies which alt mode
- const tx_fn: gpio.FunctionSelect = switch (tx_pin) {
- .Gpio14 => .alt_fn_5,
- .Gpio32 => .alt_fn_3,
- .Gpio36 => .alt_fn_2,
- };
- const rx_fn: gpio.FunctionSelect = switch (rx_pin) {
- .Gpio15 => .alt_fn_5,
- .Gpio33 => .alt_fn_3,
- .Gpio37 => .alt_fn_2,
- };
-
- // The error case is technically unreachable due to hardcoding gpio pin values
- // But at the same time this function body may change with time so it's important
- // to keep the Error
- try gpio.fn_sel(@intFromEnum(tx_pin), tx_fn);
- try gpio.fn_sel(@intFromEnum(rx_pin), rx_fn);
-
- try gpio.set_pull(@intFromEnum(tx_pin), .Off);
- try gpio.set_pull(@intFromEnum(rx_pin), .Off);
-
- // AUX_ENABLES needs bit 0 set to 1
- // Page 9: AUXENB Register
- mem.put_u32(@ptrFromInt(AUX_ENABLES), 1);
-
- // Disable interrupts
- // Page 12: AUX_MU_IER_REG Register
- // When bit 0/1 is 0, interrupts are disabled
- mem.put_u32(@ptrFromInt(AUX_MU_IER_REG), 0);
-
- // Disable RX/TX during configuration
- // Page 17: AUX_MU_CNTL_REG Register
- // When bit 0/1 is 0, UART receiver/transmitter is disabled
- mem.put_u32(@ptrFromInt(AUX_MU_CNTL_REG), 0);
-
- // Set data size to 8 bits
- // Page 14: AUX_MU_LCR_REG Register
- // When bit 0 is 1, UART is in 8-bit mode, else 7-bit
- // Errata: "bit 1 must be set for 8 bit mode"
- mem.put_u32(@ptrFromInt(AUX_MU_LCR_REG), 0b11);
-
- // Put RTS high (indicate request to send)
- // Page 14: AUX_MU_MCR_REG Register
- // When bit 1 is 0, RTS line is high, else low
- mem.put_u32(@ptrFromInt(AUX_MU_MCR_REG), 0);
-
- // Clear FIFO
- // Page 13: AUX_MU_IER_REG Register
- // When bit 1/2 is 1, receive/transmit will be cleared
- mem.put_u32(@ptrFromInt(AUX_MU_IIR_REG), 0b110);
-
- // Set baud rate
- // Page 11: 2.2.1 Mini UART Implementation Details
- // The baudrate formula is given as (system_clock_freq)/(8 * (baudrate_reg + 1))
- mem.put_u32(@ptrFromInt(AUX_MU_BAUD_REG), CLOCK_FREQ / (8 * baud) - 1);
-
- // Enable RX/TX again
- // Page 17: AUX_MU_CNTL_REG Register
- // When bit 0/1 is 1, UART receiver/transmitter is enabled
- mem.put_u32(@ptrFromInt(AUX_MU_CNTL_REG), 0b11);
-
- mem.barrier(.Write);
-
- initialized = true;
-}
-
-// Separate function to allow TX used during early boot
-pub fn enable_interrupts() Error!void {
- if (!initialized) return Error.NotInitialized;
- if (interrupts_enabled) return Error.AlreadyInitialized;
-
- mem.barrier(.Write);
-
- interrupts.set_exception_handler(.IRQ, uart_handler);
- interrupts.enable_peripheral_interrupt(.AuxInterrupt);
-
- // Enable RX/TX Interrupts
- // Page 12: AUX_MU_IIR_REG Register
- // Bit 0 - TX Interrupt
- // Bit 1 - RX Interrupt
- // Errata: "Bits 3:2 are marked as don't care, but
- // are actually required in order to receive interrupts."
-
- // TODO; figure out why TX interrupts aren't working
- // mem.put_u32_barrier(@ptrFromInt(AUX_MU_IER_REG), 0b0111);
- mem.put_u32_barrier(@ptrFromInt(AUX_MU_IER_REG), 0b0101);
-
- interrupts_enabled = true;
-}
-
-// var tx_buffer: [1024]u8 = undefined;
-// var tx_list: std.ArrayList(u8) = .initBuffer(&tx_buffer);
-
-var rx_list: lists.StackRingBuffer(u8, 4096) = .init();
-
-noinline fn uart_handler(pc: usize) void {
- _ = pc;
- mem.barrier(.Write);
- defer mem.barrier(.Write);
-
- if (!initialized) return;
- if (!interrupts.pending_peripheral_interrupt(.AuxInterrupt)) return;
-
- while (true) {
- const IIR = mem.get_u32(@ptrFromInt(AUX_MU_IIR_REG));
-
- // UART interrupt pending
- if ((IIR & 0b1) == 1) return;
-
- if ((IIR & 0b100) != 0) {
- // can't do anything with error
- rx_list.push(read_byte_raw()) catch {};
- }
-
- // TODO; figure out why TX interrupts aren't working
- // if ((IIR & 0b010) != 0) {
- // // TX empty
- // // while (can_write()) {
- // if (tx_list.items.len >= 1) {
- // const byte = tx_list.orderedRemove(0);
- // write_byte(byte, true);
- // } else {
- // // break;
- // }
- // // }
- // }
- }
-}
-
-pub fn is_initialized() bool {
- return initialized;
-}
-
-const Status = packed struct(u32) {
- rx_has_symbol: bool,
- tx_has_space: bool,
- rx_is_idle: bool,
- tx_is_idle: bool,
- rx_overrun: bool,
- tx_full: bool,
- rts_status: bool,
- ctx_status: bool,
- tx_is_empty: bool,
- tx_done: bool,
- _reserved_10_15: u6,
- rx_fill_level: u4,
- _reserved_20_23: u4,
- tx_fill_level: u4,
- _reserved_28_31: u4,
-};
-
-pub fn status() Status {
- return @bitCast(mem.get_u32_barrier(@ptrFromInt(AUX_MU_STAT_REG)));
-}
-
-pub fn read_queue_length() usize {
- const cs = mem.enter_critical_section();
- defer cs.exit();
-
- return rx_list.length();
-}
-
-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 rx_list.length() > 0; // or can_read_raw();
-}
-
-// // TODO; is this necessary
-// inline fn can_read_raw() bool {
-// return (mem.get_u32_barrier(@ptrFromInt(AUX_MU_LSR_REG)) & 0x01) == 1;
-// }
-
-pub fn read_byte() ?u8 {
- const cs = mem.enter_critical_section();
- defer cs.exit();
-
- return rx_list.pop();
-}
-
-pub fn read_byte_blocking(timeout: ?u64) ?u8 {
- var wait_until: u64 = std.math.maxInt(u64);
- if (timeout) |t| {
- wait_until = clock.current_count() + t;
- }
-
- while (true) {
- if (clock.current_count() > wait_until) {
- return null;
- }
-
- if (read_byte()) |b| {
- return b;
- }
- }
-}
-
-inline fn read_byte_raw() u8 {
- return @truncate(mem.get_u32_barrier(@ptrFromInt(AUX_MU_IO_REG)));
-}
-
-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_barrier(@ptrFromInt(AUX_MU_LSR_REG)) & 0x20) != 0;
-}
-
-// pub fn write_queued() usize {
-// return tx_list.items.len;
-// }
-
-pub fn write_byte(byte: u8) void {
- while (!can_write()) {}
-
- mem.put_u32_barrier(@ptrFromInt(AUX_MU_IO_REG), @as(u32, byte) & 0xFF);
-}
-
-pub fn write_slice(bytes: []const u8) void {
- for (bytes) |b| {
- write_byte(b);
- }
-}
-
-pub fn flush() void {
- // Loop until there's nothing left in TX queue
- while (true) {
- const s = status();
-
- if (s.tx_is_empty and s.tx_is_idle) break;
- }
-}
-
-fn writer_drain(io_w: *std.Io.Writer, data: []const []const u8, splat: usize) !usize {
- if (!initialized) return std.Io.Writer.Error.WriteFailed;
-
- // We don't care about getting the "parent"
- _ = io_w;
- _ = splat;
-
- for (data[0]) |b| {
- write_byte(b);
- }
-
- return data[0].len;
-}
-
-fn writer_flush(io_w: *std.Io.Writer) !void {
- if (!initialized) return std.Io.Writer.Error.WriteFailed;
-
- // We don't care about getting the "parent"
- _ = io_w;
-
- flush();
-}
-
-const writer_vtable: std.Io.Writer.VTable = .{ .drain = writer_drain, .flush = writer_flush };
-pub var writer = std.Io.Writer{ .buffer = undefined, .vtable = &writer_vtable };
-
-fn stream(io_r: *std.Io.Reader, io_w: *std.Io.Writer, limit: std.Io.Limit) !usize {
- // We don't care about getting the "parent"
- _ = io_r;
-
- if (limit.toInt()) |max| {
- for (0..max) |_| {
- try io_w.writeByte(read_byte_blocking(null).?);
- }
-
- return max;
- } else {
- return std.Io.Reader.StreamError.ReadFailed;
- }
-}
-
-const reader_vtable: std.Io.Reader.VTable = .{ .stream = stream };
-pub var reader = std.Io.Reader{ .buffer = undefined, .seek = 0, .end = 0, .vtable = &reader_vtable };
diff --git a/src/devices/timer.zig b/src/devices/timer.zig
@@ -1,117 +0,0 @@
-const std = @import("std");
-const pi = @import("pi");
-
-const clock = @import("./clock.zig");
-
-const interrupts = pi.interrupts;
-const mem = pi.mem;
-
-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
-}
-
-pub inline fn set_tick(tick: *const fn (usize) 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/src/main.zig b/src/main.zig
@@ -1,27 +0,0 @@
-const std = @import("std");
-
-const uart = @import("devices/mini-uart.zig");
-const clock = @import("devices/clock.zig");
-
-pub fn main() !void {
- while (true) {
- const status = uart.status();
-
- uart.writer.print("rx queue: {d}, rx hw: {d}, rx ready: {any}, tx queue: {d}, tx hw: {d}, tx ready: {any}\n", .{
- uart.read_queue_length(),
- status.rx_fill_level,
- uart.can_read(),
- 0,
- status.tx_fill_level,
- uart.can_write(),
- }) catch {};
-
- while (uart.can_read()) {
- var buffer: [1]u8 = .{0};
- uart.reader.readSliceAll(&buffer) catch {};
- uart.writer.print(" read: {s}\n", .{buffer}) catch {};
- }
-
- clock.delay_s(1);
- }
-}
diff --git a/src/root.zig b/src/root.zig
@@ -1,128 +0,0 @@
-const std = @import("std");
-const pi = @import("pi");
-
-pub const uart = @import("devices/mini-uart.zig");
-const bootloader = @import("./bootloader.zig");
-const user_main = @import("main.zig").main;
-
-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("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("Enabling Interrupts\n");
- uart.enable_interrupts() catch {};
- pi.interrupts.enable_interrupts();
-
- uart.write_slice("Starting bootloader:\n");
- bootloader.boot(&uart.writer, &uart.reader);
-
- // uart.write_slice("Calling user main\n");
- // user_main() catch |e| {
- // uart.writer.print("main returned error: {t}\n", .{e}) catch {};
- // };
-}
-
-export const __stack_init__: u32 = 0x08000000;
-
-export fn _start() linksection(".kmain") callconv(.naked) noreturn {
- // Add space for bootloader
- // asm volatile (
- // \\ mov sp, 0x08000000
- // \\ mov fp, 0x0
- // \\ bl kmain
- // \\ bl abort
- // );
- asm volatile (
- \\ b skip
- \\ .space 0x200000-0x8004,0
- \\ skip:
- \\ // Switch to Super mode and disable FIQ/IRQ
- \\ mrs r0, cpsr
- \\ and r0, r0, %[CLEAR_MODE_MASK]
- \\ orr r0, r0, %[SUPER_MODE]
- \\ orr r0, r0, %[CLEAR_MODE_IRQ_FIQ]
- \\ msr cpsr, r0
- \\ // Prefetch flush
- \\ mov r0, #0
- \\ mcr p15, 0, r0, c7, c5, 4
- \\ // Clear BSS
- \\ mov r0, #0
- \\ ldr r1, bss_start
- \\ ldr r2, bss_end
- \\ subs r2, r2, r1
- \\ beq L3
- \\ L2:
- \\ strb r0, [r1], #1
- \\ subs r2, r2, #1
- \\ bne L2
- \\ L3:
- \\ // Initialize SP
- \\ ldr sp, stack_init
- \\ and sp, sp, -16
- \\ // Clear the frame pointer
- \\ mov fp, #0
- \\ // Boot
- \\ bl kmain
- \\ bl abort
- \\ bss_start: .word __bss_start__
- \\ bss_end: .word __bss_end__
- \\ stack_init: .word __stack_init__
- :
- : [CLEAR_MODE_MASK] "i" (@as(u32, 0b11111)),
- [SUPER_MODE] "i" (@as(u32, 0b10011)),
- [CLEAR_MODE_IRQ_FIQ] "i" ((@as(u32, 1) << 7) | (@as(u32, 1) << 6)),
- );
-}
-
-export fn abort() noreturn {
- @branchHint(.cold);
- while (true) {}
-}
-
-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_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 {};
- }
- }
-
- // TODO; proper abort
- abort();
-}
-
-pub const panic = std.debug.FullPanic(panic_handler);
diff --git a/src/test-runner.zig b/src/test-runner.zig
@@ -1,63 +0,0 @@
-const std = @import("std");
-
-const gpio = @import("devices/gpio.zig");
-const root = @import("root.zig");
-const c = @cImport({
- @cInclude("rpi.h");
-});
-
-export fn output(format: [*:0]const u8, ...) u32 {
- var ap = @cVaStart();
- defer @cVaEnd(&ap);
-
- _ = format;
- return 0;
-}
-
-export fn panic(format: [*:0]const u8, ...) u32 {
- var ap = @cVaStart();
- defer @cVaEnd(&ap);
-
- _ = format;
- return 0;
-}
-
-extern fn gpio_panic_pin(pin: u32) void;
-extern fn gpio_panic_func(func: u32) void;
-
-fn gpio_panic(pin: u32) noreturn {
- // only 1 error code
- gpio_panic_pin(pin);
- std.process.exit(1);
-}
-
-export fn gpio_read(pin: u32) bool {
- return gpio.read(@truncate(pin)) catch gpio_panic(pin);
-}
-export fn gpio_write(pin: u32, state: bool) void {
- gpio.write(@truncate(pin), state) catch gpio_panic(pin);
-}
-
-export fn gpio_set_on(pin: u32) void {
- gpio.set_on(@truncate(pin)) catch gpio_panic(pin);
-}
-export fn gpio_set_off(pin: u32) void {
- gpio.set_off(@truncate(pin)) catch gpio_panic(pin);
-}
-
-export fn gpio_set_input(pin: u32) void {
- gpio.set_input(@truncate(pin)) catch gpio_panic(pin);
-}
-export fn gpio_set_output(pin: u32) void {
- gpio.set_output(@truncate(pin)) catch gpio_panic(pin);
-}
-
-export fn gpio_set_function(pin: u32, func: u32) void {
- if ((func & 0b111) != func) gpio_panic_func(func);
-
- gpio.fn_sel(@truncate(pin), @enumFromInt(func & 0b111)) catch gpio_panic(pin);
-}
-
-pub fn kmain() void {
- c.notmain();
-}