Fix riscv64 boot crash: add missing kernel stack trampoline
kernel_main on riscv64 ran directly on EDK2's UEFI boot-time stack, with no dedicated stack switch — amd64 has always had a kernel_entry.S trampoline for exactly this reason (its own comment: "the FORTH interpreter + DOE experiment loop can easily exceed that depth"). aarch64 happens to get away without one because its firmware's default stack is apparently larger, but that was never a guarantee. On riscv64 the VM bootstrap's call depth (27 word-registration modules -> physics/SSM init -> Tripod capsule birth) overflowed that small stack, corrupting a return address and producing a wild jump / page fault right after vm_init_with_host() returned — reproduced consistently across the 2026-08-01 DoE campaign logs. - src/starkernel/arch/riscv64/kernel_entry.S (new): RISC-V stack-switch trampoline mirroring amd64's, giving the kernel a dedicated 2 MiB BSS stack before anything deep runs. - kernel_main.c: riscv64 now builds kernel_main_impl (invoked via the trampoline) instead of kernel_main directly, same pattern as amd64. - Makefile.starkernel: wires the new file into the riscv64 build. - uefi_loader.c: RAW_LOG() was silently a no-op on every non-amd64 arch; added a real raw-UART writer for riscv64 (QEMU virt's uart8250 at MMIO 0x10000000) so existing loader diagnostics actually produce output. Verified: all three architectures boot clean to [Hera] ok> in the required order (amd64, aarch64, riscv64); logs and DoE CSVs from these runs included. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
a852db2209
commit
736627510b
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* kernel_entry.S — kernel stack setup and entry trampoline (riscv64)
|
||||
*
|
||||
* UEFI hands control to kernel_main() running on EDK2's own boot-time stack.
|
||||
* Unlike amd64 (see arch/amd64/kernel_entry.S, which has carried this same
|
||||
* trampoline since early on), riscv64 previously called straight into
|
||||
* kernel_main_impl with no stack switch at all. The VM bootstrap's call
|
||||
* depth (27 chained word-registration modules, physics/SSM init, capsule
|
||||
* birth, Tripod fleet spawn) overflowed EDK2's riscv64 boot stack partway
|
||||
* through vm_init_with_host()'s return, corrupting the return address and
|
||||
* producing a wild jump shortly after — observed as a load/instruction
|
||||
* page fault (scause 0xd or 0xc) whose exact address drifted between
|
||||
* builds while the crash always landed in this same call region. amd64
|
||||
* never had this problem because kernel_entry.S already switches to a
|
||||
* dedicated 2 MiB BSS stack before anything deep runs; aarch64 apparently
|
||||
* gets away with EDK2's default boot stack being large enough, but that's
|
||||
* incidental, not a guarantee.
|
||||
*
|
||||
* kernel_main is the symbol the UEFI loader calls (extern in uefi_loader.c).
|
||||
* kernel_main_impl is defined in kernel_main.c and contains all the C code.
|
||||
*
|
||||
* Calling convention: RISC-V LP64D — boot_info pointer arrives in a0 and is
|
||||
* passed unchanged to kernel_main_impl via the tail-call.
|
||||
*/
|
||||
|
||||
#include "starkernel/boot_info_offsets.h"
|
||||
|
||||
/* 2 MiB kernel stack in BSS — fallback when no dynamic stack requested */
|
||||
.section .bss
|
||||
.align 4
|
||||
.global g_kernel_stack
|
||||
g_kernel_stack:
|
||||
.space 0x200000
|
||||
g_kernel_stack_top:
|
||||
.global g_kernel_stack_top
|
||||
|
||||
.section .text
|
||||
.extern kernel_main_impl
|
||||
|
||||
/*
|
||||
* kernel_main — stack-switch trampoline, then tail-call kernel_main_impl.
|
||||
*
|
||||
* a0 = BootInfo* (RISC-V LP64D ABI — first argument register)
|
||||
*
|
||||
* Stack selection (checked in order):
|
||||
* 1. boot_info->kernel_stack_base != 0 → use loader-allocated stack
|
||||
* (top = base + size, aligned down to 16)
|
||||
* 2. Otherwise → fall back to g_kernel_stack (2 MiB BSS)
|
||||
*
|
||||
* Never returns; kernel_main_impl runs the REPL forever or panics.
|
||||
*/
|
||||
.global kernel_main
|
||||
kernel_main:
|
||||
ld t0, BOOT_INFO_KERNEL_STACK_BASE_OFFSET(a0)
|
||||
bnez t0, .Ldynamic_stack
|
||||
|
||||
/* BSS fallback */
|
||||
la t0, g_kernel_stack_top
|
||||
j .Lstack_ready
|
||||
|
||||
.Ldynamic_stack:
|
||||
ld t1, BOOT_INFO_KERNEL_STACK_SIZE_OFFSET(a0)
|
||||
add t0, t0, t1 /* top = base + size (stack grows down) */
|
||||
|
||||
.Lstack_ready:
|
||||
andi t0, t0, -16 /* 16-byte alignment (RISC-V psABI requirement) */
|
||||
mv sp, t0
|
||||
mv s0, zero /* terminate frame-pointer chain */
|
||||
tail kernel_main_impl /* a0 (boot_info) already in place */
|
||||
@@ -180,6 +180,47 @@ static void raw_serial_puts(const char *s)
|
||||
}
|
||||
}
|
||||
#define RAW_LOG(str) raw_serial_puts(str)
|
||||
|
||||
#elif defined(__riscv) || defined(ARCH_RISCV64)
|
||||
|
||||
/*
|
||||
* QEMU's riscv "virt" machine exposes a 16550-compatible UART (OpenSBI
|
||||
* reports "Platform Console Device: uart8250") as byte-addressed MMIO at
|
||||
* 0x10000000 (matches Domain0 Region03 in the OpenSBI boot banner). This
|
||||
* writes directly to the UART registers, exactly like the amd64
|
||||
* raw_serial_* helpers above, so RAW_LOG() actually produces output on
|
||||
* riscv64 instead of silently no-op'ing (previously the case for every
|
||||
* arch except amd64).
|
||||
*/
|
||||
#define UART_MMIO_BASE 0x10000000UL
|
||||
|
||||
static inline void raw_mmio_outb(uint64_t addr, uint8_t val)
|
||||
{
|
||||
*(volatile uint8_t *)addr = val;
|
||||
}
|
||||
|
||||
static inline uint8_t raw_mmio_inb(uint64_t addr)
|
||||
{
|
||||
return *(volatile uint8_t *)addr;
|
||||
}
|
||||
|
||||
static void raw_serial_putc(char c)
|
||||
{
|
||||
while ((raw_mmio_inb(UART_MMIO_BASE + 5) & 0x20) == 0) { }
|
||||
raw_mmio_outb(UART_MMIO_BASE + 0, (uint8_t)c);
|
||||
}
|
||||
|
||||
static void raw_serial_puts(const char *s)
|
||||
{
|
||||
while (*s)
|
||||
{
|
||||
char c = *s++;
|
||||
if (c == '\n') raw_serial_putc('\r');
|
||||
raw_serial_putc(c);
|
||||
}
|
||||
}
|
||||
#define RAW_LOG(str) raw_serial_puts(str)
|
||||
|
||||
#else
|
||||
#define RAW_LOG(str) ((void)0)
|
||||
#endif
|
||||
|
||||
@@ -325,10 +325,10 @@ static void print_banner(void) {
|
||||
/**
|
||||
* @brief Main kernel entry point after UEFI handoff — executes milestones M0–M6.
|
||||
*
|
||||
* On amd64, @c kernel_entry.S switches the stack from UEFI's default to a 2 MiB
|
||||
* zero-initialised BSS stack and tail-calls this function as @c kernel_main_impl.
|
||||
* On aarch64 and riscv64 the assembly trampoline is not yet implemented and the
|
||||
* UEFI loader calls @c kernel_main directly.
|
||||
* On amd64 and riscv64, @c kernel_entry.S switches the stack from UEFI's default
|
||||
* to a 2 MiB zero-initialised BSS stack and tail-calls this function as
|
||||
* @c kernel_main_impl. On aarch64 the assembly trampoline is not yet implemented
|
||||
* and the UEFI loader calls @c kernel_main directly.
|
||||
*
|
||||
* Milestone sequence:
|
||||
* - **M0 — Architecture early init** (@c arch_early_init()): On amd64, installs a
|
||||
@@ -357,7 +357,7 @@ static void print_banner(void) {
|
||||
* framebuffer descriptor, runtime services pointer, and parsed
|
||||
* kernel command-line arguments.
|
||||
*/
|
||||
#if defined(__x86_64__)
|
||||
#if defined(__x86_64__) || defined(__riscv)
|
||||
void kernel_main_impl(BootInfo *boot_info) {
|
||||
#else
|
||||
void kernel_main(BootInfo *boot_info) {
|
||||
|
||||
@@ -252,7 +252,7 @@ void vm_init_with_host(VM* vm, const VMHostServices *host)
|
||||
|
||||
/* Register Forth-79 wordset */
|
||||
register_forth79_words(vm);
|
||||
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
#if SK_PARITY_DEBUG
|
||||
console_println("[VM_BOOTSTRAP] register_forth79_words complete");
|
||||
|
||||
Reference in New Issue
Block a user