rumpk/hal/entry_riscv.zig

336 lines
8.4 KiB
Zig

// 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 was 0), sscratch=KStack
\\ csrrw sp, sscratch, sp
\\ bnez sp, 1f
// Came from Kernel (sp was 0). Restore sp.
\\ csrrw sp, sscratch, sp
\\ 1:
// Allocate stack (36 words * 8 bytes = 288 bytes)
\\ addi sp, sp, -288
// Save 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
\\ ld t0, 240(sp)
\\ csrw sepc, t0
// We restore sstatus
\\ ld t1, 248(sp)
\\ csrw sstatus, t1
// Restore GPRs
\\ 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
\\ 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;
// 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");
// uart.puts("[Trap] Checking deferred yield\n");
// Check for deferred yield
k_check_deferred_yield();
return;
}
// Delegate all other exceptions to the Kernel Immune System
// It will decide whether to segregate (worker) or halt (system)
// Note: k_handle_exception handles flow control (yield/halt) and does not return
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;
export fn zig_entry() void {
uart.init_riscv();
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;
}
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");
}
}
export fn rumpk_timer_now_ns() u64 {
var ticks: u64 = 0;
asm volatile ("rdtime %[ticks]"
: [ticks] "=r" (ticks),
);
// QEMU Virt machine is 10MHz -> 1 tick = 100ns
return ticks * 100;
}
// =========================================================
// 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;
}