/* * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2019 Western Digital Corporation or its affiliates. * * Authors: * Anup Patel */ #include #include #include #include #include #include #include #include #include #include #include #include static void __noreturn sbi_trap_error(const char *msg, int rc, u32 hartid, ulong mcause, ulong mtval, struct sbi_trap_regs *regs) { 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); 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 scratch pointer to sbi_scratch of current HART * @param epc error PC for lower privledge mode * @param cause exception cause for lower privledge mode * @param tval trap value for lower privledge mode * * @return 0 on success and negative error code on failure */ int sbi_trap_redirect(struct sbi_trap_regs *regs, struct sbi_scratch *scratch, ulong epc, ulong cause, ulong tval) { ulong hstatus, vsstatus, prev_mode; #if __riscv_xlen == 32 bool prev_virt = (regs->mstatusH & MSTATUSH_MPV) ? TRUE : FALSE; bool prev_stage2 = (regs->mstatusH & MSTATUSH_MTL) ? TRUE : FALSE; #else bool prev_virt = (regs->mstatus & MSTATUS_MPV) ? TRUE : FALSE; bool prev_stage2 = (regs->mstatus & MSTATUS_MTL) ? 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 && !prev_stage2) { switch (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 and MTL bits */ #if __riscv_xlen == 32 regs->mstatusH &= ~MSTATUSH_MPV; regs->mstatusH |= (next_virt) ? MSTATUSH_MPV : 0UL; regs->mstatusH &= ~MSTATUSH_MTL; #else regs->mstatus &= ~MSTATUS_MPV; regs->mstatus |= (next_virt) ? MSTATUS_MPV : 0UL; regs->mstatus &= ~MSTATUS_MTL; #endif /* Update HSTATUS for VS/VU-mode to HS-mode transition */ if (misa_extension('H') && prev_virt && !next_virt) { /* Update HSTATUS SP2P, SP2V, SPV, and STL bits */ hstatus = csr_read(CSR_HSTATUS); hstatus &= ~HSTATUS_SP2P; hstatus |= (regs->mstatus & MSTATUS_SPP) ? HSTATUS_SP2P : 0; hstatus &= ~HSTATUS_SP2V; hstatus |= (hstatus & HSTATUS_SPV) ? HSTATUS_SP2V : 0; hstatus &= ~HSTATUS_SPV; hstatus |= (prev_virt) ? HSTATUS_SPV : 0; hstatus &= ~HSTATUS_STL; hstatus |= (prev_stage2) ? HSTATUS_STL : 0; csr_write(CSR_HSTATUS, hstatus); } /* Update exception related CSRs */ if (next_virt) { /* Update VS-mode exception info */ csr_write(CSR_VSTVAL, tval); csr_write(CSR_VSEPC, epc); csr_write(CSR_VSCAUSE, 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, tval); csr_write(CSR_SEPC, epc); csr_write(CSR_SCAUSE, 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. Stack pointer (SP) is setup for current HART * 5. Interrupts are disabled in MSTATUS CSR * * @param regs pointer to register state * @param scratch pointer to sbi_scratch of current HART */ void sbi_trap_handler(struct sbi_trap_regs *regs, struct sbi_scratch *scratch) { int rc = SBI_ENOTSUPP; const char *msg = "trap handler failed"; u32 hartid = sbi_current_hartid(); ulong mcause = csr_read(CSR_MCAUSE); ulong mtval = csr_read(CSR_MTVAL); struct unpriv_trap *uptrap; if (mcause & (1UL << (__riscv_xlen - 1))) { mcause &= ~(1UL << (__riscv_xlen - 1)); switch (mcause) { case IRQ_M_TIMER: sbi_timer_process(scratch); break; case IRQ_M_SOFT: sbi_ipi_process(scratch); break; default: msg = "unhandled external interrupt"; goto trap_error; }; return; } switch (mcause) { case CAUSE_ILLEGAL_INSTRUCTION: rc = sbi_illegal_insn_handler(hartid, mcause, regs, scratch); msg = "illegal instruction handler failed"; break; case CAUSE_MISALIGNED_LOAD: rc = sbi_misaligned_load_handler(hartid, mcause, regs, scratch); msg = "misaligned load handler failed"; break; case CAUSE_MISALIGNED_STORE: rc = sbi_misaligned_store_handler(hartid, mcause, regs, scratch); msg = "misaligned store handler failed"; break; case CAUSE_SUPERVISOR_ECALL: case CAUSE_HYPERVISOR_ECALL: rc = sbi_ecall_handler(hartid, mcause, regs, scratch); msg = "ecall handler failed"; break; case CAUSE_LOAD_ACCESS: case CAUSE_STORE_ACCESS: case CAUSE_LOAD_PAGE_FAULT: case CAUSE_STORE_PAGE_FAULT: uptrap = sbi_hart_get_trap_info(scratch); if ((regs->mstatus & MSTATUS_MPRV) && uptrap) { rc = 0; regs->mepc += uptrap->ilen; uptrap->cause = mcause; uptrap->tval = mtval; } else { rc = sbi_trap_redirect(regs, scratch, regs->mepc, mcause, mtval); } msg = "page/access fault handler failed"; break; default: /* If the trap came from S or U mode, redirect it there */ rc = sbi_trap_redirect(regs, scratch, regs->mepc, mcause, mtval); break; }; trap_error: if (rc) { sbi_trap_error(msg, rc, hartid, mcause, csr_read(CSR_MTVAL), regs); } }