// SPDX-License-Identifier: LCL-1.0 // Copyright (c) 2026 Markus Maiwald // Stewardship: Self Sovereign Society Foundation // // This file is part of the Nexus Commonwealth. // See legal/LICENSE_COMMONWEALTH.md for license terms. //! Rumpk HAL: RISC-V Entry Point (Sovereign Trap Architecture) //! //! This is the hardware floor for RISC-V64. Sets up stack, trap vectors, //! S-mode transition, and memory management before handing off to Nim. //! //! SAFETY: Runs in bare-metal S-mode with Sv39 paging. const std = @import("std"); const uart = @import("uart.zig"); const virtio_net = @import("virtio_net.zig"); // ========================================================= // Entry Point (Naked) // ========================================================= export fn _start() callconv(.naked) noreturn { asm volatile ( // 1. Disable Interrupts \\ csrw sie, zero \\ csrw satp, zero \\ csrw sscratch, zero // 1.1 Enable FPU (sstatus.FS = Initial [01]) \\ li t0, 0x2000 \\ csrs sstatus, t0 // 1.2 Initialize Global Pointer \\ .option push \\ .option norelax \\ la gp, __global_pointer$ \\ .option pop // 1.5 Clear BSS (Zero out uninitialized globals) \\ la t0, __bss_start \\ la t1, __bss_end \\ bge t0, t1, 2f \\ 1: \\ sb zero, (t0) \\ addi t0, t0, 1 \\ blt t0, t1, 1b \\ 2: // 2. Set up Stack \\ la sp, stack_bytes \\ li t0, 65536 \\ add sp, sp, t0 // 2.1 Install Trap Handler (Direct Mode) \\ la t0, trap_entry \\ csrw stvec, t0 // 3. Jump to Zig Entry \\ call zig_entry \\ 1: wfi \\ j 1b ); unreachable; } // Trap Frame Layout (Packed on stack) const TrapFrame = extern struct { ra: usize, gp: usize, tp: usize, t0: usize, t1: usize, t2: usize, s0: usize, s1: usize, a0: usize, a1: usize, a2: usize, a3: usize, a4: usize, a5: usize, a6: usize, a7: usize, s2: usize, s3: usize, s4: usize, s5: usize, s6: usize, s7: usize, s8: usize, s9: usize, s10: usize, s11: usize, t3: usize, t4: usize, t5: usize, t6: usize, sepc: usize, sstatus: usize, scause: usize, stval: usize, }; // Full Context Save Trap Entry export fn trap_entry() align(4) callconv(.naked) void { asm volatile ( // 🔧 CRITICAL FIX: Stack Switching (User -> Kernel) // Swap sp and sscratch. // If from User: sp=KStack, sscratch=UStack // If from Kernel: sp=0, sscratch=ValidStack (Problematic logic if not careful) // Correct Logic: // If sscratch == 0: We came from Kernel. sp is already KStack. Do NOTHING to sp. // If sscratch != 0: We came from User. sp is UStack. Swap to get KStack. \\ csrrw sp, sscratch, sp \\ bnez sp, 1f // Kernel -> Kernel (recursive). Restore sp from sscratch (which had the 0). \\ csrrw sp, sscratch, sp \\ 1: // Allocation (36*8 = 288 bytes) \\ addi sp, sp, -288 // Save Registers (GPRs) \\ sd ra, 0(sp) \\ sd gp, 8(sp) \\ sd tp, 16(sp) \\ sd t0, 24(sp) \\ sd t1, 32(sp) \\ sd t2, 40(sp) \\ sd s0, 48(sp) \\ sd s1, 56(sp) \\ sd a0, 64(sp) \\ sd a1, 72(sp) \\ sd a2, 80(sp) \\ sd a3, 88(sp) \\ sd a4, 96(sp) \\ sd a5, 104(sp) \\ sd a6, 112(sp) \\ sd a7, 120(sp) \\ sd s2, 128(sp) \\ sd s3, 136(sp) \\ sd s4, 144(sp) \\ sd s5, 152(sp) \\ sd s6, 160(sp) \\ sd s7, 168(sp) \\ sd s8, 176(sp) \\ sd s9, 184(sp) \\ sd s10, 192(sp) \\ sd s11, 200(sp) \\ sd t3, 208(sp) \\ sd t4, 216(sp) \\ sd t5, 224(sp) \\ sd t6, 232(sp) // RELOAD KERNEL GLOBAL POINTER (Critical for globals access) \\ .option push \\ .option norelax \\ la gp, __global_pointer$ \\ .option pop // Save CSRs \\ csrr t0, sepc \\ sd t0, 240(sp) \\ csrr t1, sstatus \\ sd t1, 248(sp) \\ csrr t2, scause \\ sd t2, 256(sp) \\ csrr t3, stval \\ sd t3, 264(sp) // Call Handler (Arg0 = Frame Pointer) \\ mv a0, sp \\ call rss_trap_handler // Restore CSRs (Optional if modified? sepc changed for syscall) \\ ld t0, 240(sp) \\ csrw sepc, t0 // sstatus often modified to change mode? For return, we use sret. // We might want to restore sstatus if we support nested interrupts properly. \\ ld t1, 248(sp) \\ csrw sstatus, t1 // Restore Encapsulated User Context \\ ld ra, 0(sp) \\ ld gp, 8(sp) \\ ld tp, 16(sp) \\ ld t0, 24(sp) \\ ld t1, 32(sp) \\ ld t2, 40(sp) \\ ld s0, 48(sp) \\ ld s1, 56(sp) \\ ld a0, 64(sp) \\ ld a1, 72(sp) \\ ld a2, 80(sp) \\ ld a3, 88(sp) \\ ld a4, 96(sp) \\ ld a5, 104(sp) \\ ld a6, 112(sp) \\ ld a7, 120(sp) \\ ld s2, 128(sp) \\ ld s3, 136(sp) \\ ld s4, 144(sp) \\ ld s5, 152(sp) \\ ld s6, 160(sp) \\ ld s7, 168(sp) \\ ld s8, 176(sp) \\ ld s9, 184(sp) \\ ld s10, 192(sp) \\ ld s11, 200(sp) \\ ld t3, 208(sp) \\ ld t4, 216(sp) \\ ld t5, 224(sp) \\ ld t6, 232(sp) // Deallocate stack \\ addi sp, sp, 288 // 🔧 CRITICAL FIX: Swap back sscratch <-> sp ONLY if returning to User Mode // Check sstatus.SPP (Bit 8). 0 = User, 1 = Supervisor. \\ csrr t0, sstatus \\ li t1, 0x100 \\ and t0, t0, t1 \\ bnez t0, 2f // Returning to User: Swap sp (Kernel Stack) with sscratch (User Stack) \\ csrrw sp, sscratch, sp \\ 2: \\ sret ); } // L1 Kernel Logic extern fn k_handle_syscall(nr: usize, a0: usize, a1: usize, a2: usize) usize; extern fn k_handle_exception(scause: usize, sepc: usize, stval: usize) void; extern fn k_check_deferred_yield() void; export fn rss_trap_handler(frame: *TrapFrame) void { const scause = frame.scause; // uart.print("[Trap] Entered Handler. scause: "); // uart.print_hex(scause); // uart.print("\n"); // Check high bit: 0 = Exception, 1 = Interrupt if ((scause >> 63) != 0) { const intr_id = scause & 0x7FFFFFFFFFFFFFFF; if (intr_id == 9) { // PLIC Context 1 (Supervisor) Claim/Complete Register const PLIC_CLAIM: *volatile u32 = @ptrFromInt(0x0c201004); const irq = PLIC_CLAIM.*; if (irq == 10) { // UART0 is IRQ 10 on Virt machine uart.poll_input(); } else if (irq == 0) { // Spurious or no pending interrupt } // Complete the interrupt PLIC_CLAIM.* = irq; } else if (intr_id == 5) { // Supervisor Timer Interrupt // Disable (One-shot) asm volatile ("csrc sie, %[mask]" : : [mask] "r" (@as(usize, 1 << 5)), ); // Call Nim Handler rumpk_timer_handler(); } k_check_deferred_yield(); return; } // 8: ECALL from U-mode // 9: ECALL from S-mode if (scause == 8 or scause == 9) { // Advance PC to skip 'ecall' instruction (4 bytes) frame.sepc += 4; // Dispatch Syscall const res = k_handle_syscall(frame.a7, frame.a0, frame.a1, frame.a2); // Write result back to a0 frame.a0 = res; // DIAGNOSTIC: Syscall completed // uart.print("[Trap] Syscall done, returning to userland\n"); k_check_deferred_yield(); return; } // Delegate all other exceptions to the Kernel Immune System k_handle_exception(scause, frame.sepc, frame.stval); // Safety halt if kernel returns (should be unreachable) while (true) {} } // SAFETY(Stack): Memory is immediately used by _start before any read. // Initialized to `undefined` for performance (no zeroing 64KB at boot). export var stack_bytes: [64 * 1024]u8 align(16) = undefined; const hud = @import("hud.zig"); extern fn kmain() void; extern fn NimMain() void; extern fn rumpk_timer_handler() void; export fn zig_entry() void { uart.init_riscv(); // 🔧 CRITICAL FIX: Enable SUM (Supervisor User Memory) Access // S-mode needs to write to U-mode pages (e.g. loading apps at 0x88000000) // sstatus.SUM is bit 18 (0x40000) asm volatile ( \\ li t0, 0x40000 \\ csrs sstatus, t0 ); uart.print("[Rumpk L0] zig_entry reached\n"); uart.print("[Rumpk RISC-V] Handing off to Nim L1...\n"); _ = virtio_net; NimMain(); kmain(); rumpk_halt(); } export fn console_write(ptr: [*]const u8, len: usize) void { uart.write_bytes(ptr[0..len]); } export fn console_read() c_int { if (uart.read_byte()) |b| { return @as(c_int, b); } return -1; } export fn console_poll() void { uart.poll_input(); } export fn debug_uart_lsr() u8 { return uart.get_lsr(); } const virtio_block = @import("virtio_block.zig"); extern fn hal_surface_init() void; export fn hal_io_init() void { uart.init(); hal_surface_init(); virtio_net.init(); virtio_block.init(); } export fn rumpk_halt() noreturn { uart.print("[Rumpk RISC-V] Halting.\n"); while (true) { asm volatile ("wfi"); } } // RISC-V Time Constants const TIMEBASE: u64 = 10_000_000; // QEMU 'virt' machine (10 MHz) const SBI_TIME_EID: u64 = 0x54494D45; fn rdtime() u64 { var ticks: u64 = 0; asm volatile ("rdtime %[ticks]" : [ticks] "=r" (ticks), ); return ticks; } fn sbi_set_timer(stime_value: u64) void { asm volatile ( \\ ecall : : [arg0] "{a0}" (stime_value), [eid] "{a7}" (SBI_TIME_EID), [fid] "{a6}" (0), // FID 0 = set_timer : .{ .memory = true }); } export fn rumpk_timer_now_ns() u64 { return rdtime() * 100; // 10MHz = 100ns/tick } export fn rumpk_timer_set_ns(interval_ns: u64) void { if (interval_ns == std.math.maxInt(u64)) { sbi_set_timer(std.math.maxInt(u64)); // Disable STIE asm volatile ("csrc sie, %[mask]" : : [mask] "r" (@as(usize, 1 << 5)), ); return; } const ticks = interval_ns / 100; // 100ns per tick for 10MHz const now = rdtime(); const next_time = now + ticks; sbi_set_timer(next_time); // Enable STIE (Supervisor Timer Interrupt Enable) asm volatile ("csrs sie, %[mask]" : : [mask] "r" (@as(usize, 1 << 5)), ); } // ========================================================= // KEXEC (The Phoenix Protocol) // ========================================================= export fn hal_kexec(entry: u64, dtb: u64) noreturn { // 1. Disable Interrupts asm volatile ("csrc sstatus, 2"); // 2. Disable MMU (Return to Physical Reality) // WARNING: This assumes we are Identity Mapped (VA=PA) or executing from a location // where PA is the same. mm.zig creates Identity Map for Kernel code. asm volatile ("csrw satp, zero"); asm volatile ("sfence.vma zero, zero"); // 3. Jump to new kernel asm volatile ( \\ jr %[entry] : : [entry] "r" (entry), [dtb] "{a1}" (dtb), [hart] "{a0}" (0), ); unreachable; } // ========================================================= // USERLAND TRANSITION // ========================================================= export fn hal_enter_userland(entry: u64, systable: u64, sp: u64) callconv(.c) void { // 1. Set up sstatus: SPP=0 (User), SPIE=1 (Enable interrupts on return) // 2. Set sepc to entry point // 3. Set sscratch to current kernel stack // 4. Transition via sret var kstack: usize = 0; asm volatile ("mv %[kstack], sp" : [kstack] "=r" (kstack), ); asm volatile ( \\ li t0, 0x20 // sstatus.SPIE = 1 (bit 5) \\ csrs sstatus, t0 \\ li t1, 0x100 // sstatus.SPP = 1 (bit 8) \\ csrc sstatus, t1 \\ li t2, 0x40000 // sstatus.SUM = 1 (bit 18) \\ csrs sstatus, t2 \\ csrw sepc, %[entry] \\ csrw sscratch, %[kstack] \\ mv sp, %[sp] \\ mv a0, %[systable] \\ sret : : [entry] "r" (entry), [systable] "r" (systable), [sp] "r" (sp), [kstack] "r" (kstack), ); }