572 lines
16 KiB
Zig
572 lines
16 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 vm = @import("vm_riscv.zig");
|
|
const mm = @import("mm.zig");
|
|
const stubs = @import("stubs.zig"); // Force compile stubs
|
|
const uart_input = @import("uart_input.zig");
|
|
const virtio_net = @import("virtio_net.zig");
|
|
|
|
comptime {
|
|
_ = stubs;
|
|
}
|
|
|
|
// =========================================================
|
|
// Entry Point (Naked)
|
|
// =========================================================
|
|
|
|
export fn riscv_init() callconv(.naked) noreturn {
|
|
asm volatile (
|
|
// 1. Disable Interrupts
|
|
\\ csrw sie, zero
|
|
\\ csrw satp, zero
|
|
\\ csrw sscratch, zero
|
|
|
|
// PROOF OF LIFE: Raw UART write before ANY initialization
|
|
\\ li t0, 0x10000000 // UART base address
|
|
\\ li t1, 0x58 // 'X'
|
|
\\ sb t1, 0(t0) // Write to THR
|
|
|
|
// 1.1 Enable FPU (FS), Vectors (VS), and SUM (Supervisor User Memory Access)
|
|
\\ li t0, 0x42200 // SUM=bit 18, FS=bit 13, VS=bit 9
|
|
\\ 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;
|
|
|
|
// Memory Management (Page Tables)
|
|
extern fn mm_get_kernel_satp() u64;
|
|
extern fn mm_activate_satp(satp_val: u64) void;
|
|
extern fn k_get_current_satp() u64;
|
|
|
|
fn get_sstatus() u64 {
|
|
return asm volatile ("csrr %[ret], sstatus"
|
|
: [ret] "=r" (-> u64),
|
|
);
|
|
}
|
|
|
|
fn set_sum() void {
|
|
asm volatile ("csrrs zero, sstatus, %[val]"
|
|
:
|
|
: [val] "r" (@as(u64, 1 << 18)),
|
|
);
|
|
}
|
|
|
|
// Global recursion counter
|
|
var trap_depth: usize = 0;
|
|
|
|
export fn rss_trap_handler(frame: *TrapFrame) void {
|
|
// 🔥 CRITICAL: Restore kernel page table IMMEDIATELY on trap entry
|
|
// const kernel_satp = mm_get_kernel_satp();
|
|
// if (kernel_satp != 0) {
|
|
// mm_activate_satp(kernel_satp);
|
|
// }
|
|
|
|
// RECURSION GUARD
|
|
trap_depth += 1;
|
|
if (trap_depth > 3) { // Allow some recursion (e.g. syscall -> fault), but prevent infinite loops
|
|
uart.print("[Trap] Infinite Loop Detected. Halting.\n");
|
|
while (true) {}
|
|
}
|
|
defer trap_depth -= 1;
|
|
|
|
const scause = frame.scause;
|
|
|
|
// DEBUG: Diagnose Userland Crash (Only print exceptions, ignore interrupts for noise)
|
|
if ((scause >> 63) == 0) {
|
|
uart.print("\n[Trap] Exception! Cause:");
|
|
uart.print_hex(scause);
|
|
uart.print(" PC:");
|
|
uart.print_hex(frame.sepc);
|
|
uart.print(" Val:");
|
|
uart.print_hex(frame.stval);
|
|
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.print("[IRQ] 10\n");
|
|
uart_input.poll_input();
|
|
} else if (irq >= 32 and irq <= 35) {
|
|
virtio_net.virtio_net_poll();
|
|
} else if (irq == 0) {
|
|
// Spurious or no pending interrupt
|
|
} else {
|
|
// uart.print("[IRQ] Unknown: ");
|
|
// uart.print_hex(irq);
|
|
// uart.print("\n");
|
|
}
|
|
|
|
// Complete the IRQ
|
|
PLIC_CLAIM.* = irq;
|
|
} else if (intr_id == 5) {
|
|
// Timer Interrupt
|
|
asm volatile ("csrc sip, %[mask]"
|
|
:
|
|
: [mask] "r" (@as(u64, 1 << 5)),
|
|
);
|
|
k_check_deferred_yield();
|
|
} else {
|
|
// uart.print("[Trap] Unhandled Interrupt: ");
|
|
// uart.print_hex(intr_id);
|
|
// uart.print("\n");
|
|
}
|
|
} else {
|
|
// EXCEPTION HANDLING
|
|
// 8: ECALL from U-mode
|
|
// 9: ECALL from S-mode
|
|
if (scause == 8 or scause == 9) {
|
|
const nr = frame.a7;
|
|
const a0 = frame.a0;
|
|
const a1 = frame.a1;
|
|
const a2 = frame.a2;
|
|
|
|
uart.print("[Syscall] NR:");
|
|
uart.print_hex(nr);
|
|
uart.print("\n");
|
|
|
|
// Advance PC to avoid re-executing ECALL
|
|
frame.sepc += 4;
|
|
|
|
// Dispatch Sycall
|
|
const ret = k_handle_syscall(nr, a0, a1, a2);
|
|
frame.a0 = ret;
|
|
} else {
|
|
// Delegate all other exceptions to the Kernel Immune System
|
|
// This function should NOT return ideally, but if it does, we loop.
|
|
k_handle_exception(scause, frame.sepc, frame.stval);
|
|
while (true) {}
|
|
}
|
|
}
|
|
|
|
// 🔥 CRITICAL RETURN PATH: Restore User Page Table if returning to User Mode
|
|
// We check sstatus.SPP (Supervisor Previous Privilege) - Bit 8
|
|
// 0 = User, 1 = Supervisor
|
|
const sstatus = get_sstatus();
|
|
const spp = (sstatus >> 8) & 1;
|
|
|
|
if (spp == 0) {
|
|
const user_satp = k_get_current_satp();
|
|
if (user_satp != 0) {
|
|
// Enable SUM (Supervisor Access User Memory) so we can read the stack
|
|
// to restore registers (since stack is mapped in User PT)
|
|
set_sum();
|
|
mm_activate_satp(user_satp);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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");
|
|
|
|
// Networking is initialized by kmain -> rumpk_net_init
|
|
|
|
NimMain();
|
|
kmain();
|
|
rumpk_halt();
|
|
}
|
|
|
|
export fn hal_console_write(ptr: [*]const u8, len: usize) void {
|
|
uart.write_bytes(ptr[0..len]);
|
|
}
|
|
|
|
export fn console_read() c_int {
|
|
if (uart_input.read_byte()) |b| {
|
|
return @as(c_int, b);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
export fn console_poll() void {
|
|
uart_input.poll_input();
|
|
}
|
|
|
|
export fn debug_uart_lsr() u8 {
|
|
return uart.get_lsr();
|
|
}
|
|
|
|
export fn uart_print_hex(value: u64) void {
|
|
uart.print_hex(value);
|
|
}
|
|
|
|
export fn uart_print_hex8(value: u8) void {
|
|
uart.print_hex8(value);
|
|
}
|
|
|
|
const virtio_block = @import("virtio_block.zig");
|
|
|
|
extern fn hal_surface_init() void;
|
|
|
|
export fn hal_io_init() void {
|
|
uart.init();
|
|
hal_surface_init();
|
|
// Network init is now called explicitly by kernel (rumpk_net_init)
|
|
virtio_block.init();
|
|
}
|
|
|
|
export fn hal_panic(msg: [*:0]const u8) callconv(.c) noreturn {
|
|
uart.print("[HAL PANIC] ");
|
|
uart.print(std.mem.span(msg));
|
|
uart.print("\n");
|
|
rumpk_halt();
|
|
}
|
|
|
|
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),
|
|
);
|
|
}
|