sylveos

Toy Operating System
Log | Files | Refs

commit 1015568f100147044a06db957771b48b3ebb071c
parent 1cbdb8f727f0ad0b19d0b4a7f013b95a6f871e67
Author: Sylvia Ivory <git@sivory.net>
Date:   Fri, 13 Mar 2026 22:13:39 -0700

Hello World from VM

Diffstat:
Mboot/bootable.zig | 2+-
Mbuild.zig | 2+-
Mpi/devices/mini-uart.zig | 4+---
Mpi/interrupts.zig | 34+++++++++++++++++-----------------
Mpi/pt.zig | 2+-
Mpi/root.zig | 23++---------------------
Mpi/switching.zig | 4+++-
Msylveos/boot.zig | 84++++++++++++++-----------------------------------------------------------------
Msylveos/linker.ld | 24+++++++++++++++---------
Msylveos/memory.zig | 80+++++++++++++++++++++++++++++++++----------------------------------------------
Msylveos/root.zig | 48++++++++++++++++++++++++++++++++++++++++++++----
11 files changed, 133 insertions(+), 174 deletions(-)

diff --git a/boot/bootable.zig b/boot/bootable.zig @@ -1,7 +1,7 @@ // Boot stub for bootable programs (ran by bootloader) pub const bootable_asm = - \\ mov sp, 0x8000000 + \\ ldr sp, =__stack_end__ \\ mov fp, 0x0 \\ bl %[kmain_fn:P] \\ bl %[abort_fn:P] diff --git a/build.zig b/build.zig @@ -174,7 +174,7 @@ fn build_all_programs(b: *std.Build, target: std.Build.ResolvedTarget, optimize: pub fn build(b: *std.Build) !void { const pi_target = pi_zero_target(b); - const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast }); + const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseSafe }); const program = b.option([]const u8, "program", "specify pi program") orelse { try build_all_programs(b, pi_target, optimize); diff --git a/pi/devices/mini-uart.zig b/pi/devices/mini-uart.zig @@ -232,9 +232,7 @@ pub fn get_rx_written() usize { return rx_writer_written; } -noinline fn uart_handler(regs: interrupts.Registers) void { - _ = regs; - +noinline fn uart_handler(_: interrupts.Registers, _: interrupts.ExceptionVector) void { mem.barrier(.Write); defer mem.barrier(.Write); diff --git a/pi/interrupts.zig b/pi/interrupts.zig @@ -98,18 +98,18 @@ pub inline fn pending_peripheral_interrupt(i: PeripheralsInterrupt) bool { } } -fn empty(regs: Registers) void { - uart.print("unexpected interrupt\n", .{}); +fn empty(regs: Registers, vector: ExceptionVector) void { + uart.print("unexpected {s}\n", .{@tagName(vector)}); dump_registers(&regs); } -var reset_handler: *const fn (Registers) void = empty; -var undefined_instruction_handler: *const fn (Registers) void = empty; -var software_interrupt_handler: *const fn (Registers) void = empty; -var prefetch_abort_handler: *const fn (Registers) void = empty; -var data_abort_handler: *const fn (Registers) void = empty; -var interrupt_handler: *const fn (Registers) void = empty; -var fast_interrupt_handler: *const fn (Registers) void = empty; +var reset_handler: *const fn (Registers, ExceptionVector) void = empty; +var undefined_instruction_handler: *const fn (Registers, ExceptionVector) void = empty; +var software_interrupt_handler: *const fn (Registers, ExceptionVector) void = empty; +var prefetch_abort_handler: *const fn (Registers, ExceptionVector) void = empty; +var data_abort_handler: *const fn (Registers, ExceptionVector) void = empty; +var interrupt_handler: *const fn (Registers, ExceptionVector) void = empty; +var fast_interrupt_handler: *const fn (Registers, ExceptionVector) void = empty; pub const Registers = struct { gp: [13]usize, @@ -175,27 +175,27 @@ inline fn setup_reg(pc: u32, gp: *const [13]u32) Registers { // I still hate this export fn reset_stub(pc: u32, gp: *const [13]u32) void { - reset_handler(setup_reg(pc, gp)); + reset_handler(setup_reg(pc, gp), .Reset); } export fn undefined_instruction_stub(pc: u32, gp: *const [13]u32) void { - undefined_instruction_handler(setup_reg(pc, gp)); + undefined_instruction_handler(setup_reg(pc, gp), .UndefinedInstruction); } export fn software_interrupt_stub(pc: u32, gp: *const [13]u32) void { - software_interrupt_handler(setup_reg(pc, gp)); + software_interrupt_handler(setup_reg(pc, gp), .SoftwareInterrupt); } export fn prefetch_abort_stub(pc: u32, gp: *const [13]u32) void { - prefetch_abort_handler(setup_reg(pc, gp)); + prefetch_abort_handler(setup_reg(pc, gp), .PrefetchAbort); } export fn data_abort_stub(pc: u32, gp: *const [13]u32) void { - data_abort_handler(setup_reg(pc, gp)); + data_abort_handler(setup_reg(pc, gp), .DataAbort); } export fn interrupt_stub(pc: u32, gp: *const [13]u32) void { - interrupt_handler(setup_reg(pc, gp)); + interrupt_handler(setup_reg(pc, gp), .IRQ); } // TODO; @@ -301,7 +301,7 @@ pub inline fn rewrite_stacks(stack: u32) void { system.flush_self_modifying_code(); } -const ExceptionVector = enum { +pub const ExceptionVector = enum { Reset, UndefinedInstruction, SoftwareInterrupt, @@ -311,7 +311,7 @@ const ExceptionVector = enum { FIQ, }; -pub fn set_exception_handler(vector: ExceptionVector, handler: *const fn (Registers) void) void { +pub fn set_exception_handler(vector: ExceptionVector, handler: *const fn (Registers, ExceptionVector) void) void { switch (vector) { .Reset => reset_handler = handler, .UndefinedInstruction => undefined_instruction_handler = handler, diff --git a/pi/pt.zig b/pi/pt.zig @@ -79,7 +79,7 @@ pub fn set(pt: []mmu.FirstLevelDescriptor, va: u32, pa: u32, attr: pinned.Pinned pub fn remove(pt: []mmu.FirstLevelDescriptor, va: u32) !void { const index = va >> 20; - pt[index] = fault_page; + pt[index].ty = .Fault; mmu.sync_pte(); } diff --git a/pi/root.zig b/pi/root.zig @@ -37,33 +37,14 @@ pub const fs = struct { pub const FatVFS = @import("./fs/fatvfs.zig"); }; -extern const __stack_start__: u32; extern const __stack_end__: u32; -extern const __int_stack_start__: u32; extern const __int_stack_end__: u32; -// Things might get a bit fucky -var STACK_ADDRESS: u32 = 0; -var INT_STACK_ADDRESS: u32 = 0; pub inline fn get_stack_address() u32 { - if (STACK_ADDRESS == 0) { - STACK_ADDRESS = std.mem.alignForward(u32, @intFromPtr(&__stack_end__), 1024 * 1024); - } - - return STACK_ADDRESS; -} -pub inline fn update_stack_address(address: u32) void { - STACK_ADDRESS = address; + return std.mem.alignForward(u32, @intFromPtr(&__stack_end__), 1024 * 1024); } pub inline fn get_interrupt_stack_address() u32 { - if (INT_STACK_ADDRESS == 0) { - INT_STACK_ADDRESS = std.mem.alignForward(u32, @intFromPtr(&__int_stack_end__), 1024 * 1024); - } - - return INT_STACK_ADDRESS; -} -pub inline fn update_interrupt_stack_address(address: u32) void { - INT_STACK_ADDRESS = address; + return std.mem.alignForward(u32, @intFromPtr(&__int_stack_end__), 1024 * 1024); } pub inline fn cycle_counter_init() void { diff --git a/pi/switching.zig b/pi/switching.zig @@ -69,9 +69,11 @@ comptime { // Jump to lr and put sp // r0 - lr // r1 - sp + // r2 - r0 asm (mk_fn("jump", \\ mov sp, r1 \\ mov lr, r0 + \\ mov r0, r2 \\ bx lr )); } @@ -79,7 +81,7 @@ comptime { extern fn switch_state_inner(old: *interrupts.Registers, new: *const interrupts.Registers, *const fn (*interrupts.Registers) callconv(.c) void) void; pub extern fn restore_state_user(state: *const interrupts.Registers) noreturn; pub extern fn restore_state_privileged(state: *const interrupts.Registers) noreturn; -pub extern fn jump(lr: u32, sp: u32) noreturn; +pub extern fn jump(lr: u32, sp: u32, r0: u32) noreturn; pub inline fn switch_state(old: *interrupts.Registers, new: *const interrupts.Registers) void { if (new.psr.mode == .User) { diff --git a/sylveos/boot.zig b/sylveos/boot.zig @@ -13,7 +13,7 @@ const mmu = pi.mmu; const Region = memory.Region; // figure out MMU issues -fn data_abort_handler(regs: interrupts.Registers) void { +fn abort_handler(regs: interrupts.Registers, _: interrupts.ExceptionVector) 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 }); @@ -22,7 +22,9 @@ fn data_abort_handler(regs: interrupts.Registers) void { // 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); + interrupts.set_exception_handler(.DataAbort, abort_handler); + interrupts.set_exception_handler(.PrefetchAbort, abort_handler); + memory.print_regions_physical(); // Now it's time for the fun @@ -37,7 +39,8 @@ pub fn main() !void { const kernel_dest: [*]u8 = @ptrFromInt(kernel_relocation); try uart.set_rx_interrupts(true); - try get_bootload(kernel_dest); + const entry_offset = try get_bootload(kernel_dest); + try uart.set_rx_interrupts(false); // 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); @@ -53,7 +56,6 @@ pub fn main() !void { 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 const relocation: Region = .init(@intFromPtr(page_table_raw), mem_region.end); @@ -71,12 +73,6 @@ pub fn main() !void { // 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); @@ -90,13 +86,17 @@ pub fn main() !void { pi.interrupts.disable_interrupts(); - pi.switching.jump(base_address, base_address - (memory.MB / 2)); + pi.switching.jump( + base_address + entry_offset, + base_address - (memory.MB / 2), + mem_region.size, + ); } const net = @import("shared").bootloader_protocol; const Message = net.Message; const Error = net.Error; -fn get_bootload(dst: [*]u8) !void { +fn get_bootload(dst: [*]u8) !u32 { const w = &uart.writer; const r = &uart.reader; @@ -132,7 +132,7 @@ fn get_bootload(dst: [*]u8) !void { } } - net.receive_code(r, dst[0..(header.n_bytes)], header) catch |e| switch (e) { + net.receive_code(r, dst[(header.arm_base)..(header.arm_base + header.n_bytes)], header) catch |e| switch (e) { Error.BadChecksum => { try net.put_message(w, .BAD_CODE_CKSUM); @@ -143,60 +143,6 @@ fn get_bootload(dst: [*]u8) !void { 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(); -// } + return header.arm_base; +} diff --git a/sylveos/linker.ld b/sylveos/linker.ld @@ -2,20 +2,23 @@ OUTPUT_ARCH(arm) ENTRY(_start) BASE = 0x00000000; +PAGE_TABLE = 0x21000000; 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; +ENTRY_POINT = 0x21108000; SECTIONS { + __heap_start__ = BASE; + __page_table_start__ = PAGE_TABLE; + __page_table_end__ = PAGE_TABLE + 4096 * 4; + __stack_start__ = __page_table_end__; + __stack_end__ = INT_STACK; + __int_stack_start__ = INT_STACK; + __int_stack_end__ = ENTRY_POINT; + + __program_start__ = ENTRY_POINT; + /* link the code first at 0x21100000. */ .text ENTRY_POINT : { __code_start__ = .; @@ -46,6 +49,9 @@ SECTIONS __bss_end__ = .; } + . = ALIGN(8); + __program_end__ = .; + /DISCARD/ : { *(.debug*) } /DISCARD/ : { *(.comment*) } /DISCARD/ : { *(*.attributes*) } diff --git a/sylveos/memory.zig b/sylveos/memory.zig @@ -4,20 +4,21 @@ const pi = @import("pi"); const mailbox = pi.devices.mailbox; const uart = pi.devices.mini_uart; const pinned = pi.pinned; -const page_table = pi.pt; 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 __page_table_start__: u32; +extern const __page_table_end__: u32; +extern const __stack_start__: u32; +extern const __stack_end__: u32; +extern const __int_stack_start__: u32; +extern const __int_stack_end__: u32; extern const __heap_start__: u32; pub const Region = struct { - var PROGRAM_START: ?u32 = null; - 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; @@ -39,43 +40,30 @@ pub const Region = struct { } pub fn program() Region { - if (PROGRAM_START == null) { - // 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; - } + // Program code is aligned to 1mb + const program_start = std.mem.alignBackward(u32, @intFromPtr(&__program_start__), MB); + const program_end = std.mem.alignForward(u32, @intFromPtr(&__program_end__), MB); - return Region.init(PROGRAM_START orelse unreachable, PROGRAM_END orelse unreachable); + return Region.init(program_start, program_end); } - pub fn update_program_address(start: u32, end: u32) void { - PROGRAM_START = start; - PROGRAM_END = end; - } + pub fn page_table() Region { + const page_table_start = translate(@intFromPtr(&__page_table_start__)) catch 0; + const page_table_end = translate(@intFromPtr(&__page_table_end__)) catch 0; - pub fn update_heap_address(start: u32, end: u32) void { - HEAP_START = start; - HEAP_END = end; + return Region.init(page_table_start, page_table_end); } pub fn stack() Region { - const stack_end = pi.get_stack_address(); - const stack_start = if (stack_end > io().start) io().end else stack_end - MB; - - return Region.init(stack_start, stack_end); - } - - pub fn stack_boot() Region { - const stack_end: u32 = 0x0800_0000; - const stack_start = stack_end - MB; + const stack_start = @intFromPtr(&__stack_start__); + const stack_end = @intFromPtr(&__stack_end__); return Region.init(stack_start, stack_end); } pub fn interrupt_stack() Region { - const int_stack_end = pi.get_interrupt_stack_address(); - const int_stack_start = @max(int_stack_end - MB, stack().end); + const int_stack_start = @intFromPtr(&__int_stack_start__); + const int_stack_end = @intFromPtr(&__int_stack_end__); return Region.init(int_stack_start, int_stack_end); } @@ -99,21 +87,17 @@ pub const Region = struct { } pub fn heap() Region { - if (HEAP_START == 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); - } + const heap_start = std.mem.alignBackward(u32, @intFromPtr(&__heap_start__), MB); + const heap_end = std.mem.alignBackward(u32, memory().end, MB); - return Region.init(HEAP_START orelse unreachable, HEAP_END orelse unreachable); + return Region.init(heap_start, heap_end); } pub fn unmap(self: *const Region, pt: []mmu.FirstLevelDescriptor) !void { const size = (self.end / MB) - (self.start / MB); for (0..size) |offset| { - try page_table.remove(pt, self.start + offset * MB); + try pi.pt.remove(pt, self.start + offset * MB); } } @@ -121,13 +105,18 @@ pub const Region = struct { const size = (self.end / MB) - (self.start / MB); for (0..size) |offset| { - _ = try page_table.set(pt, to + offset * MB, self.start + offset * MB, attr); + _ = try pi.pt.set(pt, to + offset * MB, self.start + offset * MB, attr); } } pub fn map_identity(self: *const Region, pt: []mmu.FirstLevelDescriptor, attr: pinned.PinnedAttribute) !void { try self.map_to(pt, self.start, attr); } + + pub fn set_memory(size: u32) void { + MEMORY_START = 0; + MEMORY_END = size - (program().end - page_table().start); + } }; pub fn print_regions_physical() void { @@ -163,10 +152,12 @@ pub fn print_regions_virtual() void { const int_stack = Region.interrupt_stack(); const heap = Region.heap(); const io = Region.io(); + const pt = Region.page_table(); uart.print( \\ Virtual Memory Layout: \\ HEAP: 0x{X:0>8} - 0x{X:0>8} ({Bi}) + \\ PAGE TABLE: 0x{X:0>8} - 0x{X:0>8} ({Bi}) \\ BCM2835 IO: 0x{X:0>8} - 0x{X:0>8} ({Bi}) \\ KERNEL STACK: 0x{X:0>8} - 0x{X:0>8} ({Bi}) \\KERNEL INTERRUPT STACK: 0x{X:0>8} - 0x{X:0>8} ({Bi}) @@ -175,6 +166,7 @@ pub fn print_regions_virtual() void { \\ , .{ heap.start, heap.end, heap.size, + pt.start, pt.end, pt.size, io.start, io.end, io.size, stack.start, stack.end, stack.size, int_stack.start, int_stack.end, int_stack.size, @@ -190,12 +182,6 @@ pub fn get_allocator() std.mem.Allocator { return fba.allocator(); } -pub fn copy_kernel(to: [*]u8) void { - const program = Region.program(); - - @memcpy(to, program.raw[0..program.size]); -} - pub fn translate(va: u32) !u32 { const res = mmu.va_translation_cw(va, .PrivilegedRead); if (res.aborted) return error.FailedTranslation; diff --git a/sylveos/root.zig b/sylveos/root.zig @@ -1,10 +1,15 @@ const pi = @import("pi"); const memory = @import("./memory.zig"); +const uart = pi.devices.mini_uart; +const interrupts = pi.interrupts; +const Region = memory.Region; +const mmu = pi.mmu; pub export fn _start() linksection(".kmain") callconv(.naked) noreturn { asm volatile ( \\ // Clear BSS + \\ mov r4, r0 \\ mov r0, #0 \\ ldr r1, =__bss_start__ \\ ldr r2, =__bss_end__ @@ -16,6 +21,7 @@ pub export fn _start() linksection(".kmain") callconv(.naked) noreturn { \\ bne L2 \\ L3: \\ // Boot + \\ mov r0, r4 \\ bl %[kmain_fn:P] \\ bl %[abort_fn:P] : @@ -24,12 +30,42 @@ pub export fn _start() linksection(".kmain") callconv(.naked) noreturn { ); } +// figure out MMU issues +fn abort_handler(regs: interrupts.Registers, _: interrupts.ExceptionVector) 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(); +} + // 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", .{}); +export fn kmain(memory_size: u32) void { + uart.initialize(921600, .Gpio14, .Gpio15) catch {}; + Region.set_memory(memory_size); + + interrupts.set_exception_handler(.DataAbort, abort_handler); + interrupts.set_exception_handler(.PrefetchAbort, abort_handler); + + var page_table_raw: [*]mmu.FirstLevelDescriptor = @ptrFromInt(Region.page_table().start); + const pt = page_table_raw[0..4096]; - memory.print_regions_physical(); + pi.pt.remove(pt, 0x0000_0000) catch {}; + pi.pt.remove(pt, 0x0010_0000) catch {}; + pi.pt.remove(pt, 0x0020_0000) catch {}; + + // Keep interrupt stack on top of normal stack + + const base_address = Region.program().start; + interrupts.setup_exception_vector(base_address); + interrupts.relocate_exception_vector(base_address); + interrupts.rewrite_stacks(Region.interrupt_stack().end); + interrupts.enable_interrupts(); + + memory.print_regions_virtual(); + + main() catch |e| { + uart.print("main returned error: {t}\n", .{e}); + }; pi.reboot(); } @@ -38,3 +74,7 @@ export fn abort() noreturn { @branchHint(.cold); pi.reboot(); } + +fn main() !void { + uart.print("Hello World from VM!\n", .{}); +}