| /* |
| * 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/riscv_fp.h> |
| #include <sbi/sbi_error.h> |
| #include <sbi/sbi_trap_ldst.h> |
| #include <sbi/sbi_pmu.h> |
| #include <sbi/sbi_trap.h> |
| #include <sbi/sbi_unpriv.h> |
| #include <sbi/sbi_platform.h> |
| |
| /** |
| * Load emulator callback: |
| * |
| * @return rlen=success, 0=success w/o regs modification, or negative error |
| */ |
| typedef int (*sbi_trap_ld_emulator)(int rlen, union sbi_ldst_data *out_val, |
| struct sbi_trap_regs *regs, |
| const struct sbi_trap_info *orig_trap); |
| |
| /** |
| * Store emulator callback: |
| * |
| * @return wlen=success, 0=success w/o regs modification, or negative error |
| */ |
| typedef int (*sbi_trap_st_emulator)(int wlen, union sbi_ldst_data in_val, |
| struct sbi_trap_regs *regs, |
| const struct sbi_trap_info *orig_trap); |
| |
| static ulong sbi_misaligned_tinst_fixup(ulong orig_tinst, ulong new_tinst, |
| ulong addr_offset) |
| { |
| if (new_tinst == INSN_PSEUDO_VS_LOAD || |
| new_tinst == INSN_PSEUDO_VS_STORE) |
| return new_tinst; |
| else if (orig_tinst == 0) |
| return 0UL; |
| else |
| return orig_tinst | (addr_offset << SH_RS1); |
| } |
| |
| static int sbi_trap_emulate_load(struct sbi_trap_regs *regs, |
| const struct sbi_trap_info *orig_trap, |
| sbi_trap_ld_emulator emu) |
| { |
| ulong insn, insn_len; |
| union sbi_ldst_data val = { 0 }; |
| struct sbi_trap_info uptrap; |
| int rc, fp = 0, shift = 0, len = 0; |
| |
| if (orig_trap->tinst & 0x1) { |
| /* |
| * Bit[0] == 1 implies trapped instruction value is |
| * transformed instruction or custom instruction. |
| */ |
| insn = orig_trap->tinst | INSN_16BIT_MASK; |
| insn_len = (orig_trap->tinst & 0x2) ? INSN_LEN(insn) : 2; |
| } else { |
| /* |
| * Bit[0] == 0 implies trapped instruction value is |
| * zero or special value. |
| */ |
| insn = sbi_get_insn(regs->mepc, &uptrap); |
| if (uptrap.cause) { |
| return sbi_trap_redirect(regs, &uptrap); |
| } |
| insn_len = INSN_LEN(insn); |
| } |
| |
| if ((insn & INSN_MASK_LB) == INSN_MATCH_LB) { |
| len = 1; |
| shift = 8 * (sizeof(ulong) - len); |
| } else if ((insn & INSN_MASK_LBU) == INSN_MATCH_LBU) { |
| len = 1; |
| } else if ((insn & INSN_MASK_LW) == INSN_MATCH_LW) { |
| len = 4; |
| shift = 8 * (sizeof(ulong) - len); |
| #if __riscv_xlen == 64 |
| } else if ((insn & INSN_MASK_LD) == INSN_MATCH_LD) { |
| len = 8; |
| shift = 8 * (sizeof(ulong) - len); |
| } else if ((insn & INSN_MASK_LWU) == INSN_MATCH_LWU) { |
| len = 4; |
| #endif |
| #ifdef __riscv_flen |
| } else if ((insn & INSN_MASK_FLD) == INSN_MATCH_FLD) { |
| fp = 1; |
| len = 8; |
| } else if ((insn & INSN_MASK_FLW) == INSN_MATCH_FLW) { |
| fp = 1; |
| len = 4; |
| #endif |
| } else if ((insn & INSN_MASK_LH) == INSN_MATCH_LH) { |
| len = 2; |
| shift = 8 * (sizeof(ulong) - len); |
| } else if ((insn & INSN_MASK_LHU) == INSN_MATCH_LHU) { |
| len = 2; |
| #if __riscv_xlen >= 64 |
| } else if ((insn & INSN_MASK_C_LD) == INSN_MATCH_C_LD) { |
| len = 8; |
| shift = 8 * (sizeof(ulong) - len); |
| insn = RVC_RS2S(insn) << SH_RD; |
| } else if ((insn & INSN_MASK_C_LDSP) == INSN_MATCH_C_LDSP && |
| ((insn >> SH_RD) & 0x1f)) { |
| len = 8; |
| shift = 8 * (sizeof(ulong) - len); |
| #endif |
| } else if ((insn & INSN_MASK_C_LW) == INSN_MATCH_C_LW) { |
| len = 4; |
| shift = 8 * (sizeof(ulong) - len); |
| insn = RVC_RS2S(insn) << SH_RD; |
| } else if ((insn & INSN_MASK_C_LWSP) == INSN_MATCH_C_LWSP && |
| ((insn >> SH_RD) & 0x1f)) { |
| len = 4; |
| shift = 8 * (sizeof(ulong) - len); |
| #ifdef __riscv_flen |
| } else if ((insn & INSN_MASK_C_FLD) == INSN_MATCH_C_FLD) { |
| fp = 1; |
| len = 8; |
| insn = RVC_RS2S(insn) << SH_RD; |
| } else if ((insn & INSN_MASK_C_FLDSP) == INSN_MATCH_C_FLDSP) { |
| fp = 1; |
| len = 8; |
| #if __riscv_xlen == 32 |
| } else if ((insn & INSN_MASK_C_FLW) == INSN_MATCH_C_FLW) { |
| fp = 1; |
| len = 4; |
| insn = RVC_RS2S(insn) << SH_RD; |
| } else if ((insn & INSN_MASK_C_FLWSP) == INSN_MATCH_C_FLWSP) { |
| fp = 1; |
| len = 4; |
| #endif |
| #endif |
| } else if ((insn & INSN_MASK_C_LHU) == INSN_MATCH_C_LHU) { |
| len = 2; |
| insn = RVC_RS2S(insn) << SH_RD; |
| } else if ((insn & INSN_MASK_C_LH) == INSN_MATCH_C_LH) { |
| len = 2; |
| shift = 8 * (sizeof(ulong) - len); |
| insn = RVC_RS2S(insn) << SH_RD; |
| } else { |
| return sbi_trap_redirect(regs, orig_trap); |
| } |
| |
| rc = emu(len, &val, regs, orig_trap); |
| |
| if (rc <= 0) |
| return rc; |
| |
| if (!fp) |
| SET_RD(insn, regs, ((long)(val.data_ulong << shift)) >> shift); |
| #ifdef __riscv_flen |
| else if (len == 8) |
| SET_F64_RD(insn, regs, val.data_u64); |
| else |
| SET_F32_RD(insn, regs, val.data_ulong); |
| #endif |
| |
| regs->mepc += insn_len; |
| |
| return 0; |
| } |
| |
| static int sbi_trap_emulate_store(struct sbi_trap_regs *regs, |
| const struct sbi_trap_info *orig_trap, |
| sbi_trap_st_emulator emu) |
| { |
| ulong insn, insn_len; |
| union sbi_ldst_data val; |
| struct sbi_trap_info uptrap; |
| int rc, len = 0; |
| |
| if (orig_trap->tinst & 0x1) { |
| /* |
| * Bit[0] == 1 implies trapped instruction value is |
| * transformed instruction or custom instruction. |
| */ |
| insn = orig_trap->tinst | INSN_16BIT_MASK; |
| insn_len = (orig_trap->tinst & 0x2) ? INSN_LEN(insn) : 2; |
| } else { |
| /* |
| * Bit[0] == 0 implies trapped instruction value is |
| * zero or special value. |
| */ |
| insn = sbi_get_insn(regs->mepc, &uptrap); |
| if (uptrap.cause) { |
| return sbi_trap_redirect(regs, &uptrap); |
| } |
| insn_len = INSN_LEN(insn); |
| } |
| |
| val.data_ulong = GET_RS2(insn, regs); |
| |
| if ((insn & INSN_MASK_SB) == INSN_MATCH_SB) { |
| len = 1; |
| } else if ((insn & INSN_MASK_SW) == INSN_MATCH_SW) { |
| len = 4; |
| #if __riscv_xlen == 64 |
| } else if ((insn & INSN_MASK_SD) == INSN_MATCH_SD) { |
| len = 8; |
| #endif |
| #ifdef __riscv_flen |
| } else if ((insn & INSN_MASK_FSD) == INSN_MATCH_FSD) { |
| len = 8; |
| val.data_u64 = GET_F64_RS2(insn, regs); |
| } else if ((insn & INSN_MASK_FSW) == INSN_MATCH_FSW) { |
| len = 4; |
| val.data_ulong = GET_F32_RS2(insn, regs); |
| #endif |
| } else if ((insn & INSN_MASK_SH) == INSN_MATCH_SH) { |
| len = 2; |
| #if __riscv_xlen >= 64 |
| } else if ((insn & INSN_MASK_C_SD) == INSN_MATCH_C_SD) { |
| len = 8; |
| val.data_ulong = GET_RS2S(insn, regs); |
| } else if ((insn & INSN_MASK_C_SDSP) == INSN_MATCH_C_SDSP) { |
| len = 8; |
| val.data_ulong = GET_RS2C(insn, regs); |
| #endif |
| } else if ((insn & INSN_MASK_C_SW) == INSN_MATCH_C_SW) { |
| len = 4; |
| val.data_ulong = GET_RS2S(insn, regs); |
| } else if ((insn & INSN_MASK_C_SWSP) == INSN_MATCH_C_SWSP) { |
| len = 4; |
| val.data_ulong = GET_RS2C(insn, regs); |
| #ifdef __riscv_flen |
| } else if ((insn & INSN_MASK_C_FSD) == INSN_MATCH_C_FSD) { |
| len = 8; |
| val.data_u64 = GET_F64_RS2S(insn, regs); |
| } else if ((insn & INSN_MASK_C_FSDSP) == INSN_MATCH_C_FSDSP) { |
| len = 8; |
| val.data_u64 = GET_F64_RS2C(insn, regs); |
| #if __riscv_xlen == 32 |
| } else if ((insn & INSN_MASK_C_FSW) == INSN_MATCH_C_FSW) { |
| len = 4; |
| val.data_ulong = GET_F32_RS2S(insn, regs); |
| } else if ((insn & INSN_MASK_C_FSWSP) == INSN_MATCH_C_FSWSP) { |
| len = 4; |
| val.data_ulong = GET_F32_RS2C(insn, regs); |
| #endif |
| #endif |
| } else if ((insn & INSN_MASK_C_SH) == INSN_MATCH_C_SH) { |
| len = 2; |
| val.data_ulong = GET_RS2S(insn, regs); |
| } else { |
| return sbi_trap_redirect(regs, orig_trap); |
| } |
| |
| rc = emu(len, val, regs, orig_trap); |
| |
| if (rc <= 0) |
| return rc; |
| |
| regs->mepc += insn_len; |
| |
| return 0; |
| } |
| |
| static int sbi_misaligned_ld_emulator(int rlen, union sbi_ldst_data *out_val, |
| struct sbi_trap_regs *regs, |
| const struct sbi_trap_info *orig_trap) |
| { |
| struct sbi_trap_info uptrap; |
| int i; |
| |
| for (i = 0; i < rlen; i++) { |
| out_val->data_bytes[i] = |
| sbi_load_u8((void *)(orig_trap->tval + i), &uptrap); |
| if (uptrap.cause) { |
| uptrap.tinst = sbi_misaligned_tinst_fixup( |
| orig_trap->tinst, uptrap.tinst, i); |
| return sbi_trap_redirect(regs, &uptrap); |
| } |
| } |
| return rlen; |
| } |
| |
| int sbi_misaligned_load_handler(struct sbi_trap_regs *regs, |
| const struct sbi_trap_info *orig_trap) |
| { |
| return sbi_trap_emulate_load(regs, orig_trap, |
| sbi_misaligned_ld_emulator); |
| } |
| |
| static int sbi_misaligned_st_emulator(int wlen, union sbi_ldst_data in_val, |
| struct sbi_trap_regs *regs, |
| const struct sbi_trap_info *orig_trap) |
| { |
| struct sbi_trap_info uptrap; |
| int i; |
| |
| for (i = 0; i < wlen; i++) { |
| sbi_store_u8((void *)(orig_trap->tval + i), |
| in_val.data_bytes[i], &uptrap); |
| if (uptrap.cause) { |
| uptrap.tinst = sbi_misaligned_tinst_fixup( |
| orig_trap->tinst, uptrap.tinst, i); |
| return sbi_trap_redirect(regs, &uptrap); |
| } |
| } |
| return wlen; |
| } |
| |
| int sbi_misaligned_store_handler(struct sbi_trap_regs *regs, |
| const struct sbi_trap_info *orig_trap) |
| { |
| return sbi_trap_emulate_store(regs, orig_trap, |
| sbi_misaligned_st_emulator); |
| } |
| |
| static int sbi_ld_access_emulator(int rlen, union sbi_ldst_data *out_val, |
| struct sbi_trap_regs *regs, |
| const struct sbi_trap_info *orig_trap) |
| { |
| /* If fault came from M mode, just fail */ |
| if (((regs->mstatus & MSTATUS_MPP) >> MSTATUS_MPP_SHIFT) == PRV_M) |
| return SBI_EINVAL; |
| |
| /* If platform emulator failed, we redirect instead of fail */ |
| if (sbi_platform_emulate_load(sbi_platform_thishart_ptr(), rlen, |
| orig_trap->tval, out_val)) |
| return sbi_trap_redirect(regs, orig_trap); |
| |
| return rlen; |
| } |
| |
| int sbi_load_access_handler(struct sbi_trap_regs *regs, |
| const struct sbi_trap_info *orig_trap) |
| { |
| return sbi_trap_emulate_load(regs, orig_trap, sbi_ld_access_emulator); |
| } |
| |
| static int sbi_st_access_emulator(int wlen, union sbi_ldst_data in_val, |
| struct sbi_trap_regs *regs, |
| const struct sbi_trap_info *orig_trap) |
| { |
| /* If fault came from M mode, just fail */ |
| if (((regs->mstatus & MSTATUS_MPP) >> MSTATUS_MPP_SHIFT) == PRV_M) |
| return SBI_EINVAL; |
| |
| /* If platform emulator failed, we redirect instead of fail */ |
| if (sbi_platform_emulate_store(sbi_platform_thishart_ptr(), wlen, |
| orig_trap->tval, in_val)) |
| return sbi_trap_redirect(regs, orig_trap); |
| |
| return wlen; |
| } |
| |
| int sbi_store_access_handler(struct sbi_trap_regs *regs, |
| const struct sbi_trap_info *orig_trap) |
| { |
| return sbi_trap_emulate_store(regs, orig_trap, sbi_st_access_emulator); |
| } |