RISC-V: minimal SBI runtime; PolarFire SoC boots 4-CPU SMP Yocto Linux

pull/844/head
David Garske 2026-06-18 13:32:00 -07:00 committed by Daniele Lacamera
parent e718eed709
commit 6b3612f4b9
5 changed files with 1088 additions and 24 deletions

View File

@ -56,6 +56,22 @@ extern void (* const IV[])(void);
extern void main(void);
extern void reloc_trap_vector(const uint32_t *address);
#if defined(WOLFBOOT_RISCV_MMODE) && defined(WOLFBOOT_MMODE_SMODE_BOOT)
/* Minimal SBI runtime (src/riscv_sbi.c): services S-mode ecalls and the
* M-mode timer/software interrupts that back the S-mode timer and IPIs. */
extern unsigned long sbi_handle_ecall(unsigned long *regs, unsigned long epc);
extern void sbi_timer_irq(void);
extern void sbi_ipi_irq(unsigned long hartid);
extern unsigned long sbi_illegal_insn(unsigned long *regs, unsigned long epc,
unsigned long tval);
extern unsigned long sbi_misaligned_ldst(unsigned long *regs,
unsigned long epc,
unsigned long tval,
unsigned long cause);
extern void sbi_mscratch_init(unsigned long hartid);
extern void sbi_hart_mark_started(unsigned long hartid);
#endif
/* Trap state saved for debugging */
#if __riscv_xlen == 64
static uint64_t last_cause = 0, last_epc = 0, last_tval = 0;
@ -133,22 +149,104 @@ static void handle_external_interrupt(void)
}
#endif /* PLIC_BASE */
/* Legacy 3-arg weak hook. The asm trap entry now calls handle_trap_ex (the
* real dispatcher); it forwards here for interrupts it does not handle itself
* (after the in-tree SBI/PLIC handling) and for synchronous exceptions before
* halting. An out-of-tree override that returns a resume epc different from
* the faulting one keeps the extension point pre-dispatcher wolfBoot had; the
* weak default returns epc unchanged, so the in-tree path prints and halts. */
unsigned long WEAKFUNCTION handle_trap(unsigned long cause, unsigned long epc,
unsigned long tval)
{
(void)cause;
(void)tval;
return epc;
}
/* Regs-aware trap dispatch -- called directly from src/vector_riscv.S.
* Override this (also weak) to take full control including the saved
* register frame. Falls through to weak handle_trap so legacy 3-arg
* overrides still run. */
unsigned long WEAKFUNCTION handle_trap_ex(unsigned long cause, unsigned long epc,
unsigned long tval, unsigned long *regs)
{
#if defined(WOLFBOOT_RISCV_MMODE) && defined(WOLFBOOT_MMODE_SMODE_BOOT)
unsigned long ec;
#endif
last_cause = cause;
last_epc = epc;
last_tval = tval;
/* Always print and halt on synchronous exceptions to prevent
* infinite trap-mret loops that appear as silent hangs.
* NOTE: keep each printf SIMPLE (few args) to minimize the risk of
* recursive traps if wolfBoot's state is corrupted. */
#if defined(WOLFBOOT_RISCV_MMODE) && defined(WOLFBOOT_MMODE_SMODE_BOOT)
/* SBI runtime: service the S-mode environment calls and the M-mode
* timer/software interrupts that back the S-mode timer and IPIs. All
* other traps fall through to the fault handler below. */
ec = cause & MCAUSE_CAUSE;
if ((cause & MCAUSE_INT) != 0UL) {
if (ec == (unsigned long)IRQ_M_TIMER) {
sbi_timer_irq();
return epc;
}
if (ec == (unsigned long)IRQ_M_SOFT) {
unsigned long self;
__asm__ volatile("csrr %0, mhartid" : "=r"(self));
sbi_ipi_irq(self);
return epc;
}
}
else if (ec == 9UL) { /* environment call from S-mode */
return sbi_handle_ecall(regs, epc);
}
else if (ec == 2UL) {
/* Illegal instruction from S-mode OR U-mode: try the SBI
* emulation path (rdtime -- these harts have no time CSR, and
* userspace reaches it via the vDSO clock_gettime path). */
unsigned long mpp = (csr_read(mstatus) >> 11) & 3UL;
if (mpp != 3UL) { /* any non-M context */
unsigned long nepc = sbi_illegal_insn(regs, epc, tval);
if (nepc != 0UL) {
return nepc;
}
}
/* not handled: fall through to the fatal dump below */
}
else if (ec == 4UL || ec == 6UL) {
/* Misaligned load/store from S/U mode: these harts cannot
* delegate misaligned traps; firmware emulates them byte-wise
* (OpenSBI parity). */
unsigned long mpp = (csr_read(mstatus) >> 11) & 3UL;
if (mpp != 3UL) {
unsigned long nepc = sbi_misaligned_ldst(regs, epc, tval,
ec);
if (nepc != 0UL) {
return nepc;
}
}
/* not handled: fall through to the fatal dump below */
}
#endif
/* Halt on synchronous exceptions to prevent infinite trap-mret loops
* that appear as silent hangs. The diagnostic dump below is gated on
* DEBUG_BOOT: a production build halts silently and relies on the
* watchdog to reset the chip. NOTE: keep each DEBUG_BOOT printf SIMPLE
* (few args) to minimize the risk of recursive traps if wolfBoot's
* state is corrupted. */
if (!(cause & MCAUSE_INT)) {
wolfBoot_printf("TRAP: cause=%lx epc=%lx tval=%lx\n",
cause, epc, tval);
unsigned long resume;
/* Offer the synchronous exception to the legacy 3-arg hook so an
* out-of-tree platform override can service it (returning a resume
* epc different from the faulting one). The weak default returns
* epc unchanged, treated as "unhandled" -> fall through to print +
* halt (a bare resume-at-epc would spin in a silent trap-mret loop). */
resume = handle_trap(cause, epc, tval);
if (resume != epc) {
return resume;
}
#if defined(DEBUG_BOOT)
unsigned long sp_now;
wolfBoot_printf("TRAP: cause=%lx epc=%lx tval=%lx mstatus=%lx\n",
cause, epc, tval, csr_read(mstatus));
__asm__ volatile("mv %0, sp" : "=r"(sp_now));
wolfBoot_printf(" sp=%lx\n", sp_now);
#if defined(WOLFBOOT_RISCV_MMODE) && defined(TARGET_mpfs250)
@ -161,9 +259,53 @@ unsigned long WEAKFUNCTION handle_trap(unsigned long cause, unsigned long epc,
wolfBoot_printf("STACK OVERFLOW: under by %lu\n",
bottom - sp_now);
}
/* Dump saved register frame from trap_entry. Each slot is
* 8 bytes (REGBYTES); slot[N] = xN. See vector_riscv.S. */
if (regs != NULL) {
unsigned long *stk;
wolfBoot_printf(
" ra=%lx sp=%lx gp=%lx tp=%lx\n",
regs[1], regs[2], regs[3], regs[4]);
wolfBoot_printf(
" t0=%lx t1=%lx t2=%lx\n",
regs[5], regs[6], regs[7]);
wolfBoot_printf(
" s0=%lx s1=%lx\n",
regs[8], regs[9]);
wolfBoot_printf(
" a0=%lx a1=%lx a2=%lx a3=%lx\n",
regs[10], regs[11], regs[12], regs[13]);
wolfBoot_printf(
" a4=%lx a5=%lx a6=%lx a7=%lx\n",
regs[14], regs[15], regs[16], regs[17]);
wolfBoot_printf(
" s2=%lx s3=%lx s4=%lx s5=%lx\n",
regs[18], regs[19], regs[20], regs[21]);
wolfBoot_printf(
" s6=%lx s7=%lx s8=%lx s9=%lx\n",
regs[22], regs[23], regs[24], regs[25]);
wolfBoot_printf(
" s10=%lx s11=%lx\n",
regs[26], regs[27]);
wolfBoot_printf(
" t3=%lx t4=%lx t5=%lx t6=%lx\n",
regs[28], regs[29], regs[30], regs[31]);
/* The pre-trap SP is regs+32 (the 32-slot frame top). We do not
* walk memory above it: when the S-mode trap is serviced on the
* dedicated per-hart M-stack (mscratch-switched), the frame sits
* at the very top of that 4 KB stack, so bytes above it belong to
* the adjacent hart's stack, not the trapping caller. The saved
* registers above (incl. ra/sp) already capture the fault
* context. */
stk = (unsigned long *)(regs + 32);
wolfBoot_printf(" pre-trap sp=%lx\n", (unsigned long)stk);
}
#endif
#endif /* DEBUG_BOOT */
while (1) ; /* halt to prevent infinite trap-mret loop */
/* Halt: spin so the watchdog fires and resets the chip (recovery).
* Do NOT pet the WDT here -- production must reset out of a fault. */
while (1)
;
}
#ifdef PLIC_BASE
@ -180,7 +322,8 @@ unsigned long WEAKFUNCTION handle_trap(unsigned long cause, unsigned long epc,
/* Synchronous exceptions are not handled - just record them */
#endif
return epc;
/* Forward to the legacy 3-arg hook so out-of-tree overrides still run */
return handle_trap(cause, epc, tval);
}
/* ============================================================================
@ -214,7 +357,7 @@ uint64_t hal_get_timer_us(void)
return (ticks * 1000) / (rate / 1000);
}
#ifdef MMU
#if defined(MMU) || defined(WOLFBOOT_FDT)
int WEAKFUNCTION hal_dts_fixup(void* dts_addr)
{
(void)dts_addr;
@ -263,6 +406,42 @@ static void __attribute__((noreturn)) enter_smode(unsigned long entry,
);
__builtin_unreachable();
}
/* Public M->S handoff entry point. Sets up PMP, delegates S-mode traps,
* then transitions the calling hart to S-mode at entry. Used both by the
* default do_boot path and by HAL overrides that release a different hart. */
void __attribute__((noreturn))
riscv_mmode_to_smode(unsigned long entry, unsigned long hartid,
unsigned long dtb)
{
setup_pmp_for_smode();
delegate_traps_to_smode();
#if defined(WOLFBOOT_MMODE_SMODE_BOOT)
/* Install the wolfBoot SBI trap vector on this hart so S-mode ecalls and
* the M-timer/M-soft IRQs are serviced in M-mode here, and arm this
* hart's dedicated M-mode trap stack (the S-mode sp is virtual once the
* OS enables paging, so the trap entry must not store through it).
* Keep illegal-instruction traps in M-mode: rdtime is emulated there
* (no time CSR on these harts). mcounteren still exposes cycle/instret
* to S-mode. Enable M software interrupts for IPI delivery. */
csr_write(mtvec, (unsigned long)trap_vector_table);
sbi_mscratch_init(hartid);
sbi_hart_mark_started(hartid);
csr_write(medeleg, csr_read(medeleg) & ~(1UL << 2));
csr_write(mcounteren, 0x7UL);
csr_write(mie, csr_read(mie) | MIE_MSIE);
#endif
enter_smode(entry, hartid, dtb);
}
/* Weak default: hand the kernel off in S-mode on the current hart. Platforms
* that need a different topology (e.g. the MPFS E51 must release a U54
* because cpu@0 is disabled in the DTB) override this in their HAL. */
void __attribute__((weak, noreturn))
hal_smode_boot(unsigned long entry, unsigned long hartid, unsigned long dtb)
{
riscv_mmode_to_smode(entry, hartid, dtb);
}
#endif /* WOLFBOOT_RISCV_MMODE */
#if __riscv_xlen == 64
@ -275,7 +454,7 @@ unsigned long get_boot_hartid(void)
}
#endif
#ifdef MMU
#if defined(MMU) || defined(WOLFBOOT_FDT)
void do_boot(const uint32_t *app_offset, const uint32_t* dts_offset)
#else
void do_boot(const uint32_t *app_offset)
@ -284,9 +463,13 @@ void do_boot(const uint32_t *app_offset)
#if __riscv_xlen == 64
unsigned long hartid;
#endif
#ifdef MMU
#if defined(MMU) || defined(WOLFBOOT_FDT)
unsigned long dts_addr;
hal_dts_fixup((uint32_t*)dts_offset);
/* dts_offset is NULL when the loaded image was not a FIT (or had no
* flat_dt): skip the fixup and hand off with dtb=0 rather than deref. */
if (dts_offset != NULL) {
hal_dts_fixup((uint32_t*)dts_offset);
}
dts_addr = (unsigned long)dts_offset;
#elif defined(WOLFBOOT_RISCV_MMODE) || __riscv_xlen == 64
unsigned long dts_addr = 0;
@ -301,7 +484,7 @@ void do_boot(const uint32_t *app_offset)
#if __riscv_xlen == 64
wolfBoot_printf(", hartid=%lu", hartid);
#endif
#ifdef MMU
#if defined(MMU) || defined(WOLFBOOT_FDT)
wolfBoot_printf(", dts=0x%lx", dts_addr);
#endif
wolfBoot_printf("\n");
@ -312,12 +495,14 @@ void do_boot(const uint32_t *app_offset)
#ifdef WOLFBOOT_RISCV_MMODE
#ifdef WOLFBOOT_MMODE_SMODE_BOOT
/* M-mode -> S-mode transition for Linux boot */
wolfBoot_printf("M->S transition: entry=0x%lx\n", (unsigned long)app_offset);
setup_pmp_for_smode();
delegate_traps_to_smode();
/* M-mode -> S-mode transition for Linux boot. Default: hand off on the
* current hart. HAL may override hal_smode_boot to release a different
* hart and self-park (see hal/mpfs250.c when MPFS_DDR_INIT is set). */
wolfBoot_printf("M->S handoff requested by hart %lu: entry=0x%lx "
"dtb=0x%lx\n",
hartid, (unsigned long)app_offset, dts_addr);
/* This never returns */
enter_smode((unsigned long)app_offset, hartid, dts_addr);
hal_smode_boot((unsigned long)app_offset, hartid, dts_addr);
#else
/* Direct M-mode jump for bare-metal payloads.
* Define WOLFBOOT_MMODE_SMODE_BOOT to boot Linux via S-mode transition. */

View File

@ -159,13 +159,35 @@ _copy_params:
sd zero, 48(s11)
sd zero, 56(s11)
/* Wait for E51 to signal HLS_MAIN_HART_STARTED */
/* Wait for E51 to signal HLS_MAIN_HART_STARTED. On MPFS250 poll the
* DTIM copy of the flag, not the L2-scratch HLS: an E51 store to the
* cacheable scratchpad can be lost on dirty-line eviction (layout-
* dependent), which left the secondaries parked here until the kernel's
* HSM hart_start IPI arrived -- too late for its 1s online window.
* Other RISCV64 M-mode targets poll the generic _main_hart_hls flag
* (MPFS_DTIM_MAIN_STARTED_ADDR is MPFS250-specific). */
li t3, 0x12344321
#ifdef TARGET_mpfs250
li t1, MPFS_DTIM_MAIN_STARTED_ADDR
#else
la t1, _main_hart_hls
#endif
.L_wait_main_hart:
lwu t2, 0(t1)
bne t3, t2, .L_wait_main_hart
/* Clear any stale CLINT MSIP left by a prior boot (a warm/WDT reset does
* not clear CLINT) BEFORE arming the WFI wait, so a stale MSIP cannot fall
* the wfi loop through and resume a previous kernel handoff over
* un-reinitialized DDR. E51 sends its release IPI only after we signal
* HLS_OTHER_HART_IN_WFI below, so clearing here cannot race the real
* wakeup. */
csrr a0, mhartid
li t0, 0x02000000 /* CLINT_BASE */
slli t1, a0, 2
add t0, t0, t1
sw zero, 0(t0) /* clear stale MSIP */
li t0, 0x12345678 /* HLS_OTHER_HART_IN_WFI */
sw t0, 0(s11)
fence iorw, iorw
@ -187,6 +209,14 @@ _copy_params:
fence iorw, iorw
fence.i
/* The C code on this hart (secondary_hart_entry and the M->S release
* path) addresses small globals gp-relative; only the E51's
* .L_sram_entry path sets gp, so set it here too. */
.option push
.option norelax
la gp, __global_pointer$
.option pop
csrr a0, mhartid
mv a1, s11
la t0, secondary_hart_entry

788
src/riscv_sbi.c 100644
View File

@ -0,0 +1,788 @@
/* riscv_sbi.c
*
* Copyright (C) 2026 wolfSSL Inc.
*
* This file is part of wolfBoot.
*
* wolfBoot is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* wolfBoot is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
*/
/* Minimal RISC-V SBI (Supervisor Binary Interface) runtime for wolfBoot.
*
* When wolfBoot replaces a full firmware (e.g. HSS+OpenSBI on PolarFire
* SoC) and boots an S-mode OS (Linux), the OS issues SBI ecalls that must
* be serviced in M-mode. This provides the minimal set: BASE, TIME, IPI,
* RFENCE, HSM, DBCN + the legacy console/timer calls, enough to bring a
* RISC-V Linux kernel up to a console and timer tick.
*
* It is driven from handle_trap_ex() in src/boot_riscv.c on:
* - ecall-from-S (mcause = 9) -> sbi_handle_ecall()
* - M-timer interrupt (mcause INT|7) -> sbi_timer_irq()
* - M-soft interrupt (mcause INT|3) -> sbi_ipi_irq()
*/
#include <stdint.h>
#include "hal/riscv.h"
#include "printf.h"
#if defined(WOLFBOOT_RISCV_MMODE) && defined(WOLFBOOT_MMODE_SMODE_BOOT)
#ifdef TARGET_mpfs250
#include "hal/mpfs250.h"
#endif
/* ---- platform glue (MPFS250) ------------------------------------------- */
#ifndef CLINT_BASE
#define CLINT_BASE 0x02000000UL
#endif
/* The platform header may already provide CLINT helpers; undef to avoid a
* redefinition error, then define the exact register forms used here. */
#ifdef CLINT_MSIP
#undef CLINT_MSIP
#endif
#ifdef CLINT_MTIMECMP
#undef CLINT_MTIMECMP
#endif
#define CLINT_MSIP(h) (*(volatile uint32_t *)(CLINT_BASE + (h) * 4UL))
#define CLINT_MTIMECMP(h) (*(volatile uint64_t *)(CLINT_BASE + 0x4000UL + (h) * 8UL))
#ifndef SBI_CONSOLE_UART_BASE
#ifdef DEBUG_UART_BASE
#define SBI_CONSOLE_UART_BASE DEBUG_UART_BASE
#else
#define SBI_CONSOLE_UART_BASE 0x20000000UL /* MPFS MMUART0 */
#endif
#endif
/* MPFS MMUART: THR at +0x100, LSR at +0x14, THRE = bit 5. */
#define SBI_UART_THR (*(volatile uint8_t *)(SBI_CONSOLE_UART_BASE + 0x100UL))
#define SBI_UART_LSR (*(volatile uint8_t *)(SBI_CONSOLE_UART_BASE + 0x14UL))
#define SBI_UART_THRE 0x20U
/* ---- CSR helpers -------------------------------------------------------- */
#define csr_set_bits(csr, bits) \
__asm__ volatile("csrs " #csr ", %0" :: "r"(bits) : "memory")
#define csr_clr_bits(csr, bits) \
__asm__ volatile("csrc " #csr ", %0" :: "r"(bits) : "memory")
#define MIP_STIP (1UL << IRQ_S_TIMER) /* bit 5 */
#define MIP_SSIP (1UL << IRQ_S_SOFT) /* bit 1 */
#define MIE_MTIP (1UL << IRQ_M_TIMER) /* bit 7 */
/* ---- SBI constants ------------------------------------------------------ */
#define SBI_SUCCESS 0L
#define SBI_ERR_FAILED (-1L)
#define SBI_ERR_NOT_SUPPORTED (-2L)
#define SBI_ERR_INVALID_PARAM (-3L)
#define SBI_ERR_ALREADY_AVAIL (-6L)
/* Extension IDs (EIDs) */
#define SBI_EXT_0_1_SET_TIMER 0x00
#define SBI_EXT_0_1_CONSOLE_PUTCHAR 0x01
#define SBI_EXT_0_1_CONSOLE_GETCHAR 0x02
#define SBI_EXT_0_1_CLEAR_IPI 0x03
#define SBI_EXT_0_1_SEND_IPI 0x04
#define SBI_EXT_0_1_REMOTE_FENCE_I 0x05
#define SBI_EXT_0_1_REMOTE_SFENCE 0x06
#define SBI_EXT_0_1_REMOTE_SFENCE_ASID 0x07
#define SBI_EXT_0_1_SHUTDOWN 0x08
#define SBI_EXT_BASE 0x10
#define SBI_EXT_TIME 0x54494D45UL
#define SBI_EXT_IPI 0x00735049UL
#define SBI_EXT_RFENCE 0x52464E43UL
#define SBI_EXT_HSM 0x0048534DUL
#define SBI_EXT_SRST 0x53525354UL
#define SBI_EXT_DBCN 0x4442434EUL
/* BASE FIDs */
#define SBI_BASE_GET_SPEC_VERSION 0
#define SBI_BASE_GET_IMPL_ID 1
#define SBI_BASE_GET_IMPL_VERSION 2
#define SBI_BASE_PROBE_EXT 3
#define SBI_BASE_GET_MVENDORID 4
#define SBI_BASE_GET_MARCHID 5
#define SBI_BASE_GET_MIMPID 6
#define SBI_SPEC_VERSION ((0UL << 24) | 2UL) /* v0.2 */
/* No registry ID is assigned to wolfBoot's SBI; report a custom value that
* cannot collide with the small spec-registry IDs (0=BBL, 1=OpenSBI, 3=KVM,
* 8=PolarFire HSS, ...). 0x776F6C66 = ASCII "wolf". */
#define SBI_IMPL_ID 0x776F6C66UL /* "wolf" (custom, unregistered) */
#define SBI_IMPL_VERSION 1UL
/* HSM hart states (SBI spec) */
#define SBI_HSM_STARTED 0
#define SBI_HSM_STOPPED 1
#define SBI_HSM_START_PENDING 2
/* Register-frame indices: regs[N] == xN for the argument registers used here
* (a0/a1/a2/a6/a7). Note that under WOLFBOOT_RISCV_MMODE the trap entry
* (src/vector_riscv.S) repurposes two slots: slot 0 (x0's unused slot) holds
* mscratch-at-entry and slot 2 (x2/sp) holds the trapped sp - so regs[0] and
* regs[2] are NOT x0/x2. This file only indexes the a* slots below, which are
* unaffected. */
#define A0 10
#define A1 11
#define A2 12
#define A6 16
#define A7 17
#ifndef MPFS_NUM_HARTS
#define MPFS_NUM_HARTS 5
#endif
/* Cross-hart SBI state must NOT live in L2-scratch BSS: cacheable stores
* to the scratchpad (Zero Device) can be silently lost when the dirty
* cache line is eventually evicted, so values written by one hart vanish
* before another hart (or a later cache-cold read) sees them. Keep this
* state in the E51 DTIM instead: a small always-uncached RAM that every
* hart reads and writes coherently (the same role it has under HSS). */
#ifndef SBI_SHARED_DTIM_ADDR
#define SBI_SHARED_DTIM_ADDR 0x01000000UL /* E51 DTIM */
#endif
#define SBI_SHARED_MAGIC 0x53424921UL /* "SBI!" */
typedef struct {
volatile uint32_t init_magic;
volatile int hart_state[MPFS_NUM_HARTS];
volatile uint32_t ipi_ops[MPFS_NUM_HARTS];
} sbi_shared_state_t;
#define SBI_SHARED ((sbi_shared_state_t *)SBI_SHARED_DTIM_ADDR)
#define sbi_hart_state (SBI_SHARED->hart_state)
#define sbi_ipi_ops (SBI_SHARED->ipi_ops)
/* Per-hart IPI work flags, set by a requesting hart and consumed in the
* target hart's M-mode software-interrupt handler. */
#define SBI_IPI_OP_SSIP 1U /* inject a supervisor software interrupt */
#define SBI_IPI_OP_FENCE_I 2U /* remote instruction-stream sync */
#define SBI_IPI_OP_SFENCE 4U /* remote sfence.vma (full flush) */
/* Platform hook: release a parked hart into S-mode at saddr with a1=opaque
* (SBI HSM hart_start backend). Weak default: unsupported. */
int __attribute__((weak)) sbi_hal_hart_start(unsigned long hartid,
unsigned long saddr, unsigned long opaque)
{
(void)hartid;
(void)saddr;
(void)opaque;
return -1;
}
/* Called from the M->S release path on the hart entering S-mode. The
* first call (the boot hart, released before any other) also initializes
* the table: zeroed BSS would otherwise read as STARTED for every hart. */
void sbi_hart_mark_started(unsigned long hartid)
{
unsigned int k;
if (SBI_SHARED->init_magic != SBI_SHARED_MAGIC) {
for (k = 0; k < (unsigned int)MPFS_NUM_HARTS; k++) {
sbi_hart_state[k] = SBI_HSM_STOPPED;
sbi_ipi_ops[k] = 0;
}
__asm__ volatile("fence rw, rw" ::: "memory");
SBI_SHARED->init_magic = SBI_SHARED_MAGIC;
}
if (hartid < (unsigned long)MPFS_NUM_HARTS) {
sbi_hart_state[hartid] = SBI_HSM_STARTED;
__asm__ volatile("fence rw, rw" ::: "memory");
}
}
#define CLINT_MTIME (*(volatile uint64_t *)(CLINT_BASE + 0xBFF8UL))
#define MSTATUS_MPRV_BIT (1UL << 17)
/* Per-hart M-mode trap stacks. The trap entry (src/vector_riscv.S)
* switches to the stack armed in mscratch because the trapped S-mode
* context's sp is a virtual address once the OS enables paging.
*
* The stacks must NOT live in L2-scratch BSS: under SMP cache pressure a
* dirty frame line can be evicted mid-trap and the writeback to the
* scratchpad is lost, so the restore reads zeros (observed: the kernel
* resumed from an ecall with sp=0). Use the OS-invisible reserved DDR
* region instead (hss-buffer@103fc00000, nomap in the stock dtb -- the
* monitor carve-out under HSS). */
#ifndef SBI_MSTACK_BASE
#define SBI_MSTACK_BASE 0x103FC00000UL
#endif
#define SBI_MSTACK_SIZE 4096UL
/* Arm this hart's M-mode trap stack; call just before entering S-mode. */
void sbi_mscratch_init(unsigned long hartid)
{
if (hartid < (unsigned long)MPFS_NUM_HARTS) {
csr_write(mscratch,
SBI_MSTACK_BASE + (hartid + 1UL) * SBI_MSTACK_SIZE);
}
}
/* Copy bytes from an S-mode pointer (virtual once paging is on) into an
* M-mode buffer using the MPRV trick: with mstatus.MPRV set and MPP=S
* (true inside a trap taken from S-mode), M-mode loads are translated
* exactly like S-mode accesses. */
static void sbi_copy_from_smode(uint8_t *dst, unsigned long src,
unsigned long len)
{
unsigned long i;
csr_set_bits(mstatus, MSTATUS_MPRV_BIT);
for (i = 0; i < len; i++) {
dst[i] = ((const volatile uint8_t *)src)[i];
}
csr_clr_bits(mstatus, MSTATUS_MPRV_BIT);
}
#define MSTATUS_MXR_BIT (1UL << 19)
/* Guest-context byte accessors: with mstatus.MPRV set, M-mode memory
* accesses use the trapped (S/U) context's translation and permissions;
* in bare mode addresses pass through. MXR additionally permits reading
* execute-only pages (instruction fetch for emulation). */
static uint8_t sbi_guest_lb(unsigned long a)
{
uint8_t v;
csr_set_bits(mstatus, MSTATUS_MPRV_BIT);
v = *(const volatile uint8_t *)a;
csr_clr_bits(mstatus, MSTATUS_MPRV_BIT);
return v;
}
static void sbi_guest_sb(unsigned long a, uint8_t v)
{
csr_set_bits(mstatus, MSTATUS_MPRV_BIT);
*(volatile uint8_t *)a = v;
csr_clr_bits(mstatus, MSTATUS_MPRV_BIT);
}
static uint16_t sbi_guest_ifetch16(unsigned long a)
{
uint16_t v;
csr_set_bits(mstatus, MSTATUS_MPRV_BIT | MSTATUS_MXR_BIT);
v = *(const volatile uint16_t *)a;
csr_clr_bits(mstatus, MSTATUS_MPRV_BIT | MSTATUS_MXR_BIT);
return v;
}
/* Emulate a misaligned load (cause 4) or store (cause 6) from S/U mode.
* These harts take misaligned accesses to M-mode and expect the firmware
* to emulate them byte-wise (OpenSBI behavior). Handles the integer
* I/S-type forms and the common compressed forms; AMO and floating-point
* forms are not emulated (returns 0 -> fatal dump). Returns the
* advanced epc, or 0 if the instruction is not handled. */
unsigned long sbi_misaligned_ldst(unsigned long *regs, unsigned long epc,
unsigned long tval, unsigned long cause)
{
uint32_t insn;
unsigned long ilen;
unsigned long addr = tval;
unsigned long val = 0;
unsigned long n = 0;
unsigned long i;
uint32_t rd = 0;
uint32_t rs2 = 0;
uint32_t f3;
int sign = 0;
int store;
store = (cause == 6UL) ? 1 : 0;
insn = (uint32_t)sbi_guest_ifetch16(epc);
if ((insn & 3U) == 3U) {
insn |= ((uint32_t)sbi_guest_ifetch16(epc + 2U)) << 16;
ilen = 4U;
}
else {
ilen = 2U;
}
if (ilen == 4U) {
f3 = (insn >> 12) & 7U;
if ((store == 0) && ((insn & 0x7FU) == 0x03U)) {
/* LH/LW/LD/LHU/LWU */
rd = (insn >> 7) & 0x1FU;
switch (f3) {
case 1: n = 2; sign = 1; break;
case 2: n = 4; sign = 1; break;
case 3: n = 8; break;
case 5: n = 2; break;
case 6: n = 4; break;
default: return 0;
}
}
else if ((store != 0) && ((insn & 0x7FU) == 0x23U)) {
/* SH/SW/SD */
rs2 = (insn >> 20) & 0x1FU;
switch (f3) {
case 1: n = 2; break;
case 2: n = 4; break;
case 3: n = 8; break;
default: return 0;
}
}
else {
return 0;
}
}
else {
/* Compressed: C.LW/C.LD/C.SW/C.SD (quadrant 0, reg-reg') and the
* stack-pointer forms C.LWSP/C.LDSP/C.SWSP/C.SDSP (quadrant 2). */
uint32_t q = insn & 3U;
f3 = (insn >> 13) & 7U;
if (q == 0U) {
if ((store == 0) && (f3 == 2U || f3 == 3U)) {
rd = 8U + ((insn >> 2) & 7U);
n = (f3 == 2U) ? 4U : 8U;
sign = (f3 == 2U) ? 1 : 0;
}
else if ((store != 0) && (f3 == 6U || f3 == 7U)) {
rs2 = 8U + ((insn >> 2) & 7U);
n = (f3 == 6U) ? 4U : 8U;
}
else {
return 0;
}
}
else if (q == 2U) {
if ((store == 0) && (f3 == 2U || f3 == 3U)) {
rd = (insn >> 7) & 0x1FU;
n = (f3 == 2U) ? 4U : 8U;
sign = (f3 == 2U) ? 1 : 0;
}
else if ((store != 0) && (f3 == 6U || f3 == 7U)) {
rs2 = (insn >> 2) & 0x1FU;
n = (f3 == 6U) ? 4U : 8U;
}
else {
return 0;
}
}
else {
return 0;
}
}
if (store != 0) {
val = (rs2 != 0U) ? regs[rs2] : 0UL;
for (i = 0; i < n; i++) {
sbi_guest_sb(addr + i, (uint8_t)(val >> (8U * i)));
}
}
else {
for (i = 0; i < n; i++) {
val |= ((unsigned long)sbi_guest_lb(addr + i)) << (8U * i);
}
if (sign != 0) {
if (n == 2U) {
val = (unsigned long)(long)(short)val;
}
else if (n == 4U) {
val = (unsigned long)(long)(int)val;
}
}
if (rd != 0U) {
regs[rd] = val;
}
}
return epc + ilen;
}
/* Emulate instructions that trap as illegal from S-mode. These harts
* have no time CSR (CLINT MTIME is memory-mapped only), so the kernel's
* rdtime traps here. Returns the updated epc, or 0 if not handled. */
unsigned long sbi_illegal_insn(unsigned long *regs, unsigned long epc,
unsigned long tval)
{
unsigned long rd;
/* rdtime rd == csrrs rd, time(0xC01), x0 */
if ((tval & 0xFFFFF07FUL) == 0xC0102073UL) {
rd = (tval >> 7) & 0x1FUL;
if (rd != 0UL) {
regs[rd] = (unsigned long)CLINT_MTIME;
}
return epc + 4UL;
}
return 0UL;
}
static void sbi_putc(char c)
{
if (c == '\n') {
while ((SBI_UART_LSR & SBI_UART_THRE) == 0U) { }
SBI_UART_THR = (uint8_t)'\r';
}
while ((SBI_UART_LSR & SBI_UART_THRE) == 0U) { }
SBI_UART_THR = (uint8_t)c;
}
/* Set a hart's S-mode timer. M-mode owns the timer; we program the CLINT
* mtimecmp, drop any already-injected S-timer interrupt, and (re)enable the
* M-timer so it fires when mtime reaches the comparator. */
static void sbi_set_timer(unsigned long hartid, uint64_t stime)
{
CLINT_MTIMECMP(hartid) = stime;
csr_clr_bits(mip, MIP_STIP);
csr_set_bits(mie, MIE_MTIP);
}
/* M-timer interrupt: deliver to S-mode by setting STIP, and mask the M-timer
* until the OS reschedules via set_timer (otherwise it would re-fire). */
void sbi_timer_irq(void)
{
csr_clr_bits(mie, MIE_MTIP);
csr_set_bits(mip, MIP_STIP);
}
/* M-soft (IPI) interrupt on a RUNNING (S-mode) hart: clear the CLINT MSIP
* that woke us, perform any requested remote-fence work, and inject a
* supervisor software interrupt when an OS IPI was requested. A bare
* MSIP with no op flags is treated as an OS IPI for compatibility. */
void sbi_ipi_irq(unsigned long hartid)
{
uint32_t ops = 0;
CLINT_MSIP(hartid) = 0U;
__asm__ volatile("fence iorw, iorw" ::: "memory");
if (hartid < (unsigned long)MPFS_NUM_HARTS) {
/* Atomically read-and-clear: a remote hart may be OR-ing a new op in
* (sbi_post_ipi) concurrently with this consume. amoswap.w (rv64a)
* makes the read+clear a single operation so no posted op is lost. */
ops = __atomic_exchange_n(&sbi_ipi_ops[hartid], 0U, __ATOMIC_ACQ_REL);
}
if ((ops & SBI_IPI_OP_FENCE_I) != 0U) {
__asm__ volatile("fence.i" ::: "memory");
}
if ((ops & SBI_IPI_OP_SFENCE) != 0U) {
__asm__ volatile("sfence.vma" ::: "memory");
}
if ((ops & SBI_IPI_OP_SSIP) != 0U || ops == 0U) {
csr_set_bits(mip, MIP_SSIP);
}
}
/* Post an IPI op to every hart in (mask << base) and ring its MSIP.
* When the mask selects the calling hart: an SSIP IPI is self-delivered by
* raising our own SSIP (OpenSBI delivers self-IPIs; smp_call_function paths
* that include self rely on it), while for an RFENCE the caller has already
* performed the fence locally, so self is skipped there. */
static void sbi_post_ipi(unsigned long mask, unsigned long base,
uint32_t op, unsigned long self)
{
unsigned long i;
unsigned long h;
/* SBI v0.2: hart_mask_base == -1 selects all available harts and
* hart_mask is ignored. Normalize to (base 0, all-harts mask) so the
* loop below does not compute h = (-1)+i and skip every target. */
if (base == (unsigned long)-1) {
base = 0;
mask = (1UL << MPFS_NUM_HARTS) - 1UL;
}
for (i = 0; i < (unsigned long)MPFS_NUM_HARTS; i++) {
if ((mask & (1UL << i)) == 0UL) {
continue;
}
h = base + i;
if (h >= (unsigned long)MPFS_NUM_HARTS) {
continue;
}
if (h == self) {
if ((op & SBI_IPI_OP_SSIP) != 0U) {
csr_set_bits(mip, MIP_SSIP);
}
continue;
}
if (sbi_hart_state[h] != SBI_HSM_STARTED) {
continue; /* parked harts consume MSIP in their wake loop */
}
/* Atomic OR (amoor.w, rv64a): two harts may post to the same target
* concurrently, and the target may be consuming (sbi_ipi_irq) at the
* same time - a plain |= read-modify-write could drop an op. */
(void)__atomic_fetch_or(&sbi_ipi_ops[h], op, __ATOMIC_ACQ_REL);
__asm__ volatile("fence rw, rw" ::: "memory");
CLINT_MSIP(h) = 1U;
}
__asm__ volatile("fence iorw, iorw" ::: "memory");
}
/* Wait (bounded) for the posted fence ops to be consumed by the targets.
* The SBI remote-fence calls are synchronous; the bound guards against a
* wedged target turning into a wedged caller. */
static void sbi_wait_ipi_done(unsigned long mask, unsigned long base,
unsigned long self)
{
unsigned long i;
unsigned long h;
uint32_t spin;
/* SBI v0.2: hart_mask_base == -1 selects all harts (see sbi_post_ipi). */
if (base == (unsigned long)-1) {
base = 0;
mask = (1UL << MPFS_NUM_HARTS) - 1UL;
}
for (i = 0; i < (unsigned long)MPFS_NUM_HARTS; i++) {
if ((mask & (1UL << i)) == 0UL) {
continue;
}
h = base + i;
if (h == self || h >= (unsigned long)MPFS_NUM_HARTS ||
sbi_hart_state[h] != SBI_HSM_STARTED) {
continue;
}
spin = 10000000U;
while (sbi_ipi_ops[h] != 0U && spin > 0U) {
spin--;
}
}
}
/* Returns the (possibly advanced) PC to resume at. For ecall we skip the
* 4-byte ecall instruction. */
unsigned long sbi_handle_ecall(unsigned long *regs, unsigned long epc)
{
unsigned long eid = regs[A7];
unsigned long fid = regs[A6];
long err = SBI_SUCCESS;
unsigned long val = 0;
unsigned long hartid;
volatile uint32_t spin; /* UART-drain delay before SRST/SHUTDOWN reset */
#ifdef DEBUG_SBI
static uint32_t sbi_dbg_calls = 0;
#endif
__asm__ volatile("csrr %0, mhartid" : "=r"(hartid));
#ifdef DEBUG_SBI
/* Bring-up visibility: trace the first few ecalls so a silent kernel
* can be distinguished from a broken console path. Console and timer
* ecalls are exempted: tracing the timer path serializes every tick
* behind the UART and slows the OS dramatically. */
if (sbi_dbg_calls < 40U && eid != SBI_EXT_0_1_CONSOLE_PUTCHAR &&
eid != SBI_EXT_DBCN && eid != SBI_EXT_TIME &&
eid != SBI_EXT_0_1_SET_TIMER) {
sbi_dbg_calls++;
wolfBoot_printf("[SBI#%u h%lu] eid=0x%lx fid=%lu a0=0x%lx\n",
(unsigned)sbi_dbg_calls, hartid, eid, fid, regs[A0]);
}
#endif
switch (eid) {
case SBI_EXT_BASE:
switch (fid) {
case SBI_BASE_GET_SPEC_VERSION: val = SBI_SPEC_VERSION; break;
case SBI_BASE_GET_IMPL_ID: val = SBI_IMPL_ID; break;
case SBI_BASE_GET_IMPL_VERSION: val = SBI_IMPL_VERSION; break;
case SBI_BASE_PROBE_EXT:
/* a0 = extension id to probe; return 1 if supported. */
switch (regs[A0]) {
case SBI_EXT_BASE:
case SBI_EXT_TIME:
case SBI_EXT_IPI:
case SBI_EXT_RFENCE:
case SBI_EXT_HSM:
case SBI_EXT_SRST:
case SBI_EXT_DBCN:
val = 1; break;
default:
val = 0; break;
}
break;
case SBI_BASE_GET_MVENDORID:
case SBI_BASE_GET_MARCHID:
case SBI_BASE_GET_MIMPID:
val = 0; break;
default:
err = SBI_ERR_NOT_SUPPORTED; break;
}
break;
case SBI_EXT_TIME:
if (fid == 0) {
sbi_set_timer(hartid, (uint64_t)regs[A0]);
} else {
err = SBI_ERR_NOT_SUPPORTED;
}
break;
case SBI_EXT_IPI:
if (fid == 0) {
/* send_ipi(hart_mask, hart_mask_base) */
sbi_post_ipi(regs[A0], regs[A1], SBI_IPI_OP_SSIP, hartid);
} else {
err = SBI_ERR_NOT_SUPPORTED;
}
break;
case SBI_EXT_RFENCE: {
/* remote_fence_i(mask, base) / remote_sfence_vma(mask, base,
* start, size) / ..._asid: over-flush with full fences (legal
* per the spec) and wait for the targets to consume the op. */
uint32_t op;
if (fid == 0) {
op = SBI_IPI_OP_FENCE_I;
__asm__ volatile("fence.i" ::: "memory");
} else if (fid == 1 || fid == 2) {
op = SBI_IPI_OP_SFENCE;
__asm__ volatile("sfence.vma" ::: "memory");
} else {
err = SBI_ERR_NOT_SUPPORTED;
break;
}
sbi_post_ipi(regs[A0], regs[A1], op, hartid);
sbi_wait_ipi_done(regs[A0], regs[A1], hartid);
break;
}
case SBI_EXT_HSM:
switch (fid) {
case 0: /* hart_start(hartid, start_addr, opaque) */
if (regs[A0] >= (unsigned long)MPFS_NUM_HARTS) {
err = SBI_ERR_INVALID_PARAM;
}
else if (sbi_hart_state[regs[A0]] == SBI_HSM_STARTED) {
err = SBI_ERR_ALREADY_AVAIL;
}
else {
/* Publish START_PENDING before sbi_hal_hart_start rings the
* target MSIP: the woken target marks its own state STARTED,
* so writing PENDING afterwards could clobber it (TOCTOU).
* Roll back to STOPPED if the start request fails (only the
* pre-publish invalid-hart check can). */
sbi_hart_state[regs[A0]] = SBI_HSM_START_PENDING;
if (sbi_hal_hart_start(regs[A0], regs[A1],
regs[A2]) != 0) {
sbi_hart_state[regs[A0]] = SBI_HSM_STOPPED;
err = SBI_ERR_FAILED;
}
}
break;
case 1: /* hart_stop */
err = SBI_ERR_NOT_SUPPORTED;
break;
case 2: /* hart_get_status(hartid) */
if (regs[A0] < (unsigned long)MPFS_NUM_HARTS) {
val = (unsigned long)sbi_hart_state[regs[A0]];
} else {
err = SBI_ERR_INVALID_PARAM;
}
break;
default:
err = SBI_ERR_NOT_SUPPORTED;
break;
}
break;
case SBI_EXT_DBCN:
switch (fid) {
case 0: { /* console_write(num_bytes, base_lo, base_hi) */
unsigned long n = regs[A0];
unsigned long gp = regs[A1];
unsigned long k;
unsigned long c;
unsigned long j;
uint8_t cbuf[64];
if (n > 4096UL) {
n = 4096UL; /* bound a single call; kernel loops on val */
}
for (k = 0; k < n; k += c) {
c = n - k;
if (c > sizeof(cbuf)) {
c = sizeof(cbuf);
}
sbi_copy_from_smode(cbuf, gp + k, c);
for (j = 0; j < c; j++) {
sbi_putc((char)cbuf[j]);
}
}
val = n;
break;
}
case 2: /* console_write_byte(byte) */
sbi_putc((char)regs[A0]);
break;
case 1: /* console_read -- no input wired yet */
val = 0;
break;
default:
err = SBI_ERR_NOT_SUPPORTED;
break;
}
break;
case SBI_EXT_SRST:
/* system_reset(type, reason): announce, drain UART, then reset. */
wolfBoot_printf("[SBI] SYSTEM RESET requested: type=0x%lx "
"reason=0x%lx\n", regs[A0], regs[A1]);
for (spin = 0; spin < 20000000UL; spin++) { }
#ifdef TARGET_mpfs250
SYSREG_MSS_RESET_CR = 0xDEAD;
#endif
while (1) { }
/* ---- Legacy (v0.1) calls: return value in a0, no a1 ---- */
case SBI_EXT_0_1_SET_TIMER:
sbi_set_timer(hartid, (uint64_t)regs[A0]);
regs[A0] = 0;
return epc + 4;
case SBI_EXT_0_1_CONSOLE_PUTCHAR:
sbi_putc((char)regs[A0]);
regs[A0] = 0;
return epc + 4;
case SBI_EXT_0_1_CONSOLE_GETCHAR:
regs[A0] = (unsigned long)-1L;
return epc + 4;
case SBI_EXT_0_1_CLEAR_IPI:
csr_clr_bits(mip, MIP_SSIP);
regs[A0] = 0;
return epc + 4;
case SBI_EXT_0_1_SEND_IPI:
/* a0 = S-mode pointer to a hart mask; fetch via MPRV. */
if (regs[A0] != 0UL) {
unsigned long lmask = 0;
sbi_copy_from_smode((uint8_t *)&lmask, regs[A0],
sizeof(unsigned long));
sbi_post_ipi(lmask, 0, SBI_IPI_OP_SSIP, hartid);
}
regs[A0] = 0;
return epc + 4;
case SBI_EXT_0_1_REMOTE_FENCE_I:
case SBI_EXT_0_1_REMOTE_SFENCE:
case SBI_EXT_0_1_REMOTE_SFENCE_ASID:
__asm__ volatile("fence.i" ::: "memory");
__asm__ volatile("sfence.vma" ::: "memory");
if (regs[A0] != 0UL) {
unsigned long fmask = 0;
sbi_copy_from_smode((uint8_t *)&fmask, regs[A0],
sizeof(unsigned long));
sbi_post_ipi(fmask, 0,
(eid == SBI_EXT_0_1_REMOTE_FENCE_I) ?
SBI_IPI_OP_FENCE_I : SBI_IPI_OP_SFENCE, hartid);
sbi_wait_ipi_done(fmask, 0, hartid);
}
regs[A0] = 0;
return epc + 4;
case SBI_EXT_0_1_SHUTDOWN:
wolfBoot_printf("[SBI] legacy SHUTDOWN requested\n");
for (spin = 0; spin < 20000000UL; spin++) { }
#ifdef TARGET_mpfs250
SYSREG_MSS_RESET_CR = 0xDEAD;
#endif
while (1) { }
default:
err = SBI_ERR_NOT_SUPPORTED;
break;
}
regs[A0] = (unsigned long)err;
regs[A1] = val;
return epc + 4;
}
#endif /* WOLFBOOT_RISCV_MMODE && WOLFBOOT_MMODE_SMODE_BOOT */

View File

@ -279,8 +279,12 @@ void RAMFUNCTION wolfBoot_start(void)
#ifdef WOLFBOOT_FIXED_PARTITIONS
uint8_t p_state;
#endif
#ifdef MMU
#if defined(MMU) || defined(WOLFBOOT_FDT)
/* Passed to the 2-arg do_boot() below; NULL when there is no DTS (e.g.
* WOLFBOOT_FDT without MMU, booting a non-FIT image -> no fixup). */
uint8_t *dts_addr = NULL;
#endif
#ifdef MMU
uint32_t dts_size = 0;
#endif
#if defined(WOLFBOOT_ZYNQMP_FSBL) && defined(MMU)
@ -679,7 +683,8 @@ backup_on_failure:
(uintptr_t)dts_addr, ZYNQMP_ATF_EL2);
}
#endif
#ifdef MMU
#if defined(MMU) || defined(WOLFBOOT_FDT)
/* Match the do_boot() signature condition in src/boot_riscv.c. */
do_boot((uint32_t*)load_address,
(uint32_t*)dts_addr);
#else

View File

@ -31,11 +31,26 @@
#if __riscv_xlen == 64
/* RV64: Save all caller-saved registers and call handle_trap */
/* RV64: Save all caller-saved registers and call handle_trap_ex.
* handle_trap_ex (in src/boot_riscv.c) is the regs-aware weak hook;
* it forwards to the legacy 3-arg weak handle_trap so out-of-tree
* platform overrides keep working. */
.macro trap_entry
#ifdef WOLFBOOT_RISCV_MMODE
/* If mscratch holds a per-hart M-mode stack top (armed before mret
* into S-mode, see riscv_mmode_to_smode), switch to it: the trapped
* context's sp may be an S-mode VIRTUAL address once the OS enables
* paging, and M-mode (no translation) must not store through it.
* mscratch == 0 means an M-mode context: keep the current stack.
* (mscratch is an M-mode-only CSR; S-mode builds keep the original
* current-stack frame.) */
csrrw sp, mscratch, sp
bnez sp, 100f
csrrw sp, mscratch, sp
100:
#endif
addi sp, sp, -32 * REGBYTES
STORE x1, 1 * REGBYTES(sp)
STORE x2, 2 * REGBYTES(sp)
STORE x3, 3 * REGBYTES(sp)
STORE x4, 4 * REGBYTES(sp)
STORE x5, 5 * REGBYTES(sp)
@ -66,17 +81,54 @@
STORE x30, 30 * REGBYTES(sp)
STORE x31, 31 * REGBYTES(sp)
#ifdef WOLFBOOT_RISCV_MMODE
/* Frame slot 0 (x0's unused slot) = mscratch at entry: the trapped
* sp when we switched to the M-stack, 0 for an M-mode fallback or a
* nested trap. Zero mscratch while inside the handler so a nested
* trap keeps descending on the CURRENT stack instead of reusing the
* consumed mscratch value as a stack (which caused an infinite
* trap-in-trap recursion). Slot 2 = the trapped sp. x1 is already
* saved in slot 1, so reuse it as a scratch register. */
csrr x1, mscratch
STORE x1, 0 * REGBYTES(sp)
csrw mscratch, x0
bnez x1, 101f
addi x1, sp, 32 * REGBYTES
101:
STORE x1, 2 * REGBYTES(sp)
/* The M-mode handler's C code addresses small globals gp-relative:
* reload wolfBoot's gp (the trapped context's gp -- e.g. the OS's --
* is restored from frame slot 3 on exit). */
.option push
.option norelax
la gp, __global_pointer$
.option pop
#else
STORE x2, 2 * REGBYTES(sp)
#endif
csrr a0, MODE_PREFIX(cause)
csrr a1, MODE_PREFIX(epc)
csrr a2, MODE_PREFIX(tval)
mv a3, sp
jal handle_trap
jal handle_trap_ex
csrw MODE_PREFIX(epc), a0
.endm
.macro trap_exit
#ifdef WOLFBOOT_RISCV_MMODE
/* Re-arm mscratch with this M-stack's top, but only for the
* OUTERMOST frame of a stack-switched trap (slot 0 non-zero); nested
* frames and M-mode fallback frames leave mscratch alone. */
LOAD t0, 0 * REGBYTES(sp)
beqz t0, 102f
addi t0, sp, 32 * REGBYTES
csrw mscratch, t0
102:
#endif
LOAD x1, 1 * REGBYTES(sp)
LOAD x3, 3 * REGBYTES(sp)
LOAD x4, 4 * REGBYTES(sp)
@ -108,7 +160,11 @@
LOAD x30, 30 * REGBYTES(sp)
LOAD x31, 31 * REGBYTES(sp)
LOAD x2, 2 * REGBYTES(sp)
#ifndef WOLFBOOT_RISCV_MMODE
addi sp, sp, 32 * REGBYTES
#endif
/* M-mode: slot 2 holds the true trapped sp, so no frame-pop is
* needed; S-mode keeps the original current-stack frame layout. */
MODE_PREFIX(ret)