sylveos

Toy Operating System
Log | Files | Refs

commit f328e76f70d63511c1c6f5150cbf2cdee341144d
parent bb0addad903bc4ffb19bee59f33dd6760152e342
Author: Sylvia Ivory <git@sivory.net>
Date:   Fri, 13 Mar 2026 16:39:18 -0700

Bootloader

Diffstat:
MJustfile | 9++++++++-
Mbuild.zig | 54+++++++++++++++++++++++++++++++++++++++++++++++++-----
Mpi/fs/mbr.zig | 4++--
Asylveos/linker.ld | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Asylveos/main.zig | 40++++++++++++++++++++++++++++++++++++++++
Msylveos/memory.zig | 32+++++++++++++++++---------------
Msylveos/root.zig | 166+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
7 files changed, 297 insertions(+), 61 deletions(-)

diff --git a/Justfile b/Justfile @@ -1,6 +1,9 @@ build program mode="Bootable": if [ "{{ program }}" = "bootloader" ]; then \ zig build -Dmode=Bootloader -Dprogram=bootloader; \ + elif [ "{{ program }}" = "sylveos" ]; then \ + zig build -Dmode={{ mode }} -Dprogram={{ program }} || exit 1; \ + arm-none-eabi-objdump -m armv6 -D zig-out/bin/{{ program }}-vm.elf > zig-out/bin/{{ program }}-vm.s; \ else \ zig build -Dmode={{ mode }} -Dprogram={{ program }}; \ fi @@ -27,7 +30,11 @@ tools *args: run program *args: just build {{ program }} Bootable - just tools zig-out/bin/{{ program }}.bin {{ args }} + if [ "{{ program }}" = "sylveos" ]; then \ + just tools zig-out/bin/{{ program }}-vm.bin --arm-base 0x21100000; \ + else \ + just tools zig-out/bin/{{ program }}.bin {{ args }}; \ + fi clean: rm -rfv .zig-cache zig-out tools/target diff --git a/build.zig b/build.zig @@ -43,7 +43,7 @@ fn build_pi( name: []const u8, program_path: std.Build.LazyPath, default_mode: ?BinaryMode, -) !void { +) !*std.Build.Module { const mode = default_mode orelse b.option(BinaryMode, "mode", "specify binary output mode") orelse .Bootable; const shared = b.createModule(pi_module_opts(target, optimize, b.path("shared/root.zig"))); @@ -88,7 +88,6 @@ fn build_pi( exe.link_data_sections = true; exe.link_gc_sections = true; exe.lto = .full; - exe.pie = true; exe.setLinkerScript(b.path("pi/linker.ld")); b.installArtifact(exe); @@ -103,13 +102,57 @@ fn build_pi( const install_bin = b.addInstallBinFile(obj_copy.getOutput(), bin_name); b.getInstallStep().dependOn(&install_bin.step); + + return program; +} + +fn build_sylveos_vm( + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, +) !*std.Build.Step.ObjCopy { + const shared = b.createModule(pi_module_opts(target, optimize, b.path("shared/root.zig"))); + const pi = b.createModule(pi_module_opts(target, optimize, b.path("pi/root.zig"))); + pi.addImport("shared", shared); + pi.addIncludePath(b.path("include/")); + pi.addCSourceFile(.{ .file = b.path("include/emmc.c") }); + pi.addCSourceFile(.{ .file = b.path("include/pi-sd.c") }); + + const root = b.createModule(pi_module_opts(target, optimize, b.path("sylveos/main.zig"))); + root.addImport("shared", shared); + root.addImport("pi", pi); + + const exe = b.addExecutable(.{ + .name = "sylveos-vm.elf", + .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; + + exe.setLinkerScript(b.path("sylveos/linker.ld")); + b.installArtifact(exe); + + const obj_copy = b.addObjCopy(exe.getEmittedBin(), .{ + .format = .bin, + }); + obj_copy.step.dependOn(&exe.step); + + const install_bin = b.addInstallBinFile(obj_copy.getOutput(), "sylveos-vm.bin"); + b.getInstallStep().dependOn(&install_bin.step); + + return obj_copy; } fn build_program(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, program: []const u8, mode: ?BinaryMode) !void { const program_name = try std.fmt.allocPrint(b.allocator, "programs/{s}.zig", .{program}); defer b.allocator.free(program_name); - try build_pi(b, target, optimize, program, b.path(program_name), mode); + _ = try build_pi(b, target, optimize, program, b.path(program_name), mode); } fn build_all_programs(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) !void { @@ -135,13 +178,14 @@ pub fn build(b: *std.Build) !void { const program = b.option([]const u8, "program", "specify pi program") orelse { try build_all_programs(b, pi_target, optimize); - try build_pi(b, pi_target, optimize, "sylveos", b.path("sylveos/root.zig"), .Bootable); + _ = try build_pi(b, pi_target, optimize, "sylveos", b.path("sylveos/root.zig"), .Bootable); return; }; if (std.mem.eql(u8, program, "sylveos")) { - try build_pi(b, pi_target, optimize, "sylveos", b.path("sylveos/root.zig"), null); + _ = try build_sylveos_vm(b, pi_target, optimize); + _ = try build_pi(b, pi_target, optimize, "sylveos", b.path("sylveos/root.zig"), null); } else { try build_program(b, pi_target, optimize, program, null); } diff --git a/pi/fs/mbr.zig b/pi/fs/mbr.zig @@ -32,7 +32,7 @@ pub const MasterBootRecord = packed struct(Sector) { return self.signature == 0xAA55; } - pub fn debug_print(self: *const Self, w: *std.io.Writer) !void { + pub fn debug_print(self: *const Self, w: *std.Io.Writer) !void { var boot_code: [446]u8 = undefined; std.mem.writeInt(u3568, &boot_code, self.boot_code, .little); const crc32 = std.hash.Crc32.hash(&boot_code); @@ -130,7 +130,7 @@ pub const PartitionTableEntry = packed struct(u128) { }; } - pub fn debug_print(self: *const Self, w: *std.io.Writer) !void { + pub fn debug_print(self: *const Self, w: *std.Io.Writer) !void { try w.print( \\ bootable = {any} \\ chs_start = 0x{X} diff --git a/sylveos/linker.ld b/sylveos/linker.ld @@ -0,0 +1,53 @@ +OUTPUT_ARCH(arm) +ENTRY(_start) + +BASE = 0x00000000; +STACK = 0x21000000; +INT_STACK = 0x21080000; +ENTRY_POINT = 0x21100000; + +__heap_start__ = BASE; + +__stack_start__ = STACK; +__stack_end__ = INT_STACK; + +__int_stack_start_ = INT_STACK; +__int_stack_end__ = ENTRY_POINT; + +SECTIONS +{ + /* link the code first at 0x21100000. */ + .text ENTRY_POINT : { + __code_start__ = .; + KEEP(*(.kmain)) + *(.text*) + __code_end__ = .; + . = ALIGN(8); + } + + /* read-only data */ + .rodata ALIGN(8) : { + *(.rodata*) + . = ALIGN(8); + } + /* rw data */ + .data ALIGN(8) : { + *(.data*) + . = ALIGN(8); + } + + /* 0 data */ + .bss : { + . = ALIGN(8); + __bss_start__ = .; + *(.bss*) + *(COMMON) + . = ALIGN(8); + __bss_end__ = .; + } + + /DISCARD/ : { *(.debug*) } + /DISCARD/ : { *(.comment*) } + /DISCARD/ : { *(*.attributes*) } + /DISCARD/ : { *(*.ARM.exidx*) } +} diff --git a/sylveos/main.zig b/sylveos/main.zig @@ -0,0 +1,40 @@ +const pi = @import("pi"); + +const memory = @import("./memory.zig"); + +pub export fn _start() linksection(".kmain") callconv(.naked) noreturn { + asm volatile ( + \\ // 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: + \\ // Boot + \\ bl %[kmain_fn:P] + \\ bl %[abort_fn:P] + : + : [kmain_fn] "X" (kmain), + [abort_fn] "X" (abort), + ); +} + +// Kinda just a repeat of boot... +export fn kmain() void { + pi.devices.mini_uart.initialize(921600, .Gpio14, .Gpio15) catch {}; + pi.devices.mini_uart.print("Hello, World in VM land!\n", .{}); + + memory.print_regions_physical(); + + pi.reboot(); +} + +export fn abort() noreturn { + @branchHint(.cold); + pi.reboot(); +} diff --git a/sylveos/memory.zig b/sylveos/memory.zig @@ -9,8 +9,8 @@ const mmu = pi.mmu; pub const MB: u32 = 1024 * 1024; -extern const __program_start__: u32; -extern const __program_end__: u32; +// extern const __program_start__: u32; +// extern const __program_end__: u32; extern const __heap_start__: u32; pub const Region = struct { @@ -18,6 +18,8 @@ pub const Region = struct { var PROGRAM_END: ?u32 = null; var HEAP_START: ?u32 = null; var HEAP_END: ?u32 = null; + var MEMORY_START: ?u32 = null; + var MEMORY_END: ?u32 = null; start: u32, end: u32, @@ -38,11 +40,10 @@ pub const Region = struct { pub fn program() Region { if (PROGRAM_START == null) { - PROGRAM_START = std.mem.alignBackward(u32, @intFromPtr(&__program_start__), MB); - } - - if (PROGRAM_END == null) { - PROGRAM_END = std.mem.alignForward(u32, @intFromPtr(&__program_end__), MB); + // PROGRAM_START = std.mem.alignBackward(u32, @intFromPtr(&__program_start__), MB); + // PROGRAM_END = std.mem.alignForward(u32, @intFromPtr(&__program_end__), MB); + PROGRAM_START = 0; + PROGRAM_END = MB; } return Region.init(PROGRAM_START orelse unreachable, PROGRAM_END orelse unreachable); @@ -87,20 +88,21 @@ pub const Region = struct { } pub fn memory() Region { - const memory_info = mailbox.get_arm_memory_info() catch unreachable; - const memory_start = memory_info.base_address; - const memory_end = memory_info.base_address + memory_info.memory_size; + if (MEMORY_START == null) { + const memory_info = mailbox.get_arm_memory_info() catch unreachable; - return Region.init(memory_start, memory_end); + MEMORY_START = memory_info.base_address; + MEMORY_END = memory_info.base_address + memory_info.memory_size; + } + + return Region.init(MEMORY_START orelse unreachable, MEMORY_END orelse unreachable); } pub fn heap() Region { if (HEAP_START == null) { - HEAP_START = std.mem.alignBackward(u32, @intFromPtr(&__heap_start__), MB); - } - - if (HEAP_END == null) { const memory_end = memory().end; + + HEAP_START = std.mem.alignBackward(u32, @intFromPtr(&__heap_start__), MB); HEAP_END = std.mem.alignBackward(u32, memory_end, MB); } diff --git a/sylveos/root.zig b/sylveos/root.zig @@ -5,14 +5,24 @@ const memory = @import("./memory.zig"); const uart = pi.devices.mini_uart; const interrupts = pi.interrupts; +const clock = pi.devices.clock; const procmap = pi.procmap; const page_table = pi.pt; const mmu = pi.mmu; const Region = memory.Region; +// figure out MMU issues +fn data_abort_handler(regs: interrupts.Registers) void { + const far = pi.faults.FAR.get(); + + uart.print("got fault on 0x{X:0>8} from 0x{X:0>8}\n", .{ far, regs.pc }); + pi.reboot(); +} + // The whole point of this code is to relocate the kernel into the high addresses pub fn main() !void { + interrupts.set_exception_handler(.DataAbort, data_abort_handler); memory.print_regions_physical(); // Now it's time for the fun @@ -24,7 +34,10 @@ pub fn main() !void { const io_region = Region.io(); const kernel_relocation = mem_region.end - program_region.size; - memory.copy_kernel(@ptrFromInt(kernel_relocation)); + const kernel_dest: [*]u8 = @ptrFromInt(kernel_relocation); + + try uart.set_rx_interrupts(true); + try get_bootload(kernel_dest); // Need it to be 1mb aligned, we'll just let stack eventually merge into it and deal with that bug var page_table_raw: [*]mmu.FirstLevelDescriptor = @ptrFromInt(kernel_relocation - memory.MB); @@ -39,6 +52,7 @@ pub fn main() !void { // We want to preserve program code and stack so we can continue running try program_region.map_identity(pt, READ_WRITE_ATTR); try Region.stack().map_identity(pt, READ_WRITE_ATTR); + try Region.interrupt_stack().map_identity(pt, READ_WRITE_ATTR); try Region.stack_boot().map_identity(pt, READ_WRITE_ATTR); // And also the new program code and stack @@ -51,14 +65,18 @@ pub fn main() !void { try io_region.map_identity(pt, DEVICE_ATTR); - const new_main_offset = @intFromPtr(&new_main); - // We need to be careful with the page table // So we squeeze a space just under the kernel for it // Then we have the "root" page table // It'll live in the same page as the main stack // I doubt the main stack would need 1MB of ram + for (pt, 0..) |page, idx| { + if (page.ty == .Fault) continue; + const va = idx << 20; + uart.print("Section 0x{X:0>8} -> 0x{X:0>8}\n", .{ va, @as(u32, page.descriptor.section.section_base_address) << 20 }); + } + // Now do MMU things mmu.init(); mmu.DomainAccessControlRegister.set_all(.Manager); @@ -68,45 +86,117 @@ pub fn main() !void { mmu.sync_pte(); mmu.enable(); + const base_address = mapped_kernel + memory.MB; + pi.interrupts.disable_interrupts(); - pi.switching.jump(kernel_relocation + new_main_offset, kernel_relocation - (memory.MB / 2)); + + pi.switching.jump(base_address, base_address - (memory.MB / 2)); } -fn new_main() noreturn { - const base_address = Region.io().end + memory.MB; - // Keep interrupt stack on top of normal stack - const stack = base_address - (memory.MB / 2); - const interrupt_stack = base_address; +const net = @import("shared").bootloader_protocol; +const Message = net.Message; +const Error = net.Error; +fn get_bootload(dst: [*]u8) !void { + const w = &uart.writer; + const r = &uart.reader; - var page_table_raw: [*]mmu.FirstLevelDescriptor = @ptrFromInt(base_address - memory.MB); - const pt = page_table_raw[0..4096]; + while (true) { + // UART cannot fail + try net.put_message(w, .GET_PROG_INFO); - // Now we need to: - // 1. Load the new exception handler - // We use p15/c12 to relocate it - // 2. Unload previous sections - // Old stack, old code, old interrupt stack - // 3. Reenable exceptions - // We do waste 2mb for kernel overhead which is unfortunate - // 1mb for code - // 1mb for stacks + root page table - interrupts.setup_exception_vector(base_address); - interrupts.relocate_exception_vector(base_address); - interrupts.rewrite_stacks(interrupt_stack); - interrupts.enable_interrupts(); - - Region.interrupt_stack().unmap(pt) catch {}; - Region.stack_boot().unmap(pt) catch {}; - Region.stack().unmap(pt) catch {}; - - uart.print("Remapping regions\n\n", .{}); - - pi.update_stack_address(stack); - pi.update_interrupt_stack_address(interrupt_stack); - Region.update_program_address(base_address, base_address + memory.MB); - Region.update_heap_address(0, Region.memory().end - memory.MB * 2); - - memory.print_regions_virtual(); + if (uart.read_queue_length() >= 4) { + const response = try net.get_u32(r); - pi.reboot(); + if (response == @intFromEnum(Message.PUT_PROG_INFO)) break; + } + + clock.delay_ms(300); + } + + const header = net.receive_header(r, std.math.maxInt(u32)) catch |e| switch (e) { + Error.BadAddress => { + try net.put_message(w, .BAD_CODE_ADDR); + + return pi.reboot(); + }, + 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; + } + } + + net.receive_code(r, dst[0..(header.n_bytes)], header) catch |e| switch (e) { + Error.BadChecksum => { + try net.put_message(w, .BAD_CODE_CKSUM); + + return pi.reboot(); + }, + else => unreachable, + }; + + try net.put_message(w, .BOOT_SUCCESS); + uart.flush(); } + +// fn new_main() noreturn { +// const base_address = Region.io().end + memory.MB; +// // Keep interrupt stack on top of normal stack +// const stack = base_address - (memory.MB / 2); +// const interrupt_stack = base_address; + +// var page_table_raw: [*]mmu.FirstLevelDescriptor = @ptrFromInt(base_address - memory.MB); +// const pt = page_table_raw[0..4096]; + +// // Now we need to: +// // 1. Load the new exception handler +// // We use p15/c12 to relocate it +// // 2. Unload previous sections +// // Old stack, old code, old interrupt stack +// // 3. Reenable exceptions +// // We do waste 2mb for kernel overhead which is unfortunate +// // 1mb for code +// // 1mb for stacks + root page table +// interrupts.setup_exception_vector(base_address); +// interrupts.relocate_exception_vector(base_address); +// interrupts.rewrite_stacks(interrupt_stack); +// interrupts.enable_interrupts(); + +// uart.print("base address: 0x{X}!\n", .{base_address}); + +// const mem_region = Region.memory(); +// const program = Region.program(); + +// const relocation: Region = .init(mem_region.end - memory.MB * 2, mem_region.end); + +// Region.interrupt_stack().unmap(pt) catch {}; +// Region.stack_boot().unmap(pt) catch {}; +// Region.stack().unmap(pt) catch {}; +// relocation.unmap(pt) catch {}; +// program.unmap(pt) catch {}; + +// uart.write_slice("owo?\n"); +// uart.print("Program: 0x{X} - 0x{X}\n", .{ program.start, program.end }); +// uart.print("Remapping regions\n\n", .{}); + +// pi.update_stack_address(stack); +// pi.update_interrupt_stack_address(interrupt_stack); +// Region.update_program_address(base_address, base_address + memory.MB); +// Region.update_heap_address(0, Region.memory().end - memory.MB * 2); + +// memory.print_regions_virtual(); + +// for (pt, 0..) |page, idx| { +// if (page.ty == .Fault) continue; +// const va = idx << 20; +// uart.print("Section 0x{X:0>8} -> 0x{X:0>8}\n", .{ va, @as(u32, page.descriptor.section.section_base_address) << 20 }); +// } + +// pi.reboot(); +// }