[PATCH 3/7] firmware: Add RNMI/RNME handler infrastructure
Anup Patel
anup at brainfault.org
Thu Apr 9 01:21:57 PDT 2026
On Wed, Mar 11, 2026 at 12:03 AM Evgeny Voevodin
<evvoevod at tenstorrent.com> wrote:
>
> From: Evgeny Voevodin <evvoevod at tenstorrent.com>
>
> Implement basic Resumable NMI (RNMI) and Resumable NMI Exception (RNME) handler
> support for the RISC-V Smrnmi extension.
>
> The RNME handler allows platforms to optionally handle exceptions occurring with
> MNSTATUS.NMIE=0. If a platform provides an rnme_handler callback and
> successfully handles the exception, execution is resumed. Otherwise, the
> exception is treated as a critical failure requiring immediate halt with
> diagnostic output.
>
> The RNMI handler reuses the generic trap context structure but stores MN* CSR
> values (MNEPC, MNSTATUS, MNCAUSE) into the corresponding generic fields
> (mepc, mstatus, cause) for compatibility with existing trap infrastructure.
>
> Signed-off-by: Evgeny Voevodin <evvoevod at tenstorrent.com>
> ---
> firmware/fw_base.S | 156 +++++++++++++++++++++++++++++++++++++
> include/sbi/sbi_platform.h | 9 +++
> include/sbi/sbi_trap.h | 4 +
> lib/sbi/sbi_trap.c | 70 +++++++++++++++++
> 4 files changed, 239 insertions(+)
>
> diff --git a/firmware/fw_base.S b/firmware/fw_base.S
> index cdb15429..727fc17f 100644
> --- a/firmware/fw_base.S
> +++ b/firmware/fw_base.S
> @@ -529,6 +529,45 @@ memcmp:
> csrrw tp, CSR_MSCRATCH, tp
> .endm
>
> +.macro TRAP_SAVE_AND_SETUP_SP_T0_NMI
> + /* Swap TP and MNSCRATCH (for RNMI) */
> + csrrw tp, CSR_MNSCRATCH, tp
> +
> + /* Save T0 in scratch space */
> + REG_S t0, SBI_SCRATCH_TMP1_OFFSET(tp)
> +
> + /*
> + * Set T0 to appropriate exception stack
> + *
> + * Came_From_M_Mode = ((MNSTATUS.MNPP < PRV_M) ? 1 : 0) - 1;
> + * Exception_Stack = TP ^ (Came_From_M_Mode & (SP ^ TP))
> + */
> + csrr t0, CSR_MNSTATUS
> + srl t0, t0, 11 /* MNPP is at bits 11-12 */
> + and t0, t0, PRV_M
> + slti t0, t0, PRV_M
> + add t0, t0, -1
> + xor sp, sp, tp
> + and t0, t0, sp
> + xor sp, sp, tp
> + xor t0, tp, t0
> +
> + /* Save original SP on exception stack */
> + REG_S sp, (SBI_TRAP_REGS_OFFSET(sp) - SBI_TRAP_CONTEXT_SIZE)(t0)
> +
> + /* Set SP to exception stack and make room for trap context */
> + add sp, t0, -(SBI_TRAP_CONTEXT_SIZE)
> +
> + /* Restore T0 from scratch space */
> + REG_L t0, SBI_SCRATCH_TMP1_OFFSET(tp)
> +
> + /* Save T0 on stack */
> + REG_S t0, SBI_TRAP_REGS_OFFSET(t0)(sp)
> +
> + /* Swap TP and MNSCRATCH */
> + csrrw tp, CSR_MNSCRATCH, tp
> +.endm
> +
> .macro TRAP_SAVE_MEPC_MSTATUS have_mstatush
> /* Save MEPC and MSTATUS CSRs */
> csrr t0, CSR_MEPC
> @@ -543,6 +582,20 @@ memcmp:
> .endif
> .endm
>
> +.macro TRAP_SAVE_MNEPC_MNSTATUS have_mstatush
> + /*
> + * Save MNEPC and MNSTATUS CSRs (for RNMI)
> + * Note: Trap context structure has generic field names (mepc, mstatus),
> + * we store MN* CSR values into these same structure fields.
> + */
> + csrr t0, CSR_MNEPC
> + REG_S t0, SBI_TRAP_REGS_OFFSET(mepc)(sp)
> + csrr t0, CSR_MNSTATUS
> + REG_S t0, SBI_TRAP_REGS_OFFSET(mstatus)(sp)
> + /* MNSTATUSH doesn't exist in SMRNMI spec */
> + REG_S zero, SBI_TRAP_REGS_OFFSET(mstatusH)(sp)
> +.endm
> +
> .macro TRAP_SAVE_GENERAL_REGS_EXCEPT_SP_T0
> /* Save all general regisers except SP and T0 */
> REG_S zero, SBI_TRAP_REGS_OFFSET(zero)(sp)
> @@ -606,12 +659,36 @@ memcmp:
> CLEAR_MDT t0
> .endm
>
> +.macro TRAP_SAVE_NMI_INFO
> + /*
> + * Save NMI trap info (MNCAUSE, no MNTVAL in spec)
> + * Note: Trap info structure has generic field names (cause, tval, etc.),
> + * we store MN* CSR values into these same structure fields.
> + */
> + csrr t0, CSR_MNCAUSE
> + REG_S t0, (SBI_TRAP_REGS_SIZE + SBI_TRAP_INFO_OFFSET(cause))(sp)
> + /* MNTVAL doesn't exist in SMRNMI spec */
> + REG_S zero, (SBI_TRAP_REGS_SIZE + SBI_TRAP_INFO_OFFSET(tval))(sp)
> + REG_S zero, (SBI_TRAP_REGS_SIZE + SBI_TRAP_INFO_OFFSET(tval2))(sp)
> + REG_S zero, (SBI_TRAP_REGS_SIZE + SBI_TRAP_INFO_OFFSET(tinst))(sp)
> + REG_S zero, (SBI_TRAP_REGS_SIZE + SBI_TRAP_INFO_OFFSET(gva))(sp)
> +
> + /* We are ready to take another trap, clear MDT */
> + CLEAR_MDT t0
> +.endm
> +
> .macro TRAP_CALL_C_ROUTINE
> /* Call C routine */
> add a0, sp, zero
> call sbi_trap_handler
> .endm
>
> +.macro TRAP_CALL_C_RNME_ROUTINE
> + /* Call C routine */
> + add a0, sp, zero
> + call sbi_rnme_handler
> +.endm
> +
s/TRAP_CALL_C_RNME_ROUTINE/TRAP_CALL_C_RNMI_ROUTINE/
> .macro TRAP_RESTORE_GENERAL_REGS_EXCEPT_A0_T0
> /* Restore all general regisers except A0 and T0 */
> REG_L ra, SBI_TRAP_REGS_OFFSET(ra)(a0)
> @@ -660,6 +737,19 @@ memcmp:
> csrw CSR_MEPC, t0
> .endm
>
> +.macro TRAP_RESTORE_MNEPC_MNSTATUS
> + /*
> + * Restore MNSTATUS and MNEPC CSRs (for RNMI)
> + * Note: Load from generic structure fields (mstatus, mepc) and
> + * restore to NMI-specific CSRs (MNSTATUS, MNEPC).
> + * No MNSTATUSH in SMRNMI spec.
> + */
> + REG_L t0, SBI_TRAP_REGS_OFFSET(mstatus)(a0)
> + csrw CSR_MNSTATUS, t0
> + REG_L t0, SBI_TRAP_REGS_OFFSET(mepc)(a0)
> + csrw CSR_MNEPC, t0
> +.endm
> +
> .macro TRAP_RESTORE_A0_T0
> /* Restore T0 */
> REG_L t0, SBI_TRAP_REGS_OFFSET(t0)(a0)
> @@ -724,6 +814,72 @@ _trap_handler_hyp:
>
> mret
>
> + .section .entry, "ax", %progbits
> + .align 3
> + .globl _rnmi_handler
> +_rnmi_handler:
s/_rnmi_handler/_trap_rnmi_handler/
> + /*
> + * NMI interrupt handler using MN* CSRs
> + *
> + * Context detection via MNPP (previous privilege mode):
> + * - If MNPP < M-mode: use exception stack (TP)
> + * - If MNPP == M-mode: use current stack (SP)
> + * This handles nested interrupt cases.
> + */
> + TRAP_SAVE_AND_SETUP_SP_T0_NMI
> +
> + TRAP_SAVE_MNEPC_MNSTATUS 0
> +
> + TRAP_SAVE_GENERAL_REGS_EXCEPT_SP_T0
> +
> + TRAP_SAVE_NMI_INFO
> +
> + /* Call platform RNMI handler (returns tcntx in a0) */
> + add a0, sp, zero
> + call sbi_rnmi_handler
> +
> + TRAP_RESTORE_GENERAL_REGS_EXCEPT_A0_T0
> +
> + TRAP_RESTORE_MNEPC_MNSTATUS
> +
> + TRAP_RESTORE_A0_T0
> +
> + /* mnret - return from NMI (SMRNMI extension) */
> + .word 0x70200073
> +
> + .section .entry, "ax", %progbits
> + .align 3
> + .globl _rnme_handler
> +_rnme_handler:
> + /*
> + * When SMRNMI extension is present, handles exceptions that occur while
> + * NMIE=0
> + * This can happen during early boot or during NMI handling
> + */
> +
> + /*
> + * Save context using regular M-mode trap CSRs
> + * RNME is a normal M-mode trap that happens when NMIE=0
> + * Uses MSCRATCH/TMP0 (not MNSCRATCH/TMP1) to avoid conflict with NMI handler
> + */
> + TRAP_SAVE_AND_SETUP_SP_T0
> + TRAP_SAVE_MEPC_MSTATUS 0
> + TRAP_SAVE_GENERAL_REGS_EXCEPT_SP_T0
> + TRAP_SAVE_INFO 0 0
> +
> + /* Call platform RNME exception handler (returns tcntx in a0) */
> + TRAP_CALL_C_RNME_ROUTINE
> +
> + /* Restore context using regular M-mode trap CSRs */
> + TRAP_RESTORE_GENERAL_REGS_EXCEPT_A0_T0
> +
> + TRAP_RESTORE_MEPC_MSTATUS 0
> +
> + TRAP_RESTORE_A0_T0
> +
> + /* mret - return from exception */
> + mret
> +
Drop this _rnme_handler because C handler can differentiate
between NME and NMI based on the MSB of mncause CSR.
> .section .entry, "ax", %progbits
> .align 3
> .globl _reset_regs
> diff --git a/include/sbi/sbi_platform.h b/include/sbi/sbi_platform.h
> index e65d9877..b67a5647 100644
> --- a/include/sbi/sbi_platform.h
> +++ b/include/sbi/sbi_platform.h
> @@ -149,6 +149,15 @@ struct sbi_platform_operations {
> unsigned long log2len);
> /** platform specific pmp disable on current HART */
> void (*pmp_disable)(unsigned int n);
> +
> + /** platform specific Smrnmi exception handler.
> + * Returns SBI_SUCCESS on success, error code if exception cannot be
> + * handled. */
> + int (*rnme_handler)(struct sbi_trap_context *tcntx);
> +
> + /** platform specific Smrnmi NMI handler.
> + * Returns SBI_SUCCESS on success, error code if NMI cannot be handled. */
> + int (*rnmi_handler)(struct sbi_trap_context *tcntx);
Drop these platform callbacks from this patch. We will be
extending sbi_irqchip to support NMIs. For NME, we will
can simply crash.
> };
>
> /** Platform default per-HART stack size for exception/interrupt handling */
> diff --git a/include/sbi/sbi_trap.h b/include/sbi/sbi_trap.h
> index 731a0c98..a69c0fe3 100644
> --- a/include/sbi/sbi_trap.h
> +++ b/include/sbi/sbi_trap.h
> @@ -289,6 +289,10 @@ static inline void sbi_trap_set_context(struct sbi_scratch *scratch,
>
> struct sbi_trap_context *sbi_trap_handler(struct sbi_trap_context *tcntx);
>
> +struct sbi_trap_context *sbi_rnme_handler(struct sbi_trap_context *tcntx);
> +
> +struct sbi_trap_context *sbi_rnmi_handler(struct sbi_trap_context *tcntx);
> +
Only one sbi_trap_rnmi_handler() needed here which will be
called for both NMI and NME.
> #endif
>
> #endif
> diff --git a/lib/sbi/sbi_trap.c b/lib/sbi/sbi_trap.c
> index f41db4d1..5f950009 100644
> --- a/lib/sbi/sbi_trap.c
> +++ b/lib/sbi/sbi_trap.c
> @@ -20,6 +20,7 @@
> #include <sbi/sbi_irqchip.h>
> #include <sbi/sbi_trap_ldst.h>
> #include <sbi/sbi_pmu.h>
> +#include <sbi/sbi_platform.h>
> #include <sbi/sbi_scratch.h>
> #include <sbi/sbi_sse.h>
> #include <sbi/sbi_timer.h>
> @@ -375,3 +376,72 @@ trap_done:
> sbi_trap_set_context(scratch, tcntx->prev_context);
> return tcntx;
> }
> +
> +/**
> +* Resumable NMI Exception (RNME) handler
> +*
> +* Called when exception occurs with MNSTATUS.NMIE=0.
> +*
> +* @param tcntx Trap context (saved on NMI stack)
> +* @return Never returns - always hangs
> +*/
> +struct sbi_trap_context *sbi_rnme_handler(struct sbi_trap_context *tcntx)
> +{
> + int rc;
> + const struct sbi_platform *plat = sbi_platform_thishart_ptr();
> + const struct sbi_platform_operations *ops = sbi_platform_ops(plat);
> +
> + /* Call platform-specific exception handler if registered */
> + if (ops && ops->rnme_handler) {
> + rc = ops->rnme_handler(tcntx);
> + if (!rc) {
> + /* Platform handler indicated success on handling an exception */
> + return tcntx;
> + }
> + }
> +
> + /* Print diagnostics and hang */
> + sbi_trap_error("critical: exception with NMIE=0 (RNME)", SBI_EFAIL, tcntx);
> +
> + /* sbi_trap_error() never returns, but satisfy compiler */
> + sbi_hart_hang();
> + return tcntx;
> +}
> +
> +/**
> + * Default Resumable NMI (RNMI) handler
> + *
> + * This function is called from the _rnmi_handler assembly code.
> + * It provides a simple wrapper that calls the platform-specific
> + * NMI handler if registered. If no handler is registered, it prints
> + * diagnostic information and hangs, similar to unhandled traps.
> + *
> + * Note: The trap context stores NMI CSR values (MNCAUSE, MNEPC, MNSTATUS)
> + * in the generic trap context fields (cause, mepc, mstatus).
> + *
> + * @param tcntx Pointer to trap context (saved on stack)
> + * @return Same trap context pointer (needed for restore macros)
> + */
> +struct sbi_trap_context *sbi_rnmi_handler(struct sbi_trap_context *tcntx)
> +{
> + int rc;
> + const struct sbi_platform *plat = sbi_platform_thishart_ptr();
> + const struct sbi_platform_operations *ops = sbi_platform_ops(plat);
> +
> + /* Call platform-specific NMI handler if registered */
> + if (ops && ops->rnmi_handler) {
> + rc = ops->rnmi_handler(tcntx);
> + if (rc) {
> + /* Platform handler failed to handle NMI */
> + sbi_trap_error("platform NMI handler failed", rc, tcntx);
> + }
> + return tcntx;
> + }
> +
> + /* No platform handler - treat as unhandled NMI */
> + sbi_trap_error("unhandled NMI (no platform rnmi_handler)",
> + SBI_ENOTSUPP, tcntx);
> +
> + /* Never returns */
> + return tcntx;
> +}
> --
> 2.43.0
>
>
> --
> opensbi mailing list
> opensbi at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/opensbi
Regards,
Anup
More information about the opensbi
mailing list