sylveos

Toy Operating System
Log | Files | Refs

commit da6ca5a829f07c4bfcf9ffafb5eece2828ee4c09
parent 1831e7bc39f00ce900135d03aa188bb1cc1afb90
Author: Sylvia Ivory <git@sivory.net>
Date:   Fri, 30 Jan 2026 19:06:25 -0800

Bootloading

Diffstat:
Mbuild.zig | 24++++++++++++++++++++++++
Ainstaller/net.zig | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpi/mem.zig | 33+++++++++++++++++++++++++++++++++
Asrc/bootloader.zig | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/devices/mini-uart.zig | 99+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Asrc/lists.zig | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/main.zig | 28+++++++++++++++++++++++++++-
Msrc/root.zig | 82++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
8 files changed, 434 insertions(+), 74 deletions(-)

diff --git a/build.zig b/build.zig @@ -65,6 +65,12 @@ fn add_exe_real(exe_name: []const u8, path: std.Build.LazyPath, b: *std.Build) ! .optimize = .ReleaseSafe, }); + const net = b.createModule(.{ + .root_source_file = b.path("installer/net.zig"), + .target = b.graph.host, + .optimize = .ReleaseSafe, + }); + const boot = b.createModule(.{ .root_source_file = path, .target = b.resolveTargetQuery(target), @@ -75,6 +81,7 @@ fn add_exe_real(exe_name: []const u8, path: std.Build.LazyPath, b: *std.Build) ! .no_builtin = true, }); boot.addImport("pi", pi); + boot.addImport("net", net); pi.addImport("boot", boot); const exe = b.addExecutable(.{ @@ -178,4 +185,21 @@ pub fn build(b: *std.Build) !void { } _ = try add_exe(@tagName(config.name), b.path("src/root.zig"), b); + + const installer = b.createModule(.{ + .root_source_file = b.path("installer/root.zig"), + .target = b.graph.host, + .optimize = .ReleaseSafe, + .link_libc = true, + }); + installer.addCSourceFile(.{ .file = b.path("include/setup-tty.c") }); + installer.addIncludePath(b.path("include/")); + + const exe = b.addExecutable(.{ + .name = "pi-install", + .linkage = .dynamic, + .root_module = installer, + }); + + b.installArtifact(exe); } diff --git a/installer/net.zig b/installer/net.zig @@ -0,0 +1,103 @@ +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); + + if (@import("builtin").abi.isGnu()) { + std.debug.print("GET32:0x{X}\n", .{v}); + } + + 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 { + if (@import("builtin").abi.isGnu()) { + std.debug.print("PUT32:0x{X}\n", .{v}); + } + + 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, code: []const u8) !void { + try put_message(w, .PUT_CODE); + for (code) |byte| { + try put_u8(w, byte); + } +} + +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/mem.zig b/pi/mem.zig @@ -61,3 +61,36 @@ pub inline fn get_u32_barrier(address: *allowzero u32) u32 { barrier(.Read); return get_u32(address); } + +pub const CriticalSection = struct { + const Self = @This(); + + saved_cpsr: u32, + + // TODO; barrier? + pub inline fn enter() Self { + var cpsr: u32 = undefined; + + asm volatile ( + \\ mrs %[cpsr], cpsr + \\ cpsid i + : [cpsr] "=r" (cpsr), + : + : .{ .memory = true }); + + return .{ .saved_cpsr = cpsr }; + } + + pub inline fn exit(self: Self) void { + asm volatile ( + \\ msr cpsr_c, %[cpsr] + : + : [cpsr] "r" (self.saved_cpsr), + : .{ + .cpsr = true, + .memory = true, + }); + } +}; + +pub const enter_critical_section = CriticalSection.enter; diff --git a/src/bootloader.zig b/src/bootloader.zig @@ -0,0 +1,90 @@ +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/mini-uart.zig b/src/devices/mini-uart.zig @@ -1,6 +1,8 @@ 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; @@ -109,6 +111,7 @@ pub fn initialize(baud: u32, tx_pin: TxPin, rx_pin: RxPin) Error!void { 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; @@ -135,13 +138,14 @@ pub fn enable_interrupts() Error!void { // var tx_buffer: [1024]u8 = undefined; // var tx_list: std.ArrayList(u8) = .initBuffer(&tx_buffer); -var rx_buffer: [1024]u8 = undefined; -var rx_list: std.ArrayList(u8) = .initBuffer(&rx_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) { @@ -151,7 +155,8 @@ noinline fn uart_handler(pc: usize) void { if ((IIR & 0b1) == 1) return; if ((IIR & 0b100) != 0) { - rx_list.appendAssumeCapacity(read_byte(true)); + // can't do anything with error + rx_list.push(read_byte_raw()) catch {}; } // TODO; figure out why TX interrupts aren't working @@ -167,8 +172,6 @@ noinline fn uart_handler(pc: usize) void { // // } // } } - - mem.barrier(.Write); } pub fn is_initialized() bool { @@ -197,36 +200,51 @@ 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 (mem.get_u32_barrier(@ptrFromInt(AUX_MU_LSR_REG)) & 0x01) == 1; + return rx_list.length() > 0; // or can_read_raw(); } -pub fn read_queued() usize { - return rx_list.items.len; +// // 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(force: bool) u8 { - if (force) { - // Read from FIFO - // Page 11: AUX_MU_IO_REG Register - // Bits 7:0 holds data from the FIFO - return @truncate(mem.get_u32_barrier(@ptrFromInt(AUX_MU_IO_REG))); +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; } - if (rx_list.items.len > 0) { - // Pop from RX buffer instead - // TODO; use FIFO - const byte = rx_list.orderedRemove(0); - return byte; - } + while (true) { + if (clock.current_count() > wait_until) { + return null; + } - // TODO; support timeout/async - while (!can_read()) {} + if (read_byte()) |b| { + return b; + } + } +} - return read_byte(true); +inline fn read_byte_raw() u8 { + return @truncate(mem.get_u32_barrier(@ptrFromInt(AUX_MU_IO_REG))); } pub fn can_write() bool { @@ -240,34 +258,25 @@ pub fn can_write() bool { // return tx_list.items.len; // } -pub fn write_byte(byte: u8, force: bool) void { - if (force or can_write()) { - // Write into FIFO - // Page 11: AUX_MU_IO_REG Register - // Bits 7:0 holds data from the FIFO - mem.put_u32_barrier(@ptrFromInt(AUX_MU_IO_REG), @as(u32, byte) & 0xFF); - return; - } - - // if (interrupts_enabled) { - // // Throw onto queue, will handle later - // tx_list.appendAssumeCapacity(byte); - // return; - // } - +pub fn write_byte(byte: u8) void { while (!can_write()) {} - write_byte(byte, false); + + 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, false); + write_byte(b); } } pub fn flush() void { // Loop until there's nothing left in TX queue - while (status().tx_fill_level != 0) {} + 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 { @@ -278,7 +287,7 @@ fn writer_drain(io_w: *std.Io.Writer, data: []const []const u8, splat: usize) !u _ = splat; for (data[0]) |b| { - write_byte(b, false); + write_byte(b); } return data[0].len; @@ -300,9 +309,9 @@ fn stream(io_r: *std.Io.Reader, io_w: *std.Io.Writer, limit: std.Io.Limit) !usiz // We don't care about getting the "parent" _ = io_r; - if (limit.toInt()) |*max| { + if (limit.toInt()) |max| { for (0..max) |_| { - try io_w.writeByte(read_byte(false)); + try io_w.writeByte(read_byte_blocking(null).?); } return max; @@ -312,4 +321,4 @@ fn stream(io_r: *std.Io.Reader, io_w: *std.Io.Writer, limit: std.Io.Limit) !usiz } const reader_vtable: std.Io.Reader.VTable = .{ .stream = stream }; -pub var reader = std.Io.Reader{ .buffer = undefined, .vtable = &reader_vtable }; +pub var reader = std.Io.Reader{ .buffer = undefined, .seek = 0, .end = 0, .vtable = &reader_vtable }; diff --git a/src/lists.zig b/src/lists.zig @@ -0,0 +1,49 @@ +pub const Error = error{ + BufferOverflow, +}; + +pub fn StackRingBuffer(comptime T: type, comptime capacity: comptime_int) type { + return struct { + const Self = @This(); + + items: [capacity]T, + write_idx: usize, + read_idx: usize, + count: usize, + + pub fn init() Self { + return Self{ + .items = undefined, + .write_idx = 0, + .read_idx = 0, + .count = 0, + }; + } + + pub fn push(self: *Self, value: T) !void { + if (self.count >= capacity) { + return Error.BufferOverflow; + } + + self.items[self.write_idx] = value; + self.write_idx = (self.write_idx + 1) % capacity; + self.count += 1; + } + + pub fn pop(self: *Self) ?T { + if (self.count == 0) { + return null; + } + + const value = self.items[self.read_idx]; + self.read_idx = (self.read_idx + 1) % capacity; + self.count -= 1; + + return value; + } + + pub fn length(self: *const Self) usize { + return self.count; + } + }; +} diff --git a/src/main.zig b/src/main.zig @@ -1 +1,27 @@ -pub fn main() !void {} +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 @@ -2,27 +2,12 @@ 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; -extern const __bss_start__: usize; -extern const __bss_end__: usize; - -fn zero_bss() void { - // No bss data allocated - if (__bss_start__ >= __bss_end__) return; - - const n: usize = @divFloor(__bss_end__ - __bss_start__, @sizeOf(u32)); - - for (0..n) |b| { - // Force a write - const ptr: *allowzero u32 = @ptrFromInt(__bss_start__ + b * @sizeOf(u32)); - pi.mem.put_u32(ptr, 0); - } -} - fn initialize_interrupts() void { uart.write_slice(" Disabling interrupts\n"); - pi.interrupts.disable_interrupts(); + _ = pi.interrupts.disable_interrupts(); pi.mem.barrier(.Write); uart.write_slice(" Clearing interrupt flags\n"); @@ -37,11 +22,6 @@ fn initialize_interrupts() void { export fn kmain() void { uart.initialize(115200, .Gpio14, .Gpio15) catch {}; - uart.write_slice("Clearing bss\n"); - uart.writer.print(" __bss_start__ = 0x{X}\n", .{__bss_start__}) catch {}; - uart.writer.print(" __bss_end__ = 0x{X}\n", .{__bss_end__}) catch {}; - zero_bss(); - uart.write_slice("Initializing interrupts\n"); initialize_interrupts(); @@ -58,18 +38,64 @@ export fn kmain() void { uart.enable_interrupts() catch {}; pi.interrupts.enable_interrupts(); - uart.write_slice("Calling user main\n"); + uart.write_slice("Starting bootloader:\n"); + bootloader.boot(&uart.writer, &uart.reader); - user_main() catch |e| { - uart.writer.print("main returned error: {t}\n", .{e}) catch {}; - }; + // 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 ( - \\ mov sp, 0x800000 + \\ 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)), ); } @@ -84,7 +110,7 @@ fn panic_handler(msg: []const u8, trace_addr: ?usize) noreturn { if (uart.is_initialized()) { uart.write_slice("kernel panic: "); uart.write_slice(msg); - uart.write_byte('\n', false); + uart.write_byte('\n'); var it = std.debug.StackIterator.init(trace_addr, null); var ix: usize = 0;