sylveos

Toy Operating System
Log | Files | Refs

commit bdaf157b915565ce2802382e16bf2dfdfcfbcdf4
parent 84ee98bde8cbb1ae93be63a2559ce4cc0f4c056b
Author: Sylvia Ivory <git@sivory.net>
Date:   Sat, 31 Jan 2026 00:40:06 -0800

Reorganize code

Diffstat:
MJustfile | 28+++++++++++++++++++++-------
Aboot/bootable.zig | 20++++++++++++++++++++
Aboot/bootloader.zig | 23+++++++++++++++++++++++
Aboot/root.zig | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aboot/standalone.zig | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mbuild.zig | 252++++++++++++++++++++++++++++++-------------------------------------------------
Minstaller/bootloader.zig | 2+-
Dinstaller/net.zig | 97-------------------------------------------------------------------------------
Api/devices/clock.zig | 34++++++++++++++++++++++++++++++++++
Api/devices/gpio.zig | 146+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Api/devices/mini-uart.zig | 324+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Api/devices/timer.zig | 116+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rlinker.ld -> pi/linker.ld | 0
Mpi/root.zig | 22++++++++++++++++------
Rsrc/scheduler.zig -> pi/scheduler.zig | 0
Aprograms/bootloader.zig | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aprograms/echo.zig | 28++++++++++++++++++++++++++++
Rsrc/lists.zig -> shared/lists.zig | 0
Ashared/net.zig | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ashared/root.zig | 4++++
Dsrc/bootloader.zig | 90-------------------------------------------------------------------------------
Dsrc/devices/clock.zig | 33---------------------------------
Dsrc/devices/gpio.zig | 145-------------------------------------------------------------------------------
Dsrc/devices/mini-uart.zig | 324-------------------------------------------------------------------------------
Dsrc/devices/timer.zig | 117-------------------------------------------------------------------------------
Dsrc/main.zig | 27---------------------------
Dsrc/root.zig | 128-------------------------------------------------------------------------------
Dsrc/test-runner.zig | 63---------------------------------------------------------------
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(); -}