mirror of https://github.com/wolfSSL/wolfBoot.git
RISC-V: minimal SBI runtime; PolarFire SoC boots 4-CPU SMP Yocto Linux
parent
e718eed709
commit
6b3612f4b9
221
src/boot_riscv.c
221
src/boot_riscv.c
|
|
@ -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. */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue