commit f328e76f70d63511c1c6f5150cbf2cdee341144d
parent bb0addad903bc4ffb19bee59f33dd6760152e342
Author: Sylvia Ivory <git@sivory.net>
Date: Fri, 13 Mar 2026 16:39:18 -0700
Bootloader
Diffstat:
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();
+// }