/* StarForth — Steady-State Virtual Machine Runtime Copyright (c) 2023–2025 Robert A. James. All rights reserved. Licensed under the StarForth License, Version 1.0. */ /** * arch.c (riscv64) - Architecture abstractions for StarKernel on RISC-V 64-bit */ #include "arch.h" #include "console.h" #include extern void riscv64_install_vectors(void); extern char __kernel_start[]; /* same pattern pmm.c:67 already uses */ /* Item 4.3.5a. Prints satp's MODE/PPN fields and a known running address * as a permanent boot record (same diagnostic-verbosity convention as * apic.c's "AArch64: running at EL..." on the other ISA). Confirmed live, * 2026-08-08: firmware (EDK2 RISC-V) leaves satp.MODE=10 (Sv57) active at * kernel entry -- not Sv39/Sv48 as this file's own arch_mmu_init() stub * comment assumed; that comment is stale, corrected below. __kernel_start * printing here (0xbdd56982 in the confirming boot) landed inside the * UEFI-reported total physical RAM window (0x80000000 + 1020 MB), near the * top past the PMM-free region -- consistent with the kernel's own running * range being identity-mapped (VA==PA) under firmware's Sv57 table, not * some high canonical virtual range. That is the basis for the Bare-mode * switch below being safe. */ static void print_hex64(uint64_t val) { char buf[19]; buf[0] = '0'; buf[1] = 'x'; for (int i = 15; i >= 0; i--) { int nibble = (int)(val & 0xF); buf[2 + i] = (char)(nibble < 10 ? '0' + nibble : 'a' + nibble - 10); val >>= 4; } buf[18] = '\0'; console_puts(buf); } /** * @brief Perform the earliest RISC-V architecture initialisation (no-op). * * RISC-V has no GDT or IDT; privilege separation is handled by the * machine/supervisor/user execution modes (M/S/U), and the trap vector * table is installed by writing @c stvec in @c arch_interrupts_init() * via @c riscv64_install_vectors(). This stub satisfies the common * @c arch_early_init() call site in @c kernel_main() without any * RISC-V-specific action. */ void arch_early_init(void) { /* No GDT/IDT on RISC-V; privilege via exception levels */ uint64_t satp; __asm__ volatile ("csrr %0, satp" : "=r"(satp)); uint64_t mode = satp >> 60; uint64_t ppn = satp & 0xFFFFFFFFFFFULL; console_println("riscv64 item 4.3.5a: satp state at kernel entry (before switch)"); console_puts(" satp.MODE = "); print_hex64(mode); console_puts("\n"); console_puts(" satp.PPN = "); print_hex64(ppn); console_puts("\n"); console_puts(" __kernel_start = "); print_hex64((uint64_t)__kernel_start); console_puts("\n"); /* Explicit switch to Bare mode (satp.MODE=0): this kernel builds no * riscv64 page table of its own (vmm.c's is x86-64-shaped and never * activated here, per its own load_cr3() no-op outside __x86_64__) and * has no present use for virtual memory on this ISA, so there is no * reason to inherit firmware's Sv57 mapping -- which is confirmed to * have at least one hole (PLIC_THRESHOLD, FABRIC-0.md item 4.3.5a). * ExitBootServices() has already completed several checkpoints before * this function runs (ConOut/GOP done, BootServices exited per the * "[CKPT 008]" trace), so nothing downstream depends on firmware's * table surviving. Single asm block: the CSR write and SFENCE.VMA * must not be separated by a compiler-scheduled memory access (RISC-V * Privileged Spec §4.2.1, SFENCE.VMA ordering). */ __asm__ volatile ( "csrw satp, x0\n" "sfence.vma\n" ::: "memory" ); console_println("riscv64: satp cleared -- Bare mode, explicit (item 4.3.5a)"); } /** * @brief Enable supervisor-mode interrupts (RISC-V CSRSI sstatus). * * Issues @c CSRSI @c sstatus, @c 0x2 which sets bit 1 (@c SIE — * Supervisor Interrupt Enable) in the @c sstatus CSR. While @c SIE = 1, * pending supervisor-mode interrupts are taken. The @c memory clobber * prevents the compiler from reordering stores across the barrier. * * Must be called after @c arch_interrupts_init() has installed the * @c stvec trap handler, and after the PLIC driver has been configured * to forward the desired interrupt sources. */ void arch_enable_interrupts(void) { __asm__ volatile ("csrsi sstatus, 0x2" ::: "memory"); } /** * @brief Disable supervisor-mode interrupts (RISC-V CSRCI sstatus). * * Issues @c CSRCI @c sstatus, @c 0x2 which clears bit 1 (@c SIE) in * @c sstatus, masking all supervisor-mode interrupts. Machine-mode * interrupts are unaffected. Used around critical sections in the * single-threaded kernel. The @c memory clobber acts as a compiler * barrier; pair with a @c FENCE instruction for a hardware barrier. */ void arch_disable_interrupts(void) { __asm__ volatile ("csrci sstatus, 0x2" ::: "memory"); } /** * @brief Halt the core until the next interrupt (RISC-V WFI). * * Issues the @c WFI (Wait For Interrupt) instruction in supervisor mode. * The core enters a low-power state and resumes when a pending interrupt * is detected (or after a platform-specific timeout). In the kernel panic * path this is called inside an infinite loop with @c SIE cleared to stop * the processor permanently. In the idle path it reduces power consumption * between heartbeat ticks. * * The @c memory clobber prevents store-sinking across the @c WFI. */ void arch_halt(void) { __asm__ volatile ("wfi" ::: "memory"); } void arch_cold_reset(void) { arch_disable_interrupts(); /* SBI SRST extension (0x53525354): sbi_system_reset, cold reboot = type 1 */ register unsigned long a7 __asm__("a7") = 0x53525354UL; register unsigned long a6 __asm__("a6") = 0UL; register unsigned long a0 __asm__("a0") = 1UL; /* cold reboot */ register unsigned long a1 __asm__("a1") = 0UL; /* no reason */ __asm__ volatile ("ecall" : "+r"(a0) : "r"(a1), "r"(a6), "r"(a7) : "memory"); for (;;) __asm__ volatile ("wfi" ::: "memory"); } /** * @brief Read the RISC-V CPU cycle counter (@c rdcycle). * * Issues the @c RDCYCLE pseudo-instruction (a @c CSRRS on @c cycle, CSR * 0xC00) to read the 64-bit hardware performance counter that counts * elapsed clock cycles since reset. On RISC-V the cycle counter is the * architectural equivalent of the x86-64 TSC. * * Unlike the AArch64 @c CNTPCT_EL0, the RISC-V @c cycle register is a * per-hart counter and is not architecturally guaranteed to be synchronised * across harts. For single-core LithosAnanke this is not a concern. * Frequency is not architecturally specified; @c timer_init() assumes * 1 GHz for QEMU @c virt board compatibility. * * @return Current 64-bit cycle counter value; frequency platform-dependent. */ uint64_t arch_read_timestamp(void) { uint64_t val; __asm__ volatile ("rdcycle %0" : "=r"(val)); return val; } /** * @brief Initialise the RISC-V MMU (stub — deferred to a later milestone). * * Full Sv39/Sv48 page-table setup (SATP, PMP, etc.) is deferred to a * later milestone. This stub satisfies the common @c arch_mmu_init() * call site shared across all three supported ISAs. The VMM subsystem * (@c vmm.c) handles page-table management independently of this hook. */ void arch_mmu_init(void) { /* Stub: Sv39/Sv48 bring-up deferred */ }