commit 1015568f100147044a06db957771b48b3ebb071c
parent 1cbdb8f727f0ad0b19d0b4a7f013b95a6f871e67
Author: Sylvia Ivory <git@sivory.net>
Date: Fri, 13 Mar 2026 22:13:39 -0700
Hello World from VM
Diffstat:
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(®s);
}
-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", .{});
+}