diff options
Diffstat (limited to 'roms/opensbi/lib/sbi/sbi_trap.c')
-rw-r--r-- | roms/opensbi/lib/sbi/sbi_trap.c | 293 |
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(); +} |