aboutsummaryrefslogtreecommitdiffstats
path: root/roms/opensbi/lib/sbi/sbi_trap.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/opensbi/lib/sbi/sbi_trap.c')
-rw-r--r--roms/opensbi/lib/sbi/sbi_trap.c293
1 files changed, 293 insertions, 0 deletions
diff --git a/roms/opensbi/lib/sbi/sbi_trap.c b/roms/opensbi/lib/sbi/sbi_trap.c
new file mode 100644
index 000000000..b7349d2c9
--- /dev/null
+++ b/roms/opensbi/lib/sbi/sbi_trap.c
@@ -0,0 +1,293 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2019 Western Digital Corporation or its affiliates.
+ *
+ * Authors:
+ * Anup Patel <anup.patel@wdc.com>
+ */
+
+#include <sbi/riscv_asm.h>
+#include <sbi/riscv_encoding.h>
+#include <sbi/sbi_console.h>
+#include <sbi/sbi_ecall.h>
+#include <sbi/sbi_error.h>
+#include <sbi/sbi_hart.h>
+#include <sbi/sbi_illegal_insn.h>
+#include <sbi/sbi_ipi.h>
+#include <sbi/sbi_misaligned_ldst.h>
+#include <sbi/sbi_scratch.h>
+#include <sbi/sbi_timer.h>
+#include <sbi/sbi_trap.h>
+
+static void __noreturn sbi_trap_error(const char *msg, int rc,
+ ulong mcause, ulong mtval, ulong mtval2,
+ ulong mtinst, struct sbi_trap_regs *regs)
+{
+ u32 hartid = current_hartid();
+
+ sbi_printf("%s: hart%d: %s (error %d)\n", __func__, hartid, msg, rc);
+ sbi_printf("%s: hart%d: mcause=0x%" PRILX " mtval=0x%" PRILX "\n",
+ __func__, hartid, mcause, mtval);
+ if (misa_extension('H')) {
+ sbi_printf("%s: hart%d: mtval2=0x%" PRILX
+ " mtinst=0x%" PRILX "\n",
+ __func__, hartid, mtval2, mtinst);
+ }
+ sbi_printf("%s: hart%d: mepc=0x%" PRILX " mstatus=0x%" PRILX "\n",
+ __func__, hartid, regs->mepc, regs->mstatus);
+ sbi_printf("%s: hart%d: %s=0x%" PRILX " %s=0x%" PRILX "\n", __func__,
+ hartid, "ra", regs->ra, "sp", regs->sp);
+ sbi_printf("%s: hart%d: %s=0x%" PRILX " %s=0x%" PRILX "\n", __func__,
+ hartid, "gp", regs->gp, "tp", regs->tp);
+ sbi_printf("%s: hart%d: %s=0x%" PRILX " %s=0x%" PRILX "\n", __func__,
+ hartid, "s0", regs->s0, "s1", regs->s1);
+ sbi_printf("%s: hart%d: %s=0x%" PRILX " %s=0x%" PRILX "\n", __func__,
+ hartid, "a0", regs->a0, "a1", regs->a1);
+ sbi_printf("%s: hart%d: %s=0x%" PRILX " %s=0x%" PRILX "\n", __func__,
+ hartid, "a2", regs->a2, "a3", regs->a3);
+ sbi_printf("%s: hart%d: %s=0x%" PRILX " %s=0x%" PRILX "\n", __func__,
+ hartid, "a4", regs->a4, "a5", regs->a5);
+ sbi_printf("%s: hart%d: %s=0x%" PRILX " %s=0x%" PRILX "\n", __func__,
+ hartid, "a6", regs->a6, "a7", regs->a7);
+ sbi_printf("%s: hart%d: %s=0x%" PRILX " %s=0x%" PRILX "\n", __func__,
+ hartid, "s2", regs->s2, "s3", regs->s3);
+ sbi_printf("%s: hart%d: %s=0x%" PRILX " %s=0x%" PRILX "\n", __func__,
+ hartid, "s4", regs->s4, "s5", regs->s5);
+ sbi_printf("%s: hart%d: %s=0x%" PRILX " %s=0x%" PRILX "\n", __func__,
+ hartid, "s6", regs->s6, "s7", regs->s7);
+ sbi_printf("%s: hart%d: %s=0x%" PRILX " %s=0x%" PRILX "\n", __func__,
+ hartid, "s8", regs->s8, "s9", regs->s9);
+ sbi_printf("%s: hart%d: %s=0x%" PRILX " %s=0x%" PRILX "\n", __func__,
+ hartid, "s10", regs->s10, "s11", regs->s11);
+ sbi_printf("%s: hart%d: %s=0x%" PRILX " %s=0x%" PRILX "\n", __func__,
+ hartid, "t0", regs->t0, "t1", regs->t1);
+ sbi_printf("%s: hart%d: %s=0x%" PRILX " %s=0x%" PRILX "\n", __func__,
+ hartid, "t2", regs->t2, "t3", regs->t3);
+ sbi_printf("%s: hart%d: %s=0x%" PRILX " %s=0x%" PRILX "\n", __func__,
+ hartid, "t4", regs->t4, "t5", regs->t5);
+ sbi_printf("%s: hart%d: %s=0x%" PRILX "\n", __func__, hartid, "t6",
+ regs->t6);
+
+ sbi_hart_hang();
+}
+
+/**
+ * Redirect trap to lower privledge mode (S-mode or U-mode)
+ *
+ * @param regs pointer to register state
+ * @param trap pointer to trap details
+ *
+ * @return 0 on success and negative error code on failure
+ */
+int sbi_trap_redirect(struct sbi_trap_regs *regs,
+ struct sbi_trap_info *trap)
+{
+ ulong hstatus, vsstatus, prev_mode;
+#if __riscv_xlen == 32
+ bool prev_virt = (regs->mstatusH & MSTATUSH_MPV) ? TRUE : FALSE;
+#else
+ bool prev_virt = (regs->mstatus & MSTATUS_MPV) ? TRUE : FALSE;
+#endif
+ /* By default, we redirect to HS-mode */
+ bool next_virt = FALSE;
+
+ /* Sanity check on previous mode */
+ prev_mode = (regs->mstatus & MSTATUS_MPP) >> MSTATUS_MPP_SHIFT;
+ if (prev_mode != PRV_S && prev_mode != PRV_U)
+ return SBI_ENOTSUPP;
+
+ /* For certain exceptions from VS/VU-mode we redirect to VS-mode */
+ if (misa_extension('H') && prev_virt) {
+ switch (trap->cause) {
+ case CAUSE_FETCH_PAGE_FAULT:
+ case CAUSE_LOAD_PAGE_FAULT:
+ case CAUSE_STORE_PAGE_FAULT:
+ next_virt = TRUE;
+ break;
+ default:
+ break;
+ };
+ }
+
+ /* Update MSTATUS MPV bits */
+#if __riscv_xlen == 32
+ regs->mstatusH &= ~MSTATUSH_MPV;
+ regs->mstatusH |= (next_virt) ? MSTATUSH_MPV : 0UL;
+#else
+ regs->mstatus &= ~MSTATUS_MPV;
+ regs->mstatus |= (next_virt) ? MSTATUS_MPV : 0UL;
+#endif
+
+ /* Update HSTATUS for VS/VU-mode to HS-mode transition */
+ if (misa_extension('H') && prev_virt && !next_virt) {
+ /* Update HSTATUS SPVP and SPV bits */
+ hstatus = csr_read(CSR_HSTATUS);
+ hstatus &= ~HSTATUS_SPVP;
+ hstatus |= (prev_mode == PRV_S) ? HSTATUS_SPVP : 0;
+ hstatus &= ~HSTATUS_SPV;
+ hstatus |= (prev_virt) ? HSTATUS_SPV : 0;
+ csr_write(CSR_HSTATUS, hstatus);
+ csr_write(CSR_HTVAL, trap->tval2);
+ csr_write(CSR_HTINST, trap->tinst);
+ }
+
+ /* Update exception related CSRs */
+ if (next_virt) {
+ /* Update VS-mode exception info */
+ csr_write(CSR_VSTVAL, trap->tval);
+ csr_write(CSR_VSEPC, trap->epc);
+ csr_write(CSR_VSCAUSE, trap->cause);
+
+ /* Set MEPC to VS-mode exception vector base */
+ regs->mepc = csr_read(CSR_VSTVEC);
+
+ /* Set MPP to VS-mode */
+ regs->mstatus &= ~MSTATUS_MPP;
+ regs->mstatus |= (PRV_S << MSTATUS_MPP_SHIFT);
+
+ /* Get VS-mode SSTATUS CSR */
+ vsstatus = csr_read(CSR_VSSTATUS);
+
+ /* Set SPP for VS-mode */
+ vsstatus &= ~SSTATUS_SPP;
+ if (prev_mode == PRV_S)
+ vsstatus |= (1UL << SSTATUS_SPP_SHIFT);
+
+ /* Set SPIE for VS-mode */
+ vsstatus &= ~SSTATUS_SPIE;
+ if (vsstatus & SSTATUS_SIE)
+ vsstatus |= (1UL << SSTATUS_SPIE_SHIFT);
+
+ /* Clear SIE for VS-mode */
+ vsstatus &= ~SSTATUS_SIE;
+
+ /* Update VS-mode SSTATUS CSR */
+ csr_write(CSR_VSSTATUS, vsstatus);
+ } else {
+ /* Update S-mode exception info */
+ csr_write(CSR_STVAL, trap->tval);
+ csr_write(CSR_SEPC, trap->epc);
+ csr_write(CSR_SCAUSE, trap->cause);
+
+ /* Set MEPC to S-mode exception vector base */
+ regs->mepc = csr_read(CSR_STVEC);
+
+ /* Set MPP to S-mode */
+ regs->mstatus &= ~MSTATUS_MPP;
+ regs->mstatus |= (PRV_S << MSTATUS_MPP_SHIFT);
+
+ /* Set SPP for S-mode */
+ regs->mstatus &= ~MSTATUS_SPP;
+ if (prev_mode == PRV_S)
+ regs->mstatus |= (1UL << MSTATUS_SPP_SHIFT);
+
+ /* Set SPIE for S-mode */
+ regs->mstatus &= ~MSTATUS_SPIE;
+ if (regs->mstatus & MSTATUS_SIE)
+ regs->mstatus |= (1UL << MSTATUS_SPIE_SHIFT);
+
+ /* Clear SIE for S-mode */
+ regs->mstatus &= ~MSTATUS_SIE;
+ }
+
+ return 0;
+}
+
+/**
+ * Handle trap/interrupt
+ *
+ * This function is called by firmware linked to OpenSBI
+ * library for handling trap/interrupt. It expects the
+ * following:
+ * 1. The 'mscratch' CSR is pointing to sbi_scratch of current HART
+ * 2. The 'mcause' CSR is having exception/interrupt cause
+ * 3. The 'mtval' CSR is having additional trap information
+ * 4. The 'mtval2' CSR is having additional trap information
+ * 5. The 'mtinst' CSR is having decoded trap instruction
+ * 6. Stack pointer (SP) is setup for current HART
+ * 7. Interrupts are disabled in MSTATUS CSR
+ *
+ * @param regs pointer to register state
+ */
+void sbi_trap_handler(struct sbi_trap_regs *regs)
+{
+ int rc = SBI_ENOTSUPP;
+ const char *msg = "trap handler failed";
+ ulong mcause = csr_read(CSR_MCAUSE);
+ ulong mtval = csr_read(CSR_MTVAL), mtval2 = 0, mtinst = 0;
+ struct sbi_trap_info trap;
+
+ if (misa_extension('H')) {
+ mtval2 = csr_read(CSR_MTVAL2);
+ mtinst = csr_read(CSR_MTINST);
+ }
+
+ if (mcause & (1UL << (__riscv_xlen - 1))) {
+ mcause &= ~(1UL << (__riscv_xlen - 1));
+ switch (mcause) {
+ case IRQ_M_TIMER:
+ sbi_timer_process();
+ break;
+ case IRQ_M_SOFT:
+ sbi_ipi_process();
+ break;
+ default:
+ msg = "unhandled external interrupt";
+ goto trap_error;
+ };
+ return;
+ }
+
+ switch (mcause) {
+ case CAUSE_ILLEGAL_INSTRUCTION:
+ rc = sbi_illegal_insn_handler(mtval, regs);
+ msg = "illegal instruction handler failed";
+ break;
+ case CAUSE_MISALIGNED_LOAD:
+ rc = sbi_misaligned_load_handler(mtval, mtval2, mtinst, regs);
+ msg = "misaligned load handler failed";
+ break;
+ case CAUSE_MISALIGNED_STORE:
+ rc = sbi_misaligned_store_handler(mtval, mtval2, mtinst, regs);
+ msg = "misaligned store handler failed";
+ break;
+ case CAUSE_SUPERVISOR_ECALL:
+ case CAUSE_MACHINE_ECALL:
+ rc = sbi_ecall_handler(regs);
+ msg = "ecall handler failed";
+ break;
+ default:
+ /* If the trap came from S or U mode, redirect it there */
+ trap.epc = regs->mepc;
+ trap.cause = mcause;
+ trap.tval = mtval;
+ trap.tval2 = mtval2;
+ trap.tinst = mtinst;
+ rc = sbi_trap_redirect(regs, &trap);
+ break;
+ };
+
+trap_error:
+ if (rc)
+ sbi_trap_error(msg, rc, mcause, mtval, mtval2, mtinst, regs);
+}
+
+typedef void (*trap_exit_t)(const struct sbi_trap_regs *regs);
+
+/**
+ * Exit trap/interrupt handling
+ *
+ * This function is called by non-firmware code to abruptly exit
+ * trap/interrupt handling and resume execution at context pointed
+ * by given register state.
+ *
+ * @param regs pointer to register state
+ */
+void __noreturn sbi_trap_exit(const struct sbi_trap_regs *regs)
+{
+ struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();
+
+ ((trap_exit_t)scratch->trap_exit)(regs);
+ __builtin_unreachable();
+}